Skip to content

Computer Architecture | Oliver Abreu #271

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
23 changes: 7 additions & 16 deletions ls8/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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:
>
Expand All @@ -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.
Expand Down
211 changes: 188 additions & 23 deletions ls8/cpu.py
Original file line number Diff line number Diff line change
@@ -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")

Expand All @@ -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)
Expand All @@ -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
10 changes: 8 additions & 2 deletions ls8/ls8.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,11 @@

cpu = CPU()

cpu.load()
cpu.run()
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()