一、module
:
定义: 是构建数字系统的基本单元,用于封装电路的结构和行为。它可以表示从简单的逻辑门到复杂的处理器等任何硬件组件。
1. module
的基本定义
module 模块名 (端口列表);
// 端口声明
input [位宽] 输入端口1;
output [位宽] 输出端口1;
inout [位宽] 双向端口1; // 双向I/O(如三态总线)
// 内部信号声明
wire [位宽] 内部连线;
reg [位宽] 寄存器;
// 逻辑功能实现
// - 连续赋值语句(assign)
// - 过程块(always)
// - 模块实例化
// - 任务(task)和函数(function)
endmodule
- 特点:
- 硬件抽象:每个模块对应实际电路的一个功能单元。
- 层次性:模块可以嵌套实例化,构建复杂系统。
- 端口隔离:模块通过端口与外部通信,内部实现对外部透明。
2. 简单模块示例
示例 1:2 输入与门
module and_gate (
input a, // 输入端口a
input b, // 输入端口b
output y // 输出端口y
);
assign y = a & b; // 连续赋值语句,实现逻辑与
endmodule
示例 2:带寄存器的计数器
module counter (
input clk, // 时钟信号
input reset, // 复位信号(高有效)
input enable, // 使能信号
output reg [7:0] count // 8位计数器输出(声明为reg类型)
);
always @(posedge clk) begin
if (reset)
count <= 8'b0; // 复位时计数器清零
else if (enable)
count <= count + 1; // 使能时每个时钟周期加1
end
endmodule
3. 模块实例化与连接
模块通过实例化(Instantiation)嵌入到其他模块中,形成层次化设计。
示例 3:使用 and_gate 构建更复杂的电路
module top_module (
input x, y, z,
output f
);
wire w1, w2; // 内部连线
// 实例化and_gate模块(两个与门)
and_gate u1 (
.a(x), // 连接到输入x
.b(y), // 连接到输入y
.y(w1) // 输出连接到内部信号w1
);
and_gate u2 (
.a(w1), // 输入连接到u1的输出w1
.b(z), // 输入连接到输入z
.y(w2) // 输出连接到内部信号w2
);
// 最终输出
assign f = w2;
endmodule
4. 参数化模块
使用 parameter
可以定义可配置的常量,增强模块的灵活性。
示例 4:参数化的计数器
module param_counter #(
parameter WIDTH = 8, // 计数器位宽,默认8位
parameter INIT = 0 // 初始值,默认0
) (
input clk, reset, enable,
output reg [WIDTH-1:0] count
);
always @(posedge clk) begin
if (reset)
count <= INIT; // 复位时加载初始值
else if (enable)
count <= count + 1;
end
endmodule
assign
是连续赋值语句,用于描述组合逻辑(无记忆,输出实时跟随输入变化)。- 功能:将输入
a
和b
相加,结果赋值给输出c
。Verilog 会自动处理位宽扩展(2 位 + 2 位 = 3 位,匹配c
的位宽)。 模块实例化部分(
adder u1(.a(in1), .b(in2), .c(in3));
)这部分是复用已定义的模块(
adder
),并将其端口与外部信号连接,类似 “在电路板上焊接芯片并接线”。adder
:要实例化的模块名(必须和前面定义的module adder
一致)。u1
:实例名(自定义,同一模块可实例化多次,如u2
、u3
等,用于区分不同实例)。.端口名(外部信号)
:命名端口映射,把模块的内部端口和外部信号一一对应:.a(in1)
:模块的a
端口 ↔ 外部信号in1
(in1
需是 2 位信号)。.b(in2)
:模块的b
端口 ↔ 外部信号in2
(in2
需是 2 位信号)。.c(in3)
:模块的c
端口 ↔ 外部信号in3
(in3
需是 3 位信号)。
- 外部信号的要求
- 声明:
in1
、in2
、in3
必须在当前作用域(比如上层模块)中提前声明,例如:verilog
-
wire [1:0] in1, in2; // 2位输入信号(wire类型,因为是组合逻辑连接) wire [2:0] in3; // 3位输出信号(wire类型,因为模块输出是wire)
二、数据类型及常量变量
1、Verilog HDL有四种基本的值
(1)其中x和z不区分大小写;
(2)z也可以使用?表示,虽然不懂为什么不是表示未知……
2、Verilog HDL三类常量
(1)整型:直接用数字序列表示的整数,默认采用十进制。
1,-2;
- 特点:书写简单,但无法明确指定数值的位宽和进制。
- 注意:负数只能用十进制表示,不能用基数表示法表示负数。
B、基数表示:<位宽>’<进制><数字>
b
或B
:二进制o
或O
:八进制d
或D
:十进制h
或H
:十六进制
下划线(_):可以在数字中插入下划线,提高可读性,但不影响数值
- 无符号数:默认情况下,整型常量是无符号数。
- 有符号数:在常量前加负号(-),但负数只能用十进制表示。
-8'd10 // 错误!不能用基数表示法表示负数 -10 // 正确!十进制负数
- 不定值(x)和高阻值(z):在二进制、八进制和十六进制中,可以使用
x
或z
表示不定值或高阻值。
(2)实数型:实数型常量用于表示带有小数部分的数值,有两种表示形式:
1. 十进制形式
直接用数字和小数点表示的实数。
3.14159 // 圆周率
-0.001 // 负数
0.5 // 小数
5. // 等同于5.0
2. 科学计数法形式
使用指数表示的实数,格式为:<数字>e<指数>
或 <数字>E<指数>
。
3.6e4 // 3.6×10⁴,即36000
-1.23e-2 // -1.23×10⁻²,即-0.0123
5E7 // 5×10⁷,即50000000
3. 实数与整数的转换
- 实数赋值给整数变量时,会自动截断小数部分,只保留整数部分。
integer a;
a = 3.9; // a的值为3(截断小数部分)
- 整数赋值给实数变量时,会自动转换为实数。
real b;
b = 10; // b的值为10.0(转换为实数)
三、字符串型常量
字符串型常量用于表示一串字符,用双引号(")括起来。
"Hello, World!" // 字符串常量
"Verilog HDL" // 包含空格的字符串
"12345" // 包含数字的字符串
1. 转义字符
在字符串中,可以使用转义字符表示特殊字符。
"\n" // 换行符
"\t" // 制表符
"\"" // 双引号
"\\" // 反斜杠
2. 字符串的存储
- 字符串在 Verilog 中被存储为一系列的 ASCII 字符,每个字符占用 8 位(1 字节)。
- 字符串的长度由字符的个数决定,不包括双引号。
"ABC" // 长度为3的字符串,占用24位(3×8)
3. 字符串操作
- 字符串可以赋值给 reg 类型的变量,但变量的位宽必须足够容纳字符串的所有字符。
reg [7:0] str [0:2]; // 声明一个3个字符的字符串数组
initial begin
str[0] = "A"; // 或 8'd65 或 8'h41
str[1] = "B"; // 或 8'd66 或 8'h42
str[2] = "C"; // 或 8'd67 或 8'h43
end
initial begin
$sformatf(str, "%s", "ABC"); // 格式化字符串到数组
$display("str = %s", str); // 输出:str = ABC
end
reg [7:0]
:每个元素是 8 位宽的寄存器(对应 1 个 ASCII 字符)。str [0:2]
:数组索引范围是0
到2
,共 3 个元素。- 等效理解:这是一个 3 行 8 列 的二维数组,可存储 3 个 ASCII 字符。
- Verilog 不直接支持字符串的拼接、比较等操作,需要使用系统任务或函数。
$display("The value is %d", 10); // 系统任务输出字符串和变量
四、WIRE类型
(1)wire类型是一种基本的数据类型,用于表示硬件电路中的物理连线。它是组合逻辑设计的核心元素,下面详细介绍其定义、用法和注意事项:
一、核心定义
wire
用于建模信号连接,类似电路板上的导线,主要特点:
- 信号载体:用于传递和连接模块、门电路之间的信号。
- 被动赋值:不能存储值,必须由驱动源(如
assign
、门级实例、模块输出)持续驱动。 - 默认状态:若无驱动,值为高阻态
z
。 - 硬件对应:直接映射到物理连线或组合逻辑输出。
二、声明语法
wire [位宽] 信号名1, 信号名2, ...;
- 位宽(可选):指定信号的二进制位数,默认为 1 位。
- 示例:
wire clk; // 1位wire信号(时钟)
wire [7:0] data; // 8位向量(数据总线)
wire [3:0] addr, enable; // 同时声明多个wire
三、常见用法
1. 连续赋值(assign
语句)
通过表达式持续驱动 wire
:
wire a, b, c;
assign c = a & b; // c等于a和b的逻辑与
wire [3:0] x, y, z;
assign z = x + y; // z等于x和y的和(组合逻辑加法)
2. 门级实例化连接
作为门电路的输入 / 输出:
wire a, b, c;
and gate1(c, a, b); // 与门实例化,输出到c
3. 模块间信号连接
连接不同模块的端口:
module top;
wire clk, rst, data_in, data_out;
// 实例化时钟生成模块
clock_gen clk_gen (.clk(clk), .rst(rst));
// 实例化数据处理模块
data_proc proc (.clk(clk), .rst(rst), .in(data_in), .out(data_out));
endmodule
4. 三态门驱动
用于总线共享场景:
wire data_bus;
wire enable;
// 三态缓冲器
assign data_bus = enable ? data_out : 1'bz; // 使能时输出数据,否则高阻
五、REG类型
REG
是一种基本的数据类型,主要用于存储数值,对应硬件中的触发器(Flip-Flop)或锁存器(Latch)。下面详细介绍其定义、用法和注意事项:
一、核心定义
reg
用于表示存储元件,具有以下特点:
- 存储能力:能保持当前值,直到被新值覆盖。
- 主动赋值:通过过程块(
initial
或always
)赋值。 - 默认状态:初始值为不定态
x
(除非显式初始化)。 - 硬件对应:综合后通常映射为触发器(时序逻辑)或锁存器(组合逻辑)。
二、声明语法
reg [位宽] 信号名1, 信号名2, ...;
- 位宽(可选):指定信号的二进制位数,默认为 1 位。
示例:
reg clk; // 1位reg信号
reg [7:0] data; // 8位向量(可存储0~255)
reg [3:0] count; // 4位计数器
三、常见用法
1. 时序逻辑(触发器)
在时钟边沿触发的 always
块中使用:
reg [3:0] counter;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) // 异步复位(低电平有效)
counter <= 4'b0;
else if (en) // 使能时计数
counter <= counter + 1;
end
2. 组合逻辑(锁存器)
在电平敏感的 always
块中使用(需谨慎,易产生锁存器):
reg data_out;
always @(*) begin
if (sel)
data_out = data_in; // 组合逻辑赋值(=)
// 缺少else分支 → 综合出锁存器(不推荐)
end
3. 初始化(initial
块)
仅用于仿真,不可综合:
reg [7:0] mem [0:15]; // 存储器数组
initial begin
// 初始化存储器内容
mem[0] = 8'hAA;
mem[1] = 8'h55;
// ...
end
4. 函数和任务中的变量
function [7:0] multiply(input [3:0] a, b);
reg [7:0] result; // 函数内部变量
begin
result = a * b;
multiply = result;
end
endfunction
四、与 wire
的对比
特性 | reg |
wire |
---|---|---|
存储能力 | 有存储能力(保持值直到下一次赋值) | 无存储,仅传递信号 |
赋值方式 | 只能在 initial 或 always 块中赋值 |
由 assign 、门或模块驱动 |
适用场景 | 时序逻辑(如触发器)、变量存储 | 组合逻辑连接、模块间通信 |
默认值 | 不定值 x |
高阻态 z |
赋值符号 | 非阻塞赋值(<= )或阻塞赋值(= ) |
仅连续赋值(assign ) |
硬件对应 | 触发器、锁存器等存储元件 | 物理连线、组合逻辑输出 |
五、使用注意事项
(1)在always语句和initial语句中的赋值对象只能是reg类型,reg类型信号也只能在always语句和initial语句中被赋值,
(2)所以,always、initial块外的赋值对象和连线用wire型信号,always、initial块内的赋值对象用reg型
六、运算符与运算表达式
1、算术运算符
用于数值计算,支持整数和定点数运算。
运算符 | 描述 | 示例 |
---|---|---|
+ |
加法 | c = a + b; |
- |
减法 | c = a - b; |
* |
乘法 | c = a * b; |
/ |
除法 | c = a / b; (整数除法) |
% |
取模(取余) | c = a % b; |
** |
乘方(SystemVerilog) | c = a ** b; (a 的 b 次方) |
2、逻辑运算符
符号:=
(阻塞赋值)、<=
(非阻塞赋值)
功能:将值赋给变量,核心区别在于执行时序。
类型 | 语法 | 应用场景 | 关键特性 |
---|---|---|---|
阻塞赋值 | reg_a = reg_b; |
组合逻辑(always @(*) ) |
立即赋值,语句顺序影响结果(如 a=1; b=a; 中b 会立即等于 1)。 |
非阻塞赋值 | reg_a <= reg_b; |
时序逻辑(always @(posedge clk) ) |
3、关系运算符
符号:>
(大于)、<
(小于)、>=
(大于等于)、<=
(小于等于)
功能:比较两数大小,结果为1 位布尔值(1
/0
/X
)。
示例 | 说明 |
---|---|
a > b |
按无符号数比较(Verilog 默认,SystemVerilog 可通过signed 声明有符号比较)。 |
a <= b |
若操作数含X /Z ,结果可能为X (如 4'b10x0 <= 4'b1000 结果为X )。 |
4、相等运算符
符号:==
(逻辑相等)、!=
(逻辑不等)
功能:比较两数是否相等(==
)或不等(!=
),不严格匹配X
/Z
。
示例 | 说明 |
---|---|
a == b |
仅比较0 /1 位,若含X /Z ,结果可能为X (如 4'b10x0 == 4'b1000 结果为X )。 |
a != b |
只要有一位0 /1 不同,结果为1 ;含X 时结果可能为X 。 |
扩展:=== (全等,严格匹配X /Z )和!== (非全等),图中未列但常用。 |
5、逻辑运算符
符号:&&
(逻辑与)、||
(逻辑或)、!
(逻辑非)
功能:对布尔值(非零视为真
,零为假
)进行逻辑运算,结果为1 位。
示例 | 说明 |
---|---|
(a>0) && (b<5) |
两边均为真 (非零)时,结果为1 ;否则为0 。 |
!(a==0) |
若a 非零,结果为1 ;否则为0 。 |
与位运算符的区别: |
- 逻辑运算:结果 1 位;位运算(如
&
/|
)逐位处理,结果位宽同操作数。 - 示例:
wire log = (a && b);
(逻辑与) vswire bit = a & b;
(位与)。
6、位运算符
符号:~
(按位取反)、&
(按位与)、|
(按位或)、^
(按位异或)
功能:对操作数逐位进行逻辑运算,结果位宽与操作数一致。
运算符 | 示例 | 说明 | |||
---|---|---|---|---|---|
~ |
~4'b1010 = 4'b0101 |
每一位取反(1→0 ,0→1 )。 |
|||
& |
4'b1010 & 4'b1100 = 4'b1000 |
逐位与(1&1=1 ,否则0 )。 |
|||
` | ` | `4'b1010 | 4'b1100 = 4'b1110` | 逐位或(`0 | 0=0,否则 1`)。 |
^ |
4'b1010 ^ 4'b1100 = 4'b0110 |
逐位异或(相同为0 ,不同为1 )。 |
|||
扩展:同或(~^ 或 ^~ ),即异或非(如 4'b1010 ~^ 4'b1100 = 4'b1001 )。 |
7、移位运算符
符号:<<
(逻辑左移)、>>
(逻辑右移)
功能:将操作数按位移动,空位补 0(逻辑移位)。
运算符 | 示例 | 说明 |
---|---|---|
<< |
4'b1010 << 1 = 4'b0100 |
左移 1 位,低位补 0(值变为原来的 2 倍,注意位宽截断)。 |
>> |
4'b1010 >> 1 = 4'b0101 |
右移 1 位,高位补 0(值变为原来的 1/2,整数除法)。 |
注意: |
- Verilog 的
>>
对有符号数也会补 0(逻辑移位),SystemVerilog 的>>>
才是算术移位(补符号位)。 - 移位位数需为常量(综合要求),如
a << 2
(合法),a << b
(非法,b
需是常量)。
8、条件运算符
符号:?:
(三元运算符)
语法:condition ? expr1 : expr2
功能:条件为真
(非零)时,结果为expr1
;否则为expr2
。
示例 | 说明 |
---|---|
assign mux = sel ? a : b; |
sel=1 选a ,sel=0 选b (实现多路选择器)。 |
assign max = (a>b) ? a : b; |
选a 和b 中的较大者(支持嵌套,如三数取最大)。 |
注意:expr1 和expr2 需位宽兼容,否则会截断(如16'b0 和8'b1 拼接会报错,需显式扩展位宽)。 |
9、连接和复制操作符
符号:{}
(大括号)
功能:
- 连接(Concatenation):将多个信号按位拼接。
- 复制(Replication):将信号重复拼接(格式:
{n{expr}}
,n
为常量)。
1. 连接示例
// 示例1:简单拼接
wire [3:0] a = 4'b1010, b = 4'b0011;
wire [7:0] c = {a, b}; // 结果:8'b1010_0011(a是高4位,b是低4位)
// 示例2:位逆序(图中案例)
assign bus[3:0] = {bus[0], bus[1], bus[2], bus[3]};
// 原bus[3:0] = 4'b1010(位3=1,位2=0,位1=1,位0=0)→ 拼接后为{0,1,0,1} → 4'b0101(位序反转)。
2. 复制示例
// 示例1:信号复制
wire [1:0] a = 2'b10;
wire [5:0] b = {3{a}}; // 结果:6'b10_10_10(a重复3次)
// 示例2:图中案例
assign bus[3:0] = {2{bus[0]}, 2{bus[3]}};
// 假设bus[0]=1(1位),bus[3]=0(1位)→ 复制后:{1,1,0,0} → 4'b1100。
九、begin_end语句:
1、定义:
两条或者多条语句的组合,主要有两种。
2、语句:顺序块
(1)块内的语句顺序执行
(2)每条语句的延时为相对前一句
(3)最后一句执行完,才能跳出该块
十、fork_join语句:并行块
(1)块内语句同时执行
(2)每条语句的延时为相对于进入块仿真的时间
(较为少用)
十一、if else语句(需要在always块中使用)
if(表达式) 语句;
else if(表达式) 语句;
else 语句; (多个语句需放在begin end间)
十二、case语句:多分支语句(需要在always块中使用)
case(表达式) 分支:语句…… default:语句; endcase
十三、forever连续执行,常用于产生时钟信号
十四、while执行语句
十五、repeat
连续执行语句n次
repeat(表达式),在此表达式通常为常量表达式,表示重复次数。
begin语句;end
十六、for
十七、initial
initial:是一种用于初始化和仿真控制的过程块,主要用于测试平台(Testbench)和仿真场景。initial
块的特点如下:
- 执行一次:在仿真开始时执行一次,执行完毕后不再执行。
- 仿真专用:不可综合为硬件,仅用于仿真验证。
- 并行执行:多个
initial
块并行执行(与代码顺序无关)。 - 行为描述:用于控制信号时序、生成激励或监控输出。
二、语法格式
initial begin
// 语句序列
end
begin-end
:可选,若有多条语句则必须使用;单条语句可省略。
示例:
initial begin
a = 1'b0; // 初始化信号a为0
#10 a = 1'b1; // 延迟10个时间单位后,a置为1
#20 $finish; // 再延迟20个时间单位后,结束仿真
end
#10
是 Verilog 的延迟语句,表示暂停执行 10 个时间单位(时间单位由仿真器或timescale
指令定义)。- 延迟结束后,执行
a = 1'b1
。
#20 $finish;
- 功能:再延迟 20 个时间单位后,调用系统任务
$finish
结束仿真。 - 语法:
$finish
是 Verilog 的系统任务,用于终止当前仿真。- 整个
initial
块的执行时间为 30 个时间单位(10 + 20)。
三、常见用法
1. 信号初始化
在仿真开始时设置信号初始值:
reg clk, rst_n;
reg [7:0] data;
initial begin
clk = 1'b0; // 初始化时钟为0
rst_n = 1'b0; // 初始化复位信号为低(复位状态)
data = 8'h00; // 初始化数据为0
end
clk = 1'b0
:1'b0
表示 1 位二进制数(1'b
是 Verilog 的二进制字面量格式)
将时钟信号初始化为低电平。通常配合后续的时钟生成代码(如forever #5 clk = ~clk
)。rst_n = 1'b0
:
将复位信号初始化为低电平。大多数设计采用异步低电平复位,即rst_n=0
时电路进入复位状态。data = 8'h00
:
将数据信号初始化为 0。8'h00
表示 8 位十六进制数00
(二进制0000_0000
)。
2. 生成时钟信号
通过循环生成周期性时钟:
initial begin
forever begin
#5 clk = ~clk; // 每5个时间单位翻转一次,生成周期为10的时钟
end
end
3. 产生测试激励
按特定时序提供输入信号:
initial begin
// 复位序列
rst_n = 1'b0;
#20 rst_n = 1'b1; // 20个时间单位后释放复位
// 输入数据序列
#10 data = 8'hAA; // 释放复位后10个时间单位,发送数据AA
#20 data = 8'h55; // 再20个时间单位后,发送数据55
#30 $finish; // 结束仿真
end
4. 文件操作
读取测试数据或写入仿真结果:
integer file;
reg [7:0] data;
initial begin
file = $fopen("input.txt", "r"); // 打开输入文件
if (file == 0) begin
$display("Error: Cannot open file!");
$finish;
end
while (!$feof(file)) begin
$fscanf(file, "%h", data); // 从文件读取16进制数据
#10; // 等待10个时间单位
end
$fclose(file);
end
5. 仿真控制
使用系统任务控制仿真流程:
initial begin
#1000 $finish; // 1000个时间单位后自动结束仿真
// 打印关键信息
$display("Simulation started at time %0t", $time);
$monitor("At time %0t: a=%b, b=%b, sum=%b", $time, a, b, sum);
end
十八、always 块
是用于描述时序逻辑或组合逻辑的核心结构。它允许代码在特定条件下重复执行,是构建硬件电路行为模型的基础。
1. always
块的基本定义
always @(触发条件) begin
// 过程化语句
end
- 功能:
always
块会在触发条件满足时执行,执行完毕后等待下一次触发。 - 触发条件(敏感列表):
- 边沿触发(时序逻辑):
@(posedge clk)
(上升沿)或@(negedge clk)
(下降沿)。 - 电平触发(组合逻辑):
@(a or b)
或@(*)
(自动推断所有输入信号)。
- 边沿触发(时序逻辑):
1. @(a or b)
的含义
触发条件:当信号a
或b
的值发生变化时,always
块内的代码会立即执行。
2、@(*)
的含义
自动推断:编译器会自动分析always
块内的代码,将所有读操作的输入信号添加到敏感列表中。
always @(*) begin // 等价于 @(a or b or c)
out = a & b | c; // 自动推断敏感信号为a、b、c
end
2. 时序逻辑中的 always
块
用途:描述触发器、寄存器等时序元件。
示例 1:同步复位的 D 触发器
always @(posedge clk) begin
if (reset) // 同步复位(高电平有效)
q <= 1'b0; // 复位时输出清零
else
q <= d; // 时钟上升沿时,d的值赋给q
end
- 特点:
- 使用非阻塞赋值(
<=
)保证正确的时序行为。 - 仅在时钟上升沿触发,复位信号
reset
必须与时钟同步。
- 使用非阻塞赋值(
示例 2:带使能的计数器
reg [7:0] count;
always @(posedge clk) begin
if (reset)
count <= 8'b0;
else if (enable)
count <= count + 1; // 每个时钟周期加1
end
3. 组合逻辑中的 always
块
用途:描述逻辑门、多路选择器等组合电路。
示例 3:4 选 1 多路选择器
always @(*) begin // 敏感列表为*,自动包含所有输入
case (sel)
2'b00: out = a;
2'b01: out = b;
2'b10: out = c;
2'b11: out = d;
default: out = 1'bx; // 避免锁存器生成
endcase
end
第 1 行:always @(*)
- 触发条件:
*
表示敏感列表自动包含所有在块内被读取的信号(即sel
、a
、b
、c
、d
)。 - 组合逻辑标志:
always @(*)
是 Verilog 中定义组合逻辑的标准写法。
第 2-7 行:case
语句
- 选择逻辑:
- 当
sel
为2'b00
时,输出a
。 - 当
sel
为2'b01
时,输出b
。 - 当
sel
为2'b10
时,输出c
。 - 当
sel
为2'b11
时,输出d
。
- 当
第 8 行:default: out = 1'bx
- 默认分支:处理
sel
为非法值(如2'bx
或2'bz
)的情况。 - 避免锁存器:明确指定所有可能的输入场景,防止综合工具生成不必要的锁存器。
- 特点:
- 使用阻塞赋值(
=
)保证语句按顺序执行。 - 敏感列表需包含所有输入信号(或用
*
自动推断),否则可能导致仿真与综合结果不一致。
- 使用阻塞赋值(
示例 4:组合逻辑实现的加法器
always @(a or b) begin // 等价于 @(*)
sum = a + b;
end
4. 混合逻辑中的 always
块
用途:同时包含时序和组合逻辑的复杂电路。
示例 5:带保持功能的寄存器
always @(posedge clk or posedge reset) begin
if (reset)
q <= 1'b0;
else if (load)
q <= d; // 加载新数据
else
q <= q; // 保持原数据(等价于不操作)
end
5. 无限循环的 always
块
用途:生成时钟信号(仅用于仿真)。
示例 6:时钟生成
reg clk;
always #5 clk = ~clk; // 生成10个时间单位周期的时钟(50%占空比)
6. 敏感列表的注意事项
组合逻辑必须包含所有输入:
// 错误示例:敏感列表遗漏b,可能导致仿真错误
always @(a) begin
out = a & b; // 当b变化时,out不会更新
end
// 正确写法:
always @(a or b) begin ... end // 或使用 @(*)
时序逻辑通常仅对时钟边沿敏感:
// 同步复位的正确写法
always @(posedge clk) begin
if (reset) ...
end
// 异步复位的写法(复位信号独立触发)
always @(posedge clk or posedge reset) begin
if (reset) ...
end
7. 阻塞赋值和非阻塞赋值类型对比
特性 | 阻塞赋值(=) | 非阻塞赋值(<=) |
---|---|---|
执行顺序 | 立即执行,按顺序执行 | 并行执行,在时间步结束时更新 |
适用场景 | 组合逻辑 | 时序逻辑 |
硬件对应 | 逻辑门 | 触发器 / 寄存器 |
十九、function
用于定义可重复使用的组合逻辑单元,类似于软件中的函数。它接收输入参数,执行计算,并返回单个结果。
1. function
的基本定义
function [返回值位宽] 函数名;
input [参数位宽] 参数1;
input [参数位宽] 参数2;
// 局部变量声明
reg [位宽] 变量名;
// 函数体
begin
// 计算逻辑
函数名 = 表达式; // 将结果赋给函数名本身
end
endfunction
- 特点:
- 组合逻辑:函数内不能包含时序控制(如
#延迟
、@事件
)。 - 立即返回:执行完毕后立即返回结果。
- 单一返回值:通过函数名本身赋值返回结果。
- 组合逻辑:函数内不能包含时序控制(如
2. 简单函数示例
示例 1:计算两个数的和
function [7:0] add_numbers;
input [7:0] a;
input [7:0] b;
begin
add_numbers = a + b; // 返回a和b的和
end
endfunction
调用方式:
result = add_numbers(10, 20); // result = 30
示例 2:判断奇偶性
function bit is_odd;
input [7:0] num;
begin
is_odd = (num % 2 == 1); // 返回1(奇数)或0(偶数)
end
endfunction
调用方式:
result = is_odd(number); // result = 1'b1
3. 带局部变量的函数
function [7:0] max_of_three;
input [7:0] a, b, c;
reg [7:0] temp; // 局部变量
begin
temp = (a > b) ? a : b;
max_of_three = (temp > c) ? temp : c; // 返回三者中的最大值
end
endfunction
调用方式:
max_value = max_of_three(a, b, c); // max_value = 25
4. 多维数组作为参数
function [7:0] sum_array;
input [7:0] array [0:3]; // 4元素的数组参数
reg [7:0] i, sum;
begin
sum = 0;
for (i = 0; i < 4; i = i + 1) begin
sum = sum + array[i];
end
sum_array = sum; // 返回数组元素的和
end
endfunction
调用方式:
initial begin
// 初始化数组元素
my_array[0] = 8'd10;
my_array[1] = 8'd20;
my_array[2] = 8'd30;
my_array[3] = 8'd40;
// 调用函数计算数组总和
result = sum_array(my_array); // result = 100 (8'd100)
5. 函数的递归调用
// 计算阶乘(n!)
function [31:0] factorial;
input [7:0] n;
begin
if (n == 0)
factorial = 1;
else
factorial = n * factorial(n-1); // 递归调用
end
endfunction
调用方式:
module test_factorial;
reg [7:0] input_n; // 输入值
reg [31:0] result; // 存储结果
initial begin
input_n = 5; // 计算 5!
// 调用函数
result = factorial(input_n); // result = 120 (5! = 120)
$display("%d的阶乘是: %d", input_n, result); // 输出: 120
end
二十、task:
用于定义可复用的代码块,类似于function
,但功能更强大。与function
只能实现组合逻辑不同,task
可以包含时序控制(如延时、事件触发),支持多输出参数,且不要求立即返回结果。
1. task
的基本定义
task 任务名;
input [位宽] 输入参数1; // 输入参数(可选)
output [位宽] 输出参数1; // 输出参数(可选)
inout [位宽] 双向参数1; // 双向参数(可选)
reg [位宽] 局部变量; // 局部变量(可选)
// 任务体
begin
// 可包含时序控制和过程化语句
#延迟; // 延时语句
@(事件); // 事件触发
wait(条件); // 等待条件满足
// 逻辑操作
输出参数1 = 表达式; // 赋值给输出参数
end
endtask
- 特点:
- 时序支持:可包含
#延迟
、@事件
、wait
等时序控制语句。 - 多参数传递:通过
output
和inout
参数返回多个值。 - 过程化执行:任务内的语句按顺序执行。
- 时序支持:可包含
2. 简单任务示例
示例 1:带延时的信号生成
task generate_pulse;
input [7:0] width; // 脉冲宽度(时间单位)
output pulse; // 输出脉冲信号
begin
pulse = 1'b1; // 脉冲置高
#width; // 保持width个时间单位
pulse = 1'b0; // 脉冲置低
end
endtask
调用方式:
reg my_pulse;
generate_pulse(10, my_pulse); // 生成宽度为10的脉冲
示例 2:带输入输出的任务
task add_numbers;
input [7:0] a, b; // 输入两个数
output [8:0] sum; // 输出和(9位以避免溢出)
begin
sum = a + b; // 计算和
end
endtask
调用方式:
reg [7:0] x = 5, y = 10;
reg [8:0] result;
add_numbers(x, y, result); // result = 15 (9'd15)
3. 包含时序控制的任务
task wait_and_check;
input signal; // 待监测的信号
input [7:0] timeout; // 超时时间
output success; // 检查结果(成功/失败)
begin
success = 1'b0; // 默认失败
// 等待信号变高或超时
fork
begin
@(posedge signal); // 等待信号上升沿
success = 1'b1; // 成功
end
begin
#timeout; // 超时等待
end
join_any
disable fork; // 终止未完成的线程
end
endtask
调用方式:
reg flag, check_result;
wait_and_check(flag, 100, check_result); // 等待flag上升沿,最多100个时间单位
4. 在模块中调用任务
module test_task;
reg clk, reset, enable;
reg [7:0] data_in, data_out;
initial begin
// 初始化信号
clk = 0;
reset = 1;
enable = 0;
// 调用任务
initialize_system(); // 初始化系统
generate_clock(10); // 生成周期为10的时钟
// 执行操作
#50;
reset = 0; // 释放复位
#20;
process_data(8'd42, data_out); // 处理数据
end
// 任务定义
task initialize_system;
begin
// 系统初始化操作
reset = 1;
enable = 0;
#20; // 保持复位20个时间单位
end
endtask
task generate_clock;
input period;
begin
forever begin
#(period/2) clk = ~clk; // 生成时钟
end
end
endtask
task process_data;
input [7:0] input_data;
output [7:0] output_data;
begin
@(posedge clk); // 等待时钟上升沿
if (!reset && enable) begin
// 数据处理逻辑
output_data = input_data * 2;
end
end
endtask
endmodule
函数与任务(task)的对比
特性 | 函数(function) | 任务(task) |
---|---|---|
返回值 | 必须有一个返回值(通过函数名赋值) | 可以没有返回值,或通过 output 参数返回 |
时序控制 | 不能包含(如 # 、@ 、wait ) |
可以包含时序控制语句 |
调用 | 可在表达式中直接调用 | 必须单独作为一条语句调用 |
适用场景 | 组合逻辑计算 | 时序逻辑或复杂操作 |