all repos — xyw @ 7df01a68057768f8bebb8c7b4e271625e553c503

A minimal virtual machine and assembler for terminals.

Refactored macros.
h3rald h3rald@h3rald.com
Tue, 02 Dec 2025 15:54:27 +0100
commit

7df01a68057768f8bebb8c7b4e271625e553c503

parent

b79ea909ae21b1133725d29c98f59f5dfff27ee9

1 files changed, 106 insertions(+), 260 deletions(-)

jump to
M xywrun.cxywrun.c

@@ -39,6 +39,8 @@ xyw_byte xyw_memory[MEMORY_SIZE];

xyw_byte system_stack[STACK_SIZE]; xyw_byte user_stack[STACK_SIZE]; +#define DIRECT_PAGE (xyw_memory[SYSTEM_PAGE] << 8) + //// 1-byte pointers and registers xyw_byte *s = &xyw_memory[SYSTEM_S]; xyw_byte *u = &xyw_memory[SYSTEM_U];

@@ -125,28 +127,6 @@ xyw_dbg(" => %04X\n", val);

(*u) += 2; } -//// Instructions - -static void PSH() -{ - (*pc)++; - xyw_byte val = xyw_memory[*pc]; - us_push(val); -} - -static void PSHw() -{ - (*pc)++; - xyw_word val = mget(*pc); - us_pushw(val); - (*pc)++; -} - -static void PSHx() { us_push(*x); } -static void PSHy() { us_push(*y); } -static void PSHxw() { us_pushw(*xw); } -static void PSHyw() { us_pushw(*yw); } - static xyw_byte us_pop() { if (*u < 1)

@@ -173,217 +153,101 @@ (*u) -= 2;

return val; } -static void POPx() -{ - if (*u < 1) - { - xyw_memory[SYSTEM_ERROR] = XYW_ERROR_STACK_UNDERFLOW; - return; - } - (*u) -= 1; - *x = xyw_memory[USER_STACK_START + (*u)]; - xyw_memory[USER_STACK_START + (*u)] = 0; -} +// clang-format off -static void POPy() -{ - if (*u < 1) - { - xyw_memory[SYSTEM_ERROR] = XYW_ERROR_STACK_UNDERFLOW; - return; - } - (*u) -= 1; - *y = xyw_memory[USER_STACK_START + (*u)]; - xyw_memory[USER_STACK_START + (*u)] = 0; -} +// w = 0: 1-byte operation +// w = 1: 2-byte operation +// r = 0: value from stack +// r = 1: value from register -static void POPxw() -{ - if (*u < 2) - { - xyw_memory[SYSTEM_ERROR] = XYW_ERROR_STACK_UNDERFLOW; - return; - } - *xw = mget(USER_STACK_START + (*u) - 2); - mset(USER_STACK_START + (*u) - 2, 0); - (*u) -= 2; +//// Operations that support stack and all registers +#define OPC_SR2(opc, body) { \ + case opc: { const xyw_byte w = 0, r = 0, a = us_pop(), b = us_pop(); body; } break; \ + case XYW_MODE_X|opc: { const xyw_byte w = 0, r = 1, a = *x, b = us_pop(); body; } break; \ + case XYW_MODE_Y|opc: { const xyw_byte w = 0, r = 1, a = *y, b = us_pop(); body; } break; \ + case XYW_MODE_X|XYW_MODE_Y|opc: { const xyw_byte w = 0, r = 1, a = *x, b = *y; body; } break; \ + case XYW_MODE_W|opc: { const xyw_byte w = 1, r = 0; xyw_word a = us_popw(); xyw_word b = us_popw(); body; } break; \ + case XYW_MODE_X|XYW_MODE_W|opc: { const xyw_byte w = 1, r = 1; xyw_word a = *xw; xyw_word b = us_popw(); body; } break; \ + case XYW_MODE_Y|XYW_MODE_W|opc: { const xyw_byte w = 1, r = 1; xyw_word a = *yw; xyw_word b = us_popw(); body; } break; \ + case XYW_MODE_X|XYW_MODE_Y|XYW_MODE_W|opc: { const xyw_byte w = 1, r = 1; xyw_word a = *xw; xyw_word b = *yw; body; } break; \ } -static void POPyw() -{ - if (*u < 2) - { - xyw_memory[SYSTEM_ERROR] = XYW_ERROR_STACK_UNDERFLOW; - return; - } - *yw = mget(USER_STACK_START + (*u) - 2); - mset(USER_STACK_START + (*u) - 2, 0); - (*u) -= 2; +//// Operations that support stack and one register +#define OPC_SR1(opc, body) { \ + case opc: { const xyw_byte w = 0, r = 0, a = us_pop(); body; } break; \ + case XYW_MODE_X|opc: { const xyw_byte w = 0, r = 1, a = *x; body; } break; \ + case XYW_MODE_Y|opc: { const xyw_byte w = 0, r = 1, a = *y; body; } break; \ + case XYW_MODE_W|opc: { const xyw_byte w = 1, r = 0; xyw_word a = us_popw(); body; } break; \ + case XYW_MODE_X|XYW_MODE_W|opc: { const xyw_byte w = 1, r = 1; xyw_word a = *xw; body; } break; \ + case XYW_MODE_Y|XYW_MODE_W|opc: { const xyw_byte w = 1, r = 1; xyw_word a = *yw; body; } break; \ } -static void LDRx() { us_push(xyw_memory[*x]); } -static void LDRy() { us_push(xyw_memory[*y]); } -static void LDRxw() { us_pushw(mget(*xw)); } -static void LDRyw() { us_pushw(mget(*yw)); } - -static void LDA() { us_push(xyw_memory[us_popw()]); } -static void LDAw() { us_pushw(mget(us_popw())); } - -static void LDP() -{ - xyw_byte page = xyw_memory[SYSTEM_PAGE]; - xyw_word addr = (page << 8) | us_pop(); - us_push(xyw_memory[addr]); +//// Operations that support one register only and no stack +#define OPC_R1(opc, body) { \ + case XYW_MODE_X|opc: { const xyw_byte w = 0, r = 1, a = *x; body; } break; \ + case XYW_MODE_Y|opc: { const xyw_byte w = 0, r = 1, a = *y; body; } break; \ + case XYW_MODE_X|XYW_MODE_W|opc: { const xyw_byte w = 1, r = 1; xyw_word a = *xw; body; } break; \ + case XYW_MODE_Y|XYW_MODE_W|opc: { const xyw_byte w = 1, r = 1; xyw_word a = *yw; body; } break; \ } -static void LDPw() -{ - xyw_byte page = xyw_memory[SYSTEM_PAGE]; - xyw_word addr = (page << 8) | us_popw(); - us_pushw(mget(addr)); +//// Operations that support stack only +#define OPC_S(opc, body) { \ + case opc: { const xyw_byte w = 0, r = 0; body; } break; \ + case XYW_MODE_W|opc: { const xyw_byte w = 1, r = 0; body; } break; \ } -static void STP(xyw_byte val) -{ - xyw_word addr = (xyw_memory[SYSTEM_PAGE] << 8) | us_pop(); - xyw_memory[addr] = val; +//// POP operations +#define OPC_POP(opc) { \ + case opc: { us_pop(); } break; \ + case XYW_MODE_X|opc: { *x = us_pop(); } break; \ + case XYW_MODE_Y|opc: { *y = us_pop(); } break; \ + case XYW_MODE_W|opc: { us_pop(); } break; \ + case XYW_MODE_X|XYW_MODE_W|opc: { *xw = us_popw(); } break; \ + case XYW_MODE_Y|XYW_MODE_W|opc: { *yw = us_popw(); } break; \ } -static void STPw(xyw_word val) -{ - xyw_word addr = (xyw_memory[SYSTEM_PAGE] << 8) | us_popw(); - mset(addr, val); +//// PSH operations +#define OPC_PSH(opc) { \ + case opc: { (*pc)++; us_push(xyw_memory[*pc]); } break; \ + case XYW_MODE_X|opc: { us_push(*x); } break; \ + case XYW_MODE_Y|opc: { us_push(*y); } break; \ + case XYW_MODE_W|opc: { (*pc)++; us_pushw(mget(*pc)); (*pc)++; } break; \ + case XYW_MODE_X|XYW_MODE_W|opc: { us_pushw(*xw); } break; \ + case XYW_MODE_Y|XYW_MODE_W|opc: { us_pushw(*yw); } break; \ } - -static void JMP() { *pc = us_popw(); } -static void JCN(xyw_byte a) -{ - if (a) - *pc = us_popw(); -} -static void JCNw(xyw_word a) -{ - if (a) - *pc = us_popw(); -} -static void JSR() -{ - xyw_word addr = us_popw(); - ss_pushw(*pc); - *pc = addr; -} -static void RTS() -{ - *pc = ss_popw(); -} - -// clang-format off - -// w = 0: 1-byte operation -// w = 1: 2-byte operation -// r = 0: value from stack -// r = 1: value from register - -//// Operations that support all mode combinations -#define OPC_XYW(opc, init, body) { \ - case opc: { const xyw_byte w = 0, r = 0, a = us_pop(), b = us_pop(); init body; } break; \ - case XYW_MODE_X|opc: { const xyw_byte w = 0, r = 1, a = *x, b = us_pop(); init body; } break; \ - case XYW_MODE_Y|opc: { const xyw_byte w = 0, r = 1, a = *y, b = us_pop(); init body; } break; \ - case XYW_MODE_X|XYW_MODE_Y|opc: { const xyw_byte w = 0, r = 1, a = *x, b = *y; init body; } break; \ - case XYW_MODE_W|opc: { const xyw_byte w = 1, r = 0; xyw_word a = us_popw(); xyw_word b = us_popw(); init body; } break; \ - case XYW_MODE_X|XYW_MODE_W|opc: { const xyw_byte w = 1, r = 1; xyw_word a = *xw; xyw_word b = us_popw(); init body; } break; \ - case XYW_MODE_Y|XYW_MODE_W|opc: { const xyw_byte w = 1, r = 1; xyw_word a = *yw; xyw_word b = us_popw(); init body; } break; \ - case XYW_MODE_X|XYW_MODE_Y|XYW_MODE_W|opc: { const xyw_byte w = 1, r = 1; xyw_word a = *xw; xyw_word b = *yw; init body; } break; \ -} - -//// Operations that support X or Y and W modes only -#define OPC_XorYW(opc, init, body) { \ - case opc: { const xyw_byte w = 0, r = 0, a = us_pop(); init body; } break; \ - case XYW_MODE_X|opc: { const xyw_byte w = 0, r = 1, a = *x; init body; } break; \ - case XYW_MODE_Y|opc: { const xyw_byte w = 0, r = 1, a = *y; init body; } break; \ - case XYW_MODE_W|opc: { const xyw_byte w = 1, r = 0; xyw_word a = us_popw(); init body; } break; \ - case XYW_MODE_X|XYW_MODE_W|opc: { const xyw_byte w = 1, r = 1; xyw_word a = *xw; init body; } break; \ - case XYW_MODE_Y|XYW_MODE_W|opc: { const xyw_byte w = 1, r = 1; xyw_word a = *yw; init body; } break; \ +//// Increment/decrement operations +#define OPC_IDC(opc, delta) { \ + case opc: { us_push(us_pop() + delta);} break; \ + case XYW_MODE_X|opc: { *x = *x + delta; } break; \ + case XYW_MODE_Y|opc: { *y = *y + delta; } break; \ + case XYW_MODE_W|opc: { us_pushw(us_popw() + delta); } break; \ + case XYW_MODE_X|XYW_MODE_W|opc: { *xw = *xw + delta; } break; \ + case XYW_MODE_Y|XYW_MODE_W|opc: { *yw = *yw + delta; } break; \ } -//// Operations that support W mode only -#define OPC_W(opc, init, body) { \ - case opc: { const xyw_byte w = 0, r = 0; init body; } break; \ - case XYW_MODE_W|opc: { const xyw_byte w = 1, r = 0; init body; } break; \ +//// SWP operations +#define OPC_SWP(opc) { \ + case opc: { xyw_byte a = us_pop(); xyw_byte b = us_pop(); us_push(a); us_push(b); } break; \ + case XYW_MODE_W|opc: { xyw_word a = us_popw(); xyw_word b = us_popw(); us_pushw(a); us_pushw(b); } break; \ + case XYW_MODE_X|opc: { xyw_byte a = us_pop(); xyw_byte b = *x; *x = a; us_push(b); } break; \ + case XYW_MODE_Y|opc: { xyw_byte a = us_pop(); xyw_byte b = *y; *y = a; us_push(b); } break; \ + case XYW_MODE_X|XYW_MODE_W|opc: { xyw_word a = us_popw(); xyw_word b = *xw; *xw = a; us_pushw(b); } break; \ + case XYW_MODE_Y|XYW_MODE_W|opc: { xyw_word a = us_popw(); xyw_word b = *yw; *yw = a; us_pushw(b); } break; \ + case XYW_MODE_X|XYW_MODE_Y|opc: { xyw_byte a = *x; xyw_byte b = *y; *x = b; *y = a; } break; \ + case XYW_MODE_X|XYW_MODE_Y|XYW_MODE_W|opc: { xyw_word a = *xw; xyw_word b = *yw; *xw = b; *yw = a; } break; \ } //// "Microcode" similar to Uxn's, to be used with above macros - -// Push a value as word or byte depending on width flag 'w' -#define PUSH(n) do { if(w && !!r) { us_pushw((xyw_word)(n)); } else { us_push((xyw_byte)(n)); } } while(0) -// Push only if a register is not being used -#define PUSHS(n) do { if(!(r)) { PUSH(n); } } while(0) -// Pop value as word or byte depending on width flag 'w' +// Push a value to stack +#define PUSH(n) do { if(w && !!r) { us_pushw(n); } else { us_push(n); } } while(0) +// Pop value from stack #define POP() (w ? us_popw() : (xyw_word)us_pop()) -// Read value as word or byte depending on width flag 'w' -#define READ(addr) (w ? mget(addr) : (xyw_byte)xyw_memory[addr]) +// Read value from address +#define READ(addr) (w && !!r ? mget(addr) : (xyw_byte)xyw_memory[addr]) // Write value as word or byte depending on width flag 'w' -#define WRITE(addr, val) do { if(w) { mset(addr, (xyw_word)(val)); } else { xyw_memory[addr] = (xyw_byte)(val); } } while(0) - - - -static void STA(xyw_byte val) -{ - xyw_word addr = us_popw(); - xyw_memory[addr] = val; -} - -static void STAw(xyw_word val) -{ - xyw_word addr = us_popw(); - mset(addr, val); -} +#define WRITE(addr, val) do { if(w && !!r) { mset(addr, val); } else { xyw_memory[addr] = val; } } while(0) // clang-format on - -//// Macros for instruction implementations -//// TODO: Refactor to use above macros - -// clang-format off -// Infix operations - -// Postfix-ish operations (INC/DEC) -#define PoOP(opcode, op) \ - case opcode: us_push(us_pop() op); break; \ - case XYW_MODE_X|opcode: us_push(*x op); break; \ - case XYW_MODE_Y|opcode: us_push(*y op); break; \ - case XYW_MODE_W|opcode: us_pushw(us_popw() op); break; \ - case XYW_MODE_X|XYW_MODE_W|opcode: us_pushw(*xw op); break; \ - case XYW_MODE_Y|XYW_MODE_W|opcode: us_pushw(*yw op); break; - -// Prefix operations with two operands -#define PrOP2(opcode, op) \ - case opcode: op(us_pop(), us_pop()); break; \ - case XYW_MODE_X|opcode: op(*x, us_pop()); break; \ - case XYW_MODE_Y|opcode: op(*y, us_pop()); break; \ - case XYW_MODE_X|XYW_MODE_Y|opcode: op(*x, *y); break; - -// Prefix operations with two 2-byte operands -#define PrOP2w(opcode, op) \ - case XYW_MODE_W|opcode: op(us_popw(), us_popw()); break; \ - case XYW_MODE_X|XYW_MODE_W|opcode: op(*xw, us_popw()); break; \ - case XYW_MODE_Y|XYW_MODE_W|opcode: op(*yw, us_popw()); break; \ - case XYW_MODE_X|XYW_MODE_Y|XYW_MODE_W|opcode: op(*xw, *yw); break; - -// Prefix operations with one operand -#define PrOP(opcode, op) \ - case opcode: op(us_pop()); break; \ - case XYW_MODE_X|opcode: op(*x); break; \ - case XYW_MODE_Y|opcode: op(*y); break; - -// Prefix operations with one 2-byte operand -#define PrOPw(opcode, op) \ - case XYW_MODE_W|opcode: op(us_popw()); break; \ - case XYW_MODE_X|XYW_MODE_W|opcode: op(*xw); break; \ - case XYW_MODE_Y|XYW_MODE_W|opcode: op(*yw); break; - -// clang-format on - int run(const char *input) {

@@ -423,55 +287,37 @@ switch (xyw_memory[*pc])

{ // clang-format off /* BRK */ case 0x00: break; - /* PSH */ case 0x01: PSH(); break; - /* PSHw */ case XYW_MODE_W|0x01: PSHw(); break; - /* PSHx */ case XYW_MODE_X|0x01: PSHx(); break; - /* PSHy */ case XYW_MODE_Y|0x01: PSHy(); break; - /* PSHxw */ case XYW_MODE_X|XYW_MODE_W|0x01: PSHxw(); break; - /* PSHyw */ case XYW_MODE_Y|XYW_MODE_W|0x01: PSHyw(); break; - /* POP */ case 0x02: us_pop(); break; - /* POPw */ case XYW_MODE_W|0x02: us_popw(); break; - /* POPx */ case XYW_MODE_X|0x02: POPx(); break; - /* POPy */ case XYW_MODE_Y|0x02: POPy(); break; - /* POPxw */ case XYW_MODE_X|XYW_MODE_W|0x02: POPxw(); break; - /* POPyw */ case XYW_MODE_Y|XYW_MODE_W|0x02: POPyw(); break; - /* LDRx */ case XYW_MODE_X|0x03: LDRx(); break; - /* LDRy */ case XYW_MODE_Y|0x03: LDRy(); break; - /* LDRxw */ case XYW_MODE_X|XYW_MODE_W|0x03: LDRxw(); break; - /* LDRyw */ case XYW_MODE_Y|XYW_MODE_W|0x03: LDRyw(); break; - /* LDA */ case 0x04: LDA(); break; - /* LDAw */ case XYW_MODE_W|0x04: LDAw(); break; - /* STA */ PrOP(0x05, STA); - /* STAw */ PrOPw(0x05, STAw); - /* LDP */ case 0x06: LDP(); break; - /* LDPw */ case XYW_MODE_W|0x06: LDPw(); break; - /* STP */ PrOP(0x07, STP); - /* STPw */ PrOPw(0x07, STPw); - /* INC */ OPC_XorYW(0x08, , PUSHS(a + 1)); // When in register mode, don't push anything on stack - /* DEC */ OPC_XorYW(0x09, , PUSHS(a - 1)); // When in register mode, don't push anything on stack - /* SHL */ OPC_XYW(0x0A, , PUSH(b << a)); - /* SHR */ OPC_XYW(0x0B, , PUSH(b >> a)); - /* ADD */ OPC_XYW(0x0C, , PUSH(a + b)); - /* SUB */ OPC_XYW(0x0D, , PUSH(b - a)); - /* MUL */ OPC_XYW(0x0E, , PUSH(a * b)); - /* DIV */ OPC_XYW(0x0F, , PUSH(b / a)); - /* EQU */ OPC_XYW(0x10, , PUSH(b == a)); - /* NEQ */ OPC_XYW(0x11, , PUSH(b != a)); - /* GTH */ OPC_XYW(0x12, , PUSH(b > a)); - /* LTH */ OPC_XYW(0x13, , PUSH(b < a)); - /* AND */ OPC_XYW(0x14, , PUSH(b & a)); - /* IOR */ OPC_XYW(0x15, , PUSH(b | a)); - /* XOR */ OPC_XYW(0x16, , PUSH(b ^ a)); - /* NOT */ OPC_XorYW(0x17, , PUSH(~a)); - /* SWP */ OPC_XYW(0x18, , PUSHS(a); PUSHS(b)); - /* DUP */ OPC_XorYW(0x19, , PUSH(a)); - /* OVR */ OPC_XYW(0x1A, , PUSH(b); PUSH(a); PUSH(b)); - /* ROT */ OPC_XYW(0x1B, , PUSH(b); PUSH(POP()); PUSH(a)); - /* JMP */ case 0x1C: JMP(); break; - /* JCN */ PrOP(0x1D, JCN); - /* JCNw */ PrOPw(0x1D, JCNw); - /* JSR */ case 0x1E: JSR(); break; - /* RTS */ case 0x1F: RTS(); break; + /* PSH */ OPC_PSH(0x01); + /* POP */ OPC_POP(0x02); + /* LDR */ OPC_R1(0x03, PUSH(READ(a))); + /* LDA */ OPC_S(0x04, READ(us_popw())); + /* STA */ OPC_SR1(0x05, WRITE(us_popw(), a)); + /* LDP */ OPC_S(0x06, READ(DIRECT_PAGE | us_pop())); + /* STP */ OPC_SR1(0x07, WRITE(DIRECT_PAGE | us_pop(), a)); + /* INC */ OPC_IDC(0x08, +1); + /* DEC */ OPC_IDC(0x09, -1); + /* SHL */ OPC_SR2(0x0A, PUSH(b << a)); + /* SHR */ OPC_SR2(0x0B, PUSH(b >> a)); + /* ADD */ OPC_SR2(0x0C, PUSH(a + b)); + /* SUB */ OPC_SR2(0x0D, PUSH(b - a)); + /* MUL */ OPC_SR2(0x0E, PUSH(a * b)); + /* DIV */ OPC_SR2(0x0F, PUSH(b / a)); + /* EQU */ OPC_SR2(0x10, PUSH(b == a)); + /* NEQ */ OPC_SR2(0x11, PUSH(b != a)); + /* GTH */ OPC_SR2(0x12, PUSH(b > a)); + /* LTH */ OPC_SR2(0x13, PUSH(b < a)); + /* AND */ OPC_SR2(0x14, PUSH(b & a)); + /* IOR */ OPC_SR2(0x15, PUSH(b | a)); + /* XOR */ OPC_SR2(0x16, PUSH(b ^ a)); + /* NOT */ OPC_SR1(0x17, PUSH(~a)); + /* SWP */ OPC_SWP(0x18); + /* DUP */ OPC_SR1(0x19, PUSH(a)); + /* OVR */ OPC_SR2(0x1A, PUSH(b); PUSH(a); PUSH(b)); + /* ROT */ OPC_SR2(0x1B, PUSH(b); PUSH(POP()); PUSH(a)); + /* JMP */ case 0x1C: *pc = us_popw(); break; + /* JCN */ OPC_SR1(0x1D, if (a && !!r) { *pc = POP(); } ); + /* JSR */ case 0x1E: xyw_word addr = us_popw(); ss_pushw(*pc); *pc = addr; break; + /* RTS */ case 0x1F: *pc = ss_popw(); break; // clang-format on } // TODO: error handling etc.