skywalker-1/tools/boot_deep_verify.py
Ryan Malloy 0d6facb321 Add experimental I2C debugging and EEPROM analysis tools
One-off diagnostic scripts from experiments 0xD7-0xDB investigating
the I2C BERR deadlock. Documents the systematic elimination of
software-only recovery approaches:

- i2c_host_test.py: Proved 0xA0 register writes cannot drive I2C bus
- i2c_register_test.py: Tested I2C register writability from host
- i2c_recovery_boot.py: Attempted I2C state machine recovery via boot
- eeprom_flash_a0.py: Host-side EEPROM flash attempt (failed)
- boot_ab_test.py / boot_test.py: EEPROM boot reliability testing
- a8_autoclear_test.py: BCM4500 command register auto-clear behavior
- addr_gateway_test.py: BCM3440 gateway address routing analysis
- stock_fw_compare.py / stock_fw_test.py: Stock vs custom fw analysis
2026-02-20 10:57:10 -07:00

155 lines
4.9 KiB
Python

#!/usr/bin/env python3
"""Deep verification: full register dump + indirect register test.
Checks whether:
1. Direct registers (0xA0-0xBF) are truly all 0x02 or vary
2. Indirect registers respond (proves DSP core is running)
3. Signal monitoring works after boot
4. Boot with vs without FW download produces different indirect reg values
"""
import sys
import time
sys.path.insert(0, 'tools')
from skywalker_lib import SkyWalker1
CMD_RAW_DEMOD_READ = 0xB1
CMD_I2C_RAW_READ = 0xB5
BCM4500_ADDR = 0x08
def full_direct_dump(sw, label):
"""Read all direct registers 0xA0-0xBF via raw I2C."""
print(f'\n --- {label}: Direct Register Dump 0xA0-0xBF ---')
values = {}
for reg in range(0xA0, 0xC0):
try:
data = sw._vendor_in(CMD_I2C_RAW_READ, value=BCM4500_ADDR,
index=reg, length=1)
values[reg] = data[0]
except Exception:
values[reg] = None
# Print in 2 rows of 16
for base in [0xA0, 0xB0]:
row = []
for reg in range(base, base + 16):
v = values.get(reg)
row.append(f'{v:02X}' if v is not None else '??')
print(f' {base:02X}: {" ".join(row)}')
unique = set(v for v in values.values() if v is not None)
print(f' Unique values: {sorted(f"0x{v:02X}" for v in unique)}')
return values
def indirect_reg_test(sw, label):
"""Read indirect registers to test DSP core responsiveness.
Uses 0xB1 with wIndex=0 (indirect mode): wValue=page."""
print(f'\n --- {label}: Indirect Register Test ---')
# Key DSP pages: 0x00 (config), 0x06 (acq), 0x07 (AGC),
# 0x0A (Viterbi), 0x0F (transport)
pages = [0x00, 0x01, 0x06, 0x07, 0x0A, 0x0F, 0x10, 0x20]
results = {}
for page in pages:
try:
data = sw._vendor_in(CMD_RAW_DEMOD_READ, value=page,
index=0, length=1)
val = data[0]
results[page] = val
marker = ' << DSP alive!' if val != 0 else ''
print(f' Page 0x{page:02X}: 0x{val:02X}{marker}')
except Exception as e:
results[page] = None
print(f' Page 0x{page:02X}: FAILED ({e})')
nonzero = sum(1 for v in results.values() if v and v != 0)
print(f' Non-zero pages: {nonzero}/{len(pages)}')
return results
def boot_and_test(sw, flags, label):
"""Boot with given flags and run full diagnostics."""
print(f'\n{"="*60}')
print(f'TEST: {label} (wIndex=0x{flags:02X})')
print(f'{"="*60}')
# Shutdown + wait
sw._vendor_in(0x89, value=0, index=0, length=3)
time.sleep(0.5)
# Boot
result = sw._vendor_in(0x89, value=1, index=flags, length=3)
cfg, stage = result[0], result[1]
bits = []
if cfg & 0x01: bits.append('Started')
if cfg & 0x02: bits.append('FW_Loaded')
print(f' Config: 0x{cfg:02X} ({" | ".join(bits) if bits else "none"})')
print(f' Stage: 0x{stage:02X}' +
(' (COMPLETE)' if stage == 0xFF else f' (at {stage})'))
# Full register dumps
direct = full_direct_dump(sw, label)
indirect = indirect_reg_test(sw, label)
# Wait and re-test indirect (DSP may need startup time)
time.sleep(0.5)
indirect2 = indirect_reg_test(sw, f'{label} +500ms')
# Signal monitor
print(f'\n --- Signal Monitor ---')
try:
sig = sw.signal_monitor()
print(f' {sig}')
except Exception as e:
print(f' Failed: {e}')
return direct, indirect, indirect2
def main():
sw = SkyWalker1()
sw.open()
print('=== BCM4500 Deep Boot Verification ===')
print(f'Firmware: {sw.get_fw_version()}')
# Test 1: Init blocks only (no FW download)
d1, i1, i1b = boot_and_test(sw, 0x01,
'Init blocks only (skip FW download)')
# Test 2: Full boot (FW download + init blocks)
d2, i2, i2b = boot_and_test(sw, 0x00,
'Full boot (FW download + init blocks)')
# Compare indirect registers between the two modes
print(f'\n{"="*60}')
print('COMPARISON: Indirect Registers (init-only vs full boot)')
print('='*60)
pages = [0x00, 0x01, 0x06, 0x07, 0x0A, 0x0F, 0x10, 0x20]
for page in pages:
v1 = i1b.get(page)
v2 = i2b.get(page)
v1s = f'0x{v1:02X}' if v1 is not None else '??'
v2s = f'0x{v2:02X}' if v2 is not None else '??'
diff = ' << DIFFERENT!' if v1 != v2 else ''
print(f' Page 0x{page:02X}: init-only={v1s} full={v2s}{diff}')
# Compare direct registers
print(f'\nDirect register comparison (0xA0-0xBF):')
diffs = []
for reg in range(0xA0, 0xC0):
v1 = d1.get(reg)
v2 = d2.get(reg)
if v1 != v2:
diffs.append(f' 0x{reg:02X}: init-only=0x{v1:02X} full=0x{v2:02X}')
if diffs:
print('\n'.join(diffs))
else:
print(' No differences (all registers identical)')
sw.close()
print('\n=== Done ===')
if __name__ == '__main__':
main()