บทแนะนำตัวถอดรหัสคำสั่งไบนารี

วัตถุประสงค์ของบทแนะนำนี้ได้แก่

  • เรียนรู้โครงสร้างและไวยากรณ์ของไฟล์คำอธิบายรูปแบบไบนารี
  • เรียนรู้ว่าคำอธิบายรูปแบบไบนารีตรงกับคำอธิบาย ISA อย่างไร
  • เขียนคำอธิบายไบนารีสำหรับชุดย่อยของคำสั่ง RiscV RV32I

ภาพรวม

การเข้ารหัสคำสั่งไบนารีของ RiscV

การเข้ารหัสการสอนแบบไบนารีเป็นวิธีมาตรฐานในการเข้ารหัสคำสั่งสำหรับ ในไมโครโปรเซสเซอร์ ซึ่งปกติแล้วจะจัดเก็บอยู่ในไฟล์ปฏิบัติการ มักอยู่ในรูปแบบ ELF วิธีการอาจเป็นความกว้างคงที่หรือแปรผันก็ได้ ความกว้าง

โดยทั่วไป วิธีการจะใช้รูปแบบการเข้ารหัสกลุ่มเล็กๆ โดยแต่ละรูปแบบ ที่ปรับแต่งสำหรับประเภทคำสั่งที่เข้ารหัส ตัวอย่างเช่น ลงทะเบียน-ลงทะเบียน อาจใช้รูปแบบเดียวที่จะเพิ่มจำนวน opcode ที่ใช้ได้ คำแนะนำในการลงทะเบียนทันที ให้ใช้วิธีอื่นที่ลดจำนวน รหัส opcode ที่ใช้ได้สำหรับการเพิ่มขนาดของทันทีที่สามารถเข้ารหัส วิธีการกำหนดสาขาและการข้ามมักจะใช้รูปแบบที่เพิ่มขนาดให้ใหญ่สุด ทันทีเพื่อรองรับสาขาที่มีการชดเชยที่มากขึ้น

รูปแบบการสอนที่ใช้โดยคำสั่งที่เราต้องการถอดรหัสใน 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 ปุ่มเลือก รหัสดำเนินการ

รูปแบบ I-Type ที่ใช้สำหรับคำแนะนำในการลงทะเบียน คำแนะนำในการโหลด และ คำสั่ง jalr ทันที 12 บิต

31..20 19..15 14..12 11..7 6..0
12 5 3 5 7
imm12 rs1 func3 ปุ่มเลือก รหัสดำเนินการ

รูปแบบ I-Type เฉพาะทาง ใช้สำหรับกะการทำงานพร้อมคำสั่งได้ทันที 5 บิต ทันที:

31..25 24..20 19..15 14..12 11..7 6..0
7 5 5 3 5 7
func7 uimm5 rs1 func3 ปุ่มเลือก รหัสดำเนินการ

รูปแบบ U-Type ใช้สำหรับคำสั่งแบบยาวทันที (lui, auipc) 20 บิต ทันที:

31..12 11..7 6..0
20 5 7
uimm20 ปุ่มเลือก รหัสดำเนินการ

รูปแบบ B-Type ที่ใช้สำหรับ Branch แบบมีเงื่อนไข 12 บิตทันที

31 30..25 24..20 19..15 14..12 11..8 7 6..0
1 6 5 5 3 4 1 7
หนู หนู rs2 rs1 func3 หนู หนู รหัสดำเนินการ

รูปแบบ J-Type ที่ใช้สำหรับคำสั่ง jal และ 20 บิตทันที

31 30..21 20 19..12 11..7 6..0
1 10 1 8 5 7
หนู หนู หนู หนู ปุ่มเลือก รหัสดำเนินการ

รูปแบบ S-Type ที่ใช้สำหรับวิธีการของร้านค้า ใช้กับการตั้งค่า 12 บิตทันที

31..25 24..20 19..15 14..12 11..7 6..0
7 5 5 3 5 7
หนู rs2 rs1 func3 หนู รหัสดำเนินการ

จากรูปแบบเหล่านี้ คุณจะเห็นคำแนะนำเหล่านี้ทั้งหมดมีความยาว 32 บิต 7 บิตต่ำๆ ในแต่ละรูปแบบคือฟิลด์ opcode โปรดทราบด้วยว่า รูปแบบหลายๆ รูปแบบมีขนาดเท่ากันในทันที โดยบิตของรูปแบบจะมาจาก ส่วนต่างๆ ของการสอน จะเห็นได้ว่าเครื่องมือถอดรหัสแบบไบนารี รูปแบบที่กำหนด สามารถแสดงสิ่งนี้ได้

คำอธิบายการเข้ารหัสแบบไบนารี

การเข้ารหัสแบบไบนารีของคำสั่งจะแสดงในรูปแบบไบนารี (.bin_fmt) ไฟล์คำอธิบาย โดยจะอธิบายการเข้ารหัสแบบไบนารีของ ใน ISA เพื่อถอดรหัสคำสั่งในรูปแบบไบนารี ที่สร้างขึ้น ตัวถอดรหัสที่สร้างขึ้นจะกำหนด opcode และแยกค่าของ ตัวถูกดำเนินการและฟิลด์ที่ทันท่วงที เพื่อให้ข้อมูลที่จำเป็นสำหรับ ISA โปรแกรมถอดรหัสแบบไม่ต้องพึ่งการเข้ารหัสซึ่งอธิบายไว้ในบทแนะนำก่อนหน้านี้

ในบทแนะนำนี้ เราจะเขียนไฟล์คำอธิบายการเข้ารหัสแบบไบนารีสำหรับกลุ่มย่อย คำสั่งของ RiscV32I ที่จำเป็นต่อการจำลองคำสั่งที่ใช้ใน "สวัสดีโลก" ขนาดเล็ก ของโปรแกรม สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับ 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 ซึ่งกำหนด เนมสเปซที่จะวางโค้ดที่สร้างขึ้น อย่างที่สอง ฟิลด์ opcode_enum ซึ่งเป็นชื่อของประเภทการระบุ opcode ที่สร้างขึ้น โดยตัวถอดรหัส ISA ควรมีการอ้างอิงภายในโค้ดที่สร้างขึ้น ข้อ 3 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 ทั้งคู่มี แอตทริบิวต์ที่มีลายเซ็น/ไม่มีการรับรองเดียวกัน และอ้างถึงบิตของ Word ในวิธีเดียวกัน ด้วยชื่อ 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], ชื่อของ ประเภทการระบุ opcode ที่จะใช้ "OpcodeEnum" และคำแนะนำพื้นฐาน ประเภทการระบุ opcode ควรเป็นประเภทเดียวกันกับที่สร้างโดย ตัวถอดรหัสคำสั่งอิสระในรูปแบบที่ครอบคลุมในบทแนะนำเกี่ยวกับ ISA ตัวถอดรหัส

คำอธิบายการเข้ารหัสวิธีการแต่ละรายการประกอบด้วย 3 ส่วนดังนี้

  • ชื่อรหัสการดำเนินการ ซึ่งต้องเป็นชื่อเดียวกับที่ใช้ในวิธีการ คำอธิบายตัวถอดรหัสเพื่อให้เครื่องมือทั้งสอง ทำงานร่วมกัน
  • รูปแบบคำสั่งที่ใช้สำหรับรหัสการดำเนินการ นี่คือรูปแบบที่ ที่ใช้เพื่อตอบสนองการอ้างอิงไปยังบิตฟิลด์ในส่วนสุดท้าย
  • รายการค่าจำกัดฟิลด์บิตที่คั่นด้วยคอมมา, ==, !=, <, <=, > และ >= ทั้งหมดนั้นต้องเป็นจริงเพื่อให้ 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 host - สำหรับโฮสต์อื่น 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 Guard รวมถึงการประกาศเนมสเปซ ต่อจากนั้น มีฟังก์ชันตัวช่วยแบบเทมเพลตในเนมสเปซ 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 ปุ่มเลือก รหัสดำเนินการ

สิ่งแรกที่เราต้องทำคือการเพิ่มรูปแบบ เปิดแล้วเปิดเลย riscv32i.bin_fmt ในเครื่องมือแก้ไขที่คุณชื่นชอบ หลังจาก Inst32Format ช่วยให้ เพิ่มรูปแบบที่เรียกว่า RType ซึ่งมาจาก Inst32Format บิตฟิลด์ทั้งหมด ใน RType คือ unsigned ใช้ชื่อ ความกว้างบิต และลำดับ (จากซ้ายไปขวา) จากตารางด้านบนเพื่อกำหนดรูปแบบ หากคุณต้องการคำแนะนำหรือต้องการดู โซลูชันเต็มรูปแบบ คลิกที่นี่

ต่อไปเราจะเพิ่มวิธีการ วิธีการมีดังนี้

  • add - เพิ่มจำนวนเต็ม
  • and - บิตไวและ
  • or - บิตไวหรือ
  • sll - เลื่อนไปทางซ้ายเชิงตรรกะ
  • sltu - ตั้งค่าน้อยกว่า ไม่ได้ลงชื่อ
  • sub - การลบจำนวนเต็ม
  • xor - บิตไวส์ xor.

การเข้ารหัสมีดังนี้

31..25 24..20 19..15 14..12 11..7 6..0 ชื่อ opcode
000,0000 rs2 rs1 000 ปุ่มเลือก 011 0011 เพิ่ม
000,0000 rs2 rs1 111 ปุ่มเลือก 011 0011 และ
000,0000 rs2 rs1 110 ปุ่มเลือก 011 0011 หรือ
000,0000 rs2 rs1 001 ปุ่มเลือก 011 0011 Sll
000,0000 rs2 rs1 011 ปุ่มเลือก 011 0011 Sltu
010 0000 rs2 rs1 000 ปุ่มเลือก 011 0011 สำรอง
000,0000 rs2 rs1 100 ปุ่มเลือก 011 0011 Xor
func7 func3 รหัสดำเนินการ

เพิ่มคำจำกัดความคำสั่งเหล่านี้ก่อนคำสั่งอื่นๆ ใน กลุ่มวิธีการ RiscVInst32 สตริงไบนารีจะถูกระบุด้วยเครื่องหมายนำหน้า คำนำหน้า 0b (คล้ายกับ 0x สำหรับเลขฐานสิบหก) เพื่อให้คุณสามารถ อ่านสตริงยาวของเลขฐานสอง คุณสามารถแทรกเครื่องหมายคำพูดเดี่ยว ' เป็น ตัวคั่นหลักที่คุณเห็นพอดี

คำจำกัดความแต่ละคำสั่งจะมีข้อจำกัด 3 ประการ ได้แก่ 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 ในลำดับชั้นตัวถอดรหัสคำสั่ง แต่เนื่องจาก opcode สามารถ ก็ดูได้โดยตรงในตาราง โดยไม่มีการเปรียบเทียบเพิ่มเติม ในบรรทัดนี้ ใหม่ แทนที่จะเป็นการเรียกใช้ฟังก์ชันอื่น


เพิ่มคำสั่ง ALU ทันที

วิธีการชุดถัดไปที่เราจะเพิ่มคือคำสั่ง ALU ที่ใช้องค์ประกอบ แทนการจดทะเบียนอย่างใดอย่างหนึ่ง ซึ่งจะมีอยู่ 3 กลุ่ม คำสั่ง (ขึ้นอยู่กับฟิลด์ที่อยู่ติดกัน): คำสั่ง I-Type แบบทันที ด้วยการลงชื่อ 12 บิตทันที คำสั่ง I-Type เฉพาะในทันที สำหรับกะ และ U-Type ทันที โดยมีค่าทันทีแบบไม่ลงชื่อแบบ 20 บิต รูปแบบมีดังนี้

รูปแบบ I-Type ทันที:

31..20 19..15 14..12 11..7 6..0
12 5 3 5 7
imm12 rs1 func3 ปุ่มเลือก รหัสดำเนินการ

รูปแบบ I-Type เฉพาะทันที:

31..25 24..20 19..15 14..12 11..7 6..0
7 5 5 3 5 7
func7 uimm5 rs1 func3 ปุ่มเลือก รหัสดำเนินการ

รูปแบบ U-Type ทันทีมีดังนี้

31..12 11..7 6..0
20 5 7
uimm20 ปุ่มเลือก รหัสดำเนินการ

มีรูปแบบ I-Type อยู่แล้วใน riscv32i.bin_fmt จึงไม่มีความจำเป็น ให้เพิ่มรูปแบบนั้น

หากเราเปรียบเทียบรูปแบบ I-Type แบบพิเศษกับรูปแบบ R-Type ที่เรากำหนดไว้ ในแบบฝึกหัดก่อนหน้า เราจะเห็นความแตกต่างเพียงอย่างเดียวคือช่อง rs2 เปลี่ยนชื่อเป็น uimm5 แทนที่จะเพิ่มรูปแบบใหม่ทั้งหมด เราสามารถเสริม R-Type เราไม่สามารถเพิ่มช่องอีก เพราะเป็นการเพิ่มความกว้างของ รูปแบบนั้น แต่เราสามารถเพิ่มโฆษณาซ้อนทับ โฆษณาซ้อนทับเป็นชื่อแทนของชุด บิตในรูปแบบนี้ และสามารถใช้เพื่อรวมหลายลำดับย่อยของ เป็นเอนทิตีที่มีชื่อแยกต่างหาก ผลข้างเคียงคือโค้ดที่สร้างขึ้น จะมีฟังก์ชันการแยกสำหรับการวางซ้อนด้วย นอกเหนือจาก เหล่านั้นโดยตรง ในกรณีนี้ เมื่อทั้ง rs2 และ uimm5 ยกเลิกการลงนาม ไม่มีความแตกต่างมากนัก เพียงแค่บอกให้ทราบว่าใช้ฟิลด์นี้ โดยทันที หากต้องการเพิ่มการวางซ้อนชื่อ uimm5 ลงในรูปแบบ R-Type ให้เพิ่มพารามิเตอร์ ต่อท้ายฟิลด์สุดท้าย:

  overlays:
    unsigned uimm5[5] = rs2;

รูปแบบใหม่เดียวที่เราต้องเพิ่มคือรูปแบบ U-Type ก่อนที่เราจะเพิ่มส่วน เราจะมาพิจารณาคำสั่ง 2 รายการที่ใช้รูปแบบนั้น นั่นคือ auipc และ lui การตั้งค่าทั้ง 2 อย่างนี้จะเปลี่ยนค่า 20 บิตที่เปลี่ยนทันทีให้เหลือ 12 ก่อนที่จะใช้งาน เพื่อเพิ่ม PC ลงในเครื่อง (auipc) หรือเขียนลงในเครื่องลงทะเบียนโดยตรง (lui) การใช้การวางซ้อนเราสามารถทำให้ เปลี่ยนการคำนวณเล็กน้อยจากการดำเนินการสอนไปเป็นการสอน ถอดรหัส ก่อนอื่นให้เพิ่มรูปแบบตามช่องที่ระบุไว้ในตาราง ที่ด้านบน จากนั้นเราสามารถเพิ่มการวางซ้อนต่อไปนี้

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

ไวยากรณ์การวางซ้อนอนุญาตให้เราเชื่อมต่อ ไม่เพียงแต่ช่อง แต่รวมถึงลิเทอรัลเป็น ในกรณีนี้จะต่อเลขศูนย์ 12 ตัวเข้าด้วยกัน แล้วเลื่อนไปทางซ้าย ภายในวันที่ 12

คำสั่ง I-Type ที่เราต้องเพิ่ม ได้แก่

  • addi - เพิ่มทันที
  • andi - Bitwise และทันที
  • ori - Bitwise หรือในทันที
  • xori - Bitwise xor แบบทันที

การเข้ารหัสมีดังนี้

31..20 19..15 14..12 11..7 6..0 opcode_name
imm12 rs1 000 ปุ่มเลือก 001 0011 แอดดิ
imm12 rs1 111 ปุ่มเลือก 001 0011 Andi
imm12 rs1 110 ปุ่มเลือก 001 0011 Ori
imm12 rs1 100 ปุ่มเลือก 001 0011 โซรี
func3 รหัสดำเนินการ

คำสั่งประเภท R (ประเภท I-Type เฉพาะทาง) ที่เราต้องเพิ่ม ได้แก่

  • slli - เลื่อนไปทางซ้ายอย่างสมเหตุสมผลทันที
  • srai - เลื่อนเลขคณิตด้านขวาทันที
  • srli - เลื่อนไปทางขวาโดยใช้ตรรกะทันที

การเข้ารหัสมีดังนี้

31..25 24..20 19..15 14..12 11..7 6..0 ชื่อ opcode
000,0000 uimm5 rs1 001 ปุ่มเลือก 001 0011 SLli
010 0000 uimm5 rs1 101 ปุ่มเลือก 001 0011 Srai
000,0000 uimm5 rs1 101 ปุ่มเลือก 001 0011 Srli
func7 func3 รหัสดำเนินการ

คำสั่ง U-Type ที่เราต้องเพิ่ม ได้แก่

  • auipc - เพิ่มส่วนบนทันทีลงใน PC
  • lui - โหลดด้านบนทันที

การเข้ารหัสมีดังนี้

31..12 11..7 6..0 ชื่อ opcode
uimm20 ปุ่มเลือก 001 0111 auipc
uimm20 ปุ่มเลือก 011 0111 Lui
รหัสดำเนินการ

ทำการเปลี่ยนแปลง แล้วสร้างได้เลย ตรวจสอบเอาต์พุตที่สร้างขึ้น เพียงแค่ ก่อนหน้านี้ คุณสามารถตรวจสอบงานกับ riscv32i.bin_fmt


คำสั่งชุดถัดไปที่ต้องกำหนดคือสาขาแบบมีเงื่อนไข คำสั่ง วิธีการข้ามและลิงก์ และการลงทะเบียนการข้ามลิงก์ วิธีการ

สาขาแบบมีเงื่อนไขที่เราเพิ่มทั้งหมดจะใช้การเข้ารหัส 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-Type จะมีเลย์เอาต์เหมือนกับการเข้ารหัสประเภท R เลือกใช้ประเภทรูปแบบใหม่เพื่อให้สอดคล้องกับเอกสารประกอบของ RiscV แต่คุณอาจเพิ่มการวางซ้อนเพื่อรับ Branch ที่เหมาะสมก็ได้ การแทนที่ทันที โดยใช้ฟิลด์ func7 และ rd ของประเภท R การเข้ารหัส

จำเป็นต้องเพิ่มรูปแบบ BType ในช่องดังที่ระบุข้างต้น แต่ไม่จำเป็น เพียงพอ คุณจะเห็นว่า "ทันที" แบ่งออกเป็นช่องคำแนะนำ 2 ช่อง ยิ่งไปกว่านั้น คำสั่งสำหรับ Branch ไม่ได้ถือเป็นการเชื่อมโยงแบบง่ายของ ทั้ง 2 ช่อง แต่แต่ละช่องจะมีการแบ่งพาร์ติชันเพิ่มเติม และพาร์ติชันเหล่านี้ ต่อกันในลำดับที่ต่างกัน สุดท้าย ค่านั้นจะเลื่อนไปทางซ้าย 1 ตำแหน่งเพื่อรับค่าออฟเซ็ตแนว 16 บิต

ลำดับของบิตในคำแนะนำที่ใช้เพื่อสร้างค่าทันทีคือ 31, 7, 30..25, 11.8 ซึ่งสอดคล้องกับการอ้างอิงช่องย่อยต่อไปนี้ โดยที่ ดัชนีหรือช่วงจะระบุบิตในฟิลด์โดยเรียงตัวเลขจากขวาไปซ้าย เช่น imm7[6] หมายถึง msb ของ imm7 และ imm5[0] หมายถึง lsb ของ imm5

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

การทำให้การจัดการบิตนี้เป็นส่วนหนึ่งของคำสั่งของ Branch ด้วยตัวเองมี 2 ค่า ข้อบกพร่องใหญ่ๆ ประการแรก จะเชื่อมโยงการใช้งานฟังก์ชันความหมายกับ รายละเอียดในการสอนแบบไบนารี ข้อ 2 เพิ่มระยะเวลารันไทม์ โอเวอร์เฮด คำตอบคือให้เพิ่มการวางซ้อนในรูปแบบ BType ซึ่งรวมถึง ต่อท้าย "0" ให้คำนึงถึงฝั่งซ้าย

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

โปรดสังเกตว่ามีการทำเครื่องหมายการวางซ้อนแล้ว ดังนั้นจะมีการลงนามแบบเพิ่มเติมโดยอัตโนมัติ เมื่อดึงมาจากคำที่แนะนำ

วิธีการข้ามและลิงก์ (ทันที) ใช้การเข้ารหัสประเภท J ดังนี้

31..12 11..7 6..0
20 5 7
imm20 ปุ่มเลือก รหัสดำเนินการ

นี่เป็นรูปแบบง่ายๆ ที่เพิ่มได้ง่ายๆ แต่รูปแบบที่นำไปใช้ได้ทันที วิธีการไม่ได้ตรงไปตรงมาอย่างที่เห็น ลำดับบิตที่ใช้เพื่อ แบบฟอร์มทันทีแบบเต็มคือ: 31, 19..12, 20, 30..21 และสุดท้ายคือ เลื่อนไปทางซ้ายหนึ่งคำเพื่อจัดแนวคำแบบครึ่งคำ วิธีแก้ไขคือให้เพิ่ม ซ้อนทับ (21 บิตเพื่อใช้ Shift ด้านซ้าย) เป็นรูปแบบดังนี้

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

ดังที่คุณเห็น ไวยากรณ์สำหรับการวางซ้อนที่ระบุหลายช่วงใน ในรูปแบบย่อ นอกจากนี้ ถ้าไม่ได้ใช้ชื่อฟิลด์ ตัวเลขหมายถึงคำที่แนะนำ ดังนั้นคำข้างต้นอาจเป็น เขียนเป็น:

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

สุดท้าย ลิงก์ข้ามและลิงก์ (ลงทะเบียน) จะใช้รูปแบบ I-type ตามที่ใช้ ก่อนหน้านี้

รูปแบบ I-Type ทันที:

31..20 19..15 14..12 11..7 6..0
12 5 3 5 7
imm12 rs1 func3 ปุ่มเลือก รหัสดำเนินการ

ในครั้งนี้ จะไม่มีการเปลี่ยนแปลงใดๆ ที่ต้องทำกับรูปแบบ

วิธีการสำหรับสาขาที่เราต้องเพิ่มมีดังนี้

  • beq - สาขาถ้าเท่ากัน
  • bge - สาขาหากมากกว่าหรือเท่ากับ
  • bgeu - สาขา หากมีมากกว่าหรือเท่ากับ ไม่มีเครื่องหมาย
  • blt - สาขา หากน้อยกว่า
  • bltu - สาขา หากมีน้อยกว่าที่ไม่ได้ลงชื่อ
  • bne - สาขาหากไม่เท่ากัน

โดยมีการเข้ารหัสดังนี้

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 ชื่อ opcode
imm20 ปุ่มเลือก 110 1111 Jal
รหัสดำเนินการ

คำสั่ง jalr มีการเข้ารหัสดังนี้

31..20 19..15 14..12 11..7 6..0 opcode_name
imm12 rs1 000 ปุ่มเลือก 110 0111 Jalr
func3 รหัสดำเนินการ

ทำการเปลี่ยนแปลง แล้วสร้างได้เลย ตรวจสอบเอาต์พุตที่สร้างขึ้น เพียงแค่ ก่อนหน้านี้ คุณสามารถตรวจสอบงานกับ riscv32i.bin_fmt


เพิ่มวิธีการสำหรับร้านค้า

วิธีการของร้านใช้การเข้ารหัส S-Type ซึ่งเหมือนกับ B-Type ที่ใช้โดยคำสั่ง Branch ยกเว้นองค์ประกอบของ ทันที เราเลือกเพิ่มรูปแบบ 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 หน้าสุดท้ายคือลิงก์ตรง การต่อช่องสองฟิลด์ที่ติดกันในอนาคต ดังนั้นข้อกำหนดการซ้อนทับ ก็คือ:

  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
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 ปุ่มเลือก 000 0011 ลู
imm12 rs1 001 ปุ่มเลือก 000 0011 L
imm12 rs1 101 ปุ่มเลือก 000 0011 Lhu
imm12 rs1 010 ปุ่มเลือก 000 0011 lw
func3 รหัสดำเนินการ

ทำการเปลี่ยนแปลง แล้วสร้างได้เลย ตรวจสอบเอาต์พุตที่สร้างขึ้น เพียงแค่ ก่อนหน้านี้ คุณสามารถตรวจสอบงานกับ riscv32i.bin_fmt

บทแนะนำนี้จบลงเพียงเท่านี้ เราหวังว่าจะเป็นประโยชน์กับคุณ