Skip to content

feat(cv32e40p): add standalone platform and core config - #74

Open
marpac3 wants to merge 28 commits into
gvsoc:masterfrom
marpac3:mpaci/cv32e40p-clean
Open

marpac3 wants to merge 28 commits into
gvsoc:masterfrom
marpac3:mpaci/cv32e40p-clean

Conversation

@marpac3

@marpac3 marpac3 commented Apr 9, 2026

Copy link
Copy Markdown

CV32E40P personality for the ISS v2, under
cpu/iss_v2/{include,src}/cores/cv32e40p/:

  • CSR file: HPM counters with RTL-exact event gating, dcsr/dpc/tdata,
    mstatus write mask, the CV32E40P-only CSRs, illegal-instruction behaviour
    for the undeclared banks (PMP, S-mode);
  • exception routing: debug-mode exceptions and mret to dm_exception_addr;
  • debug-entry and IRQ model aligned with the RTL controller priorities
    (trigger > ebreak > haltreq, single-step, async-take hold for a lockstep
    driver);
  • custom-opcode (XPULP) decode legality matched against the RTL decoder
    (exhaustive sweep in both directions);
  • the CV32E40P v2 target and its configuration.

CV32E40P-specific behaviour is opt-in (defines and the new target). The few
changes to shared XPULP sources are plain bugfixes — for example the
post-increment forms corrupted x0.

History is grouped by theme; each commit message states what changed, why,
and how it maps to the CV32E40P RTL or the RISC-V spec.

Validation on this exact tree: lockstep RVVI co-simulation against the
CV32E40P RTL, 4 configurations, corev-dv random plus directed tests;
riscv-arch-test 3.9.1 via RISCOF with Sail 0.13.1 (431/434 overall, F suite
342/342).

@marpac3

marpac3 commented May 26, 2026

Copy link
Copy Markdown
Author

Branch refreshed — ready for review @haugoug

The branch has been refactored and force-pushed. Head is now d52f70c (6 commits on top of abcddd6 "Add SpatzMempool core").

Commit history (linear, cleaned)

d52f70c doc: drop residual internal markers from CV32E40P core config
030cf3b fix(pulp_iss_wrapper): read mhartid via .value
45eb0d9 chore: normalise author headers on CV32E40P files
d380f2b refactor(iss): point CV32E40P core sources at src/cv32e40p/ subdir
d588a24 feat(cv32e40p_exit): add device
27f1cbf feat(cv32e40p): standalone platform + core config

Notes on 030cf3b

mhartid was promoted from iss_reg_t to CsrReg in the companion gvsoc-core#146 change. The pulp_iss_wrapper.cpp consumer needs to read the underlying value explicitly via .value, matching the pattern already used in gvsoc-core/iss/lsu.cpp:558.

Validation

Smoke 4/4 PASS on the 4 CV32E40P config variants via cv32e40p-standalone target.

Related PRs

  • gvsoc/gvsoc-core#146 — ISS core model (head c03a77f5). Merge this PR after #146.
  • gvsoc/gvsoc#252 — outer submodule pointer bump (head 86e2e87).

Happy to address any feedback.

@marpac3
marpac3 force-pushed the mpaci/cv32e40p-clean branch from d52f70c to 9d9835a Compare June 12, 2026 15:19
@marpac3

marpac3 commented Jun 12, 2026

Copy link
Copy Markdown
Author

Refreshed the branch.

@TheSSDGuy

Copy link
Copy Markdown
Contributor

Hello @haugoug, do you think we can merge this? @marpac3, will you open/merge a PR also for this clean-v2. Actually we moved to cv32e40p core model also on magia_V3 and I might need this merged before to open a PR for magia. Thanks.

marpac3 added 24 commits August 18, 2026 14:10
Add the CV32E40P standalone GVSOC target for UVM co-simulation via the
RVVI bridge, plus the CV32E40P core model in pulp_cores.py.

The platform exposes the CV32E40P RTL generics (corev_pulp, fpu, zfinx,
corev_cluster, core_version, num_mhpmcounters) as gvrun parameters and
wires the core to a router with the CV32E40P memory map (RAM, virtual
stdout/timer sinks, debug ROM, virtual exit).

The core model computes misa and mimpid from the configured generics to
match cv32e40p_cs_registers.sv, and writes the vendor CSR reset values
and write masks consumed by Cv32e40pCsr::build() in csr_cv32e40p.cpp.
Add the CV32E40P virtual exit device that terminates the GVSOC
simulation when the test program writes exit_valid at 0x2000_0004,
mirroring the cv32e40p virtual peripheral status flags described in
test_programs.rst.

Includes the C++ device model, its Python wrapper, and the CMake
wiring under pulp/cv32e40p_exit/.
Map a new byte-granular sparse-memory component as the interconnect
default route (size=0 mapping, absolute addresses). The UVM testbench
serves the whole address space from a zero-default sparse memory, so
out-of-map accesses must behave the same on the reference platform:
reads return 0 until written, writes persist. Without this, stray
stores were silently dropped (later readback diverged) and stray
fetches faulted with mcause=1 where the RTL fetches 0 and traps
illegal instruction (mcause=2).

Also:
- exit device: retain writes in a 256B backing store so write-then-read
  (e.g. of the signature addresses) returns the stored value, as it
  does through the testbench memory.
- debug ROM: shrink 16KB -> 4KB to match the linker script dbg region;
  accesses past it now fall through to the background memory.
First step of the CV32E40P migration to the modular iss_v2 core:

- pulp/cpu/iss/cv32e40p_v2.py: core recipe on RiscvCommon with the
  generic v2 slots (IrqExternal, Event, Csr, ExecInOrder with
  scoreboard and in-order commit, LsuV2, scoreboarded Regfile,
  Hwloop) plus the CoreV ISA subset. The CV32E40P-specific CSR map,
  counters and trap behaviour will come in as slot overrides on top
  of this base, same layering as Ri5ky.
- cv32e40p-v2-spike.py: minimal bring-up platform. LsuV2 drives the
  fetch/data ports with the io_v2 protocol, so the platform lives on
  the io_v2 plane (router_v2, memory_v3, loader_v2) following the
  Ri5ky testbench layout, and reuses its MMIO peripheral (putchar,
  exit) at 0x10000000.

Integer bring-up ISA is rv32im + CoreV: with C enabled the generated
decode table still references the rvf compressed handlers even when
they are inactive (no F), and nothing declares them without an FPU
module — to be addressed separately.

Validated with hand-encoded probes on the spike target: cv.lw
post-increment (data + pointer) and a 5-iteration lp.setupi hardware
loop, both passing.
Cv32e40pCsr models the RTL CSR map on the v2 modular core (same layering
as Ri5ky): M-mode-only strict CSR space (undeclared addresses raise
illegal-instruction), RTL write masks and reset values, read-only machine
information and PULP custom CSRs (uhartid/privlv/zfinx), hardware-loop
CSRs readable but not writable through CSR instructions (the architectural
LPEND is re-derived from the hwloop module), trigger CSRs with the RTL
tie-offs, mcycle with inhibit-aware offset semantics, and fflags/frm/fcsr
legality following the RTL fs_off signal across the FPU/ZFINX/no-FPU
configurations.

The recipe switches the irq slot to the RISC-V privileged scheme (the RTL
implements mie/mip/mtvec with standard mcause codes, not the PULP event
unit) and keys the ISA cache on the PULP extension set.
…argets

The recipe forwards zfinx to the v2 core (register-file routing), tags
the ISA cache and the generated ISA name with the pulp/zfinx variant,
and emits CONFIG_GVSOC_ISS_FP_STATE_DIRTY on FPU configurations so FP
write-backs dirty mstatus.FS as in the RTL.

The bring-up platform moves to pulp/cv32e40p_v2_spike.py, shared by
three thin targets (cv32e40p-v2-spike, -fpu, -zfinx): one target name
per core configuration, so each gets its own serialized platform tree
(a non-default --parameter run would fall back to the JSON config path,
which the io_v2 components do not support).
The spike platform now builds rv32imc (base) and rv32imfc (fpu/zfinx)
decode tables. On the ZFINX variant the recipe disables the compressed
FP loads/stores: the RTL compressed decoder only accepts them with
FPU == 1 && ZFINX == 0 (cv32e40p_compressed_decoder.sv).
Counter model matching the RTL mhpm scheme (cv32e40p_cs_registers.sv):
minstret counts retires gated on mcountinhibit.IR, mhpmcounterN advances
at most once per retire when its mhpmeventN mask intersects the event
lines the instruction fired, mcycle/mcycleh become one 64-bit count that
freezes at the current value while mcountinhibit.CY is set. Events are
accumulated per instruction by Cv32e40pEvents and committed at retire
(trapping instructions drop them); Cv32e40pExec keeps the core on the
full handlers while any counter is enabled, since only those fire the
event lines (same scheme as the Ri5ky PCMR pairing). Only the
architectural lines are modelled: instr, load, store, jump, branch,
branch taken, compressed. The timing lines (stalls, imiss, APU) never
fire and their counters read zero.
Port of the cv32e40p-standalone co-simulation platform to the io_v2 plane
the iss_v2 LSU requires: same memory map (RAM, stdout/timer sinks, debug
ROM, virtual exit device, background sparse memory catch-all), rebuilt on
router_v2/memory_v3/loader_v2, one target per core configuration
(cv32e40p-v2-standalone, -fpu, -zfinx).

The exit device and the sparse background memory get io_v2 siblings with
the same register map and semantics as the io-plane originals; atomics are
rejected explicitly (no A extension on this platform). Memories pass
init=False so never-written bytes read 0 like the testbench memory (also
fixed on the spike platform, which inherited the poison default).
…he v2 core

An external stepper (RVVI bridge) samples the register file at each
retire, but the retire hook fires at issue while an async load writes
its rd only with the LSU response: expose a commit stream instead - a
PC ring pushed once the writeback is architecturally visible, at
commit-FIFO drain for held instructions - and pin the core to the full
dispatch path while the stream is observed (the fast path skips the
FIFO bookkeeping the stream is built on).

Restore the RTL WARL result of mtvec over the generic IrqRiscv callback,
which drops the mode bit the RTL keeps (cv32e40p_cs_registers.sv:667).

Boot the co-simulation platform at the fixed BOOT_ADDR like the RTL:
the ELF-entry sync would also rewrite mtvec after a CSR injection.
…code fixes on the v2 core

- Cv32e40pIrq personality: RTL priority ladder (31..16, MEI, MSI, MTI),
  vectored entry from mtvec{base,mode}, single-source IRQ_MASK and a
  mie write fixup (the generic mie_access bypasses the write mask).
- cv32e40p_irq_injector component: 19 wire masters driven through
  external_bind; the standalone platform binds them to core.i_IRQ(n).
- Cv32e40pCore / Cv32e40pException personalities: mcause stays sticky
  across mret and exception entry masks the mtvec mode bits.
- Cv32e40pRegfile: one-shot write-back suppression armed by the FP
  trap sites (CONFIG_GVSOC_ISS_CV32E40P_FP_TRAPS, set by the recipe on
  fpu/zfinx variants).
- Csr personality: user counter aliases (cycle/instret/hpm and their
  high views), mip front-end view (reads mirror the wire-driven store,
  writes dropped as on the RTL), architectural hwloop LPEND shadow,
  mip cleared in reset, FS not dirtied by trapped FP instructions.
- Events: trap sequence stamps on redirected commits so the bridge can
  gate the deferred state compare across a drain window.
- priv.hpp: CV32E40P csr dispatch where csrrc/csrrs with rs1=x0 and
  csrrsi/csrrci with uimm=0 are reads, not writes.
- Recipe: fence/fence.i decode relaxed to funct3-only as in the RTL
  decoder (reserved fields ignored), pulp parameter and the
  cv32e40p-v2-standalone-nopulp target (rv32imc, no CoreV, no X bit).
…sor on the v2 core

- The recipe now emits RISCV into the generated ISA header: the shared
  isa_lib int.h selects the RISC-V (not legacy RISCY) source order for
  lib_VEC_SHUFFLE2_*, and no v2 build was defining it (the v1 build gets
  it from the iss CMakeLists). Fixes cv.shuffle2.h half-word swap.
- Debug entry writes dcsr.cause atomically with the entry, as the RTL
  does, and bumps trap_seq so a commit-FIFO entry spanning a haltreq
  redirect is flagged stale like any other trap redirect.
- inflight_pending() exposes the parked-instruction state the bridge
  needs to drain the pipeline before a forced redirect.
Debug entry with a caller-supplied cause (req_debug_cause, written into
dcsr atomically with the entry), debug-CSR views over the base raw
fields (dcsr with the RTL WARL mask, dpc 16-bit aligned, dscratch0/1)
so the debug-ROM csrrw accesses stop raising illegal-instruction, no
interrupt take while in debug mode (the RTL controller ignores irq_req
there), and the debug_handler address in the v2 recipe (was 0).
The 0x7B0-0x7B3 views were declared with the base CsrAbtractReg type,
whose default check_access grants every access. The RTL decoder
(cv32e40p_decoder.sv, CSR_DCSR..CSR_DSCRATCH1) raises illegal-instruction
whenever they are touched outside debug mode, and generic_exception_test
checks exactly that. Give the views a Cv32e40pDebugCsr type that raises
illegal-instruction while exec.debug_mode is clear; debug-ROM code still
passes the check.
The Debug spec only defines dret in debug mode, and the RTL decoder
treats it as illegal elsewhere.  The model executed it unconditionally
and jumped through a never-written dpc.  Guard dret_exec on debug_mode,
matching the sfence.vma handling.
dcsr.prv is WARL and CV32E40P implements machine mode only, so the
hardware reads back 3 regardless of what debug software writes.  The
view kept the written value.  Mask the write down to the implemented
fields (ebreakm, ebreaku, stepie, step) and force prv to M.
…atch)

The core implements one mcontrol trigger with an execute-address match:

- tdata1 exposes bit 2 (execute match enable) as the only writable bit,
  tdata2 is fully writable; both writes are accepted only in debug mode
  and silently dropped otherwise, matching the RTL's
  tmatch_*_we & debug_mode_i gating (an M-mode write is NOT an illegal
  instruction).
- Cv32e40pIrq::check() raises the debug request with cause=2 when the
  trigger is armed and the about-to-dispatch PC equals tdata2: the match
  is evaluated BEFORE execution, as in the RTL (trigger_match_o on
  pc_id), so the matched instruction is not retired and dpc points at
  it.
With latency=1 on the soc memories every load parks its sync follower
in the inflight ring for one extra boundary: at any asynchronous
injection point (debug entry, interrupt take) the ISS has already
executed an instruction the RTL killed in ID, and no later repair can
undo the writeback. latency=0 on all ports (mem, stdout, timer,
debug_rom) makes every commit architecturally final at its own
boundary - the contract an external lockstep driver relies on when it
steps the model one commit at a time.

Standalone-platform behaviour is unaffected: instruction traces carry
the same retire order; only stall cycles differ.
…formed IRQ collision)

The v2 personality now models the four CV32E40P debug-entry causes
natively, with the arming order in Cv32e40pIrq::check() encoding the
Debug-spec priority (trigger 2 > haltreq 3 > step 4):

- ebreak with dcsr.ebreakm=1 (cause 1): the new
  CONFIG_GVSOC_ISS_CV32E40P_V2 define (cv32e40p_v2.py) gates the hook
  in the shared isa/rv32i.hpp / rv32c.hpp decoders; the instruction
  arms the request via ebreak_enter_debug() and never retires
  (mcause/mepc untouched, entry row = first debug-ROM instruction,
  like the RTL).

- single-step (cause 4): dret with dcsr.step=1 opens the window
  (dret_step_check, depc still live); check() re-enters once
  current_insn moved off step_pc - an exception redirect lands the
  entry on the handler address as the spec requires. Interrupts are
  masked inside the window unless dcsr.stepie=1. Every entry closes
  the window, so a haltreq during the window (or a debugger clearing
  dcsr.step before dret) cannot fire a stale cause=4 later.

- haltreq (cause 3): first-class injector wire (RTL debug_req_i),
  handled model-side by haltreq_sync - it arms req_debug AND wakes a
  WFI-parked hart with the full three-step release, which only the
  model can run. The RTL sleep unit exits on debug_req_i regardless
  of mie/mip, while the generic release is gated on mie & mip alone:
  a halt request arriving with mie=0 would otherwise never wake the
  model. The level is tracked so a held-high line re-halts right
  after dret, as the level-sensitive RTL input does.

- wfi_wake wire (co-simulation only, no architectural effect): pulsed
  externally when the DUT's retire stream proves a wake the interrupt
  wires cannot carry; runs the same three-step release. wfi with a
  pending debug request degrades to a nop (the RTL never sleeps with
  debug_req_i asserted).

Informed interrupt+debug collision: when the DUT's entry row carries
an interrupt take's CSR writes, the external lockstep driver posts
the taken cause id in collide_irq_id and check() takes exactly that
line ahead of the entry (irq_take, extracted from the ladder) - dpc
lands on the vectored handler entry, mstatus/mepc/mcause carry the
take. The model never guesses the arbitration: the outcome depends on
cycle timing only the DUT observes.

Injector grows from 19 to 21 lines (haltreq, wfi_wake), bound in
cv32e40p_v2_standalone.py to the ports the Cv32e40pIrq constructor
registers.

Hardening:
- check() masks the informed collision line against IRQ_MASK before
  irq_take (defense in depth on top of the driver-side validation) -
  cv32e40p_irq_pick's priority fallback would otherwise turn an unwired
  id into a silent phantom MTI take.
- Cv32e40pIrq::reset() override clears step_state, collide_irq_id and
  req_debug_cause: a live single-step window or an unconsumed collision
  id must not survive a reset.
- the wfi debugger-responsiveness guard (debug mode, dcsr.step, pending
  req_debug) is owned by the personality's wfi_exec - the shared
  IrqRiscv::wfi_handle keeps its upstream behaviour.
- release_wfi() helper deduplicates the three-step release shared by
  haltreq_sync and wfi_wake_sync.
- cv32e40p_v2.py enables the opt-in conformance the core commits gate
  for the other cores: CONFIG_GVSOC_ISS_RVC_STRICT and the
  FLEXFLOAT_TININESS_AFTER_ROUNDING c-flag.
…dary

An IRQ take adjacent to a debug entry lands its mcause on the row BEFORE
the entry row; an external lockstep driver can only nominate a candidate
(wired id + the take's expected mepc), since only the model knows the
dispatch boundary. The model certifies the candidate in
Cv32e40pIrq::check(), where current_insn IS the boundary: the take fires
only when the boundary matches the expected mepc, stale candidates are
discarded with a warning. New fields collide_expected_mepc /
collide_certify, armed alongside collide_irq_id and cleared on every
check()/reset().
The external stepper needs to know, on rvfi_trap rows, whether the
committed instruction itself raised an architectural exception: an
ecall/ebreak commit is the faulting step and must be consumed there,
while a pipeline kill-and-replay row commits a NORMAL instruction the
DUT re-executes on the next row - consuming it there shifts the compare
stream by one retire. The trap_seq stamp cannot separate the two (the
faulting insn is stamped after its own raise), so retire_account records
exec.has_exception into commit_trapped[], carried through the inflight
ring at drain like the trap_seq stamp.
…addr

CV32E40P UM (debug.rst): an exception taken in debug mode jumps to
dm_exception_addr without updating mepc/mcause/mstatus or the
privilege mode, and mret/uret in debug mode jump back to this address
"without affecting status registers". The generic paths went to mtvec
(clobbering the CSRs) and to mepc respectively.

Cv32e40pException::raise gains the debug-mode branch and reads the new
debug_exception_handler config key (falls back to the debug handler
entry when absent); Cv32e40pCore::mret_handle redirects before the
generic side effects. The platform passes the testbench default
0x1A111600 (uvme_cv32e40p_cfg).

Exercised by debug_test tests 11/12/13 (illegal CSR access, ecall and
mret inside the debugger).
… like the RTL

Two entry paths, two rules (cv32e40p_controller.sv): on a shared
DBG_TAKEN_ID boundary the trigger outranks a haltreq that latched
asynchronously before it (priority table, trigger highest), while a
closing single-step window enters through DBG_TAKEN_IF, whose cause
mux never looks at trigger_match - step wins and the trigger fires on
the following session. The trigger arm now overrides an armed cause-3
request but yields when the step window closes on the boundary; a
window still sitting on its own step_pc (dret straight onto the
matched address) keeps the trigger-first behaviour.

The trigger match - synchronous debug, not an asynchronous event -
is evaluated ahead of the internal async gate (skip_irq_check,
consumed inside check() core-side) on EVERY dispatch boundary and
falls through to the entry when it arms; the haltreq re-arm, step
window and interrupt ladder stay behind the gate.

Both directions were wrong before: dcsr.cause read 3 where the DUT
said 2 (debug_test_trigger) and 2 where the DUT said 4 (debug_test,
single-step region).
CV32E40P has no PMP: the UM's CSR chapter lists no pmpcfg/pmpaddr bank
and the RTL raises illegal instruction on any access. The generic model
declares the whole bank (pmpcfg0..15, pmpaddr0..63) even when the PMP
module is the empty variant - CONFIG_GVSOC_ISS_PMP is a type name and
is always defined - so a read returned zero instead of trapping.
Undeclare the bank alongside the other nonexistent CSRs; the
raise-on-unsupported path then matches the hardware.

Found by the all_csr_por CSR sweep: it diverged at pmpcfg0 after 1.16M
clean retires (the DUT entered the illegal handler, the ISS carried on
into pmpcfg1). With the fix the full sweep passes cleanly;
cv32e40p_csr_access_test, modeled_csr_por and readonly_csr_access pass
unchanged.
… count retires per spec

Two extensions to the model's external-stepper contract, both inert in
standalone runs.

Async-event hold (Cv32e40pIrq::dpi_async_hold). exec.skip_irq_check is a
one-shot consumed at the first check() of a step quantum, but a 20 ns
engine quantum runs several dispatches: every dispatch after the first ran
unguarded, and the model took pending wire IRQs (or re-armed haltreq) at a
boundary of its own stepping cadence, racing the DUT's entry row. The hold
is a LEVEL the driver owns: asynchronous events are delivered as state
(mip from the wires, req_debug latched) but never taken at a model-chosen
boundary; the take_irq / take_debug injection windows lower it so the
entry lands on the DUT-proven boundary. Synchronous conditions -
execute-address trigger, single-step window close, ebreak-to-debug,
driver-armed causes - keep their architectural timing and ignore the hold.

Retire accounting (Cv32e40pEvents / Cv32e40pCsr). The commit ring now
carries the raw encoding next to the PC (truncated to 16 bits on RVC rows:
the fetched word holds the next parcel in its upper half), through the
inflight FIFO as well, so the stepper can serve a live instruction-binary
compare. minstret follows the RTL event exactly (cv32e40p_id_stage.sv:1639
and :1659): EBREAK never counts - the trapping forms were already dropped
with the event lines, count_instr covers the debug-entry ebreak that
retires without a trap - and a CSR write to either counter half suppresses
that row's increment (cs_registers write-wins gate), armed by the new
write callbacks and consumed at the same retire in hpm_commit.

Validated in step-and-compare with minstret/minstreth/instreth and the
instruction-binary compare enabled.
…legal

Undeclaring a CSR removes it from Csr::reset() coverage - reset walks the
declared map only - while its raw .value, never initialized by the CsrReg
constructor, is still read unguarded by shared trap code:
Exception::raise consults medeleg for delegation and redirects through
stvec, Core::sret_handle returns sepc. Left as heap garbage, a stray
medeleg bit silently delegated synchronous traps to S-mode: mstatus.spp
set, mcause/mepc left stale, entry through a garbage stvec. Zero the
backing fields once at construction; nothing can write them afterwards,
since an undeclared CSR raises illegal on any ISA access, like the RTL.

sret itself now raises illegal instruction instead of running the generic
handler: CV32E40P has no S-mode and the RTL decoder rejects the opcode.
The generic path would jump through sepc - never architecturally written
on this core - and demote the privilege mode via mstatus.spp. Same guard
shape as dret_exec outside debug mode.
x0 is hardwired to zero (cv32e40p_register_file_ff.sv "R0 is nil"; unpriv
spec). The decoder redirects rd==x0 writes to ISS_DUMMY_REG, but the XPULP
post-increment addressing modes write the base register back through
in_regs (IN_REG_SET, corev.hpp), which is never remapped: cv.lbu
x30,(x0),x14 computed the load at address 0 correctly and then executed
x0 += x14 in the model, while the RTL register file discards the write.
The stray write is invisible at the writing instruction (the
architecturally written register is x30, and it is correct); everything
downstream that reads x0 then diverges.

Writes to x0 are architectural no-ops: drop them up front in set_reg, so
they cannot consume the wb_suppress one-shot either.

The v1 ISS has the same defect (regfile_implem.hpp:55, and pulp_v2.hpp
writes in_regs back at 32 sites without even the rd==rs1 guard): it
affects ri5cy standalone runs and is a candidate upstream report.
… the writing instruction

The RTL evaluates the counter increment gates on mcountinhibit_q in the
cycle the CSR write commits (cv32e40p_cs_registers.sv:1428), so the
instruction that writes mcountinhibit still counts under the OLD gates and
the write takes effect from the next instruction on. Latch the pre-write
value when the write executes and let hpm_commit consume it at that same
retire; both flags clear unconditionally, they belong to that retire only.
@marpac3
marpac3 force-pushed the mpaci/cv32e40p-clean branch from 9d9835a to f5764c7 Compare August 21, 2026 10:35
@marpac3

marpac3 commented Aug 21, 2026

Copy link
Copy Markdown
Author

Force-pushed: the branch is reworked and extended since the last push. The
history is now grouped by theme with self-contained commit messages, and it
carries the latest fixes (hpm gating, retire counting, debug/IRQ corners) on
top of the previously pushed work. The companion gvsoc-core work is in
gvsoc/gvsoc-core#146, with the flexfloat/RVC fixes split out as
gvsoc/gvsoc-core#160/#161/#162/#163. Validation on this tree: lockstep RVVI
co-sim against the CV32E40P RTL (4 configurations), RISCOF 431/434
(F 342/342).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants