diff options
author | Juan J. Martinez <jjm@usebox.net> | 2023-05-01 13:50:52 +0100 |
---|---|---|
committer | Juan J. Martinez <jjm@usebox.net> | 2023-05-01 13:58:09 +0100 |
commit | 8998bd04c94da08dc49ab62007da5604d53895c3 (patch) | |
tree | 43a588f7a372ce17d035536a56fd71691fa6a73f | |
download | tr8vm-8998bd04c94da08dc49ab62007da5604d53895c3.tar.gz tr8vm-8998bd04c94da08dc49ab62007da5604d53895c3.zip |
Initial import
-rw-r--r-- | .gitignore | 7 | ||||
-rw-r--r-- | COPYING | 20 | ||||
-rw-r--r-- | Makefile | 23 | ||||
-rw-r--r-- | README.md | 389 | ||||
-rw-r--r-- | example.asm | 125 | ||||
-rw-r--r-- | tr8as.c | 1341 | ||||
-rw-r--r-- | vm.c | 472 | ||||
-rw-r--r-- | vm.h | 18 |
8 files changed, 2395 insertions, 0 deletions
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 @@ -0,0 +1,20 @@ +TR8 Assembler and VM +Copyright (C) 2023 by Juan J. Martinez <jjm@usebox.net> + +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 +; +; @@ -0,0 +1,1341 @@ +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <strings.h> +#include <ctype.h> + +#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 */ @@ -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 */ @@ -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 */ |