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

Apprendre à développer sur FPGA avec Intel Quartus Prime

Communication SPI avec un convertisseur Analogique-Numérique, simulation fonctionnelle et analyse des signaux

La carte FPGA du kit DE0-Nano intègre en surface un convertisseur Analogique-Numérique en liaison SPI (Serial Peripheral Interface) avec la puce FPGA (Cyclone IV). On propose de s’exercer en langage Verilog pour communiquer avec le convertisseur en s’aidant des outils d’Intel Quartus Prime pour faire des simulations et analyser les signaux logiques.

Commentez Donner une note à l´article (5)

Article lu   fois.

L'auteur

Profil Pro

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

Pour commencer, voici une courte vidéo de démonstration du projet réalisé dans ce tutoriel (les fichiers sources sont fournis en annexeAnnexe : fichiers sources) :


[FPGA] [DE0-Nano] Communication série SPI avec un convertisseur A/N 12 bits - Quartus Prime Signal Tap Logic Analyzer


Un potentiomètre rotatif est connecté à un port situé sous la carte. Son curseur est relié à une entrée analogique d’un convertisseur Analogique-Numérique (A/N) soudé en surface de la carte FPGA DE0-Nano. En tournant le bouton du potentiomètre, on active les LED qui s’animent comme un bargraphe pour indiquer le niveau de tension sur l’entrée analogique.

Voilà le genre de projets sans réelles difficultés si la carte était une carte à microcontrôleur, surtout si c’était une Arduino. La programmation d’un FPGA est bien différente de celle d’un microcontrôleur, mais j’ai déjà évoqué aussi ses avantages, ici et là, notamment en matière de flexibilité et de traitements parallèles. Dans le cas d’un contrôleur pour écran VGA (640x480@60Hz) par exemple, un FPGA est un excellent choix.

La vidéo montre également en arrière-plan l’écran du PC de développement avec les chronogrammes des signaux comme sur l’écran d’un analyseur logique ou d’un oscilloscope.

Pour vous familiariser avec l’environnement de développement sur FPGA tel que celui d’Intel Quartus Prime, je vous recommande de commencer avec le tutoriel Débuter avec les FPGA dans l’environnement Intel Quartus Prime.

II. La cible FPGA et le convertisseur A/N

J’ai déjà présenté le kit DE0-Nano lors d’un tutoriel précédent. La carte intègre en surface un convertisseur Analogique-Numérique 12 bits (réf : ADC128S022) à 8 entrées, en surbrillance et en bas à droite sur l’image ci-dessous.

Image non disponible
Image d’après « DE0-Nano User Manual »

La puce FPGA Cyclone IV communique avec le convertisseur A/N grâce aux quatre fils ADC_CS_N, ADC_SADDR, ADC_SDAT, et ADC_SCLK permettant d’établir une communication série compatible SPI (Serial Peripheral Interface) :

Image non disponible
Image d'après « DE0-Nano User Manual »

On voit aussi sur le schéma que les huit entrées analogiques du convertisseur A/N (IN0 à IN7) sont accessibles par un port (header) 2x13 broches sur le dessous de la carte. Pour la démonstration, dans ce tutoriel, on pourra câbler un potentiomètre rotatif 10k, par exemple sur l’entrée analogique 5, et faire évoluer la tension entre 0 et 3,3 V en tournant le bouton :

Image non disponible
Image d'après « DE0-Nano User Manual »

III. Communication SPI avec le convertisseur A/N

Comme tout développeur sur systèmes embarqués, vous allez commencer par parcourir la documentation (datasheet) du convertisseur A/N : ADC128S022 datasheet. Le protocole de communication qui nous intéresse est décrit par des chronogrammes :

Image non disponible
d'après Fig.1 ADC 128S022 Operational Timing Diagram

La communication peut débuter en abaissant le signal /CS (Chip Select) du composant avec qui l’on veut communiquer. Le signal d’horloge SCLK (Serial Clock) pour synchroniser les événements est initialement au repos à l’état haut, puis est activé. À l’entrée DIN du convertisseur A/N, on doit présenter les trois bits de sélection de l’entrée analogique sur front descendant de l’horloge (ADD2, ADD1, et ADD0, les 3 bits pour sélectionner l’une des 23=8 entrées du convertisseur A/N), et à partir du front descendant repéré 3. Le contrôleur du convertisseur A/N va acquérir l’état de ces bits sur front montant de l’horloge. Pendant ce temps-là, le convertisseur A/N « pousse » des zéros sur sa sortie DOUT. À partir du front descendant repéré 5, les 12 bits DB11 à DB0 du résultat de la conversion sur l’entrée sélectionnée sont « poussés » sur DOUT, et il faut récupérer l’état des bits sur front montant de l’horloge. Le processus d’acquisition de la conversion a consommé ainsi 16 cycles d’horloge et peut reprendre indéfiniment.
Le constructeur recommandant une fréquence d’horloge maximale à 3,2 MHz, on peut acquérir ainsi jusqu’à 3,2.106 / 16 = 200 000 échantillons par seconde (200 ksps, ou kilo samples per second).

Même si le protocole est assez simple à s’approprier, il reste que la difficulté de l’exercice est de respecter au cycle d’horloge près le timing de ces diagrammes.

IV. Configuration initiale du projet Quartus Prime

Le constructeur Altera (racheté par Intel) du kit DE0-Nano propose un utilitaire de configuration initiale d’un projet sous Quartus Prime avec l’exécutable DE0_Nano_SystemBuilder.exe.

Image non disponible

On coche l’horloge CLOCK, le convertisseur ADC (Analog-Digital Converter) et le bandeau de LED x 8. Les LED serviront à reproduire visuellement l’état de l’entrée analogique sous forme de bargraphe.

En cliquant sur le bouton [Generate], vous créez un nouveau dossier (ici adc_project4dvp) avec quelques fichiers portant la configuration minimale du projet. Vous trouverez plus de détails au chapitre III. Configuration initiale du projet d’un précédent tutoriel.

Vous pouvez maintenant ouvrir le fichier du projet adc_project4dvp.qpf (Quartus Project File).

Image non disponible

J’utilise à ce jour Quartus Prime 20.1 Lite. Il y aura peut-être quelques adaptations à faire si vous prenez une version plus récente. Voir Télécharger le logiciel Intel® Quartus® Prime.

Si vous allez dans le menu Assignments → Pin planner, vous constaterez que la cible et les entrées-sorties utiles du projet sont déjà configurées. Vous pouvez maintenant faire référence à une entrée-sortie en citant nommément le nœud : CLOCK_50 pour l’horloge principale 50 MHz, LED[0] à LED[7] pour les sorties dirigées vers le bandeau de LED et ADC_xxxxx pour les entrées-sorties dirigées vers le convertisseur A/N.

Image non disponible
Assignments → Pin planner

V. Création du module d’interface SPI et premières simulations

On commence par un premier module en Verilog : menu File → New… → Verilog HDL File que l’on sauvegardera sous le nom spi_interface.v (menu File → Save As…).

Schématiquement, ce module se présente de la façon suivante :

Image non disponible

Les entrées :

  • clk : l’horloge à 3,2 MHz dérivée de l’horloge principale 50 MHz ;
  • sdat : le signal des données poussées par le convertisseur A/N sur sa sortie DOUT.

Les sorties :

  • cs_n : signal Chip Select ;
  • data_out[11..0] : bus 12 bits, résultat de la conversion A/N ;
  • end_of_conversion : une impulsion générée tous les 16 cycles d’horloge quand une nouvelle donnée issue de la conversion est disponible sur le bus data_out[11..0] ;
  • saddr : signal de sélection de l’entrée analogique (les 3 bits ADD2, ADD1 et ADD0) à diriger vers l’entrée DIN du convertisseur A/N ;
  • sclk : le signal d’horloge vers le convertisseur A/N pour la synchronisation.

Sur la figure ci-dessous, on montre schématiquement l’implémentation du bloc spi_interface du module dans la puce FPGA, ainsi que le raccordement des entrées et sorties au convertisseur A/N.

Image non disponible

V-A. Déclaration du module

En Verilog, on déclare le module comme suit :

spi-interface.v
Sélectionnez
module spi_interface
    #( 
        parameter [2:0] ADD = 3'b101    // <ADD2><ADD1><ADD0> = 101, soit Analog Input 5
    )

    (
        input clk,                      // horloge jusqu'à 3,2MHz maxi
        input sdat,                     // ADC Digital Data Output
        
        output reg saddr,               // ADC Digital Data Input
        output wire sclk,               // ADC Digital Clock Input
        output wire cs_n,               // ADC Chip Select
        output wire [11:0] data_out,    // Résultat de la conversion 12 bits
        output wire end_of_conversion   // Impulsion synchro. End Of Conversion
    );
    
    
// ---------------------------------------------------------------------------

    // corps du module ici...
    
endmodule

Le paramètre ADD[2..0] contient le numéro de l’entrée analogique. Quand on instancie le module, l’entrée par défaut est la n° 5 (en binaire, ADD = 3'b101 ).

On complète le module avec ses premiers signaux générés en sortie :

spi_interface.v
Sélectionnez
module spi_interface
    #( 
        parameter [2:0] ADD = 3'b101    // <ADD2><ADD1><ADD0> = 101, soit Analog Input 5
    )

    (
        input clk,                      // horloge jusqu'à 3,2MHz maxi
        input sdat,                     // ADC Digital Data Output
        
        output reg saddr,               // ADC Digital Data Input
        output wire sclk,               // ADC Digital Clock Input
        output wire cs_n,               // ADC Chip Select
        output wire [11:0] data_out,    // Résultat de la conversion 12 bits
        output wire end_of_conversion   // Impulsion synchro. End Of Conversion
    );
    
    
// ---------------------------------------------------------------------------

    reg [3:0] counter;  // compteur 4 bits interne
    reg cs = 1'b0;
    
    initial begin
        counter = 0;
    end
    
    assign cs_n = ~cs;

    assign sclk = cs_n ? 1'b1 : clk;


    always @(negedge clk) begin
        if (counter == 4'd1) begin
            cs <= 1'b1;   // active Chip Select
        end
    end


    always @(posedge clk) begin 
        counter <= counter + 4'd1;
    end


endmodule
  • Un compteur 4 bits counter démarre à zéro et s’incrémente à chaque front montant de l’horloge.
  • Le signal /CS sera abaissé sur front descendant de l’horloge, lorsque le compteur sera égal à 1. Comme il n’est jamais remonté à l’état haut, il restera abaissé en permanence (verrou).
  • L’assignation assign sclk = cs_n ? 1'b1 : clk; permet de produire le signal d’horloge à partir du moment où le signal /CS est abaissé (grâce à l’opérateur ternaire condition ? Valeur_si_vrai : Valeur_si_faux, comme en langage C).

V-B. Les bancs de test (ou test benches)

Pour tester le fonctionnement de cette première version du module, on peut faire une simulation fonctionnelle. La simulation permet de vérifier le comportement du circuit logique qui sera implémenté plus tard dans la puce FPGA en réponse à des stimuli en entrée, sans avoir recours à la cible FPGA matérielle. Elle est très utilisée pour détecter les erreurs de conception ou pour optimiser les performances.

Afin d’éprouver ou optimiser un module par simulation, on a besoin de le tester avec différents scénarios, en générant et traitant des signaux et stimuli un peu complexes. On peut alors recourir à des « bancs de test » ou test benches.

Notre test bench sera créé dans un module testbench.v qui va instancier le module à tester spi_interface, raccorder les signaux et stimuler les entrées :

testbench.v
Sélectionnez
`timescale 10ns/100ps
// unité de temps = 10ns, résolution = 100ps

module testbench;

    reg clock;
    wire sclk_sig;
    wire cs_n_sig;
    
    reg  sdat_sig;
    wire [11:0] data_out_sig;
    wire  end_of_conversion_sig;
    wire saddr_sig;
    
    localparam period = 100;  //période = 100 unités de temps, soit 100 x 10ns = 1us

    initial begin
        clock = 1'b0;
        sdat_sig = 1'b0;
    end

    always begin
        #(period/2) clock = ~clock; // génération signal d'horloge
    end

    spi_interface spi_inst
    (
        .clk(clock),
        .sclk(sclk_sig),
        .cs_n(cs_n_sig),
        .sdat(sdat_sig),
        .data_out(data_out_sig),
        .end_of_conversion(end_of_conversion_sig),
        .saddr(saddr_sig)
    );
    
endmodule

On stimule l’instance avec un signal d’horloge simulé (qui n’est pas à la fréquence souhaitée d’ailleurs, mais cela n’a pas d’importance ici, il s’agit uniquement de voir si les événements sont bien synchronisés sur les fronts de l’horloge), et on espère visualiser des signaux en sortie afin de les comparer aux signaux attendus.

Le projet comporte déjà deux fichiers sources en Verilog : testbench.v et spi_interface.v. C’est notre « banc de test » qui doit prendre le contrôle, pour cela, faites un clic droit sur testbench.v dans le navigateur à gauche, puis sélectionnez l’option Set as Top-Level Entity.

Image non disponible

Vous devez ensuite cliquer sur le bouton Start Analysis & ElaborationImage non disponible de la barre d’outils, ce qui va lancer l’analyse du code source et l’élaboration d’un modèle fonctionnel du circuit logique.

Image non disponible
Message : Analysis & Elaboration was successful

Ne vous inquiétez pas des avertissements (warnings) à cette étape, notre interface SPI et son « banc de test » sont encore incomplets avec des signaux non connectés. Ce qui est important, c’est d’avoir le message « Quartus Prime Analysis & Elaboration was successful. 0 errors… ». Si vous avez des erreurs signalées avec des messages en rouge, il faudra analyser les messages d’erreur et débusquer les bogues. Sans doute un oubli ou une erreur de syntaxe…

Un test bench est prévu pour être simulé, et non pour être synthétisé, c.-à-d qu’il ne peut pas être traduit en termes de circuit matériel implémenté physiquement dans la puce FPGA. La partie du code qui génère le signal d’horloge par exemple n’est pas « synthétisable ». Même si tout est décrit en langage Verilog, la sémantique du code est bien différente selon l’objectif : simulation logicielle ou synthèse pour implémentation dans le FPGA.

V-C. Simulation fonctionnelle

On utilisera ici le simulateur ModelSim qui est fourni avec Quartus Prime Lite, mais il y en a d’autres.

Commencez par vérifier l’outil de simulation et sa configuration en suivant les étapes ci-dessous :

Menu Tools → Options…

Image non disponible

Menu Assignments → Settings…
Catégorie EDA Tool Settings

Image non disponible

Menu Assignments → Settings…
Catégorie EDA Tool Settings → Simulation
Il faut renseigner ici le fichier testbench.v à prendre en compte lors du lancement de la simulation.

Image non disponible

Pour démarrer la simulation, allez dans le menu Tools → Run Simulation Tool → RTL Simulation.

ModelSim devrait s’ouvrir et exécuter automatiquement le test bench renseigné. En zoomant sur les premières étapes de la simulation dans la fenêtre Wave, les chronogrammes montrent les résultats espérés :

Image non disponible
  • /testbench/clock, en vert, est le signal d’horloge (stimulus).
  • /testbench/cs_n_sig, en jaune, est le signal /CS (Chip Select) et il est bien abaissé au cycle d’horloge suivant (lorsque le compteur counter est égal à 1) sur front descendant de l’horloge.
  • /testbench/sclk_sig, en rouge, est bien le signal d’horloge SCLK envoyé au convertisseur A/N. Au repos à l’état haut, l’horloge démarre quand /CS est abaissé.

Exercice


L’entrée analogique sélectionnée est ADD = 3'b101, soit l’entrée n° 5 par défaut. Complétez le code spi_interface.v avec un nouveau processus always de façon à produire le signal saddr de sélection de l’entrée analogique. On commence par le bit ADD[2] de poids fort qui doit être présenté sur front descendant de l’horloge lorsque le compteur counter est égal à 3.

Le résultat attendu en simulation doit être conforme aux chronogrammes suivants :

Image non disponible
Signal saddr_sig avec la séquence 3'b101 de sélection de l'entrée analogique

Solution :

spi_interface.v
Cacher/Afficher le codeSélectionnez

Les simulations sur notre « banc de test » montrent de premiers résultats concluants, mais le travail n’est pas encore abouti, car le module sous test du fichier spi_interface.v reste à compléter.

Mais ici, il est plus pertinent de poursuivre en implémentant physiquement le projet dans la puce FPGA pour voir comment réagit réellement le convertisseur A/N aux signaux d’entrées.

On reviendra alors au « banc de test » au chapitre Le « banc de test » completLe « banc de test » complet.

VI. Description du projet par schéma-blocs

Le projet à implémenter peut être décrit par schéma-blocs.

Allez dans le menu File → New… → Block Diagram/Schematic File, puis sauvegardez (File → Save As…) sous le nom Block-adc-project4dvp.bdf (Block Diagram File).

Faites un clic droit sur le fichier spi_interface.v dans le navigateur, puis sélectionnez Create Symbol Files for Current File :

Image non disponible

Le message « Quartus Prime Create Symbol File was successful. 0 errors, 0 warnings » devrait apparaître dans la console.

Double-cliquez dans la fenêtre de travail du schéma-blocs pour aller chercher le symbole de l’interface SPI et déposez-le dans la fenêtre de travail :

Image non disponible

L’horloge principale provient d’un oscillateur 50 MHz, mais le convertisseur A/N se synchronise avec un signal d’horloge qui ne doit pas dépasser 3,2 MHz. Il faut mettre en œuvre un circuit spécialisé appelé PLL (Phase-Locked Loop ou boucle à verrouillage de phase) pour asservir la fréquence de sortie à une fraction de la fréquence d’entrée.

Pour cela, allez dans le catalogue des IP (Intellectual Property) qui comprend le composant ALTPLL répondant au besoin :

Image non disponible

Le composant est configurable grâce à un assistant. Il vous suffit de renseigner les différents onglets comme décrit ci-dessous, en laissant les options non mentionnées à leur valeur par défaut :

La fréquence d’entrée du bloc est de 50 MHz.

Image non disponible

Le bloc ne comportera qu’une seule entrée, le signal inclk0. En sortie, le signal c0 sera le signal d’horloge à une fréquence réduite. Il faut donc décocher les options en surbrillance proposées par défaut.

Image non disponible

On veut en sortie une fréquence réduite à 3,2 MHz. L’assistant calcule le facteur de réduction de la fréquence = 8 / 125.

Image non disponible

Il faut générer le symbole du composant pour pouvoir le déposer à la souris dans la description par schéma-blocs.

Image non disponible

En double-cliquant à nouveau dans la fenêtre de travail, on peut aller chercher le composant PLL et déposer son symbole :

Image non disponible

Pour les symboles des entrées-sorties, il y a une icône dédiée dans la barre d’outils :

Image non disponible

Il reste à déposer les symboles des entrées et sorties et faire les connexions à la souris :

Image non disponible

L’entrée raccordée au signal inclk0 du composant PLL doit être nommée CLOCK_50 pour être dirigée vers le signal d’horloge 50 MHz conformément au Pin Planner. Les trois sorties raccordées au bloc de l’interface SPI doivent être nommées ADC_SADDR, ADC_SCLK et ADC_CS_N pour être dirigées vers les entrées du convertisseur A/N. Enfin, l’entrée ADC_SDAT d’où arrivent les données en sortie du convertisseur A/N doit être raccordée en entrée du bloc de l’interface SPI.

Avant de lancer la compilation, il faut penser à mettre le schéma-bloc comme composant maître du projet. Faites un clic droit sur le fichier Block-adc-project4dvp.bdf du schéma-blocs et sélectionnez Set as Top-Level Entity.

Vous pouvez maintenant lancer la compilation du projet en cliquant sur le bouton Start CompilationImage non disponible. La console devrait finir par afficher le message :

« Quartus Prime Full Compilation was successful. 0 errors, 22 warnings »

Ne tenez pas compte des warnings, ils ne sont pas critiques et toutes les entrées-sorties ne sont pas renseignées.

De nouvelles étiquettes PIN_XXX apparaissent sur le schéma-blocs et montrent que les noms symboliques des entrées-sorties ont bien été identifiés conformément au Pin planner :

Image non disponible

Le menu Tools → Net List Viewers → RTL Viewer permet de rendre compte de la structure actuelle du projet sous forme de schéma logique :

Image non disponible

VII. Utilisation de l’analyseur logique intégré à Quartus Prime

Quartus Prime Lite intègre un outil nommé Signal Tap Logic Analyser permettant d’implémenter un module supplémentaire dans votre projet qui va se connecter aux signaux souhaités et faire remonter les données vers le PC de développement par l’interface JTAG puis le câble USB. On pourra ainsi visualiser les signaux sur son ordinateur comme le ferait un analyseur logique connecté aux signaux de la puce FPGA.

Allez dans le menu Tools → Signal Tap Logic Analyser. La fenêtre de l’outil s’ouvre :

Image non disponible

Double-cliquez au centre dans la zone où est indiqué « Double-click to add nodes ». Sélectionnez le filtre « SignalTap : pre-synthesis » et cliquez sur le bouton [List] :

Image non disponible

Sélectionnez les nœuds de l’interface SPI à analyser (cs_n, sclk, counter, saddr et sdat), puis cliquez sur le bouton [Insert].

Dans la partie « Signal Configuration », sélectionnez l’horloge principale CLOCK_50 et un échantillonnage 4K :

Image non disponible

Vous constatez aussi sur la copie d’écran ci-dessus, dans la partie Instance Manager, l’instance supplémentaire auto_signal_tap_0 à implémenter dans la puce FPGA. Cette instance est connectée aux signaux à analyser et gère leur remontée au PC de développement. Elle va consommer 610 éléments logiques et de la mémoire supplémentaire.

En lançant à nouveau la compilation avec le bouton Start CompilationImage non disponible, l’outil vous propose de sauver les données capturées par l’analyseur avec un fichier au format .stp (Signal TaP) lié au projet et que vous retrouverez dans le navigateur du projet.

Enfin, on branche la carte FPGA sur le port USB. Dans la partie « JTAG Chain Configuration », allez chercher l’interface USB-Blaster. La carte devrait être reconnue. Cliquez sur le bouton avec les trois points […] et sélectionnez le fichier binaire adc_project4dvp.sof du projet.
Transférez le binaire .sof (SRAM Object File) du projet dans la puce FPGA en cliquant sur le bouton Program DeviceImage non disponible.

Image non disponible

Pour lancer les acquisitions en continu et visualiser les signaux, cliquez sur le bouton Autorun AnalysisImage non disponible.

Vous devriez voir les graphes des signaux s’animer en continu au fur et à mesure des acquisitions.

Image non disponible

On peut voir ci-dessus en jaune l’évolution du compteur 4 bits counter entre 0 et 15. La séquence binaire 3’b101 de sélection de l’entrée analogique n°5 apparaît en bleu et le dernier chronogramme avec la séquence en vert semble montrer une réponse du convertisseur sur 12 bits. Si vous tournez le bouton du potentiomètre, vous devriez voir la réponse évoluer. Si vous tournez le bouton au maximum dans un sens et que vous voyez un signal sdat plat à l’état bas (soit, 0 en décimal) ou un signal plat à l’état haut pendant 12 cycles (soit, 4095 en décimal), c’est bon signe !

Il faut maintenant finaliser le projet en récupérant bit par bit cette valeur de conversion sur 12 bits pour la restituer sous forme de bus.

VIII. Module d’interface SPI au complet

On rajoute quelques signaux et processus supplémentaires pour compléter le module du fichier spi-interface.v. Les résultats sont présentés sous forme de chronogrammes obtenus à l’analyseur logique de Quartus Prime Lite en reprenant la démarche du chapitre précédent.

Le nœud dout_available fournit un signal à l’état haut pendant l’intervalle défini par le compteur (qui prend successivement les valeurs 5, 6, 7 …, 14, 15, 0) où le convertisseur A/N pousse les données utiles sur sa sortie dout :

 
Sélectionnez
    wire dout_available;    
    assign dout_available = ((counter >= 5 ) || (counter == 0)) && (~cs_n);
Image non disponible
Signaux dout et dout-available en vert

Le nœud end_of_conversion en sortie du module est une impulsion (tous les 16 cycles d’horloge) pour prévenir que la conversion A/N est terminée et que le résultat de la conversion est disponible.

 
Sélectionnez
    assign end_of_conversion = (counter == 4'd1) && (~cs_n);
Image non disponible
Signal end_of_conversion en rose

Et le plus important, voici le code qui permet de récupérer les bits du résultat de la conversion et de présenter le résultat 12 bits sur le bus data_out[11:0] en sortie du module :

 
Sélectionnez
    reg [11:0] data_temp;   // donnée temporaire 12bits
    always @(posedge clk) begin
        if (dout_available) 
                data_temp <= {data_temp[10:0], sdat};   
    end
    
    assign data_out = end_of_conversion ? data_temp : data_out;

À la ligne data_temp <= {data_temp[10:0], sdat};, on décale à gauche les bits d’un registre temporaire 12 bits data_temp[11:0] pour y insérer à droite le bit en cours (par concaténation : les bits 10 à 0 sont récupérés, et on ajoute le bit en cours à la fin). L’opération est répétée 12 fois, tant que dout_available est à l’état haut.

Quand la conversion est terminée, on dirige la valeur du registre temporaire vers la sortie data_out[11:0].

Image non disponible
data_out[11:0] en orange, la valeur est disponible sur l’impulsion du signal end_of_conversion en rose

En résumé :

Image non disponible
  • la séquence 3’b101 en bleu renseigne le convertisseur sur l’entrée analogique sélectionnée, ici la n°5 où est branchée le potentiomètre rotatif ;
  • le convertisseur A/N pousse les 12 bits du résultat sur sa sortie, soit ici : 1000 1011 1101 = 2237 en décimal ;
  • une impulsion end_of_conversion, en rose, signale la fin de la conversion, et la disponibilité du résultat sur le bus 12 bits data_out[11:0] en orange.

Le résultat 2237 est l’image de la tension (2237 / 212) x 3,3 = 1,80 V. Une mesure au voltmètre entre les broches du potentiomètre devrait confirmer que vous êtes dans le vrai.

Si vous aviez obtenu une valeur erronée, probablement due à un décalage sur la position d’un bit (les valeurs du chronogramme peuvent être affichées en binaire pour s’en rendre compte), vous auriez tôt fait de vous en apercevoir d’après les chronogrammes et de corriger ce décalage dans le code.

IX. Le « banc de test » complet

Comme dit précédemmentLes bancs de test (ou test benches), un « banc de test » complet doit éprouver le module du fichier spi_interface.v. Et puisque les données poussées par le convertisseur A/N sont en entrée du module sous test, il faut simuler ces données.

On propose de compléter le test bench de la façon suivante :

testbench.v
Sélectionnez
`timescale 10ns/100ps
// unité de temps = 10ns, résolution = 100ps

module testbench;

    reg clock = 1'b0;
    wire sclk_sig;
    wire cs_n_sig;
    
    reg  sdat_sig;
    wire [11:0] data_out_sig;
    wire  end_of_conversion_sig;
    wire saddr_sig;
    
    localparam period = 100;  //période = 100 unités de temps, soit 100 x 10ns = 1us
    
    integer i;


    always begin
        #(period/2) clock = ~clock; // génération signal d'horloge
    end

    spi_interface spi_inst
    (
        .clk(clock),
        .sclk(sclk_sig),
        .cs_n(cs_n_sig),
        .sdat(sdat_sig),
        .data_out(data_out_sig),
        .end_of_conversion(end_of_conversion_sig),
        .saddr(saddr_sig)
    );
    
    
    task generate_adc_value(input [11:0] v);
    // simulation des signaux en sortie dout du convertisseur A/N
        begin                   
            #period     sdat_sig = v[11];   // DB11
            #period     sdat_sig = v[10];   // DB10
            #period     sdat_sig = v[9];    // DB9
            #period     sdat_sig = v[8];    // DB8
            #period     sdat_sig = v[7];    // DB7
            #period     sdat_sig = v[6];    // DB6
            #period     sdat_sig = v[5];    // DB5
            #period     sdat_sig = v[4];    // DB4
            #period     sdat_sig = v[3];    // DB3
            #period     sdat_sig = v[2];    // DB2
            #period     sdat_sig = v[1];    // DB1
            #period     sdat_sig = v[0];    // DB0
            
            #period     sdat_sig = 1'b0;
            #(3*period) ;   
        end
    endtask
    
        
    initial begin
        sdat_sig = 1'b0;
        
        #(4*period) // temporiser pendant 4 périodes
                
        for (i = 0; i < 4096; i = i + 1) begin
            generate_adc_value(i);
        end
                
    end

endmodule

Une procédure generate_adc_value (tasken Verilog) permet de générer le signal série d’une valeur v de taille 12 bits passée en paramètre selon le timing décrit par la documentation du convertisseur A/N.

Par exemple, la ligne #period sdat_sig = v[11]; signifie qu’après un délai de 100 unités de temps correspondant à la période de l’horloge (period = 100), le bit 11 de v est « écrit » sur la ligne du signal sdat_sig. Et si on poursuit à la ligne suivante, le bit 10 de v sera « écrit » au cycle d’horloge suivant, etc.

Les quelques lignes ci-dessous du test bench constituent le scénario de test, très simple pour commencer :

 
Sélectionnez
        for (i = 0; i < 4096; i = i + 1) begin
            generate_adc_value(i);
        end

On simule ici une rampe de tension entre 0 et 3,3 V (soit une valeur retournée entre 0 et 212 - 1) :

Image non disponible
Simulation complète sous ModelSim

La simulation obtenue ci-dessus montre un signal de sortie sur le bus data_out_sig en rouge avec les valeurs attendues.

À vous de voir s’il faut un scénario de test plus complet avec d’autres valeurs, des valeurs aléatoires, etc. Les simulations sont aussi faites pour éprouver des modules avec des scénarios difficiles à reproduire physiquement.

X. Finalisation du projet : visualisation avec le bandeau de LED

On finira ce projet avec un bargraphe composé des huit LED de la carte qui évolue en fonction de la rotation du bouton du potentiomètre.

On ajoute un nouveau module au projet dans le fichier led_interface.v. En fonction de la valeur 12 bits issue de la conversion A/N, le module produira une valeur 8 bits dirigée vers les LED (bus LED[7:0]). Le schéma ci-dessous montre en surbrillance ce nouveau module sous forme de bloc et ses connexions avec les autres blocs :

Image non disponible

Le code du module led-interface.v est très simple :

led_interface.v
Sélectionnez
module led_interface
    (
        input clk,
        input new_data,
        input wire [11:0] data,
        output reg [7:0] led_out    
    );

    always @(posedge clk) begin
        if (new_data) begin
            led_out[0] <= (data >= 256);
            led_out[1] <= (data >= 768);
            led_out[2] <= (data >= 1280);
            led_out[3] <= (data >= 1792);
            led_out[4] <= (data >= 2304);
            led_out[5] <= (data >= 2816);
            led_out[6] <= (data >= 3328);
            led_out[7] <= (data >= 3840);
        end
    end
    
endmodule

Une fois le fichier du module sauvegardé, faites un clic droit sur le fichier led_interface.v dans le navigateur, puis sélectionnez Create Symbol Files for Current File. Il reste à déposer le symbole dans le schéma-blocs et faire les connexions à la souris comme indiqué ci-dessous.
La sortie vers le bandeau de LED doit être nommée LED[7..0] :

Image non disponible

Relancez la compilation, et transférez le binaire soit à nouveau depuis l’analyseur logique, soit depuis le menu Tools → Programmer.

XI. Conclusion

Beaucoup de choses ont été vues dans ce petit projet qui est un bon prétexte pour étudier la démarche de conception d’un projet FPGA. On a commencé par la description (le design) du projet de façon architecturale ou comportementale avec un langage HDL (Hardware Description Language) tels que Verilog ou VHDL. Quartus Prime permet également d’élaborer des descriptions de façon graphique, notamment par schéma-blocs.

La phase de compilation d'un projet FPGA consiste à transformer la description à haut niveau écrite par le concepteur en un fichier binaire qui peut être transféré sur le circuit programmable. Cette phase largement automatisée comprend plusieurs étapes, telles que l’analyse, la synthèse logique, jusqu’au placement et routage du modèle structurel sur FPGA, pour finalement générer le bitstream, le fichier binaire qui contient toutes les informations pour configurer la puce FPGA.

Image non disponible
Flot de conception simplifié d'après Intel « DE0-Nano User Manual »

Comme vous pouvez le voir sur le schéma précédent, la démarche n’est pas linéaire et le concepteur devra souvent revenir sur ses pas pour corriger ou optimiser la conception jusqu’à obtenir les fonctionnalités et les performances attendues.

Parmi les outils à disposition du concepteur pour la mise au point, le débogage et l’optimisation :

  • le simulateur logiciel ModelSim : les simulations fonctionnelles permettent de vérifier la conformité des signaux aux spécifications sans avoir recours à la cible FPGA matérielle. Tous les signaux et stimuli sont générés par logiciel pour reproduire les conditions réelles. Le module sous test est soumis à ces stimuli et l’on observe les signaux en sortie. Un « banc de test » ou test bench permet de mettre en Å“uvre des scénarios de tests parfois complexes pour prendre tous les cas en considération ;
  • l’outil Signal Tap Logic Analyzer : cet outil intégré à Quartus Prime permet d’implémenter dans la puce FPGA un modèle physique d’analyseur logique avec des sondes reliées aux signaux à vérifier. Les données des sondes seront remontées jusqu’au PC de développement par le câble USB afin d’être collectées et observées sous forme de chronogrammes.

Si la mise au point de votre projet s’est bien déroulée jusque-là, sachez toutefois que certaines situations sont plus complexes. Le concepteur d’un projet arrivera toujours à faire une simulation qui répond aux spécifications, mais il peut arriver que le projet synthétisé ne se comporte pas comme la simulation fonctionnelle.

Cela est dû en général à de mauvaises pratiques de conception. Le cas classique : des signaux non initialisés ou indéterminés à certains moments et prenant des valeurs par défaut en simulation, mais ces signaux peuvent prendre des états arbitraires une fois le projet synthétisé.

Dans d’autres cas, les simulations ne rendent pas compte de problèmes d’implémentation ou liés à l’environnement réel : des incohérences d’horloge, la présence de délais engendrés par les signaux qui traversent de nombreux composants logiques et autres défauts dans le timing, des glitches, des problèmes de routage, de métastabilité, de conflits de ressources, etc. D’autres outils seront alors nécessaires pour résoudre ces problèmes.


Pour conclure, je remercie les membres de developpez pour leur travail de relecture de cet article et leurs propositions d’amélioration, en particulier : laurent_ott, User, LittleWhite et Escartefigue.

XII. Annexe : fichiers sources

Archive du projet de ce tutoriel au format Quartus Archive, à importer directement depuis Quartus Prime (version 20.1.1 Lite Edition) : spi-adc12bits.qar

_______________

spi_interface.v
Cacher/Afficher le codeSélectionnez
led_interface.v
Cacher/Afficher le codeSélectionnez
testbench.v
Cacher/Afficher le codeSélectionnez

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2023 f-leb. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.