FPGA学习

发布于:2025-07-03 ⋅ 阅读:(14) ⋅ 点赞:(0)

一、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

image

  • assign 是连续赋值语句,用于描述组合逻辑(无记忆,输出实时跟随输入变化)。
  • 功能:将输入 a 和 b 相加,结果赋值给输出 cVerilog 会自动处理位宽扩展(2 位 + 2 位 = 3 位,匹配 c 的位宽)。
  • 模块实例化部分(adder u1(.a(in1), .b(in2), .c(in3));

    这部分是复用已定义的模块adder),并将其端口与外部信号连接,类似 “在电路板上焊接芯片并接线”。

  • adder:要实例化的模块名(必须和前面定义的 module adder 一致)。
  • u1:实例名(自定义,同一模块可实例化多次,如 u2u3 等,用于区分不同实例)。
  • .端口名(外部信号)命名端口映射把模块的内部端口和外部信号一一对应:
    • .a(in1):模块的 a 端口 ↔ 外部信号 in1in1 需是 2 位信号)。
    • .b(in2):模块的 b 端口 ↔ 外部信号 in2in2 需是 2 位信号)。
    • .c(in3):模块的 c 端口 ↔ 外部信号 in3in3 需是 3 位信号)。
  • 外部信号的要求
  • 声明in1in2in3 必须在当前作用域(比如上层模块)中提前声明,例如:

    verilog

    • wire [1:0] in1, in2;  // 2位输入信号(wire类型,因为是组合逻辑连接)
      wire [2:0] in3;       // 3位输出信号(wire类型,因为模块输出是wire)

二、数据类型及常量变量

1、Verilog HDL有四种基本的值

image

(1)其中x和z不区分大小写;

(2)z也可以使用?表示,虽然不懂为什么不是表示未知……

2、Verilog HDL三类常量

 (1)整型:直接用数字序列表示的整数,默认采用十进制。

              1,-2;

  • 特点:书写简单,但无法明确指定数值的位宽和进制。
  • 注意:负数只能用十进制表示,不能用基数表示法表示负数。

B、基数表示:<位宽>’<进制><数字> 

image  

  • b 或 B:二进制
  • o 或 O:八进制
  • d 或 D:十进制
  • h 或 H:十六进制

 image

下划线(_):可以在数字中插入下划线,提高可读性,但不影响数值

image

  • 无符号数:默认情况下,整型常量是无符号数。
  • 有符号数:在常量前加负号(-),但负数只能用十进制表示。
  • -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 用于建模信号连接类似电路板上的导线,主要特点:

  1. 信号载体:用于传递和连接模块、门电路之间的信号。
  2. 被动赋值:不能存储值,必须由驱动源(如 assign、门级实例、模块输出)持续驱动。
  3. 默认状态:若无驱动,值为高阻态 z
  4. 硬件对应:直接映射到物理连线或组合逻辑输出。

二、声明语法

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 用于表示存储元件,具有以下特点:

  1. 存储能力:能保持当前值,直到被新值覆盖。
  2. 主动赋值:通过过程块(initial 或 always)赋值。
  3. 默认状态:初始值为不定态 x(除非显式初始化)。
  4. 硬件对应:综合后通常映射为触发器(时序逻辑)或锁存器(组合逻辑)。

 二、声明语法

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型

六、运算符与运算表达式

image

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);(逻辑与) vs wire bit = a & b;(位与)。

6、位运算符

符号~(按位取反)、&(按位与)、|(按位或)、^(按位异或)
功能:对操作数逐位进行逻辑运算,结果位宽与操作数一致。

运算符 示例 说明
~ ~4'b1010 = 4'b0101 每一位取反(1→00→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=1asel=0b(实现多路选择器)。
assign max = (a>b) ? a : b; ab中的较大者(支持嵌套,如三数取最大)。
注意expr1expr2需位宽兼容,否则会截断(如16'b08'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)最后一句执行完,才能跳出该块

image

十、fork_join语句:并行块

(1)块内语句同时执行

(2)每条语句的延时为相对于进入块仿真的时间

(较为少用)

image

十一、if else语句(需要在always块中使用)

if(表达式)   语句;
else if(表达式)  语句;
else   语句;
(多个语句需放在begin end间)

image

十二、case语句:多分支语句(需要在always块中使用)

case(表达式)

分支:语句……

default:语句;

endcase

十三、forever连续执行,常用于产生时钟信号

image

十四、while执行语句

image

十五、repeat

    连续执行语句n次

    repeat(表达式),在此表达式通常为常量表达式,表示重复次数。

    begin语句;end

十六、for

image

十七、initial

        initial:是一种用于初始化和仿真控制的过程块,主要用于测试平台(Testbench)和仿真场景。initial 块的特点如下:
  1. 执行一次:在仿真开始时执行一次,执行完毕后不再执行。
  2. 仿真专用:不可综合为硬件,仅用于仿真验证。
  3. 并行执行:多个 initial 块并行执行(与代码顺序无关)。
  4. 行为描述:用于控制信号时序、生成激励或监控输出。

二、语法格式

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) 的含义

                        触发条件:当信号ab的值发生变化时,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 @(*)
  • 触发条件*表示敏感列表自动包含所有在块内被读取的信号(即selabcd)。
  • 组合逻辑标志always @(*)是 Verilog 中定义组合逻辑的标准写法。
第 2-7 行:case语句
  • 选择逻辑
    • sel2'b00时,输出a
    • sel2'b01时,输出b
    • sel2'b10时,输出c
    • sel2'b11时,输出d
第 8 行:default: out = 1'bx
  • 默认分支:处理sel为非法值(如2'bx2'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. 敏感列表的注意事项

  1. 组合逻辑必须包含所有输入

// 错误示例:敏感列表遗漏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. 阻塞赋值和非阻塞赋值类型对比

特性 阻塞赋值(=) 非阻塞赋值(<=)
执行顺序 立即执行,按顺序执行 并行执行,在时间步结束时更新
适用场景 组合逻辑 时序逻辑
硬件对应 逻辑门 触发器 / 寄存器

 image

十九、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等时序控制语句。
    • 多参数传递:通过outputinout参数返回多个值。
    • 过程化执行:任务内的语句按顺序执行。

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 可以包含时序控制语句
    调用 可在表达式中直接调用 必须单独作为一条语句调用
    适用场景 组合逻辑计算 时序逻辑或复杂操作


    网站公告

    今日签到

    点亮在社区的每一天
    去签到