IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Voir le flux RSS

f-leb

[Actualité] [FPGA] [Verilog] Description d'un afficheur 7-segments en langage Verilog

Noter ce billet
par , 08/03/2021 à 08h00 (6553 Affichages)
Dans un article précédent (Découvrir le langage Lucid avec une carte de développement FPGA d’Alchitry), je vous présentais déjà cette plateforme Alchitry pour débuter sur FPGA. Je ressors donc pour l’occasion ma carte Alchitry Au sur laquelle est enfichée cette fois une carte d’extension (ou shield) Alchitry Io.
Dans ce billet, je propose une démonstration en mettant en œuvre un afficheur multiplexé 7-segments à quatre digits (anode commune) en langage Verilog.

Un chronomètre au 1/10e de seconde

En mode multiplexé, le fonctionnement de l'afficheur à 7-segments est résumé dans l'animation ci-dessous :


Si on veut afficher le nombre « 1234 », il faut afficher successivement les chiffres qui composent le nombre chacun leur tour à une vitesse suffisante pour tromper le cerveau au point qu’il ne distingue plus les clignotements des digits qui s’allument et s’éteignent à tour de rôle (phénomène de persistance rétinienne). On prépare les segments en mettant à l’état bas les cathodes correspondantes, et on met l’anode du digit où l’on veut que le chiffre apparaisse à l’état haut. Un « court » instant plus tard, on éteint le digit puis on passe au digit suivant avec un nouveau chiffre, et ainsi de suite…

Cette technique peut sembler compliquée à mettre en œuvre, mais elle permet d’économiser un bon nombre de broches à piloter : 8 cathodes + 4 anodes au lieu de 8 x 4 = 32 cathodes + 4 anodes sans multiplexage. 24 sorties économisées...

Le schéma équivalent de l'afficheur est le suivant :

Nom : schema-7segs-4digits.jpg
Affichages : 69
Taille : 91,2 Ko

Structurellement, le projet est découpé en trois modules (reset_conditioner.v, tenth_second_counter.v, seven_seg_multiplexing.v). Ci-dessous, le schéma d'analyse obtenu dans la suite Xilinx Vivado (analyse RTL) :

Nom : rtl-analysis2.png
Affichages : 65
Taille : 48,6 Ko

En sortie tout à droite :
  • io_sel[3:0] : bus de sortie vers les 4 anodes.
  • io_seg[7:0] : bus de sortie vers les 8 cathodes (les 7 segments + point décimal).



Le module principal au_top.v ci-dessous (cliquer sur le bouton [Montrer]) est donc responsable de la structure du projet en instanciant les différents modules.

au_top.v (Top Level hierarchy) :
Code verilog : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
module au_top(
    input clk,              // horloge 100 MHz
    input rst_n,            // bouton Reset, actif à l'état bas
    output [7:0] led,       // jeu de LED x 8
    input  usb_rx,          // liaison série USB : Rx          
    output usb_tx,          // liaison série USB : Tx
                            // ---- Io shield E/S ----------------------
    output [23:0] io_led,   // jeu de 8 LED x 3
    output [7:0] io_seg,    // 7-segments : cathodes x (7 segments + dp)
    output [3:0] io_sel,    // 7-segments : anode x 4
    input  [4:0] io_button, // boutons-poussoirs x 5
    input  [23:0] io_dip    // interrupteurs dip switches x 24
  );
    
    wire rst;
    
    // Conditionnement du signal Reset, synchronisation avec l'horloge
    reset_conditioner rst_cond(
         .clk(clk),
         .in(!rst_n),
         .out(rst)
         );
    
    assign led = 8'h00;             // LED x 8 éteintes
    assign io_led = 24'h000000;     // LED x 24 du shield Io éteintes

    assign usb_tx = usb_rx;         // retransmission Rx vers Tx
        
        
    wire [13:0] displayed_number;   // nombre à afficher                         
                                            
    seven_seg_multiplexing seven_seg_multiplexing_inst( 
        .clk(clk),
        .rst(rst),
        .displayed_number(displayed_number),
        .digit(io_sel),
        .segments(io_seg)
        );
        
    tenth_second_counter tenth_second_counter_inst(   
        .rst(rst),
        .clk(clk),
        .enable(~io_button[1]),  // appui sur le bouton pour arrêter le chrono
        .out(displayed_number)
        );
                                 
endmodule


Module reset_conditioner

Le module reset_conditioner est proposé directement par l'EDI Alchitry Labs. L’instance rst_cond permet de « nettoyer » le signal d’entrée, provenant de l’appui sur le bouton Reset en surface de la carte (signal rst_n), et de le synchroniser sur le front montant de l’horloge (signal clk). Ce signal conditionné servira au besoin à réinitialiser simultanément tous les composants souhaités.

Exemple de signal conditionné sur la sortie out (simulation comportementale Xilinx Vivado) :
Nom : simul1.PNG
Affichages : 59
Taille : 13,4 Ko

reset_conditioner.v :
Code verilog : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/*
   This file was generated automatically by Alchitry Labs version 1.2.5.
   Do not edit this file directly. Instead edit the original Lucid source.
   This is a temporary file and any changes made to it will be destroyed.
*/

/*
   Parameters:
     STAGES = 4
*/
module reset_conditioner(
    input clk,
    input in,
    output reg out
  );
  
  localparam STAGES = 3'h4;
  
  
  reg [3:0] M_stage_d, M_stage_q = 4'hf;
  
  always @* begin
    M_stage_d = M_stage_q;
    
    M_stage_d = {M_stage_q[0+2-:3], 1'h0};
    out = M_stage_q[3+0-:1];
  end
  
  always @(posedge clk) begin
    if (in == 1'b1) begin
      M_stage_q <= 4'hf;
    end else begin
      M_stage_q <= M_stage_d;
    end
  end
  
endmodule



Module tenth_second_counter

Ce module génère en sortie un nombre entier sur 14 bits (pour afficher des nombres entre 0 et 9999), initialement à zéro, et qui va s'incrémenter tous les dixièmes de seconde. L'appui sur le bouton Reset provoque la remise à zéro du compteur, et maintenir l'appui sur le bouton io_button[1] du shield va stopper le chronomètre.

Évolution du chronomètre, simulation Xilinx Vivado :
Nom : simul_displayed_number.PNG
Affichages : 59
Taille : 8,0 Ko
On voit les premiers instants de la valeur du chronomètre qui s'incrémente 0, 1, 2, 3... tous les 1/10è de seconde (=100 ms).

tenth_second_counter.v :
Code verilog : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
module tenth_second_counter
    #(parameter ticks_1_second = 100_000_000) // fréquence = 100 MHz par défaut
    
    (input rst,
     input clk,
     input enable,
     output reg [13:0] out
    );
    
    reg [24:0] counter;
    
    initial begin
        counter <= 0;
        out <= 0;
    end
    
    always @(posedge clk or posedge rst) begin
        if (rst == 1) begin
            counter <= 0;
            out <= 0;
        end
        else if (enable == 1) begin
                if (counter >= ticks_1_second/10 - 1) begin // 1/10e seconde atteint
                   counter <= 0;
                   out <= out + 1;
                end else
                   counter <= counter + 1;
             end       
    end 
           
endmodule


Module seven_seg_multiplexing

Ce module prend en charge l'affichage du nombre displayed_number qui se présente à l'entrée. Il faut d'abord activer les digits à tour de rôle selon la séquence : 0111, 1011, 1101, 1110, ... (l'anode du digit est activée en pilotant un transistor MOSFET canal-P, c'est-à-dire à l'état bas de la grille).
La simulation ci-dessous montre que chacun des 4 digits est rafraichi toutes les 5,24 ms, soit à une fréquence de 191 Hz suffisante pour que la persistance rétinienne joue son rôle (et un chiffre des 1/10e affiché garanti avec une précision suffisante). Cette fréquence provient de la fréquence de l'horloge de la carte (100 MHz) divisée par un facteur 219 : 100.106 / 219 = 191 Hz.

Nom : simul_io_sel.PNG
Affichages : 56
Taille : 9,6 Ko

On passe par l'intermédiaire des deux bits de poids fort digit_activating_counter = counter[18:17] d'un compteur 19 bits incrémenté à chaque front montant de l'horloge 100 MHz pour établir le signal d'activation des digits à tour de rôle (io_sel[3:0]).

Nom : simul_counter_19bits.PNG
Affichages : 58
Taille : 33,5 Ko

Un peu d'arithmétique permet de récupérer individuellement les chiffres des milliers, centaines, dizaines et unités qu'il faudra présenter en activant les segments.
Pour chaque digit activé, on voit ci-dessous en magenta l'état des 8 cathodes des segments et du point décimal (le segment ou le points décimal s'allume lorsque la cathode est à l'état bas). Dans l'ordre (en commençant par le bit de poids fort) : point décimal dp + segments g, f, e, d, c, b et a.
  • 11000000, c'est le dessin du chiffre « 0 » que l'on retrouve sur le 1er, 2e et 4e digit de l'afficheur.
  • 01000000, c'est aussi le dessin du chiffre « 0 » mais avec le point décimal allumé en plus, et que l'on retrouve sur le 3e digit uniquement. Normal, le chiffre qui suit est le chiffre des dixièmes.

Nom : simul_io_seg.PNG
Affichages : 57
Taille : 12,9 Ko

Au démarrage du chronomètre, c'est donc bien « 000.0 » qui s'affiche.

seven_seg_multiplexing.v :
Code verilog : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
module seven_seg_multiplexing(
    input rst,
    input clk,
    input [13:0] displayed_number,
    output reg [3:0] digit,
    output reg [7:0] segments
    );
    
    reg [18:0] counter; 
    wire [1:0] digit_activating_counter;
    wire third_digit;
    reg [3:0] figure;
    reg [7:0] activating_dp;
      
    initial begin 
        counter <= 0;
    end 
    
    always @(posedge clk or posedge rst) begin 
        if (rst == 1)
            counter <= 0;
        else
            counter <= counter + 1;
    end
   
    assign digit_activating_counter = counter[18:17];
    
    always @(digit_activating_counter) begin
        case (digit_activating_counter)
          2'b00: begin
                   digit = ~4'b1000; // activer le 1er digit seulement
                   figure = displayed_number / 1000; // chiffre des milliers
                 end
          2'b01: begin
                   digit = ~4'b0100; // activer le 2e digit seulement
                   figure = (displayed_number % 1000) / 100; // chiffre des centaines
                 end
          2'b10: begin
                   digit = ~4'b0010; // activer le 3e digit seulement
                   figure = ((displayed_number % 1000) % 100) / 10; // chiffre des dizaines
                 end
          2'b11: begin
                   digit = ~4'b0001; // activer le 4e digit seulement
                   figure = ((displayed_number % 1000) % 100) % 10; // chiffre des unités
                 end   
          default: begin
                     digit = ~4'b1000; // activer le 1er digit seulement
                   end
        endcase
    end
    
    assign third_digit = digit_activating_counter == 2'b10;
    
    always @(*) begin
        activating_dp = third_digit ? ~8'b10000000 : ~8'h00 ; // activation du point si 3e digit
        case (figure)           
                                   //pgfedcba			
            4'b0000: segments <= ~8'b00111111 & activating_dp; // "0"
            4'b0001: segments <= ~8'b00000110 & activating_dp; // "1" 
            4'b0010: segments <= ~8'b01011011 & activating_dp; // "2" 
            4'b0011: segments <= ~8'b01001111 & activating_dp; // "3" 
            4'b0100: segments <= ~8'b01100110 & activating_dp; // "4" 
            4'b0101: segments <= ~8'b01101101 & activating_dp; // "5" 
            4'b0110: segments <= ~8'b01111101 & activating_dp; // "6" 
            4'b0111: segments <= ~8'b00000111 & activating_dp; // "7" 
            4'b1000: segments <= ~8'b01111111 & activating_dp; // "8"     
            4'b1001: segments <= ~8'b01101111 & activating_dp; // "9" 
            default: segments <= ~8'b00111111 & activating_dp; // "0"
        endcase            
    end
    
endmodule



Exercice :
L'image qui suit montre une simulation à un moment où le chronomètre affiche « 147.4 ». En mode multiplexé, il faut donc présenter les bonnes valeurs aux cathodes et dessiner successivement les chiffres « 1 », « 4 », « 7. » et « 4 ».
  • On prépare les cathodes pour dessiner le « 1 », et on active le 1er digit.
  • On prépare les cathodes pour dessiner le « 4 », et on active le 2e digit.
  • On prépare les cathodes pour dessiner le « 7. », (le « 7 » avec le point décimal), et on active le 3e digit.
  • On prépare les cathodes pour dessiner le « 4 », et on active le 4e digit.
  • Et on reprend au 1er digit... etc.


Nom : simul1474.PNG
Affichages : 62
Taille : 74,2 Ko

Vérifiez que les valeurs présentées aux cathodes sont correctes.


Conclusion :
On ne rencontre pas beaucoup de HDL (Hardware Description Language) sur Developpez... le langage Verilog n'est pas tout jeune non plus, sa dernière version stable remontant à 2005. Il y a toutefois des évolutions récentes du langage avec SystemVerilog, dont Verilog est maintenant un sous-ensemble.
En regardant les codes, vous découvrirez différentes manières de décrire un circuit : de façon structurelle (connexion de signaux et de modules) ou comportementale où certains blocs de code sont exécutés de façon procédurale avec une syntaxe assez proche du C.
Les simulations comportementales et les chronogrammes ont été obtenus avec la suite Xilinx Vivado.

Suite au prochain épisode...

Envoyer le billet « [FPGA] [Verilog] Description d'un afficheur 7-segments en langage Verilog » dans le blog Viadeo Envoyer le billet « [FPGA] [Verilog] Description d'un afficheur 7-segments en langage Verilog » dans le blog Twitter Envoyer le billet « [FPGA] [Verilog] Description d'un afficheur 7-segments en langage Verilog » dans le blog Google Envoyer le billet « [FPGA] [Verilog] Description d'un afficheur 7-segments en langage Verilog » dans le blog Facebook Envoyer le billet « [FPGA] [Verilog] Description d'un afficheur 7-segments en langage Verilog » dans le blog Digg Envoyer le billet « [FPGA] [Verilog] Description d'un afficheur 7-segments en langage Verilog » dans le blog Delicious Envoyer le billet « [FPGA] [Verilog] Description d'un afficheur 7-segments en langage Verilog » dans le blog MySpace Envoyer le billet « [FPGA] [Verilog] Description d'un afficheur 7-segments en langage Verilog » dans le blog Yahoo

Mis à jour 08/03/2021 à 13h17 par f-leb

Tags: fpga, verilog
Catégories
Programmation

Commentaires