Skip to content

Aaron verdine #239

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 21 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
46 changes: 24 additions & 22 deletions ls8/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,21 @@ then prints it out:

The binary numeric value on the left in the `print8.ls8` code above is either:

* the machine code value of the instruction (e.g. `10000010` for `LDI`), also
- the machine code value of the instruction (e.g. `10000010` for `LDI`), also
known as the _opcode_

or

* one of the opcode's arguments (e.g. `00000000` for `R0` or `00001000` for the
- one of the opcode's arguments (e.g. `00000000` for `R0` or `00001000` for the
value `8`), also known as the _operands_.

This code above requires the implementation of three instructions:

* `LDI`: load "immediate", store a value in a register, or "set this register to
- `LDI`: load "immediate", store a value in a register, or "set this register to
this value".
* `PRN`: a pseudo-instruction that prints the numeric value stored in a
- `PRN`: a pseudo-instruction that prints the numeric value stored in a
register.
* `HLT`: halt the CPU and exit the emulator.
- `HLT`: halt the CPU and exit the emulator.

See [the LS-8 spec](../LS8-spec.md) for more details.

Expand All @@ -59,11 +59,13 @@ but you'll have to implement those three above instructions first!

## Step 0: IMPORTANT: inventory what is here!

* Make a list of files here.
* Write a short 3-10-word description of what each file does.
* Note what has been implemented, and what hasn't.
* Read this whole file.
* Skim the spec.
- Make a list of files here.
1. cpu.py - Defines a CPU class that emulates a LS-8 Microcomputer
2. ls8.py - Uses a CPU nstance to run a program
- Write a short 3-10-word description of what each file does.
- Note what has been implemented, and what hasn't.
- Read this whole file.
- Skim the spec.

## Step 1: Add the constructor to `cpu.py`

Expand Down Expand Up @@ -134,7 +136,7 @@ name instead of by numeric value.

In `run()` in your if-else block, exit the loop if a `HLT` instruction is
encountered, regardless of whether or not there are more lines of code in the
LS-8 program you loaded.
LS-8 program you loaded.

We can consider `HLT` to be similar to Python's `exit()` in that we stop
whatever we are doing, wherever we are.
Expand All @@ -151,8 +153,8 @@ value.
This is a very similar process to adding `LDI`, but the handler is simpler. See
the LS-8 spec.

*At this point, you should be able to run the program and have it print `8` to
the console!*
_At this point, you should be able to run the program and have it print `8` to
the console!_

## Step 7: Un-hardcode the machine code

Expand Down Expand Up @@ -194,7 +196,7 @@ so you can look in `sys.argv[1]` for the name of the file to load.
> expect, and print an error and exit if they didn't.

In `load()`, you will now want to use those command line arguments to open a
file, read in its contents line by line, and save appropriate data into RAM.
file, read in its contents line by line, and save appropriate data into RAM.

As you process lines from the file, you should be on the lookout for blank lines
(ignore them), and you should ignore everything after a `#`, since that's a
Expand Down Expand Up @@ -296,10 +298,10 @@ a high address) and grows _downward_ as things are pushed on. The LS-8 is no
exception to this.

Implement a system stack per the spec. Add `PUSH` and `POP` instructions. Read
the beginning of the spec to see which register is the stack pointer.
* Values themselves should be saved in the ***portion of RAM*** _that is allocated for the stack_.
- Use the stack pointer to modify the correct block of memory.
the beginning of the spec to see which register is the stack pointer.

- Values themselves should be saved in the **_portion of RAM_** _that is allocated for the stack_.
- Use the stack pointer to modify the correct block of memory.
- Make sure you update the stack pointer appropriately as you `PUSH` and `POP` items to and from the stack.

If you run `python3 ls8.py examples/stack.ls8` you should see the output:
Expand All @@ -320,21 +322,21 @@ enables you to create reusable functions.
Subroutines have many similarities to functions in higher-level languages. Just
as a function in C, JavaScript or Python will jump from the function call, to
its definition, and then return back to the line of code following the call,
subroutines will also allow us to execute instructions non-sequentially.
subroutines will also allow us to execute instructions non-sequentially.

The stack is used to hold the return address used by `RET`, so you **must**
implement the stack in step 10, first. Then, add subroutine instructions `CALL`
and `RET`.

* For `CALL`, you will likely have to modify your handler call in `cpu_run()`.
- For `CALL`, you will likely have to modify your handler call in `cpu_run()`.
The problem is that some instructions want to execute and move to the next
instruction like normal, but others, like `CALL` and `JMP` want to go to a
specific address.

> Note: `CALL` is very similar to the `JMP` instruction. However, there is one
> key difference between them. Can you find it in the specs?
> key difference between them. Can you find it in the specs?

* In **any** case where the instruction handler sets the `PC` directly, you
- In **any** case where the instruction handler sets the `PC` directly, you
_don't_ want to advance the PC to the next instruction. So you'll have to
set up a special case for those types of instructions. This can be a flag
you explicitly set per-instruction... but can also be computed from the
Expand Down
255 changes: 238 additions & 17 deletions ls8/cpu.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,143 @@
"""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
AND = 0b10101000
OR = 0b10101010
XOR = 0b10101011
NOT = 0b01101001
SHL = 0b10101100
SHR = 0b10101101
MOD = 0b10100100
ADDI = 0b10101110

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

#RO-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 op 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
self.branchtable[AND] = self.execute_AND
self.branchtable[OR] = self.execute_OR
self.branchtable[XOR] = self.execute_XOR
self.branchtable[NOT] = self.execute_NOT
self.branchtable[SHL] = self.execute_SHL
self.branchtable[SHR] = self.execute_SHR
self.branchtable[MOD] = self.execute_MOD
self.branchtable[ADDI] = self.execute_ADDI



# Property wrapper for stack pointers
@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

def load(self):
@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 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
]
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

for instruction in program:
self.ram[address] = instruction
address += 1
except:
print(f'Could not find file named: {file_name}')
sys.exit(1)


def alu(self, op, reg_a, reg_b):
Expand Down Expand Up @@ -59,7 +168,119 @@ def trace(self):
print(" %02X" % self.reg[i], end='')

print()

# Run Loop

def run(self):
"""Run the CPU."""
pass

while not self.halted:

# Fetch instructions
self.ir = self.ram_read(self.pc)

# Execute instructions
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)

# Conditionally increment program counter
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: Could not execute intsruction: {self.ir}")
sys.exit(1)

# Define operations to be loaded in the branch table

def execute_HLT(self, a=None, b=None):
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.mdr = self.ram_read(self.sp)
self.pc = self.mdr
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

def execute_AND(self):
self.reg[sel.operand_a] &= self.reg[self.operand_b]

def execute_OR(self):
self.reg[self.operand_a] |= self.reg[self.operand_b]

def execute_XOR(self):
self.reg[self.operand_a] ^= self.reg[self.operand_b]

def execute_NOT(self):
self.reg[self.operand_a] = ~self.reg[self.operand_a]

def execute_SHL(self):
self.reg[self.operand_a] <<= self.reg[self.operand_b]

def execute_SHR(self):
self.reg[self.operand_a] >>= self.reg[self.operand_b]

def execute_MOD(self):
self.reg[self.operand_a] %= self.reg[self.operand_b]

def execute_ADDI(self):
self.reg[self.operand_a] += self.operand_b



Loading