Skip to content
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

Day 1 done #251

Open
wants to merge 5 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
2 changes: 1 addition & 1 deletion LS8-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

## Internal Registers

* `PC`: Program Counter, address of the currently executing instruction
* `PC`: Program Counter, address of the currently executing instruction
* `IR`: Instruction Register, contains a copy of the currently executing instruction
* `MAR`: Memory Address Register, holds the memory address we're reading or writing
* `MDR`: Memory Data Register, holds the value to write or the value just read
Expand Down
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@

### 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

Expand Down
Empty file added instructions.py
Empty file.
43 changes: 43 additions & 0 deletions inventory.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
Inventory Step

CS Computer Architecture Repo asm --- ls8 --- examples folder (call, interrupts, keyboard, mult, print8, printstr, sctest, stack, stackoverflow) ---
README: LS8 emulator project details --- cpu.py: CPU class (needs to be developed) --- ls8.py: where file will be run FAQ LS8-cheatsheet.md
LS8-spec.md: Registers, Internal Registers, Flags, Stack, Interrupts, README.md - objectives for modules 1-4

ADD regA regB: add reg A and reg B, store in reg A
AND regA regB: bitwise-AND values in reg A and B, store in reg A
CALL register: call register
CMP regA regB: compare reg A and reg B, for equal, less than, greater than
DEC register: decrement the value in the register
DIV regA regB: reg A/reg B, store in reg A, if 0 print error and halt
HLT: halts CPU and exits emulator
INC register: increment the value in the given register
INT register: issue interrupt number stored in given register
IRET: return from interrupt handler
JEQ register: if equal flag is set to true, jump to address stored in given register
JGE register: if greater than flag or equal flag is set to true, jump to address stored in given register
JLE register: if less than or equal flag is set to true, jump to address stored in given register
JLT register: if less than flag is set to true, jump to address stored in given register
JMP register: jump to address stored in given register
JNE register: if equal flag is clear (false, 0) jump to address stored in given register
LD regA regB: loads regA with value at memory address stored in regB
LDI reg immediate: set value of a register to an interger
MOD regA regB (this instruction is handled by the ALU): divide value in the first register by the value in the second, storing the REMAINDER of the result in regA
MUL regA regB (this instruction is handled by the ALU): multiply the values in two registers together and store result in regA
NOP: no operation, do nothing for this instruction
NOT register (this instruction is handled by the ALU): perform bitwise not on value in register
OR regA regB (this is an instruction handled by the ALU): perform a bitwise-OR between values in regA and regB, storing result in regA
POP register: pop value at top of stack into given register
1. copy value from address (pointed to by SP) to the given register
2. increment SP
PRA register (pseudo-instruction): print alpha character value stored in the given register. print to the colsole the ASCII character corresponding to the value in the register
PRN register (pseudo-instruction): print numeric value store in the given register. print to the console the decimal integer value that is stored in the given register
PUSH register: push the value in the given register on the stack
1. decrement the SP
2. copy the value in the given register to the address pointed to by SP
RET: return from subroutine. pop value from top of stack and store in PC
SHL (this is an instruction handled by the ALU): shift the value in regA left by the number of bits specified in regB, filling the low bits with 0
SHR (this is an instruction handled by the ALU): shift the value in regA right by the number of bits specified in regB, filling the highest bits with 0
ST regA regB: store value in regB in the address stored in regA
SUB regA regB (this is an instruction handled by the ALU): subtract the value in the second reg from the first, storing result in regA
XOR regA regB: perform a bitwise-XOR between the values in regA and regB, storing result in regA
286 changes: 258 additions & 28 deletions ls8/cpu.py
Original file line number Diff line number Diff line change
@@ -1,65 +1,295 @@
"""CPU functionality."""

import sys
import time

HLT = 0b00000001
LDI = 0b10000010
PRN = 0b01000111
MUL = 0b10100010
ADD = 0b10100000
PUSH = 0b01000101
POP = 0b01000110
CALL = 0b01010000
RET = 0b00010001

# sprint challenge for MVP
CMP = 0b10100111
JEQ = 0b01010101
JNE = 0b01010110
JMP = 0b01010100

# stretch opcodes
SHL = 0b10101100
SHR = 0b10101101
MOD = 0b10100100
NOT = 0b01101001
OR = 0b10101010
AND = 0b10101000
XOR = 0b10101011


class CPU:
"""Main CPU class."""

def __init__(self):
"""Construct a new CPU."""
pass
self.ram = [0] * 256
self.reg = [0] * 8
self.reg[7] = 0xF4
# flags reg
self.reg[6] = 0
self.pc = 0
self.running = True
# all flags set to false on initialization
self.fl = 0b00000000
self.branchtable = {
HLT: self.hlt,
LDI: self.ldi,
PRN: self.prn,
PUSH: self.push,
POP: self.pop,
CALL: self.call,
RET: self.ret,
JEQ: self.jeq,
JNE: self.jne,
JMP: self.jmp,
}


def load(self):
"""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
if (len(sys.argv)) != 2:
print('remember to pass the second file name')
print('usage: python fileio.py <second_filename.py>')
sys.exit()
try:
with open(sys.argv[1]) as f:
address = 0
for line in f:
possible_binary = line[:line.find('#')]
# if no comment on line
if possible_binary == '':
continue # passes rest of loop
denary_int = int(possible_binary, 2)
self.ram[address] = denary_int
address += 1
except FileNotFoundError:
print(f'Error from {sys.argv[0]}: {sys.argv[1]} not found ')
sys.exit()


def alu(self, op, reg_a, reg_b):
"""ALU operations."""

if op == "ADD":
if op == ADD: # add
self.reg[reg_a] += self.reg[reg_b]
#elif op == "SUB": etc
elif op == MUL: # multiply
self.reg[reg_a] *= self.reg[reg_b]
elif op == CMP: # compare
# less than
if self.reg[reg_a] < self.reg[reg_b]:
self.reg[6] = 0b00000100
# greater than
elif self.reg[reg_a] > self.reg[reg_b]:
self.reg[6] = 0b00000010
# equal to
else:
self.reg[6] = 0b00000001
elif op == SHL: # shift left
decimal = self.reg[reg_a] << self.reg[reg_b]
self.reg[reg_a] = f"{decimal:#010b}"
elif op == SHR: # shift right
decimal = self.reg[reg_a] >> self.reg[reg_b]
self.reg[reg_a] = f"{decimal:#010b}"
elif op == MOD: # modulo, get remainder
# cannot divide by 0
if self.reg[reg_b] == 0:
# halt
self.hlt(reg_a, reg_b)
self.reg[reg_a] = self.reg[reg_a] % self.reg[reg_b]
elif op == NOT: # store bitwise not
self.reg[reg_a] = ~self.reg[reg_a]
elif op == OR: # store bitwise or
self.reg[reg_a] = self.reg[reg_a] | self.reg[reg_b]
elif op == AND: # store bitwise and
self.reg[reg_a] = self.reg[reg_a] & self.reg[reg_b]
elif op == XOR: # store bitwise xor
self.reg[reg_a] = self.reg[reg_a] ^ self.reg[reg_b]
else:
raise Exception("Unsupported ALU operation")


def ram_read(self, address):
return self.ram[address]


def ram_write(self, address, data):
self.ram[address] = data


def trace(self):
"""
Handy function to print out the CPU state. You might want to call this
from run() if you need help debugging.
"""

print(f"TRACE: %02X | %02X %02X %02X |" % (
self.pc,
#self.fl,
#self.ie,
self.fl,
self.ram_read(self.pc),
self.ram_read(self.pc + 1),
self.ram_read(self.pc + 2)
), end='')

for i in range(8):
print(" %02X" % self.reg[i], end='')

print()


def run(self):
"""Run the CPU."""
pass
IR = []
self.running = True
while self.running:
IR = self.ram[self.pc] # instruction register
num_args = IR >> 6
is_alu_op = (IR >> 5) & 0b001
operand_a = self.ram_read(self.pc+1)
operand_b = self.ram_read(self.pc+2)
is_alu_op = (IR >> 5) & 0b001 == 1
if is_alu_op:
self.alu(IR, operand_a, operand_b)
else:
self.branchtable[IR](operand_a, operand_b)
# check if command sets PC directly
sets_pc = (IR >> 4) & 0b0001 == 1
if not sets_pc:
# increment pc here
self.pc += 1 + num_args


def hlt(self, operand_a, operand_b):
self.running = False


def ldi(self, operand_a, operand_b):
self.reg[operand_a] = operand_b


def prn(self, operand_a, operand_b):
print(self.reg[operand_a])


def jmp(self, operand_a, operand_b):
reg_idx = self.reg[operand_a]
self.pc = reg_idx


def jeq(self, operand_a, operand_b):
# If equal flag is set (true), jump to the
# address stored in the given register
flags = self.reg[6]
equal_is_flagged = (flags << 7) & 0b10000000 == 128
if equal_is_flagged:
# jump to address stored in reg
given_reg_value = self.reg[operand_a]
self.pc = given_reg_value
else:
# just increment
self.pc += 2


def jne(self, operand_a, operand_b):
# If E flag is clear (false, 0), jump to
# the address stored in the given register
flags = self.reg[6]
equal_is_flagged = flags == 1
if equal_is_flagged:
# just increment
self.pc += 2
else:
# jump to address stored in reg
given_reg_value = self.reg[operand_a]
self.pc = given_reg_value


def push(self, operand_a, operand_b):
# decrement the SP.
self.reg[7] -= 1
# copy the value in the given register to the address pointed to by SP
value = self.reg[operand_a]
# copy to the address at stack pointer
SP = self.reg[7]
self.ram[SP] = value


def pop(self, operand_a, operand_b):
# get SP
SP = self.reg[7]
# get address pointed to by SP
value = self.ram[SP]
# Copy the value from the address pointed to by SP to the given register.
self.reg[operand_a] = value
# increment SP
self.reg[7] += 1


def call(self, operand_a, operand_b):
# PUSH
return_address = self.pc + 2
# decrement the SP, stored in R7
self.reg[7] -= 1
# store the value at the SP address
SP = self.reg[7]
# the return address is 8 as we can see by looking ahead by 2
self.ram[SP] = return_address
# ram (command) at 7 contains 1
reg_idx = self.ram[self.pc+1]
# reg 1 contains 24
subroutine_address = self.reg[reg_idx]
# pc is at 24
self.pc = subroutine_address


def ret(self, operand_a, operand_b):
# Return from subroutine.
# Pop value from top of stack
SP = self.reg[7]
return_address = self.ram[SP]
#store it in the PC
self.pc = return_address
# increment stack
self.reg[7] += 1


def check_inter(self):
interrupts = self.reg[self.im] & self.reg[self.isr]
for interrupt in range(8):
bit = 1 << interrupt
#if an interrupt is triggered
if interrupts & bit:
# save the old interrupt state
self.old_im = self.reg[self.im]
# disable interrupts
self.reg[self.im] = 0
# clear the interrupt
self.reg[self.isr] &= (255 ^ bit)
# decrement the stack pointer
self.reg[self.sp] -= 1
# push the pc to the stack
self.ram_write(self.reg[self.sp], self.pc)
#decrement the stack pointer
self.reg[self.sp] -= 1
# push the flags to the stack
self.ram_write(self.reg[self.sp], self.fl)
# push the registers to the stack R0-R6
for i in range(7):
self.reg[self.sp] -= 1
self.ram_write(self.reg[self.sp], self.reg[i])
self.pc = self.ram[0xF8 + interrupt]
# break out and stop checking interrupts
break


if __name__ == '__main__':
cpu = CPU()
cpu.load()
cpu.run()
Loading