前回の記事ではChiselの標準ライブラリからArbiter
を紹介した。
今日も引き続き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なエンコードを行うモジュールを見ていくつもりだ。