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 年高效路径):

  1. 先把上面 5 个组件全部跑通(最重要)
  2. 加上 coverage group(功能覆盖 + 交叉覆盖)
  3. 实现 virtual sequence(多 agent 协调)
  4. 加入寄存器模型(uvm_reg + adapter)
  5. 学习 factory override、config_db 高级用法
  6. 处理 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 用于时序会报错。

下面详解常见信号类型,使用表格总结,然后提供示例代码和完整例子。

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)中的枚举使用
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐