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

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

Chisel Bootcamp - 幕間(6) - マルチプレクサ(PriorityMuxとMux1H)

スポンサーリンク

前回の記事ではChiselの標準ライブラリに含まれているOneHotエンコードに関するブロックを紹介した。

www.tech-diningyo.info

今日はChiselのライブラリに用意されている2つのマルチプレクサについて見ていく。

Module 3.2幕間: Chiselの標準ライブラリ

マルチプレクサ

Chiselには以下の2つのマルチプレクサが存在している。

  • PriorityMux : 選択用信号の一番小さいインデックスが出力される
  • Mux1H : 選択信号がOneHotな信号となることが保証出来る場合に使う。OneHotが前提なのでその分生成される回路は効率のいいものになる。

PriorityMux

早速サンプルを見ていこう。

Driver(() => new Module {
    // Example circuit using PriorityMux
    val io = IO(new Bundle {
      val in_sels = Input(Vec(2, Bool()))
      val in_bits = Input(Vec(2, UInt(8.W)))
      val out = Output(UInt(8.W))
    })
    io.out := PriorityMux(io.in_sels, io.in_bits)
  }) { c => new PeekPokeTester(c) {
    poke(c.io.in_bits(0), 10)
    poke(c.io.in_bits(1), 20)
  
    // Select higher index only
    poke(c.io.in_sels(0), 0)
    poke(c.io.in_sels(1), 1)
    println(s"in_sels=${peek(c.io.in_sels)}, out=${peek(c.io.out)}")
  
    // Select both - arbitration needed
    poke(c.io.in_sels(0), 1)
    poke(c.io.in_sels(1), 1)
    println(s"in_sels=${peek(c.io.in_sels)}, out=${peek(c.io.out)}")
  
    // Select lower index only
    poke(c.io.in_sels(0), 1)
    poke(c.io.in_sels(1), 0)
    println(s"in_sels=${peek(c.io.in_sels)}, out=${peek(c.io.out)}")
} }

使い方としては、以下の様に選択用のビットマップ信号とそれに対応するVecを入れるのが基本的なものとなる。

PriorityMux(io.in_sels, io.in_bits)

今回のケースではVec[Bool]が選択用の2bitの信号で、Vec[UInt]が入力のデータとなっている。

このサンプルを実行すると以下の出力が得られる。

[info] [0.001] Elaborating design...
[info] [0.671] Done elaborating.
Total FIRRTL Compile Time: 190.7 ms
Total FIRRTL Compile Time: 10.8 ms
End of dependency graph
Circuit state created
[info] [0.001] SEED 1545573966896
[info] [0.003] in_sels=Vector(0, 1), out=20
[info] [0.004] in_sels=Vector(1, 1), out=10
[info] [0.005] in_sels=Vector(1, 0), out=10
test cmd2Helperanonfun1anon2 Success: 0 tests passed in 5 cycles taking 0.022209 seconds
[info] [0.008] RAN 0 CYCLES PASSED

ここではVec[Bool]を使っているので2番めのテストケースでは

  • Vec[0] = 1'b1
  • Vec[1] = 1'b1

となるがインデックスの小さいほうが優先されるため、出力がout=20となっている。

生成されるRTL

一応RTLも確認。

module cmd3Helperanonfun1anon2( // @[:@3.2]
  input        clock, // @[:@4.4]
  input        reset, // @[:@5.4]
  input  [7:0] io_in, // @[:@6.4]
  output [7:0] io_out // @[:@6.4]
);
  wire [3:0] _T_13; // @[Bitwise.scala 103:21:@10.4]
  wire [7:0] _T_14; // @[Bitwise.scala 103:31:@11.4]
  wire [3:0] _T_15; // @[Bitwise.scala 103:46:@12.4]
  wire [7:0] _GEN_0; // @[Bitwise.scala 103:65:@13.4]
  wire [7:0] _T_16; // @[Bitwise.scala 103:65:@13.4]
  wire [7:0] _T_18; // @[Bitwise.scala 103:75:@15.4]
  wire [7:0] _T_19; // @[Bitwise.scala 103:39:@16.4]
  wire [5:0] _T_23; // @[Bitwise.scala 103:21:@20.4]
  wire [7:0] _GEN_1; // @[Bitwise.scala 103:31:@21.4]
  wire [7:0] _T_24; // @[Bitwise.scala 103:31:@21.4]
  wire [5:0] _T_25; // @[Bitwise.scala 103:46:@22.4]
  wire [7:0] _GEN_2; // @[Bitwise.scala 103:65:@23.4]
  wire [7:0] _T_26; // @[Bitwise.scala 103:65:@23.4]
  wire [7:0] _T_28; // @[Bitwise.scala 103:75:@25.4]
  wire [7:0] _T_29; // @[Bitwise.scala 103:39:@26.4]
  wire [6:0] _T_33; // @[Bitwise.scala 103:21:@30.4]
  wire [7:0] _GEN_3; // @[Bitwise.scala 103:31:@31.4]
  wire [7:0] _T_34; // @[Bitwise.scala 103:31:@31.4]
  wire [6:0] _T_35; // @[Bitwise.scala 103:46:@32.4]
  wire [7:0] _GEN_4; // @[Bitwise.scala 103:65:@33.4]
  wire [7:0] _T_36; // @[Bitwise.scala 103:65:@33.4]
  wire [7:0] _T_38; // @[Bitwise.scala 103:75:@35.4]
  wire [7:0] _T_39; // @[Bitwise.scala 103:39:@36.4]
  assign _T_13 = io_in[7:4]; // @[Bitwise.scala 103:21:@10.4]
  assign _T_14 = {{4'd0}, _T_13}; // @[Bitwise.scala 103:31:@11.4]
  assign _T_15 = io_in[3:0]; // @[Bitwise.scala 103:46:@12.4]
  assign _GEN_0 = {{4'd0}, _T_15}; // @[Bitwise.scala 103:65:@13.4]
  assign _T_16 = _GEN_0 << 4; // @[Bitwise.scala 103:65:@13.4]
  assign _T_18 = _T_16 & 8'hf0; // @[Bitwise.scala 103:75:@15.4]
  assign _T_19 = _T_14 | _T_18; // @[Bitwise.scala 103:39:@16.4]
  assign _T_23 = _T_19[7:2]; // @[Bitwise.scala 103:21:@20.4]
  assign _GEN_1 = {{2'd0}, _T_23}; // @[Bitwise.scala 103:31:@21.4]
  assign _T_24 = _GEN_1 & 8'h33; // @[Bitwise.scala 103:31:@21.4]
  assign _T_25 = _T_19[5:0]; // @[Bitwise.scala 103:46:@22.4]
  assign _GEN_2 = {{2'd0}, _T_25}; // @[Bitwise.scala 103:65:@23.4]
  assign _T_26 = _GEN_2 << 2; // @[Bitwise.scala 103:65:@23.4]
  assign _T_28 = _T_26 & 8'hcc; // @[Bitwise.scala 103:75:@25.4]
  assign _T_29 = _T_24 | _T_28; // @[Bitwise.scala 103:39:@26.4]
  assign _T_33 = _T_29[7:1]; // @[Bitwise.scala 103:21:@30.4]
  assign _GEN_3 = {{1'd0}, _T_33}; // @[Bitwise.scala 103:31:@31.4]
  assign _T_34 = _GEN_3 & 8'h55; // @[Bitwise.scala 103:31:@31.4]
  assign _T_35 = _T_29[6:0]; // @[Bitwise.scala 103:46:@32.4]
  assign _GEN_4 = {{1'd0}, _T_35}; // @[Bitwise.scala 103:65:@33.4]
  assign _T_36 = _GEN_4 << 1; // @[Bitwise.scala 103:65:@33.4]
  assign _T_38 = _T_36 & 8'haa; // @[Bitwise.scala 103:75:@35.4]
  assign _T_39 = _T_34 | _T_38; // @[Bitwise.scala 103:39:@36.4]
  assign io_out = _T_39;
endmodule

前回のOHToUIntと同じような感じで2のべき乗単位で分解して選択したデータの論理和を取っていく感じの模様。

Chiselのソースコード

ソースコードはどうなっているのかも勉強がてら確認。

object PriorityMux {
  def apply[T <: Data](in: Seq[(Bool, T)]): T = SeqUtils.priorityMux(in)
  def apply[T <: Data](sel: Seq[Bool], in: Seq[T]): T = apply(sel zip in)
  def apply[T <: Data](sel: Bits, in: Seq[T]): T = apply((0 until in.size).map(sel(_)), in)
}

前回のUIntToOHなどと一緒で複数のapplyが定義されている。これらのapplyからするとサンプルで見たような(Vec[Bool], Vec[UInt])の組み合わせ以外にも

  • (Vec[Bool, UInt])
  • (UInt, Vec[Uint])

のような組み合わせを入れることも可能なようだ。(TになってるのでChiselのDataであればUIntの部分に適用可能はず)

Mux1H

こちらもまずはサンプルをチェック。

Driver(() => new Module {
    // Example circuit using Mux1H
    val io = IO(new Bundle {
      val in_sels = Input(Vec(2, Bool()))
      val in_bits = Input(Vec(2, UInt(8.W)))
      val out = Output(UInt(8.W))
    })
    io.out := Mux1H(io.in_sels, io.in_bits)
  }) { c => new PeekPokeTester(c) {
    poke(c.io.in_bits(0), 10)
    poke(c.io.in_bits(1), 20)
  
    // Select index 1
    poke(c.io.in_sels(0), 0)
    poke(c.io.in_sels(1), 1)
    println(s"in_sels=${peek(c.io.in_sels)}, out=${peek(c.io.out)}")
  
    // Select index 0
    poke(c.io.in_sels(0), 1)
    poke(c.io.in_sels(1), 0)
    println(s"in_sels=${peek(c.io.in_sels)}, out=${peek(c.io.out)}")
  
    // Select none (invalid)
    poke(c.io.in_sels(0), 0)
    poke(c.io.in_sels(1), 0)
    println(s"in_sels=${peek(c.io.in_sels)}, out=${peek(c.io.out)}")
  
    // Select both (invalid)
    poke(c.io.in_sels(0), 1)
    poke(c.io.in_sels(1), 1)
    println(s"in_sels=${peek(c.io.in_sels)}, out=${peek(c.io.out)}")
} }

こちらは冒頭に書いたとおり、選択用の信号はOneHotであることが前提となっている。そのため、エラーケースに相当する複数ビットが1'b1になっているケースもテストが実行されている。

これを実行すると以下の出力が得られる。

[info] [0.000] Elaborating design...
[info] [0.067] Done elaborating.
Total FIRRTL Compile Time: 18.2 ms
Total FIRRTL Compile Time: 10.5 ms
End of dependency graph
Circuit state created
[info] [0.001] SEED 1545574832259
[info] [0.003] in_sels=Vector(0, 1), out=20
[info] [0.004] in_sels=Vector(1, 0), out=10
[info] [0.005] in_sels=Vector(0, 0), out=0
[info] [0.007] in_sels=Vector(1, 1), out=30
test cmd4Helperanonfun1anon2 Success: 0 tests passed in 5 cycles taking 0.009944 seconds
[info] [0.008] RAN 0 CYCLES PASSED

結果として見ておくべきなのは後半2つのテストケースだと思う。

  • in_sels=Vector(0, 0), out=0 : 選択信号が"0"の場合は出力が0になる。
  • in_sels=Vector(1, 1), out=30 : 複数ビットが立った場合は1'b1となったビットを足し合わせたものになる??

生成されるRTL

先のエラーケース時の挙動が回路的にどうなっているのかは認識しておきたいのでRTLも見てみよう。

エラーケースではあるので本来は上位層でそのような信号が入力されないようにケアをするべきではあるんだけどね。

module cmd5Helperanonfun1anon2( // @[:@3.2]
  input        clock, // @[:@4.4]
  input        reset, // @[:@5.4]
  input        io_in_sels_0, // @[:@6.4]
  input        io_in_sels_1, // @[:@6.4]
  input  [7:0] io_in_bits_0, // @[:@6.4]
  input  [7:0] io_in_bits_1, // @[:@6.4]
  output [7:0] io_out // @[:@6.4]
);
  wire [7:0] _T_37; // @[Mux.scala 19:72:@8.4]
  wire [7:0] _T_39; // @[Mux.scala 19:72:@9.4]
  wire [7:0] _T_40; // @[Mux.scala 19:72:@10.4]
  assign _T_37 = io_in_sels_0 ? io_in_bits_0 : 8'h0; // @[Mux.scala 19:72:@8.4]
  assign _T_39 = io_in_sels_1 ? io_in_bits_1 : 8'h0; // @[Mux.scala 19:72:@9.4]
  assign _T_40 = _T_37 | _T_39; // @[Mux.scala 19:72:@10.4]
  assign io_out = _T_40;
endmodule

なるほど、足すは足すでも”論理和”になるということか。

Chiselのソースコード

ソースコードはどうなっているのかも勉強がてら確認。

object Mux1H {
  def apply[T <: Data](sel: Seq[Bool], in: Seq[T]): T =
    apply(sel zip in)
  def apply[T <: Data](in: Iterable[(Bool, T)]): T = SeqUtils.oneHotMux(in)
  def apply[T <: Data](sel: UInt, in: Seq[T]): T =
    apply((0 until in.size).map(sel(_)), in)
  def apply(sel: UInt, in: UInt): Bool = (sel & in).orR
}

こちらは更に一つapplyが増えて4つになっている。増えたのは(sel: UInt, in: UInt])かな。選択信号とデータの論理積とって1bitにしたものが戻る、、でいいのか??うーむ、もう少し読めるようにならんと。。

とりあえず今回はここまで。

次回のChiselネタではCounterをサクッと見て、幕間をお終いにする予定。