Hướng dẫn về bộ giải mã lệnh nhị phân

Mục tiêu của hướng dẫn này là:

  • Tìm hiểu cấu trúc và cú pháp của tệp mô tả định dạng nhị phân.
  • Tìm hiểu xem mô tả định dạng nhị phân khớp với mô tả ISA như thế nào.
  • Viết các mô tả nhị phân cho tập hợp con của lệnh RiscV RV32I.

Tổng quan

Mã hoá lệnh nhị phân RiscV

Mã hoá lệnh nhị phân là cách tiêu chuẩn để mã hoá hướng dẫn cho trên bộ vi xử lý. Chúng thường được lưu trữ trong tệp thực thi, thường ở định dạng ELF. Hướng dẫn có thể là chiều rộng cố định hoặc biến chiều rộng.

Thông thường, hướng dẫn sử dụng một tập hợp nhỏ các định dạng mã hoá, với mỗi định dạng được tuỳ chỉnh cho loại hướng dẫn được mã hoá. Ví dụ: hãy đăng ký có thể sử dụng một định dạng giúp tối đa hoá số lượng mã hoạt động có sẵn, trong khi hướng dẫn đăng ký ngay lập tức sử dụng một hướng dẫn khác đánh đổi số lượng các mã hoạt động có sẵn để tăng kích thước của các tệp tức thì có thể được mã hóa. Hướng dẫn phân nhánh và chuyển gần như luôn sử dụng các định dạng tối đa hoá kích thước của tức thì để hỗ trợ các nhánh có độ lệch lớn hơn.

Các định dạng hướng dẫn được sử dụng bởi các hướng dẫn mà chúng tôi muốn giải mã trong RiscV của mình như sau:

Định dạng R-Type, dùng cho hướng dẫn khi đăng ký tài khoản:

31..25 24..20 19..15 14..12 11..7 6..0
7 5 5 3 5 7
func7 rs2 rs1 func3 ngày 5 mã hoạt động

Định dạng I-Type, được sử dụng cho hướng dẫn đăng ký ngay lập tức, hướng dẫn tải và lệnh jalr, tức thì 12 bit.

31..20 19..15 14..12 11..7 6..0
12 5 3 5 7
imm12 rs1 func3 ngày 5 mã hoạt động

Định dạng I-Type chuyên dụng, dùng cho ca làm việc có lệnh tức thì, 5 bit tức thì:

31..25 24..20 19..15 14..12 11..7 6..0
7 5 5 3 5 7
func7 uimm5 rs1 func3 ngày 5 mã hoạt động

Định dạng U-Type, dùng cho các lệnh dài ngay lập tức (lui, auipc), 20 bit tức thì:

31..12 11..7 6..0
20 5 7
uimm20 ngày 5 mã hoạt động

Định dạng loại B, dùng cho các nhánh có điều kiện, tức thì 12 bit.

31 30..25 24..20 19..15 14..12 11..8 7 6..0
1 6 5 5 3 4 1 7
im lặng im lặng rs2 rs1 func3 im lặng im lặng mã hoạt động

Định dạng J-Type, dùng cho lệnh jal, tức thì 20 bit.

31 30..21 20 19..12 11..7 6..0
1 10 1 8 5 7
im lặng im lặng im lặng im lặng ngày 5 mã hoạt động

Định dạng S-Type, được sử dụng cho hướng dẫn lưu trữ, 12 bit tức thì.

31..25 24..20 19..15 14..12 11..7 6..0
7 5 5 3 5 7
im lặng rs2 rs1 func3 im lặng mã hoạt động

Như bạn có thể thấy trong các định dạng này, tất cả các hướng dẫn này đều dài 32 bit và 7 bit thấp ở mỗi định dạng là trường mã vận hành. Ngoài ra, xin lưu ý rằng trong khi một số định dạng có cùng kích thước ngay lập tức, các bit của chúng được lấy từ các phần khác nhau của hướng dẫn. Như chúng ta sẽ thấy, bộ giải mã nhị phân có thể thể hiện điều này.

Mô tả mã hóa nhị phân

Mã hoá nhị phân của lệnh được thể hiện ở định dạng nhị phân (.bin_fmt). Nó mô tả mã hoá nhị phân của các lệnh trong ISA để bộ giải mã lệnh định dạng nhị phân có thể tạo. Bộ giải mã được tạo sẽ xác định mã hoạt động, trích xuất giá trị của toán hạng và các trường tức thì, nhằm cung cấp thông tin mà ISA cần bộ giải mã không phụ thuộc vào mã hoá được mô tả trong hướng dẫn trước.

Trong hướng dẫn này, chúng tôi sẽ viết tệp mô tả mã hoá nhị phân cho một tập hợp con các hướng dẫn RiscV32I cần thiết để mô phỏng các hướng dẫn sử dụng trong nhỏ "Hello World" ("Xin chào thế giới") . Để biết thêm chi tiết về RiscV ISA, hãy xem Quy cách của Risc-V{.external}.

Hãy bắt đầu bằng cách mở tệp: riscv_bin_decoder/riscv32i.bin_fmt.

Nội dung của tệp được chia thành nhiều phần.

Đầu tiên, là định nghĩa 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;
};

Định nghĩa bộ giải mã của chúng tôi chỉ định tên của bộ giải mã RiscV32I, cũng như bốn thông tin bổ sung. Đầu tiên là namespace, xác định không gian tên nơi mã được tạo sẽ được đặt. Thứ hai, opcode_enum, đặt tên cho loại liệt kê mã vận hành được tạo theo bộ giải mã ISA sẽ được tham chiếu trong mã đã tạo. Thứ ba, includes {} chỉ định bao gồm các tệp cần thiết cho mã được tạo cho bộ giải mã này. Trong trường hợp này, đây là tệp được tạo bởi bộ giải mã ISA từ hướng dẫn trước. Bạn có thể chỉ định các tệp "bao gồm" bổ sung trong một includes {} trong phạm vi toàn cục định nghĩa. Điều này rất hữu ích nếu nhiều bộ giải mã được xác định và tất cả chúng cần để đưa vào một số tệp tương tự. Thứ tư là danh sách tên của hướng dẫn tạo nên các lệnh mà bộ giải mã được tạo. Trong trường hợp chỉ có một: RiscVInst32.

Tiếp theo là 3 định nghĩa định dạng. Những thông tin này đại diện cho các hướng dẫn khác nhau các định dạng cho một từ hướng dẫn 32 bit được sử dụng theo các lệnh đã xác định trong tệp.

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

Lệnh đầu tiên xác định định dạng lệnh rộng 32 bit có tên Inst32Format, hai trường: bits (rộng 25 bit) và opcode (rộng 7 bit). Mỗi trường unsigned, nghĩa là giá trị này sẽ được mở rộng bằng 0 khi được trích xuất và đặt vào kiểu số nguyên C++. Tổng chiều rộng của các trường bit phải bằng với chiều rộng của định dạng. Công cụ sẽ báo lỗi nếu có bất đồng. Định dạng này không bắt nguồn từ bất kỳ định dạng nào khác, vì vậy được coi là định dạng cấp cao nhất.

Phương thức thứ hai xác định định dạng lệnh rộng 32 bit có tên là IType, suy ra từ Inst32Format, giúp hai định dạng này có liên quan với nhau. Định dạng chứa 5 các trường: imm12, rs1, func3, rdopcode. Trường imm12signed, nghĩa là giá trị đó sẽ được mở rộng khi đó là được trích xuất và đặt vào kiểu số nguyên C++. Xin lưu ý rằng cả IType.opcode đều có cùng một thuộc tính đã ký/chưa ký và tham chiếu đến cùng các bit từ hướng dẫn dưới tên Inst32Format.opcode.

Định dạng thứ ba là định dạng tuỳ chỉnh chỉ được fence sử dụng là một hướng dẫn đã được chỉ định và chúng ta không có phải lo lắng trong hướng dẫn này.

Lưu ý quan trọng: Sử dụng lại tên trường ở nhiều định dạng có liên quan, miễn là các tên đó biểu thị cùng một bit và có cùng thuộc tính đã ký/chưa ký.

Sau phần định dạng trong riscv32i.bin_fmt sẽ xuất hiện một nhóm lệnh định nghĩa. Mọi hướng dẫn trong một nhóm hướng dẫn phải có cùng hướng dẫn độ dài bit và sử dụng định dạng suy ra (có thể gián tiếp) từ cùng một định dạng hướng dẫn cấp cao nhất. Khi một ISA có thể đưa ra hướng dẫn với các hướng dẫn khác nhau thì nhóm chỉ dẫn khác sẽ được sử dụng cho mỗi độ dài. Ngoài ra, nếu bộ giải mã ISA mục tiêu phụ thuộc vào một chế độ thực thi, chẳng hạn như Arm so với Thumb cần có một nhóm hướng dẫn riêng cho từng chế độ. Chiến lược phát hành đĩa đơn Trình phân tích cú pháp bin_fmt tạo ra một bộ giải mã nhị phân cho từng nhóm lệnh.

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

Nhóm lệnh định nghĩa tên RiscV32I, chiều rộng [32], tên của loại liệt kê mã vận hành để sử dụng "OpcodeEnum" và một lệnh cơ sở . Loại liệt kê mã vận hành phải giống như loại do giá trị bộ giải mã lệnh độc lập định dạng được đề cập trong hướng dẫn trên ISA bộ giải mã.

Mỗi nội dung mô tả mã hoá lệnh bao gồm 3 phần:

  • Tên mã hoạt động (opcode) phải giống với tên đã dùng trong hướng dẫn bộ giải mã để cả hai hoạt động cùng nhau.
  • Định dạng lệnh sử dụng cho mã vận hành. Đây là định dạng được dùng để đáp ứng các tham chiếu đến trường bit trong phần cuối cùng.
  • Danh sách các điều kiện ràng buộc đối với trường bit được phân tách bằng dấu phẩy, ==, !=, <, <=, >, và >= đều phải đúng để mã vận hành khớp thành công với từ chỉ dẫn.

Trình phân tích cú pháp .bin_fmt sử dụng tất cả thông tin này để tạo bộ giải mã:

  • Cung cấp các hàm trích xuất (đã ký/chưa ký) phù hợp với từng bit ở mọi định dạng. Các hàm trích xuất được đặt trong không gian tên được đặt tên theo phiên bản viết thường của tên định dạng. Ví dụ: hàm trích xuất cho định dạng IType được đặt trong không gian tên i_type. Mỗi hàm trích xuất được khai báo là inline, lấy uint_t hẹp nhất loại chứa chiều rộng của định dạng và trả về int_t hẹp nhất (đối với loại đã ký), loại uint_t (đối với tên chưa ký), chứa trường đã trích xuất chiều rộng. Ví dụ:
inline uint8_t ExtractOpcode(uint32_t value) {
  return value & 0x7f;
}
  • Một hàm giải mã cho từng nhóm lệnh. Phương thức này trả về một giá trị thuộc kiểu OpcodeEnum và lấy loại uint_t hẹp nhất có chiều rộng là định dạng nhóm lệnh.

Thực hiện quá trình dựng ban đầu

Thay đổi thư mục thành riscv_bin_decoder và xây dựng dự án bằng sau đây:

$ cd riscv_bin_decoder
$ bazel build :all

Bây giờ, hãy thay đổi thư mục của bạn về thư mục gốc của kho lưu trữ, sau đó hãy cùng xem tại các nguồn đã được tạo. Để làm được việc đó, hãy thay đổi thư mục thành bazel-out/k8-fastbuild/bin/riscv_bin_decoder (giả sử bạn đang dùng x86 host - đối với các máy chủ lưu trữ khác, k8-fastbuild sẽ là một chuỗi khác).

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

Tệp tiêu đề đã tạo (.h)

Mở tệp riscv32i_bin_decoder.h. Phần đầu tiên của tệp chứa tiêu chuẩn Các biện pháp bảo vệ nguyên mẫu, bao gồm tệp, khai báo không gian tên. Sau đó có một hàm trợ giúp theo mẫu trong không gian tên internal. Hàm này được dùng để trích xuất các trường bit từ những định dạng quá dài không phù hợp với C++ 64 bit số nguyên.

#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

Theo phần đầu tiên, có một tập hợp gồm ba không gian tên, mỗi không gian tên tương ứng với một không gian tên trong số các nội dung khai báo format trong tệp riscv32i.bin_fmt:


namespace fence {

...

}  // namespace fence

namespace i_type {

...

}  // namespace i_type

namespace inst32_format {

...

}  // namespace inst32_format

Trong mỗi không gian tên này, hàm trích xuất trường bit inline cho từng trường bit ở định dạng đó. Ngoài ra, định dạng cơ sở sao chép các hàm trích xuất từ các định dạng con mà 1) các tên trường chỉ xuất hiện trong một tên trường duy nhất hoặc 2) mà tên trường tham chiếu đến cùng loại trường (vị trí đã ký/chưa ký và vị trí bit) ở mỗi định dạng. Thao tác này cho phép các trường bit mô tả giống nhau bit được trích xuất bằng các hàm trong không gian tên định dạng cấp cao nhất.

Dưới đây là các hàm trong không gian tên 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

Cuối cùng, việc khai báo hàm của hàm giải mã cho lệnh nhóm RiscVInst32 được khai báo. Hàm này lấy 32 bit không dấu làm giá trị của từ hướng dẫn và trả về thành phần của lớp liệt kê OpcodeEnum kết quả trùng khớp hoặc OpcodeEnum::kNone nếu không có kết quả trùng khớp.

OpcodeEnum DecodeRiscVInst32(uint32_t inst_word);

Tệp nguồn đã tạo (.cc)

Bây giờ, hãy mở riscv32i_bin_decoder.cc. Phần đầu tiên của tệp chứa #include và khai báo không gian tên, theo sau là hàm giải mã khai báo:

#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 được dùng cho các hành động giải mã trống (ví dụ: các hành động) trả về OpcodeEnum::kNone. Ba hàm còn lại tạo thành bộ giải mã được tạo. Bộ giải mã tổng thể hoạt động theo kiểu phân cấp. Tập hợp số bit trong từ lệnh được tính toán để phân biệt giữa hoặc nhiều nhóm hướng dẫn ở cấp cao nhất. Các bit không cần tiếp giáp nhau. Số lượng bit xác định kích thước của bảng tra cứu được điền sẵn hàm giải mã cấp hai. Bạn sẽ thấy biểu tượng này trong của tệp:

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,

    ...
};

Cuối cùng, các hàm giải mã được xác định:

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

Trong trường hợp này, khi chỉ có 4 lệnh được xác định, thì chỉ có một một cấp độ giải mã và bảng tra cứu rất thưa thớt. Như hướng dẫn cấu trúc của bộ giải mã sẽ thay đổi và số lượng cấp trong có thể tăng lên.


Thêm hướng dẫn về ALU đăng ký để đăng ký

Giờ là lúc thêm một số hướng dẫn mới vào tệp riscv32i.bin_fmt. Chiến lược phát hành đĩa đơn nhóm hướng dẫn đầu tiên là các hướng dẫn ALU đăng ký như add, and, v.v. Trên RiscV32, tất cả đều sử dụng lệnh nhị phân kiểu R định dạng:

31..25 24..20 19..15 14..12 11..7 6..0
7 5 5 3 5 7
func7 rs2 rs1 func3 ngày 5 mã hoạt động

Điều đầu tiên chúng ta cần làm là thêm định dạng. Hãy tiếp tục và mở ra riscv32i.bin_fmt trong trình chỉnh sửa yêu thích của bạn. Ngay sau khi Inst32Format cho phép thêm một định dạng có tên là RType (được lấy từ Inst32Format). Tất cả các trường bit trong RTypeunsigned. Sử dụng tên, độ rộng bit và thứ tự (từ trái sang phải) từ bảng trên để xác định định dạng. Nếu bạn cần gợi ý hoặc muốn xem giải pháp đầy đủ, nhấp vào đây.

Tiếp theo, chúng ta cần thêm hướng dẫn. Nội dung hướng dẫn như sau:

  • add – thêm số nguyên.
  • and – bitwise và.
  • or – bitwise hoặc.
  • sll – di chuyển sang trái logic.
  • sltu – đặt dấu nhỏ hơn, không dấu.
  • sub – phép trừ số nguyên.
  • xor – xor theo bit.

Các kiểu mã hoá của chúng là:

31..25 24..20 19..15 14..12 11..7 6..0 tên mã hoạt động
000 0000 rs2 rs1 000 ngày 5 011 0011 thêm
000 0000 rs2 rs1 111 ngày 011 0011
000 0000 rs2 rs1 110 ngày 5 011 0011 hoặc
000 0000 rs2 rs1 001 ngày 5 011 0011 sll
000 0000 rs2 rs1 011 ngày 5 011 0011 sltu
010 0000 rs2 rs1 000 ngày 5 011 0011 sub
000 0000 rs2 rs1 100 ngày 5 011 0011 xor
func7 func3 mã hoạt động

Hãy thêm các định nghĩa hướng dẫn này trước các hướng dẫn khác trong RiscVInst32 nhóm hướng dẫn. Chuỗi nhị phân được chỉ định bằng ký tự đứng đầu tiền tố của 0b (tương tự 0x đối với số thập lục phân). Để giúp bạn dễ dàng đọc chuỗi dài gồm các chữ số nhị phân, bạn cũng có thể chèn dấu nháy đơn ' dưới dạng dấu phân tách bằng chữ số mà bạn thấy phù hợp.

Mỗi định nghĩa hướng dẫn này sẽ có 3 hạn chế, cụ thể là về func7, func3opcode. Đối với tất cả trừ sub, quy tắc ràng buộc func7 sẽ là:

func7 == 0b000'0000

Quy tắc ràng buộc func3 khác nhau giữa hầu hết các lệnh. Dành cho addsub thì:

func3 == 0b000

Quy tắc ràng buộc opcode như nhau đối với từng lệnh trong những lệnh sau:

opcode == 0b011'0011

Nhớ kết thúc mỗi dòng bằng dấu chấm phẩy ;.

Giải pháp hoàn chỉnh là tại đây.

Bây giờ, hãy tiếp tục và tạo dự án của bạn như trước đó, đồng thời mở tệp Tệp riscv32i_bin_decoder.cc. Bạn sẽ thấy các chức năng giải mã bổ sung đã được tạo để xử lý các hướng dẫn mới. Chủ yếu là tương tự như các URL đã được tạo trước đó, nhưng hãy xem xét DecodeRiscVInst32_0_c dùng cho phương thức giải mã 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];
}

Trong hàm này có một bảng giải mã tĩnh được tạo và giá trị tra cứu là được trích xuất từ từ hướng dẫn để chọn chỉ mục thích hợp. Điều này sẽ thêm lớp thứ hai trong hệ phân cấp bộ giải mã lệnh, nhưng vì mã vận hành có thể là được tra cứu trực tiếp trong một bảng mà không có thêm phép so sánh, nó nằm trong thay vì yêu cầu một lệnh gọi hàm khác.


Thêm ngay lập tức hướng dẫn ALU

Tập hợp hướng dẫn tiếp theo mà chúng tôi sẽ thêm là hướng dẫn ALU sử dụng giá trị tức thì thay vì một trong các thanh ghi. Có 3 nhóm trong số này hướng dẫn (dựa trên trường tức thì): hướng dẫn ngay lập tức I-Type với 12 bit đã ký ngay lập tức, các hướng dẫn I-Type chuyên dụng ngay lập tức cho các ca và kiểu chữ U ngay lập tức, với giá trị tức thì là 20 bit không dấu. Các định dạng được hiển thị dưới đây:

Định dạng tức thì kiểu chữ I:

31..20 19..15 14..12 11..7 6..0
12 5 3 5 7
imm12 rs1 func3 ngày 5 mã hoạt động

Định dạng tức thì chuyên biệt cho I-Type:

31..25 24..20 19..15 14..12 11..7 6..0
7 5 5 3 5 7
func7 uimm5 rs1 func3 ngày 5 mã hoạt động

Định dạng tức thì của kiểu chữ U:

31..12 11..7 6..0
20 5 7
uimm20 ngày 5 mã hoạt động

Định dạng I-Type đã có trong riscv32i.bin_fmt, do đó không cần thêm định dạng đó.

Nếu chúng ta so sánh định dạng I-Type chuyên biệt với định dạng R-Type mà chúng ta đã xác định trong trong bài tập trước, chúng ta thấy rằng sự khác biệt duy nhất là các trường rs2 được đổi tên thành uimm5. Thay vì thêm một định dạng hoàn toàn mới, chúng ta có thể tăng cường Định dạng R-Type. Chúng tôi không thể thêm trường khác, vì điều đó sẽ làm tăng chiều rộng của định dạng nhưng chúng ta có thể thêm lớp phủ. Lớp phủ là bí danh của một tập hợp các bit định dạng, và có thể được dùng để kết hợp nhiều phần phụ của dữ liệu thành một thực thể được đặt tên riêng biệt. Tác dụng phụ là mã được tạo giờ đây cũng sẽ bao gồm hàm trích xuất cho lớp phủ, ngoài danh sách đó cho các trường. Trong trường hợp này, khi cả rs2uimm5 đều chưa được ký không tạo ra nhiều khác biệt ngoại trừ việc làm rõ rằng trường này được sử dụng ngay lập tức. Để thêm lớp phủ có tên uimm5 vào định dạng R-Type, hãy thêm phương thức sau trường cuối cùng:

  overlays:
    unsigned uimm5[5] = rs2;

Định dạng mới duy nhất chúng tôi cần thêm là định dạng U-Type. Trước khi chúng tôi thêm định dạng đó, hãy xem xét 2 hướng dẫn sử dụng định dạng đó: auipclui Cả hai đều chuyển giá trị tức thì 20 bit còn lại 12 trước khi sử dụng thêm máy tính vào máy tính (auipc) hoặc ghi trực tiếp vào thanh ghi (lui). Bằng cách sử dụng lớp phủ, chúng ta có thể cung cấp phiên bản đã dịch chuyển trước của chuyển một chút công việc tính toán từ thực thi lệnh sang hướng dẫn giải mã. Trước tiên, hãy thêm định dạng theo các trường được chỉ định trong bảng ở trên. Sau đó, chúng ta có thể thêm lớp phủ sau đây:

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

Cú pháp lớp phủ cho phép chúng ta nối (không chỉ các trường) mà còn chứa các giá trị cố định dưới dạng tốt. Trong trường hợp này, chúng ta nối nó với 12 số 0, để nó dịch chuyển sang trái chậm nhất vào 12.

Hướng dẫn về Loại I mà chúng tôi cần thêm là:

  • addi – Thêm ngay lập tức.
  • andi – Đảo bit và có ngay lập tức.
  • ori – Bitwise hoặc với ngay lập tức.
  • xori – Bitwise xor với giá trị tức thì.

Các kiểu mã hoá của chúng là:

31..20 19..15 14..12 11..7 6..0 opcode_name
imm12 rs1 000 ngày 5 001 0011 thêm
imm12 rs1 111 ngày 5 001 0011 và tôi
imm12 rs1 110 ngày 5 001 0011 ori
imm12 rs1 100 ngày 5 001 0011 xori
func3 mã hoạt động

Hướng dẫn R-Type (Loại I chuyên dụng) mà chúng tôi cần thêm là:

  • slli – Di chuyển sang trái logic ngay lập tức.
  • srai – Di chuyển số học sang phải theo tức thì.
  • srli – Di chuyển logic sang phải ngay lập tức.

Các kiểu mã hoá của chúng là:

31..25 24..20 19..15 14..12 11..7 6..0 tên mã hoạt động
000 0000 uimm5 rs1 001 ngày 5 001 0011 slli
010 0000 uimm5 rs1 101 ngày 5 001 0011 srai
000 0000 uimm5 rs1 101 ngày 5 001 0011 srli
func7 func3 mã hoạt động

Hướng dẫn về Loại chữ U mà chúng tôi cần thêm là:

  • auipc – Thêm phần trên ngay bên trên vào máy tính.
  • lui – Tải phía trên ngay lập tức.

Các kiểu mã hoá của chúng là:

31..12 11..7 6..0 tên mã hoạt động
uimm20 ngày 5 001 0111 auipc
uimm20 ngày 5 011 0111 lui
mã hoạt động

Hãy tiếp tục và điều chỉnh rồi tạo bản dựng. Kiểm tra dữ liệu đầu ra đã tạo. Chỉ như trước đây, bạn có thể đối chiếu công việc của mình với riscv32i.bin_fmt.


Tập lệnh tiếp theo cần được xác định là nhánh có điều kiện chỉ dẫn, hướng dẫn nhảy-và-liên-kết, và thanh ghi nhảy-và-liên-kết chỉ dẫn.

Tất cả các nhánh có điều kiện mà chúng ta đang thêm đều sử dụng phương thức mã hoá 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 mã hoạt động

Mặc dù mã hoá B-Type có bố cục giống hệt với mã hoá R-Type, chúng tôi hãy chọn sử dụng một loại định dạng mới cho phù hợp với tài liệu RiscV. Nhưng bạn cũng có thể chỉ cần thêm lớp phủ để lấy nhánh thích hợp chuyển vị trí ngay lập tức, sử dụng các trường func7rd của R-Type mã hoá.

Việc thêm định dạng BType với các trường như được chỉ định ở trên là cần thiết, nhưng không là đủ. Như bạn có thể thấy, dữ liệu tức thì được chia thành 2 trường lệnh. Hơn nữa, hướng dẫn nhánh không coi đây là kết hợp đơn giản của vào hai trường. Thay vào đó, mỗi trường được phân vùng thêm và các phân vùng này được nối theo thứ tự khác nhau. Cuối cùng, giá trị đó được dịch chuyển sang trái một để có được độ lệch căn chỉnh 16 bit.

Chuỗi các bit trong từ lệnh dùng để tạo lập trình tức thì là: 31, 7, 30..25, 11..8. Điều này tương ứng với các tham chiếu trường phụ sau đây, trong đó chỉ mục hoặc dải ô chỉ định các bit trong trường, được đánh số từ phải sang trái, tức là imm7[6] đề cập đến msb của imm7imm5[0] là lsb của imm5.

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

Biến thao tác bit này thành một phần của hướng dẫn nhánh có hai nhược điểm lớn. Đầu tiên, nó gắn việc triển khai hàm ngữ nghĩa với các chi tiết trong biểu diễn chỉ dẫn nhị phân. Thứ hai, tính năng này tăng thêm thời gian chạy chi phí. Câu trả lời là thêm một lớp phủ vào định dạng BType, bao gồm ở cuối là '0' để tính đến sự chuyển dịch bên trái.

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

Lưu ý rằng lớp phủ đã được ký, vì vậy nó sẽ tự động được mở rộng ký khi trích xuất từ từ hướng dẫn.

Hướng dẫn nhảy và liên kết (tức thì) sử dụng kiểu mã hoá J-Type:

31..12 11..7 6..0
20 5 7
imm20 ngày 5 mã hoạt động

Đây cũng là định dạng dễ thêm vào, nhưng xin nhắc lại, định dạng tức thì được sử dụng bởi không đơn giản như bạn vẫn tưởng. Chuỗi bit được dùng để hình thức đầy đủ ngay lập tức là: 31, 19...12, 20, 30..21 và ngay lập tức cuối cùng là dịch chuyển sang trái một chút để căn chỉnh nửa từ. Giải pháp là thêm một lớp phủ (21 bit để tính đến sự dịch chuyển sang trái) thành định dạng:

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

Như bạn có thể thấy, cú pháp cho lớp phủ hỗ trợ việc chỉ định nhiều dải ô trong một ở định dạng viết tắt. Ngoài ra, nếu không có tên trường nào được sử dụng, bit các số tham chiếu đến chính từ hướng dẫn, vì vậy, câu ở trên cũng có thể được viết là:

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

Cuối cùng, Jump-and-link (đăng ký) sử dụng định dạng I-type như đã sử dụng trước đây.

Định dạng tức thì kiểu chữ I:

31..20 19..15 14..12 11..7 6..0
12 5 3 5 7
imm12 rs1 func3 ngày 5 mã hoạt động

Lần này, bạn không cần thay đổi định dạng.

Hướng dẫn nhánh mà chúng tôi cần thêm là:

  • beq – Nhánh nếu bằng.
  • bge – Nhánh nếu lớn hơn hoặc bằng.
  • bgeu – Nhánh nếu lớn hơn hoặc bằng chưa ký.
  • blt – Nhánh nếu ít hơn.
  • bltu – Nhánh nếu chưa được ký.
  • bne – Nhánh nếu không bằng.

Các mã này được mã hoá như sau:

31..25 24..20 19..15 14..12 11..7 6..0 tên mã hoạt động
imm7 rs2 rs1 000 imm5 110 0011 beq
imm7 rs2 rs1 101 imm5 110 0011 lỗ chân lông
imm7 rs2 rs1 111 imm5 110 0011 châu âu
imm7 rs2 rs1 100 imm5 110 0011 blt
imm7 rs2 rs1 110 imm5 110 0011 bltu
imm7 rs2 rs1 001 imm5 110 0011 cám
func3 mã hoạt động

Lệnh jal được mã hoá như sau:

31..12 11..7 6..0 tên mã hoạt động
imm20 ngày 5 110 1111 lời nói của jal
mã hoạt động

Lệnh jalr được mã hoá như sau:

31..20 19..15 14..12 11..7 6..0 opcode_name
imm12 rs1 000 ngày 5 110 0111 jalr
func3 mã hoạt động

Hãy tiếp tục và điều chỉnh rồi tạo bản dựng. Kiểm tra dữ liệu đầu ra đã tạo. Chỉ như trước đây, bạn có thể đối chiếu công việc của mình với riscv32i.bin_fmt.


Thêm hướng dẫn về cửa hàng

Hướng dẫn trong cửa hàng sử dụng phương thức mã hoá S-Type, giống hệt với phương thức mã hoá B-Type phương thức mã hoá được sử dụng theo các lệnh ở nhánh, ngoại trừ thành phần của đoạn mã ngay lập tức. Chúng tôi chọn thêm định dạng SType để luôn phù hợp với RiscV tài liệu.

31..25 24..20 19..15 14..12 11..7 6..0
7 5 5 3 5 7
imm7 rs2 rs1 func3 imm5 mã hoạt động

Trong trường hợp định dạng SType, phương thức ngay lập tức sẽ là một đường thẳng nối chuyển tiếp hai trường tức thì, để thông số kỹ thuật của lớp phủ đơn giản là:

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

Lưu ý rằng không yêu cầu thông số phạm vi bit khi nối toàn bộ trường.

Hướng dẫn dành cho cửa hàng được mã hoá như sau:

31..25 24..20 19..15 14..12 11..7 6..0 tên mã hoạt động
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 mã hoạt động

Hãy tiếp tục và điều chỉnh rồi tạo bản dựng. Kiểm tra dữ liệu đầu ra đã tạo. Chỉ như trước đây, bạn có thể đối chiếu công việc của mình với riscv32i.bin_fmt.


Thêm hướng dẫn tải

Hướng dẫn tải sử dụng định dạng I-Type. Bạn không cần thực hiện thay đổi nào ở đó.

Các kiểu mã hoá bao gồm:

31..20 19..15 14..12 11..7 6..0 opcode_name
imm12 rs1 000 ngày 5 000 0011 lb
imm12 rs1 100 ngày 5 000 0011 pao
imm12 rs1 001 ngày 5 000 0011 phím lh
imm12 rs1 101 ngày 5 000 0011 lhu
imm12 rs1 010 ngày 5 000 0011 lw
func3 mã hoạt động

Hãy tiếp tục và điều chỉnh rồi tạo bản dựng. Kiểm tra dữ liệu đầu ra đã tạo. Chỉ như trước đây, bạn có thể đối chiếu công việc của mình với riscv32i.bin_fmt.

Chúng tôi đã kết thúc hướng dẫn này, chúng tôi hy vọng hướng dẫn này sẽ hữu ích.