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

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

Chisel Bootcamp - 幕間(7) - カウンタ(Counter)

スポンサーリンク

前回のChiselの記事では標準ライブラリに含まれているマルチプレクサに関するブロック(PriorityMux/Mux1H)を紹介した。

www.tech-diningyo.info

今日は幕間で紹介されているChiselのライブラリとしては最後になるCounterについて見ていく。

Module 3.2幕間: Chiselの標準ライブラリ

カウンタ

そのまんま”カウンタ”である。Chiselの標準ライブラリのカウンタの機能は

  • 特定の値になるまで1サイクル毎に1ずつカウントアップ

である。

なお、Chiselの標準ライブラリで提供されるCounterはモジュールでは無い点は覚えておく必要がある(詳細は後述)

では早速サンプルコードを確認していこう。もう最後で今更だけど、今回はテスト対象のモジュールを直接Module内に実装するのではなく、classとして独立させた。

class MyCounter extends Module {
  val io = IO(new Bundle {
    val count = Input(Bool())
    val out = Output(UInt(2.W))
  })
    
  val counter = Counter(3) // 3-カウントのカウンタ (出力の範囲は[0...2]になる)
    
  when(io.count) {
    counter.inc()      
  }
   
  io.out := counter.value
}

Driver(() => new MyCounter) {
    c => new PeekPokeTester(c) {
    poke(c.io.count, 1)
    println(s"start: counter value=${peek(c.io.out)}")
  
    step(1)
    println(s"step 1: counter value=${peek(c.io.out)}")
  
    step(1)
    println(s"step 2: counter value=${peek(c.io.out)}")
  
    poke(c.io.count, 0)
    step(1)
    println(s"step without increment: counter value=${peek(c.io.out)}")
  
    poke(c.io.count, 1)
    step(1)
    println(s"step again: counter value=${peek(c.io.out)}")
} }

上記を動作させると以下の出力が得られる。

[info] [0.001] Elaborating design...
[info] [0.695] Done elaborating.
Total FIRRTL Compile Time: 207.9 ms
Total FIRRTL Compile Time: 15.5 ms
End of dependency graph
Circuit state created
[info] [0.001] SEED 1545749900235
[info] [0.003] start: counter value=0
[info] [0.003] step 1: counter value=1
[info] [0.004] step 2: counter value=2
[info] [0.005] step without increment: counter value=2
[info] [0.006] step again: counter value=0
test cmd2HelperMyCounter Success: 0 tests passed in 9 cycles taking 0.015189 seconds
[info] [0.007] RAN 4 CYCLES PASSED

テストコードの中ではpoke(c.io.count, x)で設定を行い、stepを使って1サイクルずつサイクルを進めて、その時のカウント値を表示している。

"step 2"の実行後にio.countを0に設定してサイクルを進めると、カウントの値が止まっていることからio.countがカウントアップのイネーブルとして動作する仕組みになっていることがわかると思う。

生成されるRTL

先ほどのMyCounterをRTLに変換すると以下のコードが得られる。ご覧になってわかる通りChiselのCounterに相当する部分はカウンターモジュールがインスタンスされるわけではなくreg [1:0] valueとして実装されている。

これが冒頭に記載した"Chiselの標準ライブラリで提供されるCounterはモジュールでは無い"という意味だ。

module cmd4HelperMyCounter( // @[:@3.2]
  input        clock, // @[:@4.4]
  input        reset, // @[:@5.4]
  input        io_count, // @[:@6.4]
  output [1:0] io_out // @[:@6.4]
);
  reg [1:0] value; // @[Counter.scala 26:33:@8.4]
  reg [31:0] _RAND_0;
  wire  _T_12; // @[Counter.scala 34:24:@10.6]
  wire [2:0] _T_14; // @[Counter.scala 35:22:@11.6]
  wire [1:0] _T_15; // @[Counter.scala 35:22:@12.6]
  wire [1:0] _GEN_0; // @[Counter.scala 37:21:@14.6]
  wire [1:0] _GEN_1; // @[cmd4.sc 9:18:@9.4]
  assign _T_12 = value == 2'h2; // @[Counter.scala 34:24:@10.6]
  assign _T_14 = value + 2'h1; // @[Counter.scala 35:22:@11.6]
  assign _T_15 = _T_14[1:0]; // @[Counter.scala 35:22:@12.6]
  assign _GEN_0 = _T_12 ? 2'h0 : _T_15; // @[Counter.scala 37:21:@14.6]
  assign _GEN_1 = io_count ? _GEN_0 : value; // @[cmd4.sc 9:18:@9.4]
  assign io_out = value;
  always @(posedge clock) begin
    if (reset) begin
      value <= 2'h0;
    end else begin
      if (io_count) begin
        if (_T_12) begin
          value <= 2'h0;
        end else begin
          value <= _T_15;
        end
      end
    end
  end
endmodule

Counterソースコード

ソースコードがどうなっているかも確認しておこう。

実際に呼び出しているのは、これまでにもいくつか見てきたCounterobjectになっている。このためただ単にConuterと記載して呼び出すとapplyが暗黙的に呼びだされて、その結果が返却されることになる。

Counterapplyではclass Counterインスタンスしている。

object Counter
{
  /** Instantiate a [[Counter! counter]] with the specified number of counts.
    */
  def apply(n: Int): Counter = new Counter(n)

  /** Instantiate a [[Counter! counter]] with the specified number of counts and a gate.
   *
    * @param cond condition that controls whether the counter increments this cycle
    * @param n number of counts before the counter resets
    * @return tuple of the counter value and whether the counter will wrap (the value is at
    * maximum and the condition is true).
    */
  @chiselName
  def apply(cond: Bool, n: Int): (UInt, Bool) = {
    val c = new Counter(n)
    var wrap: Bool = null
    when (cond) { wrap = c.inc() }
    (c.value, cond && wrap)
  }
}

では、実際にインスタンスされるCounterクラスはどうなってるのかを見てみよう。

@chiselName
class Counter(val n: Int) {
  require(n >= 0)
  val value = if (n > 1) RegInit(0.U(log2Ceil(n).W)) else 0.U

  /** Increment the counter, returning whether the counter currently is at the
    * maximum and will wrap. The incremented value is registered and will be
    * visible on the next cycle.
    */
  def inc(): Bool = {
    if (n > 1) {
      val wrap = value === (n-1).asUInt
      value := value + 1.U
      if (!isPow2(n)) {
        when (wrap) { value := 0.U }
      }
      wrap
    } else {
      true.B
    }
  }
}

上記の様にシンプルな構成となっており、以下の2つを行っている。

classの宣言部分を見ると一目瞭然だが、extends Moduleを行っていないためChiselのモジュールでは無いことがわかる。

とりあえずChisel-Bootcampの"3.2 Interlude"としてはChiselの標準ライブラリに関する紹介はここまでになる。この幕間に入って最初↓に書いた記事では、まだいくつかライブラリを記載しているのだが、それはまた時間を見つけて確認してくことにする。

www.tech-diningyo.info

ということで、今日はここまで。

次回からはModule3.3に入り高階関数についてを勉強していく。