0.前言
强烈推荐一个学习Verilog的刷题网站,HDLBits,从基础语法到一个比较完整的模块设计,提供了完整的练习过程,把上面的题刷完之后基本就可以完成大部分的Verilog程序设计了。不过网站是全英文的,需要一些英语基础。
1.逻辑值
Verilog中存在四种逻辑值
- 逻辑0:表示低电平,对应GND
- 逻辑1:表示高电平,对应VCC
- 逻辑X:表示未知,有可能是高电平,也可能是低电平
- 逻辑Z:表示高阻态,外部没有激励信号,是一个悬空状态。'?'是Z的另一种表达形式。
2.进制格式
Verilog 数字进制格式包括二进制(b)、 八进制(o)、 十进制(d)和十六进制(h),一般常用的为二进制、 十进制和十六进制。
书写格式:[换算成二进制后位宽总长度]['][数值进制符号][与数值进制符号对应的数值]
例如:
- 二进制: 4’b0101 表示 4 位二进制数字 0101
- 十进制: 4’d2 表示 4 位十进制数字 2(对应二进制 0010)
- 十六进制: 4’ha 表示 4 位十六进制数字 a(对应二进制 1010)
注意进制前的数值表示该数换成二进制后对应的位数
位宽与进制默认32位10进制。
用下划线可增加程序可读性不会影响计算机识别数值,如16'b1001_1010_1010_1001 = 16'h5AA5。
换算成二进制后的位宽总长度是非必须的,Verilog会为常量自动匹配合适的位宽。例如, 4’ha 写做 ‘ha 是合法的。
当总位宽大于实际位数,则自动在高位补0或X或Z,总位宽小于实际位数,则自动截断高位超出的位数。
最高有效位MSB(most significant bit)在左边。
3.数据类型
Verilog中数据主要分寄存器和连线两种类型,区别在于驱动方法(赋值方式)、保持方式、硬件实现。
3.1寄存器类型(reg)
寄存器是具有状态保持作用的硬件电路元件,通常会是一个触发器
驱动方式:
过程赋值只能出现在过程语句(initial和always)之后,及reg类型的数据只能在always语句和initial语句中被赋值
如果该过程语句描述的是时序逻辑,即always语句带有时钟信号,则该寄存器变量对应为触发器;
如果该过程语句描述的是组合逻辑,即always语句不带时钟信号,则该寄存器变量对应为硬件连线;
未赋值前,认为处于不定状态X
保持方式:
在下一次赋值前保持不变
硬件实现:
触发器和锁存器等(reg类)
寄存器数据类型有很多种
- reg,行为描述,过程赋值
- integer,32位带符号整形变量
- real,64位带符号实性
- time,63位无符号时间变量
integer,real,time均为过程中数学描述,不对应具体硬件电路,即无法综合
最常用的是reg型,刚开始接触Verilog时,只需要记住reg即可
reg key; //声明一个1位的寄存器
reg [31:0] cnt; //声明一个32位的寄存器
reg [7:0] cnt1,cnt2; //可以一次声明多个相同位宽的寄存器,用‘,’隔开
//可以通过以下形式对寄存器进行位选或片选
cnt[0] //cnt寄存器的最低位
cnt[7:0] //cnt寄存器的低8位
3.2连线类型(wire)
连线数据类型表示结构实体(例如门)之间的物理连线
驱动方式:
连到门或模块的输出,或连续赋值语句assign赋值,信号连线不能出现在过程语句(initial和always)中
无驱动时处于高阻态Z(trireg为X态)
保持方式:
没有电荷保持作用(trireg除外)
硬件实现:
物理信号的连线
连线类型的变量不能储存值,它的值是由驱动它的元件所决定的。
连线数据类型有以下几种
- wire,tri,标准连线,缺省类型
- wor,trior,多重驱动,线或特性
- wand,triand,多重驱动,线与特性
- trireg,电荷保持特性
- tri1,上拉电阻(pullup)
- tri0,下拉电阻(pulldown)
- supply1,电源线,逻辑1
- supply0,底线,逻辑0
最常用的是wire型,刚开始接触Verilog时,只需要记住wire即可
wire data; //声明一个一位的线网类型
wire [7:0] data; //声明一个8位的线网类型
wire [31:0] data1,data2; //可以同时声明多个相同位宽的线网类型,用,隔开
//对线网类型的位选与片选
data[0] //线网类型数据data的最低位
data[7:4] //线网类型数据data的高4位
3.3参数类型
参数其实就是一个常量,在VerilogHDL中用parameter定义或用localparam定义,通过localparam定义的参数只能在本模块内使用,不能被其他模块调用或覆盖。
可以一次定义多个参数,参数与参数之间需要用逗号隔开。
每个参数定义的右边必须是一个常数表达式。
parameter a=1,b=2; //可以一次定义多个参数
parameter a=x+y; //参数定义的右边必须是一个常量表达式,即x+y必须是一个定值
3.4数组
在 Verilog 中允许声明 reg, wire, integer, time, real 及其向量类型的数组。
reg [3:0] cnt [3:0] //由4个4bit寄存器构成的数组
wire [7:0] add [3:0] //由4个4位宽的线网构成的数组
wire data[3:0][7:0] //数组的维数没有限制,可以声明高维的数组
//对数组的赋值操作
cnt[3] = 4'hF; //将cnt中第四个元素(即第四个4位的寄存器)赋值为十六进制的F
data[0] = 1'b1; //此处赋值是违法的,不能省略第二个访问符号,应该为data[0][1] = 1'b1;
3.5注意事项
变量声明时不要对变量进行赋初值操作。赋初值操作应该在复位状态下完成,也建议寄存器变量都使用复位端,以保证系统上电或紊乱时,可以通过复位操作让系统恢复初始状态。
复位时语句块中所有的信号都应该赋予初值,不要漏掉相关信号。
4.运算符
4.1算术运算符
符号 | 用法 | 说明 |
+ | a+b | a加b |
- | a-b | a减b |
* | a*b | a乘b |
/ | a/b | a除b |
% | a%b | a模除b |
如果操作数某一位为 X,则计算结果会全部出现 X。
a = 2'b10;
b = 4'b100x;
c = a+b; //位数不同时可以相加,结果为c=4'bxxxx;
利用FPGA实现乘除法运算十分浪费逻辑资源,因此通常会用移位运算代替,后面会结束具体用法。
4.2关系运算符
符号 | 用法 | 说明 |
> | a>b | a大于b |
< | a<b | a小于b |
>= | a>=b | a大于等于b |
<= | a<=b | a小于等于b |
== | a==b | a等于b |
!= | a!=b | a不等于b |
关系运算符主要是用来做一些条件判断用的,在进行关系运算符时,如果声明的关系是假的,则返回值是 0,如果声明的关系是真的,则返回值是 1
还有两个很少使用的全等与不全等
全等运算符===,把不定态和高阻态也看做逻辑状态进行比较,比较结果不存在不定态,一定是1或是0
不全等运算符!==,把不定态和高阻态也看做逻辑状态进行比较,比较结果不存在不定态,一定是1或是0
4.3逻辑运算符
符号 | 用法 | 说明 |
! | !a | a的非,如果a为1,那么结果为0 |
&& | a&&b | a与b,a与b全为1时结果才为1 |
|| | a||b | a或b,a与b有一个为1,则结果为1 |
逻辑运算符是连接多个关系表达式用的,可实现更加复杂的判断,一般不单独使用,都需要配合具体语句来实现完整的意思
逻辑运算符边只有真或假,非零表示真,零表示假,运算结果也只有真假(1和0二值)
后面还有位运算符,有相似的地方,要注意区分
4.4位运算符
符号 | 用法 | 说明 |
~ | ~a | 将a的每个位取反 |
& | a&b | 将a与b的每个对应位相与 |
| | a|b | 将a与b的每个对应位相或 |
^ | a^b | 将a与b的每个对应位进行异或 |
位运算符的与、或、非与逻辑运算符逻辑与、逻辑或、逻辑非使用时候容易混淆,逻辑运算符一般用在条件判断上,位运算符一般用在信号赋值上
两个变量位宽不一样时,会自动补零
这里 & 和 | 运算符是在两个操作数之间进行的,此外还可以单独对一个操作数进行,称为归约运算符
4.5归约运算符
符号 | 用法 | 说明 |
& | &a | 将a的每一位进行与操作,得到一个1位的结果 |
| | |a | 将a的每一位进行或操作,得到一个1位的结果 |
归约操作符只有一个操作数,它对这个向量操作数逐位进行操作,最终产生一个 1bit 结果。
例如:
a = 4'b1010;
&a; //结果为1&0&1&0 = 1'b0,可以用来判断a所有位是否全文为1
此外还有^,~&,~|,~^等操作符可以进行相同的操作
逻辑操作符、按位操作符和归约操作符都使用相同的符号表示,因此有时候容易混淆。区分这些操作符的关键是分清操作数的数目,和计算结果的规则。
4.6移位运算符
符号 | 用法 | 说明 |
<< | a<<b | 将a左移b位 |
>> | a>>b | 将a右移b位 |
这两种移位运算符都用 0 来填补移出的空位。即一个二进制数,只要一直移位,最后都为0
一般使用左移位运算代替乘法,右移位运算代替除法,每移动一位,就扩大或缩小了2倍
例如
2*a可以用a<<1来代替
通过移位扩大的都是2的倍数倍,对于不是2的倍数的扩大,可以通过分解得到,例如
3*a可以通过(2+1)a得到,即a<<1+a
25*a可以通过(16+8+1)得到,即a<<4+a<<3+a
4.7条件运算符
符号 | 用法 | 说明 |
?: | a?b:c | 若a为真,就选择b,否则选择c |
条件操作符一般来构建从两个输入中选择一个作为输出的条件选择结构,功能等同于 always 中的if-else 语句。
4.8拼接运算符
符号 | 用法 | 说明 |
{} | {a,b} | 将a和b拼接起来,得到一个新的操作数 |
拼接运算符可以进行倍数的复制
{5{1'b1}} //结果为 5'b11111
{2{a,b,c}} // 结果为 {a,b,c,a,b,c}
{3'd5, {2{3'd6}}} // 结果为9'b101_110_110
注意{}的数量