ハードウェアの気になるあれこれ

技術的に興味のあることを調べて書いてくブログ。主にハードウェアがネタ。

ChiselのBlackBoxの使い方のまとめ

スポンサーリンク

前回のChiselの記事ではChisel-Bootcampを進めてModule3.6のジェネリクス型を学んでいった。

www.tech-diningyo.info

今回はChiselのTips的な話で、ネタになるのは自分がChiselを使って実装をしていた際に出くわしたVerilog HDLで実装されたモジュールをブラックボックスとして接続する方法についてだ。

Chiselのモジュールのブラックボックス

まずはBlackBoxの紹介から始めて行く。これはChiselのWikiにも記載されているので、そちらも必要に応じて参照してもらうとよいです。

github.com

あとmsyksphinzさんのFPGA開発日記の方でも既に取り上げてくださってますのでこちらもどうぞ。

msyksphinz.hatenablog.com

埋め込むサンプルのVerilog HDLコード

まずは先に今回の記事でサンプルとして使用する埋め込みたいVerilogのデザインを準備する。 今回は簡単にするために、以下のように入力したデータが加算されるだけのモジュール。 ファイル名はそのまま"adder.v"としてます。

module AdderBB
    #(
       parameter in0_bits = 'd10
      ,parameter in1_bits = 'd10
      ,parameter out_bits = in0_bits + 1
      )
    (
      input clk
     ,input rst_n
     ,input [in0_bits-1:0] in0
     ,input [in1_bits-1:0] in1
     ,output reg [out_bits-1:0] out
     );

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            out <= 'h0;
        end
        else begin
            out <= in0 + in1;
        end
    end

endmodule

BlackBoxを使ったブラックボックス

ChiselではBlackBoxというクラスを継承してモジュールを作成すると、そのクラスをブラックボックス化することが出来る。 クラス名がそのままズバリBlackBox。 モジュールの作り方は通常のモジュールと全く一緒で以下のようにすればOK。

class <モジュール名> extends BlackBox(Map[String, Param]) {
  val io = IO(<IOをインスタンスする>)
}

注意点としてはBlackBoxの内部ではクロックとリセットが定義されないため、クロック/リセットが必要な場合は 明示的にIO()の中にそれらを含めなければならない。クロックは専用のオブジェクトが用意されているのでそれを使う。

  • クロック → Clock() → 入力ポートならInput(Clock())
  • リセット → リセットは通常のBool()でOK → Input(Bool())

ブラックボックス化の示す通りBlackBoxを継承して作ったモジュール自体はIOのみが定義されて、中は完全に空のモジュールになる。 そのためBlackBoxを継承したモジュール内部でChiselのハードウェア記述を行うとエラボレートの際にエラーになる。

class AdderBB(in0Bits: Int, in1Bits: Int)
  extends BlackBox(Map(
    "in0_bits" -> IntParam(in0Bits),
    "in1_bits" -> IntParam(in1Bits),
    "out_bits" -> IntParam(in0Bits+1)))  {
  val io = IO(new Bundle {
    val clk = Input(Clock())
    val rst_n = Input(Bool())
    val in0 = Input(UInt(in0Bits.W))
    val in1 = Input(UInt(in0Bits.W))
    val out = Output(UInt((in0Bits+1).W))
  })

  // io.out := io.in0 + io.in1 // この行を有効にするとエラーが発生!
}

その際のエラーは以下のようなものだ。 要はChiselのユーザーモジュールではないよーということか。

[info] [0.001] Elaborating design...
[error] (run-main-2) chisel3.internal.ChiselException: Error: Not in a UserModule. Likely cause: Missed Module() wrap, bare chisel API call, or attempting to construct hardware inside a BlackBox.
〜長いので省略〜
[error]     at chisel3.core.Bits.binop(Bits.scala:175)
[error]     at chisel3.core.UInt.do_$plus$amp(Bits.scala:462)
[error]     at chisel3.core.UInt.do_$plus$percent(Bits.scala:464)
[error]     at chisel3.core.UInt.do_$plus(Bits.scala:444) // ユーザーモジュールじゃないのに足し算がある。というのが理由
[error]     at AdderBB.<init>(Top.scala:14)  // エラーの発生元は14行目(上記コードのio.out := ...の行)
〜長いので省略〜

Verilogのパラメータの伝搬

Verilogのモジュールインスタンス時にパラメータを指定するようなケースがあると思うが冒頭の作り方や例の部分で書いているように 継承したBlackBoxのパラメータにMapを入れることでこれにも対応が可能だ。

その部分だけを切り出したのが以下の部分だ。

class AdderBB(in0Bits: Int, in1Bits: Int)
  extends BlackBox(Map(
    "in0_bits" -> IntParam(in0Bits),        // Verilogのパラメータを必要な分だけ
    "in1_bits" -> IntParam(in1Bits),        // "パラメータ名" -> Param(パラメータ)
    "out_bits" -> IntParam(in0Bits+1)))  {  // で定義

実はこの部分は記事を書いている2019/02/22時点ではWikiの記述と違っている。最初はWikiの記載のとおりにMapの中身のタプル指定を[String, String]で指定したのだが以下のように要求される型が違うというエラーが発生した。

[error] /media/diningyo/5B14-9AF3/blackbox/src/main/scala/Top.scala:6:37: type mismatch;
[error]  found   : String("in0Bits")
[error]  required: chisel3.core.Param
[error]   extends BlackBox(Map("in0Bits" -> "in0Bits",

そこで現在のChiselのBlackBoxの実装を確認してみたのだが以下のように[String, Params]に変更されていた。 #なお手元の環境のChiselは3.1.6

abstract class BlackBox(
  val params: Map[String, Param] = Map.empty[String, Param]) // Mapの指定は[String, Params]
  (implicit compileOptions: CompileOptions) extends BaseBlackBox {

またこのParamsは以下のような定義になっている。

/** Parameters for BlackBoxes */
sealed abstract class Param
case class IntParam(value: BigInt) extends Param
case class DoubleParam(value: Double) extends Param
case class StringParam(value: String) extends Param
/** Unquoted String */
case class RawParam(value: String) extends Param

ベースをsealed abstract class ParamとしてInt/Double/String/Rawの4つが準備されている。

Paramsの種類 Paramsの引数の型 Verilogのパラメータのマッピング
IntParam Int 整数 "A" -> IntParams(10) => .A(10)
DoubleParam Double 浮動小数 "A" -> DoubleParams(10.toDouble) => .A(10.0)
StringParam String 文字列 "A" -> StringParam("a") => .A("a")
RawParam String 文字列 "A" -> RawParam("a") => .A(a)

ただ単にブラックボックス化したいだけなら、これでも十分使える。 このBlackBoxを使ったモジュールをインスタンスしたChiselコードを見てみよう。
#今回はあえてこれ単体でRTLの生成も可能な完全なコードを載せてみる(あんまり見かけなかったから。。)

class Top(in0Bits: Int, in1Bits: Int) extends Module {
  val io = IO(new Bundle {
    val in0 = Input(UInt(in0Bits.W))
    val in1 = Input(UInt(in0Bits.W))
    val out = Output(UInt((in0Bits + 1).W))
  })

  val adder = Module(new AdderBB(in0Bits, in1Bits))
  adder.io.in0 := io.in0
  adder.io.in1 := io.in1
  io.out := adder.io.out
  adder.io.clk := clock
  adder.io.rst_n := !reset.asTypeOf(Bool()) // reset != Boolなので変換しないとダメ
}

// エラボレート用のメイン
object Elaborate extends App {
  chisel3.Driver.execute(args, () => new Top(32, 32))
}

上記のコードを実行すると生成されるブラックボックス化したモジュールのインスタンス部分のRTLは以下のようになる。 IOポートの名前は通常のModuleを使った場合とは異なり、IO()内で宣言した変数名がそのまま使用される。

module Top( // @[:@13.2]
  input         clock, // @[:@14.4]
  input         reset, // @[:@15.4]
  input  [31:0] io_in0, // @[:@16.4]
  input  [31:0] io_in1, // @[:@16.4]
  output [32:0] io_out // @[:@16.4]
);
  wire [32:0] AdderBB_out; // @[Top.scala 34:23:@18.4]
  wire [31:0] AdderBB_in1; // @[Top.scala 34:23:@18.4]
  wire [31:0] AdderBB_in0; // @[Top.scala 34:23:@18.4]
  wire  AdderBB_rst_n; // @[Top.scala 34:23:@18.4]
  wire  AdderBB_clk; // @[Top.scala 34:23:@18.4]
  // ここがインスタンス部分
  AdderBB AdderBB ( // @[Top.scala 34:23:@18.4]
    .out(AdderBB_out),
    .in1(AdderBB_in1),
    .in0(AdderBB_in0),
    .rst_n(AdderBB_rst_n),
    .clk(AdderBB_clk)
  );
  assign io_out = AdderBB_out; // @[Top.scala 37:12:@26.4]
  assign AdderBB_in1 = io_in1; // @[Top.scala 36:18:@25.4]
  assign AdderBB_in0 = io_in0; // @[Top.scala 35:18:@24.4]
  assign AdderBB_rst_n = reset; // @[Top.scala 39:20:@28.4]
  assign AdderBB_clk = clock; // @[Top.scala 38:18:@27.4]
endmodule

因みにBlackBoxと似たようなモジュールにExtModuleってのもある。 こちらを使うとIO()を複数個宣言することが可能になるが、それ以外には特に差分は無いっぽいので、あるよーというお知らせだけ。今後使ってて何か発見があれば別の記事で書くかも。

ブラックボックス化したモジュールを埋め込む方法

既存のVerilog HDLコードをブラックボックスとして埋めるという点だけみれば、上記のBlackBoxを使ったモジュールのインスタンスを方法を知っていればそれでOKだったりもする。要はブラックボックス部分以外はChiselで生成したRTLにしておいて、実際のシミュレータ環境側でブラックボックス部分のRTLを別途読みこめばそれでよい。 一方でChiselにはiotesterをいうモジュールを使うことで、Scala&Chiselで書いたテストコードをそのままFIRRTLレベルや他のシミュレータ(普通はverilatorになる)を使ってテストすることが可能だ。 そのような場合にこのブラックボックスとして定義したモジュールの中身をiotesterに認識させるための仕組みが用意されているので、ここからはそれを見ていこうと思う。

まずはテストコードの準備

ということで、まずはテスト用のメインオブジェクトを準備する。 こんなの↓

object Test extends App {
  chisel3.iotesters.Driver.execute(
    args, () => new Top(32, 32)) {
    c => new chisel3.iotesters.PeekPokeTester(c) {
      for (i <- 0 until 10) {
        val in0 = i
        val in1 = i * 2
        poke(c.io.in0, in0)
        poke(c.io.in1, in1)
        expect(c.io.out, in0 + in1)
      }
    }
  }
}

iotester.Driverを使ってテストを実行する形。テストの中身は今回はどうでも良いので適当にforループ回して1step後の出力を期待値と比較して確認するだけのものにした。

そして先ほどBlackBoxを内部でインスタンスして作ったTopモジュールのコードに上記のテストコードを追加してverilatorで実行してみる。

$ sbt "runMain Test --backend-name verilator"
[info] Loading global plugins from /home/diningyo/.sbt/1.0/plugins
[info] Loading project definition from /home/diningyo/prj/study/2000_chisel/blackbox/project
[info] Loading settings for project blackbox from build.sbt ...
[info] Set current project to chisel-module-template (in build file:/home/diningyo/prj/study/2000_chisel/blackbox/)
[warn] Multiple main classes detected.  Run 'show discoveredMainClasses' to see the list
[info] Running Test --backend-name verilator
[info] [0.002] Elaborating design...
[info] [0.774] Done elaborating.
Total FIRRTL Compile Time: 302.7 ms
cd /home/diningyo/prj/study/2000_chisel/blackbox/test_run_dir/Test107190931 && verilator --cc Top.v --assert -Wno-fatal -Wno-WIDTH -Wno-STMTDLY -O1 --top-module Top +define+TOP_TYPE=VTop +define+PRINTF_COND=!Top.reset +define+STOP_COND=!Top.reset -CFLAGS "-Wno-undefined-bool-conversion -O1 -DTOP_TYPE=VTop -DVL_USER_FINISH -include VTop.h" -Mdir /home/diningyo/prj/study/2000_chisel/blackbox/test_run_dir/Test107190931 --exe /home/diningyo/prj/study/2000_chisel/blackbox/test_run_dir/Test107190931/Top-harness.cpp --trace
%Error: Top.v:13: Cannot find file containing module: AdderBB
%Error: Top.v:13: This may be because there's no search path specified with -I<dir>.
%Error: Top.v:13: Looked in:
%Error: Top.v:13:       AdderBB
%Error: Top.v:13:       AdderBB.v
%Error: Top.v:13:       AdderBB.sv
%Error: Top.v:13:       /home/diningyo/prj/study/2000_chisel/blackbox/test_run_dir/Test107190931/AdderBB
%Error: Top.v:13:       /home/diningyo/prj/study/2000_chisel/blackbox/test_run_dir/Test107190931/AdderBB.v
%Error: Top.v:13:       /home/diningyo/prj/study/2000_chisel/blackbox/test_run_dir/Test107190931/AdderBB.sv
%Error: Exiting due to 9 error(s)
%Error: Command Failed /usr/bin/verilator_bin --cc Top.v --assert -Wno-fatal -Wno-WIDTH -Wno-STMTDLY -O1 --top-module Top '+define+TOP_TYPE=VTop' '+define+PRINTF_COND=!Top.reset' '+define+STOP_COND=!Top.reset' -CFLAGS '-Wno-undefined-bool-conversion -O1 -DTOP_TYPE=VTop -DVL_USER_FINISH -include VTop.h' -Mdir /home/diningyo/prj/study/2000_chisel/blackbox/test_run_dir/Test107190931 --exe /home/diningyo/prj/study/2000_chisel/blackbox/test_run_dir/Test107190931/Top-harness.cpp --trace
[error] (run-main-0) java.lang.AssertionError: assertion failed:
[error] java.lang.AssertionError: assertion failed:
[error]         at scala.Predef$.assert(Predef.scala:170)
[error]         at chisel3.core.assert$.apply(Assert.scala:76)
[error]         at chisel3.iotesters.setupVerilatorBackend$.apply(VerilatorBackend.scala:262)
[error]         at chisel3.iotesters.Driver$$anonfun$execute$1$$anonfun$apply$mcZ$sp$1.apply$mcZ$sp(Driver.scala:56)
[error]         at chisel3.iotesters.Driver$$anonfun$execute$1$$anonfun$apply$mcZ$sp$1.apply(Driver.scala:39)
[error]         at chisel3.iotesters.Driver$$anonfun$execute$1$$anonfun$apply$mcZ$sp$1.apply(Driver.scala:39)
[error]         at logger.Logger$$anonfun$makeScope$1.apply(Logger.scala:138)
[error]         at scala.util.DynamicVariable.withValue(DynamicVariable.scala:58)
[error]         at logger.Logger$.makeScope(Logger.scala:136)
[error]         at chisel3.iotesters.Driver$$anonfun$execute$1.apply$mcZ$sp(Driver.scala:39)
[error]         at chisel3.iotesters.Driver$$anonfun$execute$1.apply(Driver.scala:39)
[error]         at chisel3.iotesters.Driver$$anonfun$execute$1.apply(Driver.scala:39)
[error]         at scala.util.DynamicVariable.withValue(DynamicVariable.scala:58)
[error]         at chisel3.iotesters.Driver$.execute(Driver.scala:38)
[error]         at chisel3.iotesters.Driver$.execute(Driver.scala:100)
[error]         at Test$.delayedEndpoint$Test$1(Top.scala:65)
[error]         at Test$delayedInit$body.apply(Top.scala:63)
[error]         at scala.Function0$class.apply$mcV$sp(Function0.scala:34)
[error]         at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12)
[error]         at scala.App$$anonfun$main$1.apply(App.scala:76)
[error]         at scala.App$$anonfun$main$1.apply(App.scala:76)
[error]         at scala.collection.immutable.List.foreach(List.scala:392)
[error]         at scala.collection.generic.TraversableForwarder$class.foreach(TraversableForwarder.scala:35)
[error]         at scala.App$class.main(App.scala:76)
[error]         at Test$.main(Top.scala:63)
[error]         at Test.main(Top.scala)
[error]         at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[error]         at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
[error]         at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
[error]         at java.lang.reflect.Method.invoke(Method.java:498)
[error] Nonzero exit code: 1
[error] (Compile / runMain) Nonzero exit code: 1
[error] Total time: 3 s, completed 2019/03/17 23:12:46

まあ、なんか盛大に怒られるんですよ。 ただエラーの内容はとても単純でverilatorで実行しようとした時に

  • モジュール"AdderBB"が見つからなかった

というもの。 verilatorは親切にも"モジュール名.[v|sv]"といった名称のファイルの探索までやってくれたうえでエラーになってるのが分かると思います。 なので、上記の探索パス部分にそのファイルがあればシミュレーションは実行可能だったりします。上記の例だと、

  • /home/diningyo/prj/study/2000_chisel/blackbox/test_run_dir/Test107190931/AdderBB.v

が存在していればシミュレーションは正常に実施できます。

Chiselの機能を使って解決

でも冒頭に書いたとおり、こんなことしなくても良い仕組みがちゃんと準備されてるので、それを使ってスマートに解決してみましょう! #上記の方法だと、シミュレーション実行するまでverilatorのディレクトリも出来ないしね。

実際にどうするかというと、以下の2つの方法があります。

  1. BlackBoxで宣言したモジュールの内部にVerilog HDLを埋め込む
  2. BlackBoxで宣言したモジュールの中に本体のVerilog HDLファイルへのパスを埋め込む

1. BlackBoxVerilog HDLのコードを埋め込む(setInline)

この場合はtrait HasBlackBoxInlineをミックスインすると使用可能になるメソッドsetInlineを使用する。 ということで先のAdderBBを以下の様に書き換える。

class AdderBB(in0Bits: Int, in1Bits: Int)
  extends BlackBox(Map(
    "in0_bits" -> IntParam(in0Bits),
    "in1_bits" -> IntParam(in1Bits),
    "out_bits" -> IntParam(in0Bits+1))) with HasBlackBoxInline {
  val io = IO(new Bundle {
    val clk = Input(Clock())
    val rst_n = Input(Bool())
    val in0 = Input(UInt(in0Bits.W))
    val in1 = Input(UInt(in0Bits.W))
    val out = Output(UInt((in0Bits+1).W))
  })

  setInline("adder.v",
  s"""
    |module AdderBB
    |    #(
    |       parameter in0_bits = 'd10
    |      ,parameter in1_bits = 'd10
    |      ,parameter out_bits = in0_bits + 1
    |     )
    |      (
    |        input clk
    |       ,input rst_n
    |       ,input [in0_bits-1:0] in0
    |       ,input [in1_bits-1:0] in1
    |       ,output reg [out_bits-1:0] out
    |      );
    |
    |    always @(posedge clk or negedge rst_n) begin
    |        if (!rst_n) begin
    |            out <= 'h0;
    |        end
    |        else begin
    |            out <= in0 + in1;
    |        end
    |    end
    |endmodule
  """.stripMargin)
}

上記の様に変更したあとに再度バックエンドをverilatorにしてシミュレーションを実行すると以下のようにテストがパスするようになる。

[info] Loading global plugins from /home/diningyo/.sbt/1.0/plugins
[info] Loading project definition from /home/diningyo/prj/study/2000_chisel/blackbox/project
[info] Loading settings for project blackbox from build.sbt ...
[info] Set current project to chisel-module-template (in build file:/home/diningyo/prj/study/2000_chisel/blackbox/)
[warn] Multiple main classes detected.  Run 'show discoveredMainClasses' to see the list
[info] Running Test --backend-name verilator
[info] [0.002] Elaborating design...
[info] [0.812] Done elaborating.
Total FIRRTL Compile Time: 311.4 ms
cd /home/diningyo/prj/study/2000_chisel/blackbox/test_run_dir/Test107190931 && verilator --cc Top.v --assert -Wno-fatal -Wno-WIDTH -Wno-STMTDLY -O1 --top-module Top +define+TOP_TYPE=VTop +define+PRINTF_COND=!Top.reset +define+STOP_COND=!Top.reset -CFLAGS "-Wno-undefined-bool-conversion -O1 -DTOP_TYPE=VTop -DVL_USER_FINISH -include VTop.h" -Mdir /home/diningyo/prj/study/2000_chisel/blackbox/test_run_dir/Test107190931 -f /home/diningyo/prj/study/2000_chisel/blackbox/test_run_dir/Test107190931/black_box_verilog_files.f --exe /home/diningyo/prj/study/2000_chisel/blackbox/test_run_dir/Test107190931/Top-harness.cpp --trace
make: ディレクトリ '/home/diningyo/prj/study/2000_chisel/blackbox/test_run_dir/Test107190931' に入ります
g++  -I.  -MMD -I/usr/share/verilator/include -I/usr/share/verilator/include/vltstd -DVL_PRINTF=printf -DVM_TRACE=1 -DVM_COVERAGE=0 -Wno-char-subscripts -Wno-parentheses-equality -Wno-sign-compare -Wno-uninitialized -Wno-unused-but-set-variable -Wno-unused-parameter -Wno-unused-variable     -Wno-undefined-bool-conversion -O1 -DTOP_TYPE=VTop -DVL_USER_FINISH -include VTop.h   -c -o Top-harness.o /home/diningyo/prj/study/2000_chisel/blackbox/test_run_dir/Test107190931/Top-harness.cpp
g++  -I.  -MMD -I/usr/share/verilator/include -I/usr/share/verilator/include/vltstd -DVL_PRINTF=printf -DVM_TRACE=1 -DVM_COVERAGE=0 -Wno-char-subscripts -Wno-parentheses-equality -Wno-sign-compare -Wno-uninitialized -Wno-unused-but-set-variable -Wno-unused-parameter -Wno-unused-variable     -Wno-undefined-bool-conversion -O1 -DTOP_TYPE=VTop -DVL_USER_FINISH -include VTop.h   -c -o verilated.o /usr/share/verilator/include/verilated.cpp
g++  -I.  -MMD -I/usr/share/verilator/include -I/usr/share/verilator/include/vltstd -DVL_PRINTF=printf -DVM_TRACE=1 -DVM_COVERAGE=0 -Wno-char-subscripts -Wno-parentheses-equality -Wno-sign-compare -Wno-uninitialized -Wno-unused-but-set-variable -Wno-unused-parameter -Wno-unused-variable     -Wno-undefined-bool-conversion -O1 -DTOP_TYPE=VTop -DVL_USER_FINISH -include VTop.h   -c -o verilated_vcd_c.o /usr/share/verilator/include/verilated_vcd_c.cpp
/usr/bin/perl /usr/share/verilator/bin/verilator_includer -DVL_INCLUDE_OPT=include VTop.cpp > VTop__ALLcls.cpp
/usr/bin/perl /usr/share/verilator/bin/verilator_includer -DVL_INCLUDE_OPT=include VTop__Trace.cpp VTop__Syms.cpp VTop__Trace__Slow.cpp > VTop__ALLsup.cpp
g++  -I.  -MMD -I/usr/share/verilator/include -I/usr/share/verilator/include/vltstd -DVL_PRINTF=printf -DVM_TRACE=1 -DVM_COVERAGE=0 -Wno-char-subscripts -Wno-parentheses-equality -Wno-sign-compare -Wno-uninitialized -Wno-unused-but-set-variable -Wno-unused-parameter -Wno-unused-variable     -Wno-undefined-bool-conversion -O1 -DTOP_TYPE=VTop -DVL_USER_FINISH -include VTop.h   -c -o VTop__ALLcls.o VTop__ALLcls.cpp
g++  -I.  -MMD -I/usr/share/verilator/include -I/usr/share/verilator/include/vltstd -DVL_PRINTF=printf -DVM_TRACE=1 -DVM_COVERAGE=0 -Wno-char-subscripts -Wno-parentheses-equality -Wno-sign-compare -Wno-uninitialized -Wno-unused-but-set-variable -Wno-unused-parameter -Wno-unused-variable     -Wno-undefined-bool-conversion -O1 -DTOP_TYPE=VTop -DVL_USER_FINISH -include VTop.h   -c -o VTop__ALLsup.o VTop__ALLsup.cpp
      Archiving VTop__ALL.a ...
ar r VTop__ALL.a VTop__ALLcls.o VTop__ALLsup.o
ranlib VTop__ALL.a
g++    Top-harness.o verilated.o verilated_vcd_c.o VTop__ALL.a    -o VTop -lm -lstdc++  2>&1 | c++filt
make: ディレクトリ '/home/diningyo/prj/study/2000_chisel/blackbox/test_run_dir/Test107190931' から出ます
sim start on diningyo-pc at Sun Mar 17 23:48:43 2019
inChannelName: 00014751.in
outChannelName: 00014751.out
cmdChannelName: 00014751.cmd
STARTING test_run_dir/Test107190931/VTop
[info] [0.002] SEED 1552834121357
Enabling waves..
Exit Code: 0
[info] [0.023] RAN 10 CYCLES PASSED ## 全部の期待値が一致した
[success] Total time: 4 s, completed 2019/03/17 23:48:43

HasBlackBoxInlineを使ってブラックボックスの中身を解決した際にはverilatorの実行ディレクトリに以下の2つのファイルが生成される。

  1. setInlineの第一引数で指定したファイル:中身は第二引数に記載したVerilog HDLのコード
  2. black_box_verilog_files.fというファイルパスが記載されたファイル
  3. このファイルがverilator実行時に読み込まれ、ブラックボックスのモジュールの実体が解決される

1. BlackBoxVerilog HDLのパスを埋め込む(setResource)

こちらはVeilog HDLのコードを埋める代わりに、ファイルへのパスを埋め込んでブラックボックスモジュールの依存関係を解決する方法。 先ほどのtrait HasBlackBoxInlineの代わりにtrait HasBlackBoxResourceをミックスインしてsetResourceメソッドにファイルパスを引き渡す。 こちらを使った例が以下。

class AdderBB(in0Bits: Int, in1Bits: Int)
  extends BlackBox(Map(
    "in0_bits" -> IntParam(in0Bits),
    "in1_bits" -> IntParam(in1Bits),
    "out_bits" -> IntParam(in0Bits+1))) with HasBlackBoxResource {
  val io = IO(new Bundle {
    val clk = Input(Clock())
    val rst_n = Input(Bool())
    val in0 = Input(UInt(in0Bits.W))
    val in1 = Input(UInt(in0Bits.W))
    val out = Output(UInt((in0Bits+1).W))
  })

  setResource("/adder.v")
}

class AdderEM(in0Bits: Int, in1Bits: Int)
  extends ExtModule(Map(
    "in0_bits" -> IntParam(in0Bits),
    "in1_bits" -> IntParam(in1Bits),
    "out_bits" -> IntParam(in0Bits+1)))  {
  val clk = IO(Input(Clock()))
  val rst_n = IO(Input(Bool()))
  val io = IO(new Bundle {
    val in0 = Input(UInt(in0Bits.W))
    val in1 = Input(UInt(in0Bits.W))
    val out = Output(UInt((in0Bits+1).W))
  })
}

setResourceメソッドに指定するパスは"src/{main, test}/resources"の下に置くことが決まりとなっており、ファイル探索に置いてもこのディレクトリ以下が探索対象となる。 つまり上記の様に"/adder.v"を指定した場合にはファイルは"src/main/resources/AdderBB.v"に配置する必要がある。 あと、ファイルパス指定の"/adder.v"の"/"は必須、無いと以下のようなエラーが発生する

[error] (run-main-0) firrtl.transforms.BlackBoxNotFoundException: BlackBox 'AdderBB.v' not found. Did you misspell it? Is it in src/{main,test}/resources?

またsetInlineを使う方法、setResourceを使う方法のどちらともファイル名は任意の名前を付けることができるが、Verilog HDLのモジュール名はChiselでBlackBoxを継承したモジュールと同名である必要があるので注意が必要だ。

テストの実行結果はsetInlineを使った場合と一緒なので割愛して、これでBlackBoxの使い方まとめはお終い。