Was a separate, untracked directory (DataSheet/) outside the repo. Renamed to lowercase and moved in as hardware/v2/docs/datasheet/, with its own .gitignore for LaTeX build byproducts (compiled PDFs stay tracked, .aux/.log/.toc/etc do not). Now versioned and shares this repo's own remote instead of living untracked on disk. Content: IT+EN LaTeX chapter sources, reference manufacturer PDFs, and compiled datasheet PDFs including the 2026-09-07 SDRAM upgrade addendum (AS4C32M16SB-7BIN part/pinout/timing) in the v2-en chapters. Note: hardware/v2/docs/DatasheetLatex/ (and the v1 sibling) is a separate, already-tracked, differently-structured LaTeX document that predates this move -- left untouched, not merged, since its chapter set and content differ and merging was not requested. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013xXuuRUWZScuo1DeYJxs3v
298 lines
18 KiB
TeX
298 lines
18 KiB
TeX
\chapter{Interfaccia host SPI}
|
|
\label{ch:spi}
|
|
|
|
\section{Livello fisico}
|
|
L'FPGA è sempre \textbf{slave} SPI. Il protocollo v1 usa SPI \textbf{Mode~0}
|
|
(CPOL=0, CPHA=0), MSB-first, single-SPI. Un comando per periodo di CS basso; il byte~0
|
|
di ogni transazione è l'opcode. I campi multi-byte sono big-endian.
|
|
|
|
\begin{fnspec}[Campionamento Mode 0]
|
|
\code{mosi} è campionato sul fronte di \textbf{salita} di \code{sclk}; \code{miso} è
|
|
pilotato sul fronte di \textbf{discesa} (stabile prima del successivo campionamento del
|
|
master). \code{spi\_slave} sincronizza \code{sclk/mosi/cs\_n} con un doppio flip-flop
|
|
(CDC a 3 stadi) prima di ogni rilevazione di fronte.
|
|
\end{fnspec}
|
|
|
|
\begin{center}
|
|
\begin{tikztimingtable}[timing/dslope=0.1,timing/.style={x=3.4ex,y=2.2ex},
|
|
xscale=1.0,font=\scriptsize]
|
|
\sig{CS\_N} & H 1L 16L 1H \\
|
|
\sig{SCLK} & L 1L {2C(2)}8{2C(2)} 6L \\
|
|
\sig{MOSI} & U 1U 2D{b7} 2D{b6} 2D{b5} 2D{b4} 2D{b3} 2D{b2} 2D{b1} 2D{b0} 2U \\
|
|
\sig{MISO} & Z 1Z 16D{dato} 1Z \\
|
|
\end{tikztimingtable}
|
|
\end{center}
|
|
\begin{center}\footnotesize\itshape\color{fnGrey}
|
|
Framing di un byte: CS scende, 8 colpi di SCLK, MSB per primo; MISO in tri-state fuori
|
|
transazione.\end{center}
|
|
|
|
\begin{fnnote}[Contratto \texttt{tx\_byte\_req}]
|
|
\code{tx\_byte\_req} è un \emph{prefetch hint}, non un evento ``byte consumato'': un
|
|
consumatore deve avanzare i puntatori (indirizzo RAM, indice byte di risposta) su
|
|
\code{rx\_valid}, che pulsa esattamente una volta per byte reale trasferito.
|
|
\end{fnnote}
|
|
|
|
\section{Framing e lunghezza esplicita}
|
|
La lunghezza dei trasferimenti RAM è \textbf{esplicita}, non delimitata dal fronte di
|
|
CS: \op{WRITE\_RAM}/\op{READ\_RAM} portano un campo lunghezza a 2~byte, così il
|
|
controller SPI necessita solo di un contatore di byte. Gli indirizzi di byte sono a
|
|
23~bit, trasportati in un campo di 3~byte con il bit più alto riservato a 0.
|
|
|
|
\section{Tabella degli opcode}
|
|
\renewcommand{\arraystretch}{1.16}
|
|
\begin{longtable}{C{1.1cm} L{2.4cm} L{3.9cm} L{2.4cm} L{4.0cm}}
|
|
\toprule
|
|
\rowh \thd{Op} & \thd{Nome} & \thd{Payload (host$\to$FPGA)} & \thd{Risposta} & \thd{Funzione} \\
|
|
\midrule
|
|
\endfirsthead
|
|
\rowh \thd{Op} & \thd{Nome} & \thd{Payload} & \thd{Risposta} & \thd{Funzione} \\ \midrule
|
|
\endhead
|
|
\bottomrule
|
|
\endfoot
|
|
\op{0x00} & NOP & --- & --- & Nessuna operazione (idle/dummy clocking). \\
|
|
\rowa \op{0x01} & WRITE\_RAM & addr(3B)+len(2B)+dati & --- & Scrive un blocco in PSRAM (X, pesi, bias, parametri). \\
|
|
\op{0x02} & READ\_RAM & addr(3B)+len(2B) & \code{len} byte & Rilegge un blocco da PSRAM. \\
|
|
\rowa \op{0x0F} & RESET & --- & --- & Reset sincrono del motore e azzeramento del latch STATUS; non cancella la PSRAM. \\
|
|
\op{0x10} & SET\_BASE & sel(1B)+addr(3B) & --- & Imposta le basi/registri (vedi §\ref{sec:setbase}). \\
|
|
\rowa \op{0x11} & SET\_NET\_TYPE & type(1B) & --- & Tipo di rete: \code{0x01}=dense (\#1), \code{0x02}=graph (\#2). Default dopo RESET=dense. \\
|
|
\rowa \op{0x20} & START & --- & --- & Avvia \code{neuron\_memory} (percorso single-layer); ignorato se busy. \\
|
|
\op{0x21} & STATUS & --- & 1 byte & bit0=\code{busy} (live), bit1=\code{done} (sticky, clear-on-read), bit2=\code{err} (guard grafo), bit3=\code{flash\_err} (sticky, clear-on-read), bit4=\code{flash\_busy} (live); bit7:5=0. \\
|
|
\rowa \op{0x22} & READ\_OUTPUT & --- & \code{N\_NEURONS} byte & \code{y\_bus} neuron-major (byte~0 = neurone~0); solo percorso dense (Tipo \#1). \\
|
|
\op{0x23} & RUN\_NETWORK & num\_layers(1B) & --- & Avvia l'esecuzione: dispatch su \code{net\_type} verso \code{layer\_sequencer} (\#1) o \code{graph\_engine} (\#2); ignorato se busy. \\
|
|
\rowa \op{0x30} & READ\_CONFIG & --- & 11 byte & Record di configurazione hardware (§\ref{sec:readcfg}). \\
|
|
\op{0x40} & FLASH\_READ\_BLOCK & flash\_addr(3B)+psram\_addr(3B)+len(3B) & --- & Lettura raw flash$\to$PSRAM, bypassa il catalogo. \\
|
|
\rowa \op{0x41} & FLASH\_WRITE\_BLOCK & psram\_addr(3B)+flash\_addr(3B)+len(3B) & --- & Scrittura raw PSRAM$\to$flash (erase-before-write interno + loop Page Program $\leq$256B + poll WIP, trasparente all'host), bypassa il catalogo. \\
|
|
\op{0x42} & FLASH\_ERASE & sector\_addr(3B) & --- & Erase di un settore da 4~KB (deve essere sector-aligned), bypassa il catalogo. \\
|
|
\rowa \op{0x43} & CAT\_READ & --- & --- & Ricarica il catalogo a 16 slot (registri on-chip) dal settore riservato in flash. \\
|
|
\op{0x44} & CAT\_WRITE\_SLOT & slot\_id(1B)+offset(3B)+len(3B)+tipo(1B) & --- & Registra/aggiorna (offset, lunghezza, tipo) dello slot nel catalogo on-chip e lo persiste in flash; marca lo slot \emph{non valido} finché \op{SAVE\_SLOT} non lo conferma. \\
|
|
\rowa \op{0x45} & LOAD\_SLOT & slot\_id(1B)+psram\_addr(3B) & --- & Flash$\to$PSRAM per lo slot (offset/lunghezza dal catalogo), verifica CRC32 live; \code{STATUS.flash\_err} se lo slot non è valido o il CRC non torna. \\
|
|
\op{0x46} & SAVE\_SLOT & slot\_id(1B)+psram\_addr(3B)+len(3B) & --- & PSRAM$\to$flash all'offset già registrato dello slot, calcola il CRC32 live; a esito positivo aggiorna e persiste la entry di catalogo (lunghezza, CRC, valid=1). \\
|
|
\rowa \op{0x47} & CAT\_INSPECT & slot\_id(1B) & 16 byte & Lettura sincrona di una entry di catalogo già caricata: offset[3]+len[3]+tipo[1]+valid[1]+CRC32[4]+riservato[4], MSB-first. \\
|
|
\end{longtable}
|
|
Tutti gli opcode flash sono \emph{fire-and-forget}: l'host fa polling su \op{STATUS}
|
|
(bit4=\code{flash\_busy}, bit3=\code{flash\_err}) o sui pin \code{irq\_n}/\code{data\_ready\_n}
|
|
per l'esito, eccetto \op{CAT\_INSPECT} che risponde in modo sincrono.
|
|
|
|
Gli 8 opcode flash (\op{0x40}--\op{0x47}) sono descritti in dettaglio, con
|
|
razionale di progetto e latenze reali misurate, in §\ref{sec:flashspi} sotto.
|
|
|
|
\section{Selettori \texttt{SET\_BASE}}
|
|
\label{sec:setbase}
|
|
\begin{tabularx}{\textwidth}{C{1.2cm} L{3.2cm} Y}
|
|
\toprule
|
|
\rowh \thd{sel} & \thd{Registro} & \thd{Uso} \\
|
|
\midrule
|
|
0 & \code{x\_base} & Base ingresso $X$. \\
|
|
\rowa 1 & \code{w\_base} & Base pesi. \\
|
|
2 & \code{bias\_addr} & Base bias. \\
|
|
\rowa 3 & \code{table\_base} & Base tabella descrittori (multi-layer). \\
|
|
4 & \code{buf\_a\_base} & Buffer ping-pong A. \\
|
|
\rowa 5 & \code{buf\_b\_base} & Buffer ping-pong B. \\
|
|
6 & \code{activation} & Attivazione (2 bit bassi) --- solo percorso single-layer. \\
|
|
\rowa 7 & \code{n\_inputs\_real} & Larghezza ingressi runtime (16-bit BE) --- single-layer. \\
|
|
8 & \code{n\_neurons\_real} & Larghezza neuroni runtime (16-bit BE) --- single-layer. \\
|
|
\rowa 9 & \code{num\_neurons\_graph} & Numero neuroni del grafo (16-bit BE) --- Tipo \#2. \\
|
|
10 & \code{n\_out} & Numero id di uscita (16-bit BE) --- Tipo \#2. \\
|
|
\bottomrule
|
|
\end{tabularx}
|
|
I selettori 6--8 riguardano solo il percorso single-layer/manuale; con \op{RUN\_NETWORK}
|
|
i valori equivalenti sono letti per-layer dalla tabella descrittori.
|
|
|
|
\begin{fnwarn}[Casi limite ``reale=0'' corretti (2026-09-04)]
|
|
La campagna di ri-certificazione (\code{docs/validation/bugs.md}) ha trovato che diversi
|
|
valori runtime pari a zero non erano protetti da alcun guard, con esiti che andavano da un
|
|
risultato silenziosamente ignorato fino a hang o scritture PSRAM a indirizzi arbitrari.
|
|
Tutti e cinque i casi seguenti sono ora no-op sicuri, verificati indipendentemente:
|
|
\begin{itemize}
|
|
\item \code{n\_inputs\_real=0} (selettore 7): completa in 1 ciclo con
|
|
$y=\text{activation}(\text{bias})$ (BUG-003).
|
|
\item \code{n\_neurons\_real=0} (selettore 8): completa senza eseguire alcun calcolo
|
|
per-neurone, molto più rapido di un run a piena larghezza (BUG-004).
|
|
\item \code{num\_neurons\_graph=0} (selettore 9): completa immediatamente dopo la copia
|
|
degli ingressi, senza mai entrare nel loop dei descrittori (BUG-006).
|
|
\item \op{RUN\_NETWORK} con \code{num\_layers=0} (percorso dense): no-op immediato ---
|
|
\textbf{prima del fix eseguiva 256 layer fasulli leggendo dati PSRAM arbitrari come
|
|
descrittori} (BUG-005, CRITICO, vedi \S\ref{sec:run-network} sotto).
|
|
\item \op{SET\_NET\_TYPE} ricevuto mentre un run è in corso: ora rifiutato silenziosamente
|
|
(nessun effetto, nessun errore SPI) invece di rimappare il multiplexer dell'arbitro a metà
|
|
esecuzione --- \textbf{prima del fix causava un hang permanente del motore in corso}
|
|
(BUG-007, CRITICO).
|
|
\end{itemize}
|
|
Dettagli, evidenza e verifica di ciascun fix in \code{docs/validation/bugs.md}.
|
|
\end{fnwarn}
|
|
|
|
\section{\texttt{STATUS.done} sticky / clear-on-read}
|
|
In \code{neuron\_memory} il segnale \code{done} è un impulso di un solo ciclo. Un host
|
|
che effettua polling via SPI (molto più lento del clock FPGA) mancherebbe quasi
|
|
certamente un impulso grezzo di un ciclo. Il banco registri SPI latcha quindi
|
|
\code{done} in un bit sticky sull'impulso e lo azzera quando l'host legge \op{STATUS}
|
|
(o \op{RESET}). Il bit \code{busy} è invece mantenuto a livello per tutta la
|
|
computazione e si legge live.
|
|
|
|
\begin{fnwarn}[Race corretto (2026-09-02)]
|
|
Una race reale nel meccanismo sticky (presente dalla Fase~4) è stata corretta latchando
|
|
uno \code{status\_snapshot} all'accettazione dell'opcode \op{STATUS} e condizionando la
|
|
pulizia del bit sticky a \code{status\_snapshot[1]} (si azzera solo se il byte
|
|
effettivamente trasmesso mostrava \code{done=1}). Un \code{done} che arriva troppo tardi
|
|
per uno snapshot viene riportato al polling successivo invece di essere perso.
|
|
\end{fnwarn}
|
|
|
|
\section{Pin di attenzione host (\texttt{data\_ready\_n}, \texttt{irq\_n})}
|
|
Oltre al polling di \op{STATUS}, il top-level espone due pin fisici attivi bassi (banco 7,
|
|
cap.~\ref{ch:hw}) che rispecchiano i bit sticky senza richiedere una transazione SPI,
|
|
utili per pilotare un GPIO/IRQ dell'host:
|
|
\begin{itemize}
|
|
\item \code{data\_ready\_n} = $\sim$\code{STATUS.done} (sticky): basso quando un risultato è
|
|
pronto da leggere, torna alto alla lettura di \op{STATUS} (clear-on-read).
|
|
\item \code{irq\_n} = $\sim$\code{STATUS.err} (guard grafo): basso quando il guard load-time
|
|
di \code{graph\_engine} è scattato. \textbf{Non} è clear-on-read: si azzera solo con
|
|
\op{RESET} o un nuovo avvio di grafo, così un errore non passa inosservato tra un polling e
|
|
l'altro.
|
|
\end{itemize}
|
|
Sono porte aggiuntive: non toccano gli opcode né i registri esistenti.
|
|
|
|
\begin{fnwarn}[\code{flash\_err} non ha un pin dedicato]
|
|
\code{STATUS.flash\_err} (bit3) è riportato \textbf{solo} nel byte \op{STATUS}, per scelta
|
|
di progetto: riusare \code{irq\_n} lo avrebbe confuso con gli errori del guard grafo (due
|
|
domini di errore indipendenti sullo stesso pin), mentre un'operazione flash è sempre
|
|
avviata dall'host con un opcode appena emesso, quindi il polling di \op{STATUS} subito dopo
|
|
--- già implicito nella convenzione ``fire-and-forget, poi polling \op{STATUS}/
|
|
\code{data\_ready\_n}'' --- è già naturale, senza bisogno di un pin asincrono in più.
|
|
\code{data\_ready\_n} invece \emph{si azzera anche al termine di un'operazione flash}: lo
|
|
specchia \code{STATUS.done} (bit1), che ora latcha anche sul completamento di un op flash,
|
|
non solo su \op{RUN\_NETWORK}/\op{START}.
|
|
\end{fnwarn}
|
|
|
|
\section{\texttt{READ\_CONFIG}}
|
|
\label{sec:readcfg}
|
|
Payload fisso di \textbf{11 byte}: permette a un unico firmware host di funzionare con
|
|
bitstream diversi senza ricompilare. I valori \code{N\_INPUTS}/\code{N\_NEURONS} riportano
|
|
il \emph{massimo} di build (il soffitto), non necessariamente la rete correntemente
|
|
caricata.
|
|
|
|
\begin{tabularx}{\textwidth}{C{1.6cm} L{3.6cm} Y}
|
|
\toprule
|
|
\rowh \thd{Byte} & \thd{Campo} & \thd{Sorgente} \\
|
|
\midrule
|
|
0 & \code{ADDR\_WIDTH} (bit) & \code{neuron\_memory.ADDR\_WIDTH} \\
|
|
\rowa 1--2 & \code{N\_INPUTS} (16-bit BE) & massimo di build \\
|
|
3 & \code{N\_NEURONS} & massimo di build \\
|
|
\rowa 4 & \code{PARALLEL} & parametro di build \\
|
|
5 & \code{DATA\_WIDTH} (bit) & parametro di build \\
|
|
\rowa 6--7 & versione protocollo (BE) & \code{0x0001} \\
|
|
8--9 & \code{N\_TOTAL} (16-bit BE) & massimo segnali grafo (Tipo \#2) \\
|
|
\rowa 10 & flag di capacità & bit0=\code{GRAPH\_SUPPORTED}=1 \\
|
|
\bottomrule
|
|
\end{tabularx}
|
|
|
|
\section{Sottosistema flash (opcode 0x40--0x47, completato 2026-09-04)}
|
|
\label{sec:flashspi}
|
|
La FPGA ha accesso \textbf{esclusivo} alla flash di boot/persistenza onboard (Winbond
|
|
\code{W25Q128JV}, 16~MB SPI NOR, cap.~\ref{ch:hw} §6/§7) tramite un SPI master dedicato e
|
|
fisicamente separato (\code{rtl/spi\_flash\_master.v}), mai per accesso diretto dell'host ai
|
|
pin della flash. \textbf{Non} è un filesystem: un catalogo a dimensione fissa (16 slot,
|
|
\code{rtl/flash\_slot\_manager.v}) mappa \code{slot\_id}~$\to$~(offset, lunghezza, tipo,
|
|
valid, CRC32) in un settore riservato della flash (settore 0) --- nessuna allocazione
|
|
dinamica, nessun garbage collection.
|
|
|
|
\begin{fnnote}[Stratificazione (ogni livello testabile a sé)]
|
|
\begin{itemize}
|
|
\item \code{rtl/spi\_flash\_master.v} --- SPI master grezzo verso il chip flash
|
|
(RDID/READ/WREN/PP/SE/RDSR-1). Bus a 4 fili completamente indipendente
|
|
(\code{sclk}/\code{mosi}/\code{miso}/\code{cs\_n}, tutti GPIO ordinario ---
|
|
Fase F7, 2026-09-04): una versione precedente riusava il pad \code{CCLK} di boot
|
|
via la primitiva ECP5 \code{USRMCLK} per risparmiare un pin, abbandonato perché
|
|
rendeva fuorviante l'affermazione di ``bus esclusivo'' (elettricamente dipendeva
|
|
comunque dal motore di configurazione) e comportava un gap di verifica mai chiuso
|
|
(timing di \code{USRMCLKTS} mai verificato contro la guida Lattice primaria).
|
|
\item \code{rtl/flash\_copy\_engine.v} --- motore di streaming a blocchi: flash$\to$PSRAM
|
|
(\code{DIR\_LOAD}), PSRAM$\to$flash con erase-before-write interno + loop Page
|
|
Program $\leq$256B + poll WIP (\code{DIR\_SAVE}), erase di settore standalone
|
|
(\code{DIR\_ERASE}). Master a bassa priorità (Porta D) su \code{rtl/mem\_arbiter.v}:
|
|
le operazioni flash sono su scala dei ms e non bloccano mai l'inferenza.
|
|
\item \code{rtl/flash\_slot\_manager.v} --- il catalogo a slot sopra, più un CRC32
|
|
(\code{rtl/crc32.v}, IEEE~802.3/zlib) calcolato live sul flusso di byte reale durante
|
|
\op{LOAD\_SLOT}/\op{SAVE\_SLOT}, così uno slot corrotto o scritto a metà (es.
|
|
alimentazione persa durante l'erase) è rilevato anche quando l'operazione flash
|
|
sottostante ha riportato successo.
|
|
\end{itemize}
|
|
\end{fnnote}
|
|
|
|
\begin{fnwarn}[Allineamento a settore obbligatorio]
|
|
\op{SAVE\_SLOT} (e i raw \op{FLASH\_WRITE\_BLOCK}/\op{FLASH\_ERASE}) richiedono che
|
|
l'indirizzo flash target sia allineato a settore da 4~KB --- rifiutato come errore
|
|
altrimenti, invece di un silenzioso read-modify-erase-write parziale del settore (non
|
|
esiste un buffer di scratch abbastanza grande per farlo, e ogni \op{SAVE\_SLOT} reale scrive
|
|
già uno slot intero e allineato per costruzione).
|
|
\end{fnwarn}
|
|
|
|
Razionale completo, ogni citazione da datasheet, ogni test avversariale (CRC non
|
|
corrispondente, slot mai salvato, attraversamento di confine pagina, simulazione di perdita
|
|
di alimentazione, contesa sull'arbitro) e i due bug reali trovati e corretti durante il
|
|
bring-up (uno pre-esistente in \code{psram\_controller.v}, uno nel nuovo handshake di
|
|
richiesta dell'arbitro) sono in \code{WORKLOG.md} (voci Fasi F1-F6) e
|
|
\code{docs/FPGA-Neural-Flash-Subsystem-Verification.md} (sunto di copertura per modulo, non
|
|
ripetuto qui).
|
|
|
|
\begin{tabularx}{\textwidth}{L{3.4cm}Y}
|
|
\toprule
|
|
\rowh \thd{Operazione} & \thd{Latenza reale misurata} \\
|
|
\midrule
|
|
ERASE (settore 4~KB) & $\approx$400~ms (dominata dal tSE interno del chip flash, indipendente dal clock host) \\
|
|
\rowa SAVE (pagina 256~B, incl. erase interno) & $\approx$403~ms (idem, tSE+tPP) \\
|
|
LOAD (4096~B) & 1.74~ms (2.35~MB/s) @80~MHz; 8.71~ms (0.47~MB/s) @16~MHz (solo SPI-clock-bound) \\
|
|
\bottomrule
|
|
\end{tabularx}
|
|
Metodologia di misura completa in \code{docs/FPGA-Neural-Flash-Subsystem-Verification.md}.
|
|
|
|
\section{Sequenze di sessione}
|
|
\subsection{Percorso single-layer}
|
|
\begin{lstlisting}[language=,caption={Sessione single-layer},basicstyle=\ttfamily\scriptsize]
|
|
RESET -> 0x0F
|
|
READ_CONFIG -> 0x30 (l'host apprende N_INPUTS/N_NEURONS/...)
|
|
WRITE_RAM (pesi) -> 0x01 ...
|
|
WRITE_RAM (bias) -> 0x01 ...
|
|
SET_BASE (X/W/BIAS) -> 0x10 x3
|
|
WRITE_RAM (input X) -> 0x01 ...
|
|
START -> 0x20
|
|
poll STATUS -> 0x21 (finche' done=1; si azzera a questa lettura)
|
|
READ_OUTPUT -> 0x22
|
|
\end{lstlisting}
|
|
|
|
\subsection{Percorso multi-layer (RUN\_NETWORK)}
|
|
\label{sec:run-network}
|
|
\begin{lstlisting}[language=,caption={Sessione multi-layer},basicstyle=\ttfamily\scriptsize]
|
|
WRITE_RAM (tabella descrittori) -> 0x01 ...
|
|
WRITE_RAM (pesi/bias per layer, X layer0)-> 0x01 ...
|
|
SET_BASE (X/TABLE/BUF_A/BUF_B) -> 0x10 x4
|
|
RUN_NETWORK(num_layers) -> 0x23 <num_layers>
|
|
poll STATUS -> 0x21 (finche' done=1)
|
|
READ_OUTPUT -> 0x22 (y_bus del layer finale)
|
|
\end{lstlisting}
|
|
|
|
\begin{fnnote}[Fuori ambito per v1]
|
|
Dual~SPI e CRC/checksum sui trasferimenti host (SPI assunto affidabile su traccia di
|
|
scheda --- da non confondere con il CRC32 del catalogo flash, §\ref{sec:flashspi}, che
|
|
protegge un dominio diverso: la persistenza flash$\leftrightarrow$PSRAM, non il link SPI
|
|
host).
|
|
\end{fnnote}
|
|
|
|
\begin{fnwarn}[\op{WRITE\_RAM}/\op{READ\_RAM} senza backpressure verso l'host --- rischio reale, non teorico]
|
|
Ogni byte ricevuto/prodotto deve essere completamente processato da \code{spi\_engine}
|
|
prima che arrivi il successivo confine di byte scandito da SCLK --- ragionevole per il
|
|
bulk-loading iniziale di pesi/ingressi, non un percorso real-time. Il rischio concreto: se
|
|
un host emette \op{WRITE\_RAM}/\op{READ\_RAM} prima che la sequenza di power-up di
|
|
\code{psram\_controller.v} sia completata ($\sim$150~\textmu s dopo il reset,
|
|
\code{STATE\_INIT}+\code{STATE\_CR\_INIT}), \code{spi\_engine} si blocca in attesa che il
|
|
primo accesso PSRAM completi, mentre l'host --- non rallentato da alcun handshake ---
|
|
continua a scandire byte. I byte ricevuti durante quello stallo vengono \textbf{scartati
|
|
silenziosamente}, senza errore e senza hang: solo dati sbagliati in PSRAM. Trovato durante
|
|
il lavoro sul sottosistema flash (\code{WORKLOG.md}, Fase~F5) con una riproduzione minimale
|
|
solo-\op{WRITE\_RAM}, senza alcun opcode flash coinvolto: è un rischio generale per
|
|
qualunque host, non specifico agli opcode flash. \textbf{Mitigazione attuale: l'host deve
|
|
attendere il power-up della PSRAM (o assicurarsi che la FPGA sia fuori reset da
|
|
$>$150~\textmu s) prima del suo primo \op{WRITE\_RAM}/\op{READ\_RAM}.} Non risolto a livello
|
|
di protocollo (richiederebbe una vera backpressure, una modifica più ampia) ---
|
|
dichiarato qui come rischio aperto, non aggirato silenziosamente.
|
|
\end{fnwarn}
|