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
|
||||
src/Bus.cpp
|
||||
src/CPU.cpp
|
||||
src/System.cpp
|
||||
|
||||
src/Devices/SysconDevice.cpp
|
||||
src/Devices/RamDevice.cpp
|
||||
)
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
||||
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/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
|
||||
|
|
|
@ -8,4 +8,6 @@ namespace riscv {
|
|||
/// A type that can repressent address space or address space offsets.
|
||||
using AddressT = u32;
|
||||
|
||||
|
||||
|
||||
} // namespace riscv
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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