Verilog 编码风格

良好的编码风格,有助于代码的阅读、调试和修改。虽然 Verilog 代码可以在保证语法正确的前提下任意编写,但是潦草的编码风格往往是一锤子买卖。有时回看自己编写的代码,既看不出信号的意义,也不了解模块的功能,还得从逻辑上一步步分析,就会消耗大量的时间和精力去消化,严重影响设计进度。

为了不让别人或自己由衷的感叹出:这特喵的是哪个"小傻宝"写的代码!下面对编码风格进行一定意义上的建议。

1. 命名

信号变量、模块等一定要使用有意义的名字,且信号名称在模块间穿梭时也应该保持不变,以便代码自身就具有清晰的说明信息,增强可读性。

当名字单词数量过多时,可以使用首字母大写或下划线"_"进行拼接。个人喜欢后者,比较清晰。

  reg     DataToDestinationClock ;
  reg     data_to_destination_clock ; //推荐

建议使用单词缩写的方式对信号进行命名,并懂得取舍,避免过长的信号命名。例如 clock 缩写为 clk, destination 缩写为 dest,source 缩写为 src 等。

reg     data_to_destination_clock ;
reg     des_data ; //推荐

巧用数字代表英文字母,例如 2 代表 to, 4 代表 for, 可以省略一丢丢代码空间。

reg     clk_for_test, sig_uart_to_spi ;
reg     clk4test, sig_uart2spi ; //推荐

虽然 Verilog 区分大小写,但是建议一般功能模块的名称、端口、信号变量等全部使用小写,parameter 使用大写,一些电源、pad 等特殊端口使用大写。只为编写代码方便,容易区分常量变量,不用考虑大小写不一样但名字相同的信号变量的差异。

parameter         DW = 8 ; //常量
reg [DW-1 : 0]    wdata ;  //变量

寄存器变量一般加后缀 _r, 延迟打拍的变量加后缀 _r1、_r2等。主要有两大好处。一是 RTL 设计时容易根据变量类型对数据进行操作。二是综合后网表的信号名字经常会改变,加入后缀容易在综合后网表中找到与 RTL 中对应的信号变量。

wire      dout_en ;
reg       dout_en_r ;
...  //dout_en_r 的逻辑
assign    dout_en = dout_en_r ;

其他尾缀:_d 可以表示延迟后的信号,_t 可以表示暂时存储的信号,_n 可以表示低有效的信号,_s 可以表示 slave 信号,_m 可以表示 master 信号等。

避免使用关键字对信号进行命名,例如 in, out, x, z 均不建议作为变量。

文件名字保持与设计的 module 名字一致,文件内尽量只包含一个设计模块。

2. 注释

每一个设计模块开头,都应该包含文件说明信息,包括版权、模块名字、作者、日期、梗概、修改记录等信息。例如:

/**********************************************************
// Copyright 1891.06.02-2017.07.14
// Contact with willrious@sina.com
================ runoob.v ======================
>> Author       : willrious
>> Date         : 1995.09.07
>> Description  : Welcome
>> note         : (1)To 
>>              : (2)My
>> V180121      : World.
************************************************************/

注释应该精炼的表达出代码所描述的意义,简短的注释在一行语句代码之后添加,过长的注释提前一行书写。

 //输出位宽小于输入位宽,求取缩小的倍数及对应的位数
 parameter       SHRINK       = DWI/DWO ;
 reg [AWI-1:0]         ADDR_WR ; //写地址

注释尽量用英文书写,以保证不同操作系统、不同编辑器下能够正常显示。

端口信号中,除一般的时钟和复位信号,其他信号最好也进行注释。

注释功能非常强大,可以使用注释信息画出时序图,甚至可以使用注释画出数字电路结构图。

3. 优化

使用圆括号确定程序的优先级或逻辑结构。为避免操作符优先级问题导致设计错误,建议多多使用圆括号。同时,圆括号的巧妙使用有时候也会优化逻辑综合后的结构。例如:

//往往被综合成串行的 3 个加法器
assign F = A + B + C + D ;
//往往被综合成并行的的 2 个加法器和 1 个级联的加法器,时序更加宽松
assign F = (A + B) + (C + D) ;

//不推荐
assign flag = cnt == 4'd2 && mode == 2'b01;
//推荐
assign flag = (cnt == 4'd2) && (mode == 2'b01);  

条件语句尽量使用 case 语句代替 if 语句。当同级别的条件判断语句过多时,使用 case 语句综合后的硬件结构,往往比 if 语句消耗更少的资源,拥有更好的时序。

状态机编写时,尽量使用 3 段式,以保证代码具有良好的整洁性和安全性。

系统设计时,尽量采用模块按功能分割、然后进行模块例化的方法。相比成千上万行代码都集成在一个文件中,模块分割有利于团队设计,便于更新维护。

4. 美观

端口信号保证每行一个信号,逗号紧跟在端口声明之后,强迫症患者请保持逗号也对齐。

//不推荐
module even_divisor (input rstn, clk, output clk_div2, clk_div4, clk_div10) ;

//推荐
module even_divisor    (
    input               rstn     ,
    input               clk      ,
    output              clk_div2 ,
    output              clk_div4 ,
    output              clk_div10
    );

一行代码内容过长时,尽量换行编写,无需使用换行符,例如:

assign rempty = (rover_flag == rq2_wptr_decode[AWI]) &&
        (raddr_ex >= rq2_wptr_decode[AWI-1:0]);

尽量使用 begin + end 的方式保证执行语句间的层叠关系。begin 与关键字同行,end 另起一行。例如,always 语句块使用时,或条件语句只有一条执行语句时,都可以省略 begin + end 关键字。但为保证结构的完整性,以及后续代码的调试与修改,还是建议加入此类关键字。

   always @(posedge dout_clk or negedge rstn) begin
      if (!rstn) begin
         dout_en_r       <= 1'b0 ;
      end
      else begin
         dout_en_r       <= rd_en_wir ;
      end
   end

尽量使用 tab 键和空格,保证语句按照层级结构对齐,变量、关键字、操作符之间也应该留有空隙,便于逻辑判断。

   generate
      if (DWO >= DWI) begin
         reg [DWI-1:0]         mem [(1<<AWI)-1 : 0] ;
         always @(posedge CLK_WR) begin
            if (WR_EN) begin
               mem[ADDR_WR]  <= D ;
            end
         end
      end
   endgenerate

模块例化时,端口信号尽量与连接信号隔开,并各自对齐。连接信号为向量时指明其位宽,方便阅读、调试。

    ram   u_ram(
        .CLK_WR          (clk),
        .WR_EN           (wren), //写满时禁止写
        .ADDR_WR         (addr),
        .D               (wdata[9:0]),
        .Q               (rdata[31:0])
        );

例化多个相同的模块时,尽量使用 generate 语句,避免过长的例化代码描述。

下一章:Verilog 代码规范

许多初学者编写 Verilog 代码,基本都是按照 C 语言的思维和风格去设计,造成了很多不规范的共性问题。本节主要总结一些不规范且危险的 Verilog 设计。主要针对可综合的数字设计,testbench 是仿真 ...