2. 实现RISC-V指令级激励生成器

任务目标

本案例要求开发一个UVM sequence,用于生成基本的RISC-V指令序列(add、sub、lw、sw等),并通过sequencer驱动到处理器接口。验证目标是确认sequence能够按预期产生指令流,且每条指令的字段(opcode、rd、rs1、rs2、imm)编码正确,最终通过driver将指令发送至DUT接口。

环境依赖

本案例基于UVM-1.2,需要标准UVM库及一个简单的RISC-V指令接口(包含时钟、复位、指令输出总线)。以下实现假设接口信号为instr_valid、instr_data[31:0],处理器在instr_valid拉高时采样指令。

关键完整代码

// riscv_instr_seq.svh
class riscv_instr_seq extends uvm_sequence #(riscv_instr_item);
    `uvm_object_utils(riscv_instr_seq)

    function new(string name = "riscv_instr_seq");
        super.new(name);
    endfunction

    // 生成一条R-type指令(add/sub)
    function riscv_instr_item create_r_type(input bit [6:0] funct7, input bit [2:0] funct3);
        riscv_instr_item item = riscv_instr_item::type_id::create("item");
        item.opcode = 7'b0110011;  // OP
        item.rd     = $urandom_range(1, 31);
        item.funct3 = funct3;
        item.rs1    = $urandom_range(0, 31);
        item.rs2    = $urandom_range(0, 31);
        item.funct7 = funct7;
        item.imm    = 0;
        return item;
    endfunction

    // 生成一条I-type指令(lw)
    function riscv_instr_item create_i_type_load(input bit [2:0] funct3);
        riscv_instr_item item = riscv_instr_item::type_id::create("item");
        item.opcode = 7'b0000011;  // LOAD
        item.rd     = $urandom_range(1, 31);
        item.funct3 = funct3;
        item.rs1    = $urandom_range(0, 31);
        item.rs2    = 0;
        item.funct7 = 0;
        item.imm    = $urandom_range(0, 4095);  // 12-bit unsigned offset
        return item;
    endfunction

    // 生成一条S-type指令(sw)
    function riscv_instr_item create_s_type_store(input bit [2:0] funct3);
        riscv_instr_item item = riscv_instr_item::type_id::create("item");
        item.opcode = 7'b0100011;  // STORE
        item.rd     = 0;
        item.funct3 = funct3;
        item.rs1    = $urandom_range(0, 31);
        item.rs2    = $urandom_range(0, 31);
        item.funct7 = 0;
        item.imm    = $urandom_range(0, 4095);
        return item;
    endfunction

    task body();
        riscv_instr_item item;
        int num_instr;

        // 生成10条混合指令序列
        for (int i = 0; i < 10; i++) begin
            int instr_type = $urandom_range(0, 3);
            case (instr_type)
                0: item = create_r_type(7'b0000000, 3'b000);  // add
                1: item = create_r_type(7'b0100000, 3'b000);  // sub
                2: item = create_i_type_load(3'b010);         // lw
                3: item = create_s_type_store(3'b010);        // sw
            endcase
            start_item(item);
            finish_item(item);
        end
    endtask
endclass

对应的riscv_instr_item定义及driver驱动逻辑如下:

// riscv_instr_item.svh
class riscv_instr_item extends uvm_sequence_item;
    `uvm_object_utils(riscv_instr_item)

    rand bit [6:0] opcode;
    rand bit [4:0] rd;
    rand bit [2:0] funct3;
    rand bit [4:0] rs1;
    rand bit [4:0] rs2;
    rand bit [6:0] funct7;
    rand bit [11:0] imm;

    function new(string name = "riscv_instr_item");
        super.new(name);
    endfunction

    // 将字段打包为32位RISC-V指令
    function bit [31:0] pack();
        bit [31:0] instr;
        case (opcode)
            7'b0110011: // R-type
                instr = {funct7, rs2, rs1, funct3, rd, opcode};
            7'b0000011: // I-type load
                instr = {imm, rs1, funct3, rd, opcode};
            7'b0100011: // S-type store
                instr = {imm[11:5], rs2, rs1, funct3, imm[4:0], opcode};
            default: instr = 32'h0;
        endcase
        return instr;
    endfunction

    function string convert2string();
        return $sformatf("opcode=%h rd=%0d funct3=%b rs1=%0d rs2=%0d imm=%0d",
                         opcode, rd, funct3, rs1, rs2, imm);
    endfunction
endclass
// riscv_driver.svh
class riscv_driver extends uvm_driver #(riscv_instr_item);
    `uvm_component_utils(riscv_driver)
    virtual riscv_if vif;

    function new(string name, uvm_component parent);
        super.new(name, parent);
    endfunction

    task run_phase(uvm_phase phase);
        forever begin
            seq_item_port.get_next_item(req);
            @(posedge vif.clk);
            vif.instr_valid <= 1;
            vif.instr_data <= req.pack();
            @(posedge vif.clk);
            vif.instr_valid <= 0;
            seq_item_port.item_done();
        end
    endtask
endclass

运行与验证

运行方式:在测试类中将sequence通过sequencer启动,并确保driver已连接到虚拟接口。编译命令示例:

vlog +incdir+$UVM_HOME/src riscv_if.sv riscv_instr_item.svh riscv_instr_seq.svh riscv_driver.svh riscv_test.svh
vsim -c -do "run -all; quit" work.riscv_test

验证方法:在driver中监测instr_valid与instr_data信号,将采集到的32位数据与sequence中对应item的pack()结果比对。可通过在scoreboard中添加如下检查:

// 在scoreboard中检查指令编码
if (vif.instr_valid) begin
    riscv_instr_item expected = new();
    // 从sequence获取当前item并比对
    assert(vif.instr_data == expected.pack()) else
        `uvm_error("SCB", $sformatf("Mismatch: got %h, expected %h", vif.instr_data, expected.pack()))
end

通过条件:仿真运行无UVM错误或致命错误,且scoreboard比对全部通过。预期输出为10条指令依次出现在接口上,每条指令的opcode字段分别对应add(0x33)、sub(0x33)、lw(0x03)、sw(0x23)的编码,且寄存器字段在合法范围内(rd非零)。

RISC-V指令生成与驱动流程 UVM Sequence 生成add/sub/lw/sw 随机化寄存器字段 start_item Sequencer 仲裁与转发 sequence item get_next_item Driver 调用pack()编码 驱动接口信号 处理器接口 (DUT) instr_valid / instr_data[31:0] 时钟上升沿采样 Scoreboard 比对instr_data与pack() 报告mismatch 监测信号 UVM组件 验证组件

常见问题:若sequence无法启动,检查是否在测试类中正确调用seq.start(sequencer);若指令编码错误,确认pack()函数中S-type的立即数位段拼接顺序(imm[11:5]应放在rs2之前,imm[4:0]放在funct3之后)。