gigatron/rom/Contrib/psr/multiply/benchmark.py
2025-01-28 19:17:01 +03:00

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)