【Verilog】工业级RTL代码风格推荐

好久没更新了,搬运一篇verilog的文章吧。

来源:《RISC-V  CPU设计——蜂鸟E203处理器核的RTL代码风格》


        蜂鸟 E203 处理器核采用一套统一的 Verilog RTL 编码风格(coding style), 该编码风格来自严谨的工业级开发标准, 其要点如下。

  1. 使用标准 DFF 模块例化、生成寄存器。
  2. 推荐使用Verilog中的 assign语法替代if-else 和case语法。

下面分别予以详述。

1. 使用标准 DFF 模块例化生成寄存器

        寄存器是数字同步电路中基本的单元。当使用 Verilog 进行数字电路设计时, 最常见的方式是使用 always块语法生成寄存器。本节介绍蜂鸟 E203 处理器核推荐的原则, 本原则来自严谨的工业级开发标准。

        对于寄存器,避免直接使用always块编写,而应该采用模块化的标准 DFF 模块进行例化。示例如下所示, 除时钟(clk)和复位信号(rst_n)之外, 一个名为flg_dfflr的寄存器还有使能信号 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 模块例化的好处以下:

  1. 便于全局替换寄存器类型。
  2. 便于在寄存器中全局插入延迟。
  3. 明确的load-enable 使能信号(如下例中的 flg _ena)可方便综合工具自动插入寄存器级别的门控时钟以降低动态功耗。
  4. 便于规避 Verilog 语法中if-else 不能传播不定态的问题。(避免bug在仿真过程中被掩盖)

标准 DFF 模块是一系列不同的模块,列举如下:

  1. sirv_gnrl_dfflrs: 带 load-enable使能信号、带异步 reset信号、复位默认值为1 的寄存器。
  2. sirv_gnrl_dfflr:带load-enable使能信号、带异步 reset信号、复位默认值为0的寄存器。
  3. sirv_gnrl_dffl: 带 load-enable使能信号、不带 reset信号的寄存器。
  4. sirv_gnrl_dffrs:不带 load-enable使能信号、带异步 reset信号、复位默认值为1 的寄存器。
  5. sirv_gnrl_dffr:不带load-enable使能信号、带异步 reset信号、复位默认值为0的寄存器。
  6. sirv_gnrl_ltch: Latch模块。

        标准 DFF 模块内部则使用 Verilog语法的 always块进行编写, 以 sirv_gnrl_dfflr为例,代码如下所示。由于 Verilog if-else 语法不能传播不定态, 因此对于 if条件中 lden信号为不定态的非法情况使用断言(assertion) 进行捕捉。

module sirv_gnrl_dfflr # (

parameter DW= 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;

end

assign qout = qout_r;

//使用 assertion 捕捉 lden信号的不定态

'ifndef FPGA_SOURCE//{

'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 语法存在两大缺点。

  1. 不能传播不定态。
  2. 会产生优先级的选择电路而非并行选择电路, 从而不利于优化时序和面积。

        为了规避这两大缺点, 蜂鸟E203 处理器核推荐使用 assign 语法进行代码编写, 本原则来自严谨的工业级开发标准。

        Verilog的 if-else不能传播不定态, 以如下代码片段为例。假设a的值为X(不定态),按照 Verilog语法它会将等效于a==0,从而让 out等于in2, 最终没有将X(不定态) 传播出去。这种情况可能会在仿真阶段掩盖某些致命的bug, 造成芯片功能错误。

if(a)

out = inl;

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 语法等效地写成如下形式, 以规避X(不定态)传播的问题。

assign out = sell ? 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。因此在实际的工程开发中, 注意以下两点。

  1. 应该明令禁止使用EDA 综合工具提供的注释(例如 synopsys parallel_case 和 full_case)。
  2. 应该使用等效的 assign 语法设计电路。

3.其他若干注意事项

其他编码风格中的若干注意事项如下。

  1.  由于带 reset信号的寄存器面积略大,时序稍微差一点, 因此在数据通路上可以使用不带reset信号的寄存器, 而只在控制通路上使用带 reset信号的寄存器。
  2.  信号名应该避免使用拼音,使用英语缩写, 信号名不可过长, 但是也不可过短。代码即注释, 应该尽量让开发人员能够从信号名中看出其功能。
  3.   Clock和 Reset信号应禁止用于任何其他的逻辑功能, Clock 和 Reset信号只能接入DFF,作为其时钟和复位信号。

相关推荐

  1. Verilog工业RTL代码风格推荐

    2024-04-08 04:58:02       15 阅读
  2. Verilog】行为建模

    2024-04-08 04:58:02       45 阅读
  3. FreeCAD的代码风格

    2024-04-08 04:58:02       16 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-04-08 04:58:02       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-08 04:58:02       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-08 04:58:02       19 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-08 04:58:02       20 阅读

热门阅读

  1. LeetCode | 数组 | 二分查找 | 69. x 的平方根【C++】

    2024-04-08 04:58:02       19 阅读
  2. 面试算法-138-移动零

    2024-04-08 04:58:02       14 阅读
  3. Kratos 基础学习记录

    2024-04-08 04:58:02       18 阅读
  4. hibernate检索方式

    2024-04-08 04:58:02       18 阅读
  5. 常见的几种字符串及其区别

    2024-04-08 04:58:02       18 阅读
  6. Linux介绍

    2024-04-08 04:58:02       16 阅读
  7. 记录CodeMirror一些常用的配置选项

    2024-04-08 04:58:02       19 阅读
  8. AI创业机会的探索

    2024-04-08 04:58:02       17 阅读