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

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

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

## 3.1 Building your Project with sbt
Chiselプロジェクトのソースツリーの構成について紹介しています。
以下のgit cloneコマンドで取得したサンプルプロジェクトの構成とは相違点がありましたが、共通していたのは次の2点でした。

```
git clone https://github.com/schoeberl/chisel-examples.git 
```

```
git clone https://github.com/ucb-bar/chisel-template.git
```

root
　|______ src
　　　　　|______ main
　　　　　|______ test

- rootにbuild.sbt、README.mdが置いてある
- srcフォルダはハードウェア用のmainとテスター用のtestに分かれ、全てのソースコードが含まれている

また、生成したVerilogファイル用のフォルダを作成することが推奨されています。

### 名前空間の使用例
mypacketというpackegeを作成したとします。

```Scala
package mypack

import chisel3._

class Abc extends Module { 
  val io = IO(new Bundle{}) 
}
```

これを使用するには3通りの方法があります。

- mypacketをインポートする

```Scala
import mypack._

class AbcUser extends Module { 
  val io = IO(new Bundle{})
  val abc = new Abc()
}
```

- 完全修飾名mypack.Abcを使用する

```Scala
class AbcUser2 extends Module { 
  val io = IO(new Bundle{})
  val abc = new mypack.Abc()
}
```

- 対象のクラスのみをインポートする

```Scala
import mypack.Abc

class AbcUser3 extends Module { 
  val io = IO(new Bundle{})
  val abc = new Abc()
}
```
## 3.1.2 Running sbt
- srcツリーから全てのChiselコードを検索してコンパイルするコマンド

```
sbt run
```

- srcツリーのChiselコード内に存在するオブジェクトを直接指定するコマンド

```
sbt "runMain mypacket.MyObject"
```

- testツリーのChiselコード内に存在するオブジェクトを実行する場合のコマンド

```
sbt "test:runMain mypacket.MyTester"
```

## 3.2 Testing with Chisel
ハードウェア設計のテストは、テストベンチと呼ばれます。 
テストベンチは、テスト対象の設計（DUT）をインスタンス化し、入力ポートを駆動し、出力ポートを観察し、それらを期待値と比較します。 
Chiselは、PeekPokeTesterのテストベンチを提供します。

PeekPokeTesterを使用するには、次のパッケージをインポートする必要があります。 

```Scala
import chisel3._ 
import chisel3.iotesters._
```

回路テストは3つの要素から成ります。
（1）テスト対象のデバイス（DUT）
（2）テストロジック、テストベンチ
（3）テストを開始するmain関数を含むテスターオブジェクト

- AND回路のDUT

```Scala
class DeviceUnderTest extends Module { 
  val io = IO(new Bundle { 
    val a = Input(UInt(2.W)) 
    val b = Input(UInt(2.W)) 
    val out = Output(UInt(2.W)) 
  })
  
  io.out := io.a & io.b
}
```

- AND回路のテストベンチ

テストベンチはPeekPokeTesterを拡張し、引数dutの型に作成したDUTを用います。

```Scala
class TesterSimple(dut: DeviceUnderTest) extends 
    PeekPokeTester(dut) {
    
    
  poke(dut.io.a, 0.U) 
  poke(dut.io.b, 1.U) 
  step(1) 
  println("Result is: " + peek(dut.io.out).toString) 
  poke(dut.io.a, 3.U) 
  poke(dut.io.b, 2.U) 
  step(1) 
  println("Result is: " + peek(dut.io.out).toString)
}
```

PeekPokeTesterは、poke()で入力値を設定し、peek()で出力値を読み戻すことができます。
println()でその出力値を表示しています。
また、step(1)で1クロック進みます。

- AND回路のテスターオブジェクト

```Scala
object TesterSimple extends App { 
  chisel3.iotesters.Driver(() => new DeviceUnderTest()) { 
    c => new TesterSimple(c) 
  } 
}
```

テストを実行すると、結果がターミナルに以下のように表示されます。

```
[info] [0.004] SEED 1544207645120 
[info] [0.008] Result is: 0 
[info] [0.009] Result is: 2 
test DeviceUnderTest Success: 0 tests passed in 7 cycles 
taking 0.021820 seconds 
[info] [0.010] RAN 2 CYCLES PASSED
```

- 期待値を設定したテストベンチ
println()で出力を目視確認するのではなく、expect()で期待値通りになっているか自動検知できます。

```Scala
class TesterSimple(dut: DeviceUnderTest) extends PeekPokeTester(dut) {

  poke(dut.io.a, 3.U) 
  poke(dut.io.b, 1.U) 
  step(1) 
  expect(dut.io.out, 1) 
  poke(dut.io.a, 2.U) 
  poke(dut.io.b, 0.U) 
  step(1) 
  expect(dut.io.out, 0)
}
```

テストを実行して、期待値を満たした場合は、以下のようになります。

```
[info] [0.001] SEED 1544208437832 
test DeviceUnderTest Success: 2 tests passed in 7 cycles 
taking 0.018000 seconds 
[info] [0.009] RAN 2 CYCLES PASSED
```

期待値を満たさない場合は、以下のように期待値と実際の値の違いを説明するエラーメッセージが表示されます。

```
[info] [0.002] SEED 1544208642263 
[info] [0.011] EXPECT AT 2 io_out got 0 expected 4 FAIL 
test DeviceUnderTest Success: 1 tests passed in 7 cycles 
taking 0.022101 seconds 
[info] [0.012] RAN 2 CYCLES FAILED FIRST AT CYCLE 2
```

## 3.2.1 Using ScalaTest 
ツールScalaTestはChiselのテストにも使用できます。
使用するには build.sbt に以下の行を追加してやります。

```
libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.5" % "test"
```

テスト用のコードは通常、 src/test/scala の階層に置き、以下のコマンドで実行します。

```
sbt test
```

- Scalaの整数加算テスト
 
以下はScalaでの整数加算テストの一例です。

```Scala
import org.scalatest._

class ExampleSpec extends FlatSpec with Matchers {

  "Integers" should "add" in { 
    val i = 2 
    val j = 3 
    i + j should be (5) 
  }
}
```

- ChiselのScalaTest

ChiselでScalaTestを使用する場合、ChiselのテストをScalaTestのクラスに入れます

```Scala
class SimpleSpec extends FlatSpec with Matchers {

  "Tester" should "pass" in { 
    chisel3.iotesters.Driver(() => new DeviceUnderTest()) { 
      c => new TesterSimple(c) 
    } should be (true) 
  }
}
```

ScalaTestを使用する利点は、全てのテストを1度のコマンドsbt test だけで実行できることです。
個別のテストを行いたい場合は、以下のようにします。

```
sbt "testOnly SimpleSpec"
```

## 3.3 Exercise
### 3.3.1 A Minimal Project
サンプルプロジェクトについて説明しています。

```
git clone https://github.com/schoeberl/chisel-examples.git 
```

Hello.scalaは、Chiselを使用する為に、最初に以下をインポートしています。

```Scala
import chisel3._
```

Verilog記述を生成するには、 Appを拡張したScalaオブジェクトが必要です。Hello.scalaは以下のオブジェクトを持ちます。

```Scala
object Hello extends App { 
  chisel3.Driver.execute(Array[String](), () => new Hello()) 
}
```

このオブジェクトがしているのは、新規にHelloオブジェクトを作成し、それをChiselドライバーの実行関数に渡すことだけです。
実行関数の最初の引数は、ビルドオプションを設定できる文字列の配列です。
このオブジェクトは、アプリケーションを起動するmain関数を暗黙的に生成します。

以下のコマンドでverilogが生成されます。

```
sbt "runMain Hello"
```

常に最新のChiselバージョンを使用するようにするためにはbuild.sbtを修正します。

【修正前】

```
libraryDependencies += "edu.berkeley.cs" %% "chisel3" % "3.1.2"
```

【修正後】

```
libraryDependencies += "edu.berkeley.cs" %% "chisel3" % "latest.release"
```

サンプルプロジェクトにはMakefileがあるので、sbt "runMain Hello"としなくても、以下のコマンドだけでverilogを生成できます。（全てのファイルを実行するので、個別に実行したい場合はmakeは使用しない）

```
make
```

### 3.3.2 A Testing Exercise
#### AND回路のテスト
実際にsrc/test/scalaの階層にandTest.scalaとして以下を作成します。

```Scala:andTest.scala
import chisel3._
import chisel3.iotesters._

class DeviceUnderTest extends Module {
  val io = IO(new Bundle {
    val a = Input(UInt(2.W))
    val b = Input(UInt(2.W))
    val out = Output(UInt(2.W))
  })

  io.out := io.a & io.b
}

class TesterSimple(dut: DeviceUnderTest) extends PeekPokeTester(dut) {

  poke(dut.io.a, 3.U)
  poke(dut.io.b, 1.U)
  step(1)
  expect(dut.io.out, 1)
  poke(dut.io.a, 2.U)
  poke(dut.io.b, 0.U)
  step(1)
  expect(dut.io.out, 0)
}

object TesterSimple extends App {
  chisel3.iotesters.Driver(() => new DeviceUnderTest()) {
    c => new TesterSimple(c)
  }
}
```

```
sbt "test:runMain TesterSimple"
```

を実行すると、コンソールに結果が表示されます。

```
[info] [0.011] SEED 1568258438789
test DeviceUnderTest Success: 2 tests passed in 7 cycles taking 0.066788 seconds
[info] [0.040] RAN 2 CYCLES PASSED
```

#### ScalaTestによるAND回路のテスト
build.sbtに先述した行を追加し、src/test/scalaの階層にandScalaTest.scalaとして以下を作成します。
2つのクラス、DeviceUnderTestとSimpleSpecはandTest.scalaにおいて既に作成しているので、このファイルには不要です。

```Scala:andScalaTest.scala
import org.scalatest._

class SimpleSpec extends FlatSpec with Matchers {

  "Tester" should "pass" in { 
    chisel3.iotesters.Driver(() => new DeviceUnderTest()) { 
      c => new TesterSimple(c) 
    } should be (true) 
  }
}
```

```
sbt test
```
もしくは

```
sbt "testOnly SimpleSpec"
```

を実行すると、コンソールに結果が表示されます。

```
[info] SimpleSpec:
[info] Tester
[info] - should pass
[info] ScalaTest
[info] Run completed in 4 seconds, 400 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
```


#### Chap2 Exercise AND回路テスト　
[Chap2のExerciseで作成したAND回路](https://qiita.com/Kosuke_Matsui/items/be68930d121b13402e2a)をsrc/main/scalaの階層に置きます。

```Chap2.scala
import chisel3.Driver

class Chap2 extends Module {
  val io = IO(new Bundle {
    val sw = Input(UInt(3.W))
    val led = Output(UInt(1.W))
  })

  val blkReg = RegInit(0.U(1.W))

  val and = io.sw(0) & io.sw(1)
  blkReg := and
  io.led := blkReg 
}

/**
 * An object extending App to generate the Verilog code.
 */
object Chap2 extends App {
  chisel3.Driver.execute(Array[String](), () => new Chap2())
}
```


src/main/scalaの階層にswAndTest.scalaを作成します。

```Scala:swAndTest.scala
import chisel3._
import chisel3.iotesters._

class AndTester(dut: Chap2) extends PeekPokeTester(dut) {

  poke(dut.io.sw, 0.U)
  step(1)
  expect(dut.io.led, 0)
  poke(dut.io.sw, 1.U)
  step(1)
  expect(dut.io.led, 0)
  poke(dut.io.sw, 2.U)
  step(1)
  expect(dut.io.led, 0)
  poke(dut.io.sw, 3.U)
  step(1)
  expect(dut.io.led, 1)
}

object TesterExercise2 extends App {
  chisel3.iotesters.Driver(() => new Chap2()) {
    c => new AndTester(c)
  }
}
```



```
sbt "test:runMain TesterExercise2"
```

を実行すると、コンソールに結果が表示されます。

```
[info] [0.001] SEED 1568262348591
test Chap2 Success: 4 tests passed in 9 cycles taking 0.082073 seconds
[info] [0.036] RAN 4 CYCLES PASSED
[success] Total time: 17 s, completed 2019/09/12 13:25:52
```

#### ScalaTestによるChap2 Exercise AND回路テスト
src/test/scalaの階層にandScalaTest.scalaとして以下を作成します。
2つのクラス、CHap2とAndTesterはChap2.scalaとswAndTest.scalaにおいて既に作成しているので、このファイルには不要です。

```Scala:swAndScalaTest.scala
import org.scalatest._

class SwAndSpec extends FlatSpec with Matchers {

  "Tester" should "pass" in { 
    chisel3.iotesters.Driver(() => new Chap2()) { 
      c => new AndTester(c) 
    } should be (true) 
  }
}
```

```
sbt test
```
もしくは

```
sbt "testOnly SwAndSpec３"
```

を実行すると、コンソールに結果が表示されます。
sbt testを実行した場合、先ほど作成したSimpleSpecテストも実行しており、2つのテストに成功したというメッセージが確認できます。

```
Circuit state created
[info] [0.002] SEED 1568263361372
test DeviceUnderTest Success: 2 tests passed in 7 cycles taking 0.065557 seconds
[info] [0.019] RAN 2 CYCLES PASSED
[info] SimpleSpec:
[info] Tester
[info] - should pass
[info] [0.000] Elaborating design...
[info] [0.060] Done elaborating.
Total FIRRTL Compile Time: 109.2 ms
Total FIRRTL Compile Time: 97.4 ms
End of dependency graph
Circuit state created
[info] [0.000] SEED 1568263363988
test Chap2 Success: 4 tests passed in 9 cycles taking 0.058363 seconds
[info] [0.045] RAN 4 CYCLES PASSED
[info] SwAndSpec:
[info] Tester
[info] - should pass
[info] ScalaTest
[info] Run completed in 4 seconds, 132 milliseconds.
[info] Total number of tests run: 2
[info] Suites: completed 2, aborted 0
[info] Tests: succeeded 2, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[info] Passed: Total 2, Failed 0, Errors 0, Passed 2
[success] Total time: 9 s, completed 2019/09/12 13:42:44
```

## 参考
- [Chiselの文法 - 入門編 〜その10：Chiselのテスト機構〜](https://www.tech-diningyo.info/entry/2019/06/23/232207)
    - テストについて詳細で分かりやすい説明がされています。


## 関連記事

[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」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)
[Chisel入門書「Digital Design with Chisel」9章の勉強記録](https://qiita.com/Kosuke_Matsui/items/4c11d32c799800680f5c)