Overview
CV32E40P is a RISC-V CPU core from the OpenHW Group. The public OpenHW GitHub repository describes it as an in-order, 4-stage RISC-V RV32IMFCXpulp CPU based on RI5CY from PULP-Platform. The repository is written primarily in SystemVerilog.
A 2024 UVM verification study describes CV32E40P as a power-efficient 32-bit RISC-V core using in-order execution with a 4-stage pipeline. The same study notes that the core was already present in several commercial SoC designs, including IoT devices and a general-purpose 32-bit microcontroller, characterizing it as "small in size but big in popularity."
ISA and implementation context
CV32E40P is part of the OpenHW CORE-V family of RISC-V-based open-source cores. In the Core-V-Verif context, the CV32E40P RTL core is described as being based on the RISC-V specification and implementing the RV32IMCZifencei ISA extensions. The public GitHub summary separately characterizes the repository as RV32IMFCXpulp; these descriptions should be read in their respective source contexts.
The Core-V-Verif environment is intended to be adapted to other future CORE-V cores, including CV32E40X, CV32E40S, CVA6, and future cores on the OpenHW roadmap plan.
Verification with Core-V-Verif
Core-V-Verif is an OpenHW functional verification project intended to verify RISC-V cores in the CORE-V family. Its initial focus was the CV32E40P core. The project provides a simulation environment for the CV32E40P RTL core and is built using UVM and SystemVerilog class libraries, making the verification environment not specific to a single EDA vendor.
The Core-V-Verif testbench includes memory-model support for virtual peripherals, implemented by responding to reads or writes at specific addresses on the data bus. It also uses Board Support Package files to align test-program resources with the resources supported by the DUT; these can include linker scripts, CSR configuration files, and assembly files needed to run C programs.
The Core-V-Verif overview testbench diagram indicates that test programs flow from a generator (e.g., riscv-dv) into the test-program environment, with results compared through a functional coverage model (an Imperas reference model is referenced) and a scoreboard, with hooks for debug and interrupts.
COREV-DV instruction-stream generation
CV32E40P's verification flow uses COREV-DV, a Core-V-Verif library of extensions to Google's riscv-dv instruction stream generator. riscv-dv is implemented as a collection of SystemVerilog classes extending from uvm_object, so extending its classes to modify behavior is straightforward. For cases where a specific instruction stream is required, it is implemented as an extension of an existing riscv-dv class; riscv-dv itself is not modified, allowing Core-V-Verif to take advantage of upstream updates. For the most part, Core-V-Verif uses riscv-dv "as is," and each core verified in Core-V-Verif is free to use whatever version of riscv-dv suits the core's needs.
A non-core-specific set of COREV-DV extensions resides at $CORE_V_VERIF/lib/corev-dv. When a Make target requires it, a specific hash of riscv-dv is cloned to $CORE_V_VERIF/$COREV_CORE/vendor_lib/riscv-dv, and the compile Makefiles compile in the required extensions to generate the core-specific version of COREV-DV needed for a test. The Core-V-Verif mk/uvmt README documents how to build and run a COREV-DV generated test-program.
Each unique core is expected to require core-specific extensions to riscv-dv, placed in $CORE_V_VERIF/$COREV_CORE/env/corev-dv. For example, $CORE_V_VERIF/lib/corev-dv/corev_asm_program_gen.sv implements an override of riscv_gen_program_header::gen_program_header() to enforce the use of common symbols required by the board support package. Since each core's verification environment is likely to use a core-specific BSP, each core is expected to need core-specific extensions to corev_asm_program_gen.sv. The Core-V-Verif directory tree shows the locations of riscv-dv and the base COREV-DV extensions plus an example of core-specific extensions for the CV32E40P.
Test-program model
The Core-V-Verif UVM environment can run test programs as long as they are compatible with the BSP. It distinguishes whether a program is pre-existing or generated at run time, and whether it is self-checking. The documented program categories are:
- Pre-existing, self-checking programs.
- Pre-existing, non-self-checking programs.
- Generated, self-checking programs.
- Generated, non-self-checking programs.
- No test program, for cases such as debug-mode CSR access through the debug module interface.
Many of the test programs inherited from the RI5CY project are both pre-existing and self-checking. Core-V-Verif incorporates a random instruction stream generator (COREV-DV / riscv-dv) to generate additional test programs. It is expected that types 1 (pre-existing, self-checking) and 4 (generated, non-self-checking) will predominate. The environment incorporates a random instruction stream generator and supports pass/fail reporting through a status-flags virtual peripheral for self-checking programs. Other checker-monitors can independently fail a simulation by signaling uvm_error.
Programs running on the core are self-contained within an execution environment defined by the ISA, the memory map supported by dpmem, and the virtual peripherals supported by mm_mem; this execution environment knows nothing about the UVM environment, so the CORE-V UVM environments are implemented to be aware of the test program and respond accordingly as part of the run-flow. For example, a self-checking test program (type 1) loads its image into memory, signals pass/fail status to the testbench via the status-flags virtual peripheral, and the simulation drops its objection when the testbench observes that the program is done.
UVM test flow
The CV32E UVM environment defines a base test class, uvmt_cv32_base_test_c, directly extended from uvm_test. Tests are expected to extend this base class to preserve the intended test flow. A typical run flow raises an objection, asserts the core fetch_en input, waits for the core and/or environment to signal completion, and then drops the objection. The base test typically customizes three time-consuming tasks:
- Reset phase: usually just calls
super.reset_phase()to invoke the default (random) reset sequence; a test-specific reset virtual sequence may instead be invoked here. - Configure phase: typically light, since the core's CSRs do not require configuration before execution begins; tests that require pre-compiled programs to be loaded into instruction memory do so here.
- Run phase: contains most of the procedural test code.
Related ISA/interface notes from the verification study
The verification study also notes that, to run the co-simulation system, a particular version of Spike is required. The RISC-V Formal Interface (RVFI) is used to provide information about retired instructions and instructions that produce synchronous traps for checking.