Les premières expériences utilisant le protocole de streaming sur l'exemple de communication CPU et processeur dans le FPGA Redd



Dans les articles précédents, nous avons déjà rencontré le bus Avalon-MM , où MM signifie Memory Mapped, c'est-à-dire projeté sur la mémoire. Ce pneu est assez polyvalent. Plusieurs appareils maître (maître) et plusieurs appareils esclaves peuvent y être connectés. Nous avons déjà connecté deux principaux appareils à la fois (Instruction Master et Data Master), car le processeur NIOS II a une architecture Harvard, donc les bus de commande et de données sont différents, mais de nombreux auteurs les connectent au même commun pour simplifier le développement logiciel de l'extérieur au bus.

Si un bloc sur le bus a une fonctionnalité d'accès direct à la mémoire (DMA), il contiendra également un maître pour le bus.

En fait, le principal inconvénient de ce pneu est basé sur ce fait (de nombreux leaders, de nombreux adeptes). Lorsque nous avons conçu notre esclave, nous avons dû décoder l'adresse. Quand il m'est arrivé de faire mon chef, il y avait beaucoup plus de bruit avec l'arbitrage. Mais un fil rouge à travers toute la série d'articles est l'affirmation selon laquelle le développement sous Redd est une partie auxiliaire du projet, il ne devrait pas nécessiter trop de travail. Et si nous pouvons nous libérer de la routine, nous devons nous en libérer.



Tous les articles du cycle:

  1. Développement du «firmware» le plus simple pour les FPGA installés dans Redd, et débogage en utilisant le test de mémoire comme exemple
  2. Développement du «firmware» le plus simple pour les FPGA installés dans Redd. Partie 2. Code de programme
  3. Développement de son propre noyau pour l'intégration dans un système de processeur basé sur FPGA
  4. Développement de programmes pour le processeur central Redd sur l'exemple d'accès au FPGA

Le document Avalon Interface Specifications que nous connaissons déjà (en général, je ne donne pas de liens directs, car ils changent toujours, de sorte que l'ensemble du réseau est jonché d'articles avec des liens morts, il est plus facile de trouver la position actuelle en insérant le nom dans le moteur de recherche) signale qu'en plus du bus Avalon-MM , il y a aussi un bus Avalon-ST , où ST signifie Stream, c'est-à-dire le streaming. Le fait est que très souvent les données transmises ont une structure de flux. Oui, même le secteur classique du disque dur. Il a une taille fixe. Il doit être transmis du début à la fin. Même si nous le considérons dans la zone adressable, les adresses augmenteront linéairement. Et si vous utilisez le bloc FIFO pour le stockage, les adresses qu'il contient nous sont complètement cachées. Ils le sont, mais travailler avec eux n'est pas notre préoccupation.

La même chose s'applique à de nombreuses autres données de streaming: elles vont toujours du début à la fin, étant placées dans des référentiels de manière séquentielle. C'est exactement ce que les protocoles de streaming sont utilisés pour transférer ces données. Outre le manque d'adressage explicite, le bus Avalon-ST est intéressant en ce qu'il relie toujours deux appareils: une source et un récepteur. Il y en a toujours deux. Un appareil est toujours la source, le second est toujours le récepteur. Par conséquent, les problèmes d'arbitrage de ce bus ne concernent pas. Voici à quoi ressemblent des paires typiques d'appareils connectés à ce bus:



Et voici les signaux typiques de ce bus:



De plus, les lignes d' erreur sont optionnelles, elles transmettent des codes d'erreur binaires attribués par nous, et on peut dire qu'il n'y a pas de code d'erreur. Et les lignes de numéro de canal, comme nous l'avons vu ci-dessus, ne sont nécessaires que si le démultiplexage est effectué plus loin. Sinon, le numéro de chaîne n'est pas nécessaire. Nous allons nous en passer pour l'instant. Il reste trois lignes: en fait, des données, un signal prêt et un signal de confirmation de données (stroboscope). Eh bien, un autre signal d'horloge, car le bus est synchrone.

De la documentation, il résulte également que trois signaux supplémentaires sont possibles, ajoutant au bus les propriétés de transmission de paquets clairement définis:



En général, le pneu est très intéressant, et aujourd'hui nous allons commencer des expériences avec lui. Comme nous le savons déjà, le FPGA est connecté au bus USB du complexe Redd via le pont FT2232H fonctionnant en mode FT245-SYNC . À strictement parler, les données qui transitent par cette interface sont assez des données en continu. Aujourd'hui, nous apprendrons comment transférer ces données vers notre système de processeur basé sur NIOS II. Il est dommage que le protocole FT245-SYNC , bien qu'en streaming, ne soit pas totalement conforme au bus Avalon-ST . Pour économiser les circuits intégrés, il dispose d'un bus de données bidirectionnel et le bus Avalon-ST est unidirectionnel. Donc, nous devons faire un bloc qui coordonne des protocoles proches mais ne correspondant pas.

Nous avons déjà fait la connaissance du protocole FT245-SYNC dans l'un des articles précédents . Permettez-moi de vous rappeler que sa description se trouve dans le document AN_130 FT2232H utilisé dans un mode FIFO synchrone de style FT245 . Voici un chronogramme typique de transmission d'un pont à un FPGA



En général, en tant que programmeur, je suis très intéressé par le fait que le paquet transmis aurait clairement marqué début et fin. Eh bien, pour le rendre plus logique dans la logique du protocole UDP, car si la transmission est dans le style TCP, vous devrez ajouter des données de référence spéciales au flux, qui seront dépensées pour ma programmation, mes efforts et mes cycles de processeur ... Cela ressemble à la ligne RXF peut nous aider. On vérifie ... On remplit le "firmware" du FPGA pour mesurer les performances, fait dans l' article précédent , et on connecte la sonde de l'oscilloscope à la ligne RXF. En tant que programme de test pour le processeur central Redd, nous utilisons la base de données, également utilisée pour mesurer les performances, juste au lieu d'envoyer de grandes quantités de données, nous envoyons un bloc monolithique de 0x400 octets.

uint8_t temp [maxBlockSize]; memset (temp,0,sizeof (temp)); uint32_t dwWritten; FT_Write(ftHandle0, temp, 0x400, &dwWritten); 

Nous obtenons l'image suivante sur la ligne RXF:



Il est clair que le microcircuit reçoit 0x200 octets de tampon (c'est-à-dire combien il peut venir dans un paquet HS USB2.0), puis il les envoie au canal. En général, cela est étrange, car la documentation indique que deux tampons sont utilisés dans chaque direction. Pendant la transmission, le deuxième tampon aurait dû avoir le temps de se remplir. Hélas. La fin de son remplissage est clairement en retard. En fait, cela montre pourquoi les performances n'atteignent pas les 52 mégaoctets théoriques par seconde: un grand pourcentage du temps (mais pas 50%) n'est tout simplement pas transmis.

Mais d'une manière ou d'une autre, nous avons découvert qu'il est possible de détecter le début d'un paquet sur un front RXF négatif uniquement si la taille du paquet ne dépasse pas 0x200 octets. Si nous envoyons uniquement des commandes avec une petite quantité de données à l'appareil, cela est tout à fait réalisable. Mais si nous envoyons de grands flux de données, nous devrons utiliser un canal continu, similaire dans sa logique à UART (ou, disons, au canal TCP), mettant en évidence les limites des paquets uniquement par programme.

En général, pour la simplicité de la présentation, nous prenons la version en streaming comme base. Nous ne considérerons pas les packages aujourd'hui. Eh bien, la version du bus Avalon-ST que nous prenons comme base est claire. Nous commençons à concevoir notre bloc. Comme indiqué ci-dessus, nous devons faire non seulement un pont, mais un commutateur, car le bus FT245FIFO est bidirectionnel et le bus Avalon-ST est unidirectionnel. Autrement dit, il est nécessaire de créer deux bus Avalon-ST à la fois: sortie et entrée.



Nous commençons à développer lentement un automate qui mettra en œuvre la logique dont nous avons besoin. Bien sûr, dans l'article, cette logique sera simplifiée au maximum. Commençons par transférer des données du FPGA vers le PC, car ce processus est un peu plus simple (vous n'avez pas besoin de changer l'état de la ligne OE, dont nous avons parlé dans le dernier article ). Autrement dit, nous implémentons le port Sink.

Du côté du bus Avalon-ST , j'ai choisi le mode de fonctionnement suivant (il y en a beaucoup dans le document, mais pour l'interface avec le FT245-SYNC celui-ci est le plus proche):



Permettez-moi de vous rappeler la direction des signaux:



Autrement dit, nous attendons simplement la confirmation sur le bus ( valide ), cliquez sur les données et transmettez ce fait avec la ligne prête .

Du côté FT245_FIFO, le protocole ressemble à ceci:



Il s'avère que nous devons attendre le signal TXE et gate les données avec le signal WR # (la polarité est inverse pour ces deux signaux).

TXE # a une fonctionnalité très similaire à ready , et WR # est valide . Les détails sont un peu différents, mais la logique est similaire.

Il s'avère que nous pouvons isoler un seul état vers PC, dans lequel les commutations les plus simples de certaines lignes seront effectuées. La condition pour entrer dans cet état sera la disponibilité des deux parties pour la transmission, c'est-à-dire (TXE # == 0) ET (valide == 1). Dès qu'une partie de l'état de préparation a disparu, nous retournons au ralenti.

Le graphe de transition de l'automate est toujours simple:



Et la table de commutation est comme ceci (où les noms des signaux sont ambigus, des indices leur sont ajoutés, où les noms sont uniques - il n'y a pas d'indices):

SignalStatut ToPCAutres conditions
WR #NON (validSink)1
readySinkNON (TXE #)0
DATAFT245_FIFODataSinkZ


Passage à un transfert un peu plus complexe de Source vers FT245_FIFO. Comme nous l'avons vu dans l'article précédent , la complication est de changer de direction avec le signal OE #:



Pour le bus Avalon_ST, tout est le même qu'auparavant, donc les images ne sont pas affichées une deuxième fois, mais maintenant nous sommes à la position Source.

Ici, la ligne RXF # correspond à la ligne valide et la ligne RD # correspond à la ligne prête . Eh bien, très bien, ajoutez quelques états à la machine:



et la logique suivante pour les signaux actifs dans cet état:

SignaldropOEfromPCAutres conditions
OE #001
RD #1PAS (readySource)1
dataSourceN'importe quelle valeurDATAFT245_FIFON'importe quelle valeur
source valide0NON (RXF #)0

Il est clair que le schéma n'était pas le plus idéal. Il existe diverses nuances associées aux dépassements ou sous-dépassements de tampon. Mais il ne devrait pas y avoir de perte de données, mais en ce qui concerne l'optimalité, vous devez commencer quelque part!

Nous commençons à transférer la théorie développée au code SystemVerilog. Certes, nous ne pouvons pas utiliser toutes les fonctionnalités de SystemVerilog. Ce fut le cas, j'ai écrit un gros article où j'ai testé la synthétisabilité pratique des belles fonctionnalités de ce langage avec un véritable environnement de développement. Ici, nous demandons simplement l'utilisation d'interfaces, car le système aura deux instances de type Avalon-ST . Hélas et ah. Voici le code de test:
 interface AvalonST #(parameter width=8)(input clk); logic [width-1:0] data; logic ready; logic valid; modport source (input clk, ready, output data,valid); modport sink (input clk, data, valid, output ready); endinterface module FT245toAvalonST ( AvalonST.source source, AvalonST.sink sink ); //assign source.ready = sink.valid; assign sink.ready = source.valid; endmodule 

Il est parfaitement synthétisé dans le compilateur principal (une ligne commentée lors de la suppression d'un commentaire provoque une erreur pour s'assurer que le synthétiseur interprète tout correctement), mais lors de la vérification du bouton Analyser les fichiers de synthèse pour un composant de ce code, une erreur est générée que le type AvalonST est inconnu. Autrement dit, l'analyse n'est pas sur SystemVerilog, mais sur Verilog pur. Quel dommage.



De plus, la langue est déterminée correctement, seul l'analyseur ne comprend pas les interfaces entre les ports.



En général, vous devez utiliser la vieille syntaxe laide.

Avec cette syntaxe, nous obtenons l'interface de module suivante:
 module FT245toAvalonST ( input clk, input reset, inout [7:0] ft245_data, input logic ft245_rxf, input logic ft245_txe, output logic ft245_rd, output logic ft245_wr, output logic ft245_oe, output logic ft245_siwu, input logic source_ready, output logic source_valid, output logic[7:0] source_data, output logic sink_ready, input logic sink_valid, input logic[7:0] sink_data ); 


Rude, vintage, mais que pouvez-vous faire.

Nous réalisons le graphe de transition de l'automate sans fioritures:
 //    enum {idle, toPC, dropOE, fromPC} state = idle; //     always_ff @(posedge clk,posedge reset) begin if (reset == 1) begin state <= idle; end else begin case (state) idle: begin if ((ft245_txe == 0) && (sink_valid == 1)) state <= toPC; else if ((ft245_rxf == 0)&&(source_ready == 1)) state <= dropOE; end toPC: begin if (!((ft245_txe == 0) && (sink_valid == 1))) state <= idle; end dropOE: begin state <= fromPC; end fromPC: begin if (!((ft245_rxf == 0)&&(source_ready == 1))) state <= idle; end endcase end end 


Le contrôle des sorties nécessite cependant quelques explications.

Une partie des installations se fait «sur le front»:
 //    //   ,        , //  -    . always_comb begin ft245_oe <= 1; ft245_rd <= 1; ft245_wr <= 1; source_valid <= 0; sink_ready <= 0; //     , //     assign- case (state) idle: begin end toPC: begin ft245_wr <= !(sink_valid); sink_ready <= !(ft245_txe); end dropOE: begin ft245_oe <= 0; end fromPC: begin ft245_oe <= 0; ft245_rd <= !(source_ready); source_valid <= !(ft245_rxf); end endcase end 


Mais, disons, pour un bus de données bidirectionnel, une solution typique devrait être appliquée. Comme nous nous en souvenons, il est déclaré dans la partie interface comme suit:

  inout [7:0] ft245_data, 

et sa lecture peut se faire de la manière habituelle. Pour notre cas, nous emballons simplement toutes les données sur les données du bus Avalon-ST sortant:

 //          assign source_data = ft245_data; 

Mais en général, vous pouvez toujours lire dans le bus et comme vous le souhaitez. Mais vous devriez y écrire en utilisant le multiplexeur. Lorsque nous écrivons des données sur le bus, ces données doivent provenir de tout autre bus pré-préparé. Habituellement, dans un module, une variable de type reg (ou logique newfangled) est démarrée. Dans notre cas, un tel bus existe déjà. Il s'agit du bus sink_data . Dans d'autres cas, l'état Z est émis. Si vous connaissez les circuits, vous connaissez bien un tampon de sortie typique. Il saute toutes les données d'entrée ou passe à l'état Z. Dans notre code, ce multiplexeur ressemble à ceci:

 //      inout- assign ft245_data = (state == toPC) ? sink_data : 8'hzz; 

Et un autre signal ft245_siwu. Nous ne l'utilisons jamais, donc selon la documentation sur FT2232H, tirez-le à l'unité:

 //   FTDI : // Tie this pin to VCCIO if not used. assign ft245_siwu = 1; 

En fait, c'est tout.

L'ensemble du module ressemble à ceci:
 module FT245toAvalonST ( input clk, input reset, inout [7:0] ft245_data, input logic ft245_rxf, input logic ft245_txe, output logic ft245_rd, output logic ft245_wr, output logic ft245_oe, output logic ft245_siwu, input logic source_ready, output logic source_valid, output logic[7:0] source_data, output logic sink_ready, input logic sink_valid, input logic[7:0] sink_data ); //    enum {idle, toPC, dropOE, fromPC} state = idle; //     always_ff @(posedge clk,posedge reset) begin if (reset == 1) begin state <= idle; end else begin case (state) idle: begin if ((ft245_txe == 0) && (sink_valid == 1)) state <= toPC; else if ((ft245_rxf == 0)&&(source_ready == 1)) state <= dropOE; end toPC: begin if (!((ft245_txe == 0) && (sink_valid == 1))) state <= idle; end dropOE: begin state <= fromPC; end fromPC: begin if (!((ft245_rxf == 0)&&(source_ready == 1))) state <= idle; end endcase end end //    //   ,        , //  -    . always_comb begin ft245_oe <= 1; ft245_rd <= 1; ft245_wr <= 1; source_valid <= 0; sink_ready <= 0; //     , //     assign- case (state) idle: begin end toPC: begin ft245_wr <= !(sink_valid); sink_ready <= !(ft245_txe); end dropOE: begin ft245_oe <= 0; end fromPC: begin ft245_oe <= 0; ft245_rd <= !(source_ready); source_valid <= !(ft245_rxf); end endcase end // -  c  ,   ... //   FTDI : // Tie this pin to VCCIO if not used. assign ft245_siwu = 1; //      inout- assign ft245_data = (state == toPC) ? sink_data : 8'hzz; //          assign source_data = ft245_data; endmodule 


Comment inclure le module dans la liste des disponibles pour une utilisation dans le système de processeur, nous avons examiné en détail dans l'un des articles précédents , donc je montre simplement le résultat dans la figure. Je me souviens que pour y parvenir, j'ai dû ajouter deux bus AVALON-ST , un bus Conduit , extraire les signaux d'un bus AVALON-MM défini de manière erronée, et lorsqu'il n'y a plus un seul signal dans ce bus, il suffit de le supprimer. En cours de route, la figure montre les paramètres que j'ai sélectionnés pour les bus AVALON-ST (8 bits par symbole, pas d'erreur, le canal maximum est nul, la latence est nulle).



Avec le développement d'un module d'amarrage des pneus - c'est tout. Mais hélas, ah. Développer n'est que le début du travail. La mise en œuvre est beaucoup plus difficile. Comme le montre la position de la molette sur l'écran, la fin de l'article est encore loin. Nous commençons donc à créer un projet simple qui utilise le bus FT245-SYNC avec les bus AVALON-ST . C'est le plus simple. Un projet sérieux ne rentre pas dans le cadre d'un seul article de taille raisonnable. Je vais maintenant faire la simplification après la simplification simplement pour que l'attention des lecteurs soit suffisante pour le reste du texte afin qu'ils ne quittent pas la lecture en un mot. La première simplification est que les horloges à 60 MHz pour le FT245_SYNC sont générées par la puce FT2232H elle-même . Je pourrais ajouter deux lignes d'horloge au système, mais dès que tout le monde le verra, nous aurons de telles toiles d'araignées que ma mère ne pleurera pas. Si je fais toujours attention aux différentes lignes d'horloge, nous serons tous confus. Par conséquent, j'annonce simplement qu'aujourd'hui notre système de processeur horloge à partir de la puce FT2232H , et non à partir d'un générateur régulier.

Pourquoi ne pouvez-vous pas toujours faire ça? Très simple: tant que le FT2232H n'est pas en mode 245_SYNC, il n'a pas ces impulsions en sortie. Autrement dit, vous devez d'abord exécuter le programme pour le processeur central, puis seulement tout charger dans le FPGA. Si nous devions créer un système pour un client externe, une telle solution créerait beaucoup de problèmes. Je sais par expérience qu'ils nous téléphonaient régulièrement pour nous dire que rien ne fonctionnait, nous rappellerions les bars, mais cela aiderait pendant un certain temps. Mais nous faisons une chose interne, et nous ne l'utiliserons que dans des conditions de laboratoire. Autrement dit, dans le cadre de cette tâche, cela est permis.

Mais cela pose de nouveaux défis. Nous avons une fréquence de 60 MHz et le bloc d'horloge SDRAM que nous utilisons actuellement est étroitement lié à une fréquence de 50 MHz. Oui, j'ai vérifié, 60 peuvent être soumis, mais supposons que nous essayons de ne pas aller au-delà des modes autorisés. Dans les articles suivants, j'essaierai de montrer comment remplacer ce bloc dur, mais aujourd'hui, nous disons simplement que puisque notre unité d'horloge de SDRAM ne peut pas fonctionner sur la fréquence utilisée, nous l'excluons du système de processeur SDRAM. Le programme et ses données seront entièrement situés dans la mémoire interne du FPGA. Il a été constaté expérimentalement que dans la configuration actuelle, les FPGA peuvent prendre au maximum 28 kilo-octets de RAM pour cette activité. Il s'avère que vous pouvez prendre des volumes et non plusieurs pouvoirs de deux ...

De plus, nous utiliserons l'horloge standard et l'unité de réinitialisation. Il est réinitialisé un peu différemment de celui que nous avons utilisé pour la SDRAM. Afin de ne pas compliquer l'article, je vais profiter du fait que le système développé fonctionnera toujours sous le contrôle du débogueur, donc je vais commencer une réinitialisation à partir du sous-système JTAG pour le débogage.

Au total, nous obtenons une telle esquisse du système de processeur de base (la ligne de réinitialisation la plus difficile est actuellement mise en évidence, le marqueur bleu est sur la source du signal):



où la fréquence a été ajustée pour l'horloge et le bloc de réinitialisation:



et pour la RAM - le volume:



Aujourd'hui, nous devons afficher le texte dans le terminal. Par conséquent, nous allons ajouter un bloc intéressant au système:



Avec ce bloc, nous pourrons appeler des fonctions similaires à printf. En plus du bus AVALON_MM, il doit également connecter la sortie de demande d'interruption.



Voilà, l'approvisionnement pour le système de processeur est terminé. Il est temps d'intégrer notre unité. Où enverra-t-il les données? Parmi les blocs dont nous disposons, il existe une mémoire FIFO à deux ports très intéressante. Son charme réside dans le fait qu'un port peut être configuré en mode AVALON-ST et le connecter à notre unité, et le second en mode AVALON_MM et travailler avec lui en utilisant le processeur NIOS II. Ce magnifique bloc se trouve ici:



Nous avons deux bus Avalon-ST (un pour la lecture, l'autre pour l'écriture), nous avons donc également besoin de deux blocs FIFO. Maintenant, je vais passer en revue l'un d'eux en détail, nous enroulons quelques kilomètres de web (et un tas d'écrans de texte avec des images), et vers la seconde nous disons "cela peut être fait par analogie", indiquant seulement des différences. Par conséquent, pour l'instant, nous ajoutons un seul bloc au système et examinons ses paramètres. Il y a beaucoup de paramètres. On pourrait simplement indiquer les valeurs requises pour que tout le monde se réfère à l'article comme référence, mais soudain, quelqu'un se retrouve dans une situation qui doit être configurée, mais il n'y a pas d'accès au réseau (et donc à l'article). Par conséquent, je vais ajouter des paramètres de manière itérative. D'abord évident, alors - comme le système l'exige, parcourez le dialogue encore et encore. Ainsi, tout le monde ressentira le processus et pourra le répéter à tout moment. Alors. Par défaut, nous avons reçu les paramètres suivants:



Je vais maintenant faire FIFO, qui collecte les données d' Avalon-ST et les télécharge sur Avalon-MM . Il s'avère que le premier montage sera comme ceci:



J'ai reçu cet avertissement intéressant:



Il s'avère que lorsqu'au moins un des ports est projeté sur la mémoire, la largeur du bus Avalon-ST doit être strictement 32 bits. Et nous avons un bus 8 bits. Comment convenir des profondeurs de bits, je vais vous dire un peu plus bas, mais pour l'instant nous faisons ici un bus 32 bits avec un caractère huit bits. Eh bien, désactivez le mode batch, comme cela a été décidé dans la partie théorique.



Vient ensuite la capacité. Supposons que j'attende 256 mots (c'est-à-dire 1024 octets):



Maintenant, le statut. Au début, je n'y attachais aucune importance, et je me suis figé le programme. Alors maintenant, je sais que ce statut est nécessaire. Étant donné que nous travaillerons avec le port de sortie par programmation, nous ajoutons le statut correspondant.



et attraper l'erreur:



Eh bien. Ajoutez une double synchronisation. Connectez simplement les deux entrées à la même ligne d'horloge, car nous en avons une.
Uhhhh Au total, nous avons:



Mais il est trop tôt pour connecter cette entreprise au système commun. Comme nous l'avons découvert, le bus Avalon-ST 8 bits quitte le bloc que nous avons développé, et cela devrait inclure celui de 32 bits. Comment on va? Remodeler votre bloc? Non! Tout a été fait avant nous. Voici ce qui va nous aider:



Ajoutez-le au système. De plus, comme c'est une couche, purement pour la beauté, nous la plaçons entre notre bloc et FIFO, en utilisant la flèche correspondante:



Nous faisons les réglages suivants: sur l'entrée, nous avons un bus 8 bits, sur la sortie 32 bits. Les signaux de paquets ne sont pas utilisés, des signaux prêts et valides sont utilisés.



Il est temps de tisser une toile. Tout d'abord, je vais poser les lignes de streaming (dans la figure, elles sont toutes les deux mises en évidence, les marqueurs sont sur les récepteurs de données):



Autrement dit, le signal provenant de la source de notre bloc va à l'entrée de l'adaptateur. Et de la sortie de l'adaptateur à l'entrée FIFO. Comme je l'ai dit, toutes les connexions dans le protocole de streaming se font point à point.
Eh bien, maintenant nous suspendons les lignes de réinitialisation, les lignes d'horloge, et nous connectons également tout au bus système et aux interruptions ...



Eh bien ... Et maintenant, selon le même principe, nous ajoutons FIFO pour émettre des données vers le FT245SYNC . Seulement là, les données entrent dans le FIFO d' Avalon-MM sous forme 32 bits. Ils passent par un adaptateur 32-en-8 et arrivent ensuite à l'entrée SINK de notre bloc, qui n'est pas connectée dans le circuit actuel ... Nous obtenons le fragment suivant du circuit final (la mémoire s'est avérée avec une seule horloge):



Autres formalités que nous avons déjà bien travaillées dans les expériences décrites dans les articles précédents ( pour la plupart - dans celui-ci ). Nous assignons des vecteurs au processeur. Pour le système, nous appelons l'attribution automatique des numéros d'interruption et des adresses. Nous sauvons le système ... Tout le monde se souvient que le nom du système enregistré doit correspondre au nom du projet pour que le système soit au niveau supérieur de la hiérarchie? Ajoutez le système au projet, faites un brouillon du projet, attribuez des étapes. Personnellement, j'ai triché: j'ai copié les affectations du fichier * .qsf du projet de projet vers celui en cours de finition (et vous pouvez prendre mon projet et copier les lignes * .qsf correspondantes dans le vôtre, mais vous pouvez simplement assigner toutes les étapes via l'interface graphique). Je porte une attention particulière au fait que le signal clk est connecté à la jambe 23, pas 25, comme dans les projets précédents. Je vous rappelle qu'ici nous cochons à partir de la sortie FT2232.



Super! Le matériel est prêt. Nous passons au logiciel. Par où commencer? Aujourd'hui, cette question n'en vaut pas la peine. Si nous commençons avec un programme qui s'exécute sur le processeur NIOS II, rien ne fonctionnera pour nous. Tout d'abord, nous devons mettre le FT2232 en mode 245-SYNC, seulement alors notre système de processeur recevra des impulsions d'horloge. Par conséquent, nous commençons avec le code du processeur central.

Nous obtenons quelque chose comme ça:
 #include <cstdio> #include <sys/time.h> #include <unistd.h> #include "ftd2xx.h" FT_HANDLE OpenFT2232H() { FT_HANDLE ftHandle0; static FT_DEVICE ftDevice; //      int nDevice = 0; while (true) { //     if (FT_Open(nDevice, &ftHandle0) != FT_OK) { printf("No FT2232 found\n"); //  ,      return 0; } //     ? if (FT_GetDeviceInfo(ftHandle0, &ftDevice, NULL, NULL, NULL, NULL) == FT_OK) { // ,    if (ftDevice == FT_DEVICE_2232H) { // ,     AN130 FT_SetBitMode(ftHandle0, 0xff, 0x00); usleep(1000000); //Sync FIFO mode FT_SetBitMode(ftHandle0, 0xff, 0x40); FT_SetLatencyTimer(ftHandle0, 2); FT_SetUSBParameters(ftHandle0, 0x10000, 0x10000); return ftHandle0; } } //    FT_Close(ftHandle0); //    nDevice += 1; } printf("No FT2232 found\n"); } int main() { FT_HANDLE ftHandle0 = OpenFT2232H(); if (ftHandle0 == 0) { printf("Cannot open device\n"); return -1; } int item; bool bWork = true; while (bWork) { printf("1 - Send 16 bytes\n"); printf("2 - Send 256 bytes\n"); printf("3 - Receive loop\n"); printf("0 - Exit\n"); scanf("%d", &item); switch (item) { case 0: bWork = false; break; case 1: { static const unsigned char data[0x10] = { 0x00,0x01,0x02,0x03, 0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f }; DWORD dwWritten; FT_Write(ftHandle0, (void*)data, sizeof(data), &dwWritten); } break; case 2: { unsigned char data[0x100]; for (size_t i = 0; i < sizeof(data); i++) { data[i] = (unsigned char)i; } DWORD dwWritten; FT_Write(ftHandle0, (void*)data, sizeof(data), &dwWritten); } break; case 3: { DWORD dwRxBytes; DWORD dwRead; DWORD buf[0x100]; while (true) { FT_GetQueueStatus(ftHandle0, &dwRxBytes); if (dwRxBytes != 0) { printf("Received %d bytes (%d DWORDs)\n", dwRxBytes, dwRxBytes / sizeof(buf[0])); if (dwRxBytes > sizeof(buf)) { dwRxBytes = sizeof(buf); } FT_Read(ftHandle0, buf, dwRxBytes, &dwRead); for (DWORD i = 0; i < dwRxBytes / sizeof(buf[0]);i++) { printf("0x%X, ",buf[i]); } printf("\n"); } } } break; } } // ,    FT_Close(ftHandle0); return 0; } 


La fonction OpenFT2232H () nous est familière depuis le dernier article . C'est elle qui ouvre l'appareil FT2232 et le met dans le mode dont nous avons besoin. Immédiatement après le lancement réussi du programme, nous obtenons des impulsions d'horloge et, avec elles, la possibilité de déboguer le programme pour NIOS II. Eh bien, la fonctionnalité de la fonction principale est aussi simple qu'un tabouret. Envoyer des données (1), envoyer beaucoup de données (2), recevoir des données (3). Veuillez noter que toutes les données sont envoyées par blocs qui sont des multiples de quatre octets. Tout cela parce que nous avons un adaptateur 8 en 32. A la sortie de celui-ci, les données doivent aller en double mots. Sinon, tout est évident.

NIOS II BSP. , Hello World Small. BSP ( BSP, ). , , , Settings, .



Generate BSP , , hello_world_small.c hello_world_small.cpp , , .

( , FIFO, — , , ). . — NIOS II. :

 extern "C" { #include "sys/alt_stdio.h" #include <system.h> #include <altera_avalon_fifo_util.h> } #include <stdint.h> int main() { while (1) { int level = IORD_ALTERA_AVALON_FIFO_LEVEL(FIFO_0_OUT_CSR_BASE); if (level != 0) { alt_printf("0x%x words received:\n",level); for (int i=0;i<level;i++) { alt_printf("0x%x,",IORD_ALTERA_AVALON_FIFO_DATA (FIFO_0_OUT_BASE)); } alt_printf("\n"); } } /* Event loop never exits. */ while (1); return 0; } 

FIFO. , .
. , . Redd «» , NIOS II. :



Si vous avez le même, cela signifie que vous avez vraiment oublié de commencer à cadencer le système de processeur. Mais maintenant, vous savez l'identifier rapidement. Et pour éliminer, il est nécessaire et suffisant d'exécuter le programme que nous avons écrit pour le processeur central. Dès qu'il démarre et initialise le pont FT2232, les impulsions d'horloge iront à notre processeur, et il sera possible de répéter le processus de démarrage du débogage. De plus, le programme pour le processeur central à ce moment-là peut être terminé. Les impulsions d'horloge n'iront nulle part: le pont est déjà configuré pour le mode FT245-SYNC .

Dans le programme du processeur central, appuyez sur 1. Selon la situation, l'un ou l'autre apparaît dans le terminal:

0x2 mots reçus:
0x3020100,0x7060504,
0x2 mots reçus:
0xb0a0908,0xf0e0d0c,


ou:

0x3 mots reçus:
0x3020100,0x7060504,0xb0a0908,
0x1 mots reçus:
0xf0e0d0c,


, 1, 3 , . , , . , , JTAG — . , . , ( , ? ), (FIFO — , , ).

Little Endian. , :

 static const unsigned char data[0x10] = { 0x00,0x01,0x02,0x03, 0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f }; 

C'est vrai. Si vous sélectionnez l'élément 2 dans le programme du processeur central, un message apparaît (pour faciliter la lecture, les lignes sont formatées lors de la préparation de l'article):

 0x3 words received: 0x3020100,0x7060504,0xb0a0908, 0x3d words received: 0xf0e0d0c, 0x13121110,0x17161514,0x1b1a1918,0x1f1e1d1c, 0x23222120,0x27262524,0x2b2a2928,0x2f2e2d2c, 0x33323130,0x37363534,0x3b3a3938,0x3f3e3d3c, 0x43424140,0x47464544,0x4b4a4948,0x4f4e4d4c, 0x53525150,0x57565554,0x5b5a5958,0x5f5e5d5c, 0x63626160,0x67666564,0x6b6a6968,0x6f6e6d6c, 0x73727170,0x77767574,0x7b7a7978,0x7f7e7d7c, 0x83828180,0x87868584,0x8b8a8988,0x8f8e8d8c, 0x93929190,0x97969594,0x9b9a9998,0x9f9e9d9c, 0xa3a2a1a0,0xa7a6a5a4,0xabaaa9a8,0xafaeadac, 0xb3b2b1b0,0xb7b6b5b4,0xbbbab9b8,0xbfbebdbc, 0xc3c2c1c0,0xc7c6c5c4,0xcbcac9c8,0xcfcecdcc, 0xd3d2d1d0,0xd7d6d5d4,0xdbdad9d8,0xdfdedddc, 0xe3e2e1e0,0xe7e6e5e4,0xebeae9e8,0xefeeedec, 0xf3f2f1f0,0xf7f6f5f4,0xfbfaf9f8,0xfffefdfc, 

Tout est également vrai. Nous procédons à la vérification de la marche arrière. Nous remplaçons le programme pour NIOS II par ceci:

  /*  -  2 */ uint32_t buf[] = {0x11223344,0x55667788,0x99aabbcc,0xddeeff00}; for (uint32_t i=0;i<sizeof(buf)/sizeof(buf[0]);i++) { IOWR_ALTERA_AVALON_FIFO_DATA (FIFO_1_IN_BASE,buf[i]); } 

Nous sélectionnons le point 3 dans le programme pour le processeur central et exécutons cette version du programme pour NIOS II. Nous

obtenons

: 16 octets reçus (4 DWORD) 0x11223344, 0x55667788, 0x99AABBCC, 0xDDEEFF00, les

deux canaux fonctionnent approximativement. Et nous allons le vérifier une autre fois d'une manière ou d'une autre.

Conclusion


Avalon-ST . Redd , . . .

. , , Redd.

Source: https://habr.com/ru/post/fr462253/


All Articles