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個め以降 :クラス名+"_<シーケンス番号>"
- 同じクラスから複数のRTLモジュールが作られる場合以下のようになる
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
処理自体は単純で以下のようになる。
- Scalaメソッド
getClass.getName
でクラス名(パッケージ名付き)を取得 - 取得したクラス名を"."で分けて
last
で最後の要素を返却 - ex.)
test.sub.MyModule
ならlast
でMyModule
が返却される
ちゃんと今回の自分が考えたようなニーズに応えるためにパブリックなメソッドとして定義されているので、継承したクラス側でオーバーライドすることが可能だ。
ということで早速試してみよう。まずは簡単な例から。
- 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個のモジュールをパラメタライズして生成するようなケースにおいても、それぞれのモジュールにユニークなモジュール名を付与することが出来そう。