lynxeyedの電音鍵盤

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

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に支配されて連携動作をします。(今回はキャッシュ非搭載のため、命令/データは都度フェッチします)

  1. ChiselプロジェクトからVerilogコードを生成させる。
  2. PlatformDesignerに読み込ませるためのトッププロジェクトを作り(今回はSystemVerilog)、上記コードをインスタンスする。
  3. 新規プロジェクトを作り、PDで上記CPU + On-chip Memory + IOを使ったプロジェクトを作る
  4. hexエディタで命令を書く
  5. SigmalTapで実行中のCPUの動作を確認

項目2.の信号はavalon-MM Master信号と下記のように対応させました。


f:id:Lynx-EyED:20200905134337p:plain
Avalon-MM Master 1チャネルに対し最低限必要なのは下記項目です。byteenebleはRISC-Vの仕様上必須です。

  • clock
  • reset
  • byteenable
  • waitrequest
  • address
  • read
  • readdata
  • write
  • writedata

PlatformDesignerに登録が終わったら、項目3に進んでいきます。
それぞれの接続は以下のようになりました。onchip_memoryは0x0000番地から、I/Oはデータメモリ開始番地0x8000に配置します。


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

項目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で動作状況を見てみました。


f:id:Lynx-EyED:20200905134729p:plain
ストア命令の時は0x8000番地、書き込みデータは0xAA、writeシグナルを発行しています。
ロード命令も動作しています。waitrequestにより1クロック待たされていますが、動作しています。

次回は

コードを手作業で入力するのが大変なので、、
インテルhex/アルテラmifファイルの生成スクリプトを書いて、実機でriscv-testsを動作できるようにしていきます。