def test_docol_ram(emulator, return_stack, ip, mode, return_stack_depth): """Test the docol implementation for RAM mode On entry: IP holds the RAM address after the one we've just come from W holds the address of our NOP instruction mode holds the mode On exit: IP holds the first address of the thread mode holds the address of NEXT3-ROM-Mode The return stack holds: Top: The address of restore-mode (little-endian) 2: The previous mode 3: The old ip (little endian) """ # Arrange return_stack.set_depth_in_bytes(return_stack_depth) set_IP(ip) set_mode(mode) set_W(asm.symbol("forth.NOP")) # Act do_test_word(emulator, "forth.NOP") # Assert assert end_of_docol == get_IP() assert asm.symbol("forth.next3.rom-mode") & 0xFF == get_mode() assert [asm.symbol("forth.RESTORE-MODE"), mode, ip] == [ return_stack.pop_u16(), return_stack.pop_u8(), return_stack.pop_u16(), ] assert len(return_stack) == return_stack_depth
def test_docol_rom(emulator, return_stack, ip, return_stack_depth): """Test the docol implementation for RAM mode On entry: IP holds the ROM address after the one we've just come from W holds the address of our NOP instruction mode holds ROM mode On exit: IP holds the first address of the thread mode holds the address of NEXT3-ROM-Mode The return stack holds the old ip (little endian) """ # Arrange return_stack.set_depth_in_bytes(return_stack_depth) set_IP(ip) set_mode(asm.symbol("forth.next3.rom-mode") & 0xFF) set_W(nop_start_rom) # Act do_test_word(emulator, nop_start_rom) # Assert assert end_of_docol == get_IP() assert asm.symbol("forth.next3.rom-mode") & 0xFF == get_mode() assert [ip] == [return_stack.pop_u16()] assert len(return_stack) == return_stack_depth
def test_high_byte_lookup(value): """Lookup of the high-byte of a quarter square should work""" RAM[vars.high_byte_action] = asm.symbol("high-byte action.store") Emulator.AC = value Emulator.Y = asm.symbol("Quarter-squares lookup table") >> 8 Emulator.next_instruction = "table entry" cycles = Emulator.run_to("high-byte action.store") assert int(math.floor((value**2) / 4)) >> 8 == Emulator.AC assert vars.cost_of_high_byte_table_entry == cycles
def test_branch_rom_mode(emulator, address, target): # Arrange # Jump target must not intersect with jump assume(not set(range(address, address + 2)) & set(range(target, target + 3))) # Jump address cannot be encoded right at the end of a page. # In practice this is not a problem, it's a two instruction encoding, and we # already have a requirement that threads can't cross pages. assume(address & 0xFF != 0xFF) set_IP(address) ip_movement = target - address + 3 ROM[target:target + 3] = [ b"\xdc\x42", # st $42,[y, x++] [0xE0, asm.symbol("forth.move-ip")], # jmp [y,] b"\xdc\x82", # $82,[y, x++] ] ROM[address:address + 2] = [ # Encoding for data [0xFC, target & 0xFF], # bra target [0x00, ip_movement & 0xFF], # ld ip_movement ] # Act do_test_word(emulator, "forth.internal.rom-mode.BRANCH", continue_on_reenter=False) # Assert assert (target + 3) & 0xFF == get_IP( ) & 0xFF # low-byte equality, because move-ip doesn't handle page crossings assert 0x8242 == get_W()
def test_subtract_quarter_square(a, b, previous_value): """to done should subtract the""" RAM[vars.a] = a RAM[vars.b] = b RAM[vars.result:vars.result + 2] = (previous_value + 1).to_bytes( 2, "little", signed=False) Emulator.Y = asm.symbol("Quarter-squares lookup table") >> 8 Emulator.next_instruction = asm.symbol(".after-first-lookup") + 2 expected = (previous_value - math.floor((a - b)**2 / 4)) & 0xFFFF cycles = Emulator.run_to(asm.symbol("done")) assert expected == int.from_bytes(RAM[vars.result:vars.result + 2], "little", signed=False) assert asm.symbol("Quarter-squares lookup table") >> 8 == Emulator.Y assert vars.cost_of_7bit_multiply - vars.cost_after_first_lookup - 2 == cycles
def test_both_byte_lookup(a, b): """Lookup of both bytes of a quarter square should work The multiplication routine actually adds 1 to the result, so storing it, so check for that. """ RAM[vars.a] = a RAM[vars.b] = b Emulator.next_instruction = "multiply 7x7" expected = int(math.floor(((a + b)**2) / 4)) + 1 cycles = Emulator.run_to(asm.symbol(".after-first-lookup") + 2) assert expected == int.from_bytes(RAM[vars.result:vars.result + 2], "little", signed=False) assert cycles == vars.cost_after_first_lookup + 2 assert asm.symbol("Quarter-squares lookup table") >> 8 == Emulator.Y
def test_low_byte_lookup(value): """Lookup of the low-byte of a quarter square should work""" Emulator.Y = asm.symbol("Quarter-squares lookup table") >> 8 Emulator.AC = value Emulator.next_instruction = "low-byte table entry" cycles = Emulator.run_to("low-byte return point") assert int(math.floor((value**2) / 4)) & 0xFF == Emulator.AC assert vars.cost_of_low_byte_table_entry == cycles
def test_question_branch_rom_mode(emulator, data_stack, address, target, data_stack_depth, tos): # Arrange # Jump target must not intersect with jump assume(not set(range(address, address + 5)) & set(range(target, target + 3))) # We need to not be within the last five bytes of the page, # as there needs to be an instruction after us to run into if we don't branch. assume((address & 0xFF) + 5 <= 0xFF) data_stack.set_depth_in_bytes(data_stack_depth) data_stack.push_word(tos) set_IP(address) ip_movement = target - address + 3 ROM[target:target + 3] = [ b"\xdc\x00", # st $00,[y, x++] [0xE0, asm.symbol("forth.move-ip")], # jmp [y,] b"\xdc\x00", # $00,[y, x++] ] ROM[address:address + 5] = [ # Encoding for data [0xFC, target & 0xFF], # bra target [0x00, ip_movement & 0xFF], # ld ip_movement # Encoding for following word b"\xdc\xff", # st $ff,[y, x++] [0xE0, asm.symbol("forth.move-ip")], # jmp [y,] b"\xdc\xff", # $ff,[y, x++] ] # Act do_test_word(emulator, "forth.internal.rom-mode.?BRANCH", continue_on_reenter=False) # Assert if not tos: # IP should point after target address assert (target + 3) & 0xFF == get_IP( ) & 0xFF # low-byte equality, because move-ip doesn't handle page crossings # Target instruction should be about to run assert 0x0000 == get_W() else: # We run into the encoding of the next instruction, so IP should point after it. assert (address + 5) & 0xFF == get_IP() & 0xFF # Next instruction should be about to run assert 0xFFFF == get_W()
def setup_module(): global vars, symbol_table, execution_address """Load the Emulator from the ROM script and Mandelbrot """ reload(asm) name, _ = os.path.splitext(os.path.basename(SCRIPT)) script_globals = {"__file__": str(SCRIPT.absolute()), "__name__": name} with SCRIPT.open("rb") as file: exec(compile(file.read(), SCRIPT, "exec"), script_globals) Emulator.load_rom_from_asm_module() vars = SimpleNamespace(**script_globals) # This sequence of calls is roughly taken from compilegcl.py old_rom_size = asm._romSize program = gcl.Program("Mandelbrot", forRom=False) user_code = asm.symbol("userCode") user_vars = asm.symbol("userVars") program.org(user_code) asm.align(1) asm.zpReset(user_vars) with GCL.open("r", encoding="utf-8") as fp: for line in fp.readlines(): program.line(line) program.end() asm.end() # Copy the resulting data straight into RAM, in the appropriate blocks data = asm.getRom1()[old_rom_size:] index, more_data = 0, bool(data) while more_data: start_address = int.from_bytes(data[index:index + 2], "big", signed=False) index += 2 size = data[index] or 256 index += 1 chunk = data[index:index + size] index += size RAM[start_address:start_address + len(chunk)] = chunk more_data = data[index] execution_address = program.execute symbol_table = program.vars
def test_MulShift8(a, b): _prepare_for_vcpu_execution_at(execution_address) Emulator.run_vcpu_to( 0x300) # Run the first page, so the variable is defined _write_word(vars.sysFn, asm.symbol("SYS_MultiplyBytes_120"), signed=False) _write_word(symbol_table["A"], a, signed=True) _write_word(symbol_table["B"], b, signed=True) _call_vcpu_function("MulShift8") assert int(a * b / 256) == _read_word(vars.vAC, signed=True)
def test_docol_ram_ram(emulator, return_stack, ip, target, return_stack_depth): """Test jumping from one thread in RAM to another""" # Arrange emulator.zero_memory() return_stack.set_depth_in_bytes(return_stack_depth) assume(not {ip, ip + 1} & {target, target + 1}) set_IP(ip) set_mode(asm.symbol("forth.next3.ram-ram-mode") & 0xFF) RAM[target:target + 2] = [ asm.symbol("forth.DOCOL") & 0xFF, asm.symbol("forth.DOCOL") >> 8, ] RAM[ip:ip + 2] = [ target & 0xFF, target >> 8, ] # Act do_test_word(emulator, "forth.next3.ram-ram-mode") # Assert assert len(return_stack) == return_stack_depth + 2 assert [ip + 2] == [return_stack.pop_u16()] assert target + 2 == get_IP()
def test_next1_unsuccessful_test(emulator, vticks, word_cost): "A failed test should result in us being in the right place" # Arrange emulator.next_instruction = "forth.next1" emulator.AC = 20 # Time remaining is 20 ticks - 40 cycles set_W(WORD_START) ROM[WORD_START] = [0xA0, word_cost ] # suba $14 - worst case runtime is twenty ticks # Act emulator.run_for(next.cost_of_failed_next1) # Assert assert emulator.next_instruction == asm.symbol( "forth.exit.from-failed-test")
def next_instruction(self, address): """Set program execution to proceed from `address` This sets the PC to address + 1, having loaded the instruction at address, as if we had just executed address - 1. """ # To start from an address, we need to fill the pipeline with the instruction at address # and set PC to address + 1. address = asm.symbol(address) or address self.PC = address + 1 self.IR = _gtemu.lib.ROM[address][0] self.D = _gtemu.lib.ROM[address][1] self._last_pc = address
def test_next3_rom(emulator): emulator.next_instruction = "forth.next3.rom-mode" set_IP(WORD_START) ROM[WORD_START:WORD_START + 3] = [ b"\xdc\x42", # st $42,[y, x++] [0xE0, asm.symbol("forth.move-ip")], # jmp [y,] b"\xdc\x82", # $82,[y, x++] ] do_test_word(emulator, continue_on_reenter=False) assert get_W() == 0x8242 assert get_IP() == WORD_START + 3
def test_next2_failure(emulator, vticks, cycles_executed_in_word): # Arrange ticks_returned = -((cycles_executed_in_word + 1) // 2 # Round up to a whole number of ticks ) # We round up on entry to next2 entry_point = ("forth.next2.even" if even(cycles_executed_in_word) else "forth.next2.odd") emulator.next_instruction = entry_point expected_cycles = next.cost_of_next2_failure if not even(cycles_executed_in_word): expected_cycles += 1 emulator.AC = ticks_returned & 0xFF set_mode(asm.symbol("forth.next3.rom-mode") & 0xFF) set_vticks(vticks) # Act cycles_taken_by_next2 = emulator.run_to("forth.exit.from-next2") # Assert assert get_W() == asm.symbol("forth.next3.rom-mode") assert cycles_taken_by_next2 == expected_cycles assert (emulator.AC + get_vticks()) * 2 == (vticks * 2 - next.cost_of_successful_test - cycles_executed_in_word - cycles_taken_by_next2)
def test_right_shift_by_n(emulator, value1, value2, shift_amount): """Test for the left-shift-by-n utility""" # Arrange RAM[variables.tmp4] = 0x1 emulator.AC = -shift_amount & 0xFF RAM[0x42] = value1 emulator.Y = 0x00 emulator.X = 0x42 emulator.next_instruction = asm.symbol("right-shift-by-n") # Act emulator.run_for(_shift.cost_of_right_shift_by_n) # Assert assert emulator.AC == (value1 >> shift_amount) assert emulator.next_instruction & 0xFF == 0x1 # Arrange emulator.AC = RAM[variables.tmp5] RAM[0x42] = value2 emulator.next_instruction = asm.symbol("right-shift-by-n.second-time") # Act emulator.run_for(_shift.cost_of_right_shift_by_n__second_time) # Assert assert emulator.AC == (value2 >> shift_amount) assert emulator.next_instruction & 0xFF == 0x1
def test_left_shift_by_n(emulator, value, shift_amount): """Test for the left-shift-by-n utility""" # Arrange RAM[variables.tmp4] = 0x1 emulator.AC = -shift_amount & 0xFF RAM[0x42] = value emulator.Y = 0x00 emulator.X = 0x42 emulator.next_instruction = asm.symbol("left-shift-by-n") # Act emulator.run_for(_shift.cost_of_left_shift_by_n) # Assert assert emulator.AC == (value << shift_amount) & 0xFF assert emulator.next_instruction & 0xFF == 0x1
def test_exit_to_ram_mode(emulator, return_stack, return_address, mode, return_stack_depth): # Arrange return_stack.set_depth_in_bytes(return_stack_depth) return_stack.push_word(return_address) return_stack.push_byte(mode) return_stack.push_word(asm.symbol("forth.RESTORE-MODE")) # Act do_test_word(emulator, "forth.core.EXIT") # Exit should have left restore-mode in IP, so next should DTRT do_test_word(emulator, "forth.next3.rom-mode") # Assert assert mode == get_mode() assert return_address == get_IP() assert len(return_stack) == return_stack_depth
def _to_address(address_or_symbol): """Convert a value that may be an address or a symbol, to an address Labels are the most likely symbols we encounter""" from_symbol = asm.symbol(address_or_symbol) if from_symbol is not None: address = from_symbol else: try: address = int(address_or_symbol) except (TypeError, ValueError): raise ValueError( f"{address_or_symbol:r} is not a valid address or symbol") if not (0 <= address < (1 << 16)): raise ValueError(f"{address:x} is out of range for an address") return address
def test_rshift(emulator, data_stack, data_stack_depth, tos, nos): # Arrange data_stack.set_depth_in_bytes(data_stack_depth) data_stack.push_word(nos) data_stack.push_word(tos) set_W(asm.symbol("forth.core.RSHIFT")) # Act do_test_word( emulator, "forth.core.RSHIFT", cycles_shifted_to_trampoline=2, before_each_entry_do=lambda: data_stack.set_x_and_y_registers(emulator ), ) # Assert assert ((nos & 0xFFFF) >> tos) == data_stack.pop_u16() assert data_stack_depth == len(data_stack)
def test_rom_mode_char_literal(emulator, data_stack, data_stack_depth, data): # Arrange data_stack.set_depth_in_bytes(data_stack_depth) set_IP(WORD_START) ROM[WORD_START : WORD_START + 5] = [ # Encoding for data [0xDC, data & 0xFF], [0x10, W], # ld $00,x # Encoding for next word b"\xdc\x42", # st $42,[y, x++] [0xE0, asm.symbol("forth.move-ip")], # jmp [y,] b"\xdc\x82", # $82,[y, x++] ] # Act do_test_word(emulator, "forth.internal.C-LIT", continue_on_reenter=False) # Assert assert data == data_stack.pop_i16() assert data_stack_depth == len(data_stack) assert WORD_START + 5 == get_IP() assert 0x8242 == get_W()
def run_to(self, address, max_instructions=1000): """Run the emulator until it is about to execute the instruction at `address` Due to the pipeline, this means that for the previous instruction PC was `address`, and therefore we have loaded the instruction. Will stop at breakpoints if they are hit, but always executes at least one cycle """ address = asm.symbol(address) or address iterator = ( range(max_instructions) if max_instructions is not None else itertools.count() ) for i, _ in enumerate(iterator): self._step() if self._last_pc == address or self._last_pc in self.breakpoints: return i + 1 raise ValueError("Did not hit address in %d instructions" % (max_instructions,))
def do_test_word( emulator, entrypoint=None, *, continue_on_reenter=True, cycles_shifted_to_trampoline=0, before_each_entry_do=lambda: None, ): """Execute a single Forth word, checking the timing related invariants If continue_on_reenter is True (the default), this will run the emulator until it returns to Next2. Otherwise it will return after the emulator returns to Next1-reenter, it will raise an assertion error if it returns to Next2. The starting instruction can be specified as a parameter, or set on the emulator object before the call. """ reenter_odd = asm.symbol("forth.next1.reenter.odd") next2_even = asm.symbol("forth.next2.even") old_breakpoints = emulator.breakpoints # Break at the inner entrypoints of the two routines emulator.breakpoints = set([reenter_odd, next2_even]) entrypoint = entrypoint or emulator.next_instruction def do_iteration(): emulator.next_instruction = entrypoint worst_case_ticks = _get_max_tick_cost_of_current_word(emulator) assert worst_case_ticks and worst_case_ticks > 0, ( f"Instruction at {entrypoint:x} does not look like a Forth word - it does not have a cost." f" IP = {get_IP():x}; W = {get_W():x}") emulator.next_instruction = entrypoint before_each_entry_do() actual_cycles = emulator.run_for(worst_case_ticks * 2 - cycles_shifted_to_trampoline) if emulator.PC == reenter_odd: # We've jumped to next1-reenter.odd, but not yet loaded the instruction. # This is actually OK. actual_cycles += emulator.run_for(1) return actual_cycles def check_next1_reenter_constraints(): assert not even(actual_cycles) # We're at the odd entrypoint assert (negate_byte(emulator.AC) * 2 == actual_cycles - 1 + cycles_shifted_to_trampoline) actual_cycles = do_iteration() while continue_on_reenter and emulator.next_instruction == reenter_odd: check_next1_reenter_constraints() entrypoint = get_W() emulator.next_instruction = entrypoint actual_cycles = do_iteration() emulator.breakpoints = old_breakpoints if not continue_on_reenter: if emulator.next_instruction == reenter_odd: # Good, that's where we expected to be check_next1_reenter_constraints() return elif emulator.next_instruction == next2_even: raise AssertionError( "Expected see jump to reenter - but instead saw jump to next2") if emulator.next_instruction == next2_even: assert even(actual_cycles) assert (negate_byte(emulator.AC) * 2 == actual_cycles + cycles_shifted_to_trampoline) return raise AssertionError( "Did not hit Next2 or Reenter within the promised {} ticks".format( actual_cycles / 2))
def setup_function(): RAM[vars.sysFn:vars.sysFn + 2] = asm.symbol("SYS_MultiplyBytes_120").to_bytes(2, "little") RAM[vars.vTicks] = 75 Emulator.next_instruction = "SYS" Emulator.AC = 270 - max(14, MAX_CYCLES // 2)
help='Create .gt1x file'), parser.add_argument('gclSource', help='GCL file') parser.add_argument('outputDir', nargs='?', default='.', help='Optional output directory') args = parser.parse_args() #----------------------------------------------------------------------- # Compile #----------------------------------------------------------------------- asm.loadBindings(args.sym) if args.gt1x: asm.loadBindings('Core/interface-dev.json') userCode = asm.symbol('userCode') userVars = asm.symbol('userVars') print('Compiling file %s' % args.gclSource) program = gcl.Program('Main', forRom=False) program.org(userCode) asm.align(1) # Forces default maximum ROM size asm.zpReset(userVars) # User variables can start here for line in open(args.gclSource).readlines(): program.line(line) program.end() asm.end() # End assembly data = asm.getRom1() #----------------------------------------------------------------------- # Append ending
def _do_test_thread(emulator, label): set_IP(0x4282) set_W(symbol(label)) do_test_word(emulator, get_W()) while get_IP() != 0x4282: do_test_word(emulator, "forth.next3.rom-mode")
import asm from forth import variables from gtemu import RAM from utilities import ( do_test_word, get_IP, get_mode, set_IP, set_mode, set_W, ) max_return_stack_size = variables.return_stack_empty - variables.return_stack_full # Address for RAM mode nop_start = asm.symbol("forth.NOP") # Address for ROM mode nop_start_rom = nop_start + 4 end_of_docol = nop_start + 8 ram_modes = one_of( just(asm.symbol("forth.next3.ram-rom-mode") & 0xFF), just(asm.symbol("forth.next3.ram-ram-mode") & 0xFF), ) def return_stack_depths(*, with_room_for_bytes=0): return integers( min_value=0, max_value=min(max_return_stack_size - with_room_for_bytes, max_return_stack_size),