diff --git a/native/projects/riscv/README.md b/native/projects/riscv/README.md index 2234249..7ba875b 100644 --- a/native/projects/riscv/README.md +++ b/native/projects/riscv/README.md @@ -9,3 +9,7 @@ This is based off [cnlohr/mini-rv32ima](https://github.com/cnlohr/mini-rv32ima), - Moved *ALL* device and MMIO code to seperate interfaces Depends on lucore. + +# Usage + +TBD (if this is moved to another repo). See the riscv_test_harness project. diff --git a/native/projects/riscv/include/riscv/CPU.hpp b/native/projects/riscv/include/riscv/CPU.hpp index 5d3c41e..f7124be 100644 --- a/native/projects/riscv/include/riscv/CPU.hpp +++ b/native/projects/riscv/include/riscv/CPU.hpp @@ -6,6 +6,7 @@ namespace riscv { /// The CPU core. struct CPU : Bus::Device { + BasicType Type() const override { return BasicType::Cpu; } bool Clocked() const override { return true; } void Clock() override; @@ -15,6 +16,17 @@ namespace riscv { void TimerInterrupt(); + constexpr CPU() { + Reset(); + } + + constexpr void Reset() { + // Initalize some state. We're cool like that :) + pc = 0x80000000; + gpr[10] = 0x0; // HART id + extraflags |= 3; // Start in Machine mode + } + // TODO: Handlers for CSR read/write (if we need it?) /// CPU state diff --git a/native/projects/riscv/include/riscv/CPUTypes.hpp b/native/projects/riscv/include/riscv/CPUTypes.hpp index 2ff2c3c..6bbde18 100644 --- a/native/projects/riscv/include/riscv/CPUTypes.hpp +++ b/native/projects/riscv/include/riscv/CPUTypes.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include namespace riscv { @@ -25,41 +26,78 @@ namespace riscv { }; enum class Gpr : u8 { - X0, // zero - X1, - X2, - X3, - X4, - X5, - X6, - X7, - X8, - X9, - X10, - X11, - X12, - X13, - X14, - X15, - X16, - X17, - X18, - X19, - X20, - X21, - X22, - X23, - X24, - X25, - X26, - X27, - X28, - X29, - X30, - X31, - PC + Zero, + Ra, + Sp, + Gp, + Tp, + T0, + T1, + T2, + S0, // also `fp` + S1, + A0, + A1, + A2, + A3, + A4, + A5, + A6, + A7, + S2, + S3, + S4, + S5, + S6, + S7, + S8, + S9, + S10, + S11, + T3, + T4, + T5, + T6 }; + constexpr std::string_view RegName(Gpr gpr) { + std::string_view table[] = { + "zero", + "ra", + "sp", + "gp", + "tp", + "t0", + "t1", + "t2", + "s0", + "s1", + "a0", + "a1", + "a2", + "a3", + "a4", + "a5", + "a6", + "a7", + "s2", + "s3", + "s4", + "s5", + "s6", + "s7", + "s8", + "s9", + "s10", + "s11", + "t3", + "t4", + "t5", + "t6" + }; + return table[static_cast(gpr)]; + } + /// Container for GPRs which can be Statically Typed or not depending on use case. /// Pretty cool, huh? struct GeneralPurposeRegisters { diff --git a/native/projects/riscv/include/riscv/Devices/RamDevice.hpp b/native/projects/riscv/include/riscv/Devices/RamDevice.hpp index 66fb789..6463bfa 100644 --- a/native/projects/riscv/include/riscv/Devices/RamDevice.hpp +++ b/native/projects/riscv/include/riscv/Devices/RamDevice.hpp @@ -14,6 +14,9 @@ namespace riscv::devices { Address Base() const override; Address Size() const override; + u8* Raw() const { + return memory; + } u8 PeekByte(Address address) override; u16 PeekShort(Address address) override; diff --git a/native/projects/riscv/include/riscv/Devices/SysconDevice.hpp b/native/projects/riscv/include/riscv/Devices/SysconDevice.hpp index efc848c..1427ebb 100644 --- a/native/projects/riscv/include/riscv/Devices/SysconDevice.hpp +++ b/native/projects/riscv/include/riscv/Devices/SysconDevice.hpp @@ -11,6 +11,7 @@ namespace riscv::devices { SysconDevice(System* system); + Address Base() const override { return BASE_ADDRESS; } Address Size() const override { return sizeof(u32); } // I think this is right? u32 Peek(Address address) override; diff --git a/native/projects/riscv/include/riscv/System.hpp b/native/projects/riscv/include/riscv/System.hpp index 8b942ec..81038ba 100644 --- a/native/projects/riscv/include/riscv/System.hpp +++ b/native/projects/riscv/include/riscv/System.hpp @@ -1,8 +1,8 @@ #include #include +#include #include #include -#include namespace riscv { @@ -10,25 +10,13 @@ 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(Address ramSize); + static System* Create(Address ramSize); ~System(); - void AddDeviceToBus(Bus::Device* device); + void Step(); - /// returns false if the cpu broke execution - bool Step(); - - CPU* GetCPU(); - Bus* GetBus(); - - private: - /// How many Cycle() calls will the bus get - /// (also decides ipsRate) - u32 cycleRate; - - /// How many instructions will the CPU execute each step - u32 ipsRate; + // TODO: callbacks for SYSCON PowerOff and Reboot. Bus* bus; @@ -37,6 +25,9 @@ namespace riscv { devices::RamDevice* ram; devices::SysconDevice* syscon; devices::ClntDevice* clnt; + + private: + System() = default; }; } // namespace riscv diff --git a/native/projects/riscv/src/Bus.cpp b/native/projects/riscv/src/Bus.cpp index 8777df3..7f8206f 100644 --- a/native/projects/riscv/src/Bus.cpp +++ b/native/projects/riscv/src/Bus.cpp @@ -14,6 +14,8 @@ namespace riscv { if(!device) return false; + device->Attached(this); + if(device->IsA()) { // Return early to avoid putting the CPU pointer inside the devices vector. // We do not actually own the CPU. @@ -119,7 +121,7 @@ namespace riscv { } Bus::Device* Bus::FindDeviceForAddress(Address address) const { - auto try_find_device = [&](auto container, Address address) { + auto try_find_device = [&](const 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 9cdfb79..5df4097 100644 --- a/native/projects/riscv/src/CPU.cpp +++ b/native/projects/riscv/src/CPU.cpp @@ -3,11 +3,12 @@ #include #include -#include "riscv/CPUTypes.hpp" +#include namespace riscv { - constexpr static Address RamImageOffset = 0x80000000; + // Not needed + //constexpr static Address RamImageOffset = 0x80000000; void CPU::Clock() { // do the thing @@ -47,31 +48,35 @@ namespace riscv { u32 rdid = 0; u32 rval = 0; - u32 pc = this->pc; u32 cycle = this->cyclel; if(interruptsInFlight()) { Trap(0x80000007); } else { for(u32 iInst = 0; iInst < instCount; ++iInst) { - auto ofs_pc = pc - RamImageOffset; + rdid = 0; // force it to gpr 0 (zero), which is not writable cycle++; - if(ofs_pc & 3) { + //lucore::LogInfo("[CPU] pc @ 0x{:08x}", pc); + + if((pc & 3)) { + lucore::LogWarning("[CPU] misaligned jump target.. 0x{:08x}", pc); Trap(TrapCode::InstructionAddressMisaligned); break; } else { - auto ir = bus->PeekWord(ofs_pc); + auto ir = bus->PeekWord(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; + rval = pc; break; } + //lucore::LogInfo("[CPU] fetch 0x{:08x} 0x{:08x}", pc, ir); + rdid = (ir >> 7) & 0x1f; // Do the thing! @@ -91,6 +96,7 @@ namespace riscv { if(reladdy & 0x00100000) reladdy |= 0xffe00000; rval = pc + 4; + //lucore::LogInfo("j/al 0x{:08x}", pc + reladdy); pc = pc + reladdy - 4; break; } @@ -100,6 +106,7 @@ namespace riscv { i32 imm_se = imm | ((imm & 0x800) ? 0xfffff000 : 0); rval = pc + 4; pc = ((gpr[((ir >> 15) & 0x1f)] + imm_se) & ~1) - 4; + //lucore::LogInfo("jalr {}, 0x{:08x}", RegName(static_cast(((ir >> 15) & 0x1f))), ((gpr[((ir >> 15) & 0x1f)] + imm_se) & ~1) - 4); break; } @@ -153,8 +160,6 @@ namespace riscv { 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: @@ -178,7 +183,7 @@ namespace riscv { } if(trapped) { - rval = rsval + RamImageOffset; + rval = rsval;// + RamImageOffset; } break; } @@ -188,7 +193,7 @@ namespace riscv { u32 addy = ((ir >> 7) & 0x1f) | ((ir & 0xfe000000) >> 20); if(addy & 0x800) addy |= 0xfffff000; - addy += rs1 - RamImageOffset; + addy += rs1; rdid = 0; switch((ir >> 12) & 0x7) { @@ -200,6 +205,7 @@ namespace riscv { bus->PokeShort(addy, rs2); break; case 2: + //lucore::LogInfo("storeWord(0x{:08x}, 0x{:08x})", addy, rs2); bus->PokeWord(addy, rs2); break; default: @@ -207,7 +213,7 @@ namespace riscv { } if(trapped) { - rval = addy + RamImageOffset; + rval = addy;// + RamImageOffset; } break; } @@ -457,13 +463,13 @@ namespace riscv { // rs1 -= MINIRV32_RAM_IMAGE_OFFSET; - rs1 -= RamImageOffset; + //rs1 -= RamImageOffset; // We don't implement load/store from UART or CLNT with RV32A here. rval = bus->PeekWord(rs1); if(trapped) { - rval = rs1 + RamImageOffset; + rval = rs1;// + RamImageOffset; break; } @@ -523,6 +529,7 @@ namespace riscv { break; if(rdid) { + //lucore::LogInfo("writing register {} -> 0x{:08x}", RegName(static_cast(rdid)), rval); gpr[rdid] = rval; } diff --git a/native/projects/riscv/src/System.cpp b/native/projects/riscv/src/System.cpp index e69de29..a67cc98 100644 --- a/native/projects/riscv/src/System.cpp +++ b/native/projects/riscv/src/System.cpp @@ -0,0 +1,36 @@ +#include + +namespace riscv { + + System* System::Create(Address ramSize) { + auto* system = new System; + + system->bus = new Bus(); + system->cpu = new CPU(); + system->ram = new devices::RamDevice(0x80000000, ramSize); + system->clnt = new devices::ClntDevice(); + system->syscon = new devices::SysconDevice(system); + + system->cpu->Reset(); + + system->bus->AttachDevice(system->cpu); + system->bus->AttachDevice(system->clnt); + system->bus->AttachDevice(system->syscon); + system->bus->AttachDevice(system->ram); + + return system; + } + + System::~System() { + delete cpu; + delete bus; // the rest of the device pointers will be deleted by the bus. + } + + void System::Step() { + // Clock the bus, it'll do everything else. + bus->Clock(); + + // Later: handling for invalid cases! + } + +} // namespace riscv diff --git a/native/projects/riscv_test_harness/main.cpp b/native/projects/riscv_test_harness/main.cpp index 11a7f68..9ea1efb 100644 --- a/native/projects/riscv_test_harness/main.cpp +++ b/native/projects/riscv_test_harness/main.cpp @@ -1,5 +1,10 @@ -//! A test harness for testing the riscv library. +//! A test harness for testing if the riscv library actually works. #include +#include + +#include // I know, I know, but this is a test program. Yell later :) +#include +#include /// simple 16550 UART implementation struct SimpleUartDevice : public riscv::Bus::MmioDevice { @@ -7,29 +12,55 @@ struct SimpleUartDevice : public riscv::Bus::MmioDevice { riscv::Address Base() const override { return BASE_ADDRESS; } - riscv::Address Size() const override { return 5; } + riscv::Address Size() const override { return 12; } // for now - // TODO: emulate properly u32 Peek(riscv::Address address) override { switch(address) { case BASE_ADDRESS: - break; + return 0x60; // active, but no keyboard input case BASE_ADDRESS + 5: - break; + return '\0'; } return 0; } void Poke(riscv::Address address, u32 value) override { - if(address == BASE_ADDRESS) { // write to data buffer - printf("%c\n", value); + if(address == BASE_ADDRESS) { + char c = value & 0x000000ff; + //lucore::LogInfo("[UART] got data buffer poke of char {:02x} @ pc 0x{:08x}", c, bus->GetCPU()->pc); + //printf("%c", c); + fputc(c, stderr); } } }; -int main() { - auto system = riscv::System::WithMemory(128 * 1024); - system->AddDeviceToBus(new SimpleUartDevice); +int main(int argc, char** argv) { + lucore::LoggerAttachStdout(); + + LUCORE_CHECK(argc == 2, "this test harness expects one argument (the file to load into riscv memory and execute). got {} arguments", argc); + + // 128 KB of ram. Won't be enough to boot linux but should be good enough to test most baremetal apps + auto system = riscv::System::Create(128 * 1024); + + // Attach our UART device + system->bus->AttachDevice(new SimpleUartDevice); + + auto fp = std::fopen(argv[1], "rb"); + LUCORE_CHECK(fp, "could not open file \"{}\"", argv[1]); + + fseek(fp, 0, SEEK_END); + auto len = ftell(fp); + fseek(fp, 0, SEEK_SET); + + fread(system->ram->Raw(), 1, len, fp); + fclose(fp); + + // Do the thing now! + while(true) { + system->Step(); + } + + delete system; return 0; } diff --git a/native/projects/riscv_test_harness/test/Makefile b/native/projects/riscv_test_harness/test/Makefile new file mode 100644 index 0000000..0cb1e33 --- /dev/null +++ b/native/projects/riscv_test_harness/test/Makefile @@ -0,0 +1,51 @@ +PROJECT = test + +# where your rv32 toolchain is +TCPATH = /home/lily/bin/riscv/bin +PREFIX = $(TCPATH)/riscv32-unknown-elf + +CC = $(PREFIX)-gcc +CXX = $(PREFIX)-g++ + +CCFLAGS = -ffreestanding -fno-stack-protector +CCFLAGS += -static -static-libgcc -fdata-sections -ffunction-sections +CCFLAGS += -g -Os -march=rv32ima -mabi=ilp32 + +CXXFLAGS = $(CCFLAGS) -std=c++20 -fno-exceptions -fno-rtti + +LDFLAGS = -T binary.ld -nostdlib -Wl,--gc-sections + +OBJS := start.o main.o + +.PHONY: all test clean + +all: $(PROJECT).bin $(PROJECT).debug.txt + +test: $(PROJECT).bin $(PROJECT).debug.txt + ../../../../build/projects/riscv_test_harness/rvtest $< + +clean: + rm $(PROJECT).elf $(PROJECT).bin $(PROJECT).debug.txt $(OBJS) + +$(PROJECT).elf: $(OBJS) + $(CC) $(CCFLAGS) $(LDFLAGS) -o $@ $(OBJS) + +$(PROJECT).bin : $(PROJECT).elf + $(PREFIX)-objcopy $^ -O binary $@ + +$(PROJECT).debug.txt : $(PROJECT).elf + $(PREFIX)-objdump -t $^ > $@ + $(PREFIX)-objdump -S $^ >> $@ + +# Compile rules + +%.o: %.cpp + $(CXX) -c $(CXXFLAGS) $< -o $@ + +%.o: %.c + $(CC) -c $(CCFLAGS) $< -o $@ + +%.o: %.S + $(CC) -x assembler-with-cpp -march=rv32ima -mabi=ilp32 -c $< -o $@ + + diff --git a/native/projects/riscv_test_harness/test/README.md b/native/projects/riscv_test_harness/test/README.md new file mode 100644 index 0000000..bd65657 --- /dev/null +++ b/native/projects/riscv_test_harness/test/README.md @@ -0,0 +1,5 @@ +# what + +This is a simple bare-metal riscv program that is able to run on the test harness that does some testing. + +It is barebones, I know. diff --git a/native/projects/riscv_test_harness/test/binary.ld b/native/projects/riscv_test_harness/test/binary.ld new file mode 100644 index 0000000..076520e --- /dev/null +++ b/native/projects/riscv_test_harness/test/binary.ld @@ -0,0 +1,73 @@ +__heap_size = 0x1000; +__stack_size = 0x1000; + +ENTRY(_start) + +SECTIONS +{ + . = 0x80000000; + .text : ALIGN(16) { + __TEXT_BEGIN__ = .; + *(.initial_jump) + *(.entry.text) + *(.init.literal) + *(.init) + *(.text) + *(.literal .text .literal.* .text.* .stub) + *(.out_jump.literal.*) + *(.out_jump.*) + __TEXT_END__ = .; + } + + /* If we're on a newer compiler */ + /DISCARD/ : + { + *(.interp) + *(.dynsym) + *(.dynstr) + *(.header) + } : phdr + + .data : ALIGN(16) { + __DATA_BEGIN__ = .; + *(.rodata) + *(.rodata.*) + *(.gnu.linkonce.r.*) + *(.rodata1) + *(.dynsbss) + *(.gnu.linkonce.sb.*) + *(.scommon) + *(.gnu.linkonce.sb2.*) + *(.sbss) + *(.sbss.*) + *(.sbss2) + *(.sbss2.*) + *(.dynbss) + *(.data) + *(.data.*) + *(.got) + *(.got.*) + __DATA_END__ = .; + } + + .bss : ALIGN( 16 ) { + __BSS_BEGIN__ = .; + *(.bss) /* Tricky: BSS needs to be allocated but not sent. GCC Will not populate these for calculating data size */ + *(.bss.*) + __BSS_END__ = .; + } + + .heap : ALIGN( 16 ) { + _sheap = .; + . = . + __heap_size; + _eheap = .; + } + + .stack : ALIGN( 16 ) { + _estack = .; + . = . + __stack_size; + _sstack = .; + } +} + + diff --git a/native/projects/riscv_test_harness/test/main.c b/native/projects/riscv_test_harness/test/main.c new file mode 100644 index 0000000..d8e14dc --- /dev/null +++ b/native/projects/riscv_test_harness/test/main.c @@ -0,0 +1,54 @@ +// a simple test program +#include + +uint32_t strlen(const char* str) { + if(!str) + return 0; + const char* c = str; + while(*c++) + ; + return c - str; +} + +#define UART_BASE 0x10000000 +#define UART_DATA *(volatile uint32_t*)UART_BASE +#define UART_STATUS UART_DATA + +void putc(char c) { + UART_DATA = (uint32_t)c; +} + +__attribute__((noinline)) void puts(const char* str) { + const uint32_t length = strlen(str); + for(uint32_t i = 0; i < length; ++i) + putc(str[i]); +} + +static uint32_t value = 0; +static uint16_t shortvalue = 0; +static uint8_t bytevalue = 0; + +#define COUNTER_TEST(var, max) \ + for(int i = 0; i < max; ++i) { \ + puts(#var " is (before modification): "); \ + putc("0123456789"[var]); \ + putc('\n'); \ + \ + var = i; \ + puts(#var " is (after modification): "); \ + putc("0123456789"[var]); \ + putc('\n'); \ + } + +void main() { + puts("hello world I guess\n"); + +#if 1 + COUNTER_TEST(value, 9); + COUNTER_TEST(shortvalue, 9); + COUNTER_TEST(bytevalue, 9); +#endif + + // loop forever + for(;;); +} diff --git a/native/projects/riscv_test_harness/test/start.S b/native/projects/riscv_test_harness/test/start.S new file mode 100644 index 0000000..be687bc --- /dev/null +++ b/native/projects/riscv_test_harness/test/start.S @@ -0,0 +1,13 @@ +# Simple bare-metal RISCV startup code. + +.section .initial_jump +.global _start + +.extern main + +.align 4 +_start: + la sp, _sstack # set up C stack + addi sp,sp,-16 # ... + sw ra,12(sp) # ... + jal ra, main # jump to C code!