From 2602c1e3e6211b89f6648c5012eb463ceeeeeac2 Mon Sep 17 00:00:00 2001 From: Jakub Dupak Date: Sun, 19 Oct 2025 15:18:24 +0200 Subject: [PATCH 01/11] 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 c6d40896..e18ed568 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 82aaf4e7c51dd12823e61c70d27a0011abf22cdd 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/11] 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 4df78bd0..5ab7d4ca 100644 --- a/src/gui/graphicsview.cpp +++ b/src/gui/graphicsview.cpp @@ -30,14 +30,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 6c6c7da1b4f253cfc7da41401f1600029f776ec6 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/11] 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 | 97 +++++++++++++++++------------------ src/machine/core/core_state.h | 5 +- 2 files changed, 51 insertions(+), 51 deletions(-) diff --git a/src/machine/core.cpp b/src/machine/core.cpp index c07cb797..e432f344 100644 --- a/src/machine/core.cpp +++ b/src/machine/core.cpp @@ -10,15 +10,11 @@ LOG_CATEGORY("machine.core"); using namespace machine; -static InstructionFlags unsupported_inst_flags_to_check(Xlen xlen, - ConfigIsaWord isa_word) { +static InstructionFlags unsupported_inst_flags_to_check(Xlen xlen, ConfigIsaWord isa_word) { unsigned flags_to_check = IMF_SUPPORTED; - if (xlen == Xlen::_32) - flags_to_check |= IMF_RV64; - if (!isa_word.contains('A')) - flags_to_check |= IMF_AMO; - if (!isa_word.contains('M')) - flags_to_check |= IMF_MUL; + if (xlen == Xlen::_32) flags_to_check |= IMF_RV64; + if (!isa_word.contains('A')) flags_to_check |= IMF_AMO; + if (!isa_word.contains('M')) flags_to_check |= IMF_MUL; return InstructionFlags(flags_to_check); } @@ -60,6 +56,7 @@ void Core::step(bool skip_break) { void Core::reset() { state.cycle_count = 0; state.stall_count = 0; + state.current_privilege = CSR::PrivilegeLevel::MACHINE; do_reset(); } @@ -140,7 +137,7 @@ void Core::register_exception_handler(ExceptionCause excause, ExceptionHandler * bool Core::handle_exception( ExceptionCause excause, - const Instruction& inst, + const Instruction &inst, Address inst_addr, Address next_addr, Address jump_branch_pc, @@ -158,7 +155,9 @@ bool Core::handle_exception( control_state->update_exception_cause(excause); if (control_state->read_internal(CSR::Id::MTVEC) != 0 && !get_step_over_exception(excause)) { - control_state->exception_initiate(CSR::PrivilegeLevel::MACHINE, CSR::PrivilegeLevel::MACHINE); + control_state->exception_initiate( + state.current_privilege, CSR::PrivilegeLevel::MACHINE); + state.current_privilege = CSR::PrivilegeLevel::MACHINE; regs->write_pc(control_state->exception_pc_address()); } } @@ -176,32 +175,32 @@ bool Core::handle_exception( } static int32_t amo32_operations(enum AccessControl memctl, int32_t a, int32_t b) { - switch(memctl) { + switch (memctl) { case AC_AMOSWAP32: return b; - case AC_AMOADD32: return a + b; - case AC_AMOXOR32: return a ^ b; - case AC_AMOAND32: return a & b; - case AC_AMOOR32: return a | b; - case AC_AMOMIN32: return a < b? a: b; - case AC_AMOMAX32: return a < b? b: a; - case AC_AMOMINU32: return (uint32_t)a < (uint32_t)b? a: b; - case AC_AMOMAXU32: return (uint32_t)a < (uint32_t)b? b: a; + case AC_AMOADD32: return a + b; + case AC_AMOXOR32: return a ^ b; + case AC_AMOAND32: return a & b; + case AC_AMOOR32: return a | b; + case AC_AMOMIN32: return a < b ? a : b; + case AC_AMOMAX32: return a < b ? b : a; + case AC_AMOMINU32: return (uint32_t)a < (uint32_t)b ? a : b; + case AC_AMOMAXU32: return (uint32_t)a < (uint32_t)b ? b : a; default: break; } return 0; } static int64_t amo64_operations(enum AccessControl memctl, int64_t a, int64_t b) { - switch(memctl) { + switch (memctl) { case AC_AMOSWAP64: return b; - case AC_AMOADD64: return a + b; - case AC_AMOXOR64: return a ^ b; - case AC_AMOAND64: return a & b; - case AC_AMOOR64: return a | b; - case AC_AMOMIN64: return a < b? a: b; - case AC_AMOMAX64: return a < b? b: a; - case AC_AMOMINU64: return (uint64_t)a < (uint64_t)b? a: b; - case AC_AMOMAXU64: return (uint64_t)a < (uint64_t)b? b: a; + case AC_AMOADD64: return a + b; + case AC_AMOXOR64: return a ^ b; + case AC_AMOAND64: return a & b; + case AC_AMOOR64: return a | b; + case AC_AMOMIN64: return a < b ? a : b; + case AC_AMOMAX64: return a < b ? b : a; + case AC_AMOMINU64: return (uint64_t)a < (uint64_t)b ? a : b; + case AC_AMOMAXU64: return (uint64_t)a < (uint64_t)b ? b : a; default: break; } return 0; @@ -252,8 +251,7 @@ enum ExceptionCause Core::memory_special( towrite_val = 1; } break; - case AC_FISRT_AMO_MODIFY32 ... AC_LAST_AMO_MODIFY32: - { + case AC_FISRT_AMO_MODIFY32 ... AC_LAST_AMO_MODIFY32: { if (!memread || !memwrite) { break; } int32_t fetched_value; fetched_value = (int32_t)(mem_data->read_u32(mem_addr)); @@ -262,8 +260,7 @@ enum ExceptionCause Core::memory_special( towrite_val = fetched_value; break; } - case AC_FISRT_AMO_MODIFY64 ... AC_LAST_AMO_MODIFY64: - { + case AC_FISRT_AMO_MODIFY64 ... AC_LAST_AMO_MODIFY64: { if (!memread || !memwrite) { break; } int64_t fetched_value; fetched_value = (int64_t)(mem_data->read_u64(mem_addr)); @@ -287,9 +284,7 @@ FetchState Core::fetch(PCInterstage pc, bool skip_break) { if (!skip_break && hw_breaks.contains(inst_addr)) { excause = EXCAUSE_HWBREAK; } - if (control_state != nullptr) { - control_state->increment_internal(CSR::Id::MCYCLE, 1); - } + if (control_state != nullptr) { control_state->increment_internal(CSR::Id::MCYCLE, 1); } if (control_state != nullptr && excause == EXCAUSE_NONE) { if (control_state->core_interrupt_request()) { excause = EXCAUSE_INT; } @@ -315,9 +310,7 @@ DecodeState Core::decode(const FetchInterstage &dt) { dt.inst.flags_alu_op_mem_ctl(flags, alu_op, mem_ctl); - if ((flags ^ check_inst_flags_val) & check_inst_flags_mask) { - excause = EXCAUSE_INSN_ILLEGAL; - } + 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; RegisterId num_rt = (flags & IMF_ALU_REQ_RT) ? dt.inst.rt() : 0; @@ -343,8 +336,7 @@ DecodeState Core::decode(const FetchInterstage &dt) { // TODO: EXCAUSE_ECALL_S, EXCAUSE_ECALL_U } } - if (flags & IMF_FORCE_W_OP) - w_operation = true; + if (flags & IMF_FORCE_W_OP) w_operation = true; return { DecodeInternalState { .alu_op_num = static_cast(alu_op.alu_op), @@ -366,8 +358,9 @@ DecodeState Core::decode(const FetchInterstage &dt) { .excause = excause, .ff_rs = FORWARD_NONE, .ff_rt = FORWARD_NONE, - .alu_component = (flags & IMF_AMO) ? AluComponent::PASS : - (flags & IMF_MUL) ? AluComponent::MUL : AluComponent::ALU, + .alu_component = (flags & IMF_AMO) ? AluComponent::PASS + : (flags & IMF_MUL) ? AluComponent::MUL + : AluComponent::ALU, .aluop = alu_op, .memctl = mem_ctl, .num_rs = num_rs, @@ -409,7 +402,8 @@ ExecuteState Core::execute(const DecodeInterstage &dt) { }(); const RegisterValue alu_val = [=] { if (excause != EXCAUSE_NONE) return RegisterValue(0); - return alu_combined_operate(dt.aluop, dt.alu_component, dt.w_operation, dt.alu_mod, alu_fst, alu_sec); + return alu_combined_operate( + dt.aluop, dt.alu_component, dt.w_operation, dt.alu_mod, alu_fst, alu_sec); }(); const Address branch_jal_target = dt.inst_addr + dt.immediate_val.as_i64(); @@ -502,11 +496,13 @@ MemoryState Core::memory(const ExecuteInterstage &dt) { // Predictor update if (dt.branch_jal) { // JAL Jump instruction (J-type (alternative to U-type with different immediate bit order)) - predictor->update(dt.inst, dt.inst_addr, dt.branch_jal_target, BranchType::JUMP, BranchResult::TAKEN); + predictor->update( + dt.inst, dt.inst_addr, dt.branch_jal_target, BranchType::JUMP, BranchResult::TAKEN); } else if (dt.branch_jalr) { // JALR Jump register instruction (I-type) predictor->update( - dt.inst, dt.inst_addr, Address(get_xlen_from_reg(dt.alu_val)), BranchType::JUMP, BranchResult::TAKEN); + dt.inst, dt.inst_addr, Address(get_xlen_from_reg(dt.alu_val)), BranchType::JUMP, + BranchResult::TAKEN); } else if (dt.branch_bxx) { // BXX Conditional branch instruction (B-type (alternative to S-type with different // immediate bit order)) @@ -523,11 +519,14 @@ 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(state.current_privilege); + state.current_privilege = restored; if (this->xlen == Xlen::_32) - computed_next_inst_addr = Address(control_state->read_internal(CSR::Id::MEPC).as_u32()); + computed_next_inst_addr + = Address(control_state->read_internal(CSR::Id::MEPC).as_u32()); else - computed_next_inst_addr = Address(control_state->read_internal(CSR::Id::MEPC).as_u64()); + computed_next_inst_addr + = Address(control_state->read_internal(CSR::Id::MEPC).as_u64()); csr_written = true; } } @@ -577,7 +576,7 @@ WritebackState Core::writeback(const MemoryInterstage &dt) { if (dt.regwrite) { regs->write_gp(dt.num_rd, dt.towrite_val); } return WritebackState { WritebackInternalState { - .inst = (dt.excause == EXCAUSE_NONE)? dt.inst: Instruction::NOP, + .inst = (dt.excause == EXCAUSE_NONE) ? dt.inst : Instruction::NOP, .inst_addr = dt.inst_addr, .value = dt.towrite_val, .num_rd = dt.num_rd, diff --git a/src/machine/core/core_state.h b/src/machine/core/core_state.h index 67c1f7a8..887c9649 100644 --- a/src/machine/core/core_state.h +++ b/src/machine/core/core_state.h @@ -1,10 +1,10 @@ #ifndef QTRVSIM_CORE_STATE_H #define QTRVSIM_CORE_STATE_H -#include "machinedefs.h" -#include "pipeline.h" #include "common/memory_ownership.h" +#include "machinedefs.h" #include "memory/address_range.h" +#include "pipeline.h" #include #include @@ -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 363141c727fb631cd6471ce1cd3aa7f4bae9ea41 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/11] Machine: add supervisor CSRs and sstatus handling Add supervisor CSRs (sstatus, stvec, sscratch, sepc, scause, stval, satp) and a write handler that presents sstatus as a masked view of mstatus so supervisor-visible bits stay in sync. --- src/machine/csr/controlstate.cpp | 77 +++++++++++++++++++++++++------- src/machine/csr/controlstate.h | 34 +++++++++++++- 2 files changed, 95 insertions(+), 16 deletions(-) diff --git a/src/machine/csr/controlstate.cpp b/src/machine/csr/controlstate.cpp index 12aeab5e..491deaa0 100644 --- a/src/machine/csr/controlstate.cpp +++ b/src/machine/csr/controlstate.cpp @@ -1,4 +1,3 @@ -#include #include "controlstate.h" #include "common/logging.h" @@ -6,6 +5,7 @@ #include "simulator_exception.h" #include +#include LOG_CATEGORY("machine.csr.control_state"); @@ -20,7 +20,8 @@ namespace machine { namespace CSR { ControlState::ControlState(const ControlState &other) : QObject(this->parent()) - , xlen(other.xlen), register_data(other.register_data) {} + , xlen(other.xlen) + , register_data(other.register_data) {} void ControlState::reset() { std::transform( @@ -83,14 +84,13 @@ namespace machine { namespace CSR { uint64_t u; u = val.as_u64() & desc.write_mask.as_u64(); u |= reg.as_u64() & ~desc.write_mask.as_u64(); - if (xlen == Xlen::_32) - u &= 0xffffffff; + if (xlen == Xlen::_32) u &= 0xffffffff; reg = u; } void ControlState::mstatus_wlrl_write_handler( const RegisterDesc &desc, RegisterValue ®, - RegisterValue val) { + RegisterValue val) { default_wlrl_write_handler(desc, reg, val); } @@ -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; } @@ -128,8 +149,7 @@ namespace machine { namespace CSR { irq_to_signal = 63 - qCountLeadingZeroBits(irqs & (~irqs + 1)); } - value = (uint64_t)(irq_to_signal | - ((uint64_t)1 << ((xlen == Xlen::_32)? 31: 63))); + value = (uint64_t)(irq_to_signal | ((uint64_t)1 << ((xlen == Xlen::_32) ? 31 : 63))); } emit write_signal(Id::MCAUSE, value); } @@ -173,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 90918fd9..f2443678 100644 --- a/src/machine/csr/controlstate.h +++ b/src/machine/csr/controlstate.h @@ -46,7 +46,20 @@ 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 }; }; @@ -170,6 +183,10 @@ namespace machine { namespace CSR { const RegisterDesc &desc, RegisterValue ®, RegisterValue val); + void sstatus_wlrl_write_handler( + const RegisterDesc &desc, + RegisterValue ®, + RegisterValue val); }; struct RegisterDesc { @@ -201,6 +218,13 @@ namespace machine { namespace CSR { 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 */ @@ -231,6 +255,14 @@ namespace machine { namespace CSR { [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 From 2d5db0ac84fe11da3f2b15dac23587923ac9f6c3 Mon Sep 17 00:00:00 2001 From: Jakub Dupak Date: Sun, 19 Oct 2025 18:10:37 +0200 Subject: [PATCH 05/11] Machine: add TLB with policies and add SV32 page-table walker --- src/machine/CMakeLists.txt | 40 ++- src/machine/machine.cpp | 224 +++++++------- src/machine/machine.h | 15 +- src/machine/machineconfig.cpp | 107 +++++++ src/machine/machineconfig.h | 56 +++- src/machine/memory/frontend_memory.cpp | 3 +- src/machine/memory/tlb/tlb.cpp | 274 ++++++++++++++++++ src/machine/memory/tlb/tlb.h | 126 ++++++++ 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, 1292 insertions(+), 138 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..741b84a4 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) @@ -205,6 +227,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 registers.cpp @@ -222,4 +250,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 c50ae78e..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()); @@ -24,9 +23,7 @@ Machine::Machine(MachineConfig config, bool load_symtab, bool load_executable) else this->machine_config.set_simulated_xlen(Xlen::_32); - if (load_symtab) { - symtab = program.get_symbol_table(); - } + if (load_symtab) { symtab = program.get_symbol_table(); } program_end = program.end(); if (program.get_executable_entry() != 0x0_addr) { @@ -38,8 +35,7 @@ Machine::Machine(MachineConfig config, bool load_symtab, bool load_executable) } data_bus = new MemoryDataBus(machine_config.get_simulated_endian()); - data_bus->insert_device_to_range( - mem, 0x00000000_addr, 0xefffffff_addr, false); + data_bus->insert_device_to_range(mem, 0x00000000_addr, 0xefffffff_addr, false); setup_serial_port(); setup_perip_spi_led(); @@ -54,11 +50,8 @@ Machine::Machine(MachineConfig config, bool load_symtab, bool load_executable) bool access_enable_burst = machine_config.memory_access_enable_burst(); cch_level2 = new Cache( - data_bus, &machine_config.cache_level2(), - access_time_read, - access_time_write, - access_time_burst, - access_enable_burst); + data_bus, &machine_config.cache_level2(), access_time_read, access_time_write, + access_time_burst, access_enable_burst); if (machine_config.cache_level2().enabled()) { access_time_read = machine_config.memory_access_time_level2(); access_time_write = machine_config.memory_access_time_level2(); @@ -66,19 +59,23 @@ Machine::Machine(MachineConfig config, bool load_symtab, bool load_executable) access_enable_burst = true; } cch_program = new Cache( - cch_level2, &machine_config.cache_program(), - access_time_read, - access_time_write, - access_time_burst, - access_enable_burst); + cch_level2, &machine_config.cache_program(), access_time_read, access_time_write, + access_time_burst, access_enable_burst); cch_data = new Cache( - cch_level2, &machine_config.cache_data(), - access_time_read, - access_time_write, - access_time_burst, - access_enable_burst); + cch_level2, &machine_config.cache_data(), access_time_read, access_time_write, + access_time_burst, access_enable_burst); + + 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); - controlst = new CSR::ControlState(machine_config.get_simulated_xlen(), machine_config.get_isa_word()); 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(), @@ -86,11 +83,12 @@ 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(), machine_config.get_isa_word(), machine_config.hazard_unit()); + 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(), machine_config.get_isa_word()); + cr = new CoreSingle( + regs, predictor, tlb_program, tlb_data, controlst, machine_config.get_simulated_xlen(), + machine_config.get_isa_word()); } connect( this, &Machine::set_interrupt_signal, controlst, &CSR::ControlState::set_interrupt_signal); @@ -101,10 +99,8 @@ Machine::Machine(MachineConfig config, bool load_symtab, bool load_executable) for (int i = 0; i < EXCAUSE_COUNT; i++) { if (i != EXCAUSE_INT && i != EXCAUSE_BREAK && i != EXCAUSE_HWBREAK) { - set_stop_on_exception( - (enum ExceptionCause)i, machine_config.osemu_exception_stop()); - set_step_over_exception( - (enum ExceptionCause)i, machine_config.osemu_exception_stop()); + set_stop_on_exception((enum ExceptionCause)i, machine_config.osemu_exception_stop()); + set_step_over_exception((enum ExceptionCause)i, machine_config.osemu_exception_stop()); } } @@ -113,16 +109,14 @@ Machine::Machine(MachineConfig config, bool load_symtab, bool load_executable) } void Machine::setup_lcd_display() { perip_lcd_display = new LcdDisplay(machine_config.get_simulated_endian()); - memory_bus_insert_range( - perip_lcd_display, 0xffe00000_addr, 0xffe4afff_addr, true); + memory_bus_insert_range(perip_lcd_display, 0xffe00000_addr, 0xffe4afff_addr, true); if (machine_config.get_simulated_xlen() == Xlen::_64) memory_bus_insert_range( perip_lcd_display, 0xffffffffffe00000_addr, 0xffffffffffe4afff_addr, false); } void Machine::setup_perip_spi_led() { perip_spi_led = new PeripSpiLed(machine_config.get_simulated_endian()); - memory_bus_insert_range( - perip_spi_led, 0xffffc100_addr, 0xffffc1ff_addr, true); + memory_bus_insert_range(perip_spi_led, 0xffffc100_addr, 0xffffc1ff_addr, true); if (machine_config.get_simulated_xlen() == Xlen::_64) memory_bus_insert_range( perip_spi_led, 0xffffffffffffc100_addr, 0xffffffffffffc1ff_addr, false); @@ -133,22 +127,19 @@ void Machine::setup_serial_port() { memory_bus_insert_range(ser_port, 0xffff0000_addr, 0xffff003f_addr, false); if (machine_config.get_simulated_xlen() == Xlen::_64) memory_bus_insert_range(ser_port, 0xffffffffffffc000_addr, 0xffffffffffffc03f_addr, false); - connect( - ser_port, &SerialPort::signal_interrupt, this, - &Machine::set_interrupt_signal); + connect(ser_port, &SerialPort::signal_interrupt, this, &Machine::set_interrupt_signal); } void Machine::setup_aclint_mtime() { aclint_mtimer = new aclint::AclintMtimer(machine_config.get_simulated_endian()); - memory_bus_insert_range(aclint_mtimer, - 0xfffd0000_addr + aclint::CLINT_MTIMER_OFFSET, - 0xfffd0000_addr + aclint::CLINT_MTIMER_OFFSET + aclint::CLINT_MTIMER_SIZE - 1, - true); + memory_bus_insert_range( + aclint_mtimer, 0xfffd0000_addr + aclint::CLINT_MTIMER_OFFSET, + 0xfffd0000_addr + aclint::CLINT_MTIMER_OFFSET + aclint::CLINT_MTIMER_SIZE - 1, true); if (machine_config.get_simulated_xlen() == Xlen::_64) - memory_bus_insert_range(aclint_mtimer, - 0xfffffffffffd0000_addr + aclint::CLINT_MTIMER_OFFSET, - 0xfffffffffffd0000_addr + aclint::CLINT_MTIMER_OFFSET + aclint::CLINT_MTIMER_SIZE - 1, - false); + memory_bus_insert_range( + aclint_mtimer, 0xfffffffffffd0000_addr + aclint::CLINT_MTIMER_OFFSET, + 0xfffffffffffd0000_addr + aclint::CLINT_MTIMER_OFFSET + aclint::CLINT_MTIMER_SIZE - 1, + false); connect( aclint_mtimer, &aclint::AclintMtimer::signal_interrupt, this, &Machine::set_interrupt_signal); @@ -156,34 +147,30 @@ void Machine::setup_aclint_mtime() { void Machine::setup_aclint_mswi() { aclint_mswi = new aclint::AclintMswi(machine_config.get_simulated_endian()); - memory_bus_insert_range(aclint_mswi, - 0xfffd0000_addr + aclint::CLINT_MSWI_OFFSET, - 0xfffd0000_addr + aclint::CLINT_MSWI_OFFSET + aclint::CLINT_MSWI_SIZE - 1, - true); + memory_bus_insert_range( + aclint_mswi, 0xfffd0000_addr + aclint::CLINT_MSWI_OFFSET, + 0xfffd0000_addr + aclint::CLINT_MSWI_OFFSET + aclint::CLINT_MSWI_SIZE - 1, true); if (machine_config.get_simulated_xlen() == Xlen::_64) - memory_bus_insert_range(aclint_mswi, - 0xfffffffffffd0000_addr + aclint::CLINT_MSWI_OFFSET, - 0xfffffffffffd0000_addr + aclint::CLINT_MSWI_OFFSET + aclint::CLINT_MSWI_SIZE - 1, - false); + memory_bus_insert_range( + aclint_mswi, 0xfffffffffffd0000_addr + aclint::CLINT_MSWI_OFFSET, + 0xfffffffffffd0000_addr + aclint::CLINT_MSWI_OFFSET + aclint::CLINT_MSWI_SIZE - 1, + false); connect( - aclint_mswi, &aclint::AclintMswi::signal_interrupt, this, - &Machine::set_interrupt_signal); + aclint_mswi, &aclint::AclintMswi::signal_interrupt, this, &Machine::set_interrupt_signal); } void Machine::setup_aclint_sswi() { aclint_sswi = new aclint::AclintSswi(machine_config.get_simulated_endian()); - memory_bus_insert_range(aclint_sswi, - 0xfffd0000_addr + aclint::CLINT_SSWI_OFFSET, - 0xfffd0000_addr + aclint::CLINT_SSWI_OFFSET + aclint::CLINT_SSWI_SIZE - 1, - true); + memory_bus_insert_range( + aclint_sswi, 0xfffd0000_addr + aclint::CLINT_SSWI_OFFSET, + 0xfffd0000_addr + aclint::CLINT_SSWI_OFFSET + aclint::CLINT_SSWI_SIZE - 1, true); if (machine_config.get_simulated_xlen() == Xlen::_64) - memory_bus_insert_range(aclint_sswi, - 0xfffffffffffd0000_addr + aclint::CLINT_SSWI_OFFSET, - 0xfffffffffffd0000_addr + aclint::CLINT_SSWI_OFFSET + aclint::CLINT_SSWI_SIZE - 1, - false); + memory_bus_insert_range( + aclint_sswi, 0xfffffffffffd0000_addr + aclint::CLINT_SSWI_OFFSET, + 0xfffffffffffd0000_addr + aclint::CLINT_SSWI_OFFSET + aclint::CLINT_SSWI_SIZE - 1, + false); connect( - aclint_sswi, &aclint::AclintSswi::signal_interrupt, this, - &Machine::set_interrupt_signal); + aclint_sswi, &aclint::AclintSswi::signal_interrupt, this, &Machine::set_interrupt_signal); } Machine::~Machine() { @@ -197,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; @@ -259,15 +250,30 @@ Cache *Machine::cache_data_rw() { } void Machine::cache_sync() { - if (cch_program != nullptr) { - cch_program->sync(); - } - if (cch_data != nullptr) { - cch_data->sync(); - } - if (cch_level2 != nullptr) { - cch_level2->sync(); - } + if (cch_program != nullptr) { cch_program->sync(); } + if (cch_data != nullptr) { cch_data->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() { @@ -291,9 +297,7 @@ LcdDisplay *Machine::peripheral_lcd_display() { } SymbolTable *Machine::symbol_table_rw(bool create) { - if (create && (symtab == nullptr)) { - symtab = new SymbolTable; - } + if (create && (symtab == nullptr)) { symtab = new SymbolTable; } return symtab; } @@ -307,9 +311,7 @@ void Machine::set_symbol( uint32_t size, unsigned char info, unsigned char other) { - if (symtab == nullptr) { - symtab = new SymbolTable; - } + if (symtab == nullptr) { symtab = new SymbolTable; } symtab->set_symbol(name, value, size, info, other); } @@ -339,10 +341,9 @@ bool Machine::exited() { // We don't allow to call control methods when machine exited or if it's busy // We rather silently fail. -#define CTL_GUARD \ - do { \ - if (exited() || stat == ST_BUSY) \ - return; \ +#define CTL_GUARD \ + do { \ + if (exited() || stat == ST_BUSY) return; \ } while (false) void Machine::play() { @@ -354,9 +355,7 @@ void Machine::play() { } void Machine::pause() { - if (stat != ST_BUSY) { - CTL_GUARD; - } + if (stat != ST_BUSY) { CTL_GUARD; } set_status(ST_READY); stop_core_clock(); emit play_paused(); @@ -449,17 +448,11 @@ void Machine::restart() { void Machine::set_status(enum Status st) { bool change = st != stat; stat = st; - if (change) { - emit status_change(st); - } + if (change) { emit status_change(st); } } -void Machine::register_exception_handler( - ExceptionCause excause, - ExceptionHandler *exhandler) { - if (cr != nullptr) { - cr->register_exception_handler(excause, exhandler); - } +void Machine::register_exception_handler(ExceptionCause excause, ExceptionHandler *exhandler) { + if (cr != nullptr) { cr->register_exception_handler(excause, exhandler); } } bool Machine::memory_bus_insert_range( @@ -467,63 +460,44 @@ bool Machine::memory_bus_insert_range( Address start_addr, Address last_addr, bool move_ownership) { - if (data_bus == nullptr) { - return false; - } - return data_bus->insert_device_to_range( - mem_acces, start_addr, last_addr, move_ownership); + if (data_bus == nullptr) { return false; } + return data_bus->insert_device_to_range(mem_acces, start_addr, last_addr, move_ownership); } void Machine::insert_hwbreak(Address address) { - if (cr != nullptr) { - cr->insert_hwbreak(address); - } + if (cr != nullptr) { cr->insert_hwbreak(address); } } void Machine::remove_hwbreak(Address address) { - if (cr != nullptr) { - cr->remove_hwbreak(address); - } + if (cr != nullptr) { cr->remove_hwbreak(address); } } bool Machine::is_hwbreak(Address address) { - if (cr != nullptr) { - return cr->is_hwbreak(address); - } + if (cr != nullptr) { return cr->is_hwbreak(address); } return false; } void Machine::set_stop_on_exception(enum ExceptionCause excause, bool value) { - if (cr != nullptr) { - cr->set_stop_on_exception(excause, value); - } + if (cr != nullptr) { cr->set_stop_on_exception(excause, value); } } bool Machine::get_stop_on_exception(enum ExceptionCause excause) const { - if (cr != nullptr) { - return cr->get_stop_on_exception(excause); - } + if (cr != nullptr) { return cr->get_stop_on_exception(excause); } return false; } void Machine::set_step_over_exception(enum ExceptionCause excause, bool value) { - if (cr != nullptr) { - cr->set_step_over_exception(excause, value); - } + if (cr != nullptr) { cr->set_step_over_exception(excause, value); } } bool Machine::get_step_over_exception(enum ExceptionCause excause) const { - if (cr != nullptr) { - return cr->get_step_over_exception(excause); - } + if (cr != nullptr) { return cr->get_step_over_exception(excause); } return false; } enum ExceptionCause Machine::get_exception_cause() const { uint32_t val; - if (controlst == nullptr) { - return EXCAUSE_NONE; - } + if (controlst == nullptr) { return EXCAUSE_NONE; } val = (controlst->read_internal(CSR::Id::MCAUSE).as_u64()); if (val & 0xffffffff80000000) { return EXCAUSE_INT; diff --git a/src/machine/machine.h b/src/machine/machine.h index fc5ba5f9..026b3933 100644 --- a/src/machine/machine.h +++ b/src/machine/machine.h @@ -3,15 +3,16 @@ #include "core.h" #include "machineconfig.h" +#include "memory/backend/aclintmswi.h" +#include "memory/backend/aclintmtimer.h" +#include "memory/backend/aclintsswi.h" #include "memory/backend/lcddisplay.h" #include "memory/backend/peripheral.h" #include "memory/backend/peripspiled.h" #include "memory/backend/serialport.h" -#include "memory/backend/aclintmtimer.h" -#include "memory/backend/aclintmswi.h" -#include "memory/backend/aclintsswi.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 0a4e74bf..f957891a 100644 --- a/src/machine/machineconfig.cpp +++ b/src/machine/machineconfig.cpp @@ -27,6 +27,10 @@ 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 1 +#define DFC_TLB_ASSOC 1 ////////////////////////////////////////////////////////////////////////////// /// Default config of CacheConfig #define DFC_EN false @@ -151,6 +155,67 @@ 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_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_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(); +} + +TLBConfig::TLBConfig(const QSettings *sts, const QString &prefix) { + vm_asid = sts->value(prefix + "VM_ASID", 0u).toUInt(); + n_sets = sts->value(prefix + "NumSets", DFC_TLB_SETS).toUInt(); + d_associativity = sts->value(prefix + "Associativity", DFC_TLB_ASSOC).toUInt(); + replac_pol = (enum ReplacementPolicy)sts->value(prefix + "Policy", DFC_REPLAC).toUInt(); +} + +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; +} MachineConfig::MachineConfig() { simulated_endian = LITTLE; @@ -186,6 +251,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 +292,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 +341,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 +378,9 @@ 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()); } #undef N @@ -558,6 +641,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 +728,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) diff --git a/src/machine/machineconfig.h b/src/machine/machineconfig.h index 5392d9ef..11527771 100644 --- a/src/machine/machineconfig.h +++ b/src/machine/machineconfig.h @@ -27,10 +27,12 @@ enum ConfigPresets { CP_PIPE // Full pipelined cpu }; -constexpr ConfigIsaWord config_isa_word_default = ConfigIsaWord::byChar('E') | ConfigIsaWord::byChar('I') | - ConfigIsaWord::byChar('A') |ConfigIsaWord::byChar('M'); +constexpr ConfigIsaWord config_isa_word_default + = ConfigIsaWord::byChar('E') | ConfigIsaWord::byChar('I') | ConfigIsaWord::byChar('A') + | ConfigIsaWord::byChar('M'); -constexpr ConfigIsaWord config_isa_word_fixed = ConfigIsaWord::byChar('E') | ConfigIsaWord::byChar('I'); +constexpr ConfigIsaWord config_isa_word_fixed + = ConfigIsaWord::byChar('E') | ConfigIsaWord::byChar('I'); class CacheConfig { public: @@ -82,6 +84,41 @@ class CacheConfig { enum WritePolicy write_pol; }; +class TLBConfig { +public: + TLBConfig(); + explicit TLBConfig(const TLBConfig *cc); + explicit TLBConfig(const QSettings *, const QString &prefix = ""); + + 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; + +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(); @@ -161,6 +198,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); @@ -181,6 +224,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; @@ -208,6 +254,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 a5e2205e..3c61fdcf 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 { @@ -153,4 +154,4 @@ bool FrontendMemory::write_generic( } 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..6872a178 --- /dev/null +++ b/src/machine/memory/tlb/tlb.cpp @@ -0,0 +1,274 @@ +#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 { + +static bool is_mmio_region(uint64_t virt) { + if (virt >= 0xFFFFC000u && virt <= 0xFFFFC1FFu) return true; + if (virt >= 0xFFE00000u && virt <= 0xFFE4AFFFu) return true; + if (virt >= 0xFFFD0000u && virt <= 0xFFFDBFFFu) return true; + return false; +} + +static Address bypass_mmio(Address vaddr) { + return vaddr; // VA == PA for devices +} + +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) + , 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 (is_mmio_region(virt)) { return bypass_mmio(vaddr); } + + 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; +} + +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..d590b9fe --- /dev/null +++ b/src/machine/memory/tlb/tlb.h @@ -0,0 +1,126 @@ +#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; + 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; + + 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..93c7eb3f --- /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..9259a965 --- /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..da22b3a5 --- /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 \ No newline at end of file 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..b26b7568 --- /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 \ No newline at end of file diff --git a/src/machine/memory/virtual/sv32.h b/src/machine/memory/virtual/sv32.h new file mode 100644 index 00000000..e9ff7aa5 --- /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..3840c57d --- /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 cc69982e..4b655f51 100644 --- a/src/machine/simulator_exception.h +++ b/src/machine/simulator_exception.h @@ -48,6 +48,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, ) \ @@ -58,6 +69,7 @@ class SimulatorException : public std::exception { EXCEPTION(UnalignedJump, Runtime) \ EXCEPTION(UnknownMemoryControl, Runtime) \ EXCEPTION(OutOfMemoryAccess, Runtime) \ + EXCEPTION(PageFault, Runtime) \ EXCEPTION(Sanity, ) \ EXCEPTION(SyscallUnknown, Runtime) From d7ff20e33dd32c88d744883fb0f0e8dc048bdfb8 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/11] 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 | 11 ++++++----- src/machine/core/core_state.h | 10 +++++++++- 7 files changed, 86 insertions(+), 7 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 + + read_internal(CSR::Id::MTVEC) != 0 && !get_step_over_exception(excause)) { control_state->exception_initiate( - state.current_privilege, CSR::PrivilegeLevel::MACHINE); - state.current_privilege = CSR::PrivilegeLevel::MACHINE; + state.current_privilege(), CSR::PrivilegeLevel::MACHINE); + state.set_current_privilege(CSR::PrivilegeLevel::MACHINE); regs->write_pc(control_state->exception_pc_address()); } } @@ -519,8 +519,9 @@ MemoryState Core::memory(const ExecuteInterstage &dt) { csr_written = true; } if (dt.xret) { - CSR::PrivilegeLevel restored = control_state->exception_return(state.current_privilege); - state.current_privilege = restored; + CSR::PrivilegeLevel restored + = control_state->exception_return(state.current_privilege()); + state.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/core_state.h b/src/machine/core/core_state.h index 887c9649..00a6f5ce 100644 --- a/src/machine/core/core_state.h +++ b/src/machine/core/core_state.h @@ -18,7 +18,15 @@ struct CoreState { AddressRange LoadReservedRange; uint32_t stall_count = 0; uint32_t cycle_count = 0; - CSR::PrivilegeLevel current_privilege = CSR::PrivilegeLevel::MACHINE; + unsigned current_privilege_u = static_cast(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 bdfdea126c796e80a3faf595c4f57e6de3f4f32c 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/11] 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 | 141 ++++++++++++++++++ src/gui/dialogs/new/newdialog.cpp | 239 ++++++++++++++---------------- src/gui/dialogs/new/newdialog.h | 9 +- src/gui/ui/pow2spinbox.cpp | 81 ++++++++++ src/gui/ui/pow2spinbox.h | 19 +++ 6 files changed, 365 insertions(+), 126 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..dd1073bb 100644 --- a/src/gui/dialogs/new/NewDialog.ui +++ b/src/gui/dialogs/new/NewDialog.ui @@ -66,6 +66,9 @@ + + 3 + Presets and ELF File @@ -263,6 +266,9 @@ + + true + Branch Predictor @@ -656,6 +662,134 @@ + + + Virtual Memory + + + + true + + + + 10 + 10 + 471 + 181 + + + + Virtual Memory + + + true + + + true + + + + true + + + + 20 + 30 + 431 + 131 + + + + 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) + + + + + + + + Memory Timings @@ -955,6 +1089,13 @@ + + + Pow2SpinBox + QSpinBox +
ui/pow2spinbox.h
+
+
diff --git a/src/gui/dialogs/new/newdialog.cpp b/src/gui/dialogs/new/newdialog.cpp index 5fd5bae2..4d779582 100644 --- a/src/gui/dialogs/new/newdialog.cpp +++ b/src/gui/dialogs/new/newdialog.cpp @@ -33,79 +33,40 @@ NewDialog::NewDialog(QWidget *parent, QSettings *settings) : QDialog(parent) { for (int i = 0; i < ui->config_pages->count(); ++i) { QString page_id = ui->config_pages->widget(i)->objectName(); QString page_name = ui->config_pages->widget(i)->accessibleName(); - config_pages_items.append(new QTreeWidgetItem(static_cast(nullptr), - QStringList{page_name, page_id})); + config_pages_items.append(new QTreeWidgetItem( + static_cast(nullptr), QStringList { page_name, page_id })); } ui->page_select_tree->insertTopLevelItems(0, config_pages_items); - connect( - ui->page_select_tree, &QTreeWidget::currentItemChanged, - this, &NewDialog::switch2page); + connect(ui->page_select_tree, &QTreeWidget::currentItemChanged, this, &NewDialog::switch2page); + connect(ui->pushButton_example, &QAbstractButton::clicked, this, &NewDialog::create_example); + connect(ui->pushButton_start_empty, &QAbstractButton::clicked, this, &NewDialog::create_empty); + connect(ui->pushButton_load, &QAbstractButton::clicked, this, &NewDialog::create); + connect(ui->pushButton_cancel, &QAbstractButton::clicked, this, &NewDialog::cancel); + connect(ui->pushButton_browse, &QAbstractButton::clicked, this, &NewDialog::browse_elf); + connect(ui->elf_file, &QLineEdit::textChanged, this, &NewDialog::elf_change); + connect(ui->preset_no_pipeline, &QAbstractButton::toggled, this, &NewDialog::set_preset); + connect(ui->preset_no_pipeline_cache, &QAbstractButton::toggled, this, &NewDialog::set_preset); + connect(ui->preset_pipelined_bare, &QAbstractButton::toggled, this, &NewDialog::set_preset); + connect(ui->preset_pipelined, &QAbstractButton::toggled, this, &NewDialog::set_preset); connect( - ui->pushButton_example, &QAbstractButton::clicked, this, - &NewDialog::create_example); - connect( - ui->pushButton_start_empty, &QAbstractButton::clicked, this, - &NewDialog::create_empty); - connect( - ui->pushButton_load, &QAbstractButton::clicked, this, - &NewDialog::create); - connect( - ui->pushButton_cancel, &QAbstractButton::clicked, this, - &NewDialog::cancel); - connect( - ui->pushButton_browse, &QAbstractButton::clicked, this, - &NewDialog::browse_elf); - connect( - ui->elf_file, &QLineEdit::textChanged, this, &NewDialog::elf_change); - connect( - ui->preset_no_pipeline, &QAbstractButton::toggled, this, - &NewDialog::set_preset); - connect( - ui->preset_no_pipeline_cache, &QAbstractButton::toggled, this, - &NewDialog::set_preset); - connect( - ui->preset_pipelined_bare, &QAbstractButton::toggled, this, - &NewDialog::set_preset); - connect( - ui->preset_pipelined, &QAbstractButton::toggled, this, - &NewDialog::set_preset); - connect( - ui->reset_at_compile, &QAbstractButton::clicked, this, - &NewDialog::reset_at_compile_change); + ui->reset_at_compile, &QAbstractButton::clicked, this, &NewDialog::reset_at_compile_change); + connect(ui->xlen_64bit, &QAbstractButton::clicked, this, &NewDialog::xlen_64bit_change); + connect(ui->isa_atomic, &QAbstractButton::clicked, this, &NewDialog::isa_atomic_change); + connect(ui->isa_multiply, &QAbstractButton::clicked, this, &NewDialog::isa_multiply_change); + connect(ui->pipelined, &QAbstractButton::clicked, this, &NewDialog::pipelined_change); + connect(ui->delay_slot, &QAbstractButton::clicked, this, &NewDialog::delay_slot_change); + connect(ui->hazard_unit, &QGroupBox::clicked, this, &NewDialog::hazard_unit_change); + connect(ui->hazard_stall, &QAbstractButton::clicked, this, &NewDialog::hazard_unit_change); connect( - ui->xlen_64bit, &QAbstractButton::clicked, this, - &NewDialog::xlen_64bit_change); - connect( - ui->isa_atomic, &QAbstractButton::clicked, this, - &NewDialog::isa_atomic_change); - connect( - ui->isa_multiply, &QAbstractButton::clicked, this, - &NewDialog::isa_multiply_change); - connect( - ui->pipelined, &QAbstractButton::clicked, this, - &NewDialog::pipelined_change); - connect( - ui->delay_slot, &QAbstractButton::clicked, this, - &NewDialog::delay_slot_change); - connect( - ui->hazard_unit, &QGroupBox::clicked, this, - &NewDialog::hazard_unit_change); - connect( - ui->hazard_stall, &QAbstractButton::clicked, this, - &NewDialog::hazard_unit_change); - connect( - ui->hazard_stall_forward, &QAbstractButton::clicked, this, - &NewDialog::hazard_unit_change); + ui->hazard_stall_forward, &QAbstractButton::clicked, this, &NewDialog::hazard_unit_change); connect( - ui->mem_protec_exec, &QAbstractButton::clicked, this, - &NewDialog::mem_protec_exec_change); + ui->mem_protec_exec, &QAbstractButton::clicked, this, &NewDialog::mem_protec_exec_change); connect( - ui->mem_protec_write, &QAbstractButton::clicked, this, - &NewDialog::mem_protec_write_change); + ui->mem_protec_write, &QAbstractButton::clicked, this, &NewDialog::mem_protec_write_change); connect( ui->mem_time_read, QOverload::of(&QSpinBox::valueChanged), this, &NewDialog::mem_time_read_change); @@ -113,8 +74,7 @@ NewDialog::NewDialog(QWidget *parent, QSettings *settings) : QDialog(parent) { ui->mem_time_write, QOverload::of(&QSpinBox::valueChanged), this, &NewDialog::mem_time_write_change); connect( - ui->mem_enable_burst, &QAbstractButton::clicked, this, - &NewDialog::mem_enable_burst_change); + ui->mem_enable_burst, &QAbstractButton::clicked, this, &NewDialog::mem_enable_burst_change); connect( ui->mem_time_burst, QOverload::of(&QSpinBox::valueChanged), this, &NewDialog::mem_time_burst_change); @@ -122,9 +82,7 @@ NewDialog::NewDialog(QWidget *parent, QSettings *settings) : QDialog(parent) { ui->mem_time_level2, QOverload::of(&QSpinBox::valueChanged), this, &NewDialog::mem_time_level2_change); - connect( - ui->osemu_enable, &QAbstractButton::clicked, this, - &NewDialog::osemu_enable_change); + connect(ui->osemu_enable, &QAbstractButton::clicked, this, &NewDialog::osemu_enable_change); connect( ui->osemu_known_syscall_stop, &QAbstractButton::clicked, this, &NewDialog::osemu_known_syscall_stop_change); @@ -158,9 +116,24 @@ NewDialog::NewDialog(QWidget *parent, QSettings *settings) : QDialog(parent) { connect( ui->slider_bp_bht_bhr_bits, &QAbstractSlider::valueChanged, this, &NewDialog::bp_bht_bhr_bits_change); - connect(ui->slider_bp_bht_addr_bits, &QAbstractSlider::valueChanged, this, + connect( + 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->tlb_number_of_sets, QOverload::of(&QSpinBox::valueChanged), this, + &NewDialog::tlb_num_sets_changed); + connect( + ui->tlb_degree_of_associativity, QOverload::of(&QSpinBox::valueChanged), this, + &NewDialog::tlb_assoc_changed); + connect( + ui->tlb_replacement_policy, QOverload::of(&QComboBox::activated), this, + &NewDialog::tlb_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()); @@ -177,17 +150,18 @@ NewDialog::NewDialog(QWidget *parent, QSettings *settings) : QDialog(parent) { void NewDialog::switch2page(QTreeWidgetItem *current, QTreeWidgetItem *previous) { (void)previous; - QWidget *page = ui->config_pages->findChild(current->text(1), - Qt::FindDirectChildrenOnly); + QWidget *page + = ui->config_pages->findChild(current->text(1), Qt::FindDirectChildrenOnly); if (page != nullptr) { ui->config_pages->setCurrentWidget(page); ui->config_page_title->setText(current->text(0)); } } -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(); } } @@ -196,9 +170,7 @@ void NewDialog::closeEvent(QCloseEvent *) { load_settings(); // Reset from settings // Close the main window if not already configured auto *prnt = (MainWindow *)parent(); - if (!prnt->configured()) { - prnt->close(); - } + if (!prnt->configured()) { prnt->close(); } } void NewDialog::cancel() { @@ -281,92 +253,91 @@ 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) { - auto isa_mask = machine::ConfigIsaWord::byChar('A'); + auto isa_mask = machine::ConfigIsaWord::byChar('A'); if (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) { - auto isa_mask = machine::ConfigIsaWord::byChar('M'); + auto isa_mask = machine::ConfigIsaWord::byChar('M'); if (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() { if (ui->hazard_unit->isChecked()) { config->set_hazard_unit( - ui->hazard_stall->isChecked() - ? machine::MachineConfig::HU_STALL - : machine::MachineConfig::HU_STALL_FORWARD); + ui->hazard_stall->isChecked() ? machine::MachineConfig::HU_STALL + : machine::MachineConfig::HU_STALL_FORWARD); } 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(); } } @@ -490,20 +461,18 @@ void NewDialog::bp_type_change() { } } break; - default: - break; + default: break; } 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(); } } @@ -511,14 +480,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())); @@ -535,7 +504,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(); } @@ -543,11 +512,38 @@ 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::tlb_num_sets_changed(int v) { + config->access_tlb_program()->set_tlb_num_sets(static_cast(v)); + config->access_tlb_data()->set_tlb_num_sets(static_cast(v)); + switch_to_custom(); +} + +void NewDialog::tlb_assoc_changed(int v) { + config->access_tlb_program()->set_tlb_associativity(static_cast(v)); + config->access_tlb_data()->set_tlb_associativity(static_cast(v)); + switch_to_custom(); +} + +void NewDialog::tlb_policy_changed(int idx) { + config->access_tlb_program()->set_tlb_replacement_policy( + static_cast(idx)); + config->access_tlb_data()->set_tlb_replacement_policy( + static_cast(idx)); + switch_to_custom(); +} + void NewDialog::config_gui() { // Basic ui->elf_file->setText(config->elf()); @@ -607,6 +603,9 @@ 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()); + // Memory ui->mem_protec_exec->setChecked(config->memory_execute_protection()); ui->mem_protec_write->setChecked(config->memory_write_protection()); @@ -660,15 +659,9 @@ void NewDialog::load_settings() { auto p = (enum machine::ConfigPresets)(preset - 1); config->preset(p); switch (p) { - case machine::CP_SINGLE: - ui->preset_no_pipeline->setChecked(true); - break; - case machine::CP_SINGLE_CACHE: - ui->preset_no_pipeline_cache->setChecked(true); - break; - case machine::CP_PIPE_NO_HAZARD: - ui->preset_pipelined_bare->setChecked(true); - break; + case machine::CP_SINGLE: ui->preset_no_pipeline->setChecked(true); break; + case machine::CP_SINGLE_CACHE: ui->preset_no_pipeline_cache->setChecked(true); break; + case machine::CP_PIPE_NO_HAZARD: ui->preset_pipelined_bare->setChecked(true); break; case machine::CP_PIPE: ui->preset_pipelined->setChecked(true); break; } } else { @@ -689,14 +682,11 @@ void NewDialog::store_settings() { } } -NewDialogCacheHandler::NewDialogCacheHandler(NewDialog *nd, - Ui::NewDialogCache *cui) : Super(nd) { +NewDialogCacheHandler::NewDialogCacheHandler(NewDialog *nd, Ui::NewDialogCache *cui) : Super(nd) { this->nd = nd; this->ui = cui; this->config = nullptr; - connect( - ui->enabled, &QGroupBox::clicked, this, - &NewDialogCacheHandler::enabled); + connect(ui->enabled, &QGroupBox::clicked, this, &NewDialogCacheHandler::enabled); connect( ui->number_of_sets, &QAbstractSpinBox::editingFinished, this, &NewDialogCacheHandler::numsets); @@ -729,31 +719,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(); + config->set_replacement_policy((enum machine::CacheConfig::ReplacementPolicy)val); + 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..1abebdd4 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,13 @@ private slots: void bp_bht_bhr_bits_change(int); void bp_bht_addr_bits_change(int); + + // Virtual Memory + void vm_enabled_change(bool); + void tlb_num_sets_changed(int); + void tlb_assoc_changed(int); + void tlb_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..3e5b2030 --- /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..1c87c12d --- /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 39802837d6503679671a517cc2b0373943a15e4a 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/11] 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 14153a3097addae342245c52cf464d6ff635e1b0 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/11] 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 | 22 +++++------ src/gui/windows/memory/memorydock.h | 9 ++--- src/gui/windows/memory/memorymodel.cpp | 52 ++++++++++++++++++++++---- src/gui/windows/memory/memorymodel.h | 3 +- src/machine/machine.h | 8 ++++ 5 files changed, 67 insertions(+), 27 deletions(-) diff --git a/src/gui/windows/memory/memorydock.cpp b/src/gui/windows/memory/memorydock.cpp index 07fd4d69..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,14 +47,14 @@ 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( - this, &MemoryDock::machine_setup, memory_model, &MemoryModel::setup); - connect( - cell_size, QOverload::of(&QComboBox::currentIndexChanged), - memory_content, &MemoryTableView::set_cell_size); + cell_size, QOverload::of(&QComboBox::currentIndexChanged), memory_content, + &MemoryTableView::set_cell_size); connect( - cached_access, QOverload::of(&QComboBox::currentIndexChanged), - memory_model, &MemoryModel::cached_access); + cached_access, QOverload::of(&QComboBox::currentIndexChanged), memory_model, + &MemoryModel::cached_access); connect( go_edit, &HexLineEdit::value_edit_finished, memory_content, [memory_content](uint32_t value) { @@ -62,12 +62,8 @@ MemoryDock::MemoryDock(QWidget *parent, QSettings *settings) : Super(parent) { }); connect( memory_content, &MemoryTableView::address_changed, go_edit, - [go_edit](machine::Address addr) { - go_edit->set_value(addr.get_raw()); - }); - connect( - this, &MemoryDock::focus_addr, memory_content, - &MemoryTableView::focus_address); + [go_edit](machine::Address addr) { go_edit->set_value(addr.get_raw()); }); + connect(this, &MemoryDock::focus_addr, memory_content, &MemoryTableView::focus_address); connect( memory_model, &MemoryModel::setup_done, memory_content, &MemoryTableView::recompute_columns); 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..6381603f 100644 --- a/src/gui/windows/memory/memorymodel.cpp +++ b/src/gui/windows/memory/memorymodel.cpp @@ -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 ((access_through_cache > 0) && (machine->cache_data() != nullptr)) { + mem = machine->cache_data(); + } + } else { + if (access_through_cache == 2) { + 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; update_all(); } - Qt::ItemFlags MemoryModel::flags(const QModelIndex &index) const { if (index.column() == 0) { return QAbstractTableModel::flags(index); @@ -215,7 +222,18 @@ 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 (access_through_cache == 2) { + mem = machine->get_tlb_data_rw(); + } else { + mem = mem_access_phys_rw(); + } + } else { + mem = mem_access_rw(); + if (access_through_cache > 0 && machine->cache_data_rw()) { + mem = machine->cache_data_rw(); + } + } if (mem == nullptr) { return false; } if ((access_through_cache > 0) && (machine->cache_data_rw() != nullptr)) { mem = machine->cache_data_rw(); @@ -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 (access_through_cache > 0 && 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 (access_through_cache > 0 && 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 331aaeb7..ae4cb871 100644 --- a/src/gui/windows/memory/memorymodel.h +++ b/src/gui/windows/memory/memorymodel.h @@ -69,7 +69,6 @@ class MemoryModel : public QAbstractTableModel { } return true; } - public slots: void setup(machine::Machine *machine); void set_cell_size(int index); @@ -83,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; 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 bc32933c8ad49298c688a2a86b10a99880bf68db 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/11] 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. --- tests/cli/stalls/stdout.txt | 2 +- tests/vm_assmebly/vm_template.s | 116 +++++++++++++++++ tests/vm_assmebly/vm_test_dtlb.s | 115 +++++++++++++++++ tests/vm_assmebly/vm_test_exec.s | 93 ++++++++++++++ tests/vm_assmebly/vm_test_itlb.s | 198 ++++++++++++++++++++++++++++++ tests/vm_assmebly/vm_test_memrw.s | 114 +++++++++++++++++ 6 files changed, 637 insertions(+), 1 deletion(-) create mode 100644 tests/vm_assmebly/vm_template.s create mode 100644 tests/vm_assmebly/vm_test_dtlb.s create mode 100644 tests/vm_assmebly/vm_test_exec.s create mode 100644 tests/vm_assmebly/vm_test_itlb.s create mode 100644 tests/vm_assmebly/vm_test_memrw.s 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 diff --git a/tests/vm_assmebly/vm_template.s b/tests/vm_assmebly/vm_template.s new file mode 100644 index 00000000..698e64a0 --- /dev/null +++ b/tests/vm_assmebly/vm_template.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/vm_assmebly/vm_test_dtlb.s b/tests/vm_assmebly/vm_test_dtlb.s new file mode 100644 index 00000000..283edb43 --- /dev/null +++ b/tests/vm_assmebly/vm_test_dtlb.s @@ -0,0 +1,115 @@ +// 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 + + // Compute level-1 table index for MAP_VA and form address of PTE in root page table. + // srli/slli extracts bits for index and scales by 4 (word-sized PTEs). + srli t3, t2, 22 + slli t3, t3, 2 + add t4, t0, t3 + + // Build leaf PTE: physical page number (vpn->ppn shift) | flags. + // srli/slli moves MAP_VA to form the physical page number field in the PTE. + srli t5, t2, 12 + slli t5, t5, 10 + li t6, PTE_FLAGS_FULL + or t5, t5, t6 + sw t5, 0(t4) + fence + + // Enable paging: write SATP with implementation-specific value. + li t0, 0 + csrw satp, t0 + li t0, SATP_ENABLE + csrw satp, t0 + fence + + // Prepare mstatus.MPP so mret will return to Supervisor (set bits appropriately). + li t0, MSTATUS_MPP_CLEAR + csrrc zero, mstatus, t0 + li t0, MSTATUS_MPP_SET + csrrs zero, mstatus, t0 + + // Set mepc to vm_entry and enter Supervisor with mret. + la t0, vm_entry + 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/vm_assmebly/vm_test_exec.s b/tests/vm_assmebly/vm_test_exec.s new file mode 100644 index 00000000..8d6cbf73 --- /dev/null +++ b/tests/vm_assmebly/vm_test_exec.s @@ -0,0 +1,93 @@ +// 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 leaf PTE + srli t1, t4, 12 + slli t1, t1, 10 + li t6, PTE_FLAGS_FULL + or t1, t1, t6 + + srli t5, t4, 22 + slli t2, t5, 2 + add t3, t0, t2 + sw t1, 0(t3) + fence + + // Enable SATP + li t0, 0 + csrw satp, t0 + li t0, SATP_ENABLE + csrw satp, t0 + fence + + // Prepare mstatus.MPP to return to Supervisor + li t0, MSTATUS_MPP_CLEAR + csrrc zero, mstatus, t0 + li t0, MSTATUS_MPP_SET + csrrs zero, mstatus, t0 + + la t0, vm_entry + 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/vm_assmebly/vm_test_itlb.s b/tests/vm_assmebly/vm_test_itlb.s new file mode 100644 index 00000000..c345b268 --- /dev/null +++ b/tests/vm_assmebly/vm_test_itlb.s @@ -0,0 +1,198 @@ +// 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 + + // Compute root page table entry location for MAP_VA and write a leaf PTE. + srli t3, t2, 22 + slli t3, t3, 2 + add t4, t0, t3 + + // Build leaf PTE: PPN from MAP_VA + full flags and store into root PT. + srli t5, t2, 12 + slli t5, t5, 10 + li t6, PTE_FLAGS_FULL + or t5, t5, t6 + sw t5, 0(t4) + fence + + // Enable SATP: construct satp value from ROOT_PT_PHYS (ppn) and MODE bit(s), + // then write to satp and fence. This form shifts root physical >> 12 and ORs + // with the mode bit mask (implementation-specific). + li t0, ROOT_PT_PHYS + srli t0, t0, 12 + li t1, 0x80000000 + or t0, t0, t1 + csrw satp, t0 + fence + + // Prepare mstatus.MPP so mret will return to Supervisor. + li t0, MSTATUS_MPP_CLEAR + csrrc zero, mstatus, t0 + li t0, MSTATUS_MPP_SET + csrrs zero, mstatus, t0 + + // Set mepc to vm_entry and enter Supervisor with mret. + la t0, vm_entry + 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/vm_assmebly/vm_test_memrw.s b/tests/vm_assmebly/vm_test_memrw.s new file mode 100644 index 00000000..537791c3 --- /dev/null +++ b/tests/vm_assmebly/vm_test_memrw.s @@ -0,0 +1,114 @@ +// 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 leaf PTE + srli t1, t4, 12 + slli t1, t1, 10 + li t6, PTE_FLAGS_FULL + or t1, t1, t6 + + srli t5, t4, 22 + slli t2, t5, 2 + add t3, t0, t2 + sw t1, 0(t3) + fence + + // Enable SATP + li t0, 0 + csrw satp, t0 + li t0, SATP_ENABLE + csrw satp, t0 + fence + + // Prepare mstatus.MPP to return to Supervisor + li t0, MSTATUS_MPP_CLEAR + csrrc zero, mstatus, t0 + li t0, MSTATUS_MPP_SET + csrrs zero, mstatus, t0 + + la t0, vm_entry + 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 From 7a204cf47302f374d7e0a7b352948141825d4a0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20N=C4=9Bmec?= Date: Fri, 24 Oct 2025 17:08:59 +0200 Subject: [PATCH 11/11] Tests: add cmake logic to run virtual memory tests as CLI tests Restructure the virtual memory test directory to align with the rest of the tests, and add CMake logic for running them. --- src/cli/CMakeLists.txt | 40 +++++++++++++++++++ .../virtual_memory/dtlb/program.S} | 0 tests/cli/virtual_memory/dtlb/stdout.txt | 5 +++ .../virtual_memory/exec/program.S} | 0 tests/cli/virtual_memory/exec/stdout.txt | 5 +++ .../virtual_memory/itlb/program.S} | 0 tests/cli/virtual_memory/itlb/stdout.txt | 5 +++ .../virtual_memory/memrw/program.S} | 0 tests/cli/virtual_memory/memrw/stdout.txt | 5 +++ .../virtual_memory/template/program.S} | 0 tests/cli/virtual_memory/template/stdout.txt | 5 +++ 11 files changed, 65 insertions(+) rename tests/{vm_assmebly/vm_test_dtlb.s => cli/virtual_memory/dtlb/program.S} (100%) create mode 100644 tests/cli/virtual_memory/dtlb/stdout.txt rename tests/{vm_assmebly/vm_test_exec.s => cli/virtual_memory/exec/program.S} (100%) create mode 100644 tests/cli/virtual_memory/exec/stdout.txt rename tests/{vm_assmebly/vm_test_itlb.s => cli/virtual_memory/itlb/program.S} (100%) create mode 100644 tests/cli/virtual_memory/itlb/stdout.txt rename tests/{vm_assmebly/vm_test_memrw.s => cli/virtual_memory/memrw/program.S} (100%) create mode 100644 tests/cli/virtual_memory/memrw/stdout.txt rename tests/{vm_assmebly/vm_template.s => cli/virtual_memory/template/program.S} (100%) 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/vm_assmebly/vm_test_dtlb.s b/tests/cli/virtual_memory/dtlb/program.S similarity index 100% rename from tests/vm_assmebly/vm_test_dtlb.s rename to tests/cli/virtual_memory/dtlb/program.S 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/vm_assmebly/vm_test_exec.s b/tests/cli/virtual_memory/exec/program.S similarity index 100% rename from tests/vm_assmebly/vm_test_exec.s rename to tests/cli/virtual_memory/exec/program.S 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/vm_assmebly/vm_test_itlb.s b/tests/cli/virtual_memory/itlb/program.S similarity index 100% rename from tests/vm_assmebly/vm_test_itlb.s rename to tests/cli/virtual_memory/itlb/program.S diff --git a/tests/cli/virtual_memory/itlb/stdout.txt b/tests/cli/virtual_memory/itlb/stdout.txt new file mode 100644 index 00000000..b64f502f --- /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: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:0x00000000 R22:0x00000000 R23:0x00000000 R24:0x00000000 R25:0x00000000 R26:0x00000000 R27:0x00000000 R28:0xffffffffc40070c8 R29:0x00000049 R30:0x000000cf R31:0x00000001 +cycle: 0x00000077 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: 0x00000077 minstret: 0x00000076 sstatus: 0x00000000 stvec: 0x00000000 sscratch: 0x00000000 sepc: 0x00000000 scause: 0x00000000 stval: 0x00000000 satp: 0x80000001 diff --git a/tests/vm_assmebly/vm_test_memrw.s b/tests/cli/virtual_memory/memrw/program.S similarity index 100% rename from tests/vm_assmebly/vm_test_memrw.s rename to tests/cli/virtual_memory/memrw/program.S 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/vm_assmebly/vm_template.s b/tests/cli/virtual_memory/template/program.S similarity index 100% rename from tests/vm_assmebly/vm_template.s rename to tests/cli/virtual_memory/template/program.S 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