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
- Sender (client): Applicazione Python con GUI Tkinter che invia comandi flash via TFTP
- Receiver (target/emulator): Server TFTP che decodifica comandi SRIO, gestisce registri flash controller e scrive su flash memory
- SRIO Flash Controller: Layer che gestisce sequenze registro-flash (MODE_REG, CMD_REG, CTRL_REG, STATUS_REG, FIFO)
- 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)
- Global Tab: IP, porta TFTP, target di default
- Flash Models Tab: Lista modelli flash con editor inline (tipo, addressing, settori, aree golden/user)
- 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
- Seleziona target test
- Premi "Write" → Scegli "User Area" → Seleziona file binario
- Verifica log TFTP panel (destra) per vedere comandi SRIO
- 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
tftpyissues confcntlWindows)
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.iniformat - ✅ JSON Persistence:
flash_profiles.jsonper 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
- Logging su File: Salvare log operazioni in file per audit e debug
- Test Hardware Reale: Validare con target fisico e confrontare timing con dwl_fw.cpp
- Checksum Verification: Aggiungere CRC32/MD5 per verifica integrità file
- Batch Operations: Supporto per programmare multipli target in parallelo
- Profile Import/Export: Esportazione profili tra macchine diverse
- 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 connetstat
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 connetstat -an | findstr 6969