diff --git a/asm/stretch.asm b/asm/stretch.asm new file mode 100644 index 000000000..644ff8984 --- /dev/null +++ b/asm/stretch.asm @@ -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 \ No newline at end of file diff --git a/ls8/cpu.py b/ls8/cpu.py index 9a307496e..8f8e16436 100644 --- a/ls8/cpu.py +++ b/ls8/cpu.py @@ -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 @@ -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") @@ -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() diff --git a/ls8/ls8.py b/ls8/ls8.py index 74128d36b..b263852c8 100755 --- a/ls8/ls8.py +++ b/ls8/ls8.py @@ -5,7 +5,18 @@ import sys from cpu import * -cpu = CPU() - -cpu.load() -cpu.run() \ No newline at end of file +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") + \ No newline at end of file diff --git a/ls8/stretch.ls8 b/ls8/stretch.ls8 new file mode 100644 index 000000000..1012cb0c6 --- /dev/null +++ b/ls8/stretch.ls8 @@ -0,0 +1,104 @@ +10000010 # LDI R1,0 +00000001 +00000000 +10000010 # LDI R2,7 +00000010 +00000111 +# MAINLOOPSTART (address 6): +10100111 # CMP R1,R2 +00000001 +00000010 +10000010 # LDI R0,MAINLOOPEND +00000000 +00100000 +01011010 # JGE R0 +00000000 +10000010 # LDI R0,1 +00000000 +00000001 +10101100 # SHL R0,R1 +00000000 +00000001 +10000010 # LDI R3,PRINTN +00000011 +00100001 +01010000 # CALL R3 +00000011 +01100101 # INC R1 +00000001 +10000010 # LDI R0,MAINLOOPSTART +00000000 +00000110 +01010100 # JMP R0 +00000000 +# MAINLOOPEND (address 32): +00000001 # HLT +# PRINTN (address 33): +01000101 # PUSH R1 +00000001 +01000101 # PUSH R2 +00000010 +01000101 # PUSH R3 +00000011 +10000010 # LDI R1,1 +00000001 +00000001 +# PRINTNLOOPSTART (address 42): +10100111 # CMP R1,R0 +00000001 +00000000 +10000010 # LDI R2,PRINTNLOOPEND +00000010 +00111110 +01010111 # JGT R2 +00000010 +10000010 # LDI R2,PRINTASTERISK +00000010 +01001101 +01010000 # CALL R2 +00000010 +01100101 # INC R1 +00000001 +10000010 # LDI R2,PRINTNLOOPSTART +00000010 +00101010 +01010100 # JMP R2 +00000010 +# PRINTNLOOPEND (address 62): +10000010 # LDI R3,NEWLINE +00000011 +01011111 +10000011 # LD R2,R3 +00000010 +00000011 +01001000 # PRA R2 +00000010 +01000110 # POP R3 +00000011 +01000110 # POP R2 +00000010 +01000110 # POP R1 +00000001 +00010001 # RET +# PRINTASTERISK (address 77): +01000101 # PUSH R0 +00000000 +01000101 # PUSH R1 +00000001 +10000010 # LDI R0,ASTERISK +00000000 +01011110 +10000011 # LD R1,R0 +00000001 +00000000 +01001000 # PRA R1 +00000001 +01000110 # POP R1 +00000001 +01000110 # POP R0 +00000000 +00010001 # RET +# ASTERISK (address 94): +00101010 # * +# NEWLINE (address 95): +00001010 # 0x0a