前言

嵌入式课程和导师的专长都是硬件电路设计,面向应用部署算法,因此有必要了解掌握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代码并运行一些简单的编译和调试。主要的设置步骤有以下:

  1. 安装iverilog编译器,这是一个开源的verilog语言编译器,ubuntu可以直接使用sudo apt install iverilog安装,windows可在网上找官网下载;
  2. 安装仿真器gtkwave,这也是开源的示波仿真器,ubuntu可以直接使用sudo apt install gtkwave安装,windows可在网上找官网下载;
  3. 安装vscode插件,必须有Verilog-HDL/SystemVerilog/Bluespec SystemVerilog,这个用于将verilog语言风格化、格式化和关键字着重标示,实现一个IDE的基本功能。此外,推荐安装WaveTrace插件,是一个可视化编译结果的插件,如同一个示波器,可以查看输入、输出电平信号。
  4. 安装ctags,以实现对代码的类型识别并调用正确的编译器,可选软件,安装请下载https://github.com/universal-ctags/ctags-win32/releases并解压到对应目录,然后根据github上的README安装教程,将该目录编译后得到软件ctags,将该软件的路径保存在插件Verilog-HDL/SystemVerilog/Bluespec SystemVerilog对应选项内。
  5. 配置格式化操作,可安装istyle软件,克隆本仓库,然后使用gcc编译仓库下的makefile文件,如果编译失败,则使用cmake重新生成一个makefile再编译,然后可运行istyle软件查看其选项,根据选项配置合适的个性化要求。如选用其他格式化软件,步骤与此相似。
  6. 配置语法检查器,可以使用iverilog或者modelsim配置语法检查,这里以iverilog配置为例,在settings.json文件中添加如下配置:
1
2
3
"verilog.linting.linter": "iverilog",
"verilog.linting.iverilog.arguments": "-i",
"verilog.linting.modelsim.arguments": "-i",

配置语法检查器不需要设置路径,切记。
7. 配置自动例化插件,当以上步骤均使用较为熟练时,如需要大量生成测试代码,则可以考虑使用Verilog_Testbench插件。settings.json文件中添加如下配置:

到此所有的配置都已经完成,可以使用vscode编写verilog代码并进行编译和调试了。

基本操作

  1. 新建工程文件夹,在文件夹下新建.v文件,类似于.c文件,是verilog编译的源代码文件,编写这个文件,目前还不太清楚verilog语言中是否存在如同头文件一样的预声明文件。
  2. 使用iverilog -o output.vcd resource_1.v resource_2.v ...编译这些源文件并链接;
  3. 使用vvp output.vcd命令将输出文件编译为可由示波器显示输出的文件;
  4. 直接点击该.vcd文件即可显示,亦可安装gtkwave文件使用命令gtkwave output.vcd命令打开外部示波器显示。

基本语法

值与常数

verilog中有四种基本的值,分别是逻辑0和逻辑1,未知值x和高阻抗值z,都 是verilog语言内建的,相当于关键标识符一样存在的常数。

verilog HDL中有三种类型的常数,分别是:整型,实数型(即浮点数类型)和字符串型,其中整型有二进制、8进制、10进制和16进制四种表示方法,分别使用b或者Bo或者Od或者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
2
3
4
5
6

wire [2: 0]haddr; //三位向量的线网
reg test_reqa; //一位的寄存器变量
integer a,b,b; //整型变量,用于高层次的建模过程,实现功能接近于C语言
time now[0:12] //时间变量数组,方括号放到后面表示数组,这里是13个时间变量构成的数组
real [63:0]r[0:5] //64位实数数组,共有5个64位实数

操作符

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>) 三种选项可以表示上升沿、下降沿和转换时间延迟以及截止时间延迟

备注:双向开关门中tranrtran不需要外加控制信号参数;延迟门中甚至不需要表达式,只需要在上述实例引用的实例名前加入井号和数字即可。此外,还可以进行实例数组,即多次重复实例引用。门电路的实例名是可以省略的,因此在实例引用中完全不需要实例名,但是,在使用实例数组的时候,不能忽略实例名。

延迟对应的表格:

延迟值 无延迟 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
2
3
and (a[0], b[0], c[0]);
and (a[1], b[1], c[1]);
and (a[2], b[2], c[2]);

可以简化为:

1
and [0:2](a, b, c);

用户定义的原语

UDP语句,具有以下的形式:

1
2
3
4
5
6
7
8
9
10
primitive UDP_name (outputName, List_of_inputs):
Output_declaration
List_of_input_declarations
[ Reg_declaration ]
[ initial_statement ]

Table
List_of_table_entries
Endtable
Endprimitive

原语是基本门控电路的集合,可以实现比门控电路更加复杂的组合逻辑和时序逻辑。原语只能有一个输出,可以描述组合电路和时序电路。在描述组合电路时,类似于真值表。描述时序逻辑时,在表示时钟时使用括号(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语句块,因为不能保证全部的编译器都能正确编译该语句。

时序控制

时序控制有两种方式,其一是使用延迟控制,分为语句内延迟和语句间延迟,这类代码使用#和表达式分隔语句,多用于仿真。其二是事件控制,这类代码使用@和表达式分隔语句,也可以使用语句内事件和语句间事件控制。@符号后面的列表被称为敏感列表,常用的是时钟的上升沿和下降沿,可分别使用posedgenegedge关键字表示。事件控制的语句可以在always语句中使用,也可以在initial语句中使用,但是延迟控制的语句只能在initial语句中使用。

赋值语句

verilog有两种赋值语句,用常规等号的称为“阻塞性赋值语句”,是指在进行赋值时将其后面的所有语句都中断,直到完成了本语句的赋值为止;另一种使用符号<=表示,其含义是“非阻塞性赋值语句”,其赋值过程中首先计算右式结果,并在规定的时刻将结果赋值给左式,当没有延迟存在时,则在当前时刻结束前最后一刻完成赋值,在赋值过程中,不影响后续语句右式的计算,因此非阻塞性赋值可以完成对不同语句的解耦合,因此使用中更推荐使用非阻塞性赋值语句。

条件语句

与C语言高度相近,值得注意的是,需要把所有的情况都枚举出来,不然程序将会卡死在例外的情况中。对于if语句,使用else包括其他情况,对于case语句,使用default包括其他情况。

循环语句

与C语言等基本一致,不再赘述。

学习体会

2023/11/5

今日学习过程建模,对verilog的并行处理有了进一步的体会,特别是在下列问题:8.10.15的建模中,对并行的需要体现得淋漓尽致:
问题原文:描述一个检测位数过半的逻辑的电路行为。输入是一个12位的向量,若其中值为1的位数超过值为0的位数,则置输出为1,当data_ready为1时,才对输入数据进行检查。