FPGA開発日記

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

BoringUtilsを使えばデバッグポートを開けずにChiselで内部信号を観察できる

Chisel3にはIOTestersというテスト用の環境が備わっており、Chiselのモジュールをテストするための機能が備わっている。

しかしこのIOTestersの弱点の一つは、ターゲットとなるモジュールのインタフェースにしかアクセスできず、内部信号にアクセスできない。したがってデバッグがとてもやりにくい、ということだ。 いろいろ試行錯誤したのだが結局IOポートにテストポートを引っ張り出すしか方法がなく、他にやり方は無いものかなあと思っていた。

    // 基本的に触れるのはIOポートだけ。内部信号には触れない。
    poke(dut.io.inA, inA)
    poke(dut.io.inB, inB)
    poke(dut.io.inC, inC)
    poke(dut.io.exp_ans,  exp_val)
    poke(dut.io.exp_flag, exp_flag)
    poke(dut.io.valid, true)

    step(1)

    expect(dut.io.ans_ok, true)
    expect(dut.io.flag_ok, true)

    poke(dut.io.valid, false)
    step(9)

しかし、どうも調べてみるとBoringUtilsというものを使えば内部信号を外に引っ張り出すことが可能らしい。やってみよう。

www.chisel-lang.org

package boringutils

import chisel3._
import chisel3.util.experimental._

class Constant extends Module {
  val io = IO(new Bundle{})
  val x = Wire(UInt(6.W))
  x := 42.U
  BoringUtils.addSource(x, "uniqueId")
}
class Expect extends Module {
  val io = IO(new Bundle{})
  val y = Wire(UInt(6.W))
  y := 0.U
  // This assertion will fail unless we bore!
  chisel3.assert(y === 42.U, "y should be 42 in module Expect")
  BoringUtils.addSink(y, "uniqueId")
}
class Top extends Module {
  val io = IO(new Bundle{})
  val constant = Module(new Constant)
  val expect = Module(new Expect)
}

object Main extends App {
  chisel3.Driver.execute(args, () => new Top())
}

BoringUtils.addSource(x, "uniqueId")によって、信号線をモジュールの外に引っ張り出している。 そして、BoringUtils.addSink(y, "uniqueId")でモジュールの中に引きこんでいるわけだ。この時の信号背の名前としてuniqueIdという名前で一致を取っている。

f:id:msyksphinz:20191121224917p:plain
BoringUtilsによる信号の引き出しと引き込み。

Verilogファイルを生成して、どのようになるかを観察してみる。

それぞれの下位のモジュールは以下のようにポートが自動的に生成される。

module Constant(
  output [5:0] x_0
);
  wire [5:0] x; // @[boringutils.scala 8:15 boringutils.scala 9:5]
  assign x = 6'h2a; // @[boringutils.scala 8:15 boringutils.scala 9:5]
  assign x_0 = x;
endmodule
module Expect(
  input        clock,
  input        reset,
  input  [5:0] uniqueId
);
...
  assign _T = uniqueId == 6'h2a; // @[boringutils.scala 17:20]
...
endmodule

接続部分のTopモジュールは以下のようになっている。

module Top(
  input   clock,
  input   reset
);
  wire [5:0] constant_x_0; // @[boringutils.scala 22:24]
  wire  expect__clock; // @[boringutils.scala 23:22]
  wire  expect__reset; // @[boringutils.scala 23:22]
  wire [5:0] expect__uniqueId; // @[boringutils.scala 23:22]
  Constant constant ( // @[boringutils.scala 22:24]
    .x_0(constant_x_0)
  );
  Expect expect_ ( // @[boringutils.scala 23:22]
    .clock(expect__clock),
    .reset(expect__reset),
    .uniqueId(expect__uniqueId)
  );
  assign expect__clock = clock;
  assign expect__reset = reset;
  assign expect__uniqueId = constant_x_0;
endmodule