Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
30 changes: 30 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,33 @@ jobs:

- name: rv64gc_mp TOR test - interp
run: LD_LIBRARY_PATH=. ./riscv-sim -f pmp_tor_test --isa rv64gc_mp --backend interp

- name: Build PMP 64-entry pmpaddr test firmware
run: |
riscv64-unknown-elf-gcc -nostdlib -march=rv64gc -mabi=lp64 \
-Wl,-Ttext=0x10000,--no-dynamic-linker \
-o pmp_64entry_addr_test \
contrib/fw/pmp-64entry-addr-test/pmp_64entry_addr_test.S

- name: rv64gc_mp 64-entry pmpaddr test - interp
run: LD_LIBRARY_PATH=. ./riscv-sim -f pmp_64entry_addr_test --isa rv64gc_mp --backend interp

- name: Build PMP 64-entry pmpcfg test firmware
run: |
riscv64-unknown-elf-gcc -nostdlib -march=rv64gc -mabi=lp64 \
-Wl,-Ttext=0x10000,--no-dynamic-linker \
-o pmp_64entry_cfg_test \
contrib/fw/pmp-64entry-cfg-test/pmp_64entry_cfg_test.S

- name: rv64gc_mp 64-entry pmpcfg test - interp
run: LD_LIBRARY_PATH=. ./riscv-sim -f pmp_64entry_cfg_test --isa rv64gc_mp --backend interp

- name: Build PMP 8-entry guard test firmware (VP/S5 model)
run: |
riscv64-unknown-elf-gcc -nostdlib -march=rv64gc -mabi=lp64 \
-Wl,-Ttext=0x10000,--no-dynamic-linker \
-o pmp_8entry_guard_test \
contrib/fw/pmp-8entry-guard-test/pmp_8entry_guard_test.S

- name: rv64gc_mp_8 8-entry enforcement test - interp (VP/S5 model)
run: LD_LIBRARY_PATH=. ./riscv-sim -f pmp_8entry_guard_test --isa rv64gc_mp_8 --backend interp
26 changes: 26 additions & 0 deletions contrib/fw/pmp-64entry-addr-test/pmp_64entry_addr_test.S
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Verify pmpaddr32 (CSR 0x3D0) is accessible - tests 64-entry pmpaddr support.
// Pass: readback matches written sentinel -> j . (exit 0)
// Fail: any trap (unregistered CSR) or readback mismatch -> semihosting SYS_EXIT (exit 2)

.section .text
.globl _start
_start:
la t0, fail
csrw mtvec, t0

li t0, 0xDEAD
csrw 0x3D0, t0 // pmpaddr32
csrr t1, 0x3D0
bne t0, t1, fail

j . // pass

fail:
li a0, 0x18
.option push
.option norvc
slli zero, zero, 0x1f
ebreak
srai zero, zero, 7
.option pop
j .
34 changes: 34 additions & 0 deletions contrib/fw/pmp-64entry-cfg-test/pmp_64entry_cfg_test.S
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Verify pmpcfg4 (CSR 0x3A4, holds entries 32-39 on RV64) is accessible and persistent.
// Tests 64-entry pmpcfg registration.
// Pass: write 0x9F -> read back 0x9F -> write 0 -> read back 0 -> j . (exit 0)
// Fail: any trap or readback mismatch -> semihosting SYS_EXIT (exit 2)

.section .text
.globl _start
_start:
la t0, fail
csrw mtvec, t0

// Write and verify a non-zero value
li t0, 0x9F
csrw 0x3A4, t0 // pmpcfg4 write
csrr t1, 0x3A4 // pmpcfg4 read
bne t0, t1, fail // must match

// Clear and verify zero
li t0, 0
csrw 0x3A4, t0
csrr t1, 0x3A4
bne t0, t1, fail

j . // pass

fail:
li a0, 0x18
.option push
.option norvc
slli zero, zero, 0x1f
ebreak
srai zero, zero, 7
.option pop
j .
50 changes: 50 additions & 0 deletions contrib/fw/pmp-8entry-guard-test/pmp_8entry_guard_test.S
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// VP guard test: verify PMP enforcement works with only 8 entries (S5 hardware limit).
// Configures entry 0 to deny load from test_word; expects load-access fault.
// Also verifies pmpaddr8+ traps (outside the 8-entry window).
//
// Pass: phase2_trap fires (load denied) -> j . (exit 0)
// Fail: CSR trap, load succeeds, or unexpected behavior -> semihosting SYS_EXIT (exit 2)

.macro semihosting_fail
li a0, 0x18
.option push
.option norvc
slli zero, zero, 0x1f
ebreak
srai zero, zero, 7
.option pop
j .
.endm

.section .text
.globl _start
_start:
// phase1_trap: any CSR trap = PMP not available
la t0, phase1_trap
csrw mtvec, t0

// Configure entry 0: NA4, no R/W/X, locked
la t5, test_word
srli t1, t5, 2
csrw pmpaddr0, t1

li t1, 0x90 // NA4(0x10) | L(0x80)
csrw pmpcfg0, t1

// phase2_trap: load fault = enforcement active = PASS
la t0, phase2_trap
csrw mtvec, t0

lw t1, 0(t5) // should fault: entry 0 denies read

semihosting_fail // no fault -> FAIL

phase1_trap:
semihosting_fail // CSR trap -> FAIL

phase2_trap:
j . // load denied as expected -> PASS

.align 2
test_word:
.word 0xCAFECAFE
29 changes: 16 additions & 13 deletions src/iss/mem/pmp.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
*
* Contributors:
* eyck@minres.com - initial implementation
* cphurley82@gmail.com - bug fixes and improvements
******************************************************************************/

#ifndef ISS_MEM_PMP_H
Expand All @@ -44,8 +45,10 @@
namespace iss {
namespace mem {

template <typename PLAT> struct pmp : public memory_elem {
using this_class = pmp<PLAT>;
template <typename PLAT, size_t NUM_ENTRIES = 64> struct pmp : public memory_elem {
static_assert(NUM_ENTRIES > 0 && NUM_ENTRIES <= 64 && (NUM_ENTRIES % sizeof(typename PLAT::reg_t)) == 0,
"NUM_ENTRIES must be a multiple of sizeof(reg_t) and at most 64");
using this_class = pmp<PLAT, NUM_ENTRIES>;
using reg_t = typename PLAT::reg_t;
static constexpr auto cfg_reg_size = sizeof(reg_t);
static constexpr reg_t cfg_valid_mask = sizeof(reg_t) == 8 ? reg_t(0x9f9f9f9f9f9f9f9fULL) : reg_t(0x9f9f9f9fU);
Expand All @@ -62,11 +65,11 @@ template <typename PLAT> struct pmp : public memory_elem {

pmp(arch::priv_if<reg_t> hart_if)
: hart_if(hart_if) {
for(size_t i = arch::pmpaddr0; i <= arch::pmpaddr15; ++i) {
for(size_t i = arch::pmpaddr0; i < arch::pmpaddr0 + NUM_ENTRIES; ++i) {
hart_if.csr_rd_cb[i] = MK_CSR_RD_CB(read_pmpaddr);
hart_if.csr_wr_cb[i] = MK_CSR_WR_CB(write_pmpaddr);
}
for(size_t i = arch::pmpcfg0; i < arch::pmpcfg0 + 4; i += pmpcfg_stride) {
for(size_t i = arch::pmpcfg0; i < arch::pmpcfg0 + (NUM_ENTRIES / cfg_reg_size) * pmpcfg_stride; i += pmpcfg_stride) {
hart_if.csr_rd_cb[i] = MK_CSR_RD_CB(read_pmpcfg);
hart_if.csr_wr_cb[i] = MK_CSR_WR_CB(write_pmpcfg);
}
Expand All @@ -82,8 +85,8 @@ template <typename PLAT> struct pmp : public memory_elem {
void set_next(memory_if mem) override { down_stream_mem = mem; }

private:
std::array<reg_t, 16> pmpaddr{0};
std::array<reg_t, 16 / sizeof(reg_t)> pmpcfg{0};
std::array<reg_t, NUM_ENTRIES> pmpaddr{0};
std::array<reg_t, NUM_ENTRIES / cfg_reg_size> pmpcfg{0};

iss::status read_mem(const addr_t& addr, unsigned length, uint8_t* data) {
assert((addr.type == iss::address_type::PHYSICAL || is_debug(addr.access)) && "Only physical addresses are expected in pmp");
Expand All @@ -109,33 +112,33 @@ template <typename PLAT> struct pmp : public memory_elem {
}

iss::status read_pmpaddr(unsigned addr, reg_t& val) {
if(addr >= arch::pmpaddr0 && addr <= arch::pmpaddr15) {
if(addr >= arch::pmpaddr0 && addr < arch::pmpaddr0 + NUM_ENTRIES) {
val = pmpaddr[addr - arch::pmpaddr0];
return iss::Ok;
}
return iss::Err;
}

iss::status write_pmpaddr(unsigned addr, reg_t const& val) {
if(addr >= arch::pmpaddr0 && addr <= arch::pmpaddr15) {
if(addr >= arch::pmpaddr0 && addr < arch::pmpaddr0 + NUM_ENTRIES) {
pmpaddr[addr - arch::pmpaddr0] = val;
return iss::Ok;
}
return iss::Err;
}

iss::status read_pmpcfg(unsigned addr, reg_t& val) {
if(addr >= arch::pmpcfg0 && addr < arch::pmpcfg0 + 4) {
if(addr >= arch::pmpcfg0 && addr < arch::pmpcfg0 + (NUM_ENTRIES / cfg_reg_size) * pmpcfg_stride) {
val = pmpcfg[(addr - arch::pmpcfg0) / pmpcfg_stride];
return iss::Ok;
}
return iss::Err;
}
iss::status write_pmpcfg(unsigned addr, reg_t val) {
if(addr >= arch::pmpcfg0 && addr < arch::pmpcfg0 + 4) {
if(addr >= arch::pmpcfg0 && addr < arch::pmpcfg0 + (NUM_ENTRIES / cfg_reg_size) * pmpcfg_stride) {
pmpcfg[(addr - arch::pmpcfg0) / pmpcfg_stride] = val & cfg_valid_mask;
any_active = false;
for(size_t i = 0; i < 16; i++) {
for(size_t i = 0; i < NUM_ENTRIES; i++) {
auto cfg = pmpcfg[i / cfg_reg_size] >> ((i % cfg_reg_size) * 8);
any_active |= cfg & PMP_A;
}
Expand All @@ -152,11 +155,11 @@ template <typename PLAT> struct pmp : public memory_elem {
memory_if down_stream_mem;
};

template <typename PLAT> bool pmp<PLAT>::pmp_check(access_type type, uint64_t addr, unsigned len) {
template <typename PLAT, size_t NUM_ENTRIES> bool pmp<PLAT, NUM_ENTRIES>::pmp_check(access_type type, uint64_t addr, unsigned len) {
if(!any_active)
return true;
reg_t base = 0;
for(size_t i = 0; i < 16; i++) {
for(size_t i = 0; i < NUM_ENTRIES; i++) {
reg_t tor = pmpaddr[i] << PMP_SHIFT;
reg_t cfg = pmpcfg[i / cfg_reg_size] >> ((i % cfg_reg_size) * 8);
if(cfg & PMP_A) {
Expand Down
9 changes: 8 additions & 1 deletion src/sysc/register_cores.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
namespace iss {
namespace interp {
using namespace sysc;
__attribute__((used)) volatile std::array<bool, 19> riscv_init = {
__attribute__((used)) volatile std::array<bool, 20> riscv_init = {
iss_factory::instance().register_creator("rv32i_m:interp",
[](unsigned gdb_port, sysc::riscv::core_complex_if* cc) -> iss_factory::base_t {
auto* cpu = new core2sc_adapter<arch::riscv_hart_m_p<arch::rv32i>>(cc);
Expand Down Expand Up @@ -132,6 +132,13 @@ __attribute__((used)) volatile std::array<bool, 19> riscv_init = {
std::make_unique<iss::mem::pmp<iss::arch::rv64gc>>(cpu->get_priv_if()));
return {sysc::core_ptr{cpu}, vm_ptr{create(static_cast<arch::rv64gc*>(cpu), gdb_port)}};
}),
iss_factory::instance().register_creator("rv64gc_mp_8:interp",
[](unsigned gdb_port, sysc::riscv::core_complex_if* cc) -> iss_factory::base_t {
auto* cpu = new core2sc_adapter<arch::riscv_hart_m_p<arch::rv64gc>>(cc);
cpu->memories.insert_before_last(
std::make_unique<iss::mem::pmp<iss::arch::rv64gc, 8>>(cpu->get_priv_if()));
return {sysc::core_ptr{cpu}, vm_ptr{create(static_cast<arch::rv64gc*>(cpu), gdb_port)}};
}),
iss_factory::instance().register_creator("rv64gc_mu:interp",
[](unsigned gdb_port, sysc::riscv::core_complex_if* cc) -> iss_factory::base_t {
auto* cpu = new core2sc_adapter<arch::riscv_hart_mu_p<arch::rv64gc>>(cc);
Expand Down
21 changes: 20 additions & 1 deletion src/vm/interp/vm_rv64gc_mp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,19 @@ struct rv64gc_mp_hart : public arch::riscv_hart_m_p<arch::rv64gc> {
}
};

// rv64gc_mp_8_hart: same as rv64gc_mp but with only 8 PMP entries (models SiFive S5).
struct rv64gc_mp_8_hart : public arch::riscv_hart_m_p<arch::rv64gc> {
mem::pmp<arch::rv64gc, 8> pmp_obj{this->get_priv_if()};

rv64gc_mp_8_hart() {
pmp_obj.set_next(this->default_mem.get_mem_if());
memory = pmp_obj.get_mem_if();
}
};

namespace {

volatile std::array<bool, 1> rv64gc_mp_dummy = {
volatile std::array<bool, 2> rv64gc_mp_dummy = {
core_factory::instance().register_creator("rv64gc_mp:interp",
[](unsigned port, void* init_data) -> std::tuple<cpu_ptr, vm_ptr> {
auto* cpu = new rv64gc_mp_hart();
Expand All @@ -35,6 +45,15 @@ volatile std::array<bool, 1> rv64gc_mp_dummy = {
cpu->set_semihosting_callback(*cb);
}
return {cpu_ptr{cpu}, vm_ptr{iss::interp::create<arch::rv64gc>(cpu, port, false)}};
}),
core_factory::instance().register_creator("rv64gc_mp_8:interp",
[](unsigned port, void* init_data) -> std::tuple<cpu_ptr, vm_ptr> {
auto* cpu = new rv64gc_mp_8_hart();
if (init_data) {
auto* cb = reinterpret_cast<semihosting_cb_t<arch::traits<arch::rv64gc>::reg_t>*>(init_data);
cpu->set_semihosting_callback(*cb);
}
return {cpu_ptr{cpu}, vm_ptr{iss::interp::create<arch::rv64gc>(cpu, port, false)}};
})
};

Expand Down
Loading