Bascules et registres

Mémorisation

En VHDL, les signaux sont sujet à un effet mémoire, c’est-à-dire qu’ils conservent leur valeur entre deux affectations successives. Considérons par exemple l’instruction suivante :

y <= x when e = '1';

Cette instruction aura le comportement suivant :

Ce comportement est également valable dans un processus avec l’instruction if :

p_y : process(x, e)
begin
    if e = '1' then
        y <= x;
    end if;
end process p_y;

Les deux exemples ci-dessus décrivent un circuit de mémorisation appelé verrou (ou latch en anglais). Dans un circuit synchrone, l’utilisation de verrous est interdite. On impose l’utilisation de bascules D (ou D flip-flop) qui mettent à jour leur valeur sur des fronts d’un signal d’horloge.

L’effet mémoire apparaît dans les situations suivantes :

Dans le code que vous allez écrire, l’effet mémoire sera parfois intentionnel, et parfois le résultat d’un oubli ou d’une erreur de conception. Si vous souhaitez décrire une fonction logique combinatoire, vérifiez que les signaux concernés reçoivent bien une valeur dans tous les cas possibles. Si vous souhaitez décrire une mémorisation, vérifiez que vous respectez les recommandations données ci-après.

Bascules et registres simples

Pour décrire une bascule ou un registre, il suffit de reprendre les exemples ci-dessus et de remplacer la condition e = '1' par une expression qui vaut true si un front d’horloge vient de se produire et false dans le cas contraire.

Il existe différentes manières de décrire la détection d’un front d’horloge en VHDL. Dans cette page, nous nous limitons aux écritures qui seront reconnues sans ambiguïté par les outils de synthèse.

Avec l’attribut event

Pour un signal s, l’expression s'event vaut true si s vient juste de changer de valeur, et false si s est resté stable dans le dernier cycle delta. On utilise typiquement l’expression s'event and s='1' pour vérifier si un front montant vient de se produire sur s.

Pour mettre à jour un signal y sur les fronts montants d’un signal clk_i, on pourra donc écrire :

p_y : process(clk_i)
begin
    if clk_i'event and clk_i='1' then
        y <= x;
    end if;
end process p_y;

Ce processus décrit une bascule ou un registre avec les connexions suivantes :

Bascule D simple

Si x et y transportent des données qui peuvent être représentées sur un seul bit, le circuit sera constitué d’une seule bascule. Si plusieurs bits sont nécessaires, les outils de synthèse sont capables de construire un circuit contenant le nombre approprié de bascules.

Observons que seul le signal clk_i figure dans la liste de sensibilité du processus. En effet, une bascule D ne réagit pas aux variations de son entrée D. Seuls les fronts d’horloge produisent un effet.

Avec la fonction rising_edge

Pour un signal s de type std_logic, l’expression s'event and s='1' n’est pas fiable pour détecter un front montant. En effet, le type std_logic définit d’autres valeurs que '0' et '1'. Ainsi, lorsque la condition s'event and s='1' est vraie, cela ne signifie pas forcément que s valait '0' avant de passer à '1'.

Pour le type std_logic, il existe une fonction prédéfinie appelée rising_edge qui permet de traiter les cas problématiques. On l’utilise de la manière suivante :

p_y : process(clk_i)
begin
    if rising_edge(clk_i) then
        y <= x;
    end if;
end process p_y;

Matériellement, ce processus décrit le même circuit que l’exemple précédent.

Réinitialisation

Les bascules D sont souvent équipées d’entrées permettant de forcer leur valeur à '0'ou à '1' sans passer par l’entrée D. Ce forçage peut avoir lieu au démarrage ou lorsqu’il faut réinitialiser le circuit au cours de son fonctionnement.

Réinitialisation asynchrone

Une réinitialisation asynchrone prend effet immédiatement, sans attendre un front du signal d’horloge. Dans l’exemple ci-dessous, le signal reset_i déclenche la remise à '0' du signal y :

p_y : process(clk_i, reset_i)
begin
    if reset_i = '1' then
        y <= '0';
    elsif rising_edge(clk_i) then
        y <= x;
    end if;
end process p_y;

La réinitialisation asynchrone est caractérisée par les deux constructions suivantes :

Bascule D avec réinitialisation

Réinitialisation synchrone

Une réinitialisation synchrone prend effet si la commande de réinitialisation est valide sur un front du signal d’horloge. Dans l’exemple ci-dessous, le signal reset_i déclenche la remise à '0' du signal y :

p_y : process(clk_i)
begin
    if rising_edge(clk_i) then
        if reset_i = '1' then
            y <= '0';
        else
            y <= x;
        end if;
    end if;
end process p_y;

La réinitialisation synchrone est caractérisée par les deux constructions suivantes :

Réinitialisation dans les FPGA

À la mise sous tension, la séquence de démarrage d’un FPGA comprend souvent une étape d’initialisation des éléments de mémorisation, à la fin du chargement du bitstream. Les valeurs initiales des signaux, telles qu’elles sont indiquées dans le code source VHDL, font partie des données enregistrées dans le bitstream.

Sauf indication contraire, au démarrage, les signaux prennent la valeur la plus à gauche dans la définition de leur type. Les signaux de type std_logic se comporteront différemment dans le simulateur et sur le matériel :

Voici quelques exemples :

subtype temperature_t is integer range -50 to 150;
type color_enum_t     is (BLACK, RED, GREEN, BLUE, YELLOW, MAGENTA, CYAN, WHITE);

                             -- Valeur initiale:
signal a : std_logic;        -- 'U' en simulation, '0' dans le FPGA
signal b : std_logic := '1'; -- '1'
signal c : integer;          -- −2 147 483 648
signal d : integer := 12;    -- 12
signal e : temperature_t;    -- -50
signal f : color_enum_t;     -- BLACK

Donner une valeur initiale à un signal combinatoire n’a pas de sens d’un point de vue matériel. Seuls les signaux à mémoire, c’est-à-dire ceux correspondant à des sorties de bascules ou de registres peuvent avoir une valeur initiale.

Les valeurs initiales des autres signaux sont ignorées par les outils de synthèse.

Dans le document Get Smart About Reset: Think Local, Not Global (Ken Chapman, Xilinx, 2008), l’auteur donne des arguments contre l’utilisation d’un signal de reset global. Les principales raisons invoquées sont :

Autorisation de mise à jour

Les bascules D sont souvent équipées d’une entrée E (enable) qui permet d’autoriser ou d’interdire la mise à jour sur certains fronts d’horloge.

On écrira typiquement :

p_y : process(clk_i)
begin
    if rising_edge(clk_i) then
        if e = '1' then
            y <= x;
        end if;
    end if;
end process p_y;

Bascule D avec autorisation

Il est possible de combiner la présence d’une commande de réinitialisation avec la présence d’une autorisation de mise à jour :

p_y : process(clk_i, reset_i)
begin
    if reset_i = '1' then
        y <= '0';
    elsif rising_edge(clk_i) then
        if e = '1' then
            y <= x;
        end if;
    end if;
end process p_y;

L’attribut event et la fonction rising_edge ne doivent jamais être utilisés pour autre chose que pour créer des bascules D.

De plus, dans une description VHDL respectant les principes de la conception synchrone, tous les processus qui contiennent une détection de front doivent utiliser le même signal d’horloge.

Exemples à ne pas suivre

Pour les outils de synthèse, les exemples donnés dans les paragraphes précédents seront reconnus sans ambiguïté comme la description de bascules et de registres D. Si vous respectez les mêmes constructions dans vos propres descriptions, vous obtiendrez des circuits conformes à ce que vous aurez décrit.

Les exemples suivants, au contraire, risquent de faire échouer la synthèse de vos circuits :

Un même processus contient plusieurs détections de fronts

p_y : process(clk_i)
begin
    if rising_edge(clk_i) and e = '1' then
        y <= w;
    elsif rising_edge(clk_i) and f = '1' then
        y <= x;
    end if;
end process p_y;

Réécrire ce processus de la manière suivante :

p_y : process(clk_i)
begin
    if rising_edge(clk_i) then
        if e = '1' then
            y <= w;
        elsif f = '1' then
            y <= x;
        end if;
    end if;
end process p_y;

Une détection de front est imbriquée dans une autre instruction if, case ou une boucle for

Dans ce processus, la détection de front n’est pas prioritaire sur l’entrée E.

p_y : process(clk_i, e)
begin
    if e = '1' then
        if rising_edge(clk_i) then
            y <= x;
        end if;
    end if;
end process p_y;

Réécrire ce processus de la manière suivante :

p_y : process(clk_i)
begin
    if rising_edge(clk_i) then
        if e = '1' then
            y <= x;
        end if;
    end if;
end process p_y;

La condition de réinitialisation n’est pas un simple signal logique

La commande de réinitialisation devrait être un simple signal logique dédié à cet usage et non pas le résultat d’un calcul.

p_y : process(clk_i, reset_i, clear)
begin
    if reset_i = '1' or clear = '1' then
        y <= '0';
    elsif rising_edge(clk_i) then
        y <= x;
    end if;
end process p_y;

Dans ce cas, utiliser une réinitialisation synchrone.

p_y : process(clk_i)
begin
    if rising_edge(clk_i) then
        if reset_i = '1' or clear = '1' then
            y <= '0';
        else
            y <= x;
        end if;
    end if;
end process p_y;

Vous pouvez également utiliser les deux types de réinitialisation dans le même processus :

p_y : process(clk_i, reset_i)
begin
    if reset_i = '1' then
        y <= '0';
    elsif rising_edge(clk_i) then
        if clear = '1' then
            y <= '0';
        else
            y <= x;
        end if;
    end if;
end process p_y;

La valeur initiale d’un signal n’est pas une constante

Considérons l’exemple ci-dessous où w est un signal. La réinitialisation ne peut pas utiliser les entrées set et reset des bascules puisque la valeur de w n’est pas connue à l’avance.

p_y : process(clk_i, reset_i)
begin
    if reset_i = '1' then
        y <= w;
    elsif rising_edge(clk_i) then
        y <= x;
    end if;
end process p_y;

Dans ce cas, utiliser une réinitialisation synchrone :

p_y : process(clk_i)
begin
    if rising_edge(clk_i) then
        if reset_i = '1' then
            y <= w;
        else
            y <= x;
        end if;
    end if;
end process p_y;

Dans un même processus, certaines affectations sont synchronisées et d’autres non

Dans cet exemple, l’affectation de u n’est pas soumise à la détection d’un front de clk_i. Le type de circuit qui doit gérer le signal u n’est pas clairement identifié.

p_yu : process(clk_i)
begin
    if rising_edge(clk_i) then
        y <= x;
    end if;
    u <= w;
end process p_yu;

Deux solutions sont envisageables :

p_yu : process(clk_i)
begin
    if rising_edge(clk_i) then
        y <= x;
        u <= w;
    end if;
end process p_yu;
p_y : process(clk_i)
begin
    if rising_edge(clk_i) then
        y <= x;
    end if;
end process p_y;

u <= w;

C’est à vous de déterminer laquelle de ces deux solutions est la plus pertinente.