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 :
e
vaut '1'
, la valeur de x
est copiée dans y
.e
vaut '0'
, y
conserve la dernière valeur qui lui a été affectée.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 :
when-else
) sans
clause else
finale.with-select
) dans
laquelle certains cas ont été omis, et sans clause when others
.if
sans clause else
finale.if
ou case
dans laquelle un signal n’est pas affecté dans
toutes les branches.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.
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.
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 :
clk_i
.x
.y
.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.
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.
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.
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 :
reset_i
figure dans la liste de sensibilité du processus.
Le processus peut donc réagir aux variations de reset_i
sans attendre un front
de clk_i
.reset_i = '1'
est vérifiée en premier. Elle est prioritaire sur
la détection du front d’horloge.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 :
reset_i
ne figure pas dans la liste de sensibilité du processus.reset_i = '1'
est vérifiée après la détection du front d’horloge.À 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 :
std_logic
valent 'U'
(Uninitialized).'0'
.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 :
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;
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.
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 :
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;
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 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;
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 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 :
u
sur front d’horloge également :p_yu : process(clk_i)
begin
if rising_edge(clk_i) then
y <= x;
u <= w;
end if;
end process p_yu;
u
séparément, dans une instruction d’affectation concurrente
ou un autre processus :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.