Transaction / Sequence Item(最基础的事务)
apb_agent├── sequencer ← apb_sequence (产生 transaction)├── driver ← 从 sequencer 取 tx → 驱动 vif 接口└── monitor → 收集 vif 信号 → 产生 transaction → 发送到 analysis_portapb_env└── scoreboard ← 通过 analysis_port 接收 t
UVM 具体代码示例
以下提供几个最常用、最实用的 UVM 组件示例代码,全部基于 IEEE 1800.2 UVM 1.2 标准(2026 年主流库)。
这些示例以一个简单的 APB (Advanced Peripheral Bus) Slave 为背景(非常常见的入门协议),方便你直接拿来修改、扩展。
1. Transaction / Sequence Item(最基础的事务)
// apb_transaction.sv
class apb_transaction extends uvm_sequence_item;
rand bit pwrite; // 0=read, 1=write
rand bit [31:0] paddr;
rand bit [31:0] pwdata;
rand bit [3:0] pstrb; // byte strobe
bit [31:0] prdata;
bit pslverr;
// 约束示例
constraint addr_c { paddr inside {[0:32'hFFFF]}; } // 限制地址范围
constraint strb_c { pstrb != 4'b0000; } // 至少写一个 byte
`uvm_object_utils_begin(apb_transaction)
`uvm_field_int(pwrite, UVM_ALL_ON)
`uvm_field_int(paddr, UVM_ALL_ON)
`uvm_field_int(pwdata, UVM_ALL_ON)
`uvm_field_int(pstrb, UVM_ALL_ON)
`uvm_field_int(prdata, UVM_ALL_ON | UVM_NOCOPY)
`uvm_field_int(pslverr, UVM_ALL_ON | UVM_NOCOPY)
`uvm_object_utils_end
function new(string name = "apb_transaction");
super.new(name);
endfunction
// 可选:打印函数,便于 debug
virtual function void do_print(uvm_printer printer);
super.do_print(printer);
printer.print_field("pwrite", pwrite, 1, UVM_BIN);
printer.print_field("paddr ", paddr, 32, UVM_HEX);
endfunction
endclass
2. Sequence(产生随机/定向事务)
// apb_sequence.sv
class apb_base_sequence extends uvm_sequence #(apb_transaction);
`uvm_object_utils(apb_base_sequence)
function new(string name = "apb_base_sequence");
super.new(name);
endfunction
endclass
// 随机写读序列
class apb_rand_wr_rd_sequence extends apb_base_sequence;
`uvm_object_utils(apb_rand_wr_rd_sequence)
rand int num_trans = 50;
constraint num_c { num_trans inside {[20:100]}; }
task body();
apb_transaction tx;
repeat(num_trans) begin
tx = apb_transaction::type_id::create("tx");
start_item(tx);
assert(tx.randomize() with {
pwrite dist {0:/40, 1:/60}; // 60% 写,40% 读
});
finish_item(tx);
`uvm_info("SEQ", $sformatf("Sent: %s addr=0x%0h data=0x%0h %s",
tx.pwrite?"WR":"RD", tx.paddr, tx.pwrite?tx.pwdata:tx.prdata,
tx.pslverr?"ERR":"OK"), UVM_MEDIUM)
end
endtask
endclass
3. Driver(把 transaction 驱动到 DUT 接口)
// apb_driver.sv
class apb_driver extends uvm_driver #(apb_transaction);
`uvm_component_utils(apb_driver)
virtual apb_if vif; // 虚拟接口
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if (!uvm_config_db#(virtual apb_if)::get(this, "", "vif", vif))
`uvm_fatal("DRV", "No virtual interface set!")
endfunction
task run_phase(uvm_phase phase);
forever begin
seq_item_port.get_next_item(req);
drive_transaction(req);
seq_item_port.item_done();
end
endtask
task drive_transaction(apb_transaction tx);
@(posedge vif.pclk);
vif.psel <= 1'b1;
vif.penable <= 1'b0;
vif.paddr <= tx.paddr;
vif.pwrite <= tx.pwrite;
vif.pwdata <= tx.pwdata;
vif.pstrb <= tx.pstrb;
@(posedge vif.pclk);
vif.penable <= 1'b1;
@(posedge vif.pclk iff vif.pready);
if (tx.pwrite == 0) begin
tx.prdata = vif.prdata;
tx.pslverr = vif.pslverr;
end
vif.psel <= 1'b0;
vif.penable <= 1'b0;
@(posedge vif.pclk); // 等待一个周期,确保接口干净
endtask
endclass
4. Monitor(被动观察 DUT 接口,收集 transaction)
// apb_monitor.sv
class apb_monitor extends uvm_monitor;
`uvm_component_utils(apb_monitor)
virtual apb_if vif;
uvm_analysis_port #(apb_transaction) ap; // 广播端口
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
ap = new("ap", this);
if (!uvm_config_db#(virtual apb_if)::get(this, "", "vif", vif))
`uvm_fatal("MON", "No virtual interface!")
endfunction
task run_phase(uvm_phase phase);
apb_transaction tx;
forever begin
@(posedge vif.pclk);
if (vif.psel && vif.penable && vif.pready) begin
tx = apb_transaction::type_id::create("tx");
tx.pwrite = vif.pwrite;
tx.paddr = vif.paddr;
tx.pstrb = vif.pstrb;
if (tx.pwrite)
tx.pwdata = vif.pwdata;
else begin
@(negedge vif.pclk); // 等待数据稳定
tx.prdata = vif.prdata;
tx.pslverr = vif.pslverr;
end
ap.write(tx);
`uvm_info("MON", $sformatf("Captured: %s @0x%0h data=0x%0h",
tx.pwrite?"WR":"RD", tx.paddr, tx.pwrite?tx.pwdata:tx.prdata), UVM_HIGH)
end
end
endtask
endclass
5. Scoreboard(简单比较写读数据)
// apb_scoreboard.sv
class apb_scoreboard extends uvm_scoreboard;
`uvm_component_utils(apb_scoreboard)
uvm_analysis_imp #(apb_transaction, apb_scoreboard) ap_export;
// 简单内存模型:记录所有写操作
bit [31:0] mem_model[bit[31:0]];
function new(string name, uvm_component parent);
super.new(name, parent);
ap_export = new("ap_export", this);
endfunction
function void write(apb_transaction tx);
if (tx.pwrite) begin
// 写操作:更新模型
foreach (tx.pstrb[i]) begin
if (tx.pstrb[i])
mem_model[tx.paddr + i] = tx.pwdata[8*i +: 8];
end
`uvm_info("SCB", $sformatf("Write model: addr=0x%0h data=0x%0h", tx.paddr, tx.pwdata), UVM_MEDIUM)
end
else begin
// 读操作:比较
bit [31:0] expected = 0;
foreach (tx.pstrb[i]) begin
if (tx.pstrb[i])
expected[8*i +: 8] = mem_model.exists(tx.paddr + i) ? mem_model[tx.paddr + i] : 8'h00;
end
if (tx.prdata !== expected) begin
`uvm_error("SCB", $sformatf("Read mismatch! addr=0x%0h exp=0x%0h act=0x%0h",
tx.paddr, expected, tx.prdata))
end else begin
`uvm_info("SCB", $sformatf("Read PASS: addr=0x%0h data=0x%0h", tx.paddr, tx.prdata), UVM_LOW)
end
end
endfunction
endclass
快速总结:这些组件如何组合?
apb_agent
├── sequencer ← apb_sequence (产生 transaction)
├── driver ← 从 sequencer 取 tx → 驱动 vif 接口
└── monitor → 收集 vif 信号 → 产生 transaction → 发送到 analysis_port
apb_env
├── apb_agent
└── scoreboard ← 通过 analysis_port 接收 transaction,进行比较
apb_test
└── apb_env
+ run sequence: apb_rand_wr_rd_sequence.start(sequencer)
下一步推荐学习顺序(2026 年高效路径):
- 先把上面 5 个组件全部跑通(最重要)
- 加上 coverage group(功能覆盖 + 交叉覆盖)
- 实现 virtual sequence(多 agent 协调)
- 加入寄存器模型(uvm_reg + adapter)
- 学习 factory override、config_db 高级用法
- 处理 phase objection、objection 调试
Verilog 信号类型详解
Verilog 是一种硬件描述语言(HDL),其信号类型用于定义电路中的数据载体,如连线、寄存器等。信号类型决定了信号的存储方式、驱动能力、仿真行为和综合结果。Verilog 标准(IEEE 1364-2005)定义了基本类型,SystemVerilog(IEEE 1800)扩展了更多。信号类型主要分为网类型(Net Types)和变量类型(Variable Types)。
- 网类型:如 wire,用于表示物理连线,支持多驱动(多源驱动),默认用于端口。
- 变量类型:如 reg,用于存储值,只能单驱动,常用于 always 块。
- 数据宽度:信号可指定位宽,如
[7:0](8 位向量),默认 1 位。 - 注意事项(2026 年最佳实践):
- 现代设计推荐 SystemVerilog 的
logic类型(统一 wire/reg)。 - 综合时,工具(如 Vivado)会将信号映射到硬件(线/触发器)。
- 非综合信号(如 real)仅用于仿真/测试。
- 常见错误:reg 用于组合逻辑会导致锁存器;wire 用于时序会报错。
- 现代设计推荐 SystemVerilog 的
下面详解常见信号类型,使用表格总结,然后提供示例代码和完整例子。
1. 常见信号类型总结表
| 信号类型 | 分类 | 定义与用途 | 默认值/宽度 | 示例声明 | 注意事项(综合/仿真) |
|---|---|---|---|---|---|
| wire | 网类型 | 表示物理连线,支持多驱动(如多个 assign)。用于组合逻辑、模块间连接。 | 未定义/1位 | wire [7:0] data; | 可综合;未赋值默认为 Z(高阻);多驱动解析冲突。 |
| reg | 变量类型 | 表示寄存器或变量,用于存储值。常在 always 块中赋值,用于时序/行为描述。 | 未定义/1位 | reg signed [31:0] count; | 可综合为触发器;仅单驱动;默认用于 output reg。 |
| logic | 变量类型(SystemVerilog) | 统一类型,可替换 wire/reg。支持四态(0/1/X/Z)。用于 RTL 设计。 | 未定义/1位 | logic [3:0] state; | 推荐使用;综合时工具推断为 wire 或 reg。 |
| integer | 变量类型 | 32 位有符号整数,用于循环/计算。常用于测试台(非综合)。 | 0/32位 | integer i; | 非综合;仿真中用于 for 循环等。 |
| real | 变量类型 | 浮点数,用于模拟/建模(如延时)。非数字电路。 | 0.0/64位 | real delay; | 非综合;仅仿真用。 |
| wand/wor/tri | 网类型(有线逻辑) | wand: 有线与;wor: 有线或;tri: 三态。用于总线建模。 | 未定义/1位 | wand bus; | 可综合为门电路;少用,现代设计用 wire + 驱动控制。 |
| supply0/supply1 | 网类型 | 固定为 0/1,用于电源/地。 | -/1位 | supply0 gnd; | 可综合为常量;用于顶层连接。 |
- 其他扩展(SystemVerilog):
- bit:二态(0/1),用于节省内存(无 X/Z)。
- enum:枚举类型,如
enum {IDLE, RUN} state;(提高可读性)。 - struct/union:复合类型,用于打包数据。
- 四态 vs 二态:Verilog 默认四态(0/1/X/Z),X=未知,Z=高阻;二态(bit)更快但忽略不确定性。
- 数组:支持多维,如
reg [7:0] mem[0:255];(256x8 RAM)。
2. 示例代码:基本信号类型使用
以下是简单模块,展示不同信号类型的声明和赋值。
// signal_types_example.v
module signal_types_example (
input wire clk, // wire: 输入端口,默认类型
input wire rst_n,
input logic [7:0] din, // logic: SystemVerilog 统一类型
output reg [7:0] dout // reg: 输出寄存器
);
wire [7:0] wire_sum; // wire: 组合逻辑连线
reg [7:0] reg_count; // reg: 时序寄存器
logic signed [15:0] logic_mult; // logic: 有符号向量
integer int_loop; // integer: 用于循环
real real_delay = 1.5; // real: 浮点,仿真用
// wire: 连续赋值(组合逻辑)
assign wire_sum = din + 8'h01;
// reg/logic: always 块(时序逻辑)
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
reg_count <= 8'b0;
logic_mult <= 16'sh0;
dout <= 8'b0;
end else begin
reg_count <= reg_count + 1;
logic_mult <= din * 2; // 有符号乘法
dout <= wire_sum;
end
end
// integer: 用于行为描述(非综合)
initial begin
for (int_loop = 0; int_loop < 10; int_loop = int_loop + 1) begin
$display("Loop %d", int_loop);
end
end
// real: 用于延时仿真(非综合)
always @(din) begin
#real_delay $display("Delayed display: din=%h", din);
end
endmodule
- 解释:
wire_sum:组合逻辑,实时计算。reg_count:时序计数器,非阻塞赋值<=。logic_mult:SystemVerilog 类型,灵活。int_loop:测试台循环。real_delay:仿真延时。
3. 完整例子:使用多种信号类型的异步复位计数器
这是一个 8 位计数器模块,结合 wire、reg、logic 和 integer。包括测试台(testbench)验证。
- DUT 模块(counter.v):
module counter (
input wire clk,
input logic rst_n, // logic: 输入
input wire en,
output reg [7:0] count // reg: 输出寄存器
);
wire enable_internal; // wire: 内部连线
assign enable_internal = en & rst_n; // 组合逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
count <= 8'b0;
else if (enable_internal)
count <= count + 1;
end
endmodule
- 测试台(tb_counter.v):
module tb_counter;
reg clk = 0; // reg: 测试信号
logic rst_n = 1; // logic: SystemVerilog
wire en = 1; // wire: 常量连线
wire [7:0] count; // wire: 输出连接
integer sim_cycle = 0; // integer: 仿真计数
counter dut (
.clk(clk),
.rst_n(rst_n),
.en(en),
.count(count)
);
always #5 clk = ~clk; // 时钟生成
initial begin
#10 rst_n = 0; #20 rst_n = 1; // 复位序列
for (sim_cycle = 0; sim_cycle < 20; sim_cycle = sim_cycle + 1) begin
#10;
$display("Cycle %d: count=%h", sim_cycle, count);
end
$finish;
end
endmodule
-
仿真结果示例(ModelSim 输出):
Cycle 0: count=00 Cycle 1: count=01 ... Cycle 19: count=13 -
解释:结合 wire(连线)、reg(寄存器)、logic(输入)、integer(循环)。测试台使用 reg 生成信号。
4. 最佳实践与扩展(2026 年趋势)
- 优先级:RTL 设计用 logic(SystemVerilog);老项目用 wire/reg。
- 调试:用 $display 检查信号值;波形查看 X/Z 状态。
- 高级:用 enum 定义状态信号,如
enum logic [1:0] {IDLE=2'b00, BUSY=2'b01} fsm_state;。 - 工具:Vivado/Quartus 综合时,检查信号类型推断报告。
- 常见问题:wire 未连接默认为 Z,导致仿真 X;reg 未复位导致不确定行为。
SystemVerilog 中的 enum 类型详解
enum(枚举类型)是 SystemVerilog 引入的最实用特性之一,它极大提高了代码的可读性、可维护性和安全性,尤其在状态机、控制信号、协议状态等场景中几乎是必用的。
1. enum 的基本语法与种类
// 最常见写法(推荐)
typedef enum logic [N-1:0] { // 显式指定底层类型(强烈推荐)
IDLE = 2'b00,
BUSY = 2'b01,
DONE = 2'b10,
ERROR = 2'b11
} state_t;
// 也可以不指定底层类型(自动推断为 int,32位)
typedef enum {
RED,
GREEN,
BLUE,
YELLOW = 10, // 可以手动指定数值
PURPLE // 自动递增 → 11
} color_t;
// 匿名枚举(很少用)
enum { LOW=0, MEDIUM, HIGH } level; // level 变量直接是这个匿名枚举类型
// 带默认值(常用于配置)
typedef enum logic [1:0] {
MODE_OFF = 2'b00,
MODE_LOW = 2'b01,
MODE_HIGH = 2'b10,
MODE_AUTO = 2'b11
} mode_t default MODE_OFF;
2. 推荐写法对比表(2026 年主流实践)
| 写法风格 | 推荐度 | 适用场景 | 优点 | 缺点/风险 |
|---|---|---|---|---|
enum logic [N-1:0] {...} |
★★★★★ | 状态机、协议状态、控制寄存器 | 最安全、最清晰、可综合优化好 | 稍显冗长 |
enum {IDLE, BUSY, DONE} |
★★★☆☆ | 简单标志、内部状态(非输出) | 最简洁 | 底层是 32bit int,浪费面积/功耗 |
enum int unsigned {...} |
★★☆☆☆ | 需要大范围枚举值 | 值域大 | 几乎不用在 RTL |
enum bit [N-1:0] {...} |
★★★★☆ | 对功耗/面积极度敏感的 FPGA 项目 | 二态(无 X/Z),面积小 | 仿真时丢失 X/Z 调试信息 |
typedef enum ... + default |
★★★★☆ | 配置参数、可选模式 | 复位/初始值更安全 | — |
2026 年业界强烈建议:
几乎所有状态机、FSM 状态、协议状态都用 typedef enum logic [N-1:0] 写法!
3. 实际使用示例(最常见几种场景)
示例 1:经典 FSM 状态机(最推荐写法)
module fsm_example (
input logic clk,
input logic rst_n,
input logic start,
input logic done,
output logic busy,
output logic [1:0] state_out // 可用于调试/观察
);
typedef enum logic [1:0] {
IDLE = 2'b00,
START = 2'b01,
PROCESS= 2'b10,
FINISH = 2'b11
} state_t;
state_t current_state, next_state;
// 状态寄存器
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n)
current_state <= IDLE;
else
current_state <= next_state;
end
// 下一状态逻辑(清晰易读)
always_comb begin
next_state = current_state;
case (current_state)
IDLE: if (start) next_state = START;
START: next_state = PROCESS;
PROCESS: if (done) next_state = FINISH;
FINISH: next_state = IDLE;
default: next_state = IDLE;
endcase
end
// 输出(Moore 机风格)
assign busy = (current_state != IDLE);
assign state_out = current_state; // 直接输出枚举值,便于波形观察
endmodule
示例 2:协议命令/响应类型(带默认值)
typedef enum logic [2:0] {
CMD_IDLE = 3'b000,
CMD_READ = 3'b001,
CMD_WRITE = 3'b010,
CMD_ERASE = 3'b100,
CMD_STATUS = 3'b101
} cmd_t default CMD_IDLE;
// 使用
cmd_t current_cmd;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n)
current_cmd <= CMD_IDLE; // 利用 default 值更安全
else
current_cmd <= next_cmd;
end
示例 3:错误码/状态码(常见于接口设计)
typedef enum logic [3:0] {
OK = 4'h0,
ERR_TIMEOUT = 4'h1,
ERR_PARITY = 4'h2,
ERR_CRC = 4'h4,
ERR_LENGTH = 4'h8,
ERR_UNKNOWN = 4'hF
} err_code_t;
// 使用
err_code_t status;
always_comb begin
casez (some_condition)
4'b1???: status = ERR_TIMEOUT;
4'b?1??: status = ERR_PARITY;
...
default: status = OK;
endcase
end
4. enum 的高级用法与技巧(进阶)
// 1. 枚举值自动递增(从 0 开始)
typedef enum logic [3:0] { A, B, C, D=8, E } auto_t; // A=0,B=1,C=2,D=8,E=9
// 2. 枚举作为数组索引(很实用)
state_t state;
logic [7:0] counter [state_t]; // 每个状态一个计数器
// 3. 枚举与 case 语句完美匹配(工具会检查完整性)
case (current_state)
IDLE: ...
BUSY: ...
DONE: ...
ERROR: ...
default: `uvm_error("FSM", "Illegal state reached!")
endcase
// 4. 转换为字符串(调试神器)
$display("Current state = %s", current_state.name); // 输出 "IDLE"、"BUSY" 等
5. 一句话总结(2026 年从业者真实感受)
如果你还在用 parameter 定义状态机状态,或者用 reg [2:0] state 写 FSM,
那你大概率是在写 2010 年的代码……
现代 RTL 设计中,状态机、协议状态、控制字段 → **一律用 typedef enum logic [N-1:0]**!
需要看更多复杂例子吗?
比如:
- 带 one-hot / gray 编码的 enum
- enum 在 UVM sequence / coverage 中的用法
- 多状态机协调(virtual sequence)中的枚举使用
更多推荐

所有评论(0)