UVM环境介绍
HEAD commitID: 1f968ef
1. tb/core/uvmt/uvmt_cv32e40p_dut_wrap.sv
`ifndef __UVMT_CV32E40P_DUT_WRAP_SV__
`define __UVMT_CV32E40P_DUT_WRAP_SV__
/**
* Module wrapper for CV32E40P RTL DUT.
*/
module uvmt_cv32e40p_dut_wrap #(
// CV32E40P parameters. See User Manual.
parameter PULP_XPULP = 0,
PULP_CLUSTER = 0,
FPU = 0,
PULP_ZFINX = 0,
NUM_MHPMCOUNTERS = 1,
// Remaining parameters are used by TB components only
INSTR_ADDR_WIDTH = 32,
INSTR_RDATA_WIDTH = 32,
RAM_ADDR_WIDTH = 22
)
(
uvma_clknrst_if clknrst_if,
uvma_interrupt_if interrupt_if,
// vp_status_if is driven by ENV and used in TB
uvma_interrupt_if vp_interrupt_if,
uvmt_cv32e40p_core_cntrl_if core_cntrl_if,
uvmt_cv32e40p_core_status_if core_status_if,
uvma_obi_memory_if obi_memory_instr_if,
uvma_obi_memory_if obi_memory_data_if
);
import uvm_pkg::*; // needed for the UVM messaging service (`uvm_info(), etc.)
// signals connecting core to memory
logic instr_req;
logic instr_gnt;
logic instr_rvalid;
logic [INSTR_ADDR_WIDTH-1 :0] instr_addr;
logic [INSTR_RDATA_WIDTH-1:0] instr_rdata;
logic data_req;
logic data_gnt;
logic data_rvalid;
logic [31:0] data_addr;
logic data_we;
logic [3:0] data_be;
logic [31:0] data_rdata;
logic [31:0] data_wdata;
logic [31:0] irq_vp;
logic [31:0] irq_uvma;
logic [31:0] irq;
logic irq_ack;
logic [ 4:0] irq_id;
logic debug_req_vp;
logic debug_req_uvma;
logic debug_req;
logic debug_havereset;
logic debug_running;
logic debug_halted;
assign debug_if.clk = clknrst_if.clk;
assign debug_if.reset_n = clknrst_if.reset_n;
assign debug_req_uvma = debug_if.debug_req;
assign debug_req = debug_req_vp | debug_req_uvma;
// --------------------------------------------
// Instruction bus is read-only, OBI v1.0
assign obi_memory_instr_if.we = 'b0;
assign obi_memory_instr_if.be = '1;
// Data bus is read/write, OBI v1.0
// --------------------------------------------
// Connect to uvma_interrupt_if
assign interrupt_if.clk = clknrst_if.clk;
assign interrupt_if.reset_n = clknrst_if.reset_n;
assign irq_uvma = interrupt_if.irq;
assign vp_interrupt_if.clk = clknrst_if.clk;
assign vp_interrupt_if.reset_n = clknrst_if.reset_n;
assign irq_vp = vp_interrupt_if.irq;
assign interrupt_if.irq_id = irq_id;
assign interrupt_if.irq_ack = irq_ack;
assign irq = irq_uvma | irq_vp;
// --------------------------------------------
// instantiate the core
cv32e40p_wrapper #(
.PULP_XPULP (PULP_XPULP),
.PULP_CLUSTER (PULP_CLUSTER),
.FPU (FPU),
.PULP_ZFINX (PULP_ZFINX),
.NUM_MHPMCOUNTERS (NUM_MHPMCOUNTERS)
)
cv32e40p_wrapper_i
(
.clk_i ( clknrst_if.clk ),
.rst_ni ( clknrst_if.reset_n ),
.pulp_clock_en_i ( core_cntrl_if.pulp_clock_en ),
.scan_cg_en_i ( core_cntrl_if.scan_cg_en ),
.boot_addr_i ( core_cntrl_if.boot_addr ),
.mtvec_addr_i ( core_cntrl_if.mtvec_addr ),
.dm_halt_addr_i ( core_cntrl_if.dm_halt_addr ),
.hart_id_i ( core_cntrl_if.hart_id ),
.dm_exception_addr_i ( core_cntrl_if.dm_exception_addr),
.instr_req_o ( obi_memory_instr_if.req ), // core to agent
.instr_gnt_i ( obi_memory_instr_if.gnt ), // agent to core
.instr_rvalid_i ( obi_memory_instr_if.rvalid ),
.instr_addr_o ( obi_memory_instr_if.addr ),
.instr_rdata_i ( obi_memory_instr_if.rdata ),
.data_req_o ( obi_memory_data_if.req ),
.data_gnt_i ( obi_memory_data_if.gnt ),
.data_rvalid_i ( obi_memory_data_if.rvalid ),
.data_we_o ( obi_memory_data_if.we ),
.data_be_o ( obi_memory_data_if.be ),
.data_addr_o ( obi_memory_data_if.addr ),
.data_wdata_o ( obi_memory_data_if.wdata ),
.data_rdata_i ( obi_memory_data_if.rdata ),
// APU not verified in cv32e40p (future work)
.apu_req_o ( ),
.apu_gnt_i ( 1'b0 ),
.apu_operands_o ( ),
.apu_op_o ( ),
.apu_flags_o ( ),
.apu_rvalid_i ( 1'b0 ),
.apu_result_i ( {32{1'b0}} ),
.apu_flags_i ( {5{1'b0}} ), // APU_NUSFLAGS_CPU
.irq_i ( irq_uvma ),
.irq_ack_o ( irq_ack ),
.irq_id_o ( irq_id ),
.debug_req_i ( debug_req_uvma ),
.debug_havereset_o ( debug_havereset ),
.debug_running_o ( debug_running ),
.debug_halted_o ( debug_halted ),
.fetch_enable_i ( core_cntrl_if.fetch_en ),
.core_sleep_o ( core_status_if.core_busy )
); // cv32e40p_wrapper_i
endmodule : uvmt_cv32e40p_dut_wrap
`endif // __UVMT_CV32E40P_DUT_WRAP_SV__
1. 文件概述
uvmt_cv32e40p_dut_wrap.sv
是一个 SystemVerilog 文件,其主要功能是作为 CV32E40P RTL(寄存器传输级)设计的封装模块,用于测试平台(TB)环境。该文件对 CV32E40P 核心、内存以及一些虚拟外设进行封装,并且处理了这些组件之间的信号连接。
2. 版权和许可信息
文件开头包含了版权声明以及遵循 Solderpad Hardware Licence 和 Apache License 2.0 的许可信息,这表明文件的使用和分发需要遵循这些开源许可证的规定。
3. 模块定义
module uvmt_cv32e40p_dut_wrap #(
// CV32E40P parameters. See User Manual.
parameter PULP_XPULP = 0,
PULP_CLUSTER = 0,
FPU = 0,
PULP_ZFINX = 0,
NUM_MHPMCOUNTERS = 1,
// Remaining parameters are used by TB components only
INSTR_ADDR_WIDTH = 32,
INSTR_RDATA_WIDTH = 32,
RAM_ADDR_WIDTH = 22
)
(
uvma_clknrst_if clknrst_if,
uvma_interrupt_if interrupt_if,
// vp_status_if is driven by ENV and used in TB
uvma_interrupt_if vp_interrupt_if,
uvmt_cv32e40p_core_cntrl_if core_cntrl_if,
uvmt_cv32e40p_core_status_if core_status_if,
uvma_obi_memory_if obi_memory_instr_if,
uvma_obi_memory_if obi_memory_data_if
);
- 参数部分:定义了一系列参数,这些参数有的与 CV32E40P 核心的配置相关(如
PULP_XPULP
、FPU
等),有的则是供测试平台组件使用(如INSTR_ADDR_WIDTH
、RAM_ADDR_WIDTH
等)。 - 端口部分:包含多个接口类型的端口,这些接口用于与时钟复位、中断、核心控制、核心状态以及内存访问等功能模块进行连接。
4. 信号声明
// signals connecting core to memory
logic instr_req;
logic instr_gnt;
logic instr_rvalid;
logic [INSTR_ADDR_WIDTH-1 :0] instr_addr;
logic [INSTR_RDATA_WIDTH-1:0] instr_rdata;
logic data_req;
logic data_gnt;
logic data_rvalid;
logic [31:0] data_addr;
logic data_we;
logic [3:0] data_be;
logic [31:0] data_rdata;
logic [31:0] data_wdata;
logic [31:0] irq_vp;
logic [31:0] irq_uvma;
logic [31:0] irq;
logic irq_ack;
logic [ 4:0] irq_id;
logic debug_req_vp;
logic debug_req_uvma;
logic debug_req;
logic debug_havereset;
logic debug_running;
logic debug_halted;
声明了一系列逻辑信号,这些信号用于连接核心与内存、处理中断和调试请求等。
5. 信号赋值
assign debug_if.clk = clknrst_if.clk;
assign debug_if.reset_n = clknrst_if.reset_n;
assign debug_req_uvma = debug_if.debug_req;
assign debug_req = debug_req_vp | debug_req_uvma;
// --------------------------------------------
// Instruction bus is read-only, OBI v1.0
assign obi_memory_instr_if.we = 'b0;
assign obi_memory_instr_if.be = '1;
// Data bus is read/write, OBI v1.0
// --------------------------------------------
// Connect to uvma_interrupt_if
assign interrupt_if.clk = clknrst_if.clk;
assign interrupt_if.reset_n = clknrst_if.reset_n;
assign irq_uvma = interrupt_if.irq;
assign vp_interrupt_if.clk = clknrst_if.clk;
assign vp_interrupt_if.reset_n = clknrst_if.reset_n;
assign irq_vp = vp_interrupt_if.irq;
assign interrupt_if.irq_id = irq_id;
assign interrupt_if.irq_ack = irq_ack;
assign irq = irq_uvma | irq_vp;
对信号进行赋值操作,将时钟、复位信号进行连接,设置指令总线为只读,同时处理中断信号的合并。
6. 核心实例化
cv32e40p_wrapper #(
.PULP_XPULP (PULP_XPULP),
.PULP_CLUSTER (PULP_CLUSTER),
.FPU (FPU),
.PULP_ZFINX (PULP_ZFINX),
.NUM_MHPMCOUNTERS (NUM_MHPMCOUNTERS)
)
cv32e40p_wrapper_i
(
.clk_i ( clknrst_if.clk ),
.rst_ni ( clknrst_if.reset_n ),
.pulp_clock_en_i ( core_cntrl_if.pulp_clock_en ),
.scan_cg_en_i ( core_cntrl_if.scan_cg_en ),
.boot_addr_i ( core_cntrl_if.boot_addr ),
.mtvec_addr_i ( core_cntrl_if.mtvec_addr ),
.dm_halt_addr_i ( core_cntrl_if.dm_halt_addr ),
.hart_id_i ( core_cntrl_if.hart_id ),
.dm_exception_addr_i ( core_cntrl_if.dm_exception_addr),
.instr_req_o ( obi_memory_instr_if.req ), // core to agent
.instr_gnt_i ( obi_memory_instr_if.gnt ), // agent to core
.instr_rvalid_i ( obi_memory_instr_if.rvalid ),
.instr_addr_o ( obi_memory_instr_if.addr ),
.instr_rdata_i ( obi_memory_instr_if.rdata ),
.data_req_o ( obi_memory_data_if.req ),
.data_gnt_i ( obi_memory_data_if.gnt ),
.data_rvalid_i ( obi_memory_data_if.rvalid ),
.data_we_o ( obi_memory_data_if.we ),
.data_be_o ( obi_memory_data_if.be ),
.data_addr_o ( obi_memory_data_if.addr ),
.data_wdata_o ( obi_memory_data_if.wdata ),
.data_rdata_i ( obi_memory_data_if.rdata ),
// APU not verified in cv32e40p (future work)
.apu_req_o ( ),
.apu_gnt_i ( 1'b0 ),
.apu_operands_o ( ),
.apu_op_o ( ),
.apu_flags_o ( ),
.apu_rvalid_i ( 1'b0 ),
.apu_result_i ( {32{1'b0}} ),
.apu_flags_i ( {5{1'b0}} ), // APU_NUSFLAGS_CPU
.irq_i ( irq_uvma ),
.irq_ack_o ( irq_ack ),
.irq_id_o ( irq_id ),
.debug_req_i ( debug_req_uvma ),
.debug_havereset_o ( debug_havereset ),
.debug_running_o ( debug_running ),
.debug_halted_o ( debug_halted ),
.fetch_enable_i ( core_cntrl_if.fetch_en ),
.core_sleep_o ( core_status_if.core_busy )
);
实例化 cv32e40p_wrapper
模块,将之前定义的参数和信号连接到该模块的对应端口,实现核心与其他模块的交互。
2. tb/core/uvmt/uvmt_cv32e40p_interrupt_assert.sv
module uvmt_cv32e40p_interrupt_assert
import uvm_pkg::*;
import cv32e40p_pkg::*;
(
input clk, // Gated clock
input clk_i, // Free-running core clock
input rst_ni,
// Core inputs
input fetch_enable_i, // external core fetch enable
// External interrupt interface
input [31:0] irq_i,
input irq_ack_o,
input [4:0] irq_id_o,
// External debug req (for WFI modeling)
input debug_req_i,
input debug_mode_q,
// CSR Interface
input [5:0] mcause_n, // mcause_n[5]: interrupt, mcause_n[4]: vector
input [31:0] mip, // machine interrupt pending
input [31:0] mie_q, // machine interrupt enable
input [31:0] mie_n, // machine interrupt enable
input mstatus_mie, // machine mode interrupt enable
input [1:0] mtvec_mode_q, // machine mode interrupt vector mode
// Instruction fetch stage
input if_stage_instr_rvalid_i, // Instruction word is valid
input [31:0] if_stage_instr_rdata_i, // Instruction word data
// Instruction ID stage (determines executed instructions)
input id_stage_instr_valid_i, // instruction word is valid
input [31:0] id_stage_instr_rdata_i, // Instruction word data
input [4:0] ctrl_fsm_cs, // Controller FSM to get hint for interrupt taken
// Determine whether to cancel instruction if branch taken
input branch_taken_ex,
// WFI Interface
input core_sleep_o
);
// ---------------------------------------------------------------------------
// Local parameters
// ---------------------------------------------------------------------------
localparam NUM_IRQ = 32;
localparam VALID_IRQ_MASK = 32'hffff_0888; // Valid external interrupt signals
localparam WFI_INSTR_MASK = 32'hffffffff;
localparam WFI_INSTR_DATA = 32'h10500073;
localparam WFI_WAKEUP_LATENCY = 40;
// ---------------------------------------------------------------------------
// Local variables
// ---------------------------------------------------------------------------
string info_tag = "CV32E40P_IRQ_ASSERT";
wire [31:0] pending_enabled_irq;
wire [31:0] pending_enabled_irq_q;
wire id_instr_is_wfi; // ID instruction is a WFI
reg in_wfi; // Local model of WFI state of core
reg[31:0] irq_q;
reg[31:0] next_irq;
reg next_irq_valid;
reg[31:0] next_irq_q;
reg next_irq_valid_q;
reg[31:0] saved_mie_q;
reg[31:0] expected_irq;
logic expected_irq_ack;
reg[31:0] last_instr_rdata;
// ---------------------------------------------------------------------------
// Clocking blocks
// ---------------------------------------------------------------------------
// Single clock, single reset design, use default clocking
default clocking @(posedge clk_i); endclocking
default disable iff !(rst_ni);
// ---------------------------------------------------------------------------
// Begin module code
// ---------------------------------------------------------------------------
assign pending_enabled_irq = irq_i & mie_n;
assign pending_enabled_irq_q = irq_q & mie_n;
// ---------------------------------------------------------------------------
// Interrupt interface checks
// ---------------------------------------------------------------------------
// irq_ack_o is always a pulse
property p_irq_ack_o_pulse;
irq_ack_o |=> !irq_ack_o;
endproperty
a_irq_ack_o_pulse: assert property(p_irq_ack_o_pulse)
else
`uvm_error(info_tag,
"Interrupt ack was asserted for more than one cycle");
// irq_id_o is never a reserved irq
property p_irq_id_o_not_reserved;
irq_ack_o |-> VALID_IRQ_MASK[irq_id_o];
endproperty
a_irq_id_o_not_reserved: assert property(p_irq_id_o_not_reserved)
else
`uvm_error(info_tag,
$sformatf("int_id_o output is 0x%0x which is reserved", irq_id_o));
// irq_id_o is never a disabled irq
property p_irq_id_o_mie_enabled;
irq_ack_o |-> mie_n[irq_id_o];
endproperty
a_irq_id_o_mie_enabled: assert property(p_irq_id_o_mie_enabled)
else
`uvm_error(info_tag,
$sformatf("irq_id_o output is 0x%0x which is disabled in MIE: 0x%08x", irq_id_o, mie_n));
// irq_ack_o cannot be asserted if mstatus_mie is deasserted
property p_irq_id_o_mstatus_mie_enabled;
irq_ack_o |-> mstatus_mie;
endproperty
a_irq_id_o_mstatus_mie_enabled: assert property(p_irq_id_o_mstatus_mie_enabled)
else
`uvm_error(info_tag,
$sformatf("int_id_o output is 0x%0x but MSTATUS.MIE is disabled", irq_id_o));
// ---------------------------------------------------------------------------
// Interrupt CSR checks
// ---------------------------------------------------------------------------
// Coverage for individual interupt assertions
sequence s_irq_taken(irq);
irq_i[irq] ##0 mie_q[irq] ##0 mstatus_mie ##0 irq_ack_o ##0 irq_id_o == irq;
endsequence : s_irq_taken
// Interrupt fired, global interrupts enabled, but not taken due to global MSTATUS.MIE setting
property p_irq_masked(irq);
irq_i[irq] ##0 !mie_q[irq] ##0 mstatus_mie;
endproperty : p_irq_masked
// Interrupt fired and locally enabled in MIE, but masked due to MSTATUS_MIE
property p_irq_masked_mstatus(irq);
irq_i[irq] ##0 mie_q[irq] ##0 !mstatus_mie;
endproperty : p_irq_masked_mstatus
// Interrupt taken
property p_irq_taken(irq);
s_irq_taken(irq);
endproperty : p_irq_taken
// Interrupt enabled via MIE locally masked
property p_irq_masked_then_enabled(irq);
irq_i[irq] ##0 !mie_q[irq] ##0 mstatus_mie ##1 irq_i[irq] ##0 mie_q[irq] ##0 mstatus_mie;
endproperty : p_irq_masked_then_enabled
// Interrupt enabled via MSTATUS_MIE locally masked
property p_irq_masked_mstatus_then_enabled(irq);
irq_i[irq] ##0 mie_q[irq] ##0 !mstatus_mie ##1 irq_i[irq] ##0 mie_q[irq] ##0 mstatus_mie;
endproperty : p_irq_masked_mstatus_then_enabled
// Interrupt request deasserted when enabled but not acked
property p_irq_deasserted_while_enabled_not_acked(irq);
irq_i[irq] ##0 mie_q[irq] ##0 mstatus_mie ##0 !irq_ack_o ##1
!irq_i[irq] ##0 !irq_ack_o;
endproperty : p_irq_deasserted_while_enabled_not_acked
// Interrupt taken in each supported mtvec mode
property p_irq_in_mtvec(irq, mtvec);
s_irq_taken(irq) ##0 mtvec_mode_q == mtvec;
endproperty
generate for(genvar gv_i = 0; gv_i < NUM_IRQ; gv_i++) begin : gen_irq_cov
if (VALID_IRQ_MASK[gv_i]) begin : gen_valid
c_irq_masked: cover property(p_irq_masked(gv_i));
c_irq_masked_mstatus: cover property(p_irq_masked_mstatus(gv_i));
c_irq_taken: cover property(p_irq_taken(gv_i));
c_irq_masked_then_enabled: cover property(p_irq_masked_then_enabled(gv_i));
c_irq_masked_mstatus_then_enabled: cover property(p_irq_masked_mstatus_then_enabled(gv_i));
c_irq_deasserted_while_enabled_not_acked: cover property(p_irq_deasserted_while_enabled_not_acked(gv_i));
c_irq_in_mtvec_fixed: cover property(p_irq_in_mtvec(gv_i, 0));
c_irq_in_mtvec_vector: cover property(p_irq_in_mtvec(gv_i, 1));
end
end
endgenerate
// Detect arbitration of interrupt assertion
always @* begin
next_irq_valid = 1'b0;
next_irq = '0;
casex ({pending_enabled_irq_q[31:16], pending_enabled_irq_q[11], pending_enabled_irq_q[3], pending_enabled_irq_q[7]})
19'b1???_????_????_????_???: begin next_irq = 'd31; next_irq_valid = '1; end
19'b01??_????_????_????_???: begin next_irq = 'd30; next_irq_valid = '1; end
19'b001?_????_????_????_???: begin next_irq = 'd29; next_irq_valid = '1; end
19'b0001_????_????_????_???: begin next_irq = 'd28; next_irq_valid = '1; end
19'b0000_1???_????_????_???: begin next_irq = 'd27; next_irq_valid = '1; end
19'b0000_01??_????_????_???: begin next_irq = 'd26; next_irq_valid = '1; end
19'b0000_001?_????_????_???: begin next_irq = 'd25; next_irq_valid = '1; end
19'b0000_0001_????_????_???: begin next_irq = 'd24; next_irq_valid = '1; end
19'b0000_0000_1???_????_???: begin next_irq = 'd23; next_irq_valid = '1; end
19'b0000_0000_01??_????_???: begin next_irq = 'd22; next_irq_valid = '1; end
19'b0000_0000_001?_????_???: begin next_irq = 'd21; next_irq_valid = '1; end
19'b0000_0000_0001_????_???: begin next_irq = 'd20; next_irq_valid = '1; end
19'b0000_0000_0000_1???_???: begin next_irq = 'd19; next_irq_valid = '1; end
19'b0000_0000_0000_01??_???: begin next_irq = 'd18; next_irq_valid = '1; end
19'b0000_0000_0000_001?_???: begin next_irq = 'd17; next_irq_valid = '1; end
19'b0000_0000_0000_0001_???: begin next_irq = 'd16; next_irq_valid = '1; end
19'b0000_0000_0000_0000_1??: begin next_irq = 'd11; next_irq_valid = '1; end
19'b0000_0000_0000_0000_01?: begin next_irq = 'd3; next_irq_valid = '1; end
19'b0000_0000_0000_0000_001: begin next_irq = 'd7; next_irq_valid = '1; end
endcase
end
always @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
irq_q <= 0;
next_irq_q <= 0;
next_irq_valid_q <= 0;
saved_mie_q <= 0;
end
else begin
irq_q <= irq_i;
next_irq_q <= next_irq;
next_irq_valid_q <= next_irq_valid;
saved_mie_q <= mie_q;
end
end
always @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni)
expected_irq <= 0;
else
expected_irq <= next_irq_q;
end
assign expected_irq_ack = next_irq_valid & mstatus_mie;
// Check expected interrupt wins
property p_irq_arb;
irq_ack_o |-> irq_id_o == next_irq;
endproperty
a_irq_arb: assert property(p_irq_arb)
else
`uvm_error(info_tag,
$sformatf("Expected winning interrupt: %0d, actual interrupt: %0d", next_irq, irq_id_o))
// Check that an interrupt is expected
property p_irq_expected;
irq_ack_o |-> expected_irq_ack;
endproperty
a_irq_expected: assert property(p_irq_expected)
else
`uvm_error(info_tag,
$sformatf("Did not expect interrupt ack: %0d", irq_id_o))
// ---------------------------------------------------------------------------
// The infamous "first" flag (kludge for $past() handling of t=0 values)
// Would like to use a leading ##1 in the property instead but this currently
// does not work with dsim
// ---------------------------------------------------------------------------
reg first;
always @(posedge clk or negedge rst_ni)
if (!rst_ni)
first <= 1'b1;
else
first <= 1'b0;
// mip reflects flopped interrupt inputs (irq_i) regardless of other configuration
// Note that this runs on the gated clock
property p_mip_irq_i;
@(posedge clk)
!first |-> mip == ($past(irq_i) & VALID_IRQ_MASK);
endproperty
a_mip_irq_i: assert property(p_mip_irq_i)
else
`uvm_error(info_tag,
$sformatf("MIP of 0x%08x does not follow flopped irq_i input: 0x%08x", mip, $past(irq_i)));
// mip should not be reserved
property p_mip_not_reserved;
(mip & ~VALID_IRQ_MASK) == 0;
endproperty
a_mip_not_reserved: assert property(p_mip_not_reserved)
else
`uvm_error(info_tag,
$sformatf("MIP of reserved interrupt is asserted: mip = 0x%08x", mip));
// ---------------------------------------------------------------------------
// Instruction coverage when taking an interrupt
// ---------------------------------------------------------------------------
always @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
last_instr_rdata <= '0;
end
else if (id_stage_instr_valid_i) begin
last_instr_rdata <= id_stage_instr_rdata_i;
end
end
// ---------------------------------------------------------------------------
// WFI Checks
// ---------------------------------------------------------------------------
assign is_wfi = id_stage_instr_valid_i &&
((id_stage_instr_rdata_i & WFI_INSTR_MASK) == WFI_INSTR_DATA) &&
!branch_taken_ex;
always @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
in_wfi <= 1'b0;
end
else begin
if (is_wfi)
in_wfi <= 1'b1;
else if (|pending_enabled_irq || debug_req_i)
in_wfi <= 1'b0;
end
end
// WFI assertion will assert core_sleep_o in 6 clocks
property p_wfi_assert_core_sleep_o;
!pending_enabled_irq_q ##0 !in_wfi ##1 !pending_enabled_irq_q ##0
((!pending_enabled_irq && !debug_mode_q && !debug_req_i) throughout in_wfi[*38])
|-> core_sleep_o;
endproperty
a_wfi_assert_core_sleep_o: assert property(p_wfi_assert_core_sleep_o)
else
`uvm_error(info_tag,
"Aassertion of core_sleep_o did not occur within 6 clocks")
// core_sleep_o deassertion in wfi should be followed by WFI deassertion
property p_core_sleep_deassert;
$fell(core_sleep_o) ##0 in_wfi |-> ##1 !in_wfi;
endproperty
a_core_sleep_deassert: assert property(p_core_sleep_deassert)
else
`uvm_error(info_tag,
"Deassertion of core_sleep_o in WFI not followed by WFI wakeup");
// When WFI deasserts the core should be awake
property p_wfi_deassert_core_sleep_o;
core_sleep_o ##1 pending_enabled_irq |-> !core_sleep_o;
endproperty
a_wfi_deassert_core_sleep_o: assert property(p_wfi_deassert_core_sleep_o)
else
`uvm_error(info_tag,
"Deassertion of WFI occurred and core is still asleep");
// WFI wakeup to next instruction fetch
property p_wfi_wake_to_instr_fetch;
disable iff (!rst_ni || !fetch_enable_i || debug_mode_q)
core_sleep_o ##0 in_wfi ##1 !in_wfi[->1] |-> ##[1:WFI_WAKEUP_LATENCY] if_stage_instr_rvalid_i;
endproperty
a_wfi_wake_to_instr_fetch: assert property(p_wfi_wake_to_instr_fetch)
else
`uvm_error(info_tag,
$sformatf("Core did not start fetching %0d cycles after WFI completed", WFI_WAKEUP_LATENCY));
// Cover property, detect sleep deassertion due to asserted and non-asserted interrupts
property p_wfi_wake_mstatus_mie(irq, mie);
$fell(in_wfi) ##0 irq_i[irq] ##0 mie_q[irq] ##0 mstatus_mie == mie;
endproperty
generate for(genvar gv_i = 0; gv_i < 32; gv_i++) begin : gen_wfi_cov
if (VALID_IRQ_MASK[gv_i]) begin
c_wfi_wake_mstatus_mie_0: cover property(p_wfi_wake_mstatus_mie(gv_i, 0));
c_wfi_wake_mstatus_mie_1: cover property(p_wfi_wake_mstatus_mie(gv_i, 1));
end
end
endgenerate
endmodule : uvmt_cv32e40p_interrupt_assert
uvmt_cv32e40p_interrupt_assert.sv
是一个 SystemVerilog 文件,用于对 cv32e40p
处理器的中断接口和相关控制状态寄存器(CSR)进行断言检查和覆盖率收集。
1. 版权和许可声明
文件开头包含版权声明,表明该文件由 OpenHW Group 和 Datum Technology Corporation 拥有版权,并遵循 Solderpad Hardware Licence, Version 2.0 许可。
2. 模块定义
module uvmt_cv32e40p_interrupt_assert
import uvm_pkg::*;
import cv32e40p_pkg::*;
(
// 端口定义
input clk, // Gated clock
input clk_i, // Free-running core clock
input rst_ni,
// 其他端口...
);
- 模块名称:
uvmt_cv32e40p_interrupt_assert
,用于对cv32e40p
处理器的中断相关信号进行断言检查。 - 导入包:导入了
uvm_pkg
和cv32e40p_pkg
包,可能用于使用 UVM 框架和cv32e40p
相关的类型和常量。 - 端口定义:包含时钟信号(
clk
和clk_i
)、复位信号(rst_ni
)、核心输入信号、外部中断接口信号、调试请求信号、CSR 接口信号、指令阶段信号等。
3. 局部参数定义
localparam NUM_IRQ = 32;
localparam VALID_IRQ_MASK = 32'hffff_0888; // Valid external interrupt signals
// 其他局部参数...
- 定义了一些局部参数,如中断数量
NUM_IRQ
、有效中断掩码VALID_IRQ_MASK
等,用于后续的断言和覆盖率收集。
4. 局部变量定义
string info_tag = "CV32E40P_IRQ_ASSERT";
wire [31:0] pending_enabled_irq;
// 其他局部变量...
- 定义了一些局部变量,如信息标签
info_tag
、待处理且使能的中断信号pending_enabled_irq
等,用于存储和处理中间结果。
5. 时钟块定义
default clocking @(posedge clk_i); endclocking
default disable iff !(rst_ni);
- 定义了默认的时钟块,使用
clk_i
的上升沿作为时钟信号,并在复位信号rst_ni
为低电平时禁用断言。
6. 组合逻辑和连续赋值
assign pending_enabled_irq = irq_i & mie_n;
assign pending_enabled_irq_q = irq_q & mie_n;
- 使用
assign
语句进行连续赋值,计算待处理且使能的中断信号。
7. 中断接口检查
// irq_ack_o is always a pulse
property p_irq_ack_o_pulse;
irq_ack_o |=> !irq_ack_o;
endproperty
a_irq_ack_o_pulse: assert property(p_irq_ack_o_pulse)
else
`uvm_error(info_tag,
"Interrupt ack was asserted for more than one cycle");
- 定义了一系列属性(
property
)和断言(assert
),用于检查中断接口信号的合法性,如irq_ack_o
是否为单周期脉冲、irq_id_o
是否为有效中断号、irq_id_o
是否为使能的中断等。
8. 中断 CSR 检查
// Coverage for individual interupt assertions
sequence s_irq_taken(irq);
irq_i[irq] ##0 mie_q[irq] ##0 mstatus_mie ##0 irq_ack_o ##0 irq_id_o == irq;
endsequence : s_irq_taken
// 其他中断 CSR 相关的序列、属性和覆盖率收集...
- 定义了一些序列(
sequence
)、属性和覆盖率收集(cover property
),用于检查中断 CSR 的状态和行为,如中断是否被正确屏蔽、是否被正确处理等。
9. 中断仲裁检测
always @* begin
next_irq_valid = 1'b0;
next_irq = '0;
casex ({pending_enabled_irq_q[31:16], pending_enabled_irq_q[11], pending_enabled_irq_q[3], pending_enabled_irq_q[7]})
// 各种情况...
endcase
end
- 使用
always @*
块进行组合逻辑设计,根据待处理且使能的中断信号,确定下一个要处理的中断。
10. 时序逻辑
always @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
irq_q <= 0;
// 其他复位操作...
end
else begin
irq_q <= irq_i;
// 其他更新操作...
end
end
- 使用
always @(posedge clk_i or negedge rst_ni)
块进行时序逻辑设计,在时钟上升沿或复位信号下降沿时更新寄存器的值。
11. 指令覆盖和 WFI 检查
// Instruction coverage when taking an interrupt
always @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
last_instr_rdata <= '0;
end
else if (id_stage_instr_valid_i) begin
last_instr_rdata <= id_stage_instr_rdata_i;
end
end
// WFI Checks
assign is_wfi = id_stage_instr_valid_i &&
((id_stage_instr_rdata_i & WFI_INSTR_MASK) == WFI_INSTR_DATA) &&
!branch_taken_ex;
// 其他 WFI 相关的属性和断言...
- 记录最后执行的指令数据,用于指令覆盖率收集。
- 定义了与等待中断(WFI)指令相关的逻辑和断言,如 WFI 指令是否正确触发、WFI 唤醒后是否正确恢复执行等。
总结
该文件主要用于对 cv32e40p
处理器的中断接口和相关 CSR 进行断言检查和覆盖率收集,确保中断系统的正确性和可靠性。通过定义各种属性、序列和断言,对中断信号的合法性、中断仲裁、CSR 状态等进行了全面的检查。同时,还对 WFI 指令的行为进行了检查,确保处理器在等待中断时能够正确响应。
3. tb/core/uvmt/uvmt_cv32e40p_iss_wrap.sv
`ifndef __UVMT_CV32E40P_ISS_WRAP_SV__
`define __UVMT_CV32E40P_ISS_WRAP_SV__
/**
* Module wrapper for Imperas OVP.
* Instanitates "CPU", the OVP wrapper, and "RAM" a spare memory model.
*/
module uvmt_cv32e40p_iss_wrap
#(
parameter int ROM_START_ADDR = 'h00000000,
parameter int ROM_BYTE_SIZE = 'h0,
parameter int RAM_BYTE_SIZE = 'h1B000000,
parameter int ID = 0
)
(
input realtime clk_period,
uvma_clknrst_if clknrst_if,
uvmt_cv32e40p_step_compare_if step_compare_if,
uvmt_cv32e40p_isa_covg_if isa_covg_if
);
RVVI_bus bus();
RVVI_io io();
MONITOR mon(bus, io);
RAM #(
.ROM_START_ADDR(ROM_START_ADDR),
.ROM_BYTE_SIZE(ROM_BYTE_SIZE),
.RAM_BYTE_SIZE(RAM_BYTE_SIZE)) ram(bus);
CPU #(.ID(ID), .VARIANT("CV32E40P")) cpu(bus, io);
assign bus.Clk = clknrst_if.clk;
// monitor rvvi updates
always @(cpu.state.notify) begin
int i;
step_compare_if.ovp_cpu_PCr = cpu.state.pc;
for(i=0; i<32; i++)
step_compare_if.ovp_cpu_GPR[i] = cpu.state.x[i];
// generate events
if (cpu.state.valid) begin
// $display("Instruction Retired %08X %08X", cpu.state.pc, cpu.state.pcw);
-> step_compare_if.ovp_cpu_valid;
end else if (cpu.state.trap &&
cpu.state.decode == "ecall" &&
cpu.io.irq_ack_o == 0 &&
(!cpu.io.DM || !cpu.state.csr["dcsr"][2])) begin
// With introduction of OVPSIM model v20200821.377
// the valid behavior was changed, the above clause was introduced to all signal valid
// instruction on valid ecalls, which must be checked out by the step and compare interface
// Eventually this module should be replaced by a RVFI/RVVI UVM scoreboard which will not rely on this
// $display("Instruction Retired %08X %08X", cpu.state.pc, cpu.state.pcw);
-> step_compare_if.ovp_cpu_valid;
end else if (cpu.state.trap) begin
// $display("Instruction Fault %08X %08X", cpu.state.pc, cpu.state.pcw);
-> step_compare_if.ovp_cpu_trap;
end
end
// monitor debug control updates
always @(cpu.control.notify) begin
step_compare_if.ovp_cpu_state_idle = cpu.control.state_idle;
step_compare_if.ovp_cpu_state_stepi = cpu.control.state_stepi;
step_compare_if.ovp_cpu_state_stop = cpu.control.state_stop;
step_compare_if.ovp_cpu_state_cont = cpu.control.state_cont;
end
function automatic void split(ref string in_s, ref string s1, s2);
automatic int i;
for (i=0; i<in_s.len(); i++) begin
if (in_s.getc(i) == ":")
break;
end
if (i==0 ) begin
`uvm_fatal("ERROR not : found in split '%0s'", in_s);
end
s1 = in_s.substr(0,i-1);
s2 = in_s.substr(i+1,in_s.len()-1);
endfunction
function automatic void sample();
string decode = cpu.state.decode;
string ins_str, op[4], key, val;
int i;
int num = $sscanf (decode, "%s %s %s %s %s", ins_str, op[0], op[1], op[2], op[3]);
isa_covg_if.ins.ins_str = ins_str;
isa_covg_if.ins.pc = step_compare_if.ovp_cpu_PCr;
isa_covg_if.ins.compressed = dut_wrap.cv32e40p_wrapper_i.tracer_i.insn_compressed;
for (i=0; i<num-1; i++) begin
split(op[i], key, val);
isa_covg_if.ins.ops[i].key=key;
isa_covg_if.ins.ops[i].val=val;
end
`uvm_info("OVPSIM", $sformatf("Decoded instr: %s%s pc: 0x%08x",
isa_covg_if.ins.compressed ? "c." : "",
decode,
isa_covg_if.ins.pc),
UVM_DEBUG)
->isa_covg_if.ins_valid;
endfunction
always @(step_compare_if.ovp_cpu_valid) begin
sample();
end
initial begin
clknrst_if.clk = 1'b0;
#1; // time for clknrst_if_dut to set the clk_period
wait (clk_period != 0.0);
`uvm_info("OVPWRAP", "Starting OVP clock", UVM_LOW)
clknrst_if.set_period(clk_period);
clknrst_if.start_clk();
end
endmodule : uvmt_cv32e40p_iss_wrap
`endif // __UVMT_CV32E40P_ISS_WRAP_SV__
uvmt_cv32e40p_iss_wrap.sv
是一个 SystemVerilog 文件,其作用是对 Imperas OVP(Open Virtual Platform)进行封装,在测试平台里模拟 CPU 及其内存环境。
1. 版权和许可声明
文件开头包含版权信息与许可声明,表明此文件遵循 Solderpad Hardware Licence, Version 2.0。
2. 宏定义与模块声明
`ifndef __UVMT_CV32E40P_ISS_WRAP_SV__
`define __UVMT_CV32E40P_ISS_WRAP_SV__
/**
* Module wrapper for Imperas OVP.
* Instanitates "CPU", the OVP wrapper, and "RAM" a spare memory model.
*/
module uvmt_cv32e40p_iss_wrap
#(
parameter int ROM_START_ADDR = 'h00000000,
parameter int ROM_BYTE_SIZE = 'h0,
parameter int RAM_BYTE_SIZE = 'h1B000000,
parameter int ID = 0
)
(
input realtime clk_period,
uvma_clknrst_if clknrst_if,
uvmt_cv32e40p_step_compare_if step_compare_if,
uvmt_cv32e40p_isa_covg_if isa_covg_if
);
- 运用条件编译防止头文件被重复包含。
uvmt_cv32e40p_iss_wrap
模块的功能是对 Imperas OVP 进行封装,模块有四个参数:ROM_START_ADDR
、ROM_BYTE_SIZE
、RAM_BYTE_SIZE
和ID
,分别代表 ROM 的起始地址、ROM 的字节大小、RAM 的字节大小以及实例 ID。- 模块的端口包括:
clk_period
:时钟周期,为实时类型。clknrst_if
:时钟和复位接口。step_compare_if
:步骤比较接口。isa_covg_if
:指令集覆盖接口。
3. 接口和实例化
RVVI_bus bus();
RVVI_io io();
MONITOR mon(bus, io);
RAM #(
.ROM_START_ADDR(ROM_START_ADDR),
.ROM_BYTE_SIZE(ROM_BYTE_SIZE),
.RAM_BYTE_SIZE(RAM_BYTE_SIZE)) ram(bus);
CPU #(.ID(ID), .VARIANT("CV32E40P")) cpu(bus, io);
- 实例化
RVVI_bus
和RVVI_io
接口。 - 实例化
MONITOR
、RAM
和CPU
模块,并且将模块参数传递给RAM
和CPU
。
4. 时钟连接
assign bus.Clk = clknrst_if.clk;
把时钟信号 clknrst_if.clk
连接到 bus.Clk
。
5. 状态监测与事件触发
// monitor rvvi updates
always @(cpu.state.notify) begin
int i;
step_compare_if.ovp_cpu_PCr = cpu.state.pc;
for(i=0; i<32; i++)
step_compare_if.ovp_cpu_GPR[i] = cpu.state.x[i];
// generate events
if (cpu.state.valid) begin
// $display("Instruction Retired %08X %08X", cpu.state.pc, cpu.state.pcw);
-> step_compare_if.ovp_cpu_valid;
end else if (cpu.state.trap &&
cpu.state.decode == "ecall" &&
cpu.io.irq_ack_o == 0 &&
(!cpu.io.DM || !cpu.state.csr["dcsr"][2])) begin
// With introduction of OVPSIM model v20200821.377
// the valid behavior was changed, the above clause was introduced to all signal valid
// instruction on valid ecalls, which must be checked out by the step and compare interface
// Eventually this module should be replaced by a RVFI/RVVI UVM scoreboard which will not rely on this
// $display("Instruction Retired %08X %08X", cpu.state.pc, cpu.state.pcw);
-> step_compare_if.ovp_cpu_valid;
end else if (cpu.state.trap) begin
// $display("Instruction Fault %08X %08X", cpu.state.pc, cpu.state.pcw);
-> step_compare_if.ovp_cpu_trap;
end
end
- 监测
cpu.state.notify
信号,当该信号变化时,更新step_compare_if
接口里的 CPU 状态信息。 - 根据
cpu.state
的状态,触发不同的事件:- 若
cpu.state.valid
为真,触发step_compare_if.ovp_cpu_valid
事件。 - 若满足特定条件(
cpu.state.trap
且cpu.state.decode
为"ecall"
等),触发step_compare_if.ovp_cpu_valid
事件。 - 若
cpu.state.trap
为真,触发step_compare_if.ovp_cpu_trap
事件。
- 若
6. 调试控制监测
// monitor debug control updates
always @(cpu.control.notify) begin
step_compare_if.ovp_cpu_state_idle = cpu.control.state_idle;
step_compare_if.ovp_cpu_state_stepi = cpu.control.state_stepi;
step_compare_if.ovp_cpu_state_stop = cpu.control.state_stop;
step_compare_if.ovp_cpu_state_cont = cpu.control.state_cont;
end
监测 cpu.control.notify
信号,当该信号变化时,更新 step_compare_if
接口里的 CPU 调试控制状态信息。
7. 字符串分割函数
function automatic void split(ref string in_s, ref string s1, s2);
automatic int i;
for (i=0; i<in_s.len(); i++) begin
if (in_s.getc(i) == ":")
break;
end
if (i==0 ) begin
`uvm_fatal("ERROR not : found in split '%0s'", in_s);
end
s1 = in_s.substr(0,i-1);
s2 = in_s.substr(i+1,in_s.len()-1);
endfunction
split
函数的作用是把输入字符串 in_s
按冒号 :
分割成两部分 s1
和 s2
。若字符串中没有冒号,使用 uvm_fatal
宏输出错误信息。
8. 采样函数
function automatic void sample();
string decode = cpu.state.decode;
string ins_str, op[4], key, val;
int i;
int num = $sscanf (decode, "%s %s %s %s %s", ins_str, op[0], op[1], op[2], op[3]);
isa_covg_if.ins.ins_str = ins_str;
isa_covg_if.ins.pc = step_compare_if.ovp_cpu_PCr;
isa_covg_if.ins.compressed = dut_wrap.cv32e40p_wrapper_i.tracer_i.insn_compressed;
for (i=0; i<num-1; i++) begin
split(op[i], key, val);
isa_covg_if.ins.ops[i].key=key;
isa_covg_if.ins.ops[i].val=val;
end
`uvm_info("OVPSIM", $sformatf("Decoded instr: %s%s pc: 0x%08x",
isa_covg_if.ins.compressed ? "c." : "",
decode,
isa_covg_if.ins.pc),
UVM_DEBUG)
->isa_covg_if.ins_valid;
endfunction
sample
函数的作用是对 CPU 状态进行采样,将解码后的指令信息存储到isa_covg_if.ins
中。- 利用
$sscanf
函数解析cpu.state.decode
字符串,提取指令信息。 - 调用
split
函数分割操作数,将操作数的键值对存储到isa_covg_if.ins.ops
中。 - 使用
uvm_info
宏输出解码后的指令信息。 - 触发
isa_covg_if.ins_valid
事件。
9. 采样触发
always @(step_compare_if.ovp_cpu_valid) begin
sample();
end
当 step_compare_if.ovp_cpu_valid
事件触发时,调用 sample
函数进行采样。
10. 时钟初始化
initial begin
clknrst_if.clk = 1'b0;
#1; // time for clknrst_if_dut to set the clk_period
wait (clk_period != 0.0);
`uvm_info("OVPWRAP", "Starting OVP clock", UVM_LOW)
clknrst_if.set_period(clk_period);
clknrst_if.start_clk();
end
- 在初始块中,将时钟信号
clknrst_if.clk
初始化为0
。 - 等待
clk_period
不为0
。 - 使用
uvm_info
宏输出启动 OVP 时钟的信息。 - 调用
clknrst_if.set_period
函数设置时钟周期,调用clknrst_if.start_clk
函数启动时钟。
总结
该文件构建了一个用于 Imperas OVP 的封装模块,对 CPU 及其内存环境进行模拟。模块通过监测 CPU 状态和调试控制信号,更新接口信息并触发相应事件,同时对指令信息进行采样,最终实现时钟的初始化和启动。