我们知道触发器是边沿敏感,锁存器是电平敏感的存储单元。那么它们在FPGA内部究竟有什么区别呢?本文通过几个实际的案例来说明。
在xilinx 7系列的FPGA中,CLB(Configurable Logic Block)是逻辑实现的主要资源,在ug474中详细介绍了CLB。每个CLB包含两个slices,每个slices由4个(A,B,C,D)6输入LUT和8个寄存器,1个CARRY4,3个MUX(多路选择器)组成。同一CLB中的两片slices没有直接的线路连接,分属于两个不同的列,每列拥有独立的快速进位链资源。slice分为两种类型 SLICEL, SLICEM . SLICEL可用于产生逻辑,算术,ROM。 SLICEM除以上作用外还可配置成分布式RAM或32位的移位寄存器。每个CLB可包含两个SLICEL或者一个SLICEL与一个SLICEM.(https://www.eefocus.com/b3574027/blog/15-05/312609_2e5ad.html)
打开vivado的device就能看到每个蓝色框内就是1个CLB,左起第三个slice就是slicem。
在ug953中介绍各种底层硬件资源,其中CLB中的寄存器可以配置为D Flip-Flop (D触发器)和Transparent Data Latch(锁存器),在245页上有描述,具体又可以分为以下几种:
触发器:
异步复位 | FDCE | 复位后Q输出0 |
FDPE | 复位后Q输出1 | |
同步复位 | FDRE | 复位后Q输出0 |
FDSE | 复位后Q输出1 |
锁存器:
异步复位 | LDCE | 复位后Q输出0 |
LDPE | 复位后Q输出1 |
触发器的基本结构如下图所示:
当复位端CLR拉高时Q输出0,当CE等于0时,Q保持不变,当CLR = 0;CE = 1; 在C端边沿触发时将D的数据同步到Q端。
锁存器的基本结构如下图所示:
当复位端CLR拉高时Q输出0,当GE G有一个拉低时,Q端保持不变,都为高时,Q输出D的值。可以看到锁存器是没有同步复位的。无论altera还是xilinx的ff都可以配置成fd或者ld。
1.异步复位时序电路
以4分频器为例。
module test(
output c,
input clk,
input rst
);
reg clk2 = 1'd0;
reg [1:0]cnt = 1'd0;
always@(posedge clk or posedge rst)
if(rst)
begin
clk2 <= 1'd0;
cnt <= 1'd0;
end
else if(cnt == 1'd1)
begin
clk2 <= ~clk2;
cnt <= 1'd0;
end
else
cnt <= cnt + 1'd1;
assign c = clk2;
endmodule
综合后的RTL图为:
可以看到占用了2个FF和2个LUT,而且FF都是FDCE,是异步复位,初始值为0的触发器,这和代码是一致的。LUT1的作用就是一个反相器,当cnt = 1时,cnt = 0;cnt = 0时,cnt = 1;同理,可以分析LUT2的真值表,当I0 = 1时,O输出的是I1的取反,这和代码也是一致的。总之,我们可以看出异步复位电路,RST异步接入,高电平有效时,FF配置为FDCE。
当代码修改为if(!rst)时,RTL如下图所示:
编译器会用一个lut对rst做反相,这将占用更多的资源,所以一般来说用高电平复位,同时,复位时应将用到的寄存器都初始化。
2.同步复位时序电路
同步复位就是在always 敏感列表里不添加rst,仅在clk边沿变化时,D发生变化。代码修改为:
always@(posedge clk)
if(rst)
begin
clk2 <= 1'd0;
cnt <= 1'd0;
end
else if(cnt == 1'd1)
begin
clk2 <= ~clk2;
cnt <= 1'd0;
end
else
cnt <= cnt + 1'd1;
综合后的RTL图为:
可以看到rst信号接入到了lut,FF配置为了FDRE。对于xilinx的LUT来说,触发器支持高电平的异步和同步复位。
3.完整if else组合逻辑电路
一般认为组合逻辑是指输出只与当前的输入状态有关电路,而时序逻辑是指输出不仅与当前输入有关,还跟历史输入有关,例如前面的电路。
例如,a = (b&c&d) + e; 这明显是组合逻辑。组合逻辑里没有时钟,输入信号至LUT里后,直接输出Q,不经过触发器,除非电路中生成了锁存器,Q输出后会接入一个由FF配置成的LD。锁存器会在什么地方产生呢?我们一般回答在组合逻辑里分支没有完全描述的情况下产生,例如if后没有else,case没有default。
下面是完整的if else例子。
module test(
input a,b,
output [1:0]c,
input clk,
input rst
);
reg [1:0]reg_c = 1'd0;
always @(rst or a or b)
if(rst)
reg_c = 2'd0;
else if(a)
reg_c = 2'd1;
else if(b)
reg_c = 2'd2;
else
reg_c = 1'd0;
assign c = reg_c;
endmodule
生成的RTL图如下图所示:
由于它是组合逻辑,敏感列表里写不写都是一样的(我猜的)。
4不完整if else if组合逻辑电路
如果少了else会是什么情况呢?
module test(
input a,b,
output [1:0]c,
input clk,
input rst
);
reg [1:0]reg_c = 1'd0;
always @(*)
if(rst)
reg_c = 2'd0;
else if(a)
reg_c = a - b;
else if(b)
reg_c = a + b;
assign c = reg_c;
endmodule
注意代码,没有了else,同时else if里的内容也有修改。RTL图为:
可以看到,在LUT输出后,加入了一个LDCE,这便是锁存器。因为在两个else if以外的情况下,编译器会将逻辑定为保持当前输出不变,因此,观察LDCE的端口,rst接到了异步复位的CLR,锁存器只支持异步复位,当a b条件不满足else if时,即在LUT2中描述情况下a b同时为0时,数据将锁存,因此LUT会输出0到LDCE的G端,此时Q将保持当前值。可以看到当条件罗列完全时,仅需要LUT就可以完成组合逻辑的描述,未罗列完全时,还需要LUT用于描述LDCE的G端,因此会多占用FF资源。同时,锁存器也不利于静态时序分析,所谓静态时序分析,我们通常是以clk的周期为参考,去分析DATA和CLK的路径,计算是否满足下一级FF的setup和hold时间,而latch的复位是异步的,G端的变化是异步的,latch和时钟毫无关系,分析起来自然十分麻烦。因此我们应尽量避免产生latch。
时序逻辑中if 或case不完整会不会产生latch呢?
答案是不一定产生,因为时序逻辑是在clk的变化沿同步数据,它会用一个lut来描述没有穷举的情况,并输出到FF的CE端,使FF在输出时保持前一个值。如下图所示。
反正最好是把else和default写完整。也可以在组合逻辑always@(*)的首行,把信号先用阻塞方式(=)把信号先赋值,效果等同于else或default。
那么如何检查代码中有latch呢?
最简单的方法是在综合后的message窗口,ctrl + f,搜索latch或者设置条件为latch,点击确定,即可看到有多少个latch。从资源占用中看到只用了1个LUT 和1个FF就实现了,可是明明是2个LUT和1个FF?这时候要打开版图来看。
如果在FLOP_LATCH下没有LATCH选项,说明该设计中就没有latch产生,同理,用Find命令可以查看其它资源。也可以在输入脚本命令 all_latches。输入all_ 会有命令提示,可以查看许多资源,请自行尝试。
至此,本文介绍了latch和ff的区别,和latch产生的原因。实际上数字电路基础中花了很多篇幅去讲晶体三极管和MOS管组成TTL和CMOS电路,有或与非等基本电路,分析它们的电流和电压特性,但FPGA里负责大量逻辑运算是基于SRAM的LUT,是查找表,应该有一个区别的认识,不是FPGA里有很多或与非门,是用LUT组成的或与非逻辑。
FPGA设计时常说的设计流程就是先综合生成原理图,再布局布线映射到FPGA中CLB中去。比如有16个并行总线端口输入后经clk采样,在原理图看都是一样的路径,但由于映射到BGA引脚上,信号从PAD到16个寄存器有无数种布线方法,它们之间的延时也是不固定的,当采样速度很高,布线的延时差异不能忽略时,就容易采样错误,因此就要用时序约束,set input delay来描述这些信号相对于clk到来的时刻,布局布线时就根据这些约束保证输入的路径是差不多的。输出也是同理。为了得到更好的采样结果,通常会将输出输入打1个节拍,用IOB的FF去输出或采样,可以保证每次布局布线时,从FF到PAD的时间都是固定的,而且最短。
5.阻塞和非阻塞
简单说来,阻塞就是 = ,非阻塞就是 <=。为什么称它为阻塞呢?因为 = 在同一个always 它是有先后执行顺序的,后面的=被前面的 = 阻塞了。非阻塞就是并行的,一起运行。
懒得写了,直接引用吧。
https://blog.csdn.net/August_cwj/article/details/77989071
限于水平,还有很多问题没有描述,权当一个敲门砖,我们FPGAer还是应该要理解你的代码实际上对应的是什么电路,毕竟这是自古流传的传统。