From b88b8bffc9a3a8599a799b0da8b47cd49c04abe3 Mon Sep 17 00:00:00 2001 From: OusmBlueNinja <89956790+OusmBlueNinja@users.noreply.github.com> Date: Mon, 23 Dec 2024 23:17:07 -0600 Subject: [PATCH] Main --- .gitattributes | 2 + asm-to-prg.py | 914 ++++++++++++++++++++++++++++++++++++++++++ asm/example.asm | 112 ++++++ asm/gradiant.asm | 66 +++ asm/try_to_fix_me.asm | 84 ++++ c-to-asm.py | 548 +++++++++++++++++++++++++ drive8.bin | Bin 0 -> 68 bytes main.asm | 35 ++ main.c | 12 + main.py | 769 +++++++++++++++++++++++++++++++++++ program.py | 1 + std.asm | 18 + std.h | 19 + test.asm | 34 ++ tests/C-Parcer.py | 309 ++++++++++++++ tests/lang-to-asm.py | 117 ++++++ tests/main.c | 16 + tests/test.py | 0 18 files changed, 3056 insertions(+) create mode 100644 .gitattributes create mode 100644 asm-to-prg.py create mode 100644 asm/example.asm create mode 100644 asm/gradiant.asm create mode 100644 asm/try_to_fix_me.asm create mode 100644 c-to-asm.py create mode 100644 drive8.bin create mode 100644 main.asm create mode 100644 main.c create mode 100644 main.py create mode 100644 program.py create mode 100644 std.asm create mode 100644 std.h create mode 100644 test.asm create mode 100644 tests/C-Parcer.py create mode 100644 tests/lang-to-asm.py create mode 100644 tests/main.c create mode 100644 tests/test.py diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/asm-to-prg.py b/asm-to-prg.py new file mode 100644 index 0000000..bdbbeb1 --- /dev/null +++ b/asm-to-prg.py @@ -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 ") + + + + \ No newline at end of file diff --git a/asm/example.asm b/asm/example.asm new file mode 100644 index 0000000..ed8fd05 --- /dev/null +++ b/asm/example.asm @@ -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 diff --git a/asm/gradiant.asm b/asm/gradiant.asm new file mode 100644 index 0000000..170ef78 --- /dev/null +++ b/asm/gradiant.asm @@ -0,0 +1,66 @@ +main: + jsr lcd_init ; Initialize the screen + ldw b, 0 ; Initialize register b (x position) + ldw c, 0 ; Initialize register c (y position) + ldw d, 1 ; Increment value + ldw f, 600 ; Screen width/height limit + + ; Store scaling factors in memory (safe zone past 0xC8) + ldw a, 0x0 ; Scaling factor for green (shift left by 8 bits) + str a, 0xD0 ; Store in memory at address 0xD0 + ldw a, 0xFF0000 ; Scaling factor for red (shift left by 16 bits) + str a, 0xC8 ; Store in memory at address 0xC8 + ldw a, 0x00FF00 ; Scaling factor for green (shift left by 8 bits) + str a, 0xCC ; Store in memory at address 0xCC + + +loop: + ; Compute red channel + mov a, b ; Copy x position to a + ldr e, 0xC8 ; Load red scaling factor + mul a, e ; Multiply x by red scaling factor + + ; Compute green channel + mov e, c ; Copy y position to e + ldr a, 0xCC ; Load green scaling factor into a + mul e, a ; Multiply y by green scaling factor (result in e) + + ; Combine red and green channels + add a, e ; Combine red and green channels (result in a) + + ; Add blue channel (based on x for simplicity) + add a, b ; Blue intensity based on x position + + ; Draw the pixel + int 0x71 ; Set pixel color at (b, c) + int 0xF6 ; get key pressed + + +next: + ; Update position + ldw a, 0 ; Reset a + add b, d ; Increment x (b += 1) + bne b, f, loop ; If b < 256, continue loop + ldw b, 0 ; Reset x + add c, d ; Increment y (c += 1) + bne c, f, loop ; If c < 256, continue loop + + ; Halt + int 0xFF ; Stop program + +lcd_init: + push a + push b + push c + + + ldw a, 0x0 ; mode + + ldw b, 256 ; Vertical resolution + ldw c, 256 ; Horizontal resolution + int 0x70 ; Initialize screen + + pop c + pop b + pop a + ret diff --git a/asm/try_to_fix_me.asm b/asm/try_to_fix_me.asm new file mode 100644 index 0000000..cf1aa2a --- /dev/null +++ b/asm/try_to_fix_me.asm @@ -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 diff --git a/c-to-asm.py b/c-to-asm.py new file mode 100644 index 0000000..70e98b5 --- /dev/null +++ b/c-to-asm.py @@ -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") diff --git a/drive8.bin b/drive8.bin new file mode 100644 index 0000000000000000000000000000000000000000..77b77dec0732f82c6f724c88a614c8cc9a52fb71 GIT binary patch literal 68 jcmZQ5Pb^MBMmgD8i79Dtlmll08QDM#l7*@OQ6PB$l2sb4 literal 0 HcmV?d00001 diff --git a/main.asm b/main.asm new file mode 100644 index 0000000..b275eda --- /dev/null +++ b/main.asm @@ -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" \ No newline at end of file diff --git a/main.c b/main.c new file mode 100644 index 0000000..e57a9b7 --- /dev/null +++ b/main.c @@ -0,0 +1,12 @@ +int main() +{ + + char *msg = "Welcome!\n"; + print_string(msg); + + while (1) // Not implemented + { + } + + return 0; +} diff --git a/main.py b/main.py new file mode 100644 index 0000000..21e21ff --- /dev/null +++ b/main.py @@ -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() \ No newline at end of file diff --git a/program.py b/program.py new file mode 100644 index 0000000..66bb3e2 --- /dev/null +++ b/program.py @@ -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] \ No newline at end of file diff --git a/std.asm b/std.asm new file mode 100644 index 0000000..06a7f6e --- /dev/null +++ b/std.asm @@ -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 \ No newline at end of file diff --git a/std.h b/std.h new file mode 100644 index 0000000..2bc2643 --- /dev/null +++ b/std.h @@ -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; +} \ No newline at end of file diff --git a/test.asm b/test.asm new file mode 100644 index 0000000..3479949 --- /dev/null +++ b/test.asm @@ -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 diff --git a/tests/C-Parcer.py b/tests/C-Parcer.py new file mode 100644 index 0000000..7a6124f --- /dev/null +++ b/tests/C-Parcer.py @@ -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 ") \ No newline at end of file diff --git a/tests/lang-to-asm.py b/tests/lang-to-asm.py new file mode 100644 index 0000000..7acf4fb --- /dev/null +++ b/tests/lang-to-asm.py @@ -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('; - - - - - - - - - -') \ No newline at end of file diff --git a/tests/main.c b/tests/main.c new file mode 100644 index 0000000..00e1506 --- /dev/null +++ b/tests/main.c @@ -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; +} \ No newline at end of file diff --git a/tests/test.py b/tests/test.py new file mode 100644 index 0000000..e69de29