Utilisation de l'outil de configuration de Datapath



Nous devons franchir l'avant-dernière étape dans le développement pratique de la collaboration avec l'UDB. Aujourd'hui, nous ne développerons pas à l'aide de l'éditeur UDB automatisé, mais en mode semi-manuel à l'aide de Datapath Config Tool. Une très bonne aide pour maîtriser cet outil est AN82156 - PSoC 3, PSoC 4 et PSoC 5LP - Conception de composants PSoC Creator avec des chemins de données UDB. En fait, je l'ai étudié moi-même.

Peut-être, en lisant nos traductions de la documentation sur UDB , quelqu'un a essayé de reproduire les connaissances à partir de là dans la pratique et a remarqué que toutes les fonctionnalités décrites dans les publications ne sont pas disponibles dans l'éditeur UDB. Cela est dû au fait que les développeurs n'ont pas commencé à placer des mécanismes particulièrement lourds dans l'éditeur UDB. Les auteurs de AN82156 soutiennent que grâce à l'éditeur UDB, vous ne pouvez pas faire les choses suivantes:

  • organiser l'entrée et la sortie de données parallèles;
  • organiser une gestion dynamique du FIFO;
  • mettre en œuvre l'inverse du signal d'horloge FIFO;
  • implémenter la fonction CRC;
  • implémenter la fonction PRS;
  • mettre en œuvre le choix du transfert entrant;
  • implémenter la migration entrante dynamique.

De moi-même, j'ajouterai que je n'ai pas trouvé comment implémenter la permutation des grignotages dans l'éditeur UDB.

Si ces fonctions sont nécessaires dans le projet, vous devrez créer votre propre code Verilog. J'ai spécifiquement utilisé le mot «créer» plutôt que «écrire». Connaître ce langage de programmation suffit au niveau de la lecture. Je veux dire, vous devez comprendre quel design est nécessaire pour quoi. Et pouvoir écrire à partir de zéro est toujours utile, mais cette compétence n'est pas requise pour ce qui est présenté dans cet article.

Comme problème résoluble, j'ai choisi un boîtier semi-synthétique. En général, j'ai décidé de sortir certaines données sur le port parallèle, et en particulier, à partir de ce qui est à portée de main, le texte LCD a un port parallèle. Je l'ai retiré de l'imprimante 3D MZ3D il y a trois ans lorsque j'ai transplanté cette dernière en STM32. Par conséquent, le boîtier est semi-synthétique: aujourd'hui, de tels indicateurs ont généralement une entrée I2C, et ils n'ont pas besoin de se connecter via une pile de fils dans la vraie vie. Cependant, les écrans LCD modernes ont également des ports parallèles, donc tout le monde peut les utiliser pour répéter l'expérience.

Considérez le schéma de commutation d'affichage tiré de reprap.org (ce n'était pas facile, mon fournisseur bloque ce site, ainsi que plusieurs autres sites techniques, le motivant du fait qu'il vit sur la même IP que quelqu'un bloqué).



Grande disposition! Tout d'abord, je n'ai pas à penser à la lecture: les données sur l'écran LCD ne peuvent être écrites (la ligne R / W est mise à la terre et n'est pas disponible sur le connecteur). Deuxièmement, les données sont au format 4 bits, ce qui signifie que nous pouvons non seulement déterminer la sortie parallèle, mais également vérifier le fonctionnement de la fonction de permutation de quartets.

Création de projet


Alors, lancez PSoC Creator et sélectionnez Fichier-> Nouveau-> Projet :



Ensuite, je choisis ma planche à pain:



Vient ensuite le diagramme vide:



J'appellerai le projet LCDTest2 :



Maintenant, comme auparavant, allez dans l'onglet Composants :



Et, après avoir sélectionné le projet, appuyez sur le bouton droit de la souris, puis sélectionnez Ajouter un élément de composant .



Et ici, vous devez choisir l' Assistant Symbole . Donnez un nom ... Eh bien, disons LCD4bit .



J'ai attribué les ports suivants au symbole:



clk est l'entrée d'horloge. Les ports avec un préfixe LCD sont des ports LCD standard. faim - sorties indiquant à l'unité DMA qu'il y a de l'espace libre dans FIFO, l'idée a été discutée dans un article sur le contrôle des LED RGB . Cliquez sur OK pour obtenir le personnage.



Maintenant, basé sur ce symbole, un modèle Verilog devrait être généré. Cliquez avec le bouton droit de la souris à proximité du symbole et sélectionnez Générer Verilog dans le menu contextuel.



Nous avons obtenu le modèle illustré dans la figure ci-dessous (sous forme de texte, cela n'a pas encore de sens):



Nous avons créé un module et quelques sections. Mais ils n'ont pas encore créé Datapath. Pour l'ajouter, allez dans l'arborescence du projet, sélectionnez le fichier LCD4bit.v , appuyez sur le bouton droit de la souris et sélectionnez Datapath Config Tool dans le menu contextuel qui apparaît:



Une fenêtre s'ouvre devant nous, pour l'instant je ne montrerai que partiellement:



Veuillez aimer et favoriser, éditeur Datapath. Il contient tous les bits décrits dans la traduction de la documentation propriétaire. Mais il y a tellement de ces morceaux qu'au début, je l'ai regardé, mais j'avais peur de faire quoi que ce soit. Regardez, regardez et sortez. Et ce n'est qu'après un certain temps, en s'y habituant, qu'il a commencé à essayer de faire quelque chose. En fait, c'est pourquoi je n'ai apporté qu'une partie de la fenêtre. Pourquoi effrayer tout le monde à l'avance? En attendant, nous avons juste besoin de créer un chemin de données, nous sélectionnons donc l'élément de menu Édition-> Nouveau chemin de données :



Quelle option choisir dans la boîte de dialogue qui apparaît?



La question est un peu plus sérieuse qu'il n'y paraît. Permettez-moi même de mettre en évidence le paragraphe suivant afin que personne ne se fasse attraper (je me suis fait attraper, puis j'ai vu des questions sur le réseau de celles que j'ai eues, et personne n'y a vraiment répondu, et la réponse est en AN82156 , il vous suffit de le lire en diagonale, comme il est dit ici courte phrase discrète).
Si vous prévoyez de travailler avec des données parallèles, vous devez absolument choisir l'option CY_PSOC3_DP. Aucune autre option ne contiendra de ports pour la connexion de données parallèles.
Alors. Que l'instance soit appelée LCD_DP:



Cliquez sur OK et fermez l' outil de configuration de Datapath pour l'instant , en acceptant d'enregistrer le résultat. Nous reviendrons ici plus tard.

Notre code Verilog s'est étendu. Maintenant, il a Datapath. Son début est totalement illisible. Ce n'est pas effrayant, il est configuré par Datapath Config Tool .



Et nous déciderons de la fin de la description de Datapath. Notre site ressemble à ceci
(à partir de là, il est logique de tout mettre sous forme de texte).
)) LCD_DP( /* input */ .reset(1'b0), /* input */ .clk(1'b0), /* input [02:00] */ .cs_addr(3'b0), /* input */ .route_si(1'b0), /* input */ .route_ci(1'b0), /* input */ .f0_load(1'b0), /* input */ .f1_load(1'b0), /* input */ .d0_load(1'b0), /* input */ .d1_load(1'b0), /* output */ .ce0(), /* output */ .cl0(), /* output */ .z0(), /* output */ .ff0(), /* output */ .ce1(), /* output */ .cl1(), /* output */ .z1(), /* output */ .ff1(), /* output */ .ov_msb(), /* output */ .co_msb(), /* output */ .cmsb(), /* output */ .so(), /* output */ .f0_bus_stat(), /* output */ .f0_blk_stat(), /* output */ .f1_bus_stat(), /* output */ .f1_blk_stat(), /* input */ .ci(1'b0), // Carry in from previous stage /* output */ .co(), // Carry out to next stage /* input */ .sir(1'b0), // Shift in from right side /* output */ .sor(), // Shift out to right side /* input */ .sil(1'b0), // Shift in from left side /* output */ .sol(), // Shift out to left side /* input */ .msbi(1'b0), // MSB chain in /* output */ .msbo(), // MSB chain out /* input [01:00] */ .cei(2'b0), // Compare equal in from prev stage /* output [01:00] */ .ceo(), // Compare equal out to next stage /* input [01:00] */ .cli(2'b0), // Compare less than in from prv stage /* output [01:00] */ .clo(), // Compare less than out to next stage /* input [01:00] */ .zi(2'b0), // Zero detect in from previous stage /* output [01:00] */ .zo(), // Zero detect out to next stage /* input [01:00] */ .fi(2'b0), // 0xFF detect in from previous stage /* output [01:00] */ .fo(), // 0xFF detect out to next stage /* input [01:00] */ .capi(2'b0), // Software capture from previous stage /* output [01:00] */ .capo(), // Software capture to next stage /* input */ .cfbi(1'b0), // CRC Feedback in from previous stage /* output */ .cfbo(), // CRC Feedback out to next stage /* input [07:00] */ .pi(8'b0), // Parallel data port /* output [07:00] */ .po() // Parallel data port ); 


Effrayant Maintenant, nous allons découvrir ce qui est quoi - ce n'est plus effrayant. En fait, il y a trois groupes distincts dans ce texte. Rappelons la traduction de la documentation. À quoi ressemblait le chemin de données dans l'image? Je noterai immédiatement sur la figure les endroits auxquels appartiennent les groupes «1», «2» et «3».



En fait, le premier groupe de ports dans le code verilog est les entrées. Comparez les noms en sortie du multiplexeur d'entrée («1» sur la figure) et les noms des signaux dans le code.

Maintenant, toutes les entrées sont nulles. Nous devrons connecter l'entrée d'horloge et nous pouvons transmettre jusqu'à six lignes d'entrée, comme cela a été fait dans l'éditeur UDB. Ces entrées sont:

  /* input */ .reset(1'b0), /* input */ .clk(1'b0), /* input [02:00] */ .cs_addr(3'b0), /* input */ .route_si(1'b0), /* input */ .route_ci(1'b0), /* input */ .f0_load(1'b0), /* input */ .f1_load(1'b0), /* input */ .d0_load(1'b0), /* input */ .d1_load(1'b0), 

Le deuxième groupe est celui des sorties. Les noms dans le code coïncident également avec les noms des entrées du multiplexeur de sortie "2":

  /* output */ .ce0(), /* output */ .cl0(), /* output */ .z0(), /* output */ .ff0(), /* output */ .ce1(), /* output */ .cl1(), /* output */ .z1(), /* output */ .ff1(), /* output */ .ov_msb(), /* output */ .co_msb(), /* output */ .cmsb(), /* output */ .so(), /* output */ .f0_bus_stat(), /* output */ .f0_blk_stat(), /* output */ .f1_bus_stat(), /* output */ .f1_blk_stat(), 

Seule l'espèce Datapath donnée a le troisième groupe (les autres n'en ont pas, donc il n'y a pas de données parallèles). Il s'agit de signaux de chemin de données internes à travers lesquels vous pouvez chaîner indépendamment ou effectuer d'autres actions utiles. Les noms dans le code coïncident également avec les noms des signaux internes diffusés sur la figure. Nous, via l'un d'entre eux (le dernier de la liste, son nom est po ), sortirons des données parallèles directement vers les pattes de la puce.

  /* input */ .ci(1'b0), // Carry in from previous stage /* output */ .co(), // Carry out to next stage /* input */ .sir(1'b0), // Shift in from right side /* output */ .sor(), // Shift out to right side /* input */ .sil(1'b0), // Shift in from left side /* output */ .sol(), // Shift out to left side /* input */ .msbi(1'b0), // MSB chain in /* output */ .msbo(), // MSB chain out /* input [01:00] */ .cei(2'b0), // Compare equal in from prev stage /* output [01:00] */ .ceo(), // Compare equal out to next stage /* input [01:00] */ .cli(2'b0), // Compare less than in from prv stage /* output [01:00] */ .clo(), // Compare less than out to next stage /* input [01:00] */ .zi(2'b0), // Zero detect in from previous stage /* output [01:00] */ .zo(), // Zero detect out to next stage /* input [01:00] */ .fi(2'b0), // 0xFF detect in from previous stage /* output [01:00] */ .fo(), // 0xFF detect out to next stage /* input [01:00] */ .capi(2'b0), // Software capture from previous stage /* output [01:00] */ .capo(), // Software capture to next stage /* input */ .cfbi(1'b0), // CRC Feedback in from previous stage /* output */ .cfbo(), // CRC Feedback out to next stage /* input [07:00] */ .pi(8'b0), // Parallel data port /* output [07:00] */ .po() // Parallel data port ); 

Alors. Pendant que nous travaillons, nous devrons connecter certaines de ces entrées et sorties à nos propres entités, et les autres - il suffit de les laisser sous la forme dans laquelle nous les avons créées.

Utiliser l'éditeur UDB comme référence


Et maintenant nous avons un blanc, nous savons où et ce que nous devons écrire. Reste à comprendre ce que nous y entrerons exactement. Il se trouve que j'utilise le langage Verilog pas tous les jours, donc en général je me souviens de tout, et écrire à partir de zéro pour moi est toujours une situation stressante. Lorsque le projet est déjà en cours, tout est mémorisé, mais si après quelques mois d'inactivité je commence quelque chose à partir de zéro, bien sûr, je ne me souviens plus des détails de syntaxe de ce langage particulier. Par conséquent, je suggère de demander à l'environnement de développement de nous aider.

L'éditeur UDB pour l'auto-surveillance construit du code Verilog. Nous profitons du fait que les composants qui ne sont pas impliqués dans le circuit principal ne sont pas compilés, nous pouvons donc créer un composant auxiliaire dans l'éditeur UDB, et il n'entrera pas dans le code de sortie. Nous y dessinerons un automate, nous effectuerons un ajustement approximatif des entrées et des sorties de Datapath, puis transférerons simplement le texte généré automatiquement vers notre module verilog et modifierons tout de manière créative. C'est beaucoup plus simple que de se souvenir des détails de la syntaxe Verilog et de tout écrire à partir de zéro (bien que quiconque utilise Verilog en permanence, bien sûr, il sera plus facile d'écrire à partir de zéro: l'achèvement créatif, comme nous le verrons bientôt, est simple, mais nécessite temps).

Donc, nous commençons à faire un composant auxiliaire. Avec le mouvement habituel de la main, nous ajoutons un nouvel élément au projet:



Ce sera un document UDB, appelons-le UDBhelper :



Il est temps de penser à la machine, que nous placerons sur la feuille créée. Pour ce faire, nous devons considérer quel diagramme temporel nous devons former avec lui:





Alors. Vous devez d'abord définir le signal RS (car R / W est soudé à zéro dans le matériel). Ensuite, attendez tAS, puis augmentez le signal E et réglez les données (le paramétrage des données par rapport au front positif E n'est pas limité). Les données doivent être sur le bus pas moins que tDSW, après quoi le signal E doit être abandonné. Les données doivent rester sur le bus pendant au moins tDHW et RS pendant au moins tAH.

RS est l'indicateur de commande ou de données. Si RS est nul, alors une commande est écrite, si elle est une, les données sont écrites.

Je suggère d'envoyer des commandes via FIFO0 et des données via FIFO1 . Dans le cadre de la tâche actuelle, cela ne contredit rien. La machine à états finis que j'ai proposée aura alors la forme suivante:



À l'état inactif , la machine ne contient toujours pas de données FIFO. Si des données sont apparues dans FIFO0 , elles vont à LoadF0 , où à l'avenir elles recevront des données de FIFO0 à A0.

Pendant la transmission des commandes, les données ne doivent pas être envoyées. Par conséquent, la condition de réception des données sera moins prioritaire que la condition de réception des commandes.



Les données sont reçues dans A1 dans l'état LoadF1 (à partir de FIFO1, elles ne peuvent aller que dans le registre A1 et ne peuvent pas aller dans le registre A0), puis elles sont copiées de A1 vers A0 dans l'état A1toA0 .

Quelle que soit la façon dont nous allons au point de convergence des flèches, nous avons des données en A0. Ils sont déjà sortis sur le port parallèle. Nous armons E (dans l'état E_UP1 ), laissons tomber E (dans l'état E_DOWN1 ). Ensuite, nous aurons un état pour permuter les quartets ( SWAP ), après quoi E remonte ( E_UP2 ). Sur ce point, j'ai épuisé huit états qui peuvent être encodés en trois bits. Et nous nous souvenons que la RAM de configuration dynamique Datapath n'a que trois entrées d'adresse. Quelques astuces pourraient être appliquées, mais l'article est déjà volumineux. Par conséquent, juste la deuxième fois, nous laisserons tomber E à l'état inactif . Ensuite, huit États nous suffisent.

Nous avons également mis Datapath sur la feuille et assigné ses entrées et sorties d'une manière familière dans les articles précédents. Voici les entrées:



Voici les sorties:



Rien de nouveau, tout a déjà été décrit dans les précédents articles du cycle. Donc, nous avons un blanc, sur la base duquel nous pouvons faire quelque chose de nous-mêmes. Certes, pour nous assurer que tout se passe bien, nous devons amener notre système au plus haut niveau du projet, sinon aucune erreur ne sera trouvée. Et dans les premières expériences sans erreurs ne fonctionnera pas. Par conséquent, nous effectuerons une action supplémentaire.

La description de la fabrication du circuit va au-delà de la description du travail avec UDB. Je vais juste vous montrer quel circuit j'ai. Il n'y a qu'une seule unité DMA: lors de l'envoi de commandes à l'écran LCD, il est nécessaire de supporter des pauses importantes, il est donc encore plus facile de le faire par programme. Pour d'autres applications, vous pouvez simplement mettre le deuxième bloc DMA par analogie en utilisant le signal hungry0 .



Pour respecter précisément le délai, j'ai choisi une fréquence d'horloge égale à un mégahertz. Il serait possible de prendre une fréquence et plus, mais les données sont transmises sur de longs fils dans des conditions de forte interférence, il est donc préférable de prendre le temps de régler les données avant et après la porte avec une marge. Si quelqu'un répète mes expériences sur la même planche à pain - n'utilisez pas le port P3.2: un condensateur est soudé à cette jambe sur la carte. J'ai tué pendant une demi-heure, jusqu'à ce que je découvre pourquoi je n'ai pas formé d'impulsion E, que j'ai d'abord connectée là-bas. Je l'ai lancé en P3.1 - tout a fonctionné tout de suite. Mon bus de données passe à P3.7-P3.4, RS passe à P3.3, donc E est initialement allé à P3.2 ...

Eh bien ici. Maintenant, si vous essayez de compiler le projet, nous obtenons des erreurs complètement prévisibles



Le système essaie donc de collecter quelque chose. Mais elle n'a toujours rien à collectionner. Nous procédons à la copie du code. Pour ce faire, dans l'éditeur UDB, basculez vers l'onglet Verilog (cet onglet est situé sous la fenêtre avec la feuille de l'éditeur UDB):



Qu'est-ce qui vous est familier? À la toute fin du texte se trouve le corps de l'automate. Commençons la migration à partir de celui-ci.

Placez-le également sous Datapath:
 /* ==================== State Machine: SM ==================== */ always @ (posedge clock) begin : Idle_state_logic case(SM) Idle : begin if (( !F0empty ) == 1'b1) begin SM <= LoadF0 ; end else if (( !F1empty ) == 1'b1) begin SM <= LoadF1 ; end end LoadF0 : begin if (( 1'b1 ) == 1'b1) begin SM <= E_Up1 ; end end E_Up1 : begin if (( 1'b1 ) == 1'b1) begin SM <= E_Down1 ; end end E_Down1 : begin if (( 1'b1 ) == 1'b1) begin SM <= SWAP ; end end SWAP : begin if (( 1'b1 ) == 1'b1) begin SM <= E_UP2 ; end end E_UP2 : begin if (( 1'b1 ) == 1'b1) begin SM <= Idle ; end end LoadF1 : begin if (( 1'b1 ) == 1'b1) begin SM <= A1toA0 ; end end A1toA0 : begin if (( 1'b1 ) == 1'b1) begin SM <= E_Up1 ; end end default : begin SM <= Idle; end endcase end 


Il y a des déclarations en haut pour ce code (noms pour les états, chaînes pour Datapath, un registre encodant l'état d'un automate). Nous les transférons vers les
section de notre code:
 /* ==================== Wire and Register Declarations ==================== */ localparam [2:0] Idle = 3'b000; localparam [2:0] LoadF0 = 3'b001; localparam [2:0] LoadF1 = 3'b010; localparam [2:0] E_Up1 = 3'b100; localparam [2:0] A1toA0 = 3'b011; localparam [2:0] E_Down1 = 3'b101; localparam [2:0] SWAP = 3'b110; localparam [2:0] E_UP2 = 3'b111; wire hungry0; wire F0empty; wire hungry1; wire F1empty; wire Datapath_1_d0_load; wire Datapath_1_d1_load; wire Datapath_1_f0_load; wire Datapath_1_f1_load; wire Datapath_1_route_si; wire Datapath_1_route_ci; wire [2:0] Datapath_1_select; reg [2:0] SM; 


Et bien

le site de liaison du signal est transférable:
 /* ==================== Assignment of Combinatorial Variables ==================== */ assign Datapath_1_d0_load = (1'b0); assign Datapath_1_d1_load = (1'b0); assign Datapath_1_f0_load = (1'b0); assign Datapath_1_f1_load = (1'b0); assign Datapath_1_route_si = (1'b0); assign Datapath_1_route_ci = (1'b0); assign Datapath_1_select[0] = (SM[0]); assign Datapath_1_select[1] = (SM[1]); assign Datapath_1_select[2] = (SM[2]); 


Il est temps de brancher Datapath. Le code porté depuis l'éditeur UDB est bon pour l'édition machine, mais pas très bon pour l'édition manuelle. Là, des chaînes sont créées qui se connectent aux entrées Datapath à une extrémité et aux constantes à l'autre. Mais dans le code créé par l' outil de configuration de Datapath (qui fait tout pour le travail manuel), toutes les entrées sont déjà directement connectées à zéro constantes. Je ne connecterai donc que les lignes qui ne sont pas des constantes, mais je supprimerai tout ce qui concerne le transfert des constantes du texte transféré. La connexion s'est déroulée comme ceci (la couleur met en évidence les endroits que j'ai modifiés concernant les créés automatiquement dans l'outil de configuration de Datapath):



Même texte:
 )) LCD_DP( /* input */ .reset(1'b0), /* input */ .clk(clk), /* input [02:00] */ .cs_addr(SM), /* input */ .route_si(1'b0), /* input */ .route_ci(1'b0), /* input */ .f0_load(1'b0), /* input */ .f1_load(1'b0), /* input */ .d0_load(1'b0), /* input */ .d1_load(1'b0), /* output */ .ce0(), /* output */ .cl0(), /* output */ .z0(), /* output */ .ff0(), /* output */ .ce1(), /* output */ .cl1(), /* output */ .z1(), /* output */ .ff1(), /* output */ .ov_msb(), /* output */ .co_msb(), /* output */ .cmsb(), /* output */ .so(), /* output */ .f0_bus_stat(hungry0), /* output */ .f0_blk_stat(F0empty), /* output */ .f1_bus_stat(hungry1), /* output */ .f1_blk_stat(F1empty), 


Les données parallèles sont un peu plus compliquées. Datapath possède un port huit bits et seuls quatre d'entre eux doivent être sortis. Par conséquent, nous démarrons le circuit auxiliaire et n'en connectons que la moitié à la sortie:

 wire [7:0] tempBus; assign LCD_D = tempBus[7:4]; 

Et connectez-le comme ceci:



Même texte:
  /* input [07:00] */ .pi(8'b0), // Parallel data port /* output [07:00] */ .po( tempBus) // Parallel data port ); 


Nous essayons d'assembler (Shift + F6 ou via l'élément de menu Build-> Generate Application ). Nous obtenons l'erreur:



Nous avons des ports hungry0 et hungry1 (apparus lors de la création du composant), ainsi que des chaînes du même nom (apparus lors du glissement depuis l'exemple). Retirez simplement ces chaînes (quittez les ports). Et quelque part le signal d' horloge a fui, et nous avons ce circuit appelé clk .

Après avoir supprimé tous les circuits inutiles (ceux qui ont initialement transmis zéro constantes aux entrées Datapath, ainsi que hungry0 et hungry1 ), nous obtenons le code suivant pour le début de notre fichier:

 // Your code goes here /* ==================== Wire and Register Declarations ==================== */ localparam [2:0] Idle = 3'b000; localparam [2:0] LoadF0 = 3'b001; localparam [2:0] LoadF1 = 3'b010; localparam [2:0] E_Up1 = 3'b100; localparam [2:0] A1toA0 = 3'b011; localparam [2:0] E_Down1 = 3'b101; localparam [2:0] SWAP = 3'b110; localparam [2:0] E_UP2 = 3'b111; wire F0empty; wire F1empty; reg [2:0] SM; /* ==================== Assignment of Combinatorial Variables ==================== */ wire [7:0] tempBus; assign LCD_D = tempBus[7:4]; 

Et lors du remplacement de l' horloge par clk dans le corps de la machine, en même temps, je jetterai toutes les lignes qui sont bonnes pour la génération automatique, mais avec l'édition manuelle ne crée que de la confusion (toutes les comparaisons qui donnent un résultat inconditionnel VRAI et ainsi de suite). En particulier, dans l'exemple ci-dessous, vous pouvez barrer environ la moitié des lignes (et certains début / fin sont facultatifs, parfois ils seront nécessaires, car nous ajouterons des actions, je les ai surlignées):



Après avoir peigné selon le principe ci-dessus (et remplacé l' horloge par clk ), un tel corps reste

(il est devenu plus court, ce qui signifie qu'il est plus facile à lire):
 always @ (posedge clk) begin : Idle_state_logic case(SM) Idle : begin if (( !F0empty ) == 1'b1) begin SM <= LoadF0 ; end else if (( !F1empty ) == 1'b1) begin SM <= LoadF1 ; end end LoadF0 : begin SM <= E_Up1 ; end E_Up1 : begin SM <= E_Down1 ; end E_Down1 : begin SM <= SWAP ; end SWAP : begin SM <= E_UP2 ; end E_UP2 : begin SM <= Idle ; end LoadF1 : begin SM <= A1toA0 ; end A1toA0 : begin SM <= E_Up1 ; end default : begin SM <= Idle; end endcase end 


Maintenant, lors de la compilation, on nous dit que les circuits LCD_E et LCD_RS ne sont pas connectés.

En fait, c'est vrai:



Le moment est venu d'ajouter de l'action à la machine d'état. Nous remplacerons les déclarations des ports correspondant aux chaînes non connectées par reg , puisque nous les écrirons dans le corps de la machine (c'est la syntaxe du langage Verilog, si nous écrivons, les données doivent cliquer, pour cela nous avons besoin d'un déclencheur, et elles sont données par le mot-clé reg ):


Même texte:
 module LCD4bit ( output hungry0, output hungry1, output [3:0] LCD_D, output reg LCD_E, output reg LCD_RS, input clk ); 


Et remplissez la machine d'actions. J'ai déjà dit la logique ci-dessus lorsque je considérais le graphe de transition de l'automate, donc je ne montrerai que le résultat:


Même texte:
 always @ (posedge clk) begin : Idle_state_logic case(SM) Idle : begin LCD_E <= 0; if (( !F0empty ) == 1'b1) begin SM <= LoadF0 ; LCD_RS <= 0; end else if (( !F1empty ) == 1'b1) begin SM <= LoadF1 ; LCD_RS <= 1; end end LoadF0 : begin SM <= E_Up1 ; end E_Up1 : begin SM <= E_Down1 ; LCD_E <= 1'b1; end E_Down1 : begin SM <= SWAP ; LCD_E <= 1'b0; end SWAP : begin SM <= E_UP2 ; end E_UP2 : begin SM <= Idle ; LCD_E <= 1; end LoadF1 : begin SM <= A1toA0 ; end A1toA0 : begin SM <= E_Up1 ; end default : begin SM <= Idle; end endcase end 


A partir de ce moment, le projet commence à se monter. Mais il ne fonctionnera pas encore. Jusqu'à présent, j'ai dit: «Dans cet état, nous chargerons le registre à partir de FIFO», «Dans ce cas, A1 sera copié dans A0», «Nibbles sera réorganisé dans ce domaine». En général, j'ai beaucoup parlé, mais jusqu'à présent, il n'y a eu aucune action. Le moment est venu de les réaliser. Nous regardons comment les états ont été encodés:

 localparam [2:0] Idle = 3'b000; localparam [2:0] LoadF0 = 3'b001; localparam [2:0] LoadF1 = 3'b010; localparam [2:0] E_Up1 = 3'b100; localparam [2:0] A1toA0 = 3'b011; localparam [2:0] E_Down1 = 3'b101; localparam [2:0] SWAP = 3'b110; localparam [2:0] E_UP2 = 3'b111; 

Rouvrez l'outil de configuration de chemin de données :



Et commencez à éditer les lignes CFGRAM . Lors de l'édition, vous devez garder à l'esprit le schéma Datapath, à savoir:



Les cadres rouges dans la figure ci-dessous (et les flèches dans la figure ci-dessus) ont mis en évidence les zones corrigées (et le chemin de données) pour l'état LoadF0 (code 001, c'est-à-dire Reg1 ). J'ai également entré des commentaires manuellement. Le contenu de F0 devrait entrer dans A0.



Avec des cadres et des flèches vertes, j'ai marqué les paramètres et le chemin de l'état LoadF1 (code 010 - Reg2 ).

Avec des cadres et des flèches bleues, j'ai marqué les paramètres et le chemin pour l'état A1toA0 (code 011 - Reg3 ).

Les cadres et les flèches violets ont marqué les paramètres et le chemin pour l'état de SWAP (code 110 - Reg6 ).

Enfin, les flèches oranges indiquent le chemin de données parallèle. Et aucune action n'est entreprise pour eux. Ils sortent toujours de la SRCA . Nous avons presque toujours A0 sélectionné comme SRCA : les données proviennent de A0. Donc, pour rediriger les données d'entrée, nous devons effectuer beaucoup d'actions auxiliaires, mais nous n'acceptons aucune donnée, donc ici nous n'avons pas besoin de ces actions, et tout le monde trouvera sa liste dans AN82156 . Nous n'avons pas non plus besoin de modifier les paramètres de chemin de données statiques, fermez donc l' outil de configuration de chemin de données .

C’est tout. Matériel conçu terminé. Commencer à développer du code C. Pour ce faire, accédez à l'onglet Source et modifiez le fichier main.c.



L'initialisation LCD régulière et la sortie du caractère "ABC" ressemblent à ceci (je vous rappelle que les commandes vont à FIFO0 , la documentation doit insérer des pauses entre les équipes, et les données vont à FIFO1 , je n'ai rien trouvé sur les pauses entre les données):

  volatile uint8_t* pFIFO0 = (uint8_t*) LCD4bit_1_LCD_DP__F0_REG; volatile uint8_t* pFIFO1 = (uint8_t*) LCD4bit_1_LCD_DP__F1_REG; pFIFO0[0] = 0x33; CyDelay (5); pFIFO0[0] = 0x33; CyDelay (100); pFIFO0[0] = 0x33; CyDelay (5); pFIFO0[0] = 0x20; CyDelay (5); pFIFO0[0] = 0x0C; //   CyDelay (50); pFIFO0[0] = 0x01; //   CyDelay (50); pFIFO1[0] = 'A'; pFIFO1[0] = 'B'; pFIFO1[0] = 'C'; 

Qu'est-ce que Pourquoi n'y a-t-il que le premier caractère à l'écran?



Et si vous ajoutez des retards entre la sortie des données - tout va bien:



L'oscilloscope n'a pas suffisamment de canaux pour un tel travail. Nous vérifions le travail sur un analyseur logique. Le processus d'enregistrement des données est le suivant.



Toutes les données sont en place (trois paires de packages). Le temps d'installation et de capture des données est alloué en un volume suffisant. En général, du point de vue des diagrammes de temps - tout est fait correctement. Le problème scientifique est résolu, les chronogrammes souhaités se forment. Voici l'ingénierie - non. La raison en est la lenteur du processeur installé sur l'écran LCD. Entre les octets, ajoutez des retards.

Nous formerons des retards à l'aide d'un compteur à sept bits, en même temps nous nous entraînerons à l'ajouter à un tel système. Soyons au repos au moins un certain temps, et un compteur à sept bits mesurera ce temps pour nous. Et encore une fois, nous n'écrirons pas, mais créerons du code. Par conséquent, nous allons à nouveau au composant auxiliaire de l'éditeur UDB et ajoutons un compteur à la feuille de calcul, en définissant ses paramètres comme suit:



Ce compteur fonctionnera toujours ( Enable est réglé sur 1). Mais il se charge lorsque la machine est dans l'état E_UP2 (après quoi nous tombons immédiatement dans l'état inactif ). La ligne Count7_1_tc sera augmentée à 1 lorsque le compteur comptera jusqu'à zéro, ce qui nous fera une condition supplémentaire pour sortir de l'état inactif . La figure contient également la valeur de la période, mais nous ne la trouverons pas dans le code Verilog. Il devra être entré dans le code C. Mais d'abord, nous transférons le code Verilog généré automatiquement en basculant vers l'onglet Verilog. Tout d'abord, le compteur doit être connecté (nous voyons ce code au début du fichier et le déplaçons également au début):

 `define CY_BLK_DIR "$CYPRESS_DIR\..\psoc\content\CyComponentLibrary\CyComponentLibrary.cylib\Count7_v1_0" `include "$CYPRESS_DIR\..\psoc\content\CyComponentLibrary\CyComponentLibrary.cylib\Count7_v1_0\Count7_v1_0.v" 

La façon dont le raffinement créatif des lignes et des constantes est effectué a déjà été décrite, je vais donc simplement montrer le résultat. Voici les chaînes et les affectations ajoutées en conséquence (le reste définit les constantes, donc je les ai jetées):

 wire Count7_1_load; wire Count7_1_tc; assign Count7_1_load = (SM==E_UP2); 

Et voici le compteur lui-même, placé à la fin du fichier. Toutes les constantes sont affectées aux ports directement dans cette déclaration:

  Count7_v1_0 Count7_1 ( .en(1'b1), .load(Count7_1_load), .clock(clk), .reset(1'b0), .cnt(), .tc(Count7_1_tc)); defparam Count7_1.EnableSignal = 1; defparam Count7_1.LoadSignal = 1; 

Pour permettre à ce compteur de fonctionner, nous ajoutons automatiquement une condition supplémentaire pour quitter l'état inactif :


Même texte:
  case(SM) Idle : begin LCD_E <= 0; if (( !F0empty ) == 1'b1) begin SM <= LoadF0 ; LCD_RS <= 0; end else if (( !F1empty &Count7_1_tc ) == 1'b1) begin SM <= LoadF1 ; LCD_RS <= 1; end end 


L'API pour le compteur ajouté de cette manière n'est pas créée, nous ajoutons donc deux lignes magiques à la fonction principale , que j'ai formées à l'image et à la ressemblance de ce que j'ai vu dans l'API à partir de projets précédents (la première ligne définit la valeur chargée du compte, la même charge, la seconde démarre le compteur):

  *((uint8_t*)LCD4bit_1_Count7_1_Counter7__PERIOD_REG) = 0x20; *((uint8_t*)LCD4bit_1_Count7_1_Counter7__CONTROL_AUX_CTL_REG) |= 0x20; // Start 

L'analyseur montre que dans le cas modifié le retard est évident:



L'écran LCD possède également les trois caractères.

Mais la sortie de caractère programmatique dans la vie réelle est inacceptable. Le simple fait de les ajouter à FIFO débordera. Attendez que le FIFO se vide - cela signifie créer de grands retards pour le cœur du processeur. 72 , 7-8 1 . DMA. « ». UDB, FIFO DMA. DMA, , .

:
  static const char line[] = "This is a line"; /* Defines for DMA_D */ #define DMA_D_BYTES_PER_BURST 1 #define DMA_D_REQUEST_PER_BURST 1 /* Variable declarations for DMA_D */ /* Move these variable declarations to the top of the function */ uint8 DMA_D_Chan; uint8 DMA_D_TD[1]; /* DMA Configuration for DMA_D */ DMA_D_Chan = DMA_D_DmaInitialize(DMA_D_BYTES_PER_BURST, DMA_D_REQUEST_PER_BURST, HI16(line), HI16(LCD4bit_1_LCD_DP__F1_REG)); DMA_D_TD[0] = CyDmaTdAllocate(); CyDmaTdSetConfiguration(DMA_D_TD[0], sizeof(line)-1, CY_DMA_DISABLE_TD, CY_DMA_TD_INC_SRC_ADR); CyDmaTdSetAddress(DMA_D_TD[0], LO16((uint32)line), LO16((uint32)LCD4bit_1_LCD_DP__F1_REG)); CyDmaChSetInitialTd(DMA_D_Chan, DMA_D_TD[0]); CyDmaChEnable(DMA_D_Chan, 1); 


:



Conclusion


, , UDB — Datapath Config Tool. , UDB Editor, UDB, , UDB Editor. , , , UDB Editor.

.

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


All Articles