Gli obiettivi di questo tutorial sono:
- Scopri la struttura e la sintassi del file di descrizione del formato binario.
- Scopri come la descrizione del formato binario corrisponde alla descrizione ISA.
- Scrivi le descrizioni binarie per il sottoinsieme RiscV RV32I di istruzioni.
Panoramica
Codifica dell'istruzione binaria RiscV
La codifica delle istruzioni binarie è il metodo standard per codificare le istruzioni per: su un microprocessore. Di solito sono archiviati in un file eseguibile, di solito nel formato ELF. Le istruzioni possono essere a larghezza fissa o variabili standard.
In genere, le istruzioni utilizzano un piccolo insieme di formati di codifica, con ogni formato personalizzate per il tipo di istruzioni codificate. Ad esempio, registry-register le istruzioni possono usare un unico formato che massimizza il numero di codici operativi disponibili, mentre le istruzioni di registrazione immediata ne usano un'altra che bilancia il numero disponibili per aumentare la dimensione dell'immediato che può essere codificato. Le istruzioni per rami e salti utilizzano quasi sempre formati che massimizzano le dimensioni nell'immediato in modo da supportare rami con offset più grandi.
I formati delle istruzioni utilizzati dalle istruzioni che vogliamo decodificare nel nostro RiscV sono le seguenti:
Formato R-Type, utilizzato per le istruzioni di registro:
31,25 | 24/20 | 19/15 | 14..12 | 11..7 | 6,0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | rs2 | rs1 | func3 | ° | opcode, codice operativo |
Formato I-Type, utilizzato per le istruzioni di registro immediate, le istruzioni di caricamento e
l'istruzione jalr
, immediatamente a 12 bit.
31/20 | 19/15 | 14..12 | 11..7 | 6,0 |
---|---|---|---|---|
12 | 5 | 3 | 5 | 7 |
imm12 | rs1 | func3 | ° | opcode, codice operativo |
Formato I-Type specializzato, utilizzato per i turni con istruzioni immediate, a 5 bit immediato:
31,25 | 24/20 | 19/15 | 14..12 | 11..7 | 6,0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | uimm5 | rs1 | func3 | ° | opcode, codice operativo |
Formato tipo U, utilizzato per istruzioni lunghe e immediate (lui
, auipc
), 20 bit
immediato:
31.12 | 11..7 | 6,0 |
---|---|---|
20 | 5 | 7 |
uimm20 | ° | opcode, codice operativo |
Formato B-Type, utilizzato per i rami condizionali, immediato a 12 bit.
31 | 30:00 | 24/20 | 19/15 | 14..12 | 11,8 | 7 | 6,0 |
---|---|---|---|---|---|---|---|
1 | 6 | 5 | 5 | 3 | 4 | 1 | 7 |
imm | imm | rs2 | rs1 | func3 | imm | imm | opcode, codice operativo |
Formato J-Type, utilizzato per l'istruzione jal
, immediato a 20 bit.
31 | 30/21 | 20 | 19/12 | 11..7 | 6,0 |
---|---|---|---|---|---|
1 | 10 | 1 | 8 | 5 | 7 |
imm | imm | imm | imm | ° | opcode, codice operativo |
Formato S-Type, utilizzato per le istruzioni dello store, immediato a 12 bit.
31,25 | 24/20 | 19/15 | 14..12 | 11..7 | 6,0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
imm | rs2 | rs1 | func3 | imm | opcode, codice operativo |
Come puoi vedere da questi formati, tutte queste istruzioni sono a 32 bit e il basso a 7 bit in ogni formato è il campo opcode. Nota inoltre che, anche se diversi formati hanno immediate dimensioni, i loro bit vengono presi parti diverse dell'istruzione. Come vedremo, il decoder binario formato delle specifiche è in grado di esprimerlo.
Descrizione codifica binaria
La codifica binaria dell'istruzione è espressa in formato binario.
(.bin_fmt
) file di descrizione. Descrive la codifica binaria
istruzioni in un ISA in modo che un decoder di istruzioni in formato binario
generati. Il decoder generato determina l'opcode, estrae il valore di
operando e campi immediati, al fine di fornire le informazioni richieste dall'ISA
decoder indipendente dalla codifica descritto nel tutorial precedente.
In questo tutorial scriveremo un file di descrizione della codifica binaria per un sottoinsieme delle istruzioni RiscV32I necessarie per simulare le istruzioni utilizzate in "Hello World" piccolo . Per maggiori dettagli sull'ISA di RiscV, consulta Specifiche Risc-V{.external}.
Per iniziare, apri il file:
riscv_bin_decoder/riscv32i.bin_fmt
.
I contenuti del file sono suddivisi in diverse sezioni.
Innanzitutto, la definizione di 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 definizione del decoder specifica il nome del decoder RiscV32I
, così come
altre quattro informazioni. Il primo è namespace
e definisce
lo spazio dei nomi in cui verrà inserito il codice generato. In secondo luogo,
opcode_enum
, che indica il modo in cui viene generato il tipo di enumerazione opcode
dal decoder ISA deve essere fatto riferimento all'interno del codice generato. In terzo luogo,
includes {}
specifica di includere i file necessari per il codice generato
questo decoder.
Nel nostro caso, si tratta del file che viene prodotto dal decoder ISA dalla
tutorial precedente.
Puoi specificare altri file di inclusione in un campo includes {}
con ambito globale
definizione di Kubernetes. Questo è utile se vengono definiti più decoder e tutti devono
per includere alcuni degli stessi file. La quarta è un elenco di nomi di
gruppi che compongono le istruzioni per cui viene generato il decoder. Nel nostro
caso ce ne sia solo uno: RiscVInst32
.
Ci sono poi tre definizioni del formato. Queste rappresentano istruzioni diverse formati per una parola di istruzione a 32 bit utilizzata dalle istruzioni già definite nel file.
// 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];
};
Il primo definisce un formato di istruzione a 32 bit denominato Inst32Format
che ha
due campi: bits
(a 25 bit) e opcode
(a 7 bit di larghezza). Ogni campo
unsigned
, il che significa che il valore sarà esteso a zero al momento dell'estrazione
e posizionati in un tipo intero C++. La somma delle larghezze dei campi di bit deve
uguale alla larghezza del formato. Lo strumento genera un errore se viene rilevato
disaccordi. Questo formato non deriva da nessun altro formato, quindi
considerata un formato di primo livello.
Il secondo definisce un formato di istruzione a 32 bit denominato IType
che ricava
da Inst32Format
, rendendo questi due formati correlati. Il formato contiene 5
campi: imm12
, rs1
, func3
, rd
e opcode
. Il campo imm12
è
signed
, il che significa che il valore sarà esteso per segno quando è
estratte e inserite in un tipo intero C++. Tieni presente che IType.opcode
ha entrambi
lo stesso attributo firmato e non firmato e si riferisce agli stessi bit di parola di istruzione
come Inst32Format.opcode
.
Il terzo è un formato personalizzato utilizzato esclusivamente dall'fence
istruzione, ovvero un'istruzione già specificata e al momento
di cui preoccuparsi in questo tutorial.
Punto chiave: riutilizza i nomi dei campi in diversi formati correlati, a condizione che rappresentano gli stessi bit e hanno lo stesso attributo firmato/non firmato.
Dopo le definizioni del formato in riscv32i.bin_fmt
arriva un gruppo di istruzioni
definizione di Kubernetes. Tutte le istruzioni di un gruppo di istruzioni devono avere lo stesso
di bit di bit e utilizzare un formato che derivi (magari indirettamente) dalla stessa
formato di istruzione di primo livello. Quando un ISA può avere istruzioni con diverse
lunghezze, viene utilizzato un gruppo di istruzioni diverso per ogni lunghezza. Inoltre,
se la decodifica ISA target dipende da una modalità di esecuzione, come Arm vs. Thumb
istruzioni, è necessario un gruppo di istruzioni separato per ogni modalità. La
Il parser bin_fmt
genera un decoder binario per ogni gruppo di istruzioni.
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;
};
Il gruppo di istruzioni definisce un nome RiscV32I
, una larghezza [32]
, il nome
il tipo di enumerazione opcode per utilizzare "OpcodeEnum"
e un'istruzione di base
formato. Il tipo di enumerazione del codice operativo deve essere lo stesso prodotto dalla
formato da un decoder di istruzioni indipendente descritto nel tutorial sulla
decodificatore.
La descrizione della codifica di ogni istruzione è composta da 3 parti:
- Il nome del codice operativo, che deve essere lo stesso utilizzato nell'istruzione del decoder in modo che interagiscano.
- Il formato dell'istruzione da utilizzare per l'opcode. Questo è il formato usato per soddisfare i riferimenti ai campi di bit nella parte finale.
- Un elenco separato da virgole di vincoli di campo di bit,
==
,!=
,<
,<=
,>
, e>=
che tutti i valori siano veri affinché l'opcode possa corrispondere correttamente al parola di istruzione.
Il parser .bin_fmt
utilizza tutte queste informazioni per creare un decoder che:
- Fornisce funzioni di estrazione (firmato/non firmato) in base alle esigenze di ogni bit
campo in ogni formato. Le funzioni di estrazione sono inserite negli spazi dei nomi
denominato in base alla versione snake-case del nome del formato. Ad esempio,
le funzioni di estrazione per il formato
IType
vengono inserite nello spazio dei nomii_type
. Ogni funzione di estrazione è dichiaratainline
, prende il valoreuint_t
che contiene la larghezza del formato e restituisce il valoreint_t
più stretto (per il tipo con firma),uint_t
(per il tipo non firmato), che contiene il campo estratto standard. ad esempio:
inline uint8_t ExtractOpcode(uint32_t value) {
return value & 0x7f;
}
- Una funzione di decodifica per ogni gruppo di istruzioni. Restituisce un valore di tipo
OpcodeEnum
e prende il tipouint_t
più stretto che contiene la larghezza di il formato del gruppo di istruzioni.
Esegui build iniziale
Cambia la directory in riscv_bin_decoder
e crea il progetto utilizzando
seguente comando:
$ cd riscv_bin_decoder
$ bazel build :all
Ora reimposta la directory alla radice del repository, quindi diamo un'occhiata
alle origini generate. Per farlo, cambia la directory in
bazel-out/k8-fastbuild/bin/riscv_bin_decoder
(supponendo che tu stia utilizzando un dispositivo x86)
host - per altri host, k8-fastbuild sarà un'altra stringa).
$ cd ..
$ cd bazel-out/k8-fastbuild/bin/riscv_bin_decoder
riscv32i_bin_decoder.h
riscv32i_bin_decoder.cc
Il file di intestazione generato (.h)
Apri riscv32i_bin_decoder.h
. La prima parte del file contiene
file boilerplate, dichiarazioni dello spazio dei nomi e file inclusi. In seguito
c'è una funzione helper basata su modelli nello spazio dei nomi internal
. Questa funzione
viene utilizzato per estrarre campi di bit da formati troppo lunghi per adattarsi a un file C++ a 64 bit.
numero intero.
#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
Dopo la sezione iniziale c'è un insieme di tre spazi dei nomi, uno per ogni
delle dichiarazioni format
nel file riscv32i.bin_fmt
:
namespace fence {
...
} // namespace fence
namespace i_type {
...
} // namespace i_type
namespace inst32_format {
...
} // namespace inst32_format
All'interno di ciascuno di questi spazi dei nomi, la funzione di estrazione del campo di bit inline
per ogni campo di bit in quel formato. Inoltre, il formato di base
duplica funzioni di estrazione dai formati discendenti per cui
1) i nomi dei campi sono presenti solo in un nome di campo singolo oppure 2) per il quale le
i nomi dei campi fanno riferimento allo stesso campo del tipo (con firma/non firmato e posizioni bit)
in tutti i formati in cui si presentano. In questo modo vengono abilitati i campi di bit che descrivono
bit da estrarre utilizzando le funzioni nello spazio dei nomi del formato di primo livello.
Di seguito sono mostrate le funzioni nello spazio dei nomi i_type
:
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
Infine, la dichiarazione della funzione decoder per l'istruzione
il gruppo RiscVInst32
è stato dichiarato. Prende un token non firmato a 32 bit come valore
la parola di istruzione e restituisce il membro della classe di enumerazione OpcodeEnum
corrisponde a quello specificato o OpcodeEnum::kNone
se non ci sono corrispondenze.
OpcodeEnum DecodeRiscVInst32(uint32_t inst_word);
Il file di origine generato (.cc)
Ora apri riscv32i_bin_decoder.cc
. La prima parte del file contiene
le dichiarazioni #include
e dello spazio dei nomi, seguite dalla funzione decoder
dichiarazioni:
#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
viene utilizzato per le azioni di decodifica vuote, ad esempio quelle
che restituiscono OpcodeEnum::kNone
. Le altre tre funzioni costituiscono
decoder generato. Il decoder complessivo funziona in modo gerarchico. Un insieme
di bit nella parola di istruzione viene calcolato
istruzioni o gruppi di istruzioni al livello superiore. I bit non devono
essere contigui. Il numero di bit determina la dimensione di una tabella di ricerca che
viene compilata con funzioni di decoder di secondo livello. come vedrai nel prossimo
sezione del file:
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,
...
};
Infine, le funzioni di decoder sono definite:
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;
}
In questo caso, quando sono definite solo 4 istruzioni, c'è solo un un singolo livello di decodifica e una tabella di ricerca molto sparsa. Poiché le istruzioni sono aggiunto, la struttura del decoder cambierà e il numero di livelli in decoder possono aumentare.
Aggiungi istruzioni ALU per la registrazione e la registrazione
Ora è il momento di aggiungere alcune nuove istruzioni al file riscv32i.bin_fmt
. La
Il primo gruppo di istruzioni è costituito dalle istruzioni ALU registrate nel registro, come
add
, and
e così via. Su RiscV32, queste utilizzano tutte l'istruzione binaria di tipo R
formato:
31,25 | 24/20 | 19/15 | 14..12 | 11..7 | 6,0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | rs2 | rs1 | func3 | ° | opcode, codice operativo |
La prima cosa da fare è aggiungere il formato. Vai avanti e apri
riscv32i.bin_fmt
nel tuo editor preferito. Subito dopo Inst32Format
aggiungi un formato denominato RType
che deriva da Inst32Format
. Tutti i campi di bit
in RType
sono unsigned
. Utilizza nomi, larghezza in bit e ordine (da sinistra a destra)
dalla tabella precedente per definire il formato. Se hai bisogno di un suggerimento o vuoi vedere
la soluzione completa,
fai clic qui.
Poi dobbiamo aggiungere le istruzioni. Ecco le istruzioni:
add
- aggiunta di numeri interi.and
: bit a bit e.or
: bit a bit o.sll
: sposta verso sinistra in modo logico.sltu
: imposta meno di, non firmato.sub
: numero intero sottratto.xor
: xor a livello di bit.
Le loro codifiche sono:
31,25 | 24/20 | 19/15 | 14..12 | 11..7 | 6,0 | nome opcode |
---|---|---|---|---|---|---|
000 0000 | rs2 | rs1 | 000 | ° | 011 0011 | add |
000 0000 | rs2 | rs1 | 111 | ° | 011 0011 | e |
000 0000 | rs2 | rs1 | 110 | ° | 011 0011 | o |
000 0000 | rs2 | rs1 | 001 | ° | 011 0011 | sll |
000 0000 | rs2 | rs1 | 011 | ° | 011 0011 | sltu |
010 0000 | rs2 | rs1 | 000 | ° | 011 0011 | Pub/Sub. |
000 0000 | rs2 | rs1 | 100 | ° | 011 0011 | Xor |
func7 | func3 | opcode, codice operativo |
Aggiungi queste definizioni di istruzione prima delle altre istruzioni nella sezione
Gruppo di istruzioni RiscVInst32
. Le stringhe binarie sono specificate con
prefisso di 0b
(simile a 0x
per i numeri esadecimali). Per semplificare la
leggere lunghe stringhe di cifre binarie, puoi anche inserire l'apice singola '
come
un separatore di cifre che ritieni adeguato.
Ognuna di queste definizioni di istruzione avrà tre vincoli, ovvero
func7
, func3
e opcode
. Per tutti tranne sub
, il vincolo func7
potrebbe essere:
func7 == 0b000'0000
Il vincolo func3
varia per la maggior parte delle istruzioni. Per add
e
sub
è:
func3 == 0b000
Il vincolo opcode
è lo stesso per ciascuna di queste istruzioni:
opcode == 0b011'0011
Ricorda di terminare ogni riga con un punto e virgola ;
.
La soluzione completa è qui
A questo punto, crea il tuo progetto come in precedenza e apri lo strumento
riscv32i_bin_decoder.cc
. Possiamo vedere che funzioni decoder aggiuntive
generati per gestire le nuove istruzioni. Per la maggior parte sono
simili a quelli generati in precedenza, ma guarda
DecodeRiscVInst32_0_c
utilizzato per la decodifica 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];
}
In questa funzione viene generata una tabella di decodifica statica e viene generato un valore di ricerca estratti dalla parola di istruzione per selezionare l'indice appropriato. Questa operazione aggiunge secondo livello nella gerarchia del decoder delle istruzioni, ma poiché l'opcode può essere cercato direttamente in una tabella senza ulteriori confronti, è incorporato in anziché richiedere un'altra chiamata di funzione.
Aggiungi istruzioni ALU con immediate
La prossima serie di istruzioni che aggiungeremo è costituita da istruzioni ALU che utilizzano un valore immediato invece che uno dei registri. Esistono tre gruppi di questi istruzioni (in base al campo immediato): le istruzioni immediate tipo I-Type con firma immediata a 12 bit, le istruzioni specializzate in I-Type per gli spostamenti e U-Type immediato, con un valore immediato senza segno a 20 bit. Di seguito sono riportati i formati:
Formato immediato I-Type:
31/20 | 19/15 | 14..12 | 11..7 | 6,0 |
---|---|---|---|---|
12 | 5 | 3 | 5 | 7 |
imm12 | rs1 | func3 | ° | opcode, codice operativo |
Il formato immediato I-Type specializzato:
31,25 | 24/20 | 19/15 | 14..12 | 11..7 | 6,0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | uimm5 | rs1 | func3 | ° | opcode, codice operativo |
Il formato immediato di U-Type:
31.12 | 11..7 | 6,0 |
---|---|---|
20 | 5 | 7 |
uimm20 | ° | opcode, codice operativo |
Il formato I-Type esiste già in riscv32i.bin_fmt
, quindi non è necessario
aggiungi quel formato.
Se confrontiamo il formato I-Type specializzato con il formato R-Type definito in
nell'esercizio precedente, vediamo che l'unica differenza è che i campi rs2
è stato rinominato in uimm5
. Invece di aggiungere un nuovo formato, possiamo ampliare
formato R-Type. Non possiamo aggiungere un altro campo, perché verrebbe aumentato la larghezza del
il formato, ma possiamo aggiungere un overlay. Un overlay è l'alias di un insieme di
bit nel formato e può essere utilizzato per combinare più sottosequenze
in un'entità con nome separata. L'effetto collaterale è che il codice generato
ora includerà anche una funzione di estrazione per l'overlay, oltre a
per i campi. In questo caso, quando rs2
e uimm5
non sono firmati,
non fa molta differenza se non per chiarire che il campo viene utilizzato
nell'immediato. Per aggiungere un overlay denominato uimm5
al formato R-Type, aggiungi
dopo l'ultimo campo:
overlays:
unsigned uimm5[5] = rs2;
L'unico nuovo formato che dobbiamo aggiungere è il formato U-Type. Prima di aggiungere
formato, consideriamo le due istruzioni che utilizzano tale formato: auipc
e
lui
. Entrambe spostano il valore immediato a 20 bit lasciato di 12 prima di usarlo
aggiungere il pc (auipc
) o scriverlo direttamente in un registro
(lui
). Usando un overlay possiamo fornire una versione pre-spostamento del testo immediato,
di spostare un po' di calcolo dall'esecuzione dell'istruzione all'istruzione
decodificare. Innanzitutto aggiungi il formato in base ai campi specificati nella tabella
in alto. Poi possiamo aggiungere il seguente overlay:
overlays:
unsigned uimm32[32] = uimm20, 0b0000'0000'0000;
La sintassi dell'overlay ci permette di concatenare non solo i campi, ma i valori letterali beh. In questo caso la concateniamo con 12 zeri, spostandola quindi verso sinistra per 12.
Le istruzioni I-Type che dobbiamo aggiungere sono:
addi
- Aggiungi subito.andi
: a livello di bit e con immediatezza.ori
: bit a bit o con immediatezza.xori
: xor a livello di bit con effetto immediato.
Le loro codifiche sono:
31/20 | 19/15 | 14..12 | 11..7 | 6,0 | opcode_name |
---|---|---|---|---|---|
imm12 | rs1 | 000 | ° | 001 0011 | Addi |
imm12 | rs1 | 111 | ° | 001 0011 | Andi |
imm12 | rs1 | 110 | ° | 001 0011 | Ori |
imm12 | rs1 | 100 | ° | 001 0011 | Xori |
func3 | opcode, codice operativo |
Le istruzioni R-Type (specializzato I-Type) che dobbiamo aggiungere sono:
slli
- Sposta la logica verso sinistra immediatamente.srai
: sposta l'aritmetica a destra per immediata.srli
: sposta subito verso destra la logica.
Le loro codifiche sono:
31,25 | 24/20 | 19/15 | 14..12 | 11..7 | 6,0 | nome opcode |
---|---|---|---|---|---|---|
000 0000 | uimm5 | rs1 | 001 | ° | 001 0011 | SLL |
010 0000 | uimm5 | rs1 | 101 | ° | 001 0011 | Sara |
000 0000 | uimm5 | rs1 | 101 | ° | 001 0011 | srli |
func7 | func3 | opcode, codice operativo |
Le istruzioni per il tipo U che dobbiamo aggiungere sono:
auipc
- Aggiungi immediatamente superiore al pc.lui
- Carica immediatamente la parte superiore della pagina.
Le loro codifiche sono:
31.12 | 11..7 | 6,0 | nome opcode |
---|---|---|---|
uimm20 | ° | 001 0111 | auipc |
uimm20 | ° | 011 0111 | lui |
opcode, codice operativo |
Apporta le modifiche e crea. Controlla l'output generato. Soltanto Come in precedenza, puoi verificare il tuo lavoro riscv32i.bin_fmt.
Aggiungi istruzioni per salti e collegamenti
La serie successiva di istruzioni da definire è il ramo condizionale istruzioni, le istruzioni jump-and-link e il registro jump-and-link istruzioni.
Tutti i rami condizionali che stiamo aggiungendo utilizzano la codifica B-Type.
31,25 | 24/20 | 19/15 | 14..12 | 11..7 | 6,0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
imm7 | rs2 | rs1 | func3 | imm5 | opcode, codice operativo |
Mentre la codifica B-Type è identica nel layout alla codifica R-Type,
scegli di usare un nuovo tipo di formato in modo che sia in linea con la documentazione di RiscV.
Potresti anche aver aggiunto un overlay per ottenere il ramo appropriato
dello spostamento immediato, usando i campi func7
e rd
del parametro R-Type
codifica.
È necessario aggiungere un formato BType
con i campi come specificato sopra, ma non
sufficienti. Come puoi vedere, le istruzioni immediate sono suddivise in due campi di istruzione.
Inoltre, le istruzioni per i rami non lo considerano una semplice concatenazione di
nei due campi. Ogni campo è invece ulteriormente partizionato e queste partizioni
sono concatenati in un ordine diverso. Infine, questo valore viene spostato a sinistra
uno per ottenere un offset allineato a 16 bit.
La sequenza di bit nella parola di istruzione utilizzata per formare l'immediato sono: 31,
7, 30..25, 11..8. Ciò corrisponde ai seguenti riferimenti di campi secondari,
l'indice o l'intervallo specifica i bit nel campo, numerati da destra a sinistra, ovvero
imm7[6]
si riferisce alla metrica msb di imm7
e imm5[0]
alla lsb di
imm5
.
imm7[6], imm5[0], imm7[5..0], imm5[4..1]
Rendere questa manipolazione dei bit parte integrante delle istruzioni del ramo di per sé ha due
grandi svantaggi. Innanzitutto, collega l'implementazione della funzione semantica a
nella rappresentazione dell'istruzione binaria. In secondo luogo, aggiunge più tempo di esecuzione
overhead. La risposta è aggiungere un overlay al formato BType
, includendo
"0" finale per tenere conto del turno di sinistra.
overlays:
signed b_imm[13] = imm7[6], imm5[0], imm7[5..0], imm5[4..1], 0b0;
Nota che l'overlay è firmato, quindi verrà esteso automaticamente. quando viene estratto dalla parola di istruzione.
L'istruzione jump-and-link (immediata) utilizza la codifica J-Type:
31.12 | 11..7 | 6,0 |
---|---|---|
20 | 5 | 7 |
imm20 | ° | opcode, codice operativo |
Anche questo è un formato facile da aggiungere, ma ancora una volta, l'immediato utilizzato l'istruzione non è così semplice come sembra. Le sequenze di bit utilizzate l'immediato completo sono: 31, 19..12, 20, 30..21, e l'immediato finale è spostato a sinistra di uno per l'allineamento a metà parola. La soluzione consiste nell'aggiungere un altro overlay (21 bit per tenere conto dello spostamento sinistro) al formato:
overlays:
signed j_imm[21] = imm20[19, 7..0, 8, 18..9], 0b0;
Come puoi vedere, la sintassi per gli overlay supporta la specifica di più intervalli in un in un formato breve. Inoltre, se non viene utilizzato alcun nome di campo, il bit numeri fanno riferimento alla parola di istruzione stessa, quindi quanto sopra potrebbe essere scritto come:
signed j_imm[21] = [31, 19..12, 20, 30..21], 0b0;
Infine, il jump-and-link (registro) utilizza il formato I-type usato in precedenza.
Formato immediato I-Type:
31/20 | 19/15 | 14..12 | 11..7 | 6,0 |
---|---|---|---|---|
12 | 5 | 3 | 5 | 7 |
imm12 | rs1 | func3 | ° | opcode, codice operativo |
Questa volta, non è necessario apportare modifiche al formato.
Le istruzioni per i rami che dobbiamo aggiungere sono:
beq
: ramo se uguale.bge
- Ramo se maggiore o uguale a.bgeu
- Ramo se maggiore o uguale a senza firma.blt
- Ramo se inferiore a.bltu
- Ramo se è inferiore a quello non firmato.bne
: ramo se diverso.
Sono codificati nel seguente modo:
31,25 | 24/20 | 19/15 | 14..12 | 11..7 | 6,0 | nome opcode |
---|---|---|---|---|---|---|
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 | opcode, codice operativo |
L'istruzione jal
è codificata come segue:
31.12 | 11..7 | 6,0 | nome opcode |
---|---|---|---|
imm20 | ° | 110 x 1111 | Jal |
opcode, codice operativo |
L'istruzione jalr
è codificata come segue:
31/20 | 19/15 | 14..12 | 11..7 | 6,0 | opcode_name |
---|---|---|---|---|---|
imm12 | rs1 | 000 | ° | 110 0111 | Jalr |
func3 | opcode, codice operativo |
Apporta le modifiche e crea. Controlla l'output generato. Soltanto Come in precedenza, puoi verificare il tuo lavoro riscv32i.bin_fmt.
Aggiungi istruzioni per il negozio
Le istruzioni dello store utilizzano la codifica S-Type, che è identica alla codifica B-Type
utilizzata dalle istruzioni di ramo, ad eccezione della composizione del
nell'immediato. Scegliamo di aggiungere il formato SType
per rimanere in linea con il RiscV
documentazione.
31,25 | 24/20 | 19/15 | 14..12 | 11..7 | 6,0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
imm7 | rs2 | rs1 | func3 | imm5 | opcode, codice operativo |
Nel caso del formato SType
, per fortuna l'immediato è una risposta
concatenazione in avanti dei due campi immediati, quindi la specifica dell'overlay
è semplicemente:
overlays:
signed s_imm[12] = imm7, imm5;
Tieni presente che non sono necessari specificatori di intervallo di bit quando concatenano interi campi.
Le istruzioni dello store sono codificate come segue:
31,25 | 24/20 | 19/15 | 14..12 | 11..7 | 6,0 | nome opcode |
---|---|---|---|---|---|---|
imm7 | rs2 | rs1 | 000 | imm5 | 010 0011 | sb |
imm7 | rs2 | rs1 | 001 | imm5 | 010 0011 | s |
imm7 | rs2 | rs1 | 010 | imm5 | 010 0011 | sw |
func3 | opcode, codice operativo |
Apporta le modifiche e crea. Controlla l'output generato. Soltanto Come in precedenza, puoi verificare il tuo lavoro riscv32i.bin_fmt.
Aggiungi istruzioni per il caricamento
Le istruzioni di caricamento utilizzano il formato I-Type. Non è necessario apportare modifiche.
Le codifiche sono:
31/20 | 19/15 | 14..12 | 11..7 | 6,0 | opcode_name |
---|---|---|---|---|---|
imm12 | rs1 | 000 | ° | 000 0011 | lb |
imm12 | rs1 | 100 | ° | 000 0011 | lbu |
imm12 | rs1 | 001 | ° | 000 0011 | sx |
imm12 | rs1 | 101 | ° | 000 0011 | lhu |
imm12 | rs1 | 010 | ° | 000 0011 | lw |
func3 | opcode, codice operativo |
Apporta le modifiche e crea. Controlla l'output generato. Soltanto Come in precedenza, puoi verificare il tuo lavoro riscv32i.bin_fmt.
Il tutorial termina qui, ci auguriamo che ti sia stato utile.