ScalaとChiselで電子回路をつくる高位合成の話

2015年12月20日日曜日

このエントリはHDL Advent Calendarの20日目のものです。
同時に、2015/08/24-25にクパチーノでおこなわれたHot Chips 27カンファレンスへ参加し、そこで紹介されていたRISC-Vに興味を持った関連エントリです。
このカンファレンスではQUALCOMMの新DSPアーキテクチャ発表や5Gネットワークに関する講演、MicrosoftのSmartNICなど興味深いトピックが多く扱われていました。
この中で私が最も強く関心を持ったのがScalaからの高位合成ツールであるChiselです。

Chiselの概要

Chiselは次のような特徴を持ちます。
  • 入力言語にScalaを用いたオープンソースの高位合成ツールキット
    • 出力はVerilog
  • クロック同期保証つきのシミュレータコード(C++)を書き出すことができ、これによって大規模チップの開発イテレーションすら比較的高速に回せるとしている
    • UC Berkeleyで開発され、RISC-Vの開発に利用されている
私自身はScalaをよく分かっているわけではありませんが、別段Verilogへの習熟度が高いわけでもないので、IntelliJ IDEAを使ってサクサクとコードを書ける分だけScalaのほうが楽かな、という感じで興味を持ちました。

とにかくChiselを触ってみよう

何はともあれ、半加算器を作るところからスタートしてみます。
やることは
  • 入力値同士を足してキャリーを発生させる
だけなのですが、主にScalaに不慣れなあたりでハマりました。ざっと失敗した箇所を挙げてみます。
  • .otherwiseの最後のブレースを抜かしてコンパイルエラー
  • BoolをBOOLと書いてコンパイルエラー
  • := を =としていて再定義エラー
  • Bool宣言したものにそのままのboolean値を突っ込もうとして型ミスマッチエラー(Bool(true)な必要ある)
    • implicit type conversion入れといて欲しい・・・
  • expectにboolean値もBool(...)も渡せない問題
    • ここはBitsとして扱うしかなさそう
このあたりはScala自体の学習が圧倒的に足りないので、Scala関数型デザイン&プログラミング—Scalazコントリビューターによる関数型徹底ガイドを読んで勉強中です。
ともかく、以下のようなコードが書けました。テストコードもゴリゴリと書いています。
package Td4Scala

import Chisel._

class HalfAdder extends Module {
        val io = new Bundle {
                val in0 = Bool(INPUT)
                val in1 = Bool(INPUT)
                val out = Bool(OUTPUT)
                val carry = Bool(OUTPUT)
        }
        when (io.in0 ^ io.in1) {
                io.out := Bool(true)
        } .otherwise {
                io.out := Bool(false)
        }
        when (io.in0 && io.in1) {
                io.carry := Bool(true)
        } .otherwise {
                io.carry := Bool(false)
        }
}

class HalfAdderTests(c: HalfAdder) extends Tester(c) {
        poke(c.io.in0, false)
        poke(c.io.in1, false)
        step(1)
        expect(c.io.out, 0)
        expect(c.io.carry, 0)
  //
        poke(c.io.in0, true)
        poke(c.io.in1, false)
        step(1)
        expect(c.io.out, 1)
        expect(c.io.carry, 0)
  //
        poke(c.io.in0, false)
        poke(c.io.in1, true)
        step(1)
        expect(c.io.out, 1)
        expect(c.io.carry, 0)
  //
        poke(c.io.in0, true)
        poke(c.io.in1, true)
        step(1)
        expect(c.io.out, 0)
        expect(c.io.carry, 1)
}
テストモジュールをもりもり作りながら進めるスタイルが定着するのもいいなっという感じがしますね。

Verilogへ出してみる

書けたScalaソースファイルをScalaの流儀に従ってbuild.sbtファイルの定義のもとビルドしていくのですが、少々うまくいかないところもありました。
  • Makefileのターゲットを変えるとコマンド出力的にはできそうだが実際には.vファイルが生成されない
  • vpi_user.hが無いと怒られる
    • ハーネス側がだめっぽい?
    • 無駄オプションを削ってコンパイルオプションを--backend vだけにする
  • 普通に通った
    • やっぱハーネスかー。これは後で調べる
無事に通った結果、生成された.vファイルは次のようになりました。
module HalfAdder(
  input  io_in0,
  input  io_in1,
  output io_out,
  output io_carry
);

  wire T0;
  wire T1;
  wire T2;
  wire T3;


  assign io_carry = T0;
  assign T0 = T1 ? 1'h1 : 1'h0;
  assign T1 = io_in0 & io_in1;
  assign io_out = T2;
  assign T2 = T3 ? 1'h1 : 1'h0;
  assign T3 = io_in0 ^ io_in1;
endmodule
生成されていますね。
タイミング検証とテストコード実行をおこなうためのエミュレータ用C++コードは次のとおり生成されました。
#include "HalfAdder.h"

// ... 略 ...

void HalfAdder_t::clock_lo ( dat_t<1> reset ) {
  val_t T0;
  { T0 = HalfAdder__io_in0.values[0] ^ HalfAdder__io_in1.values[0];}
  val_t T1;
  { T1 = TERNARY(T0, 0x1L, 0x0L);}
  { HalfAdder__io_out.values[0] = T1;}
  val_t T2;
  { T2 = HalfAdder__io_in0.values[0] & HalfAdder__io_in1.values[0];}
  val_t T3;
  { T3 = TERNARY(T2, 0x1L, 0x0L);}
  { HalfAdder__io_carry.values[0] = T3;}
}

// ... 略 ...

この先

半加算器だけではなんとも悲しいので、ひとまずこのまま4bit同士の加算をできるところまで拡張してみて、手持ちのFPGA評価ボードで計算の途中経過をLED出力できるようにしてみようと思っています。
それには入力用のスイッチ8つと出力用のLED5つを制御する必要があるのですが、これPmod(手持ちのMicroZedは100ピンの高周波回路用コネクタ以外には基本的にPmodコネクタぐらいしかないので)ではギリギリ足りないな? と気付いて頭を抱えたりしている昨今です。

Scalaで電子回路、楽しいかもしれない

Chiselが高位合成ツールセットとして楽しいポイントは、概要にも書いたようにクロック同期保証付きのエミュレータ用コード生成をおこなってくれるあたりです。小さなものを書いている中でもすでにcpp経由で開発マシン上のバイナリを吐き出してサクサク実行できることの楽さは感じつつあります*1し、これはきっと扱う対象が大規模化するにつれて更に嬉しくなっていくのでしょう。
趣味で作るハードウェアでも「ScalaコードをコミットしたらJenkinsで速やかにタイミングチェックまでおこなって、結果が怪しければ即座にエラーメールが届く」というワークフローになるとずいぶん楽しいと思いませんか。
また、今回Chiselを使ってみた結果、Scalaを勉強する機会にもなりました。これまでScalaといえばコンパイルが遅い言語でなんとなくHaskellのほうがかっこいい、ぐらいの印象でいたのですが、ちゃんと学ぶ気力が湧いてきたのでとてもよかったと思います。
Chiselの続編はまたそのうち。明日の担当はikwzmさんです。
[*1] 特に私の場合はScalaにもVerilogにもさほど慣れていないので、コードの正しさを判定するためのセカンドオピニオンとしてC++コードを利用できる安心感は大きいです。

0 件のコメント:

コメントを投稿