Mit dieser Anleitung werden folgende Ziele erreicht:
- Informationen zur Struktur und Syntax der Beschreibungsdatei im Binärformat.
- Hier erfahren Sie, wie die Beschreibung des Binärformats mit der ISA-Beschreibung übereinstimmt.
- Schreiben Sie die binären Beschreibungen für den Teilsatz der RiscV RV32I-Anweisungen.
Übersicht
Codierung von RiscV-Binäranweisungen
Die Codierung von Binärbefehlen ist die Standardmethode zum Codieren von Anweisungen für auf einem Mikroprozessor ausgeführt. Sie werden normalerweise in einer ausführbaren Datei gespeichert, normalerweise im ELF-Format. Anweisungen können entweder mit fester Breite oder variabel sein Breite.
Üblicherweise werden in der Anleitung nur wenige Codierungsformate verwendet, wobei jedes Format die an die Art der codierten Anweisungen angepasst sind. Beispielsweise „register-register“, Anweisungen ein Format verwenden, das die Anzahl der verfügbaren Opcodes maximiert, bei sofortiger Registrierung eine andere, bei der die Anzahl der verfügbaren Opcodes zum Erhöhen der Größe des codierten unmittelbaren Werts. Branch- und Jump-Anweisungen verwenden fast immer Formate, die die Größe von um Zweige mit größeren Offsets zu unterstützen.
Die Anweisungsformate, die von den Anweisungen verwendet werden, die wir in unserem RiscV decodieren möchten Simulatoren festlegen:
R-Type-Format, das für Anweisungen zur Registrierung/Registrierung verwendet wird:
31–25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | rs2 | rs1 | func3 | 3. | Opcode |
I-Type-Format, das für Anweisungen mit sofortiger Registrierung, Ladeanweisungen und
jalr
-Anweisung, 12 Bit, sofort.
31..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|
12 | 5 | 3 | 5 | 7 |
imm12 | rs1 | func3 | 3. | Opcode |
Spezialisiertes I-Type-Format, das für die Schicht mit sofortigen Anweisungen verwendet wird, 5 Bit sofort:
31–25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | uimm5 | rs1 | func3 | 3. | Opcode |
U-Typ-Format, das für lange Direktbefehle verwendet wird (lui
, auipc
), 20 Bit
sofort:
31..12 | 11..7 | 6..0 |
---|---|---|
20 | 5 | 7 |
uimm20 | 3. | Opcode |
B-Typ-Format, das für bedingte Zweige verwendet wird, sofort 12-Bit.
31 | 30–25 | 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 |
J-Type-Format, das für die Anweisung jal
verwendet wird, sofort 20 Bit.
31 | 30..21 | 20 | 19.12 | 11..7 | 6..0 |
---|---|---|---|---|---|
1 | 10 | 1 | 8 | 5 | 7 |
imm | imm | imm | imm | 3. | Opcode |
S-Type-Format, das für Anweisungen zum Laden verwendet wird, sofort 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 |
Wie Sie an diesen Formaten erkennen können, sind alle Anweisungen 32 Bit lang. Die niedrigen 7 Bits in jedem Format sind das Opcode-Feld. Beachten Sie außerdem, dass Mehrere Formate haben die gleiche Größe. Ihre Teile stammen aus in verschiedenen Teilen der Anleitung. Wir werden sehen, dass der Binärcode Spezifikationsformat ausdrücken.
Beschreibung der Binärcodierung
Die Binärcodierung der Anweisung wird im Binärformat ausgedrückt.
Beschreibungsdatei (.bin_fmt
). Sie beschreibt die binäre Codierung des
damit ein Binärformat-Anweisungsdecoder durchgeführt werden kann.
generiert. Der generierte Decoder bestimmt den Opcode und extrahiert den Wert von
Operand und Unmittelbar, um die vom ISA erforderlichen Informationen bereitzustellen
Codierungsunabhängigen Decoder an, der im vorherigen Tutorial beschrieben wurde.
In dieser Anleitung schreiben wir eine binäre Codierungsdatei zur Beschreibung einer Teilmenge. RiscV32I-Anweisungen zur Simulation der in einer kleines „Hello World“ . Weitere Informationen zum RiscV ISA findest du unter Risc-V-Spezifikationen{.external}.
Öffnen Sie zunächst die Datei:
riscv_bin_decoder/riscv32i.bin_fmt
Der Inhalt der Datei ist in mehrere Abschnitte unterteilt.
Die erste ist die decoder
-Definition.
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;
};
Unsere Decoderdefinition enthält den Namen unseres Decoders RiscV32I
sowie
vier weitere Informationen. Die erste ist namespace
. Damit wird definiert,
Den Namespace, in dem der generierte Code platziert wird. Zweitens: Der Parameter
opcode_enum
, der angibt, wie der generierte Opcode-Enumerationstyp
vom ISA-Decodierer muss im generierten Code referenziert werden. Drittens:
includes {}
gibt die Dateien an, die für den generierten Code erforderlich sind.
diesem Decoder.
In unserem Fall ist dies die Datei, die vom ISA-Decodierer aus dem
zum vorherigen Tutorial.
Zusätzliche Include-Dateien können in einem includes {}
mit globalem Geltungsbereich angegeben werden
Definition. Dies ist nützlich, wenn mehrere Decoder definiert sind und diese alle
um dieselben Dateien aufzunehmen. Der vierte Punkt ist eine Liste
der Anweisungsnamen,
Gruppen, aus denen die Anweisungen bestehen, für die der Decoder generiert wird. In unserem
falls es nur eine gibt: RiscVInst32
.
Als Nächstes gibt es drei Formatdefinitionen. Sie stellen unterschiedliche Anweisungen dar, Formate für ein 32-Bit-Anweisungswort, das von den bereits definierten Anweisungen verwendet wird in der Datei.
// 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];
};
Mit dem ersten wird ein 32-Bit-Befehlsformat namens Inst32Format
definiert, bei dem
zwei Felder: bits
(25 Bit breit) und opcode
(7 Bit breit). Jedes Feld ist
unsigned
, was bedeutet, dass der Wert bei der Extraktion auf null gesetzt wird.
und in einen C++-Ganzzahltyp
versetzt. Die Summe der Breiten der Bitfelder muss
der Breite des Formats entspricht. Das Tool generiert einen Fehler, wenn
Meinungsverschiedenheiten. Dieses Format leitet sich von keinem anderen Format ab, daher ist es
als Top-Level-Format betrachtet.
Die zweite definiert ein 32-Bit-Befehlsformat namens IType
, das
von Inst32Format
, sodass diese beiden Formate zusammenhängen. Das Format enthält 5
Felder: imm12
, rs1
, func3
, rd
und opcode
. Das Feld imm12
ist
signed
bedeutet, dass der Wert vorzeichenerweitert wird, wenn der Wert
extrahiert und in einen C++-Ganzzahltyp eingefügt. IType.opcode
hat beide
dasselbe vorzeichenbehaftete/nicht vorzeichenbehaftete Attribut und bezieht sich auf dieselben Anweisungswort-Bits.
als Inst32Format.opcode
.
Das dritte Format ist ein benutzerdefiniertes Format, das nur vom fence
verwendet wird.
Anweisung, eine Anweisung, die bereits angegeben wurde,
die Sie sich in dieser Anleitung ansehen müssen.
Wichtig: Feldnamen in verschiedenen verwandten Formaten wiederverwenden, sofern sie dieselben Bits und dasselbe Attribut mit Vorzeichen und ohne Vorzeichen haben.
Nach den Formatdefinitionen in riscv32i.bin_fmt
kommt eine Anweisungsgruppe
Definition. Alle Anweisungen in einer Anweisungsgruppe müssen dieselben
Bitlänge und verwenden Sie ein Format, das (eventuell indirekt)
Anleitungsformat der obersten Ebene. Wenn ein ISA Anweisungen mit unterschiedlichen
Längen verwendet wird, wird für jede Länge eine andere Anweisungsgruppe verwendet. Außerdem
Die Ziel-ISA-Decodierung hängt von einem Ausführungsmodus ab, z. B. Arm oder Thumb
wird für jeden Modus eine eigene Anleitungsgruppe benötigt. Die
Der bin_fmt
-Parser generiert einen binären Decoder für jede Anweisungsgruppe.
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;
};
Die Anweisungsgruppe definiert den Namen RiscV32I
, die Breite [32]
, den Namen
Opcode-Enumerationstyp zur Verwendung von "OpcodeEnum"
und eine Basisanweisung
Format. Der Opcode-Enumerationstyp muss mit dem übereinstimmen, der vom
formatunabhängiger Anweisungsdecoder, der im ISA-Tutorial behandelt wird
Decoder
Jede Beschreibung für die Anweisungscodierung besteht aus drei Teilen:
- Der Opcode-Name, der mit dem in der Anweisung verwendeten Namen übereinstimmen muss eine Decoder-Beschreibung, damit die beiden zusammenwirken.
- Das Anweisungsformat, das für den Opcode verwendet werden soll. Dies ist das Format, damit im letzten Teil Verweise auf Bitfelder erfüllt werden.
- Eine durch Kommas getrennte Liste von Bitfeldeinschränkungen,
==
,!=
,<
,<=
,>
und>=
, dass alles wahr sein muss, damit der Opcode die Übereinstimmung Anweisung.
Der Parser .bin_fmt
verwendet all diese Informationen, um einen Decoder zu erstellen, der:
- Stellt Extraktionsfunktionen (vorzeichenbehaftet/unsigniert) für jedes Bit bereit
in jedem Format. Die Extrahiererfunktionen werden in Namespaces platziert
nach der Snake-Case-Version des Formatnamens. Zum Beispiel
Extrahiererfunktionen für das Format
IType
werden im Namespacei_type
platziert. Jede Extrahiererfunktion wird alsinline
deklariert und verwendet die schmalsteuint_t
-Typ, der die Breite des Formats enthält und den schmalstenint_t
-Wert zurückgibt (für signiert),uint_t
(für nicht signiert), Typ, der das extrahierte Feld enthält Breite. Beispiel:
inline uint8_t ExtractOpcode(uint32_t value) {
return value & 0x7f;
}
- Eine Decodierungsfunktion für jede Anweisungsgruppe. Sie gibt einen Wert des Typs
OpcodeEnum
und verwendet den schmalstenuint_t
-Typ mit der Breite von Format der Anweisungsgruppe.
Ersten Build ausführen
Ändern Sie das Verzeichnis in riscv_bin_decoder
und erstellen Sie das Projekt mithilfe des
folgenden Befehl:
$ cd riscv_bin_decoder
$ bazel build :all
Wechseln Sie nun zurück zum Repository-Stammverzeichnis. Sehen wir uns
an den Quellen, die generiert wurden. Ändern Sie dazu das Verzeichnis in
bazel-out/k8-fastbuild/bin/riscv_bin_decoder
(vorausgesetzt, Sie verwenden ein x86-
host – bei anderen Hosts ist der k8-fastbuild ein weiterer String).
$ cd ..
$ cd bazel-out/k8-fastbuild/bin/riscv_bin_decoder
riscv32i_bin_decoder.h
riscv32i_bin_decoder.cc
Die generierte Headerdatei (.h)
riscv32i_bin_decoder.h
öffnen. Der erste Teil der Datei enthält Standard-
Boilerplate Guards, Einbinden von Dateien, Namespace-Deklarationen. Im Anschluss
Es gibt eine vorlagenbasierte Hilfsfunktion im Namespace internal
. Diese Funktion
wird verwendet, um Bitfelder aus Formaten zu extrahieren, die zu lang für eine 64-Bit-C++-
Integer
#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
Im Anschluss an den ersten Abschnitt finden Sie eine Reihe von drei Namespaces, einen für jeden
der format
-Deklarationen in der Datei riscv32i.bin_fmt
:
namespace fence {
...
} // namespace fence
namespace i_type {
...
} // namespace i_type
namespace inst32_format {
...
} // namespace inst32_format
Die Bitfeld-Extraktionsfunktion inline
in jedem dieser Namespaces
für jedes Bitfeld in diesem Format definiert ist. Außerdem wird das Basisformat
Dupliziert Extraktionsfunktionen aus den nachfolgenden Formaten, für die
1) die Feldnamen nur in einem einzelnen Feldnamen vorkommen, oder 2) für die der
Feldnamen beziehen sich auf dasselbe Typfeld (vorzeichenbehaftet/ohne Vorzeichen und Bitpositionen).
in welchem Format sie vorkommen. Dadurch werden Bitfelder aktiviert, die dieselben
Bits, die mithilfe von Funktionen im Format-Namespace der obersten Ebene extrahiert werden sollen.
Die Funktionen im Namespace i_type
sind unten dargestellt:
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
Die Funktionsdeklaration der Decoderfunktion für die Anweisung
Gruppe RiscVInst32
ist deklariert. Der Wert von 32 Bit ohne Vorzeichen
das Anweisungswort und gibt das OpcodeEnum
-Enumerationsklassenmitglied zurück
oder OpcodeEnum::kNone
, wenn es keine Übereinstimmung gibt.
OpcodeEnum DecodeRiscVInst32(uint32_t inst_word);
Die generierte Quelldatei (.cc)
Öffne jetzt riscv32i_bin_decoder.cc
. Der erste Teil der Datei enthält
Die #include
- und Namespace-Deklarationen, gefolgt von der Funktion „decoder“
Deklarationen:
#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);
Der DecodeRiscVInst32None
wird für leere Decodierungsaktionen verwendet, d.h. solche,
die OpcodeEnum::kNone
zurückgeben. Die anderen drei Funktionen bilden die
generierter Decoder. Der Decoder insgesamt arbeitet hierarchisch. Eine Reihe
von Bits im Anweisungswort wird berechnet, um zwischen
oder Gruppen von Anweisungen auf der obersten Ebene. Die einzelnen Elemente
zusammenhängend sein. Die Anzahl der Bits bestimmt die Größe einer Suchtabelle,
wird mit Decoderfunktionen
der zweiten Ebene gefüllt. Dies wird in den nächsten
der Datei:
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,
...
};
Schließlich werden die Decoderfunktionen definiert:
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 diesem Fall, in dem nur vier Anweisungen definiert sind, ist nur eine und eine sehr dünnbesetzte Suchtabelle. Wie die Anweisungen ändert sich die Struktur des Decoders und die Anzahl der Ebenen im Die Hierarchie der Decoder-Tabelle kann sich erhöhen.
Anleitung zum Register-Register-ALU hinzufügen
Jetzt ist es an der Zeit, der Datei riscv32i.bin_fmt
einige neue Anweisungen hinzuzufügen. Die
erste Gruppe von ALU-Anweisungen, wie z. B.
add
, and
usw. Unter RiscV32 verwenden diese alle die Binäranweisung R-Typ
Format:
31–25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | rs2 | rs1 | func3 | 3. | Opcode |
Zunächst müssen wir das Format hinzufügen. Dann öffnen Sie
riscv32i.bin_fmt
in Ihrem bevorzugten Editor. Direkt nach der Inst32Format
fügen Sie ein Format namens RType
hinzu, das aus Inst32Format
abgeleitet ist. Alle Bitfelder
in RType
sind unsigned
. Namen, Bitbreite und Reihenfolge (von links nach rechts) verwenden
aus der Tabelle oben, um das Format zu definieren. Wenn Sie einen Tipp brauchen oder
Komplettlösung zu finden,
klicken Sie hier.
Als Nächstes müssen wir die Anweisungen hinzufügen. Die Anleitung lautet:
add
: Ganzzahl hinzufügen.and
– das bitweise und.or
– das bitweise oder.sll
: Logische Verschiebung nach links.sltu
: Kleiner als, nicht signiert.sub
: Ganzzahlsubtrahieren.xor
– bitweises XOR.
Ihre Codierungen sind:
31–25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 | Opcode-Name |
---|---|---|---|---|---|---|
000 0000 | rs2 | rs1 | 000 | 3. | 011 0011 | Hinzufügen |
000 0000 | rs2 | rs1 | 111 | 3. | 011 0011 | und |
000 0000 | rs2 | rs1 | 110 | 3. | 011 0011 | oder |
000 0000 | rs2 | rs1 | 001 | 3. | 011 0011 | Sll |
000 0000 | rs2 | rs1 | 011 | 3. | 011 0011 | Sltu |
010 0000 | rs2 | rs1 | 000 | 3. | 011 0011 | sub |
000 0000 | rs2 | rs1 | 100 | 3. | 011 0011 | XOR |
func7 | func3 | Opcode |
Fügen Sie diese Anweisungsdefinitionen vor den anderen Anweisungen in der
RiscVInst32
Anleitungsgruppe. Binärstrings werden mit einem
Präfix von 0b
(ähnlich 0x
für Hexadezimalzahlen). Damit es einfacher ist,
lange Zeichenfolgen von Binärziffern lesen, können Sie auch '
mit einfachen Anführungszeichen als
ein Zifferntrennzeichen, wo es passend erscheint.
Für jede dieser Anweisungsdefinitionen gibt es drei Einschränkungen:
func7
, func3
und opcode
. Für alle außer sub
gilt die Einschränkung func7
sein:
func7 == 0b000'0000
Die Einschränkung func3
variiert bei den meisten Anleitungen. Für add
und
sub
:
func3 == 0b000
Die Einschränkung opcode
ist für jede dieser Anweisungen gleich:
opcode == 0b011'0011
Vergessen Sie nicht, jede Zeile mit einem Semikolon ;
zu beenden.
Die fertige Lösung hier.
Erstellen Sie nun Ihr Projekt wie zuvor und öffnen Sie das generierte
riscv32i_bin_decoder.cc
-Datei. Sie werden sehen, dass zusätzliche Decoderfunktionen
generiert wurden, um die neuen Anweisungen zu verarbeiten. Größtenteils sind sie
ähnlich wie die zuvor generierten. Sie sollten sich
DecodeRiscVInst32_0_c
, das für die add
/sub
-Decodierung verwendet wird:
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 dieser Funktion wird eine statische Decodierungstabelle generiert und ein Suchwert aus dem Anweisungswort extrahiert, um den entsprechenden Index auszuwählen. Dadurch wird ein in der Anweisungsdecoderhierarchie an. Da der Opcode jedoch die direkt ohne weitere Vergleiche in einer Tabelle nachgeschlagen wurden, und kein weiterer Funktionsaufruf erforderlich ist.
ALU-Anweisungen mit Direktbefehlen hinzufügen
Die nächsten Anweisungen sind ALU-Anweisungen, bei denen ein direkter Wert statt in einem der Register. Es gibt drei Gruppen davon, Anweisungen (basierend auf dem unmittelbaren Feld): Direktanweisungen für I-Typen mit einem 12-Bit-Sofortcode signiert, kann die spezielle I-Type-Anweisung für Verschiebungen und den U-Typ „sofort“ mit einem vorzeichenlosen 20-Bit-Direktwert. Folgende Formate sind verfügbar:
Das I-Type-Format unmittelbar:
31..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|
12 | 5 | 3 | 5 | 7 |
imm12 | rs1 | func3 | 3. | Opcode |
Das spezialisierte I-Type-Direktformat:
31–25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | uimm5 | rs1 | func3 | 3. | Opcode |
Direktes U-Typ-Format:
31..12 | 11..7 | 6..0 |
---|---|---|
20 | 5 | 7 |
uimm20 | 3. | Opcode |
Das I-Typ-Format ist in riscv32i.bin_fmt
bereits vorhanden, daher ist es nicht erforderlich,
fügen Sie dieses Format hinzu.
Wenn wir das spezialisierte I-Type-Format mit dem R-Type-Format vergleichen, das wir in
in der vorherigen Übung sehen wir, dass der einzige Unterschied darin besteht, dass die rs2
-Felder
wird in uimm5
umbenannt. Anstatt ein ganz neues Format hinzuzufügen, können wir
R-Type-Format. Wir können kein weiteres Feld hinzufügen, da dies die Breite
Format, aber wir können ein Overlay hinzufügen. Ein Overlay ist ein Alias für eine Reihe von
Bits im Format und können verwendet werden, um mehrere Teilsequenzen des
in eine separate benannte Entität umwandeln. Der Nebeneffekt ist, dass der generierte Code
jetzt auch eine Extraktionsfunktion für das Overlay
die für die Felder. In diesem Fall, wenn sowohl rs2
als auch uimm5
nicht signiert sind
macht keinen Unterschied, außer deutlich zu machen, dass das Feld
sofort. Um ein Overlay mit dem Namen uimm5
zum R-Type-Format hinzuzufügen, fügen Sie den
nach dem letzten Feld hinzu:
overlays:
unsigned uimm5[5] = rs2;
Das einzige neue Format, das wir hinzufügen müssen, ist das U-Typ-Format. Bevor wir die Funktion
Format ist, betrachten wir die beiden Anweisungen, in denen dieses Format verwendet wird: auipc
und
lui
. Beide verschieben den 20-Bit-Sofortwert um 12 nach links, bevor er verwendet wird
fügen Sie entweder den PC hinzu (auipc
) oder schreiben Sie ihn direkt in ein Register.
(lui
) Mithilfe eines Overlays können wir eine
verschobene Version der unmittelbaren,
die Verschiebung eines Teils der Berechnungen von der Anweisungsausführung zur Anweisung
decodieren. Fügen Sie zuerst das Format gemäß den in der Tabelle angegebenen Feldern hinzu
oben. Dann können wir das folgende Overlay hinzufügen:
overlays:
unsigned uimm32[32] = uimm20, 0b0000'0000'0000;
Mit der Overlay-Syntax können wir nicht nur Felder, sondern auch Literale als gut. In diesem Fall verketten wir es mit 12 Nullen und verschieben es praktisch nach links. bis 12.
Folgende I-Type-Anweisungen müssen hinzugefügt werden:
addi
: Sofort hinzufügen.andi
: Bitweise und mit sofortiger Wirkung.ori
: Bitweise oder mit sofortiger Wirkung.xori
: Bitweises xor mit sofortiger Wirkung.
Ihre Codierungen sind:
31..20 | 19..15 | 14..12 | 11..7 | 6..0 | opcode_name |
---|---|---|---|---|---|
imm12 | rs1 | 000 | 3. | 001 0011 | Addi |
imm12 | rs1 | 111 | 3. | 001 0011 | Andi |
imm12 | rs1 | 110 | 3. | 001 0011 | Ori |
imm12 | rs1 | 100 | 3. | 001 0011 | Xori |
func3 | Opcode |
Folgende R-Type-Anweisungen (spezialisierter I-Type) müssen hinzugefügt werden:
slli
: Sofortige Verschiebung nach links.srai
: Arithmetik wird sofort nach rechts verschoben.srli
: Wechselt unmittelbar nach rechts.
Ihre Codierungen sind:
31–25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 | Opcode-Name |
---|---|---|---|---|---|---|
000 0000 | uimm5 | rs1 | 001 | 3. | 001 0011 | SLI |
010 0000 | uimm5 | rs1 | 101 | 3. | 001 0011 | Sari |
000 0000 | uimm5 | rs1 | 101 | 3. | 001 0011 | Srli |
func7 | func3 | Opcode |
Folgende U-Type-Anweisungen müssen hinzugefügt werden:
auipc
: Fügen Sie dem PC sofort obere Elemente hinzu.lui
: Oberer Wert wird sofort geladen.
Ihre Codierungen sind:
31..12 | 11..7 | 6..0 | Opcode-Name |
---|---|---|---|
uimm20 | 3. | 001 0111 | auipc |
uimm20 | 3. | 011 0111 | lui |
Opcode |
Nehmen Sie die Änderungen vor und erstellen Sie dann den Build. Prüfen Sie die generierte Ausgabe. Nur können Sie Ihre Arbeit wie zuvor riscv32i.bin_fmt
Branch- und Jump-and-Link-Anweisungen hinzufügen
Die nächsten Anweisungen, die definiert werden müssen, sind der bedingte Zweig die Jump-and-Link-Anweisungen und das Jump-and-Link-Register Anleitung.
Die bedingten Zweige, die wir hinzufügen, verwenden die B-Typ-Codierung.
31–25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
imm7 | rs2 | rs1 | func3 | imm5 | Opcode |
Obwohl die B-Typ-Codierung im Layout identisch ist
und die R-Typ-Codierung,
einen neuen Formattyp auswählen, der mit der RiscV-Dokumentation übereinstimmt.
Sie können aber auch einfach ein Overlay hinzufügen, um den entsprechenden Zweig zu finden.
Verschiebung sofort mithilfe der Felder func7
und rd
des R-Typs
Codierung.
Das Format BType
mit den oben angegebenen Feldern ist erforderlich, jedoch nicht
ausreichend. Wie Sie sehen, ist der unmittelbare Teil auf zwei Anweisungsfelder aufgeteilt.
Außerdem wird dies in den Zweiganweisungen nicht als einfache Verkettung von
in den beiden Feldern. Stattdessen wird jedes Feld weiter partitioniert und diese Partitionen
sind in einer anderen
Reihenfolge verkettet. Schließlich wird dieser Wert um
ein, um einen 16-Bit-Offset zu erhalten.
Die Reihenfolge der Bits im Anweisungswort, die zur Bildung der Instant-Funktion verwendet wird, ist: 31,
7, 30..25, 11..8. Dies entspricht den folgenden Unterfeldverweisen, wobei
Der Index oder Bereich gibt die Bits im Feld an, von rechts nach links nummeriert, d.h.
imm7[6]
bezieht sich auf die msb von imm7
und imm5[0]
auf den lsb-Wert von
imm5
.
imm7[6], imm5[0], imm7[5..0], imm5[4..1]
Diese Bit-Manipulation ist Teil der Zweiganweisungen selbst.
Nachteile hat. Zunächst verknüpft sie die Implementierung der semantischen Funktion mit der
Details in der Binäranweisungsdarstellung. Zweitens wird die Laufzeit
koordiniert. Die Lösung besteht darin, dem BType
-Format ein Overlay hinzuzufügen, einschließlich eines
nachgestellte '0' um die Linksverschiebung zu berücksichtigen.
overlays:
signed b_imm[13] = imm7[6], imm5[0], imm7[5..0], imm5[4..1], 0b0;
Das Overlay ist signiert, es wird also automatisch unterschrieben. wenn es aus dem Anweisungswort extrahiert wird.
Die Jump-and-Link-Anweisung (immediate) verwendet die J-Type-Codierung:
31..12 | 11..7 | 6..0 |
---|---|---|
20 | 5 | 7 |
imm20 | 3. | Opcode |
Auch dieses Format lässt sich leicht hinzufügen, aber auch hier ist das unmittelbare Format, das vom ist die Anleitung nicht so einfach, wie es auf den ersten Blick scheint. Die Bitsequenzen, die für 31, 19..12, 20, 30..21 und die endgültige sofortige sofortige für die Halbwortausrichtung um eins nach links verschoben. Die Lösung ist, eine weitere Overlay (21 Bits für die Verschiebung nach links) in das Format ein:
overlays:
signed j_imm[21] = imm20[19, 7..0, 8, 18..9], 0b0;
Wie Sie sehen, unterstützt die Syntax für Overlays die Angabe mehrerer Bereiche in einem . Wenn kein Feldname verwendet wird, Zahlen sich auf das Anweisungswort selbst. geschrieben als:
signed j_imm[21] = [31, 19..12, 20, 30..21], 0b0;
Schließlich verwendet das Jump-and-Link-(Register)-Format das I-Typ-Format, wie es verwendet wird. .
Das I-Type-Format unmittelbar:
31..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|
12 | 5 | 3 | 5 | 7 |
imm12 | rs1 | func3 | 3. | Opcode |
Diesmal muss das Format nicht geändert werden.
Die Branch-Anweisungen, die wir hinzufügen müssen, lauten:
beq
– Zweig, wenn gleich.bge
: Zweig, wenn größer oder gleich.bgeu
– Zweig, wenn größer oder gleich ohne Vorzeichen ist.blt
– Zweig, wenn kleiner als.bltu
– Zweig, wenn kleiner als nicht signiert.bne
– Zweig, wenn nicht gleich.
Sie sind wie folgt codiert:
31–25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 | Opcode-Name |
---|---|---|---|---|---|---|
imm7 | rs2 | rs1 | 000 | imm5 | 110 0011 | Beq |
imm7 | rs2 | rs1 | 101 | imm5 | 110 0011 | Bég |
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 |
Die Anweisung jal
ist so codiert:
31..12 | 11..7 | 6..0 | Opcode-Name |
---|---|---|---|
imm20 | 3. | 110 1111 | Jal |
Opcode |
Die Anweisung jalr
ist so codiert:
31..20 | 19..15 | 14..12 | 11..7 | 6..0 | opcode_name |
---|---|---|---|---|---|
imm12 | rs1 | 000 | 3. | 110 0111 | Jalr |
func3 | Opcode |
Nehmen Sie die Änderungen vor und erstellen Sie dann den Build. Prüfen Sie die generierte Ausgabe. Nur können Sie Ihre Arbeit wie zuvor riscv32i.bin_fmt
Anleitung zum Geschäft hinzufügen
In der Geschäftsanleitung wird die S-Type-Codierung verwendet, die mit dem B-Typ identisch ist.
Codierung, die von Zweiganweisungen verwendet wird, mit Ausnahme der Zusammensetzung der
sofort. Wir fügen das SType
-Format hinzu, um im Einklang mit den RiscV-Richtlinien zu bleiben
Dokumentation.
31–25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
imm7 | rs2 | rs1 | func3 | imm5 | Opcode |
Im Fall des SType
-Formats ist die unmittelbare zum Glück eine gerade
Vorwärtsverkettung der beiden unmittelbaren Felder, sodass die Overlay-Spezifikation
ist einfach:
overlays:
signed s_imm[12] = imm7, imm5;
Beachten Sie, dass beim Verketten ganzer Felder keine Bitbereichsspezifizierer erforderlich sind.
Die Anweisungen für den Shop sind wie folgt codiert:
31–25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 | Opcode-Name |
---|---|---|---|---|---|---|
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 | Opcode |
Nehmen Sie die Änderungen vor und erstellen Sie dann den Build. Prüfen Sie die generierte Ausgabe. Nur können Sie Ihre Arbeit wie zuvor riscv32i.bin_fmt
Ladeanweisungen hinzufügen
Die Ladeanweisungen haben das I-Type-Format. Dort müssen keine Änderungen vorgenommen werden.
Die Codierungen sind:
31..20 | 19..15 | 14..12 | 11..7 | 6..0 | opcode_name |
---|---|---|---|---|---|
imm12 | rs1 | 000 | 3. | 000 0011 | lb |
imm12 | rs1 | 100 | 3. | 000 0011 | LBU |
imm12 | rs1 | 001 | 3. | 000 0011 | LH |
imm12 | rs1 | 101 | 3. | 000 0011 | Lhu |
imm12 | rs1 | 010 | 3. | 000 0011 | lw |
func3 | Opcode |
Nehmen Sie die Änderungen vor und erstellen Sie dann den Build. Prüfen Sie die generierte Ausgabe. Nur können Sie Ihre Arbeit wie zuvor riscv32i.bin_fmt
Damit sind wir am Ende dieser Anleitung angelangt. Wir hoffen, dass sie Ihnen weitergeholfen hat.