Compare commits

...

10 Commits

27 changed files with 502 additions and 147 deletions

8
LICENSE Normal file
View File

@ -0,0 +1,8 @@
Copyright 2023 Lily Tsuru <lily.modeco80@protonmail.ch>
Portions copyright 2022 Charles Lohr (CNLohr)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -5,13 +5,38 @@ LCPU is an alternative CPU core addon for GMod/Wiremod.
It provides:
- A standard RISC-V architechure (rv32ima) CPU core, implemented in native code
- No wiremod native code embargos here!
- No wiremod native code embargos here, so we get actually good performance without half our SENTs becoming lag machine fodder!
- Interoperation with the Wiremod addon (and addons which implement Wiremod integration)
# Building Native Module
TODO: Steps to build the LCPU native module on Windows and Linux
This addon (for now) works with both the non-beta branch and the x86-64 beta branches of GMod.
# Installation
This repository is set up in the exact directory structure to be a Legacy Addon; therefore once you have built the native module (TODO), adding it to a server should work fine.
This repository is set up to be a Filesystem Addon; therefore, workflows which clone repositories from Git and put them in addons/ should be able to work with the LCPU addon just fine.
Preliminary installation steps:
```
garrysmod/addons$ git clone --recursive https://github.com/modeco80/gmod-lcpu.git lcpu
garrysmod/addons$ cd lcpu
# Build the LCPU native module. These steps build the linux64 version of the module
# on linux; you'll need to alter it if you want to build for 32-bit.
garrysmod/addons/lcpu$ cmake -Wno-dev -GNinja -S native -B build \
-DCMAKE_BUILD_TYPE=Release
garrysmod/addons/lcpu$ ninja -C build
# Install the native module (Linux)
# For Windows you can do the same thing, just replace this
# with how you'd do it in batch (or use Explorer, I'm not your dad)
garrysmod/addons/lcpu$ [[ ! -d '../../lua/bin']] && mkdir -p ../../lua/bin && cp build/projects/lcpu/*.dll ../../lua/bin
# Tada!
```
On Linux you can alternatively use the `./build_module.sh` script that will do all the build and installation steps automatically.
# Special Thanks
[CNLohr](https://github.com/CNLohr) - I may not like the mini-rv32ima code that much, but it did still help a lot (and my emulation core library is based off it, with tons of refactoring and a C++ rewrite to boot.)

16
build_module.sh Executable file
View File

@ -0,0 +1,16 @@
#!/bin/bash
# Build the LCPU addon for the reccomended environment
# and install it into the proper directory gmod wants native modules to be.
# This expects to be ran in [gmod]/addons/[addon folder].
set -x
cmake -Wno-dev -GNinja -S native -B build -DCMAKE_BUILD_TYPE=Release
ninja -C build
[[ ! -d '../../lua/bin' ]] && {
mkdir -p ../../lua/bin
}
cp -v build/projects/lcpu/*.dll ../../lua/bin

View File

@ -8,7 +8,6 @@ This is basically the working ideas for the LCPU project.
- RISC-V rv32ima core
- Would supporting more than one CPU core type be worthwhile? If so, the project is relatively setup for such workflow...
- Controllable paramaters (RAM size, ...)
- Our own framebuffer screen SENT (since wiremod decided to go stupid mode and saw off the gpu)
## Code upload
@ -63,3 +62,4 @@ This is basically the working ideas for the LCPU project.
- Wiremod interopability
- Wiremod GPIO peripheral (to interface with wire world)
- special Console Screen peripheral (interfacing specifically with it)
- frambuffer peripheral which uses the Digital Screen

View File

@ -1,10 +1,3 @@
-- skeleton load file to get gmod to recognize this as an addon
-- this will contain files later on in life.
print("hello world?")
-- detect if wiremod is installed
if not istable(WireLib) then
print("[LCPU] we need wiremod..")
return
end
AddCSLuaFile()

View File

@ -1,39 +1,18 @@
add_library(gmod_headers INTERFACE)
target_include_directories(gmod_headers INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gmod_headers/include)
# Originally from facepunch cmake build system, modified to be slightly less painful
function(set_gmod_suffix_prefix library)
set_target_properties(${library} PROPERTIES PREFIX "gmsv_")
if(APPLE)
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4")
set_target_properties(${library} PROPERTIES SUFFIX "_osx.dll")
else()
set_target_properties(${library} PROPERTIES SUFFIX "_osx64.dll")
endif()
elseif(UNIX)
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4")
set_target_properties(${library} PROPERTIES SUFFIX "_linux.dll")
else()
set_target_properties(${library} PROPERTIES SUFFIX "_linux64.dll")
endif()
elseif(WIN32)
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4")
set_target_properties(${library} PROPERTIES SUFFIX "_win32.dll")
else()
set_target_properties(${library} PROPERTIES SUFFIX "_win64.dll")
endif()
endif()
endfunction()
include(./gmod_headers.cmake)
add_library(lcpu_native SHARED
src/main.cpp
src/SourceSink.cpp
)
if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
target_link_options(lcpu_native PRIVATE "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/gmod_abi.ver")
target_link_options(lcpu_native PRIVATE
"-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/gmod_abi.ver"
# screw it, dude
"-static-libstdc++"
"-static-libgcc"
)
endif()
target_link_libraries(lcpu_native

View File

@ -0,0 +1,26 @@
add_library(gmod_headers INTERFACE)
target_include_directories(gmod_headers INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gmod_headers/include)
# Originally from facepunch cmake build system, modified to be slightly less painful
function(set_gmod_suffix_prefix library)
set_target_properties(${library} PROPERTIES PREFIX "gmsv_")
if(APPLE)
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4")
set_target_properties(${library} PROPERTIES SUFFIX "_osx.dll")
else()
set_target_properties(${library} PROPERTIES SUFFIX "_osx64.dll")
endif()
elseif(UNIX)
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4")
set_target_properties(${library} PROPERTIES SUFFIX "_linux.dll")
else()
set_target_properties(${library} PROPERTIES SUFFIX "_linux64.dll")
endif()
elseif(WIN32)
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4")
set_target_properties(${library} PROPERTIES SUFFIX "_win32.dll")
else()
set_target_properties(${library} PROPERTIES SUFFIX "_win64.dll")
endif()
endif()
endfunction()

View File

@ -0,0 +1,93 @@
#include "SourceSink.hpp"
#include <cstdlib>
#include <lucore/Assert.hpp>
#include <lucore/Library.hpp>
#include <lucore/Types.hpp>
// The old non-beta branch of GMod on Linux has multiple tier0 libraries for client and server.
// This compatibility define allows to support that case (for now). Once this define is removed,
// the old codepath can be totally removed.
#define LCPU_SUPPORT_OLD_GMOD
namespace tier0 {
// TODO: For now this'll leak..
lucore::Library* library = nullptr;
using Msg_t = void (*)(const char*, ...);
Msg_t Msg {};
bool OpenLibrary() {
#ifdef LCPU_SUPPORT_OLD_GMOD
constexpr static std::string_view tier0_libraries[] {
#ifdef __linux__
"tier0_srv",
#endif
"tier0"
};
for(auto lib : tier0_libraries) {
if(lucore::Library::Loaded(lib)) {
// Found the correct tier0 library to open; use that.
tier0::library = lucore::Library::OpenExisting(lib);
break;
}
}
#else
// The x86-64 branch of GMod, including the 32-bit binaries in the branch,
// have a single tier0 library, which makes the codepath much simpler.
// Hopefully I can switch to this path at some point.
tier0::library = lucore::Library::OpenExisting("tier0");
#endif
if(tier0::library == nullptr)
return false;
return true;
}
bool GrabSymbols() {
#define GRAB_SYMBOL(name, T) \
name = library->Symbol<T>(#name); \
if(name == nullptr) \
return false;
GRAB_SYMBOL(Msg, Msg_t);
#undef GRAB_SYMBOL
return true;
}
} // namespace tier0
namespace lcpu {
SourceSink& SourceSink::The() {
static SourceSink sink;
return sink;
}
SourceSink::SourceSink() {
if(!tier0::OpenLibrary()) {
std::printf("Tier0 could not be opened\n");
std::quick_exit(10);
}
// TODO: A bit nicer of an error message?
// Explain *what* to do if you see this message.
if(!tier0::GrabSymbols()) {
std::printf("Tier0 symbols could not be grabbed\n");
std::quick_exit(10);
}
}
SourceSink::~SourceSink() {
delete tier0::library;
}
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));
tier0::Msg("%s\n", formatted.c_str());
}
} // namespace lcpu

View File

@ -0,0 +1,16 @@
#include <lucore/Logger.hpp>
namespace lcpu {
/// A lucore logger sink which funnels to the Source engine's
/// Tier0 library's logging functionality.
struct SourceSink : public lucore::Logger::Sink {
static SourceSink& The();
SourceSink();
~SourceSink();
void OutputMessage(const lucore::Logger::MessageData& data) override;
};
} // namespace lcpu

View File

@ -1,6 +1,19 @@
#include <GarrysMod/Lua/Interface.h>
#include "SourceSink.hpp"
#include <lucore/Assert.hpp>
LUA_FUNCTION(lcpu_native_test) {
}
GMOD_MODULE_OPEN() {
lucore::Logger::The().AttachSink(lcpu::SourceSink::The());
lucore::LogInfo("LCPU Native Module loading");
return 0;
}

View File

@ -6,10 +6,28 @@ project(lucore
)
add_library(lucore
# Assertion/Check failure handling
src/Assert.cpp
# Logging functionality
src/Logger.cpp
src/StdoutSink.cpp
# Dynamic library wrappers
src/Library.cpp
)
# Target-specific Lucore sources.
if(WIN32)
target_sources(lucore PRIVATE
src/OsLibrary.win32.cpp
)
elseif(UNIX)
target_sources(lucore PRIVATE
src/OsLibrary.linux.cpp
)
endif()
target_compile_features(lucore PUBLIC cxx_std_20)
target_include_directories(lucore PUBLIC ${PROJECT_SOURCE_DIR}/include)

View File

@ -5,18 +5,15 @@
#include <format>
namespace lucore {
// TODO: wrapper which uses source_location (we don't need no macros anymore!)
[[noreturn]] void ExitMsg(const char* fileName, int fileLine, const char* message);
[[noreturn]] void ExitMsg(const char* message);
} // namespace lucore
#ifndef NDEBUG
#define LUCORE_ASSERT(expr, fmt, ...) \
if(!(expr)) [[unlikely]] { \
auto msg = std::format("Assertion \"{}\" failed with message: {}", #expr, \
std::format(fmt, ##__VA_ARGS__)); \
::lucore::ExitMsg(__FILE__, __LINE__, msg.c_str()); \
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, ...)
@ -25,7 +22,7 @@ namespace lucore {
// 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, \
std::format(fmt, ##__VA_ARGS__)); \
::lucore::ExitMsg(__FILE__, __LINE__, msg.c_str()); \
auto msg = std::format("Check \"{}\" @ {}:{} failed with message: {}", #expr, __FILE__, \
__LINE__, std::format(fmt, ##__VA_ARGS__)); \
::lucore::ExitMsg(msg.c_str()); \
}

View File

@ -0,0 +1,29 @@
#pragma once
#include <string_view>
namespace lucore {
struct Library {
using Handle = void*;
/// Open an already loaded library
static Library* OpenExisting(std::string_view dllname);
/// Query if [dllname] is loaded in the process.
static bool Loaded(std::string_view dllname);
~Library();
template <class T>
T Symbol(std::string_view symbolName) {
return reinterpret_cast<T>(SymbolImpl(symbolName.data()));
}
private:
void* SymbolImpl(const char* symbol);
constexpr explicit Library(Handle handle) : handle(handle) {}
Handle handle {};
};
} // namespace lucore

View File

@ -1,5 +1,6 @@
//! Logging utilities for Lucore
//! Using Standard C++ <format>
#pragma once
#include <chrono>
#include <cstdint>
@ -35,7 +36,6 @@ namespace lucore {
/// Get the single instance of the logger.
static Logger& The();
Logger() = default;
Logger(const Logger&) = delete;
Logger(Logger&&) = delete;
@ -76,20 +76,31 @@ namespace lucore {
}
private:
Logger() = default;
void VOut(MessageSeverity severity, std::string_view format, std::format_args args);
MessageSeverity logLevel { MessageSeverity::Info };
std::vector<Sink*> sinks;
};
/// A logger sink implementation that prints to standard output.
struct StdoutSink : public Logger::Sink {
static StdoutSink& The();
template <class... Args>
void LogInfo(std::string_view format, Args... args) {
Logger::The().Info(format, std::forward<Args>(args)...);
}
virtual void OutputMessage(const Logger::MessageData& data) override;
};
template <class... Args>
void LogWarning(std::string_view format, Args... args) {
Logger::The().Warning(format, std::forward<Args>(args)...);
}
/// Attach the stdout logger sink to the logger.
void LoggerAttachStdout();
template <class... Args>
void LogError(std::string_view format, Args... args) {
Logger::The().Error(format, std::forward<Args>(args)...);
}
template <class... Args>
void LogFatal(std::string_view format, Args... args) {
Logger::The().Fatal(format, std::forward<Args>(args)...);
}
} // namespace lucore

View File

@ -0,0 +1,17 @@
#pragma once
#include <lucore/Logger.hpp>
namespace lucore {
/// A logger sink implementation that prints to standard output.
struct StdoutSink : public Logger::Sink {
static StdoutSink& The();
virtual void OutputMessage(const Logger::MessageData& data) override;
};
/// Attach the stdout logger sink to the global Lucore logger.
void LoggerAttachStdout();
}

View File

@ -0,0 +1,15 @@
#include <cstdint>
//namespace lucore {
using u8 = std::uint8_t;
using s8 = std::int8_t;
using u16 = std::uint16_t;
using s16 = std::int16_t;
using u32 = std::uint32_t;
using s32 = std::int32_t;
using u64 = std::uint64_t;
using s64 = std::int64_t;
using usize = std::size_t;
using ssize = std::intptr_t;
//} // namespace lucore

View File

@ -6,8 +6,8 @@
namespace lucore {
[[noreturn]] void ExitMsg(const char* fileName, int fileLine, const char* message) {
Logger::The().Fatal(message);
[[noreturn]] void ExitMsg(const char* message) {
LogFatal("{}", message);
std::quick_exit(0xAF);
}

View File

@ -0,0 +1,39 @@
#include <format>
#include <lucore/Library.hpp>
#include <string>
#include "OsLibrary.hpp"
namespace lucore {
namespace {
std::string FormatLibraryName(std::string_view dllName) {
#ifdef _WIN32
return std::format("{}.dll", dllName);
#else
return std::format("lib{}.so", dllName);
#endif
}
} // namespace
Library* Library::OpenExisting(std::string_view dllname) {
auto name = FormatLibraryName(dllname);
if(!detail::OsLibraryLoaded(name.c_str()))
return nullptr;
return new Library(detail::OsOpenLibrary(name.c_str()));
}
bool Library::Loaded(std::string_view dllname) {
return detail::OsLibraryLoaded(FormatLibraryName(dllname).c_str());
}
Library::~Library() {
if(handle) {
detail::OsFreeLibrary(handle);
}
}
void* Library::SymbolImpl(const char* symbolName) {
return detail::OsLibrarySymbol(handle, symbolName);
}
} // namespace lucore

View File

@ -32,42 +32,5 @@ namespace lucore {
sink->OutputMessage(data);
}
StdoutSink& StdoutSink::The() {
static StdoutSink sink;
return sink;
}
void StdoutSink::OutputMessage(const Logger::MessageData& data) {
// This is kinda iffy, but required until more standard libraries support the C++23 <print>
// header.
struct FileOutIterator {
using iterator_category = std::output_iterator_tag;
using value_type = void;
using difference_type = std::ptrdiff_t;
using pointer = void;
using reference = void;
FileOutIterator(std::FILE* file) : file(file) {}
FileOutIterator& operator*() { return *this; }
FileOutIterator& operator++() { return *this; }
FileOutIterator& operator++(int) { return *this; }
FileOutIterator& operator=(const char& val) {
fputc(val, file);
return *this;
}
private:
std::FILE* file;
};
std::format_to(
FileOutIterator(data.severity < Logger::MessageSeverity::Error ? stdout : stderr),
"[Lucore/{}] [{}] {}\n", Logger::SeverityToString(data.severity), data.time,
std::vformat(data.format, data.args));
}
void LoggerAttachStdout() {
Logger::The().AttachSink(StdoutSink::The());
}
} // namespace lucore

View File

@ -0,0 +1,21 @@
//! Operating-system independent utilities for opening
//! shared libraries. This is currently a detail-only
//! Lucore API, and its stability is NOT guaranteed.
namespace lucore::detail {
/// Opaque handle type for libraries.
using OsLibraryHandle = void*;
/// Open a library.
OsLibraryHandle OsOpenLibrary(const char* filename);
/// Query if the library with the given [filename] is loaded.
bool OsLibraryLoaded(const char* filename);
/// Get a pointer to the symbol of a library.
void* OsLibrarySymbol(OsLibraryHandle dll, const char* symbolName);
/// Free the library.
void OsFreeLibrary(OsLibraryHandle handle);
} // namespace lucore::detail

View File

@ -0,0 +1,24 @@
#include "OsLibrary.hpp"
#include <dlfcn.h>
namespace lucore::detail {
OsLibraryHandle OsOpenLibrary(const char* filename) {
return dlopen(filename, RTLD_LAZY);
}
bool OsLibraryLoaded(const char* filename) {
return dlopen(filename, RTLD_NOLOAD | RTLD_LAZY) != nullptr;
}
void* OsLibrarySymbol(OsLibraryHandle dll, const char* symbolName) {
return dlsym(dll, symbolName);
}
void OsFreeLibrary(OsLibraryHandle handle) {
// The reference count on *Nix will be incremented by the launcher
// process itself, therefore we do not risk accidentally pulling the
// library out of the rug of the engine in either case.
dlclose(handle);
}
} // namespace lucore::detail

View File

@ -0,0 +1,25 @@
#include "OsLibrary.hpp"
#define _WIN32_LEAN_AND_MEAN
#include <windows.h>
namespace lucore::detail {
OsLibraryHandle OsOpenLibrary(const char* filename) {
return reinterpret_cast<OsLibraryHandle>(GetModuleHandleA(filename);
}
bool OsLibraryLoaded(const char* filename) {
return GetModuleHandleA(filename) != nullptr;
}
void* OsLibrarySymbol(OsLibraryHandle dll, const char* symbolName) {
return GetProcAddressA(reinterpret_cast<HMODULE*>(dll), symbolName);
}
void OsFreeLibrary(OsLibraryHandle handle) {
// GetModuleHandle*() does not increment the reference count;
// therefore, we have nothing to do here on Windows.
}
} // namespace lucore::detail

View File

@ -0,0 +1,41 @@
#include <lucore/StdoutSink.hpp>
namespace lucore {
StdoutSink& StdoutSink::The() {
static StdoutSink sink;
return sink;
}
void StdoutSink::OutputMessage(const Logger::MessageData& data) {
// This is kinda iffy, but required until more standard libraries support the C++23 <print>
// header.
struct FputcIterator {
using iterator_category = std::output_iterator_tag;
using value_type = void;
using difference_type = std::ptrdiff_t;
using pointer = void;
using reference = void;
FputcIterator(std::FILE* file) : file(file) {}
FputcIterator& operator*() { return *this; }
FputcIterator& operator++() { return *this; }
FputcIterator& operator++(int) { return *this; }
FputcIterator& operator=(const char& val) {
fputc(val, file);
return *this;
}
private:
std::FILE* file;
};
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));
}
void LoggerAttachStdout() {
Logger::The().AttachSink(StdoutSink::The());
}
} // namespace lucore

View File

@ -1,28 +1,34 @@
#include <riscv/Types.hpp>
#include <lucore/OptionalRef.hpp>
#include <vector>
#include <riscv/Types.hpp>
#include <unordered_map>
#include <vector>
namespace riscv {
struct CPU;
/// An address bus.
/// An address/memory bus. No virtual address translation is implemented;
/// all addresses/devices are placed in physical addresses.
struct Bus {
/// Interface all memory bus devices use.
struct Device {
Device() = default;
// Devices have no need to be copied or moved.
Device(const Device&) = delete;
Device(Device&&) = delete;
virtual ~Device() = default;
// How many bytes does this device occupy of address space?
/// How many bytes does this device occupy of address space? This should
/// effectively be a constant, and should probably not change during CPU execution.
virtual AddressT Size() const = 0;
// Used to allow bus devices to know when they are attached to a memory bus,
// and ultimately, an instance of a System
/// Used to allow bus devices to know when they are attached to a memory bus,
/// and ultimately, an instance of a System
virtual void Attached(Bus* memoryBus, AddressT baseAddress) = 0;
/// Is this device clocked?
/// Does this device require a clock "signal"?
virtual bool Clocked() const { return false; }
/// This function is called to give clocked devices
@ -32,8 +38,7 @@ namespace riscv {
// TODO(feat): default implementations of Peek* and Poke* should exist
// and trap the CPU (similarly to what happens if a unmapped bus read occurs).
// Peek() -> reads a value from this device.
/// Peek() -> reads a value from this device.
virtual u8 PeekByte(AddressT offset) = 0;
virtual u16 PeekShort(AddressT offset) = 0;
virtual u32 PeekWord(AddressT offset) = 0;
@ -42,7 +47,6 @@ namespace riscv {
virtual void PokeByte(AddressT offset, u8 value) = 0;
virtual void PokeShort(AddressT offset, u16 value) = 0;
virtual void PokeWord(AddressT offset, u32 value) = 0;
};
Bus(CPU* cpu);
@ -60,7 +64,7 @@ namespace riscv {
/// - The provided base address overlaps a already attached device in some way
bool AttachDevice(AddressT baseAddress, Device* device);
/// Clock all clocked devices.
/// Clock all clocked devices mapped onto the bus..
void Clock();
u8 PeekByte(AddressT address);
@ -70,16 +74,14 @@ namespace riscv {
void PokeByte(AddressT address, u8 value);
void PokeShort(AddressT address, u16 value);
void PokeWord(AddressT address, u32 value);
private:
private:
lucore::OptionalRef<Device> FindDeviceForAddress(AddressT address) const;
CPU* attachedCpu{};
CPU* attachedCpu {};
// TODO: if this ends up being a hotpath replace with ankerl::unordered_dense
std::unordered_map<AddressT, Device*> mapped_devices;
};
}
} // namespace riscv

View File

@ -3,8 +3,9 @@
namespace riscv {
/** The CPU core. */
/// The CPU core.
struct CPU {
/// CPU core state.
struct State {
u32 gpr[32];
u32 pc;

View File

@ -1,18 +1,16 @@
#include <riscv/Bus.hpp>
#include "riscv/Types.hpp"
namespace riscv::devices {
/// A block of RAM which can be used by the CPU.
struct RamDevice : public Bus::Device {
RamDevice(AddressT size);
virtual ~RamDevice();
AddressT Size() const override;
// Implementation of Device interface
AddressT Size() const override;
void Attached(Bus* bus, AddressT base) override;
u8 PeekByte(AddressT address) override;
@ -24,7 +22,7 @@ namespace riscv::devices {
void PokeWord(AddressT address, u32 value) override;
private:
/// helper used for implementing stuff
/// helper used for implementing Peek/Poke API
template <class T>
constexpr usize AddressToIndex(AddressT address) {
return ((address - baseAddress) % memorySize) / sizeof(T);

View File

@ -1,26 +1,11 @@
//! Common types
#pragma once
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <lucore/Types.hpp>
namespace riscv {
using u8 = std::uint8_t;
using s8 = std::int8_t;
using u16 = std::uint16_t;
using s16 = std::int16_t;
using u32 = std::uint32_t;
using s32 = std::int32_t;
using u64 = std::uint64_t;
using s64 = std::int64_t;
using usize = std::size_t;
using ssize = std::intptr_t;
/// A type that can repressent address space or address space offsets.
using AddressT = u32;
} // namespace riscv