Verilog 几种代码风格的建模效果
Verilog毕竟是硬件描述语言,使用Verilog这类HDL语言的目的始终是对电路的建模,并最终得到工具转换出来的实际电路,所以写代码的过程中要能抽象出对应的电路。
但同时,Verilog毕竟还是一种程序语言,就像其他程序语言一样,所以仍然需要熟悉它的语法特性,这样才可以在遇到不熟悉的代码风格时候,能抽象出相应电路或者找出建模过程中出错的地方。
本文关注于常见代码风格的建模效果以及综合是否改变仿真结果。
---
系统环境:Centos 6.5
测试工具:Questasim 10.1b
综合工具:DC 2013.03(针对ASIC)
波形工具:Verdi 2013
----
case
1.译码器
以3-8译码器作为例子来看case的建模效果。代码和tb分别如下:
module case_endcase_basic( input [2:0] data_in, output [7:0] data_out ); reg [7:0] data_out_reg; always @ (*) begin case( data_in ) 3\'h0: data_out_reg = 8\'b0000_0001; 3\'h1: data_out_reg = 8\'b0000_0010; 3\'h2: data_out_reg = 8\'b0000_0100; 3\'h3: data_out_reg = 8\'b0000_1000; 3\'h4: data_out_reg = 8\'b0001_0000; 3\'h5: data_out_reg = 8\'b0010_0000; 3\'h6: data_out_reg = 8\'b0100_0000; 3\'h7: data_out_reg = 8\'b1000_0000; endcase end assign data_out = data_out_reg; endmodule
module case_endcase_basic_tb; reg [2:0] data_in; wire [7:0] data_out; initial begin data_in = 0; #10 data_in = 1; #10 data_in = 2; #10 data_in = 3; #10 data_in = 4; #10 data_in = 5; #10 data_in = 6; #10 data_in = 7; #10 data_in = 8; #10 $finish; end `ifdef DUMP_FSDB initial begin $fsdbDumpfile("test.fsdb"); $fsdbDumpvars; end `endif case_endcase_basic case_endcase_basic_instance( .data_in ( data_in ), .data_out( data_out ) ); endmodule
使用QuestaSim仿真后,得到的波形图:
DC综合之后效果图:
使用 综合后的网标文件和sdf文件进行后仿真,波形图如下:
可以看出在信号稳定的时候,综合后的仿真效果和综合前的是一样的。而对于两个不同输入值之前出现的“0”值,可以判断是cell的1->0 比0->1的传播速度要快。
下面讨论下case语句中常见的集中代码风格差生的影响
2.case不完全
仿照上面的例子。只是取消掉data_in=3’h7的情况。代码如下。
module case_endcase_basic1( input [2:0] data_in, output [7:0] data_out ); reg [7:0] data_out_reg; always @ (*) begin case( data_in ) 3\'h0: data_out_reg = 8\'b0000_0001; 3\'h1: data_out_reg = 8\'b0000_0010; 3\'h2: data_out_reg = 8\'b0000_0100; 3\'h3: data_out_reg = 8\'b0000_1000; 3\'h4: data_out_reg = 8\'b0001_0000; 3\'h5: data_out_reg = 8\'b0010_0000; 3\'h6: data_out_reg = 8\'b0100_0000; endcase end assign data_out = data_out_reg; endmodule
tb沿用上一个。测试后的波形如下:
可以看出来data_in=7的时候,data_out的值并没有变化。猜测simulator在这种情况下,保持之前的值。
虽然rtl仿真结果只是简单的一点变化,但是综合的效果却会有比较大的差异:
明显感觉到要复杂了很多,同时出现了latch,而且latch前的逻辑会比较复杂。除此,代码中去掉的那种情况,电路的变动也对应于圈出的地方,这条路径最后输出为data_out[7],可从工具综合出来的电路看出,data_out[7]将一直为0。
进行综合后仿真,波形图如下:
可以看出后仿后的波形中,data_out延时要比第一种情况下的延时长。
3 case 不完全条件+default
如果能告诉工具 在没有列出的条件情况下 的输出值,就可以避免产生latch。比如最常用的default关键词。代码如下:
module case_endcase_basic2( input [2:0] data_in, output [7:0] data_out ); reg [7:0] data_out_reg; always @ (*) begin case( data_in ) 3\'h0: data_out_reg = 8\'b0000_0001; 3\'h1: data_out_reg = 8\'b0000_0010; 3\'h2: data_out_reg = 8\'b0000_0100; 3\'h3: data_out_reg = 8\'b0000_1000; 3\'h4: data_out_reg = 8\'b0001_0000; 3\'h5: data_out_reg = 8\'b0010_0000; 3\'h6: data_out_reg = 8\'b0100_0000; default: data_out_reg = 8\'b1000_0000; endcase end assign data_out = data_out_reg; endmodule
tb仍然同上。波形如下:
综合后的效果:
可以看出,和第一种情况下的电路结构是一样的。因为电路效果是相同的。综合后仿真的波形图,也如第一种情况。
4 case 不完全条件+输出初始化
除了使用default语句,也可以在case之前对输出值赋一遍值,达到相同效果。代码如下:
module case_endcase_basic3( input [2:0] data_in, output [7:0] data_out ); reg [7:0] data_out_reg; always @ (*) begin data_out_reg = 8\'b1000_0000; case( data_in ) 3\'h0: data_out_reg = 8\'b0000_0001; 3\'h1: data_out_reg = 8\'b0000_0010; 3\'h2: data_out_reg = 8\'b0000_0100; 3\'h3: data_out_reg = 8\'b0000_1000; 3\'h4: data_out_reg = 8\'b0001_0000; 3\'h5: data_out_reg = 8\'b0010_0000; 3\'h6: data_out_reg = 8\'b0100_0000; endcase end assign data_out = data_out_reg; endmodule
tb仍然如上。wave:
综合效果:
相比较于default,这种风格的代码,综合的效果多少有点不一样(圈出来的地方),但从下面的综合后仿真来看逻辑功能还是一样的。单纯从电路图上可以估计此种风格电路的效果关键路径的延迟会较前一种要多一些。
从DC粗略综合后的简略时序报告来看,也确实后者的延迟长一点。
再回头看综合后仿真的波形图,当data_in=7的情况下,data_out的输出要比default时候的长一些。
同时查看生成latch时候的关键路径的延迟,单纯latch就有1.31 。
至于为何有这种差异,因为对DC的综合算法不了解,也不好下定论。至于再复杂些情况会不会差异小或者后者效果好,并没有验证,不过个人还是建议使用default。
5 case default的变体
最近看opencores上uart的代码时候,看到一种写法,测试一下。
module case_endcase_basic2_m( input [2:0] data_in, output [7:0] data_out ); reg [7:0] data_out_reg; always @ (*) begin case( data_in ) 3\'h0: data_out_reg = 8\'b0000_0001; 3\'h1: data_out_reg = 8\'b0000_0010; 3\'h2: data_out_reg = 8\'b0000_0100; 3\'h3: data_out_reg = 8\'b0000_1000; 3\'h4: data_out_reg = 8\'b0001_0000; 3\'h5: data_out_reg = 8\'b0010_0000; 3\'h6: data_out_reg = 8\'b0100_0000; default: ; endcase end assign data_out = data_out_reg; endmodule
tb仍不变,wave如下:
综合效果:
看出,和case不完整条件的效果是一样的。综合后仿真wave:
这种风格代码,建模纯粹的组合电路不可取,但是用来建模时序电路倒是效果还不错。
6 译码器变体(错误版本)
在使用case建模译码、选择电路的时候,常常会碰到这些代码风格:只在相应的条件下,只对相应的输出值赋值。代码如下:
module case_endcase_basic_m( input [2:0] data_in, output [7:0] data_out ); reg [7:0] data_out_reg; always @ (*) begin case( data_in ) 3\'h0: data_out_reg[0] = 1\'b1; 3\'h1: data_out_reg[1] = 1\'b1; 3\'h2: data_out_reg[2] = 1\'b1; 3\'h3: data_out_reg[3] = 1\'b1; 3\'h4: data_out_reg[4] = 1\'b1; 3\'h5: data_out_reg[5] = 1\'b1; 3\'h6: data_out_reg[6] = 1\'b1; 3\'h7: data_out_reg[7] = 1\'b1; endcase end assign data_out = data_out_reg; endmodule
tb不变,wave:
在使用dc综合的时候碰到了一个问题:
综合出了这样的电路。经dxzhang帮忙,发现这是优化过得电路,在log中对这有所提示:
dc工具在默认情况下对电路进行了优化,需要关闭:
set compile_seqmap_propagate_constants false set compile_delete_unloaded_sequential_cells false
综合效果:
会发现,这种建模风格同样也产生了latch。综合的电路可以分成两部分,前面圈出来的的部分和第一种译码器一样,第二部分是一组译码器。RS-latch中R接地,恒等于0,只有S有效,s=0时保持,s=1时候赋值为1。最后所有位都会变成1。
综合后仿真wave:
现在回过头来看原来的代码,每一种情况只对指定位赋值为1,其他的位并没有提及,这时候工具认为是”dont care”,即x。可以粗略想到最简单的化简结果就是每一个输出恒为1(可以大体参照卡诺图化简过程)。这时候会发现,原来代码本身就是写错了的。但同时这也提供了另一种构建译码器的代码风格,只需要改动一点即可,如下面一种情况。
7 译码器变体(正确)
代码中只在case前一行有所改动。
module case_endcase_basic_mr( input [2:0] data_in, output [7:0] data_out ); reg [7:0] data_out_reg; always @ (*) begin data_out_reg = 8\'h00; case( data_in ) 3\'h0: data_out_reg[0] = 1\'b1; 3\'h1: data_out_reg[1] = 1\'b1; 3\'h2: data_out_reg[2] = 1\'b1; 3\'h3: data_out_reg[3] = 1\'b1; 3\'h4: data_out_reg[4] = 1\'b1; 3\'h5: data_out_reg[5] = 1\'b1; 3\'h6: data_out_reg[6] = 1\'b1; 3\'h7: data_out_reg[7] = 1\'b1; endcase end assign data_out = data_out_reg; endmodule
tb不变,wave如下:
综合后电路:
容易看出,这和第一个版本的电路是完全一样的,至于同样使用这种多重赋值的第4种电路中为何多出来一部分逻辑还不清楚。
不过,我个人认为,可以使用这种多重赋值来替换default。或者在某些情况下,既有default的存在,又有这种多重赋值。