Les objectifs de ce tutoriel sont les suivants:
- Découvrez la structure et la syntaxe du fichier de description au format binaire.
- Découvrez la correspondance entre la description du format binaire et la description de l'ISA.
- Rédigez les descriptions binaires du sous-ensemble d'instructions RiscV RV32I.
Présentation
Encodage d'instructions binaires RiscV
L'encodage d'instructions binaires est la méthode standard pour encoder les instructions pour sur un microprocesseur. Ils sont normalement stockés dans un fichier exécutable, généralement au format ELF. Les instructions peuvent avoir une largeur fixe ou être variables la largeur.
En règle générale, les instructions utilisent un petit ensemble de formats d'encodage, chacun d'entre eux personnalisés pour le type d'instructions encodées. Par exemple, "register-register" les instructions peuvent utiliser un format qui maximise le nombre d'opérations disponibles, tandis que les instructions d'enregistrement immédiat en utilisent une autre qui compromet le nombre de disponibles pour augmenter la taille de l’élément direct qui peut être encodé. Les instructions Branch et jump utilisent presque toujours des formats qui maximisent la taille afin de prendre en charge les branches avec des décalages plus importants.
Les formats d'instructions utilisés par les instructions que nous voulons décoder dans notre code RiscV sont les suivants:
Format R-Type utilisé pour les instructions d'enregistrement et d'enregistrement:
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6... |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | rs2 | rs1 | func3 | e | code opération |
format I-Type, utilisé pour les instructions d’enregistrement immédiat, les instructions de chargement et
l'instruction jalr
, 12 bits immédiat.
31..20 | 19..15 | 14..12 | 11..7 | 6... |
---|---|---|---|---|
12 | 5 | 3 | 5 | 7 |
imm12 | rs1 | func3 | e | code opération |
Format I-Type spécialisé, utilisé pour le décalage avec des instructions immédiates, 5 bits immédiat:
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6... |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | uimm5 | rs1 | func3 | e | code opération |
Format U-Type, utilisé pour les instructions immédiates longues (lui
, auipc
), 20 bits
immédiat:
31..12 | 11..7 | 6... |
---|---|---|
20 | 5 | 7 |
uimm20 | e | code opération |
Format B-Type, utilisé pour les branches conditionnelles, 12 bits immédiat.
31 | 30..25 | 24..20 | 19..15 | 14..12 | 11..8 | 7 | 6... |
---|---|---|---|---|---|---|---|
1 | 6 | 5 | 5 | 3 | 4 | 1 | 7 |
Imm | Imm | rs2 | rs1 | func3 | Imm | Imm | code opération |
Format J-Type, utilisé pour l'instruction jal
, immédiatement sur 20 bits.
31 | 30..21 | 20 | 19..12 | 11..7 | 6... |
---|---|---|---|---|---|
1 | 10 | 1 | 8 | 5 | 7 |
Imm | Imm | Imm | Imm | e | code opération |
Format S-Type, utilisé pour les instructions de stockage, 12 bits immédiat.
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6... |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
Imm | rs2 | rs1 | func3 | Imm | code opération |
Comme vous pouvez le voir avec ces formats, toutes ces instructions sont de 32 bits et les 7 bits faibles dans chaque format correspond au champ opcode. Notez également que même si plusieurs formats ont des instantanés de la même taille, leurs bits proviennent différentes parties de l'instruction. Comme nous le verrons, le décodeur binaire de spécification peut exprimer cela.
Description de l'encodage binaire
L'encodage binaire de l'instruction est exprimé au format binaire
(.bin_fmt
). Il décrit l'encodage binaire du
des instructions dans une ISA, afin qu'un décodeur d'instructions au format binaire puisse être
générées. Le décodeur généré détermine l'opération, extrait la valeur
opérande et champs immédiats, afin de fournir les informations requises par l'ISA
le décodeur indépendant de l'encodage décrit dans le tutoriel précédent.
Dans ce tutoriel, nous allons écrire un fichier de description de l'encodage binaire pour un sous-ensemble des instructions RiscV32I nécessaires pour simuler les instructions utilisées dans un petit "Hello World" programme. Pour en savoir plus sur l'ISA RiscV, consultez Spécifications Risc-V{.external}.
Commencez par ouvrir le fichier:
riscv_bin_decoder/riscv32i.bin_fmt
Le contenu du fichier est divisé en plusieurs sections.
Tout d'abord, la définition de decoder
.
decoder RiscV32I {
// The namespace in which code will be generated.
namespace mpact::sim::codelab;
// The name (including any namespace qualifiers) of the opcode enum type.
opcode_enum = "OpcodeEnum";
// Include files specific to this decoder.
includes {
#include "riscv_isa_decoder/solution/riscv32i_decoder.h"
}
// Instruction groups for which to generate decode functions.
RiscVInst32;
};
La définition du décodeur spécifie le nom du décodeur RiscV32I
, ainsi que
quatre informations
supplémentaires. Le premier est namespace
, qui définit
l'espace de noms dans lequel le code généré sera placé. Deuxièmement,
opcode_enum
, qui nomme la façon dont le type d'énumération OPcode généré
par le décodeur ISA doit être référencé dans le code généré. Troisièmement,
includes {}
spécifie les fichiers d'inclusion nécessaires pour le code généré pour
ce décodeur.
Dans notre cas, il s'agit du fichier produit par le décodeur ISA à partir du
dans le tutoriel précédent.
Vous pouvez spécifier des fichiers d'inclusion supplémentaires dans un includes {}
à portée globale.
définition. Cela s'avère utile lorsque plusieurs décodeurs sont définis et qu'ils ont tous besoin
pour inclure certains
de ces mêmes fichiers. Quatrièmement, une liste de noms d'instruction
qui constituent les instructions pour lesquelles le décodeur est généré. Dans notre
au cas où il n'y en avait qu'un: RiscVInst32
.
Ensuite, il y a trois définitions de format. Il s'agit de différentes instructions formats pour un mot d'instruction de 32 bits utilisé par les instructions déjà définies dans le fichier.
// The generic RiscV 32 bit instruction format.
format Inst32Format[32] {
fields:
unsigned bits[25];
unsigned opcode[7];
};
// RiscV 32 bit instruction format used by a number of instructions
// needing a 12 bit immediate, including CSR instructions.
format IType[32] : Inst32Format {
fields:
signed imm12[12];
unsigned rs1[5];
unsigned func3[3];
unsigned rd[5];
unsigned opcode[7];
};
// RiscV instruction format used by fence instructions.
format Fence[32] : Inst32Format {
fields:
unsigned fm[4];
unsigned pred[4];
unsigned succ[4];
unsigned rs1[5];
unsigned func3[3];
unsigned rd[5];
unsigned opcode[7];
};
Le premier définit un format d'instruction de 32 bits nommé Inst32Format
, qui contient
deux champs: bits
(largeur de 25 bits) et opcode
(largeur de 7 bits). Chaque champ est
unsigned
, ce qui signifie que la valeur sera étendue à zéro lors de son extraction.
et placée dans un type entier C++. La somme des largeurs des champs de bits doit
égale à la largeur du format. L'outil génère une erreur
désaccord. Ce format ne provient d'aucun autre format. Il est donc
est considéré comme un format de premier niveau.
La seconde définit un format d'instruction de 32 bits nommé IType
, qui dérive
de Inst32Format
, ce qui rend ces deux formats liés. Le format contient cinq
: imm12
, rs1
, func3
, rd
et opcode
. Le champ imm12
est
signed
, ce qui signifie que la valeur est complétée par un signe lorsqu'elle est
extrait et placé dans un type entier C++. Notez que IType.opcode
a toutes les deux
le même attribut signé/non signé et fait référence aux mêmes bits de mot d'instruction
en tant que Inst32Format.opcode
.
Le troisième format est un format personnalisé qui n'est utilisé que par la propriété fence
qui est déjà spécifiée, mais qui n'est pas
à vous soucier dans ce tutoriel.
Important: Réutilisez les noms de champs dans différents formats associés, à condition qu'ils représentent les mêmes bits et ont le même attribut signé/non signé.
Un groupe d'instructions apparaît après les définitions de format dans riscv32i.bin_fmt
.
définition. Toutes les instructions d'un groupe d'instructions doivent avoir le même
et utilisent un format qui dérive (peut-être indirectement) de la même
un format d'instruction de premier niveau. Lorsqu’une ISA peut avoir des instructions avec différentes
un groupe d'instructions différent est utilisé pour chaque longueur. En outre,
si le décodage ISA cible dépend d'un mode d'exécution, tel que Arm ou Thumb
d'instructions, un groupe d'instructions distinct est requis pour chaque mode. La
L'analyseur bin_fmt
génère un décodeur binaire pour chaque groupe d'instructions.
instruction group RiscV32I[32] "OpcodeEnum" : Inst32Format {
fence : Fence : func3 == 0b000, opcode == 0b000'1111;
csrs : IType : func3 == 0b010, rs1 != 0, opcode == 0b111'0011;
csrw_nr : IType : func3 == 0b001, rd == 0, opcode == 0b111'0011;
csrs_nw : IType : func3 == 0b010, rs1 == 0, opcode == 0b111'0011;
};
Le groupe d'instructions définit un nom RiscV32I
, une largeur [32]
, le nom
le type d'énumération OPcode à utiliser "OpcodeEnum"
, et une instruction de base
. Le type d'énumération OPcode doit être identique à celui produit par
d'instructions indépendantes de format abordées dans le tutoriel sur l'ISA
décodeur.
La description de l'encodage de chaque instruction se compose de trois parties:
- Le nom de l'opération, qui doit être identique à celui utilisé dans l'instruction du décodeur pour qu'ils fonctionnent ensemble.
- Format d'instruction à utiliser pour le code d'opération. Il s'agit du format qui est utilisé pour satisfaire les références aux champs de bits dans la partie finale.
- Une liste de contraintes de champ de bits séparées par une virgule,
==
,!=
,<
,<=
,>
, et>=
qui doivent toutes être vraies pour que le code d'opération corresponde correctement au de l'instruction.
L'analyseur .bin_fmt
utilise toutes ces informations pour créer un décodeur qui:
- Fournit des fonctions d'extraction (signées/non signées) appropriées pour chaque bit
dans tous les formats. Les fonctions d'extracteur sont placées dans des espaces de noms
nommée par la version snake-case du nom de format. Par exemple, le paramètre
les fonctions d'extracteur au format
IType
sont placées dans l'espace de nomsi_type
. Chaque fonction d'extracteur est déclaréeinline
et prend lauint_t
la plus étroite qui contient la largeur du format et renvoie la valeurint_t
la plus étroite (pour les signatures signées),uint_t
(pour les entités non signées), qui contient le champ extrait la largeur. Par exemple :
inline uint8_t ExtractOpcode(uint32_t value) {
return value & 0x7f;
}
- Une fonction de décodage pour chaque groupe d'instructions. Elle renvoie une valeur de type
OpcodeEnum
, et utilise le typeuint_t
le plus étroit contenant la largeur de le format du groupe d'instructions.
Exécuter la compilation initiale
Remplacez le répertoire par riscv_bin_decoder
et compilez le projet à l'aide de la méthode
la commande suivante:
$ cd riscv_bin_decoder
$ bazel build :all
Remettez votre répertoire à la racine du dépôt, puis voyons
aux sources générées. Pour cela, remplacez
le répertoire par
bazel-out/k8-fastbuild/bin/riscv_bin_decoder
(en supposant que vous utilisez un ordinateur x86
host - pour les autres hôtes, k8-fastbuild sera une autre chaîne).
$ cd ..
$ cd bazel-out/k8-fastbuild/bin/riscv_bin_decoder
riscv32i_bin_decoder.h
riscv32i_bin_decoder.cc
Le fichier d'en-tête généré (.h)
Ouvrez riscv32i_bin_decoder.h
. La première partie du fichier contient
Protections standards, fichiers inclus et déclarations d'espace de noms Ensuite,
une fonction d'assistance modélisée se trouve dans l'espace de noms internal
. Cette fonction
est utilisé pour extraire les champs de bits de formats trop longs pour tenir dans un C++ 64 bits
entier.
#ifndef RISCV32I_BIN_DECODER_H
#define RISCV32I_BIN_DECODER_H
#include <iostream>
#include <cstdint>
#include "third_party/absl/functional/any_invocable.h"
#include "learning/brain/research/mpact/sim/codelab/riscv_isa_decoder/solution/riscv32i_decoder.h"
namespace mpact {
namespace sim {
namespace codelab {
namespace internal {
template <typename T>
static inline T ExtractBits(const uint8_t *data, int data_size,
int bit_index, int width) {
if (width == 0) return 0;
int byte_pos = bit_index >> 3;
int end_byte = (bit_index + width - 1) >> 3;
int start_bit = bit_index & 0x7;
// If it is only from one byte, extract and return.
if (byte_pos == end_byte) {
uint8_t mask = 0xff >> start_bit;
return (mask & data[byte_pos]) >> (8 - start_bit - width);
}
// Extract from the first byte.
T val = 0;
val = data[byte_pos++] & 0xff >> start_bit;
int remainder = width - (8 - start_bit);
while (remainder >= 8) {
val = (val << 8) | data[byte_pos++];
remainder -= 8;
}
// Extract any remaining bits.
if (remainder > 0) {
val <<= remainder;
int shift = 8 - remainder;
uint8_t mask = 0b1111'1111 << shift;
val |= (data[byte_pos] & mask) >> shift;
}
return val;
}
} // namespace internal
Après la section initiale, il y a un ensemble de trois espaces de noms, un pour chaque
des déclarations format
dans le fichier riscv32i.bin_fmt
:
namespace fence {
...
} // namespace fence
namespace i_type {
...
} // namespace i_type
namespace inst32_format {
...
} // namespace inst32_format
Dans chacun de ces espaces de noms, la fonction d'extraction de champ de bits inline
pour chaque champ de bits dans
ce format est défini. De plus, le format de base
duplique les fonctions d'extraction des formats descendants pour lesquels
1) les noms de champ ne figurent que dans un seul nom de champ, ou 2) pour lesquels le
les noms de champ font référence au même champ de type (positions de bits signées/non signées)
dans chaque format
dans lequel elles apparaissent. Cela permet d'utiliser des champs de bits décrivant
bits à extraire à l'aide de fonctions dans l'espace de noms du format de premier niveau.
Les fonctions de l'espace de noms i_type
sont présentées ci-dessous:
namespace i_type {
inline uint8_t ExtractFunc3(uint32_t value) {
return (value >> 12) & 0x7;
}
inline int16_t ExtractImm12(uint32_t value) {
int16_t result = ( (value >> 20) & 0xfff) << 4;
result = result >> 4;
return result;
}
inline uint8_t ExtractOpcode(uint32_t value) {
return value & 0x7f;
}
inline uint8_t ExtractRd(uint32_t value) {
return (value >> 7) & 0x1f;
}
inline uint8_t ExtractRs1(uint32_t value) {
return (value >> 15) & 0x1f;
}
} // namespace i_type
Enfin, la déclaration de la fonction du décodeur pour l'instruction
le groupe RiscVInst32
est déclaré. Il prend un 32 bits non signés comme valeur
l'instruction et renvoie le membre de la classe d'énumération OpcodeEnum
.
qui correspond ou OpcodeEnum::kNone
en l'absence de correspondance.
OpcodeEnum DecodeRiscVInst32(uint32_t inst_word);
Fichier source généré (.cc)
Ouvrez maintenant riscv32i_bin_decoder.cc
. La première partie
du fichier contient
Les déclarations #include
et d'espace de noms, suivies de la fonction du décodeur
déclarations:
#include "riscv32i_bin_decoder.h"
namespace mpact {
namespace sim {
namespace codelab {
OpcodeEnum DecodeRiscVInst32None(uint32_t);
OpcodeEnum DecodeRiscVInst32_0(uint32_t inst_word);
OpcodeEnum DecodeRiscVInst32_0_3(uint32_t inst_word);
OpcodeEnum DecodeRiscVInst32_0_3c(uint32_t inst_word);
OpcodeEnum DecodeRiscVInst32_0_5c(uint32_t inst_word);
DecodeRiscVInst32None
est utilisé pour les actions de décodage vides, c'est-à-dire
qui renvoient OpcodeEnum::kNone
. Les trois autres fonctions constituent
généré par un décodeur. Le décodeur global fonctionne de manière hiérarchique. Un ensemble
de bits dans le mot d'instruction est calculé pour différencier
ou des groupes d'instructions de niveau supérieur. Il n'est pas nécessaire que les bits
être contiguës. Le nombre de bits détermine la taille
d'un tableau de conversion qui
contient des fonctions de décodeur de deuxième niveau. Nous verrons cela dans
du fichier:
absl::AnyInvocable<OpcodeEnum(uint32_t)> parse_group_RiscVInst32_0[kParseGroupRiscVInst32_0_Size] = {
&DecodeRiscVInst32None, &DecodeRiscVInst32None,
&DecodeRiscVInst32None, &DecodeRiscVInst32_0_3,
&DecodeRiscVInst32None, &DecodeRiscVInst32None,
...
&DecodeRiscVInst32None, &DecodeRiscVInst32None,
&DecodeRiscVInst32None, &DecodeRiscVInst32None,
&DecodeRiscVInst32_0_3c, &DecodeRiscVInst32None,
...
};
Enfin, les fonctions du décodeur sont définies:
OpcodeEnum DecodeRiscVInst32None(uint32_t) {
return OpcodeEnum::kNone;
}
OpcodeEnum DecodeRiscVInst32_0(uint32_t inst_word) {
if ((inst_word & 0x4003) != 0x3) return OpcodeEnum::kNone;
uint32_t index;
index = (inst_word >> 2) & 0x1f;
index |= (inst_word >> 7) & 0x60;
return parse_group_RiscVInst32_0[index](inst_word);
}
OpcodeEnum DecodeRiscVInst32_0_3(uint32_t inst_word) {
return OpcodeEnum::kFence;
}
OpcodeEnum DecodeRiscVInst32_0_3c(uint32_t inst_word) {
if ((inst_word & 0xf80) != 0x0) return OpcodeEnum::kNone;
return OpcodeEnum::kCsrwNr;
}
OpcodeEnum DecodeRiscVInst32_0_5c(uint32_t inst_word) {
uint32_t rs1_value = (inst_word >> 15) & 0x1f;
if (rs1_value != 0x0)
return OpcodeEnum::kCsrs;
if (rs1_value == 0x0)
return OpcodeEnum::kCsrsNw;
return OpcodeEnum::kNone;
}
OpcodeEnum DecodeRiscVInst32(uint32_t inst_word) {
OpcodeEnum opcode;
opcode = DecodeRiscVInst32_0(inst_word);
return opcode;
}
Dans ce cas, lorsqu'il n'y a que 4 instructions définies, un seul niveau de décodage et une table de conversion très creuse. Comme les instructions sont la structure du décodeur change et le nombre de niveaux dans la hiérarchie des tables du décodeur peut augmenter.
Ajouter des instructions ALU d'enregistrement et d'enregistrement
Vous allez maintenant ajouter de nouvelles instructions au fichier riscv32i.bin_fmt
. La
le premier groupe d'instructions sont
des instructions ALU d'enregistrement et d'enregistrement, telles que
add
, and
, etc. Sous RiscV32, ils utilisent tous l'instruction binaire de type R.
format:
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6... |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | rs2 | rs1 | func3 | e | code opération |
La première chose à faire est
d'ajouter le format. Allez-y, ouvrez
riscv32i.bin_fmt
dans votre éditeur préféré. Juste après le Inst32Format
,
ajoutez un format appelé RType
, dérivé de Inst32Format
. Tous les champs de bits
dans RType
sont unsigned
. Utilisez les noms, la largeur en bits et l'ordre (de gauche à droite).
du tableau ci-dessus pour définir le format. Si vous avez besoin d'un indice ou voulez voir
la solution complète,
cliquez ici.
Nous devons ensuite ajouter les instructions. Voici les instructions à suivre:
add
: addition d'un nombre entierand
: opérateur et au niveau du bit.or
: or au niveau du bit.sll
: décalage vers la gauche de la logique.sltu
: défini inférieur à, non signé.sub
: soustraction d'entier.xor
: fonction xor au niveau du bit.
Voici leurs encodages:
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6... | nom du code d'opération |
---|---|---|---|---|---|---|
000 000 | rs2 | rs1 | 000 | e | 011 0011 | add |
000 000 | rs2 | rs1 | 111 | e | 011 0011 | et |
000 000 | rs2 | rs1 | 110 | e | 011 0011 | ou |
000 000 | rs2 | rs1 | 001 | e | 011 0011 | sll |
000 000 | rs2 | rs1 | 011 | e | 011 0011 | sltu |
010 0000 | rs2 | rs1 | 000 | e | 011 0011 | Pub/Sub. |
000 000 | rs2 | rs1 | 100 | e | 011 0011 | XOR |
func7 | func3 | code opération |
Ajoutez ces définitions d'instructions avant les autres instructions dans le
Groupe d'instructions RiscVInst32
. Les chaînes binaires sont spécifiées
préfixe de 0b
(semblable à 0x
pour les nombres hexadécimaux). Pour faciliter
lire de longues chaînes de chiffres binaires, vous pouvez également insérer le guillemet simple '
comme
comme séparateur de chiffres.
Chacune de ces définitions d'instructions
aura trois contraintes, à savoir
func7
, func3
et opcode
. Pour toutes les entités sauf sub
, la contrainte func7
être:
func7 == 0b000'0000
La contrainte func3
varie dans la plupart des instructions. Pour add
et
sub
, c'est:
func3 == 0b000
La contrainte opcode
est la même pour chacune des instructions suivantes:
opcode == 0b011'0011
N'oubliez pas de terminer chaque ligne par un point-virgule ;
.
La solution finale est cliquez ici.
Créez maintenant votre projet comme vous l'avez fait précédemment, puis ouvrez le fichier
riscv32i_bin_decoder.cc
. Vous constaterez que les fonctions de décodeur supplémentaires
ont été générés pour gérer les nouvelles instructions. Dans la plupart des cas, ce sont
semblables à ceux générés précédemment, mais regardez
DecodeRiscVInst32_0_c
, utilisé pour le décodage add
/sub
:
OpcodeEnum DecodeRiscVInst32_0_c(uint32_t inst_word) {
static constexpr OpcodeEnum opcodes[2] = {
OpcodeEnum::kAdd,
OpcodeEnum::kSub,
};
if ((inst_word & 0xbe000000) != 0x0) return OpcodeEnum::kNone;
uint32_t index;
index = (inst_word >> 30) & 0x1;
return opcodes[index];
}
Dans cette fonction, une table de décodage statique est générée, et une valeur de recherche est extrait de l'instruction pour sélectionner l'index approprié. Cela permet d'ajouter deuxième couche de la hiérarchie du décodeur d'instruction, mais comme le code d'opération peut être sont consultées directement dans un tableau sans effectuer d'autres comparaisons, au lieu d'exiger un autre appel de fonction.
Ajouter des instructions ALU avec des résultats immédiats
La prochaine série d'instructions que nous ajouterons concerne les instructions ALU qui utilisent un une valeur immédiate au lieu de l'un des registres. Il existe trois groupes de ces instructions (basées sur le champ immédiat): les instructions immédiates I-Type avec une signature immédiate de 12 bits, les instructions spécialisées I-Type immédiates pour les décalages et le type U immédiate, avec une valeur immédiate non signée de 20 bits. Les formats sont présentés ci-dessous:
Le format I-Type immédiat:
31..20 | 19..15 | 14..12 | 11..7 | 6... |
---|---|---|---|---|
12 | 5 | 3 | 5 | 7 |
imm12 | rs1 | func3 | e | code opération |
Le format I-Type spécialisé immédiat:
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6... |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | uimm5 | rs1 | func3 | e | code opération |
Format immédiat de type U:
31..12 | 11..7 | 6... |
---|---|---|
20 | 5 | 7 |
uimm20 | e | code opération |
Le format I-Type existe déjà dans riscv32i.bin_fmt
. Il n'est donc pas nécessaire de
ajouter ce format.
Si nous comparons le format spécialisé I-Type
au format R-Type que nous avons défini dans
Dans l'exercice précédent, nous constatons que la seule différence est que les champs rs2
est renommée uimm5
. Au lieu d'ajouter un tout nouveau format, nous pouvons enrichir l'interface
R-Type. Impossible d'ajouter un autre champ, car cela augmenterait la largeur de
le format, mais nous pouvons ajouter une superposition. Une superposition est un alias pour un ensemble
bits au format et peuvent être utilisés pour combiner plusieurs sous-séquences du
en une entité nommée distincte. L'effet secondaire est que le code généré
inclut désormais une fonction d'extraction pour la superposition,
pour les champs. Dans ce cas, lorsque rs2
et uimm5
ne sont pas signés,
ne fait pas beaucoup de différence, si ce n'est qu'il faut indiquer explicitement que le champ est utilisé
immédiatement. Pour ajouter une superposition nommée uimm5
au format R-Type, ajoutez la méthode
après le dernier champ:
overlays:
unsigned uimm5[5] = rs2;
Le seul nouveau format que nous devons ajouter est le format U-Type. Avant d'ajouter le
prenons le cas des deux instructions qui utilisent ce format: auipc
et
lui
Ces deux éléments décalent la valeur immédiate de 20 bits de 12 avant de l'utiliser
pour y ajouter le PC (auipc
) ou l'écrire directement dans un registre
(lui
). En utilisant une superposition, nous pouvons fournir
une version pré-décalée de l'image,
en transférant un peu de calcul
de l'exécution de l'instruction à l'instruction
le décodage. Ajoutez d'abord le format en fonction des champs spécifiés dans le tableau
ci-dessus. Nous pouvons ensuite ajouter la superposition suivante:
overlays:
unsigned uimm32[32] = uimm20, 0b0000'0000'0000;
La syntaxe de superposition nous permet de concaténer non seulement des champs, mais aussi des littéraux bien. Ici, nous la concaténons avec 12 zéros, ce qui permet de la déplacer vers la gauche. par 12.
Les instructions I-Type que nous devons ajouter sont les suivantes:
addi
: ajouter immédiatement.andi
: au niveau du bit et avec immédiate.ori
: au niveau du bit ou avec immédiate.xori
: valeur xor au niveau du bit avec immédiat.
Voici leurs encodages:
31..20 | 19..15 | 14..12 | 11..7 | 6... | opcode_name |
---|---|---|---|---|---|
imm12 | rs1 | 000 | e | 001 0011 | Addi |
imm12 | rs1 | 111 | e | 001 0011 | andi |
imm12 | rs1 | 110 | e | 001 0011 | ori |
imm12 | rs1 | 100 | e | 001 0011 | Xori |
func3 | code opération |
Les instructions R-Type (spécialisées I-Type) que nous devons ajouter sont les suivantes:
slli
: décalage immédiat de la logique vers la gauche.srai
: décale l'arithmétique vers la droite de manière immédiate.srli
: décale immédiatement la logique vers la droite.
Voici leurs encodages:
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6... | nom du code d'opération |
---|---|---|---|---|---|---|
000 000 | uimm5 | rs1 | 001 | e | 001 0011 | slli |
010 0000 | uimm5 | rs1 | 101 | e | 001 0011 | srai |
000 000 | uimm5 | rs1 | 101 | e | 001 0011 | srli |
func7 | func3 | code opération |
Les instructions de type U que nous devons ajouter sont les suivantes:
auipc
: ajoute la valeur "upper" immédiate au PC.lui
: charge la partie supérieure immédiatement.
Voici leurs encodages:
31..12 | 11..7 | 6... | nom du code d'opération |
---|---|---|---|
uimm20 | e | 001 0111 | auipc |
uimm20 | e | 011 0111 | lui |
code opération |
Apportez les modifications nécessaires, puis créez votre solution. Vérifiez le résultat généré. Juste comme précédemment, vous pouvez comparer votre travail riscv32i.bin_fmt.
Ajouter des instructions de branche et de saut et de lien
La prochaine série d'instructions à définir est la branche conditionnelle les instructions de lancement, l'instruction de création de lien et de lien instruction.
Les branches conditionnelles que nous ajoutons toutes utilisent l'encodage de type B.
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6... |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
imm7 | rs2 | rs1 | func3 | imm5 | code opération |
Bien que l'encodage B-Type soit
identique à l'encodage R-Type, nous
choisissez d'utiliser un nouveau type de format
pour qu'il s'aligne sur la documentation RiscV.
Mais vous pouvez aussi simplement ajouter une superposition pour obtenir la branche appropriée
déplacement immédiat, à l'aide des champs func7
et rd
du type R
l'encodage.
Il est nécessaire d'ajouter un format BType
avec les champs spécifiés ci-dessus, mais ce n'est pas nécessaire.
suffisant. Comme vous pouvez le voir, le résultat immédiat est divisé en deux champs d'instruction.
De plus, les instructions de branche ne la traitent pas comme une simple concaténation de
les deux champs. Au lieu de cela, chaque champ est partitionné davantage, et ces partitions
sont concaténés dans
un ordre différent. Enfin, cette valeur est décalée vers la gauche
pour obtenir un décalage aligné de 16 bits.
La séquence de bits du mot d'instruction utilisé pour former l'instance immédiate est: 31,
7, 30..25, 11..8. Cela correspond aux références de sous-champs suivantes, où
L'index ou la plage spécifient les bits du champ, numérotés de droite à gauche, c'est-à-dire
imm7[6]
fait référence au msb de imm7
, et imm5[0]
fait référence au fichier lsb de
imm5
imm7[6], imm5[0], imm7[5..0], imm5[4..1]
L'intégration de cette manipulation de bits dans les instructions de branche elles-mêmes comportent deux
de gros inconvénients. Tout d'abord, elle lie l'implémentation
de la fonction sémantique à
dans la représentation de l'instruction binaire. Deuxièmement, elle augmente la durée d'exécution
ou d'autres frais généraux. La solution consiste à ajouter une superposition au format BType
, y compris un élément
"0" à la fin pour tenir compte du décalage de gauche.
overlays:
signed b_imm[13] = imm7[6], imm5[0], imm7[5..0], imm5[4..1], 0b0;
Notez que la superposition est signée et sera donc automatiquement étendue. lorsqu'il est extrait du mot de l'instruction.
L'instruction jump-and-link (immédiate) utilise le codage J-Type:
31..12 | 11..7 | 6... |
---|---|---|
20 | 5 | 7 |
imm20 | e | code opération |
Il s'agit également d'un format facile à ajouter, mais là encore, le format immédiat utilisé par le l'instruction n'est pas aussi simple qu’il y paraît. Les séquences de bits utilisées pour des résultats immédiats complets sont: 31, 19..12, 20, 30..21, et les résultats immédiats finaux sont décalé d'un caractère vers la gauche pour un alignement à la moitié du mot. La solution consiste à ajouter superposition (21 bits pour tenir compte du décalage gauche) au format:
overlays:
signed j_imm[21] = imm20[19, 7..0, 8, 18..9], 0b0;
Comme vous pouvez le voir, la syntaxe des superpositions prend en charge la spécification de plusieurs plages dans une dans un format raccourci. De plus, si aucun nom de champ n'est utilisé, le bit les chiffres font référence à l'instruction lui-même, ce qui peut aussi bien être écrit sous la forme:
signed j_imm[21] = [31, 19..12, 20, 30..21], 0b0;
Enfin, le jump-and-link (enregistre) utilise le format I-type utilisé précédemment.
Le format I-Type immédiat:
31..20 | 19..15 | 14..12 | 11..7 | 6... |
---|---|---|---|---|
12 | 5 | 3 | 5 | 7 |
imm12 | rs1 | func3 | e | code opération |
Cette fois, aucune modification ne doit être apportée au format.
Les instructions concernant la branche que nous devons ajouter sont les suivantes:
beq
: branche si elle est égale.bge
: branche si elle est supérieure ou égale à.bgeu
: branche si supérieure ou égale à non signée.blt
: branche si la valeur est inférieure à.bltu
: branche si la valeur n'est pas déjà signée.bne
: branche si différente.
Ils sont encodés comme suit:
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6... | nom du code d'opération |
---|---|---|---|---|---|---|
imm7 | rs2 | rs1 | 000 | imm5 | 110 0011 | beq |
imm7 | rs2 | rs1 | 101 | imm5 | 110 0011 | Bge |
imm7 | rs2 | rs1 | 111 | imm5 | 110 0011 | bgeu |
imm7 | rs2 | rs1 | 100 | imm5 | 110 0011 | blt |
imm7 | rs2 | rs1 | 110 | imm5 | 110 0011 | bltu |
imm7 | rs2 | rs1 | 001 | imm5 | 110 0011 | Bne |
func3 | code opération |
L'instruction jal
est encodée comme suit:
31..12 | 11..7 | 6... | nom du code d'opération |
---|---|---|---|
imm20 | e | 110 1111 | Jal |
code opération |
L'instruction jalr
est encodée comme suit:
31..20 | 19..15 | 14..12 | 11..7 | 6... | opcode_name |
---|---|---|---|---|---|
imm12 | rs1 | 000 | e | 110 0111 | Jalr |
func3 | code opération |
Apportez les modifications nécessaires, puis créez votre solution. Vérifiez le résultat généré. Juste comme précédemment, vous pouvez comparer votre travail riscv32i.bin_fmt.
Ajouter des instructions pour les magasins
Les instructions du magasin utilisent l'encodage S-Type, qui est identique au type B
utilisé par les instructions sur les branches, à l'exception de la composition du
immédiatement. Nous avons choisi d'ajouter le format SType
pour rester aligné sur le RiscV
dans la documentation Google Cloud.
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6... |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
imm7 | rs2 | rs1 | func3 | imm5 | code opération |
Dans le cas du format SType
, l'expression immédiate est heureusement
concaténation directe des deux champs immédiats, de sorte que la spécification de superposition
est simplement:
overlays:
signed s_imm[12] = imm7, imm5;
Notez qu'aucun spécificateur de plage de bits n'est requis lorsque vous concaténez des champs entiers.
Les instructions du magasin sont encodées comme suit:
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6... | nom du code d'opération |
---|---|---|---|---|---|---|
imm7 | rs2 | rs1 | 000 | imm5 | 010 0011 | sb |
imm7 | rs2 | rs1 | 001 | imm5 | 010 0011 | sh |
imm7 | rs2 | rs1 | 010 | imm5 | 010 0011 | sw |
func3 | code opération |
Apportez les modifications nécessaires, puis créez votre solution. Vérifiez le résultat généré. Juste comme précédemment, vous pouvez comparer votre travail riscv32i.bin_fmt.
Ajouter des instructions de chargement
Les instructions de chargement utilisent le format I-Type. Aucune modification ne doit y être apportée.
Les encodages sont les suivants:
31..20 | 19..15 | 14..12 | 11..7 | 6... | opcode_name |
---|---|---|---|---|---|
imm12 | rs1 | 000 | e | 000 0011 | lb |
imm12 | rs1 | 100 | e | 000 0011 | lbu |
imm12 | rs1 | 001 | e | 000 0011 | m |
imm12 | rs1 | 101 | e | 000 0011 | Lhu |
imm12 | rs1 | 010 | e | 000 0011 | lw |
func3 | code opération |
Apportez les modifications nécessaires, puis créez votre solution. Vérifiez le résultat généré. Juste comme précédemment, vous pouvez comparer votre travail riscv32i.bin_fmt.
Ce tutoriel est à présent terminé. Nous espérons qu'il vous a été utile.