Projet | Quatrième SoC écrit en VHDL |
---|---|
Auteur | Richard James Howe |
Droit d'auteur | 2013-2019 Richard Howe |
Licence | MIT/LGPL |
[email protected] |
Ce projet implémente un ordinateur à petite pile conçu pour exécuter Forth basé sur le processeur J1. Le processeur a été réécrit en VHDL de Verilog et légèrement étendu.
Les objectifs du projet sont les suivants :
Tous trois sont terminés.
Le processeur H2, comme le J1, est un processeur basé sur une pile qui exécute un jeu d'instructions particulièrement adapté à FORTH.
La cible actuelle est la carte Nexys3, avec un FPGA Xilinx Spartan-6 XC6LX16-CS324, de nouvelles cartes seront ciblées à l'avenir car cette carte arrive en fin de vie. Le VHDL est écrit de manière générique, les composants matériels étant déduits au lieu d'être explicitement instanciés, cela devrait rendre le code assez portable, bien que les interfaces des composants de la carte Nexys3 soient spécifiques aux périphériques de cette carte.
Une vidéo du projet en action, sur le matériel, peut être visionnée ici :
Le SoC peut également être simulé avec un simulateur écrit en C, comme indiqué ci-dessous :
L'architecture du système est la suivante :
Les licences utilisées par le projet sont mixtes et par fichier. Pour mon code, j'utilise la licence MIT - alors n'hésitez pas à l'utiliser comme vous le souhaitez. Les autres licences utilisées sont la licence LGPL et Apache 2.0, elles sont limitées à des modules uniques et peuvent donc être supprimées si vous avez une certaine aversion pour le code LGPL.
La seule carte cible disponible pour le moment est la Nexys3, cela devrait changer dans le futur car la carte est actuellement en fin de vie. Les prochaines cartes que je cherche à prendre en charge sont son successeur, la Nexys 4, et la myStorm BlackIce (https://mystorm.uk/). La carte myStorm utilise une chaîne d'outils entièrement open source pour la synthèse, le placement, le routage et la génération de fichiers de bits.
La version a été testée sous Debian Linux, version 8.
Vous aurez besoin de :
Matériel:
Xilinx ISE peut (ou pourrait être) téléchargé gratuitement, mais nécessite une inscription. ISE doit être sur votre chemin :
PATH=$PATH:/opt/Xilinx/14.7/ISE_DS/ISE/bin/lin64;
PATH=$PATH:/opt/Xilinx/14.7/ISE_DS/ISE/lib/lin64;
Pour créer la chaîne d'outils basée sur C :
make embed.hex
Pour créer un fichier bit pouvant être flashé sur la carte cible :
make simulation synthesis implementation bitfile
Pour télécharger le fichier bit sur la carte cible :
make upload
Pour visualiser la forme d'onde générée par "make simulation" :
make viewer
Le simulateur CLI basé sur C peut être invoqué avec :
make run
Ce qui assemblera le fichier source H2 Forth embed.fth et exécutera le fichier objet assemblé sous le simulateur H2 avec le débogueur activé. Un simulateur graphique peut être exécuté avec :
make gui-run
Ce qui nécessite freeglut ainsi qu'un compilateur C.
Le projet J1 original est disponible sur :
Ce projet cible le noyau J1 d'origine et fournit une implémentation eForth (écrite en utilisant Gforth comme pour la méta-compilation/compilation croisée vers le noyau J1). Il fournit également un simulateur pour le système écrit en C.
L'interpréteur eForth sur lequel le méta-compilateur est construit peut être trouvé à l'adresse :
Le processeur H2 et les périphériques associés sont désormais assez stables, mais la source est toujours le guide définitif sur le comportement des instructions et des périphériques, ainsi que sur la carte des registres.
Il y a quelques modifications apportées au processeur J1, notamment :
Le CPU H2 se comporte de manière très similaire au CPU J1, et le PDF J1 peut être lu afin de mieux comprendre ce processeur. Le processeur est de 16 bits avec des instructions prenant un seul cycle d'horloge. La plupart des mots primitifs du Forth peuvent également être exécutés en un seul cycle, une exception notable est le magasin ("!"), qui est divisé en deux instructions.
Le processeur a l'état suivant :
Les charges et les stockages dans le bloc RAM qui contient le programme H2 éliminent le bit le plus bas, toutes les autres opérations de mémoire utilisent le bit le plus faible (comme les sauts et les charges et les stockages sur les périphériques d'entrée/sortie). Cela permet aux applications d'utiliser le bit le plus bas pour les opérations sur les caractères lors de l'accès à la RAM du programme.
Le jeu d’instructions est décodé de la manière suivante :
+---------------------------------------------------------------+
| F | E | D | C | B | A | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+---------------------------------------------------------------+
| 1 | LITERAL VALUE |
+---------------------------------------------------------------+
| 0 | 0 | 0 | BRANCH TARGET ADDRESS |
+---------------------------------------------------------------+
| 0 | 0 | 1 | CONDITIONAL BRANCH TARGET ADDRESS |
+---------------------------------------------------------------+
| 0 | 1 | 0 | CALL TARGET ADDRESS |
+---------------------------------------------------------------+
| 0 | 1 | 1 | ALU OPERATION |T2N|T2R|N2A|R2P| RSTACK| DSTACK|
+---------------------------------------------------------------+
| F | E | D | C | B | A | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+---------------------------------------------------------------+
T : Top of data stack
N : Next on data stack
PC : Program Counter
LITERAL VALUES : push a value onto the data stack
CONDITIONAL : BRANCHS pop and test the T
CALLS : PC+1 onto the return stack
T2N : Move T to N
T2R : Move T to top of return stack
N2A : STORE T to memory location addressed by N
R2P : Move top of return stack to PC
RSTACK and DSTACK are signed values (twos compliment) that are
the stack delta (the amount to increment or decrement the stack
by for their respective stacks: return and data)
Toutes les opérations ALU remplacent T :
Valeur | Opération | Description |
---|---|---|
0 | T | Haut de la pile |
1 | N | Copier T vers N |
2 | T + N | Ajout |
3 | T&N | ET au niveau du bit |
4 | Déchiré | OU au niveau du bit |
5 | T^N | XOR au niveau du bit |
6 | ~T | Inversion au niveau du bit |
7 | T = N | Test d'égalité |
8 | N < T | Comparaison signée |
9 | N >> T | Décalage logique vers la droite |
10 | T-1 | Décrémenter |
11 | R. | Haut de la pile de retour |
12 | [T] | Charger à partir de l'adresse |
13 | N << T | Décalage logique à gauche |
14 | profondeur | Profondeur de pile |
15 | N u< T | Comparaison non signée |
16 | Définir l'état du processeur | Activer les interruptions |
17 | Obtenir l'état du processeur | Les interruptions sont-elles activées ? |
18 | profondeur | Profondeur de retour stk |
19 | 0= | T == 0 ? |
20 | ID du processeur | Identifiant du processeur |
21 | LITTÉRAL | Instructions internes |
Les registres marqués d'un préfixe « o » sont des registres de sortie, ceux avec un préfixe « i » sont des registres d'entrée. Les registres sont divisés en une section de registres d'entrée et de sortie et les adresses des registres d'entrée et de sortie ne correspondent pas dans tous les cas.
Les périphériques suivants ont été implémentés dans le SoC VHDL pour s'interfacer avec les appareils de la carte Nexys3 :
Le SoC propose également un ensemble limité d'interruptions qui peuvent être activées ou désactivées.
La carte du registre de sortie :
Registre | Adresse | Description |
---|---|---|
oUart | 0x4000 | Registre UART |
oVT100 | 0x4002 | Écriture du terminal VT100 |
oLed | 0x4004 | Sorties LED |
oTimerCtrl | 0x4006 | Contrôle de la minuterie |
oMemDout | 0x4008 | Sortie de données mémoire |
oMemControl | 0x400A | Contrôle de la mémoire/adresse haute |
oMemAddrLow | 0x400C | Adresse mémoire basse |
o7SegLED | 0x400E | 4 x affichage LED à 7 segments |
oIrcMasque | 0x4010 | Masque d'interruption du processeur |
oUartBaudTx | 0x4012 | Réglage de l'horloge UART Tx Baud |
oUartBaudRx | 0x4014 | Réglage de l'horloge en bauds UART Rx |
L'entrée enregistre :
Registre | Adresse | Description |
---|---|---|
iUart | 0x4000 | Registre UART |
iVT100 | 0x4002 | État du terminal et clavier PS/2 |
iCommutateurs | 0x4004 | Boutons et interrupteurs |
iTimerDin | 0x4006 | Valeur actuelle de la minuterie |
iMemDin | 0x4008 | Entrée de données en mémoire |
La description suivante des registres doit être lue dans l'ordre et décrit également le fonctionnement des périphériques.
Un UART avec un débit en bauds et un format fixes (115200, 8 bits, 1 bit d'arrêt) est présent sur le SoC. L'UART dispose d'un FIFO de profondeur 8 sur les canaux RX et TX. Le contrôle de l'UART est réparti entre oUart et iUart.
Pour écrire une valeur dans l'UART, affirmez TXWE tout en plaçant les données dans TXDO. L'état FIFO peut être analysé en consultant le registre iUart.
Pour lire une valeur de l'UART : iUart peut être vérifié pour voir si des données sont présentes dans le FIFO, si elle est affirmée RXRE dans le registre oUart, au cycle d'horloge suivant, les données seront présentes dans le registre iUart.
Le débit en bauds de l'UART peut être modifié en reconstruisant le projet VHDL, la longueur des bits, les bits de parité et les bits d'arrêt ne peuvent être modifiés qu'en modifiant uart.vhd
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| X | X |TXWE| X | X |RXRE| X | X | TXDO |
+-------------------------------------------------------------------------------+
TXWE: UART TX Write Enable
RXRE: UART RX Read Enable
TXDO: UART TX Data Output
Le périphérique VGA Text émule un terminal avec lequel l'utilisateur peut parler en écrivant dans le registre oVT100. Il prend en charge un sous-ensemble des fonctionnalités du terminal VT100. L'interface se comporte un peu comme si vous écriviez sur un UART avec les mêmes signaux d'occupation et de contrôle. L'entrée provient d'un clavier PS/2 disponible sur la carte, celui-ci se comporte comme le mécanisme RX de l'UART.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| X | X |TXWE| X | X |RXRE| X | X | TXDO |
+-------------------------------------------------------------------------------+
TXWE: VT100 TX Write Enable
RXRE: UART RX Read Enable
TXDO: UART TX Data Output
Sur la carte Nexys3 se trouve une banque de LED situées à côté des commutateurs, ces LED peuvent être allumées (1) ou éteintes (0) en écrivant dans LEDO. Chaque LED correspond ici à l'interrupteur à côté duquel elle se trouve.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| X | X | X | X | X | X | X | X | LEDO |
+-------------------------------------------------------------------------------+
LEDO: LED Output
La minuterie est contrôlable par le registre oTimerCtrl, il s'agit d'une minuterie 13 bits fonctionnant à 100 MHz, elle peut éventuellement générer des interruptions et le décompte interne des minuteries actuelles peut être relu avec le registre iTimerDin.
Le temporisateur compte une fois que le bit TE est affirmé, une fois que le temporisateur atteint la valeur TCMP, il revient en boucle et peut éventuellement générer une interruption en affirmant INTE. Cela bascule également les lignes Q et NQ qui sortent du timer et sont acheminées vers les broches de la carte (voir le fichier de contraintes top.ucf pour les broches).
La minuterie peut être réinitialisée en écrivant dans RST.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| TE | RST|INTE| TCMP |
+-------------------------------------------------------------------------------+
TE: Timer Enable
RST: Timer Reset
INTE: Interrupt Enable
TCMP: Timer Compare Value
Le noyau H2 dispose d'un mécanisme pour les interruptions, les interruptions doivent être activées ou désactivées avec une instruction. Chaque interruption peut être masquée avec un bit dans IMSK pour activer cette interruption spécifique. Un « 1 » dans un bit d'IMSK active cette interruption spécifique, qui sera délivrée au CPU si des interruptions y sont activées.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| X | X | X | X | X | X | X | X | IMSK |
+-------------------------------------------------------------------------------+
IMSK: Interrupt Mask
Ce registre est utilisé pour définir la fréquence en bauds et l'horloge d'échantillonnage pour la transmission uniquement.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| BTXC |
+-------------------------------------------------------------------------------+
BTXC: Baud Clock Settings
Ce registre est utilisé pour définir la fréquence en bauds et l'horloge d'échantillonnage pour la réception uniquement.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| BRXC |
+-------------------------------------------------------------------------------+
BRXC: Baud Clock Settings
Données à sortir à l'adresse sélectionnée lorsque l'autorisation d'écriture (WE) est émise dans oMemControl.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| Data Ouput |
+-------------------------------------------------------------------------------+
Ce registre contient les registres de contrôle de la mémoire embarquée sur la carte Nexys3. La carte contient trois dispositifs de mémoire, deux dispositifs de mémoire non volatile et un dispositif basé sur la RAM volatile. Les deux appareils accessibles par une simple interface SRAM (un volatile M45W8MW16, un non volatile - un NP8P128A13T1760E) sont tous deux accessibles, le troisième est un dispositif de mémoire basé sur SPI, NP5Q128A13ESFC0E) et n'est actuellement pas accessible.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| OE | WE | RST|WAIT| RCS| FCS| Address Hi |
+-------------------------------------------------------------------------------+
OE: Output Enable - enable reading from current address into iMemDin
WE: Write Enable - enable writing oMemDout into ram at current address
RST: Reset the Flash memory controller
RCS: RAM Chip Select, Enable Volatile Memory
FCS: Flash Chip Select, Enable Non-Volatile Memory
Address Hi: High Bits of RAM address
OE et WE s'excluent mutuellement ; si les deux sont définis, il n'y a aucun effet.
Le contrôleur de mémoire est en développement actif et son interface pourrait changer.
Il s'agit des bits d'adresse inférieurs de la RAM.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| Address Lo |
+-------------------------------------------------------------------------------+
Sur la carte Nexys3 se trouve une banque d'afficheurs à 7 segments, avec un point décimal (8 segments en réalité), qui peuvent être utilisés pour la sortie numérique. Les segments LED ne peuvent pas être directement adressés. Au lieu de cela, la valeur stockée dans L8SD est mappée sur une valeur d'affichage hexadécimale (ou une valeur BCD, mais cela nécessite une régénération du SoC et une modification d'un générique dans le VHDL).
La valeur '0' correspond à un zéro affiché sur le segment LED, '15' à un 'F', etc.
Il y a 4 affichages d'affilée.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| L7SD0 | L7SD1 | L7SD2 | L7SD3 |
+-------------------------------------------------------------------------------+
L7SD0: LED 7 Segment Display (leftmost display)
L7SD1: LED 7 Segment Display
L7SD2: LED 7 Segment Display
L7SD3: LED 7 Segment Display (right most display)
Le registre iUart fonctionne en conjonction avec le registre oUart. L'état du FIFO qui met en mémoire tampon à la fois la transmission et la réception des octets est disponible dans le registre iUart, ainsi que tous les octets reçus.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| X | X | X |TFFL|TFEM| X |RFFL|RFEM| RXDI |
+-------------------------------------------------------------------------------+
TFFL: UART TX FIFO Full
TFEM: UART TX FIFO Empty
RFFL: UART RX FIFO Full
RFEM: UART RX FIFO Empty
RXDI: UART RX Data Input
Le registre iVT100 fonctionne conjointement avec le registre oVT100. L'état du FIFO qui met en mémoire tampon la transmission et la réception des octets est disponible dans le registre iVT100, ainsi que tous les octets reçus. Cela fonctionne de la même manière que les registres iUart/oUart.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| X | X | X |TFFL|TFEM| X |RFFL|RFEM| 0 | ACHR |
+-------------------------------------------------------------------------------+
TFFL: VGA VT100 TX FIFO Full
TFEM: VGA VT100 TX FIFO Empty
RFFL: PS2 VT100 RX FIFO Full
RFEM: PS2 VT100 RX FIFO Empty
ACHR: New character available on PS2 Keyboard
Ce registre contient la valeur actuelle du compteur des minuteries.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| X | X | X | TCNT |
+-------------------------------------------------------------------------------+
TCNT: Timer Counter Value
iSwitches contient des lignes d'entrée provenant de plusieurs sources. Les boutons (BUP, BDWN, BLFT, BRGH et BCNT) correspondent à un D-Pad sur la carte Nexys3. Les interrupteurs (TSWI) sont ceux mentionnés dans oLeds, chacun a une LED à côté d'eux.
Les commutateurs et les boutons sont déjà anti-rebond dans le matériel, ils n'ont donc pas besoin d'être traités davantage une fois lus à partir de ces registres.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| X | X | X | BUP|BDWN|BLFT|BRGH|BCNT| TSWI |
+-------------------------------------------------------------------------------+
BUP: Button Up
BDWN: Button Down
BLFT: Button Left
BRGH: Button Right
BCNT: Button Center
TSWI: Two Position Switches
Entrée mémoire, soit à partir de la SRAM ou de Flash, indexée par oMemControl et oMemAddrLow. Lors de la lecture à partir du flash, il peut s'agir en fait d'informations d'état ou d'informations provenant de la table de requête.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| Data Input |
+-------------------------------------------------------------------------------+
Les routines de service d'interruption suivantes sont définies :
Nom | Nombre | Description |
---|---|---|
isrAucun | 0 | Non utilisé |
isrRxFifoNotEmpty | 1 | UART RX FIFO n'est pas vide |
isrRxFifoFull | 2 | UART RX FIFI est plein |
isrTxFifoNotEmpty | 3 | UART TX FIFO n'est pas vide |
isrTxFifoFull | 4 | UART TX FIFO est plein |
isrKbdNouveau | 5 | Nouveau personnage du clavier PS/2 |
isrTimer | 6 | Compteur de minuterie |
isrDPadButton | 7 | Tout bouton du D-Pad change d’état |
Lorsqu'une interruption se produit et que les interruptions sont activées dans le processeur, un appel à l'emplacement en mémoire est effectué - l'emplacement est le même que le numéro ISR. Un ISR portant le numéro « 4 » effectuera un appel (pas un saut) vers l'emplacement « 4 » en mémoire, par exemple.
Les interruptions ont une latence d'au moins 4 à 5 cycles avant d'être traitées, il y a un délai de deux à trois cycles dans le gestionnaire de demande d'interruption, puis l'appel à l'emplacement ISR en mémoire doit être effectué, puis l'appel au mot qui met en œuvre l’ISR lui-même.
Si deux interruptions se produisent en même temps, elles sont traitées du numéro d'interruption le plus bas au plus élevé.
Les interruptions sont perdues lorsqu'une interruption portant le même numéro se produit et qui n'a pas été traitée.
Le désassembleur et le simulateur basé sur C pour le H2 sont regroupés dans un seul programme (voir h2.c). Ce simulateur complète le banc de test VHDL tb.vhd et ne le remplace pas. Le méta-compilateur s'exécute sur un interpréteur eForth et est contenu dans les fichiers embed.c et embed.blk. Le méta-compilateur (en langage Forth pour un compilateur croisé) est un programme Forth qui est utilisé pour créer l'image eForth qui s'exécute sur la cible.
La chaîne d'outils est actuellement en évolution, et à l'avenir, il est probable qu'il y ait davantage d'intégration entre h2.c et embed.c, ainsi que la transformation de la machine virtuelle intégrée en une machine qui ressemble plus au processeur H2 dans le but à long terme de créer un auto-hébergement. système.
Pour construire les deux, un compilateur C est nécessaire, la cible de build "h2" construira l'exécutable, h2, et "embed" construira le méta-compilateur :
make h2 embed
Et il peut être exécuté sur le fichier source embed.fth avec la cible make :
make run
Le fichier make n'est pas nécessaire :
Linux:
cc -std=c99 h2.c -o h2 # To build the h2 executable
cc -std=c99 embed.c -o embed # To build the embed VM executable
./embed embed.blk embed.hex embed.fth # Create the target eForth image
./h2 -h # For a list of options
./h2 -r embed.hex # Run the assembled file
Windows:
gcc -std=c99 h2.c -o h2.exe # Builds the h2.exe executable
gcc -std=c99 embed.c -o embed.exe # Builds the embed.exe executable
embed.exe embed.blk embed.hex embed.fth # Create the target eForth iamge
h2.exe -h # For a list of options
h2.exe -r embed.hex # Run the assembled file
Une liste des options de ligne de commande disponibles :
- stop processing options, following arguments are files
-h print a help message and exit
-v increase logging level
-d disassemble input files (default)
-D full disassembly of input files
-T Enter debug mode when running simulation
-r run hex file
-L # load symbol file
-s # number of steps to run simulation (0 = forever)
-n # specify NVRAM block file (default is nvram.blk)
file* file to process
Ce programme est publié sous licence MIT, n'hésitez pas à l'utiliser et à le modifier à votre guise. Avec un minimum de modifications, il devrait être capable d'assembler des programmes pour le noyau J1 d'origine.
Le méta-compilateur s'exécute au-dessus de la machine virtuelle intégrée, il s'agit d'une machine virtuelle 16 bits issue à l'origine du processeur H2. Le projet comprend un schéma de méta-compilation qui permet à une image eForth de générer une nouvelle image eForth avec des modifications. Ce système a été adapté pour être utilisé avec le H2, qui a remplacé le compilateur croisé écrit en C, qui permettait de créer la première image du H2.
Le méta-compilateur est un programme Forth ordinaire, il est contenu dans embed.fth. Le programme du méta-compilateur Forth est ensuite utilisé pour créer une image eForth capable de s'exécuter sur la cible H2.
Pour plus d'informations sur la méta-compilation dans Forth, consultez :
Le désassembleur prend un fichier texte contenant le programme assemblé, composé de nombres hexadécimaux de 16 bits. Il tente ensuite de démonter les instructions. Il peut également être alimenté par un fichier de symboles qui peut être généré par l'assembleur et tenter de trouver les emplacements vers lesquels pointent les sauts et les appels.
Le désassembleur est utilisé par un script tcl appelé par GTKwave, il transforme la trace d'instruction du H2 à partir d'une série de nombres en instructions et destinations de branchement qu'ils représentent. Cela rend le débogage du VHDL beaucoup plus facile.
La trace violette montre les instructions démontées.
Le simulateur en C implémente le cœur H2 et la plupart des SoC. Les E/S du simulateur ne sont pas précises en termes de cycle, mais peuvent être utilisées pour exécuter et déboguer des programmes avec des résultats très similaires au comportement du matériel. C'est beaucoup plus rapide que de reconstruire le fichier bit utilisé pour flasher le FPGA.
Le simulateur comprend également un débogueur, conçu pour être similaire au programme DEBUG.COM disponible sous DOS. Le débogueur peut être utilisé pour démonter des sections de mémoire, inspecter l'état des périphériques et vider des sections de mémoire sur l'écran. Il peut également être utilisé pour définir des points d'arrêt, une seule étape et parcourir le code jusqu'à ce qu'un point d'arrêt soit atteint.
Pour exécuter le débogueur, un fichier hexadécimal ou un fichier source doit être fourni :
# -T turns debugging mode on
./h2 -T -r file.hex # Run simulator
Les deux modes de fonctionnement peuvent être complétés par un fichier de symboles, qui répertorie l'emplacement des variables, des étiquettes et des fonctions avec le noyau assemblé.
Lorsque l'option "-T" est donnée, le mode débogage sera activé avant l'exécution de la simulation. Une invite devrait apparaître et la ligne de commande devrait ressembler à ceci :
$ ./h2 -T -R h2.fth
Debugger running, type 'h' for a list of command
debug>
Les points d'arrêt peuvent être définis symboliquement ou par emplacement du programme, la commande 'b' est utilisée pour définir les points d'arrêt :
Les nombres peuvent être saisis en octal (préfixez le nombre avec « 0 »), en hexadécimal (préfixez avec « 0x ») ou en décimal. À titre d'exemple, les trois commandes de débogage suivantes définissent toutes un point d'arrêt au même emplacement :
debug> b 16
debug> b 0x10
debug> b 020
« k » peut être utilisé pour répertorier les points d'arrêt actuellement définis :
debug> k
0x0010
Ceci définit un point d'arrêt lorsque la fonction "key?" s'appelle :
debug> b key?
Les fonctions et les étiquettes peuvent toutes deux être arrêtées, cela nécessite soit qu'un fichier de symboles soit spécifié sur la ligne de commande, soit qu'il soit assemblé et exécuté pour être utilisé sur un fichier source, pas un fichier hexadécimal. Les fichiers de symboles peuvent être utilisés sur des fichiers source ou hexadécimaux.
Pour effectuer une seule étape, la commande « s » peut être donnée, bien que peu de choses se passeront si le traçage est désactivé (le traçage est désactivé par défaut). Le traçage peut être activé ou désactivé avec la commande « t » :
debug> s
debug> s
debug> t
trace on
debug> s
0001: pc(089a) inst(4889) sp(0) rp(0) tos(0000) r(0000) call 889 init
debug> s
0002: pc(0889) inst(807a) sp(0) rp(1) tos(0000) r(089b) 7a
debug> s
0003: pc(088a) inst(e004) sp(1) rp(1) tos(007a) r(089b) 6004
Il est conseillé de désactiver le traçage lors de l'exécution de la commande « c » ou continuer.
Le '.' La commande peut être utilisée pour afficher l’état interne des cœurs H2 :
debug> .
Return Stack:
0000: 0000 08aa 0883 017b 0000 031b 0000 ffb0 0000 02eb ffb5 0210 0167 0167
0167 0167
0010: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
0000 0000
Variable Stack:
tos: 0000
0001: 0000 0000 0000 0001 0004 0005 0000 ffb0 0000 0000 0000 0000 0000 0000
0000 0000
0011: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
0000 0000
pc: 0538
rp: 0001
dp: 0000
ie: false
Et la commande 'p' peut être utilisée pour afficher l'état des périphériques simulés :
debug> p
LEDS: 00
VGA Cursor: 0005
VGA Control: 007a
Timer Control: 8032
Timer: 001b
IRC Mask: 0000
UART Input: 6c
LED 7seg: 0005
Switches: 00
LFSR: 40ba
Waiting: false
Pour une liste complète des commandes, utilisez la commande « h ».
D'autres façons d'entrer en mode débogage incluent l'insertion de la directive assembleur ".break" dans le code source (cela ne fonctionne que si la commande assemble and run est utilisée sur les fichiers source, pas sur les fichiers hexadécimaux) et l'utilisation du caractère d'échappement lorsque le simulateur est essayer de lire des données via le clavier UART ou PS/2 simulé (l'échappement sera toujours transmis au simulateur, mais il active également le mode débogage).
Un programme séparé peut être compilé, testé sous Linux et Windows. Cela simule les périphériques de la carte Nexys3 avec lesquels le SoC s'interface, mais fournit un environnement graphique, contrairement à l'utilitaire de ligne de commande. Il est plus facile d'interagir avec l'appareil et de voir ce qu'il fait, mais les sessions de débogage sont moins contrôlées. Cela nécessite une surabondance gratuite.
Vous trouverez ci-dessous une image d'une session en cours dans le simulateur GUI :
La construction peut être réalisée avec
make gui
Et en courant :
make gui-run
Ou:
./gui h2.hex (on Linux)
gui.exe h2.hex (on Windows)
La version Linux devrait fonctionner lorsque le package de développement pour Free Glut est installé sur votre système, la version Windows peut nécessiter des modifications du système de construction et/ou une installation manuelle du compilateur, des bibliothèques et des en-têtes.
La carte clé actuelle est la suivante :
Up Activate Up D-Pad Button, Release turns off
Down Activate Down D-Pad Button, Release turns off
Left Activate Left D-Pad Button, Release turns off
Right Activate Right D-Pad Button, Release turns off
F1 - F8 Toggle Switch On/Off, F1 is left most, F8 Right Most
F11 Toggle UART/PS2 Keyboard Input
F12 Toggle Debugging Information
Escape Quit simulator
Toutes les autres touches du clavier sont redirigées vers l'entrée clavier UART ou PS/2.
Les boutons Switches et D-Pad peuvent être cliqués pour les allumer, les switchs s'allument avec un clic gauche et s'éteignent avec un clic droit. Les boutons D-Pads s'allument en cliquant dessus et s'éteignent en appuyant sur une touche n'importe où sur l'écran.
Les composants VHDL utilisés dans ce système sont conçus pour être réutilisables et portables sur différentes chaînes d'outils et fournisseurs. Les composants matériels, comme la RAM en bloc, sont déduits et non explicitement instanciés. Les composants sont également conçus pour être aussi génériques que possible, la plupart ayant des largeurs sélectionnables. Cela serait poussé à l'extrême, mais malheureusement, de nombreux fournisseurs ne prennent toujours pas en charge la norme VHDL-2008.
Déposer | Licence | Auteur | Description |
---|---|---|---|
util.vhd | MIT | Richard J Howe | Une collection de composants génériques |
h2.vhd | MIT | Richard J Howe | H2 Quatrième cœur de processeur |
uart.vhd | MIT | Richard J Howe | UART TX/RX (temps d'exécution personnalisable) |
vga.vhd | LGPL3.0 | Javier V García | Affichage VGA 80x40 en mode texte |
Richard J Howe | (et émulateur de terminal VT100) | ||
kbd.vhd | ??? | Scott Larson | Clavier PS/2 |
Le pseudo langage de type Forth utilisé comme assembleur est décrit ci-dessus, l'application qui s'exécute réellement sur le noyau Forth est en elle-même un interpréteur Forth. Cette section décrit l'interpréteur Forth qui s'exécute sur H2 Core, il est contenu dans embed.fth.
FAIRE:
Plusieurs langages sont utilisés tout au long de ce projet, tous radicalement différents les uns des autres et nécessitent leur propre ensemble de normes de codage et de guides de style.
Noms courants des signaux :
clk - The system clock
rst - A reset signal for the module
we - Write Enable
re - Read Enable
di - Data In
din - Data In
do - Data Out
dout - Data Out
control - Generally an input to a register, the documentation
for the module will need to be consulted to find out
what each bit means
signal_we - The write enable for 'signal'
signal_i - This is an input signal
signal_o - This is an output signal
Généralement, l'utilisation des suffixes "_i" et "_o" n'est pas utilisée, les modules restent courts et les noms sont choisis de manière à ce que leur signification soit évidente. Cette règle pourrait être revue une fois que le projet se développera.
Les composants doivent :
constant N: positive := 4;
signal a: std_logic_vector(N - 1 downto 0) := (others => '1');
Au lieu de:
signal a: std_logic_vector(3 downto 0) := x"F";
Les règles de style sont les suivantes :
Un exemple de directives de formatage, décrit un simple registre de largeur arbitraire :
-- Lots of comments about what the unit does should go
-- here. Describe the waveforms, states and use ASCII
-- art where possible.
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all; -- numeric_std not std_logic_arith
entity reg is -- generic and port indented one tab, their parameters two
generic (
N: positive); -- Generic parameters make for a generic component
port (
clk: in std_logic; -- standard signal names
rst: in std_logic; --
we: in std_logic;
di: in std_logic_vector(N - 1 downto 0);
do: out std_logic_vector(N - 1 downto 0)); -- note the position of ");
end entity; -- "end entity", not "end reg"
architecture rtl of reg is
signal r_c, r_n: std_logic_vector(N - 1 downto 0) := (others => '0');
begin
do <= r_c;
process(rst, clk)
begin
if rst = '1' then -- asynchronous reset
r_c <= (others => '0');
elsif rising_edge(clk) then -- rising edge, not "clk'event and clk = '1'"
r_c <= r_n;
end if;
end process;
process(r_c, di, we)
begin
r_n <= r_c;
if we = '1' then
r_n <= di;
end if;
end process;
end; -- "end" or "end architecture"
Il y a beaucoup de code C utilisé dans ce projet, utilisé pour créer une chaîne d'outils pour le noyau H2 et pour simuler le système.
Il n'y a rien de trop surprenant dans le code C contenu ici, donc certaines exceptions doivent être traitées.
static const char *alu_op_to_string(uint16_t instruction) {
/* notice also that the 'case' clauses are inline with the
* switch selector */
switch (ALU_OP(instruction)) {
case ALU_OP_T: return "T";
case ALU_OP_N: return "N";
case ALU_OP_T_PLUS_N: return "T+N";
case ALU_OP_T_AND_N: return "T&N";
case ALU_OP_T_OR_N: return "T|N";
case ALU_OP_T_XOR_N: return "T^N";
case ALU_OP_T_INVERT: return "~T";
case ALU_OP_T_EQUAL_N: return "N=T";
case ALU_OP_N_LESS_T: return "T>N";
case ALU_OP_N_RSHIFT_T: return "N>>T";
case ALU_OP_T_DECREMENT: return "T-1";
case ALU_OP_R: return "R";
case ALU_OP_T_LOAD: return "[T]";
case ALU_OP_N_LSHIFT_T: return "N<N";
case ALU_OP_ENABLE_INTERRUPTS: return "seti";
case ALU_OP_INTERRUPTS_ENABLED: return "iset?";
case ALU_OP_RDEPTH: return "rdepth";
case ALU_OP_T_EQUAL_0: return "0=";
case ALU_OP_CPU_ID: return "cpu-id";
default: return "unknown";
}
}
if (foo)
bar();
else
baz();
picocom --omap delbs -b 115200 -e b /dev/ttyUSB1