Assembler working
This commit is contained in:
parent
9460e94ed0
commit
7164a70f81
|
@ -0,0 +1,62 @@
|
|||
# YTD 12-bit Computer
|
||||
*Yeahbut, aka Kyler Olsen*
|
||||
|
||||
It is a custom computer and instruction set architecture. It also has its own
|
||||
assembly language with assembler. Custom high level language coming soon!
|
||||
|
||||
## ISA
|
||||
*WIP*
|
||||
|
||||
## Assembly Language
|
||||
*WIP*
|
||||
|
||||
### Registers
|
||||
|
||||
- `ZR`
|
||||
- `PC`
|
||||
- `SP`
|
||||
- `MP`
|
||||
- `D0`
|
||||
- `D1`
|
||||
- `D2`
|
||||
- `D3`
|
||||
|
||||
### Zero Operand Instructions
|
||||
|
||||
- `NOP`
|
||||
- `HLT`
|
||||
- `INT`
|
||||
- `BNZ`
|
||||
- `BLK`
|
||||
- `ENB`
|
||||
|
||||
### One Operand Instructions
|
||||
|
||||
- `GLA` `Destination Register`
|
||||
- `GET` `Destination Register`
|
||||
- `LOD` `Destination Register`
|
||||
- `STR` `Source Register`
|
||||
- `PSH` `Source Register`
|
||||
- `POP` `Destination Register`
|
||||
- `LDI` `Immediate Value`
|
||||
- `LDI` :`Label`
|
||||
|
||||
### Two Operand Instructions
|
||||
|
||||
- `LSH` `Destination Register` `Source Register`
|
||||
- `RSH` `Destination Register` `Source Register`
|
||||
- `INC` `Destination Register` `Source Register`
|
||||
- `DEC` `Destination Register` `Source Register`
|
||||
|
||||
### Three Operand Instructions
|
||||
|
||||
- `AND` `Destination Register` `Source Register A` `Source Register B`
|
||||
- `OR` `Destination Register` `Source Register A` `Source Register B`
|
||||
- `NAD` `Destination Register` `Source Register A` `Source Register B`
|
||||
- `SUB` `Destination Register` `Source Register A` `Source Register B`
|
||||
- `XOR` `Destination Register` `Source Register A` `Source Register B`
|
||||
- `NOR` `Destination Register` `Source Register A` `Source Register B`
|
||||
- `ADD` `Destination Register` `Source Register A` `Source Register B`
|
||||
|
||||
## High Level Language
|
||||
*WIP*
|
|
@ -1,12 +1,98 @@
|
|||
# Kyler Olsen
|
||||
# Feb 2024
|
||||
|
||||
import argparse
|
||||
from collections import namedtuple
|
||||
from typing import TypeVar, Generic, Iterable
|
||||
|
||||
INSTRUCTIONS_COUNT = 0x700
|
||||
|
||||
class AssemblerError(Exception): pass
|
||||
class LinkerError(Exception): pass
|
||||
|
||||
KEY = TypeVar("KEY")
|
||||
ANTIKEY = TypeVar("ANTIKEY")
|
||||
|
||||
|
||||
class TwoWayDictionary(Generic[KEY, ANTIKEY]):
|
||||
|
||||
_dict: dict[KEY, ANTIKEY]
|
||||
_antidict: dict[ANTIKEY, KEY]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
keys: Iterable[KEY] | None = None,
|
||||
antikeys: Iterable[ANTIKEY] | None = None,
|
||||
):
|
||||
if (keys is None) and (antikeys is None):
|
||||
self._dict = {}
|
||||
self._antidict = {}
|
||||
elif (
|
||||
(keys is None) or
|
||||
(antikeys is None) or
|
||||
len(keys) != len(antikeys) # type: ignore
|
||||
):
|
||||
raise ValueError
|
||||
else:
|
||||
self._dict = dict(zip(keys, antikeys)) # type: ignore
|
||||
self._antidict = dict(zip(antikeys, keys)) # type: ignore
|
||||
|
||||
def get_forwards(self, key: KEY, default: None | ANTIKEY = None) -> ANTIKEY:
|
||||
if key not in self._dict:
|
||||
if default is None: raise KeyError("Key not in dictionary")
|
||||
else: return default
|
||||
else: return self._dict[key]
|
||||
|
||||
def get_backwards(
|
||||
self,
|
||||
antikey: ANTIKEY,
|
||||
default: None | KEY = None,
|
||||
) -> KEY:
|
||||
if antikey not in self._antidict:
|
||||
if default is None: raise KeyError("Anti-Key not in dictionary")
|
||||
else: return default
|
||||
else: return self._antidict[antikey]
|
||||
|
||||
def set_forwards(self, key: KEY, antikey: ANTIKEY):
|
||||
if antikey in self._antidict:
|
||||
raise KeyError("Anti-Key already in dictionary.")
|
||||
if key in self._dict:
|
||||
del self._antidict[self._dict[key]]
|
||||
self._dict[key] = antikey
|
||||
self._antidict[antikey] = key
|
||||
|
||||
def set_backwards(self, antikey: ANTIKEY, key: KEY):
|
||||
if key in self._dict:
|
||||
raise KeyError("Key already in dictionary.")
|
||||
if antikey in self._antidict:
|
||||
del self._dict[self._antidict[antikey]]
|
||||
self._antidict[antikey] = key
|
||||
self._dict[key] = antikey
|
||||
|
||||
def del_forwards(self, key: KEY):
|
||||
if key not in self._dict:
|
||||
raise KeyError("Key not in dictionary.")
|
||||
del self._antidict[self._dict[key]]
|
||||
del self._dict[key]
|
||||
|
||||
def del_backwards(self, antikey: ANTIKEY):
|
||||
if antikey not in self._antidict:
|
||||
raise KeyError("Anti-Key not in dictionary.")
|
||||
del self._dict[self._antidict[antikey]]
|
||||
del self._antidict[antikey]
|
||||
|
||||
def keys(self) -> Iterable[KEY]:
|
||||
return self._dict.keys()
|
||||
|
||||
def antikeys(self) -> Iterable[ANTIKEY]:
|
||||
return self._antidict.keys()
|
||||
|
||||
def items(self) -> Iterable[tuple[KEY, ANTIKEY]]:
|
||||
return self._dict.items()
|
||||
|
||||
def antiitems(self) -> Iterable[tuple[ANTIKEY, KEY]]:
|
||||
return self._antidict.items()
|
||||
|
||||
|
||||
class Instruction(namedtuple('Instruction', ['bb', 'bl', 'lb', 'll'])):
|
||||
|
||||
|
@ -34,49 +120,193 @@ class Instruction(namedtuple('Instruction', ['bb', 'bl', 'lb', 'll'])):
|
|||
)
|
||||
|
||||
|
||||
class Label:
|
||||
class Directive:
|
||||
|
||||
value: str
|
||||
@staticmethod
|
||||
def directive(line: str, line_number: int) -> "Directive":
|
||||
value = int(line[1:], base=0)
|
||||
return MemoryLocation(value)
|
||||
|
||||
|
||||
class MemoryLocation(Directive):
|
||||
|
||||
_value: int
|
||||
|
||||
def __init__(self, value: int):
|
||||
self._value = value
|
||||
|
||||
@property
|
||||
def value(self) -> int:
|
||||
return self._value
|
||||
|
||||
|
||||
class Label(Directive):
|
||||
|
||||
_value: str
|
||||
|
||||
def __init__(self, value: str):
|
||||
self.value = value
|
||||
self._value = value
|
||||
|
||||
@property
|
||||
def value(self) -> str:
|
||||
return self._value
|
||||
|
||||
|
||||
class Immediate(Label): pass
|
||||
class Immediate(Directive):
|
||||
|
||||
_value: str
|
||||
|
||||
def __init__(self, value: str):
|
||||
self._value = value
|
||||
|
||||
@property
|
||||
def value(self) -> str:
|
||||
return self._value
|
||||
|
||||
|
||||
class Program:
|
||||
|
||||
_instructions: list[Instruction | Label]
|
||||
_labels: list[tuple[int, Label]]
|
||||
instruction_set = {
|
||||
"NOP": lambda *_: Instruction(0, 0, 0, 0),
|
||||
"HLT": lambda *_: Instruction(0, 0, 0, 1),
|
||||
"INT": lambda *_: Instruction(0, 0, 0, 2),
|
||||
"BNZ": lambda *_: Instruction(0, 0, 0, 3),
|
||||
"BLK": lambda *_: Instruction(0, 0, 0, 4),
|
||||
"ENB": lambda *_: Instruction(0, 0, 0, 5),
|
||||
|
||||
"GLA": lambda l, i: Instruction(0, 0, 2, reg1(l, i)),
|
||||
"GET": lambda l, i: Instruction(0, 0, 3, reg1(l, i)),
|
||||
"LOD": lambda l, i: Instruction(0, 0, 4, reg1(l, i)),
|
||||
"STR": lambda l, i: Instruction(0, 0, 5, reg1(l, i)),
|
||||
"PSH": lambda l, i: Instruction(0, 0, 6, reg1(l, i)),
|
||||
"POP": lambda l, i: Instruction(0, 0, 7, reg1(l, i)),
|
||||
|
||||
"LDI": lambda l, i: immediate(l, i),
|
||||
|
||||
"LSH": lambda l, i: Instruction(0, 4, *reg2(l, i)),
|
||||
"RSH": lambda l, i: Instruction(0, 5, *reg2(l, i)),
|
||||
"INC": lambda l, i: Instruction(0, 6, *reg2(l, i)),
|
||||
"DEC": lambda l, i: Instruction(0, 7, *reg2(l, i)),
|
||||
|
||||
"AND": lambda l, i: Instruction(1, *reg3(l, i)),
|
||||
"OR": lambda l, i: Instruction(2, *reg3(l, i)),
|
||||
"NAD": lambda l, i: Instruction(3, *reg3(l, i)),
|
||||
"SUB": lambda l, i: Instruction(4, *reg3(l, i)),
|
||||
"XOR": lambda l, i: Instruction(5, *reg3(l, i)),
|
||||
"NOR": lambda l, i: Instruction(6, *reg3(l, i)),
|
||||
"ADD": lambda l, i: Instruction(7, *reg3(l, i)),
|
||||
}
|
||||
|
||||
_program: str
|
||||
_instructions: list[tuple[int, Instruction | Directive]]
|
||||
_immediate: list[tuple[int, Immediate]]
|
||||
|
||||
def __init__(self, instructions: list[Instruction | Label]):
|
||||
self._instructions = instructions
|
||||
self._labels = []
|
||||
_instruction_map: TwoWayDictionary[int, int]
|
||||
_label_map: dict[str, int]
|
||||
|
||||
def __init__(self, program: str):
|
||||
self._program = program
|
||||
|
||||
self._instructions = self.parse(self._program)
|
||||
self._immediate = []
|
||||
for index, item in enumerate(self._instructions):
|
||||
if isinstance(item, Label) and not isinstance(item, Immediate):
|
||||
self._labels.append((index, item))
|
||||
elif isinstance(item, Immediate):
|
||||
self._immediate.append((index, item))
|
||||
self._label_map = {}
|
||||
self._instruction_map = TwoWayDictionary()
|
||||
|
||||
self._link()
|
||||
|
||||
def __bytes__(self) -> bytes:
|
||||
if self._labels or self._immediate:
|
||||
raise LinkerError("Program Not Linked Properly.")
|
||||
for item in self._instructions:
|
||||
if not isinstance(item, Instruction):
|
||||
raise LinkerError("Program Not Linked Properly.")
|
||||
|
||||
output = bytearray()
|
||||
for i in self._instructions:
|
||||
output += bytes(i) # type: ignore
|
||||
return output
|
||||
for i in range(INSTRUCTIONS_COUNT):
|
||||
instruction = self._get_instruction(i)
|
||||
output += bytes(instruction)
|
||||
return bytes(output)
|
||||
|
||||
def link(self):
|
||||
pass
|
||||
def labels(self) -> dict[str, int]:
|
||||
return self._label_map.copy()
|
||||
|
||||
def reg(reg: str) -> int:
|
||||
def _get_instruction(self, index: int) -> Instruction:
|
||||
new_index = self._instruction_map.get_forwards(index, -1)
|
||||
if new_index == -1: value = self.instruction_set["NOP"]("NOP", -1)
|
||||
else: _, value = self._instructions[new_index]
|
||||
if isinstance(value, Immediate):
|
||||
return self.instruction_set["LDI"](
|
||||
f"LDI {self._label_map[value.value]}", -1)
|
||||
elif isinstance(value, Directive):
|
||||
raise LinkerError("Unexpected directive!")
|
||||
else:
|
||||
return value
|
||||
|
||||
def _get_instruction_line(self, index: int) -> int:
|
||||
return self._instruction_map.get_backwards(index)
|
||||
|
||||
def _link(self):
|
||||
instruction_line = 0
|
||||
for index, (line_number, item) in enumerate(self._instructions):
|
||||
if isinstance(item, MemoryLocation):
|
||||
instruction_line = item.value
|
||||
elif isinstance(item, Label):
|
||||
if item.value in self._label_map:
|
||||
raise LinkerError(
|
||||
"Label already declared on line"
|
||||
f" {line_number}: {item.value}"
|
||||
)
|
||||
self._label_map[item.value] = instruction_line
|
||||
elif isinstance(item, Immediate):
|
||||
self._immediate.append((index, item))
|
||||
self._instruction_map.set_forwards(instruction_line, index)
|
||||
instruction_line += 1
|
||||
elif isinstance(item, Directive):
|
||||
raise LinkerError(
|
||||
f"Unknown or Invalid Directive! on line {line_number}.")
|
||||
else:
|
||||
self._instruction_map.set_forwards(instruction_line, index)
|
||||
instruction_line += 1
|
||||
|
||||
@classmethod
|
||||
def parse(cls, s: str) -> list[tuple[int, Instruction | Directive]]:
|
||||
instructions: list[tuple[int, Instruction | Directive]] = []
|
||||
|
||||
last_error = None
|
||||
|
||||
for raw_line_number, raw_line in enumerate(s.splitlines(False)):
|
||||
try:
|
||||
line_number = raw_line_number + 1
|
||||
line = raw_line.strip().upper()
|
||||
if len(line) == 0 or line[0] == ";":
|
||||
pass
|
||||
elif line[:3] in cls.instruction_set:
|
||||
instructions.append((
|
||||
line_number,
|
||||
cls.instruction_set[line[:3]](line, line_number),
|
||||
))
|
||||
elif line[:2] in cls.instruction_set:
|
||||
instructions.append((
|
||||
line_number,
|
||||
cls.instruction_set[line[:2]](line, line_number),
|
||||
))
|
||||
elif line[-1] == ":":
|
||||
instructions.append((
|
||||
line_number,
|
||||
Label(line[:-1]),
|
||||
))
|
||||
elif line[0] == ".":
|
||||
instructions.append((
|
||||
line_number,
|
||||
Directive.directive(line, line_number),
|
||||
))
|
||||
elif line:
|
||||
raise AssemblerError(
|
||||
f"Invalid Instruction on line {line_number}: '{line}'")
|
||||
except (AssemblerError, LinkerError) as e:
|
||||
last_error = e
|
||||
print(f"Error:\n\t{e}")
|
||||
if last_error is not None:
|
||||
raise last_error
|
||||
|
||||
return instructions
|
||||
|
||||
|
||||
def reg(reg: str, line_number: int) -> int:
|
||||
register_names = [
|
||||
"ZR",
|
||||
"PC",
|
||||
|
@ -102,42 +332,44 @@ def reg(reg: str) -> int:
|
|||
elif reg in register_numbers:
|
||||
return register_numbers.index(reg)
|
||||
else:
|
||||
raise AssemblerError(f"Invalid Register: {reg}")
|
||||
raise AssemblerError(
|
||||
f"Invalid Register on line {line_number}: {reg}")
|
||||
|
||||
def reg1(l: str) -> int:
|
||||
args = l.split(' ')
|
||||
def reg1(line: str, line_number: int) -> int:
|
||||
args = line.split(' ')
|
||||
if len(args) == 2:
|
||||
return reg(args[1])
|
||||
return reg(args[1], line_number)
|
||||
else:
|
||||
raise AssemblerError(f"Invalid number of arguments: {args[0]}")
|
||||
raise AssemblerError(
|
||||
f"Invalid number of arguments on line {line_number}: {args[0]}")
|
||||
|
||||
def reg2(l: str) -> tuple[int, int]:
|
||||
args = l.split(' ')
|
||||
def reg2(line: str, line_number: int) -> tuple[int, int]:
|
||||
args = line.split(' ')
|
||||
if len(args) == 3:
|
||||
return reg(args[2]), reg(args[1])
|
||||
return reg(args[2], line_number), reg(args[1], line_number)
|
||||
else:
|
||||
raise AssemblerError(f"Invalid number of arguments: {args[0]}")
|
||||
raise AssemblerError(
|
||||
f"Invalid number of arguments on line {line_number}: {args[0]}")
|
||||
|
||||
def reg3(l: str) -> tuple[int, int, int]:
|
||||
args = l.split(' ')
|
||||
def reg3(line: str, line_number: int) -> tuple[int, int, int]:
|
||||
args = line.split(' ')
|
||||
if len(args) == 4:
|
||||
return reg(args[2]), reg(args[3]), reg(args[1])
|
||||
return (
|
||||
reg(args[2], line_number),
|
||||
reg(args[3], line_number),
|
||||
reg(args[1], line_number),
|
||||
)
|
||||
else:
|
||||
raise AssemblerError(f"Invalid number of arguments: {args[0]}")
|
||||
raise AssemblerError(
|
||||
f"Invalid number of arguments on line {line_number}: {args[0]}")
|
||||
|
||||
def immediate(l: str) -> Instruction | Immediate:
|
||||
args = l.split(' ')
|
||||
def immediate(line: str, line_number: int) -> Instruction | Immediate:
|
||||
args = line.split(' ')
|
||||
if len(args) == 2:
|
||||
if ':' in args[1]:
|
||||
return Immediate(args[1][1:])
|
||||
elif 'b' in args[1]:
|
||||
value = int(args[1].split('b')[1], base=2)
|
||||
elif 'o' in args[1]:
|
||||
value = int(args[1].split('o')[1], base=8)
|
||||
elif 'x' in args[1]:
|
||||
value = int(args[1].split('x')[1], base=16)
|
||||
else:
|
||||
value = int(args[1])
|
||||
value = int(args[1], base=0)
|
||||
|
||||
return Instruction(
|
||||
0,
|
||||
|
@ -146,52 +378,32 @@ def immediate(l: str) -> Instruction | Immediate:
|
|||
value & 0x007,
|
||||
)
|
||||
else:
|
||||
raise AssemblerError(f"Invalid number of arguments: {args[0]}")
|
||||
raise AssemblerError(
|
||||
f"Invalid number of arguments on line {line_number}: {args[0]}")
|
||||
|
||||
def parse(s: str) -> list[Instruction | Label]:
|
||||
instruction_set = {
|
||||
"NOP": lambda _: Instruction(0,0,0,0),
|
||||
"HLT": lambda _: Instruction(0,0,0,1),
|
||||
"INT": lambda _: Instruction(0,0,0,2),
|
||||
"BNZ": lambda _: Instruction(0,0,0,3),
|
||||
"BLK": lambda _: Instruction(0,0,0,4),
|
||||
"ENB": lambda _: Instruction(0,0,0,5),
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
prog='ytd12LA',
|
||||
description='ytd 12-bit Linker and Assembler',
|
||||
# epilog='Text at the bottom of help',
|
||||
)
|
||||
parser.add_argument('input_file')
|
||||
parser.add_argument('-o', '--output_file')
|
||||
parser.add_argument('-l', '--labels_file')
|
||||
|
||||
"GLA": lambda l: Instruction(0,0,2,reg1(l)),
|
||||
"GET": lambda l: Instruction(0,0,3,reg1(l)),
|
||||
"LOD": lambda l: Instruction(0,0,4,reg1(l)),
|
||||
"STR": lambda l: Instruction(0,0,5,reg1(l)),
|
||||
"PSH": lambda l: Instruction(0,0,6,reg1(l)),
|
||||
"POP": lambda l: Instruction(0,0,7,reg1(l)),
|
||||
args = parser.parse_args()
|
||||
|
||||
"LDI": lambda l: immediate(l),
|
||||
with open(args.input_file) as ifile:
|
||||
program = Program(ifile.read())
|
||||
|
||||
"LSH": lambda l: Instruction(0,4,*reg2(l)),
|
||||
"RSH": lambda l: Instruction(0,5,*reg2(l)),
|
||||
"INC": lambda l: Instruction(0,6,*reg2(l)),
|
||||
"DEC": lambda l: Instruction(0,7,*reg2(l)),
|
||||
if args.output_file:
|
||||
with open(args.output_file, 'wb') as ofile:
|
||||
ofile.write(bytes(program))
|
||||
|
||||
"AND": lambda l: Instruction(1,*reg3(l)),
|
||||
"OR": lambda l: Instruction(2,*reg3(l)),
|
||||
"NAD": lambda l: Instruction(3,*reg3(l)),
|
||||
"SUB": lambda l: Instruction(4,*reg3(l)),
|
||||
"XOR": lambda l: Instruction(5,*reg3(l)),
|
||||
"NOR": lambda l: Instruction(6,*reg3(l)),
|
||||
"ADD": lambda l: Instruction(7,*reg3(l)),
|
||||
}
|
||||
instructions = []
|
||||
if args.labels_file:
|
||||
with open(args.labels_file, 'w') as lfile:
|
||||
for label, location in program.labels().items():
|
||||
lfile.write(f"{hex(location)}, {label}\n")
|
||||
|
||||
for rl in s.splitlines(False):
|
||||
l = rl.strip()
|
||||
if l[0] == ";":
|
||||
pass
|
||||
elif l[:3] in instruction_set:
|
||||
instructions.append(instruction_set[l[:3]](l))
|
||||
elif l[:2] in instruction_set:
|
||||
instructions.append(instruction_set[l[:2]](l))
|
||||
elif l[-1] == ":":
|
||||
instructions.append(Label(l[:-1]))
|
||||
elif l.strip():
|
||||
raise AssemblerError(f"Invalid Instruction: '{l.strip()}'")
|
||||
|
||||
return instructions
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
; Yeahbut - Feb 2024
|
||||
; Example 1 - ytd 12-bit Computer
|
||||
; Fibonacci
|
||||
|
||||
.0x5
|
||||
main:
|
||||
; Initialize values
|
||||
ldi 1
|
||||
or D0 MP ZR
|
||||
or D1 ZR ZR
|
||||
or D2 ZR ZR
|
||||
|
||||
loop:
|
||||
; Output current value
|
||||
ldi 0x7FE
|
||||
str D0
|
||||
|
||||
; Move values down
|
||||
or D2 D1 ZR
|
||||
or D1 D0 ZR
|
||||
|
||||
; Add last two values to get the next value
|
||||
add D0 D1 D2
|
||||
|
||||
ldi :loop
|
||||
or PC MP ZR
|
Loading…
Reference in New Issue