Assembler working

This commit is contained in:
Kyler 2024-02-23 00:29:40 -07:00
parent 9460e94ed0
commit 7164a70f81
3 changed files with 391 additions and 91 deletions

View File

@ -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*

View File

@ -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()

26
language/examples/test1.s Normal file
View File

@ -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