ハードウェアの気になるあれこれ

技術的に興味のあることを調べて書いてくブログ。主にハードウェアがネタ。

Chisel Bootcamp - Module2.4 (3) - マルチクロック&マルチリセット

スポンサーリンク

前回の記事ではChisel BootcampのModule2.4で学んだレジスタを使ったフロー制御を学んだ。

www.tech-diningyo.info

今回はModule2.4の残りである練習問題に取り組んでいく。

Module 2.4: 順序回路

練習問題

練習問題:シフトレジスタ

問題についてはそのまんま引用させていただく。

このモジュールで得たレジスタの知識を使ってLSFRのためのシフトレジスタを作ってみよう。

仕様は以下のとおりだ:

  • 全ての要素は1-bit幅

  • 4-bitの出力を持つ

  • 1-bitの入力をもらい、それがシフトレジスタへの次の入力となる

  • 出力はシフトレジスタの各ビットを並列にしたもので、シフトレジスタの最後の要素がMSB、最初の要素がLSBとなる。Catが役に立つはずだ

  • 出力の初期値はb0001とする

  • シフト処理は1サイクル毎に行う(enable信号無し)

  • **Chiselでは次のようなビット指定のアサインは文法エラーとなる

    scala out(0) := io.in

    f:id:diningyo-kpuku-jougeki:20181028234330p:plain

基本的なモジュールの雛形、テストベクタ、ドライバーは以下のものとする。最初のレジスタについては既に定義済みにしてある。

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エキスパートの言葉を思い出してほしい

http://i.qkme.me/3qbd5u.jpg

とりあえず最低限の要素は学びきったから、もう自分の回路書けるぜ!!ってことのようで。Module2は残すところ2.5が残っている。

次のModule2.5ではModule2を通して学んだことを使った練習問題となっており、これまでの各文法を復習していくのにとても有益なので、次回はModule2.5の練習問題に取り組んでいくことにする。