[電子工作]RISC-V互換 32bit/64bitプロセッサの設計に挑戦ーーーー目次ーーーー ーーーーーーーーーー 1.RISC-Vとは?RISC-Vは、2010年に米国カリフォルニア大学バークレー校で開発を開始し、多くの学生や企業が 参画し策定されたフリーでオープンなRISC(Reduced Instruction Set Computer) ISA (Instruction Set Architecture)です。 ISAを私が見た感想は、多くの命令でイミーディエイト値を設定できる点、フラグレジスタが存在せず 条件文にレジスタ名と大/小/一致の条件を記載できる点、が良いなと感じました。 また、一般に知られているように回路規模を小さくできる、命令セットの拡張性、C命令によるコード量 削減、そしてなんと言ってもロイヤリティ不要(!)という、組み込み用途に適した特徴も見逃せません。 元々は学生の研究用に開発されたので、設計は容易です。 仕様がほぼ安定した2016年頃、私も1週間ほどで基本命令(RV32IM)が動作するCPUを設計でき ました。 しかし、これに飛びついたのが中国です。中国では、多くの学生や企業がすぐに回路設計を行い、 チップを製造しました。私も、Hummingbird E203のRTLをネットで見つけ、その上位であるE310を 搭載したチップを開発したことを知って、自分で設計するのを止めてしまいました。 しかし、2020年、コロナ禍の中、E203を越えるRV32IMAFCの仕様で完成させました。 更に、2021年、RV64GCに対応し、64bit、倍精度浮動小数点演算に対応しました。 2.Hummingbird E203を調べよう(1)Hummingbird E203のシミュレーション方法 Tang Primerに組み込まれているTang_E203_Miniをシミュレーションしましょう。使うのは、フリーの IcarusVerilog。但し、使えるのはWindows版の「iverilog-v11-20190809-x64_setup.exe」だけです。 Linux版のv10では、実行しても無限ループに陥って終了しません。(原因はWait信号の記述の 仕方が良くないから。実回路でも、発振する可能性があるため、要注意。なお、私のIPでは、 そのようなことが起こらないように、記述しています。) それ以外では、Modelsimで動作することを確認しています。但し、RTLに間違いがあり、下記のように RTLの修正が必要ですので、ご注意下さい。
1. ネットを参考に、「iverilog-v11-20190809-x64_setup.exe」をダウンロードし、インストール。 2. Tang_e203_Miniの中のsrcフォルダーを2.を展開したフォルダーにコピー。 3. RTLに間違いがあり、次のように修正。これを行わないと、エラーが発生したり、無限ループに 陥って終了しない事がある。 src/e203_defines.v E203_FLEN_IS_32のdefineを追加するため、下記の記述に差し替え。
moduleの前に、次の文を追加。
私の方で、Windows版iverilog v11で検証済みのファイルは次の通りです。実行方法は次の通り。 >>sim_cpu rv32ui-p-add テストパターン名(rv32ui-p-add)は、generatedフォルダにある「?.verilog」の名前を入れて下さい。 シミュレーションは自動で停止し、gtkwaveで波形表示されます。 但し、「dhrystone4sim」は、下記のSI-RISCVのhbird-e-sdkに含まれているもので、dhrystoneの シミュレーション結果が画面に表示されます。しかし、自動的に終了しないので、必ず、Ctrl-Cを 押して、手で終了して下さい。 Tang_E203_Mini.zip 1,208,360byte
(2)シミュレーション用のソフト開発 TangPrimerのE203用のコンパイラでもバイナリはできるのですが、シミュレーション用に RAMやFlashメモリのシミュレーションモデルで読み込むヘキサファイルを作成する時は、 SI-RISCVのhbird-e-sdkを使った方が良いです。ちなみに、同じHPにオリジナルのe203コア の回路データも置かれています。 hbird-e-sdk/softwareの中で、???4simがシミュレーション用です。
なお、自作RISC-Vはメモリマップや割り込みの機構が異なるのでそのまま動かないと思われます。 (ちゃんと調べていませんが...)
3.自作RISC-Vマイコンの実装(1)自作RISC-Vの変遷 2016年頃、自作RISC-Vの設計を開始し、RV32IMの基本命令が独自バス、ウェイト無し、割り込みなし で動くところまで作ったのですが、Hummingbird E203のRTLをネットで見つけて止めてしまいました。 しかし、2020年、コロナ禍の中、TangPrimerを手に入れたこともあって開発を再開し、TangPrimerの FPGAに実装して動くところまで来ました。 また、PYNQ-Z2を使うことで、回路規模を気にせず開発できるようになりました。 現在の仕様は次の通りです。 ・対応するインストラクションは、RV32IM、RV32IMAC、RV32IMAFC、RV64IMAC、RV64GC の大きく5種類のIPを準備しています。 Compressed(16bit)命令、CSR、Atomic命令、単精度浮動小数点/倍精度浮動小数点 などに対応するモジュールを設計し、基本的にはこれらの組み合わせで実現しています。 ・命令バス、データバスを分離したハーバード・アーキテクチャで共に、AHBバス。 (RV32は32bit AHB、RV64は64bit AHB) そのままハーバード・アーキテクチャで使うことも可能ですが、一般には、AHBバスアービトレータを 用いて、ユニファイドバスで使います。 ・マシンモードの割り込みのみ対応。ecall、ebreak、未定義命令等には対応しますが、メモリアクセス 違反などには非対応。 ・Block RAMを使った大容量の命令キャッシュメモリを搭載できるようにしました。 (容量やアドレス数はFPGAのリソースに依存。)
(2)FPGAへの実装状況 ・TangPrimerへの実装状況
・PYNQ-Z2への実装状況
(3)riscv testsによる互換性テスト状況 GitHubにRISC-Vのテストプログラムとして、riscv-software-src/riscv-tests があります。 自分でコンパイルできていないので、Si-Five社のhummingbird E203に付いていたバイナリを 実行した結果を記載致します。 なお、riscv testsを実行するためには、最低限マシンモードの特権モードをサポートし、割り込み とecall命令を正しく実装しておく必要があります。 また、浮動小数点演算では、infやnanの扱い、fflagsの判定も実装しておく必要があります。 そのため、かなり、ハードルは高いです。 ・ui:RV32Iの基本命令 全てpass。 fence.iは、パイプラインやキャッシュを持っていても正しく動作します。 ・um:RV32Mの乗算、除算、余りを求める命令 全てpass。 0で割り算した時の商や余りは、とりあえずriscv testsに合わせましたが、何が正解か疑問。 ・ua:RV32A命令 全てpass。 ・uc:RV32C命令 1本だけですがpass。 ・uf:RV32Fの単精度浮動小数点 計算に問題はないが、fcmp、fcvt、fminは、fflagsで不一致が発生。lockstepは正常終了せず。 上記4つがfail。その他はpass。 なお、rounding modeは仕様書通りに実装していない。(現状、全て切り捨て) fflagsのNV(Invalid Operation)の扱いは納得いかないので、どこまでriscv testsに合わせるか、 疑問である。今は、回路がシンプルになるような判定にしている。 ・mi:マシンモードの特権モード 現状、csr、ma_addr、ma_fetch、shamt、socの5つがfail。その他はpass。 使う予定が無い機能は実装していません。 現状、全く実装できていないのは、CSRのMachineCounter/Timers(mcycleなど)、 Debug/TraceRegisters、Supervisorモード、NMI(マスク不可割り込み)、奇数アドレスへの 16bitアクセスや4の倍数以外のアドレスへの32bitアクセス、メモリのアクセス違反の対応、 などです。 4.RV32IMACの詳細(1)ハード構成 32bitのRISC-Vプロセッサで最も標準的な構成です。 TangPrimer版の仕様書はこちら。 その他の特徴は次の通り。 ・RISC-Vプロセッサ:RV32IMAC、32bit AHBバス(アービトレータによるユニファイドバス) 2KB〜4KB(4アドレス)命令キャッシュ 乗算器はDSPを使用し2clk、除算器は2〜17clk。 ・ブートROM:32bit×4K(0wait) ・SRAM:32bit×24K(0wait) ・SDRAM:32bit×2M(4〜5wait!、内部72MHz) ・DMA:UART、SPI、メモリーメモリに使用可能 ・UART:PCからプログラムの書き込み、PCとの通信 ・SPIホスト/スレーブ:LCDパネル制御、SDカード ・QSPIフラッシュメモリI/F:QSPIフラッシュメモリ ・I2Cホスト/スレーブ: ・PWM:サウンド ・ADC:アナログジョイスティック ・タイマ:タイマ割り込み用 ・GPIO:ボタンなど ・割り込みコントローラ:エッジ/レベル、極性設定可
(2)ソフトウェア 最初は自作のRV32アセンブラでプログラムを作成して検証を行いましたが、現在はRISC-Vのクロス コンパイラgccを使ってます。 RV32IMACアーキテクチャでは、16bit命令と32bit命令が混在して厳しい条件ですが、乗算、除算、 浮動小数点演算ライブラリ(ソフトエミュレート)など含め、問題なく動いています。割り込み、DMA転送を 有効にしても安定して動いているようです。SPI、UART、液晶パネルへの文字やグラフィック表示等を 行うプログラムを作成し、問題なく動作しています。(注意.ハード制御に関係する全ての変数に、 「volatile」を付ける事) また、やや大規模なソフトとして自作ゲームをArduinoから移植しましたが、3Dポリゴン、 PWMサウンド、ADC含め動作に問題はありませんでした。 マンデルブロート集合を描いたり、レイトレーシングを行うソフトも問題なく動作しました。 最適化を行えば、整数演算だけならESP32並の速度で動作しました。但し、浮動小数点を使うと、 ESP32に全く及びません。 RISC-Vのクロスコンパイラのインストール方法は次の通りです。
1.Cコンパイラをインストール >>mkdir ダウンロードするフォルダ >>cd ダウンロードするフォルダ >>sudo apt update >>sudo apt-get install autoconf automake autotools-dev curl python3 libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev >>git clone --recursive https://github.com/riscv/riscv-gnu-toolchain >>cd riscv-gnu-toolchain >>./configure --prefix=/opt/riscv32 --with-arch=rv32imac --with-abi=ilp32 >>sudo make -j4 >>export PATH=/opt/riscv32/bin:$PATH
2.初期化ルーチンの設定:「start.S」の作成 割り込みなしで動作検証する場合、スタックポインタだけ設定して、main()を実行します。 割り込みを使用する場合、今は、割り込み処理ルーチンや割り込みのエントリやイネーブル (CSRの書き換え)等もここに書いています。そしてグローバル変数を書き換えることで、 メインコードに割り込み発生を知らせています。(メインコードの関数を呼び出すこともできるはず)
3.リンク情報の設定:「link.ld」の作成 ROM起動用のコード生成時は、コードをROM領域(0x20000000-)、データをSDRAM領域(0x80000000-) に格納できるように、次のような内容にしました。(初期値を持つグローバル変数に対応できるように修正) SRAMに書き込んで実行する場合は、コードをRAM領域(0x01000000-)に設定します。
SDRAMに書き込んで実行する場合は、SDRAM領域(0x80000000-)だけを設定します。
4.コンパイルの実行:「make.sh」の作成 初期の動作検証は、最適化無しで、次のようにしてコンパイルします。 FPGAやシミュレータで使用するHEXファイルと、逆アセンブルしたファイルも出力しています。
sprintfやstring関係の関数も使いたい場合は、次のようにします。(見つけるのに苦労しました。) なお、SPIやUART等のレジスタアクセスで、ステータスをポーリングするコードが最適化で消えてしまう ので、関係する全ての変数に、「volatile」を付ける必要があります。
5.RV32IMAFCの詳細(1)ハード構成 32bitのRISC-Vプロセッサに単精度浮動小数点演算器を追加した構成です。 TangPrimer版の仕様書はこちら。 ・RISC-Vプロセッサ:RV32IMAFC、32bit AHBバス(アービトレータによるユニファイドバス) 1KB〜4KB(4アドレス)命令キャッシュ 単精度浮動小数点演算は、加算が4clk、乗算が5clk、割り算が17clk、ルートが16clk、積和が7clk。 ・ブートROM:32bit×4K(0wait) ・SRAM:32bit×24K(0wait) ・SDRAM:32bit×2M(4〜5wait!、内部72MHz) ・DMA:UART、SPI、メモリーメモリに使用可能 ・UART:PCからプログラムの書き込み、PCとの通信 ・SPIホスト/スレーブ:LCDパネル制御、SDカード ・QSPIフラッシュメモリI/F:QSPIフラッシュメモリ ・I2Cホスト/スレーブ: ・PWM:サウンド ・ADC:アナログジョイスティック ・タイマ:タイマ割り込み用 ・GPIO:ボタンなど ・割り込みコントローラ:エッジ/レベル、極性設定可
(2)ソフトウェア RV32IMAFCアーキテクチャでは、浮動小数点演算もハードで問題なく計算できています。演算速度も RV32IMACとは桁違いです。 やや大規模なソフトとして自作ゲームをArduinoから移植しましたが、3Dポリゴン、オセロの思考ルーチン、 PWMサウンド、ADC含め動作に問題はなく、最適化を行えば、整数演算だけでなく浮動小数点を 使ったソフトもESP32と差を感じないような速度で動作しました。 マンデルブロート集合を描いたり、レイトレーシングを行うソフトも問題なく動作しました。 RISC-Vのクロスコンパイラのインストール方法は次の通りです。
1.Cコンパイラをインストール RV32IMACと同じ。
(2)初期化ルーチンの設定:「start.S」の作成 RV32IMACと同じ。
(3)リンク情報の設定:「link.ld」の作成 RV32IMACと同じ。
(4)コンパイルの実行:「make.sh」の作成 RV32IMAFCで浮動小数点演算命令(F命令)を使う場合は、次のように設定します。 なお、組み込み関数は sin→sinf、cos→cosf のように変更が必要です。これによりソフト エミュレートに比べ格段に速くなります。 最適化を行う場合、memset等の組み込み関数が必要です。更に、浮動小数点ライブラリや sprintfやstring関係の関数も使いたい場合は、次のようにします。(見つけるのに苦労しました。) なお、SPIやUART等のレジスタアクセスで、ステータスをポーリングするコードが最適化で消えてしまう ので、関係する全ての変数に、「volatile」を付ける必要があります。
6.RV64GCの詳細(1)ハード構成 RISC-Vプロセッサでほぼフルスペックのものが、RV64GCです。命令セットとしては、 RV64IMAFDCになります。 さすがに回路規模が大きく、TangPrimerには実装できません。PYNQ-Z2へ実装しました。 RV64はRV32とアッパーコンパチではありません。特に、C命令は、同じコードを異なる 命令に割り振っているので、RV32命令は全く動作しないとお考え下さい。 また、64bitプロセッサは速いと思うかもしれませんが、そもそも回路が複雑になっている ため、動作周波数が低下します。また、intやfloatを使用する場合、64bitで計算した結果 を32bitに丸める処理が追加になり、32bit版より遅くなります。 明らかに効果があるのは、キャッシュメモリが64bit単位で読み込めることと、倍精度浮動 小数点のdoubleを使用する場合、1cycleでメモリに読み書きできるため2倍以上速くなります。 特徴は次の通り。 ・RISC-Vプロセッサ:RV64IMAFDC、64bit AHBバス(アービトレータによるユニファイドバス) 8KB(4アドレス)命令キャッシュ 乗算器はDSPを使用し2clk、除算器は2〜33clk。 倍精度浮動小数点演算は、加算が4clk、乗算が5clk、割り算が33clk、ルートが32clk、積和が7clk。 ・ブートROM:64bit×2K(0wait) ・SRAM:64bit×64K(0wait) ・DMA:UART、SPI、メモリーメモリに使用可能 ・UART:PCからプログラムの書き込み、PCとの通信 ・SPIホスト/スレーブ:LCDパネル制御、SDカード ・QSPIフラッシュメモリI/F:QSPIフラッシュメモリ ・I2Cホスト/スレーブ: ・PWM:サウンド ・タイマ:タイマ割り込み用 ・GPIO:5方向スイッチ、ボタンなど ・割り込みコントローラ:エッジ/レベル、極性設定可
(2)ソフトウェア 最初は自作のRV64アセンブラでプログラムを作成して検証を行いましたが、現在はRISC-Vのクロス コンパイラgccを使ってます。 まず、RV64はRV32と全く互換性がありません。単にレジスタのbit数の問題だけでなく、C命令は 同じコードを異なる命令に割り振っているので、全く動作しません。 現状、RV32と同じプログラムを動かしていますが、浮動小数点演算含め、問題なく動いています。 割り込み、DMA転送を有効にしても安定して動いているようです。 (注意.ハード制御に関係する全ての変数に、「volatile」を付ける事) RISC-Vのクロスコンパイラのインストール方法は次の通りです。toolchainは同じものを使用しますが、 設定方法は、当然、RV32とは異なります。
1.Cコンパイラをインストール >>mkdir ダウンロードするフォルダ >>cd ダウンロードするフォルダ >>sudo apt update >>sudo apt-get install autoconf automake autotools-dev curl python3 libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev >>git clone --recursive https://github.com/riscv/riscv-gnu-toolchain >>cd riscv-gnu-toolchain >>./configure --prefix=/opt/riscv >>sudo make >>export PATH=/opt/riscv/bin:$PATH
2.初期化ルーチンの設定:「start.S」の作成 RV32IMACと同じ。
3.リンク情報の設定:「link.ld」の作成 RV32IMACと同じ。
4.コンパイルの実行:「make.sh」の作成 最適化を行う場合、memset等の組み込み関数が必要です。更に、浮動小数点ライブラリや sprintfやstring関係の関数も使いたい場合は、次のようにします。 なお、SPIやUART等のレジスタアクセスで、ステータスをポーリングするコードが最適化で消えてしまう ので、関係する全ての変数に、「volatile」を付ける必要があります。
Arduinoの紹介はこちら。
「ESP32の使い方」はこちら。
「Sipeed Maix BitでA.I.を体験」はこちら。
「TangPrimer:中国製FPGAでプロセッサの設計に挑戦」はこちら。「PYNQ:PythonでFPGAを動かそう」はこちら。 |