RiscV ISA デコーダ

このチュートリアルの目的は次のとおりです。

  • MPACT-Sim シミュレータで命令がどのように表示されるかを学びます。
  • ISA ディスクリプション ファイルの構造と構文について説明します。
  • RiscV RV32I 命令のサブセットの ISA 説明を作成する

概要

MPACT-Sim では、ターゲット命令がデコードされ、内部 より可用性が高くなり、データのセマンティクスが 実行時間を短縮できます。これらの命令インスタンスは、命令ブロックのキャッシュに 頻繁に実行される命令の数を減らすために、キャッシュに 実行されます。

指示クラス

始める前に、命令が何であるかを少し確認しておきましょう。 表します。Instruction クラスは、 mpact-sim/mpact/sim/generic/instruction.h.

Instruction クラスのインスタンスには、タスクの実行に必要なすべての情報が 命令が「実行された」ときに、次のようにシミュレートします。

  1. 命令アドレス、シミュレートされた命令サイズ、つまり .text のサイズ。
  2. 命令オペコード。
  3. 述語オペランド インターフェース ポインタ(該当する場合)。
  4. ソース オペランド インターフェース ポインタのベクトル。
  5. 宛先オペランド インターフェース ポインタのベクトル。
  6. セマンティック関数の呼び出し可能。
  7. アーキテクチャ状態オブジェクトへのポインタ。
  8. コンテキスト オブジェクトへのポインタ。
  9. 子インスタンスと次の命令インスタンスへのポインタ。
  10. 逆アセンブル文字列。

これらのインスタンスは通常、命令(インスタンス)キャッシュに保存されます。 命令が再実行されるたびに再利用されます。これにより 作成されます。

コンテキスト オブジェクトへのポインタを除き、すべて ISA の記述から生成される命令デコーダ。今回 このチュートリアルでは、これらのアイテムの詳細を 直接使用します。その使われ方を大まかに把握しておくと、 十分です

セマンティック関数の呼び出し可能オブジェクトは C++ の関数/メソッド/関数オブジェクト (ラムダを含む)。命令のセマンティクスを実装します。対象 たとえば、add 命令の場合、各ソースオペランドを読み込み、2 つの 結果を単一の宛先オペランドに書き込みます。トピック セマンティック関数については、セマンティクス関数のチュートリアルで詳しく説明します。

命令オペランド

この命令クラスには、次の 3 種類のオペランド インターフェースへのポインタが含まれています。 source、destination です。これらのインターフェースを使用すると、セマンティック関数を 基になる命令の実際の型に関係なく記述できる オペランド。たとえば、レジスタと即値の値へのアクセスは、 同じインターフェースからアクセスできます。つまり、同じ処理を実行する命令は 異なるオペランド(レジスタと immedates など)では、 実装されています。

述語オペランド インターフェース(述語オペランドをサポートする ISA 用) 命令の実行(他の ISA の場合は null)を使用して、命令の実行が 述語のブール値に基づいて特定の命令を実行する必要があります。

// 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 インスタンス(レジスタ値を格納するために使用される内部データ型)。 宛先オペランドにもレイテンシが関連付けられています この命令によって割り当てられたデータ バッファ インスタンスが セマンティック関数を使用して、ターゲット レジスタの値を更新します。対象 たとえば、add 命令のレイテンシは 1 で、mpy 命令のレイテンシは 1 です。 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(命令セット アーキテクチャ)は抽象モデルを定義する ソフトウェアがハードウェアと相互作用します。トレーニング データに含まれる データ型、レジスタ、その他のマシン状態を その動作(セマンティクス)を確認できます。目的 MPACT-Sim では、ISA に命令の実際のエンコードは含まれません。 この情報は個別に扱われます。

プロセッサの ISA は、インフラストラクチャの 抽象化、エンコーディング非依存レベルで実行できます。説明ファイル 使用可能な命令セットを列挙します。命令ごとに、 その名前、オペランドの数と名前、 セマンティクスを実装する C++ 関数/呼び出し可能オブジェクトへのバインド。また 逆アセンブルした書式設定文字列を指定でき、命令での ハードウェア リソース名を指定します。前者は、テキスト プロンプトを生成するのに デバッグ、トレース、インタラクティブ使用のための命令の表現。「 後者を使用すると、シミュレーションでより正確なサイクル精度を構築できます。

ISA 記述ファイルは isa-parser によって解析され、 表現に依存しない命令デコーダです。このデコーダは、入力シーケンスの 指示オブジェクトのフィールドに入力できます。具体的な値は、 デスティネーション レジスタ番号。フォーマット固有の命令から取得 使用します。そのようなデコーダの一つがバイナリ デコーダで、 説明します

このチュートリアルでは、シンプルなスカラーの ISA 記述ファイルを記述する方法について説明します。 説明します。RiscV RV32I 命令セットのサブセットを使用して、 他のチュートリアルとともに、Google Cloud の 「Hello World」をシミュレートする。RiscV ISA について詳しくは、以下をご覧ください。 Risc-V の仕様

まず、ファイルを開きます。 riscv_isa_decoder/riscv32i.isa

ファイルの内容は複数のセクションに分割されています。1 つ目は ISA です 宣言:

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

これにより、RiscV32I が ISA の名前として宣言され、コード生成ツールが API 呼び出しを定義する RiscV32IEncodingBase というクラスを作成します。 オペランドとオペランド情報を取得するために、生成されたデコーダが使用します。名前 このクラスは、ISA 名をパスカルケースに変換して生成されます。 EncodingBase で連結します。宣言 slots { riscv32; } RiscV32I 内に命令スロット riscv32 が 1 つしかないことを指定します。 ISA(VLIW 命令内の複数のスロットとは対照的)であり、 有効な命令は、riscv32 で実行するように定義されている命令です。

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

これは、任意の逆アセンブルされたオブジェクトの最初の逆アセンブル フラグメントが、 仕様(下記参照)は左揃えで半角 15 文字(全角 7 文字)で指定します。 あります。後続のフラグメントは、このフィールドに何も追加されず、 スペースの追加はお控えください。

その下には、riscv32izicsrriscv32 の 3 つのスロット宣言があります。 上記の isa の定義に基づき、riscv32 に対して定義された手順のみ RiscV32I isa の一部になります。他の 2 つのスロットの目的は何ですか。

スロットを使用して命令を別々のグループに分解し、そのグループに 最後に 1 つのスロットに統合されます: riscv32i, zicsr という表記に注目してください。 を riscv32 スロット宣言内で指定します。これは、スロット riscv32 が継承するように指定します。 スロット zicsr および riscv32i で定義されたすべての命令。RiscV 32 ビット ISA RV32I と呼ばれる基本 ISA で構成されており、オプションの拡張機能のセットはこれに対して 追加できます。スロット メカニズムを使用すると、これらの拡張機能内の命令に 個別に指定し、最後に必要に応じて組み合わせて、 理解することが重要です。この場合、RisvV の「I」セクションの定義できます。 「zicsr」という名前のものとは別にできます。追加のグループを定義可能 「M」(乗算/除算)、'F'(単精度浮動小数点)、'D' (倍精度浮動小数点数)、'C'(コンパクトな 16 ビット命令)などがこれに該当します。 望ましい最終的な RiscV ISA に必要な要素です。

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

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

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

zicsr スロットと riscv32 スロットの定義を変更する必要はありません。ただし、 このチュートリアルでは、必要な定義を 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 size 宣言と default latency 宣言で定義します。ただし、 特に指定しなければ、命令のサイズは 4 であり、命令の 宛先オペランドの書き込みサイクルは 0 サイクルです。注意すべき点は、命令のサイズを これは、プログラム カウンタの値を計算するための シミュレートされたシーケンスで次に実行するシーケンシャル命令のアドレス あります。このファイルのサイズ(バイト単位)と異なってもかまいません。 入力実行可能ファイル内の命令表現です。

スロットの定義の中心は、オペコード セクションです。ご覧のとおり、2 つしか オペコード(命令)fence および ebreak はこれまでに定義されました。 riscv32ifence オペコードは、名前(fence)と オペランド指定({: imm12 : })の後にオプションの逆アセンブルが続く 形式("fence")、セマンティックとしてバインドされる呼び出し可能オブジェクト 関数("&RV32IFence")を使用します。

命令オペランドは、各コンポーネントが 1 つずつ、 述語「:」をセミコロンで区切るソース オペランド リスト ':' 宛先オペランド リスト。送信元と宛先のオペランド リストはカンマです オペランド名の区切りリストを使用します。ご覧のとおり、この演算の命令オペランドは fence 命令に述語オペランドがなく、ソースが 1 つだけ含まれている オペランド名 imm12、宛先オペランドなし。RiscV RV32I サブセットは、 述語実行をサポートしていないため、述語オペランドは常に空になる 使用します。

セマンティック関数は、C++ コード セマンティック関数の呼び出しに使用する関数または呼び出し可能オブジェクトです。署名の セマンティック関数/呼び出し可能オブジェクトは void(Instruction *) である。

逆アセンブル仕様は、文字列のカンマ区切りリストで構成されます。 通常、2 つの文字列のみが使用されます。1 つはオペコード用、もう 1 つはオペコード用です。 オペランドになりますフォーマット(手順の AsString() 呼び出しを使用)すると、 文字列は disasm widths に従ってフィールド内にフォーマットされます。 必要があります。

次の演習は、riscv32i.isa ファイルに手順を追加する際に役立ちます。 「Hello World」をシミュレートするのに十分。お急ぎの場合は、 ソリューションについては riscv32i.isa および rv32i_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_decodermpact/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_isaincludes セクションが使用されている ソースファイルに含めることができる追加の .isa ファイルを指定します。「 isa_name は、特定の isa を指定するために使用されます。複数の isa がある場合は必須です。 デコーダを生成するソースファイル内で指定されます。


レジスタ / レジスタ ALU 命令の追加

次に、riscv32i.isa ファイルに新しい指示を追加します。最初の 命令のグループは、add などのレジスタ / レジスタ ALU 命令です。 and など。RiscV32 では、これらはすべて R 型のバイナリ命令形式を使用します。

31 ~ 25 24 ~ 20 19 ~ 15 14 ~ 12 11 ~ 7 6 ~ 0
7 5 5 3 5 7
func7 rs2 rs1 func3 オペコード

.isa ファイルは形式に依存しないデコーダの生成に使用されますが、 バイナリ形式とそのレイアウトを検討するとエントリーに役立ちます。皆さんが デコーダに関連する 3 つのフィールドがあり、 命令オブジェクト: rs2rs1rd。今回は 同じ方法でエンコードされた整数レジスタの名前(ビットシーケンス) 同じ命令フィールド、すべての命令で、

追加する手順は次のとおりです。

  • add - 整数の加算。
  • and - ビット演算 AND。
  • or - ビット単位の OR。
  • sll - 左にシフトします。
  • sltu - 小なり記号を符号なしに設定します。
  • sub - 整数の減算。
  • xor - ビット単位の XOR。

これらの各手順は、Terraform の opcodes セクションに riscv32i スロットの定義。前に説明したように、オペコードの名前、オペコード、 セマンティック関数が含まれています。名前は簡単です。 上記のオペコード名を使用します。また、それらはすべて同じオペランドを使用するため、 オペランドの指定には { : rs1, rs2 : rd} を使用できます。つまり rs1 により指定されるレジスタソース オペランドは、インデックス 0 を持ちます。 命令オブジェクト内のオペランド ベクトル、指定されたレジスタ ソース オペランド rs2 によって指定されるレジスタ宛先のオペランドは、インデックス 1 を持ち、rd によって指定されるレジスタ宛先のオペランドになります。 が宛先オペランド ベクトル(インデックス 0)内の唯一の要素になります。

次はセマンティック関数の仕様です。これには semfunc と、割り当てに使用できる呼び出し可能オブジェクトを指定する C++ 文字列 std::function にマッピング。このチュートリアルでは関数を使用するため、呼び出し可能 文字列は "&MyFunctionName" になります。Terraform が推奨する命名規則を使用して、 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 命令は、 イミディエイト値を使用します。この 3 つのグループは、 指示(即時フィールドに基づく): I タイプの即時指示 12 ビットの符号付き即値(I-Type 即値命令)があります。 U 型即値型(20 ビットの符号なし即値)があります。 形式は次のとおりです。

I-Type 即時形式:

31 ~ 20 19 ~ 15 14 ~ 12 11 ~ 7 6 ~ 0
12 5 3 5 7
imm12 rs1 func3 オペコード

専用の I 型即時形式:

31 ~ 25 24 ~ 20 19 ~ 15 14 ~ 12 11 ~ 7 6 ~ 0
7 5 5 3 5 7
func7 uimm5 rs1 func3 オペコード

U-Type 即時形式:

31 ~ 12 11 ~ 7 6 ~ 0
20 5 7
uimm20 オペコード

ご覧のとおり、オペランド名の rs1rd は同じビット フィールドを参照しています。 整数レジスタを表すために使用されます。このため、 保持されます。即時値のフィールドは長さと位置が異なり、 2 つ(uimm5uimm20)は符号なしですが、imm12 は符号付きです。各 それぞれ独自の名前を使用します

したがって、I タイプのオペランドは { : rs1, imm12 :rd } にする必要があります。特殊な I タイプの指示の場合は、{ : rs1, uimm5 : rd} にします。 U 型命令のオペランド指定は { : uimm20 : rd } にする必要があります。

追加する必要のある I タイプの指示は次のとおりです。

  • addi - 即時を追加します。
  • andi - ビット演算および即値。
  • ori - ビット演算または即値。
  • xori - 即値を伴うビット単位の XOR。

追加する必要がある特殊な I 型命令は次のとおりです。

  • slli - 即値で左にシフトします。
  • srai - 算術演算を即値で右にシフトします。
  • srli - 即値で右に論理シフトします。

追加する必要がある U-Type 命令は次のとおりです。

  • auipc - 大文字の即値を PC に追加します。
  • lui - 上限の即時読み込み。

オペコードに使用する名前は、命令名から自然に続くものです (新しいものを考える必要はありません。どれも独自性があります)。データの 意味関数を指定しています。前述のように、命令オブジェクトは 基になるオペランドに依存しない、ソース オペランドへのインターフェース あります。つまり、命令は同じオペレーションを含み、 オペランド型が異なる場合があります。また、同じセマンティック関数を共有できます。たとえば 次の場合、addi 命令は add 命令と同じ演算を実行します。 オペランド型が無視されるので、同じセマンティック関数を 仕様 "&RV32IAdd"andiorixorislli も同様です。 他の命令では新しいセマンティック関数を使用していますが、これらの関数には必ず オペランドではなく演算に基づいているため、srai には "&RV32ISra" を使用します。「 U-Type 命令 auipclui には同等のレジスタがないため、問題ありません。 "&RV32IAuipc""&RV32ILui" を使用します。

逆アセンブル文字列は前の演習の文字列とよく似ていますが、 想定されるとおり、%rs2 への参照は %imm12%uimm5 に置き換えられます。 または %uimm20 を適宜指定します。

変更を加えてビルドします。生成された出力を確認します。同じ 自分の作業内容を riscv32i.isa および rv32i_instructions.h


追加する必要があるブランチとジャンプ&リンクの手順では、どちらもデスティネーションを使用しています オペランド、つまり次の PC の命令の中でのみ あります。この段階で、これを名前を持つ適切なオペランドとして扱います。 next_pc。これについては、後のチュートリアルで詳しく説明します。

ブランチに関する手順

追加するブランチはすべて B-Type エンコードを使用します。

31 30 ~ 25 24 ~ 20 19 ~ 15 14 ~ 12 11 ~ 8 7 6 ~ 0
1 6 5 5 3 4 1 7
うーん うーん rs2 rs1 func3 うーん うーん オペコード

さまざまな即値を連結して 12 ビットの符号付き即値値に変換する あります。フォーマットにあまり関連性がないため、ここでは「即時」と呼ぶことにします。 bimm12: 12 ビットの即時分岐の場合。断片化への対処には バイナリデコーダの作成に関する次のチュートリアルに進みましょうすべての 分岐命令は、rs1 と rs2 で指定された整数レジスタを比較します。 条件が true の場合、即値が現在の PC 値に加算され、 次に実行される命令のアドレスを生成します。オペランドは、 したがって、分岐命令は { : rs1, rs2, bimm12 : next_pc } である必要があります。

追加する必要があるブランチの指示は次のとおりです。

  • beq - 等しい場合は分岐します。
  • bge - 大きいか等しい場合は分岐します。
  • bgeu - 符号なしより大きいか等しい場合は分岐します。
  • blt - より小さい場合は分岐します。
  • bltu - 署名なしの場合は分岐します。
  • bne - 等しくない場合は分岐します。

これらのオペコード名はすべて一意であるため、.isa で再利用できます。 説明します。当然ながら、新しいセマンティック関数名を追加する必要があります(例: "&RV32IBeq" など

逆アセンブルの仕様は少し複雑になります。これは、 命令はデスティネーションを実際に計算するのではなく、 表します。ただし、Google Cloud で保存されている情報 取得できるので、使用可能になります。この問題を解決するには、 逆アセンブリ文字列の式構文を使用します。「%」を使わない、 オペランド名には、%(expression: print format) と入力できます。非常にシンプル 式はサポートされていますが、@ シンボル。印刷形式は C スタイルの printf 形式。ただし、先頭に % はありません。次の逆アセンブル形式を使用すると、 beq 命令は次のようになります。

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

追加する必要があるのは、ジャンプとリンクの命令の 2 つ、jal(ジャンプとリンク)と、 jalr(間接的なジャンプ アンド リンク)。

jal 命令では、J-Type エンコードを使用します。

31 30 ~ 21 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)"

間接ジャンプ&リンクは、12 ビット即値を持つ I 型形式を使用します。これは、 指定された整数レジスタに符号拡張された即値を加算します。 rs1: ターゲット命令アドレスを生成します。リンクレジスタは、 rd で指定された整数レジスタ。

31 ~ 20 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.isa および rv32i_instructions.h です。


店舗に関する指示を追加する

ストアの手順は非常にシンプルです。これらはすべて S-Type 形式を使用します。

31 ~ 25 24 ~ 20 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.isa および rv32i_instructions.h です。


読み込み手順の追加

読み込み手順は、他の手順とは若干異なる方法でモデル化されます。 表示されます。負荷のレイテンシが 50% を超えるケースをモデル化するには 読み込み命令は、次の 2 つのアクションに分けられます。1)有効 アドレス計算とメモリアクセス、2)結果の書き戻しです。 負荷のセマンティック アクションを 2 つに分割して、 メイン命令と子命令があります。さらに、 オペランドを指定するときは、main と 命令。そのために、オペランド指定を 出力します。構文は以下のとおりです:

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

読み込み命令はすべて、前のバージョンと同様に I-Type 形式を使用します。 手順:

31 ~ 20 19 ~ 15 14 ~ 12 11 ~ 7 6 ~ 0
12 5 3 5 7
imm12 rs1 func3 オペコード

オペランド指定では、アドレスの計算に必要なオペランドを分割します そして、ロードデータに対するレジスタのデスティネーションからメモリアクセスを開始します。 {( : rs1, imm12 : ), ( : : rd) }

セマンティック アクションは 2 つの命令に分割されるため、セマンティック関数は 同様に 2 つの呼び出し可能オブジェクトを指定する必要があります。lw(読み込み単語)の場合、次のようになります。 記述:

    semfunc: "&RV32ILw", "&RV32ILwChild"

逆アセンブルの仕様は、より標準的な方法です。言及されていない あります。lw の場合は、次のようになります。

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

実装する必要がある読み込み命令は次のとおりです。

  • lb - バイトを読み込みます。
  • lbu - 符号なしバイトを読み込みます。
  • lh - ハーフワードを読み込みます。
  • lhu - 符号なしハーフワードを読み込みます。
  • lw - 単語を読み込みます。

変更を加えてからビルドします。生成された出力を確認します。ジャスト 前のように、現在の作業内容を riscv32i.isa および rv32i_instructions.h です。

これまでありがとうございます。以上の情報がお役に立てば幸いです。