Tang PrimerまたはArty A7で自作riscv-rv32iを動かす

# 始めに
Seed FPGA board Tang Primerで、小さなCPUもどきを作っていたのですが、一つまともなものを作ってみようと思い、最小のRISC-V RV32I命令セットで作ってみることにしたのが約1年前。そこから論理は書いたものの、テストをせずに放置していたものを思い直して自作テストを作り、実装した命令を１回ずつテスト通ったので、公開してみました。今回公開したのは、RISC-VおよびUARTを使用したモニタのverilog RTL論理および動作環境です。一応Tang Primerを歌っておりますが、clock PLLのみIP使用であり、特殊な記述もないため他のFPGAへの移植も容易と思います。

### 始めに２(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](https://github.com/yoshiki9636/my-riscv-rv32i-dc)
変更点等の記事は以下です。
[Arty A7で自作riscv-rv32iを動かす②　DRAMでDキャッシュのみ版](https://qiita.com/yoshiki9636/items/87a14f6bc71c84762c95)

### 2025/4/25追記
・instruction RAMをキャッシュとしてDRAMに接続しました。レポジトリは以下。
[https://github.com/yoshiki9636/my-riscv-rv32i-dc](https://github.com/yoshiki9636/my-riscv-rv32i-dc)
まだCプログラムで一部動作しませんが、変更点等の記事は以下になります。
[Arty A7で自作riscv-rv32iを動かす③　DRAMでIキャッシュDキャッシュ](https://qiita.com/yoshiki9636/items/306a4839c4c04e663acd)

### 2025/5/21追記
・instruction RAM版でのいくつかのbug fixをしました。I,Dキャッシュのサイズを14ビット(arty A7 T100の場合)に設定すると、ctest/ 以下のほとんどのプログラムは動作するようになりました。しかし、I,Dキャッシュのサイズを8bitに絞ると動作しないものが出てきます。I,Dキャッシュ双方がスラッシングを起こすような条件で動作しない不具合がまだあるようです。

# 参考URL

[Tang Primerのページ](https://tang.sipeed.com/en/)

[Tang PrimerでUARTを使ってみる2020](https://qiita.com/yoshiki9636/items/3ff482240621d33cb92e)

[Tang Primer 開発環境をセッティング](https://tomono.tokyo/2021/06/20/9829/)

[RISC-V-RV32I命令セット](https://msyksphinz-self.github.io/riscv-isadoc/html/index.html)

[my-riscv-rv32i](https://github.com/yoshiki9636/my-riscv-rv32i)

[my-riscv-rv32i-dc 　Dキャッシュ版](https://github.com/yoshiki9636/my-riscv-rv32i-dc)

# 1. Version 0.1の制約  
以下の制約があります。  
  
- ALU周り、Load/Store、Jump系を作成。  
- fence系、csr系、ecall系、uret系などは未実装。割り込みも未実装。  
- メモリはinstructionとdataでセパレート。~~各々1KWords~~instruction 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

https://github.com/yoshiki9636/my-riscv-rv32i

![スクリーンショット 2021-08-13 150559.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/575990/367ec845-a139-b125-d3ae-a35fd27342b9.png)

※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、出力~~90MHz~~85MHzで動作を確認しております。  
  
(3.1) ssyn/uart_if.vの周波数設定を~~90MHz~~85MHzにします。
　　　※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)ツールチェーンを取得、コンパイルとインストールをします。

大体[ここのページ](https://blog.n-hassy.info/2021/07/riscv-gnu-toolchain/)の手順で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を入手して動作速度がアップいたしました。こちらをメインで作っていこうと思います。