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