\chapter{Sottosistema di memoria} \label{ch:mem} \section{Catena di memoria} Il motore di calcolo lavora con indirizzi e dati a livello di \emph{byte} (INT8), mentre la PSRAM è un dispositivo a parola da 16~bit. Tre moduli in cascata realizzano la conversione e l'accesso fisico: \begin{center} \begin{tikzpicture}[font=\scriptsize,node distance=8mm] \node[fnblockD,minimum width=30mm,minimum height=12mm](nm){master byte-level\\{\scriptsize \code{neuron\_memory} / \code{spi\_engine} / \code{layer\_sequencer}}}; \node[fnblockT,right=10mm of nm,minimum width=28mm,minimum height=12mm](ia){\code{int8\_memory\_access}\\{\scriptsize byte $\leftrightarrow$ word 16-bit}}; \node[fnblock,right=10mm of ia,minimum width=26mm,minimum height=12mm](mi){\code{memory\_interface}\\{\scriptsize FSM IDLE/WAIT}}; \node[fnblockA,below=9mm of mi,minimum width=26mm,minimum height=12mm](pc){\code{psram\_controller}\\{\scriptsize bus fisico async 70\,ns}}; \node[fnblock,left=10mm of pc,minimum width=26mm,minimum height=12mm](ps){PSRAM\\{\scriptsize 8\,MB 4M$\times$16}}; \draw[fnbus] (nm)--node[fnlbl,above]{req/wr/addr}(ia); \draw[fnbus] (ia)--node[fnlbl,above]{16-bit}(mi); \draw[fnbus] (mi)--(pc); \draw[fnbus] (pc)--node[fnlbl,above]{DQ/A/ctrl}(ps); \end{tikzpicture} \end{center} \section{\texttt{int8\_memory\_access} --- conversione byte/word} Converte l'interfaccia INT8 (indirizzo di byte) nell'interfaccia a parola. L'indirizzo di byte viene diviso per due (\code{addr>>1}) per ottenere l'indirizzo di parola; il bit meno significativo seleziona il byte: \begin{itemize} \item \code{addr[0]=0} $\to$ byte basso: \code{lb\_n=0}, \code{ub\_n=1}, dato su DQ[7:0]; \item \code{addr[0]=1} $\to$ byte alto: \code{lb\_n=1}, \code{ub\_n=0}, dato su DQ[15:8]. \end{itemize} In lettura estrae il byte corretto da \code{mem\_rdata}. La FSM ha due stati (IDLE, WAIT) e restituisce \code{ready} come impulso di un ciclo. \section{\texttt{memory\_interface} --- handshake} FSM a due stati che serializza una singola transazione: in IDLE, alla richiesta \code{req}, latcha \code{wr/addr/wdata/lb\_n/ub\_n} ed emette un impulso \code{mem\_req} di un ciclo verso il controller; in WAIT attende \code{mem\_ready}, cattura \code{rdata} in lettura e asserisce \code{ready}. Garantisce il contratto ``una transazione per volta''. \section{\texttt{psram\_controller} --- bus fisico} Controller del bus PSRAM parallelo asincrono, con supporto al \textbf{page mode} di lettura del chip (\S~\ref{sec:pagemode}). La macchina a stati principale è: \begin{center} \begin{tikzpicture}[font=\scriptsize] \node[fnstate](init) at (0,0){INIT}; \node[fnstate](idle) at (3.2,0){IDLE}; \node[fnstate](read) at (7,2.7){READ}; \node[fnstate](popen) at (11,2.7){PAGE\\OPEN}; \node[fnstate](write) at (7,-2.7){WRITE}; \node[fnstate](ww) at (11,-2.7){WRITE\\WAIT}; \draw[fnarrow] (init)--node[fnlbl,above]{INIT\_CYCLES + CR load}(idle); \draw[fnarrow] (idle)--node[fnlbl,above,sloped]{req \& !wr}(read); \draw[fnarrow] (idle)--node[fnlbl,below,sloped]{req \& wr}(write); \draw[fnarrow] (read)--node[fnlbl,above]{ready}(popen); \draw[fnarrowT] (popen) to[bend left=25] node[fnlbl,below]{req \& !wr}(read); \draw[fnarrow] (popen) to[bend right=20] node[fnlbl,above,sloped]{req \& wr}(write); \draw[fnarrow] (popen) to[out=-100,in=15,looseness=1.15] node[fnlbl,pos=0.55]{timeout tCEM}(idle); \draw[fnarrow] (write)--node[fnlbl,above]{ACCESS\_CYCLES}(ww); \draw[fnarrow] (ww) to[out=160,in=-70] node[fnlbl,pos=0.5,left]{ready}(idle); \end{tikzpicture} \end{center} Da INIT il controller passa automaticamente per una sotto-sequenza di caricamento del registro di configurazione (\code{STATE\_CR\_INIT}, 4 passi) prima di raggiungere IDLE per la prima volta --- vedi \S~\ref{sec:pagemode}. La transizione PAGE~OPEN $\to$~WRITE (freccia in basso a destra) passa internamente per due micro-stati di transito, \code{STATE\_PAGE\_CLOSE} e \code{STATE\_PAGE\_REOPEN} (un ciclo ciascuno): il primo forza CE\#/OE\# alti per almeno un ciclo prima che il controller inizi a pilotare il bus dati, evitando contesa con l'uscita ancora attiva della PSRAM ($\geq t_{HZ}$); il secondo riavvia la transazione già latchata esattamente come farebbe IDLE. Non sono disegnati come nodi separati per non appesantire la figura. \subsection{Temporizzazione} \begin{fnspec}[Formule di temporizzazione] $\text{ACCESS\_CYCLES}=\lceil (70\times \text{CLK\_FREQ\_MHZ})/1000\rceil$ \quad (latenza di accesso casuale, $t_{AA}$/$t_{RC}$ = 70~ns)\\[3pt] $\text{PAGE\_CYCLES}=\lceil (20\times \text{CLK\_FREQ\_MHZ})/1000\rceil$ \quad (continuazione nella stessa pagina, $t_{APA}$/$t_{PC}$ = 20~ns)\\[3pt] $\text{INIT\_CYCLES}=150\times \text{CLK\_FREQ\_MHZ}$ \quad (inizializzazione di power-up, $t_{PU}$ = 150~\textmu s)\\[3pt] $\text{PAGE\_TIMEOUT\_CYCLES}=\lceil (6000\times \text{CLK\_FREQ\_MHZ})/1000\rceil$ \quad (chiusura automatica della pagina, margine di sicurezza sotto $t_{CEM}$ = 8~\textmu s) \end{fnspec} Il bus dati è pilotato in tri-state: \code{psram\_dq = dq\_oe ? dq\_out : Z}. In lettura \code{dq\_oe=0}; in scrittura \code{dq\_oe=1} durante l'impulso di \code{we\_n}. Uno stato di WRITE\_WAIT mantiene attivi \code{ce\_n/lb\_n/ub\_n} per l'hold finale prima del rilascio. \begin{fnwarn}[Non è QSPI] Questa è un'interfaccia SRAM-asincrona classica, \textbf{non} QSPI: la maggior parte delle ``PSRAM'' serie/QSPI in commercio non è compatibile con questo controller senza riscrittura. Vedere il cap.~\ref{ch:hw} per la parte raccomandata (ISSI parallela). \end{fnwarn} \section{Page mode di lettura} \label{sec:pagemode} Il chip raccomandato (cap.~\ref{ch:hw}) è ``asynchronous/\textbf{page mode}'': una volta fatto un primo accesso casuale a $t_{AA}$~=~70~ns, letture successive all'interno della stessa pagina da 16 word (bit di indirizzo sopra \code{A[3]} invariati) costano solo $t_{APA}$/$t_{PC}$~=~20~ns, perché CE\#/OE\# restano attivi e cambia solo il bus indirizzi. Il page mode è \textbf{disabilitato di default} all'accensione (bit~7 del registro di configurazione, CR~=~\texttt{0x0070} di default) e va abilitato esplicitamente. \begin{itemize} \item \textbf{Abilitazione all'avvio}: subito dopo INIT, il controller esegue la ``software-access sequence'' del datasheet (2 letture dummy + 2 scritture, \texttt{0x0000} di sblocco poi CR reale \texttt{0x00F0} = default con il bit Page attivo) all'indirizzo più alto del chip --- riusa esattamente la stessa logica READ/WRITE di ogni altra transazione, quindi passa dagli stessi controlli di temporizzazione. \item \textbf{Burst di pagina}: dopo una READ il controller non chiude più CE\#/OE\# (stato PAGE~OPEN). Una READ successiva nella stessa pagina aspetta solo PAGE\_CYCLES; una READ che attraversa pagina resta comunque senza toggle di CE\# ma paga un ACCESS\_CYCLES pieno per quella parola (qualunque cambio a \code{A[4]} o superiore richiede un nuovo $t_{AA}$). Un contatore chiude la pagina prima del limite $t_{CEM}$ con margine di sicurezza. \item \textbf{Solo una WRITE chiude la pagina.} I cambi di \code{lb\_n}/\code{ub\_n} \emph{non} la chiudono: \code{int8\_memory\_access} alterna questi segnali a quasi ogni accesso (accesso byte-granulare su bus a 16~bit), quindi trattarli come condizione di chiusura --- primo tentativo di implementazione --- rendeva il workload reale \emph{più lento}, non più veloce (misurato: 53.25$\to$61.25 cicli/edge sul gather di \code{graph\_engine}); rimosso, corretto a 53.25$\to$37.53 cicli/edge (banda +42\%, \S~\ref{sec:bandwidth}). \end{itemize} \begin{fnwarn}[Nessun beneficio senza pattern sequenziale] Il page mode accelera solo accessi che restano nella stessa pagina (o quasi) mentre il controller resta in attesa di una nuova richiesta con la pagina ancora aperta. Accessi isolati e sparsi (indirizzo casuale ogni volta) pagano comunque ACCESS\_CYCLES pieno, più un piccolo overhead di chiusura/riapertura se preceduti da una WRITE o da un timeout $t_{CEM}$: non è un guadagno universale, dipende dal pattern di accesso del chiamante. \end{fnwarn} Fmax reale (\code{nextpnr-ecp5}, cap.~\ref{ch:impl}) sul sistema integrato \code{spi\_neuron\_top} con Tipo~\#2 abilitato: \textbf{75.73~MHz} a \code{PARALLEL}=2 (era 55.59~MHz prima dell'aggiunta del page mode) e \textbf{65.13~MHz} a \code{PARALLEL}=8, entrambe ancora FAIL all'obiettivo di 80~MHz ma non regredite. Il percorso critico resta, in entrambi i casi, interamente dentro \code{u\_graph\_engine.u\_neuron} (catena di accumulo \code{mac8}/\code{neuron\_parallel}, cap.~\ref{ch:impl}) --- \code{psram\_controller} non compare mai nel percorso critico nonostante la crescita di risorse del page mode. \section{Mappa degli indirizzi e convenzioni} Lo spazio di indirizzamento è di \code{ADDR\_WIDTH}=23~bit (indirizzo di \emph{byte}), per 8~MB pieni. Le regioni non hanno indirizzi cablati: le loro basi sono registri impostati dall'host via \op{SET\_BASE} (percorso single-layer) o lette dalla tabella descrittori (percorso multi-layer). \begin{tabularx}{\textwidth}{L{3.2cm} L{3.4cm} Y} \toprule \rowh \thd{Regione} & \thd{Base} & \thd{Contenuto / convenzione} \\ \midrule Ingresso $X$ & \code{x\_base} & Vettore di ingresso condiviso, letto una volta per invocazione. \\ \rowa Pesi $W$ & \code{w\_base} & Neuron-major: i pesi del neurone $n$ a \code{w\_base + n*N\_INPUTS} byte. \\ Bias & \code{bias\_addr} & Un byte per neurone: bias del neurone $n$ a \code{bias\_addr + n}. \\ \rowa Tabella descrittori & \code{table\_base} & \code{N\_LAYERS} voci da 11 byte (cap.~\ref{ch:seq}). \\ Buffer ping-pong A/B & \code{buf\_a\_base} / \code{buf\_b\_base} & Uscite intermedie tra layer. \\ \bottomrule \end{tabularx} \subsection{Indirizzamento fisico della PSRAM} La PSRAM raccomandata è 4M$\times$16 (8~MB), che richiede un indirizzo di parola a 22~bit (A0--A21). \code{int8\_memory\_access} calcola \code{addr>>1} portando l'indirizzo di byte a 23~bit in un indirizzo di parola a 22~bit che mappa esattamente su A0--A21; il bit~22 di \code{psram\_a} è quindi sempre 0 e sul PCB restano 22 linee di indirizzo reali. \section{Larghezza di banda} \label{sec:bandwidth} Misurata sul gather della lista di edge di \code{graph\_engine} (cap.~\ref{ch:grafo}), per differenza tra due dimensioni di grafo per isolare il costo per-edge dall'overhead fisso per-neurone (\code{sim/graph\_engine\_bandwidth\_tb.v}): \begin{tabularx}{\textwidth}{L{5.2cm} Y Y Y} \toprule \rowh \thd{} & \thd{Prima (no page mode)} & \thd{Dopo (page mode)} & \thd{$\Delta$} \\ \midrule Cicli/edge & 53.25 & 37.53 & $-29.5\%$ \\ \rowa Banda @80\,MHz & 6.01\,MB/s & 8.53\,MB/s & $+41.9\%$ \\ Banda @16\,MHz\textsuperscript{*} & 1.20\,MB/s & 1.71\,MB/s & $+41.9\%$ \\ \bottomrule \end{tabularx} \textsuperscript{*}oscillatore reale raccomandato (cap.~\ref{ch:hw}). Il modello resta comunque memory-bound per costruzione: \code{neuron\_memory} legge $X$ una volta e rilegge $W$/bias per ciascun neurone (cap.~\ref{ch:seq}), un neurone per volta; il page mode riduce il costo per-byte dell'accesso sequenziale, non elimina il pattern di accesso stesso.