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/System.cpp
# Basic system devices
src/Devices/ClntDevice.cpp
src/Devices/SysconDevice.cpp
src/Devices/RamDevice.cpp
)

View File

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

View File

@ -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.
};

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

@ -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);
}
}

View File

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

View File

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

View File

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