147 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			147 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
import copy
 | 
						|
import pathlib
 | 
						|
from importlib import reload
 | 
						|
from unittest.mock import patch
 | 
						|
 | 
						|
import gcl0x as gcl
 | 
						|
 | 
						|
import asm
 | 
						|
from gtemu import RAM, Emulator
 | 
						|
 | 
						|
ROOT = (pathlib.Path(__file__).parent).resolve()
 | 
						|
REPO_ROOT = (ROOT / ".." / ".." / "..").resolve()
 | 
						|
 | 
						|
SCRIPT = ROOT / "sys" / "ROM.asm.py"
 | 
						|
RESET = REPO_ROOT / "Core" / "Reset_v5.gcl"
 | 
						|
BOOT = REPO_ROOT / "Apps" / "CardTest" / "CardBoot.gcl"
 | 
						|
ORIGINAL_MANDELBROT = REPO_ROOT / "Apps" / "Mandelbrot" / "Mandelbrot_v1.gcl"
 | 
						|
MY_MANDELBROT = ROOT / "mandelbrot" / "Mandelbrot.gcl"
 | 
						|
 | 
						|
 | 
						|
CLOCK_FREQUENCY = 6.250e06  # Hz
 | 
						|
CLOCK_PERIOD = 1 / CLOCK_FREQUENCY
 | 
						|
 | 
						|
 | 
						|
for file in [SCRIPT, RESET, BOOT, ORIGINAL_MANDELBROT, MY_MANDELBROT]:
 | 
						|
    assert file.exists(), f"File does not exist: {file}"
 | 
						|
 | 
						|
 | 
						|
script_globals = {"__file__": str(SCRIPT.absolute()), "__name__": "__main__"}
 | 
						|
with SCRIPT.open("rb") as file:
 | 
						|
    compiled_script = compile(file.read(), SCRIPT, "exec")
 | 
						|
script_argv = [str(SCRIPT.name), f"Reset={RESET}", f"Boot={BOOT}"]
 | 
						|
 | 
						|
 | 
						|
def get_symbol_table(gcl_file):
 | 
						|
    """Compile and discard gcl, and return the symbol table
 | 
						|
 | 
						|
    Requires the rom script to already have been run in so that the
 | 
						|
    """
 | 
						|
    # Execute the hacked ROM script, to define all of the required variables
 | 
						|
    reload(asm)
 | 
						|
    asm.define("Main", 0x0000)  # Must be defined to something
 | 
						|
    with patch("sys.argv", script_argv), patch("asm.writeRomFiles"), patch(
 | 
						|
        "asm.enableListing"
 | 
						|
    ), patch("asm.disableListing"), patch("asm.print"), patch("gcl0x.print"):
 | 
						|
        exec(compiled_script, copy.copy(script_globals))
 | 
						|
    asm.align(1)
 | 
						|
    asm.zpReset(asm.symbol("userVars"))
 | 
						|
    program = gcl.Program("Mandelbrot", forRom=False)
 | 
						|
    program.org(asm.symbol("userCode"))
 | 
						|
    with gcl_file.open("r", encoding="utf-8") as fp:
 | 
						|
        for line in fp.readlines():
 | 
						|
            with patch("gcl0x.print"):
 | 
						|
                program.line(line)
 | 
						|
    with patch("gcl0x.print"):
 | 
						|
        program.end()
 | 
						|
    asm.end()
 | 
						|
    return program.vars
 | 
						|
 | 
						|
 | 
						|
def compile_and_load_rom(main_gcl):
 | 
						|
    reload(asm)
 | 
						|
    with patch("sys.argv", script_argv + [f"Main={main_gcl}"]), patch(
 | 
						|
        "asm.writeRomFiles"
 | 
						|
    ), patch("asm.enableListing"), patch("asm.disableListing"), patch(
 | 
						|
        "asm.print"
 | 
						|
    ), patch(
 | 
						|
        "gcl0x.print"
 | 
						|
    ):
 | 
						|
        exec(compiled_script, copy.copy(script_globals))
 | 
						|
    Emulator.reset()  # To reset PC etc. should really be part of load_rom_from_asm_module
 | 
						|
    Emulator.load_rom_from_asm_module()
 | 
						|
 | 
						|
 | 
						|
def _read_word(address, *, signed):
 | 
						|
    return int.from_bytes(RAM[address : address + 2], "little", signed=signed)
 | 
						|
 | 
						|
 | 
						|
def _load(gcl_file):
 | 
						|
    symbols = get_symbol_table(gcl_file)
 | 
						|
    compile_and_load_rom(gcl_file)
 | 
						|
    return symbols
 | 
						|
 | 
						|
 | 
						|
def _run_to_main_start():
 | 
						|
    cycles = Emulator.run_to("SYS_Reset_88", max_instructions=10_000_000)
 | 
						|
    print(f"Boot reached SYS_RESET_88 in {cycles} cycles ({cycles * CLOCK_PERIOD}s)")
 | 
						|
    cycles += Emulator.run_to("SYS_Exec_88")
 | 
						|
    assert _read_word(asm.symbol("sysArgs0"), signed=False) == asm.symbol("Reset")
 | 
						|
    print(
 | 
						|
        f"Boot reached SYS_Exec_88 (Reset) in {cycles} cycles ({cycles * CLOCK_PERIOD}s)"
 | 
						|
    )
 | 
						|
    cycles += Emulator.run_to("SYS_Exec_88", max_instructions=10_000_000)
 | 
						|
    assert _read_word(asm.symbol("sysArgs0"), signed=False) == asm.symbol("Main")
 | 
						|
    print(
 | 
						|
        f"Boot reached SYS_Exec_88 (Main) in {cycles} cycles ({cycles * CLOCK_PERIOD}s)"
 | 
						|
    )
 | 
						|
    cycles += Emulator.run_vcpu_to(0x200)
 | 
						|
    print(
 | 
						|
        f"Program was ready to begin exectution after {cycles} cycles ({cycles * CLOCK_PERIOD}s)"
 | 
						|
    )
 | 
						|
    return cycles
 | 
						|
 | 
						|
 | 
						|
def _run_to_function_entry(symbols, function):
 | 
						|
    # Can't run to CALL, as it's included in DEF
 | 
						|
    target_instruction = asm.symbol("CALL") + 1
 | 
						|
    cycles = Emulator.run_to(target_instruction, max_instructions=1_000_000)
 | 
						|
    if Emulator.next_instruction != target_instruction:
 | 
						|
        # Must have hit a breakpoint, just return and hope the caller knows what to do
 | 
						|
        return cycles
 | 
						|
    # May be calling the wrong function
 | 
						|
    while Emulator.AC != symbols[function]:
 | 
						|
        cycles += Emulator.run_to(target_instruction, max_instructions=1_000_000)
 | 
						|
        if Emulator.next_instruction != target_instruction:
 | 
						|
            return cycles
 | 
						|
    # Allow CALL to run, so that we return when to enter the function
 | 
						|
    cycles += Emulator.step_vcpu()
 | 
						|
    return cycles
 | 
						|
 | 
						|
 | 
						|
def _complete_function():
 | 
						|
    return Emulator.run_vcpu_to(Emulator.vLR)
 | 
						|
 | 
						|
 | 
						|
def benchmark(name, gcl_file):
 | 
						|
    symbols = _load(gcl_file)
 | 
						|
    message = f"Running {name}"
 | 
						|
    print(message)
 | 
						|
    print("=" * len(message))
 | 
						|
    cycles = _run_to_main_start()
 | 
						|
    cycles += _run_to_function_entry(symbols, "CalcSet")
 | 
						|
    print(
 | 
						|
        f"Program initialisation was complete after {cycles} cycles ({cycles * CLOCK_PERIOD}s)"
 | 
						|
    )
 | 
						|
    cycles += _complete_function()
 | 
						|
    print(f"CalcSet was complete after {cycles} cycles ({cycles * CLOCK_PERIOD}s)")
 | 
						|
    print()
 | 
						|
 | 
						|
 | 
						|
if __name__ == "__main__":
 | 
						|
    for name, gcl_file in [
 | 
						|
        ("Original", ORIGINAL_MANDELBROT),
 | 
						|
        ("Modified", MY_MANDELBROT),
 | 
						|
    ]:
 | 
						|
        benchmark(name, gcl_file)
 |