Very High Speed Integrated Circuit Hardware Description Language/Micro contrôleur embarqué : le PicoBlaze
Le picoblaze est probablement l'architecture par laquelle on doit commencer pour apprendre à utiliser un processeur softcore dans un FPGA Xilinx. Il a une architecture très simple qui peut facilement s'étendre matériellement.
- Le picoblaze est une architecture avec laquelle on travaille en VHDL (ou verilog) : architecture RTL
- Malheureusement le picoblaze est une architecture qui est liée au constructeur Xilinx
- Il existe une version du picoblaze utilisable avec les autres fabricants écrit en verilog
Le modèle de programmation du PicoBlaze
[modifier | modifier le wikicode]Le modèle de programmation est très simple. Le picoBlaze est constitué de 16 registres 8 bits d'utilisation générale notés de s0 à sF. On trouve aussi un compteur programme sur 10 bits, un pointeur de pile (tos = Top Of Stack) sur 31 niveaux et un registre d'état avec 3 bits appelés drapeaux :
- C : retenue (Carry)
- Z : zéro
- I : masque d'interruption
Les deux mémoires, programme et données, sont organisées de la manière suivante:
- La mémoire programme est organisée en 1024 instructions sur 18 bits. L'adressage de la mémoire programme se fait donc sur 10 bits ().
- La mémoire donnée est organisée en 64 octets. L'adressage se fait donc sur 6 bits ()
Par comparaison, les premiers microprocesseurs 8 bits des années 1970 avaient un espace d'adressage sur 16 bits. Cet espace mélangeait données et programme. Ici on a une séparation, une telle architecture est appelée Harvard.
Quelques instructions
[modifier | modifier le wikicode]Les modes d'adressage
[modifier | modifier le wikicode]Le PicoBlaze connaît cinq modes d'adressage.
- Adressage registre : c’est l'adressage le plus simple, il ne peut concerner que des registres.
- Instruction en adressage registre
Op-code (Hexa) instruction opérande(s) commentaire 0 10 10 load s0,s1 ;charger s1 dans s0
Remarquez la taille de l’Opcode : 18 bits. Ces 18 bits sont composés de 5 caractères Hexadécimaux de 4 bits. Ils représentent donc 20 bits. Pour se contenter de 18 bits, le caractère le plus à gauche ne peut dépasser la valeur 3.
- Adressage immédiat (Immediate) : l’opérande correspondant se trouve directement dans le programme derrière le code de l’instruction et un premier opérande. Il se fait sur 8 bits.
0 00 7F load s0,7F ;charger 127 dans s0 1 80 10 add s0,10 ;additionner 16
On peut remarquer que les nombres de l'adressage immédiat (deuxième opérande) sont systématiquement écrits en hexadécimal.
- Instruction en adressage immédiat
instruction opérande 1 opérande 2 Op-code LOAD sX aaaa aaaa 00 0000 xxxx aaaa aaaa ADD sX aaaa aaaa 01 0010 xxxx aaaa aaaa
sX désigne un registre dont le numéro X peut varier de 0 à 15 qui est donc codé sur 4 bits "xxxx".
- Immédiat 10 bits : l'adresse est donnée sur 10 bits. L'instruction JUMP utilise ce mode d'adressage parce que l'adresse fournie est une adresse absolue de programme.
- Instruction en adressage immédiat 10 bits
instruction opérande 1 opérande 2 Op-code JUMP aa aaaa aaaa 11 0100 00aa aaaa aaaa
- Direct 6 bits : l'adresse de la mémoire se trouve dans le dernier octet du code de l'instruction (sur les 6 bits de poids faible). Les instructions STORE et FETCH fonctionnent avec ce mode d'adressage et permettent d'adresser 64 octets (seulement) de la mémoire.
0601F FETCH s0,1F ;charge le contenu de 1F dans s0 18056 ADD s0,56 ;additionne 56 en hexadécimal 34188 JUMP 188 ;saute à l'adresse 188 ;(sur {{Unité|10|bits}})
À noter encore les opcodes sur 18 bits (5 caractères hexadécimaux).
- Instruction en adressage direct 6 bits (RAM)
instruction opérande 1 opérande 2 Op-code FETCH sX aa aaaa 00 0110 xxxx 00aa aaaa
L'instruction FETCH est une instruction qui permet de lire une donnée en mémoire.
- Adressage indexé : l'adresse de la mémoire se trouve dans un registre. Nous donnons ci-dessous le format de cette instruction.
- Instruction en adressage indexé par un registre (RAM)
instruction opérande 1 opérande 2 Op-code FETCH sX (sY) 00 0111 xxxx yyyy 0000
Prenez ce qu’il y a dans le registre sY, cela fait une adresse mémoire que l’on va lire et mettre dans le registre sX. L'intérêt est qu'après cette lecture on peut incrémenter sY et refaire un "FETCH" qui lira l'adresse suivante... ce qui est bien pratique dans une boucle.
Jeu d'instructions
[modifier | modifier le wikicode]Le jeu d'instructions est présenté maintenant sans explications. Si quelqu’un veut ajouter une colonne aux tableaux ci-dessous pour expliquer ce que font les instructions ....
- Les instructions de contrôle du picoblaze
instruction opérande 1 opérande 2 Op-code JUMP aa aaaa aaaa 11 0100 00aa aaaa aaaa JUMP Z, aa aaaa aaaa 11 0101 00aa aaaa aaaa JUMP NZ, aa aaaa aaaa 11 0101 01aa aaaa aaaa JUMP C, aa aaaa aaaa 11 0101 10aa aaaa aaaa JUMP NC, aa aaaa aaaa 11 0101 11aa aaaa aaaa CALL aa aaaa aaaa 11 0000 00aa aaaa aaaa CALL Z, aa aaaa aaaa 11 0001 00aa aaaa aaaa CALL NZ, aa aaaa aaaa 11 0001 01aa aaaa aaaa CALL C, aa aaaa aaaa 11 0001 10aa aaaa aaaa CALL NC, aa aaaa aaaa 11 0001 11aa aaaa aaaa RETURN 10 1010 0000 0000 0000 RETURN Z, 10 1011 0000 0000 0000 RETURN NZ, 10 1011 0100 0000 0000 RETURN C, 10 1011 1000 0000 0000 RETURN NC, 10 1011 1100 0000 0000
- Les instructions de mouvement de données
instruction opérande 1 opérande 2 Op-code LOAD sX aaaa aaaa 00 0000 xxxx aaaa aaaa LOAD sX sY 00 0001 xxxx yyyy 0000 FETCH sX aa aaaa 00 0110 xxxx 00aa aaaa FETCH sX (sY) 00 0111 xxxx yyyy 0000 STORE sX aa aaaa 10 1110 xxxx 00aa aaaa STORE sX (sY) 10 1111 xxxx yyyy 0000 INPUT sX aaaa aaaa 00 0100 xxxx aaaa aaaa INPUT sX (sY) 00 0101 xxxx yyyy 0000 OUTPUT sX aaaa aaaa 10 0100 xxxx aaaa aaaa OUTPUT sX (sY) 10 0101 xxxx yyyy 0000
- Instructions arithmétiques
instruction opérande 1 opérande 2 Op-code ADD sX aaaa aaaa 01 0010 xxxx aaaa aaaa ADD sX sY 01 0011 xxxx yyyy 0000 ADDCY sX aaaa aaaa 01 1010 xxxx aaaa aaaa ADDCY sX sY 01 1011 xxxx yyyy 0000 SUB sX aaaa aaaa 01 1100 xxxx aaaa aaaa SUB sX sY 01 1101 xxxx yyyy 0000 SUBCY sX aaaa aaaa 01 1110 xxxx aaaa aaaa SUBCY sX sY 01 1111 xxxx yyyy 0000 COMPARE sX aaaa aaaa 01 0100 xxxx aaaa aaaa COMPARE sX sY 01 0101 xxxx yyyy 0000
- Instructions logiques
instruction opérande 1 opérande 2 Op-code AND sX aaaa aaaa 00 1010 xxxx aaaa aaaa AND sX (sY) 00 1011 xxxx yyyy 0000 OR sX aaaa aaaa 00 1100 xxxx aaaa aaaa OR sX (sY) 00 1101 xxxx yyyy 0000 XOR sX aaaa aaaa 00 1110 xxxx aaaa aaaa XOR sX (sY) 00 1111 xxxx yyyy 0000 TEST sX aaaa aaaa 01 0010 xxxx aaaa aaaa TEST sX (sY) 01 0011 xxxx yyyy 0000
- Instructions de décalage et rotations
instruction opérande 1 opérande 2 Op-code SR0 sX 10 0000 xxxx 0000 1110 SR1 sX 10 0000 xxxx 0000 1111 SRX sX 10 0000 xxxx 0000 1010 SRA sX 10 0000 xxxx 0000 1000 RR sX 10 0000 xxxx 0000 1100 SL0 sX 10 0000 xxxx 0000 0110 SL1 sX 10 0000 xxxx 0000 0111 SLX sX 10 0000 xxxx 0000 0010 SLA sX 10 0000 xxxx 0000 0000 RL sX 10 0000 xxxx 0000 0100
Une figure nous expliquera un peu ces instructions :
et vous en déduirez facilement ce qui se passe pour les décalages gauches.
- Instructions d'interruptions
instruction opérande 1 opérande 2 Op-code ENABLE INTERRUPT 11 1100 0000 0000 0001 DISABLE INTERRUPT 11 1100 0000 0000 0000 RETURNI ENABLE 11 1000 0000 0000 0001 RETURNI DISABLE 11 1000 0000 0000 0000
Comparaison des mnémoniques et directives
[modifier | modifier le wikicode]Si vous développez autour du PicoBlaze vous serez amené à utiliser plusieurs assembleurs très proches mais pas complètement similaires, celui du KCPSM3 et celui du simulateur PBlazeIDE. À noter que si vous travaillez sous Linux, vous n'aurez pas ce problème. En effet l'unique environnement kpicosim permet de simuler d’une part et de générer la ROM d’autre part.
Nous présentons donc maintenant les différents mnémoniques des deux environnements Windows. KCPSM3 est l'assembleur fourni par Xilinx pour générer les ROMs à partir du programme tandis que PBlazeIDE est un environnement de simulation que l’on peut télécharger gratuitement.
- Les différents mnémoniques du picoblaze
KCPSM3 PBlazeIDE addcy addc subcy subc compare comp store sX, (sY) store sX, sY fetch sX, (sY) fetch sX, sY input sX, (sY) in sX, sY input Sx,KK in sX,$KK ouput sX, (sY) out sX, sY return ret returni reti enable interrupt eint disable interrupt dint
Il existe d'autres différences avec les directives que nous présentons maintenant.
- Les différents directives du picoblaze
Fonction KCPSM3 PBlazeIDE positionnement de code address 3FF org $3FF constante constant MAX, 3F MAX equ $3F alias de registre namereg addr, s2 addr equ s2 alias de ports constant in_port, 00 in_port dsin $00 constant out_port 10 out_port dsout $10 constant bi_port, 0F bi_port dsio $0F
Mon premier programme en assembleur
[modifier | modifier le wikicode]Nous avons déjà eu l’occasion de présenter quelques champs à la section 3-1. Nous allons présenter d'autres possibilités maintenant. Regardez le programme ci-dessous :
CONSTANT m,10 ;commentaire ADDRESS 300 LOAD s3,m JUMP suite suite: LOAD ...
On distingue dans ce programme une étiquette , une définition symbolique, un commentaire et la définition de l'origine du programme. L'étiquette est un nom suivi de deux points, ici "suite:". La définition symbolique est m pour la valeur 10 (en hexadécimal). L'origine du programme est en adresse 300 (hexadécimal) ; on utilise la directive ADDRESS.
Les structures de contrôle
[modifier | modifier le wikicode]On présente maintenant les structures de contrôle pour le PicoBlaze. Les structures de contrôle utilisent l'instruction COMPARE qu’il faut bien connaître. On la rencontre sous deux formes :
COMPARE sX, sY; Compare sX et sY. COMPARE sX, kk; Compare sX et la constante kk.
Cette instruction est mieux comprise avec son pseudocode qui montre comment les deux drapeaux Carry et Zero sont affectés par cette instruction :
if ( Operand > sX ) then CARRY 1 else CARRY 0 endif if ( sX = Operand ) then ZERO 1 else ZERO 0 endif PC PC + 1
Vous allez pouvoir comprendre les cas particuliers présentés maintenant.
Structure si-alors-sinon
[modifier | modifier le wikicode]En écriture en langage C cette structure de contrôle test si sinon s'écrit :
if (s0==s1) {
// partie alors
}
else{
// partie autrement
}
En assembleur PicoBlaze cela s'écrit :
compare s0,s1 jump nz, branche_autrement ;code pour la branche alors jump if_fini branche_autrement: ;code pour la branche autrement if_fini: ;code suivant le if
Pour les autres sauts conditionnels voir dans la liste des instructions.
Structure répéter ... tant que ... et structure tant que...
[modifier | modifier le wikicode]On donne la structure correspondante dans plusieurs cas particuliers. Remarquez les difficultés pour le passage de s0>=5 à s0>5.
; tant que s0==s1 faire action tque: COMPARE s0,s1 JUMP NZ, suite ; s0-s1<>0 on va a suite ;action qui modifie s0 et/ou s1 JUMP tque suite: ....
La comparaison suivante est basée sur le fait qu'une comparaison est équivalente à une soustraction et qu'ainsi le drapeau C est positionné si et seulement si le résultat de la soustraction est strictement négatif. Attention ce résultat général n’est pas intuitif.
; tant que s0>=5 faire action tque: COMPARE s0,5 ; si s0-5<0 alors C=1 JUMP C, suite ; s0-5<0 on va a suite ;action qui modifie s0 JUMP tque suite: ....
La comparaison suivante est plus complexe à réaliser car aucune instruction de saut n'est conditionnée par les deux drapeaux Z et C simultanément. Un moyen d'y parvenir est d'inverser la comparaison ce qui nécessite de mettre 5 dans un registre !
; tant que s0>5 faire action LOAD s1,5 ; obligatoire pour inverser comparaison tque: COMPARE s1,s0 JUMP NC, suite ; s0-5<=0 on va a suite ;action qui modifie s0 JUMP tque suite: ....
Structure for ...
[modifier | modifier le wikicode]En utilisant le plus possible les définitions symboliques on peut réaliser ce type de boucle avec un programme du type :
; pour i variant de 1 à 15 ;(compris) faire action constant MAX, 15 namereg s0,i LOAD i, MAX boucle: ;code de boucle ... SUB i,01 JUMP NZ,boucle ;code suivant la boucle ...
L'adressage indexé
[modifier | modifier le wikicode]Indexé : l'adresse est calculée par le contenu du registre sX utilisé dans l'adressage. Ce registre est mis entre parenthèses dans ce cas. L'espace mémoire étant limité à 64 octets, seuls les 6 bits de poids faibles sont utilisés dans ce mode d'adressage.
00205 LOAD s2,05 ;boucle 5 fois (s2 compteur de boucle) 0188 07010 FETCH s0,(s1) ;charge le contenu donné par s1 dans s0 18300 ADD s3,s0 ;additionne 5F en hexadécimal 18101 ADD s1,01 ;permet d'aller à l'adresse suivante SUB S2,01 ;s2 <- s2-1 34188 JUMP NZ,188 ;saute à l'adresse 188 (sur {{Unité|10|bits}})
Les sous-programmes le switch et le PicoBlaze
[modifier | modifier le wikicode]Les sous-programmes
[modifier | modifier le wikicode]Les instructions CALL et RETURN sont destinées à l'appel de sous-programme et son retour. Expliquer le rôle de la pile pour les appels imbriqués. La pile ne comporte que 31 niveaux est n'est manipulable par aucune instruction spécifique. Ceci est inconvénient pour les langages évolués qui ne peuvent pas réaliser le passage de paramètres en utilisant la pile. Cela veut aussi dire qu’il n'y aura donc pas d'initialisation de la pile en début de programme.
La programmation de suivant le cas
[modifier | modifier le wikicode]La programmation du test selon s'écrit en C :
switch (variable) { /* variable est de type int ou char */
case valeur_1 : action_1; break;
case valeur_2 : action_2; break;
...
case valeur_n : action_n; break;
default : action_0;
}
Si action_n est un sous programme il faut le remplacer par action_n() en C. Les "break" imposent des conditions exclusives sur la variable. S'ils ne sont pas présents (car non obligatoires) l'algorithme exécuté peut se traduire : AU CAS OU
- condition 1, FAIRE : action_1 et toutes les suivantes
- condition 2, FAIRE : action_2 et toutes les suivantes
- condition 3, FAIRE : action_3 et toutes les suivantes
- condition 4, FAIRE : action_4 et la suivante
- DANS LES AUTRES CAS, FAIRE : action_0
Voici comment tout ceci se met en forme en assembleur PicoBlaze :
constant valeur_1, ... constant valeur_2, ... ... constant valeur_n, ... compare s0, valeur_1 jump nz case_2 ...; code pour action_1 jump case_done case_2: compare s0, valeur_2 jump nz case_3 ...; code pour action_2 jump case_done case_3: ...; etc case_n: compare s0, valeur_n jump nz default ...; code pour action_n jump case_done default: ...; code pour action_0 case_done: ...; code suivant le case
les actions "action_x" ne sont pas de vrais sous-programmes (appelés avec CALL finissant par RETURN).
Nous allons maintenant donner des exemples de morceau de code afin d'améliorer l'apprentissage de l'assembleur du picoBlaze.
Code utile pour le PicoBlaze
[modifier | modifier le wikicode]Nous allons donner un peu de code pour les personnes intéressées par développer du code.
Triple boucle d'attente
[modifier | modifier le wikicode]Voici un code qui réalise un sous-programme d'attente d'environ une seconde :
constant MAX, A0 NAMEREG s0, i NAMEREG s1, j NAMEREG s2, k ;...... ;...... ;=== triple boucle d'attente=== wait: LOAD i,MAX loop: LOAD j,MAX loop_1: LOAD k,MAX loop_2: SUB k,01 JUMP NZ, loop_2 SUB j,01 JUMP NZ, loop_1 SUB i,01 JUMP NZ,loop RETURN
Augmenter la valeur A0 augmentera la période.
incrémentation et décrémentation BCD
[modifier | modifier le wikicode]Lorsqu'on désire sortir le résultat d'un compteur sur des afficheurs sept segments il est intéressant d’avoir un compteur BCD. Voici un sous-programme qui réalise cela :
NAMEREG s5, cmpt ; compteur à afficher NAMEREG s9, test ;...... ;...... ;=== incrémente nb en BCD === incrementBCD: ;incrémente cmpt en BCD LOAD test,cmpt ; on ne bouge pas cmpt : test <- cmpt AND test,0F COMPARE test,09 JUMP NZ,suite_3 ADD cmpt,06 ; 06 classique en BCD suite_3: ADD cmpt,01 COMPARE cmpt,A0 JUMP NZ,suite_4 LOAD cmpt,00 suite_4: RETURN
Voici le petit frère qui décrémente :
NAMEREG s5, cmpt ; compteur à afficher NAMEREG s9, test ;...... ;...... ;=== decrémente nb en BCD === decrementBCD: ;decrémente nb en BCD LOAD test,cmpt ; on ne bouge pas nb : test <- nb AND test,0F JUMP NZ,suite_5 SUB cmpt,06 ; 06 classique en BCD suite_5: SUB cmpt,01 COMPARE cmpt,F9 JUMP NZ,suite_6 LOAD cmpt,99 suite_6: RETURN
Une fois cela calculé, il faut l'afficher et cela commence par un transcodage.
Transcodeur
[modifier | modifier le wikicode]Il n’est pas possible de mettre dans ce morceau de code tous les transcodages possibles et imaginables.
Le code donné dans cette section nécessite les déclarations :
constant MAX, FF namereg s0,i NAMEREG s1, octet_lsb NAMEREG s3, s7seg ; rename register s3 as “s7seg” NAMEREG s4, Aff
Tout commence par une initialisation. Ici on donne du code pour un afficheur sept segment en hexadécimal avec un '1' pour éteindre le segment.
La première ligne d'initialisation qui est sensée afficher '0' nous montre que le segment 'g' est poids faible.
debut: ; initialisation RAM : table de conversion LOAD s0,01 STORE s0,00 LOAD s0,4F STORE s0,01 LOAD s0,12 STORE s0,02 LOAD s0,06 STORE s0,03 LOAD s0,4C STORE s0,04 LOAD s0,24 STORE s0,05 LOAD s0,20 STORE s0,06 LOAD s0,0F STORE s0,07 LOAD s0,00 STORE s0,08 LOAD s0,04 STORE s0,09 LOAD s0,08 STORE s0,0A LOAD s0,60 STORE s0,0B LOAD s0,31 STORE s0,0C LOAD s0,42 STORE s0,0D LOAD s0,30 STORE s0,0E LOAD s0,38 STORE s0,0F
Une boucle infinie peut utiliser cela :
boucle: ;entree des Unité INPUT octet_lsb,0 AND octet_lsb,0F ;conversion par RAM FETCH s7seg,(octet_lsb) ;sortie 7 segs OUTPUT s7seg,0 LOAD Aff,FE ;sélection afficheur sur PORT différent ne marche donc pas avec module PMOD OUTPUT Aff,1 CALL wait
Maintenant au tour des poids forts et la fin de la boucle :
; maintenant poids forts INPUT octet_lsb,0 ; relecture SR0 octet_lsb SR0 octet_lsb SR0 octet_lsb SR0 octet_lsb AND octet_lsb,0F ;inutile en principe ;conversion par RAM FETCH s7seg,(octet_lsb) ;sortie 7 segs OUTPUT s7seg,0 LOAD Aff,FD ;sélection afficheur OUTPUT Aff,1 CALL wait JUMP boucle
Une routine d'attente même trop rapide peut fonctionner :
wait: LOAD i,MAX loop: SUB i,01 JUMP NZ,loop RETURN
La partie matérielle du PicoBlaze
[modifier | modifier le wikicode]Embarquer le PicoBlaze
[modifier | modifier le wikicode]Le PicoBlaze est un microcontrôleur destiné à être embarqué dans un FPGA. Ce qui caractérise ce type de composant est qu’il comporte en général peu de logique d'interfaçage vers l'extérieur (PORTS) et encore moins de logique interne traditionnelle (timer, comparaison, capture, ...). La raison à cela est qu’il va se trouver dans un FPGA où le développement de cette logique externe se fait très facilement. On peut donc l'adapter à notre propre problème. Cette absence de ports va rendre l'architecture de ce micro-contrôleur facile à comprendre. Commençons par présenter l'aspect fonctionnel du PicoBalze :
L'article Wikipédia sur le PicoBlaze vous donne l'entité équivalente à cette figure en VHDL.
La plus petite application consiste à lui ajouter une mémoire morte avec les instructions correspondantes dedans. Cela nous donne le schéma ci-dessous où toutes les entrées ainsi que les sorties seront reliées à de la logique du FPGA comme cela sera partiellement détaillé dans la section suivante.
Remarquez dans cette figure que la ROM est synchrone et qu'elle utilise la même horloge que le PicoBlaze.
Les entrées sorties
[modifier | modifier le wikicode]Nous allons apprendre dans ce chapitre à interfacer des ports au PicoBlaze. Commençons par les entrées.
Interfacer un PORT d'entrée
[modifier | modifier le wikicode]Le diagramme temporel correspondant est présenté maintenant.
Pour faire l'acquisition d'une valeur externe, il faut réaliser une instruction « INPUT » en précisant un numéro de port sur 8 bits. Il y a donc 256 ports possibles.
Le schéma donné ci-dessous est incorrect et sera corrigé plus tard. Il ne faut pas utiliser le signal "read_strobe" pour une lecture de PORT sauf dans le cas où l’on a un FIFO en entrée si l’on en croit la documentation Xilinx !!! La section suivante, par contre, est correcte : utilisation de "write_strobe" pour une écriture dans un PORT. Cela nous montre la dissymétrie entre une entrée et une sortie. |
On remarque que l'instruction « INPUT » est réalisée en deux fronts d'horloge (comme toutes les instructions) et qu'un signal READ_STROBE est disponible sur un seul front d'horloge. La partie matérielle peut être réalisée comme suit :
Dans cet exemple on dispose de 4 registres 8 bits pour lesquels on n'a pas dessiné les entrées arrivant de l'extérieur. Le multiplexeur ainsi que la logique combinatoire de décodage peuvent n'utiliser que les 2 bits de poids faible du signal PORT_ID. Il est possible de rajouter un registre entre le multiplexeur et l'entrée du PicoBlaze pour augmenter les performances (pipeline).
On donne un exemple de programme VHDL correspondant aux deux composants ajoutés, le multiplexeur et la logique de décodage ainsi qu'une partie du registre.
-- multiplexeur
with port_id(1 downto 0) select
data <= indata_0 when "00",
indata_1 when "01",
indata_2 when "10",
indata_3 when "11";
-- circuit décodage
process(read_strobe,port_id) begin
if read_strobe='0' then
rv <= "0000";
else
case port_id(1 downto 0) is
when "00" => rv <= "0001";
when "01" => rv <= "0010";
when "10" => rv <= "0100";
when others => rv <= "1000";
end case;
end if;
end process;
-- registre {{Unité|8|bits}}
-- A mettre dans une architecture avec l'entité correspondante
-- car on l’utilisera 4 fois
process(clk) begin
if clk'event and clk='1' then
if rd = '1' then
r_data <= d;
end if;
end if;
end process;
Interfacer un PORT de sortie
[modifier | modifier le wikicode]Le diagramme temporel de l'instruction OUTPUT est donnée ci-dessous.
On n'a pas représenté les signaux de sorties du circuit de décodage mais ils sont légèrement décalés par rapport à WRITE_STROBE puisqu’ils l'utilisent. Nous montrons ci-dessous un exemple de circuit pouvant réaliser une sortie sur un des quatre ports présents :
Pour quatre ports seuls les deux bits de poids faible de PORT_ID[7:0] peuvent être utilisés. On donne pour une meilleure compréhension les programmes VHDL correspondants :
-- circuit décodage
process(write_strobe,port_id) begin
if write_strobe='0' then
en_d <= "0000";
else
case port_id(1 downto 0) is
when "00" => en_d <= "0001";
when "01" => en_d <= "0010";
when "10" => en_d <= "0100";
when others => en_d <= "1000";
end case;
end if;
end process;
Remarquons qu’il est possible de simplifier largement le décodage si on utilise comme numéros de PORT : 1, 2, 4 et 8 (au lieu de 1, 2, 3 et 4) et du coup les 4 bits de poids faible de PORT_ID[7:0] :
-- circuit décodage simplifié
process(write_strobe,port_id) begin
if write_strobe='0' then
en_d <= "0000";
else
en_d <= port_id(3 downto 0);
end if;
end process;
Un autre problème va nous intéresser maintenant : les interruptions.
Les interruptions
[modifier | modifier le wikicode]La gestion des interruptions est en général très dépendante de l’application à réaliser. Encore une fois le PicoBlaze ne comprend par défaut que le minimum de logique pour gérer les interruptions. Quand on dit minimum, c’est le minimum : même pas de gestion de timer par exemple. C'est à l'utilisateur de réaliser la logique correspondante chargée de réaliser le seul signal « INTERRUPT » présent (une seule source d'interruption donc par défaut). L'activation de ce signal force le PicoBlaze à réaliser un saut vers l'adresse 3FF (dernière adresse de la mémoire programme). À noter qu'après un RESET le signal d'interruption est inactif : une instruction « ENABLE INTERRUPT » doit être réalisée pour l'activer. À noter que l'exécution d'une interruption préserve les drapeaux C et Z du registre d'état.
Partie logicielle
[modifier | modifier le wikicode]L'architecture d'un programme assembleur gérant une interruption sera comme :
Lorsqu'une interruption (1) arrive, le programme est dérouté (2) à l'adresse 3FF. Cette adresse branche (3) à la routine d'interruption proprement dite. Puis quand la routine est terminée on revient là où l’on a été interrompu (4).
Chronogramme
[modifier | modifier le wikicode]La routine d'interruption n’est pas instantanée dès réception du signal d'interruption. L'iinstruction en cours continue à s'exécuter puis une fois terminée l’ensemble des opérations décrites dans la section précédente sont synchronisées avec l'horloge comme indiqué sur le chronogramme suivant :
Partie matérielle
[modifier | modifier le wikicode]Un exemple simple comprenant une seule source d'interruption est présenté maintenant.
Les signaux SET et RESET sont asynchrones.
Le schéma ci-dessus trouvé dans la documentation Xilinx ne fonctionne pas correctement si la durée du signal d'interruption est trop courte (et si la bascule D comporte une horloge). Nous remplacerons ce schéma à l’occasion en faisant disparaître ce bandeau. |
L'entrée "interrupt" du picoBlaze est active sur niveau haut. Les interruptions doivent être autorisées à l'intérieur du programme avec une instruction "ENABLE INTERRUPT" pour que cette entrée soit prise en compte. Il est bon de se souvenir aussi que quand le picoBlaze commence l'exécution d'une interruption, il désactive automatiquement les interruptions. Cela restera ainsi sauf si votre retour d'interruption est réalisé avec un "RETURNI ENABLE" comme instruction.
Voici un morceau de code VHDL donné par Ken Chapman dans le forum picoBlaze pour gérer les interruptions :
interrupt_control: process(clk)
begin
if clk'event and clk='1' then
-- Detect rising edge and generate a pulse
extint_delay <= extint;
if (extint = '1' and extint_delay = '0') then
interrupt_event <= '1';
else
interrupt_event <= '0';
end if;
-- Drive interrupt until acknowledged by PicoBlaze
if interrupt_event = '1' then
interrupt <= '1';
else
if interrupt_ack = '1' then
interrupt <= '0';
else
interrupt <= interrupt;
end if;
end if;
end if;
end process interrupt_control;
Ce code ne fonctionne correctement que si le signal "extint" est synchrone.
Voir aussi
[modifier | modifier le wikicode]Liens internes
[modifier | modifier le wikicode]liens externes
[modifier | modifier le wikicode]- copyBlaze un clone du picoBlaze qui supporte une interface de type Wishbone. Deux instructions ont été ajoutées au picoBlaze pour gérer le bus wishbone.
- Assembleur picoBlaze donne des indications sur la façon d’utiliser data2mem puisqu’un fichier BMM est donné.
- pbcc un compilateur C pour picoBlaze dérivé de sdcc. C'est une bonne nouvelle.
- « mcx16 » (Archive • Wikiwix • Que faire ?). Consulté le 2014-09-19 un clone du picoBlaze sur 16 bits
Exercices
[modifier | modifier le wikicode]Exercice 1
[modifier | modifier le wikicode]Soit le programme suivant :
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;
entity top is port (
entrees : in std_logic_vector(7 downto 0);
sorties : out std_logic_vector(7 downto 0);
clk,reset : in std_logic
);
end entity;
architecture behavior of top is
component kcpsm3 is
Port ( address : out std_logic_vector(9 downto 0);
instruction : in std_logic_vector(17 downto 0);
port_id : out std_logic_vector(7 downto 0);
write_strobe : out std_logic;
out_port : out std_logic_vector(7 downto 0);
read_strobe : out std_logic;
in_port : in std_logic_vector(7 downto 0);
interrupt : in std_logic;
interrupt_ack : out std_logic;
reset : in std_logic;
clk : in std_logic);
end component;
component mpu_rom is
Port ( address : in std_logic_vector(9 downto 0);
instruction : out std_logic_vector(17 downto 0);
clk : in std_logic);
end component;
signal address : std_logic_vector(9 downto 0);
signal instruction : std_logic_vector(17 downto 0);
signal ws,rs: std_logic;
signal s_sorties,s_entrees: std_logic_vector(7 downto 0);
begin
ic1 : kcpsm3 port map (
address => address,
instruction => instruction,
port_id => open,
write_strobe => ws,
out_port => s_sorties,
read_strobe => rs,
in_port => s_entrees,
interrupt => '0',
interrupt_ack => open,
reset => reset,
clk => clk);
ic2 : mpu_rom port map (
address=> address,
instruction=> instruction,
clk=> clk);
-- bascule D en entree
process(clk) begin
if rising_edge(clk) then
if rs='1' then
s_entrees <= entrees;
end if;
end if;
end process;
-- bascule D en sortie
process(clk) begin
if rising_edge(clk) then
if ws='1' then
sorties <= s_sorties;
end if;
end if;
end process;
end behavior;
Réaliser le schéma correspondant à cette architecture.
Exercice 2
[modifier | modifier le wikicode]ANNEXE
[modifier | modifier le wikicode]Nous avons utilisé kpicosim pour les TPs... mais kpicosim ne s'installe plus sur les nouvelles versions de Linux. Voici donc un autre moyen de générer la ROM pour picoBlaze.
Utiliser picoasm pour générer la ROM VHDL prog_rom.vhd
[modifier | modifier le wikicode]picoasm est un assembleur non graphique. Son code source se trouve facilement sur internet. Il tourne donc sur les nouvelles versions de Linux (contrairement à kpicosim) et probablement sous Windows. S'il est installé dans un sous-répertoire "soft" du projet picoBlaze, il se lance avec la commande :
./picoasm -i prog1.psm -t ROM_form.vhd -m prog_rom -d ../
Il nécessite, comme son cousin graphique kpicosim, un fichier de modèle appelé ici "ROM_form.vhd" et génère un fichier "prog_rom.vhd".