diff --git a/chip8/cpu.py b/chip8/cpu.py index 6123ce5..adf5117 100644 --- a/chip8/cpu.py +++ b/chip8/cpu.py @@ -156,6 +156,7 @@ def __init__( # CPU starts with F (e.g. operand Fn07 would call # self.move_delay_timer_into_reg) self.misc_routine_lookup = { + 0x00: self.index_load_long, # F000 - LOADLONG 0x01: self.set_bitplane, # Fn01 - BITPLANE n 0x07: self.move_delay_timer_into_reg, # Ft07 - LOAD Vt, DELAY 0x0A: self.wait_for_keypress, # Ft0A - KEYD Vt @@ -244,11 +245,17 @@ def keyboard_routines(self): # Skip if the key specified in the source register is pressed if operation == 0x9E: - self.pc += 2 if keys_pressed[KEY_MAPPINGS[key_to_check]] else 0 + if keys_pressed[KEY_MAPPINGS[key_to_check]]: + self.pc += 2 + if self.memory[self.pc - 2] == 0xF0 and self.memory[self.pc - 1] == 0x00: + self.pc += 2 # Skip if the key specified in the source register is not pressed if operation == 0xA1: - self.pc += 2 if not keys_pressed[KEY_MAPPINGS[key_to_check]] else 0 + if not keys_pressed[KEY_MAPPINGS[key_to_check]]: + self.pc += 2 + if self.memory[self.pc - 2] == 0xF0 and self.memory[self.pc - 1] == 0x00: + self.pc += 2 def misc_routines(self): """ @@ -396,7 +403,10 @@ def skip_if_reg_equal_val(self): advancing it by 2 bytes. """ x = (self.operand & 0x0F00) >> 8 - self.pc += 2 if self.v[x] == (self.operand & 0x00FF) else 0 + if self.v[x] == (self.operand & 0x00FF): + self.pc += 2 + if self.memory[self.pc - 2] == 0xF0 and self.memory[self.pc - 1] == 0x00: + self.pc += 2 self.last_op = f"SKE V{x:01X}, {self.operand & 0x00FF:02X}" def skip_if_reg_not_equal_val(self): @@ -414,7 +424,10 @@ def skip_if_reg_not_equal_val(self): """ x = (self.operand & 0x0F00) >> 8 self.last_op = f"SKNE V{x:X}, {self.operand & 0x00FF:02X} (comparing {self.v[x]:02X} to {self.operand & 0xFF:02X})" - self.pc += 2 if self.v[x] != (self.operand & 0x00FF) else 0 + if self.v[x] != (self.operand & 0x00FF): + self.pc += 2 + if self.memory[self.pc - 2] == 0xF0 and self.memory[self.pc - 1] == 0x00: + self.pc += 2 def skip_if_reg_equal_reg(self): """ @@ -431,7 +444,10 @@ def skip_if_reg_equal_reg(self): """ x = (self.operand & 0x0F00) >> 8 y = (self.operand & 0x00F0) >> 4 - self.pc += 2 if self.v[x] == self.v[y] else 0 + if self.v[x] == self.v[y]: + self.pc += 2 + if self.memory[self.pc - 2] == 0xF0 and self.memory[self.pc - 1] == 0x00: + self.pc += 2 self.last_op = f"SKE V{x:01X}, V{y:01X}" def move_value_to_reg(self): @@ -661,7 +677,10 @@ def skip_if_reg_not_equal_reg(self): """ x = (self.operand & 0x0F00) >> 8 y = (self.operand & 0x00F0) >> 4 - self.pc += 2 if self.v[x] != self.v[y] else 0 + if self.v[x] != self.v[y]: + self.pc += 2 + if self.memory[self.pc - 2] == 0xF0 and self.memory[self.pc - 1] == 0x00: + self.pc += 2 self.last_op = f"SKNE V{x:01X}, V{y:01X} (comparing {self.v[x]:02X} to {self.v[y]:02X})" def load_index_reg_with_value(self): @@ -823,6 +842,17 @@ def draw_extended(self, x_pos, y_pos): self.v[0xF] += 1 self.screen.update() + def index_load_long(self): + """ + F000 - LOADLONG + + Loads the index register with a 16-bit long value. Consumes the next two + bytes from memory and increments the PC by two bytes. + """ + self.index = (self.memory[self.pc] << 8) + self.memory[self.pc+1] + self.pc += 2 + self.last_op = f"LOADLONG {self.index:04X}" + def set_bitplane(self): """ Fn01 - BITPLANE n diff --git a/test/test_chip8cpu.py b/test/test_chip8cpu.py index 5655521..2f88938 100644 --- a/test/test_chip8cpu.py +++ b/test/test_chip8cpu.py @@ -119,6 +119,14 @@ def test_skip_if_reg_equal_value(self): else: self.assertEqual(self.cpu.pc, 0) + def test_skip_if_reg_equal_val_load_long_exception(self): + self.cpu.memory[0x0200] = 0xF0 + self.cpu.memory[0x0201] = 0x00 + self.cpu.v[1] = 1 + self.cpu.operand = 0x3101 + self.cpu.skip_if_reg_equal_val() + self.assertEqual(0x0204, self.cpu.pc) + def test_skip_if_reg_not_equal_val(self): for register in range(0x10): for value in range(0, 0xFF, 0x10): @@ -133,6 +141,14 @@ def test_skip_if_reg_not_equal_val(self): else: self.assertEqual(self.cpu.pc, 0) + def test_skip_if_reg_not_equal_val_load_long_exception(self): + self.cpu.memory[0x0200] = 0xF0 + self.cpu.memory[0x0201] = 0x00 + self.cpu.v[1] = 1 + self.cpu.operand = 0x4102 + self.cpu.skip_if_reg_not_equal_val() + self.assertEqual(0x0204, self.cpu.pc) + def test_skip_if_reg_equal_reg(self): for reg_num in range(0x10): self.cpu.v[reg_num] = reg_num @@ -155,6 +171,15 @@ def test_skip_if_reg_equal_reg(self): else: self.assertEqual(self.cpu.pc, 0) + def test_skip_if_reg_equal_reg_load_long_exception(self): + self.cpu.memory[0x0200] = 0xF0 + self.cpu.memory[0x0201] = 0x00 + self.cpu.v[1] = 1 + self.cpu.v[2] = 1 + self.cpu.operand = 0x5120 + self.cpu.skip_if_reg_equal_reg() + self.assertEqual(0x0204, self.cpu.pc) + def test_move_value_to_reg(self): val = 0x23 for reg_num in range(0x10): @@ -410,6 +435,15 @@ def test_skip_if_reg_not_equal_reg(self): else: self.assertEqual(self.cpu.pc, 0) + def test_skip_if_reg_not_equal_reg_load_long_exception(self): + self.cpu.memory[0x0200] = 0xF0 + self.cpu.memory[0x0201] = 0x00 + self.cpu.v[1] = 1 + self.cpu.v[2] = 2 + self.cpu.operand = 0x9120 + self.cpu.skip_if_reg_not_equal_reg() + self.assertEqual(0x0204, self.cpu.pc) + def test_load_index_reg_with_value(self): for value in range(0x10000): self.cpu.operand = value @@ -679,6 +713,18 @@ def test_operation_9E_pc_skips_if_key_pressed(self): self.assertTrue(key_mock.asssert_called) self.assertEqual(2, self.cpu.pc) + def test_operation_9E_pc_skips_if_key_pressed_load_long_exception(self): + self.cpu.operand = 0x09E + self.cpu.v[0] = 1 + result_table = [False] * 512 + self.cpu.memory[0x0200] = 0xF0 + self.cpu.memory[0x0201] = 0x00 + result_table[pygame.K_1] = True + with mock.patch("pygame.key.get_pressed", return_value=result_table) as key_mock: + self.cpu.keyboard_routines() + self.assertTrue(key_mock.asssert_called) + self.assertEqual(0x0204, self.cpu.pc) + def test_operation_9E_pc_does_not_skip_if_key_not_pressed(self): self.cpu.operand = 0x09E self.cpu.v[0] = 1 @@ -699,6 +745,17 @@ def test_operation_A1_pc_skips_if_key_not_pressed(self): self.assertTrue(key_mock.asssert_called) self.assertEqual(2, self.cpu.pc) + def test_operation_A1_pc_skips_if_key_not_pressed_load_long_exception(self): + self.cpu.operand = 0x0A1 + self.cpu.v[0] = 1 + result_table = [False] * 512 + self.cpu.memory[0x0200] = 0xF0 + self.cpu.memory[0x0201] = 0x00 + with mock.patch("pygame.key.get_pressed", return_value=result_table) as key_mock: + self.cpu.keyboard_routines() + self.assertTrue(key_mock.asssert_called) + self.assertEqual(0x0204, self.cpu.pc) + def test_operation_A1_pc_does_not_skip_if_key_pressed(self): self.cpu.operand = 0x0A1 self.cpu.v[0] = 1 @@ -749,10 +806,10 @@ def test_execute_logical_instruction_raises_exception_on_unknown_op_codes(self): self.cpu.execute_logical_instruction() def test_misc_routines_raises_exception_on_unknown_op_codes(self): - self.cpu.operand = 0x0 + self.cpu.operand = 0xF0FF with self.assertRaises(UnknownOpCodeException) as context: self.cpu.misc_routines() - self.assertEqual("Unknown op-code: 0000", str(context.exception)) + self.assertEqual("Unknown op-code: F0FF", str(context.exception)) def test_scroll_down_called(self): self.cpu.operand = 0x00C4 @@ -1006,6 +1063,24 @@ def test_read_subset_regs_integration(self): self.assertEqual(9, self.cpu.v[2]) self.assertEqual(8, self.cpu.v[3]) + def test_load_long(self): + self.cpu.index = 0x5000 + self.cpu.memory[0x0200] = 0x12 + self.cpu.memory[0x0201] = 0x34 + self.cpu.index_load_long() + self.assertEqual(0x1234, self.cpu.index) + self.assertEqual(0x0202, self.cpu.pc) + + def test_load_long_integration(self): + self.cpu.index = 0x5000 + self.cpu.memory[0x0200] = 0xF0 + self.cpu.memory[0x0201] = 0x00 + self.cpu.memory[0x0202] = 0x12 + self.cpu.memory[0x0203] = 0x34 + self.cpu.execute_instruction() + self.assertEqual(0x1234, self.cpu.index) + self.assertEqual(0x0204, self.cpu.pc) + # M A I N #####################################################################