写在前面
- 本篇虽命名为初识VerilogHDl,但是本篇完稿已近四千字。介绍了大部分VerilogHDL的基础知识,其中运算符那一小节不乏有老生长谈的加减乘除运算符,也有独树一帜的位拼接运算符。读者可选择需要的进行浏览,最后一小节系统函数只是粗略带过,笔者后续会在另一专栏利用ModelSim对系统函数进行较为详细的解释,希望大家多多关注。笔者才疏学浅,本篇如有缺点和疏漏在所难免,恳请广大读者批评指正。
记录一下吧:2022年4月25日始,2022年4月26日终。
- 初识VerilogHDL是本专栏第一篇文章,介绍的都是基本知识
- 笔者想通过这一个专栏的写作,分享自己阶段性的学习,共同进步
- 本文简要介绍硬件描述语言VerilogHDL的身世、简单语法、运算符和部分系统函数
VerilogHDL的身世
硬件描述语言的强强对决
当前最流行的硬件设计语言有两种,即VHDL与VerilogHDL,两者各有优劣,也各有相当多的拥护者
- VHDL语言由美国军方推出,最早通过国际电机工程师学会(IEEE)的标准,在北美及欧洲应用非常普遍。
- VerilogHDL语言由Gateway公司提出,这家公司辗转被Cadence所购并,并得到Synopsys的支持。在得到这两大EDA公司的支持后,也随后通过了IEEE标准,在美国,日本及中国台湾地区使用非常普遍。
VHDL和VerilogHDL
- 两者的文件扩展名不一样,VHDL:.vhd,VerilogHDL:.v
笔者才疏学浅,还未学习使用VHDL。此处仅展示VerilogHDL的.v扩展文件。 - VerilogHDL简称为verilog,下文均使用Verilog对VerilogHDL进行代称。
- Verilog和VHDL代码结构不同(此处仅介绍Verilog代码一般规范)
下面引入简单的利用FPGA控制小灯的Verilog代码进行介绍。
-
模块结构(module...endmodul
-
module 模块名(端口列表);
-
输入输出端口说明;变量类型说明;
-
assignyvjv(连续赋值语句);
-
元件例化语句;
-
always@(敏感列表)
begin
...
end
-
endmodule
其中assign语句,元件例化语句,always语句的顺序可以更换。(上图代码仅给出assign语句的赋值使用,写这篇文章只是先对Verilog语言进行简要的介绍,后续会对这些内容具体介绍。)
下面为一个简单的Verilog代码,其中给出了always语句的使用,感兴趣的可以先看一下
Verilog简单语法
-
module:模块开始,endmodule:模块结束
-
input:输入信号,outut:输出信号,inout:输入输出信号
-
wire:线网型变量,在可综合的逻辑中会被映射为一条真实的物理连线;
reg:寄存器型变量,对某一时间点状态有保持的功能,在可综合的时序逻辑中,会被映射为一个真实的物理寄存器。
-
parameter:参数,实例化时参数可修改;
localparam:参数,只能在模块的内部使用,不能进行实例化。
-
一些英文字母表示的进制
h:十六进制、d:十进制、o:八进制、b:二进制
-
常量
基数表示法
格式:【换算为二进制后位宽的总长度】【 ' 】【数值进制符号】【与数值进制符号对应的数值】
8'd171:位宽是8bit,十进制的171;
8'hab:8bit的十六进制ab;
8'o253:8bit的八进制数253;
8'b1010_1011:8bit的二进制数1010_1011,下划线增强可读性。
【换算为二进制后位宽的总长度】:可有可无,Verilog会为常量自动匹配合适的位宽。
当总位宽大于实际位宽,则自动在左边补0,总位宽小于实际位宽,则自动截断左边超出 的位数。
'd7与8'd7:表示相同数值,8'd7换算为二进制就是8'b0000_0111,前面5位补0;
2'd7换算为二进制就是2'b11,超过2位宽的部分被截断;
如果直接写参数,例如100,表示位宽为32bit的十进制数100。
-
阻塞赋值和非阻塞赋值
阻塞赋值:顺序执行、非阻塞赋值:并行执行。(先简要了解一下,后续会重点提到)
Verilog运算符
运算符分类
Verilog语言中运算符所带的操作数是不同的,按其所带的操作数的个数运算符可分为以下三种
- 单目运算符:可以带一个错作数,操作数放在运算符的右边
- 双目运算符:可以带两个操作数,操作数放在运算符的两边
- 三目运算符:可以带三个操作数,这三个操作数被三目运算符分隔开
算术运算符
算数运算符,在Verilog语言中,算数运算符又称为二进制运算符,共有以下表格列出的几种
| 算数运算符的使用 | +、-、*、/、% | %:取模运算 |
|---|---|---|
| 符号 | 使用方法 | 说明 |
| + | a + b | a加上b |
| - | a - b | a减去b |
| * | a * b | a乘以b |
| / | a / b | a除以b |
| % | a % b | a模除b |
在进行整数除法运算时,结果值要略去小数部分,只取整数部分; 而在进行取模运算时,结果值的符号位采用模运算式里第一个操作数的符号位。(下方利用表格给出说明)
| 模运算表达式 | 结果 | 说明 |
|---|---|---|
| 10 % 3 | 1 | 余数为1 |
| 11 % 3 | 2 | 余数为2 |
| 12 % 3 | 0 | 余数为0即无余数 |
| -10 % 3 | -1 | 结果取第一个操作数的符号位,所以余数为-1 |
| 11 % 3 | 2 | 结果取第一个操作数的符号位,所以余数为2 |
注意:在进行算术运算操作时,如果某一个操作数有不确定的值x,则整个结果也为不定值x。
- Verilog实现乘除比较浪费组合逻辑资源,尤其是除法。一般2的指数次幂的乘除法使用移位运算符来完成运算,详情可看下面移位运算符一节。
- 非2的指数次幂的乘除法一般是调用现成的IP,QUARTUS/ISE等工具软件会有提供,不过这些工具软件提供的IP也是由最底层的组合逻辑(与或非门等)搭建起来的。
归约运算符、按位运算符
操作符:与( & )、与非( ~& )、异或( ^ )、异或非( ~^ )、或( | )、或非( ~| )
下面以“&”操作符为例,进行说明,以上各个操作符均可进行这种操作,还请读者独立思考并使用。
以“&”操作符为例,“&”操作符有两种用途,既可以作为一元运算符(仅有一个参与运算的量),也可以作为二元运算符(有两个参与运算的量)。
当“&”作为一元运算符时表示归约与,&m是将m中所有比特相与,最后的结果为1bit。
例如:
&4'b1111 = 1&1&1&1 = 1'b1
&4'b1101 = 1&1&0&1 = 1'b0
当“&”作为二元运算符时表示按位与,m&n是将m的每个比特与n的相应比特相与,在运算的时候要保证m和n的比特数位相同。
例如:
4'b1010 & 4'b0101 = 4'b0000
4'b1101 & 4'b1111 = 4'b1101
逻辑运算符
操作符:逻辑与( && )、辑或( || )、逻辑非( ! )、逻辑相等( == )、逻辑不等( != )
下面以“&&”操作符为例,进行说明,以上操作符除逻辑非这个单目运算符外均可进行这种操作,还请读者独立思考并使用。
以“&&”操作符为例,“&&”表示逻辑与,运算规则:逻辑与运算符号两边只有真或者假,非零表示真,零表示假,逻辑运算符两边都不为零则结果为1,否则为0。
例如:
a = 4'ha、b = 4'd0、c = a && b,则c的值为0。
" ! "是单目运算符,只要求一个操作数,如 !0 = 1
逻辑运算符中“ && ”和“ || ”的优先级别低于关系运算符,“ !”高于算数运算符。
- (a > b) && (x > y)可写成:a > b && x > y;
- (a == b) || (x == y)可写成:a == b || x == y;
- ( !a ) || (a > b)可写成:!a || a>b。
为了提高程序的可读性,明确表达各运算符间的优先关系,建议使用括号。
关系运算符
操作符:" > "," < "," <= " 和 " >= " | 关系运算符 | 说明 | | --- | --- | | a < b | a 小于 b | | a > b | a 大于 b | | a <= b | a 小于等于 b | | a >= b | a 大于等于 b |
在进行关系运算时,如果声明的关系是假的(flase),则返回值是0;如果声明的关系是真的(true),则返回值是1;如果某个操作数的值不定,则关系是模糊的,返回值是不定值。
所有的关系运算符有着相同的优先级,关系运算符的优先级低于算术运算符的优先级。
例如:a < size - 1 等价于 a < ( size - 1 )。
等式运算符
操作符:等于 " == "、不等于 " != "、等于 " === "和不等于 " !== "
- 这四个运算符都是二目运算符,他要求有两个操作数。
- " == "和" != "又称为逻辑等式运算符,其结果由两个操作数的值决定。由于操作数中某些位可能是不定值x和高阻值z,结果可能为不定值。
- " === "和" !== "运算符则不同,它在对操作数进行比较时对某些位的不定值x和高阻值也进行比较,两个操作数必须完全一致,其结果才是1,否则为0。
- " === "和" !== "运算符常用于case表达式的判别,所以又称为case等式运算符、四个等式运算符的优先级别是相同的。
移位运算符
操作符:左移符号( << )、右移符号( >> )
移位运算符是二元运算符,左移符号为“<<”,右移符号为“>>”,将运算符左边的操作数左移或右移指定的位数,用0来补充空闲位。
b <= a << 1;即让a的每一位都往左移动1位,结果赋值给b;
b <= a >> 2;即让a的每一位都往右移动2位,结果赋值给b。
在应用移位运算符的时候一定要注意它的这个特性,那就是空闲位用0来补充,也就是说,一个二进制数不管原数值是多少,只要一直移位,最终全部会变成0。
例如:
4'b1000 >> 3后的结果为4'b0001,4'b1000 >> 4的结果为4'b0000。
移位运算符在使用时,可代替乘法和除法,左移一位可以看成是乘以2,右移一位可以看成是除以2,但要注意位宽的拓展。
位拼接运算符
操作符:{ , }
位拼接运算符由一对花括号加逗号组成"{ , }",拼接的不同数据之间用“ ,”隔开。
例如:
将8bit的a、3bit的b、5bit的c按顺序拼接成一个16位的d,表示方法为:d = {a,b,c};
- 位拼接还可以用重复法来简化表达式:{ 4{ w } }等同于{ w,w,w,w };
- 位拼接也可以用嵌套的方式来表达:{ b,{ 3{ a,b } }等同于{ b,a,b,a,b,a,b }。
缩减运算符
- 缩减运算符是单目运算符,也有与或非运算。
- 由于缩减运算的与、或、非运算类似于为运算符与、或、非运算规则,这里不再详细讲述,可参照位运算的运算规则介绍。
-
其与或非运算规则类似于位运算符的与或非运算规则,但其运算过程不同。位运算是对操作数的相应位进行与或非运算,操作数是几位数则运算结果也是几位数。
-
而缩减运算则不同,缩减运算是对单个操作数进行或与非递推运算,最后的运算结果是一位的二进制数。
-
缩减运算的具体运算过程是这样的:第一步先将操作数的第一位与第二位进行或与非运算,第二步将运算结果与第三位进行或与非运算,依次类推,直至最后一位。
例如:
条件运算符
操作符:" ? : "
条件运算符:" ? : ",是一个三目运算符,即有三个参与运算的量,条件表达式的一般形式为:表达式1 ? 表达式2 : 表达式3。
执行过程是:当表达式1为真,则表达式2作为条件表达式的值,否则以表达式3作为条件表达式的值。
例如:当a = 6,b = 7,c = (a > b) ? a : b的结果为7.
要注意的是,使用条件表达式时" ? "和 " : "是一对,不可以只是用一个。
运算符优先级
-
总的优先级关系为:归约运算符 > 算数运算符 > 移位运算符 > 关系运算符 > " == "和" != " >按位运算符 > " && " 和 " || " >条件运算符;
-
总的来说是单目运算符 > 双目运算符 > 三目运算符。
如果在编写代码的时候对这些关系容易混淆,最好的方式就是使用"( )"来增加优先级。
Verilog的系统函数
Verilog语言中预先定义了一些任务和函数,用于完成一些特殊的功能,它们被称为系统任务和系统函数,这些函数大多数都是只能在Testbench仿真中使用的,使我们更方便的进行验证。
时间尺度预编译指令
-
时间单位和时间精度由值1、10和100以及单位s、ms、us、ns、ps和fs组成。
-
时间单位:定义仿真过程与时间相关量的单位。
-
仿真中使用“#数字”表示延时相应单位的时间,例:#10表示延时10个时间单位的时间,即10ns。
-
时间精度:决定时间相关量的精度及仿真显示的最小刻度。
-
下面这种写法就是错误的,因为时间单位不能比时间精度小。
Verilog中主要的函数有如下这些,在支持Verilog语法的编辑器中都会显示为高亮的关键字。
| 函数 | 功能 |
|---|---|
| $display | 打印信息、自动换行 |
| $write | 打印信息 |
| $strobe | 打印信息、自动换行、最后执行 |
| $monitor | 监测变量 |
| $stop | 暂停仿真 |
| $finish | 结束仿真 |
| $time | 时间函数 |
| $random | 随机函数 |
| $readmemb | 读文件函数 |
下面我们单独介绍它们的功能。
$display用于输出、打印信息
使用格式为:
$write用于输出、打印信息
使用格式为:
$strobe用于输出、打印信息
使用格式为:
$monitor用于持续监测变量
使用格式为:
$finish用于结束仿真;[]stop用于暂停仿真;
使用格式为:
random用于产生随机函数,返回随机数
使用格式为:
readmemh用于读十六进制文件函数
使用格式为: