FPGA開発日記

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

「量子コンピュータ・超並列計算のからくり」を購入

師匠のブログでおすすめとのことで、すごく分かりやすいよとアピールを受けたので購入。

なるほど、このサイズで横書きというのはなかなか珍しい。これから時間を見つけて読みます。

RISC-Vにも対応できるJTAG Debugger ARM-USB-TINY-Hを買った

f:id:msyksphinz:20180630131007p:plain

Freedom SoCのデバッグをしたいので、ARMのJTAG Debuggerを購入した。だいたい5000円くらい。

Strawberry Linuxで購入できるとのことだけど、クレジットカードに対応していなくて代引きのみ。 そんなの仕事してる最中にどうやって払えっていうんだよ!

というわけでDigi-Keyで購入。到着まで3~4日くらい。

あとは接続用のケーブルを買っていないので秋葉原に行って買ってこなきゃ...

高速C++コンパイラZapccの試行(2. GCC / LLVM / zapcc でのコンパイル速度比較)

https://www.zapcc.com/wp-content/uploads/2018/06/xcopy-zapcc-logo.png.pagespeed.ic.HTEiBd-jW-.webp

zapccは先日発表されたClangをベースとしたコンパイラである。基本的な高速化の手法についてはいろんなところで公開されているのでそちらをチェックして欲しい。

サーバクライアント方式にすることで高速化を図っているということなのだが、実際にはどの程度の速度が出るのか挑戦してみたい。

ということで、自分で管理しているRISC-VシミュレータをGCC/LLVM/zapccでコンパイルして、コンパイル速度を比較してみたい。

調査したのは以下のコンパイラについて自作RISC-Vシミュレータをコンパイルしてその速度を比較する。 ビルド環境はCMakeで作ってあるのでCC, CXXを変更してコンパイル速度を見る。 ここではtimeコマンドを使って、user時間のみを計測した。

  • 使用バージョン
$ clang++ --version
clang version 7.0.0 (http://llvm.org/git/clang.git 7d013cb1a231fb165b41120e8aa786b044b0b8bd) (http://llvm.org/git/llvm.git 04d15315b2afc0be0839c9d9045644841c384455)
Target: x86_64-unknown-linux-gnu
Thread model: posix

$ zapcc++ --version
clang version 6.0.0 (trunk) (https://github.com/yrnkrn/zapcc.git 2c88635dd66151adad5119c4929a81d8d428c739)
Target: x86_64-unknown-linux-gnu
Thread model: posix

$ gcc --version
gcc (GCC) 6.2.0
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ CC=zapcc CXX=zapcc++ cmake . && make   # zapccによるビルド
$ CC=clang CXX=clang++ cmake . && make # LLVMによるビルド
$ CC=gcc CXX=c++ cmake . && make # gccによるビルド

それぞれ結果は以下のようになった。

  • zapcc : 0m13.323s
  • LLVM : 0m39.539s
  • gcc : 0m47.468s

なるほど、zapcc、高速だ。っていうかClangもgccより速いんですね。LLVM系列にビルド環境は移行しようかな。

f:id:msyksphinz:20180706230639p:plain

AWSで動作するRISC-VデザインFireSimのカスタマイズ : オリジナルデバイスをVerilog付きでシミュレーションする

前回の記事で、どうにかFixedInputStreamが実行できるようになったが、これはテストベンチまですべてScalaで記述して常に同じ動作を繰り返す環境になっている。 これは嫌なので、Verilogなどを接続して外部から引数を渡し、自由にシミュレーションを実行できるようにしたい。

というわけで、以下のチュートリアルを進めて、さらにFireChipのオリジナルデザインを拡張していく。

f:id:msyksphinz:20180628023357p:plain

Creating Simulation Model — FireSim documentation

まずは、ChiselのデザインとVerilogのデザインを接続するインタフェースを作らなければならない。 そこで、「実際にはVerilogのデザイン」なのだけれども、まずはScalaで型を作る。これにはBlackBoxというクラスを使用する。

class SimInputStream(w: Int) extends BlackBox(Map("DATA_BITS" -> IntParam(w))) {
  val io = IO(new Bundle {
    val clock = Input(Clock())
    val reset = Input(Bool())
    val out = Decoupled(UInt(w.W))
  })
}

このインタフェースを接続するためにSimInputStreamConfigコンフィグレーションを作成する。 さらにconnectSimInputを定義し、VerilogScalaをインタフェースするためのしくみを作る。

  • src/main/scala/example/Config.scala
class WithSimInputStream extends Config((site, here, up) => {
  case BuildTop => (clock: Clock, reset: Bool, p: Parameters) => {
    val top = Module(LazyModule(new ExampleTopWithInputStream()(p)).module)
    top.connectSimInput(clock, reset)
    top
  }
})

class SimInputStreamConfig extends Config(
  new WithSimInputStream ++ new BaseExampleConfig)
  • src/main/scala/example/InputStream.scala
 trait HasPeripheryInputStream { this: BaseSubsystem =>
   private val portName = "input-stream"
@@ -30,6 +41,13 @@ trait HasPeripheryInputStreamModuleImp extends LazyModuleImp {
     val fixed = Module(new FixedInputStream(data, outer.streamWidth))
     stream_in <> fixed.io.out
   }
+
+  def connectSimInput(clock: Clock, reset: Bool) {
+    val sim = Module(new SimInputStream(outer.streamWidth))
+    sim.io.clock := clock
+    sim.io.reset := reset
+    stream_in <> sim.io.out
+  }
 }

そしてVerilog/C++のインタフェースを作成して接続していく。これは長いしCopy&Pasteなので割愛。

要点としては、Verilogで定義されたSimInputStreamScalaで定義されたSimInputStreamconnectSimInputで接続する、ということ。

これができればあとはデータをC++インタフェース側からひたすら流し込むだけである。

  • src/main/resources/vsrc/SimInputStream.v
module SimInputStream #(DATA_BITS=64) 
   (
    input                    clock,
    input                    reset,
    output                   out_valid,
    input                    out_ready,
    output [DATA_BITS-1:0] out_bits
    );
   
   bit                     __out_valid;
...

これでコンパイルを行い、シミュレーションを行った。

$ make CONFIG=SimInputStreamConfig
$ dd if=/dev/urandom of=instream.img bs=32 count=1
1+0 records in
1+0 records out
32 bytes copied, 0.000306216 s, 105 kB/s
$ hexdump instream.img
0000000 1d2b e182 6629 006e 46be 3532 06c6 f1f2
0000010 2d00 aece 4ef7 d17b 4500 513a fd1e ca96
0000020
$ ./simulator-example-SimInputStreamConfig +instream=instream.img ../tests/input-stream.riscv
006e6629e1821d2b
f1f206c6353246be
d17b4ef7aece2d00
ca96fd1e513a4500

SiFive社から発表されたRISC-Vコアまとめ

f:id:msyksphinz:20180628004733p:plain

2018/11/01追記。だいぶ増えてきた。表更新。

2019/04/17追記。S21を追加。

f:id:msyksphinz:20181101230035p:plain
  • U7, S7, E7 Series
U74-MC U74 Standard S76-MC S76 Standard E76-MC(Main-Core) E76-MC(Sub-core) E76 Standard
Core Type U74 RV64C S7 RV64IMAC RV64GC RV64GC RV64GC RV32IMAFC RV32IMAFC
L1 Icache 32kB 16kB 32kB 32kB, ITIM option 32kB, ITIM option 32kB, ITIM option 32kB, ITIM option
L1 Dcache 32kB 8kB DTIM 32kB 32kB, FIO RAM option 32kB, FIO RAM option 32kB, FIO RAM 32kB, FIO RAM
Memory Protection 8 8 8
Local Interruption Per Core
Core Local Interrupt CLIC - CLIC
Virtual Memory Support Sv39 - Sv39
L2 ECC 2MB 128kB
DMIPS/MHz 2.5 2.5 2.5 2.3 2.3
Coremark/MHz 4.9 4.9 4.9 4.9 4.9
Pipeline In-order, 8-stage In-order, 8-stage 8-stage 8-stage
  • U5, S5 Series
U54‑MC(Main-Core) U54‑MC(Sub-Core) S54 Standard Core S51 Standard Core
Core Type U54 RV64GC E51 RV64IMAC RV64IMAFDC RV64IMAC
L1 Icache 32kB 16kB 16kB, ITIM option 16kB, ITIM option
L1 Dcache 32kB 8kB DTIM 64kB DTIM 64kB DTIM
Memory Protection 8 8 8 8
Local Interruption Per Core 48 48 16 16
Core Local Interrupt - - 1 timer, 1SW 1 timer, 1 SW
Virtual Memory Support Sv39 - Up to 40 physical address -
L2 ECC 2MB - -
DMIPS/MHz 1.7 1.61 1.7 1.7
Coremark/MHz 2.75 2.73 3.01 3.01
Pipeline In-order, 5-6stage In-order, 5-6stage
  • E3, E2, S2 Series
E34 Standard Core E31 Standard Core E24 Standard Core E21 Standard Core E20 Standard Core S21
Core Type RV32IMAFC RV32IMAC RV32IMAFC RV32IMAC RV32IMC RV64IMAC
L1 Icache 16kB, ITIM option 16kB, ITIM option - - - -
L1 Dcache 64kB, DTIM support 64kB, DTIM support - - - -
Memory Protection 8 8 8 2 - Up to 8
Local Interruption Per Core 16 16 ? 127 - Up to 1024
Core Local Interrupt 1 timer, 1 SW 1 timer, 1 SW 1024 Interrupts 127 Interrupts 32 Interrupts CLIC with 127 interrupts
Virtual Memory Support - - - - - -
L2 ECC - - - - - -
DMIPS/MHz 1.61 1.61 1.38 1.38 1.1 1.6
Coremark/MHz 3.01 3.01 3.1 3.1 2.4 3.2
Pipeline In-order, 5-6stage In-order, 5-6stage 3-stage 3-stage 2-stage -

AWSで動作するRISC-VデザインFireSimのカスタマイズ : Firechipにオリジナルデバイスを追加する

f:id:msyksphinz:20180617195844p:plain

FireSimについてある程度動かしたので、今度はカスタマイズをしてみたい。

というわけで、FireSimの実際のデザイン部分であるFireChipを使ってみることにした。

オリジナルのデザインを追加してみることにした。

メモリマップのレジスタを作る

参考にしたのは以下のチュートリアル

Memory-mapped Registers — FireSim documentation

ここで作るのは、外部からの情報を取ってきてメモリに書き込むデバイスだ(まどろっこしい書き方をしたが、つまりDMAだ)。 メモリマップされたレジスタを持っており、CPUはこのレジスタを使ってデバイスをコントロールすることができる。

まずはTLRegisterNodeオブジェクトを作って、RTLのレジスタを作成する。

  • src/main/scala/example/InputStream.scala
class InputStream(
    address: BigInt,
    val beatBytes: Int = 8)
    (implicit p: Parameters) extends LazyModule {

  val device = new SimpleDevice("input-stream", Seq("example,input-stream"))
  val regnode = TLRegisterNode(
    address = Seq(AddressSet(address, 0x3f)),
    device = device,
    beatBytes = beatBytes)

  lazy val module = new InputStreamModuleImp(this)
}

ここでTLRegisterNodeに対してパラメータを設定しているのだが、 - address : レジスタのアドレスとアドレスマスク。ここでは0x3fをマスクとして設定することで、64バイトのレジスタを宣言している。 - device : SimpleDevice型として作られたdeviceというオブジェクトを接続しているのだが、これはLinuxカーネルドライバで使用するためのもの。ここでは無視。 - beatBytes : TileLinkで規定されているデータ幅。この長さのレジスタが作られる。

ここではDMAを作るので、以下のレジスタがあれば良さそうだ。

  • addr : アドレス
  • len : 転送長
  • running : デバイスが実行中かどうか
  • complete : 転送が完了したか

実際のモジュールとしては、以下のようなInputStreamModuleImp を作成した。

  • src/main/scala/example/InputStream.scala
class InputStreamModuleImp(outer: InputStream) extends LazyModuleImp(outer) {
    val addrBits = 64
    val w = 64
    val io = IO(new Bundle {
        // Not used yet
        val in = Flipped(Decoupled(UInt(w.W)))
    }
    val addr = Reg(UInt(addrBits.W))
    val len = Reg(UInt(addrBits.W))
    val running = RegInit(false.B)
    val complete = RegInit(false.B)

    outer.regnode.regmap(
        0x00 -> Seq(RegField(addrBits, addr)),
        0x08 -> Seq(RegField(addrBits, len)),
        0x10 -> Seq(RegField(1, running)),
        0x18 -> Seq(RegField(1, complete)))
}

TileLinkのポートを作る

参考にしたのは以下。

DMA and Interrupts — FireSim documentation

つくるのはDMAなので、まずはTileLinkのポートを作らないと始まらない。これは先ほどのInputStreamに追加する。 TLClientNodeをモジュールとして追加した。

  • src/main/scala/example/InputStream.scala
class InputStream(
    address: BigInt,
    val beatBytes: Int = 8,
    val maxInflight: Int = 4)
    (implicit p: Parameters) extends LazyModule {

  val device = new SimpleDevice("input-stream", Seq("example,input-stream"))
  val regnode = TLRegisterNode(
    address = Seq(AddressSet(address, 0x3f)),
    device = device,
    beatBytes = beatBytes)
  val dmanode = TLClientNode(Seq(TLClientPortParameters(
    Seq(TLClientParameters(
      name = "input-stream",
      sourceId = IdRange(0, maxInflight))))))

  lazy val module = new InputStreamModuleImp(this)
}

TLClientNodeのパラメータとしてはとりあえず何となくわかりそうなものだが、ソースIDはTileLinkの識別IDを設定している。 これはどんな自由なモジュールでもこの調子で名前を付けていいのかしら?

次にDMAのステートマシンを作る。これも上記のチュートリアルの写経だが...

  • src/main/scala/example/InputStream.scala
class InputStreamModuleImp(outer: InputStream) extends LazyModuleImp(outer) {
  val (tl, edge) = outer.dmanode.out(0)
  val addrBits = edge.bundle.addressBits
  val w = edge.bundle.dataBits
  val beatBytes = (w / 8)

  val io = IO(new Bundle {
    val in = Flipped(Decoupled(UInt(w.W)))
  })

  val addr = Reg(UInt(addrBits.W))
  val len = Reg(UInt(addrBits.W))
  val running = RegInit(false.B)
...

やっていることとしては、IOポートから入ってくるデータを、tlポート(TileLinkポート)に出力している。

以下のコードが肝になるらしい?TileLinkのAポートでValidを出している。

  • src/main/scala/example/InputStream.scala
...
  io.in.ready := canIssue && tl.a.ready
  tl.a.valid  := canIssue && io.in.valid
  tl.a.bits   := edge.Put(
    fromSource = OHToUInt(xactOnehot),
    toAddress = addr,
    lgSize = log2Ceil(beatBytes).U,
    data = io.in.bits)._2
  tl.d.ready := running && xactBusy.orR
...

SoCへの組み込み

次に、SoC環境へのDMAコントローラの追加を行う。

参考にしたのは以下のチュートリアル

Connecting Devices to Bus — FireSim documentation

以下のようなtraitを作る。BaseSubsystemに対してデバイスを追加する。アドレスは 0x10017000 だ。

BaseSubsystemはシステムバスsbusペリフェラルバスのpbus、さらに割り込み用のバスとしてibusを持っている。 pbusレジスタノードに接続し、sbusをDMAノード、割り込みノードをibusに追加する。

  • src/main/scala/example/InputStream.scala
trait HasPeripheryInputStream { this: BaseSubsystem =>
  private val portName = "input-stream"
  val streamWidth = pbus.beatBytes * 8
  val inputstream = LazyModule(new InputStream(0x10017000, pbus.beatBytes))
  pbus.toVariableWidthSlave(Some(portName)) { inputstream.regnode }
  sbus.fromPort(Some(portName))() := inputstream.dmanode
  ibus.fromSync := inputstream.intnode
}
trait HasPeripheryInputStreamModuleImp extends LazyModuleImp {
  val outer: HasPeripheryInputStream

  val stream_in = IO(Flipped(Decoupled(UInt(outer.streamWidth.W))))
  outer.inputstream.module.io.in <> stream_in

  def connectFixedInput(data: Seq[BigInt]) {
    val fixed = Module(new FixedInputStream(data, outer.streamWidth))
    stream_in <> fixed.io.out
  }
}

そしてTopへの接続だ。さらにシミュレーション環境を接続して完了となる。

  • src/main/scala/example/Top.scala
class ExampleTopWithInputStream(implicit p: Parameters) extends ExampleTop
    with HasPeripheryInputStream {
  override lazy val module = new ExampleTopWithInputStreamModule(this)
}

class ExampleTopWithInputStreamModule(outer: ExampleTopWithInputStream)
  extends ExampleTopModuleImp(outer)
  with HasPeripheryInputStreamModuleImp
  • src/main/scala/example/Configs.scala
class WithFixedInputStream extends Config((site, here, up) => {
  case BuildTop => (clock: Clock, reset: Bool, p: Parameters) => {
    val top = Module(LazyModule(new ExampleTopWithInputStream()(p)).module)
    top.connectFixedInput(Seq(
      BigInt("1002abcd", 16),
      BigInt("34510204", 16),
      BigInt("10329999", 16),
      BigInt("92101222", 16)))
    top
  }
})

class FixedInputStreamConfig extends Config(
  new WithFixedInputStream ++ new BaseExampleConfig)

最後にVerilogを出力してみたのだが、FixedInputStreamがないと怒られる。たしかに、資料を探してもFixedInputStreamが見当たらない。自分で作らないといけないのかなあ?

$  make CONFIG=FixedInputStreamConfig
mkdir -p /home/msyksphinz/work/firechip/verisim/generated-src
cd /home/msyksphinz/work/firechip && java -Xmx2G -Xss8M -XX:MaxPermSize=256M -jar /home/msyksphinz/work/firechip/rocket-chip/sbt-launch.jar "runMain example.Generator  /home/msyksphinz/work/firechip/verisim/generated-src example TestHarness example FixedInputStreamConfig"
OpenJDK 64-Bit Server VM warning: ignoring option MaxPermSize=256M; support was removed in 8.0
[info] Loading project definition from /home/msyksphinz/work/firechip/project
[info] Loading settings from build.sbt ...
[info] Loading settings from build.sbt ...
[info] Loading settings from build.sbt ...
[info] Loading settings from build.sbt ...
[info] Loading settings from plugins.sbt ...
[info] Loading project definition from /home/msyksphinz/work/firechip/rocket-chip/project
[info] Loading settings from build.sbt ...
[info] Loading settings from build.sbt ...
[info] Loading settings from build.sbt ...
Using addons:
[info] Set current project to example (in build file:/home/msyksphinz/work/firechip/)
[warn] Multiple main classes detected.  Run 'show discoveredMainClasses' to see the list
[info] Compiling 3 Scala sources to /home/msyksphinz/work/firechip/target/scala-2.11/classes ...
[error] /home/msyksphinz/work/firechip/src/main/scala/example/InputStream.scala:30:28: not found: type FixedInputStream
[error]     val fixed = Module(new FixedInputStream(data, outer.streamWidth))
[error]                            ^
[error] one error found
[error] (Compile / compileIncremental) Compilation failed
[error] Total time: 6 s, completed Jun 26, 2018 11:18:11 PM
/home/msyksphinz/work/firechip/Makefrag:28: recipe for target '/home/msyksphinz/work/firechip/verisim/generated-src/example.TestHarness.FixedInputStreamConfig.fir' failed
make: *** [/home/msyksphinz/work/firechip/verisim/generated-src/example.TestHarness.FixedInputStreamConfig.fir] Error 1
f:id:msyksphinz:20180627003813p:plain

2018/06/28追加。MLで聞いてみるとFixedInputStreamの実装を教えてくれた。以下を追加した。

  • src/main/scala/example/InputStream.scala
class FixedInputStream(data: Seq[BigInt], w: Int) extends Module {
    val io = IO(new Bundle {
        val out = Decoupled(UInt(w.W))
    })

    val s_start :: s_send :: s_done :: Nil = Enum(3)
    val state = RegInit(s_start)

    val dataVec = VecInit(data.map(_.U(w.W)))
    val (sendIdx, sendDone) = Counter(io.out.fire(), data.size)

    io.out.valid := (state === s_send)
    io.out.bits := dataVec(sendIdx)

    when (state === s_start) { state := s_send }
    when (sendDone) { state := s_done }
}

コンパイルが完了すると、チュートリアルの通りにテストプログラムを作成し、コンパイルする。 実行すると、以下のようになった。正しく動いている。

$ ./simulator-example-FixedInputStreamConfig ../tests/input-stream.riscv
000000001002abcd
0000000034510204
0000000010329999
0000000092101222

何だこの出力は?と思うかもしれないが、これは最初にChiselでFixした入力信号なのでそれが出力されているだけだ。

  • src/main/scala/example/Config.scala
class WithFixedInputStream extends Config((site, here, up) => {
  case BuildTop => (clock: Clock, reset: Bool, p: Parameters) => {
    val top = Module(LazyModule(new ExampleTopWithInputStream()(p)).module)
    top.connectFixedInput(Seq(
      BigInt("1002abcd", 16),
      BigInt("34510204", 16),
      BigInt("10329999", 16),
      BigInt("92101222", 16)))
    top
  }
})

これだけでは単純にテストベクタが固まっているので、もっとカスタマイズして実行したい。

AWSで動作するRISC-VデザインFireSimのカスタマイズ : Firechipを試す

f:id:msyksphinz:20180617195844p:plain

FireSimについてある程度動かしたので、今度はカスタマイズをしてみたい。

というわけで、Firesimの実際のデザイン部分であるFireChipを使ってみることにした。

Tutorial: Developing New Devices — FireSim documentation

まずはデザインをForkしてダウンロードしてみる。とりあえずriscv-toolsはインストールしなくても良いらしい。

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

私はVCSは持っていないので、Veritalorでシミュレーションを行う。

$ cd verisim/
$ make

これで、DefaultExampleConfig という構成でFireSimの構成が作られる。

これで次に進もうとしたのだが、run-regression-testsが動作しなくて途中で止めてしまった。これ、定義されてるのかなあ?

$ make run-regression-tests

その代わり、普通にビルドしたベンチマークプログラムは動作するらしい。試しに何本か流してみた。

msyksphinz@msyksphinz-VirtualBox:~/work/firechip/verisim$ ./simulator-example-DefaultExampleConfig /home/msyksphinz/riscv64/riscv64-unknown-elf/share/riscv-tests/benchmarks/dhrystone.riscv
Microseconds for one run through Dhrystone: 540
Dhrystones per Second:                      1850
mcycle = 270271
minstret = 196530

msyksphinz@msyksphinz-VirtualBox:~/work/firechip/verisim$ ./simulator-example-DefaultExampleConfig /home/msyksphinz/riscv64/riscv64-unknown-elf/share/riscv-tests/benchmarks/rsort.riscv
mcycle = 238440
minstret = 171171