文章目录
目录
前言
本专栏的博文都是关于Verilog HDL 语法。目前(2021/9/7),本人接触FPGA和Verilog HDL 语法也差不多有近半年的时间了。对于一些常规的模块编写、IP核配置、仿真等没有太大问题。但是如果涉及到特别复杂的逻辑设计时,也会出现较多的语法空白需要填补。所以我准备以博客的形式重新整理相关的Verilog HDL 语法,做到知识的沉淀。
有很多从事FPGA数字设计的人都说过,掌握Verilog HDL 的基础语法就能够编写将近90%的逻辑,尽管事实确实如此。但是全面一些我觉得会更好。因为设计的正确性和最优性是两个不同的概念。掌握基本的语法足够让你做到正确性,但是要想做到最优设计,必须要对语法有比较全面、透彻的认识。
Verilog HDL 编程风格和编程规范程度都很重要。规范的编程不仅是有助于自己排错查错,也利于别人理解。尽量在初学阶段就逐渐形成自己规范的编程风格。
本文主要介绍一种规范的编程方式,供借鉴。
一、文件声明
每个文件的开始,最好有说明该文件的文字段。主要包括:
1、公司、版权
2、文件名
3、作者
4、日期
5、版本号
6、概述(此部分包括模块的功能描述、注意事项、关键点等)
如果后期对该模块进行版本修改、升级、维护等,在原本的声明文字后再添加对应版本修改信息。方便后期整理维护。
文件声明具体格式参考:
//----------------文件的相关说明-------------------//
// 公司版权:
// 文件名称:
// 功能说明:
// 作 者:
// 设计日期:
// 版 本:
// 相关参考:
//----------------- 版 本 修 改--------------------//
// 改后版本:
// 修改日期:
// 修改作者:
// 修改原因:
二、命名
1、模块(module)命名
一个 Verilog 文件只能包含一个 module ,module 的名称统一小写或者大写。尽量不要大小写混着用。因为大小写切换麻烦。这只是命名,不是设置密码,模块(module)的名称要能够有一定的功能、参数代表性。要做到见名识义。
2、parameter 命名
参数 parameter 的命名统一大写。本地参数 localparam 也统一用大写命名。建议用 parameter 、localparam 参数类型命名一些诸如数据位宽、版本号、状态值等有意义的常数,方便程序改版升级。
3、信号命名
输入信号、输出信号、内部信号等的命名同一大写或者小写,不要混合使用(除了复位信号)。信号名称最好能见名识义。最好在信号名称最前面添加信号性质或者类型标识,方便使用。信号名称不应过长(尽量小于20个字符)。信号名称缩写要有一定的通用性,不能过于生僻。
命名举例:
input I_SYS_CLK, //系统时钟
input I_ARst_n, //异步复位信号 低电平有效
input I_Rst_n, //同步复位信号 低电平有效
input I_ARst, //异步复位信号 高电平有效
input [15:0] I_FFT_DATA_RE,//FFT计算输入数据实部
input [15:0] I_FFT_DATA_IM,//FFT计算输入数据虚部
output [15:0] O_FFT_RES_RE,//FFT计算结果实部
output [15:0] O_FFT_RES_IM,//FFT计算结果虚部
output O_VALID, //输出有效信号
wire W_READ_FLAG;//读标志信号
wire [11:0] W_FIR_RES; //FIR滤波器输出结果
reg R_1_VALID; //valid 信号打一拍
reg R_2_VALID; //valid 信号打二拍
reg [15:0] R_COUNTER; //计数器
三、注释
用 // 注释单独的一行,用 /* */ 注释多行。注释的内容不要冗长。
四、模块
模块的例化名要做到统一,且有规律可循。IP 核的例化也是一样。
一条语句占一行。
模块的接口信号按照输入(input)、双端口(inout)、输出(output)的顺序定义。
多bit信号定义按照【width-1:0】的方式定义。
不能用向量方式定义一组时钟信号。如 input [2:0] CLK
module内不能存在无驱动源的多余信号,也不能存在无驱动的输出信号。
顶层模块中,除了例化模块和它们之间彼此的连线,避免出现其他的逻辑。
子模块的输出建议用寄存器输出。
子模块内避免双向端口(inout),最好在顶层模块处理双向总线。
子模块内禁止用三态逻辑(除非子模块的三态逻辑输出直接供顶层模块使用),顶层模块可以用三态逻辑。
模块内避免存在未连接的端口。
为逻辑升级等准备的端口、信号,现阶段先注释掉。
五、wire、reg
禁止锁存器和触发器在不同的always块中被赋值,形成多重驱动。
always 语句实现时序逻辑用 非阻塞赋值语句,实现组合逻辑用 阻塞赋值语句。同一种信号的赋值不能既是阻塞赋值 又是 非阻塞赋值。
所定义的wire 、 reg 类型的信号不能未使用。
建议不要使用 integer 类型的寄存器。
寄存器类型(reg)的信号要初始化。
除了移位寄存器,一个always语句块中只对一个信号赋值、运算。
六、表达式
表达式内多用小括号()表示运算优先级。
设计中用到的逻辑电平值用基数表示。如:1’b0、8‘b0101_1111、16’hA89B
进行端口声明、信号赋值、变量比较时,一定注意位宽匹配。
七、条件语句
if 、 else if 语句要有 else 语句与之相对应。
case 语句要有default。
if 语句的条件表达式不能是常数。
不建议5级以上的 if-else 嵌套。
条件表达式必须为 1 bit 。如:
// 异步复位信号 低电平有效
if (I_ARst_n == 1'b0)
// 不建议的写法:
if (I_ARst_n == 0)
if (!I_ARst_n)
八、可综合性
避免使用 include 语句;
应采用复位方式初始化,仿真时可用 initial 语句初始化;
不要使用 specify 模块;
不要使用 === 、!== 等不可综合的操作符;
除仿真外不要使用 fork-join 语句、while 语句、repeat 语句、forever 语句、force 语句、release语句、named events 语句、系统任务($);
不能在连续赋值语句中引入 驱动强度 和 延时;
禁止用 trireg 型线网;
禁止用tri1、tri0、triand、trior、型连接;
禁止位驱动型(supply0、supply1)线网赋值;
禁止在RTL代码中实例化门级单元,如:CMOS/RCMOS/NMOS/PMOS/RNMOS/RPMOS
/trans/rtrans/tranif0/tranif1
九、可重用性
进口信号尽量少,接口时序尽量简单;
将状态机电路与其他电路分开,异步电路与同步电路分开,便于后续的综合、约束;
顶层模块中将 I/O 端口、边界测试电路、逻辑设计相区分;
十、同步设计
同一个模块内,时序电路应在时钟的同一个边沿动作。如果有的部分需要用到上升沿而有的部分需要用到下降沿,那么就要用两个模块来设计。
顶层模块中,时钟信号必须可见,不要再模块内部生成时钟信号。要用PLL/MMCM等生成。
避免使用门控时钟、门控复位。
对于同步复位电路,建议在同一时钟域使用单一的全局同步复位电路;对于异步复位电路,建议使用单一全局异步复位电路。
不要在时钟、复位路径上添加任何 buffer。
避免使用latch(锁存器)。
寄存器的异步复位和置位不能同时有效。
避免使用组合反馈逻辑。
复杂电路将组合逻辑和时序逻辑分成独立的 always 块描述。
十一、循环语句
不建议使用循环语句,必要时可用 for 语句。
十二、约束
对全部的时钟频率、占空比进行约束。
对全局时钟的 skew 进行约束。
根据输入输出信号特性进行上下拉约束。
综合设置,建议将 fanout 设置为30。
布局布线报告中 IOB、LUTs、RAM等资源利用率低于80%。
十三、PLL、DCM
使用FPGA内部PLL/DCM资源时,应保证输入时钟抖动小于300ps,以防止失锁。
输入时钟出现瞬断,必须复位PLL/DCM。
十四、关于对齐
代码对齐用空格,尽量不用 Tab 。
参考声明
【1】Verilog 红宝书编程规范。
【2】Verilog 编程规范。
欢迎提出宝贵意见~
文末推荐
掌握了基本的Verilog语法,不去实战练习的话很难有大的突破。牛客网可以为大家提供一个免费的刷题练习的平台。非常推荐大家使用。
链接如下:牛客网-Verilog专项https://www.nowcoder.com/link/pc_csdncpt_zls_verilog这个里面有很多代码题目练习,对于新手来说可以快速掌握Verilog编程的基本语法,对于老手来说也可以巩固自己的编程能力。不用付费免费试用哦。基本是每个即将找工作的人必备的刷题网站。快行动起来吧!