Help us understand the problem. What is going on with this article?

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を作成したとします。

package mypack

import chisel3._

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

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

  • mypacketをインポートする
import mypack._

class AbcUser extends Module { 
  val io = IO(new Bundle{})
  val abc = new Abc()
}
  • 完全修飾名mypack.Abcを使用する
class AbcUser2 extends Module { 
  val io = IO(new Bundle{})
  val abc = new mypack.Abc()
}
  • 対象のクラスのみをインポートする
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を使用するには、次のパッケージをインポートする必要があります。

import chisel3._ 
import chisel3.iotesters._

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

  • AND回路のDUT
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を用います。

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回路のテスターオブジェクト
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()で期待値通りになっているか自動検知できます。
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での整数加算テストの一例です。

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のクラスに入れます

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を使用する為に、最初に以下をインポートしています。

import chisel3._

Verilog記述を生成するには、 Appを拡張したScalaオブジェクトが必要です。Hello.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として以下を作成します。

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において既に作成しているので、このファイルには不要です。

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回路を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を作成します。

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において既に作成しているので、このファイルには不要です。

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 SwAndSpec3"

を実行すると、コンソールに結果が表示されます。
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入門書「Digital Design with Chisel」1章の勉強記録
Chisel入門書「Digital Design with Chisel」2章の勉強記録
Chisel入門書「Digital Design with Chisel」4章の勉強記録
Chisel入門書「Digital Design with Chisel」5章の勉強記録
Chisel入門書「Digital Design with Chisel」6章の勉強記録
Chisel入門書「Digital Design with Chisel」7章の勉強記録
Chisel入門書「Digital Design with Chisel」8章の勉強記録
Chisel入門書「Digital Design with Chisel」9章の勉強記録

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away