Decoder RiscV ISA

Tujuan tutorial ini adalah:

  • Pelajari bagaimana instruksi direpresentasikan dalam simulator MPACT-Sim.
  • Pelajari struktur dan sintaksis file deskripsi ISA.
  • Menulis deskripsi ISA untuk subset instruksi RiscV RV32I

Ringkasan

Dalam MPACT-Sim, instruksi target didekode dan disimpan dalam untuk membuat informasi mereka lebih tersedia dan semantik lebih cepat untuk dieksekusi. Instance instruksi ini di-cache dalam instruksi cache sehingga mengurangi berapa kali instruksi yang sering dieksekusi telah dijalankan.

Class instruksi

Sebelum kita mulai, ada baiknya untuk melihat sedikit bagaimana instruksi yang direpresentasikan dalam MPACT-Sim. Class Instruction ditentukan di mpact-sim/mpact/sim/generic/instruction.h.

Instance class Instruksi berisi semua informasi yang diperlukan untuk simulasikan petunjuk saat "dijalankan", seperti:

  1. Alamat petunjuk, ukuran petunjuk yang disimulasikan, yaitu ukuran dalam .text.
  2. Opcode instruksi.
  3. Predikat pointer antarmuka operand (jika ada).
  4. Vektor pointer antarmuka operand sumber.
  5. Vektor pointer antarmuka operand tujuan.
  6. Fungsi semantik callable.
  7. Pointer ke objek status arsitektur.
  8. Pointer ke objek konteks.
  9. Pointer ke turunan dan instance Petunjuk berikutnya.
  10. String pembongkaran.

Instance ini umumnya disimpan dalam cache petunjuk (instance), dan digunakan kembali setiap kali instruksi dijalankan kembali. Hal ini meningkatkan performa selama runtime.

Kecuali untuk pointer ke objek konteks, semua diisi oleh decoder instruksi yang dihasilkan dari deskripsi ISA. Untuk ini penting untuk mengetahui detail dari item ini karena kami tidak menggunakannya secara langsung. Sebagai gantinya, pemahaman tingkat tinggi tentang bagaimana mereka digunakan adalah memadai.

Fungsi semantik callable adalah objek fungsi/metode/fungsi C++ (termasuk lambda) yang mengimplementasikan semantik petunjuk. Sebagai Untuk instruksi add, kode tersebut memuat setiap operand sumber, menambahkan dua operand, dan menulis hasilnya ke satu operand tujuan. Topik dari fungsi semantik dibahas secara mendalam di tutorial fungsi semantik.

Operand petunjuk

Class petunjuk menyertakan pointer ke tiga jenis antarmuka operand: predikat, sumber, dan tujuan. Antarmuka ini memungkinkan fungsi semantik untuk ditulis secara terpisah dari jenis aktual instruksi dasar operand. Misalnya, mengakses nilai-nilai register dan segera dilakukan melalui antarmuka yang sama. Ini berarti bahwa instruksi yang melakukan tetapi pada operand yang berbeda (mis., register vs immedates) bisa diimplementasikan menggunakan fungsi semantik yang sama.

Antarmuka operand predikat, untuk ISA yang mendukung resource eksekusi instruksi (untuk ISA lainnya adalah null), digunakan untuk menentukan apakah instruksi yang diberikan harus dieksekusi berdasarkan nilai boolean predikat.

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

Antarmuka operand sumber memungkinkan fungsi semantik instruksi untuk membaca nilai dari operand petunjuk tanpa mempedulikan operand yang mendasarinya . Metode antarmuka mendukung operand bernilai skalar dan vektor.

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

Antarmuka operand tujuan menyediakan metode untuk mengalokasikan dan menangani Instance DataBuffer (tipe data internal yang digunakan untuk menyimpan nilai register). J operand tujuan juga memiliki latensi yang terkait dengannya, yang merupakan jumlah siklus untuk menunggu hingga instance buffer data dialokasikan oleh instruksi fungsi semantik digunakan untuk memperbarui nilai register target. Sebagai Sebagai contoh, latensi instruksi add mungkin 1, sedangkan untuk mpy instruksi, mungkin 4. Hal ini dibahas secara lebih mendetail dalam tentang fungsi semantik.

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

Deskripsi ISA

ISA (Instruction Set Architecture) prosesor menentukan model abstrak di mana perangkat lunak berinteraksi dengan perangkat kerasnya. Mendefinisikan set yang tersedia, tipe data, register, dan komputer lain menyatakan instruksi beroperasi, serta perilakunya (semantik). Untuk tujuan dari MPACT-Sim, ISA tidak menyertakan pengkodean instruksi yang sebenarnya. Hal ini diperlakukan secara terpisah.

ISA prosesor dinyatakan dalam file deskripsi yang menjelaskan set instruksi pada level abstrak, encoding agnostik. File deskripsi menghitung set instruksi yang tersedia. Untuk setiap instruksi, diperlukan wajib untuk mencantumkan namanya, jumlah dan nama operand-nya, mengikat ke fungsi C++/callable yang mengimplementasikan semantiknya. Selain itu, kita dapat menentukan {i>string<i} pemformatan pembongkaran, dan penggunaan instruksi dari nama sumber daya perangkat keras. Model pertama berguna untuk membuat representasi dari instruksi untuk debug, pelacakan, atau penggunaan interaktif. Tujuan dapat digunakan untuk meningkatkan akurasi siklus dalam simulasi.

File deskripsi ISA diuraikan oleh isa-parser yang menghasilkan kode untuk decoder instruksi agnostik representasi. Decoder ini bertanggung jawab untuk mengisi kolom objek instruksi. Nilai-nilai yang spesifik, misalnya nomor pendaftaran tujuan, diperoleh dari instruksi format tertentu decoder. Salah satu decoder tersebut adalah decoder biner, yang merupakan fokus dari tutorial berikutnya.

Tutorial ini membahas cara menulis file deskripsi ISA untuk skalar sederhana tentang arsitektur ini. Kita akan menggunakan subset dari set instruksi RiscV RV32I untuk menggambarkan hal ini, dan bersama dengan tutorial lainnya, membangun simulator yang mampu dari simulasi "Hello World" (Halo Dunia) program ini. Untuk detail selengkapnya tentang RiscV ISA, lihat Spesifikasi Risc-V.

Mulailah dengan membuka file: riscv_isa_decoder/riscv32i.isa

Isi file dibagi menjadi beberapa bagian. Pertama adalah ISA pernyataan:

isa RiscV32I {
  namespace mpact::sim::codelab;
  slots { riscv32; }
}

Perintah ini mendeklarasikan RiscV32I sebagai nama ISA dan pembuat kode akan buat class bernama RiscV32IEncodingBase yang menentukan antarmuka yang akan digunakan decoder untuk mendapatkan informasi opcode dan operand. Nama class ini dibuat dengan mengonversi nama ISA menjadi Pascal-case, lalu menggabungkannya dengan EncodingBase. Pernyataan slots { riscv32; } menentukan bahwa hanya ada satu slot instruksi riscv32 di RiscV32I ISA (bukan beberapa slot dalam instruksi VLIW), dan bahwa satu-satunya instruksi yang valid adalah instruksi yang ditetapkan untuk dieksekusi dalam riscv32.

// First disasm fragment is 15 char wide and left justified.
disasm widths = {-15};

Ini menentukan bahwa fragmen pembongkaran pertama dari setiap spesifikasi (lihat lebih lanjut di bawah), akan diratakan sepanjang 15 karakter bidang yang luas. Fragmen berikutnya akan ditambahkan ke kolom ini tanpa spasi tambahan apa pun.

Di bawahnya ada tiga deklarasi slot: riscv32i, zicsr, dan riscv32. Berdasarkan definisi isa di atas, hanya petunjuk yang ditentukan untuk riscv32 akan menjadi bagian dari isa RiscV32I. Untuk apa dua slot lainnya?

Slot dapat digunakan untuk memfaktorkan instruksi ke dalam kelompok terpisah, yang kemudian dapat digabungkan menjadi satu slot di akhir. Perhatikan notasi : riscv32i, zicsr di deklarasi slot riscv32. Ini menentukan bahwa slot riscv32 mewarisi semua instruksi yang ditetapkan dalam slot zicsr dan riscv32i. ISA 32 bit RiscV terdiri dari ISA dasar yang disebut RV32I, di mana satu set ekstensi opsional akan ditambahkan. Mekanisme slot memungkinkan instruksi dalam ekstensi ini untuk ditentukan secara terpisah, lalu digabungkan sesuai kebutuhan untuk menentukan secara keseluruhan. Dalam hal ini, instruksi di RiscV 'I' grup ditentukan secara terpisah dari yang ada di 'zicsr' ras. Grup tambahan dapat ditentukan untuk 'M' (kalikan/bagi), 'F' (floating point presisi tunggal), 'D' (floating point presisi ganda), 'C' (petunjuk 16-bit ringkas) dll. sebagai yang diperlukan untuk RiscV ISA akhir yang diinginkan.

// The RiscV 'I' instructions.
slot riscv32i {
  ...
}

// RiscV32 CSR manipulation instructions.
slot zicsr {
  ...
}

// The final instruction set combines riscv32i and zicsr.
slot riscv32 : riscv32i, zicsr {
  ...
}

Definisi slot zicsr dan riscv32 tidak perlu diubah. Namun fokus pada tutorial ini adalah menambahkan definisi yang diperlukan ke riscv32i slot waktu. Mari kita lihat lebih dekat apa yang saat ini didefinisikan dalam slot ini:

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

Pertama, ada bagian includes {} yang mencantumkan file header yang memerlukan untuk disertakan dalam kode yang dihasilkan saat slot ini direferensikan, secara langsung atau secara tidak langsung, dalam ISA akhir. File sertakan juga dapat dicantumkan dalam dengan cakupan includes {}, yang dalam hal ini akan selalu disertakan. Hal ini dapat berguna jika file include yang sama harus ditambahkan ke setiap slot definisi.

Deklarasi default size dan default latency menentukan hal itu, kecuali jika tidak ditentukan, ukuran instruksinya adalah 4, dan bahwa latensi dari operasi tulis operand tujuan adalah 0 siklus. Catatan, ukuran petunjuk yang ditentukan di sini, adalah ukuran pertambahan penghitung program untuk menghitung alamat instruksi berurutan berikutnya untuk dijalankan dalam simulasi prosesor. Ini mungkin sama atau tidak sama dengan ukuran dalam byte representasi petunjuk dalam file input yang dapat dieksekusi.

Bagian tengah dari definisi slot adalah bagian opcode. Seperti yang terlihat, hanya dua opcode (petunjuk) fence dan ebreak telah ditentukan sejauh ini di riscv32i. Opcode fence ditentukan dengan menentukan nama (fence) dan spesifikasi operand ({: imm12 : }), diikuti dengan pembongkaran opsional format ("fence"), dan callable yang akan terikat sebagai semantik fungsi ("&RV32IFence").

Operand petunjuk ditetapkan sebagai triple, dengan setiap komponen dipisahkan dengan titik koma, predikat ':' daftar operand sumber ':' daftar operand tujuan. Daftar operand sumber dan tujuan adalah koma daftar nama operand yang terpisah. Seperti yang dapat Anda lihat, operand instruksi untuk instruksi fence berisi, tanpa operand predikat, hanya satu sumber nama operand imm12, dan tidak ada operand tujuan. Subset RiscV RV32I melakukan tidak mendukung eksekusi berpredikat, sehingga operand predikat akan selalu kosong dalam tutorial ini.

Fungsi semantik ditetapkan sebagai string yang diperlukan untuk menentukan C++ {i>function<i} atau {i>callable<i} yang akan digunakan untuk memanggil fungsi semantik. Tanda tangan fungsi semantik/callable adalah void(Instruction *).

Spesifikasi pembongkaran terdiri dari daftar string yang dipisahkan koma. Biasanya hanya dua {i>string<i} yang digunakan, satu untuk kode operasi, dan satu untuk operand. Saat diformat (menggunakan panggilan AsString() dalam Petunjuk), masing-masing string diformat dalam kolom sesuai dengan disasm widths spesifikasi yang dijelaskan di atas.

Latihan berikut membantu Anda menambahkan petunjuk ke file riscv32i.isa cukup untuk melakukan simulasi "Hello World" program ini. Bagi mereka yang terburu-buru, solusi dapat ditemukan di riscv32i.isa dan rv32i_instructions.h.


Melakukan Build Awal

Jika Anda belum mengubah direktori ke riscv_isa_decoder, lakukan sekarang. Selanjutnya membangun proyek sebagai berikut - pembangunan ini akan berhasil.

$ cd riscv_isa_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_isa_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_isa_decoder

Dalam direktori ini, di antara file lainnya akan ada hal berikut file C++ yang dihasilkan:

  • riscv32i_decoder.h
  • riscv32i_decoder.cc
  • riscv32i_enums.h
  • riscv32i_enums.cc

Mari kita lihat riscv32i_enums.h dengan mengkliknya di browser. Anda seharusnya lihat bahwa file tersebut berisi sesuatu seperti:

#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

Seperti yang Anda lihat, setiap slot, opcode, dan operand yang didefinisikan dalam File riscv32i.isa ditentukan dalam salah satu jenis enumerasi. Selain itu, ada array OpcodeNames yang menyimpan semua nama opcode (yaitu ditentukan di riscv32i_enums.cc). File lainnya berisi decoder yang dihasilkan, yang akan dibahas lebih lanjut dalam tutorial lain.

Aturan Versi Bazel

Target dekoder ISA di Bazel didefinisikan menggunakan makro aturan khusus yang bernama mpact_isa_decoder, yang dimuat dari mpact/sim/decoder/mpact_sim_isa.bzl di repositori mpact-sim. Untuk tutorial ini, target build yang ditentukan dalam riscv_isa_decoder/BUILD adalah:

mpact_isa_decoder(
    name = "riscv32i_isa",
    src = "riscv32i.isa",
    includes = [],
    isa_name = "RiscV32I",
    deps = [
        "//riscv_semantic_functions:riscv32i",
    ],
)

Aturan ini memanggil alat parser dan generator ISA untuk menghasilkan kode C++, lalu mengompilasi library yang dihasilkan ke library yang dapat diandalkan oleh aturan lain label //riscv_isa_decoder:riscv32i_isa. Bagian includes digunakan untuk menentukan file .isa tambahan yang mungkin disertakan oleh file sumber. Tujuan isa_name digunakan untuk menentukan isa spesifik, yang diperlukan jika lebih dari satu isa yang ditentukan, dalam file sumber yang akan digunakan untuk membuat decoder.


Menambahkan Petunjuk ALU Daftar-Mendaftar

Sekarang saatnya menambahkan beberapa petunjuk baru ke file riscv32i.isa. Yang pertama adalah instruksi ALU register-daftar seperti add, and, dll. Di RiscV32, ini semua menggunakan format instruksi biner tipe R:

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

Meskipun digunakan untuk menghasilkan decoder agnostik format, file .isa tetap berguna untuk mempertimbangkan format biner dan tata letaknya untuk memandu entri. Saat Anda ada tiga kolom yang relevan dengan decoder yang mengisi objek petunjuk: rs2, rs1, dan rd. Pada tahap ini, kita akan memilih untuk menggunakan nama-nama ini untuk register integer yang dienkode dengan cara yang sama (urutan bit), di bidang instruksi yang sama, dalam semua instruksi.

Petunjuk yang akan kita tambahkan adalah sebagai berikut:

  • 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.

Setiap petunjuk ini akan ditambahkan ke bagian opcodes dari Definisi slot riscv32i. Ingat bahwa kita harus menentukan nama, {i>opcode<i}, pembongkaran dan fungsi semantik untuk setiap instruksi. Namanya mudah, mari kita gunakan nama {i>opcode<i} di atas. Juga, mereka semua menggunakan Operand yang sama, jadi kita dapat menggunakan { : rs1, rs2 : rd} untuk spesifikasi operand. Hal ini berarti bahwa operand sumber daftar yang ditentukan oleh rs1 akan memiliki indeks 0 di sumber vektor operand dalam objek instruksi, operand sumber register yang ditetapkan oleh rs2 akan memiliki indeks 1, dan operand tujuan register yang ditentukan oleh rd akan menjadi satu-satunya elemen dalam vektor operand tujuan (pada indeks 0).

Berikutnya adalah spesifikasi fungsi semantik. Hal ini dilakukan dengan menggunakan kata kunci semfunc dan string C++ yang menentukan fungsi callable yang dapat digunakan untuk menetapkan menjadi std::function. Dalam tutorial ini kita akan menggunakan fungsi, sehingga fungsi callable stringnya adalah "&MyFunctionName". Menggunakan skema penamaan yang disarankan oleh Instruksi fence, ini harus berupa "&RV32IAdd", "&RV32IAnd", dll.

Terakhir adalah spesifikasi pembongkaran. Dimulai dengan kata kunci disasm dan diikuti oleh daftar string yang dipisahkan koma yang menentukan cara petunjuk harus dicetak sebagai string. Menggunakan tanda % di depan nama operand menunjukkan substitusi string menggunakan representasi string dari operand itu. Untuk instruksi add, nilainya adalah: disasm: "add", "%rd, %rs1,%rs2". Ini berarti entri untuk instruksi add harus terlihat seperti:

    add{ : rs1, rs2 : rd},
      semfunc: "&RV32IAdd",
      disasm: "add", "%rd, %rs1, %rs2";

Lanjutkan dan edit file riscv32i.isa dan tambahkan semua petunjuk ini ke Deskripsi .isa. Jika Anda membutuhkan bantuan (atau ingin memeriksa pekerjaan Anda), file deskripsi adalah di sini.

Setelah petunjuk ditambahkan ke file riscv32i.isa, petunjuk tersebut akan diperlukan menambahkan deklarasi fungsi untuk masing-masing fungsi semantik baru yang yang direferensikan ke file rv32i_instructions.h yang terletak di `../semantic_functions/. Sekali lagi, jika Anda memerlukan bantuan (atau ingin memeriksa hasil pekerjaan Anda), jawabannya adalah di sini.

Setelah semuanya selesai, lanjutkan dan ubah kembali ke riscv_isa_decoder dan membangun ulang. Jangan ragu untuk memeriksa {i>file<i} sumber yang dihasilkan.


Menambahkan Petunjuk ALU dengan Segera

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

Seperti yang dapat Anda lihat, nama operand rs1 dan rd merujuk pada kolom bit yang sama dengan sebelumnya, dan digunakan untuk merepresentasikan register integer, sehingga nama ini bisa dipertahankan. Isian nilai langsung memiliki panjang dan lokasi yang berbeda, dan dua (uimm5 dan uimm20) tidak ditandatangani, sedangkan imm12 ditandatangani. Masing-masing mereka akan menggunakan namanya sendiri.

Operand untuk petunjuk I-Type harus { : rs1, imm12 :rd }. Untuk petunjuk I-Type khusus, nilainya harus { : rs1, uimm5 : rd}. Spesifikasi operand petunjuk U-Type harus { : uimm20 : rd }.

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.

Instruksi 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.

Instruksi U-Type yang perlu kita tambahkan adalah:

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

Nama-nama yang akan digunakan untuk opcode mengikuti secara alami dari nama instruksi di atas (tidak perlu membuat yang baru - semuanya unik). Jika menyangkut menentukan fungsi semantik, ingat bahwa objek petunjuk mengenkode antarmuka ke operand sumber yang tidak bergantung pada operand yang mendasarinya . Artinya untuk instruksi yang memiliki operasi yang sama, tetapi mungkin berbeda dalam jenis operand, bisa berbagi fungsi semantik yang sama. Contohnya, instruksi addi melakukan operasi yang sama seperti instruksi add jika satu mengabaikan jenis operand, sehingga dapat menggunakan fungsi semantik yang sama spesifikasi "&RV32IAdd". Demikian pula untuk andi, ori, xori, dan slli. Petunjuk lain menggunakan fungsi semantik baru, tetapi harus diberi nama berdasarkan operasi, bukan operand, jadi untuk srai gunakan "&RV32ISra". Tujuan Instruksi U-Type auipc dan lui tidak memiliki padanan pendaftaran, jadi tidak apa-apa untuk menggunakan "&RV32IAuipc" dan "&RV32ILui".

{i>String<i} pembongkaran sangat mirip dengan latihan di latihan sebelumnya, tetapi seperti yang Anda harapkan, referensi ke %rs2 diganti dengan %imm12, %uimm5, atau %uimm20, sebagaimana diperlukan.

Lanjutkan membuat perubahan, lalu bangun. Periksa output yang dihasilkan. Sama seperti sebelumnya, Anda dapat memeriksa pekerjaan Anda dengan riscv32i.isa dan rv32i_instructions.h.


Petunjuk cabang dan lompat dan tautkan yang perlu kita tambahkan menggunakan tujuan operand yang hanya tersirat di dalam instruksi itu sendiri, yaitu komputer berikutnya dengan sejumlah nilai. Pada tahap ini, kita akan memperlakukan ini sebagai operand yang tepat dengan nama next_pc. Hal ini akan didefinisikan lebih lanjut dalam tutorial berikutnya.

Petunjuk Cabang

Cabang yang kita tambahkan semuanya menggunakan encoding B-Type.

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

Berbagai isian segera yang berbeda digabungkan menjadi 12 bit yang ditandai dengan sejumlah nilai. Karena formatnya tidak benar-benar relevan, kita akan menyebutnya langsung bimm12, untuk cabang 12-bit secara langsung. Fragmentasi akan diatasi dengan tutorial berikutnya tentang membuat decoder biner. Semua instruksi cabang membandingkan register bilangan bulat yang ditentukan oleh rs1 dan rs2, jika kondisinya benar, nilai langsung ditambahkan ke nilai {i>pc<i} saat ini untuk menghasilkan alamat instruksi berikutnya yang akan dieksekusi. Operand untuk instruksi cabang karenanya harus { : rs1, rs2, bimm12 : next_pc }.

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.

Semua nama opcode ini unik, sehingga dapat digunakan kembali di .isa pengguna. Tentu saja, nama fungsi semantik baru harus ditambahkan, misalnya, "&RV32IBeq", dll.

Spesifikasi pembongkaran sekarang sedikit lebih rumit, karena alamat instruksi digunakan untuk menghitung destinasi, tanpa benar-benar menjadi bagian dari operand instruksi. Namun, itu adalah bagian dari informasi yang disimpan di objek instruksi, sehingga tersedia. Solusinya adalah dengan menggunakan sintaks ekspresi dalam {i>string<i} pembongkaran. Daripada menggunakan '%' diikuti dengan nama operand, Anda dapat mengetikkan %(expression: print format). Hanya sangat sederhana ekspresi didukung, tetapi offset plus alamat adalah salah satunya, dengan @ yang digunakan untuk alamat instruksi saat ini. Format cetaknya mirip dengan Format printf gaya C, tetapi tanpa % di awal. Format pembongkaran untuk instruksi beq akan menjadi:

    disasm: "beq", "%rs1, %rs2, %(@+bimm12:08x)"

Hanya dua petunjuk lompat dan tautkan yang perlu ditambahkan, jal (lompat dan tautkan) dan jalr (lompat dan link tidak langsung).

Petunjuk jal menggunakan encoding Jenis J:

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

Sama seperti instruksi cabang, 20-bit langsung difragmentasi di beberapa kolom, jadi kita akan menamainya jimm20. Fragmentasi tidak penting saat ini, tetapi akan dibahas nanti tutorial tentang cara membuat decoder biner. Operand spesifikasinya kemudian menjadi { : jimm20 : next_pc, rd }. Perhatikan bahwa ada dua operand tujuan, nilai {i>next pc<i} dan {i>link register<i} yang ditentukan dalam instruksi.

Mirip dengan petunjuk cabang di atas, format pembongkaran menjadi:

    disasm: "jal", "%rd, %(@+jimm20:08x)"

Lompat dan tautan tidak langsung menggunakan format I-Type dengan langsung 12-bit. Ini menambahkan nilai langsung {i>sign-extended<i} ke register integer yang ditentukan oleh rs1 untuk menghasilkan alamat petunjuk target. {i>Link register<i} adalah register bilangan bulat yang ditentukan oleh rd.

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

Jika telah melihat pola, Anda sekarang dapat menyimpulkan bahwa spesifikasi operand untuk jalr harus { : rs1, imm12 : next_pc, rd }, dan pembongkaran spesifikasi:

    disasm: "jalr", "%rd, %rs1, %imm12"

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


Tambahkan Petunjuk Toko

Petunjuk tokonya sangat sederhana. Mereka semua menggunakan format S-Type:

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, ini adalah kasus lain dari 12-bit terfragmentasi langsung, menyebutnya simm12. Semua petunjuk penyimpanan menyimpan nilai bilangan bulat yang ditentukan oleh rs2 ke alamat efektif dalam memori yang diperoleh dengan menambahkan nilai dari register integer yang ditentukan oleh rs1 ke nilai {i>sign-extended<i} dari 12-bit secara langsung. Format operand harus { : rs1, simm12, rs2 } untuk semua petunjuk toko.

Petunjuk toko yang perlu diterapkan adalah:

  • sb - Menyimpan byte.
  • sh - Menyimpan setengah kata.
  • sw - Simpan kata.

Spesifikasi pembongkaran untuk sb adalah seperti yang Anda harapkan:

    disasm: "sb", "%rs2, %simm12(%rs1)"

Spesifikasi fungsi semantik juga sesuai dengan yang Anda harapkan: "&RV32ISb", dll.

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


Tambahkan Petunjuk Pemuatan

Model instruksi pemuatan dirancang sedikit berbeda dari instruksi lain dalam simulator. Agar dapat memodelkan kasus di mana latensi beban tidak pasti, instruksi pemuatan dibagi menjadi dua tindakan terpisah: 1) efektif komputasi alamat dan akses memori, dan 2) menulis kembali hasil. Di kolom Hal ini dilakukan dengan membagi tindakan semantik dari beban menjadi dua instruksi terpisah, instruksi utama dan instruksi turunan. Selain itu, ketika kita menetapkan operand, kita perlu menetapkannya untuk operand child. Hal ini dilakukan dengan memperlakukan spesifikasi operand sebagai daftar triplet. Syntax-nya adalah,

{(predicate : sources : destinations), (predicate : sources : destinations), ... }

Semua petunjuk pemuatan menggunakan format I-Type sama seperti petunjuk:

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

Spesifikasi operand membagi operand yang diperlukan untuk menghitung alamat dan memulai akses memori dari tujuan register untuk data pemuatan: {( : rs1, imm12 : ), ( : : rd) }.

Karena tindakan semantik dibagi menjadi dua instruksi, fungsi semantik Anda juga perlu menentukan dua fungsi callable. Untuk lw (muat kata), ini akan menjadi ditulis:

    semfunc: "&RV32ILw", "&RV32ILwChild"

Spesifikasi pembongkaran lebih konvensional. Tidak ada yang menyebutkan petunjuk anak. Untuk lw, seharusnya:

    disasm: "lw", "%rd, %imm12(%rs1)"

Petunjuk pemuatan yang perlu diterapkan adalah:

  • lb - Memuat byte.
  • lbu - Memuat byte tanpa label.
  • lh - Memuat setengah kata.
  • lhu - Memuat setengah kata tanpa tanda tangan.
  • lw - Memuat kata.

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

Terima kasih telah sampai sejauh ini. Kami harap ini bermanfaat.