ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

数字IC前端学习笔记:锁存器Latch的综合

数字IC前端学习笔记:锁存器Latch的综合

相关文章

数字IC前端专栏https://blog.csdn.net/weixin_45791458/category_12173698.html?spm=1001.2014.3001.5482


锁存器的生成方式有两种,一种是有意的,而另一种是无意的。无意生成的锁存器会浪费硅片面积,因为在大部分FPGA资源中,没有现成的锁存器,需要使用更多的资源来实现锁存器,且锁存器的存在会使静态时序分析(STA)变得更加复杂。因此,懂得什么样的设计会被综合工具综合出锁存器是很重要的。

可以用带反馈的门级原语或连续赋值语句锁存器,我们可以看以下两种尝试构造锁存器的行为。

// 下面用两种方式描述了一对交叉耦合的与非门构成的SR锁存器 // 但是很遗憾,有些综合器不支持这种构造锁存器的行为 // 方式一 assign q = ~(set & qbar); assign qbar = ~(rst & q); // 方式二 nand(q, set, qbar); nand(qbar, rst, q);

一个带反馈和条件操作符(?:)的连续赋值语句(注意这里是一个,和上面的反馈不一样),带反馈即赋值的变量(RHS)又出现在条件操作符表达式中,会被综合出一个带反馈的多路选择器构成的锁存器,一般情况下,这个多路选择器直接使用组合逻辑生成,不会调用Mux库单元。这是一种有意生成锁存器的方法,如在构建存储器时。

SRAM存储器单元可由以下带反馈的连续赋值语句建模:

assign data_out = ( CS_B == 0 ) ? ( WE_b == 0 ) ?data_in:data_out:1'bz;

CS_b和WE_b分别是片选和写使能信号,均为低电平有效。如果CS_b有效且WE_b有效,那么data_out透明输出data_in的值,但当WE_b切换到1时,data_out=data_out。根据这一反馈,综合工具会生成带反馈的多路选择器构成的锁存器,因为当WE_b=1时,data_out不受输入影响,而是会保持之前的值。当CS_b=1时,电路将处在高阻状态。

一、锁存器的无意综合

例1模块or4_behave描述了一个4输入或门(虽然看起来有点奇怪)。always块中首先将输出初始化为0,然后循环测试输入。若有一输入为1,则设置输出为1并跳出循环。该描述被综合成组合逻辑,应该注意的是,尽管输出变量被声明为寄存器变量,但是并没有被综合成寄存器(所有过程赋值的变量都应该是寄存器变量)。

module or4_behav (y, x_in); parameter word_length = 4; output y; input [word_length - 1: 0] x_in; reg y; integer k; always @ (x_in) begin: check_for_1 y = 0; for (k = 0; k <= word_length -1; k = k+1) if (x_in[k] == 1) begin y = 1; disable check_for_1; end end endmodule

现在再来分析一下下面这个模块or4_behav_latch的描述,它的事件控制对x_in[0]不敏感,这就会无意间综合出锁存输出,即电路仅在x_in[3:0]变化时才会激活周期性行为,并更新输出,当x_in变化时,输出处于锁存状态。

module or4_behav_latch (y, x_in); parameter word_length = 4; output y; input [word_length - 1: 0] x_in; reg y; integer k; always @ (x_in[3:1]) begin: check_for_1 y = 0; for (k = 0; k <= word_length -1; k = k+1) if (x_in[k] == 1) begin y = 1; disable check_for_1; end end endmodule

事实也确实如此,下图(a)是or4_behave的综合结果,而图(b)是or4_behav_latch的综合结果,我们可以明显看到图(b)中带有一个锁存器。

如果描述中没有隐含对存储器结构的需求,电平敏感周期性行为会被综合为组合逻辑。否则设计中则会出现锁存器。为了避免出现锁存器,组合逻辑的所有输入信号,都必须包含在事件控制表达式中。例如在赋值表达式的右逻辑发生变化的所有情况下,都必须给赋值表达式左值赋值,所以等号右边的所有信号都要出现在事件控制表达式中。或者像例1一样的if控制表达式中的控制信号,也必须出现在事件控制表达式中。

实际上,由于不完整的敏感列表带来的风险如此巨大,HDL语言新加了一个*通配符来形成一个完整的敏感列表。*自动将RHS、函数和任务调用、case语句的expression和item项、if语句的expression项、LHS的索引加入敏感列表(这里不包括wait语句中、内嵌事件控制表达式中、和LHS这三个地方的信号)。

当在电平敏感周期性行为中,赋值表达式右端的信号又出现在左端,则行为隐含了锁存器,而不是无反馈的组合逻辑了。

当在连续赋值语句中,赋值表达式右端的信号通过条件运算符(?:)出现在了左端,如前言那样,则行为隐含了锁存器,而不是无反馈的组合逻辑了。

当一个case语句或if语句没有对所有可能的输入指定输出值时,即未定义完整的case和if语句,可能会出现非预期的锁存器。(因为这表示在特定输入下,输出保持原值)不管是在进入case前初始化输出,还是使用default语句(针对case语句)指定默认输出,都必须保证case和if是完整的。

例2当case语句不能完全译码时,综合工具即将导出锁存器,用以在有输入信号行为未定义的情况下保持输出值。在这个例子中,当{sel_a, sel_b}=2'b00和2'b11时,锁存器使能,保持输出不变。图a给出了一个使用通用单元综合的实现,图b给出了使用实际库单元的实现。后者用到了库中的双通道MUX和一个泄放电荷的上/下拉器件,在这个例子中,该器件上拉连接到锁存器复位输入端。

module mux_latch (y_out, sel_a, sel_b, data_a, data_b); output y_out; input sel_a, sel_b, data_a, data_b; reg y_out; always @ ( sel_a or sel_b or data_a or data_b) //y_out=1'b0; 加上能消除锁存器 case ({sel_a, sel_b}) 2'b10: y_out = data_a; 2'b01: y_out = data_b; //default:y_out=1'b0;加上也能消除锁存器 endcase endmodule

例3还有一种情况会产生锁存器,也是很容易被忽略的情况,就是在一种情况下,always块内的赋值不完整,即赋值没有覆盖所有组合逻辑变量,这时你就隐含地告诉了综合器,在某些特定信号下,需要保持输出不变,从而综合成一个锁存器,如下所示。

module mux_latch (y_out, sel_a, sel_b, data_a, data_b); output y_out; input sel_a, sel_b, data_a, data_b; reg y_out,z_out; always @ ( sel_a or sel_b or data_a or data_b) case ({sel_a, sel_b}) 2'b10: y_out = data_a; 2'b01: z_out = data_b; default:y_out=1'b0,z_out=1'b0; endcase endmodule

这个代码中有两个组合逻辑输出,但在{sel_a, sel_b}=2'b10时,没有对z_out的赋值,在{sel_a, sel_b}=2'b01时,没有对y_out的赋值。赋值没有覆盖所有组合逻辑变量,因此会综合出锁存器。这种情况下,消除寄存器的方法只能通过补齐case语句所有情况下的赋值,或者直接在进入always语句后对所有组合逻辑变量赋初值。

实际上,以上说的综合出latch的情形也不是绝对的,为什么这么说,在以上的例子中,我们对输入信号是没有限制的,比如说,在例2中sel的取值是2'b00、2'b01、2'b10、2'b11中的任意一个,所以如果case语句不能完全译码,就会出现某些信号变化时,不改变y_out,从而综合出锁存器,如果前面的逻辑限制sel只能是2'b01、2'b10中的一个时,即使不能完全译码,也不会综合出锁存器。综合工具能自行根据上下文判断是否一定要使用锁存器,综合出锁存器的本质原因是出现了某些情况下需要保持信号值不变的要求。

二、锁存器的有意综合

如上节所述,如果行为流在任何情况下都能给指定寄存器赋值,即不存在不完整的case语句和if语句,那么仅当显式地将某个变量赋值给自己时,才会出现反馈生成锁存器。

而赋值给自己的行为分为两种,一种是直接过程赋值,一种是使用?:条件运算符反馈赋值。

直接过程赋值分为两种:一、在电平敏感周期性行为中,会生成带反馈的多路选择器构成的锁存器。二、在边沿敏感周期性行为中不会生成锁存器,而会是一个带有反馈的寄存器。

使用?:条件运算符反馈赋值分为三种:一、在连续赋值语句中,会生成带反馈的多路选择器构成的锁存器。二、在过程赋值中,会直接生成硬件锁存器(就像敏感列表和case或if不完整那样)。三、在边沿敏感周期性行为中不会生成锁存器,而会是一个带有反馈的寄存器。

总结就是,如果if和case、敏感列表完整,但有反馈赋值的情况下。在电平敏感周期性行为赋值中,使用?:反馈赋值会直接生成硬件锁存器,在边沿敏感周期性行为中不会生成锁存器,在其他情况下生成的是带反馈的多路选择器构成的锁存器。

例3下面的latch_if1模块是一个带有完整敏感列表、if语句也完整的周期性行为的描述,if语句中具有直接过程赋值的data_out反馈赋值。如上所述,综合结果是一个带反馈的多路选择器构成的锁存器。(不过我在Synopsys DC 2018上的实验表示,只要在电平敏感周期性行为有给自己的显式赋值,都会生成硬件锁存器,可能是作者的综合工具不同导致的)

module latch_if1(data_out, data_in, latch_enable); output [3: 0] data_out; input [3: 0] data_in; input latch_enable; reg [3: 0] data_out; always @ (latch_enable or data_in) if (latch_enable) data_out = data_in; else data_out = data_out; //assign data_out=latch_enable?data_in:data_out; 使用这种形式综合结果是一样的 //always @ (latch_enable or data_in) 但这种形式会生成硬件锁存器 //data_out = latch_enable?data_in:data_out; 但这种形式会生成硬件锁存器 endmodule

例4下面的latch_if1模块是一个带有完整敏感列表,但if语句不完整的周期性行为的描述,if语句缺少else分支。如上所述,综合结果就是硬件锁存器。

module latch_if2 (data_out, data_in, latch_enable); output [3: 0] data_out; input [3: 0] data_in; input latch_enable; reg [3: 0] data_out; always @ (latch_enable or data_in) if (latch_enable) data_out = data_in; endmodule

三、更多示例

关于锁存器综合的更多示例,可以阅读下面的博客。

数字IC前端学习笔记:锁存器Latch的综合https://blog.csdn.net/weixin_45791458/article/details/147801585?ops_request_misc=elastic_search_misc&request_id=9e811f830df10ecfb3a07d258a4c24ec&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~ElasticSearch~search_v2-1-147801585-null-null.541^v3^pc_search_result_blog8&utm_term=%E9%94%81%E5%AD%98%E5%99%A8&spm=1018.2226.3001.4450

以上内容来源于《Verilog HDL高级数字设计》和《Verilog编程艺术》

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进