วัตถุประสงค์ของบทแนะนำนี้มีดังนี้
- ดูโครงสร้างและไวยากรณ์ของไฟล์คำอธิบายรูปแบบไบนารี
- ดูว่าคำอธิบายรูปแบบไบนารีตรงกับคำอธิบาย ISA อย่างไร
- เขียนคำอธิบายไบนารีสำหรับชุดคำสั่งย่อย RiscV RV32I
ภาพรวม
การเข้ารหัสคำสั่งไบนารี RiscV
การเข้ารหัสคำสั่งแบบไบนารีเป็นวิธีมาตรฐานในการเข้ารหัสคำสั่งสําหรับการดําเนินการบนไมโครโปรเซสเซอร์ โดยปกติแล้ว ไฟล์เหล่านี้จะจัดเก็บไว้ในไฟล์ที่เรียกใช้งานได้ ซึ่งมักจะอยู่ในรูปแบบ ELF วิธีการอาจเป็นได้ทั้งแบบความกว้างคงที่หรือแบบความกว้างผันแปร
โดยปกติแล้ว คำสั่งจะใช้รูปแบบการเข้ารหัสชุดเล็กๆ โดยแต่ละรูปแบบจะปรับแต่งสำหรับประเภทคำสั่งที่เข้ารหัส เช่น คำสั่งรีจิสเตอร์-รีจิสเตอร์อาจใช้รูปแบบหนึ่งที่เพิ่มจำนวนอ็อปโค้ดที่ใช้ได้สูงสุด ขณะที่คำสั่งรีจิสเตอร์-ค่าทันทีใช้รูปแบบอื่นที่ลดจำนวนอ็อปโค้ดที่ใช้ได้เพื่อเพิ่มขนาดของค่าทันทีที่สามารถเข้ารหัสได้ คำสั่ง Branch และ Jump มักจะใช้รูปแบบที่เพิ่มขนาดของค่าทันทีให้มากที่สุดเพื่อรองรับ Branch ที่มีออฟเซตขนาดใหญ่
รูปแบบคำสั่งที่ใช้โดยคำสั่งที่เราต้องการถอดรหัสในโปรแกรมจำลอง RiscV มีดังนี้
รูปแบบ R-Type ใช้สำหรับคำสั่งการลงทะเบียนกับตัวลงทะเบียน
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | rs2 | rs1 | func3 | rd | รหัสดำเนินการ |
รูปแบบ I-Type ใช้สำหรับคำสั่งแบบทันทีของรีจิสเตอร์ คำสั่งโหลด และคำสั่ง jalr
แบบทันที 12 บิต
31..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|
12 | 5 | 3 | 5 | 7 |
imm12 | rs1 | func3 | rd | รหัสดำเนินการ |
รูปแบบ I-Type เฉพาะที่ใช้สำหรับ Shift พร้อมคำสั่งทันที 5 บิต ทันที
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | uimm5 | rs1 | func3 | rd | รหัสดำเนินการ |
รูปแบบ U-Type ที่ใช้สำหรับคำสั่งยาวๆ ทันที (lui
, auipc
), 20 บิตทันที ดังนี้
31..12 | 11..7 | 6..0 |
---|---|---|
20 | 5 | 7 |
uimm20 | rd | รหัสดำเนินการ |
รูปแบบ B-Type ใช้สำหรับสาขาแบบมีเงื่อนไข 12 บิตทันที
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 | รหัสดำเนินการ |
รูปแบบ J-Type ที่ใช้สำหรับคำสั่ง jal
และ 20 บิตทันที
31 | 30..21 | 20 | 19..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
1 | 10 | 1 | 8 | 5 | 7 |
imm | imm | imm | imm | ปุ่มเลือก | รหัสดำเนินการ |
รูปแบบ S-Type ใช้สำหรับคำสั่งจัดเก็บ 12 บิตทันที
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
imm | rs2 | rs1 | func3 | imm | รหัสดำเนินการ |
ดังที่คุณเห็นจากรูปแบบเหล่านี้ คำสั่งทั้งหมดมีความยาว 32 บิต และบิตต่ำ 7 บิตในแต่ละรูปแบบคือช่อง opcode นอกจากนี้ โปรดทราบว่าแม้ว่ารูปแบบต่างๆ จะมีค่าทันทีที่มีขนาดเท่ากัน แต่บิตของค่าเหล่านั้นจะมาจากส่วนต่างๆ ของคำสั่ง อย่างที่เห็น รูปแบบการกำหนดตัวถอดรหัสไบนารี สามารถแสดงข้อมูลนี้
คำอธิบายการเข้ารหัสไบนารี
การเข้ารหัสไบนารีของคำสั่งจะแสดงในรูปแบบไบนารีในไฟล์คำอธิบาย (.bin_fmt
) ซึ่งอธิบายการเข้ารหัสไบนารีของคำสั่งใน ISA เพื่อให้สร้างโปรแกรมถอดรหัสคำสั่งรูปแบบไบนารีได้ ตัวถอดรหัสที่สร้างขึ้นจะระบุ opcode และแยกค่าตัวถูกดำเนินการและช่องแบบทันที เพื่อให้ข้อมูลที่จำเป็นสำหรับตัวถอดรหัสที่ไม่เข้ารหัสของ ISA ซึ่งอธิบายไว้ในบทแนะนำก่อนหน้านี้
ในบทแนะนำนี้ เราจะเขียนไฟล์คำอธิบายการเข้ารหัสแบบไบนารีสำหรับชุดย่อยของคำสั่ง RiscV32I ที่จำเป็นเพื่อจำลองคำสั่งที่ใช้ในโปรแกรม " Hello World" ขนาดเล็ก สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับ RiscV ISA ดูที่ ข้อกำหนดของ Risc-V{.external}
เริ่มด้วยการเปิดไฟล์ ดังนี้
riscv_bin_decoder/riscv32i.bin_fmt
เนื้อหาของไฟล์จะแบ่งออกเป็นหลายส่วน
อย่างแรก คือคำจำกัดความของ 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;
};
คําจํากัดความของโปรแกรมถอดรหัสจะระบุชื่อโปรแกรมถอดรหัส RiscV32I
รวมถึงข้อมูลอีก 4 รายการ รายการแรกคือ namespace
ซึ่งจะกําหนดเนมสเปซที่จะวางโค้ดที่สร้างขึ้น ประการที่ 2 คือ opcode_enum
ซึ่งระบุวิธีอ้างอิงประเภทการแจกแจงคำสั่งที่ตัวถอดรหัส ISA สร้างขึ้นภายในโค้ดที่สร้างขึ้น ประการที่สาม includes {}
จะระบุไฟล์รวมที่จําเป็นสําหรับโค้ดที่สร้างขึ้นสําหรับโปรแกรมถอดรหัสนี้
ในกรณีของเรา นี่คือไฟล์ที่ตัวถอดรหัส ISA สร้างขึ้นจากบทแนะนำก่อนหน้านี้
คุณสามารถระบุไฟล์อื่นๆ ที่มีได้ในคำจำกัดความ includes {}
ที่มีขอบเขตรวมทั้งหมด ซึ่งมีประโยชน์ในกรณีที่มีการกำหนดตัวถอดรหัสหลายรายการ และตัวถอดรหัสทั้งหมดต้องรวมไฟล์เดียวกันบางไฟล์ ที่ 4 คือรายการชื่อกลุ่มคำสั่งที่ประกอบกันเป็นคำสั่งสำหรับการสร้างตัวถอดรหัส ในกรณีของเรา มีเพียงรายการเดียวคือ RiscVInst32
ต่อไปคือคำจำกัดความรูปแบบ 3 รูปแบบ ซึ่งแสดงรูปแบบคำสั่งที่แตกต่างกันสำหรับคำคำสั่ง 32 บิตที่ใช้โดยคำสั่งที่กําหนดไว้แล้วในไฟล์
// 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];
};
รายการแรกกำหนดรูปแบบคำสั่งแบบ 32 บิตที่มีชื่อว่า Inst32Format
ซึ่งมี 2 ช่อง ได้แก่ bits
(กว้าง 25 บิต) และ opcode
(กว้าง 7 บิต) แต่ละช่องเป็น unsigned
ซึ่งหมายความว่าระบบจะเพิ่มค่า 0 เมื่อดึงข้อมูลและวางไว้ในประเภทจำนวนเต็ม C++ ผลรวมของความกว้างของช่องข้อมูลไบต์ต้องเท่ากับความกว้างของรูปแบบ เครื่องมือจะแสดงข้อผิดพลาดหากข้อมูลไม่ตรงกัน รูปแบบนี้ไม่ได้มาจากรูปแบบอื่น จึงถือว่าเป็นรูปแบบระดับบนสุด
ส่วนรายการที่ 2 จะกำหนดรูปแบบคำสั่งแบบ 32 บิตที่มีชื่อว่า IType
ซึ่งมาจาก Inst32Format
ทำให้รูปแบบทั้ง 2 นี้เกี่ยวข้องกัน รูปแบบนี้มี 5 ช่อง ได้แก่ imm12
, rs1
, func3
, rd
และ opcode
ช่อง imm12
คือ signed
ซึ่งหมายความว่าค่าจะขยายออกเมื่อดึงค่าออกมาและวางอยู่ในประเภทจำนวนเต็ม C++ โปรดทราบว่า IType.opcode
ทั้ง 2 อย่างมีแอตทริบิวต์ที่มีการรับรอง/ไม่มีลายเซ็นเหมือนกัน และอ้างถึงบิตคำศัพท์ในคำสั่งเหมือนกับ Inst32Format.opcode
รูปแบบที่ 3 คือรูปแบบที่กำหนดเองซึ่งใช้โดยfence
คำสั่งเท่านั้น ซึ่งเป็นคำสั่งที่ระบุไว้แล้วและเราไม่ต้องกังวลในบทแนะนำนี้
ประเด็นสำคัญ: ใช้ชื่อช่องซ้ำในรูปแบบที่เกี่ยวข้องที่แตกต่างกันตราบใดที่ชื่อนั้นแทนบิตเดียวกันและมีแอตทริบิวต์ที่มีลายเซ็น/ไม่มีลายเซ็นเดียวกัน
หลังจากคำจำกัดความของรูปแบบใน riscv32i.bin_fmt
จะมีคำจำกัดความกลุ่มคำสั่ง คำสั่งทั้งหมดในกลุ่มคำสั่งต้องมีความยาวบิตเท่ากัน และใช้รูปแบบที่มาจาก (อาจโดยอ้อม) รูปแบบคำสั่งระดับบนสุดเดียวกัน เมื่อ ISA มีคำสั่งที่มีความยาวต่างกัน ระบบจะใช้กลุ่มคำสั่งที่แตกต่างกันสำหรับความยาวแต่ละรายการ นอกจากนี้ หากการถอดรหัส ISA เป้าหมายขึ้นอยู่กับโหมดการดําเนินการ เช่น คำสั่ง Arm กับ Thumb คุณจะต้องสร้างกลุ่มคำสั่งแยกกันสำหรับแต่ละโหมด โปรแกรมวิเคราะห์ภาษา bin_fmt
จะสร้างตัวถอดรหัสไบนารีสําหรับกลุ่มคำสั่งแต่ละกลุ่ม
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;
};
กลุ่มคำสั่งจะกำหนดชื่อ RiscV32I
, ความกว้าง [32]
, ชื่อประเภทการแจกแจงรายการอ็อปโค้ดที่จะใช้ "OpcodeEnum"
และรูปแบบคำสั่งพื้นฐาน ประเภทการแจกแจงรหัสการดำเนินการควรเหมือนกับที่ผลิตโดยโปรแกรมถอดรหัสคำสั่งแบบไม่ขึ้นอยู่กับรูปแบบที่กล่าวถึงในบทแนะนำเกี่ยวกับโปรแกรมถอดรหัส ISA
คำอธิบายการเข้ารหัสคำสั่งแต่ละรายการประกอบด้วย 3 ส่วนดังนี้
- ชื่ออ็อปโค้ด ซึ่งต้องเหมือนกับที่ใช้ในคำอธิบายโปรแกรมถอดรหัสเพื่อให้ทั้ง 2 อย่างทำงานร่วมกันได้
- รูปแบบคำสั่งที่จะใช้สำหรับอ็อบเจ็กต์โค้ด รูปแบบนี้เป็นรูปแบบที่ใช้เพื่อตอบสนองการอ้างอิงถึงบิตฟิลด์ในส่วนสุดท้าย
- รายการค่าจำกัดของช่องบิต
==
,!=
,<
,<=
,>
และ>=
ที่คั่นด้วยคอมมา ซึ่งทั้งหมดจะต้องเป็นจริงเพื่อให้ opcode สามารถจับคู่กับคำคำสั่งได้สำเร็จ
โปรแกรมแยกวิเคราะห์ .bin_fmt
จะใช้ข้อมูลทั้งหมดนี้ในการสร้างตัวถอดรหัสที่มีลักษณะดังนี้
- มีฟังก์ชันการดึงข้อมูล (แบบมีเครื่องหมาย/ไม่มีเครื่องหมาย) ตามความเหมาะสมสำหรับแต่ละช่องบิตในทุกรูปแบบ ฟังก์ชันเครื่องมือแยกข้อมูลจะอยู่ในเนมสเปซที่มีชื่อรูปแบบในเวอร์ชัน Snake-case ตัวอย่างเช่น การวางฟังก์ชันตัวดึงข้อมูลสำหรับรูปแบบ
IType
จะอยู่ในเนมสเปซi_type
ฟังก์ชันตัวดึงข้อมูลแต่ละรายการจะประกาศเป็นinline
โดยรับประเภทuint_t
ที่แคบที่สุดซึ่งเก็บความกว้างของรูปแบบไว้ และแสดงผลประเภทint_t
(สำหรับแบบมีเครื่องหมาย)uint_t
(สำหรับแบบไม่มีเครื่องหมาย) ที่แคบที่สุดซึ่งเก็บความกว้างของช่องที่ดึงข้อมูลไว้ เช่น
inline uint8_t ExtractOpcode(uint32_t value) {
return value & 0x7f;
}
- ฟังก์ชันการถอดรหัสสําหรับกลุ่มคำสั่งแต่ละกลุ่ม โดยจะแสดงผลค่าประเภท
OpcodeEnum
และใช้uint_t
ประเภทที่แคบที่สุดซึ่งเก็บความกว้างของรูปแบบกลุ่มคำสั่ง
ทำการบิลด์ครั้งแรก
เปลี่ยนไดเรกทอรีเป็น riscv_bin_decoder
และสร้างโปรเจ็กต์โดยใช้คำสั่งต่อไปนี้
$ cd riscv_bin_decoder
$ bazel build :all
ตอนนี้ให้เปลี่ยนไดเรกทอรีกลับไปเป็นรูทของที่เก็บ จากนั้นมาดูแหล่งที่มาที่สร้างขึ้นกัน โดยเปลี่ยนไดเรกทอรีเป็น bazel-out/k8-fastbuild/bin/riscv_bin_decoder
(สมมติว่าคุณใช้โฮสต์ x86 สำหรับโฮสต์อื่น k8-fastbuild จะเป็นสตริงอื่น)
$ cd ..
$ cd bazel-out/k8-fastbuild/bin/riscv_bin_decoder
riscv32i_bin_decoder.h
riscv32i_bin_decoder.cc
ไฟล์ส่วนหัวที่สร้างขึ้น (.h)
เปิด riscv32i_bin_decoder.h
ส่วนแรกของไฟล์มีการป้องกันแบบ Boilerplate มาตรฐาน ซึ่งรวมถึงการประกาศไฟล์และเนมสเปซ ถัดจากนั้น จะมีฟังก์ชันตัวช่วยที่ใช้เทมเพลตในเนมสเปซ internal
ฟังก์ชันนี้ใช้ในการดึงฟิลด์บิตจากรูปแบบที่ยาวเกินไปที่จะอยู่ในจำนวนเต็ม C++ 64 บิต
#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
ถัดจากส่วนแรกจะมีชุดเนมสเปซ 3 ชุด โดยแต่ละชุดมีไว้สําหรับประกาศ format
แต่ละรายการในไฟล์ riscv32i.bin_fmt
namespace fence {
...
} // namespace fence
namespace i_type {
...
} // namespace i_type
namespace inst32_format {
...
} // namespace inst32_format
ภายในเนมสเปซเหล่านี้แต่ละรายการ ระบบจะกำหนดฟังก์ชันการแยกบิตฟิลด์ inline
สำหรับแต่ละช่องของบิตในรูปแบบดังกล่าว นอกจากนี้ รูปแบบฐานจะทำซ้ำฟังก์ชันการดึงข้อมูลจากรูปแบบสืบทอดซึ่ง 1) ชื่อช่องเกิดขึ้นเฉพาะในชื่อช่องเดียว หรือ 2) ที่ชื่อช่องหมายถึงช่องประเภทเดียวกัน (ตำแหน่งแบบมี/ไม่มีลายเซ็น และบิต) ในแต่ละรูปแบบที่เกิดขึ้น ซึ่งช่วยให้ดึงข้อมูลช่องบิตที่อธิบายบิตเดียวกันได้โดยใช้ฟังก์ชันในพื้นที่เนมสเปซของรูปแบบระดับบนสุด
ฟังก์ชันในเนมสเปซ i_type
มีดังต่อไปนี้
namespace i_type {
inline uint8_t ExtractFunc3(uint32_t value) {
return (value >> 12) & 0x7;
}
inline int16_t ExtractImm12(uint32_t value) {
int16_t result = ( (value >> 20) & 0xfff) << 4;
result = result >> 4;
return result;
}
inline uint8_t ExtractOpcode(uint32_t value) {
return value & 0x7f;
}
inline uint8_t ExtractRd(uint32_t value) {
return (value >> 7) & 0x1f;
}
inline uint8_t ExtractRs1(uint32_t value) {
return (value >> 15) & 0x1f;
}
} // namespace i_type
สุดท้ายจะมีการประกาศฟังก์ชันของฟังก์ชันตัวถอดรหัสสำหรับกลุ่มคำสั่ง RiscVInst32
โดยจะใช้ค่า 32 บิตแบบไม่ลงนามเป็นค่าของคำคำสั่งและแสดงผลOpcodeEnum
สมาชิกคลาสการแจกแจงที่ตรงกัน หรือ OpcodeEnum::kNone
หากไม่พบรายการที่ตรงกัน
OpcodeEnum DecodeRiscVInst32(uint32_t inst_word);
ไฟล์ต้นฉบับที่สร้างขึ้น (.cc)
ตอนนี้ให้เปิด riscv32i_bin_decoder.cc
ส่วนแรกของไฟล์มีการประกาศ #include
และเนมสเปซ ตามด้วยการประกาศฟังก์ชันตัวถอดรหัส
#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
ใช้สำหรับการดำเนินการถอดรหัสที่ว่างเปล่า เช่น การดำเนินการที่แสดงผล OpcodeEnum::kNone
ฟังก์ชันอีก 3 รายการที่เหลือประกอบกันเป็นโปรแกรมถอดรหัสที่สร้างขึ้น ตัวถอดรหัสโดยรวมทำงานแบบลําดับชั้น ระบบจะคำนวณชุดบิตในข้อความระบุคำสั่งเพื่อแยกความแตกต่างระหว่างคำสั่งหรือกลุ่มคำสั่งที่ระดับบนสุด ข้อมูลเล็กๆ น้อยๆ
ไม่จำเป็นต้องอยู่ติดกัน จํานวนบิตจะกําหนดขนาดของตารางการค้นหาที่สร้างขึ้นด้วยฟังก์ชันตัวถอดรหัสระดับที่ 2 ซึ่งจะแสดงในส่วนถัดไปของไฟล์
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,
...
};
สุดท้าย ให้กําหนดฟังก์ชันตัวถอดรหัส
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;
}
ในกรณีนี้ที่มีการกําหนดวิธีการเพียง 4 รายการ จะมีการถอดรหัสเพียงระดับเดียวและตารางการค้นหาที่กระจัดกระจายมาก เมื่อเพิ่มคำสั่ง โครงสร้างของตัวถอดรหัสจะเปลี่ยนไป และจำนวนระดับในลําดับชั้นของตารางตัวถอดรหัสอาจเพิ่มขึ้น
เพิ่มคำสั่ง ALU แบบรีจิสเตอร์ต่อรีจิสเตอร์
ตอนนี้ถึงเวลาเพิ่มวิธีการใหม่ลงในไฟล์ riscv32i.bin_fmt
กลุ่มคำสั่งแรกคือคำสั่ง ALU แบบรีจิสเตอร์ต่อรีจิสเตอร์ เช่น add
, and
เป็นต้น ใน RiscV32 คำสั่งทั้งหมดเหล่านี้ใช้รูปแบบคำสั่งไบนารีประเภท R ดังนี้
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | rs2 | rs1 | func3 | rd | รหัสดำเนินการ |
สิ่งแรกที่ต้องทำคือการเพิ่มรูปแบบ เปิดไฟล์ riscv32i.bin_fmt
ในเครื่องมือแก้ไขที่ต้องการ ถัดจาก Inst32Format
ให้ใส่รูปแบบชื่อ RType
ซึ่งมาจาก Inst32Format
บิตฟิลด์ทั้งหมดใน RType
เป็น unsigned
ใช้ชื่อ ความกว้างของบิต และลําดับ (จากซ้ายไปขวา) จากตารางด้านบนเพื่อกําหนดรูปแบบ หากต้องการคำแนะนำหรือดูวิธีแก้ปัญหาแบบเต็ม ให้คลิกที่นี่
ต่อไปเราต้องเพิ่มวิธีการ วิธีการมีดังนี้
add
- การเพิ่มจำนวนเต็มand
- bitwise andor
- การดำเนินการ OR แบบบิตsll
- เลื่อนไปทางซ้ายเชิงตรรกะsltu
- ตั้งค่าน้อยกว่าที่ไม่ได้ลงนามsub
- การลบจำนวนเต็มxor
- bitwise xor
การเข้ารหัสมีดังนี้
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 | ชื่อรหัสดำเนินการ |
---|---|---|---|---|---|---|
000 0000 | rs2 | rs1 | 000 | rd | 011 0011 | เพิ่ม |
000 0000 | rs2 | rs1 | 111 | rd | 011 0011 | และ |
000,000 | rs2 | rs1 | 110 | ปุ่มเลือก | 011 0011 | หรือ |
000,000 | rs2 | rs1 | 001 | rd | 011 0011 | sll |
000,000 | rs2 | rs1 | 011 | rd | 011 0011 | Sltu |
010 0000 | rs2 | rs1 | 000 | rd | 011 0011 | สำรอง |
000 0000 | rs2 | rs1 | 100 | rd | 011 0011 | xor |
func7 | func3 | รหัสดำเนินการ |
เพิ่มคําจํากัดความวิธีการเหล่านี้ก่อนวิธีการอื่นๆ ในกลุ่มวิธีการ RiscVInst32
สตริงฐาน 2 จะระบุด้วยคำนำหน้า 0b
(คล้ายกับ 0x
สำหรับตัวเลขฐาน 16) คุณอาจแทรกเครื่องหมายคำพูดเดี่ยว '
เป็นตัวคั่นหลักในจุดที่เหมาะสมเพื่อให้อ่านสตริงยาวของเลขฐานสองได้ง่ายขึ้น
คําจํากัดความของคำสั่งแต่ละรายการจะมีข้อจำกัด 3 ข้อ ได้แก่ on
func7
, func3
และ opcode
สำหรับทุกเงื่อนไขยกเว้น sub
ข้อจำกัด func7
จะเป็นดังนี้
func7 == 0b000'0000
ข้อจำกัด func3
จะแตกต่างกันไปตามคำสั่งส่วนใหญ่ สำหรับ add
และ sub
จะเป็นดังนี้
func3 == 0b000
ข้อจำกัด opcode
จะเหมือนกันสำหรับคำสั่งแต่ละรายการต่อไปนี้
opcode == 0b011'0011
อย่าลืมใส่เครื่องหมายอัฒภาค ;
ปิดท้ายแต่ละบรรทัด
โซลูชันที่เสร็จสมบูรณ์อยู่ที่นี่
จากนั้นให้สร้างโปรเจ็กต์ตามเดิม และเปิดไฟล์ riscv32i_bin_decoder.cc
ที่สร้างขึ้น คุณจะเห็นว่ามีการสร้างฟังก์ชันตัวถอดรหัสเพิ่มเติมเพื่อจัดการกับคำสั่งใหม่ โดยส่วนใหญ่แล้ว รายการเหล่านี้จะคล้ายกับรายการที่สร้างขึ้นก่อนหน้านี้ แต่ให้ดูDecodeRiscVInst32_0_c
ซึ่งใช้สําหรับการถอดรหัส 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];
}
ฟังก์ชันนี้จะสร้างตารางถอดรหัสแบบคงที่ขึ้นมา และดึงค่าที่ค้นหาออกจากคำคำแนะนำเพื่อเลือกดัชนีที่เหมาะสม ซึ่งจะเพิ่มชั้นที่ 2 ไว้ในลําดับชั้นของตัวถอดรหัสคําสั่ง แต่เนื่องจากสามารถค้นหาอ็อปโค้ดในตารางได้โดยตรงโดยไม่ต้องเปรียบเทียบเพิ่มเติม จึงมีการแทรกไว้ในฟังก์ชันนี้แทนที่จะต้องเรียกใช้ฟังก์ชันอื่น
เพิ่มคำสั่ง ALU ที่มีค่าทันที
ชุดคำสั่งถัดไปที่เราจะเพิ่มคือคำสั่ง ALU ที่ใช้ค่าทันทีแทนรีจิสเตอร์ คำสั่งเหล่านี้มี 3 กลุ่ม (อิงตามช่อง Immediate) ได้แก่ คำสั่ง Immediate ประเภท I ที่มี Immediate แบบลงนาม 12 บิต คำสั่ง Immediate ประเภท I เฉพาะสำหรับการเปลี่ยน และ Immediate ประเภท U ที่มี Immediate แบบไม่ลงนาม 20 บิต รูปแบบต่างๆ มีดังนี้
รูปแบบทันทีประเภท I
31..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|
12 | 5 | 3 | 5 | 7 |
imm12 | rs1 | func3 | rd | รหัสดำเนินการ |
รูปแบบทันทีประเภท I เฉพาะ
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | uimm5 | rs1 | func3 | rd | รหัสดำเนินการ |
รูปแบบทันทีแบบตัว U
31..12 | 11..7 | 6..0 |
---|---|---|
20 | 5 | 7 |
uimm20 | rd | รหัสดำเนินการ |
มีรูปแบบ I-Type อยู่แล้วใน riscv32i.bin_fmt
จึงไม่จำเป็นต้องเพิ่มรูปแบบดังกล่าว
หากเปรียบเทียบรูปแบบ I-Type เฉพาะกับรูปแบบ R-Type ที่เรากําหนดไว้ในแบบฝึกหัดก่อนหน้า เราจะเห็นว่าความแตกต่างเพียงอย่างเดียวคือช่อง rs2
เปลี่ยนชื่อเป็น uimm5
เราอาจเพิ่มรูปแบบ R-Type แทนการเพิ่มรูปแบบใหม่ทั้งหมด เราเพิ่มช่องอื่นไม่ได้เนื่องจากจะทำให้รูปแบบกว้างขึ้น แต่เราเพิ่มการวางซ้อนได้ การวางซ้อนคือชื่อแทนสําหรับชุดบิตในรูปแบบ และสามารถใช้เพื่อรวมลําดับย่อยหลายรายการของรูปแบบเข้าด้วยกันเป็นเอนทิตีที่มีชื่อแยกต่างหาก ผลข้างเคียงคือตอนนี้โค้ดที่สร้างขึ้นจะมีฟังก์ชันการดึงข้อมูลสำหรับการวางซ้อนด้วย นอกเหนือจากฟังก์ชันสำหรับช่อง ในกรณีนี้ เมื่อไม่ได้ลงชื่อทั้ง rs2
และ uimm5
ก็ไม่ได้มีความแตกต่างมากนัก ยกเว้นการระบุอย่างชัดเจนว่าจะใช้ช่องนี้ทันที หากต้องการเพิ่มการวางซ้อนชื่อ uimm5
ลงในรูปแบบ R ให้เพิ่มข้อมูลต่อไปนี้ต่อจากช่องสุดท้าย
overlays:
unsigned uimm5[5] = rs2;
รูปแบบใหม่เพียงรูปแบบเดียวที่เราต้องการเพิ่มคือรูปแบบ U ก่อนที่จะเพิ่มรูปแบบนั้น ลองพิจารณาวิธีการ 2 รายการที่ใช้รูปแบบดังกล่าว ซึ่งได้แก่ auipc
และ lui
ทั้ง 2 คำสั่งนี้จะเลื่อนค่าทันที 20 บิตไปทางซ้าย 12 ตำแหน่งก่อนนำไปใช้เพื่อเพิ่ม PC เข้าไป (auipc
) หรือเขียนค่าดังกล่าวลงในรีจิสเตอร์โดยตรง (lui
) การใช้การวางซ้อนช่วยให้เราสามารถระบุค่าทันทีเวอร์ชันที่เลื่อนไว้ล่วงหน้าได้ ซึ่งจะเปลี่ยนการคำนวณเล็กน้อยจากการดำเนินการคำสั่งเป็นการถอดรหัสคำสั่ง ก่อนอื่นให้เพิ่มรูปแบบตามช่องที่ระบุไว้ในตารางด้านบน จากนั้นเราจะเพิ่มการวางซ้อนต่อไปนี้ได้
overlays:
unsigned uimm32[32] = uimm20, 0b0000'0000'0000;
ไวยากรณ์การวางซ้อนช่วยให้เราต่อเชื่อมได้ไม่เพียงช่องข้อมูลเท่านั้น แต่ยังต่อเชื่อมค่าคงที่ได้ด้วย ในกรณีนี้ เราจะต่อสตริงด้วย 0 12 ตัว ซึ่งจะส่งผลให้มีการเลื่อนไปทางซ้าย 12 ตำแหน่ง
คำสั่ง I-Type ที่เราต้องเพิ่ม ได้แก่
addi
- เพิ่มทันทีandi
- Bitwise and with immediateori
- Bitwise or พร้อม immediatexori
- Bitwise xor with immediate
การเข้ารหัสมีดังนี้
31..20 | 19..15 | 14..12 | 11..7 | 6..0 | opcode_name |
---|---|---|---|---|---|
imm12 | rs1 | 000 | rd | 001 0011 | addi |
imm12 | rs1 | 111 | ปุ่มเลือก | 001 0011 | andi |
imm12 | rs1 | 110 | rd | 001 0011 | ori |
imm12 | rs1 | 100 | rd | 001 0011 | xori |
func3 | รหัสดำเนินการ |
คำสั่งประเภท R (ประเภท I เฉพาะ) ที่เราจำเป็นต้องเพิ่มมีดังนี้
slli
- เลื่อนไปทางซ้ายอย่างสมเหตุสมผลทันทีsrai
- เลื่อนไปทางขวาแบบเชิงตัวเลขโดยทันทีsrli
- เลื่อนไปทางขวาแบบตรรกะโดยทันที
การเข้ารหัสมีดังนี้
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 | ชื่อรหัสดำเนินการ |
---|---|---|---|---|---|---|
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 | รหัสดำเนินการ |
คำสั่ง U-Type ที่เราต้องเพิ่ม ได้แก่
auipc
- เพิ่ม immediate บนไปยัง PClui
- โหลดส่วนบนทันที
การเข้ารหัสมีดังนี้
31..12 | 11..7 | 6..0 | ชื่อ opcode |
---|---|---|---|
uimm20 | rd | 001 0111 | auipc |
uimm20 | rd | 011 0111 | lui |
รหัสดำเนินการ |
โปรดทําการเปลี่ยนแปลงแล้วสร้าง ตรวจสอบเอาต์พุตที่สร้างขึ้น คุณตรวจสอบงานกับ riscv32i.bin_fmt ได้เช่นเดิม
เพิ่มคำสั่งสาขาและคำสั่ง Jump-and-Link
วิธีการชุดต่อไปที่จำเป็นต้องกำหนด ได้แก่ วิธีการสำหรับ Branch แบบมีเงื่อนไข วิธีการข้ามและลิงก์ และวิธีการลงทะเบียนแบบข้ามและลิงก์
สาขาแบบมีเงื่อนไขที่เราเพิ่มทั้งหมดจะใช้การเข้ารหัส B-Type
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
imm7 | rs2 | rs1 | func3 | imm5 | รหัสดำเนินการ |
แม้ว่าการเข้ารหัสประเภท B จะมีเลย์เอาต์เหมือนกับการเข้ารหัสประเภท R ทุกประการ แต่เราเลือกใช้ประเภทรูปแบบใหม่เพื่อให้สอดคล้องกับเอกสารประกอบสำหรับ RiscV
แต่คุณยังเพิ่มการวางซ้อนเพื่อให้ Branch ที่เหมาะสมทราบได้ทันทีโดยใช้ช่อง func7
และ rd
ของการเข้ารหัส R-Type
การเพิ่มรูปแบบ BType
ที่มีช่องตามที่ระบุไว้ข้างต้นเป็นสิ่งจําเป็น แต่ยังไม่เพียงพอ ดังที่คุณเห็น คำสั่งแบบทันทีจะแบ่งออกเป็น 2 ช่องคำสั่ง
นอกจากนี้ คำสั่งสาขาจะไม่ถือว่าการดำเนินการนี้เป็นการเชื่อมต่อช่อง 2 ช่องเข้าด้วยกัน แต่ระบบจะแบ่งแต่ละช่องออกเป็นหลายส่วน และต่อเชื่อมแต่ละส่วนเหล่านี้ตามลําดับอื่น สุดท้าย ระบบจะเลื่อนค่านั้นไปทางซ้าย 1 ตำแหน่งเพื่อให้ได้ออฟเซตที่สอดคล้องกับ 16 บิต
ลําดับของบิตในคําสั่งที่ใช้สร้างค่าทันทีคือ 31, 7, 30..25, 11..8 ซึ่งสอดคล้องกับการอ้างอิงฟิลด์ย่อยต่อไปนี้ โดยที่ดัชนีหรือช่วงจะระบุบิตในฟิลด์ โดยนับจากขวาไปซ้าย ดังนี้
imm7[6]
หมายถึง MSB ของ imm7
และ imm5[0]
หมายถึง MSB ของ imm5
imm7[6], imm5[0], imm7[5..0], imm5[4..1]
การทำให้การจัดการบิตนี้เป็นส่วนหนึ่งของคำสั่งสาขามีข้อเสียที่สำคัญ 2 ข้อ ประการแรก เชื่อมโยงการใช้งานฟังก์ชันเชิงความหมายกับรายละเอียดในการนําเสนอคําสั่งแบบไบนารี ประการที่ 2 คือการเพิ่มค่าใช้จ่ายเพิ่มเติมของรันไทม์ คำตอบคือการเพิ่มการวางซ้อนลงในรูปแบบ BType
รวมถึง "0" ต่อท้ายเพื่อพิจารณาการเลื่อนไปทางซ้าย
overlays:
signed b_imm[13] = imm7[6], imm5[0], imm7[5..0], imm5[4..1], 0b0;
โปรดทราบว่าการวางซ้อนมีการเซ็นชื่อ ดังนั้นระบบจะขยายค่าลงท้ายโดยอัตโนมัติเมื่อดึงข้อมูลออกจากคำคำสั่ง
คำสั่ง Jump-and-Link (ทันที) ใช้การเข้ารหัสประเภท J ดังนี้
31..12 | 11..7 | 6..0 |
---|---|---|
20 | 5 | 7 |
imm20 | rd | รหัสดำเนินการ |
รูปแบบนี้ยังเพิ่มได้ง่ายอีกด้วย แต่คําสั่ง immediate ที่ใช้นั้นไม่ได้ตรงไปตรงมาอย่างที่คิด ลำดับบิตที่ใช้สร้างค่าทันทีแบบเต็มคือ 31, 19..12, 20, 30..21 และค่าทันทีสุดท้ายจะเลื่อนไปทางซ้าย 1 บิตเพื่อการจัดแนวแบบครึ่งคำ วิธีแก้ไขคือเพิ่มการวางซ้อน (21 บิตเพื่อชดเชยการเลื่อนไปทางซ้าย) ในรูปแบบดังนี้
overlays:
signed j_imm[21] = imm20[19, 7..0, 8, 18..9], 0b0;
ดังที่คุณเห็น ไวยากรณ์ของการวางซ้อนรองรับการระบุหลายช่วงในช่องในรูปแบบตัวย่อ นอกจากนี้ หากไม่ได้ใช้ชื่อช่อง ตัวเลขไบต์จะอ้างอิงถึงคําสั่งโดยตรง ดังนั้นบรรทัดด้านบนจึงเขียนได้ดังนี้
signed j_imm[21] = [31, 19..12, 20, 30..21], 0b0;
สุดท้าย คำสั่ง Jump-and-Link (Register) ใช้รูปแบบ I-Type ตามที่ใช้ในก่อนหน้านี้
รูปแบบทันทีประเภท I
31..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|
12 | 5 | 3 | 5 | 7 |
imm12 | rs1 | func3 | rd | รหัสดำเนินการ |
ครั้งนี้คุณไม่จําเป็นต้องเปลี่ยนแปลงรูปแบบ
คำสั่งสาขาที่เราจำเป็นต้องเพิ่มมีดังนี้
beq
- Branch if equalbge
- Branch if greater than or equal.bgeu
- สาขา หากมีมากกว่าหรือเท่ากับ ไม่มีเครื่องหมายblt
- สาขา หากน้อยกว่าbltu
- สาขา หากมีน้อยกว่าที่ไม่ได้ลงชื่อbne
- Branch if not equal
โดยมีการโค้ดดังนี้
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 | ชื่อ 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 | รหัสดำเนินการ |
คำสั่ง jal
มีการเข้ารหัสดังนี้
31..12 | 11..7 | 6..0 | ชื่อรหัสดำเนินการ |
---|---|---|---|
imm20 | rd | 110 1111 | jal |
รหัสดำเนินการ |
คำสั่ง jalr
ได้รับการเข้ารหัสดังนี้
31..20 | 19..15 | 14..12 | 11..7 | 6..0 | opcode_name |
---|---|---|---|---|---|
imm12 | rs1 | 000 | rd | 110 0111 | Jalr |
func3 | รหัสดำเนินการ |
ทำการเปลี่ยนแปลง แล้วสร้างได้เลย ตรวจสอบเอาต์พุตที่สร้างขึ้น คุณตรวจสอบงานกับ riscv32i.bin_fmt ได้เช่นเดิม
เพิ่มวิธีการในร้านค้า
คำสั่งจัดเก็บใช้การเข้ารหัสประเภท S ซึ่งเหมือนกับการเข้ารหัสประเภท B ที่คำสั่งสาขาใช้ ยกเว้นองค์ประกอบของค่าทันที เราเลือกที่จะเพิ่มรูปแบบ SType
เพื่อให้สอดคล้องกับเอกสารประกอบของ RiscV
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
imm7 | rs2 | rs1 | func3 | imm5 | รหัสดำเนินการ |
ในกรณีของรูปแบบ SType
ข้อมูลทันทีจะต่อท้ายกันโดยตรงจากช่องข้อมูลทันที 2 ช่อง ดังนั้นข้อกําหนดเฉพาะของการวางซ้อนจึงมีดังนี้
overlays:
signed s_imm[12] = imm7, imm5;
โปรดทราบว่าไม่จำเป็นต้องมีตัวระบุช่วงบิตเมื่อจะเชื่อมต่อทั้งช่อง
วิธีการของสโตร์มีการเข้ารหัสดังต่อไปนี้
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 | ชื่อ 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 | รหัสดำเนินการ |
ทำการเปลี่ยนแปลง แล้วสร้างได้เลย ตรวจสอบเอาต์พุตที่สร้างขึ้น คุณสามารถตรวจสอบงานของคุณเทียบกับ riscv32i.bin_fmt ได้เช่นเดียวกับก่อนหน้านี้
เพิ่มวิธีการโหลด
คำแนะนำการโหลดจะใช้รูปแบบ I-Type คุณไม่จำเป็นต้องทำการเปลี่ยนแปลงใดๆ
การเข้ารหัสมีดังนี้
31..20 | 19..15 | 14..12 | 11..7 | 6..0 | opcode_name |
---|---|---|---|---|---|
imm12 | rs1 | 000 | ปุ่มเลือก | 000 0011 | lb |
imm12 | rs1 | 100 | rd | 000 0011 | lbu |
imm12 | rs1 | 001 | ปุ่มเลือก | 000 0011 | lh |
imm12 | rs1 | 101 | rd | 000 0011 | lhu |
imm12 | rs1 | 010 | rd | 000 0011 | lw |
func3 | รหัสดำเนินการ |
ทำการเปลี่ยนแปลง แล้วสร้างได้เลย ตรวจสอบเอาต์พุตที่สร้างขึ้น คุณสามารถตรวจสอบงานของคุณเทียบกับ riscv32i.bin_fmt ได้เช่นเดียวกับก่อนหน้านี้
บทแนะนำนี้จบแล้ว เราหวังว่าจะเป็นประโยชน์