This page is at least 12 years old !
|
Cartouche CPLD + Flash parallèle
Quelques semaines avant de me lancer dans ce projet, [Grapsus] est tombé chez Farnell sur un petit CPLD en QFP44, tolérant le 5V et peu cher : le EPM3032 d'Altera. Puisqu'il aurait pu finir comme mapper dans une cartouche, il me propose d'en commander pour moi. Un peu plus tard, après les avoir reçus, il veut simplifier leur utilisation en créant une breakout board sur mesure grâce à sa technique de gravure de circuits (dont je suis jaloux). Je reçois donc deux de ces CPLD ainsi qu'une breakout board de sa part, sans vraiment savoir ce que je vais en faire. En me lamentant sur l'échec de la cartouche à base d'AVR, mon regard croise le petit circuit imprimé. L'espoir revient instantanément ! Comme je n'allais pas pouvoir m'en sortir avec de la mémoire série i2c, j'allais sûrement pouvoir m'en sortir en imitant l'architecture interne des VSM, c'est à dire avec un peu de logique et de la mémoire parallèle ! ![]() Je monte alors le CPLD sur la carte, installe deux condensateurs de découplage, et réalise une autre carte sur de la bakélite de prototypage pour accueillir la mémoire parallèle et les régulateurs de tension. N'ayant que des flash 29SF256 (32ko), je câble le support DIP28 de manière à fixer A14 à l'état bas et ainsi n'utiliser que les 16 premiers kilooctets. Pour l'alimentation, comme on ne peut avoir que le -3.5V et VBAT (+6V) sur le connecteur cartouche, j'ai simplement choisi de mettre la masse des régulateurs sur le -3.5V. Un 78L05 abaisse les 9.5V à 5V pour les besoins de la flash, et un 1117-3.3 derrière fournit le 3.3V pour le CPLD. J'installe cette fois de vrais diviseurs de tension sur les entrées, afin de ne pas brusquer le CPLD qui fait déjà l'effort de tolérer du 5V. J'ai choisi des résistances de valeurs assez élevées pour ne pas tirer trop fort les lignes au -3.5V : 68k et 28k permettent d'obtenir 2.7V avec 9.5V, un niveau largement acceptable pour le CPLD. Le câblage se fait au fil Kynar, bien pratique dans cette situation quoiqu'un peu fragile. Je prévois un connecteur JTAG sur le bord droit de la carte. Comme avec l'AVR, j'utilise un transistor externe (Q1) pour faire une sortie a collecteur ouvert sur le CPLD. Comme la plupart des IDE pour CPLD, celui d'Altera (Quartus), permet de définir la logique de nombreuses manières différentes. Étant déjà passé par la case "saisie de schéma", j'ai voulu cette fois m'aventurer dans le Verilog. Pour ne pas aller trop vite, j'écris d'abord un simple compteur incremental base sur le signal ROMCLK et dont les sorties vont sur le bus d'adresse de la flash. Premier problème : Lorsque je connecte le Byteblaster (programmateur Altera) au port JTAG, la dictée plante. Comme ce programmateur possède un buffer qui s'adapte au 3.3V, et que cette tension était bonne, le problème ne pouvait venir que de la masse. Deuxième problème : la première ligne d'adresse en sortie du CPLD, qui devait être à la fréquence de ROMCLK divisée par 2, ne ressemble à rien. Avec du code si simple, la cause de ce souci ne pouvait pas venir de bien loin : le CPLD ne devait sûrement pas bien recevoir ROMCLK. Pour mieux cerner le problème, je simplifie le code à l'extrême en faisant sortir une simple copie du signal. Surprise : le signal semble bien correspondre à ROMCLK... A l'exception d'un détail. En zoomant sur le front descendant de la réplique du signal, j'aperçois un sursaut très court, d'une centaine de nanosecondes, qui n'est pas présent sur le signal issu de la dictée. Un doute m'envahit : n'y aurait-il pas de trigger de Schmitt sur les entrées du CPLD ? En effet, la pente descendante du vrai ROMCLK est bien plus longue que la pente montante. Le fait que la tension varie (trop) doucement provoquerait un état instable sur l'entrée et causerait ce sursaut. La documentation confirme le doute : il n'y a que de l'hystéresis sur les broches JTAG. La solution vient tout naturellement : intercaler un composant avec de telles entrées entre la dictée et le CPLD. J'ai pris un CD4093 (portes NAND) car je n'avais que ça sous la main, et problème résolu ! On voit ce circuit pendre avec la plupart de ses broches coupées sur le côté droit de la carte. A ce stade, j'espérais que ROMCLK était le seul signal à traiter de la sorte. Après un rapide coup d'œil au bus et à M1/M0, je fus rassuré de voir que ces signaux n'avaient pas de telle pente. Je reprends donc mon code avec le compteur, pour tester le bus d'adresse côté flash, et rencontre deux incohérences sur les lignes A4 et A11. Je ne remarque pas que ces deux broches sont opposées sur le support de la flash et passe une bonne demi-heure à chercher d'où vient le problème. Je m'aperçois finalement qu'il y a un court circuit entre ces deux lignes, et qu'il est causé par une mauvaise coupure de piste sur la plaque bakélite. Stupide erreur de précipitation. Une fois proprement coupée, j'étais sûr que toutes les lignes d'adresse fonctionnaient. Cependant, quelque chose m'inquiétait à nouveau : lorsque je synthétisais mon code avec le compteur 14 bits, Quartus m'indiquait que je consommais 27 des 32 macrocellules disponibles du CPLD. Même pas un quart de la fonctionnalité, et seulement 5 restantes. Je me mentais en espérant que le tout allait tenir. Peu importe, je me lance dans le code pour simuler du mieux possible le comportement des VSM. Pour cela, ModelSim (livre avec Quartus) m'a été d'une très grande aide en me permettant de visualiser le comportement des sorties et des signaux internes en fonction des entrées, sans avoir à programmer le CPLD "en réel" et à le câbler/recâbler sans arrêt. En assignant tous les signaux nécessaires aux broches du CPLD, je m'aperçois que j'ai été très chanceux pour une fois, le compte tombe pile-poil : 34 broches utilisées sur 34 broches disponibles. Après des dizaines d'essais et l'arrachage de mes cheveux restants, j'arrive à une simulation qui ressemble beaucoup aux chronogrames des datasheets. Le code utilise finalement 45 des 64 macrocellules ainsi que divers signaux et registres internes. Le plus notable est "intaddress", un registre 23 bits qui contient l'adresse du prochain bit (oui bit, pas octet) à lire. A: Front montant de ROMCLK, M1 et gotnop sont hauts, on latche 4 bits d'adresse et on passe gotnop a 0 afin d'ignorer M1 au prochain front montant de ROMCLK. Le seul problème avec ce code, c'est qu'avec ces signaux asynchrones et le gros compteur 23 bits, il se synthétise en 45 macrocellules. Le CPLD n'en a que 32. Je prie alors pour qu'une version plus fournie existe dans le même boîtier et miracle : le EPM3064 existe bel et bien. Je le commande, le remplace sur la carte, et balance le fusemap. J'écris un ROM minimal avec un seul mot et ses données LPC, programme la flash avec, l'installe sur la carte, et allume la dictée. Sueur froide juste avant d'appuyer sur le bouton "Module select", qui passe. Je sais alors au moins que la définition logique est assez bonne pour répondre les magic bytes.
Déclaration des entrées/sorties du module (appelle "snsrom"): romclk, m0, m1, le bus 4 bits et les 8 bits de données provenant de la flash en entrée. Le bus d'adresse pour la flash et la réponse sur ADD8 en sorties. module snsrom ( input romclk, // Horloge 160kHz venant du synthe input m0, // Commande VSM input m1, // Commande VSM input [3:0] bus, // ADD8/4/2/1 output reg bus8o, // Sortie bit (collecteur ouvert sur ADD8) output reg [13:0] address, // Adresse flash input [7:0] data // Donnees flash ); Déclaration des registres internes: gotnop est un flag pour savoir si on est passe par un nop (m0 et m1 a 0), dummy pour savoir si on a reçu un dummy read (si on l'attend ou si on l'a reçu), outp est un flag pour savoir si on doit sortir un bit au prochain front descendant de m0 ou pas. intaddress est l'adresse courante au niveau bit (4 de chip select, 14 d'adresse octet, 3 d'adresse bit). datab est le registre a décalage pour la sortie des bits. reg gotnop; // On a eu un nop ? reg dummy; // On a eu un dummy read ? reg outp; // On doit sortir un bit ? reg [22:0] intaddress; // __CC CCAA AAAA AAAA AAAA BBB, C=Chip select, A=Addresse, B=Numero du bit reg [7:0] datab; // Registre a decalage donnees Les événements asynchrones: quand on a un front descendant sur m0 et que outp est a 1, on sort un bit. Quand on a un front montant sur m1, on lâche le bus, quoi qu'il arrive. always @(negedge m0 or posedge m1) begin if (m1 == 1) begin bus8o <= 1'b0; // Lacher le bus end else if (outp == 1 && gotnop == 1) begin if (datab[0] == 1) // Sortir bit (inverse car collecteur ouvert) bus8o <= 1'b0; else bus8o <= 1'b1; end end Les événements synchrones: Si on a un nop, on met gotnop a 1. Si outp est a 1, on est sur le premier nop après avoir sorti un bit, on shift datab a droite une fois pour préparer le suivant et passe outp a 0. always @(posedge romclk) // Devrait etre negedge, mais romclk est inverse par le CD4093 begin if ({m1,m0} == 2'b00) // 00: Nop begin gotnop = 1; if (outp == 1) // Premier nop apres un read bit ? begin datab[6:0] = datab[7:1]; // Oui: shifter outp = 0; end end Si on reçoit la commande set address et que gotnop est a 1, on shift intaddress a droite de 4 et on coince la valeur sur le but dans les MSB. On remet dummy gotnop et outp a 0. else if ({m1,m0} == 2'b10) // 10: Set address begin if (gotnop == 1) // Actif qu'apres un nop begin intaddress[18:3] = intaddress[22:7]; // Shift de l'adresse x4 intaddress[22:19] = bus[3:0]; // Charger les MSB end dummy = 0; gotnop = 0; outp = 0; end Si on reçoit la commande read bit, il faut préparer les données pour le prochain front descendant de m0. Si c'est la première commande read bit après un set d'adresse, alors c'est un dummy read: on charge datab avec l'octet que la flash donne, on remet a zéro le compteur de bits dans intaddress et on indique que le dummy read est passe en mettant dummy a 1. else if ({m1,m0} == 2'b01) // 01: Read bit begin if (dummy == 0) begin datab[7:0] = data[7:0]; // Dummy read, charger octet et compteur de bit a zero intaddress[2:0] = 0; dummy = 1; end else begin intaddress[20:0] = intaddress[20:0] + 1; // Vraie lecture, pre-incrementer l'addresse if (intaddress[2:0] == 1) datab[7:0] = data[7:0]; // Charger un nouvel octet si on est au bit 1 if (intaddress[20:17] == 4'b0010) outp = 1; // Si le chip select en dur correspond, preparer la sortie end end end On attribue les bits 3 a 16 du registre intaddress aux lignes d'adresse de la flash (on ne prend pas les 4 MSB qui servent de chip select ni les 3 LSB qui servent de compteur de bit). always @* begin address[13:0] = intaddress[16:3]; end endmodule |