前回までで、Chiselのイントロダクションを完了した。 しかし実際に使ってみなければどのように使えばよいのかは分からない。 いろいろ作って試してみよう。
Chiselを簡単に試すことのできるプロジェクトテンプレート
上記のプロジェクトを使えば、Chiselをいろいろ試すことができる。 簡単なチュートリアルも付いているので、それを見ながら試すことができる。
Chiselをコンパイルするためのsbtインストール
ChiselはScalaをベースに記述されているので、もちろんScalaのビルド環境が必要になる。
正直Scalaについてはさっぱり分からないが、Ubuntuを使ったらビルド環境は簡単に構築することができる。apt-get install
するだけである。
以下のページを参考にした。
sbt Reference Manual — Linux への sbt のインストール
echo "deb https://dl.bintray.com/sbt/debian /" | sudo tee -a /etc/apt/sources.list.d/sbt.list git fetch originsudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 2EE0EA64E40A89B84B2DF73499E82A75642AC823 sudo apt-get update sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 2EE0EA64E40A89B84B2DF73499E82A75642AC823 sudo apt-get update sudo apt-get install sbt
sbtをインストールしたら、次はChisel Project TemplateをCloneしてみる。
git clone https://github.com/ucb-bar/chisel-template.git
cd chisel-template
Chiselをプロジェクトを実行してみる。
sbt test
すると、GCDを動作させる回路が生成され、テストが実行される。Passしたようだ。
STARTING test_run_dir/example.test.GCDTester168775820/VGCD [info] [0.000] SEED 1502420920692 Enabling waves.. Exit Code: 0 [info] [0.135] RAN 1102 CYCLES PASSED [info] GCDTester: [info] GCD [info] - should calculate proper greatest common denominator (with firrtl) [info] GCD [info] - should calculate proper greatest common denominator (with verilator) [info] ScalaTest [info] Run completed in 3 seconds, 540 milliseconds. [info] Total number of tests run: 2 [info] Suites: completed 1, 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: 31 s, completed 2017/08/11 12:08:42
このGCDを計算するソースコードとテストプログラムは以下に置かれている。
GCDプログラム
gcdはChiselで以下のように記述されている。
class GCD extends Module { val io = IO(new Bundle { val a = Input(UInt(16.W)) val b = Input(UInt(16.W)) val e = Input(Bool()) val z = Output(UInt(16.W)) val v = Output(Bool()) }) val x = Reg(UInt()) val y = Reg(UInt()) when (x > y) { x := x - y } .otherwise { y := y - x } when (io.e) { x := io.a; y := io.b } io.z := x io.v := y === 0.U }
まずは前回までのチュートリアルを見ながら、ソースコードを読み取る。
- 入出力ポート
GCDの回路では、入力としてa
:16bit, b
:16bit, e
:1bitを定義している。a
,b
はGCDの初期値だが、e
は計算開始を示すスタート制御信号である。
出力としては、GCDの計算終了したことを示すv
:1bitであり、そのときの答えをz
:16bit として定義している。
すぐに分かるが、レジスタとしてそれぞれx
とy
を定義している。
- ScalaのSInt、IntとChiselのUIntは異なる
Scalaの型とは別に、ChiselではUIntという型を定義している。
これはそのままハードウェアのwire
やreg
に変換される方と考えてよい。
基本的にScalaの整数型とUIntとは別物だ。互換性が無く、変換するためには変換用のメソッドをかませなければならない。
基本的にChiselの回路定義中は、UIntを使って書くのが望ましい。 一方で、テストプログラムや周りのサポートプログラムでは、ScalaのIntを使って記述しても構わない。
- GCDの計算部分
入出力ポートにはio.x
などという記述でアクセスする。
io.e
がアサートされた場合に、まずは初期値を内部レジスタx
, y
に格納する。
次に、when
以下で記述されている記述でx
とy
を計算していく。
:=
は、レジスタへの代入と考えてほぼ間違いない。x
をアップデートしているので、1サイクル毎に計算し、その結果をx
とy
に格納していく。
最後に、y
が0になった時点で終了という訳だ。
- GCD回路の計算時間
つまり、この回路はGCDを計算するのに、y
が0になるまでのステップ分サイクル数が必要になる。
GCDのテストプログラム
作成したテストプログラムをテストするために、以下のScalaテストプログラムが用意されている。
テストプログラムの構造
GCD回路のテストプログラムは以下のようになっており、
- scala自身でGCDを計算して答え合わせをするためのプログラム
computeGcd
computeGcd
を呼び出して実際にテストを実行するプログラム
に分けられている。computeGcd
は答えとしてGCDの計算結果と、計算にかかったステップ数(depth
)を返すような仕組みになっている。
class GCDUnitTester(c: GCD) extends PeekPokeTester(c) { /** * compute the gcd and the number of steps it should take to do it * * @param a positive integer * @param b positive integer * @return the GCD of a and b */ def computeGcd(a: Int, b: Int): (Int, Int) = { var x = a var y = b var depth = 1 while(y > 0 ) { if (x > y) { x -= y } else { y -= x } depth += 1 } (x, depth) } private val gcd = c for(i <- 1 to 40 by 3) { for (j <- 1 to 40 by 7) { poke(gcd.io.a, i) poke(gcd.io.b, j) poke(gcd.io.e, 1) step(1) poke(gcd.io.e, 0) val (expected_gcd, steps) = computeGcd(i, j) step(steps - 1) // -1 is because we step(1) already to toggle the enable expect(gcd.io.z, expected_gcd) expect(gcd.io.v, 1) } } }
まず、poke
というのは、Chiselで記述した回路の入力ポートに対してデータを与える。まず、
poke(gcd.io.a, i) // 入力ポートaに対して初期値を設定 poke(gcd.io.b, j) // 入力ポートbに対して初期値を設定 poke(gcd.io.e, 1) // 計算開始 step(1) // 1サイクル待つ poke(gcd.io.e, 0) // 計算開始信号をdeassert
computeGcd
で必要サイクル数まで計算できているので、そのサイクル数分だけ待って、(step(steps - 1)
) 計算結果を確認する。
計算結果を突き合わせるのには、expect()
を使う。
第一引数が回路側の答え、第2引数がテストプログラムの答えだ。
つまり第1引数の型がUInt
、第2引数の型がInt
であることを想定している(逆にしたら動作しなかった)。
何回かテストを行って、すべて回路の答えとテストプログラムの答えが一致したら合格、というわけだ。
さて、ここまででGCD回路をChiselで記述して、テストする方法は分かった。 次に、実際に自分で数値ベクトルの値をリダクションして加算する回路をChiselで記述して、Scalaを使ってテストし、Verilogファイルを生成してみる。