前回の記事ではChiselの標準ライブラリからQueue
を紹介した。
今日も引き続きChiselの標準ライブラリを紹介していく。今日はChiselのアービターだ。
Module 3.2幕間: Chiselの標準ライブラリ
アービター
Chiselの標準ライブラリには2つのアービターが存在する。いずれのアービターも各々のモジュールの優先順位にした以外N個のDecoupledIO
の入力から選択された一つのDecoupledIO
を出力する。
なお標準ライブラリのアービターはいずれも組み合わせ回路で構成されることに注意が必要だ。
サンプルコード
早速サンプルコードを見ていこう。
Driver(() => new Module { // Example circuit using a priority arbiter val io = IO(new Bundle { val in = Flipped(Vec(2, Decoupled(UInt(8.W)))) val out = Decoupled(UInt(8.W)) }) // Arbiter doesn't have a convenience constructor, so it's built like any Module val arbiter = Module(new Arbiter(UInt(8.W), 2)) // 2 to 1 Priority Arbiter arbiter.io.in <> io.in io.out <> arbiter.io.out }) { c => new PeekPokeTester(c) { poke(c.io.in(0).valid, 0) poke(c.io.in(1).valid, 0) println(s"Start:") println(s"\tin(0).ready=${peek(c.io.in(0).ready)}, in(1).ready=${peek(c.io.in(1).ready)}") println(s"\tout.valid=${peek(c.io.out.valid)}, out.bits=${peek(c.io.out.bits)}") poke(c.io.in(1).valid, 1) // Valid input 1 poke(c.io.in(1).bits, 42) // What do you think the output will be? println(s"valid input 1:") println(s"\tin(0).ready=${peek(c.io.in(0).ready)}, in(1).ready=${peek(c.io.in(1).ready)}") println(s"\tout.valid=${peek(c.io.out.valid)}, out.bits=${peek(c.io.out.bits)}") poke(c.io.in(0).valid, 1) // Valid inputs 0 and 1 poke(c.io.in(0).bits, 43) // What do you think the output will be? Which inputs will be ready? println(s"valid inputs 0 and 1:") println(s"\tin(0).ready=${peek(c.io.in(0).ready)}, in(1).ready=${peek(c.io.in(1).ready)}") println(s"\tout.valid=${peek(c.io.out.valid)}, out.bits=${peek(c.io.out.bits)}") poke(c.io.in(1).valid, 0) // Valid input 0 // What do you think the output will be? println(s"valid input 0:") println(s"\tin(0).ready=${peek(c.io.in(0).ready)}, in(1).ready=${peek(c.io.in(1).ready)}") println(s"\tout.valid=${peek(c.io.out.valid)}, out.bits=${peek(c.io.out.bits)}") } }
前回のQueue
と同じくテスターにモジュールの実装を直接渡している。このコードを実行すると(当然だが)テストにパスする結果が得られる。テスターに渡したアービターは2つの入力のDecoupledIO
を持っており、各々のvalid
端子を0x1にすることで転送の要求を行う。上記のテストコードではvalid
を立てた際にどちらのDecoupledIO
の値が出力されるかをチェックしている。
[info] [0.001] Elaborating design... [info] [0.708] Done elaborating. Total FIRRTL Compile Time: 230.5 ms Total FIRRTL Compile Time: 39.3 ms End of dependency graph Circuit state created [info] [0.001] SEED 1544881672950 [info] [0.002] Start: [info] [0.003] in(0).ready=1, in(1).ready=1 [info] [0.004] out.valid=0, out.bits=117 [info] [0.005] valid input 1: [info] [0.006] in(0).ready=1, in(1).ready=1 [info] [0.007] out.valid=1, out.bits=42 [info] [0.007] valid inputs 0 and 1: [info] [0.008] in(0).ready=1, in(1).ready=0 [info] [0.009] out.valid=1, out.bits=43 [info] [0.010] valid input 0: [info] [0.011] in(0).ready=1, in(1).ready=0 [info] [0.011] out.valid=1, out.bits=43 test cmd2Helperanonfun1anon2 Success: 0 tests passed in 5 cycles taking 0.025763 seconds [info] [0.013] RAN 0 CYCLES PASSED
最初に書いたとおり、Arbiter
は組み合わせ論理で構成されるため、テストの実行結果の最後にあるようにサイクルが経過していないことがわかる。
使い方
以下が最初のサンプルコードからモジュールの定義部分のみを抜粋したものだ。
new Module { // Example circuit using a priority arbiter val io = IO(new Bundle { val in = Flipped(Vec(2, Decoupled(UInt(8.W)))) val out = Decoupled(UInt(8.W)) }) // Arbiter doesn't have a convenience constructor, so it's built like any Module val arbiter = Module(new Arbiter(UInt(8.W), 2)) // 2 to 1 Priority Arbiter arbiter.io.in <> io.in io.out <> arbiter.io.out }
使用するときは
となる。
Arbiter
から生成されるRTL
確認のために上記のテストに入力したモジュールから生成されるRTLを見ておこう。
生成されるモジュールは以下の2つだ。
- Arbiter : Chiselの標準ライブラリ
Aribter
から生成されたアービター。 - cmd4Helperanonfun1anon2 :
Arbiter
をインスタンスしているラッパー階層のモジュール
必要なところには適宜コメントを入れたつもりなので、そちらも一緒に見てほしい。
module Arbiter( // @[:@3.2] output io_in_0_ready, // @[:@6.4] input io_in_0_valid, // @[:@6.4] input [7:0] io_in_0_bits, // @[:@6.4] output io_in_1_ready, // @[:@6.4] input io_in_1_valid, // @[:@6.4] input [7:0] io_in_1_bits, // @[:@6.4] input io_out_ready, // @[:@6.4] output io_out_valid, // @[:@6.4] output [7:0] io_out_bits // @[:@6.4] ); wire [7:0] _GEN_1; // @[Arbiter.scala 121:27:@10.4] wire _T_70; // @[Arbiter.scala 31:78:@14.4] wire _T_72; // @[Arbiter.scala 129:19:@17.4] wire _T_74; // @[Arbiter.scala 130:19:@19.4] wire _T_75; // @[Arbiter.scala 130:31:@20.4] // 出力のデータ選択部分 - インデックスの小さい"0"が優先される assign _GEN_1 = io_in_0_valid ? io_in_0_bits : io_in_1_bits; // @[Arbiter.scala 121:27:@10.4] // io_in_1_validの生成 assign _T_70 = io_in_0_valid == 1'h0; // @[Arbiter.scala 31:78:@14.4] assign _T_72 = _T_70 & io_out_ready; // @[Arbiter.scala 129:19:@17.4] assign _T_74 = _T_70 == 1'h0; // @[Arbiter.scala 130:19:@19.4] // io_out_validの生成 - _T_70 == 1'h0なのでio_in_valid == 1'h1 assign _T_75 = _T_74 | io_in_1_valid; // @[Arbiter.scala 130:31:@20.4] assign io_in_0_ready = io_out_ready; assign io_in_1_ready = _T_72; assign io_out_valid = _T_75; assign io_out_bits = _GEN_1; endmodule module cmd4Helperanonfun1anon2( // @[:@23.2] input clock, // @[:@24.4] input reset, // @[:@25.4] output io_in_0_ready, // @[:@26.4] input io_in_0_valid, // @[:@26.4] input [7:0] io_in_0_bits, // @[:@26.4] output io_in_1_ready, // @[:@26.4] input io_in_1_valid, // @[:@26.4] input [7:0] io_in_1_bits, // @[:@26.4] input io_out_ready, // @[:@26.4] output io_out_valid, // @[:@26.4] output [7:0] io_out_bits // @[:@26.4] ); wire Arbiter_io_in_0_ready; // @[cmd4.sc 8:25:@28.4] wire Arbiter_io_in_0_valid; // @[cmd4.sc 8:25:@28.4] wire [7:0] Arbiter_io_in_0_bits; // @[cmd4.sc 8:25:@28.4] wire Arbiter_io_in_1_ready; // @[cmd4.sc 8:25:@28.4] wire Arbiter_io_in_1_valid; // @[cmd4.sc 8:25:@28.4] wire [7:0] Arbiter_io_in_1_bits; // @[cmd4.sc 8:25:@28.4] wire Arbiter_io_out_ready; // @[cmd4.sc 8:25:@28.4] wire Arbiter_io_out_valid; // @[cmd4.sc 8:25:@28.4] wire [7:0] Arbiter_io_out_bits; // @[cmd4.sc 8:25:@28.4] // newで宣言したArbiterがインスタンされる Arbiter Arbiter ( // @[cmd4.sc 8:25:@28.4] .io_in_0_ready(Arbiter_io_in_0_ready), .io_in_0_valid(Arbiter_io_in_0_valid), .io_in_0_bits(Arbiter_io_in_0_bits), .io_in_1_ready(Arbiter_io_in_1_ready), .io_in_1_valid(Arbiter_io_in_1_valid), .io_in_1_bits(Arbiter_io_in_1_bits), .io_out_ready(Arbiter_io_out_ready), .io_out_valid(Arbiter_io_out_valid), .io_out_bits(Arbiter_io_out_bits) ); // "<>"で接続したとおりにRTL上で結線される。 assign io_in_0_ready = Arbiter_io_in_0_ready; assign io_in_1_ready = Arbiter_io_in_1_ready; assign io_out_valid = Arbiter_io_out_valid; assign io_out_bits = Arbiter_io_out_bits; assign Arbiter_io_in_0_valid = io_in_0_valid; assign Arbiter_io_in_0_bits = io_in_0_bits; assign Arbiter_io_in_1_valid = io_in_1_valid; assign Arbiter_io_in_1_bits = io_in_1_bits; assign Arbiter_io_out_ready = io_out_ready; endmodule
Arbiter
とRRArbiter
を使ったサンプル
ただ単に例を見るだけというのも味気ない&RRArbiter
を使ってないので、両方のアービターを使って優先順位の選択を切り替え可能にしたアービター・ジェネレータを作ってみる。
class MyArbiter(InputNum: Int, DataWidth: Int, Type: Int = 0) extends Module { val io = IO(new Bundle { val in = Flipped(Vec(InputNum, Decoupled(UInt(DataWidth.W)))) val out = Decoupled(UInt(DataWidth.W)) }) var arb = Module if (Type == 1) { val arb = Module(new Arbiter(UInt(DataWidth.W), InputNum)) arb.io.in <> io.in io.out <> arb.io.out } else { val arb = Module(new RRArbiter(UInt(DataWidth.W), InputNum)) arb.io.in <> io.in io.out <> arb.io.out } }
一応、所望の機能にはなるんだけど、これだとarb
の結線部分が野暮ったいんだよな。。。なんか方法はあるんだろうけどまだよくわからん。。。あとで調べることにしよう。
因みに上記でRRArbiter
を有効にしてRTLを生成すると、以下の様に直前に選択された入力を保持するようなRTLが生成されていた。
module RRArbiter( // @[:@3.2] input clock, // @[:@4.4] output io_in_0_ready, // @[:@6.4] input io_in_0_valid, // @[:@6.4] input [7:0] io_in_0_bits, // @[:@6.4] output io_in_1_ready, // @[:@6.4] input io_in_1_valid, // @[:@6.4] input [7:0] io_in_1_bits, // @[:@6.4] input io_out_ready, // @[:@6.4] output io_out_valid, // @[:@6.4] output [7:0] io_out_bits, // @[:@6.4] output io_chosen // @[:@6.4] ); reg lastGrant; // @[Reg.scala 11:16:@14.4] reg [31:0] _RAND_0; wire _GEN_4; // @[Arbiter.scala 41:16:@11.4] wire [7:0] _GEN_5; // @[Arbiter.scala 41:16:@11.4] wire _T_79; // @[Decoupled.scala 37:37:@13.4] wire _GEN_6; // @[Reg.scala 12:19:@15.4] wire grantMask_1; // @[Arbiter.scala 67:57:@19.4] wire validMask_1; // @[Arbiter.scala 68:83:@21.4] wire _T_84; // @[Arbiter.scala 31:68:@23.4] wire _T_88; // @[Arbiter.scala 31:78:@25.4] wire _T_90; // @[Arbiter.scala 31:78:@26.4] wire _T_94; // @[Arbiter.scala 72:50:@30.4] wire _T_95; // @[Arbiter.scala 60:21:@31.4] wire _T_96; // @[Arbiter.scala 60:21:@33.4] wire _GEN_7; // @[Arbiter.scala 77:27:@35.4] wire choice; // @[Arbiter.scala 79:25:@38.4] assign _GEN_4 = io_chosen ? io_in_1_valid : io_in_0_valid; // @[Arbiter.scala 41:16:@11.4] assign _GEN_5 = io_chosen ? io_in_1_bits : io_in_0_bits; // @[Arbiter.scala 41:16:@11.4] assign _T_79 = io_out_ready & io_out_valid; // @[Decoupled.scala 37:37:@13.4] assign _GEN_6 = _T_79 ? io_chosen : lastGrant; // @[Reg.scala 12:19:@15.4] assign grantMask_1 = 1'h1 > lastGrant; // @[Arbiter.scala 67:57:@19.4] assign validMask_1 = io_in_1_valid & grantMask_1; // @[Arbiter.scala 68:83:@21.4] assign _T_84 = validMask_1 | io_in_0_valid; // @[Arbiter.scala 31:68:@23.4] assign _T_88 = validMask_1 == 1'h0; // @[Arbiter.scala 31:78:@25.4] assign _T_90 = _T_84 == 1'h0; // @[Arbiter.scala 31:78:@26.4] assign _T_94 = grantMask_1 | _T_90; // @[Arbiter.scala 72:50:@30.4] assign _T_95 = _T_88 & io_out_ready; // @[Arbiter.scala 60:21:@31.4] assign _T_96 = _T_94 & io_out_ready; // @[Arbiter.scala 60:21:@33.4] assign _GEN_7 = io_in_0_valid ? 1'h0 : 1'h1; // @[Arbiter.scala 77:27:@35.4] assign choice = validMask_1 ? 1'h1 : _GEN_7; // @[Arbiter.scala 79:25:@38.4] assign io_in_0_ready = _T_95; assign io_in_1_ready = _T_96; assign io_out_valid = _GEN_4; assign io_out_bits = _GEN_5; assign io_chosen = choice; // 最後に選択された入力を保持 always @(posedge clock) begin if (_T_79) begin lastGrant <= io_chosen; end end endmodule
後で調べることは残ってるんだけど、Arbiter
の紹介としてはこれでおしまい。
次回は”ステートレス”な機能からビット単位の操作を行うユーティリティを紹介する予定。