An imaginary 16-bit CPU architecture with custom assembly language and instructions
from binary import *
from consts import *

class Register:
    def __init__(self, value):
        self.value = value

    def get_value(self):
        return self.value

    def set_value(self, value):
        self.value = value

    def incr(self):
        self.value += Binary(1)

class CPU:
    def __init__(self, instructions, registers):
        self.instructions = instructions
        self.registers = registers
        self.run = True

    def __repr__(self):
        output = "= = = = CPU = = = =\n"
        output += "Register 1: " + str(self.registers[0].get_value()) + " (" + str(int(self.registers[0].get_value())) + ")\n"
        output += "Register 2: " + str(self.registers[1].get_value()) + " (" + str(int(self.registers[1].get_value())) + ")\n"
        output += "Register 3: " + str(self.registers[2].get_value()) + " (" + str(int(self.registers[2].get_value())) + ")\n"
        output += "Register 4: " + str(self.registers[3].get_value()) + " (" + str(int(self.registers[3].get_value())) + ")\n"
        output += "Register 5: " + str(self.registers[4].get_value()) + " (" + str(int(self.registers[4].get_value())) + ")\n"
        output += "= = = = = = = = = =\n"
        return output

    def add(self):
        self.registers[0].set_value(self.registers[0].get_value() + self.registers[1].get_value())

    def subtract(self):
        self.registers[0].set_value(self.registers[0].get_value() - self.registers[1].get_value())

    def assign(self, value):
        if self.registers[2].get_value() == Binary(1):
            self.registers[0].set_value(value)
            
        elif self.registers[2].get_value() == Binary(2):
            self.registers[1].set_value(value)

        elif self.registers[2].get_value() == Binary(3):
            self.registers[3].set_value(value - Binary(2))
            
        else:
            pass

    def perform_instruction(self, instruction, mem):
        if instruction == self.instructions["add"]:
            self.add()

        elif instruction == self.instructions["sub"]:
            self.subtract()

        elif instruction == self.instructions["mod_reg1"]:
            self.registers[2].set_value(Binary(1))

        elif instruction == self.instructions["mod_reg2"]:
            self.registers[2].set_value(Binary(2))

        elif instruction == self.instructions["jmp"]:
            self.registers[2].set_value(Binary(3))

        elif instruction == self.instructions["write"]:
            self.registers[4].set_value(self.registers[0].get_value())

        elif instruction == self.instructions["recall"]:
            self.registers[0].set_value(self.registers[4].get_value())

        elif instruction == self.instructions["jg"]:
            if self.registers[0].get_value() > self.registers[1].get_value():
                self.registers[2].set_value(Binary(3))
            else:
                self.registers[2].set_value(Binary(0))

        elif instruction == self.instructions["jl"]:
            if self.registers[0].get_value() < self.registers[1].get_value():
                self.registers[2].set_value(Binary(3))
            else:
                self.registers[2].set_value(Binary(0))

        elif instruction == self.instructions["je"]:
            if self.registers[0].get_value() == self.registers[1].get_value():
                self.registers[2].set_value(Binary(3))
            else:
                self.registers[2].set_value(Binary(0))

        elif instruction == self.instructions["memwrite"]:
            address = self.registers[1].get_value()
            value = self.registers[0].get_value()
            mem.write_to_memory(address, value)

        elif instruction == self.instructions["memread"]:
            address = self.registers[1].get_value()
            self.registers[0].set_value(mem.read_from_memory(address))

        elif instruction == self.instructions["halt"]:
            self.run = False

        else:
            self.assign(instruction)

    def read_instruction(self, mem):
        address = self.registers[3].get_value()
        try:
            new_instruction = mem.data[int(address)]
            return new_instruction
        except IndexError:
            self.run = False
            return 0

    def step(self, mem):
        if self.run:
            instruction = self.read_instruction(mem)
            self.perform_instruction(instruction, mem)
            self.registers[3].incr()

src/CPU.py
arda-guler/TurnaCore
class Binary:
    def __init__(self, value=0):
        # Ensure the value is within the range of a 16-bit binary number
        if value < 0 or value > 65535:
            raise ValueError("Value must be between 0 and 65535 inclusive.")
        self.value = value

    def __repr__(self):
        return f"Binary({self.value})"

    def __str__(self):
        return bin(self.value)[2:].zfill(16)  # Pad with zeros to ensure 16 bits

    def __int__(self):
        return self.value

    def __eq__(self, other):
        if not isinstance(other, Binary):
            return False
        return self.value == other.value

    def __add__(self, other):
        if not isinstance(other, Binary):
            raise TypeError("Unsupported operand type: must be Binary")
        result = self.value + other.value
        return Binary(result & 0xFFFF)  # Keep only the lowest 16 bits

    def __sub__(self, other):
        if not isinstance(other, Binary):
            raise TypeError("Unsupported operand type: must be Binary")
        result = self.value - other.value
        return Binary(result & 0xFFFF)  # Keep only the lowest 16 bits

    def __lt__(self, other):
        if not isinstance(other, Binary):
            raise TypeError("Unsupported operand type: must be Binary")
        return self.value < other.value

    def __le__(self, other):
        if not isinstance(other, Binary):
            raise TypeError("Unsupported operand type: must be Binary")
        return self.value <= other.value

    def __gt__(self, other):
        if not isinstance(other, Binary):
            raise TypeError("Unsupported operand type: must be Binary")
        return self.value > other.value

    def __ge__(self, other):
        if not isinstance(other, Binary):
            raise TypeError("Unsupported operand type: must be Binary")
        return self.value >= other.value

src/binary.py
arda-guler/TurnaCore
from consts import *

def compile_program(input_file, output_file="output.cis"):
    infile = open(input_file, "r")
    inlines = [line.rstrip() for line in infile.readlines() if ((not line.startswith(';')) and (not line == '\n'))]

    inlines_sanitized = []
    for element in inlines:
        if ' ' in element:
            split_elements = element.split(' ')
            inlines_sanitized.extend(split_elements)
        else:
            inlines_sanitized.append(element)

    labels = {}
    statement_pos = 1
    for line in inlines_sanitized:
        if line.endswith(":"):
            labels[line[0:-1].lower()] = str(statement_pos)
        elif line.startswith("#"):
            pass
        else:
            statement_pos += 1

    consts = {}
    for line in inlines_sanitized:
        if line.lower().startswith("#"):
            lsplit = line[1:].split("=")
            consts[lsplit[0].lower()] = str(lsplit[1])

    print(consts)
    
    outlines = []
    for line in inlines_sanitized:
        if line.startswith("#"):
            pass
        elif line.lower() in labels:
            outlines.append(labels[line.lower()])
        elif line.lower() in consts:
            outlines.append(consts[line.lower()])
        elif line.lower() == "add":
            outlines.append(str(int(cpu_add)))
        elif line.lower() == "sub":
            outlines.append(str(int(cpu_sub)))
        elif line.lower() == "mr1":
            outlines.append(str(int(cpu_mod_reg1)))
        elif line.lower() == "mr2":
            outlines.append(str(int(cpu_mod_reg2)))
        elif line.lower() == "jmp":
            outlines.append(str(int(cpu_jmp)))
        elif line.lower() == "jg":
            outlines.append(str(int(cpu_jg)))
        elif line.lower() == "jl":
            outlines.append(str(int(cpu_jl)))
        elif line.lower() == "je":
            outlines.append(str(int(cpu_je)))
        elif line.lower() == "write":
            outlines.append(str(int(cpu_write)))
        elif line.lower() == "recall":
            outlines.append(str(int(cpu_recall)))
        elif line.lower() == "memw":
            outlines.append(str(int(cpu_memwrite)))
        elif line.lower() == "memr":
            outlines.append(str(int(cpu_memread)))
        elif line.lower() == "halt":
            outlines.append(str(int(cpu_halt)))
        elif line.lower() == "null":
            outlines.append("0")
        elif line.endswith(":"):
            pass
        else:
            outlines.append(line)

    infile.close()
    outfile = open(output_file, "w+")
    for line in outlines:
        outfile.write(line)
        outfile.write("\n")

    outfile.close()

src/compiler.py
arda-guler/TurnaCore
from binary import *

cpu_add =       Binary(65535)
cpu_sub =       Binary(65534)
cpu_mod_reg1 =  Binary(65533)
cpu_mod_reg2 =  Binary(65532)
cpu_jmp =       Binary(65531)
cpu_jg =        Binary(65530)
cpu_jl =        Binary(65529)
cpu_je =        Binary(65528)
cpu_write =     Binary(65527)
cpu_recall =    Binary(65526)
cpu_memwrite =  Binary(65525)
cpu_memread =   Binary(65524)
cpu_halt =      Binary(65523)
NULL =          Binary(0)

src/consts.py
arda-guler/TurnaCore
import sys
import time
import tkinter as tk
import os

from binary import *
from CPU import *
from memory import *
from consts import *

def init_CPU():
    cpu_instructions = {"add": cpu_add,
                        "sub": cpu_sub,
                        "mod_reg1": cpu_mod_reg1,
                        "mod_reg2": cpu_mod_reg2,
                        "jmp": cpu_jmp,
                        "jg": cpu_jg,
                        "jl": cpu_jl,
                        "je": cpu_je,
                        "write": cpu_write,
                        "recall": cpu_recall,
                        "memwrite": cpu_memwrite,
                        "memread": cpu_memread,
                        "halt": cpu_halt}

    reg1 = Register(NULL)
    reg2 = Register(NULL)
    reg3 = Register(NULL)
    reg4 = Register(NULL)
    reg5 = Register(NULL)

    registers = [reg1, reg2, reg3, reg4, reg5]
    
    cpu = CPU(cpu_instructions, registers)
    return cpu

def main():
    sys_args = sys.argv
    if len(sys_args) == 2:
        program_file = sys_args[1]
        slow = "y"
    elif len(sys_args) == 3:
        program_file = sys_args[1]
        slow = bool(sys_args[2])
    else:
        print("Programs on disk:")
        progfiles = []
        for f in os.listdir("../programs"):
            if f.endswith(".ao"):
                print(f)
                progfiles.append(f)
        print("\n")
                
        program_file = input("Enter the filename of the program you wish to run: ")
        slow = input("Slow mode (Y/n): ")

    if slow.lower() == "n":
        slow = False
    else:
        slow = True

    program_file = "../programs/" + program_file
    if not program_file.endswith(".ao") or not program_file.endswith(".cis"):
        program_file = program_file + ".ao"

    print("Initializing CPU...")
    cpu = init_CPU()

    mem = Memory(program_file)
    print("Loaded program into memory.")
    print(mem)

    window = tk.Tk()
    window.title("TurnaCore")
    window.iconphoto(False, tk.PhotoImage(file="../icon/tc_icon.png"))
    canvas = tk.Canvas(window, height=480, width=640, bg="black")
    canvas.pack()

    print("Running...")
    while int(cpu.registers[3].get_value()) < mem.size and cpu.run:
        cpu.step(mem)
        # print(cpu)

        if slow:
            # draw memory
            canvas.create_rectangle(360, 10, 620, 470, fill="green")
            mempos = cpu.registers[3].get_value()
            canvas.create_text(380, 24, text="Mem. Loc.: " + str(mempos), anchor="w", fill="white", font=('Arial', 12))
            canvas.create_rectangle(370, 40, 610, 42, fill="black")
            canvas.create_rectangle(370, 225, 610, 255, fill="red")

            for i in range(1, 7):
                if int(mempos) - i > 0:
                    if mem.data[int(mempos) - i] == cpu_add:
                        mtext = str(mem.data[int(mempos) - i]) + " (ADD)"
                    elif mem.data[int(mempos) - i] == cpu_sub:
                        mtext = str(mem.data[int(mempos) - i]) + " (SUB)"
                    elif mem.data[int(mempos) - i] == cpu_mod_reg1:
                        mtext = str(mem.data[int(mempos) - i]) + " (MR1)"
                    elif mem.data[int(mempos) - i] == cpu_mod_reg2:
                        mtext = str(mem.data[int(mempos) - i]) + " (MR2)"
                    elif mem.data[int(mempos) - i] == cpu_jmp:
                        mtext = str(mem.data[int(mempos) - i]) + " (JMP)"
                    elif mem.data[int(mempos) - i] == cpu_jg:
                        mtext = str(mem.data[int(mempos) - i]) + " (JG)"
                    elif mem.data[int(mempos) - i] == cpu_jl:
                        mtext = str(mem.data[int(mempos) - i]) + " (JL)"
                    elif mem.data[int(mempos) - i] == cpu_je:
                        mtext = str(mem.data[int(mempos) - i]) + " (JE)"
                    elif mem.data[int(mempos) - i] == cpu_write:
                        mtext = str(mem.data[int(mempos) - i]) + " (WRITE)"
                    elif mem.data[int(mempos) - i] == cpu_recall:
                        mtext = str(mem.data[int(mempos) - i]) + " (RECALL)"
                    elif mem.data[int(mempos) - i] == cpu_memwrite:
                        mtext = str(mem.data[int(mempos) - i]) + " (MEMW)"
                    elif mem.data[int(mempos) - i] == cpu_memread:
                        mtext = str(mem.data[int(mempos) - i]) + " (MEMR)"
                    elif mem.data[int(mempos) - i] == cpu_halt:
                        mtext = str(mem.data[int(mempos) - i]) + " (HALT)"
                    else:
                        mtext = str(mem.data[int(mempos) - i]) + " (" + str(int(mem.data[int(mempos) - i])) + ")"
                else:
                    mtext = ""
                    
                y_margin = 30
                pos_x = 380
                pos_y = 240 - i * y_margin
                canvas.create_text(pos_x, pos_y, text=mtext, fill="white", font=('Arial', 12), anchor="w")

            for i in range(8):
                if int(mempos) + i < mem.size:
                    if mem.data[int(mempos) + i] == cpu_add:
                        mtext = str(mem.data[int(mempos) + i]) + " (ADD)"
                    elif mem.data[int(mempos) + i] == cpu_sub:
                        mtext = str(mem.data[int(mempos) + i]) + " (SUB)"
                    elif mem.data[int(mempos) + i] == cpu_mod_reg1:
                        mtext = str(mem.data[int(mempos) + i]) + " (MR1)"
                    elif mem.data[int(mempos) + i] == cpu_mod_reg2:
                        mtext = str(mem.data[int(mempos) + i]) + " (MR2)"
                    elif mem.data[int(mempos) + i] == cpu_jmp:
                        mtext = str(mem.data[int(mempos) + i]) + " (JMP)"
                    elif mem.data[int(mempos) + i] == cpu_jg:
                        mtext = str(mem.data[int(mempos) + i]) + " (JG)"
                    elif mem.data[int(mempos) + i] == cpu_jl:
                        mtext = str(mem.data[int(mempos) + i]) + " (JL)"
                    elif mem.data[int(mempos) + i] == cpu_je:
                        mtext = str(mem.data[int(mempos) + i]) + " (JE)"
                    elif mem.data[int(mempos) + i] == cpu_write:
                        mtext = str(mem.data[int(mempos) + i]) + " (WRITE)"
                    elif mem.data[int(mempos) + i] == cpu_recall:
                        mtext = str(mem.data[int(mempos) + i]) + " (RECALL)"
                    elif mem.data[int(mempos) + i] == cpu_memwrite:
                        mtext = str(mem.data[int(mempos) + i]) + " (MEMW)"
                    elif mem.data[int(mempos) + i] == cpu_memread:
                        mtext = str(mem.data[int(mempos) + i]) + " (MEMR)"
                    elif mem.data[int(mempos) + i] == cpu_halt:
                        mtext = str(mem.data[int(mempos) + i]) + " (HALT)"
                    else:
                        mtext = str(mem.data[int(mempos) + i]) + " (" + str(int(mem.data[int(mempos) + i])) + ")"
                else:
                    mtext = ""
                    
                y_margin = 30
                pos_x = 380
                pos_y = 240 + i * y_margin
                canvas.create_text(pos_x, pos_y, text=mtext, fill="white", font=('Arial', 12), anchor="w")

            # draw cpu
            canvas.create_rectangle(10, 10, 350, 470, fill="blue")
            canvas.create_rectangle(10, 135, 350, 165, fill="cyan")
            
            for i in range(5):
                canvas.create_text(20, 30 + i * 30, text="Register" + str(i+1) + ": " + str(cpu.registers[i].get_value()) + " (" + str(int(cpu.registers[i].get_value())) + ")", anchor="w", font=('Arial', 12))
            
            window.update()
            time.sleep(0.05)

            if int(cpu.registers[3].get_value()) < mem.size and cpu.run:
                canvas.delete("all") 

    print("Done!\n")
    print(cpu)
    print(mem)

    if slow:
        input("Press Enter to exit.")

main()

src/main.py
arda-guler/TurnaCore
from binary import *
from compiler import *
from consts import *

class Memory:
    def __init__(self, file):
        self.file = file
        self.data = []
        self.size = 0

        # compile the program into a CIS before loading
        # if it is an AO file
        if file.endswith(".ao"):
            print("Compiling", file, "...")
            compile_program(file, file[0:-3] + ".cis")
            print("Compilation complete.")
        self.file = file[0:-3] + ".cis"
        
        self.load()

    def __repr__(self):
        output = "\nMEMORY:\n"
        output += "--------\n"
        for idx, i in enumerate(self.data):
            output += str(idx) + ": " + str(i) + "\n"

        return output

    def load(self):
        with open(self.file) as f:
            lines = [Binary(int(line.rstrip())) for line in f]

        self.data = lines
        self.size = len(lines)

    def write_to_memory(self, address, value):
        address = int(address)

        if address <= len(self.data) - 1:
            self.data[address] = value
            return
        
        else:
            padding = address - len(self.data)# + 1
            for i in range(padding):
                self.data.append(NULL)

        self.data.append(value)
        self.size = len(self.data)

    def read_from_memory(self, address):
        address = int(address)
        
        if address > len(self.data) - 1:
            return NULL

        return self.data[address]

src/memory.py
arda-guler/TurnaCore