172 lines
8.8 KiB
Python
172 lines
8.8 KiB
Python
#!/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 <disk.d64> <presetControlHex> [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()
|