バイナリ命令デコーダのチュートリアル

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

  • バイナリ形式の説明ファイルの構造と構文について学習します。
  • バイナリ形式の説明と ISA の説明が一致する仕組みを確認します。
  • RiscV RV32I 命令のサブセットのバイナリ記述を記述します。

概要

RiscV バイナリ命令エンコード

バイナリ命令エンコードは、命令をエンコードする標準的な方法です。 マイクロプロセッサ上で実行されています。これらは通常、実行可能なファイル、 ELF 形式です命令は固定幅または可変のいずれかにできます あります。

通常、命令では、各形式とともに少数のエンコード形式を使用します。 エンコードされた命令のタイプに応じてカスタマイズされます。たとえば、register-register が可能です。 命令は、使用可能なオペコードの数を最大化する 1 つの形式を使用できます。 レジスタ即時命令は、レジスタ即時命令に代わり、 エンコード可能な即値のサイズを増やすために使用可能なオペコード。 ほとんどの場合、分岐命令とジャンプ命令では、ファイルのサイズを最大化する形式を使用します。 オフセットの大きい分岐をサポートするために、即値を使用

RiscV でデコードする命令で使用される命令形式 シミュレータには次のものがあります。

レジスタ / レジスタ命令に使用される R 型形式。

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

I-Type 形式。レジスタ即時命令、ロード命令、 jalr 命令(12 ビット即値)。

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

特殊 I 型形式、即値命令によるシフトに使用、5 ビット 即時:

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

U-Type 形式、長即値命令(luiauipc)に使用、20 ビット 即時:

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

条件分岐に使用する B 型形式(12 ビット即値)。

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 うーん うーん オペコード

J 型形式。jal 命令に使用されます。20 ビット即値。

31 30 ~ 21 20 19 ~ 12 11 ~ 7 6 ~ 0
1 10 1 8 5 7
うーん うーん うーん うーん オペコード

S 型形式。ストア命令に使用されます。12 ビット即値。

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

これらの形式からわかるように、これらの命令はすべて 32 ビット長であり、 各フォーマットの下位 7 ビットはオペコードフィールドです。また 同じサイズの即値を持つ形式がいくつかあり、そのビットは 部分に分割されます。これから説明するように、2 進デコーダは これを表現できます。

バイナリ エンコードの説明

命令のバイナリ エンコードがバイナリ形式で表現される。 (.bin_fmt)記述ファイル。これは、Terraform で記述されたコードのバイナリ エンコードを バイナリ形式命令デコーダを 生成されます。生成されたデコーダはオペコードを決定し、生成されたデコーダの値を オペランドとイミディエイト フィールド。 デコーダを使用する方法もあります。

このチュートリアルでは、サブセットのバイナリ エンコードの記述ファイルを作成します 使用される命令をシミュレートするために必要な RiscV32I 命令の 「Hello World」と呼び、。RiscV ISA について詳しくは、以下をご覧ください。 Risc-V の仕様{.external}。

まず、ファイルを開きます。 riscv_bin_decoder/riscv32i.bin_fmt

ファイルの内容は複数のセクションに分かれています。

まず、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;
};

デコーダ定義では、デコーダ RiscV32I の名前と、 4 つの追加情報が必要です1 つ目は namespace です。これは、 生成されたコードが配置される名前空間。2 つ目は、 opcode_enum: 生成されるオペコード列挙型の生成方法を指定します。 生成されたコード内で参照する必要があります。第 3 に includes {} には、生成されたコードに必要なインクルード ファイルを指定します。 使用します。 この例では、ISA デコーダによって生成されたファイルを 説明しました 追加のインクルード ファイルは、グローバル スコープの includes {} で指定できます。 定義します。これは、複数のデコーダが定義されていて、それらすべてにデコーダが必要な場合に便利です。 同じファイルをいくつか含めるようにします。4 つ目は指示の名前のリストです デコーダが生成される命令を構成するグループ。Google の RiscVInst32 が 1 つだけの場合は、

次に、3 つの形式の定義があります。これらは、異なる命令を表す すでに定義されている命令で使用されている 32 ビット命令ワードのフォーマット あります。

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

1 つ目は、Inst32Format という名前の 32 ビット幅の命令形式を定義しています。 bits(25 ビット幅)と opcode(7 ビット幅)の 2 つのフィールドがあります。各フィールドは、 unsigned: 値が抽出されるときにゼロ拡張が行われます。 C++ 整数型に配置されます。ビットフィールドの幅の合計は、 フォーマットの幅と同じになります。エラーがある場合は、 意見の相違が生じることもありますこの形式は他の形式から派生したものではないため、 トップレベル形式と見なされます。

2 つ目は、次の値を導出する IType という名前の 32 ビット幅の命令形式を定義しています。 Inst32Format からのものであるため、この 2 つの形式は関連しています。フォーマットには 5 つの フィールド: imm12rs1func3rdopcodeimm12 フィールドは次のようになります。 signed。つまり、値が真の場合、値が符号拡張されます。 C++ 整数型に配置されます。なお、IType.opcode は両方とも 同じ符号付き/符号なし属性があり、同じ命令ワードビットを参照する Inst32Format.opcode として。

3 つ目の形式は、fence でのみ使用されるカスタム形式です。 指示とは、すでに指定されていて、ここでは説明が 確認しましょう。

重要なポイント: フィールド名が互いに異なる関連形式で再利用する 同じビットを表し、同じ符号付き/符号なし属性を持ちます。

riscv32i.bin_fmt の形式定義の後に、指示グループを記述します。 定義します。指示グループ内のすべての手順は同じである必要があります され、同じから(おそらく間接的に)派生した形式を使用します。 記述できます。ISA の手順がそれぞれ異なる場合、 異なる命令グループが使用されます。また ターゲット ISA デコードが実行モードに依存するかどうか(Arm と Thumb など) モードごとに個別の命令グループが必要です。「 bin_fmt パーサーは、命令グループごとにバイナリ デコーダを生成します。

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

命令グループは、名前 RiscV32I、幅 [32]、名前を定義します。 "OpcodeEnum" を使用するオペコードの列挙型、基本命令 使用できます。オペコードの列挙型は、 (ISA のチュートリアルで取り上げている形式独立命令デコーダ) 使用します。

各命令エンコードの説明は、次の 3 つの部分で構成されます。

  • オペコード名。命令で使用されているのと同じでなければなりません。 説明し、2 つを連携させるようにします。
  • オペコードに使用する命令形式。この形式では、 最後の部分でビットフィールドへの参照を満たすために使用されます。
  • ビット フィールド制約のカンマ区切りリスト。==!=<<=>>= オペコードが一致するには、 指示文。

.bin_fmt パーサーは、これらの情報をすべて使用して、次の処理を行うデコーダを作成します。

  • 各ビットに応じて抽出機能(符号付き/符号なし)を提供 フィールドです。extractor 関数は Namespace 内の スネークケース形式のフォーマット名で作成されます。たとえば、 IType 形式のエクストラクタ関数は名前空間 i_type に配置されます。 各エクストラクタ関数は inline で宣言されており、最も狭い uint_t を受け取ります。 形式の幅を保持し、最も狭い int_t を返す型 (符号付きの場合)、抽出されたフィールドを保持する uint_t(符号なしの場合)型 あります。例:
inline uint8_t ExtractOpcode(uint32_t value) {
  return value & 0x7f;
}
  • 命令グループごとにデコード関数。次の型の値が返されます。 OpcodeEnum です。また、最小の幅を持つ uint_t 型を使用します。 指示グループの書式を指定します

最初のビルドを実行する

riscv_bin_decoder ディレクトリに移動し、次のコマンドを使用してプロジェクトをビルドします。 次のコマンドを実行します。

$ cd riscv_bin_decoder
$ bazel build :all

ディレクトリをリポジトリのルートに戻して 検証できます。そのためには、ディレクトリを bazel-out/k8-fastbuild/bin/riscv_bin_decoder(x86 host 「k8-fastbuild」は別の文字列になります。

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

生成されたヘッダー ファイル(.h)

riscv32i_bin_decoder.h を開きます。ファイルの最初の部分には標準の ボイラープレート ガード、インクルード ファイル、名前空間宣言などがあります。その後 名前空間 internal にテンプレート化されたヘルパー関数があります。この関数 64 ビット C++ では長すぎる形式からビット フィールドを抽出するために使用される 整数。

#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

最初のセクションの後に、3 つの Namespace(各 Namespace に 1 つずつ)があります。 riscv32i.bin_fmt ファイル内の format 宣言の次の部分に置き換えます。


namespace fence {

...

}  // namespace fence

namespace i_type {

...

}  // namespace i_type

namespace inst32_format {

...

}  // namespace inst32_format

これらの各名前空間内では、inline ビットフィールド抽出関数を使用します。 が定義されます。基本のフォーマットは 抽出関数を複製します。 1) フィールド名が 1 つのフィールド名にしか出現しない、または 2) フィールド名が 1 つのフィールド名に フィールド名が同じ型のフィールドを参照している(符号付き/符号なし、ビット位置) その発生フォーマットごとにこれにより、同じ値を表すビット フィールドが トップレベル形式の名前空間にある関数を使用して抽出されるビット。

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

最後は、この命令のデコーダ関数の関数宣言です。 グループ RiscVInst32 が宣言されている。値として、符号なし 32 ビットを取ります。 命令の単語であり、OpcodeEnum 列挙型クラスメンバーを返します。 一致するものがない場合は OpcodeEnum::kNone を返します。

OpcodeEnum DecodeRiscVInst32(uint32_t inst_word);

生成されたソースファイル(.cc)

riscv32i_bin_decoder.cc を開きます。ファイルの最初の部分には、 #include、名前空間宣言、デコーダ関数 宣言します。

#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 は、空のデコード アクション(つまり、 OpcodeEnum::kNone を返す関数。他の 3 つの関数は 使用します。デコーダ全体は階層的に機能します。セット 次のビットを判別するために命令ワード内のビットが 上位レベルの命令群が含まれます。ビットは必ずしも 連続している必要がありますビット数によって、対応するルックアップ テーブルのサイズ 第 2 レベルのデコーダ関数が代入されます。これは次の セクション内にあります。

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,

    ...
};

最後に、デコーダ関数を定義します。

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

この例では、命令が 4 つしか定義されておらず、命令が 4 つしか ルックアップテーブルがあります。指示が 追加されると、デコーダの構造が変化し、デコーダ内の テーブル階層が増加する可能性があります。


レジスタ / レジスタ ALU 命令を追加する

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

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

まず、形式を追加します。さっそく開いてください 任意のエディタで「riscv32i.bin_fmt」と入力します。Inst32Format の直後に、 Inst32Format から派生した RType という形式を追加します。すべてのビットフィールド RTypeではunsignedです。名前、ビット幅、順序(左から右へ)を使用する を使用して形式を定義します。ヒントや ソリューション全体 こちらをクリックしてください。

次に、指示を追加します。手順は次のとおりです。

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

エンコードは次のとおりです。

31 ~ 25 24 ~ 20 19 ~ 15 14 ~ 12 11 ~ 7 6 ~ 0 オペコード名
000,000 rs2 rs1 000 011 0011 追加
000,000 rs2 rs1 111 011 0011
000,000 rs2 rs1 110 011 0011 または
000,000 rs2 rs1 001 011 0011 SLL
000,000 rs2 rs1 011 011 0011 SLTU
010 0000 rs2 rs1 000 011 0011 Pub/Subです
000,000 rs2 rs1 100 011 0011 XOR
func7 func3 オペコード

これらの命令定義を、 「RiscVInst32」命令グループ。バイナリ文字列は、先頭に 0b の接頭辞(16 進数の 0x と同様)。より簡単に 2 進数の長い文字列を読み取るには、次のように単一引用符 ' を挿入することもできます。 適当な桁の区切り記号を使用します。

これらの命令定義にはそれぞれ 3 つの制約があります。 func7func3opcodesub を除くすべてのケースで、func7 制約が適用されます。 次のとおりです。

func7 == 0b000'0000

func3 制約は、ほとんどの命令で異なります。addsub:

func3 == 0b000

opcode 制約は、これらのどの手順でも同じです。

opcode == 0b011'0011

各行の最後には、必ずセミコロン ; を付けます。

完成したソリューションは こちらをご覧ください。

次に、先ほどと同じようにプロジェクトをビルドし、生成されたファイルを riscv32i_bin_decoder.cc ファイル。その他のデコーダ関数についても、 新しい命令を処理するために生成されます。ほとんどの場合、 これは以前に生成されたものと似ていますが add/sub デコードに使用される DecodeRiscVInst32_0_c:

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

この関数では、静的なデコード テーブルが生成され、ルックアップ値が 抽出したデータを使って適切なインデックスを選択します。これにより、 第 2 階層になりますが、オペコードは テーブル内で直接検索した場合、 別の関数呼び出しを必要としません。


即値による 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 オペコード

I-Type 形式は riscv32i.bin_fmt にすでに存在するため、追加する必要はありません。 その形式を追加します。

特殊な I-Type 形式と、先ほど定義した R-Type 形式を比較すると、 前の演習では、唯一の違いは rs2 フィールドが 名前は uimm5 に変更されます。まったく新しいフォーマットを追加する代わりに、 R-Type 形式。別のフィールドを追加することはできません。追加すると オーバーレイを追加できます。オーバーレイは、 フォーマットの複数のビットを表現し、同じシーケンスの複数のサブシーケンスを 独立した名前付きエンティティに変換します。副作用として、生成されるコードが には、オーバーレイの抽出関数のほか、 フィールドの値ですこの例では、rs2uimm5 の両方が署名されていない場合、 フィールドが使用されていることを明示的に示す以外、大きな違いはありません。 使用できます。uimm5 という名前のオーバーレイを R-Type 形式に追加するには、 次のように置き換えます。

  overlays:
    unsigned uimm5[5] = rs2;

追加する必要がある新しい形式は、U-Type 形式のみです。追加の前に、 この形式を使用する 2 つの命令、auipclui。どちらも 20 ビットの即値を左に 12 シフトしてから使用する PC をそれに追加する(auipc)か、レジスタに直接書き込む (lui)。オーバーレイを使用すると、事前のシフトされた即値、 計算を命令の実行から命令にシフトすることで、 あります。まず、表で指定されているフィールドに従って形式を追加します。 ご覧ください。次に、以下のオーバーレイを追加します。

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

オーバーレイ構文を使用すると、フィールドだけでなくリテラルも連結できます。 あります12 個のゼロを連結します。つまり、左にシフトします。 12 まで上げます

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

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

エンコードは次のとおりです。

31 ~ 20 19 ~ 15 14 ~ 12 11 ~ 7 6 ~ 0 opcode_name
imm12 rs1 000 001 0011 アディ
imm12 rs1 111 001 0011 andi
imm12 rs1 110 001 0011 ori
imm12 rs1 100 001 0011 チョリ語
func3 オペコード

追加する必要がある R タイプ(特殊な I タイプ)の指示は次のとおりです。

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

エンコードは次のとおりです。

31 ~ 25 24 ~ 20 19 ~ 15 14 ~ 12 11 ~ 7 6 ~ 0 オペコード名
000,000 uimm5 rs1 001 001 0011 スリム
010 0000 uimm5 rs1 101 001 0011 スライ
000,000 uimm5 rs1 101 001 0011 SRR
func7 func3 オペコード

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

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

エンコードは次のとおりです。

31 ~ 12 11 ~ 7 6 ~ 0 オペコード名
uimm20 001 0111 auipc
uimm20 011 0111 lui
オペコード

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


次に定義する必要がある一連の命令は、条件分岐です。 ジャンプ&リンク命令、ジャンプ&リンクレジスタ できます。

追加する条件分岐はすべて 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 オペコード

B タイプのエンコードと R タイプのエンコードのレイアウトは同じですが、 RiscV のドキュメントに合わせて、新しい形式タイプの使用を選択する。 しかし、オーバーレイを追加して適切なブランチを取得することもできます。 R 型の func7 フィールドと rd フィールドを使用した変位即時出力 あります。

上記のフィールドで形式 BType を追加する必要がありますが、追加する必要はありません 十分ですご覧のとおり、即値が 2 つの命令フィールドに分割されています。 さらに、分岐命令ではこれを単純な文字列の連結として扱いません。 表示されます。代わりに、各フィールドがさらに分割され、 異なる順序で連結されます。最後に、その値が左にシフトされ、 16 ビットの整列済みオフセットを取得します。

即座を形成するために使用される命令ワード内のビットの順序は、31、 7、30..25、11..8.これは、次のサブフィールド参照に対応します。 インデックスまたは範囲は、フィールド内のビットを右から左へ番号付けします。つまり、 imm7[6]imm7 の msb を表し、imm5[0] は lsb を表します。 imm5

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

このビット操作を分岐命令自体に含めると、 大きな欠点がありますまず、セマンティック関数の実装を 詳細が確認できます。第二に、実行時間が長くなる オーバーヘッドを軽減できます。解決策は、BType 形式にオーバーレイを追加することです。 末尾の「0」考慮する必要があります

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

オーバーレイは署名されているため、自動的に符号拡張が行われます。 単語から抽出されるデータです。

ジャンプ&リンク(即時)命令は、J-Type エンコードを使用します。

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

この形式も簡単に追加できますが、 思ったほど簡単ではありませんでした。このビット列は、 完全な即座は 31, 19..12, 20, 30..21 であり、最終即座は シフトします。これを解決するには、 左シフトに対応する 21 ビットを次の形式にオーバーレイします。

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

おわかりのように、オーバーレイの構文では、 フィールドに入力します。また、フィールド名を使用しない場合は、 数字は指示語そのものを指すため、上記は同様に 次のように記述します。

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

最後に、ジャンプ&リンク(レジスタ)は、使用されている I 型形式を使用します。 使用します。

I-Type 即時形式:

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

今回は、形式を変更する必要はありません。

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

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

次のようにエンコードされます。

31 ~ 25 24 ~ 20 19 ~ 15 14 ~ 12 11 ~ 7 6 ~ 0 オペコード名
imm7 rs2 rs1 000 imm5 110 0011 ベク
imm7 rs2 rs1 101 imm5 110 0011 BGE
imm7 rs2 rs1 111 imm5 110 0011 Bgeu
imm7 rs2 rs1 100 imm5 110 0011 blt
imm7 rs2 rs1 110 imm5 110 0011 bltu
imm7 rs2 rs1 001 imm5 110 0011 BNE
func3 オペコード

jal 命令は次のようにエンコードされます。

31 ~ 12 11 ~ 7 6 ~ 0 オペコード名
imm20 110 1111 ジャル
オペコード

jalr 命令は次のようにエンコードされます。

31 ~ 20 19 ~ 15 14 ~ 12 11 ~ 7 6 ~ 0 opcode_name
imm12 rs1 000 110 0111 ジャル
func3 オペコード

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


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

ストアの指示では S-Type エンコードを使用します。S-Type エンコードは B-Type エンコードと同じです。 分岐命令で使用されるエンコーディングで記述します。ただし、 なります。RiscV に合わせて SType 形式を追加することにしています。 ご覧ください

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

SType 形式の場合、即値の値は単純であり、 2 つの即値フィールドを前方連結しているため、オーバーレイ仕様は は単純です。

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

フィールド全体を連結する場合、ビット範囲指定子は必要ありません。

ストア命令は次のようにエンコードされます。

31 ~ 25 24 ~ 20 19 ~ 15 14 ~ 12 11 ~ 7 6 ~ 0 オペコード名
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 オペコード

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


読み込み手順を追加

読み込み手順では I-Type 形式を使用します。そこで変更を行う必要はありません。

エンコードは次のとおりです。

31 ~ 20 19 ~ 15 14 ~ 12 11 ~ 7 6 ~ 0 opcode_name
imm12 rs1 000 000 0011 lb
imm12 rs1 100 000 0011 lbu
imm12 rs1 001 000 0011 lh
imm12 rs1 101 000 0011 ルー
imm12 rs1 010 000 0011 lw
func3 オペコード

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

以上でこのチュートリアルは終了です。お役立ていただけましたら幸いです。