55 / 63 · Concetto
Memoria sincrona e contratto delle collisioni
Definire risposta registrata, read-first/write-first/no-change e responsabilità distinte di valid e inizializzazione.
Le lezioni sono gratuite. Iscriviti per salvare i progressi.
Prima dell'indirizzo viene l'istante della risposta
Un contratto di memoria specifica capacità, latenza e collisioni. Costruiamo una memoria con 16 locazioni da otto bit, capace di accettare una lettura e una scrittura a ogni fronte. Condividono il clock ma hanno indirizzi indipendenti. Capacità: 16×8=128 bit; indirizzo: bit.
Con indirizzo e rd_en stabili prima del fronte, dopo appaiono rd_data e rd_valid registrati: uno stadio di lettura. Un altro always_ff sullo stesso fronte vede ancora il vecchio valore; il registroRegistro Memorizza uno stato di più bit. I registri sincroni del corso acquisiscono gli ingressi specificati al fronte di clock. Approfondisci consumatore riceve la risposta al fronte seguente. Distinguete richiesta e osservazione. Si può accettare una nuova lettura ogni clock: uno stadio di latenza non dimezza il throughputThroughput Numero di operazioni accettate o completate per unità di tempo. Una pipeline può aumentarlo senza ridurre la latenza del singolo lavoro. Approfondisci.
Leggere e scrivere lo stesso indirizzo
Prima del fronte M[3]=8'h25; si legge 3 mentre vi si scrive 8'h9A. Read-first risponde 25 e memorizza 9A; write-first risponde 9A. No-change mantiene il registro d'uscita, che non è necessariamente il precedente contenuto di 3. Sono tre contratti validi distinti, descritti anche nei modi di sincronizzazione RAM AMD.
Qui usiamo read-first. è l'array prima del fronte, dopo. Senza reset, per ogni indirizzo :
Anche in collisione si legge , non . Senza richiesta rd_data mantiene il valore.
module sync_mem16x8 (
input logic clk, rst,
input logic wr_en, rd_en,
input logic [3:0] wr_addr, rd_addr,
input logic [7:0] wr_data,
output logic [7:0] rd_data,
output logic rd_valid
);
timeunit 1ns;
timeprecision 1ps;
logic [7:0] mem [0:15];
always_ff @(posedge clk) begin
if (rst) begin
rd_valid <= 1'b0;
end else begin
rd_valid <= rd_en;
if (wr_en)
mem[wr_addr] <= wr_data;
if (rd_en)
rd_data <= mem[rd_addr];
end
end
endmoduleEntrambi i membri destri usano valori vecchi; gli aggiornamenti non bloccantiNonblocking Assegnazione RTL sequenziale con <=. Valuta il membro destro e pianifica l’aggiornamento, permettendo ai registri dello stesso fronte di usare lo stato precedente. Approfondisci seguono dopo. Mettere la scrittura prima nel codice non crea write-first. Per farlo si può usare (wr_en && wr_addr==rd_addr) ? wr_data : mem[rd_addr]. Il bypass conserva comunque il registro d'uscita.
Valid e inizializzazione sono promesse diverse
rst è sincrono active-high. Con rst=1 si ignorano entrambe le richieste e si azzera solo rd_valid; array e rd_data non vengono cancellati. Dopo accensione, indirizzi mai scritti non sono definiti: possono apparire X in simulazione e non si può presumere un numero nell'hardware.
rd_valid=1 significa «ciclo di risposta», non «indirizzo inizializzato e dato noto». Leggete solo indirizzi già scritti o mantenete una tabella d'inizializzazione. Anche una lettura read-first insieme alla prima scrittura restituisce il contenuto precedente sconosciuto. Inoltre serve ignorare rd_data con valid=0. Il controllo d’inizializzazione e il controllo di valid (valid guard) non si sostituiscono a vicenda.
Reset non cancella il contenuto già scritto nell'RTL. Se il sistema decide di scartarlo, deve gestire anche la tabella esterna d'inizializzazione. Cancellare dati sensibili è un requisito distinto dal reset di controllo.
Ogni colonna registra un fronte di salitaFronte di salita Passaggio del clock da 0 a 1, distinto dall’intero intervallo di livello CLK=1. Approfondisci. Gli ingressi sono quelli immediatamente precedenti; gli stati indicati con «after» sono quelli subito dopo l’aggiornamento. Le colonne indicano l’ordine dei campioni, non il ritardo fisico di propagazioneRitardo di propagazione Tempo dalla variazione d’ingresso all’assestamento corretto dell’uscita. Equivalenza logica e proprietà temporali sono distinte. Approfondisci. Tutti gli indirizzi sono 3. E0 scrive per la prima volta 25; E1 sovrascrive con 9A e legge. Il trattino in data indica un valore indeterminato; valid=0 non conta come risposta. I dati sono esadecimali.
Mostra i dati della forma d’onda
| Segnale | Forma d’onda | Valori del bus |
|---|---|---|
| edge | 2345 | E0 → E1 → E2 → E3 |
| wr_en | 1.0. | |
| wr_data | 23.. | 25 → 9A |
| rd_en | 01.0 | |
| M[3] after | 23.. | 25 → 9A |
| rd_data after | 234. | - → 25 → 9A |
| rd_valid after | 01.0 |
Seguire quattro fronti a mano
E0 scrive 25 a 3 con rd_en=0. E1 scrive 9A e legge 3: dopo vale valid=1, data=25. E2 legge senza scrittura e restituisce 9A. E3 con rd_en=0 porta valid=0 e mantiene data=9A. Contare il bus a E3 come nuova risposta elabora due volte lo stesso dato.
Indirizzi diversi sono indipendenti: leggere 7 scrivendo 3 restituisce il vecchio contenuto di 7. La risorsa RAM FPGA inferita e le sue collisioni vanno verificate nei rapporti di sintesi e nella documentazione del dispositivo. Definire la semantica RTL e verificare una realizzazione concreta sono fasi distinte.
Prova tu
M[6]=8'h10. E0 legge 6 e scrive 8'h20; E1 legge senza scrivere; E2 ha rd_en=0. Calcolate data/valid read-first. Cosa cambia con write-first? La prima collisione read-first su indirizzo non inizializzato dà un valore definito?
Leggi la spiegazione
E0: data=10, valid=1, array aggiornato a 20. E1: data=20, valid=1. E2 mantiene data=20 con valid=0: nessuna risposta. Write-first cambia solo E0 in data=20. La prima collisione read-first restituisce il vecchio contenuto non inizializzato, senza atteso numerico definito. valid=1 non garantisce inizializzazione.