Skip to content

Add RV64 PMP with bug fixes and firmware test suite#50

Merged
eyck merged 7 commits into
Minres:mainfrom
cphurley82:rv64gc-pmp
May 14, 2026
Merged

Add RV64 PMP with bug fixes and firmware test suite#50
eyck merged 7 commits into
Minres:mainfrom
cphurley82:rv64gc-pmp

Conversation

@cphurley82
Copy link
Copy Markdown

@cphurley82 cphurley82 commented May 13, 2026

This PR enables Physical Memory Protection (PMP) on the RV64 target by introducing a new rv64gc_mp ISA variant, fixing bugs in the PMP implementation, adding a firmware test suite that validates each fix, and improving CI to reduce build time from ~1.5 hours to ~15 minutes.


Firmware test suite (contrib/fw/)

Six assembly test programs exercise the fixed code paths:

Test Validates
pmp-csr-test pmpaddr0 CSR accessible without illegal-instruction trap
pmp-enforce-test PMP denial fires a load-access fault on a protected region
pmp-shift-test Correct byte-shift in cfg extraction (entry 1 enforcement)
pmp-upper-cfg-test RV64 upper 32 bits of pmpcfg0 preserved across write
pmp-cfg2-test pmpcfg2 accessible and readable on RV64 (entries 8–15)
pmp-tor-test TOR mode denies access to address exactly at the upper boundary

All tests use a consistent semihosting exit pattern: an infinite j . loop signals success (exit 0); SYS_EXIT with code 2 signals failure.

CI changes (.github/workflows/ci.yml)

Replaced ci-compile.yml with a two-job workflow:

  • C++ compliance - Ubuntu 24.04, C++17 and C++20, system LLVM 19 via apt (eliminates Conan building LLVM, drops build time from ~1.5 h to ~15 min), uploads binaries as artifacts.
  • PMP functional tests - downloads artifacts, builds firmware with the RISC-V toolchain, runs all firmware tests.

Comment thread conanfile.py Outdated
self.requires("zlib/1.3.1")
self.requires("asmjit/cci.20240531")
if "WITH_LLVM" in os.environ:
if "WITH_LLVM" in os.environ and "WITH_SYSTEM_LLVM" not in os.environ:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since WITH_SYSTEM_LLVM is a CMake cached variable there is no nee to check it in conan.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The WITH_SYSTEM_LLVM check has been removed

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SInce vm_rv64gc.cpp is generated from a template, additions will be overwritten whenever the file is re-generated due to changes in the template or generation process. Pls. move the additions to a separate file.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. I moved to src/vm/interp/vm_rv64gc_mp.cpp. vm_rv64gc.cpp is back to its generated state.

cphurley82@gmail.com added 2 commits May 13, 2026 08:39
…to 15min

- Add WITH_SYSTEM_LLVM cmake option; set LLVM_DIR directly and use
  BYPASS_PROVIDER to skip conan_provider's version-check quirk
- Pin CI runner to ubuntu-24.04 for reproducibility
- Remove redundant apt-get update (package lists are fresh in image)
- Add workflow_dispatch trigger for manual CI runs
- ci.yml: split into two jobs. cpp-compliance (matrix
  C++17/20) handles pure build compliance and uploads the C++17
  binary as an artifact. pmp-tests downloads that binary,
  compiles firmware with only gcc-riscv64-unknown-elf, and runs
  the test directly. Trigger paths extended to include **.S for firmware
  changes.

- contrib/fw/pmp-csr-test/pmp_csr_test.S: firmware that writes
  0xDEAD to pmpaddr0, reads it back, and exits 0 on match (j .)
  or non-zero via semihosting SYS_EXIT on mismatch.
cphurley82@gmail.com added 2 commits May 13, 2026 09:19
vm_rv64gc_mp.cpp: rv64gc_mp_hart is a derived class that rewires the
memory chain in its constructor body. The base constructor sets
memory = default_mem via memories.append, but virtual dispatch during
base construction bypasses any derived override of set_next. The
constructor body runs after all members are initialized and explicitly
sets pmp_obj downstream to default_mem and memory to pmp_obj.get_mem_if().
Kept in a dedicated file separate from the generated vm_rv64gc.cpp so
the implementation survives template re-generation.

register_cores.cpp: register rv64gc_mp for the SystemC backend.

pmp.h: read_pmpcfg and write_pmpcfg both operated on pmpaddr[] instead
of pmpcfg[], so pmpcfg[] was never written and any_active was never set.
Fixed both to use pmpcfg[].

pmp_enforce_test.S: firmware test for entry 0 (pmpaddr0 + pmpcfg0
byte 0 = NA4|L). Passes when PMP denies a load to the protected
address; fails via semihosting SYS_EXIT if the load succeeds or any
CSR write traps.
pmp_shift_test.S: firmware test for entry 1 (pmpaddr1 + pmpcfg0
byte 1 = 0x9000). Passes when PMP denies a load to the protected
address; fails via semihosting SYS_EXIT if load succeeds.

pmp.h: the any_active loop in write_pmpcfg and the cfg extraction
in pmp_check both used (i % cfg_reg_size) as the shift amount,
treating the index as a bit offset rather than a byte index.
Changed both to (i % cfg_reg_size) * 8 so that each PMP entry's
config byte is correctly extracted from the packed register.
cphurley82@gmail.com added 3 commits May 13, 2026 10:58
On RV64 pmpcfg0 is a 64-bit register holding 8 config bytes (entries 0-7).
The hard-coded 32-bit mask 0x9f9f9f9f silently zeroed bytes 4-7 on every
write, making PMP entries 4-7 permanently unconfigurable.

Add pmp-upper-cfg-test firmware and CI step to guard the fix.
On RV64 only even-numbered pmpcfg CSRs exist (pmpcfg0 at 0x3A0,
pmpcfg2 at 0x3A2). The constructor loop incremented by 1, registering
pmpcfg0+pmpcfg1 instead of pmpcfg0+pmpcfg2, making entries 8-15
unreachable and pmpcfg1 incorrectly writable.

Add pmpcfg_stride (cfg_reg_size/4: 1 for RV32, 2 for RV64), step the
registration loop by it, and divide the CSR-address-to-array index by
it in read_pmpcfg/write_pmpcfg. Add pmp-cfg2-test firmware and CI step
to guard the fix.
The per-sector TOR match used `cur_addr + len - 1` for the lower-bound
test, where `len` is the total access length. This made the check too
loose: a sector lying entirely below `base` would pass as long as the
full access reached into the TOR region, causing `all_match=true` for
a partial-overlap access and incorrectly allowing it.

Replace with `base <= cur_addr`: TOR boundaries are 4-byte aligned so
the sector start alone determines whether the sector is inside the
range.

Add pmp-tor-test firmware and CI step to guard the fix.
@cphurley82 cphurley82 changed the title Rv64gc pmp Add RV64 PMP with bug fixes and firmware test suite May 13, 2026
@cphurley82 cphurley82 marked this pull request as ready for review May 13, 2026 22:37
Copy link
Copy Markdown
Contributor

@eyck eyck left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@eyck eyck merged commit 7a799ac into Minres:main May 14, 2026
3 checks passed
@eyck
Copy link
Copy Markdown
Contributor

eyck commented May 14, 2026

@cphurley82 While you are on it, could you extend the number of pmpcfg/pmpaddr CSRs to match Privileged Architecture Spec Version 20260120? It says:

The PMP configuration registers are densely packed into CSRs to minimize context-switch time. For RV32,
sixteen CSRs, pmpcfg0–pmpcfg15, hold the configurations pmp0cfg–pmp63cfg for the 64 PMP entries, as
shown in Figure 30. For RV64, eight even-numbered CSRs, pmpcfg0, pmpcfg2, …, pmpcfg14, hold the
configurations for the 64 PMP entries, as shown in Figure 31. For RV64, the odd-numbered configuration
registers, pmpcfg1, pmpcfg3, …, pmpcfg15, are illegal.

@cphurley82
Copy link
Copy Markdown
Author

@cphurley82 While you are on it, could you extend the number of pmpcfg/pmpaddr CSRs to match Privileged Architecture Spec Version 20260120? It says:

The PMP configuration registers are densely packed into CSRs to minimize context-switch time. For RV32,
sixteen CSRs, pmpcfg0–pmpcfg15, hold the configurations pmp0cfg–pmp63cfg for the 64 PMP entries, as
shown in Figure 30. For RV64, eight even-numbered CSRs, pmpcfg0, pmpcfg2, …, pmpcfg14, hold the
configurations for the 64 PMP entries, as shown in Figure 31. For RV64, the odd-numbered configuration
registers, pmpcfg1, pmpcfg3, …, pmpcfg15, are illegal.

@eyck , I'll see if I can knock this out real quick. The RV64 core I'm modeling only uses 8 PMP entries so I'll make it configurable.

I'm curious about the history of PMP on dbt-rise-riscv, was it used much that you know of or might we be the first ones to try?

@eyck
Copy link
Copy Markdown
Contributor

eyck commented May 15, 2026

We used it to verify our RTL implementation. But this is based on the RISC-V Privileged Architectures V20211203 and only for 32bit.
I'm not aware of any others using PMP.

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