Faire la tête d'un analyseur de bus USB basé sur le complexe Redd

Dans les deux derniers articles, nous avons examiné un exemple de "firmware" pour le complexe Redd, faisant de sa partie FPGA un analyseur logique à usage général. Ensuite, j'ai eu le désir de passer à l'étape suivante et de le transformer en analyseur de bus USB. Le fait est que les analyseurs de marque de ce type sont très chers, et je dois vérifier pourquoi le même USB fonctionne, s'il est connecté à la machine, fonctionne, et si vous allumez la machine alors que tout est déjà branché sur le connecteur, cela ne fonctionne pas. Autrement dit, les analyseurs de logiciels ne peuvent pas faire face ici. Pendant que j'écrivais, je me suis en quelque sorte emporté et j'ai écrit un bloc de cinq articles. Maintenant, nous pouvons dire qu'ils montrent non seulement l'analyseur lui-même, mais aussi le processus typique de sa création en mode «hâte». L'article vous montrera comment créer un tel analyseur non seulement basé sur Redd, mais également sur des planches à pain prêtes à l'emploi,qui peuvent être achetés sur Ali Express.









Peut-être qu'aujourd'hui je briserai même la tradition et déboguerai le projet non pas sur le complexe Redd, mais sur une mise en page régulière. Tout d'abord, je suis conscient que l'écrasante majorité des lecteurs n'ont pas accès à un tel complexe, mais ils ont accès à Ali Express. Eh bien, et deuxièmement, je suis trop paresseux pour clôturer un jardin avec une paire de périphériques USB et d'hôte connectés, et aussi pour faire face aux interférences émergentes.



En 2017, je cherchais des solutions toutes faites sur le réseau et j'ai trouvé une chose si merveilleuse , ou plutôt son ancêtre. Maintenant, ils ont tout sur une carte spécialisée, mais partout il y avait des photos d'une simple maquette de Xilinx, à laquelle une carte de WaveShare était connectée (vous pouvez en savoir plus ici ). Jetons un œil à la photo de cette planche.







Il dispose de deux connecteurs USB à la fois. De plus, le diagramme montre qu'ils sont parallélisés. Vous pouvez brancher vos périphériques USB dans une prise de type A, et vous pouvez connecter un câble au connecteur mini USB, que nous allons brancher sur l'hôte. Et la description du projet OpenVizsla dit que cela fonctionne. Le seul dommage est que le projet lui-même est assez difficile à lire. Vous pouvez le prendre sur github, mais je vais donner un lien non pas vers le compte qui est indiqué sur la page, tout le monde le trouvera quand même, mais il a été refait pour MiGen, mais la version que j'ai trouvée en 2017: http: // github. com / ultraembedded / cores, c'est sur un Verilog propre, et il y a la branche usb_sniffer. Là, tout ne passe pas directement par ULPI, mais par le convertisseur ULPI vers UTMI (ces deux mots obscènes sont des microcircuits de niveau physique qui correspondent au canal USB 2.0 haute vitesse avec des bus compréhensibles pour les processeurs et les FPGA), et ne fonctionnent qu'ensuite avec cet UTMI. Comment tout fonctionne là-bas, je n'ai pas compris. Par conséquent, j'ai préféré faire mon développement à partir de zéro, puisque nous verrons bientôt que tout y fait peur plutôt que difficile.



Sur quel matériel pouvez-vous travailler



La réponse à la question du titre est simple: sur toute personne possédant un FPGA et une mémoire externe. Bien sûr, dans cette série, nous ne considérerons que les FPGA Altera (Intel). Cependant, gardez à l'esprit que les données du microcircuit ULPI (c'est sur ce mouchoir) fonctionnent à 60 MHz. Les longs fils ne sont pas acceptables ici. Il est également important de connecter la ligne CLK à l'entrée FPGA du groupe GCK, sinon tout fonctionnera puis échouera. Mieux vaut ne pas risquer. Je ne vous conseille pas de le transmettre par programme. J'ai essayé. Tout s'est terminé par un fil à la jambe du groupe GCK.



Pour les expériences d'aujourd'hui, à ma demande, une connaissance m'a soudé un tel système:







Micromodule avec FPGA et SDRAM (cherchez-le sur ALI express par la phrase FPGA AC608) et la même carte ULPI de WaveShare. Voici à quoi ressemble le module sur les photos de l'un des vendeurs. Je suis juste trop paresseux pour le dévisser du boîtier:







au fait, les trous de ventilation, comme sur la photo de mon boîtier, sont très intéressants. Sur le modèle, dessinez un calque solide et, dans le slicer, définissez le remplissage à, disons, 40% et dites que vous devez créer zéro couche solide en dessous et au-dessus. En conséquence, l'imprimante 3D dessine elle-même cette ventilation. Très confortablement.

En général, l'approche pour trouver du matériel est claire. Maintenant, nous commençons à concevoir l'analyseur. Au contraire, nous avons déjà fait l'analyseur lui-même dans les deux derniers articles ( ici nous avons travaillé avec du matériel , et ici - avec un accès à celui-ci ), maintenant nous allons simplement concevoir une tête orientée problème qui capte les données provenant du microcircuit ULPI.



Ce que la tête devrait être capable de faire



Dans le cas de l'analyseur logique, tout était simple et facile. Il y a des données. Nous nous sommes connectés à eux et avons commencé à emballer et à les envoyer vers le bus AVALON_ST. Tout est plus compliqué ici. La spécification ULPI peut être trouvée ici . Quatre-vingt-treize feuilles de texte ennuyeux. Personnellement, cela me plonge dans le découragement. La description de la puce USB3300, qui est installée dans la carte WaveShare, semble un peu plus simple. Vous pouvez l'obtenir ici . Même si j'ai encore accumulé du courage depuis ce même décembre 2017, en lisant parfois le document et en le fermant aussitôt, alors que je ressentais l'approche de la dépression.



D'après la description, il est clair que l'ULPI dispose d'un ensemble de registres qui doivent être remplis avant de commencer les travaux. Cela est principalement dû aux résistances de rappel et de terminaison. Voici une image pour expliquer le point:







Selon le rôle (hôte ou appareil), ainsi que la vitesse choisie, différentes résistances doivent être incluses. Mais nous ne sommes ni un hôte ni un appareil! Il faut déconnecter toutes les résistances pour ne pas interférer avec les principaux appareils sur le bus! Cela se fait en écrivant dans des registres.



Eh bien, et la vitesse. Il est nécessaire de choisir une vitesse de travail. Pour ce faire, vous devez également écrire dans les registres.



Lorsque tout est configuré, vous pouvez commencer à récupérer les données. Mais au nom de l'ULPI, les lettres «LP» signifient «Low Pins». Et cette même réduction du nombre de jambes a conduit à un protocole si furieux qui tient bon! Regardons de plus près le protocole.



Protocole ULPI



Le protocole ULPI est quelque peu inhabituel pour l'homme ordinaire. Mais si vous vous asseyez avec un document et que vous méditez, alors certaines caractéristiques plus ou moins compréhensibles commencent à apparaître. Il devient clair que les développeurs ont tout mis en œuvre pour vraiment réduire le nombre de contacts utilisés.



Je ne retaperai pas la documentation complète ici. Limitons-nous aux choses les plus importantes. Le plus important d'entre eux est la direction des signaux. Il est impossible de s'en souvenir, il vaut mieux regarder l'image à chaque fois:







ULPI LINK est notre FPGA.



Chronogramme de réception des données



Au repos, nous devons émettre une constante 0x00 sur le bus de données, ce qui correspond à la commande IDLE. Si les données proviennent du bus USB, le protocole d'échange ressemblera à ceci:







Le cycle commencera par le fait que le signal DIR volera jusqu'à un. Premièrement, il y aura un cycle d'horloge pour que le système ait le temps de changer la direction du bus de données. De plus, les miracles de l'économie commencent. Vous voyez le nom du signal NXT? Cela signifie SUIVANT lorsqu'il est transmis par nous. Et ici, c'est un signal complètement différent. Quand DIR est un, j'appellerais NXT C / D. Niveau bas - nous avons une équipe. Élevé - données.



Autrement dit, nous devons fixer 9 bits (le bus DATA et le signal NXT) soit toujours à un DIR élevé (puis filtrage de la première horloge par logiciel), soit à partir de la deuxième horloge après le décollage du DIR. Si la ligne DIR tombe à zéro, nous commutons le bus de données en écriture et recommençons à diffuser la commande IDLE.



Avec la réception de données - c'est clair. Analysons maintenant le travail avec les registres.



Chronogramme de l'écriture dans le registre ULPI



Pour écrire dans le registre, la maison temporaire suivante est utilisée (j'ai délibérément basculé dans le jargon, car je sens que je tends vers GOST 2.105, et c'est ennuyeux, je vais donc m'en éloigner):







Tout d'abord, il faut attendre l'état DIR = 0. A l'horloge T0, il faut régler la constante TXD CMD sur le bus de données. Qu'est-ce que ça veut dire? Vous ne pouvez pas le comprendre tout de suite, mais si vous creusez un peu dans les documents, il s'avère que la valeur souhaitée peut être trouvée ici:







c'est-à-dire que la valeur «10» doit être placée dans les bits de données supérieurs (pour l'octet entier, le masque sera 0x80), et dans les inférieurs - le numéro de registre.



Ensuite, vous devez attendre que le signal NXT décolle. Avec ce signal, le microcircuit confirme qu'il nous a entendus. Dans l'image ci-dessus, nous l'avons attendu à l'horloge T2 et avons réglé les données sur l'horloge suivante (T3). Sur l'horloge T4, l'ULPI recevra les données et supprimera le NXT. Et nous marquerons la fin du cycle d'échange d'unité dans STP. Sur T5 également, les données seront verrouillées dans le registre interne. Le processus est terminé. Voici un retour sur investissement pour un petit nombre de conclusions. Mais nous n'aurons besoin d'écrire les données qu'au démarrage, donc, bien sûr, nous devrons souffrir du développement, mais tout cela n'affectera pas particulièrement le travail.



Chronogramme de lecture du registre ULPI



Honnêtement, pour les tâches pratiques, la lecture des registres n'est pas si importante, mais regardons-la aussi. La lecture sera utile au moins pour s'assurer que nous avons correctement implémenté l'enregistrement.







Nous voyons que devant nous se trouve un mélange explosif des deux maisons temporaires précédentes. Nous définissons l'adresse comme nous l'avons fait pour l'écriture dans le registre, et nous prenons les données selon les règles de lecture des données.



Bien? Commençons à concevoir un automate qui façonnera tout cela pour nous?



Schéma structurel de la tête



Comme vous pouvez le voir dans la description ci-dessus, la tête doit être connectée à deux bus à la fois: AVALON_MM pour accéder aux registres et AVALON_ST pour émettre des données à stocker dans la RAM. La principale chose dans la tête est le cerveau. Et donc ce devrait être une machine à états qui générera les diagrammes de temps que nous avons examinés plus tôt.







Commençons son développement par la fonction de réception de données. Il faut garder à l'esprit ici que nous ne pouvons en aucun cas influencer le flux du bus ULPI. Données à partir de là, si elles ont commencé à disparaître, elles disparaîtront. Ils ne se soucient pas de savoir si le bus AVALON_ST est prêt ou non. Par conséquent, nous ignorerons simplement l'indisponibilité du bus. Dans un analyseur réel, il sera possible d'ajouter une indication d'alarme en cas de sortie de données sans disponibilité. Tout devrait être simple dans le cadre de l'article, alors rappelons-nous simplement cela pour le futur. Et pour assurer la disponibilité du bus, comme dans un analyseur logique, nous aurons un bloc FIFO externe. Au total, le graphe de transition de l'automate de réception du flux de données est le suivant:







DIR a décollé - a commencé à recevoir. Nous avons suspendu une horloge en attente1, puis nous l'acceptons alors que DIR est égal à un. Tombé à zéro - après une horloge (mais pas le fait qu'elle soit nécessaire, mais pour l'instant, nous allons définir l'état wait2) est revenue au repos.



Jusqu'à présent, tout est simple. N'oubliez pas que non seulement les lignes D0_D7, mais aussi la ligne NXT doivent aller sur le bus AVALON_ST, car il détermine ce qui est transmis maintenant: une commande ou des données.



Un cycle d'écriture de registre peut avoir un temps d'exécution imprévisible. Du point de vue du bus AVALON_MM, ce n'est pas très bon. Par conséquent, nous allons le rendre un peu plus délicat. Créons un registre tampon. Les données y entreront, après quoi le bus AVALON_MM sera immédiatement libéré. Du point de vue de l'automate en cours de développement, le signal d'entrée have_reg apparaît (les données du registre ont été reçues, qui doivent être envoyées) et le signal de sortie reg_served (signifiant que le processus d'émission du registre est terminé). Ajoutez la logique d'écriture au registre sur le graphe de transition de l'automate.







J'ai mis en évidence la condition DIR = 1 en rouge pour indiquer clairement qu'elle a la priorité la plus élevée. Il est alors possible d'exclure l'espérance de la valeur nulle du signal DIR dans la nouvelle branche de l'automate. La connexion à une succursale avec une valeur différente ne sera tout simplement pas possible. L'état SET_CMDw est bleu car il est le plus susceptible d'être purement virtuel. Ce ne sont que des actions à réaliser! Personne ne prend la peine de régler la constante correspondante sur le bus de données et juste pendant la transition! Dans l'état STPw, entre autres choses, le signal reg_served peut également être armé pendant un cycle d'horloge pour effacer le signal BSY pour le bus AVALON_MM, permettant un nouveau cycle d'écriture.



Eh bien, il reste à ajouter une branche pour lire le registre ULPI. Ici, le contraire est vrai. La machine de service de bus nous envoie une demande et attend notre réponse. Lorsque les données sont reçues, il peut les traiter. Et cela fonctionnera avec la suspension de bus ou le sondage, ce sont les problèmes de cette machine. Aujourd'hui, j'ai décidé de travailler sur une enquête. Demande de données - BSY est apparu. Comment BSY a disparu - vous pouvez recevoir des données lues. Au total, le graphique prend la forme:







Peut-être, au cours du développement, il y aura quelques ajustements, mais pour l'instant, nous allons adhérer à ce graphique. Après tout, ce n'est pas un rapport, mais une instruction sur la méthodologie de développement. Et la technique est telle que vous devez d'abord dessiner un graphique de transition, puis - faire la logique, selon cette figure, ajustée pour les détails contextuels.



Caractéristiques de l'implémentation de l'automate du côté AVALON_MM



Lorsque vous travaillez avec le bus AVALON_MM, vous pouvez procéder de deux manières. La première consiste à créer des délais d'accès au bus. Nous avons exploré ce mécanisme dans l' un des articles précédents et j'ai prévenu qu'il était semé d'embûches. La deuxième manière est classique. Entrez le registre d'état. Au début de la transaction, réglez le signal BSY, à sa fin - réinitialisation. Et attribuez la responsabilité de tout à la logique maître du bus (processeur Nios II ou pont JTAG). Chacune des options a ses propres avantages et inconvénients. Puisque nous avons déjà fait des variantes avec des retards de bus, faisons tout aujourd'hui, pour un changement, via le registre d'état.



Nous concevons la machine principale



La première chose sur laquelle j'aimerais attirer votre attention sont mes déclencheurs RS préférés. Nous avons deux machines. Le premier sert le bus AVALON_MM, le second - l'interface ULPI. Nous avons découvert que la connexion entre eux passe par quelques indicateurs. Un seul processus peut écrire sur chaque indicateur. Chaque automate est implémenté par son propre processus. Comment être? Depuis quelque temps, je viens de commencer à ajouter un déclencheur RS. Nous avons deux bits, ils doivent donc être générés par deux bascules RS. Les voici:

//   
always_ff @(posedge ulpi_clk)
begin
      //    
      if  (reg_served)
           write_busy <= 0;
      else if (have_reg)
           write_busy <= 1;

      //    
      if  (read_finished)
           read_busy <= 0;
      else if (reg_request)
           read_busy <= 1;
end


Un processus cocks reg_served, le second cocks have_reg. Et la bascule RS dans son propre processus génère le signal write_busy sur leur base. De même, read_busy est formé de read_finished et reg_request. Vous pouvez le faire différemment, mais à ce stade du chemin créatif, j'aime cette méthode.



C'est ainsi que les indicateurs BSY sont définis. Le jaune correspond au processus d'écriture, le bleu au processus de lecture. Le processus Verilogov a une caractéristique très intéressante. Dans celui-ci, vous pouvez attribuer des valeurs non pas une fois, mais plusieurs fois. Par conséquent, si je veux qu'un signal décolle pendant un cycle d'horloge, je l'annule au début du processus (nous voyons que les deux signaux y sont annulés), et le règle à un par une condition qui est exécutée pendant un cycle d'horloge. La saisie de la condition remplacera la valeur par défaut. Dans tous les autres cas, cela fonctionnera. Ainsi, l'écriture sur le port de données initie le décollage du signal have_reg pour un cycle d'horloge, et l'écriture du bit 0 sur le port de contrôle lance le décollage du signal reg_request.





Le même texte.
//  AVALON_MM  
always_ff @(posedge ulpi_clk)
begin
   //    ,    
   //      
   have_reg    <= 0;
   reg_request <= 0;

   if (write == 1) 
   begin
      case (address)
          0 : addr_to_ulpi <= writedata [5:0];
          //       
          1 : begin
                data_to_ulpi <= writedata [7:0];
                have_reg <= 1;
              end
          2 : begin
                //      
                reg_request <= writedata[0];
		force_reset = writedata [31];
              end
         3: begin end
      endcase
   end
end   






Comme nous l'avons vu ci-dessus, un cycle d'horloge suffit pour que la bascule RS correspondante se règle sur un. Et à partir de ce moment, le signal BSY réglé commence à être lu à partir du registre d'état:





Le même texte.
//  AVALON_MM  
always_comb 
begin
   case (address)
      //   (  )
      0 : readdata <= {26'b0, addr_to_ulpi};

      //  
      1 : readdata <= {23'b0, data_from_ulpi};

      // 2 -  ,   -   

      //  
      3 : readdata <= {30'b0, (reg_request | read_busy), (have_reg | write_busy)};
      default: readdata <= 0;
   endcase
end   






En fait, nous nous sommes naturellement familiarisés avec les processus qui fonctionnent avec le bus AVALON_MM.

Permettez-moi également de vous rappeler les principes de travail avec le bus ulpi_data. Ce bus est bidirectionnel. Par conséquent, vous devez utiliser une technique standard pour travailler avec. Voici comment le port correspondant est déclaré:

   inout        [7:0]  ulpi_data,


Nous pouvons lire à partir de ce bus, mais nous ne pouvons pas écrire directement. Au lieu de cela, nous créons une copie pour l'enregistrement.

logic [7:0] ulpi_d = 0;


Et nous connectons cette copie au bus principal via le multiplexeur suivant:

//      inout-
assign ulpi_data = (ulpi_dir == 0) ? ulpi_d : 8'hzz;


J'ai essayé de commenter autant que possible la logique de la machine principale dans le code Verilog. Comme je m'y attendais lors du développement du graphe de transition, dans la mise en œuvre réelle, la logique a quelque peu changé. Certains États ont été expulsés. Néanmoins, en comparant le graphique et le texte source, j'espère que vous comprendrez tout ce qui s'y fait. Par conséquent, je ne parlerai pas de cette machine. Il est préférable de donner pour référence le texte intégral du module, pertinent au moment avant la modification en fonction des résultats d'expériences pratiques.

Texte intégral du module.
module ULPIhead
(
   input               reset,
   output              clk66,

   // AVALON_MM
   input        [1:0]  address,
   input               write,
   input        [31:0] writedata,
   input               read,
   output logic [31:0] readdata = 0,

   // AVALON_ST
   input  logic        source_ready,
   output logic        source_valid = 0,
   output logic [15:0] source_data = 0,

   // ULPI
   inout        [7:0]  ulpi_data,
   output logic        ulpi_stp = 0,
   input               ulpi_nxt,
   input               ulpi_dir,
   input               ulpi_clk,
   output              ulpi_rst
);

logic      have_reg = 0;
logic      reg_served = 0;
logic      reg_request = 0;
logic      read_finished = 0;
logic [5:0] addr_to_ulpi;
logic [7:0] data_to_ulpi;
logic [7:0] data_from_ulpi;

logic      write_busy = 0;
logic      read_busy = 0;

logic [7:0] ulpi_d = 0;

logic force_reset = 0;

//   
always_ff @(posedge ulpi_clk)
begin
      //    
      if  (reg_served)
           write_busy <= 0;
      else if (have_reg)
           write_busy <= 1;

      //    
      if  (read_finished)
           read_busy <= 0;
      else if (reg_request)
           read_busy <= 1;
end

//  AVALON_MM  
always_comb 
begin
   case (address)
      //   (  )
      0 : readdata <= {26'b0, addr_to_ulpi};

      //  
      1 : readdata <= {23'b0, data_from_ulpi};

      // 2 -  ,   -   

      //  
      3 : readdata <= {30'b0, (reg_request | read_busy), (have_reg | write_busy)};
      default: readdata <= 0;
   endcase
end   

//  AVALON_MM  
always_ff @(posedge ulpi_clk)
begin
   //    ,    
   //      
   have_reg    <= 0;
   reg_request <= 0;

   if (write == 1) 
   begin
      case (address)
          0 : addr_to_ulpi <= writedata [5:0];
          //       
          1 : begin
                data_to_ulpi <= writedata [7:0];
                have_reg <= 1;
              end
          2 : begin
                //      
                reg_request <= writedata[0];
		force_reset = writedata [31];
              end
         3: begin end
      endcase
   end
end   

//   
enum {idle,
wait1,wr_st,
wait_nxt_w,hold_w,
wait_nxt_r,wait_dir1,latch,wait_dir0

} state = idle;
always_ff @ (posedge ulpi_clk)
begin
   if (reset)
   begin
       state <= idle;
   end else
   begin
      //    
      source_valid <= 0;
      reg_served  <= 0;
      ulpi_stp <= 0;
      read_finished <= 0;
      case (state)
      idle: begin
           if (ulpi_dir)
               state <= wait1;
           else if (have_reg) 
                begin
                  //      , 
                  //    ,   
                  // 
                  ulpi_d [7:6] <= 2'b10;
                  ulpi_d [5:0] <= addr_to_ulpi;
                  state <= wait_nxt_w;
                end
           else if (reg_request)
                begin
                  //  -   
                  ulpi_d [7:6] <= 2'b11;
                  ulpi_d [5:0] <= addr_to_ulpi;
                  state <= wait_nxt_r;
                end
         end
      //      TURN_AROUND
      wait1 : begin
            state <= wr_st;
            //    ,   
            source_valid <= 1; 
            source_data <= {7'h0,!ulpi_nxt,ulpi_data};
         end
      //     DIR -    AVALON_ST
      wr_st : begin
            if (ulpi_dir)
            begin
              //   ,    
               source_valid <= 1;
               source_data <= {7'h0,!ulpi_nxt,ulpi_data};
            end else
               //      wait2,
               //   ,   - . 
               state <= idle;
         end
      wait_nxt_w : begin
           if (ulpi_nxt)
           begin
              ulpi_d <= data_to_ulpi;
              state <= hold_w;
           end
         end
      hold_w: begin
           //   ,  ULPI 
           //     .   NXT
           //  ...
           if (ulpi_nxt) begin
              // ,  AVALON_MM    
              reg_served  <= 1;
              ulpi_d <= 0;    //   idle
              ulpi_stp <= 1;  //     STP
              state <= idle;  //   -    idle
           end
         end
       //   STPw   ...
       // ...
      //    . ,   NXT
      //    ,    
      wait_nxt_r : begin
           if (ulpi_nxt)
           begin
              ulpi_d <= 0;    //    
              state <= wait_dir1;
           end
         end
      // ,    
      wait_dir1: begin
          if (ulpi_dir)
             state <= latch;
        end
      //    
      //   -   
      latch: begin
          data_from_ulpi <= ulpi_data;
          state <= wait_dir0;
        end
      // ,     
      wait_dir0: begin
          if (!ulpi_dir)
          begin
             state <= idle;
             read_finished <= 1;
          end
        end
   
      default:	begin
         state <= idle;
         end
      endcase
    end
end
//      inout-
assign ulpi_data = (ulpi_dir == 0) ? ulpi_d : 8'hzz;

// reset   ,      
assign ulpi_rst = reset | force_reset;

assign clk66 = ulpi_clk;

endmodule




Guide du programmeur



Port d'adresse de registre ULPI (+0)



L'adresse du registre ULPI du bus, avec lequel le travail ira, doit être placée dans le port avec offset +0



Port de données du registre ULPI (+4)



Lors de l'écriture sur ce port: le processus d'écriture dans le registre ULPI, dont l'adresse a été définie dans le port de l'adresse du registre, démarre automatiquement. Il est interdit d'écrire sur ce port tant que le processus de l'écriture précédente n'est pas terminé.



En lecture: ce port renverra la valeur obtenue à partir de la dernière lecture du registre ULPI.



Port de contrôle ULPI (+8)



La lecture est toujours nulle. L'affectation des bits pour l'écriture est la suivante:



Bit 0 - Lors de l'écriture d'une valeur unique, lance le processus de lecture du registre ULPI, dont l'adresse est définie dans le port d'adresse du registre ULPI.



Bit 31 - Lors de l'écriture d'un, envoie un signal RESET à la puce ULPI.



Le reste des bits est réservé.



Port d'état (+ 0x0C)



Lecture seulement.



Bit 0 - WRITE_BUSY. S'il est égal à un, le processus d'écriture dans le registre ULPI est en cours.



Bit 1 - READ_BUSY. S'il est égal à un, le processus de lecture du registre ULPI est en cours.



Le reste des bits est réservé.



Conclusion



Nous nous sommes familiarisés avec la méthode d'organisation physique de la tête d'analyseur USB, avons conçu un automate de base pour travailler avec le microcircuit ULPI et implémenté un projet de module SystemVerilog pour cette tête. Dans les articles suivants, nous examinerons le processus de modélisation, simulerons ce module, puis effectuerons des expériences pratiques avec celui-ci, en fonction des résultats desquels nous finaliserons proprement le code. Autrement dit, jusqu'à la fin, nous avons au moins quatre autres articles.



All Articles