riscv: implement syscon + system hooks

This finally allows the test harness to cleanly shut down. Awesome!

This commit also reformats the whole project's native code. Oops!
This commit is contained in:
Lily Tsuru 2023-07-24 01:56:50 -04:00
parent 7af85f5601
commit 90e684e1e3
25 changed files with 211 additions and 375 deletions

View File

@ -16,6 +16,7 @@ AllowShortBlocksOnASingleLine: false
AllowShortFunctionsOnASingleLine: InlineOnly
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: true
BinPackArguments: true
BinPackParameters: true

View File

@ -84,8 +84,7 @@ namespace lcpu {
void SourceSink::OutputMessage(const lucore::Logger::MessageData& data) {
auto formatted =
std::format("[LCPU Native/{}] [{}] {}", lucore::Logger::SeverityToString(data.severity),
data.time, std::vformat(data.format, data.args));
std::format("[LCPU Native/{}] [{}] {}", lucore::Logger::SeverityToString(data.severity), data.time, std::vformat(data.format, data.args));
tier0::Msg("%s\n", formatted.c_str());
}

View File

@ -1,11 +1,10 @@
#include <GarrysMod/Lua/Interface.h>
#include "SourceSink.hpp"
#include <lucore/Assert.hpp>
LUA_FUNCTION(lcpu_native_test) {
#include "SourceSink.hpp"
LUA_FUNCTION(lcpu_native_test) {
}
GMOD_MODULE_OPEN() {
@ -13,7 +12,6 @@ GMOD_MODULE_OPEN() {
lucore::LogInfo("LCPU Native Module loading");
return 0;
}

View File

@ -9,20 +9,18 @@ namespace lucore {
} // namespace lucore
#ifndef NDEBUG
#define LUCORE_ASSERT(expr, fmt, ...) \
if(!(expr)) [[unlikely]] { \
auto msg = std::format("Assertion \"{}\" @ {}:{} failed with message: {}", #expr, \
__FILE__, __LINE__, std::format(fmt, ##__VA_ARGS__)); \
::lucore::ExitMsg(msg.c_str()); \
#define LUCORE_ASSERT(expr, fmt, ...) \
if(!(expr)) [[unlikely]] { \
auto msg = std::format("Assertion \"{}\" @ {}:{} failed with message: {}", #expr, __FILE__, __LINE__, std::format(fmt, ##__VA_ARGS__)); \
::lucore::ExitMsg(msg.c_str()); \
}
#else
#define LUCORE_ASSERT(expr, format, ...)
#endif
// CHECK() is always active, even in release builds
#define LUCORE_CHECK(expr, fmt, ...) \
if(!(expr)) [[unlikely]] { \
auto msg = std::format("Check \"{}\" @ {}:{} failed with message: {}", #expr, __FILE__, \
__LINE__, std::format(fmt, ##__VA_ARGS__)); \
::lucore::ExitMsg(msg.c_str()); \
#define LUCORE_CHECK(expr, fmt, ...) \
if(!(expr)) [[unlikely]] { \
auto msg = std::format("Check \"{}\" @ {}:{} failed with message: {}", #expr, __FILE__, __LINE__, std::format(fmt, ##__VA_ARGS__)); \
::lucore::ExitMsg(msg.c_str()); \
}

View File

@ -20,20 +20,16 @@ namespace lucore::detail {
/// Do *not* give this class invalid references.
template <class T>
struct OptionalRef {
constexpr OptionalRef() : ptr(nullptr) {
}
constexpr OptionalRef() : ptr(nullptr) {}
// trigger explicit null construction
constexpr OptionalRef(Nullref_t) : OptionalRef() {
}
constexpr OptionalRef(Nullref_t) : OptionalRef() {}
constexpr OptionalRef(T& ref) : ptr(&ref) {
}
constexpr OptionalRef(T& ref) : ptr(&ref) {}
// polymorphic downconstruction from another OptionalRef<U>
template <class U>
constexpr OptionalRef(const OptionalRef<U>& other) : ptr(&other.ptr) {
}
constexpr OptionalRef(const OptionalRef<U>& other) : ptr(&other.ptr) {}
constexpr T& Value() const {
// this is a CHECK() since allowing unchecked access in release builds is probably a
@ -42,22 +38,14 @@ namespace lucore::detail {
return *ptr;
}
constexpr bool HasValue() const {
return ptr != nullptr;
}
constexpr bool HasValue() const { return ptr != nullptr; }
constexpr T& operator*() const {
return Value();
}
constexpr T& operator*() const { return Value(); }
// unchecked access: DO NOT use this without checking beforehand
constexpr T* operator->() const {
return ptr;
}
constexpr T* operator->() const { return ptr; }
constexpr operator bool() const {
return HasValue();
}
constexpr operator bool() const { return HasValue(); }
private:
T* ptr {};

View File

@ -14,4 +14,4 @@ namespace lucore {
/// Attach the stdout logger sink to the global Lucore logger.
void LoggerAttachStdout();
}
} // namespace lucore

View File

@ -1,15 +1,15 @@
#include <cstdint>
//namespace lucore {
using u8 = std::uint8_t;
using i8 = std::int8_t;
using u16 = std::uint16_t;
using i16 = std::int16_t;
using u32 = std::uint32_t;
using i32 = std::int32_t;
using u64 = std::uint64_t;
using i64 = std::int64_t;
using usize = std::size_t;
using isize = std::intptr_t;
// namespace lucore {
using u8 = std::uint8_t;
using i8 = std::int8_t;
using u16 = std::uint16_t;
using i16 = std::int16_t;
using u32 = std::uint32_t;
using i32 = std::int32_t;
using u64 = std::uint64_t;
using i64 = std::int64_t;
using usize = std::size_t;
using isize = std::intptr_t;
//} // namespace lucore

View File

@ -23,14 +23,10 @@ namespace lucore {
if(severity < logLevel)
return;
MessageData data { .time = std::chrono::system_clock::now(),
.severity = severity,
.format = format,
.args = args };
MessageData data { .time = std::chrono::system_clock::now(), .severity = severity, .format = format, .args = args };
for(auto sink : sinks)
sink->OutputMessage(data);
}
} // namespace lucore

View File

@ -31,8 +31,7 @@ namespace lucore {
};
auto it = FputcIterator(data.severity < Logger::MessageSeverity::Error ? stdout : stderr);
std::format_to(it, "[Lucore/{}] [{}] {}\n", Logger::SeverityToString(data.severity),
data.time, std::vformat(data.format, data.args));
std::format_to(it, "[Lucore/{}] [{}] {}\n", Logger::SeverityToString(data.severity), data.time, std::vformat(data.format, data.args));
}
void LoggerAttachStdout() {

View File

@ -1,8 +1,8 @@
#pragma once
#include <lucore/Assert.hpp>
#include <riscv/Types.hpp>
#include <riscv/CPUTypes.hpp>
#include <riscv/Types.hpp>
#include <unordered_map>
#include <vector>
@ -28,9 +28,7 @@ namespace riscv {
virtual ~Device() = default;
virtual void Attached(Bus* bus) {
this->bus = bus;
}
virtual void Attached(Bus* bus) { this->bus = bus; }
virtual BasicType Type() const { return BasicType::Device; }
@ -46,11 +44,11 @@ namespace riscv {
template <class T>
constexpr bool IsA() {
if constexpr (std::is_same_v<T, CPU*>) {
if constexpr(std::is_same_v<T, CPU*>) {
return this->Type() == BasicType::Cpu;
} else if constexpr (std::is_same_v<T, Bus::MemoryDevice*>) {
} else if constexpr(std::is_same_v<T, Bus::MemoryDevice*>) {
return this->Type() == BasicType::PlainMemory;
} else if constexpr (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.
@ -63,7 +61,8 @@ namespace riscv {
LUCORE_ASSERT(IsA<T>(), "Upcast failure: this is not a T");
return static_cast<T>(this);
}
protected:
protected:
/// The bus this device is attached to.
Bus* bus;
};
@ -75,8 +74,8 @@ namespace riscv {
virtual BasicType Type() const override { return BasicType::PlainMemory; }
virtual Address Base() const = 0;
/// How many bytes does this device occupy of address space?
/// How many bytes does this device occupy of address space?
/// This should not change during execution.
virtual Address Size() const = 0;
@ -100,7 +99,7 @@ namespace riscv {
virtual Address Base() const = 0;
/// How many bytes does this device occupy of address space?
/// How many bytes does this device occupy of address space?
/// This should not change during execution.
virtual Address Size() const = 0;
@ -139,8 +138,7 @@ namespace riscv {
CPU* GetCPU() { return cpu; }
private:
// TODO: version which takes Device::BasicType
// TODO: version which takes Device::BasicType
Bus::Device* FindDeviceForAddress(Address address) const;
CPU* cpu;

View File

@ -6,7 +6,7 @@ namespace riscv {
/// The CPU core.
struct CPU : Bus::Device {
BasicType Type() const override { return BasicType::Cpu; }
BasicType Type() const override { return BasicType::Cpu; }
bool Clocked() const override { return true; }
void Clock() override;
@ -16,14 +16,11 @@ namespace riscv {
void TimerInterrupt();
constexpr CPU() {
Reset();
}
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
}

View File

@ -61,40 +61,8 @@ namespace riscv {
};
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"
};
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)];
}

View File

@ -12,19 +12,18 @@ namespace riscv::devices {
Address Base() const override { return BASE_ADDRESS; }
Address Size() const override { return 0xbfff; }
bool Clocked() const override { return true; }
bool Clocked() const override { return true; }
void Clock() override;
u32 Peek(Address address) override;
void Poke(Address address, u32 value) override;
private:
private:
u32 timerCountHigh;
u32 timerCountLow;
u32 timerMatchHigh;
u32 timerMatchLow;
u32 timerMatchLow;
};
}
} // namespace riscv::devices

View File

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

View File

@ -2,7 +2,9 @@
#include <riscv/Bus.hpp>
namespace riscv { struct System; }
namespace riscv {
struct System;
}
namespace riscv::devices {
/// RISC-V SYSCON device. This will later talk to the system to tell it things.
@ -16,7 +18,8 @@ namespace riscv::devices {
u32 Peek(Address address) override;
void Poke(Address address, u32 value) override;
private:
private:
System* system;
};
}
} // namespace riscv::devices

View File

@ -1,3 +1,4 @@
#include <functional> // use function_ref when we get c++23?
#include <riscv/Bus.hpp>
#include <riscv/CPU.hpp>
#include <riscv/Devices/ClntDevice.hpp>
@ -16,7 +17,8 @@ namespace riscv {
void Step();
// TODO: callbacks for SYSCON PowerOff and Reboot.
std::function<void()> OnPowerOff;
std::function<void()> OnReboot;
Bus* bus;
@ -27,6 +29,11 @@ namespace riscv {
devices::ClntDevice* clnt;
private:
friend struct devices::SysconDevice;
void SysconPowerOff();
void SysconReboot();
System() = default;
};

View File

@ -8,6 +8,4 @@ namespace riscv {
/// A type that can repressent address space or address space offsets.
using Address = u32;
} // namespace riscv

View File

@ -54,6 +54,7 @@ namespace riscv {
if((pc & 3)) {
Trap(TrapCode::InstructionAddressMisaligned);
rval = pc;
break;
} else {
auto ir = bus->PeekWord(pc);
@ -136,8 +137,7 @@ namespace riscv {
if((u32)rs1 >= (u32)rs2)
pc = immm4;
break;
default:
Trap(TrapCode::IllegalInstruction);
default: Trap(TrapCode::IllegalInstruction);
}
break;
}
@ -150,31 +150,20 @@ namespace riscv {
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;
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;
rval = rsval;
}
break;
}
case 0x23: { // store
u32 rs1 = gpr[(ir >> 15) & 0x1f];
u32 rs2 = gpr[(ir >> 20) & 0x1f];
@ -186,22 +175,14 @@ namespace riscv {
switch((ir >> 12) & 0x7) {
// SB, SH, SW
case 0:
bus->PokeByte(addy, rs2);
break;
case 1:
bus->PokeShort(addy, rs2);
break;
case 2:
// lucore::LogInfo("storeWord(0x{:08x}, 0x{:08x})", addy, rs2);
bus->PokeWord(addy, rs2);
break;
default:
Trap(TrapCode::IllegalInstruction);
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;
rval = addy;
}
break;
}
@ -217,19 +198,10 @@ namespace riscv {
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 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)
@ -259,30 +231,14 @@ namespace riscv {
} 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;
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;
@ -305,36 +261,16 @@ namespace riscv {
// 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 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)
@ -349,51 +285,23 @@ namespace riscv {
}
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
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 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
@ -434,15 +342,14 @@ namespace riscv {
case 1: // breakpoint
Trap(TrapCode::Breakpoint);
break;
default:
Trap(TrapCode::IllegalInstruction);
break;
default: Trap(TrapCode::IllegalInstruction); break;
}
}
} else
Trap(TrapCode::IllegalInstruction);
break;
}
case 0x2f: // RV32A (0b00101111)
{
u32 rs1 = gpr[(ir >> 15) & 0x1f];
@ -451,7 +358,7 @@ namespace riscv {
rval = bus->PeekWord(rs1);
if(trapped) {
rval = rs1; // + RamImageOffset;
rval = rs1;
break;
}
@ -466,32 +373,15 @@ namespace riscv {
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)
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;
@ -501,8 +391,7 @@ namespace riscv {
bus->PokeWord(rs1, rs2);
break;
}
default:
Trap(TrapCode::IllegalInstruction);
default: Trap(TrapCode::IllegalInstruction);
}
}

View File

@ -1,14 +1,12 @@
#include <lucore/Logger.hpp>
#include <riscv/Devices/ClntDevice.hpp>
#include <riscv/CPU.hpp>
#include <riscv/Devices/ClntDevice.hpp>
namespace riscv::devices {
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,
TIMERH_ADDRESS = ClntDevice::BASE_ADDRESS + 0xbffc;
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,
TIMERH_ADDRESS = ClntDevice::BASE_ADDRESS + 0xbffc;
void ClntDevice::Clock() {
// TODO: handle timer
@ -20,8 +18,7 @@ namespace riscv::devices {
timerCountHigh++;
timerCountLow = new_timer;
if((timerCountHigh > timerMatchHigh ||
(timerCountHigh == timerMatchHigh && timerCountLow == timerMatchLow)) &&
if((timerCountHigh > timerMatchHigh || (timerCountHigh == timerMatchHigh && timerCountLow == timerMatchLow)) &&
(timerMatchHigh || timerMatchLow)) {
// Fire the CLNT timer interrupt
bus->GetCPU()->TimerInterrupt();
@ -30,38 +27,28 @@ namespace riscv::devices {
u32 ClntDevice::Peek(Address address) {
switch(address) {
case TIMERL_ADDRESS:
return timerCountLow;
case TIMERL_ADDRESS: return timerCountLow;
case TIMERH_ADDRESS:
return timerCountHigh;
case TIMERH_ADDRESS: return timerCountHigh;
case MATCHL_ADDRESS:
return timerMatchLow;
case MATCHL_ADDRESS: return timerMatchLow;
case MATCHH_ADDRESS:
return timerMatchHigh;
case MATCHH_ADDRESS: return timerMatchHigh;
default:
return 0x0;
default: return 0x0;
}
}
void ClntDevice::Poke(Address address, u32 value) {
switch(address) {
case MATCHL_ADDRESS:
timerMatchLow = value;
break;
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;
default: lucore::LogInfo("CLNT({}) unhandled poke @ 0x{:08x} : 0x{:08x}", static_cast<void*>(this), address, value); break;
}
}

View File

@ -6,8 +6,7 @@ namespace riscv::devices {
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);
LUCORE_CHECK(memory, "Could not allocate buffer for memory device with size 0x{:08x}.", size);
}
RamDevice::~RamDevice() {

View File

@ -2,30 +2,22 @@
#include <riscv/Devices/SysconDevice.hpp>
#include <riscv/System.hpp>
// TODO: This device is largely a stub. It needs to be implemented!
namespace riscv::devices {
SysconDevice::SysconDevice(System* system) : system(system) {
}
u32 SysconDevice::Peek(Address address) {
lucore::LogInfo("SYSCON({}) Peek @ 0x{:08x}", static_cast<void*>(this), address);
return -1;
}
void SysconDevice::Poke(Address 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;
if(address == BASE_ADDRESS) {
if(value == 0x5555)
system->SysconPowerOff();
else if(value == 0x7777)
system->SysconReboot();
}
}
} // namespace riscv::devices

View File

@ -16,11 +16,15 @@ namespace riscv {
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;
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;
}
@ -36,4 +40,15 @@ namespace riscv {
// Later: handling for invalid cases!
}
void System::SysconPowerOff() {
if(OnPowerOff)
OnPowerOff();
}
void System::SysconReboot() {
if(OnReboot)
OnReboot();
bus->GetCPU()->Reset();
}
} // namespace riscv

View File

@ -1,10 +1,9 @@
//! A test harness for testing if the riscv library actually works.
#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>
#include <lucore/StdoutSink.hpp>
#include <riscv/System.hpp>
/// simple 16550 UART implementation
struct SimpleUartDevice : public riscv::Bus::MmioDevice {
@ -16,10 +15,8 @@ struct SimpleUartDevice : public riscv::Bus::MmioDevice {
u32 Peek(riscv::Address address) override {
switch(address) {
case BASE_ADDRESS:
return 0x60; // active, but no keyboard input
case BASE_ADDRESS + 5:
return '\0';
case BASE_ADDRESS: return 0x60; // active, but no keyboard input
case BASE_ADDRESS + 5: return '\0';
}
return 0;
@ -28,8 +25,6 @@ struct SimpleUartDevice : public riscv::Bus::MmioDevice {
void Poke(riscv::Address address, u32 value) override {
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);
}
}
@ -39,7 +34,7 @@ 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);
LUCORE_CHECK(system, "could not create system for some reason.");
@ -50,15 +45,19 @@ int main(int argc, char** argv) {
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);
std::fseek(fp, 0, SEEK_END);
auto len = std::ftell(fp);
std::fseek(fp, 0, SEEK_SET);
fread(system->ram->Raw(), 1, len, fp);
fclose(fp);
std::fread(system->ram->Raw(), 1, len, fp);
std::fclose(fp);
// Do the thing now!
while(true) {
// This allows the host program running under the test
// harness to tell us to shut down.
bool shouldExit = false;
system->OnPowerOff = [&shouldExit]() { shouldExit = true; };
while(!shouldExit) {
system->Step();
}

View File

@ -7,26 +7,28 @@ 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
ARCHFLAGS = -ffreestanding -fno-stack-protector -fdata-sections -ffunction-sections -march=rv32ima -mabi=ilp32
CCFLAGS = -g -Os $(ARCHFLAGS) -std=c18
CXXFLAGS = $(ARCHFLAGS) -g -Os -std=c++20 -fno-exceptions -fno-rtti
LDFLAGS = -T binary.ld -nostdlib -Wl,--gc-sections
OBJS := start.o main.o
OBJS = start.o \
main.o
.PHONY: all test clean
all: $(PROJECT).bin $(PROJECT).debug.txt
# this assumes the lcpu project build dir you're using is
# [lcpu repo root]/build
test: $(PROJECT).bin $(PROJECT).debug.txt
../../../../build/projects/riscv_test_harness/rvtest $<
clean:
rm $(PROJECT).elf $(PROJECT).bin $(PROJECT).debug.txt $(OBJS)
# Link rules
$(PROJECT).elf: $(OBJS)
$(CC) $(CCFLAGS) $(LDFLAGS) -o $@ $(OBJS)

View File

@ -10,6 +10,8 @@ uint32_t strlen(const char* str) {
return c - str;
}
#define SYSCON *(volatile uint32_t*)0x11100000
#define UART_BASE 0x10000000
#define UART_DATA *(volatile uint32_t*)UART_BASE
#define UART_STATUS UART_DATA
@ -28,16 +30,16 @@ 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) { \
#define COUNTER_TEST(var, max) \
for(int i = 0; i < max; ++i) { \
puts(#var " is (before modification): "); \
putc("0123456789"[var]); \
putc('\n'); \
\
var = i; \
putc("0123456789"[var]); \
putc('\n'); \
\
var = i; \
puts(#var " is (after modification): "); \
putc("0123456789"[var]); \
putc('\n'); \
putc("0123456789"[var]); \
putc('\n'); \
}
void main() {
@ -49,6 +51,10 @@ void main() {
COUNTER_TEST(bytevalue, 9);
#endif
// Shut down the test harness once we're done testing.
puts("Tests done, shutting down test harness...\n");
SYSCON = 0x5555;
// loop forever
for(;;);
// for(;;);
}