diff --git a/ideas.md b/ideas.md index 1482225..71a90d5 100644 --- a/ideas.md +++ b/ideas.md @@ -11,15 +11,15 @@ This is basically the working ideas for the LCPU project. ## Code upload -- Upload a raw binary to execute, generated from any user tooling (goes into server data folder) +- Upload a raw binary to execute, generated from any user tooling (goes into server `data/lcpu/users/[steamid]/`) - Yes, this means you can run Linux in GMod. No, I'm not sorry. -## Integrated simple project workflow +## Integrated simple project workflow (WIP, not in the addon yet) - Uses official RISC-V GCC toolchain - In a podman container for jailing reasons? -- Write assembly/C/C++ code using a tiny project system (source for them would go in server data folder ?) +- Write assembly/C/C++ code using a tiny project system (source for them would go in server `data/lcpu/users/[steamid]/projects/[project]`) - At the root of a project, a `project.json` file is expected to exist, with contents like: ```json { @@ -53,8 +53,8 @@ This is basically the working ideas for the LCPU project. - `BaseCCompileFlags` and `BaseCppCompileFlags` are defaulted to sane values for each language. - This will be transpiled into a `Makefile` by the addon. - - A standalone tool will be provided and used for transpiling `project.json` to a `Makefile` (and maybe even passed into the container and transpiled there, to reduce the actions on the host to just the podman run?) - - which, when a Build is done in GMod; is then run with `make` in a temporary podman container which only has access to the source code for the project (and nothing else, besides riscv tools). + - A standalone tool will be provided and used for transpiling `project.json` to a `Makefile` (and maybe even built into the container and transpiled there, to reduce the actions on the host to just the podman run?) + - which, when a Build is done in GMod; is then run with `make` in a temporary podman container which only has access to the source code folder for the project (and nothing else, besides riscv tools which are in the image). - Command line is probably something like `make CONFIG=${config}` - the output binary will be stored alongside the source code on the server side, with a name like `${name}-${config}.bin` - This file can then be selected for loading (without needing to be uploaded from the client). @@ -63,12 +63,13 @@ This is basically the working ideas for the LCPU project. - There is no conditional compilation in the `project.json` system - All files in a project are always built by that project. -- No notion of subprojects/build dependencies other than GCC generated dependencies +- No notion of subprojects/build dependencies - This is meant to be simple for easy development in GMod. If you want complex build features you can export the project onto your own computer and use `lcpu_projgen` to generate Makefiles (which you can then maintain) - Text editor used to edit project source files - Use the Wire editor? (we need wiremod anyways, and the text editor is.. OK I suppose.) - - Or I guess I could try getting Monaco to play nicely with DHTML + - Or: https://github.com/Metastruct/gmod-monaco + - https://github.com/JustMrPhoenix/Noir/tree/master - Some example projects? - A simple bare metal "Hello World" diff --git a/lua/autorun/lcpu_load.lua b/lua/autorun/lcpu_load.lua index 669edc6..e104041 100644 --- a/lua/autorun/lcpu_load.lua +++ b/lua/autorun/lcpu_load.lua @@ -12,11 +12,11 @@ if SERVER then LCPU = {}; LCPU.Devices = {}; + -- Uncomment this to enable debug logging (useful for troubleshooting bugs) --LCPUNative.EnableDebug() - + AddCSLuaFile("entities/gmod_lcpu_cpu.lua") - -- Serverside devices + -- Serverside devices (that don't depend on wiremod being loaded) include("lcpu/devices/uart.lua") - include("lcpu/devices/gmlua_test.lua") end diff --git a/lua/entities/gmod_lcpu_cpu.lua b/lua/entities/gmod_lcpu_cpu.lua index f8144e8..1d4b453 100644 --- a/lua/entities/gmod_lcpu_cpu.lua +++ b/lua/entities/gmod_lcpu_cpu.lua @@ -6,6 +6,10 @@ ENT.Author = "Lily <3" -- no more, this deeply uses native APIs if CLIENT then return end +-- Include the devices which require Wiremod here +-- (hacky, but /shrug) +include("lcpu/devices/wire_interface.lua") + -- TODO: serverside convars to control execution rate & cycle count function ENT:Initialize() @@ -15,13 +19,11 @@ function ENT:Initialize() -- CPU callbacks? self.cpu = LCPUNative.CreateCPU(128 * 1024) - - -- UART & GLua test device self.uart = LCPU.Devices.UART(0x10000000) - self.test_device = LCPU.Devices.LuaTest() + self.wireInterface = LCPU.Devices.WireInterface(0x11310000, self, 8, 8) self.cpu:AttachDevice(self.uart) - self.cpu:AttachDevice(self.test_device) + self.cpu:AttachDevice(self.wireInterface) self:SetOverlayText("hi :)") end @@ -29,9 +31,13 @@ end function ENT:Think() -- Avoid running if the cpu is not powered on if not self.cpu:PoweredOn() then return end + self.cpu:Cycle() -- Even though this is gated by tickrate I'm just trying to be nice here self:NextThink(CurTime() + 0.1) - self.cpu:Cycle() +end + +function ENT:Reset() + self.cpu:Reset() end function ENT:PowerOn() diff --git a/lua/lcpu/devices/gmlua_test.lua b/lua/lcpu/devices/gmlua_test.lua deleted file mode 100644 index bff57d8..0000000 --- a/lua/lcpu/devices/gmlua_test.lua +++ /dev/null @@ -1,31 +0,0 @@ --- Lua test device. This'll probably get removed soon, this is just for testing --- that Lua->C++ interop actually works like it should - -function LCPU.Devices.LuaTest() - local test_device = LCPUNative.CreateDevice(0x11300000, 0x8) - test_device.register = 0x0 - - function test_device:Peek(address) - --print(string.format("TestDevice:Peek @ 0x%08x", address)) - if address == self.Base then return CurTime() end -- it a test! - if address == self.Base + 4 then return self.register end - - return 0xffffffff - end - - function test_device:Poke(address, value) - --print(string.format("TestDevice:Poke @ 0x%08x -> 0x%08x", address, value)) - if address == self.Base + 4 then - --print("LUAREG write") - self.register = value - end - end - - function test_device:Reset() - --print("TestDevice:Reset") - -- clear the register - self.register = 0 - end - - return test_device -end diff --git a/lua/lcpu/devices/wire_interface.lua b/lua/lcpu/devices/wire_interface.lua new file mode 100644 index 0000000..15d7353 --- /dev/null +++ b/lua/lcpu/devices/wire_interface.lua @@ -0,0 +1,67 @@ +-- Basic Wiremod interface device. + +function LCPU.Devices.WireInterface(base, entity, nrInputs, nrOutputs) + local HEADER_SIZE = 8 + local function MmioSize() + return HEADER_SIZE + (nrInputs * 4) + (nrOutputs * 4) + end + + local device = LCPUNative.CreateDevice(base, MmioSize()) + device.data = { + entity = entity, + nrInputs = nrInputs, + nrOutputs = nrOutputs + } + + function device:AddressToIndex(address) + return ((address - self.Base) / 4) - 1 + end + + function device:InitWireStuff() + local inputNames = {} + local outputNames = {} + + for i = 1, self.data.nrInputs do + inputNames[i] = string.format("Input%d", i) + end + + for i = 1, self.data.nrOutputs do + outputNames[i] = string.format("Output%d", i) + end + + -- this will also pollute the attached entity but that's fine + self.data.wireInputs = WireLib.CreateSpecialInputs(self.data.entity, inputNames, {}, {}) + self.data.wireOutputs = WireLib.CreateSpecialOutputs(self.data.entity, outputNames, {}, {}) + end + + function device:Peek(address) + if address == self.Base then return self.data.nrInputs end + if address == self.Base + 4 then return self.data.nrOutputs end + + local inputIndex = self:AddressToIndex(address) + if inputIndex > self.data.nrInputs then + -- Invalid input register read or trying to read an output register + return 0xffffffff + end + + return self.data.wireInputs[string.format("Input%d", inputIndex)].Value + end + + function device:Poke(address, value) + if address == self.Base or address == self.Base + 4 then return end -- Don't allow writing read-only registers + local outputIndex = self:AddressToIndex(address) - self.data.nrInputs + if outputIndex > self.data.nrInputs then + return + end + WireLib.TriggerOutput(self.data.entity, string.format("Output%d", outputIndex), value) + end + + function device:Reset() + for i = 1, self.data.nrOutputs do + WireLib.TriggerOutput(self.data.entity, string.format("Output%d", i), 0) + end + end + + device:InitWireStuff() + return device +end diff --git a/lua/wire/stools/lcpu.lua b/lua/wire/stools/lcpu.lua index 4aaf7be..70bce6a 100644 --- a/lua/wire/stools/lcpu.lua +++ b/lua/wire/stools/lcpu.lua @@ -49,6 +49,12 @@ if SERVER then end function TOOL:LeftClick_Update(trace) + -- power on (TODO) + if trace.Entity ~= nil then + trace.Entity:Reset() + trace.Entity:PowerOn() + end + return true end function TOOL:MakeEnt(ply, model, Ang, trace) @@ -62,7 +68,7 @@ if SERVER then } ) - self:LeftClick_Update(trace) + --self:LeftClick_Update(trace) return ent end diff --git a/native/projects/lcpu/src/LuaCpu.cpp b/native/projects/lcpu/src/LuaCpu.cpp index ab5d961..3c05956 100644 --- a/native/projects/lcpu/src/LuaCpu.cpp +++ b/native/projects/lcpu/src/LuaCpu.cpp @@ -25,7 +25,6 @@ namespace lcpu { return 0; self->poweredOn = false; - self->system->bus->Reset(); return 0; } @@ -70,7 +69,6 @@ namespace lcpu { system = riscv::System::Create(memorySize); system->OnPowerOff = [&]() { poweredOn = false; - system->bus->Reset(); }; // lame test code. this WILL be removed, I just want this for a quick test diff --git a/test-gmod/main.c b/test-gmod/main.c index 207b6dc..713a631 100644 --- a/test-gmod/main.c +++ b/test-gmod/main.c @@ -1,8 +1,28 @@ -// a simple test program - this version talks to a device -// that is implemented in GLua thanks to the LCPU native addon +// Test program for lcpu #include #include +// MMIO types + +/// This structure is at WIRE_BASE +typedef struct { + uint32_t nrInputs; + uint32_t nrOutputs; +} WireDevice_Header; + +#define WIRE_BASE 0x11310000 +#define WIRE_HEADER ((volatile WireDevice_Header*)WIRE_BASE) +#define WIRE_IO_BASE WIRE_BASE + sizeof(WireDevice_Header) +#define WIRE_OUTPUT_BASE WIRE_IO_BASE + (WIRE_HEADER->nrInputs * sizeof(uint32_t)) +#define WIRE_INPUT(i) *(volatile const uint32_t*)(WIRE_IO_BASE + (i * sizeof(uint32_t))) +#define WIRE_OUTPUT(i) *(volatile uint32_t*)(WIRE_OUTPUT_BASE + (i * sizeof(uint32_t))) + +#define SYSCON *(volatile uint32_t*)0x11100000 + +#define UART_BASE 0x10000000 +#define UART_DATA *(volatile uint32_t*)UART_BASE +#define UART_STATUS UART_DATA + uint32_t strlen(const char* str) { if(!str) return 0; @@ -12,15 +32,6 @@ uint32_t strlen(const char* str) { return c - str; } -#define GLUA_DEVICE_BASE 0x11300000 // base address of the lua test device -#define GLUA_DEVICE_WORLDTIME *(volatile uint32_t*)GLUA_DEVICE_BASE // world time register (read only) -#define GLUA_DEVICE_LUAREG *(volatile uint32_t*)(GLUA_DEVICE_BASE + 4) // lua number register (read/write) - -#define SYSCON *(volatile uint32_t*)0x11100000 - -#define UART_BASE 0x10000000 -#define UART_DATA *(volatile uint32_t*)UART_BASE -#define UART_STATUS UART_DATA void putc(char c) { UART_DATA = (uint32_t)c; @@ -74,8 +85,6 @@ void vprintf(const char* format, va_list val) { case '%': if(format[i + 1] == '%') putc('%'); - if(format[i+1] == '\0') - return; switch(format[i + 1]) { case 'i': case 'd': { @@ -115,16 +124,21 @@ void printf(const char* format, ...) { } void main() { - puts("fuck you garry I win\n"); + printf("Wire interface: %d inputs, %d outputs\n", WIRE_HEADER->nrInputs, WIRE_HEADER->nrOutputs); - for(int i = 0; i < 8; ++i) - printf("GLUA_DEVICE_WORLDTIME reading says -> %d\n", GLUA_DEVICE_WORLDTIME); + for(uint32_t i = 0; i < WIRE_HEADER->nrInputs; ++i) + printf("Wire input %d: value %d\n", i, WIRE_INPUT(i)); - // try writing to it - GLUA_DEVICE_LUAREG = 0x1234; - - for(int i = 0; i < 8; ++i) - printf("GLUA_DEVICE_LUAREG reading says -> %d\n", GLUA_DEVICE_LUAREG); + for(uint32_t i = 0; i < WIRE_HEADER->nrOutputs; ++i) { + printf("Setting wire output %d\n", i); + WIRE_OUTPUT(i) = 10 + i; + } +#if 1 + for(;;) + ; + __builtin_unreachable(); +#else SYSCON = 0x5555; +#endif }