diff --git a/ls8/cpu.py b/ls8/cpu.py index 9a307496e..8028820ae 100644 --- a/ls8/cpu.py +++ b/ls8/cpu.py @@ -1,35 +1,126 @@ """CPU functionality.""" import sys +import os.path + +HLT = 0b00000001 +LDI = 0b10000010 +PRN = 0b01000111 +MUL = 0b10100010 +PUSH = 0b01000101 +POP = 0b01000110 +CALL = 0b01010000 +RET = 0b00010001 +ADD = 0b10100000 +CMP = 0b10100111 +JMP = 0b01010100 +JEQ = 0b01010101 +JNE = 0b01010110 + + + class CPU: """Main CPU class.""" def __init__(self): """Construct a new CPU.""" - pass + # 256-byte RAM, each element is 1 byte (can only store integers 0-255) + self.ram = [0] * 256 + + # R0-R7: 8-bit general purpose registers, R5 = interrupt mask (IM), + # R6 = interrupt status (IS), R7 = stack pointer (SP) + self.reg = [0] * 8 + + # Internal Registers + self.pc = 0 # Program Counter: address of the currently executing instruction + self.ir = 0 # Instruction Register: contains a copy of the currently executing instruction + self.mar = 0 # Memory Address Register: holds the memory address we're reading or writing + self.mdr = 0 # Memory Data Register: holds the value to write or the value just read + self.fl = 0 # Flag Register: holds the current flags status + self.halted = False + + # Initialize the Stack Pointer + # SP points at the value at the top of the stack (most recently pushed), or at address F4 if the stack is empty. + self.reg[7] = 0xF4 # 244 # int('F4', 16) + + # Setup Branch Table + self.branchtable = {} + self.branchtable[HLT] = self.execute_HLT + self.branchtable[LDI] = self.execute_LDI + self.branchtable[PRN] = self.execute_PRN + self.branchtable[MUL] = self.execute_MUL + self.branchtable[PUSH] = self.execute_PUSH + self.branchtable[POP] = self.execute_POP + self.branchtable[CALL] = self.execute_CALL + self.branchtable[RET] = self.execute_RET + self.branchtable[ADD] = self.execute_ADD + self.branchtable[CMP] = self.execute_CMP + self.branchtable[JMP] = self.execute_JMP + self.branchtable[JEQ] = self.execute_JEQ + self.branchtable[JNE] = self.execute_JNE + + # Property wrapper for SP (Stack Pointer) + + @property + def sp(self): + return self.reg[7] + + @sp.setter + def sp(self, a): + self.reg[7] = a & 0xFF + + # Computed Properties + + @property + def operand_a(self): + return self.ram_read(self.pc + 1) + + @property + def operand_b(self): + return self.ram_read(self.pc + 2) + + @property + def instruction_size(self): + return ((self.ir >> 6) & 0b11) + 1 + + @property + def instruction_sets_pc(self): + return ((self.ir >> 4) & 0b0001) == 1 + + # CPU Methods + + def ram_read(self, mar): + if mar >= 0 and mar < len(self.ram): + return self.ram[mar] + else: + print(f"Error: Attempted to read from memory address: {mar}, which is outside of the memory bounds.") + return -1 - def load(self): - """Load a program into memory.""" + def ram_write(self, mar, mdr): + if mar >= 0 and mar < len(self.ram): + self.ram[mar] = mdr & 0xFF + else: + print(f"Error: Attempted to write to memory address: {mar}, which is outside of the memory bounds.") + def load(self, file_name): + """Load a program into memory.""" address = 0 - # For now, we've just hardcoded a program: - - program = [ - # From print8.ls8 - 0b10000010, # LDI R0,8 - 0b00000000, - 0b00001000, - 0b01000111, # PRN R0 - 0b00000000, - 0b00000001, # HLT - ] - - for instruction in program: - self.ram[address] = instruction - address += 1 - + file_path = os.path.join(os.path.dirname(__file__), file_name) + try: + with open(file_path) as f: + for line in f: + num = line.split("#")[0].strip() # "10000010" + try: + instruction = int(num, 2) + self.ram[address] = instruction + address += 1 + except: + continue + except: + print(f'Could not find file named: {file_name}') + sys.exit(1) def alu(self, op, reg_a, reg_b): """ALU operations.""" @@ -60,6 +151,86 @@ def trace(self): print() + # Run Loop + def run(self): """Run the CPU.""" - pass + while not self.halted: + + # Fetch the next instruction (it is decoded lazily using computed properties) + self.ir = self.ram_read(self.pc) + + # Execute the instruction + if self.ir in self.branchtable: + self.branchtable[self.ir]() + else: + print(f"Error: Could not find instruction: {self.ir} in branch table.") + sys.exit(1) + + # Increment the program counter if necessary + if not self.instruction_sets_pc: + self.pc += self.instruction_size + + + # Define the operations to be loaded into the branch table + + def execute_HLT(self): + self.halted = True + + def execute_LDI(self): + self.reg[self.operand_a] = self.operand_b + + def execute_PRN(self): + print(self.reg[self.operand_a]) + + def execute_MUL(self): + self.reg[self.operand_a] *= self.reg[self.operand_b] + + def execute_PUSH(self): + self.sp -= 1 + self.mdr = self.reg[self.operand_a] + self.ram_write(self.sp, self.mdr) + + def execute_POP(self): + self.mdr = self.ram_read(self.sp) + self.reg[self.operand_a] = self.mdr + self.sp += 1 + + def execute_CALL(self): + self.sp -= 1 + self.ram_write(self.sp, self.pc + self.instruction_size) + self.pc = self.reg[self.operand_a] + + def execute_RET(self): + self.pc = self.ram_read(self.sp) + self.sp += 1 + + def execute_ADD(self): + self.reg[self.operand_a] += self.reg[self.operand_b] + + def execute_CMP(self): + if self.reg[self.operand_a] < self.reg[self.operand_b]: + self.fl = 0b00000100 + + elif self.reg[self.operand_a] > self.reg[self.operand_b]: + self.fl = 0b00000010 + + else: + self.fl = 0b00000001 + + def execute_JMP(self): + self.pc = self.reg[self.operand_a] + + def execute_JEQ(self): + if self.fl == 0b00000001: + self.execute_JMP() + + else: + self.pc += self.instruction_size + + def execute_JNE(self): + if self.fl != 0b00000001: + self.execute_JMP() + + else: + self.pc += self.instruction_size diff --git a/ls8/examples/sctest.ls8 b/ls8/examples/sctest.ls8 index 7853b76a4..8f86bba7e 100644 --- a/ls8/examples/sctest.ls8 +++ b/ls8/examples/sctest.ls8 @@ -1,3 +1,10 @@ +# Code to test the Sprint Challenge +# +# Expected output: +# 1 +# 4 +# 5 + 10000010 # LDI R0,10 00000000 00001010 diff --git a/ls8/ls8.py b/ls8/ls8.py index 74128d36b..98f591d13 100755 --- a/ls8/ls8.py +++ b/ls8/ls8.py @@ -2,10 +2,27 @@ """Main.""" +argv_err_msg = """ +Error: You must specify a file name as a command line argument. +This file you specify should contain a binary instruction set to load into the CPU. + Examples: + python3 ls8.py examples/print8.ls8 + python3 ls8.py examples/mult.ls8 + python3 ls8.py examples/stack.ls8 + python3 ls8.py examples/call.ls8 +""" + import sys from cpu import * -cpu = CPU() +if len(sys.argv) != 2: + print(argv_err_msg) + sys.exit(1) +else: + # cd ../S7-Computer-Architecture/M1-4/Computer-Architecture/ls8 + # python3 ls8.py examples/print8.ls8 + file_name = sys.argv[1] -cpu.load() -cpu.run() \ No newline at end of file +cpu = CPU() +cpu.load(file_name) +cpu.run()