模块的结构
(1)Verilog的基本设计单元是“模块”(block)。
(2)一个模块由两部分组成,一部分描述接口,另一部分描述逻辑功能。
(3)每个Verilog程序包括4个主要的部分:
①端口定义 ② IO说明 ③内部信号定义 ④功能定义
结构语句
initial语句和always语句
initial语句
在模块中只执行一次。
常用于测试文件的编写,用来产生仿真测试信号,或用于对存储量变量赋初值。
always语句
① always语句一直不断重复活动。
② 但是只有和一定的时间控制结合在一起才有作用。
③ always的时间控制可以是边沿触发,也可以是电平触发。
④ 多个信号用or连接
⑤ always后连接的多个事件名或信号名组成的列表称为“敏感列表”
边沿触发的always块常用于描述时序逻辑。
电平触发的always块常用于描述组合逻辑。
always@( * )表示( * )对后面语句块中所以输入变量都是敏感的。
1.标识符
1.1 标识符的定义:用于定义常数、变量、信号、端口、子模块或参数名称。(Verilog 语言区分大小写)
1.2标识符的使用:标识符使用大写和小写都可以,标识符可以是字母、数字、$(美元符号)和下划线的任意组合(标识符的第一个字符必须是字母或者下划线)。
2.逻辑值
在二进制计数中,单比特逻辑值只有“0”和“1”两种状态,在 Verilog 语言中,为了对电路了进行精确的建模又增加了两种逻辑状态(“X”和“Z”)。
逻辑0:表示低电平,表示GND(接地)。
逻辑1:表示高电平,表示VCC(接电源)。
逻辑X:表示未知,有可能是高电平,也有可能是低电平。
逻辑Z:表示高阻态,没有激励信号,悬空状态。
3.常量
3.1. 常量是 Verilog 中不变的数值,Verilog 中的常量有三种类型
(1) 整数型;
(2) 实数型;
(3) 字符串型。
3.2. 用户可以使用简单的十进制表示一个整数型常量例如:
(1) 直接写 16 表示位宽为 32bit 的十进制数 16;
3.3整数型常量也可以采用基数表示法表示(推荐),例如:
(1) 8’hab 表示 8bit 的十六进制数,换算成二进制是 1010_1011;
(2) 8’d171 表示 8bit 的十进制数,换算成二进制是 1010_1011;
(3) 8’o253 表示 8bit 的八进制数,换算成二进制是 1010_1011;
(4) 8’b1010_1011 表示 8bit 的二进制数,二进制就是 1010_1011。
上述的四种表示方式不同,但都表示的都是相同的值,数值经过运算后的结果也都是相同的。
3.4基数表示法的基本格式
(1)标准格式为: [换算为二进制后位宽的总长度][’][数值进制符号][与数值进制符号对应的数值]。例如:1‘b1;
(2)数值进制符号分别有:
h:表示十六进制。
o:表示八进制。
b:表示二进制。
d:表示十进制。
(3) 值得注意的是:
① [换算为二进制后位宽的总长度] > [与数值进制符号对应的数值]的实际位数时,则自动在[与数值进制符号对应的数值]的左边补足 0 (低位补0)。
② [换算为二进制后位宽的总长度] < [与数值进制符号对应的数值]的实际位数时,自动截断[与数值进制符号对应的数值]左边超出的位数 (只取高位)。
(4) 如果将数字写成“’haa”,那么这个十六进制数的[换算为二进制后位宽的总长度]就取决于[与数值进制符号对应的数值]的长度。
(5) 在基数表示法中如果遇到 x,则在十六进制数中表示 4 个 x,在八进制中表示 3 个 x。
(6) 数字中的下划线没有任何意义,只是为了增加可读性,推荐每 4 个bit 后加一个下划线。
3.5 Verilog 语言中的实数型变量可以采用十进制,也可以采用科学计数法,例如:132.18e2 表示 13218
3.6. 字符串是指双引号中的字符序列,是 8 位 ASCII 码值的序列,例如:
“Hello World”,该字符串包含 11 个 ASCII 符号(两个单词共 10 个符号,单词之间的空格位一个符号,共 11 个 ASCII 符号),一个 ASCII 符号需要 1 个 byte 存储,所以“Hello World”字符串共需要 11 个 byte 存储。
4.数据类型
Verilog 语言中主要的两种变量类型:
① 线网型(wire)
② 寄存器型(reg)
③ 参数数据类型
4.1线网型(wire)
① 表示结构实体之间的物理连线。
②门、连续赋值语句、assign可以驱动线网型(wire)
③如果没有驱动原件连接,wire连接的变量为高阻,值为Z;
4.2 寄存器类型(reg)
① reg类型的默认初始值为 x。
② 只能在always和initial语句中被赋值。
时序逻辑:always语句带有时钟信号,寄存器变量对应为触发器。
组合逻辑:always语句不带有时钟信号,寄存器变量对应为硬件连接。
4.3 参数类型
① 本质是一个常量,用关键字parameter定义常量。
② 可以一次定义多个参数,参数间用逗号隔开。
③ 参数定义的右边必须是一个常数表达式。
定义格式:parameter A = 4‘d1 ;
补充说明:parameter 和 localparam的区别:
parameter :全局参数定义,可在整个设计中传递参数。
localparam:仅限于当前模块的参数定义,跨模块不可用。
5.赋值语句:阻塞赋值(=)和非阻塞赋值(<=)
5.1 阻塞赋值:b = a ;计算RHS(右手语句)并同时更新LHS(左手语句)
阻塞赋值的操作也称为阻塞型过程赋值。阻塞型过程赋值语句的特点如下:
① 在 begin-end 串行语句块中的各条阻塞型过程赋值语句将以它们在顺序块后排列次序依次得到执行;
② 阻塞型过程赋值语句的执行过程是:首先计算右端赋值表达式的值,然后立即将计算结果赋值给“=”左端的被赋值变量。
③ 在串行语句块中,下一条语句的执行会被本条阻塞型过程赋值语句所阻塞,只有在当前这条阻塞型过程赋值语句所对应的赋值操作执行完后下一条语句才能开始执行。
5.2 非阻塞赋值:b <= a ; 非阻塞赋值的操作过程可以看做两个步骤: 1. 赋值开始时,计算RHS(右手语句)。 2. 赋值结束时,更新LHS(左手语句)。
① 非阻塞赋值的概念是指:在计算非阻塞赋值的RHS以及更新LHS期间,允许其他的非阻塞赋值语句同时计算RHS和更新LHS。
② 非阻塞赋值只能用于对寄存器类型的变量进行赋值,因此只能用在initial块和always块等过程块中。
③ 时序逻辑的always块用非阻塞赋值(<=)(时序逻辑电路结构往往与触发沿有关系,只有在触发沿时才可能发生赋值变化)
6.运算符
6.1 关系运算符:
(1) a < b,a 小于 b。
(2) a > b,a 大于 b。
(3) a <= b,a 小于或者等于 b。
(4) a >= b,a 大于或者等于 b。
在进行关系运算时,声明的关系为假(false),返回值是 0;
声明的关系是真的(true),返回值是 1;若某个操作数的值不定,则关系是模糊的,返回值为 x 。
6.2归约运算符、按位运算符和逻辑运算符
(1) 归约运算符和按位运算符
“&”操作符有两种用途,既可以作为一元运算符(仅有一个参与运算的量),也可以作为二元运算符(有两个参与运算的量)。
当“&”作为一元运算符时表示**归约与**。&m 是将 m 中所有比特相与,最后的结果为1bit。例如:
1
2
&4’b1111 = 1&1&1&1 = 1’b1
&4’b1101 = 1&1&0&1 = 1’b0
1
2
当“&”作为二元运算符时表示按位与。m&n 是将 m 的每个比特与 n 的相应比特相与,在运算的时候要保证 m 和 n 的比特数相等,最后的结果和 m(n)的比特数相同。例如:
4’b1010&4’b0101 = 4’b0000
4’b1101&4’b1111 = 4’b1101
1
2
“&”、“^”、“^”、“|”、“~|”同理。
(2) 逻辑运算符
!:逻辑取反,结果只有0或1 。
&&:逻辑与,只有a,b都为1,a&&b才都为1,结果只有0或1。
||:逻辑或,a,b有一个为1,a || b才为1,结果只有0或1 。
(3) 位运算符
~:按位取反(把每一位都取反)
&:按位与(每一位对应相与)同时为1才为1。
| :按位或(每一位与对应的位相或)有1为1。
^:按位异或(每一位与对应的位相异或)相同为0,不同为1。
(4) 移位运算符
① <<:左移符号(将操作数左移指定位数,用0补充空闲位),移一位为乘2。
② >>:右移符号(将操作数右移指定位数,用0补充空闲位),移一位为除2。
一个二进制数不管原数值是多少,只要一直移位,最终全部会变为0。
例如:4’b1000 >> 3 的结果为 4’b0001 。
4’b1000 >> 4 的结果为 4’b0000 。
(5) 条件运算符(? : )
条件运算符(?:)是一个三元运算符,即有三个参与运算的量。
由条件运算符组成的条件表达式的一般形式为:表达式 1 ? 表达式 2 : 表达式 3 。
判断依据是:当表达式 1 为真,则表达式 2 作为条件表达式的值,否则以表达式 3 作为条件表达式的值。例如:
当 a = 6, b = 7,条件表达式(a > b) ? a : b 的结果为 7。
注意事项:
使用条件表达式时“?”和“:”是一对,不可以只是用一个;
条件运算符从右向左结合,例如:
a > b ? a : c > d ? c : d
//等价于
a > b ? a : (c > d ? c : d)
1
2
3
一般在assign语句中使用。
(6)位拼接运算符({,})
位拼接运算符由一对花括号加逗号组成“{ , }”,拼接的不同数据之间用“,”隔开。位拼接运算符的作用主要有两种,一种是将位宽较短的数据拼接成一个位宽长的数据;另一种是可以通过位拼接实现移位的效果。
1、 实现增长位宽的作用
如果需要将 8bit 的 a、3bit 的 b、5bit 的按顺序拼接成一个 16 位的 d,表示方法为:
wire [15:0] d;
d = {a, b, c};
1
2
2、 实现移位的作用
① 右移,din 是 1bit 的串行数据,假如刚开始传来的数据是 1,后面的数据都是 0,则第一个时钟时 4bit dout 的值为 4’b1000,第二个时钟时 dout 的高三位放到最后,新来的 0 放到 dout的最高位,变为 4’b0100,从而实现了数据的右移功能。
always@(posedge clk or negedge rst_n)
if(rst_n == 1’b0)
dout <= 4’b0;
else
dout <= {din, dout[3:1]}; //右移
1
2
3
4
5
②左移同理,din 是 1bit 的串行数据,假如刚开始传来的数据是 1,后面的数据都是 0,则第一个时钟时 4bit dout 的值为4’b0001,第二个时钟时 dout 的低三位放到最前面,新来的 0 放到 dout 的最低位,变为 4’b0010,从而实现了数据的左移功能。
always@(posedge clk or negedge rst_n)
if(rst_n == 1’b0)
dout <= 4’b0;
else
dout <= {dout[2:0], din}; //左移
1
2
3
4
5
7.条件语句:if-else 语句与 case语句
条件语句必须在过程块中(initial和always语句引导)中使用
①if-else语句,只有if语句中的值为1,才按真处理,允许if语句的嵌套。
Verilog HDL 语言中存在两种分支语言:
① if-else 条件分支语句
② case 分支控制语句
if-else 条件分支语句:
if-else 条件分支语句的作用是根据指定的判断条件是否满足来确定下一步要执行的操作。它在使用时可以采用如下三种形式:
(1)只有if,没有else 不推荐!
if(<条件表达式>)
语句或语句块;
1
2
在 if-else 条件语句的这种使用形式中没有出现 else 项,这种情况下条件分支语句的执行过程是:
如果指定的<条件表达式>成立(也就是这个条件表达式的逻辑值为“ 1”),则执行条件分支语句内给出的“语句或语句块”,然后退出条件分支语句的执行; 如果<条件表达式>不成立(也就是条件的表达式的逻辑值为“0”、“x”、“z”),则不执行条件分支语句内给出的“语句或语句块”,而是直接退出条件语句的执行。这种写法如果在always 块中表达组合逻辑时会产生 latch,所以不推荐这种写法。
(2) if 配套 else 推荐!!!
if(<条件表达式 1>)
语句或语句块 1;
else if(<条件表达式 2>)
语句或语句块 2;
………
else
1
2
3
4
5
6
在执行这种形式的 if-else 条件分支语句时,将按照各分支项的排列顺序对各个条件表达式是否成立做出判断,当遇到某一项的条件表达式成立时,就执行这一项所指定的语句或语句块;如果所有的条件表达式都不成立,则执行最后的 else 项。这种形式的 if-else 条件分支语句实现了一种多路分支选择控制。这种写法是我们在使用根据波形写代码的方法中最常用的一种写法。
(3) Verilog HDL 允许 if-else 条件分支语句的嵌套使用,但是不要嵌套太多层,也不推荐这种嵌套的写法,因为嵌套会有优先级的问题,最后导致逻辑混乱,if 和 else 的结合混乱,代码也不清晰,如果写代码时遇到这种情况往往是可以将其合并的,最终写成(2)的形式
if(<条件表达式 1>) //外层 if 语句
if(<条件表达式 2>) //内层 if 语句 1
语句或语句块 1;
else //内层 else 语句 2
语句或语句块 2;
else //外层 else 语句 1
语句或语句块 3;
case 分支控制语句
注意事项:
① 分支表达式的值互不相同。
② 表达式的位宽必须相等:不能用‘bx代替n’bx。
case 分支语句是另一种用来实现多路分支控制的分支语句。与使用 if-else 条件分支语句相比,采用 case 分支语句来实现多路控制将显得更为方便与直观。
case分支语句主要有三种:
① case:
② casez:比较时不考虑表达式中的高阻值。
③ casex:不考虑高阻值Z和不定值X。
case(<控制表达式>)
<分支语句 1> : 语句块 1;
<分支语句 2> : 语句块 2;
<分支语句 3> : 语句块 3;
………
<分支语句 n> : 语句块 n;
default : 语句块 n+1;
<控制表达式>代表着对程序流向进行控制的控制信号:各个<分支表达式>则是控制表达式的某些具体状态取值,在实际使用中这些分支项表达式通常是一些常量表达式:各个“语句”则指定了在各个分支下所要执行的操作,它们也可以是由单条语句构成,处于最后的、以关键词 default 开头的那个分支项称为“default”分支项,它是可以缺省的。
case 语句的执行过程:
(1) 当“控制表达式”的取值等于“分支项表达式 1”时,执行第一个分支项所包含的语句块 1;
(2) 当“控制表达式”的取值等于“分支项表达式 2”时,执行第二个分支项所包含的语句块 2. …………;
(3) 当“控制表达式”的取值等于“分支项表达式 n”时,执行第 N 个分支项所包含的语句块 n;
(4) 在执行了某一分支项内的语句后,跳出 case 语句结构,终止 case 语句的执行。case 语句中各个“分支项表达式”的取值必须是互不相同的,否则就会出现矛盾现象。
8.Verilog 语言中的系统任务和系统函数
Verilog 语言中预先定义了一些任务和函数,用于完成一些特殊的功能,它们被称为系统任务和系统函数,这些函数大多数都是只能在 Testbench 仿真中使用的,使我们更方便的进行验证。
`timescale 1ns/1ns //时间尺度预编译指令 时间单位/时间精度
1
时间单位和时间精度由值 1、10、和 100 以及单位 s、ms、us、ns、ps 和 fs 组成。
时间单位:定义仿真过程所有与时间相关量的单位。
仿真中使用“#数字”表示延时相应时间单位的时间,例#10 表示延时 10 个单位的时间, 即 10ns。
时间精度:决定时间相关量的精度及仿真显示的最小刻度。
主要的函数有如下这些,在支持 Verilog 语法的编辑器中都会显示为高亮关键字
① $display //打印信息,自动换行
② $write //打印信息
③ $strobe //打印信息,自动换行,最后执行
④ $monitor //监测变量
⑤ $stop //暂停仿真
⑥ $finish //结束仿真
⑦ $time //时间函数
⑧ $random //随机函数
⑨ $readmemb //读文件函数 b表示读二进制文件函数。
//格式“%b+%b=%d” 格式控制,未指定时默认十进制
%h 或%H //以十六进制的形式输出
%d 或%D //以十进制的形式输出
%o 或%O //以八进制的形式输出
%b 或%B //以二进制的形式输出