FPGA開発日記

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

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,
...

RISC-Vの開発環境をcrosstool-ngとriscv-toolsでセットアップする方法

RISC-Vでのハードウェア・ソフトウェア開発をするにあたり、まず必要になるのがツール群だ。

RISC-Vのソフトウェアツール群は、riscv-toolsというリポジトリにまとめられており、まずはこのリポジトリをダウンロードしてコンパイラなどを構築する必要がある。 しかし、このリポジトリはサブリポジトリを含めコンパイラソースコードをすべてダウンロードするなど、すべてダウンロードすると数GBになってしまうためディスク容量を食ったり、ネットワーク要領を食ったりして非常に不便なものだった。

github.com

ところがここ最近、このriscv-toolsがアップデートされ、コンパイラなどの重たいツール群が除去された。 リポジトリが軽くなったのは良いことなのだが、ではコンパイラはどこから持ってくるのか?READMEを参照すると、crosstool-ngを使用する、と書いてある。 なるほど、crosstool-ngはもうRISC-Vに対応しているのか。ならば、crosstool-ngとriscv-toolsでRISC-Vの開発環境を構築できるはずだ。やってみよう。

crosstool-ngのインストール

crosstool-ngは以下のページからダウンロードする。

crosstool-ng.github.io

cd /tmp
curl -L http://crosstool-ng.org/download/crosstool-ng/crosstool-ng-1.24.0.tar.xz | tar Jx
cd crosstool-ng-1.24.0
./configure
make -j$(nproc)
sudo make install

これでct-ngコマンドが使えるようになった。

ct-ngコマンドで、サンプルターゲットを確認しよう。

ct-ng update-samples
ct-ng list-samples | grep riscv
$ ct-ng list-samples | grep riscv
[G..X]   riscv32-hifive1-elf
[G..X]   riscv32-unknown-elf
[G..X]   riscv64-unknown-elf
[G..X]   riscv64-unknown-linux-gnu

そこで、riscv64-unknown-elfをインストールしてみる。

$ ct-ng riscv64-unknown-elf
  CONF  riscv64-unknown-elf
#
# configuration written to .config
#

***********************************************************

Initially reported by: Paul Walmsley <paul.walmsley@sifive.com>
URL: https://www.sifive.com/

***********************************************************

WARNING! This sample may enable experimental features.
         Please be sure to review the configuration prior
         to building and using your toolchain!
Now, you have been warned!

***********************************************************

Now configured for "riscv64-unknown-elf"

ct-ng menuconfigで、PREFIXを設定してインストールする先を指定する。

f:id:msyksphinz:20190510015507p:plain
ct-ng menuconfigでのPREFIXの設定

ビルドする。

ct-ng build
[INFO ]  Performing some trivial sanity checks
[WARN ]  Number of open files 1024 may not be sufficient to build the toolchain; increasing to 2048
[INFO ]  Build started 20190509.232202
[INFO ]  Building environment variables
[WARN ]  Directory '/home/msyksphinz/src' does not exist.
[WARN ]  Will not save downloaded tarballs to local storage.
[EXTRA]  Preparing working directories
[EXTRA]  Installing user-supplied crosstool-NG configuration
[EXTRA]  =================================================================
[EXTRA]  Dumping internal crosstool-NG configuration
[EXTRA]    Building a toolchain for:
...
[INFO ]    Stripping all toolchain executables
[EXTRA]    Creating toolchain aliases
[EXTRA]    Removing installed documentation
[EXTRA]    Collect license information from: /tmp/riscv64/.build/riscv64-unknown-elf/src
[EXTRA]    Put the license information to: /home/msyksphinz/riscv64-ctng/share/licenses
[INFO ]  Finalizing the toolchain's directory: done in 40.42s (at 87:59)
[INFO ]  Build completed at 20190510.004957
[INFO ]  (elapsed: 87:55.45)
[INFO ]  Finishing installation (may take a few seconds)...
[87:59] / %

ここまでで、インストール先を確認する。PREFIX=/home/msyksphinz/riscv64-ctngにインストールした。

$ ls -l riscv64-ctng
total 764
dr-xr-xr-x 1 msyksphinz msyksphinz   4096  5月 10 00:49 bin
-r--r--r-- 1 msyksphinz msyksphinz 782011  5月 10 00:49 build.log.bz2
dr-xr-xr-x 1 msyksphinz msyksphinz   4096  5月 10 00:38 include
dr-xr-xr-x 1 msyksphinz msyksphinz   4096  5月 10 00:38 lib
dr-xr-xr-x 1 msyksphinz msyksphinz   4096  5月 10 00:38 libexec
dr-xr-xr-x 1 msyksphinz msyksphinz   4096  5月 10 00:26 riscv64-unknown-elf
dr-xr-xr-x 1 msyksphinz msyksphinz   4096  5月 10 00:49 share

このツール群は、PATHLD_LIBRARY_PATHを通しておく。次にriscv-toolsをダウンロードして、ビルドする。

cd riscv-tools
export PATH=${HOME}/riscv64-ctng-tools
MAKEFLAGS="-j$(nproc)" ./build.sh

これでしばらく待っているとビルドが完了する。これで、crosstool-ngを使ったRISC-Vコンパイラの構成とriscv-toolsのツール群の構築が完了した。

AWS F1インスタンス上のFireSimでBOOMコアをシミュレーションする試行(7. Firechipでオリジナルデザインを設計してシミュレーションする)

f:id:msyksphinz:20190405012417p:plain

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

FireSimのコアデザイン"Firechip"

FireSimは、RISC-Vコアを動作させるための環境そのものを指す。FireSimのリポジトリにはChiselで記述されたいくつかのデザインは入っているが、コアそのものではなく、周辺IPだったり、SoCを構成するための部品がメインになる。一方で、シミュレーションするコア自体は別のリポジトリで管理されている。それが"firechip"というリポジトリになる。

FireSimのリポジトリのサブリポジトリとして、"firechip"リポジトリが配置されている。

firesim
├── deploy
├── docs
├── platforms
│   └── f1
├── riscv-tools-install
├── scripts
├── sim
├── sw
├── target-design
│   ├── firechip
│   └── switch

このfirechipリポジトリは、Rocket-Chipのリポジトリと同様、単体でRTLシミュレーションを行うことができる。また、Rocket-Chipと同様、カスタマイズも可能だ。

cd firesim
cd target-design/firechip/verisim   # Verilatorを使う。VCSを使う場合はtarget-design/firechip/vsim
make clean && make run-regression-tests

つまり、FireChipを改造すればAWS f1インスタンス上でFireSimを使い、オリジナルに改造したRISC-Vコアを走らせることができる。どのように進めればよいか見ていく。

FireChipにカスタマイズアクセレレータを実装する

さて、実装方針が決まったので、FireChipに行列積アクセラレータを実装する。あらかじめ、今回はGitHubに公開されているfirechipのリポジトリをforkし、msyksphinz/firechip.gitで作業を行う。FireChipリポジトリをcloneし、セットアップを行う。この作業はAWSインスタンス上で行う必要はなく、ローカルマシンで実行しても問題ない。

git clone https://github.com/msyksphinz/firechip.git
cd firechip
git submodule update --init
cd rocket-chip
git submodule update --init
cd ..

行列アクセラレータを実装するために、以下のファイルを作成・修正する。

src/main/scala/example/Configs.scala
src/main/scala/example/MatrixMul.scala
  • src/main/scala/example/Configs.scala
class RoccMatrixMulConfig extends Config(
  new WithRoccMatrixMul ++ new DefaultExampleConfig)
  • src/main/scala/example/MatrixMul.scala
class MatrixMul(opcodes: OpcodeSet, val n: Int = 4)(implicit p: Parameters) extends LazyRoCC(opcodes) {
  override lazy val module = new MatrixMulModuleImp(this)
}

class MatrixMulModuleImp(outer: MatrixMul, n: Int = 4)(implicit p: Parameters) extends LazyRoCCModuleImp(outer)
    with HasCoreParameters {
...

コンフィグレーションはRoccMatrixMulConfigを指定した。Verilog生成時に、CONFIGオプションでRoccMatrixMulConfigを指定すると、所望のVerilogが生成される。

cd firechip/verisim
make CONFIG=RoccMatrixMulConfig

そして、上記で示したテストプログラムを実行する。プログラムは自前でコンパイルしても良いだが、firechip/testsに配置してMakefileを追加すると自動的にコンパイルしてくれる。

diff --git a/tests/Makefile b/tests/Makefile
index a199743..1102ecf 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -3,7 +3,7 @@ OBJDUMP=riscv64-unknown-elf-objdump
 CFLAGS=-mcmodel=medany -std=gnu99 -O2 -fno-common -fno-builtin-printf -Wall
 LDFLAGS=-static -nostdlib -nostartfiles -lgcc

-PROGRAMS = pwm blkdev accum charcount nic-loopback pingd big-blkdev input-stream
+PROGRAMS = pwm blkdev accum charcount nic-loopback pingd big-blkdev input-stream matrixmul

 default: $(addsuffix .riscv,$(PROGRAMS))

RTLシミュレーションを実行する。SW実行と比べて、ハードウェアアクセラレータを付加した場合にどれくらい高速化されるか検証した。

make CONFIG=FireSimRocketChipMatrixMulConfig
./simulator-example-RoccMatrixMulConfig ../tests/matrixmul.riscv

シミュレーションで正しく実行されることを確認した。

                    SW          HW
----------------------------------
 2 x 2 x 2 :        278        152
 4 x 4 x 4 :       1238        521
 8 x 8 x 8 :       9185       2691
16 x16 x16 :      70288      17621
18 x24 x28 :     209390      47615

Wi-Fi モジュールの付いたRISC-Vボード HiFive1 Rev.Bが届いた

SiFive社が販売しているRISC-Vボードの第3弾、Wi-Fiモジュールの搭載されたRISC-VボードHiFive1 Rev.Bが自宅に到着した。

f:id:msyksphinz:20190507220931p:plain
HiFive1 Rev.Bパッケージ
f:id:msyksphinz:20190507221025p:plain
HiFive1 Rev.B内容物

HiFive1 Rev.Bの仕様は、SoCの部分はHiFive1と一緒だ。

  • 動作周波数320MHz
  • RISC-Vコア Freedom FE310搭載。FE310-G002。サポート仕様はRV32IMAC
    • 32-bit RISC-Vアーキテクチャ、ハードウェア乗除算命令搭載、アトミック命令搭載、Compressed命令搭載
    • 16kB L1命令キャッシュ
    • 16kB SRAMクラッチパッドメモリ
  • 外部割込みピン 19本
  • UART, PWM搭載
  • JTAGデバッグサポート
  • 128-bit SPIフラッシュメモリ搭載
  • WiFiモジュール
    • ESP32-SOLO-1

搭載されているWirelessモジュールはESP32-SOLO-1というものらしい。調べてみるとこれ自体は日本の技適は取れている?ということはこのモジュールは日本国内で使っても大丈夫なのかな?

f:id:msyksphinz:20190507222026p:plain

まあボードを買ってみたものの触る時間がほとんどないのが問題だ。。。試行できるのはいつの日か。。。

AWS F1インスタンス上のFireSimでBOOMコアをシミュレーションする試行(6. Firesimで生成されたDCPをVivadoで表示する)

f:id:msyksphinz:20190405012417p:plain

FireSimのシミュレーションに使用する独自のAFIイメージを作成する。

AFIとは、Amazon FPGA Imageの略称で、AFIを作成しておけばすぐにAWS f1インスタンスにデザインをデプロイできる。

Amazon S3のアカウントをセットアップしていることを前提にする。 Amazon S3のコンソール上で[Create bucket]をクリックして新しいバケットをセットアップする。

[Bucket name]は"firesim-(アカウント名)"とする。 そうしないとfiresimがS3バケットを認識できない。 私の場合は、"firesim-msyksphinz"とした。次に[Next]をクリックする。

f:id:msyksphinz:20190506224239p:plain
FireSimで作成したFPGAイメージを格納するS3バケットを作成する。バケット名は”firesim-(アカウント名)”とすること。

そのまま[Next]をクリックし続け、[Create Bucket]をクリックして新しいバケットを生成する。

S3上でバケットの構築が完了すると、次にマネージャインスタンス上で作業する。firesim/deploy/config_build.iniを編集する。

[afibuild]

s3bucketname=firesim-msyksphinz

[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-lbp
# #firesim-singlecore-nic-lbp
# #firesim-quadcore-no-nic-lbp
# #firesim-quadcore-nic-lbp
# firesim-quadcore-no-nic-ddr3-llc4mb
# firesim-quadcore-nic-ddr3-llc4mb
# #fireboom-singlecore-no-nic-lbp
# fireboom-singlecore-no-nic-ddr3-llc4mb
# #fireboom-singlecore-nic-lbp
# fireboom-singlecore-nic-ddr3-llc4mb
# #firesim-supernode-singlecore-nic-ddr3-llc4mb
# #firesim-supernode-quadcore-nic-ddr3-llc4mb
# firesim-supernode-singlecore-nic-lbp


[agfistoshare]
firesim-singlecore-no-nic-lbp
# #firesim-singlecore-nic-lbp
# #firesim-quadcore-no-nic-lbp
# #firesim-quadcore-nic-lbp
# firesim-quadcore-no-nic-ddr3-llc4mb
# firesim-quadcore-nic-ddr3-llc4mb
# #fireboom-singlecore-no-nic-lbp
# fireboom-singlecore-no-nic-ddr3-llc4mb
# #fireboom-singlecore-nic-lbp
# fireboom-singlecore-nic-ddr3-llc4mb
# #firesim-supernode-singlecore-nic-ddr3-llc4mb
# #firesim-supernode-quadcore-nic-ddr3-llc4mb
# firesim-supernode-singlecore-nic-lbp

ターゲットとなるfiresim-singlecore-no-nic-lbpのみを残し、あとはコメントアウトする。 これをしっかり行わないと、ターゲットとなるイメージの数だけインスタンスが生成されてしまい、料金がかさむことになってしまう。

上記のように編集した後、firesim buildafiを実行する。処理が完了するのに、3~4時間程度かかる。 途中で以下のようなメッセージが連発されるが、辛抱強く待つこと(筆者は我慢できずGitHub Issueで質問したら、「我慢しろ」という回答が返ってきた)。

Current state: pending
Current state: pending
Current state: pending
Current state: pending
Current state: pending
...

辛抱強く待つと、

RTLの生成と、Vivadoでの論理合成とイメージの作成が行われ、その結果がS3に書き込まれる。 このイメージには、論理合成のログとVivadoのdcp(Design Check Point: プロジェクトファイルのようなもの)が格納されている。Vivadoでdcpファイルを開くと、どのようなデザインがどのようにFPGAマッピングされたのかチェックできる。

S3バケットを見てみる。"firesim-msyksphinz"バケットの中を覗いてみると、tarで圧縮されたファイルが格納されているのが分かると思う。

f:id:msyksphinz:20190506224319p:plain
FireSimで作成したFPGAイメージがS3バケットに格納されているか確認する。”firesim-msyksphinz”の中に入っているはず。
f:id:msyksphinz:20190506224341p:plain
FireSimで作成したFPGAイメージが、tarで圧縮されて格納されている。

FireSimのFPGAイメージはS3バケットに格納されているので、基本的にどのインスタンスを使ってVivadoを立ち上げても構わない(つまり、日本から遠いNorth Virginiaのインスタンスを使わなくても、TokyoのインスタンスFPGA Developer AMIを使ってインスタンスを立ち上げても問題ない)。しかし手っ取り早いのはFireSimのマネージャインスタンスを使う事だ。 AWSのコンフィグレーションも終わっているので、S3との連携も完了している。 なお、f1インスタンスで使用されているのは特殊なAWS用に開発された特殊なFPGAのようなので、FPGA Developer AMIに同梱されているVivado以外ではFPGAバイスを認識できなかった。 手元のローカルマシンでdcpを開こうと何度も挑戦したのだが、うまくいかなかった。 やはり、AMI上のVivadoを使うのが最も安心のようだ。

念のため、VivadoをオープンするためにはGUIを使用するので、f1インスタンスsshで接続している場合はX11フォワーディングの設定をしておく。 筆者は以下のパッケージを追加でインストールする必要があった。

sudo yum install xorg-x11-xauth.x86_64 xorg-x11-server-utils.x86_64 dbus-x11.x86_64

さて、以下のようにしてS3上に格納されているVivadoのDCPをマネージャインスタンスLinuxにダウンロードする。

aws s3 cp s3://firesim-msyksphinz/dcp/19_04_20-032417.Developer_CL.tar-192.168.4.9-VF6PT4V3DA.tar  .

tarを解凍し、dcpをオープンする。

tar xf 19_04_20-032417.Developer_CL.tar-192.168.4.9-VF6PT4V3DA.tar
cd to_aws
vivado 19_04_20-032417.SH_CL_routed.dcp

Vivadoがたちがあり、dcpをロードしている。 しばらくすると、以下のような画面になると思う。 FireSimが配置配線され、FPGA上にインスタンスされている様子が何となくだが分かると思う。

f:id:msyksphinz:20190506224550p:plain
FireSimで作成したFPGAイメージをVivadoでオープンする。FPGA上にインプリメントされている様子が、何となくだが分かる。

より詳細にブロックダイアグラムを見ていくと、どのようなモジュールがインスタンスされているのかわかると思う。FireSimはRISC-Vコアのほかに、PCI ExpressのモジュールやDDRのコントローラなどもインスタンスされていることが分かる。

f:id:msyksphinz:20190506224442p:plain
FireSimで作成したFPGAイメージを階層構造で確認してみる。RISC-Vコア以外に、DDRコントローラやPCI Expressなどがインスタンスされていることが分かる。
f:id:msyksphinz:20190506224522p:plain
FireSimで作成したFPGAイメージを階層構造で確認してみる。RISC-Vコア以外に、DDRコントローラやPCI Expressなどがインスタンスされていることが分かる。

RISC-VのVector Extensionの仕様概観

f:id:msyksphinz:20190505224354p:plain

少しRISC-VのVector Extensionについて調査した。

Vector Extensionは、いわゆるベクトル命令だ。1命令で複数のデータを扱う方法といえば、SIMD(Single Instruction Multiple Data)もあるが、RISC-Vではまずはベクトル命令が定義された。現代のアーキテクチャでは、ベクトル拡張よりもSIMD拡張が一般的だと思うが、RISC-Vではまずはベクトル拡張が定義されている。これはV拡張といわれ、MISAレジスタのビットではVビットで規定されている。ベクトル命令のエンコーディングはまだ規定されていない。いわゆるSIMD命令は、ベクトルレジスタのサイズは命令の中に組み込まれているが、ベクトル命令では、実装形態によってベクトルレジスタのサイズが決まる。

RISC-Vのベクトル拡張仕様は、日進月歩で改定が進んでいる。RISC-V User-Level ISA V2.2をもとに使用の概要を説明するが、この仕様が現在の公式だが、常にアップデートが行われており、現在はGitHub上で議論が行われている。ベクトル拡張についてより深く学びたいという型は、RISC-V Vector Extensionの仕様書を作成しているGitHubのリポジトリを参照すること。

RISC-Vのベクトル拡張では少し面白い仕様が取り込まれている。「動的レジスタ・タイピング」(dynamic type field)という機能によって、ベクトルレジスタが取り扱える型は動的に決定される仕様になっている。これは、例えばベクトルレジスタ0は整数型、ベクトルレジスタ1は単精度浮動小数点型、というように、ベクトルレジスタ毎にデータの型を指定できるようになる。この方式のメリットは、ベクトル命令と、ベクトル命令の取り扱えるデータ型を分離する、と言う事だ。たとえば、通常のSIMD命令であれば、

というように、命令に対してデータ型と演算幅が決まる。これに対して、RISC-Vのベクトル拡張では、命令にデータ型が付属しているのではなく、レジスタに対してデータ型が付随している。

RISC-Vのベクトル拡張を有効にすると、以下のレジスタが有効になる。

これを実現するために、RISC-Vのベクトル拡張では、以下に示したCSRが追加された。

CSR ベースISA 説明
vl 0x020 RV32, RV64, RV128 ベクトル長を示す。
vxrm 0x020 RV32, RV64, RV128 浮動小数点演算命令における丸めモード
vxsat 0x020 RV32, RV64, RV128 固定小数点演算命令におけるSaturation状態を示す。
vcsr 0x020 RV32, RV64, RV128
vcnpred 0x020 RV32, RV64, RV128 有効なプレディケートレジスタの数を示す。0から8までの値が格納される。
vcmaxw 0x020 RV32, RV64, RV128 ベクトルレジスタがサポートできる最大ビットサイズを示す。4×32=128ビットが定義されている。
vcmaxw1 0x020 RV32 RV32において、vcmaxwの[63:32]を参照するためのレジスタ
vcmaxw2 0x020 RV32, RV64 RV32において、vcmaxwの[95:64]を参照するためのレジスタ。RV64において、vcmaxwの[127:64]を参照するためのレジスタ
vcmaxw3 0x020 RV32 RV32において、vcmaxwの[127:96]を参照するためのレジスタ
vctype 0x020 RV32, RV64, RV128 ベクトルレジスタが格納しているデータ型を示す。4×32=128ビットが定義されている。
vctype1 0x020 RV32 RV32において、vctypeの[63:32]を参照するためのレジスタ
vctype2 0x020 RV32, RV64 RV32において、vctypeの[95:64]を参照するためのレジスタ。RV64において、vctypeの[127:64]を参照するためのレジスタ
vctype3 0x020 RV32 RV32において、vctypeの[127:96]を参照するためのレジスタ
vctypev0 0x020 RV32, RV64, RV128
vctypev1 0x020 RV32, RV64, RV128
... 0x020 RV32, RV64, RV128
vctypev31 0x020 RV32, RV64, RV128

これらを使用して、ベクトル拡張では「最大ベクトル長(maximum vector length: MVL)」という値が定義される。

vlレジスタは、ベクトル拡張の「アクティブベクトル長(active vector length)」を格納する。これは、計算ターゲットとなるベクトルレジスタの長さを指定する。このvlレジスタへは、専用命令setvlを使って格納する。setvlレジスタは、MVLよりも大きな値を設定することはできない。vlはMVLよりも大きな値を設定しようとすると、自動的にトランケートしてMVLよりも小さな値に抑える。AVLの値が2$$\times$$MVLよりも小さな場合が少し特殊だが、これはベクトル演算のパイプライン化により性能を最大限に引き出すための工夫だ。

vcmaxwのフィールドは、ベクトルレジスタに格納できる最大データ幅を示する。これは、以下の表に従って設定される。例えば、ベクトルレジスタでサポートされるデータが最大で64ビットならば、vcmaxwは1011となる。vcmaxwは4ビットのフィールドがベクトルレジスタの32個分、つまり128ビット用意する必要がある。RV128ならば128ビットのデータに対して一度に参照できるのだが、RV64とRV32ではそうもいかない。 そこで、RV64ではvcmaxw2レジスタ、さらにRV32のためにvcmaxw1,vcmaxw2, vcmaxw3レジスタが用意されている。

エンコーディング
無効 0000
8 1000
16 1001
32 1010
64 1011
128 1100
f:id:msyksphinz:20190505224138p:plain

なぜこのようなエンコーディングになっているかというと、次に登場するCSRのvctypeとのつじつまを合わせるためだ。各ベクトルレジスタには、4ビット幅のvctypeレジスタが付属している。このCSRは各ベクトルレジスタがどのようなサイズのデータを格納しているかを示しているので、vcmaxwレジスタとは異なり、明確に整数型と浮動小数点型を区別する。

vctypeレジスタエンコーディングが、整数型を取り扱う時は最上位ビットが1、浮動小数点型を取り扱う時は最上位ビットを0に設定する。この2つのレジスタの関係は、常にvctypeレジスタvcmaxwレジスタの値よりも小さくなければならない。

さらに、プレディケートレジスタについて説明しておく。これは、ベクトルレジスタのマスクをするためのレジスタだ。このプレディケートレジスタを使用することで、ベクトルレジスタの中で処理を適用するエレメントと、そうでないエレメントを自由に制御することができるようになる。

vcnpredレジスタは、プレディケートレジスタが何本使用できるかを指定する。プレディケートレジスタは、前述の通りvp0からvp7まで8本存在するが、vpnpredは0から8までの値を取ることがでく。0を書き込むと全てのプレディケートレジスタを使用できなくなる。一方で8以上の値を書き込むと不正命令例外が発生する。

さらに、これまで説明したベクトルレジスタの設定をより高速に実行するための命令が存在する。これはvcfgd命令で、csrrw命令の一種なのだが、どのベクトルレジスタをどのデータ型に割り当てるのかを一気に設定することができる。

ベクトル命令の例

最後に、ベクトルレジスタをどのようにして使うのかを例で見てみる。プログラム[refs:vec_daxpy]のコードは、RISC-Vのベクトル拡張を使用して書いたベクトル同士の加算を行うためのコードだ(RISC-V User-Level ISA V2.2より抜粋)。

# 32ビット整数型どうしのベクトル配列の加算コード
# ベクトルレジスタは32ビット整数型をサポートするように構成されているものとする。
# a0 : ベクトル長Nを保持している。
# a1 : 加算結果を格納するベクトルのポインタを保持している。
# a2 : 加算するベクトル1つ目のポインタを保持している。
# a3 : 加算するベクトル2つ目のポインタを保持している。
loop: 
  setvl t0, a0    # Nをvlに設定する。t0に現在のvlの値を設定する。
  vld   v0, a2    # v0に1つ目のベクトルをロードする。
  sll   t1, t0, 2 # t1はベクトル長Nに相当するバイト数を返す(32ビット整数なので×4)
  add   a2, t1    # a2をロードしたバイト数だけ進める
  vld   v1, a3    # v1に2つ目のベクトルをロードする。
  add   a3, t1    # a3をロードしたバイト数だけ進める。
  vadd  v0, v1    # v0とv1を加算し、その結果をv0に格納する。
  sub   a0, t0    # ベクトル命令で処理した分だけa0の値を減らす。
  vst   v0, a1    # 加算した結果をメモリにストアする。
  add   a1, t1    # 結果ベクトルのポインタを進め®う。
  bnez  a0, loop  # ベクトル要素が残っていればループを繰り返す。そうでなければ終了する。

setvl命令により、まず1回のループ(つまり、1回のベクトル命令で処理できるデータの数)が設定される。次に加算対象となるベクトルをv0, v1にロードする。そしてベクトル演算により加算を行い、その結果をメモリに格納する。そしてベクトルサイズを1回のループで実行したデータ数で減算し、残りの処理すべきデータ数を計算する。残りのデータ数が0よりも小さくなるまでこの処理を繰り返して、全てのベクトルが処理されるまで続ける。