diff --git a/class3.py b/class3.py new file mode 100644 index 000000000..66c9fba22 --- /dev/null +++ b/class3.py @@ -0,0 +1,124 @@ + +Computer arch topics we have implemented in our project +- RAM, self.ram +- registers! +- CPU, self.run +- ALU +Why LS8? +- 8 bits +- not the registers, as you might think +- Also, 8-bit CPU and ALU architectures are those that are based on registers, +address buses, or data buses of that size + CPU + 00000000 + 00000000 + 00000000 + 00000000 + RAM +- with 8-bit address bus, the CPU can address 256 bytes of RAM +General purpose calculating machine vs specialized calculating machines + "computer" "calculator" + Can do anything hardwired for specific calculations + Broadly applicable Faster (often) +More memory? Stack! +- more variables +- function calls and nested function calls +To make a stack? +- Push +- Pop +- Memory space +-- RAM +-- 0-255 (256 bytes) +- a pointer to track where the top of the stack +-- variable that is a memory address +how to push +how to Pop +Handling 'leftovers' from push +Stack underflow +Why doesn't the CPU prevent underflow, or prevent wrapping around in memory? +- don't want the CPU to spend time/energy checking +- used to be dev's job, now it's the compiler's job +Stack overflow +- software's job to prevent this +Stacks and nested function calls +self.ram = [0] * 256 +registers[7] = F3 +SP = F3 +memory[SP] +FF: 00 +FE: 00 +FD: 00 +FC: 00 +FB: 00 +FA: 00 +F9: 00 +F8: 00 +F7: 00 +F6: 00 +F5: 00 +F4: 00 +F3: 42 <--- SP +F2: 42 +F1: 42 +F0: 00 +EF: 00 +EE: 00 +ED: 00 +EC: 00 +. +. +. +2B: 42 +2A: 42 +. +. +. +10: 42 +9: 42 +8: 42 +7: JUMP +6: R3 +5: PUSH +4: R3 +3: 42 +2: SAVE +1: PRINT_TIM +0: PRINT_TIM +R0: 99 +R1: 42 +R3: 42 +PUSH R0: +- decrement the SP +- copy the value from the given register +PUSH R1: +- decrement the SP +- copy the value from the given register +POP R3: +- copy into the register +- increment SP +PUSH R0: +- decrement the SP +- copy the value from the given register +POP R3: +- copy into the register +- increment SP +Stack +700: 4 +699: 2 +698: 3 +697: 6 +696: 6 +695: 7 <-- SP +694: 3 +693: 6 +registers[4] = 6 +a = 4 +def mult(x, y): + z = x * y + return z +def main(): + a = 2 + b = 3 + c = a * b + d = mult(a, b) + e = 7 diff --git a/class_example.py b/class_example.py new file mode 100644 index 000000000..92fd33e9d --- /dev/null +++ b/class_example.py @@ -0,0 +1,144 @@ +PRINT_TIM = 0b00000001 +HALT = 0b00000010 +PRINT_NUM = 0b00000011 # a 2-byte command, takes 1 argument +SAVE = 0b00000100 # a 3-byte command, takes 2 arguments +PRINT_REG = 0b00000101 +PRINT_SUM = 0b00000110 +​ +​ +# a data-driven machine +# function call +# a "variable" == registers for our programs to save things into +​ +​ +# RAM +memory = [ + PRINT_TIM, + PRINT_TIM, + PRINT_NUM, # print 99 or some other number + 42, + SAVE, # save 99 into register 2 # <-- PC + 99, # the number to save + 2, # the register to put it into + PRINT_REG, + 2, + PRINT_SUM, # R1 + R2 + HALT, +] +​ +​ +# registers, R0-R7 +registers = [0] * 8 +​ +running = True +​ +# program counter +pc = 0 +​ +while running: + command = memory[pc] +​ +​ +if command == PRINT_TIM: + print('tim!') +​ +elif command == PRINT_NUM: + num_to_print = memory[pc + 1] # we already incremented PC! + print(num_to_print) +​ +pc += 1 # but increment again +​ +​ +elif command == SAVE: + num_to_save = memory[pc + 1] + register_address = memory[pc + 2] +​ +registers[register_address] = num_to_save +​ +# shorter: +# registers[memory + 2] = memory[pc + 1] +​ +pc += 2 +​ +elif command == PRINT_REG: + reg_address = memory[pc + 1] +​ +saved_number = registers[reg_address] +​ +print(saved_number) +​ +# print(registers[memory[pc + 1]]) +​ +​ +elif command == HALT: + running = False +​ +​number +pc += 1 # so we don't get sucked into an infinite loop! + +# masking +# >> and use & with ob1 to essentially delete any higher bits +ob1010 +& ob0000 +------ + +0b1010 +& 0b0011 zeros where you don't care, ones where you do +------ +0b0010 + +shift to the right, then masking +v + +10100000 >> 5 +101 +00000101 + +00000101 +&00000001 +-------- +00000001 + + +# LOADING STUFF +# file i/o in python +try: + if len(sys.argv) < 2: + print(f'error from {sys.argv[0]}: {sys.argv[1]} not found') + sys.exit(1) + + # add a counter that loads memory at that index + ram_index = 0 + + with open(file_name, sys.argv[1]) as f: + for line in f: + print(line.split("#")[0]) + print(split_line.strip()) + # how to only get the numbers + if stripped_split_line != "": + print(stripped_split_line) + command = int(stripped_split_line, 2) + # loead command into memory + memory[ram_index] = command + ram_index += 1 + +except FileNotFoundError: + print(f"could not find that file {sys.argv[1]}") + +file = open("print8.ls8", 'r') +lines = file.read() + +print(lines) +# make sure you close files +file.close() + +# read in filename from command line + +memory = [0] * 256 + + +elif command = ADD: + reg1_address = memory[pc + 1] + reg2_address = memory[pc + 2] + + registers[reg1_address] += registers[reg2_address] diff --git a/class_example2.py b/class_example2.py new file mode 100644 index 000000000..6462cc7c6 --- /dev/null +++ b/class_example2.py @@ -0,0 +1,192 @@ +import sys +​ +PRINT_TIM = 0b00000001 +HALT = 0b00000010 +PRINT_NUM = 0b01000011 # a 2-byte command, takes 1 argument +SAVE = 0b10000100 # a 3-byte command, takes 2 arguments +PRINT_REG = 0b01000101 +ADD = 0b10100110 +PUSH = 0b0100111 +POP = 0b01001000 +​ +​ +# a data-driven machine +# function call +# a "variable" == registers for our programs to save things into +​ +​ +# RAM +memory = [0] * 256 +​ +# registers, R0-R7 +registers = [0] * 8 +registers[7] += 0xF4​ +running = True +​ +# program counter +pc = 0 +​ + + +def load_ram(): + try: + if len(sys.argv) < 2: + print(f'Error from {sys.argv[0]}: missing filename argument') + print(f'Usage: python3 {sys.argv[0]} ') + sys.exit(1) + + +​ +​ +# add a counter that adds to memory at that index +ram_index = 0 +​ +with open(sys.argv[1]) as f: + for line in f: + split_line = line.split("#")[0] + stripped_split_line = split_line.strip() +​ +if stripped_split_line != "": + command = int(stripped_split_line, 2) + + # load command into memory + memory[ram_index] = command +​ +ram_index += 1 +​ +except FileNotFoundError: + print(f'Error from {sys.argv[0]}: {sys.argv[1]} not found') + print("(Did you double check the file name?)") +​ +load_ram() +​ +while running: + command = memory[pc] +​ +​ +if command == PRINT_TIM: + print('tim!') +​ +elif command == PRINT_NUM: + num_to_print = memory[pc + 1] # we already incremented PC! + print(num_to_print) +​ +# pc += 1 # but increment again +​ +​ +elif command == SAVE: + num_to_save = memory[pc + 1] + register_address = memory[pc + 2] +​ +registers[register_address] = num_to_save +​ +# shorter: +# registers[memory + 2] = memory[pc + 1] +​ +# pc += 2 +​ +elif command == PRINT_REG: + reg_address = memory[pc + 1] +​ +saved_number = registers[reg_address] +​ +print(saved_number) +​ +# print(registers[memory[pc + 1]]) +​ +elif command == ADD: + reg1_address = memory[pc + 1] + reg2_address = memory[pc + 2] +​ +registers[reg1_address] += registers[reg2_address] +​ +elif command == HALT: + running = False + +elif command == PUSH +# decrement the stack pointer +# at address f4 +# can just do arithmetic, F4 - 1 +# R7 is our sp, has value f4 +registers[7] -= 1 + +# copy value from given register into address pointed to by SP +# value from register +register_address = memory[pc+1] +value = registers[register_address] + +# copy into sp address +# copy this value into memory, +# put it where stack pointer wants us to put it. +SP = register[7] +memory[SP] = value + +elif command = "POP": + # copy value from address pointed to sp to the given register + # get stack pointer and get value from memory at sp address + SP = registers[7] + value = memory[SP] + + # get register address + # aka where should we put this value from ram + register_address = memory[pc+1] + + # put value in that register + register[register_address] = value + + # increment the stack pointer + registers[7] += 1 + + +​ +number_of_operands = command >> 6 +pc += (1 + number_of_operands) +# pc += 1 # so we don't get sucked into an infinite loop! + + +# 01000111 +# 00000010 #from r2 + + +# making call and return + +CALL = 0b01001001 +RET = 0b00001010 + + +def call(self): + # step 1:push the return address onto the stack + # find the address/index of the command AFTER Call + next_command_address = pc + 2 + + # push the address onto the stack + # decrement the stack pointer + reg[7] -= 1 + # put the next command address at the location in memory where the stack pointer points + SP = reg[7] + memory[SP] = next_command_address + + # step 2, jump, set the PC to wherever the registeer says + # find the number of the register to look at + register_num_jump = memory[pc+1] + + # get address of the subroutine out fo that register + address_to_jump_to = registers[register_num_jump] + + # set the pc + pc = address_to_jump_to + + elif command == RET: + # pop the value from the top of the stack and store it in the pc + + # pop from the top of the stack + # get the value first + SP = registers[7] + return_address = memory[SP] + + # then move the stack pointer up + registers[7] += 1 + + # step 2, jump back, set the pc to this value + pc = return_address diff --git a/ls8/README.md b/ls8/README.md index d9170d968..e86aaf318 100644 --- a/ls8/README.md +++ b/ls8/README.md @@ -60,8 +60,12 @@ but you'll have to implement those three above instructions first! ## Step 0: IMPORTANT: inventory what is here! * Make a list of files here. + We have a ls8 file and a cpu file. * Write a short 3-10-word description of what each file does. + The cpu file is setting up a cpu class with methods. + The ls8 file calls the class and runs the Run method on the cpu class. * Note what has been implemented, and what hasn't. + Alu and trace have some work done, but looks like the rest need built out≥ * Read this whole file. * Skim the spec. @@ -478,3 +482,20 @@ Doing this correctly requires implementing `CMP`, and some comparative forms of Hint: Look in the `asm/` directory and learn how to use the `asm.js` assembler. This way you can write your code in assembly language and use the assembler to build it to machine code and then run it on your emulator. + + +What we have built: +1. Ram - self.ram +2. registers +3. CPU - self.run() +4. ALU +5. Why ls8? - All instructions are 8 bits. + Also, 8-bit CPU and ALU architectures are those that are based on registers, address buses, and data buses of that size. + + Address bus is the 8 wires running from cpu to ram + With 8 bits, the cpu can address 256 bytes of ram + To have more ram you need more wires. + + General calculating machines, vs specialized calculating machines. + "computer" "calculator" + \ No newline at end of file diff --git a/ls8/cpu.py b/ls8/cpu.py index 9a307496e..df5e3e3e9 100644 --- a/ls8/cpu.py +++ b/ls8/cpu.py @@ -2,12 +2,33 @@ import sys + class CPU: """Main CPU class.""" def __init__(self): """Construct a new CPU.""" - pass + self.ram = [0] * 256 + self.reg = [0] * 8 + self.pc = 0 + self.flag = 0b00000000 + self.reg[7] = 0xf4 + self.running = False + self.branchTable = { + 130: self.ldi, + 71: self.prn, + 162: self.mult, + 160: self.add, + 1: self.halt, + 85: self.jeq_run, + 86: self.jne_run, + 69: self.pushy, + 70: self.poppy, + 80: self.cal, + 17: self.retrn, + 84: self.jump, + 167: self.cmp_run + } def load(self): """Load a program into memory.""" @@ -15,28 +36,69 @@ def load(self): 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 - - - def alu(self, op, reg_a, reg_b): + try: + if len(sys.argv) < 2: + print(f'Error from {sys.argv[0]}): missing file name argument') + sys.exit(1) + + with open(sys.argv[1]) as f: + code_line = 0 + for line in f: + split_line = line.split("#")[0] + stripped_line = split_line.strip() + # print(int(stripped_line, 2)) + if stripped_line != "": + command = int(stripped_line, 2) + # print(command, code_line) + + self.ram[address] = command + code_line += 1 + address += 1 + except FileNotFoundError: + print(f'Error from {sys.argv[0]}: {sys.argv[1]} not found') + print("(Did you double check the file name?)") + # 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): """ALU operations.""" - + reg_a = self.ram_read(self.pc+1) + reg_b = self.ram_read(self.pc+2) if op == "ADD": self.reg[reg_a] += self.reg[reg_b] - #elif op == "SUB": etc + self.pc += 3 + + elif op == "SUB": + self.reg[reg_a] -= self.reg[reg_b] + self.pc += 2 + elif op == 'MUL': + # print("multiply") + result = self.reg[reg_a] * self.reg[reg_b] + self.pc += 2 + return result + elif op == "CMP": + # print("reg_a vs reg_b", reg_a, reg_b) + if self.reg[reg_a] < self.reg[reg_b]: + # print("less than") + self.flag = 0b00000100 + if self.reg[reg_a] > self.reg[reg_b]: + # print("greater than") + self.flag = 0b00000010 + if self.reg[reg_a] == self.reg[reg_b]: + # print("equal") + self.flag = 0b00000001 + self.pc += 3 else: raise Exception("Unsupported ALU operation") @@ -48,8 +110,8 @@ def trace(self): print(f"TRACE: %02X | %02X %02X %02X |" % ( self.pc, - #self.fl, - #self.ie, + # self.fl, + # self.ie, self.ram_read(self.pc), self.ram_read(self.pc + 1), self.ram_read(self.pc + 2) @@ -60,6 +122,131 @@ def trace(self): print() + def ldi(self): + self.reg[self.ram[self.pc+1]] = self.ram[self.pc+2] + self.pc += 3 + + def prn(self): + print("Printing", self.reg[self.ram[self.pc+1]]) + self.pc += 2 + + def halt(self): + # print("Halt") + self.running = False + + def mult(self): + print("alu", self.alu("MUL")) + + def add(self): + print("alu add", self.alu("ADD")) + + def pushy(self): + self.reg[7] -= 1 + register_address = self.ram[self.pc + 1] + value = self.reg[register_address] + self.ram[self.reg[7]] = value + self.pc += 2 + + def poppy(self): + value = self.ram[self.reg[7]] + register_address = self.ram[self.pc + 1] + self.reg[register_address] = value + self.reg[7] += 1 + self.pc += 2 + + def cal(self): + next_command_address = self.pc + 2 + self.reg[7] -= 1 + SP = self.reg[7] + self.ram[SP] = next_command_address + register_num_jump = self.ram[self.pc + 1] + address_to_jump_to = self.reg[register_num_jump] + self.pc = address_to_jump_to + + def retrn(self): + SP = self.reg[7] + return_address = self.ram[SP] + self.reg[7] += 1 + self.pc = return_address + + def cmp_run(self): + print("alu cmp", self.alu("CMP")) + + def jeq_run(self): + if self.flag == 1: + # print("it is true") + address = self.ram[self.pc + 1] + jump_address = self.reg[address] + self.pc = jump_address + else: + self.pc += 2 + + def jne_run(self): + # print("JNE") + if self.flag == 4 or self.flag == 2: + # print("jne true") + address = self.ram[self.pc + 1] + # print(address) + jump_address = self.reg[address] + # print("jump address", jump_address) + self.pc = jump_address + else: + self.pc += 2 + + def jump(self): + # print("JUMP") + given_address = self.ram[self.pc + 1] + address_to_jump_to = self.reg[given_address] + # print(address_to_jump_to) + self.pc = address_to_jump_to + def run(self): - """Run the CPU.""" - pass + self.running = True + + while self.running: + instruction = self.ram_read(self.pc) + # print(instruction, "instruction") + # operand_a = self.ram_read(self.pc+1) + # operand_b = self.ram_read(self.pc+2) + try: + self.branchTable[instruction]() + + except Exception: + print( + f'Could not find that particular instruction.{instruction}') + self.running = False + break + + # def run(self): + # ldi = 130 + # prn = 71 + # mul = 162 + # hlt = 1 + # """Run the CPU.""" + # self.running = True + + # while self.running: + # instruction = self.ram_read(self.pc) + # operand_a = self.ram_read(self.pc+1) + # operand_b = self.ram_read(self.pc+2) + # if instruction == ldi: + # self.reg[operand_a] = operand_b + # self.pc += 3 + # print("first loop") + # elif instruction == prn: + # print("Printing", self.reg[operand_a]) + # self.pc += 2 + # elif instruction == hlt: + # print("Halt") + # self.running = False + # elif instruction == mul: + # print("alu", self.alu("MUL", operand_a, operand_b)) + # else: + # print( + # f'Could not find that particular instruction.{instruction}') + + def ram_read(self, mar): + return self.ram[mar] + + def ram_write(self, mar, mdr): + self.ram[mar] = mdr diff --git a/ls8/examples/print8.ls8 b/ls8/examples/print8.ls8 index e3b3457cf..d6aa4528b 100644 --- a/ls8/examples/print8.ls8 +++ b/ls8/examples/print8.ls8 @@ -9,3 +9,5 @@ 01000111 # PRN R0 00000000 00000001 # HLT + + diff --git a/ls8/our_program2.ls8 b/ls8/our_program2.ls8 new file mode 100644 index 000000000..9e0cd18b1 --- /dev/null +++ b/ls8/our_program2.ls8 @@ -0,0 +1,10 @@ +PRINT_NUM +42 + +CALL + +HALT + +PRINT_NUM +99 +RET \ No newline at end of file diff --git a/ls8/test.py b/ls8/test.py new file mode 100644 index 000000000..d12afeea8 --- /dev/null +++ b/ls8/test.py @@ -0,0 +1,29 @@ +OP1 = 0b10101010 +OP2 = 0b11110000 + + +class Foo: + + def __init__(self): + # Set up the branch table + self.branchtable = {} + self.branchtable[OP1] = self.handle_op1 + self.branchtable[OP2] = self.handle_op2 + + def handle_op1(self, a): + print("op 1: " + a) + + def handle_op2(self, a): + print("op 2: " + a) + + def run(self): + # Example calls into the branch table + ir = OP1 + self.branchtable[ir]("foo") + + ir = OP2 + self.branchtable[ir]("bar") + + +c = Foo() +c.run()