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.

https://www.youtube.com/watch?v=yRJb7t5qiEAUn 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 :

592664
https://www.romux.com/bootloader/usb-pic18f4550/seven-segment-display-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 :

592718

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

592665

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 ) est donc responsable de la structure du projet en instanciant les différents modules.

au_top.v (Top Level hierarchy) :
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), // 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) :
592677

reset_conditioner.v :
/*
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 du shield va stopper le chronomètre.

Évolution du chronomètre, simulation Xilinx Vivado :
592688
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 :
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.

592692

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

592745

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.

592694

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

seven_seg_multiplexing.v :
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.


592716

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

Vous avez lu gratuitement 6 121 articles depuis plus d'un an.
Soutenez le club developpez.com en souscrivant un abonnement pour que nous puissions continuer à vous proposer des publications.

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