diff --git a/.gitignore b/.gitignore index d902cf9..f0aebc1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.ods# docs/*.pdf +__pycache__ diff --git a/emulator/emulator.py b/emulator/emulator.py index d32a47a..0bb841f 100644 --- a/emulator/emulator.py +++ b/emulator/emulator.py @@ -3,7 +3,11 @@ from typing import BinaryIO +ROM_SIZE = 0x700 MAX_INT = 0x1000 +MAX_IMMEDIATE = 0x80 + +class ConfigurationError(Exception): pass class Device: @@ -37,10 +41,14 @@ class Memory: rom: list[int], devices: list[Device] | None = None, ) -> None: - self._rom = [0] * 0x700 + self._rom = [0] * ROM_SIZE self._devices = (devices or list())[:] self._ram = [0] * 0x1000 + if len(rom) > ROM_SIZE: + raise ConfigurationError( + f"ROM too long: {hex(len(rom))} > {hex(ROM_SIZE)}") + for i, data in enumerate(rom): self._rom[i] = data % MAX_INT @@ -84,13 +92,29 @@ class Memory: with open(file, 'b') as f: while f: incoming = f.read(3) - rom.append(incoming[0] << 4 | ((incoming[1] & 0xf0) >> 4)) - rom.append(((incoming[1] & 0xf) << 8) | incoming[2]) + if len(incoming) == 3: + rom.append(incoming[0] << 4 | ((incoming[1] & 0xf0) >> 4)) + rom.append(((incoming[1] & 0xf) << 8) | incoming[2]) + elif len(incoming) == 2: + rom.append(incoming[0] << 4 | ((incoming[1] & 0xf0) >> 4)) + rom.append(((incoming[1] & 0xf) << 8)) + elif len(incoming) == 1: + rom.append(incoming[0] << 4) + else: + break else: while file: incoming = file.read(3) - rom.append(incoming[0] << 4 | ((incoming[1] & 0xf0) >> 4)) - rom.append(((incoming[1] & 0xf) << 8) | incoming[2]) + if len(incoming) == 3: + rom.append(incoming[0] << 4 | ((incoming[1] & 0xf0) >> 4)) + rom.append(((incoming[1] & 0xf) << 8) | incoming[2]) + elif len(incoming) == 2: + rom.append(incoming[0] << 4 | ((incoming[1] & 0xf0) >> 4)) + rom.append(((incoming[1] & 0xf) << 8)) + elif len(incoming) == 1: + rom.append(incoming[0] << 4) + else: + break return rom @@ -223,8 +247,14 @@ class Computer: def interrupt(self, index: int): self._interrupt_flag.append(index % MAX_INT) - def step(self): + def step(self, verbose: bool = False): instruction = self._mem[self.program_counter] + if verbose: + print( + f"; {hex(self.program_counter)} : {oct(instruction)} " + f"({hex(instruction)})" + ) + self.verbose_step() if instruction == 0: self.NOP() elif instruction == 1: self.HLT() @@ -240,28 +270,125 @@ class Computer: elif instruction & 0xFF8 == 0x38: self.POP(instruction & 0x7) elif instruction & 0xF80 == 0x80: self.LDI(instruction & 0x7F) elif instruction & 0xFC0 == 0x100: - self.LSH(instruction & 0x38, instruction & 0x7) + self.LSH((instruction & 0x38) >> 3, instruction & 0x7) elif instruction & 0xFC0 == 0x140: - self.RSH(instruction & 0x38, instruction & 0x7) + self.RSH((instruction & 0x38) >> 3, instruction & 0x7) elif instruction & 0xFC0 == 0x180: - self.INC(instruction & 0x38, instruction & 0x7) + self.INC((instruction & 0x38) >> 3, instruction & 0x7) elif instruction & 0xFC0 == 0x1C0: - self.DEC(instruction & 0x38, instruction & 0x7) - elif instruction & 0xE0 == 0x200: - self.AND(instruction & 0x1C, instruction & 0x38, instruction & 0x7) - elif instruction & 0xE0 == 0x400: - self.OR(instruction & 0x1C, instruction & 0x38, instruction & 0x7) - elif instruction & 0xE0 == 0x600: - self.NAD(instruction & 0x1C, instruction & 0x38, instruction & 0x7) - elif instruction & 0xE0 == 0x800: - self.SUB(instruction & 0x1C, instruction & 0x38, instruction & 0x7) - elif instruction & 0xE0 == 0xA00: - self.XOR(instruction & 0x1C, instruction & 0x38, instruction & 0x7) - elif instruction & 0xE0 == 0xC00: - self.NOR(instruction & 0x1C, instruction & 0x38, instruction & 0x7) - elif instruction & 0xE0 == 0xE00: - self.ADD(instruction & 0x1C, instruction & 0x38, instruction & 0x7) - else: raise LookupError() + self.DEC((instruction & 0x38) >> 3, instruction & 0x7) + elif instruction & 0xE00 == 0x200: + self.AND( + instruction & 0x7, + (instruction & 0x38) >> 3, + (instruction & 0x1C0) >> 6, + ) + elif instruction & 0xE00 == 0x400: + self.OR( + instruction & 0x7, + (instruction & 0x38) >> 3, + (instruction & 0x1C0) >> 6, + ) + elif instruction & 0xE00 == 0x600: + self.NAD( + instruction & 0x7, + (instruction & 0x38) >> 3, + (instruction & 0x1C0) >> 6, + ) + elif instruction & 0xE00 == 0x800: + self.SUB( + instruction & 0x7, + (instruction & 0x38) >> 3, + (instruction & 0x1C0) >> 6, + ) + elif instruction & 0xE00 == 0xA00: + self.XOR( + instruction & 0x7, + (instruction & 0x38) >> 3, + (instruction & 0x1C0) >> 6, + ) + elif instruction & 0xE00 == 0xC00: + self.NOR( + instruction & 0x7, + (instruction & 0x38) >> 3, + (instruction & 0x1C0) >> 6, + ) + elif instruction & 0xE00 == 0xE00: + self.ADD( + instruction & 0x7, + (instruction & 0x38) >> 3, + (instruction & 0x1C0) >> 6, + ) + else: + raise LookupError( + f"Cannot find instruction " + f"{hex(self.program_counter)}: {oct(instruction)}" + ) + + def verbose_step(self): + instruction = self._mem[self.program_counter] + + if instruction == 0: print("NOP") + elif instruction == 1: print("HLT") + elif instruction == 2: print("INT") + elif instruction == 3: print("BNZ") + elif instruction == 4: print("BLK") + elif instruction == 5: print("ENB") + elif instruction & 0xFF8 == 0x10: print(f"GLA {instruction & 0x7}") + elif instruction & 0xFF8 == 0x18: print(f"GET {instruction & 0x7}") + elif instruction & 0xFF8 == 0x20: print(f"LOD {instruction & 0x7}") + elif instruction & 0xFF8 == 0x28: print(f"STR {instruction & 0x7}") + elif instruction & 0xFF8 == 0x30: print(f"PSH {instruction & 0x7}") + elif instruction & 0xFF8 == 0x38: print(f"POP {instruction & 0x7}") + elif instruction & 0xF80 == 0x80: print(f"LDI {instruction & 0x7F}") + elif instruction & 0xFC0 == 0x100: + print(f"LSH {instruction & 0x7} {(instruction & 0x38) >> 3}") + elif instruction & 0xFC0 == 0x140: + print(f"RSH {instruction & 0x7} {(instruction & 0x38) >> 3}") + elif instruction & 0xFC0 == 0x180: + print(f"INC {instruction & 0x7} {(instruction & 0x38) >> 3}") + elif instruction & 0xFC0 == 0x1C0: + print(f"DEC {instruction & 0x7} {(instruction & 0x38) >> 3}") + elif instruction & 0xE00 == 0x200: + print( + f"AND {instruction & 0x7} {(instruction & 0x1C0) >> 6} " + f"{(instruction & 0x38) >> 3}" + ) + elif instruction & 0xE00 == 0x400: + print( + f"OR {instruction & 0x7} {(instruction & 0x1C0) >> 6} " + f"{(instruction & 0x38) >> 3}" + ) + elif instruction & 0xE00 == 0x600: + print( + f"NAD {instruction & 0x7} {(instruction & 0x1C0) >> 6} " + f"{(instruction & 0x38) >> 3}" + ) + elif instruction & 0xE00 == 0x800: + print( + f"SUB {instruction & 0x7} {(instruction & 0x1C0) >> 6} " + f"{(instruction & 0x38) >> 3}" + ) + elif instruction & 0xE00 == 0xA00: + print( + f"XOR {instruction & 0x7} {(instruction & 0x1C0) >> 6} " + f"{(instruction & 0x38) >> 3}" + ) + elif instruction & 0xE00 == 0xC00: + print( + f"NOR {instruction & 0x7} {(instruction & 0x1C0) >> 6} " + f"{(instruction & 0x38) >> 3}" + ) + elif instruction & 0xE00 == 0xE00: + print( + f"ADD {instruction & 0x7} {(instruction & 0x1C0) >> 6} " + f"{(instruction & 0x38) >> 3}" + ) + else: + print( + f"LookupError: Cannot find instruction " + f"{hex(self.program_counter)}: {oct(instruction)}" + ) # === Operations === @@ -315,7 +442,7 @@ class Computer: self.program_counter += 1 def LDI(self, Immediate: int): - self.pointer = Immediate % 0x3F + self.pointer = Immediate % MAX_IMMEDIATE self.program_counter += 1 def LSH(self, REG_D: int, REG_A: int): diff --git a/emulator/main.py b/emulator/main.py index 49b7b2d..6dce04b 100644 --- a/emulator/main.py +++ b/emulator/main.py @@ -6,15 +6,12 @@ from typing import Sequence from .emulator import Computer, Memory from .devices import tty - -def simple_computer_with_tty(rom: list[int]) -> Computer: - return Computer(Memory(rom, [tty(0x7FE, 0x7FF)])) - -def main(argv: Sequence[str]): +def main(argv: Sequence[str] | None = None): import argparse + from time import sleep machines = { - 'tty': simple_computer_with_tty + 'tty': lambda rom: Computer(Memory(rom, [tty(0x7FE, 0x7FF)])) } parser = argparse.ArgumentParser( @@ -23,14 +20,36 @@ def main(argv: Sequence[str]): parser.add_argument('rom_file', type=argparse.FileType('rb')) parser.add_argument( '-m', '--machine', choices=machines.keys(), default='tty') + parser.add_argument('-v', '--verbose', action='store_true') + parser.add_argument('-s', '--step', action='store_true') + parser.add_argument('-c', '--clock', default='100') args = parser.parse_args(argv) computer = machines[args.machine](Memory.load_rom_file(args.rom_file)) - while computer.active: - computer.step() + try: + while computer.active: + if args.verbose: + print( + f"ZR: {hex(0)} \t" + f"PC: {hex(computer.program_counter)} \t" + f"SP: {hex(computer.stack_pointer)} \t" + f"MP: {hex(computer.pointer)}" + ) + print( + f"D0: {hex(computer.data_0)} \t" + f"D1: {hex(computer.data_1)} \t" + f"D2: {hex(computer.data_2)} \t" + f"D3: {hex(computer.data_3)}" + ) + if args.step: + input("Press enter to step to next instruction...") + computer.step(args.verbose) + if not args.step: + sleep(int(args.clock)/1000) + except KeyboardInterrupt: + print("Keyboard Interrupt: Program Exiting...") if __name__ == '__main__': - from sys import argv - main(argv) + main() diff --git a/language/assembler.py b/language/assembler.py index ccd35e5..32ef0b5 100644 --- a/language/assembler.py +++ b/language/assembler.py @@ -5,6 +5,7 @@ from collections import namedtuple from typing import TypeVar, Generic, Iterable, Sequence INSTRUCTIONS_COUNT = 0x700 +MAX_IMMEDIATE = 0x80 class AssemblerError(Exception): pass class LinkerError(Exception): pass @@ -105,10 +106,16 @@ class Instruction(namedtuple('Instruction', ['bb', 'bl', 'lb', 'll'])): def __bytes__(self) -> bytes: value = int(self) - u = value & 0xf00 >> 8 - m = value & 0xf0 >> 4 + u = (value & 0xf00) >> 8 + l = value & 0xff + return bytes((u, l)) + + def hex_tuple(self) -> tuple[int, int, int]: + value = int(self) + u = (value & 0xf00) >> 8 + m = (value & 0xf0) >> 4 l = value & 0xf - return bytes((u, m, l)) + return (u, m, l) def oct_str(self) -> str: return ( @@ -215,10 +222,25 @@ class Program: def __bytes__(self) -> bytes: output = bytearray() + for i in range(0, INSTRUCTIONS_COUNT, 2): + instruction = self._get_instruction(i) + u1, m1, l1 = instruction.hex_tuple() + output += bytes(((u1 << 4) | m1, )) + if i + 1 < INSTRUCTIONS_COUNT: + instruction = self._get_instruction(i + 1) + u2, m2, l2 = instruction.hex_tuple() + output += bytes(((l1 << 4) | u2, )) + output += bytes(((m2 << 4) | l2, )) + else: + output += bytes((l1 << 4, )) + return bytes(output) + + def hex_str(self) -> str: + output = "" for i in range(INSTRUCTIONS_COUNT): instruction = self._get_instruction(i) - output += bytes(instruction) - return bytes(output) + output += f"{hex(int(instruction))}\n" + return output def labels(self) -> dict[str, int]: return self._label_map.copy() @@ -229,7 +251,7 @@ class Program: else: _, value = self._instructions[new_index] if isinstance(value, Immediate): return self.instruction_set["LDI"]( - f"LDI {self._label_map[value.value]}", -1) + f"LDI {self._label_map[value.value] -1}", -1) elif isinstance(value, Directive): raise LinkerError("Unexpected directive!") else: @@ -300,7 +322,8 @@ class Program: last_error = e print(f"Error:\n\t{e}") if last_error is not None: - raise last_error + # raise last_error + exit() return instructions @@ -370,17 +393,21 @@ def immediate(line: str, line_number: int) -> Instruction | Immediate: else: value = int(args[1], base=0) - return Instruction( - 0, - 2 + ((value & 0x040) >> 6), - (value & 0x038) >> 3, - value & 0x007, - ) + if value >= MAX_IMMEDIATE: + raise AssemblerError( + f"Immediate value too large {line_number}: {args[1]}") + + return Instruction( + 0, + 2 + ((value & 0x040) >> 6), + (value & 0x038) >> 3, + value & 0x007, + ) else: raise AssemblerError( f"Invalid number of arguments on line {line_number}: {args[0]}") -def main(argv: Sequence[str]): +def main(argv: Sequence[str] | None = None): import argparse parser = argparse.ArgumentParser( description='ytd 12-bit Computer Linker and Assembler', @@ -388,11 +415,23 @@ def main(argv: Sequence[str]): parser.add_argument('input_file', type=argparse.FileType('r')) parser.add_argument('-o', '--output_file', type=argparse.FileType('wb')) parser.add_argument('-l', '--labels_file', type=argparse.FileType('w')) + parser.add_argument('-x', '--hex_file', type=argparse.FileType('w')) args = parser.parse_args(argv) program = Program(args.input_file.read()) + try: assert len(bytes(program)) <= INSTRUCTIONS_COUNT * 1.5 + except AssertionError: + print( + hex(int(len(bytes(program)) / 1.5)), + '>', + hex(INSTRUCTIONS_COUNT), + ':', + hex(int(len(bytes(program)) / 1.5) - INSTRUCTIONS_COUNT), + ) + raise + if args.output_file: args.output_file.write(bytes(program)) @@ -400,6 +439,8 @@ def main(argv: Sequence[str]): for label, location in program.labels().items(): args.labels_file.write(f"{hex(location)}, {label}\n") + if args.hex_file: + args.hex_file.write(program.hex_str()) + if __name__ == '__main__': - from sys import argv - main(argv) + main() diff --git a/language/examples/test1.s b/language/examples/test1.s index fe1c746..f95f689 100644 --- a/language/examples/test1.s +++ b/language/examples/test1.s @@ -2,9 +2,19 @@ ; Example 1 - ytd 12-bit Computer ; Fibonacci -.0x5 +; .0x5 main: - ; Initialize values + ; Load Output Device Address + ldi 0x7F + or D3 MP ZR + lsh D3 D3 + lsh D3 D3 + lsh D3 D3 + lsh D3 D3 + ldi 0xE + or D3 MP D3 + + ; Initialize Fibonacci values ldi 1 or D0 MP ZR or D1 ZR ZR @@ -12,7 +22,7 @@ main: loop: ; Output current value - ldi 0x7FE + or MP D3 ZR str D0 ; Move values down