前回の記事でChisel BootcampのModule2.3の学習自体が終わったところまで進んだ。
今回はModule2.3の練習問題に取り組んでいく。
Module 2.3: フロー制御
練習問題
多項式の計算回路
ということでModule2.3の練習問題に取り組んでいく。
ここでの練習問題は、以下の3つの多項式についてのものとなる。
これまでの練習問題と少し違っているのは、すぐにChiselのモジュールの作成に入らずにいくつかのステップを踏んで進めていくとことだ。
こうすることの意味はTDDによる開発を意識することにあるようだ。
ステップ1 - Scalaで各多項式を関数化
まずは最初のステップとして、Scalaで上記3つの多項式に対するチェックをパスできる多項式の計算処理を実装する。
def poly0(x: Int): Int = ??? def poly1(x: Int): Int = ??? def poly2(x: Int): Int = ??? assert(poly0(0) == 1) assert(poly1(0) == 3) assert(poly2(0) == -5) assert(poly0(1) == 0) assert(poly1(1) == 11) assert(poly2(1) == -11)
見ての通りで、3つのpoly0
~poly2
の宣言のみが用意されており、処理自体は未実装となっている。またその後のassert
文で簡単なチェックが出来るようになっている。
これまで同じく、下記は自分で実行した際の解答になるので見たくない場合は開かないように注意。
出力結果は以下の通り 解答 - クリックすると開くでので、見たくない場合は開かないように注意。
def poly0(x: Int): Int = (x * x) - (2 * x) + 1
def poly1(x: Int): Int = (2 * x * x) + (6 * x) + 3
def poly2(x: Int): Int = (4 * x * x) - (10 * x) - 5
assert(poly0(0) == 1)
assert(poly1(0) == 3)
assert(poly2(0) == -5)
assert(poly0(1) == 0)
assert(poly1(1) == 11)
assert(poly2(1) == -11)
defined function poly0
defined function poly1
defined function poly2
assert
文が発火しなかったので、ただ単にpoly0
~poly2
が定義されたというメッセージが出るのみとなる。
ステップ2 - Scalaで各多項式の関数をまとめる
ステップ2では、ステップ1のpoly
~poly2
を一つのpoly
関数にまとめていく。どの多項式を計算するかを選択するための引数select
が追加されている。
このステップもScalaで実装を行うように指示があるので、それに従おう。
def poly(select: Int, x: Int): Int = { ??? } assert(poly(1, 0) == 3) assert(poly(1, 1) == 11) assert(poly(2, 1) == -11)
あんまり説明文読んでなかったので普通に書きなおしたが、Bootcampの解答は 出力結果は以下の通り。これも先ほどと一緒で 解答 - クリックすると開くでので、見たくない場合は開かないように注意。
def poly(select: Int, x: Int): Int = {
val square = x * x
if (select == 0) {
square - (2 * x) + 1
} else if (select == 1) {
(2 * square) + (6 * x) + 3
} else {
(4 * square) - (10 * x) - 5
}
}
assert(poly(1, 0) == 3)
assert(poly(1, 1) == 11)
assert(poly(2, 1) == -11)
select
の値に応じて先程のpoly0
~poly2
をそのまま呼ぶ形になっていた。(もともとの指示は"select the polynomial"になってる)assert
が発火していないので、poly
が定義されたというメッセージのみ。defined function poly
ステップ3 - Chiselでモジュールを作成
いよいよChiselのモジュールとして、Sort4モジュールを作成してみる。
指示もシンプルで、
以下のテンプレートを使って自分の回路を作ってみよう
となっている。
// compute the polynomial class Polynomial extends Module { val io = IO(new Bundle { val select = Input(UInt(2.W)) val x = Input(SInt(32.W)) val fOfX = Output(SInt(32.W)) }) val result = Wire(SInt(32.W)) val square = Wire(SInt(32.W)) ??? io.fOfX := result } // verify that the computation is correct class PolynomialTester(c: Polynomial) extends PeekPokeTester(c) { for(x <- 0 to 20) { for(select <- 0 to 2) { poke(c.io.select, select) poke(c.io.x, x) expect(c.io.fOfX, poly(select, x)) } } } // Test Polynomial val works = Driver(() => new Polynomial) { c => new PolynomialTester(c) } assert(works) // Scala Code: if works == false, will throw an error println("SUCCESS!!") // Scala Code: if we get here, our tests passed!
出力結果は以下の通り 解答 - クリックすると開くでので、見たくない場合は開かないように注意。
// compute the polynomial
class Polynomial extends Module {
val io = IO(new Bundle {
val select = Input(UInt(2.W))
val x = Input(SInt(32.W))
val fOfX = Output(SInt(32.W))
})
val result = Wire(SInt(32.W))
val square = Wire(SInt(32.W))
square := io.x * io.x
when(io.select === 0.U) {
result := square - (2.S * io.x) + 1.S
}.elsewhen (io.select === 1.U) {
result := (2.S * square) + (6.S * io.x) + 3.S
}.otherwise {
result := (4.S * square) - (10.S * io.x) - 5.S
}
io.fOfX := result
}
// verify that the computation is correct
class PolynomialTester(c: Polynomial) extends PeekPokeTester(c) {
for(x <- 0 to 20) {
for(select <- 0 to 2) {
poke(c.io.select, select)
poke(c.io.x, x)
expect(c.io.fOfX, poly(select, x))
}
}
}
// Test Polynomial
val works = Driver(() => new Polynomial) {
c => new PolynomialTester(c)
}
assert(works) // Scala Code: if works == false, will throw an error
println("SUCCESS!!") // Scala Code: if we get here, our tests passed!
[info] [0.000] Elaborating design...
[info] [0.106] Done elaborating.
Total FIRRTL Compile Time: 39.7 ms
Total FIRRTL Compile Time: 32.9 ms
End of dependency graph
Circuit state created
[info] [0.000] SEED 1540117201319
test cmd5HelperPolynomial Success: 63 tests passed in 5 cycles taking 0.028744 seconds
[info] [0.024] RAN 0 CYCLES PASSED
SUCCESS!!
自分で1から書いてみるまであんまり意識しなかったけど、
// 間違い when (条件1) { 条件1がTrueの時の処理 } elsewhen (条件2) { 条件2がTrueの時の処理 } otherwise { それ以外の時の処理 }
じゃなくて、
// 正解 when (条件1) { 条件1がTrueの時の処理 } .elsewhen (条件2) { 条件2がTrueの時の処理 } .otherwise { それ以外の時の処理 }
なのね(elsewhen
/otherwise
の前に.
が必要)
同じようなのがもうひとつあって、
// 間違い when (io.in == 1.U)
じゃなくて
// 正解 when (io.in === 1.U)
になる(”一致する”を表すときは=
が3つ)
Chiselのceahtsheetにも丁寧に"Equalitytriple equals"と書いてあった。
ステートマシン
次の練習問題はハードウェアの制御には欠かせないステートマシンだ。ここは問題分がしっかり書いてあるのでまとめて訳して引用させてもらう。
カルノー図を使っったステートマシンの最適化は面倒だが、それは合成ツールが解決してくれる。しかし、その結果生成されるのは直感的でなく読みづらいコードになりがちだ。だからここでは、Chiselのフロー制御と最終接続の法則を使うことで、もっと直感的なコードを書いてみよう。
グラッドの生徒はその過程において4つの状態を通過する。その4つはIdle、Coding、 Writing、Graduatingだ。これらのステートの遷移は3つの入力によって起きる。すなわち、コーヒー(Coffee)、 アイデアが浮かんだ時(Ideas)、彼らのアドバイザーからの進捗に対するプレッシャー(Pressure)だ。一度卒業(Graduate)すると、状態はIdleに戻る。このFSMダイアグラムを描くと以下の図になる。ラベルのついていない遷移(入力が無いときなど)は、現在のステートにとどまらずにIdleに戻る。入力の優先順位は、
- Coffee
- Ideas
- Pressure
の順だ。
そのため、IdleステートにおいてCoffeeとPressureの入力があった倍には、生徒はCodingステートに遷移する。
ステップ1:テスト作成
以下の準備されたコードの"???"に必要なコードを実装しよう。仕様については前項で引用した文と画像に従ったものを作れば良い。
// state map def states = Map("idle" -> 0, "coding" -> 1, "writing" -> 2, "grad" -> 3) // life is full of question marks def gradLife (state: Int, coffee: Boolean, idea: Boolean, pressure: Boolean): Int = { var nextState = states("idle") ??? nextState } // some sanity checks (0 until states.size).foreach{ state => assert(gradLife(state, false, false, false) == states("idle")) } assert(gradLife(states("writing"), true, false, true) == states("writing")) assert(gradLife(states("idle"), true, true, true) == states("coding")) assert(gradLife(states("idle"), false, true, true) == states("idle")) assert(gradLife(states("grad"), false, false, false) == states("idle"))
出力結果は以下の通り 解答 - クリックすると開くでので、見たくない場合は開かないように注意。
// state map
def states = Map("idle" -> 0, "coding" -> 1, "writing" -> 2, "grad" -> 3)
// life is full of question marks
def gradLife (state: Int, coffee: Boolean, idea: Boolean, pressure: Boolean): Int = {
var nextState = states("idle")
if (state == states("idle")) {
if (coffee) {
nextState = states("coding")
} else if (idea) {
nextState = states("idle")
} else if (pressure) {
nextState = states("writing")
}
} else if (state == states("coding")) {
if (coffee) {
nextState = states("coding")
} else if (pressure || idea){
nextState = states("writing")
}
} else if (state == states("writing")) {
if (coffee || idea) {
nextState = states("writing")
} else if (pressure) {
nextState = states("grad")
}
} else {
nextState = states("idle")
}
nextState
}
// some sanity checks
(0 until states.size).foreach{ state => assert(gradLife(state, false, false, false) == states("idle")) }
assert(gradLife(states("writing"), true, false, true) == states("writing"))
assert(gradLife(states("idle"), true, true, true) == states("coding"))
assert(gradLife(states("idle"), false, true, true) == states("idle"))
assert(gradLife(states("grad"), false, false, false) == states("idle"))
defined function states
defined function gradLife
ステップ1:ChiselでFSM作成
ここも本文を引用させていただく。
まだ順序回路を学んでいないので、先にScalaの関数で作った
gradLife
のように、現在のステートはModule
への入力として与え、その次に遷移するステートを出力する回路を作成することにする。Chiselはステートマシンをマッピングするのに適した
Enum
という関数を持っている。これを使うことで、各ステートをUInt
のリテラルとして扱える。Chiselの一致判断は3つの
=
になることを忘れないように!
うん、イコール3つは、さっきまさにハマったやつだね。
// life gets hard-er class GradLife extends Module { val io = IO(new Bundle { val state = Input(UInt(2.W)) val coffee = Input(Bool()) val idea = Input(Bool()) val pressure = Input(Bool()) val nextState = Output(UInt(2.W)) }) val idle :: coding :: writing :: grad :: Nil = Enum(4) io.nextState := idle ??? } // verify that the hardware matches the golden model class GradLifeSim(c: GradLife) extends PeekPokeTester(c) { for (state <- 0 to 3) { for (coffee <- List(true, false)) { for (idea <- List(true, false)) { for (pressure <- List(true, false)) { poke(c.io.state, state) poke(c.io.coffee, coffee) poke(c.io.idea, idea) poke(c.io.pressure, pressure) expect(c.io.nextState, gradLife(state, coffee, idea, pressure)) } } } } } // Test val works = Driver(() => new GradLife) {c => new GradLifeSim(c)} assert(works) // Scala Code: if works == false, will throw an error println("SUCCESS!!") // Scala Code: if we get here, our tests passed!
この練習問題は、先ほどScalaで実装したgradLife
をそのまんまChiselのコードに置き換えればそれでオッケー。
出力結果は以下の通り 解答 - クリックすると開くでので、見たくない場合は開かないように注意。
// life gets hard-er
class GradLife extends Module {
val io = IO(new Bundle {
val state = Input(UInt(2.W))
val coffee = Input(Bool())
val idea = Input(Bool())
val pressure = Input(Bool())
val nextState = Output(UInt(2.W))
})
val idle :: coding :: writing :: grad :: Nil = Enum(4)
io.nextState := idle
when (io.state === idle) {
when (io.coffee) {
io.nextState := coding
} .elsewhen (io.idea) {
io.nextState := idle
} .elsewhen (io.pressure) {
io.nextState := writing
}
} .elsewhen (io.state === coding) {
when (io.coffee) {
io.nextState := coding
} .elsewhen (io.idea || io.pressure) {
io.nextState := writing
}
} .elsewhen (io.state === writing) {
when (io.coffee || io.idea) {
io.nextState := writing
} .elsewhen (io.pressure) {
io.nextState := grad
}
} .otherwise {
io.nextState := idle
}
}
// verify that the hardware matches the golden model
class GradLifeSim(c: GradLife) extends PeekPokeTester(c) {
for (state <- 0 to 3) {
for (coffee <- List(true, false)) {
for (idea <- List(true, false)) {
for (pressure <- List(true, false)) {
poke(c.io.state, state)
poke(c.io.coffee, coffee)
poke(c.io.idea, idea)
poke(c.io.pressure, pressure)
expect(c.io.nextState, gradLife(state, coffee, idea, pressure))
}
}
}
}
}
// Test
val works = Driver(() => new GradLife) {c => new GradLifeSim(c)}
assert(works) // Scala Code: if works == false, will throw an error
println("SUCCESS!!") // Scala Code: if we get here, our tests passed!
[info] [0.000] Elaborating design...
[info] [0.067] Done elaborating.
Total FIRRTL Compile Time: 22.1 ms
Total FIRRTL Compile Time: 18.6 ms
End of dependency graph
Circuit state created
[info] [0.000] SEED 1540131941742
test cmd10HelperGradLife Success: 32 tests passed in 5 cycles taking 0.013625 seconds
[info] [0.012] RAN 0 CYCLES PASSED
SUCCESS!!
これでModule2.3も全て終了。次の記事からModule2.4に入っていよいよ順序回路を勉強していく。