前回の記事ではChisel BootcampのModule2.2の学習を終え、残りは練習問題のみというところまで進めた。
今回はModule2.2の残りの練習問題を見ていく。
Module 2.2: 組み合わせ回路
練習問題
これを始めるにあたって以下の注意が記されている。
この練習問題を解くために、Chisel cheetsheetを参照する必要があるかもしれない
このcheatsheetだが、Chiselの文法がとっても綺麗にまとめてあるので、手元に置いておくと開発のお供に役に立つこと間違い無しだ。 是非参照することをオススメする。
では練習問題へ。。
なお、これまでの記事においても練習問題を載せる場合は問題についてはそれなりに訳した本文を載せ、答えについては基本隠すようにしてきた。 これ以降の記事でも、この方針に従って進めていこうと思う。
練習問題:MAC
MACの機能(
(A*B)+C
)を持ったChiselのモジュールを作成し、テストにパスするようにせよ。
以前の練習問題と同じく、部分的に不完全なコード(以下の???
)が用意してあって、その不完全な部分を埋める課題となっている。
class MAC extends Module { val io = IO(new Bundle { val in_a = Input(UInt(4.W)) val in_b = Input(UInt(4.W)) val in_c = Input(UInt(4.W)) val out = Output(UInt(8.W)) }) ??? } class MACTester(c: MAC) extends PeekPokeTester(c) { val cycles = 100 import scala.util.Random for (i <- 0 until cycles) { val in_a = Random.nextInt(16) val in_b = Random.nextInt(16) val in_c = Random.nextInt(16) poke(c.io.in_a, in_a) poke(c.io.in_b, in_b) poke(c.io.in_c, in_c) expect(c.io.out, in_a*in_b+in_c) } } assert(Driver(() => new MAC) {c => new MACTester(c)}) println("SUCCESS!!")
以下は自分が試した解答。見たくない場合は展開しないようにご注意を。
そして出力解答
class MAC extends Module {
val io = IO(new Bundle {
val in_a = Input(UInt(4.W))
val in_b = Input(UInt(4.W))
val in_c = Input(UInt(4.W))
val out = Output(UInt(8.W))
})
val mul = io.in_a * io.in_b
io.out := mul + io.in_c
}
class MACTester(c: MAC) extends PeekPokeTester(c) {
val cycles = 100
import scala.util.Random
for (i <- 0 until cycles) {
val in_a = Random.nextInt(16)
val in_b = Random.nextInt(16)
val in_c = Random.nextInt(16)
poke(c.io.in_a, in_a)
poke(c.io.in_b, in_b)
poke(c.io.in_c, in_c)
expect(c.io.out, in_a*in_b+in_c)
}
}
assert(Driver(() => new MAC) {c => new MACTester(c)})
println("SUCCESS!!")
[info] [0.001] Elaborating design...
[info] [0.703] Done elaborating.
Total FIRRTL Compile Time: 220.6 ms
Total FIRRTL Compile Time: 11.7 ms
End of dependency graph
Circuit state created
[info] [0.001] SEED 1539871064250
test cmd2HelperMAC Success: 100 tests passed in 5 cycles taking 0.039588 seconds
[info] [0.031] RAN 0 CYCLES PASSED
SUCCESS!!
練習問題:アービター
以下の回路はFIFOのデータを2つのパラレルな処理ユニットへ調停する。FIFOと処理ユニット(PEs)はready-validのインターフェースによって通信する。PEがreadyの際にFIFOのデータが送信されるようにアービターを設計せよ。
なお両方のPEがreadyの場合にはPE0が優先されるものとする。アービターはどちらか一方のPEがreadyの場合にはFIFOにデータを要求するようにすることを忘れないこと。また、データがvalidになる前にPEがreadyになったことを通知することを待つ必要がある。この練習問題を解くためにはおそらくバイナリ演算子が必要になるだろう。
という問題だ。
”以下の回路”はこれ↓。
まとめると以下のようになる。
- 以下の仕様を満たすアービターを設計せよ
これに対するコードは以下。
class Arbiter extends Module { val io = IO(new Bundle { // FIFO val fifo_valid = Input(Bool()) val fifo_ready = Output(Bool()) val fifo_data = Input(UInt(16.W)) // PE0 val pe0_valid = Output(Bool()) val pe0_ready = Input(Bool()) val pe0_data = Output(UInt(16.W)) // PE1 val pe1_valid = Output(Bool()) val pe1_ready = Input(Bool()) val pe1_data = Output(UInt(16.W)) }) ??? } class ArbiterTester(c: Arbiter) extends PeekPokeTester(c) { import scala.util.Random val data = Random.nextInt(65536) poke(c.io.fifo_data, data) for (i <- 0 until 8) { poke(c.io.fifo_valid, (i>>0)%2) poke(c.io.pe0_ready, (i>>1)%2) poke(c.io.pe1_ready, (i>>2)%2) expect(c.io.fifo_ready, i>1) expect(c.io.pe0_valid, i==3 || i==7) expect(c.io.pe1_valid, i==5) if (i == 3 || i ==7) { expect(c.io.pe0_data, data) } else if (i == 5) { expect(c.io.pe1_data, data) } } } assert(Driver(() => new Arbiter) {c => new ArbiterTester(c)}) println("SUCCESS!!")
”バイナリ演算子が必要になるだろう”とのことなのでcheetsheetをここで確認しておく。
使いそうなのは以下ですかね。
Chisel | Explanation | Width |
---|---|---|
!x |
Logical NOT | 1 |
x && y |
Logical AND | 1 |
`x | y` | Logical OR | 1 |
以下は自分が試した解答。繰り返しになるが見たくない場合は展開しないようにご注意を。
そして出力解答
class Arbiter extends Module {
val io = IO(new Bundle {
// FIFO
val fifo_valid = Input(Bool())
val fifo_ready = Output(Bool())
val fifo_data = Input(UInt(16.W))
// PE0
val pe0_valid = Output(Bool())
val pe0_ready = Input(Bool())
val pe0_data = Output(UInt(16.W))
// PE1
val pe1_valid = Output(Bool())
val pe1_ready = Input(Bool())
val pe1_data = Output(UInt(16.W))
})
io.fifo_ready := (io.pe0_ready || io.pe1_ready)
io.pe0_valid := io.fifo_valid && io.pe0_ready
io.pe0_data := io.fifo_data
io.pe1_valid := io.fifo_valid && (!io.pe0_valid) && io.pe1_ready
io.pe1_data := io.fifo_data
}
class ArbiterTester(c: Arbiter) extends PeekPokeTester(c) {
import scala.util.Random
val data = Random.nextInt(65536)
poke(c.io.fifo_data, data)
for (i <- 0 until 8) {
poke(c.io.fifo_valid, (i>>0)%2)
poke(c.io.pe0_ready, (i>>1)%2)
poke(c.io.pe1_ready, (i>>2)%2)
expect(c.io.fifo_ready, i>1)
expect(c.io.pe0_valid, i==3 || i==7)
expect(c.io.pe1_valid, i==5)
if (i == 3 || i ==7) {
expect(c.io.pe0_data, data)
} else if (i == 5) {
expect(c.io.pe1_data, data)
}
}
}
assert(Driver(() => new Arbiter) {c => new ArbiterTester(c)})
println("SUCCESS!!")
[info] [0.000] Elaborating design...
[info] [0.010] Done elaborating.
Total FIRRTL Compile Time: 19.0 ms
Total FIRRTL Compile Time: 14.6 ms
End of dependency graph
Circuit state created
[info] [0.001] SEED 1539872134453
test cmd6HelperArbiter Success: 27 tests passed in 5 cycles taking 0.010229 seconds
[info] [0.009] RAN 0 CYCLES PASSED
SUCCESS!!
練習問題:パラメタライズ版加算器(オプション)
オプション扱いなのだが、一応やってみる。
ということで問題文は以下。
この追加の練習問題はChiselの最も強力な機能の一つでもある、パラメタライゼーションを見せることになる。これを試すために以下の機能を持つパラメタライズ版加算器を設計してみよう
- オーバフロー発生時に、以下のいずれかを持つ加算器
- 飽和させる
- 結果を切り捨てる
まず最初に
Module
を見てみよう。Module
に渡したパラメータにsaturate
と呼ばれるBoolean
データがある。これはScalaのBoolean
でありChiselのBool
ではないので、単一のハードウェアで上記の2つの機能をもったものは作れない。しかしジェネレータをつるくことによって、飽和機能を持った加算器と切り捨て機能を持った加算器の両方を作成することが出来る。これらの機能はコンパイル時に決定される次に加算器の入出力が4-bitの
UInt
であることに注目してほしい。Chiselはビルドインのビット幅推論機を持っている。もしあなたがcheetsheetを見ているなら、通常の加算におけるビット幅は2つの入力の最大のビット幅になるのを見つけただろう。これはつまり
scala val sum = io.in_a + io.in_b
という演算は、4-bit同士の演算であり、加算後の値は4-bitの入力に合わせて切り捨てられることを意味する。加算処理時に飽和するかどうかを確認すべきであれば、計算結果が5-bitの信号となる場所を用意する必要がある。これは加算時に
+&
を使用することで可能になるということがcheetsheetに載っているはずだ。
scala val sum = io.in_a +& io.in_b
最後に、4-bitの
UInt
の信号に5-bitのUInt
の信号を接続するとデフォルトの動きとしてMSBが切り捨てられる。これを利用することにてって、非飽和の加算器の結果を切り捨てることが出来る。
上記で触れられているオーバーフローのビットを含んだ加算はcheetsheetでは以下のように記載されている。
Chisel | Explanation | Width |
---|---|---|
+& |
Additon | max(w(x),w(y))+1 |
-& |
Subtraction | max(w(x),w(y))+1 |
見てのとおりだが、演算結果のビット幅が演算時の2つのデータの最大のビット幅+1になる。
さて問題のコードを見てみよう。
class ParameterizedAdder(saturate: Boolean) extends Module { val io = IO(new Bundle { val in_a = Input(UInt(4.W)) val in_b = Input(UInt(4.W)) val out = Output(UInt(4.W)) }) ??? } class ParameterizedAdderTester(c: ParameterizedAdder, saturate: Boolean) extends PeekPokeTester(c) { // 100 random tests val cycles = 100 import scala.util.Random import scala.math.min for (i <- 0 until cycles) { val in_a = Random.nextInt(16) val in_b = Random.nextInt(16) poke(c.io.in_a, in_a) poke(c.io.in_b, in_b) if (saturate) { expect(c.io.out, min(in_a+in_b, 15)) } else { expect(c.io.out, (in_a+in_b)%16) } } // ensure we test saturation vs. truncation poke(c.io.in_a, 15) poke(c.io.in_b, 15) if (saturate) { expect(c.io.out, 15) } else { expect(c.io.out, 14) } } for (saturate <- Seq(true, false)) { assert(Driver(() => new ParameterizedAdder(saturate)) {c => new ParameterizedAdderTester(c, saturate)}) } println("SUCCESS!!")
出力結果は以下の通り解答
class ParameterizedAdder(saturate: Boolean) extends Module {
val io = IO(new Bundle {
val in_a = Input(UInt(4.W))
val in_b = Input(UInt(4.W))
val out = Output(UInt(4.W))
})
val sum = io.in_a +& io.in_b
if (saturate) {
io.out := Mux(sum > 15.U, 15.U, sum) // 飽和したのでクリップ
}
else {
io.out := sum // sumを入れることでMSBであるbit[4]をカット
}
}
class ParameterizedAdderTester(c: ParameterizedAdder, saturate: Boolean) extends PeekPokeTester(c) {
// 100 random tests
val cycles = 100
import scala.util.Random
import scala.math.min
for (i <- 0 until cycles) {
val in_a = Random.nextInt(16)
val in_b = Random.nextInt(16)
poke(c.io.in_a, in_a)
poke(c.io.in_b, in_b)
if (saturate) {
expect(c.io.out, min(in_a+in_b, 15))
} else {
expect(c.io.out, (in_a+in_b)%16)
}
}
// ensure we test saturation vs. truncation
poke(c.io.in_a, 15)
poke(c.io.in_b, 15)
if (saturate) {
expect(c.io.out, 15)
} else {
expect(c.io.out, 14)
}
}
for (saturate <- Seq(true, false)) {
assert(Driver(() => new ParameterizedAdder(saturate)) {c => new ParameterizedAdderTester(c, saturate)})
}
println("SUCCESS!!")
[info] [0.000] Elaborating design...
[info] [0.052] Done elaborating.
Total FIRRTL Compile Time: 12.9 ms
Total FIRRTL Compile Time: 11.5 ms
End of dependency graph
Circuit state created
[info] [0.000] SEED 1539874024771
test cmd7HelperParameterizedAdder Success: 101 tests passed in 5 cycles taking 0.015463 seconds
[info] [0.016] RAN 0 CYCLES PASSED
[info] [0.000] Elaborating design...
[info] [0.004] Done elaborating.
Total FIRRTL Compile Time: 7.5 ms
Total FIRRTL Compile Time: 5.2 ms
End of dependency graph
Circuit state created
[info] [0.000] SEED 1539874024877
test cmd7HelperParameterizedAdder Success: 101 tests passed in 5 cycles taking 0.009467 seconds
[info] [0.009] RAN 0 CYCLES PASSED
SUCCESS!!
これでModule2.2も全て終了。次回からはModule2.3に取り組んでいこう。https://www.tech-diningyo.info/entry/2018/10/18/235201