一、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, // 输入端口ainput b, // 输入端口boutput 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) beginif (reset)count <= 8'b0; // 复位时计数器清零else if (enable)count <= count + 1; // 使能时每个时钟周期加1
endendmodule
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) beginif (reset)count <= INIT; // 复位时加载初始值else if (enable)count <= count + 1;
endendmodule
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 beginstr[0] = "A"; // 或 8'd65 或 8'h41str[1] = "B"; // 或 8'd66 或 8'h42str[2] = "C"; // 或 8'd67 或 8'h43
endinitial 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) beginif (!rst_n) // 异步复位(低电平有效)counter <= 4'b0;else if (en) // 使能时计数counter <= counter + 1;
end
2. 组合逻辑(锁存器)
在电平敏感的 always
块中使用(需谨慎,易产生锁存器):
reg data_out;always @(*) beginif (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; // 函数内部变量beginresult = 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、定义:
用于将多条语句组合成一个顺序执行的代码块,类似于 C 语言中的花括号{}
。它主要用于always
、initial
、task
和function
等过程块中,确保语句按顺序执行。
begin语句1;语句2;// ... 更多语句
end
- 顺序执行:块内语句按书写顺序依次执行(仅适用于过程块)。
- 命名块:可通过
begin: 块名
为块命名,用于调试或声明局部变量。
2. 在always
块中的应用
示例 1:组合逻辑(阻塞赋值)
always @(*) beginif (sel == 1'b0)out = a;elseout = b;
end
- 说明:
begin-end
将if-else
语句组合为一个整体。- 使用阻塞赋值(
=
),确保语句按顺序执行。
示例 2:时序逻辑(非阻塞赋值)
always @(posedge clk or posedge reset) beginif (reset)q <= 1'b0; // 异步复位elseq <= d; // 时钟上升沿更新
end
- 说明:
- 使用非阻塞赋值(
<=
),避免竞争冒险。 begin-end
包裹复位和赋值操作。
- 使用非阻塞赋值(
3. 在initial
块中的应用
示例:测试平台的初始化
initial begin// 初始化信号clk = 0;reset = 1;data = 8'h00;// 控制时序#10 reset = 0; // 10个时间单位后释放复位#20 data = 8'h55; // 再20个时间单位后写入数据#30 $finish; // 结束仿真
end
initial
块仅执行一次,常用于测试平台(Testbench)。begin-end
组合多条初始化和延时语句。
4. 在task
和function
中的应用
示例:任务(task)中的顺序操作
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 begina = 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 beginclk = 1'b0; // 初始化时钟为0rst_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 beginforever 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 beginfile = $fopen("input.txt", "r"); // 打开输入文件if (file == 0) begin$display("Error: Cannot open file!");$finish;endwhile (!$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) beginif (reset) // 同步复位(高电平有效)q <= 1'b0; // 复位时输出清零elseq <= d; // 时钟上升沿时,d的值赋给q
end
- 特点:
- 使用非阻塞赋值(
<=
)保证正确的时序行为。 - 仅在时钟上升沿触发,复位信号
reset
必须与时钟同步。
- 使用非阻塞赋值(
示例 2:带使能的计数器
reg [7:0] count;
always @(posedge clk) beginif (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) beginif (reset)q <= 1'b0;else if (load)q <= d; // 加载新数据elseq <= q; // 保持原数据(等价于不操作)
end
5. 无限循环的 always
块
用途:生成时钟信号(仅用于仿真)。
示例 6:时钟生成
reg clk;
always #5 clk = ~clk; // 生成10个时间单位周期的时钟(50%占空比)
6. 敏感列表的注意事项
-
组合逻辑必须包含所有输入:
// 错误示例:敏感列表遗漏b,可能导致仿真错误
always @(a) beginout = a & b; // 当b变化时,out不会更新
end// 正确写法:
always @(a or b) begin ... end // 或使用 @(*)
时序逻辑通常仅对时钟边沿敏感:
// 同步复位的正确写法
always @(posedge clk) beginif (reset) ...
end// 异步复位的写法(复位信号独立触发)
always @(posedge clk or posedge reset) beginif (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;
beginadd_numbers = a + b; // 返回a和b的和
end
endfunction
调用方式:
result = add_numbers(10, 20); // result = 30
示例 2:判断奇偶性
function bit is_odd;input [7:0] num;
beginis_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; // 局部变量
begintemp = (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;
beginsum = 0;for (i = 0; i < 4; i = i + 1) beginsum = sum + array[i];endsum_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;
beginif (n == 0)factorial = 1;elsefactorial = n * factorial(n-1); // 递归调用
end
endfunction
调用方式:
module test_factorial;reg [7:0] input_n; // 输入值reg [31:0] result; // 存储结果initial begininput_n = 5; // 计算 5!// 调用函数result = factorial(input_n); // result = 120 (5! = 120)$display("%d的阶乘是: %d", input_n, result); // 输出: 120end
二十、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; // 输出脉冲信号
beginpulse = 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位以避免溢出)
beginsum = 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; // 检查结果(成功/失败)
beginsuccess = 1'b0; // 默认失败// 等待信号变高或超时forkbegin@(posedge signal); // 等待信号上升沿success = 1'b1; // 成功endbegin#timeout; // 超时等待endjoin_anydisable 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个时间单位endendtasktask generate_clock;input period;beginforever begin#(period/2) clk = ~clk; // 生成时钟endendendtasktask process_data;input [7:0] input_data;output [7:0] output_data;begin@(posedge clk); // 等待时钟上升沿if (!reset && enable) begin// 数据处理逻辑output_data = input_data * 2;endendendtask
endmodule
函数与任务(task)的对比
特性 | 函数(function) | 任务(task) |
---|---|---|
返回值 | 必须有一个返回值(通过函数名赋值) | 可以没有返回值,或通过 output 参数返回 |
时序控制 | 不能包含(如 # 、@ 、wait ) | 可以包含时序控制语句 |
调用 | 可在表达式中直接调用 | 必须单独作为一条语句调用 |
适用场景 | 组合逻辑计算 | 时序逻辑或复杂操作 |