Main
This commit is contained in:
parent
77946c4d6f
commit
b88b8bffc9
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
914
asm-to-prg.py
Normal file
914
asm-to-prg.py
Normal file
@ -0,0 +1,914 @@
|
||||
|
||||
filename = "test.asm"
|
||||
|
||||
|
||||
#
|
||||
# Change the filename here to the path of your asm file
|
||||
# then copy the output to 'main.py' and replace the 'program' variable
|
||||
# with the list, then run the 'main.py' file with python 3.11+
|
||||
#
|
||||
# there are 2 example programs, one that demonstrates bitmap mode
|
||||
# and one that demonstrates text mode, with typing useing the bios
|
||||
# interupts.
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
import re
|
||||
from termcolor import colored
|
||||
|
||||
|
||||
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)}")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
import os
|
||||
from termcolor import colored
|
||||
|
||||
|
||||
macro_definitions = {}
|
||||
|
||||
|
||||
def preprocess(lines, filename="main.asm", included_files=None):
|
||||
if included_files is None:
|
||||
included_files = set() # Tracks included files to prevent recursion
|
||||
|
||||
errors = []
|
||||
warnings = []
|
||||
error_flag = False
|
||||
|
||||
# Memory and stack tracking
|
||||
instruction_count = 0
|
||||
memory_limit = 1024 # 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", "ldb", "stb"}
|
||||
label_references = []
|
||||
labels = {}
|
||||
|
||||
# Expand include directives
|
||||
expanded_lines = []
|
||||
for line_number, line in enumerate(lines, start=1):
|
||||
code = line.strip()
|
||||
|
||||
if code.startswith("%include"):
|
||||
# Handle include directives
|
||||
parts = code.split(maxsplit=1)
|
||||
if len(parts) != 2:
|
||||
errors.append((line_number, "Invalid %include syntax", line))
|
||||
error_flag = True
|
||||
continue
|
||||
|
||||
include_file = parts[1].strip("\"")
|
||||
if include_file in included_files:
|
||||
errors.append((line_number, f"Recursive inclusion detected for file '{include_file}'", line))
|
||||
error_flag = True
|
||||
continue
|
||||
|
||||
if not os.path.exists(include_file):
|
||||
errors.append((line_number, f"Included file '{include_file}' not found", line))
|
||||
error_flag = True
|
||||
continue
|
||||
|
||||
try:
|
||||
included_files.add(include_file)
|
||||
with open(include_file, 'r') as f:
|
||||
included_lines = f.readlines()
|
||||
expanded_lines.extend(preprocess(included_lines, filename=include_file, included_files=included_files))
|
||||
except Exception as e:
|
||||
errors.append((line_number, f"Failed to include file '{include_file}': {str(e)}", line))
|
||||
error_flag = True
|
||||
|
||||
elif code.startswith("%define"):
|
||||
# Handle macros
|
||||
parts = code.split(maxsplit=2)
|
||||
if len(parts) != 3:
|
||||
errors.append((line_number, "Invalid %define syntax", line))
|
||||
error_flag = True
|
||||
continue
|
||||
|
||||
macro_name, macro_value = parts[1], parts[2]
|
||||
if macro_name in macro_definitions:
|
||||
errors.append((line_number, f"Macro '{macro_name}' redefined", line))
|
||||
error_flag = True
|
||||
continue
|
||||
|
||||
macro_definitions[macro_name] = macro_value
|
||||
continue # Skip adding %define line to the output
|
||||
|
||||
else:
|
||||
expanded_lines.append(line)
|
||||
|
||||
# First pass: Parse instructions and calculate program length
|
||||
for line_number, line in enumerate(expanded_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 = code.split()
|
||||
instruction = parts[0].lower()
|
||||
if instruction == "db":
|
||||
# Handle string definitions
|
||||
if len(parts) < 2:
|
||||
errors.append((line_number, f"Missing operand for '{instruction}'", line))
|
||||
error_flag = True
|
||||
continue
|
||||
|
||||
string_literal = " ".join(parts[1:]).strip("\"")
|
||||
instruction_count += len(string_literal) + 1 # Include null terminator
|
||||
elif instruction in valid_instructions:
|
||||
if instruction in {"ldw", "mov", "add", "sub", "str", "ldr", "xor", "and", "mul", "div", "ldb", "stb"}:
|
||||
instruction_count += 3
|
||||
elif instruction in {"bne", "beq", "blt"}:
|
||||
instruction_count += 4
|
||||
elif instruction in {"push", "pop", "int", "jmp", "jsr", "ret"}:
|
||||
instruction_count += 3
|
||||
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(expanded_lines, start=1):
|
||||
code = line.split(";")[0].strip()
|
||||
if not code:
|
||||
continue
|
||||
|
||||
# Handle labels
|
||||
if code.endswith(":"):
|
||||
continue
|
||||
|
||||
parts = code.split()
|
||||
instruction = parts[0].lower()
|
||||
operands = parts[1:] if len(parts) > 1 else []
|
||||
|
||||
# Handle db strings
|
||||
if instruction == "db":
|
||||
string_literal = " ".join(operands).strip("\"")
|
||||
if not string_literal:
|
||||
errors.append((line_number, "Empty string literal in 'db'", line))
|
||||
error_flag = True
|
||||
continue
|
||||
|
||||
# Strip commas from operands
|
||||
operands = [op.replace(",", "") for op in operands]
|
||||
# Validate instruction and operands (same as before)
|
||||
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:
|
||||
if str(address).startswith("0x"):
|
||||
mem_address = int(address, 16)
|
||||
else:
|
||||
mem_address = int(address)
|
||||
|
||||
|
||||
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", "ldb", "stb"} 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
|
||||
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"))
|
||||
|
||||
if not error_flag:
|
||||
print(colored(f"{filename}: Done!", "green"))
|
||||
else:
|
||||
exit(1)
|
||||
|
||||
return expanded_lines
|
||||
|
||||
|
||||
lines = preprocess(lines, filename=filename)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
lineNumber = 0
|
||||
|
||||
COMPILE_ERROR = False
|
||||
|
||||
def _ValueError(message):
|
||||
global COMPILE_ERROR
|
||||
COMPILE_ERROR = True
|
||||
print("ValueError: %s on line:" % message, lineNumber)
|
||||
|
||||
def _IndexError(message):
|
||||
global COMPILE_ERROR
|
||||
COMPILE_ERROR = True
|
||||
|
||||
print("IndexError: %s on line:" % message, lineNumber)
|
||||
|
||||
def _InstructionError(message):
|
||||
global COMPILE_ERROR
|
||||
COMPILE_ERROR = True
|
||||
|
||||
print("InstructionError: %s on line:" % message, lineNumber)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def load_include(filename):
|
||||
with open(filename, "r") as f:
|
||||
lines = f.readlines()
|
||||
|
||||
return lines
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
itterrator = 0
|
||||
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.startswith('%'):
|
||||
# Get the command within the current line
|
||||
|
||||
command = ""
|
||||
|
||||
for char in stripped_line:
|
||||
if char == "%":
|
||||
continue
|
||||
|
||||
if char == " ": # Space
|
||||
break
|
||||
|
||||
command += char
|
||||
|
||||
#print(command)
|
||||
|
||||
if command == "define": #! defines are handled in the preprocessor
|
||||
pass
|
||||
|
||||
elif command == "include":
|
||||
# Get value between quotes
|
||||
inQuote = False
|
||||
include_filepath = ""
|
||||
for char in stripped_line:
|
||||
if char == '"':
|
||||
inQuote = not inQuote
|
||||
elif inQuote:
|
||||
include_filepath += char
|
||||
|
||||
if include_filepath == '':
|
||||
continue
|
||||
|
||||
|
||||
|
||||
lines += ["\n"] + load_include(include_filepath)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
|
||||
itterrator+=1
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# 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(",", "") # Remove commas
|
||||
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', 'ldb', 'stb'}: # 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 and not COMPILE_ERROR:
|
||||
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 trailing spaces
|
||||
line = line.replace(",", "") # Remove commas
|
||||
line = line.split(" ") # Split into instruction parts
|
||||
|
||||
if not line[0]:
|
||||
continue
|
||||
|
||||
line[0] = line[0].lower() # Normalize instruction to lowercase
|
||||
|
||||
|
||||
# Regular instruction processing (already present in your code)
|
||||
index = 0
|
||||
for operator in line:
|
||||
if operator in macro_definitions:
|
||||
line[index] = macro_definitions[line[index]]
|
||||
index += 1
|
||||
|
||||
|
||||
|
||||
#print(line)
|
||||
|
||||
#! Code to convert to bytes
|
||||
bytes = []
|
||||
try:
|
||||
|
||||
# Handle 'db' directive for defining strings or raw bytes
|
||||
if line[0] == 'db':
|
||||
if len(line) < 3:
|
||||
_InstructionError("Missing data for 'db' directive")
|
||||
raw_data = " ".join(line[1:]).strip("'\"")
|
||||
bytes = [ord(char) for char in raw_data] # Convert characters to ASCII values
|
||||
bytes.append(0) # Null terminator
|
||||
outputBytes += bytes
|
||||
current_byte_offset += len(bytes)
|
||||
continue
|
||||
|
||||
# Add 'ldb' instruction handling
|
||||
elif line[0] == 'ldb': # Load byte to register
|
||||
|
||||
bytes.append(0x15) # Assuming 0x15 for 'ldb'
|
||||
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] == 'stb': # Load byte to register
|
||||
|
||||
bytes.append(0x16) # Assuming 0x15 for 'ldb'
|
||||
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] == '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)
|
||||
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:
|
||||
print(line)
|
||||
_InstructionError("Unknown Instruction")
|
||||
|
||||
|
||||
|
||||
except IndexError:
|
||||
_IndexError("Maformed Instruction")
|
||||
except ValueError:
|
||||
print(line)
|
||||
_ValueError("Unknown Error")
|
||||
|
||||
current_byte_offset += len(bytes)
|
||||
|
||||
|
||||
lineNumber+=1
|
||||
outputBytes += bytes
|
||||
|
||||
if not COMPILE_ERROR:
|
||||
with open("program.py", "w") as f:
|
||||
bytecode = []
|
||||
for _, y in enumerate(outputBytes):
|
||||
bytecode.append(str(y))
|
||||
|
||||
prg = "program = [" + ",".join(bytecode) + "]"
|
||||
f.write(prg)
|
||||
else:
|
||||
print(f"Compilation Error")
|
||||
print(f"This is most likely due to invalid macro, please check your code for typos ")
|
||||
|
||||
|
||||
|
||||
|
112
asm/example.asm
Normal file
112
asm/example.asm
Normal file
@ -0,0 +1,112 @@
|
||||
;; Interupt list
|
||||
|
||||
; 0x00 -> print register a as char
|
||||
; 0x01 -> print register a as int
|
||||
;
|
||||
; 0xFF -> Halt
|
||||
; 0xFE -> Error Interupt
|
||||
; 0xF6 -> put keycode of currently pressed key in A register
|
||||
; and but a value (bool)(0 or 1) into b register to
|
||||
; indicate whether it is the first time this key has been
|
||||
; pressed.
|
||||
;
|
||||
;
|
||||
; 0x70 -> Init graphics,
|
||||
; (A) mode
|
||||
; (B) X resolution
|
||||
; (C) Y Resulution
|
||||
; 0x71 -> Set Pixel, (bitmap mode)
|
||||
; (A) Color (0x000000 to 0xFFFFFF)
|
||||
; (B) x position
|
||||
; (C) y position
|
||||
; 0x72 -> Set Char, (Text Mode)
|
||||
; (A) Character Code
|
||||
; (B) Cursor Position
|
||||
; (C) Color (0x000000 to 0xFFFFFF)
|
||||
;
|
||||
;
|
||||
; ! ALLL FILESYSTEM DRIVE NUMBER ARE LOCAL FILES !
|
||||
; drive( drive number (0 to 9)).bin
|
||||
; "drive8.bin"
|
||||
;
|
||||
; drive number -> ( 0 to 9 )
|
||||
; sector number -> ( 0 to 15 )
|
||||
; byte offset -> ( 0 to 254 )
|
||||
|
||||
;
|
||||
; 0x80 -> read byte from disk
|
||||
; (A) drive number
|
||||
; (B) sector number
|
||||
; (C) byte offset
|
||||
; moves output to A register
|
||||
; 0x81 -> write byte from disk
|
||||
; (A) drive number
|
||||
; (B) sector number
|
||||
; (C) byte offset
|
||||
; (D) byte to write
|
||||
|
||||
; Instructions
|
||||
;
|
||||
; add ; adds 2 registers together acumulating in the first register
|
||||
; sub ; same as add ; except subtracts
|
||||
; ldw ; load word, load a immediate to a register
|
||||
; ldr ; load a byte from a adderss to a register
|
||||
; ldb ; load a byte from an adress (address stored in a register) to another register
|
||||
; str ; stores from a register to an address
|
||||
; jsr ; jump to a label
|
||||
; push/pop ; stack stuff, takes 1 register
|
||||
; ret ; return (pops off of stack)
|
||||
; mov ; move a value from one register to another
|
||||
; mul ; multiply takes 2 registers
|
||||
; div ; divide takes 2 registers
|
||||
; bne/beq ; branch not equal example: b(n)e a, b, Label1
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
;
|
||||
|
||||
; Campialer option
|
||||
; %include include anotehr asm file
|
||||
; %define all accurances of the define will be raplaced
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
main:
|
||||
ldw a, 0xFF ; load 255 to A register, Unused
|
||||
str a, 0xFF ; will cause an error because its writing to program memory
|
||||
; You will have to make the address bigger than the program
|
||||
int 0x00 ; interupt to print a register to terminal as a char (debug)
|
||||
|
||||
ldw a, 0 ; will be the graphics mode
|
||||
str a, 0xF0 ; copy A register to memory address
|
||||
|
||||
ldw a, 800 ; screen width
|
||||
str a, 0xF1 ; copy A register to memory address
|
||||
|
||||
ldw a, 600 ; screen height
|
||||
str a, 0xF2 ; copy A register to memory address
|
||||
jsr init_graphics_mode
|
||||
|
||||
|
||||
|
||||
init_graphics_mode:
|
||||
push a
|
||||
push b
|
||||
push c
|
||||
|
||||
ldr a, 0xF0 ; copy data from memory address to register 'A'
|
||||
ldr b, 0xF1 ; copy data from memory address to register 'B'
|
||||
ldr c, 0xF2 ; copy data from memory address to register 'C'
|
||||
|
||||
int 0x70 ; all interupts in 0x70 - 0x7F are for graphics,
|
||||
; only 0x70 to 0x72 are implemented
|
||||
|
||||
pop c
|
||||
pop b
|
||||
pop a
|
||||
|
||||
ret
|
66
asm/gradiant.asm
Normal file
66
asm/gradiant.asm
Normal 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
|
84
asm/try_to_fix_me.asm
Normal file
84
asm/try_to_fix_me.asm
Normal file
@ -0,0 +1,84 @@
|
||||
; 0x00 -> print register a as char
|
||||
; 0x01 -> print register a as int
|
||||
;
|
||||
; 0xFF -> Halt
|
||||
; 0xFE -> Error Interupt
|
||||
; 0xF6 -> put keycode of currently pressed key in A register
|
||||
; and but a value (bool)(0 or 1) into b register to
|
||||
; indicate whether it is the first time this key has been
|
||||
; pressed.
|
||||
;
|
||||
;
|
||||
; 0x70 -> Init graphics,
|
||||
; (A) mode
|
||||
; (B) X resolution
|
||||
; (C) Y Resulution
|
||||
; 0x71 -> Set Pixel, (bitmap mode)
|
||||
; (A) Color (0x000000 to 0xFFFFFF)
|
||||
; (B) x position
|
||||
; (C) y position
|
||||
; 0x72 -> Set Char, (Text Mode)
|
||||
; (A) Character Code
|
||||
; (B) Cursor Position
|
||||
; (C) Color (0x000000 to 0xFFFFFF)
|
||||
;
|
||||
;
|
||||
; ! ALLL FILESYSTEM DRIVE NUMBER ARE LOCAL FILES !
|
||||
; drive( drive number (0 to 9)).bin
|
||||
; "drive8.bin"
|
||||
;
|
||||
; drive number -> ( 0 to 9 )
|
||||
; sector number -> ( 0 to 15 )
|
||||
; byte offset -> ( 0 to 254 )
|
||||
|
||||
;
|
||||
; 0x80 -> read byte from disk
|
||||
; (A) drive number
|
||||
; (B) sector number
|
||||
; (C) byte offset
|
||||
; moves output to A register
|
||||
; 0x81 -> write byte from disk
|
||||
; (A) drive number
|
||||
; (B) sector number
|
||||
; (C) byte offset
|
||||
; (D) byte to write
|
||||
|
||||
|
||||
|
||||
main:
|
||||
ldw a, 0xFF ; load 255 to A register, Unused
|
||||
str a, 0xEF ; will cause an error because its writing to program memory
|
||||
; You will have to make the address bigger than the program
|
||||
int 0x00 ; interupt to print a register to terminal as a char (debug)
|
||||
|
||||
ldw a, 0 ; will be the graphics mode
|
||||
str a, 0xF0 ; copy A register to memory address
|
||||
|
||||
ldw a, 800 ; screen width
|
||||
str a, 0xF1 ; copy A register to memory address
|
||||
|
||||
ldw a, 600 ; screen height
|
||||
str a, 0xF2 ; copy A register to memory address
|
||||
jsr init_graphics_mode
|
||||
|
||||
|
||||
|
||||
init_graphics_mode:
|
||||
push a
|
||||
push b
|
||||
push c
|
||||
|
||||
ldw a, 0xFF ; This is never used
|
||||
|
||||
ldr a, 0xF0 ; copy data from memory address to register 'A'
|
||||
ldr b, 0xF1 ; copy data from memory address to register 'B'
|
||||
ldr c, 0xF2 ; copy data from memory address to register 'C'
|
||||
|
||||
int 0x70 ; all interupts in 0x70 - 0x7F are for graphics,
|
||||
; only 0x70 to 0x72 are implemented
|
||||
|
||||
pop c
|
||||
pop b
|
||||
pop a
|
||||
|
||||
ret
|
548
c-to-asm.py
Normal file
548
c-to-asm.py
Normal file
@ -0,0 +1,548 @@
|
||||
import re
|
||||
import os
|
||||
from termcolor import colored
|
||||
|
||||
|
||||
class Variable:
|
||||
def __init__(self, name, address, var_type="int"):
|
||||
self.name = name
|
||||
self.address = address
|
||||
self.type = var_type
|
||||
|
||||
class Compiler:
|
||||
def __init__(self):
|
||||
# 1024 bytes total: 0x000 to 0x3FF
|
||||
self.data_ptr = 0x400
|
||||
self.variables = {}
|
||||
self.struct_definitions = {}
|
||||
self.in_struct_def = False
|
||||
self.current_struct_name = None
|
||||
self.current_struct_fields = []
|
||||
self.defines = {} # For #define macros
|
||||
self.typedefs = {} # For typedef
|
||||
self.label_counter = 0
|
||||
self.block_stack = [] # For if/while blocks
|
||||
|
||||
def new_label(self, prefix):
|
||||
lbl = f"{prefix}{self.label_counter}"
|
||||
self.label_counter += 1
|
||||
return lbl
|
||||
|
||||
def preprocess(self, filename):
|
||||
lines = self._read_file_recursive(filename)
|
||||
processed_lines = self._apply_defines(lines)
|
||||
return processed_lines
|
||||
|
||||
def _read_file_recursive(self, filename, included_files=None):
|
||||
if included_files is None:
|
||||
included_files = set()
|
||||
|
||||
if filename in included_files:
|
||||
# Prevent infinite recursion on includes
|
||||
return []
|
||||
|
||||
included_files.add(filename)
|
||||
|
||||
result_lines = []
|
||||
try:
|
||||
with open(filename, "r") as f:
|
||||
for line in f:
|
||||
line_stripped = line.strip()
|
||||
|
||||
# #include "file"
|
||||
inc_match = re.match(r'#include\s+"([^"]+)"', line_stripped)
|
||||
if inc_match:
|
||||
inc_file = inc_match.group(1)
|
||||
included_content = self._read_file_recursive(inc_file, included_files)
|
||||
result_lines.extend(included_content)
|
||||
continue
|
||||
|
||||
# #define KEY VALUE
|
||||
def_match = re.match(r'#define\s+([a-zA-Z_]\w*)\s+(.*)', line_stripped)
|
||||
if def_match:
|
||||
key = def_match.group(1)
|
||||
value = def_match.group(2)
|
||||
self.defines[key] = value
|
||||
continue
|
||||
|
||||
# typedef oldtype newtype;
|
||||
tmatch = re.match(r'typedef\s+([a-zA-Z_]\w*)\s+([a-zA-Z_]\w*)\s*;', line_stripped)
|
||||
if tmatch:
|
||||
oldt = tmatch.group(1)
|
||||
newt = tmatch.group(2)
|
||||
# Resolve oldt if it's also a typedef
|
||||
oldt = self.apply_typedef(oldt)
|
||||
self.typedefs[newt] = oldt
|
||||
continue
|
||||
|
||||
result_lines.append(line)
|
||||
except FileNotFoundError as e:
|
||||
print(colored(f"{filename}:0: error: {e}", "red"))
|
||||
|
||||
|
||||
return result_lines
|
||||
|
||||
def _apply_defines(self, lines):
|
||||
|
||||
token_pattern = re.compile(r'([A-Za-z0-9_]+)')
|
||||
|
||||
processed = []
|
||||
for line in lines:
|
||||
parts = token_pattern.split(line)
|
||||
# parts: tokens and separators
|
||||
for i, part in enumerate(parts):
|
||||
if part in self.defines:
|
||||
print(f"Replaced {part} with {self.defines[part]}")
|
||||
part = self.defines[part]
|
||||
parts[i] = part
|
||||
new_line = "".join(parts)
|
||||
processed.append(new_line)
|
||||
return processed
|
||||
|
||||
def apply_typedef(self, t):
|
||||
if t in self.typedefs:
|
||||
return self.typedefs[t]
|
||||
return t
|
||||
|
||||
def allocate_bytes(self, count):
|
||||
start_addr = self.data_ptr - (count - 1)
|
||||
if start_addr < 0x000:
|
||||
raise Exception("Out of memory!")
|
||||
self.data_ptr = start_addr - 1
|
||||
return start_addr
|
||||
|
||||
def allocate_var(self, name, var_type="int"):
|
||||
var_type = self.apply_typedef(var_type)
|
||||
if name in self.variables:
|
||||
return self.variables[name]
|
||||
|
||||
if var_type.startswith("struct:"):
|
||||
sname = var_type.split(":")[1]
|
||||
fields = self.struct_definitions[sname]
|
||||
length = len(fields) # each 1 byte
|
||||
start_addr = self.allocate_bytes(length)
|
||||
var = Variable(name, start_addr, var_type)
|
||||
self.variables[name] = var
|
||||
return var
|
||||
else:
|
||||
start_addr = self.allocate_bytes(1)
|
||||
var = Variable(name, start_addr, var_type)
|
||||
self.variables[name] = var
|
||||
return var
|
||||
|
||||
def allocate_array(self, name, length, var_type="int"):
|
||||
var_type = self.apply_typedef(var_type)
|
||||
arr_start = self.allocate_bytes(length)
|
||||
var_addr = self.allocate_bytes(1)
|
||||
var = Variable(name, var_addr, "array")
|
||||
self.variables[name] = var
|
||||
return var, arr_start
|
||||
|
||||
def store_string(self, string_value):
|
||||
string_value = string_value.replace('\\n', '\n')
|
||||
length = len(string_value) + 1
|
||||
start_addr = self.allocate_bytes(length)
|
||||
asm = []
|
||||
current_addr = start_addr
|
||||
for ch in string_value:
|
||||
ascii_val = ord(ch)
|
||||
asm.append(f"ldw a, {ascii_val}")
|
||||
asm.append(f"str a, 0x{current_addr:X}")
|
||||
current_addr += 1
|
||||
asm.append("ldw a, 0")
|
||||
asm.append(f"str a, 0x{current_addr:X}")
|
||||
return asm, start_addr
|
||||
|
||||
def get_struct_field_offset(self, struct_type, field_name):
|
||||
sname = struct_type.split(":")[1]
|
||||
fields = self.struct_definitions[sname]
|
||||
for i, (fname, ftype) in enumerate(fields):
|
||||
if fname == field_name:
|
||||
return i
|
||||
raise Exception(f"Field {field_name} not found in {struct_type}")
|
||||
|
||||
def parse_condition(self, cond_str):
|
||||
# cond_str like "a == b" or "a != b"
|
||||
m = re.match(r'([a-zA-Z_]\w*)\s*(==|!=)\s*([a-zA-Z_]\w*)', cond_str.strip())
|
||||
if not m:
|
||||
raise Exception("Unsupported condition: " + cond_str)
|
||||
var1, op, var2 = m.groups()
|
||||
return var1, op, var2
|
||||
|
||||
def compile_condition(self, var1, op, var2):
|
||||
asm = []
|
||||
v1 = self.allocate_var(var1)
|
||||
v2 = self.allocate_var(var2)
|
||||
asm.append(f"ldr a, 0x{v1.address:X}")
|
||||
asm.append(f"ldr b, 0x{v2.address:X}")
|
||||
# a = a - b
|
||||
asm.append("sub a, b")
|
||||
return asm, op
|
||||
|
||||
def extract_comment(self, line):
|
||||
comment_index = line.find('//')
|
||||
if comment_index != -1:
|
||||
code_part = line[:comment_index]
|
||||
comment_part = line[comment_index+2:].strip()
|
||||
return code_part, comment_part
|
||||
return line, None
|
||||
|
||||
def compile_line(self, code_part):
|
||||
line = code_part.strip()
|
||||
asm = []
|
||||
|
||||
if self.in_struct_def:
|
||||
if line.startswith("};"):
|
||||
self.struct_definitions[self.current_struct_name] = self.current_struct_fields
|
||||
self.in_struct_def = False
|
||||
self.current_struct_name = None
|
||||
self.current_struct_fields = []
|
||||
return asm
|
||||
mfield = re.match(r'int\s+([a-zA-Z_]\w*)\s*;', line)
|
||||
if mfield:
|
||||
fname = mfield.group(1)
|
||||
ftype = "int"
|
||||
self.current_struct_fields.append((fname, ftype))
|
||||
return asm
|
||||
|
||||
# struct definition start
|
||||
msd = re.match(r'struct\s+([a-zA-Z_]\w*)\s*\{', line)
|
||||
if msd:
|
||||
self.in_struct_def = True
|
||||
self.current_struct_name = msd.group(1)
|
||||
self.current_struct_fields = []
|
||||
return asm
|
||||
|
||||
# struct var declaration
|
||||
msv = re.match(r'struct\s+([a-zA-Z_]\w*)\s+([a-zA-Z_]\w*)\s*;', line)
|
||||
if msv:
|
||||
sname, varname = msv.groups()
|
||||
var_type = "struct:" + sname
|
||||
self.allocate_var(varname, var_type)
|
||||
return asm
|
||||
|
||||
# if statement
|
||||
mif = re.match(r'if\s*\(([^)]+)\)\s*\{', line)
|
||||
if mif:
|
||||
cond_str = mif.group(1)
|
||||
var1, op, var2 = self.parse_condition(cond_str)
|
||||
end_label = self.new_label("endif")
|
||||
cond_code, cmp_op = self.compile_condition(var1, op, var2)
|
||||
asm.extend(cond_code)
|
||||
# if '==': jump if not zero a != 0
|
||||
# if '!=': jump if zero a == 0
|
||||
if cmp_op == '==':
|
||||
asm.append("bne a, 0, " + end_label)
|
||||
else:
|
||||
asm.append("beq a, 0, " + end_label)
|
||||
self.block_stack.append(('if', end_label))
|
||||
return asm
|
||||
|
||||
# while statement
|
||||
mwhile = re.match(r'while\s*\(([^)]+)\)\s*\{', line)
|
||||
if mwhile:
|
||||
cond_str = mwhile.group(1)
|
||||
var1, op, var2 = self.parse_condition(cond_str)
|
||||
start_label = self.new_label("whilestart")
|
||||
end_label = self.new_label("whileend")
|
||||
asm.append(start_label + ":")
|
||||
cond_code, cmp_op = self.compile_condition(var1, op, var2)
|
||||
asm.extend(cond_code)
|
||||
if cmp_op == '==':
|
||||
asm.append("bne a, 0, " + end_label)
|
||||
else:
|
||||
asm.append("beq a, 0, " + end_label)
|
||||
self.block_stack.append(('while', start_label, end_label))
|
||||
return asm
|
||||
|
||||
# end of block
|
||||
if line == "}":
|
||||
if not self.block_stack:
|
||||
return asm
|
||||
blk = self.block_stack.pop()
|
||||
if blk[0] == 'if':
|
||||
end_label = blk[1]
|
||||
asm.append(end_label + ":")
|
||||
elif blk[0] == 'while':
|
||||
start_label = blk[1]
|
||||
end_label = blk[2]
|
||||
# jump back to start
|
||||
asm.append(f"jmp {start_label}")
|
||||
asm.append(end_label + ":")
|
||||
return asm
|
||||
|
||||
# p.x = number;
|
||||
m = re.match(r'([a-zA-Z_]\w*)\.([a-zA-Z_]\w*)\s*=\s*(\d+)\s*;', line)
|
||||
if m:
|
||||
varname, fieldname, value = m.groups()
|
||||
value = int(value)
|
||||
v = self.allocate_var(varname)
|
||||
offset = self.get_struct_field_offset(v.type, fieldname)
|
||||
asm.append(f"ldr a, 0x{v.address:X}")
|
||||
if offset != 0:
|
||||
asm.append(f"ldw b, {offset}")
|
||||
asm.append("add a, b")
|
||||
asm.append(f"ldw c, {value}")
|
||||
asm.append("stb c, a")
|
||||
return asm
|
||||
|
||||
# p.x = var + number;
|
||||
m = re.match(r'([a-zA-Z_]\w*)\.([a-zA-Z_]\w*)\s*=\s*([a-zA-Z_]\w*)\s*\+\s*(\d+)\s*;', line)
|
||||
if m:
|
||||
varname, fieldname, srcvar, number = m.groups()
|
||||
number = int(number)
|
||||
v = self.allocate_var(varname)
|
||||
offset = self.get_struct_field_offset(v.type, fieldname)
|
||||
asm.append(f"ldr a, 0x{v.address:X}")
|
||||
if offset != 0:
|
||||
asm.append(f"ldw b, {offset}")
|
||||
asm.append("add a, b")
|
||||
v2 = self.allocate_var(srcvar)
|
||||
asm.append(f"ldr c, 0x{v2.address:X}")
|
||||
asm.append(f"ldw d, {number}")
|
||||
asm.append("add c, d")
|
||||
asm.append("stb c, a")
|
||||
return asm
|
||||
|
||||
# p.x = srcvar;
|
||||
m = re.match(r'([a-zA-Z_]\w*)\.([a-zA-Z_]\w*)\s*=\s*([a-zA-Z_]\w*)\s*;', line)
|
||||
if m:
|
||||
varname, fieldname, srcvar = m.groups()
|
||||
v = self.allocate_var(varname)
|
||||
offset = self.get_struct_field_offset(v.type, fieldname)
|
||||
asm.append(f"ldr a, 0x{v.address:X}")
|
||||
if offset != 0:
|
||||
asm.append(f"ldw b, {offset}")
|
||||
asm.append("add a, b")
|
||||
v2 = self.allocate_var(srcvar)
|
||||
asm.append(f"ldr c, 0x{v2.address:X}")
|
||||
asm.append("stb c, a")
|
||||
return asm
|
||||
|
||||
# x = p.x;
|
||||
m = re.match(r'([a-zA-Z_]\w*)\s*=\s*([a-zA-Z_]\w*)\.([a-zA-Z_]\w*)\s*;', line)
|
||||
if m:
|
||||
dst, varname, fieldname = m.groups()
|
||||
v = self.allocate_var(varname)
|
||||
offset = self.get_struct_field_offset(v.type, fieldname)
|
||||
vd = self.allocate_var(dst)
|
||||
asm.append(f"ldr a, 0x{v.address:X}")
|
||||
if offset != 0:
|
||||
asm.append(f"ldw b, {offset}")
|
||||
asm.append("add a, b")
|
||||
asm.append("ldb c, a")
|
||||
asm.append(f"str c, 0x{vd.address:X}")
|
||||
return asm
|
||||
|
||||
# print_int(p.x);
|
||||
m = re.match(r'print_int\(([a-zA-Z_]\w*)\.([a-zA-Z_]\w*)\)\s*;', line)
|
||||
if m:
|
||||
varname, fieldname = m.groups()
|
||||
v = self.allocate_var(varname)
|
||||
offset = self.get_struct_field_offset(v.type, fieldname)
|
||||
asm.append(f"ldr a, 0x{v.address:X}")
|
||||
if offset != 0:
|
||||
asm.append(f"ldw b, {offset}")
|
||||
asm.append("add a, b")
|
||||
asm.append("ldb a, a")
|
||||
asm.append("int 0x01")
|
||||
return asm
|
||||
|
||||
# int arr[10];
|
||||
m = re.match(r'int\s+([a-zA-Z_]\w*)\[(\d+)\]\s*;', line)
|
||||
if m:
|
||||
varname = m.group(1)
|
||||
length = int(m.group(2))
|
||||
arr_var, start_addr = self.allocate_array(varname, length)
|
||||
asm.append(f"ldw a, 0x{start_addr:X}")
|
||||
asm.append(f"str a, 0x{arr_var.address:X}")
|
||||
return asm
|
||||
|
||||
# int x = number;
|
||||
m = re.match(r'int\s+([a-zA-Z_]\w*)\s*=\s*(\d+)\s*;', line)
|
||||
if m:
|
||||
varname = m.group(1)
|
||||
value = int(m.group(2))
|
||||
var = self.allocate_var(varname, "int")
|
||||
asm.append(f"ldw a, {value}")
|
||||
asm.append(f"str a, 0x{var.address:X}")
|
||||
return asm
|
||||
|
||||
# int y = x + number;
|
||||
m = re.match(r'int\s+([a-zA-Z_]\w*)\s*=\s*([a-zA-Z_]\w*)\s*\+\s*(\d+)\s*;', line)
|
||||
if m:
|
||||
varname, var2, number = m.groups()
|
||||
number = int(number)
|
||||
v1 = self.allocate_var(varname, "int")
|
||||
v2 = self.allocate_var(var2, "int")
|
||||
asm.append(f"ldr a, 0x{v2.address:X}")
|
||||
asm.append(f"ldw b, {number}")
|
||||
asm.append("add a, b")
|
||||
asm.append(f"str a, 0x{v1.address:X}")
|
||||
return asm
|
||||
|
||||
# char *msg = "Hello\n";
|
||||
m = re.match(r'char\s*\*\s*([a-zA-Z_]\w*)\s*=\s*"([^"]*)"\s*;', line)
|
||||
if m:
|
||||
varname, string_val = m.groups()
|
||||
v = self.allocate_var(varname, "char*")
|
||||
code, start_addr = self.store_string(string_val)
|
||||
asm.extend(code)
|
||||
asm.append(f"ldw a, 0x{start_addr:X}")
|
||||
asm.append(f"str a, 0x{v.address:X}")
|
||||
return asm
|
||||
|
||||
# var = number;
|
||||
m = re.match(r'([a-zA-Z_]\w*)\s*=\s*(\d+)\s*;', line)
|
||||
if m:
|
||||
varname, value = m.groups()
|
||||
value = int(value)
|
||||
v = self.allocate_var(varname, "int")
|
||||
asm.append(f"ldw a, {value}")
|
||||
asm.append(f"str a, 0x{v.address:X}")
|
||||
return asm
|
||||
|
||||
# var = var2 + number;
|
||||
m = re.match(r'([a-zA-Z_]\w*)\s*=\s*([a-zA-Z_]\w*)\s*\+\s*(\d+)\s*;', line)
|
||||
if m:
|
||||
varname, var2, number = m.groups()
|
||||
number = int(number)
|
||||
v1 = self.allocate_var(varname, "int")
|
||||
v2 = self.allocate_var(var2, "int")
|
||||
asm.append(f"ldr a, 0x{v2.address:X}")
|
||||
asm.append(f"ldw b, {number}")
|
||||
asm.append("add a, b")
|
||||
asm.append(f"str a, 0x{v1.address:X}")
|
||||
return asm
|
||||
|
||||
# var[index] = number;
|
||||
m = re.match(r'([a-zA-Z_]\w*)\[(\d+)\]\s*=\s*(\d+)\s*;', line)
|
||||
if m:
|
||||
arr, index, value = m.groups()
|
||||
index = int(index)
|
||||
value = int(value)
|
||||
arr_var = self.allocate_var(arr)
|
||||
asm.append(f"ldr a, 0x{arr_var.address:X}")
|
||||
asm.append(f"ldw b, {index}")
|
||||
asm.append("add a, b")
|
||||
asm.append(f"ldw c, {value}")
|
||||
asm.append("stb c, a")
|
||||
return asm
|
||||
|
||||
# x = arr[index];
|
||||
m = re.match(r'([a-zA-Z_]\w*)\s*=\s*([a-zA-Z_]\w*)\[(\d+)\]\s*;', line)
|
||||
if m:
|
||||
varname, arr, index = m.groups()
|
||||
index = int(index)
|
||||
v = self.allocate_var(varname, "int")
|
||||
arr_var = self.allocate_var(arr)
|
||||
asm.append(f"ldr a, 0x{arr_var.address:X}")
|
||||
asm.append(f"ldw b, {index}")
|
||||
asm.append("add a, b")
|
||||
asm.append("ldb d, a")
|
||||
asm.append(f"str d, 0x{v.address:X}")
|
||||
return asm
|
||||
|
||||
# print_char(var);
|
||||
m = re.match(r'print_char\(([a-zA-Z_]\w*)\)\s*;', line)
|
||||
if m:
|
||||
varname = m.group(1)
|
||||
v = self.allocate_var(varname)
|
||||
asm.append(f"ldr a, 0x{v.address:X}")
|
||||
asm.append("int 0x00")
|
||||
return asm
|
||||
|
||||
# print_char(arr[index]);
|
||||
m = re.match(r'print_char\(([a-zA-Z_]\w*)\[(\d+)\]\)\s*;', line)
|
||||
if m:
|
||||
arr, index = m.groups()
|
||||
index = int(index)
|
||||
arr_var = self.allocate_var(arr)
|
||||
asm.append(f"ldr a, 0x{arr_var.address:X}")
|
||||
asm.append(f"ldw b, {index}")
|
||||
asm.append("add a, b")
|
||||
asm.append("ldb a, a")
|
||||
asm.append("int 0x00")
|
||||
return asm
|
||||
|
||||
# print_int(var);
|
||||
m = re.match(r'print_int\(([a-zA-Z_]\w*)\)\s*;', line)
|
||||
if m:
|
||||
varname = m.group(1)
|
||||
v = self.allocate_var(varname)
|
||||
asm.append(f"ldr a, 0x{v.address:X}")
|
||||
asm.append("int 0x01")
|
||||
return asm
|
||||
|
||||
# print_string(var);
|
||||
m = re.match(r'print_string\(([a-zA-Z_]\w*)\)\s*;', line)
|
||||
if m:
|
||||
varname = m.group(1)
|
||||
v = self.allocate_var(varname, "char*")
|
||||
asm.append("ldw d, 0")
|
||||
asm.append(f"ldr b, 0x{v.address:X}")
|
||||
asm.append("ldw c, 1")
|
||||
asm.append("string_loop:")
|
||||
asm.append("ldb a, b")
|
||||
asm.append("beq a, d, string_end")
|
||||
asm.append("int 0x00")
|
||||
asm.append("add b, c")
|
||||
asm.append("jmp string_loop")
|
||||
asm.append("string_end:")
|
||||
return asm
|
||||
|
||||
# return number;
|
||||
m = re.match(r'return\s+(\d+)\s*;', line)
|
||||
if m:
|
||||
asm.append("int 0xFF")
|
||||
return asm
|
||||
|
||||
# Unrecognized line or empty
|
||||
return asm
|
||||
|
||||
def compile_c(self, c_code):
|
||||
# First, parse everything to detect structs and typedef done in preprocess
|
||||
all_lines = c_code.split('\n')
|
||||
# struct definitions might appear outside main
|
||||
for cline in all_lines:
|
||||
self.compile_line(cline)
|
||||
|
||||
# Extract lines inside main
|
||||
lines = []
|
||||
in_main = False
|
||||
for cline in all_lines:
|
||||
cline = cline.rstrip()
|
||||
if 'int main(' in cline:
|
||||
in_main = True
|
||||
continue
|
||||
if in_main:
|
||||
if cline.startswith('}'):
|
||||
in_main = False
|
||||
break
|
||||
lines.append(cline)
|
||||
|
||||
asm = ["main:"]
|
||||
for line in lines:
|
||||
code_part, comment_part = self.extract_comment(line)
|
||||
instructions = self.compile_line(code_part)
|
||||
if instructions:
|
||||
for i, instr in enumerate(instructions):
|
||||
if i == 0 and comment_part:
|
||||
asm.append(f" {instr} ; {comment_part}")
|
||||
else:
|
||||
asm.append(f" {instr}")
|
||||
else:
|
||||
if comment_part:
|
||||
asm.append(f" ; {comment_part}")
|
||||
|
||||
return asm
|
||||
|
||||
if __name__ == "__main__":
|
||||
compiler = Compiler()
|
||||
preprocessed_lines = compiler.preprocess("main.c")
|
||||
c_code = "\n".join(preprocessed_lines)
|
||||
asm_code = compiler.compile_c(c_code)
|
||||
|
||||
with open("test.asm", "w") as out:
|
||||
for line in asm_code:
|
||||
out.write(line + "\n")
|
BIN
drive8.bin
Normal file
BIN
drive8.bin
Normal file
Binary file not shown.
35
main.asm
Normal file
35
main.asm
Normal file
@ -0,0 +1,35 @@
|
||||
; Initialize text mode
|
||||
|
||||
|
||||
main:
|
||||
ldw a, 0 ; Mode: 1 for text mode
|
||||
ldw b, 800 ; Horizontal resolution
|
||||
ldw c, 600 ; Vertical resolution
|
||||
int 0x70 ; Initialize display
|
||||
jsr test
|
||||
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)
|
||||
jmp main_loop ; Jump back to the main loop
|
||||
|
||||
|
||||
|
||||
%include "std.asm"
|
12
main.c
Normal file
12
main.c
Normal file
@ -0,0 +1,12 @@
|
||||
int main()
|
||||
{
|
||||
|
||||
char *msg = "Welcome!\n";
|
||||
print_string(msg);
|
||||
|
||||
while (1) // Not implemented
|
||||
{
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
769
main.py
Normal file
769
main.py
Normal file
@ -0,0 +1,769 @@
|
||||
import pygame, time, os
|
||||
|
||||
class CPU:
|
||||
def __init__(self, memory_size=1024):
|
||||
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), 32): # 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(32):
|
||||
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.stack, self.SP)
|
||||
"""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: # jsr
|
||||
value, opt = instruction[1], instruction[2]
|
||||
self._jsr(value, opt)
|
||||
elif opcode == 0x0E: # ret
|
||||
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)
|
||||
|
||||
elif opcode == 0x15: # beq (Branch if Equal)
|
||||
reg, reg2 = instruction[1], instruction[2]
|
||||
self._ldb(reg, reg2)
|
||||
|
||||
elif opcode == 0x16: # beq (Branch if Equal)
|
||||
reg, reg2 = instruction[1], instruction[2]
|
||||
self._stb(reg, reg2)
|
||||
|
||||
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 _ldb(self, reg, reg2):
|
||||
addr = self._get_register(reg2)
|
||||
if addr < 0 or addr >= len(self.memory):
|
||||
raise ValueError("Invalid memory address.")
|
||||
value = self.memory[addr]
|
||||
self._set_register(reg, value)
|
||||
|
||||
def _stb(self, reg, reg2):
|
||||
addr = self._get_register(reg2)
|
||||
value = self._get_register(reg)
|
||||
|
||||
if addr < 0 or addr >= len(self.memory):
|
||||
raise ValueError("Invalid memory address.")
|
||||
self.memory[addr] = value
|
||||
#self._set_register(reg, value)
|
||||
|
||||
|
||||
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")
|
||||
print(self.PC, self.PC+3)
|
||||
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):
|
||||
#print(self.stack, self.SP)
|
||||
|
||||
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
|
||||
|
||||
print(mode, x, y)
|
||||
|
||||
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: # write 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), end="")
|
||||
def _print_register_int(self):
|
||||
|
||||
char = self._get_register(0x0)
|
||||
print(char, end="")
|
||||
|
||||
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
|
||||
|
||||
|
||||
from program import program
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Initialize CPU, load program, and run
|
||||
cpu = CPU()
|
||||
cpu.load_program(program)
|
||||
cpu.run()
|
||||
|
||||
|
||||
cpu.state()
|
1
program.py
Normal file
1
program.py
Normal file
@ -0,0 +1 @@
|
||||
program = [1,0,87,5,0,1004,1,0,101,5,0,1005,1,0,108,5,0,1006,1,0,99,5,0,1007,1,0,111,5,0,1008,1,0,109,5,0,1009,1,0,101,5,0,1010,1,0,33,5,0,1011,1,0,10,5,0,1012,1,0,0,5,0,1013,1,0,1004,5,0,1024,1,3,0,6,1,1024,1,2,1,21,0,1,9,0,3,91,10,0,0,3,1,2,17,75,0,10,255,0]
|
18
std.asm
Normal file
18
std.asm
Normal file
@ -0,0 +1,18 @@
|
||||
; Simplified Standard Library for CPU Emulator
|
||||
; No sections, strings, or floating-point numbers
|
||||
|
||||
; Mode definitions
|
||||
%define BITMAP_MODE 0
|
||||
%define TEXT_MODE 1
|
||||
|
||||
; Boolean values
|
||||
%define null 0
|
||||
%define true 1
|
||||
%define false 0
|
||||
|
||||
|
||||
test:
|
||||
ldw b, 0x5
|
||||
ldb a, b
|
||||
int 0x1
|
||||
ret
|
19
std.h
Normal file
19
std.h
Normal file
@ -0,0 +1,19 @@
|
||||
#define true 1
|
||||
#define false 0
|
||||
#define TEST 256
|
||||
|
||||
struct vec2 {
|
||||
int x;
|
||||
int y;
|
||||
};
|
||||
|
||||
struct vec3 {
|
||||
int x;
|
||||
int y;
|
||||
int z;
|
||||
};
|
||||
|
||||
|
||||
int test() {
|
||||
return 1;
|
||||
}
|
34
test.asm
Normal file
34
test.asm
Normal file
@ -0,0 +1,34 @@
|
||||
main:
|
||||
ldw a, 87
|
||||
str a, 0x3EC
|
||||
ldw a, 101
|
||||
str a, 0x3ED
|
||||
ldw a, 108
|
||||
str a, 0x3EE
|
||||
ldw a, 99
|
||||
str a, 0x3EF
|
||||
ldw a, 111
|
||||
str a, 0x3F0
|
||||
ldw a, 109
|
||||
str a, 0x3F1
|
||||
ldw a, 101
|
||||
str a, 0x3F2
|
||||
ldw a, 33
|
||||
str a, 0x3F3
|
||||
ldw a, 10
|
||||
str a, 0x3F4
|
||||
ldw a, 0
|
||||
str a, 0x3F5
|
||||
ldw a, 0x3EC
|
||||
str a, 0x400
|
||||
ldw d, 0
|
||||
ldr b, 0x400
|
||||
ldw c, 1
|
||||
string_loop:
|
||||
ldb a, b
|
||||
beq a, d, string_end
|
||||
int 0x00
|
||||
add b, c
|
||||
jmp string_loop
|
||||
string_end:
|
||||
int 0xFF
|
309
tests/C-Parcer.py
Normal file
309
tests/C-Parcer.py
Normal file
@ -0,0 +1,309 @@
|
||||
|
||||
|
||||
|
||||
# User settings
|
||||
|
||||
filename = "main.c"
|
||||
DEBUG = False
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
types = ['int', 'void'] # Recognized types
|
||||
|
||||
variable_data = {}
|
||||
|
||||
|
||||
CurrentCompilerLine = 1
|
||||
|
||||
|
||||
import re
|
||||
|
||||
|
||||
def CompileError(message):
|
||||
global CurrentCompilerLine
|
||||
column = 0
|
||||
print(f"{filename}:{CurrentCompilerLine}:{column}: Error: {message}")
|
||||
|
||||
|
||||
def DEBUG_MODE_PRINT(*args):
|
||||
if DEBUG:
|
||||
print(*args)
|
||||
|
||||
|
||||
|
||||
# Tokenization regex for splitting code into meaningful segments
|
||||
tokenizer = re.compile(r'(\w+|\(|\)|,|;|=|\+|-|\*|/|{|})')
|
||||
|
||||
# Function to parse C code into chunks
|
||||
def parse_code_to_chunks(code):
|
||||
chunks = [] # List to hold all chunks
|
||||
current_chunk = [] # Temporary list for tokens in the current chunk
|
||||
tokens = tokenizer.findall(code) # Split the code into tokens
|
||||
|
||||
for token in tokens:
|
||||
if token == ';': # End of a statement
|
||||
if current_chunk:
|
||||
#current_chunk.append(token) # Include the semicolon
|
||||
chunks.append(current_chunk) # Finalize the chunk
|
||||
current_chunk = [] # Reset for the next chunk
|
||||
elif token in ['{', '}']: # Start or end of a block
|
||||
if current_chunk:
|
||||
chunks.append(current_chunk) # Finalize the current chunk
|
||||
current_chunk = []
|
||||
chunks.append([token]) # Braces are standalone chunks
|
||||
else:
|
||||
current_chunk.append(token) # Add token to the current chunk
|
||||
|
||||
# Add any remaining tokens as a final chunk
|
||||
if current_chunk:
|
||||
chunks.append(current_chunk)
|
||||
|
||||
return chunks
|
||||
|
||||
|
||||
def classify_assignment(assignment):
|
||||
# Split the assignment into variable and value
|
||||
var, value = assignment.split('=', 1)
|
||||
var = var.strip()
|
||||
value = value.strip()
|
||||
|
||||
# Check if the value is a single number (constant)
|
||||
if value.isdigit():
|
||||
return 1 # Single number
|
||||
|
||||
# Check if the value is a simple arithmetic expression
|
||||
elif any(op in value for op in ['+', '-', '*', '/']):
|
||||
# Check for parentheses and handle them recursively
|
||||
if '(' in value and ')' in value:
|
||||
# Expression with parentheses: check for math with variables or constants
|
||||
return 3 # Math between variables or variables/constants, including parentheses
|
||||
else:
|
||||
# Extract operands and operator to handle math without parentheses
|
||||
operator = next((op for op in ['+', '-', '*', '/'] if op in value), None)
|
||||
left, right = [p.strip() for p in value.split(operator, 1)]
|
||||
|
||||
if left.isdigit() and right.isdigit():
|
||||
return 2 # Math between two numbers
|
||||
elif left.isidentifier() or right.isidentifier():
|
||||
return 3 # Math between variables or variables/constants
|
||||
|
||||
# Check if the value is equal to another variable
|
||||
elif value.isidentifier():
|
||||
return 4 # Equal to another variable
|
||||
|
||||
# Return 0 if the assignment does not match any known category
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
import re
|
||||
|
||||
def parse_operands(equation: str):
|
||||
# Remove spaces for easier handling
|
||||
equation = equation.replace(" ", "")
|
||||
|
||||
# Base case: if no parentheses, split by the operator
|
||||
if '(' not in equation and ')' not in equation:
|
||||
return parse_simple_expression(equation)
|
||||
|
||||
# If parentheses are present, handle recursively
|
||||
while '(' in equation:
|
||||
# Find the innermost parentheses expression
|
||||
innermost = re.search(r'\(([^()]+)\)', equation)
|
||||
if innermost:
|
||||
# Recursively parse the expression inside parentheses
|
||||
inner_expr = innermost.group(1)
|
||||
result = parse_simple_expression(inner_expr)
|
||||
# Replace the expression inside parentheses with the parsed result
|
||||
equation = equation.replace(f'({inner_expr})', result)
|
||||
|
||||
return parse_simple_expression(equation)
|
||||
|
||||
def parse_simple_expression(equation):
|
||||
# This function handles simple expressions without parentheses
|
||||
match = re.match(r'([a-zA-Z0-9_]+|\d+)\s*([+\-*/])\s*([a-zA-Z0-9_]+|\d+)', equation)
|
||||
if match:
|
||||
operand1 = match.group(1) # The first operand (could be a variable or constant)
|
||||
operator = match.group(2) # The operator (+, -, *, /)
|
||||
operand2 = match.group(3) # The second operand (could be a variable or constant)
|
||||
|
||||
# Return operands in a list, this can be extended for more complex parsing
|
||||
return [operand1, operand2]
|
||||
else:
|
||||
raise ValueError(f"Invalid equation format: {equation}")
|
||||
|
||||
|
||||
|
||||
def create_new_variable(name, address):
|
||||
variable_data[name] = address
|
||||
|
||||
def equasion_to_asm(equasion:list, output_address:int) -> list[list[str]]:
|
||||
equasion = " ".join(equasion)
|
||||
#DEBUG_MODE_PRINT(equasion)
|
||||
output = []
|
||||
__ = equasion.split("=")
|
||||
value = __[1].strip()
|
||||
del __
|
||||
|
||||
|
||||
|
||||
#DEBUG_MODE_PRINT(value)
|
||||
if classify_assignment(equasion) == 1:
|
||||
# constant
|
||||
# int x = 1;
|
||||
DEBUG_MODE_PRINT("1 >", value)
|
||||
|
||||
output.append([f'ldw a, {value}'])
|
||||
output.append([f'str a, {output_address}'])
|
||||
|
||||
elif classify_assignment(equasion) == 4:
|
||||
DEBUG_MODE_PRINT("4 >", value)
|
||||
# equal to variable
|
||||
# int x = y
|
||||
address = variable_data[value]
|
||||
output.append([f'ldr a, {address}']) # move the data in address to register a
|
||||
output.append([f'str a, {output_address}']) # put register a into free_memory_address
|
||||
|
||||
elif classify_assignment(equasion) == 3:
|
||||
|
||||
# arithmetic expression: example x = y + 5;
|
||||
# Evaluate the expression and store the result
|
||||
DEBUG_MODE_PRINT("3 >", value)
|
||||
operands = parse_operands(value) # You need to parse operands like 'y + 5'
|
||||
if operands[0].isidentifier():
|
||||
output.append([f'ldr a, {variable_data[operands[0]]}']) # load value of 'y' into a
|
||||
else:
|
||||
output.append([f'ldw a, {operands[0]}']) # load value of 'y' into a
|
||||
|
||||
if operands[1].isidentifier():
|
||||
output.append([f'ldr b, {variable_data[operands[1]]}']) # load value of 'y' into a
|
||||
else:
|
||||
output.append([f'ldw b, {operands[1]}']) # load value of 'y' into a
|
||||
|
||||
output.append([f'add a, b']) # perform addition a = y + 5
|
||||
|
||||
|
||||
# Store in variable or in new variable
|
||||
output.append([f'str a, {output_address}']) # store result into memory address
|
||||
|
||||
else:
|
||||
# Not Implemented
|
||||
DEBUG_MODE_PRINT("NotImpl: ",equasion)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
# Read the C code from the file
|
||||
with open(filename, "r") as f:
|
||||
code = f.read()
|
||||
|
||||
# Preprocess the code: remove newlines and excess whitespace
|
||||
code = re.sub(r'\s+', ' ', code.strip())
|
||||
# Parse the code into chunks
|
||||
chunks = parse_code_to_chunks(code)
|
||||
#DEBUG_MODE_PRINT("Chunks:", chunks)
|
||||
# Extract functions and variables
|
||||
|
||||
|
||||
|
||||
|
||||
free_memory_address = 0xFF
|
||||
|
||||
|
||||
output = []
|
||||
|
||||
|
||||
|
||||
for chunk in chunks:
|
||||
#DEBUG_MODE_PRINT(chunk)
|
||||
chunk: list
|
||||
|
||||
if chunk[0] in types:
|
||||
if "=" in chunk:
|
||||
if chunk[0] in variable_data:
|
||||
CompileError(f"Redefinition of variable: '{chunk[0]}'")
|
||||
create_new_variable(chunk[1], free_memory_address)
|
||||
output += equasion_to_asm(chunk, free_memory_address)
|
||||
free_memory_address -= 1
|
||||
elif "=" in chunk:
|
||||
#DEBUG_MODE_PRINT(chunk)
|
||||
if chunk[0] in variable_data:
|
||||
|
||||
output += equasion_to_asm(chunk, free_memory_address)
|
||||
else:
|
||||
CompileError(f"Undefined Variable: '{chunk[0]}'")
|
||||
else:
|
||||
# DEBUG_MODE_PRINT(chunk)
|
||||
for i,_ in enumerate(chunk):
|
||||
chunk[i] = chunk[i].strip("{}")
|
||||
|
||||
|
||||
if chunk != ['']:
|
||||
#print(chunk)
|
||||
|
||||
if chunk[0] == 'return':
|
||||
# handle return statements
|
||||
pass
|
||||
elif chunk[0].isidentifier():
|
||||
#print("Function Name: " + str(chunk[0]))
|
||||
function_name = chunk[0]
|
||||
|
||||
arguments = chunk[1:]
|
||||
#print(arguments)
|
||||
|
||||
|
||||
for itter, argument in enumerate(arguments):
|
||||
if argument == ',' or argument == '(' or argument == ')':
|
||||
arguments.pop(itter)
|
||||
continue
|
||||
|
||||
arguments[itter] = argument
|
||||
|
||||
|
||||
#print(arguments)
|
||||
|
||||
if function_name == 'syscall':
|
||||
output += [f'int {int(arguments[0], 16)}']
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
CurrentCompilerLine += 1
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
with open("output.asm", "w") as f:
|
||||
# Write the assembly code to the output file
|
||||
f.write("main:\n ")
|
||||
for line in output:
|
||||
f.write("".join(line))
|
||||
f.write("\n ")
|
117
tests/lang-to-asm.py
Normal file
117
tests/lang-to-asm.py
Normal file
@ -0,0 +1,117 @@
|
||||
def parse_math_to_instructions(math_expression, memory_map):
|
||||
"""
|
||||
Converts a simple math expression or variable assignment to assembly instructions.
|
||||
:param math_expression: The math expression to convert.
|
||||
:param memory_map: A dictionary tracking variable memory locations.
|
||||
:return: A list of assembly instructions.
|
||||
"""
|
||||
instructions = []
|
||||
temp_memory_address = 0xFF # Starting memory address for variables
|
||||
|
||||
# Check for assignment
|
||||
if '=' in math_expression:
|
||||
var_name, expr = map(str.strip, math_expression.split('='))
|
||||
expr = expr.strip(';') # Remove trailing semicolon
|
||||
var_name = var_name.strip()
|
||||
|
||||
var_name = var_name.strip("int ")
|
||||
|
||||
# Generate instructions for the expression
|
||||
expr_instructions, result_register = parse_expression(expr, memory_map, temp_memory_address)
|
||||
instructions.extend(expr_instructions)
|
||||
|
||||
# Assign the result to the variable
|
||||
if var_name not in memory_map:
|
||||
memory_map[var_name] = temp_memory_address
|
||||
temp_memory_address -= 1
|
||||
instructions.append(f"str {result_register}, 0x{memory_map[var_name]:X} ; Save variable {var_name} in memory")
|
||||
else:
|
||||
# Generate instructions for the expression
|
||||
expr_instructions, _ = parse_expression(math_expression, memory_map, temp_memory_address)
|
||||
instructions.extend(expr_instructions)
|
||||
|
||||
return instructions
|
||||
|
||||
def precedence(op):
|
||||
"""
|
||||
Returns the precedence of the given operator.
|
||||
"""
|
||||
if op in ('+', '-'):
|
||||
return 1
|
||||
if op in ('*', '/'):
|
||||
return 2
|
||||
return 0
|
||||
|
||||
def apply_operator(instructions, operand_stack, operator):
|
||||
"""
|
||||
Applies an operator to the top two operands in the operand stack and generates instructions.
|
||||
"""
|
||||
b = operand_stack.pop()
|
||||
a = operand_stack.pop()
|
||||
if operator == '+':
|
||||
instructions.append("add a, b")
|
||||
elif operator == '-':
|
||||
instructions.append("sub a, b")
|
||||
elif operator == '*':
|
||||
instructions.append("mul a, b")
|
||||
elif operator == '/':
|
||||
instructions.append("div a, b")
|
||||
operand_stack.append('a')
|
||||
|
||||
def parse_expression(expr, memory_map, temp_memory_address):
|
||||
"""
|
||||
Parses a math expression and generates instructions.
|
||||
:param expr: The math expression.
|
||||
:param memory_map: Memory map for variables.
|
||||
:param temp_memory_address: Memory address to use for new variables.
|
||||
:return: (list of instructions, result_register)
|
||||
"""
|
||||
instructions = []
|
||||
tokens = expr.replace('(', ' ( ').replace(')', ' ) ').split()
|
||||
operator_stack = []
|
||||
operand_stack = []
|
||||
|
||||
for token in tokens:
|
||||
if token.isdigit():
|
||||
if 'a' not in operand_stack:
|
||||
instructions.append(f"ldw a, {token}")
|
||||
operand_stack.append('a')
|
||||
else:
|
||||
instructions.append(f"ldw b, {token}")
|
||||
operand_stack.append('b')
|
||||
elif token in memory_map:
|
||||
if 'a' not in operand_stack:
|
||||
instructions.append(f"ldr a, 0x{memory_map[token]:X}")
|
||||
operand_stack.append('a')
|
||||
else:
|
||||
instructions.append(f"ldr b, 0x{memory_map[token]:X}")
|
||||
operand_stack.append('b')
|
||||
elif token in ['+', '-', '*', '/']:
|
||||
while (operator_stack and precedence(operator_stack[-1]) >= precedence(token)):
|
||||
apply_operator(instructions, operand_stack, operator_stack.pop())
|
||||
operator_stack.append(token)
|
||||
elif token == '(':
|
||||
operator_stack.append(token)
|
||||
elif token == ')':
|
||||
while operator_stack and operator_stack[-1] != '(':
|
||||
apply_operator(instructions, operand_stack, operator_stack.pop())
|
||||
operator_stack.pop() # Remove '('
|
||||
|
||||
while operator_stack:
|
||||
apply_operator(instructions, operand_stack, operator_stack.pop())
|
||||
|
||||
return instructions, 'a'
|
||||
|
||||
# Example Usage
|
||||
memory_map = {}
|
||||
expressions = [
|
||||
"1 + ( 3 + 8 )",
|
||||
"int variable = 1 + ( 3 + 8 );",
|
||||
"int variable_dose = variable + ( 4 + 4 );"
|
||||
]
|
||||
|
||||
for expr in expressions:
|
||||
instructions = parse_math_to_instructions(expr, memory_map)
|
||||
for instr in instructions:
|
||||
print(instr)
|
||||
print('; - - - - - - - - - -')
|
16
tests/main.c
Normal file
16
tests/main.c
Normal file
@ -0,0 +1,16 @@
|
||||
int main()
|
||||
{
|
||||
int num1 = 5;
|
||||
int num2 = 3;
|
||||
int sum = num1 + num2;
|
||||
sum = num1 + 9;
|
||||
syscall(0x01);
|
||||
}
|
||||
|
||||
|
||||
void test()
|
||||
{
|
||||
int x = 4;
|
||||
int y = x;
|
||||
return x;
|
||||
}
|
0
tests/test.py
Normal file
0
tests/test.py
Normal file
Loading…
Reference in New Issue
Block a user