Travaux pratiques avec FPGA dans l'ensemble de Redd. MaĂźtriser le DMA pour le bus Avalon-ST et basculer entre les bus Avalon-MM

Nous continuons d'Ă©voluer vers la crĂ©ation de vrais appareils basĂ©s sur le complexe Redd FPGA. Pour un autre projet All-Hardware, j'ai besoin d'un simple analyseur logique, nous allons donc aller dans cette direction. Heureusement - et accĂ©dez Ă  l'analyseur de bus USB (mais c'est toujours Ă  long terme). Le cƓur de tout analyseur est la RAM et une unitĂ© qui tĂ©lĂ©charge d'abord les donnĂ©es et les rĂ©cupĂšre ensuite. Aujourd'hui, nous allons le concevoir.

Pour ce faire, nous maĂźtriserons le bloc DMA. En gĂ©nĂ©ral, le DMA est mon sujet prĂ©fĂ©rĂ©. J'ai mĂȘme fait un excellent article sur le DMA sur certains contrĂŽleurs ARM . De cet article, il est clair que le DMA prend des cycles d'horloge du bus. Dans l'article actuel, nous examinerons comment les choses se passent avec le systĂšme de processeur basĂ© sur FPGA.




Création de matériel


Nous commençons Ă  crĂ©er le matĂ©riel. Afin de comprendre comment le bloc DMA entre en conflit au cours des cycles d'horloge, nous devrons effectuer des mesures prĂ©cises Ă  haute charge sur le bus Avalon-MM (Avalon Memory-Mapped). Nous avons dĂ©jĂ  dĂ©couvert que le pont Altera JTAG-Avalon-MM ne peut pas fournir des charges de bus Ă©levĂ©es. Par consĂ©quent, nous devons aujourd'hui ajouter un cƓur de processeur au systĂšme afin qu'il accĂšde au bus Ă  grande vitesse. La procĂ©dure Ă  suivre a Ă©tĂ© dĂ©crite ici . Par souci d'optimalitĂ©, dĂ©sactivons les deux caches pour le cƓur du processeur, mais crĂ©ons un bus fortement connectĂ©, comme nous l'avons fait ici .



Ajoutez 8 kilo-octets de mĂ©moire de programme et de mĂ©moire de donnĂ©es. N'oubliez pas que la mĂ©moire doit ĂȘtre Ă  double port et avoir une adresse dans une plage spĂ©ciale (pour l'empĂȘcher de sauter, verrouillez-la, nous en avons discutĂ© les raisons ici ).



Nous avons dĂ©jĂ  crĂ©Ă© le projet mille fois, il n'y a donc rien de particuliĂšrement intĂ©ressant dans le processus de crĂ©ation lui-mĂȘme (au contraire, toutes les Ă©tapes pour le crĂ©er sont dĂ©crites ici ).

La base est prĂȘte. Maintenant, nous avons besoin d'une source de donnĂ©es que nous mettrons en mĂ©moire. L'idĂ©al est une minuterie qui tourne constamment. Si pendant une certaine mesure le bloc DMA n'a pas pu traiter les donnĂ©es, nous le verrons immĂ©diatement par la valeur manquante. Eh bien, c'est-Ă -dire que si en mĂ©moire il y a des valeurs 1234 et 1236, cela signifie que sur l'horloge, lorsque le temporisateur a Ă©mis 1235, le bloc DMA n'a pas transfĂ©rĂ© de donnĂ©es. CrĂ©er un fichierTimer_ST.sv avec un compteur aussi simple:

module Timer_ST (
  input              clk,
  input              reset,
	
  input  logic       source_ready,
  output logic       source_valid,
  output logic[31:0] source_data
	
);
    logic [31:0] counter;
    always @ (posedge clk, posedge reset)
    if (reset == 1)
    begin
        counter <= 0;
    end else
    begin
        counter <= counter + 1;
    end

    assign source_valid = 1;
    assign source_data [31:24] = counter [7:0];
    assign source_data [23:16] = counter [15:8];
    assign source_data [15:8] = counter [23:16];
    assign source_data [7:0] = counter [31:24];

endmodule

Ce compteur est comme un pionnier: il est toujours prĂȘt (Ă  la sortie source_valid est toujours un) et il compte toujours (sauf pour les moments de l'Ă©tat reset). Pourquoi le module a exactement ces signaux - nous avons discutĂ© dans cet article .

Nous créons maintenant notre propre composant (comment cela se fait est décrit ici ). Automation a choisi par erreur le bus Avalon_MM pour nous. Remplacez-le par avalon_streaming_source et mappez les signaux comme indiqué ci-dessous:



Excellent. Ajoutez notre composant au systÚme. Maintenant, nous recherchons le bloc DMA ... Et nous n'en trouvons pas un, mais trois. Tous sont décrits dans le document Embedded Peripheral IP User Guide d'Altera (comme toujours, je donne des noms, mais pas des liens, car les liens changent toujours).



Lequel utiliser? Je ne rĂ©siste pas Ă  la nostalgie. En 2012, j'ai crĂ©Ă© un systĂšme basĂ© sur le bus PCIe. Tous les manuels d'Altera contenaient un exemple basĂ© sur le premier de ces blocs. Mais lui avec le composant PCIe a donnĂ© une vitesse ne dĂ©passant pas 4 mĂ©gaoctets par seconde. À cette Ă©poque, j'ai crachĂ© et Ă©crit mon bloc DMA. Maintenant, je ne me souviens pas de sa vitesse, mais il a conduit les donnĂ©es des disques SATA Ă  la limite des capacitĂ©s des disques et SSD de l'Ă©poque. Autrement dit, j'ai aiguisĂ© une dent sur ce bloc. Mais je ne glisserai pas dans une comparaison des trois blocs. Le fait est qu'aujourd'hui nous devons travailler avec une source basĂ©e sur Avalon-ST (Avalon Streaming Interface), et seul le bloc DMA Modular Scatter-Gather prend en charge ces sources . Ici, nous le mettons sur le diagramme.

Dans les paramĂštres de blocage, sĂ©lectionnez le modeStreaming vers la mĂ©moire mappĂ©e . De plus - je veux conduire les donnĂ©es du lancement au remplissage de la SDRAM, j'ai donc remplacĂ© l'unitĂ© de transfert de donnĂ©es maximale de 1 kilo-octet Ă  4 mĂ©gaoctets. Certes, on m'a prĂ©venu qu'Ă  la fin, le paramĂštre FMax ne sera pas aussi chaud (mĂȘme si vous remplacez le bloc maximum par 2 kilo-octets). Mais pour aujourd'hui, FMax est acceptable (104 MHz), puis nous le dĂ©couvrirons. J'ai laissĂ© les paramĂštres restants inchangĂ©s. Vous pouvez Ă©galement dĂ©finir le mode de transmission sur Full Word Access Only, ce qui augmentera FMax Ă  109 MHz. Mais nous ne nous battrons pas pour la performance aujourd'hui.



Donc. La source est, DMA est. RĂ©cepteur ... SDRAM? Dans les futures conditions de combat, oui. Mais aujourd'hui, nous avons besoin d'une mĂ©moire aux caractĂ©ristiques connues. Malheureusement, la SDRAM doit envoyer pĂ©riodiquement des commandes qui prennent plusieurs cycles d'horloge, et cette mĂ©moire peut ĂȘtre occupĂ©e par la rĂ©gĂ©nĂ©ration. Par consĂ©quent, au lieu de cela, nous allons maintenant utiliser la mĂ©moire FPGA intĂ©grĂ©e. Tout fonctionne pour elle en une seule Ă©tape, sans retards imprĂ©visibles.

Étant donnĂ© que le contrĂŽleur SDRAM est Ă  port unique, la mĂ©moire intĂ©grĂ©e peut Ă©galement ĂȘtre utilisĂ©e exclusivement en mode port unique. C'est important. Le fait est que nous voulons Ă©crire dans la mĂ©moire Ă  l'aide du maĂźtre de bloc DMA, mais d'un autre cĂŽtĂ©, nous voulons lire Ă  partir de cette mĂ©moire en utilisant le cƓur du processeur ou le bloc Altera JTAG-to-Avalon-MM. La main tend la main et connecte les blocs d’écriture et de lecture Ă  deux ports diffĂ©rents ... Mais vous ne pouvez pas! Il est plutĂŽt interdit par les conditions du problĂšme. Parce qu'aujourd'hui c'est possible, mais demain nous remplacerons la mĂ©moire par une mĂ©moire exclusivement Ă  port unique. En gĂ©nĂ©ral, nous obtenons un tel bloc de trois composants (minuterie, DMA et mĂ©moire):



Eh bien, et juste pour le proforma, je vais ajouter UT et sysid au systÚme JTAG (bien que le second n'a pas aidé, je devais encore le conjurer avec un adaptateur JTAG). Ce que c'est et comment leur ajout résout de petits problÚmes, nous avons déjà étudié. Je ne teinterai pas les pneus, tout est clair avec eux. Montrez simplement à quoi tout cela ressemble dans mon projet:



c'est tout. Le systĂšme est prĂȘt. Nous attribuons des adresses, attribuons des vecteurs de processeur, gĂ©nĂ©rons un systĂšme (n'oubliez pas que vous devez enregistrer avec le mĂȘme nom que le projet lui-mĂȘme, puis il ira au niveau supĂ©rieur de la hiĂ©rarchie), l'ajouter au projet. Rendez la jambe de rĂ©initialisation virtuelle, connectez clk Ă  la jambe pin_25. Nous assemblons le projet, le versons dans le matĂ©riel ... Comment est-il, pauvre chose, dans le bureau vide Ă  cause de la distance totale? ... C'est solitaire et effrayant pour elle, probablement, seul ... Mais j'Ă©tais distrait.

Création d'une partie logicielle


EntraĂźnement


Dans l'Ă©diteur BSP avec le mouvement habituel de la main, j'active le support C ++. J'ai si souvent insĂ©rĂ© une capture d'Ă©cran de cette affaire que j'arrĂȘte de le faire. Mais une autre capture d'Ă©cran, bien qu'elle ait dĂ©jĂ  Ă©tĂ© vue, est toujours aussi courante. Alors discutons-en une fois de plus. Nous nous souvenons que le systĂšme essaie de mettre les donnĂ©es dans la plus grande mĂ©moire. Et tel est Buffer . Par consĂ©quent, nous forçons tout sur les donnĂ©es :



Expérience de programme


Nous faisons un code qui remplit simplement la mémoire avec le contenu de la source (dans le rÎle du compteur).

Afficher le code
#include "sys/alt_stdio.h"
#include <altera_msgdma.h>
#include <altera_msgdma_descriptor_regs.h>
#include <system.h>
#include <string.h>

int main()
{ 
  alt_putstr("Hello from Nios II!\n");

  memset (BUFFER_BASE,0,BUFFER_SIZE_VALUE);

  //  ,   
  IOWR_ALTERA_MSGDMA_CSR_CONTROL(MSGDMA_0_CSR_BASE,
      ALTERA_MSGDMA_CSR_STOP_DESCRIPTORS_MASK);

  //   ,      ,
  //    ,   .  .

  //    FIFO
  IOWR_ALTERA_MSGDMA_DESCRIPTOR_READ_ADDRESS(MSGDMA_0_DESCRIPTOR_SLAVE_BASE,
      (alt_u32)0);
  IOWR_ALTERA_MSGDMA_DESCRIPTOR_WRITE_ADDRESS(MSGDMA_0_DESCRIPTOR_SLAVE_BASE,
      (alt_u32)BUFFER_BASE);
  IOWR_ALTERA_MSGDMA_DESCRIPTOR_LENGTH(MSGDMA_0_DESCRIPTOR_SLAVE_BASE,
      BUFFER_SIZE_VALUE);
  IOWR_ALTERA_MSGDMA_DESCRIPTOR_CONTROL_STANDARD(MSGDMA_0_DESCRIPTOR_SLAVE_BASE,
      ALTERA_MSGDMA_DESCRIPTOR_CONTROL_GO_MASK);

   //  ,    
   IOWR_ALTERA_MSGDMA_CSR_CONTROL(MSGDMA_0_CSR_BASE,
       ALTERA_MSGDMA_CSR_STOP_ON_ERROR_MASK
       & (~ALTERA_MSGDMA_CSR_STOP_DESCRIPTORS_MASK)
       &(~ALTERA_MSGDMA_CSR_GLOBAL_INTERRUPT_MASK)) ;


   //   
   static const alt_u32 errMask = ALTERA_MSGDMA_CSR_STOPPED_ON_ERROR_MASK |
           ALTERA_MSGDMA_CSR_STOPPED_ON_EARLY_TERMINATION_MASK |
           ALTERA_MSGDMA_CSR_STOP_STATE_MASK |
           ALTERA_MSGDMA_CSR_RESET_STATE_MASK;

  volatile alt_u32 status;
  do
  {
     status = IORD_ALTERA_MSGDMA_CSR_STATUS(MSGDMA_0_CSR_BASE);
  } while (!(status & errMask) &&(status & ALTERA_MSGDMA_CSR_BUSY_MASK));     

  alt_putstr("You can play with memory!\n");

  /* Event loop never exits. */
  while (1);

  return 0;
}


On commence, on attend le message "Tu peux jouer avec la mémoire!" , mettre le programme en pause et regarder la mémoire, à partir de l'adresse 0. Au début, j'avais trÚs peur: à



partir de l'adresse 0x80, le compteur change brusquement sa valeur. De plus, une trĂšs grande quantitĂ©. Mais il s'est avĂ©rĂ© que tout allait bien. Chez nous, le compteur ne s'arrĂȘte jamais et est toujours prĂȘt, et DMA a sa propre file d'attente de lecture anticipĂ©e. Permettez-moi de vous rappeler les paramĂštres du bloc DMA:



0x80 octets sont des mots de trente-deux bits 0x20. Seulement 32 décimales. Tout s'emboßte. Dans des conditions de débogage, ce n'est pas effrayant. Dans des conditions de combat, la source fonctionnera plus correctement (sa disponibilité sera réinitialisée). Par conséquent, nous ignorons simplement cette section. Dans d'autres régions, le compteur compte séquentiellement. Je ne montrerai que le fragment de vidage en largeur. Prenez un mot que je l'ai examiné dans son intégralité.



Ne me faisant pas confiance, j'ai écrit un code qui vérifie automatiquement les données:

  volatile alt_u32* pData = (alt_u32*)BUFFER_BASE;
  volatile alt_u32 cur = pData[0x10];
  int nLine = 0;
  for (volatile int i=0x11;i<BUFFER_SIZE_VALUE/4;i++)
  {
	  if (pData[i]!=cur+1)
	  {
		  alt_printf("Problem at 0x%x\n",i*4);
		  if (nLine++ > 10)
		  {
			  break;
		  }
	  }
	  cur = pData[i];
  }

Il ne révÚle également aucun problÚme.

Essayer de trouver au moins quelques problĂšmes


En fait, l'absence de problÚmes n'est pas toujours bonne. Dans le cadre de l'article, j'avais besoin de trouver les problÚmes, puis de montrer comment ils sont résolus. AprÚs tout, les problÚmes sont évidents. Un bus occupé ne peut pas transmettre de données sans retards! Il devrait y avoir des retards! Mais vérifions pourquoi tout se passe si bien. Tout d'abord, il peut s'avérer que le tout se trouve dans la FIFO du bloc DMA. Réduisez leur taille au minimum:



tout continue de fonctionner! Bien. Assurez-vous que nous provoquons un nombre d'accÚs au bus supérieur à la dimension FIFO. Ajoutez un compteur d'accÚs:



MĂȘme texte:
  volatile alt_u32 status;
  volatile int n = 0;
  do
  {
	  status = IORD_ALTERA_MSGDMA_CSR_STATUS(MSGDMA_0_CSR_BASE);
	  n += 1;
  } while (!(status & errMask) &&(status & ALTERA_MSGDMA_CSR_BUSY_MASK));  


À la fin du travail, il est de 29. C'est plus de 16. Autrement dit, le FIFO devrait dĂ©border. Juste au cas oĂč, ajoutons plus de lectures de registre d'Ă©tat. N'aide pas.

Avec chagrin, je me suis déconnecté du complexe distant de Redd, j'ai refait le projet sur ma maquette existante, à laquelle je peux me connecter avec un oscilloscope en ce moment (au bureau, personne n'est à distance, je ne peux pas atteindre l'oscilloscope). Ajout de deux ports à la minuterie:

   output    clk_copy,
   output    ready_copy

Et les a nommés:

    assign clk_copy = clk;
    assign ready_copy = source_ready;

En conséquence, le module a commencé à ressembler à ceci:

module Timer_ST (
   input           clk,
   input           reset,
	
   input logic     source_ready,
   output logic    source_valid,
   output logic[31:0] source_data,

   output    clk_copy,
   output    ready_copy
	
);
    logic [31:0] counter;
    always @ (posedge clk, posedge reset)
    if (reset == 1)
    begin
        counter <= 0;
    end else
    begin
        counter <= counter + 1;
    end

    assign source_valid = 1;
    assign source_data [31:24] = counter [7:0];
    assign source_data [23:16] = counter [15:8];
    assign source_data [15:8] = counter [23:16];
    assign source_data [7:0] = counter [31:24];

    assign clk_copy = clk;
    assign ready_copy = source_ready;

endmodule

À la maison, j'ai un modĂšle plus petit avec un cristal, j'ai donc dĂ» rĂ©duire l'appĂ©tit de la mĂ©moire. Et il s'est avĂ©rĂ© que mon programme primitif ne rentrerait pas dans une section de 4 kilo-octets. Donc, le sujet soulevĂ© dans le dernier article est, oh, combien pertinent. MĂ©moire dans le systĂšme - Ă  peine suffisante!

Lorsque le dĂ©but du programme, nous obtenons un prĂȘt vague de 16 ou 17 mesures. Il est rempli avec le FIFO du bloc DMA. Le mĂȘme effet qui m'a fait peur au dĂ©but. Ce sont ces donnĂ©es qui formeront le trĂšs faux remplissage du tampon.



Ensuite, nous avons une belle image Ă  40960 nanosecondes, soit 2048 cycles (avec un cristal domestique, le tampon a dĂ» ĂȘtre rĂ©duit Ă  8 kilo-octets, soit 2048 mots de trente-deux bits). Voici son dĂ©but:



Voici la fin:



Eh bien, et tout au long - pas un seul Ă©chec. Non, il Ă©tait clair que cela arriverait, mais il y avait de l'espoir ...

Peut-ĂȘtre devrions-nous essayer d'Ă©crire sur le bus, et pas seulement de le lire? J'ai ajoutĂ© un bloc GPIO au systĂšme: y ai ajoutĂ©



une entrĂ©e en attendant d'ĂȘtre prĂȘt:



MĂȘme texte
  volatile alt_u32 status;
  volatile int n = 0;
  do
  {
	status = IORD_ALTERA_MSGDMA_CSR_STATUS(MSGDMA_0_CSR_BASE);
       IOWR_ALTERA_AVALON_PIO_DATA (PIO_0_BASE,0x01);
       IOWR_ALTERA_AVALON_PIO_DATA (PIO_0_BASE,0x00);

       n += 1;
  } while (!(status & errMask) &&(status & ALTERA_MSGDMA_CSR_BUSY_MASK));  


Il n'y a aucun problĂšme et c'est tout! Qui est Ă  blĂąmer?

Il n'y a pas de miracles, mais il y a des choses inexplorées


Qui est Ă  blĂąmer? Avec peine, j'ai commencĂ© Ă  Ă©tudier tous les menus de l'outil Platform Designer et, semble-t-il , j'ai trouvĂ© un indice. À quoi ressemble gĂ©nĂ©ralement un pneu? Ensemble de fils auxquels les clients sont connectĂ©s. Donc? Vraisemblablement. Nous le voyons dans les chiffres de l'Ă©diteur. Juste, le deuxiĂšme objectif de l'article Ă©tait de montrer comment le bus peut ĂȘtre divisĂ© en deux segments indĂ©pendants, chacun fonctionnant sans interfĂ©rer avec l'autre.

Mais regardons les messages qui apparaissent lorsque le systÚme est généré. Mettez en surbrillance les mots clés:



Et il y a beaucoup de messages similaires: il est ajouté, il est ajouté. Il s'avÚre qu'aprÚs l'édition avec des stylos, beaucoup de choses supplémentaires sont ajoutées au systÚme. Comment envisagez-vous un schéma dans lequel tout cela est déjà disponible? Je nage toujours dans ce domaine, mais nous pouvons obtenir la réponse la plus probable dans le cadre de l'article en choisissant cet élément de menu: L'



image ouverte est impressionnante en soi, mais je ne la donnerai pas. Et immédiatement,



je sélectionne cet onglet: Et là, nous voyons ce qui suit: Je vais



afficher plus grand le plus important:



Les pneus ne sont pas combinĂ©s! Ils sont segmentĂ©s! Je ne peux pas le justifier (peut-ĂȘtre que les experts me corrigeront dans les commentaires), mais il semble que le systĂšme ait insĂ©rĂ© les interrupteurs pour nous! Ce sont ces commutateurs qui crĂ©ent les segments de bus isolĂ©s, et le systĂšme principal peut fonctionner en parallĂšle avec l'unitĂ© DMA, qui peut actuellement accĂ©der Ă  la mĂ©moire sans conflit!

Nous provoquons de vrais problĂšmes


Ayant reçu toutes ces connaissances, nous concluons que nous pouvons trĂšs bien provoquer des problĂšmes. Cela est nĂ©cessaire pour s'assurer que le systĂšme de test peut les crĂ©er, ce qui signifie que l'environnement de dĂ©veloppement les rĂ©sout vraiment indĂ©pendamment. Nous ne ferons pas rĂ©fĂ©rence aux pĂ©riphĂ©riques abstraits sur le bus, mais Ă  la mĂȘme mĂ©moire tampon afin que le bloc cmd_mux_005 rĂ©partisse le bus entre le cƓur du processeur et le bloc DMA. Nous rĂ©Ă©crivons la fonction d'attente longue comme ceci:



MĂȘme texte
  volatile alt_u32 status;
  volatile int n = 0;
  volatile alt_u32* pBuf = (alt_u32*)BUFFER_BASE;
  volatile alt_u32 sum = 0;
  do
  {
	  status = IORD_ALTERA_MSGDMA_CSR_STATUS(MSGDMA_0_CSR_BASE);
	  sum += pBuf[n];

	  n += 1;
  } while (!(status & errMask) &&(status & ALTERA_MSGDMA_CSR_BUSY_MASK));


Et enfin, des creux sont apparus sur la forme d'onde!



La fonction de vérification de la mémoire a également trouvé beaucoup d'omissions:



Oui, et nous voyons trÚs bien que les données sont déplacées d'une ligne à l'autre:



Et voici un exemple d'un mauvais endroit spécifique (6CCE488F est manquant):



Maintenant, nous voyons que l'expérience a été effectuée correctement, juste l'environnement de développement effectué optimisation pour nous. C'est le cas lorsque je prononce la phrase «Tous intelligemment blessé tout l'acier» non pas avec moquerie, mais avec gratitude. Merci aux développeurs Quartus pour cette question!

Conclusion


Nous avons appris Ă  insĂ©rer un bloc DMA dans le systĂšme pour transfĂ©rer des donnĂ©es de streaming vers la mĂ©moire. Nous avons Ă©galement veillĂ© Ă  ce que le processus de tĂ©lĂ©chargement d'autres appareils sur le bus n'interfĂšre pas avec le processus de tĂ©lĂ©chargement. L'environnement de dĂ©veloppement crĂ©era automatiquement un segment isolĂ© qui s'exĂ©cutera en parallĂšle avec d'autres sections du bus. Bien sĂ»r, si quelqu'un se tourne vers le mĂȘme segment, les conflits et le temps passĂ© Ă  les rĂ©soudre sont inĂ©vitables, mais le programmeur peut trĂšs bien prĂ©voir de telles choses.

Dans le prochain article, nous allons remplacer la RAM par un contrĂŽleur SDRAM, et le timer par une vraie "tĂȘte" et faire le premier analyseur logique. Est-ce que ça va marcher? Je ne sais pas encore. J'espĂšre que les problĂšmes n'apparaissent pas.

All Articles