Chapitre 18 — Mémoires en VHDL
Objectif — Finalité du chapitre Décrire des mémoires ROM et RAM en VHDL, maîtriser les lectures et écritures synchrones, comprendre les mémoires internes des FPGA et vérifier leur inférence dans les rapports de synthèse. |
Objectifs pédagogiques
À la fin de ce chapitre, l’étudiant devra être capable de :
- définir un mot mémoire, une adresse, une capacité et un bus ;
- calculer la capacité d’une mémoire à partir de sa profondeur et de sa largeur ;
- décrire une ROM avec un tableau constant ;
- réaliser une lecture combinatoire ou synchrone ;
- décrire une RAM simple port avec écriture synchrone ;
- décrire une RAM double port ;
- distinguer read-first, write-first et no-change ;
- favoriser l’inférence d’une Block RAM ou d’une Distributed RAM ;
- lire un rapport de synthèse concernant les ressources mémoire ;
- concevoir une table de sinus, une mémoire de programme ou un tampon ;
- vérifier une ROM et une RAM avec un banc de test automatisé.
Prérequis
- tableaux VHDL et types personnalisés ;
- unsigned et conversions avec numeric_std ;
- processus combinatoires et cadencés ;
- synthèse logique et lecture des rapports ;
- bancs de test et assertions.
Organisation du chapitre
Section | Contenu | Compétence principale |
|---|---|---|
| 18.1 | Organisation d’une mémoire | Dimensionner et interfacer |
| 18.2 | ROM | Décrire des données constantes |
| 18.3 | RAM | Lire et écrire des données |
| 18.4 | Mémoires internes du FPGA | Contrôler l’inférence |
| 18.5 | Applications | Exploiter les mémoires dans un système |
| TP 14 | ROM et RAM | Simuler puis implanter |
Concept clé — Deux dimensions essentielles Une mémoire est caractérisée par sa profondeur, nombre de mots, et sa largeur, nombre de bits par mot. Sa capacité totale vaut profondeur × largeur. |
18.1 Organisation d’une mémoire
Une mémoire stocke un ensemble de mots binaires. Chaque mot est sélectionné par une adresse et transféré par un bus de données.
18.1.1 Mot mémoire
Un mot est l’unité de données lue ou écrite en une opération. Sa largeur peut être de 1, 8, 16, 32 bits ou davantage.
Largeur du mot | Exemple d’utilisation |
|---|---|
| 1 bit | Drapeaux ou bitmap |
| 8 bits | Octets, caractères, pixels simples |
| 16 bits | Échantillons, instructions compactes |
| 32 bits | Données de processeur ou registres |
| 64 bits | Données larges ou calcul intensif |
18.1.2 Adresse
L’adresse indique le numéro du mot ciblé. Avec A bits d’adresse, une mémoire peut sélectionner jusqu’à 2^A mots.
Bits d’adresse | Nombre maximal de mots |
|---|---|
| 4 | 16 |
| 8 | 256 |
| 10 | 1 024 |
| 12 | 4 096 |
| 16 | 65 536 |
18.1.3 Capacité
| Formule générale | Calcul |
| CAPACITE_BITS = NOMBRE_DE_MOTS * BITS_PAR_MOT |
Organisation | Capacité |
|---|---|
| 256 × 8 | 2 048 bits = 256 octets |
| 1 024 × 16 | 16 384 bits = 2 Kio |
| 4 096 × 32 | 131 072 bits = 16 Kio |
| 512 × 1 | 512 bits = 64 octets |
Unité — Kio et Ko Dans ce chapitre, 1 Kio représente 1 024 octets. Les fabricants peuvent exprimer certaines capacités en bits ou en blocs. |
18.1.4 Lecture
La lecture sélectionne un mot et le place sur le bus de sortie. Elle peut être combinatoire ou enregistrée par l’horloge.
Lecture combinatoire | Lecture synchrone |
|---|---|
| Sortie dépend immédiatement de l’adresse en RTL | Sortie mise à jour au front actif |
| Latence logique sans cycle complet | Latence d’un cycle typique |
| Peut utiliser LUT ou mémoire distribuée | Favorise souvent Block RAM |
| Chemin adresse-vers-donnée combinatoire | Sortie enregistrée et timing mieux maîtrisé |
18.1.5 Écriture
L’écriture modifie le mot à l’adresse sélectionnée. Dans les conceptions FPGA, elle est généralement synchrone.
| Écriture synchrone conceptuelle | VHDL |
| if rising_edge(clk) then if we = '1' then memoire(adresse) <= data_in; end if; end if; |
18.1.6 Bus d’adresses
Le bus d’adresses doit être assez large pour représenter la dernière adresse.
Profondeur | Largeur minimale du bus |
|---|---|
| 16 mots | 4 bits |
| 100 mots | 7 bits |
| 256 mots | 8 bits |
| 1 000 mots | 10 bits |
| 4 096 mots | 12 bits |
18.1.7 Bus de données
Le bus de données possède normalement la largeur d’un mot. Une RAM peut avoir des bus d’entrée et de sortie séparés ou un bus bidirectionnel à l’interface externe.
18.1.8 Signaux de contrôle
Signal | Fonction |
|---|---|
| clk | Synchronise les opérations |
| we | Autorise l’écriture |
| en | Active le port mémoire |
| adresse | Sélectionne le mot |
| data_in | Donnée à écrire |
| data_out | Donnée lue |
| byte_enable | Autorise certains octets d’un mot |
18.1.9 Organisation sous forme de tableau
| Type mémoire | VHDL |
| type memoire_t is array (0 to 255) of std_logic_vector(7 downto 0); signal memoire : memoire_t; |
Le premier intervalle définit les adresses ; le type des éléments définit la largeur des mots.
18.1.10 Adresse std_logic_vector ou unsigned
| Conversion vers un indice entier | VHDL |
| indice <= to_integer(unsigned(adresse)); data_out <= memoire( to_integer(unsigned(adresse)) ); |
L’indice d’un tableau est souvent un integer ou natural. numeric_std permet la conversion.
18.1.11 Temps d’accès et latence
Notion | Définition |
|---|---|
| Temps d’accès | Temps entre l’adresse valide et la donnée disponible |
| Latence | Nombre de cycles entre demande et réponse |
| Débit | Nombre d’opérations possibles par cycle |
| Port | Interface indépendante de lecture/écriture |
18.1.12 Alignement et adresses en octets
Une mémoire interne VHDL peut être adressée par mot. Dans un processeur, les adresses sont souvent exprimées en octets et les bits faibles sélectionnent l’octet du mot.
Interface — Spécification Toujours préciser si l’adresse représente un mot, un octet ou un autre élément. Une ambiguïté sur l’unité d’adressage provoque des erreurs d’intégration. |
18.2 ROM
Une ROM contient des valeurs constantes pendant le fonctionnement normal. Son contenu peut être défini dans le code ou chargé lors de la configuration du FPGA.
18.2.1 Déclaration d’un tableau constant
| ROM 16 mots × 8 bits | VHDL |
| type rom_t is array (0 to 15) of std_logic_vector(7 downto 0); constant ROM : rom_t := ( 0 => x"12", 1 => x"34", 2 => x"56", 3 => x"78", 4 => x"9A", 5 => x"BC", 6 => x"DE", 7 => x"F0", others => x"00" ); |
18.2.2 Lecture combinatoire
| Lecture immédiate | VHDL |
| data_out <= ROM( to_integer(unsigned(adresse)) ); |
La sortie suit l’adresse après la propagation logique du modèle. Selon la taille et la cible, la ROM peut utiliser des LUT ou une mémoire interne.
18.2.3 ROM complète combinatoire
| rom16x8.vhd — Entité | VHDL |
| library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; entity rom16x8 is port ( adresse : in std_logic_vector(3 downto 0); data_out : out std_logic_vector(7 downto 0) ); end entity rom16x8; |
| rom16x8.vhd — Architecture | VHDL |
| architecture rtl of rom16x8 is type rom_t is array (0 to 15) of std_logic_vector(7 downto 0); constant ROM : rom_t := ( x"3C", x"42", x"A5", x"5A", x"00", x"11", x"22", x"33", x"44", x"55", x"66", x"77", x"88", x"99", x"AA", x"FF" ); begin data_out <= ROM( to_integer(unsigned(adresse)) ); end architecture rtl; |
18.2.4 Lecture synchrone
| ROM enregistrée | VHDL |
| process(clk) begin if rising_edge(clk) then if enable = '1' then data_out <= ROM( to_integer(unsigned(adresse)) ); end if; end if; end process; |
La donnée correspondant à l’adresse est disponible après le front d’horloge. Cette forme favorise souvent une Block RAM synchrone.
18.2.5 Initialisation des données
Méthode | Caractéristique |
|---|---|
| Agrégat positionnel | Valeurs listées dans l’ordre |
| Association nommée | Adresse => valeur |
| others | Valeur par défaut des positions restantes |
| Fichier d’initialisation | Dépend du fabricant et de l’outil |
| Fonction d’initialisation | Calcule un tableau constant à l’élaboration |
18.2.6 Association positionnelle
| Contenu ordonné | VHDL |
| constant ROM : rom_t := ( x"10", -- adresse 0 x"20", -- adresse 1 x"30", -- adresse 2 x"40" -- adresse 3 ); |
18.2.7 Association nommée
| Adresses explicites | VHDL |
| constant ROM : rom_t := ( 0 => x"10", 3 => x"40", 7 => x"80", others => x"00" ); |
18.2.8 Table de correspondance avec case
| Décodeur sous forme de ROM logique | VHDL |
| process(all) begin case adresse is when "0000" => data_out <= x"12"; when "0001" => data_out <= x"34"; when "0010" => data_out <= x"56"; when "0011" => data_out <= x"78"; when others => data_out <= x"00"; end case; end process; |
Pour une petite table, case et tableau constant peuvent conduire à un réseau similaire. Le tableau est souvent plus compact et réutilisable.
18.2.9 Fonction d’initialisation
| Création algorithmique d’une table | VHDL |
| function initialiser_rom return rom_t is variable temp : rom_t; begin for i in temp'range loop temp(i) := std_logic_vector( to_unsigned(i * 3, 8) ); end loop; return temp; end function; constant ROM : rom_t := initialiser_rom; |
La fonction est évaluée à l’élaboration. Elle ne crée pas une boucle séquentielle dans le matériel.
18.2.10 Valeur hors plage
Si le bus d’adresse peut représenter plus d’adresses que le tableau, l’indice doit être contrôlé pour éviter une erreur de simulation.
| Protection | VHDL |
| process(all) variable indice : natural; begin indice := to_integer(unsigned(adresse)); if indice <= ROM'high then data_out <= ROM(indice); else data_out <= (others => '0'); end if; end process; |
18.2.11 ROM et reset
Le contenu de la ROM ne nécessite pas de reset. Seul un registre de sortie éventuel peut être réinitialisé.
18.2.12 ROM distribuée ou Block ROM
ROM petite/combinatoire | ROM grande/synchrone |
|---|---|
| Souvent LUT ou Distributed ROM | Souvent Block RAM configurée en ROM |
| Accès combinatoire possible | Sortie enregistrée fréquente |
| Très faible profondeur | Capacité plus importante |
| Consomme la logique distribuée | Utilise les blocs mémoire dédiés |
18.3 RAM
Une RAM permet la lecture et l’écriture pendant le fonctionnement. Sa description doit suivre un motif reconnu pour obtenir la ressource mémoire souhaitée.
18.3.1 RAM simple port
Une RAM simple port possède une seule adresse active à un instant donné. Le port peut lire ou écrire selon le signal we.
| Interface simple port | VHDL |
| entity ram_simple_port is generic ( LARGEUR : positive := 8; PROFONDEUR: positive := 256; ADR_BITS : positive := 8 ); port ( clk : in std_logic; enable : in std_logic; we : in std_logic; adresse : in unsigned(ADR_BITS-1 downto 0); data_in : in std_logic_vector(LARGEUR-1 downto 0); data_out : out std_logic_vector(LARGEUR-1 downto 0) ); end entity ram_simple_port; |
18.3.2 Écriture synchrone
| Écriture au front | VHDL |
| if rising_edge(clk) then if enable = '1' and we = '1' then ram(to_integer(adresse)) <= data_in; end if; end if; |
18.3.3 Lecture asynchrone
| RAM avec sortie combinatoire | VHDL |
| process(clk) begin if rising_edge(clk) then if enable = '1' and we = '1' then ram(to_integer(adresse)) <= data_in; end if; end if; end process; data_out <= ram(to_integer(adresse)); |
Cette forme peut inférer de la mémoire distribuée ou des registres. Elle n’est pas toujours compatible avec la lecture synchrone des Block RAM.
18.3.4 Lecture synchrone
| RAM synchrone simple port | VHDL |
| process(clk) begin if rising_edge(clk) then if enable = '1' then if we = '1' then ram(to_integer(adresse)) <= data_in; end if; data_out <= ram( to_integer(adresse) ); end if; end if; end process; |
L’adresse est échantillonnée au front et la sortie est mise à jour selon le comportement de lecture pendant écriture reconnu par l’outil.
18.3.5 RAM simple port complète
| Architecture générique — Déclarations et validation | VHDL |
| architecture rtl of ram_simple_port is type ram_t is array (0 to PROFONDEUR-1) of std_logic_vector(LARGEUR-1 downto 0); signal ram : ram_t; begin assert (2 ** ADR_BITS) >= PROFONDEUR report "ADR_BITS insuffisant" severity failure; |
| Architecture générique — Processus mémoire | VHDL |
| process(clk) begin if rising_edge(clk) then if enable = '1' then if we = '1' then ram(to_integer(adresse)) <= data_in; end if; data_out <= ram(to_integer(adresse)); end if; end if; end process; end architecture rtl; |
18.3.6 Lecture pendant écriture
Lorsque lecture et écriture ciblent la même adresse au même front, trois comportements courants existent.
Mode | Valeur observée en sortie |
|---|---|
| Read-first / old data | Ancienne valeur du mot |
| Write-first / new data | Nouvelle donnée écrite |
| No-change | La sortie conserve sa valeur précédente |
Important — Comportement dépendant de la cible Le mode exact doit être défini par le code, les primitives ou les options de l’outil. Les collisions non spécifiées peuvent produire des différences entre simulation et matériel. |
18.3.7 Modèle write-first
| Nouvelle donnée visible | VHDL |
| process(clk) begin if rising_edge(clk) then if enable = '1' then if we = '1' then ram(to_integer(adresse)) <= data_in; data_out <= data_in; else data_out <= ram( to_integer(adresse) ); end if; end if; end if; end process; |
18.3.8 Modèle no-change
| Sortie conservée pendant l’écriture | VHDL |
| process(clk) begin if rising_edge(clk) then if enable = '1' then if we = '1' then ram(to_integer(adresse)) <= data_in; else data_out <= ram( to_integer(adresse) ); end if; end if; end if; end process; |
18.3.9 Signal d’activation d’écriture
enable | we | Opération |
|---|---|---|
| 0 | 0 ou 1 | Aucune opération |
| 1 | 0 | Lecture |
| 1 | 1 | Écriture et comportement de sortie défini par le mode |
18.3.10 RAM double port
Une RAM double port permet deux accès dans le même cycle. Elle peut être simple dual-port, un port d’écriture et un port de lecture, ou true dual-port, les deux ports pouvant lire et écrire.
18.3.11 Simple dual-port
| Un port d’écriture et un port de lecture | VHDL |
| process(clk) begin if rising_edge(clk) then if we_a = '1' then ram(to_integer(adresse_a)) <= data_a; end if; data_b <= ram( to_integer(adresse_b) ); end if; end process; |
18.3.12 True dual-port, horloge commune
| Deux ports lecture/écriture | VHDL |
| process(clk) begin if rising_edge(clk) then if we_a = '1' then ram(to_integer(adresse_a)) <= data_in_a; end if; data_out_a <= ram(to_integer(adresse_a)); if we_b = '1' then ram(to_integer(adresse_b)) <= data_in_b; end if; data_out_b <= ram(to_integer(adresse_b)); end if; end process; |
Double port — Collision Si les deux ports écrivent la même adresse au même instant, le résultat peut être indéfini ou spécifique au composant. Le protocole doit empêcher ce cas, sauf si la primitive le documente explicitement. |
18.3.13 Deux horloges indépendantes
Certaines Block RAM acceptent une horloge par port. L’inférence correcte est fortement dépendante du synthétiseur. Pour les cas complexes, une IP mémoire du fabricant est souvent préférable.
18.3.14 Byte enable
| Écriture par octets d’un mot 32 bits | VHDL |
| if rising_edge(clk) then if we = '1' then for i in 0 to 3 loop if byte_en(i) = '1' then ram(adresse)( 8*i+7 downto 8*i ) <= data_in( 8*i+7 downto 8*i ); end if; end loop; end if; end if; |
18.3.15 Initialisation d’une RAM
Une RAM interne peut parfois être initialisée lors de la configuration du FPGA. Le support dépend de la famille, du type de mémoire et de l’outil.
| Valeur initiale par agrégat | VHDL |
| signal ram : ram_t := ( 0 => x"12", 1 => x"34", others => x"00" ); |
Cette initialisation ne remplace pas nécessairement une remise à zéro dynamique du contenu, qui serait coûteuse ou non supportée directement par une Block RAM.
18.3.16 Reset d’une mémoire
Synthèse — Ne pas réinitialiser tous les mots naïvement Un reset parcourant toute la RAM dans une boucle peut empêcher l’inférence de Block RAM et créer un grand réseau de registres. Réinitialiser plutôt les pointeurs, les compteurs et le registre de sortie lorsque possible. |
18.4 Inférence des mémoires internes du FPGA
Les FPGA comportent des mémoires distribuées dans les LUT et des blocs mémoire dédiés de capacité plus importante.
18.4.1 Block RAM
- blocs mémoire dédiés ;
- capacités typiquement de plusieurs kilobits ;
- ports simples ou doubles ;
- lectures souvent synchrones ;
- modes de largeur/profondeur configurables ;
- possibilité d’initialisation à la configuration.
18.4.2 Distributed RAM
- mémoire réalisée dans les LUT ;
- adaptée aux petites capacités ;
- lecture combinatoire souvent possible ;
- proche de la logique consommatrice ;
- consomme des ressources logiques.
18.4.3 Registres
Une petite mémoire ou une description non reconnue peut devenir un ensemble de bascules et de multiplexeurs.
Implémentation | Avantage | Limite |
|---|---|---|
| Block RAM | Grande capacité efficace | Nombre de blocs fini, lecture souvent synchrone |
| Distributed RAM | Souple et accès combinatoire | Consomme des LUT |
| Registres | Contrôle maximal, petits tableaux | Surface élevée pour grandes mémoires |
18.4.4 Conditions d’inférence
- motif VHDL reconnu par le synthétiseur ;
- dimensions constantes à l’élaboration ;
- types et indices compatibles ;
- lecture/écriture correspondant aux ports matériels ;
- absence de reset global de tous les mots ;
- comportement de collision compatible ;
- taille suffisante pour justifier une Block RAM.
18.4.5 Lecture synchrone et Block RAM
Une sortie mise à jour dans le processus cadencé correspond généralement mieux à la structure interne d’une Block RAM qu’une lecture combinatoire.
18.4.6 Taille et seuils
Le choix automatique dépend de la taille, de la famille FPGA, des autres ressources utilisées et de la stratégie de synthèse. Il n’existe pas un seuil universel.
18.4.7 Attribut de style mémoire
| Exemple couramment utilisé avec Vivado | VHDL |
| attribute ram_style : string; attribute ram_style of ram : signal is "block"; |
| Demande de mémoire distribuée | VHDL |
| attribute ram_style : string; attribute ram_style of ram : signal is "distributed"; |
Ces attributs sont spécifiques à l’outil et peuvent être ignorés si le motif n’est pas compatible avec la ressource demandée.
18.4.8 Attribut Intel/Quartus conceptuel
| Exemple de style | VHDL |
| attribute ramstyle : string; attribute ramstyle of ram : signal is "M10K"; |
Méthode — Portabilité Conserver une version RTL générique et isoler les attributs propres au fabricant dans une zone clairement documentée. |
18.4.9 Rapport de synthèse
Information | Question à poser |
|---|---|
| Nombre de BRAM | La mémoire utilise-t-elle les blocs dédiés ? |
| LUT RAM | Une partie est-elle distribuée ? |
| Registres | La mémoire a-t-elle été décomposée ? |
| Largeur/profondeur | L’organisation correspond-elle au code ? |
| Ports | Simple ou double port reconnu ? |
| Avertissements | Un motif ou mode est-il non supporté ? |
18.4.10 Schéma RTL
- identifier le bloc mémoire ;
- vérifier les adresses et bus de données ;
- repérer les multiplexeurs ajoutés ;
- contrôler les registres de sortie ;
- vérifier le nombre de ports.
18.4.11 Raisons d’une inférence inattendue
Cause | Effet possible |
|---|---|
| Lecture asynchrone | Distributed RAM au lieu de BRAM |
| Reset de tous les mots | Registres au lieu de mémoire |
| Deux écritures incompatibles | Échec ou duplication |
| Dimensions faibles | LUT préférées |
| Mode de collision particulier | Logique supplémentaire |
| Attribut non compatible | Avertissement ou attribut ignoré |
18.4.12 Optimisation de la mémoire
- choisir la largeur et la profondeur adéquates ;
- regrouper plusieurs petites tables lorsque pertinent ;
- utiliser la lecture synchrone pour BRAM ;
- éviter les ports inutiles ;
- définir clairement le mode read-during-write ;
- vérifier le taux d’occupation des blocs.
18.4.13 Mémoire et timing
Les sorties enregistrées améliorent souvent la fermeture temporelle. Une grande logique d’adressage ou de multiplexage autour de la mémoire peut néanmoins devenir critique.
18.5 Applications
18.5.1 Stockage de données
Une RAM peut stocker des échantillons, des résultats intermédiaires, des paquets ou des paramètres modifiables.
Application | Organisation possible |
|---|---|
| 256 échantillons 12 bits | 256 × 12 |
| Buffer de 1 024 octets | 1 024 × 8 |
| Table de 64 coefficients 16 bits | 64 × 16 |
| Image monochrome 128 × 64 | 8 192 × 1 ou mots regroupés |
18.5.2 Mémoire de programme
Un processeur ou automate microprogrammé peut lire ses instructions dans une ROM.
| Instruction à l’adresse PC | VHDL |
| instruction <= PROGRAMME( to_integer(unsigned(pc)) ); |
18.5.3 Table de sinus
Une table de correspondance transforme une phase discrète en amplitude. Elle évite de calculer le sinus en temps réel.
| ROM de sinus simplifiée | VHDL |
| type sinus_t is array (0 to 15) of signed(7 downto 0); constant SINUS : sinus_t := ( 0, 49, 90, 117, 127, 117, 90, 49, 0, -49, -90,-117, -127,-117, -90, -49 ); amplitude <= SINUS( to_integer(unsigned(phase)) ); |
Les valeurs doivent être converties au type signed attendu dans une version compilable complète, par exemple avec to_signed.
18.5.4 Version typée de la table de sinus
| Initialisation avec to_signed | VHDL |
| constant SINUS : sinus_t := ( to_signed(0, 8), to_signed(49, 8), to_signed(90, 8), to_signed(117, 8), to_signed(127, 8), others => to_signed(0, 8) ); |
18.5.5 Génération de caractères
Une ROM de police stocke les lignes de pixels de chaque caractère.
| Adresse d’une ligne de glyphe | VHDL |
| adresse_rom <= code_caractere & numero_ligne; pixels <= FONT_ROM( to_integer(unsigned(adresse_rom)) ); |
18.5.6 Exemple de caractère 8×8
| Motifs du caractère A | VHDL |
| constant A_GLYPH : glyph_t := ( "00011000", "00100100", "01000010", "01111110", "01000010", "01000010", "01000010", "00000000" ); |
18.5.7 Tampon de données
Un buffer stocke temporairement des données lorsque le producteur et le consommateur ne travaillent pas exactement au même rythme.
- buffer circulaire ;
- FIFO synchrone ;
- FIFO asynchrone entre deux horloges ;
- double buffering pour image ou audio ;
- mémoire de paquets.
18.5.8 Buffer circulaire
| Écriture avec pointeur | VHDL |
| if rising_edge(clk) then if write_en = '1' then ram(to_integer(write_ptr)) <= data_in; write_ptr <= write_ptr + 1; end if; end if; |
18.5.9 Double buffering
Deux zones mémoire alternent les rôles : l’une est remplie pendant que l’autre est lue. Cette technique évite qu’un affichage lise une image en cours de modification.
18.5.10 Table de coefficients
Les filtres numériques utilisent souvent une ROM ou une RAM pour stocker les coefficients. Une RAM permet leur mise à jour pendant le fonctionnement.
18.5.11 Mémoire vidéo
Le balayage vidéo lit continuellement la mémoire, tandis qu’un autre bloc peut la modifier. Une RAM double port est adaptée à ce partage.
18.5.12 Journal d’événements
Une RAM peut enregistrer des mesures ou événements successifs avec un compteur d’adresse. La lecture est réalisée après acquisition.
Méthode de conception d’une mémoire
1. Définir la profondeur et la largeur.
2. Calculer la largeur du bus d’adresse.
3. Choisir ROM ou RAM.
4. Déterminer le nombre de ports.
5. Choisir lecture combinatoire ou synchrone.
6. Définir le comportement pendant l’écriture.
7. Choisir les signaux enable et write enable.
8. Écrire un motif compatible avec la ressource cible.
9. Simuler les adresses limites et collisions.
10. Examiner le rapport de synthèse et le schéma RTL.
Checklist d’inférence
- Les dimensions sont-elles constantes à l’élaboration ?
- L’indice reste-t-il dans la plage du tableau ?
- La lecture est-elle compatible avec la mémoire visée ?
- Le reset modifie-t-il inutilement tous les mots ?
- Le mode read-during-write est-il spécifié ?
- Les deux ports peuvent-ils entrer en collision ?
- Le rapport confirme-t-il BRAM ou Distributed RAM ?
- Les attributs spécifiques sont-ils nécessaires et documentés ?
Erreurs fréquentes
Erreur | Conséquence | Correction |
|---|---|---|
| Adresse trop large sans contrôle | Indice hors plage en simulation | Tester ROM'high |
| Lecture combinatoire pour une grande RAM | BRAM non inférée | Lecture synchrone |
| Reset de toute la RAM | Registres et forte surface | Réinitialiser pointeurs/sorties |
| Collision double port ignorée | Résultat imprévisible | Définir un protocole |
| Mauvaise largeur d’adresse | Adresses inaccessibles ou invalides | Calculer ceil(log2(profondeur)) |
| Mode write-first supposé | Écart simulation/matériel | Coder explicitement |
| Attribut fabricant non vérifié | Style ignoré | Lire le rapport |
Travaux pratiques
TP 14 — ROM et RAM
1. Objectifs du TP
- créer une ROM 16 × 8 ;
- créer une RAM simple port 256 × 8 ;
- réaliser des lectures et écritures synchrones ;
- vérifier automatiquement les données ;
- analyser les modes de lecture pendant écriture ;
- contrôler l’inférence dans les blocs mémoire du FPGA.
2. Organisation du projet
| Arborescence proposée | Arborescence |
| tp14_memoires/ ├── src/ │ ├── rom16x8.vhd │ ├── ram256x8.vhd │ └── top_fpga.vhd ├── sim/ │ ├── tb_rom16x8.vhd │ └── tb_ram256x8.vhd └── constraints/ └── carte.xdc_ou_qsf |
3. Partie A — ROM 16 × 8
| rom16x8.vhd — Entité | VHDL |
| library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; entity rom16x8 is port ( adresse : in unsigned(3 downto 0); data_out : out std_logic_vector(7 downto 0) ); end entity rom16x8; |
| rom16x8.vhd — Architecture | VHDL |
| architecture rtl of rom16x8 is type rom_t is array (0 to 15) of std_logic_vector(7 downto 0); constant ROM : rom_t := ( x"10", x"21", x"32", x"43", x"54", x"65", x"76", x"87", x"98", x"A9", x"BA", x"CB", x"DC", x"ED", x"FE", x"0F" ); begin data_out <= ROM(to_integer(adresse)); end architecture rtl; |
4. Banc de test exhaustif de la ROM
| tb_rom16x8.vhd — Déclarations | VHDL |
| library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; use STD.ENV.ALL; entity tb_rom16x8 is end entity tb_rom16x8; architecture simulation of tb_rom16x8 is type rom_ref_t is array (0 to 15) of std_logic_vector(7 downto 0); constant ATTENDU : rom_ref_t := ( x"10", x"21", x"32", x"43", x"54", x"65", x"76", x"87", x"98", x"A9", x"BA", x"CB", x"DC", x"ED", x"FE", x"0F" ); signal adresse : unsigned(3 downto 0) := (others => '0'); signal data_out : std_logic_vector(7 downto 0); begin DUT : entity work.rom16x8(rtl) port map ( adresse => adresse, data_out => data_out ); |
| tb_rom16x8.vhd — Vérification | VHDL |
| stimuli : process begin for i in 0 to 15 loop adresse <= to_unsigned(i, adresse'length); wait for 1 ns; assert data_out = ATTENDU(i) report "ROM incorrecte a l'adresse " & integer'image(i) severity error; end loop; report "ROM : 16 adresses valides" severity note; stop; wait; end process; end architecture simulation; |
5. Partie B — RAM 256 × 8
| ram256x8.vhd — Entité | VHDL |
| library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; entity ram256x8 is port ( clk : in std_logic; enable : in std_logic; we : in std_logic; adresse : in unsigned(7 downto 0); data_in : in std_logic_vector(7 downto 0); data_out : out std_logic_vector(7 downto 0) ); end entity ram256x8; |
| ram256x8.vhd — Architecture read-first | VHDL |
| architecture rtl of ram256x8 is type ram_t is array (0 to 255) of std_logic_vector(7 downto 0); signal ram : ram_t; attribute ram_style : string; attribute ram_style of ram : signal is "block"; begin process(clk) begin if rising_edge(clk) then if enable = '1' then if we = '1' then ram(to_integer(adresse)) <= data_in; end if; data_out <= ram( to_integer(adresse) ); end if; end if; end process; end architecture rtl; |
6. Banc de test de la RAM
| tb_ram256x8.vhd — Déclarations | VHDL |
| library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; use STD.ENV.ALL; entity tb_ram256x8 is end entity tb_ram256x8; architecture simulation of tb_ram256x8 is constant PERIODE : time := 20 ns; signal clk : std_logic := '0'; signal enable : std_logic := '0'; signal we : std_logic := '0'; signal adresse : unsigned(7 downto 0) := (others => '0'); signal data_in : std_logic_vector(7 downto 0) := (others => '0'); signal data_out : std_logic_vector(7 downto 0); begin clk <= not clk after PERIODE / 2; DUT : entity work.ram256x8(rtl) port map ( clk => clk, enable => enable, we => we, adresse => adresse, data_in => data_in, data_out => data_out ); |
| tb_ram256x8.vhd — Procédure d’écriture | VHDL |
| stimuli : process procedure ecrire( constant adr : natural; constant valeur : natural ) is begin adresse <= to_unsigned(adr, adresse'length); data_in <= std_logic_vector( to_unsigned(valeur, data_in'length) ); enable <= '1'; we <= '1'; wait until rising_edge(clk); wait for 1 ns; end procedure; |
| tb_ram256x8.vhd — Procédure de lecture | VHDL |
| procedure lire_et_verifier( constant adr : natural; constant attendu : natural ) is begin adresse <= to_unsigned(adr, adresse'length); enable <= '1'; we <= '0'; wait until rising_edge(clk); wait for 1 ns; assert unsigned(data_out) = to_unsigned(attendu, 8) report "RAM incorrecte a l'adresse " & integer'image(adr) severity error; end procedure; |
| tb_ram256x8.vhd — Scénario | VHDL |
| begin -- Écriture de plusieurs mots. ecrire(0, 16#12#); ecrire(1, 16#34#); ecrire(15, 16#A5#); ecrire(255, 16#FF#); -- Lecture et vérification. lire_et_verifier(0, 16#12#); lire_et_verifier(1, 16#34#); lire_et_verifier(15, 16#A5#); lire_et_verifier(255, 16#FF#); enable <= '0'; wait until rising_edge(clk); report "RAM : lectures et ecritures valides" severity note; stop; wait; end process; end architecture simulation; |
7. Test read-during-write
Ajouter un scénario écrivant une nouvelle valeur à une adresse déjà utilisée et observer data_out au même front.
| Scénario de collision simple port | VHDL |
| adresse <= to_unsigned(15, 8); data_in <= x"5A"; enable <= '1'; we <= '1'; wait until rising_edge(clk); wait for 1 ns; -- Avec le modèle read-first, data_out contient -- normalement l'ancienne valeur x"A5". assert data_out = x"A5" report "Mode read-first non observe" severity warning; |
8. Implantation sur FPGA
- synthétiser la ROM puis la RAM comme entités supérieures ;
- ouvrir le rapport d’utilisation ;
- rechercher BRAM, Block Memory ou M10K selon la cible ;
- vérifier que la RAM 256 × 8 n’est pas réalisée uniquement en bascules ;
- ouvrir le schéma RTL ;
- comparer avec et sans attribut ram_style.
9. Niveau supérieur de démonstration
| top_fpga.vhd — Exemple conceptuel | VHDL |
| -- Les interrupteurs choisissent l'adresse. -- Un bouton déclenche une écriture. -- Les LED affichent data_out. U_RAM : entity work.ram256x8(rtl) port map ( clk => clk_carte, enable => '1', we => impulsion_ecriture, adresse => unsigned(interrupteurs), data_in => valeur_a_ecrire, data_out => leds ); |
Intégration — Bouton L’impulsion d’écriture doit provenir d’un bouton synchronisé, filtré et détecté sur front, conformément au chapitre 17. |
10. Commandes GHDL
| Compilation | Terminal |
| ghdl -a --std=08 src/rom16x8.vhd ghdl -a --std=08 src/ram256x8.vhd ghdl -a --std=08 sim/tb_rom16x8.vhd ghdl -a --std=08 sim/tb_ram256x8.vhd |
| Simulation de la ROM | Terminal |
| ghdl -e --std=08 tb_rom16x8 ghdl -r --std=08 tb_rom16x8 |
| Simulation de la RAM | Terminal |
| ghdl -e --std=08 tb_ram256x8 ghdl -r --std=08 tb_ram256x8 --vcd=tb_ram256x8.vcd gtkwave tb_ram256x8.vcd |
11. Signaux à observer
Signal | Observation |
|---|---|
| clk | Fronts des opérations |
| enable | Activation du port |
| we | Lecture ou écriture |
| adresse | Mot sélectionné |
| data_in | Valeur écrite |
| data_out | Valeur lue après le front |
12. Tableau de résultats
Bloc | Test | Attendu | Observé | Conforme |
|---|---|---|---|---|
| ROM | Adresses 0 à 15 | 16 valeurs correctes | ||
| RAM | Écriture adresse 0 | x12 stocké | ||
| RAM | Écriture adresse 255 | xFF stocké | ||
| RAM | Lecture synchrone | Donnée après le front | ||
| RAM | Read-during-write | Ancienne donnée | ||
| Synthèse | Ressource mémoire | Block RAM souhaitée |
13. Questions d’analyse
1. Quelle différence existe entre profondeur et largeur ?
2. Combien de bits d’adresse faut-il pour 256 mots ?
3. Pourquoi une ROM est-elle déclarée comme constante ?
4. Quelle latence ajoute une lecture synchrone ?
5. Quel est le rôle du signal we ?
6. Quelle différence existe entre read-first et write-first ?
7. Pourquoi éviter de réinitialiser tous les mots d’une Block RAM ?
8. Pourquoi une lecture combinatoire peut-elle empêcher l’inférence de BRAM ?
9. Quel risque existe lorsque deux ports écrivent la même adresse ?
10. Quelles informations rechercher dans le rapport de synthèse ?
14. Extensions proposées
- Créer une ROM synchrone.
- Créer une RAM write-first.
- Ajouter un signal byte enable.
- Créer une RAM simple dual-port.
- Créer une table de sinus sur 256 valeurs.
- Stocker une police de caractères 8 × 8.
- Construire un buffer circulaire.
- Comparer Distributed RAM et Block RAM.
15. Barème indicatif
Critère | Points |
|---|---|
| ROM 16 × 8 | 3 |
| Test exhaustif de la ROM | 2 |
| RAM 256 × 8 | 4 |
| Lecture et écriture synchrones | 3 |
| Banc de test automatisé | 3 |
| Analyse de l’inférence mémoire | 2 |
| Validation sur FPGA | 2 |
| Qualité du compte rendu | 1 |
| Total | 20 |
Synthèse du chapitre
Notion | Résumé |
|---|---|
| Mot | Unité de donnée stockée. |
| Adresse | Indice du mot sélectionné. |
| Profondeur | Nombre de mots. |
| Largeur | Nombre de bits par mot. |
| ROM | Mémoire constante pendant le fonctionnement. |
| RAM | Mémoire accessible en lecture et écriture. |
| Lecture synchrone | Donnée mise à jour au front d’horloge. |
| Write enable | Autorisation d’écriture. |
| Simple port | Une interface d’accès. |
| Double port | Deux accès possibles dans le même cycle. |
| Block RAM | Mémoire dédiée de grande capacité. |
| Distributed RAM | Mémoire réalisée dans les LUT. |
Autoévaluation
1. Comment calculer la capacité d’une mémoire ?
Réponse : Profondeur × largeur.
2. Combien de mots adresse un bus de 8 bits ?
Réponse : 256 mots.
3. Quel objet VHDL convient à une ROM ?
Réponse : Un tableau constant.
4. Quand une lecture synchrone produit-elle la donnée ?
Réponse : Après un front actif.
5. Quel signal autorise l’écriture ?
Réponse : we ou write enable.
6. Que signifie read-first ?
Réponse : La sortie présente l’ancienne donnée pendant l’écriture.
7. Quel type de mémoire utilise les LUT ?
Réponse : Distributed RAM.
8. Quel type utilise des blocs dédiés ?
Réponse : Block RAM.
9. Pourquoi éviter un reset global de la RAM ?
Réponse : Il peut empêcher l’inférence de Block RAM.
10. Comment confirmer l’inférence ?
Réponse : Avec le rapport de synthèse et le schéma RTL.
Exercice de consolidation
Concevoir une ROM de 32 mots de 12 bits et déterminer la largeur du bus d’adresse. La sortie doit être synchrone et activée par enable.
| Correction proposée | VHDL |
| type rom_t is array (0 to 31) of std_logic_vector(11 downto 0); constant ROM : rom_t := ( 0 => x"123", 1 => x"456", 2 => x"789", others => x"000" ); process(clk) begin if rising_edge(clk) then if enable = '1' then data_out <= ROM( to_integer(unsigned(adresse)) ); end if; end if; end process; -- 32 mots nécessitent 5 bits d'adresse. |
Suite du cours — Transition vers la suite Après les mémoires, le cours peut aborder les unités arithmétiques, la représentation en virgule fixe, les opérateurs optimisés et la conception d’une UAL. |