Bộ giải mã RiscV ISA

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

  • Tìm hiểu cách trình bày chỉ dẫn trong trình mô phỏng MPACT-Sim.
  • Tìm hiểu cấu trúc và cú pháp của tệp mô tả ISA.
  • Viết nội dung mô tả ISA cho tập hợp con của hướng dẫn RiscV RV32I

Tổng quan

Trong MPACT-Sim, các lệnh đích được giải mã và lưu trữ trong một biểu diễn để giúp thông tin của họ sẵn có hơn và ngữ nghĩa thực thi nhanh hơn. Các thực thể chỉ dẫn này được lưu vào bộ nhớ đệm trong một chỉ dẫn bộ nhớ đệm để giảm số lần các lệnh được thực thi thường xuyên thực thi.

Lớp hướng dẫn

Trước khi bắt đầu, bạn nên xem xét một chút về cách thức được trình bày bằng MPACT-Sim. Lớp Instruction được định nghĩa trong mpact-sim/mpact/sim/generic/instruction.h.

Thực thể của lớp Hướng dẫn chứa tất cả thông tin cần thiết để mô phỏng lệnh khi lệnh đó được "thực thi", chẳng hạn như:

  1. Địa chỉ hướng dẫn, kích thước hướng dẫn được mô phỏng, tức là kích thước ở dạng .text.
  2. Mã hoạt động hướng dẫn.
  3. Thuộc tính con trỏ giao diện toán hạng (nếu có).
  4. Vectơ của con trỏ giao diện toán hạng nguồn.
  5. Vectơ của con trỏ giao diện toán hạng đích.
  6. Chức năng ngữ nghĩa có thể gọi.
  7. Con trỏ đến đối tượng trạng thái kiến trúc.
  8. Con trỏ đến đối tượng ngữ cảnh.
  9. Con trỏ đến các thực thể con và Hướng dẫn tiếp theo.
  10. Chuỗi tháo rời.

Các thực thể này thường được lưu trữ trong bộ nhớ đệm hướng dẫn (thực thể) và được sử dụng lại mỗi khi thực thi lại hướng dẫn. Điều này giúp cải thiện hiệu suất trong thời gian chạy.

Ngoại trừ con trỏ đến đối tượng ngữ cảnh, tất cả đều được điền bằng bộ giải mã lệnh được tạo từ mô tả ISA. Để làm việc này bạn không cần biết chi tiết về các mục này vì chúng tôi sẽ không trực tiếp sử dụng chúng. Thay vào đó, bạn cần nắm rõ cách sử dụng là đủ.

Hàm ngữ nghĩa có thể gọi là đối tượng hàm/phương thức/hàm C++ (bao gồm cả lambda) triển khai ngữ nghĩa của hướng dẫn. Cho Ví dụ: đối với một lệnh add, lệnh này tải từng toán hạng nguồn, thêm hai toán hạng và ghi kết quả vào một toán hạng đích. Chủ đề về hàm ngữ nghĩa sẽ được trình bày chi tiết trong hướng dẫn về hàm ngữ nghĩa.

Toán hạng lệnh

Lớp lệnh bao gồm con trỏ đến 3 loại giao diện toán hạng: vị từ, nguồn và đích đến. Các giao diện này cho phép hàm ngữ nghĩa được viết độc lập với loại thực tế của chỉ dẫn cơ bản toán hạng. Ví dụ: việc truy cập giá trị của các thanh ghi và các giá trị tức thì được thực hiện thông qua cùng một giao diện. Điều này có nghĩa là các hướng dẫn thực hiện giống nhau nhưng trên các toán hạng khác nhau (ví dụ: thanh ghi so với đối tượng biến đổi) có thể là được triển khai bằng cùng một hàm ngữ nghĩa.

Giao diện toán hạng vị từ, đối với những ISA hỗ trợ dự đoán thực thi lệnh (đối với các ISA khác thì giá trị này là rỗng) được dùng để xác định xem một chỉ dẫn đã cho cần thực thi dựa trên giá trị boolean của vị từ.

// The predicte operand interface is intended primarily as the interface to
// read the value of instruction predicates. It is separated from source
// predicates to avoid mixing it in with the source operands needed for modeling
// the instruction semantics.
class PredicateOperandInterface {
 public:
  virtual bool Value() = 0;
  // Return a string representation of the operand suitable for display in
  // disassembly.
  virtual std::string AsString() const = 0;
  virtual ~PredicateOperandInterface() = default;
};

Giao diện toán hạng nguồn cho phép hàm ngữ nghĩa của lệnh đọc giá trị từ các toán hạng lệnh mà không tính đến toán hạng cơ bản loại. Các phương thức giao diện hỗ trợ cả toán hạng có giá trị vectơ và vô hướng.

// The source operand interface provides an interface to access input values
// to instructions in a way that is agnostic about the underlying implementation
// of those values (eg., register, fifo, immediate, predicate, etc).
class SourceOperandInterface {
 public:
  // Methods for accessing the nth value element.
  virtual bool AsBool(int index) = 0;
  virtual int8_t AsInt8(int index) = 0;
  virtual uint8_t AsUint8(int index) = 0;
  virtual int16_t AsInt16(int index) = 0;
  virtual uint16_t AsUint16(int) = 0;
  virtual int32_t AsInt32(int index) = 0;
  virtual uint32_t AsUint32(int index) = 0;
  virtual int64_t AsInt64(int index) = 0;
  virtual uint64_t AsUint64(int index) = 0;

  // Return a pointer to the object instance that implements the state in
  // question (or nullptr) if no such object "makes sense". This is used if
  // the object requires additional manipulation - such as a fifo that needs
  // to be pop'ed. If no such manipulation is required, nullptr should be
  // returned.
  virtual std::any GetObject() const = 0;

  // Return the shape of the operand (the number of elements in each dimension).
  // For instance {1} indicates a scalar quantity, whereas {128} indicates an
  // 128 element vector quantity.
  virtual std::vector<int> shape() const = 0;

  // Return a string representation of the operand suitable for display in
  // disassembly.
  virtual std::string AsString() const = 0;

  virtual ~SourceOperandInterface() = default;
};

Giao diện toán hạng đích cung cấp các phương thức để phân bổ và xử lý Thực thể DataBuffer (loại dữ liệu nội bộ dùng để lưu trữ giá trị của gói đăng ký). Đáp toán hạng đích cũng có độ trễ tương ứng. Đó là số số chu kỳ phải chờ cho đến khi thực thể vùng đệm dữ liệu được lệnh phân bổ hàm ngữ nghĩa được dùng để cập nhật giá trị của thanh ghi đích. Cho chẳng hạn như độ trễ của một lệnh add có thể là 1, trong khi đối với một mpy có thể là 4. Điều này được đề cập chi tiết hơn trong hướng dẫn về hàm ngữ nghĩa.

// The destination operand interface is used by instruction semantic functions
// to get a writable DataBuffer associated with a piece of simulated state to
// which the new value can be written, and then used to update the value of
// the piece of state with a given latency.
class DestinationOperandInterface {
 public:
  virtual ~DestinationOperandInterface() = default;
  // Allocates a data buffer with ownership, latency and delay line set up.
  virtual DataBuffer *AllocateDataBuffer() = 0;
  // Takes an existing data buffer, and initializes it for the destination
  // as if AllocateDataBuffer had been called.
  virtual void InitializeDataBuffer(DataBuffer *db) = 0;
  // Allocates and initializes data buffer as if AllocateDataBuffer had been
  // called, but also copies in the value from the current value of the
  // destination.
  virtual DataBuffer *CopyDataBuffer() = 0;
  // Returns the latency associated with the destination operand.
  virtual int latency() const = 0;
  // Return a pointer to the object instance that implmements the state in
  // question (or nullptr if no such object "makes sense").
  virtual std::any GetObject() const = 0;
  // Returns the order of the destination operand (size in each dimension).
  virtual std::vector<int> shape() const = 0;
  // Return a string representation of the operand suitable for display in
  // disassembly.
  virtual std::string AsString() const = 0;
};

Mô tả ISA

ISA (Cấu trúc tập lệnh) của bộ xử lý xác định mô hình trừu tượng nhờ đó phần mềm tương tác với phần cứng. Định nghĩa này xác định tập hợp hướng dẫn hiện có, loại dữ liệu, thanh ghi và các máy khác nêu rõ hoạt động của các hướng dẫn, cũng như hành vi của chúng (ngữ nghĩa). Đối với các mục đích của MPACT-Sim, ISA không bao gồm việc mã hoá thực tế của hướng dẫn. Việc này được xử lý riêng.

ISA của đơn vị xử lý được thể hiện trong tệp mô tả mô tả tập lệnh ở cấp độ trừu tượng, mã hoá không phụ thuộc. Tệp mô tả liệt kê tập hợp các hướng dẫn có sẵn. Đối với mỗi hướng dẫn, bắt buộc phải liệt kê tên, số và tên toán hạng cũng như liên kết với một hàm C++/có thể gọi triển khai ngữ nghĩa của hàm đó. Ngoài ra, bạn có thể chỉ định chuỗi định dạng tách và việc sử dụng tên tài nguyên phần cứng. Dữ liệu đầu ra rất hữu ích cho việc tạo văn bản nội dung trình bày hướng dẫn gỡ lỗi, theo dõi hoặc sử dụng mang tính tương tác. Chiến lược phát hành đĩa đơn có thể được sử dụng để xây dựng độ chính xác cao hơn cho chu kỳ trong quá trình mô phỏng.

Tệp mô tả ISA được phân tích cú pháp bằng trình phân tích cú pháp isa. Trình phân tích cú pháp này tạo mã cho bộ giải mã lệnh không phụ thuộc vào biểu diễn. Bộ giải mã này chịu trách nhiệm điền sẵn các trường của đối tượng lệnh. Các giá trị cụ thể, giả sử số đăng ký đích, lấy từ một lệnh cụ thể theo định dạng bộ giải mã. Một trong những bộ giải mã đó là bộ giải mã nhị phân, là trọng tâm của hướng dẫn tiếp theo.

Hướng dẫn này đề cập đến cách viết tệp mô tả ISA cho một đại lượng vô hướng đơn giản cấu trúc. Chúng tôi sẽ sử dụng tập hợp con tập lệnh RiscV RV32I thành tập hợp minh hoạ điều này và cùng với các hướng dẫn khác, hãy xây dựng một trình mô phỏng có khả năng về việc mô phỏng bài hát "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.

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

Nội dung của tệp được chia thành nhiều phần. Đầu tiên là ISA khai báo:

isa RiscV32I {
  namespace mpact::sim::codelab;
  slots { riscv32; }
}

Thao tác này khai báo RiscV32I là tên của ISA và trình tạo mã sẽ tạo một lớp có tên là RiscV32IEncodingBase. Lớp này xác định giao diện mà bộ giải mã được tạo sẽ sử dụng để nhận mã vận hành và thông tin toán hạng. Tên của lớp này được tạo bằng cách chuyển đổi tên ISA sang Pascal-case, sau đó nối phương thức đó với EncodingBase. Nội dung khai báo slots { riscv32; } chỉ định rằng chỉ có một khe lệnh riscv32 trong RiscV32I ISA (trái ngược với nhiều vị trí trong một lệnh VLIW) và mã nguồn duy nhất lệnh hợp lệ là những lệnh được xác định để thực thi trong riscv32.

// First disasm fragment is 15 char wide and left justified.
disasm widths = {-15};

Điều này chỉ định rằng mảnh tháo rời đầu tiên của bất kỳ sự cố nào thông số kỹ thuật (xem thêm bên dưới), sẽ được căn đều trong 15 ký tự trường rộng. Mọi mảnh tiếp theo sẽ được thêm vào trường này mà không bất kỳ khoảng cách nào khác.

Bên dưới có 3 khai báo vùng: riscv32i, zicsrriscv32. Dựa trên định nghĩa isa ở trên, chỉ các hướng dẫn được xác định cho riscv32 vị trí sẽ là một phần của RiscV32I isa. Hai vị trí còn lại là gì?

Bạn có thể dùng ô trống để đưa hướng dẫn vào các nhóm riêng biệt. Sau đó, được kết hợp vào một khe duy nhất ở cuối. Hãy lưu ý ký hiệu : riscv32i, zicsr trong phần khai báo vị trí riscv32. Điều này chỉ định rằng vị trí riscv32 kế thừa tất cả các lệnh đã xác định trong ô zicsrriscv32i. ISA RiscV 32 bit bao gồm một ISA cơ sở được gọi là RV32I, trong đó một tập hợp các tiện ích tuỳ chọn có thể được thêm vào. Cơ chế vị trí cho phép hướng dẫn trong các tiện ích này được chỉ định riêng biệt và sau đó được kết hợp (nếu cần) để xác định ISA tổng thể. Trong trường hợp này, hướng dẫn trong RiscV 'I' nhóm được xác định riêng biệt với các mã trong cột "zicsr" nhóm. Có thể xác định các nhóm bổ sung cho 'M' (nhân/chia), "F" (dấu phẩy động với độ chính xác đơn), "D" (dấu phẩy động có độ chính xác kép), "C" (hướng dẫn 16 bit nhỏ gọn), v.v. như cần thiết cho RiscV ISA cuối cùng mong muốn.

// The RiscV 'I' instructions.
slot riscv32i {
  ...
}

// RiscV32 CSR manipulation instructions.
slot zicsr {
  ...
}

// The final instruction set combines riscv32i and zicsr.
slot riscv32 : riscv32i, zicsr {
  ...
}

Bạn không cần thay đổi định nghĩa vị trí zicsrriscv32. Tuy nhiên Trọng tâm của hướng dẫn này là thêm các định nghĩa cần thiết vào riscv32i vị trí. Hãy xem xét kỹ hơn về những gì hiện được xác định trong vùng này:

// The RiscV 'I' instructions.
slot riscv32i {
  // Include file that contains the declarations of the semantic functions for
  // the 'I' instructions.
  includes {
    #include "learning/brain/research/mpact/sim/codelab/riscv_semantic_functions/solution/rv32i_instructions.h"
  }
  // These are all 32 bit instructions, so set default size to 4.
  default size = 4;
  // Model these with 0 latency to avoid buffering the result. Since RiscV
  // instructions have sequential semantics this is fine.
  default latency = 0;
  // The opcodes.
  opcodes {
    fence{: imm12 : },
      semfunc: "&RV32IFence"c
      disasm: "fence";
    ebreak{},
      semfunc: "&RV32IEbreak",
      disasm: "ebreak";
  }
}

Đầu tiên, có một phần includes {} liệt kê những tệp tiêu đề cần để được đưa vào mã được tạo khi vùng này được tham chiếu trực tiếp hoặc gián tiếp, trong ISA cuối cùng. Bao gồm các tệp cũng có thể được liệt kê trong danh sách phạm vi includes {}. Trong trường hợp đó, các giá trị này sẽ luôn được đưa vào. Điều này có thể thuận tiện nếu cùng một tệp đính kèm phải được thêm vào mọi vị trí định nghĩa.

Nội dung khai báo default sizedefault latency xác định điều đó, trừ phi có quy định khác, kích thước của một lệnh là 4 và độ trễ của một lệnh cách ghi toán hạng đích là 0 chu kỳ. Lưu ý, kích thước của hướng dẫn được chỉ định ở đây, là kích thước của số gia số của bộ đếm chương trình dùng để tính toán địa chỉ của hướng dẫn tuần tự tiếp theo để thực thi trong bộ xử lý. Kích thước này có thể giống hoặc không giống với kích thước tính bằng byte của bản trình bày lệnh trong tệp thực thi đầu vào.

Trung tâm của định nghĩa vị trí là phần mã hoạt động. Như bạn có thể thấy, chỉ có hai opcodes (hướng dẫn) fenceebreak đến nay đã được xác định trong riscv32i. Mã hoạt động fence được xác định bằng cách chỉ định tên (fence) và thông số toán hạng ({: imm12 : }), theo sau là quá trình tháo rời tuỳ chọn ("fence") và đối tượng có thể gọi được liên kết dưới dạng ngữ nghĩa ("&RV32IFence").

Các toán hạng lệnh được chỉ định dưới dạng bộ ba, với mỗi thành phần phân tách bằng dấu chấm phẩy, predicate ':' danh sách toán hạng nguồn ':' danh sách toán hạng đích. Danh sách toán hạng nguồn và đích là dấu phẩy danh sách tên toán hạng riêng. Như bạn có thể thấy, toán hạng lệnh cho lệnh fence chứa, không có toán hạng vị từ, chỉ chứa một nguồn duy nhất tên toán hạng imm12 và không có toán hạng đích. Tập hợp con RiscV RV32I không hỗ trợ thực thi dự đoán, nên toán hạng vị từ sẽ luôn trống trong hướng dẫn này.

Hàm ngữ nghĩa được chỉ định làm chuỗi cần thiết để chỉ định C++ hoặc có thể gọi để gọi hàm ngữ nghĩa. Chữ ký của hàm ngữ nghĩa/có thể gọi là void(Instruction *).

Thông số kỹ thuật tháo rời bao gồm một danh sách các chuỗi được phân tách bằng dấu phẩy. Thông thường chỉ có hai chuỗi được sử dụng, một cho mã hoạt động và một cho toán hạng. Khi được định dạng (sử dụng lệnh gọi AsString() trong Hướng dẫn), mỗi được định dạng trong một trường theo disasm widths quy cách được mô tả ở trên.

Các bài tập sau đây sẽ giúp bạn thêm hướng dẫn vào tệp riscv32i.isa đủ để mô phỏng câu lệnh "Hello World" ("Xin chào thế giới") . Đối với những người đang vội, giải pháp có thể được tìm thấy trong riscv32i.isarv32i_instructions.h.


Thực hiện quá trình tạo bản dựng ban đầu

Nếu bạn chưa thay đổi thư mục thành riscv_isa_decoder, hãy thực hiện ngay. Sau đó tạo dự án như sau – bản dựng này sẽ thành công.

$ cd riscv_isa_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_isa_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_isa_decoder

Trong thư mục này, cùng với các tệp khác, sẽ có những nội dung sau tệp C++ được tạo:

  • riscv32i_decoder.h
  • riscv32i_decoder.cc
  • riscv32i_enums.h
  • riscv32i_enums.cc

Hãy xem riscv32i_enums.h bằng cách nhấp vào nó trong trình duyệt. Bạn nên thấy rằng tệp này chứa nội dung như:

#ifndef RISCV32I_ENUMS_H
#define RISCV32I_ENUMS_H

namespace mpact {
namespace sim {
namespace codelab {
  enum class SlotEnum {
    kNone = 0,
    kRiscv32,
  };

  enum class PredOpEnum {
    kNone = 0,
    kPastMaxValue = 1,
  };

  enum class SourceOpEnum {
    kNone = 0,
    kCsr = 1,
    kImm12 = 2,
    kRs1 = 3,
    kPastMaxValue = 4,
  };

  enum class DestOpEnum {
    kNone = 0,
    kCsr = 1,
    kRd = 2,
    kPastMaxValue = 3,
  };

  enum class OpcodeEnum {
    kNone = 0,
    kCsrs = 1,
    kCsrsNw = 2,
    kCsrwNr = 3,
    kEbreak = 4,
    kFence = 5,
    kPastMaxValue = 6
  };

  constexpr char kNoneName[] = "none";
  constexpr char kCsrsName[] = "Csrs";
  constexpr char kCsrsNwName[] = "CsrsNw";
  constexpr char kCsrwNrName[] = "CsrwNr";
  constexpr char kEbreakName[] = "Ebreak";
  constexpr char kFenceName[] = "Fence";
  extern const char *kOpcodeNames[static_cast<int>(
      OpcodeEnum::kPastMaxValue)];

  enum class SimpleResourceEnum {
    kNone = 0,
    kPastMaxValue = 1
  };

  enum class ComplexResourceEnum {
    kNone = 0,
    kPastMaxValue = 1
  };

  enum class AttributeEnum {
    kPastMaxValue = 0
  };

}  // namespace codelab
}  // namespace sim
}  // namespace mpact

#endif  // RISCV32I_ENUMS_H

Như bạn có thể thấy, mỗi vị trí, mã hoạt động và toán hạng được xác định trong Tệp riscv32i.isa được xác định theo một trong các kiểu liệt kê. Ngoài ra có một mảng OpcodeNames lưu trữ tất cả tên của các mã hoạt động (đó là xác định trong riscv32i_enums.cc). Các tệp khác chứa bộ giải mã đã tạo, Nội dung này sẽ được đề cập thêm trong một hướng dẫn khác.

Quy tắc tạo Bazel

Mục tiêu bộ giải mã ISA trong Bazel được xác định bằng macro quy tắc tuỳ chỉnh có tên mpact_isa_decoder được tải từ mpact/sim/decoder/mpact_sim_isa.bzl trong kho lưu trữ mpact-sim. Đối với hướng dẫn này, mục tiêu bản dựng được xác định trong riscv_isa_decoder/BUILD là:

mpact_isa_decoder(
    name = "riscv32i_isa",
    src = "riscv32i.isa",
    includes = [],
    isa_name = "RiscV32I",
    deps = [
        "//riscv_semantic_functions:riscv32i",
    ],
)

Quy tắc này gọi công cụ và trình tạo trình phân tích cú pháp ISA để tạo mã C++, sau đó biên dịch dữ liệu được tạo thành một thư viện mà các quy tắc khác có thể phụ thuộc vào nhãn //riscv_isa_decoder:riscv32i_isa. Phần includes được sử dụng để chỉ định các tệp .isa bổ sung mà tệp nguồn có thể bao gồm. Chiến lược phát hành đĩa đơn isa_name dùng để chỉ định một isa cụ thể, bắt buộc nếu có nhiều hơn một được chỉ định, trong tệp nguồn để tạo bộ giải mã.


Thêm hướng dẫn 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.isa. Đầu tiên nhóm hướng dẫn là các hướng dẫn ALU đăng ký đăng ký chẳng hạn như add, and, v.v. Trên RiscV32, tất cả đều sử dụng định dạng lệnh nhị phân loại R:

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

Mặc dù tệp .isa được dùng để tạo bộ giải mã không phụ thuộc vào định dạng, nhưng tệp này vẫn hữu ích khi xem xét định dạng nhị phân và bố cục của nó để hướng dẫn các mục nhập. Khi bạn có thể thấy, có ba trường có liên quan đến bộ giải mã điền các đối tượng lệnh: rs2, rs1rd. Đến đây, chúng tôi sẽ chọn sử dụng các tên này cho các thanh ghi số nguyên được mã hoá theo cách tương tự (chuỗi bit), trong các trường hướng dẫn giống nhau, trong tất cả hướng dẫn.

Sau đây là hướng dẫn mà chúng tôi sẽ thêm:

  • 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.

Mỗi hướng dẫn này sẽ được thêm vào phần opcodes của Định nghĩa vị trí riscv32i. Hãy nhớ rằng chúng ta phải chỉ định tên, mã hoạt động, quá trình phân tách và hàm ngữ nghĩa cho mỗi lệnh. Tên này rất dễ, hãy chỉ sử dụng tên mã vận hành ở trên. Ngoài ra, tất cả đều sử dụng cùng một toán hạng, vì vậy chúng ta có thể sử dụng { : rs1, rs2 : rd} cho tham số toán hạng. Điều này có nghĩa là toán hạng nguồn thanh ghi do rs1 chỉ định sẽ có chỉ số 0 trong nguồn vectơ toán hạng trong đối tượng lệnh, toán hạng nguồn thanh ghi được chỉ định bởi rs2 sẽ có chỉ mục 1 và toán hạng đích của thanh ghi được chỉ định bởi rd sẽ là phần tử duy nhất trong vectơ toán hạng đích (tại chỉ mục 0).

Tiếp theo là thông số kỹ thuật của hàm ngữ nghĩa. Bạn có thể thực hiện việc này bằng từ khoá semfunc và chuỗi C++ chỉ định một lệnh gọi có thể dùng để gán thành std::function. Trong hướng dẫn này, chúng ta sẽ sử dụng các hàm, cho phép sẽ là "&MyFunctionName". Sử dụng lược đồ đặt tên do fence, các lệnh này phải là "&RV32IAdd", "&RV32IAnd", v.v.

Cuối cùng là thông số kỹ thuật tháo rời. Nó bắt đầu bằng từ khoá disasm và theo sau là danh sách các chuỗi được phân tách bằng dấu phẩy chỉ rõ cách chỉ dẫn nên được in dưới dạng chuỗi. Sử dụng ký hiệu % trước một tên toán hạng cho biết thay thế chuỗi bằng cách biểu diễn chuỗi của toán hạng đó. Đối với lệnh add, lệnh này sẽ là: disasm: "add", "%rd, %rs1,%rs2". Tức là mục nhập cho lệnh add sẽ có dạng như:

    add{ : rs1, rs2 : rd},
      semfunc: "&RV32IAdd",
      disasm: "add", "%rd, %rs1, %rs2";

Hãy tiếp tục và chỉnh sửa tệp riscv32i.isa rồi thêm tất cả các hướng dẫn này vào Nội dung mô tả .isa. Nếu bạn cần trợ giúp (hoặc muốn kiểm tra bài làm của mình), hãy tệp mô tả là tại đây.

Sau khi thêm hướng dẫn vào tệp riscv32i.isa, bạn cần để thêm phần khai báo hàm cho mỗi hàm ngữ nghĩa mới đã được được tham chiếu đến tệp rv32i_instructions.h nằm trong ../Semantic_functions/. Một lần nữa, nếu bạn cần được trợ giúp (hoặc muốn kiểm tra bài làm của mình), câu trả lời là tại đây.

Sau khi hoàn tất, hãy tiếp tục và thay đổi về thành riscv_isa_decoder và xây dựng lại thư mục. Vui lòng kiểm tra các tệp nguồn đã tạo.


Thêm hướng dẫn ALU bằng thẻ Lập tức

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ư bạn có thể thấy, tên toán hạng rs1rd tham chiếu đến các trường bit tương tự như và được dùng để biểu diễn các thanh ghi số nguyên, vì vậy, các tên này có thể là đã giữ lại. Các trường giá trị tức thì có độ dài và vị trí khác nhau, và hai (uimm5uimm20) chưa có chữ ký, trong khi imm12 có dấu. Mỗi các ảnh này sẽ sử dụng tên riêng.

Do đó, toán hạng cho lệnh I-Type phải là { : rs1, imm12 :rd }. Đối với các hướng dẫn chuyên biệt về Loại chữ I, giá trị này phải là { : rs1, uimm5 : rd}. Thông số toán hạng lệnh U phải là { : uimm20 : rd }.

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ì.

Hướng dẫn chuyên biệt về Loại I 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.

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.

Tên cần sử dụng cho các mã opcode tự nhiên được lấy từ tên lệnh ở trên (không cần tạo ra cái mới - tất cả đều là duy nhất). Khi nói đến chỉ định các hàm ngữ nghĩa, nhớ rằng các đối tượng lệnh đã mã hoá giao diện với các toán hạng nguồn không phụ thuộc vào toán hạng cơ bản loại. Điều này có nghĩa là đối với các hướng dẫn có cùng thao tác, nhưng có thể khác nhau về loại toán hạng, có thể có cùng một hàm ngữ nghĩa. Ví dụ: lệnh addi thực hiện cùng một thao tác như lệnh add nếu một bỏ qua loại toán hạng, nên chúng có thể sử dụng cùng một hàm ngữ nghĩa quy cách "&RV32IAdd". Tương tự cho andi, ori, xorislli. Những lệnh còn lại sử dụng các hàm ngữ nghĩa mới, nhưng phải được đặt tên là dựa trên toán tử chứ không phải toán hạng, vì vậy đối với srai hãy sử dụng "&RV32ISra". Chiến lược phát hành đĩa đơn Hướng dẫn về kiểu chữ U auipclui không có quy tắc tương đương cho thanh ghi, vì vậy, điều này chấp nhận được để sử dụng "&RV32IAuipc""&RV32ILui".

Các chuỗi tháo rời rất giống với các chuỗi trong bài tập trước, nhưng như bạn mong đợi, các tham chiếu đến %rs2 sẽ được thay thế bằng %imm12, %uimm5, hoặc %uimm20, nếu phù hợp.

Hãy tiếp tục và thay đổi rồi tạo bản dựng. Kiểm tra dữ liệu đầu ra đã tạo. Giống như trước đây, bạn có thể kiểm tra công việc của mình dựa trên riscv32i.isarv32i_instructions.h.


Hướng dẫn nhánh và hướng dẫn chuyển đổi và liên kết mà chúng ta cần thêm cả hai đều sử dụng một đích đến toán hạng chỉ được ngụ ý trong chính lệnh, cụ thể là máy tính tiếp theo giá trị. Ở giai đoạn này, chúng ta sẽ coi đây là một toán hạng phù hợp với tên next_pc. Nó sẽ được định nghĩa kỹ hơn trong hướng dẫn sau.

Hướng dẫn dành cho chi nhánh

Các nhánh mà chúng tôi đang thêm đều sử dụng phương thức mã hoá B-Type.

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

Các trường tức thì khác nhau được nối thành một dấu ngay lập tức 12 bit có dấu giá trị. Do định dạng không thực sự phù hợp, chúng tôi sẽ gọi thao tác này là bimm12, dành cho nhánh 12 bit ngay lập tức. Việc phân mảnh sẽ được giải quyết trong hướng dẫn tiếp theo về cách tạo bộ giải mã nhị phân. Tất cả Các lệnh nhánh so sánh các thanh ghi số nguyên do rs1 và rs2 chỉ định, nếu điều kiện đúng, giá trị tức thì được thêm vào giá trị máy tính hiện tại để tạo địa chỉ của lệnh tiếp theo cần thực thi. Toán hạng cho do đó, hướng dẫn nhánh phải là { : rs1, rs2, bimm12 : next_pc }.

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 tên mã hoạt động này đều là duy nhất, nên có thể sử dụng lại trong .isa mô tả. Tất nhiên, bạn phải thêm tên hàm ngữ nghĩa mới, ví dụ: "&RV32IBeq", v.v.

Thông số kỹ thuật tháo rời bây giờ phức tạp hơn một chút, vì địa chỉ của chỉ dẫn được dùng để tính toán đích đến mà không thực sự là một phần của toán hạng lệnh. Tuy nhiên, đó là một phần của thông tin được lưu trữ trong đối tượng chỉ dẫn để có sẵn. Giải pháp là sử dụng trong chuỗi phân tách. Thay vì sử dụng '%' tiếp theo là tên toán hạng, bạn có thể nhập %(expression: print format (định dạng in). Chỉ rất đơn giản biểu thức được hỗ trợ, nhưng địa chỉ cộng với độ lệch là một trong số đó, với @ được dùng cho địa chỉ chỉ dẫn hiện tại. Định dạng in tương tự với Định dạng printf kiểu C, nhưng không có % ở đầu. Định dạng tháo rời của lệnh beq sau đó sẽ trở thành:

    disasm: "beq", "%rs1, %rs2, %(@+bimm12:08x)"

Bạn chỉ cần thêm hai hướng dẫn nhảy và liên kết, jal (nhảy và liên kết) và jalr (đường liên kết ngẫu nhiên gián tiếp).

Lệnh jal sử dụng bộ mã hoá J-Type:

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

Tương tự như hướng dẫn nhánh, lệnh 20 bit ngay lập tức được phân mảnh trên nhiều trường, nên chúng ta sẽ đặt tên trường đó là jimm20. Việc phân mảnh không quan trọng vào thời điểm này, nhưng sẽ được giải quyết trong hướng dẫn về cách tạo bộ giải mã nhị phân. Toán hạng sau đó sẽ trở thành { : jimm20 : next_pc, rd }. Xin lưu ý rằng có hai toán hạng đích, giá trị trên máy tính tiếp theo và thanh ghi liên kết được chỉ định trong chỉ dẫn.

Tương tự như hướng dẫn nhánh ở trên, định dạng tháo rời trở thành:

    disasm: "jal", "%rd, %(@+jimm20:08x)"

Đường liên kết nhảy gián tiếp sử dụng định dạng I-Type với giá trị tức thì 12 bit. Nó thêm giá trị tức thì mở rộng ký vào thanh ghi số nguyên do rs1 để tạo địa chỉ lệnh đích. Thanh ghi liên kết là thanh ghi số nguyên do rd chỉ định.

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

Nếu đã nhìn thấy mẫu, bạn có thể suy ra rằng thông số toán hạng cho jalr sẽ là { : rs1, imm12 : next_pc, rd } và quá trình tháo rời quy cách:

    disasm: "jalr", "%rd, %rs1, %imm12"

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.isarv32i_instructions.h.


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

Hướng dẫn của cửa hàng rất đơn giản. Tất cả các chiến dịch này đều sử dụng định dạng S-Type:

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, đây là một trường hợp khác của ngay lập tức 12 bit bị phân mảnh, đặt tên cho nó là simm12. Theo hướng dẫn của cửa hàng, tất cả đều lưu trữ giá trị của số nguyên thanh ghi do rs2 chỉ định cho địa chỉ hiệu lực trong bộ nhớ thu được bằng cách thêm giá trị của thanh ghi số nguyên do rs1 chỉ định cho giá trị mở rộng có dấu của 12 bit tức thì. Định dạng toán hạng phải là { : rs1, simm12, rs2 } cho tất cả hướng dẫn dành cho cửa hàng.

Hướng dẫn dành cho cửa hàng cần được triển khai là:

  • sb – Lưu trữ byte.
  • sh – Lưu trữ nửa từ.
  • sw - Từ lưu trữ.

Thông số kỹ thuật tháo rời của sb như bạn mong đợi:

    disasm: "sb", "%rs2, %simm12(%rs1)"

Bạn cũng có thể sử dụng thông số kỹ thuật của hàm ngữ nghĩa: "&RV32ISb", và hơn thế nữa

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.isarv32i_instructions.h.


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

Hướng dẫn tải được lập mô hình hơi khác so với các hướng dẫn khác trong trình mô phỏng. Để có thể lập mô hình các trường hợp có độ trễ tải là không chắc chắn, hướng dẫn tải được chia thành hai hành động riêng biệt: 1) có hiệu lực tính toán địa chỉ và truy cập bộ nhớ, và 2) ghi lại kết quả. Trong thực hiện điều này bằng cách chia hành động ngữ nghĩa của tải thành hai phần hướng dẫn riêng biệt, hướng dẫn chính và hướng dẫn dành cho trẻ em. Hơn nữa, khi chỉ định toán hạng, chúng ta cần chỉ định chúng cho cả hàm chính và child. Điều này được thực hiện bằng cách coi thông số toán hạng là một danh sách bộ ba. Cú pháp là:

{(predicate : sources : destinations), (predicate : sources : destinations), ... }

Tất cả hướng dẫn tải đều sử dụng định dạng I-Type giống như nhiều định dạng trước đây hướng dẫn:

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

Thông số toán hạng phân tách các toán hạng cần thiết để tính địa chỉ và bắt đầu truy cập bộ nhớ từ đích đăng ký cho dữ liệu tải: {( : rs1, imm12 : ), ( : : rd) }.

Vì hành động ngữ nghĩa được chia thành 2 lệnh, nên hàm ngữ nghĩa tương tự cũng cần chỉ định hai hàm gọi được. Đối với lw (tải từ), giá trị này sẽ là được viết:

    semfunc: "&RV32ILw", "&RV32ILwChild"

Thông số kỹ thuật tháo rời mang tính thông thường hơn. Không đề cập đến chỉ dẫn trẻ em. Đối với lw, mã phải là:

    disasm: "lw", "%rd, %imm12(%rs1)"

Hướng dẫn tải cần được triển khai là:

  • lb – Tải byte.
  • lbu – Tải byte chưa được ký.
  • lh – Tải nửa từ.
  • lhu – Tải nửa từ không có chữ ký.
  • lw – Tải từ.

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.isarv32i_instructions.h.

Cảm ơn bạn đã cố gắng đạt được mục tiêu này. Chúng tôi hy vọng thông tin này hữu ích.