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