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

Computer architecture work + sprint #272

Open
wants to merge 8 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
59 changes: 59 additions & 0 deletions asm/stretch.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
LDI R1, 0 ; loop index
LDI R2, 7 ; number of lines to print

MainLoopStart:
CMP R1, R2
LDI R0, MainLoopEnd
JGE R0
LDI R0, 1
SHL R0, R1 ; number of chars to print
LDI R3, PrintN
CALL R3
INC R1
LDI R0, MainLoopStart
JMP R0

MainLoopEnd:
HLT

; R0 arg corresponds to number of chars to print
PrintN:
PUSH R1
PUSH R2
PUSH R3
LDI R1, 1

PrintNLoopStart:
CMP R1, R0
LDI R2, PrintNLoopEnd
JGT R2
LDI R2, PrintAsterisk
CALL R2
INC R1
LDI R2, PrintNLoopStart
JMP R2

PrintNLoopEnd:
LDI R3, Newline
LD R2, R3
PRA R2
POP R3
POP R2
POP R1
RET

PrintAsterisk:
PUSH R0
PUSH R1
LDI R0, Asterisk
LD R1, R0 ; load the * character into R0
PRA R1
POP R1
POP R0
RET

Asterisk:
ds *

Newline:
db 0x0a
256 changes: 237 additions & 19 deletions ls8/cpu.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,83 @@
"""CPU functionality."""

import sys
import time
import traceback

IM = 5
IS = 6
SP = 7

HLT = 0b0001
LDI = 0b0010
LD = 0b0011
ST = 0b0100
PUSH = 0b0101
POP = 0b0110
PRN = 0b0111
PRA = 0b1000

CALL = 0b0000
RET = 0b0001
INT = 0b0010
IRET = 0b0011
JMP = 0b0100
JEQ = 0b0101
JNE = 0b0110
JGT = 0b0111
JLT = 0b1000
JLE = 0b1001
JGE = 0b1010

ALU_ADD = 0b0000
ALU_SUB = 0b0001
ALU_MUL = 0b0010
ALU_DIV = 0b0011
ALU_MOD = 0b0100
ALU_INC = 0b0101
ALU_DEC = 0b0110
ALU_CMP = 0b0111
ALU_AND = 0b1000
ALU_NOT = 0b1001
ALU_OR = 0b1010
ALU_XOR = 0b1011
ALU_SHL = 0b1100
ALU_SHR = 0b1101

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

def __init__(self):
"""Construct a new CPU."""
pass
self.ram = [0] * 256
self.reg = [0] * 8
self.reg[SP] = 0xf4
self.pc = 0
self.fl = 0
# these aren't mentioned in the spec
# but there didn't seem to be a suitable place to put them
self.start_time = 0
self.time = 0

def load(self):
"""Load a program into memory."""
def ram_read(self, addr):
return self.ram[addr]

address = 0
def ram_write(self, val, addr):
self.ram[addr] += val

def push(self, val):
self.reg[SP] -= 1
self.ram[self.reg[SP]] = val

# For now, we've just hardcoded a program:
def pop(self):
val = self.ram[self.reg[SP]]
self.reg[SP] += 1
return val

def load(self, program):
"""Load a program into memory."""

program = [
# From print8.ls8
0b10000010, # LDI R0,8
0b00000000,
0b00001000,
0b01000111, # PRN R0
0b00000000,
0b00000001, # HLT
]
address = 0

for instruction in program:
self.ram[address] = instruction
Expand All @@ -34,9 +87,47 @@ def load(self):
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
def add(): self.reg[reg_a] += self.reg[reg_b]
def sub(): self.reg[reg_a] -= self.reg[reg_b]
def mul(): self.reg[reg_a] *= self.reg[reg_b]
def div():
if reg_b == 0:
raise "Can't divide by 0"
self.reg[reg_a] //= self.reg[reg_b]
# def mod(a, b): self.reg[a] %= self.reg[b]
def cmp():
a = self.reg[reg_a]
b = self.reg[reg_b]
if a == b:
self.fl = 0b1
elif a > b:
self.fl = 0b10
else:
self.fl = 0b100
def shl(): self.reg[reg_a] <<= self.reg[reg_b]
def shr(): self.reg[reg_a] >>= self.reg[reg_b]
def inc(): self.reg[reg_a] += 1
def dec(): self.reg[reg_a] -= 1

ALU_INSTRUCTIONS = {
ALU_ADD: add,
ALU_SUB: sub,
ALU_MUL: mul,
ALU_DIV: div,
# ALU_MOD: lambda a, b: self.reg[a] % self.reg[b],
ALU_INC: inc,
ALU_DEC: dec,
ALU_CMP: cmp,
# ALU_AND: lambda _: ,
# ALU_NOT: lambda _: ,
# ALU_OR: lambda _: ,
# ALU_XOR: lambda _: ,
ALU_SHL: shl,
ALU_SHR: shr,
}

if op in ALU_INSTRUCTIONS:
ALU_INSTRUCTIONS[op]()
else:
raise Exception("Unsupported ALU operation")

Expand All @@ -61,5 +152,132 @@ def trace(self):
print()

def run(self):
"""Run the CPU."""
pass
try:
self.start_time = time.time()
while True:
self.reg[IS] = 0
# timer interrupt status
self.time = time.time()
if self.time >= self.start_time + 1:
self.start_time += ((self.time - self.start_time) // 1)
self.reg[IS] |= 1 # timer interrupt bit

# keyboard interrupt status
# TODO

# check interrupts
maskedInterrupts = self.reg[IS] & self.reg[IM]
for bit in range(8):
if (maskedInterrupts >> bit) & 1:
self.reg[IS] & ~(1 << bit)
self.push(self.pc)
self.push(self.fl)
for reg in range(7):
self.push(self.reg[reg])
# go to interrupt handler from interrupt address table
self.pc = self.ram[0xf8 + bit]
# disable further interrupts
self.reg[IM] = 0
break

ir = self.ram_read(self.pc)

self.pc += 1

instruction = ir & 0b1111
is_alu = (ir >> 5) & 1
sets_pc = (ir >> 4) & 1
operand_count = ir >> 6

op_position = self.pc
operands = (self.ram_read(op_position + i) for i in range(operand_count))
self.pc += operand_count

if is_alu:
self.alu(instruction, next(operands), next(operands, 0))
elif sets_pc:
def call():
self.push(self.pc)
self.pc = self.reg[next(operands)]
def ret(): self.pc = self.pop()
def jmp(): self.pc = self.reg[next(operands)]
def int_(): self.reg[IS] |= 1 << self.reg[next(operands)]
def iret():
for reg in reversed(range(7)):
self.reg[reg] = self.pop()
self.fl = self.pop()
self.pc = self.pop()
self.reg[IM] = ~0
def jeq():
if self.fl & 1: self.pc = self.reg[next(operands)]
def jne():
if not self.fl & 1: self.pc = self.reg[next(operands)]
def jgt():
if self.fl & 0b10: self.pc = self.reg[next(operands)]
def jge():
if self.fl & 0b11: self.pc = self.reg[next(operands)]
def jlt():
if self.fl & 0b100: self.pc = self.reg[next(operands)]
def jle():
if self.fl & 0b101: self.pc = self.reg[next(operands)]

INSTRUCTIONS = {
CALL: call,
RET: ret,
JMP: jmp,
JEQ: jeq,
JNE: jne,
JGT: jgt,
JLT: jlt,
JLE: jle,
JGE: jge,
INT: int_,
IRET: iret
}

if instruction in INSTRUCTIONS:
INSTRUCTIONS[instruction]()
else:
raise Exception(f"UNRECOGNIZED INSTRUCTION: {instruction:b}")
elif instruction == HLT:
break
else:
def ldi():
a = next(operands)
b = next(operands)
self.reg[a] = b
def ld():
a = next(operands)
b = next(operands)
self.reg[a] = self.ram[self.reg[b]]
def prn():
print(self.reg[next(operands)], end="")
sys.stdout.flush()
def pra():
print(chr(self.reg[next(operands)]), end="")
sys.stdout.flush()
def push(): self.push(self.reg[next(operands)])
def pop(): self.reg[next(operands)] = self.pop()
def st():
a = next(operands)
b = next(operands)
self.ram[self.reg[a]] = self.reg[b]

INSTRUCTIONS = {
LDI: ldi,
LD: ld,
PRN: prn,
PRA: pra,
PUSH: push,
POP: pop,
ST: st,
}

if instruction in INSTRUCTIONS:
INSTRUCTIONS[instruction]()
else:
raise Exception(f"UNRECOGNIZED INSTRUCTION: {instruction:b}")
except Exception as e:
print(f"ERROR OCCURED: {e}")
traceback.print_exc()
self.trace()
19 changes: 15 additions & 4 deletions ls8/ls8.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,18 @@
import sys
from cpu import *

cpu = CPU()

cpu.load()
cpu.run()
if len(sys.argv) > 1:
with open(sys.argv[1]) as program_file:
program = []
for line in program_file.readlines():
ir_str = line.strip().partition("#")[0]
if len(ir_str) == 0:
# there was a comment or blank line
continue
program.append(int(ir_str, 2))
cpu = CPU()
cpu.load(program)
cpu.run()
else:
print("Required argument: program file name")

Loading