Leçon 18 sur 22

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.1Organisation d’une mémoireDimensionner et interfacer
18.2ROMDécrire des données constantes
18.3RAMLire et écrire des données
18.4Mémoires internes du FPGAContrôler l’inférence
18.5ApplicationsExploiter les mémoires dans un système
TP 14ROM et RAMSimuler 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 bitDrapeaux ou bitmap
8 bitsOctets, caractères, pixels simples
16 bitsÉchantillons, instructions compactes
32 bitsDonnées de processeur ou registres
64 bitsDonné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

416
8256
101 024
124 096
1665 536

 

18.1.3 Capacité

Formule générale  |  Calcul
CAPACITE_BITS = NOMBRE_DE_MOTS
                * BITS_PAR_MOT

 

Organisation

Capacité

256 × 82 048 bits = 256 octets
1 024 × 1616 384 bits = 2 Kio
4 096 × 32131 072 bits = 16 Kio
512 × 1512 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 RTLSortie mise à jour au front actif
Latence logique sans cycle completLatence d’un cycle typique
Peut utiliser LUT ou mémoire distribuéeFavorise souvent Block RAM
Chemin adresse-vers-donnée combinatoireSortie 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 mots4 bits
100 mots7 bits
256 mots8 bits
1 000 mots10 bits
4 096 mots12 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

clkSynchronise les opérations
weAutorise l’écriture
enActive le port mémoire
adresseSélectionne le mot
data_inDonnée à écrire
data_outDonnée lue
byte_enableAutorise 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èsTemps entre l’adresse valide et la donnée disponible
LatenceNombre de cycles entre demande et réponse
DébitNombre d’opérations possibles par cycle
PortInterface 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 positionnelValeurs listées dans l’ordre
Association nomméeAdresse => valeur
othersValeur par défaut des positions restantes
Fichier d’initialisationDépend du fabricant et de l’outil
Fonction d’initialisationCalcule 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 ROMSouvent Block RAM configurée en ROM
Accès combinatoire possibleSortie enregistrée fréquente
Très faible profondeurCapacité plus importante
Consomme la logique distribuéeUtilise 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 dataAncienne valeur du mot
Write-first / new dataNouvelle donnée écrite
No-changeLa 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

00 ou 1Aucune opération
10Lecture
11É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 RAMGrande capacité efficaceNombre de blocs fini, lecture souvent synchrone
Distributed RAMSouple et accès combinatoireConsomme des LUT
RegistresContrôle maximal, petits tableauxSurface é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 BRAMLa mémoire utilise-t-elle les blocs dédiés ?
LUT RAMUne partie est-elle distribuée ?
RegistresLa mémoire a-t-elle été décomposée ?
Largeur/profondeurL’organisation correspond-elle au code ?
PortsSimple ou double port reconnu ?
AvertissementsUn 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 asynchroneDistributed RAM au lieu de BRAM
Reset de tous les motsRegistres au lieu de mémoire
Deux écritures incompatiblesÉchec ou duplication
Dimensions faiblesLUT préférées
Mode de collision particulierLogique supplémentaire
Attribut non compatibleAvertissement 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 bits256 × 12
Buffer de 1 024 octets1 024 × 8
Table de 64 coefficients 16 bits64 × 16
Image monochrome 128 × 648 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ôleIndice hors plage en simulationTester ROM'high
Lecture combinatoire pour une grande RAMBRAM non inféréeLecture synchrone
Reset de toute la RAMRegistres et forte surfaceRéinitialiser pointeurs/sorties
Collision double port ignoréeRésultat imprévisibleDéfinir un protocole
Mauvaise largeur d’adresseAdresses inaccessibles ou invalidesCalculer ceil(log2(profondeur))
Mode write-first supposéÉcart simulation/matérielCoder 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

clkFronts des opérations
enableActivation du port
weLecture ou écriture
adresseMot sélectionné
data_inValeur écrite
data_outValeur lue après le front

 

12. Tableau de résultats

Bloc

Test

Attendu

Observé

Conforme

ROMAdresses 0 à 1516 valeurs correctes  
RAMÉcriture adresse 0x12 stocké  
RAMÉcriture adresse 255xFF stocké  
RAMLecture synchroneDonnée après le front  
RAMRead-during-writeAncienne donnée  
SynthèseRessource mémoireBlock 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 × 83
Test exhaustif de la ROM2
RAM 256 × 84
Lecture et écriture synchrones3
Banc de test automatisé3
Analyse de l’inférence mémoire2
Validation sur FPGA2
Qualité du compte rendu1
Total20

 

Synthèse du chapitre

Notion

Résumé

MotUnité de donnée stockée.
AdresseIndice du mot sélectionné.
ProfondeurNombre de mots.
LargeurNombre de bits par mot.
ROMMémoire constante pendant le fonctionnement.
RAMMémoire accessible en lecture et écriture.
Lecture synchroneDonnée mise à jour au front d’horloge.
Write enableAutorisation d’écriture.
Simple portUne interface d’accès.
Double portDeux accès possibles dans le même cycle.
Block RAMMémoire dédiée de grande capacité.
Distributed RAMMé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.