diff --git a/src/hardware/CPU.cpp b/src/hardware/CPU.cpp index 2bfdbec..2962b2c 100644 --- a/src/hardware/CPU.cpp +++ b/src/hardware/CPU.cpp @@ -24,41 +24,178 @@ namespace dragon { namespace hw { - CPU::CPU(BUS& bus) : m_bus(bus) + // ===================================================================== + // Core + // ===================================================================== + + Core::Core(CPU& cpu, BUS& bus, u32 core_id) + : m_cpu(cpu) + , m_bus(bus) + , m_coreId(core_id) + , m_mmu(bus, &m_accessMode) { + reset(); + } + + void Core::reset(void) + { + // Architectural reset state. At power-on every core sits here; + // the BIOS / kernel then differentiates them by CORE_ID and wakes + // secondaries via IPI. + m_pc = 0; + m_sp = 0; + m_fl = 0; + m_iv = 0; + m_epc = 0; + m_cause = 0; + m_badaddr = 0; + m_estatus = 0; + m_scratch = 0; + m_faultingPc = 0; + m_gpr.fill(0); + + m_accessMode = MMUAccessMode::Privileged; + m_interruptsEnabled = false; + + m_mmu.disable(); + m_mmu.setPtb(0); + m_mmu.setCurrentAsid(0); + + m_irqPending.store(false, std::memory_order_release); + + // Core 0 wakes at reset; all others stay halted until IPI'd. + m_halted.store(m_coreId != 0, std::memory_order_release); + } + + void Core::run(void) + { + // Unhalt this core if it was halted (e.g. after IPI wakes us from HALT). + m_halted.store(false, std::memory_order_release); + + while (!m_halted.load(std::memory_order_acquire)) + { + // Check for pending interrupts before each instruction. + // Only take the interrupt if IE is set; otherwise leave it + // pending so the kernel can decide when to handle it. + if (m_irqPending.load(std::memory_order_acquire) && m_interruptsEnabled) + { + m_irqPending.store(false, std::memory_order_release); + take_exception(ExceptionCause::HardwareInterrupt, 0); + continue; + } + + // Save the PC of the instruction about to execute, so that if + // it faults we know what to put in EPC. + m_faultingPc = m_pc; + + try + { + step(); + } + catch (const GuestException& e) + { + take_exception(e.cause, e.fault_addr); + } + } + } + + void Core::halt(void) + { + m_halted.store(true, std::memory_order_release); + } + + void Core::signalInterrupt(void) + { + m_irqPending.store(true, std::memory_order_release); + // Future: also wake from HALT via condition variable. + } + + void Core::step(void) + { + // TODO: fetch instruction at m_pc, decode, execute. + // For now, a placeholder that halts immediately to avoid infinite loops. + m_halted.store(true, std::memory_order_release); + } + + void Core::take_exception(ExceptionCause cause, VirtualAddress fault_addr) + { + // Pack current mode + IE into ESTATUS for later restoration by iret. + // Layout: bit 0 = previous IE, bit 1 = previous mode (0=priv, 1=user). + u32 prev_mode_bit = (m_accessMode == MMUAccessMode::User) ? 1u : 0u; + u32 prev_ie_bit = m_interruptsEnabled ? 1u : 0u; + m_estatus = (prev_mode_bit << 1) | prev_ie_bit; + + // Save fault information. + m_epc = m_faultingPc; + m_cause = static_cast(cause); + m_badaddr = fault_addr; + + // Switch to supervisor mode, disable interrupts. + m_accessMode = MMUAccessMode::Privileged; + m_interruptsEnabled = false; + + // Jump to the single exception entry point. + m_pc = m_iv; + } + + u32 Core::readSpecialReg(u8 num) + { + // TODO: privilege check based on register's access policy. + // TODO: dispatch by register number. + // For MMU-owned registers (PTB, ASID, MMU enable), forward to m_mmu. + (void)num; + return 0; + } + + void Core::writeSpecialReg(u8 num, u32 value) + { + // TODO: privilege check based on register's access policy. + // TODO: dispatch by register number, applying side effects. + (void)num; + (void)value; + } + + // ===================================================================== + // CPU + // ===================================================================== + + CPU::CPU(BUS& bus, u32 num_cores) + : m_bus(bus) + { + m_cores.reserve(num_cores); + for (u32 i = 0; i < num_cores; ++i) + m_cores.emplace_back(std::make_unique(*this, bus, i)); } bool CPU::run(void) { - // Should probably be moved to Core? - // while (!m_halted) - // { - // try - // { - // step(); // fetch + decode + execute one instruction - // } - // catch (const GuestException& e) - // { - // take_exception(e.cause, e.fault_addr); - // } - // } + m_stopping.store(false, std::memory_order_release); + + // Phase A: single-threaded. Run core 0 only. + // (Secondary cores start halted; nothing to do until SMP wakeup.) + if (!m_cores.empty()) + m_cores[0]->run(); + + // Phase B (future): spawn one host thread per core. + // std::vector threads; + // for (auto& c : m_cores) + // threads.emplace_back([&c]() { c->run(); }); + // for (auto& t : threads) t.join(); + return true; } - void CPU::take_exception(ExceptionCause cause, VirtualAddress fault_addr) + void CPU::stop(void) { - // m_epc = m_faulting_pc; // saved before/during the faulting instr - // m_cause = static_cast(cause); - // m_badaddr = fault_addr; - // m_estatus = pack_mode_and_ie(); // save mode/IE - // set_mode_supervisor(); - // set_interrupts_enabled(false); - // m_pc = m_iv; // jump to single entry point + m_stopping.store(true, std::memory_order_release); + for (auto& c : m_cores) + c->halt(); } - void CPU::step(void) + void CPU::sendIpi(u32 target_core) { - + if (target_core < m_cores.size()) + m_cores[target_core]->signalInterrupt(); } } } diff --git a/src/hardware/CPU.hpp b/src/hardware/CPU.hpp index 239b2c3..165e3d6 100644 --- a/src/hardware/CPU.hpp +++ b/src/hardware/CPU.hpp @@ -23,58 +23,138 @@ #include "../common/Data.hpp" #include "MMU.hpp" +#include +#include +#include +#include + namespace dragon { namespace hw { class BUS; - class CPU + class CPU; // forward declaration so Core can reference its parent + + // One emulated CPU core. Each core has its own register file, its own + // MMU, its own privilege/interrupt state, and (eventually) its own host + // thread. Cores are independent except for the shared BUS / RAM. + class Core { - public: struct Core - { - MMU m_mmu; - bool m_halted { true }; - - std::array m_gpr; - u32 m_pc; - u32 m_sp; - u32 m_fl; - u32 m_mode; - u32 m_iv; - u32 m_epc; - u32 m_cause; - u32 m_badaddr; - u32 m_estatus; - u32 m_scratch; - u32 m_ptb; - u32 m_asid; - u32 m_coreid; - u32 m_cycle; - u32 m_timer_deadline; - - inline Core(CPU& cpu) : m_mmu(cpu.m_bus, &(cpu.m_accessMode)) - { - - } - - private: - }; - public: - CPU(BUS& bus); - bool run(void); + Core(CPU& cpu, BUS& bus, u32 core_id); + + // Main execution loop. Runs until m_halted becomes true. + // In single-thread mode: called directly by CPU::run(). + // In multi-thread mode: called by a host thread dedicated to this core. + void run(void); + + // Stop the run loop. Called by another thread (CPU::stop, debugger, etc.). + // Returns when the core's run loop has noticed and is about to exit. + void halt(void); + + // Wake this core from a HALT instruction. Set by another core sending + // an IPI. The flag is checked between instructions. + void signalInterrupt(void); + + // Reset to the architectural reset state: PC=0, supervisor mode, + // interrupts disabled, MMU disabled, registers zeroed. + void reset(void); + + // Inspection (used by debugger, tests, host orchestration). + inline u32 coreId(void) const { return m_coreId; } + inline bool isHalted(void) const { return m_halted.load(std::memory_order_acquire); } + inline MMU& mmu(void) { return m_mmu; } private: - void take_exception(ExceptionCause cause, VirtualAddress fault_addr); + // One iteration of the fetch-decode-execute cycle. void step(void); + // Take an exception: save state, switch to supervisor, jump to IV. + void take_exception(ExceptionCause cause, VirtualAddress fault_addr); + + // GP register access — handles R0-as-zero invariant in one place. + inline u32 gpr(u8 idx) const { return idx == 0 ? 0 : m_gpr[idx]; } + inline void setGpr(u8 idx, u32 value) { if (idx != 0) m_gpr[idx] = value; } + + // Special register access (used by mfsr/mtsr after privilege check). + u32 readSpecialReg(u8 num); + void writeSpecialReg(u8 num, u32 value); + + private: + // Parent references. Lifetime-managed by CPU. + CPU& m_cpu; + BUS& m_bus; + const u32 m_coreId; + + // MMU lives in the Core — per-core translation state. + MMU m_mmu; + + // === State observed by other threads === + // These need to be atomic because peer cores or the host can + // read/write them while this core is running. + std::atomic m_halted { true }; // start halted; only core 0 wakes at reset + std::atomic m_irqPending { false }; // set externally on IPI delivery + + // === State only this core's thread touches === + // No atomicity needed; access is single-threaded by construction. + + // Mode / interrupt enable. Owned by Core, referenced by MMU. + MMUAccessMode m_accessMode { MMUAccessMode::Privileged }; + bool m_interruptsEnabled { false }; + + // GP registers (R0..R31). R0 invariant enforced by accessors above. + std::array m_gpr {}; + + // Core control registers + u32 m_pc { 0 }; + u32 m_sp { 0 }; + u32 m_fl { 0 }; + + // Implementation detail: PC of the instruction currently executing. + // Saved into EPC if that instruction faults. + u32 m_faultingPc { 0 }; + + // Exception state registers + u32 m_iv { 0 }; // exception handler entry point + u32 m_epc { 0 }; // saved PC at fault + u32 m_cause { 0 }; // ExceptionCause value + u32 m_badaddr { 0 }; // faulting address (or other cause-specific data) + u32 m_estatus { 0 }; // saved mode/IE bits + u32 m_scratch { 0 }; // per-core OS scratch + + // Note: PTB, ASID, MMU enable live in m_mmu — Core dispatches mfsr/mtsr + // to MMU accessors for those register numbers. + }; + + + class CPU + { + public: + // Construct with a fixed core count. Cores are created in the + // constructor and live until CPU is destroyed. + CPU(BUS& bus, u32 num_cores); + + // Run until all cores halt permanently. Phase A: runs core 0 + // directly on the calling thread. Later: spawns one host thread + // per core and joins them. + bool run(void); + + // Stop all cores. Each core checks its halt flag between instructions + // and exits cleanly. Call from another thread or from the host. + void stop(void); + + // Access a specific core (for debugger, BIOS init, IPI delivery). + inline Core& core(u32 idx) { return *m_cores.at(idx); } + inline u32 numCores(void) const { return static_cast(m_cores.size()); } + + // Deliver an IPI to the target core. Called by an MMIO write to the + // IPI controller region, or by host orchestration. + void sendIpi(u32 target_core); + private: - MMUAccessMode m_accessMode { MMUAccessMode::Privileged }; BUS& m_bus; - Core m_core0 { *this }; - Core m_core1 { *this }; - Core m_core2 { *this }; - Core m_core3 { *this }; + std::vector> m_cores; + std::atomic m_stopping { false }; }; } }