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:
parent
7af85f5601
commit
90e684e1e3
|
@ -16,6 +16,7 @@ AllowShortBlocksOnASingleLine: false
|
|||
AllowShortFunctionsOnASingleLine: InlineOnly
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AllowShortCaseLabelsOnASingleLine: true
|
||||
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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()); \
|
||||
}
|
||||
|
|
|
@ -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 {};
|
||||
|
|
|
@ -14,4 +14,4 @@ namespace lucore {
|
|||
/// Attach the stdout logger sink to the global Lucore logger.
|
||||
void LoggerAttachStdout();
|
||||
|
||||
}
|
||||
} // namespace lucore
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)];
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -8,6 +8,4 @@ namespace riscv {
|
|||
/// A type that can repressent address space or address space offsets.
|
||||
using Address = u32;
|
||||
|
||||
|
||||
|
||||
} // namespace riscv
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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(;;);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue