diff --git a/lua/entities/gmod_lcpu_cpu.lua b/lua/entities/gmod_lcpu_cpu.lua index a0ccaac..0783883 100644 --- a/lua/entities/gmod_lcpu_cpu.lua +++ b/lua/entities/gmod_lcpu_cpu.lua @@ -11,6 +11,33 @@ function ENT:Initialize() self:SetSolid(SOLID_VPHYSICS) -- 64 kb of ram for now. self.cpu = LCPUNative.CreateCPU(128 * 1024) + + -- test device framework + -- this is how I ideally want to do things, + -- dunno if it's possible + self.test_device = LCPUNative.CreateDevice() + print(self.test_device) + self.test_device:SetBase(0x12000000) + self.test_device:SetSize(0x10) + function self.test_device:Clock() + print("TestDevice Clock()") + end + + function self.test_device:Peek(address) + if address == 0x12000000 then + -- it a test! + return CurTime() + end + return 0x10000 + end + + function self.test_device:Poke(address, value) + if address == 0x12000000 then + print("What you doing?") + end + end + + self.cpu:AttachDevice(self.test_device) end function ENT:Think() diff --git a/lua/lcpu/upload_lib.lua b/lua/lcpu/upload_lib.lua new file mode 100644 index 0000000..a51f367 --- /dev/null +++ b/lua/lcpu/upload_lib.lua @@ -0,0 +1,2 @@ +-- upload library +-- (TODO) diff --git a/native/projects/lcpu/CMakeLists.txt b/native/projects/lcpu/CMakeLists.txt index ed11c9d..131bbe7 100644 --- a/native/projects/lcpu/CMakeLists.txt +++ b/native/projects/lcpu/CMakeLists.txt @@ -4,8 +4,7 @@ add_library(lcpu_native SHARED src/main.cpp src/LcpuGlobals.cpp src/LuaCpu.cpp - #src/LuaDevice.cpp - + src/LuaDevice.cpp src/SourceSink.cpp ) diff --git a/native/projects/lcpu/src/LcpuGlobals.cpp b/native/projects/lcpu/src/LcpuGlobals.cpp index ce8d5c7..dccfe87 100644 --- a/native/projects/lcpu/src/LcpuGlobals.cpp +++ b/native/projects/lcpu/src/LcpuGlobals.cpp @@ -1,35 +1,30 @@ #include "LcpuGlobals.hpp" -#include - #include "LuaCpu.hpp" #include "LuaDevice.hpp" -/// This should be bumped on any incompatible change to the native bindings -/// that would break older Lua code, or requires newer Lua code to run. -#define LCPU_MODULE_VERSION 1 + LUA_FUNCTION(LCPUNative_CreateCPU) { LUA->CheckType(1, GarrysMod::Lua::Type::Number); - auto memorySize = (u32)std::round(LUA->GetNumber(1)); + auto memorySize = static_cast(LUA->GetNumber(1)); // TODO: There's probably a way to like, ensure a per-player max. - if(memorySize > (64 * 1024 * 1024)) { + if(memorySize > (64 * 1024 * 1024)) LUA->ThrowError("Over RAM size limit."); - } LuaCpu::Create(LUA, memorySize); return 1; } LUA_FUNCTION(LCPUNative_CreateDevice) { - //LuaDevice::Create(LUA); - return 0;//1; + LuaDevice::Create(LUA); + return 1; } void GlobalsBind(GarrysMod::Lua::ILuaBase* LUA) { LuaCpu::Bind(LUA); - //LuaDevice::Bind(LUA); + // LuaDevice::Bind(LUA); // clang-format off LUA->PushSpecial(GarrysMod::Lua::SPECIAL_GLOB); diff --git a/native/projects/lcpu/src/LcpuGlobals.hpp b/native/projects/lcpu/src/LcpuGlobals.hpp index 2779590..68ad63e 100644 --- a/native/projects/lcpu/src/LcpuGlobals.hpp +++ b/native/projects/lcpu/src/LcpuGlobals.hpp @@ -1,3 +1,7 @@ #include "LuaHelpers.hpp" +/// This should be bumped on any incompatible change to the native bindings +/// that would break older Lua code, or requires newer Lua code to run. +#define LCPU_MODULE_VERSION 1 + void GlobalsBind(GarrysMod::Lua::ILuaBase* LUA); diff --git a/native/projects/lcpu/src/LuaCpu.cpp b/native/projects/lcpu/src/LuaCpu.cpp index 89a52e2..3767ce1 100644 --- a/native/projects/lcpu/src/LuaCpu.cpp +++ b/native/projects/lcpu/src/LuaCpu.cpp @@ -34,67 +34,56 @@ struct SimpleUartDevice : public riscv::Bus::MmioDevice { LUA_CLASS_BIND_VARIABLES_IMPLEMENT(LuaCpu); LUA_MEMBER_FUNCTION_IMPLEMENT(LuaCpu, PoweredOn) { - LUA_CLASS_GET(LuaCpu, self, 1); + auto self = LUA_CLASS_GET(LuaCpu)(1); LUA->PushBool(self->poweredOn); return 1; } LUA_MEMBER_FUNCTION_IMPLEMENT(LuaCpu, Cycle) { - LUA_CLASS_GET(LuaCpu, self, 1); - [&self]() { - if(!self->poweredOn) - return; - self->system->Step(); - }(); + auto self = LUA_CLASS_GET(LuaCpu)(1); + if(!self->poweredOn) + return 0; + self->system->Step(); return 0; } LUA_MEMBER_FUNCTION_IMPLEMENT(LuaCpu, PowerOff) { - LUA_CLASS_GET(LuaCpu, self, 1); - [&self]() { - if(!self->poweredOn) - return; - - self->poweredOn = false; - self->system->bus->Reset(); - }(); + auto self = LUA_CLASS_GET(LuaCpu)(1); + if(!self->poweredOn) + return 0; + self->poweredOn = false; + self->system->bus->Reset(); return 0; } LUA_MEMBER_FUNCTION_IMPLEMENT(LuaCpu, PowerOn) { - LUA_CLASS_GET(LuaCpu, self, 1); - [&self]() { - if(self->poweredOn) - return; + auto self = LUA_CLASS_GET(LuaCpu)(1); + if(self->poweredOn) + return 0; - self->poweredOn = true; - self->system->bus->Reset(); - }(); + self->poweredOn = true; + self->system->bus->Reset(); return 0; } LUA_MEMBER_FUNCTION_IMPLEMENT(LuaCpu, Reset) { - LUA_CLASS_GET(LuaCpu, self, 1); - [&self]() { self->system->bus->Reset(); }(); + auto self = LUA_CLASS_GET(LuaCpu)(1); + self->system->bus->Reset(); return 0; } LUA_MEMBER_FUNCTION_IMPLEMENT(LuaCpu, AttachDevice) { - LUA_CLASS_GET(LuaCpu, self, 1); - bool result = false; -#if 0 - [&]() { - LUA_CLASS_GET(LuaDevice, device, 2); - if(!device) - return; // the bus is safe against this possibility, but - // I'd rather be doubly-safe tbh + auto self = LUA_CLASS_GET(LuaCpu)(1); + auto device = LUA_CLASS_GET(LuaDevice)(1); + + // the bus is safe against this possibility, but + // I'd rather be doubly-safe tbh + if(!device) + LUA->ThrowError("Null device pointer"); - // Attach it - result = self->system->bus->AttachDevice(static_cast(device)); - }(); -#endif - LUA->PushBool(result); + // Attach it + LUA->PushBool(self->system->bus->AttachDevice(static_cast(device))); return 1; } @@ -116,7 +105,7 @@ void LuaCpu::Create(GarrysMod::Lua::ILuaBase* LUA, u32 memorySize) { // lame test code. this WILL be removed, I just want this for a quick test cpuWrapper->system->bus->AttachDevice(new SimpleUartDevice); - auto fp = std::fopen("/home/lily/test.bin", "rb"); + auto fp = std::fopen("/home/lily/test-gmod.bin", "rb"); if(fp) { std::fseek(fp, 0, SEEK_END); auto len = std::ftell(fp); @@ -130,8 +119,6 @@ void LuaCpu::Create(GarrysMod::Lua::ILuaBase* LUA, u32 memorySize) { } LuaCpu::LuaCpu(u32 memorySize) { - lucore::LogInfo("in LuaCpu::LuaCpu(0x{:08x})\n", memorySize); - poweredOn = true; system = riscv::System::Create(memorySize); system->OnPowerOff = [&]() { diff --git a/native/projects/lcpu/src/LuaDevice.cpp b/native/projects/lcpu/src/LuaDevice.cpp index 0fffa0e..e35e11d 100644 --- a/native/projects/lcpu/src/LuaDevice.cpp +++ b/native/projects/lcpu/src/LuaDevice.cpp @@ -1,15 +1,107 @@ #include "LuaDevice.hpp" +#include "LuaHelpers.hpp" + LUA_CLASS_BIND_VARIABLES_IMPLEMENT(LuaDevice); +LUA_MEMBER_FUNCTION_IMPLEMENT(LuaDevice, SetBase) { + auto self = LUA_CLASS_GET(LuaDevice)(1); + if(self->bus) + LUA->ThrowError("Do not call this on an attached device"); + self->base = static_cast(LUA->CheckNumber(2)); + return 0; +} + +LUA_MEMBER_FUNCTION_IMPLEMENT(LuaDevice, SetSize) { + auto self = LUA_CLASS_GET(LuaDevice)(1); + if(self->bus) + LUA->ThrowError("Do not call this on an attached device"); + self->size = static_cast(LUA->CheckNumber(2)); + return 0; +} + +bool LuaDevice::Clocked() const { + // Even if Lua devices may not have a clock handler + // installed there's no real non-awful way to check, + // so just lie and say yes always. + return true; +} + +void LuaDevice::Clock() { + LuaState->PushUserType(this, __lua_typeid); + LuaState->GetField(-2, "Clock"); + if(!LuaState->IsType(-1, GarrysMod::Lua::Type::Function)) { + LuaState->Pop(2); + return; + } + LuaState->Push(-2); // "self" + LuaState->Call(1, 0); + LuaState->Pop(); +} + +riscv::Address LuaDevice::Base() const { + return base; +} + +riscv::Address LuaDevice::Size() const { + return base; +} + +u32 LuaDevice::Peek(riscv::Address address) { + LuaState->PushUserType(this, __lua_typeid); + LuaState->GetField(-2, "Peek"); + if(!LuaState->IsType(-1, GarrysMod::Lua::Type::Function)) { + LuaState->Pop(2); + return -1; + } + LuaState->Push(-2); // "self" + LuaState->PushNumber(address); + LuaState->Call(2, 1); + auto result = LuaState->GetNumber(-1); + LuaState->Pop(); + return static_cast(result); +} + +void LuaDevice::Poke(riscv::Address address, u32 value) { + LuaState->PushUserType(this, __lua_typeid); + LuaState->GetField(-2, "Poke"); + if(!LuaState->IsType(-1, GarrysMod::Lua::Type::Function)) { + LuaState->Pop(2); + return; + } + LuaState->Push(-2); // "self" + LuaState->PushNumber(address); + LuaState->PushNumber(value); + LuaState->Call(3, 0); + LuaState->Pop(); +} + +void LuaDevice::Reset() { + LuaState->PushUserType(this, __lua_typeid); + LuaState->GetField(-2, "Reset"); + if(!LuaState->IsType(-1, GarrysMod::Lua::Type::Function)) + return; + + LuaState->Push(-2); // "self" + LuaState->Call(1, 0); + LuaState->Pop(); +} + +LuaDevice::~LuaDevice() = default; + void LuaDevice::Bind(GarrysMod::Lua::ILuaBase* LUA) { // clang-format off + // TODO: I need to figure out how to like, set up metamethod stuff + // so it all properly works. LUA_CLASS_BIND_BEGIN(LuaDevice); - // todo handlers + LUA_SET_C_FUNCTION(SetBase) + LUA_SET_C_FUNCTION(SetSize) LUA_CLASS_BIND_END(); // clang-format on } void LuaDevice::Create(GarrysMod::Lua::ILuaBase* LUA) { - LUA->PushUserType(new LuaDevice(), __lua_typeid); + auto device = new LuaDevice(); + device->LuaState = LUA; + LUA->PushUserType(device, __lua_typeid); } diff --git a/native/projects/lcpu/src/LuaDevice.hpp b/native/projects/lcpu/src/LuaDevice.hpp index c983c53..19ecc18 100644 --- a/native/projects/lcpu/src/LuaDevice.hpp +++ b/native/projects/lcpu/src/LuaDevice.hpp @@ -10,8 +10,14 @@ struct LuaDevice : public riscv::Bus::MmioDevice { static void Bind(GarrysMod::Lua::ILuaBase* LUA); static void Create(GarrysMod::Lua::ILuaBase* LUA); - riscv::Address Base() const override { return base; } - riscv::Address Size() const override { return size; } // I think this is right? + ~LuaDevice(); + + bool Clocked() const override; + void Clock() override; + void Reset() override; + + riscv::Address Base() const override; + riscv::Address Size() const override; u32 Peek(riscv::Address address) override; void Poke(riscv::Address address, u32 value) override; @@ -24,14 +30,10 @@ struct LuaDevice : public riscv::Bus::MmioDevice { LUA_MEMBER_FUNCTION(SetBase); LUA_MEMBER_FUNCTION(SetSize); - LUA_MEMBER_FUNCTION(SetClockHandler); - LUA_MEMBER_FUNCTION(SetResetHandler); - LUA_MEMBER_FUNCTION(SetPeekHandler); - LUA_MEMBER_FUNCTION(SetPokeHandler); + // GetBase/GetSize? riscv::Address base {}; riscv::Address size {}; - // ...? GarrysMod::Lua::ILuaBase* LuaState; }; diff --git a/native/projects/lcpu/src/LuaHelpers.hpp b/native/projects/lcpu/src/LuaHelpers.hpp index 980983b..f818f2e 100644 --- a/native/projects/lcpu/src/LuaHelpers.hpp +++ b/native/projects/lcpu/src/LuaHelpers.hpp @@ -2,6 +2,7 @@ #pragma once #include + #include // These are like the official GMOD header LUA_FUNCTION but allow forward declaration @@ -17,20 +18,25 @@ #define LUA_MEMBER_FUNCTION_IMPLEMENT(CLASS, FUNC) int CLASS::FUNC##__ImpStatic(GarrysMod::Lua::ILuaBase* LUA) -// will make a "self" variable with the class -#define LUA_CLASS_GET(T, name, stackPos) \ - LUA->CheckType(stackPos, T::__lua_typeid); \ - auto name = LUA->GetUserType(stackPos, T::__lua_typeid); \ - if(!name) { \ - LUA->ThrowError("nullptr " #T " passed to function which requires a valid instance"); \ +// this synthesizes a lambda which takes the stack argument to get. this can actually also be +// stored as a variable for later usage (... if you so desire?) +#define LUA_CLASS_GET(T) \ + [LUA](int stackPos) { \ + LUA->CheckType(stackPos, T::__lua_typeid); \ + return LUA->GetUserType(stackPos, T::__lua_typeid); \ } +// This class binding package always implements the __gc metamethod +// to free any C++ object bound to Lua. + +// Declare required binding variables. #define LUA_CLASS_BIND_VARIABLES(ACCESS_LEVEL) \ public: \ static int __lua_typeid; \ ACCESS_LEVEL: \ LUA_MEMBER_FUNCTION(__gc); +// Implement required binding variables (typically in a .cpp file). #define LUA_CLASS_BIND_VARIABLES_IMPLEMENT(T) \ int T::__lua_typeid = 0; \ LUA_MEMBER_FUNCTION_IMPLEMENT(T, __gc) { \ @@ -43,6 +49,8 @@ return 0; \ } +// Begin the Bind() method of a class. This just sets up boilerplate +// and required things to setup a class. #define LUA_CLASS_BIND_BEGIN(T) \ T::__lua_typeid = LUA->CreateMetaTable(#T); \ LUA->PushSpecial(GarrysMod::Lua::SPECIAL_REG); \ @@ -53,13 +61,15 @@ LUA->SetField(-2, "__index"); \ LUA_SET_C_FUNCTION(__gc) +// End the Bind() method. #define LUA_CLASS_BIND_END() LUA->Pop(); -// Helpers for lua functions +// Set a C function as a field. #define LUA_SET_C_FUNCTION(name) \ LUA->PushCFunction(name); \ LUA->SetField(-2, #name); +// Set a C function as a field with an alternative field name. #define LUA_SET_C_FUNCTION_NAME(name, altName) \ LUA->PushCFunction(name); \ LUA->SetField(-2, altName); diff --git a/native/projects/lcpu/src/main.cpp b/native/projects/lcpu/src/main.cpp index 0894afe..c671f09 100644 --- a/native/projects/lcpu/src/main.cpp +++ b/native/projects/lcpu/src/main.cpp @@ -8,7 +8,7 @@ GMOD_MODULE_OPEN() { lucore::Logger::The().AttachSink(lcpu::SourceSink::The()); - lucore::LogInfo("LCPU Native Module!"); + lucore::LogInfo("LCPU Native Module! (ModuleVersion {})", LCPU_MODULE_VERSION); GlobalsBind(LUA); return 0; } diff --git a/test-gmod/Makefile b/test-gmod/Makefile new file mode 100644 index 0000000..33e4f65 --- /dev/null +++ b/test-gmod/Makefile @@ -0,0 +1,53 @@ +PROJECT = test + +# where your rv32 toolchain is +TCPATH = /home/lily/bin/riscv/bin +PREFIX = $(TCPATH)/riscv32-unknown-elf + +CC = $(PREFIX)-gcc +CXX = $(PREFIX)-g++ + +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 + +.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) + +$(PROJECT).bin : $(PROJECT).elf + $(PREFIX)-objcopy $^ -O binary $@ + +$(PROJECT).debug.txt : $(PROJECT).elf + $(PREFIX)-objdump -t $^ > $@ + $(PREFIX)-objdump -S $^ >> $@ + +# Compile rules + +%.o: %.cpp + $(CXX) -c $(CXXFLAGS) $< -o $@ + +%.o: %.c + $(CC) -c $(CCFLAGS) $< -o $@ + +%.o: %.S + $(CC) -x assembler-with-cpp -march=rv32ima -mabi=ilp32 -c $< -o $@ + + diff --git a/test-gmod/binary.ld b/test-gmod/binary.ld new file mode 100644 index 0000000..076520e --- /dev/null +++ b/test-gmod/binary.ld @@ -0,0 +1,73 @@ +__heap_size = 0x1000; +__stack_size = 0x1000; + +ENTRY(_start) + +SECTIONS +{ + . = 0x80000000; + .text : ALIGN(16) { + __TEXT_BEGIN__ = .; + *(.initial_jump) + *(.entry.text) + *(.init.literal) + *(.init) + *(.text) + *(.literal .text .literal.* .text.* .stub) + *(.out_jump.literal.*) + *(.out_jump.*) + __TEXT_END__ = .; + } + + /* If we're on a newer compiler */ + /DISCARD/ : + { + *(.interp) + *(.dynsym) + *(.dynstr) + *(.header) + } : phdr + + .data : ALIGN(16) { + __DATA_BEGIN__ = .; + *(.rodata) + *(.rodata.*) + *(.gnu.linkonce.r.*) + *(.rodata1) + *(.dynsbss) + *(.gnu.linkonce.sb.*) + *(.scommon) + *(.gnu.linkonce.sb2.*) + *(.sbss) + *(.sbss.*) + *(.sbss2) + *(.sbss2.*) + *(.dynbss) + *(.data) + *(.data.*) + *(.got) + *(.got.*) + __DATA_END__ = .; + } + + .bss : ALIGN( 16 ) { + __BSS_BEGIN__ = .; + *(.bss) /* Tricky: BSS needs to be allocated but not sent. GCC Will not populate these for calculating data size */ + *(.bss.*) + __BSS_END__ = .; + } + + .heap : ALIGN( 16 ) { + _sheap = .; + . = . + __heap_size; + _eheap = .; + } + + .stack : ALIGN( 16 ) { + _estack = .; + . = . + __stack_size; + _sstack = .; + } +} + + diff --git a/test-gmod/main.c b/test-gmod/main.c new file mode 100644 index 0000000..0b4fbc2 --- /dev/null +++ b/test-gmod/main.c @@ -0,0 +1,122 @@ +// a simple test program - this version would talk to a device +// written in GLua +#include +#include + +uint32_t strlen(const char* str) { + if(!str) + return 0; + const char* c = str; + while(*c++) + ; + return c - str; +} + +#define GLUA_DEVICE *(volatile uint32_t*)0x12000000 + +#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; +} + +__attribute__((noinline)) void puts(const char* str) { + const uint32_t length = strlen(str); + for(uint32_t i = 0; i < length; ++i) + putc(str[i]); +} + +int itoa(int value, char* sp, int radix) { + char tmp[16]; + char* tp = tmp; + int i; + unsigned v; + + int sign = (radix == 10 && value < 0); + if(sign) + v = -value; + else + v = (unsigned)value; + + while(v || tp == tmp) { + i = v % radix; + v /= radix; + if(i < 10) + *tp++ = i + '0'; + else + *tp++ = i + 'a' - 10; + } + + int len = tp - tmp; + + if(sign) { + *sp++ = '-'; + len++; + } + + while(tp > tmp) + *sp++ = *--tp; + + *sp = '\0'; + return len; +} + +void vprintf(const char* format, va_list val) { + const int fl = strlen(format); + for(int i = 0; i < fl; ++i) { + switch(format[i]) { + case '%': + if(format[i+1] == '%') + putc('%'); + switch(format[i+1]) { + case 'i': + case 'd': { + char a[32]; + itoa(va_arg(val, uint32_t), &a[0], 10); + + const int al = strlen(a); + for(int j = 0; j < al; ++j) + putc(a[j]); + } break; + + case 's': { + char* p = va_arg(val, char*); + if(!p) + puts("(null)"); + else + puts(p); + }; + + default: + putc(' '); + break; + } + break; + default: + putc(format[i]); + break; + } + } +} + +void printf(const char* format, ...) { + va_list val; + va_start(val, format); + vprintf(format, val); + va_end(val); +} + + + +void main() { + puts("fuck you garry I win"); + + for(int i = 0; i < 256; ++i) + printf("uhh %d\n", GLUA_DEVICE); + + SYSCON = 0x5555; +} diff --git a/test-gmod/start.S b/test-gmod/start.S new file mode 100644 index 0000000..be687bc --- /dev/null +++ b/test-gmod/start.S @@ -0,0 +1,13 @@ +# Simple bare-metal RISCV startup code. + +.section .initial_jump +.global _start + +.extern main + +.align 4 +_start: + la sp, _sstack # set up C stack + addi sp,sp,-16 # ... + sw ra,12(sp) # ... + jal ra, main # jump to C code!