始めに
Seed FPGA board Tang Primerで、小さなCPUもどきを作っていたのですが、一つまともなものを作ってみようと思い、最小のRISC-V RV32I命令セットで作ってみることにしたのが約1年前。そこから論理は書いたものの、テストをせずに放置していたものを思い直して自作テストを作り、実装した命令を1回ずつテスト通ったので、公開してみました。今回公開したのは、RISC-VおよびUARTを使用したモニタのverilog RTL論理および動作環境です。一応Tang Primerを歌っておりますが、clock PLLのみIP使用であり、特殊な記述もないため他のFPGAへの移植も容易と思います。
始めに2(2021/9/5追記)
新たにXilinx Artix-7を使ったDigilentのArty A7で動作確認をしました。使用法をアップデートしました。
始めに3(2023/12/9追記)
外部割込みとmret命令、illegal operationのexceptionを追加しました。
2024/1/6追記
my-systolicサポート用にData RAMをパラメータ化し16Kwordに拡張しました。Arty A7でのみ動作確認しています。
2024/11/28追記
・Instruction RAMもパラメータ化しました。
・UART経由で表示ができるようにI/Oを追加いたしました。Arty A7で動作確認しました。
・またいくつかバグをつぶしてCのベアメタル動作で、libgccを使い、整数割り算と浮動小数点演算のソフトエミュレートテストを動作させてみました。動作方法を追記しました。
2024/12/21追記
・data RAMをキャッシュとしてDRAMに接続できるようにしました。レポジトリは以下。
https://github.com/yoshiki9636/my-riscv-rv32i-dc
変更点等の記事は以下です。
Arty A7で自作riscv-rv32iを動かす② DRAMでDキャッシュのみ版
参考URL
1. Version 0.1の制約
以下の制約があります。
- ALU周り、Load/Store、Jump系を作成。
- fence系、csr系、ecall系、uret系などは未実装。割り込みも未実装。
- メモリはinstructionとdataでセパレート。
各々1KWordsinstruction 4Kwords, data 4 or 16 Kwordsの大きさ。 - I/OはRGB LEDの3ピン×4。
2. 使い方
Tang Primerの合成環境のセットアップが済んでいることが前提となります。
上記URLを参考にしてください。
まずはgit cloneしてください。
https://github.com/yoshiki9636/my-riscv-rv32i
2.1 RTL simulation
Intel FPGA用に配布されている、Modelsim 10.5b で動作確認しております。
基本的なverilog記述しか使用していないので、大抵のシミュレータで動作可能と思います。
(1) ssim/ディレクトリを作成し、cpu/ fpga/ io/ mon/ sim/の内容をコピーします。
(2) asm/ディレクトリで riscv-asm1.plを使い、テスト命令列test.txtを作成。ssim/にコピーします。
例:./risc-vasm1.pl lchika.asm > test.txt
(3) ssim/内でverilogシミュレータを動作させます。
2.2 Tang Primer Synthesis & run
Tang Primer専用のIDEを使用して合成、FPGAへの書き込みを行います。詳しくは、SiPEEDのページを参照ください。
https://tang.sipeed.com/en/
(1) ssyn/ディレクトリを作成し、cpu/ fpga/ io/ mon/ syn/の内容をコピーします。fpga_top.v内のifdefはTANG_PRIMERを有効にしてください。
(2) IDEを立ち上げ、プロジェクトを作成、ssynをディレクトリに指定して、すべてのverilogファイルを指定します。
(3) PLLの追加が必要なので、Tools->IP Generatorを選択し、Create New IPから、PLLを選択します。名称はpll、入力24MHz、出力36MHzとして作成します。
(3.1)ssyn/uart_if.vの周波数設定が36MHz設定にします。
(4) 後は用法通りに合成、書き込みをします。
(5) USB - UART変換器を使って、FPGAとシリアル通信します。変換器のRxをB15、TxをB16、GndをGのどれかに接続します。
(6) Teratermを使ってシリアル通信をします。Teratermの新しい接続で、シリアルを選択し、COMを変換器のものにします。
(7) 設定->シリアルポートで、スピード:9600 データ:8bit パリティ:none ストップビット:1bit フロー制御:none
(8) 設定->端末で、 改行コード 受信:AUTO 送信:CR これでキーボードをたたくとエコーバックが帰ってくれば、動いております。
(9) プログラムを書き込みます。qを押して、状態をクリアしたのち、i 00000000と打ち込むと、改行されます。シミュレーションで作ったtest.txtの内容をコピペしてください。最後にqを押します。
※Teratermを使っている場合は、i 00000000と打ち込んだのち、ファイル->ファイル転送を選択してtest.txtを指定すると、ファイルをペーストしてくれます。
(10) 書き込んだプログラムをダンプするには、p 00000000 00000100と打ち込んでください。先ほどのプログラムがダンプされます。
(11) 実行は、g 00000000で実行状態になります。lchika.asmであれば、RGB LEDが色を変化させます。そのほかのテストプログラムも項目を確認後、テストパスがわかるように同じようなLチカとなります。
(12) 実行の停止もqで停止します。それ以外のコマンドは、以下の通りです。
command format
g : goto PC address ( run program until quit ) : format: g <start addess>
q : quit from any command : format: q
w : write date to memory : format: w <start adderss> <data> ....<data> q
r : read data from memory : format: r <start address> <end adderss>
t : trashed memory data and 0 clear : format: t
s : program step execution : format: s
p : read instruction memory : format: p <start address> <end adderss>
i : write instruction memory : format: i <start adderss> <data> ....<data> q
j : print current PC value : format: j
※2021/9/5追記
2.3 Arty A7 Synthesis & Run
Digilent Arty A7を使う場合の合成方法のメモです。Xilinx Vivadoを使用します。詳しくはArty A7の設計ドキュメントを探してください。
(1) ssyn/ディレクトリを作成し、cpu/ fpga/ io/ mon/ をコピーしてください。ssyn/riscv_io_pins.xdcをコピーします。fpga_top.vのifdefをARTY_A7を有効にしてください。
(2) Vivadoを立ち上げ、projectを作成、ssynの中のRTLを指定し、constraintsとして、riscv_io_pins.xdcを指定します。
(3) MMCMの追加が必要なので、IPカタログから追加します。周波数は入力100MHz、出力90MHz85MHzで動作を確認しております。
(3.1) ssyn/uart_if.vの周波数設定を90MHz85MHzにします。
※uart_if.vの定数を85MHzに変更しました。アップデートをお願いいたします。
※定数TERMの設定は以下の式になります。HARFはTERMの1/2です。
TERM = int( (UARTビットレートの周期[us] / クロック周期[us]) )
= int ( (1,000,000/UARTビットレート[bps]) / (1 / クロック周波数[MHz]) )
(4) Vivadoのお作法で合成以下を行い、Arty A7に書き込みます。
(5) Arty A7はUSB接続がUARTとして使用できるので、特にUART変換器は必要ありません。あとはTang Primerの方法(7)からと同一です。
3. ベアメタルCの動作メモ(2024/11/28追記)
(1)ツールチェーンを取得、コンパイルとインストールをします。
大体ここのページの手順でOKです。
違いは、configureのところで、アーキティクチャをiのみにします。abiはilp32にします。
$ ./configure --prefix=/opt/riscv32 --with-arch=rv32i --with-abi=ilp32
インストールまで終わったら、レポジトリのctest内のファイルを以下の手順でコンパイルします。
まずstart.sをアセンブルします。
$ riscv32-unknown-linux-gnu-as -march=rv32i -mabi=ilp32 -o start.o start.s
次に以下の処理をすることで、最終的にバイナリのダンプのみのファイルを作ります。ここではprint_test.cを例にします。
$ riscv32-unknown-linux-gnu-gcc -march=rv32i -mabi=ilp32 -mstrict-align -mpreferred-stack-boundary=4 -c -o print_test.o print_test.c
$ riscv32-unknown-linux-gnu-ld -b elf32-littleriscv start.o print_test.o -T link.ld -o print_test
$ riscv32-unknown-linux-gnu-objdump -b elf32-littleriscv -D print_test > print_test.elf.dump
$ riscv32-unknown-linux-gnu-objcopy -O binary print_test print_test.bin
$ od -An -tx4 -w4 -v print_test.bin > print_test.hex
あとは、print_test.hexを.asmファイルの時と同じように読み込んで実行します。
このとき、同じイメージを命令RAM(i 00000000)とデータRAM(w 00000000)双方に書き込んでください。命令だけでないのは、一部変数の初期値がデータ領域に入っているため、データRAMにもコピーが必要なためです。
※Teratermを使っている場合は、i 00000000と打ち込んだのち、ファイル->ファイル転送を選択してtest.txtを指定すると、ファイルをペーストしてくれます。
上記print_test.cがうまく実行できれば、「Hello World!」がUartのコンソール上に表示されます。
print_test3.cは浮動小数点を表示してみています。sprintfが使えない(たぶん乗り切らない)ため、自前で少数を簡単に表示するようになっています。
最後に
これからぼちぼちとTang Primerに実装できる範囲で未実装の機能を実装していこうと思います。ただTang Primerの論理容量がネックになりつつあるので、(まだ容量ありました)そのうちにFPGAをアップグレードすることになりそうです。
2021/9/5追記
Arty A7を入手して動作速度がアップいたしました。こちらをメインで作っていこうと思います。