Verilog HDL文档规范
07 Apr 2019 5171字 18分 次 Verilog HDL打赏作者 CC BY 4.0 (除特别声明或转载文章外)
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使用场景不一致等。