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

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

Chisel Bootcamp - 幕間(4) - PopCountとReverse

スポンサーリンク

前回の記事ではChiselの標準ライブラリからArbiterを紹介した。

www.tech-diningyo.info

今日も引き続きChiselの標準ライブラリを紹介していく。今日はChiselのユーティリティブロックからいくつかのモジュールを勉強する。

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

ビット単位のユーティリティ・ブロック

ここではバス信号をビット単位で操作するライブラリを紹介していく。前回の最後に書いたとおり"ステートレス"な処理なので、全て組み合わせ回路で実現されることには留意しておきたい。

PopCount

入力したUInt型の入力を1ビットずつチェックして、0x1になっていたビットの数を返却するモジュール。

早速例を見てみる。

Driver(() => new Module {
    // Example circuit using Reverse
    val io = IO(new Bundle {
      val in = Input(UInt(8.W))
      val out = Output(UInt(8.W))
    })
    io.out := PopCount(io.in)
  }) { c => new PeekPokeTester(c) {
    // Integer.parseInt is used create an Integer from a binary specification
    poke(c.io.in, Integer.parseInt("00000000", 2))
    println(s"in=0b${peek(c.io.in).toInt.toBinaryString}, out=${peek(c.io.out)}")
  
    poke(c.io.in, Integer.parseInt("00001111", 2))
    println(s"in=0b${peek(c.io.in).toInt.toBinaryString}, out=${peek(c.io.out)}")
  
    poke(c.io.in, Integer.parseInt("11001010", 2))
    println(s"in=0b${peek(c.io.in).toInt.toBinaryString}, out=${peek(c.io.out)}")
  
    poke(c.io.in, Integer.parseInt("11111111", 2))
    println(s"in=0b${peek(c.io.in).toInt.toBinaryString}, out=${peek(c.io.out)}")
} }

前回、前々回と同様にテスト用のドライバに直接テスト用にPopCountを使った回路を入力している。

モジュールの中身はとってもシンプルで、以下の入出力の関係になっている。

  • 入力:8bitのUInt
  • 出力:入力のUIntデータをPopCountに入力して得られた結果

また、テストではpokeを使って適当な値を入力している。ここでは2進数の文字列をInteger.parseIntを使ってInt型に変換したデータを与えるようになっている。

以下が実行結果となる。

[info] [0.001] Elaborating design...
[info] [0.653] Done elaborating.
Total FIRRTL Compile Time: 192.0 ms
Total FIRRTL Compile Time: 16.6 ms
End of dependency graph
Circuit state created
[info] [0.001] SEED 1544970607344
[info] [0.003] in=0b0, out=0
[info] [0.004] in=0b1111, out=4
[info] [0.006] in=0b11001010, out=4
[info] [0.008] in=0b11111111, out=8
test cmd2Helperanonfun1anon2 Success: 0 tests passed in 5 cycles taking 0.023412 seconds
[info] [0.010] RAN 0 CYCLES PASSED

見ての通りで、入力した2進数文字列の1の数がoutとして得られた。

RTLに変換すると

これもモジュールなので当然RTLを生成可能だ。

module cmd4Helperanonfun1anon2( // @[:@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  _T_9; // @[Bitwise.scala 50:65:@8.4]
  wire  _T_10; // @[Bitwise.scala 50:65:@9.4]
  wire  _T_11; // @[Bitwise.scala 50:65:@10.4]
  wire  _T_12; // @[Bitwise.scala 50:65:@11.4]
  wire  _T_13; // @[Bitwise.scala 50:65:@12.4]
  wire  _T_14; // @[Bitwise.scala 50:65:@13.4]
  wire  _T_15; // @[Bitwise.scala 50:65:@14.4]
  wire  _T_16; // @[Bitwise.scala 50:65:@15.4]
  wire [1:0] _T_17; // @[Bitwise.scala 48:55:@16.4]
  wire [1:0] _T_18; // @[Bitwise.scala 48:55:@17.4]
  wire [2:0] _T_19; // @[Bitwise.scala 48:55:@18.4]
  wire [1:0] _T_20; // @[Bitwise.scala 48:55:@19.4]
  wire [1:0] _T_21; // @[Bitwise.scala 48:55:@20.4]
  wire [2:0] _T_22; // @[Bitwise.scala 48:55:@21.4]
  wire [3:0] _T_23; // @[Bitwise.scala 48:55:@22.4]
  assign _T_9 = io_in[0]; // @[Bitwise.scala 50:65:@8.4]
  assign _T_10 = io_in[1]; // @[Bitwise.scala 50:65:@9.4]
  assign _T_11 = io_in[2]; // @[Bitwise.scala 50:65:@10.4]
  assign _T_12 = io_in[3]; // @[Bitwise.scala 50:65:@11.4]
  assign _T_13 = io_in[4]; // @[Bitwise.scala 50:65:@12.4]
  assign _T_14 = io_in[5]; // @[Bitwise.scala 50:65:@13.4]
  assign _T_15 = io_in[6]; // @[Bitwise.scala 50:65:@14.4]
  assign _T_16 = io_in[7]; // @[Bitwise.scala 50:65:@15.4]
  assign _T_17 = _T_9 + _T_10; // @[Bitwise.scala 48:55:@16.4]
  assign _T_18 = _T_11 + _T_12; // @[Bitwise.scala 48:55:@17.4]
  assign _T_19 = _T_17 + _T_18; // @[Bitwise.scala 48:55:@18.4]
  assign _T_20 = _T_13 + _T_14; // @[Bitwise.scala 48:55:@19.4]
  assign _T_21 = _T_15 + _T_16; // @[Bitwise.scala 48:55:@20.4]
  assign _T_22 = _T_20 + _T_21; // @[Bitwise.scala 48:55:@21.4]
  assign _T_23 = _T_19 + _T_22; // @[Bitwise.scala 48:55:@22.4]
  assign io_out = {{4'd0}, _T_23};
endmodule

回路的には単純にビット単位にデータを分解してそれぞれを足し合わせる、という回路になるようだ。

そのためビット数が多い場合には注意が必要な気がする。

Reverse

お次はReverseだ。これは簡単に処理も推測できると思うが、そのまんまで入力したUInt型のデータをビット単位で逆にして出力するものだ。

Driver(() => new Module {
    // Example circuit using Reverse
    val io = IO(new Bundle {
      val in = Input(UInt(8.W))
      val out = Output(UInt(8.W))
    })
    io.out := Reverse(io.in)
  }) { c => new PeekPokeTester(c) {
    // Integer.parseInt is used create an Integer from a binary specification
    poke(c.io.in, Integer.parseInt("01010101", 2))
    println(s"in=0b${peek(c.io.in).toInt.toBinaryString}, out=0b${peek(c.io.out).toInt.toBinaryString}")
  
    poke(c.io.in, Integer.parseInt("00001111", 2))
    println(s"in=0b${peek(c.io.in).toInt.toBinaryString}, out=0b${peek(c.io.out).toInt.toBinaryString}")
  
    poke(c.io.in, Integer.parseInt("11110000", 2))
    println(s"in=0b${peek(c.io.in).toInt.toBinaryString}, out=0b${peek(c.io.out).toInt.toBinaryString}")
  
    poke(c.io.in, Integer.parseInt("11001010", 2))
    println(s"in=0b${peek(c.io.in).toInt.toBinaryString}, out=0b${peek(c.io.out).toInt.toBinaryString}")
} }

先ほどのPopCountと同様にInteger.parseintを使って2進数文字列を整数に変換して入力している。出力はビット単位で反転しているのがわかりやすくなるようにtoBinaryStringで2進数の文字列に変換したものをそれぞれ表示している。

結果は以下の通り。

[info] [0.000] Elaborating design...
[info] [0.080] Done elaborating.
Total FIRRTL Compile Time: 35.7 ms
Total FIRRTL Compile Time: 25.1 ms
End of dependency graph
Circuit state created
[info] [0.000] SEED 1544971021729
[info] [0.004] in=0b1010101, out=0b10101010
[info] [0.009] in=0b1111, out=0b11110000
[info] [0.011] in=0b11110000, out=0b1111
[info] [0.013] in=0b11001010, out=0b1010011
test cmd3Helperanonfun1anon2 Success: 0 tests passed in 5 cycles taking 0.020902 seconds
[info] [0.015] RAN 0 CYCLES PASSED
RTLに変換すると

こちらもRTLを見ておこう。

module cmd5Helperanonfun1anon2( // @[:@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

あんまり真面目には追ってないけど、シフトと&と|を使ってビットをずらしていってる感じ。こちらもテストのコードを見てもわかる通り、組み合わせ回路で構成される。

この通りに真面目に回路にするとそれなりの段数になりそうなので、ビット幅の大きなバスで使う場合には適当なビット単位でバラして処理とかを考えたほうが良い気がする。

ということでとても駆け足ではあるが、ビット単位の操作を行うライブラリを2つ勉強した。

次回もChiselの標準ライブラリを確認していくつもりで、OneHotなエンコードを行うモジュールを見ていくつもりだ。