Projet PAF : Processeur RISC


Processeur RISC-V

Descriptif rapide

L’objectif de ce projet est de développer un processeur utilisant l’architecture de jeu d’instruction (Instruction Set Architecture, ISA) libre et ouverte RISC-V.

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 de jeu d’instruction nommée RISC-V, initialement créée à l’Université de Californie à Berkeley et maintenue par une fondation regroupant de très nombreuses entreprises et centres de recherche. Cette architecture est libre et ouverte, les spécifications complètes sont disponibles et chacun peut créer un processeur se basant sur ce jeu d’instruction.

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 RISC-V

Nous nous concentrerons sur la variante RV32I (RISC-V, 32 bits, instructions de calcul sur les entiers) de l’ISA. Il existe d’autres variantes proposant des tailles différentes (64 ou 128 bits), une version adaptée au monde de l’embarqué (E), et toute une série d’extensions, certaines standardisées, d’autres laissées libres aux constructeurs.

Avec cette variante, notre processeur 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 utilisées sont elles-mêmes codées sur 32 bits.

Les spécifications du RISC-V sont disponibles ici.

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. Votre processeur contient 32 registres (numérotés de x0 à x31) 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, le registre x0 contient toujours la valeur 0. Une lecture depuis ce registre renvoie toujours la valeur 0 et une écriture dans ce registre est tout simplement ignorée.

Il existe enfin un registre supplémentaire, pc qui contient l’adresse de l’instruction en cours d’exécution.

Pour plus de détails, référez-vous à la section 2.1 (pp. 13 à 15 des spécifications).

Instructions

Codage

Toutes les instructions sont codées sur 32 bits. Il existe plusieurs codages différents. En effet, en fonction du type d’instruction, il faut, parmi ces 32 bits, spécifier :

Pour plus de détails, référez-vous à la section 2.2 (pp. 15 à 17 des spécifications). Les valeurs du champ opcode sont données page 130.

Liste

La liste des instructions pour la variante RV32I est donnée dans les pages 17 à 30 des spécifications.

Dans un premier temps, nous ne nous intéresserons pas aux instructions FENCE, SYSTEM (ECALL et EBREAK), ni aux différents HINT. Nous supposerons également que les accès mémoires seront alignés.

L’exécution d’une instruction

Cette ISA essaie de ne pas faire d’hypothèses sur la manière dont elle est implémentée (sans pipeline, pipeline à 3 étages, pipeline à 5 étages, etc.). À des fins pédagogiques, nous étudierons un pipeline à 5 étages et donc nous décomposerons dès le début l’exécution d’une instruction en 5 étapes :

Le bloc processeur

Le processeur 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  x1,x0,1      # x1 = x0 | 1  (= 1)
        or   x2,x0,x1     # x2 = x0 | x1 (= x1 = 1)
        ori  x3,x0,10     # x3 = x0 | 10 (= 10)

boucle: add  x4,x1,x2     # x4 = x1 + x2
        or   x1,x0,x2     # x1 = x2
        or   x2,x0,x4     # x2 = x4
        addi x3,x3,-1     # x3 = x3 + (-1)
        bne  x3,x0,boucle # Si x3 != 0, on saute à boucle

fin:    jal  x0, 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 ainsi que les sections correspondantes des spécifications
  2. Sur papier, réflechir à l’architecture du processeur et identifier les sous-modules nécessaires pour réaliser votre processeur (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 BEQ, JAL, LOAD, STORE)
  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