From d91a07374d813130a4681ca1568a320fcc7fbd9a Mon Sep 17 00:00:00 2001 From: Skantastico Date: Thu, 3 Dec 2020 22:08:40 -0500 Subject: [PATCH 01/11] initial commit --- ls8/ls8.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ls8/ls8.py b/ls8/ls8.py index 74128d36b..141ace232 100755 --- a/ls8/ls8.py +++ b/ls8/ls8.py @@ -8,4 +8,6 @@ cpu = CPU() cpu.load() -cpu.run() \ No newline at end of file +cpu.run() + +# initial commmit From ce7ad621bdb08f9af635e6da14362c76016a8f31 Mon Sep 17 00:00:00 2001 From: Skantastico Date: Sun, 6 Dec 2020 19:49:08 -0500 Subject: [PATCH 02/11] added constructor, ram read and ram write --- ls8/cpu.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ls8/cpu.py b/ls8/cpu.py index 9a307496e..a59a4b942 100644 --- a/ls8/cpu.py +++ b/ls8/cpu.py @@ -7,7 +7,15 @@ class CPU: def __init__(self): """Construct a new CPU.""" - pass + self.ram = [0] * 256 + self.reg = [0] * 8 + self.pc = 0 + + def ram_read(self, address): + return self.ram[address] + + def ram_write(self, address, data): + self.ram[address] = data def load(self): """Load a program into memory.""" From 71f7b328ad6eaea6ecaa29b9f9ecd4c20642a3cb Mon Sep 17 00:00:00 2001 From: Skantastico Date: Sun, 6 Dec 2020 20:06:34 -0500 Subject: [PATCH 03/11] implemented core of run --- ls8/cpu.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/ls8/cpu.py b/ls8/cpu.py index a59a4b942..90450afbd 100644 --- a/ls8/cpu.py +++ b/ls8/cpu.py @@ -70,4 +70,22 @@ def trace(self): def run(self): """Run the CPU.""" - pass + halted = False + + while not halted: + instruction = self.ram_read(self.pc) + + if instruction == HLT: + halted = True + + elif instruction = LDI: + new_register = self.ram[self.pc + 1] + new_value = self.ram[self.pc + 2] + + self.reg[new_register] = new_value + self.pc += 3 + + elif instruction == PRN: + new_register = self.ram[self.pc +1] + print(self.reg[new_register]) + self.pc += 2 From cda1b1c96e3781854085877667307d7fc5709aba Mon Sep 17 00:00:00 2001 From: Skantastico Date: Sun, 6 Dec 2020 20:12:16 -0500 Subject: [PATCH 04/11] implemented HLT, LDI, and PRN commands, program is working --- ls8/cpu.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ls8/cpu.py b/ls8/cpu.py index 90450afbd..b217d97df 100644 --- a/ls8/cpu.py +++ b/ls8/cpu.py @@ -70,6 +70,12 @@ def trace(self): def run(self): """Run the CPU.""" + + HLT = 0b00000001 + LDI = 0b10000010 + PRN = 0b01000111 + + halted = False while not halted: @@ -78,7 +84,7 @@ def run(self): if instruction == HLT: halted = True - elif instruction = LDI: + elif instruction == LDI: new_register = self.ram[self.pc + 1] new_value = self.ram[self.pc + 2] From df6990cec5f40ad2731922c352496f45e9755d52 Mon Sep 17 00:00:00 2001 From: Skantastico Date: Sun, 6 Dec 2020 20:34:50 -0500 Subject: [PATCH 05/11] refactored cpus and ls8 to take other programs --- ls8/cpu.py | 48 ++++++++++++++++++++++++++++++++---------------- ls8/ls8.py | 23 +++++++++++++++++++---- 2 files changed, 51 insertions(+), 20 deletions(-) diff --git a/ls8/cpu.py b/ls8/cpu.py index b217d97df..915f15548 100644 --- a/ls8/cpu.py +++ b/ls8/cpu.py @@ -1,6 +1,7 @@ """CPU functionality.""" import sys +import os.path class CPU: """Main CPU class.""" @@ -17,26 +18,41 @@ def ram_read(self, address): def ram_write(self, address, data): self.ram[address] = data - def load(self): + 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) + + # # 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 def alu(self, op, reg_a, reg_b): diff --git a/ls8/ls8.py b/ls8/ls8.py index 141ace232..98f591d13 100755 --- a/ls8/ls8.py +++ b/ls8/ls8.py @@ -2,12 +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 = CPU() +cpu.load(file_name) cpu.run() - -# initial commmit From 86855a2e86215244b105d91b1cd62c79e381efc5 Mon Sep 17 00:00:00 2001 From: Skantastico Date: Mon, 7 Dec 2020 21:43:52 -0500 Subject: [PATCH 06/11] got mult8 working --- ls8/cpu.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/ls8/cpu.py b/ls8/cpu.py index 915f15548..9b557619c 100644 --- a/ls8/cpu.py +++ b/ls8/cpu.py @@ -60,7 +60,10 @@ def alu(self, op, reg_a, reg_b): if op == "ADD": self.reg[reg_a] += self.reg[reg_b] - #elif op == "SUB": etc + + elif op == "MUL": + self.reg[reg_a] *=self.reg[reg_b] + else: raise Exception("Unsupported ALU operation") @@ -90,6 +93,7 @@ def run(self): HLT = 0b00000001 LDI = 0b10000010 PRN = 0b01000111 + MUL = 0b10100010 halted = False @@ -111,3 +115,11 @@ def run(self): new_register = self.ram[self.pc +1] print(self.reg[new_register]) self.pc += 2 + + elif instruction == MUL: + self.pc += 1 + num_1 = self.ram_read(self.pc) + self.pc += 1 + num_2 = self.ram_read(self.pc) + self.alu("MUL", num_1, num_2) + self.pc += 1 From b39f2a6656896c1022154f89357f5ffd3bd86c92 Mon Sep 17 00:00:00 2001 From: Skantastico Date: Tue, 8 Dec 2020 22:41:00 -0500 Subject: [PATCH 07/11] refactored code to be O(1) and got stack working! --- ls8/cpu.py | 140 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 91 insertions(+), 49 deletions(-) diff --git a/ls8/cpu.py b/ls8/cpu.py index 9b557619c..54343079e 100644 --- a/ls8/cpu.py +++ b/ls8/cpu.py @@ -3,20 +3,72 @@ import sys import os.path + +HLT = 0b00000001 # halt code +LDI = 0b10000010 # load integer +PRN = 0b01000111 # print +MUL = 0b10100010 # multiply +PUSH = 0b01000101 # push stack +POP = 0b01000110 # pop stack + + + class CPU: """Main CPU class.""" def __init__(self): """Construct a new CPU.""" - self.ram = [0] * 256 - self.reg = [0] * 8 - self.pc = 0 + self.ram = [0] * 256 # ram + self.reg = [0] * 8 # registers + self.pc = 0 # counter + self.ir = 0 # instruction register + self.mar = 0 # memory address register + self.mdr = 0 # memory data register + self.fl = 0 # flag register + self.halted = False + + # initialize stack pointer + self.reg[7] = 0xF4 + + # 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 + + # stack pointer + + @property + def pointer(self): + return self.reg[7] + + @pointer.setter + def pointer(self, a): + self.reg[7] = a & 0xFF + + def instruction_size(self): + return ((self.ir >> 6) & 0b11) + 1 + + def instruction_sets_pc(self): + return ((self.ir >> 4) & 0b0001) == 1 + + def ram_read(self, mar): + if mar >= 0 and mar < len(self.ram): + return self.ram[mar] + else: + print(f"Error: Attempted to read memory out of bounds.") + return -1 - def ram_read(self, address): - return self.ram[address] + def ram_write(self, mar, mdr): + if mar >= 0 and mar < len(self.ram): + self.ram[mar] = mdr & 0xFF + else: + print("Error Attempted to write memory out of bounds.") - def ram_write(self, address, data): - self.ram[address] = data def load(self, file_name): """Load a program into memory.""" @@ -38,23 +90,6 @@ def load(self, file_name): print(f'Could not find file named: {file_name}') sys.exit(1) - # # 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 - - def alu(self, op, reg_a, reg_b): """ALU operations.""" @@ -90,36 +125,43 @@ def trace(self): def run(self): """Run the CPU.""" - HLT = 0b00000001 - LDI = 0b10000010 - PRN = 0b01000111 - MUL = 0b10100010 + while not self.halted: + self.ir = self.ram_read(self.pc) + operand_a = self.ram_read(self.pc + 1) + operand_b = self.ram_read(self.pc + 2) + + if not self.instruction_sets_pc(): + self.pc += self.instruction_size() + self.execute_instruction(operand_a, operand_b) + + def execute_instruction(self, operand_a, operand_b): + if self.ir in self.branchtable: + self.branchtable[self.ir](operand_a, operand_b) + else: + print(f"Error: Instruction not found.") + sys.exit(1) - halted = False + # refactored commands - while not halted: - instruction = self.ram_read(self.pc) + def execute_HLT(self, operand_a, operand_b): + self.halted = True - if instruction == HLT: - halted = True + def execute_LDI(self, operand_a, operand_b): + self.reg[operand_a] = operand_b - elif instruction == LDI: - new_register = self.ram[self.pc + 1] - new_value = self.ram[self.pc + 2] + def execute_PRN(self, operand_a, operand_b): + print(self.reg[operand_a]) - self.reg[new_register] = new_value - self.pc += 3 + def execute_MUL(self, operand_a, operand_b): + self.reg[operand_a] *= self.reg[operand_b] - elif instruction == PRN: - new_register = self.ram[self.pc +1] - print(self.reg[new_register]) - self.pc += 2 + def execute_PUSH(self, operand_a, operand_b): + self.pointer -= 1 + value_in_register = self.reg[operand_a] + self.ram[self.pointer] = value_in_register - elif instruction == MUL: - self.pc += 1 - num_1 = self.ram_read(self.pc) - self.pc += 1 - num_2 = self.ram_read(self.pc) - self.alu("MUL", num_1, num_2) - self.pc += 1 + def execute_POP(self, operand_a, operand_b): + top_of_stack = self.ram[self.pointer] + self.reg[operand_a] = top_of_stack + self.pointer += 1 From 88e458119d7749e58ff230957f79eb9e2505bbef Mon Sep 17 00:00:00 2001 From: Skantastico Date: Fri, 11 Dec 2020 08:47:52 -0500 Subject: [PATCH 08/11] added call, ret, add, got call.ls8 working --- ls8/cpu.py | 151 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 91 insertions(+), 60 deletions(-) diff --git a/ls8/cpu.py b/ls8/cpu.py index 54343079e..2ba3f638c 100644 --- a/ls8/cpu.py +++ b/ls8/cpu.py @@ -3,35 +3,41 @@ import sys import os.path - -HLT = 0b00000001 # halt code -LDI = 0b10000010 # load integer -PRN = 0b01000111 # print -MUL = 0b10100010 # multiply -PUSH = 0b01000101 # push stack -POP = 0b01000110 # pop stack - - +HLT = 0b00000001 +LDI = 0b10000010 +PRN = 0b01000111 +MUL = 0b10100010 +PUSH = 0b01000101 +POP = 0b01000110 +CALL = 0b01010000 +RET = 0b00010001 +ADD = 0b10100000 class CPU: """Main CPU class.""" def __init__(self): """Construct a new CPU.""" - self.ram = [0] * 256 # ram - self.reg = [0] * 8 # registers - self.pc = 0 # counter - self.ir = 0 # instruction register - self.mar = 0 # memory address register - self.mdr = 0 # memory data register - self.fl = 0 # flag register + # 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 stack pointer - self.reg[7] = 0xF4 - - # setup branch table + # 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 @@ -39,40 +45,55 @@ def __init__(self): 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 - # stack pointer + # Property wrapper for SP (Stack Pointer) @property - def pointer(self): + def sp(self): return self.reg[7] - @pointer.setter - def pointer(self, a): + @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 memory out of bounds.") + print(f"Error: Attempted to read from memory address: {mar}, which is outside of the memory bounds.") return -1 def ram_write(self, mar, mdr): if mar >= 0 and mar < len(self.ram): self.ram[mar] = mdr & 0xFF else: - print("Error Attempted to write memory out of bounds.") - + 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 file_path = os.path.join(os.path.dirname(__file__), file_name) @@ -95,10 +116,7 @@ def alu(self, op, reg_a, reg_b): if op == "ADD": self.reg[reg_a] += self.reg[reg_b] - - elif op == "MUL": - self.reg[reg_a] *=self.reg[reg_b] - + #elif op == "SUB": etc else: raise Exception("Unsupported ALU operation") @@ -122,46 +140,59 @@ def trace(self): print() + # Run Loop + def run(self): """Run the CPU.""" - while not self.halted: + + # Fetch the next instruction (it is decoded lazily using computed properties) self.ir = self.ram_read(self.pc) - operand_a = self.ram_read(self.pc + 1) - operand_b = self.ram_read(self.pc + 2) - if not self.instruction_sets_pc(): - self.pc += self.instruction_size() + # 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) - self.execute_instruction(operand_a, operand_b) + # Increment the program counter if necessary + if not self.instruction_sets_pc: + self.pc += self.instruction_size - def execute_instruction(self, operand_a, operand_b): - if self.ir in self.branchtable: - self.branchtable[self.ir](operand_a, operand_b) - else: - print(f"Error: Instruction not found.") - sys.exit(1) - # refactored commands + # Define the operations to be loaded into the branch table - def execute_HLT(self, operand_a, operand_b): + def execute_HLT(self): self.halted = True - def execute_LDI(self, operand_a, operand_b): - self.reg[operand_a] = operand_b + 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_PRN(self, operand_a, operand_b): - print(self.reg[operand_a]) + def execute_POP(self): + self.mdr = self.ram_read(self.sp) + self.reg[self.operand_a] = self.mdr + self.sp += 1 - def execute_MUL(self, operand_a, operand_b): - self.reg[operand_a] *= self.reg[operand_b] + 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_PUSH(self, operand_a, operand_b): - self.pointer -= 1 - value_in_register = self.reg[operand_a] - self.ram[self.pointer] = value_in_register + def execute_RET(self): + self.pc = self.ram_read(self.sp) + self.sp += 1 - def execute_POP(self, operand_a, operand_b): - top_of_stack = self.ram[self.pointer] - self.reg[operand_a] = top_of_stack - self.pointer += 1 + def execute_ADD(self): + self.reg[self.operand_a] += self.reg[self.operand_b] From 0750f88b9cb7379bc622f759b0a00845a761a7b8 Mon Sep 17 00:00:00 2001 From: Skantastico Date: Sun, 13 Dec 2020 21:19:52 -0500 Subject: [PATCH 09/11] added CMP, JMP, JEQ, and JNE commands to list and to branch table --- ls8/cpu.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ls8/cpu.py b/ls8/cpu.py index 2ba3f638c..8391dd037 100644 --- a/ls8/cpu.py +++ b/ls8/cpu.py @@ -12,6 +12,11 @@ CALL = 0b01010000 RET = 0b00010001 ADD = 0b10100000 +CMP = 0b10100111 +JMP = 0b01010100 +JEQ = 0b01010101 +JNE = 0b01010110 + class CPU: """Main CPU class.""" @@ -48,6 +53,10 @@ def __init__(self): 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) From 4af10a917fa0167efb699b05fc71c4a4b20fae0d Mon Sep 17 00:00:00 2001 From: Skantastico Date: Sun, 13 Dec 2020 21:34:57 -0500 Subject: [PATCH 10/11] added the 4 new methods to branch table --- ls8/cpu.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/ls8/cpu.py b/ls8/cpu.py index 8391dd037..26c373982 100644 --- a/ls8/cpu.py +++ b/ls8/cpu.py @@ -205,3 +205,30 @@ def execute_RET(self): 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 From 4bf9aa8fbf91aa767dfe1555360a3d28a579deca Mon Sep 17 00:00:00 2001 From: Skantastico Date: Sun, 13 Dec 2020 21:48:24 -0500 Subject: [PATCH 11/11] added sctest from sprint repo and got test passing! --- ls8/cpu.py | 6 ++++-- ls8/examples/sctest.ls8 | 7 +++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/ls8/cpu.py b/ls8/cpu.py index 26c373982..8028820ae 100644 --- a/ls8/cpu.py +++ b/ls8/cpu.py @@ -18,6 +18,8 @@ JNE = 0b01010110 + + class CPU: """Main CPU class.""" @@ -220,9 +222,9 @@ def execute_JMP(self): self.pc = self.reg[self.operand_a] def execute_JEQ(self): - if self.fl = 0b00000001: + 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