Verilog RTL代码风格介绍
08 Aug 2019 3538字 12分 次 Verilog HDL打赏作者 CC BY 4.0 (除特别声明或转载文章外)
1 使用标准的DFF模块例化生成寄存器
寄存器是数字同步电路中最基本的单元。使用Verilog进行数字电路设计时,最常见的方式是使用always块语法生成寄存器,要点如下:
- 对于寄存器避免直接使用always块编写,而是应该采用模块化的标准DFF模块进行例化。示例如下所示,一个名为flg_dfflr的寄存器,除了时钟(clk)和复位信号(rst_n)之外,还带有使能信号flg_ena和输入(flg_nxt)/输出信号(flg_r)。
wire flg_r; wire flg_nxt = ~flg_r; wire flg_ena = (ptr_r == ('E203_OITF_DEPTH - 1)) & ptr_ena; //此处使用例化sirv_gnrl_dfflr的方式实现寄存器,而不是使用显示的always块sirv_gnrl_dfflr #(1) flg_dfflrs(flg_ena, flg_nxt, flg_r, clk, rst_n);
- 使用标准的DFF模块例化的好处包括以下内容:
- 便于全局替换寄存器类型;
- 便于在寄存器中全局插入延迟;
- 明确的load-enable使能信号(如下例的flg_ena)方便综合工具自动插入寄存器级别的门控时钟以降低动态功耗;
- 便于规避Verilog语法if-else不能传播不定态的问题。
- 标准DFF模块是一系列不同的模块,结构如下:
|----rtl //存放RTL目录 |----e203 //E203核和SoC的RTL目录 |----general //存放一些通用模块的RTL代码 |----sirv_gnrl_dffs.v //该文件中编写了一系列不同的DFF模块
例如:
sirv_gnrl_dfflrs //带有load-enable使能,带有异步reset,复为默认值为1的寄存器 sirl_gnrl_dfflr //带有load-enable使能,带有异步reset,复为默认值为0的寄存器 sirl_gnrl_dffl //带有load-enable使能,不带有异步reset寄存器 sirl_gnrl_dffrs //不带有load-enable使能,带有异步reset,复为默认值为1的寄存器 sirl_gnrl_dffr //不带有load-enable使能,带有异步reset,复为默认值为0的寄存器 sirl_gnrl_ltch //latch锁存器模块
- 标准DFF模块内部则使用Verilog语法的always块进行编写,以dfflr为例,如下所示。由于Verilog if-else语法不能传播不定态,对处于if条件中的lden信号为不定态的非法情况使用断言(assertion)进行捕捉。
//标准DFF模块,以sirv_gnrl_dfflr为例,代码片段如下 module sirv_gnrl_dfflr # (parameter DE = 32)( input lden, input [DW-1:0] dnxt, output [DW-1:0] qout, input clk, input rst_n ); reg [DW-1:0] qout_r; //使用always块编写寄存器逻辑 always@(posedge clk or negedge rst_n) begin:DFFLR_PROC if(rst_n == 1'b0) qout_r <= {DW{1'b0}}; else if (lden == 1'b1) qout_r <= dnxt; ens assign qout = qout_r; //使用assertion捕捉lden信号的不定态 `ifndef FPGA_SOURCE//1{ `ifndef SYNTHESIS//{ sirv_gnrl_xchecker # ( //该模块内部是个SystemVerilog编写的断言 .DW(1) ) u_sirv_gnrl_xchecker( .i_dat(lden), .clk (clk) ); `endif//} `endif//} endmodule //sirv_gnrl_xchecker模块代码片段 //此模块专门捕捉不定态,一旦输入的i_dat出现不定态,则会报错并终止仿真 module sirv_gnrl_xchecker # ( parameter DW = 32 ) ( input [DW-1:0] i_dat, input clk ); CHECK_THE_X_VALUE: assert property (@(posedge clk) ((^(i_dat))!==1'bx) else $fatal ("\n Error:Oops,detected a X value! This should never happen. \n"); endmodule
2 推荐使用assign语法代替if-else和case语法
Verilog中的if-else和case语法存在两大缺点:
- 不能传播不定态;
- 会产生优先级的选择电路而非并行选择电路,从而不利于时序和面积。
为了规避这两大缺点,推荐使用assign语法进行代码编写,本原则来自于严谨的工业级开发标准:
- Verilog的if-else不能传播不定态,以如下代码片段为例。假设a的值为X不定态,按照Veriolg语法会将其等效于a==0,从而让out输出值等于in2最终没有将X不定态传播出去。这种情况可能会在仿真阶段掩盖某些致命的bug,造成芯片功能性错误。
if(a) out = in1; else out = in2;
而使用功能等效的assign语法如下所示,假设a的值为X不定态,按照Verilog语法,则会将X不定态传播出去,从而让out输出值也等于X。通过X不定态的传播,可以在仿真阶段将bug彻底暴露出来:
assign out = a?in1:in2;
虽然现在有的EDA工具提供专有选项(例如Synopsys VCS提供xprop选项)可以使用Verilog原始语法中定义的“不传播不定态”的情形强行传播出来,但是一方面不是所有的EDA工具均支持此功能;另一方面在操作中此选项也时常被忽视,从而造成疏漏。
- Verilog的case语法也不能传播不定态,与上一部分中的if-else同理。而使用等效的assign语法即可规避此缺陷。
- Verilog的if-else语法会被综合为优先级选择的电路,面积和时序不够优化,如下所示:
if(sell) out = in1[3:0]; else if(sel2) out = in2[3:0]; else if(sel3) out = in3[3:0]; else out = 4'b0;
如果此处确实是希望生成一种优先级选择的逻辑,则推荐使用assign语法等效的编写成如下形式,以规避C不定态传播的问题:
assign out = sel1 ? in1[3:0] : sel2 ? in2[3:0] : sel3 ? in3[3:0] : 4'b0;
而如果此处本来是希望生成一种并选择的逻辑,则推荐使用assign语法明确地使用“与”、“或”逻辑,编写如下:
assign out = ({4{sel1}} & in1[3:0]) |({4{sel2}} & in2[3:0]) |({4{sel3}} & in3[3:0]);
使用明确的assign语法编写的“与”、“或”逻辑一定能够保证综合生成并行选择的电路。
- 与问题三同理,Verilog的case语法也会被综合成为优先级选择的电路,面积和时序均不够优化。有的EDA综合工具可以提供指引注释(例如Synopsys parallel_case和full_case)来使得综合工具能够综合处并行选择逻辑,但是这样可能会造成前后仿真不一致的严重问题,从而产生重大bug。因此,在实际的工程开发中:
- 应该明令禁止使用EDA综合工具提供的指引注释(例如Synopsys parallel_case和full_case)
- 应该使用等效assign语法编写电路。
3 其他
其他编码风格中的若干要点如下:
- 由于带有reset的寄存器面积和时序会稍微差一点,因此在数据通路上可以使用不带reset的寄存器,而只在控制通路上使用呆reset的寄存器;
- 信号名定义应该避免使用拼音,注重使用英语缩写,信号名不可定义的过长,但是也不可以定义的过短。所谓代码即注释,应该尽量从信号名中能够看出此信号的功能作用;
- clock和reset信号应禁止被用于任何其他的逻辑功能,clock和reset信号只能接入DFF作为其时钟和复位信号之用。
4 小结
上述推荐使用的asign语法和标准DFF例化方法能够使得任何不定态在前仿真阶段无处遁形,综合工具能够综合出很高质量的电路,综合出的电路门控时钟率也很高。
告辞。