Chisel3を始めるにあたって(1/2)
scalaでHDLが記述できるライブラリの導入
FPGAの論理回路設計にはHDLの習得が必須である。従来であればVHDL, Verilog HDL, SystemVerilogあたりが主流である。
しかしHDLで論理実装を行うのは敷居が高くまた時間がかかることもあって近年では高位合成なども流行っている(*要出典)
私はverilogで育ってきたのだが、確かにverilogの中途半端にゆるい成約は謎のバグの温床になっていたりすることがおおい。
かといってVHDLを書いてみても冗長という感想なので、altHDLを探していたところにscalaでHDLをなすChiselなるライブラリがある。
Chisel - Constructing Hardware in a Scala Embedded Language
scalaとHDLを両方習得している人間は稀だろと様子を見ていたが、RISC-VやTPUに使われているらしく改めて触れてみることにする。
長々と書いたが、Chiselで作って見るところまでの手順まとめである。荒削りなメモなので誤り等があるかもしれないのでご容赦いただきたい。
導入
Java
Scalaやsbtを動かすのに必要。JDKを公式ページよりインストール
インストール確認
sbt
scalaのプロジェクト管理ツールのようなもの。plugin管理とビルドスクリプトを兼ねたものだと思ってもらえれば。
動作確認
icarus-verilog
verilogファイルをシミュレーションするために必要。ベンダーツールのVivado(Vivado Simulator), Quartus Prime(ModelSim Intel)でもできる。
動作確認
gtkwave
シミュレーション波形の表示用。
プロジェクト作成
chisel
とテスト用のchisel-iotesters
を追加するために/build.sbt
に以下を記述。パッケージ名とか組織名は適宜。
この状態で$ sbt
をすると、周辺ライブラリも引っ張ってきてくれた後に対話モードに移る。今後のビルド、テストはこのsbtの対話モードで行う。といってもreload
, test
, run
あたりがメインである。
開発
作るもの
作りたいもの次第だが、今回はオーディオエフェクターやミキサーにあるコンプレッサを作ってみる。
音の粒度を整えるなどというが、司会者マイクなどですごく大きな音などを抑制するときなどに使う。
単純なものであればでかすぎる音をでかすぎないように傾きをいじっているだけである。
この傾きをratio、大きい音と判定するしきい値をthreshold(今回記載したコードではpoint)と呼ぶ。
aをratio、bをpointとした場合に、入力された音xに対し以下の処理を施すだけで良い。
(まともなやつは大きな音が入ってから圧縮するまでのAttack、音のレベルを抑制する期間であるReleaseなどでトリガするものが主流。興味があれば調べてみてほしい)
方式
HDLを設計するにあたって以下を考える。
- Interface
- in : 音声入力
- out : 音声出力
- point : 傾きの変化点、threashold
- ratio : 傾き、今回は単純に入力値を割るだけにする
- Interfaceはすべてint32で扱う。表現は2-complementary
- Masterより供給されるクロックに同期して結果を出力
- 途中計算をラッチすることも考慮し、total delayは1~5あたりで
実装
/src/main/scala/audio/Compressor.scala
流石に解説を簡単に書く。
インポート
これはchiselのライブラリを使うためのおまじない。
入出力定義
自作モジュールの宣言と、IOポートの定義をしている。chiselのモジュールとして定義する場合chisel3.Module
を継承する
class Compressor(width: Int)
のwidth
はコンストラクタであり、ビット幅可変で使えるようにしている。verilogで言うところのlocalparam。
Bundle
というのはchisel上でユーザ定義の構造体を扱うような機構のこと。これをIO
に渡すと、合成時に入出力ポートとして合成されるらしい。
Input()
およびOutput()
はポート方向の定義、その中にあるSInt(width.W)
は符号付きでビット幅がwidthビットの整数であることを示している。
width.W
となっているが、chisel上ではビット幅指定するところの型がchisel3.Width
のためInt->chisel3.Width
変換を呼び出している具合である。
余談だが、これはchisel上のライブラリによってあたかもInt
のメンバ関数(.w
)が増えているかのように扱える。scalaの良い機能である。他にも[Int].U
でInt->chisel3.UInt
、[Int].S
でInt->chisel3.SInt
、[Bool].B
でBool->chisel3.Boolean
などがある。
ロジック
※ここは自分の解釈の途中である部分も含まれるので、誤りがあったら指摘いただきたい。
statementの後ろに数字を付けたが、定義したものは大きく以下のパターンに分類される。
(1)レジスタ
reg
として定義される。HDL設計の上では暗黙の了解になっているが、組み合わせ論理回路をひたすら連結した設計をしてしまうと、信号が変化してから確定するまでのパスが非常に長くなってしまう。これは周波数を上げるのにあまり向かなかったりする。理由はこれだけではないが、ある程度の計算ごとにRegすなわちFF(フリップフロップ)を挿入するなどしたりする。
dstが33bitの変数になっていることに気づいたらかなり鋭い。chiselはビット幅を明示しなかった場合に、行われている計算から推論してくれているように見えている(*要出典)
(2):=
演算子
レジスタやワイヤ、ポートを:=
演算子でつなぐと、基本はassign
に変換されるようだ。
基本といったのは、後述するRegNext
などを使用した場合はFFが生成されるからである
(3)scalaでの変数定義
これらは組み合わせ論理回路、すなわちassign
に変換されるようだ。これはクロックには同期しない。
(4)Mux
これらは組み合わせ論理回路だが、3項演算子と等価になるようだ。
(5)同期代入
RegNext
を使った場合、同期回路に変換される。このモジュールに供給されたクロックCLKの立ち上がりに同期して、入力値をキャプチャするような回路になる。
verilogで書き下すと以下の回路と等価になるのでそのまま変換される。
また、reg
のように初期値が決まっている場合はリセット付きの以下の回路が生成される。
(6)when句
どうやら組み合わせ回路のみの場合はassignの条件として生成され、同期回路が含まれていた場合は(5)項にあるようなalways
の内部にifが生成されるようだった。おそらくswitch
なども同様な気がする。
書いていたら意外とボリュームがあるので、ここで区切ることにする。
まだまだ理解がおぼつかない部分があるが、HLSのアプローチとは異なりHDLをより抽象度を高めて書けるところにメリットが有る、と感じる。
Chisel3を始めるにあたって(2/2)へ