#!/usr/bin/env python3 # testAdoptPair.py - two machines, true C64 speed: does the driver adopt the line settings the # hardware was already configured for, and does the link still work afterwards? # # python3 testAdoptPair.py [presetCommandHex|-] [seconds] [tag] # # testAdopt.py answers the same question on one machine against a serial sink, which is enough to # read a register but not enough to say the link works. This script pre-programmes BOTH 6551s # through the monitor at the one moment the value still means anything - after the opponent module is # resident and before the "PRESS A OR O" prompt is answered, i.e. before anything in the module has # touched the chip - and then opens a link between them and measures it. # # * the control register is read on both machines before the preset is written, after it is # written, and again after the link is open, so "before and after" is a measurement rather than # an inference; # * the command register can be preset too (that is where a 6551 keeps parity), which is how the # question "is parity adopted as well?" gets an answer instead of an assumption; # * baudIndex $E055, the live control byte bitPeriodLo $E056 and the adopt entry's own byte # $E726 are read alongside, because those three say WHY the chip ends up holding what it holds. # # Nothing here writes any driver byte. The only monitor writes are to the ACIA's own registers, # which is what a user's hardware configuration would have left there. import os import sys import time sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from viceHarness import ViceSession, aciaArgs, readByte, ACIA_BASE, SCRATCH from testTwoMachines import NullModemRelay, pickModemOpponent, answerModemPrompts from testRealtime import (BITS_PER_BYTE, LOGS, NTSC_HZ, SHOTS, command, decodeControl, measureWindowOnce, pauseAll, resumeAll, showState, snapshot, waitForPhase) from testHighRates import moduleResident, perSecond, sampleRings CONTROL = ACIA_BASE + 3 COMMAND = ACIA_BASE + 2 ADOPT_SLOT = 0xE726 # baudEntryTable $E710 + 22: the adopt entry's control byte PAGE_LATCH = 0xE5BF LIVE_CONTROL = 0xE056 # bitPeriodLo: whatever loadBaudParameters last wrote def registers(session, tag, note): pauseAll([(session, tag)]) row = {"control": readByte(session, CONTROL), "command": readByte(session, COMMAND), "baudIndex": readByte(session, 0xE055), "live": readByte(session, LIVE_CONTROL), "adoptSlot": readByte(session, ADOPT_SLOT), "latch": readByte(session, PAGE_LATCH), "shadow": readByte(session, 0xE5BE), "nmiState": readByte(session, 0xE5BD)} resumeAll([(session, tag)]) text = " ".join(f"{name}=" + ("None" if value is None else f"${value:02X}") for name, value in row.items()) print(f"[{tag}] {note}: {text}", flush=True) if row["control"] is not None: print(f"[{tag}] control {decodeControl(row['control'])}", flush=True) return row def main(): disk = os.path.abspath(sys.argv[1]) preset = int(sys.argv[2], 16) presetCommand = None if len(sys.argv) < 4 or sys.argv[3] == "-" else int(sys.argv[3], 16) seconds = float(sys.argv[4]) if len(sys.argv) > 4 else 75.0 tag = sys.argv[5] if len(sys.argv) > 5 else "ap" os.makedirs(SHOTS, exist_ok=True) os.makedirs(LOGS, exist_ok=True) relay = NullModemRelay() print(f"relay on 127.0.0.1:{relay.port}; both ports pre-set to control ${preset:02X}" + (f", command ${presetCommand:02X}" if presetCommand is not None else ""), flush=True) args = aciaArgs(rsDevAddress=f"127.0.0.1:{relay.port}", baud=38400) a = ViceSession(disk, f"{SCRATCH}/ap.{tag}.a.vice.log", args, label="A", warp=False) b = ViceSession(disk, f"{SCRATCH}/ap.{tag}.b.vice.log", args, label="B", warp=False) sessions = [(a, "A"), (b, "B")] rows = {} try: a.connect() b.connect() for session, sessionTag in sessions: command(session, "warp on") a.bootPastLoader(waitSecs=900) b.bootPastLoader(waitSecs=900) pauseAll(sessions) for session, sessionTag in sessions: command(session, "warp off") print(f"[{sessionTag}] {command(session, 'warp').strip()}", flush=True) resumeAll(sessions) a.findWindow() b.findWindow() a.focus() b.focus() time.sleep(25) pickModemOpponent(a, "A", "a") pickModemOpponent(b, "B", "o") for session, sessionTag in sessions: if not moduleResident(session, sessionTag): raise SystemExit(f"{sessionTag}: the opponent module never loaded") # ---- the one moment the user's own setting still exists: the module is resident and has # ---- not touched the chip yet. for session, sessionTag in sessions: rows[(sessionTag, "atPrompt")] = registers(session, sessionTag, "at the A/O prompt, before the preset") pauseAll([(session, sessionTag)]) command(session, f"> {CONTROL:04x} {preset:02x}") if presetCommand is not None: command(session, f"> {COMMAND:04x} {presetCommand:02x}") resumeAll([(session, sessionTag)]) rows[(sessionTag, "preset")] = registers(session, sessionTag, "after the preset was written") a.shot(f"{SHOTS}/ap{tag}01preset.png") answerModemPrompts(a, "A", "a") answerModemPrompts(b, "B", "o") for session, sessionTag in sessions: rows[(sessionTag, "opened")] = registers(session, sessionTag, "after the link was opened") ok, phases = waitForPhase(sessions, 3, 180) print(f"both in the packet phase: {ok} ({phases})", flush=True) for session, sessionTag in sessions: rows[(sessionTag, "phase3")] = registers(session, sessionTag, "in the packet phase") a.shot(f"{SHOTS}/ap{tag}02linked.png") b.shot(f"{SHOTS}/ap{tag}02linkedB.png") run = measureWindowOnce(relay, sessions, seconds, f"adopt ${preset:02X}") buckets = perSecond(run["samples"]) zeros = {} for index, name in ((1, "conn0"), (2, "conn1")): values = [row[index] for row in buckets] zeros[name] = sum(1 for value in values if value < 0.5) print(f" {name} per second: min {min(values):.1f}, max {max(values):.1f}, " f"seconds carrying nothing: {zeros[name]} of {len(values)}", flush=True) open(f"{LOGS}/adoptPair.{tag}.samples.txt", "w").write( "\n".join(f"{t}\t{c0}\t{c1}" for t, c0, c1 in run["samples"]) + "\n") for session, sessionTag in sessions: rows[(sessionTag, "afterWindow")] = registers(session, sessionTag, "at the end of the window") print("\n---- uartRxCount $E42D sampled repeatedly ----", flush=True) sampleRings(sessions, 12, 0.5, f"adoptPair.{tag}") for state in [snapshot(session, sessionTag) for session, sessionTag in sessions]: showState(state) a.shot(f"{SHOTS}/ap{tag}03end.png") b.shot(f"{SHOTS}/ap{tag}03endB.png") print("\n================ SUMMARY ================", flush=True) print(f"preset control ${preset:02X}" + (f", preset command ${presetCommand:02X}" if presetCommand is not None else ""), flush=True) for sessionTag in ("A", "B"): for when in ("atPrompt", "preset", "opened", "phase3", "afterWindow"): row = rows[(sessionTag, when)] text = " ".join(f"{name}=" + ("None" if row[name] is None else f"${row[name]:02X}") for name in ("control", "command", "baudIndex", "live", "adoptSlot", "latch")) print(f" [{sessionTag}] {when:12s} {text}", flush=True) print(f" window {run['wall']:.2f} s wall; speeds " f"{ {tag2: round(100.0 * value, 1) for tag2, value in run['speed'].items()} }", flush=True) for index in (0, 1): count = run["counts"][index] print(f" conn{index} {count} bytes = {count / run['wall']:.2f} B/s; " f"wire {run['wire'][index]}", flush=True) print(f" seconds carrying nothing: {zeros}", flush=True) print(f" both reached phase 3: {ok} ({phases})", flush=True) finally: a.close() b.close() time.sleep(1) relay.stop() for index in (0, 1): print(f"[relay] conn{index} total {len(relay.log[index])} bytes", flush=True) if __name__ == "__main__": main()