ハードウェアの気になるあれこれ

技術的に興味のあることを調べて書いてくブログ。主にハードウェアがネタ。

Chiselで作るNIC - (3)- Arbiter

スポンサーリンク

前回に引き続きChiselで作るお試しNICの話。 3回目はArbiter部分について。

NICDecoder

初回に記載したブロック図中のArbiter部分。名前はNICArbiterにしてます。

ソースコード

まずはソースコードを。

// 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の動作時の波形

もう解説することが無いので、動作を確認してお終いにします。 調停処理のモジュールなので、波形は各入力がぶつかった場合のものにしました。

レジスタスライスなしの場合

f:id:diningyo-kpuku-jougeki:20190723223928p:plain
レジスタスライス無しの場合の波形

レジスタスライスありの場合

スライスを入れた関係でready-validが伝わるのが遅れるため、出力側の挙動が変化しているのがわかると思います。

f:id:diningyo-kpuku-jougeki:20190723223956p:plain
レジスタスライス有りの場合の波形

文中で言及したchiselTypeOfの動作に関する記事は以下です。興味があればご覧ください。