Beispiel #1
0
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
Beispiel #2
0
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
Beispiel #3
0
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
Beispiel #4
0
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()
Beispiel #5
0
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
Beispiel #6
0
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
Beispiel #7
0
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
Beispiel #8
0
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()
Beispiel #9
0
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
Beispiel #10
0
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)
Beispiel #11
0
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()
Beispiel #12
0
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")
Beispiel #13
0
    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
Beispiel #14
0
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
Beispiel #15
0
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)
Beispiel #16
0
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
Beispiel #17
0
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
Beispiel #18
0
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
Beispiel #19
0
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
Beispiel #20
0
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)
Beispiel #21
0
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()
Beispiel #22
0
    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,))
Beispiel #23
0
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))
Beispiel #24
0
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)
Beispiel #25
0
                    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
Beispiel #26
0
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")
Beispiel #27
0
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),