skywalker-1/tools/boot_ab_test.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

195 lines
6.3 KiB
Python

#!/usr/bin/env python3
"""A/B test: compare BCM4500 register state with and without firmware download.
Uses wIndex boot flags on BOOT_8PSK (0x89):
bit 0 (0x01): skip EEPROM firmware download
bit 1 (0x02): add 200ms DSP startup delay after download
bit 2 (0x04): skip register init blocks
Test matrix:
A) wIndex=0x01 — skip FW download, init blocks only
B) wIndex=0x00 — full boot (FW download + init blocks)
C) wIndex=0x02 — full boot + 200ms DSP delay
D) wIndex=0x04 — FW download only, skip init blocks
"""
import sys
import time
sys.path.insert(0, 'tools')
from skywalker_lib import SkyWalker1
CMD_RAW_DEMOD_READ = 0xB1
CMD_I2C_RAW_READ = 0xB5
CMD_GET_PLL_DIAG = 0xBF
BCM4500_ADDR = 0x08
# Key direct registers
KEY_REGS = [
(0xA0, 'CFG_MODE'),
(0xA2, 'STATUS'),
(0xA4, 'LOCK'),
(0xA6, 'PAGE'),
(0xA7, 'DATA'),
(0xA8, 'CMD'),
(0xA9, 'PLL_A9'),
(0xAA, 'PLL_AA'),
(0xAB, 'PLL_AB'),
]
def read_key_regs(sw, label):
"""Read key BCM4500 registers via raw I2C (ground truth)."""
print(f' --- {label} ---')
results = {}
for reg, name in KEY_REGS:
try:
data = sw._vendor_in(CMD_I2C_RAW_READ, value=BCM4500_ADDR,
index=reg, length=1)
val = data[0]
results[reg] = val
marker = ''
if val == 0x00:
marker = ' (zero)'
elif val == 0x02:
marker = ' << non-zero!'
elif val != 0x00:
marker = ' << non-zero!'
print(f' 0x{reg:02X} ({name:8s}): 0x{val:02X}{marker}')
except Exception as e:
results[reg] = None
print(f' 0x{reg:02X} ({name:8s}): FAILED ({e})')
nonzero = sum(1 for v in results.values() if v and v != 0)
print(f' Non-zero registers: {nonzero}/{len(results)}')
return results
def boot_with_flags(sw, wval=1, flags=0, label=''):
"""Send BOOT_8PSK with wValue and wIndex (boot flags)."""
print(f'\n{"="*60}')
print(f'BOOT: wValue={wval}, wIndex=0x{flags:02X}{label}')
print(f'{"="*60}')
# Send boot command: wValue=boot mode, wIndex=flags
try:
result = sw._vendor_in(0x89, value=wval, index=flags, length=3)
cfg = result[0]
stage = result[1]
bits = []
if cfg & 0x01: bits.append('Started')
if cfg & 0x02: bits.append('FW_Loaded')
if cfg & 0x04: bits.append('Intersil')
if cfg & 0x08: bits.append('DVBmode')
print(f' Config: 0x{cfg:02X} ({" | ".join(bits) if bits else "none"})')
print(f' Boot stage: 0x{stage:02X}' +
(' (COMPLETE)' if stage == 0xFF else f' (stopped at {stage})'))
except Exception as e:
print(f' Boot command failed: {e}')
return None
# PLL diagnostics
try:
pd = sw._vendor_in(CMD_GET_PLL_DIAG, value=0, index=0, length=10)
print(f' PLL diag: eeprom={"Y" if pd[0] else "N"} '
f'blocks={pd[2]} '
f'exit={"OK" if pd[6]==1 else "FAIL" if pd[6]==0 else "skip"} '
f'result={"OK" if pd[7]==1 else "FAIL"}')
except Exception:
pass
return read_key_regs(sw, 'Registers immediately after boot')
def main():
sw = SkyWalker1()
sw.open()
print('=== BCM4500 Boot A/B Test ===')
print(f'Firmware: {sw.get_fw_version()}')
# Pre-boot register state (BCM4500 may still be in stock-firmware state)
print('\n' + '='*60)
print('PRE-BOOT: Register state before any boot command')
print('='*60)
pre = read_key_regs(sw, 'Before boot')
# Shutdown first to establish baseline
print('\n--- Shutting down BCM4500 ---')
sw._vendor_in(0x89, value=0, index=0, length=3)
time.sleep(0.5)
# ===== TEST A: Init blocks only (no firmware download) =====
regs_a = boot_with_flags(sw, wval=1, flags=0x01,
label='Init blocks ONLY (skip FW download)')
# Read again after 200ms
time.sleep(0.2)
regs_a2 = read_key_regs(sw, 'After 200ms settle (test A)')
# Shutdown and wait
print('\n--- Shutting down BCM4500 ---')
sw._vendor_in(0x89, value=0, index=0, length=3)
time.sleep(0.5)
# ===== TEST B: Full boot (firmware download + init blocks) =====
regs_b = boot_with_flags(sw, wval=1, flags=0x00,
label='Full boot (FW download + init blocks)')
# Read again after 200ms
time.sleep(0.2)
regs_b2 = read_key_regs(sw, 'After 200ms settle (test B)')
# Shutdown and wait
print('\n--- Shutting down BCM4500 ---')
sw._vendor_in(0x89, value=0, index=0, length=3)
time.sleep(0.5)
# ===== TEST C: Full boot + 200ms DSP startup delay =====
regs_c = boot_with_flags(sw, wval=1, flags=0x02,
label='Full boot + 200ms DSP delay')
regs_c2 = read_key_regs(sw, 'Registers (test C)')
# Shutdown and wait
print('\n--- Shutting down BCM4500 ---')
sw._vendor_in(0x89, value=0, index=0, length=3)
time.sleep(0.5)
# ===== TEST D: Firmware download only (no init blocks) =====
regs_d = boot_with_flags(sw, wval=1, flags=0x04,
label='FW download ONLY (skip init blocks)')
time.sleep(0.2)
regs_d2 = read_key_regs(sw, 'After 200ms settle (test D)')
# ===== SUMMARY =====
print('\n' + '='*60)
print('SUMMARY: Register 0xA2 (STATUS) across tests')
print('='*60)
def val_str(regs, reg=0xA2):
if regs is None:
return 'FAIL'
v = regs.get(reg)
if v is None:
return 'FAIL'
return f'0x{v:02X}'
print(f' Pre-boot (stock FW state): {val_str(pre)}')
print(f' A) Init only, immediate: {val_str(regs_a)}')
print(f' A) Init only, +200ms: {val_str(regs_a2)}')
print(f' B) Full boot, immediate: {val_str(regs_b)}')
print(f' B) Full boot, +200ms: {val_str(regs_b2)}')
print(f' C) Full boot + DSP delay: {val_str(regs_c)}')
print(f' D) FW only, +200ms: {val_str(regs_d2)}')
print()
print('Expected: A should show 0x02 (previous working state)')
print(' B should show 0x00 (current broken state)')
print(' C tests if DSP just needs more startup time')
print(' D isolates firmware download effect')
sw.close()
print('\n=== Done ===')
if __name__ == '__main__':
main()