欢迎光临
我们一直在努力

Verilog 模块结构详解

Verilog 模块结构详解

Verilog 是一种硬件描述语言(HDL),其核心是模块(Module),模块是电路设计的构建块。每个 Verilog 设计都由一个或多个模块组成,这些模块可以层次化地实例化(类似于函数调用),形成更大的系统。模块描述了电路的接口(端口)、内部逻辑和行为。

模块结构是 Verilog 的基础,理解它有助于编写可综合、可仿真的代码。下面我将详解模块的结构,包括语法、组成部分、注意事项,然后提供示例代码和完整例子。内容基于 IEEE 1364-2005 标准(传统 Verilog)和 SystemVerilog 扩展(现代常用)。

1. 模块的基本语法结构

一个 Verilog 模块的典型结构如下:

module 模块名 (
// 端口声明(接口)
input 类型 [宽度-1:0] 输入端口1, 输入端口2, …,
output 类型 [宽度-1:0] 输出端口1, 输出端口2, …,
inout 类型 [宽度-1:0] 双向端口1, 双向端口2, …
);

// 内部信号声明
wire 类型 [宽度-1:0] 内部连线信号;
reg 类型 [宽度-1:0] 寄存器信号; // 或 logic 在 SystemVerilog 中
// 其他类型如 integer, real 等(非综合时用)

// 模块主体:逻辑描述
// 1. 连续赋值(组合逻辑)
assign 输出 = 表达式;

// 2. always 块(时序/组合逻辑)
always @(敏感列表) begin
// 语句
end

// 3. 实例化子模块
子模块名 实例名 (
.端口1(信号1),
.端口2(信号2),

);

// 4. 其他结构:initial(测试用,非综合)、generate(参数化生成)等

endmodule

  • 关键组成部分详解:

  • module 关键字和模块名:

    • 语法:module 模块名;(模块名应唯一、描述性强,如 adder)。
    • 目的:定义模块的开始。模块名用于实例化时引用。
    • 注意:模块名不能是 Verilog 关键字(如 always),且区分大小写(但工具通常不敏感)。
  • 端口列表(Port List):

    • 在括号内声明所有外部接口。
    • 类型:
      • input:输入端口,只能读(从外部传入)。
      • output:输出端口,只能写(向外部输出)。
      • inout:双向端口(如总线),可读可写,但需小心避免冲突。
    • 数据类型和宽度:
      • 默认是 wire 类型,1 位宽。
      • 可指定:input wire [7:0] data;(8 位向量)。
      • SystemVerilog 中常用 logic 类型(更灵活,可用于 reg/wire)。
    • 注意:端口声明必须完整,否则综合工具会报错。端口顺序影响实例化时的位置连接(但推荐用命名连接)。
  • 内部声明(Declarations):

    • wire:用于组合逻辑的连线(默认类型)。
    • reg:用于时序逻辑的寄存器(always 块中赋值)。
    • logic(SystemVerilog):统一类型,可替换 wire/reg。
    • 参数(parameter):用于可配置,如 parameter WIDTH = 8;。
    • 注意:所有信号必须声明宽度(如 [31:0]),否则默认为 1 位。避免隐式网(implicit net)。
  • 模块主体(Body):

    • 连续赋值(assign):描述组合逻辑,如 assign sum = a + b;。实时计算,无时钟。
    • always 块:
      • always @(posedge clk):时序逻辑(触发器、计数器)。
      • always @(*) 或 always_comb(SystemVerilog):组合逻辑。
      • always @(posedge clk or posedge rst):带异步复位。
      • 注意:阻塞赋值 = 用于组合,非阻塞 <= 用于时序。
    • initial 块:初始化信号,仅用于仿真(非综合)。
    • generate/endgenerate:参数化生成电路(如 for 循环生成多位逻辑)。
    • 实例化(Instantiation):调用其他模块,如函数调用。两种方式:
      • 位置连接:adder u1 (a, b, sum);(按顺序)。
      • 命名连接:adder u1 (.a(a), .b(b), .sum(sum));(推荐,防错)。
    • 注意:主体中不能有循环依赖。综合时避免非 RTL 结构(如 delay #10,非综合)。
  • endmodule:

    • 结束模块定义。每个 module 必须匹配一个 endmodule。
  • 整体注意事项:

    • 可综合性:模块应是 RTL 风格(寄存器传输级),避免行为级(如 for 循环在 always 中,除非 generate)。工具如 Vivado 会忽略非综合代码。
    • 层次化:模块可嵌套实例化,形成顶层(top module)。
    • SystemVerilog 增强:添加 interface、enum、struct 等,提高抽象性。
    • 常见错误:端口未连接、敏感列表不全(导致锁存器)、类型不匹配。
    • 调试:用测试台(testbench)仿真模块,检查波形。
2. 示例代码:简单模块

以下是几个逐步复杂的示例,展示模块结构的应用。

示例1:基本组合逻辑模块(2位加法器)

module adder (
input [1:0] a, // 输入端口
input [1:0] b,
output [1:0] sum, // 输出端口
output carry
);

// 内部信号(可选,这里无)

// 主体:连续赋值
assign {carry, sum} = a + b; // 组合逻辑

endmodule

  • 解释:无内部信号,直接用 assign 描述加法。适合简单门级电路。
示例2:带时序逻辑的模块(D触发器)

module d_flip_flop (
input clk, // 时钟输入
input rst_n, // 异步复位(低有效)
input d, // 数据输入
output reg q // 输出寄存器
);

// 无额外内部信号

// 主体:always 块(时序逻辑)
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
q <= 1'b0; // 复位
else
q <= d; // 正常赋值
end

endmodule

  • 解释:使用 reg 类型输出,always 块处理时序。注意非阻塞赋值 <=。
示例3:参数化模块(可配置宽度移位寄存器)

module shift_register #(
parameter WIDTH = 8 // 参数声明
) (
input clk,
input rst_n,
input shift_en, // 使能
input din, // 串行输入
output reg [WIDTH-1:0] q // 并行输出
);

// 内部信号(可选)

// 主体:时序逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
q <= {WIDTH{1'b0}}; // 全0复位
else if (shift_en)
q <= {q[WIDTH-2:0], din}; // 左移
end

endmodule

  • 解释:使用 parameter 使模块可复用。实例化时可覆盖:shift_register #(.WIDTH(16)) u1 (…);。
3. 完整例子:层次化模块(简单计数器系统)

假设构建一个 4 位计数器系统,包括子模块(加法器)和顶层模块。

  • 子模块:加法器(从示例1复用)

// adder.v(如上)

  • 顶层模块:计数器(实例化加法器)

module counter_4bit (
input clk,
input rst_n,
input en, // 使能
output [3:0] count // 输出
);

reg [3:0] current_count; // 内部寄存器
wire [3:0] next_count; // 内部连线

// 实例化子模块
adder u_adder (
.a(current_count),
.b(4'b0001), // +1
.sum(next_count),
.carry() // 未用,空接
);

// 时序逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
current_count <= 4'b0000;
else if (en)
current_count <= next_count;
end

// 输出赋值
assign count = current_count;

endmodule

  • 解释:顶层模块实例化 adder 子模块,形成层次。内部用 reg 存状态,wire 连线。测试时,可写 testbench:

module tb_counter;
reg clk = 0, rst_n = 1, en = 1;
wire [3:0] count;

counter_4bit dut (.clk(clk), .rst_n(rst_n), .en(en), .count(count));

always #5 clk = ~clk; // 时钟生成

initial begin
#10 rst_n = 0; #10 rst_n = 1;
#100 $finish;
end
endmodule

  • 运行仿真:输出 count 从 0 递增到 15,然后溢出。
4. 总结与最佳实践
  • 结构优势:模块化设计便于复用、调试和团队协作。
  • 工具推荐:用 ModelSim 仿真,Vivado/Quartus 综合。
  • 进阶:学习 SystemVerilog 的 module 接口(interface),用于复杂设计。
  • 常见问题解决:如果端口连接错,用命名实例化;时序问题,检查敏感列表。

Verilog Testbench 示例详解(2026 年视角,包含经典写法 + 现代推荐)

Testbench(简称 TB)是用来验证 Verilog 设计(DUT = Device Under Test)的仿真环境。
它不是可综合的硬件代码,而是纯仿真用的“软件”代码。

Testbench 典型结构(从简单到复杂)

复杂度写法风格适用场景推荐度(2026年)主要特点
入门 纯 initial + always 小模块、快速验证、面试/作业 ★★★★☆ 简单、直观、代码量少
中级 initial + task + $random 中等规模、随机测试、边界条件 ★★★★★ 灵活、可复用、常见面试题写法
进阶 SystemVerilog class + interface 大型 ASIC/SoC 验证 ★★★★☆ 可维护、可重用、UVM 前置
专业级 UVM(Universal Verification Methodology) 工业级芯片验证 ★★★★★(大厂) 标准化、复用性极强、但学习曲线陡峭

1. 最经典入门级 Testbench(纯 Verilog 风格)

DUT:简单 4 位计数器(带使能、同步复位)

// counter.v
module counter (
input clk,
input rst_n,
input en,
output reg [3:0] count
);
always @(posedge clk) begin
if (!rst_n)
count <= 4'b0000;
else if (en)
count <= count + 1'b1;
end
endmodule

Testbench(tb_counter.v)—— 最常见写法

`timescale 1ns / 1ps // 时间单位 1ns,精度 1ps

module tb_counter;

// 1. 声明信号(DUT 的输入用 reg,输出用 wire)
reg clk;
reg rst_n;
reg en;
wire [3:0] count;

// 2. 实例化 DUT(推荐命名式连接)
counter dut (
.clk (clk),
.rst_n (rst_n),
.en (en),
.count (count)
);

// 3. 时钟生成(最经典写法)
initial begin
clk = 0;
forever #5 clk = ~clk; // 周期 10ns → 100MHz
end

// 4. 复位序列(非常重要!)
initial begin
rst_n = 0; // 复位有效(低有效)
en = 0;
#20; // 保持复位至少 2 个周期
rst_n = 1; // 释放复位
end

// 5. 激励(stimulus)生成
initial begin
// 等待复位释放
wait(rst_n == 1);
@(posedge clk); // 等待下一个上升沿(同步)
#2; // 小延迟,避免与边沿重合(好习惯)

// 测试用例 1:使能计数
en = 1;
repeat(20) @(posedge clk); // 数 20 次

// 测试用例 2:暂停计数
en = 0;
repeat(5) @(posedge clk);

// 测试用例 3:再次使能,看是否继续
en = 1;
repeat(10) @(posedge clk);

// 结束仿真
#20;
$display("Simulation finished at time %0t", $time);
$finish;
end

// 6. 监控(可选,但强烈推荐)
initial begin
$monitor("Time=%0t | rst_n=%b | en=%b | count=%d (%b)",
$time, rst_n, en, count, count);
end

// 7. 波形转储(VCD 文件,用于波形查看)
initial begin
$dumpfile("counter_tb.vcd");
$dumpvars(0, tb_counter); // 0 = 所有层级
end

endmodule

运行命令示例(ModelSim/Questa/VCS 等):

vlog counter.v tb_counter.v
vsim -c tb_counter -do "run -all; quit -f"
# 或直接 GUI 运行

2. 中级写法:使用 task + 随机测试(推荐日常使用)

增加 task 封装常用操作,提高可读性与复用性

// 在上面的 tb_counter 基础上增加以下部分

// task:复位任务
task automatic do_reset();
rst_n = 0;
repeat(3) @(posedge clk);
rst_n = 1;
repeat(2) @(posedge clk);
endtask

// task:施加一次使能脉冲
task automatic apply_enable(input integer cycles);
en = 1;
repeat(cycles) @(posedge clk);
en = 0;
endtask

// 主激励 initial 块改成这样
initial begin
$display("Starting test…");

do_reset();

// 测试 1:正常计数 15 次
apply_enable(15);

// 测试 2:随机使能/暂停
repeat(8) begin
if ($random % 2) // 50% 概率使能
apply_enable($urandom_range(3,12));
else
repeat($urandom_range(2,8)) @(posedge clk);
end

#100;
$display("All tests PASS!");
$finish;
end

3. 现代推荐:带 self-checking 的简单 SystemVerilog 风格

增加自动检查(assert),防止手动看波形出错

// DUT 同上

module tb_counter_sv;

timeunit 1ns;
timeprecision 100ps;

logic clk = 0, rst_n = 0, en = 0;
logic [3:0] count;

counter dut (.*); // SystemVerilog 自动连接(.*)

// 时钟
always #5 clk = ~clk;

// 黄金模型(reference model)
logic [3:0] count_exp = 0;

always @(posedge clk) begin
if (!rst_n)
count_exp <= 0;
else if (en)
count_exp <= count_exp + 1;
end

// 自动检查
always @(posedge clk) begin
if (rst_n) begin // 复位后才检查
assert (count == count_exp)
else begin
$error("Count mismatch! Expected=%0d, Got=%0d at time %0t",
count_exp, count, $time);
$fatal(1); // 或 $stop
end
end
end

initial begin
rst_n = 0; #23; rst_n = 1;

repeat(5) @(posedge clk);
en = 1;
repeat(30) @(posedge clk);
en = 0;
repeat(10) @(posedge clk);

$display("Test finished OK!");
$finish;
end

// 波形 + monitor
initial begin
$dumpfile("dump.vcd");
$dumpvars;
$monitor("%t | en=%b | count=%h | exp=%h", $time, en, count, count_exp);
end

endmodule

快速总结表(2026 年工程师真实选择)

场景推荐写法为什么?
学习/面试/小项目 纯 initial + task 简单、好理解、面试官最爱看
中型 FPGA 项目 SystemVerilog + task + assert 自动检查、减少手动 debug
大型 ASIC 验证 UVM 或 class-based TB 可重用、覆盖率管理、团队协作
超快速验证(几分钟搞定) initial + $random + $finish 最快出结果

一句话建议(2026 年版):

先把纯 Verilog 的 initial + task + $monitor/assert 写熟练(占日常 70%),再学 SystemVerilog interface + class,最后视项目规模决定是否深入 UVM。

Verilog FIFO Testbench 示例
(2026 年主流风格:带自检查 + 随机测试 + 覆盖基本场景)

下面给出一个同步 FIFO(深度 16,数据宽度 8bit)的完整 Testbench 示例,包含以下测试点:

  • 基本写入/读取
  • 满(full)标志检查
  • 空(empty)标志检查
  • 连续写满再连续读空
  • 随机写读交替(模拟真实使用场景)
  • 写满后继续写(检查是否正确保持 full)
  • 读空后继续读(检查是否正确保持 empty)
  • 同时读写(当 FIFO 不满不空时)

DUT:简单同步 FIFO(参考实现)

// sync_fifo.v
module sync_fifo #(
parameter DEPTH = 16,
parameter DATA_WIDTH = 8
) (
input wire clk,
input wire rst_n,
input wire wr_en,
input wire [DATA_WIDTH-1:0] din,
input wire rd_en,
output reg [DATA_WIDTH-1:0] dout,
output reg empty,
output reg full
);

localparam ADDR_WIDTH = $clog2(DEPTH);

reg [DATA_WIDTH-1:0] mem [0:DEPTH-1];
reg [ADDR_WIDTH:0] wr_ptr, rd_ptr; // 多一位用于区分满/空

wire wr_ok = wr_en && !full;
wire rd_ok = rd_en && !empty;

// 写入
always @(posedge clk) begin
if (wr_ok)
mem[wr_ptr[ADDR_WIDTH-1:0]] <= din;
end

// 读出
always @(posedge clk) begin
if (rd_ok)
dout <= mem[rd_ptr[ADDR_WIDTH-1:0]];
end

// 指针更新
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
wr_ptr <= 0;
rd_ptr <= 0;
end
else begin
if (wr_ok) wr_ptr <= wr_ptr + 1;
if (rd_ok) rd_ptr <= rd_ptr + 1;
end
end

// 满/空判断(经典方法:多一位指针)
wire [ADDR_WIDTH:0] next_wr_ptr = wr_ptr + 1;
wire [ADDR_WIDTH:0] next_rd_ptr = rd_ptr + 1;

always @(*) begin
full = (next_wr_ptr[ADDR_WIDTH-1:0] == rd_ptr[ADDR_WIDTH-1:0]) && (next_wr_ptr[ADDR_WIDTH] != rd_ptr[ADDR_WIDTH]);
empty = (wr_ptr == rd_ptr);
end

endmodule

Testbench(推荐写法:带自检查 + 随机测试)

`timescale 1ns / 1ps

module tb_sync_fifo;

// 参数与 DUT 保持一致
localparam DEPTH = 16;
localparam DATA_WIDTH = 8;
localparam CLK_PERIOD = 10;

// 时钟与复位
reg clk = 0;
reg rst_n = 0;

// FIFO 接口
reg wr_en = 0;
reg [DATA_WIDTH-1:0] din = 0;
reg rd_en = 0;
wire [DATA_WIDTH-1:0] dout;
wire empty;
wire full;

// 实例化 DUT
sync_fifo #(
.DEPTH (DEPTH),
.DATA_WIDTH (DATA_WIDTH)
) dut (
.clk (clk),
.rst_n (rst_n),
.wr_en (wr_en),
.din (din),
.rd_en (rd_en),
.dout (dout),
.empty (empty),
.full (full)
);

// 时钟生成
always #(CLK_PERIOD/2) clk = ~clk;

// 黄金模型(用于自检查)
reg [DATA_WIDTH-1:0] golden_queue [$]; // 队列模拟 FIFO 内容
integer error_cnt = 0;

// 任务:复位
task automatic fifo_reset();
rst_n = 0;
wr_en = 0; rd_en = 0; din = 0;
repeat(5) @(posedge clk);
rst_n = 1;
repeat(3) @(posedge clk);
$display("[%t] Reset complete", $time);
endtask

// 任务:单次写
task automatic fifo_write(input [DATA_WIDTH-1:0] data);
@(posedge clk);
wr_en = 1;
din = data;
@(posedge clk);
wr_en = 0;
golden_queue.push_back(data); // 更新黄金模型
$display("[%t] WRITE 0x%02h full=%b empty=%b", $time, data, full, empty);
endtask

// 任务:单次读 + 检查
task automatic fifo_read();
reg [DATA_WIDTH-1:0] expected;
@(posedge clk);
rd_en = 1;
@(negedge clk); // 等待数据稳定
if (!empty) begin
expected = golden_queue.pop_front();
if (dout !== expected) begin
$display("[%t] ERROR! dout=0x%02h, expected=0x%02h", $time, dout, expected);
error_cnt++;
end else begin
$display("[%t] READ 0x%02h (PASS) full=%b empty=%b", $time, dout, full, empty);
end
end else begin
$display("[%t] READ attempt but FIFO EMPTY!", $time);
end
@(posedge clk);
rd_en = 0;
endtask

// 主测试序列
initial begin
$display("===== FIFO Test Start =====");

fifo_reset();

// 测试1:写满再读空
$display("\\n=== Test 1: Fill then Empty ===");
repeat(DEPTH) begin
fifo_write($random);
end

repeat(DEPTH) begin
fifo_read();
end

// 测试2:随机写读交替
$display("\\n=== Test 2: Random Write/Read Mix ===");
repeat(100) begin
@(posedge clk);
if ($random % 3 == 0) // ~33% 概率写
if (!full) fifo_write($random);
else if ($random % 3 == 1) // ~33% 概率读
if (!empty) fifo_read();
// 其余时间空闲
end

// 测试3:写满后强行写 + 读空后强行读
$display("\\n=== Test 3: Over-write & Over-read protection ===");
repeat(DEPTH) fifo_write($random); // 写满
fifo_write(8'hAA); // 应该被忽略
fifo_write(8'hBB);

repeat(DEPTH+5) fifo_read(); // 多读几次

// 结束
#100;
if (error_cnt == 0)
$display("\\n===== ALL TESTS PASS! (no mismatch) =====");
else
$display("\\n===== TEST FAILED! %0d errors =====", error_cnt);

$finish;
end

// 波形与监控
initial begin
$dumpfile("fifo_tb.vcd");
$dumpvars(0, tb_sync_fifo);

$monitor("[%t] wr_en=%b rd_en=%b full=%b empty=%b din=0x%02h dout=0x%02h",
$time, wr_en, rd_en, full, empty, din, dout);
end

endmodule

常用测试点总结(建议都测到)

测试场景目的是否包含在上面示例
复位后 empty=1 检查初始状态
写满后 full=1 容量验证
满时继续写 写保护(不覆盖旧数据)
空时继续读 读保护(不输出垃圾数据)
同时读写 中间状态行为 部分(随机测试中)
随机写读顺序 模拟真实流量
数据完整性(自检查) 确保进出的数据一致
边界:深度=1、深度=2 特殊情况验证 可自行扩展

小建议(2026 年日常使用):

  • 日常快速验证 → 用上面这种 task + 黄金模型队列 的方式
  • 中大型项目 → 建议迁移到 SystemVerilog interface + class 或直接上 UVM
  • 追求最高覆盖率 → 再加 corner case:复位期间写读、异步时钟域(如果做异步 FIFO)等

UVM (Universal Verification Methodology) 详解
(2026 年最新视角:IEEE 1800.2 标准,主流 ASIC/FPGA 验证框架)

UVM 是目前行业标准的功能验证方法学(Methodology),基于 SystemVerilog 语言构建。
它不是一种工具或语言,而是一套类库 + 最佳实践 + 架构规范,目的是让验证环境可重用、可扩展、可移植,极大降低大型 SoC/ASIC 验证成本。

1. 为什么需要 UVM?(对比纯 SystemVerilog Testbench)

维度纯 SystemVerilog TestbenchUVM Testbench优势对比(2026 年主流看法)
重用性 基本靠手动 copy-paste 或 package 天生模块化 + factory + config_db UVM 完胜
可扩展性 规模大后维护地狱 从 block → subsystem → SoC 轻松扩展 UVM 完胜
标准化 各团队风格千差万别 IEEE 1800.2 标准,所有大厂(Synopsys/Cadence/Mentor)统一支持 UVM 完胜
招聘难度 招到会 SV 就行 招到会 UVM(几乎所有大厂职位必备) 纯 SV 更难招人
学习曲线 较平缓 陡峭(尤其是前 3-6 个月) 纯 SV 胜(短期)
验证效率 随机/覆盖率靠手动实现 内置 sequence、scoreboard、coverage、phasing 等 UVM 长期胜
当前市场占有率 小项目/FPGA 原型/个人项目 大型 ASIC/SoC 验证 90%+(2025-2026 数据) UVM 统治地位

一句话总结:
小项目/学习阶段 → 纯 SV + task/assert 够用
中大型项目/职业发展 → 必须掌握 UVM(尤其是带 UVM 的 VIP)

2. UVM 核心组件与典型架构(2026 年最常见结构)

经典 UVM Testbench 架构图(简化版):

+——————-+
| uvm_test | ← 测试用例(选择 sequence、配置)
+——————-+
|
| build_phase / connect_phase
v
+——————-+
| uvm_env | ← 顶层环境(可包含多个 sub-env)
+——————-+
/ | \\
+————+ +————+ +————+
| uvm_agent | | uvm_agent | | scoreboard |
| (主动/被动)| | (被动) | | / predictor|
+————+ +————+ +————+
| |
+———+ +———–+
| driver |<—>| sequencer | ← 产生随机/定向 sequence
+———+ +———–+
| ^
+———+ |
| monitor |———–> ← 收集 DUT 输出 → analysis port
+———+ TLM 接口
|
+—————–+
| DUT (RTL) |
+—————–+

核心组件快速说明(按重要性排序):

组件全称 / 作用是否 uvm_component?是否可重用?备注(2026 年最佳实践)
uvm_sequence_item 事务(transaction)载体,如 read/write packet 否(uvm_object) 用 rand 字段 + constraint
uvm_sequence 产生 sequence_item 的“剧本” 否(uvm_object) virtual sequence 用于多 agent 协调
uvm_sequencer 调度 sequence、管理仲裁 通常不改,默认 uvm_sequencer
uvm_driver 把 sequence_item 转为 DUT 接口信号 通过 TLM port 从 sequencer 取 item
uvm_monitor 观察 DUT 接口,收集/转换 transaction 发到 analysis_port → scoreboard
uvm_agent 打包 driver + sequencer + monitor(主动/被动) is_active = UVM_ACTIVE / PASSIVE
uvm_scoreboard 比较预期 vs 实际(黄金模型) 视情况 常用 uvm_analysis_imp 接收 monitor 数据
uvm_env 包含多个 agent + scoreboard + sub-env 层次化重用关键
uvm_test 顶层测试用例,选择 env 配置 + 跑哪些 sequence 部分 继承 base_test + factory override
uvm_reg 寄存器模型(RAL) 现代项目几乎必备

3. UVM 关键机制(必须掌握)

  • Factory:动态创建/替换组件(uvm_component_utils / set_type_override)
  • Phasing:分阶段执行(build → connect → end_of_elaboration → run → extract → check → report 等)
  • Config_db:配置传递(uvm_config_db#(virtual if)::set(…))
  • TLM(Transaction Level Modeling):组件间通信(port / export / imp / analysis_port)
  • Sequences & Virtual Sequences:分层产生激励
  • Register Layer (RAL):寄存器抽象,自动生成前后门访问

4. 2026 年 UVM 现状与趋势

  • 最新标准:IEEE 1800.2-2020(UVM 1.2 基础上小幅更新,2020.3.1 是当前主流库)
  • VIP 生态:几乎所有主流协议(AXI/PCIe/CXL/USB/DDR/LPDDR/UCIe 等)都有成熟商用 VIP(Synopsys/Cadence/Mentor)
  • 新兴趋势:
    • UVM + Emulation/Prototyping 友好架构(emulation-friendly)
    • Portable Stimulus Standard (PSS) + UVM 混合
    • AI/ML 辅助生成 sequence / coverage
    • Python + cocotb 作为补充(但 UVM 仍主导 ASIC)
  • 未来:短期(5-8 年)内 UVM 仍是大厂主流,长期看可能与 PSS/更高抽象融合,但不会很快被取代

5. 学习路径建议(2026 年版,高效路线)

  • 先熟练 SystemVerilog(OOP、interface、constraint、coverage、assert)
  • 掌握 基本 UVM(agent-env-test,sequence-driver-monitor)
  • 学会 factory + config_db + phasing
  • 做 简单项目:如 APB/UART/SPI 的 UVM 环境
  • 进阶 VIP 开发 + 寄存器模型 + virtual sequence
  • 学习 UVM 最佳实践(Easier UVM Coding Guidelines)
  • 实际项目中 debug 大量 UVM 报错(phase objection、config 没 set 等)
  • 推荐资源(2026 年仍活跃):

    • Verification Academy(Mentor/Siemens EDA)免费教程(最权威)
    • ChipVerify / The Art of Verification 网站
    • YouTube:Clifford Cummings、Verification Academy 频道
    • 书籍:《UVM Cookbook》(Siemens 版,2018 更新)
    • Accellera 官网下载最新 UVM 库

    一句话总结(从业者视角):
    “不会 UVM,就别说自己是 ASIC 验证工程师” —— 2026 年大厂真实招聘要求。

    异步 FIFO + FWFT 风格 + 简单 UVM Testbench 示例说明

    以下是三种常见但有代表性的实现方式对比,供你参考选择适合自己项目的风格:

    类型时钟域输出延迟典型应用场景复杂度推荐场景
    同步 FIFO 单时钟 读延迟 1~2 clk 内部模块间缓冲 ★☆☆ 简单项目、学习
    异步 FIFO (标准) 读/写不同时钟 读延迟 2~3 clk 跨时钟域数据传输 ★★☆ 大多数跨时钟域需求
    异步 FIFO + FWFT 读/写不同时钟 读延迟 0 clk (first word fall through) 对延迟敏感的应用、数据流式处理 ★★★ 高速接口、视频流、DMA 等
    UVM Testbench (任意 FIFO) 工业级验证、回归测试、覆盖率收集 ★★★★ 中大型项目、团队开发

    1. 异步 FIFO + FWFT 风格 参考代码结构(最常用风格)

    // async_fifo_fwft.v 异步 FIFO + FWFT 风格(读延迟0)
    module async_fifo_fwft #(
    parameter DATA_WIDTH = 8,
    parameter ADDR_WIDTH = 4, // 深度 = 2^ADDR_WIDTH
    parameter ALMOST_FULL_OFFSET = 2,
    parameter ALMOST_EMPTY_OFFSET = 2
    ) (
    // 写域
    input wire wr_clk,
    input wire wr_rst_n,
    input wire wr_en,
    input wire [DATA_WIDTH-1:0] din,
    output reg full,
    output reg almost_full,

    // 读域
    input wire rd_clk,
    input wire rd_rst_n,
    input wire rd_en,
    output reg [DATA_WIDTH-1:0] dout,
    output reg empty,
    output reg almost_empty,

    // 状态指示(可选)
    output reg wr_ack, // 写成功应答
    output reg rd_valid // 读数据有效(FWFT关键)
    );

    // 内部实现要点(灰码指针 + 双寄存器同步 + FWFT预读机制)
    // 1. 写指针(gray code) wr_ptr_bin → wr_ptr_gray
    // 2. 读指针(gray code) rd_ptr_bin → rd_ptr_gray
    // 3. 跨时钟域同步:读域看到写指针的同步版本
    // 4. FWFT 核心:当 empty==0 时,自动预读第一个字到 dout,并置 rd_valid=1
    // 5. 读使能 rd_en 只是消费当前 dout,不触发新的读取(直到消费完才预读下一个)

    // …(完整实现通常 200~300 行,包含灰码转换、同步器、指针比较逻辑)

    // 典型接口行为总结:
    // • 写:wr_en && !full → 写入成功,wr_ack=1
    // • 读:!empty → dout 随时有效,rd_valid=1
    // • 当 rd_en=1 且 rd_valid=1 → 消费当前字,自动预读下一个(若还有)
    // • empty 仅在预读缓冲也为空时才置位

    endmodule

    FWFT vs 标准异步 FIFO 最直观区别(行为对比表):

    时间点 标准异步FIFO FWFT异步FIFO
    ————- ——————- ——————-
    刚写第一个数据 empty=0, 但 dout 无效 empty=0, dout=有效数据, rd_valid=1
    第一次 rd_en 读出第一个数据 消费第一个数据,同时 dout 自动变为第二个(若有)
    延迟 读出延迟 1~2 个 rd_clk **读出延迟 0**(first word 直接在 dout 上)
    控制复杂度 较简单 稍复杂(需要预读状态机)

    2. 异步 FIFO 的简单传统 Testbench(非 UVM)

    // 典型异步 FIFO 验证要点(非 UVM 版本)
    initial begin
    // 写域 & 读域异步复位
    fork
    begin wr_rst_n=0; #100; wr_rst_n=1; end
    begin rd_rst_n=0; #120; rd_rst_n=1; end
    join

    // 测试1:写满再读空(不同频率)
    fork
    begin repeat(DEPTH+5) @(posedge wr_clk) if(!full) wr_data($random); end
    begin repeat(DEPTH+10) @(posedge rd_clk) if(!empty) rd_data_check(); end
    join

    // 测试2:随机写读(不同速率)
    fork
    forever begin @(posedge wr_clk); if($random%3==0 && !full) wr_data($random); end
    forever begin @(posedge rd_clk); if($random%4==1 && !empty) rd_data_check(); end
    join_none

    // 测试3:几乎满/几乎空边界
    // 测试4:复位期间写/读行为
    // 测试5:写读同时进行(back pressure)
    end

    3. UVM 风格的 FIFO Testbench 框架(高度抽象版)

    // ——————- UVM 环境典型结构 ——————-

    class fifo_item extends uvm_sequence_item;
    rand bit [7:0] data;
    rand bit wr_en;
    rand bit rd_en;
    `uvm_object_utils_begin(fifo_item)
    `uvm_field_int(data, UVM_ALL_ON)
    `uvm_field_int(wr_en, UVM_ALL_ON)
    `uvm_field_int(rd_en, UVM_ALL_ON)
    `uvm_object_utils_end
    endclass

    class fifo_sequence extends uvm_sequence #(fifo_item);
    `uvm_object_utils(fifo_sequence)
    // random burst, back-to-back, corner cases…
    endclass

    class fifo_driver extends uvm_driver #(fifo_item);
    virtual async_fifo_if vif;
    `uvm_component_utils(fifo_driver)
    // drive wr_clk domain & rd_clk domain separately
    endclass

    class fifo_monitor extends uvm_monitor;
    // two monitors: one for write side, one for read side
    uvm_analysis_port #(fifo_item) ap_write;
    uvm_analysis_port #(fifo_item) ap_read;
    endclass

    class fifo_scoreboard extends uvm_scoreboard;
    uvm_tlm_analysis_fifo #(fifo_item) fifo_model;
    // 简单模型:写进队列,读出来对比
    // 或者使用 golden reference model
    endclass

    class fifo_agent extends uvm_agent;
    // contains driver, sequencer, monitor (active/passive)
    endclass

    class fifo_env extends uvm_env;
    fifo_agent wr_agent;
    fifo_agent rd_agent;
    fifo_scoreboard scb;
    // connect analysis ports to scoreboard
    endclass

    class fifo_test extends uvm_test;
    fifo_env env;
    `uvm_component_utils(fifo_test)

    function new(…);
    virtual function void build_phase(uvm_phase phase);
    // set config_db for virtual interface
    // override sequence if needed
    endfunction

    task run_phase(…);
    fifo_sequence seq = fifo_sequence::type_id::create("seq");
    seq.start(env.wr_agent.sequencer); // 或用 virtual sequencer
    endtask
    endclass

    快速选择建议(2025~2026 实际项目)

    需求 → 推荐实现方式
    ———————————————–
    简单学习/小项目 → 同步 FIFO + 基本 TB
    普通跨时钟域传输 → 标准异步 FIFO + 中级 TB(task+黄金模型)
    对读延迟极度敏感(视频、DMA) → **异步 FWFT FIFO** + 仔细验证预读逻辑
    团队项目、回归测试、覆盖率 → 任意 FIFO + **UVM 环境**(强烈推荐)
    要同时验证时序收敛、CDC → UVM + 形式验证 + CDC 专用工具(SpyGlass/VC LP 等)

    异步 FIFO 的完整 Verilog 实现

    异步 FIFO(Asynchronous FIFO)是一种用于跨时钟域数据传输的缓冲器,读写操作使用不同的时钟(wr_clk 和 rd_clk)。它通过 Gray 码指针同步来避免亚稳态问题,确保数据可靠传输。典型应用包括多时钟域 SoC、接口桥接(如 AXI 到 APB)等。

    关键设计原理(2026 年主流实践)
    • Gray 码指针:读/写指针使用二进制转 Gray 码,跨域同步只需 2-3 级翻转器。
    • 同步器:双翻转器(或多级)同步指针,减少亚稳态风险。
    • 满/空判断:基于同步后的指针比较(空:读写指针相等;满:写指针追上读指针一圈)。
    • 内存:双端口 RAM(写域写、读域读)。
    • 参数化:深度(DEPTH,通常 2^n)、数据宽度(DATA_WIDTH)。
    • 注意事项:
      • 避免同时读写导致的竞争(通过指针设计)。
      • 测试中需模拟不同时钟频率、随机激励、CDC(Clock Domain Crossing)验证。
      • 工具:用 Vivado/Quartus 检查时序,SpyGlass 检查 CDC。

    以下是典型异步 FIFO 的架构图(参考标准设计):

    另一个变体块图(包括指针同步细节):

    完整 Verilog 代码(经典风格,基于 Gray 码 + 双翻转同步器)

    这是一个参数化的异步 FIFO 实现,包括顶层模块和必要子模块。深度为 16(可配置),数据宽度 8 位。代码可综合,用于 ASIC/FPGA。

    // 子模块1: 双翻转同步器(用于跨域同步 Gray 码指针)
    module sync_2ff #(
    parameter PTR_WIDTH = 5 // 指针宽度 = log2(DEPTH) + 1
    ) (
    input wire clk,
    input wire rst_n,
    input wire [PTR_WIDTH-1:0] in_ptr, // 输入指针(Gray 码)
    output reg [PTR_WIDTH-1:0] out_ptr // 输出同步指针
    );
    reg [PTR_WIDTH-1:0] sync1;

    always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
    sync1 <= 0;
    out_ptr <= 0;
    end else begin
    sync1 <= in_ptr;
    out_ptr <= sync1;
    end
    end
    endmodule

    // 子模块2: 写指针处理器(生成写指针、满标志)
    module wptr_handler #(
    parameter PTR_WIDTH = 5
    ) (
    input wire wr_clk,
    input wire wr_rst_n,
    input wire wr_en,
    input wire [PTR_WIDTH-1:0] rq2_wptr, // 同步后的读指针
    output reg [PTR_WIDTH-1:0] wptr, // 二进制写指针
    output reg [PTR_WIDTH-1:0] wptr_gray, // Gray 码写指针
    output reg full
    );
    reg [PTR_WIDTH-1:0] wptr_next;

    // 二进制指针递增
    always @(posedge wr_clk or negedge wr_rst_n) begin
    if (!wr_rst_n) begin
    wptr <= 0;
    wptr_gray <= 0;
    end else if (wr_en && !full) begin
    wptr <= wptr + 1;
    wptr_gray <= (wptr + 1) ^ ((wptr + 1) >> 1); // Bin to Gray
    end
    end

    // 满判断:写指针追上读指针一圈(Gray 码比较)
    wire [PTR_WIDTH-1:0] wptr_next_gray = (wptr + 1) ^ ((wptr + 1) >> 1);
    always @(*) begin
    full = (wptr_next_gray == {~rq2_wptr[PTR_WIDTH-1:PTR_WIDTH-2], rq2_wptr[PTR_WIDTH-3:0]});
    end
    endmodule

    // 子模块3: 读指针处理器(生成读指针、空标志)
    module rptr_handler #(
    parameter PTR_WIDTH = 5
    ) (
    input wire rd_clk,
    input wire rd_rst_n,
    input wire rd_en,
    input wire [PTR_WIDTH-1:0] wq2_rptr, // 同步后的写指针
    output reg [PTR_WIDTH-1:0] rptr, // 二进制读指针
    output reg [PTR_WIDTH-1:0] rptr_gray, // Gray 码读指针
    output reg empty
    );
    reg [PTR_WIDTH-1:0] rptr_next;

    // 二进制指针递增
    always @(posedge rd_clk or negedge rd_rst_n) begin
    if (!rd_rst_n) begin
    rptr <= 0;
    rptr_gray <= 0;
    end else if (rd_en && !empty) begin
    rptr <= rptr + 1;
    rptr_gray <= (rptr + 1) ^ ((rptr + 1) >> 1); // Bin to Gray
    end
    end

    // 空判断:读写指针相等(Gray 码比较)
    wire [PTR_WIDTH-1:0] rptr_next_gray = (rptr + 1) ^ ((rptr + 1) >> 1);
    always @(*) begin
    empty = (rptr_next_gray == wq2_rptr);
    end
    endmodule

    // 子模块4: FIFO 内存(双端口 RAM)
    module fifo_mem #(
    parameter DATA_WIDTH = 8,
    parameter DEPTH = 16,
    parameter ADDR_WIDTH = 4 // log2(DEPTH)
    ) (
    input wire wr_clk,
    input wire wr_en,
    input wire [ADDR_WIDTH-1:0] waddr, // 写地址(二进制指针低位)
    input wire [DATA_WIDTH-1:0] din,
    input wire rd_clk,
    input wire rd_en,
    input wire [ADDR_WIDTH-1:0] raddr, // 读地址(二进制指针低位)
    output reg [DATA_WIDTH-1:0] dout
    );
    reg [DATA_WIDTH-1:0] mem [0:DEPTH-1];

    // 写操作
    always @(posedge wr_clk) begin
    if (wr_en)
    mem[waddr] <= din;
    end

    // 读操作
    always @(posedge rd_clk) begin
    if (rd_en)
    dout <= mem[raddr];
    end
    endmodule

    // 顶层模块: 异步 FIFO
    module async_fifo #(
    parameter DATA_WIDTH = 8,
    parameter DEPTH = 16 // 必须是 2 的幂
    ) (
    // 写域
    input wire wr_clk,
    input wire wr_rst_n,
    input wire wr_en,
    input wire [DATA_WIDTH-1:0] din,
    output wire full,

    // 读域
    input wire rd_clk,
    input wire rd_rst_n,
    input wire rd_en,
    output wire [DATA_WIDTH-1:0] dout,
    output wire empty
    );

    localparam ADDR_WIDTH = $clog2(DEPTH);
    localparam PTR_WIDTH = ADDR_WIDTH + 1; // 多一位用于满/空区分

    // 内部信号
    wire [PTR_WIDTH-1:0] wptr_gray, rptr_gray;
    wire [PTR_WIDTH-1:0] wq2_rptr, rq2_wptr; // 同步指针
    wire [ADDR_WIDTH-1:0] waddr = wptr[PTR_WIDTH-2:0]; // 地址 = 指针低位
    wire [ADDR_WIDTH-1:0] raddr = rptr[PTR_WIDTH-2:0];

    wire [PTR_WIDTH-1:0] wptr, rptr;

    // 实例化写指针处理器
    wptr_handler #(PTR_WIDTH) u_wptr (
    .wr_clk (wr_clk),
    .wr_rst_n (wr_rst_n),
    .wr_en (wr_en && !full),
    .rq2_wptr (rq2_wptr),
    .wptr (wptr),
    .wptr_gray(wptr_gray),
    .full (full)
    );

    // 实例化读指针处理器
    rptr_handler #(PTR_WIDTH) u_rptr (
    .rd_clk (rd_clk),
    .rd_rst_n (rd_rst_n),
    .rd_en (rd_en && !empty),
    .wq2_rptr (wq2_rptr),
    .rptr (rptr),
    .rptr_gray(rptr_gray),
    .empty (empty)
    );

    // 写指针同步到读域
    sync_2ff #(PTR_WIDTH) u_sync_w2r (
    .clk (rd_clk),
    .rst_n (rd_rst_n),
    .in_ptr (wptr_gray),
    .out_ptr(wq2_rptr)
    );

    // 读指针同步到写域
    sync_2ff #(PTR_WIDTH) u_sync_r2w (
    .clk (wr_clk),
    .rst_n (wr_rst_n),
    .in_ptr (rptr_gray),
    .out_ptr(rq2_wptr)
    );

    // 内存实例化
    fifo_mem #(DATA_WIDTH, DEPTH, ADDR_WIDTH) u_mem (
    .wr_clk (wr_clk),
    .wr_en (wr_en && !full),
    .waddr (waddr),
    .din (din),
    .rd_clk (rd_clk),
    .rd_en (rd_en && !empty),
    .raddr (raddr),
    .dout (dout)
    );

    endmodule

    简单 Testbench 示例(用于验证)

    以下是一个基本 testbench,使用随机激励测试满/空、跨域传输。运行时用 ModelSim/Vivado Simulator。

    module tb_async_fifo;

    parameter DATA_WIDTH = 8;
    parameter DEPTH = 16;

    reg wr_clk = 0;
    reg rd_clk = 0;
    reg wr_rst_n = 0;
    reg rd_rst_n = 0;
    reg wr_en = 0;
    reg [DATA_WIDTH-1:0] din = 0;
    reg rd_en = 0;
    wire [DATA_WIDTH-1:0] dout;
    wire full;
    wire empty;

    // 实例化 DUT
    async_fifo #(DATA_WIDTH, DEPTH) dut (
    .wr_clk (wr_clk),
    .wr_rst_n (wr_rst_n),
    .wr_en (wr_en),
    .din (din),
    .full (full),
    .rd_clk (rd_clk),
    .rd_rst_n (rd_rst_n),
    .rd_en (rd_en),
    .dout (dout),
    .empty (empty)
    );

    // 时钟生成(不同频率:写 100MHz,读 80MHz)
    always #5 wr_clk = ~wr_clk; // 10ns 周期
    always #6.25 rd_clk = ~rd_clk; // 12.5ns 周期

    // 黄金模型队列(用于自检查)
    reg [DATA_WIDTH-1:0] golden_queue [$];
    integer error_cnt = 0;

    initial begin
    // 异步复位
    wr_rst_n = 0; rd_rst_n = 0;
    #20;
    wr_rst_n = 1; #5; rd_rst_n = 1;

    // 测试1: 写满
    repeat(DEPTH) begin
    @(posedge wr_clk);
    if (!full) begin
    din = $random;
    wr_en = 1;
    golden_queue.push_back(din);
    @(posedge wr_clk);
    wr_en = 0;
    end
    end

    // 测试2: 读空 + 检查数据
    repeat(DEPTH) begin
    @(posedge rd_clk);
    if (!empty) begin
    rd_en = 1;
    @(negedge rd_clk); // 等待数据稳定
    if (dout != golden_queue.pop_front()) error_cnt++;
    @(posedge rd_clk);
    rd_en = 0;
    end
    end

    // 测试3: 随机写读
    repeat(100) begin
    @(posedge wr_clk);
    if ($urandom % 2 && !full) begin // 随机写
    din = $random;
    wr_en = 1;
    golden_queue.push_back(din);
    @(posedge wr_clk);
    wr_en = 0;
    end
    @(posedge rd_clk);
    if ($urandom % 3 && !empty) begin // 随机读
    rd_en = 1;
    @(negedge rd_clk);
    if (dout != golden_queue.pop_front()) error_cnt++;
    @(posedge rd_clk);
    rd_en = 0;
    end
    end

    #100;
    if (error_cnt == 0) $display("All tests PASS!");
    else $display("Tests FAILED with %d errors!", error_cnt);
    $finish;
    end

    // 波形转储
    initial begin
    $dumpfile("async_fifo_tb.vcd");
    $dumpvars(0, tb_async_fifo);
    end

    endmodule

    参数与性能总结表
    参数默认值描述注意事项
    DATA_WIDTH 8 数据位宽 任意正整数
    DEPTH 16 FIFO 深度(2 的幂) 影响指针宽度,建议 2^n
    PTR_WIDTH 5 指针宽度(log2(DEPTH)+1) 用于满/空区分
    同步级数 2 双翻转器 高频时钟可增至 3-4 级
    读延迟 1 clk 标准风格 若需 FWFT,可修改预读逻辑

    此实现基于经典论文(如 Peter Alfke 的 Xilinx TechXclusive)和开源代码(如 ASIC World、ZipCPU)。

    赞(0)
    未经允许不得转载:171主机测评 » Verilog 模块结构详解
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址