From 1f112427624ae17932c06dbe7719c4fc1258cd5c Mon Sep 17 00:00:00 2001 From: David Goeke Date: Sat, 18 Jul 2026 19:28:11 -0500 Subject: [PATCH] CAR: added stack operations, call, ret, jmp and some other random stuff --- .gitignore | 6 ++++ README.md | 30 +++++++++++------ fpu.c | 88 +++++++++++++++++++++++++++++++++++++++++++++++++ helpers.c | 13 ++++++++ include/defs.h | 67 +++++++++++++++++++++++++++++++++---- load_store.c | 55 +++++++++++++++++++++++++++++++ misc.c | 49 +++++++++++++++++++++++++++ operations.c | 2 -- run | 4 +-- vm | Bin 0 -> 73344 bytes 10 files changed, 294 insertions(+), 20 deletions(-) create mode 100644 .gitignore create mode 100644 fpu.c create mode 100644 misc.c create mode 100755 vm diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4350edd --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ + +*.o + +*.helper + +!.gitignore diff --git a/README.md b/README.md index a04cbb7..768febb 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,19 @@ -#Cool ARM Ripoff +# Cool ARM Ripoff *A simple (and ridiculous) virtual machine/CPU made for no real reason/purpose.* ***Written in C, made by David Goeke.*** -###Current features: +### Current features: - few to none. - 32 general purpose registers - 32-bit addresses -###Needed features: -- push/pop -- call/ret -- jmp/conditional jmps -- store high 11 bits of an immediate in memory +### Needed features: +- conditional jmps -###instuctions (arithmetic): +### Instuctions (arithmetic): - add - sub (subtract) - mul (multiply) @@ -31,8 +28,13 @@ - xor (logical XOR) - not (logical NOT/invertion) - xchg (exchange registers) +- fp_add (add floating point values in registers) +- fp_sub (subtract floating point values in registers) +- fp_mul (multiply floating point values in registers) +- fp_div (divide floating point values in registers) +- fp_mod (modulus (division remainder) floating point values in registers) -###Insutrctions (load/store): +### Insutrctions (load/store): - ldr (load register with value from memory, using the program counter as the base) - lds (load register with value from memory, using the stack pointer as the base) - ldb (load register with value from memory, using a specified register as the base) @@ -43,6 +45,14 @@ - ldh (load high 11 bits of register with immediate) - sti (store a 21-bit immediate at memory, using the program counter as the base) - stj (store a 21-bit immediate at memory, using the stack pointer as the base) +- push (push register onto stack) +- pushi (push 21-bit immediate onto stack) +- pop (pop value from stack into register) +- sth (stroe high 11 bits of register to memory, using pc as the base) +- stk (stroe high 11 bits of register to memory, using sp as the base) -###Instructions (misc): +### Instructions (misc): - nop (do nothing) +- call (call subroutine/function) +- ret (return from subroutine/function) +- jmp (jump to memory address) diff --git a/fpu.c b/fpu.c new file mode 100644 index 0000000..e39b67e --- /dev/null +++ b/fpu.c @@ -0,0 +1,88 @@ +/* + * Source file for floating point operations. + * + * CAR (Cool ARM Ripoff) copyright (c) 2026 David J Goeke. All rights reserved. + * Unauthorized (re)distribution is prohibited. +*/ + +// This is mostly just wrappers around the hardware/system FPU. +// We don't do any calculations ourselves. + +#include "include/defs.h" + +DEFINE_OP(fp_add) +{ + register_instruction_t i = convert_raw2register(ri); + + uint8_t dest = i.dest; + uint8_t src1 = i.src1; + uint8_t src2 = i.src2; + + state->gprs[dest] = (uint32_t) ((float) state->gprs[src1] + (float) state->gprs[src2]); + + OP_RETURN_NORMAL +} + +DEFINE_OP(fp_sub) +{ + register_instruction_t i = convert_raw2register(ri); + + uint8_t dest = i.dest; + uint8_t src1 = i.src1; + uint8_t src2 = i.src2; + + state->gprs[dest] = (uint32_t) ((float) state->gprs[src1] - (float) state->gprs[src2]); + + OP_RETURN_NORMAL +} + +DEFINE_OP(fp_mul) +{ + register_instruction_t i = convert_raw2register(ri); + + uint8_t dest = i.dest; + uint8_t src1 = i.src1; + uint8_t src2 = i.src2; + + state->gprs[dest] = (uint32_t) ((float) state->gprs[src1] * (float) state->gprs[src2]); + + OP_RETURN_NORMAL +} + +DEFINE_OP(fp_div) +{ + register_instruction_t i = convert_raw2register(ri); + + uint8_t dest = i.dest; + uint32_t src1 = state->gprs[i.src1]; + uint32_t src2 = state->gprs[i.src2]; + + if (src1 != 0 && src2 != 0) + { + state->gprs[dest] = (uint32_t) ((float) src1 / (float) src2); + OP_RETURN_NORMAL + } + + state->gprs[dest] = 0xffffffff; + + OP_RETURN_NORMAL +} + +DEFINE_OP(fp_mod) +{ + register_instruction_t i = convert_raw2register(ri); + + uint8_t dest = i.dest; + uint32_t src1 = state->gprs[i.src1]; + uint32_t src2 = state->gprs[i.src2]; + + if (src1 != 0 && src2 != 0) + { + state->gprs[dest] = (uint32_t) ((float) src1 / (float) src2); + OP_RETURN_NORMAL + } + + state->gprs[dest] = 0xffffffff; + + OP_RETURN_NORMAL +} diff --git a/helpers.c b/helpers.c index 1160a2d..c2e7525 100644 --- a/helpers.c +++ b/helpers.c @@ -4,10 +4,23 @@ * CAR (Cool ARM Ripoff) copyright (c) 2026 David J Goeke. All rights reserved. * Unauthorized (re)distribution is prohibited. */ +#include #include #include "include/defs.h" +void _push(uint32_t v, pstate_t* state) +{ + state->sp--; + memcpy(state->memory + state->sp, &v, sizeof(v)); +} + +void _pop(uint32_t* a, pstate_t* state) +{ + memcpy(a, state->memory + state->sp, sizeof(*a)); + state->sp++; +} + void dump_regs(pstate_t* s) { printf("r0: 0x%08x r1: 0x%08x r2: 0x%08x, r3: 0x%08x\n", s->gprs[0], s->gprs[1], s->gprs[2], s->gprs[3]); diff --git a/include/defs.h b/include/defs.h index 3b7d8ea..25ce167 100644 --- a/include/defs.h +++ b/include/defs.h @@ -5,7 +5,8 @@ #include #include -#include +#include + //#define _DEBUG @@ -27,6 +28,22 @@ int processor_start(pstate_t* state); void dump_regs(pstate_t* s); +void _push(uint32_t v, pstate_t* state); +void _pop(uint32_t* a, pstate_t* state); + + +// returns !error (true on no error, false on error) { true on safe address, false on invalid address } +static inline bool check_addr(uint32_t addr, pstate_t* state) +{ + if (addr < state->memory_size) + { + return true; + } + + fprintf(stderr, "[ERROR]: memory address out of range: address 0x%x with memory size 0x%x\n", addr, (uint32_t) state->memory_size); + return false; +} + #define FLAG_CARRY (1 << 0) #define FLAG_SIGN (1 << 1) #define FLAG_OVERFLOW (1 << 2) @@ -116,21 +133,31 @@ typedef struct pstate_t* state) static const op_return_t _OP_RETV_NORMAL = { .do_inc_pc = true, .exiting_error = false }; -static const op_return_t _OP_RETV_EXITING_ERROR = { .do_inc_pc = true, .exiting_error = true }; +static const op_return_t _OP_RETV_EXITING_ERROR = { .do_inc_pc = true, .exiting_error = true }; +static const op_return_t _OP_RETV_NO_INC_PC = { .do_inc_pc = false, .exiting_error = false }; #define OP_RETURN_NORMAL return _OP_RETV_NORMAL; +#define OP_RETURN_NO_INC_PC return _OP_RETV_NO_INC_PC; #define OP_RETURN_EXITING_ERROR return _OP_RETV_EXITING_ERROR; typedef op_return_t (*op)(raw_instruction_t*, pstate_t*); -static DEFINE_OP(nop) -{ - OP_RETURN_NORMAL -} +DEFINE_OP(nop); + +DEFINE_OP(call); +DEFINE_OP(ret); + +DEFINE_OP(jmp); static const op misc_ops[] = { op_nop, + + op_call, + op_ret, + + op_jmp, + op_jb, }; static const size_t NUM_MISC_OPCODES = sizeof(misc_ops) / sizeof(misc_ops[0]); @@ -152,6 +179,12 @@ DEFINE_OP(bnot); DEFINE_OP(xchg); +DEFINE_OP(fp_add); +DEFINE_OP(fp_sub); +DEFINE_OP(fp_mul); +DEFINE_OP(fp_div); +DEFINE_OP(fp_mod); + static const op reg_ops[] = { op_add, @@ -171,6 +204,12 @@ static const op reg_ops[] = op_bnot, op_xchg, + + op_fp_add, + op_fp_sub, + op_fp_mul, + op_fp_div, + op_fp_mod, }; static const size_t NUM_REG_OPCODES = sizeof(reg_ops) / sizeof(reg_ops[0]); @@ -188,6 +227,13 @@ DEFINE_OP(ldh); DEFINE_OP(sti); DEFINE_OP(stj); +DEFINE_OP(push); +DEFINE_OP(pushi); // push immediate +DEFINE_OP(pop); + +DEFINE_OP(sth); +DEFINE_OP(stk); + static const op ls_ops[] = { op_ldr, @@ -203,6 +249,15 @@ static const op ls_ops[] = op_sti, op_stj, + + op_push, + op_pushi, + op_pop, + + op_sth, // store high 11 bits of immediate at address with base of pc + op_stk, // store high 11 bits of immediate at address with base of sp + + // 1 more }; static const size_t NUM_LS_OPCODES = sizeof(ls_ops) / sizeof(ls_ops[0]); diff --git a/load_store.c b/load_store.c index b0376bf..ed26775 100644 --- a/load_store.c +++ b/load_store.c @@ -160,3 +160,58 @@ DEFINE_OP(stj) OP_RETURN_NORMAL } + +DEFINE_OP(push) +{ + ls_instruction_t i = convert_raw2ls(ri); + + _push(state->gprs[i.reg], state); + + OP_RETURN_NORMAL +} + +DEFINE_OP(pushi) +{ + ls_instruction_t i = convert_raw2ls(ri); + + _push(i.imm, state); + + OP_RETURN_NORMAL +} + +DEFINE_OP(pop) +{ + ls_instruction_t i = convert_raw2ls(ri); + + uint32_t v; + + _pop(&v, state); + + state->gprs[i.reg] = v; + + OP_RETURN_NORMAL +} + +DEFINE_OP(sth) +{ + ls_instruction_t i = convert_raw2ls(ri); + + uint32_t addr = state->pc + state->gprs[i.reg]; + uint32_t v = (i.imm >> 21) & 0x7ff; + + memcpy(state->memory + addr, &v, sizeof(v)); + + OP_RETURN_NORMAL +} + +DEFINE_OP(stk) +{ + ls_instruction_t i = convert_raw2ls(ri); + + uint32_t addr = state->sp + state->gprs[i.reg]; + uint32_t v = (i.imm >> 21) & 0x7ff; + + memcpy(state->memory + addr, &v, sizeof(v)); + + OP_RETURN_NORMAL +} diff --git a/misc.c b/misc.c new file mode 100644 index 0000000..117411f --- /dev/null +++ b/misc.c @@ -0,0 +1,49 @@ +/* + * Source file for miscellaneous operations. + * + * CAR (Cool ARM Ripoff) copyright (c) 2026 David J Goeke. All rights reserved. + * Unauthorized (re)distribution is prohibited. +*/ + +#include "include/defs.h" + + +DEFINE_OP(nop) +{ + OP_RETURN_NORMAL +} + +DEFINE_OP(call) +{ + misc_instruction_t i = convert_raw2misc(ri); + + _push(state->pc + 1, state); + + uint32_t addr = (uint32_t) (state->pc + (int32_t) i.imm); + + state->pc = addr; + + OP_RETURN_NO_INC_PC +} + +DEFINE_OP(ret) +{ + uint32_t r_addr; + + _pop(&r_addr, state); + + state->pc = r_addr; + + OP_RETURN_NO_INC_PC +} + +DEFINE_OP(jmp) +{ + misc_instruction_t i = convert_raw2misc(ri); + + uint32_t addr = (uint32_t) (state->pc + (int32_t) i.imm); + + state->pc = addr; + + OP_RETURN_NO_INC_PC +} diff --git a/operations.c b/operations.c index 4a3308e..18a5c27 100644 --- a/operations.c +++ b/operations.c @@ -9,8 +9,6 @@ #include "include/defs.h" - - DEFINE_OP(add) { register_instruction_t i = convert_raw2register(ri); diff --git a/run b/run index d6bb6ad..e7b8c9b 100755 --- a/run +++ b/run @@ -4,8 +4,8 @@ # CAR (Cool ARM Ripoff) copyright (c) 2026 David J Goeke. All rights reserved. # Unauthorized (re)distribution is prohibited. -FILES="processor.c load_store.c operations.c helpers.c main.c" -CFLAGS="" #"-g -O0 -fsanitize=address" +FILES="processor.c fpu.c misc.c load_store.c operations.c helpers.c main.c" +CFLAGS="-O2 -Wall" #"-g -O0 -fsanitize=address" TARGET="vm" diff --git a/vm b/vm new file mode 100755 index 0000000000000000000000000000000000000000..2d90529ca08b1562a6997c6d0bd8454733c6d530 GIT binary patch literal 73344 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 QXaAE~wq~)*z*SWL1N7T+oB#j- literal 0 HcmV?d00001