前回の記事ではChisel BootcampのModule2.4で学んだレジスタを使ったフロー制御を学んだ。
今回はModule2.4の残りである練習問題に取り組んでいく。
Module 2.4: 順序回路
練習問題
練習問題:シフトレジスタ
問題についてはそのまんま引用させていただく。
このモジュールで得たレジスタの知識を使ってLSFRのためのシフトレジスタを作ってみよう。
仕様は以下のとおりだ:
全ての要素は1-bit幅
4-bitの出力を持つ
1-bitの入力をもらい、それがシフトレジスタへの次の入力となる
出力はシフトレジスタの各ビットを並列にしたもので、シフトレジスタの最後の要素がMSB、最初の要素がLSBとなる。
Cat
が役に立つはずだ出力の初期値は
b0001
とするシフト処理は1サイクル毎に行う(enable信号無し)
**Chiselでは次のようなビット指定のアサインは文法エラーとなる
scala out(0) := io.in
基本的なモジュールの雛形、テストベクタ、ドライバーは以下のものとする。最初のレジスタについては既に定義済みにしてある。
class MyShiftRegister(val init: Int = 1) extends Module { val io = IO(new Bundle { val in = Input(Bool()) val out = Output(UInt(4.W)) }) val state = RegInit(UInt(4.W), init.U) ??? } class MyShiftRegisterTester(c: MyShiftRegister) extends PeekPokeTester(c) { var state = c.init for (i <- 0 until 10) { // poke in LSB of i (i % 2) poke(c.io.in, i % 2) // update expected state state = ((state * 2) + (i % 2)) & 0xf step(1) expect(c.io.out, state) } } assert(chisel3.iotesters.Driver(() => new MyShiftRegister()) { c => new MyShiftRegisterTester(c) }) println("SUCCESS!!")
これまでのModuleで学んできたことを使えば、そのまま解けるはず。
シフト演算使ってもいいし、上で書いてあるようにCat
を使ったやり方も存在してる。
ここでは 出力は以下のように、テストもパスすることが確認できた。 解答 - クリックすると開くので、見たくない場合は開かないように注意。
class MyShiftRegister(val init: Int = 1) extends Module {
val io = IO(new Bundle {
val in = Input(Bool())
val out = Output(UInt(4.W))
})
val state = RegInit(UInt(4.W), init.U)
state := Cat(state, io.in)
io.out := state
}
class MyShiftRegisterTester(c: MyShiftRegister) extends PeekPokeTester(c) {
var state = c.init
for (i <- 0 until 10) {
// poke in LSB of i (i % 2)
poke(c.io.in, i % 2)
// update expected state
state = ((state * 2) + (i % 2)) & 0xf
step(1)
expect(c.io.out, state)
}
}
assert(chisel3.iotesters.Driver(() => new MyShiftRegister()) {
c => new MyShiftRegisterTester(c)
})
println("SUCCESS!!")
Cat
を使ったやり方で書いてみた。Cat
すると処理後の結果が5-bitになるが、State
が4-bitなのでMSBが削れて、シフトした状態になる。[info] [0.000] Elaborating design...
[info] [0.006] Done elaborating.
Total FIRRTL Compile Time: 10.2 ms
Total FIRRTL Compile Time: 11.6 ms
End of dependency graph
Circuit state created
[info] [0.000] SEED 1540734648119
test cmd4HelperMyShiftRegister Success: 10 tests passed in 15 cycles taking 0.009679 seconds
[info] [0.009] RAN 10 CYCLES PASSED
SUCCESS!!
練習問題:パラメタライズ版シフトレジスタ
上記で作成したシフトレジスタをもう少し改良して、「遅延値をパラメタライズしたシフトレジスタを作ってみよう」という問題。
先ほどのシフトレジスタからの変更点は以下の3点だ。
- 遅延量を
n
で指定できるようにする - 初期値を
init
で指定できるようにする - 入力にイネーブル信号
en
を追加
// n is the output width (number of delays - 1) // init state to init class MyOptionalShiftRegister(val n: Int, val init: BigInt = 1) extends Module { val io = IO(new Bundle { val en = Input(Bool()) val in = Input(Bool()) val out = Output(UInt(n.W)) }) val state = RegInit(init.U(n.W)) ??? } class MyOptionalShiftRegisterTester(c: MyOptionalShiftRegister) extends PeekPokeTester(c) { val inSeq = Seq(0, 1, 1, 1, 0, 1, 1, 0, 0, 1) var state = c.init var i = 0 poke(c.io.en, 1) while (i < 10 * c.n) { // poke in repeated inSeq val toPoke = inSeq(i % inSeq.length) poke(c.io.in, toPoke) // update expected state state = ((state * 2) + toPoke) & BigInt("1"*c.n, 2) step(1) expect(c.io.out, state) i += 1 } } // test different depths for (i <- Seq(3, 4, 8, 24, 65)) { println(s"Testing n=$i") assert(chisel3.iotesters.Driver(() => new MyOptionalShiftRegister(n = i)) { c => new MyOptionalShiftRegisterTester(c) }) } println("SUCCESS!!")
パラメタライズ版を作ってみよう!な練習問題かと思いきや、その部分はスケルトンに含まれていた。 なので、実際に必要なのは、イネーブル信号 これを実際に実行してみると、以下のようにテストにパスすることが確認できる。 解答 - クリックすると開くので、見たくない場合は開かないように注意。
// n is the output width (number of delays - 1)
// init state to init
class MyOptionalShiftRegister(val n: Int, val init: BigInt = 1) extends Module {
val io = IO(new Bundle {
val en = Input(Bool())
val in = Input(Bool())
val out = Output(UInt(n.W))
})
val state = RegInit(init.U(n.W))
when (io.en) {
state := Cat(state, io.in)
}
io.out := state
}
class MyOptionalShiftRegisterTester(c: MyOptionalShiftRegister) extends PeekPokeTester(c) {
val inSeq = Seq(0, 1, 1, 1, 0, 1, 1, 0, 0, 1)
var state = c.init
var i = 0
poke(c.io.en, 1)
while (i < 10 * c.n) {
// poke in repeated inSeq
val toPoke = inSeq(i % inSeq.length)
poke(c.io.in, toPoke)
// update expected state
state = ((state * 2) + toPoke) & BigInt("1"*c.n, 2)
step(1)
expect(c.io.out, state)
i += 1
}
}
// test different depths
for (i <- Seq(3, 4, 8, 24, 65)) {
println(s"Testing n=$i")
assert(chisel3.iotesters.Driver(() => new MyOptionalShiftRegister(n = i)) {
c => new MyOptionalShiftRegisterTester(c)
})
}
println("SUCCESS!!")
en
の対応のみ。Testing n=3
[info] [0.000] Elaborating design...
[info] [0.015] Done elaborating.
Total FIRRTL Compile Time: 17.3 ms
Total FIRRTL Compile Time: 13.1 ms
End of dependency graph
Circuit state created
[info] [0.000] SEED 1540735247028
test cmd5HelperMyOptionalShiftRegister Success: 30 tests passed in 35 cycles taking 0.016585 seconds
[info] [0.015] RAN 30 CYCLES PASSED
Testing n=4
[info] [0.000] Elaborating design...
[info] [0.004] Done elaborating.
Total FIRRTL Compile Time: 9.9 ms
Total FIRRTL Compile Time: 13.1 ms
End of dependency graph
Circuit state created
[info] [0.000] SEED 1540735247112
test cmd5HelperMyOptionalShiftRegister Success: 40 tests passed in 45 cycles taking 0.011618 seconds
[info] [0.012] RAN 40 CYCLES PASSED
Testing n=8
[info] [0.000] Elaborating design...
[info] [0.004] Done elaborating.
Total FIRRTL Compile Time: 17.0 ms
Total FIRRTL Compile Time: 9.6 ms
End of dependency graph
Circuit state created
[info] [0.000] SEED 1540735247163
test cmd5HelperMyOptionalShiftRegister Success: 80 tests passed in 85 cycles taking 0.022463 seconds
[info] [0.023] RAN 80 CYCLES PASSED
Testing n=24
[info] [0.000] Elaborating design...
[info] [0.003] Done elaborating.
Total FIRRTL Compile Time: 11.4 ms
Total FIRRTL Compile Time: 10.3 ms
End of dependency graph
Circuit state created
[info] [0.000] SEED 1540735247228
test cmd5HelperMyOptionalShiftRegister Success: 240 tests passed in 245 cycles taking 0.035221 seconds
[info] [0.036] RAN 240 CYCLES PASSED
Testing n=65
[info] [0.000] Elaborating design...
[info] [0.014] Done elaborating.
Total FIRRTL Compile Time: 12.3 ms
Total FIRRTL Compile Time: 10.1 ms
End of dependency graph
Circuit state created
[info] [0.000] SEED 1540735247299
test cmd5HelperMyOptionalShiftRegister Success: 650 tests passed in 655 cycles taking 0.044734 seconds
[info] [0.044] RAN 650 CYCLES PASSED
SUCCESS!!
Appendix: 明示的なクロックとリセット
このModuleで書いてあったとおり、Chiselでは通常モジュール(Module
)を作成すると、クロックとリセットがそのモジュールの入力として定義される。また、本モジュールで学んだReg
はそのクロックとリセットを暗黙的に使用したレジスタとなる。
例えば、先ほどのシフトレジスタの演習で作ったモジュールのRTLを見てみると以下のようになっている(必要な部分だけ抜粋)
module cmd6HelperMyShiftRegister( // @[:@3.2] input clock, // @[:@4.4] input reset, // @[:@5.4] input io_in, // @[:@6.4] output [3:0] io_out // @[:@6.4] ); always @(posedge clock) begin // clock if (reset) begin // reset state <= 4'h1; end else begin state <= _T_11[3:0]; end end endmodule
見ての通り、clock
/reset
を使ったRTLが生成されている。
これでもそれなりに開発は可能だが、この挙動を変更したいというケースも多々あるはずだ。
例えば
- クロックやリセットを生成するブラックボックモジュールを作りたい
- マルチクロックの設計を行いたい
といったような場合だ。
Chiselはこれらの要望に応えられるような機能を提供している。クロックやリセットは以下の文法によってオーバーライドが可能だ
withClock() {}
withReset() {}
withClockAndReset() {}
例題:マルチクロックのモジュール
では早速例を見てみよう
// we need to import multi-clock features import chisel3.experimental.{withClock, withReset, withClockAndReset} class ClockExamples extends Module { val io = IO(new Bundle { val in = Input(UInt(10.W)) val alternateReset = Input(Bool()) val alternateClock = Input(Clock()) val outImplicit = Output(UInt()) val outAlternateReset = Output(UInt()) val outAlternateClock = Output(UInt()) val outAlternateBoth = Output(UInt()) }) val imp = RegInit(0.U(10.W)) imp := io.in io.outImplicit := imp withReset(io.alternateReset) { // everything in this scope with have alternateReset as the reset val altRst = RegInit(0.U(10.W)) altRst := io.in io.outAlternateReset := altRst } withClock(io.alternateClock) { val altClk = RegInit(0.U(10.W)) altClk := io.in io.outAlternateClock := altClk } withClockAndReset(io.alternateClock, io.alternateReset) { val alt = RegInit(0.U(10.W)) alt := io.in io.outAlternateBoth := alt } } println(getVerilog(new ClockExamples))
上記を実行すると以下のような出力が得られる。
[info] [0.000] Elaborating design... [info] [0.017] Done elaborating. Total FIRRTL Compile Time: 26.6 ms module cmd7HelperClockExamples( // @[:@3.2] input clock, // @[:@4.4] input reset, // @[:@5.4] input [9:0] io_in, // @[:@6.4] input io_alternateReset, // @[:@6.4] input io_alternateClock, // @[:@6.4] output [9:0] io_outImplicit, // @[:@6.4] output [9:0] io_outAlternateReset, // @[:@6.4] output [9:0] io_outAlternateClock, // @[:@6.4] output [9:0] io_outAlternateBoth // @[:@6.4] ); reg [9:0] imp; // @[cmd7.sc 14:20:@8.4] reg [31:0] _RAND_0; reg [9:0] _T_23; // @[cmd7.sc 20:25:@11.4] reg [31:0] _RAND_1; reg [9:0] _T_26; // @[cmd7.sc 26:25:@14.4] reg [31:0] _RAND_2; reg [9:0] _T_29; // @[cmd7.sc 32:22:@17.4] reg [31:0] _RAND_3; assign io_outImplicit = imp; assign io_outAlternateReset = _T_23; assign io_outAlternateClock = _T_26; assign io_outAlternateBoth = _T_29; `ifdef RANDOMIZE_GARBAGE_ASSIGN `define RANDOMIZE `endif `ifdef RANDOMIZE_INVALID_ASSIGN `define RANDOMIZE `endif `ifdef RANDOMIZE_REG_INIT `define RANDOMIZE `endif `ifdef RANDOMIZE_MEM_INIT `define RANDOMIZE `endif `ifdef RANDOMIZE integer initvar; initial begin `ifndef verilator #0.002 begin end `endif `ifdef RANDOMIZE_REG_INIT _RAND_0 = {1{$random}}; imp = _RAND_0[9:0]; `endif // RANDOMIZE_REG_INIT `ifdef RANDOMIZE_REG_INIT _RAND_1 = {1{$random}}; _T_23 = _RAND_1[9:0]; `endif // RANDOMIZE_REG_INIT `ifdef RANDOMIZE_REG_INIT _RAND_2 = {1{$random}}; _T_26 = _RAND_2[9:0]; `endif // RANDOMIZE_REG_INIT `ifdef RANDOMIZE_REG_INIT _RAND_3 = {1{$random}}; _T_29 = _RAND_3[9:0]; `endif // RANDOMIZE_REG_INIT end `endif // RANDOMIZE always @(posedge clock) begin if (reset) begin imp <= 10'h0; end else begin imp <= io_in; end if (io_alternateReset) begin _T_23 <= 10'h0; end else begin _T_23 <= io_in; end end always @(posedge io_alternateClock) begin if (reset) begin _T_26 <= 10'h0; end else begin _T_26 <= io_in; end if (io_alternateReset) begin _T_29 <= 10'h0; end else begin _T_29 <= io_in; end end endmodule
上記の実行結果から、先ほどの
withClock() {}
withReset() {}
withClockAndReset() {}
に対応する部分を抜粋してみよう
module cmd7HelperClockExamples( // @[:@3.2] input clock, // @[:@4.4] input reset, // @[:@5.4] input [9:0] io_in, // @[:@6.4] input io_alternateReset, // @[:@6.4] input io_alternateClock, // @[:@6.4] output [9:0] io_outImplicit, // @[:@6.4] output [9:0] io_outAlternateReset, // @[:@6.4] output [9:0] io_outAlternateClock, // @[:@6.4] output [9:0] io_outAlternateBoth // @[:@6.4] ); // implicit assign io_outImplicit = imp; always @(posedge clock) begin if (reset) begin imp <= 10'h0; end else begin imp <= io_in; end // withReset(io.alternateReset) { if (io_alternateReset) begin _T_23 <= 10'h0; end else begin _T_23 <= io_in; end end assign io_outAlternateReset = _T_23; // withClock(io.alternateClock) { assign io_outAlternateClock = _T_26; always @(posedge io_alternateClock) begin if (reset) begin _T_26 <= 10'h0; end else begin _T_26 <= io_in; end // withClockAndReset(io.alternateClock, io.alternateReset) { if (io_alternateReset) begin _T_29 <= 10'h0; end else begin _T_29 <= io_in; end end assign io_outAlternateBoth = _T_29; endmodule
一つのalways文で複数のレジスタの記述が入っているため、若干わかりにくいが元のChiselのコードでwith???
のブロック式で囲った部分の処理に対応するクロックとリセットが使用されたレジスタができているのがわかると思う。
なおここで以下の点には気をつけておいてほしいの但し書きがあったので、それについても記載しておく
reset
は常に同期リセットでかつBool
型となる- クロックはChiselの型の一つである
Clock
型となり、明示的に使用する場合にもそれが使われるべきである Bool
型の変数はasClock
を使うことでClock
に変換可能だが、それを使って愚かなことはしないように注意すべきだ- 現時点では
chisel-testers
はマルチクロックのデザインは完全にはサポートされていない
ラップアップ
ということで、Module2.4の最後の項目”ラップアップ”だ。ここには特に文法的な何かが書いてあるわけではないのだが、頑張ったな!!的なことが書いてあるので、紹介したい。
とうとうこのセクションをやりきったな!君はもうChiselでレジスタを宣言して順序回路を書く方法を学んだんだ。そしてそれは実際の回路を書くための基本的は方法を学んだことを意味している。
次のセクションでは一つの例題で学んだ各要素を結合していくことになる!もし、もう少し励みがほしいのであれば、このChiselエキスパートの言葉を思い出してほしい
とりあえず最低限の要素は学びきったから、もう自分の回路書けるぜ!!ってことのようで。Module2は残すところ2.5が残っている。
次のModule2.5ではModule2を通して学んだことを使った練習問題となっており、これまでの各文法を復習していくのにとても有益なので、次回はModule2.5の練習問題に取り組んでいくことにする。