lynxeyedの電音鍵盤

MBDとFPGAと車載で使うデバイスの備忘録

Chiselを使ったRISC-Vの勉強(最終話:FPGAへの実装2)

今月中にQuartus Primeユーザー向けにもまとめを書きます。ご期待いただければ。

この記事はChiselサイドから見たブログ記事にしたいと思います。

Alternative HDLとしてそこそこ使えるじゃん!という手応えを感じたのとriscv-testsにFPGA実機上でもパスできるようになったので、ここでお勉強は一旦おしまいにしたいと思います。ここから先は、CPUコアへのデバッグロジックの接続、Cコンパイラ向けリンカスクリプトの記述、とChiselではない話になっていきます。

もちろんこれからChiselはかなりの頻度でネタとして取り上げると思います。(現時点で車載LKAS画像認識エンジンの記述に使っています。)


今回ターゲットはintel FPGA Cyclone 10LPです。
RV32IのISAに準拠、マシンモードのみ(=スーパーバイザ非対応)ですが、仕様通りの例外にも対応しています。
割り込みは外部割り込みのみ対応。(タイマー割り込みはメモリマップドタイマーモジュールを設け、トリガーを外部割り込みへ接続し使うようにしてください)

ふつーの32bit組み込みマイコンです。
github.com

git clone http://github.com/panda5mt/KyogenRV -b 0.2.6 --depth 1 

プログラム例

徳ではなくLチカ基板を積むのが組み込みエンジニア道らしいのでBlink LEDしてみます。PIOにLEDを接続するものとして、PIOのアドレスを0x8000番地、8bit幅と想定し記述します。GPIOに約0.5secごとに0x55,0xAAを交互に記述します。動作周波数は70MHzとします。
このCPUはmtimeを64bitで実装しています。今回は下位32bitだけを参照します。mtimeはクロックに同期してカウントアップします。(kyogenrv-root-directory)/src/sw/test.sに以下を記述します。

        nop
        lui     x1, 0x08        # x1 = 0x8000
_loop0:
        csrr    x2, time        # x2 = time (oldtime)
        li      x3, 0xAA        # x3 = 0xAA
        sw      x3, 0(x1)       # dmem[x1] = x3 = 0xAA
_loop1:
        csrr    x4, time        # x4 = time (nowtime)
        sub     x5, x4, x2      # x5 = x4 - x2
        lui     x6, 0x2160      # x6 = 0x2160_000
        bltu    x5, x6, _loop1  # if(x5 < x6) then loop
_loop2:
        csrr    x2, time        # x2 = time (oldtime)
        li      x3, 0x55        # x3 = 0x55
        sw      x3, 0(x1)       # dmem[x1] = x3 = 0x55
_loop3:
        csrr    x4, time        # x4 = time (nowtime)
        sub     x5, x4, x2      # x5 = x4 - x2
        lui     x6, 0x2160      # x6 = 0x2160_000
        bltu    x5, x6, _loop3  # if(x5 < x6) then loop
        jal     x0, _loop0


記述したら

make test #ここでアセンブルとシミュレーションが行われる
./mk_intel_hex.py

します。python3.7以上が必要です。(kyogenrv-root-directory)/fpga/chisel_generated/test_intel.hexが生成hexです。
(kyogenrv-root-directory)/fpga/がQuartus Primeのフォルダです。Quartus Prime Lite 20.1.1で動作確認しています。
Platform Designer上で、On-chip MemoryのMemory Initializationの項目で先程生成したhexファイルを指定し、Generate HDL-> Finish ->全体のコンパイルをします。


f:id:Lynx-EyED:20201213141614p:plain

書き込むと、こんな感じ。
youtu.be


以下はこれを設計する際に行ったことと感想です。

Chiselの学習をしてみて

学習コストがすごく高い。座学と簡単な例題解いていても永遠に終わらない気がしたのでRV32Iを設計しながら学習を進めました。
このブログのChisel3のタグで見ていただければ何をやっていたかは把握できると思います。
コストが高いものの、Chisel-iotester*1で実機を使わずとも、バイナリを読み込ませてCPUとして実行した時の挙動が詳細に追えました。

また、Bundleを使うことによって信号線を整理し、再利用がしやすい環境なのも良い点です。メモリ、バス、CPU本体、デバッグ用信号を分けて開発できました。加えて、高位言語ではないため合成された結果がまるで理解不能、ということもありませんでした。(そこまで大した物を作っていない、ということもありますが)

  • みんなFPGA使うならメモリはSyncReadMemつかおうね

Chiselそのものが悪いわけではありませんが。同期読み込みではレイテンシが1クロック以上あるはずです。これを前提にしていないRISCV/Chisel教本,ブログがありすぎました。ほんとダメ。ASIC用途ならそれでいいのか知りませんが(多分非同期多すぎてもダメだと思うんだけどどうなのかな)、ASIC起こすユーザそんなにいるのかな。。。非同期メモリはたとえFPGAベンダでサポートしているとしてもロジックの使用率の上昇、Fmaxの低下、期待しないビヘイビアの原因になるので、極力避けたい。

命令メモリ、データメモリ、汎用レジスタを全てMemで記述してる例が多く、ドツボにハマりました。
FPGA実装段階で5段パイプラインを1から作り直しました。
今更なのですが、WBステージで格納した汎用レジスタをEXステージで利用する場合、これが原因で取りこぼしてしまうため、該当する場合のみストールするようにしました。今度一から作り直す機会があったら6段パイプラインにしよう。

Avalon-MMスレーブはかなり作っていたのですが、マスタ(しかも命令+データのダブルマスタ)はほぼ初めてでした。かなり難儀しました。
ごくごく普通のパイプラインかつレイテンシが1(Avalon MM自体はレイテンシ変更可能)のメモリとして考えると、ああなんだ、そんなに難しくないじゃん。となります。あと、waitrequestが発生したら問答無用でCPU側(Master側)のステートマシンは全停止させる。ピクリとも動かさない。というのを徹底するとすんなり動きます。
いつもは読めるが、稀に記録もした覚えがない変なデータが読める(1 or 2ビットだけ異なるというのがよくありました)、違うアドレスに書き込んだ値が読める、しかも再現不能という場合はwaitrequestが来ているにもかかわらず、マスタを動かしている可能性があるかもしれません。

さいごに

Cyclone 10LPはイイゾ。

*1:これがなかったらChiselの価値ない気がします。なかったら勉強しませんでした