Projet PAF : Processeur RISC


Processeur DLX

Descriptif rapide

L’objectif de ce projet est de développer un processeur utilisant l’architecture DLX ainsi que son assembleur.

Durant les cours de PAN (ELECINF102), vous avez manipulé un petit processeur baptisé NanoProcesseur. Ce processeur était extrêmement limité. Le premier objectif de ce projet est de réaliser un processeur plus complet et réaliste, en se basant sur une architecture nommée DLX créée par John Hennessy et David Patterson à des fins d’enseignement, mais très proche de l’architecture MIPS utilisée par de nombreux processeurs embarqués.

Dans un premier temps, vous décrirez en SystemVerilog une version simplifiée du processeur (reprenant un sous-ensemble du jeu d’instruction et sans pipeline) que vous simulerez (pour vérifier son bon fonctionnement puis que testerez sur la carte FPGA DE1-SoC (celle que vous auriez du utiliser en TP d’ELECINF102). Puis, dans un second temps, vous enrichirez ce premier prototype en ajoutant les instructions manquantes, en implémentant le pipeline et en implémentant quelques périphériques.

Le second objectif de ce projet est, pour pouvoir utiliser le processeur ainsi développé, de programmer un assembleur, c’est-à-dire un programme prenant en entrée un programme écrit en langage assembleur et produisant le code machine correspondant, prêt à être exécuté par le processeur.

L’architecture DLX

Le processeur DLX est un processeur 32 bits. Il travaille donc nativement avec des données codées sur 32 bits (c’est-à-dire que les opérandes et les résultats des calculs arithmériques et logiques sont représentés sur 32 bits). De plus, toutes les instructions du DLX sont elles-mêmes codées sur 32 bits.

Registres

Dans l’UE ELECINF102, le nanoprocesseur possédait un accumulateur qui servait à stocker le résultat de la dernière opérations effectuée et dont le contenu était utilisé comme un des opérandes par l’ALU.

Dans les processeurs actuels, le concept d’accumulateur est étendu et la notion de registre apparaît. Le processeur DLX contient 32 registres (numérotés de R0 à R31) pouvant chacun contenir une donnée sur 32 bits. Toutes les opérations arithmétiques et logiques travaillent sur les registres (c’est-à-dire que les opérandes de l’opération proviennent de deux registres et le résultat de l’opération est stocké dans un registre). Seules quelques instructions particulières (baptisées load/store) permettent d’effectuer des transferts entre les registres et la mémoire.

Pour des raisons pratiques, parmi les 32 registres du processeur DLX, deux ont un comportement particulier :

Instructions

Codage

Toutes les instructions sont codées sur 32 bits. Il existe trois codages différents pour les instructions (en fonction du type d’instruction).

Format I[31:26] I[25:21] I[20:16] I[15:11] I[10:6] I[5:0]
R 0x0 Rs1 Rs2 Rd Inutilisé Opcode
I Opcode Rs1 Rd immediate immediate immediate
J Opcode value value value value value

Liste

Voici le sous-ensemble des instructions du processeur DLX que vous allez implémenter.

Instruction Description Format Opcode Opération (expression à la C)
ADD add R 0x20 Rd = Rs1 + Rs2
ADDI add immediate I 0x08 Rd = Rs1 + sign_extend(immediate)
AND and R 0x24 Rd = Rs1 & Rs2
ANDI and immediate I 0x0c Rd = Rs1 & extend(immediate)
BEQZ branch if equal to zero I 0x04 PC += (Rs1 == 0 ? sign_extend(immediate) : 0)
BNEZ branch if not equal to zero I 0x05 PC += (Rs1 != 0 ? sign_extend(immediate) : 0)
J jump J 0x02 PC += sign_extend(value)
JAL jump and link J 0x03 R31 = PC + 4 ; PC += sign_extend(value)
JALR jump and link register I 0x13 R31 = PC + 4 ; PC = Rs1
JR jump register I 0x12 PC = Rs1
LHI load high bits I 0x0f Rd = extend(immediate) << 16
LW load word I 0x23 Rd = MEM[Rs1 + sign_extend(immediate)]
OR or R 0x25 Rd = Rs1 | Rs2
ORI or immediate I 0x0d Rd = Rs1 | extend(immediate)
SEQ set if equal R 0x28 Rd = (Rs1 == Rs2 ? 1 : 0)
SEQI set if equal to immediate I 0x18 Rd = (Rs1 == sign_extend(immediate) ? 1 : 0)
SLE set if less than or equal R 0x2c Rd = (Rs1 <= Rs2 ? 1 : 0)
SLEI set if less than or equal to immediate I 0x1c Rd = (Rs1 <= sign_extend(immediate) ? 1 : 0)
SLL shift left logical R 0x04 Rd = Rs1 << (Rs2 % 8)
SLLI shift left logical immediate I 0x14 Rd = Rs1 << (extend(immediate) % 8)
SLT set if less than R 0x2a Rd = (Rs1 < Rs2 ? 1 : 0)
SLTI set if less than immediate I 0x1a Rd = (Rs1 < sign_extend(immediate) ? 1 : 0)
SNE set if not equal R 0x29 Rd = (Rs1 != Rs2 ? 1 : 0)
SNEI set if not equal to immediate I 0x19 Rd = (Rs1 != sign_extend(immediate) ? 1 : 0)
SRA shift right arithmetic R 0x07 Rd = Rs1 >>> (Rs2 % 8)
SRAI shift right arithmetic immediate I 0x17 Rd = Rs1 >>> (extend(immediate) % 8)
SRL shift right logical R 0x06 Rd = Rs1 >> (Rs2 % 8)
SRLI shift right logical immediate I 0x16 Rd = Rs1 >> (extend(immediate) % 8)
SUB subtract R 0x22 Rd = Rs1 - Rs2
SUBI subtract immediate I 0x0a Rd = Rs1 - sign_extend(immediate)
SW store word I 0x2b MEM[Rs1 + sign_extend(immediate)] = Rd
XOR exclusive or R 0x26 Rd = Rs1 ^ Rs2
XORI exclusive or immediate I 0x0e Rd = Rs1 ^ extend(immediate)

Quelques remarques sur ces instructions :

L’exécution d’une instruction

L’architecture DLX est prévue pour être pipelinée. L’exécution d’une instruction est décomposée en 5 étapes :

Le bloc processeur

Le processeur DLX est un module avec des entrées et des sorties. Dans un premier temps, voici la description de ces signaux :

Nom Direction Taille Description
clk Entrée 1 L’horloge du processeur
reset_n Entrée 1 Signal de remise à zéro asynchrone actif à l’état bas
i_address Sortie 32 Adresse pour la mémoire d’instruction
i_data_read Entrée 32 Donnée (instruction) lue depuis la mémoire d’instructions
i_data_valid Entrée 1 La donnée présentée sur i_data_read est valide
d_address Sortie 32 Adresse pour la mémoire de données
d_data_read Entrée 32 Donnée lue depuis la mémoire de données
d_data_write Sortie 32 Donnée à écrire dans la mémoire de données
d_write_enable Sortie 1 Signal d’écriture en mémoire de données
d_data_valid Entrée 1 Dans le cas d’une lecture (d_write_enable==0), indique que la donnée présentée sur d_data_read est valide. Dans le cas d’une écriture (d_write_enable==1), indique que la donnée a bien été écrite.

Le fonctionnement des deux mémoires (données et instructions) est synchrone : l’adresse présentée à la mémoire est échantillonnée sur un front montant d’horloge et la donnée à cette adresse est présentée en sortie de la mémoire peu de temps après (temps de propagation).

On supposera dans un premier temps que les mémoires répondent toujours en un cycle et donc que les signaux i_data_valid et d_data_valid sont toujours à 1 et donc qu’il n’y a pas besoin d’en tenir compte.

Langage assembleur

Le langage assembleur est un langage, lisible par un être humain, qui a la particularité d’être très proche du code machine car il y a normalement une correspondance 1 à 1 entre une instruction assembleur et une instruction machine.

Voici un exemple de code assembleur que votre programme d’assemblage devra être capable de traiter. Par la suite, plus de fonctionnalités seront ajoutées.

# Petit programme calculant les 10 premiers termes de fibo

debut:  ORI  $1,$0,1     # R1 = R0 | 1  (= 1)
        OR   $2,$0,$1    # R2 = R0 | R1 (= R1 = 1)
        ORI  $3,$0,10    # R3 = R0 | 10 (= 10)

boucle: ADD  $4,$1,$2    # R4 = R1 + R2
        OR   $1,$0,$2    # R1 = R2
        OR   $2,$0,$4    # R2 = R4
        ADDI $3,$3,-1    # R3 = R3 + (-1)
        BNEZ $3,boucle   # Si R3 != 0, on saute à boucle

fin:    J    fin         # Boucle inifinie à la fin

Travail demandé pendant les deux semaines du projet PAF

Partie matérielle

Partie logicielle

Consignes générales

Planning pour le début du projet

  1. Lire et (essayer de) comprendre le texte ci-dessus
  2. Sur papier, réflechir à l’architecture du processeur et identifier les sous-modules nécessaires pour réaliser un processeur DLX (vous pouvez partir de la description du nano-processeur)
  3. Identifier les signaux de contrôle nécessaires aux différents blocs identifiés précédemment
  4. Faire un chronogramme pour identifier le déroulement du traitement d’une instruction (regarder les différents types d’instruction : ADD puis BEQZ, JAL, LHI, LW, SW)
  5. Traduire le code assembleur calculant les premiers termes de la suite de fibo (voir plus haut) en langage machine (c’est-à-dire chaque instruction codée sur 32 bits)
  6. Présenter les résultats de vos réflexions à votre encadrant

© Copyright 2021 Guillaume Duc. Le contenu de cette page est mis à disposition selon les termes de la Licence Creative Commons Attribution - Partage dans les Mêmes Conditions 4.0 International (à l'exception des exemples de code tirés du noyau Linux et qui sont distribués sous leurs licences d'origine).

Licence
Creative Commons