From 8998bd04c94da08dc49ab62007da5604d53895c3 Mon Sep 17 00:00:00 2001 From: "Juan J. Martinez" Date: Mon, 1 May 2023 13:50:52 +0100 Subject: Initial import --- .gitignore | 7 + COPYING | 20 + Makefile | 23 + README.md | 389 +++++++++++++++++ example.asm | 125 ++++++ tr8as.c | 1341 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ vm.c | 472 +++++++++++++++++++++ vm.h | 18 + 8 files changed, 2395 insertions(+) create mode 100644 .gitignore create mode 100644 COPYING create mode 100644 Makefile create mode 100644 README.md create mode 100644 example.asm create mode 100644 tr8as.c create mode 100644 vm.c create mode 100644 vm.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dd2005d --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +*.o +*.swp +*~ +Makefile.deps +tr8as +tr8vm +*.tr8 diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..1273d4b --- /dev/null +++ b/COPYING @@ -0,0 +1,20 @@ +TR8 Assembler and 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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..746491b --- /dev/null +++ b/Makefile @@ -0,0 +1,23 @@ +CC := gcc +CFLAGS := -std=c89 -Wpedantic -s -O2 -Wall -I. +LDFLAGS := + +all: tr8as tr8vm + +tr8vm: vm.c vm.h + $(CC) $(CFLAGS) -DDO_MAIN $< -o $@ + +tr8as: tr8as.c + $(CC) $(CFLAGS) -DDO_MAIN $< -o $@ + +example: example.tr8 tr8vm + ./tr8vm example.tr8 + +example.tr8: example.asm tr8as + ./tr8as example.asm example.tr8 + +clean: + rm -f tr8as tr8vm example.tr8 + +.PHONY: clean all example + diff --git a/README.md b/README.md new file mode 100644 index 0000000..8c35301 --- /dev/null +++ b/README.md @@ -0,0 +1,389 @@ +# TR-8 + +**Important**: this is a work in progress with pre-alpha quality. Not all the information in this document is final, complete or even accurate. + +8-bit fantasy console inspired by MIPS, Z80 and 6502 real world CPUs. + +## Specs + +``` +Display: 128 x 128 pixels 16 colors (from palette of 32) +Memory: 64K +CPU: TR-8, 8M VM instr/sec +Sound: TBD +``` + +Other features: + +* programable in ASM +* TR-8 CPU + - 16-bit registers: stack pointer (SP) and program counter (PC) + - 8-bit registers: 4 general purpose registers (a, b, x, y), flags refister (F) +* frame interrupt (60Hz) and external IO interrupt +* port based IO + +Memory map: + +``` +0x0000 + program (48896 bytes) +0xbf00 + frame buffer (16K) +0xff00 + frame interrupt vector +0xff02 + IO interrupt vector +0xff04 + stack (251 bytes) +0xffff +``` + +## How to build + +It requires SDL2 and SDL_Mixer. + +On Linux install a C compiler and the development packages for the requirements. + +Example on Debian/Ubuntu using `sudo`: + + sudo apt install build-essential libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev + +Then run `make`. + +This will result in two binaries: + + - *tr8as*: the TR8 assembler + - *tr8vm*: the TR8 VM that can run `tr8` files + +You can then compile and run the example with `make example`. + +## Detailed specs + +### Ports + +| Port | Type | Result | +| --- | --- | --- | +| 0xe0 to 0xef | write | Color select | +| 0xf0 | read | Status of the controller 1 | +| 0xf1 | read | Status of the controller 2 | + +### Controller + +The controller support d-pad with 4 directions, 2 action buttons, select and start. + +Read the port `0xf0` for the 8 1-bit flags reporting the state of the controller 1, and `0xf1` for controller 2. + +``` +76543210 +|||||||\ a (fire 1) +||||||\ b (fire 2) +|||||\ d-pad up +||||\ d-pad down +|||\ d-pad left +||\ d-pad right +|\ select +\ start +``` + +### Palette + +Ports from `0xe0` to `0xef` can be used to select the colors to use in the 16 color palette from the 32 available. + +TODO: palette colours + +### Instructions + +All the instructions are 16-bit, with the exception of `JMP addr` and `CALL +addr` that use an extra 16-bit parameter for "addr". All the instructions take +the same time to run, and being 8 MIPS it can fit `133333` instructions in a +frame (at 60Hz). + +There are no 16-bit registers, but they can be implemented with 2 registers, +and that is supported in some addressing modes with `[r1:r2]`. + +For example: + +```asm + ; fill with color 15 + ld a, 15 + push a + call fill + pop a + + ; halt the CPU + sif + halt + + ; + ; void fill(uint8_t color) + ; + ; fill frame-buffer with a color + ; +fill: + ; grab the color from the stack + ld b, [sp + 2] + ; 64 * 256 is 16K + ld y, 64 + ; a:x is our 16-bit register + ld a, 0xbf + ld x, 0 +fill_loop: + ld [a : x], b + inc x + bno + jmp fill_loop + ; x overfows, so we increment a + ; and decrement y (one 256 block done) + inc a + dec y + bnz + jmp fill_loop + ret +``` + +Arithmetic operations on the "16-bit" can be performed easily using branching +instructions: + +```asm + ; inc a:x + inc x + ; if x overflows we need to increase a + bo + inc a +``` + +#### Directives + +.org addr +Set the address to that value. By default the starting address is `0x0000`. + +.db imm [, imm] +Literal byte. Label can be used with `<` prefix for the low byte of the address and `>`for the high byte. + +.dw imm [, imm] +Literal word. + +#### Load and Store + +LD r1, r2 +Load the value of r2 in r1. + +LD r1, imm +Load the immediate value in r1. + +LD [r1:r2], r3 +Load the value of r3 in the memory address provided by r1:r2. + +LD r3, [r1:r2] +Load into r3 the value in the memory address provided by r1:r2. + +LD [SP + imm], r1 +Load r1 into the stack on top address plus the immediate value. + +LD r1, [SP + imm] +Load into r1 the stack value on top address plus the immediate value. + +#### Stack + +PUSH r1 +Save the value of r1 on the top of the stack. + +POP r1 +Restore the value of r1 by popping it from the top of the stack. + +PUSH F +Save the flags in the stack. + +POP F +Restore the flags from the top of the stack. + +XSP r1 +Exchange the high byte of the stack pointer by the value in r1. + +#### Logical + +AND r1, r2 +Logical AND between r1 and r2, storing the result in r1. +Affected flags: ZF, SF + +AND r1, imm +Logical AND between r1 and immediate value, storing the result in r1. +Affected flags: ZF, SF + +OR r1, r2 +Logical OR between r1 and r2, storing the result in r1. +Affected flags: ZF, SF + +OR r1, imm +Logical OR between r1 and immediate value, storing the result in r1. +Affected flags: ZF, SF + +XOR r1, r2 +Logical exclusive OR between r1 and r2, storing the result in r1. +Affected flags: ZF, SF + +XOR r1, imm +Logical exclusive OR between r1 and immediate value, storing the result in r1. +Affected flags: ZF, SF + +#### Arithmetic + +INC r1 +Incremet r1. +Affected flags: ZF, CF, OF, SF + +DEC r1 +Decrement r1. +Affected flags: ZF, CF, OF, SF + +ADD r1, r2 +Add r1 and r2, storing the result in r1. +Affected flags: ZF, CF, OF, SF + +ADD r1, imm +Add r1 and the immediate value, storing the result in r1. +Affected flags: ZF, CF, OF, SF + +SUB r1, r2 +Subtract r2 to r1, storing the result in r1. +Affected flags: ZF, CF, OF, SF + +SUB r1, imm +Subtract the immediate value to r1, storing the result in r1. +Affected flags: ZF, CF, OF, SF + +CMP r1, r2 +Perform a SUB of r1 minus r2, without storing the result and only updating the flags. +Affected flags: ZF, CF, OF, SF + +CMP r1, imm +Perform a SUB of r1 minus the immediate value without storing the result and only updating the flags. +Affected flags: ZF, CF, OF, SF + +#### Bit Operations + +SHL r1, n +Shift r1 n bits to the left (0 to 7), storing the result in r1. +Affected flags: ZF, CF, SF + +SHR r1, n +Shift r1 n bits to the right (0 to 7), storing the result in r1. +Affected flags: ZF, CF, SF + +ROL r1, n +Rotate r1 n bits to the left (0 to 7), storing the result in r1. +Affected flags: ZF, CF, SF + +ROR r1, n +Rotate r1 n bits to the right (0 to 7), storing the result in r1. +Affected flags: ZF, CF, SF + +BIT r1, n +Test bit n (0 to 7) or r1, setting ZF if it is set or clearing it otherwise. +Affected flags: ZF + +#### Jump and Call + +JMP addr +Set the PC to the 16-bit address. + +CALL addr +Store the next PC in the stack (16-bit address) and sets te PC to the 16-bit address. + +CALL [r1:r2] +Store the next PC in the stack (16-bit address) and sets te PC to the 16-bit address provided by r1:r2. + +RET +Return from a call by setting PC to the top 16-bit value popped from the stack. + +#### Branching + +BZ inst +Branch if Zero, will skip the next instruction if ZF is not set. +Affected flags: BF + +BNZ inst +Branch if not Zero, will skip the next instruction if ZF flag is set. +Affected flags: BF + +BC inst +Branch if Carry, will skip the next instruction if CF flag is not set. +Affected flags: BF + +BNC inst +Branch if not Carry, will skip the next instruction if CF flag is set. +Affected flags: BF + +BO inst +Branch if Overflow, will skip the next instruction if OF flag is not set. +Affected flags: BF + +BNO inst +Branch if not Overflow, will skip the next instruction if OF flag is set. +Affected flags: BF + +BS inst +Branch if Sign, will skip the next instruction if SF flag is not set. +Affected flags: BF + +BNS inst +Branch if not Sign, will skip the next instruction if SF flag is set. +Affected flags: BF + +BI inst +Branch if Interrupt, will skip the next instruction if IF flag is not set. +Affected flags: BF + +BNI inst +Branch if not Interrupt, will skip the next instruction if IF flag is set. +Affected flags: BF + +#### IO, flags and Misc + +HALT +Stop the execution until there is frame interrupt. If the interruption flag is set, this will hang the CPU. + +PORT r1, r2 +Write the value of r2 in the port number provided by r1. If there is an output, the value will be stored in r1. + +NOP +No instruction has no effect. + +SIF +Set IF, disabling the interrupt. +Affected flags: IF + +CIF +Clear IF, enabling the interrupt. If called in an interrupt handler, it won't have effect. Use IRET instead to return from the interrupt to unset IF. +Affected flags: IF + +CCF +Clear carry flag. +Affected flags: CF + +SCF +Set carry flag. +Affected flags: CF + +COF +Clear overflow flag. +Affected flags: OF + +IRET +Return from an interupt handler by setting PC to the top 16-bit value popped from the stack. It enables interrupts by clearing the interrupt flag. +Affected flags: IF + +### TR8 file format + +It is a binary file to be run on the TR-8. + +It doesn't have a header and it will be loaded at `0x0000` address. The execution will start on that address with interruptions disabled (interruption flag set). + +See the memory map for further information. + +## Author and Licence + +This was made by [Juan J. Martinez](https://www.usebox.net/jjm/about/me/). + +The code is MIT licensed. + diff --git a/example.asm b/example.asm new file mode 100644 index 0000000..6d4521e --- /dev/null +++ b/example.asm @@ -0,0 +1,125 @@ +; +; example.asm for TR8 +; +.org 0 + +; ; copy 16K from 0x0000 to 0xbf00 +; ; +; start: +; ld a, 0 +; ld b, 0xbf +; ld x, 0 +; ld y, 0x40 +; loop: +; push y +; ld y, [a : x] +; ld [b : x], y +; pop y +; inc x +; bno +; jmp loop +; inc a +; inc b +; dec y +; bnz +; jmp loop +; + ld b, 15 + call fill + + halt +; +; ; void copy(void *dst, void *src, uint16_t size) +; ; size = 0x1000 +; ; src = 0 +; ; dst 0xbf00 +; ld a, 0x10 +; push a +; ld a, 0 +; push a +; push a +; push a +; ld a, 0xbf +; push a +; ld a, 0 +; push a +; call copy +; pop a +; pop a +; pop a +; pop a +; pop a +; pop a +; halt + + ; fill frame-buffer with a color in reg b +fill: + ld a, 0xbf + ld x, 0 + ld y, 0x40 +fill_loop: + ld [a : x], b + inc x + bno + jmp fill_loop + inc a + dec y + bnz + jmp fill_loop + ret + + ; void copy(void *dst, void *src, uint16_t size) + ; + ; using C calling convention with the stack + ; not used, but return value in a (8-bit), a: x (16-bit) +; copy: +; ld x, [sp + 6] +; ld a, [sp + 7] ; size a:x +; +; ; local variable +; push a +; push x +; +; ld y, [sp + 4] +; ld b, [sp + 5] ; dst b:y +; ld x, [sp + 6] +; ld a, [sp + 7] ; src a:x +; copy_loop: +; ; copy byte from a:x to b:y +; push x +; ld x, [a : x] +; ld [b : y], x +; pop x +; +; ; inc a:x +; inc x +; bo +; inc a +; +; ; inc b:y +; inc y +; bo +; inc b +; +; ; dec local var size +; push a +; push x +; ld x, [sp + 2] +; ld a, [sp + 3] +; dec x +; bo +; dec a +; ld [sp + 2], x +; ld [sp + 3], a +; or x, a +; pop x +; pop a +; +; bnz +; jmp copy_loop +; +; pop x +; pop x ; free local var +; ret +; +; diff --git a/tr8as.c b/tr8as.c new file mode 100644 index 0000000..3f5aba2 --- /dev/null +++ b/tr8as.c @@ -0,0 +1,1341 @@ +#include +#include +#include +#include +#include +#include + +#define MAX_LINE 1024 +#define MAX_ID 0x40 +#define MAX_LABELS 0x200 +#define MAX_REFS 0x1000 + +#define FHH 2 +#define FHL 1 +#define FL 32 + +typedef struct +{ + const char *filename; + uint16_t line; +} Location; + +typedef struct +{ + char id[MAX_ID + 1]; + uint16_t addr; + Location loc; +} Label; + +typedef enum +{ + ModNone = 0, + ModLow, + ModHigh +} Mod; + +typedef struct +{ + char id[MAX_ID + 1]; + Mod mod; + uint16_t mask; + uint16_t addr; + Location loc; +} Reference; + +typedef struct +{ + uint8_t out[UINT16_MAX + 1]; + size_t size; + + uint16_t addr; + Location loc; + + Label labels[MAX_LABELS]; + uint16_t lcnt; + + Reference refs[MAX_REFS]; + uint16_t rcnt; +} As; + +typedef struct +{ + char id[5]; + uint8_t (*parse)(As *, char *); +} InstParse; + +static uint8_t error_l(const char *msg, Location *loc, const char *reason) +{ + fprintf(stderr, "%s (%s:%d): %s\n", msg, loc->filename, loc->line, reason); + return 0; +} + +static uint8_t error(const char *msg, const char *reason) +{ + if (reason == NULL) + fprintf(stderr, "error: %s\n", msg); + else + fprintf(stderr, "%s: %s\n", msg, reason); + return 0; +} + +static char * skip_whitespace(char *c) +{ + while (*c && isspace(*c)) + c++; + return c; +} + +static uint8_t isspecial(char c) +{ + return c == '$' || c == '_' || c == '.' || c == '#' || c == '<' || c == '>'; +} + +static char * next_word(char *c, char *word, uint8_t *wlen) +{ + c = skip_whitespace(c); + + *wlen = 0; + while (*c && (isalnum(*c) || isspecial(*c))) + { + word[(*wlen)++] = *c++; + if (*wlen == MAX_ID) + { + /* XXX: should be an error? */ + break; + } + } + word[*wlen] = 0; + + return c; +} + +static uint8_t next_imm(char *word, uint16_t *n) +{ + *n = 0; + + if (*word == '0') + { + /* hex */ + if (word[1] == 'x') + { + word += 2; + while (*word) + { + if (*word >= '0' && *word <= '9') + *n = (*n) * 16 + (*word - '0'); + else if (*word >= 'a' && *word <= 'f') + *n = (*n) * 16 + 10 + (*word - 'a'); + else if (*word >= 'A' && *word <= 'F') + *n = (*n) * 16 + 10 + (*word - 'A'); + else + break; + + word++; + } + return *word; + } + /* bin */ + else if (word[1] == 'b') + { + word += 2; + while (*word) + { + if (*word == '0' || *word == '1') + *n = (*n) * 2 + (*word - '0'); + else + break; + + word++; + } + return *word; + } + } + + /* dec */ + while (*word) + { + if (*word >= '0' && *word <= '9') + *n = (*n) * 10 + (*word - '0'); + else + break; + + word++; + } + + return *word; +} + +static uint8_t parse_register(char *word) +{ + if (!strcasecmp(word, "a")) + return 0; + if (!strcasecmp(word, "b")) + return 1; + if (!strcasecmp(word, "x")) + return 2; + if (!strcasecmp(word, "y")) + return 3; + + return 0xff; +} + +static Label * find_label(As *as, char *word) +{ + uint8_t i; + + for (i = 0; i < as->lcnt; i++) + if (!strcmp(word, as->labels[i].id)) + return &as->labels[i]; + + return NULL; +} + +static uint8_t new_label(As *as, char *word) +{ + if (find_label(as, word)) + return error_l("Label redefined", &as->loc, word); + + strcpy(as->labels[as->lcnt].id, word); + + as->labels[as->lcnt].loc.filename = as->loc.filename; + as->labels[as->lcnt].loc.line = as->loc.line; + as->labels[as->lcnt++].addr = as->addr; + + if (as->lcnt == MAX_LABELS) + return error("Too many labels", NULL); + + return 1; +} + +static uint8_t new_ref(As *as, char *word, uint16_t mask, uint16_t addr) +{ + Mod mod = ModNone; + char *pt = word; + + if (*pt == '<') + { + mod = ModLow; + pt++; + } + else if (*pt == '>') + { + mod = ModHigh; + pt++; + } + + if (!*pt) + return error_l("Syntax error", &as->loc, "expected label"); + + if (mask == 0xff && mod == ModNone) + return error_l("Overflow in immediate", &as->loc, word); + + strcpy(as->refs[as->rcnt].id, pt); + as->refs[as->rcnt].mod = mod; + as->refs[as->rcnt].mask = mask; + as->refs[as->rcnt].loc = as->loc; + as->refs[as->rcnt++].addr = addr; + + if (as->rcnt == MAX_REFS) + return error("Too many references", NULL); + + return 1; +} + +static uint8_t resolve(As *as) +{ + uint16_t i; + uint16_t v; + Label *label; + + for (i = 0; i < as->rcnt; i++) + { + label = find_label(as, as->refs[i].id); + if (!label) + return error_l("Unresolved reference", &as->refs[i].loc, as->refs[i].id); + + v = label->addr; + switch (as->refs[i].mod) + { + case ModLow: + v &= 0xff; + break; + case ModHigh: + v >>= 8; + break; + default: + break; + } + as->out[as->refs[i].addr] = v & 0xff; + if (as->refs[i].mask == 0xffff) + as->out[as->refs[i].addr + 1] = v >> 8; + } + + return 1; +} + +/* + * inst: instruction opcode + * p1: r1 + * p2: either FHx or r2 + * p3: either FL, r3 or immediate + */ +static void emit(As *as, uint8_t instr, uint8_t p1, uint8_t p2, uint8_t p3) +{ + as->out[as->addr++] = p3; + as->out[as->addr++] = (instr << 4) | (p1 << 2) | p2; + if (as->addr > as->size) + as->size = as->addr; +} + +static void emit_imm(As *as, uint16_t imm) +{ + as->out[as->addr++] = imm & 0xff; + as->out[as->addr++] = imm >> 8; + if (as->addr > as->size) + as->size = as->addr; +} + +static uint8_t parse_org(As *as, char *c) +{ + char word[MAX_ID + 1]; + uint8_t wlen; + + /* .org imm */ + + next_word(c, word, &wlen); + if (wlen == 0) + return error_l("Syntax error", &as->loc, "expected immediate"); + + if (next_imm(word, &as->addr)) + return error_l("Syntax error", &as->loc, word); + + if (as->addr > as->size) + as->size = as->addr; + + return 1; +} + +static uint8_t parse_db(As *as, char *c) +{ + char word[MAX_ID + 1]; + char *pt; + uint8_t wlen; + uint16_t imm = 0xff; + + /* .db imm [, imm] */ + + pt = c; + while (1) + { + pt = next_word(pt, word, &wlen); + if (wlen == 0) + return error_l("Syntax error", &as->loc, "expected immediate"); + + if (isdigit(*word)) + { + if (next_imm(word, &imm)) + return error_l("Syntax error", &as->loc, word); + + if (imm > 0xff) + return error_l("Overflow in immediate", &as->loc, word); + } + else if (!new_ref(as, word, 0xff, as->addr)) + return 0; + + as->out[as->addr++] = imm & 0xff; + if (as->addr > as->size) + as->size = as->addr; + + if (*pt == ',') + { + pt++; + continue; + } + break; + } + + return 1; +} + +static uint8_t parse_dw(As *as, char *c) +{ + char word[MAX_ID + 1]; + char *pt; + uint8_t wlen; + uint16_t imm; + + /* .dw imm [, imm] */ + + pt = c; + while (1) + { + pt = next_word(pt, word, &wlen); + if (wlen == 0) + return error_l("Syntax error", &as->loc, "expected immediate"); + + if (isdigit(*word)) + { + if (next_imm(word, &imm)) + return error_l("Syntax error", &as->loc, word); + } + else if (!new_ref(as, word, 0xffff, as->addr)) + return 0; + + as->out[as->addr++] = imm & 0xff; + as->out[as->addr++] = imm >> 8; + if (as->addr > as->size) + as->size = as->addr; + + if (*pt == ',') + { + pt++; + continue; + } + break; + } + + return 1; +} + +static uint8_t parse_nop(As *as, char *c) +{ + /* NOP */ + emit(as, 0, 0, 0, 0); + return 1; +} + +static uint8_t parse_sif(As *as, char *c) +{ + /* SIF */ + emit(as, 11, 0, FHL, 0); + return 1; +} + +static uint8_t parse_cif(As *as, char *c) +{ + /* CIF */ + emit(as, 11, 0, 0, 0); + return 1; +} + +static uint8_t parse_ccf(As *as, char *c) +{ + /* CCF */ + emit(as, 13, 0, FHH | FHL, 0); + return 1; +} + +static uint8_t parse_scf(As *as, char *c) +{ + /* SCF */ + emit(as, 13, 0, FHH, 0); + return 1; +} + +static uint8_t parse_sof(As *as, char *c) +{ + /* SOF */ + emit(as, 13, 0, FHL, 0); + return 1; +} + +static uint8_t parse_cof(As *as, char *c) +{ + /* COF */ + emit(as, 13, 0, 0, 0); + return 1; +} + +static uint8_t parse_halt(As *as, char *c) +{ + /* HALT */ + emit(as, 0, 0, FHH | FHL, 0); + return 1; +} + +static uint8_t parse_iret(As *as, char *c) +{ + /* IRET */ + emit(as, 0, 0, FHL, 0); + return 1; +} + +static uint8_t parse_ret(As *as, char *c) +{ + /* RET */ + emit(as, 0, 0, 0, FL); + return 1; +} + +static uint8_t parse_r1_r2_or_imm(As *as, char *c, uint8_t *r1, uint8_t *r2, uint16_t *imm) +{ + char word[MAX_ID + 1]; + char *pt; + uint8_t wlen; + + /* ? r1, ? */ + pt = next_word(c, word, &wlen); + if (wlen == 0) + return error_l("Syntax error", &as->loc, "expected register"); + + *r1 = parse_register(word); + if (*r1 == 0xff) + return error_l("Syntax error", &as->loc, word); + + pt = skip_whitespace(pt); + if (*pt != ',') + return error_l("Syntax error", &as->loc, "expected ,"); + pt++; + + pt = next_word(pt, word, &wlen); + if (wlen == 0) + return error_l("Syntax error", &as->loc, "expected register or immediate"); + + /* ? r1, r2 */ + *r2 = parse_register(word); + if (*r2 != 0xff) + return 1; + + /* ? r1, imm */ + if (isdigit(*word)) + { + if (next_imm(word, imm)) + return error_l("Syntax error", &as->loc, word); + + if (*imm > 0xff) + return error_l("Overflow in immediate", &as->loc, word); + } + /* AND r1, label */ + else if (!new_ref(as, word, 0xff, as->addr + 1)) + return 0; + + return 1; +} + +static uint8_t parse_r1_imm(As *as, char *c, uint8_t *r1, uint16_t *imm) +{ + char word[MAX_ID + 1]; + char *pt; + uint8_t wlen; + + /* ? r1, imm */ + pt = next_word(c, word, &wlen); + if (wlen == 0) + return error_l("Syntax error", &as->loc, "expected register"); + + *r1 = parse_register(word); + if (*r1 == 0xff) + return error_l("Syntax error", &as->loc, word); + + pt = skip_whitespace(pt); + if (*pt != ',') + return error_l("Syntax error", &as->loc, "expected ,"); + pt++; + + pt = next_word(pt, word, &wlen); + if (wlen == 0) + return error_l("Syntax error", &as->loc, "expected immediate"); + + if (!isdigit(*word)) + return error_l("Syntax error", &as->loc, "expected immediate"); + + if (next_imm(word, imm)) + return error_l("Syntax error", &as->loc, word); + + return 1; +} + +static uint8_t parse_and(As *as, char *c) +{ + uint16_t imm = 0xff; + uint8_t r1, r2; + + if (!parse_r1_r2_or_imm(as, c, &r1, &r2, &imm)) + return 0; + + if (r2 != 0xff) + /* AND r1, r2 */ + emit(as, 4, r1, FHH | FHL, (r2 << 6)); + else + /* AND r1, imm */ + emit(as, 4, r1, FHH, imm); + + return 1; +} + +static uint8_t parse_or(As *as, char *c) +{ + uint16_t imm = 0xff; + uint8_t r1, r2; + + if (!parse_r1_r2_or_imm(as, c, &r1, &r2, &imm)) + return 0; + + if (r2 != 0xff) + /* OR r1, r2 */ + emit(as, 4, r1, FHL, (r2 << 6)); + else + /* OR r1, imm */ + emit(as, 4, r1, 0, imm); + + return 1; +} + +static uint8_t parse_xor(As *as, char *c) +{ + uint16_t imm = 0xff; + uint8_t r1, r2; + + if (!parse_r1_r2_or_imm(as, c, &r1, &r2, &imm)) + return 0; + + if (r2 != 0xff) + /* XOR r1, r2 */ + emit(as, 5, r1, FHH | FHL, (r2 << 6)); + else + /* XOR r1, imm */ + emit(as, 5, r1, FHH, imm); + + return 1; +} + +static uint8_t parse_cmp(As *as, char *c) +{ + uint16_t imm = 0xff; + uint8_t r1, r2; + + if (!parse_r1_r2_or_imm(as, c, &r1, &r2, &imm)) + return 0; + + if (r2 != 0xff) + /* CMP r1, r2 */ + emit(as, 5, r1, FHL, (r2 << 6)); + else + /* CMP r1, imm */ + emit(as, 5, r1, 0, imm); + + return 1; +} + +static uint8_t parse_add(As *as, char *c) +{ + uint16_t imm = 0xff; + uint8_t r1, r2; + + if (!parse_r1_r2_or_imm(as, c, &r1, &r2, &imm)) + return 0; + + if (r2 != 0xff) + /* ADD r1, r2 */ + emit(as, 6, r1, FHH | FHL, (r2 << 6)); + else + /* ADD r1, imm */ + emit(as, 6, r1, FHH, imm); + + return 1; +} + +static uint8_t parse_sub(As *as, char *c) +{ + uint16_t imm = 0xff; + uint8_t r1, r2; + + if (!parse_r1_r2_or_imm(as, c, &r1, &r2, &imm)) + return 0; + + if (r2 != 0xff) + /* SUB r1, r2 */ + emit(as, 6, r1, FHL, (r2 << 6)); + else + /* SUB r1, imm */ + emit(as, 6, r1, 0, imm); + + return 1; +} + +static uint8_t parse_bit(As *as, char *c) +{ + uint16_t imm = 0xff; + uint8_t r1; + + /* BIT r1, imm */ + if (!parse_r1_imm(as, c, &r1, &imm)) + return 0; + + if (imm > 7) + return error_l("Immediate out of range", &as->loc, "expected bit (0-7)"); + + emit(as, 7, r1, FHH, imm); + return 1; +} + +static uint8_t parse_shl(As *as, char *c) +{ + uint16_t imm = 0xff; + uint8_t r1; + + /* SHL r1, imm */ + if (!parse_r1_imm(as, c, &r1, &imm)) + return 0; + + if (imm > 7) + return error_l("Immediate out of range", &as->loc, "expected bit (0-7)"); + + emit(as, 7, r1, FHL, imm); + return 1; +} + +static uint8_t parse_shr(As *as, char *c) +{ + uint16_t imm = 0xff; + uint8_t r1; + + /* SHR r1, imm */ + if (!parse_r1_imm(as, c, &r1, &imm)) + return 0; + + if (imm > 7) + return error_l("Immediate out of range", &as->loc, "expected bit (0-7)"); + + emit(as, 7, r1, 0, imm); + return 1; +} + +static uint8_t parse_ror(As *as, char *c) +{ + uint16_t imm = 0xff; + uint8_t r1; + + /* ROR r1, imm */ + if (!parse_r1_imm(as, c, &r1, &imm)) + return 0; + + if (imm > 7) + return error_l("Immediate out of range", &as->loc, "expected bit (0-7)"); + + emit(as, 8, r1, 0, imm); + return 1; +} + +static uint8_t parse_rol(As *as, char *c) +{ + uint16_t imm = 0xff; + uint8_t r1; + + /* ROL r1, imm */ + if (!parse_r1_imm(as, c, &r1, &imm)) + return 0; + + if (imm > 7) + return error_l("Immediate out of range", &as->loc, "expected bit (0-7)"); + + emit(as, 8, r1, FHH, imm); + return 1; +} + +static uint8_t parse_push(As *as, char *c) +{ + char word[MAX_ID + 1]; + uint8_t wlen; + uint8_t r1; + + /* PUSH r1 */ + next_word(c, word, &wlen); + if (wlen == 0) + return error_l("Syntax error", &as->loc, "expected register"); + + r1 = parse_register(word); + if (r1 == 0xff) + { + /* PUSH F */ + if (*word == 'f' || *word == 'F') + { + emit(as, 3, 0, FHH | FHL, 0); + return 1; + } + return error_l("Syntax error", &as->loc, word); + } + + emit(as, 3, r1, FHL, 0); + return 1; +} + +static uint8_t parse_port(As *as, char *c) +{ + char word[MAX_ID + 1]; + uint8_t wlen; + uint8_t r1, r2; + + /* PORT r1, r2 */ + next_word(c, word, &wlen); + if (wlen == 0) + return error_l("Syntax error", &as->loc, "expected register"); + + r1 = parse_register(word); + if (r1 == 0xff) + return error_l("Syntax error", &as->loc, word); + + next_word(c, word, &wlen); + if (wlen == 0) + return error_l("Syntax error", &as->loc, "expected register"); + + r2 = parse_register(word); + if (r2 == 0xff) + return error_l("Syntax error", &as->loc, word); + + emit(as, 0, r1, FHH, r2); + return 1; +} + +static uint8_t parse_pop(As *as, char *c) +{ + char word[MAX_ID + 1]; + uint8_t wlen; + uint8_t r1; + + /* POP r1 */ + next_word(c, word, &wlen); + if (wlen == 0) + return error_l("Syntax error", &as->loc, "expected register"); + + r1 = parse_register(word); + if (r1 == 0xff) + { + /* POP F */ + if (*word == 'f' || *word == 'F') + { + emit(as, 3, 0, FHH, 0); + return 1; + } + return error_l("Syntax error", &as->loc, word); + } + + emit(as, 3, r1, 0, 0); + return 1; +} + +static uint8_t parse_xsp(As *as, char *c) +{ + char word[MAX_ID + 1]; + uint8_t wlen; + uint8_t r1; + + /* XSP r1 */ + next_word(c, word, &wlen); + if (wlen == 0) + return error_l("Syntax error", &as->loc, "expected register"); + + r1 = parse_register(word); + if (r1 == 0xff) + return error_l("Syntax error", &as->loc, word); + + emit(as, 3, r1, 0, FL); + return 1; +} + +static uint8_t parse_inc(As *as, char *c) +{ + char word[MAX_ID + 1]; + uint8_t wlen; + uint8_t r1; + + /* INC r1 */ + next_word(c, word, &wlen); + if (wlen == 0) + return error_l("Syntax error", &as->loc, "expected register"); + + r1 = parse_register(word); + if (r1 == 0xff) + return error_l("Syntax error", &as->loc, word); + + emit(as, 11, r1, FHH | FHL, 0); + return 1; +} + +static uint8_t parse_dec(As *as, char *c) +{ + char word[MAX_ID + 1]; + uint8_t wlen; + uint8_t r1; + + /* INC r1 */ + next_word(c, word, &wlen); + if (wlen == 0) + return error_l("Syntax error", &as->loc, "expected register"); + + r1 = parse_register(word); + if (r1 == 0xff) + return error_l("Syntax error", &as->loc, word); + + emit(as, 11, r1, FHH, 0); + return 1; +} + +static uint8_t parse_bz(As *as, char *c) +{ + /* BZ */ + emit(as, 10, 0, 0, 0); + return 1; +} + +static uint8_t parse_bnz(As *as, char *c) +{ + /* BNZ */ + emit(as, 10, 0, FHH, 0); + return 1; +} + +static uint8_t parse_bc(As *as, char *c) +{ + /* BC */ + emit(as, 10, 0, 0, 1); + return 1; +} + +static uint8_t parse_bnc(As *as, char *c) +{ + /* BNC */ + emit(as, 10, 0, FHH, 1); + return 1; +} + +static uint8_t parse_bo(As *as, char *c) +{ + /* BO */ + emit(as, 10, 0, 0, 2); + return 1; +} + +static uint8_t parse_bno(As *as, char *c) +{ + /* BNO */ + emit(as, 10, 0, FHH, 2); + return 1; +} + +static uint8_t parse_bs(As *as, char *c) +{ + /* BS */ + emit(as, 10, 0, 0, 3); + return 1; +} + +static uint8_t parse_bns(As *as, char *c) +{ + /* BNS */ + emit(as, 10, 0, FHH, 3); + return 1; +} + +static uint8_t parse_bi(As *as, char *c) +{ + /* BI */ + emit(as, 10, 0, 0, 4); + return 1; +} + +static uint8_t parse_bni(As *as, char *c) +{ + /* BNI */ + emit(as, 10, 0, FHH, 4); + return 1; +} + +static uint8_t parse_indirect(As *as, char **c, uint8_t *r1, uint8_t *r2, uint16_t *imm) +{ + char word[MAX_ID + 1]; + uint8_t wlen; + + /* [r1:r2] */ + *c = next_word(*c, word, &wlen); + if (wlen == 0) + return error_l("Syntax error", &as->loc, "expected register"); + + *r1 = parse_register(word); + if (*r1 == 0xff) + { + if (imm == NULL) + return error_l("Syntax error", &as->loc, word); + + /* [SP + imm] */ + if (!strcasecmp(word, "sp")) + { + *c = skip_whitespace(*c); + if (**c != '+') + return error_l("Syntax error", &as->loc, "expected +"); + (*c)++; + + *c = next_word(*c, word, &wlen); + if (wlen == 0) + return error_l("Syntax error", &as->loc, "expected immediate"); + + if (!isdigit(*word)) + return error_l("Syntax error", &as->loc, "expected immediate"); + + if (next_imm(word, imm)) + return error_l("Syntax error", &as->loc, word); + + if (*imm > 0xff) + return error_l("Overflow in immediate", &as->loc, word); + + *c = skip_whitespace(*c); + if (**c != ']') + return error_l("Syntax error", &as->loc, "expected ]"); + (*c)++; + + return 1; + } + else + return error_l("Syntax error", &as->loc, word); + } + + *c = skip_whitespace(*c); + if (**c != ':') + return error_l("Syntax error", &as->loc, "expected :"); + + *c = next_word(*c + 1, word, &wlen); + if (wlen == 0) + return error_l("Syntax error", &as->loc, "expected register"); + + *r2 = parse_register(word); + if (*r2 == 0xff) + return error_l("Syntax error", &as->loc, word); + + *c = skip_whitespace(*c); + if (**c != ']') + return error_l("Syntax error", &as->loc, "expected ]"); + + (*c)++; + return 1; +} + +static uint8_t parse_jmp(As *as, char *c) +{ + char word[MAX_ID + 1]; + char *pt; + uint8_t wlen; + uint16_t imm = 0xffff; + uint8_t r1, r2; + + pt = skip_whitespace(c); + + /* JMP [r1:r2] */ + if (*pt == '[') + { + pt++; + if (!parse_indirect(as, &pt, &r1, &r2, NULL)) + return 0; + + emit(as, 9, r1, 0, (r2 << 6) | FL); + return 1; + } + + next_word(pt, word, &wlen); + if (wlen == 0) + return error_l("Syntax error", &as->loc, "expected label or immediate"); + + /* JMP imm */ + if (isdigit(*word)) + { + if (next_imm(word, &imm)) + return error_l("Syntax error", &as->loc, word); + } + /* JMP label */ + else if (!new_ref(as, word, 0xffff, as->addr + 2)) + return 0; + + emit(as, 9, 0, FHH, FL); + emit_imm(as, imm); + return 1; +} + +static uint8_t parse_call(As *as, char *c) +{ + char word[MAX_ID + 1]; + char *pt; + uint8_t wlen; + uint16_t imm = 0xffff; + uint8_t r1, r2; + + pt = skip_whitespace(c); + + /* CALL [r1:r2] */ + if (*pt == '[') + { + pt++; + if (!parse_indirect(as, &pt, &r1, &r2, NULL)) + return 0; + + emit(as, 9, r1, 0, (r2 << 6)); + return 1; + } + + next_word(pt, word, &wlen); + if (wlen == 0) + return error_l("Syntax error", &as->loc, "expected label or immediate"); + + /* CALL imm */ + if (isdigit(*word)) + { + if (next_imm(word, &imm)) + return error_l("Syntax error", &as->loc, word); + } + /* CALL label */ + else if (!new_ref(as, word, 0xffff, as->addr + 2)) + return 0; + + emit(as, 9, 0, FHH, 0); + emit_imm(as, imm); + return 1; +} + +static uint8_t parse_ld(As *as, char *c) +{ + char word[MAX_ID + 1]; + char *pt; + uint8_t wlen; + uint16_t imm = 0xff; + + uint8_t r1, r2, r3; + + pt = skip_whitespace(c); + /* LD [r1:r2], r3 */ + /* LD [sp + imm], r2 */ + if (*pt == '[') + { + pt++; + if (!parse_indirect(as, &pt, &r1, &r2, &imm)) + return 0; + + /* expected , */ + pt = skip_whitespace(pt); + if (*pt != ',') + return error_l("Syntax error", &as->loc, "expected ,"); + + pt = next_word(pt + 1, word, &wlen); + if (wlen == 0) + return error_l("Syntax error", &as->loc, "expected register"); + + r3 = parse_register(word); + if (r3 == 0xff) + return error_l("Syntax error", &as->loc, word); + + if (r1 == 0xff) + emit(as, 12, r3, FHH, imm); + else + emit(as, 2, r1, r2, (r3 << 6) | FL); + return 1; + } + + /* LD r1, ? */ + pt = next_word(pt, word, &wlen); + if (wlen == 0) + return error_l("Syntax error", &as->loc, "expected register"); + + r1 = parse_register(word); + if (r1 == 0xff) + return error_l("Syntax error", &as->loc, word); + + pt = skip_whitespace(pt); + if (*pt != ',') + return error_l("Syntax error", &as->loc, "expected ,"); + pt++; + + pt = skip_whitespace(pt); + /* LD r1, [r2:r3] */ + /* LD r1, [sp + imm] */ + if (*pt == '[') + { + pt++; + if (!parse_indirect(as, &pt, &r2, &r3, &imm)) + return 0; + + if (r2 == 0xff) + emit(as, 12, r1, 0, imm); + else + emit(as, 2, r2, r3, r1 << 6); + return 1; + } + + pt = next_word(pt, word, &wlen); + if (wlen == 0) + return error_l("Syntax error", &as->loc, "expected register or immediate"); + + /* LD r1, r2 */ + r2 = parse_register(word); + if (r2 != 0xff) + { + emit(as, 1, r1, FHH, (r2 << 6)); + return 1; + } + + /* LD r1, imm */ + if (isdigit(*word)) + { + if (next_imm(word, &imm)) + return error_l("Syntax error", &as->loc, word); + + if (imm > 0xff) + return error_l("Overflow in immediate", &as->loc, word); + } + /* LD r1, label */ + else if (!new_ref(as, word, 0xff, as->addr + 1)) + return 0; + + emit(as, 1, r1, 0, imm); + return 1; +} + +static InstParse insts[] = { + { ".org", parse_org }, + { ".db", parse_db }, + { ".dw", parse_dw }, + { "halt", parse_halt }, + { "push", parse_push }, + { "port", parse_port }, + { "iret", parse_iret }, + { "call", parse_call }, + { "xsp", parse_xsp }, + { "and", parse_and }, + { "cmp", parse_cmp }, + { "add", parse_add }, + { "sub", parse_sub }, + { "bit", parse_bit }, + { "shl", parse_shl }, + { "shr", parse_shr }, + { "ror", parse_ror }, + { "rol", parse_rol }, + { "xor", parse_xor }, + { "ret", parse_ret }, + { "pop", parse_pop }, + { "nop", parse_nop }, + { "inc", parse_inc }, + { "dec", parse_dec }, + { "bnz", parse_bnz }, + { "bnc", parse_bnc }, + { "bno", parse_bno }, + { "bns", parse_bns }, + { "bni", parse_bni }, + { "jmp", parse_jmp }, + { "sif", parse_sif }, + { "cif", parse_cif }, + { "ccf", parse_ccf }, + { "scf", parse_scf }, + { "sof", parse_sof }, + { "cof", parse_cof }, + { "or", parse_or }, + { "bz", parse_bz }, + { "bc", parse_bc }, + { "bo", parse_bo }, + { "bs", parse_bs }, + { "bi", parse_bi }, + { "ld", parse_ld }, + { "", NULL }, +}; + +static uint8_t parse(As *as, char *c) +{ + char word[MAX_ID + 1]; + char *pt; + uint8_t wlen; + uint8_t i; + + pt = next_word(c, word, &wlen); + if (wlen == 0) + return 1; + + /* comment */ + if (word[0] == ';') + return 1; + + /* new label */ + if (*pt == ':') + { + pt++; + + if (!new_label(as, word)) + return 0; + } + else + { + /* instructions */ + for (i = 0; insts[i].parse; i++) + if (!strcasecmp(insts[i].id, word)) + return insts[i].parse(as, pt); + printf("NOPE %s\n", word); + + return error_l("Parse error", &as->loc, word); + } + + /* EOL */ + pt = next_word(pt, word, &wlen); + if (wlen == 0) + return 1; + + /* or comment */ + if (word[0] == ';') + return 1; + + return error_l("Parse error", &as->loc, word); +} + +static uint8_t asm(As *as, const char *filename, FILE *in) +{ + char line[MAX_LINE]; + memset(as, 0, sizeof(As)); + + as->loc.filename = filename; + + while (fgets(line, MAX_LINE - 1, in)) + { + as->loc.line++; + if (!parse(as, line)) + return 0; + } + + return resolve(as); +} + +#ifdef DO_MAIN + +int main(int argc, char *argv[]) +{ + int rc = 0; + FILE *src, *out; + As as; + + if (argc < 3) + { + error("usage", "input.asm output.tr8"); + return 1; + } + src = fopen(argv[1], "rt"); + if (!src) + { + error("Failed to open input", argv[1]); + return 1; + } + + if (asm(&as, argv[1], src)) + { + out = fopen(argv[2], "wb"); + if (!out) + { + fclose(src); + error("Failed to open output", argv[2]); + return 1; + } + fwrite(as.out, as.size, 1, out); + fclose(out); + + fprintf(stderr, "%s: %lu bytes, OK\n", argv[2], as.size); + } + else + rc = 11; + + fclose(src); + return rc; +} + +#endif /* DO_MAIN */ 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 +#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.) * 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 */ diff --git a/vm.h b/vm.h new file mode 100644 index 0000000..81a0de0 --- /dev/null +++ b/vm.h @@ -0,0 +1,18 @@ +#ifndef _TR8_H +#define _TR8_H + +typedef struct +{ + uint16_t pc; + uint16_t sp; + uint16_t ssp; + uint8_t f; + uint8_t regs[4]; + uint8_t *ram; + uint32_t icnt; +} Tr8; + +void tr8_init(Tr8 *vm, uint8_t *ram); +uint8_t tr8_eval(Tr8 *vm); + +#endif /* _TR8_H */ -- cgit v1.2.3