/* * TR8 VM * Copyright (C) 2023 by Juan J. Martinez * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ #include #include #include #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.) * 4000000 */ #define INSTR_PER_FRAME 66666L 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 & 0xff); } void tr8_init(Tr8 *vm, void (*write_m)(uint16_t, uint8_t), uint8_t (*read_m)(uint16_t), uint8_t (*port)(uint8_t, uint8_t)) { memset(vm, 0, sizeof(Tr8)); vm->write_m = write_m; vm->read_m = read_m; vm->port = port; vm->f = IF; } void tr8_dump(Tr8 *vm, FILE *fd) { uint8_t i; fprintf(fd, " PC 0x%04x: ", vm->pc); for (i = 0; i < 16 && i + vm->pc < UINT16_MAX + 1; i++) fprintf(fd, "0x%02x ", vm->read_m(vm->pc + i)); fprintf(fd, "\n"); fprintf(fd, " SP 0x%04x: ", vm->sp); if (vm->sp == vm->ssp) fprintf(fd, "-"); else { for (i = 0; i < 16 && i + vm->sp < UINT16_MAX + 1; i++) fprintf(fd, "0x%02x ", vm->read_m(vm->sp + i)); } fprintf(fd, "\n"); fprintf(fd, " 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 in current frame\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(fd, "****\n"); } uint8_t tr8_frame_int(Tr8 *vm) { if (vm->f & IF) return 1; vm->write_m(--vm->sp, vm->pc & 0xff); vm->write_m(--vm->sp, vm->pc >> 8); vm->write_m(--vm->sp, vm->f); vm->pc = vm->read_m(FRAME_INT_VECT) | (vm->read_m(FRAME_INT_VECT + 1) << 8); vm->f = IF; vm->intr = 1; return tr8_eval(vm); } uint8_t tr8_eval(Tr8 *vm) { uint16_t instr; vm->icnt = 0; for (;;) { /* each instruction is 16-bit */ instr = vm->read_m(vm->pc) | (vm->read_m(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 == 0x9220 || instr == 0x9200) 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 with IF*\n"); vm->pc -= 2; tr8_dump(vm, stderr); return 0; } return 1; } /* PORT r1, r3 */ vm->regs[R1(instr)] = vm->port(vm->regs[R1(instr)], vm->regs[R3(instr)]); } else { if (FHL(instr)) { /* IRET */ vm->f = vm->read_m(vm->sp++); vm->pc = vm->read_m(vm->sp + 1) | (vm->read_m(vm->sp) << 8); vm->sp += 2; if (vm->intr) { vm->intr = 0; return 1; } } else { if (FL(instr)) { /* RET */ vm->pc = vm->read_m(vm->sp + 1) | (vm->read_m(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->write_m(ADDR(vm->regs[R1(instr)], vm->regs[R2(instr)]), vm->regs[R3(instr)]); else /* LD r3, [r1:r2] */ vm->regs[R3(instr)] = vm->read_m(ADDR(vm->regs[R1(instr)], vm->regs[R2(instr)])); break; case 3: if (FHH(instr)) { if (FHL(instr)) /* PUSH F */ vm->write_m(--vm->sp, vm->f); else /* POP F */ vm->f = vm->read_m(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->write_m(--vm->sp, vm->regs[R1(instr)]); else /* POP r1 */ vm->regs[R1(instr)] = vm->read_m(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->read_m(vm->pc) | (vm->read_m(vm->pc + 1) << 8); else { /* JMP [r1:r3] */ uint8_t addr = ADDR(vm->regs[R1(instr)], vm->regs[R3(instr)]); vm->pc = vm->read_m(addr) | (vm->read_m(addr + 1) << 8); } } else { if (FHH(instr)) { /* CALL [vm->pc] */ vm->write_m(--vm->sp, (vm->pc + 2) & 0xff); vm->write_m(--vm->sp, (vm->pc + 2) >> 8); vm->pc = vm->read_m(vm->pc) | (vm->read_m(vm->pc + 1) << 8); } else { /* CALL [r1:r3] */ uint16_t addr = ADDR(vm->regs[R1(instr)], vm->regs[R3(instr)]); vm->write_m(--vm->sp, vm->pc & 0xff); vm->write_m(--vm->sp, vm->pc >> 8); vm->pc = addr; } } 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 */ if (!vm->intr) vm->f &= (~IF); } break; case 12: if (FHH(instr)) /* LD [SP + imm], r1 */ vm->write_m(vm->sp + IMM(instr), vm->regs[R1(instr)]); else /* LD r1, [SP + imm] */ vm->regs[R1(instr)] = vm->read_m(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; case 14: if (FHL(instr)) { if (FHH(instr)) { uint8_t f = 0; /* CMPI */ flags(&vm->f, vm->read_m(ADDR(vm->regs[0], vm->regs[2])) - vm->read_m(ADDR(vm->regs[1], vm->regs[3])), ZF | CF | OF | SF); vm->regs[2] = flags(&f, vm->regs[2] + 1, OF); if (f & OF) vm->regs[0]++; vm->regs[3] = flags(&f, vm->regs[3] + 1, OF); if (f & OF) vm->regs[1]++; vm->icnt += 3; } else { uint8_t f = 0; /* CMPD */ flags(&vm->f, vm->read_m(ADDR(vm->regs[0], vm->regs[2])) - vm->read_m(ADDR(vm->regs[1], vm->regs[3])), ZF | CF | OF | SF); vm->regs[2] = flags(&f, vm->regs[2] - 1, OF); if (f & OF) vm->regs[0]--; vm->regs[3] = flags(&f, vm->regs[3] - 1, OF); if (f & OF) vm->regs[1]--; vm->icnt += 3; } } else { if (FHH(instr)) { uint8_t f = 0; /* LDI */ vm->write_m(ADDR(vm->regs[1], vm->regs[3]), vm->read_m(ADDR(vm->regs[0], vm->regs[2]))); vm->regs[2] = flags(&f, vm->regs[2] + 1, OF); if (f & OF) vm->regs[0]++; vm->regs[3] = flags(&f, vm->regs[3] + 1, OF); if (f & OF) vm->regs[1]++; vm->icnt += 3; } else { uint8_t f = 0; /* LDD */ vm->write_m(ADDR(vm->regs[1], vm->regs[3]), vm->read_m(ADDR(vm->regs[0], vm->regs[2]))); vm->regs[2] = flags(&f, vm->regs[2] - 1, OF); if (f & OF) vm->regs[0]--; vm->regs[3] = flags(&f, vm->regs[3] - 1, OF); if (f & OF) vm->regs[1]--; vm->icnt += 3; } } break; default: fprintf(stderr, "*invalid opcode 0x%01x at PC=0x%04x*\n", (instr >> 12), 2 * (vm->pc - 1)); tr8_dump(vm, stderr); return 1; } if (vm->icnt >= INSTR_PER_FRAME) return 1; } } #ifdef DO_MAIN uint8_t ram[UINT16_MAX + 1] = { 0 }; void write_m(uint16_t addr, uint8_t b) { ram[addr] = b; } uint8_t read_m(uint16_t addr) { return ram[addr]; } uint8_t port(uint8_t p, uint8_t v) { (void)p; return v; } 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, write_m, read_m, port); return tr8_eval(&vm); } #endif /* DO_MAIN */