Tutorial decoder instruksi biner

Tujuan tutorial ini adalah:

  • Mempelajari struktur dan sintaksis file deskripsi format biner.
  • Pelajari bagaimana deskripsi format biner sesuai dengan deskripsi ISA.
  • Menulis deskripsi biner untuk instruksi RiscV RV32I.

Ringkasan

Encoding petunjuk biner RiscV

Pengkodean instruksi biner adalah cara standar untuk mengenkode instruksi untuk dieksekusi pada mikroprosesor. Mereka biasanya disimpan dalam file yang dapat dieksekusi, biasanya dalam format ELF. Petunjuk dapat berupa lebar tetap atau variabel lebarnya.

Biasanya, instruksi menggunakan satu set format encoding, dengan masing-masing format disesuaikan untuk jenis petunjuk yang dienkode. Misalnya, {i>register-register<i} instruksi dapat menggunakan satu format yang memaksimalkan jumlah opcode yang tersedia, sedangkan instruksi {i>register-immediate<i} menggunakan petunjuk lain yang mengorbankan jumlah opcode yang tersedia untuk meningkatkan ukuran segera yang dapat dienkode. Instruksi cabang dan lompatan hampir selalu menggunakan format yang segera untuk mendukung cabang dengan offset yang lebih besar.

Format instruksi yang digunakan oleh instruksi yang ingin kita dekode di RiscV simulator adalah sebagai berikut:

Format R-Type, digunakan untuk petunjuk pendaftaran-daftar:

31..25 24..20 19..15 14..12 11..7 6..0
7 5 5 3 5 7
func7 rs2 rs1 func3 rd kode operasi

Format I-Type, digunakan untuk instruksi segera pendaftaran, memuat instruksi, dan instruksi jalr, segera dalam 12 bit.

31..20 19..15 14..12 11..7 6..0
12 5 3 5 7
imm12 rs1 func3 rd kode operasi

Format khusus I-Type, digunakan untuk shift dengan instruksi langsung, 5 bit segera:

31..25 24..20 19..15 14..12 11..7 6..0
7 5 5 3 5 7
func7 uimm5 rs1 func3 rd kode operasi

Format U-Type, digunakan untuk instruksi langsung yang panjang (lui, auipc), 20 bit segera:

31..12 11..7 6..0
20 5 7
uimm20 rd kode operasi

Format B-Type, digunakan untuk cabang bersyarat, segera 12-bit.

31 30..25 24..20 19..15 14..12 11..8 7 6..0
1 6 5 5 3 4 1 7
imm imm rs2 rs1 func3 imm imm kode operasi

Format J-Type, digunakan untuk instruksi jal, 20 bit langsung.

31 30..21 20 19..12 11..7 6..0
1 10 1 8 5 7
imm imm imm imm rd kode operasi

Format S-Type, digunakan untuk instruksi penyimpanan, 12 bit seketika.

31..25 24..20 19..15 14..12 11..7 6..0
7 5 5 3 5 7
imm rs2 rs1 func3 imm kode operasi

Seperti yang Anda lihat dari format-format ini, semua instruksi ini panjangnya 32 bit, dan 7 bit rendah dalam setiap format adalah isian {i>opcode<i}. Perhatikan juga bahwa meskipun beberapa format memiliki ukuran instan yang sama, bitnya diambil dari bagian-bagian yang berbeda dari instruksi. Seperti yang akan kita lihat, decoder biner dapat menyatakan hal ini.

Deskripsi encoding biner

Pengkodean biner instruksi dinyatakan dalam format biner (.bin_fmt). Ini menjelaskan pengkodean biner dari instruksi dalam ISA sehingga decoder instruksi format biner dapat dibuat. Decoder yang dihasilkan menentukan opcode, mengekstrak nilai bidang operand dan langsung, untuk memberikan informasi yang dibutuhkan oleh ISA decoder agnostik encoding yang dijelaskan dalam tutorial sebelumnya.

Dalam tutorial ini kita akan menulis file deskripsi pengkodean biner untuk subset instruksi RiscV32I yang diperlukan untuk menyimulasikan instruksi yang digunakan dalam "Halo Dunia" kecil program ini. Untuk detail selengkapnya tentang RiscV ISA, lihat Spesifikasi Risc-V{.external}.

Mulailah dengan membuka file: riscv_bin_decoder/riscv32i.bin_fmt.

Isi file dibagi menjadi beberapa bagian.

Pertama, definisi 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;
};

Definisi decoder kami menentukan nama decoder RiscV32I, serta empat informasi tambahan. Yang pertama adalah namespace, yang menentukan namespace tempat kode yang dihasilkan akan ditempatkan. Kedua, opcode_enum, yang menyebutkan cara jenis enumerasi opcode yang dihasilkan oleh decoder ISA harus direferensikan dalam kode yang dihasilkan. Ketiga, includes {} menentukan penyertaan file yang diperlukan oleh kode yang dibuat untuk decoder ini. Dalam kasus kami, ini adalah file yang dihasilkan oleh decoder ISA dari tutorial sebelumnya. File penyertaan tambahan dapat ditentukan di includes {} dengan cakupan global definisi. Ini berguna jika beberapa decoder ditentukan, dan semuanya membutuhkan untuk menyertakan beberapa file yang sama. Keempat adalah daftar nama-nama instruksi grup yang membentuk instruksi untuk membuat decoder. Di jika hanya ada satu: RiscVInst32.

Selanjutnya ada tiga definisi format. Keduanya mewakili instruksi yang berbeda format untuk kata instruksi 32-bit yang digunakan oleh instruksi yang sudah ditentukan dalam file.

// 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];
};

Yang pertama menentukan format petunjuk selebar 32 bit bernama Inst32Format yang memiliki dua kolom: bits (lebar 25 bit) dan opcode (lebar 7 bit). Setiap {i>field<i} memiliki unsigned, yang berarti bahwa nilai akan diperluas dari nol saat nilai tersebut diekstrak dan ditempatkan dalam tipe integer C++. Jumlah lebar bitfield harus sama dengan lebar format. Alat ini akan menghasilkan pesan {i>error<i} jika ada perbedaan pendapat. Format ini tidak berasal dari format lain, sehingga dianggap sebagai format tingkat atas.

Yang kedua menentukan format petunjuk selebar 32 bit bernama IType yang memperoleh dari Inst32Format, sehingga kedua format tersebut terkait. Formatnya berisi 5 kolom: imm12, rs1, func3, rd, dan opcode. Kolom imm12 signed, yang berarti nilai tersebut akan di-sign-extended saat nilainya diekstrak dan ditempatkan dalam tipe integer C++. Perhatikan bahwa IType.opcode keduanya memiliki atribut bertanda tangan/tidak bertanda tangan yang sama dan mengacu pada bit kata instruksi yang sama sebagai Inst32Format.opcode.

Format ketiga adalah format kustom yang hanya digunakan oleh fence instruksi, yang merupakan instruksi yang sudah ditentukan dan kita tidak memiliki Anda khawatirkan dalam tutorial ini.

Poin utama: Gunakan kembali nama kolom dalam berbagai format terkait selama nama tersebut mewakili bit yang sama dan memiliki atribut bertanda tangan/tidak bertanda tangan yang sama.

Setelah definisi format di riscv32i.bin_fmt muncullah grup petunjuk definisi. Semua instruksi dalam grup instruksi harus memiliki bit panjang, dan menggunakan format yang berasal (mungkin secara tidak langsung) dari sumber format petunjuk tingkat atas. Ketika ISA dapat memiliki instruksi dengan panjang, grup instruksi yang berbeda digunakan untuk setiap panjang. Selain itu, jika dekode ISA target bergantung pada mode eksekusi, seperti Arm vs. Thumb instruksi terpisah diperlukan untuk setiap mode. Tujuan Parser bin_fmt menghasilkan decoder biner untuk setiap grup petunjuk.

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;
};

Grup petunjuk menentukan nama RiscV32I, lebar [32], nama jenis enumerasi opcode untuk menggunakan "OpcodeEnum", dan petunjuk dasar format font. Jenis enumerasi opcode harus sama dengan yang dihasilkan oleh decoder instruksi independen format yang dibahas dalam tutorial tentang ISA decoder.

Setiap deskripsi encoding petunjuk terdiri dari 3 bagian:

  • Nama opcode, yang harus sama dengan yang digunakan dalam petunjuk deskripsi decoder agar keduanya dapat bekerja sama.
  • Format petunjuk yang akan digunakan untuk opcode. Inilah format yang digunakan untuk memenuhi referensi ke bitfield di bagian akhir.
  • Daftar batasan kolom bit yang dipisahkan koma, ==, !=, <, <=, >, dan >= bahwa semua harus benar agar kode operasi berhasil mencocokkan dengan kata instruksi.

Parser .bin_fmt menggunakan semua informasi ini untuk membuat decoder yang:

  • Menyediakan fungsi ekstraksi (bertanda/tidak bertanda tangan) yang sesuai untuk setiap bit di setiap format. Fungsi ekstraktor ditempatkan di namespace yang dinamakan oleh versi {i> snake-case<i} dari nama format. Misalnya, fungsi ekstraktor untuk format IType ditempatkan dalam namespace i_type. Setiap fungsi ekstraktor dideklarasikan sebagai inline, mengambil uint_t terkecil jenis yang memiliki lebar format, dan menampilkan int_t tersempit (untuk jenis yang ditandatangani), uint_t (untuk yang tidak ditandatangani), yang menyimpan kolom yang diekstrak lebarnya. Mis.:
inline uint8_t ExtractOpcode(uint32_t value) {
  return value & 0x7f;
}
  • Fungsi dekode untuk setiap grup petunjuk. Metode ini mengembalikan nilai jenis OpcodeEnum, dan mengambil jenis uint_t tersempit yang memiliki lebar format grup instruksi.

Menjalankan build awal

Ubah direktori ke riscv_bin_decoder dan build project menggunakan perintah berikut:

$ cd riscv_bin_decoder
$ bazel build :all

Sekarang ubah direktori Anda kembali ke root repositori, lalu mari kita lihat pada sumber yang dibuat. Untuk itu, ubah direktori ke bazel-out/k8-fastbuild/bin/riscv_bin_decoder (dengan asumsi Anda menggunakan x86 {i>host<i} - untuk {i>host<i} lain, k8-fastbuild akan menjadi string lain).

$ cd ..
$ cd bazel-out/k8-fastbuild/bin/riscv_bin_decoder
  • riscv32i_bin_decoder.h
  • riscv32i_bin_decoder.cc

File header yang dihasilkan (.h)

Buka riscv32i_bin_decoder.h. Bagian pertama dari file berisi daftar perlindungan boilerplate, termasuk file, deklarasi namespace. Setelah itu terdapat fungsi bantuan dengan template dalam namespace internal. Fungsi ini digunakan untuk mengekstrak {i>field<i} bit dari format yang terlalu panjang untuk muat dalam C++ 64-bit bilangan bulat.

#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

Setelah bagian awal, terdapat kumpulan tiga namespace, masing-masing dari deklarasi format dalam file riscv32i.bin_fmt:


namespace fence {

...

}  // namespace fence

namespace i_type {

...

}  // namespace i_type

namespace inst32_format {

...

}  // namespace inst32_format

Dalam setiap namespace ini, fungsi ekstraksi bitfield inline untuk setiap isian bit dalam format itu telah ditentukan. Selain itu, format dasar duplikasi fungsi ekstraksi dari format turunan yang 1) nama {i>field<i} hanya muncul dalam satu nama {i>field<i}, atau 2) yang nama {i>field<i} mengacu pada jenis {i>field<i} yang sama (posisi bertanda/tidak bertanda tangan dan bit) dalam setiap format kemunculannya. Hal ini memungkinkan isian bit{i> <i} yang mendeskripsikan bit yang akan diekstrak menggunakan fungsi dalam namespace format tingkat atas.

Fungsi dalam namespace i_type ditampilkan di bawah ini:

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

Terakhir, deklarasi fungsi dari fungsi decoder untuk instruksi grup RiscVInst32 dideklarasikan. Dibutuhkan 32 bit tanpa tandatangan sebagai nilai dari kata petunjuk dan menampilkan anggota class enumerasi OpcodeEnum yang cocok, atau OpcodeEnum::kNone jika tidak ada kecocokan.

OpcodeEnum DecodeRiscVInst32(uint32_t inst_word);

File Sumber yang dihasilkan (.cc)

Sekarang buka riscv32i_bin_decoder.cc. Bagian pertama dari {i>file<i} berisi deklarasi #include dan namespace, diikuti dengan fungsi decoder deklarasi:

#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);

DecodeRiscVInst32None digunakan untuk tindakan dekode kosong, yaitu yang yang menampilkan OpcodeEnum::kNone. Tiga fungsi lainnya membentuk decoder yang dihasilkan. Keseluruhan decoder bekerja secara hierarkis. Satu set bit dalam kata instruksi dihitung untuk membedakan antara petunjuk atau kelompok petunjuk di tingkat atas. Bit tersebut tidak perlu berdekatan. Jumlah bit menentukan ukuran tabel pemeta yang diisi dengan fungsi decoder tingkat kedua. Hal ini terlihat dalam bagian dari file:

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,

    ...
};

Terakhir, fungsi decoder ditentukan:

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;
}

Dalam hal ini, di mana hanya ada 4 instruksi yang didefinisikan, hanya ada satu tingkat dekode dan tabel pencarian yang sangat renggang. Seperti petunjuk struktur decoder akan berubah dan jumlah level di hierarki tabel decoder akan meningkat.


Tambahkan petunjuk ALU daftar-daftar

Sekarang saatnya menambahkan beberapa petunjuk baru ke file riscv32i.bin_fmt. Tujuan kelompok instruksi pertama adalah instruksi daftar-daftar ALU seperti add, and, dll. Di RiscV32, semuanya menggunakan instruksi biner jenis R format:

31..25 24..20 19..15 14..12 11..7 6..0
7 5 5 3 5 7
func7 rs2 rs1 func3 rd kode operasi

Hal pertama yang perlu kita lakukan adalah menambahkan format. Silakan buka riscv32i.bin_fmt di editor favorit Anda. Tepat setelah Inst32Format memungkinkan tambahkan format bernama RType yang berasal dari Inst32Format. Semua bidang bit dalam RType adalah unsigned. Gunakan nama, lebar bit, dan urutan (kiri ke kanan) dari tabel di atas untuk menentukan format. Jika Anda butuh petunjuk, atau ingin melihat solusi lengkap, klik di sini.

Selanjutnya, kita perlu menambahkan instruksi. Petunjuknya adalah:

  • add - penambahan bilangan bulat.
  • and - bitwise dan.
  • or - bitwise atau.
  • sll - geser ke kiri logis.
  • sltu - menetapkan lebih kecil dari, tidak ditandatangani.
  • sub - pengurangan bilangan bulat.
  • xor - bitwise xor.

Encodingnya adalah:

31..25 24..20 19..15 14..12 11..7 6..0 nama opcode
000 0000 rs2 rs1 000 rd 011 0011 tambahkan
000 0000 rs2 rs1 111 rd 011 0011 dan
000 0000 rs2 rs1 110 rd 011 0011 atau
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 sub
000 0000 rs2 rs1 100 rd 011 0011 Xor
func7 func3 kode operasi

Tambahkan definisi petunjuk ini sebelum instruksi lain dalam Grup petunjuk RiscVInst32. String biner ditentukan dengan awalan awalan 0b (mirip dengan 0x untuk angka heksadesimal). Untuk memudahkan membaca string digit biner yang panjang, Anda juga dapat memasukkan tanda kutip tunggal ' sebagai pemisah digit sesuai pilihan Anda.

Masing-masing definisi petunjuk ini akan memiliki tiga batasan, yaitu pada func7, func3, dan opcode. Untuk semua kecuali sub, batasan func7 akan menjadi:

func7 == 0b000'0000

Batasan func3 bervariasi di sebagian besar petunjuk. Untuk add dan sub itu:

func3 == 0b000

Batasan opcode untuk setiap petunjuk ini sama:

opcode == 0b011'0011

Jangan lupa untuk mengakhiri setiap baris dengan titik koma ;.

Solusi akhirnya adalah di sini.

Sekarang lanjutkan dan bangun project Anda seperti sebelumnya, lalu buka project File riscv32i_bin_decoder.cc. Anda akan melihat bahwa fungsi decoder tambahan dibuat untuk menangani instruksi baru. Sebagian besar mirip dengan yang telah dibuat sebelumnya. Namun, perhatikan DecodeRiscVInst32_0_c yang digunakan untuk dekode 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];
}

Dalam fungsi ini, ada tabel dekode statis yang dihasilkan, dan nilai pencariannya yang diekstrak dari kata instruksi untuk memilih indeks yang sesuai. Tindakan ini menambahkan lapisan kedua dalam hierarki decoder instruksi, tetapi karena opcode dapat berupa mencari langsung dalam tabel tanpa perbandingan lebih lanjut, ini adalah contoh fungsi yang merupakan kebalikan dari memerlukan panggilan fungsi lain.


Tambahkan petunjuk ALU dengan langsung

Rangkaian petunjuk berikutnya yang akan kita tambahkan adalah instruksi ALU yang menggunakan nilai langsung alih-alih salah satu register. Ada tiga kelompok instruksi (berdasarkan bidang langsung): instruksi langsung I-Type dengan segera ditandatangani 12 bit, instruksi langsung I-Type khusus untuk shift, dan jenis U-Type langsung, dengan nilai langsung 20-bit yang tidak ditandatangani. Format tersebut ditunjukkan di bawah ini:

Format langsung I-Type:

31..20 19..15 14..12 11..7 6..0
12 5 3 5 7
imm12 rs1 func3 rd kode operasi

Format langsung I-Type khusus:

31..25 24..20 19..15 14..12 11..7 6..0
7 5 5 3 5 7
func7 uimm5 rs1 func3 rd kode operasi

Format langsung U-Type:

31..12 11..7 6..0
20 5 7
uimm20 rd kode operasi

Format I-Type sudah ada di riscv32i.bin_fmt, jadi tidak perlu menambahkan format tersebut.

Jika kita membandingkan format I-Type khusus dengan format R-Type yang kita tentukan latihan sebelumnya, kita melihat bahwa satu-satunya perbedaan adalah kolom rs2 diganti namanya menjadi uimm5. Daripada menambahkan format baru, kita dapat meningkatkan format R-Type. Kita tidak dapat menambahkan isian lain, karena itu akan menambah lebar formatnya, tetapi kita dapat menambahkan overlay. Overlay adalah alias untuk serangkaian bit dalam formatnya, dan dapat digunakan untuk menggabungkan beberapa suburutan dari ke dalam entitas bernama terpisah. Efek sampingnya adalah kode yang dihasilkan kini juga akan menyertakan fungsi ekstraksi untuk overlay, selain {i>field <i}untuk {i>field <i}itu. Dalam hal ini, jika rs2 dan uimm5 tidak ditandatangani, tidak membuat banyak perbedaan kecuali untuk membuatnya eksplisit bahwa {i>field<i} tersebut digunakan secara langsung. Untuk menambahkan overlay bernama uimm5 ke format R-Type, tambahkan setelah kolom terakhir:

  overlays:
    unsigned uimm5[5] = rs2;

Satu-satunya format baru yang perlu kita tambahkan adalah format U-Type. Sebelum kita menambahkan mari kita pertimbangkan dua petunjuk yang menggunakan format tersebut: auipc dan lui. Keduanya menggeser nilai langsung 20-bit yang tersisa sebesar 12 sebelum menggunakannya untuk menambahkan PC ke dalamnya (auipc) atau menulisnya langsung ke register (lui). Dengan menggunakan {i>overlay<i}, kita dapat menyediakan versi {i>pre-shifted<i} dari mengalihkan sedikit komputasi dari eksekusi instruksi ke instruksi melakukan dekode. Pertama, tambahkan format sesuai dengan kolom yang ditentukan dalam tabel di atas. Kemudian, kita dapat menambahkan overlay berikut:

  overlays:
    unsigned uimm32[32] = uimm20, 0b0000'0000'0000;

{i>Syntax<i} overlay memungkinkan kita menggabungkan, tidak hanya {i>field<i}, tetapi juga literal seperti ya. Dalam hal ini kita menggabungkannya dengan 12 angka nol, yang efeknya menggeser ke kiri dengan 12 tahun.

Instruksi I-Type yang perlu kita tambahkan adalah:

  • addi - Tambahkan langsung.
  • andi - Bitwise dan dengan segera.
  • ori - Bitwise atau dengan segera.
  • xori - Bitwise xor dengan segera.

Encodingnya adalah:

31..20 19..15 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 kode operasi

Instruksi {i>R-Type<i} (I-Type khusus) yang perlu kita tambahkan adalah:

  • slli - Beralih ke logika kiri dengan segera.
  • srai - Menggeser aritmetika kanan dengan seketika.
  • srli - Beralih ke logika kanan secara langsung.

Encodingnya adalah:

31..25 24..20 19..15 14..12 11..7 6..0 nama opcode
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 kode operasi

Instruksi U-Type yang perlu kita tambahkan adalah:

  • auipc - Menambahkan bagian atas segera ke pc.
  • lui - Memuat langsung bagian atas.

Encodingnya adalah:

31..12 11..7 6..0 nama opcode
uimm20 rd 001 0111 auipc
uimm20 rd 011 0111 lui
kode operasi

Lanjutkan membuat perubahan, lalu bangun. Periksa output yang dihasilkan. Hanya seperti sebelumnya, Anda dapat memeriksa pekerjaan Anda dengan riscv32i.bin_fmt.


Rangkaian petunjuk berikutnya yang perlu ditentukan adalah cabang bersyarat instruksi {i> jump-and-link<i}, dan register lompat dan tautan, instruksi.

Cabang bersyarat yang kita tambahkan semuanya menggunakan encoding Jenis B.

31..25 24..20 19..15 14..12 11..7 6..0
7 5 5 3 5 7
imm7 rs2 rs1 func3 imm5 kode operasi

Meskipun pengkodean Tipe-B identik dalam tata letak dengan pengkodean {i>R-Type<i}, kita memilih untuk menggunakan jenis format baru agar selaras dengan dokumentasi RiscV. Tetapi Anda juga dapat menambahkan overlay untuk mendapatkan cabang yang sesuai perpindahan langsung, menggunakan kolom func7 dan rd dari R-Type encoding.

Menambahkan format BType dengan kolom seperti yang ditentukan di atas diperlukan, tetapi tidak memadai. Seperti yang dapat Anda lihat, proses langsung dibagi menjadi dua kolom petunjuk. Selain itu, instruksi cabang tidak memperlakukan ini sebagai penyambungan sederhana dari kedua {i>field<i} tersebut. Sebaliknya, setiap isian dipartisi lebih lanjut, dan partisi-partisi ini digabungkan dalam urutan yang berbeda. Akhirnya, nilai itu digeser ke kiri sebesar satu untuk mendapatkan offset sejajar 16-bit.

Urutan bit dalam kata instruksi yang digunakan untuk membentuk segera adalah: 31, 7, 30..25, 11..8. Hal ini sesuai dengan referensi sub-bidang berikut, di mana indeks atau rentang menentukan bit dalam ruang isian, diberi nomor dari kanan ke kiri, yaitu imm7[6] mengacu pada msb dari imm7, dan imm5[0] mengacu pada lsb dari imm5.

imm7[6], imm5[0], imm7[5..0], imm5[4..1]

Membuat manipulasi bit ini menjadi bagian dari instruksi cabang itu sendiri memiliki dua kelemahan besar. Pertama, model ini mengaitkan implementasi fungsi semantik ke detail dalam representasi instruksi biner. Kedua, penambahan waktu proses overhead. Jawabannya adalah menambahkan overlay ke format BType, termasuk akhir '0' untuk memperhitungkan pergeseran kiri.

  overlays:
    signed b_imm[13] = imm7[6], imm5[0], imm7[5..0], imm5[4..1], 0b0;

Perhatikan bahwa overlay ditandatangani, sehingga akan otomatis ditandatangani juga bila diekstrak dari kata instruksi.

Instruksi lompat dan tautkan (langsung) menggunakan encoding Jenis J:

31..12 11..7 6..0
20 5 7
imm20 rd kode operasi

Ini juga merupakan format yang mudah untuk ditambahkan, tetapi sekali lagi, format langsung yang digunakan oleh instruksinya tidak semudah yang terlihat. Urutan bit yang digunakan untuk membentuk langsung penuh adalah: 31, 19..12, 20, 30..21, dan yang digeser ke kiri sebanyak satu untuk penyelarasan setengah kata. Solusinya adalah dengan menambahkan overlay (21 bit untuk memperhitungkan pergeseran kiri) ke format:

  overlays:
    signed j_imm[21] = imm20[19, 7..0, 8, 18..9], 0b0;

Seperti yang dapat Anda lihat, sintaksis untuk overlay mendukung penetapan beberapa rentang {i>field<i} dalam format singkatan. Selain itu, jika tidak ada nama {i>field<i} yang digunakan, bit angka mengacu pada kata instruksi itu sendiri, jadi hal di atas bisa saja ditulis sebagai:

    signed j_imm[21] = [31, 19..12, 20, 30..21], 0b0;

Terakhir, jump-and-link (register) menggunakan format tipe I seperti yang digunakan seperti yang telah dibahas sebelumnya.

Format langsung I-Type:

31..20 19..15 14..12 11..7 6..0
12 5 3 5 7
imm12 rs1 func3 rd kode operasi

Kali ini, tidak ada perubahan yang harus dilakukan pada format.

Instruksi cabang yang perlu kita tambahkan adalah:

  • beq - Cabang jika sama.
  • bge - Cabang jika lebih besar dari atau sama dengan.
  • bgeu - Cabang jika lebih besar dari atau sama tidak ditandatangani.
  • blt - Cabang jika kurang dari.
  • bltu - Cabang jika kurang dari tidak ditandatangani.
  • bne - Cabang jika tidak sama.

Kolom tersebut dienkode sebagai berikut:

31..25 24..20 19..15 14..12 11..7 6..0 nama opcode
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 kode operasi

Instruksi jal dienkode sebagai berikut:

31..12 11..7 6..0 nama opcode
imm20 rd 110 1111 Jal
kode operasi

Instruksi jalr dienkode sebagai berikut:

31..20 19..15 14..12 11..7 6..0 opcode_name
imm12 rs1 000 rd 110 0111 Jalr
func3 kode operasi

Lanjutkan membuat perubahan, lalu bangun. Periksa output yang dihasilkan. Hanya seperti sebelumnya, Anda dapat memeriksa pekerjaan Anda dengan riscv32i.bin_fmt.


Tambahkan petunjuk toko

Petunjuk penyimpanan menggunakan encoding S-Type, yang identik dengan B-Type pengkodean yang digunakan oleh instruksi cabang, kecuali untuk komposisi secara langsung. Kita memilih untuk menambahkan format SType agar tetap selaras dengan RiscV dokumentasi tambahan.

31..25 24..20 19..15 14..12 11..7 6..0
7 5 5 3 5 7
imm7 rs2 rs1 func3 imm5 kode operasi

Dalam kasus format SType, item langsung untungnya berupa straight sambungan maju dari dua bidang langsung, sehingga spesifikasi overlay sederhana adalah:

  overlays:
    signed s_imm[12] = imm7, imm5;

Perhatikan bahwa penentu rentang bit tidak diperlukan saat menggabungkan seluruh kolom.

Petunjuk penyimpanan dienkode sebagai berikut:

31..25 24..20 19..15 14..12 11..7 6..0 nama opcode
imm7 rs2 rs1 000 imm5 010 0011 sb
imm7 rs2 rs1 001 imm5 010 0011 sh
imm7 rs2 rs1 010 imm5 010 0011 sw
func3 kode operasi

Lanjutkan membuat perubahan, lalu bangun. Periksa output yang dihasilkan. Hanya seperti sebelumnya, Anda dapat memeriksa pekerjaan Anda dengan riscv32i.bin_fmt.


Tambahkan petunjuk pemuatan

Petunjuk pemuatan menggunakan format I-Type. Tidak ada perubahan yang harus dilakukan di sana.

Encoding:

31..20 19..15 14..12 11..7 6..0 opcode_name
imm12 rs1 000 rd 000 0011 lb
imm12 rs1 100 rd 000 0011 {i>lbu<i}
imm12 rs1 001 rd 000 0011 hh
imm12 rs1 101 rd 000 0011 Lhu
imm12 rs1 010 rd 000 0011 lw
func3 kode operasi

Lanjutkan membuat perubahan, lalu bangun. Periksa output yang dihasilkan. Hanya seperti sebelumnya, Anda dapat memeriksa pekerjaan Anda dengan riscv32i.bin_fmt.

Demikianlah tutorial ini, semoga tutorial ini bermanfaat.