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 AllowShortFunctionsOnASingleLine: InlineOnly
AllowShortIfStatementsOnASingleLine: Never AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false AllowShortLoopsOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: true
BinPackArguments: true BinPackArguments: true
BinPackParameters: true BinPackParameters: true

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,8 @@
#pragma once #pragma once
#include <lucore/Assert.hpp> #include <lucore/Assert.hpp>
#include <riscv/Types.hpp>
#include <riscv/CPUTypes.hpp> #include <riscv/CPUTypes.hpp>
#include <riscv/Types.hpp>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
@ -28,9 +28,7 @@ namespace riscv {
virtual ~Device() = default; virtual ~Device() = default;
virtual void Attached(Bus* bus) { virtual void Attached(Bus* bus) { this->bus = bus; }
this->bus = bus;
}
virtual BasicType Type() const { return BasicType::Device; } virtual BasicType Type() const { return BasicType::Device; }
@ -46,11 +44,11 @@ namespace riscv {
template <class T> template <class T>
constexpr bool IsA() { constexpr bool IsA() {
if constexpr (std::is_same_v<T, CPU*>) { if constexpr(std::is_same_v<T, CPU*>) {
return this->Type() == BasicType::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; 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; return this->Type() == BasicType::Mmio;
} else { } else {
// Invalid types should do this. // Invalid types should do this.
@ -63,6 +61,7 @@ namespace riscv {
LUCORE_ASSERT(IsA<T>(), "Upcast failure: this is not a T"); LUCORE_ASSERT(IsA<T>(), "Upcast failure: this is not a T");
return static_cast<T>(this); return static_cast<T>(this);
} }
protected: protected:
/// The bus this device is attached to. /// The bus this device is attached to.
Bus* bus; Bus* bus;
@ -139,7 +138,6 @@ namespace riscv {
CPU* GetCPU() { return cpu; } CPU* GetCPU() { return cpu; }
private: private:
// TODO: version which takes Device::BasicType // TODO: version which takes Device::BasicType
Bus::Device* FindDeviceForAddress(Address address) const; Bus::Device* FindDeviceForAddress(Address address) const;

View File

@ -16,14 +16,11 @@ namespace riscv {
void TimerInterrupt(); void TimerInterrupt();
constexpr CPU() { constexpr CPU() { Reset(); }
Reset();
}
constexpr void Reset() { constexpr void Reset() {
// Initalize some state. We're cool like that :) // Initalize some state. We're cool like that :)
pc = 0x80000000; pc = 0x80000000;
gpr[Gpr::A0] = 0x0; // HART id
extraflags |= 3; // Start in Machine mode extraflags |= 3; // Start in Machine mode
} }

View File

@ -61,40 +61,8 @@ namespace riscv {
}; };
constexpr std::string_view RegName(Gpr gpr) { constexpr std::string_view RegName(Gpr gpr) {
std::string_view table[] = { std::string_view table[] = { "zero", "ra", "sp", "gp", "tp", "t0", "t1", "t2", "s0", "s1", "a0", "a1", "a2", "a3", "a4", "a5",
"zero", "a6", "a7", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6" };
"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)]; return table[static_cast<usize>(gpr)];
} }

View File

@ -15,7 +15,6 @@ namespace riscv::devices {
bool Clocked() const override { return true; } bool Clocked() const override { return true; }
void Clock() override; void Clock() override;
u32 Peek(Address address) override; u32 Peek(Address address) override;
void Poke(Address address, u32 value) override; void Poke(Address address, u32 value) override;
@ -27,4 +26,4 @@ namespace riscv::devices {
u32 timerMatchLow; u32 timerMatchLow;
}; };
} } // namespace riscv::devices

View File

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

View File

@ -2,7 +2,9 @@
#include <riscv/Bus.hpp> #include <riscv/Bus.hpp>
namespace riscv { struct System; } namespace riscv {
struct System;
}
namespace riscv::devices { namespace riscv::devices {
/// RISC-V SYSCON device. This will later talk to the system to tell it things. /// 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; u32 Peek(Address address) override;
void Poke(Address address, u32 value) override; void Poke(Address address, u32 value) override;
private: private:
System* system; 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/Bus.hpp>
#include <riscv/CPU.hpp> #include <riscv/CPU.hpp>
#include <riscv/Devices/ClntDevice.hpp> #include <riscv/Devices/ClntDevice.hpp>
@ -16,7 +17,8 @@ namespace riscv {
void Step(); void Step();
// TODO: callbacks for SYSCON PowerOff and Reboot. std::function<void()> OnPowerOff;
std::function<void()> OnReboot;
Bus* bus; Bus* bus;
@ -27,6 +29,11 @@ namespace riscv {
devices::ClntDevice* clnt; devices::ClntDevice* clnt;
private: private:
friend struct devices::SysconDevice;
void SysconPowerOff();
void SysconReboot();
System() = default; System() = default;
}; };

View File

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

View File

@ -54,6 +54,7 @@ namespace riscv {
if((pc & 3)) { if((pc & 3)) {
Trap(TrapCode::InstructionAddressMisaligned); Trap(TrapCode::InstructionAddressMisaligned);
rval = pc;
break; break;
} else { } else {
auto ir = bus->PeekWord(pc); auto ir = bus->PeekWord(pc);
@ -136,8 +137,7 @@ namespace riscv {
if((u32)rs1 >= (u32)rs2) if((u32)rs1 >= (u32)rs2)
pc = immm4; pc = immm4;
break; break;
default: default: Trap(TrapCode::IllegalInstruction);
Trap(TrapCode::IllegalInstruction);
} }
break; break;
} }
@ -150,31 +150,20 @@ namespace riscv {
switch((ir >> 12) & 0x7) { switch((ir >> 12) & 0x7) {
// LB, LH, LW, LBU, LHU // LB, LH, LW, LBU, LHU
case 0: case 0: rval = (i8)bus->PeekByte(rsval); break;
rval = (i8)bus->PeekByte(rsval); case 1: rval = (i16)bus->PeekShort(rsval); break;
break; case 2: rval = bus->PeekWord(rsval); break;
case 1: case 4: rval = bus->PeekByte(rsval); break;
rval = (i16)bus->PeekShort(rsval); case 5: rval = bus->PeekShort(rsval); break;
break; default: Trap(TrapCode::IllegalInstruction); 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) { if(trapped) {
rval = rsval; // + RamImageOffset; rval = rsval;
} }
break; break;
} }
case 0x23: { // store case 0x23: { // store
u32 rs1 = gpr[(ir >> 15) & 0x1f]; u32 rs1 = gpr[(ir >> 15) & 0x1f];
u32 rs2 = gpr[(ir >> 20) & 0x1f]; u32 rs2 = gpr[(ir >> 20) & 0x1f];
@ -186,22 +175,14 @@ namespace riscv {
switch((ir >> 12) & 0x7) { switch((ir >> 12) & 0x7) {
// SB, SH, SW // SB, SH, SW
case 0: case 0: bus->PokeByte(addy, rs2); break;
bus->PokeByte(addy, rs2); case 1: bus->PokeShort(addy, rs2); break;
break; case 2: bus->PokeWord(addy, rs2); break;
case 1: default: Trap(TrapCode::IllegalInstruction);
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);
} }
if(trapped) { if(trapped) {
rval = addy; // + RamImageOffset; rval = addy;
} }
break; break;
} }
@ -217,19 +198,10 @@ namespace riscv {
if(is_reg && (ir & 0x02000000)) { if(is_reg && (ir & 0x02000000)) {
switch((ir >> 12) & 7) // 0x02000000 = RV32M switch((ir >> 12) & 7) // 0x02000000 = RV32M
{ {
case 0: case 0: rval = rs1 * rs2; break; // MUL
rval = rs1 * rs2; case 1: rval = ((i64)((i32)rs1) * (i64)((i32)rs2)) >> 32; break; // MULH
break; // MUL case 2: rval = ((i64)((i32)rs1) * (u64)rs2) >> 32; break; // MULHSU
case 3: rval = ((u64)rs1 * (u64)rs2) >> 32; break; // MULHU
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: case 4:
if(rs2 == 0) if(rs2 == 0)
@ -259,30 +231,14 @@ namespace riscv {
} else { } else {
// These could be either op-immediate or op commands. Be careful. // These could be either op-immediate or op commands. Be careful.
switch((ir >> 12) & 7) { switch((ir >> 12) & 7) {
case 0: case 0: rval = (is_reg && (ir & 0x40000000)) ? (rs1 - rs2) : (rs1 + rs2); break;
rval = (is_reg && (ir & 0x40000000)) ? (rs1 - rs2) : (rs1 + rs2); case 1: rval = rs1 << (rs2 & 0x1F); break;
break; case 2: rval = (i32)rs1 < (i32)rs2; break;
case 1: case 3: rval = rs1 < rs2; break;
rval = rs1 << (rs2 & 0x1F); case 4: rval = rs1 ^ rs2; break;
break; case 5: rval = (ir & 0x40000000) ? (((i32)rs1) >> (rs2 & 0x1F)) : (rs1 >> (rs2 & 0x1F)); break;
case 2: case 6: rval = rs1 | rs2; break;
rval = (i32)rs1 < (i32)rs2; case 7: rval = rs1 & rs2; break;
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; break;
@ -305,36 +261,16 @@ namespace riscv {
// https://raw.githubusercontent.com/riscv/virtual-memory/main/specs/663-Svpbmt.pdf // https://raw.githubusercontent.com/riscv/virtual-memory/main/specs/663-Svpbmt.pdf
// Generally, support for Zicsr // Generally, support for Zicsr
switch(csrno) { switch(csrno) {
case 0x340: case 0x340: rval = mscratch; break;
rval = mscratch; case 0x305: rval = mtvec; break;
break; case 0x304: rval = mie; break;
case 0x305: case 0xC00: rval = cycle; break;
rval = mtvec; case 0x344: rval = mip; break;
break; case 0x341: rval = mepc; break;
case 0x304: case 0x300: rval = mstatus; break; // mstatus
rval = mie; case 0x342: rval = mcause; break;
break; case 0x343: rval = mtval; break;
case 0xC00: case 0xf11: rval = 0xff0ff0ff; break; // mvendorid
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: case 0x301:
rval = 0x40401101; rval = 0x40401101;
break; // misa (XLEN=32, IMA+X) break; // misa (XLEN=32, IMA+X)
@ -349,51 +285,23 @@ namespace riscv {
} }
switch(microop) { switch(microop) {
case 1: case 1: writeval = rs1; break; // CSRRW
writeval = rs1; case 2: writeval = rval | rs1; break; // CSRRS
break; // CSRRW case 3: writeval = rval & ~rs1; break; // CSRRC
case 2: case 5: writeval = rs1imm; break; // CSRRWI
writeval = rval | rs1; case 6: writeval = rval | rs1imm; break; // CSRRSI
break; // CSRRS case 7: writeval = rval & ~rs1imm; break; // CSRRCI
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) { switch(csrno) {
case 0x340: case 0x340: mscratch = writeval; break;
mscratch = writeval; case 0x305: mtvec = writeval; break;
break; case 0x304: mie = writeval; break;
case 0x305: case 0x344: mip = writeval; break;
mtvec = writeval; case 0x341: mepc = writeval; break;
break; case 0x300: mstatus = writeval; break; // mstatus
case 0x304: case 0x342: mcause = writeval; break;
mie = writeval; case 0x343: mtval = writeval; break;
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 0x3a0: break; //pmpcfg0
// case 0x3B0: break; //pmpaddr0 // case 0x3B0: break; //pmpaddr0
// case 0xf11: break; //mvendorid // case 0xf11: break; //mvendorid
@ -434,15 +342,14 @@ namespace riscv {
case 1: // breakpoint case 1: // breakpoint
Trap(TrapCode::Breakpoint); Trap(TrapCode::Breakpoint);
break; break;
default: default: Trap(TrapCode::IllegalInstruction); break;
Trap(TrapCode::IllegalInstruction);
break;
} }
} }
} else } else
Trap(TrapCode::IllegalInstruction); Trap(TrapCode::IllegalInstruction);
break; break;
} }
case 0x2f: // RV32A (0b00101111) case 0x2f: // RV32A (0b00101111)
{ {
u32 rs1 = gpr[(ir >> 15) & 0x1f]; u32 rs1 = gpr[(ir >> 15) & 0x1f];
@ -451,7 +358,7 @@ namespace riscv {
rval = bus->PeekWord(rs1); rval = bus->PeekWord(rs1);
if(trapped) { if(trapped) {
rval = rs1; // + RamImageOffset; rval = rs1;
break; break;
} }
@ -466,32 +373,15 @@ namespace riscv {
rval = (extraflags >> 3 != (rs1 & 0x1fffffff)); // Validate that our reservation slot is OK. rval = (extraflags >> 3 != (rs1 & 0x1fffffff)); // Validate that our reservation slot is OK.
dowrite = !rval; // Only write if slot is valid. dowrite = !rval; // Only write if slot is valid.
break; break;
case 1: case 1: break; // AMOSWAP.W (0b00001)
break; // AMOSWAP.W (0b00001) case 0: rs2 += rval; break; // AMOADD.W (0b00000)
case 0: case 4: rs2 ^= rval; break; // AMOXOR.W (0b00100)
rs2 += rval; case 12: rs2 &= rval; break; // AMOAND.W (0b01100)
break; // AMOADD.W (0b00000) case 8: rs2 |= rval; break; // AMOOR.W (0b01000)
case 4: case 16: rs2 = ((i32)rs2 < (i32)rval) ? rs2 : rval; break; // AMOMIN.W (0b10000)
rs2 ^= rval; case 20: rs2 = ((i32)rs2 > (i32)rval) ? rs2 : rval; break; // AMOMAX.W (0b10100)
break; // AMOXOR.W (0b00100) case 24: rs2 = (rs2 < rval) ? rs2 : rval; break; // AMOMINU.W (0b11000)
case 12: case 28: rs2 = (rs2 > rval) ? rs2 : rval; break; // AMOMAXU.W (0b11100)
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: default:
Trap(TrapCode::IllegalInstruction); Trap(TrapCode::IllegalInstruction);
dowrite = 0; dowrite = 0;
@ -501,8 +391,7 @@ namespace riscv {
bus->PokeWord(rs1, rs2); bus->PokeWord(rs1, rs2);
break; break;
} }
default: default: Trap(TrapCode::IllegalInstruction);
Trap(TrapCode::IllegalInstruction);
} }
} }

View File

@ -1,13 +1,11 @@
#include <lucore/Logger.hpp> #include <lucore/Logger.hpp>
#include <riscv/Devices/ClntDevice.hpp>
#include <riscv/CPU.hpp> #include <riscv/CPU.hpp>
#include <riscv/Devices/ClntDevice.hpp>
namespace riscv::devices { namespace riscv::devices {
constexpr static Address MSIP_ADDRESS = ClntDevice::BASE_ADDRESS, constexpr static Address MSIP_ADDRESS = ClntDevice::BASE_ADDRESS, MATCHL_ADDRESS = ClntDevice::BASE_ADDRESS + 0x4000,
MATCHL_ADDRESS = ClntDevice::BASE_ADDRESS + 0x4000, MATCHH_ADDRESS = ClntDevice::BASE_ADDRESS + 0x4004, TIMERL_ADDRESS = ClntDevice::BASE_ADDRESS + 0xbff8,
MATCHH_ADDRESS = ClntDevice::BASE_ADDRESS + 0x4004,
TIMERL_ADDRESS = ClntDevice::BASE_ADDRESS + 0xbff8,
TIMERH_ADDRESS = ClntDevice::BASE_ADDRESS + 0xbffc; TIMERH_ADDRESS = ClntDevice::BASE_ADDRESS + 0xbffc;
void ClntDevice::Clock() { void ClntDevice::Clock() {
@ -20,8 +18,7 @@ namespace riscv::devices {
timerCountHigh++; timerCountHigh++;
timerCountLow = new_timer; timerCountLow = new_timer;
if((timerCountHigh > timerMatchHigh || if((timerCountHigh > timerMatchHigh || (timerCountHigh == timerMatchHigh && timerCountLow == timerMatchLow)) &&
(timerCountHigh == timerMatchHigh && timerCountLow == timerMatchLow)) &&
(timerMatchHigh || timerMatchLow)) { (timerMatchHigh || timerMatchLow)) {
// Fire the CLNT timer interrupt // Fire the CLNT timer interrupt
bus->GetCPU()->TimerInterrupt(); bus->GetCPU()->TimerInterrupt();
@ -30,38 +27,28 @@ namespace riscv::devices {
u32 ClntDevice::Peek(Address address) { u32 ClntDevice::Peek(Address address) {
switch(address) { switch(address) {
case TIMERL_ADDRESS: case TIMERL_ADDRESS: return timerCountLow;
return timerCountLow;
case TIMERH_ADDRESS: case TIMERH_ADDRESS: return timerCountHigh;
return timerCountHigh;
case MATCHL_ADDRESS: case MATCHL_ADDRESS: return timerMatchLow;
return timerMatchLow;
case MATCHH_ADDRESS: case MATCHH_ADDRESS: return timerMatchHigh;
return timerMatchHigh;
default: default: return 0x0;
return 0x0;
} }
} }
void ClntDevice::Poke(Address address, u32 value) { void ClntDevice::Poke(Address address, u32 value) {
switch(address) { switch(address) {
case MATCHL_ADDRESS: case MATCHL_ADDRESS: timerMatchLow = value; break;
timerMatchLow = value;
break;
case MATCHH_ADDRESS: case MATCHH_ADDRESS:
timerMatchHigh = value; timerMatchHigh = value;
break; break;
// ? // ?
default: default: lucore::LogInfo("CLNT({}) unhandled poke @ 0x{:08x} : 0x{:08x}", static_cast<void*>(this), address, value); break;
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) { RamDevice::RamDevice(Address base, Address size) : memoryBase(base), memorySize(size) {
memory = new u8[size]; memory = new u8[size];
LUCORE_CHECK(memory, "Could not allocate buffer for memory device with size 0x{:08x}.", LUCORE_CHECK(memory, "Could not allocate buffer for memory device with size 0x{:08x}.", size);
size);
} }
RamDevice::~RamDevice() { RamDevice::~RamDevice() {

View File

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

View File

@ -16,10 +16,14 @@ namespace riscv {
system->cpu->Reset(); system->cpu->Reset();
// attach everything into the bus // attach everything into the bus
if(!system->bus->AttachDevice(system->cpu)) return nullptr; if(!system->bus->AttachDevice(system->cpu))
if(!system->bus->AttachDevice(system->clnt)) return nullptr; return nullptr;
if(!system->bus->AttachDevice(system->syscon)) return nullptr; if(!system->bus->AttachDevice(system->clnt))
if(!system->bus->AttachDevice(system->ram)) return nullptr; return nullptr;
if(!system->bus->AttachDevice(system->syscon))
return nullptr;
if(!system->bus->AttachDevice(system->ram))
return nullptr;
return system; return system;
} }
@ -36,4 +40,15 @@ namespace riscv {
// Later: handling for invalid cases! // Later: handling for invalid cases!
} }
void System::SysconPowerOff() {
if(OnPowerOff)
OnPowerOff();
}
void System::SysconReboot() {
if(OnReboot)
OnReboot();
bus->GetCPU()->Reset();
}
} // namespace riscv } // namespace riscv

View File

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

View File

@ -7,26 +7,28 @@ PREFIX = $(TCPATH)/riscv32-unknown-elf
CC = $(PREFIX)-gcc CC = $(PREFIX)-gcc
CXX = $(PREFIX)-g++ CXX = $(PREFIX)-g++
CCFLAGS = -ffreestanding -fno-stack-protector ARCHFLAGS = -ffreestanding -fno-stack-protector -fdata-sections -ffunction-sections -march=rv32ima -mabi=ilp32
CCFLAGS += -static -static-libgcc -fdata-sections -ffunction-sections CCFLAGS = -g -Os $(ARCHFLAGS) -std=c18
CCFLAGS += -g -Os -march=rv32ima -mabi=ilp32 CXXFLAGS = $(ARCHFLAGS) -g -Os -std=c++20 -fno-exceptions -fno-rtti
CXXFLAGS = $(CCFLAGS) -std=c++20 -fno-exceptions -fno-rtti
LDFLAGS = -T binary.ld -nostdlib -Wl,--gc-sections LDFLAGS = -T binary.ld -nostdlib -Wl,--gc-sections
OBJS := start.o main.o OBJS = start.o \
main.o
.PHONY: all test clean .PHONY: all test clean
all: $(PROJECT).bin $(PROJECT).debug.txt 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 test: $(PROJECT).bin $(PROJECT).debug.txt
../../../../build/projects/riscv_test_harness/rvtest $< ../../../../build/projects/riscv_test_harness/rvtest $<
clean: clean:
rm $(PROJECT).elf $(PROJECT).bin $(PROJECT).debug.txt $(OBJS) rm $(PROJECT).elf $(PROJECT).bin $(PROJECT).debug.txt $(OBJS)
# Link rules
$(PROJECT).elf: $(OBJS) $(PROJECT).elf: $(OBJS)
$(CC) $(CCFLAGS) $(LDFLAGS) -o $@ $(OBJS) $(CC) $(CCFLAGS) $(LDFLAGS) -o $@ $(OBJS)

View File

@ -10,6 +10,8 @@ uint32_t strlen(const char* str) {
return c - str; return c - str;
} }
#define SYSCON *(volatile uint32_t*)0x11100000
#define UART_BASE 0x10000000 #define UART_BASE 0x10000000
#define UART_DATA *(volatile uint32_t*)UART_BASE #define UART_DATA *(volatile uint32_t*)UART_BASE
#define UART_STATUS UART_DATA #define UART_STATUS UART_DATA
@ -49,6 +51,10 @@ void main() {
COUNTER_TEST(bytevalue, 9); COUNTER_TEST(bytevalue, 9);
#endif #endif
// Shut down the test harness once we're done testing.
puts("Tests done, shutting down test harness...\n");
SYSCON = 0x5555;
// loop forever // loop forever
for(;;); // for(;;);
} }