FPGA開発日記

FPGAというより、コンピュータアーキテクチャかもね! カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages

LLVMのバックエンドを作るための第一歩 (4. 命令フォーマットを定義する)

f:id:msyksphinz:20190425001356p:plain
LLVM Compiler Infrastructure

LLVMバックエンドを追加するにあたり、MYRISCVXアーキテクチャを定義するためのtdファイルを作成する必要がある。 tdファイルはLLVMのバックエンドを定義するためのDSLで、バックエンドを理解するためには避けては通れないものだ。

MYRISCVXInstrInfo.td

次に追加すべきは、MYRISCVXの命令を定義するためのMYRISCVXInstrInfo.tdだ。 MYRISCVXInstrInfo.td内には命令フォーマットを定義するためのMYRISCVXInstrFormats.tdを用意する必要がある。

  • lib/Target/MYRISCVX/MYRISCVXInstrFormats.td
// Generic MYRISCVX Format
class MYRISCVXInst<dag outs, dag ins, string asmstr, list<dag> pattern,
                   InstrItinClass itin, Format f>: Instruction
{
  // Inst and Size: for tablegen(... -gen-emitter) and
  // tablegen(... -gen-disassembler) in CMakeLists.txt
  field bits<32> Inst;
  Format Form = f;

  let Namespace = "MYRISCVX";

  let Size = 4;

  let OutOperandList = outs;
  let InOperandList  = ins;

  let AsmString   = asmstr;
  let Pattern     = pattern;
  let Itinerary   = itin;

  //
  // Attributes specific to MYRISCVX instructions...
  //
  bits<4> FormBits = Form.Value;

  // TSFlags layout should be kept in sync with MYRISCVXInstrInfo.h.
  let TSFlags{3-0}   = FormBits;

  let DecoderNamespace = "MYRISCVX";

  field bits<32> SoftFail = 0;
}

MYRISCVXInstがベースのクラスになる。32ビットのフィールドInstを定義し、これが命令フィールドそのものになる。 OutOperandListはこの命令で書き込まれるレジスタのリスト、InOperandListはこの命令で読み込まれるレジスタのリストだ。 このオペランドリストを使用してデータフローを構築したり、レジスタ割り付けの解析を行う。 さらに、命令のニーモニックを格納するためにAsmStringを定義し、PatternLLVM IRから命令を生成するために必要なパターンを格納する。

さて、ここから先はいよいよ命令フォーマットの定義氏をしていく。RISC-Vには、基本として以下の命令フォーマットが決められている。

f:id:msyksphinz:20190501234316p:plain
RISC-Vの命令フォーマット

ここでは、上記の6つの命令フォーマットについてクラスを作ればよいことになる。 それぞれ、上記で定義したMYRISCVXInstクラスを継承して定義する。

f:id:msyksphinz:20190501234502p:plain
命令フォーマットクラスの継承関係
  • lib/Target/MYRISCVX/MYRISCVXInstrFormats.td
//===----------------------------------------------------------------------===//
// R-Type instruction class in MYRISCVX : <|opcode|funct7|funct3|rd|rs1|rs2|>
//===----------------------------------------------------------------------===//

class MYRISCVX_R<bits<7> opcode, bits<3> funct3, bits<7> funct7,
                dag outs, dag ins, string asmstr,
                list<dag> pattern, InstrItinClass itin>:
      MYRISCVXInst<outs, ins, asmstr, pattern, itin, FrmR>
{
  bits<5>  rd;
  bits<5>  rs1;
  bits<5>  rs2;

  let Inst{31-25} = funct7;
  let Inst{24-20} = rs2;
  let Inst{19-15} = rs1;
  let Inst{14-12} = funct3;
  let Inst{11-7}  = rd;
  let Inst{6-0}   = opcode;
}

MYRISCVXInstを継承して、MYRISCVX_Rクラスを作成した。 引数としてopcode, funct3, func7を取る。 また、outs, insは使用すレジスタを指定し、データフローを構築する。 asmstrには命令のニーモニックを定義する。

rd, rs1, rs2を定義して、オペランドとして指定されるレジスタオペランドを作成する。 そして、MYRISCVXInstインスタンスしたInst変数に対して、ビットフィールドを指定する形で値を設定する。 Inst{31-25}ならば、Inst変数の31ビットから25ビットまでの7ビットにfunct7が代入されるという意味になる。

  • lib/Target/MYRISCVX/MYRISCVXInstrFormats.td

MYRISCVX_Iは即値命令を扱うためのフォーマットだ。

//===----------------------------------------------------------------------===//
// I-Type instruction class in MYRISCVX : <|opcode|funct3|rd|rs1|imm12|>
//===----------------------------------------------------------------------===//
class MYRISCVX_I<bits<7> opcode, bits<3> funct3,
                 dag outs, dag ins, string asmstr, list<dag> pattern,
                 InstrItinClass itin>:
  MYRISCVXInst<outs, ins, asmstr, pattern, itin, FrmI>
{
  bits<5>  rd;
  bits<5>  rs1;
  bits<12> imm12;

  let Inst{31-20} = imm12;
  let Inst{19-15} = rs1;
  let Inst{14-12} = funct3;
  let Inst{11-7}  = rd;
  let Inst{6-0}   = opcode;
}
  • lib/Target/MYRISCVX/MYRISCVXInstrFormats.td

MYRISCVX_Sはメモリストア命令を扱うためのフォーマットだ。

//===----------------------------------------------------------------------===//
// S-Type instruction class in MYRISCVX : <|opcode|rs2|rs1|width|offset>
//===----------------------------------------------------------------------===//
class MYRISCVX_S<bits<7> opcode, bits<3> funct3,
                 dag outs, dag ins, string asmstr, list<dag> pattern,
                 InstrItinClass itin>:
  MYRISCVXInst<outs, ins, asmstr, pattern, itin, FrmS>
{
  bits<5>  rs2;
  bits<5>  rs1;
  bits<12> simm12;

  let Inst{31-25} = simm12{11-5};
  let Inst{19-15} = rs1;
  let Inst{24-20} = rs2;
  let Inst{14-12} = funct3;
  let Inst{11-7}  = simm12{4-0};
  let Inst{6-0}   = opcode;

  let DecoderMethod = "DecodeStore";
}
  • lib/Target/MYRISCVX/MYRISCVXInstrFormats.td

MYRISCVX_Uは即値生成命令(LUI命令やAUIPC命令など)を扱うためのフォーマットだ。

//===----------------------------------------------------------------------===//
// U-Type instruction class in MYRISCVX : <|opcode|rd|imm31-12>
//===----------------------------------------------------------------------===//
class MYRISCVX_U<bits<7> opcode,
                 dag outs, dag ins, string asmstr, list<dag> pattern,
                 InstrItinClass itin>:
  MYRISCVXInst<outs, ins, asmstr, pattern, itin, FrmI>
{
  bits<5>  rd;
  bits<20> imm20;

  let Inst{31-12} = imm20;
  let Inst{11-7}  = rd;
  let Inst{6-0}   = opcode;
}
  • lib/Target/MYRISCVX/MYRISCVXInstrFormats.td

MYRISCVX_Bは条件分岐命令を扱うためのフォーマットだ。

//===----------------------------------------------------------------------===//
// B-Type instruction class in MYRISCVX : <|opcode|funct3|rs1|rs2|imm12|>
//===----------------------------------------------------------------------===//
class MYRISCVX_B<bits<7> opcode, bits<3> funct3,
                 dag outs, dag ins, string asmstr, list<dag> pattern,
                 InstrItinClass itin>:
  MYRISCVXInst<outs, ins, asmstr, pattern, itin, FrmI>
{
  bits<12> imm12;
  bits<5>  rs2;
  bits<5>  rs1;

  let Inst{31}    = imm12{11};
  let Inst{30-25} = imm12{9-4};
  let Inst{14-12} = funct3;
  let Inst{11-8}  = imm12{3-0};
  let Inst{7}     = imm12{10};
  let Inst{6-0}   = opcode;
}
  • lib/Target/MYRISCVX/MYRISCVXInstrFormats.td

MYRISCVX_Jは即値ジャンプ命令を扱うためのフォーマットだ。

//===----------------------------------------------------------------------===//
// J-Type instruction class in MYRISCVX : <|opcode|imm20|>
//===----------------------------------------------------------------------===//
class MYRISCVX_J<bits<7> opcode,
                 dag outs, dag ins, string asmstr, list<dag> pattern,
                 InstrItinClass itin>:
  MYRISCVXInst<outs, ins, asmstr, pattern, itin, FrmI>
{
  bits<20> imm20;

  let Inst{31}    = imm20{19};
  let Inst{30-21} = imm20{9-0};
  let Inst{20}    = imm20{10};
  let Inst{19-12} = imm20{18-11};
  let Inst{6-0}   = opcode;
}

Formatsを定義する

上記の命令区分に応じて、命令を区別するためのFormatsを定義する。 これはMYRISCVXInst内でTSFlagsに格納される。

  • lib/Target/MYRISCVX/MYRISCVXInstrFormats.td
// Format specifies the encoding used by the instruction.  This is part of the
// ad-hoc solution used to emit machine instruction encodings by our machine
// code emitter.
class Format<bits<3> val> {
  bits<3> Value = val;
}

def Pseudo : Format<0>;
def FrmR   : Format<1>;
def FrmI   : Format<2>;
def FrmS   : Format<3>;
def FrmU   : Format<4>;
def FrmB   : Format<5>;
def FrmJ   : Format<6>;