跳到主要内容

Verilog 基本语法

1 基本模块说明

Verilog 使用模块(module)来描述一个电路,module 是层次化设计的基本构件,逻辑描述放在 module 内部,如下所示

module ALU
// 这里描述模块的具体内容
endmodule

module 可以表示物理块(如 IC 或 ASIC 单元)、逻辑块(如一个 CPU 设计的 ALU 部分)和整个系统。

Verilog 模块的结构由在 module 和 endmodule 关键词之间的四个主要部分组成:

  1. 端口信息:module <module_name> (<port_list>);
  2. 输入/输出说明:input [<range>] <port_name>;
  3. 内部信号:wire [<range>] <wire_name>;
  4. 功能定义: 实现具体的逻辑

如下所示

module DFF (d, clk, clr, q, qb);
input d, clk, clr;
output q, qb;
// 功能定义 + 内部信号
endmodule
信息
  1. 端口在模块名字后的括号中列出
  2. 端口等价于硬件的引脚(pin)
  3. 端口可以说明为 input, output 及 inout
  4. 输入/输出端口信号类型声明,缺省为 wire 型

2 词汇约定

2.1 术语及定义

  • 空白符:空格、tabs 及换行
  • 标志符(Identifier):Verilog 中对象(如模块或信号)的名字
  • LSB:最低有效位(Lease significant bit)
  • MSB:最高有效位(Most significant bit)

2.2 空白符和注释

Verilog 里面的空白符不影响程序的运行,但可以提高程序的可读性。

单行注释使用//,多行注释使用/* */

2.3 四值逻辑系统

Verilog 使用四值逻辑系统,即逻辑运算的结果可以是 01Z(未定义)或 X(未连接)。

  • 逻辑0:表示低电平,对应电路的GND
  • 逻辑1:表示高电平,对应电路的VCC;
  • 逻辑X:表示不确定,通常用于表示无法确定逻辑值的情况;
  • 逻辑Z:表示高阻态,对应电路的悬空状态。

2.4 整数常量和实数常量

Verilog中,常量(literals)可是整数也可以是实数。

整数使用 <size>'<base><value> 表示,其中:

  • <size>:表示整数的位数,由十进制数表示的位数表示,缺省为32位
  • <base>:表示整数的进制,简称数基,可以是二进制(b)、八进制(o)、十进制(d)和十六进制(h),缺省为十进制
  • <value>:表示整数的值,需要和所选进制对应,包括 XZ

实数使用十进制或者科学计数法表示。

下面给出一些整数与实数常量例子:

常量描述
1232位十进制整数 12
'h83a32位十六进制整数 83a
8'b1100_00018位二进制整数 1100 0001
16'hff0116位十六进制整数 ff01
32'bz01x32位二进制整数 z01x,其中z表示对应的位为高阻态,向左边扩展
3'b1010 11113位二进制整数 111,由于只能存储3位,截去高位,转化为 3'b111
1.532位实数 1.5
1.5E232位实数 150,表示 1.5 * 10^2
1.5e-232位实数 0.015,表示 1.5 * 10^-2
注意
  1. 没有定义大小(size)的整数默认为32位
  2. 缺省数基默认为十进制
  3. 数基和数(16进制)中的字母无大小写之分
  4. 当数值大于指定的大小时,截去高位。如 2'b1101 表示的是 2'b01
  5. 数字中的下划线 _ 可以忽略

2.5 字符串

字符串通常用于测试中,显示测试结果等信息。
字符串要在一行中用双引号括起来,也就是不能跨行;字符串中可以使用一些转义符,如 \t, \n等。
display 语句中可以使用一些格式符(如 %b)在仿真时产生格式化输出,例如:

display("A = %b", A); // 输出 A 的二进制表示:A = 0001

2.6 标识符

标识符是用户在描述时给 Verilog 对象(电路模块、信号等)起的名字,必须满足以下规则:

  • 必须以字母(a-z, A-Z)或( _ )开头,后面可以是字母、数字、( $ )或( _ )
  • 最长为1023个字符
  • 区分大小写

有效标识符:shift_reg_a, busa_index, _bus3;
无效标识符:34net, a*b_net, n@238

2.7 系统任务及函数

使用 $<identifier> 指示这是系统任务和函数,比如:

  • $display:打印信息
  • $stop:停止仿真
  • $time:返回仿真时间

2.8 编译指导

使用符号 ` 表示一个编译指导,这些编译指导使仿真编译器进行一些特殊的操作,一直保持有效直到被覆盖或解除。
比如 `timescale 1ns/1ps 表示单位时间为1ns,时钟精度为1ps。

2.9 文本包含 `include

编译指导 `include 在当前内容中插入一个文件,格式为 `include "filename",文件路径可以是绝对路径,也可以是相对路径。

  • 全局的或经常用到的一些定义,如文本宏,可以放在一个文件中,然后通过 `include "filename" 引入
  • 在模块内部 include 一些任务,提高代码的可维护性

2.10 文本替换 `define

编译指导 `define 提供了一种简单的文本替换的功能,用法为 `define <identifier> <text>,在编译的时候yi时,所有 `<identifier> 都会被替换为 <text>

3 数据类型

Verilog 有两大类数据类型:线网类型和寄存器类型。

  • 线网类型(net type): 表示 Verilog 结构化元件间的物理连线。它的值由驱动元件的值决定,例如连续赋值或门的输出。如果没有驱动元件连接到线网,线网的缺省值为 z
  • 寄存器类型(register type):表示一个抽象的数据存储单元,它只能在 always 语句和 initial 等过程语句中被赋值,并且它的值从一个赋值到另一个赋值被保存下来。寄存器类型的变量具有x的缺省值。

3.1 线网类型

线网类型在电路中表示元件之间的物理连线,值由输入端决定。

net 需要被持续的驱动,驱动它的可以是门和模块。当 net 驱动器的值发生变化时,新值被传送到 net 上。
在下例中,线网 outor 门驱动。当 or 门的输入信号置位时将传输到线网 net 上。

Net

线网类型如下表所示:

类型描述
wire, tri标准内部连接线(缺省)
supply1, supply0电源和地
wor, trior多驱动源线或
wand, triand多驱动源线与
trireg能保存电荷的tri
tri0, tri1带上拉/下拉电阻的tri
  • 没有声明的线网类型,默认为 wire
  • 可以综合的线网数据类型包括 wiretrisupply1,supply0,最常用的线网数据类型是 wire
  • wiretri 在 Verilog 仿真和综合中的实际行为是完全一样的,tri 主要是一个语义提示,表明该信号可能是一个潜在的多驱动网络

线网数据类型使用语法为 net_type [msb:lsb] net_name,其中:

  • net_type:线网数据类型
  • [msb:lsb]:用于定义线网范围的常量表达式,如果没有定义,缺省为1位
  • net_name:线网名称

线网数据类型声明的例子:

module mux2to1(out, a, b, sel);
input a, b, sel; // 没有声明信号类型,默认为 wire
output out;

wire out;

assign out = (sel) ? b : a;
endmodule

3.2 寄存器类型

寄存器类型在电路中表示一个抽象的数据存储单位,有四种数据类型,分别为 reg, integer, real, time

类型描述
reg最常用的寄存器类型,无符号型
integer32位有符号整数变量,算术操作产生二进制补码形式的结果。通常用作不会由硬件实现的的数据处理
real双精度的带符号浮点变量,用法与integer相同
time64位无符号整数变量,用于仿真时间的保存与处理

reg 是寄存器数据类型最常见的数据类型,使用语法为 reg [msb:lsb] reg_name,其中:

  • [msb:lsb]:用于定义寄存器的范围,如果没有定义,缺省为1位
  • reg_name:寄存器名称

寄存器数据类型声明的例子:

  • reg [3:0] counter; :4位无符号寄存器 counter
  • reg counter; : 1位无符号寄存器 counter
  • reg [31:1] counter; :31位无符号寄存器 counter
module mux2to1(out, a, b, sel);
input a, b, sel;
output out;
reg out;

always @(a or b or sel)
if (sel) out = b;
else out = a;
endmodule
如何给给模块端口信号选择正确的数据类型

输入端口只能是net,输出端口只能驱动net

  • 输入端口只能是 net;但输入端口可以由 net/register 驱动
  • 输出端口可以是 net/register 类型,但输出端口只能驱动 net

例如下面的例子,c、d、o2 是输入信号,却被定义为 register 类型,会导致错误。所以应该将 o1 定义为register 类型,其余的为 net 类型。

module example(o1, o2, a, b, c, d);
input a, b, c, d;
output o1, o2;
reg c, d; // ERROR
reg o2; // ERROR
and u1(o2, c, d); // 一般来说,输出信号放在前面,输入信号放在后面
always @(a or b)
if (a) o1 = b;
else o1 = 0;
endmodule

3.3 参数

参数是一个常量,它可以被模块实例化时指定一个值。参数声明的语法为 parameter param_name = param_value

  • 推荐:一般定义参数等常量名称用大写,变量名称用小写!
  • 可一次定义多个参数,用逗号隔开,比如 parameter PARAM1 = 1, PARAM2 = 2
  • 参数的定义是局部的,只在当前模块中有效。
  • parameter作用于声明的那个模块; `define 从编译器读到这条指令开始到编译结束都有效,或者遇到 `undef 命令使之失效。
  • 也可以使用 `include 实现 parameter 的跨文件
  • 使用参数实现模块实例化时的参数传递(例化传递)
    module Decoder(...); // 子模块
    parameter WIDTH = 1, POLARITY = 1;
    endmodule

    module Top(...); // 顶层模块
    Decode #(4, 0) U_D1(); // 使得 WIDTH = 4, POLARITY = 0
    Decode #(8) U_D2(); // 使得 WIDTH = 8, POLARITY 不变
    Decode #(.POLARITY(3)) U_D3(); // 使得 WIDTH 不变,POLARITY = 3
    endmodule

3.4 位选择

位选择从向量中抽取特定的位,使用语法为 [msb:lsb],例如:

reg [31:0] Breg;
wire [31:0] Bwire;

assign Bwire[31:0] = Breg[31:0];
注意

整数不能作为位向量访问,比如定义 interger B,使用B[31:0]会报错。解决方法是将整数赋值给一般的reg类型变量,然后从中选取相应的位。

4 运算符

Verilog 中运算符与 C/C++ 类似,包括:

  • 算术运算符:+-*/%
  • 逻辑运算符:!&&||
  • 位运算符:&|^~>><<
  • 关系运算符:><>=<=!======?:
  • 赋值运算符:=<=
  • 拼接运算符:{}{n{b}}

4.1 算数运算符

  • 将负数赋值给 reg 或其它无符号变量,使用 2 的补码算术。(正数的补码为原码,负数的补码为源码取反后加 1)
  • 如果操作数的某一位是 x 或 z,则结果为 x
  • 在整数除法中,余数舍弃
  • 模运算中使用第一个操作数的符号

4.2 关系运算符

===表示全等,==表示相等

x === x //为true

4.3 赋值运算符

阻塞赋值=常用于组合逻辑,例如assign语句和always@(*)语句块;非阻塞赋值<=常用于时序逻辑,例如always@(posedge clk)语句块。​​​​​​​

wire [11:0] data_in;
reg [11:0] data_out;

assign data_in = 6'b10111; //assign语句常给wire类型复制
always @( posedge rst) begin //always语句块常给reg类型赋值
data_out <= data_in;
end

非阻塞赋值=与C/C++相同,计算后立刻赋值;阻塞赋值<=则先进行计算,在统一赋值。

4.4 拼接运算符

连接运算符{}用于将不同的信号合成为一个信号。由于非定长常数的长度未知, 不允许连接非定长常数,以及位数不匹配的情况。

wire [5:0] a,b,c;
wire [11:0] d;
assign a = 6'b101101;
assign b = 6'b111000
assign c = 2'b11
assign d = {a[5],b[2:0],c}//d = 100011
assign d = {a, 5} // ERROR,a长度未知
assign d = {a, b, c} // ERROR,d的长度和a、b、c的长度不匹配

{n{}} 表示复制操作,将信号复制 n 次。

assign d = {2{4'b1011}}; // d = 8'b1011_1011
assign d = {{4{1'b1}}, 4'b1011}; // d = 8'b1111_1101
assign d = {4{1'b1}, 4'b1011}; // ERROR,4{1'b1}不是完整的信号