这里写目录标题
Verilog语法知识
1.Verilog数据类型
Net型变量,相当于硬件电路中的各种物理连接,其特点是输出的值紧跟输入值的变化而变化,一般为wire型
Variable型变量,可以保存上次写入的数据,一般对应硬件上的一个触发器或者锁存器等存储单元,综合时会感觉被赋值情况来具体确定映射成连线还是存储单元,有reg型
2.向量有两种:标量类向量(scalared)和向量类向量(vectored)
3. ==
和===
的区别:
- 两者都是相等或比较运算符。
==
测检查二值逻辑相等,而===
运算符测试四值逻辑相等。 ==
比较四值逻辑,如果出现X或者Z,则结果为X。===
比较四值逻辑,如果出现X或Z,则结果为0或1,能够正确的进行比较。
4.位拼接运算符{ }
可以用来重复信号的某些位
5.Always敏感信号列表可以使用通配符“*”,表示该过程语句中的所有输入信号变量
6.连续赋值assign,主要用于对wire型变量赋值
过程赋值always,initial,多用于对reg型变量赋值
非阻塞赋值 <= 并发执行
阻塞赋值 = 顺序执行
7.case、casez、casex的区别
8.条件编译语句:`ifdef、else、endif``
9.Verilog行为语句的可综合性
10: 运算符优先级别
11: a[MSB:LSB]、a[BASE - : WIDTH]、a[BASE + : WIDTH]
For example:
reg [31: 0] big_vect;
reg [0 :31] little_vect;
reg [63: 0] dword;
integer sel;
big_vect[ 0 +: 8] // == big_vect[ 7 : 0]
big_vect[15 -: 8] // == big_vect[15 : 8]
little_vect[ 0 +: 8] // == little_vect[0 : 7]
little_vect[15 -: 8] // == little_vect[8 :15]
dword[8*sel +: 8] // variable part-select with fixed width
Q:锁存器
module latch(Enable,D,Q,Qbar)
input D, Enable;
output Q, Qbar;
reg Q, Qbar;
always @ (D or Enable)
begin
if(Enable) begin Q <= D; Qbar <= ~D; end
end
endmodule
Q:D触发器
基本D触发器
module D_FF(clk,D,Q,Qbar)
input D, clk;
output Q, Qbar;
reg Q, Qbar; //在always语句中被赋值的信号要声明为reg类型
always @ (posedge clk)
begin Q <= D; Qbar <= ~D; end
endmodule
带异步清0、异步置1的D触发器
module D_FF(Q,Qbar,D,clk,set,reset)
input D,clk,set,reset;
output Q, Qbar;
reg Q, Qbar; //寄存器定义
always @ (posedge clk or negedge set or negedge reset)
begin
if(!reset) begin Q <= 0; Qbar <= 1; end //异步清0,低有效
else if(!set) begin Q <= 1; Qbar <= 0; end //异步置1,低有效
else begin Q <= D; Qbar <= ~D; end
end
endmodule
带同步清0、同步置1的D触发器
module D_FF(Q,Qbar,D,clk,set,reset)
input D,clk,set,reset;
output Q, Qbar;
reg Q, Qbar; //寄存器定义
always @ (posedge clk)
begin
if(reset) begin Q <= 0; Qbar <= 1; end //同步清0,高有效
else if(set) begin Q <= 1; Qbar <= 0; end //同步置1,高有效
else begin Q <= D; Qbar <= ~D; end
end
endmodule
Q:消除毛刺
将传输过来的信号经过两级触发器就可以消除毛刺。(这是我自己采用的方式:这种方式消除毛刺是需要满足一定条件的,并不能保证一定可以消除)
module a(clk,data,q_out)
input clk,data;
output reg q_out;
reg q1;
always@(posedge clk)
begin
q1<=data;
q_out<=q1;
end
endmodule
Q:同步复位和异步复位
同步复位
即如果复位信号有效,则只能在时钟上升沿让电路复位。对应写法为:
always @ (posedge clk) begin
if (!rst_n)
xxxx;
end
//注意,在此always块中,敏感量只有一个,即clk的上升沿,此含义是,只有在clk的上升沿才能执行always块,否则不执行。
//于是如果复位信号有效,也只能等到clk上升沿才能执行always块,才能使电路复位!
异步复位
复位信号不受时钟的控制,只要复位信号有效,那么电路就会复位。
always @ (posedge clk or negedge rst_n) begin
if (!rst_n)
xxxx;
else if (xx) begin
xxxx;
xxxx;
end
end
//注意,在此always块中,敏感量为两个,一个是clk的上升沿(posedge clk),一个是复位信号rst_n的下降沿(negedge rst_n),
//当复位信号下降沿出现时,不论clk在什么状态,都执行always块,即复位!
异步复位同步释放
由于异步复位信号与时钟无必然联系,两者都是独立的,所以复位信号的拉高将有一定的概率导致电路出现亚稳态。
对于亚稳态的处理,通常是利用同步器进行同步,使其输出能够受到时钟clk的控制。同步器通常采用 “打两拍” 的方式,通过两个D触发器,最终得到与时钟同步的复位信号。
module system_ctrl(
input wire clk,
input wire rst_n, //复位信号,低电平有效
input wire din, //输入信号
output reg dout //输出信号
);
reg sys_rst_n_r; //触发器打拍中间信号
reg sys_rst_n; //进行同步处理后的复位信号
always @(posedge clk or negedge rst_n) begin //对复位信号进行同步处理
if(!rst_n) begin
sys_rst_n_r <= 0;
sys_rst_n <= 0;
end
else begin
sys_rst_n_r <= 1;
sys_rst_n <= sys_rst_n_r;
end
end
always @(posedge clk or negedge sys_rst_n) begin //使用同步处理后的复位信号
if(!sys_rst_n)
dout <= 0;
else
dout <= din;
end
endmodule
Q:边沿检测
- 将输入信号打两拍
- 将第二拍的信号取反与第一拍信号相与,检测上升沿
- 将第一拍信号取反并与第二拍信号相与,检测下降沿
- 得到的高电平就是指示信号
…
reg d0;
reg d1;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
d0 <= 0;
d1 <= 0;
end
else begin
d0 <= din;
d1 <= d0;
end
end
assign rising_edge = d0 & (~d1); //上升沿
assign falling_edge = (~d0) & d1; //下降沿
assign double_edge = d0 ^ d1; //双边沿
Q:握手信号
- 使用握手协议方式处理跨时钟域数据传输,只需要对双方的握手信号(req和ack)分别使用脉冲检测方法进行同步。在具体实现中,假设req、ack、data总线在初始化时都处于无效状态,发送域先把数据放入总线,随后发送有效的req信号给接收域。接收域在检测到有效的req信号后锁存数据总线,然后回送一个有效的ack信号表示读取完成应答。发送域在检测到有效ack信号后撤销当前的req信号,接收域在检测到req撤销后也相应撤销ack信号,此时完成一次正常握手通信。此后,发送域可以继续开始下一次握手通信,如此循环。该方式能够使接收到的数据稳定可靠,有效的避免了亚稳态的出现,但控制信号握手检测会消耗通信双方较多的时间。
module handshack(
input clk,
input rst_n,
input req, //请求信号,高电平有效
input[7:0] datain,
output ack, //应答信号,高电平有效
output[7:0] dataout
);
//req上升沿检测
reg reqr1, reqr2, reqr3;
always @(posedge clk or negedge rst_n)
if(!rst_n) begin
reqr1 <= 1'b0;
reqr2 <= 1'b0;
reqr3 <= 1'b0;
end
else begin
reqr1 <= req;
reqr2 <= reqr1;
reqr3 <= reqr2;
end
//pos_req2比pos_req1延后一个时钟周期,确保数据被稳定锁存
wire pos_req1 = reqr1 & ~reqr2; //req上升沿标志位,高有效一个时钟周期
wire pos_req2 = reqr2 & ~reqr3; //req上升沿标志位,高有效一个时钟周期
//数据锁存
reg[7:0] dataoutr;
always @(posedge clk or negedge rst_n)
if(!rst_n)
dataoutr <= 8'h00;
else if(pos_req1) //检测到req有效后锁存输入数据
dataoutr <= datain;
assign dataout = dataoutr;
//产生应答信号ack
reg ackr;
always @(posedge clk or negedge rst_n)
if(!rst_n)
ackr <= 1'b0;
else if(pos_req2)
ackr <= 1'b1;
else if(!req)
ackr <= 1'b0;
assign ack = ackr;
endmodule
Q:脉冲展宽(单bit慢采快)
module Sync_Pulse(
input clka,
input clkb,
input rst_n,
input pulse_ina, //脉冲或电平信号都可以
output pulse_outb, //脉冲信号
output signal_outb //电平信号
);
reg signal_a;
reg signal_a_r1;
reg signal_a_r2;
reg signal_b;
reg signal_b_r1;
//a时钟域生成展宽信号
always @(posedge clka or negedge rst_n)begin
if(!rst_n)begin
signal_a <= 1'b0;
end
else if(pulse_ina) begin //检测到脉冲
signal_a <= 1'b1; //拉高
end
else if(signal_a_r2) begin //同步到b后同步回a
signal_a <= 1'b0; //拉低,展宽使命完成
end
end
//展宽信号同步到b时钟域再同步回a时钟域
always @(posedge clkb or negedge rst_n)begin
if(!rst_n)begin
signal_b <= 1'b0;
signal_b_r1 <= 1'b0;
end
else begin
signal_b <= signal_a;
signal_b_r1 <= signal_b;
end
end
always @(posedge clka or negedge rst_n)begin
if(!rst_n)begin
signal_a_r1 <= 1'b0;
signal_a_r2 <= 1'b0;
end
else begin
signal_a_r1 <= signal_b_r1;
signal_a_r2 <= signal_a_r1;
end
end
//脉冲信号输出,上升沿检测
assign pulse_outb = ~signal_b_r1 & signal_b;
//电平信号输出,b时钟域展宽信号
assign signal_outb = signal_b_r1;
endmodule
Q:二进制与格雷码的转换
二进制转格雷码
assign gray_code = (bin_code>>1) ^ bin_code;
格雷码转二进制
always @ (gray_code) begin
bin_code[length-1]=gray_code[length-1];
for(i=length-2; i>=0; i--)
bin_code[i]=bin_code[i+1] ^ gray_code[i];
end
Q:二进制和BCD码的转换
二进制转BCD码
module bin2bcd(ena,bin,bcd); //ena为使能端,binary为待转换的二进制数,bcd为转换后的BCD码
input ena,binary;
parameter SIZE = 8; //SIZE为二进制数所占的位数,可根据需要进行扩展
input binary, ena; //ena高电平有效,低电平时bcd=0
output bcd;
wire ena;
wire [SIZE-1:0] binary;
reg [SIZE-1:0] bin;
reg [SIZE+3:0] bcd; // bcd的长度应根据实际情况进行修改
reg [SIZE+3:0] result; //result的长度=bcd的长度
always@(binary or ena)
begin
bin = binary;
result = 0;
if(ena == 0)
bcd <= 0;
else
begin
repeat(SIZE-1) //使用repeat语句进行循环计算
begin
result[0] = bin[SIZE-1];
if(result[3:0] > 4)
result[3:0] = result[3:0] + 4'd3;
if(result[7:4] > 4)
result[7:4] = result[7:4] + 4'd3;
if(result[11:8] > 4)
result[11:8] = result[11:8] + 4'd3; //扩展时应参照此三条if语句续写
result = result << 1;
bin = bin << 1;
end
result[0] = bin[SIZE-1];
bcd <= result;
end
end
endmodule
BCD码转二进制
module bcd2bin(
input wire clk,
input wire rst,
input wire[11:0] bcd,
output wire [9:0]binary //12位的BCD码最大为999,用10就可以完全表示
);
reg[9:0] temp5,temp4,temp3,temp2,temp1,temp0;
always@(posedge clk or negedge rst)
if(!rst) begin
temp5 = 0;
temp4 = 0;
temp3 = 0;
temp2 = 0;
temp1 = 0;
temp0 = 0;
end
else begin //100=(64+32+4)=(2^6+2^5+2^2); 10=(8+2)=(2^3+2^1);
temp5 <= bcd[11:8] << 6;
temp4 <= bcd[11:8] << 5;
temp3 <= bcd[11:8] << 2;
temp2 <= bcd[7:4] << 3;
temp1 <= bcd[7:4] << 1;
temp0 <= bcd[3:0];
end
assign binary=temp5+temp4+temp3+temp2+temp1+temp0;
endmodule
Q:串并转换
串并转换的原理是:新输入的位值成为原来数据的最低位,将原来数据的最高位舍去,这里可以通过一个简单的“连接符”就能搞定。
module serial_pal( //四位串并转换程序
input clk,en,rst_n,din,
output[3:0] dout
);
reg[3:0] cout;
always @ (posedge clk or negedge rst_n)
begin
if(!rst)
dout<=4'b0;
else if(en)
dout<={dout[2:0],din};
else
dout<=dout;
end
endmodule
Q:并串转换
并串转换的原理是:先将四位数据暂存于一个四位寄存器器中,然后左移输出到一位输出端口,这里通过一个"移位"指令就可以了。
module pal_serial( //四位并串转换
input clk,rst_n,load,
input[3:0] din,
output dout
);
reg[3:0] databuff;
always @ (posedge clk or negedge rst or posedge load)
begin
if(!rst_n)
databuff<=4'b0;
else if(load)
databuff<=din;
else
//databuff<={databuff[2:0],1'b0};
databuff<=databuff<<1; //将寄存器内的值左移,依次读出
end
assign dout=databuff[3];
endmodule
Q:加法器
四位全加器
module adder4(a,b,ci,s,co);
input ci;
input [3:0] a,b;
output co;
output [3:0] s;
assign {co,s}=a+b+ci;
endmodule
利用半加器设计一个16位全加器
Q:计数器
10进制计数器
module counter10(clk,rst,count);
input clk,rst;
output [3:0] count;
reg [3:0] count;
always@(posedge clk)
begin
if(!rst)
count<=0;
else if(count>=4’d9)
count<=0;
else
count<=count+1;
end
endmodule
Q:比较器
//如果1>2,比较结果compare_flag输出为1,否则输出为零
//flag为1代表作为有符号数比较,0代表作为无符号数比较
module compg(
input [31:0] data1,
input [31:0] data2,
input flag,
output reg compare_flag
);
always @(data1 or data2 or flag)
begin
if(flag==1) begin
if(data1[31]==1&&data2[31]==0)
compare_flag=0;
else if(data1[31]==0&&data2[31]==1)
compare_flag=1;
else if((data1[31]==1)&&(data2[31]==1))
compare_flag = (data1<data2) ? 1 : 0;
else
compare_flag = (data1>data2) ? 1 : 0 ;
end
else if(flag==0) begin
compare_flag = (data1>data2) ? 1 : 0 ;
end
else begin
compare_flag =1'bz; end
end
endmodule
Q:分频器
D触发器实现二分频
module div2(clk,rst,clk_out);
input clk,rst;
output reg clk_out;
always@(posedge clk)
begin
if(!rst)
clk_out <=0;
else
clk_out <=~ clk_out;
end
endmodule
现实工程设计中一般不采用这样的方式来设计,二分频一般通过DCM来实现。通过DCM得到的分频信号没有相位差。
任意偶数(2N)分频
module clk_divider(
input clk,
input rst_n,
output clk_2N,
);
parameter N=3;
reg [5:0] count=6'b0;
reg clk_2N_r=1'b0;
// 实现模N计数器(从0计数到N-1)
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
count <= 6'b0;
else if(count == 2*N-1)
count <= 6'b0;
else
count <= count + 6'b1;
end
// 计数器计到(N-1)将输出时钟翻转(0 - N-1是N个数)
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
clk_2N_r <= 1'b0;
else if(count == 0 || count == N)
clk_2N_r <= ~clk_2N_r;
else
clk_2N_r <= clk_2N_r;
end
//2N分频时钟输出
assign clk_2N=clk_2N_r;
endmodule
奇数(2N+1)分频
module clk_divider(
input clk,
input rst_n,
output clk_2N_1,
);
parameter N=3;
reg [5:0] count1=6'b0,count2=6'b0;
reg clk1=1'b0,clk2=1'b0;
// 对上升沿计数2N+1个
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
count1<=6'b0;
else if(count1==2*N)
count1<=6'b0;
else
count1<=count1+6'b1;
end
// 中间时钟clk1
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
clk1<=6'b0;
else if(count1==0||count1==N)
//else if(count1==0||count1==2*N)
clk1<=~clk1;
else
clk1<=clk1;
end
// 将原始时钟翻转
assign clk_inv = ~clk;
// 对下降沿计数2N+1个
always@(posedge clk_inv or negedge rst_n)
begin
if(!rst_n)
count2<=6'b0;
else if(count2==2*N)
count2<=6'b0;
else
count2<=count2+6'b1;
end
// 中间时钟clk2
always@(posedge clk_inv or negedge rst_n)
begin
if(!rst_n)
clk2<=1'b0;
else if(count2==0||count2==N)
//else if(count2==0||count2==2*N)
clk2<=~clk2;
else
clk2<=clk2;
end
// 2N+1分频时钟输出
assign clk_2N_1 = clk1 | clk2; //计数到0或N时翻转,使用或
//assign clk_2N_1 = clk1 & clk2; //计数到0或2N时翻转,使用与
endmodule
小数分频
比如8.7分频。因为没办法用计数器表示0.7这种数字,所以就用一个等效的概念来进行8.7分频,原时钟87个周期的总时间等于分频后的时钟10个周期的总时间。
先做3次8分频得到时钟周期数是24,再做7次9(8加1)分频得到时钟周期数63,总共就87个时钟周期;在这87个时钟周期里面分频时钟跳变20次总共10个周期。原理可以用下图来概括。
如下图所示:一个小数分频器就由两部分组成:ZN和ZN+1为分频系数的多路分频器,还有一个ACC计数器。
分频器在输入信号enout=0的时候是ZN分频;
分频器在输入信号enout=1的时候是ZN+1分频;
ACC计数器的作用是对ZN分频和ZN+1分频的次数计数,对于一位小数计数总次数为10(两位小数为100…),输出信号enout决定下次是ZN分频还是ZN+1分频。
(1)ZN/ZN+1多路分频器设计
ZN/ZN+1分频器的设计包含了偶分频器和奇分频器,首先定义ZN/ZN+1分频器的模块名字MDIV。下图是MDIV的引脚信号的名字,及功能定义:
MDIV模块的代码如下:
module mdiv(en,clk,rst,zn,clkn,clkout);
parameter NUM=7;
input en,clk,rst;
input [NUM:0] zn;
output clkn,clkout;
wire en,clk,rst;
wire [NUM:0] zn;
reg clkn;
wire clkout;
reg clkn1;
reg [NUM:0] cnt;
reg clk_neg;
assign clkout = zn ? (en ? clkn&clk_neg : clkn) :
(en ? clkn : clkn|clkn1);
always@(posedge clk or negedge rst)
begin
if(rst) begin
cnt <= {NUM{1'b0}};
clkn <= 1'b0;
end
else begin
if(en && cnt==zn+1 || !en && cnt==zn) begin
cnt<={NUM{1'b0}};
clkn <= ~clkn;
end
else if(cnt == zn >> 1) begin
cnt <= cnt+1'b1;
clkn <= ~clkn;
end
else begin
cnt <= cnt+1'b1;
clkn <= ~clkn;
end
end
end
always@(negedge clk or negedge rst)
begin
if(rst)
clk_neg <= 1'b0;
else
clk_neg <= clkn;
end
always@(negedge clk or negedge rst)
begin
if(rst)
clkn1 <= 1'b0;
else if(en && cnt==zn+1 || !en && cnt==zn)
clkn1 <= ~clkn1;
else if(cnt==zn>>1)
clkn1 <= ~clkn1;
else
clkn1 <= clkn1;
end
endmodule
(2)ACC计数器设计
ACC计数器就是控制做N次ZN分频和M次ZN+1次分频,具体控制过程可以分为以下几种情况:
第1种情况 :先做N次ZN分频,再做M次ZN+1次分频;
第2种情况: 先做M次ZN+1次分频,再做N次ZN分频;
第3种情况 :把N次ZN分频平均插入到M次ZN+1分频中;
第4种情况 :把M次ZN+1次分频平均插入到N次ZN分频中。
组合N次ZN分频和M次ZN+1次分频的情况很多。第1、2种情况前后时钟频率不太均匀,因此相位抖动比较大;
第3、4种情况前后时钟频率均匀性稍好,因此相位抖动会减小。
下面以8.7分频为例子设计ACC计数器模块名ACCT,下图为模块ACCT的引脚:
8.7分频的原理是用3次8分频和7次9分频的对应的时钟总时间来等效原时钟87个周期的总时间。
下图选用前面所述的第3种情况,把3次8分频平均地插入到7次9分频中,这个过程也叫混频。
采用第3种情况设计ACCT的Verilog代码代码如下所示:
module acct(clk,rst,ckn,enout);
input clk,rst,ckn;
output enout;
//pin
wire clk,rst,ckn;
reg enout;
//intwire
wire ckn_go;
reg ckn_d;
reg[4:0] cnt;
//采样器
assign ckn_go = ~ckn & ckn_d;
always@(posedge clk or negedge rst)
if(rst)
ckn_d <= 1'b0;
else
ckn_d <= ckn;
//计数器
always@(posedge clk or negedge rst)
if(rst) begin
cnt <= 5'h00;
end
else if(cnt == 10) begin
cnt <= 5'h00;
end
else begin
cnt <= ckn_go ? cnt+1 : cnt;
end
//混频器
always@(posedge clk or posedge rst)
begin
if(rst)
enout <= 1'b0;
else
case(cnt)
5'd0,5'd1,5'd2,5'd4,5'd5,5'd7,5'd8 : enout <= 1'b1;
default : enout <= 1'b1;
endcase
end
endmodule
(3)8.7分频器设计
完成了模块MDIV 和ACCT之后,就可以用组成一个8.7分频器,这个分频器的模块名是FENDIV,框图如下所示:
Verilog代码如下:
module fendiv(clk,rst,clkout);
input clk,rst;
output clkout;
//pin
wire clk,rst,clkout;
//intwire
wire enout,clkn;
acct XACCT(.clk(clk),.rst(rst),.ckn(clkn),.enout(enout));
mdiv XMDIV(.en(enout),.clk(clk),.rst(rst),.zn(7),.clkn(clkn),.clkout(clkout));
endmodule
Q:二倍频
module twice(clk, clk_out );
input clk;
output clk_out;
wire clk_temp;
wire d_outn;
reg d_out=0;
assign clk_temp = clk ^ d_out ;
assign clk_out = clk_temp ;
assign d_outn = ~d_out ;
always@(posedge clk_temp)
begin
d_out <= d_outn ;
end
endmodule
Q:FIFO
FIFO是英文First In First Out 的缩写,是一种先进先出的数据缓存器,它与普通存储器的区别是没有外部读写地址线,这样使用起来非常简单,但缺点就是只能顺序写入数据,顺序的读出数据, 其数据地址由内部读写指针自动加1完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。
作用: FIFO一般用于不同时钟域之间的数据传输,比如FIFO的一端是AD数据采集, 另一端是计算机的PCI总线,假设其AD采集的速率为16位 100K SPS,那么每秒的数据量为100K×16bit=1.6Mbps,而PCI总线的速度为33MHz,总线宽度32bit,其最大传输速率为 1056Mbps,在两个不同的时钟域间就可以采用FIFO来作为数据缓冲。另外对于不同宽度的数据接口也可以用FIFO,例如单片机位8位数据输出,而 DSP可能是16位数据输入,在单片机与DSP连接时就可以使用FIFO来达到数据匹配的目的。
分类:根据FIFO工作的时钟域,可以将FIFO分为同步FIFO和异步FIFO。同步FIFO是指读时钟和写时钟为同一个时钟。在时钟沿来临时同时发生读写操作。异步FIFO是指读写时钟不一致,读写时钟是互相独立的。
若输入输出总线为同一时钟域,FIFO只是作为缓存使用,用同步FIFO即可,此时,FIFO在同一时钟下工作,FIFO的写使能、读使能、满信号、空信号、输入输出数据等各种信号都在同一时钟沿打入或输出。
若输入输出为不同时钟域,FIFO作时钟协同作用,需要采用异步FIFO,此时,FIFO在读与写分别在各自时钟下工作,FIFO的写使能、写满信号、输入数据等各种输入信号都在同一输入时钟沿打入或输出。读使能、读空信号、输出数据等各种输出信号都在同一输出时钟沿打入或输出。
设计:FIFO设计的难点在于怎样判断FIFO的空/满状态。为了保证数据正确的写入或读出,而不发生溢出或读空的状态出现,必须保证FIFO在满的情况下,不能进行写操作。在空的状态下不能进行读操作。怎样判断FIFO的满/空就成了FIFO设计的核心问题。
读写指针的工作原理
- 读指针:总是指向下一个将要被写入的单元,复位时,指向第1个单元(编号为0)。
- 写指针:总是指向当前要被读出的数据,复位时,指向第1个单元(编号为0)
FIFO的“空”/“满”检测
FIFO设计的关键:产生可靠的FIFO读写指针和生成FIFO“空”/“满”状态标志。
当读写指针相等时,表明FIFO为空,这种情况发生在复位操作时,或者当读指针读出FIFO中最后一个字后,追赶上了写指针时,如下左图所示:
当读写指针再次相等时,表明FIFO为满,这种情况发生在,当写指针转了一圈,折回来(wrapped around)又追上了读指针,如上面右图:
为了区分到底是满状态还是空状态,可以采用以下方法:
在指针中添加一个额外的位(extra bit),当写指针增加并越过最后一个FIFO地址时,就将写指针这个未用的MSB加1,其它位回零。对读指针也进行同样的操作。此时,对于深度为2^n的FIFO,需要的读/写指针位宽为(n+1)位,如对于深度为8的FIFO,需要采用4bit的计数器,0000~1000、1001~1111,MSB作为折回标志位,而低3位作为地址指针。
如果两个指针的MSB不同,说明写指针比读指针多折回了一次;如r_addr=0000,而w_addr = 1000,为满。
如果两个指针的MSB相同,则说明两个指针折回的次数相等。其余位相等,说明FIFO为空。
同步FIFO
module fifo(datain, rd, wr, rst, clk, dataout, full, empty);
input [7:0] datain;
input rd, wr, rst, clk;
output [7:0] dataout;
output full, empty;
wire [7:0] dataout;
reg full_in, empty_in;
reg [7:0] mem [15:0];
reg [3:0] rp, wp;
assign full = full_in;
assign empty = empty_in;
// 输出数据
assign dataout = mem[rp];
// 输入数据
always@(posedge clk) begin
if(wr && ~full_in)
mem[wp]<=datain;
end
// 写指针增加
always@(posedge clk or negedge rst) begin
if(!rst)
wp<=0;
else begin
if(wr && ~full_in)
wp<= wp+1'b1;
end
end
// 读指针增加
always@(posedge clk or negedge rst) begin
if(!rst)
rp <= 0;
else begin
if(rd && ~empty_in)
rp <= rp + 1'b1;
end
end
// 产生写满信号
always@(posedge clk or negedge rst) begin
if(!rst)
full_in <= 1'b0;
else begin
if( (~rd && wr)&&((wp==rp-1)||(rp==4'h0&&wp==4'hf)))
full_in <= 1'b1;
else if(full_in && rd)
full_in <= 1'b0;
end
end
// 产生读空信号
always@(posedge clk or negedge rst) begin
if(!rst)
empty_in <= 1'b1;
else begin
if((rd&&~wr)&&(rp==wp-1 || (rp==4'hf&&wp==4'h0)))
empty_in<=1'b1;
else if(empty_in && wr)
empty_in<=1'b0;
end
end
endmodule
异步FIFO
(1)由于是异步FIFO的设计,读写时钟不一样,在产生读空信号和写满信号时,会涉及到跨时钟域的问题,如何解决?
跨时钟域的问题:由于读指针是属于读时钟域的,写指针是属于写时钟域的,而异步FIFO的读写时钟域不同,是异步的,要是将读时钟域的读指针与写时钟域的写指针不做任何处理直接比较肯定是错误的,因此我们需要进行同步处理以后仔进行比较
解决方法:加两级寄存器同步 + 格雷码(目的都是消除亚稳态)
- 使用异步信号进行使用的时候,好的设计都会对异步信号进行同步处理,同步一般采用多级D触发器级联处理,如下图。这种模型大部分资料都说的是第一级寄存器产生亚稳态后,第二级寄存器稳定输出概率为90%,第三极寄存器稳定输出的概率为99%,如果亚稳态跟随电路一直传递下去,那就会另自我修护能力较弱的系统直接崩溃。
- 将一个二进制的计数值从一个时钟域同步到另一个时钟域的时候很容易出现问题,因为采用二进制计数器时所有位都可能同时变化,在同一个时钟沿同步多个信号的变化会产生亚稳态问题。而使用格雷码只有一位变化,因此在两个时钟域间同步多个位不会产生问题。所以需要一个二进制到gray码的转换电路,将地址值转换为相应的gray码,然后将该gray码同步到另一个时钟域进行对比,作为空满状态的检测。
那么,多位二进制码如何转化为格雷码?
换一种描述方法:
verilog代码实现就一句:assign gray_code = (bin_code>>1) ^ bin_code;
注意:当异步FIFO的地址线为4位时,其深度可以是13或者14等非16的数吗?
答案是不可以,因为使用的是格雷码,深度需要为2^n,这样才能保证第一个码和最后一个码也是相差1bit。
(2)在格雷码域如何判断空与满?
-
空标志是由数据读取引起的,所以空标志是同步与读时钟域的;
-
满标志是由数据写入引起的,所以空标志是同步与写时钟域的;
-
为了有效获得empty情况,将写数据的地址转换成格雷码从写时钟域同步到读时钟域,需要注意empty中的" 虚空 “情况,即由于写地址同步到读时钟域是可能滞后于当前的真实写入地址的,所以可能在empty信号有效时,但是FIFO中此时还有数据,即为” 虚空 “。但” 虚空 "不会造成传输错误。
-
full标志也类似,将读数据的地址转换成格雷码从读时钟域同步到写时钟域。由于格雷码的位宽相较于用于读写操作的二进制地址宽一位,而该位恰好可以用来表示" 写操作比读操作多跑了一圈 ",所以读写地址的格雷码
这里就可以得出结论:
- 判断读空时:需要读时钟域的格雷码rgray_next和被同步到读时钟域的写指针rd2_wp每一位完全相同;
- 判断写满时:需要写时钟域的格雷码wgray_next和被同步到写时钟域的读指针wr2_rp高两位不相同,其余各位完全相同;
(3)Verilog实现
module fifo #(parameter WSIZE = 8; parameter DSIZE = 32;)
(
input wr_clk,
input rst,
input wr_en,
input [WSIZE-1 : 0]din,
input rd_clk,
input rd_en,
output [WSIZE-1 : 0]dout,
output reg rempty,
output reg wfull
);
// 定义变量
reg [WSIZE-1 :0] mem [DSIZE-1 : 0];
reg [WSIZE-1 : 0] waddr,raddr;
reg [WSIZE : 0] wbin,rbin,wbin_next,rbin_next;
reg [WSIZE : 0] wgray_next,rgray_next;
reg [WSIZE : 0] wp,rp;
reg [WSIZE : 0] wr1_rp,wr2_rp,rd1_wp,rd2_wp;
wire rempty_val,wfull_val;
// 输出数据
assign dout = mem[raddr];
// 输入数据
always@(posedge wr_clk)
if(wr_en && !wfull)
mem[waddr] <= din;
// 产生存储实体的读地址raddr
// 将普通二进制转化为格雷码,并赋给读指针rp
always@(posedge rd_clk or negedge rst_n)
if(!rst_n)
{rbin,rp} <= 0;
else
{rbin,rp} <= {rbin_next,rgray_next};
assign raddr = rbin[WSIZE-1 : 0];
assign rbin_next = rbin + (rd_en & ~rempty);
assign rgray_next = rbin_next ^ (rbin_next >> 1);
// 产生存储实体的写地址waddr
// 将普通二进制转化为格雷码,并赋给写指针wp
always@(posedge wr_clk or negedge rst_n)
if(!rst_n)
{wbin,wp} <= 0;
else
{wbin,wp} <= {wbin_next,wgray_next};
assign waddr = wbin[WSIZE-1 : 0];
assign wbin_next = wbin + (wr_en & ~wfull);
assign wgray_next = wbin_next ^ (wbin_next >> 1);
// 将读指针rp同步到写时钟域
always@(posedge wr_clk or negedge rst_n)
if(!rst_n)
{wr2_rp,wr1_rp} <= 0;
else
{wr2_rp,wr1_rp} <= {wr1_rp,rp};
// 将写指针wp同步到读时钟域
always@(posedge rd_clk or negedge rst_n)
if(!rst_n)
{rd2_wp,rd1_wp} <= 0;
else
{rd2_wp,rd1_wp} <= {rd1_wp,wp};
// 产生读空信号rempty
assign rempty_val = (rd2_wp == rgray_next);
always@(posedge rd_clk or negedge rst_n)
if(rst_n)
rempty <= 1'b1;
else
rempty <= rempty_val;
// 产生写满信号wfull
assign wfull_val = ((~(wr2_rp[WSIZE : WSIZE-1]),wr2_rp[WSIZE-2 : 0]) == wgray_next);
always@(posedge wr_clk or negedge rst_n)
if(!rst_n)
wfull <= 1'b0;
else
wfull <= wfull_val;
endmodule
Q:FIFO深度计算
Q:有限状态机(FSM)
参考文章
Verilog HDL 的语句块都是并行执行的, 但是在很多情况下,我们希望执行按照顺序的方式进行,而状态机就可以很好的实现顺序执行。
有限状态机(Finite State Machine, FSM) 又简称为状态机, 是我们用Verilog HDL 描述数字电路的重要组成方式。状态机分为moore 型和mealy 型。
Q:状态机分类
- Moore 状态机的输出仅与当前状态值有关, 且只在时钟边沿到来时才会有状态变化。
- Mealy 状态机的输出不仅与当前状态值有关, 而且与当前输入值有关。
体现在状态转移图上就是,moore机的输出在状态圆圈内,mealy机的输出在转移曲线上
体现在verilog代码中就是,moore机的最后输出逻辑只判断state,mealy机的输出逻辑中判断state && input
状态转移图
从它们的特征来看,Mealy状态机要比Moore状态机少一个状态。
以一个序列检测器为例,检测到输入信号11时输出z为1,其他时候为0。用Moore型FSM实现需要用到三个状态(A,B,C)。而用Mealy型FSM实现则只需要两个状态(A,B)。这是因为Moore型FSM的输出只由状态变量决定,要想输出z=1,必须有C状态形成,即寄存器中的两个1都打进去后才可以,输出z=1会在下一个有效沿到来的时候被赋值。而Mealy型FSM的输出是由输入和状态变量共同决定的。状态在B的时候如果输入为1,则直接以组合电路输出z=1,不需要等到下个有效沿到来,从而也就不需要第三个状态C。
Q:状态机描述方法
状态机的设计基本上采取 always 块加上 case 语句的结构,一般分为三种描述方式,即一段式、二段式、三段式。
- 一段式
指的是在一个 always块内使用时序逻辑既描述状态的转移,同时也描述数据的输出;
优点:采用一个过程块的编程方法由于通过寄存器完成逻辑输出而不容易产生毛刺。
缺点:不符合将时序逻辑和组合逻辑分开描述的 Coding Style,整个代码不清晰,不利于维护修改,状态记忆和逻辑输出都由寄存器实现,消耗面积资源大,且因为使用nonblocking去描述输出逻辑,所以要提前一个clk去判断,容易弄错。
(时序逻辑,用 “<=” 非阻塞赋值)
(组合逻辑,用 “=” 阻塞赋值)
eg:
always@(posedge clk or negedge rst) //时序逻辑和组合逻辑混杂在一起
if(!rst) begin
state <= S0;
out = 0;
end
else begin
case(state)
S0: begin
state <= (in) ? S0 : s1;
out = ??? ;
end
S1: begin
state = (in) ? S1 : s2;
out = ??? ;
end
S2: .........................
endcase
end
一段式写法可以概括为如下图描述的结构。
- 二段式
指一个always 块使用时序逻辑描述状态转移,另外一个 always 块使用组合逻辑描述数据输出以及判断状态转移条件。
优点:两个过程块有面积和时序的优势
缺点:组合逻辑输出会产生毛刺,如果输出作为一种控制信号或者使能信号,输出毛刺会带来致命的错误;由于状态机的输出向量必须由状态向量译码,增加了状态向量到输出的延时;由于组合逻辑输出占用了部分时钟周期,即增加了它驱动下一个模块的输入延迟,因此不利于系统的综合优化。
eg:
//第一个always块描述状态转移(时序逻辑)
always@(posedge clk or negedge rst)
begin
if (!nrst)
CS <= IDLE;
else
CS <=NS;
end
//第二个always块描述状态输出以及判断状态转移(组合逻辑)
always@(CS or input)
begin
NS = xxx;
out = 000;
case(CS)
S0: begin
NS = (in) ? S0 : s1;
out = ??? ;
end
S1: begin
NS = (in) ? S1 : s2;
out = ??? ;
end
S2:......................
endcase
end
两段式写法可以概括为如下图描述的结构
- 三段式
使用三个 always 块,一个 always 模块采用时序逻辑描述状态转移,一个 always块采用组合逻辑判断状态转移条件,描述状态转移规律,另一个 always 块描述状态输出(可以用组合逻辑,也可以用时序逻辑)。
eg:
//第一个always块描述状态转移(时序逻辑)
always@(posedge clk or negedge rst)
begin
if (!rst)
CS <= IDLE;
else
CS <=NS;
end
//第二个always块判断状态转移(组合逻辑)
always@(CS or input)
begin
NS = xxx;
case(CS)
S0: NS = (in) ? S0 : s1;
S1: NS = (in) ? S1 : s2;
S2: ......................
endcase
end
//第三个always块描述状态输出(这里用了时序逻辑)
always(posedge clk or negedge rst)
begin
if(!rst)
out <= 0;
else begin
case(NS) //注意此处的判断条件是下一状态“NS”
S0: out <= ;
S1: out <= ;
S2: .........
endcase
end
end
三段式写法可以概括为如下图描述的结构
由上图可知:当一个clk上升沿到来时,时序逻辑方面,原来的次态变为现态,并根据次态给出状态机输出,组合逻辑方面,根据现态和输入得到新的次态,并根据新的次态得到新的状态输出。
两段式 FSM 描述方法虽然有很多好处,但是它有一个明显的缺点就是其输出一般使用组合逻辑描述,而组合逻辑易产生毛刺等不稳定因素,并且在 FPGA/CPLD 等逻辑器件中过多的组合逻辑会影响实现的速率(这点与 ASIC 设计不同)。所以在上面我们特别提到了在两段式 FSM 描述方法中,如果时序允许插入一个额外的时钟节拍,则尽量在在后级电路对FSM 的组合逻辑输出用寄存器寄存一个节拍,则可以有效地消除毛刺。但是很多情况下,设计并不允许额外的节拍插入(Latency),此时,解决方法就是采用 3 段式 FSM 描述方法。三段式描述方法与两段式描述方法相比,关键在于使用同步时序逻辑寄存 FSM 的输出。
那这是为什么呢?——为什么三段式解决了消除毛刺的问题?
答案:三段式巧妙地根据下一状态的判断,解决了不改变时序要求的前提下用寄存器做状态输出的问题,也就是在第三个always块中case的判断条件是"NS",而不是"CS"。从三段式的原理图中可以看到,其输出模块直接接的是"下一状态"这根线,在clk到来之前,两个时序逻辑都已经准备好了数据,时钟触发之后两个时序逻辑同步输出数据,在不改变时序要求的情况下,状态输出经过了寄存器,消除了毛刺。但如果其输出模块接的是"当前状态"这根线(在程序中来说就是第三个always块中case的判断条件是"CS"),那么clk到来之前,前一时序逻辑准备好了数据,而后一时序逻辑的输入需要经过前一时序逻辑的输出延时和组合逻辑延时这两部分时间才能到达寄存器,时钟触发之后,很有可能会导致不满足时序要求而产生输出错误。
其实输出模块接"当前状态"这根线,本质上和二段式插入中一个额外的时钟节拍类似,如果时序允许,是可以的,如果时序不允许,那么这种方法不起作用。
三段式描述方法与两段式描述相比,虽然代码结构复杂了一些,但是换来的优势是使 FSM 做到了同步寄存器输出,消除了组合逻辑输出的不稳定与毛刺的隐患,而且更利于时序路径分组,一般来说在 FPGA/CPLD 等可编程逻辑器件上的综合与布局布线效果更佳。
三种状态机性能比较
Q:两段式建模和三段式建模的关系
从代码上看,三段式建模的前两段与两段式建模完全相同,仅仅多了一段寄存器 FSM 输出。一般来说,使用寄存器输出可以改善输出的时序条件,还能避免组合电路的毛刺,所以是更为推荐的描述方式。但是电路设计不是一成不变的,在某些情况下,两段式结构比三段式结构更有优势。请大家再分析一下两段式和三段式的结构图,细心的读者会发现,两段式用状态寄存器分割了两部分组合逻辑(状态转移条件组合逻辑和输出组合逻辑);而三段式结构中,从输入到寄存器状态输出的路径上,要经过两部分组合逻辑(状态转移条件组合逻辑和输出组合逻辑),从时序上,这两部分组合逻辑完全可以看为一体。这样这条路径的组合逻辑就比较繁杂,该路径的时序相对紧张。也就是说,两段式建模中用状态寄存器分割了组合逻辑,而三段式将寄存器移到组合逻辑的最后端。如果寄存器前的组合逻辑过于复杂,势必会成为整个设计的关键路径,此时就不宜再使用三段式建模,而要使用两段式建模。解决两段式建模组合逻辑输出产生毛刺的方法是,额外的在 FSM 后级电路插入寄存器,调整时序,完成功能。
Q:状态编码
状态机所包含的N种状态通常需要用某种编码方式来表示,即状态编码。
常用的编码方式有:
- 顺序二进制编码
- 格雷码编码
- 独热码编码
二进制编码是最紧密的编码,优点在于它使用状态向量的位数最少,因此需要的触发器也就少,节约了逻辑资源,但在实际应用中,往往需要较多组合逻辑对状态向量进行解码以产生输出,因此节约资源的效果并不明显。二进制编码还存在一个缺点就是从一个状态转换到另一个状态时,可能有多个bit位发生变化,瞬变次数多,容易产生毛刺。
格雷码编码在相邻状态的转换中,每次只有一个bit位发生变化,减少了产生毛刺和一些暂态的可能,但当有很多状态跳转时,需要合理的分配状态编码并保证每个状态跳转与状态编码唯一对应。
独热码是指对任意给定的状态,状态向量中仅有一位为"1"而其余位都为"0",因此在状态比较时仅仅需要比较一个 bit,一定程度上简化了比较逻辑,减少了毛刺产生的概率。独热码状态机的速度与其状态数量无关,仅仅取决于状态跳转的数量。独热码状态机还具有设计简单,修改灵活,易于调试,易于综合,易于寻找关键路径,易于进行静态时序分析等优点。
在物理实现时,N状态的状态机需要N个触发器,虽然增加了触发器的使用量,但由于状态译码简单,节省和简化了组合逻辑电路。FPGA器件由于寄存器数量多而逻辑资源紧张,采用独热码编码可以有效提高FPGA资源的利用率和电路的速度。
独热码有很多无效状态,应确保状态机一旦进入无效状态时,可以立即跳转到确定的已知状态以避免死锁现象的出现。
Q:需要注意的点
1:n 段式 FSM描述方法强调的是一种建模思路,绝不是简单的 always 语法块个数。
2:一个完备的状态机(健壮性强)应该具备初始化状态和默认状态。当芯片加电或者复位后,状态机应该能够自动将所有判断条件复位,并进入初始化状态。
3:状态编码的定义可以用 parameter 定义,但是不推荐使用`define 宏定义的方式,因为’define 宏定义在编译时自动替换整个设计中所定义的宏,而parameter 仅仅定义模块内部的参数,定义的参数不会与模块外的其他状态机混淆。
4:如果使用 2 段式 FSM 描述 Mealy 状态机,输出逻辑可以用"?语句"描述,或者使用 case 语句判断转移条件与输入信号即可。如果输出条件比较复杂,而且多个状态共用某些输出,则建议使用 task/endtask 将输出封装起来,达到模块复用的目的。
5:为了避免不必要的竞争冒险,不论是做两段式还是三段式 FSM 描述时,必须遵循时序逻辑 always 模块使用非阻塞赋值“<=”,即当前状态向下一状态时序转移,和寄存 FSM 输出等时序 always 模块中都要使用非阻塞赋值;而组合逻辑 always 模块使用阻塞赋值“=”,即状态转移条件判断,组合逻辑输出等always 模块中都要使用阻塞赋值。
6:Full Case 与 Parallel Case 综合属性
所谓 Full Case 是指:FSM 的所有编码向量都可以与 case 结构的某个分支或 default 默认情况匹配起来。如果一个 FSM 的状态编码是 8bit,则对应的256 个状态编码(全状态编码是 n 2 个)都可以与 case 的某个分支或者 default映射起来。
所谓 Parallel Case 是指:在 case 结构中,每个 case 的判断条件表达式,有且仅有唯一的 case 语句的分支(与之对应,即两者关系是一一对应关系。
目前知名综合器如 Synplify Pro、Precision RTL 和 Synopys 综合工具等都支持“ synthesis full_case”和“ synthesis parallel_case”这些综合约束属性,合理使用 Full Case 约束属性,可以增强设计的安全性;合理使用 Parallel Case约束属性,可以改善状态机译码逻辑。但是设计者必须具体情况具体分析,对于有的设计,不当使用这两条语句,会占用大量逻辑资源,并恶化 FSM 的时序表现。
Q:状态机典型题目
序列检测器
售货机
交通信号灯
1:检测序列10110?
输入x,输出z
- s0–没有检测到序列
- s1–检测到1
- s2–检测到10
- s3–检测到101
- s4–检测到1011
- s5–检测到10110
状态机如下:mealy机
module seq_detector(clk,rst,x,z);
input x,clock;
input reset; //active high
output z;
reg [2:0] CS, NS;
parameter s0=3'b000,
s1=3'b001,
s2=3'b010,
s3=3'b011,
s4=3'b100;
always @ (posedge clk or negedge rst)
begin
if (!rst)
CS <= s0;
else
CS <=NS;
end
always @ (x or CS)
case(CS)
s0: NS = (x) ? s1 : s0;
s1: NS = (x) ? s1 : s2;
s2: NS = (x) ? s3 : s0;
s3: NS = (x) ? s4 : s2;
s4: NS = (x) ? s1 : s2;
default: NS = s0;
endcase
always @ (x or CS)
case(CS)
s4: z = (x) ? 1'b1 : 1'b0;
s0,s1,s2,s3: z = 1'b0;
default: z = 1'b0;
endcase
endmodule
2:接受1,2,5分钱的卖报机,每份报纸5分钱,有找零。
(1)确定输入,din[2:0]: din[0] = 1表示1分钱,din[1] = 1表示2分钱,din[2] = 1表示5分钱。
(2)确定输出,dout[1:0],dout[1] = 1表示找零1分钱,dout[0] = 1表示输出一份报纸。
(3)确定状态数画出状态转移图,初始状态IDLE,投入了1分S1,投入了2分S2,投入了3分S3,投入了4分S4,投入了5分S5,投入了6分S6
module maibaoji(
input clk,
input rst,
input [2:0]din,
output reg [1:0]dout
);
parameter idle = 0,
st1 = 1,
st2 = 2,
st3 = 3,
st4 = 4,
st5 = 5,
st6 = 6;
reg [2:0] CS, NS;
always@(posedge clk or negedge rst)
begin
if(!rst)
CS <= idle;
else
CS <= NS;
end
always@(CS or din)
begin
case(CS)
idle:
if(din == 3’b100)
NS = st1;
else if(din == 3’b010)
NS = st2;
else if(din == 3’b001)
NS = st5;
else if(din == 3’b000)
NS = idle;
st1:
if(din == 3’b100)
NS = st2;
else if(din == 3’b010)
NS = st3;
else if(din == 3’b000)
NS = st1;
st2:
if(din == 3’b100)
NS = st3;
else if(din == 3’b010)
NS = st4;
else if(din == 3’b000)
NS = st2;
st3:
if(din == 3’b100)
NS = st4;
else if(din == 3’b010)
NS = st5;
else if(din == 3’b000)
NS = st3;
st4:
if(din == 3’b100)
NS = st5;
else if(din == 3’b010)
NS = st6;
else if(din == 3’b000)
NS = st4;
st5:
if(din == 3’b100)
NS = st1;
else if(din == 3’b010)
NS = st2;
else if(din == 3’b000)
NS = idle;
st6:
if(din == 3’b100)
NS = st2;
else if(din == 3’b010)
NS = st3;
else if(din == 3’b000)
NS = st1;
default:
NS = idle;
endcase
end
always@(posedge clk)
begin
if(NS == 5)
dout <= 2’b10;
else if (NS == st6)
dout <= 2’b11;
else
dout <= 2’b00;
end
endmodule
3、交通信号灯
由状态机和计数器组成
Q:斐波那契数列
斐波那契数列是一种数列,每一项是通过将前两项相加而得到的。 从0和1开始,顺序为0、1、1、2、3、5、8、13、21、34,依此类推。 通常,表达式为Xn = Xn-1 + Xn-2。 假设最大值n = 256,以下代码将生成第n个斐波那契数。
值“n”(nth_number)作为输入传递给模块
module fibonacci(input clk, reset, input [7:0] nth_number, output [19:0] fibonacci_number, output number_ready);
reg [19:0] previous_value, current_value;
reg [7:0] internal_counter;
reg number_ready_r;
always @(posedge clock or posedge reset)
begin
if(reset) begin
previous_value <='d0; //1st Fibonacci Number
current_value <='d1; //2nd Fibonacci Number
internal_counter <='d1;
number_ready_r <= 0;
end
else begin
if (internal_counter == (nth_number-2)) begin
number_ready_r <= 1;
end
else begin
internal_counter <= internal_counter + 1;
current_value <= current_value + previous_value;
previous_value <= current_value;
number_ready_r <= 0;
end
end
end
assign fibonacci_number = current_value;
assign number_ready = number_ready_r
endmodule