Verilog HDL 入门

Verilog 文法:https://github.com/antlr/grammars-v4/blob/master/verilog/verilog/VerilogParser.g4

基础

常量

整数常量

18'hFF       // 8 位十六进制常量
28'd255      // 8 位十进制常量
38'o377      // 8 位八进制常量
48'b11111111 // 8 位二进制常量

实数常量

11.0        // 十进制实数常量,将转换为整数 1
20.8        // 十进制实数常量,将转换为整数 1

实数通过四舍五入隐式地转换为最相近的整数

字符串常量

1"Hello World" // 将被转换成 ASCII 码序列 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100

注释

1// 单行注释
2/* 多行注释 */

类型

net 类型

net 表示物理连接,对应电路中的硬连线。位宽 1, 无符号。

具体类型
 1net_type
 2	: 'supply0' // 一个逻辑零电源(供应电源),用于强制将连续赋值的信号线上的值保持为逻辑零
 3	| 'supply1' // 一个逻辑一电源(供应电源),用于强制将连续赋值的信号线上的值保持为逻辑一
 4	| 'tri'     // 三态信号线,可以具有高阻态,也可以由多个模块驱动
 5	| 'triand'  // 三态与门,用于将多个三态信号线的值进行与操作
 6	| 'trior'   // 三态或门,用于将多个三态信号线的值进行或操作
 7	| 'tri0'    // 三态信号线,其值可以是逻辑零、高阻态或由多个模块驱动
 8	| 'tri1'    // 三态信号线,其值可以是逻辑一、高阻态或由多个模块驱动
 9	| 'uwire'   // 未驱动连续赋值信号线,只能被一个单一的源所驱动,不能被多个模块或电路连接
10	| 'wire'    // 普通连续赋值信号线,可以连接多个模块和逻辑电路,但不能驱动 reg 型的寄存器
11	| 'wand'    // 三态与非门,用于将多个三态信号线的值进行与非操作
12	| 'wor'     // 三态或非门,用于将多个三态信号线的值进行或非操作
13	;
uwire wire 有什么不同?

在Verilog中,wireuwire都是用于建立电路中的信号线(wire)的关键字,但它们有以下不同:

  1. wire关键字表示的是一个普通的连续赋值的信号线,它可以连接多个模块和逻辑电路,但不能驱动reg型的寄存器。而uwire关键字则表示的是一个未驱动的连续赋值的信号线,它只能被一个单一的源所驱动,不能被多个模块或电路连接,也不能驱动寄存器。

  2. wire关键字可以用来连接多个模块或电路,而uwire关键字不能。这是因为uwire关键字只能被一个单一的源所驱动,因此不能被多个模块或电路所连接。

  3. wire关键字通常用于建立连续赋值的信号线,而uwire关键字则更适合用于建立连续赋值的单一源信号线。

在实际设计中,uwire关键字很少使用,因为它的应用场景较为有限,通常只在特殊情况下使用。大多数情况下,wire关键字可以满足设计需求。

variable 类型

variable 表示存储器,对应电路中的寄存器。

具体类型

在计算机编程中,variable(变量)是指一个存储数据值的内存位置,可以通过变量名来引用和访问该内存位置中存储的数据。在编程语言中,变量通常需要先声明并指定其类型,以便在程序运行时为其分配内存空间。

在 Verilog 中,变量(variable)也有类似的概念,不过其具体的类型和用法与其他编程语言可能有所不同。下面是 Verilog 中常见的一些变量类型:

  1. reg(寄存器):用于存储逻辑电路中的状态变量,可以通过赋值操作进行更新。其用法类似于其他编程语言中的变量,但不同的是,在 Verilog 中,reg类型的变量可以不仅可以存储数字和布尔值,还可以存储多位二进制数(也可以存储 ASCII 字符)。

  2. integer(整数):用于存储整数类型的变量,可以进行加减乘除等基本算术运算。其范围为 -2^31 到 2^31 - 1。

  3. real(实数):用于存储浮点数类型的变量,可以进行基本的算术运算和一些特殊的数学函数运算。其精度和范围可能因实现而异。

  4. time(时间):用于存储时间类型的变量,可以表示电路中的时间间隔,通常以纳秒为单位。在仿真过程中,时间变量可以用于控制模拟过程的进行。

  5. event(事件):用于在电路中表示事件,可以通过事件触发器(Event Trigger)来控制模块的行为。

  6. parameter(参数):用于在模块内部定义常量,其值在模块实例化时被确定。在模块中,参数可以用于配置模块的行为。

除了上述的变量类型之外,Verilog 中还有其他一些变量类型,如bitbytelogic等,它们的具体用法和特点可能略有不同。在实际设计中,可以根据需要选择和使用适当的变量类型来实现设计。

向量

向量用于表示多位的二进制数。向量的位宽表示形式为:[MSB : LSB],其中 MSB 表示最高有效位(Most Significant Bit),LSB 表示最低有效位(Least Significant Bit)。

例子:

1wire [3:0] a; // 表示一个4位的二进制数,且从高位到低位排列。
2reg  [0:7] b; // 表示一个8位的二进制数,且从低位到高位排列。

向量的类型

  1. 标量类向量。类似 C 中的一维数组。

    1reg scalared [3:0] a; // 标量类向量
    
  2. 向量类向量。类似 C 中的多维数组。

    1reg vectored [3:0][3:0] a; // 向量类向量,表示一个4x4的二维数组
    2logic [1:0] [1:0] matrix [1:2]; // 向量类向量,表示一个3x2x2的三维数组
    

    上面定义中,matrix 是一个 3 位的向量类向量,包含三个元素,每个元素都是一个 2x2 的矩阵,其中 matrix[1] 表示第一个矩阵,matrix[1][0] 表示第一个矩阵的第一行,第一个元素,即 matrix[1][0][0] 表示第一个矩阵的左上角的元素。

运算符

算术运算符

运算符 说明
+ 加法
- 减法
* 乘法
/ 除法
% 取模

逻辑运算符

运算符 说明
&& 逻辑与
|| 逻辑或
! 逻辑非

位运算符

运算符 说明
& 位与
| 位或
^ 位异或
~ 位取反
« 位左移
» 位右移
~^ 位同或

比较运算符

运算符 说明
== 等于
=== 全等于
!= 不等于
> 大于
< 小于
>= 大于等于
<= 小于等于

注:=== 运算符用于比较两个变量的值和类型是否相等,== 运算符用于比较两个变量的值是否相等。

缩位运算符

缩位运算符用于将一个向量的高位截断,只保留低位。

运算符 说明
&
~& 与非
|
~| 或非
^ 异或
~^ 同或

三目运算符

三目运算符用于对两个表达式进行条件判断,如果条件为真,则返回第一个表达式的值,否则返回第二个表达式的值。

1condition ? expr1 : expr2

位拼接运算符

位拼接运算符用于将两个向量进行拼接,拼接后的向量的位宽为两个向量的位宽之和。

1{expr1, expr2, expr3, ...}

位选择运算符

位选择运算符用于从一个向量中选择一部分位,选择后的向量的位宽为选择的位数。

1expr[MSB:LSB]

语句

过程语句

initial 语句

initial 语句用于在仿真开始时对模块中的变量进行初始化。不能被综合。

1initial begin
2    // 初始化语句
3end

always 语句

always 语句用于描述模块中的时序逻辑。会不断执行。

1always @(<event>)
2begin
3    // 时序逻辑
4end

其中,<event> 表示事件触发器,用于指定时序逻辑的触发条件。可以看作一个依赖列表,当列表中的变量发生变化时,时序逻辑会被执行。(有点像 React 中的 useEffect)

例如:

  • always @(in1 or in2) 表示当 in1in2 发生变化时,时序逻辑会被执行。

  • always @(posedge clk) 表示当 clk 从低电平变为高电平时,时序逻辑会被执行。

  • always @(negedge clk) 表示当 clk 从高电平变为低电平时,时序逻辑会被执行。

  • always @(*) 表示当模块中的任意变量发生变化时,时序逻辑会被执行。

赋值语句

assign 语句是持续赋值语句,用于描述模块中的组合逻辑。可以看作连线操作。

而对 reg 变量赋值,需要使用过程赋值语句,有以下两种形式:

  • <=:非阻塞赋值语句,只能确保整个所在语句块结束时完成赋值。不保证立刻完成。可以并发执行。

  • =:阻塞赋值语句,会立刻完成赋值。只能顺序执行。

条件语句

if 语句

1if (condition)
2    statements
3else if (condition)
4    statements
5else
6    statements

case 语句

普通 case 语句
1case (expression)
2    expression: statements
3    expression: statements
4    default: statements
casez 语句

casez 语句会忽略 Z 的位值。

1casez (expression)
2    expression: statements
3    expression: statements
4    default: statements
casex 语句

casex 语句会忽略 X 的位值。

循环语句

for 语句

1for (initialization; condition; step)
2    statements

例子:

1for (i = 0; i < 10; i = i + 1)
2    statements

repeat 语句

1repeat (expression)
2    statements

forever 语句

会一直执行。

1forever
2    statements

预处理

`define 宏定义

1`define 宏名 宏值

如果要在代码中引用宏,需要在宏名前加上一个反引号 `。

1`宏名

`ifdef 宏判断

1`ifdef 宏名
2    statements
3`endif
4
5`ifndef 宏名
6    statements
7`else
8    statements
9`endif

`include 文件包含

1`include "文件名"

需要注意 `include 不能被综合。

仿真控制

系统函数

$display

1$display("字符串", 表达式, ...)

用于在控制台输出信息。

$monitor

1$monitor("字符串", 表达式, ...)

用于在控制台输出信息,但是会在每个时钟周期都输出。

$time

1$display("%d", $time)

用于获取当前仿真时间。以纳秒为单位。

$finish

1$finish

用于结束仿真。

$stop

1$stop

用于暂停仿真。

$readmemh

1$readmemh("文件名", 变量名)

用于从文件中读取数据,存储到变量中。

文件格式:

1@地址 数据
2@地址 数据
3...

文件例子:

1@0 0
2@1 1
3@2 2

数据采用十六进制表示。每行默认是一个 32 位的数据。

如果地址从 0 开始,可以省略地址。

10
21

$writememh

1$writememh("文件名", 变量名)

用于将变量中的数据写入到文件中。