From aa66a1ff2e768963196e1c894b8ff353b74c3ae6 Mon Sep 17 00:00:00 2001 From: modeco80 Date: Sat, 22 Jul 2023 22:43:49 -0400 Subject: [PATCH] riscv: Implement partial CLINT and SYSCON. --- native/projects/riscv/CMakeLists.txt | 3 ++ native/projects/riscv/README.md | 8 +-- native/projects/riscv/include/riscv/Bus.hpp | 23 +++++--- native/projects/riscv/include/riscv/CPU.hpp | 15 +++--- .../include/riscv/Devices/ClntDevice.hpp | 30 +++++++++++ .../riscv/include/riscv/Devices/RamDevice.hpp | 4 +- .../include/riscv/Devices/SysconDevice.hpp | 21 ++++++++ .../projects/riscv/include/riscv/System.hpp | 22 ++++---- native/projects/riscv/include/riscv/Types.hpp | 2 + native/projects/riscv/src/Bus.cpp | 8 +++ native/projects/riscv/src/CPU.cpp | 9 ++++ .../projects/riscv/src/Devices/ClntDevice.cpp | 52 +++++++++++++++++++ .../riscv/src/Devices/SysconDevice.cpp | 30 +++++++++++ native/projects/riscv/src/System.cpp | 0 14 files changed, 198 insertions(+), 29 deletions(-) create mode 100644 native/projects/riscv/include/riscv/Devices/ClntDevice.hpp create mode 100644 native/projects/riscv/include/riscv/Devices/SysconDevice.hpp create mode 100644 native/projects/riscv/src/Devices/ClntDevice.cpp create mode 100644 native/projects/riscv/src/Devices/SysconDevice.cpp create mode 100644 native/projects/riscv/src/System.cpp diff --git a/native/projects/riscv/CMakeLists.txt b/native/projects/riscv/CMakeLists.txt index 9dda00c..0904cf9 100644 --- a/native/projects/riscv/CMakeLists.txt +++ b/native/projects/riscv/CMakeLists.txt @@ -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 ) diff --git a/native/projects/riscv/README.md b/native/projects/riscv/README.md index e47b0f6..2234249 100644 --- a/native/projects/riscv/README.md +++ b/native/projects/riscv/README.md @@ -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. diff --git a/native/projects/riscv/include/riscv/Bus.hpp b/native/projects/riscv/include/riscv/Bus.hpp index 9275f94..7b69b42 100644 --- a/native/projects/riscv/include/riscv/Bus.hpp +++ b/native/projects/riscv/include/riscv/Bus.hpp @@ -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 constexpr bool IsA() { - if(std::is_same_v) { + if constexpr (std::is_same_v) { + return this->Type() == BasicType::Cpu; + } else if constexpr (std::is_same_v) { return this->Type() == BasicType::PlainMemory; - } else if(std::is_same_v) { + } else if constexpr (std::is_same_v) { return this->Type() == BasicType::Mmio; + } else { + // Invalid types should do this. + return false; } - - // Invalid types should do this. - return false; } template @@ -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 devices; // TODO: if these end up being a hotpath replace with ankerl::unordered_dense + // (or just use the [devices] vector, probably.) std::unordered_map mapped_devices; std::unordered_map mmio_devices; }; diff --git a/native/projects/riscv/include/riscv/CPU.hpp b/native/projects/riscv/include/riscv/CPU.hpp index 062ec7a..d2aa172 100644 --- a/native/projects/riscv/include/riscv/CPU.hpp +++ b/native/projects/riscv/include/riscv/CPU.hpp @@ -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 diff --git a/native/projects/riscv/include/riscv/Devices/ClntDevice.hpp b/native/projects/riscv/include/riscv/Devices/ClntDevice.hpp new file mode 100644 index 0000000..73e1854 --- /dev/null +++ b/native/projects/riscv/include/riscv/Devices/ClntDevice.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include + +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; + }; + +} diff --git a/native/projects/riscv/include/riscv/Devices/RamDevice.hpp b/native/projects/riscv/include/riscv/Devices/RamDevice.hpp index 38dec96..60f08a0 100644 --- a/native/projects/riscv/include/riscv/Devices/RamDevice.hpp +++ b/native/projects/riscv/include/riscv/Devices/RamDevice.hpp @@ -1,3 +1,5 @@ +#pragma once + #include namespace riscv::devices { @@ -27,7 +29,7 @@ namespace riscv::devices { constexpr usize AddressToIndex(AddressT address) { return ((address - memoryBase) % memorySize) / sizeof(T); } - + AddressT memoryBase {}; u8* memory {}; diff --git a/native/projects/riscv/include/riscv/Devices/SysconDevice.hpp b/native/projects/riscv/include/riscv/Devices/SysconDevice.hpp new file mode 100644 index 0000000..702dfa1 --- /dev/null +++ b/native/projects/riscv/include/riscv/Devices/SysconDevice.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include + +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; + }; +} diff --git a/native/projects/riscv/include/riscv/System.hpp b/native/projects/riscv/include/riscv/System.hpp index 0d67cb2..3e08336 100644 --- a/native/projects/riscv/include/riscv/System.hpp +++ b/native/projects/riscv/include/riscv/System.hpp @@ -1,15 +1,15 @@ #include +#include #include +#include +#include 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 diff --git a/native/projects/riscv/include/riscv/Types.hpp b/native/projects/riscv/include/riscv/Types.hpp index 32ca17c..307aa9a 100644 --- a/native/projects/riscv/include/riscv/Types.hpp +++ b/native/projects/riscv/include/riscv/Types.hpp @@ -8,4 +8,6 @@ namespace riscv { /// A type that can repressent address space or address space offsets. using AddressT = u32; + + } // namespace riscv diff --git a/native/projects/riscv/src/Bus.cpp b/native/projects/riscv/src/Bus.cpp index 34d5e97..dc9c488 100644 --- a/native/projects/riscv/src/Bus.cpp +++ b/native/projects/riscv/src/Bus.cpp @@ -1,5 +1,6 @@ #include #include +#include namespace riscv { @@ -13,6 +14,11 @@ namespace riscv { if(!device) return false; + if(device->IsA()) { + cpu = device->Upcast(); + return true; + } + if(device->IsA()) { auto* upcasted = device->Upcast(); @@ -47,6 +53,8 @@ namespace riscv { if(device->Clocked()) device->Clock(); } + + cpu->Clock(); } u8 Bus::PeekByte(AddressT address) { diff --git a/native/projects/riscv/src/CPU.cpp b/native/projects/riscv/src/CPU.cpp index e69de29..52716de 100644 --- a/native/projects/riscv/src/CPU.cpp +++ b/native/projects/riscv/src/CPU.cpp @@ -0,0 +1,9 @@ +#include + +namespace riscv { + + void CPU::Clock() { + // do the thing + } + +} // namespace riscv diff --git a/native/projects/riscv/src/Devices/ClntDevice.cpp b/native/projects/riscv/src/Devices/ClntDevice.cpp new file mode 100644 index 0000000..2c7468d --- /dev/null +++ b/native/projects/riscv/src/Devices/ClntDevice.cpp @@ -0,0 +1,52 @@ +#include +#include + +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(this), address, value); + break; + } + } + +} diff --git a/native/projects/riscv/src/Devices/SysconDevice.cpp b/native/projects/riscv/src/Devices/SysconDevice.cpp new file mode 100644 index 0000000..5b5f964 --- /dev/null +++ b/native/projects/riscv/src/Devices/SysconDevice.cpp @@ -0,0 +1,30 @@ +#include +#include + +#include + +namespace riscv::devices { + + SysconDevice::SysconDevice(System* system) : system(system) { + } + + u32 SysconDevice::Peek(AddressT address) { + + lucore::LogInfo("SYSCON({}) Peek @ 0x{:08x}", static_cast(this), address); + return -1; + } + + void SysconDevice::Poke(AddressT address, u32 value) { + lucore::LogInfo("SYSCON({}) Poke @ 0x{:08x}: 0x{:08x}", static_cast(this), address, value); + /* + if(address == BASE_ADDRESS) { + if(value == 0x5555) + system->PowerOff(); + else if (value == 0x7777) + system->Reset(); + } + */ + return; + } + +} // namespace riscv::devices diff --git a/native/projects/riscv/src/System.cpp b/native/projects/riscv/src/System.cpp new file mode 100644 index 0000000..e69de29