小生才疏学浅,孤陋寡闻,下文若有不当之处,还请赐教
本文记录verilog可综合代码的学习记录,不可综合的代码部分可参考参见链接:
IC验证——testbench编写_ic测试程序_KGback的博客-CSDN博客
一、代码编写
1.1 编写规范
- 排版
input,output,inout,reg,wire,parameter不缩进,且每次声明各占一行;
always,task,function不缩进;
输入输出信号的宽度定义与关键字之间,信号名与宽度间用空格分开;相同类型的定义对齐;
always中一般要用begin end区分,if else中仅有一个语句时,不使用begin end - 命名风格
端口,信号,变量名所用字母小写,若含义较复杂,可分段描述(三段),如tx_data_val;
函数名,宏定义,参数定义用大写;
低电平有效信号,后加"_n";
建议模块的input/output端口加 “ _i/_o ”;
无条件寄存的寄存信号在原信号上加ff1,ff2...如data_in_ff1;
不能用reg作为后缀名,因为综合工具会给寄存器自动加_reg,此时会扰乱网表可读性 - 表达式书写
在长式子中适当添加括号;
赋值时需给常数注明比特宽度,如32‘habcd - 条件判断语句
if else必须配合使用,case中也要添加default - FSM
状态机的时序逻辑部分和组合逻辑部分要分开写
推荐使用三段式写法,即两个组合逻辑电路和一个时序逻辑电路 - 注释
最好每个always前都要加注释
1.2 Verilog行为级描述
本节只描述可综合语句。
(一)标识符
Verilog中的标识符包括模块、变量、端口、函数等。
(1) 关键字
变量类型除了parameter等参数外,硬件电路类型只有线网型和寄存器型两种。
- wire
线网型,可理解为实际电路的导线,可作任意表达式的输入
不存储值,只有受到驱动才有效;
如果某信号出现在assign等号的左边,则该信号必为wire型;模块被例化时,上层模块传递到目标模块的信号也应是wire类型
e.g.
wire d;
assign d=1'b1;
- reg
寄存器类型,可做任意表达式的输入
如果某信号在always或initial模块中被赋值,则该信号必为reg型
输出信号尽量设置成reg类型,即采用寄存器输出方式。原因是组合逻辑输出存在毛刺,寄存器输出非常干净,毛刺较少
赋值缺省为x态
可以在声明时赋初值。
e.g.
reg a=1; - integer
和reg一样是寄存器类型,通常表示循环变量,即循环次数
结合for循环使用
由于verilog中没有定义大小(size)整数缺省为32位,因此定义数字最好用完整形式 如8’d50
赋值缺省值为x - parameter
定义符号常量
用标识符代替常量,提高代码可读性
- 定义数组(reg型的扩展)
也被称为memory 型数据。在Verilog中通过对reg数据建立数组来对存储器进行建模,verilog中没有多维数组存在,memory型数据是通过扩展reg型数据的地址范围实现的。存储器的地址索引必须是常数表达式(n-1,m-1等必须是常量,符号常量也可以)。
verilog-1998只支持一维数组,verilog-2001支持二维数组。
如:reg [n-1:0] array [0:m-1]; //包含了m个n位数据的数组array。
在这里[n-1:0]定义了存储器中每一个存储单元的大小,即n位寄存器。array名后面的[0:m-1],表示定义的存储器中有多少个这样的寄存器。
(2) 操作符
- << 和 >>
Verilog中的逻辑移位运算
符号右边指代移位位数,是无符号数;如若值为X或Z,则运算结果为X - <<<和>>>
算术左移和右移,右移高位补符号位 - ^
异或,位操作 - ~ 和 !
~表示按位操作,与&、|同类,输出单bit和多bit都有可能;
!表示逻辑操作,与&&、||同类,输出为单bit。
对于1 bit信号,处理方式相同;而多bit信号则不同。 - === / !==
判断全等或不等,可包含x,z判断;双X或双Z则为1;
不可综合,只用于行为描述
参考链接:verilog的“==”与“===”_verilog ===-CSDN博客 - 缩减运算符:| 、& 等
参考链接:Verilog与SystemVerilog——运算操作符_systemverilog 幂运算_SD.ZHAI的博客-CSDN博客
(3) 特殊符号
- { }
表示拼接,{第一位,第二位...};
{{ }}表示拼接运算,例如:{4{a}}等同于{a,a,a,a};
{{127{8'h10} }, 8'h55},127{8'h10}外面必须加大括号 - $
表示系统任务和函数,用于仿真、调试和验证
在系统任务名称前加$使之与用户定义的任务和函数相区分,比如常用的$display(显示信号值),$monitor(监视信号值),$time(返回当前仿真时间),$stop(暂停仿真),$finish(结束仿真)等 - #
(1) #(a,b,c.......)表示改变参数型常量(parameter)的值。
(2) #10 表示延时10个时间单位。 - `
宏定义标志 - 数组: ram[i*8 +: 8]
从“:”左边索引开始,一共有“:”右边数字的位数,其中 +号是递增,-号是递减
例如ram[2*8+:8]表示为: ram[23 : 16]
例如addr[20+:3]表示为:addr[23-1:20],即addr[22:20]
(二)数据流描述语句
(1) assign——持续赋值语句
用来描述组合逻辑
- 一个assign语句等式右边的变量变化即执行
- 连续驱动,一个assign可以多次执行,始终处于活跃状态
- 多个assign并发执行
- assign和always不可互相嵌套
- 只有线网型变量才能在assign语句中赋值
(2)
(三)过程结构
(1) always过程语句块
描述逻辑功能(组合/时序逻辑)
- 一个always块的执行需要触发条件
- 一个always块可以多次执行
- 多个always块并发执行
- 单个过程块内是顺序执行的
- 多个过程块(包括initial过程块)在仿真一开始便并行执行,他们之间的同步是通过信号的变化(event触发)、对特定事件的等待(时钟周期)或时间(固定延时)来完成。
- 分为电平敏感和边沿敏感
电平敏感:一般描述组合逻辑,敏感列表必须包含所有出现在等式右边和条件中的信号,always块中赋值对象不能出现在等号右边。例如:always @(a or b)、always @(*)等
边沿敏感:一般描述时序逻辑,等号左边的变量可以出现在等号的右边 - 只有寄存器型变量才能在always中被赋值
(2) 过程赋值语句:阻塞赋值(=)和非阻塞赋值(<=)
如果后面的赋值语句要等前面的赋值语句完成才执行,称为阻塞,即一个begin/end中的阻塞赋值语句是顺序执行的;
如多条语句同时执行,则称为非阻塞,即一个begin/end中的非组设赋值是并发执行的
(1) always块中时序电路建模时,用非阻塞赋值。时序电路中,对于阻塞赋值,硬件没有对应的电路,因而综合结果未知
(2) always块中建立时序和组合逻辑电路时,用非阻塞赋值。
(3) always块建立组合逻辑模型时,用阻塞赋值。
(4) 在同一个always块中不要既用非阻塞赋值又用阻塞赋值。
(5) 不要在一个以上的always块中为同一个reg变量赋值。
(四)块语句
诸如if、case、for等语句中如果存在两条或两条以上的语句需要用块语句封装。可综合的块语句只有begin...end。
(1) 条件说明语句(if-else/case)
条件说明语句必须放在always和Initial过程块内部;同时语句必须完整if-else/case-default,否则可能会综合出锁存器。
if else和case的选择:
if else的综合是由与或非门构成的,也可能一组多路选择器;case结构的综合是由一条多路选择器组成。
对于要写出平行结构的条件,优先写成case结构,例如地址译码等;而条件之间由重复和嵌套的情况则写成if else结构
(2) for循环语句
(3) 有名块
begin后加: block_name,但会增加新的层次。
e.g.
begin: Hello_kevin //块名定义为Hello_kevin
...
end
(4) generate
Verilog-2001中新增的描述。
定义
generate-for结构
generate内不能嵌套多个for循环,本质上,for循环是电路功能描述,generate是复制电路逻辑,是一种抽象级的描述。
generate-if结构
同样也可以使用case
(五)预编译表述
在verilog解析器在编译之前会对verilog代码进行预编译处理,主要功能是将verilog中的参数实例化。主要处理的内容包括
- `ifdef、`else、`endif
条件编译 - `include
将一个源文件包含到另一个源文件中,支持重复使用,资源共享
格式:`include "文件名" 或`include "路径/文件名" - `define和`undef
使用有意义的名字替代没有意义的数字、符号、表达式——提高可读性,可移植性。即用标识符代表字符串,无论数字还是算术运算符,都被视为“字符串”,在预编译阶段处理时将宏名替换为字符串。
功能类似parameter,但`define可在模块外使用,而parameter只能在模块内使用
(六)verilog中支持的数学函数
$clog2(a)
类似于算式log2,即计算a的位宽
1.3 Verilog的门级建模
通过基本的逻辑门基元互连来描述电路,可以在电路和模型间提供一种更紧密的点对点映射方法。当行为级描述的综合记过满足不了性能、面积、功耗等指标要求时,才手工进行精确的电路级设计,建立门级模型。这种方法很直观,但不适合大规模复杂系统建模。
基元集合
Verilog定义了26个门级基元和开关级基元:
门级基元有四类,共14个,包括多输入门(and/nand/or/xor)、多输出门(buf/not)、三态门(bufif0/bufif1)、上拉门/下拉门。
开关级基元:
mos开关(nmos、pmos、cmos、rcmos、rnmos、rpmos)和双向开关(tran、tranif1、tranif0、rtran、rtranif1、rtranif0)
描述语法
nand1(x2, in1.in2)
1.4 verilog的不同版本
当前verilog有verilog-1995、verilog-2001等版本。
verilog-1995只允许定义一维数组
verilog-2001允许定义二维数组,但没有定义EDA工具如何存储数组元素。
补充:systemverilog的扩展
增加了用户自定义(typedef)、枚举(enum)、结构体(struct)等类C的数据类型。
增加bit用于抽象建模
四态数据类型:logic
具有四态逻辑0、1、X、Z,是一种更直观地描述通用的针对硬件的数据类型
可以和verilog的reg,互换使用,还可有限地替代wire数类型。
二、Verilog组合逻辑电路的描述
2.1 组合逻辑的描述方法
真值表
门级原理图
布尔方程式
2.2 组合逻辑的优化方法
速度是优化目标时,选用增加流水线或香农扩展算法、逻辑复制等优化关键路径延时,以面积换速度。
面积和功耗是优化目标时,就要资源共享、逻辑复用来节约面积。
(1)流水线时序优化
把规模较大、时延较长的组合逻辑分成几个级,在每一级插入下一级的输入,即用寄存器合理分割较长的组合逻辑路径。
这样各级中最长的组合路径延迟决定了系统的时钟频率。
优点:
改善电路性能,即缩短关键路径延时,提高主频,提高吞吐率
缺点:
首次输出延迟增大,且流水线设计以消费更多寄存器资源为代价(空间换时间)
(2)模块复用和资源共享
从功能模块编码角度考虑面积(时间换空间),即多个同类功能在互斥条件下分时复用一个硬件部件。
本质上这种优化是源操作数的计算在进去操作前实现的多,还是在当前操作实现的多。如果在之前实现的多,则体现了模块复用的思想。
这样节约了面积,减少了功耗,有时可能会损失一些性能
考虑角度:尽量使用移位操作或加法器替代乘法器,尽量用选择器替代加法器
如图补码平方器的实现电路:
转换为:
(3)逻辑复制
通过增加面积来改善时序条件(空间换时间),与模块复用相反
例如地址计算电路:
增加面积后:
(4)香农扩展运算
也是一种逻辑复制,以面积换速度的方法,本质上即布尔逻辑扩展,是卡诺逻辑化简的反运算,计算公式如下:
本质上通过增加MUX来提高某个信号的优先级、可明显降低基于该信号的组合路径延时,从而提高该关键路径的工作频率。
例如表达式in0[7:0],in1[7:0],in2[7:0]都为8bit的数据,信号late和en为1位的控制信号,late是本设计的关键路径,时延很大,
那么可通过香农扩展运算表示为:
(5)其他的编码技巧
1)增加括号使逻辑并行

2.3 组合逻辑关键路径
有些信号的路径比较长,信号本身来的比较晚,从而造成时序电路的建立时间不够,建立时间是时钟沿跳变前需要信号保持状态的最小时间,如果这个时间不够,将导致时钟周期更长,这样就降低了电路的工作频率,这种信号路径就是关键路径。
例如组合逻辑描述,如果b信号在进入该逻辑前延迟最大,可改成右图设计,使其靠近输出,从而减小b到Y的延迟。
修改电路描述的原则是:
延迟大的信号最后再计算,先作延迟小的运算。
2.4 常见问题
敏感表不全
可能会使综合前仿真时无法触发与未列出的敏感信号相关的仿真进程,使得综合前后的仿真结果不一致。 最好使用always@(*)
组合电路引入锁存器
当组合逻辑电路有reg时,如果reg随相关语句块的任何一个输入的变化而变化,则不会引入硬件寄存器;如果reg并不是随任意的输入变化而变化,则会综合出Latch。因此在综合后一定要检查报告中生成的latch是否是需要的。
Latch的是一种时序逻辑电路,电路结构可参考:
数字IC设计——数字电路基本元器件-CSDN博客
出现的原因表述:
1)if语句不完整,且信号在条件语句前为赋值
2)分支语句(case,casex等) 中,分支语句不完整或分支语句中对信号赋值说明不完整,且信号在分支语句前没有初始赋值。
例如:
组合电路反馈
即组合逻辑环,组合电路行程的环路
组合反馈环路的逻辑功能完全依赖于反馈环路上的布线延迟和门延迟,如果这些延迟有任何改变,整体逻辑功能将改变。
组合反馈环路是数据同步设计的大忌,最容易因为振荡、毛刺、时序违规等问题引起整个系统的不稳定和不可靠,也会造成后端静态时序分析时的不精确。
因此当综合工具报告有组合逻辑环,必须消除。
三、Verilog时序逻辑电路的描述
时序逻辑主要由组合逻辑和存储电路构成,有了存储,记忆特性就成了时序逻辑电路的基本特征,输出不仅和当前输入有关,还和电路原来的状态有关。
3.1 时序逻辑电路的分类
同步时序电路
所有存储单元状态变化都由同一时钟信号控制,比较容易满足建立时间和保持时间的要求。
同步设计一般使用触发器实现同步时序,可以很好地避免毛刺,便于后端时序分析(STA),插入扫描链,EDA工具保证电路系统时序收敛。也有利于模块移植。
低功耗设计尝试用latch做的门控时钟。
异步时序电路
不存在全局时钟,各触发器翻转的时间不定,电路的核心由组合逻辑实现,比如异步FIFO/RAM的读写信号。设计复杂性增加,数据传输效率较低,最大的问题是容易产生毛刺,影响电路可靠性、稳定性
时钟控制方式
沿触发(触发器)
电平触发(锁存器)
3.2 时序电路复位
复位是为了使电路上电进入预知的初始状态,也可以使电路在错误中恢复。
同步复位
敏感列表中只有时钟沿信号
有利于时序分析,但会带来额外的单元门和路径延时。同步复位仅在时钟的有效沿生效,可有效避免因复位电路毛刺造成的亚稳态和错误,增强了电路的稳定性。如果复位必须由一组内部条件产生,推荐使用同步复位。
复位信号长度大于时钟周期才能保证可靠复位
always @(posedge clk)
if (~reset) ....
异步复位
敏感列表中加入复位信号的有效沿。异步复位作用与时钟沿无关,如果异步复位释放时间与时钟有效沿到达时间一致,容易造成触发器输出亚稳态。如果异步复位的逻辑树的组合逻辑产生了毛刺,则毛刺的有效沿会使触发器误复位。
设计简单,节约逻辑资源,复位与时钟工作无关;
如果Fountry提供的单元库带有异步复位的触发器,就能保证数据路径是干净的:数据路径上没有任何复位信号
always @(posedge clk or negedge reset)
if (~reset) ....
异步复位+同步释放
3.3 状态机
状态机是描述时序电路的基本方法,非常适合有先后顺序和逻辑规律的场景。
根据状态机的数量是否是有限个,可将状态机分为FSM和ISM
FSM的基本要素:状态、输出、输入
Moore和Mealy型状态机
根据状态机的输出和输入关系可分为Moore型和Mealy型
Moore的输出只和当前状态CS有关。
Mealy的输出和当前状态以及输入有关。
有限状态机
同步时序逻辑电路的一种描述方式,表示电路状态的一种改变
FSM状态的编码方式
共有四种顺序编码、Gray编码、One-hot编码、Johnson编码
FSM的三段式描述
四、Verilog特殊处理
信号的赋值状态
四值系统,取指为0,1,x(未知态),z(高阻态/浮空量)
出现X的情况通常是信号没有赋值,或者一个变量的赋值有多个来源;
出现Z的情况通常是对三态总线进行建模
信号X态的处理
出现X态的原因:
输入端口如果没有信号输入即可表现为X态
X态导致的后果:
在仿真状态下,X态的传播可能会导致仿真出错。
解决办法:
https://kgback.blog.csdn.net/article/details/127923026
verilog中的数据类型
线网型
net,可理解为实际电路的导线,不存储值,只有受到驱动才有效;
包含如下种类:
wire/tri/wor/wand/supply0/supply1/tri0/tri1
supply1/supply0:电源和地,不可综合
trireg: 能保存电荷的net,不可综合
tri1/tri0:无驱动时上拉/下拉,不可综合
寄存器型
表示临时存储数据的变量,居于记忆特性,但不能认为是实际电路的寄存器。
包含如下类型:
reg/interger/time/real/realtime
real: 双精度的带符号浮点变量,用法和integer类似
reg类型的综合
(原)verilog中的reg类型变量,一定会综合出触发器吗?_weixin_30284355的博客-CSDN博客
冒险和竞争
一个逻辑门的两个输入端的信号同时向相反方向变化,而变化的时间有差异的现象称为竞争,推荐使用非阻塞赋值或特定的信号延迟来解决同步的问题。
由竞争而可能产生输出干扰脉冲的现象称为冒险。
五、常见errors/warnings
好记性不如烂笔头——记录项目过程中的编译错误及解决方法(持续记录中)-CSDN博客
路径名错误
指代用户目录的~不要用,在某些版本下会找不到文件
编码错误
- 信号指定不明
例如:
output [31:0] count; #定义信号
reg count;
该写法错误,count会被综合成1 bit。