Chisel3.4.0の変更点確認の2回目。今回からは、気になった機能を確認していく。 まず最初はChisel3.4.0の目玉機能である、コンパイラプラグインとPrefixingによる、命名規則の改善について。
(#1448) Improved Chisel Naming via Compiler Plugins + Prefixing
概要については、前回の記事でざっとまとめたものを転載。
今回の目玉機能。コンパイラのプラグインと
prefix
というメソッドが導入された。 コンパイラのプラグインによって、Chiselのコード中の変数名の多くが、そのままRTLの信号名として使用されるようになった。 またprefix
メソッドを使用することで、信号名に任意のプリフィックスの付与を行い、RTLの可読性を向上させることができるようになった。PRに載っている効果は次の通りで、16000あった"anonymous wires"が1400まで減ったとのこと。
- Just auto-naming: reduced number of Chisel source locations which generated anonymous registers from 95 to 19, and anonymous wires > from 19220 to 16073 (16% decrease in bad names).
- Auto-naming + prefixing of temporaries: further reduction of anonymous wires from 16073 to 10880.
- Auto-naming + auto-prefixing + added prefixing to one file (Monitor.scala) reduces 10880 -> 1395 anonymous wires.
ということで、なんだか凄い改善が見られている気がする。 この記事では、次の内容を確認してみようと思う
Chisel 3.3.x → Chisel 3.4.0
この"Just auto-naming"というのは、ただ単にChiselのバージョンを3.3から3.4に切り替えるだけで得ることが出来る効果になる。 なので、生成されるRTLの可読性を少しでも上げたいなー、、と考えている人は、すぐにでもChiselのバージョンを上げると、少し幸せになれるかも。
この効果を確認するには、そこそこの規模のデザインでChisel3.3/3.4でRTLを生成してみるのが手っ取り早いので、以前に作成したオレオレRISC-Vのコードを使って実験をしてみた。
比較したのはdirvの中のIduモジュール。変化の大きい部分をキャプチャしたものを以下に掲載。全部の結果は長いのでgistのリンクを次のコンパイラプラグインのところに貼ってあります。
コンパイラプラグインを有効にする
最初やり方がイマイチ分からなかったのだが、このコンパイラプラグインはbuild.sbt
に次の記述を追加すると有効に出来る。
lazy val chiselPluginLib = "edu.berkeley.cs" % "chisel3-plugin" % chiselVersion cross CrossVersion.full addCompilerPlugin(chiselPluginLib)
コンパイラ・プラグインが有効になっているかどうかはsbtシェルで次のコマンドを実行すれば確認可能。有効になっていればchisel3-plugin
が表示される。
IJ]sbt:dirv> show Compile / scalacOptions [info] * -Xsource:2.11 [info] * -Xplugin:/home/diningyo/.cache/coursier/v1/https/repo1.maven.org/maven2/org/scalamacros/paradise_2.12.10/2.1.1/paradise_2.12.10-2.1.1.jar [info] * -Xplugin:/home/diningyo/.cache/coursier/v1/https/repo1.maven.org/maven2/edu/berkeley/cs/chisel3-plugin_2.12.10/3.4.0/chisel3-plugin_2.12.10-3.4.0.jar [success] Total time: 0 s, completed 2020/11/23 20:55:21
全部の結果については、以下のgistのリンクをどうぞ。選択したモジュールがイマイチな例だった、、、というのもあり、あまり劇的な感じではなかった。。 それでもメソッド内部のローカルな変数として宣言したものがきちんとRTLに反映されており、効果は確認することができた。
def mulInstDecode(): Unit = { val m = funct7(0) mul.get := aluReg && m && (funct3 === "b000".U)
wire m = inst_funct7[0]; // @[Idu.scala 286:19] wire _inst_mul_T = inst_aluReg & m; // @[Idu.scala 287:23]
prefixオブジェクト
PRの内容を読んだ自分の理解としては、ここでのprefixing
というのが、今回Chisel 3.4.0で追加されたprefix
オブジェクトのことのハズ。
使い方はシンプルで、次のようにprefix
オブジェクトの(apply
の)引数に付与したいprefixをString
で与えるだけ。
def apply[T](name: String)(f: => T): T = {
とりあえず、簡単なコードで効果を確認してみる。
class TestBundle extends Bundle { val bool = Bool() val uint = UInt(8.W) val sint = SInt(8.W) } class EgPrefix(usePrefix: Boolean) extends Module { val io = IO(new Bundle { val in = Input(new TestBundle) val out = Output(new TestBundle) }) if (usePrefix) { // prefixに"r_"を指定 io.out := prefix("r") { RegNext(io.in) } } else { io.out := RegNext(io.in) } } object Elaborate extends App { println(new (chisel3.stage.ChiselStage).emitVerilog(new EgPrefix(true))) println(new (chisel3.stage.ChiselStage).emitVerilog(new EgPrefix(false))) }
ここでは次の3種類で比較。
- Chisel 3.3.xで生成したRTL
- Chisel 3.4.0 &&
usePrefix == true
で生成したRTL Chisel 3.4.0 &&
usePrefix == false
で生成したRTLChisel 3.3.xで生成したRTL
module EgPrefix( input clock, input reset, input io_in_bool, input [7:0] io_in_uint, input [7:0] io_in_sint, output io_out_bool, output [7:0] io_out_uint, output [7:0] io_out_sint ); // RegNextの部分が_T_xxxになる reg _T_bool; // @[EgPrefix.scala 23:20] reg [7:0] _T_uint; // @[EgPrefix.scala 23:20] reg [7:0] _T_sint; // @[EgPrefix.scala 23:20] assign io_out_bool = _T_bool; // @[EgPrefix.scala 23:10] assign io_out_uint = _T_uint; // @[EgPrefix.scala 23:10] assign io_out_sint = _T_sint; // @[EgPrefix.scala 23:10] always @(posedge clock) begin _T_bool <= io_in_bool; _T_uint <= io_in_uint; _T_sint <= io_in_sint; end endmodule
usePrefix == false
で生成したRTL
module EgPrefix( input clock, input reset, input io_in_bool, input [7:0] io_in_uint, input [7:0] io_in_sint, output io_out_bool, output [7:0] io_out_uint, output [7:0] io_out_sint ); // Chisel 3.4.0では_TがREGに変化する reg io_out_REG_bool; // @[EgPrefix.scala 23:20] reg [7:0] io_out_REG_uint; // @[EgPrefix.scala 23:20] reg [7:0] io_out_REG_sint; // @[EgPrefix.scala 23:20] assign io_out_bool = io_out_REG_bool; // @[EgPrefix.scala 23:10] assign io_out_uint = io_out_REG_uint; // @[EgPrefix.scala 23:10] assign io_out_sint = io_out_REG_sint; // @[EgPrefix.scala 23:10] always @(posedge clock) begin io_out_REG_bool <= io_in_bool; // @[EgPrefix.scala 23:20] io_out_REG_uint <= io_in_uint; // @[EgPrefix.scala 23:20] io_out_REG_sint <= io_in_sint; // @[EgPrefix.scala 23:20] end endmodule
usePrefix == true
で生成したRTL
module EgPrefix( input clock, input reset, input io_in_bool, input [7:0] io_in_uint, input [7:0] io_in_sint, output io_out_bool, output [7:0] io_out_uint, output [7:0] io_out_sint ); // prefixを使用すると指定したプリフィックス"r_"がついて // r_REGに変化する reg io_out_r_REG_bool; // @[EgPrefix.scala 24:34] reg [7:0] io_out_r_REG_uint; // @[EgPrefix.scala 24:34] reg [7:0] io_out_r_REG_sint; // @[EgPrefix.scala 24:34] assign io_out_bool = io_out_r_REG_bool; // @[EgPrefix.scala 24:10] assign io_out_uint = io_out_r_REG_uint; // @[EgPrefix.scala 24:10] assign io_out_sint = io_out_r_REG_sint; // @[EgPrefix.scala 24:10] always @(posedge clock) begin io_out_r_REG_bool <= io_in_bool; // @[EgPrefix.scala 24:34] io_out_r_REG_uint <= io_in_uint; // @[EgPrefix.scala 24:34] io_out_r_REG_sint <= io_in_sint; // @[EgPrefix.scala 24:34] end endmodule
余談だけど、Chisel 3.4.0からRTLの構成に若干の変更が入ったようで、RTLの可読性を下げている理由の1つであったランダマイズの処理がモジュールの最後に位置するようになった。
先程のRTLを全部載せると次のような感じ。
module EgPrefix( input clock, input reset, input io_in_bool, input [7:0] io_in_uint, input [7:0] io_in_sint, output io_out_bool, output [7:0] io_out_uint, output [7:0] io_out_sint ); `ifdef RANDOMIZE_REG_INIT reg [31:0] _RAND_0; reg [31:0] _RAND_1; reg [31:0] _RAND_2; `endif // RANDOMIZE_REG_INIT reg io_out_REG_bool; // @[EgPrefix.scala 22:22] reg [7:0] io_out_REG_uint; // @[EgPrefix.scala 22:22] reg [7:0] io_out_REG_sint; // @[EgPrefix.scala 22:22] assign io_out_bool = io_out_REG_bool; // @[EgPrefix.scala 22:12] assign io_out_uint = io_out_REG_uint; // @[EgPrefix.scala 22:12] assign io_out_sint = io_out_REG_sint; // @[EgPrefix.scala 22:12] // レジスタの実装が先に来るように変更されている。 always @(posedge clock) begin io_out_REG_bool <= io_in_bool; // @[EgPrefix.scala 22:22] io_out_REG_uint <= io_in_uint; // @[EgPrefix.scala 22:22] io_out_REG_sint <= io_in_sint; // @[EgPrefix.scala 22:22] end // このifdefブロックが最後に来ている // Register and memory initialization `ifdef RANDOMIZE_GARBAGE_ASSIGN `define RANDOMIZE `endif `ifdef RANDOMIZE_INVALID_ASSIGN `define RANDOMIZE `endif `ifdef RANDOMIZE_REG_INIT `define RANDOMIZE `endif `ifdef RANDOMIZE_MEM_INIT `define RANDOMIZE `endif `ifndef RANDOM `define RANDOM $random `endif `ifdef RANDOMIZE_MEM_INIT integer initvar; `endif `ifndef SYNTHESIS `ifdef FIRRTL_BEFORE_INITIAL `FIRRTL_BEFORE_INITIAL `endif initial begin `ifdef RANDOMIZE `ifdef INIT_RANDOM `INIT_RANDOM `endif `ifndef VERILATOR `ifdef RANDOMIZE_DELAY #`RANDOMIZE_DELAY begin end `else #0.002 begin end `endif `endif `ifdef RANDOMIZE_REG_INIT _RAND_0 = {1{`RANDOM}}; io_out_REG_bool = _RAND_0[0:0]; _RAND_1 = {1{`RANDOM}}; io_out_REG_uint = _RAND_1[7:0]; _RAND_2 = {1{`RANDOM}}; io_out_REG_sint = _RAND_2[7:0]; `endif // RANDOMIZE_REG_INIT `endif // RANDOMIZE end // initial `ifdef FIRRTL_AFTER_INITIAL `FIRRTL_AFTER_INITIAL `endif `endif // SYNTHESIS endmodule
このprefix
の使いどころについては、Chisel3.4.0のリリース時に追加されているドキュメントに次のような記載がある。
If the plugin is enabled, these signals could be intermediate values which are consumed by either assertions or when predicates. In these cases, the compiler plugin often can't find a good prefix for the generated intermediate signals. We recommend you manually insert calls to prefix to fix these cases. We did this to Rocket Chip and saw huge benefits!
ということで「プラグインを入れたけど、まだ_T_xx
が残っている場合」にピンポイントでprefix
を指定して使うのが良さそう。
さきほど紹介したIdu
モジュールにも_T_1
といった信号が含まれているので、その信号に対してprefix
を適用して確認してみる。
その該当箇所は次のChselコード中のadd
の(!arith)
// def aluInstDecode(): Unit = { // register aluReg := opcode === "b0110011".U add := (funct3 === "b000".U) && aluReg && (!arith)
この(!arith)
はRTLになると、次のような信号に変換されている。
wire _inst_add_T_2 = ~inst_arith; // @[Idu.scala 258:48]
この(!arith)
をprefix
を使って次のように変更して、RTLを生成した結果は次のように指定したプリフィックスが適用された。
prefix
を適用したコード
def aluInstDecode(): Unit = { // register aluReg := opcode === "b0110011".U add := (funct3 === "b000".U) && aluReg && prefix("NOT_arith") { (!arith) }
- 生成したRTL
wire _inst_add_NOT_arith_T = ~inst_arith; // @[Idu.scala 259:70]
noPrefix もある
逆の処理を行うオブジェクトnoPrefix
もChisel3.4.0で追加された。使い方は次のようにnoPrefix
のブロック式で囲うだけ。
noPrefix { <prefixをつけたくない実装> }
あんまり良い例ではないけど、効果が確認できたのがdirvのCSRの実装部分の次のコード。
// Csrf.scala L.387 // address decode val csrSelBits = Cat(CSR.csrRegAddrs.reverse.map(i => i.asUInt() === inst.getCsr ))
このコードではcsrRegAddrs
に格納されているCSRのアドレスをmap
を使ってinst
に含まれるアドレスと比較している。
このコードからRTLを生成すると、次のようになる。
wire [11:0] _csrSelBits_T = {io_inst_funct7,io_inst_rs2}; // @[Cat.scala 29:58] wire csrSelBits_right_right_right_right = 12'hf14 == _csrSelBits_T; // @[Csrf.scala 387:63] wire csrSelBits_right_right_right_left = 12'hf13 == _csrSelBits_T; // @[Csrf.scala 387:63] wire csrSelBits_right_right_left_right = 12'hf12 == _csrSelBits_T; // @[Csrf.scala 387:63] wire csrSelBits_right_right_left_left = 12'hf11 == _csrSelBits_T; // @[Csrf.scala 387:63] wire csrSelBits_right_left_right_right = 12'h344 == _csrSelBits_T; // @[Csrf.scala 387:63] wire csrSelBits_right_left_right_left = 12'h343 == _csrSelBits_T; // @[Csrf.scala 387:63]
で、このコードに対してnoPrefix
を適用すると、次のように_csrSelBits
がプリフィックスとして扱われて、それが丸々削除される結果となった。
- Chisel
val csrSelBits = noPrefix { Cat(CSR.csrRegAddrs.reverse.map(i => i.asUInt() === inst.getCsr )) }
- RTL
wire [11:0] _T = {io_inst_funct7,io_inst_rs2}; // @[Cat.scala 29:58] wire right_right_right_right = 12'hf14 == _T; // @[Csrf.scala 389:79] wire right_right_right_left = 12'hf13 == _T; // @[Csrf.scala 389:79] wire right_right_left_right = 12'hf12 == _T; // @[Csrf.scala 389:79] wire right_right_left_left = 12'hf11 == _T; // @[Csrf.scala 389:79] wire right_left_right_right = 12'h344 == _T; // @[Csrf.scala 389:79] wire right_left_right_left = 12'h343 == _T; // @[Csrf.scala 389:79]
ということで、Chisel3.4.0の目玉機能と思われる可読性向上のお話でした。