RiscV ISA 解碼器

本教學課程的目標是:

  • 瞭解 MPACT-Sim 模擬器如何表示指示。
  • 瞭解 ISA 說明檔案的結構和語法。
  • 為 RiscV RV32I 部分指示撰寫 ISA 說明

總覽

在 MPACT-Sim 中,目標指示會進行解碼並儲存在內部 表示法以便獲得更多資訊 執行這些指示執行個體會在操作說明中進行快取 快取,以便減少經常執行指示的次數 執行狀態

教學課程

開始前,先瞭解 指示 是以 MPACT-Sim 代表的Instruction 類別已定義於 mpact-sim/mpact/sim/generic/instruction.h.

指令類別執行個體包含 模擬「執行」時的指令,例如:

  1. 操作說明地址、模擬指示大小,即 .text 中的大小。
  2. 命令 Ocode。
  3. 述詞運算元介面指標 (如適用)。
  4. 來源運算元介面指標的向量。
  5. 目的地運算元介面指標的向量。
  6. 語意函式可呼叫。
  7. 架構狀態物件的指標。
  8. 結構定義物件的指標。
  9. 指向子項和下一個指示例項的指標。
  10. 拆解字串。

這些執行個體通常會儲存在指示 (執行個體) 快取中。 都會重複使用。這麼做可以提高效能 執行任務

除了結構定義物件的指標以外,所有都會填入 根據 ISA 說明產生的指示解碼器。為此 教學課程 不需要瞭解這些項目的詳細資料,因為我們不會 直接使用這些功能而是會概略說明這些容器的使用方式 而負責任的 AI 技術做法 有助於達成這項目標

語意函式可呼叫為 C++ 函式/方法/函式物件 (包括 lambda) 實作指令語意。適用對象 舉例來說,對於 add 指令,它會載入每個來源運算元,並將兩個 運算結果,並將結果寫入單一目的地運算元。主題 語意函式教學課程將詳細說明語意函式。

指令運算元

指令類別包含三種運算元介面的指標: 述詞、來源和目的地。這些介面可讓語意函式 而不受實際指令類型影響 運算元。例如,存取暫存器的值與立即存取的過程 可透過相同的介面這意味著在執行 執行不同運算元 (例如暫存器與動畫) 之間的運算 使用相同的語意函式實作

述詞運算元介面,適用於支援指定參數的 ISAs。 指令執行 (對其他 ISA 為空值) 可用於判斷 指定的指示應根據述詞的布林值執行。

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

來源運算元介面可允許指令語意函式讀取 值,不會與基礎運算元無關 類型。介面方法可同時支援純量和向量值運算元。

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

目的地運算元介面提供配置和處理的方法 DataBuffer 例項 (用來儲存註冊值的內部資料類型)。A 罩杯 目的地運算元也有延遲時間,也就是 等待指示所分配到的資料緩衝區執行個體 語意函式可用來更新目標暫存器的值。適用對象 舉例來說,add 指令的延遲時間可能是 1,而 mpy 則可能是 4。有關這點,請參閱 語意函式的教學課程

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

ISA 說明

處理器的 ISA (指令集架構) 會定義抽像模型 軟體與硬體互動的方式這會定義 可用的指令、資料類型、暫存器及其他機器 指令的運作以及其行為 (語意)。目的 但 ISA 不包含操作說明的實際編碼。 將另行處理。

處理器 ISA 會在說明檔案中提供 指令位於各編碼通用的抽象層級。說明檔案 列舉一組可用指示。針對每個指示 必須列出名稱、運算元的數量和名稱,以及 繫結至實作語意的 C++ 函式/可呼叫項目。此外, 就可以指定反組譯碼格式字串,以及指示使用 硬體資源名稱。前者有助於產生 偵錯、追蹤或互動用途的指示表示法。 後者可用於模擬模擬中的週期準確度。

Isa 剖析器會剖析 ISA 說明檔案,進而產生 通用表示法的解碼器解碼器會負責 填入指示物件的欄位。具體值包括 目的地暫存器號碼,從特定格式的特定指示中取得 編碼器和解碼器其中一個解碼器是二進位解碼器 下個單元將深入探討 活化函數等類神經網路

這個教學課程說明如何編寫簡單的純量 ISA 說明檔案 這個架構的簡短總覽我們會使用一組 RiscV RV32I 指令 說明上述概念並和其他教學課程 建構能使用的模擬器 模擬「Hello World」計畫。如要進一步瞭解 RiscV ISA,請參閱 Risc-V 規格

先開啟檔案: riscv_isa_decoder/riscv32i.isa

檔案內容分成多個部分。首先是 ISA 宣告:

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

這宣告 RiscV32I 為 ISA 名稱,程式碼產生器會 建立名為 RiscV32IEncodingBase 的類別,定義要接收的介面 所產生的解碼器會使用 來取得運算碼和運算元資訊名稱 這個類別是由 ISA 名稱轉換成 Pascal 命名法, 將此函式與 EncodingBase 串連。宣告 slots { riscv32; } 指定 RiscV32I 中只有一個指令版位 riscv32 ISA (與 VLIW 指示中的多個插槽不同), 有效的指令是在 riscv32 中定義的執行。

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

這表示任何故意首次拆解的片段 規格 (詳情請見下方) 15 個半形字元, 欄位系統會將任何後續片段附加至這個欄位,但不含 所有額外間距

這個下方有三個版位宣告:riscv32izicsrriscv32。 根據上述 isa 定義,只有為 riscv32 定義的指示 版位將是 RiscV32I 區域的一部分另外兩個時段分別適合使用哪些功能?

運算單元可用來將操作說明分成不同的群組, 合併為一個版位請注意,: riscv32i, zicsr 標記 導入 riscv32 版位宣告中。這會指定 riscv32 運算單元沿用 版位 zicsrriscv32i 中定義的所有指示。RiscV 32 位元 ISA 包含一組名為「RV32I」的 ISA 基準, 。運算單元機制可讓這些擴充功能中的指示採用 然後視需要合併為最後部分 。在這個範例中, RiscV「I」是否已定義 與「zicsr」不同的值群組。您可以定義其他群組 「M」的結果(乘除/除號),「F」(單精度浮點)、「D」 (雙精度浮點)、「C」(16 位元精簡指示) 等。 。

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

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

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

您不需要變更 zicsrriscv32 版位定義。不過 本教學課程的重點是將必要的定義新增至 riscv32i 。讓我們進一步瞭解這個版位目前的定義:

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

首先是 includes {} 區段,列出需要的標頭檔案 加到產生的程式碼中 (直接參照或 採用最終的 ISA 程序也可以將檔案列於全域 範圍為 includes {} 區段,此時一律會納入。這可以 如果需要將相同的納入檔案加到每個版位 定義

default sizedefault latency 宣告會定義其定義,除非 否則,指示的大小是 4,而延遲載入為 目的地運算元寫入為 0 個週期。請注意,指示大小 是由程式計數器遞增的大小, 下一個連續指示在模擬中執行 處理器這個大小不一定與 輸入執行檔中的指示表示法。

運算單元定義的中心就是「opcode」區段。如您所見 opcode (指示) fenceebreak 目前已經在 riscv32i。定義 fence 運算碼的方式是指定名稱 (fence) 並 運算元規格 ({: imm12 : }),後面則是可選用的反組譯碼 格式 ("fence"),以及要繫結為語意的可呼叫函式 函式 ("&RV32IFence")。

指示運算元被指定為三元組,每個元件各有 中間以半形分號分隔,述詞「:」來源運算元清單「:」 目的地運算元清單。來源和目的地運算元清單為半形逗號 運算元名稱清單。如畫面所示 fence 指令包含無述詞運算元,但僅限單一來源 運算元名稱 imm12,而且沒有目的地運算元。RiscV RV32I 子集 不支援預先指定的執行作業,因此述詞運算元一律為空白 其他範例

語意函式指定為指定 C++ 的必要字串 函式或可呼叫的項目,用於呼叫語意函式。簽署的 語意函式/呼叫為 void(Instruction *)

反組譯碼規格由逗號分隔的字串清單組成。 通常只會使用兩個字串,一個用於運算碼,另一個用於 運算元。格式化後 (在「操作說明」中使用 AsString() 呼叫),每個 字串在欄位中採用以 disasm widths 為準的格式 規格。

下列練習可協助您在 riscv32i.isa 檔案中加入操作說明 足以模擬「Hello World」計畫。適合趕時間的話 您可前往 riscv32i.isarv32i_instructions.h


執行初始建構

如果您尚未將目錄變更為 riscv_isa_decoder,請立即進行。接著 請按照以下方式建構專案 - 這項建構作業應會成功

$ cd riscv_isa_decoder
$ bazel build :all

現在將目錄改回存放區根目錄,接著來看看 所產生的來源為此,請將目錄變更為 bazel-out/k8-fastbuild/bin/riscv_isa_decoder (假設您使用 x86 格式) host - 如果是其他主機,k8-fastbuild 會是另一個字串)。

$ cd ..
$ cd bazel-out/k8-fastbuild/bin/riscv_isa_decoder

在這個目錄中,其他檔案會包含以下內容 產生的 C++ 檔案:

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

首先,請在瀏覽器中按一下riscv32i_enums.h。請 您就會發現其中包含類似下方的內容:

#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

如您所見,在 riscv32i.isa 檔案是在其中一種列舉類型中定義。此外, 有一個 OpcodeNames 陣列儲存了運算法的所有名稱 (這是 定義於 riscv32i_enums.cc)。另一個檔案包含產生的解碼器 我們會在其他教學課程中詳細說明

Bazel 建構規則

Bazel 的 ISA 解碼器目標是透過名為 mpact_isa_decoder,是從 mpact/sim/decoder/mpact_sim_isa.bzl 載入 在 mpact-sim 存放區中在本教學課程中,可以看到 riscv_isa_decoder/BUILD 是:

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

這項規則會呼叫 ISA 剖析器工具和產生器產生 C++ 程式碼。 接著,將產生的程式碼編譯成其他規則可依附於 「//riscv_isa_decoder:riscv32i_isa」標籤。使用「includes」部分 指定來源檔案可能包含的其他 .isa 檔案。 isa_name 是用來指定哪一個特定 isa (如果有多個) 的檔案名稱。


新增註冊 ALU 操作說明

接下來,請在 riscv32i.isa 檔案中新增一些指示。第一個 操作說明群組是註冊 ALU 操作說明,例如 add and 等。在 RiscV32 上,這些項目都會使用 R 型二進位檔指示格式:

2025 年 31 月 31 日 2020 年 24 月 19:15 14 月 12 日 11..7 6..0 版
7 5 5 3 5 7
func7 rs2 rs1 func3 運算程式碼

雖然 .isa 檔案是用來產生跨格式解碼器,但檔案仍不會 考慮二進位格式及其版面配置來引導項目。如同你 有三個欄位與填入 指令物件:rs2rs1rd。目前我們選擇使用 這些註冊方式的整數暫存器名稱採用相同的編碼方式 (位元序列), 產生的指示值。

我們稍後要新增的操作說明如下:

  • add - 加入整數,
  • and:位元和。
  • or:位元或。
  • sll - 將邏輯往左移。
  • sltu - 設定小於或未簽署的字元。
  • sub - 減去整數。
  • xor - 位元 xor。

這些操作說明都會加到opcodes riscv32i 個運算單元定義。如前所述,我們必須指定名稱、運算碼 每個指示的反組譯碼和語意函式名稱很簡單 我們只用上面的 opcode 名稱此外,它們都使用相同的運算元 我們可以使用 { : rs1, rs2 : rd} 做為運算元規格。也就是說 rs1 指定的註冊來源運算元在來源中的索引為 0 指令物件中的運算元向量,是指定的註冊來源運算元 的 rs2 將索引 1,而 rs2 指定的註冊目的地運算元 會是目的地運算元向量中唯一的元素 (位於索引 0)。

接下來說明語意函式規格方法是使用關鍵字 semfunc 和 C++ 字串,指定可用來指派可呼叫的呼叫項目 至 std::function。在本教學課程中,我們將使用函式,因此可以呼叫的 字串將是 "&MyFunctionName"。使用 fence 指示,應為 "&RV32IAdd""&RV32IAnd" 等。

最後是拆解規格開頭為 disasm 這個關鍵字, 後面接著以半形逗號分隔的字串清單,用於指定 指示應以字串的形式顯示。在% 運算元名稱代表使用 該運算元如果是 add 指令,則應為:disasm: "add", "%rd, %rs1,%rs2"。這表示 add 指令的項目應該看起來 例如:

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

請直接編輯 riscv32i.isa 檔案,然後將這些指示加入 .isa 說明。如果你需要協助 (或要查看作業),請參閱 說明檔案為 請按這裡

將操作說明新增至 riscv32i.isa 檔案後,就需要 為每個新的語意函式新增函式宣告 已參照這個位置:rv32i_instructions.h `../semantic_functions/.如果您需要協助 (或想查看作業) 答案是 請按這裡

完成上述操作後,請返回 riscv_isa_decoder 目錄及重新建構您可以檢查產生的來源檔案。


新增 ALU 指示與立即通知

下一組操作說明為 ALU 指示,使用 即時值,而不是其中一個暫存器。我們有三個群組 指令 (根據即時欄位):I-Type 立即指示 以及 12 位元立即簽署的特殊 I-Type 立即指示 以及 U-Type 會立即使用 20 位元無正負號的即時值。 格式如下:

I-Type 立即格式:

2020 年 3 月 31 日 19:15 14 月 12 日 11..7 6..0 版
12 5 3 5 7
imm12 rs1 func3 運算程式碼

專用的 I-Type 立即格式:

2025 年 31 月 31 日 2020 年 24 月 19:15 14 月 12 日 11..7 6..0 版
7 5 5 3 5 7
func7 uimm5 rs1 func3 運算程式碼

U-Type 立即格式:

12 月 31 日 11..7 6..0 版
20 5 7
uimm20 運算程式碼

如您所見,運算元名稱 rs1rd 與 是用來代表整數暫存器,因此這些名稱可以是 續用折扣即時值欄位的長度和位置不同,且 兩組 (uimm5uimm20) 為未簽署,但 imm12 則是簽署。每個 這些名稱會使用自己的名稱

因此,I-Type 指令的運算元應為 { : rs1, imm12 :rd }。如需特殊的 I-Type 指示,則應為 { : rs1, uimm5 : rd}。 U-Type 指令運算元規格應為 { : uimm20 : rd }

需要新增的 I-Type 操作說明如下:

  • addi - 立即新增。
  • andi - 位元和立即。
  • ori:位元或立即。
  • xori - 具有立即的位元 xor。

需要新增的特殊 I-Type 指示如下:

  • slli - 立即將左移邏輯列。
  • srai - 立即將右算法調整。
  • srli - 立即將右移邏輯運算。

需要新增的 U-Type 操作說明如下:

  • auipc - 立即將上限新增至電腦。
  • lui - 立即載入上方。

運算程式碼要使用的名稱自然取決於指示名稱 以上 (你們不必想到新的帳戶,因為它們都是獨一無二的)。這張投影片的內容 指定語意函式, 請回想一下,指示物件經過編碼 通用運算元通用的來源運算元介面 類型。也就是說,適用於含有相同作業的指令, 可能不同,且可以共用相同的語意函式。舉例來說: 如果發生以下情況,addi 指令會執行與 add 指令相同的作業: 第一,會忽略運算元類型,因此可以使用相同的語意函式 "&RV32IAdd" 規格。andiorixorislli 也是一樣。 其他操作說明會使用新的語意函式,但應為這些函式命名 取決於作業,而非運算元,因此 srai 會使用 "&RV32ISra"。 U-Type 指令 auipclui 沒有註冊對等項目,因此可以正常運作 以便使用 "&RV32IAuipc""&RV32ILui"

拆解字串與先前練習中的字串非常相似,但 一如預期,參照 %rs2 的參照已替換為 %imm12%uimm5、 或 %uimm20 (如適用)。

請繼續進行變更並建構。檢查產生的輸出內容。就像 您可以先檢查自己的實驗 riscv32i.isarv32i_instructions.h


我們需要加入分支版本和跳轉連結的操作說明,均使用目的地 僅於指示中隱含的運算元,即下一個電腦 值。在這個階段,我們會把這個註解當成名稱的適當運算元 next_pc。後續教學課程會進一步定義這項設定。

分支版本操作說明

我們新增的分支全都使用 B-Type 編碼。

31 30.25 2020 年 24 月 19:15 14 月 12 日 11..8 7 6..0 版
1 6 5 5 3 4 1 7
rs2 rs1 func3 運算程式碼

不同的直接欄位會串連為 12 位元即時簽署 值。由於格式不相關,我們會立即呼叫此參數 bimm12,適用於 12 位元分支版本。片段處理的解決方式為 下個單元將示範如何建立二進位解碼器所有 分支版本操作說明會比較 rs1 和 rs2 指定的整數暫存器 (如果有的話) 條件為 true,則立即值會加入目前的 PC 值, 產生下一個要執行指示的位址。的運算元 因此分支版本操作說明應為 { : rs1, rs2, bimm12 : next_pc }

需要新增的分支版本指示如下:

  • beq - 分支版本 (如果相等)。
  • bge - 大於或等於分支版本。
  • bgeu - 大於或等於未簽署的分支版本。
  • blt - 所屬的分支版本,
  • bltu - 分支版本 (如果小於未簽署文件)。
  • bne - 否則為分支版本。

這些運算代碼名稱不得重複,因此可以在 .isa 中重複使用 生成 3D 物件當然,必須新增語意函式名稱,例如 "&RV32IBeq"

由於 指令會用於計算目的地,但實際上並不包含 指令運算元。但這是儲存在 Google 伺服器的資訊中 此為指示物件可用的物件時,就視為可用。解決方法是使用 運算式語法。不使用「%」後面接著 運算元名稱,您可以輸入 %(expression: print format)。只相當單純 支援運算式,但地址加上偏移量是其中一個運算式,其中 @ 符號,用於目前的指示地址。列印格式與 C 樣式 printf 格式,但沒有開頭的 %。解譯碼格式 beq 指令就會變為:

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

只需要新增兩段跳轉與連結的操作說明,jal (跳至連結及連結) 和 jalr (間接跳連結)。

jal 指令使用 J-Type 編碼:

31 2021 年 3 月 30 日 20 19:12 11..7 6..0 版
1 10 1 8 5 7
運算程式碼

如同分支指令,20 位元會立即分散在 多個欄位,因此我們將其命名為 jimm20。分割不重要 但會在下次 這個教學課程到此告一段落運算元 但會成為 { : jimm20 : next_pc, rd }請注意 目的地運算元、下一個 PC 值,以及 指示

與上述分支版本操作說明類似,拆解格式會變為:

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

間接的跳連結使用 I-Type 格式與 12 位元立即可用。這項服務 會將正負號即時值新增到 rs1 可產生目標指示地址。連結的作用是 由 rd 指定的整數暫存器。

2020 年 3 月 31 日 19:15 14 月 12 日 11..7 6..0 版
12 5 3 5 7
imm12 rs1 func3 運算程式碼

如果您已看過這個模式,您現在應該推斷運算元規格 jalr 應為 { : rs1, imm12 : next_pc, rd }, 規格:

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

請繼續進行變更,然後開始建構。檢查產生的輸出內容。只要 您可以先檢查 riscv32i.isarv32i_instructions.h


新增商店操作說明

商店指示非常簡單。它們都使用 S-Type 格式:

2025 年 31 月 31 日 2020 年 24 月 19:15 14 月 12 日 11..7 6..0 版
7 5 5 3 5 7
rs2 rs1 func3 運算程式碼

如畫面所示,這是 12 位元立即分割的另一個情況 命名為 simm12。商店指示所有儲存整數的值 透過新增 rs2 取得記憶體的有效位址 將 rs1 指定的整數暫存器值設為 12 位元會立即生效運算元格式應為 { : rs1, simm12, rs2 }。 所有店面操作說明

需要實作的商店指示如下:

  • sb - 儲存位元組。
  • sh - 儲存半字。
  • sw - 儲存字詞。

sb 的反組譯碼規格如下:

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

語意函式規格也應如此:"&RV32ISb"、 依此類推

請繼續進行變更,然後開始建構。檢查產生的輸出內容。只要 您可以先檢查 riscv32i.isarv32i_instructions.h


新增載入指示

載入操作說明的模型,與 模擬工具如要模擬延遲載入的情況, 不確定,載入指示會分為兩個不同的動作:1) 有效 地址運算和記憶體存取,以及 2) 結果寫回。在 將負載的語意動作分割成 兩個獨立的操作說明、主要指令和子項指示。此外 當我們指定運算元時,需要同時指定 main 和 child 指示。方法是將運算元規格 三連串清單The syntax is:

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

和先前一樣,載入指示均使用 I-Type 格式 說明:

2020 年 3 月 31 日 19:15 14 月 12 日 11..7 6..0 版
12 5 3 5 7
imm12 rs1 func3 運算程式碼

運算元規格會分割計算位址所需的運算元 然後從載入資料的註冊目的地啟動記憶體存取: {( : rs1, imm12 : ), ( : : rd) }

由於語意動作會分割在兩個指令中,因此語意函式 也同樣需要指定兩個可呼叫元件如果是 lw (載入字詞),這會是 寫入:

    semfunc: "&RV32ILw", "&RV32ILwChild"

反組譯碼規格較為傳統。不會提及 子項指示。如果是 lw,則應為:

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

需要實作的載入指示如下:

  • lb - 載入位元組。
  • lbu - 載入未簽署的位元組。
  • lh - 載入半字。
  • lhu - 載入未簽署的半字。
  • lw - 載入字詞。

請繼續進行變更,然後開始建構。檢查產生的輸出內容。只要 您可以先檢查 riscv32i.isarv32i_instructions.h

感謝你的耐心配合。希望這對您有所幫助。