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:
Lily Tsuru 2023-07-23 05:19:08 -04:00
parent 09b1969d22
commit cd684e1f3e
8 changed files with 276 additions and 87 deletions

View File

@ -10,6 +10,8 @@ add_library(riscv
src/CPU.cpp src/CPU.cpp
src/System.cpp src/System.cpp
# Basic system devices
src/Devices/ClntDevice.cpp
src/Devices/SysconDevice.cpp src/Devices/SysconDevice.cpp
src/Devices/RamDevice.cpp src/Devices/RamDevice.cpp
) )

View File

@ -2,6 +2,7 @@
#include <lucore/Assert.hpp> #include <lucore/Assert.hpp>
#include <riscv/Types.hpp> #include <riscv/Types.hpp>
#include <riscv/CPUTypes.hpp>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
@ -27,6 +28,10 @@ namespace riscv {
virtual ~Device() = default; virtual ~Device() = default;
virtual void Attached(Bus* bus) {
this->bus = bus;
}
virtual BasicType Type() const { return BasicType::Device; } virtual BasicType Type() const { return BasicType::Device; }
/// Does this device clock? /// Does this device clock?
@ -58,6 +63,9 @@ namespace riscv {
LUCORE_ASSERT(IsA<T>(), "Upcast failure: this is not a T"); LUCORE_ASSERT(IsA<T>(), "Upcast failure: this is not a T");
return static_cast<T>(this); return static_cast<T>(this);
} }
protected:
/// The bus this device is attached to.
Bus* bus;
}; };
/// Interface plain memory bus devices use. /// Interface plain memory bus devices use.
@ -67,8 +75,9 @@ namespace riscv {
virtual BasicType Type() const override { return BasicType::PlainMemory; } virtual BasicType Type() const override { return BasicType::PlainMemory; }
virtual AddressT Base() const = 0; 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; virtual AddressT Size() const = 0;
/// Peek() -> reads a value from this device. /// Peek() -> reads a value from this device.
@ -91,8 +100,8 @@ namespace riscv {
virtual AddressT Base() const; virtual AddressT Base() const;
/// How many bytes does this device occupy of address space? This should /// How many bytes does this device occupy of address space?
/// effectively be a constant, and should not change during CPU execution. /// This should not change during execution.
virtual AddressT Size() const = 0; virtual AddressT Size() const = 0;
virtual u32 Peek(AddressT address) = 0; virtual u32 Peek(AddressT address) = 0;
@ -127,7 +136,12 @@ namespace riscv {
void PokeShort(AddressT address, u16 value); void PokeShort(AddressT address, u16 value);
void PokeWord(AddressT address, u32 value); void PokeWord(AddressT address, u32 value);
CPU* GetCPU() { return cpu; }
private: private:
void CpuTrap(u32 trapCode);
// TODO: version which takes Device::BasicType // TODO: version which takes Device::BasicType
Bus::Device* FindDeviceForAddress(AddressT address) const; Bus::Device* FindDeviceForAddress(AddressT address) const;

View File

@ -1,48 +1,49 @@
#include <riscv/Bus.hpp> #include <riscv/Bus.hpp>
#include <riscv/CPUTypes.hpp>
#include <riscv/Types.hpp> #include <riscv/Types.hpp>
namespace riscv { namespace riscv {
/// The CPU core. /// The CPU core.
struct CPU : Bus::Device { struct CPU : Bus::Device {
/// CPU core state. bool Clocked() const override { return true; }
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; }
void Clock() override; void Clock() override;
// TODO: Handlers for CSR read/write 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;
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: private:
State state; /// Set by [CPU::Trap] to tell the CPU it was trapped.
Bus* bus; /// 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. // todo: counters for chrono/inst count.
}; };

View File

@ -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

View File

@ -6,7 +6,7 @@ namespace riscv {
Bus::~Bus() { Bus::~Bus() {
// Free all devices // Free all devices
for(auto device: devices) for(auto device : devices)
delete device; delete device;
} }
@ -15,6 +15,8 @@ namespace riscv {
return false; return false;
if(device->IsA<CPU*>()) { 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*>(); cpu = device->Upcast<CPU*>();
return true; return true;
} }
@ -54,47 +56,65 @@ namespace riscv {
device->Clock(); device->Clock();
} }
// The CPU is always clocked last to ensure devices can generate
// interrupts properly. I don't make the rules.
cpu->Clock(); cpu->Clock();
} }
u8 Bus::PeekByte(AddressT address) { u8 Bus::PeekByte(AddressT address) {
if(auto dev = FindDeviceForAddress(address); dev) if(auto dev = FindDeviceForAddress(address); dev)
return dev->Upcast<MemoryDevice*>()->PeekByte(address); return dev->Upcast<MemoryDevice*>()->PeekByte(address);
return -1; else {
cpu->Trap(TrapCode::LoadAccessFault);
return -1;
}
} }
u16 Bus::PeekShort(AddressT address) { u16 Bus::PeekShort(AddressT address) {
if(auto dev = FindDeviceForAddress(address); dev) if(auto dev = FindDeviceForAddress(address); dev)
return dev->Upcast<MemoryDevice*>()->PeekShort(address); return dev->Upcast<MemoryDevice*>()->PeekShort(address);
return -1; else {
cpu->Trap(TrapCode::LoadAccessFault);
return -1;
}
} }
u32 Bus::PeekWord(AddressT address) { u32 Bus::PeekWord(AddressT address) {
if(auto dev = FindDeviceForAddress(address); dev) { if(auto dev = FindDeviceForAddress(address); dev) {
if(dev->IsA<MmioDevice*>()) if(dev->IsA<MmioDevice*>())
return dev->Upcast<MmioDevice*>()->Peek(address); return dev->Upcast<MmioDevice*>()->Peek(address);
else else
return dev->Upcast<MemoryDevice*>()->PeekWord(address); return dev->Upcast<MemoryDevice*>()->PeekWord(address);
} else {
cpu->Trap(TrapCode::LoadAccessFault);
return -1;
} }
return -1;
} }
void Bus::PokeByte(AddressT address, u8 value) { void Bus::PokeByte(AddressT address, u8 value) {
if(auto dev = FindDeviceForAddress(address); dev) if(auto dev = FindDeviceForAddress(address); dev)
return dev->Upcast<MemoryDevice*>()->PokeByte(address, value); return dev->Upcast<MemoryDevice*>()->PokeByte(address, value);
else {
cpu->Trap(TrapCode::StoreAccessFault);
}
} }
void Bus::PokeShort(AddressT address, u16 value) { void Bus::PokeShort(AddressT address, u16 value) {
if(auto dev = FindDeviceForAddress(address); dev) if(auto dev = FindDeviceForAddress(address); dev)
return dev->Upcast<MemoryDevice*>()->PokeShort(address, value); return dev->Upcast<MemoryDevice*>()->PokeShort(address, value);
else {
cpu->Trap(TrapCode::StoreAccessFault);
}
} }
void Bus::PokeWord(AddressT address, u32 value) { void Bus::PokeWord(AddressT address, u32 value) {
if(auto dev = FindDeviceForAddress(address); dev) { if(auto dev = FindDeviceForAddress(address); dev) {
if(dev->IsA<MmioDevice*>()) if(dev->IsA<MmioDevice*>())
dev->Upcast<MmioDevice*>()->Poke(address, value); dev->Upcast<MmioDevice*>()->Poke(address, value);
else else
dev->Upcast<MemoryDevice*>()->PokeWord(address, value); dev->Upcast<MemoryDevice*>()->PokeWord(address, value);
} else {
cpu->Trap(TrapCode::StoreAccessFault);
} }
} }

View File

@ -1,9 +1,81 @@
#include <riscv/CPU.hpp> #include <riscv/CPU.hpp>
#include <riscv/Bus.hpp>
namespace riscv { namespace riscv {
void CPU::Clock() { void CPU::Clock() {
// do the thing // 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 } // namespace riscv

View File

@ -1,58 +1,68 @@
#include <riscv/Devices/ClntDevice.hpp>
#include <lucore/Logger.hpp> #include <lucore/Logger.hpp>
#include <riscv/Devices/ClntDevice.hpp>
#include <riscv/CPU.hpp>
namespace riscv::devices { namespace riscv::devices {
/// anonymous enum to make documenting stuff easier constexpr static AddressT MSIP_ADDRESS = ClntDevice::BASE_ADDRESS,
/// could be done with constexpr but Lazy lilypad MATCHL_ADDRESS = ClntDevice::BASE_ADDRESS + 0x4000,
enum : AddressT { MATCHH_ADDRESS = ClntDevice::BASE_ADDRESS + 0x4004,
MATCHL_ADDRESS = ClntDevice::BASE_ADDRESS + 0x4000, TIMERL_ADDRESS = ClntDevice::BASE_ADDRESS + 0xbff8,
MATCHH_ADDRESS = ClntDevice::BASE_ADDRESS + 0x4004, TIMERH_ADDRESS = ClntDevice::BASE_ADDRESS + 0xbffc;
TIMERL_ADDRESS = ClntDevice::BASE_ADDRESS + 0xbff8, void ClntDevice::Clock() {
TIMERH_ADDRESS = ClntDevice::BASE_ADDRESS + 0xbffc, // 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() { u32 new_timer = timerCountLow + 1;
// TODO: handle timer if(new_timer < timerCountLow)
// If match low and high match the timer during a clock timerCountHigh++;
// we should fire the interrupt, otherwise not do so 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) { case TIMERH_ADDRESS:
switch(address) { return timerCountHigh;
case TIMERL_ADDRESS:
return timerCountLow;
case TIMERH_ADDRESS: case MATCHL_ADDRESS:
return timerCountHigh; return timerMatchLow;
case MATCHL_ADDRESS: case MATCHH_ADDRESS:
return timerMatchLow; return timerMatchHigh;
case MATCHH_ADDRESS: default:
return timerMatchHigh; return 0x0;
default:
return 0x0;
}
} }
}
void ClntDevice::Poke(AddressT address, u32 value) { void ClntDevice::Poke(AddressT address, u32 value) {
switch(address) { switch(address) {
case MATCHL_ADDRESS: case MATCHL_ADDRESS:
timerMatchLow = value; timerMatchLow = value;
break; break;
case MATCHH_ADDRESS:
timerMatchHigh = value;
break;
// ? case MATCHH_ADDRESS:
default: timerMatchHigh = value;
lucore::LogInfo("CLNT({}) unhandled poke @ 0x{:08x} : 0x{:08x}", static_cast<void*>(this), address, value); break;
break;
} // ?
default:
lucore::LogInfo("CLNT({}) unhandled poke @ 0x{:08x} : 0x{:08x}",
static_cast<void*>(this), address, value);
break;
} }
}
} } // namespace riscv::devices

View File

@ -11,8 +11,7 @@ namespace riscv::devices {
} }
RamDevice::~RamDevice() { RamDevice::~RamDevice() {
if(memory) delete[] memory;
delete[] memory;
} }
AddressT RamDevice::Base() const { AddressT RamDevice::Base() const {