Files
psxrecomp/tools/probe_card_writes.py
Matthew Stanley 1b59103b3c runtime: card-read summary + card-data-writes diagnostic rings
Two passive, always-on rings for auditing memory-card reads end-to-end
without arming/timing probes:

card_read_summary (non-evicting, first 32 successful reads):
  Captured at the MC_READ_END transition in sio_process_byte. Records
  slot/cmd/sector/checksum/data_idx/first 16 bytes/cycle/caller func.
  TCP cmds: card_read_summary, card_read_summary_reset.

card_data_writes (4096 entries, rolling tail):
  Arm side: sio_read(SIO_RX_DATA) sets a pending flag while the card
  protocol is in MC_READ_DATA..MC_READ_END.
  Check side: psx_write_byte/half/word capture the next RAM write
  whose new_val matches the armed RX byte, then disarm.
  Records dest addr / value / store_pc / func / mc_idx / slot / cycle.
  TCP cmds: card_data_writes, card_data_writes_reset.

Helpers:
  tools/probe_card_reads.py
  tools/probe_card_writes.py
  tools/probe_card_writes_raw.py
  tools/peek_cards.py

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 22:20:55 -07:00

74 lines
2.2 KiB
Python

"""Dump the card-data-writes ring (BIOS RAM destinations of incoming card bytes).
Each entry: (seq, cyc, addr, value, width, mc_state, mc_idx, slot, store_pc, func).
The mc_state values: 8=READ_DATA, 9=READ_CHK, 10=READ_END (or whatever your enum says).
mc_idx is 0..127 — the offset within the 128-byte sector that this byte represents.
"""
import socket, json, sys
count = int(sys.argv[1]) if len(sys.argv) > 1 else 1024
host, port = '127.0.0.1', 4370
s = socket.create_connection((host, port))
s.settimeout(3.0)
s.sendall(json.dumps({'id': 1, 'cmd': 'card_data_writes', 'count': count}).encode() + b'\n')
buf = b''
while True:
try:
chunk = s.recv(65536)
except socket.timeout:
break
if not chunk:
break
buf += chunk
depth = 0; in_str = False; esc = False
for c in buf:
if esc:
esc = False; continue
if c == 0x5C:
esc = True; continue
if c == 0x22:
in_str = not in_str; continue
if in_str:
continue
if c == 0x7B:
depth += 1
elif c == 0x7D:
depth -= 1
if depth == 0 and buf.strip():
break
s.close()
obj = json.loads(buf.decode())
print(f"# total_seq={obj['total_seq']} avail={obj['avail']} count={obj['count']}")
# Group consecutive entries by destination buffer base.
entries = obj.get('entries', [])
if not entries:
print("# no entries")
sys.exit(0)
# Show summary: dest base inferred from each run of mc_idx 0..127.
i = 0
while i < len(entries):
run = [entries[i]]
j = i + 1
while j < len(entries):
# consecutive idx OR new run starts at idx 0
prev = run[-1]
cur = entries[j]
if cur['mc_idx'] == prev['mc_idx'] + 1 and cur['slot'] == prev['slot']:
run.append(cur)
j += 1
else:
break
base = run[0]
end = run[-1]
base_addr = int(base['addr'], 16)
end_addr = int(end['addr'], 16)
print(f"slot={base['slot']} idx_range=[{base['mc_idx']}..{end['mc_idx']}] "
f"addr=0x{base_addr:08X}..0x{end_addr:08X} ({len(run)} bytes) "
f"first_func={base['func']} first_pc={base['store_pc']} "
f"first_val={base['value']}")
i = j