Verilog学习
基于《HDL数字系统设计与实践》一书的Verilog学习
verilog HDL 层次化设计
模块和端口
- 模块=模块名定义+端口描述+内部功能逻辑
- 逻辑功能描述:变量声明、数据流描述语句、门级实例化描述语句、行为描述语句、任务与函数。
- 关键词:
module
定义模块名字;input
、output
指定端口方向;endmodule
结束模块描述。 - 模块定义格式:
1
2
3
4module 模块名
端口定义
......
unmodule - 端口定义:有两种格式
- 普通风格
1
2
3
4module 模块名
input [位宽-1: 0] 端口名1,端口名2
output [位宽-1: 0] 端口3
inout [位宽-1: 0]端口4 - ANSI C风格
1
2
3
4
5module 模块名
( input [位宽-1: 0] 端口名1,端口名2
output [位宽-1: 0] 端口3
inout [位宽-1: 0]端口4
);
- 普通风格
- 实例化:不允许嵌套,模块之间只能通过实例化
层次化设计思想
- 自顶向下
类似于根据一张图纸造物 - 自底向上
类似于根据材料造物,可以是已知也可以是未知的
Testbench概念
用于测试设计的电路的功能是否正常
verilog HDL基本语法
词法约定
- 空白符:空格
\b
,制表\t
和换行 - 注释:单行
//
,多行*/.../*
(不允许嵌套) - 操作符:单目操作符
~
,双目操作符+
,三目操作符?
- 标识符:
- 第一个字符是字母或下划线
- 区分大小写
- 转义
\
- 空白符结束
- 关键字:均为小写
数据类型
– 线网:除tri
和reg
所有网线类型不能存储数据值
– 变量:抽象的数据存储单元
- 逻辑值与常量
- 基本值:0/1真假,x未知,z高阻(不分大小写)
- 整数:二进制、十进制、十六进制、八进制
格式:[位宽]'[进制][数值]
负数:加负号 - 实数:十进制和科学计数法(可用e或E表示)
- 逻辑强度:supply最强,highz最弱
- 线网类型:
wire
和tri
最常见
格式:wire [7:0] datain, dataoutt
(*两个8位的wire数据)【这是举例】 - 变量类型:
reg
类型、integer
型、real
型、time
型 - 向量:位宽大于1
- 数组:数组中的每一个元素可以是标量也可以是向量
- 参数:
defparam语句
(同样不可嵌套)
- 表达式
- 操作数
- 算术操作符:单双目操作数。单目
+ -
表正负,双目* / + -
运算符号 - 逻辑操作符:逻辑与
&&
,逻辑或||
,逻辑非!
- 关系操作符:
>
<
>=
<=
- 相等操作符:逻辑相等
==
,逻辑不等!=
,逻辑全等===
,逻辑非全等!==
- 按位操作符:反
~
,与&
,或|
,异或^
,同或^~, ~^
- 缩减操作符:缩减与
&
,缩减与非-&
,缩减或|
,缩减或非~|
,缩减异或^
,缩减同或~^, ^~
- 位移操作:右移
>>
,左移<<
,算术左移<<<
,算术右移>>>
- 拼接操作符:格式
{操作1,操作2,操作3,...,操作n}
- 条件操作符:根据条件表达式的值从两个表达式中选择一个表达式作为输出结果,格式
条件表达式? 真表达式 : 假表达式
Verilog HDL行为描述
Verilog HDL基本描述形式
- 数据流描述方法
1
assing [延迟] wire类型 = 表达式
- 行为描述方式
两种语句:initial和always1
2
3
4
5
6
7always @(事件控制列表) begin
···
end
或
intial begin
···
end - 层次化描述方式
1
模块名 实例名
结构化过程语句
initial和always语句不能相互嵌套使用
- initial语句
从仿真的0时刻开始,只执行一次。1
2
3
4
5
6
7
8reg a, b, c; //initial中只有一条赋值语句
initial
a = 1'b0;
//若含多条赋值语句,需要begin end
initial begin
b = 1'b0;
c = 1'b0;
end - always语句
从仿真的0时刻开始,会重新执行命令1
2
3
4reg a;
initial a = 0;
always #50 a =-a;
//从0开始,每隔50个时间单位的反复操作
顺序块和并行块
- 顺序块:按书写顺讯依次执行
- 并行块:
fork
是同时开始 - 块语句的其他特点
- 嵌套块
- 命名块
过程赋值语句
- 阻塞赋值语句
用=
作为赋值符,按顺序执行1
2
3
4
5
6
7reg a, b;
initial begin
a = 1'b0;
b = 1'b0;
#10 a = 1'b1; //阻塞赋值语句对a赋新值,a变为1后才继续执行后面的命令
b = a;
end - 非阻塞赋值语句
用<=
作为赋值符,不会阻塞同一个块语句中的其他语句的执行1
2
3
4
5
6
7reg a, b;
initial begin
a = 1'b0;
b = 1'b0;
#10 a <= 1'b1; //用非阻塞赋值语句赋值,赋值还未完成先完成后面的语句
b <= a; //由于a的赋值未完成,所以b的值还是0
end
条件语句
有三种情况,if
表达式,if-else
表达式,if-else if
表达式。
注意,这里容易弄混,一定要搞清楚。
多路分支语句
当分支特别多时,if
else
语句非常不好用,就可以使用case
语句case
语句的关键词:case
default
endcase
格式如下
1 | case(表达式) |
条件语句和多路分支语句的比较
if...else
语句有优先级,而case
语句没有,是并行的关系。
循环语句
- while循环当表示式为真时,则循环执行里面的雨具;如果为假,中止循环并跳出while。如果while中表达式的值为x或z时,当作假处理。
1
2while(条件表达式)
语句: - for循环在for中,C语言有i++,而verilog没有,改成i=i+1
1
for(循环变量初值;循环结束条件;循环变量增值)
- repeat循环
1
repeat(循环次数表达式)
- forever循环
时序控制
- 延迟控制
分为常规和内嵌,两者区别为:常规是整个语句执行后,在推迟的时间后,赋值语句开始计算;内嵌是开始执行时刻就立即计算表达式右边的值,此值会一直保持至延迟结束。
- 常规延迟
# [延迟值] 语句
- 内嵌延迟
语句 #[延迟值]
- 事件控制
发生某个事件(变量、线网信号或表达式的值发生变化)之后,整个逻辑发生变化
- 边沿敏感事件控制
用边缘敏感符号@
,格式为这里有个关键字:posedge(上升沿跳变)/negedge(下降沿跳变) 记住即可1
2@ 事件
语句;
如果出现了多个敏感事件,则用or
或者,
即可 - 电平敏感事件控制
以信号值变化的边沿为标志的,即一定要达到指定信号的某个值的变化边沿才会触发执行,关键字为wait
当事件为真时,后面的语句才会执行1
wait(事件) 语句;
- 时序控制语句在综合代码中的应用
在进行电路综合时,延迟控制语句会被综合工具自动忽略,当作无延迟处理
组合逻辑建模
数字电路建模方式
电路与代码的一一对应关系
组合逻辑的门级描述
与门(and)、与非门(nand)、或门(or)、或非门(nor)、异或门(xor)、同或门(xnor)、缓冲器(buf)、非门(not)、三台缓冲器控制信号低电平有效(bufif0)、三台缓冲器控制信号高电平有效(bufif1)、三态非门控制信号低电平有效(notif0)、三态非门控制信号高电平有效(notif1)
组合逻辑的数据流描述
连续赋值语句
assign[延迟]wire型变量=表达式;
数据流描述
利用数据流描述可以很方便的描述一个加法器等,用符号就可以了,比如+
*
等
组合逻辑的行为描述
组合逻辑建模实例
- 比较器
- 译码器和编码器
时序逻辑电路
时序逻辑建模概述
时序逻辑电路指的是在verilog HDL所描述的电路中,包含一个或多个存储单元。这些存储单元可以是边沿触发的寄存器,或者是电平触发的锁存器。由于引入了存储单元,时序逻辑电路具有了“记忆”功能,可以记录当前时刻之前的输入激励情况及电路状态。
寄存器和锁存器设计
- 寄存器实例
在always
后面的敏感列表中加入边沿敏感的信号,即可设计出一个简单的寄存器。posedge
是在时钟的上升沿触发并采集数据端口的值;negedge
则在下降沿触发。
1 | module dff |
- 锁存器的设计实例
用verilog描述一个的锁存器,该锁存器在控制信号i_en为高电平时开启,为低电平时锁存器当前值
1 | module latch |
寄存器和锁存器的推断
- 寄存器的推断
- 寄存器是一种时序元件,用于存储数据,并且在时钟信号的上升沿或下降沿触发时更新数据。
- 寄存器在时钟边沿触发时,将其输入数据传递到输出,具有确定的时序行为。
- 在Verilog中,通常使用触发器(Flip-Flops)来实现寄存器。
1 | //举例说明 |
- 锁存器的推断
- 锁存器是一种组合逻辑元件,它不需要时钟信号,而是根据输入信号的变化实时更新输出。
- 锁存器不是时序元件,通常应该避免在数字电路中使用锁存器,因为它们可能会导致不稳定的行为和时序问题。
- 锁存器可能会导致冒险条件,因此在设计中应该谨慎使用。
1 | //举例说明 |
存储器的设计与建模
- ROM建模
ROM是只读存储器
1 | //变量z以数组的方式描述了一个ROM |
- RAM建模
一个电平变化触发的16位宽RAM模型通常是基于时序逻辑的,使用触发器来存储数据。以下是一个简单的电平变化触发的16位宽RAM模型示例,使用2个16位触发器作为存储元件:
1 | module LevelSensitiveRAM16 ( |
同步有限状态机
同步有限状态机(Synchronous Finite State Machine,SFSM)是一种在特定时钟信号下运行的状态机,其中状态的转换和输出的更新都与时钟信号同步。SFSM 包括状态寄存器、组合逻辑块以及时钟信号。
以下是一个简单的 2 状态同步有限状态机的示例,其状态转换和输出更新是同步的:
1 | module SynchronousFSM ( |
时序逻辑建模实例
- 计数器
- 串并/并串转换器
1 | module p2s(clk,en,rsy,pin,sout); |
- 时钟分频电路
练习题
5.4
1 | module 8bit( |
5.6
1 | module 32_RAM(clk,wr,addr,rdata,wdata); |
5.7
1 | module 32_RAM(clk,wr,addr,data); |
5.11
1 | module fsm(clk,rst_n,sel.hready,write,burst,resp_ok) |
行为级仿真模型建模
组合逻辑和时序逻辑建模可以通过门级描述、数据流描述或行为描述来实现,用数据流或行为级语法进行可综合的电路描述,被称为RTL描述,即寄存器传输级描述。而利用门级语法进行描述的,通常称为结构级描述或层次化描述。
概述
verilog提供的时序控制语句主要有3种:延迟控制语句,事件控制语句和条件等待语句。
仿真时间和时序控制
事件控制语句指利用语法@()进行描述,@后面的括号里包含需要的语句
仿真模型建模实例
时钟发生器
简单的仿真环境
从文件读取激励
输出结果监控
当调用$monitor
任务时,参数列表中指定的变量都将被仿真器控制。总线功能模型
总线功能模型(BFM)通常是某个总线主设备的接口电路模型
SoC芯片指在单个芯片上集成一个完整的数字电路系统
各层次verilog HDL描述形式与电路建模
基本的数字单路单元模块
各抽象层次的verilog HDL描述形式
verilog HDL的描述形式可概括分为:
门级描述(或称层次化描述):and nl(out, opa, opb);
数据流描述:assign out=opa&opb;
行为描述:
1 | always@(opa or opb)begin |
- 利用各层次描述进行组合逻辑建模
数据流转换为行为描述的步骤- 将数据流描述中连续赋值语句左边的变量定义为 reg 类型;
- 利用 always 模块描述组合逻辑,将连续赋值语句等号右边的所有信号都加到 always语句后面的变量敏感列表中,且所有信号都是电平敏感的;
- 在 always 模块中利用阻塞赋值语句对左边变量进行赋值,赋值语句等号右边的表达式与连续赋值语句右手边的表达相同。
- 利用各层次描述进行时序逻辑建模
- 利用各层次描述进行行为仿真逻辑建模
任务与函数
任务语句说明
语法定义:
1 | task 任务名; |
任务的调用语法:任务名 (参数1, 参数2, 参数3, ...);
$time 系统函数将返回当前仿真时间
任务和函数的联系和区别
- 函数不能包含时序控制语句,如@()、#10 等;
- 在函数中不能调用任务,而任务可以调用其他任务和函数;
- 函数必须包含至少一个端口;
- 函数必须返回一个值,而任务不能返回值。
$monitor任务
$monitor任务可以用来监控并打印任何指定的变量或表达式
编译预处理
编译指令实现的编程预处理功能
‘define, ‘undef
‘define指令用于定义文本替换;
‘undef指令则是取消前面定义的宏
‘ifdef, ‘else, ‘elsif, ‘endif, ‘ifndef
工程师会根据某些条件选择性地编译某部分代码,同时选择性地忽略掉一些代码,而实现这样功能的编程指令就是条件编译指令。
verilog HDL设计与综合中的陷阱
阻塞语句和非阻塞语句
阻塞语句
阻塞语句用操作符号 = 进行连接,起基本语法格式为寄存器变量(reg) = 表达式/变量
阻塞语句的含义:
- 在多个阻塞语句顺序出现,出现的语句会完全阻塞后面的动作,直到前面的语句被执行结束,即执行的顺序性;
- 在赋值钱不能插入任何动作
非阻塞语句
非阻塞语句用操作符号 <= 进行连接,起基本语法格式为寄存器变量(reg) <= 表达式/变量
规则
- 当描述时序逻辑时,用非阻塞语句;
- 当描述组合逻辑时,用阻塞语句;
- 当在一个always模块中同时描述组合逻辑和时序逻辑时用非阻塞语句;
- 在同一个always模块中不要混合使用阻塞和非阻塞语句。
锁存器的产生与危害
无意识锁存器产生的主要原因有两个:
- 在设计组合逻辑中使用不完整的条件判断语句,即有if没有else,或是在设计组合逻辑中使用不完整的case语句;
- 设计中使用到了组合逻辑反馈等异步逻辑。
组合逻辑反馈
规则:
- 当描述组合逻辑时,禁止使用组合逻辑反馈;
- 当需要描述反馈环路时,必须使用边沿触发。
复位电路设计问题与改进
同步复位电路
同步复位的好处:- 保证整个系统是一个完全同步的系统;
- 复位信号只在时钟的有效边沿处才能复位寄存器,在一定程度上,滤除了复位信号上的毛刺;
- 在某些设计中,复位信号由一系列条件产生,这时同步复位是一种有效的方法,因为同步复位能够滤除组合逻辑在时钟信号之间的毛刺;
同步复位的坏处:
- 同步复位需要时钟参与,复位信号有效期间如果没有工作时钟,则不能复位;
- 同步复位增加了组合逻辑的时延,降低了设计的速度。
异步复位电路
异步复位的好处:- 直接利用内部寄存器的硬复位引脚来复位寄存器;
- 复位信号不再参与到组合逻辑电路中,不影响逻辑的速度;
- 不需要时钟参与。
异步复位的坏处:
- 复位信号上的毛刺很容易导致系统的异常复位;
- 异步复位带来的最大问题就是异步,无论是寄存器复位还是复位的释放都是异步的。异步复位阶段一般没有问题,问题出在复位信号的释放阶段。如果复位信号正好在时钟沿附近释放,寄存器的输出将出现亚稳态。