Avertissement: Ce TP ne peux être réalisé sans avoir étudié les leçon L1 et L2. Les salles dédiées à ce TP sont les salles A405 et A406.
L'objectif de ce TP est d'une part de vous familiariser avec le langage SystemVerilog et d'autre part de découvrir l'outil Quartus qui sera utilisé tout au long du module. Vous allez donc devoir développer du code SystemVerilog puis mettre en oeuvre votre code sur les maquettes disponibles dans les salles de TP.
Les transparents (pour les profs)...
La maquette de TP que vous allez utiliser, est basée sur l'utilisation d'un circuit appelé FPGA (Field Programmable Gate Array). Il s'agit d'un circuit composé d'un grand nombre de blocs de traitement logiques (combinatoires ou séquentiels) , dont on peut reprogrammer la fonctionnalité à volonté. Ces blocs de traitement reliés entre eux par des connections dont la topologie est elle même reprogrammable.

Le circuit FPGA se situe dans la zone centrale de votre maquette

Sur le coté gauche vous distinguez la prise d'alimentation de la maquette (vérifiez si elle est bien branchée à l'alimentation, ou si l'alimentation est elle même bien branchée). L e bouton marche/arrêt (rouge) se situe juste à coté.

Dans la zone en bas à gauche, vous disposez de 10 interrupteurs. Ces interrupteurs permettent de forcer les entrées du FPGA nommée sw[9], sw[8]...sw[0] à 0 (position basse) ou à 1 (position haute).

A droite des interrupteurs, vous trouverez 4 boutons poussoirs, connectés aux entrées key[3], key[2], key[1] et key[0] du FPGA. Attention, l'entrée key[i] vaut 1 lorsque le bouton correspondant est relaché, et 0 lorsrqu'il est appuyé.

Au dessus des interrupteurs vos disposez de 10 LEDS connectées au sorties ledr[9], ledr[8], ...,ledr[0] du FPGA. Les LEDS sont allumées si la sortie ledr[i] correspondantes vaut 1.

Vous disposez (en bas à gauche) de 6 afficheurs "7-segment" connectés aux signaux hex5[6:0], hex4[6:0],...,hex0[6:0] du FPGA.
Attention, pour allumer le segment i de l'afficheur j, il faut que le signal hexj[i] soit à 0, pour l'éteindre il faut que ce signal soit à 1.

Les autres connecteurs sont réservées à des interfaces spécialisées (audio, vidéo, ethernet,...) et ne seront pas utilisées dans ce TP.

Enfin, sur la gauche, se trouve le connecteur USB esclave permettant de programmer le contenu du FPGA depuis votre PC. Vérifiez que ce cable est bien connecté (à la maquette et au PC)

II A vous de jouer : Un squelette de design pour découvrir les outils Quartus
- Téléchargez l'archive de projet Quartus disponible ici.
- Ouvrez une fenêtre de terminal et décomprimez l'archive dans le répertoire ou vous l'avez placée: tar xjvf bac_a_sable.tbz.
- Toujours dans ce terminal placez vous dans le répertoire "bac_a_sable/quartus"
- Toujours dans ce terminal, lancez l'outil Quartus: quartus &
L'outil prend un peu de temps à ce charger. Cet outil va vous permettre de décrire des structures logiques en langage SystemVerilog, et de les transformer en un fichier de programmation pour le FPGA.
- Utilisez le menu déroulant "File->Open Project". Faites bien attention a ouvrir un "projet" et pas un simple fichier.
- Et ouvrez le fichier fpga.qpf.
Ce fichier contient toutes les informations nécessaires à l'outil pour compiler le source SystemVerilog: Le type de FPGA, où sont ses entrées/sorties, quels sont les fichiers source nécessaires....
Vous disposez maintenant d'une fenêtre "Project Navigator":
- Dans le menu déroulant sur sa droite, choisissez "Files" à la place de "Hierarchy"
-
Ouvrez le fichier fpga.sv (double-click...)
Sur la gauche apparaît un fichier source en langage SystemVerilog. Pour le moment, nous ne nous attarderons pas sur la syntaxe du langage. Sachez simplement, que ce fichier contient la définition d'un module entre les mots clefs module et endmodule ainsi que la définition des entrées sorties du module
- Prenez la peine d'établir les correspondances entre les différents signaux déclarés est ceux décrits dans le chapitre précédent.
Nous allons simplement rajouter du code pour connecter l'ensemble des entrées d'interrupteurs sw aux leds rouges ledr.
- Ajoutez la ligne suivante après le commentaire "// ajouter votre code..." et avant "endmodule"
always @(*) ledr <= sw;
Nous allons maintenant compiler le fichier SystemVerilog. Pour cela:
- Appuyez le bouton
situé dans la barre supérieur de l'outil
Même pour un design simple comme celui-ci, la compilation peu prendre une minute ou deux. S'il n'y a pas de message d'erreur (voir la fenêtre Messages), l'outil a créé un fichier de programmation pour fpga.sof le FPGA. Nous allons programmer le FPGA, pour cela vérifiez que la carte DES1-SoC est bien alimentée, et que le cable de transfert USB est bien connecté.
- Appuyez le bouton
situé dans la barre supérieure de l'outil
La fenêtre suivante devrait s'ouvrir.
- Vérifiez que le fichier fpga.sof est bien sélectionner et que la case Program/Configure est bien cochée.
- Appuyez sur le bouton Start
Le FPGA est maintenant programmé, si tel n'est pas le cas (messages d'erreur du programmeur, demandez à un encadrant de vous aider).
- Vérifiez que les leds rouges s'éteignent ou s'allument lorsque vous modifiez la position des interrupteurs.
Votre environnement de TP est maintenant en place. Vous pouvez sauver votre projet en l'état.
Avant de continuer dans les exercices, prenez la peine de lire l'introduction au langage SystemVerilog qui suit.
III Introduction au langage SystemVerilog
SystemVerilog est un langage de description de matériel. Il permet de décrire des circuits (réels ou virtuels) de deux façons principales :
- Description d'une structure: on décrit de quoi est composé un circuit en terme de blocs, et comment ces blocs sont reliés entre eux. Les blocs peuvent aussi à leur tour être décrits de cette façon, en instanciant des blocs plus petits, etc. jusqu'aux portes élémentaires.
- Description d'un comportement: plutôt que de décrire un circuit en terme de sous-blocs plus petits, on peut décrire son comportement (ce qu'il fait, sa fonctionnalité).
La syntaxe de System Verilog est proche de celle de C, cependant ne pas oublier les quelques nuances importantes :
- Une description System Verilog n'est pas un programme : elle décrit le comportement d'un circuit composés de plusieurs entités fonctionnant en parallèle, alors qu'un programme C est une succession d'ordres qu'un processeur exécutera successivement les uns après les autres.
- System Verilog manipule des signaux plus que des variables (au sens C...)
Nous n'indiquons ici que les éléments de syntaxe de base du langage nécessaires aux séances pratiques.
III.1 Les modules
En SystemVerilog, les blocs sont appelés modules. Avant d'utiliser un module, il faut le déclarer.
On déclare un module de la façon suivante :
contenu_du_module
endmodule
Les signaux d'entrée/sortie sont déclarés dans l'entête de déclaration du module. Il s'agit d'une liste de déclarations individuelles séparées par des virgules. Chaque déclaration individuelle doit comprendre :
- La déclaration du type d'entrée-sortie : input pour une entrée , output pour une sortie.
- Le type de signal : nous utiliseront exclusivement le type logic.
- La largeur éventuelle du signal : la notation [j:i] indique un vecteur de (j-i+1) bits indexés de i à j. Attention : i est le bit de poids faible. On a donc généralement j > i...
Exemples de déclaration d'entrées/sorties:
output logic aaa, // Un signal sortant de largeur 1 bit appelé "aaa"
input logic [7:0] d // Une donnée (mot) entrante de largeur 8 bits appelé "d"
Les modules contiennent des signaux internes. Ces signaux internes peuvent être déclarés de façon similaire aux entrées/sorties.
logic mon_sig ; // Un signal de largeur 1 bit appelé "mon_sig"
logic [3:0] mon_autre_sig ; // Un bus de largeur 8 bits appelé "mon_autre_sig"
...
Quelques règles générales de syntaxe
- Les commentaires sont comme en Java :
//
ou/* .... */
- Le "
;
" a le même rôle de séparateur qu'en Java. - Il ne faut pas mettre de séparateur "
;
" après les mots-clefs enend
... commeendmodule
etendcase
.
III.2 Contenu des modules : Description d'un comportement combinatoire
Logique combinatoire
On décrit généralement un comportement en spécifiant les valeurs affectées aux sorties d'un module ou à des signaux à l'intérieur de celui-ci (en fonction des entrées, bien sûr). Pour l'instant nous nous limitons à la description de comportements de type calcul combinatoire. La syntaxe générale utilisée est la suivante:
nom_du_signal <= fonction_combinatoire ;
Il est important de bien comprendre la signification de cette syntaxe:
- Le signal nom_du_signal est la sortie d'une fonction combinatoire fonction_combinatoire.
- A chaque fois (always) qu'un entrée quelconque (*) de cette fonction est modifiée (@), le signal nom_du_signal est remis à jour.
Nous vous proposons deux modes de calcul des fonctions combinatoires :
- une expression algébrique directe,
- une expression tabulée grâce à l'usage de la syntaxe case.
Exemples:
logic [1:0] Z ; // Déclaration d'un signal interne sur 2 bits
always @(*) a <= b & c ; // Il y a une fontion logique ET dont les entrées
// sont b et c et dont la sortie est a.
always@ (*) a <= c ; // Le signal a est identique au signal c.
// Le signal a est défini par une
always@ (*) // fonction complexe de Z, b c et d...
case (Z)
2'b00 : a <= b ; // cas où Z vaut O
2'b01 : a <= c & d ; // cas où Z vaut 1
default : a <= d ; // Tous les autres cas
endcase
Les expressions algébriques utilisent les primitives suivantes :
Fonction | Symbole |
Négation | ~ |
Et | & |
Ou | | |
Xor | ^ |
La syntaxe que nous utiliserons pour le case est :
valeur_1 : mon_signal <= expression_algébrique_1 ;
valeur_2 : mon_signal <= expression_algébrique_2 ;
...
default : mon_signal <= expression_algébrique_default ;
endcase
Attention : Lorsque l'on utilise l'expression case il faut veiller à définir complètement la table de vérité de la fonction booléenne correspondante, soit explicitement, soit implicitement via l'expression default.
Les valeurs indiquées dans le case sont des constantes.
Valeurs constantes
La base 10 est utilisée par défaut, vous pouvez choisir la base (décimal:d, hexadécimal:h ou binaire:b) ainsi que la taille de la constante de la façon suivante:
8'd25 // constante codée sur 8 bits valant 25 (en base 10)
3'b101 : ... // constante codée sur 3 bits valant 101 en base 2 (donc 5 en base 10)
4'ha : ... // constante codée sur 4 bits valant A en hexadécimal (donc 10 en base 10)
1'b1 : ... // constante codée sur 1 bit et valant 1...
Manipulation des vecteurs (mots ou bus...)
Les entrées/sorties ainsi que les signaux internes peuvent être des vecteurs de bits:
- Ces vecteurs peuvent être de taille arbitraire.
- Il est possible d'accéder à n'importe quel élément ou partie d'un vecteur.
Les exemples suivant vous montrent quelques cas d'utilisation :
logic [3:0] B, C, D; // Déclaration de 3 vecteurs B, C et D de 4 bits
logic Z; // Z est un élément de 1 bit
always @(*) B <= A[7:4]; // Le quartet B est identique à la moitié de
// poids fort de l'octet A
always @(*) Z <= A[3]; // Z est identique au bit n°3 de A
always @(*) A <= {C,D}; // A est la concaténation des mots C et D
// équivalent à A[7:4] <= C et
// A[3:0] <= D
Arithmétique entière et expressions
En SystemVerilog, les vecteurs de bits de type logic [i:0] sont implicitement interprétés comme des entiers non signés de (i+1) bits. Vous pouvez utiliser les opérateurs arithmétiques standards : +' et -.
SytemVerilog gère automatiquement les opérations d'extension de taille et de modulo lorsque les opérandes sont de tailles différentes:
logic [4:0] b ;
logic c ;
logic [5:0] y ;
logic [3:0] z ;
logic [2:0] t ;
always @(*) b <= 5'b01110 ; // b est un entier de 5 bits valant 14.
always @(*) a <= 2'b11 ; // la valeur constante 3 est affectée à l'entier a
always @(*) c <= 3 ; // c est un entier de 1 bit, on lui affecte la valeur 3 (2'b11),
// après troncature c vaut la valeur 1
always @(*) y <= a + b + c ; // y est codé sur un nombre de bits suffisant
// quelles que soient les valeurs de a, b et c
// (ici y = 18) 010010 = 000011 + 001110 + 000011
always @(*) z <= a + b + c ; // z prends les 4 bits de poids faible du résultat
// (z = 2) 0010 = 0011 + 1110 + 0011
always @(*) {t,z} <= a + b + c ; // z prends les 4 bits de poids faible du résultat : z = 2 (remarque 1)
always @(*) z <= {4{c}} ^ a ; // Le mot z est identique au mot a si c est égal a 0
// sinon le mot z est le complémentaire du mot a (remarque 2)
(1) Remarquez l'usage des accolades pour concaténer deux mots
(2) Remarquez l'usage des accolades pour générer un mot de 4 bits identiques.
Expressions booléennes
- En SystemVerilog, un signal de type
logic
est équivalent à un booléen :- vrai est représenté par un
1
- faux est représenté par un
0
- vrai est représenté par un
- Les opérateurs utilisés dans les expressions booléennes sont semblables à ceux du langage C
- L'expression ternaire
? est identique à celle du langage C (dans les cas que vous rencontrerez cette année...):
&& |
et |
|| |
ou |
== |
égalité |
! |
négation logique |
!= |
non égalité> |
|
inférieur |
... | ... |
logic [7:0] b ;
always @(*) a <= (b == 8) ; // a prend la valeur 1'b1 lorque b est égal à 8
always @(*) a <= (!(b == 8)) && c ; // a prend la valeur 1'b1 lorsque b
// est différent de 8 et que c est égal à 1'b1
always @(*) a <= (b == 8) ? c : d ; // a prend la valeur c si b est égal à 8 ,
// sinon il prends la valeur de d
Nombres signés
Par défaut, les vecteurs de bits déclarés comme logic[i:0
]
sont considérés comme des nombres non signés. Les opérations arithmétiques ainsi que les opérations de comparaison arithmétique les interpréteront comme des nombres non signés.
Pour pouvoir faire de l'arithmétique sur des nombres signés il faut utiliser le mot clé signed
au moment de la déclaration.
logic c;
always @(*) A <= 4'b1111;
always @(*) B <= 4'b0000;
always @(*) c <= (A>B); // c vaut 0 car A est interprété comme un nombre signé
// (A vaut -1 et B vaut 0)
III.3 Descriptions de structures : l'instanciation de modules.
La figure suivante présente l'exemple d'un module M_a instanciant deux occurrences Inst1
et Inst2
d'un même module M_b
.
Remarquez le signal nommé S1
qui connecte la sortie Y
d'un sous-module à l'entrée A
d'un autre sous-module.
Le code correspondant à cette description est le suivant :
input logic Y,
output logic Z );
logic S1; // Déclaration du signal S1 interne à M_a
M_b Inst1(.A(X),.B(Y),.Y(S1)) ; // Instance Inst1 de M_b
M_b Inst2(.A(S1),.B(Y),.Y(Z)) ; // Instance Inst2 de M_b
endmodule
En résumé,
- La déclaration d'un signal interne à un module ce fait en précisant:
- Le type de signal pour nous exclusivement logic
- La largeur éventuelle du signal : la notation
[i:j]
indique un vecteur de i-j+1 bits indexés de j à i - Le nom du signal
- L' instanciation d'un sous-module ce fait en précisant:
- Le nom du sous_module
- Le nom de l'instance particulière du sous-module
- Entre parenthèses, une liste de connexions des entrées/sorties. La syntaxe pour chaque Entrée/Sortie est:
.nom_du_signal_du_sous_module( nom_du_signal_du_module )
Exemples de connexions :
logic [1:0]D ;
xxx inst_xxx(.E(C),... // Le bus C est relié à l'entrée E de xxx.
// Ils ont le même nombre de bits.
xxx inst_xxx(.F(C[0]),... // Le bit 0 de C est connecté à l'E/S F de xxx.
// (F doit donc normalement avoir une largeur de 1 bit)
xxx inst_xxx(.G({C,D}),... // Les signaux C et D sont regroupés en un vecteur
// de largeur 4 bits, connecté à l'E/S G de xxx.
// (G doit donc ici normalement avoir une largeur de 4 bits)
...
IV Exercices à réaliser.
Ces exercices on pour but de vous familiariser pas à pas aux méthodes de codage en SystemVerilog. Vous ne pourrez peut-re pas réaliser tous les exercices, ce n'est pas grave, mais tentez quand même d'en réaliser le maximum...
Exercice 1:
Il s'agit de visualiser un nombre de 4 bits au format hexadécimal à l'aide des afficheurs de la maquette.
- Modifiez le code du module FPGA de façon à afficher la valeur représentée par les interrupteurs sw[3:0] sur l'afficheur hex0.
- Compilez et testez votre code sur la maquette.
Exercice 2:
Nous désirons afficher plusieurs valeurs sur les différents afficheurs (hex0, hex1,...). Nous ne voulons pas dupliquer le code (bonne pratique identique à celle du développement logiciel).
- Dans le répertoire src, qui contient déjà le fichier fpga.sv, créez un nouveau fichier nommé decodeur7seg.sv qui contiendra la définition d'un module decodeur7seg dont les entrées/sorties sont:
- entrée : din, mot de 4bits représentant le code à afficher.
- sortie : dout, mot de 7 bits représentant les bits de l'afficheur.
- Modifiez le code de fpga de façon à instancier le module decodeur7seg en lieu et place du code précédent.
- Compilez et testez votre code sur la maquette.
Exercice 3:
Faisons un peu d'arithmétique, nous désirons additionner 2 mots de 4 bits (non signés) , afficher chacune des valeurs ainsi que le résultat.
- Nous supposons que sw[3:0] représente un nombre non signé de 4 bits.
- Nous supposons que sw[7:4] représente un nombre non signé de 4 bits.
- Modifiez fpga pour:
- Afficher sw[3:0] sur hex0
- Afficher sw[7:4] sur hex1
- afficher la somme de sw[3:0] et de sw[7:4] sur hex3 et hex4. (Expliquez pourquoi nous devons utiliser 2 afficheurs)
- Compilez et testez sur votre maquette.
Exercice 4:
On suppose maintenant que les nombres sont signés, codés sur 4 bits en complément à 2.
- Transformez votre additionneur en conséquence
- Compilez et testez sur votre maquette.
Exercice 5:
- Transformez votre additionneur en additionneur/soustracteur. sw[9] sera utilisé pour choisir addition ou soustraction.
- Compilez et testez sur votre maquette.
Exercice 6:
Lire en hexadécimal est un peu fatiguant, nous désirons afficher les nombres en décimal:
- Chaque nombre servant à l'addition devra utiliser 2 afficheurs:
- le premier afficheur affichera l'amplitude du nombre (de 0 à 8)
- le second afficheur affichera le signe du nombre s'il est négatif.
- Le resultat utilisera 2 afficheurs
- Le couple d'afficheur devra afficher des nombres codés de (-16 a 16)
- Le premier afficheur affichera les unités (0..9)
- Le deuxième afficheur affichera les dizaines : -1, 0 ou 1
- Transformez votre additionneur/soustracteur en conséquence
- Compilez et testez sur votre maquette.
Fichier attaché | Taille |
---|---|
![]() | 2.9 Ko |