Samouczek dotyczący dekodera instrukcji binarnych

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 nazw i_type. Każda funkcja wyodrębniania jest zadeklarowana inline, przyjmuje najwęższą z tych funkcji: uint_t typu, który przechowuje szerokość formatu i zwraca najwęższą z nich int_t (w przypadku podpisu) typ uint_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 typ uint_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 lub
  • sll – 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.


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.