def _write_values_to_memory( self, vm: Chip8VirtualMachine, max_register: int, write_dest: int = 0xA00, value_start: int = 128 ) -> None: """ Helper function that executes a bulk register save operation. Will write value_start +0, ... value_start + max_register to the first max_register registers. Then runs FX55, with max_register as X. :param vm: vm object to write to :param max_register: the highest register that will be saved :param value_start: what the start of the written values will be :return: """ # set up the registers with unique values that can be checked # for correct write order in memory. for index in range(0, max_register + 1): vm.v_registers[index] = value_start + index # set up where the registers will be stored to vm.i_register = write_dest load_and_execute_instruction( vm, 0xF055, x=max_register )
def test_00e0_calls_vram_clear_screen(): """00E0 causes VM to call screen clear on video ram""" vm = VM() vm.video_ram = Mock(VideoRam) load_and_execute_instruction(vm, 0x00E0) assert vm.video_ram.clear_screen.called_once()
def _load_from_memory( self, vm: Chip8VirtualMachine, max_register: int, read_src: int = 0xA00, value_start: int = 128 ) -> None: """ Helper function that executes a bulk register save operation. Will write value_start +0, ... value_start + max_register to the first max_register registers. Then runs FX55, with max_register as X. :param vm: vm object to write to :param max_register: the highest register that will be saved :param value_start: what the start of the written values will be :return: """ # set up the memory with values to read into registers for register_index in range(0, max_register + 1): vm.memory[read_src + register_index] = 128 + register_index # set up where the registers will be stored to vm.i_register = read_src load_and_execute_instruction( vm, 0xF065, x=max_register )
def test_8xy0_same_value_for_x_and_y_leaves_reg_unchanged(self, reg_index): """8xy0 does not alter the value when x & y are the same""" vm = VM() vm.v_registers[reg_index] = 5 load_and_execute_instruction(vm, 0x8000, x=reg_index, y=reg_index) assert vm.v_registers[reg_index] == 5
def test_data_past_ram_end_raises_indexerror(self, ram_size, location): vm = VM(memory_size=ram_size) # 1 after end of RAM data_len = 1 + ram_size - location with pytest.raises(IndexError): vm.load_to_memory(b"a" * data_len, location)
def test_6xy0_skips_next_if_vx_not_equal_vy(self, x, y): vm = VM() vm.v_registers[x] = 0 vm.v_registers[y] = 1 load_and_execute_instruction(vm, 0x9000, x=x, y=y) assert vm.program_counter ==\ DEFAULT_EXECUTION_START + (2 * INSTRUCTION_LENGTH)
def test_8xy0_leaves_original_alone(self, x, y): """8xy0 leaves VY alone""" vm = VM() vm.v_registers[x] = 2 vm.v_registers[y] = 3 load_and_execute_instruction(vm, 0x8000, x=x, y=y) assert other_registers_untouched(vm, {x, y})
def test_8xy6_sets_vf_to_least_significant_digit_of_vy(self, x, y, a, b): """8xy6 sets VF to least significant""" vm = VM() vm.v_registers[x] = a vm.v_registers[y] = b load_and_execute_instruction(vm, 0x8006, x=x, y=y) assert vm.v_registers[0xF] == b & 1
def test_8xye_sets_vf_to_most_significant_digit_of_vy(self, x, y, a, b): """8xyE sets VF to most significant digit of VY""" vm = VM() vm.v_registers[x] = a vm.v_registers[y] = b load_and_execute_instruction(vm, 0x800E, x=x, y=y) assert vm.v_registers[0xF] == bool(b & 0b10000000)
def test_8xy2_leaves_other_registers_alone(self, x, y): """8xy2 leaves registers other than VX and VY alone""" vm = VM() vm.v_registers[x] = 0b10101010 vm.v_registers[y] = 0b01010101 load_and_execute_instruction(vm, 0x8002, x=x, y=y) assert other_registers_untouched(vm, (x, y))
def test_4xkk_does_not_skip_next_instruction_if_vx_eq_kk( self, vx: int, vx_and_kk_value: int, exec_start: int): vm = VM(execution_start=exec_start) vm.v_registers[vx] = vx_and_kk_value load_and_execute_instruction(vm, 0x4000, load_point=exec_start, x=vx, kk=vx_and_kk_value) assert vm.program_counter == exec_start + INSTRUCTION_LENGTH
def test_9xy0_doesnt_skip_next_if_vx_eq_vy(self, x, y, equal_val): vm = VM() vm.v_registers[x] = 7 vm.v_registers[y] = 7 load_and_execute_instruction(vm, 0x9000, x=x, y=y) assert vm.program_counter ==\ DEFAULT_EXECUTION_START + INSTRUCTION_LENGTH
def test_00ee_decrements_stack_size(call_location): """Return instruction decrements stack size""" vm = VM() vm.stack_call(call_location) load_and_execute_instruction( vm, 0x00EE, # return instruction load_point=call_location) assert vm.stack_size == 0
def test_00ee_returns_to_last_location_plus_two(call_location): """Return instruction returns to last location on the stack""" vm = VM() vm.stack_call(call_location) load_and_execute_instruction( vm, 0x00EE, # return instruction load_point=call_location) assert vm.program_counter == DEFAULT_EXECUTION_START + 2
def test_8xye_sets_vx_to_vy_shifted_left_one(self, x, y, a, b): """8xyE sets VX = VY >> 1""" vm = VM() vm.v_registers[x] = a vm.v_registers[y] = b load_and_execute_instruction(vm, 0x800E, x=x, y=y) assert vm.v_registers[x] == 0xFF & (b << 1)
def test_8xy6_sets_vx_to_vy_shifted_right_one(self, x, y, a, b): """8xy6 sets VX = VY >> 1""" vm = VM() vm.v_registers[x] = a vm.v_registers[y] = b load_and_execute_instruction(vm, 0x8006, x=x, y=y) assert vm.v_registers[x] == b >> 1
def test_8xy5_sets_vf_to_not_borrow(self, x, y, a, b): """8xy5 sets VF to 1 if VX >= VY, otherwise 0""" vm = VM() vm.v_registers[x] = a if x != y: vm.v_registers[y] = b load_and_execute_instruction(vm, 0x8005, x=x, y=y) assert vm.v_registers[0xF] == int(a >= b)
def test_8xy4_sets_vf_to_carry_bit(self, x, y, a, b): """8xy4 sets VF to 1 if VX + VY > 255, otherwise 0""" vm = VM() vm.v_registers[x] = a if x != y: vm.v_registers[y] = b load_and_execute_instruction(vm, 0x8004, x=x, y=y) assert vm.v_registers[0xF] == int(a + b > 255)
def test_4xkk_skips_next_instruction_if_vx_neq_kk(self, vx: int, vx_value, kk_value, exec_start): vm = VM(execution_start=exec_start) vm.v_registers[vx] = vx_value load_and_execute_instruction( vm, 0x4000, load_point=exec_start, x=vx_value, kk=kk_value, ) assert vm.program_counter == exec_start + (2 * INSTRUCTION_LENGTH)
def test_8xy0_sets_vx_to_vy(self, x, y): """8xy0 sets VX = VX OR VY""" vm = VM() original_x_value = 2 default_y_value = 3 vm.v_registers[x] = original_x_value vm.v_registers[y] = default_y_value load_and_execute_instruction(vm, 0x8000, x=x, y=y) assert vm.v_registers[x] == default_y_value
def test_8xye_leaves_other_registers_alone_unless_theyre_vf(self, x, y): """8xy6 leaves registers other than VX alone except for VF""" vm = VM() vm.v_registers[x] = 2 vm.v_registers[y] = 1 load_and_execute_instruction(vm, 0x800E, x=x, y=y) # make sure we don't check VF since it should always be set touched = {x, 0xF} assert other_registers_untouched(vm, touched)
def test_8xy2_sets_vx_to_and_of_vx_and_vy(self, x, y): """8xy2 sets VX to VX AND VY""" vm = VM() left_half_filled = 0b11110000 vm.v_registers[x] = 0xFF vm.v_registers[y] = left_half_filled # set vx = vx AND 0x11110000. If vx is already set to 0x11110000, # the value will still be 0x11110000 after the instruction runs. load_and_execute_instruction(vm, 0x8002, x=x, y=y) assert vm.v_registers[x] == left_half_filled
def test_bnnn_jumps_to_address_plus_offset(memory_location, v0): """Bnnn sets program counter to nnn + v0""" vm = VM() assert vm.program_counter == DEFAULT_EXECUTION_START assert vm.stack_size == 0 vm.v_registers[0] = v0 load_and_execute_instruction( vm, 0xB000, nnn=memory_location, ) assert vm.program_counter == memory_location + v0 assert vm.stack_size == 0
def test_8xy3_sets_vx_to_xor_of_vx_and_vy(self, x, y): """8xy3 sets VX to VX XOR VY""" vm = VM() vm.v_registers[x] = 0b10101111 vm.v_registers[y] = 0b01011111 load_and_execute_instruction(vm, 0x8003, x=x, y=y) if x != y: assert vm.v_registers[x] == 0b11110000 else: # any value xor itself yields zero assert vm.v_registers[x] == 0
def setup_vm(self, x: int, key: int) -> VM: """ Helper function to set up the VM and template instructions """ vm = VM() load_multiple( vm, (0xE09E, { 'x': x }), # set the I register. This command can't touch that I, and # I is set to zero on VM initialization. Therefore, it is # safe to use I as a test condition for ex9e. 0xA0FF) vm.v_registers[x] = key return vm
def test_8xy4_sets_vx_to_sum_of_vx_and_vy(self, x, y, a, b): """8xy4 sets VX to VX + VY, modulo 256""" vm = VM() vm.v_registers[x] = a if x != y: vm.v_registers[y] = b load_and_execute_instruction(vm, 0x8004, x=x, y=y) if x != y: assert vm.v_registers[x] == (a + b) % 256 else: assert vm.v_registers[x] == (a * 2) % 256
def test_8xy1_sets_vx_to_logical_or_of_vx_and_vy(self, x, y): """8xy1 sets VX = VX OR VY""" vm = VM() original_x_value = 0b10101010 default_y_value = 0b01010101 vm.v_registers[x] = original_x_value if y != x: vm.v_registers[y] = default_y_value load_and_execute_instruction(vm, 0x8001, x=x, y=y) if y != x: assert vm.v_registers[x] == original_x_value | default_y_value else: assert vm.v_registers[x] == original_x_value
def test_8xy7_sets_vx_to_vy_minus_vx(self, x, y, a, b): """8xy7 sets VX = VY - VX, clamped to 0 minimum""" vm = VM() vm.v_registers[x] = a if x != y: vm.v_registers[y] = b load_and_execute_instruction(vm, 0x8007, x=x, y=y) if x != y: assert vm.v_registers[x] == max(b - a, 0) else: # any value minus itself yields zero assert vm.v_registers[x] == 0
def test_fx55_writes_registers_to_ram(self, max_register): """Fx55 writes register contents to RAM correctly""" vm = Chip8VirtualMachine() self._write_values_to_memory( vm, max_register, 0xA00, 128 ) for index in range(0, max_register+1): assert vm.memory[0xA00 + index] == 128 + index
def test_fx65_loads_ram_to_registers(self, max_register): """Fx65 reads ram contents to registers correctly""" vm = Chip8VirtualMachine() self._load_from_memory( vm, max_register, 0xA00, 128 ) for register_index in range(0, max_register+1): assert vm.v_registers[register_index] == 128 + register_index