aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/asm_eval.py47
-rw-r--r--src/asm_ops.py154
-rw-r--r--src/asm_pass0.py50
-rw-r--r--src/asm_pass1.py48
-rw-r--r--src/asm_pass2.py40
-rw-r--r--src/asm_pass3.py78
-rw-r--r--src/asm_pass4.py61
-rwxr-xr-xsrc/assembler.py82
-rw-r--r--src/ast_compiler.py716
-rw-r--r--src/bytecode_compiler.py220
-rw-r--r--src/charmem.py63
-rw-r--r--src/convert_ttf.py52
-rw-r--r--src/dig_install.py60
-rw-r--r--src/optimizer.py189
-rw-r--r--src/tokenizer.py47
-rwxr-xr-xsrc/ucode.py112
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)