前回に引き続きChiselで作るお試しNICの話。 3回目はArbiter部分について。
NICDecoder
初回に記載したブロック図中のArbiter部分。名前はNICArbiter
にしてます。
ソースコード
まずはソースコードを。
- NICArbiter.scala
// See LICENSE for license details. import chisel3._ import chisel3.util._ /** * NICArbiterのIOクラス * @param numOfInput 入力ポートの数 */ class NICArbiterIO(numOfInput: Int) extends Bundle { val in = Vec(numOfInput, Flipped(Decoupled(new NICBaseData(numOfInput)))) val out = Decoupled(new NICBaseData(numOfInput)) override def cloneType: this.type = new NICArbiterIO(numOfInput).asInstanceOf[this.type] } /** * NICArbiter * @param numOfInput 出力ポートの数 * @param sliceEn 出力ポートの手前にレジスタスライス挿入するかどうか */ class NICArbiter(numOfInput: Int, sliceEn: Boolean) extends Module{ val io = IO(new NICArbiterIO(numOfInput)) val arb = Module(new Arbiter( chiselTypeOf(io.in(0).bits), numOfInput)) val slice = Queue(arb.io.out, 1, !sliceEn, !sliceEn) for ((in_port, arb_in) <- io.in zip arb.io.in) { arb_in <> in_port } io.out <> slice }
Arbiter部分はほぼ書くこと無いな。。。
IO部分
以下のような形になってます。
class NICArbiterIO(numOfInput: Int) extends Bundle { val in = Vec(numOfInput, Flipped(Decoupled(new NICBaseData(numOfInput)))) val out = Decoupled(new NICBaseData(numOfInput)) override def cloneType: this.type = new NICArbiterIO(numOfInput).asInstanceOf[this.type] }
関係性は前回のDecoder部分とはちょうど逆になり"N入力-1出力"になるので入力側をVec
にしています。
Arbiter
調停処理にはChiselのutil
の下にあるArbiter
を用いました。
少し前の記事で解説したchiselTypeOf
を使うことでio.in
のインスタンスから型情報を取得してArbiter
に渡しています。
今回はやっていませんがこのArbiter
の種類を選択出来るようにパラメタライズするのもアリですね。
val arb = Module(new Arbiter( chiselTypeOf(io.in(0).bits), numOfInput))
入力とArbiterの接続
前回のDecoderと同様にN個の入力とArbiterの入力をループで処理しています。
for ((in_port, arb_in) <- io.in zip arb.io.in) {
arb_in <> in_port
}
テスト
あんまり書くこと無いので、実装時に書いたテストもついでに載せておきます。
一応簡単にレジスタスライスなし/ありの確認をしています。
まあそのへんの処理はutil.Arbiter
にお任せなので、つなぎ間違えないよなーくらいの確認という感じです。
// See LICENSE for license details. import scala.util.Random import chisel3._ import chisel3.iotesters._ /** * NICDecoderのユニットテスト用クラス * @param c テストモジュール */ class NICDecoderUnitTester(c: NICDecoder) extends PeekPokeTester(c) { val in = c.io.in val out = c.io.out val r = new Random(1) def idle(cycle: Int = 1): Unit = { poke(in.valid, false) poke(in.bits.dst, 0) poke(in.bits.data, 0) for (out_port <- out) { poke(out_port.ready, true) } step(cycle) } /** * データ送信 * @param dst 出力先のポート番号 * @param data 送信データ */ def sendData(dst: Int, data: BigInt): Unit = { poke(in.valid, true) poke(in.bits.dst, dst) poke(in.bits.data, data) } /** * データの期待値比較 * @param dst 出力先のポート番号 * @param data 送信データ */ def compareData(dst: Int, data: BigInt): Unit = { for ((out_port, idx) <- out.zipWithIndex) { val validExp = if (dst == idx) true else false expect(out_port.valid, validExp) expect(out_port.bits.dst, dst) expect(out_port.bits.data, data) } } } /** * NICDecoderのテストクラス */ class NICDecoderTester extends BaseTester { behavior of "NICDecoder" val testArgs = baseArgs :+ "-tn=NICDecoder" val defaultNumOfOutput = 3 it should "io.in.validをHighにした時のio.dstに従って" + "出力ポートが選択される" in { iotesters.Driver.execute( testArgs :+ "-td=test_run_dir/NICDecoder-000", () => new NICDecoder(defaultNumOfOutput, false)) { c => new NICDecoderUnitTester(c) { idle(10) for (dst <- 0 until defaultNumOfOutput) { val data = intToUnsignedBigInt(r.nextInt()) sendData(dst, data) compareData(dst, data) step(1) poke(in.valid, false) step(1) idle(10) } } } should be (true) } it should "選択した出力先のout(N).io.readyがLowになると" + "io.in.readyもLowになる" in { iotesters.Driver.execute( testArgs :+ s"-td=test_run_dir/NICDecoder-001", () => new NICDecoder(defaultNumOfOutput, false)) { c => new NICDecoderUnitTester(c) { idle(10) for (dst <- 0 until defaultNumOfOutput) { val data = intToUnsignedBigInt(r.nextInt()) // 選択したポートのみreadyをfalseに for (idx <- 0 until defaultNumOfOutput) { val v = if (dst == idx) false else true println(s"$v") poke(out(idx).ready, v) } sendData(dst, data) // データがqueueに格納されるため、このサイクル // ではreadyはtrue expect(in.ready, true) step(1) poke(in.valid, false) compareData(dst, data) expect(in.ready, false) step(1) idle(10) } } } should be (true) } it should "io.in.validをHighにした時のio.dstに従って出力ポート" + "が選択され、1cycle後にvalidがHighになる" in { iotesters.Driver.execute( testArgs :+ "-td=test_run_dir/NICDecoder-100", () => new NICDecoder(defaultNumOfOutput, true)) { c => new NICDecoderUnitTester(c) { idle(10) for (dst <- 0 until defaultNumOfOutput) { val data = intToUnsignedBigInt(r.nextInt()) sendData(dst, data) step(1) poke(in.valid, false) compareData(dst, data) step(1) idle(10) } } } should be (true) } it should "選択した出力先のout(N).io.readyがLowになると" + "io.in.readyが1cycle後にLowになる" in { iotesters.Driver.execute( testArgs :+ s"-td=test_run_dir/NICDecoder-101", () => new NICDecoder(defaultNumOfOutput, true)) { c => new NICDecoderUnitTester(c) { idle(10) for (dst <- 0 until defaultNumOfOutput) { val data = intToUnsignedBigInt(r.nextInt()) // 選択したポートのみreadyをfalseに for (idx <- 0 until defaultNumOfOutput) { val v = if (dst == idx) false else true println(s"$v") poke(out(idx).ready, v) } sendData(dst, data) step(1) compareData(dst, data) poke(in.valid, false) expect(in.ready, false) step(1) idle(10) } } } should be (true) } }
NICDecoderの動作時の波形
もう解説することが無いので、動作を確認してお終いにします。 調停処理のモジュールなので、波形は各入力がぶつかった場合のものにしました。
レジスタスライスなしの場合
レジスタスライスありの場合
スライスを入れた関係でready-validが伝わるのが遅れるため、出力側の挙動が変化しているのがわかると思います。
文中で言及したchiselTypeOf
の動作に関する記事は以下です。興味があればご覧ください。