diff --git a/README.md b/README.md index 20c417457..64dd7b2b3 100644 --- a/README.md +++ b/README.md @@ -2,35 +2,35 @@ ## Project -* [Implement the LS-8 Emulator](ls8/) +- [Implement the LS-8 Emulator](ls8/) ## Task List: add this to the first comment of your Pull Request ### Day 1: Get `print8.ls8` running -- [ ] Inventory what is here -- [ ] Implement the `CPU` constructor -- [ ] Add RAM functions `ram_read()` and `ram_write()` -- [ ] Implement the core of `run()` -- [ ] Implement the `HLT` instruction handler -- [ ] Add the `LDI` instruction -- [ ] Add the `PRN` instruction +- [x] Inventory what is here +- [x] Implement the `CPU` constructor +- [x] Add RAM functions `ram_read()` and `ram_write()` +- [x] Implement the core of `run()` +- [x] Implement the `HLT` instruction handler +- [x] Add the `LDI` instruction +- [x] Add the `PRN` instruction ### Day 2: Add the ability to load files dynamically, get `mult.ls8` running -- [ ] Un-hardcode the machine code -- [ ] Implement the `load()` function to load an `.ls8` file given the filename +- [x] Un-hardcode the machine code +- [x] Implement the `load()` function to load an `.ls8` file given the filename passed in as an argument -- [ ] Implement a Multiply instruction (run `mult.ls8`) +- [x] Implement a Multiply instruction (run `mult.ls8`) ### Day 3: Stack -- [ ] Implement the System Stack and be able to run the `stack.ls8` program +- [x] Implement the System Stack and be able to run the `stack.ls8` program ### Day 4: Get `call.ls8` running -- [ ] Implement the CALL and RET instructions -- [ ] Implement Subroutine Calls and be able to run the `call.ls8` program +- [x] Implement the CALL and RET instructions +- [x] Implement Subroutine Calls and be able to run the `call.ls8` program ### Stretch diff --git a/ls8/README.md b/ls8/README.md index d9170d968..811d6031c 100644 --- a/ls8/README.md +++ b/ls8/README.md @@ -5,17 +5,12 @@ _Objective_: to gain a deeper understanding of how a CPU functions at a low level. -We're going to write an emulator for the world-famous LambdaSchool-8 computer, -otherwise known as LS-8! This is an 8-bit computer with 8-bit memory addressing, -which is about as simple as it gets. +We're going to write an emulator for the world-famous LambdaSchool-8 computer, otherwise known as LS-8! This is an 8-bit computer with 8-bit memory addressing, which is about as simple as it gets. -An 8 bit CPU is one that only has 8 wires available for addresses (specifying -where something is in memory), computations, and instructions. With 8 bits, our -CPU has a total of 256 bytes of memory and can only compute values up to 255. +An 8 bit CPU is one that only has 8 wires available for addresses (specifying where something is in memory), computations, and instructions. With 8 bits, our CPU has a total of 256 bytes of memory and can only compute values up to 255. The CPU could support 256 instructions, as well, but we won't need them. -For starters, we'll execute code that stores the value 8 in a register, -then prints it out: +For starters, we'll execute code that stores the value 8 in a register, then prints it out: ``` # print8.ls8: Print the number 8 on the screen @@ -48,8 +43,7 @@ This code above requires the implementation of three instructions: See [the LS-8 spec](../LS8-spec.md) for more details. -The above program is already hardcoded into the source file `cpu.py`. To run it, -you will eventually: +The above program is already hardcoded into the source file `cpu.py`. To run it, you will eventually: ``` python3 ls8.py @@ -67,8 +61,7 @@ but you'll have to implement those three above instructions first! ## Step 1: Add the constructor to `cpu.py` -Add list properties to the `CPU` class to hold 256 bytes of memory and 8 -general-purpose registers. +Add list properties to the `CPU` class to hold 256 bytes of memory and 8 general-purpose registers. > Hint: you can make a list of a certain number of zeros with this syntax: > @@ -78,13 +71,11 @@ general-purpose registers. Also add properties for any internal registers you need, e.g. `PC`. -Later on, you might do further initialization here, e.g. setting the initial -value of the stack pointer. +Later on, you might do further initialization here, e.g. setting the initial value of the stack pointer. ## Step 2: Add RAM functions -In `CPU`, add method `ram_read()` and `ram_write()` that access the RAM inside -the `CPU` object. +In `CPU`, add method `ram_read()` and `ram_write()` that access the RAM inside the `CPU` object. `ram_read()` should accept the address to read and return the value stored there. diff --git a/ls8/cpu.py b/ls8/cpu.py index 9a307496e..56406f230 100644 --- a/ls8/cpu.py +++ b/ls8/cpu.py @@ -1,42 +1,138 @@ """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 + self.ram = [0] * 256 + # GENERAL-PURPOSE REGISTERS: + self.reg = [0] * 8 + # --> R0 + # --> R1 + # --> R2 + # --> R3 + # --> R4 + # --> R5: RESERVED FOR INTERRUPT MASK (IM) + # --> R6: RESERVED FOR INTERRUPT STATUS (IS) + # --> R7: RESERVED FOR STACK POINTER (SP) + + # INTERNAL REGISTERS: + # --> (PC) PROGRAM COUNTER --------- ADDRESS OF THE CURRENTLY EXECUTING INSTRUCTION + self.pc = 0 + # --> (IR) INSTRUCTION REGISTER ---- CONTAINS A COPY OF THE CURRENTLY EXECUTING INSTRUCTION + self.ir = 0 + # --> (MAR) MEMORY ADDRESS REGISTER - HOLDS THE MEMORY ADDRESS CURRENTLY BEING READ OR WRITTEN + self.mar = 0 + # --> (MDR) MEMORY DATA REGISTER ---- HOLDS THE VALUE TO WRITE OR THE VALUE JUST READ + self.mdr = 0 + # --> (FL) FLAG REGISTER ----------- HOLDS THE CURRENT FLAG STATUS + self.fl = 0 + + self.running = True + + # INITIALIZE THE STACKPOINTER + self.reg[7] = 0xF4 + + 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 + def sp(self): + return self.reg[7] + + @sp.setter + def sp(self, a): + self.reg[7] = a & 0xFF + + @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 + + # `RAM_READ()` - SHOULD ACCEPT THE ADDRESS TO READ AND RETURN THE VALUE STORED + + def ram_read(self, mar): + if mar >= 0 and mar < len(self.ram): + return self.ram[mar] + else: + print( + f"Error: No memory at address '{mar}' ") + return -1 + + # `RAM_WRITE()` - SHOULD ACCEPT A VALUE TO WRITE AND THE ADDRESS TO WRITE IT TO + def ram_write(self, mar, mdr): + if mar >= 0 and mar < len(self.ram): + self.ram[mar] = mdr & 0xFF + else: + print(f"Error: Unable to write to memory at address '{mar}' ") - def load(self): + def load(self, filename): """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__), filename) + try: + with open(file_path) as file: + for line in file: + num = line.split("#")[0].strip() + try: + instruction = int(num, 2) + self.ram[address] = instruction + address += 1 + except: + continue + except: + print(f"Could not find file with name '{filename}' ") + sys.exit(1) def alu(self, op, reg_a, reg_b): """ALU operations.""" if op == "ADD": self.reg[reg_a] += self.reg[reg_b] - #elif op == "SUB": etc + # elif op == "SUB": etc else: raise Exception("Unsupported ALU operation") @@ -48,8 +144,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) @@ -62,4 +158,73 @@ def trace(self): def run(self): """Run the CPU.""" - pass + while self.running: + + self.ir = self.ram_read(self.pc) + + if self.ir in self.branchtable: + self.branchtable[self.ir]() + else: + print(f"Operation '{self.ir}' could be found") + sys.exit(1) + + # ENSURE THAT THE PROGRAM COUNTER IS INCREMENTED + if not self.instruction_sets_pc: + self.pc += self.instruction_size + + 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/ls8.py b/ls8/ls8.py index 74128d36b..559671379 100755 --- a/ls8/ls8.py +++ b/ls8/ls8.py @@ -7,5 +7,11 @@ cpu = CPU() -cpu.load() -cpu.run() \ No newline at end of file +if len(sys.argv) != 2: + print(f"Error from {sys.argv[0]}: {sys.argv[1]} not found") + sys.exit(1) +else: + file_name = sys.argv[1] + +cpu.load(file_name) +cpu.run()