Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
201fada
feat(cv32e40p): add standalone GVSOC platform and core config
marpac3 Jun 12, 2026
c61df88
feat(cv32e40p_exit): add virtual exit device
marpac3 Jun 12, 2026
e0f1e36
feat(cv32e40p): serve unmapped addresses from a background sparse memory
marpac3 Jul 15, 2026
f009204
feat(cv32e40p): add iss_v2 bring-up recipe and spike target
marpac3 Jul 16, 2026
9353d7a
feat(cv32e40p): CSR personality on iss_v2
marpac3 Jul 17, 2026
9e51529
feat(cv32e40p): FPU and ZFINX configurations on the iss_v2 bring-up t…
marpac3 Jul 17, 2026
ac97e27
feat(cv32e40p): compressed ISA on the v2 bring-up targets
marpac3 Jul 17, 2026
41b6ede
feat(cv32e40p): performance-counter personality on the v2 core
marpac3 Jul 17, 2026
3a543b3
feat(cv32e40p): co-simulation platform on the iss_v2 core
marpac3 Jul 17, 2026
ef012e7
feat(cv32e40p): architectural commit stream and mtvec WARL fixup on t…
marpac3 Jul 17, 2026
fe25feb
feat(cv32e40p): IRQ wire delivery, trap/priv personalities and RTL de…
marpac3 Jul 18, 2026
b305ee7
fix(cv32e40p): SIMD operand order, atomic debug entry and drain acces…
marpac3 Jul 19, 2026
eeda65d
feat(cv32e40p): debug-mode support on the v2 core
marpac3 Jul 20, 2026
e2828d4
fix(cv32e40p): reject M-mode access to the debug CSRs
marpac3 Jul 20, 2026
7944f8e
fix: dret outside debug mode raises an illegal instruction
marpac3 Jul 22, 2026
b352316
fix: pin dcsr.prv to M on writes (WARL on an M-only core)
marpac3 Jul 22, 2026
567e613
fix: model the CV32E40P trigger module (tdata write gating, execute m…
marpac3 Aug 5, 2026
404a030
fix: zero-latency memory ports keep the ISS at the commit boundary
marpac3 Aug 5, 2026
f5a8ad7
feat: native debug-entry model (ebreak, single-step, haltreq wire, in…
marpac3 Aug 6, 2026
9d6d636
feat: certify externally armed IRQ collide takes at the dispatch boun…
marpac3 Aug 7, 2026
4158d13
feat: stamp each commit-stream entry with the trapped bit
marpac3 Aug 7, 2026
f5c1707
feat(cv32e40p): route debug-mode exceptions and mret to dm_exception_…
marpac3 Aug 11, 2026
5521ceb
fix(cv32e40p): arbitrate a trigger match against armed debug requests…
marpac3 Aug 11, 2026
a5cd7f4
fix(cv32e40p): the PMP CSR bank must raise illegal instruction
marpac3 Aug 12, 2026
9d65a2c
fix(cv32e40p): hold async takes for the co-sim driver; carry insn and…
marpac3 Aug 16, 2026
f8e7170
fix(cv32e40p): zero the undeclared S-mode CSR backing; sret raises il…
marpac3 Aug 16, 2026
8a5b299
fix(cv32e40p): drop writes to x0 - the XPULP post-increment corrupted it
marpac3 Aug 16, 2026
f5764c7
fix(cv32e40p): gate hpm increments on the pre-write mcountinhibit for…
marpac3 Aug 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions cpu/iss_v2/include/cores/cv32e40p/core.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: 2026 Fondazione Chips-it
//
// SPDX-License-Identifier: Apache-2.0
//
// Authors: Marco Paci (marco.paci@chips.it)

#pragma once

#include <vp/vp.hpp>
#include <cpu/iss_v2/include/core.hpp>

class Cv32e40pCore : public Core
{
public:
Cv32e40pCore(Iss &iss) : Core(iss), iss(iss) {}

/* MRET with the mcause hold-over of the RTL; shadows the generic
* handler (static dispatch via CONFIG_GVSOC_ISS_CORE). */
iss_reg_t mret_handle();

private:
Iss &iss;
};
296 changes: 296 additions & 0 deletions cpu/iss_v2/include/cores/cv32e40p/csr.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
// SPDX-FileCopyrightText: 2026 Fondazione Chips-it
//
// SPDX-License-Identifier: Apache-2.0
//
// Authors: Marco Paci (marco.paci@chips.it)

#pragma once

#include <vp/vp.hpp>
#include <cpu/iss_v2/include/csr.hpp>

/* Static personality configuration, set by the Python recipe
* (pulp/cpu/iss/cv32e40p_v2.py):
* CONFIG_GVSOC_ISS_CV32E40P_FPU_IN_ISA F extension present (0 for ZFINX)
* CONFIG_GVSOC_ISS_CV32E40P_ZFINX ZFINX variant
* CONFIG_GVSOC_ISS_CV32E40P_PULP COREV_PULP (XPULP) configuration
* CONFIG_GVSOC_ISS_CV32E40P_NUM_MHPMCOUNTERS implemented HPM counters
*/
#ifndef CONFIG_GVSOC_ISS_CV32E40P_FPU_IN_ISA
#define CONFIG_GVSOC_ISS_CV32E40P_FPU_IN_ISA 0
#endif
#ifndef CONFIG_GVSOC_ISS_CV32E40P_ZFINX
#define CONFIG_GVSOC_ISS_CV32E40P_ZFINX 0
#endif
#ifndef CONFIG_GVSOC_ISS_CV32E40P_PULP
#define CONFIG_GVSOC_ISS_CV32E40P_PULP 1
#endif
#ifndef CONFIG_GVSOC_ISS_CV32E40P_NUM_MHPMCOUNTERS
#define CONFIG_GVSOC_ISS_CV32E40P_NUM_MHPMCOUNTERS 1
#endif
static_assert(CONFIG_GVSOC_ISS_CV32E40P_NUM_MHPMCOUNTERS <= 29,
"at most 29 HPM counters (mhpmcounter3..31)");

/* CSR that is read-only from CSR instructions: any write attempt raises an
* illegal-instruction exception before the access happens, so the destination
* register is not written (matches the RTL decoder behaviour). */
class Cv32e40pRoCsr : public CsrReg
{
public:
bool check_access(Iss *iss, bool write, bool read) override;
};

/* fflags / frm / fcsr front-end. Access legality follows the RTL fs_off
* signal: rejected with an illegal-instruction exception while mstatus.FS
* is Off (FPU in the ISA), always rejected without an FPU, always granted
* for ZFINX — see fp_access_illegal(). The register content lives in the
* base class fcsr field; the value mapping is done by the registered
* callback. */
class Cv32e40pFpCsr : public CsrAbtractReg
{
public:
bool check_access(Iss *iss, bool write, bool read) override;
};

/* Hardware-loop CSR front-end (lpstart/lpend/lpcount). Readable via CSR
* instructions, but the RTL decoder raises illegal-instruction on any CSR
* write to them (they are only programmed through the cv.* instructions). */
class Cv32e40pHwloopCsr : public CsrAbtractReg
{
public:
bool check_access(Iss *iss, bool write, bool read) override;
};

/* User-mode counter alias (cycle/instret/hpmcounterN and the H views):
* reads mirror the machine counter through a registered callback, writes
* raise illegal-instruction (0xCxx is the architecturally read-only CSR
* range, and the RTL has no write path for it). */
class Cv32e40pCounterAlias : public CsrAbtractReg
{
public:
bool check_access(Iss *iss, bool write, bool read) override;
};

/* Debug-mode CSR front-end (dcsr/dpc/dscratch0-1): accessible only while in
* debug mode. The RTL decoder raises illegal-instruction on any M-mode
* access (cv32e40p_decoder.sv, CSR_DCSR..CSR_DSCRATCH1 with !debug_mode_i),
* so generic_exception_test relies on these accesses trapping. Debug-ROM
* code runs with debug_mode set and passes the check. */
class Cv32e40pDebugCsr : public CsrAbtractReg
{
public:
bool check_access(Iss *iss, bool write, bool read) override;
};

class Cv32e40pCsr : public Csr
{
public:
/* mcountinhibit implemented bits: CY, IR and one per HPM counter. */
static constexpr iss_reg_t MCOUNTINHIBIT_MASK =
0x5 | (((1u << CONFIG_GVSOC_ISS_CV32E40P_NUM_MHPMCOUNTERS) - 1) << 3);

Cv32e40pCsr(Iss &iss);

void start();
void reset(bool active);

/* FP CSR access legality: illegal while mstatus.FS == Off (00). */
inline bool fp_access_illegal();

/* Promote mstatus.FS to Dirty (11) on FP state change. The RTL forces it
* on FP regfile writes, fflags updates and FP-CSR writes when the FPU is
* in the ISA (FPU=1, ZFINX=0). SD (bit 31) is derived at read time.
* Out-of-line: the trapped-instruction guard needs the full Iss type. */
void fp_state_dirty();

/* Advance the counters for one retired instruction: events is the OR of
* the RTL hpm_events lines it fired (see cores/cv32e40p/events.hpp);
* count_instr is the RTL minstret event line (false for EBREAK, which
* never counts - cv32e40p_id_stage.sv:1639). Called once per retire by
* Cv32e40pEvents::event_retire_account. */
inline void hpm_commit(uint32_t events, bool count_instr);

/* True while any implemented counter is enabled: keeps the core on the
* full handlers, where the event lines fire (Cv32e40pExec). */
inline bool hpm_counting();

/* EBREAK in M-mode enters debug when dcsr.ebreakm=1 (RISC-V Debug
* Spec, dcsr bit 15). Consumed by ebreak_exec/c_ebreak_exec
* (isa/rv32i.hpp, isa/rv32c.hpp), which check debug_mode first, so
* this is only reached outside debug mode. */
bool ebreak_m_mode_enters_debug() { return ((this->dcsr >> 15) & 1) != 0; }

/* CV32E40P-only CSRs, absent from the generic register file. */
Cv32e40pRoCsr mvendorid_ro; /* 0xF11 (replaces the base read/write reg) */
Cv32e40pRoCsr marchid_ro; /* 0xF12 (replaces the base read/write reg) */
Cv32e40pRoCsr mimpid; /* 0xF13 */
Cv32e40pRoCsr mhartid_csr; /* 0xF14 */
CsrReg tinfo; /* 0x7A4, read-only through a zero mask */
CsrReg mcontext; /* 0x7A8, writable only from debug mode */
CsrReg scontext; /* 0x7AA, writable only from debug mode */
CsrReg minstret; /* 0xB02 */
#if ISS_REG_WIDTH == 32
CsrReg mcycleh; /* 0xB80 */
CsrReg minstreth; /* 0xB82 */
#endif
CsrReg mhpmevent[29]; /* 0x323..0x33F */

/* PULP custom CSRs (COREV_PULP configurations). */
Cv32e40pRoCsr uhartid; /* 0xCD0 */
Cv32e40pRoCsr privlv; /* 0xCD1 */
Cv32e40pRoCsr zfinx_csr; /* 0xCD2, undeclared when FPU=1 && ZFINX=0 */

/* Hardware-loop CSRs: 0xCC0..0xCC2 / 0xCC4..0xCC6 (gap at 0xCC3). */
Cv32e40pHwloopCsr hwloop_csr[6];

/* Architectural LPEND per loop, written by the corev.hpp setters. The
* Hwloop module stores the loop-back point (LPEND - 4), so it cannot
* serve the CSR read: a never-programmed loop must read back 0. */
iss_reg_t hwloop_lpend[2] = {0, 0};

/* mip front-end (0x344): reads mirror the wire-driven base register,
* CSR writes are silently dropped — the RTL has no mip write path
* (cv32e40p_cs_registers.sv reads it from the interrupt lines only).
* Replaces the base mip in the CSR map, so the generic IrqRiscv write
* callback (wdata & 0xAAA, which also clears the fast-line bits) can
* never corrupt the pending state. */
CsrAbtractReg mip_view;

/* Debug-mode CSRs (0x7B0-0x7B3): views over the base raw fields
* (dcsr/depc/scratch0/scratch1), which the debug-entry and dret paths
* write directly. The base register file leaves these addresses
* undeclared, so without the views every debug-ROM csrrw raises
* illegal-instruction. Access is legal from debug mode only: the RTL
* decoder (not cv32e40p_cs_registers.sv, which decodes them at any
* time) rejects M-mode accesses with illegal-instruction. */
Cv32e40pDebugCsr dcsr_view; /* 0x7B0 */
Cv32e40pDebugCsr dpc_view; /* 0x7B1 */
Cv32e40pDebugCsr dscratch0_view; /* 0x7B2 */
Cv32e40pDebugCsr dscratch1_view; /* 0x7B3 */

/* User counter aliases: 0xC00/0xC02/0xC03..0xC1F and the H views at
* 0xC80/0xC82/0xC83..0xC9F. time (0xC01) is absent. */
Cv32e40pCounterAlias cycle_alias;
Cv32e40pCounterAlias instret_alias;
Cv32e40pCounterAlias hpmcounter_alias[29];
#if ISS_REG_WIDTH == 32
Cv32e40pCounterAlias cycleh_alias;
Cv32e40pCounterAlias instreth_alias;
Cv32e40pCounterAlias hpmcounterh_alias[29];
#endif

/* fflags / frm / fcsr (0x001..0x003). */
Cv32e40pFpCsr fflags_csr;
Cv32e40pFpCsr frm_csr;
Cv32e40pFpCsr fcsr_csr;

private:
bool fflags_access(iss_insn_t *insn, bool is_write, iss_reg_t &value);
bool frm_access(iss_insn_t *insn, bool is_write, iss_reg_t &value);
bool fcsr_access(iss_insn_t *insn, bool is_write, iss_reg_t &value);
bool hwloop_csr_access(iss_insn_t *insn, bool is_write, iss_reg_t &value, int index);
bool tselect_read_zero(iss_insn_t *insn, bool is_write, iss_reg_t &value);
bool tdata_debug_gate(iss_insn_t *insn, bool is_write, iss_reg_t &value);
bool mip_view_access(iss_insn_t *insn, bool is_write, iss_reg_t &value);
bool dcsr_view_access(iss_insn_t *insn, bool is_write, iss_reg_t &value);
bool dpc_view_access(iss_insn_t *insn, bool is_write, iss_reg_t &value);
bool dscratch0_view_access(iss_insn_t *insn, bool is_write, iss_reg_t &value);
bool dscratch1_view_access(iss_insn_t *insn, bool is_write, iss_reg_t &value);
bool mcycle_access(iss_insn_t *insn, bool is_write, iss_reg_t &value);
bool mcycleh_access(iss_insn_t *insn, bool is_write, iss_reg_t &value);
bool minstret_access(iss_insn_t *insn, bool is_write, iss_reg_t &value);
bool minstreth_access(iss_insn_t *insn, bool is_write, iss_reg_t &value);
bool cycle_alias_access(iss_insn_t *insn, bool is_write, iss_reg_t &value);
bool cycleh_alias_access(iss_insn_t *insn, bool is_write, iss_reg_t &value);
bool instret_alias_access(iss_insn_t *insn, bool is_write, iss_reg_t &value);
bool instreth_alias_access(iss_insn_t *insn, bool is_write, iss_reg_t &value);
bool hpm_alias_access(iss_insn_t *insn, bool is_write, iss_reg_t &value, int index);
bool hpmh_alias_access(iss_insn_t *insn, bool is_write, iss_reg_t &value, int index);
bool mcountinhibit_access(iss_insn_t *insn, bool is_write, iss_reg_t &value);
bool mstatus_read_fixup(iss_insn_t *insn, bool is_write, iss_reg_t &value);
bool mtvec_write_fixup(iss_insn_t *insn, bool is_write, iss_reg_t &value);

/* Current 64-bit mcycle count: the frozen register pair while
* mcountinhibit.CY is set, the offset clock otherwise. */
uint64_t mcycle_count();
void mcycle_set(uint64_t count);

int64_t mcycle_offset = 0;

/* Set by a CSR write to minstret/minstreth, consumed (and cleared) by
* hpm_commit at that same instruction's retire: the RTL suppresses the
* minstret increment on the cycle the counter is written
* (cv32e40p_cs_registers.sv, !write_lower && !write_upper gate), so
* the csrw itself must not count on top of the written value. */
bool minstret_written = false;

/* Armed by a CSR write to mcountinhibit, consumed (and cleared) by
* hpm_commit at that same instruction's retire: the RTL evaluates the
* increment gates on mcountinhibit_q in the cycle the write commits
* (cv32e40p_cs_registers.sv:1428), so the writing instruction is still
* gated by the OLD value and the write takes effect from the next
* instruction on. */
bool mcountinhibit_stale = false;
iss_reg_t mcountinhibit_old = 0;
};

inline bool Cv32e40pCsr::fp_access_illegal()
{
/* RTL (cv32e40p_cs_registers.sv:1110 + decoder): illegal when there is
* no FPU; gated on mstatus.FS only with the FPU registers in the ISA;
* always legal for ZFINX (no FS state, flags/rm still implemented). */
#if CONFIG_GVSOC_ISS_CV32E40P_FPU_IN_ISA
return this->mstatus.fs == 0;
#elif CONFIG_GVSOC_ISS_CV32E40P_ZFINX
return false;
#else
return true;
#endif
}

inline bool Cv32e40pCsr::hpm_counting()
{
/* CY excluded: mcycle is clock-derived and needs no full-handler
* support, only minstret and the event counters do. */
constexpr iss_reg_t event_bits = MCOUNTINHIBIT_MASK & ~(iss_reg_t)0x1;
return (this->mcountinhibit.value & event_bits) != event_bits;
}

inline void Cv32e40pCsr::hpm_commit(uint32_t events, bool count_instr)
{
/* An instruction writing mcountinhibit is gated by the pre-write
* value; both flags clear unconditionally: they belong to this
* retire only. */
iss_reg_t inhibit = this->mcountinhibit_stale ? this->mcountinhibit_old
: this->mcountinhibit.value;
this->mcountinhibit_stale = false;
/* minstret: retired instructions, gated on mcountinhibit.IR (bit 2),
* on the RTL event line (count_instr, false for EBREAK) and on the
* same-row write suppression. */
bool wrote_counter = this->minstret_written;
this->minstret_written = false;
if (count_instr && !wrote_counter && !(inhibit & 0x4))
{
if (++this->minstret.value == 0)
{
#if ISS_REG_WIDTH == 32
this->minstreth.value++;
#endif
}
}
/* mhpmcounterN advances at most +1 per retire when the mhpmeventN mask
* intersects the fired lines and its mcountinhibit bit is clear. */
for (int i = 0; i < CONFIG_GVSOC_ISS_CV32E40P_NUM_MHPMCOUNTERS; i++)
{
if ((this->mhpmevent[i].value & events)
&& !(inhibit & (1u << (3 + i))))
{
if (++this->mhpmcounter[i].value == 0)
{
#if ISS_REG_WIDTH == 32
this->mhpmcounterh[i].value++;
#endif
}
}
}
}
Loading