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
, rd
và opcode
. Trường imm12
là
signed
, 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êni_type
. Mỗi hàm trích xuất được khai báo làinline
, lấyuint_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ạiuint_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ạiuint_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 RType
là unsigned
. 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 | và |
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
, func3
và opcode
. Đố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 add
và
sub
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ả rs2
và uimm5
đề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 đó: auipc
và
lui
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.
Thêm hướng dẫn về nhánh và chuyển đổi liên kết
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 func7
và rd
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 imm7
và imm5[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.