Sync push
This commit is contained in:
parent
ad190f9f4f
commit
678bbf6381
2 changed files with 279 additions and 62 deletions
|
|
@ -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<u32>(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<Core>(*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<std::thread> 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<u32>(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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,58 +23,138 @@
|
|||
#include "../common/Data.hpp"
|
||||
#include "MMU.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
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<u32, 32> 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<bool> m_halted { true }; // start halted; only core 0 wakes at reset
|
||||
std::atomic<bool> 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<u32, 32> 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<u32>(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<std::unique_ptr<Core>> m_cores;
|
||||
std::atomic<bool> m_stopping { false };
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue