Mit dieser Anleitung werden folgende Ziele erreicht:
- Erfahren Sie, wie Anweisungen im MPACT-Sim-Simulator dargestellt werden.
- Informationen zur Struktur und Syntax der ISA-Beschreibungsdatei
- ISA-Beschreibungen für die RiscV RV32I-Anweisungen schreiben
Übersicht
In MPACT-Sim werden die Zielanweisungen decodiert und in einer internen -Darstellung, um ihre Informationen verfügbarer zu machen, und die Semantik Ausführung beschleunigen. Diese Anweisungsinstanzen werden in einer Anweisung zwischengespeichert um die Anzahl der häufig ausgeführten Befehle zu reduzieren. ausgeführt haben.
Instruktionskurs
Bevor wir beginnen, sollten wir uns kurz ansehen,
in MPACT-Sim dargestellt. Die Klasse Instruction
ist definiert in
mpact-sim/mpact/sim/generic/instruction.h.
Die Instanz der Instruction-Klasse enthält alle Informationen, die für die Anweisung zu simulieren, wenn sie „ausgeführt“ wird, zum Beispiel:
- Anweisungsadresse, simulierte Anweisungsgröße, d. h. Größe in Text.
- Befehls-Opcode.
- Prädikat-Operandenschnittstellen-Zeiger (falls zutreffend).
- Vektor der Quell-Operand-Schnittstellenzeiger.
- Vektor der Zeiger der Ziel-Operandenschnittstelle.
- Callable für Semantische Funktion.
- Zeiger auf das Objekt des Architekturstatus.
- Zeiger auf Kontextobjekt.
- Zeiger auf untergeordnete und nächste Anweisungsinstanzen.
- Disassembly-String.
Diese Instanzen werden in der Regel in einem Anweisungscache (Instanz-) gespeichert und bei erneuter Ausführung der Anweisung wiederverwendet werden. Dadurch wird die Leistung verbessert. während der Laufzeit.
Mit Ausnahme des Zeigers auf das Kontextobjekt werden alle durch das Objekt Anweisungsdecoder, der aus der ISA-Beschreibung generiert wird. In diesem Fall Tutorials, müssen Sie keine Details zu diesen Elementen kennen, da wir Ihnen direkt verwenden können. Vielmehr ist ein grobes Verständnis ausreichend.
Die aufrufbare semantische Funktion ist das Funktions-/Methoden-/Funktionsobjekt in C++.
(einschließlich Lambdas) enthält, die die Semantik der Anweisung implementiert. Für
wird bei einer add
-Anweisung jeder Quelloperanden geladen und die beiden
Operanden und schreibt das Ergebnis in einen einzelnen Zieloperanden. Das Thema der
werden im Tutorial zu Semantikfunktionen ausführlich behandelt.
Instruktionsoperanden
Die Anweisungsklasse enthält Verweise auf drei Typen von Operanden-Schnittstellen: Prädikat, Quelle und Ziel. Diese Schnittstellen ermöglichen es semantischen Funktionen, unabhängig vom tatsächlichen Typ der zugrunde liegenden Anweisung geschrieben werden. Operand in das Feld. Zum Beispiel wird auf die Werte von Registern und sofortigen Zeilen zugegriffen. über dieselbe Benutzeroberfläche. Das bedeutet, dass Anweisungen, die die gleiche Operation, aber an verschiedenen Operanden (z. B. Registers vs. Immedates) mit derselben semantischen Funktion implementiert.
Die Prädikat-Operandenschnittstelle für die ISAs, die prädizierte Anweisungsausführung (für andere ISAs ist er null), wird verwendet, um zu bestimmen, ob ein basierend auf dem booleschen Wert des Prädikats ausgeführt werden soll.
// The predicte operand interface is intended primarily as the interface to
// read the value of instruction predicates. It is separated from source
// predicates to avoid mixing it in with the source operands needed for modeling
// the instruction semantics.
class PredicateOperandInterface {
public:
virtual bool Value() = 0;
// Return a string representation of the operand suitable for display in
// disassembly.
virtual std::string AsString() const = 0;
virtual ~PredicateOperandInterface() = default;
};
Die Schnittstelle „Source Operand“ ermöglicht der semantischen Anweisungsfunktion Folgendes: Werte aus den Anweisungsoperanden ohne Berücksichtigung des zugrunde liegenden Operanden Typ. Die Interface-Methoden unterstützen sowohl skalare als auch vektorwertige Operanden.
// The source operand interface provides an interface to access input values
// to instructions in a way that is agnostic about the underlying implementation
// of those values (eg., register, fifo, immediate, predicate, etc).
class SourceOperandInterface {
public:
// Methods for accessing the nth value element.
virtual bool AsBool(int index) = 0;
virtual int8_t AsInt8(int index) = 0;
virtual uint8_t AsUint8(int index) = 0;
virtual int16_t AsInt16(int index) = 0;
virtual uint16_t AsUint16(int) = 0;
virtual int32_t AsInt32(int index) = 0;
virtual uint32_t AsUint32(int index) = 0;
virtual int64_t AsInt64(int index) = 0;
virtual uint64_t AsUint64(int index) = 0;
// Return a pointer to the object instance that implements the state in
// question (or nullptr) if no such object "makes sense". This is used if
// the object requires additional manipulation - such as a fifo that needs
// to be pop'ed. If no such manipulation is required, nullptr should be
// returned.
virtual std::any GetObject() const = 0;
// Return the shape of the operand (the number of elements in each dimension).
// For instance {1} indicates a scalar quantity, whereas {128} indicates an
// 128 element vector quantity.
virtual std::vector<int> shape() const = 0;
// Return a string representation of the operand suitable for display in
// disassembly.
virtual std::string AsString() const = 0;
virtual ~SourceOperandInterface() = default;
};
Die Zieloperanden-Schnittstelle bietet Methoden für die Zuweisung und Verarbeitung
DataBuffer
-Instanzen (der interner Datentyp zum Speichern von Registerwerten). A
dem Zieloperanden auch eine Latenz zugeordnet, die sich aus der Zahl
der Zyklen, die auf die von der Anweisung zugewiesene Datenpufferinstanz gewartet werden sollen
semantische Funktion wird verwendet, um den Wert des Zielregisters zu aktualisieren. Für
kann die Latenz eines add
-Befehls 1 sein, während für eine mpy
-Anweisung
4. Dies wird ausführlicher in der
zu semantischen Funktionen.
// The destination operand interface is used by instruction semantic functions
// to get a writable DataBuffer associated with a piece of simulated state to
// which the new value can be written, and then used to update the value of
// the piece of state with a given latency.
class DestinationOperandInterface {
public:
virtual ~DestinationOperandInterface() = default;
// Allocates a data buffer with ownership, latency and delay line set up.
virtual DataBuffer *AllocateDataBuffer() = 0;
// Takes an existing data buffer, and initializes it for the destination
// as if AllocateDataBuffer had been called.
virtual void InitializeDataBuffer(DataBuffer *db) = 0;
// Allocates and initializes data buffer as if AllocateDataBuffer had been
// called, but also copies in the value from the current value of the
// destination.
virtual DataBuffer *CopyDataBuffer() = 0;
// Returns the latency associated with the destination operand.
virtual int latency() const = 0;
// Return a pointer to the object instance that implmements the state in
// question (or nullptr if no such object "makes sense").
virtual std::any GetObject() const = 0;
// Returns the order of the destination operand (size in each dimension).
virtual std::vector<int> shape() const = 0;
// Return a string representation of the operand suitable for display in
// disassembly.
virtual std::string AsString() const = 0;
};
ISA-Beschreibung
Der ISA (Instruction Set Architecture) eines Prozessors definiert das abstrakte Modell. bei dem Software mit der Hardware interagiert. Sie definiert die Menge der Anweisungen, die Datentypen, die Register und der andere Rechnerstatus, und deren Verhalten (Semantik) festgelegt sind. Für die Zwecke, von MPACT-Sim enthält das ISA nicht die eigentliche Codierung der Anweisungen. Dies wird separat behandelt.
Der Prozessor-ISA ist in einer Beschreibungsdatei definiert, in der die Anweisungssatz auf einer abstrakten, codierungsunabhängigen Ebene. Die Beschreibungsdatei listet die verfügbaren Anweisungen auf. Für jede Anweisung ist erforderlich, um seinen Namen, die Anzahl und die Namen seiner Operanden sowie seine Bindung an eine C++-Funktion bzw. ein Callable, das ihre Semantik implementiert. Außerdem kann man einen Formatierungsstring für das Zerlegen angeben Hardware-Ressourcennamen. Mit Ersteren erstellen Sie eine textbasierte Darstellung der Anleitung für die Fehlerbehebung, das Tracing oder die interaktive Verwendung Die kann verwendet werden, um eine höhere Zyklusgenauigkeit in der Simulation zu erzielen.
Die ISA-Beschreibungsdatei wird vom isa-Parser geparst, der Code für Darstellungsunabhängige Anweisungs-Decodierer. Dieser Decoder ist für das Ausfüllen der Felder der Anweisungsobjekte. Die spezifischen Werte, etwa Zielregisternummer, werden aus einer formatspezifischen Anweisung abgerufen. Decoder Einer dieser Decoder ist der Binärdecoder, der im Mittelpunkt des nächste Anleitung.
In diesem Tutorial erfahren Sie, wie Sie eine ISA-Beschreibungsdatei für einen einfachen, skalaren Architektur. Wir verwenden eine Teilmenge der RiscV RV32I-Anweisung, und zusammen mit den anderen Anleitungen einen Simulator erstellen, der eine „Hallo Welt“-Simulation . Weitere Informationen zum RiscV ISA findest du unter Risc-V-Spezifikationen.
Öffnen Sie zunächst die Datei:
riscv_isa_decoder/riscv32i.isa
Der Inhalt der Datei ist in mehrere Abschnitte unterteilt. Erstens: das ISA, Erklärung:
isa RiscV32I {
namespace mpact::sim::codelab;
slots { riscv32; }
}
Dadurch wird RiscV32I
als Name des ISA angegeben und der Codegenerator wird
Erstellen Sie eine Klasse namens RiscV32IEncodingBase
, die die Schnittstelle definiert, die
den generierten Decoder zum Abrufen von Opcode- und Operandeninformationen verwendet. Der Name
Diese Klasse wird generiert, indem der ISA-Name in die Pascal-Schreibweise konvertiert wird.
die Verkettung mit EncodingBase
. Die Deklaration slots { riscv32; }
gibt an, dass es nur einen einzelnen Anweisungsslot riscv32
in der RiscV32I gibt
im Gegensatz zu mehreren Slots in einer VLIW-Anweisung
Gültige Anweisungen sind diejenigen, die zur Ausführung in riscv32
definiert sind.
// First disasm fragment is 15 char wide and left justified.
disasm widths = {-15};
Damit wird angegeben, dass das erste Teil einer Demontage -Spezifikation (siehe weitere unten) mit einer Länge von 15 Zeichen linksbündig Wide Feld ein. Alle nachfolgenden Fragmente werden an dieses Feld angehängt, zusätzliche Abstände.
Darunter befinden sich drei Slot-Deklarationen: riscv32i
, zicsr
und riscv32
.
Basierend auf der obigen Definition für isa
dürfen nur Anweisungen für riscv32
definiert werden
der Slot ist dann Teil der RiscV32I
-ID. Wozu dienen die anderen beiden Slots?
Slots können verwendet werden, um Anweisungen in separate Gruppen aufzuteilen, die dann
zu einem einzelnen Slot am Ende zusammengefasst. Beachten Sie die Notation : riscv32i, zicsr
in der riscv32
-Slotdeklaration. Damit wird angegeben, dass die Anzeigenfläche riscv32
alle Anweisungen in den Slots zicsr
und riscv32i
definiert. RiscV-32-Bit-ISA
besteht aus einem Basis-ISA namens RV32I, für den eine Reihe optionaler Erweiterungen
hinzugefügt werden. Der Slot-Mechanismus ermöglicht es, die Anweisungen in diesen Erweiterungen
separat angegeben und am Ende nach Bedarf kombiniert, um
und das Gesamt-ISA. In diesem Fall wird die Anleitung im Feld 'I' Gruppe sind definiert
getrennt von denen im Zicsr, Gruppe. Es können weitere Gruppen definiert werden
für "M" (multiplizieren/dividieren), "F" (Gleitkommazahl mit einfacher Genauigkeit), „D“
(Gleitkommazahl mit doppelter Genauigkeit), "C" (kompakte 16-Bit-Anleitung) usw.
die für das gewünschte endgültige RiscV-ISA erforderlich sind.
// The RiscV 'I' instructions.
slot riscv32i {
...
}
// RiscV32 CSR manipulation instructions.
slot zicsr {
...
}
// The final instruction set combines riscv32i and zicsr.
slot riscv32 : riscv32i, zicsr {
...
}
Die Slotdefinitionen für zicsr
und riscv32
müssen nicht geändert werden. Allerdings
In dieser Anleitung werden die erforderlichen Definitionen zum riscv32i
hinzugefügt.
Slot. Sehen wir uns an, was derzeit in dieser Anzeigenfläche definiert ist:
// The RiscV 'I' instructions.
slot riscv32i {
// Include file that contains the declarations of the semantic functions for
// the 'I' instructions.
includes {
#include "learning/brain/research/mpact/sim/codelab/riscv_semantic_functions/solution/rv32i_instructions.h"
}
// These are all 32 bit instructions, so set default size to 4.
default size = 4;
// Model these with 0 latency to avoid buffering the result. Since RiscV
// instructions have sequential semantics this is fine.
default latency = 0;
// The opcodes.
opcodes {
fence{: imm12 : },
semfunc: "&RV32IFence"c
disasm: "fence";
ebreak{},
semfunc: "&RV32IEbreak",
disasm: "ebreak";
}
}
Zunächst gibt es einen includes {}
-Abschnitt, in dem die Headerdateien aufgeführt sind,
die direkt in den generierten Code aufgenommen werden sollen, wenn auf diese Anzeigenfläche verwiesen wird, oder
nicht direkt im endgültigen ISA liegt. Einschlussdateien können auch in einem globalen
bereichsbezogenen includes {}
-Abschnitt. In diesem Fall sind sie immer enthalten. Dies kann
praktisch, wenn sonst die gleiche Einschlussdatei zu jeder Anzeigenfläche hinzugefügt werden müsste.
Definition.
Die Deklarationen default size
und default latency
definieren dies, es sei denn,
angegeben ist, beträgt die Größe einer Anweisung 4, und die Latenz
Der Schreibvorgang des Zieloperanden beträgt 0 Zyklen. Die Größe der Anweisung
ist die Größe des Programmzählerinkrements zur Berechnung des
Adresse der nächsten sequenziellen Anweisung, die in der simulierten
Prozessor. Dieser Wert kann der Größe in Byte des Objekts entsprechen.
Anweisungsdarstellung in der ausführbaren Eingabedatei.
Im Mittelpunkt der Slot-Definition steht der Opcode-Abschnitt. Wie Sie sehen, sind nur zwei
Die Opcodes (Anleitung) fence
und ebreak
wurden bisher in
riscv32i
Um den Opcode fence
zu definieren, geben Sie den Namen (fence
) und
die Operandenspezifikation ({: imm12 : }
), gefolgt von der optionalen Demontage
Format ("fence"
) und das Callable, das als semantische
-Funktion ("&RV32IFence"
) hinzu.
Die Anweisungsoperanden werden als Dreifach angegeben, wobei jede Komponente
durch ein Semikolon getrennt, Prädikat ':' Quelloperandenliste ':'
Liste der Zieloperatoren. Die Quell- und Zieloperandenlisten sind durch Komma getrennt
getrennten Listen mit Operandennamen. Wie Sie sehen, werden die Anweisungsoperanden für
Die fence
-Anweisung enthält, keine Prädikatoperanden, nur eine einzige Quelle
Operandenname imm12
und keine Zieloperanden. Die RiscV RV32I-Teilmenge
unterstützt keine vorhergesagte Ausführung, sodass der Prädikatoperanden immer leer ist.
in dieser Anleitung.
Die semantische Funktion wird als Zeichenfolge angegeben, die zur Angabe der C++-
oder Callable zum Aufrufen der semantischen Funktion. Die Unterschrift des
semantische Funktion/aufrufbare Funktion ist void(Instruction *)
.
Die Disassembly-Spezifikation besteht aus einer durch Kommas getrennten Liste von Strings.
Normalerweise werden nur zwei Zeichenfolgen verwendet: eine für den Opcode und eine für den
Operanden. Bei der Formatierung (mit dem AsString()
-Aufruf in der Anleitung) wird jeder
String in einem Feld entsprechend dem disasm widths
formatiert
der oben beschriebenen Spezifikation zu ändern.
Mithilfe der folgenden Übungen können Sie der Datei riscv32i.isa
Anweisungen hinzufügen
um eine „Hallo Welt“ zu simulieren, . Für alle, die es eilig haben,
finden Sie Lösungen in
riscv32i.isa
und
rv32i_instructions.h an.
Ersten Build ausführen
Wenn Sie das Verzeichnis nicht in riscv_isa_decoder
geändert haben, tun Sie dies jetzt. Dann
erstellen Sie das Projekt wie folgt – dieser Build sollte erfolgreich sein.
$ cd riscv_isa_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_isa_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_isa_decoder
In diesem Verzeichnis befinden sich neben anderen Dateien folgende Dateien: generierte C++ Dateien:
riscv32i_decoder.h
riscv32i_decoder.cc
riscv32i_enums.h
riscv32i_enums.cc
Sehen wir uns riscv32i_enums.h
an. Klicken Sie dazu im Browser darauf. Sie sollten
sehen wir, dass er etwa Folgendes enthält:
#ifndef RISCV32I_ENUMS_H
#define RISCV32I_ENUMS_H
namespace mpact {
namespace sim {
namespace codelab {
enum class SlotEnum {
kNone = 0,
kRiscv32,
};
enum class PredOpEnum {
kNone = 0,
kPastMaxValue = 1,
};
enum class SourceOpEnum {
kNone = 0,
kCsr = 1,
kImm12 = 2,
kRs1 = 3,
kPastMaxValue = 4,
};
enum class DestOpEnum {
kNone = 0,
kCsr = 1,
kRd = 2,
kPastMaxValue = 3,
};
enum class OpcodeEnum {
kNone = 0,
kCsrs = 1,
kCsrsNw = 2,
kCsrwNr = 3,
kEbreak = 4,
kFence = 5,
kPastMaxValue = 6
};
constexpr char kNoneName[] = "none";
constexpr char kCsrsName[] = "Csrs";
constexpr char kCsrsNwName[] = "CsrsNw";
constexpr char kCsrwNrName[] = "CsrwNr";
constexpr char kEbreakName[] = "Ebreak";
constexpr char kFenceName[] = "Fence";
extern const char *kOpcodeNames[static_cast<int>(
OpcodeEnum::kPastMaxValue)];
enum class SimpleResourceEnum {
kNone = 0,
kPastMaxValue = 1
};
enum class ComplexResourceEnum {
kNone = 0,
kPastMaxValue = 1
};
enum class AttributeEnum {
kPastMaxValue = 0
};
} // namespace codelab
} // namespace sim
} // namespace mpact
#endif // RISCV32I_ENUMS_H
Jeder Slot, Opcode und Operand, der im Feld
Die Datei riscv32i.isa
ist in einem der Aufzählungstypen definiert. Zusätzlich
gibt es ein OpcodeNames
-Array, in dem alle Namen der Opcodes gespeichert sind (es ist
definiert in riscv32i_enums.cc
). Die anderen Dateien enthalten den generierten Decoder,
Weitere Informationen dazu finden Sie
in einer anderen Anleitung.
Bass-Build-Regel
Das ISA-Decoder-Ziel in Bazel wird mit einem Makro für benutzerdefinierte Regeln mit dem Namen
mpact_isa_decoder
, die von mpact/sim/decoder/mpact_sim_isa.bzl
geladen wird
im Repository mpact-sim
. In dieser Anleitung ist das in
riscv_isa_decoder/BUILD
ist:
mpact_isa_decoder(
name = "riscv32i_isa",
src = "riscv32i.isa",
includes = [],
isa_name = "RiscV32I",
deps = [
"//riscv_semantic_functions:riscv32i",
],
)
Diese Regel ruft das ISA-Parser-Tool und den -Generator auf, um den C++-Code zu generieren.
kompiliert die generierten Dateien dann in eine Bibliothek, von der andere Regeln abhängen können.
das Label //riscv_isa_decoder:riscv32i_isa
. Der Abschnitt includes
wird verwendet
, um zusätzliche .isa
-Dateien anzugeben, die die Quelldatei enthalten kann. Die
Mit isa_name
wird angegeben, welche spezifische isa vorhanden ist. Dies ist erforderlich, wenn mehrere ist
angegeben ist, in der Quelldatei, für die der Decoder generiert werden soll.
ALU-Anweisungen für Registrierung und Registrierung hinzufügen
Jetzt ist es an der Zeit, der Datei riscv32i.isa
einige neue Anweisungen hinzuzufügen. Die erste
Gruppe von Anweisungen sind Register-Register-ALU-Anweisungen wie add
,
and
usw. Unter RiscV32 verwenden diese alle das R-Typ-Binäranweisungsformat:
31–25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | rs2 | rs1 | func3 | 3. | Opcode |
Die Datei .isa
wird zwar zum Generieren eines formatunabhängigen Decoders verwendet, ist aber trotzdem
ist es hilfreich, das Binärformat und sein Layout als Leitfaden für die Einträge zu berücksichtigen. Während Sie
gibt es drei Felder, die für den Decoder relevant sind, der den
Anweisungsobjekte: rs2
, rs1
und rd
. An dieser Stelle wählen wir
diese Namen für ganzzahlige Register, die gleich codiert sind (Bitsequenzen),
in allen Anweisungen in den gleichen Anweisungsfeldern.
Wir fügen folgende Anweisungen hinzu:
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.
Jede dieser Anweisungen wird dem Abschnitt opcodes
der
riscv32i
-Slotdefinition. Wir müssen den Namen, die Opcodes
und die semantische Funktion für jede Anweisung. Der Name ist einfach,
verwenden wir einfach die
Opcode-Namen oben. Außerdem verwenden sie alle
den gleichen Operanden,
können wir { : rs1, rs2 : rd}
für die Operandenspezifikation verwenden. Das bedeutet, dass
Der von rs1 angegebene Registrierungs-Quelloperanden hat in der Quelle den Index 0
Operandenvektor im Anweisungsobjekt, angegebener Quelloperanden
von rs2 hat Index 1 und der Registrierungszieloperand, der durch rd angegeben ist
ist das einzige Element im Zieloperandenvektor (bei Index 0).
Als Nächstes folgt die semantische Funktionsspezifikation. Hierzu wird das Keyword
semfunc
und ein C++-String, der ein Callable angibt, mit dem
zu einem std::function
. In dieser Anleitung verwenden wir Funktionen, sodass das Callable
String ist "&MyFunctionName"
. Die Verwendung des Namensschema, das vom
fence
-Anweisung, diese sollten "&RV32IAdd"
, "&RV32IAnd"
usw. sein.
Und zuletzt die Spezifikation zum Auseinanderbauen. Sie beginnt mit dem Keyword disasm
und
gefolgt von einer durch Kommas getrennten Liste von Zeichenfolgen, die angibt,
-Anweisung sollte als String ausgegeben werden. Die Verwendung eines %
-Zeichens vor einem
Operandenname gibt eine Zeichenfolgenersetzung mithilfe der Zeichenfolgendarstellung von an
mit diesem Operanden. Für die add
-Anweisung wäre das so: disasm: "add", "%rd,
%rs1,%rs2"
. Das bedeutet, dass der Eintrag für die add
-Anweisung folgendermaßen aussehen sollte:
wie:
add{ : rs1, rs2 : rd},
semfunc: "&RV32IAdd",
disasm: "add", "%rd, %rs1, %rs2";
Bearbeiten Sie nun die Datei riscv32i.isa
und fügen Sie all diese Anweisungen zum
.isa
– Beschreibung. Wenn Sie Hilfe benötigen oder Ihre Arbeit überprüfen möchten,
Beschreibungsdatei ist
hier.
Sobald die Anleitung der Datei riscv32i.isa
hinzugefügt wurde,
um Funktionsdeklarationen für jede der neuen semantischen Funktionen hinzuzufügen,
verweist auf die Datei rv32i_instructions.h
im
„../semantic_functions/. Wenn Sie Hilfe benötigen oder Ihre Arbeit überprüfen möchten,
lautet die Antwort
hier.
Wenn Sie fertig sind, wechseln Sie wieder zu riscv_isa_decoder
und erstellen Sie das Verzeichnis neu. Sie können sich die generierten Quelldateien gern ansehen.
ALU-Anweisungen mit Sofortnachrichten 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 |
Wie Sie sehen, beziehen sich die Operandennamen rs1
und rd
auf dieselben Bitfelder wie
und werden zur Darstellung von Ganzzahlregistern verwendet, sodass diese Namen
bleiben erhalten. Die unmittelbaren Wertefelder haben unterschiedliche Länge und Position und
Zwei (uimm5
und uimm20
) sind nicht signiert, imm12
hingegen ist signiert. Jede von
erhalten diese ihren eigenen Namen.
Die Operanden für die I-Typ-Anweisungen sollten daher { : rs1, imm12 :rd
}
sein. Für die speziellen I-Type-Anweisungen sollte { : rs1, uimm5 : rd}
lauten.
Die Spezifikation des U-Typ-Anweisung Operand muss { : uimm20 : rd }
lauten.
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.
Folgende spezielle I-Type-Anweisungen müssen hinzugefügt werden:
slli
: Sofortige Verschiebung nach links.srai
: Arithmetik wird sofort nach rechts verschoben.srli
: Wechselt unmittelbar nach rechts.
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.
Die für die Opcodes zu verwendenden Namen folgen natürlich den Anweisungsnamen.
(Sie müssen sich keine neuen ausdenken, sie sind alle einzigartig). Wenn es darum geht,
die semantischen Funktionen angeben, erinnern Sie sich daran,
dass die Anweisungsobjekte
Schnittstellen zu den Quelloperanden, die vom zugrunde liegenden Operanden unabhängig sind
Typ. Das bedeutet, dass für Anweisungen, die zwar dieselbe Operation haben, aber
können sich in Operandentypen unterscheiden, können dieselbe semantische Funktion haben. Beispiel:
Die Anweisung addi
führt dieselbe Operation wie die Anweisung add
aus, wenn
ignoriert man den Operandentyp, sodass sie dieselbe semantische Funktion verwenden können.
Spezifikation "&RV32IAdd"
. Gleiches gilt für andi
, ori
, xori
und slli
.
In der anderen Anleitung werden neue semantische Funktionen verwendet, diese sollten jedoch benannt werden.
basiert auf der Operation und nicht auf Operanden. Verwenden Sie daher für srai
"&RV32ISra"
. Die
Für die U-Type-Anweisungen auipc
und lui
gibt es keine äquivalenten Zeichen, also in Ordnung.
um "&RV32IAuipc"
und "&RV32ILui"
zu verwenden.
Die Zeichenfolgen zum Auseinanderbauen sind denen in der vorherigen Übung sehr ähnlich, aber
wie zu erwarten, werden Verweise auf %rs2
durch %imm12
, %uimm5
,
oder %uimm20
.
Nehmen Sie die Änderungen vor und erstellen Sie den Build. Prüfen Sie die generierte Ausgabe. Genauso zuvor können Sie Ihre Arbeit anhand riscv32i.isa und die rv32i_instructions.h an.
Branch- und Jump-and-Link-Anweisungen hinzufügen
In der Branch- und Jump-and-Link-Anleitung, die wir hinzufügen müssen, wird ein Ziel verwendet.
Operand, der nur in der Anweisung selbst impliziert wird, d. h. im nächsten PC
Wert. In dieser Phase behandeln wir dies als richtiger Operand mit dem Namen
next_pc
Er wird in einer späteren Anleitung näher definiert.
Anleitung für Zweig
Die Zweige, die wir hinzufügen, verwenden die B-Type-Codierung.
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 |
Die verschiedenen Direktfelder sind zu einem 12-Bit-Sofortfeld mit Vorzeichen verkettet.
Wert. Da das Format nicht wirklich relevant ist, nennen wir es „sofort“.
bimm12
, für 12-Bit-Verzweigung. Die Fragmentierung wird im
Anleitung zum Erstellen des Binärdecodierer-Tools weiter. Alle
Mit Zweiganweisungen werden die mit rs1 und rs2 angegebenen ganzzahligen Register verglichen, wenn
die Bedingung erfüllt ist, wird der unmittelbare Wert zum aktuellen PC-Wert
die Adresse der nächsten auszuführenden Anweisung. Die Operanden für die
Zweiganweisungen sollten daher { : rs1, rs2, bimm12 : next_pc }
lauten.
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.
Diese Opcode-Namen sind alle eindeutig und können daher im .isa
wiederverwendet werden.
Beschreibung. Natürlich müssen neue semantische Funktionsnamen hinzugefügt werden, z.B.
"&RV32IBeq"
usw.
Die Vorgabe für das Auseinanderbauen ist jetzt etwas komplizierter, da die Adresse
-Anweisung wird zur Berechnung des Ziels verwendet, ohne dass es tatsächlich Teil ist
der Anweisungsoperanden. Sie ist jedoch Teil der Informationen, die in
das Anweisungsobjekt, sodass es verfügbar ist. Die Lösung ist die Verwendung des
im Disassembly-String enthalten. Anstelle von "%" gefolgt von
den Operandennamen, können Sie %(expression: print format) eingeben. Nur sehr einfach
Ausdrücke werden unterstützt, aber darunter die Adresse plus Offset, mit dem @
, das für die aktuelle Anweisungsadresse verwendet wird. Das Druckformat ähnelt
printf -Formate im C-Stil, aber ohne das vorangestellte %
. Das Demontageformat für
Die Anweisung beq
wird dann zu:
disasm: "beq", "%rs1, %rs2, %(@+bimm12:08x)"
Jump-and-Link-Anweisungen
Es müssen nur zwei Jump-and-Link-Anweisungen hinzugefügt werden: jal
(Jump-and-Link) und
jalr
(indirekter Jump-and-Link).
Die Anweisung jal
verwendet die J-Type-Codierung:
31 | 30..21 | 20 | 19.12 | 11..7 | 6..0 |
---|---|---|---|---|---|
1 | 10 | 1 | 8 | 5 | 7 |
imm | imm | imm | imm | 3. | Opcode |
Wie bei den Verzweigungen ist auch die 20-Bit-Sofortübersetzung
mehrere Felder enthalten, nennen wir sie jimm20
. Die Fragmentierung ist nicht wichtig,
derzeit aber zu diesem Zeitpunkt, wird jedoch in den
Anleitung zum Erstellen des Binärdecoders erhalten. Der Operand
wird aus der Spezifikation { : jimm20 : next_pc, rd }
. Beachten Sie, dass es zwei
Ziel-Operanden, der nächste PC-Wert und das Link-Register, die in den
Anleitung.
Ähnlich wie in der Anleitung oben wird das Demontageformat zu:
disasm: "jal", "%rd, %(@+jimm20:08x)"
Der indirekte Jump-and-Link verwendet das I-Type-Format mit dem 12-Bit-Sofortformat. Es
fügt den vorzeichenerweiterten unmittelbaren Wert zum Ganzzahlregister hinzu, das durch
rs1
, um die Zielanweisungsadresse zu erzeugen. Das Linkregister ist das
Ganzzahliges Register, das durch rd
angegeben wird.
31..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|
12 | 5 | 3 | 5 | 7 |
imm12 | rs1 | func3 | 3. | Opcode |
Wenn Sie das Muster gesehen haben, folgern Sie nun, dass die Operandenspezifikation
für jalr
sollte { : rs1, imm12 : next_pc, rd }
sein und das Auseinanderbauen
Spezifikation:
disasm: "jalr", "%rd, %rs1, %imm12"
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.isa und rv32i_instructions.h an.
Anleitung zum Geschäft hinzufügen
Die Anleitung zum Shop ist sehr einfach. Sie alle verwenden das S-Typ-Format:
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 sehen, ist dies ein weiterer Fall einer fragmentierten 12-Bit-Sofortübersetzung.
und nennen Sie sie simm12
. In den Speicheranweisungen wird der Wert der Ganzzahl gespeichert.
der von rs2 angegebenen effektiven Adresse im Arbeitsspeicher, die durch Addieren von
den Wert des Ganzzahlregisters, das von rs1 zum vorzeichenerweiterten Wert von
sofort mit der 12-Bit-Version. Das Operandenformat muss { : rs1, simm12, rs2 }
sein für
alle Anweisungen des Händlers zu lesen.
Folgende Anleitungen müssen implementiert werden:
sb
– Byte speichern.sh
: Halbwort speichern.sw
– Wort speichern.
Die Zerlegungsspezifikation für sb
sieht so aus, wie Sie es erwarten würden:
disasm: "sb", "%rs2, %simm12(%rs1)"
Auch die semantischen Funktionsspezifikationen sollten Sie erwarten: "&RV32ISb"
,
usw.
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.isa und rv32i_instructions.h an.
Anweisungen zum Laden hinzufügen
Ladeanweisungen sind etwas anders modelliert als die anderen Anweisungen in den Simulator an. Um Fälle zu modellieren, bei denen die Ladelatenz Unklare Ladeanweisungen werden in zwei separate Aktionen unterteilt: 1) wirksam und 2) das Zurückschreiben von Ergebnissen. Im wird die semantische Aktion der Last in zwei Teile geteilt. separaten Anleitung, der Hauptanweisung und einer child-Anweisung. Außerdem wenn wir Operanden angeben, müssen wir diese sowohl für die Haupt- als auch für die child verwenden. Dies geschieht, indem die Operandenspezifikation als Liste der Triolen. Die Syntax lautet:
{(predicate : sources : destinations),
(predicate : sources : destinations), ... }
Die Anweisungen zum Laden werden alle im I-Type-Format verwendet, ebenso viele wie die Anweisungen:
31..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|
12 | 5 | 3 | 5 | 7 |
imm12 | rs1 | func3 | 3. | Opcode |
Die Operandenspezifikation teilt die Operanden auf, die zum Berechnen der Adresse erforderlich sind.
und initiieren Sie den Speicherzugriff vom Registerziel aus für die Ladedaten:
{( : rs1, imm12 : ), ( : : rd) }
Da die semantische Aktion auf zwei Anweisungen aufgeteilt ist,
müssen ebenfalls
zwei Callables angeben. Für lw
(Wort laden) wäre dies
geschrieben:
semfunc: "&RV32ILw", "&RV32ILwChild"
Die Vorgabe für das Auseinanderbauen ist konventioneller. Es wird nicht erwähnt,
Anweisungen für Kinder. Für lw
sollte sie so aussehen:
disasm: "lw", "%rd, %imm12(%rs1)"
Folgende Ladeanweisungen müssen implementiert werden:
lb
– Byte laden.lbu
: Byte ohne Vorzeichen ladenlh
: Halbwort wird geladen.lhu
: Halbwort wird unvorzeichenlos geladen.lw
– Wort wird geladen.
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.isa und rv32i_instructions.h an.
Vielen Dank, dass Sie so weit gekommen sind. Wir hoffen, dass Ihnen diese Informationen weiterhelfen.