本文
主要介绍了一些verilog的编码风格准则。
版本 | 说明 |
---|---|
0.1 | 初版发布 |
参考
- RTL Coding Styles That Yield Simulation and Synthesis Mismatches(链接:https://pan.baidu.com/s/16fD72mrvHfSIA531h33J2A 密码:vzrl)
- VERILOG CODING STYLES FOR IMPROVED SIMULATIONEFFICIENCY (链接:https://pan.baidu.com/s/1r8RETSe3ehe2T%5FK5brZbjQ 密码:t2hp)
写在前头
本文主要从三个角度来对verilog编码风格进行说明:
- 利于模拟的编码风格:可以加快模拟速度或减少模拟占用内存。
- 利于综合的编码风格:可以减少综合前后模拟不一致问题。
- 利于维护的编码风格:有利于设计的维护、交互与修改,提高设计效率。
利于模拟的编码风格
- 多条件下尽量使用 case 语句,不使用嵌套 if-else if-else 语句,两者占用内存差不多,但模拟速度 case 比 if 要快。
- 不使用多余的 begin-end, always 或 if 语句中只有一条语句,是不需要使用 begin-end 的,多余的 begin-end 会占用更多内存和减缓模拟速度。
- 时钟信号产生,使用 always 语句描述模拟速度更快,如下代码:
|
|
- 尽量避免将赋值语句分散到多个 always 块,如寄存器的赋值,建议尽量将多个寄存器描述在一个 always 块语句,而不是将每个寄存器都用一个 always 块语句来描述,这样可以减少内存和提高模拟速度。
- 尽量减少模块的端口数,模块端口数越多,模拟速度越慢,因此需要对设计合理划分模块。
- 加载测试向量时,避免在时钟的上下沿变化,而是在延时一个时间单位后,这样更能够模拟真实电路的行为,为了便于维护,可以定义CYCLE数值,延时单位采用 “0.1*`CYCLE” 形式。
- 显示非阻塞赋值语句的变量值,使用 $strobe ,而不是 $display 和 $write 。下面介绍一下两者的区别,举例如下:
|
|
可见 $strobe 是在整个块语句结束后执行的,而 $display 是在程序位置执行的。这里是一个顺序执行的块语句,而对于非阻塞赋值语句不是顺序执行的,所以在需要显示非阻塞赋值语句的变量值时,使用 $strobe ,确保能够取到稳定的赋值后的值。
以上是公认的编码原则,强烈建议遵循,而下面内容,仅供参考:
- 尽量减少设计层次,层次越多模拟速度越慢。
- parameter 比 define 更占用内存,但模拟速度相当。
- `timescale 精度越高,模拟速度越慢。
- $time 等系统调用程序非常影响模拟速度,可以使用但不要滥用。
- 不建议设计文件中写 `timescale ,建议写在单独文件,统一进行 `include ,或在仿真工具中参数化输入。
利于综合的编码风格
- 顶层模块不要有粘连逻辑,顶层模块甚至次顶层模块只用来例化和连接子模块,这样不仅可以节省顶层的编译时间,还有利于布局布线的实施。
- 减少不必要的设计层次,更多的层次会降低综合性能,当然也可以先使用工具将设计层次铺平,如 dc 的 ungroup 。
- 组合逻辑与时序逻辑尽量分开描述,换句话说,寄存器always语句描述中应该只有赋值,而无其他运算。
- 将不同类的逻辑分散到不同模块,这样综合工具可以针对不同的逻辑类型采用适当的优化技术。
- 相关的组合逻辑不要拆散到不同模块,否则会限制综合工具的优化力度,好的设计就是所有输出都是寄存器输出,对于组合逻辑分散到不同模块,虽然通过工具在综合前将设计铺平的方法可以缓解问题,但作为设计师还是尽量遵循此原则。
- 所有输出都是寄存器,这样所有组合逻辑都在前端,最后寄存输出,这也是所说的 “cloud-register原则” ,这样既可以得到更好的综合结果,而且简化了综合的约束设计。
- 敏感列表要完整,当然现在可以使用*代替,或者使用 SystemVerilog 的 always_comb 。
- 注意阻塞赋值的语句顺序。
- 避免 if/case 条件不完整,否则会产生 latch,可以使用 Systemverilog 中的 always_comb、always_latch、always_ff 语句,编译时会严格检查,也可以在块描述语句 if/case 前对信号赋初值。
- 用 always 语句描述组合逻辑使用阻塞赋值 = 。
- 描述 latch 时使用阻塞赋值 = 。
- 描述时序逻辑使用非阻塞赋值 <= 。
- 在同一个块语句中不要混用两种赋值方式。
- 不要使用 #0 延迟赋值。
- FSM 中的状态名应该使用 parameter。
- FSM 计算下一状态的组合逻辑应该放在一个单独的 always 块。
- FSM 计算下一状态的组合逻辑应该使用 case 语句。
- 每个 FSM 使用单独的 module 描述。
利于维护的编码风格
- 每个项目都使用相同的目录结构,相同的目录结构有利于脚本移植,以及增加复用性和可维护性,同时利于工作交接。
- 项目中每个设计都保持类似的目录结构,理由同上。
- 项目内脚本尽量使用相对路径,需要全路径名称则使用环境变量。
- 对 `include 加载的文件尽量使用完整相对路径,方便设计的集成和移植,或者对所有设计的 include 文件规范命名规则。
- 每个文件只包含一个 module 。
- 文件名和模块名保持一致。
- 测试文件名与被测试模块名相对应,添加 _tb 后缀。
- 每个文件都添加头信息:版权声明、文件描述、创建时间、修改时间、原始作者信息、当前作者信息。
- 注释描述代码功能,而不是行为。
- 每个 parameter 声明后要注释。
- function 要加注释。
- 每个主要逻辑段落要加注释。
- 信号名要有实际意义,并且输入输出信号名信息中要包含输入输出模块。
- 废代码删掉,而不是注释。
- 每个模块逻辑代码尽量不要超过400行,可读性可调试性强。
- 每级缩进建议四个空格,并且不建议使用TAB缩进,不同编辑器对TAB支持不同,可能会导致代码显示乱掉。
- 每行最多72个字符,在恰当的地方换行,并保持对齐。
- 控制流嵌套不超过三级。
- 每行只写一条语句。
- 连续声明语句对齐,并且每行只声明一个。
- 句尾的注释尽量对齐。
- 多次使用的逻辑使用 function 。
- 操作符周围使用一个空格与其他项隔开。
- 所有信号名尽量使用小写字母,并且用下划线将单词分隔开。
- 常数定义和宏定义使用大写字母。
- 模块的名字要体现层次结构。
- 模块的例化名与模块名基本保持一致,不过例化名可不体现层次结构,加 “i_” 或 “inst_” 前缀。
- 信号名要有意义,至少5个字符。
- 模块根据功能命名,不要依据类型或作者命名。
- 项目内命名要规范,避免模块重名。
- 根据功能或逻辑顺序声明端口,而不是方向。
- 低电平有效信号加后缀 “_n” ,如复位信号 “rst_n” 。
- 建议时钟信号统一加前置 “clk_” 。
- 建议复位信号统一加前缀 “rst_” 。
- 建议寄存器信号统一加后缀 “_ff” ,下一拍寄存器加载的值信号添加后缀 “_nxt” 。
- 总线范围定义为 “N-1:0” 。
文章原创,可能存在部分错误,欢迎指正,联系邮箱 cao_arvin@163.com。