VERDVANA'S BLOG Verdvana

Verilog HDL文档规范


1 前言

        之前写Verilog一直放飞自我,怎么看着好看怎么来,跟当初耍STM32的时候写C一样,没有规范过格式。后来组队写逻辑的时候每个人的风格都不一样,我最后集成的时候十分头疼,可见文档规范在团队协作当中的重要性。


2 文件头定义格式

        Verilog文件的一开始都需要文字说明,说清楚文件的名字、作者、版权、日期、内容介绍等等。如下:

//==============================================================================
//
//Module Name:					Edge_Detection.v
//Department:					Xidian University
//Function Description:	        上升沿、下降沿边沿检测,检测到输出1,否则输出0。
//
//------------------------------------------------------------------------------
//
//Version 	Design		Coding		Simulata	  Review	Rel data
//V1.0		Verdvana	Verdvana				        2019-6-22
//
//==============================================================================

3 规则格式

  • 首行Tab对其;
  • Verilog HDL关键字与其他串之间留有一个空格;
  • 表达式内使用括号表示运算的优先级;
  • 一行中不能出现多个表达式;
  • 0、1、z等常数采用基数法书写:1’b0,1’b1,1’bz。

4 命名规则

  • 模块命名:首字母使用除“u”以外的英文字母,不能使用@、*、+、&等符号,最多只可在中间使用一个“_”字符数字只能表示数据位宽和深度
  • 模块例化命名:如果只例化一个,可以为:xxx_u;例化多个,为:yyy_u0,yyy_u1;
  • 信号命名:首字母使用除字母“u”以外的英文字母,其他字符可以是英文字母、0~9数字以及“_”;
  • 模块对外接口信号的命名:输入接口信号为i_xxx,输出接口信号为o_xxx。其中xxx符合信号命名规则;
  • 模块之间点对点接口信号命名:u1_aaa2bbb_u2_xxx_yyy_zzz2,其中u1_aaa为源模块(例化名为aaa_u1),bbb_u2为目标块(例化名为bbb_u2),xxx_yyy_zzz2为信号字段;
  • 模块之间点对多点接口信号命名:aaa_u1_xxx_yyy_zzz2,其中aaa_u1为源例化模块,xxx_yyy_zzz2为信号名字段;
  • 时钟命名:时钟信号可以写为clk、clk_freq、xxx_clk、xxx_clk_freq。其中频率的小数点用p代替;
  • 异步复位命名:异步高电平复位写为asy_rst、xxx_asy_rst、asy_rst_freq、xxx_asy_rst_freq;异步低电平复位写为asy_rst_n、xxx_asy_rst_n、asy_rst_freq_n、xxx_asy_rst_freq_n;
  • 顶层模块中的时钟信号必须注明频率,如pll_clk_61p44、adc_clk_4p4;
  • 顶层模块中同步产生、异步使用的复位信号必须注明频率,如adc_asy_rst_122p88、asy_rst_61p44;
  • 所有仿真文件的命名:xxx_sim.v,xxx为要仿真模块;
  • 信号延时为xxx_dlyN,其中xxx为原始信号,N为延时拍数;信号提前为xxx_advX,其中,xxx为原始信号,X为提前拍数;
  • 数(parameter),常量(constant)和块标号(block label)名必须一致采用大写;信号、变量、结构名(construct)以及实例标号(instance)必须一致采用小写。

5 整体编码规则

  • 禁止采用同步复位方式,建议采用同步化异步复位信号实现复位;
  • 除非第三方IP指定,否则所有异步复位必须采用统一的有效电平;
  • 使用三段式Moore行状态机进行状态机电路描述;
  • 状态机编码使用独热码,即N个向量采用N位编码,每个向量编码中只有一位为1,其余位为0.这种独热码有助于综合器最优化状态机;
  • 对代码中的数字加注释或者使用参数;
  • 进行parameter参数定义及宏定义时,参数名及宏定义名必须采用大写,以示区分。
  • 对于组合逻辑,always语句的事件控制敏感列表必须完整,应包含所读取的所有变量;
  • 在条件允许的情况下,尽量使用多路选择分支结构case,而减少串行优先结构if……else if;
  • 必须在if……else if语句的所有分支中都对变量进行明确的赋值,否则会在推导过程中产生锁存。而锁存在ASIC中的DFT测试相对较为困难;
  • case同上,default也要加;
  • 时序逻辑使用非阻塞赋值“<=”,禁止使用阻塞语句:
    • 对于以下代码
        always@(posedge clk) begin
            q1=d;
            q2=q1;
            q3=q2;
        end
      

      上述错误在于:阻塞语句是顺序执行的,实际执行结果变为q3=d。

    • 将上述代码修改为
        always@(posedge clk) begin
            q1<=d;
            q2<=q1;
            q3<=q2;
        end        
      

      最终生成的电路为两级触发延迟电路。

  • 一个always只赋值一个或一组向量;
  • 组合逻辑中不能引入异步复位;
  • 乘法器尽量调用IP核,减少类似z=x×y逻辑的综合使用;
  • 所有的信号声明(端口类型声明与数据类型声明)定义必须放在模块接口定义的后面、功能描述体的前面,例子如下:
      module xxx(
      ……
      );
    
      //信号定义
      reg [7:0] sg_cnt;
      reg [7:0] chip_cnt;
      wire [3:0] ……;
    
      //功能描述体
      always@(posedge clk or posedge asy_rst) begin
          ……
      end
    
      endmodule
    
  • 所例化RAM的数据延迟地址应两拍输出;
  • 在always过程块中,不允许出现组合逻辑与时序逻辑混编的语句;
  • 对于从快时钟域到慢时钟域的单位跨时钟域信号,使用双锁存器法;
  • 对于多位跨时钟域信号,使用FIFO或伪双口RAM实现数据的跨时钟域转换。基于FIFO的跨时钟域标准代码如下:
      always@(posedge wrclk or asy_rst) begin
          if(asy_rst)
              wren_adv[4:0] <= 5'b00000;
          else
              wren_adv[4:0] <= {wren_adv[3:0],1'b1};
      end
    
      assign wren = wren_adv[4];
    
      always@(posedge wrclk or asy_rst) begin
          if(asy_rst)
              rden_adv[7:0] <= 8'h0;
          else
              rden_adv[7:0] <= {rden_adv[6:0],wren_adv[4]};
      end
    
      assign rden = rden_adv[7];
    

    在代码中rden有效比wren有效要延时几个rdclk,因此在FIFO开始读时,FIFO内部已经有了几个有效数据深度,这个深度可以避免因rdclk与wrclk之间的抖动引起empty有效时仍进行读操作而造成FIFO的错误。严禁将wren和rden接为固定高电平。

  • 在逻辑资源及设计延迟允许的情况下,对组合逻辑应采用Pipeline流水线操作;
  • 组合逻辑中不能将输出反馈引回输入;
  • 组合逻辑输出容易产生毛刺,在寄存器资源允许的的情况下,应尽量采用寄存器输出;
  • 进行端口声明、比较、赋值等操作时,数据位宽要匹配;
  • 宏定義在头文件中进行,头文件中加入包含文件防范语句放置重复定义。包含文件防范是防止同一个文件被多次包含的技术。包含文件中的代码全部写在“`ifndef”之中,并在其中定义防范用的宏。当再次引用该文件时,“`ifndef”中的代码就会无效,规则如下:
      `ifndef  __INC_GUARD__      //包含文件防范
          `define __INC_GUARD__   //包含文件防范用的宏
    
          `define DataBus 31:0    //比特位或总线用驼峰拼写法
          `define DATA_W  32      //常数使用大写英文字幕和下划线
      `endif                      //包含文件防范
    
  • 编译指示符:
      `include "stddef.h"         //引用stddef.h文件
    
      `define BYTE_DATA_W 8       //定义宏BYTE_DATA_W
    
      `ifdef TEST1                //宏TEST1存在时执行
          ……
      `else                       //宏TEST1不存在时执行
          ……
      `endif
    
      `ifndef TEST2               //宏TEST2不存在时执行
          ……
      `else                       //宏TEST2存在时执行
          ……
      `endif
    
    
  • 不要给信号赋x态,以免x值传递;
  • 模块中不用的输出端口用“-nc”为后缀的信号连接;
  • 模块中不用的输入端口用0或1得到固定电平连接。

6 全局信号编码规则

  • 在一个模块中,在时钟信号的同一个时钟沿动作。如果必须使用时钟上升沿和时钟下降沿,要分两个模块设计;
  • 在设计顶层时,要专门设计时钟信号、复位信号、功率控制信号以及其他必要的全局信号;
  • 除顶层外,在模块内部避免使用门控时钟和门控复位;
  • 对于同步复位电路,建议在同一时钟域中使用单一的全局同步复位电路;对于异步复位电路,建议在同一时钟域中,使用单一的全局异步复位电路;
  • 不要在时钟路径上添加任何缓冲器;
  • 不要再复位路径上添加任何缓冲器;
  • 寄存器的异步复位和异步置位信号不能同时有效;
  • 避免使用组合反馈电路,尤其注意由异步复位带来的组合反馈路径;
  • always语句中的敏感事件列表要完整,否则可能会造成前后仿真结果不一致。在异步复位情况下,需要让异步复位信号和时钟沿作为敏感量,在同步复位情况下,只需要让时钟沿作为敏感量;
  • 对于复杂电路,将组合逻辑和时序逻辑分成独立的always描述。

7 模块编码规则

  • 采用基于名字的调用而非基于顺序的调用;
  • 模块的接口信号按输入、双向、输出顺序定义;
  • 为了保持代码的清晰、美观和层次感,一条语句占用一行,每行限制在80个字符以内,如果较长则要换行;
  • 使用降序定义向量的有效位顺序,最低位是0;
  • 不要采用向量的方式定义一组时钟信号;
  • 逻辑内部不对输入进行驱动,在模块内不存在没有驱动源的信号,避免在elaborate是产生警告;
  • 在顶层模块中,除了内部的互连和模块的例化外,避免再有其他逻辑;
  • 在内部模块端口处要避免双向总线,尽量在最顶层模块内处理双向总线;
  • 三态逻辑可以再顶层模块内使用,在子模块中禁止使用三态逻辑。如果能确保该信号不会被其他子模块使用,且直接用过顶层模块输出要用到I/O口,可以在子模块中使用三态逻辑;
  • 没有未连接的端口;
  • 为逻辑升级保留的无用端口以及信号要有注释;
  • 建议采用层次化设计,模块之间相对独立;
  • 对于层次化设计的逻辑,在升级中采用增量编译;
  • 建议每个模块加时间片;
  • 不要书写空的模块,即一个模块至少有一个输入和输出;
  • 引脚和信号说明部分,一个引脚和一组总线占用一行,说明清晰;
  • 出于层次设计和同步设计的考虑,子模块建议用寄存器锁存输出信号。

8 可综合性设计

  • 不要使用disable、initial等综合工具不支持的电路,而应采用复位方式进行初始化,但在testbench电路中可以使用disable、initial等;
  • 不要使用specify模块;
  • 不要使用===、!==等不可综合的操作符;
  • 除仿真外,不要使用fork-join、while、repeat、forever、系统任务($)、deassign、force、release、named events语句;
  • 不要在连续赋值语句中引入驱动强度和延时;
  • 禁止使用trireg型网线;
  • 禁止使用tri1、tri0、triand和trior型的连接;
  • 不要为驱动supply0和supply1型的线网赋值;
  • 在设计中不要使用macro_module;
  • 不要再RTL代码中实例门级元件,尤其是下列单元:CMOS/RCMOS/NMOS/PMOS/RNMOS/RPMOS/trans/rtrans/tranif0/rtranif0/rtranif1/pull gate;
  • 不要使用include语句。

9 可重用设计

  • 在设计的顶层模块中,将I/O口、边界电路以及设计逻辑区分开;
  • 考虑未使用的输入信号power_down,避免传入不稳定态;
  • 将异步电路和同步电路区分开来,便于综合和后端约束;
  • 将相关的逻辑放在一个模块内;
  • 合理划分设计的功能模块,保证模块功能的独立性;
  • 合理划分模块的大小,避免模块过大;
  • 接口信号尽量少,接口时序尽量简单;
  • 将状态机电路与其他电路分开,便于综合和后端约束。

        原则上上述编程规范是普遍适用的,但在很多场合下可能会出现个别例外情况,例如因为工具的局限性,ASIC和FPGA使用场景不一致等。