Dans sa version la plus simple, un compteur est un circuit qui incrémente une valeur entière à chaque front montant d’un signal d’horloge. Un compteur est l’association de deux éléments :
L’exemple ci-dessous décrit un compteur modulo dix :
architecture Behavioral of SomeEntity is
signal cnt_reg, cnt_next : integer range 0 to 9;
begin
p_cnt_reg : process(clk_i)
begin
if rising_edge(clk_i) then
cnt_reg <= cnt_next;
end if;
end process p_cnt_reg;
cnt_next <= 0 when cnt_reg = 9 else cnt_reg + 1;
...
end Behavioral;
Dans cette architecture,
cnt_reg
mémorise la valeur courante du compteur ;p_cnt_reg
gère la mise à jour de cnt_reg
sur les fronts montants de clk_i
;cnt_next
reçoit la valeur suivante du compteur dans une instruction
d’affectation conditionnelle.Le processus sera synthétisé comme un registre. L’instruction d’affectation concurrente sera synthétisée comme une fonction logique combinatoire composée d’un comparateur, d’un additionneur et d’un multiplexeur.
Le signal cnt_next
peut être omis en regroupant dans le processus p_cnt_reg
le calcul de la valeur suivante et la mise à jour du registre.
Le circuit correspondant est rigoureusement le même :
architecture Behavioral of SomeEntity is
signal cnt_reg : integer range 0 to 9;
begin
p_cnt_reg : process(clk_i)
begin
if rising_edge(clk_i) then
if cnt_reg = 9 then
cnt_reg <= 0;
else
cnt_reg <= cnt_reg + 1;
end if;
end if;
end process p_cnt_reg;
...
end Behavioral;
Un compteur peut être utilisé comme un séquenceur pour commander des actions répétitives. La commande de feux tricolores est un exemple typique. Imaginons une intersection où les feux doivent respecter la séquence suivante :
Cette séquence peut être réalisée avec un compteur modulo 10 comme illustré par le tableau ci-dessous :
Compteur | Voie A | Voie B |
---|---|---|
0 | Vert | Rouge |
1 | Vert | Rouge |
2 | Vert | Rouge |
3 | Vert | Rouge |
4 | Orange | Rouge |
5 | Rouge | Vert |
6 | Rouge | Vert |
7 | Rouge | Vert |
8 | Rouge | Vert |
9 | Rouge | Orange |
Dans la description VHDL suivante, la commande des feux de chaque voie est
représentée par un vecteur de trois bits qui commandent respectivement les
feux vert, orange et rouge.
Pour améliorer la lisibilité, nous avons déclaré trois constantes
RED
, ORANGE
et GREEN
avec les trois valeurs possibles de ce vecteur.
entity TrafficLights is
port(
clk_i : in std_logic;
lights_a_o, lights_b_o : out std_logic_vector(2 downto 0)
);
end TrafficLights;
architecture Sequencer of TrafficLights is
signal cnt_reg : integer range 0 to 9;
constant RED : std_logic_vector(2 downto 0) := "001";
constant ORANGE : std_logic_vector(2 downto 0) := "010";
constant GREEN : std_logic_vector(2 downto 0) := "100";
begin
p_cnt_reg : process(clk_i)
begin
if rising_edge(clk_i) then
if cnt_reg = 9 then
cnt_reg <= 0;
else
cnt_reg <= cnt_reg + 1;
end if;
end if;
end process p_cnt_reg;
with cnt_reg select
lights_a_o <= GREEN when 0 | 1 | 2 | 3,
ORANGE when 4,
RED when others;
with cnt_reg select
lights_b_o <= GREEN when 5 | 6 | 7 | 8,
ORANGE when 9,
RED when others;
end Sequencer;
Son fonctionnement est illustré par ce chronogramme :
On pouvait aussi écrire :
lights_a_o <= GREEN when cnt_reg < 4 else
ORANGE when cnt_reg < 5 else
RED;
lights_b_o <= RED when cnt_reg < 5 else
GREEN when cnt_reg < 9 else
ORANGE when others;
Cela permet de proposer une version générique de ce séquenceur pour l’adapter à différents scénarios d’utilisation et à différentes fréquences d’horloge :
entity TrafficLights is
generic(
RED_TIME : positive;
ORANGE_TIME : positive;
GREEN_TIME : positive
);
port(
clk_i : in std_logic;
lights_a_o, lights_b_o : out std_logic_vector(2 downto 0)
);
end TrafficLights;
architecture Sequencer of TrafficLights is
constant CNT_MAX : positive := RED_TIME + ORANGE_TIME + GREEN_TIME - 1
signal cnt_reg : integer range 0 to CNT_MAX;
constant RED : std_logic_vector(2 downto 0) := "001";
constant ORANGE : std_logic_vector(2 downto 0) := "010";
constant GREEN : std_logic_vector(2 downto 0) := "100";
begin
p_cnt_reg : process(clk_i)
begin
if rising_edge(clk_i) then
if cnt_reg = CNT_MAX then
cnt_reg <= 0;
else
cnt_reg <= cnt_reg + 1;
end if;
end if;
end process p_cnt_reg;
lights_a_o <= GREEN when cnt_reg < GREEN_TIME else
ORANGE when cnt_reg < GREEN_TIME + ORANGE_TIME else
RED;
lights_b_o <= RED when cnt_reg < RED_TIME else
GREEN when cnt_reg < RED_TIME + GREEN_TIME else
ORANGE;
end Sequencer;
Par exemple, si clk_i
a une fréquence de 100 MHz, une utilisation réaliste
de cette entité pourrait être :
lights_inst : entity work.TrafficLights
generic map(
RED_TIME => 3_000_000_000, -- 30 secondes
ORANGE_TIME => 300_000_000, -- 3 secondes
GREEN_TIME => 2_700_000_000 -- 27 secondes
)
port map(
clk_i => ...,
lights_a_o => ...,
lights_b_o => ...
);
Un diviseur de fréquence est un circuit capable de produire un signal périodique dont la fréquence est un sous-multiple de la fréquence d’un signal d’entrée. Diviser la fréquence par revient à multiplier par la durée d’une période. Autrement dit, une période du signal de sortie aura une durée égale à périodes du signal d’entrée.
Le cas le plus simple est le diviseur de fréquence par deux, dans lequel un signal logique s’inverse à chaque front montant de l’horloge :
signal clk_div2_reg : std_logic := '0';
...
p_clk_div2_reg : process(clk_i)
begin
if rising_edge(clk_i) then
clk_div2_reg <= not clk_div2_reg;
end if;
end process p_clk_div2_reg;
Dans l’exemple ci-dessus, en l’absence d’un signal de reset,
pensez à indiquer une valeur initiale dans la déclaration de clk_div2_reg
.
En cas d’oubli, votre diviseur de fréquence fonctionnera sur un FPGA
mais pas dans un simulateur.
En simulation, clk_div2_reg
vaudra 'U'
(Uninitialized) au démarrage de la simulation.
Pour , un diviseur de fréquence par peut être réalisé avec un compteur modulo qui compte le nombre de périodes du signal d’entrée. C’est la technique que nous avons utilisée pour réaliser un chronomètre.
Dans l’exemple ci-dessous, nous avons utilisé un compteur timer_reg
modulo 10.
Les signaux half
et cycle
sont à '1'
pendant une période d’horloge
à chaque fois que timer_reg
atteint la moitié ou la fin de son cycle de comptage.
Ces signaux sont utilisés pour déclencher l’inversion du signal clk_div10_reg
:
signal timer_reg : integer range 0 to 9;
signal half, cycle : std_logic;
signal clk_div10_reg : std_logic := '0';
...
p_timer_reg : process(clk_i)
begin
if rising_edge(clk_i) then
if cycle = '1' then
timer_reg <= 0;
else
timer_reg <= timer_reg + 1;
end if;
end if;
end process p_timer_reg;
half <= '1' when timer_reg = 4 else '0';
cycle <= '1' when timer_reg = 9 else '0';
p_clk_div10_reg : process(clk_i)
begin
if rising_edge(clk_i) then
if half = '1' or cycle = '1' then
clk_div10_reg <= not clk_div10_reg;
end if;
end if;
end process p_clk_div10_reg;
Le résultat est représenté sur ce chronogramme :
Lorsque est pair, il est possible d’obtenir le même comportement avec un compteur modulo . Cela permet d’économiser un comparateur et une bascule D.
Exercice : proposez une implémentation d’un diviseur de fréquence par 10 utilisant un compteur modulo 5.
Imaginons à présent que notre application doive produire deux signaux d’horloge :
clk_div10
avec une fréquence 10 fois plus petite que celle de clk_i
.clk_div60
avec une fréquence 60 fois plus petite que celle de clk_i
.Une solution immédiate consiste à réaliser deux diviseurs de fréquence indépendants,
un diviseur par 10 et un diviseur par 60, en utilisant la méthode décrite plus haut.
Une autre solution consiste à observer que , autrement dit,
la sortie clk_div60
peut être obtenue en appliquant une division de fréquence par 6
au signal clk_div10
.
Dans l’exemple ci-dessous, nous avons repris la structure du diviseur de fréquence
par 10, représenté par les signaux timer_div10_reg
, half_div10
, cycle_div10
et clk_div10_reg
:
signal timer_div10_reg : integer range 0 to 9;
signal half_div10, cycle_div10 : std_logic;
signal clk_div10_reg : std_logic := '0';
...
p_timer_div10_reg : process(clk_i)
begin
if rising_edge(clk_i) then
if cycle_div10 = '1' then
timer_div10_reg <= 0;
else
timer_div10_reg <= timer_div10_reg + 1;
end if;
end if;
end process p_timer_div10_reg;
half_div10 <= '1' when timer_div10_reg = 4 else '0';
cycle_div10 <= '1' when timer_div10_reg = 9 else '0';
p_clk_div10_reg : process(clk_i)
begin
if rising_edge(clk_i) then
if half_div10 = '1' or cycle_div10 = '1' then
clk_div10_reg <= not clk_div10_reg;
end if;
end if;
end process p_clk_div10_reg;
Le diviseur de fréquence par 6 suit la même structure, avec les signaux
timer_div60_reg
, half_div60
, cycle_div60
et clk_div60_reg
.
La mise en cascade des deux diviseurs est réalisée en utilisant le signal
cycle_div10
comme condition pour les opérations suivantes :
timer_div60_reg
.'1'
des signaux half_div60
et cycle_div60
.signal timer_div60_reg : integer range 0 to 5;
signal half_div60, cycle_div60 : std_logic;
signal clk_div60_reg : std_logic := '0';
...
p_timer_div60_reg : process(clk_i)
begin
if rising_edge(clk_i) then
if cycle_div60 = '1' then
timer_div60_reg <= 0;
elsif cycle_div10 = '1' then
timer_div60_reg <= timer_div60_reg + 1;
end if;
end if;
end process p_timer_div10_reg;
half_div60 <= cycle_div10 when timer_div60_reg = 2 else '0';
cycle_div60 <= cycle_div10 when timer_div60_reg = 5 else '0';
p_clk_div60_reg : process(clk_i)
begin
if rising_edge(clk_i) then
if half_div60 = '1' or cycle_div60 = '1' then
clk_div60_reg <= not clk_div60_reg;
end if;
end if;
end process p_clk_div60_reg;