riscv: Implement partial CLINT and SYSCON.

This commit is contained in:
Lily Tsuru 2023-07-22 22:43:49 -04:00
parent 6a3170dc5c
commit aa66a1ff2e
14 changed files with 198 additions and 29 deletions

View File

@ -8,6 +8,9 @@ project(riscv_emu
add_library(riscv
src/Bus.cpp
src/CPU.cpp
src/System.cpp
src/Devices/SysconDevice.cpp
src/Devices/RamDevice.cpp
)

View File

@ -1,11 +1,11 @@
# riscv
This is the RISC-V emulation core that LCPU uses in its native emulation module.
This is a simple RISC-V RV32IMA emulation library.
This is based off [cnlohr/mini-rv32ima](https://github.com/cnlohr/mini-rv32ima), but:
- Rewritten in C++20 (because I like sanity)
- Cleaned up somewhat
- Cleaned up vastly
- Moved *ALL* device and MMIO code to seperate interfaces
- Re-implemented the timer device and the UART as said oop interface
- Lua devices use a wrapper which can contain lua callbacks
Depends on lucore.

View File

@ -16,6 +16,7 @@ namespace riscv {
struct Device {
enum class BasicType {
Device, // do not upcast.
Cpu, // upcast to CPU is safe.
PlainMemory, // upcast to MemoryDevice is safe.
Mmio // upcast to MmioDevice is safe.
};
@ -34,16 +35,22 @@ namespace riscv {
/// This function is called by the bus to clock devices.
virtual void Clock() {}
// ability to interrupt
// probably some reset functionality later on
template <class T>
constexpr bool IsA() {
if(std::is_same_v<T, Bus::MemoryDevice*>) {
if constexpr (std::is_same_v<T, CPU*>) {
return this->Type() == BasicType::Cpu;
} else if constexpr (std::is_same_v<T, Bus::MemoryDevice*>) {
return this->Type() == BasicType::PlainMemory;
} else if(std::is_same_v<T, Bus::MmioDevice*>) {
} else if constexpr (std::is_same_v<T, Bus::MmioDevice*>) {
return this->Type() == BasicType::Mmio;
} else {
// Invalid types should do this.
return false;
}
// Invalid types should do this.
return false;
}
template <class T>
@ -121,12 +128,16 @@ namespace riscv {
void PokeWord(AddressT address, u32 value);
private:
// TODO: version which takes Device::BasicType
Bus::Device* FindDeviceForAddress(AddressT address) const;
/// All devices attached to the bus
CPU* cpu;
/// All plain memory or mmio devices attached to the bus
std::vector<Device*> devices;
// TODO: if these end up being a hotpath replace with ankerl::unordered_dense
// (or just use the [devices] vector, probably.)
std::unordered_map<AddressT, MemoryDevice*> mapped_devices;
std::unordered_map<AddressT, MmioDevice*> mmio_devices;
};

View File

@ -4,7 +4,7 @@
namespace riscv {
/// The CPU core.
struct CPU {
struct CPU : Bus::Device {
/// CPU core state.
struct State {
u32 gpr[32];
@ -13,11 +13,6 @@ namespace riscv {
u32 cyclel;
u32 cycleh;
u32 timerl;
u32 timerh;
u32 timermatchl;
u32 timermatchh;
u32 mscratch;
u32 mtvec;
u32 mie;
@ -38,14 +33,18 @@ namespace riscv {
State& GetState() { return state; }
bool Clocked() const override { return true; }
void Clock() override;
// TODO: Handlers for CSR read/write
u32 Step(u32 elapsedMicroseconds, u32 instCount);
private:
State state;
Bus* bus;
u32 Step(u32 elapsedMicroseconds, u32 instCount);
// todo: counters for chrono/inst count.
};
} // namespace riscv

View File

@ -0,0 +1,30 @@
#pragma once
#include <riscv/Bus.hpp>
namespace riscv::devices {
/// Partial implementation of a CLINT.
/// The timer device is implemented, SIP is not.
struct ClntDevice : public Bus::MmioDevice {
constexpr static AddressT BASE_ADDRESS = 0x11000000;
AddressT Base() const override { return BASE_ADDRESS; }
AddressT Size() const override { return 0xbfff; }
bool Clocked() const override { return true; }
void Clock() override;
u32 Peek(AddressT address) override;
void Poke(AddressT address, u32 value) override;
private:
u32 timerCountHigh;
u32 timerCountLow;
u32 timerMatchHigh;
u32 timerMatchLow;
};
}

View File

@ -1,3 +1,5 @@
#pragma once
#include <riscv/Bus.hpp>
namespace riscv::devices {

View File

@ -0,0 +1,21 @@
#pragma once
#include <riscv/Bus.hpp>
namespace riscv { struct System; }
namespace riscv::devices {
/// RISC-V SYSCON device. This will later talk to the system to tell it things.
struct SysconDevice : public Bus::MmioDevice {
constexpr static AddressT BASE_ADDRESS = 0x11100000;
SysconDevice(System* system);
AddressT Size() const override { return sizeof(u32); } // I think this is right?
u32 Peek(AddressT address) override;
void Poke(AddressT address, u32 value) override;
private:
System* system;
};
}

View File

@ -1,15 +1,15 @@
#include <riscv/Bus.hpp>
#include <riscv/CPU.hpp>
#include <riscv/Devices/RamDevice.hpp>
#include <riscv/Devices/SysconDevice.hpp>
#include <riscv/Devices/ClntDevice.hpp>
namespace riscv {
// fwd decls
struct CPU;
/// a system.
struct System {
/// Create
/// Create a basic system with the basic periphials created.
/// All other periphials should be managed by the creator of this System
static System* WithMemory(AddressT ramSize);
~System();
@ -23,18 +23,20 @@ namespace riscv {
Bus* GetBus();
private:
/// How many Cycle() calls will the bus get
/// (also decides ipsRate)
u32 cycleRate;
u32 cycleRate;
/// How many instructions will the CPU execute each step
u32 ipsRate;
// Most of our basic required devices.
CPU* cpu;
Bus* bus;
// Required devices.
CPU* cpu;
devices::RamDevice* ram;
}
devices::SysconDevice* syscon;
devices::ClntDevice* clnt;
};
} // namespace riscv

View File

@ -8,4 +8,6 @@ namespace riscv {
/// A type that can repressent address space or address space offsets.
using AddressT = u32;
} // namespace riscv

View File

@ -1,5 +1,6 @@
#include <algorithm>
#include <riscv/Bus.hpp>
#include <riscv/CPU.hpp>
namespace riscv {
@ -13,6 +14,11 @@ namespace riscv {
if(!device)
return false;
if(device->IsA<CPU*>()) {
cpu = device->Upcast<CPU*>();
return true;
}
if(device->IsA<MemoryDevice*>()) {
auto* upcasted = device->Upcast<MemoryDevice*>();
@ -47,6 +53,8 @@ namespace riscv {
if(device->Clocked())
device->Clock();
}
cpu->Clock();
}
u8 Bus::PeekByte(AddressT address) {

View File

@ -0,0 +1,9 @@
#include <riscv/CPU.hpp>
namespace riscv {
void CPU::Clock() {
// do the thing
}
} // namespace riscv

View File

@ -0,0 +1,52 @@
#include <riscv/Devices/ClntDevice.hpp>
#include <lucore/Logger.hpp>
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,
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
}
u32 ClntDevice::Peek(AddressT address) {
switch(address) {
case TIMERL_ADDRESS:
return timerCountLow;
case TIMERH_ADDRESS:
return timerCountHigh;
default:
return 0x0;
}
}
void ClntDevice::Poke(AddressT address, u32 value) {
switch(address) {
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);
break;
}
}
}

View File

@ -0,0 +1,30 @@
#include <riscv/Devices/SysconDevice.hpp>
#include <riscv/System.hpp>
#include <lucore/Logger.hpp>
namespace riscv::devices {
SysconDevice::SysconDevice(System* system) : system(system) {
}
u32 SysconDevice::Peek(AddressT address) {
lucore::LogInfo("SYSCON({}) Peek @ 0x{:08x}", static_cast<void*>(this), address);
return -1;
}
void SysconDevice::Poke(AddressT address, u32 value) {
lucore::LogInfo("SYSCON({}) Poke @ 0x{:08x}: 0x{:08x}", static_cast<void*>(this), address, value);
/*
if(address == BASE_ADDRESS) {
if(value == 0x5555)
system->PowerOff();
else if (value == 0x7777)
system->Reset();
}
*/
return;
}
} // namespace riscv::devices

View File