前回の記事ではChisel BootcampはModule3.2の練習問題でRISC-VのISA向けのレジスタファイルを作成した。
今日はModule3.2とModule3.3の間にある幕間の章でChiselの標準ライブラリについて紹介していく。 最初はDecoupledから。
Module 3.2幕間: Chiselの標準ライブラリ
モチベーション
いつもどおりモチベーションを引用。
Chiselは全てを再利用するので、標準ライブラリとして提供するのはRTLの相互運用を促進するインターフェースやハードウェアのブロックとして一般的に使われるものとするのが理にかなっている。
Chiselチートシート
ここで紹介する標準ライブラリの機能は以前にも紹介したChiselのチートシートに載っている。
とりあえずチートシートに載っているものをざっと列挙してみよう。このページでも紹介していくが、Chisel3のAPIへのリンクも貼ってあるのでそちらも一緒に見てみると良いかも。
- Function Block
- Stateless
- Stateful
- Interfaces
- DecoupledIO
- ValidIO :リンクが見当たらない。。。(ReadyValidIOならあるんだけど。。)
- Queue
- Pipe
- Arbiter
Decoupled : 標準のReady-Validインターフェース
まずはDecoupledIO
から。これはChiselが提供している一般的に使用されるインターフェースの一つでデータ転送のためのready-validインターフェースの機能を提供するものだ。
とりあえず使い方は以下の感じらしい。
val myChiselData = UInt(8.W) // or any Chisel data type, such as Bool(), SInt(...), or even custom Bundles val myDecoupled = Decoupled(myChiselData)
実際どういうこと??とも思うので試してみる。
class MyDecoupledIO extends Module { val io = IO(new Bundle { val dcp = Decoupled(UInt(8.W)) }) val ctr = RegInit(2.U(2.W)) val r = RegInit(0.U(8.W)) val valid = (ctr === 0.U) val update = io.dcp.ready && valid when (update) { r := r + 1.U } when (ctr === 0.U) { when (update) { ctr := 2.U } }.otherwise { ctr := ctr - 1.U } io.dcp.valid := valid io.dcp.bits := r }
回路的には何も意味が無い回路となっており、ctr
が0になった際にvalid
が上がって、ready
が入ってくるとctr
とr
が更新されるだけとなる。
とりあえずDecoupled
を使う際に大事なのは、以下の点。
Decoupled
はIOの一種で宣言すると、以下の3つの信号が宣言されるvalid
:bits
が有効であることを示す出力信号ready
: 対向のモジュールがデータを受け取れるかどうかを示す信号bits
: 出力データ
valid
/ready
を使ったデータのやり取りを行う際には、各信号向けにxx_valid
/xx_ready
/xx_data
とか書くよりはこっちを使ったほうが、シンプルに書けるはず。
で、上記のなんの意味もない回路ではあるが実際にRTLを生成すると以下の出力が得られる。
回路の中身よりは、宣言したDecoupled
が
- io_dcp_ready
- io_dcp_valid
- io_dcp_bits
にマッピングされていることが大事。
module decoupled_io( // @[:@3.2] input clock, // @[:@4.4] input reset, // @[:@5.4] input io_dcp_ready, // @[:@6.4] output io_dcp_valid, // @[:@6.4] output [7:0] io_dcp_bits // @[:@6.4] ); reg [1:0] ctr; // @[cmd21.sc 6:22:@8.4] reg [31:0] _RAND_0; reg [7:0] r; // @[cmd21.sc 7:20:@9.4] reg [31:0] _RAND_1; wire valid; // @[cmd21.sc 9:22:@10.4] wire update; // @[cmd21.sc 10:31:@11.4] wire [8:0] _T_17; // @[cmd21.sc 13:16:@13.6] wire [7:0] _T_18; // @[cmd21.sc 13:16:@14.6] wire [7:0] _GEN_0; // @[cmd21.sc 12:19:@12.4] wire [1:0] _GEN_1; // @[cmd21.sc 17:23:@19.6] wire [2:0] _T_23; // @[cmd21.sc 21:20:@24.6] wire [2:0] _T_24; // @[cmd21.sc 21:20:@25.6] wire [1:0] _T_25; // @[cmd21.sc 21:20:@26.6] wire [1:0] _GEN_2; // @[cmd21.sc 16:24:@18.4] assign valid = ctr == 2'h0; // @[cmd21.sc 9:22:@10.4] assign update = io_dcp_ready & valid; // @[cmd21.sc 10:31:@11.4] assign _T_17 = r + 8'h1; // @[cmd21.sc 13:16:@13.6] assign _T_18 = _T_17[7:0]; // @[cmd21.sc 13:16:@14.6] assign _GEN_0 = update ? _T_18 : r; // @[cmd21.sc 12:19:@12.4] assign _GEN_1 = update ? 2'h2 : ctr; // @[cmd21.sc 17:23:@19.6] assign _T_23 = ctr - 2'h1; // @[cmd21.sc 21:20:@24.6] assign _T_24 = $unsigned(_T_23); // @[cmd21.sc 21:20:@25.6] assign _T_25 = _T_24[1:0]; // @[cmd21.sc 21:20:@26.6] assign _GEN_2 = valid ? _GEN_1 : _T_25; // @[cmd21.sc 16:24:@18.4] assign io_dcp_valid = valid; assign io_dcp_bits = r; always @(posedge clock) begin if (reset) begin ctr <= 2'h2; end else begin if (valid) begin if (update) begin ctr <= 2'h2; end end else begin ctr <= _T_25; end end if (reset) begin r <= 8'h0; end else begin if (update) begin r <= _T_18; end end end endmodule
最後に実装時の注意点が3つ書いてあったので、それを載せておく。
ready
とvalid
は互いが組み合わせの論理とならないようにする。さもないと、組み合わせのループによって合成が出来ないという結果になってしまう。- また
ready
は対向がデータを受け取ることが出来るかどうか、valid
はモジュール内に送信するデータがあるかどうか、という点にのみ依存するようにする。- トランザクションが完了したあと(次のクロックサイクル)でデータは更新されるようにする。
ということで今日はここまで。次からは最初に載せた各APIの例を見ていくことにする。