From bbbcd98e72faa894f4baf79e0fe9168a15755f35 Mon Sep 17 00:00:00 2001 From: Jakub Dupak Date: Sun, 19 Oct 2025 15:18:24 +0200 Subject: [PATCH 01/12] Fix deprecation warnings for custom literals --- src/machine/csr/address.h | 2 +- src/machine/memory/address.h | 2 +- src/machine/registers.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/machine/csr/address.h b/src/machine/csr/address.h index 41660b13..17c5f93f 100644 --- a/src/machine/csr/address.h +++ b/src/machine/csr/address.h @@ -55,7 +55,7 @@ namespace machine { namespace CSR { bool operator!=(const Address &rhs) const { return data != rhs.data; } }; - constexpr Address operator"" _csr(unsigned long long literal) { + constexpr Address operator""_csr(unsigned long long literal) { return Address(literal); } }} // namespace machine::CSR diff --git a/src/machine/memory/address.h b/src/machine/memory/address.h index fe9fb293..a6bf81b2 100644 --- a/src/machine/memory/address.h +++ b/src/machine/memory/address.h @@ -108,7 +108,7 @@ class Address { constexpr inline int64_t operator-(const Address &other) const; }; -constexpr Address operator"" _addr(unsigned long long literal) { +constexpr Address operator""_addr(unsigned long long literal) { return Address(literal); } diff --git a/src/machine/registers.h b/src/machine/registers.h index 2b7b5a02..67bdd586 100644 --- a/src/machine/registers.h +++ b/src/machine/registers.h @@ -42,7 +42,7 @@ inline constexpr RegisterId::RegisterId(uint8_t value) : data(value) { } inline RegisterId::RegisterId() : RegisterId(0) {} -inline RegisterId operator"" _reg(unsigned long long value) { +inline RegisterId operator""_reg(unsigned long long value) { return { static_cast(value) }; } From 028f6aa01cbb1ae4f657297ad7ba458c9511209e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20N=C4=9Bmec?= Date: Sun, 5 Oct 2025 18:14:57 +0200 Subject: [PATCH 02/12] GUI: fix zoom scaling issue This resolves the incorrect zoom behavior. --- src/gui/graphicsview.cpp | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/gui/graphicsview.cpp b/src/gui/graphicsview.cpp index ab1ed103..bfae3442 100644 --- a/src/gui/graphicsview.cpp +++ b/src/gui/graphicsview.cpp @@ -28,14 +28,9 @@ void GraphicsView::update_scale() { prev_height = width(); prev_width = height(); - qreal scale = 1; - if (height() > h && width() > w) { - if (height() > width()) { - scale = (qreal)width() / w; - } else { - scale = (qreal)height() / h; - } - } + qreal scale_x = (qreal)width() / w; + qreal scale_y = (qreal)height() / h; + qreal scale = qMin(scale_x, scale_y); QTransform t; t.scale(scale, scale); setTransform(t, false); From a963bc381a11dfa953e7b0da6d5aa75a2c6388fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20N=C4=9Bmec?= Date: Wed, 3 Sep 2025 16:58:46 +0200 Subject: [PATCH 03/12] Machine: store current privilege level in CoreState Add tracking of the hart's current privilege level to the core state so code handling exceptions/returns and visualization can read/update it from the central CoreState structure. --- src/machine/core.cpp | 15 +++++++++++++-- src/machine/core.h | 2 ++ src/machine/core/core_state.h | 1 + 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/machine/core.cpp b/src/machine/core.cpp index 42a43936..d6d7f966 100644 --- a/src/machine/core.cpp +++ b/src/machine/core.cpp @@ -57,6 +57,7 @@ void Core::reset() { state.cycle_count = 0; state.stall_count = 0; do_reset(); + set_current_privilege(CSR::PrivilegeLevel::MACHINE); } unsigned Core::get_cycle_count() const { @@ -124,6 +125,14 @@ Xlen Core::get_xlen() const { return xlen; } +void Core::set_current_privilege(CSR::PrivilegeLevel privilege) { + state.current_privilege = privilege; +} + +CSR::PrivilegeLevel Core::get_current_privilege() const { + return state.current_privilege; +}; + void Core::register_exception_handler(ExceptionCause excause, ExceptionHandler *exhandler) { if (excause == EXCAUSE_NONE) { ex_default_handler.reset(exhandler); @@ -155,7 +164,8 @@ bool Core::handle_exception( if (control_state->read_internal(CSR::Id::MTVEC) != 0 && !get_step_over_exception(excause)) { control_state->exception_initiate( - CSR::PrivilegeLevel::MACHINE, CSR::PrivilegeLevel::MACHINE); + get_current_privilege(), CSR::PrivilegeLevel::MACHINE); + set_current_privilege(CSR::PrivilegeLevel::MACHINE); regs->write_pc(control_state->exception_pc_address()); } } @@ -517,7 +527,8 @@ MemoryState Core::memory(const ExecuteInterstage &dt) { csr_written = true; } if (dt.xret) { - control_state->exception_return(CSR::PrivilegeLevel::MACHINE); + CSR::PrivilegeLevel restored = control_state->exception_return(get_current_privilege()); + set_current_privilege(restored); if (this->xlen == Xlen::_32) computed_next_inst_addr = Address(control_state->read_internal(CSR::Id::MEPC).as_u32()); diff --git a/src/machine/core.h b/src/machine/core.h index 3f2f1855..3da42197 100644 --- a/src/machine/core.h +++ b/src/machine/core.h @@ -58,6 +58,8 @@ class Core : public QObject { bool get_stop_on_exception(enum ExceptionCause excause) const; void set_step_over_exception(enum ExceptionCause excause, bool value); bool get_step_over_exception(enum ExceptionCause excause) const; + void set_current_privilege(CSR::PrivilegeLevel privilege); + CSR::PrivilegeLevel get_current_privilege() const; /** * Abstracts XLEN from code flow. XLEN core will obtain XLEN value from register value. diff --git a/src/machine/core/core_state.h b/src/machine/core/core_state.h index 8c65887a..887c9649 100644 --- a/src/machine/core/core_state.h +++ b/src/machine/core/core_state.h @@ -18,6 +18,7 @@ struct CoreState { AddressRange LoadReservedRange; uint32_t stall_count = 0; uint32_t cycle_count = 0; + CSR::PrivilegeLevel current_privilege = CSR::PrivilegeLevel::MACHINE; }; } // namespace machine From 97df4e43eebd24e0e6310f475d87af0ef71993ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20N=C4=9Bmec?= Date: Wed, 3 Sep 2025 17:25:02 +0200 Subject: [PATCH 04/12] Machine: add supervisor CSRs and sstatus handling The next supervisor CSRs has been added: sstatus, stvec, sscratch, sepc, scause, stval, satp Write handler has been added as well. It presents sstatus as a masked view of mstatus so supervisor-visible bits stay in sync. --- src/machine/csr/controlstate.cpp | 64 ++++++++++++-- src/machine/csr/controlstate.h | 145 +++++++++++++++++++------------ tests/cli/stalls/stdout.txt | 2 +- 3 files changed, 146 insertions(+), 65 deletions(-) diff --git a/src/machine/csr/controlstate.cpp b/src/machine/csr/controlstate.cpp index e3cc8cf6..491deaa0 100644 --- a/src/machine/csr/controlstate.cpp +++ b/src/machine/csr/controlstate.cpp @@ -104,6 +104,27 @@ namespace machine { namespace CSR { write_signal(Id::CYCLE, register_data[Id::CYCLE]); } + void ControlState::sstatus_wlrl_write_handler( + const RegisterDesc &desc, + RegisterValue ®, + RegisterValue val) { + uint64_t s_mask + = Field::mstatus::SIE.mask() | Field::mstatus::SPIE.mask() | Field::mstatus::SPP.mask(); + + if (xlen == Xlen::_64) { + s_mask |= Field::mstatus::UXL.mask(); + s_mask |= Field::mstatus::SXL.mask(); + } + uint64_t write_val = val.as_u64() & desc.write_mask.as_u64(); + uint64_t mstatus_val = register_data[Id::MSTATUS].as_u64(); + mstatus_val = (mstatus_val & ~s_mask) | (write_val & s_mask); + register_data[Id::MSTATUS] = mstatus_val; + uint64_t new_sstatus = mstatus_val & s_mask; + if (xlen == Xlen::_32) new_sstatus &= 0xffffffff; + reg = new_sstatus; + emit write_signal(Id::MSTATUS, register_data[Id::MSTATUS]); + } + bool ControlState::operator==(const ControlState &other) const { return register_data == other.register_data; } @@ -172,14 +193,41 @@ namespace machine { namespace CSR { PrivilegeLevel ControlState::exception_return(enum PrivilegeLevel act_privlev) { size_t reg_id = Id::MSTATUS; RegisterValue ® = register_data[reg_id]; - PrivilegeLevel restored_privlev; - Q_UNUSED(act_privlev) - - write_field(Field::mstatus::MIE, read_field(Field::mstatus::MPIE).as_u32()); - write_field(Field::mstatus::MPIE, (uint64_t)1); - - restored_privlev = static_cast(read_field(Field::mstatus::MPP).as_u32()); - write_field(Field::mstatus::MPP, (uint64_t)0); + PrivilegeLevel restored_privlev = PrivilegeLevel::MACHINE; + if (act_privlev == PrivilegeLevel::MACHINE) { + // MRET semantics: + // MIE <- MPIE + // MPIE <- 1 + // restored_privlev <- MPP + // MPP <- 0 + write_field(Field::mstatus::MIE, read_field(Field::mstatus::MPIE).as_u32()); + write_field(Field::mstatus::MPIE, (uint64_t)1); + uint32_t raw_mpp + = static_cast(read_field(Field::mstatus::MPP).as_u32()) & 0x3; + switch (raw_mpp) { + case 0: restored_privlev = PrivilegeLevel::UNPRIVILEGED; break; + case 1: restored_privlev = PrivilegeLevel::SUPERVISOR; break; + case 2: restored_privlev = PrivilegeLevel::HYPERVISOR; break; + case 3: restored_privlev = PrivilegeLevel::MACHINE; break; + default: restored_privlev = PrivilegeLevel::UNPRIVILEGED; break; + } + write_field(Field::mstatus::MPP, (uint64_t)0); // clear MPP per spec + } else if (act_privlev == PrivilegeLevel::SUPERVISOR) { + // SRET semantics: + // SIE <- SPIE + // SPIE <- 1 + // restored_privlev <- SPP + // SPP <- 0 + write_field(Field::mstatus::SIE, read_field(Field::mstatus::SPIE).as_u32()); + write_field(Field::mstatus::SPIE, (uint64_t)1); + uint32_t raw_spp + = static_cast(read_field(Field::mstatus::SPP).as_u32()) & 0x1; + restored_privlev + = (raw_spp == 1) ? PrivilegeLevel::SUPERVISOR : PrivilegeLevel::UNPRIVILEGED; + write_field(Field::mstatus::SPP, (uint64_t)0); + } else { + restored_privlev = PrivilegeLevel::UNPRIVILEGED; + } emit write_signal(reg_id, reg); diff --git a/src/machine/csr/controlstate.h b/src/machine/csr/controlstate.h index 9a045579..f2443678 100644 --- a/src/machine/csr/controlstate.h +++ b/src/machine/csr/controlstate.h @@ -1,13 +1,13 @@ #ifndef CONTROLSTATE_H #define CONTROLSTATE_H -#include "bitfield.h" #include "common/math/bit_ops.h" -#include "config_isa.h" #include "csr/address.h" #include "machinedefs.h" #include "register_value.h" #include "simulator_exception.h" +#include "bitfield.h" +#include "config_isa.h" #include #include @@ -17,7 +17,7 @@ namespace machine { namespace CSR { /** CSR register names mapping the registers to continuous locations in internal buffer */ struct Id { - enum IdxType { + enum IdxType{ // Unprivileged Counter/Timers CYCLE, // Machine Information Registers @@ -46,16 +46,35 @@ namespace machine { namespace CSR { // ... MCYCLE, MINSTRET, - _COUNT, + // Supervisor Trap Setup + SSTATUS, + // ... + STVEC, + // ... + // Supervisor Trap Handling + SSCRATCH, + SEPC, + SCAUSE, + STVAL, + // ... + // Supervisor Protection and Translation + SATP, + _COUNT }; }; struct RegisterDesc; struct RegisterFieldDesc { - uint64_t decode(uint64_t val) const { return field.decode(val); } - uint64_t encode(uint64_t val) const { return field.encode(val); } - uint64_t mask() const { return field.mask(); } + uint64_t decode(uint64_t val) const { + return field.decode(val); + } + uint64_t encode(uint64_t val) const { + return field.encode(val); + } + uint64_t mask() const { + return field.mask(); + } uint64_t update(uint64_t orig, uint64_t val) const { return field.encode(val) | (orig & ~mask()); } @@ -152,12 +171,22 @@ namespace machine { namespace CSR { std::array register_data; public: - void - default_wlrl_write_handler(const RegisterDesc &desc, RegisterValue ®, RegisterValue val); - void - mstatus_wlrl_write_handler(const RegisterDesc &desc, RegisterValue ®, RegisterValue val); - void - mcycle_wlrl_write_handler(const RegisterDesc &desc, RegisterValue ®, RegisterValue val); + void default_wlrl_write_handler( + const RegisterDesc &desc, + RegisterValue ®, + RegisterValue val); + void mstatus_wlrl_write_handler( + const RegisterDesc &desc, + RegisterValue ®, + RegisterValue val); + void mcycle_wlrl_write_handler( + const RegisterDesc &desc, + RegisterValue ®, + RegisterValue val); + void sstatus_wlrl_write_handler( + const RegisterDesc &desc, + RegisterValue ®, + RegisterValue val); }; struct RegisterDesc { @@ -171,64 +200,69 @@ namespace machine { namespace CSR { RegisterValue write_mask = (register_storage_t)0xffffffffffffffff; WriteHandlerFn write_handler = &ControlState::default_wlrl_write_handler; struct { - const RegisterFieldDesc *const *array; + const RegisterFieldDesc * const *array; const unsigned count; - } fields = { nullptr, 0 }; + } fields = {nullptr, 0}; }; - namespace Field { namespace mstatus { - static constexpr RegisterFieldDesc SIE - = { "SIE", Id::MSTATUS, { 1, 1 }, "System global interrupt-enable" }; - static constexpr RegisterFieldDesc MIE - = { "MIE", Id::MSTATUS, { 1, 3 }, "Machine global interrupt-enable" }; - static constexpr RegisterFieldDesc SPIE - = { "SPIE", Id::MSTATUS, { 1, 5 }, "Previous SIE before the trap" }; - static constexpr RegisterFieldDesc MPIE - = { "MPIE", Id::MSTATUS, { 1, 7 }, "Previous MIE before the trap" }; - static constexpr RegisterFieldDesc SPP - = { "SPP", Id::MSTATUS, { 1, 8 }, "System previous privilege mode" }; - static constexpr RegisterFieldDesc MPP - = { "MPP", Id::MSTATUS, { 2, 11 }, "Machine previous privilege mode" }; - static constexpr RegisterFieldDesc UXL - = { "UXL", Id::MSTATUS, { 2, 32 }, "User mode XLEN (RV64 only)" }; - static constexpr RegisterFieldDesc SXL - = { "SXL", Id::MSTATUS, { 2, 34 }, "Supervisor mode XLEN (RV64 only)" }; - static constexpr const RegisterFieldDesc *fields[] - = { &SIE, &MIE, &SPIE, &MPIE, &SPP, &MPP, &UXL, &SXL }; - static constexpr unsigned count = sizeof(fields) / sizeof(fields[0]); - }} // namespace Field::mstatus + namespace Field { + namespace mstatus { + static constexpr RegisterFieldDesc SIE = { "SIE", Id::MSTATUS, {1, 1}, "System global interrupt-enable"}; + static constexpr RegisterFieldDesc MIE = { "MIE", Id::MSTATUS, {1, 3}, "Machine global interrupt-enable"}; + static constexpr RegisterFieldDesc SPIE = { "SPIE", Id::MSTATUS, {1, 5}, "Previous SIE before the trap"}; + static constexpr RegisterFieldDesc MPIE = { "MPIE", Id::MSTATUS, {1, 7}, "Previous MIE before the trap"}; + static constexpr RegisterFieldDesc SPP = { "SPP", Id::MSTATUS, {1, 8}, "System previous privilege mode"}; + static constexpr RegisterFieldDesc MPP = { "MPP", Id::MSTATUS, {2, 11}, "Machine previous privilege mode"}; + static constexpr RegisterFieldDesc UXL = { "UXL", Id::MSTATUS, {2, 32}, "User mode XLEN (RV64 only)"}; + static constexpr RegisterFieldDesc SXL = { "SXL", Id::MSTATUS, {2, 34}, "Supervisor mode XLEN (RV64 only)"}; + static constexpr const RegisterFieldDesc *fields[] = { &SIE, &MIE, &SPIE, &MPIE, &SPP, &MPP, &UXL, &SXL}; + static constexpr unsigned count = sizeof(fields) / sizeof(fields[0]); + } + namespace satp { + static constexpr RegisterFieldDesc MODE = { "MODE", Id::SATP, {1, 31}, "Address translation mode" }; + static constexpr RegisterFieldDesc ASID = { "ASID", Id::SATP, {9, 22}, "Address-space ID" }; + static constexpr RegisterFieldDesc PPN = { "PPN", Id::SATP, {22, 0}, "Root page-table physical page number" }; + static constexpr const RegisterFieldDesc *fields[] = { &MODE, &ASID, &PPN }; + static constexpr unsigned count = sizeof(fields)/sizeof(fields[0]); + } + } /** Definitions of supported CSR registers */ inline constexpr std::array REGISTERS { { // Unprivileged Counter/Timers - [Id::CYCLE] = { "cycle", 0xC00_csr, "Cycle counter for RDCYCLE instruction.", 0, 0 }, + [Id::CYCLE] = { "cycle", 0xC00_csr, "Cycle counter for RDCYCLE instruction.", 0, 0}, // Priviledged Machine Mode Registers - [Id::MVENDORID] = { "mvendorid", 0xF11_csr, "Vendor ID.", 0, 0 }, - [Id::MARCHID] = { "marchid", 0xF12_csr, "Architecture ID.", 0, 0 }, - [Id::MIMPID] = { "mimpid", 0xF13_csr, "Implementation ID.", 0, 0 }, + [Id::MVENDORID] = { "mvendorid", 0xF11_csr, "Vendor ID.", 0, 0}, + [Id::MARCHID] = { "marchid", 0xF12_csr, "Architecture ID.", 0, 0}, + [Id::MIMPID] = { "mimpid", 0xF13_csr, "Implementation ID.", 0, 0}, [Id::MHARTID] = { "mhardid", 0xF14_csr, "Hardware thread ID." }, - [Id::MSTATUS] = { "mstatus", - 0x300_csr, - "Machine status register.", - 0, - 0x007FFFEA, - &ControlState::mstatus_wlrl_write_handler, - { Field::mstatus::fields, Field::mstatus::count } }, - [Id::MISA] = { "misa", 0x301_csr, "Machine ISA Register.", 0, 0 }, - [Id::MIE] = { "mie", 0x304_csr, "Machine interrupt-enable register.", 0, 0x00ff0AAA }, - [Id::MTVEC] = { "mtvec", 0x305_csr, "Machine trap-handler base address." }, + [Id::MSTATUS] = { "mstatus", 0x300_csr, "Machine status register.", + 0, 0x007FFFEA, &ControlState::mstatus_wlrl_write_handler, + {Field::mstatus::fields, Field::mstatus::count} }, + [Id::MISA] = { "misa", 0x301_csr, "Machine ISA Register.", 0, 0}, + [Id::MIE] = { "mie", 0x304_csr, "Machine interrupt-enable register.", + 0, 0x00ff0AAA}, + [Id::MTVEC] = { "mtvec", 0x305_csr, "Machine trap-handler base address."}, [Id::MSCRATCH] = { "mscratch", 0x340_csr, "Scratch register for machine trap handlers." }, [Id::MEPC] = { "mepc", 0x341_csr, "Machine exception program counter." }, [Id::MCAUSE] = { "mcause", 0x342_csr, "Machine trap cause." }, [Id::MTVAL] = { "mtval", 0x343_csr, "Machine bad address or instruction." }, - [Id::MIP] = { "mip", 0x344_csr, "Machine interrupt pending.", 0, 0x00000222 }, + [Id::MIP] = { "mip", 0x344_csr, "Machine interrupt pending.", + 0, 0x00000222}, [Id::MTINST] = { "mtinst", 0x34A_csr, "Machine trap instruction (transformed)." }, [Id::MTVAL2] = { "mtval2", 0x34B_csr, "Machine bad guest physical address." }, // Machine Counter/Timers - [Id::MCYCLE] - = { "mcycle", 0xB00_csr, "Machine cycle counter.", 0, - (register_storage_t)0xffffffffffffffff, &ControlState::mcycle_wlrl_write_handler }, - [Id::MINSTRET] = { "minstret", 0xB02_csr, "Machine instructions-retired counter." }, + [Id::MCYCLE] = { "mcycle", 0xB00_csr, "Machine cycle counter.", + 0, (register_storage_t)0xffffffffffffffff, &ControlState::mcycle_wlrl_write_handler}, + [Id::MINSTRET] = { "minstret", 0xB02_csr, "Machine instructions-retired counter."}, + // Supervisor-level CSRs + [Id::SSTATUS] = { "sstatus", 0x100_csr, "Supervisor status register.", 0, 0xffffffff, &ControlState::sstatus_wlrl_write_handler }, + [Id::STVEC] = { "stvec", 0x105_csr, "Supervisor trap-handler base address." }, + [Id::SSCRATCH] = { "sscratch", 0x140_csr, "Scratch register for supervisor trap handlers." }, + [Id::SEPC] = { "sepc", 0x141_csr, "Supervisor exception program counter." }, + [Id::SCAUSE] = { "scause", 0x142_csr, "Supervisor trap cause." }, + [Id::STVAL] = { "stval", 0x143_csr, "Supervisor bad address or instruction." }, + [Id::SATP] = { "satp", 0x180_csr, "Supervisor address translation and protection", 0, 0xffffffff, &ControlState::default_wlrl_write_handler, { Field::satp::fields, Field::satp::count } } } }; /** Lookup from CSR address (value used in instruction) to internal id (index in continuous @@ -261,7 +295,6 @@ namespace machine { namespace CSR { } initialized = true; } - public: size_t at(std::string name) { if (!initialized) init(); diff --git a/tests/cli/stalls/stdout.txt b/tests/cli/stalls/stdout.txt index d63a1a66..4019e4ab 100644 --- a/tests/cli/stalls/stdout.txt +++ b/tests/cli/stalls/stdout.txt @@ -2,4 +2,4 @@ Machine stopped on BREAK exception. Machine state report: PC:0x00000244 R0:0x00000000 R1:0x00000011 R2:0x00000022 R3:0x00000033 R4:0x00000000 R5:0x00000055 R6:0x00000000 R7:0x00000000 R8:0x00000000 R9:0x00000000 R10:0x00000000 R11:0x00000000 R12:0x00000000 R13:0x00000000 R14:0x00000000 R15:0x00000000 R16:0x00000000 R17:0x00000000 R18:0x00000000 R19:0x00000000 R20:0x00000000 R21:0x00000011 R22:0x00000022 R23:0x00000033 R24:0x00000044 R25:0x00000055 R26:0x00000000 R27:0x00000000 R28:0x00000000 R29:0x00000000 R30:0x00000000 R31:0x00000000 -cycle: 0x0000000c mvendorid: 0x00000000 marchid: 0x00000000 mimpid: 0x00000000 mhardid: 0x00000000 mstatus: 0x00000000 misa: 0x40001111 mie: 0x00000000 mtvec: 0x00000000 mscratch: 0x00000000 mepc: 0x00000240 mcause: 0x00000003 mtval: 0x00000000 mip: 0x00000000 mtinst: 0x00000000 mtval2: 0x00000000 mcycle: 0x0000000c minstret: 0x0000000b +cycle: 0x0000000c mvendorid: 0x00000000 marchid: 0x00000000 mimpid: 0x00000000 mhardid: 0x00000000 mstatus: 0x00000000 misa: 0x40001111 mie: 0x00000000 mtvec: 0x00000000 mscratch: 0x00000000 mepc: 0x00000240 mcause: 0x00000003 mtval: 0x00000000 mip: 0x00000000 mtinst: 0x00000000 mtval2: 0x00000000 mcycle: 0x0000000c minstret: 0x0000000b sstatus: 0x00000000 stvec: 0x00000000 sscratch: 0x00000000 sepc: 0x00000000 scause: 0x00000000 stval: 0x00000000 satp: 0x00000000 From ff9542fb4020a99e4366c3b34eeb19276261a278 Mon Sep 17 00:00:00 2001 From: Jakub Dupak Date: Sun, 19 Oct 2025 18:10:37 +0200 Subject: [PATCH 05/12] Machine: add TLB with policies and add SV32 page-table walker Implements a set-associative Translation Lookaside Buffer (TLB) with replacement policies, Page-Table Walker, and adds SV32-specific definitions. --- src/machine/CMakeLists.txt | 34 ++- src/machine/machine.cpp | 39 ++- src/machine/machine.h | 9 + src/machine/machineconfig.cpp | 153 +++++++++- src/machine/machineconfig.h | 55 ++++ src/machine/memory/frontend_memory.cpp | 3 +- src/machine/memory/tlb/tlb.cpp | 267 ++++++++++++++++++ src/machine/memory/tlb/tlb.h | 129 +++++++++ src/machine/memory/tlb/tlb_policy.cpp | 108 +++++++ src/machine/memory/tlb/tlb_policy.h | 72 +++++ .../memory/virtual/page_table_walker.cpp | 54 ++++ .../memory/virtual/page_table_walker.h | 26 ++ src/machine/memory/virtual/sv32.h | 139 +++++++++ src/machine/memory/virtual/virtual_address.h | 174 ++++++++++++ src/machine/simulator_exception.h | 12 + 15 files changed, 1263 insertions(+), 11 deletions(-) create mode 100644 src/machine/memory/tlb/tlb.cpp create mode 100644 src/machine/memory/tlb/tlb.h create mode 100644 src/machine/memory/tlb/tlb_policy.cpp create mode 100644 src/machine/memory/tlb/tlb_policy.h create mode 100644 src/machine/memory/virtual/page_table_walker.cpp create mode 100644 src/machine/memory/virtual/page_table_walker.h create mode 100644 src/machine/memory/virtual/sv32.h create mode 100644 src/machine/memory/virtual/virtual_address.h diff --git a/src/machine/CMakeLists.txt b/src/machine/CMakeLists.txt index f3cca553..48066b50 100644 --- a/src/machine/CMakeLists.txt +++ b/src/machine/CMakeLists.txt @@ -22,6 +22,9 @@ set(machine_SOURCES memory/cache/cache_policy.cpp memory/frontend_memory.cpp memory/memory_bus.cpp + memory/tlb/tlb.cpp + memory/tlb/tlb_policy.cpp + memory/virtual/page_table_walker.cpp programloader.cpp predictor.cpp registers.cpp @@ -68,6 +71,11 @@ set(machine_HEADERS utils.h execute/alu_op.h execute/mul_op.h + memory/virtual/virtual_address.h + memory/tlb/tlb.h + memory/virtual/sv32.h + memory/tlb/tlb_policy.h + memory/virtual/page_table_walker.h ) # Object library is preferred, because the library archive is never really @@ -79,7 +87,7 @@ target_link_libraries(machine PRIVATE ${QtLib}::Core PUBLIC libelf) -if(NOT ${WASM}) +if (NOT ${WASM}) # Machine tests (not available on WASM) add_executable(alu_test @@ -87,7 +95,7 @@ if(NOT ${WASM}) execute/alu.test.h execute/alu.cpp execute/alu.h - ) + ) target_link_libraries(alu_test PRIVATE ${QtLib}::Core ${QtLib}::Test) add_test(NAME alu COMMAND alu_test) @@ -100,12 +108,14 @@ if(NOT ${WASM}) registers.test.h simulator_exception.cpp simulator_exception.h - ) + ) target_link_libraries(registers_test PRIVATE ${QtLib}::Core ${QtLib}::Test) add_test(NAME registers COMMAND registers_test) add_executable(memory_test + machineconfig.cpp + machineconfig.h memory/backend/backend_memory.h memory/backend/memory.cpp memory/backend/memory.h @@ -113,6 +123,12 @@ if(NOT ${WASM}) memory/backend/memory.test.h memory/frontend_memory.cpp memory/frontend_memory.h + memory/tlb/tlb.h + memory/tlb/tlb.cpp + memory/tlb/tlb_policy.h + memory/tlb/tlb_policy.cpp + memory/virtual/page_table_walker.h + memory/virtual/page_table_walker.cpp memory/memory_bus.cpp memory/memory_bus.h simulator_exception.cpp @@ -138,6 +154,12 @@ if(NOT ${WASM}) memory/cache/cache_policy.h memory/frontend_memory.cpp memory/frontend_memory.h + memory/tlb/tlb.h + memory/tlb/tlb.cpp + memory/tlb/tlb_policy.h + memory/tlb/tlb_policy.cpp + memory/virtual/page_table_walker.h + memory/virtual/page_table_walker.cpp memory/memory_bus.cpp memory/memory_bus.h simulator_exception.cpp @@ -158,7 +180,7 @@ if(NOT ${WASM}) instruction.test.h simulator_exception.cpp simulator_exception.h - ) + ) target_link_libraries(instruction_test PRIVATE ${QtLib}::Core ${QtLib}::Test) add_test(NAME instruction COMMAND instruction_test) @@ -179,7 +201,7 @@ if(NOT ${WASM}) simulator_exception.h symboltable.cpp symboltable.h - ) + ) target_link_libraries(program_loader_test PRIVATE ${QtLib}::Core ${QtLib}::Test libelf) add_test(NAME program_loader COMMAND program_loader_test) @@ -222,4 +244,4 @@ if(NOT ${WASM}) add_custom_target(machine_unit_tests DEPENDS alu_test registers_test memory_test cache_test instruction_test program_loader_test core_test) -endif() +endif () diff --git a/src/machine/machine.cpp b/src/machine/machine.cpp index 7c34c435..17bf015f 100644 --- a/src/machine/machine.cpp +++ b/src/machine/machine.cpp @@ -12,7 +12,6 @@ Machine::Machine(MachineConfig config, bool load_symtab, bool load_executable) : machine_config(std::move(config)) , stat(ST_READY) { regs = new Registers(); - if (load_executable) { ProgramLoader program(machine_config.elf()); this->machine_config.set_simulated_endian(program.get_endian()); @@ -68,6 +67,15 @@ Machine::Machine(MachineConfig config, bool load_symtab, bool load_executable) controlst = new CSR::ControlState(machine_config.get_simulated_xlen(), machine_config.get_isa_word()); + + tlb_program = new TLB( + cch_program, PROGRAM, machine_config.access_tlb_program(), machine_config.get_vm_enabled()); + tlb_data = new TLB( + cch_data, DATA, machine_config.access_tlb_data(), machine_config.get_vm_enabled()); + controlst->write_internal(CSR::Id::SATP, 0); + tlb_program->on_csr_write(CSR::Id::SATP, 0); + tlb_data->on_csr_write(CSR::Id::SATP, 0); + predictor = new BranchPredictor( machine_config.get_bp_enabled(), machine_config.get_bp_type(), machine_config.get_bp_init_state(), machine_config.get_bp_btb_bits(), @@ -75,11 +83,11 @@ Machine::Machine(MachineConfig config, bool load_symtab, bool load_executable) if (machine_config.pipelined()) { cr = new CorePipelined( - regs, predictor, cch_program, cch_data, controlst, machine_config.get_simulated_xlen(), + regs, predictor, tlb_program, tlb_data, controlst, machine_config.get_simulated_xlen(), machine_config.get_isa_word(), machine_config.hazard_unit()); } else { cr = new CoreSingle( - regs, predictor, cch_program, cch_data, controlst, machine_config.get_simulated_xlen(), + regs, predictor, tlb_program, tlb_data, controlst, machine_config.get_simulated_xlen(), machine_config.get_isa_word()); } connect( @@ -176,6 +184,10 @@ Machine::~Machine() { regs = nullptr; delete mem; mem = nullptr; + delete tlb_program; + tlb_program = nullptr; + delete tlb_data; + tlb_data = nullptr; delete cch_program; cch_program = nullptr; delete cch_data; @@ -243,6 +255,27 @@ void Machine::cache_sync() { if (cch_level2 != nullptr) { cch_level2->sync(); } } +void Machine::tlb_sync() { + if (tlb_program) { tlb_program->sync(); } + if (tlb_data) { tlb_data->sync(); } +} + +const TLB *Machine::get_tlb_program() const { + return tlb_program ? &*tlb_program : nullptr; +} + +const TLB *Machine::get_tlb_data() const { + return tlb_data ? &*tlb_data : nullptr; +} + +TLB *Machine::get_tlb_program_rw() { + return tlb_program ? &*tlb_program : nullptr; +} + +TLB *Machine::get_tlb_data_rw() { + return tlb_data ? &*tlb_data : nullptr; +} + const MemoryDataBus *Machine::memory_data_bus() { return data_bus; } diff --git a/src/machine/machine.h b/src/machine/machine.h index 4a87880a..026b3933 100644 --- a/src/machine/machine.h +++ b/src/machine/machine.h @@ -12,6 +12,7 @@ #include "memory/backend/serialport.h" #include "memory/cache/cache.h" #include "memory/memory_bus.h" +#include "memory/tlb/tlb.h" #include "predictor.h" #include "registers.h" #include "simulator_exception.h" @@ -20,6 +21,7 @@ #include #include #include +#include namespace machine { @@ -41,6 +43,11 @@ class Machine : public QObject { const Cache *cache_level2(); Cache *cache_data_rw(); void cache_sync(); + const TLB *get_tlb_program() const; + const TLB *get_tlb_data() const; + TLB *get_tlb_program_rw(); + TLB *get_tlb_data_rw(); + void tlb_sync(); const MemoryDataBus *memory_data_bus(); MemoryDataBus *memory_data_bus_rw(); SerialPort *serial_port(); @@ -131,6 +138,8 @@ private slots: Cache *cch_program = nullptr; Cache *cch_data = nullptr; Cache *cch_level2 = nullptr; + TLB *tlb_program = nullptr; + TLB *tlb_data = nullptr; CSR::ControlState *controlst = nullptr; BranchPredictor *predictor = nullptr; Core *cr = nullptr; diff --git a/src/machine/machineconfig.cpp b/src/machine/machineconfig.cpp index 3a1de4ed..9cdd71e0 100644 --- a/src/machine/machineconfig.cpp +++ b/src/machine/machineconfig.cpp @@ -27,6 +27,11 @@ using namespace machine; #define DFC_BP_BTB_BITS 2 #define DFC_BP_BHR_BITS 0 #define DFC_BP_BHT_ADDR_BITS 2 +/// Default config of Virtual Memory +#define DFC_VM_ENABLED false +#define DFC_TLB_SETS 16 +#define DFC_TLB_ASSOC 1 +#define DFC_TLB_REPLAC RP_LRU ////////////////////////////////////////////////////////////////////////////// /// Default config of CacheConfig #define DFC_EN false @@ -151,6 +156,104 @@ bool CacheConfig::operator==(const CacheConfig &c) const { bool CacheConfig::operator!=(const CacheConfig &c) const { return !operator==(c); } +////////////////////////////////////////////////////////////////////////////// + +TLBConfig::TLBConfig() { + vm_asid = 0; + n_sets = DFC_TLB_SETS; + d_associativity = DFC_TLB_ASSOC; + replac_pol = (enum ReplacementPolicy)DFC_TLB_REPLAC; +} + +TLBConfig::TLBConfig(const TLBConfig *tc) { + if (tc == nullptr) { + vm_asid = 0; + n_sets = DFC_TLB_SETS; + d_associativity = DFC_TLB_ASSOC; + replac_pol = (enum ReplacementPolicy)DFC_TLB_REPLAC; + return; + } + vm_asid = tc->get_vm_asid(); + n_sets = tc->get_tlb_num_sets(); + d_associativity = tc->get_tlb_associativity(); + replac_pol = tc->get_tlb_replacement_policy(); +} + +#define N(STR) (prefix + QString(STR)) + +TLBConfig::TLBConfig(const QSettings *sts, const QString &prefix) { + vm_asid = sts->value(N("VM_ASID"), 0u).toUInt(); + n_sets = sts->value(N("NumSets"), DFC_TLB_SETS).toUInt(); + d_associativity = sts->value(N("Associativity"), DFC_TLB_ASSOC).toUInt(); + replac_pol = (enum ReplacementPolicy)sts->value(N("Policy"), DFC_TLB_REPLAC).toUInt(); +} + +void TLBConfig::store(QSettings *sts, const QString &prefix) const { + sts->setValue(N("VM_ASID"), get_vm_asid()); + sts->setValue(N("NumSets"), get_tlb_num_sets()); + sts->setValue(N("Associativity"), get_tlb_associativity()); + sts->setValue(N("Policy"), (unsigned)get_tlb_replacement_policy()); +} + +#undef N + +void TLBConfig::preset(enum ConfigPresets p) { + switch (p) { + case CP_PIPE: + case CP_SINGLE_CACHE: + case CP_SINGLE: + case CP_PIPE_NO_HAZARD: + vm_asid = 0; + n_sets = DFC_TLB_SETS; + d_associativity = DFC_TLB_ASSOC; + replac_pol = (enum ReplacementPolicy)DFC_TLB_REPLAC; + } +} + +void TLBConfig::set_vm_asid(uint32_t a) { + vm_asid = a; +} + +uint32_t TLBConfig::get_vm_asid() const { + return vm_asid; +} + +void TLBConfig::set_tlb_num_sets(unsigned v) { + n_sets = v > 0 ? v : 1; +} + +void TLBConfig::set_tlb_associativity(unsigned v) { + d_associativity = v > 0 ? v : 1; +} + +void TLBConfig::set_tlb_replacement_policy(TLBConfig::ReplacementPolicy p) { + replac_pol = p; +} + +unsigned TLBConfig::get_tlb_num_sets() const { + return n_sets; +} + +unsigned TLBConfig::get_tlb_associativity() const { + return d_associativity; +} + +TLBConfig::ReplacementPolicy TLBConfig::get_tlb_replacement_policy() const { + return replac_pol; +} + +bool TLBConfig::operator==(const TLBConfig &c) const { +#define CMP(GETTER) (GETTER)() == (c.GETTER)() + return CMP(get_vm_asid) && CMP(get_tlb_num_sets) && CMP(get_tlb_associativity) && + CMP(get_tlb_replacement_policy); +#undef CMP +} + +bool TLBConfig::operator!=(const TLBConfig &c) const { + return !operator==(c); +} + +////////////////////////////////////////////////////////////////////////////// MachineConfig::MachineConfig() { simulated_endian = LITTLE; @@ -186,6 +289,11 @@ MachineConfig::MachineConfig() { bp_bhr_bits = DFC_BP_BHR_BITS; bp_bht_addr_bits = DFC_BP_BHT_ADDR_BITS; bp_bht_bits = bp_bhr_bits + bp_bht_addr_bits; + + // Virtual memory + vm_enabled = DFC_VM_ENABLED; + tlb_program = TLBConfig(); + tlb_data = TLBConfig(); } MachineConfig::MachineConfig(const MachineConfig *config) { @@ -222,6 +330,11 @@ MachineConfig::MachineConfig(const MachineConfig *config) { bp_bhr_bits = config->get_bp_bhr_bits(); bp_bht_addr_bits = config->get_bp_bht_addr_bits(); bp_bht_bits = bp_bhr_bits + bp_bht_addr_bits; + + // Virtual memory + vm_enabled = config->get_vm_enabled(); + tlb_program = config->tlbc_program(); + tlb_data = config->tlbc_data(); } #define N(STR) (prefix + QString(STR)) @@ -266,6 +379,11 @@ MachineConfig::MachineConfig(const QSettings *sts, const QString &prefix) { bp_bhr_bits = sts->value(N("BranchPredictor_BitsBHR"), DFC_BP_BHR_BITS).toUInt(); bp_bht_addr_bits = sts->value(N("BranchPredictor_BitsBHTAddr"), DFC_BP_BHT_ADDR_BITS).toUInt(); bp_bht_bits = bp_bhr_bits + bp_bht_addr_bits; + + // Virtual memory + vm_enabled = sts->value(N("VMEnabled"), DFC_VM_ENABLED).toBool(); + tlb_data = TLBConfig(sts, N("DataTLB_")); + tlb_program = TLBConfig(sts, N("ProgramTLB_")); } void MachineConfig::store(QSettings *sts, const QString &prefix) { @@ -298,6 +416,11 @@ void MachineConfig::store(QSettings *sts, const QString &prefix) { sts->setValue(N("BranchPredictor_BitsBTB"), get_bp_btb_bits()); sts->setValue(N("BranchPredictor_BitsBHR"), get_bp_bhr_bits()); sts->setValue(N("BranchPredictor_BitsBHTAddr"), get_bp_bht_addr_bits()); + + // Virtual memory + sts->setValue(N("VMEnabled"), get_vm_enabled()); + tlbc_program().store(sts, N("ProgramTLB_")); + tlbc_data().store(sts, N("DataTLB_")); } #undef N @@ -341,6 +464,10 @@ void MachineConfig::preset(enum ConfigPresets p) { access_cache_data()->preset(p); access_cache_level2()->preset(p); + set_vm_enabled(DFC_VM_ENABLED); + access_tlb_program()->preset(p); + access_tlb_data()->preset(p); + set_simulated_xlen(Xlen::_32); set_isa_word(config_isa_word_default); @@ -558,6 +685,22 @@ CacheConfig *MachineConfig::access_cache_level2() { return &cch_level2; } +TLBConfig *MachineConfig::access_tlb_program() { + return &tlb_program; +} + +TLBConfig *MachineConfig::access_tlb_data() { + return &tlb_data; +} + +const TLBConfig &MachineConfig::tlbc_program() const { + return tlb_program; +} + +const TLBConfig &MachineConfig::tlbc_data() const { + return tlb_data; +} + Endian MachineConfig::get_simulated_endian() const { return simulated_endian; } @@ -629,6 +772,14 @@ uint8_t MachineConfig::get_bp_bht_bits() const { return bp_bht_bits; } +void MachineConfig::set_vm_enabled(bool v) { + vm_enabled = v; +} + +bool MachineConfig::get_vm_enabled() const { + return vm_enabled; +} + bool MachineConfig::operator==(const MachineConfig &c) const { #define CMP(GETTER) (GETTER)() == (c.GETTER)() return CMP(pipelined) && CMP(delay_slot) && CMP(hazard_unit) && CMP(get_simulated_xlen) @@ -638,7 +789,7 @@ bool MachineConfig::operator==(const MachineConfig &c) const { && CMP(memory_access_time_read) && CMP(memory_access_time_write) && CMP(memory_access_time_burst) && CMP(memory_access_time_level2) && CMP(memory_access_enable_burst) && CMP(elf) && CMP(cache_program) && CMP(cache_data) - && CMP(cache_level2); + && CMP(cache_level2) && CMP(get_vm_enabled) && CMP(tlbc_data) && CMP(tlbc_program); #undef CMP } diff --git a/src/machine/machineconfig.h b/src/machine/machineconfig.h index e6215188..50eb82d5 100644 --- a/src/machine/machineconfig.h +++ b/src/machine/machineconfig.h @@ -84,6 +84,48 @@ class CacheConfig { enum WritePolicy write_pol; }; +class TLBConfig { +public: + TLBConfig(); + explicit TLBConfig(const TLBConfig *cc); + explicit TLBConfig(const QSettings *, const QString &prefix = ""); + + void store(QSettings *, const QString &prefix = "") const; + + void preset(enum ConfigPresets); + + enum VmMode { VM_BARE, VM_SV32 }; + + enum ReplacementPolicy { + RP_RAND, // Random + RP_LRU, // Least recently used + RP_LFU, // Least frequently used + RP_PLRU // Pseudo Least recently used + }; + + // Virtual Memory + void set_vm_asid(uint32_t a); + uint32_t get_vm_asid() const; + + void set_tlb_num_sets(unsigned); + void set_tlb_associativity(unsigned); + void set_tlb_replacement_policy(ReplacementPolicy); + + unsigned get_tlb_num_sets() const; + unsigned get_tlb_associativity() const; + ReplacementPolicy get_tlb_replacement_policy() const; + + bool operator==(const TLBConfig &c) const; + bool operator!=(const TLBConfig &c) const; + +private: + bool vm_enabled = false; + uint32_t vm_asid = 0; + unsigned n_sets = 1; + unsigned d_associativity = 1; + enum ReplacementPolicy replac_pol = RP_RAND; +}; + class MachineConfig { public: MachineConfig(); @@ -163,6 +205,12 @@ class MachineConfig { Xlen get_simulated_xlen() const; ConfigIsaWord get_isa_word() const; + // Virtual memory + void set_vm_enabled(bool v); + bool get_vm_enabled() const; + const TLBConfig &tlbc_program() const; + const TLBConfig &tlbc_data() const; + // Branch predictor - Setters void set_bp_enabled(bool e); void set_bp_type(PredictorType t); @@ -183,6 +231,9 @@ class MachineConfig { CacheConfig *access_cache_data(); CacheConfig *access_cache_level2(); + TLBConfig *access_tlb_program(); + TLBConfig *access_tlb_data(); + bool operator==(const MachineConfig &c) const; bool operator!=(const MachineConfig &c) const; @@ -210,6 +261,10 @@ class MachineConfig { uint8_t bp_bhr_bits; uint8_t bp_bht_addr_bits; uint8_t bp_bht_bits; // = bp_bhr_bits + bp_bht_addr_bits + + // Virtual memory + bool vm_enabled; + TLBConfig tlb_program, tlb_data; }; } // namespace machine diff --git a/src/machine/memory/frontend_memory.cpp b/src/machine/memory/frontend_memory.cpp index 594b838a..6e0791c3 100644 --- a/src/machine/memory/frontend_memory.cpp +++ b/src/machine/memory/frontend_memory.cpp @@ -1,6 +1,7 @@ #include "memory/frontend_memory.h" #include "common/endian.h" +#include "tlb/tlb.h" namespace machine { @@ -133,4 +134,4 @@ bool FrontendMemory::write_generic(Address address, const T value, AccessEffects } FrontendMemory::FrontendMemory(Endian simulated_endian) : simulated_machine_endian(simulated_endian) {} -} // namespace machine \ No newline at end of file +} // namespace machine diff --git a/src/machine/memory/tlb/tlb.cpp b/src/machine/memory/tlb/tlb.cpp new file mode 100644 index 00000000..153107e0 --- /dev/null +++ b/src/machine/memory/tlb/tlb.cpp @@ -0,0 +1,267 @@ +#include "tlb.h" + +#include "csr/controlstate.h" +#include "machine.h" +#include "memory/virtual/page_table_walker.h" +#include "memory/virtual/sv32.h" + +LOG_CATEGORY("machine.TLB"); + +namespace machine { + +TLB::TLB( + FrontendMemory *memory, + TLBType type_, + const TLBConfig *config, + bool vm_enabled, + uint32_t memory_access_penalty_r, + uint32_t memory_access_penalty_w, + uint32_t memory_access_penalty_b, + bool memory_access_enable_b) + : FrontendMemory(memory->simulated_machine_endian) + , mem(memory) + , uncached_start(0xf0000000_addr) + , uncached_last(0xfffffffe_addr) + , type(type_) + , tlb_config(config) + , vm_enabled(vm_enabled) + , access_pen_r(memory_access_penalty_r) + , access_pen_w(memory_access_penalty_w) + , access_pen_b(memory_access_penalty_b) + , access_ena_b(memory_access_enable_b) { + num_sets_ = tlb_config.get_tlb_num_sets(); + associativity_ = tlb_config.get_tlb_associativity(); + auto pol = tlb_config.get_tlb_replacement_policy(); + repl_policy = make_tlb_policy(static_cast(pol), associativity_, num_sets_); + table.assign(num_sets_, std::vector(associativity_)); + const char *tag = (type == PROGRAM ? "I" : "D"); + LOG("TLB[%s] constructed; sets=%zu way=%zu", tag, num_sets_, associativity_); + + emit hit_update(hit_count_); + emit miss_update(miss_count_); + emit memory_reads_update(mem_reads); + emit memory_writes_update(mem_writes); + update_all_statistics(); +} + +void TLB::on_csr_write(size_t internal_id, RegisterValue val) { + if (internal_id != CSR::Id::SATP) return; + current_satp_raw = static_cast(val.as_u64()); + for (size_t s = 0; s < num_sets_; s++) { + for (size_t w = 0; w < associativity_; w++) { + auto &e = table[s][w]; + if (e.valid) { + uint16_t old_asid = e.asid; + uint64_t old_vpn = e.vpn; + e.valid = false; + emit tlb_update( + static_cast(w), static_cast(s), false, old_asid, old_vpn, + 0ull, false); + } + } + } + LOG("TLB: SATP changed → flushed all; new SATP=0x%08x", current_satp_raw); + update_all_statistics(); +} + +void TLB::flush_single(VirtualAddress va, uint16_t asid) { + uint64_t vpn = va.get_raw() >> 12; + size_t s = set_index(vpn); + for (size_t w = 0; w < associativity_; w++) { + auto &e = table[s][w]; + if (e.valid && e.vpn == vpn && e.asid == asid) { + uint16_t old_asid = e.asid; + uint64_t old_vpn = e.vpn; + e.valid = false; + const char *tag = (type == PROGRAM ? "I" : "D"); + LOG("TLB[%s]: flushed VA=0x%llx ASID=%u", tag, (unsigned long long)va.get_raw(), asid); + emit tlb_update( + static_cast(w), static_cast(s), false, old_asid, old_vpn, 0ull, + false); + update_all_statistics(); + } + } +} + +void TLB::flush() { + if (num_sets_ == 0 || associativity_ == 0) return; + const char *tag = (type == PROGRAM ? "I" : "D"); + for (size_t s = 0; s < num_sets_; s++) { + for (size_t w = 0; w < associativity_; w++) { + auto &e = table[s][w]; + if (e.valid) { + uint16_t old_asid = e.asid; + uint64_t old_vpn = e.vpn; + e.valid = false; + emit tlb_update( + static_cast(w), static_cast(s), false, old_asid, old_vpn, + 0ull, false); + } + } + } + change_counter++; + LOG("TLB[%s]: flushed all entries", tag); + update_all_statistics(); +} + +void TLB::sync() { + flush(); +} + +Address TLB::translate_virtual_to_physical(Address vaddr) { + uint64_t virt = vaddr.get_raw(); + + if (!vm_enabled) { return vaddr; } + + constexpr unsigned PAGE_SHIFT = 12; + constexpr uint64_t PAGE_MASK = (1ULL << PAGE_SHIFT) - 1; + + uint64_t off = virt & ((1ULL << PAGE_SHIFT) - 1); + uint64_t vpn = virt >> PAGE_SHIFT; + size_t s = set_index(vpn); + const char *tag = (type == PROGRAM ? "I" : "D"); + uint16_t asid = (current_satp_raw >> 22) & 0x1FF; + + // Check TLB hit + for (size_t w = 0; w < associativity_; w++) { + auto &e = table[s][w]; + if (e.valid && e.vpn == vpn && e.asid == asid) { + repl_policy->notify_access(s, w, /*valid=*/true); + uint64_t pbase = e.phys.get_raw() & ~((1ULL << PAGE_SHIFT) - 1); + hit_count_++; + emit hit_update(hit_count_); + emit tlb_update( + static_cast(w), static_cast(s), true, e.asid, e.vpn, pbase, + false); + update_all_statistics(); + return Address { pbase + off }; + } + } + + // TLB miss -> resolve with page table walker + VirtualAddress va { static_cast(virt) }; + PageTableWalker walker(mem); + + Address resolved_pa = walker.walk(va, current_satp_raw); + mem_reads += 1; + emit memory_reads_update(mem_reads); + + // Cache the resolved mapping in the TLB + uint64_t phys_base = resolved_pa.get_raw() & ~PAGE_MASK; + size_t victim = repl_policy->select_way(s); + auto &ent = table[s][victim]; + ent.valid = true; + ent.asid = asid; + ent.vpn = vpn; + ent.phys = Address { phys_base }; + repl_policy->notify_access(s, victim, /*valid=*/true); + miss_count_++; + emit miss_update(miss_count_); + emit tlb_update( + static_cast(victim), static_cast(s), true, ent.asid, ent.vpn, phys_base, + false); + + LOG("TLB[%s]: cached VA=0x%llx -> PA=0x%llx (ASID=%u) on miss", tag, (unsigned long long)virt, + (unsigned long long)phys_base, asid); + update_all_statistics(); + return Address { phys_base + off }; +} + +WriteResult TLB::translate_and_write(Address dst, const void *src, size_t sz, WriteOptions opts) { + Address pa = translate_virtual_to_physical(dst); + return mem->write(pa, src, sz, opts); +} + +ReadResult TLB::translate_and_read(void *dst, Address src, size_t sz, ReadOptions opts) { + Address pa = translate_virtual_to_physical(src); + return mem->read(dst, pa, sz, opts); +} + +bool TLB::reverse_lookup(Address paddr, VirtualAddress &out_va) const { + uint64_t ppn = paddr.get_raw() >> 12; + uint64_t offset = paddr.get_raw() & 0xFFF; + for (size_t s = 0; s < num_sets_; s++) { + for (size_t w = 0; w < associativity_; w++) { + auto &e = table[s][w]; + if (e.valid && (e.phys.get_raw() >> 12) == ppn) { + out_va = VirtualAddress { (e.vpn << 12) | offset }; + return true; + } + } + } + return false; +} +bool TLB::is_in_uncached_area(Address source) const { + return (source >= uncached_start && source <= uncached_last); +} + +double TLB::get_hit_rate() const { + unsigned comp = hit_count_ + miss_count_; + if (comp == 0) return 0.0; + return (double)hit_count_ / (double)comp * 100.0; +} + +uint32_t TLB::get_stall_count() const { + uint32_t st_cycles = mem_reads * (access_pen_r - 1) + mem_writes * (access_pen_w - 1); + st_cycles += miss_count_; + if (access_ena_b) { + st_cycles -= burst_reads * (access_pen_r - access_pen_b) + + burst_writes * (access_pen_w - access_pen_b); + } + return st_cycles; +} + +const TLBConfig &TLB::get_config() const { + return tlb_config; +} + +double TLB::get_speed_improvement() const { + unsigned comp = hit_count_ + miss_count_; + if (comp == 0) return 100.0; + uint32_t lookup_time = comp; + uint32_t mem_access_time = mem_reads * access_pen_r + mem_writes * access_pen_w; + if (access_ena_b) { + mem_access_time -= burst_reads * (access_pen_r - access_pen_b) + + burst_writes * (access_pen_w - access_pen_b); + } + double baseline_time = (double)comp * (double)access_pen_r; + double with_tlb_time = (double)lookup_time + (double)mem_access_time; + if (with_tlb_time == 0.0) return 100.0; + return (baseline_time / with_tlb_time) * 100.0; +} + +void TLB::update_all_statistics() { + emit statistics_update(get_stall_count(), get_speed_improvement(), get_hit_rate()); +} + +void TLB::reset() { + for (size_t s = 0; s < num_sets_; s++) { + for (size_t w = 0; w < associativity_; w++) { + auto &e = table[s][w]; + if (e.valid) { + uint16_t old_asid = e.asid; + uint64_t old_vpn = e.vpn; + e.valid = false; + emit tlb_update( + static_cast(w), static_cast(s), false, old_asid, old_vpn, + 0ull, false); + } + } + } + + hit_count_ = 0; + miss_count_ = 0; + mem_reads = 0; + mem_writes = 0; + burst_reads = 0; + burst_writes = 0; + change_counter = 0; + + emit hit_update(get_hit_count()); + emit miss_update(get_miss_count()); + emit memory_reads_update(get_read_count()); + emit memory_writes_update(get_write_count()); + update_all_statistics(); +} + +} // namespace machine diff --git a/src/machine/memory/tlb/tlb.h b/src/machine/memory/tlb/tlb.h new file mode 100644 index 00000000..5af3a3cc --- /dev/null +++ b/src/machine/memory/tlb/tlb.h @@ -0,0 +1,129 @@ +#ifndef TLB_H +#define TLB_H + +#include "common/logging.h" +#include "memory/frontend_memory.h" +#include "memory/virtual/sv32.h" +#include "memory/virtual/virtual_address.h" +#include "tlb_policy.h" + +#include + +namespace machine { + +enum TLBType { PROGRAM, DATA }; + +class Machine; + +// Implements a set-associative Translation Lookaside Buffer (TLB) frontend over physical memory, +// handling virtual to physical translation, flush, and replacement policy. +class TLB : public FrontendMemory { + Q_OBJECT +public: + TLB(FrontendMemory *memory, + TLBType type, + const TLBConfig *config, + bool vm_enabled = true, + uint32_t memory_access_penalty_r = 1, + uint32_t memory_access_penalty_w = 1, + uint32_t memory_access_penalty_b = 0, + bool memory_access_enable_b = false); + + void on_csr_write(size_t internal_id, RegisterValue val); + void flush_single(VirtualAddress va, uint16_t asid); + + void flush(); + + void sync() override; + + Address translate_virtual_to_physical(Address va); + + WriteResult write(Address dst, const void *src, size_t sz, WriteOptions opts) override { + return translate_and_write(dst, src, sz, opts); + } + ReadResult read(void *dst, Address src, size_t sz, ReadOptions opts) const override { + return const_cast(this)->translate_and_read(dst, src, sz, opts); + } + + uint32_t get_change_counter() const override { return mem->get_change_counter(); } + + void set_replacement_policy(std::unique_ptr p) { repl_policy = std::move(p); } + + uint32_t root_page_table_ppn() const { return current_satp_raw & ((1u << PPN_BITS) - 1); } + + bool reverse_lookup(Address paddr, VirtualAddress &out_va) const; + + unsigned get_hit_count() const { return hit_count_; } + unsigned get_miss_count() const { return miss_count_; } + double get_hit_rate() const; + uint32_t get_read_count() const { return mem_reads; } + uint32_t get_write_count() const { return mem_writes; } + uint32_t get_burst_read_count() const { return burst_reads; } + uint32_t get_burst_write_count() const { return burst_writes; } + + uint32_t get_stall_count() const; + double get_speed_improvement() const; + const TLBConfig &get_config() const; + + void reset(); + void update_all_statistics(); + +signals: + void tlb_update( + unsigned way, + unsigned set, + bool valid, + uint16_t asid, + uint64_t vpn, + uint64_t phys_base, + bool is_write); + void hit_update(unsigned val); + void miss_update(unsigned val); + void statistics_update(unsigned stalled_cycles, double speed_improv, double hit_rate); + void memory_reads_update(uint32_t val); + void memory_writes_update(uint32_t val); + +private: + struct Entry { + bool valid = false; + uint16_t asid = 0; + uint64_t vpn = 0; + Address phys = Address { 0 }; + uint32_t lru = 0; + }; + + FrontendMemory *mem; + const Address uncached_start; + const Address uncached_last; + TLBType type; + const TLBConfig tlb_config; + uint32_t current_satp_raw = 0; + const bool vm_enabled; + + size_t num_sets_; + size_t associativity_; + std::vector> table; + std::unique_ptr repl_policy; + + const uint32_t access_pen_r; + const uint32_t access_pen_w; + const uint32_t access_pen_b; + const bool access_ena_b; + + mutable unsigned hit_count_ = 0; + mutable unsigned miss_count_ = 0; + mutable uint32_t mem_reads = 0; + mutable uint32_t mem_writes = 0; + mutable uint32_t burst_reads = 0; + mutable uint32_t burst_writes = 0; + mutable uint32_t change_counter = 0; + + bool is_in_uncached_area(Address source) const; + WriteResult translate_and_write(Address dst, const void *src, size_t sz, WriteOptions opts); + ReadResult translate_and_read(void *dst, Address src, size_t sz, ReadOptions opts); + inline size_t set_index(uint64_t vpn) const { return vpn & (num_sets_ - 1); } +}; + +} // namespace machine + +#endif // TLB_H diff --git a/src/machine/memory/tlb/tlb_policy.cpp b/src/machine/memory/tlb/tlb_policy.cpp new file mode 100644 index 00000000..283a990b --- /dev/null +++ b/src/machine/memory/tlb/tlb_policy.cpp @@ -0,0 +1,108 @@ +#include "tlb_policy.h" + +#include +#include +#include +#include + +namespace machine { + +TLBPolicyRAND::TLBPolicyRAND(size_t assoc) : associativity(assoc) { + std::srand(1); +} +size_t TLBPolicyRAND::select_way(size_t) const { + return std::rand() % associativity; +} +void TLBPolicyRAND::notify_access(size_t, size_t, bool) { + /* no state */ +} + +TLBPolicyLRU::TLBPolicyLRU(size_t assoc, size_t sets) : associativity(assoc), set_count(sets) { + stats.resize(sets, std::vector(assoc)); + for (auto &row : stats) { + std::iota(row.begin(), row.end(), 0); + } +} +size_t TLBPolicyLRU::select_way(size_t set) const { + return stats[set][0]; +} +void TLBPolicyLRU::notify_access(size_t set, size_t way, bool valid) { + auto &row = stats[set]; + uint32_t next = way; + if (valid) { + for (int i = int(row.size()) - 1; i >= 0; --i) { + std::swap(row[i], next); + if (next == way) break; + } + } else { + for (unsigned int &i : row) { + std::swap(i, next); + if (next == way) break; + } + } +} + +TLBPolicyLFU::TLBPolicyLFU(size_t assoc, size_t sets) : associativity(assoc), set_count(sets) { + stats.assign(sets, std::vector(assoc, 0)); +} +size_t TLBPolicyLFU::select_way(size_t set) const { + const auto &row = stats[set]; + size_t idx = 0; + uint32_t minv = row[0]; + for (size_t i = 1; i < row.size(); ++i) { + if (row[i] == 0 || row[i] < minv) { + minv = row[i]; + idx = i; + if (minv == 0) break; + } + } + return idx; +} +void TLBPolicyLFU::notify_access(size_t set, size_t way, bool valid) { + if (valid) { + stats[set][way]++; + } else { + stats[set][way] = 0; + } +} + +TLBPolicyPLRU::TLBPolicyPLRU(size_t assoc, size_t sets) + : associativity(assoc) + , set_count(sets) + , c_log2(std::ceil(std::log2(float(assoc)))) { + size_t tree_size = (1u << c_log2) - 1; + tree.assign(sets, std::vector(tree_size, 0)); +} +size_t TLBPolicyPLRU::select_way(size_t set) const { + const auto &bits = tree[set]; + size_t idx = 0; + size_t node = 0; + for (size_t lvl = 0; lvl < c_log2; ++lvl) { + uint8_t b = bits[node]; + idx = (idx << 1) | b; + node = ((1u << (lvl + 1)) - 1) + idx; + } + return std::min(idx, associativity - 1); +} +void TLBPolicyPLRU::notify_access(size_t set, size_t way, bool) { + auto &bits = tree[set]; + size_t node = 0; + for (size_t lvl = 0; lvl < c_log2; ++lvl) { + uint8_t dir = (way >> (c_log2 - lvl - 1)) & 1; + bits[node] = dir ? 0 : 1; + node = ((1u << (lvl + 1)) - 1) + ((dir ? 1 : 0)); + } +} + +std::unique_ptr +make_tlb_policy(TLBPolicyKind kind, size_t associativity, size_t set_count) { + switch (kind) { + case TLBPolicyKind::RAND: return std::make_unique(associativity); + case TLBPolicyKind::LRU: return std::make_unique(associativity, set_count); + case TLBPolicyKind::LFU: return std::make_unique(associativity, set_count); + case TLBPolicyKind::PLRU: return std::make_unique(associativity, set_count); + } + return std::make_unique(associativity, set_count); +} + +} // namespace machine diff --git a/src/machine/memory/tlb/tlb_policy.h b/src/machine/memory/tlb/tlb_policy.h new file mode 100644 index 00000000..7d47cb37 --- /dev/null +++ b/src/machine/memory/tlb/tlb_policy.h @@ -0,0 +1,72 @@ +#ifndef TLB_POLICY_H +#define TLB_POLICY_H + +#include +#include +#include +#include + +namespace machine { + +// Abstract TLB replacement policy interface & implementations (RAND, LRU, LFU, PLRU) for +// set-associative tables. +class TLBPolicy { +public: + virtual size_t select_way(size_t set) const = 0; + + virtual void notify_access(size_t set, size_t way, bool valid) = 0; + + virtual ~TLBPolicy() = default; +}; + +class TLBPolicyRAND final : public TLBPolicy { + size_t associativity; + +public: + explicit TLBPolicyRAND(size_t assoc); + size_t select_way(size_t set) const override; + void notify_access(size_t set, size_t way, bool valid) override; +}; + +class TLBPolicyLRU final : public TLBPolicy { + size_t associativity; + size_t set_count; + std::vector> stats; + +public: + TLBPolicyLRU(size_t assoc, size_t sets); + size_t select_way(size_t set) const override; + void notify_access(size_t set, size_t way, bool valid) override; +}; + +class TLBPolicyLFU final : public TLBPolicy { + size_t associativity; + size_t set_count; + std::vector> stats; + +public: + TLBPolicyLFU(size_t assoc, size_t sets); + size_t select_way(size_t set) const override; + void notify_access(size_t set, size_t way, bool valid) override; +}; + +class TLBPolicyPLRU final : public TLBPolicy { + size_t associativity; + size_t set_count; + size_t c_log2; + std::vector> tree; + +public: + TLBPolicyPLRU(size_t assoc, size_t sets); + size_t select_way(size_t set) const override; + void notify_access(size_t set, size_t way, bool valid) override; +}; + +enum class TLBPolicyKind { RAND, LRU, LFU, PLRU }; + +std::unique_ptr +make_tlb_policy(TLBPolicyKind kind, size_t associativity, size_t set_count); + +} // namespace machine + +#endif // TLB_POLICY_H diff --git a/src/machine/memory/virtual/page_table_walker.cpp b/src/machine/memory/virtual/page_table_walker.cpp new file mode 100644 index 00000000..ba5e8069 --- /dev/null +++ b/src/machine/memory/virtual/page_table_walker.cpp @@ -0,0 +1,54 @@ +#include "page_table_walker.h" + +#include "common/logging.h" +#include "machine.h" +#include "memory/backend/backend_memory.h" + +#include + +LOG_CATEGORY("machine.PTW"); + +namespace machine { + +Address PageTableWalker::walk(const VirtualAddress &va, uint32_t raw_satp) const { + if (((raw_satp >> 31) & 1) == 0) return Address { va.get_raw() }; + + auto root_ppn = raw_satp & ((1u << 22) - 1); + auto va_raw = static_cast(va.get_raw()); + auto vpn1 = (va_raw >> VPN1_SHIFT) & VPN_MASK; + auto vpn0 = (va_raw >> VPN0_SHIFT) & VPN_MASK; + auto ppn = root_ppn; + + for (int lvl = 1; lvl >= 0; --lvl) { + uint32_t idx = (lvl == 1 ? vpn1 : vpn0); + Address pte_addr { (uint64_t(ppn) << PAGE_SHIFT) + idx * 4 }; + + uint32_t raw_pte; + memory->read(&raw_pte, pte_addr, sizeof(raw_pte), { .type = ae::INTERNAL }); + LOG("PTW: L%u PTE@0x%08" PRIx64 " = 0x%08x", lvl, pte_addr.get_raw(), raw_pte); + + Sv32Pte pte = Sv32Pte::from_uint(raw_pte); + + if (!pte.is_valid()) { + throw SIMULATOR_EXCEPTION( + PageFault, "PTW: page fault, leaf PTE invalid", + QString::number(pte_addr.get_raw(), 16)); + } + + if (pte.is_leaf()) { + Address pa = make_phys(va_raw, pte, lvl); + LOG("PTW: L%u leaf → PA=0x%08" PRIx64, lvl, pa.get_raw()); + return pa; + } + + if (pte.r() || pte.w() || pte.x()) { + throw SIMULATOR_EXCEPTION( + PageFault, "PTW: invalid non-leaf", QString::number(raw_pte, 16)); + } + ppn = unsigned(pte.ppn()); + } + + throw SIMULATOR_EXCEPTION(PageFault, "PTW: no leaf found", ""); +} + +} // namespace machine diff --git a/src/machine/memory/virtual/page_table_walker.h b/src/machine/memory/virtual/page_table_walker.h new file mode 100644 index 00000000..2ecfe396 --- /dev/null +++ b/src/machine/memory/virtual/page_table_walker.h @@ -0,0 +1,26 @@ +#ifndef PAGE_TABLE_WALKER_H +#define PAGE_TABLE_WALKER_H + +#include "memory/frontend_memory.h" +#include "sv32.h" +#include "virtual_address.h" + +#include + +namespace machine { + +// Performs multi-level page-table walks (SV32) in memory to resolve a virtual address to a physical +// one. +class PageTableWalker { +public: + explicit PageTableWalker(FrontendMemory *mem) : memory(mem) {} + + Address walk(const VirtualAddress &va, uint32_t raw_satp) const; + +private: + FrontendMemory *memory; +}; + +} // namespace machine + +#endif // PAGE_TABLE_WALKER_H diff --git a/src/machine/memory/virtual/sv32.h b/src/machine/memory/virtual/sv32.h new file mode 100644 index 00000000..f08b7318 --- /dev/null +++ b/src/machine/memory/virtual/sv32.h @@ -0,0 +1,139 @@ +#ifndef SV32_H +#define SV32_H + +// SV32-specific definitions: page-table entry (PTE) bitfields, shifts/masks, and PTE to physical +// address helpers. This header documents the SV32 layout (RISC-V 32-bit virtual memory): +// - Page size: 4 KiB (PAGE_SHIFT = 12). +// - Virtual page number (VPN) is split into two 10-bit indices VPN[1] and VPN[0]. +// - PTE low bits encode flags and low-order info; high bits encode the physical +// page number (PPN). +namespace machine { +static constexpr unsigned PAGE_SHIFT = 12; // Page size = 2^PAGE_SHIFT bytes. For SV32 this is 4 + // KiB. +static constexpr unsigned VPN_BITS = 10; // Number of bits for each VPN level in SV32: 10 bits for + // VPN[1] and 10 bits for VPN[0]. + +// Shift values for extracting VPN parts from a virtual address: +static constexpr unsigned VPN0_SHIFT = PAGE_SHIFT; // VPN0 is the low VPN field, it starts at the + // page offset (PAGE_SHIFT). +static constexpr unsigned VPN1_SHIFT = PAGE_SHIFT + VPN_BITS; // VPN1 is the next field above VPN0. + +static constexpr unsigned VPN_MASK = (1u << VPN_BITS) - 1; // Mask to extract a single VPN level (10 + // bits, value 0..1023). + +// Number of bits available for the physical page number (PPN) in SV32 PTE. +// SV32 uses 22 PPN bits (bits 10..31 of a 32-bit PTE) which permits addressing up to 4GiB of +// physical memory. +static constexpr unsigned PPN_BITS = 22; +static constexpr unsigned PPN_MASK = (1u << PPN_BITS) - 1; + +static constexpr uint64_t PHYS_PPN_START = 0x200; // I have noticed that programs are loaded into + // memory starting at 0x200. + +// Sv32Pte wraps the raw 32-bit PTE value and provides helpers to read flags and fields. +// Layout (bit indices): +// 0 V (valid) +// 1 R (read) +// 2 W (write) +// 3 X (execute) +// 4 U (user) +// 5 G (global) +// 6 A (accessed) +// 7 D (dirty) +// 8..9 RSW (reserved for supervisor use) +// 10..31 PPN (physical page number) +// +// A PTE is considered a "leaf" when it grants read or execute permission (R or X). +// Validation rules: PTE is valid if V==1 and (if W==1 then R must also be 1). +struct Sv32Pte { + uint64_t raw = 0; + + // Bit positions (shifts) for the fields described above. + static constexpr unsigned V_SHIFT = 0; + static constexpr unsigned R_SHIFT = 1; + static constexpr unsigned W_SHIFT = 2; + static constexpr unsigned X_SHIFT = 3; + static constexpr unsigned U_SHIFT = 4; + static constexpr unsigned G_SHIFT = 5; + static constexpr unsigned A_SHIFT = 6; + static constexpr unsigned D_SHIFT = 7; + static constexpr unsigned RSW_SHIFT = 8; // two bits: 8..9 + static constexpr unsigned PPN_SHIFT = 10; // PPN starts at bit 10 + + // Masks for each single-bit flag + static constexpr uint64_t V_MASK = (1u << V_SHIFT); + static constexpr uint64_t R_MASK = (1u << R_SHIFT); + static constexpr uint64_t W_MASK = (1u << W_SHIFT); + static constexpr uint64_t X_MASK = (1u << X_SHIFT); + static constexpr uint64_t U_MASK = (1u << U_SHIFT); + static constexpr uint64_t G_MASK = (1u << G_SHIFT); + static constexpr uint64_t A_MASK = (1u << A_SHIFT); + static constexpr uint64_t D_MASK = (1u << D_SHIFT); + + // Mask for the 2-bit RSW field (bits 8..9). + static constexpr uint64_t RSW_MASK = (0x3u << RSW_SHIFT); + + // Mask that selects the PPN field within the raw PTE (bits 10..(10 + PPN_BITS - 1)) + static constexpr uint64_t PPN_MASK32 = ((PPN_MASK) << PPN_SHIFT); + + constexpr Sv32Pte() = default; + constexpr explicit Sv32Pte(uint64_t raw_) : raw(raw_) {} + + constexpr uint64_t to_uint() const { return raw; } + static constexpr Sv32Pte from_uint(uint64_t r) { return Sv32Pte(r); } + + // Flag accessors + constexpr bool v() const noexcept { return (raw >> V_SHIFT) & 0x1u; } + constexpr bool r() const noexcept { return (raw >> R_SHIFT) & 0x1u; } + constexpr bool w() const noexcept { return (raw >> W_SHIFT) & 0x1u; } + constexpr bool x() const noexcept { return (raw >> X_SHIFT) & 0x1u; } + constexpr bool u() const noexcept { return (raw >> U_SHIFT) & 0x1u; } + constexpr bool g() const noexcept { return (raw >> G_SHIFT) & 0x1u; } + constexpr bool a() const noexcept { return (raw >> A_SHIFT) & 0x1u; } + constexpr bool d() const noexcept { return (raw >> D_SHIFT) & 0x1u; } + constexpr uint64_t rsw() const noexcept { return (raw >> RSW_SHIFT) & 0x3u; } + constexpr uint64_t ppn() const noexcept { return (raw >> PPN_SHIFT) & PPN_MASK; } + + // Convenience methods used by the page-table walker + bool is_leaf() const noexcept { return r() || x(); } + bool is_valid() const noexcept { return v() && (!w() || r()); } + + // Helper to construct a PTE from fields. + static constexpr Sv32Pte make( + bool v_, + bool r_, + bool w_, + bool x_, + bool u_, + bool g_, + bool a_, + bool d_, + uint64_t rsw_, + uint64_t ppn_) { + uint64_t r = 0; + r |= (uint64_t(v_) << V_SHIFT); + r |= (uint64_t(r_) << R_SHIFT); + r |= (uint64_t(w_) << W_SHIFT); + r |= (uint64_t(x_) << X_SHIFT); + r |= (uint64_t(u_) << U_SHIFT); + r |= (uint64_t(g_) << G_SHIFT); + r |= (uint64_t(a_) << A_SHIFT); + r |= (uint64_t(d_) << D_SHIFT); + r |= ((rsw_ & 0x3u) << RSW_SHIFT); + r |= ((ppn_ & PPN_MASK) << PPN_SHIFT); + return Sv32Pte(r); + } +}; + +inline Address make_phys(uint64_t va_raw, const Sv32Pte &pte, int level) { + uint64_t offset = va_raw & ((1u << PAGE_SHIFT) - 1); + uint64_t phys_ppn = pte.ppn(); + if (level == 1) { + uint64_t vpn0 = (va_raw >> PAGE_SHIFT) & VPN_MASK; + phys_ppn = (phys_ppn & ~VPN_MASK) | vpn0; + } + return Address { (uint64_t(phys_ppn) << PAGE_SHIFT) | offset }; +} +} // namespace machine + +#endif // SV32_H diff --git a/src/machine/memory/virtual/virtual_address.h b/src/machine/memory/virtual/virtual_address.h new file mode 100644 index 00000000..abcbf7c2 --- /dev/null +++ b/src/machine/memory/virtual/virtual_address.h @@ -0,0 +1,174 @@ +#ifndef VIRTUAL_ADDRESS_H +#define VIRTUAL_ADDRESS_H + +#include "utils.h" + +#include +#include + +using std::uint64_t; + +namespace machine { + +// Lightweight VirtualAddress wrapper offering raw access, alignment checks, arithmetic, and +// comparisons. +class VirtualAddress { +private: + uint64_t data; // Raw virtual address + +public: + constexpr explicit VirtualAddress(uint64_t); + + constexpr VirtualAddress(); + + constexpr VirtualAddress(const VirtualAddress &address) = default; + constexpr VirtualAddress &operator=(const VirtualAddress &address) = default; + + [[nodiscard]] constexpr uint64_t get_raw() const; + + constexpr explicit operator uint64_t() const; + + constexpr static VirtualAddress null(); + + [[nodiscard]] constexpr bool is_null() const; + + template + [[nodiscard]] constexpr bool is_aligned() const; + + /* Equality */ + constexpr inline bool operator==(const VirtualAddress &other) const; + constexpr inline bool operator!=(const VirtualAddress &other) const; + + /* Ordering */ + constexpr inline bool operator<(const VirtualAddress &other) const; + constexpr inline bool operator>(const VirtualAddress &other) const; + constexpr inline bool operator<=(const VirtualAddress &other) const; + constexpr inline bool operator>=(const VirtualAddress &other) const; + + /* Offset arithmetic */ + constexpr inline VirtualAddress operator+(const uint64_t &offset) const; + constexpr inline VirtualAddress operator-(const uint64_t &offset) const; + inline void operator+=(const uint64_t &offset); + inline void operator-=(const uint64_t &offset); + + /* Bitwise */ + constexpr inline VirtualAddress operator&(const uint64_t &mask) const; + constexpr inline VirtualAddress operator|(const uint64_t &mask) const; + constexpr inline VirtualAddress operator^(const uint64_t &mask) const; + constexpr inline VirtualAddress operator>>(const uint64_t &size) const; + constexpr inline VirtualAddress operator<<(const uint64_t &size) const; + + /* Distance arithmetic */ + constexpr inline int64_t operator-(const VirtualAddress &other) const; +}; + +constexpr VirtualAddress operator""_vaddr(unsigned long long literal) { + return VirtualAddress(literal); +} + +// Implementations + +constexpr VirtualAddress::VirtualAddress(uint64_t address) : data(address) {} + +constexpr VirtualAddress::VirtualAddress() : data(0) {} + +constexpr uint64_t VirtualAddress::get_raw() const { + return data; +} + +constexpr VirtualAddress::operator uint64_t() const { + return this->get_raw(); +} + +constexpr VirtualAddress VirtualAddress::null() { + return VirtualAddress(0x0); +} + +constexpr bool VirtualAddress::is_null() const { + return this->get_raw() == 0; +} + +template +constexpr bool VirtualAddress::is_aligned() const { + return is_aligned_genericdata), T>(this->data); +} + +/* Equality */ + +constexpr bool VirtualAddress::operator==(const VirtualAddress &other) const { + return this->get_raw() == other.get_raw(); +} + +constexpr bool VirtualAddress::operator!=(const VirtualAddress &other) const { + return this->get_raw() != other.get_raw(); +} + +/* Ordering */ + +constexpr bool VirtualAddress::operator<(const VirtualAddress &other) const { + return this->get_raw() < other.get_raw(); +} + +constexpr bool VirtualAddress::operator>(const VirtualAddress &other) const { + return this->get_raw() > other.get_raw(); +} + +constexpr bool VirtualAddress::operator<=(const VirtualAddress &other) const { + return this->get_raw() <= other.get_raw(); +} + +constexpr bool VirtualAddress::operator>=(const VirtualAddress &other) const { + return this->get_raw() >= other.get_raw(); +} + +/* Offset arithmetic */ + +constexpr VirtualAddress VirtualAddress::operator+(const uint64_t &offset) const { + return VirtualAddress(this->get_raw() + offset); +} + +constexpr VirtualAddress VirtualAddress::operator-(const uint64_t &offset) const { + return VirtualAddress(this->get_raw() - offset); +} + +void VirtualAddress::operator+=(const uint64_t &offset) { + data += offset; +} + +void VirtualAddress::operator-=(const uint64_t &offset) { + data -= offset; +} + +/* Bitwise */ + +constexpr VirtualAddress VirtualAddress::operator&(const uint64_t &mask) const { + return VirtualAddress(this->get_raw() & mask); +} + +constexpr VirtualAddress VirtualAddress::operator|(const uint64_t &mask) const { + return VirtualAddress(this->get_raw() | mask); +} + +constexpr VirtualAddress VirtualAddress::operator^(const uint64_t &mask) const { + return VirtualAddress(this->get_raw() ^ mask); +} + +constexpr VirtualAddress VirtualAddress::operator>>(const uint64_t &size) const { + return VirtualAddress(this->get_raw() >> size); +} + +constexpr VirtualAddress VirtualAddress::operator<<(const uint64_t &size) const { + return VirtualAddress(this->get_raw() << size); +} + +/* Distance arithmetic */ + +constexpr int64_t VirtualAddress::operator-(const VirtualAddress &other) const { + return this->get_raw() - other.get_raw(); +} + +} // namespace machine + +Q_DECLARE_METATYPE(machine::VirtualAddress) + +#endif // VIRTUAL_ADDRESS_H diff --git a/src/machine/simulator_exception.h b/src/machine/simulator_exception.h index de48085d..598ba50f 100644 --- a/src/machine/simulator_exception.h +++ b/src/machine/simulator_exception.h @@ -49,6 +49,17 @@ class SimulatorException : public std::exception { * As we are simulating whole 32bit memory address space then this is most * probably QtRvSim bug if raised not program. Sanity: This is sanity check * exception + * PageFault: + * A page-fault occurs during virtual memory translation when the requested + * access cannot be satisfied. Typical causes: + * - No PTE present for the accessed virtual page (page not mapped). + * - PTE is present but not valid (V == 0). + * - Permissions violation (access type not allowed by PTE: read/write/execute). + * - Malformed or unexpected PTE contents (e.g. non-leaf with R/W/X set). + * - Wrong privilege level or ASID mismatch. + * PageFault is a runtime exception raised by the page-table walker and + * is recoverable after the page-fault handler allocates pages or installs + * mappings (demand paging). */ #define SIMULATOR_EXCEPTIONS \ EXCEPTION(Input, ) \ @@ -59,6 +70,7 @@ class SimulatorException : public std::exception { EXCEPTION(UnalignedJump, Runtime) \ EXCEPTION(UnknownMemoryControl, Runtime) \ EXCEPTION(OutOfMemoryAccess, Runtime) \ + EXCEPTION(PageFault, Runtime) \ EXCEPTION(Sanity, ) \ EXCEPTION(SyscallUnknown, Runtime) From d193030d1a4ec31cdfd46c83e7b5676b5643bfb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20N=C4=9Bmec?= Date: Wed, 3 Sep 2025 20:36:12 +0200 Subject: [PATCH 06/12] GUI: show current privilege level in core state view Add privilege level mapping to the GUI so the current hart privilege (UNPRIV, SUPERV, HYPERV, MACHINE) is displayed in core state visualization. --- extras/core_graphics/diagram.drawio | 12 +++++++++++- src/gui/windows/coreview/data.h | 9 +++++++++ src/gui/windows/coreview/schemas/forwarding.svg | 17 +++++++++++++++++ src/gui/windows/coreview/schemas/pipeline.svg | 17 +++++++++++++++++ src/gui/windows/coreview/schemas/simple.svg | 17 +++++++++++++++++ src/machine/core.cpp | 4 ++-- src/machine/core/core_state.h | 10 +++++++++- 7 files changed, 82 insertions(+), 4 deletions(-) diff --git a/extras/core_graphics/diagram.drawio b/extras/core_graphics/diagram.drawio index a83e6910..204c9c66 100644 --- a/extras/core_graphics/diagram.drawio +++ b/extras/core_graphics/diagram.drawio @@ -15,7 +15,7 @@ - + @@ -968,6 +968,16 @@ + + + + + + + + + + diff --git a/src/gui/windows/coreview/data.h b/src/gui/windows/coreview/data.h index 8edbd786..35fb6bc2 100644 --- a/src/gui/windows/coreview/data.h +++ b/src/gui/windows/coreview/data.h @@ -52,6 +52,13 @@ static const std::unordered_map STALL_TEXT_TABLE = { { 2, QStringLiteral("FORWARD") }, }; +static const std::unordered_map PRIVILEGE_TEXT_TABLE = { + { static_cast(machine::CSR::PrivilegeLevel::UNPRIVILEGED), QStringLiteral("UNPRIV") }, + { static_cast(machine::CSR::PrivilegeLevel::SUPERVISOR), QStringLiteral("SUPERV") }, + { static_cast(machine::CSR::PrivilegeLevel::HYPERVISOR), QStringLiteral("HYPERV") }, + { static_cast(machine::CSR::PrivilegeLevel::MACHINE), QStringLiteral("MACHINE") }, +}; + /** * Link targets available for use in the SVG. * @@ -175,6 +182,8 @@ const struct { MULTITEXT_LENS(pipeline.memory.internal.excause_num, EXCEPTION_NAME_TABLE) }, { QStringLiteral("hazard"), MULTITEXT_LENS(pipeline.execute.internal.stall_status, STALL_TEXT_TABLE) }, + { QStringLiteral("Privilege"), + MULTITEXT_LENS(current_privilege_u, PRIVILEGE_TEXT_TABLE) }, }; const unordered_map> INSTRUCTION { diff --git a/src/gui/windows/coreview/schemas/forwarding.svg b/src/gui/windows/coreview/schemas/forwarding.svg index 1fdabac9..e06505fd 100644 --- a/src/gui/windows/coreview/schemas/forwarding.svg +++ b/src/gui/windows/coreview/schemas/forwarding.svg @@ -1000,6 +1000,23 @@ Stalls: + + + + Privilege: + + + + + + MACHINE + + Stalls: + + + + Privilege: + + + + + + MACHINE + + Stalls: + + + + Privilege: + + + + + + MACHINE + + (CSR::PrivilegeLevel::MACHINE); + + [[nodiscard]] CSR::PrivilegeLevel current_privilege() const noexcept { + return static_cast(current_privilege_u); + } + + void set_current_privilege(CSR::PrivilegeLevel p) noexcept { + current_privilege_u = static_cast(p); + } }; } // namespace machine From 5629a7e4ba9d4bb2efeaf66f634175f59d5e59ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20N=C4=9Bmec?= Date: Wed, 3 Sep 2025 21:03:48 +0200 Subject: [PATCH 07/12] GUI: add virtual memory configuration to NewDialog Extend NewDialog with controls for virtual memory setup, including TLB number of sets, associativity, and replacement policy. --- src/gui/CMakeLists.txt | 2 + src/gui/dialogs/new/NewDialog.ui | 235 ++++++++++++++++++++++++++++++ src/gui/dialogs/new/newdialog.cpp | 143 ++++++++++++++---- src/gui/dialogs/new/newdialog.h | 12 +- src/gui/ui/pow2spinbox.cpp | 81 ++++++++++ src/gui/ui/pow2spinbox.h | 19 +++ 6 files changed, 464 insertions(+), 28 deletions(-) create mode 100644 src/gui/ui/pow2spinbox.cpp create mode 100644 src/gui/ui/pow2spinbox.h diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 636f1f72..1280dc31 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -25,6 +25,7 @@ set(gui_SOURCES windows/messages/messagesview.cpp dialogs/new/newdialog.cpp ui/hexlineedit.cpp + ui/pow2spinbox.cpp windows/editor/highlighterasm.cpp windows/editor/highlighterc.cpp windows/editor/linenumberarea.cpp @@ -71,6 +72,7 @@ set(gui_HEADERS windows/messages/messagesview.h dialogs/new/newdialog.h ui/hexlineedit.h + ui/pow2spinbox.h windows/editor/highlighterasm.h windows/editor/highlighterc.h windows/editor/linenumberarea.h diff --git a/src/gui/dialogs/new/NewDialog.ui b/src/gui/dialogs/new/NewDialog.ui index 76b0afad..b427e2d9 100644 --- a/src/gui/dialogs/new/NewDialog.ui +++ b/src/gui/dialogs/new/NewDialog.ui @@ -656,6 +656,234 @@ + + + Virtual Memory + + + + + + true + + + Virtual Memory + + + true + + + true + + + + + + true + + + Program Translation Lookaside Buffer (TLB) + + + false + + + + + + Number of sets: + + + + + + + 1 + + + 1024 + + + 1 + + + QAbstractSpinBox::DefaultStepType + + + 1 + + + 10 + + + + + + + Degree of associativity: + + + + + + + 1 + + + 1 + + + 1 + + + + + + + Replacement policy: + + + + + + + + Random + + + + + Least Recently Used (LRU) + + + + + Least Frequently Used (LFU) + + + + + Pseudo Least Recently Used (PLRU) + + + + + + + + + + + true + + + Data Translation Lookaside Buffer (TLB) + + + false + + + + + + Number of sets: + + + + + + + 1 + + + 1024 + + + 1 + + + QAbstractSpinBox::DefaultStepType + + + 1 + + + 10 + + + + + + + Degree of associativity: + + + + + + + 1 + + + 1 + + + 1 + + + + + + + Replacement policy: + + + + + + + + Random + + + + + Least Recently Used (LRU) + + + + + Least Frequently Used (LFU) + + + + + Pseudo Least Recently Used (PLRU) + + + + + + + + + + + + + + Qt::Vertical + + + + 21 + 40 + + + + + + Memory Timings @@ -955,6 +1183,13 @@ + + + Pow2SpinBox + QSpinBox +
ui/pow2spinbox.h
+
+
diff --git a/src/gui/dialogs/new/newdialog.cpp b/src/gui/dialogs/new/newdialog.cpp index b6bba178..0a6b9c65 100644 --- a/src/gui/dialogs/new/newdialog.cpp +++ b/src/gui/dialogs/new/newdialog.cpp @@ -120,6 +120,29 @@ NewDialog::NewDialog(QWidget *parent, QSettings *settings) : QDialog(parent) { ui->slider_bp_bht_addr_bits, &QAbstractSlider::valueChanged, this, &NewDialog::bp_bht_addr_bits_change); + // Virtual Memory + connect( + ui->group_vm, QOverload::of(&QGroupBox::toggled), this, + &NewDialog::vm_enabled_change); + connect( + ui->itlb_number_of_sets, QOverload::of(&QSpinBox::valueChanged), this, + &NewDialog::itlb_num_sets_changed); + connect( + ui->itlb_degree_of_associativity, QOverload::of(&QSpinBox::valueChanged), this, + &NewDialog::itlb_assoc_changed); + connect( + ui->itlb_replacement_policy, QOverload::of(&QComboBox::activated), this, + &NewDialog::itlb_policy_changed); + connect( + ui->dtlb_number_of_sets, QOverload::of(&QSpinBox::valueChanged), this, + &NewDialog::dtlb_num_sets_changed); + connect( + ui->dtlb_degree_of_associativity, QOverload::of(&QSpinBox::valueChanged), this, + &NewDialog::dtlb_assoc_changed); + connect( + ui->dtlb_replacement_policy, QOverload::of(&QComboBox::activated), this, + &NewDialog::dtlb_policy_changed); + cache_handler_d = new NewDialogCacheHandler(this, ui_cache_d.data()); cache_handler_p = new NewDialogCacheHandler(this, ui_cache_p.data()); cache_handler_l2 = new NewDialogCacheHandler(this, ui_cache_l2.data()); @@ -144,9 +167,10 @@ void NewDialog::switch2page(QTreeWidgetItem *current, QTreeWidgetItem *previous) } } -void NewDialog::switch2custom() { +void NewDialog::switch_to_custom() { if (!ui->preset_custom->isChecked()) { - ui->preset_custom->setChecked(true); + ui->preset_custom->setChecked(true); // Select "Custom" preset and refresh GUI (no-op if + // already selected). config_gui(); } } @@ -238,7 +262,7 @@ void NewDialog::xlen_64bit_change(bool val) { config->set_simulated_xlen(machine::Xlen::_64); else config->set_simulated_xlen(machine::Xlen::_32); - switch2custom(); + switch_to_custom(); } void NewDialog::isa_atomic_change(bool val) { @@ -247,7 +271,7 @@ void NewDialog::isa_atomic_change(bool val) { config->modify_isa_word(isa_mask, isa_mask); else config->modify_isa_word(isa_mask, machine::ConfigIsaWord::empty()); - switch2custom(); + switch_to_custom(); } void NewDialog::isa_multiply_change(bool val) { @@ -256,18 +280,18 @@ void NewDialog::isa_multiply_change(bool val) { config->modify_isa_word(isa_mask, isa_mask); else config->modify_isa_word(isa_mask, machine::ConfigIsaWord::empty()); - switch2custom(); + switch_to_custom(); } void NewDialog::pipelined_change(bool val) { config->set_pipelined(val); ui->hazard_unit->setEnabled(config->pipelined()); - switch2custom(); + switch_to_custom(); } void NewDialog::delay_slot_change(bool val) { config->set_delay_slot(val); - switch2custom(); + switch_to_custom(); } void NewDialog::hazard_unit_change() { @@ -278,51 +302,51 @@ void NewDialog::hazard_unit_change() { } else { config->set_hazard_unit(machine::MachineConfig::HU_NONE); } - switch2custom(); + switch_to_custom(); } void NewDialog::mem_protec_exec_change(bool v) { config->set_memory_execute_protection(v); - switch2custom(); + switch_to_custom(); } void NewDialog::mem_protec_write_change(bool v) { config->set_memory_write_protection(v); - switch2custom(); + switch_to_custom(); } void NewDialog::mem_time_read_change(int v) { if (config->memory_access_time_read() != (unsigned)v) { config->set_memory_access_time_read(v); - switch2custom(); + switch_to_custom(); } } void NewDialog::mem_time_write_change(int v) { if (config->memory_access_time_write() != (unsigned)v) { config->set_memory_access_time_write(v); - switch2custom(); + switch_to_custom(); } } void NewDialog::mem_enable_burst_change(bool v) { if (config->memory_access_enable_burst() != v) { config->set_memory_access_enable_burst(v); - switch2custom(); + switch_to_custom(); } } void NewDialog::mem_time_burst_change(int v) { if (config->memory_access_time_burst() != (unsigned)v) { config->set_memory_access_time_burst(v); - switch2custom(); + switch_to_custom(); } } void NewDialog::mem_time_level2_change(int v) { if (config->memory_access_time_level2() != (unsigned)v) { config->set_memory_access_time_level2(v); - switch2custom(); + switch_to_custom(); } } @@ -450,14 +474,14 @@ void NewDialog::bp_type_change() { } bp_toggle_widgets(); - if (need_switch2custom) switch2custom(); + if (need_switch2custom) switch_to_custom(); } void NewDialog::bp_enabled_change(bool v) { if (config->get_bp_enabled() != v) { config->set_bp_enabled(v); bp_toggle_widgets(); - switch2custom(); + switch_to_custom(); } } @@ -465,14 +489,14 @@ void NewDialog::bp_init_state_change(void) { auto v = ui->select_bp_init_state->currentData().value(); if (v != config->get_bp_init_state()) { config->set_bp_init_state(v); - switch2custom(); + switch_to_custom(); } } void NewDialog::bp_btb_addr_bits_change(int v) { if (config->get_bp_btb_bits() != v) { config->set_bp_btb_bits((uint8_t)v); - switch2custom(); + switch_to_custom(); } ui->text_bp_btb_addr_bits_number->setText(QString::number(config->get_bp_btb_bits())); ui->text_bp_btb_bits_number->setText(QString::number(config->get_bp_btb_bits())); @@ -489,7 +513,7 @@ void NewDialog::bp_bht_bits_texts_update(void) { void NewDialog::bp_bht_bhr_bits_change(int v) { if (config->get_bp_bhr_bits() != v) { config->set_bp_bhr_bits((uint8_t)v); - switch2custom(); + switch_to_custom(); } bp_bht_bits_texts_update(); } @@ -497,11 +521,68 @@ void NewDialog::bp_bht_bhr_bits_change(int v) { void NewDialog::bp_bht_addr_bits_change(int v) { if (config->get_bp_bht_addr_bits() != v) { config->set_bp_bht_addr_bits((uint8_t)v); - switch2custom(); + switch_to_custom(); } bp_bht_bits_texts_update(); } +void NewDialog::vm_enabled_change(bool v) { + if (config->get_vm_enabled() != v) { + config->set_vm_enabled(v); + switch_to_custom(); + } +} + +void NewDialog::itlb_num_sets_changed(int v) { + unsigned u = v >= 0? v : 0; + if (config->access_tlb_program()->get_tlb_num_sets() != u) { + config->access_tlb_program()->set_tlb_num_sets(u); + switch_to_custom(); + } +} + +void NewDialog::itlb_assoc_changed(int v) { + unsigned u = v >= 0? v : 0; + if (config->access_tlb_program()->get_tlb_associativity() != u) { + config->access_tlb_program()->set_tlb_associativity(u); + switch_to_custom(); + } +} + +void NewDialog::itlb_policy_changed(int idx) { + machine::TLBConfig::ReplacementPolicy pol; + pol = static_cast(idx); + if (config->access_tlb_program()->get_tlb_replacement_policy() != pol) { + config->access_tlb_program()->set_tlb_replacement_policy(pol); + switch_to_custom(); + } +} + +void NewDialog::dtlb_num_sets_changed(int v) { + unsigned u = v >= 0? v : 0; + if (config->access_tlb_data()->get_tlb_num_sets() != u) { + config->access_tlb_data()->set_tlb_num_sets(u); + switch_to_custom(); + } +} + +void NewDialog::dtlb_assoc_changed(int v) { + unsigned u = v >= 0? v : 0; + if (config->access_tlb_data()->get_tlb_associativity() != u) { + config->access_tlb_data()->set_tlb_associativity(u); + switch_to_custom(); + } +} + +void NewDialog::dtlb_policy_changed(int idx) { + machine::TLBConfig::ReplacementPolicy pol; + pol = static_cast(idx); + if (config->access_tlb_data()->get_tlb_replacement_policy() != pol) { + config->access_tlb_data()->set_tlb_replacement_policy(pol); + switch_to_custom(); + } +} + void NewDialog::config_gui() { // Basic ui->elf_file->setText(config->elf()); @@ -561,6 +642,14 @@ void NewDialog::config_gui() { ui->text_bp_bht_entries_number->setText(QString::number(qPow(2, config->get_bp_bht_bits()))); bp_type_change(); + // Virtual + ui->group_vm->setChecked(config->get_vm_enabled()); + ui->itlb_number_of_sets->setValue(config->tlbc_program().get_tlb_num_sets()); + ui->itlb_degree_of_associativity->setValue(config->tlbc_program().get_tlb_associativity()); + ui->itlb_replacement_policy->setCurrentIndex((int)config->tlbc_program().get_tlb_replacement_policy()); + ui->dtlb_number_of_sets->setValue(config->tlbc_data().get_tlb_num_sets()); + ui->dtlb_degree_of_associativity->setValue(config->tlbc_data().get_tlb_associativity()); + ui->dtlb_replacement_policy->setCurrentIndex((int)config->tlbc_data().get_tlb_replacement_policy()); // Memory ui->mem_protec_exec->setChecked(config->memory_execute_protection()); ui->mem_protec_write->setChecked(config->memory_write_protection()); @@ -674,30 +763,30 @@ void NewDialogCacheHandler::config_gui() { void NewDialogCacheHandler::enabled(bool val) { config->set_enabled(val); - nd->switch2custom(); + nd->switch_to_custom(); } void NewDialogCacheHandler::numsets() { config->set_set_count(ui->number_of_sets->value()); - nd->switch2custom(); + nd->switch_to_custom(); } void NewDialogCacheHandler::blocksize() { config->set_block_size(ui->block_size->value()); - nd->switch2custom(); + nd->switch_to_custom(); } void NewDialogCacheHandler::degreeassociativity() { config->set_associativity(ui->degree_of_associativity->value()); - nd->switch2custom(); + nd->switch_to_custom(); } void NewDialogCacheHandler::replacement(int val) { config->set_replacement_policy((enum machine::CacheConfig::ReplacementPolicy)val); - nd->switch2custom(); + nd->switch_to_custom(); } void NewDialogCacheHandler::writeback(int val) { config->set_write_policy((enum machine::CacheConfig::WritePolicy)val); - nd->switch2custom(); + nd->switch_to_custom(); } diff --git a/src/gui/dialogs/new/newdialog.h b/src/gui/dialogs/new/newdialog.h index c8c7175e..f2f7c8c1 100644 --- a/src/gui/dialogs/new/newdialog.h +++ b/src/gui/dialogs/new/newdialog.h @@ -19,7 +19,7 @@ class NewDialog : public QDialog { public: NewDialog(QWidget *parent, QSettings *settings); - void switch2custom(); + void switch_to_custom(); protected: void closeEvent(QCloseEvent *) override; @@ -64,6 +64,16 @@ private slots: void bp_bht_bhr_bits_change(int); void bp_bht_addr_bits_change(int); + + // Virtual Memory + void vm_enabled_change(bool); + void itlb_num_sets_changed(int); + void itlb_assoc_changed(int); + void itlb_policy_changed(int); + void dtlb_num_sets_changed(int); + void dtlb_assoc_changed(int); + void dtlb_policy_changed(int); + private: Box ui {}; Box ui_cache_p {}, ui_cache_d {}, ui_cache_l2 {}; diff --git a/src/gui/ui/pow2spinbox.cpp b/src/gui/ui/pow2spinbox.cpp new file mode 100644 index 00000000..4c84fa8f --- /dev/null +++ b/src/gui/ui/pow2spinbox.cpp @@ -0,0 +1,81 @@ +#include "pow2spinbox.h" + +Pow2SpinBox::Pow2SpinBox(QWidget *parent) : QSpinBox(parent) { + setRange(1, 1024); + setValue(1); +} + +QValidator::State Pow2SpinBox::validate(QString &input, int &pos) const { + Q_UNUSED(pos); + + if (input.isEmpty()) return QValidator::Intermediate; + + bool ok = false; + qint64 v = input.toLongLong(&ok); + if (!ok || v <= 0) return QValidator::Invalid; + + if ((v & (v - 1)) == 0) return QValidator::Acceptable; + + return QValidator::Intermediate; +} + +int Pow2SpinBox::valueFromText(const QString &text) const { + return text.toInt(); +} + +QString Pow2SpinBox::textFromValue(int value) const { + return QString::number(value); +} + +void Pow2SpinBox::stepBy(int steps) { + int v = value(); + if (v < 1) v = 1; + + auto isPow2 = [](int x) { return x > 0 && (x & (x - 1)) == 0; }; + + auto nextPow2 = [](int x) -> int { + if (x <= 1) return 1; + int p = 1; + while (p < x && (p << 1) > 0) { + p <<= 1; + } + return p; + }; + + auto prevPow2 = [](int x) -> int { + if (x <= 1) return 1; + int p = 1; + while ((p << 1) <= x) { + p <<= 1; + } + if (p > x) { p >>= 1; } + return p; + }; + + if (steps > 0) { + if (!isPow2(v)) { + v = nextPow2(v); + } else { + for (int i = 0; i < steps; ++i) { + if (v > (maximum() >> 1)) { + v = maximum(); + break; + } + v <<= 1; + } + } + } else { + if (!isPow2(v)) { + v = prevPow2(v); + } else { + for (int i = 0; i < -steps; ++i) { + if (v <= 1) { + v = 1; + break; + } + v >>= 1; + } + } + } + setValue(qBound(minimum(), v, maximum())); +} diff --git a/src/gui/ui/pow2spinbox.h b/src/gui/ui/pow2spinbox.h new file mode 100644 index 00000000..a26e7c1c --- /dev/null +++ b/src/gui/ui/pow2spinbox.h @@ -0,0 +1,19 @@ +#ifndef POW2SPINBOX_H +#define POW2SPINBOX_H + +#include +#include + +class Pow2SpinBox : public QSpinBox { + Q_OBJECT +public: + explicit Pow2SpinBox(QWidget *parent = nullptr); + +protected: + QValidator::State validate(QString &input, int &pos) const override; + int valueFromText(const QString &text) const override; + QString textFromValue(int value) const override; + void stepBy(int steps) override; +}; + +#endif // POW2SPINBOX_H From e3102017697ac714c03e4c834be39791eb771756 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20N=C4=9Bmec?= Date: Wed, 3 Sep 2025 21:25:22 +0200 Subject: [PATCH 08/12] GUI: add TLB visualization and statistics dock Introduce new components for displaying and tracking TLB state similar to cache. TLBViewBlock and TLBAddressBlock render per-set and per-way TLB contents, updated on tlb_update signals. TLBViewScene assembles these views based on associativity. TLBDock integrates into the GUI, showing hit/miss counts, memory accesses, stall cycles, hit rate, and speed improvement, with live updates from the TLB. --- src/gui/CMakeLists.txt | 16 ++- src/gui/mainwindow/MainWindow.ui | 20 +++- src/gui/mainwindow/mainwindow.cpp | 12 +- src/gui/mainwindow/mainwindow.h | 6 + src/gui/windows/tlb/tlbdock.cpp | 139 +++++++++++++++++++++++ src/gui/windows/tlb/tlbdock.h | 57 ++++++++++ src/gui/windows/tlb/tlbview.cpp | 179 ++++++++++++++++++++++++++++++ src/gui/windows/tlb/tlbview.h | 64 +++++++++++ 8 files changed, 485 insertions(+), 8 deletions(-) create mode 100644 src/gui/windows/tlb/tlbdock.cpp create mode 100644 src/gui/windows/tlb/tlbdock.h create mode 100644 src/gui/windows/tlb/tlbview.cpp create mode 100644 src/gui/windows/tlb/tlbview.h diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 1280dc31..074a5f26 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -26,6 +26,8 @@ set(gui_SOURCES dialogs/new/newdialog.cpp ui/hexlineedit.cpp ui/pow2spinbox.cpp + windows/tlb/tlbview.cpp + windows/tlb/tlbdock.cpp windows/editor/highlighterasm.cpp windows/editor/highlighterc.cpp windows/editor/linenumberarea.cpp @@ -53,7 +55,7 @@ set(gui_SOURCES windows/predictor/predictor_btb_dock.cpp windows/predictor/predictor_bht_dock.cpp windows/predictor/predictor_info_dock.cpp - ) +) set(gui_HEADERS dialogs/about/aboutdialog.h windows/cache/cachedock.h @@ -73,6 +75,8 @@ set(gui_HEADERS dialogs/new/newdialog.h ui/hexlineedit.h ui/pow2spinbox.h + windows/tlb/tlbview.h + windows/tlb/tlbdock.h windows/editor/highlighterasm.h windows/editor/highlighterc.h windows/editor/linenumberarea.h @@ -100,19 +104,19 @@ set(gui_HEADERS windows/predictor/predictor_btb_dock.h windows/predictor/predictor_bht_dock.h windows/predictor/predictor_info_dock.h - ) +) set(gui_UI dialogs/gotosymbol/gotosymboldialog.ui dialogs/new/NewDialog.ui windows/peripherals/peripheralsview.ui mainwindow/MainWindow.ui dialogs/new/NewDialogCache.ui - ) +) set(gui_RESOURCES resources/icons/icons.qrc resources/samples/samples.qrc windows/coreview/schemas/schemas.qrc - ) +) if ("${WASM}") @@ -162,7 +166,7 @@ set_target_properties(gui PROPERTIES MACOSX_BUNDLE_BUNDLE_VERSION "${MAIN_PROJECT_VERSION}" MACOSX_BUNDLE_SHORT_VERSION_STRING "${MAIN_PROJECT_VERSION}" MACOSX_BUNDLE_ICONFILE ${ICON_NAME} - ) +) # END MACOS # ============================================================================= @@ -176,5 +180,5 @@ set_target_properties(gui PROPERTIES install(TARGETS gui RUNTIME DESTINATION bin BUNDLE DESTINATION ${EXECUTABLE_OUTPUT_PATH} - ) +) diff --git a/src/gui/mainwindow/MainWindow.ui b/src/gui/mainwindow/MainWindow.ui index 11de9c22..0edbd8de 100644 --- a/src/gui/mainwindow/MainWindow.ui +++ b/src/gui/mainwindow/MainWindow.ui @@ -53,7 +53,7 @@ 0 0 900 - 20 + 19 @@ -87,6 +87,8 @@ + + @@ -647,6 +649,22 @@ Branch Predictor (Info) + + + &Data TLB + + + Data TLB + + + + + Instruction TLB + + + Instruction TLB + + diff --git a/src/gui/mainwindow/mainwindow.cpp b/src/gui/mainwindow/mainwindow.cpp index 7e36afdf..fa56b237 100644 --- a/src/gui/mainwindow/mainwindow.cpp +++ b/src/gui/mainwindow/mainwindow.cpp @@ -115,6 +115,10 @@ MainWindow::MainWindow(QSettings *settings, QWidget *parent) cache_data->hide(); cache_level2.reset(new CacheDock(this, "L2")); cache_level2->hide(); + tlb_program.reset(new TLBDock(this, "Instruction")); + tlb_program->hide(); + tlb_data.reset(new TLBDock(this, "Data")); + tlb_data->hide(); bp_btb.reset(new DockPredictorBTB(this)); bp_btb->hide(); bp_bht.reset(new DockPredictorBHT(this)); @@ -161,7 +165,8 @@ MainWindow::MainWindow(QSettings *settings, QWidget *parent) connect(ui->actionProgram_Cache, &QAction::triggered, this, &MainWindow::show_cache_program); connect(ui->actionData_Cache, &QAction::triggered, this, &MainWindow::show_cache_data); connect(ui->actionL2_Cache, &QAction::triggered, this, &MainWindow::show_cache_level2); - + connect(ui->actionInstruction_TLB, &QAction::triggered, this, &MainWindow::show_tlb_program); + connect(ui->actionData_TLB, &QAction::triggered, this, &MainWindow::show_tlb_data); // Branch predictor connect( ui->actionBranch_Predictor_History_table, &QAction::triggered, this, @@ -336,6 +341,8 @@ void MainWindow::create_core( cache_data->setup(machine->cache_data()); bool cache_after_cache = config.cache_data().enabled() || config.cache_program().enabled(); cache_level2->setup(machine->cache_level2(), cache_after_cache); + tlb_program->setup(machine->get_tlb_program_rw()); + tlb_data->setup(machine->get_tlb_data_rw()); // Branch predictor bp_btb->setup(machine->core()->get_predictor(), machine->core()); @@ -447,6 +454,8 @@ SHOW_HANDLER(memory, Qt::RightDockWidgetArea, true) SHOW_HANDLER(cache_program, Qt::RightDockWidgetArea, false) SHOW_HANDLER(cache_data, Qt::RightDockWidgetArea, false) SHOW_HANDLER(cache_level2, Qt::RightDockWidgetArea, false) +SHOW_HANDLER(tlb_program, Qt::RightDockWidgetArea, false) +SHOW_HANDLER(tlb_data, Qt::RightDockWidgetArea, false) SHOW_HANDLER(bp_btb, Qt::RightDockWidgetArea, false) SHOW_HANDLER(bp_bht, Qt::RightDockWidgetArea, false) SHOW_HANDLER(bp_info, Qt::RightDockWidgetArea, false) @@ -783,6 +792,7 @@ void MainWindow::compile_source() { } machine->cache_sync(); + machine->tlb_sync(); auto editor = editor_tabs->get_current_editor(); auto filename = editor->filename().isEmpty() ? "Unknown" : editor->filename(); diff --git a/src/gui/mainwindow/mainwindow.h b/src/gui/mainwindow/mainwindow.h index e206a3e0..cecee5a5 100644 --- a/src/gui/mainwindow/mainwindow.h +++ b/src/gui/mainwindow/mainwindow.h @@ -28,6 +28,7 @@ #include "windows/program/programdock.h" #include "windows/registers/registersdock.h" #include "windows/terminal/terminaldock.h" +#include "windows/tlb/tlbdock.h" #include #include @@ -78,6 +79,8 @@ public slots: void reset_state_cache_program(); void reset_state_cache_data(); void reset_state_cache_level2(); + void reset_state_tlb_program(); + void reset_state_tlb_data(); void reset_state_peripherals(); void reset_state_terminal(); void reset_state_lcd_display(); @@ -89,6 +92,8 @@ public slots: void show_cache_data(); void show_cache_program(); void show_cache_level2(); + void show_tlb_program(); + void show_tlb_data(); void show_peripherals(); void show_terminal(); void show_lcd_display(); @@ -143,6 +148,7 @@ public slots: Box program {}; Box memory {}; Box cache_program {}, cache_data {}, cache_level2 {}; + Box tlb_program {}, tlb_data {}; // Branch predictor Box bp_btb {}; diff --git a/src/gui/windows/tlb/tlbdock.cpp b/src/gui/windows/tlb/tlbdock.cpp new file mode 100644 index 00000000..a14bcf02 --- /dev/null +++ b/src/gui/windows/tlb/tlbdock.cpp @@ -0,0 +1,139 @@ +#include "tlbdock.h" + +TLBDock::TLBDock(QWidget *parent, const QString &type) + : QDockWidget(parent) + , tlbscene(nullptr) + , connected_tlb(nullptr) { + top_widget = new QWidget(this); + setWidget(top_widget); + layout_box = new QVBoxLayout(top_widget); + + top_form = new QWidget(top_widget); + top_form->setVisible(false); + layout_box->addWidget(top_form); + layout_top_form = new QFormLayout(top_form); + + l_hit = new QLabel("0", top_form); + l_hit->setTextFormat(Qt::PlainText); + layout_top_form->addRow("Hit:", l_hit); + + l_miss = new QLabel("0", top_form); + l_miss->setTextFormat(Qt::PlainText); + layout_top_form->addRow("Miss:", l_miss); + + l_m_reads = new QLabel("0", top_form); + l_m_reads->setTextFormat(Qt::PlainText); + layout_top_form->addRow("Memory reads:", l_m_reads); + + l_m_writes = new QLabel("0", top_form); + l_m_writes->setTextFormat(Qt::PlainText); + layout_top_form->addRow("Memory writes:", l_m_writes); + + l_stalled = new QLabel("0", top_form); + l_stalled->setTextFormat(Qt::PlainText); + layout_top_form->addRow("Memory stall cycles:", l_stalled); + + l_hit_rate = new QLabel("0.000%", top_form); + l_hit_rate->setTextFormat(Qt::PlainText); + layout_top_form->addRow("Hit rate:", l_hit_rate); + + l_speed = new QLabel("100%", top_form); + l_speed->setTextFormat(Qt::PlainText); + layout_top_form->addRow("Improved speed:", l_speed); + + graphicsview = new GraphicsView(top_widget); + graphicsview->setVisible(false); + layout_box->addWidget(graphicsview); + tlbscene = nullptr; + + no_tlb = new QLabel("No " + type + " TLB configured", top_widget); + layout_box->addWidget(no_tlb); + + setObjectName(type + "TLB"); + setWindowTitle(type + " TLB"); +} + +void TLBDock::setup(machine::TLB *tlb) { + memory_reads = 0; + memory_writes = 0; + hit = 0; + miss = 0; + stalled = 0; + speed_improv = 0.0; + hit_rate = 0.0; + + l_hit->setText("0"); + l_miss->setText("0"); + l_stalled->setText("0"); + l_m_reads->setText("0"); + l_m_writes->setText("0"); + l_hit_rate->setText("0.000%"); + l_speed->setText("100%"); + + if (tlb != nullptr) { + connect(tlb, &machine::TLB::hit_update, this, &TLBDock::hit_update, Qt::UniqueConnection); + connect(tlb, &machine::TLB::miss_update, this, &TLBDock::miss_update, Qt::UniqueConnection); + connect( + tlb, &machine::TLB::statistics_update, this, &TLBDock::statistics_update, + Qt::UniqueConnection); + connect( + tlb, &machine::TLB::memory_reads_update, this, &TLBDock::memory_reads_update, + Qt::UniqueConnection); + connect( + tlb, &machine::TLB::memory_writes_update, this, &TLBDock::memory_writes_update, + Qt::UniqueConnection); + } + connected_tlb = const_cast(tlb); + top_form->setVisible(tlb != nullptr); + no_tlb->setVisible(tlb == nullptr); + + delete tlbscene; + tlbscene = nullptr; + if (tlb != nullptr) { + tlbscene = new TLBViewScene(tlb); + graphicsview->setScene(tlbscene); + } else { + graphicsview->setScene(nullptr); + } + graphicsview->setVisible(tlb != nullptr); +} + +void TLBDock::paintEvent(QPaintEvent *event) { + l_stalled->setText(QString::number(stalled)); + l_hit_rate->setText(QString::number(hit_rate, 'f', 3) + QString("%")); + l_speed->setText(QString::number(speed_improv, 'f', 0) + QString("%")); + l_hit->setText(QString::number(hit)); + l_miss->setText(QString::number(miss)); + l_m_reads->setText(QString::number(memory_reads)); + l_m_writes->setText(QString::number(memory_writes)); + QDockWidget::paintEvent(event); +} + +void TLBDock::hit_update(unsigned val) { + hit = val; + update(); +} + +void TLBDock::miss_update(unsigned val) { + miss = val; + update(); +} + +void TLBDock::statistics_update(unsigned stalled_cycles, double speed_improv_v, double hit_rate_v) { + stalled = stalled_cycles; + speed_improv = speed_improv_v; + hit_rate = hit_rate_v; + update(); +} + +void TLBDock::memory_reads_update(unsigned val) { + memory_reads = val; + l_m_reads->setText(QString::number(memory_reads)); + update(); +} + +void TLBDock::memory_writes_update(unsigned val) { + memory_writes = val; + l_m_writes->setText(QString::number(memory_writes)); + update(); +} diff --git a/src/gui/windows/tlb/tlbdock.h b/src/gui/windows/tlb/tlbdock.h new file mode 100644 index 00000000..a47155c7 --- /dev/null +++ b/src/gui/windows/tlb/tlbdock.h @@ -0,0 +1,57 @@ +#ifndef TLBDOCK_H +#define TLBDOCK_H + +#include "graphicsview.h" +#include "machine/machine.h" +#include "tlbview.h" + +#include +#include +#include +#include + +class TLBDock : public QDockWidget { + Q_OBJECT +public: + TLBDock(QWidget *parent, const QString &type); + + void setup(machine::TLB *tlb); + + void paintEvent(QPaintEvent *event) override; + +private slots: + void hit_update(unsigned val); + void miss_update(unsigned val); + void statistics_update(unsigned stalled_cycles, double speed_improv, double hit_rate); + void memory_reads_update(unsigned val); + void memory_writes_update(unsigned val); + +private: + QVBoxLayout *layout_box; + QWidget *top_widget; + QWidget *top_form; + QFormLayout *layout_top_form; + + QLabel *l_hit; + QLabel *l_miss; + QLabel *l_stalled; + QLabel *l_speed; + QLabel *l_hit_rate; + QLabel *no_tlb; + QLabel *l_m_reads; + QLabel *l_m_writes; + + GraphicsView *graphicsview; + TLBViewScene *tlbscene; + + unsigned memory_reads = 0; + unsigned memory_writes = 0; + unsigned hit = 0; + unsigned miss = 0; + unsigned stalled = 0; + double speed_improv = 0.0; + double hit_rate = 0.0; + + QPointer connected_tlb; +}; +#endif // TLBDOCK_H diff --git a/src/gui/windows/tlb/tlbview.cpp b/src/gui/windows/tlb/tlbview.cpp new file mode 100644 index 00000000..a1c2c529 --- /dev/null +++ b/src/gui/windows/tlb/tlbview.cpp @@ -0,0 +1,179 @@ +#include "tlbview.h" + +#include +#include +#include +#include + +static const int ROW_HEIGHT = 16; +static const int VCOL_WIDTH = 18; +static const int FIELD_WIDTH = 120; + +TLBAddressBlock::TLBAddressBlock(machine::TLB *tlb, unsigned width) { + this->width = width; + rows = tlb->get_config().get_tlb_num_sets(); + s_row = rows > 1 ? (32 - __builtin_clz(rows - 1)) : 0; + s_tag = 32 - s_row - 2; + tag = 0; + row = 0; + + connect(tlb, &machine::TLB::tlb_update, this, &TLBAddressBlock::tlb_update); +} + +QRectF TLBAddressBlock::boundingRect() const { + return QRectF(0, 0, width, 40); +} + +void TLBAddressBlock::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) { + QFont fnt; + fnt.setPointSize(8); + painter->setFont(fnt); + + painter->drawText(QRectF(0, 0, width, 14), Qt::AlignCenter, "TLB Address"); + painter->drawText(QRectF(5, 18, 100, 16), Qt::AlignLeft, QString("Set: %1").arg(row)); +} + +void TLBAddressBlock::tlb_update(unsigned, unsigned set, bool, unsigned, quint64, quint64, bool) { + this->row = set; + update(); +} + +/////////////////////////////////////////// + +TLBViewBlock::TLBViewBlock(machine::TLB *tlb, unsigned way) : tlb(tlb), way_index(way) { + rows = tlb->get_config().get_tlb_num_sets(); + curr_row = 0; + last_set = 0; + last_highlighted = false; + + QFont font; + font.setPixelSize(10); + + validity.reserve(rows); + asid.reserve(rows); + vpn.reserve(rows); + phys.reserve(rows); + + int y = 2; + for (unsigned i = 0; i < rows; ++i) { + int x = 2; + auto *v = new QGraphicsSimpleTextItem("0", this); + v->setFont(font); + v->setPos(x, y); + x += VCOL_WIDTH; + + auto *a = new QGraphicsSimpleTextItem("", this); + a->setFont(font); + a->setPos(x, y); + x += 60; + + auto *vpnItem = new QGraphicsSimpleTextItem("", this); + vpnItem->setFont(font); + vpnItem->setPos(x, y); + x += FIELD_WIDTH; + + auto *physItem = new QGraphicsSimpleTextItem("", this); + physItem->setFont(font); + physItem->setPos(x, y); + + validity.push_back(v); + asid.push_back(a); + vpn.push_back(vpnItem); + phys.push_back(physItem); + + y += ROW_HEIGHT; + } + + auto *l_v = new QGraphicsSimpleTextItem("V", this); + l_v->setFont(font); + l_v->setPos(2, -14); + + auto *l_asid = new QGraphicsSimpleTextItem("ASID", this); + l_asid->setFont(font); + l_asid->setPos(2 + VCOL_WIDTH + 2, -14); + + auto *l_vpn = new QGraphicsSimpleTextItem("VPN", this); + l_vpn->setFont(font); + l_vpn->setPos(2 + VCOL_WIDTH + 62, -14); + + auto *l_phys = new QGraphicsSimpleTextItem("PBASE", this); + l_phys->setFont(font); + l_phys->setPos(2 + VCOL_WIDTH + 62 + FIELD_WIDTH, -14); + + connect(tlb, &machine::TLB::tlb_update, this, &TLBViewBlock::tlb_update); +} + +QRectF TLBViewBlock::boundingRect() const { + return QRectF(-2, -18, VCOL_WIDTH + 60 + FIELD_WIDTH + 200, rows * ROW_HEIGHT + 40); +} + +void TLBViewBlock::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) { + int width = boundingRect().width(); + painter->drawRect(0, 0, width, rows * ROW_HEIGHT); + for (unsigned i = 0; i <= rows; ++i) { + painter->drawLine(0, i * ROW_HEIGHT, width, i * ROW_HEIGHT); + } +} + +void TLBViewBlock::tlb_update( + unsigned way, + unsigned set, + bool valid, + unsigned asid_v, + quint64 vpn_v, + quint64 phys_v, + bool write) { + if (way != way_index) return; + validity[set]->setText(valid ? "1" : "0"); + asid[set]->setText(valid ? QString::number(asid_v) : QString()); + vpn[set]->setText( + valid ? QString("0x%1").arg(QString::number(vpn_v, 16).toUpper()) : QString()); + phys[set]->setText( + valid ? QString("0x%1").arg(QString::number(phys_v, 16).toUpper()) : QString()); + + if (last_highlighted) { + validity[last_set]->setBrush(QBrush(QColor(0, 0, 0))); + asid[last_set]->setBrush(QBrush(QColor(0, 0, 0))); + vpn[last_set]->setBrush(QBrush(QColor(0, 0, 0))); + phys[last_set]->setBrush(QBrush(QColor(0, 0, 0))); + } + if (valid) { + QColor c = write ? QColor(200, 0, 0) : QColor(0, 0, 150); + validity[set]->setBrush(QBrush(c)); + asid[set]->setBrush(QBrush(c)); + vpn[set]->setBrush(QBrush(c)); + phys[set]->setBrush(QBrush(c)); + } + last_highlighted = true; + last_set = set; +} + +/////////////////////////////////////////// + +TLBViewScene::TLBViewScene(machine::TLB *tlb) { + associativity = tlb->get_config().get_tlb_associativity(); + block.reserve(associativity); + int offset = 0; + for (unsigned i = 0; i < associativity; ++i) { + auto b = std::make_unique(tlb, i); + addItem(b.get()); + b->setPos(1, offset); + offset += b->boundingRect().height(); + block.push_back(std::move(b)); + } + ablock = std::make_unique( + tlb, block.empty() ? FIELD_WIDTH : static_cast(block[0]->boundingRect().width())); + addItem(ablock.get()); + ablock->setPos(0, -ablock->boundingRect().height() - 16); +} + +TLBViewScene::~TLBViewScene() { + for (auto &bptr : block) { + if (bptr) removeItem(bptr.get()); + } + block.clear(); + if (ablock) { + removeItem(ablock.get()); + ablock.reset(); + } +} diff --git a/src/gui/windows/tlb/tlbview.h b/src/gui/windows/tlb/tlbview.h new file mode 100644 index 00000000..4ceeb81c --- /dev/null +++ b/src/gui/windows/tlb/tlbview.h @@ -0,0 +1,64 @@ +#ifndef TLBVIEW_H +#define TLBVIEW_H + +#include "machine/memory/tlb/tlb.h" +#include "svgscene/utils/memory_ownership.h" + +#include +#include +#include +#include + +class TLBAddressBlock : public QGraphicsObject { + Q_OBJECT +public: + TLBAddressBlock(machine::TLB *tlb, unsigned width); + QRectF boundingRect() const override; + void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) override; + + public slots: + void tlb_update(unsigned way, unsigned set, bool valid, unsigned asid, quint64 vpn, quint64 phys, bool write); + +private: + unsigned width; + unsigned rows; + unsigned tag, row; + unsigned s_row, s_tag; +}; + +class TLBViewBlock : public QGraphicsObject { + Q_OBJECT +public: + TLBViewBlock(machine::TLB *tlb, unsigned way); + ~TLBViewBlock() override = default; + QRectF boundingRect() const override; + void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) override; + + public slots: + void tlb_update(unsigned way, unsigned set, bool valid, unsigned asid, quint64 vpn, quint64 phys, bool write); + +private: + QPointer tlb; + unsigned way_index; + unsigned rows; + std::vector validity; + std::vector asid; + std::vector vpn; + std::vector phys; + unsigned curr_row; + unsigned last_set; + bool last_highlighted; +}; + +class TLBViewScene : public QGraphicsScene { +public: + TLBViewScene(machine::TLB *tlb); + ~TLBViewScene() override; + +private: + unsigned associativity; + std::vector> block; + std::unique_ptr ablock; +}; + +#endif // TLBVIEW_H From 3c242259c42e0c43944c757d5a1668290192160c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20N=C4=9Bmec?= Date: Wed, 3 Sep 2025 22:02:09 +0200 Subject: [PATCH 09/12] GUI: add "As CPU (VMA)" memory access view Introduce an "As CPU (VMA)" access option in the cached access selector to render memory contents as observed by the CPU through the frontend interface. --- src/gui/windows/memory/memorydock.cpp | 3 +- src/gui/windows/memory/memorydock.h | 9 ++-- src/gui/windows/memory/memorymodel.cpp | 58 +++++++++++++++++++++----- src/gui/windows/memory/memorymodel.h | 13 +++++- src/machine/machine.h | 8 ++++ 5 files changed, 72 insertions(+), 19 deletions(-) diff --git a/src/gui/windows/memory/memorydock.cpp b/src/gui/windows/memory/memorydock.cpp index 2e22d56f..c4c0179d 100644 --- a/src/gui/windows/memory/memorydock.cpp +++ b/src/gui/windows/memory/memorydock.cpp @@ -24,6 +24,7 @@ MemoryDock::MemoryDock(QWidget *parent, QSettings *settings) : Super(parent) { auto *cached_access = new QComboBox(); cached_access->addItem("Direct", 0); cached_access->addItem("Cached", 1); + cached_access->addItem("As CPU (VMA)", 2); auto *memory_content = new MemoryTableView(nullptr, settings); // memory_content->setSizePolicy(); @@ -37,7 +38,6 @@ MemoryDock::MemoryDock(QWidget *parent, QSettings *settings) : Super(parent) { auto *layout_top = new QHBoxLayout; layout_top->addWidget(cell_size); layout_top->addWidget(cached_access); - auto *layout = new QVBoxLayout; layout->addLayout(layout_top); layout->addWidget(memory_content); @@ -47,6 +47,7 @@ MemoryDock::MemoryDock(QWidget *parent, QSettings *settings) : Super(parent) { setWidget(content); + connect(this, &MemoryDock::machine_setup, memory_model, &MemoryModel::setup); connect(this, &MemoryDock::machine_setup, memory_model, &MemoryModel::setup); connect( cell_size, QOverload::of(&QComboBox::currentIndexChanged), memory_content, diff --git a/src/gui/windows/memory/memorydock.h b/src/gui/windows/memory/memorydock.h index 5b103be6..53528eb9 100644 --- a/src/gui/windows/memory/memorydock.h +++ b/src/gui/windows/memory/memorydock.h @@ -3,10 +3,8 @@ #include "machine/machine.h" #include "machine/memory/address.h" - #include #include -#include class MemoryDock : public QDockWidget { Q_OBJECT @@ -18,11 +16,12 @@ class MemoryDock : public QDockWidget { void setup(machine::Machine *machine); -signals: - void machine_setup(machine::Machine *machine); + signals: + void machine_setup(machine::Machine *machine); void focus_addr(machine::Address); private: + machine::Machine *machinePtr; }; -#endif // MEMORYDOCK_H +#endif // MEMORYDOCK_H \ No newline at end of file diff --git a/src/gui/windows/memory/memorymodel.cpp b/src/gui/windows/memory/memorymodel.cpp index 3698e53c..30f36744 100644 --- a/src/gui/windows/memory/memorymodel.cpp +++ b/src/gui/windows/memory/memorymodel.cpp @@ -12,7 +12,7 @@ MemoryModel::MemoryModel(QObject *parent) : Super(parent), data_font("Monospace" machine = nullptr; memory_change_counter = 0; cache_data_change_counter = 0; - access_through_cache = 0; + mem_access_kind = MEM_ACC_AS_CPU; } const machine::FrontendMemory *MemoryModel::mem_access() const { @@ -59,7 +59,7 @@ QVariant MemoryModel::data(const QModelIndex &index, int role) const { QString s, t; machine::Address address; uint32_t data; - const machine::FrontendMemory *mem; + const machine::FrontendMemory *mem = nullptr; if (!get_row_address(address, index.row())) { return QString(""); } if (index.column() == 0) { t = QString::number(address.get_raw(), 16); @@ -67,11 +67,20 @@ QVariant MemoryModel::data(const QModelIndex &index, int role) const { return { QString("0x") + s + t }; } if (machine == nullptr) { return QString(""); } - mem = mem_access(); - if (mem == nullptr) { return QString(""); } - if ((access_through_cache > 0) && (machine->cache_data() != nullptr)) { - mem = machine->cache_data(); + bool vm_enabled = machine->config().get_vm_enabled(); + if (!vm_enabled) { + mem = mem_access(); + if ((mem_access_kind > MEM_ACC_AS_CPU) && (machine->cache_data() != nullptr)) { + mem = machine->cache_data(); + } + } else { + if (mem_access_kind == MEM_ACC_PHYS_ADDR) { + mem = machine->get_tlb_data(); + } else { + mem = mem_access_phys(); + } } + if (mem == nullptr) { return QString(""); } address += cellSizeBytes() * (index.column() - 1); if (address < index0_offset) { return QString(""); } switch (cell_size) { @@ -192,12 +201,10 @@ bool MemoryModel::adjustRowAndOffset(int &row, machine::Address address) { } return get_row_for_address(row, address); } - void MemoryModel::cached_access(int cached) { - access_through_cache = cached; + mem_access_kind = cached; update_all(); } - Qt::ItemFlags MemoryModel::flags(const QModelIndex &index) const { if (index.column() == 0) { return QAbstractTableModel::flags(index); @@ -215,9 +222,20 @@ bool MemoryModel::setData(const QModelIndex &index, const QVariant &value, int r if (!ok) { return false; } if (!get_row_address(address, index.row())) { return false; } if (index.column() == 0 || machine == nullptr) { return false; } - mem = mem_access_rw(); + if (machine->config().get_vm_enabled()) { + if (mem_access_kind == MEM_ACC_PHYS_ADDR) { + mem = machine->get_tlb_data_rw(); + } else { + mem = mem_access_phys_rw(); + } + } else { + mem = mem_access_rw(); + if (mem_access_kind > MEM_ACC_AS_CPU && machine->cache_data_rw()) { + mem = machine->cache_data_rw(); + } + } if (mem == nullptr) { return false; } - if ((access_through_cache > 0) && (machine->cache_data_rw() != nullptr)) { + if ((mem_access_kind > MEM_ACC_AS_CPU) && (machine->cache_data_rw() != nullptr)) { mem = machine->cache_data_rw(); } address += cellSizeBytes() * (index.column() - 1); @@ -230,3 +248,21 @@ bool MemoryModel::setData(const QModelIndex &index, const QVariant &value, int r } return true; } + +const machine::FrontendMemory *MemoryModel::mem_access_phys() const { + if (!machine) return nullptr; + if (mem_access_kind > MEM_ACC_AS_CPU && machine->cache_data()) { + return machine->cache_data(); + } else { + return machine->memory_data_bus(); + } +} + +machine::FrontendMemory *MemoryModel::mem_access_phys_rw() const { + if (!machine) return nullptr; + if (mem_access_kind > MEM_ACC_AS_CPU && machine->cache_data_rw()) { + return machine->cache_data_rw(); + } else { + return machine->memory_data_bus_rw(); + } +} diff --git a/src/gui/windows/memory/memorymodel.h b/src/gui/windows/memory/memorymodel.h index 7a814498..15d7f926 100644 --- a/src/gui/windows/memory/memorymodel.h +++ b/src/gui/windows/memory/memorymodel.h @@ -18,6 +18,14 @@ class MemoryModel : public QAbstractTableModel { CELLSIZE_WORD, }; + enum MemoryAccessAtLevel { + MEM_ACC_AS_CPU = 0, + MEM_ACC_VIRT_ADDR = 1, + MEM_ACC_PHYS_ADDR = 2, + MEM_ACC_PHYS_ADDR_SKIP_CACHES = 3, + MEM_ACC_AS_MACHINE = 4, + }; + explicit MemoryModel(QObject *parent); [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; [[nodiscard]] int columnCount(const QModelIndex &parent = QModelIndex()) const override; @@ -61,7 +69,6 @@ class MemoryModel : public QAbstractTableModel { } return true; } - public slots: void setup(machine::Machine *machine); void set_cell_size(int index); @@ -75,6 +82,8 @@ public slots: private: [[nodiscard]] const machine::FrontendMemory *mem_access() const; [[nodiscard]] machine::FrontendMemory *mem_access_rw() const; + [[nodiscard]] const machine::FrontendMemory *mem_access_phys() const; + [[nodiscard]] machine::FrontendMemory *mem_access_phys_rw() const; enum MemoryCellSize cell_size; unsigned int cells_per_row; machine::Address index0_offset; @@ -82,7 +91,7 @@ public slots: machine::Machine *machine; uint32_t memory_change_counter; uint32_t cache_data_change_counter; - int access_through_cache; + int mem_access_kind; }; #endif // MEMORYMODEL_H diff --git a/src/machine/machine.h b/src/machine/machine.h index 026b3933..b659b651 100644 --- a/src/machine/machine.h +++ b/src/machine/machine.h @@ -92,6 +92,14 @@ class Machine : public QObject { bool get_step_over_exception(enum ExceptionCause excause) const; enum ExceptionCause get_exception_cause() const; + Address virtual_to_physical(Address v) { + if (tlb_data) { + return tlb_data->translate_virtual_to_physical(v); + } else { + return v; + } + } + public slots: void play(); void pause(); From 7fd4f2dc0b8ba5be610e7798d42c09a58bdaf234 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20N=C4=9Bmec?= Date: Wed, 3 Sep 2025 18:46:56 +0200 Subject: [PATCH 10/12] Tests: add integration test for SV32 page-table and TLB Add a set of small assembly tests that exercise the SV32 page-table walker, SATP enablement and the new TLB code. The tests create a root page table and map a virtual page at 0xC4000000, then exercise several scenarios. The tests verify page-table walker behaviour, SATP switching and TLB caching/flush logic. Tests were written based on the consultation. --- src/cli/CMakeLists.txt | 40 ++++ tests/cli/virtual_memory/dtlb/program.S | 124 +++++++++++ tests/cli/virtual_memory/dtlb/stdout.txt | 5 + tests/cli/virtual_memory/exec/program.S | 106 ++++++++++ tests/cli/virtual_memory/exec/stdout.txt | 5 + tests/cli/virtual_memory/itlb/program.S | 206 +++++++++++++++++++ tests/cli/virtual_memory/itlb/stdout.txt | 5 + tests/cli/virtual_memory/memrw/program.S | 127 ++++++++++++ tests/cli/virtual_memory/memrw/stdout.txt | 5 + tests/cli/virtual_memory/template/program.S | 116 +++++++++++ tests/cli/virtual_memory/template/stdout.txt | 5 + 11 files changed, 744 insertions(+) create mode 100644 tests/cli/virtual_memory/dtlb/program.S create mode 100644 tests/cli/virtual_memory/dtlb/stdout.txt create mode 100644 tests/cli/virtual_memory/exec/program.S create mode 100644 tests/cli/virtual_memory/exec/stdout.txt create mode 100644 tests/cli/virtual_memory/itlb/program.S create mode 100644 tests/cli/virtual_memory/itlb/stdout.txt create mode 100644 tests/cli/virtual_memory/memrw/program.S create mode 100644 tests/cli/virtual_memory/memrw/stdout.txt create mode 100644 tests/cli/virtual_memory/template/program.S create mode 100644 tests/cli/virtual_memory/template/stdout.txt diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index 90f5c664..7f8b0233 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -77,3 +77,43 @@ add_cli_test( --asm "${CMAKE_SOURCE_DIR}/tests/cli/modifiers-pcrel/program.S" EXPECTED_OUTPUT "tests/cli/modifiers-pcrel/stdout.txt" ) + +add_cli_test( + NAME virtual_memory_template + ARGS + --asm "${CMAKE_SOURCE_DIR}/tests/cli/virtual_memory/template/program.S" + --dump-registers + EXPECTED_OUTPUT "tests/cli/virtual_memory/template/stdout.txt" +) + +add_cli_test( + NAME virtual_memory_dtlb + ARGS + --asm "${CMAKE_SOURCE_DIR}/tests/cli/virtual_memory/dtlb/program.S" + --dump-registers + EXPECTED_OUTPUT "tests/cli/virtual_memory/dtlb/stdout.txt" +) + +add_cli_test( + NAME virtual_memory_itlb + ARGS + --asm "${CMAKE_SOURCE_DIR}/tests/cli/virtual_memory/itlb/program.S" + --dump-registers + EXPECTED_OUTPUT "tests/cli/virtual_memory/itlb/stdout.txt" +) + +add_cli_test( + NAME virtual_memory_memrw + ARGS + --asm "${CMAKE_SOURCE_DIR}/tests/cli/virtual_memory/memrw/program.S" + --dump-registers + EXPECTED_OUTPUT "tests/cli/virtual_memory/memrw/stdout.txt" +) + +add_cli_test( + NAME virtual_memory_exec + ARGS + --asm "${CMAKE_SOURCE_DIR}/tests/cli/virtual_memory/exec/program.S" + --dump-registers + EXPECTED_OUTPUT "tests/cli/virtual_memory/exec/stdout.txt" +) \ No newline at end of file diff --git a/tests/cli/virtual_memory/dtlb/program.S b/tests/cli/virtual_memory/dtlb/program.S new file mode 100644 index 00000000..8854e139 --- /dev/null +++ b/tests/cli/virtual_memory/dtlb/program.S @@ -0,0 +1,124 @@ +// D-TLB test — map data pages at MAP_VA; write 'A'..'H' to the first byte of eight pages and read/print them. + +.globl _start +.option norelax + +// Serial port/terminal registers +.equ SERIAL_PORT_BASE, 0xffffc000 // base address of serial port region +.equ SERP_RX_ST_REG_o, 0x0000 // Offset of RX_ST_REG +.equ SERP_RX_DATA_REG_o, 0x0004 // Offset of RX_DATA_REG +.equ SERP_TX_ST_REG_o, 0x0008 // Offset of TX_ST_REG +.equ SERP_TX_ST_REG_READY_m,0x1 // Transmitter can accept next byte +.equ SERP_TX_DATA_REG_o, 0x000c // Offset of TX_DATA_REG + +.equ ROOT_PT_PHYS, 0x1000 +.equ MAP_VA, 0xC4000000 + +// PTE flags: 0xCF = 11001111 +// bit0 V = 1 (Valid) +// bit1 R = 1 (Readable) +// bit2 W = 1 Writable) +// bit3 X = 1 (Executable) +// bit4 U = 0 (NOT user-accessible) +// bit5 G = 0 (NOT global) +// bit6 A = 1 (Accessed) +// bit7 D = 1 (Dirty) +.equ PTE_FLAGS_FULL, 0xCF + +// mstatus MPP manipulation masks (for preparing mret to change privilege) +.equ MSTATUS_MPP_CLEAR, 0x1000 // mask to clear MPP[12] (set bit 12 -> will be cleared via csrrc) +.equ MSTATUS_MPP_SET, 0x800 // mask to set MPP[11] (set bit 11 -> will be set via csrrs) + +.equ SATP_ENABLE, 0x80000001 // satp value to enable paging (implementation-specific) + +.org 0x00000200 +.text +_start: + // t0 = physical address of root page table + li t0, ROOT_PT_PHYS + + // t4 = virtual address we want to map (MAP_VA) + li t4, MAP_VA + + // Build a leaf PTE value in t1: + // Take VA >> 12 (remove page offset) then shift left 10 to position PPN bits for a PTE, + // then OR in the PTE flags. + srli t1, t4, 12 // t1 = MAP_VA >> 12 (page number) + slli t1, t1, 10 // t1 <<= 10 to position as PPN bits for a PTE entry + li t6, PTE_FLAGS_FULL // t6 = flags + or t1, t1, t6 // t1 = (PPN << 10) | PTE_FLAGS_FULL + + // Calculate the root page table entry index for the high VPN (VPN[1]): + // t5 = MAP_VA >> 22 (VPN[1]) + // t2 = t5 << 2 (multiply by 4 bytes per PTE to get byte offset) + // t3 = root_pt_phys + offset (address of PTE in root page table) + srli t5, t4, 22 + slli t2, t5, 2 + add t3, t0, t2 + + // Store the constructed PTE into the root page table (making a mapping) + sw t1, 0(t3) + fence + + // Ensure satp is cleared before setting new value (flush previous translations) + li t0, 0 + csrw satp, t0 + + // Enable the MMU by writing SATP; this switches address translation on + li t0, SATP_ENABLE + csrw satp, t0 + fence + + // Prepare mstatus MPP so that mret will return to Supervisor mode: + // Clear MPP[12] bit then set MPP[11] bit (resulting MPP=01 => Supervisor). + li t0, MSTATUS_MPP_CLEAR + csrrc zero, mstatus, t0 // clear bit 12 of mstatus.MPP + li t0, MSTATUS_MPP_SET + csrrs zero, mstatus, t0 // set bit 11 of mstatus.MPP + + // Set mepc to the virtual address of vm_entry and return from machine mode to + // the prepared privilege level (Supervisor) using mret. + la t0, vm_entry // load address of vm_entry (virtual address after mapping) + csrw mepc, t0 + mret + +.org 0xC4000000 +.text +vm_entry: + li t0, SERIAL_PORT_BASE + la t1, MAP_VA // pointer to start of mapped virtual region + li t2, 0 // page counter + li t3, 8 // number of pages to write/read (A..H) + li t4, 65 // ASCII 'A' + li t5, 0x1000 // page size (4KB) + +// write_pages_loop: write one byte (A..H) at the start of each mapped page. +write_pages_loop: + sb t4, 0(t1) + add t1, t1, t5 + addi t4, t4, 1 + addi t2, t2, 1 + blt t2, t3, write_pages_loop + + // Reset pointer and counter to read back and print the first byte of each page. + la t1, MAP_VA + li t2, 0 + +read_print_loop: + lb t6, 0(t1) // load the byte stored at start of current page + + // wait_tx: poll transmitter status until ready, then write byte to TX data reg. +wait_tx: + lw t4, SERP_TX_ST_REG_o(t0) + andi t4, t4, SERP_TX_ST_REG_READY_m + beq t4, zero, wait_tx + sw t6, SERP_TX_DATA_REG_o(t0) + + add t1, t1, t5 + addi t2, t2, 1 + blt t2, t3, read_print_loop + + ebreak + +1: auipc t0, 0 + jalr zero, 0(t0) diff --git a/tests/cli/virtual_memory/dtlb/stdout.txt b/tests/cli/virtual_memory/dtlb/stdout.txt new file mode 100644 index 00000000..403b29eb --- /dev/null +++ b/tests/cli/virtual_memory/dtlb/stdout.txt @@ -0,0 +1,5 @@ +Machine stopped on BREAK exception. +Machine state report: +PC:0xc4000078 +R0:0x00000000 R1:0x00000000 R2:0xbfffff00 R3:0x00000000 R4:0x00000000 R5:0xffffffffffffc000 R6:0xffffffffc4008000 R7:0x00000008 R8:0x00000000 R9:0x00000000 R10:0x00000000 R11:0x00000000 R12:0x00000000 R13:0x00000000 R14:0x00000000 R15:0x00000000 R16:0x00000000 R17:0x00000000 R18:0x00000000 R19:0x00000000 R20:0x00000000 R21:0x00000000 R22:0x00000000 R23:0x00000000 R24:0x00000000 R25:0x00000000 R26:0x00000000 R27:0x00000000 R28:0x00000008 R29:0x00000001 R30:0x00001000 R31:0x00000048 +cycle: 0x00000098 mvendorid: 0x00000000 marchid: 0x00000000 mimpid: 0x00000000 mhardid: 0x00000000 mstatus: 0x00000080 misa: 0x40001111 mie: 0x00000000 mtvec: 0x00000000 mscratch: 0x00000000 mepc: 0xc4000074 mcause: 0x00000003 mtval: 0x00000000 mip: 0x00000000 mtinst: 0x00000000 mtval2: 0x00000000 mcycle: 0x00000098 minstret: 0x00000097 sstatus: 0x00000000 stvec: 0x00000000 sscratch: 0x00000000 sepc: 0x00000000 scause: 0x00000000 stval: 0x00000000 satp: 0x80000001 diff --git a/tests/cli/virtual_memory/exec/program.S b/tests/cli/virtual_memory/exec/program.S new file mode 100644 index 00000000..be41bb73 --- /dev/null +++ b/tests/cli/virtual_memory/exec/program.S @@ -0,0 +1,106 @@ +// Place a tiny function in the mapped virtual page and jump to it (tests X bit). + +.globl _start +.option norelax + +// Serial port/terminal registers +.equ SERIAL_PORT_BASE, 0xffffc000 // base address of serial port region +.equ SERP_RX_ST_REG_o, 0x0000 // Offset of RX_ST_REG +.equ SERP_RX_DATA_REG_o, 0x0004 // Offset of RX_DATA_REG +.equ SERP_TX_ST_REG_o, 0x0008 // Offset of TX_ST_REG +.equ SERP_TX_ST_REG_READY_m,0x1 // Transmitter can accept next byte +.equ SERP_TX_DATA_REG_o, 0x000c // Offset of TX_DATA_REG + +.equ ROOT_PT_PHYS, 0x1000 +.equ MAP_VA, 0xC4000000 + +// PTE flags: 0xCF = 11001111 +// bit0 V = 1 (Valid) +// bit1 R = 1 (Readable) +// bit2 W = 1 Writable) +// bit3 X = 1 (Executable) +// bit4 U = 0 (NOT user-accessible) +// bit5 G = 0 (NOT global) +// bit6 A = 1 (Accessed) +// bit7 D = 1 (Dirty) +.equ PTE_FLAGS_FULL, 0xCF + +// mstatus MPP manipulation masks (for preparing mret to change privilege) +.equ MSTATUS_MPP_CLEAR, 0x1000 // mask to clear MPP[12] (set bit 12 -> will be cleared via csrrc) +.equ MSTATUS_MPP_SET, 0x800 // mask to set MPP[11] (set bit 11 -> will be set via csrrs) + +.equ SATP_ENABLE, 0x80000001 // satp value to enable paging (implementation-specific) + +.org 0x00000200 +.text +_start: + // t0 = physical address of root page table + li t0, ROOT_PT_PHYS + + // t4 = virtual address we want to map (MAP_VA) + li t4, MAP_VA + + // Build a leaf PTE value in t1: + // Take VA >> 12 (remove page offset) then shift left 10 to position PPN bits for a PTE, + // then OR in the PTE flags. + srli t1, t4, 12 // t1 = MAP_VA >> 12 (page number) + slli t1, t1, 10 // t1 <<= 10 to position as PPN bits for a PTE entry + li t6, PTE_FLAGS_FULL // t6 = flags + or t1, t1, t6 // t1 = (PPN << 10) | PTE_FLAGS_FULL + + // Calculate the root page table entry index for the high VPN (VPN[1]): + // t5 = MAP_VA >> 22 (VPN[1]) + // t2 = t5 << 2 (multiply by 4 bytes per PTE to get byte offset) + // t3 = root_pt_phys + offset (address of PTE in root page table) + srli t5, t4, 22 + slli t2, t5, 2 + add t3, t0, t2 + + // Store the constructed PTE into the root page table (making a mapping) + sw t1, 0(t3) + fence + + // Ensure satp is cleared before setting new value (flush previous translations) + li t0, 0 + csrw satp, t0 + + // Enable the MMU by writing SATP; this switches address translation on + li t0, SATP_ENABLE + csrw satp, t0 + fence + + // Prepare mstatus MPP so that mret will return to Supervisor mode: + // Clear MPP[12] bit then set MPP[11] bit (resulting MPP=01 => Supervisor). + li t0, MSTATUS_MPP_CLEAR + csrrc zero, mstatus, t0 // clear bit 12 of mstatus.MPP + li t0, MSTATUS_MPP_SET + csrrs zero, mstatus, t0 // set bit 11 of mstatus.MPP + + // Set mepc to the virtual address of vm_entry and return from machine mode to + // the prepared privilege level (Supervisor) using mret. + la t0, vm_entry // load address of vm_entry (virtual address after mapping) + csrw mepc, t0 + mret + +.org 0xC4000000 +.text +vm_entry: + li a0, SERIAL_PORT_BASE + + // Call the function placed in the same mapped page + la t0, mapped_function + jalr zero, 0(t0) + + ebreak + +// small function placed in the mapped page +.org 0xC4000100 +mapped_function: + li a0, SERIAL_PORT_BASE + li t1, 88 +wait_tx: + lw t0, SERP_TX_ST_REG_o(a0) + andi t0, t0, SERP_TX_ST_REG_READY_m + beq t0, zero, wait_tx + sw t1, SERP_TX_DATA_REG_o(a0) + ebreak diff --git a/tests/cli/virtual_memory/exec/stdout.txt b/tests/cli/virtual_memory/exec/stdout.txt new file mode 100644 index 00000000..8e08cec7 --- /dev/null +++ b/tests/cli/virtual_memory/exec/stdout.txt @@ -0,0 +1,5 @@ +Machine stopped on BREAK exception. +Machine state report: +PC:0xc4000124 +R0:0x00000000 R1:0x00000000 R2:0xbfffff00 R3:0x00000000 R4:0x00000000 R5:0x00000001 R6:0x00000058 R7:0x00000c40 R8:0x00000000 R9:0x00000000 R10:0xffffffffffffc000 R11:0x00000000 R12:0x00000000 R13:0x00000000 R14:0x00000000 R15:0x00000000 R16:0x00000000 R17:0x00000000 R18:0x00000000 R19:0x00000000 R20:0x00000000 R21:0x00000000 R22:0x00000000 R23:0x00000000 R24:0x00000000 R25:0x00000000 R26:0x00000000 R27:0x00000000 R28:0x00001c40 R29:0xffffffffc4000000 R30:0x00000310 R31:0x000000cf +cycle: 0x0000002d mvendorid: 0x00000000 marchid: 0x00000000 mimpid: 0x00000000 mhardid: 0x00000000 mstatus: 0x00000080 misa: 0x40001111 mie: 0x00000000 mtvec: 0x00000000 mscratch: 0x00000000 mepc: 0xc4000120 mcause: 0x00000003 mtval: 0x00000000 mip: 0x00000000 mtinst: 0x00000000 mtval2: 0x00000000 mcycle: 0x0000002d minstret: 0x0000002c sstatus: 0x00000000 stvec: 0x00000000 sscratch: 0x00000000 sepc: 0x00000000 scause: 0x00000000 stval: 0x00000000 satp: 0x80000001 diff --git a/tests/cli/virtual_memory/itlb/program.S b/tests/cli/virtual_memory/itlb/program.S new file mode 100644 index 00000000..420437fd --- /dev/null +++ b/tests/cli/virtual_memory/itlb/program.S @@ -0,0 +1,206 @@ +// I-TLB test — map code pages at MAP_VA and execute tiny functions placed on eight +// pages to print A–H, exercising instruction fetch from different pages. + +.globl _start +.option norelax + +// Serial port/terminal registers +.equ SERIAL_PORT_BASE, 0xffffc000 // base address of serial port region +.equ SERP_RX_ST_REG_o, 0x0000 // Offset of RX_ST_REG +.equ SERP_RX_DATA_REG_o, 0x0004 // Offset of RX_DATA_REG +.equ SERP_TX_ST_REG_o, 0x0008 // Offset of TX_ST_REG +.equ SERP_TX_ST_REG_READY_m,0x1 // Transmitter can accept next byte +.equ SERP_TX_DATA_REG_o, 0x000c // Offset of TX_DATA_REG + +.equ ROOT_PT_PHYS, 0x1000 +.equ MAP_VA, 0xC4000000 + +// PTE flags: 0xCF = 11001111 +// bit0 V = 1 (Valid) +// bit1 R = 1 (Readable) +// bit2 W = 1 Writable) +// bit3 X = 1 (Executable) +// bit4 U = 0 (NOT user-accessible) +// bit5 G = 0 (NOT global) +// bit6 A = 1 (Accessed) +// bit7 D = 1 (Dirty) +.equ PTE_FLAGS_FULL, 0xCF + +// mstatus MPP manipulation masks (for preparing mret to change privilege) +.equ MSTATUS_MPP_CLEAR, 0x1000 // mask to clear MPP[12] (set bit 12 -> will be cleared via csrrc) +.equ MSTATUS_MPP_SET, 0x800 // mask to set MPP[11] (set bit 11 -> will be set via csrrs) + +.equ SATP_ENABLE, 0x80000001 // satp value to enable paging (implementation-specific) + +.org 0x00000200 +.text +_start: + // t0 = physical address of root page table + li t0, ROOT_PT_PHYS + + // t4 = virtual address we want to map (MAP_VA) + li t4, MAP_VA + + // Build a leaf PTE value in t1: + // Take VA >> 12 (remove page offset) then shift left 10 to position PPN bits for a PTE, + // then OR in the PTE flags. + srli t1, t4, 12 // t1 = MAP_VA >> 12 (page number) + slli t1, t1, 10 // t1 <<= 10 to position as PPN bits for a PTE entry + li t6, PTE_FLAGS_FULL // t6 = flags + or t1, t1, t6 // t1 = (PPN << 10) | PTE_FLAGS_FULL + + // Calculate the root page table entry index for the high VPN (VPN[1]): + // t5 = MAP_VA >> 22 (VPN[1]) + // t2 = t5 << 2 (multiply by 4 bytes per PTE to get byte offset) + // t3 = root_pt_phys + offset (address of PTE in root page table) + srli t5, t4, 22 + slli t2, t5, 2 + add t3, t0, t2 + + // Store the constructed PTE into the root page table (making a mapping) + sw t1, 0(t3) + fence + + // Ensure satp is cleared before setting new value (flush previous translations) + li t0, 0 + csrw satp, t0 + + // Enable the MMU by writing SATP; this switches address translation on + li t0, SATP_ENABLE + csrw satp, t0 + fence + + // Prepare mstatus MPP so that mret will return to Supervisor mode: + // Clear MPP[12] bit then set MPP[11] bit (resulting MPP=01 => Supervisor). + li t0, MSTATUS_MPP_CLEAR + csrrc zero, mstatus, t0 // clear bit 12 of mstatus.MPP + li t0, MSTATUS_MPP_SET + csrrs zero, mstatus, t0 // set bit 11 of mstatus.MPP + + // Set mepc to the virtual address of vm_entry and return from machine mode to + // the prepared privilege level (Supervisor) using mret. + la t0, vm_entry // load address of vm_entry (virtual address after mapping) + csrw mepc, t0 + mret + + +.org 0xC4007000 +.text +vm_entry: + li t0, SERIAL_PORT_BASE + li t4, 65 // 'A' + + // print_self: print current char, then step through executing code on other mapped pages. +print_self: + lw t6, SERP_TX_ST_REG_o(t0) + andi t6, t6, SERP_TX_ST_REG_READY_m + beq t6, zero, print_self + sw t4, SERP_TX_DATA_REG_o(t0) + addi t4, t4, 1 + + // Execute code placed in separate mapped pages. Each page contains a tiny + // function that prints the current character then returns using the address + // stored in t3. The sequence tests I-TLB / instruction fetch of different pages. + la t1, page_func_0 + la t3, resume_0 + jalr zero, 0(t1) +resume_0: + addi t4, t4, 1 + + la t1, page_func_1 + la t3, resume_1 + jalr zero, 0(t1) +resume_1: + addi t4, t4, 1 + + la t1, page_func_2 + la t3, resume_2 + jalr zero, 0(t1) +resume_2: + addi t4, t4, 1 + + la t1, page_func_3 + la t3, resume_3 + jalr zero, 0(t1) +resume_3: + addi t4, t4, 1 + + la t1, page_func_4 + la t3, resume_4 + jalr zero, 0(t1) +resume_4: + addi t4, t4, 1 + + la t1, page_func_5 + la t3, resume_5 + jalr zero, 0(t1) +resume_5: + addi t4, t4, 1 + + la t1, page_func_6 + la t3, resume_6 + jalr zero, 0(t1) +resume_6: + addi t4, t4, 1 + + ebreak + + +.org 0xC4000000 +page_func_0: + // Each page_func_* polls transmitter, writes current char (t4) then jumps + // back to the resume address stored in t3. These functions live on + // separate mapped pages to exercise instruction fetch from different pages. + lw t6, SERP_TX_ST_REG_o(t0) + andi t6, t6, SERP_TX_ST_REG_READY_m + beq t6, zero, page_func_0 + sw t4, SERP_TX_DATA_REG_o(t0) + jalr zero, 0(t3) + +.org 0xC4001000 +page_func_1: + lw t6, SERP_TX_ST_REG_o(t0) + andi t6, t6, SERP_TX_ST_REG_READY_m + beq t6, zero, page_func_1 + sw t4, SERP_TX_DATA_REG_o(t0) + jalr zero, 0(t3) + +.org 0xC4002000 +page_func_2: + lw t6, SERP_TX_ST_REG_o(t0) + andi t6, t6, SERP_TX_ST_REG_READY_m + beq t6, zero, page_func_2 + sw t4, SERP_TX_DATA_REG_o(t0) + jalr zero, 0(t3) + +.org 0xC4003000 +page_func_3: + lw t6, SERP_TX_ST_REG_o(t0) + andi t6, t6, SERP_TX_ST_REG_READY_m + beq t6, zero, page_func_3 + sw t4, SERP_TX_DATA_REG_o(t0) + jalr zero, 0(t3) + +.org 0xC4004000 +page_func_4: + lw t6, SERP_TX_ST_REG_o(t0) + andi t6, t6, SERP_TX_ST_REG_READY_m + beq t6, zero, page_func_4 + sw t4, SERP_TX_DATA_REG_o(t0) + jalr zero, 0(t3) + +.org 0xC4005000 +page_func_5: + lw t6, SERP_TX_ST_REG_o(t0) + andi t6, t6, SERP_TX_ST_REG_READY_m + beq t6, zero, page_func_5 + sw t4, SERP_TX_DATA_REG_o(t0) + jalr zero, 0(t3) + +.org 0xC4006000 +page_func_6: + lw t6, SERP_TX_ST_REG_o(t0) + andi t6, t6, SERP_TX_ST_REG_READY_m + beq t6, zero, page_func_6 + sw t4, SERP_TX_DATA_REG_o(t0) + jalr zero, 0(t3) \ No newline at end of file diff --git a/tests/cli/virtual_memory/itlb/stdout.txt b/tests/cli/virtual_memory/itlb/stdout.txt new file mode 100644 index 00000000..1c340b47 --- /dev/null +++ b/tests/cli/virtual_memory/itlb/stdout.txt @@ -0,0 +1,5 @@ +Machine stopped on BREAK exception. +Machine state report: +PC:0xc40070d0 +R0:0x00000000 R1:0x00000000 R2:0xbfffff00 R3:0x00000000 R4:0x00000000 R5:0xffffffffffffc000 R6:0xffffffffc4006000 R7:0x00000c40 R8:0x00000000 R9:0x00000000 R10:0x00000000 R11:0x00000000 R12:0x00000000 R13:0x00000000 R14:0x00000000 R15:0x00000000 R16:0x00000000 R17:0x00000000 R18:0x00000000 R19:0x00000000 R20:0x00000000 R21:0x00000000 R22:0x00000000 R23:0x00000000 R24:0x00000000 R25:0x00000000 R26:0x00000000 R27:0x00000000 R28:0xffffffffc40070c8 R29:0x00000049 R30:0x00000310 R31:0x00000001 +cycle: 0x00000076 mvendorid: 0x00000000 marchid: 0x00000000 mimpid: 0x00000000 mhardid: 0x00000000 mstatus: 0x00000080 misa: 0x40001111 mie: 0x00000000 mtvec: 0x00000000 mscratch: 0x00000000 mepc: 0xc40070cc mcause: 0x00000003 mtval: 0x00000000 mip: 0x00000000 mtinst: 0x00000000 mtval2: 0x00000000 mcycle: 0x00000076 minstret: 0x00000075 sstatus: 0x00000000 stvec: 0x00000000 sscratch: 0x00000000 sepc: 0x00000000 scause: 0x00000000 stval: 0x00000000 satp: 0x80000001 diff --git a/tests/cli/virtual_memory/memrw/program.S b/tests/cli/virtual_memory/memrw/program.S new file mode 100644 index 00000000..04eed622 --- /dev/null +++ b/tests/cli/virtual_memory/memrw/program.S @@ -0,0 +1,127 @@ +// Write ASCII bytes into the mapped virtual page and read them back printing to verify. + +.globl _start +.option norelax + +// Serial port/terminal registers +.equ SERIAL_PORT_BASE, 0xffffc000 // base address of serial port region +.equ SERP_RX_ST_REG_o, 0x0000 // Offset of RX_ST_REG +.equ SERP_RX_DATA_REG_o, 0x0004 // Offset of RX_DATA_REG +.equ SERP_TX_ST_REG_o, 0x0008 // Offset of TX_ST_REG +.equ SERP_TX_ST_REG_READY_m,0x1 // Transmitter can accept next byte +.equ SERP_TX_DATA_REG_o, 0x000c // Offset of TX_DATA_REG + +.equ ROOT_PT_PHYS, 0x1000 +.equ MAP_VA, 0xC4000000 + +// PTE flags: 0xCF = 11001111 +// bit0 V = 1 (Valid) +// bit1 R = 1 (Readable) +// bit2 W = 1 Writable) +// bit3 X = 1 (Executable) +// bit4 U = 0 (NOT user-accessible) +// bit5 G = 0 (NOT global) +// bit6 A = 1 (Accessed) +// bit7 D = 1 (Dirty) +.equ PTE_FLAGS_FULL, 0xCF + +// mstatus MPP manipulation masks (for preparing mret to change privilege) +.equ MSTATUS_MPP_CLEAR, 0x1000 // mask to clear MPP[12] (set bit 12 -> will be cleared via csrrc) +.equ MSTATUS_MPP_SET, 0x800 // mask to set MPP[11] (set bit 11 -> will be set via csrrs) + +.equ SATP_ENABLE, 0x80000001 // satp value to enable paging (implementation-specific) + +.org 0x00000200 +.text +_start: + // t0 = physical address of root page table + li t0, ROOT_PT_PHYS + + // t4 = virtual address we want to map (MAP_VA) + li t4, MAP_VA + + // Build a leaf PTE value in t1: + // Take VA >> 12 (remove page offset) then shift left 10 to position PPN bits for a PTE, + // then OR in the PTE flags. + srli t1, t4, 12 // t1 = MAP_VA >> 12 (page number) + slli t1, t1, 10 // t1 <<= 10 to position as PPN bits for a PTE entry + li t6, PTE_FLAGS_FULL // t6 = flags + or t1, t1, t6 // t1 = (PPN << 10) | PTE_FLAGS_FULL + + // Calculate the root page table entry index for the high VPN (VPN[1]): + // t5 = MAP_VA >> 22 (VPN[1]) + // t2 = t5 << 2 (multiply by 4 bytes per PTE to get byte offset) + // t3 = root_pt_phys + offset (address of PTE in root page table) + srli t5, t4, 22 + slli t2, t5, 2 + add t3, t0, t2 + + // Store the constructed PTE into the root page table (making a mapping) + sw t1, 0(t3) + fence + + // Ensure satp is cleared before setting new value (flush previous translations) + li t0, 0 + csrw satp, t0 + + // Enable the MMU by writing SATP; this switches address translation on + li t0, SATP_ENABLE + csrw satp, t0 + fence + + // Prepare mstatus MPP so that mret will return to Supervisor mode: + // Clear MPP[12] bit then set MPP[11] bit (resulting MPP=01 => Supervisor). + li t0, MSTATUS_MPP_CLEAR + csrrc zero, mstatus, t0 // clear bit 12 of mstatus.MPP + li t0, MSTATUS_MPP_SET + csrrs zero, mstatus, t0 // set bit 11 of mstatus.MPP + + // Set mepc to the virtual address of vm_entry and return from machine mode to + // the prepared privilege level (Supervisor) using mret. + la t0, vm_entry // load address of vm_entry (virtual address after mapping) + csrw mepc, t0 + mret + + .org 0xC4000000 + .text +vm_entry: + li a0, SERIAL_PORT_BASE + + // pointer to mapped virtual page + la a1, MAP_VA + + // write ASCII letters A..H into the first 8 bytes + li t0, 65 + sb t0, 0(a1) + li t0, 66 + sb t0, 1(a1) + li t0, 67 + sb t0, 2(a1) + li t0, 68 + sb t0, 3(a1) + li t0, 69 + sb t0, 4(a1) + li t0, 70 + sb t0, 5(a1) + li t0, 71 + sb t0, 6(a1) + li t0, 72 + sb t0, 7(a1) + + // Now read back and print each byte + li t5, 0 +read_print_loop: + lb t1, 0(a1) + // print t1 +wait_tx2: + lw t0, SERP_TX_ST_REG_o(a0) + andi t0, t0, SERP_TX_ST_REG_READY_m + beq t0, zero, wait_tx2 + sw t1, SERP_TX_DATA_REG_o(a0) + + addi a1, a1, 1 + addi t5, t5, 1 + li t6, 8 + blt t5, t6, read_print_loop + + ebreak \ No newline at end of file diff --git a/tests/cli/virtual_memory/memrw/stdout.txt b/tests/cli/virtual_memory/memrw/stdout.txt new file mode 100644 index 00000000..63e7f982 --- /dev/null +++ b/tests/cli/virtual_memory/memrw/stdout.txt @@ -0,0 +1,5 @@ +Machine stopped on BREAK exception. +Machine state report: +PC:0xc40000a4 +R0:0x00000000 R1:0x00000000 R2:0xbfffff00 R3:0x00000000 R4:0x00000000 R5:0x00000001 R6:0x00000048 R7:0x00000c40 R8:0x00000000 R9:0x00000000 R10:0xffffffffffffc000 R11:0xffffffffc4000008 R12:0x00000000 R13:0x00000000 R14:0x00000000 R15:0x00000000 R16:0x00000000 R17:0x00000000 R18:0x00000000 R19:0x00000000 R20:0x00000000 R21:0x00000000 R22:0x00000000 R23:0x00000000 R24:0x00000000 R25:0x00000000 R26:0x00000000 R27:0x00000000 R28:0x00001c40 R29:0xffffffffc4000000 R30:0x00000008 R31:0x00000008 +cycle: 0x0000008e mvendorid: 0x00000000 marchid: 0x00000000 mimpid: 0x00000000 mhardid: 0x00000000 mstatus: 0x00000080 misa: 0x40001111 mie: 0x00000000 mtvec: 0x00000000 mscratch: 0x00000000 mepc: 0xc40000a0 mcause: 0x00000003 mtval: 0x00000000 mip: 0x00000000 mtinst: 0x00000000 mtval2: 0x00000000 mcycle: 0x0000008e minstret: 0x0000008d sstatus: 0x00000000 stvec: 0x00000000 sscratch: 0x00000000 sepc: 0x00000000 scause: 0x00000000 stval: 0x00000000 satp: 0x80000001 diff --git a/tests/cli/virtual_memory/template/program.S b/tests/cli/virtual_memory/template/program.S new file mode 100644 index 00000000..698e64a0 --- /dev/null +++ b/tests/cli/virtual_memory/template/program.S @@ -0,0 +1,116 @@ +// Test template: Sets up a page table, enables virtual memory, and prints "Hello world" via serial port. + +.globl _start +.option norelax + +// Serial port/terminal registers +.equ SERIAL_PORT_BASE, 0xffffc000 // base address of serial port region +.equ SERP_RX_ST_REG_o, 0x0000 // Offset of RX_ST_REG +.equ SERP_RX_DATA_REG_o, 0x0004 // Offset of RX_DATA_REG +.equ SERP_TX_ST_REG_o, 0x0008 // Offset of TX_ST_REG +.equ SERP_TX_ST_REG_READY_m,0x1 // Transmitter can accept next byte +.equ SERP_TX_DATA_REG_o, 0x000c // Offset of TX_DATA_REG + +.equ ROOT_PT_PHYS, 0x1000 +.equ MAP_VA, 0xC4000000 + +// PTE flags: 0xCF = 11001111 +// bit0 V = 1 (Valid) +// bit1 R = 1 (Readable) +// bit2 W = 1 Writable) +// bit3 X = 1 (Executable) +// bit4 U = 0 (NOT user-accessible) +// bit5 G = 0 (NOT global) +// bit6 A = 1 (Accessed) +// bit7 D = 1 (Dirty) +.equ PTE_FLAGS_FULL, 0xCF + +// mstatus MPP manipulation masks (for preparing mret to change privilege) +.equ MSTATUS_MPP_CLEAR, 0x1000 // mask to clear MPP[12] (set bit 12 -> will be cleared via csrrc) +.equ MSTATUS_MPP_SET, 0x800 // mask to set MPP[11] (set bit 11 -> will be set via csrrs) + +.equ SATP_ENABLE, 0x80000001 // satp value to enable paging (implementation-specific) + +.org 0x00000200 +.text +_start: + // t0 = physical address of root page table + li t0, ROOT_PT_PHYS + + // t4 = virtual address we want to map (MAP_VA) + li t4, MAP_VA + + // Build a leaf PTE value in t1: + // Take VA >> 12 (remove page offset) then shift left 10 to position PPN bits for a PTE, + // then OR in the PTE flags. + srli t1, t4, 12 // t1 = MAP_VA >> 12 (page number) + slli t1, t1, 10 // t1 <<= 10 to position as PPN bits for a PTE entry + li t6, PTE_FLAGS_FULL // t6 = flags + or t1, t1, t6 // t1 = (PPN << 10) | PTE_FLAGS_FULL + + // Calculate the root page table entry index for the high VPN (VPN[1]): + // t5 = MAP_VA >> 22 (VPN[1]) + // t2 = t5 << 2 (multiply by 4 bytes per PTE to get byte offset) + // t3 = root_pt_phys + offset (address of PTE in root page table) + srli t5, t4, 22 + slli t2, t5, 2 + add t3, t0, t2 + + // Store the constructed PTE into the root page table (making a mapping) + sw t1, 0(t3) + fence + + // Ensure satp is cleared before setting new value (flush previous translations) + li t0, 0 + csrw satp, t0 + + // Enable the MMU by writing SATP; this switches address translation on + li t0, SATP_ENABLE + csrw satp, t0 + fence + + // Prepare mstatus MPP so that mret will return to Supervisor mode: + // Clear MPP[12] bit then set MPP[11] bit (resulting MPP=01 => Supervisor). + li t0, MSTATUS_MPP_CLEAR + csrrc zero, mstatus, t0 // clear bit 12 of mstatus.MPP + li t0, MSTATUS_MPP_SET + csrrs zero, mstatus, t0 // set bit 11 of mstatus.MPP + + // Set mepc to the virtual address of vm_entry and return from machine mode to + // the prepared privilege level (Supervisor) using mret. + la t0, vm_entry // load address of vm_entry (virtual address after mapping) + csrw mepc, t0 + mret + +.org 0xC4000000 +.text +vm_entry: + li a0, SERIAL_PORT_BASE + la a1, hello_str + +print_next_char: + // Load next byte from string; if zero (end), branch to done + lb t1, 0(a1) + beq t1, zero, print_done + addi a1, a1, 1 // advance to next character + +wait_tx_ready: + // Poll transmit status register until TX ready bit is set + lw t0, SERP_TX_ST_REG_o(a0) + andi t0, t0, SERP_TX_ST_REG_READY_m + beq t0, zero, wait_tx_ready + + // Write byte to transmit-data register and loop for next char + sw t1, SERP_TX_DATA_REG_o(a0) + jal zero, print_next_char + +print_done: + ebreak + +1: auipc t0, 0 + jalr zero, 0(t0) + +.data +.org 0xC4000100 +hello_str: + .asciz "Hello world.\n" \ No newline at end of file diff --git a/tests/cli/virtual_memory/template/stdout.txt b/tests/cli/virtual_memory/template/stdout.txt new file mode 100644 index 00000000..2fbaaea1 --- /dev/null +++ b/tests/cli/virtual_memory/template/stdout.txt @@ -0,0 +1,5 @@ +Machine stopped on BREAK exception. +Machine state report: +PC:0xc4000034 +R0:0x00000000 R1:0x00000000 R2:0xbfffff00 R3:0x00000000 R4:0x00000000 R5:0x00000001 R6:0x00000000 R7:0x00000c40 R8:0x00000000 R9:0x00000000 R10:0xffffffffffffc000 R11:0xffffffffc400010d R12:0x00000000 R13:0x00000000 R14:0x00000000 R15:0x00000000 R16:0x00000000 R17:0x00000000 R18:0x00000000 R19:0x00000000 R20:0x00000000 R21:0x00000000 R22:0x00000000 R23:0x00000000 R24:0x00000000 R25:0x00000000 R26:0x00000000 R27:0x00000000 R28:0x00001c40 R29:0xffffffffc4000000 R30:0x00000310 R31:0x000000cf +cycle: 0x0000008e mvendorid: 0x00000000 marchid: 0x00000000 mimpid: 0x00000000 mhardid: 0x00000000 mstatus: 0x00000080 misa: 0x40001111 mie: 0x00000000 mtvec: 0x00000000 mscratch: 0x00000000 mepc: 0xc4000030 mcause: 0x00000003 mtval: 0x00000000 mip: 0x00000000 mtinst: 0x00000000 mtval2: 0x00000000 mcycle: 0x0000008e minstret: 0x0000008d sstatus: 0x00000000 stvec: 0x00000000 sscratch: 0x00000000 sepc: 0x00000000 scause: 0x00000000 stval: 0x00000000 satp: 0x80000001 From ee811fa7007cd052f106b7f0636ddd203c068aaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20N=C4=9Bmec?= Date: Sun, 2 Nov 2025 15:40:45 +0100 Subject: [PATCH 11/12] Machine: gate TLB updates by root register and privilege mode Ensure that TLBs are only updated when the root register is set, and disable TLB updates while running in Machine mode. --- src/machine/core.cpp | 4 ++-- src/machine/core.h | 5 +++++ src/machine/core/core_state.h | 9 +++++++++ src/machine/machine.cpp | 4 +++- src/machine/memory/frontend_memory.cpp | 6 ++++-- src/machine/memory/frontend_memory.h | 15 +++++++++++++-- src/machine/memory/tlb/tlb.cpp | 21 +++++++++++++++++++-- src/machine/memory/tlb/tlb.h | 4 ++++ 8 files changed, 59 insertions(+), 9 deletions(-) diff --git a/src/machine/core.cpp b/src/machine/core.cpp index 2fb9938d..d371d801 100644 --- a/src/machine/core.cpp +++ b/src/machine/core.cpp @@ -480,8 +480,8 @@ MemoryState Core::memory(const ExecuteInterstage &dt) { excause = memory_special( dt.memctl, dt.inst.rt(), memread, memwrite, towrite_val, dt.val_rt, mem_addr); } else if (is_regular_access(dt.memctl)) { - if (memwrite) { mem_data->write_ctl(dt.memctl, mem_addr, dt.val_rt); } - if (memread) { towrite_val = mem_data->read_ctl(dt.memctl, mem_addr); } + if (memwrite) { mem_data->write_ctl(dt.memctl, mem_addr, dt.val_rt, make_ctrl_info(state)); } + if (memread) { towrite_val = mem_data->read_ctl(dt.memctl, mem_addr, make_ctrl_info(state)); } } else { Q_ASSERT(dt.memctl == AC_NONE); // AC_NONE is memory NOP diff --git a/src/machine/core.h b/src/machine/core.h index 3da42197..df36a229 100644 --- a/src/machine/core.h +++ b/src/machine/core.h @@ -60,6 +60,11 @@ class Core : public QObject { bool get_step_over_exception(enum ExceptionCause excause) const; void set_current_privilege(CSR::PrivilegeLevel privilege); CSR::PrivilegeLevel get_current_privilege() const; + static inline uint32_t make_ctrl_info(const CoreState &st) { + uint32_t priv = static_cast(st.current_privilege()) & 0x3u; + uint32_t asid = static_cast(st.current_asid()) & 0x1FFu; + return priv | (asid << 2); + } /** * Abstracts XLEN from code flow. XLEN core will obtain XLEN value from register value. diff --git a/src/machine/core/core_state.h b/src/machine/core/core_state.h index 00a6f5ce..0ac328e6 100644 --- a/src/machine/core/core_state.h +++ b/src/machine/core/core_state.h @@ -19,6 +19,7 @@ struct CoreState { uint32_t stall_count = 0; uint32_t cycle_count = 0; unsigned current_privilege_u = static_cast(CSR::PrivilegeLevel::MACHINE); + unsigned current_asid_u = 0u; [[nodiscard]] CSR::PrivilegeLevel current_privilege() const noexcept { return static_cast(current_privilege_u); @@ -27,6 +28,14 @@ struct CoreState { void set_current_privilege(CSR::PrivilegeLevel p) noexcept { current_privilege_u = static_cast(p); } + + [[nodiscard]] uint16_t current_asid() const noexcept { + return static_cast(current_asid_u & 0x1FFu); + } + + void set_current_asid(uint16_t a) noexcept { + current_asid_u = static_cast(a & 0x1FFu); + } }; } // namespace machine diff --git a/src/machine/machine.cpp b/src/machine/machine.cpp index 17bf015f..e85a1a08 100644 --- a/src/machine/machine.cpp +++ b/src/machine/machine.cpp @@ -72,9 +72,11 @@ Machine::Machine(MachineConfig config, bool load_symtab, bool load_executable) cch_program, PROGRAM, machine_config.access_tlb_program(), machine_config.get_vm_enabled()); tlb_data = new TLB( cch_data, DATA, machine_config.access_tlb_data(), machine_config.get_vm_enabled()); - controlst->write_internal(CSR::Id::SATP, 0); tlb_program->on_csr_write(CSR::Id::SATP, 0); tlb_data->on_csr_write(CSR::Id::SATP, 0); + connect(controlst, &CSR::ControlState::write_signal, tlb_program, &machine::TLB::on_csr_write); + connect(controlst, &CSR::ControlState::write_signal, tlb_data, &machine::TLB::on_csr_write); + controlst->write_internal(CSR::Id::SATP, 0); predictor = new BranchPredictor( machine_config.get_bp_enabled(), machine_config.get_bp_type(), diff --git a/src/machine/memory/frontend_memory.cpp b/src/machine/memory/frontend_memory.cpp index 6e0791c3..628c95c7 100644 --- a/src/machine/memory/frontend_memory.cpp +++ b/src/machine/memory/frontend_memory.cpp @@ -37,7 +37,7 @@ uint64_t FrontendMemory::read_u64(Address address, AccessEffects type) const { return read_generic(address, type); } -void FrontendMemory::write_ctl(enum AccessControl ctl, Address offset, RegisterValue value) { +void FrontendMemory::write_ctl(enum AccessControl ctl, Address offset, RegisterValue value, uint32_t ctrl_info ) { switch (ctl) { case AC_NONE: { break; @@ -68,9 +68,10 @@ void FrontendMemory::write_ctl(enum AccessControl ctl, Address offset, RegisterV QString::number(ctl)); } } + handle_control_signal(ctrl_info); } -RegisterValue FrontendMemory::read_ctl(enum AccessControl ctl, Address address) const { +RegisterValue FrontendMemory::read_ctl(enum AccessControl ctl, Address address, uint32_t ctrl_info ) const { switch (ctl) { case AC_NONE: return 0; case AC_I8: return (int8_t)read_u8(address); @@ -87,6 +88,7 @@ RegisterValue FrontendMemory::read_ctl(enum AccessControl ctl, Address address) QString::number(ctl)); } } + const_cast(this)->handle_control_signal(ctrl_info); } void FrontendMemory::sync() {} diff --git a/src/machine/memory/frontend_memory.h b/src/machine/memory/frontend_memory.h index c7306093..1a2f8769 100644 --- a/src/machine/memory/frontend_memory.h +++ b/src/machine/memory/frontend_memory.h @@ -11,6 +11,8 @@ #include #include +#include "csr/address.h" + // Shortcut for enum class values, type is obvious from context. using ae = machine::AccessEffects; @@ -68,7 +70,7 @@ class FrontendMemory : public QObject { * REGULAR. * @param control_signal CPU control unit signal */ - void write_ctl(AccessControl control_signal, Address destination, RegisterValue value); + void write_ctl(AccessControl control_signal, Address destination, RegisterValue value, uint32_t ctrl_info = 0); /** * Read with size specified by the CPU control unit. @@ -77,12 +79,21 @@ class FrontendMemory : public QObject { * ae::REGULAR. * @param control_signal CPU control unit signal */ - [[nodiscard]] RegisterValue read_ctl(enum AccessControl ctl, Address source) const; + [[nodiscard]] RegisterValue read_ctl(enum AccessControl ctl, Address source, uint32_t ctrl_info = 0) const; + + virtual void handle_control_signal(uint32_t ctrl_info) { Q_UNUSED(ctrl_info); } virtual void sync(); [[nodiscard]] virtual LocationStatus location_status(Address address) const; [[nodiscard]] virtual uint32_t get_change_counter() const = 0; + static inline CSR::PrivilegeLevel unpack_priv(uint32_t token) { + return static_cast(token & 0x3u); + } + static inline uint16_t unpack_asid(uint32_t token) { + return static_cast((token >> 2) & 0x1FFu); + } + /** * Write byte sequence to memory * diff --git a/src/machine/memory/tlb/tlb.cpp b/src/machine/memory/tlb/tlb.cpp index 153107e0..ac7ac3c4 100644 --- a/src/machine/memory/tlb/tlb.cpp +++ b/src/machine/memory/tlb/tlb.cpp @@ -3,7 +3,6 @@ #include "csr/controlstate.h" #include "machine.h" #include "memory/virtual/page_table_walker.h" -#include "memory/virtual/sv32.h" LOG_CATEGORY("machine.TLB"); @@ -64,6 +63,8 @@ void TLB::on_csr_write(size_t internal_id, RegisterValue val) { update_all_statistics(); } + + void TLB::flush_single(VirtualAddress va, uint16_t asid) { uint64_t vpn = va.get_raw() >> 12; size_t s = set_index(vpn); @@ -111,7 +112,9 @@ void TLB::sync() { Address TLB::translate_virtual_to_physical(Address vaddr) { uint64_t virt = vaddr.get_raw(); - if (!vm_enabled) { return vaddr; } + if (!vm_enabled || !translation_enabled || is_in_uncached_area(vaddr)) { + return vaddr; + } constexpr unsigned PAGE_SHIFT = 12; constexpr uint64_t PAGE_MASK = (1ULL << PAGE_SHIFT) - 1; @@ -191,6 +194,20 @@ bool TLB::reverse_lookup(Address paddr, VirtualAddress &out_va) const { } return false; } + +void TLB::handle_control_signal(uint32_t ctrl_info) { + CSR::PrivilegeLevel priv = unpack_priv(ctrl_info); + uint16_t asid = unpack_asid(ctrl_info); + + bool satp_mode_on = is_mode_enabled_in_satp(current_satp_raw); + bool should_translate = vm_enabled && satp_mode_on && (priv != CSR::PrivilegeLevel::MACHINE); + + if (translation_enabled != should_translate) { + translation_enabled = should_translate; + LOG("TLB: translation_enabled -> %d (ASID=%u)", (int)translation_enabled, asid); + } +} + bool TLB::is_in_uncached_area(Address source) const { return (source >= uncached_start && source <= uncached_last); } diff --git a/src/machine/memory/tlb/tlb.h b/src/machine/memory/tlb/tlb.h index 5af3a3cc..f6cdfacf 100644 --- a/src/machine/memory/tlb/tlb.h +++ b/src/machine/memory/tlb/tlb.h @@ -2,6 +2,7 @@ #define TLB_H #include "common/logging.h" +#include "csr/address.h" #include "memory/frontend_memory.h" #include "memory/virtual/sv32.h" #include "memory/virtual/virtual_address.h" @@ -67,6 +68,7 @@ class TLB : public FrontendMemory { void reset(); void update_all_statistics(); + void handle_control_signal(uint32_t ctrl_info) override; signals: void tlb_update( @@ -98,6 +100,7 @@ class TLB : public FrontendMemory { TLBType type; const TLBConfig tlb_config; uint32_t current_satp_raw = 0; + bool translation_enabled = false; const bool vm_enabled; size_t num_sets_; @@ -122,6 +125,7 @@ class TLB : public FrontendMemory { WriteResult translate_and_write(Address dst, const void *src, size_t sz, WriteOptions opts); ReadResult translate_and_read(void *dst, Address src, size_t sz, ReadOptions opts); inline size_t set_index(uint64_t vpn) const { return vpn & (num_sets_ - 1); } + inline bool is_mode_enabled_in_satp(uint32_t satp_raw) { return (satp_raw & (1u << 31)) != 0; } }; } // namespace machine From 0fc627fae28c41794b9e78b30b0e9289c29034a6 Mon Sep 17 00:00:00 2001 From: nemecad Date: Sun, 2 Nov 2025 20:20:19 +0100 Subject: [PATCH 12/12] Machine: propagate xRET type and add privilege flags illegal instruction checks Decode MRET/SRET/URET in the decode stage, carry the return type through the interstage registers, and pass it to ControlState::exception_return in the memory stage. Extend instruction metadata with privilege flags (IMF_PRIV_M/H/S) for privileged operations and use them for masking. --- src/machine/core.cpp | 44 +++++++++++++++++++++++++++++--- src/machine/core.h | 4 +-- src/machine/csr/controlstate.cpp | 33 +++++++++++++++++++----- src/machine/csr/controlstate.h | 7 ++--- src/machine/instruction.cpp | 25 ++++++++++++++++-- src/machine/instruction.h | 5 ++++ src/machine/pipeline.h | 5 +++- 7 files changed, 105 insertions(+), 18 deletions(-) diff --git a/src/machine/core.cpp b/src/machine/core.cpp index d371d801..ec89acf7 100644 --- a/src/machine/core.cpp +++ b/src/machine/core.cpp @@ -127,6 +127,27 @@ Xlen Core::get_xlen() const { void Core::set_current_privilege(CSR::PrivilegeLevel privilege) { state.set_current_privilege(privilege); + const unsigned PRIV_BITS = unsigned(IMF_PRIV_M) | unsigned(IMF_PRIV_H) | unsigned(IMF_PRIV_S); + + InstructionFlags base_mask = InstructionFlags(unsigned(check_inst_flags_mask) & ~PRIV_BITS); + InstructionFlags base_val = InstructionFlags(unsigned(check_inst_flags_val) & ~PRIV_BITS); + + unsigned allowed_priv = 0; + if (privilege >= CSR::PrivilegeLevel::SUPERVISOR) { + allowed_priv |= unsigned(IMF_PRIV_S); + } + if (privilege >= CSR::PrivilegeLevel::HYPERVISOR) { + allowed_priv |= unsigned(IMF_PRIV_H); + } + if (privilege >= CSR::PrivilegeLevel::MACHINE) { + allowed_priv |= unsigned(IMF_PRIV_M); + } + unsigned disallowed_priv = (PRIV_BITS & ~allowed_priv); + InstructionFlags new_mask = InstructionFlags(unsigned(base_mask) | disallowed_priv); + InstructionFlags new_val = base_val; + + check_inst_flags_mask = new_mask; + check_inst_flags_val = new_val; } CSR::PrivilegeLevel Core::get_current_privilege() const { @@ -317,7 +338,18 @@ DecodeState Core::decode(const FetchInterstage &dt) { ExceptionCause excause = dt.excause; dt.inst.flags_alu_op_mem_ctl(flags, alu_op, mem_ctl); - + CSR::PrivilegeLevel inst_xret_priv = CSR::PrivilegeLevel::UNPRIVILEGED; + if (flags & IMF_XRET) { + if (flags & IMF_PRIV_M) { + inst_xret_priv = CSR::PrivilegeLevel::MACHINE; + } else if (flags & IMF_PRIV_H) { + inst_xret_priv = CSR::PrivilegeLevel::HYPERVISOR; + } else if (flags & IMF_PRIV_S) { + inst_xret_priv = CSR::PrivilegeLevel::SUPERVISOR; + } else { + inst_xret_priv = CSR::PrivilegeLevel::UNPRIVILEGED; + } + } if ((flags ^ check_inst_flags_val) & check_inst_flags_mask) { excause = EXCAUSE_INSN_ILLEGAL; } RegisterId num_rs = (flags & (IMF_ALU_REQ_RS | IMF_ALU_RS_ID)) ? dt.inst.rs() : 0; @@ -333,7 +365,7 @@ DecodeState Core::decode(const FetchInterstage &dt) { CSR::Address csr_address = (flags & IMF_CSR) ? dt.inst.csr_address() : CSR::Address(0); RegisterValue csr_read_val - = ((control_state != nullptr && (flags & IMF_CSR))) ? control_state->read(csr_address) : 0; + = ((control_state != nullptr && (flags & IMF_CSR))) ? control_state->read(csr_address, get_current_privilege()) : 0; bool csr_write = (flags & IMF_CSR) && (!(flags & IMF_CSR_TO_ALU) || (num_rs != 0)); if ((flags & IMF_EXCEPTION) && (excause == EXCAUSE_NONE)) { @@ -393,6 +425,7 @@ DecodeState Core::decode(const FetchInterstage &dt) { .csr_to_alu = bool(flags & IMF_CSR_TO_ALU), .csr_write = csr_write, .xret = bool(flags & IMF_XRET), + .xret_privlev = inst_xret_priv, .insert_stall_before = bool(flags & IMF_CSR) } }; } @@ -463,6 +496,7 @@ ExecuteState Core::execute(const DecodeInterstage &dt) { .csr = dt.csr, .csr_write = dt.csr_write, .xret = dt.xret, + .xret_privlev = dt.xret_privlev, } }; } @@ -523,11 +557,12 @@ MemoryState Core::memory(const ExecuteInterstage &dt) { if (control_state != nullptr && dt.is_valid && dt.excause == EXCAUSE_NONE) { control_state->increment_internal(CSR::Id::MINSTRET, 1); if (dt.csr_write) { - control_state->write(dt.csr_address, dt.alu_val); + control_state->write(dt.csr_address, dt.alu_val, get_current_privilege()); csr_written = true; } if (dt.xret) { - CSR::PrivilegeLevel restored = control_state->exception_return(get_current_privilege()); + CSR::PrivilegeLevel restored = + control_state->exception_return(get_current_privilege(), dt.xret_privlev); set_current_privilege(restored); if (this->xlen == Xlen::_32) computed_next_inst_addr @@ -577,6 +612,7 @@ MemoryState Core::memory(const ExecuteInterstage &dt) { .regwrite = regwrite, .is_valid = dt.is_valid, .csr_written = csr_written, + .xret_privlev = dt.xret_privlev, } }; } diff --git a/src/machine/core.h b/src/machine/core.h index df36a229..91e9dd27 100644 --- a/src/machine/core.h +++ b/src/machine/core.h @@ -115,8 +115,8 @@ class Core : public QObject { Address mem_ref_addr); const Xlen xlen; - const InstructionFlags check_inst_flags_val; - const InstructionFlags check_inst_flags_mask; + InstructionFlags check_inst_flags_val; + InstructionFlags check_inst_flags_mask; BORROWED Registers *const regs; BORROWED CSR::ControlState *const control_state; BORROWED BranchPredictor *const predictor; diff --git a/src/machine/csr/controlstate.cpp b/src/machine/csr/controlstate.cpp index 491deaa0..0bec3453 100644 --- a/src/machine/csr/controlstate.cpp +++ b/src/machine/csr/controlstate.cpp @@ -55,16 +55,22 @@ namespace machine { namespace CSR { } } - RegisterValue ControlState::read(Address address) const { - // Only machine level privilege is supported so no checking is needed. + RegisterValue ControlState::read(Address address, PrivilegeLevel current_priv) const { size_t reg_id = get_register_internal_id(address); + PrivilegeLevel required = address.get_privilege_level(); + if (current_priv < required) { + throw SIMULATOR_EXCEPTION( + UnsupportedInstruction, + QString("CSR address %1 not accessible at current privilege level.").arg(address.data), + ""); + } RegisterValue value = register_data[reg_id]; DEBUG("Read CSR[%u] == 0x%" PRIx64, address.data, value.as_u64()); emit read_signal(reg_id, value); return value; } - void ControlState::write(Address address, RegisterValue value) { + void ControlState::write(Address address, RegisterValue value, PrivilegeLevel current_priv) { DEBUG( "Write CSR[%u/%zu] <== 0x%zu", address.data, get_register_internal_id(address), value.as_u64()); @@ -74,6 +80,15 @@ namespace machine { namespace CSR { UnsupportedInstruction, QString("CSR address %1 is not writable.").arg(address.data), ""); } + + PrivilegeLevel required = address.get_privilege_level(); + if (current_priv < required) { + throw SIMULATOR_EXCEPTION( + UnsupportedInstruction, + QString("CSR address %1 not writable at current privilege level.").arg(address.data), + ""); + } + write_internal(get_register_internal_id(address), value); } @@ -190,11 +205,11 @@ namespace machine { namespace CSR { emit write_signal(reg_id, reg); } - PrivilegeLevel ControlState::exception_return(enum PrivilegeLevel act_privlev) { + PrivilegeLevel ControlState::exception_return(enum PrivilegeLevel act_privlev, enum PrivilegeLevel xret_privlev) { size_t reg_id = Id::MSTATUS; RegisterValue ® = register_data[reg_id]; PrivilegeLevel restored_privlev = PrivilegeLevel::MACHINE; - if (act_privlev == PrivilegeLevel::MACHINE) { + if (xret_privlev == PrivilegeLevel::MACHINE) { // MRET semantics: // MIE <- MPIE // MPIE <- 1 @@ -212,7 +227,7 @@ namespace machine { namespace CSR { default: restored_privlev = PrivilegeLevel::UNPRIVILEGED; break; } write_field(Field::mstatus::MPP, (uint64_t)0); // clear MPP per spec - } else if (act_privlev == PrivilegeLevel::SUPERVISOR) { + } else if (xret_privlev == PrivilegeLevel::SUPERVISOR) { // SRET semantics: // SIE <- SPIE // SPIE <- 1 @@ -229,6 +244,12 @@ namespace machine { namespace CSR { restored_privlev = PrivilegeLevel::UNPRIVILEGED; } + // If the instruction was executed in M-mode and the restored privilege is less-privileged + // than M, clear MPRV per the privileged spec. + if (act_privlev == PrivilegeLevel::MACHINE && restored_privlev != PrivilegeLevel::MACHINE) { + write_field(Field::mstatus::MPRV, (uint64_t)0); + } + emit write_signal(reg_id, reg); return restored_privlev; diff --git a/src/machine/csr/controlstate.h b/src/machine/csr/controlstate.h index f2443678..0a7dc3b4 100644 --- a/src/machine/csr/controlstate.h +++ b/src/machine/csr/controlstate.h @@ -99,7 +99,7 @@ namespace machine { namespace CSR { ControlState(const ControlState &); /** Read CSR register with ISA specified address. */ - [[nodiscard]] RegisterValue read(Address address) const; + [[nodiscard]] RegisterValue read(Address address, PrivilegeLevel current_priv) const; /** * Read CSR register with an internal id. @@ -110,7 +110,7 @@ namespace machine { namespace CSR { [[nodiscard]] RegisterValue read_internal(size_t internal_id) const; /** Write value to CSR register by ISA specified address and receive the previous value. */ - void write(Address address, RegisterValue value); + void write(Address address, RegisterValue value, PrivilegeLevel current_priv); /** Used for writes occurring as a side-effect (instruction count update...) and * internally by the write method. */ @@ -150,7 +150,7 @@ namespace machine { namespace CSR { public slots: void set_interrupt_signal(uint irq_num, bool active); void exception_initiate(PrivilegeLevel act_privlev, PrivilegeLevel to_privlev); - PrivilegeLevel exception_return(enum PrivilegeLevel act_privlev); + PrivilegeLevel exception_return(enum PrivilegeLevel act_privlev, enum PrivilegeLevel xret_privlev); private: static size_t get_register_internal_id(Address address); @@ -211,6 +211,7 @@ namespace machine { namespace CSR { static constexpr RegisterFieldDesc MIE = { "MIE", Id::MSTATUS, {1, 3}, "Machine global interrupt-enable"}; static constexpr RegisterFieldDesc SPIE = { "SPIE", Id::MSTATUS, {1, 5}, "Previous SIE before the trap"}; static constexpr RegisterFieldDesc MPIE = { "MPIE", Id::MSTATUS, {1, 7}, "Previous MIE before the trap"}; + static constexpr RegisterFieldDesc MPRV = { "MPRV", Id::MSTATUS, {1, 17}, "Modify privilege for loads/stores/fetches" }; static constexpr RegisterFieldDesc SPP = { "SPP", Id::MSTATUS, {1, 8}, "System previous privilege mode"}; static constexpr RegisterFieldDesc MPP = { "MPP", Id::MSTATUS, {2, 11}, "Machine previous privilege mode"}; static constexpr RegisterFieldDesc UXL = { "UXL", Id::MSTATUS, {2, 32}, "User mode XLEN (RV64 only)"}; diff --git a/src/machine/instruction.cpp b/src/machine/instruction.cpp index b60a7e17..a3e9cae1 100644 --- a/src/machine/instruction.cpp +++ b/src/machine/instruction.cpp @@ -464,7 +464,7 @@ static const struct InstructionMap SYSTEM_PRIV_map[] = { IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, - {"sret", IT_I, NOALU, NOMEM, nullptr, {}, 0x10200073, 0xffffffff, { .flags = IMF_SUPPORTED | IMF_XRET }, nullptr}, + {"sret", IT_I, NOALU, NOMEM, nullptr, {}, 0x10200073, 0xffffffff, { .flags = IMF_SUPPORTED | IMF_XRET | IMF_PRIV_S }, nullptr}, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, @@ -480,7 +480,7 @@ static const struct InstructionMap SYSTEM_PRIV_map[] = { IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, - {"mret", IT_I, NOALU, NOMEM, nullptr, {}, 0x30200073, 0xffffffff, { .flags = IMF_SUPPORTED | IMF_XRET }, nullptr}, + {"mret", IT_I, NOALU, NOMEM, nullptr, {}, 0x30200073, 0xffffffff, { .flags = IMF_SUPPORTED | IMF_XRET | IMF_PRIV_M }, nullptr}, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, @@ -749,6 +749,27 @@ void Instruction::flags_alu_op_mem_ctl( flags = (enum InstructionFlags)im.flags; alu_op = im.alu; mem_ctl = im.mem_ctl; + if (flags & IMF_CSR) { + machine::CSR::Address csr = csr_address(); + uint32_t csr12 = csr.data & 0xfffu; + uint32_t min_bits = (csr12 >> 8) & 0x3u; // csr[9:8] + switch (min_bits) { + case 0u: + // User (no extra flag required) + break; + case 1u: + flags = InstructionFlags(flags | IMF_PRIV_S); + break; + case 2u: + flags = InstructionFlags(flags | IMF_PRIV_H); + break; + case 3u: + flags = InstructionFlags(flags | IMF_PRIV_M); + break; + default: + break; + } + } } bool Instruction::operator==(const Instruction &c) const { diff --git a/src/machine/instruction.h b/src/machine/instruction.h index c2bf742b..c33a6e31 100644 --- a/src/machine/instruction.h +++ b/src/machine/instruction.h @@ -60,6 +60,11 @@ enum InstructionFlags : unsigned { // TODO do we want to add those signals to the visualization? IMF_RV64 = 1L << 24, /**< Mark instructions which are available in 64-bit mode only. */ + + /* Privilege requirement flags */ + IMF_PRIV_S = 1L << 25, /**< Requires at least Supervisor privilege */ + IMF_PRIV_H = 1L << 26, /**< Requires at least Hypervisor privilege */ + IMF_PRIV_M = 1L << 27, /**< Requires Machine privilege */ }; /** diff --git a/src/machine/pipeline.h b/src/machine/pipeline.h index de7c6881..60c0f356 100644 --- a/src/machine/pipeline.h +++ b/src/machine/pipeline.h @@ -116,7 +116,8 @@ struct DecodeInterstage { bool csr = false; // Zicsr, implies csr read and csr write bool csr_to_alu = false; bool csr_write = false; - bool xret = false; // Return from exception, MRET and SRET + bool xret = false; // Return from exception, MRET and SRET + CSR::PrivilegeLevel xret_privlev = CSR::PrivilegeLevel::UNPRIVILEGED; bool insert_stall_before = false; public: @@ -179,6 +180,7 @@ struct ExecuteInterstage { bool csr = false; bool csr_write = false; bool xret = false; + CSR::PrivilegeLevel xret_privlev = CSR::PrivilegeLevel::UNPRIVILEGED; public: /** Reset to value corresponding to NOP. */ @@ -245,6 +247,7 @@ struct MemoryInterstage { bool regwrite = false; bool is_valid = false; bool csr_written = false; + CSR::PrivilegeLevel xret_privlev = CSR::PrivilegeLevel::UNPRIVILEGED; public: /** Reset to value corresponding to NOP. */