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
197 lines
6.9 KiB
Python
197 lines
6.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Test I2C communication from host side while FX2LP CPU is halted.
|
|
|
|
When the CPU is halted (CPUCS=1), I2CS=0x0A (clean state). When the CPU
|
|
runs (CPUCS=0), I2CS=0xF6 (stuck). This script tests whether the I2C
|
|
controller is functional during CPU halt by attempting to read the boot
|
|
EEPROM header (which MUST contain 0xC0 or 0xC2).
|
|
|
|
Uses USB vendor command 0xA0 to read/write XDATA registers directly.
|
|
"""
|
|
|
|
import sys
|
|
import time
|
|
import usb.core
|
|
import usb.util
|
|
|
|
# XDATA register addresses
|
|
I2CS_ADDR = 0xE678
|
|
I2DAT_ADDR = 0xE679
|
|
I2CTL_ADDR = 0xE67A
|
|
CPUCS_ADDR = 0xE600
|
|
|
|
# I2CS bit masks
|
|
bmSTART = 0x80
|
|
bmSTOP = 0x40
|
|
bmLASTRD = 0x20
|
|
bmBERR = 0x04
|
|
bmACK = 0x02
|
|
bmDONE = 0x01
|
|
|
|
EEPROM_ADDR_W = 0xA2 # 0x51 << 1 | 0 (write)
|
|
EEPROM_ADDR_R = 0xA3 # 0x51 << 1 | 1 (read)
|
|
|
|
def fx2_read(dev, addr, length=1):
|
|
"""Read XDATA register(s) via USB vendor command 0xA0."""
|
|
return dev.ctrl_transfer(0xC0, 0xA0, addr, 0, length, timeout=1000)
|
|
|
|
def fx2_write(dev, addr, data):
|
|
"""Write XDATA register(s) via USB vendor command 0xA0."""
|
|
dev.ctrl_transfer(0x40, 0xA0, addr, 0, data, timeout=1000)
|
|
|
|
def i2cs_str(val):
|
|
"""Decode I2CS register value."""
|
|
flags = []
|
|
if val & 0x80: flags.append('START')
|
|
if val & 0x40: flags.append('STOP')
|
|
if val & 0x20: flags.append('LASTRD')
|
|
if val & 0x04: flags.append('BERR')
|
|
if val & 0x02: flags.append('ACK')
|
|
if val & 0x01: flags.append('DONE')
|
|
return f"0x{val:02X} ({' | '.join(flags) if flags else 'idle'})"
|
|
|
|
def wait_done(dev, timeout_ms=100):
|
|
"""Poll I2CS for DONE bit."""
|
|
deadline = time.monotonic() + timeout_ms / 1000.0
|
|
while time.monotonic() < deadline:
|
|
i2cs = fx2_read(dev, I2CS_ADDR, 1)[0]
|
|
if i2cs & bmDONE:
|
|
return True, i2cs
|
|
time.sleep(0.001)
|
|
return False, i2cs
|
|
|
|
def main():
|
|
dev = usb.core.find(idVendor=0x09C0, idProduct=0x0203)
|
|
if not dev:
|
|
print("SkyWalker-1 not found")
|
|
sys.exit(1)
|
|
|
|
print("SkyWalker-1 I2C Host-Side Test")
|
|
print("=" * 50)
|
|
|
|
# Step 1: Halt CPU
|
|
print("\n[1] Halting CPU (CPUCS=1)...")
|
|
fx2_write(dev, CPUCS_ADDR, bytes([0x01]))
|
|
time.sleep(0.05)
|
|
|
|
# Step 2: Read I2C state
|
|
i2cs = fx2_read(dev, I2CS_ADDR, 1)[0]
|
|
i2ctl = fx2_read(dev, I2CTL_ADDR, 1)[0]
|
|
print(f" I2CS = {i2cs_str(i2cs)}")
|
|
print(f" I2CTL = 0x{i2ctl:02X}")
|
|
|
|
if i2cs == 0xF6:
|
|
print(" WARNING: I2CS stuck at 0xF6 even during CPU halt!")
|
|
print(" I2C test will likely fail.")
|
|
|
|
# Step 3: Try to write I2CTL and read back
|
|
print("\n[2] Testing register writability...")
|
|
fx2_write(dev, I2CTL_ADDR, bytes([0x01])) # 400kHz
|
|
time.sleep(0.01)
|
|
i2ctl_rb = fx2_read(dev, I2CTL_ADDR, 1)[0]
|
|
print(f" Wrote I2CTL=0x01, read back 0x{i2ctl_rb:02X}", end="")
|
|
if i2ctl_rb == 0x01:
|
|
print(" (write works!)")
|
|
else:
|
|
print(f" (write IGNORED — register is read-only during halt)")
|
|
|
|
# Step 4: Try hardware I2C — read EEPROM header byte at address 0x0000
|
|
print("\n[3] Attempting EEPROM read via hardware I2C...")
|
|
print(" Target: EEPROM 0x51, address 0x0000 (boot header)")
|
|
|
|
# Issue START
|
|
print("\n [START] Writing I2CS = 0x80...")
|
|
fx2_write(dev, I2CS_ADDR, bytes([bmSTART]))
|
|
done, i2cs = wait_done(dev, 200)
|
|
print(f" I2CS = {i2cs_str(i2cs)}, DONE={'YES' if done else 'NO'}")
|
|
|
|
if not done:
|
|
print(" START didn't complete. Trying alternative: write I2DAT first...")
|
|
# Some controllers need I2DAT written before START can proceed
|
|
# Write EEPROM address (0xA2 = 0x51 write)
|
|
fx2_write(dev, I2DAT_ADDR, bytes([EEPROM_ADDR_W]))
|
|
time.sleep(0.01)
|
|
i2cs = fx2_read(dev, I2CS_ADDR, 1)[0]
|
|
print(f" After I2DAT=0xA2: I2CS = {i2cs_str(i2cs)}")
|
|
|
|
if not done:
|
|
# Try the Cypress-documented sequence: START is issued by
|
|
# writing the slave address to I2DAT AFTER writing START to I2CS
|
|
print("\n Trying standard Cypress I2C sequence...")
|
|
# Re-issue START
|
|
fx2_write(dev, I2CS_ADDR, bytes([bmSTART]))
|
|
time.sleep(0.001)
|
|
# Write slave address — this should clock the address byte
|
|
fx2_write(dev, I2DAT_ADDR, bytes([EEPROM_ADDR_W]))
|
|
done, i2cs = wait_done(dev, 200)
|
|
print(f" After START + I2DAT=0xA2: I2CS = {i2cs_str(i2cs)}, DONE={'YES' if done else 'NO'}")
|
|
|
|
if done:
|
|
ack = 'ACK' if (i2cs & bmACK) else 'NAK'
|
|
print(f" Address phase: {ack}")
|
|
|
|
if done:
|
|
# Write EEPROM address bytes (16-bit address: 0x0000)
|
|
print("\n Writing address 0x0000...")
|
|
fx2_write(dev, I2DAT_ADDR, bytes([0x00])) # addr high
|
|
done, i2cs = wait_done(dev, 200)
|
|
ack = 'ACK' if (i2cs & bmACK) else 'NAK'
|
|
print(f" Addr high: {ack}, DONE={'YES' if done else 'NO'}")
|
|
|
|
if done:
|
|
fx2_write(dev, I2DAT_ADDR, bytes([0x00])) # addr low
|
|
done, i2cs = wait_done(dev, 200)
|
|
ack = 'ACK' if (i2cs & bmACK) else 'NAK'
|
|
print(f" Addr low: {ack}, DONE={'YES' if done else 'NO'}")
|
|
|
|
if done:
|
|
# Re-START for read phase
|
|
print("\n Re-START for read phase...")
|
|
fx2_write(dev, I2CS_ADDR, bytes([bmSTART]))
|
|
time.sleep(0.001)
|
|
fx2_write(dev, I2DAT_ADDR, bytes([EEPROM_ADDR_R]))
|
|
done, i2cs = wait_done(dev, 200)
|
|
ack = 'ACK' if (i2cs & bmACK) else 'NAK'
|
|
print(f" Read addr: {ack}, DONE={'YES' if done else 'NO'}")
|
|
|
|
if done:
|
|
# Read first byte (and only byte — set LASTRD + STOP)
|
|
print("\n Reading first byte (LASTRD + STOP)...")
|
|
fx2_write(dev, I2CS_ADDR, bytes([bmLASTRD]))
|
|
time.sleep(0.001)
|
|
# Dummy read to trigger byte transfer
|
|
dummy = fx2_read(dev, I2DAT_ADDR, 1)[0]
|
|
done, i2cs = wait_done(dev, 200)
|
|
if done:
|
|
data = fx2_read(dev, I2DAT_ADDR, 1)[0]
|
|
fx2_write(dev, I2CS_ADDR, bytes([bmSTOP]))
|
|
print(f" Boot header byte: 0x{data:02X}", end="")
|
|
if data == 0xC0:
|
|
print(" (C0 = no renumerate)")
|
|
elif data == 0xC2:
|
|
print(" (C2 = renumerate)")
|
|
else:
|
|
print(f" (unexpected!)")
|
|
print("\n *** EEPROM READ SUCCESSFUL! ***")
|
|
else:
|
|
print(f" Read DONE timeout. I2CS = {i2cs_str(i2cs)}")
|
|
|
|
# Final state
|
|
i2cs_final = fx2_read(dev, I2CS_ADDR, 1)[0]
|
|
print(f"\n[4] Final I2CS = {i2cs_str(i2cs_final)}")
|
|
|
|
# Try STOP to clean up
|
|
fx2_write(dev, I2CS_ADDR, bytes([bmSTOP]))
|
|
time.sleep(0.01)
|
|
i2cs_stop = fx2_read(dev, I2CS_ADDR, 1)[0]
|
|
print(f" After STOP: I2CS = {i2cs_str(i2cs_stop)}")
|
|
|
|
# Release CPU
|
|
print("\n[5] Releasing CPU (CPUCS=0)...")
|
|
fx2_write(dev, CPUCS_ADDR, bytes([0x00]))
|
|
time.sleep(0.5)
|
|
print(" CPU released. Device will re-enumerate.")
|
|
|
|
if __name__ == '__main__':
|
|
main()
|