最近发现一个问题,代码中会特地的新建一个D触发器用来锁存信号,让很多人都比较疑惑,明明一个D触发器就可以检测输入是上升沿和下降沿。
两个触发器的目的主要是为了防止触发器变成亚稳态
`timescale 1ns / 1ps
module key_test
(
input clk,
input [3:0] key,
output[3:0] led
);
reg[3:0] led_r;
always@(posedge clk)
begin
led_r <= ~key; //第一个触发器
end
assign led = led_r;
endmodule
综合后的RTL图如下
如果有两个D触发器
`timescale 1ns / 1ps
module key_test
(
input clk,
input [3:0] key,
output[3:0] led
);
reg[3:0] led_r;
reg[3:0] led_r1;
always@(posedge clk)
begin
led_r <= ~key;
end
always@(posedge clk)
begin
led_r1 <= led_r; //第二个触发器
end
assign led = led_r1;
endmodule
那么他的RTL图为
这样做的好处参考csdn中https://blog.csdn.net/VCA821/article/details/81989846的博客是就是建立时间和保持时间也就是
Setup/Hold Time 用于测试芯片对输入信号和时钟信号之间的时间要求。建立时间(Setup Time)是指触发器的时钟信号上升沿到来以前,数据能够保持稳 定不变的时间。输入数据信号应提前时钟上升沿(如上升沿有效)T 时间到达芯片,这个T就是建立时间通常所说的 Setup。如不满足 Setup Time,这个数据就不能被这一时钟打入触发器,只有在下一个时钟上升沿到来时,数据才能被打入 触发器。保持时间(Hold Time)是指触发器的时钟信号上升沿到来以后,数据保持稳定不变的时间。如果 Hold Time 不够,数据同样不能被打入触发器。当我们按键时,很有可能会正好碰到与时钟上升沿时间距离很短的时候(也就是建立时间不足),这时候触发器会进入亚稳态用一组图片说明参考https://blog.csdn.net/dongdongnihao_/article/details/79590213以及https://www.cnblogs.com/amanlikethis/p/3721968.html
在第一个时钟上升沿,前边的触发器采集D1信号,将高电平打入触发器,经过Tco的触发器输出延时到达组合逻辑电路(comb)。又经过组合逻辑电路的延时Tcomb(我们假定组合逻辑电路此时没有改变信号的高低,可以把它假定为一个缓冲器)送到了D2接口上。在第二个时钟上升沿到来之前,
D2的信号要满足稳定时间>触发器的建立时间Tsu。
因为触发器内部数据的形成是需要一定的时间的,如果不满足建立和保持时间,触发器将进入亚稳态,进入亚稳态后触发器的输出将不稳定,在0和1之间变化,这时需要经过一个恢复时间,其输出才能稳定,但稳定后的值并不一定是你的输入值。这就是为什么要用两级触发器来同步异步输入信号。这样做可以防止由于异步输入信号对于本级时钟可能不满足建立保持时间而使本级触发器产生的亚稳态传播到后面逻辑中,导致亚稳态的传播。
(比较容易理解的方式)换个方式理解:需要建立时间是因为触发器的D段像一个锁存器在接受数据,为了稳定的设置前级门的状态需要一段稳定时间;需要保持时间是因为在时钟沿到来之后,触发器要通过反馈来所存状态,从后级门传到前级门需要时间。
亚稳态是指触发器无法在某个规定的时间段内到达一个可以确认的状态。两级触发器可防止亚稳态传播的原理:假设第一级触发器的输入不满足其建立保持时间,它在第一个脉冲沿到来后输出的数据就为亚稳态,那么在下 一个脉冲沿到来之前,其输出的亚稳态数据在一段恢复时间后必须稳定下来,而且稳定的数据必须满足第二级触发器的建立时间,如果都满足了,在下一个脉冲沿到 来时,第二级触发器将不会出现亚稳态,因为其输入端的数据满足其建立保持时间。
解决亚稳态的方法:
1 降低系统时钟频率
2 用反应更快的FF
3 引入同步机制,防止亚稳态传播(可以采用前面说的加两级触发器)。
4 改善时钟质量,用边沿变化快速的时钟信号
通过对各个小模块的输入输出的例化命名,使这三个模块相连,构成这一层的模块
例化名,FPGA中主要描述的是实体而在实体描述中需要调用其他的实体时,就要将你要调用的实体例化到你现在所描述的实体中,例化名就是你在例化时给调用的实体取的名字一般用 大写+数字例
我们用一个简单的工程予以说明参考https://wenku.baidu.com/view/25a7c8703186bceb18e8bb0d.html
下图这是一个流水灯的顶层模块
module water_led(clk,rst_n,led ); //流水灯模块
input clk ;
input rst_n ;
output [3:0]led ;
wire out_freq; //定义内部接线
.freq m0( //freq是被调用的子模块的名称
.clk(clk), //m0是指上文提到的为freq这个实体取的名字
.rst_n(rst_n), //括号中的即为本模块—顶层的变量名,外面的
.out_freq(out_freq)//括号外的为调用模块的变量名
);
led n0(
.clk(out_freq),
.rst_n(rst_n),
.out_led(led)
);
endmodule
子模块分频器,状态机
module freq(clk,rst_n,out_freq);
input clk;
input rst_n;
output out_freq;
reg out_freq;
reg [31:0]flag;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
flag <= 32'd0;
out_freq <= 1'd0;
end
else
if(flag<32'd49_999_999)
flag <= flag+32'd1;
else
begin
flag <= 1'd0;
out_freq = ~out_freq;
end
end
endmodule
module led(rst_n,clk,out_led);
input rst_n;
input clk;
output[3:0] out_led;
reg[3:0] out_led;
reg[1:0] state;
always@( posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
out_led<= 4'b0000;
state <= 0;
end
else
begin
case(state)
2'd0:begin
out_led <= 4'b0001;
state <= 2'd1;
end
2'd1:begin
out_led <= 4'b0010;
state <= 2'd2;
end
2'd2:begin
out_led <= 4'b0100;
state <= 2'd3;
end
2'd3:begin
out_led <= 4'b1000;
state <= 2'd0;
end
default: state <= 0;
endcase
end
end
endmodule
1 写状态机if_else一定要排查,不要出现忘记else,排查办法看综合后的RTL。
2 endcase 和default一定别忘。
3 数字最好都加上位宽,无论是用‘b(二) ’d(十)注意‘而不是`//尺度timescale。
4 例化模块一定要设置例化名字最好U1,U2,W1,W2这样。
5信号名用小写 _n低电平,一定要加rst_n。参数用大写。
6在FPGA的设计上禁止用纯组合逻辑产生latch,带D触发器的latch的是允许的,比如配置寄存器就是这种类型。
看到一篇帖子,了解到层次化设计时要各个模块用同一个时钟,不要时钟跑的满天飞。
也就是说如果两个模块所需时钟不一样我们用前一模块的输出来使能后一模块的时钟接收端——《FPGA规范》https://www.jianshu.com/p/b6583c1474ed
一个模块尽量只用一个时钟,这里的一个模块是指一个module或者是一个entity。在多时钟域的设计中涉及到跨时钟域的设计中最好有专门一个模块做时钟域的隔离。这样做可以让综合器综合出更优的结果。
1尽量在底层模块上做逻辑,在高层尽量做例化,顶层模块只能做例化,禁止出现任何胶连逻辑(glue logic),哪怕仅仅是对某个信号取反。理由同上。
2 一般来说,进入FPGA的信号必须先同步,以提高系统工作频率(板级)。所有模块的输出都要寄存器化,以提高工作频率,这对设计做到时序收敛也是极有好处的。
3 除非是低功耗设计,不然不要用门控时钟–这会增加设计的不稳定性,在要用到门控时钟的地方,也要将门控信号用时钟的下降沿 打一拍再输出与时钟相与。
最好的解决门控时钟的办法是使用或门(上升沿触发),如果门控触发器是下降沿触发,则应该使用与门。
现改进上文提到的流水灯工程
这是新生成的RTL图
可看到此时状态机的clk就是fpga的clk而分频器的输出作为状态机的使能端达到分频的目的
在顶层模块添加
led n0(
.en(out_freq),//添加了一使能端
.clk(clk),
.rst_n(rst_n),
.out_led(led)
);
具体在子模块更改的代码如下
if(!rst_n)
begin
out_led<= 4'b0000;
state <= 0;
end
else
if(!en)//添加的使能端
begin
out_led<= 4'b0000;
state <= 0;
end
else
begin
case(state)
latch:锁存器,是由电平触发
一篇关于latch的讲解http://www.elecfans.com/d/663922.html
生成latch来锁clk,在传输的数据不是电路所需要的时候,将寄存器时钟关闭的技术,能够有效降低功耗, 是低功耗设计的重要方法之一。门控时钟是一个逻辑模块,在寄存器的输入数据无效时,将寄存器的输入时钟置为0,而此时寄存器值保持不变,此时没有时钟翻转,避免了动态功耗。https://blog.csdn.net/icxiaoge/article/details/80792819