マルチサイクル RISC-V CPU を作成したい
#自作CPU #RISC-V #Verilog #ULX3S #FPGA #Odeeen #CPU実験 #東大CPU実験
https://gyazo.com/0d9c5448e09d85a03ad2ef2832e0a336
マルチサイクルな 32ビット RISC-V CPU の作成メモ。
以前作ったCPU( https://github.com/thata/R4 )はシングルサイクルなCPUだったが、
シングルサイクルなCPUだと以下のような制限があるため、マルチサイクルなCPUを作ることにする。
プログラムメモリとデータメモリを共通化できない
ブロックRAM / SDRAM の読み込み待ちができない
浮動小数点数の計算待ちができない
リポジトリ
Odeeen
https://github.com/thata/odeeen
発表資料
「第5回 自作CPUを語る会」で発表した資料
https://thata.github.io/odeeen/presentation.html
発表内容検討メモ
東大CPU実験のレイトレを自作CPUの上で動かした話
CPUアーキテクチャ
32ビットRISC-V(RV32IF)
整数演算 + 単精度浮動小数点演算
コンパクト命令には非対応
実装した命令
整数命令
lw, sw, add, addi, sub, and, or, xor, slt, sltu, beq, bne, ble, bne, blt, bgt, bge, jal, jalr, lui, auipc, ori, srl, sra, sll
浮動小数点命令
flw, fsw, fadd_s, fsub_s, fmul_s, fdiv_s, fcvt_s_w, fcvt_w_s, fsgnj_s, fsgnjn_s, feq_s, flt_s, fle_s, fmv_x_w, fmv_w_x
バスインターフェース
PicoRV32のネイティブメモリインターフェース
メモリ構成
ROM(ブロックRAM):256KB
RAM(SDRAM):32MB
周辺機器(メモリマップトIO)
UART
LED(8ビット)
周辺機器とメモリマップ
使用するFPGAボード
ULX3S(ECP5 85F 搭載)
開発環境
例のレイトレの開発環境
レイトレを動かすのに必要なFPGAのスペック
LUT数
12K以上(必須)
自作CPUを載せるのに12K程度のLUTが必要
ECP5 85FのLUT数は85Kなので問題なし
Tang Nano 9Kには入らない
Tang Nano 20Kにはたぶん入りそう
SDRAMまたはHyperRAMのサイズ
5MB以上(必須)
128KB以上(必須)
スタック領域(1MB)+ ヒープ領域(4MB)
スタック領域(64KB)+ ヒープ領域(64KB)
ブロックRAMサイズ
250KB以上(推奨)
プログラムがブロックRAMに収まらない場合、SDカードなどからのローダが必要
メモリバス
今回のCPUはSDRAMに対応したい。SDRAMのデータの読み書きには複数クロックかかるので、SDRAMの読み書きが終わるまでCPU側が待機する必要がある。
以前作成したCPUのメモリバスは読み書きを待つための信号線を持たなかったので、今回のCPUでは、PicoRV32のメモリバスを参考にVALIDとREADYにより待ち合わせを行うインターフェースを採用する。
BRAMコントローラー
VALIDとREADYによる待ち合わせを行うメモリが必要なので、新たにBRAMコントローラーを作成した。
VALIDとREADYにより待ち合わせを行うBRAMコントローラー
参考資料
シングルサイクルCPUのマルチサイクル化の手順については、以下が参考になりそう。
POCOのマルチサイクル化
https://www.am.ics.keio.ac.jp/pocobook/multc.pdf
MinCamlコンパイラ
MinCamlで書かれたレイトレーサーを動かすのが最終目標なので、MinCamlで書かれたプログラムを動かせるようにしたい。
MinCamlのプログラムのビルドには、去年RISC-Vへ移植したこちらを利用する。
https://github.com/thata/min-caml/tree/rv32
MinCamlのRISC-Vへの移植についてはこちらを参照
→ MinCamlでRISC-Vのアセンブリコードを出力したい
開発記録
NOP命令とJAL命令(その1)
JAL命令(その2)
レジスタとADD命令とADDI命令
SW命令
LW命令
BEQ命令
JAL命令とJALR命令
MinCamlを動かすのに必要な命令
Odeeen CPUを実機で動かす
周辺機器とメモリマップ
周辺機器を追加(LEDコントローラ)
JALまわりのバグを修正
周辺機器を追加(UARTコントローラ)
Verilog 上で定義したメモリへのファームウェアの書き込み
putcとgetc
自作CPUへのSDRAMの組み込み
ULX3Sで確保可能なブロックRAMのサイズ
自作CPUをristv-testsでテストしたい(できてない)
足りてない命令を追加(RV32I)
自作CPUでMinCamlを動かしてみる
min_caml_print_int をアセンブリで実装
浮動小数点ユニットの素振り
自作CPUに実装予定の浮動小数点命令
浮動小数点数の比較回路のつくり方
FLW命令とFSW命令
FADD.S命令
FSUB.S命令
FMUL.S命令とFDIV_S命令
FCVT.S.W命令とFCVT.W.S命令
FSGNJ.S命令とFSGNJN.S命令
FMV.X.W命令とFMV.W.X命令
浮動小数点数の比較命令
MinCamlのテストプログラム
乗算と除算を追加したらタイミングエラーが発生した話
レイトレを動かすのに必要なライブラリ関数
sqrt関数
三角関数群の実装
floor関数
abs_float関数
print_byte関数
read_int関数とread_float関数
UARTからの受信を取りこぼしまくる問題
レイトレを動かしてみる
rubyserialでレイトレのデータを受信したい
レイトレが完動して感動した
自作CPU上でレイトレを動かす手順
ステージ分割
1つのブロックRAMに「プログラムメモリ」と「データメモリ」の両方を格納したい。
「命令の取得」と「メモリへのデータの読み書き」を同時に行おうとすると、メモリバスで衝突が発生してしまうため、「命令を取得するステージ(IFステージ)」と「命令を実行するステージ(EXステージ)」とを分けることで、メモリバスの衝突を回避する。
IFステージ(命令フェッチ)
メインメモリ上の pc_reg が指す番地から命令を取得し、instr_reg へ格納
mem_addr に pc_reg の値をセット
pc_reg の更新はここでは行わない
EXステージ(実行)
instr_reg に格納した命令を実行
pc_reg の更新を行う
(追記)その後、データメモリへの読み書きの待ち合わせ用のステージが欲しくなったので、最終的に以下のようにステージ分けをおこなった。ステージの分け方については、デジタル回路設計とコンピュータアーキテクチャ(7章)が参考になった。
IFステージ(命令フェッチ)
EXステージ(実行)
MEMステージ(メモリアクセス)
WBステージ(レジスタ書き戻し、writeback)
https://gyazo.com/20646eff97e8edc11363d174ca80a49e
(さらに追記)その後、FPU導入にともない、以下のようになりました
code:verilog
typedef enum {
IF_STAGE,
EX_STAGE,
FP1_STAGE, // FPU へ入力を渡す
FP2_STAGE, // FPU からの出力を待機
FP3_STAGE, // FPU の出力をレジスタへ保存
MEM_STAGE,
WB_STAGE,
ERR_STAGE
} stage_t;
stage_t stage_reg, stage_next;
RISC-Vの命令セット
RISC-Vの命令セットについては以下が参考になる
https://github.com/jameslzhu/riscv-card/releases/download/latest/riscv-card.pdf
RISC-Vリファレンス
実装予定の命令
当面は使う予定の無い以下の命令を除いた RV32I の命令を実装する
ecall
ebreak
lw 以外のロード系命令
sw 以外のストア系命令
最終的に実装した命令一覧はこちら
→ MinCamlを動かすのに必要な命令
ビット列を浮動小数点数に変換するワンライナー
動作確認用のRubyのワンライナースクリプト。
code:ruby
"0_00000000_00000000000000000000000".gsub("_", "").pack('B*').unpack("g")
#=> 0.0
指数部の最大値と最小値
0xFF
無限大 or NaN
0x00
ゼロ or 非正規数
最大
0xFE (指数 = 127)
仮数 * 170141183460469231731687303715884105728(2 ^ 127)
最小
0x01(指数 = -126)
仮数 * 1.1754943508222875e-38
Tang Nano 9K対応
Tang Nano 9Kで確保可能なブロックRAMのサイズ
Tang Primer 20Kで例のレイトレを動かしたい