diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/asm_eval.py | 47 | ||||
| -rw-r--r-- | src/asm_ops.py | 154 | ||||
| -rw-r--r-- | src/asm_pass0.py | 50 | ||||
| -rw-r--r-- | src/asm_pass1.py | 48 | ||||
| -rw-r--r-- | src/asm_pass2.py | 40 | ||||
| -rw-r--r-- | src/asm_pass3.py | 78 | ||||
| -rw-r--r-- | src/asm_pass4.py | 61 | ||||
| -rwxr-xr-x | src/assembler.py | 82 | ||||
| -rw-r--r-- | src/ast_compiler.py | 716 | ||||
| -rw-r--r-- | src/bytecode_compiler.py | 220 | ||||
| -rw-r--r-- | src/charmem.py | 63 | ||||
| -rw-r--r-- | src/convert_ttf.py | 52 | ||||
| -rw-r--r-- | src/dig_install.py | 60 | ||||
| -rw-r--r-- | src/optimizer.py | 189 | ||||
| -rw-r--r-- | src/tokenizer.py | 47 | ||||
| -rwxr-xr-x | src/ucode.py | 112 |
16 files changed, 0 insertions, 2019 deletions
diff --git a/src/asm_eval.py b/src/asm_eval.py deleted file mode 100644 index c05fc03..0000000 --- a/src/asm_eval.py +++ /dev/null @@ -1,47 +0,0 @@ -constants: dict[str, str] = { - # Registers - "ra": "0", - "rb": "1", - "rc": "2", - "rd": "3", - "re": "4", - "rf": "5", - "rg": "6", - "rh": "7", - # ALU instructions - "al_plus": "0", - "al_minus": "1", - "al_and": "2", - "al_or": "3", - "al_xor": "4", - "al_slr": "5", - "al_sar": "6", - "al_sll": "7", - # ALU flags - "carry": "0", - "overflow": "1", - "zero": "2", - "sign": "3", -} - -Symbols = dict[str, int] - -def check_size(bits: int, val: int) -> None: - if val >= 2 ** bits: - raise Exception(f"Value does not fit in {bits} bits: 0x{val:>04x}") - -def eval_symbol(symbols: Symbols, c: str) -> str: - if c in symbols: - return str(symbols[c]) - - if c in constants: - return constants[c] - - return c - -def eval_expr(symbols: Symbols, expr: str, bits: int = 16) -> int: - expr = expr.lower() - expr = eval_symbol(symbols, expr) - ret = eval(expr, symbols.copy()) # eval as Python expr - check_size(bits, ret) - return ret diff --git a/src/asm_ops.py b/src/asm_ops.py deleted file mode 100644 index 885c275..0000000 --- a/src/asm_ops.py +++ /dev/null @@ -1,154 +0,0 @@ -from typing import Callable -from asm_eval import * -from dataclasses import dataclass - -@dataclass -class Meta: - address: int - -def make_alr(meta: Meta, symbols: Symbols, alu_op: str, left: str, right: str, target: str) -> int: - """ALR 0000 TTTL LLRR RSSS""" - target_e = eval_expr(symbols, target, bits=3) - left_e = eval_expr(symbols, left, bits=3) - right_e = eval_expr(symbols, right, bits=3) - alu_op_e = eval_expr(symbols, alu_op, bits=3) - word = (0b0000 << 12) + \ - (target_e << 9) + \ - (left_e << 6) + \ - (right_e << 3) + \ - alu_op_e - return word - -def make_ali(meta: Meta, symbols: Symbols, alu_op: str, left: str, imm: str, target: str) -> int: - """ALI 0001 TTTL LLII ISSS""" - target_e = eval_expr(symbols, target, bits=3) - left_e = eval_expr(symbols, left, bits=3) - imm_e = eval_expr(symbols, imm, bits=3) - alu_op_e = eval_expr(symbols, alu_op, bits=3) - word = (0b0001 << 12) + \ - (target_e << 9) + \ - (left_e << 6) + \ - (imm_e << 3) + \ - alu_op_e - return word - -def make_ldr(meta: Meta, symbols: Symbols, addr_reg: str, to_reg: str) -> int: - """LDR 0010 TTTR RRXX XXXX""" - to_reg_e = eval_expr(symbols, to_reg, bits=3) - addr_reg_e = eval_expr(symbols, addr_reg, bits=3) - word = (0b0010 << 12) + \ - (to_reg_e << 9) + \ - (addr_reg_e << 6) - return word - -def make_str(meta: Meta, symbols: Symbols, from_reg: str, addr_reg: str) -> int: - """STR 0011 XXXL LLRR RXXX""" - from_reg_e = eval_expr(symbols, from_reg, bits=3) - addr_reg_e = eval_expr(symbols, addr_reg, bits=3) - word = (0b0011 << 12) + \ - (addr_reg_e << 6) + \ - (from_reg_e << 3) - return word - -def make_ldi(meta: Meta, symbols: Symbols, imm: str, to_reg: str) -> int: - """LDI 0100 TTTI IIII IIII""" - to_reg_e = eval_expr(symbols, to_reg, bits=3) - imm_e = eval_expr(symbols, imm, bits=9) - word = (0b0100 << 12) + \ - (to_reg_e << 9) + \ - imm_e - return word - -def make_jpr(meta: Meta, symbols: Symbols, addr_reg: str) -> int: - """JPR 0101 XXXR RRXX XXXX""" - addr_reg_e = eval_expr(symbols, addr_reg, bits=3) - word = (0b0101 << 12) + \ - (addr_reg_e << 6) - return word - -def make_jpi(meta: Meta, symbols: Symbols, imm: str) -> int: - """JPI 0110 XXXI IIII IIII""" - imm_e = eval_expr(symbols, imm, bits=9) - imm_e = imm_e - meta.address - 1 - imm_e = imm_e & (0b111111111) - #print("symbols:", symbols) - #print(meta, imm, f"0x{imm_e:>0x}") - word = (0b0110 << 12) + \ - imm_e - return word - -def make_brr(meta: Meta, symbols: Symbols, flag_s: str, addr_reg: str) -> int: - """brr 0111 XFFR RRXX XXXX""" - flag_s_e = eval_expr(symbols, flag_s, bits=2) - addr_reg_e = eval_expr(symbols, addr_reg, bits=3) - word = (0b0111 << 12) + \ - (flag_s_e << 9) + \ - (addr_reg_e << 6) - return word - -def make_bri(meta: Meta, symbols: Symbols, flag_s: str, imm: str) -> int: - """BRI 1000 XFFI IIII IIII""" - flag_s_e = eval_expr(symbols, flag_s, bits=2) - imm_e = eval_expr(symbols, imm, bits=9) - imm_e = imm_e - meta.address - 1 - imm_e = imm_e & (0b111111111) - word = (0b1000 << 12) + \ - (flag_s_e << 9) + \ - imm_e - return word - -def make_lpc(meta: Meta, symbols: Symbols, target: str) -> int: - """LPC 1001 TTTX XXXX XXXX""" - target_e = eval_expr(symbols, target, bits=3) - word = (0b1001 << 12) + \ - (target_e << 9) - return word - -def make_rti(meta: Meta, symbols: Symbols) -> int: - """RTI 1110 XXXX XXXX XXXX""" - word = (0b1110 << 12) - return word - -def make_hlt(meta: Meta, symbols: Symbols) -> int: - """HLT 1111 XXXX XXXX XXXX""" - word = (0b1111 << 12) - return word - -OpWordDict = dict[str, Callable[..., int]] -operations: OpWordDict = { - "alr": make_alr, - "ali": make_ali, - "ldr": make_ldr, - "str": make_str, - "ldi": make_ldi, - "jpr": make_jpr, - "jpi": make_jpi, - "brr": make_brr, - "bri": make_bri, - "lpc": make_lpc, - "rti": make_rti, - "hlt": make_hlt, -} - -ExpandResult = list[list[str]] -ExpandFn = Callable[..., ExpandResult] -OpExpansionDict = dict[str, ExpandFn] - -def expand_id(*parts: str) -> ExpandResult: - return [list(parts)] - -default_expansions: OpExpansionDict = { - "alr": lambda *args: expand_id("alr", *args), - "ali": lambda *args: expand_id("ali", *args), - "ldr": lambda *args: expand_id("ldr", *args), - "str": lambda *args: expand_id("str", *args), - "ldi": lambda *args: expand_id("ldi", *args), - "jpr": lambda *args: expand_id("jpr", *args), - "jpi": lambda *args: expand_id("jpi", *args), - "brr": lambda *args: expand_id("brr", *args), - "bri": lambda *args: expand_id("bri", *args), - "lpc": lambda *args: expand_id("lpc", *args), - "rti": lambda *args: expand_id("rti", *args), - "hlt": lambda *args: expand_id("hlt", *args), -} - diff --git a/src/asm_pass0.py b/src/asm_pass0.py deleted file mode 100644 index f5b6b0b..0000000 --- a/src/asm_pass0.py +++ /dev/null @@ -1,50 +0,0 @@ -from dataclasses import dataclass -import os.path -from asm_ops import * -from asm_eval import * -from tokenizer import * - -@dataclass -class Result0Line: - line_num: int - src_file: str - line: str - -@dataclass -class Result0: - lines: list[Result0Line] - -def pass_0(lines: list[str], file_name: str) -> Result0: - result_lines: list[Result0Line] = [] - - for (line_num, line) in enumerate(lines): - line = line.split(";")[0].strip() - if line == "": continue - - keyword, *args = tokenize(line) - - match keyword: - case "@include": - asm_file_name = args[0] - path = os.path.join(os.path.dirname(file_name), asm_file_name + ".atk16") - with open(path, "r") as f: - incl_lines = f.readlines() - - incl_result0 = pass_0(incl_lines, asm_file_name) - for incl_line in incl_result0.lines: - result_lines.append(Result0Line( - src_file=incl_line.src_file, - line_num=incl_line.line_num, - line=incl_line.line - )) - - case _: - result_lines.append(Result0Line( - src_file=file_name, - line_num=line_num, - line=line - )) - - return Result0( - lines=result_lines - ) diff --git a/src/asm_pass1.py b/src/asm_pass1.py deleted file mode 100644 index 0b06738..0000000 --- a/src/asm_pass1.py +++ /dev/null @@ -1,48 +0,0 @@ -import importlib -import sys -import os.path -from dataclasses import dataclass -from asm_ops import * -from asm_eval import * -from asm_pass0 import * -from tokenizer import tokenize - -@dataclass -class Result1Line: - line_num: int - src_file: str - parts: list[str] - -@dataclass -class Result1: - lines: list[Result1Line] - operations: OpExpansionDict - -def pass_1(result0: Result0) -> Result1: - result_lines: list[Result1Line] = [] - operations: OpExpansionDict = default_expansions.copy() - - for line in result0.lines: - keyword, *args = tokenize(line.line) - match keyword: - case "@use": - module_name, ops = args[0].split(":") - ops_split = ops.split(",") - sys.path.append(os.path.dirname(line.src_file)) - module = importlib.import_module(module_name) - mod_expansions: OpExpansionDict = module.expansions - for op in mod_expansions: - expansion = mod_expansions[op] - if ops == "*" or op in ops_split: - operations[op] = expansion - case _: - result_lines.append(Result1Line( - src_file=line.src_file, - line_num=line.line_num, - parts=[keyword, *args] - )) - - return Result1( - operations=operations, - lines=result_lines - ) diff --git a/src/asm_pass2.py b/src/asm_pass2.py deleted file mode 100644 index e2ad4ec..0000000 --- a/src/asm_pass2.py +++ /dev/null @@ -1,40 +0,0 @@ -from dataclasses import dataclass -from asm_ops import * -from asm_eval import * -from asm_pass1 import * - -@dataclass -class Result2Line: - line_num: int - src_file: str - parts: list[str] - original_parts: list[str] - -@dataclass -class Result2: - lines: list[Result2Line] - operations: OpExpansionDict - -def pass_2(result1: Result1) -> Result2: - result_lines: list[Result2Line] = [] - - for line in result1.lines: - keyword, *args = line.parts - if keyword in result1.operations: - fn = result1.operations[keyword] - output: list[list[str]] = fn(*args) - else: - output = [line.parts] - - for (idx, parts) in enumerate(output): - result_lines.append(Result2Line( - line_num=line.line_num, - src_file=line.src_file, - parts=parts, - original_parts=line.parts if idx == 0 else ["..."] - )) - - return Result2( - operations=result1.operations, - lines=result_lines - ) diff --git a/src/asm_pass3.py b/src/asm_pass3.py deleted file mode 100644 index 9c8cb34..0000000 --- a/src/asm_pass3.py +++ /dev/null @@ -1,78 +0,0 @@ -from dataclasses import dataclass -from asm_ops import * -from asm_eval import * -from asm_pass2 import * - -@dataclass -class Result3Line: - line_num: int - src_file: str - address: int - parts: list[str] - original_parts: list[str] - -@dataclass -class Result3: - lines: list[Result3Line] - operations: OpExpansionDict - symbols: dict[str, int] - -def pass_3(result2: Result2) -> Result3: - result_lines: list[Result3Line] = [] - symbols: dict[str, int] = {} - address = 0 - - for line in result2.lines: - keyword, *args = line.parts - match keyword: - case "@address": - address = eval_expr(symbols, args[0]) - continue - case "@label": - label = args[0] - if label in symbols: - raise Exception(f"When defining label {label} as {address:>04x}, symbol {label} already defined as {symbols[label]:>04x}") - symbols[args[0]] = address - continue - case "@let": - symbols[args[0]] = eval_expr(symbols, args[1]) - continue - case _: - result_lines.append(Result3Line( - line_num=line.line_num, - src_file=line.src_file, - parts=line.parts, - address=address, - original_parts=line.original_parts, - )) - address += 1 - - result_lines.sort(key=lambda l: l.address) - for result_line in result_lines: - rows_with_same_addr = list(filter(lambda l: l.address == result_line.address, result_lines)) - n = len(rows_with_same_addr) - if n > 1: - formatted = format_overlapping_rows(rows_with_same_addr) - raise Exception(f"Overlapping segments: address 0x{result_line.address:>04x} has conflicting definitions:\n{formatted}") - return Result3( - operations=result2.operations, - lines=result_lines, - symbols=symbols, - ) - -def format_overlapping_rows(rows: list[Result3Line]) -> str: - longest_val = 0 - for row in rows: - joined = " ".join(row.parts) - if len(joined) > longest_val: - longest_val = len(joined) - - first_col_width = longest_val + 4 - - results: list[str] = [] - for row in rows: - joined = " ".join(row.parts) - result = joined + (first_col_width - len(joined)) * " " + f" ({row.src_file}:{row.line_num})" - results.append(result) - - return "\n".join(results) diff --git a/src/asm_pass4.py b/src/asm_pass4.py deleted file mode 100644 index 2fff870..0000000 --- a/src/asm_pass4.py +++ /dev/null @@ -1,61 +0,0 @@ -from dataclasses import dataclass -from asm_ops import * -from asm_eval import * -from asm_pass3 import * - -@dataclass -class Result4Line: - line_num: int - src_file: str - address: int - word: int - text: str - original_text: str - -@dataclass -class Result4: - lines: list[Result4Line] - operations: OpExpansionDict - symbols: dict[str, int] - -def pass_4(result3: Result3) -> Result4: - result_lines: list[Result4Line] = [] - - for line in result3.lines: - keyword, *args = line.parts - meta = Meta( - address=line.address, - ) - - text = " ".join([keyword, *args]) - original_text = " ".join(line.original_parts) - - if keyword in operations: - fn = operations[keyword] - word = fn(meta, result3.symbols, *args) - result_lines.append(Result4Line( - line_num=line.line_num, - src_file=line.src_file, - address=line.address, - word=word, - text=text, - original_text=original_text, - )) - else: - try: - result_lines.append(Result4Line( - line_num=line.line_num, - src_file=line.src_file, - address=line.address, - word=eval_expr(result3.symbols, keyword), - text=text, - original_text=original_text, - )) - except: - raise Exception(f"Invalid assembly at {line.src_file}:{line.line_num + 1}\n\n{line}") - - return Result4( - operations=result3.operations, - symbols=result3.symbols, - lines=result_lines, - ) diff --git a/src/assembler.py b/src/assembler.py deleted file mode 100755 index c678ba0..0000000 --- a/src/assembler.py +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/bin/env python3 -# Assemble ATK16 assembly to bytecode - -import sys -from asm_ops import * -from asm_eval import * -from asm_pass0 import pass_0 -from asm_pass1 import pass_1 -from asm_pass2 import pass_2 -from asm_pass3 import pass_3 -from asm_pass4 import pass_4 - -if len(sys.argv) != 3: - print("usage: assembler.py <infile> <outfile> # read from file") - print(" assembler.py - <outfile> # read from stdin") - sys.exit(1) - -infile_path = sys.argv[1] -outfile_path = sys.argv[2] - -src = "" -if (infile_path == "-"): - for line in sys.stdin: - src += line -else: - with open(infile_path, "r") as f: - src = f.read() - -src_lines = src.splitlines() - -### Utils - -def parse(line: str) -> list[str]: - depth = 0 - result: list[str] = [] - acc: str = "" - for c in line: - if c.isspace() and depth == 0: - result.append(acc) - acc = "" - elif c == "(": - depth += 1 - acc += "(" - elif c == ")": - depth -= 1 - acc += ")" - else: - acc += c - - result.append(acc) - return list(filter(lambda x: len(x) > 0, result)) - -result0 = pass_0(src_lines, infile_path) -result1 = pass_1(result0) -result2 = pass_2(result1) -result3 = pass_3(result2) -result4 = pass_4(result3) - -nop = bytearray([0b1000_0000, 0]) -result = bytearray() -# initially one nop -result.extend(nop) - -for line in result4.lines: - for (symbol, symbol_value) in result4.symbols.items(): - if line.address == symbol_value: - print(f"{symbol}:") - - if len(result) < 2 * line.address + 1: - result.extend((2 * line.address + 1 - len(result)) * nop) - - out_line = f"{line.address:>08x} 0x{line.word:>04x} {line.text}" - out_spaces_n = (42 - len(out_line)) - out_spaces = out_spaces_n * " " if out_spaces_n > 0 else 4 * " " - print(f"{out_line}{out_spaces}{line.original_text}") - result[2 * line.address + 0] = ((line.word >> 8) & 0xff) - result[2 * line.address + 1] = ((line.word >> 0) & 0xff) - -with open(outfile_path, "wb") as f: - f.write(result) - -print(f"Wrote {len(result)} bytes to {outfile_path}") diff --git a/src/ast_compiler.py b/src/ast_compiler.py deleted file mode 100644 index fe6f5a6..0000000 --- a/src/ast_compiler.py +++ /dev/null @@ -1,716 +0,0 @@ -#!/usr/bin/env python3 -# Generate .atk16 assembly from a subset of Python - -import sys -import ast - -from dataclasses import dataclass -from typing import Literal, Set, cast, Any, TypeAlias, TypeVar -from collections import OrderedDict - - -if len(sys.argv) != 3: - print("usage: ast_compiler.py <infile.py> <outfile.atk16>") - sys.exit(1) - -infile_path = sys.argv[1] -outfile_path = sys.argv[2] - -Label = str -StackOffset = int -RegChar = Literal["A", "B", "C", "D", "E", "F", "G", "H"] - -@dataclass -class Reg(): - reg: RegChar - - def __str__(self): - return f"R{self.reg}" - -ALL_REGS: list[RegChar] = ["A", "B", "C", "D", "E", "F", "G", "H"] -FRAME_POINTER_REG_CHAR: RegChar = "G" # points to base of stack frame -FRAME_POINTER_REG = Reg(FRAME_POINTER_REG_CHAR) -STACK_POINTER_REG_CHAR: RegChar = "H" -STACK_POINTER_REG = Reg(STACK_POINTER_REG_CHAR) -SPECIAL_REGS: list[RegChar] = [FRAME_POINTER_REG_CHAR, STACK_POINTER_REG_CHAR] -GENERIC_REGS: OrderedDict[RegChar, None] = OrderedDict() -for char in ALL_REGS: - if char not in SPECIAL_REGS: - GENERIC_REGS[cast(RegChar, char)] = None - -def format_asm_row(asm: str) -> str: - if not (asm.startswith("@") or asm.startswith(";")) and not asm.startswith(" ") and len(asm) > 0: - return " " + asm - else: - return asm - -class Compiler(ast.NodeVisitor): - def __init__(self): - self.const_asm: list[str] = [] - self.program_asm: list[str] = [ - "@label main" - ] - self.function_def_asms: list[str] = [] - self.currently_emitting_asm_list = self.program_asm - - self.local_bindings: dict[str, StackOffset] = {} - self.const_bindings: dict[str, Label] = {} - - self.unique_name_counter = 0 - self.latest_break_target: Label | None = None - - self.reserved_regs: OrderedDict[RegChar, None] = OrderedDict() - - def get_unique_name(self, prefix: str): - ret = f"{prefix}_{self.unique_name_counter}" - self.unique_name_counter += 1 - return ret - - def assign_const(self, name: str, value: int): - prev_currently_emitting_asm_list = self.currently_emitting_asm_list - self.currently_emitting_asm_list = self.const_asm - - self.emit_label(name) - self.emit(f"{value}") - - self.const_bindings[name] = name - - self.currently_emitting_asm_list = prev_currently_emitting_asm_list - - def emit(self, asm: str): - asm = asm.strip() - asm = format_asm_row(asm) - - self.currently_emitting_asm_list.append(asm) - - def emit_label(self, label_name: str): - label_name = label_name.lower() - self.emit(f"@label {label_name}") - - def alloc_reg(self) -> Reg: - for reg in GENERIC_REGS: - if not reg in self.reserved_regs: - self.reserved_regs[reg] = None - return Reg(reg) - - raise Exception("Ran out of registers, TODO use stack") - - def free_reg(self, reg: Reg): - self.reserved_regs.pop(reg.reg) - - class RegContextManager: - def __init__(self, compiler): - self.compiler = compiler - - def __enter__(self) -> Reg: - self.reg = self.compiler.alloc_reg() - return self.reg - - def __exit__(self, exc_type, exc_value, exc_tb): - self.compiler.free_reg(self.reg) - - def allocated_reg(self): - return Compiler.RegContextManager(self) - - def compile(self, bootstrap_asm: str, source: str) -> str: - tree = ast.parse(source) - print(ast.dump(tree, indent=4)) - self.visit(tree) - return "\n".join([ - bootstrap_asm, - "", - "\n".join(self.const_asm), - "", - "\n".join(self.function_def_asms), - "", - "\n".join(self.program_asm), - "" - ]) - - def generic_visit(self, node: ast.AST) -> Any: - raise NotImplementedError(f"type {type(node)}, value: {node}") - - def emit_builtin_call(self, name: str, args: list[ast.expr]): - self.emit(f"; Builtin call {name} {args}") - match name: - case "asm": - match args: - case [ast.Constant(str(value))]: - self.emit(value) - - # return None - with self.allocated_reg() as arg: - self.emit(f"ldi 0 {arg}") - self.emit(f"spu {arg}") - - case other: raise Exception(f"asm: invalid args: {other}") - - case "store": - if len(args) != 2: - raise Exception("Invalid number of arguments to store: " + str(len(args))) - - # Evaluate args before call - for arg in args: - self.visit(arg) - - with self.allocated_reg() as arg1, self.allocated_reg() as arg2: - self.emit(f"spo {arg2}") - self.emit(f"spo {arg1}") - self.emit(f"str {arg2} {arg1}") - - # return None - self.emit(f"ldi 0 {arg1}") - self.emit(f"spu {arg1}") - - case "load": - if len(args) != 1: - raise Exception("Invalid number of arguments to load: " + str(len(args))) - - self.visit(args[0]) - - self.emit("; (load) dereferencing top of stack") - with self.allocated_reg() as arg: - self.emit(f"spo {arg}") - self.emit(f"ldr {arg} {arg}") - self.emit(f"spu {arg}") - - case "ord": - if len(args) != 1: - raise Exception("Invalid number of arguments to ord: " + str(len(args))) - - self.visit(args[0]) - - case other: - raise Exception(f"Unknown builtin: {other}") - - def eval_int_constant_and_spu(self, value: int): - with self.allocated_reg() as reg: - if value >= 0 and value < 8: - self.emit(f"ldi {value} {reg}") - else: - name = self.get_unique_name("int") - self.assign_const(name, value) - self.emit(f"ldi {name} {reg}") - self.emit(f"ldr {reg} {reg} ; {name} = {value}") - - self.emit(f"spu {reg}") - - def emit_add_imm(self, reg1: str, addend: int, target_reg: str): - if addend >= 0 and addend < 8: - self.emit(f"addi {reg1} {addend} {target_reg}") - else: - name = self.get_unique_name("int") - self.assign_const(name, addend) - - with self.allocated_reg() as addend_reg: - self.emit(f"ldi {name} {addend_reg}") - self.emit(f"ldr {addend_reg} {addend_reg}") - self.emit(f"add {reg1} {addend_reg} {target_reg}") - - def visit_Module(self, node: ast.Module): - stmts = node.body - frame_names = self.collect_local_variables(stmts) - - self.emit("; stack frame with offsets") - for offset, name in enumerate(frame_names): - self.local_bindings[name] = offset - self.emit(f"; {name} {offset}") - - self.emit_add_imm("SP", len(frame_names), "SP") - - for stmt in stmts: - if type(stmt) == ast.Expr and type(stmt.value) != ast.Call: - self.emit("; NOP top-level expression") - continue - - self.visit(stmt) - - self.emit(f"mov FP SP") - self.emit("hlt") - - def visit_Expr(self, expr: ast.Expr): - self.visit(expr.value) - - with self.allocated_reg() as reg: - self.emit("; popping free-standing Expr result from stack") - self.emit(f"spo {reg}") - - def visit_UnaryOp(self, node: ast.UnaryOp): - self.emit(f"; {node.op}") - self.visit(node.operand) - match node.op: - case ast.Not(): - with self.allocated_reg() as reg1, self.allocated_reg() as reg2: - self.emit(f"spo {reg1}") - self.emit(f"ldi 1 {reg2}") - self.emit(f"andi {reg1} 1 {reg1}") - self.emit(f"xor {reg1} {reg2} {reg1}") - self.emit(f"spu {reg1}") - - case ast.Invert(): # aka bitwise not - with self.allocated_reg() as reg1: - self.emit(f"spo {reg1}") - self.emit(f"not {reg1}") - self.emit(f"spu {reg1}") - - case ast.UAdd(): # +a - pass # nop - - case ast.USub(): # -a - with self.allocated_reg() as reg1: - self.emit(f"spo {reg1}") - self.emit(f"not {reg1}") - self.emit(f"addi {reg1} 1 {reg1}") - self.emit(f"spu {reg1}") - - case other: - raise NotImplementedError(f"Unhandled UnaryOp: {other}") - - def visit_BoolOp(self, node: ast.BoolOp): - self.emit(f"; {node.op}") - - match node.op: - case ast.And(): - label_short_circuit = self.get_unique_name("And_short_circuit") - - with self.allocated_reg() as reg: - for arg in node.values: - self.emit(f"; And operand {arg}") - self.visit(arg) - - self.emit(f"spo {reg}") - self.emit(f"addi {reg} 0 {reg}") - self.emit(f"bri zero {label_short_circuit}") - - self.emit_label(label_short_circuit) - self.emit(f"spu {reg}") - - case ast.Or(): - label_short_circuit = self.get_unique_name("Or_short_circuit") - - with self.allocated_reg() as reg1, self.allocated_reg() as reg2: - for arg in node.values: - self.emit(f"; Or operand {arg}") - self.visit(arg) - - self.emit(f"spo {reg1}") - self.emit(f"subi {reg1} 1 {reg2}") - self.emit(f"bri carry {label_short_circuit}") - - self.emit_label(label_short_circuit) - self.emit(f"spu {reg1}") - - case other: - raise NotImplementedError(f"Unhandled BoolOp: {other}") - - def visit_BinOp(self, node: ast.BinOp): - self.emit(f"; {node.op}") - - self.emit(f"; BinOp lhs {node}") - self.visit(node.left) - self.emit(f"; BinOp rhs {node}") - self.visit(node.right) - - with self.allocated_reg() as reg1, self.allocated_reg() as reg2: - self.emit(f"spo {reg2}") - self.emit(f"spo {reg1}") - - match node.op: - case ast.Add(): - self.emit(f"add {reg1} {reg2} {reg1}") - case ast.Sub(): - self.emit(f"sub {reg1} {reg2} {reg1}") - case ast.BitAnd(): - self.emit(f"and {reg1} {reg2} {reg1}") - case ast.BitOr(): - self.emit(f"or {reg1} {reg2} {reg1}") - case ast.BitXor(): - self.emit(f"xor {reg1} {reg2} {reg1}") - case ast.LShift(): - self.emit(f"sll {reg1} {reg2} {reg1}") - case ast.RShift(): - self.emit(f"slr {reg1} {reg2} {reg1}") - case other: - raise NotImplementedError(f"Unhandled BinOp: {other}") - - self.emit(f"spu {reg1}") - - def visit_Constant(self, node: ast.Constant): - self.emit(f"; {node}") - match node.value: - case bool(value): - int_value = 1 if value else 0 - self.eval_int_constant_and_spu(int_value) - case int(value): - self.eval_int_constant_and_spu(value) - case str(value): - if len(value) > 1: - raise Exception("Invalid string, only single char values allowed: " + value) - - c = value[0] - int_value = ord(c) - self.eval_int_constant_and_spu(int_value) - case other: - raise NotImplementedError(f"Unhandled Constant: {other}") - - def visit_If(self, node: ast.If): - self.emit(f"; {node}") - - self.visit(node.test) - label_false = self.get_unique_name("If_false_branch") - label_end = self.get_unique_name("If_end_branch") - - with self.allocated_reg() as reg: - self.emit(f"spo {reg}") - self.emit(f"addi {reg} 0 {reg}") - self.emit(f"bri zero {label_false}") - - for true_branch_stmt in node.body: - if type(true_branch_stmt) == ast.Expr and type(true_branch_stmt.value) != ast.Call: - self.emit("; NOP top-level expression") - continue - - self.visit(true_branch_stmt) - - self.emit(f"jpi {label_end}") - self.emit_label(label_false) - - for false_branch_stmt in node.orelse: - if type(false_branch_stmt) == ast.Expr and type(false_branch_stmt.value) != ast.Call: - self.emit("; NOP top-level expression") - continue - - self.visit(false_branch_stmt) - - self.emit_label(label_end) - - def visit_Compare(self, node: ast.Compare): - self.emit(f"; {node}") - - if len(node.ops) > 1: - raise Exception("Multiple compare ops not supported") - - if len(node.comparators) > 1: - raise Exception("Multiple comparators not supported") - - op = node.ops[0] - left = node.left - right = node.comparators[0] - - label_true = self.get_unique_name("Compare_true") - label_end = self.get_unique_name("Compare_end") - - self.visit(left) - self.visit(right) - - self.emit(f"; Comparing {left} {op} {right}") - with self.allocated_reg() as reg_lhs, self.allocated_reg() as reg_rhs: - match op: - case ast.Lt(): # lhs < rhs - self.emit(f"spo {reg_rhs}") - self.emit(f"spo {reg_lhs}") - - self.emit(f"sub {reg_lhs} {reg_rhs} {reg_lhs}") - self.emit(f"bri carry {label_true}") - - # false branch - self.emit(f"ldi 0 {reg_lhs}") - self.emit(f"spu {reg_lhs}") - self.emit(f"jpi {label_end}") - - # true branch - self.emit_label(label_true) - self.emit(f"ldi 1 {reg_lhs}") - self.emit(f"spu {reg_lhs}") - - self.emit_label(label_end) - - def visit_While(self, node: ast.While): - self.emit(f"; {node}") - - label_test = self.get_unique_name("While_test") - label_else = self.get_unique_name("While_else") - label_end = self.get_unique_name("While_end") - - prev_break_target = self.latest_break_target - self.latest_break_target = label_end - - with self.allocated_reg() as reg: - self.emit_label(label_test) - self.visit(node.test) - self.emit(f"spo {reg}") - self.emit(f"addi {reg} 0 {reg}") - self.emit(f"bri zero {label_else}") - - for body_stmt in node.body: - if type(body_stmt) == ast.Expr and type(body_stmt.value) != ast.Call: - self.emit("; NOP top-level expression") - continue - - self.visit(body_stmt) - - self.emit(f"jpi {label_test}") - - self.emit_label(label_else) - - for else_stmt in node.orelse: - self.visit(else_stmt) - - self.emit_label(label_end) - - self.latest_break_target = prev_break_target - - def visit_Pass(self, node: ast.Pass): - self.emit(f"; {node}") - pass - - def visit_Break(self, node: ast.Break): - self.emit(f"; {node}") - - if self.latest_break_target is None: - raise Exception("Invalid break: no break target defined, i.e. no place to break out to") - - self.emit(f"jpi {self.latest_break_target}") - - def visit_For(self, node: ast.For): - raise Exception("For loops not supported. Consider using a while loop instead.") - - def visit_Lambda(self, node: ast.Lambda): - raise Exception("Lambda functions not supported. Consider using a named function instead.") - - def collect_local_variables(self, stmts: list[ast.stmt]): - result: list[str] = [] - symbols: list[str] = [] - for stmt in stmts: - match stmt: - case ast.Assign(targets=[ast.Name(name)]): - symbols.append(name) - case ast.Assign(other): - raise Exception(f"Unsupported assignment in function definition body: {other}") - case ast.While(body=body): - symbols += self.collect_local_variables(body) - case ast.If(body=tb, orelse=fb): - symbols += self.collect_local_variables(tb) - symbols += self.collect_local_variables(fb) - case other: pass - - for symbol in symbols: - if symbol not in result: - result.append(symbol) - - return result - - def visit_FunctionDef(self, node: ast.FunctionDef): - prev_currently_emitting_asm_list = self.currently_emitting_asm_list - self.currently_emitting_asm_list = self.function_def_asms - self.emit(f"; {node}") - - fn_name = node.name - fn_params = [str(param.arg) for param in node.args.args] - fn_stmts = node.body - - # Add a "return None" to the end to make sure the function returns - if len(fn_stmts) == 0 or type(fn_stmts[len(fn_stmts) - 1]) != ast.Return: - fn_stmts.append(ast.Return(value=None)) - - if len(node.args.kwonlyargs) > 0 or len(node.args.posonlyargs) > 0 or len(node.args.kw_defaults) > 0 or len(node.args.defaults) > 0: - raise Exception("Only simple positional args are supported for now in function definitions.") - - self.emit_label(fn_name) - local_var_names = self.collect_local_variables(fn_stmts) - frame_names = fn_params + local_var_names - - # Move stack pointer to accommodate local variables - if len(local_var_names) > 0: - self.emit_add_imm("SP", len(local_var_names), "SP") - - prev_local_bindings = self.local_bindings - self.local_bindings = {} - self.emit("; stack frame with offsets") - for offset, name in enumerate(frame_names): - self.local_bindings[name] = offset - self.emit(f"; {name} {offset}") - - for stmt in fn_stmts: - self.visit(stmt) - - self.local_bindings = prev_local_bindings - self.currently_emitting_asm_list = prev_currently_emitting_asm_list - - def visit_Return(self, node: ast.Return): - self.emit(f"; {node}") - - with self.allocated_reg() as reg1, self.allocated_reg() as reg2: - if node.value is not None: - self.visit(node.value) - else: - self.emit(f"ldi 0 {reg1}") - self.emit(f"spu {reg1}") - - self.emit(f"; return value is on top of stack") - - self.emit(f"; return from function") - self.emit(f"subi FP 1 FP") - self.emit(f"ldr FP {reg2}") - self.emit(f"jpr {reg2}") - - def visit_Call(self, node: ast.Call): - self.emit(f"; {node}") - - match node.func: - case ast.Attribute(ast.Name(id="atk16"), attr): - self.emit_builtin_call(attr, node.args) - case ast.Name(name): - self.emit(f"; Call function {name}") - - # Push the current FP - self.emit("spu FP") - #self.emit("mov SP FP") # BUG: FP needs to be moved AFTER EVALING ARGS! args depend on FP in calling frame if there are names to resolve! - - # Reserve a stack slot for the return address - # FP will point to this slot - with self.allocated_reg() as reg: - self.emit(f"ldi 0 {reg}") - self.emit(f"spu {reg}") - - # Evaluate args before call, pushing them to stack - for arg in node.args: - self.visit(arg) - - self.emit(f"subi SP {1 + len(node.args)} FP") - - with self.allocated_reg() as reg: - self.emit("; set up return address and jump to subroutine") - self.emit(f"lpc {reg}") - self.emit(f"addi {reg} 4 {reg}") # imm must equal number of primitive instrs from lpc until jpi - self.emit(f"str {reg} FP") - self.emit(f"addi FP 1 FP") - self.emit(f"jpi {name}") - # ...after return from call... - self.emit(f"spo {reg}") - - self.emit("mov FP SP") # move stack pointer to base of stack frame, i.e. top of previous frame - self.emit("spo FP") # restore FP of previous frame - self.emit(f"spu {reg}") - - case other: - raise NotImplementedError(f"Unhandled Call: {other}") - - def resolve_name(self, name: str) -> Label | StackOffset: - if name in self.local_bindings: - return self.local_bindings[name] - - if name in self.const_bindings: - return self.const_bindings[name] - - raise Exception(f"{name} is unbound") - - def visit_Name(self, node: ast.Name): - self.emit(f"; {node} ({node.id})") - - name = node.id.lower() - addr = self.resolve_name(name) - - with self.allocated_reg() as reg: - match addr: - case Label(label): - self.emit(f"ldi {label} {reg}") - case StackOffset(offset): - self.emit(f"ldi {offset} {reg}") - self.emit(f"add FP {reg} {reg}") - case other: - raise Exception(f"Unsupported addr value: {other}") - - self.emit(f"ldr {reg} {reg}") - self.emit(f"spu {reg}") - - def visit_Assign(self, node: ast.Assign): - self.emit(f"; {node}") - - targets = node.targets - value = node.value - match targets: - case [ast.Name(name)]: - offset = self.resolve_name(name) - - if type(offset) != StackOffset: - raise Exception(f"Invalid address {offset} for name {name}. Can only assign to stack offsets.") - - self.emit(f"; assigning {name} at FP + {offset}") - - with self.allocated_reg() as reg1, self.allocated_reg() as reg2: - self.emit(f"; evaluating value to be assigned") - self.visit(value) - self.emit(f"; assigning stack address (FP + {offset}) := top of stack") - self.emit(f"spo {reg1}") # value - - self.emit(f"ldi {offset} {reg2}") - self.emit(f"add {reg2} FP {reg2}") # address - self.emit(f"str {reg1} {reg2}") - - case other: - raise Exception(f"Unsupported assign targets: {other}") - - def visit_AugAssign(self, node: ast.AugAssign): - op = node.op - lhs = node.target - rhs = node.value - - if type(lhs) != ast.Name: - raise Exception("AugAssign to non-Name lhs not yet supported!") - - # construct bin op and assignment - bin_op = ast.BinOp(left=lhs, op=op, right=rhs) - assign = ast.Assign(targets=[lhs], value=bin_op) - - self.visit(assign) - - def visit_Import(self, node: ast.Import) -> Any: - match node.names: - case [ast.alias(name="atk16")]: - return - case other: - raise NotImplementedError(f"Unsupported import {other}") - - def visit_AnnAssign(self, node: ast.AnnAssign): - match node: - case ast.AnnAssign( - target=ast.Name(name), - annotation=ast.Attribute( - value=ast.Name(id="atk16"), - attr="ConstWord16"), - value=ast.Constant(value) # TODO: constant folding - ): - if type(value) == int: - self.assign_const(name.lower(), value) - return - - raise NotImplementedError("Unhandled AnnAssign:\n" + ast.dump(node, indent=4)) - - def get_module_exports(self, node: ast.Module): - result: list[str] = [] - for stmt in node.body: - match stmt: - case ast.FunctionDef(name): - result.append(name) - case _: - # only function exported since recognizing which assignments are constants is kinda hard - pass - - return result - -with open(infile_path, "r") as f: - source_py = f.read() - -with open("asm/ast_compiler_bootstrap.atk16", "r") as f: - bootstrap_asm = f.read() - -compiler = Compiler() -asm_out = compiler.compile( - bootstrap_asm, - source_py, -) - -with open(outfile_path, "w") as f: - f.write(asm_out) - diff --git a/src/bytecode_compiler.py b/src/bytecode_compiler.py deleted file mode 100644 index 0492ddb..0000000 --- a/src/bytecode_compiler.py +++ /dev/null @@ -1,220 +0,0 @@ -#!/usr/bin/env python3 -# Generate .atk16 assembly from a subset of Python - -import sys -import dis - -from dataclasses import dataclass -from typing import Literal, Set, cast, Any -from collections import OrderedDict - -if len(sys.argv) != 3: - print("usage: compiler.py <infile.py> <outfile.atk16>") - sys.exit(1) - -infile_path = sys.argv[1] -outfile_path = sys.argv[2] - -with open(infile_path, "r") as f: - source_py = f.read() - -compiled_code = compile(source_py, infile_path, 'exec') -disassembled = dis.get_instructions(compiled_code) - -co_consts = compiled_code.co_consts -co_names = compiled_code.co_names -print("### Constants") -print(co_consts) -print("\n### Names") -print(co_names) -print("\n### Instructions") -instrs: list[tuple[str, int | None]] = [] -for instr in disassembled: - print(f"{instr.opname}\t{instr.arg}") - instrs.append((instr.opname, instr.arg)) - -### compile -asm_out: list[str] = [] -def emit(stmt: str) -> int: - ret = len(asm_out) - asm_out.append(stmt) - return ret - -_counter = 0 -def get_unique_name(prefix: str): - global _counter - ret = f"{prefix}_{_counter}" - _counter += 1 - return ret - -@dataclass -class Label: - label: str - - def __str__(self): - return f"@label {self.label}" - -RegChar = Literal["A", "B", "C", "D", "E", "F", "G", "H"] -GENERIC_REGS_LST: list[RegChar] = ["A", "B", "C", "D", "E", "F"] -GENERIC_REGS: OrderedDict[RegChar, None] = OrderedDict() -for char in GENERIC_REGS_LST: - GENERIC_REGS[cast(RegChar, char)] = None - -@dataclass -class Reg: - reg: RegChar - - def __str__(self): - return f"R{self.reg}" - -Value = Label | Reg - -reserved_regs: OrderedDict[RegChar, None] = OrderedDict() -def alloc_reg() -> Reg: - for reg in GENERIC_REGS: - if not reg in reserved_regs: - reserved_regs[reg] = None - return Reg(reg) - - raise Exception("Ran out of registers, TODO use stack") - -def free_reg(reg: Reg): - reserved_regs.pop(reg.reg) - -def emit_serialize_const(const: Any): - match const: - case int(v): - emit(f" {v}") - case str(v): - if len(v) > 1: - raise Exception("Cannot serialize const string: " + v) - - emit(f" {ord(v[0])}") - case _: - print(f"warn: cannot serialize {type(const)} ({const}), emitting zero") - emit(" 0") - -STACK_POINTER_REG = Reg('H') -emit(f"@let SP {STACK_POINTER_REG}") -emit("@use ext_std:*") -emit("@include bootstrap") - -py_consts_addr = emit("@label py_consts") - -for const_i, const in enumerate(co_consts): - emit(f"; {const_i}: {const}") - emit_serialize_const(const) - -emit("@label atk_store") -_reg_value = alloc_reg() -_reg_addr = alloc_reg() -emit(f" str RB RA") -emit(f" rsr") -free_reg(_reg_value) -free_reg(_reg_addr) - -emit("@label atk_enable_text_mode") -emit(" set_graphics_mode gr_text_mode") -emit(" rsr") - -emit("@label main") - -instr_i = 0 - -def instr_pop(): - global instr_i - ret = instrs[instr_i] - instr_i += 1 - return ret - -def instr_pop_expect(opcode: str, arg: int | None = -1): - ret_op, ret_arg = instr_pop() - if ret_op != opcode: - raise Exception(f"Expected opcode {opcode}, got {ret_op}") - if arg != -1 and arg != ret_arg: - raise Exception(f"Expected instruction arg {arg}, got {ret_arg}") - return ret_op, ret_arg - -def instr_peek(): - return instrs[instr_i] - -def instr_peek_expect(opcode: str, arg: int | None = -1): - ret_op, ret_arg = instr_peek() - if ret_op != opcode: - raise Exception(f"Expected {opcode}, got {ret_op}") - if arg != -1 and arg != ret_arg: - raise Exception(f"Expected instruction arg {arg}, got {ret_arg}") - return ret_op, ret_arg - -instr_pop_expect("RESUME") -instr_pop_expect("LOAD_CONST", 0) -instr_pop_expect("LOAD_CONST", 1) -instr_pop_expect("IMPORT_NAME", 0) -instr_pop_expect("IMPORT_STAR", None) - -while instr_i < len(instrs): - opcode, arg = instr_pop() - emit(f"; {opcode} {arg}") - match opcode: - case "LOAD_CONST": - reg = alloc_reg() - emit(f" ldi ${{py_consts + {cast(int, arg)}}} {reg}") - emit(f" ldr {reg} {reg}") - emit(f" spu {reg}") - free_reg(reg) - case "PUSH_NULL": - reg = alloc_reg() - emit(f" ldi 0 {reg}") - emit(f" spu {reg}") - free_reg(reg) - case "LOAD_NAME": - name = co_names[cast(int, arg)] - reg = alloc_reg() - emit(f" ldi {name} {reg}") - emit(f" spu {reg}") - free_reg(reg) - case "CALL": - arg_count = cast(int, arg) - # if arg_count > len(GENERIC_REGS_LST): - # raise Exception(f"Function calls with > {len(GENERIC_REGS_LST)} arguments not supported") - - regs_to_restore: list[tuple[Reg, Reg]] = [] - for j in range(arg_count): - i = arg_count - j - 1 - ith_reg_char = GENERIC_REGS_LST[i] - ith_reg = Reg(ith_reg_char) - if ith_reg_char in reserved_regs: - reg = alloc_reg() - emit(f" mov {ith_reg} {reg}") - regs_to_restore.append((reg, ith_reg)) - - emit(f" spo {ith_reg}") - reserved_regs[ith_reg_char] = None # manually reserve ith_reg - - fn_reg = alloc_reg() - emit(f" spo {fn_reg}") - # emit(f" ldr {fn_reg} {fn_reg}") - emit(f" csr {fn_reg}") - free_reg(fn_reg) - - for from_reg, ith_reg in regs_to_restore: - emit(f" mov {from_reg} {ith_reg}") - - for j in range(arg_count): - i = arg_count - j - 1 - ith_reg_char = GENERIC_REGS_LST[i] - ith_reg = Reg(ith_reg_char) - free_reg(ith_reg) - - case "POP_TOP": - reg = alloc_reg() - emit(f" spo {reg}") - free_reg(reg) - case _: - print(f"Skipping not implemented opcode: {opcode}") - -emit("@label keep_alive") -emit(" jpi keep_alive") - -with open(outfile_path, "w") as f: - f.write("\n".join(asm_out)) diff --git a/src/charmem.py b/src/charmem.py deleted file mode 100644 index aaac122..0000000 --- a/src/charmem.py +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env python3 -# Generate .bin file with ATK16 CPU TPU charmem data - -# Addressed by _CCC CCCC CYYY -# Data width 8bit: ___X XXXX -# where _ = unused, C = 8bit character code, Y = character y coordinate, X = pixel value - -import sys - -if len(sys.argv) != 2: - print("usage: charmem.py <outfile.bin>") - sys.exit(1) - -outfile_path = sys.argv[1] - -with open("charset.txt", "r") as f: - lines = [s.strip("\n") for s in f.readlines()] - -header_chars = lines[0] - -charset: list[list[str]] = [] - -CHAR_HEIGHT = 8 -CHAR_WIDTH = 8 - -i = 0 -while i < len(lines): - i += 1 # skip comment line - char = lines[i:i+CHAR_HEIGHT] - for row in char: - if len(row) != CHAR_WIDTH: - raise Exception(f"char row number {i} has width != {CHAR_WIDTH}: {len(row)}\n" + row + "\n" + "\n".join(char)) - charset.append(char) - i += CHAR_HEIGHT + 1 - -CHAR_N = len(charset) - -TOTAL_BYTEARRAY_SIZE = CHAR_N * CHAR_HEIGHT - -def string_to_byte(input_string: str): - # Replace spaces with '0' and '#' with '1' - binary_string = input_string.replace(' ', '0').replace('█', '1') - - assert(len(input_string) == CHAR_WIDTH) - - # Check if the string contains only '0' or '1' - if not all(c in '01' for c in binary_string): - raise ValueError("Input string must contain only spaces and '█' characters.") - - # Convert the binary string to an unsigned integer byte - return int(binary_string[::-1], 2) - - -res_b = bytearray(TOTAL_BYTEARRAY_SIZE) -for cn, char in enumerate(charset): - for cy in range(0, CHAR_HEIGHT): - addr = (cn << 3) + cy - byte = string_to_byte(char[cy]) - print(f"{byte:>08b}") - res_b[addr] = byte - -with open(outfile_path, "wb") as f: - f.write(res_b) diff --git a/src/convert_ttf.py b/src/convert_ttf.py deleted file mode 100644 index c1135cb..0000000 --- a/src/convert_ttf.py +++ /dev/null @@ -1,52 +0,0 @@ -from PIL import Image, ImageFont, ImageDraw - -import sys - -if len(sys.argv) != 3: - print("usage: convert_ttf.py <infile.ttf> <outfile.txt>") - sys.exit(1) - -# Define the font file and size -font_file = sys.argv[1] -out_file = sys.argv[2] -font_size = 8 # 8x8 pixels - -# Create a font object -font = ImageFont.truetype(font_file, font_size) - - -# backspace 0x8, tab 0x9, newline 0xA, space 0x20 - -# Define the characters to render -#characters = ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ' - -def to_char_code(i: int) -> str: - if i == 10: - return " " - return chr(i) - -characters = [to_char_code(i) for i in range(0, 256)] - - -# Open a file to write the bitmaps to -with open(out_file, 'w') as bitmap_file: - for char_idx, char in enumerate(characters): - # Create a new image with a white background - image = Image.new('1', (8, 8), 1) - - # Create a drawing context - draw = ImageDraw.Draw(image) - - # Draw the character - draw.text((0, 0), char, font=font, fill=0) - - # Convert the image to a list of 0's and 1's - pixels = image.getdata() - bitmap = [1 if pixel == 0 else 0 for pixel in pixels] - - # Write the bitmap to the file - bitmap_file.write(f'Character {char_idx}: {char}\n') - for i in range(0, 64, 8): - row = bitmap[i:i+8] - bitmap_file.write(''.join(str(bit) for bit in row) + '\n') - bitmap_file.write('\n') diff --git a/src/dig_install.py b/src/dig_install.py deleted file mode 100644 index c32229b..0000000 --- a/src/dig_install.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env python3 -# Install a generated .bin file into a memory chip in a .dig circuit file - -import sys -from typing import cast -import xml.etree.ElementTree as ET - -if len(sys.argv) != 4: - print("usage: dig_install.py <infile.bin> <circuitfile.dig> <chip_label>") - sys.exit(1) - -infile = sys.argv[1] -circuitfile = sys.argv[2] -chip_label = sys.argv[3] - -with open(infile, "rb") as f: - program_bytes = f.read() - -i = 0 -program_words: list[str] = [] -while i < len(program_bytes): - hi_byte = program_bytes[i] - lo_byte = program_bytes[i + 1] - word = f"{hi_byte:>02x}{lo_byte:>02x}" - #print(word) - program_words.append(word) - i += 2 - -program_data = ",".join(program_words) -print("program_data:", program_data) - -tree = ET.parse(circuitfile) -root = tree.getroot() - -# Iterate through each 'visualElement' element -def find_data_element(root: ET.Element) -> ET.Element: - is_correct_visual_element = False - for visual_element in root.findall('.//visualElement'): - element_attributes = visual_element.find('elementAttributes') - if element_attributes is None: continue - - entries = list(element_attributes) - for entry in entries: - strings = entry.findall("string") - is_label_entry = len(strings) == 2 and strings[0].text == "Label" and strings[1].text == chip_label - if is_label_entry and not is_correct_visual_element: - is_correct_visual_element = True - continue - - is_data_entry = len(strings) == 1 and strings[0].text == "Data" - if is_data_entry and is_correct_visual_element: - data_element = entry.findall("data")[0] - return data_element - - raise Exception(f"No data entry matching label {chip_label} found in tree") - -data_element = find_data_element(root) -data_element.text = program_data - -tree.write(circuitfile)
\ No newline at end of file diff --git a/src/optimizer.py b/src/optimizer.py deleted file mode 100644 index 0ee2837..0000000 --- a/src/optimizer.py +++ /dev/null @@ -1,189 +0,0 @@ -from tokenizer import tokenize - -def format_asm_row(asm: str) -> str: - if not (asm.startswith("@") or asm.startswith(";")) and not asm.startswith(" ") and len(asm) > 0: - return " " + asm - else: - return asm - -class Optimizer: - def __init__(self): - pass - - def optimize(self, asm_str: str): - asm = asm_str.split("\n") - asm = [row.strip() for row in asm] - asm = [self.strip_comment(row) for row in asm] - asm = [row for row in asm if not len(row) == 0] - asm = [tokenize(row, retain_curlies=True) for row in asm] - asm = self.compact_spu_spo_pattern(asm) - - # TODO: not safe when setting loading SP and FP - # ldr RA SP - # mov RA FP - # gets optimized to - # ldr RA FP - #asm = self.compact_target_mov_pattern(asm) - #asm = self.compact_target_mov_pattern(asm) - - # TODO: not safe at all. E.g. breaks a while True: pass loop - #asm = self.compact_mov_source_pattern(asm) - #asm = self.compact_mov_source_pattern(asm) - - asm = self.compact_spu_load_spo_pattern(asm) - # asm = self.convert_alr_to_ali(asm) - result = "\n".join([format_asm_row(" ".join(row)) for row in asm]) - - return result - - def strip_comment(self, row: str): - ret: str = "" - for c in row: - if c == ";": break - ret += c - - return ret - - def compact_spu_spo_pattern(self, asm: list[list[str]]) -> list[list[str]]: - i = 0 - result: list[list[str]] = [] - while i < len(asm): - current = asm[i] - next = asm[i + 1] if i + 1 < len(asm) else None - i += 1 - if current[0].startswith("@") or next is None: - result.append(current) - continue - - if current[0] == "spu" and next[0] == "spo": - print("=== compact_spu_spo_pattern") - print(current) - print(next) - - arg_current = current[1] - arg_next = next[1] - - print("=== arg_current:", arg_current) - print("=== arg_next:", arg_next) - - if arg_current == arg_next: - print("=== pass") - pass # remove both spu and spo - else: - print("=== mov") - mov = ["mov", arg_current, arg_next] - result.append(mov) - print("=== mov:", mov) - - print("=== last of result:", result[len(result) - 1]) - print() - i += 1 - continue - - result.append(current) - - return result - - def compact_target_mov_pattern(self, asm: list[list[str]]) -> list[list[str]]: - ops_with_target = ["ldi", "ldr", "add", "sub", "addi", "subi", "and", "or", "xor", "sll", "slr", "sar", "slli", "slri", "sari", "inc", "dec", "mov", "ali", "alr", "lpc"] - i = 0 - result: list[list[str]] = [] - while i < len(asm): - current = asm[i] - next = asm[i + 1] if i + 1 < len(asm) else None - i += 1 - if current[0].startswith("@") or next is None: - result.append(current) - continue - - if current[0] in ops_with_target and next[0] == "mov": - - op_op, op_operands, op_target_reg = current[0], current[1:len(current) - 1], current[len(current) - 1] - mov_from_reg, mov_to_reg = next[1], next[2] - - if op_target_reg == mov_from_reg: - ret = [op_op, *op_operands, mov_to_reg] - result.append(ret) - i += 1 - continue - - result.append(current) - - return result - - def compact_mov_source_pattern(self, asm: list[list[str]]) -> list[list[str]]: - ops_with_source = ["str", "ldr", "add", "sub", "addi", "subi", "and", "or", "xor", "sll", "slr", "sar", "slli", "slri", "sari", "mov", "ali", "alr"] - i = 0 - result: list[list[str]] = [] - while i < len(asm): - current = asm[i] - next = asm[i + 1] if i + 1 < len(asm) else None - i += 1 - if current[0].startswith("@") or next is None: - result.append(current) - continue - - if current[0] == "mov" and next[0] in ops_with_source: - mov_from_reg, mov_to_reg = current[1], current[2] - op_op, op_source_reg, op_operands = next[0], next[1], next[2:] - - if op_source_reg == mov_to_reg: - ret = [op_op, op_source_reg, *op_operands] - result.append(ret) - i += 1 - continue - - result.append(current) - - return result - - def compact_spu_load_spo_pattern(self, asm: list[list[str]]) -> list[list[str]]: - # spu RA - # ldi int_7 RA - # ldr RA RB - # spo RA - # OR - # spu RA - # ldi 3 RB - # spo RA - - i = 0 - result: list[list[str]] = [] - while i < len(asm): - instr0 = asm[i] - instr1 = asm[i + 1] if i + 1 < len(asm) else None - instr2 = asm[i + 2] if i + 2 < len(asm) else None - instr3 = asm[i + 3] if i + 3 < len(asm) else None - i += 1 - if instr0[0].startswith("@") or instr1 is None or instr2 is None or instr3 is None: - result.append(instr0) - continue - - if instr0[0] == "spu" and instr1[0] == "ldi" and instr2[0] == "ldr" and instr3[0] == "spo": - spu_op, spu_reg = instr0 - ldi_op, ldi_imm, ldi_target_reg = instr1 - ldr_op, ldr_from_reg, ldr_to_reg = instr2 - spo_op, spo_reg = instr3 - - if spu_reg == spo_reg and ldi_target_reg == ldr_from_reg and ldr_from_reg != ldr_to_reg: - ret0 = f"ldi {ldi_imm} {ldr_to_reg}".split() - ret1 = f"ldr {ldr_to_reg} {ldr_to_reg}".split() - result.append(ret0) - result.append(ret1) - i += 3 - continue - - elif instr0[0] == "spu" and instr1[0] == "ldi" and instr2[0] == "spo": - spu_op, spu_reg = instr0 - ldi_op, ldi_imm, ldi_target_reg = instr1 - spo_op, spo_reg = instr2 - - if spu_reg == spo_reg and ldi_target_reg != spu_reg: - ret0 = f"ldi {ldi_imm} {ldi_target_reg}".split() - result.append(ret0) - i += 2 - continue - - result.append(instr0) - - return result diff --git a/src/tokenizer.py b/src/tokenizer.py deleted file mode 100644 index cf31b56..0000000 --- a/src/tokenizer.py +++ /dev/null @@ -1,47 +0,0 @@ -def tokenize(line: str, retain_curlies = False) -> list[str]: - cur: str = "" - result: list[str] = [] - is_py_expr = False - is_string = False - idx = 0 - while idx < len(line): - c = line[idx] - if not is_py_expr and c == "$": - is_py_expr = True - if retain_curlies: - cur += "${" - idx += 1 - elif is_py_expr and c == "$": - raise Exception("Unexpected start of python expr while already parsing a python expression:\n" + line) - elif not is_py_expr and c == "}": - raise Exception("Unexpected end of python expr while not parsing a python expression:\n" + line) - elif is_py_expr and c == "}": - is_py_expr = False - if retain_curlies: - cur += c - result.append(cur) - cur = "" - elif is_py_expr: - cur += c - elif not is_string and c == "\"": - cur += "\"" - is_string = True - elif is_string and c == "\"": - cur += "\"" - is_string = False - result.append(cur) - cur = "" - elif is_string: - cur += c - elif c == " " or c == "\t": - result.append(cur) - cur = "" - else: - cur += c - - idx += 1 - - if len(cur) > 0: - result.append(cur) - - return [r for r in result if r != ""]
\ No newline at end of file diff --git a/src/ucode.py b/src/ucode.py deleted file mode 100755 index 07d0554..0000000 --- a/src/ucode.py +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/env python3 -# Generate .bin file with ATK16 CPU microcode - -# Addressed by OOOO BUUU -# where O = opcode, B = branch flag, U = microsequencer value - -import sys - -if len(sys.argv) != 2: - print("usage: ucode.py <outfile.bin>") - sys.exit(1) - -outfile_path = sys.argv[1] - -PC_CO = 1 << 0 -PC_IE = 1 << 1 -PC_OE = 1 << 2 -MAR_IE = 1 << 3 -MEM_IE = 1 << 4 -MEM_OE = 1 << 5 -RW_IE = 1 << 6 -R1_OE = 1 << 7 -R2_OE = 1 << 8 -IR_IE = 1 << 9 -IM_M = 1 << 10 -LI_OE = 1 << 11 -ALU_OE = 1 << 12 -FR_IE = 1 << 13 -HALT = 1 << 14 -US_RS = 1 << 15 -ISRA_OE = 1 << 16 -IM_DS = 1 << 17 -IM_EN = 1 << 18 -IPC_IE = 1 << 19 -IPC_OE = 1 << 20 -NOP5 = 1 << 21 -NOP6 = 1 << 22 -NOP7 = 1 << 23 - -BRANCH_FLAG_STATES_N = 2 -UCODE_N: int = 2**3 -CONTROL_WORD_SIZE = 3 - -def not_branch(bs: list[int]) -> list[list[int]]: - return BRANCH_FLAG_STATES_N * [bs] - -def branch(false_branch: list[int], true_branch: list[int]) -> list[list[int]]: - return [false_branch, true_branch] - -fetch = [PC_OE|MAR_IE, MEM_OE|IR_IE|PC_CO] - -def nop(): - return not_branch([*fetch, US_RS, 0, 0, 0, 0, 0]) - -ucode = [ - # ALR 0000 TTTL LLRR RSSS - not_branch([*fetch, ALU_OE|FR_IE|RW_IE, US_RS, 0, 0, 0, 0]), - # ALI 0001 TTTL LLII ISSS - not_branch([*fetch, IM_M|ALU_OE|FR_IE|RW_IE, US_RS, 0, 0, 0, 0]), - # LDR 0010 TTTR RRXX XXXX - not_branch([*fetch, R1_OE|MAR_IE, MEM_OE|RW_IE, US_RS, 0, 0, 0]), - # STR 0011 XXXL LLRR RXXX - not_branch([*fetch, R1_OE|MAR_IE, R2_OE|MEM_IE, US_RS, 0, 0, 0]), - # LDI 0100 TTTI IIII IIII - not_branch([*fetch, LI_OE|RW_IE, US_RS, 0, 0, 0, 0]), - # JPR 0101 XXXR RRXX XXXX - not_branch([*fetch, R1_OE|PC_IE, US_RS, 0, 0, 0, 0]), - # JPI 0110 XXXI IIII IIII - not_branch([*fetch, IM_M|LI_OE|PC_IE, US_RS, 0, 0, 0, 0]), - # BRR 0111 XFFR RRXX XXXX - branch([*fetch, US_RS, 0, 0, 0, 0, 0], - [*fetch, R1_OE|PC_IE, US_RS, 0, 0, 0, 0]), - # BRI 1000 XFFI IIII IIII - branch([*fetch, US_RS, 0, 0, 0, 0, 0], - [*fetch, IM_M|LI_OE|PC_IE, US_RS, 0, 0, 0, 0]), - # LPC 1001 TTTX XXXX XXXX - not_branch([*fetch, PC_OE|RW_IE, US_RS, 0, 0, 0, 0]), - # NOP 1010 XXXX XXXX XXXX - nop(), - # NOP 1011 XXXX XXXX XXXX - nop(), - # ISRP0 1100 XXXX XXXX XXXX - not_branch([IM_EN|PC_OE|IPC_IE, ISRA_OE|MAR_IE, MEM_OE|PC_IE, US_RS, 0, 0, 0, 0]), - # ISRP1 1101 XXXX XXXX XXXX - not_branch([IM_EN|PC_OE|IPC_IE, ISRA_OE|MAR_IE, MEM_OE|PC_IE, US_RS, 0, 0, 0, 0]), - # RTI 1110 XXXX XXXX XXXX - not_branch([*fetch, IM_DS|IPC_OE|PC_IE, US_RS, 0, 0, 0, 0]), - # HLT 1111 XXXX XXXX XXXX - not_branch([*fetch, HALT, 0, 0, 0, 0, 0]), -] - -INST_N = len(ucode) -TOTAL_BYTEARRAY_SIZE = INST_N * BRANCH_FLAG_STATES_N * UCODE_N * CONTROL_WORD_SIZE - -res_b = bytearray(TOTAL_BYTEARRAY_SIZE) - -for i in range(INST_N): - for j in range(BRANCH_FLAG_STATES_N): - for k in range(UCODE_N): - for l in range(CONTROL_WORD_SIZE): - idx = l + \ - k * CONTROL_WORD_SIZE + \ - j * CONTROL_WORD_SIZE * UCODE_N + \ - i * CONTROL_WORD_SIZE * UCODE_N * BRANCH_FLAG_STATES_N - cword = ucode[i][j][k] - assert len(ucode[i][j]) == 8 - cbyte = (cword >> (8 * (CONTROL_WORD_SIZE - l - 1))) & 0xff - print(f"inst: {i:>04b}, idx: {idx:>04x}, cbyte: {cbyte:>08b}") - res_b[idx] = cbyte - -with open(outfile_path, "wb") as f: - f.write(res_b) |
