From 38e7fc464697ca397a0cfc34f9de1159ed8eee64 Mon Sep 17 00:00:00 2001 From: modeco80 Date: Sun, 23 Jul 2023 18:13:03 -0400 Subject: [PATCH] no one will know the pain i go through --- .clang-format | 2 +- .../projects/lucore/include/lucore/Types.hpp | 10 +- native/projects/riscv/include/riscv/Bus.hpp | 42 +- native/projects/riscv/include/riscv/CPU.hpp | 6 +- .../projects/riscv/include/riscv/CPUTypes.hpp | 4 +- .../include/riscv/Devices/ClntDevice.hpp | 10 +- .../riscv/include/riscv/Devices/RamDevice.hpp | 22 +- .../include/riscv/Devices/SysconDevice.hpp | 8 +- .../projects/riscv/include/riscv/System.hpp | 2 +- native/projects/riscv/include/riscv/Types.hpp | 4 +- native/projects/riscv/src/Bus.cpp | 16 +- native/projects/riscv/src/CPU.cpp | 531 +++++++++++++++++- .../projects/riscv/src/Devices/ClntDevice.cpp | 6 +- .../projects/riscv/src/Devices/RamDevice.cpp | 18 +- .../riscv/src/Devices/SysconDevice.cpp | 11 +- native/projects/riscv_test_harness/main.cpp | 19 +- 16 files changed, 600 insertions(+), 111 deletions(-) diff --git a/.clang-format b/.clang-format index d3f1e90..ae4b473 100755 --- a/.clang-format +++ b/.clang-format @@ -22,7 +22,7 @@ BinPackParameters: true BreakConstructorInitializers: BeforeColon BreakStringLiterals: false -ColumnLimit: 100 +ColumnLimit: 150 CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: true diff --git a/native/projects/lucore/include/lucore/Types.hpp b/native/projects/lucore/include/lucore/Types.hpp index 98a77fe..1fac33c 100644 --- a/native/projects/lucore/include/lucore/Types.hpp +++ b/native/projects/lucore/include/lucore/Types.hpp @@ -2,14 +2,14 @@ //namespace lucore { using u8 = std::uint8_t; - using s8 = std::int8_t; + using i8 = std::int8_t; using u16 = std::uint16_t; - using s16 = std::int16_t; + using i16 = std::int16_t; using u32 = std::uint32_t; - using s32 = std::int32_t; + using i32 = std::int32_t; using u64 = std::uint64_t; - using s64 = std::int64_t; + using i64 = std::int64_t; using usize = std::size_t; - using ssize = std::intptr_t; + using isize = std::intptr_t; //} // namespace lucore diff --git a/native/projects/riscv/include/riscv/Bus.hpp b/native/projects/riscv/include/riscv/Bus.hpp index 1b4a327..16b8d9a 100644 --- a/native/projects/riscv/include/riscv/Bus.hpp +++ b/native/projects/riscv/include/riscv/Bus.hpp @@ -74,21 +74,21 @@ namespace riscv { virtual BasicType Type() const override { return BasicType::PlainMemory; } - virtual AddressT Base() const = 0; + virtual Address Base() const = 0; /// How many bytes does this device occupy of address space? /// This should not change during execution. - virtual AddressT Size() const = 0; + virtual Address Size() const = 0; /// Peek() -> reads a value from this device. - virtual u8 PeekByte(AddressT address) = 0; - virtual u16 PeekShort(AddressT address) = 0; - virtual u32 PeekWord(AddressT address) = 0; + virtual u8 PeekByte(Address address) = 0; + virtual u16 PeekShort(Address address) = 0; + virtual u32 PeekWord(Address address) = 0; /// Poke() -> Writes a value to this device's space in memory - virtual void PokeByte(AddressT address, u8 value) = 0; - virtual void PokeShort(AddressT address, u16 value) = 0; - virtual void PokeWord(AddressT address, u32 value) = 0; + virtual void PokeByte(Address address, u8 value) = 0; + virtual void PokeShort(Address address, u16 value) = 0; + virtual void PokeWord(Address address, u32 value) = 0; }; /// A device in the MMIO region. @@ -98,14 +98,14 @@ namespace riscv { virtual BasicType Type() const override { return BasicType::Mmio; } - virtual AddressT Base() const; + virtual Address Base() const = 0; /// How many bytes does this device occupy of address space? /// This should not change during execution. - virtual AddressT Size() const = 0; + virtual Address Size() const = 0; - virtual u32 Peek(AddressT address) = 0; - virtual void Poke(AddressT address, u32 value) = 0; + virtual u32 Peek(Address address) = 0; + virtual void Poke(Address address, u32 value) = 0; }; /// Bus destructor. @@ -128,20 +128,20 @@ namespace riscv { /// Clock all clocked devices mapped onto the bus.. void Clock(); - u8 PeekByte(AddressT address); - u16 PeekShort(AddressT address); - u32 PeekWord(AddressT address); + u8 PeekByte(Address address); + u16 PeekShort(Address address); + u32 PeekWord(Address address); - void PokeByte(AddressT address, u8 value); - void PokeShort(AddressT address, u16 value); - void PokeWord(AddressT address, u32 value); + void PokeByte(Address address, u8 value); + void PokeShort(Address address, u16 value); + void PokeWord(Address address, u32 value); CPU* GetCPU() { return cpu; } private: // TODO: version which takes Device::BasicType - Bus::Device* FindDeviceForAddress(AddressT address) const; + Bus::Device* FindDeviceForAddress(Address address) const; CPU* cpu; @@ -150,8 +150,8 @@ namespace riscv { // 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; + std::unordered_map mapped_devices; + std::unordered_map mmio_devices; }; } // namespace riscv diff --git a/native/projects/riscv/include/riscv/CPU.hpp b/native/projects/riscv/include/riscv/CPU.hpp index 8d5b0b1..5d3c41e 100644 --- a/native/projects/riscv/include/riscv/CPU.hpp +++ b/native/projects/riscv/include/riscv/CPU.hpp @@ -9,6 +9,7 @@ namespace riscv { bool Clocked() const override { return true; } void Clock() override; + /// Trap the CPU. Bus devices can call this. void Trap(u32 trapCode); inline void Trap(TrapCode trapCode) { Trap(static_cast(trapCode)); } @@ -16,6 +17,7 @@ namespace riscv { // TODO: Handlers for CSR read/write (if we need it?) + /// CPU state GeneralPurposeRegisters gpr; u32 pc; u32 mstatus; @@ -38,9 +40,9 @@ namespace riscv { private: /// 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 }; + + /// Set by [CPU::Trap] for the trap code. u32 trapCode { 0 }; u32 Step(u32 instCount); diff --git a/native/projects/riscv/include/riscv/CPUTypes.hpp b/native/projects/riscv/include/riscv/CPUTypes.hpp index 9d27db5..2ff2c3c 100644 --- a/native/projects/riscv/include/riscv/CPUTypes.hpp +++ b/native/projects/riscv/include/riscv/CPUTypes.hpp @@ -3,10 +3,10 @@ namespace riscv { - // Note that + /// Most possible trap codes. enum class TrapCode : u32 { InstructionAddressMisaligned, - InstructionAddressFault, + InstructionAccessFault, IllegalInstruction, Breakpoint, // I see what you did there ;) LoadAddressMisaligned, diff --git a/native/projects/riscv/include/riscv/Devices/ClntDevice.hpp b/native/projects/riscv/include/riscv/Devices/ClntDevice.hpp index 73e1854..2e8ae82 100644 --- a/native/projects/riscv/include/riscv/Devices/ClntDevice.hpp +++ b/native/projects/riscv/include/riscv/Devices/ClntDevice.hpp @@ -7,17 +7,17 @@ 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; + constexpr static Address BASE_ADDRESS = 0x11000000; - AddressT Base() const override { return BASE_ADDRESS; } - AddressT Size() const override { return 0xbfff; } + Address Base() const override { return BASE_ADDRESS; } + Address 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; + u32 Peek(Address address) override; + void Poke(Address address, u32 value) override; private: u32 timerCountHigh; diff --git a/native/projects/riscv/include/riscv/Devices/RamDevice.hpp b/native/projects/riscv/include/riscv/Devices/RamDevice.hpp index 60f08a0..66fb789 100644 --- a/native/projects/riscv/include/riscv/Devices/RamDevice.hpp +++ b/native/projects/riscv/include/riscv/Devices/RamDevice.hpp @@ -6,31 +6,31 @@ namespace riscv::devices { /// A block of RAM which can be used by the CPU. struct RamDevice : public Bus::MemoryDevice { - RamDevice(AddressT base, AddressT size); + RamDevice(Address base, Address size); virtual ~RamDevice(); // Implementation of Device interface - AddressT Base() const override; - AddressT Size() const override; + Address Base() const override; + Address Size() const override; - u8 PeekByte(AddressT address) override; - u16 PeekShort(AddressT address) override; - u32 PeekWord(AddressT address) override; + u8 PeekByte(Address address) override; + u16 PeekShort(Address address) override; + u32 PeekWord(Address address) override; - void PokeByte(AddressT address, u8 value) override; - void PokeShort(AddressT address, u16 value) override; - void PokeWord(AddressT address, u32 value) override; + void PokeByte(Address address, u8 value) override; + void PokeShort(Address address, u16 value) override; + void PokeWord(Address address, u32 value) override; private: /// helper used for implementing Peek/Poke API template - constexpr usize AddressToIndex(AddressT address) { + constexpr usize AddressToIndex(Address address) { return ((address - memoryBase) % memorySize) / sizeof(T); } - AddressT memoryBase {}; + Address memoryBase {}; u8* memory {}; usize memorySize {}; diff --git a/native/projects/riscv/include/riscv/Devices/SysconDevice.hpp b/native/projects/riscv/include/riscv/Devices/SysconDevice.hpp index 702dfa1..efc848c 100644 --- a/native/projects/riscv/include/riscv/Devices/SysconDevice.hpp +++ b/native/projects/riscv/include/riscv/Devices/SysconDevice.hpp @@ -7,14 +7,14 @@ 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; + constexpr static Address BASE_ADDRESS = 0x11100000; SysconDevice(System* system); - AddressT Size() const override { return sizeof(u32); } // I think this is right? + Address Size() const override { return sizeof(u32); } // I think this is right? - u32 Peek(AddressT address) override; - void Poke(AddressT address, u32 value) override; + u32 Peek(Address address) override; + void Poke(Address 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 3e08336..8b942ec 100644 --- a/native/projects/riscv/include/riscv/System.hpp +++ b/native/projects/riscv/include/riscv/System.hpp @@ -10,7 +10,7 @@ namespace riscv { struct System { /// 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); + static System* WithMemory(Address ramSize); ~System(); diff --git a/native/projects/riscv/include/riscv/Types.hpp b/native/projects/riscv/include/riscv/Types.hpp index 307aa9a..b996a74 100644 --- a/native/projects/riscv/include/riscv/Types.hpp +++ b/native/projects/riscv/include/riscv/Types.hpp @@ -6,8 +6,8 @@ namespace riscv { /// A type that can repressent address space or address space offsets. - using AddressT = u32; + using Address = u32; + - } // namespace riscv diff --git a/native/projects/riscv/src/Bus.cpp b/native/projects/riscv/src/Bus.cpp index 1bab13c..8777df3 100644 --- a/native/projects/riscv/src/Bus.cpp +++ b/native/projects/riscv/src/Bus.cpp @@ -61,7 +61,7 @@ namespace riscv { cpu->Clock(); } - u8 Bus::PeekByte(AddressT address) { + u8 Bus::PeekByte(Address address) { if(auto dev = FindDeviceForAddress(address); dev) return dev->Upcast()->PeekByte(address); else { @@ -70,7 +70,7 @@ namespace riscv { } } - u16 Bus::PeekShort(AddressT address) { + u16 Bus::PeekShort(Address address) { if(auto dev = FindDeviceForAddress(address); dev) return dev->Upcast()->PeekShort(address); else { @@ -79,7 +79,7 @@ namespace riscv { } } - u32 Bus::PeekWord(AddressT address) { + u32 Bus::PeekWord(Address address) { if(auto dev = FindDeviceForAddress(address); dev) { if(dev->IsA()) return dev->Upcast()->Peek(address); @@ -91,7 +91,7 @@ namespace riscv { } } - void Bus::PokeByte(AddressT address, u8 value) { + void Bus::PokeByte(Address address, u8 value) { if(auto dev = FindDeviceForAddress(address); dev) return dev->Upcast()->PokeByte(address, value); else { @@ -99,7 +99,7 @@ namespace riscv { } } - void Bus::PokeShort(AddressT address, u16 value) { + void Bus::PokeShort(Address address, u16 value) { if(auto dev = FindDeviceForAddress(address); dev) return dev->Upcast()->PokeShort(address, value); else { @@ -107,7 +107,7 @@ namespace riscv { } } - void Bus::PokeWord(AddressT address, u32 value) { + void Bus::PokeWord(Address address, u32 value) { if(auto dev = FindDeviceForAddress(address); dev) { if(dev->IsA()) dev->Upcast()->Poke(address, value); @@ -118,8 +118,8 @@ namespace riscv { } } - Bus::Device* Bus::FindDeviceForAddress(AddressT address) const { - auto try_find_device = [&](auto container, AddressT address) { + Bus::Device* Bus::FindDeviceForAddress(Address address) const { + auto try_find_device = [&](auto container, Address address) { return std::find_if(container.begin(), container.end(), [&](const auto& pair) { return // We can shorcut region checking if the requested addess matches base address. diff --git a/native/projects/riscv/src/CPU.cpp b/native/projects/riscv/src/CPU.cpp index 243ace4..9cdfb79 100644 --- a/native/projects/riscv/src/CPU.cpp +++ b/native/projects/riscv/src/CPU.cpp @@ -1,8 +1,14 @@ -#include +//! Portions of this code are copyright 2022 Charles Lohr (CNLohr). + #include +#include + +#include "riscv/CPUTypes.hpp" namespace riscv { + constexpr static Address RamImageOffset = 0x80000000; + void CPU::Clock() { // do the thing Step(1024); @@ -16,66 +22,547 @@ namespace riscv { // If this is because of an interrupt if(trapCode & 0x80000000) { // Set MIP.MEIP. - //mip |= 1 << 11; + // mip |= 1 << 11; // Always clear WFI bit. - extraflags &= ~4; + extraflags &= ~4; } } void CPU::TimerInterrupt() { // Set MIP.MTIP. mip |= 1 << 7; - //extraflags &= ~4; - //trapped = true; - //trapCode = 0x80000007; + // 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*/); + 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; + u32 rdid = 0; + u32 rval = 0; + u32 pc = this->pc; + u32 cycle = this->cyclel; + if(interruptsInFlight()) { Trap(0x80000007); } else { - u32 rval = 0; - u32 pc = this->pc; - u32 cycle = this->cyclel; - for(u32 iInst = 0; iInst < instCount; ++iInst) { - + auto ofs_pc = pc - RamImageOffset; cycle++; - if(pc & 3) { + if(ofs_pc & 3) { Trap(TrapCode::InstructionAddressMisaligned); break; } else { - auto ir = bus->PeekWord(pc); - auto rdid = (ir >> 7) & 0x1f; - //switch(ir & 0x7f) { + auto ir = bus->PeekWord(ofs_pc); + if(trapped) { + // Overwrite the trap that the bus generated. This might not really work out + // in practice but should at least kind-of replicate behaviour (our address + // space is emulated using the [Bus] class, so there is no heap write issue + // that could be caused by leaving it unbound). + Trap(TrapCode::InstructionAccessFault); + rval = ofs_pc + RamImageOffset; + break; + } - //} + rdid = (ir >> 7) & 0x1f; + + // Do the thing! + switch(ir & 0x7f) { + case 0x37: // lui + rval = (ir & 0xfffff000); + break; + + case 0x17: // auipc + rval = pc + (ir & 0xfffff000); + break; + + case 0x6f: { // jal + i32 reladdy = ((ir & 0x80000000) >> 11) | ((ir & 0x7fe00000) >> 20) | ((ir & 0x00100000) >> 9) | ((ir & 0x000ff000)); + + // Sign extend in this case + if(reladdy & 0x00100000) + reladdy |= 0xffe00000; + rval = pc + 4; + pc = pc + reladdy - 4; + break; + } + + case 0x67: { // jalr + u32 imm = ir >> 20; + i32 imm_se = imm | ((imm & 0x800) ? 0xfffff000 : 0); + rval = pc + 4; + pc = ((gpr[((ir >> 15) & 0x1f)] + imm_se) & ~1) - 4; + break; + } + + case 0x63: { // branch + u32 immm4 = ((ir & 0xf00) >> 7) | ((ir & 0x7e000000) >> 20) | ((ir & 0x80) << 4) | ((ir >> 31) << 12); + if(immm4 & 0x1000) + immm4 |= 0xffffe000; + i32 rs1 = gpr[(ir >> 15) & 0x1f]; + i32 rs2 = gpr[(ir >> 20) & 0x1f]; + immm4 = pc + immm4 - 4; + rdid = 0; + switch((ir >> 12) & 0x7) { + // BEQ, BNE, BLT, BGE, BLTU, BGEU + case 0: // BEQ + if(rs1 == rs2) + pc = immm4; + break; + case 1: // BNE + if(rs1 != rs2) + pc = immm4; + break; + + case 4: // BLT + if(rs1 < rs2) + pc = immm4; + break; + + case 5: // BGE + if(rs1 >= rs2) + pc = immm4; + break; + + case 6: // BLTU + if((uint32_t)rs1 < (uint32_t)rs2) + pc = immm4; + break; + + case 7: // BGEU + if((uint32_t)rs1 >= (uint32_t)rs2) + pc = immm4; + break; + default: + Trap(TrapCode::IllegalInstruction); + } + break; + } + + case 0x03: { // load + u32 rs1 = gpr[(ir >> 15) & 0x1f]; + u32 imm = ir >> 20; + i32 imm_se = imm | ((imm & 0x800) ? 0xfffff000 : 0); + u32 rsval = rs1 + imm_se; + + rsval -= RamImageOffset; + + switch((ir >> 12) & 0x7) { + // LB, LH, LW, LBU, LHU + case 0: + rval = (i8)bus->PeekByte(rsval); + break; + case 1: + rval = (i16)bus->PeekShort(rsval); + break; + case 2: + rval = bus->PeekWord(rsval); + break; + case 4: + rval = bus->PeekByte(rsval); + break; + case 5: + rval = bus->PeekShort(rsval); + break; + default: + Trap(TrapCode::IllegalInstruction); + break; + } + + if(trapped) { + rval = rsval + RamImageOffset; + } + break; + } + case 0x23: { // store + u32 rs1 = gpr[(ir >> 15) & 0x1f]; + u32 rs2 = gpr[(ir >> 20) & 0x1f]; + u32 addy = ((ir >> 7) & 0x1f) | ((ir & 0xfe000000) >> 20); + if(addy & 0x800) + addy |= 0xfffff000; + addy += rs1 - RamImageOffset; + rdid = 0; + + switch((ir >> 12) & 0x7) { + // SB, SH, SW + case 0: + bus->PokeByte(addy, rs2); + break; + case 1: + bus->PokeShort(addy, rs2); + break; + case 2: + bus->PokeWord(addy, rs2); + break; + default: + Trap(TrapCode::IllegalInstruction); + } + + if(trapped) { + rval = addy + RamImageOffset; + } + break; + } + + case 0x13: // op-imm + case 0x33: { // op + u32 imm = ir >> 20; + imm = imm | ((imm & 0x800) ? 0xfffff000 : 0); + u32 rs1 = gpr[(ir >> 15) & 0x1f]; + u32 is_reg = !!(ir & 0x20); + u32 rs2 = is_reg ? gpr[imm & 0x1f] : imm; + + if(is_reg && (ir & 0x02000000)) { + switch((ir >> 12) & 7) // 0x02000000 = RV32M + { + case 0: + rval = rs1 * rs2; + break; // MUL + + case 1: + rval = ((i64)((i32)rs1) * (i64)((i32)rs2)) >> 32; + break; // MULH + case 2: + rval = ((i64)((i32)rs1) * (u64)rs2) >> 32; + break; // MULHSU + case 3: + rval = ((u64)rs1 * (u64)rs2) >> 32; + break; // MULHU + + case 4: + if(rs2 == 0) + rval = -1; + else + rval = ((i32)rs1 == INT32_MIN && (i32)rs2 == -1) ? rs1 : ((i32)rs1 / (i32)rs2); + break; // DIV + case 5: + if(rs2 == 0) + rval = 0xffffffff; + else + rval = rs1 / rs2; + break; // DIVU + case 6: + if(rs2 == 0) + rval = rs1; + else + rval = ((i32)rs1 == INT32_MIN && (i32)rs2 == -1) ? 0 : ((u32)((i32)rs1 % (i32)rs2)); + break; // REM + case 7: + if(rs2 == 0) + rval = rs1; + else + rval = rs1 % rs2; + break; // REMU + } + } else { + // These could be either op-immediate or op commands. Be careful. + switch((ir >> 12) & 7) { + case 0: + rval = (is_reg && (ir & 0x40000000)) ? (rs1 - rs2) : (rs1 + rs2); + break; + case 1: + rval = rs1 << (rs2 & 0x1F); + break; + case 2: + rval = (i32)rs1 < (i32)rs2; + break; + case 3: + rval = rs1 < rs2; + break; + case 4: + rval = rs1 ^ rs2; + break; + case 5: + rval = (ir & 0x40000000) ? (((i32)rs1) >> (rs2 & 0x1F)) : (rs1 >> (rs2 & 0x1F)); + break; + case 6: + rval = rs1 | rs2; + break; + case 7: + rval = rs1 & rs2; + break; + } + } + break; + } + + case 0x0f: // 0b0001111 (ifenze?) + rdid = 0; // fencetype = (ir >> 12) & 0b111; We ignore fences in this impl. + break; + + case 0x73: { // Zifencei+Zicsr (0b1110011) + + u32 csrno = ir >> 20; + u32 microop = (ir >> 12) & 0x7; + // Zicsr + if((microop & 3)) { + int rs1imm = (ir >> 15) & 0x1f; + u32 rs1 = gpr[rs1imm]; + u32 writeval = rs1; + + // https://raw.githubusercontent.com/riscv/virtual-memory/main/specs/663-Svpbmt.pdf + // Generally, support for Zicsr + switch(csrno) { + case 0x340: + rval = mscratch; + break; + case 0x305: + rval = mtvec; + break; + case 0x304: + rval = mie; + break; + case 0xC00: + rval = cycle; + break; + case 0x344: + rval = mip; + break; + case 0x341: + rval = mepc; + break; + case 0x300: + rval = mstatus; + break; // mstatus + case 0x342: + rval = mcause; + break; + case 0x343: + rval = mtval; + break; + case 0xf11: + rval = 0xff0ff0ff; + break; // mvendorid + case 0x301: + rval = 0x40401101; + break; // misa (XLEN=32, IMA+X) + // case 0x3B0: rval = 0; break; //pmpaddr0 + // case 0x3a0: rval = 0; break; //pmpcfg0 + // case 0xf12: rval = 0x00000000; break; //marchid + // case 0xf13: rval = 0x00000000; break; //mimpid + // case 0xf14: rval = 0x00000000; break; //mhartid + default: + // MINIRV32_OTHERCSR_READ(csrno, rval); + break; + } + + switch(microop) { + case 1: + writeval = rs1; + break; // CSRRW + case 2: + writeval = rval | rs1; + break; // CSRRS + case 3: + writeval = rval & ~rs1; + break; // CSRRC + case 5: + writeval = rs1imm; + break; // CSRRWI + case 6: + writeval = rval | rs1imm; + break; // CSRRSI + case 7: + writeval = rval & ~rs1imm; + break; // CSRRCI + } + + switch(csrno) { + case 0x340: + mscratch = writeval; + break; + case 0x305: + mtvec = writeval; + break; + case 0x304: + mie = writeval; + break; + case 0x344: + mip = writeval; + break; + case 0x341: + mepc = writeval; + break; + case 0x300: + mstatus = writeval; + break; // mstatus + case 0x342: + mcause = writeval; + break; + case 0x343: + mtval = writeval; + break; + // case 0x3a0: break; //pmpcfg0 + // case 0x3B0: break; //pmpaddr0 + // case 0xf11: break; //mvendorid + // case 0xf12: break; //marchid + // case 0xf13: break; //mimpid + // case 0xf14: break; //mhartid + // case 0x301: break; //misa + default: + // MINIRV32_OTHERCSR_WRITE(csrno, writeval); + break; + } + } else if(microop == 0x0) { // "SYSTEM" 0b000 + rdid = 0; + if(csrno == 0x105) { // WFI (Wait for interrupts) + mstatus |= 8; // Enable interrupts + extraflags |= 4; // Set inernal WFI bit + this->pc = pc + 4; + return 1; + } else if(((csrno & 0xff) == 0x02)) { // MRET + // https://raw.githubusercontent.com/riscv/virtual-memory/main/specs/663-Svpbmt.pdf + // Table 7.6. MRET then in mstatus/mstatush sets MPV=0, MPP=0, + // MIE=MPIE, and MPIE=1. La + // Should also update mstatus to reflect correct mode. + u32 startmstatus = mstatus; + u32 startextraflags = extraflags; + mstatus = ((startmstatus & 0x80) >> 4) | ((startextraflags & 3) << 11) | 0x80; + extraflags = (startextraflags & ~3) | ((startmstatus >> 11) & 3); + pc = mepc - 4; + } else { + switch(csrno) { + case 0: + if(extraflags & 3) { + Trap(TrapCode::EnvCallMMode); + } else { + Trap(TrapCode::EnvCallUMode); + } + break; + case 1: // breakpoint + Trap(TrapCode::Breakpoint); + break; + default: + Trap(TrapCode::IllegalInstruction); + break; + } + } + } else + Trap(TrapCode::IllegalInstruction); + break; + } + case 0x2f: // RV32A (0b00101111) + { + u32 rs1 = gpr[(ir >> 15) & 0x1f]; + u32 rs2 = gpr[(ir >> 20) & 0x1f]; + u32 irmid = (ir >> 27) & 0x1f; + + // rs1 -= MINIRV32_RAM_IMAGE_OFFSET; + + rs1 -= RamImageOffset; + + // We don't implement load/store from UART or CLNT with RV32A here. + + rval = bus->PeekWord(rs1); + if(trapped) { + rval = rs1 + RamImageOffset; + break; + } + + u32 dowrite = 1; + switch(irmid) { + case 2: // LR.W (0b00010) + dowrite = 0; + extraflags = (extraflags & 0x07) | (rs1 << 3); + break; + case 3: // SC.W (0b00011) (Make sure we have a slot, and, it's + // valid) + rval = (extraflags >> 3 != (rs1 & 0x1fffffff)); // Validate that our reservation slot is OK. + dowrite = !rval; // Only write if slot is valid. + break; + case 1: + break; // AMOSWAP.W (0b00001) + case 0: + rs2 += rval; + break; // AMOADD.W (0b00000) + case 4: + rs2 ^= rval; + break; // AMOXOR.W (0b00100) + case 12: + rs2 &= rval; + break; // AMOAND.W (0b01100) + case 8: + rs2 |= rval; + break; // AMOOR.W (0b01000) + case 16: + rs2 = ((i32)rs2 < (i32)rval) ? rs2 : rval; + break; // AMOMIN.W (0b10000) + case 20: + rs2 = ((i32)rs2 > (i32)rval) ? rs2 : rval; + break; // AMOMAX.W (0b10100) + case 24: + rs2 = (rs2 < rval) ? rs2 : rval; + break; // AMOMINU.W (0b11000) + case 28: + rs2 = (rs2 > rval) ? rs2 : rval; + break; // AMOMAXU.W (0b11100) + default: + Trap(TrapCode::IllegalInstruction); + dowrite = 0; + break; // Not supported. + } + if(dowrite) + bus->PokeWord(rs1, rs2); + break; + } + default: + Trap(TrapCode::IllegalInstruction); + } } + // If some operation caused a trap break out after executing the instruction + if(trapped) + break; + + if(rdid) { + gpr[rdid] = rval; + } + + pc += 4; } } if(trapped) { mcause = trapCode; + + // If prefixed with 1 in MSB, it's an interrupt, not a trap. if(trapCode & 0x80000000) { mtval = 0; - // pc -= 4 + pc += 4; // PC needs to point to where the PC will return to. } else { - + if(trapCode > 5 && trapCode <= 8) + mtval = rval; + else + mtval = pc; } + mepc = pc; // TRICKY: The kernel advances mepc automatically. + // CSR( mstatus ) & 8 = MIE, & 0x80 = MPIE + // On an interrupt, the system moves current MIE into MPIE + mstatus = (mstatus & 0x08) << 4 | ((extraflags)&3) << 11; + pc = (mtvec - 4); + + // If trapping, always enter machine mode. + extraflags |= 3; + + // Reset trap flags + trapped = false; + trapCode = 0; + pc += 4; } + + if(cyclel > cycle) + cycleh++; + cyclel = cycle; + pc = pc; + return 0; } } // namespace riscv diff --git a/native/projects/riscv/src/Devices/ClntDevice.cpp b/native/projects/riscv/src/Devices/ClntDevice.cpp index 2b0edee..ea3546a 100644 --- a/native/projects/riscv/src/Devices/ClntDevice.cpp +++ b/native/projects/riscv/src/Devices/ClntDevice.cpp @@ -4,7 +4,7 @@ namespace riscv::devices { - constexpr static AddressT MSIP_ADDRESS = ClntDevice::BASE_ADDRESS, + constexpr static Address MSIP_ADDRESS = ClntDevice::BASE_ADDRESS, MATCHL_ADDRESS = ClntDevice::BASE_ADDRESS + 0x4000, MATCHH_ADDRESS = ClntDevice::BASE_ADDRESS + 0x4004, TIMERL_ADDRESS = ClntDevice::BASE_ADDRESS + 0xbff8, @@ -28,7 +28,7 @@ namespace riscv::devices { } } - u32 ClntDevice::Peek(AddressT address) { + u32 ClntDevice::Peek(Address address) { switch(address) { case TIMERL_ADDRESS: return timerCountLow; @@ -47,7 +47,7 @@ namespace riscv::devices { } } - void ClntDevice::Poke(AddressT address, u32 value) { + void ClntDevice::Poke(Address address, u32 value) { switch(address) { case MATCHL_ADDRESS: timerMatchLow = value; diff --git a/native/projects/riscv/src/Devices/RamDevice.cpp b/native/projects/riscv/src/Devices/RamDevice.cpp index 46d1dec..cc943d9 100644 --- a/native/projects/riscv/src/Devices/RamDevice.cpp +++ b/native/projects/riscv/src/Devices/RamDevice.cpp @@ -4,7 +4,7 @@ namespace riscv::devices { - RamDevice::RamDevice(AddressT base, AddressT size) : memoryBase(base), memorySize(size) { + RamDevice::RamDevice(Address base, Address size) : memoryBase(base), memorySize(size) { memory = new u8[size]; LUCORE_CHECK(memory, "Could not allocate buffer for memory device with size 0x{:08x}.", size); @@ -14,35 +14,35 @@ namespace riscv::devices { delete[] memory; } - AddressT RamDevice::Base() const { + Address RamDevice::Base() const { return memoryBase; } - AddressT RamDevice::Size() const { + Address RamDevice::Size() const { return memorySize; } - u8 RamDevice::PeekByte(AddressT address) { + u8 RamDevice::PeekByte(Address address) { return memory[AddressToIndex(address)]; } - u16 RamDevice::PeekShort(AddressT address) { + u16 RamDevice::PeekShort(Address address) { return std::bit_cast(memory)[AddressToIndex(address)]; } - u32 RamDevice::PeekWord(AddressT address) { + u32 RamDevice::PeekWord(Address address) { return std::bit_cast(memory)[AddressToIndex(address)]; } - void RamDevice::PokeByte(AddressT address, u8 value) { + void RamDevice::PokeByte(Address address, u8 value) { memory[AddressToIndex(address)] = value; } - void RamDevice::PokeShort(AddressT address, u16 value) { + void RamDevice::PokeShort(Address address, u16 value) { std::bit_cast(memory)[AddressToIndex(address)] = value; } - void RamDevice::PokeWord(AddressT address, u32 value) { + void RamDevice::PokeWord(Address address, u32 value) { std::bit_cast(memory)[AddressToIndex(address)] = value; } diff --git a/native/projects/riscv/src/Devices/SysconDevice.cpp b/native/projects/riscv/src/Devices/SysconDevice.cpp index 5b5f964..7a40abc 100644 --- a/native/projects/riscv/src/Devices/SysconDevice.cpp +++ b/native/projects/riscv/src/Devices/SysconDevice.cpp @@ -1,21 +1,22 @@ +#include #include #include -#include +// TODO: This device is largely a stub. It needs to be implemented! namespace riscv::devices { SysconDevice::SysconDevice(System* system) : system(system) { } - u32 SysconDevice::Peek(AddressT address) { - + u32 SysconDevice::Peek(Address 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); + void SysconDevice::Poke(Address address, u32 value) { + lucore::LogInfo("SYSCON({}) Poke @ 0x{:08x}: 0x{:08x}", static_cast(this), address, + value); /* if(address == BASE_ADDRESS) { if(value == 0x5555) diff --git a/native/projects/riscv_test_harness/main.cpp b/native/projects/riscv_test_harness/main.cpp index f9b5d7f..11a7f68 100644 --- a/native/projects/riscv_test_harness/main.cpp +++ b/native/projects/riscv_test_harness/main.cpp @@ -1,19 +1,16 @@ -#include -#include -#include - -#include "riscv/Types.hpp" +//! A test harness for testing the riscv library. +#include /// simple 16550 UART implementation struct SimpleUartDevice : public riscv::Bus::MmioDevice { - constexpr static riscv::AddressT BASE_ADDRESS = 0x10000000; + constexpr static riscv::Address BASE_ADDRESS = 0x10000000; - riscv::AddressT Base() const override { return BASE_ADDRESS; } + riscv::Address Base() const override { return BASE_ADDRESS; } - riscv::AddressT Size() const override { return 5; } + riscv::Address Size() const override { return 5; } // TODO: emulate properly - u32 Peek(riscv::AddressT address) override { + u32 Peek(riscv::Address address) override { switch(address) { case BASE_ADDRESS: break; @@ -24,7 +21,7 @@ struct SimpleUartDevice : public riscv::Bus::MmioDevice { return 0; } - void Poke(riscv::AddressT address, u32 value) override { + void Poke(riscv::Address address, u32 value) override { if(address == BASE_ADDRESS) { // write to data buffer printf("%c\n", value); } @@ -32,5 +29,7 @@ struct SimpleUartDevice : public riscv::Bus::MmioDevice { }; int main() { + auto system = riscv::System::WithMemory(128 * 1024); + system->AddDeviceToBus(new SimpleUartDevice); return 0; }