相关阅读
数字IC前端专栏https://blog.csdn.net/weixin_45791458/category_12173698.html?spm=1001.2014.3001.5482
锁存器是一种时序逻辑,与触发器相比面积更小,同时也可以放宽常见设计中的沿到沿时序要求,但它的存在会使静态时序分析(STA)变得更加复杂,因此懂得什么样的设计会综合出锁存器是很重要的,本文就将对此进行详细介绍。
显式锁存器
显式锁存器指的是在连续赋值语句或组合逻辑always结构中,将信号显式赋值给自己时出现的锁存器,一般显式锁存器是有意为之的。
连续赋值语句
最直观的锁存器
图1 D型锁存器
// 例1 module NonLatch1 ( input wire d, input wire en, output wire q, output wire qbar ); wire set, rst; assign set = !(en & d); assign rst = !(en & !d); assign q = !(set & qbar); assign qbar = !(rst & q); endmodule图2是例1在Design Compiler 2024中的综合结果,可以看出,即使可以综合,综合工具并没有推断出D锁存器,而是综合为存在组合环(Combinational Loop)或者说反馈的结构。
图2 例1的综合结果
使用条件操作符
// 例2 module NonLatch2 ( input wire d, input wire en, output wire q, output wire qbar ); assign q = en ? d : q; assign qbar = !q; endmodule图3是例2的综合结果,可以看出,综合工具也没有推断出D锁存器,而是综合为存在组合环(Combinational Loop)或者说反馈的多路选择器。
图3 例2的综合结果
always结构
需要注意的是,这里指的是组合逻辑always结构,而不是边沿控制的时序逻辑always块,对于后者,综合工具会推断出触发器而不是锁存器。
使用条件操作符
// 例3 module Latch1 ( input wire d, input wire en, output reg q, output reg qbar ); always @(*) begin q = en ? d : q; qbar = !q; end endmodule图4是例3的综合结果,可以看出,综合工具将其综合为D锁存器。
图4 例3的综合结果
使用if-else语句
// 例4 module Latch2 ( input wire d, input wire en, output reg q, output reg qbar ); always @(*) begin if (en) begin q = d; qbar = ~d; end else begin q = q; qbar = qbar; end end endmodule图5是例4的综合结果,可以看出,综合工具将其综合为D锁存器,但与图4有一点区别,原因是发生了在优化过程中发生了相位反转和触发器合并。
图5 例4的综合结果
需要注意的是,如果在例4中将else分支的q和qbar换为!q和!qbar,则会综合出组合逻辑环(Timing Loop)而不是锁存器。
隐式锁存器
隐式锁存器指的是在always结构中,没有将自己显式赋值给自己,但由于意料之外的因素出现的锁存器。
不完整的敏感列表
// 例5 module NonLatch3 ( input wire a, input wire b, input wire c, output reg y ); always @(a) begin y = a & b & c; end endmodule例5使用组合逻辑always结构描述了一个三输入与门,但敏感列表中只有a而不含b和c,这导致了只有a发生变化时才会激活always结构。对于以前的综合工具,这会导致锁存器的产生;对于现在的综合工具,其对组合逻辑always结构的综合不依赖敏感列表,因此不会有锁存器产生,但这会导致前/后仿真的不一致,因为对于仿真工具根据敏感列表进行判断,因此行为就像是锁存器。
图6是例5的综合结果,可以看出,综合工具将其综合为三输入与门。
图6 例5的综合结果
实际上,由于不完整的敏感列表带来的风险如此巨大,Verilog 2001标准新加了一个*通配符来形成一个完整的敏感列表。使用通配符意味着always结构中RHS表达式、函数和任务调用的参数、case语句的case expression和case item、if语句的expression信号将自动加入敏感列表,如例6所示。
// 例6 module NonLatch4 ( input wire a, input wire b, input wire c, output reg y ); always @(*) begin y = a & b & c; end endmodule不完整的if语句
// 例7 module Latch3 ( input wire a, input wire b, output reg y ); always @(*) begin if (a) y = b; end endmodule例7中的if语句缺少else分支,即当a为0时输出保持不变(这其实与例4等价,只不过例4是显式赋值),这是一个锁存器的行为,图7是例7的综合结果,可以看出,综合工具将其综合为D锁存器。
图7 例7的综合结果
解决方法1
保持if语句完整且在每个分支中对所有信号的赋值,如例8所示。
// 例8 module NonLatch5 ( input wire a, input wire b, output reg y ); always @(*) begin if (a) y = b; else y = 1'b0; end endmodule解决方法2
在always结构的开头对所有信号赋初值,如例9所示。
// 例9 module NonLatch6 ( input wire a, input wire b, output reg y ); always @(*) begin y = 1'b0; if (a) y = b; end endmodule不完整的case语句
// 例10 module Latch4 ( input wire [1:0] sel, input wire a, input wire b, output reg y ); always @(*) begin case (sel) 2'b00: y = 1'b0; 2'b01: y = 1'b1; endcase end endmodule与例7类似,例10中的case语句缺少case item,即当sel[1]为1时输出保持不变,这是一个锁存器的行为,图8是例10的综合结果,可以看出,综合工具将其综合为D锁存器。
图8 例10的综合结果
解决方法1
保持case语句完整且在每个分支中对所有信号的赋值(或者干脆使用case default),如例11所示。
// 例11 module NonLatch7 ( input wire [1:0] sel, input wire a, input wire b, output reg y ); always @(*) begin case (sel) 2'b00: y = a; 2'b01: y = b; 2'b01: y = 1'b1; 2'b01: y = 1'b0; // default: y = 1'b0; endcase end endmodule解决方法2
在always结构的开头对所有信号赋初值,如例12所示。
// 例12 module NonLatch8 ( input wire [1:0] sel, input wire a, input wire b, output reg y ); always @(*) begin y = 1'b0; case (sel) 2'b00: y = a; 2'b01: y = b; endcase end endmodule根本原因
以上这些无意间综合出的锁存器产生的根本原因是出现了在某些情况下需要保持信号值不变的需求,而又使用了组合逻辑always结构,因此只能使用锁存器实现功能。
思考一个问题:如果不使用本文提到的这些解决方法,不完整的if语句和case语句一定会综合出锁存器吗?例13会综合出锁存器吗?
// 例13 module Latch_or_Not ( input wire sel, input wire a, input wire b, output reg y ); reg [1:0] sel_with_constraint; always @(*) begin if (sel) sel_with_constraint = 2'b00; else sel_with_constraint = 2'b01; end always @(*) begin case (sel_with_constraint) 2'b00: y = a; 2'b01: y = b; endcase end endmodule图9是例13的综合结果,如果这与你想的不一样,想想锁存器产生的根本原因,而不是孤立地看待if语句和case语句。
图9 例13的综合结果
下面再来看一个例子,例14会综合出组合逻辑环(Timing Loop)吗?
// 例14 module popcount_100 ( input wire [99:0] in, output reg [6:0] count ); integer i; always @(*) begin count = 0; for (i = 0; i < 100; i = i + 1) begin if (in[i] == 1'b1) count = count + 1'b1; end end endmodule虽然在for语句中,循环中的count信号参与的计算结果显式赋值给自己是描述组合环逻辑环的代码风格,但如今的Design Compiler已经可以处理这种风格的RTL代码,并将其转化为下面这种意思。
count_0 = 0; count_1 = in[0] ? count_0 + 1 : count_0; count_2 = in[1] ? count_1 + 1 : count_1; count_3 = in[2] ? count_2 + 1 : count_2; count = count_3;