Add files via upload

This commit is contained in:
OusmBlueNinja 2024-12-08 20:41:50 -06:00 committed by GitHub
parent 00dfb22a1c
commit 3014c9c3e9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 1503 additions and 0 deletions

656
asm-to-prg.py Normal file
View File

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

0
drive8.bin Normal file
View File

66
gradiant.asm Normal file
View File

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

46
main.asm Normal file
View File

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

735
main.py Normal file
View File

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