Compare commits

..

2 Commits

Author SHA1 Message Date
Lily Tsuru 7af85f5601 riscv: misc code cleanup (now I can actually focus on stuff!) 2023-07-24 00:20:40 -04:00
Lily Tsuru 878990a921 riscv: IT WORKS
finally. once i'm sure it's 100% working i can probably like, develop this addon finally
2023-07-24 00:01:39 -04:00
15 changed files with 398 additions and 100 deletions

View File

@ -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 - Moved *ALL* device and MMIO code to seperate interfaces
Depends on lucore. Depends on lucore.
# Usage
TBD (if this is moved to another repo). See the riscv_test_harness project.

View File

@ -6,6 +6,7 @@ namespace riscv {
/// The CPU core. /// The CPU core.
struct CPU : Bus::Device { struct CPU : Bus::Device {
BasicType Type() const override { return BasicType::Cpu; }
bool Clocked() const override { return true; } bool Clocked() const override { return true; }
void Clock() override; void Clock() override;
@ -15,6 +16,17 @@ namespace riscv {
void TimerInterrupt(); void TimerInterrupt();
constexpr CPU() {
Reset();
}
constexpr void Reset() {
// Initalize some state. We're cool like that :)
pc = 0x80000000;
gpr[Gpr::A0] = 0x0; // HART id
extraflags |= 3; // Start in Machine mode
}
// TODO: Handlers for CSR read/write (if we need it?) // TODO: Handlers for CSR read/write (if we need it?)
/// CPU state /// CPU state
@ -45,9 +57,7 @@ namespace riscv {
/// Set by [CPU::Trap] for the trap code. /// Set by [CPU::Trap] for the trap code.
u32 trapCode { 0 }; u32 trapCode { 0 };
u32 Step(u32 instCount); void Step(u32 instCount);
// todo: counters for chrono/inst count.
}; };
} // namespace riscv } // namespace riscv

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <riscv/Types.hpp> #include <riscv/Types.hpp>
#include <string_view>
namespace riscv { namespace riscv {
@ -25,41 +26,78 @@ namespace riscv {
}; };
enum class Gpr : u8 { enum class Gpr : u8 {
X0, // zero Zero,
X1, Ra,
X2, Sp,
X3, Gp,
X4, Tp,
X5, T0,
X6, T1,
X7, T2,
X8, S0, // also `fp`
X9, S1,
X10, A0,
X11, A1,
X12, A2,
X13, A3,
X14, A4,
X15, A5,
X16, A6,
X17, A7,
X18, S2,
X19, S3,
X20, S4,
X21, S5,
X22, S6,
X23, S7,
X24, S8,
X25, S9,
X26, S10,
X27, S11,
X28, T3,
X29, T4,
X30, T5,
X31, T6
PC
}; };
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<usize>(gpr)];
}
/// Container for GPRs which can be Statically Typed or not depending on use case. /// Container for GPRs which can be Statically Typed or not depending on use case.
/// Pretty cool, huh? /// Pretty cool, huh?
struct GeneralPurposeRegisters { struct GeneralPurposeRegisters {

View File

@ -14,6 +14,9 @@ namespace riscv::devices {
Address Base() const override; Address Base() const override;
Address Size() const override; Address Size() const override;
u8* Raw() const {
return memory;
}
u8 PeekByte(Address address) override; u8 PeekByte(Address address) override;
u16 PeekShort(Address address) override; u16 PeekShort(Address address) override;

View File

@ -11,6 +11,7 @@ namespace riscv::devices {
SysconDevice(System* system); SysconDevice(System* system);
Address Base() const override { return BASE_ADDRESS; }
Address 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(Address address) override; u32 Peek(Address address) override;

View File

@ -1,8 +1,8 @@
#include <riscv/Bus.hpp> #include <riscv/Bus.hpp>
#include <riscv/CPU.hpp> #include <riscv/CPU.hpp>
#include <riscv/Devices/ClntDevice.hpp>
#include <riscv/Devices/RamDevice.hpp> #include <riscv/Devices/RamDevice.hpp>
#include <riscv/Devices/SysconDevice.hpp> #include <riscv/Devices/SysconDevice.hpp>
#include <riscv/Devices/ClntDevice.hpp>
namespace riscv { namespace riscv {
@ -10,25 +10,13 @@ namespace riscv {
struct System { struct System {
/// Create a basic system with the basic periphials created. /// Create a basic system with the basic periphials created.
/// All other periphials should be managed by the creator of this System /// All other periphials should be managed by the creator of this System
static System* WithMemory(Address ramSize); static System* Create(Address ramSize);
~System(); ~System();
void AddDeviceToBus(Bus::Device* device); void Step();
/// returns false if the cpu broke execution // TODO: callbacks for SYSCON PowerOff and Reboot.
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;
Bus* bus; Bus* bus;
@ -37,6 +25,9 @@ namespace riscv {
devices::RamDevice* ram; devices::RamDevice* ram;
devices::SysconDevice* syscon; devices::SysconDevice* syscon;
devices::ClntDevice* clnt; devices::ClntDevice* clnt;
private:
System() = default;
}; };
} // namespace riscv } // namespace riscv

View File

@ -14,6 +14,8 @@ namespace riscv {
if(!device) if(!device)
return false; return false;
device->Attached(this);
if(device->IsA<CPU*>()) { if(device->IsA<CPU*>()) {
// Return early to avoid putting the CPU pointer inside the devices vector. // Return early to avoid putting the CPU pointer inside the devices vector.
// We do not actually own the CPU. // We do not actually own the CPU.
@ -119,7 +121,7 @@ namespace riscv {
} }
Bus::Device* Bus::FindDeviceForAddress(Address address) const { 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 std::find_if(container.begin(), container.end(), [&](const auto& pair) {
return return
// We can shorcut region checking if the requested addess matches base address. // We can shorcut region checking if the requested addess matches base address.

View File

@ -1,16 +1,12 @@
//! Portions of this code are copyright 2022 Charles Lohr (CNLohr). //! Portions of this code are copyright 2022 Charles Lohr (CNLohr),
//! from [mini-rv32ima](https://github.com/cnlohr/mini-rv32ima).
#include <riscv/Bus.hpp> #include <riscv/Bus.hpp>
#include <riscv/CPU.hpp> #include <riscv/CPU.hpp>
#include "riscv/CPUTypes.hpp"
namespace riscv { namespace riscv {
constexpr static Address RamImageOffset = 0x80000000;
void CPU::Clock() { void CPU::Clock() {
// do the thing
Step(1024); Step(1024);
} }
@ -36,39 +32,38 @@ namespace riscv {
// trapCode = 0x80000007; // trapCode = 0x80000007;
} }
u32 CPU::Step(u32 instCount) { void CPU::Step(u32 instCount) {
auto interruptsInFlight = [&]() { 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 // Don't run if waiting for an interrupt
if(extraflags & 4) if(extraflags & 4)
return 1; return;
u32 rdid = 0; u32 rdid = 0;
u32 rval = 0; u32 rval = 0;
u32 pc = this->pc;
u32 cycle = this->cyclel; u32 cycle = this->cyclel;
if(interruptsInFlight()) { if(interruptsInFlight()) {
Trap(0x80000007); Trap(0x80000007);
} else { } else {
for(u32 iInst = 0; iInst < instCount; ++iInst) { 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++; cycle++;
if(ofs_pc & 3) { if((pc & 3)) {
Trap(TrapCode::InstructionAddressMisaligned); Trap(TrapCode::InstructionAddressMisaligned);
break; break;
} else { } else {
auto ir = bus->PeekWord(ofs_pc); auto ir = bus->PeekWord(pc);
if(trapped) { if(trapped) {
// Overwrite the trap that the bus generated. This might not really work out // 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 // 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 // space is emulated using the [Bus] class, so there is no heap write issue
// that could be caused by leaving it unbound). // that could be caused by leaving it unbound).
Trap(TrapCode::InstructionAccessFault); Trap(TrapCode::InstructionAccessFault);
rval = ofs_pc + RamImageOffset; rval = pc;
break; break;
} }
@ -133,12 +128,12 @@ namespace riscv {
break; break;
case 6: // BLTU case 6: // BLTU
if((uint32_t)rs1 < (uint32_t)rs2) if((u32)rs1 < (u32)rs2)
pc = immm4; pc = immm4;
break; break;
case 7: // BGEU case 7: // BGEU
if((uint32_t)rs1 >= (uint32_t)rs2) if((u32)rs1 >= (u32)rs2)
pc = immm4; pc = immm4;
break; break;
default: default:
@ -153,8 +148,6 @@ namespace riscv {
i32 imm_se = imm | ((imm & 0x800) ? 0xfffff000 : 0); i32 imm_se = imm | ((imm & 0x800) ? 0xfffff000 : 0);
u32 rsval = rs1 + imm_se; u32 rsval = rs1 + imm_se;
rsval -= RamImageOffset;
switch((ir >> 12) & 0x7) { switch((ir >> 12) & 0x7) {
// LB, LH, LW, LBU, LHU // LB, LH, LW, LBU, LHU
case 0: case 0:
@ -178,7 +171,7 @@ namespace riscv {
} }
if(trapped) { if(trapped) {
rval = rsval + RamImageOffset; rval = rsval; // + RamImageOffset;
} }
break; break;
} }
@ -188,7 +181,7 @@ namespace riscv {
u32 addy = ((ir >> 7) & 0x1f) | ((ir & 0xfe000000) >> 20); u32 addy = ((ir >> 7) & 0x1f) | ((ir & 0xfe000000) >> 20);
if(addy & 0x800) if(addy & 0x800)
addy |= 0xfffff000; addy |= 0xfffff000;
addy += rs1 - RamImageOffset; addy += rs1;
rdid = 0; rdid = 0;
switch((ir >> 12) & 0x7) { switch((ir >> 12) & 0x7) {
@ -200,6 +193,7 @@ namespace riscv {
bus->PokeShort(addy, rs2); bus->PokeShort(addy, rs2);
break; break;
case 2: case 2:
// lucore::LogInfo("storeWord(0x{:08x}, 0x{:08x})", addy, rs2);
bus->PokeWord(addy, rs2); bus->PokeWord(addy, rs2);
break; break;
default: default:
@ -207,7 +201,7 @@ namespace riscv {
} }
if(trapped) { if(trapped) {
rval = addy + RamImageOffset; rval = addy; // + RamImageOffset;
} }
break; break;
} }
@ -417,7 +411,7 @@ namespace riscv {
mstatus |= 8; // Enable interrupts mstatus |= 8; // Enable interrupts
extraflags |= 4; // Set inernal WFI bit extraflags |= 4; // Set inernal WFI bit
this->pc = pc + 4; this->pc = pc + 4;
return 1; return;
} else if(((csrno & 0xff) == 0x02)) { // MRET } else if(((csrno & 0xff) == 0x02)) { // MRET
// https://raw.githubusercontent.com/riscv/virtual-memory/main/specs/663-Svpbmt.pdf // 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, // Table 7.6. MRET then in mstatus/mstatush sets MPV=0, MPP=0,
@ -455,15 +449,9 @@ namespace riscv {
u32 rs2 = gpr[(ir >> 20) & 0x1f]; u32 rs2 = gpr[(ir >> 20) & 0x1f];
u32 irmid = (ir >> 27) & 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); rval = bus->PeekWord(rs1);
if(trapped) { if(trapped) {
rval = rs1 + RamImageOffset; rval = rs1; // + RamImageOffset;
break; break;
} }
@ -522,10 +510,8 @@ namespace riscv {
if(trapped) if(trapped)
break; break;
if(rdid) { if(rdid)
gpr[rdid] = rval; gpr[rdid] = rval;
}
pc += 4; pc += 4;
} }
} }
@ -543,13 +529,11 @@ namespace riscv {
else else
mtval = pc; mtval = pc;
} }
mepc = pc; // TRICKY: The kernel advances mepc automatically. mepc = pc; // Interrupt handler will advance mepc
// CSR( mstatus ) & 8 = MIE, & 0x80 = MPIE
// On an interrupt, the system moves current MIE into MPIE
mstatus = (mstatus & 0x08) << 4 | ((extraflags)&3) << 11; mstatus = (mstatus & 0x08) << 4 | ((extraflags)&3) << 11;
pc = (mtvec - 4); pc = (mtvec - 4);
// If trapping, always enter machine mode. // Always enter machine mode when trapping.
extraflags |= 3; extraflags |= 3;
// Reset trap flags // Reset trap flags
@ -561,8 +545,6 @@ namespace riscv {
if(cyclel > cycle) if(cyclel > cycle)
cycleh++; cycleh++;
cyclel = cycle; cyclel = cycle;
pc = pc;
return 0;
} }
} // namespace riscv } // namespace riscv

View File

@ -0,0 +1,39 @@
#include <riscv/System.hpp>
namespace riscv {
System* System::Create(Address ramSize) {
auto* system = new System;
// create all the devices we require.
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);
// techinically this is done on construction but lets be hard about it
system->cpu->Reset();
// attach everything into the bus
if(!system->bus->AttachDevice(system->cpu)) return nullptr;
if(!system->bus->AttachDevice(system->clnt)) return nullptr;
if(!system->bus->AttachDevice(system->syscon)) return nullptr;
if(!system->bus->AttachDevice(system->ram)) return nullptr;
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

View File

@ -1,5 +1,10 @@
//! A test harness for testing the riscv library. //! A test harness for testing if the riscv library actually works.
#include <riscv/System.hpp> #include <riscv/System.hpp>
#include <lucore/StdoutSink.hpp>
#include <cstdio> // I know, I know, but this is a test program. Yell later :)
#include <lucore/Assert.hpp>
#include <lucore/Logger.hpp>
/// simple 16550 UART implementation /// simple 16550 UART implementation
struct SimpleUartDevice : public riscv::Bus::MmioDevice { struct SimpleUartDevice : public riscv::Bus::MmioDevice {
@ -7,29 +12,56 @@ struct SimpleUartDevice : public riscv::Bus::MmioDevice {
riscv::Address Base() const override { return BASE_ADDRESS; } 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 { u32 Peek(riscv::Address address) override {
switch(address) { switch(address) {
case BASE_ADDRESS: case BASE_ADDRESS:
break; return 0x60; // active, but no keyboard input
case BASE_ADDRESS + 5: case BASE_ADDRESS + 5:
break; return '\0';
} }
return 0; return 0;
} }
void Poke(riscv::Address address, u32 value) override { void Poke(riscv::Address address, u32 value) override {
if(address == BASE_ADDRESS) { // write to data buffer if(address == BASE_ADDRESS) {
printf("%c\n", value); 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() { int main(int argc, char** argv) {
auto system = riscv::System::WithMemory(128 * 1024); lucore::LoggerAttachStdout();
system->AddDeviceToBus(new SimpleUartDevice);
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);
LUCORE_CHECK(system, "could not create system for some reason.");
// 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; return 0;
} }

View File

@ -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 $@

View File

@ -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.

View File

@ -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 = .;
}
}

View File

@ -0,0 +1,54 @@
// a simple test program
#include <stdint.h>
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(;;);
}

View File

@ -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!