diff --git a/native/projects/riscv/CMakeLists.txt b/native/projects/riscv/CMakeLists.txt index 0904cf9..1168dfa 100644 --- a/native/projects/riscv/CMakeLists.txt +++ b/native/projects/riscv/CMakeLists.txt @@ -10,6 +10,8 @@ add_library(riscv src/CPU.cpp src/System.cpp + # Basic system devices + src/Devices/ClntDevice.cpp src/Devices/SysconDevice.cpp src/Devices/RamDevice.cpp ) diff --git a/native/projects/riscv/include/riscv/Bus.hpp b/native/projects/riscv/include/riscv/Bus.hpp index 7b69b42..5851630 100644 --- a/native/projects/riscv/include/riscv/Bus.hpp +++ b/native/projects/riscv/include/riscv/Bus.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -27,6 +28,10 @@ namespace riscv { virtual ~Device() = default; + virtual void Attached(Bus* bus) { + this->bus = bus; + } + virtual BasicType Type() const { return BasicType::Device; } /// Does this device clock? @@ -58,6 +63,9 @@ namespace riscv { LUCORE_ASSERT(IsA(), "Upcast failure: this is not a T"); return static_cast(this); } + protected: + /// The bus this device is attached to. + Bus* bus; }; /// Interface plain memory bus devices use. @@ -67,8 +75,9 @@ namespace riscv { virtual BasicType Type() const override { return BasicType::PlainMemory; } virtual AddressT Base() const = 0; - /// How many bytes does this device occupy of address space? This should - /// effectively be a constant, and should not change during CPU execution. + + /// How many bytes does this device occupy of address space? + /// This should not change during execution. virtual AddressT Size() const = 0; /// Peek() -> reads a value from this device. @@ -91,8 +100,8 @@ namespace riscv { virtual AddressT Base() const; - /// How many bytes does this device occupy of address space? This should - /// effectively be a constant, and should not change during CPU execution. + /// How many bytes does this device occupy of address space? + /// This should not change during execution. virtual AddressT Size() const = 0; virtual u32 Peek(AddressT address) = 0; @@ -127,7 +136,12 @@ namespace riscv { void PokeShort(AddressT address, u16 value); void PokeWord(AddressT address, u32 value); + CPU* GetCPU() { return cpu; } + private: + + void CpuTrap(u32 trapCode); + // TODO: version which takes Device::BasicType Bus::Device* FindDeviceForAddress(AddressT address) const; diff --git a/native/projects/riscv/include/riscv/CPU.hpp b/native/projects/riscv/include/riscv/CPU.hpp index d2aa172..8d5b0b1 100644 --- a/native/projects/riscv/include/riscv/CPU.hpp +++ b/native/projects/riscv/include/riscv/CPU.hpp @@ -1,48 +1,49 @@ #include +#include #include namespace riscv { /// The CPU core. struct CPU : Bus::Device { - /// CPU core state. - struct State { - u32 gpr[32]; - u32 pc; - u32 mstatus; - u32 cyclel; - u32 cycleh; - - u32 mscratch; - u32 mtvec; - u32 mie; - u32 mip; - - u32 mepc; - u32 mtval; - u32 mcause; - - // Note: only a few bits are used. (Machine = 3, User = 0) - // Bits 0..1 = privilege. - // Bit 2 = WFI (Wait for interrupt) - // Bit 3+ = Load/Store reservation LSBs. - u32 extraflags; - }; - - CPU(Bus* bus); - - State& GetState() { return state; } - - bool Clocked() const override { return true; } + bool Clocked() const override { return true; } void Clock() override; - // TODO: Handlers for CSR read/write + void Trap(u32 trapCode); + inline void Trap(TrapCode trapCode) { Trap(static_cast(trapCode)); } + + void TimerInterrupt(); + + // TODO: Handlers for CSR read/write (if we need it?) + + GeneralPurposeRegisters gpr; + u32 pc; + u32 mstatus; + u32 cyclel; + u32 cycleh; + u32 mscratch; + u32 mtvec; + u32 mie; + u32 mip; + + u32 mepc; + u32 mtval; + u32 mcause; + + // Note: only a few bits are used. (Machine = 3, User = 0) + // Bits 0..1 = privilege. + // Bit 2 = WFI (Wait for interrupt) + // Bit 3+ = Load/Store reservation LSBs. + u32 extraflags; private: - State state; - Bus* bus; + /// Set by [CPU::Trap] to tell the CPU it was trapped. + /// Codes with the sign bit set are actually interrupts, + /// and are processed first. + bool trapped { false }; + u32 trapCode { 0 }; - u32 Step(u32 elapsedMicroseconds, u32 instCount); + u32 Step(u32 instCount); // todo: counters for chrono/inst count. }; diff --git a/native/projects/riscv/include/riscv/CPUTypes.hpp b/native/projects/riscv/include/riscv/CPUTypes.hpp new file mode 100644 index 0000000..9d27db5 --- /dev/null +++ b/native/projects/riscv/include/riscv/CPUTypes.hpp @@ -0,0 +1,71 @@ +#pragma once +#include + +namespace riscv { + + // Note that + enum class TrapCode : u32 { + InstructionAddressMisaligned, + InstructionAddressFault, + IllegalInstruction, + Breakpoint, // I see what you did there ;) + LoadAddressMisaligned, + LoadAccessFault, + StoreAddressMisaligned, + StoreAccessFault, + + EnvCallUMode, + EnvCallSMode, + EnvCallMMode, + + // Not used but documented + InstructionPageFault, + LoadPageFault, + StorePageFault + }; + + enum class Gpr : u8 { + X0, // zero + X1, + X2, + X3, + X4, + X5, + X6, + X7, + X8, + X9, + X10, + X11, + X12, + X13, + X14, + X15, + X16, + X17, + X18, + X19, + X20, + X21, + X22, + X23, + X24, + X25, + X26, + X27, + X28, + X29, + X30, + X31, + PC + }; + + /// Container for GPRs which can be Statically Typed or not depending on use case. + /// Pretty cool, huh? + struct GeneralPurposeRegisters { + constexpr u32& operator[](Gpr gpr) { return operator[](static_cast(gpr)); } + constexpr u32& operator[](usize index) { return gprs_[index]; } + u32 gprs_[32]; + }; + +} // namespace riscv diff --git a/native/projects/riscv/src/Bus.cpp b/native/projects/riscv/src/Bus.cpp index dc9c488..1bab13c 100644 --- a/native/projects/riscv/src/Bus.cpp +++ b/native/projects/riscv/src/Bus.cpp @@ -6,7 +6,7 @@ namespace riscv { Bus::~Bus() { // Free all devices - for(auto device: devices) + for(auto device : devices) delete device; } @@ -15,6 +15,8 @@ namespace riscv { return false; if(device->IsA()) { + // Return early to avoid putting the CPU pointer inside the devices vector. + // We do not actually own the CPU. cpu = device->Upcast(); return true; } @@ -54,47 +56,65 @@ namespace riscv { device->Clock(); } + // The CPU is always clocked last to ensure devices can generate + // interrupts properly. I don't make the rules. cpu->Clock(); } u8 Bus::PeekByte(AddressT address) { if(auto dev = FindDeviceForAddress(address); dev) return dev->Upcast()->PeekByte(address); - return -1; + else { + cpu->Trap(TrapCode::LoadAccessFault); + return -1; + } } u16 Bus::PeekShort(AddressT address) { if(auto dev = FindDeviceForAddress(address); dev) return dev->Upcast()->PeekShort(address); - return -1; + else { + cpu->Trap(TrapCode::LoadAccessFault); + return -1; + } } u32 Bus::PeekWord(AddressT address) { if(auto dev = FindDeviceForAddress(address); dev) { - if(dev->IsA()) + if(dev->IsA()) return dev->Upcast()->Peek(address); else return dev->Upcast()->PeekWord(address); + } else { + cpu->Trap(TrapCode::LoadAccessFault); + return -1; } - return -1; } void Bus::PokeByte(AddressT address, u8 value) { if(auto dev = FindDeviceForAddress(address); dev) return dev->Upcast()->PokeByte(address, value); + else { + cpu->Trap(TrapCode::StoreAccessFault); + } } void Bus::PokeShort(AddressT address, u16 value) { if(auto dev = FindDeviceForAddress(address); dev) return dev->Upcast()->PokeShort(address, value); + else { + cpu->Trap(TrapCode::StoreAccessFault); + } } void Bus::PokeWord(AddressT address, u32 value) { if(auto dev = FindDeviceForAddress(address); dev) { - if(dev->IsA()) + if(dev->IsA()) dev->Upcast()->Poke(address, value); else dev->Upcast()->PokeWord(address, value); + } else { + cpu->Trap(TrapCode::StoreAccessFault); } } diff --git a/native/projects/riscv/src/CPU.cpp b/native/projects/riscv/src/CPU.cpp index 52716de..243ace4 100644 --- a/native/projects/riscv/src/CPU.cpp +++ b/native/projects/riscv/src/CPU.cpp @@ -1,9 +1,81 @@ #include +#include namespace riscv { void CPU::Clock() { // do the thing + Step(1024); + } + + void CPU::Trap(u32 trapCode) { + // The CPU core will get to this later. + trapped = true; + trapCode = trapCode; + + // If this is because of an interrupt + if(trapCode & 0x80000000) { + // Set MIP.MEIP. + //mip |= 1 << 11; + // Always clear WFI bit. + extraflags &= ~4; + } + } + + void CPU::TimerInterrupt() { + // Set MIP.MTIP. + mip |= 1 << 7; + //extraflags &= ~4; + //trapped = true; + //trapCode = 0x80000007; + } + + u32 CPU::Step(u32 instCount) { + auto interruptsInFlight = [&]() { + return + (mip & (1 << 7) /*|| mip & (1 << 11)*/) && + (mie & (1 << 7) /*|| mie & (1 << 11)*/) && + (mstatus & 0x8 /*mie*/); + }; + + // Don't run if waiting for an interrupt + if(extraflags & 4) + return 1; + + if(interruptsInFlight()) { + Trap(0x80000007); + } else { + u32 rval = 0; + u32 pc = this->pc; + u32 cycle = this->cyclel; + + for(u32 iInst = 0; iInst < instCount; ++iInst) { + + cycle++; + + if(pc & 3) { + Trap(TrapCode::InstructionAddressMisaligned); + break; + } else { + auto ir = bus->PeekWord(pc); + auto rdid = (ir >> 7) & 0x1f; + //switch(ir & 0x7f) { + + //} + } + + } + } + + if(trapped) { + mcause = trapCode; + if(trapCode & 0x80000000) { + mtval = 0; + // pc -= 4 + } else { + + } + } } } // namespace riscv diff --git a/native/projects/riscv/src/Devices/ClntDevice.cpp b/native/projects/riscv/src/Devices/ClntDevice.cpp index ba41953..2b0edee 100644 --- a/native/projects/riscv/src/Devices/ClntDevice.cpp +++ b/native/projects/riscv/src/Devices/ClntDevice.cpp @@ -1,58 +1,68 @@ -#include #include +#include +#include namespace riscv::devices { - /// anonymous enum to make documenting stuff easier - /// could be done with constexpr but Lazy lilypad - enum : AddressT { - MATCHL_ADDRESS = ClntDevice::BASE_ADDRESS + 0x4000, - MATCHH_ADDRESS = ClntDevice::BASE_ADDRESS + 0x4004, + constexpr static AddressT MSIP_ADDRESS = ClntDevice::BASE_ADDRESS, + MATCHL_ADDRESS = ClntDevice::BASE_ADDRESS + 0x4000, + MATCHH_ADDRESS = ClntDevice::BASE_ADDRESS + 0x4004, + TIMERL_ADDRESS = ClntDevice::BASE_ADDRESS + 0xbff8, + TIMERH_ADDRESS = ClntDevice::BASE_ADDRESS + 0xbffc; - TIMERL_ADDRESS = ClntDevice::BASE_ADDRESS + 0xbff8, - TIMERH_ADDRESS = ClntDevice::BASE_ADDRESS + 0xbffc, - }; + void ClntDevice::Clock() { + // TODO: handle timer + // If match low and high match the timer during a clock + // we should fire the interrupt, otherwise not do so - void ClntDevice::Clock() { - // TODO: handle timer - // If match low and high match the timer during a clock - // we should fire the interrupt, otherwise not do so + u32 new_timer = timerCountLow + 1; + if(new_timer < timerCountLow) + timerCountHigh++; + timerCountLow = new_timer; + + if((timerCountHigh > timerMatchHigh || + (timerCountHigh == timerMatchHigh && timerCountLow == timerMatchLow)) && + (timerMatchHigh || timerMatchLow)) { + // Fire the CLNT timer interrupt + bus->GetCPU()->TimerInterrupt(); } + } + u32 ClntDevice::Peek(AddressT address) { + switch(address) { + case TIMERL_ADDRESS: + return timerCountLow; - u32 ClntDevice::Peek(AddressT address) { - switch(address) { - case TIMERL_ADDRESS: - return timerCountLow; + case TIMERH_ADDRESS: + return timerCountHigh; - case TIMERH_ADDRESS: - return timerCountHigh; + case MATCHL_ADDRESS: + return timerMatchLow; - case MATCHL_ADDRESS: - return timerMatchLow; - - case MATCHH_ADDRESS: - return timerMatchHigh; - - default: - return 0x0; - } + case MATCHH_ADDRESS: + return timerMatchHigh; + + default: + return 0x0; } + } - void ClntDevice::Poke(AddressT address, u32 value) { - switch(address) { - case MATCHL_ADDRESS: - timerMatchLow = value; - break; - case MATCHH_ADDRESS: - timerMatchHigh = value; - break; + void ClntDevice::Poke(AddressT address, u32 value) { + switch(address) { + case MATCHL_ADDRESS: + timerMatchLow = value; + break; - // ? - default: - lucore::LogInfo("CLNT({}) unhandled poke @ 0x{:08x} : 0x{:08x}", static_cast(this), address, value); - break; - } + case MATCHH_ADDRESS: + timerMatchHigh = value; + break; + + // ? + default: + lucore::LogInfo("CLNT({}) unhandled poke @ 0x{:08x} : 0x{:08x}", + static_cast(this), address, value); + break; } + } -} +} // namespace riscv::devices diff --git a/native/projects/riscv/src/Devices/RamDevice.cpp b/native/projects/riscv/src/Devices/RamDevice.cpp index fe7dcdc..46d1dec 100644 --- a/native/projects/riscv/src/Devices/RamDevice.cpp +++ b/native/projects/riscv/src/Devices/RamDevice.cpp @@ -11,8 +11,7 @@ namespace riscv::devices { } RamDevice::~RamDevice() { - if(memory) - delete[] memory; + delete[] memory; } AddressT RamDevice::Base() const {