FPGAボード(Sipeed Lichee Tang Primer)を使って設計したオリジナル16ビットCPUコアMoscoviumのパフォーマンスを向上しようと、内部演算器(ALU)と汎用レジスタを32ビットに拡張した、オリジナル32ビットCPUコアNihoniumを設計しました。
出来上がったNihoniumのパフォーマンスを、実機(FPGAボード)やシミュレーションにて検証したところ、かけた費用(コスト)に対して、効果(パフォーマンス)が伸びていないことが判りました。同時に、「きっと、こういう物なのだな」と、悟りを開いてしまいました(1^−^;)。
つまり、コスト→論理の増加量なので、50%論理が増加する工夫によって、パフォーマンス→実行速度が50%高速化すると思ってはいけないのだと。マイコンのパフォーマンスを向上しようとする目論見には、それ相応の大きな代償(コスト)がかかるものなのだという事。
そこで、「コスト増加量が大きすぎる」として一旦保留したスーパスケイラを実施して、費用対効果がどのように見えるものなのか?検証してみたいと思います。
前回、スーパスケイラ版Nihonium-SS全体のブロック図を起こし、32ビット命令フェッチROMを設計、具体的な命令の並列実行可否について考察を始めたところまで進みました。今回は同様に、具体的な例を想定しながら、命令の同時発行可否について事例研究を進めたいと思います。
本節では、同時発行できない⛔場合のある、特殊な命令について議論します。
以下に登場しない、その他の命令は、更に次節にて解説するリソースの依存関係を使って判定が必要です。
分岐命令は、他の命令と異なってプログラムカウンタpcを更新する、特殊な命令です。
例2−1)分岐命令と、次命令
つまり分岐命令は、スロット0に発行される場合、常に単独で実行しなくてはなりません。「単独」とは、スロット1に他の命令を同時発行しないことを指します。
分岐命令がスロット1に発行されることは、リソース依存関係がない限り、問題ありません。
分岐命令の場合、条件が合いさえすれば、他の命令と並列実行可能な場合もありますが、かなり限られた条件が成立する必要があるため、ほぼ、単独で発行され、シングルスロットと同じ実行状態となります。高速化することは困難ですが、シングルスロット版に比べてペナルティはありません。
分岐命令は、Branch(b**)命令、jmp命令、jal命令、rtn命令、rti命令です。
Nihonium(/Moscovium)では、CPUの内部(A,B,C)バスが、最上位階層に引き出されていて、コプロセッサとのI/Fに使用されます。
スーパスケイラでは、スロット0とスロット1にそれぞれ内部(A,B,C)バス0,1が存在します。
2組のバスを最上位階層に引き出しておくと、コプロセッサ命令も2命令同時発行可能にすることができます。ただしその場合、コプロセッサも×2個持ちとなって、マイコンの論理規模が増大します。
現時点でNihonium用に接続可能なコプロセッサは、半精度浮動小数演算ユニットH-FPUとなりますが、論理規模は約1,100LUTです。×2個搭載すると、2,200LUTにもなります。オリジナル16ビットCPUコアMoscoviumが約2,100LUTなので、もう1個CPUを追加するのと同程度ですね。
そこまでしてコプロセッサ命令を2命令並列実行したいケースがあるの?って考えてみました。
よほどのことが無い限り、無いですね。結論として、コプロセッサI/Fとして最上位階層に引き出すのは、スロット0の内部(A,B,C)バス0としました。従って、コプロセッサ命令は必ずスロット0に発行されなければならず、スロット1への発行を禁止します。
例2−4)コプロセッサ命令と、次命令
コプロセッサ命令は、現在のところ半精度浮動小数演算ユニットH-FPU関係の命令(hadd命令等)だけです。
Nihoniumの状態レジスタsrには、CPUの動作モードを設定するためのビットが含まれており、このビットが変化すると、CPUの動作状態が変わってしまいます。
状態レジスタsrの設定ビット概要:
状態レジスタsrは、プログラムカウンタpcと同じ様に、CPUの動作実行状態に深く関与する特別なレジスタなので、Srを更新する命令に関しては、分岐命令の所で説明したのと同様、特別な扱いが必要です。
例2−5)状態レジスタの更新と、次命令
NihoniumでSrの設定ビットを変化させる可能性があるのは、rti命令、movtc命令、sesrh命令、sesrl命令、clsrh命令、clsrl命令、popc命令、ldch命令、ldcl命令です。
現時点で、多ワード長命令は、ldli命令、link命令です。
Nihoniumには、およそ100個の命令が存在します。
前節までに例示してきた通り、全く同じ命令であっても、プログラム中の登場順番によって同時発行できるかどうかが異なる場合があります。つまり、命令の組み合わせの数は、概算で100×100=10,000通りとなります。
前節では主に、特殊な命令の組み合わせに着目して同時発行可否を議論しました。同時発行可否を決める要因として、更にリソースの依存関係があります。
リソースとは、主にレジスタを指します。Nihoniumには汎用レジスタ(r0〜r7、8本)と、制御レジスタ(sr,pc,iv,sp,tr、5本)が存在します。
本節では引き続き、リソースの依存関係について考察して行きます。
例3−1)
例3−1)、例3−2)では、命令としては同じ、ldw命令とadd命令ですが、指定されたレジスタ番号によって、同時発行可否が変わりました。
2レジスタオペランド命令×2個の場合、汎用レジスタは8本あるのでレジスタ組み合わせの数は84通りです。命令組み合わせ数(約10,000通り)に積で効いてきますので、この時点で既に莫大な組み合わせの数です。そろそろ、場合の数を勘定するのも嫌になってきましたね。
まだまだ、隠れたリソースの依存関係があります。続けます。
例3−3)隠れたリソース更新、Sr
Nihonium(/Moscovium)では、アセンブラソースコードレベルに登場するレジスタは、1命令当たり0〜2個ほどだし、明示的にレジスタ値を更新する転送先レジスタは1個以下です。しかし、例3−3)〜例3−5)に示した様に、命令の実行に伴って、実は更新されているTr,Sp,Sr等との依存関係も判定条件に加えなければならないことが判明しました。
ここまでの考察結果をまとめてみると、まず、命令の組み合わせによって、同時発行の可否が決まります。
命令の組み合わせをクリアすると、次はリソースの依存関係に注目します。リソースの依存関係次第で、同時発行不可能な場合があります。
判定の対象となる命令は2個存在します。命令0と命令1です。更新・参照レジスタの復号結果も、命令0,命令1各々に発生するので、命令0の復号結果には接尾子として「0」、命令1の復号結果には「1」を追加して区別します。
もし、連続する2命令(命令0、命令1)にリソースの依存関係がある場合、以下の論理式が真(TRUE)となります。
リソース依存関係=(
fch_updreg_x0[3:0]==fch_refreg_x1[3:0] ||
fch_updreg_x0[3:0]==fch_refreg_y1[3:0] ||
fch_updreg_x0[3:0]==fch_refreg_z1[3:0] ||
fch_updreg_y0[3:0]==fch_refreg_x1[3:0] ||
fch_updreg_y0[3:0]==fch_refreg_y1[3:0] ||
fch_updreg_y0[3:0]==fch_refreg_z1[3:0]);
上式は、命令0の更新レジスタ(fch_updreg_*0)と、命令1の参照レジスタ(fch_refreg_*1)が一致するかどうか?を調べています。この時、更新レジスタ無しのケースと、参照レジスタ無しのケースが一致しないように、別々なコードを割り当ててあります。
組み合わせの数としては、14種の更新レジスタ2個と、14種の参照レジスタ3個の、計142×143=145通りで決着した様です。
回路としては、4ビットの一致回路(EOR,AND)が6個と、6入力OR回路が1個であり、信号の伝搬遅延時間としては大したことはありません。→ 判定は高速に行うことができます。
ただ、命令コードからfch_updreg_*、fch_refreg_*を復号するデコーダ論理が深いため、デコーダの信号伝搬時間は相応に長いでしょう。本来であれば、パイプライン段数を増やしてIDステージで1サイクルかけて判定すれば良いのですが、Nihoniumはパイプライン段数が2段と浅いため、恐らくはクリティカルパスとなり得ます。
STAによる確認はもちろんのこと、余計な伝搬遅延回路を作り込まないように注意が必要です。
※とか言いながら、何か所かSTAで引っかかって、対策を余儀なくされました……。「少し考えが甘かった」と反省しながら、チマチマと高速化を実施して、完成した回路に落ち着いています。
リソース依存関係式にて依存関係あり(真、TRUE)と判定された場合、命令0と命令1を同時に発行することはできません。この場合、命令0だけがスロット0に発行され、スロット1には命令を発行しません。スロット1はストールして、次の命令が発行されるまで待機します。
命令1はキュー(NIR、下図)に溜まって、次の命令実行時、新たに命令0として判定が実行されることになります。以下図にフェッチ制御FCHブロックのブロック図を示しておきます。
※スーパスケイラ版Nihonium(CPU)全体のブロック図はこちら。
通常、プログラムカウンタpcは、現在実行中の命令が格納されたアドレス+2バイト(1ワード)先のアドレスを指しています。このアドレスは、分岐しなかった場合に、次に実行すべき命令のアドレスです。
従って、命令実行中にPcを参照した場合、読み出せるアドレス値はスロット0とスロット1では2(バイト)だけ異なります。Pcを参照する命令がスロット0に入る場合と、スロット1に入る場合で、異なるPc値が読めなくてはならないことを示しています。
前節のブロック図に示した様に、参照用のPc値はPc0,Pc1として、FCHブロックがRGFブロックへ出力しています。
実行に伴ってPcを参照する命令は、Branch(b**)命令、jal命令、movfc命令、pushc命令が該当します。
32ビットに拡張したフェッチバスは、ロングワード境界からの32ビットデータをフェッチします。プログラムカウンタpcは、32ビットデータをフェッチすると+4増加して次のデータをフェッチします。
分岐命令などが、非ロングワード境界(奇数ワード)アドレスに分岐した場合、最初のフェッチで取り込んだ32ビットデータの内、有効なデータは下位16ビットのみとなります。上位側16ビットデータは、分岐先アドレスの2バイト手前にあるデータなので、実行してはなりません。
例4−1)非ロングワード境界(奇数ワードアドレス)への分岐
メモリアクセスは、最終的に1組のメモリバスに対するアクセスに集約されます。
つまり、もし、2個のメモリアクセス命令を同時発行したとしても、メモリバスは1組しかないので、アクセスは時分割で順番に実行されることになります。
例5−1)メモリリード命令
メモリアクセスMEMブロックのブロック図は、以下図の様になります。
※スーパスケイラ版Nihonium(CPU)全体のブロック図はこちら。
内部(A,B,C)バスが2組出入りする以外では、図中bctlブロック内に、2個のスロットからのアクセス要求を調停するためのステートマシンが追加されています。
次回は、残りの詳細設計を進めて、スーパスケイラ版Nihonium-SSを完成させたいと思っています。