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

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

desiredNameを使ったChiselのモジュール名の変更

スポンサーリンク

Chiselのモジュール名は通常宣言したクラス名がそのまま使用されるのだが、これだと問題起こりそうなケースってあるよな、、、って思っていた。 そこで今回はこれに関する解決方法が無いかを調査したので紹介したい。

Chiselのモジュール名

Chiselのモジュール名は冒頭に書いたとおり、モジュール宣言時のクラス名が使用されるのが基本となる。例えば以下のような感じだ。

  • MyModule
class MyModule(bits: Int) extends Module {
  val io = IO(new Bundle {
    val in = Input(UInt(bits.W))
    val out = Output(UInt(bits.W))
  })

  io.out := io.in
}

object ElaborateMyModule extends App {
  Driver.execute(args, () => new MyModule(1))
}
  • MyModuleのRTL
module MyModule(
  input   clock,
  input   reset,
  input   io_in,
  output  io_out
);
  assign io_out = io_in; // @[Test.scala 106:10]
endmodule

パラメタライズ機能を持ったモジュールクラスでは入力するパラメータによって同じクラスから複数のRTLのモジュールが生成される場合がある。 この場合は”クラス名+シーケンス番号”といった形で、RTL上別モジュールとして認識されるようChiselが処理を行う。

class MyModuleTop extends Module {
  val io = IO(new Bundle {
    val in = Input(UInt(8.W))
    val out1 = Output(UInt(8.W))
    val out2 = Output(UInt(8.W))
  })

  // 以下のようにパラメータを変更すると
  // RTL上では別モジュールになる
  val m_mod1 = Module(new MyModule(8))
  val m_mod2 = Module(new MyModule(9))

  m_mod1.io.in := io.in
  m_mod2.io.in := io.in

  io.out1 := m_mod1.io.out
  io.out2 := m_mod2.io.out
}

object ElaborateMyModule extends App {
  Driver.execute(args, () => new MyModuleTop)
}
  • 生成されるRTL
    • 同じクラスから複数のRTLモジュールが作られる場合以下のようになる
      • 最初の一個:クラス名のみ
      • 2個め以降 :クラス名+"_<シーケンス番号>"
module MyModule(
  input  [7:0] io_in,
  output [7:0] io_out
);
  assign io_out = io_in; // @[Test.scala 106:10]
endmodule
module MyModule_1(
  input  [8:0] io_in,
  output [8:0] io_out
);
  assign io_out = io_in; // @[Test.scala 106:10]
endmodule
module MyModuleTop(
  input        clock,
  input        reset,
  input  [7:0] io_in,
  output [7:0] io_out1,
  output [7:0] io_out2
);
  wire [7:0] m_mod1_io_in; // @[Test.scala 116:22]
  wire [7:0] m_mod1_io_out; // @[Test.scala 116:22]
  wire [8:0] m_mod2_io_in; // @[Test.scala 117:22]
  wire [8:0] m_mod2_io_out; // @[Test.scala 117:22]
  MyModule m_mod1 ( // @[Test.scala 116:22]
    .io_in(m_mod1_io_in),
    .io_out(m_mod1_io_out)
  );
  MyModule_1 m_mod2 ( // @[Test.scala 117:22]
    .io_in(m_mod2_io_in),
    .io_out(m_mod2_io_out)
  );
  assign io_out1 = m_mod1_io_out; // @[Test.scala 122:11]
  assign io_out2 = m_mod2_io_out[7:0]; // @[Test.scala 123:11]
  assign m_mod1_io_in = io_in; // @[Test.scala 119:16]
  assign m_mod2_io_in = {{1'd0}, io_in}; // @[Test.scala 120:16]
endmodule

このような仕組みになっているため、RTL化した階層以下に同名のクラスから生成されたモジュールが複数個あってもモジュール名の衝突は起きない。

desiredName

前項で説明したとおりRTL化した際の最上位階層以下に含まれるモジュール間ではモジュール名の衝突が起きない仕組みではあるのだが、Chiselで作ったデザインを別のシステムに組み込むような場合に問題が起きることがある。

自分が経験したのはパッケージ名違いでクラス名が異なるモジュールクラスを作成して、別々にRTL化した後にデザインに組み込んだ際に、モジュール名の衝突が発生したことがあった。このようなケースにおいてはパッケージ名をモジュール名に反映できれば解決することが出来る。

ということで、今回Chiselのソースを再度調査して見つけたのがdesiredNameメソッドだ。このメソッドは通常のChiselのモジュールを作成する際に継承するModuleクラスの基底クラスBaseModuleで以下のように定義されている

    /** Desired name of this module. Override this to give this module a custom, perhaps parametric,
      * name.
      */
    def desiredName: String = this.getClass.getName.split('.').last

処理自体は単純で以下のようになる。

  1. ScalaメソッドgetClass.getNameでクラス名(パッケージ名付き)を取得
  2. 取得したクラス名を"."で分けてlastで最後の要素を返却
  3. ex.)test.sub.MyModuleならlastMyModuleが返却される

ちゃんと今回の自分が考えたようなニーズに応えるためにパブリックなメソッドとして定義されているので、継承したクラス側でオーバーライドすることが可能だ。

ということで早速試してみよう。まずは簡単な例から。

  • desiredNameを使ったモジュール名の変更
import chisel3._
import chisel3.util._
import test.{NoDesiredName, OverrideDesiredName}

class TrialDesiredName extends Module {
  val io = IO(new Bundle {
    val in = Input(UInt(8.W))
    val out_ndn = Output(UInt(8.W))
    val out_odn = Output(UInt(8.W))
  })

  val m_ndn = Module(new NoDesiredName)
  val m_odn = Module(new OverrideDesiredName)

  m_ndn.io.in := io.in
  m_odn.io.in := io.in

  io.out_ndn := m_ndn.io.out
  io.out_odn := m_odn.io.out
}

object ElaborateDesiredName extends App {
  Driver.execute(Array(
    "-tn=TrialDesiredName",
    "-td=rtl/TrialDesiredName"
  ),
    () => new TrialDesiredName
  )
}

/**
  * getClass.getNameの戻り値をわかりやすくするために
  * packet testの中にモジュールを定義する
  */
package test {

  /**
    * desiredNameをオーバーライドしないモジュール
    */
  class NotOverrideDesiredName extends Module {
    val io = IO(new Bundle {
      val in = Input(UInt(8.W))
      val out = Output(UInt(8.W))
    })

    io.out := io.in * 2.U
  }

  /**
    * desireNameをオーバーライドしてモジュール名を
    * 変更したモジュール
    */
  class OverrideDesiredName extends Module {
    val io = IO(new Bundle {
      val in = Input(UInt(8.W))
      val out = Output(UInt(8.W))
    })

    val idx = 100

    /**
      * モジュール名を指定するメソッド
      * @return 名称を変更したモジュール名
      */
    override def desiredName: String = {
      println("getClass         = " + getClass)
      println("getClass.getName = " + getClass.getName)
      val name = getClass.getName.replaceAll("\\.", "")
      println("new module name  = " + name)
      name
    }
}

上記をエラボレートすると以下のようにオーバーライドしたdesiredName内で表示したgetClass/getNameの値が表示される

[IJ]sbt:dma> runMain ElaborateDesiredName
~略~
[info] Running ElaborateDesiredName
[info] [0.015] Elaborating design...
getClass         = class test.OverrideDesiredName
getClass.getName = test.OverrideDesiredName
new module name  = testOverrideDesiredName  <- 付けたい名前
[info] [1.234] Done elaborating.
Total FIRRTL Compile Time: 709.0 ms
  • 生成されたRTL
module NotOverrideDesiredName(
  input  [7:0] io_in,
  output [7:0] io_out
);
  wire [9:0] _T; // @[Test.scala 69:21]
  assign _T = io_in * 8'h2; // @[Test.scala 69:21]
  assign io_out = _T[7:0]; // @[Test.scala 69:12]
endmodule
module testOverrideDesiredName(
  input  [7:0] io_in,
  output [7:0] io_out
);
  wire [9:0] _T; // @[Test.scala 96:21]
  assign _T = io_in * 8'h3; // @[Test.scala 96:21]
  assign io_out = _T[7:0]; // @[Test.scala 96:12]
endmodule
module TrialDesiredName(
  input        clock,
  input        reset,
  input  [7:0] io_in,
  output [7:0] io_out_ndn,
  output [7:0] io_out_odn
);
  wire [7:0] m_ndn_io_in; // @[Test.scala 13:21]
  wire [7:0] m_ndn_io_out; // @[Test.scala 13:21]
  wire [7:0] m_odn_io_in; // @[Test.scala 14:21]
  wire [7:0] m_odn_io_out; // @[Test.scala 14:21]
  NotOverrideDesiredName m_ndn ( // @[Test.scala 13:21]
    .io_in(m_ndn_io_in),
    .io_out(m_ndn_io_out)
  );
  testOverrideDesiredName m_odn ( // @[Test.scala 14:21]
    .io_in(m_odn_io_in),
    .io_out(m_odn_io_out)
  );
  assign io_out_ndn = m_ndn_io_out; // @[Test.scala 19:14]
  assign io_out_odn = m_odn_io_out; // @[Test.scala 20:14]
  assign m_ndn_io_in = io_in; // @[Test.scala 16:15]
  assign m_odn_io_in = io_in; // @[Test.scala 17:15]
endmodule

クラスパラメータを使ってモジュール名を生成

先程示したメソッドの定義のようにdesiredName自体は引数を持たないが、クラスパラメータを参照することが可能なので、それを使えばモジュール内でインスタンスするモジュール名をクラスパラメータで変更することも可能だ。

import chisel3._
import chisel3.util._

class MyModule(bits: Int, name: String) extends Module {
  val io = IO(new Bundle {
    val in = Input(UInt(bits.W))
    val out = Output(UInt(bits.W))
  })

  io.out := io.in

  override def desiredName: String = name
}

class Top(bits1: Int, bits2: Int) extends Module {
  val io = IO(new Bundle {
    val in = Input(UInt(bits1.W))
    val out1 = Output(UInt(bits1.W))
    val out2 = Output(UInt(bits2.W))
  })

  val m_mod1 = Module(new MyModule(bits1, "AA"))
  val m_mod2 = Module(new MyModule(bits2, "BB"))

  m_mod1.io.in := io.in
  m_mod2.io.in := io.in

  io.out1 := m_mod1.io.out
  io.out2 := m_mod2.io.out
}

object Test extends App {
  Driver.execute(args, () => new Top(7, 8))
}
  • 生成されたRTL
module AA(
  input  [6:0] io_in,
  output [6:0] io_out
);
  assign io_out = io_in; // @[Test.scala 11:10]
endmodule
module BB(
  input  [7:0] io_in,
  output [7:0] io_out
);
  assign io_out = io_in; // @[Test.scala 11:10]
endmodule
module Top(
  input        clock,
  input        reset,
  input  [6:0] io_in,
  output [6:0] io_out1,
  output [7:0] io_out2
);
  wire [6:0] m_mod1_io_in; // @[Test.scala 23:22]
  wire [6:0] m_mod1_io_out; // @[Test.scala 23:22]
  wire [7:0] m_mod2_io_in; // @[Test.scala 24:22]
  wire [7:0] m_mod2_io_out; // @[Test.scala 24:22]
  AA m_mod1 ( // @[Test.scala 23:22]
    .io_in(m_mod1_io_in),
    .io_out(m_mod1_io_out)
  );
  BB m_mod2 ( // @[Test.scala 24:22]
    .io_in(m_mod2_io_in),
    .io_out(m_mod2_io_out)
  );
  assign io_out1 = m_mod1_io_out; // @[Test.scala 29:11]
  assign io_out2 = m_mod2_io_out; // @[Test.scala 30:11]
  assign m_mod1_io_in = io_in; // @[Test.scala 26:16]
  assign m_mod2_io_in = {{1'd0}, io_in}; // @[Test.scala 27:16]
endmodule

上記のRTLの通りでモジュール名が指定した名前に変更されることが確認できた。 これをうまいこと利用すればSeq等でN個のモジュールをパラメタライズして生成するようなケースにおいても、それぞれのモジュールにユニークなモジュール名を付与することが出来そう。