def test_poke(self): """ Tests address range and data for poke """ e = ElfCPU() e.load_string('0,1,2,3,4,5,6,7,8,9') # TypeError with self.assertRaises(TypeError): # noinspection PyTypeChecker e.poke('x', 2) # Above memory range with self.assertRaises(ValueError): e.poke(2**65, 2) # Below memory range with self.assertRaises(ValueError): e.poke(-1, 2) # Value with self.assertRaises(ValueError): e.poke(0, 2**64 + 1) self.assertEqual(e.poke(0, 99), 99) self.assertEqual(e.poke(9, 88), 88) self.assertEqual(e.peek(0), 99) self.assertEqual(e.peek(9), 88)
def test_invalid_instr(self): """ Tests for invalid op code """ e = ElfCPU() e.load_string('123456789') with self.assertRaises(InvalidInstructionError): e.execute()
def test_load_string_types(self): """ Checks for TypeError """ e = ElfCPU() with self.assertRaises(TypeError): # noinspection PyTypeChecker e.load_string(0) e.load_string('1,2,3,4')
def test_gpf(self): """ Tests for a general protection fault by allowing the program counter to go past the end of the memory. """ e = ElfCPU() # Jump to 2**20, the last memory address e.load_string('1106,0,1048576') with self.assertRaises(ProtectionFaultError): e.execute()
def test_halt(self): """ Tests for the halt op code """ e = ElfCPU() e.load_string('1,0,0,0,99') e.step() self.assertFalse(e.is_halted) e.step() self.assertTrue(e.is_halted)
def test_op_jmp_true(self): """ Tests jump if true op code """ e = ElfCPU() """ Tests address 8 (which is 1) if it is non-zero. Since this is true, it jumps to the value of address 9 (which is 7). This terminates the program. """ e.load_string('5,8,9,1101,1,1,8,99,1,7') e.execute() self.assertEqual(e.peek(8), 1) """ Tests immediate value 8 if it is non-zero. Since it is true, jump to immediate address 7 which terminates. """ e.load_string('1105,8,7,1101,1,1,8,99,1,7') e.execute() self.assertEqual(e.peek(8), 1) """ Tests address 8 (which is 0) if it is non-zero. Since this is false it does not jump and instead adds 1+1 to address 8. """ e.load_string('5,8,9999,11101,1,1,8,99,0,7') e.execute() self.assertEqual(e.peek(8), 2) """ Tests immediate value 0 if it is non-zero. Since it is false it does not jump and instead adds 1+1 to address 8. """ e.load_string('1105,0,9999,11101,1,1,8,99,0,7') e.execute() self.assertEqual(e.peek(8), 2)
def test_reset(self): """ Tests for CPU reset """ e = ElfCPU() e.load_string('1,0,0,0,99') e.execute() e.reset() # Halted gets cleared self.assertFalse(e.is_halted) # Program counter goes to 0 self.assertEqual(e.pc, 0) # Memory gets wiped so address 1 becomes invalid with self.assertRaises(ValueError): e.peek(1)
def test_peek(self): """ Tests address range for peek """ e = ElfCPU() e.load_string('0,1,2,3,4,5,6,7,8,9') # TypeError with self.assertRaises(TypeError): # noinspection PyTypeChecker e.peek('x') # Above memory range with self.assertRaises(ValueError): e.peek(2**65) # Below memory range with self.assertRaises(ValueError): e.peek(-1) self.assertEqual(e.peek(0), 0) self.assertEqual(e.peek(9), 9)
def test_op_relative_base(self): """ Tests the relative base mode op code """ e = ElfCPU() # Position e.load_string('9,5,204,1,99,6,7,777') e.interrupts = True # Step over relative mode op e.step() with self.assertRaises(OutputInterrupt): e.execute() self.assertEqual(e.output_buffer, 777) # Immediate e.reset() e.load_string('109,5,204,1,99,444,777') e.interrupts = True # Step over relative mode op e.step() with self.assertRaises(OutputInterrupt): e.execute() self.assertEqual(e.output_buffer, 777) # Relative e.reset() e.load_string('209,9,209,6,204,-2,99,5,333,4,6') e.interrupts = True e.debug = True with self.assertRaises(OutputInterrupt): e.execute() self.assertEqual(e.output_buffer, 333)
def test_op_add(self): """ Tests ADD op code [dst]:=[a]+[b] """ e = ElfCPU() # Invalid address 123456789 for a e.load_string('1,123456789,0,0') with self.assertRaises(ProtectionFaultError): e.step() # Invalid address 123456789 for b e.load_string('1,0,123456789,0') with self.assertRaises(ProtectionFaultError): e.step() # Invalid address 123456789 for dst e.load_string('1,0,0,123456789') with self.assertRaises(ProtectionFaultError): e.step() # 1 + 1 = 2 @ address 0 e.load_string('1,0,0,0,99') e.step() self.assertEqual(e.peek(0), 2) # 2**64 + 1 = 1 @ address 0 (overflow and wrap) #e.load_string('1,5,6,0,99,'+str(2**64)+',1') #e.step() #self.assertEqual(e.peek(0), 1) # [dst]:=a+[b] e.load_string('101,44,5,6,99,2,6') e.execute() self.assertEqual(e.peek(6), 46) # [dst]:=[a]+b e.load_string('1001,5,50,6,99,2,6') e.execute() self.assertEqual(e.peek(6), 52) # [dst]:=a+b e.load_string('1101,5,5,6,99,2,6') e.execute() self.assertEqual(e.peek(6), 10) # [dst]:=r[a]+b e.load_string('109,10,1201,0,5,7,99,7,3,3,5') e.execute() self.assertEqual(e.peek(7), 10) # [dst]:=a+r[b] e.load_string('109,10,2101,20,0,7,99,7,3,3,10') e.execute() self.assertEqual(e.peek(7), 30) # [dst]:=a+r[b] e.load_string('109,10,2101,20,0,7,99,7,3,3,10') e.execute() self.assertEqual(e.peek(7), 30) # r[dst]:=a+b e.load_string('109,10,21101,16,16,0,99,7,3,3,7') e.execute() self.assertEqual(e.peek(10), 32)
def test_op_eq(self): """ Tests equals op code """ e = ElfCPU() """ Tests if value of address 5 (10) is equal to value of address 6 (10). Since this is true, write 1 to address 10. """ e.load_string('8,8,9,10,99,10,10,-1,10,10,7') e.execute() self.assertEqual(e.peek(10), 1) """ Tests if value of address 5 (10) is equal to value of address 6 (0). Since this is false, write 0 to address 10. """ e.load_string('8,8,9,10,99,10,0,-1,5,6,7') e.execute() self.assertEqual(e.peek(10), 0) """ Tests if immediate value 10 is equal to immediate value 10. Since this is true, write 1 to address 7. """ e.load_string('1108,10,10,7,99,2,3,-1') e.execute() self.assertEqual(e.peek(7), 1) """ Tests if immediate value of 0 is equal to immediate value 10. Since this is false, write 0 to address 7. """ e.load_string('1108,0,10,7,99,2,3,-1') e.execute() self.assertEqual(e.peek(7), 0) """ if r[a] = r[b] r[dst]:=1 else r[dst]:=0 """ e.load_string('109,10,22208,0,1,2,99,222,222,222,555,555,1') e.execute() self.assertEqual(e.peek(12), 1) """ if r[a] < r[b] r[dst]:=1 else r[dst]:=0 """ e.load_string('109,10,22208,0,1,2,99,222,222,222,-500,100,1') e.execute() self.assertEqual(e.peek(12), 0)
def test_op_cmp_lessthan(self): """ Tests compare less than op code """ e = ElfCPU() """ Tests if value of address 8 (5) is less than value of address 9 (10). Since this is true write 1 to address 10. """ e.load_string('7,8,9,10,99,5,10,-1,5,10,7') e.execute() self.assertEqual(e.peek(10), 1) """ Tests if value of address 5 (10) is less than value of address 6 (5). Since this is false write 0 to address 10. """ e.load_string('7,8,9,10,99,5,10,-1,10,5,7') e.execute() self.assertEqual(e.peek(10), 0) """ Tests if immediate value of 5 is less than immediate value of 10. Since this is true write 1 to address 7. """ e.load_string('1107,5,10,7,99,0,0,-1') e.execute() self.assertEqual(e.peek(7), 1) """ Tests if immediate value of 10 is less than immediate value of 5. Since this is false write 0 to address 7. """ e.load_string('11107,10,5,7,99,0,0,-1') e.execute() self.assertEqual(e.peek(7), 0) """ if r[a] < r[b] r[dst]:=1 else r[dst]:=0 """ e.load_string('109,10,22207,0,1,2,99,222,222,222,100,50,1') e.execute() self.assertEqual(e.peek(12), 0) """ if r[a] < r[b] r[dst]:=1 else r[dst]:=0 """ e.load_string('109,10,22207,0,1,2,99,222,222,222,50,100,1') e.execute() self.assertEqual(e.peek(12), 1)
def test_op_output(self): """ Tests output op code Use io.StringIO() to capture the output """ e = ElfCPU() # Interrupts off e.load_string('4,5,104,66,99,55,5') e.interrupts = False result = None with patch('sys.stdout', new=io.StringIO()) as output: e.execute() result = output.getvalue() result = result.splitlines() # First is a reference to memory address 5 self.assertEqual(result[0].strip(), '55') # Second is an immediate value self.assertEqual(result[1].strip(), '66') # Interrupts on e.load_string('4,5,104,66,99,55,5') e.interrupts = True with self.assertRaises(OutputInterrupt): e.execute() self.assertEqual(e.output_buffer, 55) # Don't clear buffer with self.assertRaises(OutputOverflow): e.execute() # Restart test e.reset() e.load_string('4,5,104,66,99,55,5') e.interrupts = True with self.assertRaises(OutputInterrupt): e.execute() self.assertEqual(e.output_buffer, 55) # Clear buffer del e.output_buffer with self.assertRaises(OutputInterrupt): e.execute() self.assertEqual(e.output_buffer, 66) ############################################### # Interrupts on RELATIVE MODE # Restart test e.reset() e.load_string('109,5,204,1,99,6,1234') e.interrupts = True with self.assertRaises(OutputInterrupt): e.execute() self.assertEqual(e.output_buffer, 1234)
def test_op_input(self): """ Tests input op code Use unittest.mock.patch to fake the input value """ e = ElfCPU() # Interrupts off e.load_string('103,3,99,-1') e.interrupts = False with patch('builtins.input', return_value='1234'): e.execute() self.assertEqual(e.peek(3), 1234) # Interrupts on IMMEDIATE MODE e.load_string('103,5,103,5,99,-1') e.interrupts = True with self.assertRaises(InputInterrupt): e.step() # Should be back at pc = 0 self.assertEqual(e.pc, 0) # Load input e.input_buffer = 567 # Loading again overflows with self.assertRaises(InputOverflow): e.input_buffer = 123 # Execute the input instruction e.step() self.assertEqual(e.peek(5), 567) # Exec next input instruction with self.assertRaises(InputInterrupt): e.step() e.input_buffer = 987 # Execute until end e.execute() self.assertEqual(e.peek(5), 987) ###################################################### # Interrupts on RELATIVE MODE e.load_string('109,10,203,0,203,1,203,-1,99,102,100,101') e.interrupts = True # step past the relative base op code e.step() with self.assertRaises(InputInterrupt): e.step() # Should be back at pc = 2 (after relative base op code) self.assertEqual(e.pc, 2) # Load input e.input_buffer = 567 # Loading again overflows with self.assertRaises(InputOverflow): e.input_buffer = 123 # Execute the input instruction e.step() self.assertEqual(e.peek(10), 567) # Exec next input instruction with self.assertRaises(InputInterrupt): e.step() e.input_buffer = 987 # Step to execute this input e.step() self.assertEqual(e.peek(11), 987) # Exec next input instruction with self.assertRaises(InputInterrupt): e.step() e.input_buffer = 456 # Execute until end e.execute() self.assertEqual(e.peek(9), 456) ###################################################### # Interrupts on POSITIONAL MODE e.load_string('3,7,3,8,3,9,99,1,3,5') e.interrupts = True with self.assertRaises(InputInterrupt): e.step() # Should be back at pc = 0 self.assertEqual(e.pc, 0) # Load input e.input_buffer = 345 # Loading again overflows with self.assertRaises(InputOverflow): e.input_buffer = 123 # Execute the input instruction e.step() self.assertEqual(e.peek(7), 345) # Exec next input instruction with self.assertRaises(InputInterrupt): e.step() e.input_buffer = 765 # Step to execute this input e.step() self.assertEqual(e.peek(8), 765) # Exec next input instruction with self.assertRaises(InputInterrupt): e.step() e.input_buffer = 555 # Execute until end e.execute() self.assertEqual(e.peek(9), 555)
def test_op_mul(self): """ Tests MUL op code [dst]:=[a]*[b] """ e = ElfCPU() # Invalid address 123456789 for a e.load_string('2,123456789,0,0') with self.assertRaises(ProtectionFaultError): e.step() # Invalid address 123456789 for b e.load_string('2,0,123456789,0') with self.assertRaises(ProtectionFaultError): e.step() # Invalid address 123456789 for dst e.load_string('2,0,0,123456789') with self.assertRaises(ProtectionFaultError): e.step() # [dst]:=[a]*[b] e.load_string('2,0,0,0,99') e.step() self.assertEqual(e.peek(0), 4) # [dst]:=a*[b] e.load_string('102,44,5,6,99,2,6') e.execute() self.assertEqual(e.peek(6), 88) # [dst]:=[a]*b e.load_string('1002,5,50,6,99,2,6') e.execute() self.assertEqual(e.peek(6), 100) # [dst]:=a*b e.load_string('1102,5,5,6,99,2,6') e.execute() self.assertEqual(e.peek(6), 25) # [dst]:=r[a]*b e.load_string('109,10,1202,0,4,7,99,7,3,3,4') e.execute() self.assertEqual(e.peek(7), 16) # [dst]:=a*r[b] e.load_string('109,10,2102,7,0,7,99,7,3,3,2') e.execute() self.assertEqual(e.peek(7), 14) # [dst]:=r[a]*r[b] e.load_string('109,10,2202,0,1,7,99,7,3,3,2,6') e.execute() self.assertEqual(e.peek(7), 12) # dst:=a*b e.load_string('11102,6,6,0,99') e.execute() self.assertEqual(e.peek(0), 36) # r[dst]:=a*b e.load_string('109,7,21102,8,3,0,99,1') e.execute() self.assertEqual(e.peek(7), 24)
class HullRobot(object): def __init__(self, debug: bool = False): # The brain self._cpu = ElfCPU() # Current position x,y self._pos_x = 0 self._pos_y = 0 self._dir = Direction.UP # State self._state = State.PAINTING # Debug? if not isinstance(debug, bool): raise TypeError( f"debug must be of type bool, got {debug.__class__}") self._debug = debug if self._debug: print("HullRobot powered on") def run(self) -> None: """ Tells robot to begin """ if self._debug: print("HullRobot run requested") while not self._cpu.is_halted: try: if self._debug: print("HullRobot CPU executing") self._cpu.execute() except InputInterrupt: if self._debug: print("HullRobot input interrupt") self._cpu.input_buffer = self._camera().value except OutputInterrupt: if self._debug: print("HullRobot output interrupt") if self._state == State.PAINTING: # Paint the hull self._paint(Paint(self._cpu.output_buffer)) self._state = State.MOVING elif self._state == State.MOVING: # Move self._move(Move(self._cpu.output_buffer)) self._state = State.PAINTING # Clear the output buffer del self._cpu.output_buffer def load_program(self, code: str) -> None: """ Loads a new program """ if not isinstance(code, str): raise TypeError(f"code must be of type str, got {code.__class__}") if self._debug: print("HullRobot loaded program code") self._cpu.load_string(code) self._cpu.interrupts = True def _move(self, dest: Move) -> None: """ Moves the robot left or right """ if self._dir == Direction.UP: if dest == Move.LEFT: self._pos_x -= 1 self._dir = Direction.LEFT elif dest == Move.RIGHT: self._pos_x += 1 self._dir = Direction.RIGHT elif self._dir == Direction.DOWN: if dest == Move.LEFT: self._pos_x += 1 self._dir = Direction.RIGHT elif dest == Move.RIGHT: self._pos_x -= 1 self._dir = Direction.LEFT elif self._dir == Direction.LEFT: if dest == Move.LEFT: self._pos_y -= 1 self._dir = Direction.DOWN elif dest == Move.RIGHT: self._pos_y += 1 self._dir = Direction.UP elif self._dir == Direction.RIGHT: if dest == Move.LEFT: self._pos_y += 1 self._dir = Direction.UP elif dest == Move.RIGHT: self._pos_y -= 1 self._dir = Direction.DOWN if self._debug: print( f"HullRobot moved {'left' if dest == Move.LEFT else 'right'} to ({self._pos_x}, {self._pos_y})" ) def _camera(self) -> Paint: """ Access the robot camera to determine if over black or white panel by looking up the hull map. Anything not explicitly painted by the robot is black. """ cam = hull_map.get((self._pos_x, self._pos_y), Paint.BLACK) if self._debug: print(f"HullRobot camera at ({self._pos_x}, {self._pos_y}) sees " f"{'black' if cam == Paint.BLACK else 'white'}") return cam def _paint(self, color: Paint) -> None: """ Robot has painted its current location """ try: x = hull_map[(self._pos_x, self._pos_y)] except KeyError: x = None if self._debug: print( f"HullRobot {'painting' if x is None else 're-painting'} " f"{'black' if color == Paint.BLACK else 'white'} at ({self._pos_x}, {self._pos_y})" ) hull_map[(self._pos_x, self._pos_y)] = color