diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..787bdef --- /dev/null +++ b/LICENSE @@ -0,0 +1,8 @@ +Copyright 2023 Lily Tsuru +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. diff --git a/README.md b/README.md index 736e494..db0bd1a 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,36 @@ 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! +``` + + +# 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.) diff --git a/ideas.md b/ideas.md index 757f462..3b068d5 100644 --- a/ideas.md +++ b/ideas.md @@ -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 diff --git a/native/projects/lcpu/src/SourceSink.cpp b/native/projects/lcpu/src/SourceSink.cpp index da72bd0..ddbeabc 100644 --- a/native/projects/lcpu/src/SourceSink.cpp +++ b/native/projects/lcpu/src/SourceSink.cpp @@ -3,126 +3,62 @@ #include #include +// 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). +#define LCPU_SUPPORT_OLD_GMOD + namespace tier0 { // TODO: For now this'll leak.. lucore::Library* library = nullptr; - /// Minimally Source compatiable Color type - /// (Even more POD than Source) - struct Color { - constexpr explicit Color(u8 r, u8 g, u8 b, u8 a) { - colors[0] = r; - colors[1] = g; - colors[2] = b; - colors[3] = a; - } - - u8 colors[4]; - }; - - using LoggingChannelID_t = s32; - enum LoggingSeverity_t { - LS_MESSAGE, - LS_WARNING, - LS_ASSERT, - LS_ERROR, - - LS_HIGHEST_SEVERITY - }; - - enum LoggingChannelFlags_t { LCF_CONSOLE_ONLY = 1, LCF_DO_NOT_ECHO = 2 }; - - using RegisterTagsFunc = void (*)(); - - // LoggingSystem_ APIs - using RegisterLoggingChannel_t = LoggingChannelID_t (*)(const char*, RegisterTagsFunc, - int flags, LoggingSeverity_t severity, - Color color); - using Log_t = s32 (*)(LoggingChannelID_t, LoggingSeverity_t, const char*, ...); - using GetChannelColor_t = Color (*)(LoggingChannelID_t); - - RegisterLoggingChannel_t LoggingSystem_RegisterLoggingChannel {}; - Log_t LoggingSystem_Log {}; - GetChannelColor_t LoggingSystem_GetChannelColor {}; + using Msg_t = void (*)(const char*, ...); + Msg_t Msg {}; } // namespace tier0 namespace lcpu { - tier0::LoggingChannelID_t gSourceSinkLoggerChannel { -1 }; - SourceSink& SourceSink::The() { static SourceSink sink; return sink; } - void SourceSink::Init() { - // Modern 64-bit GMod has one tier0 library. - // I am not supporting non-x86-64 branch servers, so this is OK and I don't need to - // do any library scanning nonsense to pick the correct one. - // - // (note that the x86-64 branch also includes 32-bit binaries, rather - // weirdly. You can build the module for this too, but not the non-x86-64 branch - // 32-bit GMod, and it also shares having only one tier0 library.) + SourceSink::SourceSink() { +#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::Open(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. tier0::library = lucore::Library::Open("tier0"); +#endif #define GRAB_SYMBOL(name, T) tier0::name = tier0::library->Symbol(#name); + GRAB_SYMBOL(Msg, tier0::Msg_t); + } - GRAB_SYMBOL(LoggingSystem_RegisterLoggingChannel, tier0::RegisterLoggingChannel_t); - GRAB_SYMBOL(LoggingSystem_Log, tier0::Log_t); - GRAB_SYMBOL(LoggingSystem_GetChannelColor, tier0::GetChannelColor_t); - - gSourceSinkLoggerChannel = tier0::LoggingSystem_RegisterLoggingChannel( - "LCPU Native", nullptr, 0, tier0::LS_MESSAGE, tier0::Color(0xff, 0x99, 0x00, 0xff)); + SourceSink::~SourceSink() { + delete tier0::library; } void SourceSink::OutputMessage(const lucore::Logger::MessageData& data) { auto formatted = - std::format("[LCPU Native/{}] [{}] {}\n", lucore::Logger::SeverityToString(data.severity), + std::format("[LCPU Native/{}] [{}] {}", lucore::Logger::SeverityToString(data.severity), data.time, std::vformat(data.format, data.args)); - auto lucore_to_source = [severity = data.severity]() -> tier0::LoggingSeverity_t { - using enum lucore::Logger::MessageSeverity; - switch(severity) { - case Info: - return tier0::LS_MESSAGE; - case Warning: - return tier0::LS_WARNING; - case Error: - return tier0::LS_WARNING; - case Fatal: - return tier0::LS_ASSERT; - default: - return tier0::LS_MESSAGE; - } - }(); -#if 0 - // This is a pretty stupid hack for console colors on Linux. - // I don't really like it that much tbh, wish tier0 supported this. - auto color = tier0::LoggingSystem_GetChannelColor(gSourceSinkLoggerChannel); - - { - using enum lucore::Logger::MessageSeverity; - switch(data.severity) { - case Warning: - color = tier0::Color(0xff, 0xff, 0x0, 0xff); - break; - case Error: - case Fatal: - color = tier0::Color(0xff, 0x0, 0x0, 0xff); - break; - default: - break; - } - } - - tier0::LoggingSystem_Log(gSourceSinkLoggerChannel, lucore_to_source, - "\033[38;2;%d;%d;%dm%s\033[0m", color.colors[0], color.colors[1], - color.colors[2], formatted.c_str()); -#else - tier0::LoggingSystem_Log(gSourceSinkLoggerChannel, lucore_to_source, "%s", - formatted.c_str()); -#endif + tier0::Msg("%s\n", formatted.c_str()); } } // namespace lcpu diff --git a/native/projects/lcpu/src/SourceSink.hpp b/native/projects/lcpu/src/SourceSink.hpp index ab1772b..0d83cff 100644 --- a/native/projects/lcpu/src/SourceSink.hpp +++ b/native/projects/lcpu/src/SourceSink.hpp @@ -7,9 +7,10 @@ namespace lcpu { struct SourceSink : public lucore::Logger::Sink { static SourceSink& The(); - static void Init(); + SourceSink(); + ~SourceSink(); void OutputMessage(const lucore::Logger::MessageData& data) override; }; -} +} // namespace lcpu diff --git a/native/projects/lcpu/src/main.cpp b/native/projects/lcpu/src/main.cpp index 9d10ac9..23fb61f 100644 --- a/native/projects/lcpu/src/main.cpp +++ b/native/projects/lcpu/src/main.cpp @@ -5,13 +5,9 @@ #include GMOD_MODULE_OPEN() { - // Initalize the source logging sink and attach it to the lucore logger. - lcpu::SourceSink::Init(); lucore::Logger::The().AttachSink(lcpu::SourceSink::The()); - lucore::LogInfo("Hello Source World :) {} {}", 123.456, "This should work"); - lucore::LogWarning("test"); - LUCORE_CHECK(false, "this should bring the process down"); + lucore::LogInfo("LCPU Native Module loading"); return 0; } diff --git a/native/projects/lucore/CMakeLists.txt b/native/projects/lucore/CMakeLists.txt index 825bdee..ba53d9e 100644 --- a/native/projects/lucore/CMakeLists.txt +++ b/native/projects/lucore/CMakeLists.txt @@ -6,15 +6,28 @@ project(lucore ) add_library(lucore + # Assertion/Check failure handling src/Assert.cpp + # Logging functionality src/Logger.cpp src/StdoutSink.cpp - src/OsLibrary.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) diff --git a/native/projects/lucore/include/lucore/Logger.hpp b/native/projects/lucore/include/lucore/Logger.hpp index cd19400..77eeec4 100644 --- a/native/projects/lucore/include/lucore/Logger.hpp +++ b/native/projects/lucore/include/lucore/Logger.hpp @@ -35,7 +35,6 @@ namespace lucore { /// Get the single instance of the logger. static Logger& The(); - Logger() = default; Logger(const Logger&) = delete; Logger(Logger&&) = delete; @@ -76,6 +75,7 @@ namespace lucore { } private: + Logger() = default; void VOut(MessageSeverity severity, std::string_view format, std::format_args args); MessageSeverity logLevel { MessageSeverity::Info }; diff --git a/native/projects/lucore/include/lucore/StdoutSink.hpp b/native/projects/lucore/include/lucore/StdoutSink.hpp index 8ede41b..b6eeefa 100644 --- a/native/projects/lucore/include/lucore/StdoutSink.hpp +++ b/native/projects/lucore/include/lucore/StdoutSink.hpp @@ -3,15 +3,13 @@ namespace lucore { /// A logger sink implementation that prints to standard output. - /// Not used by the lcpu native module, but could be used by applications - /// using lucore later on. struct StdoutSink : public Logger::Sink { static StdoutSink& The(); virtual void OutputMessage(const Logger::MessageData& data) override; }; - /// Attach the stdout logger sink to the logger. + /// Attach the stdout logger sink to the global Lucore logger. void LoggerAttachStdout(); } diff --git a/native/projects/lucore/src/OsLibrary.cpp b/native/projects/lucore/src/OsLibrary.cpp deleted file mode 100644 index 0b4e27a..0000000 --- a/native/projects/lucore/src/OsLibrary.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include "OsLibrary.hpp" - -#if defined(_WIN32) -#include "OsLibrary.win32.cpp" -#elif defined(__linux__) -#include "OsLibrary.linux.cpp" -#else -#error No OsLibrary implementation for this platform -#endif diff --git a/native/projects/lucore/src/OsLibrary.linux.cpp b/native/projects/lucore/src/OsLibrary.linux.cpp index bf3184e..4983b1c 100644 --- a/native/projects/lucore/src/OsLibrary.linux.cpp +++ b/native/projects/lucore/src/OsLibrary.linux.cpp @@ -1,3 +1,5 @@ +#include "OsLibrary.hpp" + #include namespace lucore::detail { diff --git a/native/projects/lucore/src/OsLibrary.win32.cpp b/native/projects/lucore/src/OsLibrary.win32.cpp index 1b5e3a0..8818297 100644 --- a/native/projects/lucore/src/OsLibrary.win32.cpp +++ b/native/projects/lucore/src/OsLibrary.win32.cpp @@ -1,3 +1,5 @@ +#include "OsLibrary.hpp" + #define _WIN32_LEAN_AND_MEAN #include @@ -20,4 +22,4 @@ namespace lucore::detail { // therefore, we have nothing to do here on Windows. } -} +} // namespace lucore::detail diff --git a/native/projects/riscv/include/riscv/Bus.hpp b/native/projects/riscv/include/riscv/Bus.hpp index 43ba5b8..ca3738d 100644 --- a/native/projects/riscv/include/riscv/Bus.hpp +++ b/native/projects/riscv/include/riscv/Bus.hpp @@ -1,28 +1,34 @@ -#include #include - -#include +#include #include - +#include 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); @@ -57,10 +61,10 @@ namespace riscv { /// This function returns true if the device was able to be put on the bus. /// This function returns false in the following error cases: /// - [device] is a null pointer - /// - The provided base address overlaps a already attached device in some way + /// - 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 FindDeviceForAddress(AddressT address) const; - CPU* attachedCpu{}; + CPU* attachedCpu {}; // TODO: if this ends up being a hotpath replace with ankerl::unordered_dense std::unordered_map mapped_devices; }; - - -} +} // namespace riscv diff --git a/native/projects/riscv/include/riscv/CPU.hpp b/native/projects/riscv/include/riscv/CPU.hpp index bc8c88c..3bbcf46 100644 --- a/native/projects/riscv/include/riscv/CPU.hpp +++ b/native/projects/riscv/include/riscv/CPU.hpp @@ -3,8 +3,9 @@ namespace riscv { - /** The CPU core. */ + /// The CPU core. struct CPU { + /// CPU core state. struct State { u32 gpr[32]; u32 pc; diff --git a/native/projects/riscv/include/riscv/Devices/RamDevice.hpp b/native/projects/riscv/include/riscv/Devices/RamDevice.hpp index 6ba096c..a9ff34a 100644 --- a/native/projects/riscv/include/riscv/Devices/RamDevice.hpp +++ b/native/projects/riscv/include/riscv/Devices/RamDevice.hpp @@ -1,18 +1,16 @@ - #include -#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 constexpr usize AddressToIndex(AddressT address) { return ((address - baseAddress) % memorySize) / sizeof(T);