wow holy shit lily finally figured out how to work on the cpu core!
In all seriousness, this commit is a major milestone; insofar that it is the first time I can actually work on the CPU core.
This commit is contained in:
parent
09b1969d22
commit
cd684e1f3e
|
@ -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
|
||||
)
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <lucore/Assert.hpp>
|
||||
#include <riscv/Types.hpp>
|
||||
#include <riscv/CPUTypes.hpp>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
|
@ -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<T>(), "Upcast failure: this is not a T");
|
||||
return static_cast<T>(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;
|
||||
|
||||
|
|
|
@ -1,18 +1,26 @@
|
|||
#include <riscv/Bus.hpp>
|
||||
#include <riscv/CPUTypes.hpp>
|
||||
#include <riscv/Types.hpp>
|
||||
|
||||
namespace riscv {
|
||||
|
||||
/// The CPU core.
|
||||
struct CPU : Bus::Device {
|
||||
/// CPU core state.
|
||||
struct State {
|
||||
u32 gpr[32];
|
||||
bool Clocked() const override { return true; }
|
||||
void Clock() override;
|
||||
|
||||
void Trap(u32 trapCode);
|
||||
inline void Trap(TrapCode trapCode) { Trap(static_cast<u32>(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;
|
||||
|
@ -27,22 +35,15 @@ namespace riscv {
|
|||
// 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; }
|
||||
void Clock() override;
|
||||
|
||||
// TODO: Handlers for CSR read/write
|
||||
|
||||
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.
|
||||
};
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
#pragma once
|
||||
#include <riscv/Types.hpp>
|
||||
|
||||
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<usize>(gpr)); }
|
||||
constexpr u32& operator[](usize index) { return gprs_[index]; }
|
||||
u32 gprs_[32];
|
||||
};
|
||||
|
||||
} // namespace riscv
|
|
@ -15,6 +15,8 @@ namespace riscv {
|
|||
return false;
|
||||
|
||||
if(device->IsA<CPU*>()) {
|
||||
// Return early to avoid putting the CPU pointer inside the devices vector.
|
||||
// We do not actually own the CPU.
|
||||
cpu = device->Upcast<CPU*>();
|
||||
return true;
|
||||
}
|
||||
|
@ -54,20 +56,28 @@ 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<MemoryDevice*>()->PeekByte(address);
|
||||
else {
|
||||
cpu->Trap(TrapCode::LoadAccessFault);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
u16 Bus::PeekShort(AddressT address) {
|
||||
if(auto dev = FindDeviceForAddress(address); dev)
|
||||
return dev->Upcast<MemoryDevice*>()->PeekShort(address);
|
||||
else {
|
||||
cpu->Trap(TrapCode::LoadAccessFault);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
u32 Bus::PeekWord(AddressT address) {
|
||||
if(auto dev = FindDeviceForAddress(address); dev) {
|
||||
|
@ -75,18 +85,26 @@ namespace riscv {
|
|||
return dev->Upcast<MmioDevice*>()->Peek(address);
|
||||
else
|
||||
return dev->Upcast<MemoryDevice*>()->PeekWord(address);
|
||||
}
|
||||
} else {
|
||||
cpu->Trap(TrapCode::LoadAccessFault);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
void Bus::PokeByte(AddressT address, u8 value) {
|
||||
if(auto dev = FindDeviceForAddress(address); dev)
|
||||
return dev->Upcast<MemoryDevice*>()->PokeByte(address, value);
|
||||
else {
|
||||
cpu->Trap(TrapCode::StoreAccessFault);
|
||||
}
|
||||
}
|
||||
|
||||
void Bus::PokeShort(AddressT address, u16 value) {
|
||||
if(auto dev = FindDeviceForAddress(address); dev)
|
||||
return dev->Upcast<MemoryDevice*>()->PokeShort(address, value);
|
||||
else {
|
||||
cpu->Trap(TrapCode::StoreAccessFault);
|
||||
}
|
||||
}
|
||||
|
||||
void Bus::PokeWord(AddressT address, u32 value) {
|
||||
|
@ -95,6 +113,8 @@ namespace riscv {
|
|||
dev->Upcast<MmioDevice*>()->Poke(address, value);
|
||||
else
|
||||
dev->Upcast<MemoryDevice*>()->PokeWord(address, value);
|
||||
} else {
|
||||
cpu->Trap(TrapCode::StoreAccessFault);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,81 @@
|
|||
#include <riscv/CPU.hpp>
|
||||
#include <riscv/Bus.hpp>
|
||||
|
||||
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
|
||||
|
|
|
@ -1,24 +1,32 @@
|
|||
#include <riscv/Devices/ClntDevice.hpp>
|
||||
#include <lucore/Logger.hpp>
|
||||
#include <riscv/Devices/ClntDevice.hpp>
|
||||
#include <riscv/CPU.hpp>
|
||||
|
||||
namespace riscv::devices {
|
||||
|
||||
/// anonymous enum to make documenting stuff easier
|
||||
/// could be done with constexpr but Lazy lilypad
|
||||
enum : AddressT {
|
||||
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,
|
||||
};
|
||||
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
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -44,15 +52,17 @@ namespace riscv::devices {
|
|||
case MATCHL_ADDRESS:
|
||||
timerMatchLow = value;
|
||||
break;
|
||||
|
||||
case MATCHH_ADDRESS:
|
||||
timerMatchHigh = value;
|
||||
break;
|
||||
|
||||
// ?
|
||||
default:
|
||||
lucore::LogInfo("CLNT({}) unhandled poke @ 0x{:08x} : 0x{:08x}", static_cast<void*>(this), address, value);
|
||||
lucore::LogInfo("CLNT({}) unhandled poke @ 0x{:08x} : 0x{:08x}",
|
||||
static_cast<void*>(this), address, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace riscv::devices
|
||||
|
|
|
@ -11,7 +11,6 @@ namespace riscv::devices {
|
|||
}
|
||||
|
||||
RamDevice::~RamDevice() {
|
||||
if(memory)
|
||||
delete[] memory;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue