FPGA设计中锁存器产生、避免与消除
- 一、锁存器的产生
- 1.1 组合逻辑中使用保持状态
- 1.2 组合逻辑中的if-else语句或case语句未列出所有可能性
- 1.3 小结
- 二、锁存器的避免
- 三、锁存器的消除
- 3.1 情况一
一、锁存器的产生
锁存器的产生主要有以下两种情况:(1)组合逻辑中使用保持状态;(2)组合逻辑中的if-else语句或case语句未列出所有可能性;
1.1 组合逻辑中使用保持状态
assign data_out = valid ? data_in : data_out; //变量保持当前值
always @(*) begin
if(valid)
data_out = data_in;
else
data_out = data_out; //变量保持当前值
end
1.2 组合逻辑中的if-else语句或case语句未列出所有可能性
对于组合逻辑中,如果使用if-else语句,未补全else语句,则默认在其他条件下,数据均保持为原来的状态,那么也会产生锁存器。
//if-else语句缺少else
always @(*) begin
if(valid)
data_out = data_in;
end
而如果在组合逻辑中使用case语句,未列出case中条件所有的可能性,则相当于对于未列出的那些情况,数据均保持为原来的状态,也会产生锁存器。
//case语句未列出所有可能性
always @(*) begin
case(sel)
2'b00: data_out = 2'b00;
2'b01: data_out = data_in;
endcase
end
1.3 小结
那么,总而言之,言而总之。对于锁存器,其产生的原因可以总结为一点:想要通过组合逻辑保持数据不变。对于组合逻辑的实现,我们可以认为它是用很多门电路搭建而成的,那么,门电路与寄存器不同,其不具有保持当前状态的功能。也就是说,构成当前组合逻辑的电路中任何一个信号发生改变,都会导致输出结果发生改变。如果要通过组合逻辑实现保持数据的不变,那就只能产生锁存器了(可以参考《数字电子技术基础》第五版的SR触发器)。
二、锁存器的避免
我们知道了锁存器的产生原因,就可以对症下药,在编写Verilog代码时注意编码风格,即可避免锁存器的产生。
(1)在组合逻辑中使用if-else语句时补全else语句
always @(*) begin
if(sel)
data_out = data_in;
else
data_out = 2'b00;
end
(2)在组合逻辑中使用case语句时,设置默认状态default,并默认状态下的数据进行赋值
always @(*) begin
case(sel)
xxx: data_out <= 2'b01;
xxx: data_out <= data_in;
default: data_out = 2'b00;
endcase
end
(3)在组合逻辑中,不可一个变量赋值给变量自身
//错误示例
always @(*) begin
if(sel)
data_out = data_in;
else
data_out = data_out; //变量赋值给变量自身
end
//正确示例
always @(*) begin
if(sel)
data_out = data_in;
else
data_out = 2'b00; //需要赋值一个准确的数值或者其他变量
end
三、锁存器的消除
在FPGA设计过程中,有可能出现一些情况,必须使用组合逻辑,保证其实时性,且需要保持数据不变,保证其他运算的正确性。那么又该如何处理?
3.1 情况一
假如我们要实现如下面时序图所示功能,输出data_out在有效信号valid的上升沿处锁存输入data_in的值,那么要如何实现?
最开始考虑的是采用时序逻辑进行实现,代码如下。
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
data_out <= 'd0;
else if(valid)
data_out <= data_in;
else
data_out <= data_out;
end
那么,采用时序逻辑实现,会导致延迟一怕,无法在有效信号valid的上升沿锁存数据,如下图所示。
于是考虑使用组合逻辑进行实现,代码如下。
assign data_out = valid ? data_in : data_out;
但是组合逻辑要实现保持状态,必然会产生锁存器。综合后会再Messages界面会警告出现锁存器,同时在Schematic中也可以看到出现了锁存器,如下图所示。而在我们的设计中,我们是不希望出现锁存器的,因为其不利于时序分析。
那么,也可以采用组合逻辑+时序逻辑的方式实现,通过多使用一部分寄存器资源,来实现在valid上升沿处进行数据采样,且不产生锁存器,代码如下。重新综合后,可见锁存器消失,同时可以对齐进行仿真,时序符合前面的要求。
reg [3:0] r_data_in;
always @(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
r_data_in <= 'd0;
else
r_data_in <= data_in;
assign data_out = valid ? data_in ? r_data_in;