前回のChiselの記事ではFIRRTLを可視化するツールdiagrammerを試してみた内容をまとめた。
今回はしばらく進めていなかったChisel-Bootcampに戻り、Module3.5の残りを見ていく。 オブジェクト指向プログラミングをChiselに適用すると??という話だ。
Chiselでオブジェクト指向プログラミング
とそれらしいタイトルをつけては見たが、実はこれまでに山ほどChiselでオブジェクト指向プログラミングはやってきているという話。 Bootcampの説明を借りると
- Chiselのモジュール →
class Module
を継承してるでしょ? - ChiselのIO →
Bundle
使ってるよね?? - Chiselのデータ型(
UInt
とかBool
) → あれはData
のサブクラスだよ?
になる。
因みに、一個だけ気になるキーワードがあるので自分向けのメモの意味も含めて以下の部分だけ引用しておく(気になったのはRecord
ってやつ、そのうち調べよう)。
全てのChiselのIOは
Bundle
を継承して作られている(もしくは特別な状況下ではBundle
のサブタイプRecord
)
Module
例題:グレイエンコーダとデコーダ
ここからは例を使って見ていく。 Chisel-Bootcampの順番とは変えて、まずはグレイコードの生成部分を。
import scala.math.pow // create a module class GrayCoder(bitwidth: Int) extends Module { val io = IO(new Bundle{ val in = Input(UInt(bitwidth.W)) val out = Output(UInt(bitwidth.W)) val encode = Input(Bool()) // decode on false }) when (io.encode) { //encode io.out := io.in ^ (io.in >> 1.U) } .otherwise { // decode, much more complicated io.out := Seq.fill(log2Ceil(bitwidth))(Wire(UInt(bitwidth.W))).zipWithIndex.fold((io.in, 0)){ case ((w1: UInt, i1: Int), (w2: UInt, i2: Int)) => { w2 := w1 ^ (w1 >> pow(2, log2Ceil(bitwidth)-i2-1).toInt.U) (w2, i1) } }._1 } }
なんか急に複雑になった気がしないでもない。特にotherwise
の部分。とりあえず、入力のio.encode
の状態に応じてエンコード/デコードの処理が切り替えられるようになっている。
エンコード部分は特に難しいことは何もなく、ただ入ってきた2進数データをグレイコードに変換しているだけになっている。
で、やたら複雑にみえるデコード部分を細かく見てみよう。
io.out := Seq.fill(log2Ceil(bitwidth))(Wire(UInt(bitwidth.W))).zipWithIndex.fold((io.in, 0)){ case ((w1: UInt, i1: Int), (w2: UInt, i2: Int)) => { w2 := w1 ^ (w1 >> pow(2, log2Ceil(bitwidth)-i2-1).toInt.U) (w2, i1) } }._1
Seq.fill(log2Ceil(bitwidth))(Wire(UInt(bitwidth.W)))
aSeq.fill(N)
でNに指定したサイズのSeqを生成して- 中身をビット幅
UInt(bitwidth.W)
のWire
で初期化
.zipWithIndex
- 作った
Wire
の入ったSeq
と要素数をタプルにしたSeq
を生成- 型で書くと
Seq[(UInt, Int)]
の状態
- 型で書くと
- 作った
.fold((io.in, 0))
- 最初の要素が
(io.in, 0)
として、後続のブロック式部分をfold
で適用case ((w1: UInt, i1: Int), (w2: UInt, i2: Int))
にマッチした際に後続のブロック式を適用- foldで計算が行われるため
w1
/w2
はSeq.fill
で作成したWire
かio.in
、i1
/i2
はzipWithIndex
で作成したInt型のインデックス - 最初の演算だと
w1==io.in
,i1==0
となり、この2つデータから計算された値がw2
にアサインされる
- foldで計算が行われるため
(w2, i1)
がこのブロック式の戻り値
- 最初の要素が
}._1
- ケース文部分のブロック式の戻り値が
(w2, i2)
のタプルになっており、そのタプルの1番目の要素(==w2
)をio.out
にアサインする
- ケース文部分のブロック式の戻り値が
テストコード
早速これをテストしてみよう、、というコードが以下になる。
// test our gray coder val bitwidth = 4 Driver(() => new GrayCoder(bitwidth)) { c => new PeekPokeTester(c) { def toBinary(i: Int, digits: Int = 8) = String.format("%" + digits + "s", i.toBinaryString).replace(' ', '0') println("Encoding:") for (i <- 0 until pow(2, bitwidth).toInt) { poke(c.io.in, i) poke(c.io.encode, true) step(1) println(s"In = ${toBinary(i, bitwidth)}, Out = ${toBinary(peek(c.io.out).toInt, bitwidth)}") } println("Decoding:") for (i <- 0 until pow(2, bitwidth).toInt) { poke(c.io.in, i) poke(c.io.encode, false) step(1) println(s"In = ${toBinary(i, bitwidth)}, Out = ${toBinary(peek(c.io.out).toInt, bitwidth)}") } } }
今回は特にexpect
による期待値比較は行われていないが、エンコードとデコードのテストがそれぞれ実施されているので、出力を見比べれば結果が正しいことは確認できる。
[info] [0.000] Elaborating design... [info] [0.632] Done elaborating. Total FIRRTL Compile Time: 219.7 ms Total FIRRTL Compile Time: 19.1 ms End of dependency graph Circuit state created [info] [0.001] SEED 1551359800217 [info] [0.002] Encoding: [info] [0.005] In = 0000, Out = 0000 [info] [0.006] In = 0001, Out = 0001 [info] [0.009] In = 0010, Out = 0011 [info] [0.011] In = 0011, Out = 0010 [info] [0.012] In = 0100, Out = 0110 [info] [0.013] In = 0101, Out = 0111 [info] [0.014] In = 0110, Out = 0101 [info] [0.016] In = 0111, Out = 0100 [info] [0.018] In = 1000, Out = 1100 [info] [0.019] In = 1001, Out = 1101 [info] [0.021] In = 1010, Out = 1111 [info] [0.025] In = 1011, Out = 1110 [info] [0.034] In = 1100, Out = 1010 [info] [0.037] In = 1101, Out = 1011 [info] [0.038] In = 1110, Out = 1001 [info] [0.039] In = 1111, Out = 1000 [info] [0.040] Decoding: [info] [0.047] In = 0000, Out = 0000 [info] [0.048] In = 0001, Out = 0001 [info] [0.050] In = 0010, Out = 0011 [info] [0.056] In = 0011, Out = 0010 [info] [0.058] In = 0100, Out = 0111 [info] [0.059] In = 0101, Out = 0110 [info] [0.060] In = 0110, Out = 0100 [info] [0.063] In = 0111, Out = 0101 [info] [0.064] In = 1000, Out = 1111 [info] [0.066] In = 1001, Out = 1110 [info] [0.067] In = 1010, Out = 1100 [info] [0.070] In = 1011, Out = 1101 [info] [0.072] In = 1100, Out = 1000 [info] [0.074] In = 1101, Out = 1001 [info] [0.075] In = 1110, Out = 1011 [info] [0.076] In = 1111, Out = 1010 test cmd3HelperGrayCoder Success: 0 tests passed in 37 cycles taking 0.087356 seconds [info] [0.079] RAN 32 CYCLES PASSED
AsyncFIFO
上記でグレイコードのエンコーダとデコーダが出来た。ということでグレイコードを使った例ということで非同期のFIFO内での使われ方が部分的にではあるがこれも一応載せておく。 とはいえ、とりあえず実装例、、というよりは先程のグレイコードのモジュールをどうやってインスタンス&結線するよーという例でしかないのだが。。
class AsyncFIFO(depth: Int = 16) extends Module { val io = IO(new Bundle{ // write inputs val write_clock = Input(Clock()) val write_enable = Input(Bool()) val write_data = Input(UInt(32.W)) // read inputs/outputs val read_clock = Input(Clock()) val read_enable = Input(Bool()) val read_data = Output(UInt(32.W)) // FIFO status val full = Output(Bool()) val empty = Output(Bool()) }) // add extra bit to counter to check for fully/empty status assert(isPow2(depth), "AsyncFIFO needs a power-of-two depth!") val write_counter = withClock(io.write_clock) { Counter(io.write_enable && !io.full, depth*2)._1 } val read_counter = withClock(io.read_clock) { Counter(io.read_enable && !io.empty, depth*2)._1 } // encode val encoder = new GrayCoder(write_counter.getWidth) encoder.io.in := write_counter encoder.io.encode := true.B // synchronize val sync = withClock(io.read_clock) { ShiftRegister(encoder.io.out, 2) } // decode val decoder = new GrayCoder(read_counter.getWidth) decoder.io.in := sync decoder.io.encode := false.B // status logic goes here }
上記の回路をちゃんと組んでみるのもいい勉強になるかも。 時間を見つけて試してみたい。