Verilog语法#
逻辑值#
- 逻辑0:低电平(GND)
- 逻辑1:高电平(VCC)
- 逻辑X:电平未知
- 逻辑Z:高阻态,悬空状态
数据进制格式#
二进制(binary)、八进制(Octal)、十进制(Decimal)、十六进制(Hexadecimal)
- 二进制:
4'b0101
表示4位二进制数字0101 - 十进制:
4'd2
表示4位十进制数字2 - 十六进制:
4'ha
表示4位十六进制数字a
可以发现,Verilog对数字的定义为 大小
[size] +
'
+ 进制[base_format]
+
数值[number]
。
数值的每四位建议下划线( _
)分割,以增加程序的可读性。
例如: 16'b1001_1010_1010_1001=16'h9AA9
对于未指定 进制[base_format]
的数值,默认为十进制数;对于未指定 大小[size]
的数字,采用默认位数(取决于设备类型)
对于负数,符号应当放置在 大小[size]
前,而不应放在其他位置
1 | -4'd2 //correct |
字符串#
不可分行书写,用 " "
包含。
1 | "Hello World!" // string with 12 characters -> require 12 bytes |
标识符#
用于定义模块名、端口名、信号名等。
可是 字母 、 数字 、 $ 和 下划线 的组合。
注意:与C语言相同,标识符的第一个字母必须是 字母 或 下划线 ,且对大小写敏感。
关键字#
语言保留关键字,不可用做标识符名称。

数据类型#
寄存器类型 reg
#
用来表示存储单元
1 | reg [25:0] cnt; //size为26位 |
reg
类型不可直接赋初值,只能在 always
和
initial
语句中赋值。
[bit+: width]
: 从起始 bit 位开始递增,位宽为 width。[bit-: width]
: 从起始 bit 位开始递减,位宽为 width。
1 | //下面 2 种赋值是等效的 |
线网类型 wire
#
用于表示硬件单元间的物理连线,缺省值为 Z
。
1 | wire interrupt ; |
数组:#
在 Verilog 中允许声明 reg, wire, integer, time, real 及其向量类型的数组。维数不限。
1 | integer flag [7:0] ; //8个整数组成的数组 |
字符串#
1 | reg [0: 14*8-1] str ; |
参数#
用 parameter
声明,只能赋值一次。局部参数用
localparam
声明。
1 | parameter data_width = 10'd32 ; |
其他类型#
integer
、 real
、 time
and so
on 此处略去。
表达式与运算符#
运算优先级与C语言相同
1 | real a,b,c; |
算术运算符:即加(
+
)、减(-
)、乘(*
)、除(/
)、求幂(**
)、取模(%
),注意定义的数据位宽。1
2
3
4
5
6reg [3:0] a,b;
reg [4:0] c,d,e;
a=4'b0010;//a=2
b=4'b1001;//b=9
c=a+b;//result:c=4'b1011 即c=11
c=b/a;//result:c=4 取整若操作数某一位为
x
,Verilog 会认为 “输入有不确定信号”,结果也会被标记为x
。1
2
3
4
5
6
7
8//example
a=4'b1010;
b=4'bx001;
c=a+b;//result:c=4'bx011
//不只是加法,其他运算也有同样的结果
c = a & b; // 结果 c=4'bx000(因为 b[3] 是 X,与运算后这一位也 X)
d = a | b; // 结果 d=4'bx011(b[3] 是 X,或运算后这一位也 X)
e = ~b; // 结果 e=4'bx110(b[3] 是 X,取反后还是 X)表示负数时,应当指定位宽
size
(因为负数用的二进制补码表示),否则会出现意想不到的后果。关系运算符:大于(
>
)、小于(<
)、大于等于(>=
)、小于等于(<=
)。基本规则与C语言相同。真为
1
,假为0
。值得注意的是,若操作数有一位为
x
orz
则返回结果为x
。等价运算符:逻辑相等(
==
)、逻辑不等(!=
)、全等(===
)、非全等(!==
)。基本规则与C语言相同。真为1,假为0。
值得注意的是,执行逻辑比较时,若操作数有一位为
x
orz
则返回结果为x
;执行全等比较时,若两数同时在同一位有x
orz
,结果也能返回1
。1
2
3
4
5
6
7
8
9A = 4 ;
B = 8'h04 ;
C = 4'bxxxx ;
D = 4'hx ;
A == B //为真
A == (B + 1) //为假
A == C //为X,不确定
A === C //为假,返回值为0
C === D //为真,返回值为1逻辑运算符:逻辑与(
&&
)、逻辑或(||
)、逻辑非(!
)基本规则与C语言相同。操作数不为0,等价于
1
;操作数为0,等价于0
;操作数为x
orz
,等价于x
。值得注意的是,如果任意一个操作数包含
x
,逻辑操作符运算结果不一定为x
。位运算符:取反(
~
)、与(&
)、或(|
)、异或(^
)、同或(~^
)。具体规则详见我的另一篇文章:https://skina.cn/bit_operation/
这里简单补充同或规则:两个输入值相同时,结果为真。该运算的结果与异或相反,故满足如下条件
A~^B == ~(A^B)
1
2
3
4
5
6
7
8
9
10
11A = 4'b1010 ;
B = 4'b0110 ;
C = 4'bx101 ;
~A // 4'b0101
A & B // 4'b0010
A | B // 4'b1110
A^B // 4'b1100
A ~^ B // 4'b0011
B | C // 4'bx111
B & C // 4'bx100移位运算符:左移(
<<
)、右移(>>
)、算术左移(<<<
)、算数右移(>>>
)。具体规则详见我的另一篇文章:https://skina.cn/bit_operation/
这里补充算数左移和算术右移的规则。
算术左移:将操作数的二进制位向左移动指定的位数,低位补
0
,高位丢弃。算术右移:将操作数的二进制位向右移动指定的位数,对于有符号数,高位用符号位填充,低位丢弃。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 无符号数,用于逻辑移位、算术左移(无符号数算术左移和逻辑左移行为一致)
reg [7:0] unsigned_num = 8'b1010_1100; // 十进制 172,二进制 10101100
// 有符号数,用于算术右移对比
reg signed [7:0] signed_num = 8'b1101_0011; // 十进制 -45(补码),二进制11010011
// 移位结果存储
reg [7:0] arith_left_res;
reg [7:0] arith_right_res;
reg [7:0] logic_left_res;
reg [7:0] logic_right_res;
arith_left_res = unsigned_num << 1;//算术左移(<<):无符号数左移,低位补 0,相当于 ×2(不溢出时)
arith_right_res = signed_num >> 1;//算术右移(>>):有符号数右移,高位补符号位(这里最高位是 1,补 1)
logic_left_res = unsigned_num <<< 1;//逻辑左移(<<<):和算术左移对无符号数效果一样,低位补 0
logic_right_res = signed_num >>> 1;//逻辑右移(>>>):不管符号,高位补 0归约运算符:将一个多位宽的向量逐位进行逻辑运算,最终得到一个 1 位的结果。有
&
(与)、|
(或)、^
(异或)、~&
(与非)、~|
(或非)、~^
(同或)等运算符。语法为:规约运算符
+表达式
。1
2
3
4reg [3:0] a = 4'b1011;
wire and_result = &a; // 逐位与:1&0&1&1 = 0
wire or_result = |a; // 逐位或:1|0|1|1 = 1
wire xor_result = ^a; // 逐位异或:1^0^1^1 = 1常用于判断向量是否全为 1(
&a
)或全为 0(~|a
)。拼接运算符:将多个信号或常量按位拼接成一个新的向量。语法为:
{信号1, 信号2, ...}
1
2
3
4reg [3:0] a = 4'b1010;
reg [1:0] b = 2'b11;
wire [5:0] c = {a, b}; // 拼接结果:6'b101011
wire [7:0] d = {2'b00, a, 1'b1}; // 拼接常量和变量:8'b00101001值得注意的是,拼接时需明确指定每部分的位宽,该语法常用于构建更大的向量(如地址、数据总线等)
三目运算符:与C语言相同,不再赘述。
编译指令#
`define
和
`undef
#
类似C语言中的 #define
和 undef
与之配套的有`ifdef
、`ifndef
、`else
和 `endif
1 |
|
宏可以是常量、文本片段或表达式,且宏在定义后全局有效,直到被undef
或文件结束。
`include
#
将另一个文件的内容插入到当前位置(类似 C
语言的#include
)。
1 | // 包含自定义定义文件 |
`timescale
#
指定模块的时间单位和精度。
- 时间单位和精度必须是 1、10 或
100,后跟
fs
、ps
、ns
、us
、ms
、s
。 - 影响时间相关函数。
语法规则为: `timescale 时间单位/时间精度
1 | // 时间单位为1纳秒,精度为1皮秒 |
`default_nettype
#
用于指定未声明的标识符的默认网络类型。
语法规则为:`default_nettype 网络类型
1 | // 默认网络类型为wire |
`resetall
#
重置所有编译指令为默认状态(常用于文件末尾),清除所有
define
宏、恢复 default_nettype
等
语法规则为:`resetall
`celldefine
和
`endcelldefine
#
用于将模块标记为单元模块,他们包含模块的定义。例如一些与、或、非门,一些 PLL 单元,PAD 模型,以及一些 Analog IP 等。
1 |
|
连续赋值#
assign#
可用 assign
语句对 wire
类型信号进行连续赋值。
语法规则: assign 目标信号=表达式;
其中目标信号必须是
wire
类型
1 | wire a, b, c; |
全加器#
全加器是一个实现三个一位二进制数相加的电路
输入为:
- A 和 B:两个待相加的二进制位
- Cin:进位输入
输出为:
- Sum:本位和
- Cout:进位输出
逻辑表达式为: \[
Sum = A \oplus B \oplus Cin
\\
Cout = ( A \& B ) + ( B \& Cin ) + ( A \& Cin )
\] 使用 assign
语句实现全加器的方法如下
1 | module full_adder( |
一些解释:
进位输入 Cin 的作用:来自 低位的进位信号,表示低位相加后是否产生进位。
在计算 2 位二进制数相加(如 \(11_2+01_2\) )时:
- 最低位(第 0 位):没有来自更低位的进位,因此 \(C_{in}=0\)。
- 次低位(第 1 位):最低位相加后产生进位(\(1+1=0\) 余 \(1\) ),因此 \(C_{in}=1\)。
进位输出 Cout 的作用:当前位相加后 产生的进位信号,传递给 更高位 作为进位输入。
以 \(11_2+01_2\) 为例:
- 最低位(第 0 位):\(1+1=0\) 余 \(1\) ,因此 \(Sum=0\) ,\(Cout=1\) 。
- 次低位(第 1 位):\(1+0+C_{in}(1)=2\) ,即 \(Sum=0\) ,\(Cout=1\)。 最终结果为 \(100_2\)(十进制 \(4\) )。
延时#
连续赋值时延#
- 普通时延
1 | //A&B的计算结果延时10个单位时间再赋值给Z |
- 隐式时延
1 | //声明一个wire类型变量时,对其包含一定时延的连续赋值 |
- 声明时延
1 | //声明一个wire型变量是指定一个时延。因此对该变量所有的连续赋值都会被推迟到指定的时间。除非门级建模中,一般不推荐使用此类方法建模。 |
惯性时延与传输时延
惯性时延:默认行为,若输入脉冲宽度小于时延,输出不会响应。
传输时延:使用
#(时延)
语法,所有输入变化都会传递到输出。
1
2
3
4
5// 惯性时延(宽度小于5ns的脉冲会被过滤)
assign #5 out = in;
// 传输时延(所有输入变化都会传递)
assign #(5) out = in;
过程赋值时延#
- 阻塞赋值时延:
#时延 变量 = 表达式;
先延迟,再执行赋值 - 非阻塞赋值时延:
变量 <= #时延 表达式;
先计算表达式,在未来时刻赋值
1 | always @(posedge clk) begin |
其他时延#
如 事件控制时延,组合与时序逻辑中的时延 自行查找资料,不再赘述。
用时延模拟触发器#
1 | module d_flip_flop( |
过程结构#
initial语句#
initial
块中的代码在仿真开始时执行一次,执行完毕后不再重复,多个
initial
块之间相互独立,互不影响(可以理解为同步执行?)
示例:
1 | module testbench; |
时序图如下

always语句#
always语句是重复执行的,他从 0 时刻开始执行其中的行为语句;当执行完最后一条语句后,便从头开始执行
基本语法:
1 | always @(敏感列表) begin |
边沿触发(时序逻辑):
@(posedge 信号 or negedge 信号)
1
2
3
4always @(posedge clk or negedge rst_n) begin // 时钟上升沿或复位下降沿触发
if (!rst_n) q <= 1'b0; // 异步复位
else q <= d; // 同步数据更新
end电平触发:
@(信号1, 信号2, ...)
或@(*)
1
2
3
4
5
6
7
8always @(a, b, c) begin // 所有输入信号变化时触发
y = (a & b) | c;
end
// 等效于:
always @(*) begin // 自动包含所有被读取的信号
y = (a & b) | c;
end
过程赋值#
阻塞赋值#
按顺序依次执行,前一条语句完成后才执行下一条。
适用于存在组合逻辑的情况。
1 | always @(*) begin |
非阻塞赋值#
在当前时间步结束时并行执行,避免竞争条件。
适用于时序逻辑中。
1 | always @(posedge clk) begin |
(未完待续...)