VLSI 设计 - Verilog 简介
Verilog 是一种硬件描述语言 (HDL)。它是一种用于描述数字系统(如网络交换机、微处理器、内存或触发器)的语言。这意味着,通过使用 HDL,我们可以在任何级别描述任何数字硬件。用 HDL 描述的设计与技术无关,非常易于设计和调试,并且通常比原理图更有用,尤其是对于大型电路。
Verilog 支持多个抽象级别的设计。主要的三个是 −
- 行为级别
- 寄存器传输级别
- 门级别
行为级别
此级别通过并发算法(行为)描述系统。每种算法都是顺序的,这意味着它由一组逐一执行的指令组成。函数、任务和块是主要元素。不考虑设计的结构实现。
寄存器传输级
使用寄存器传输级的设计使用操作和寄存器之间的数据传输来指定电路的特性。RTL 代码的现代定义是"任何可合成的代码都称为 RTL 代码"。
门级
在逻辑级内,系统的特性由逻辑链接及其时序属性描述。所有信号都是离散信号。它们只能具有确定的逻辑值("0"、"1"、"X"、"Z")。可用的操作是预定义的逻辑原语(基本门)。门级建模可能不是逻辑设计的正确想法。门级代码是使用综合工具等工具生成的,其网表用于门级仿真和后端。
词汇标记
Verilog 语言源文本文件是词汇标记流。一个标记由一个或多个字符组成,每个字符都只在一个标记中。
Verilog HDL 使用的基本词汇标记与 C 编程语言中的词汇标记类似。Verilog 区分大小写。所有关键词都小写。
空格
空格可以包含空格、制表符、换行符和换页符。这些字符会被忽略,除非它们用于分隔标记。
空白字符包括空格、制表符、回车符、换行符和换页符。
注释
有两种形式来表示注释
- 1) 单行注释以标记 // 开头,以回车符结尾。
例如://这是单行语法
- 2) 多行注释以标记 /* 开头,以标记 */ 结尾
例如:/* 这是多行语法*/
数字
您可以指定二进制、八进制、十进制或十六进制格式的数字。负数以 2 的补码表示。 Verilog 允许整数、实数以及有符号和无符号数。
语法由 − <size> <radix> <value> 给出
大小或无大小数可以在 <Size> 中定义,而 <radix> 定义它是二进制、八进制、十六进制还是十进制。
标识符
标识符是用于定义对象的名称,例如函数、模块或寄存器。标识符应以字母或下划线字符开头。例如 A_Z、a_z、_
标识符是字母、数字、下划线和 $ 字符的组合。它们最多可以有 1024 个字符。
运算符
运算符是用于设置条件或操作变量的特殊字符。有一个、两个,有时是三个字符用于对变量执行操作。
例如 >、+、~、&!=。
Verilog 关键字
在 Verilog 中具有特殊含义的词称为 Verilog 关键字。例如,assign、case、while、wire、reg、and、or、nand 和 module。它们不应用作标识符。Verilog 关键字还包括编译器指令、系统任务和函数。
门级建模
Verilog 具有内置原语,如逻辑门、传输门和开关。这些很少用于设计工作,但它们在后期综合领域用于 ASIC/FPGA 单元的建模。
门级建模表现出两个属性 −
驱动强度 − 输出门的强度由驱动强度定义。如果与源有直接连接,则输出最强。如果连接是通过导电晶体管进行的,则强度会降低,而通过上拉/下拉电阻连接时,强度会最小。通常不指定驱动强度,在这种情况下,强度默认为 strong1 和 strong0。
延迟 − 如果未指定延迟,则门没有传播延迟;如果指定了两个延迟,则第一个延迟代表上升延迟,第二个延迟代表下降延迟;如果仅指定一个延迟,则上升和下降都相等。综合时可以忽略延迟。
门原语
Verilog 中使用了一个输出和多个输入的基本逻辑门。GATE 使用关键字之一 - and、nand、or、nor、xor、xnor,用于 Verilog 中的 N 个输入和 1 个输出。
Example: Module gate() Wire ot0; Wire ot1; Wire ot2; Reg in0,in1,in2,in3; Not U1(ot0,in0); Xor U2(ot1,in1,in2,in3); And U3(ot2, in2,in3,in0)
传输门原语
传输门原语包括缓冲器和反相器。它们具有单个输入和一个或多个输出。在下面显示的门实例化语法中,GATE 代表关键字 buf 或 NOT 门。
示例:Not、buf、bufif0、bufif1、notif0、notif1
Not – n 输出反相器
Buf – n 输出缓冲器
Bufifo – 三态缓冲器,低电平有效使能
Buf1 – 三态缓冲器,高电平有效使能
Notifo – 三态反相器,低电平有效使能
Notif1 – 三态反相器,高电平有效使能
Example: Module gate() Wire out0; Wire out1; Reg in0,in1; Not U1(out0,in0); Buf U2(out0,in0);
数据类型
值集
Verilog 主要由四个基本值组成。Verilog 中使用的所有 Verilog 数据类型都存储这些值 −
0(逻辑零,或假条件)
1(逻辑一,或真条件)
x(未知逻辑值)
z(高阻抗状态)
x 和 z 的使用对于综合来说非常有限。
Wire(导线)
Wire(导线)用于表示电路中的物理Wire(导线),用于连接门或模块。Wire(导线)的值只能读取,不能在函数或块中分配。Wire(导线)不能存储值,但始终由连续赋值语句或通过将Wire(导线)连接到门/模块的输出来驱动。其他特定类型的线路是 −
Wand(有线与) − 此处 Wand 的值取决于与其连接的所有设备驱动程序的逻辑与。
Wor(有线或) − 此处 Wor 的值取决于与其连接的所有设备驱动程序的逻辑或。
Tri(三态) − 此处连接到 tri 的所有驱动程序都必须是 z,只有一个除外(它决定 tri 的值)。
Example: Wire [msb:lsb] wire_variable_list; Wirec // simple wire Wand d; Assign d = a; // value of d is the logical AND of Assign d = b; // a and b Wire [9:0] A; // a cable (vector) of 10 wires. Wand [msb:lsb] wand_variable_list; Wor [msb:lsb] wor_variable_list; Tri [msb:lsb] tri_variable_list;
Reg
Reg(寄存器)是一个数据对象,它保存着从一个程序赋值到下一个程序赋值的值,并且只用于不同的函数和程序块。Reg 是一个简单的 Verilog 变量类型寄存器,不能表示物理寄存器。在多位寄存器中,数据以无符号数字的形式存储,不使用符号扩展。
示例 −
reg c; // 单个 1 位寄存器变量
reg [5:0] gem; // 一个 6 位向量;
reg [6:0] d, e; // 两个 7 位变量
输入、输出、输入输出
这些关键字用于声明任务或模块的输入、输出和双向端口。此处输入和输入输出端口为 wire 类型,输出端口配置为 wire、reg、wand、wor 或 tri 类型。始终默认为 wire 类型。
示例
Module sample(a, c, b, d); Input c; // 使用 wire 的输入。 Output a, b; // 使用 wire 的两个输出。 Output [2:0] d; /* 三位输出。必须在单独的语句中声明类型。*/ reg [1:0] a; // 上面的"a"端口用于在 reg 中声明。
Integer
Integer 整数用于通用变量。它们主要用于循环索引、常量和参数。它们属于"reg"类型的数据类型。它们将数据存储为有符号数字,而明确声明的 reg 类型将它们存储为无符号数据。如果在编译时未定义整数,则默认大小为 32 位。
如果整数包含常数,则合成器会将其调整为编译时所需的最小宽度。
示例
Integer c; // 单个 32 位整数 Assign a = 63; // 63 默认为 7 位变量。
Supply0, Supply1
Supply0 定义与逻辑 0(接地)绑定的电线,supply1 定义与逻辑 1(电源)绑定的电线。
示例
supply0 logic_0_wires; supply0 gnd1; // 相当于指定为 0 的Wire(导线) supply1 logic_1_wires; supply1 c, s;
time
time 是一个 64 位量,可与 $time 系统任务结合使用以保存模拟时间。时间不支持综合,因此仅用于模拟目的。
示例
time time_variable_list; time c; c = $time; //c = current simulation time
Parameter
Parameter 参数定义一个常量,可以在使用模块时设置,从而允许在实例化过程中自定义模块。
Example Parameter add = 3’b010, sub = 2’b11; Parameter n = 3; Parameter [2:0] param2 = 3’b110; reg [n-1:0] jam; /* A 3-bit register with length of n or above. */ always @(z) y = {{(add - sub){z}}; if (z) begin state = param2[1]; else state = param2[2]; end
运算符
算术运算符
这些运算符执行算术运算。 + 和 + 可用作一元 (x) 或二元 (z−y) 运算符。
算术运算中包含的运算符有 +
+ (加法)、- (减法)、* (乘法)、/ (除法)、& (模数)
示例 +
参数 v = 5; reg[3:0] b, d, h, i, count; h = b + d; i = d - v; cnt = (cnt +1)%16; //可以计数 0 到 15。
关系运算符
这些运算符比较两个操作数并以单个位 1 或 0 返回结果。
Wire 和 reg 变量为正数。因此 (−3'd001) = = 3'd111 和 (−3b001)>3b110。
关系运算中包含的运算符是 −
- ==(等于)
- !=(不等于)
- >(大于)
- >=(大于或等于)
- < (小于)
- <=(小于或等于)
示例
if (z = = y) c = 1; else c = 0; // Compare in 2’s compliment; d>b reg [3:0] d,b; if (d[3]= = b[3]) d[2:0] > b[2:0]; else b[3]; Equivalent Statement e = (z == y);
按位运算符
按位运算符对两个操作数进行逐位比较。
按位运算中包含的运算符有 −
- & (按位与)
- | (按位或)
- ~ (按位非)
- ^ (按位异或)
- ~^ 或 ^~ (按位异或非)
示例
module and2 (d, b, c); input [1:0] d, b; output [1:0] c; assign c = d & b; end module
逻辑运算符
逻辑运算符是按位运算符,仅用于单比特操作数。它们返回单个比特值,0 或 1。它们可以对整数或比特组、表达式进行操作,并将所有非零值视为 1。逻辑运算符通常用于条件语句,因为它们与表达式一起使用。
逻辑运算中包含的运算符是 −
- !(逻辑非)
- &&(逻辑与)
- ||(逻辑或)
示例
wire[7:0] a, b, c; // a、b 和 c 是多位变量。 reg x; if ((a == b) && (c)) x = 1; //如果 a 等于 b 且 c 非零,则 x = 1。 else x = !a; //如果 a 不为零,则 x =0。
归约运算符
归约运算符是按位运算符的一元形式,对操作数向量的所有位进行操作。它们也返回单个位值。
归约运算中包含的运算符是 −
- & (归约与)
- | (归约或)
- ~& (归约 NAND)
- ~| (归约 NOR)
- ^ (归约 XOR)
- ~^ 或 ^~(归约 XNOR)
示例
Module chk_zero (x, z); Input [2:0] x; Output z; Assign z = & x; // Reduction AND End module
移位运算符
移位运算符,将第一个操作数移位语法中第二个操作数指定的位数。对于两个方向、左移和右移(不使用符号扩展),空位都用零填充。
移位操作中包含的运算符是 −
- <<(左移)
- >> (右移)
示例
Assign z = c << 3; /* z = c 左移 3 位;
空位用 0 填充 */
连接运算符
连接运算符将两个或多个操作数组合起来形成一个更大的向量。
连接运算中包含的运算符是 − { }(连接)
示例
wire [1:0] a, h; wire [2:0] x; wire [3;0] y, Z; assign x = {1’b0, a}; // x[2] = 0, x[1] = a[1], x[0] = a[0] assign b = {a, h}; /* b[3] = a[1], b[2] = a[0], b[1] = h[1], b[0] = h[0] */ assign {cout, b} = x + Z; // 结果的连接
复制运算符
复制运算符正在制作一个项目的多个副本。
复制操作中使用的运算符是 − {n{item}}(项目的 n 倍复制)
示例
Wire [1:0] a, f; wire [4:0] x; Assign x = {2{1’f0}, a}; // Equivalent to x = {0,0,a } Assign y = {2{a}, 3{f}}; //Equivalent to y = {a,a,f,f} For synthesis, Synopsis did not like a zero replication. For example:- Parameter l = 5, k = 5; Assign x = {(l-k){a}}
条件运算符
条件运算符合成到多路复用器。它与 C/C++ 中使用的类型相同,并根据条件评估两个表达式之一。
条件运算中使用的运算符是 −
(条件) ? (条件为真时的结果) −
(条件为假时的结果)
示例
Assign x = (g) ? a : b; Assign x = (inc = = 2) ? x+1 : x-1; /* if (inc), x = x+1, else x = x-1 */
操作数
文字
文字是 Verilog 表达式中使用的常量值操作数。两个常用的 Verilog 文字是 −
字符串 − 字符串文字操作数是一维字符数组,用双引号 (" ") 括起来。
数字 − 常数操作数以二进制、八进制、十进制或十六进制数字指定。
示例
n − 表示位数的整数
F −四种可能的基本格式之一 −
b 表示二进制,o 表示八进制,d 表示十进制,h 表示十六进制。
"time is" // string literal 267 // 32-bit decimal number 2’b01 // 2-bit binary 20’hB36F // 20-bit hexadecimal number ‘062 // 32-bit octal number
连线、寄存器和参数
连线、寄存器和参数是 Verilog 表达式中用作操作数的数据类型。
位选择"x[2]"和部分选择"x[4:2]"
位选择和部分选择分别用于从连线、寄存器或参数向量中选择一位和多位,使用方括号"[ ]"。位选择和部分选择也可用作表达式中的操作数,其使用方式与使用其主要数据对象的方式相同。
示例
reg [7:0] x, y; reg [3:0] z; reg a; a = x[7] & y[7]; // bit-selects z = x[7:4] + y[3:0]; // part-selects
函数调用
在函数调用中,函数的返回值直接用于表达式,无需先将其分配给寄存器或线路。它只是将函数调用作为操作数类型之一。需要确保您知道函数调用返回值的位宽。
Example Assign x = y & z & chk_yz(z, y); // chk_yz is a function . . ./* 函数定义 */ Function chk_yz; // 函数定义 Input z,y; chk_yz = y^z; End function
模块
模块声明
在 Verilog 中,模块是主要的设计实体。它表示名称和端口列表(参数)。接下来的几行指定每个端口的输入/输出类型(输入、输出或输入输出)和宽度。默认端口宽度只有 1 位。端口变量必须由 wire、wand、...、reg 声明。默认端口变量是 wire。通常,输入是 wire,因为它们的数据锁存在模块外部。如果输出的信号存储在内部,则输出为 reg 类型。
示例
module sub_add(add, in1, in2, out); input add; // defaults to wire input [7:0] in1, in2; wire in1, in2; output [7:0] out; reg out; ... statements ... End module
连续赋值
模块中的连续赋值用于将值赋给Wire(导线),这是在 always 或 initial 块外部使用的正常赋值。此赋值通过显式赋值语句完成,或在Wire(导线)声明期间将值赋给Wire(导线)。连续赋值在模拟时连续执行。赋值语句的顺序不会影响它。如果您对任何右侧输入信号进行任何更改,它将改变左侧输出信号。
示例
Wire [1:0] x = 2'y01; // 在声明时赋值 Assign y = c | d; // 使用赋值语句 Assign d = a & b; /* 赋值语句的顺序无关紧要。 */
模块实例化
模块声明是用于创建实际对象的模板。模块在其他模块内实例化,每个实例化都从该模板创建一个对象。顶级模块是例外,它是其自己的实例。模块的端口必须与模板中定义的端口匹配。它指定为 −
按名称,使用点".template 端口名称(连接到端口的电线名称)"。或者
按位置,将端口放在模板和实例的端口列表中的同一位置。
示例
MODULE DEFINITION Module and4 (x, y, z); Input [3:0] x, y; Output [3:0] z; Assign z = x | y; End module