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)
|