tis100

Reimplementation of Zachtronics TIS-100 as a TUI game
git clone https://git.sinitax.com/sinitax/tis100
Log | Files | Refs | sfeed.txt

commit d7865d956f9fe3a08ebe4429dce4428a9f74bc6c
Author: Louis Burda <quent.burda@gmail.com>
Date:   Mon, 24 Jul 2023 00:43:33 +0200

Add initial rough outline

Diffstat:
A.gitignore | 4++++
AMakefile | 23+++++++++++++++++++++++
Atest/test.asm | 17+++++++++++++++++
Atis-as.c | 362+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atis-curses.c | 306+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atpu.c | 27+++++++++++++++++++++++++++
Atpu.h | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 821 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,4 @@ +tis-as +tis-curses +compile_commands.json +.cache diff --git a/Makefile b/Makefile @@ -0,0 +1,23 @@ +PREFIX ?= /usr/local +BINDIR ?= /bin + +CFLAGS = -Wunused-function -Wunused-variable -Wconversion -Wswitch + +all: tis-as tis-curses + +clean: + rm -f tis-as tis-curses + +tis-as: tis-as.c tpu.c + $(CC) -o $@ $^ $(CFLAGS) + +tis-curses: tis-curses.c tpu.c + $(CC) -o $@ $^ $(CFLAGS) -lncursesw + +install: + install -m755 tis-as tis-curses -t "$(DESTDIR)$(PREFIX)$(BINDIR)" + +uninstall: + rm -f "$(DESTDIR)$(PREFIX)$(BINDIR)"/{tis-as,tis-curses} + +.PHONY: all clean install uninstall diff --git a/test/test.asm b/test/test.asm @@ -0,0 +1,17 @@ +in X1 Y1 +out X3 Y1 + +tpu X1 Y1 + mov acc, bak +label: + goto label +end + +tpu X2 Y1 + mov UP, RIGHT + mov RIGHT +end + +tpu X3 Y1 + +edn diff --git a/tis-as.c b/tis-as.c @@ -0,0 +1,362 @@ +#include "tpu.h" + +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <stdint.h> +#include <stdbool.h> +#include <stdlib.h> + +#define WHITESPACE " \t\v\r\n" + +enum tok { + /* Global */ + TOK_STDIN, TOK_STDOUT, TOK_TPU, TOK_END, + + /* Operands (order like OP_*) */ + TOK_ACC, TOK_BAK, TOK_NIL, TOK_LEFT, TOK_RIGHT, + TOK_UP, TOK_DOWN, TOK_ANY, TOK_LAST, TOK_LIT, + + /* Instructions (order like INST_*) */ + TOK_NOP, TOK_MOV, TOK_SWP, TOK_SAV, TOK_ADD, TOK_SUB, + TOK_NEG, TOK_JMP, TOK_JEZ, TOK_JNZ, TOK_JLZ, TOK_JRO, + + /* Misc */ + TOK_COMMENT, TOK_LABEL, TOK_XPOS, TOK_YPOS, TOK_NL, TOK_EOF +}; + +struct tokenizer { + FILE *file; + enum tok tok; + char *tokstr; + size_t lineno, off; + char linebuf[256]; +}; + +static int stdin_x = 0, stdin_y = 0; +static int stdout_x = 0, stdout_y = 0; + +struct tpu_map map; + +static const char *tok_reprs[] = { + /* Global */ + "STDIN", "STDOUT", "BLOCK", "END", + + /* Operands */ + "ACC", "BAK", "NIL", "LEFT", "RIGHT", "UP", + "DOWN", "ANY", "LAST", "<LIT>", + + /* Instructions */ + "NOP", "MOV", "SWP", "SAV", "ADD", "SUB", + "NEG", "JMP", "JEZ", "JNZ", "JLZ", "JRO", + + /* Misc */ + "#<COMMENT>", "<LABEL>", "X<INT>", "Y<INT>", "<NL>", "<EOF>" +}; + +static void +__attribute__((format(printf, 1, 2))) +__attribute__((noreturn)) +die(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + fprintf(stderr, "tis: "); + vfprintf(stderr, fmt, ap); + if (*fmt && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else { + fputc('\n', stderr); + } + va_end(ap); + + exit(1); +} + +bool +is_lit(const char *str) +{ + unsigned long v; + char *end; + + v = strtoul(str, &end, 10); + if (!end || *end) return false; + + return v < 256; +} + +uint8_t +str2lit(const char *str) +{ + return (uint8_t) atoi(str); +} + +enum tpu_inst_type +tok_to_inst(enum tok tok) +{ + if (tok < TOK_NOP || tok >= TOK_JRO) abort(); + return tok - TOK_NOP + INST_NOP; +} + +enum tpu_inst_op_type +tok_to_optype(enum tok tok) +{ + if (tok < TOK_ACC || tok >= TOK_LIT) abort(); + return tok - TOK_ACC + OP_ACC; +} + +enum tok +tok_next(struct tokenizer *tok) +{ + size_t len; + char *s; + + if (!tok->linebuf[tok->off]) { + if (feof(tok->file)) return TOK_EOF; + s = fgets(tok->linebuf, sizeof(tok->linebuf), tok->file); + if (!s && !feof(tok->file)) die("fgets:"); + if (!s) return TOK_EOF; + if (*s && s[strlen(s)-1] != '\n') + die("tokenizer: line %lu too long", tok->lineno); + tok->off = 0; + } + + s = tok->linebuf + tok->off; + len = strspn(s, WHITESPACE); + tok->off += len; + s += len; + + len = strcspn(s, WHITESPACE); + s[len] = '\0'; + tok->off += len; + if (!len) return TOK_NL; + + if (!strcasecmp(s, "stdin")) { + return TOK_STDIN; + } else if (!strcasecmp(s, "stdout")) { + return TOK_STDIN; + } else if (!strcasecmp(s, "block")) { + return TOK_TPU; + } else if (!strcasecmp(s, "end")) { + return TOK_END; + } else if (!strcasecmp(s, "acc")) { + return TOK_ACC; + } else if (!strcasecmp(s, "bak")) { + return TOK_BAK; + } else if (!strcasecmp(s, "nil")) { + return TOK_NIL; + } else if (!strcasecmp(s, "left")) { + return TOK_LEFT; + } else if (!strcasecmp(s, "right")) { + return TOK_RIGHT; + } else if (!strcasecmp(s, "up")) { + return TOK_UP; + } else if (!strcasecmp(s, "down")) { + return TOK_DOWN; + } else if (!strcasecmp(s, "any")) { + return TOK_ANY; + } else if (!strcasecmp(s, "last")) { + return TOK_LAST; + } else if (is_lit(s)) { + return TOK_LIT; + } else if (!strcasecmp(s, "nop")) { + return TOK_NOP; + } else if (!strcasecmp(s, "mov")) { + return TOK_MOV; + } else if (!strcasecmp(s, "swp")) { + return TOK_SWP; + } else if (!strcasecmp(s, "sav")) { + return TOK_SAV; + } else if (!strcasecmp(s, "add")) { + return TOK_ADD; + } else if (!strcasecmp(s, "sub")) { + return TOK_SUB; + } else if (!strcasecmp(s, "neg")) { + return TOK_NEG; + } else if (!strcasecmp(s, "jmp")) { + return TOK_JMP; + } else if (!strcasecmp(s, "jez")) { + return TOK_JEZ; + } else if (!strcasecmp(s, "jnz")) { + return TOK_JNZ; + } else if (!strcasecmp(s, "jlz")) { + return TOK_JLZ; + } else if (!strcasecmp(s, "jro")) { + return TOK_JRO; + } else if (*s == '#') { + tok->off = strlen(s); + return TOK_COMMENT; + } else if (len && s[len-1] == ':') { + return TOK_LABEL; + } else if (*s == 'X' && atoi(s+1) > 0) { + return TOK_XPOS; + } else if (*s == 'Y' && atoi(s+1) > 0) { + return TOK_YPOS; + } else { + die("tokenizer: line %lu, invalid token '%s'", tok->lineno, s); + } +} + +enum tok +tok_next_in(struct tokenizer *tokenizer, ...) +{ + va_list ap, cpy; + enum tok tok; + bool first; + int arg; + + tok = tok_next(tokenizer); + + va_copy(cpy, ap); + + va_start(cpy, tokenizer); + while ((arg = va_arg(cpy, int)) > 0) { + if (tok == arg) return tok; + } + va_end(cpy); + + fprintf(stderr, "tis-as: tokenizer: "); + fprintf(stderr, "line %lu, got tok '%s', expected one of ", + tokenizer->lineno, tok_reprs[tok]); + + first = true; + va_start(ap, tokenizer); + while ((arg = va_arg(ap, int)) > 0) { + if (!first) fputc(',', stderr); + fputs(tok_reprs[arg], stderr); + first = false; + } + va_end(ap); + + fprintf(stderr, "\n"); + exit(1); +} + +static void +load(const char *fname) +{ + struct tokenizer tokenizer; + enum tpu_inst_op_type op1, op2; + enum tok op1_tok, op2_tok; + uint8_t op1_lit, op2_lit; + enum tpu_inst_type inst; + struct tpu *tpu = NULL; + enum tok tok; + FILE *file; + + file = fopen(fname, "r"); + if (!file) die("fopen:"); + + tokenizer.lineno = 1; + tokenizer.file = file; + tokenizer.off = 0; + tokenizer.tokstr = NULL; + tokenizer.linebuf[tokenizer.off] = '\0'; + while ((tok = tok_next(&tokenizer)) != TOK_EOF) { + switch (tok) { + case TOK_STDIN: + if (tpu) goto disallowed; + tok_next_in(&tokenizer, TOK_XPOS, -1); + stdin_x = atoi(tokenizer.tokstr + 1); + tok_next_in(&tokenizer, TOK_YPOS, -1); + stdin_y = atoi(tokenizer.tokstr + 1); + tok_next_in(&tokenizer, TOK_NL, -1); + break; + case TOK_STDOUT: + if (tpu) goto disallowed; + tok_next_in(&tokenizer, TOK_XPOS, -1); + stdout_x = atoi(tokenizer.tokstr + 1); + tok_next_in(&tokenizer, TOK_YPOS, -1); + stdout_y = atoi(tokenizer.tokstr + 1); + tok_next_in(&tokenizer, TOK_NL, -1); + break; + case TOK_TPU: + if (tpu) goto disallowed; + tpu = malloc(sizeof(struct tpu)); + if (!tpu) die("malloc:"); + tpu_init(tpu); + tok_next_in(&tokenizer, TOK_XPOS, -1); + tpu->x = atoi(tokenizer.tokstr + 1); + tok_next_in(&tokenizer, TOK_YPOS, -1); + tpu->y = atoi(tokenizer.tokstr + 1); + tok_next_in(&tokenizer, TOK_NL, -1); + /* TODO insert tpu */ + break; + case TOK_END: + if (!tpu) goto disallowed; + tpu = NULL; + break; + case TOK_NOP: case TOK_MOV: case TOK_SWP: case TOK_SAV: + case TOK_ADD: case TOK_SUB: case TOK_NEG: case TOK_JMP: + case TOK_JEZ: case TOK_JNZ: case TOK_JLZ: case TOK_JRO: + if (!tpu) goto disallowed; + inst = tok_to_inst(tok); + + op1_tok = tok_next_in(&tokenizer, TOK_ACC, + TOK_BAK, TOK_UP, TOK_DOWN, + TOK_LEFT, TOK_RIGHT, TOK_NL, -1); + if (op1_tok == TOK_NL) { + tok_next_in(&tokenizer, TOK_NL, -1); + tpu_add_inst(tpu, inst, -1, 0, -1, 0); + break; + } + + op1 = tok_to_optype(op1_tok); + if (op1 == OP_LIT) + op1_lit = str2lit(tokenizer.tokstr); + + op2_tok = tok_next_in(&tokenizer, TOK_ACC, + TOK_BAK, TOK_UP, TOK_DOWN, + TOK_LEFT, TOK_RIGHT, TOK_NL, -1); + if (op2_tok == TOK_NL) { + tok_next_in(&tokenizer, TOK_NL, -1); + tpu_add_inst(tpu, inst, + (int) op1, op1_lit, -1, 0); + break; + } + + op2 = tok_to_optype(op2_tok); + if (op2 == OP_LIT) + op2_lit = str2lit(tokenizer.tokstr); + tok_next_in(&tokenizer, TOK_NL, -1); + tpu_add_inst(tpu, inst, (int) op1, op1_lit, + (int) op2, op2_lit); + break; + case TOK_COMMENT: + tok_next_in(&tokenizer, TOK_NL, -1); + break; + case TOK_LABEL: + /* TODO */ + break; + default: + goto disallowed; + } + } + + fclose(file); + +disallowed: + die("tokenizer: line %lu, token %s not allowed here", + tokenizer.lineno, tok_reprs[tok]); +} + +void +run(void) +{ + +} + +int +main(int argc, const char **argv) +{ + if (argc != 2) { + fprintf(stderr, "Usage: tis-as FILE\n"); + exit(1); + } + + load(argv[1]); + + run(); +} diff --git a/tis-curses.c b/tis-curses.c @@ -0,0 +1,306 @@ +#define NCURSES_WIDECHAR 1 + +#include "tpu.h" + +#include <curses.h> + +#include <locale.h> +#include <errno.h> +#include <stdarg.h> +#include <string.h> +#include <stdio.h> +#include <stdbool.h> +#include <stdlib.h> + +#define KEY_ESC 0x1b +#define KEY_CTRL(c) ((c) & ~0x60) + +#define TCELL_INPUT_ROWS 14 +#define TCELL_INPUT_COLS 20 +#define TCELL_INFO_W 6 +#define TCELL_INFO_H 4 +#define TCELL_CNT 6 +#define TCELL_W (TCELL_INPUT_COLS + 2 + 6) +#define TCELL_H (TCELL_INPUT_ROWS + 2) + +enum { + COLOR_HEADING, + COLOR_VAL +}; + +struct tpu_cell { + struct tpu tpu; + enum tpu_mode mode; + size_t x, y; + const char source[TCELL_INPUT_ROWS][TCELL_INPUT_COLS + 1]; + bool enabled; + char *warn; + float idle; +}; + +static size_t tpu_cell_rows = 2; +static size_t tpu_cell_cols = 3; + +static size_t screenw = 80; +static size_t screenh = 40; + +static struct tpu_cell *tpu_cells = NULL; + +static void +__attribute__((format(printf, 1, 2))) +__attribute__((noreturn)) +die(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + fprintf(stderr, "tis: "); + vfprintf(stderr, fmt, ap); + if (*fmt && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else { + fputc('\n', stderr); + } + va_end(ap); + + exit(1); +} + +static const char * +cell_mode_str(struct tpu_cell *cell) +{ + switch (cell->mode) { + case MODE_READ: + return "=R="; + case MODE_WRITE: + return "=W="; + case MODE_IDLE: + return "=I="; + case MODE_RUN: + return "=X="; + } +} + +static const char * +cell_last_str(struct tpu_cell *cell) +{ + return "N/A"; +} + +static void +tui_draw_box(int sx, int sy, int w, int h, const cchar_t *ul, const cchar_t *ur, + const cchar_t *ll, const cchar_t *lr) +{ + int x, y; + + mvadd_wch(sy, sx, ul); + mvadd_wch(sy, sx + w - 1, ur); + mvadd_wch(sy + h - 1, sx, ll); + mvadd_wch(sy + h - 1, sx + w - 1, lr); + for (x = sx + 1; x < sx + w - 1; x++) + mvadd_wch(sy, x, WACS_D_HLINE); + for (x = sx + 1; x < sx + w - 1; x++) + mvadd_wch(sy + h - 1, x, WACS_D_HLINE); + for (y = sy + 1; y < sy + h - 1; y++) + mvadd_wch(y, sx, WACS_D_VLINE); + for (y = sy + 1; y < sy + h - 1; y++) + mvadd_wch(y, sx + w - 1, WACS_D_VLINE); +} + +static void +__attribute__((format(printf, 4, 5))) +tui_draw_text(int x, int y, int attr, const char *fmt, ...) +{ + char buf[256]; + va_list ap; + + va_start(ap, fmt); + vsnprintf(buf, 256, fmt, ap); + va_end(ap); + + attron(attr); + mvprintw(y, x, "%s", buf); + attroff(attr); +} + +static void +tui_draw_wch(int x, int y, int attr, const cchar_t *c) +{ + attron(attr); + mvadd_wch(y, x, c); + attroff(attr); +} + +static void +tui_draw_tpu_cell(struct tpu_cell *cell) +{ + struct tpu_port *port; + int x, y, w, h; + + tui_draw_box((int) cell->x, (int) cell->y, TCELL_W, TCELL_H, + WACS_D_ULCORNER, WACS_D_URCORNER, WACS_D_LLCORNER, WACS_D_LRCORNER); + + x = (int) cell->x + TCELL_W - TCELL_INFO_W; + w = TCELL_INFO_W; + h = TCELL_INFO_H; + + tui_draw_box(x, (y = (int) cell->y), w, h, + WACS_D_TTEE, WACS_D_URCORNER, WACS_D_LTEE, WACS_D_RTEE); + tui_draw_text(x + 2, y + 1, A_BOLD, "ACC"); + tui_draw_text(x + 2, y + 2, 0, "%03i", cell->tpu.acc); + + tui_draw_box(x, (y += TCELL_INFO_H - 1), w, h, + WACS_D_LTEE, WACS_D_RTEE, WACS_D_LTEE, WACS_D_RTEE); + tui_draw_text(x + 2, y + 1, A_BOLD, "BAK"); + tui_draw_text(x + 2, y + 2, 0, "%03i", cell->tpu.bak); + + tui_draw_box(x, (y += TCELL_INFO_H - 1), w, h, + WACS_D_LTEE, WACS_D_RTEE, WACS_D_LTEE, WACS_D_RTEE); + tui_draw_text(x + 2, y + 1, A_BOLD, "LST"); + tui_draw_text(x + 2, y + 2, 0, "%s", cell_last_str(cell)); + + tui_draw_box(x, (y += TCELL_INFO_H - 1), w, h, + WACS_D_LTEE, WACS_D_RTEE, WACS_D_LTEE, WACS_D_RTEE); + tui_draw_text(x + 2, y + 1, A_BOLD, "MOD"); + tui_draw_text(x + 2, y + 2, 0, "%s", cell_mode_str(cell)); + + tui_draw_box(x, (y += TCELL_INFO_H - 1), w, h, + WACS_D_LTEE, WACS_D_RTEE, WACS_D_BTEE, WACS_D_LRCORNER); + tui_draw_text(x + 2, y + 1, A_BOLD, "IDL"); + tui_draw_text(x + 2, y + 2, 0, "%03i", (int) (cell->idle * 100)); + + if (cell->tpu.ports[DIR_LEFT].dst_tpu) { + port = &cell->tpu.ports[DIR_LEFT]; + if (port->type & PORT_IN) + tui_draw_wch((int) cell->x - 1, (int) cell->y + 8, + port->read ? A_BOLD : 0, WACS_RARROW); + if (port->type & PORT_OUT) + tui_draw_wch((int) cell->x - 1, (int) cell->y + 7, + port->write ? A_BOLD : 0, WACS_LARROW); + if (port->write) + tui_draw_text((int) cell->x - 3, (int) cell->y + 6, + A_BOLD, "%03i", port->val); + } + + if (cell->tpu.ports[DIR_RIGHT].dst_tpu) { + port = &cell->tpu.ports[DIR_RIGHT]; + if (port->type & PORT_IN) + tui_draw_wch((int) cell->x + TCELL_W + 1, (int) cell->y + 7, + port->read ? A_BOLD : 0, WACS_LARROW); + if (port->type & PORT_OUT) + tui_draw_wch((int) cell->x + TCELL_W + 1, (int) cell->y + 8, + port->write ? A_BOLD : 0, WACS_RARROW); + if (port->write) + tui_draw_text((int) cell->x + TCELL_W + 1, (int) cell->y + 9, + A_BOLD, "%03i", port->val); + } + + if (cell->tpu.ports[DIR_UP].dst_tpu) { + port = &cell->tpu.ports[DIR_UP]; + if (port->type & PORT_IN) + tui_draw_wch((int) cell->x + 13, (int) cell->y - 1, + port->read ? A_BOLD : 0, WACS_DARROW); + if (port->type & PORT_OUT) + tui_draw_wch((int) cell->x + 15, (int) cell->y - 1, + port->write ? A_BOLD : 0, WACS_UARROW); + if (port->write) + tui_draw_text((int) cell->x + 16, (int) cell->y - 1, + A_BOLD, "%03i", port->val); + } + + if (cell->tpu.ports[DIR_DOWN].dst_tpu) { + port = &cell->tpu.ports[DIR_DOWN]; + if (port->type & PORT_IN) + tui_draw_wch((int) cell->x + 13, (int) cell->y + TCELL_H, + port->write ? A_BOLD : 0, WACS_DARROW); + if (port->type & PORT_OUT) + tui_draw_wch((int) cell->x + 15, (int) cell->y + TCELL_H, + port->read ? A_BOLD : 0, WACS_UARROW); + if (port->write) + tui_draw_text((int) cell->x + 10, (int) cell->y + TCELL_H, + A_BOLD, "%03i", port->val); + } +} + +static void +tui_draw(void) +{ + int i; + + for (i = 0; i < TCELL_CNT; i++) + tui_draw_tpu_cell(&tpu_cells[i]); + + refresh(); +} + +static void +tui_resize(void) +{ + size_t x, y, i; + + screenw = (size_t) getmaxx(stdscr); + screenh = (size_t) getmaxy(stdscr); + + for (y = 0; y < tpu_cell_rows; y++) { + for (x = 0; x < tpu_cell_cols; x++) { + i = y * tpu_cell_cols + x; + tpu_cells[i].x = 2 + x * (TCELL_W + 4); + tpu_cells[i].y = 2 + y * (TCELL_H + 2); + } + } +} + +int +main(int argc, const char **argv) +{ + size_t x, y, i; + bool quit; + int key; + + setlocale(LC_ALL, ""); + + initscr(); + + raw(); + noecho(); + keypad(stdscr, TRUE); + start_color(); + curs_set(0); + + /* TODO load like tis-as */ + + tpu_cells = calloc(tpu_cell_rows * tpu_cell_cols, sizeof(struct tpu_cell)); + if (!tpu_cells) die("malloc:"); + + for (y = 0; y < tpu_cell_rows; y++) { + for (x = 0; x < tpu_cell_cols; x++) { + i = y * tpu_cell_cols + x; + tpu_cells[i].enabled = true; + tpu_init(&tpu_cells[i].tpu); + memset((void *) tpu_cells[i].source, 0, + TCELL_INPUT_ROWS * (TCELL_INPUT_COLS + 1)); + } + } + + tui_resize(); + quit = false; + while (!quit) { + tui_draw(); + key = getch(); + switch (key) { + case KEY_RESIZE: + tui_resize(); + break; + case KEY_CTRL('c'): + quit = true; + break; + } + } + + for (i = 0; i < TCELL_CNT; i++) + tpu_deinit(&tpu_cells[i].tpu); + free(tpu_cells); + + endwin(); +} diff --git a/tpu.c b/tpu.c @@ -0,0 +1,27 @@ +#include "tpu.h" + +void +tpu_init(struct tpu *tpu) +{ + +} + +void +tpu_add_inst(struct tpu *tpu, enum tpu_inst_type inst, + int op1, uint8_t op1_lit, int op2, uint8_t op2_lit) +{ + +} + +void +tpu_step(struct tpu *tpu, uint8_t *up, uint8_t *right, + uint8_t *down, uint8_t *left) +{ + +} + +void +tpu_deinit(struct tpu *tpu) +{ + +} diff --git a/tpu.h b/tpu.h @@ -0,0 +1,82 @@ +#pragma once + +#include <stdint.h> +#include <stdbool.h> +#include <stdlib.h> + +#define TPU_MAP_BUCKETS 64 + +enum tpu_mode { + MODE_IDLE, MODE_RUN, MODE_READ, MODE_WRITE +}; + +enum tpu_inst_type { + INST_NOP, INST_MOV, INST_SWP, INST_SAV, + INST_ADD, INST_SUB, INST_NEG, INST_JMP, + INST_JEZ, INST_JNZ, INST_JLZ, INST_JRO +}; + +enum tpu_inst_op_type { + OP_LIT, OP_UP, OP_DOWN, OP_LEFT, OP_RIGHT, + OP_ACC, OP_BAK, OP_NIL, OP_ANY, OP_LAST +}; + +enum tpu_port_dir { + DIR_UP, DIR_RIGHT, DIR_DOWN, DIR_LEFT +}; + +enum tpu_port_type { + PORT_IN = 0b01, PORT_OUT = 0b10, PORT_BIDI = 0b11 +}; + +struct tpu_inst_op { + enum tpu_inst_op_type type; + uint8_t lit; +}; + +struct tpu_inst { + enum tpu_inst_type type; + struct tpu_inst_op ops[2]; +}; + +struct tpu_port { + struct tpu *dst_tpu; + enum tpu_port_type type; + struct tpu_port *dst_port; + bool write, read; + uint8_t val; +}; + +struct tpu { + struct tpu_port ports[4]; + uint8_t acc, bak; + int x, y; + + size_t pc; + struct inst *inst; + size_t inst_cap; + size_t inst_cnp; + +}; + +struct tpu_map_link { + size_t x, y; + struct tpu *tpu; + struct tpu_map_link *next; +}; + +struct tpu_map { + struct tpu_map_link *buckets[TPU_MAP_BUCKETS]; +}; + +void tpu_init(struct tpu *tpu); +void tpu_add_inst(struct tpu *tpu, enum tpu_inst_type inst, + int op1, uint8_t op1_lit, int op2, uint8_t op2_lit); +void tpu_step(struct tpu *tpu, uint8_t *up, uint8_t *right, + uint8_t *down, uint8_t *left); +void tpu_deinit(struct tpu *tpu); + +void tpu_map_init(struct tpu_map *map); +void tpu_map_add(struct tpu_map *map, struct tpu *tpu); +struct tpu *tpu_map_get(int x, int y); +void tpu_map_deinit(struct tpu_map *map);