Écrire un processeur et un environnement simples pour cela

Bonjour Dans cet article, je vais vous expliquer les étapes à suivre pour créer un processeur et un environnement simples.


Architecture du jeu de commandes (ISA)


Vous devez d'abord décider quel sera le processeur. Les paramètres suivants sont importants:


  • La taille du mot machine et des registres (bit / "bit" du processeur)
  • Instructions de la machine (instructions) et leur taille

Les architectures de processeur peuvent être divisées en 2 types en fonction de la taille des instructions (en fait, il y en a plus, mais d'autres options sont moins populaires):



Leur principale différence est que les processeurs RISC ont la même taille d'instruction. Leurs instructions sont simples et s'exécutent relativement rapidement, tandis que les processeurs CISC peuvent avoir différentes tailles d'instructions, dont certaines peuvent prendre un certain temps.


J'ai décidé de faire un processeur RISC un peu comme MIPS .


Je l'ai fait pour un certain nombre de raisons:


  • Il est assez simple de créer un prototype d'un tel processeur.
  • Toute la complexité de ce type de processeur est transférée à des programmes tels que l'assembleur et / ou le compilateur.

Voici les principales caractéristiques de mon processeur:



Le type de registre (ci-après le type de registre) ressemble à ceci:


rtype


La particularité de ces instructions est qu'elles fonctionnent avec trois registres.


Type immédiat :


itype


Les instructions de ce type fonctionnent avec deux registres et un numéro.


OP est le numéro de l'instruction à exécuter (ou pour indiquer que cette instruction de type registre ).


R0 , R1 , R2 sont des numéros de registre qui servent d'opérandes pour l'instruction.


Func est un champ supplémentaire utilisé pour indiquer le type d'instructions de type Register .


Imm est le champ où la valeur est écrite, que nous voulons fournir explicitement des instructions sous forme d'opérande.


  • Seulement 28 instructions

Une liste complète des instructions peut être trouvée dans le dépôt github .


En voici quelques-unes:


nor r0, r1, r2 

NOR est une instruction de type Registre qui effectue un OU logique NON sur les registres r1 et r2, après quoi elle écrit le résultat dans le registre r0.


Pour utiliser cette instruction, vous devez modifier le champ OP en 0000 et le champ Func en 0000000111 dans le système numérique binaire.


 lw r0, n(r1) 

LW est une instruction de type Immédiat qui charge une valeur de mémoire à r1 + n dans le registre r0.


Pour utiliser cette instruction, vous devez à son tour changer le champ OP en 0111 et écrire le nombre n dans le champ IMM .


Écriture du code du processeur


Après avoir créé l' ISA, vous pouvez commencer à écrire le processeur.


Pour cela, nous avons besoin de connaître une sorte de langage de description d'équipement. En voici quelques uns:


  • Verilog
  • VHDL (à ne pas confondre avec le précédent!)

J'ai choisi Verilog, car sa programmation faisait partie de mon cursus universitaire.


Pour écrire un processeur, vous devez comprendre la logique de son fonctionnement:


  1. Obtenir des instructions sur le compteur de commandes (PC)
  2. Instructions de décodage
  3. Exécution des instructions
  4. Ajout au compteur de la taille de commande de l'instruction exécutée

Et ainsi de suite à l'infini.


Il s'avère que vous devez créer plusieurs modules:



Nous analyserons chaque module individuellement.


Enregistrer le fichier


Un fichier de registre permet d'accéder aux registres. Avec lui, vous devez obtenir les valeurs de certains registres ou les modifier.


Dans mon cas, j'ai 64 registres. Dans l'un des registres, le résultat de l'opération sur les deux autres est écrit, donc je dois fournir la possibilité de changer un seul et d'obtenir les valeurs des deux autres.


Décodeur


Un décodeur est cette unité qui est responsable des instructions de décodage. Il indique quelles opérations doivent être effectuées par l'ALU et d'autres unités.


Par exemple, l' instruction addi doit ajouter la valeur du registre $ zéro (elle stocke toujours 0 ) et 20 et mettre le résultat dans le registre $ t0.


 addi $t0, $zero, 20 

À ce stade, le décodeur détermine que cette instruction:


  • Type immédiat
  • Doit écrire le résultat dans le registre

Et transfère ces informations dans les blocs suivants.


ALU


Après le contrôle passe à ALU. Il effectue généralement toutes les opérations mathématiques et logiques, ainsi que les opérations de comparaison des nombres.


Autrement dit, si nous considérons la même instruction addi , à ce stade, l'addition de 0 et 20 se produit.


Autre


En plus des blocs ci-dessus, le processeur devrait pouvoir:


  • Obtenir et modifier des valeurs en mémoire
  • Effectuer des sauts conditionnels

Ici et là, vous pouvez voir à quoi cela ressemble dans le code.


Assembleur


Après avoir écrit le processeur, nous avons besoin d'un programme qui convertit les commandes de texte en code machine afin de ne pas le faire manuellement. Par conséquent, vous devez écrire l'assembleur.


J'ai décidé de l'implémenter dans le langage de programmation C.


Étant donné que mon processeur a une architecture RISC , afin de simplifier ma vie, j'ai décidé de concevoir l'assembleur afin de pouvoir facilement ajouter mes propres pseudo instructions (combinaisons de plusieurs instructions élémentaires ou d'autres pseudo instructions).


Vous pouvez l'implémenter à l'aide d'une structure de données qui stocke le type d'instruction, son format, un pointeur sur une fonction qui renvoie les codes d'instructions machine et son nom.


Un programme régulier commence par une déclaration de segment.


Deux segments .text nous suffisent - dans lesquels le code source de nos programmes sera stocké - et .data - dans lequel nos données et constantes seront stockées.


Une instruction pourrait ressembler à ceci:


 .text jie $zero, $zero, $zero #  addi $t1, $zero, 2 # $t1 = $zero + 2 lw $t1, 5($t2) # $t1 = *($t2 + 5) syscall 0, $zero, $zero # syscall(0, 0, 0) la $t1, label# $t1 = label 

D'abord, le nom de l'instruction est indiqué, puis les opérandes.


En .data , les déclarations de données sont indiquées.


 .data .byte 23 #   1  .half 1337 #   2  .word 69000, 25000 #   4  .asciiz "Hello World!" #     ( ) .ascii "12312009" #   ( ) .space 45 #  45  

Une annonce doit commencer par un point et un nom de type de données, suivis de constantes ou d'arguments.


Il est pratique d'analyser (scanner) le fichier assembleur comme suit:


  1. Commencez par scanner le segment
  2. S'il s'agit d'un segment .data , nous analysons différents types de données ou un segment .text
  3. S'il s'agit d'un segment .text , nous analysons les commandes ou un segment .data

Pour fonctionner, l'assembleur doit parcourir le fichier source 2 fois. La première fois qu'il considère les décalages des liens (ils servent), ils ressemblent généralement à ceci:


  la $s4, loop #   loop  s4 loop: # ! mul $s2, $s2, $s1 # s2 = s2 * s1 addi $s1, $s1, -1 # s1 = s1 - 1 jil $s3, $s1, $s4 #  s3 < s1     

Et dans le deuxième passage, vous pouvez déjà générer un fichier.


Résumé


À l'avenir, vous pouvez exécuter le fichier de sortie de l'assembleur sur notre processeur et évaluer le résultat.


De plus, un assembleur prêt à l'emploi peut être utilisé dans le compilateur C. Mais c'est plus tard.


Références:


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


All Articles