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

Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

FPGA - Description d'un afficheur 7-segments en langage Verilog,
Un billet de f-leb

Le , par f-leb

0PARTAGES

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 :


https://www.romux.com/bootloader/usb...isplay-circuit

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 :


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) :


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
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) :

reset_conditioner.v :




Code verilog : Sélectionner tout
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 :
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
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.


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]).


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.


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

seven_seg_multiplexing.v :




Code verilog : Sélectionner tout
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.



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...

Une erreur dans cette actualité ? Signalez-nous-la !