Verilog 学习
前言
嵌入式课程和导师的专长都是硬件电路设计,面向应用部署算法,因此有必要了解掌握Verilog语法及其设计思想。
名词解释
VHDL : VHSIC : Very High Speed Integrated Circuit Hardware Description Language.
EDA : Electronic Design Automation
Verilog的五种抽象模型:系统级,算法级,RTL级(Register transfer level)寄存器级,门电路级,开关电路级(深入到三极管内部)
准备工作
使用vscode编译verilog语言并可视化运行
intel自带的quartus软件界面实在过于古老,代码编写跟matlab一样难用,所以我决定使用vscode编写verilog代码并运行一些简单的编译和调试。主要的设置步骤有以下:
- 安装iverilog编译器,这是一个开源的verilog语言编译器,ubuntu可以直接使用
sudo apt install iverilog
安装,windows可在网上找官网下载; - 安装仿真器gtkwave,这也是开源的示波仿真器,ubuntu可以直接使用
sudo apt install gtkwave
安装,windows可在网上找官网下载; - 安装vscode插件,必须有
Verilog-HDL/SystemVerilog/Bluespec SystemVerilog
,这个用于将verilog语言风格化、格式化和关键字着重标示,实现一个IDE的基本功能。此外,推荐安装WaveTrace
插件,是一个可视化编译结果的插件,如同一个示波器,可以查看输入、输出电平信号。 - 安装ctags,以实现对代码的类型识别并调用正确的编译器,可选软件,安装请下载
https://github.com/universal-ctags/ctags-win32/releases
并解压到对应目录,然后根据github上的README安装教程,将该目录编译后得到软件ctags
,将该软件的路径保存在插件Verilog-HDL/SystemVerilog/Bluespec SystemVerilog
对应选项内。 - 配置格式化操作,可安装
istyle
软件,克隆本仓库,然后使用gcc
编译仓库下的makefile
文件,如果编译失败,则使用cmake
重新生成一个makefile
再编译,然后可运行istyle
软件查看其选项,根据选项配置合适的个性化要求。如选用其他格式化软件,步骤与此相似。 - 配置语法检查器,可以使用
iverilog
或者modelsim配置语法检查,这里以iverilog
配置为例,在settings.json
文件中添加如下配置:
1 | "verilog.linting.linter": "iverilog", |
配置语法检查器不需要设置路径,切记。
7. 配置自动例化插件,当以上步骤均使用较为熟练时,如需要大量生成测试代码,则可以考虑使用Verilog_Testbench
插件。settings.json
文件中添加如下配置:
到此所有的配置都已经完成,可以使用vscode编写verilog代码并进行编译和调试了。
基本操作
- 新建工程文件夹,在文件夹下新建
.v
文件,类似于.c
文件,是verilog编译的源代码文件,编写这个文件,目前还不太清楚verilog语言中是否存在如同头文件一样的预声明文件。 - 使用
iverilog -o output.vcd resource_1.v resource_2.v ...
编译这些源文件并链接; - 使用
vvp output.vcd
命令将输出文件编译为可由示波器显示输出的文件; - 直接点击该
.vcd
文件即可显示,亦可安装gtkwave
文件使用命令gtkwave output.vcd
命令打开外部示波器显示。
基本语法
值与常数
verilog中有四种基本的值,分别是逻辑0和逻辑1,未知值x和高阻抗值z,都 是verilog语言内建的,相当于关键标识符一样存在的常数。
verilog HDL中有三种类型的常数,分别是:整型,实数型(即浮点数类型)和字符串型,其中整型有二进制、8进制、10进制和16进制四种表示方法,分别使用b或者B、o或者O、d或者D以及h或者H表示这些进制,同时在进制标识的前面冠以s或者S表示有符号数。声明一个常整数时,需要声明位数、符号、进制和按进制正确表示的值。例如:
数值示例 | 数值说明 | 备注 |
---|---|---|
8 'b10101111 |
8位二进制数10101111 | 无符号数 |
5 'SB101 |
5位二进制数11101 | 有符号数用符号位填充至足额位数 |
同理,如果数的最高位是未知或者高阻抗,则用对应的未知数或者高阻抗数填充。反之当给定的位数不够时,则从低到高顺序截断至足够位数。 |
实数有两种表示方法,可按十进制表示或者科学计数法表示,与C语言相同。字符串类型常数用双绰号括起,不得分行,使用反斜杠表示转义字符。
数据类型
verilog中主要有两大类数据类型,一种是变量类型(IEEE 1364-2001标准公布之前被称为寄存器类型),相当于一个寄存器单元,只能在always
语句或者initial
语句中被赋值,缺省时默认为x;另一种是线网类型(Net),它相当于电路连线的接口,值由驱动的元件决定,缺省时默认为z。声明类型的方式与C语言一致,声明位数时,在变量类型前面增加方括号括起的位数,例如:
1 |
|
操作符
verilog的操作运算符大多与C语言相同,介绍几个不同的:
运算符 | 功能 | 备注 |
---|---|---|
~ |
一元按位取反 | 把每一位取反,输入输出位数是一样的 |
! |
一元逻辑非 | 只能对逻辑值计算,将其取反 |
** |
幂 | 同Python |
>>> |
算术右移 | 高位用符号位、x或者z补齐,逻辑左移可知 |
=== |
全等于 | 逻辑运算符,比较每一位 |
!== |
不全等 | |
& |
与,或者按位与 | 按位与不必多言,与指“缩减与”,同理还有缩减或,缩减异或等等 |
{,} |
拼接 | 将两个向量按顺序合并成为一个向量 |
{<num>{}} |
重复 | 将向量重复多次后合并成为一个向量 |
解释:缩减,一元操作符,返回值为一个二进制数,缩减与意为当只要向量中出现0就返回0,缩减或则是出现1就返回1,缩减异或是如果有x或者z就返回x,否则当操作数中含有偶数个0时返回,缩减同或则是缩减异或的取反;
门级建模
verilog提供了一系列的内建基元门电路实例,如与门、与非门、或门等,这些门电路是搭建后续复杂电路的基础。对门电路基元实例引用的语法类似于C++里面的直接初始化,即使用括号将参数括起来。
门电路类型 | 门电路类型名 | 引用语法 | 说明 |
---|---|---|---|
多输入门 | and nand or nor xor nxor |
type_name [instance_name] (output, input1, input2, ...) |
|
多输出门 | buf not |
type_name [instance_name] (output1, output2,..., outputN, input) |
|
三态门 | bufif0 buif1 notif0 notif1 |
type_name [instance_name] (output, input, control) |
|
上拉门和下拉门 | pullup pulldown |
type_name [instance_name] (output) |
|
单向开关门 | cmos nmos pmos rcmos rnmos rpmos |
type_name [instance_name] (output, input, control) |
|
双向开关门 | tran tranif0 tranif1 rtran rtranif0 rtranif1 |
type_name [instance_name] (output, input, control) |
|
延迟门 | – | #(<num>,<num>,<num>) |
三种选项可以表示上升沿、下降沿和转换时间延迟以及截止时间延迟 |
备注:双向开关门中tran
和rtran
不需要外加控制信号参数;延迟门中甚至不需要表达式,只需要在上述实例引用的实例名前加入井号和数字即可。此外,还可以进行实例数组,即多次重复实例引用。门电路的实例名是可以省略的,因此在实例引用中完全不需要实例名,但是,在使用实例数组的时候,不能忽略实例名。
延迟对应的表格:
延迟值 | 无延迟 | 1个选项值(d1) |
2个选项值(d1,d2) |
3个选项值(d1,d2,d3) |
---|---|---|---|---|
上升沿延迟 | 0 |
d1 |
d1 |
d1 |
下降沿延迟 | 0 |
d1 |
d2 |
d2 |
转换延迟 | 0 |
d1 |
min(d1,d2) |
d3 |
截止延迟 | 0 |
d1 |
min(d1,d2) |
d3 |
实例数组
实例数组是指多次重复实例引用,其语法为:type_name [instance_name] [range] (output, input1, input2, ...)
,其中range
是一个方括号括起来的范围,表示重复的次数,例如:
1 | and (a[0], b[0], c[0]); |
可以简化为:
1 | and [0:2](a, b, c); |
用户定义的原语
UDP语句,具有以下的形式:
1 | primitive UDP_name (outputName, List_of_inputs): |
原语是基本门控电路的集合,可以实现比门控电路更加复杂的组合逻辑和时序逻辑。原语只能有一个输出,可以描述组合电路和时序电路。在描述组合电路时,类似于真值表。描述时序逻辑时,在表示时钟时使用括号(ab)表示从a转换到b,可以使用x,使用?表示任意值(0,1和x)。
练习题: 6.6.5 6.6.6
过程建模和行为建模
verilog里面有三种建模方式:结构建模、数据流建模和行为建模。其中结构建模可以类比C++等面向对象语言中的类,是按模块的构建,数据流建模指的是沿电信号的传递顺序,使用assign或者其他赋值语句完成对信号流的建模,行为建模则类似于C等面向过程的语言,是对一个任务的分解建模。另一种分类的方式是建模对象的层级,可以分为晶体管级、门电路级、寄存器传输级、算法级、系统级。
连续赋值语句
用于在线网类型间的赋值,与一般的赋值在形式上没有区别。默认被赋值的目标是标量线网类型。同时,线网的赋值可以在声明中同步进行。
initial语句
一条initial
语句只执行一次,其开始执行的时间是仿真0时刻,多用于仿真,而实际使用中基本不使用。不同的initial
语句之间是全并行处理的。在initial
语句内可以串联代码块即begin end
语句块,实现不同代码块的顺序执行。
always语句
always
语句是被反复执行的,是verilog语句的主要构成部分。由于其执行为无限循环,因此必须带有延时控制,才能得到有效的输出信号,同理,在always
语句内部也可以使用begin end
语句块实现不同代码块的顺序执行。另一种语句块是fork join
语句块,其内部的代码块是并行执行的,但是fork join
语句块内部的代码块是顺序执行的,一般来说不建议使用fork join
语句块,因为不能保证全部的编译器都能正确编译该语句。
时序控制
时序控制有两种方式,其一是使用延迟控制,分为语句内延迟和语句间延迟,这类代码使用#
和表达式分隔语句,多用于仿真。其二是事件控制,这类代码使用@
和表达式分隔语句,也可以使用语句内事件和语句间事件控制。@
符号后面的列表被称为敏感列表,常用的是时钟的上升沿和下降沿,可分别使用posedge
和negedge
关键字表示。事件控制的语句可以在always
语句中使用,也可以在initial
语句中使用,但是延迟控制的语句只能在initial
语句中使用。
赋值语句
verilog有两种赋值语句,用常规等号的称为“阻塞性赋值语句”,是指在进行赋值时将其后面的所有语句都中断,直到完成了本语句的赋值为止;另一种使用符号<=
表示,其含义是“非阻塞性赋值语句”,其赋值过程中首先计算右式结果,并在规定的时刻将结果赋值给左式,当没有延迟存在时,则在当前时刻结束前最后一刻完成赋值,在赋值过程中,不影响后续语句右式的计算,因此非阻塞性赋值可以完成对不同语句的解耦合,因此使用中更推荐使用非阻塞性赋值语句。
条件语句
与C语言高度相近,值得注意的是,需要把所有的情况都枚举出来,不然程序将会卡死在例外的情况中。对于if
语句,使用else
包括其他情况,对于case
语句,使用default
包括其他情况。
循环语句
与C语言等基本一致,不再赘述。
学习体会
2023/11/5
今日学习过程建模,对verilog的并行处理有了进一步的体会,特别是在下列问题:8.10.15的建模中,对并行的需要体现得淋漓尽致:
问题原文:描述一个检测位数过半的逻辑的电路行为。输入是一个12位的向量,若其中值为1的位数超过值为0的位数,则置输出为1,当data_ready为1时,才对输入数据进行检查。