Chiselを使ったRISC-Vの勉強(13.FPGAへの実装1)
メモリを同期読み込み書き込みにする
製作してきたChiselプロジェクトをFPGAに実装してみました。
Chiselを使ったRISC-Vの勉強(1)から目標にしていたようにAvalon-MM Masterとして動作させようとすると、
- 命令フェッチ時にデータ取得が1クロック遅れる
- ロード動作時に1クロック遅れる
(= どちらもメモリ読み込み動作時にのみ1クロック遅れる)
という現象が生じていました。これはChiselで命令メモリ/データメモリをMem(同期書き込み・非同期読み込みメモリ)として実装していたからです。
命令メモリ、データメモリ共にSyncReadMemに置き換えました。
github.com
git clone http://github.com/panda5mt/KyogenRV -b 0.1.1 --depth 1
超ミラクルスーパーウルトラ大規模改修工事の恐れがありましたが、パイプライン動作そのものを大きく変更するような変更がなかったため比較的小規模な改修ですみました。riscv-testsも変わらずパスしています。
この修正に伴い、追加で修正した分
- メモリR/W動作をハンドシェイクからレイテンシ動作に変更
Avalon-MMでは指定されたレイテンシでメモリリードライトを行います。もしスレーブが期待されたレイテンシで応答できない場合、スレーブ側はホストへwaitrequest信号を発行します。
ですので、Chisel側も準じた実装に変更しました。レイテンシに対応したステージでR/W動作を行い、waitrequest信号がきたらストールするだけです。(かえってハンドシェイク方式より単純になりました)
FPGAへの実装テスト
ここでQuartus Primeの使い方を紹介するつもりはないので少し駆け足で説明します。
ハーバードアーキテクチャCPUソフトコアをAvalon-MM Masterとする場合、命令メモリとデータメモリの2つのMasterを持つと思います。
この二つは独立ではなく、Back Pressureに支配されて連携動作をします。(今回はキャッシュ非搭載のため、命令/データは都度フェッチします)
- ChiselプロジェクトからVerilogコードを生成させる。
- PlatformDesignerに読み込ませるためのトッププロジェクトを作り(今回はSystemVerilog)、上記コードをインスタンスする。
- 新規プロジェクトを作り、PDで上記CPU + On-chip Memory + IOを使ったプロジェクトを作る
- hexエディタで命令を書く
- SigmalTapで実行中のCPUの動作を確認
項目2.の信号はavalon-MM Master信号と下記のように対応させました。
- clock
- reset
- byteenable
- waitrequest
- address
- read
- readdata
- write
- writedata
PlatformDesignerに登録が終わったら、項目3に進んでいきます。
それぞれの接続は以下のようになりました。onchip_memoryは0x0000番地から、I/Oはデータメモリ開始番地0x8000に配置します。
項目4.
以下は疑似コードです。これをアセンブラで記述します。今回書くコードの目的は命令/データメモリが正しく動作しているかの確認です。
uint32_t data; *(*PIO_BASE_ADDRESS) = 0xAA; // PIO = 0xAA data = *(*PIO_BASE_ADDRESS); data = data + 1; *(*PIO_BASE_ADDRESS) = data; // PIO = 0xAB
上記をアセンブラで記述します。
lui x1, 0x08 # x1=0x8000 li x2, 0xAA # x2 = 0xAA sw x2, 0(x1) # dmem[0x8000] = 0xAA lw x3, 0(x1) # x3 = dmem[0x8000] addi x3, x3, 1 # x3 = x3 + 1 sw x3, 0(x1) # dmem[0x8000] = x3 _loop: jal x0, _loop # loop
と書いてもいいのですが、これだとデータメモリに格納される前にデータフォワーディングでRWされるデータがよしなに取り扱われてしまうので、適宜パイプライン段数以上のnopを入れてやります。そうすることで本当にデータメモリの読み書きが機能しているかを確認することができます。
lui x1, 0x08 # x1=0x8000 li x2, 0xAA # x2 = 0xAA sw x2, 0(x1) # dmem[0x8000] = 0xAA nop nop nop nop nop lw x3, 0(x1) # x3 = dmem[0x8000] nop nop nop nop nop addi x3, x3, 1 # x3 = x3 + 1 nop nop nop nop nop sw x3, 0(x1) # dmem[0x8000] = x3 _loop: jal x0, _loop # loop
みたいな感じです。アセンブルして、得られた機械語をQuartusのHexエディタで記録保存、Onchip Memoryの初期値データとします。
SignalTapで動作状況を見てみました。
ロード命令も動作しています。waitrequestにより1クロック待たされていますが、動作しています。