#!/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()