Cele tego samouczka to:
- Poznaj strukturę i składnię pliku z opisem w formacie binarnym.
- Dowiedz się, jak opis formatu binarnego pasuje do opisu ISA.
- Napisz opisy binarne podzbioru instrukcji RiscV RV32I.
Omówienie
Kodowanie instrukcji binarnych RiscV
Kodowanie instrukcji binarnych to standardowy sposób kodowania instrukcji na mikroprocesorze. Zwykle są przechowywane w pliku wykonywalnym, zwykle w formacie ELF. Instrukcje mogą mieć stałą szerokość lub zmienną .
Zazwyczaj instrukcje korzystają z małego zestawu formatów kodowania, z których każdy dostosowany do zakodowanego typu instrukcji. Na przykład zarejestruj-zarejestruj mogą używać jednego formatu, który maksymalizuje liczbę dostępnych kodów operacji, Z kolei instrukcje związane z natychmiastową rejestracją korzystają z innych, które pozwalają są dostępne kody operacji pozwalające zwiększyć rozmiar natychmiastowego działania, który można zakodować. Instrukcje gałęzi i skoku niemal zawsze korzystają z formatów, które maksymalizują rozmiar bezpośrednio w celu obsługi gałęzi o większych przesunięciach.
Formaty instrukcji używane w instrukcjach, które chcemy dekodować w naszym RiscV są takie symulatory:
Format R-Type używany do instrukcji dotyczących rejestracji (rejestracji):
31..25 | 24..20 | 19.01.2015 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | rs2 | rs1 | func3 | rd | kod operacji |
format I-Type używany do przesyłania instrukcji natychmiastowych i wczytywania oraz
instrukcja jalr
, 12-bitowa natychmiast.
31.20 | 19.01.2015 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|
12 | 5 | 3 | 5 | 7 |
imm12 | rs1 | func3 | rd | kod operacji |
Specjalny format I-Type, używany do przenoszenia z natychmiastowymi instrukcjami, 5-bitowy natychmiast:
31..25 | 24..20 | 19.01.2015 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | uimm5 | rs1 | func3 | rd | kod operacji |
Format U-Type używany do wyświetlania długich natychmiastowych instrukcji (lui
, auipc
), 20-bitowych
natychmiast:
31..12 | 11..7 | 6..0 |
---|---|---|
20 | 5 | 7 |
uimm20 | rd | kod operacji |
Format typu B używany w gałęziach warunkowych, 12-bitowy natychmiast.
31 | 30..25 | 24..20 | 19.01.2015 | 14..12 | 11..8 | 7 | 6..0 |
---|---|---|---|---|---|---|---|
1 | 6 | 5 | 5 | 3 | 4 | 1 | 7 |
Imm | Imm | rs2 | rs1 | func3 | Imm | Imm | kod operacji |
Format J-Type używany w instrukcji jal
, 20-bitowy.
31 | 30..21 | 20 | 19.012 | 11..7 | 6..0 |
---|---|---|---|---|---|
1 | 10 | 1 | 8 | 5 | 7 |
Imm | Imm | Imm | Imm | rd | kod operacji |
Format S-Type używany do instrukcji przechowywania, 12-bitowy natychmiastowy.
31..25 | 24..20 | 19.01.2015 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
Imm | rs2 | rs1 | func3 | Imm | kod operacji |
Jak widać w tych formatach, wszystkie te instrukcje są 32-bitowe. niskie 7 bitów w każdym formacie to pole kodu operacji. Zwróć też uwagę, że podczas kilka formatów ma natychmiastowe rozmiary tego samego rozmiaru, ich fragmenty są pobierane z różnych części instrukcji. Jak zobaczymy, dekoder plików binarnych specyfikacja formatu.
Opis kodowania plików binarnych
Kodowanie binarne instrukcji jest wyrażone w formacie binarnym
(.bin_fmt
) plik z opisem. Opisuje kodowanie binarne
w ISA, tak aby dekoder instrukcji w formacie binarnym mógł zostać
. Wygenerowany dekoder określa kod operacji, wyodrębnia wartość
operand i pola bezpośrednie, w celu dostarczenia informacji wymaganych przez ISA
dekoder niezależny od kodowania opisany w poprzednim samouczku.
W tym samouczku napiszemy plik z opisem kodowania binarnego dla podzbioru instrukcji RiscV32I niezbędnych do symulowania instrukcji stosowanych w mały „Hello, World” programu. Więcej informacji o ISA RiscV ISA znajdziesz na stronie Specyfikacje Risc-V {.external}.
Otwórz plik:
riscv_bin_decoder/riscv32i.bin_fmt
Zawartość pliku jest podzielona na kilka sekcji.
Pierwsza z nich to definicja atrybutu decoder
.
decoder RiscV32I {
// The namespace in which code will be generated.
namespace mpact::sim::codelab;
// The name (including any namespace qualifiers) of the opcode enum type.
opcode_enum = "OpcodeEnum";
// Include files specific to this decoder.
includes {
#include "riscv_isa_decoder/solution/riscv32i_decoder.h"
}
// Instruction groups for which to generate decode functions.
RiscVInst32;
};
Definicja dekodera zawiera nazwę dekodera RiscV32I
oraz
4 kolejne informacje. Pierwszą z nich jest namespace
, która określa
przestrzeń nazw, w której zostanie umieszczony wygenerowany kod. Po drugie,
opcode_enum
, który określa sposób generowania typu wyliczenia kodu operacji
przez dekoder ISA powinno być odwołaniem w wygenerowanym kodzie. Po trzecie,
includes {}
określa uwzględnij pliki niezbędne dla kodu wygenerowanego dla
dla tego dekodera.
W naszym przypadku jest to plik generowany przez dekoder ISA
poprzedniego samouczka.
Dodatkowe pliki z danymi typu „include” można określić w elemencie includes {}
o zakresie globalnym
definicji. Jest to przydatne, gdy zdefiniowanych jest wiele dekoderów, które muszą
niektóre z tych samych plików. Czwarta to lista nazw instrukcji
grupy składające się z instrukcji, dla których generowany jest dekoder. W naszym
jest tylko jeden: RiscVInst32
.
Dalej mamy 3 definicje formatów: Odpowiadają one różnym instrukcjom dla 32-bitowego słowa instrukcji używanego przez już zdefiniowane instrukcje w pliku.
// 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];
};
Pierwsza określa 32-bitowy format instrukcji o nazwie Inst32Format
, który zawiera
dwa pola: bits
(szeroki 25 bitów) i opcode
(szerokość 7 bitów). Każde pole zawiera
unsigned
, co oznacza, że po wyodrębnieniu wartość zostanie wydłużona o zero
i umieszczoną w formacie liczb całkowitym C++. Suma szerokości pól bitowych musi
musi być taka sama jak szerokość formatu. Narzędzie wygeneruje błąd, jeśli
sprzeciwu. Ten format nie wywodzi się od żadnego innego formatu, dlatego jest
uważany za format najwyższego poziomu.
Drugi określa 32-bitowy format instrukcji o nazwie IType
, który generuje
od Inst32Format
, dzięki czemu te 2 formaty są powiązane. Format zawiera 5
pola: imm12
, rs1
, func3
, rd
i opcode
. Aktualne pole imm12
to
signed
, co oznacza, że wartość zostanie powiększona, gdy wartość wynosi
wyodrębniony i umieszczony w formie liczby całkowitej C++. Zwróć uwagę, że usługa IType.opcode
ma
ten sam atrybut podpisany/niepodpisany i odnosi się do tych samych bitów instrukcji
jako Inst32Format.opcode
.
Trzeci format to format niestandardowy, który jest używany tylko przez fence
czyli instrukcji, która została już określona, przy czym nie
niepokoić się w tym samouczku.
Ważne: używaj ponownie nazw pól w różnych powiązanych formatach, o ile są one przedstawiają te same bity i mają ten sam atrybut podpisany/niepodpisany.
Po definicjach formatu w polu riscv32i.bin_fmt
pojawia się grupa instrukcji
definicji. Wszystkie instrukcje w grupie instrukcji muszą być takie same
i użyć formatu, który wywodzi (być może pośrednio) z tego samego
formatem instrukcji najwyższego poziomu. Gdy ISA może mieć instrukcje z różnymi
dla każdej długości jest wykorzystywana inna grupa instrukcji. Dodatkowo:
jeśli docelowe dekodowanie ISA zależy od trybu wykonywania, na przykład A/B
instrukcji, dla każdego trybu wymagana jest oddzielna grupa instrukcji.
Parser bin_fmt
generuje dekoder binarny dla każdej grupy instrukcji.
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;
};
Grupa instrukcji definiuje nazwę RiscV32I
, szerokość [32]
, nazwę
typ wyliczenia kodu operacji, który ma być użyty "OpcodeEnum"
, oraz instrukcja podstawowa
. Typ wyliczenia kodu operacji powinien być taki sam jak wygenerowany przez
format niezależnego dekodera instrukcji omawianego w samouczku na stronie ISA.
za pomocą dekodera.
Każdy opis kodowania instrukcji składa się z 3 części:
- Nazwa kodu operacji, która musi być taka sama jak użyta w instrukcji. zgodnie z opisem dekodera, że powinny one ze sobą współpracować.
- Format instrukcji dla kodu operacji. To format służy do wypełniania odwołań do pól bitowych w ostatniej części.
- Rozdzielona przecinkami lista ograniczeń pola bitowego,
==
,!=
,<
,<=
,>
, i>=
muszą być spełnione wszystkie warunki, aby kod operacji pasował do słowo instruktażowe.
Parser .bin_fmt
wykorzystuje wszystkie te informacje do utworzenia dekodera, który:
- Udostępnia funkcje wyodrębniania (ze znakiem/bez podpisu) odpowiednie dla każdego bitu.
we wszystkich formatach. Funkcje wyodrębniania są umieszczane w przestrzeniach nazw
wersję nazwy formatu (z przypadkiem węża). Na przykład plik
funkcje wyodrębniania dla formatu
IType
są umieszczane w przestrzeni nazwi_type
. Każda funkcja wyodrębniania jest zadeklarowanainline
, przyjmuje najwęższą z tych funkcji:uint_t
typu, który przechowuje szerokość formatu i zwraca najwęższą z nichint_t
(w przypadku podpisu) typuint_t
(w przypadku braku podpisu), który zawiera wyodrębnione pole . np.:
inline uint8_t ExtractOpcode(uint32_t value) {
return value & 0x7f;
}
- Funkcja dekodowania dla każdej grupy instrukcji. Zwraca wartość typu
OpcodeEnum
i przyjmuje najwęższy typuint_t
, który obejmuje szerokość format grupy instrukcji.
Wykonaj początkową kompilację
Zmień katalog na riscv_bin_decoder
i skompiluj projekt za pomocą
to polecenie:
$ cd riscv_bin_decoder
$ bazel build :all
Zmień katalog z powrotem na katalog główny repozytorium i sprawdźmy,
w wygenerowanych źródłach. Aby to zrobić, zmień katalog na
bazel-out/k8-fastbuild/bin/riscv_bin_decoder
(zakładając, że używasz procesora x86
host – w przypadku innych hostów kolejnym ciągiem znaków będzie k8-fastbuild).
$ cd ..
$ cd bazel-out/k8-fastbuild/bin/riscv_bin_decoder
riscv32i_bin_decoder.h
riscv32i_bin_decoder.cc
wygenerowany plik nagłówka (.h),
Otwórz aplikację riscv32i_bin_decoder.h
. Pierwsza część pliku zawiera standardowe
zabezpieczenia standardowe, uwzględniać pliki, deklaracje przestrzeni nazw. W następnej kolejności
w przestrzeni nazw internal
istnieje funkcja pomocnicza oparta na szablonie. Ta funkcja
służy do wyodrębniania pól bitów z formatów, które są zbyt długie, aby zmieścić się w 64-bitowym C++
liczba całkowita.
#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
W sekcji początkowej znajduje się zbiór 3 przestrzeni nazw, po 1 dla każdej
z deklaracji format
w pliku riscv32i.bin_fmt
:
namespace fence {
...
} // namespace fence
namespace i_type {
...
} // namespace i_type
namespace inst32_format {
...
} // namespace inst32_format
W każdej z tych przestrzeni nazw funkcja ekstrakcji pola bitowego inline
dla każdego pola bitowego w tym formacie. Dodatkowo format podstawowy
duplikuje funkcje wyodrębniania z formatów podrzędnych, dla których
1) nazwy pól występują tylko w jednej nazwie pola lub 2) w przypadku których
nazwy pól odnoszą się do tego samego pola typu (pozycje znaków i elementów oraz bity)
w każdym z tych formatów. Umożliwia to włączenie pól bitowych opisujących to samo
bity do wyodrębnienia za pomocą funkcji w przestrzeni nazw formatu najwyższego poziomu.
Poniżej przedstawiono funkcje w przestrzeni nazw i_type
:
namespace i_type {
inline uint8_t ExtractFunc3(uint32_t value) {
return (value >> 12) & 0x7;
}
inline int16_t ExtractImm12(uint32_t value) {
int16_t result = ( (value >> 20) & 0xfff) << 4;
result = result >> 4;
return result;
}
inline uint8_t ExtractOpcode(uint32_t value) {
return value & 0x7f;
}
inline uint8_t ExtractRd(uint32_t value) {
return (value >> 7) & 0x1f;
}
inline uint8_t ExtractRs1(uint32_t value) {
return (value >> 15) & 0x1f;
}
} // namespace i_type
Na koniec deklaracja funkcji funkcji dekodera dla instrukcji
zadeklarowano grupę RiscVInst32
. Przyjmuje ona 32-bitową wartość bez znaku jako wartość
słowo instrukcji i zwraca element klasy wyliczenia OpcodeEnum
pasujące, lub OpcodeEnum::kNone
, jeśli nie ma pasujących kryteriów.
OpcodeEnum DecodeRiscVInst32(uint32_t inst_word);
wygenerowany plik źródłowy (.cc),
Teraz otwórz aplikację riscv32i_bin_decoder.cc
. Pierwsza część pliku zawiera
deklaracje #include
i przestrzeni nazw, a po niej funkcja dekodera
deklaracje:
#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);
Pole DecodeRiscVInst32None
jest używane do pustych działań dekodowania, tj.
zwracające OpcodeEnum::kNone
. Pozostałe 3 funkcje składają się
za pomocą dekodera. Ogólny dekoder działa w sposób hierarchiczny. Zbiór
bitów w słowie instrukcji jest obliczany w celu rozróżnienia
instrukcji lub ich grup. Fragmenty nie muszą być
nie są ze sobą sąsiadujące. Liczba bitów określa rozmiar tabeli przeglądowej, która
jest wypełniane funkcjami dekodera drugiego poziomu. Pojawi się w następnym
pliku:
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,
...
};
Na koniec zostały zdefiniowane funkcje dekodera:
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;
}
W tym przypadku, gdy zdefiniowane są tylko 4 instrukcje, pojawi się tylko jeden poziom dekodowania i bardzo rozproszona tabela przeglądowa. Instrukcje zmieni się struktura dekodera i liczba poziomów hierarchia tabel dekodera może się zwiększyć.
Dodaj instrukcje dotyczące rejestracji ALU
Teraz musisz dodać nowe instrukcje do pliku riscv32i.bin_fmt
.
pierwszą grupą instrukcji są instrukcje dotyczące rejestracji i rejestracji, takie jak
add
, and
itd. W RiscV32 wszystkie używają instrukcji binarnych typu R
format:
31..25 | 24..20 | 19.01.2015 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | rs2 | rs1 | func3 | rd | kod operacji |
Najpierw musimy dodać format. Otwórz aplikację
riscv32i.bin_fmt
w ulubionym edytorze. Zaraz za Inst32Format
,
dodaj format o nazwie RType
, który pochodzi od Inst32Format
. Wszystkie pola bitowe
RType
wynosi unsigned
. Użyj nazw, szerokości bitów i kolejności bitów (od lewej do prawej).
za pomocą tabeli powyżej, aby zdefiniować format. Jeśli potrzebna jest wskazówka lub chcesz się dowiedzieć
kompleksowego rozwiązania.
kliknij tutaj.
Teraz musimy dodać instrukcje. Instrukcje:
add
– dodanie liczby całkowitej.and
– bitowe i.or
– bitowa lubsll
– przesunięcie w lewo – wartość logiczna.sltu
– ustaw wartość „mniejsze niż”, „bez podpisu”.sub
– odejmowanie liczby całkowitej.xor
– bitowa xor.
Ich kodowanie:
31..25 | 24..20 | 19.01.2015 | 14..12 | 11..7 | 6..0 | nazwa kodu operacji |
---|---|---|---|---|---|---|
000 0000 | rs2 | rs1 | 000 | rd | 011 0011 | dodaj |
000 0000 | rs2 | rs1 | 111 | rd | 011 0011 | i |
000 0000 | rs2 | rs1 | 110 | rd | 011 0011 | lub |
000 0000 | rs2 | rs1 | 001 | rd | 011 0011 | Sll |
000 0000 | rs2 | rs1 | 011 | rd | 011 0011 | Sltu |
010 0000 | rs2 | rs1 | 000 | rd | 011 0011 | zast. |
000 0000 | rs2 | rs1 | 100 | rd | 011 0011 | Xor |
func7 | func3 | kod operacji |
Dodaj te definicje przed innymi instrukcjami w
Grupa instrukcji RiscVInst32
. Ciągi binarne są oznaczone znakiem początkowym
przedrostek 0b
(podobny do 0x
w przypadku liczb szesnastkowych). Aby ułatwić
do odczytywania długich ciągów cyfr binarnych, możesz też wstawić cudzysłów '
jako
separatorem cyfr w odpowiednim miejscu.
Każda z tych definicji będzie miała trzy ograniczenia:
func7
, func3
i opcode
. W przypadku wszystkich elementów oprócz sub
ograniczenie func7
będzie
być:
func7 == 0b000'0000
Ograniczenie func3
różni się w przypadku większości instrukcji. Dotyczy: add
i
sub
to:
func3 == 0b000
Ograniczenie opcode
jest takie samo w przypadku każdej z tych instrukcji:
opcode == 0b011'0011
Pamiętaj, aby zakończyć każdy wiersz średnikiem ;
.
Gotowe rozwiązanie to tutaj.
Teraz skompiluj projekt tak jak poprzednio i otwórz wygenerowany plik
riscv32i_bin_decoder.cc
. Zobaczysz, że dodatkowe funkcje dekodera
które zostały wygenerowane na potrzeby obsługi nowych instrukcji. W większości przypadków
podobne do wygenerowanych wcześniej, ale przyjrzyj się
DecodeRiscVInst32_0_c
, który służy do dekodowania add
/sub
:
OpcodeEnum DecodeRiscVInst32_0_c(uint32_t inst_word) {
static constexpr OpcodeEnum opcodes[2] = {
OpcodeEnum::kAdd,
OpcodeEnum::kSub,
};
if ((inst_word & 0xbe000000) != 0x0) return OpcodeEnum::kNone;
uint32_t index;
index = (inst_word >> 30) & 0x1;
return opcodes[index];
}
W tej funkcji generowana jest statyczna tabela dekodowania, a wartością wyszukiwaną jest wyodrębnione ze słowa instrukcji w celu wybrania odpowiedniego indeksu. Powoduje to dodanie w hierarchii dekodera instrukcji, ale ponieważ kod opcode może być szukanych bezpośrednio w tabeli bez dodatkowych porównań. zamiast wymagać innego wywołania funkcji.
Dodawanie instrukcji dotyczących ALU z natychmiastowym działaniem
Następny zestaw instrukcji to instrukcje dotyczące ALU, które wykorzystują natychmiastową wartość zamiast jednego z rejestrów. Wyróżniamy 3 grupy instrukcje (na podstawie bezpośredniego pola): instrukcje I-Type natychmiastowe z 12-bitowym podpisem natychmiastowym, specjalistycznymi natychmiastowymi instrukcjami I-Type dla przesunięć oraz natychmiastowy typ U z 20-bitową wartością natychmiastową bez znaku. Formaty są wymienione poniżej:
Bezpośredni format typu I:
31.20 | 19.01.2015 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|
12 | 5 | 3 | 5 | 7 |
imm12 | rs1 | func3 | rd | kod operacji |
Specjalny format bezpośredni typu I-Type:
31..25 | 24..20 | 19.01.2015 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | uimm5 | rs1 | func3 | rd | kod operacji |
Natychmiastowy format U-Type:
31..12 | 11..7 | 6..0 |
---|---|---|
20 | 5 | 7 |
uimm20 | rd | kod operacji |
Format I-Type już istnieje w riscv32i.bin_fmt
, więc nie ma potrzeby
i dodać taki format.
Jeśli porównamy wyspecjalizowany format I-Type z formatem R-Type zdefiniowanym w
w poprzednim ćwiczeniu widzimy, że jedyną różnicą jest to, że pola rs2
została zmieniona na uimm5
. Zamiast dodawać nowy format, możemy wzbogacić
w formacie R-Type. Nie możemy dodać kolejnego pola, ponieważ spowodowałoby to zwiększenie szerokości
format, ale możemy dodać nakładkę. Nakładka to alias dla zestawu
w formacie i można go użyć do łączenia wielu podciągów argumentu
w oddzielną nazwę. Skutkiem ubocznym jest to, że wygenerowany kod
oprócz funkcji wyodrębniania nakładki
dla treningów terenowych. W tym przypadku, gdy zarówno rs2
, jak i uimm5
są niepodpisane,
nie powoduje znaczącej różnicy, poza wyraźnym zaznaczeniem, że pole jest używane.
natychmiastowy. Aby dodać nakładkę o nazwie uimm5
do formatu R-Type, dodaj instrukcję
następujące dane za ostatnim polem:
overlays:
unsigned uimm5[5] = rs2;
Jedyny nowy format, który musimy dodać, to format U-Type. Zanim dodamy funkcję
spójrzmy na 2 instrukcje, w których jest on używany: auipc
oraz
lui
Obie te przesuwają 20-bitową wartość natychmiastową w lewo o 12, zanim zostaną użyte.
aby dodać do niego komputer (auipc
) lub zapisać go bezpośrednio w rejestrze
(lui
). Dzięki nakładce możemy udostępnić przesuwną wersję natychmiastowego,
przechodząc od obliczeń do instrukcji,
do ich dekodowania. Najpierw dodaj format zgodny z polami określonymi w tabeli
powyżej. Następnie możemy dodać taką nakładkę:
overlays:
unsigned uimm32[32] = uimm20, 0b0000'0000'0000;
Składnia nakładki pozwala łączyć nie tylko pola, ale także literały cóż. W tym przypadku łączymy ją przez 12 zer, co powoduje przesunięcie go w lewo. do 12.
Instrukcje, które musimy dodać, są następujące:
addi
– dodaj natychmiastowe.andi
– bitowa i natychmiastowa.ori
– bitowa lub natychmiastowa.xori
– bitowe xor z trybem natychmiastowym.
Ich kodowanie:
31.20 | 19.01.2015 | 14..12 | 11..7 | 6..0 | opcode_name |
---|---|---|---|---|---|
imm12 | rs1 | 000 | rd | 001 0011 | Addi |
imm12 | rs1 | 111 | rd | 001 0011 | andi |
imm12 | rs1 | 110 | rd | 001 0011 | ori |
imm12 | rs1 | 100 | rd | 001 0011 | Xori |
func3 | kod operacji |
Instrukcje dla typu R-Type (specjalistycznych I-Type), które musimy dodać, to:
slli
– przesunięcie w lewo o wartość logiczną natychmiastowe.srai
– przesunięcie arytmetyczne w prawo według trybu natychmiastowego.srli
– przesunięcie logiczne w prawo o tryb natychmiastowy.
Ich kodowanie:
31..25 | 24..20 | 19.01.2015 | 14..12 | 11..7 | 6..0 | nazwa kodu operacji |
---|---|---|---|---|---|---|
000 0000 | uimm5 | rs1 | 001 | rd | 001 0011 | Slli |
010 0000 | uimm5 | rs1 | 101 | rd | 001 0011 | srai |
000 0000 | uimm5 | rs1 | 101 | rd | 001 0011 | srli |
func7 | func3 | kod operacji |
Instrukcje typu U, które musimy dodać:
auipc
– dodaj natychmiastowe górne dane do komputera.lui
– wczytaj od razu u góry.
Ich kodowanie:
31..12 | 11..7 | 6..0 | nazwa kodu operacji |
---|---|---|---|
uimm20 | rd | 001 0111 | auipc |
uimm20 | rd | 011 0111 | Lui |
kod operacji |
Wprowadź zmiany i zacznij budować. Sprawdź wygenerowane dane wyjściowe. Tylko tak jak poprzednio, możesz porównać swoją pracę z riscv32i.bin_fmt.
Dodawanie instrukcji typu gałąź i skok i linki
Następny zestaw instrukcji, które należy zdefiniować, to gałąź warunkowa instrukcje, instrukcja „skok i linki” oraz rejestr „już i link” wraz z instrukcjami.
Gałęzie warunkowe, które dodajemy, używają kodowania typu B.
31..25 | 24..20 | 19.01.2015 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
imm7 | rs2 | rs1 | func3 | imm5 | kod operacji |
Mimo że kodowanie B-Type jest identyczne pod względem układu,
wybierz nowy typ formatu, aby dostosować go do dokumentacji RiscV.
Można też dodać nakładkę, by uzyskać odpowiednią gałąź
natychmiastowe przemieszczenie na zewnątrz przy użyciu pól func7
i rd
typu R-Type.
kodowanie.
Dodanie formatu BType
z polami podanymi powyżej jest konieczne, ale nie
wystarczająca. Jak widać, bezpośrednie połączenie jest podzielone na dwa pola instrukcji.
Ponadto instrukcje odgałęzień nie traktują tego jako prostego połączenia
te 2 pola. Zamiast tego każde pole jest dalej partycjonowane, a te partycje
są połączone w innej kolejności. Ostatnia wartość jest przesuwana w lewo o
aby uzyskać przesunięcie 16-bitowe.
Sekwencja bitów w słowie instruktażowym użytym do utworzenia natychmiastowego to: 31,
7, 30..25, 11..8. Odpowiada to poniższym odwołaniom do pól podrzędnych, gdzie
indeks lub zakres określają bity w polu, numerowane od prawej do lewej, tzn.
imm7[6]
odnosi się do msb zmiennej imm7
, a imm5[0]
to lsb
imm5
imm7[6], imm5[0], imm7[5..0], imm5[4..1]
Ten fragment instrukcji dotyczących gałęzi jest częścią instrukcji
wiele wad. Najpierw łączy wdrożenie funkcji semantycznej z
w reprezentacji instrukcji binarnych. Po drugie: wydłuża czas działania,
nadmiarowe. Musisz to zrobić, dodając do formatu BType
nakładkę, zawierającą m.in.
końcowy „0” aby uwzględnić przesunięcie w lewo.
overlays:
signed b_imm[13] = imm7[6], imm5[0], imm7[5..0], imm5[4..1], 0b0;
Zwróć uwagę, że nakładka jest podpisana, więc zostanie automatycznie rozszerzona jest wyodrębniany ze słowa instrukcji.
Instrukcja szybkiego przejścia do linku (natychmiastowego) wykorzystuje kodowanie J-Type:
31..12 | 11..7 | 6..0 |
---|---|---|
20 | 5 | 7 |
imm20 | rd | kod operacji |
Ten format również można łatwo dodać, lecz także bezpośredni, używany przez nie jest tak prosta, jak się wydaje. Sekwencje bitów używane do w pełni natychmiastowe to: 31, 19..12, 20, 30..21, a ostateczna przesunięcie w lewo o jeden w celu wyrównania do połowy słów. Rozwiązaniem jest dodanie kolejnego nakładka (21 bitów, aby uwzględnić przesunięcie w lewo) do formatu:
overlays:
signed j_imm[21] = imm20[19, 7..0, 8, 18..9], 0b0;
Jak widać, w składni nakładek można podawać wiele zakresów w formie skróconej. Ponadto, jeśli nie zostanie użyta nazwa pola, bit liczby odnoszą się do samego słowa instrukcji, więc powyższe przykłady mogą być równie zapisany jako:
signed j_imm[21] = [31, 19..12, 20, 30..21], 0b0;
Natomiast funkcja Ju-and-link (rejestracja) wykorzystuje format I-type. wcześniej.
Bezpośredni format typu I:
31.20 | 19.01.2015 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|
12 | 5 | 3 | 5 | 7 |
imm12 | rs1 | func3 | rd | kod operacji |
Tym razem nie musisz wprowadzać żadnych zmian w formacie.
Instrukcje dotyczące gałęzi, które musimy dodać, są następujące:
beq
– gałąź, jeśli jest równa.bge
– gałąź, jeśli jest większa lub równa.bgeu
– gałąź, jeśli jest większa lub równa bez znaku.blt
– gałąź, jeśli ma wartość mniejszą niż.bltu
– gałąź, jeśli jest mniejsza niż brak podpisu.bne
– gałąź, jeśli nie jest równa.
Są one kodowane w następujący sposób:
31..25 | 24..20 | 19.01.2015 | 14..12 | 11..7 | 6..0 | nazwa kodu operacji |
---|---|---|---|---|---|---|
imm7 | rs2 | rs1 | 000 | imm5 | 110 0011 | Beq |
imm7 | rs2 | rs1 | 101 | imm5 | 110 0011 | Bge |
imm7 | rs2 | rs1 | 111 | imm5 | 110 0011 | Bgeu |
imm7 | rs2 | rs1 | 100 | imm5 | 110 0011 | blt |
imm7 | rs2 | rs1 | 110 | imm5 | 110 0011 | bltu |
imm7 | rs2 | rs1 | 001 | imm5 | 110 0011 | bne |
func3 | kod operacji |
Instrukcja jal
jest kodowana w ten sposób:
31..12 | 11..7 | 6..0 | nazwa kodu operacji |
---|---|---|---|
imm20 | rd | 110 1111 | Jal |
kod operacji |
Instrukcja jalr
jest kodowana w ten sposób:
31.20 | 19.01.2015 | 14..12 | 11..7 | 6..0 | opcode_name |
---|---|---|---|---|---|
imm12 | rs1 | 000 | rd | 110 0111 | Jalr |
func3 | kod operacji |
Wprowadź zmiany i zacznij budować. Sprawdź wygenerowane dane wyjściowe. Tylko tak jak poprzednio, możesz porównać swoją pracę z riscv32i.bin_fmt.
Dodaj instrukcje dotyczące sklepu
Instrukcje dotyczące sklepu wykorzystują kodowanie S-Type, które jest identyczne z kodowaniem B-Type.
używane w instrukcjach odgałęzień, z wyjątkiem kompozycji
natychmiast. Dodajemy format SType
, aby zachować zgodność z formatem RiscV.
dokumentacji.
31..25 | 24..20 | 19.01.2015 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
imm7 | rs2 | rs1 | func3 | imm5 | kod operacji |
W przypadku formatu SType
natychmiastowe jest na szczęście proste
przez połączenie dwóch bezpośrednich pól, przez co specyfikacja nakładki
jest po prostu:
overlays:
signed s_imm[12] = imm7, imm5;
Pamiętaj, że podczas łączenia całych pól nie są wymagane żadne specyfikatory zakresu bitów.
Instrukcje dla sklepu są zakodowane w ten sposób:
31..25 | 24..20 | 19.01.2015 | 14..12 | 11..7 | 6..0 | nazwa kodu operacji |
---|---|---|---|---|---|---|
imm7 | rs2 | rs1 | 000 | imm5 | 010 0011 | sb |
imm7 | rs2 | rs1 | 001 | imm5 | 010 0011 | sz |
imm7 | rs2 | rs1 | 010 | imm5 | 010 0011 | sw |
func3 | kod operacji |
Wprowadź zmiany i zacznij budować. Sprawdź wygenerowane dane wyjściowe. Tylko tak jak poprzednio, możesz porównać swoją pracę z riscv32i.bin_fmt.
Dodaj instrukcje wczytywania
Instrukcje ładowania są w formacie I-Type. Nie trzeba tam wprowadzać żadnych zmian.
Kodowanie:
31.20 | 19.01.2015 | 14..12 | 11..7 | 6..0 | opcode_name |
---|---|---|---|---|---|
imm12 | rs1 | 000 | rd | 000 0011 | lb |
imm12 | rs1 | 100 | rd | 000 0011 | lbu |
imm12 | rs1 | 001 | rd | 000 0011 | LH |
imm12 | rs1 | 101 | rd | 000 0011 | LHU |
imm12 | rs1 | 010 | rd | 000 0011 | lw |
func3 | kod operacji |
Wprowadź zmiany i zacznij budować. Sprawdź wygenerowane dane wyjściowe. Tylko tak jak poprzednio, możesz porównać swoją pracę z riscv32i.bin_fmt.
To koniec samouczka. Mamy nadzieję, że był przydatny.