aboutsummaryrefslogtreecommitdiff
path: root/vm.c
diff options
context:
space:
mode:
authorJuan J. Martinez <jjm@usebox.net>2023-05-01 13:50:52 +0100
committerJuan J. Martinez <jjm@usebox.net>2023-05-01 13:58:09 +0100
commit8998bd04c94da08dc49ab62007da5604d53895c3 (patch)
tree43a588f7a372ce17d035536a56fd71691fa6a73f /vm.c
downloadtr8vm-8998bd04c94da08dc49ab62007da5604d53895c3.tar.gz
tr8vm-8998bd04c94da08dc49ab62007da5604d53895c3.zip
Initial import
Diffstat (limited to 'vm.c')
-rw-r--r--vm.c472
1 files changed, 472 insertions, 0 deletions
diff --git a/vm.c b/vm.c
new file mode 100644
index 0000000..e4cd219
--- /dev/null
+++ b/vm.c
@@ -0,0 +1,472 @@
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "vm.h"
+
+#define R1(x) (((x) >> 10) & 3)
+#define R2(x) (((x) >> 8) & 3)
+#define R3(x) (((x) >> 6) & 3)
+
+/* instr flag low */
+#define FL(x) ((x) & 0x20)
+/* instr flags high (h/l) */
+#define FHH(x) ((x) & 0x200)
+#define FHL(x) ((x) & 0x100)
+
+#define ADDR(x, y) (((x) << 8) | (y))
+#define IMM(x) ((x) & 0xff)
+
+/* flags masks */
+#define ZF 1
+#define CF 2
+#define OF 4
+#define SF 8
+#define IF 16
+#define BF 32
+
+#define INT_VECTOR ((uin16_t)0xff00)
+
+/* (1 / 60.) * 8000000 */
+#define INSTR_PER_FRAME 133333L
+
+static uint8_t flags(uint8_t *f, uint16_t v, uint8_t mask)
+{
+ *f &= ~mask;
+ if (mask & ZF)
+ *f |= (v & 0xff) ? 0 : ZF;
+ if (mask & CF)
+ *f |= (v & 0xff00) ? CF : 0;
+ if (mask & OF)
+ *f |= (v > 0xff) ? OF : 0;
+ if (mask & SF)
+ *f |= (v & 0x80) ? SF : 0;
+
+ return v;
+}
+
+static uint8_t port(uint8_t p, uint8_t v)
+{
+ /* TODO */
+ return p ^ v;
+}
+
+static void dump(Tr8 *vm)
+{
+ uint8_t i;
+
+ fprintf(stderr, " PC 0x%04x: ", vm->pc);
+ for (i = 0; i < 16 && i + vm->pc < UINT16_MAX + 1; i++)
+ fprintf(stderr, "0x%02x ", vm->ram[vm->pc + i]);
+ fprintf(stderr, "\n");
+
+ fprintf(stderr, " SP 0x%04x: ", vm->sp);
+ if (vm->sp == vm->ssp)
+ fprintf(stderr, "-");
+ else
+ {
+ for (i = 0; i < 16 && i + vm->sp < UINT16_MAX + 1; i++)
+ fprintf(stderr, "0x%02x ", vm->ram[vm->sp + i]);
+ }
+ fprintf(stderr, "\n");
+
+ fprintf(stderr, " F: %c%c%c%c%c%c\n"
+ " A: 0x%02x B: 0x%02x\n"
+ " X: 0x%02x Y: 0x%02x\n\n"
+ " %d instr run\n",
+ (vm->f & ZF) ? 'Z' : 'z',
+ (vm->f & CF) ? 'C' : 'c',
+ (vm->f & OF) ? 'O' : 'o',
+ (vm->f & SF) ? 'S' : 's',
+ (vm->f & IF) ? 'I' : 'i',
+ (vm->f & BF) ? 'B' : 'b',
+ vm->regs[0],
+ vm->regs[1],
+ vm->regs[2],
+ vm->regs[3],
+ vm->icnt);
+
+ fprintf(stderr, "****\n");
+}
+
+void tr8_init(Tr8 *vm, uint8_t *ram)
+{
+ memset(vm, 0, sizeof(Tr8));
+ vm->f = IF;
+ vm->ram = ram;
+}
+
+uint8_t tr8_eval(Tr8 *vm)
+{
+ uint16_t instr;
+
+ vm->icnt = 0;
+
+ for (;;)
+ {
+ /* each instruction is 16-bit */
+ instr = vm->ram[vm->pc] | (vm->ram[vm->pc + 1] << 8);
+ vm->pc += 2;
+
+ /* if the branch flag is set, we skip this instruction */
+ if (vm->f & BF)
+ {
+ vm->f &= ~BF;
+
+ /* JMP imm and CALL imm are double length */
+ if (instr == 0x9320 || instr == 0x9220)
+ vm->pc += 2;
+
+ continue;
+ }
+
+ vm->icnt++;
+
+ /* 4 bit for opcode */
+ switch (instr >> 12)
+ {
+ case 0:
+ if (FHH(instr))
+ {
+ /* HALT */
+ if (FHL(instr))
+ {
+ if (vm->f & IF)
+ {
+ fprintf(stderr, "*HALT*\n");
+ vm->pc -= 2;
+ dump(vm);
+ return 1;
+ }
+ return 0;
+ }
+ /* PORT r1, r3 */
+ vm->regs[R1(instr)] = port(vm->regs[R1(instr)], vm->regs[R3(instr)]);
+ }
+ else
+ {
+ if (FHL(instr))
+ {
+ /* IRET */
+ vm->pc = vm->ram[vm->sp + 1] | (vm->ram[vm->sp] << 8);
+ vm->sp += 2;
+ vm->f |= (~IF);
+ }
+ else
+ {
+ if (FL(instr))
+ {
+ /* RET */
+ vm->pc = vm->ram[vm->sp + 1] | (vm->ram[vm->sp] << 8);
+ vm->sp += 2;
+ }
+ }
+ }
+
+ /* otherwise is NOP */
+ break;
+
+ case 1:
+ if (FHH(instr))
+ /* LD r1, r3 */
+ vm->regs[R1(instr)] = vm->regs[R3(instr)];
+ else
+ /* LD r1, imm */
+ vm->regs[R1(instr)] = IMM(instr);
+ break;
+
+ case 2:
+ if (FL(instr))
+ /* LD [r1:r2], r3 */
+ vm->ram[ADDR(vm->regs[R1(instr)], vm->regs[R2(instr)])] = R3(instr);
+ else
+ /* LD r3, [r1:r2] */
+ vm->regs[R3(instr)] = vm->ram[ADDR(vm->regs[R1(instr)], vm->regs[R2(instr)])];
+ break;
+
+ case 3:
+ if (FHH(instr))
+ {
+ if (FHL(instr))
+ /* PUSH F */
+ vm->ram[--vm->sp] = vm->f;
+ else
+ /* POP F */
+ vm->f = vm->ram[vm->sp++];
+ }
+ else
+ {
+ if (FL(instr))
+ {
+ /* XSP r1 */
+ uint8_t old_sp = vm->sp >> 8;
+ vm->ssp = vm->sp = ADDR(vm->regs[R1(instr)], 0);
+ vm->regs[R1(instr)] = old_sp;
+ }
+ else
+ {
+ if (FHL(instr))
+ /* PUSH r1 */
+ vm->ram[--vm->sp] = vm->regs[R1(instr)];
+ else
+ /* POP r1 */
+ vm->regs[R1(instr)] = vm->ram[vm->sp++];
+ }
+ }
+ break;
+
+ case 4:
+ if (FHH(instr))
+ {
+ if (FHL(instr))
+ /* AND r1, r3 */
+ vm->regs[R1(instr)] = flags(&vm->f, vm->regs[R1(instr)] & vm->regs[R3(instr)], ZF | SF);
+ else
+ /* AND r1, im */
+ vm->regs[R1(instr)] = flags(&vm->f, vm->regs[R1(instr)] & IMM(instr), ZF | SF);
+ }
+ else
+ {
+ if (FHL(instr))
+ /* OR r1, r3 */
+ vm->regs[R1(instr)] = flags(&vm->f, vm->regs[R1(instr)] | vm->regs[R3(instr)], ZF | SF);
+ else
+ /* OR r1, im */
+ vm->regs[R1(instr)] = flags(&vm->f, vm->regs[R1(instr)] | IMM(instr), ZF | SF);
+ }
+ break;
+
+ case 5:
+ if (FHH(instr))
+ {
+ if (FHL(instr))
+ /* XOR r1, r3 */
+ vm->regs[R1(instr)] = flags(&vm->f, vm->regs[R1(instr)] ^ vm->regs[R3(instr)], ZF | SF);
+ else
+ /* XOR r1, im */
+ vm->regs[R1(instr)] = flags(&vm->f, vm->regs[R1(instr)] ^ IMM(instr), ZF | SF);
+ }
+ else
+ {
+ if (FHL(instr))
+ /* CMP r1, r3 */
+ flags(&vm->f, vm->regs[R1(instr)] - vm->regs[R3(instr)], ZF | CF | OF | SF);
+ else
+ /* CMP r1, im */
+ flags(&vm->f, vm->regs[R1(instr)] - IMM(instr), ZF | CF | OF | SF);
+ }
+ break;
+
+ case 6:
+ if (FHH(instr))
+ {
+ if (FHL(instr))
+ /* ADD r1, r3 */
+ vm->regs[R1(instr)] = flags(&vm->f, vm->regs[R1(instr)] + vm->regs[R3(instr)], ZF | CF | OF | SF);
+ else
+ /* ADD r1, im */
+ vm->regs[R1(instr)] = flags(&vm->f, vm->regs[R1(instr)] + IMM(instr), ZF | CF | OF | SF);
+ }
+ else
+ {
+ if (FHL(instr))
+ /* SUB r1, r3 */
+ vm->regs[R1(instr)] = flags(&vm->f, vm->regs[R1(instr)] - vm->regs[R3(instr)], ZF | CF | OF | SF);
+ else
+ /* SUB r1, im */
+ vm->regs[R1(instr)] = flags(&vm->f, vm->regs[R1(instr)] - IMM(instr), ZF | CF | OF | SF);
+ }
+ break;
+
+ case 7:
+ if (FHH(instr))
+ {
+ /* BIT r1, im */
+ flags(&vm->f, vm->regs[R1(instr)] & (1 << (IMM(instr) & 7)) ? 0 : 1, ZF);
+ }
+ else {
+ if (FHL(instr))
+ /* SHL r1, im */
+ vm->regs[R1(instr)] = flags(&vm->f, vm->regs[R1(instr)] << (IMM(instr) & 7), ZF | CF | SF);
+ else
+ /* SHR r1, im */
+ vm->regs[R1(instr)] = flags(&vm->f, vm->regs[R1(instr)] >> (IMM(instr) & 7), ZF | CF | SF);
+ }
+ break;
+
+ case 8:
+ {
+ /* XXX: is this correct? */
+ uint8_t v = vm->regs[R1(instr)],
+ bits = IMM(instr) & 7;
+
+ if (FHH(instr))
+ /* ROL r1, im */
+ vm->regs[R1(instr)] = flags(&vm->f, (v << bits) | (v >> (8 - bits)), ZF | CF | SF);
+ else
+ /* ROR r1, im */
+ vm->regs[R1(instr)] = flags(&vm->f, (v >> bits) | (v << (8 - bits)), ZF | CF | SF);
+ }
+ break;
+
+ case 9:
+ if (FL(instr))
+ {
+ if (FHH(instr))
+ /* JMP [vm->pc] */
+ vm->pc = vm->ram[vm->pc] | (vm->ram[vm->pc + 1] << 8);
+ else
+ {
+ /* JMP [r1:r3] */
+ uint8_t addr = ADDR(vm->regs[R1(instr)], vm->regs[R3(instr)]);
+ vm->pc = vm->ram[addr] | (vm->ram[addr + 1] << 8);
+ }
+ }
+ else
+ {
+ if (FHH(instr))
+ {
+ /* CALL [vm->pc] */
+ vm->ram[--vm->sp] = (vm->pc + 2) & 0xff;
+ vm->ram[--vm->sp] = (vm->pc + 2) >> 8;
+ vm->pc = vm->ram[vm->pc] | (vm->ram[vm->pc + 1] << 8);
+ }
+ else
+ {
+ /* CALL [r1:r3] */
+ uint16_t addr = ADDR(vm->regs[R1(instr)], vm->regs[R3(instr)]);
+
+ vm->ram[--vm->sp] = vm->pc & 0xff;
+ vm->ram[--vm->sp] = vm->pc >> 8;
+
+ vm->pc = vm->ram[addr] | (vm->ram[addr + 1] << 8);
+ }
+ }
+ break;
+
+ case 10: /* BZ, BC, BO, BS, BI, BNZ, BNC, BNO, BNS, BNI */
+ {
+ uint8_t cond = vm->f & (1 << (instr & 7));
+
+ /* negated */
+ if (FHH(instr))
+ cond = !cond;
+
+ /* skip next instruction;
+ * so e.g. jmp if zero flag: BZ JMP label
+ * but also others like BZ RET */
+ if (!cond)
+ vm->f |= BF;
+ }
+ break;
+
+ case 11:
+ if (FHH(instr))
+ {
+ if (FHL(instr))
+ /* INC r1 */
+ vm->regs[R1(instr)] = flags(&vm->f, vm->regs[R1(instr)] + 1, ZF | CF | OF | SF);
+ else
+ /* DEC r1 */
+ vm->regs[R1(instr)] = flags(&vm->f, vm->regs[R1(instr)] - 1, ZF | CF | OF | SF);
+ }
+ else
+ {
+ if (FHL(instr))
+ /* SIF */
+ vm->f |= IF;
+ else
+ /* CIF */
+ /* TODO: not in an interrupt */
+ vm->f &= (~IF);
+ }
+ break;
+
+ case 12:
+ if (FHH(instr))
+ /* LD [SP + imm], r1 */
+ vm->ram[vm->sp + IMM(instr)] = vm->regs[R1(instr)];
+ else
+ /* LD r1, [SP + imm] */
+ vm->regs[R1(instr)] = vm->ram[vm->sp + IMM(instr)];
+ break;
+
+ case 13:
+ if (FHH(instr))
+ {
+ if (FHL(instr))
+ /* CCF */
+ vm->f &= (~CF);
+ else
+ /* SCF */
+ vm->f |= CF;
+ }
+ else
+ {
+ if (FHL(instr))
+ /* SOF */
+ vm->f &= (~OF);
+ else
+ /* COF */
+ vm->f |= OF;
+ }
+ break;
+
+ default:
+ fprintf(stderr, "*invalid opcode 0x%01x at PC=0x%04x*\n", (instr >> 12), 2 * (vm->pc - 1));
+ dump(vm);
+ return 1;
+ }
+
+ if (vm->icnt == INSTR_PER_FRAME)
+ {
+ dump(vm);
+ return 1;
+ }
+ }
+}
+
+#ifdef DO_MAIN
+uint8_t ram[UINT16_MAX + 1] = { 0 };
+
+int main(int argc, char *argv[])
+{
+ FILE *fd;
+ size_t size;
+
+ Tr8 vm;
+
+ if (argc < 2)
+ {
+ fprintf(stderr, "usage: input.prg\n");
+ return 1;
+ }
+ fd = fopen(argv[1], "rb");
+ if (!fd)
+ {
+ fclose(fd);
+ fprintf(stderr, "error opening input\n");
+ return 1;
+ }
+ fseek(fd, 0, SEEK_END);
+ size = ftell(fd);
+ if (size > UINT16_MAX + 1)
+ {
+ fclose(fd);
+ fprintf(stderr, "input is too large\n");
+ return 1;
+ }
+ fseek(fd, 0, SEEK_SET);
+ if (fread(ram, 1, size, fd) != size)
+ {
+ fclose(fd);
+ fprintf(stderr, "error reading input\n");
+ return 1;
+ }
+ fclose(fd);
+
+ tr8_init(&vm, ram);
+ return tr8_eval(&vm);
+}
+
+#endif /* DO_MAIN */