ROM, RAM et FIFO
Décrire des mémoires que les outils peuvent reconnaître et gérer correctement les limites d'une FIFO.
Une ROM peut être décrite avec un case
Une petite table constante se décrit simplement avec un bloc combinatoire. Toutes les adresses doivent produire une valeur.
module opcode_rom (
input wire [2:0] i_address,
output reg [7:0] o_data
);
always @* begin
case (i_address)
3'd0: o_data = 8'h13;
3'd1: o_data = 8'h27;
3'd2: o_data = 8'h42;
3'd3: o_data = 8'h81;
3'd4: o_data = 8'hA5;
default: o_data = 8'h00;
endcase
end
endmoduleSelon sa taille et le FPGA ciblé, l'outil peut réaliser cette table avec de la logique distribuée ou une mémoire interne.
Décrire une RAM synchrone
Une mémoire est un tableau de mots. Le style du process détermine souvent le type de ressource qui peut être inféré.
module synchronous_ram #(
parameter WIDTH = 8,
parameter ADDRESS_WIDTH = 6
) (
input wire i_clk,
input wire i_write_enable,
input wire [ADDRESS_WIDTH-1:0] i_address,
input wire [WIDTH-1:0] i_write_data,
output reg [WIDTH-1:0] o_read_data
);
localparam DEPTH = 1 << ADDRESS_WIDTH;
reg [WIDTH-1:0] memory [0:DEPTH-1];
always @(
La lecture est enregistrée. L'adresse présentée avant un front donne sa donnée après ce front. Le résultat d'une lecture et d'une écriture simultanées à la même adresse dépend du style de code, de l'outil et du mode de la mémoire ciblée. Ce cas doit être défini dans le cahier des charges puis vérifié après synthèse.
Réinitialiser chaque mot avec une boucle peut empêcher l'utilisation d'une RAM bloc sur certains FPGA. Si les données n'ont pas besoin d'une valeur de reset, il vaut mieux réinitialiser seulement les registres de contrôle.
Ce que stocke une FIFO
Une FIFO synchrone contient :
- une mémoire ;
- un pointeur d'écriture ;
- un pointeur de lecture ;
- un compteur ou une autre méthode pour distinguer vide et plein.
Les indicateurs doivent refléter les transferts réellement acceptés, pas seulement les demandes reçues.
module synchronous_fifo #(
parameter WIDTH = 8,
parameter ADDRESS_WIDTH = 3
) (
input wire i_clk,
input wire i_reset,
input wire i_write,
input wire [WIDTH-1:0] i_write_data,
input wire i_read,
output reg [WIDTH-1:0] o_read_data,
output reg o_read_valid,
output wire o_empty,
output wire o_full,
output wire [ADDRESS_WIDTH:0] o_level
);
localparam DEPTH
Cette version utilise une profondeur qui est une puissance de deux. Les pointeurs rebouclent naturellement. Elle refuse une écriture lorsque la FIFO est déjà pleine et une lecture lorsqu'elle est vide. Une lecture et une écriture acceptées pendant le même cycle ne changent pas count.
Les choix d'interface doivent être écrits
Il existe plusieurs comportements valables : sortie directe sur le premier mot, sortie enregistrée après une demande, acceptation d'une écriture pendant une lecture lorsque la FIFO est pleine, ou passage direct d'une donnée lorsque la FIFO est vide.
Aucun de ces choix n'est universel. Le producteur, le consommateur et le testbench doivent partager la même règle.
Une FIFO asynchrone est un autre circuit
Ajouter une deuxième horloge à cette FIFO ne suffit pas. Une FIFO asynchrone demande des pointeurs adaptés au passage de domaine, souvent codés en Gray, des synchroniseurs et un calcul prudent des états plein et vide. Elle doit être conçue et vérifiée comme une architecture différente.
À retenir
- Le style RTL influence l'inférence de ROM ou de RAM.
- Une lecture synchrone ajoute une latence d'horloge.
- Le comportement lecture pendant écriture doit être connu.
- Une FIFO avance uniquement lors des transferts acceptés.
- Une FIFO asynchrone ne se déduit pas d'une FIFO synchrone en ajoutant une horloge.
📝 Tester mes connaissances - Quiz du chapitre