SXXXXXXX_PyDownloadFwViaSRIO/doc/architecture.md
2026-01-22 17:10:05 +01:00

19 KiB

Architecture Documentation

Overview

pydownloadfwviasrio implementa un sistema di programmazione flash remota che usa SRIO-over-TFTP per comunicare con un target hardware. L'implementazione replica fedelmente il protocollo del software C++ di riferimento (dwl_fw.cpp) con workflow identico per garantire compatibilità hardware.

Componenti principali

  1. Sender (client): Applicazione Python con GUI Tkinter che invia comandi flash via TFTP
  2. Receiver (target/emulator): Server TFTP che decodifica comandi SRIO, gestisce registri flash controller e scrive su flash memory
  3. SRIO Flash Controller: Layer che gestisce sequenze registro-flash (MODE_REG, CMD_REG, CTRL_REG, STATUS_REG, FIFO)
  4. Profiles System: Configurazione multi-target con supporto GlobalConfig → FlashModel → FlashTarget hierarchy

Protocollo SRIO-over-TFTP

Architettura Registri Flash Controller

Il sistema emula un flash controller SRIO con registri mappati in memoria (da dwl_fw.h):

// Register addresses (256-byte aligned)
#define MODE_REG      0x4700002C  // Flash mode: 0x00=primary, 0x08=secondary
#define CMD_REG       0x47000030  // Flash command byte
#define ADDR_REG      0x47000034  // Flash memory address (32-bit)
#define NUM_BYTE_REG  0x47000038  // Number of bytes to transfer
#define CTRL_REG      0x47000060  // Control: 0x01=START, 0x00=END, 0x03=WRITE/READ
#define TX_FIFO_REG   0x47000400  // TX FIFO (256 bytes for write data)
#define STATUS_REG    0x47000864  // Status: bit[11]=busy/ack, bit[12]=tx_empty
#define RX_FIFO_REG   0x47000C00  // RX FIFO (256 bytes for read data/status)

Formato comando TFTP

I comandi SRIO vengono codificati nel filename TFTP secondo questo schema:

$SRIO:<slot>/<address>+<length>

Esempi:

  • $SRIO:0x13/0x4700002C+256 → Write 256 byte a MODE_REG (registro)
  • $SRIO:0x13/0x47000400+256 → Write 256 byte a TX_FIFO_REG (dati flash)
  • $SRIO:0x13/0x01000000+256 → Write/Read 256 byte flash address 0x01000000

Operazioni Base

Write Register (TFTP PUT)

# Scrive valore 0x06 a MODE_REG
data = struct.pack('<I', 0x06)  # 4 byte little-endian
padded = data + b'\x00' * 252   # Pad a 256 byte
client.srio_write(slot="0x13", address=MODE_REG, data=padded)

Read Register (TFTP GET)

# Legge STATUS_REG
block = client.srio_read(slot="0x13", address=STATUS_REG, length=256)
status = struct.unpack('<I', block[STATUS_REG & 0xFF : (STATUS_REG & 0xFF)+4])[0]

Workflow Flash Operations (da dwl_fw.cpp)

1. Inizializzazione QSPI

Eseguita automaticamente al primo erase/write/verify (chiamata 2 volte come da main() line 713-714):

def op_init_qspi(endpoint):
    """dwl_fw.cpp line 299-323"""
    # Write Enable
    srio_write_reg(endpoint, MODE_REG, 0x00 + flashport)
    srio_write_reg(endpoint, CMD_REG, 0x06)
    send_request(endpoint)
    
    # Write Volatile Config Register (0x61, data=0x6F)
    srio_write_reg(endpoint, MODE_REG, 0x00 + flashport)
    srio_write_reg(endpoint, CMD_REG, 0x61)
    start_request(endpoint)
    srio_write_reg(endpoint, TX_FIFO_REG, 0x6F)
    write_data(endpoint)
    
    # Write Enable again
    srio_write_reg(endpoint, MODE_REG, 0x02 + flashport)
    srio_write_reg(endpoint, CMD_REG, 0x06)
    send_request(endpoint)
    
    # Enter 4-byte address mode (0xB7)
    srio_write_reg(endpoint, MODE_REG, 0x02 + flashport)
    srio_write_reg(endpoint, CMD_REG, 0xB7)
    send_request(endpoint)

Chiamata nel main:

op_init_qspi(endpoint);  // Prima chiamata
op_init_qspi(endpoint);  // Seconda chiamata (dwl_fw.cpp line 713-714)

2. Erase Sector (64KB)

def erase_section(endpoint, address):
    """dwl_fw.cpp line 580-594"""
    # Write Enable
    srio_write_reg(endpoint, MODE_REG, 0x02 + flashport)
    srio_write_reg(endpoint, CMD_REG, 0x06)
    send_request(endpoint)
    
    # Sector Erase command (0xD8)
    srio_write_reg(endpoint, MODE_REG, 0x06 + flashport)
    srio_write_reg(endpoint, ADDR_REG, address)
    srio_write_reg(endpoint, NUM_BYTE_REG, 0x00)
    srio_write_reg(endpoint, CMD_REG, 0xD8)
    send_request(endpoint)

def wait_flash(endpoint):
    """dwl_fw.cpp line 500-513"""
    # Read flash status register (0x05)
    srio_write_reg(endpoint, MODE_REG, 0x06 + flashport)
    srio_write_reg(endpoint, CMD_REG, 0x05)
    read_data(endpoint)
    srio_wait_reg(endpoint, RX_FIFO_REG, bit=0, value=0)  # Wait bit[0]=0 (ready)
    end_request(endpoint)

# Main erase loop (dwl_fw.cpp line 723-733)
for i in range(0, file_size, ERASE_BLOCK_SIZE):
    erase_section(endpoint, start_address + i)
    wait_flash_with_retry(endpoint, MAX_RETRY_READ=1000, timeout_us=1000)

3. Write Flash (256 byte chunks)

def write_flash(endpoint, address, data_256bytes):
    """dwl_fw.cpp line 555-578"""
    # Write Enable
    srio_write_reg(endpoint, MODE_REG, 0x02 + flashport)
    srio_write_reg(endpoint, CMD_REG, 0x06)
    send_request(endpoint)
    
    # Page Program command (0x02)
    srio_write_reg(endpoint, MODE_REG, 0x06 + flashport)
    srio_write_reg(endpoint, ADDR_REG, address)
    srio_write_reg(endpoint, NUM_BYTE_REG, 256)
    srio_write_reg(endpoint, CMD_REG, 0x02)
    start_request(endpoint)
    srio_write_data(endpoint, TX_FIFO_REG, data_256bytes)
    write_data(endpoint)

# Main write loop (dwl_fw.cpp line 747-767)
address = start_address
for chunk in chunks_256bytes:
    write_flash(endpoint, address, chunk)
    usleep(WRITE_DELAY_US)           # 100ms delay (line 562)
    wait_flash_with_retry(endpoint, MAX_RETRY_READ=1000, timeout_us=1000)
    usleep(WAITFLASH_DELAY_US)       # 100ms delay (line 564)
    address += 256

4. Read/Verify Flash

def read_flash(endpoint, address, num_bytes):
    """dwl_fw.cpp line 344-358"""
    # Fast Read command (0x0B)
    srio_write_reg(endpoint, MODE_REG, 0x06 + flashport)
    srio_write_reg(endpoint, ADDR_REG, address)
    srio_write_reg(endpoint, NUM_BYTE_REG, num_bytes)
    srio_write_reg(endpoint, CMD_REG, 0x0B)
    read_data(endpoint)
    data = srio_read_reg(endpoint, RX_FIFO_REG, num_bytes)
    end_request(endpoint)
    return data

Macro SRIO (dwl_fw.cpp line 235-293)

def start_request(endpoint):
    """Set CTRL=0x01, wait STATUS[11]=1"""
    srio_write_reg(endpoint, CTRL_REG, 0x01)
    srio_wait_reg(endpoint, STATUS_REG, bit=11, value=1)

def end_request(endpoint):
    """Check error bits, set CTRL=0x00, wait STATUS[11]=0"""
    for bit in [1, 4, 5, 6, 10]:
        srio_wait_reg(endpoint, STATUS_REG, bit=bit, value=0)
    srio_write_reg(endpoint, CTRL_REG, 0x00)
    srio_wait_reg(endpoint, STATUS_REG, bit=11, value=0)

def send_request(endpoint):
    """Full request: START → WRITE/READ → END"""
    start_request(endpoint)
    srio_write_reg(endpoint, CTRL_REG, 0x03)
    srio_wait_reg(endpoint, STATUS_REG, bit=12, value=1)
    srio_write_reg(endpoint, CTRL_REG, 0x01)
    srio_wait_reg(endpoint, STATUS_REG, bit=12, value=0)
    end_request(endpoint)

def write_data(endpoint):
    """Write TX_FIFO data to flash"""
    srio_write_reg(endpoint, CTRL_REG, 0x03)
    srio_wait_reg(endpoint, STATUS_REG, bit=12, value=1)
    srio_write_reg(endpoint, CTRL_REG, 0x01)
    srio_wait_reg(endpoint, STATUS_REG, bit=12, value=0)
    end_request(endpoint)

def read_data(endpoint):
    """Read flash data into RX_FIFO"""
    start_request(endpoint)
    srio_write_reg(endpoint, CTRL_REG, 0x03)
    srio_wait_reg(endpoint, STATUS_REG, bit=12, value=1)
    srio_write_reg(endpoint, CTRL_REG, 0x01)
    srio_wait_reg(endpoint, STATUS_REG, bit=12, value=0)

Gestione Profili (Hierarchy System)

Struttura a 3 livelli

GlobalConfig
    ├─ ip: "192.168.1.100"
    ├─ port: 69
    ├─ default_target: "TGT-DWL_BRD-1"
    │
    ├─ FlashModel (MODEL-1)
    │   ├─ model: "MT25QL128"
    │   ├─ flash_type: "NOR"
    │   ├─ is_4byte_addressing: false
    │   ├─ num_sectors: 256
    │   ├─ golden_start: 0x00000000
    │   ├─ golden_stop:  0x007FFFFF
    │   ├─ user_start:   0x00800000
    │   └─ user_stop:    0x00FFFFFF
    │
    └─ FlashTarget (TGT-1)
        ├─ id_target: "TGT-DWL_BRD-1"
        ├─ slot_address: 0x13
        ├─ architecture: "PowerPC"
        ├─ model: "MODEL-1" (→ riferimento a FlashModel)
        ├─ golden_binary_path: "C:/firmware/golden.bin"
        └─ user_binary_path: "C:/firmware/user.bin"

Caricamento da targets.ini

[default]
ip = 192.168.1.100
port = 69
default_target = TGT-DWL_BRD-1

[MODEL-1]
model = MT25QL128
description = Micron 128Mbit NOR Flash
flash_type = NOR
is_4byte_addressing = false
num_sectors = 256
golden_start = 0x00000000
golden_stop = 0x007FFFFF
user_start = 0x00800000
user_stop = 0x00FFFFFF

[TGT-1]
id_target = TGT-DWL_BRD-1
description = Download Board #1
slot_address = 0x13
architecture = PowerPC
model = MODEL-1
golden_binary_path = 
user_binary_path =

ProfileManager

manager = ProfileManager()
manager.load_from_ini(Path("targets.ini"))  # Carica da INI
manager.export_to_ini(Path("targets.ini"))  # Salva su INI
manager.save()  # Persiste in flash_profiles.json
manager.load()  # Carica da flash_profiles.json

# Ottieni target con modello
target, model = manager.get_target_with_model("TGT-DWL_BRD-1")

File Structure

pydownloadfwviasrio/
├── core/
│   ├── core.py              # FirmwareFlasher (high-level erase/write/verify)
│   └── srio_flash.py        # SRIOFlashController (low-level register protocol)
├── gui/
│   └── gui.py               # FlasherGUI (Tkinter UI con dual logs)
├── gui/
│   └── gui.py               # FlasherGUI (Tkinter UI con dual logs)
├── profiles.py              # GlobalConfig, FlashModel, FlashTarget, ProfileManager
├── tftp_client.py           # SimpleTFTPClient + SRIOTFTPClient (TFTP sender)
└── __main__.py              # Entry point

tools/
└── tftp_receiver.py         # TFTP server emulator con simulazione flash controller

_OLD/
├── linux_app/
│   ├── dwl_fw.cpp           # ⭐ REFERENCE implementation (native SRIO Linux)
│   └── dwl_fw.h             # Register definitions
└── Vecchia_app/
    └── FpgaBeamMeUp/        # Vecchia Qt app (SRIO-over-TFTP sender)
        ├── fpgaflashengine.cpp
        ├── fpgaflashinterface.h
        └── fgpaprogrammer.cpp

GUI Features

Main Window

  • Target Selector: ComboBox con lista target da flash_profiles.json
  • Target Info Panel: Visualizza IP, porta, slot SRIO, modello flash, aree memoria, path binari golden/user
  • Operation Buttons: Erase, Write, Verify, Erase+Write+Verify
  • Progress Bar: Barra progresso con percentuale
  • Dual Log Panels:
    • General Log (sinistra): Messaggi applicazione, errori, progress
    • TFTP Commands (destra, monospace): Trace protocollo TFTP (PUT/GET con indirizzi)

Configuration Manager (3 Tabs)

  1. Global Tab: IP, porta TFTP, target di default
  2. Flash Models Tab: Lista modelli flash con editor inline (tipo, addressing, settori, aree golden/user)
  3. Targets Tab: Lista target con editor inline (slot SRIO, modello, architettura, path binari)

Memory Area Selection Dialog

Quando si preme Erase/Write/Verify, appare dialog per scegliere:

  • Golden Area: Mostra indirizzo start-stop da modello flash
  • User Area: Mostra indirizzo start-stop da modello flash
  • Usa il path binario corrispondente configurato nel target

Testing Locale

1. Avvia il server TFTP emulator

python tools/tftp_receiver.py --port 6969 --workdir ./tftp_root

Il server:

  • Simula registri SRIO (MODE_REG, CMD_REG, CTRL_REG, STATUS_REG, FIFO)
  • Gestisce state machine flash controller (START/END request, bit polling)
  • Esegue operazioni flash (erase sector → 0xFF, write → salva in file, read → legge da file)
  • Logga tutte le operazioni:
SRIO WRITE: slot=0x13, addr=0x4700002C, len=256
  MODE_REG = 0x06
SRIO WRITE: slot=0x13, addr=0x47000030, len=256
  CMD_REG = 0xD8
SRIO WRITE: slot=0x13, addr=0x47000060, len=256
  CTRL_REG = 0x03 → WRITE/READ_DATA (STATUS[12]=1)
    → Flash: ERASE sector at 0x01000000

2. Avvia la GUI

python -m pydownloadfwviasrio

3. Configura target di test

In Configuration Manager → Global Tab:

  • IP: 127.0.0.1
  • Port: 6969

Oppure crea target in targets.ini:

[TGT-TEST]
id_target = TGT-TEST-LOCAL
slot_address = 0x13
model = MODEL-1
golden_binary_path = C:/test/golden_test.bin
user_binary_path = C:/test/user_test.bin

4. Testa operazioni

  1. Seleziona target test
  2. Premi "Write" → Scegli "User Area" → Seleziona file binario
  3. Verifica log TFTP panel (destra) per vedere comandi SRIO
  4. Verifica server console per vedere operazioni flash

Compatibilità con Vecchia_app

Il protocollo è 100% compatibile con il formato usato da FpgaBeamMeUp:

  • Stesso schema filename: $SRIO:<slot>/<address>+<len>
  • Stesso chunk size: 256 byte (dwl_fw.cpp DWL_CHUNK_SIZE)
  • Stesso comportamento TFTP read/write
  • Stessa sequenza registri flash (da dwl_fw.cpp)

Differenze (miglioramenti):

  • Semplificato: nessun Qt event loop, nessun layer FpgaFlashEngine/FlashOperation
  • Workflow identico a dwl_fw.cpp: stesso ordine operazioni, stessi delay (100ms)
  • Dual log: separazione log applicazione vs protocollo TFTP
  • Testabile: emulator locale con simulazione completa registri
  • Type-safe: type hints Python, no casting void*
  • Cross-platform: nessuna dipendenza Qt, Windows/Linux ready

Riferimenti Codice Legacy

dwl_fw.cpp (Linux SRIO nativo) REFERENCE

  • main(): Sequenza completa erase→write (line 600-792)
    • op_init_qspi() x2 (line 713-714)
    • Erase loop con wait_flash_with_retry() (line 723-733)
    • Write loop con delay 100ms + wait + delay 100ms (line 747-767)
  • Macro SRIO: start_request(), end_request(), send_request(), write_data(), read_data() (line 235-293)
  • Flash ops: erase_section(), write_flash(), read_flash(), wait_flash() (line 344-594)
  • Constants: WRITE_DELAY_US=100000, WAITFLASH_DELAY_US=100000, MAX_RETRY_READ=1000 (line 18-20)

fgpaprogrammer.cpp (Qt SRIO-over-TFTP sender)

  • rtgWrite(): TFTP PUT con filename $SRIO:<slot>/0x<address>+<len> (line 194-260)
  • rtgRead(): TFTP GET (line 338-410)
  • Retry logic: NUM_REPEAT_WRITE, NUM_REPEAT_READ, exponential backoff (line 225-240)

qgtftptargetsim.cpp (Qt TFTP server emulator)

  • bsk_tftpd_receive_delegate(): Decodifica filename SRIO e gestisce operazioni (line 178-279)
  • bsk_tftp_mfs_decode_filename(): Parser filename $SRIO: format (line 235)

fpgaflashengine.cpp / fpgaflashinterface.h (Qt flash abstraction)

  • Layer troppo complesso per le nostre esigenze
  • Usavamo solo per riferimento architetturale (simulator pattern)

Decisioni Architetturali

Perché TFTP e non TCP/UDP custom?

  • TFTP è standard RFC 1350, testabile con tool esterni (tftp, Wireshark)
  • Target hardware espone già server TFTP funzionante
  • Compatibilità con infrastruttura esistente
  • Debugging semplice: filename in chiaro nei log

Perché filename-as-command?

  • Evita di implementare protocollo binario custom
  • Parsing trivial (regex su stringa)
  • Self-documenting (log leggibili)
  • Compatibile con vecchia app Qt

Perché Python e non C++?

  • Rapid prototyping e facilità test
  • Cross-platform senza ricompilare
  • Type hints moderni (meglio di Qt C++ pre-C++11)
  • GUI Tkinter nativa (no dipendenze Qt)
  • TFTP client custom minimale (no tftpy issues con fcntl Windows)

Perché SRIOFlashController separato da FirmwareFlasher?

  • Separation of Concerns:
    • SRIOFlashController: Low-level register protocol (dwl_fw.cpp lines 100-594)
    • FirmwareFlasher: High-level flash operations (dwl_fw.cpp main loop)
  • Testability: Posso testare controller SRIO senza file binari
  • Reusability: Controller riutilizzabile per altre operazioni SRIO

Perché dual log panels?

  • Debugging: TFTP trace separato dai messaggi applicazione
  • User Experience: Utente vede progress in General Log, developer vede protocollo in TFTP Log
  • Protocol Analysis: Facile confrontare sequenza TFTP con dwl_fw.cpp

Features Implementate

Core

  • SRIO Flash Controller: Gestione completa registri (MODE, CMD, CTRL, STATUS, FIFO)
  • QSPI Init: op_init_qspi() chiamato 2x automaticamente (dwl_fw.cpp line 713-714)
  • Erase: Sector erase 64KB con wait_flash_with_retry(MAX_RETRY_READ=1000)
  • Write: 256-byte chunks con delay 100ms + wait + delay 100ms (dwl_fw.cpp line 561-564)
  • Verify: Read back e confronto byte-by-byte
  • Retry Logic: Exponential backoff su timeout TFTP (max 3 attempts)
  • Timing: Delay esatti da dwl_fw.cpp (WRITE_DELAY_US, WAITFLASH_DELAY_US)

GUI

  • Target Selection: ComboBox con lista da flash_profiles.json
  • Info Panel: IP, slot, modello, aree memoria, path binari golden/user
  • Dual Logs: General + TFTP (monospace per trace protocollo)
  • Progress Bar: Percentuale e barra visuale
  • Memory Area Dialog: Selezione golden/user con indirizzi visibili
  • Config Manager: 3 tabs (Global, Models, Targets) con edit inline

Configuration

  • INI Support: Caricamento/salvataggio targets.ini format
  • JSON Persistence: flash_profiles.json per stato runtime
  • 3-Level Hierarchy: GlobalConfig → FlashModel → FlashTarget
  • Binary Paths: golden_binary_path + user_binary_path per target
  • Migration: Tool per convertire vecchi profili (tools/migrate_profiles_json.py)

Testing

  • TFTP Server Emulator: Simulazione completa flash controller
    • Registri SRIO (MODE_REG, CMD_REG, CTRL_REG, STATUS_REG, FIFO)
    • State machine (START/END request, bit polling)
    • Flash operations (erase → 0xFF, write → file, read → file)
    • Logging dettagliato operazioni
  • Local Testing: Server + client su localhost per test senza hardware

Prossimi Step Possibili

  1. Logging su File: Salvare log operazioni in file per audit e debug
  2. Test Hardware Reale: Validare con target fisico e confrontare timing con dwl_fw.cpp
  3. Checksum Verification: Aggiungere CRC32/MD5 per verifica integrità file
  4. Batch Operations: Supporto per programmare multipli target in parallelo
  5. Profile Import/Export: Esportazione profili tra macchine diverse
  6. Wireshark Dissector: Plugin per decode pacchetti TFTP con $SRIO: format

Troubleshooting

TFTP Timeout

  • Causa: Server non raggiungibile o firewall blocca porta 69/6969
  • Soluzione: Verificare con ping <ip>, disabilitare firewall per test, controllare porta con netstat

Flash Write Failed

  • Causa: QSPI non inizializzato, flash protetto write, bad sector
  • Soluzione: Verificare log TFTP per sequenza op_init_qspi(), controllare STATUS_REG error bits

Verify Mismatch

  • Causa: Flash non scritta correttamente, read timeout, bit flip
  • Soluzione: Ripetere erase+write, aumentare retry count, controllare checksum file sorgente

Server Not Responding

  • Causa: Server TFTP emulator non avviato, porta già in uso
  • Soluzione: python tools/tftp_receiver.py --port 6969, controllare con netstat -an | findstr 6969