Anleitung zum Decoder für binäre Anweisungen

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 Namespace i_type platziert. Jede Extrahiererfunktion wird als inline deklariert und verwendet die schmalste uint_t -Typ, der die Breite des Formats enthält und den schmalsten int_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 schmalsten uint_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


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.