FPGA開発日記

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

ハードウェア記述言語Chiselコンパイラの内部解析(3. Chiselコンパイルのフローを追いかける)

https://cdn-ak.f.st-hatena.com/images/fotolife/m/msyksphinz/20181105/20181105012126.png

Chiselのコンパイルフローの解析の続き。前の記事は以下。

どこでChiselからFIRへの変換が行われているのかというと、それはChiselStageEmitterが変換処理を行っているらしい。

  • chisel3/src/main/scala/chisel3/internal/firrtl/Emitter.scala
  private def emit(e: Command, ctx: Component): String = { // scalastyle:ignore cyclomatic.complexity
    val firrtlLine = e match {
      case e: DefPrim[_] => s"node ${e.name} = ${e.op.name}(${e.args.map(_.fullName(ctx)).mkString(", ")})"
      case e: DefWire => s"wire ${e.name} : ${emitType(e.id)}"
      case e: DefReg => s"reg ${e.name} : ${emitType(e.id)}, ${e.clock.fullName(ctx)}"
      case e: DefRegInit => s"reg ${e.name} : ${emitType(e.id)}, ${e.clock.fullName(ctx)} with : (reset => (${e.reset.fullName(ctx)}, ${e.init.fullName(ctx)}))" // scalastyle:ignore line.size.limit
      case e: DefMemory => s"cmem ${e.name} : ${emitType(e.t)}[${e.size}]"
      case e: DefSeqMemory => s"smem ${e.name} : ${emitType(e.t)}[${e.size}]"
      case e: DefMemPort[_] => s"${e.dir} mport ${e.name} = ${e.source.fullName(ctx)}[${e.index.fullName(ctx)}], ${e.clock.fullName(ctx)}" // scalastyle:ignore line.size.limit
      case e: Connect => s"${e.loc.fullName(ctx)} <= ${e.exp.fullName(ctx)}"
      case e: BulkConnect => s"${e.loc1.fullName(ctx)} <- ${e.loc2.fullName(ctx)}"
      case e: Attach => e.locs.map(_.fullName(ctx)).mkString("attach (", ", ", ")")
      case e: Stop => s"stop(${e.clock.fullName(ctx)}, UInt<1>(1), ${e.ret})"
...

ステップを突き詰めていくと、まずChisel3のemitVerilogが呼び出されると、各種コンパイルのステージを通っていきChiselStageというフェーズに入る。この中でEmitterが呼び出され、FIRへの変換が行われるらしい。

f:id:msyksphinz:20190830235936p:plain
Chisel3のemitVerilogからのコンパイルフロー(一部)

ここでDefPrimやらDefMemoryやらで挙動が変わるような仕組みになっているが、これはどこで宣言されているのだろうか?

Chiselでは、例えばレジスタを宣言する場合にDefRegInitなどのオブジェクトが宣言され、これによりオブジェクトの階層が構成されるようになっている。

  • chisel3/chiselFrontend/src/main/scala/chisel3/Reg.scala
object RegInit {
  /** Construct a [[Reg]] from a type template initialized to the specified value on reset
    * @param t The type template used to construct this [[Reg]]
    * @param init The value the [[Reg]] is initialized to on reset
    */
  def apply[T <: Data](t: T, init: T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): T = {
    if (compileOptions.declaredTypeMustBeUnbound) {
      requireIsChiselType(t, "reg type")
    }
    val reg = t.cloneTypeFull
    val clock = Builder.forcedClock
    val reset = Builder.forcedReset

    reg.bind(RegBinding(Builder.forcedUserModule))
    requireIsHardware(init, "reg initializer")
    pushCommand(DefRegInit(sourceInfo, reg, clock.ref, reset.ref, init.ref))
    reg
  }
...

上記の場合は、DefRegInitというコマンドを登録している。DefRegInitの宣言は以下だ。

  • chisel3/chiselFrontend/src/main/scala/chisel3/internal/firrtl/IR.scala
case class DefRegInit(sourceInfo: SourceInfo, id: Data, clock: Arg, reset: Arg, init: Arg) extends Definition

Moduleの場合は、DefInstanceが呼び出される。

  • chisel3/chiselFrontend/src/main/scala/chisel3/Module.scala
object Module extends SourceInfoDoc {
  /** A wrapper method that all Module instantiations must be wrapped in
    * (necessary to help Chisel track internal state).
...
  */
  /** @group SourceInfoTransformMacro */
  def do_apply[T <: BaseModule](bc: => T)
                               (implicit sourceInfo: SourceInfo,
                                         compileOptions: CompileOptions): T = {
...
    // Handle connections at enclosing scope
    if(!Builder.currentModule.isEmpty) {
      pushCommand(DefInstance(sourceInfo, module, component.ports))
      module.initializeInParent(compileOptions)
    }
    module
  }