Virgule est un processeur RISC (Reduced Instruction Set Computer) développé par Guillaume Savaton dans un but pédagogique. Il implémente un sous-ensemble minimal du jeu d’instruction RISC-V 32 bits. Ici, « minimal » signifie que ce processeur accepte toutes les instructions de traitement de données en nombres entiers, de branchement et d’accès mémoire susceptibles d’être produites par un compilateur C comme GCC pour un programme autonome typique.
Les enseignements en architecture de l’ordinateur et en conception de circuits numériques visent l’acquisition des compétences suivantes :
D’un point de vue pratique, le choix d’une architecture de processeur comme support pédagogique est souvent un compromis entre, d’une part le besoin de faciliter les apprentissages, et d’autre part l’adaptation de l’enseignement aux réalités industrielles. Nous avons ainsi identifié trois contraintes :
Parmi les candidates, l’architecture RISC-V remplit tous ces critères :
Virgule est capable d’exécuter toutes les instructions de traitement de données, de branchement
et d’accès mémoire du jeu d’instructions de base (RV32I) de la spécification RISC-V.
Nous avons également conservé une instruction de retour d’exception (mret
), ce qui permet
de prendre en charge les interruptions matérielles.
Les instructions qui ont été écartées sont :
FENCE
),ECALL
, EBREAK
),L’architecture RISC-V supporte trois niveaux de privilèges (utilisateur, superviseur, machine). Le mode machine est obligatoire et les deux autres sont facultatifs. Le cœur Virgule fonctionne uniquement dans le mode machine.
Par ailleurs, la gestion des interruptions a été simplifiée à l’extrême.
Virgule possède les registres suivants, tous de largeur 32 bits :
x0
à x31
.
Le registre x0
vaut toujours zéro.pc
. Sa valeur est l’adresse de l’instruction en cours
d’exécution et est toujours multiple de 4. Ce registre est mis à zéro au démarrage.mepc
mémorise l’adresse de retour lors d’une exception en mode machine.
Il est affecté avant le traitement d’une interruption.Pour un compilateur respectant l’ABI (Application Binary Interface) RISC-V, les registres généraux sont utilisés de la manière suivante :
Registre | Nom ABI | Rôle |
---|---|---|
x0 |
zero |
Zéro câblé |
x1 |
ra |
Adresse de retour |
x2 |
sp |
Pointeur de pile |
x3 |
gp |
Pointeur gobal |
x4 |
tp |
Pointeur de thread |
x5 à x7 |
t0 à t2 |
Données temporaires |
x8 |
s0 ou fp |
Donnée sauvegardée |
x9 |
s1 |
Donnée sauvegardée |
x10 à x11 |
a0 à a1 |
Arguments et résultats de sous-programmes |
x12 à x17 |
a2 à a7 |
Arguments de sous-programmes |
x18 à x27 |
s2 à s11 |
Donnée sauvegardée |
x28 à x31 |
t3 à t6 |
Données temporaires |
Le pointeur global peut être utilisé pour simplifier l’accès aux variables globales en utilisant un adressage relatif.
Par convention, les valeurs des registres s0
à s11
sont sauvegardés dans la
pile en début de sous-programme et restaurées à la fin.
Les valeurs des registres t0
à t6
doivent être sauvegardées par l’appelant
s’il souhaite les conserver.
Les formats de données utilisent la terminologie suivante :
Taille (bits) | Nom |
---|---|
8 | Byte (B ) |
16 | Half word (H ) |
32 | Word (W ) |
Les accès sur 16 ou 32 bits devront être alignés respectivement sur des adresses multiples de 2 ou 4. L’ordre des octets en mémoire suit la convention little-endian, c’est-à-dire que les valeurs sont rangées, de l’octet de poids faible à l’octet de poids fort, à des adresses croissantes.
Dans la syntaxe des instructions :
rd
représente le registre de destination (x0
à x31
).rs1
et rs2
représentent les registres sources (x0
à x31
).imm
représente une valeur immédiate.Instruction | Signification | Opération |
---|---|---|
LUI rd, imm |
Load Upper Immediate | rd ← imm |
AUIPC rd, imm |
Add Upper Immediate to PC | rd ← pc + imm |
JAL rd, imm |
Jump And Link | rd ← pc + 4; pc ← pc + imm |
JALR rd, rs1, imm |
Jump And Link Register | rd ← pc + 4; pc ← rs1 + imm |
BEQ rs1, rs2, imm |
Branch if Equal | pc ← si rs1 = rs2: pc + imm, sinon: pc + 4 |
BNE rs1, rs2, imm |
Branch if Not Equal | pc ← si rs1 ≠ rs2: pc + imm, sinon: pc + 4 |
BLT rs1, rs2, imm |
Branch if Less Than | pc ← si signed(rs1) < signed(rs2): pc + imm, sinon: pc + 4 |
BGE rs1, rs2, imm |
Branch if Greater or Equal | pc ← si signed(rs1) ≥ signed(rs2): pc + imm, sinon: pc + 4 |
BLTU rs1, rs2, imm |
Branch if Less Than Unsigned | pc ← si unsigned(rs1) < unsigned(rs2): pc + imm, sinon: pc + 4 |
BGEU rs1, rs2, imm |
Branch if Greater or Equal Unsigned | pc ← si unsigned(rs1) ≥ unsigned(rs2): pc + imm, sinon: pc + 4 |
LB rd, imm(rs1) |
Load Byte | rd ← signed(mem[rs1+imm]) |
LH rd, imm(rs1) |
Load Half word | rd ← signed(mem[rs1+imm:rs1+imm+1]) |
LW rd, imm(rs1) |
Load Word | rd ← signed(mem[rs1+imm:rs1+imm+3]) |
LBU rd, imm(rs1) |
Load Byte Unsigned | rd ← unsigned(mem[rs1+imm]) |
LHU rd, imm(rs1) |
Load Half word Unsigned | rd ← unsigned(mem[rs1+imm:rs1+imm+1]) |
SB rs2, imm(rs1) |
Store Byte | mem[rs1+imm] ← rs2[7:0] |
SH rs2, imm(rs1) |
Store Half word | mem[rs1+imm:rs1+imm+1] ← rs2[15:0] |
SW rs2, imm(rs1) |
Store Word | mem[rs1+imm:rs1+imm+3] ← rs2 |
ADDI rd, rs1, imm |
Add Immediate | rd ← rs1 + imm |
SLLI rd, rs1, imm |
Shift Left Logical Immediate | rd ← rs1 sll imm |
SLTI rd, rs1, imm |
Set on Less Than Immediate | rd ← si signed(rs1) < signed(imm): 1, sinon: 0 |
SLTIU rd, rs1, imm |
Set on Less Than Immediate Unsigned | rd ← si unsigned(rs1) < unsigned(imm): 1, sinon: 0 |
XORI rd, rs1, imm |
Exclusive Or Immediate | rd ← rs1 xor imm |
SRLI rd, rs1, imm |
Shift Right Logical Immediate | rd ← rs1 srl imm |
SRAI rd, rs1, imm |
Shift Right Arithmetic Immediate | rd ← rs1 sra imm |
ORI rd, rs1, imm |
Or Immediate | rd ← rs1 or imm |
ANDI rd, rs1, imm |
And Immediate | rd ← rs1 and imm |
ADD rd, rs1, rs2 |
Add | rd ← rs1 + rs2 |
SUB rd, rs1, rs2 |
Subtract | rd ← rs1 - rs2 |
SLL rd, rs1, rs2 |
Shift Left Logical | rd ← rs1 sll rs2 |
SLT rd, rs1, rs2 |
Set on Less Than | rd ← si signed(rs1) < signed(rs2): 1, sinon: 0 |
SLTU rd, rs1, rs2 |
Set on Less Than Unsigned | rd ← si unsigned(rs1) < unsigned(rs2): 1, sinon: 0 |
XOR rd, rs1, rs2 |
Exclusive Or | rd ← rs1 xor rs2 |
SRL rd, rs1, rs2 |
Shift Right Logical | rd ← rs1 srl rs2[4:0] |
SRA rd, rs1, rs2 |
Shift Right Arithmetic | rd ← rs1 sra rs2[4:0] |
OR rd, rs1, rs2 |
Or | rd ← rs1 or rs2 |
AND rd, rs1, rs2 |
And | rd ← rs1 and rs2 |
MRET |
Machine Return | pc ← mepc |
Les opérations logiques sont notées de la manière suivante :
Notation | Opération |
---|---|
and |
Et logique bit à bit |
or |
Ou logique bit à bit |
xor |
Ou exclusif bit à bit |
sll |
Décalage logique à gauche (avec insertion de zéros par la droite) |
srl |
Décalage logique à droite (avec insertion de zéros par la gauche) |
sra |
Décalage arithmétique à droite (avec extension du bit de signe) |
Le cœur Virgule réalise un mécanisme simple d’interruption matérielle dans le mode machine du RISC-V. Aucun registre ou instruction de contrôle des interruptions n’est implémenté dans le processeur (cf les CSR de la spécification RISC-V). L’activation, la désactivation et l’interrogation de l’état des interruptions seront gérés, soit individuellement sur chaque périphérique, soit à l’aide d’un contrôleur d’interruption séparé.
Dans le plan mémoire, le vecteur d’interruption se trouve à l’adresse 4. À cette adresse, on placera l’une des choses suivantes :
mret
,jal zero, irq_handler
).Lors d’une demande d’interruption :
pc
est mémorisée dans mepc
.
Si l’instruction en cours d’exécution est un branchement, alors mepc
reçoit l’adresse de destination du branchement.
Sinon, mepc
reçoit pc + 4
.pc
reçoit l’adresse du vecteur d’interruption.Lors d’un retour d’interruption (instruction mret
) :
pc
reçoit la valeur de mepc
.Le code d’une instruction se compose des champs suivants :
funct7
, funct3
et opcode
définissent l’opération réalisée,imm
représente une valeur entière littérale (immédiate),rs1
et rs2
sont des numéros de registres sources,rd
est le numéro du registre destination.On distingue six formats d’instructions qui utilisent chacun une partie des champs ci-dessus :
Format | 31:25 | 24:20 | 19:15 | 14:12 | 11:7 | 6:0 |
---|---|---|---|---|---|---|
R |
funct7 |
rs2 |
rs1 |
funct3 |
rd |
opcode |
I |
imm[11:5] /funct7 |
imm[4:0] |
rs1 |
funct3 |
rd |
opcode |
S |
imm[11:5] |
rs2 |
rs1 |
funct3 |
imm[4:0] |
opcode |
B |
imm[12,10:5] |
rs2 |
rs1 |
funct3 |
imm[4:1,11] |
opcode |
U |
imm[31:25] |
imm[24:20] |
imm[19:15] |
imm[14:12] |
rd |
opcode |
J |
imm[20,10:5] |
imm[4:1,11] |
imm[19:15] |
imm[14:12] |
rd |
opcode |
Les valeurs immédiates subissent une extension de bit de signe pour atteindre 32 bits. Les bits de poids faible qui ne sont pas codés dans l’instruction sont mis à 0.
Les formats B
et J
sont présentés comme des variantes des formats S
et U
.
Dans les formats B
et J
, les valeurs immédiates sont toujours paires.
Elles représentent un déplacement dans une instruction de branchement.
Elles sont codées de manière à posséder un grand nombre de bits communs avec d’autres formats tout en plaçant leur bit de signe sur le bit 31 de l’instruction.
La composition des valeurs immédiates est résumée dans le tableau ci-dessous :
Format | imm[31:25] |
imm[24:21] |
imm[20] |
imm[19:15] |
imm[14:12] |
imm[11] |
imm[10:5] |
imm[4:1] |
imm[0] |
---|---|---|---|---|---|---|---|---|---|
I |
inst[31] |
inst[31] |
inst[31] |
inst[31] |
inst[31] |
inst[31] |
inst[30:25] |
inst[24:21] |
inst[20] |
S |
inst[31] |
inst[31] |
inst[31] |
inst[31] |
inst[31] |
inst[31] |
inst[30:25] |
inst[11:8] |
inst[7] |
B |
inst[31] |
inst[31] |
inst[31] |
inst[31] |
inst[31] |
inst[7] |
inst[30:25] |
inst[11:8] |
0 |
U |
inst[31:25] |
inst[24:21] |
inst[20] |
inst[19:15] |
inst[14:12] |
0 | 0 | 0 | 0 |
J |
inst[31] |
inst[31] |
inst[31] |
inst[19:15] |
inst[14:12] |
inst[20] |
inst[30:25] |
inst[24:21] |
0 |
Le tableau ci-dessous liste les opcodes du jeu d’instructions RISC-V qui ont été conservés dans le cœur Virgule. À chaque opcode, correspond un format d’instruction. Dans la liste des instructions plus bas, on désignera les opcodes par leurs noms plutôt que par leurs valeurs binaires.
inst[6:0] |
Nom | Format |
---|---|---|
0000011 | LOAD |
I |
0010011 | OP-IMM |
I |
0010111 | AUIPC |
U |
0100011 | STORE |
S |
0110011 | OP |
R |
0110111 | LUI |
U |
1100011 | BRANCH |
B |
1100111 | JALR |
I |
1101111 | JAL |
J |
1110011 | SYSTEM |
I |
Dans le tableau ci-dessous, la colonne opcode
fait référence aux noms figurant
dans le tableau des opcodes de base.
Instruction | opcode |
funct3 |
funct7 |
rs2 |
---|---|---|---|---|
LUI |
LUI |
— | — | — |
AUIPC |
AUIPC |
— | — | — |
JAL |
JAL |
— | — | — |
JALR |
JALR |
000 | — | — |
BEQ |
BRANCH |
000 | — | — |
BNE |
BRANCH |
001 | — | — |
BLT |
BRANCH |
100 | — | — |
BGE |
BRANCH |
101 | — | — |
BLTU |
BRANCH |
110 | — | — |
BGEU |
BRANCH |
111 | — | — |
LB |
LOAD |
000 | — | — |
LH |
LOAD |
001 | — | — |
LW |
LOAD |
010 | — | — |
LBU |
LOAD |
100 | — | — |
LHU |
LOAD |
101 | — | — |
SB |
STORE |
000 | — | — |
SH |
STORE |
001 | — | — |
SW |
STORE |
010 | — | — |
ADDI |
OP-IMM |
000 | — | — |
SLLI |
OP-IMM |
001 | 0000000 | — |
SLTI |
OP-IMM |
010 | — | — |
SLTIU |
OP-IMM |
011 | — | — |
XORI |
OP-IMM |
100 | — | — |
SRLI |
OP-IMM |
101 | 0000000 | — |
SRAI |
OP-IMM |
101 | 0100000 | — |
ORI |
OP-IMM |
110 | — | — |
ANDI |
OP-IMM |
111 | — | — |
ADD |
OP |
000 | 0000000 | — |
SUB |
OP |
000 | 0100000 | — |
SLL |
OP |
001 | 0000000 | — |
SLT |
OP |
010 | 0000000 | — |
SLTU |
OP |
011 | 0000000 | — |
XOR |
OP |
100 | 0000000 | — |
SRL |
OP |
101 | 0000000 | — |
SRA |
OP |
101 | 0100000 | — |
OR |
OP |
110 | 0000000 | — |
AND |
OP |
111 | 0000000 | — |
MRET |
SYSTEM |
000 | 0011000 | 00010 |