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

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

ChiselのDecoupledIOの使い方を考えなおした話

スポンサーリンク

ChiselのQueueを使って設計をしていて、少し考えたことがあったので今日はそれについて。 ただ単にDecoupledIOをどう使うか、、、という話。

Queueの実装例

幾つかのデータを纏めてChiselのQueueに入れて受け渡したいときにはDecoupledIO等のReadyValidIOの派生クラスを使って以下のように記述したりする。

import chisel3._
import chisel3.util._

/**
 * Queueに入れたいデータを纏めた型
 */
class BundleA extends Bundle {
  val a = Bool()
  val b = UInt(8.W)
}

/**
 * MyQueueモジュールのIOクラス
 */
class MyQueueIO extends Bundle {
  val enq = Flipped(DecoupledIO(new BundleA))
  val deq = DecoupledIO(new BundleA)
}

/**
 * MyQueue
 * 単にChiselのQueueをインスタンスして繋いだだけ
 */
class MyQueue extends Module {
  val io = IO(new MyQueueIO)

  // object chisel3.util.Queueをインスタンス
  //  -> object Queueを使った場合はio.enqがQueueのenqに接続される
  val m_q = Queue(io.enq, 1)

  // objectを使った場合はm_qがdeqになっている
  io.deq <> m_q
}

上記をエラボレートすると、以下のようにChiselのQueueから生成されたモジュールがRTLにインスタンスされる。

module MyQueue( // @[:@46.2]
  input        clock, // @[:@47.4]
  input        reset, // @[:@48.4]
  output       io_enq_ready, // @[:@49.4]
  input        io_enq_valid, // @[:@49.4]
  input        io_enq_bits_a, // @[:@49.4]
  input  [7:0] io_enq_bits_b, // @[:@49.4]
  input        io_deq_ready, // @[:@49.4]
  output       io_deq_valid, // @[:@49.4]
  output       io_deq_bits_a, // @[:@49.4]
  output [7:0] io_deq_bits_b // @[:@49.4]
);
  wire  m_q_clock; // @[Decoupled.scala 294:21:@51.4]
  wire  m_q_reset; // @[Decoupled.scala 294:21:@51.4]
  wire  m_q_io_enq_ready; // @[Decoupled.scala 294:21:@51.4]
  wire  m_q_io_enq_valid; // @[Decoupled.scala 294:21:@51.4]
  wire  m_q_io_enq_bits_a; // @[Decoupled.scala 294:21:@51.4]
  wire [7:0] m_q_io_enq_bits_b; // @[Decoupled.scala 294:21:@51.4]
  wire  m_q_io_deq_ready; // @[Decoupled.scala 294:21:@51.4]
  wire  m_q_io_deq_valid; // @[Decoupled.scala 294:21:@51.4]
  wire  m_q_io_deq_bits_a; // @[Decoupled.scala 294:21:@51.4]
  wire [7:0] m_q_io_deq_bits_b; // @[Decoupled.scala 294:21:@51.4]
  Queue m_q ( // @[Decoupled.scala 294:21:@51.4]
    .clock(m_q_clock),
    .reset(m_q_reset),
    .io_enq_ready(m_q_io_enq_ready),
    .io_enq_valid(m_q_io_enq_valid),
    .io_enq_bits_a(m_q_io_enq_bits_a),
    .io_enq_bits_b(m_q_io_enq_bits_b),
    .io_deq_ready(m_q_io_deq_ready),
    .io_deq_valid(m_q_io_deq_valid),
    .io_deq_bits_a(m_q_io_deq_bits_a),
    .io_deq_bits_b(m_q_io_deq_bits_b)
  );
  assign io_enq_ready = m_q_io_enq_ready; // @[Decoupled.scala 297:17:@57.4]
  assign io_deq_valid = m_q_io_deq_valid; // @[MyQueue.scala 81:10:@60.4]
  assign io_deq_bits_a = m_q_io_deq_bits_a; // @[MyQueue.scala 81:10:@59.4]
  assign io_deq_bits_b = m_q_io_deq_bits_b; // @[MyQueue.scala 81:10:@58.4]
  assign m_q_clock = clock; // @[:@52.4]
  assign m_q_reset = reset; // @[:@53.4]
  assign m_q_io_enq_valid = io_enq_valid; // @[Decoupled.scala 295:22:@54.4]
  assign m_q_io_enq_bits_a = io_enq_bits_a; // @[Decoupled.scala 296:21:@56.4]
  assign m_q_io_enq_bits_b = io_enq_bits_b; // @[Decoupled.scala 296:21:@55.4]
  assign m_q_io_deq_ready = io_deq_ready; // @[MyQueue.scala 81:10:@61.4]
endmodule

IOの形を書き換えてみる

上記のように書いていて「DecoupledIOが野暮ったいな」って思って、書き換えて以下の形で多少の違和感を覚えながらもこれを使っていた。

class BundleA extends Bundle {
  val a = Bool()
  val b = UInt(8.W)
}

class DecoupledBundleA extends Bundle {
  val a = DecoupledIO(new BundleA)
}


class MyQueueIO extends Bundle {
  val enq = Flipped(new DecoupledBundleA)
  val deq = new DecoupledBundleA
}

class MyQueue extends Module {
  val io = IO(new MyQueueIO)

  val m_q = Queue(io.enq.a, 1)

  io.deq.a <> m_q
}
  • 生成されたRTL
module MyQueue( // @[:@46.2]
  input        clock, // @[:@47.4]
  input        reset, // @[:@48.4]
  output       io_enq_a_ready, // @[:@49.4]
  input        io_enq_a_valid, // @[:@49.4]
  input        io_enq_a_bits_a, // @[:@49.4]
  input  [7:0] io_enq_a_bits_b, // @[:@49.4]
  input        io_deq_a_ready, // @[:@49.4]
  output       io_deq_a_valid, // @[:@49.4]
  output       io_deq_a_bits_a, // @[:@49.4]
  output [7:0] io_deq_a_bits_b // @[:@49.4]
);
  wire  m_q_clock; // @[Decoupled.scala 294:21:@51.4]
  wire  m_q_reset; // @[Decoupled.scala 294:21:@51.4]
  wire  m_q_io_enq_ready; // @[Decoupled.scala 294:21:@51.4]
  wire  m_q_io_enq_valid; // @[Decoupled.scala 294:21:@51.4]
  wire  m_q_io_enq_bits_a; // @[Decoupled.scala 294:21:@51.4]
  wire [7:0] m_q_io_enq_bits_b; // @[Decoupled.scala 294:21:@51.4]
  wire  m_q_io_deq_ready; // @[Decoupled.scala 294:21:@51.4]
  wire  m_q_io_deq_valid; // @[Decoupled.scala 294:21:@51.4]
  wire  m_q_io_deq_bits_a; // @[Decoupled.scala 294:21:@51.4]
  wire [7:0] m_q_io_deq_bits_b; // @[Decoupled.scala 294:21:@51.4]
  Queue m_q ( // @[Decoupled.scala 294:21:@51.4]
    .clock(m_q_clock),
    .reset(m_q_reset),
    .io_enq_ready(m_q_io_enq_ready),
    .io_enq_valid(m_q_io_enq_valid),
    .io_enq_bits_a(m_q_io_enq_bits_a),
    .io_enq_bits_b(m_q_io_enq_bits_b),
    .io_deq_ready(m_q_io_deq_ready),
    .io_deq_valid(m_q_io_deq_valid),
    .io_deq_bits_a(m_q_io_deq_bits_a),
    .io_deq_bits_b(m_q_io_deq_bits_b)
  );
  assign io_enq_a_ready = m_q_io_enq_ready; // @[Decoupled.scala 297:17:@57.4]
  assign io_deq_a_valid = m_q_io_deq_valid; // @[MyQueue.scala 86:12:@60.4]
  assign io_deq_a_bits_a = m_q_io_deq_bits_a; // @[MyQueue.scala 86:12:@59.4]
  assign io_deq_a_bits_b = m_q_io_deq_bits_b; // @[MyQueue.scala 86:12:@58.4]
  assign m_q_clock = clock; // @[:@52.4]
  assign m_q_reset = reset; // @[:@53.4]
  assign m_q_io_enq_valid = io_enq_a_valid; // @[Decoupled.scala 295:22:@54.4]
  assign m_q_io_enq_bits_a = io_enq_a_bits_a; // @[Decoupled.scala 296:21:@56.4]
  assign m_q_io_enq_bits_b = io_enq_a_bits_b; // @[Decoupled.scala 296:21:@55.4]
  assign m_q_io_deq_ready = io_deq_a_ready; // @[MyQueue.scala 86:12:@61.4]
endmodule

感じていた違和感というのは、Chiselのソースでio.enq.aという風にアクセスをしないと行けない部分。 これはそのままRTLをにもio_enq_aという形で現れていた。

継承してスッキリ

ふと「継承すればいいんじゃ( ゚д゚)ハッ!」って思って試してみた。 先程のDecoupledBundleAを以下のように継承する形に変更する。変数aがいなくなるのでそれに従ってMyQueueも最初の形に戻す。

class DecoupledBundleA extends DecoupledIO(new BundleA) {
  override def cloneType: this.type = new DecoupledBundleA().asInstanceOf[this.type]
}

class MyQueue extends Module {
  val io = IO(new MyQueueIO)

  val m_q = Queue(io.enq, 1)

  io.deq <> m_q
}
  • 生成されたRTL
module MyQueue( // @[:@46.2]
  input        clock, // @[:@47.4]
  input        reset, // @[:@48.4]
  output       io_enq_ready, // @[:@49.4]
  input        io_enq_valid, // @[:@49.4]
  input        io_enq_bits_a, // @[:@49.4]
  input  [7:0] io_enq_bits_b, // @[:@49.4]
  input        io_deq_ready, // @[:@49.4]
  output       io_deq_valid, // @[:@49.4]
  output       io_deq_bits_a, // @[:@49.4]
  output [7:0] io_deq_bits_b // @[:@49.4]
);
  wire  m_q_clock; // @[Decoupled.scala 294:21:@51.4]
  wire  m_q_reset; // @[Decoupled.scala 294:21:@51.4]
  wire  m_q_io_enq_ready; // @[Decoupled.scala 294:21:@51.4]
  wire  m_q_io_enq_valid; // @[Decoupled.scala 294:21:@51.4]
  wire  m_q_io_enq_bits_a; // @[Decoupled.scala 294:21:@51.4]
  wire [7:0] m_q_io_enq_bits_b; // @[Decoupled.scala 294:21:@51.4]
  wire  m_q_io_deq_ready; // @[Decoupled.scala 294:21:@51.4]
  wire  m_q_io_deq_valid; // @[Decoupled.scala 294:21:@51.4]
  wire  m_q_io_deq_bits_a; // @[Decoupled.scala 294:21:@51.4]
  wire [7:0] m_q_io_deq_bits_b; // @[Decoupled.scala 294:21:@51.4]
  Queue m_q ( // @[Decoupled.scala 294:21:@51.4]
    .clock(m_q_clock),
    .reset(m_q_reset),
    .io_enq_ready(m_q_io_enq_ready),
    .io_enq_valid(m_q_io_enq_valid),
    .io_enq_bits_a(m_q_io_enq_bits_a),
    .io_enq_bits_b(m_q_io_enq_bits_b),
    .io_deq_ready(m_q_io_deq_ready),
    .io_deq_valid(m_q_io_deq_valid),
    .io_deq_bits_a(m_q_io_deq_bits_a),
    .io_deq_bits_b(m_q_io_deq_bits_b)
  );
  assign io_enq_ready = m_q_io_enq_ready; // @[Decoupled.scala 297:17:@57.4]
  assign io_deq_valid = m_q_io_deq_valid; // @[MyQueue.scala 88:10:@60.4]
  assign io_deq_bits_a = m_q_io_deq_bits_a; // @[MyQueue.scala 88:10:@59.4]
  assign io_deq_bits_b = m_q_io_deq_bits_b; // @[MyQueue.scala 88:10:@58.4]
  assign m_q_clock = clock; // @[:@52.4]
  assign m_q_reset = reset; // @[:@53.4]
  assign m_q_io_enq_valid = io_enq_valid; // @[Decoupled.scala 295:22:@54.4]
  assign m_q_io_enq_bits_a = io_enq_bits_a; // @[Decoupled.scala 296:21:@56.4]
  assign m_q_io_enq_bits_b = io_enq_bits_b; // @[Decoupled.scala 296:21:@55.4]
  assign m_q_io_deq_ready = io_deq_ready; // @[MyQueue.scala 88:10:@61.4]
endmodule

最初の場合と同様のRTLが生成された!

コンパニオンオブジェクト版

更に「コンパニオンオブジェクト使って定義する方がいいかも」と思ったのでそちらのケースも載せておく。 RTLは全く同じなので割愛。

class BundleA extends Bundle {
  val a = Bool()
  val b = UInt(8.W)
}

object BundleA {
  def apply(): DecoupledIO[BundleA] = DecoupledIO(new BundleA())
}

class MyQueueIO extends Bundle {
  val enq = Flipped(BundleA())
  val deq = BundleA()
}

class MyQueue extends Module {
  val io = IO(new MyQueueIO)

  val m_q = Queue(io.enq, 1)

  io.deq <> m_q
}

個人的にはコンパニオンオブジェクトの方がいいかなーという感じ。 今まで考えが及ばずあんまりobjectを上手く活用できていないので、意識して使っていきたいと思う。