Compteurs et diviseurs de fréquence

Compteurs

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,

Compteur modulo dix

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.

Compteur modulo 10

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;

Séquencement d’actions répétitives

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 :

Feux tricolores

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 => ...
    );

Diviseurs de fréquence

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 NN revient à multiplier par NN la durée d’une période. Autrement dit, une période du signal de sortie aura une durée égale à NN 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.

Division de fréquence par deux

Pour N>2N>2, un diviseur de fréquence par NN peut être réalisé avec un compteur modulo NN 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 :

Division de fréquence

Lorsque NN est pair, il est possible d’obtenir le même comportement avec un compteur modulo N/2N/2. 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.

Mise en cascade de diviseurs de fréquence

Imaginons à présent que notre application doive produire deux signaux d’horloge :

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 60=10×660 = 10\times 6, 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 :

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;

Division de fréquence