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:
- Alamat petunjuk, ukuran petunjuk yang disimulasikan, yaitu ukuran dalam .text.
- Opcode instruksi.
- Predikat pointer antarmuka operand (jika ada).
- Vektor pointer antarmuka operand sumber.
- Vektor pointer antarmuka operand tujuan.
- Fungsi semantik callable.
- Pointer ke objek status arsitektur.
- Pointer ke objek konteks.
- Pointer ke turunan dan instance Petunjuk berikutnya.
- 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.
Menambahkan Petunjuk Cabang dan Jump-And-Link
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)"
Petunjuk Jump-And-Link
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.