Chisel入門書「Digital Design with Chisel」9章の勉強記録

# 記事の概要
Chiselの入門書「Digital Design with Chisel」の9章の勉強記録です。
本文の概要を備忘録として整理し、また実際に行った演習を紹介します。

本のpdfデータとプログラム一式は無料で以下から入手できます。
https://raw.githubusercontent.com/wiki/schoeberl/chisel-book/chisel-book.pdf
https://github.com/schoeberl/chisel-book

## 9.1 Conﬁgure with Parameters
Chiselでコンポーネントや関数をパラメーターを指定して構成する方法を紹介しています。

### 9.1.1 Simple Parameters
ビット幅をパラメータとして定義する例

- 加算器

```Scala
class ParamAdder(n: Int) extends Module { 
  val io = IO(new Bundle{ 
    val a = Input(UInt(n.W)) 
    val b = Input(UInt(n.W)) 
    val c = Output(UInt(n.W)) 
  })

  io.c := io.a + io.b
}
```

以下のようにパラメータにビット幅を定義して使用します。

```Scala
val add8 = Module(new ParamAdder(8)) 
val add16 = Module(new ParamAdder(16))
```

### 9.1.2 Functions with Type Parameter
型ををパラメータとして定義する例

- マルチプレクサ

```Scala
def myMux[T <: Data](sel: Bool, tPath: T, fPath: T): T = {

  val ret = WireInit(fPath) 
  when (sel) { 
    ret := tPath 
  } 
  ret
}
```

ここでは、型パラメータTに対して、"[T <: Data]"のように上限境界<:が使用されています。
これは型パラメータTには、Chisel型DataおよびDataのサブクラスしか使用できないことを意味しています。
また、tPathとfPathは型が一致していないといけません。

```Scala
val resA = myMux(selA, 5.U, 10.U)
```

以下のように、tPathとfPathの型が異なるとエラーになります。

```Scala
val resErr = myMux(selA, 5.U, 10.S)
```

パラメータにData型"Uint"ではなく、Dataのサブ型を使用する例を以下に示します。
Bundleにより2つのフィールドを持つタイプを定義します。

```Scala
class ComplexIO extends Bundle {
  val d = UInt(10.W) 
  val b = Bool()
}
```

まず、ワイヤを作成してから、サブフィールドを設定することで、定数を定義します。 
すると、この複合型をパラメータに使用して、マルチプレクサを作成できます。

```Scala
val tVal = Wire(new ComplexIO) 
tVal.b := true.B 
tVal.d := 42.U 
val fVal = Wire(new ComplexIO) 
fVal.b := false.B 
fVal.d := 13.U

// The mulitplexer with a complex type 
val resB = myMux(selB, tVal, fVal)
```

### Wireを初期化せずに使用したい場合
関数myMuxにおいては、WireInitを使用して、初期値を持つ型パラメータTのワイヤを作成しました。
そのため、初期値を持たないワイヤはtPathやfPathに使用できません。

もし、初期値を持たない型のみのワイヤを使用したい場合、fPath.cloneTypeを使用します。

```Scala
def myMuxAlt[T <: Data](sel: Bool, tPath: T, fPath: T): T = {

  val ret = Wire(fPath.cloneType) 
  ret := fPath 
  when (sel) { 
    ret := tPath 
  } 
  ret
}
```
<font color="red">ここでcloneTypeを使用する理由が分かりませんでした。
cloneTypeはBundleで作成した型をテンプレートとして使用する時に、自動でクローンを作成ができない場合に必要になるという理解でいます。
Wireを初期化なしで使用することと何の関係があるのか分かりませんでした。初期値がないから自動でクローン作成できないので必要になるとか？</font>


### 9.1.3 Modules with Type Parameters
以下のようにモジュールでもパラメーターを使用できます。

クラスNocRouterを定義する際、インターフェースのデータ型をハードコードせずにパラメータ化します。また、ポートの数nもパラメータ化しておきます。

```Scala
class NocRouter[T <: Data](dt: T, n: Int) extends Module { 
  val io =IO(new Bundle { 
    val inPort = Input(Vec(n, dt)) 
    val address = Input(Vec(n, UInt(8.W))) 
    val outPort = Output(Vec(n, dt)) 
  })

// Route the payload according to the address 
// ...
```
NocRouterを使用するには、まず型パラメータTをBundleにより定義します。

```Scala
class Payload extends Bundle { 
  val data = UInt(16.W) 
  val flag = Bool() 
}
```

これを用いてインスタンスを作成できます。

```Scala
val router = Module(new NocRouter(new Payload, 2))
```
 
### 9.1.4 Parametrize Bundles
NocRouterは入力に2つのフィールドベクトルを使用しました。この2つはアドレス用とデータ用でしたが、これらをパラメータ化されたBundleでクラスPortにまとめます。

```Scala
class Port[T <: Data](private val dt: T) extends Bundle { 
  val address = UInt(8.W) 
  val data = dt.cloneType 
}
```

<font color="red">（理解があいまいなので、以下の説明には誤りがあるかもしれません。）</font>
Bundleをテンプレートとして使うと、同じ型を持つ新しいインスタンスを作成します。その際に、多くの場合は自動でクローンを作成してくれます。
ですが、Bundleがパラメータを持つ場合（上記ではdtの場合）、Chiselはそれをクローンできないので、手動でcloneTypeを実装しないといけません。

また、Bundleの型をクローンする際、例えばvecとして使用する場合、このパラメータがpublicフィールドになると邪魔なのでdtをprivateにしています。
<font color="red">（何が邪魔になるのか私は理解できていません。）</font>

また、クラスNocRouter2を以下のように定義します。

```Scala
class NocRouter2[T <: Data](dt: T, n: Int) extends Module { 
  val io =IO(new Bundle { 
    val inPort = Input(Vec(n, dt)) 
    val outPort = Output(Vec(n, dt)) 
  })

// Route the payload according to the address 
// ...
```

これらを用いて9.1.3と同様のインスタンスを作成できます。

```Scala
val router = Module(new NocRouter2(new Port(new Payload), 2))
```

## 9.2 Generate Combinational Logic
### ファイル「data.txt」の読み取り

Scala標準ライブラリSourceクラスを用いて、ファイル「data.txt」をハードウェア生成時に論理テーブルへ読み取ることができます。

```Scala
import chisel3._ 
import scala.io.Source

class FileReader extends Module { 
  val io = IO(new Bundle { 
    val address = Input(UInt(8.W)) 
    val data = Output(UInt(8.W)) 
  })
  
  val array = new Array[Int](256) 
  var idx = 0
  
  // read the data into a Scala array 
  val source = Source.fromFile("data.txt") 
  for (line <- source.getLines()) { 
    array(idx) = line.toInt 
    idx += 1 
  }
  
  // convert the Scala integer array into the Chisel type Vec 
  val table = VecInit(array.map(_.U(8.W)))
  
  // use the table 
  io.data := table(io.address)
}
```

"val table = VecInit(array.map(_.U(8.W)))"について補足します。

Scala配列は、マッピング関数mapをサポートするSeqに暗黙裡に変換されます。
\_.U(8.W)は、Scalaの配列の各Int値を表わす\_を8ビット幅のUInt値にします。
VecInitによりChisel型のSeqからVecが作成されます。

### BCD変換
2進数を2進化10進数（BCD）表現へ変換します。
例えば13は、2進数では1101で、BCDでは1_3=b0001_0011となります。

Chiselではハードウェア生成時にバイナリからBCDへの変換テーブルも生成できます。

```Scala
import chisel3._

class BcdTable extends Module { 
  val io = IO(new Bundle { 
    val address = Input(UInt(8.W)) 
    val data = Output(UInt(8.W)) 
  })
  
  val array = new Array[Int](256)

  // Convert binary to BCD 
  for (i <- 0 to 99) { 
    array(i) = ((i/10)<<4) + i%10 
  }
  
  val table = VecInit(array.map(_.U(8.W))) 
  io.data := table(io.address)
}
```

## 9.3 Use Inheritance
Chiselはオブジェクト指向言語なので継承が使用できます。

まず抽象クラスTickerを考えます。
抽象クラスの作り方は、classキーワードの前にabstractをつけます。

```Scala
abstract class Ticker(n: Int) extends Module { 
  val io = IO(new Bundle{ 
    val tick = Output(Bool()) 
  }) 
}
```

次にTickerを拡張し、TickerのサブクラスUpTickerを作成します。
UpTickerはカウントアップするカウンターを持ちます。

```Scala
class UpTicker(n: Int) extends Ticker(n) {

  val N = (n-1).U

  val cntReg = RegInit(0.U(8.W))

  cntReg := cntReg + 1.U 
  when(cntReg === N) { 
    cntReg := 0.U 
  }
  
  io.tick := cntReg === N
}
```

Tickerのサブクラスが複数あった場合、その全てを単一のテストベンチで試験できます。

```Scala
import chisel3.iotesters.PeekPokeTester 
import org.scalatest._

class TickerTester[T <: Ticker](dut: T, n: Int) extends 
    PeekPokeTester(dut: T) {
    
  // -1 is the notion that we have not yet seen the first tick 
  var count = -1 
  for (i <- 0 to n * 3) { 
    if (count > 0) { 
      expect(dut.io.tick, 0) 
    } 
    if (count == 0) { 
      expect(dut.io.tick, 1) 
    } 
    val t = peek(dut.io.tick) 
    // On a tick we reset the tester counter to N-1, 
    // otherwise we decrement the tester counter 
    if (t == 1) { 
      count = n-1 
    } else { 
      count -= 1 
    }
    
    step(1)
  }
}
```

このテストベンチには3つのパラメータがあります。

- TickerまたはTickerを継承するクラスのみを使用可能とする型パラメータ[T <：Ticker]
- 型パラメータTもしくはそのサブタイプであるテスト対象の設計dut
- 各tickに要すると思われるクロックサイクル数n


ここで更に2つのTickerのサブクラスを定義します。

サブクラスDownTickerはカウントダウンするカウンターを持ち、サブクラスNerdTickerは[6.2.3でも紹介した-1までカウントし、最上位ビットを見てカウントリセットするカウンター](https://qiita.com/Kosuke_Matsui/items/dd526ae4d59f30d1f586)を持ちます。


```Scala
class DownTicker(n: Int) extends Ticker(n) {

  val N = (n-1).U

  val cntReg = RegInit(N)
  
  cntReg := cntReg - 1.U 
  when(cntReg === 0.U) { 
    cntReg := N 
  }
  
  io.tick := cntReg === N
}
```


```Scala
class NerdTicker(n: Int) extends Ticker(n) {

  val N = n

  val MAX = (N - 2).S(8.W) 

  val cntReg = RegInit(MAX) 
  io.tick := false.B
  
  cntReg := cntReg - 1.S 
  when(cntReg(7)) { 
    cntReg := MAX 
    io.tick := true.B 
  }
}
```

この3つのバージョンの試験を先の1つのテストベンチで実行できます。

```Scala
class TickerSpec extends FlatSpec with Matchers {

  "UpTicker 5" should "pass" in { 
    chisel3.iotesters.Driver(() => new UpTicker(5)) { c => 
      new TickerTester(c, 5) 
    } should be (true) }
    
  "DownTicker 7" should "pass" in { 
    chisel3.iotesters.Driver(() => new DownTicker(7)) { c => 
      new TickerTester(c, 7) 
    } should be (true) 
  }

  "NerdTicker 11" should "pass" in { 
    chisel3.iotesters.Driver(() => new NerdTicker(11)) { c => 
      new TickerTester(c, 11) 
    } should be (true) 
  }
}
```

以下のコマンドでこれらの試験は実行されます

```
sbt "testOnly TickerSpec"
```

## 参考
- [Chiselの文法 - 入門編 〜その8：回路のビット幅のパラメタライズとrequire〜](https://www.tech-diningyo.info/entry/2019/06/09/143354)
    - パラメータ化について分かりやすい説明がされています。
- [Chiselの文法 - 入門編 〜その9：I/Oポートのパラメタライズ〜](https://www.tech-diningyo.info/entry/2019/06/18/233600)
    - I/Oポートのパラメータ化について分かりやすい説明がされています。


## 関連記事

[Chisel入門書「Digital Design with Chisel」1章の勉強記録](https://qiita.com/Kosuke_Matsui/items/80dde1219e3ce02c4b66)
[Chisel入門書「Digital Design with Chisel」2章の勉強記録](https://qiita.com/Kosuke_Matsui/items/be68930d121b13402e2a)
[Chisel入門書「Digital Design with Chisel」3章の勉強記録](https://qiita.com/Kosuke_Matsui/items/b82edca18089084350c0)
[Chisel入門書「Digital Design with Chisel」4章の勉強記録](https://qiita.com/Kosuke_Matsui/items/23800cf34fb435225ec9)
[Chisel入門書「Digital Design with Chisel」5章の勉強記録](https://qiita.com/Kosuke_Matsui/items/847665e371185f14e735)
[Chisel入門書「Digital Design with Chisel」6章の勉強記録](https://qiita.com/Kosuke_Matsui/items/dd526ae4d59f30d1f586)
[Chisel入門書「Digital Design with Chisel」7章の勉強記録](https://qiita.com/Kosuke_Matsui/items/53a9e61facbc1cc79025)
[Chisel入門書「Digital Design with Chisel」8章の勉強記録](https://qiita.com/Kosuke_Matsui/items/3ab4be6bdba6d94be10b)