riscv: Implement partial CLINT and SYSCON.
This commit is contained in:
parent
6a3170dc5c
commit
aa66a1ff2e
|
@ -8,6 +8,9 @@ project(riscv_emu
|
||||||
add_library(riscv
|
add_library(riscv
|
||||||
src/Bus.cpp
|
src/Bus.cpp
|
||||||
src/CPU.cpp
|
src/CPU.cpp
|
||||||
|
src/System.cpp
|
||||||
|
|
||||||
|
src/Devices/SysconDevice.cpp
|
||||||
src/Devices/RamDevice.cpp
|
src/Devices/RamDevice.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
# riscv
|
# 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:
|
This is based off [cnlohr/mini-rv32ima](https://github.com/cnlohr/mini-rv32ima), but:
|
||||||
|
|
||||||
- Rewritten in C++20 (because I like sanity)
|
- Rewritten in C++20 (because I like sanity)
|
||||||
- Cleaned up somewhat
|
- Cleaned up vastly
|
||||||
- Moved *ALL* device and MMIO code to seperate interfaces
|
- 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.
|
||||||
|
|
|
@ -16,6 +16,7 @@ namespace riscv {
|
||||||
struct Device {
|
struct Device {
|
||||||
enum class BasicType {
|
enum class BasicType {
|
||||||
Device, // do not upcast.
|
Device, // do not upcast.
|
||||||
|
Cpu, // upcast to CPU is safe.
|
||||||
PlainMemory, // upcast to MemoryDevice is safe.
|
PlainMemory, // upcast to MemoryDevice is safe.
|
||||||
Mmio // upcast to MmioDevice is safe.
|
Mmio // upcast to MmioDevice is safe.
|
||||||
};
|
};
|
||||||
|
@ -34,17 +35,23 @@ namespace riscv {
|
||||||
/// This function is called by the bus to clock devices.
|
/// This function is called by the bus to clock devices.
|
||||||
virtual void Clock() {}
|
virtual void Clock() {}
|
||||||
|
|
||||||
|
// ability to interrupt
|
||||||
|
|
||||||
|
// probably some reset functionality later on
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
constexpr bool IsA() {
|
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;
|
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;
|
return this->Type() == BasicType::Mmio;
|
||||||
}
|
} else {
|
||||||
|
|
||||||
// Invalid types should do this.
|
// Invalid types should do this.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
constexpr T Upcast() {
|
constexpr T Upcast() {
|
||||||
|
@ -121,12 +128,16 @@ namespace riscv {
|
||||||
void PokeWord(AddressT address, u32 value);
|
void PokeWord(AddressT address, u32 value);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// TODO: version which takes Device::BasicType
|
||||||
Bus::Device* FindDeviceForAddress(AddressT address) const;
|
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;
|
std::vector<Device*> devices;
|
||||||
|
|
||||||
// TODO: if these end up being a hotpath replace with ankerl::unordered_dense
|
// 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, MemoryDevice*> mapped_devices;
|
||||||
std::unordered_map<AddressT, MmioDevice*> mmio_devices;
|
std::unordered_map<AddressT, MmioDevice*> mmio_devices;
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
namespace riscv {
|
namespace riscv {
|
||||||
|
|
||||||
/// The CPU core.
|
/// The CPU core.
|
||||||
struct CPU {
|
struct CPU : Bus::Device {
|
||||||
/// CPU core state.
|
/// CPU core state.
|
||||||
struct State {
|
struct State {
|
||||||
u32 gpr[32];
|
u32 gpr[32];
|
||||||
|
@ -13,11 +13,6 @@ namespace riscv {
|
||||||
u32 cyclel;
|
u32 cyclel;
|
||||||
u32 cycleh;
|
u32 cycleh;
|
||||||
|
|
||||||
u32 timerl;
|
|
||||||
u32 timerh;
|
|
||||||
u32 timermatchl;
|
|
||||||
u32 timermatchh;
|
|
||||||
|
|
||||||
u32 mscratch;
|
u32 mscratch;
|
||||||
u32 mtvec;
|
u32 mtvec;
|
||||||
u32 mie;
|
u32 mie;
|
||||||
|
@ -38,14 +33,18 @@ namespace riscv {
|
||||||
|
|
||||||
State& GetState() { return state; }
|
State& GetState() { return state; }
|
||||||
|
|
||||||
|
bool Clocked() const override { return true; }
|
||||||
|
void Clock() override;
|
||||||
|
|
||||||
// TODO: Handlers for CSR read/write
|
// TODO: Handlers for CSR read/write
|
||||||
|
|
||||||
u32 Step(u32 elapsedMicroseconds, u32 instCount);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
State state;
|
State state;
|
||||||
Bus* bus;
|
Bus* bus;
|
||||||
|
|
||||||
|
u32 Step(u32 elapsedMicroseconds, u32 instCount);
|
||||||
|
|
||||||
|
// todo: counters for chrono/inst count.
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace riscv
|
} // namespace riscv
|
||||||
|
|
|
@ -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;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
#include <riscv/Bus.hpp>
|
#include <riscv/Bus.hpp>
|
||||||
|
|
||||||
namespace riscv::devices {
|
namespace riscv::devices {
|
||||||
|
|
|
@ -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;
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,15 +1,15 @@
|
||||||
#include <riscv/Bus.hpp>
|
#include <riscv/Bus.hpp>
|
||||||
|
#include <riscv/CPU.hpp>
|
||||||
#include <riscv/Devices/RamDevice.hpp>
|
#include <riscv/Devices/RamDevice.hpp>
|
||||||
|
#include <riscv/Devices/SysconDevice.hpp>
|
||||||
|
#include <riscv/Devices/ClntDevice.hpp>
|
||||||
|
|
||||||
namespace riscv {
|
namespace riscv {
|
||||||
|
|
||||||
// fwd decls
|
|
||||||
struct CPU;
|
|
||||||
|
|
||||||
/// a system.
|
/// a system.
|
||||||
struct System {
|
struct System {
|
||||||
|
/// Create a basic system with the basic periphials created.
|
||||||
/// Create
|
/// All other periphials should be managed by the creator of this System
|
||||||
static System* WithMemory(AddressT ramSize);
|
static System* WithMemory(AddressT ramSize);
|
||||||
|
|
||||||
~System();
|
~System();
|
||||||
|
@ -23,7 +23,6 @@ namespace riscv {
|
||||||
Bus* GetBus();
|
Bus* GetBus();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
/// How many Cycle() calls will the bus get
|
/// How many Cycle() calls will the bus get
|
||||||
/// (also decides ipsRate)
|
/// (also decides ipsRate)
|
||||||
u32 cycleRate;
|
u32 cycleRate;
|
||||||
|
@ -31,10 +30,13 @@ namespace riscv {
|
||||||
/// How many instructions will the CPU execute each step
|
/// How many instructions will the CPU execute each step
|
||||||
u32 ipsRate;
|
u32 ipsRate;
|
||||||
|
|
||||||
// Most of our basic required devices.
|
|
||||||
CPU* cpu;
|
|
||||||
Bus* bus;
|
Bus* bus;
|
||||||
|
|
||||||
|
// Required devices.
|
||||||
|
CPU* cpu;
|
||||||
devices::RamDevice* ram;
|
devices::RamDevice* ram;
|
||||||
}
|
devices::SysconDevice* syscon;
|
||||||
|
devices::ClntDevice* clnt;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace riscv
|
} // namespace riscv
|
||||||
|
|
|
@ -8,4 +8,6 @@ namespace riscv {
|
||||||
/// A type that can repressent address space or address space offsets.
|
/// A type that can repressent address space or address space offsets.
|
||||||
using AddressT = u32;
|
using AddressT = u32;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace riscv
|
} // namespace riscv
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <riscv/Bus.hpp>
|
#include <riscv/Bus.hpp>
|
||||||
|
#include <riscv/CPU.hpp>
|
||||||
|
|
||||||
namespace riscv {
|
namespace riscv {
|
||||||
|
|
||||||
|
@ -13,6 +14,11 @@ namespace riscv {
|
||||||
if(!device)
|
if(!device)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if(device->IsA<CPU*>()) {
|
||||||
|
cpu = device->Upcast<CPU*>();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if(device->IsA<MemoryDevice*>()) {
|
if(device->IsA<MemoryDevice*>()) {
|
||||||
auto* upcasted = device->Upcast<MemoryDevice*>();
|
auto* upcasted = device->Upcast<MemoryDevice*>();
|
||||||
|
|
||||||
|
@ -47,6 +53,8 @@ namespace riscv {
|
||||||
if(device->Clocked())
|
if(device->Clocked())
|
||||||
device->Clock();
|
device->Clock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cpu->Clock();
|
||||||
}
|
}
|
||||||
|
|
||||||
u8 Bus::PeekByte(AddressT address) {
|
u8 Bus::PeekByte(AddressT address) {
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
#include <riscv/CPU.hpp>
|
||||||
|
|
||||||
|
namespace riscv {
|
||||||
|
|
||||||
|
void CPU::Clock() {
|
||||||
|
// do the thing
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace riscv
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
Loading…
Reference in New Issue