FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

ChiselのParametersとConfigによるパラメタライズの方法

Chiselで記述されたRocket-Chipのデザインを見ていると、不思議な演算子が使われているのを見たことがあるかもしれない。

f:id:msyksphinz:20190517003942p:plain
ConfigInRV32, ConfigInRV64, ConfigOutによるパラメタライズ
  • src/main/scala/system/Configs.scala
class DefaultConfig extends Config(new WithNBigCores(1) ++ new BaseConfig)

Rocket-ChipはこのようなConfigParametersをうまく活用して、非常にフレキシブルな構成を可能にしている。 ただしこの部分は非常にややこしく、理解するのに時間がかかる。Chiselにおけるパラメタリゼーションについて少し調べて見た。

ベースにしているのは、以下の資料 Advanced Chisel Parameterization だ。しかしこれはChisel2をベースにしているので少し古いかもしれない。

まずConfig(new WithNBigCores(1) ++ new BaseConfig)のような記述を実現しているのは、Chiselで作られたConfigクラスの役割だ。これはChiselデフォルトの機能ではなく、freechipsの成果物であるのでソースコードを持ってきて自分のプロジェクトに配置する必要がある。

  • src/main/scala/subsystem/Configs.scala
/* Composable partial function Configs to set individual parameters */

class WithNBigCores(n: Int) extends Config((site, here, up) => {
  case RocketTilesKey => {
    val big = RocketTileParams(
      core   = RocketCoreParams(mulDiv = Some(MulDivParams(
        mulUnroll = 8,
...

このConfigクラスの実体はRocket-Chipリポジトリ./src/main/scala/config/Config.scalaに置かれている。

github.com

abstract class View {
  final def apply[T](pname: Field[T]): T = apply(pname, this)
  final def apply[T](pname: Field[T], site: View): T = {
...
}
abstract class Parameters extends View {
  final def ++ (x: Parameters): Parameters =
    new ChainParameters(this, x)
...
}
class Config(p: Parameters) extends Parameters {
  def this(f: (View, View, View) => PartialFunction[Any,Any]) = this(Parameters(f))
....
}

Configクラスの使い方は上記のAdvanced Chisel Parameterにある程度説明がある。 例えばパラメータ化したい要素を追加するときは以下のようにする(ここではSyntax Sugarを使っている)。

以下ではInWidthというパラメータを持つコンフィグレーションを2つ用意している。

case object InWidth extends Field[Int]
class ConfigInRV64 extends Config((site, here, up) => {
  case InWidth => 64
})
class ConfigInRV32 extends Config((site, here, up) => {
  case InWidth => 32
})

一方で、もう一つOutWidthパラメータを持つコンフィグレーションを1つ定義する。この状態でConfigInRV64/ConfigInRV32ConfigOutは全く別のコンフィグレーションとして定義されている。

case object OutWidth extends Field[Int]
class ConfigOut extends Config((site, here, up) => {
  case OutWidth => 32
})

しかし、これらのConfigをベースとしたクラスどうしは++演算子を用いて接続し、新たなクラスを作成することができるという特徴がある。

class DefaultConfig32 extends Config(new ConfigRV32 ++ new ConfigOut)
class DefaultConfig64 extends Config(new ConfigRV64 ++ new ConfigOut)

これらのコンフィグレーションは、パラメータクラスに変換して、モジュールのパラメータとして渡すことができる。

  val tile_parameters = (new DefaultConfig32).toInstance
  chisel3.Driver.execute(args, () => new TestModule()(tile_parameters))

TestModuleは以下のように定義されている。implicit p: Parameterstile_parametersが渡される。

class TestModule (implicit p: Parameters) extends Module {
  val io = IO(new Bundle {
    val in = Input(UInt(p(InWidth).W))
    val success = Output(UInt(p(OutWidth).W))
  })

  io.success := io.in
}

p = ParametersDefaultConifg32を設定した状態でVerilogを生成すると、inポートが32ビット、outポートが32ビットとなる。

module TestModule( // @[:@3.2]
  input         clock, // @[:@4.4]
  input         reset, // @[:@5.4]
  input  [31:0] io_in, // @[:@6.4]
  output [31:0] io_success // @[:@6.4]
);
  assign io_success = io_in; // @[param_test.scala 27:14:@11.4]
endmodule

一方で、DefaultConfig64をパラメータに渡してVerilogを生成するとinポートが64ビット、outポートが64ビットとなる。

module TestModule( // @[:@3.2]
  input         clock, // @[:@4.4]
  input         reset, // @[:@5.4]
  input  [63:0] io_in, // @[:@6.4]
  output [31:0] io_success // @[:@6.4]
);
  assign io_success = io_in[31:0]; // @[param_test.scala 27:14:@11.4]
endmodule

このように、パラメタライズしたい項目に合わせて定数値を切り替えたり、パラメータの有無でモジュールのON/OFFを切り替えるということが柔軟に行うことができるようになる。

LLVMのバックエンドを作るための第一歩 (7. ターゲットとサブターゲット)

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

MYRISCVXTargetMachine.{h,cpp}

その名の通りターゲットマシンを定義するファイルだ。MYRISCVXTargetMachineLLVMTargetMachineを継承したクラスで、ターゲットマシンのすべての情報を集約する。 このクラスには、サブターゲットの情報も含まれている。

f:id:msyksphinz:20190515233753p:plain
MYRISCVXTargetMachineクラス
  • MYRISCVXTargetMachine.h
namespace llvm {

class MYRISCVXTargetMachine : public LLVMTargetMachine {
  std::unique_ptr<TargetLoweringObjectFile> TLOF;
  // Selected ABI
  MYRISCVXABIInfo ABI;
  MYRISCVXSubtarget DefaultSubtarget;
...

また、MYRISCVXTargetMachineを継承したクラスとして、リトルエンディアンの実装であるMYRISCVXelTargetMachineが定義される。 MIPSなどのバイエンディアンアーキテクチャでは、リトルエンディアンとビックエンディアンの両方が定義されるが、RISC-Vではリトルエンディアンしか存在しないので1種類しか不要なのだが、ここでは32ビット版と64ビット版のターゲットマシンを作りたいので、MYRISCVX32TargetMachineMYRISCVX64TargetMachineを定義する。

  • MYRISCVXTargetMachine.h
/// MYRISCVX32TargetMachine - MYRISCVX32 little endian target machine.
///
class MYRISCVX32TargetMachine : public MYRISCVXTargetMachine {
  virtual void anchor();
 public:
  MYRISCVX32TargetMachine(const Target &T, const Triple &TT, StringRef CPU,
                          StringRef FS, const TargetOptions &Options,
                          Optional<Reloc::Model> RM,
                          Optional<CodeModel::Model> CM,
                          CodeGenOpt::Level OL, bool JIT);
};

/// MYRISCVX64TargetMachine - MYRISCVX64 little endian target machine.
///
class MYRISCVX64TargetMachine : public MYRISCVXTargetMachine {
  virtual void anchor();
 public:
  MYRISCVX64TargetMachine(const Target &T, const Triple &TT, StringRef CPU,
                          StringRef FS, const TargetOptions &Options,
                          Optional<Reloc::Model> RM,
                          Optional<CodeModel::Model> CM,
                          CodeGenOpt::Level OL, bool JIT);
};

TargetMachineには何を記述すべきなのか

MYRISCVXTargetMachine.cppには、ターゲットアーキテクチャの様々なことを記述する必要がある。

  • コンストラク MYRISCVXTargetMachine (...) : 親クラスLLVMTargetMachineを呼び出して基本的な情報の登録を行う。基本的な情報とは、ビッグエンディアン・リトルエンディアン、ABI、リロケーションモデルなどだ。 MYRISCVX32TargetMachineMYRISCVX64TargetMachineは両方ともリトルエンディアンなので、とりあえず現時点ではコンストラクタに渡す引数は同じだ。
  MYRISCVX32TargetMachine::MYRISCVX32TargetMachine(const Target &T, const Triple &TT,
                                                   StringRef CPU, StringRef FS,
                                                   const TargetOptions &Options,
                                                   Optional<Reloc::Model> RM,
                                                   Optional<CodeModel::Model> CM,
                                                   CodeGenOpt::Level OL, bool JIT)
      : MYRISCVXTargetMachine(T, TT, CPU, FS, Options, RM, CM, OL, JIT, true) {}
  
  void MYRISCVX64TargetMachine::anchor() { }
  
  MYRISCVX64TargetMachine::MYRISCVX64TargetMachine(const Target &T, const Triple &TT,
                                                   StringRef CPU, StringRef FS,
                                                   const TargetOptions &Options,
                                                   Optional<Reloc::Model> RM,
                                                   Optional<CodeModel::Model> CM,
                                                   CodeGenOpt::Level OL, bool JIT)
      : MYRISCVXTargetMachine(T, TT, CPU, FS, Options, RM, CM, OL, JIT, true) {}
  
  // MYRISCVX32TargetMachine / MYRISCVX64TargetMachineが呼び出す先のコンストラクタ
  MYRISCVXTargetMachine::MYRISCVXTargetMachine(const Target &T, const Triple &TT,
                                               StringRef CPU, StringRef FS,
                                               const TargetOptions &Options,
                                               Optional<Reloc::Model> RM,
                                               Optional<CodeModel::Model> CM,
                                               CodeGenOpt::Level OL, bool JIT,
                                               bool isLittle)
      : LLVMTargetMachine(T, computeDataLayout(TT, CPU, Options, isLittle), TT,
                          CPU, FS, Options, getEffectiveRelocModel(JIT, RM),
                          getEffectiveCodeModel(CM, CodeModel::Small), OL),
        isLittle(isLittle), TLOF(make_unique<MYRISCVXTargetObjectFile>()),
        ABI(MYRISCVXABIInfo::computeTargetABI()),
        DefaultSubtarget(TT, CPU, FS, isLittle, *this) {
  }

LLVMTargetMachineクラスのインスタンス化にあたり必要な情報は、以下の関数で算出している。

  • computeDataLayout(TT, CPU, Options, isLittle) xxx節で説明した。
  • getEffectiveRelocModel(JIT, RM) : MYRISCVXの有効なリロケーションモデルを返す。リロケーションモデルには以下が定義されている。

    • include/llvm/Support/CodeGen.h cpp // Relocation model types. namespace Reloc { enum Model { Static, PIC_, DynamicNoPIC, ROPI, RWPI, ROPI_RWPI }; }
  • getEffectiveCodeModel(CM, CodeModel::Small) : MYRISCVXのコードモデルを設定する。コードモデルというのは命令生成に置いてメモリアドレスの計算の方法を示し、現在のPCに対してコードとデータの相対的な関係に対する制限を付け加えることができる。ここでは、Default値としてCodeModel::Smallが設定されている。

MYRISCVXSubTarget.{h,cpp}

f:id:msyksphinz:20190515233849p:plain
MYRISCVXSubtarget

サブターゲットはMYRISCVXの中でもアーキテクチャのバリエーションを付けるために使用されるもので、その名の通りサブのターゲットだ。 llcにおいてアーキテクチャ-march=myriscvx32で指定するが、サブターゲットは-mcpu=で指定する。例えば、同じMIPSアーキテクチャの中でも、ISAのバージョン違いや実装の違いによってさまざまなバリエーションがある。

  • lib/Target/Mips/Mips.td
  def FeatureNoABICalls  : SubtargetFeature<"noabicalls", "NoABICalls", "true",
                                  "Disable SVR4-style position-independent code">;
  def FeaturePTR64Bit    : SubtargetFeature<"ptr64", "IsPTR64bit", "true",
                                  "Pointers are 64-bit wide">;
  def FeatureGP64Bit     : SubtargetFeature<"gp64", "IsGP64bit", "true",
                                  "General Purpose Registers are 64-bit wide">;
  def FeatureFP64Bit     : SubtargetFeature<"fp64", "IsFP64bit", "true",
                                  "Support 64-bit FP registers">;
  ...
./bin/llc -march=mips -mcpu=help
...

Available features for this target:

  mips1                    - Mips I ISA Support [highly experimental].
  mips16                   - Mips16 mode.
  mips2                    - Mips II ISA Support [highly experimental].
  mips3                    - MIPS III ISA Support [highly experimental].
  mips32                   - Mips32 ISA Support.
...
  noabicalls               - Disable SVR4-style position-independent code.
...

このサブターゲットを定義するためのクラスMYRISCVXSubTargetMYRISCVX.tdから生成された自動生成クラスMYRISCVXGenSubtargetInfoから生成される。

RISC-Vの場合、アーキテクチャに大きなバリエーションがある訳ではないのだが、ISAのどのモジュールが使用できるのか、例えば乗除算命令は使用できるのか、Compressed命令は使用できるのか、浮動小数点命令は使用できるのかなどのバリエーションが作れそうだ。

  • lib/Target/MYRISCVX/MYRISCVXSubtarget.h
    class MYRISCVXSubtarget : public MYRISCVXGenSubtargetInfo {
  ...
       protected:
      bool is_enable_M = false;
      bool is_enable_A = false;
      bool is_enable_F = false;
      bool is_enable_D = false;
  ...
  • lib/Target/MYRISCVX/MYRISCVXSubtarget.cpp
  MYRISCVXSubtarget &
  MYRISCVXSubtarget::initializeSubtargetDependencies(StringRef CPU, StringRef FS,
                                                     const TargetMachine &TM) {
    if (TargetTriple.getArch() == Triple::myriscvx32) {
      if (CPU.empty() || CPU == "generic") {
        CPU = "cpu-rv32";
      }
    } else if (TargetTriple.getArch() == Triple::myriscvx64) {
      if (CPU.empty() || CPU == "generic") {
        CPU = "cpu-rv64";
      }
    } else {
      errs() << "!!!Error, TargetTriple.getArch() = " << TargetTriple.getArch()
             <<  "CPU = " << CPU << "\n";
      exit(0);
    }
  
    // Parse features string.
    ParseSubtargetFeatures(CPU, FS);
    // Initialize scheduling itinerary for the specified CPU.
    InstrItins = getInstrItineraryForCPU(CPU);
  
    return *this;
  }

LLVMのバックエンドを作るための第一歩 (6. ELFとリロケーションレコード)

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

LLVMのバックエンドにオリジナルターゲットアーキテクチャを追加していくプロジェクト、MYRISCVXターゲットアーキテクチャを追加したら、今度はELFの情報を追加する必要がある。

MYRISCVXはRISC-Vのオリジナル実装なんで、ELFやABIはRISC-Vと全く同一で進めようと思う。

MYRISCVXのELF出力を登録する

  • include/llvm/BinaryFormat/ELF.h こちらもELFを生成するにあたり必要だ。ELFのアーキテクチャ番号は、実は取り決めがありhttp://www.sco.com/developers/gabi/latest/ch4.eheader.htmlなどを参考にすること。このページでは、EM_RISCVは243と決められている。ここでは、まだ誰も使用していない248をMYRISCVXとして使用することにする。
  diff --git a/include/llvm/BinaryFormat/ELF.h b/include/llvm/BinaryFormat/ELF.h
  index ce35d127d43..55a6c7ddb4b 100644
  --- a/include/llvm/BinaryFormat/ELF.h
  +++ b/include/llvm/BinaryFormat/ELF.h
  @@ -312,6 +312,7 @@ enum {
     EM_RISCV = 243,         // RISC-V
     EM_LANAI = 244,         // Lanai 32-bit processor
     EM_BPF = 247,           // Linux kernel bpf virtual machine
  +  EM_MYRISCVX = 248,      // MYRISCVX
   };
  
   // Object file classes.

さらに、MYRISCVX向けのe_flagsを作成する。e_flagsはプロセッサ固有のフラグを示すのに使われる。たとえば、アーキテクチャ内の固有の実装を示したり、ISAの中のどのカテゴリをサポートしているか、などを示すためにe_flagsが使用される。例えば、MIPSの例を示す。

  • include/llvm/BinaryFormat/ELF.h
  // Mips Specific e_flags
  enum : unsigned {
    EF_MIPS_NOREORDER = 0x00000001, // Don't reorder instructions
    EF_MIPS_PIC = 0x00000002,       // Position independent code
    EF_MIPS_CPIC = 0x00000004,      // Call object with Position independent code
    EF_MIPS_ABI2 = 0x00000020,      // File uses N32 ABI
    EF_MIPS_32BITMODE = 0x00000100, // Code compiled for a 64-bit machine
  ...

RISC-Vの場合はどうなっているのか?以下の資料を参考にすると、以下の項目を定義しなければならないようだ。

Bit 0 Bit 1 - 2 Bit 3 Bit 4 Bit 5 - 31
RVC Float ABI RVE TSO Reserved

詳細はリンク先に譲る。ここでは、ELF.hに以下を追加した(というか、本家のRISC-Vと一緒だ)。

  enum : unsigned {
    EF_RISCV_RVC = 0x0001,
    EF_RISCV_FLOAT_ABI = 0x0006,
    EF_RISCV_FLOAT_ABI_SOFT = 0x0000,
    EF_RISCV_FLOAT_ABI_SINGLE = 0x0002,
    EF_RISCV_FLOAT_ABI_DOUBLE = 0x0004,
    EF_RISCV_FLOAT_ABI_QUAD = 0x0006,
    EF_RISCV_RVE = 0x0008
  };

リロケーションレコード

関数呼び出しなどをコンパイルするときに、その関数が実際にどこに配置されるかが分からないため、再配置(リロケーション)のためのシンボルが挿入される。 このリロケーションのアルゴリズムアーキテクチャ毎に決められており、LLVMではリンク時にリロケーションのアルゴリズムに応じてアドレス計算の命令が挿入される。 ELFフォーマットには、リロケーションの情報も含まれており、MYRISCVXもリロケーション情報を登録しなければならない。 ここではRISC-Vと全く同様のリロケーション情報を登録した。 RISC-Vのリロケーションは、https://github.com/riscv/riscv-elf-psabi-doc/blob/master/riscv-elf.md#relocations で見ることができる。 このリロケーション情報をinclude/llvm/BinaryFormat/ELFRelocs/MYRISCVX.defに置く。

  • include/llvm/BinaryFormat/ELFRelocs/MYRISCVX.def
  #ifndef ELF_RELOC
  #error "ELF_RELOC must be defined"
  #endif
  
  ELF_RELOC(R_MYRISCVX_NONE,               0)
  ELF_RELOC(R_MYRISCVX_32,                 1)
  ELF_RELOC(R_MYRISCVX_64,                 2)
  ELF_RELOC(R_MYRISCVX_RELATIVE,           3)
  ELF_RELOC(R_MYRISCVX_COPY,               4)
  ELF_RELOC(R_MYRISCVX_JUMP_SLOT,          5)
  ELF_RELOC(R_MYRISCVX_TLS_DTPMOD32,       6)
  ELF_RELOC(R_MYRISCVX_TLS_DTPMOD64,       7)
  ELF_RELOC(R_MYRISCVX_TLS_DTPREL32,       8)
  ELF_RELOC(R_MYRISCVX_TLS_DTPREL64,       9)
  ELF_RELOC(R_MYRISCVX_TLS_TPREL32,       10)
  ELF_RELOC(R_MYRISCVX_TLS_TPREL64,       11)
  ELF_RELOC(R_MYRISCVX_BRANCH,            16)
  ELF_RELOC(R_MYRISCVX_JAL,               17)
  ELF_RELOC(R_MYRISCVX_CALL,              18)
  ELF_RELOC(R_MYRISCVX_CALL_PLT,          19)
  ELF_RELOC(R_MYRISCVX_GOT_HI20,          20)
  ELF_RELOC(R_MYRISCVX_TLS_GOT_HI20,      21)
  ELF_RELOC(R_MYRISCVX_TLS_GD_HI20,       22)
  ELF_RELOC(R_MYRISCVX_PCREL_HI20,        23)
  ELF_RELOC(R_MYRISCVX_PCREL_LO12_I,      24)
  ELF_RELOC(R_MYRISCVX_PCREL_LO12_S,      25)
  ELF_RELOC(R_MYRISCVX_HI20,              26)
  ELF_RELOC(R_MYRISCVX_LO12_I,            27)
  ELF_RELOC(R_MYRISCVX_LO12_S,            28)
  ELF_RELOC(R_MYRISCVX_TPREL_HI20,        29)
  ELF_RELOC(R_MYRISCVX_TPREL_LO12_I,      30)
  ELF_RELOC(R_MYRISCVX_TPREL_LO12_S,      31)
  ELF_RELOC(R_MYRISCVX_TPREL_ADD,         32)
  ELF_RELOC(R_MYRISCVX_ADD8,              33)
  ELF_RELOC(R_MYRISCVX_ADD16,             34)
  ELF_RELOC(R_MYRISCVX_ADD32,             35)
  ELF_RELOC(R_MYRISCVX_ADD64,             36)
  ELF_RELOC(R_MYRISCVX_SUB8,              37)
  ELF_RELOC(R_MYRISCVX_SUB16,             38)
  ELF_RELOC(R_MYRISCVX_SUB32,             39)
  ELF_RELOC(R_MYRISCVX_SUB64,             40)
  ELF_RELOC(R_MYRISCVX_GNU_VTINHERIT,     41)
  ELF_RELOC(R_MYRISCVX_GNU_VTENTRY,       42)
  ELF_RELOC(R_MYRISCVX_ALIGN,             43)
  ELF_RELOC(R_MYRISCVX_RVC_BRANCH,        44)
  ELF_RELOC(R_MYRISCVX_RVC_JUMP,          45)
  ELF_RELOC(R_MYRISCVX_RVC_LUI,           46)
  ELF_RELOC(R_MYRISCVX_GPREL_I,           47)
  ELF_RELOC(R_MYRISCVX_GPREL_S,           48)
  ELF_RELOC(R_MYRISCVX_TPREL_I,           49)
  ELF_RELOC(R_MYRISCVX_TPREL_S,           50)
  ELF_RELOC(R_MYRISCVX_RELAX,             51)
  ELF_RELOC(R_MYRISCVX_SUB6,              52)
  ELF_RELOC(R_MYRISCVX_SET6,              53)
  ELF_RELOC(R_MYRISCVX_SET8,              54)
  ELF_RELOC(R_MYRISCVX_SET16,             55)
  ELF_RELOC(R_MYRISCVX_SET32,             56)
  ELF_RELOC(R_MYRISCVX_32_PCREL,          57)

このMYRISCVXのリロケーションテーブルは、e_flagと同様、include/llvm/BinaryFormat/ELF.h上でインクルードされる。

  • include/llvm/BinaryFormat/ELF.h
    // MYRISCVX Specific e_flags
    enum : unsigned {
      EF_MYRISCVX_RVC = 0x0001,
      EF_MYRISCVX_FLOAT_ABI = 0x0006,
    ...
        
    // ELF Relocation types for MYRISCXV
    enum {
    #include "ELFRelocs/MYRISCVX.def"
    };

このリロケーション情報を使ってどのようにしてアドレス計算を行うのかは、おいおい解説していく。

RISC-VボードHiFive UnleashedでWebサーバを立ち上げてみる

HiFive Unleashd上でDebian GNU/Linuxが動いてしまえば、あとは様々なことが可能だ。 ウェブサービスを立ち上げたり、各種アプリケーションを立ち上げることも可能となる。

例として、ngnixをインストールして、簡単なウェブサーバを立ち上げてみる。 といってもDebianが動いていますのでインストールはaptコマンドを入力するだけだ。

sudo apt install -y nginx ufw net-tools
nginx -v
nginx version: nginx/1.14.2

nginxがインストールされた。

systemctl enable ufw
service nginx start

Debianに割り当てられているIPアドレスWebブラウザからアクセスすると、nginxが動作しているのが確認できた。

f:id:msyksphinz:20190514001449p:plain
HiFive Unleashed上でnginxが動いた。

ただし、様々なウェブサービスをインストールしようとしてみたのだが、まだRISC-Vに対応していないのかMySQLがインストールできなかった。 この辺りは、各種パッケージがRISC-Vに対応するのを待つか、SQLiteなどのより簡易なSQLサーバを使ってみるしかいようだ。

AWS F1インスタンス上のFireSimを実行する(10. FireSimのワークロードを作成してプログラムを動かす)

f:id:msyksphinz:20190405012417p:plain

Rocketコアをカスタマイズし、FireSimのプラットフォームに乗せ、さらにFPGA合成したAGFIまで作成した。いよいよf1インスタンス上で動作させ、ベンチマークプログラムを動かす。

このとき、前章で紹介したようにFireSim上でBuildrootを立ち上げLinuxにログインした上でベンチマークプログラムを動かしてもよいのだが、これにはいろいろ問題がある。

まず、Buildrootのファイルシステムにどうやってコンパイル済みのベンチマークプログラムを配置するのかだが、一般的には様々な方法がある。例えばwgetコマンドで外部サーバからコンパイル済みのバイナリをダウンロードするして実行することもできるかもしれないが、残念ながらFireSim上で動いているBuildrootはネットワークの設定がされておらず、外部のインターネットに接続することができなかった。

今回はLinuxを立ち上げてベンチマークを動かす方法は諦める。その代わり、FireSimが提供している「ワークロード」というシステムを使う。

「ワークロード」は、FireSimがターゲットのコンフィグレーションをFPGAに流し込み、そして指定のプログラムをRISC-Vコアに流し込んで結果を回収するための仕組み。 FireSimのベンチマークリグレッションテストも、このワークロードを使って実行されている。

ここでは、前章で作成したmatrixmulプログラムをAGFI firesim-singlecore-no-nic-matrixmulで動かして結果を回収するために、matrixmulワークロードを作成してみる。

新しいワークロードを定義するためにはdeploy/workloads/matrixmul.iniを作成する。中身はリスト[refs:firesim_workload_matrixmul.ini]のようにする。

リスト[refs:firesim_workload_matrixmul.ini] : deploy/workloads/matrixmul.ini

[runfarm]
runfarmtag=matrixmul-unifarm

f1_16xlarges=0
m4_16xlarges=0
f1_4xlarges=0
f1_2xlarges=1

runinstancemarket=ondemand
spotinterruptionbehavior=terminate
spotmaxprice=ondemand

[targetconfig]
topology=no_net_config
no_net_num_nodes=1
linklatency=6405
switchinglatency=10
netbandwidth=200
profileinterval=-1

# This references a section from config_hwconfigs.ini
# In homogeneous configurations, use this to set the hardware config deployed
# for all simulators
defaulthwconfig=firesim-singlecore-no-nic-matrixmul

[tracing]
enable=no
startcycle=0
endcycle=-1

[workload]
workloadname=matrixmul.json
terminateoncompletion=no

defaulthwconfigに、先ほど作成したfiresim-singlecore-no-nic-matrixmulを指定する。 使用するf1インスタンスはf1_2xlargeなので、f1_2xlarges=1を指定する。さらに、ワークロードとして前章で私用したベンチマークプログラムを使いたいので、ベンチマークの詳細を指定するためのmatrixmul.jsonを作成する。このファイルはworkloadnameで設定する。

次に、matixmul.jsonを作成しましょう。リスト[refs:firesim_workload_matrixmul.json]に、matrixmul.jsonを示す。

リスト[refs:firesim_workload_matrixmul.json] : deploy/workloads/matrixmul.ini

{
  "benchmark_name" : "matrixmul",
  "common_rootfs" : "dummy.ext2",
  "deliver_dir" : "matrixmul",
  "common_args" : ["--copies 1"],
  "common_files" : ["intrate.sh"],
  "common_outputs" : ["/output"],
  "common_simulation_outputs" : ["uartlog"],
  "workloads" : [
    {
      "name": "matrixmul.riscv",
      "bootbinary" : "matrixmul.riscv",
      "simulation_outputs": [],
      "outputs": []
    }
  ]
}

ワークロードとしてmatrixmul.riscvを指定した。 このワークロードを実行すると、1つのベンチマークプログラムmarixmul.riscvがFireSimに流し込まれ、その結果が回収される。

次に、matrixmulのワークロードに必要なディレクトリを作成する。 deliver_dirに指定したように、matrixmulディレクトリを作成し、そこに流したいパタンとダミーのファイルシステムを置いておく必要がある。

# firesim/deploy/workloads で作業
mkdir matrixmul
cd matrixmul
cp ../check-rtc/dummy.ext2 .
# target-design/firechip-msyksphinz/tests/ 以下でmakeを実行し、あらかじめmatrixmul.riscvを作成しておいてくこと。
ln -s ../../../target-design/firechip-msyksphinz/tests/matrixmul.riscv

これで準備は完了。FireSimを立ち上げる。

# firesim/deployディレクトリ上で作業をすること。別のディレクトリで実行するとiniとjsonファイルのパスが合わずエラーとなった。
cd firesim/deploy
workloads/run-workload.sh workloads/matrixmul.ini --withlaunch

f1インスタンスが立ち上がり、FireSimのデザインが流し込まる。 シミュレーションが開始され、しばらく待っているとコンソールが戻ってくる。 ログが生成されるので確認してみる。

less /home/centos/firesim-msyksphinz/deploy/logs/[実行した日付]-launchrunfarm-B8G7C0DQNCNB0OF0.log
...
2019-05-11 02:45:59,050 [run_workload] [DEBUG]  jobs complete dict {u'matrixmul.riscv': True}
2019-05-11 02:45:59,050 [run_workload] [DEBUG]  global status: [True]
2019-05-11 02:45:59,050 [run_workload] [INFO ]  FireSim Simulation Exited Successfully. See results in:
/home/centos/firesim-msyksphinz/deploy/results-workload/2019-05-11--02-44-10-matrixmul/

さらに、uartlogを確認してみる。

less /home/centos/firesim-msyksphinz/deploy/results-workload/2019-05-11--02-44-10-matrixmul/matrixmul.riscv/uartlog
Script started on Sat 11 May 2019 02:44:13 AM UTC
AFI PCI  Vendor ID: 0x1d0f, Device ID 0xf000
Using xdma write queue: /dev/xdma0_h2c_0
Using xdma read queue: /dev/xdma0_c2h_0
UART0 is here (stdin/stdout).
command line for program 0. argc=19:
+permissive +mm_relaxFunctionalModel=0 +mm_writeMaxReqs=16 +mm_readMaxReqs=16 +mm_writeLatency=30 +mm_readLatency=30 +macaddr0=00:12:6D:00:00:02 +slotid=0 +niclog0=niclog +trace-start0=0 +trace-end0=-1 +linklatency0=6405 +netbw0=200 +profile-interval=-1 +zero-out-dram +shmemportname0=default +permissive-off matrixmul.riscv +blkdev0=dummy.ext2
random min: 0x0, random max: 0xffffffffffffffff
Zeroing out FPGA DRAM. This will take a few seconds...
Commencing simulation.
                    SW          HW
----------------------------------
 2 x 2 x 2 :        327        219
 4 x 4 x 4 :       1024        488
 8 x 8 x 8 :       7041       2527
16 x16 x16 :      54559      16118
18 x24 x28 :     154583      44362

*** PASSED *** after 3913301296 cycles
time elapsed: 25.0 s, simulation speed = 156.26 MHz
FPGA-Cycles-to-Model-Cycles Ratio (FMR): 1.00
Runs 3913301296 cycles
[PASS] FireSimNoNIC Test
SEED: 1557542653
f:id:msyksphinz:20190511142749p:plain
FireSimの実行結果確認

パタンが通り、シミュレーションが終了したのを確認できた(ちなみにデバッグモードにすると、SW実行した場合とHW実行した場合の結果が一致しているのも確認できる。

AWS F1インスタンス上のFireSimを実行する(9. カスタマイズしたFireSimのFPGAイメージを作成する)

f:id:msyksphinz:20190405012417p:plain

カスタムアクセラレータ付きのFireSimデザインが完成したので今度はVivadoで合成し、f1インスタンスで動作させるためのAGFIを作成する。AGFIとはAmazon Global FPGA Image IDの略称で、FPGAのイメージのようなものだ。このイメージIDを指定することで、何度もVivadoの合成を動かす必要なく、f1インスタンスFPGAに書き込むことができる。

cd firesim/deploy

FireSimのコンフィグレーションをビルドするためには、config_build_recipes.config_build.iniを編集する。 このファイルを編集することで、どのコンフィグレーションを合成しFPGAイメージを作成するのかを指定することができる。ここではビルドターゲットにfiresim-singlecore-no-nic-matrixmulを設定する。s3bucketnameは"firesim-[ユーザ名]"に変更しておく。

  • firesim/deploy/config_build_recipes.ini
...
[firesim-singlecore-no-nic-matrixmul]
DESIGN=FireSimNoNIC
TARGET_CONFIG=FireSimRocketChipMatrixMulConfig
PLATFORM_CONFIG=FireSimConfig160MHz
instancetype=c4.4xlarge
deploytriplet=None
...
  • firesim/deploy/config_build.ini
[afibuild]

s3bucketname=firesim-msyksphinz
buildinstancemarket=ondemand
spotinterruptionbehavior=terminate
spotmaxprice=ondemand
postbuildhook=

[builds]
# this section references builds defined in config_build_recipes.ini
# if you add a build here, it will be built when you run buildafi
firesim-singlecore-no-nic-matrixmul
# firesim-singlecore-no-nic-lbp
...

[agfistoshare]
firesim-singlecore-no-nic-matrixmul
# firesim-singlecore-no-nic-lbp
...

[sharewithaccounts]
somebodysname=123456789012

設定が完了すると、ビルドを行います。コマンドラインで以下のように入力する。

firesim buildafi

ビルドが始まる。ビルドには2~3時間かかる。

ビルドが完了すると、以下のようなメッセージがログの中に現れる(これらのログはfiresimコマンドを実行する度にすべてファイルに保存されるので、安心してよい)。

2019-05-09 05:43:23,541 [aws_build   ] [INFO ]  Your AGFI has been created!
Add
[firesim-singlecore-no-nic-matrixmul]
agfi=agfi-09f5ba9b181d00aeb
deploytripletoverride=None
customruntimeconfig=None


to your config_hwdb.ini to use this hardware configuration.

AGFIが作成された。 指示にある通り、config_hwdb.iniにこのAGFIを追加する。 これにより、FireSimがカスタマイズしたRocketコアを使用できるようになった。

  • firesim/deploy/config_hwdb.ini
# Hardware config database for FireSim Simulation Manager
# See docs/Advanced-Usage/Manager/Manager-Configuration-Files.rst for documentation of all of these params.

# Hardware configs represent a combination of an agfi, a deploytriplet override
# (if needed), and a custom runtime config (if needed)

# The AGFIs provided below are public and available to all users.
# Only AGFIs for the latest release of FireSim are guaranteed to be available.
# If you are using an older version of FireSim, you will need to generate your
# own images.

...

[firesim-singlecore-no-nic-matrixmul]
agfi=agfi-09f5ba9b181d00aeb
deploytripletoverride=None
customruntimeconfig=None

AWS F1インスタンス上のFireSimでBOOMコアをシミュレーションする試行(8. カスタマイズしたアクセラレータをFireSimのプラットフォーム上に構築する)

f:id:msyksphinz:20190405012417p:plain

FireSimの環境で、どうにかRocketシングルコアを動かすことができた。ここまでできれば、今度は様々なハードウェアをf1インスタンスで動かしてみたい。RocketはChiselというハードウェア記述言語で設計されているが、ChiselでオリジナルのモジュールをRocketに追加して、オリジナルのRocketコアを作れば、f1インスタンス上で動作させることができると思う。

前回までのFireChipのシミュレーションは、いわゆるコアの単体でソフトウェアとハードウェアの動作を確認した。今度は、AWS f1インスタンス上で動作させるために、FireChipのデザインをFireSimに乗せる必要がある。

イメージとしては、FireSimがf1インスタンスを含むFPGAイメージ+SoCと考えればよい。SoCの中にFireChipが入っており、1部品としてFireSimに埋め込む。

このためには、FireSimのデザインに、FireChipのデザインを認識させなければならない。 FireSimリポジトリにはtarget-design/firechipとしてサブリポジトリが配置されている。このfirechipリポジトリはUCBのfirechipプロジェクト公式(https://github.com/firesim/firechip)のものだ。しかしこのリポジトリをforkしてオリジナルのfirechipリポジトリを作っていた。まずはこのリポジトリtarget-designディレクトリに配置しなければならない。

cd target-design
git submodule add https://github.com/msyksphinz/firechip -b memory-mapped-io firechip-msyksphinz

以下のようなディレクトリ構成になる。

$ tree -L 1 target-design
target-design
├── firechip
├── firechip-msyksphinz
└── switch

では次にFireSimリポジトリ内で新しいコンフィグレーションを作成する。コンフィグレーションというのは、ターゲットのハードウェアの構成などを示したChiselで書かれたクラスだ。あるコンフィグレーションを指定するとコアが1つ、あるいは別のコンフィグレーションを指定するとキャッシュサイズを変えることができる、など、1つのリポジトリの中に複数のコンフィグレーションを定義することができ、Verilogの生成や論理合成時にどのターゲットを使用するか指定する。

今回はFireSimのリポジトリに以下のソースファイルを追加した。

  • sim/src/main/scala/firesim/TargetConfigs.scala
diff --git a/sim/src/main/scala/firesim/TargetConfigs.scala b/sim/src/main/scala/firesim/TargetConfigs.scala
index d18574f..7bccd4f 100644
--- a/sim/src/main/scala/firesim/TargetConfigs.scala
+++ b/sim/src/main/scala/firesim/TargetConfigs.scala
@@ -9,6 +9,7 @@ import boom.system.BoomTilesKey
 import testchipip.{WithBlockDevice, BlockDeviceKey, BlockDeviceConfig}
 import sifive.blocks.devices.uart.{PeripheryUARTKey, UARTParams}
 import icenet._
+import example._

 class WithBootROM extends Config((site, here, up) => {
   case BootROMParams => BootROMParams(
@@ -90,6 +91,20 @@ class FireSimRocketChipConfig extends Config(
   new WithPerfCounters ++
   new freechips.rocketchip.system.DefaultConfig)

+
+class FireSimMatrixMulConfig extends Config(
+  new WithBootROM ++
+  new WithPeripheryBusFrequency(BigInt(3200000000L)) ++
+  new WithExtMemSize(0x400000000L) ++ // 16GB
+  new WithoutTLMonitors ++
+  new WithUARTKey ++
+  new WithNICKey ++
+  new WithBlockDevice ++
+  new WithRocketL2TLBs(1024) ++
+  new WithPerfCounters ++
+  new example.RoccMatrixMulConfig)
+
+
 class WithNDuplicatedRocketCores(n: Int) extends Config((site, here, up) => {
   case RocketTilesKey => List.tabulate(n)(i => up(RocketTilesKey).head.copy(hartId = i))
 })
@@ -100,6 +115,9 @@ class FireSimRocketChipTracedConfig extends Config(
 // single core config
 class FireSimRocketChipSingleCoreConfig extends Config(new FireSimRocketChipConfig)

+// single core config
+class FireSimRocketChipMatrixMulConfig extends Config(new FireSimMatrixMulConfig)
+

一番大元になるコンフィグレーション名はFireSimRocketChipMatrixMulConfigだ。このクラスはFireSimMatrixMulConfigを呼び出しており、これはexample.RoccMatrixMulConfigと各種デバイスやバスを定義している。RoccMatrixMulConfigはFireChipで定義したコンフィグレーションなので、このFireSimRocketChipMatrixMulConfigVerilogを生成すればアクセラレータ付きのFireSimデザインを生成することができる。

まだもう少しやることがある。先ほどサブモジュールとしてチェックアウトしたfiresim-msyksphinzを、Chiselが参照するようにディレクトリを指定しなければならない。Chiselのソースコードの管理や、プロジェクトの管理はsim/build.sbtで管理する。

  • sim.build.sbt
diff --git a/sim/build.sbt b/sim/build.sbt
index daae7dc..fda246f 100644
--- a/sim/build.sbt
+++ b/sim/build.sbt
@@ -21,8 +21,8 @@ def isolateAllTests(tests: Seq[TestDefinition]) = tests map { test =>

 testGrouping in Test := isolateAllTests( (definedTests in Test).value )

-val rocketChipDir = file("target-rtl/firechip/rocket-chip")
-val fireChipDir  = file("target-rtl/firechip")
+val rocketChipDir = file("target-rtl/firechip-msyksphinz/rocket-chip")
+val fireChipDir  = file("target-rtl/firechip-msyksphinz")

 // Subproject definitions begin
 // NB: FIRRTL dependency is unmanaged (and dropped in sim/lib)
@@ -67,6 +67,10 @@ lazy val icenet     = (project in fireChipDir / "icenet")
   .settings(commonSettings)
   .dependsOn(rocketchip, testchipip)

+lazy val example    = (project in fireChipDir / "src/main/scala/example")
+  .settings(commonSettings)
+  .dependsOn(rocketchip, testchipip, icenet)
+
 // MIDAS-specific dependencies
 lazy val mdf        = RootProject(file("barstools/mdf/scalalib"))
 lazy val barstools  = (project in file("barstools/macros"))
@@ -79,4 +83,4 @@ lazy val midas      = (project in file("midas"))
 // Finally the root project
 lazy val firesim    = (project in file("."))
   .settings(commonSettings)
-  .dependsOn(rocketchip, midas, boom, icenet, sifiveip)
+  .dependsOn(rocketchip, midas, boom, icenet, sifiveip, example)

まず、rocketChipDirfireChipDirtarge-rtl/firechip/からtarget-rtl/firechip-msyksphinzに書き換えている。また、FireChipディレクトリの中でsrc/main/scala/example内のコンフィグレーションを参照してほしいので、example変数を定義し、さらにfiresim変数の依存関係にexampleを追加している。

これで、simディレクトリに移動してmakeを実行してみる。

make TARGET_CONFIG=FireSimRocketChipMatrixMulConfig

しばらく待つと、RTLが生成されているのが分かる。生成されたRTLを見てみると、開発したMatrixMulモジュールがしっかり組み込まれていた。

find . -name "*.v" | grep Matrix
...
./generated-src/f1/FireSim-FireSimRocketChipMatrixMulConfig-FireSimConfig/FPGATop.v
  • generated-src/f1/FireSim-FireSimRocketChipMatrixMulConfig-FireSimConfig/FPGATop.v
module MatrixMul(
  input         clock,
  input         reset,
  output        io_cmd_ready,
  input         io_cmd_valid,
  input  [6:0]  io_cmd_bits_inst_funct,
  input  [4:0]  io_cmd_bits_inst_rd,
  input  [63:0] io_cmd_bits_rs1,
  input  [63:0] io_cmd_bits_rs2,
...