diff --git a/asm-to-prg.py b/asm-to-prg.py new file mode 100644 index 0000000..4e039fc --- /dev/null +++ b/asm-to-prg.py @@ -0,0 +1,656 @@ + +import re +from termcolor import colored + +filename = "main.asm" + +with open(filename,"r") as f: + lines = f.readlines() + + + +def convert_to_int(value): + if isinstance(value, str): # Check if the value is a string + if value.startswith("0x"): # Handle hexadecimal strings + return int(value, 16) + else: # Handle decimal strings + return int(value) + elif isinstance(value, int): # Value is already an integer + return value + else: + raise ValueError(f"Unsupported type for conversion: {type(value)}") + + + + + + + + + +def preprocess(lines, filename="main.asm"): + errors = [] + warnings = [] + error_flag = False + + # Memory and stack tracking + instruction_count = 0 + memory_limit = 256 # Total memory available + stack_balance = 0 + program_length = 0 # To calculate and validate memory access + + # Valid registers and instructions + valid_registers = {"a", "b", "c", "d", "e", "f"} + valid_instructions = {"ldw", "mov", "add", "sub", "str", "ldr", "int", + "push", "pop", "jsr", "ret", "xor", "and", "jmp", + "mul", "div", "bne", "beq", "blt"} + label_references = [] + labels = {} + + # First pass: Parse instructions and calculate program length + for line_number, line in enumerate(lines, start=1): + code = line.split(";")[0].strip() # Strip comments and whitespace + if not code: + continue + + # Handle labels + if code.endswith(":"): + label_name = code[:-1] + if label_name in labels: + warnings.append((line_number, f"duplicate label '{label_name}'", line)) + labels[label_name] = instruction_count + continue + + # Parse instruction + parts = re.split(r"\s+", code) + instruction = parts[0].lower() + if instruction in valid_instructions: + if instruction in {"ldw", "mov", "add", "sub", "str", "ldr", "xor", "and", "mul", "div"}: + instruction_count += 3 # These are 3-byte instructions + elif instruction in {"bne", "beq", "blt"}: + instruction_count += 4 # Conditional branches are 4-byte instructions + elif instruction in {"push", "pop", "int", "jmp", "jsr", "ret"}: + instruction_count += 3 # Fixed size for other instructions + else: + errors.append((line_number, f"unknown instruction '{instruction}'", line)) + error_flag = True + + program_length = instruction_count # Final length of the program + + # Second pass: Validate instructions and operands + for line_number, line in enumerate(lines, start=1): + code = line.split(";")[0].strip() # Strip comments and whitespace + if not code: + continue + + # Skip labels + if code.endswith(":"): + continue + + parts = re.split(r"\s+", code) + instruction = parts[0].lower() + operands = parts[1:] if len(parts) > 1 else [] + + # Strip commas from registers and operands + operands = [op.replace(",", "") for op in operands] + + # Validate instruction and operands + if instruction == "ldw" and len(operands) == 2: + reg, value = operands + if reg not in valid_registers: + errors.append((line_number, f"invalid register '{reg}'", line)) + error_flag = True + elif instruction == "str" and len(operands) == 2: + reg, address = operands + if reg not in valid_registers: + errors.append((line_number, f"invalid register '{reg}'", line)) + error_flag = True + try: + mem_address = int(address, 16) + if mem_address < program_length: + errors.append((line_number, f"illegal memory write to program space in '{code}'", line)) + error_flag = True + if mem_address > memory_limit: + errors.append((line_number, f"illegal memory write out of bounds in '{code}'", line)) + error_flag = True + + except ValueError: + errors.append((line_number, f"invalid memory address '{address}'", line)) + error_flag = True + elif instruction in {"add", "sub", "mov", "xor", "and", "mul", "div"} and len(operands) == 2: + reg1, reg2 = operands + if reg1 not in valid_registers or reg2 not in valid_registers: + errors.append((line_number, f"invalid register(s) in '{code}'", line)) + error_flag = True + elif instruction in {"push", "pop"} and len(operands) == 1: + reg = operands[0] + if reg not in valid_registers: + errors.append((line_number, f"invalid register '{reg}'", line)) + error_flag = True + if instruction == "push": + stack_balance += 1 + if stack_balance > 16: # Example stack limit + warnings.append((line_number, "stack overflow detected", line)) + elif instruction == "pop": + stack_balance -= 1 + if stack_balance < 0: + errors.append((line_number, f"stack underflow detected at '{code}'", line)) + error_flag = True + # Validate branch instructions with two registers and one label + elif instruction in {"bne", "beq", "blt"}: + if len(operands) != 3: + errors.append((line_number, f"branch instruction '{instruction}' should have 2 registers and 1 label", line)) + error_flag = True + else: + reg1, reg2, label = operands + if reg1 not in valid_registers or reg2 not in valid_registers: + errors.append((line_number, f"invalid register(s) in '{instruction}'", line)) + error_flag = True + label_references.append((line_number, label, line)) # The third operand should be a label + + elif instruction in {"jmp", "jsr"}: + if len(operands) != 1: + errors.append((line_number, f"'{instruction}' instruction should have 1 operand (label)", line)) + error_flag = True + label = operands[0] # The only operand should be a label + label_references.append((line_number, label, line)) + + # Check undefined labels + for line_number, label, line in label_references: + if label not in labels: + errors.append((line_number, f"undefined label '{label}'", line)) + error_flag = True + + # Check stack balance at the end + if stack_balance != 0: + warnings.append((0, "stack imbalance detected, unbalanced push/pop operations", "")) + + # Print errors and warnings + for line_number, message, code_line in errors: + print(colored(f"{filename}:{line_number}: error: {message}", "red")) + print(colored(f" {line_number} | {code_line}", "white")) + print(colored(f" | {'^' * len(code_line)}", "cyan")) + + for line_number, message, code_line in warnings: + if line_number == 0: + print(colored(f"{filename}: warning: {message}", "yellow")) + else: + print(colored(f"{filename}:{line_number}: warning: {message}", "yellow")) + print(colored(f" {line_number} | {code_line}", "white")) + + if program_length >= memory_limit: + error_flag = True + print(colored(f"GLOBAL: error: Program too big, size: {program_length}", "red")) + + + # Final success message + if not error_flag: + print(colored("Preprocessing complete. No errors detected!", "green")) + else: + exit(1) + + + + +preprocess(lines) + + + + + + + + + +lineNumber = 0 + +def _ValueError(message): + print("ValueError: %s on line:" % message, lineNumber) + +def _IndexError(message): + print("IndexError: %s on line:" % message, lineNumber) + +def _InstructionError(message): + print("InstructionError: %s on line:" % message, lineNumber) + + + + + +# for line in lines: +# line = line.split(";")[0] # filter out comments +# print(line) + +# Dictionary to store labels and associated instructions +label_to_instructions = {} +current_label = None + + + + +for line in lines: + # Remove leading and trailing whitespace + stripped_line = line.strip() + + if stripped_line.endswith(':'): + # It's a label, use it as the new key in the dictionary + current_label = stripped_line[:-1] # Remove the colon + current_label = current_label.upper() + label_to_instructions[current_label] = [] # Initialize empty instruction list + elif stripped_line: + # It's an instruction; add it to the current label's list + if current_label is not None: + label_to_instructions[current_label].append(stripped_line) + + + + +# register letter to identifyer +registerDict = {'a':0x0,'b':0x1,'c':0x2,'d':0x3,'e':0x4,'f':0x5} + + + +current_byte_offset = 0 # Tracks the current byte address +label_addresses = {} # Maps label names to their resolved byte addresses + +for label in label_to_instructions: + label_addresses[label] = current_byte_offset + for line in label_to_instructions[label]: + line = line.strip().split(";")[0] # strip comments + line = line.rstrip(" ") # strip spaces at end + + + line = line.replace(",", "") # stupid way to remove commas but it works + + line = line.split(" ") # get each part of the instruction + + + line[0] = line[0].lower() # make instruction lowercase + + if line[0] == '': + continue + + if line[0] in {"ldw","mov","add","sub","str","ldr","int","push","pop","jsr", "ret", 'xor', 'and', 'jmp', 'mul', 'div'}: # 3 byte instructions + current_byte_offset += 3 + + elif line[0] in {'bne', 'beq', 'blt'}: # 4 byte instructions + current_byte_offset += 4 + + + + +current_byte_offset = 0 # Tracks the current byte address +#print(label_addresses) + +outputBytes = [] +for label in label_to_instructions: + #print(label) + if label_addresses[label] != current_byte_offset: + raise IndexError(f"address mismatch, expected {label_addresses[label]}, got {current_byte_offset}") + # Output the results + for line in label_to_instructions[label]: + line = line.strip().split(";")[0] # strip comments + line = line.rstrip(" ") # strip spaces at end + + + line = line.replace(",", "") # stupid way to remove commas but it works + + line = line.split(" ") # get each part of the instruction + + + line[0] = line[0].lower() # make instruction lowercase + + if line[0] == '': + continue + + #print(line) + + + #! Code to convert to bytes + bytes = [] + try: + if line[0] == 'ldw': # Load immediate to register + bytes.append(0x1) # byte for load immediate value + # set register ID: + register = registerDict.get(line[1].lower(),-1) + if register >= 0 and register <= 5: + bytes.append(register) + + else: + _ValueError("Invalid Register") + + bytes.append(convert_to_int(line[2])) # the actual value as an int + + elif line[0] == 'mov': # Load immediate to register + bytes.append(0x2) # byte for load immediate value + # set register ID: + register = registerDict.get(line[1].lower(),-1) + if register >= 0 and register <= 5: + bytes.append(register) + + else: + _ValueError("Invalid Register") + + #bytes.append(0x0) + + register = registerDict.get(line[2].lower(),-1) + if register >= 0 and register <= 5: + bytes.append(register) + + else: + _ValueError("Invalid Register") + + #bytes.append(convert_to_int(line[2])) # the actual value as an int + + elif line[0] == 'add': # Load immediate to register + bytes.append(0x3) + # set register ID: + register = registerDict.get(line[1].lower(),-1) + if register >= 0 and register <= 5: + bytes.append(register) + + else: + _ValueError("Invalid Register") + + register = registerDict.get(line[2].lower(),-1) + if register >= 0 and register <= 5: + bytes.append(register) + + else: + _ValueError("Invalid Register") + + elif line[0] == 'sub': # Load immediate to register + bytes.append(0x4) + # set register ID: + register = registerDict.get(line[1].lower(),-1) + if register >= 0 and register <= 5: + bytes.append(register) + + else: + _ValueError("Invalid Register") + + register = registerDict.get(line[2].lower(),-1) + if register >= 0 and register <= 5: + bytes.append(register) + + else: + _ValueError("Invalid Register") + + elif line[0] == 'str': # Load immediate to register + bytes.append(0x5) + # set register ID: + register = registerDict.get(line[1].lower(),-1) + if register >= 0 and register <= 5: + bytes.append(register) + + else: + _ValueError("Invalid Register") + + + bytes.append(convert_to_int(line[2])) # the actual value as an int + + + elif line[0] == 'ldr': # Load immediate to register + bytes.append(0x6) + # set register ID: + register = registerDict.get(line[1].lower(),-1) + if register >= 0 and register <= 5: + bytes.append(register) + + else: + _ValueError("Invalid Register") + + + bytes.append(convert_to_int(line[2])) # the actual value as an int + + + elif line[0] == 'int': # Load immediate to register + bytes.append(0xA) + + bytes.append(convert_to_int(line[1])) # the actual value as an int + + bytes.append(0x0) #! NEED THIS TO KEEP THE INSTRUCTION AT 3 BYTES + + + elif line[0] == 'bne': # Load immediate to register + bytes.append(0x8) + # set register ID: + register = registerDict.get(line[1].lower(),-1) + if register >= 0 and register <= 5: + bytes.append(register) + + else: + _ValueError("Invalid Register") + + register = registerDict.get(line[2].lower(),-1) + if register >= 0 and register <= 5: + bytes.append(register) + + else: + _ValueError("Invalid Register") + + + label = line[3].upper() + if label == -1: + _InstructionError("Missing Label") + continue + + if label in label_to_instructions: + bytes.append(label_addresses[label]) + else: + _InstructionError("Unknown Label") + + elif line[0] == 'beq': # Load immediate to register + bytes.append(0x9) + # set register ID: + register = registerDict.get(line[1].lower(),-1) + if register >= 0 and register <= 5: + bytes.append(register) + + else: + _ValueError("Invalid Register") + + register = registerDict.get(line[2].lower(),-1) + if register >= 0 and register <= 5: + bytes.append(register) + + else: + _ValueError("Invalid Register") + + + label = line[3].upper() + if label == -1: + _InstructionError("Missing Label") + continue + + if label in label_to_instructions: + bytes.append(label_addresses[label]) + else: + _InstructionError("Unknown Label") + + elif line[0] == 'push': # Load immediate to register + bytes.append(0xB) + # set register ID: + register = registerDict.get(line[1].lower(),-1) + if register >= 0 and register <= 5: + bytes.append(register) + + else: + _ValueError("Invalid Register") + + + + bytes.append(0x0) # padding + elif line[0] == 'pop': # Load immediate to register + bytes.append(0xC) + # set register ID: + register = registerDict.get(line[1].lower(),-1) + if register >= 0 and register <= 5: + bytes.append(register) + + else: + _ValueError("Invalid Register") + + + bytes.append(0x0) # padding + + elif line[0] == 'jsr': # Load immediate to register + bytes.append(0xD) + # set register ID: + + label = line[1].upper() + if label == -1: + _InstructionError("Missing Label") + continue + + if label in label_to_instructions: + bytes.append(label_addresses[label]) + else: + _InstructionError("Unknown Label") + + bytes.append(0x0) # padding + + elif line[0] == 'ret': # Load immediate to register + bytes.append(0xE) + # set register ID: + bytes.append(0x0) # padding + bytes.append(0x0) # padding + + + elif line[0] == 'xor': # Load immediate to register + bytes.append(0xF) + + register = registerDict.get(line[1].lower(),-1) + if register >= 0 and register <= 5: + bytes.append(register) + + else: + _ValueError("Invalid Register") + + register = registerDict.get(line[2].lower(),-1) + if register >= 0 and register <= 5: + bytes.append(register) + + else: + _ValueError("Invalid Register") + + elif line[0] == 'and': # Load immediate to register + bytes.append(0x10) + + register = registerDict.get(line[1].lower(),-1) + if register >= 0 and register <= 5: + bytes.append(register) + + else: + _ValueError("Invalid Register") + + register = registerDict.get(line[2].lower(),-1) + if register >= 0 and register <= 5: + bytes.append(register) + + else: + _ValueError("Invalid Register") + + elif line[0] == 'jmp': # Load immediate to register + bytes.append(0x11) + # set register ID: + + label = line[1].upper() + if label == -1: + _InstructionError("Missing Label") + continue + + if label in label_to_instructions: + bytes.append(label_addresses[label]) + else: + _InstructionError("Unknown Label") + + bytes.append(0x0) # padding + + + + elif line[0] == 'mul': # Load immediate to register + bytes.append(0x12) + # set register ID: + register = registerDict.get(line[1].lower(),-1) + if register >= 0 and register <= 5: + bytes.append(register) + + else: + _ValueError("Invalid Register") + + register = registerDict.get(line[2].lower(),-1) + if register >= 0 and register <= 5: + bytes.append(register) + + else: + _ValueError("Invalid Register") + + elif line[0] == 'div': # Load immediate to register + bytes.append(0x13) + # set register ID: + register = registerDict.get(line[1].lower(),-1) + if register >= 0 and register <= 5: + bytes.append(register) + + else: + _ValueError("Invalid Register") + + register = registerDict.get(line[2].lower(),-1) + if register >= 0 and register <= 5: + bytes.append(register) + + else: + _ValueError("Invalid Register") + + elif line[0] == 'blt': # Load immediate to register + bytes.append(0x14) + # set register ID: + register = registerDict.get(line[1].lower(),-1) + if register >= 0 and register <= 5: + bytes.append(register) + + else: + _ValueError("Invalid Register") + + register = registerDict.get(line[2].lower(),-1) + if register >= 0 and register <= 5: + bytes.append(register) + + else: + _ValueError("Invalid Register") + + + label = line[3].upper() + if label == -1: + _InstructionError("Missing Label") + continue + + if label in label_to_instructions: + bytes.append(label_addresses[label]) + else: + _InstructionError("Unknown Label") + + + else: + _InstructionError("Unknown Instruction") + + + + except IndexError: + _IndexError("Maformed Instruction") + except ValueError: + _ValueError("Unknown Error") + + current_byte_offset += len(bytes) + + + lineNumber+=1 + outputBytes += bytes + + +print(outputBytes) + + + + \ No newline at end of file diff --git a/drive8.bin b/drive8.bin new file mode 100644 index 0000000..e69de29 diff --git a/gradiant.asm b/gradiant.asm new file mode 100644 index 0000000..16cae10 --- /dev/null +++ b/gradiant.asm @@ -0,0 +1,66 @@ +main: + jsr lcd_init ; Initialize the screen + ldw b, 0 ; Initialize register b (x position) + ldw c, 0 ; Initialize register c (y position) + ldw d, 1 ; Increment value + ldw f, 600 ; Screen width/height limit + + ; Store scaling factors in memory (safe zone past 0xC8) + ldw a, 0x0 ; Scaling factor for green (shift left by 8 bits) + str a, 0xD0 ; Store in memory at address 0xD0 + ldw a, 0xFF0000 ; Scaling factor for red (shift left by 16 bits) + str a, 0xC8 ; Store in memory at address 0xC8 + ldw a, 0x00FF00 ; Scaling factor for green (shift left by 8 bits) + str a, 0xCC ; Store in memory at address 0xCC + + +loop: + ; Compute red channel + mov a, b ; Copy x position to a + ldr e, 0xC8 ; Load red scaling factor + mul a, e ; Multiply x by red scaling factor + + ; Compute green channel + mov e, c ; Copy y position to e + ldr a, 0xCC ; Load green scaling factor into a + mul e, a ; Multiply y by green scaling factor (result in e) + + ; Combine red and green channels + add a, e ; Combine red and green channels (result in a) + + ; Add blue channel (based on x for simplicity) + add a, b ; Blue intensity based on x position + + ; Draw the pixel + int 0x71 ; Set pixel color at (b, c) + int 0xF6 ; get key pressed + + +next: + ; Update position + ldw a, 0 ; Reset a + add b, d ; Increment x (b += 1) + bne b, f, loop ; If b < 256, continue loop + ldw b, 0 ; Reset x + add c, d ; Increment y (c += 1) + bne c, f, loop ; If c < 256, continue loop + + ; Halt + int 0xFF ; Stop program + +lcd_init: + push a + push b + push c + + + ldw a, 0x0 ; mode + + ldw b, 256 ; Vertical resolution + ldw c, 256 ; Horizontal resolution + int 0x70 ; Initialize screen + + pop c + pop b + pop a + ret diff --git a/main.asm b/main.asm new file mode 100644 index 0000000..6610d1c --- /dev/null +++ b/main.asm @@ -0,0 +1,46 @@ +; Initialize text mode +main: + ldw a, 1 ; Mode: 1 for text mode + ldw b, 800 ; Horizontal resolution + ldw c, 600 ; Vertical resolution + int 0x70 ; Initialize display + ldw b, 0 ; Cursor position (character cell index) + ldw c, 0xFFFFFF ; White color + + +main_loop: + ; Get key down (handle key press/release) + mov f, b ; f <- b + int 0xF6 ; a <- keycode | b <- first press + add b, f, ; b = f + b ; This moves the value in f back to B, and sence b is eyther 1 or 0, it will eyther increment it, or not increment it. + + str b, 0xEE + str a, 0xEF + + + + ; If a key is not pressed (register 0x1 == 0), continue the loop + ldw d, 0 ; Check if a key is pressed (if register 0x1 == 1) + beq a, d, main_loop ; If A == 0, loop back to main_loop (no key is pressed) + + ; Render the character + int 0x72 ; Render the character (using the keycode from register 0x0 at position b) + + ; Write letter to disk at index ;push a + ; Write letter to disk at index ;push b + ; Write letter to disk at index ;push c + ; Write letter to disk at index ;push d + ; Write letter to disk at index ; + ; Write letter to disk at index ;ldw a, 8 ; disk number + ; Write letter to disk at index ;ldw b, 0 ; sector number + ; Write letter to disk at index ;ldr c, 0xEE ; byte offset + ; Write letter to disk at index ;ldr d, 0xEF ; value to write + ; Write letter to disk at index ; + ; Write letter to disk at index ;int 0x81 ; Write + ; Write letter to disk at index ; + ; Write letter to disk at index ;pop d + ; Write letter to disk at index ;pop c + ; Write letter to disk at index ;pop b + ; Write letter to disk at index ;pop a + ; Reset cursor to the top of the screen + jmp main_loop ; Jump back to the main loop diff --git a/main.py b/main.py new file mode 100644 index 0000000..f09525d --- /dev/null +++ b/main.py @@ -0,0 +1,735 @@ +import pygame, time, os + +class CPU: + def __init__(self, memory_size=256): + stack_size = 32 + self.memory = [0] * memory_size # Fixed-size memory + self.stack = [0] * stack_size + self.SP = len(self.stack) # Stack grows downward + self.PC = 0x00 # Program Counter + self.A = 0 # Register A + self.B = 0 # Register B + self.C = 0 # Register C + self.D = 0 # Register D + self.E = 0 # Register E + self.F = 0 # Register F + + self.running = True + + self.cycles = 0 + + # Initialize pygame screen for 256x256 resolution + self.V_res = -1 + self.H_res = -1 + self.screen = None + + self.keydown = False + self.last_key = None + + + self.text_buffer = None # Space and white color + + + + + def state(self): + # Print Registers in Hex Format + print("Registers:") + print(f" A: 0x{self.A:02X}") + print(f" B: 0x{self.B:02X}") + print(f" C: 0x{self.C:02X}") + print(f" D: 0x{self.D:02X}") + print(f" E: 0x{self.E:02X}") + print(f" F: 0x{self.F:02X}") + + # Print Stack (if needed, in a similar hex format) + print("\nStack:") + print(self.stack) + + # Print Total Cycles + print(f"\nTotal Cycles: {self.cycles}") + + # Print Memory in Hex, with addresses on the left and values aligned + print("\nMemory:") + i = 0 + for address in range(0, len(self.memory), 16): # Iterate by 16 values (one row at a time) + # Print address + print(f"0x{address:04X}: ", end="") # Print the memory address in hex (4 digits) + + # Print 16 values on the same line + for j in range(16): + if address + j < len(self.memory): # Avoid out-of-bounds + print(f"{self.memory[address + j]:02X}", end=" ") + else: + print(" ", end=" ") # Empty spaces for remaining uninitialized memory + print() # Move to the next line + + + + def load_program(self, program): + """Load the machine code program into memory.""" + self.memory[:len(program)] = program + + + def fetch(self): + """Fetch the next instruction.""" + if self.PC >= len(self.memory): + self.running = False + return None + + # Check for 4-byte instructions + opcode = self.memory[self.PC] + length = 4 if opcode in (0x08, 0x09) else 3 + instruction = self.memory[self.PC:self.PC + length] + + if len(instruction) < length: + instruction += [0] * (length - len(instruction)) + + self.PC += length + return instruction + + + + def execute(self, instruction): + self.cycles+=1 + #print(self.PC) + """Execute an instruction.""" + if instruction is None: + return + + opcode = instruction[0] + if opcode == 0x00: # Halt + self.running = False + elif opcode == 0x01: # LOAD + reg, value = instruction[1], instruction[2] + self._load(reg, value) + elif opcode == 0x02: # MOV + dest, src = instruction[1], instruction[2] + self._mov(dest, src) + elif opcode == 0x03: # ADD + dest, src = instruction[1], instruction[2] + self._add(dest, src) + elif opcode == 0x04: # SUB + dest, src = instruction[1], instruction[2] + + self._sub(dest, src) + + elif opcode == 0x05: # STORE + reg, addr = instruction[1], instruction[2] + self._store(reg, addr) + elif opcode == 0x06: # LOADM + reg, addr = instruction[1], instruction[2] + self._loadm(reg, addr) + elif opcode == 0x08: # BNE (Branch if Not Equal) + reg, reg2, target = instruction[1], instruction[2], instruction[3] + self._bne(reg, reg2, target) + elif opcode == 0x09: # beq (Branch if Equal) + reg, reg2, target = instruction[1], instruction[2], instruction[3] + self._beq(reg, reg2, target) + elif opcode == 0x0A: # int interupt handler + value, opt = instruction[1], instruction[2] + self._int(value, opt) + elif opcode == 0x0B: # push stack + reg, opt = instruction[1], instruction[2] + self._push(reg, opt) + + elif opcode == 0x0C: # pop stack + reg, opt = instruction[1], instruction[2] + self._pop(reg, opt) + elif opcode == 0x0D: # pop stack + value, opt = instruction[1], instruction[2] + self._jsr(value, opt) + elif opcode == 0x0E: # pop stack + value, opt = instruction[1], instruction[2] + self._ret(value, opt) + + elif opcode == 0x0F: # xor + reg, reg2 = instruction[1], instruction[2] + self._xor(reg, reg2) + + elif opcode == 0x10: # and + reg, reg2 = instruction[1], instruction[2] + self._and(reg, reg2) + + elif opcode == 0x11: # jmp + addr, opt = instruction[1], instruction[2] + self._jmp(addr, opt) + + elif opcode == 0x12: # mul + reg1, reg2 = instruction[1], instruction[2] + self._mul(reg1, reg2) + + elif opcode == 0x13: # div + reg1, reg2 = instruction[1], instruction[2] + self._div(reg1, reg2) + + elif opcode == 0x14: # beq (Branch if Equal) + reg, reg2, target = instruction[1], instruction[2], instruction[3] + self._blt(reg, reg2, target) + + else: + raise ValueError(f"Unknown opcode: {opcode}") + + + + def _mul(self, reg1, reg2): + result = self._get_register(reg1) * self._get_register(reg2) + self._set_register(reg1, result) + + def _div(self, reg1, reg2): + a = int(self._get_register(reg1)) + b = int(self._get_register(reg2)) + if b == 0: + raise ZeroDivisionError("Divided by 0") + result = a / b + self._set_register(reg1, result) + + + def _xor(self, reg, reg2): + # Retrieve values from the registers, ensuring they're integers + val1 = int(self._get_register(reg)) + val2 = int(self._get_register(reg2)) + # Perform XOR operation + result = val1 ^ val2 + + # Ensure the result is also an integer and set it back to the register + self._set_register(reg, result) + + def _and(self, reg, reg2): + result = self._get_register(reg) & self._get_register(reg2) + self._set_register(reg, result) + + + def _load(self, reg, value): + if reg == 0x00: + self.A = value + elif reg == 0x01: + self.B = value + elif reg == 0x02: + self.C = value + elif reg == 0x03: + self.D = value + elif reg == 0x04: + self.E = value + elif reg == 0x05: + self.F = value + else: + raise ValueError("Invalid register.") + + def _push(self, reg, opt): + self.SP -= 1 + if self.SP < 0: + #print(f"STACK OVERFLOW: SP={self.SP}") + raise OverflowError("Stack overflow") + val = self._get_register(reg) + self.stack[self.SP] = val + #print(f"PUSH: SP={self.SP}, VALUE={val}, STACK={self.stack}") + + def _pop(self, reg, opt): + if self.SP >= len(self.stack): + #print(f"STACK UNDERFLOW: SP={self.SP}") + raise OverflowError("Stack underflow") + val = self.stack[self.SP] + self.stack[self.SP] = 0 + self.SP += 1 + self._set_register(reg, val) + #print(f"POP: SP={self.SP}, VALUE={val}, STACK={self.stack}") + + def _jsr(self, value, opt): + self.SP -= 1 + if self.SP < 0: + #print(f"STACK OVERFLOW: SP={self.SP}") + raise OverflowError("Stack overflow") + self.stack[self.SP] = self.PC+3 + self.PC = value + #print(f"JSR: SP={self.SP}, PC={self.PC}, STACK={self.stack}") + + def _jmp(self, value, opt): + self.PC = value + + + + def _ret(self, value, opt): + if self.SP >= len(self.stack): + #print(f"STACK UNDERFLOW: SP={self.SP}") + raise OverflowError("Stack underflow") + address = self.stack[self.SP] + self.stack[self.SP] = 0 + self.SP += 1 + self.PC = address + #print(f"RET: SP={self.SP}, PC={self.PC}, STACK={self.stack}") + + + + + def _display_init(self): + mode = self._get_register(0x0) + x = self._get_register(0x1) + y = self._get_register(0x2) + + self.V_res = y + self.H_res = x + self.mode = mode # Save the display mode + + pygame.init() + + if mode == 0: # Bitmap mode + self.screen = pygame.display.set_mode((x, y)) + self.screen.fill((0, 0, 0)) # Black background + pygame.display.set_caption("Bitmap Mode") + elif mode == 1: # Text mode + self.cell_width = 10 # Width of each text cell (in pixels) + self.cell_height = 16 # Height of each text cell (in pixels) + self.max_columns = self.H_res // self.cell_width + self.max_rows = self.V_res // self.cell_height + + # Initialize the text buffer + self.text_buffer = [[(0x20, 0xFFFFFF) for _ in range(self.max_columns)] for _ in range(self.max_rows)] + + self.font = pygame.font.Font(pygame.font.get_default_font(), self.cell_height) + self.screen = pygame.display.set_mode((x, y)) + self.screen.fill((0, 0, 0)) # Black background + pygame.display.set_caption("Text Mode") + else: + raise ValueError("Invalid display mode") + + + + + + + def _int(self, value, opt): + + #? Display Interrupts + if value == 0x70: # Init display + self._display_init() + elif value == 0x71: # Set pixel (bitmap mode) + self._set_pixel() + elif value == 0x72: # Render character (text mode) + self._add_text() + + #? Terminal Interrupts + elif value == 0x00: # Print character + self._print_register_char() + elif value == 0x01: # Print integer + self._print_register_int() + + #? System Interrupts + elif value == 0xFF: # Halt + self.running = False + return + elif value == 0xF6: # Get key down + keys = pygame.key.get_pressed() # Get the state of all keys + + key_pressed = False # Flag to check if a key was pressed in this cycle + current_key = None # Track the currently detected key + + for keycode in range(len(keys)): + if keys[keycode]: # If this key is pressed + current_key = keycode # Set the current key + + self._set_register(0x0, keycode) # Set register A to the keycode + + if not self.keydown or (self.last_key != current_key): + # If it's the first press or a new key is pressed + self._set_register(0x1, 1) # Set register B to indicate a new key press + self.keydown = True # Mark that a key is pressed + self.last_key = current_key # Update the last pressed key + else: + # If the same key is still being held + self._set_register(0x1, 0) # Set register B to indicate no new key + key_pressed = True + return + + if not key_pressed: # If no key is pressed (key release) + self._set_register(0x0, 0) # Set register A to 0 (no key pressed) + self._set_register(0x1, 0) # Set register B to 0 (reset key state) + self.keydown = False # Reset the keydown flag + self.last_key = None # Clear the last pressed key + + + + + + elif value == 0xFE: # Error interrupt + print("Error interrupt") + print(f"Register A: {self._get_register(0)}") + print(f"Register B: {self._get_register(1)}") + print(f"Register C: {self._get_register(2)}") + self.running = False + + + + elif value == 0x80: # Read byte from disk + self._read_byte_from_disk(opt) + + elif value == 0x81: # Read byte from disk + self._write_byte_from_disk(opt) + + + + else: + raise ValueError(f"Unknown interrupt: {value}") + + def _read_byte_from_disk(self, opt): + # Register layout: + # a = drive number (0-9) + # b = sector number (0-15) + # c = byte offset within sector (0-254) + + drive_number = self._get_register(0x0) # Drive number + sector_number = self._get_register(0x1) # Sector number + byte_offset = self._get_register(0x2) # Byte offset within sector + + # Validate input values and trigger error interrupt if invalid + if not (0 <= drive_number <= 9): + self._set_register(0x0, 0x81) # Error code 1: Invalid drive number + self._set_register(0x1, self.PC) # Set current PC to register 0xB + self._set_register(0x2, 0) # Set register 0xC to 0 as specified + self._int(0xFE, 0) # Trigger the error interrupt + return + + if not (0 <= sector_number <= 15): + self._set_register(0x0, 0x82) # Error code 2: Invalid sector number + self._set_register(0x1, self.PC) + self._set_register(0x2, 0) + self._int(0xFE, 0) + return + + if not (0 <= byte_offset <= 254): + self._set_register(0x0, 0x83) # Error code 3: Invalid byte offset + self._set_register(0x1, self.PC) + self._set_register(0x2, 0) + self._int(0xFE, 0) + return + + # Construct the disk file path (e.g., "disk0.bin", "disk1.bin") + disk_file = f"disk{drive_number}.bin" + + if not os.path.exists(disk_file): + self._set_register(0x0, 0x84) # Error code 4: Disk file not found + self._set_register(0x1, self.PC) + self._set_register(0x2, 0) + self._int(0xFE, 0) + return + + # Open the disk file and read the desired byte + with open(disk_file, 'rb') as f: + # Calculate the position to read from: + # (sector_number * 255 bytes per sector) + byte_offset + position = (sector_number * 255) + byte_offset + f.seek(position) + + byte_value = f.read(1) # Read one byte + if len(byte_value) == 0: + self._set_register(0x0, 0x85) # Error code 5: Failed to read byte + self._set_register(0x1, self.PC) + self._set_register(0x2, 0) + self._int(0xFE, 0) + return + + # Store the byte value in a register (0x4 for this example) + self._set_register(0x4, byte_value[0]) # Store byte in register 0x4 + #print(f"Read byte: {byte_value[0]:#04x} from {disk_file}, sector {sector_number}, byte {byte_offset}") + + def _write_byte_from_disk(self, opt): + # Register layout: + # a = drive number (0-9) + # b = sector number (0-15) + # c = byte offset within sector (0-254) + # d = byte value to write (0-255) + + drive_number = self._get_register(0x0) # Drive number + sector_number = self._get_register(0x1) # Sector number + byte_offset = self._get_register(0x2) # Byte offset within sector + byte_to_write = self._get_register(0x3) # Byte to write + + + # Validate input values and trigger error interrupt if invalid + if not (0 <= drive_number <= 9): + + self._set_register(0x0, 0x81) # Error code 1: Invalid drive number + self._set_register(0x1, self.PC) # Set current PC to register 0xB + self._set_register(0x2, 0) # Set register 0xC to 0 as specified + self._int(0xFE, 0) # Trigger the error interrupt + return + + if not (0 <= sector_number <= 15): + + self._set_register(0x0, 0x82) # Error code 2: Invalid sector number + self._set_register(0x1, self.PC) + self._set_register(0x2, 0) + self._int(0xFE, 0) + return + + if not (0 <= byte_offset <= 254): + self._set_register(0x0, 0x83) # Error code 3: Invalid byte offset + self._set_register(0x1, self.PC) + self._set_register(0x2, 0) + self._int(0xFE, 0) + return + + # Construct the disk file path (e.g., "disk0.bin", "disk1.bin") + disk_file = f"drive{drive_number}.bin" + + if not os.path.exists(disk_file): + print(0x84) + self._set_register(0x0, 0x84) # Error code 4: Disk file not found + self._set_register(0x1, self.PC) + self._set_register(0x2, 0) + self._int(0xFE, 0) + return + + # Open the disk file in 'r+b' mode for reading and writing + with open(disk_file, 'r+b') as f: + # Calculate the position to write to: + # (sector_number * 255 bytes per sector) + byte_offset + position = (sector_number * 255) + byte_offset + f.seek(position) # Move the file pointer to the desired position + + # Write the byte to the file + f.write(bytes([byte_to_write])) # Write a single byte to the file + #print(f"Written byte: {byte_to_write:#04x} to {disk_file}, sector {sector_number}, byte {byte_offset}") + + + + def _print_register_char(self): + + char = self._get_register(0x0) + print(chr(char)) + def _print_register_int(self): + + char = self._get_register(0x0) + print(char) + + def _mov(self, dest, src): + value = self._get_register(src) + self._set_register(dest, value) + + def _add(self, dest, src): + result = self._get_register(dest) + self._get_register(src) + self._set_register(dest, result) + + def _sub(self, dest, src): + + result = self._get_register(dest) - self._get_register(src) + + self._set_register(dest, result) + + def _store(self, reg, addr): + if addr < 0 or addr >= len(self.memory): + raise ValueError("Invalid memory address.") + self.memory[addr] = self._get_register(reg) + + + + def _loadm(self, reg, addr): + if addr < 0 or addr >= len(self.memory): + raise ValueError("Invalid memory address.") + value = self.memory[addr] + self._set_register(reg, value) + + + def _set_pixel(self): + if not self.screen or self.mode != 0: # Ensure it's bitmap mode + return + + _color = self._get_register(0) + _x = self._get_register(1) + _y = self._get_register(2) + + if 0 <= _x < self.H_res and 0 <= _y < self.V_res: + r = (_color & 0xFF0000) >> 16 + g = (_color & 0x00FF00) >> 8 + b = (_color & 0x0000FF) + self.screen.set_at((_x, _y), (r, g, b)) + else: + raise ValueError("Pixel coordinates out of bounds") + + + + def _add_text(self): + if not self.screen or self.mode != 1: # Ensure it's text mode + return + + # Retrieve registers + _char = self._get_register(0) & 0xFF # ASCII character + _cursor_pos = self._get_register(1) # Current cursor position (in terms of character cells) + _color = self._get_register(2) # Text color + + # Ensure cursor position is within bounds + if _cursor_pos >= self.max_columns * self.max_rows: + _cursor_pos = (self.max_columns * self.max_rows) - 1 + + # Calculate row and column from the cursor position + cursor_x = _cursor_pos % self.max_columns + cursor_y = _cursor_pos // self.max_columns + + # Update the text buffer + self.text_buffer[cursor_y][cursor_x] = (_char, _color) + + # Redraw the entire screen + self.screen.fill((0, 0, 0)) # Clear the screen + for row_index, row in enumerate(self.text_buffer): + for col_index, (char, color) in enumerate(row): + # Convert to pixel coordinates + _x = col_index * self.cell_width + _y = row_index * self.cell_height + + # Extract color components (RGB) + r = (color & 0xFF0000) >> 16 + g = (color & 0x00FF00) >> 8 + b = (color & 0x0000FF) + + # Render the character + text_surface = self.font.render(chr(char), True, (r, g, b)) + self.screen.blit(text_surface, (_x, _y)) + + # Update the display + pygame.display.flip() + + + + + + + + + def _bne(self, reg, reg2, target): + """Branch to target address if register != value.""" + if self._get_register(reg) != self._get_register(reg2): + if 0 <= target < len(self.memory): + self.PC = target + else: + raise ValueError(f"Invalid branch target address: {target}") + + def _blt(self, reg, reg2, target): + """Branch to target address if register < value.""" + if self._get_register(reg) < self._get_register(reg2): + if 0 <= target < len(self.memory): + self.PC = target + else: + raise ValueError(f"Invalid branch target address: {target}") + + + + def _beq(self, reg, reg2, target): + """Branch to target address if register == value.""" + if self._get_register(reg) == self._get_register(reg2): + self.PC = target + + def _get_register(self, reg): + if reg == 0x00: # A + return self.A + elif reg == 0x01: # B + return self.B + elif reg == 0x02: # C + return self.C + elif reg == 0x03: + return self.D + elif reg == 0x04: + return self.E + elif reg == 0x05: + return self.F + else: + raise ValueError(f"Invalid register: {reg}") + + + def _set_register(self, reg, value): + if type(value) != int: + raise TypeError(f"Invalid register type") + if reg == 0x00: + self.A = value + elif reg == 0x01: + self.B = value + elif reg == 0x02: + self.C = value + elif reg == 0x03: + self.D = value + elif reg == 0x04: + self.E = value + elif reg == 0x05: + self.F = value + else: + raise ValueError("Invalid register code.") + + def run(self): + """Run the loaded program with error interrupts.""" + line = 0 + timer = 0 + try: + end = time.time() + start = time.time() + while self.running: + start = time.time() + + instruction = self.fetch() + #print(instruction) + + if self.screen: + if timer >= 1 / 60: + pygame.display.update() + timer = 0 + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + self.running = False + + self.execute(instruction) + line += 1 + end = time.time() + timer += end - start + + except ValueError as e: + print(e, self.B, self.C) + # Trigger an error interrupt with details + self._set_register(0x0, 0x1) # Error Type + self._set_register(0x1, self.PC) # Error Address + self._set_register(0x2, instruction[0]) # Error Type + + self._int(0xFE, 0) + except IndexError as e: + print(e, self.B, self.C) + # Trigger an error interrupt with details + self._set_register(0x0, 0x2) # Error Type + self._set_register(0x1, self.PC) # Error Address + self._set_register(0x2, instruction[0]) # Error Type + + self._int(0xFE, 0) + except OverflowError as e: + print(e, self.B, self.C) + # Trigger an error interrupt with details + self._set_register(0x0, 0x3) # Error Type + self._set_register(0x1, self.PC) # Error Address + self._set_register(0x2, instruction[0]) # Error Type + + self._int(0xFE, 0) + + except ZeroDivisionError as e: + print(e, self.B, self.C) + # Trigger an error interrupt with details + self._set_register(0x0, 0x4) # Error Type + self._set_register(0x1, self.PC) # Error Address + self._set_register(0x2, instruction[0]) # Error Type + + self._int(0xFE, 0) # call error interrupt + + #ValueError, IndexError, OverflowError + + + + +program = [1, 0, 1, 1, 1, 800, 1, 2, 600, 10, 112, 0, 1, 1, 0, 1, 2, 16777215, 2, 5, 1, 10, 246, 0, 3, 1, 5, 5, 1, 238, 5, 0, 239, 1, 3, 0, 9, 0, 3, 18, 10, 114, 0, 11, 0, 0, 11, 1, 0, 11, 2, 0, 11, 3, 0, 1, 0, 8, 1, 1, 0, 6, 2, 238, 6, 3, 239, 10, 129, 0, 12, 3, 0, 12, 2, 0, 12, 1, 0, 12, 0, 0, 17, 18, 0] + + + + + + +# Initialize CPU, load program, and run +cpu = CPU() +cpu.load_program(program) +cpu.run() + + +cpu.state() \ No newline at end of file