公開日:2022/05/21
title.png

20th Season


#20-10 クローズ(設計の完了)

 FPGAボード(Sipeed Lichee Tang Primer)を使って設計したオリジナル16ビットCPUコアMoscoviumのパフォーマンスを向上しようと、内部演算器(ALU)と汎用レジスタを32ビットに拡張した、オリジナル32ビットCPUコアNihoniumを設計しました。
 出来上がったNihoniumのパフォーマンスを、実機(FPGAボード)やシミュレーションにて検証したところ、かけた費用(コスト)に対して、効果(パフォーマンス)が伸びていないことが判りました。同時に、「きっと、こういう物なのだな」と、悟りを開いてしまいました(1^−^;)。

 つまり、コスト→論理の増加量なので、50%論理が増加する工夫によって、パフォーマンス→実行速度が50%高速化すると思ってはいけないのだと。マイコンのパフォーマンスを向上しようとする目論見には、それ相応の大きな代償(コスト)がかかるものなのだという事。
 そこで、「コスト増加量が大きすぎる」として一旦保留したスーパスケイラを実施して、費用対効果がどのように見えるものなのか?検証してみたいと思います。


 前回は、命令およびリソース依存関係によって同時発行可否が変わるケースについて考察し、フェッチ制御FCHブロックとメモリアクセスMEMブロックの詳細設計まで終了しました。今回は残りの詳細設計を進めて、スーパスケイラ版Nihonium-SSを完成させたいと思います。

レジスタの更新タイミング

  例6-1)演算フラグの更新a20a_pln_eg61.png


  例6−2)実行サイクル数の異なる命令の並列実行
a20a_pln_eg62.png

 このケースの着目点は、各命令の実行サイクル数です。
 命令0(divu)は32ビットモード(NH=1)では10+DR[サイクル]かかります。SrレジスタのDRビット=1に設定して、剰余を取得する場合は11サイクルです。他方の命令1(mov)は1サイクル実行です。
 2個の命令共、同じ汎用レジスタr1を更新しています。命令1(mov)はr1を更新するけど参照はしないため、リソースの依存関係はなく、同時発行可能なケースです。

 この時、最終的にr1に残るべき値は、順番的に後から実行されるはずだった、命令1(mov)が更新したr1であるべきです。
 ところが自然に任せて、実行サイクル順にレジスタを更新してしまうと、以下の様になってしまいます。

 命令の実行サイクル数に任せて、自然にレジスタを更新してしまうのは不可、と判りました。どうやら、同時に発行された命令は、両方の命令が実行終了するのを待ってから、演算結果をレジスタに更新しなければならないようです。→ 上図赤色点線矢印。

 そこで、レジスタファイルRGFブロック内にライトデータバッファを設けて、命令が同時発行された場合、両方の命令が終了したタイミングでレジスタを更新するように回路を追加しました。
 かつ、2個のスロットから同時に、同じレジスタへ更新要求が有った場合には、スロット1側の書き込み値を優先して、スロット0からの書き込み値は無視(破棄)する処理としました。
 以上によって、実行順番通りの更新値がレジスタに反映されます。

※そもそも、連続する2命令が同じレジスタを更新する状況は特異です。命令0で更新した値を捨てて、命令1で上書きしてしまっているわけですから、命令0を実行する意味が無くなってしまうためです。上の例6−2)は、数少ない「意味のある(かもしれない)例」だと思います。DR=1としてdivu命令を実行して剰余を取得し、剰余r2だけが必要だったケースでは商r1は不必要なので、命令1で上書きして問題ないのかもしれないからです。


  例6−3)メモリアクセス命令と、その他の命令の同時発行
a20a_pln_eg63.png


 例6−2)例6−3)のパイプライン実行図に示した通り、同時に発行される命令の中には、実行サイクル数の異なる命令があります。実行サイクル数の短い命令は先に実行を終了して、他方の実行が終了するまでストールしているため、その間スロットは遊んでいます。
 本当は、スロットに空きができたら、さらに先(次)の命令を発行してスロットを埋めていく方が、並列実行率が向上します。この辺りの処理を積極的に取り入れながら発展させて行くと、アウトオブオーダっぽくなって行く様です。


 レジスタファイルRGFブロックのブロック図を以下に示します。

a20a_rgfblk.png

※スーパスケイラ版Nihonium(CPU)全体のブロック図はこちら

 汎用レジスタ(32ビット×8本×2バンク)はbank02,bank13ブロックの中にあります。その他、制御レジスタ名と同名のブロックは、各制御レジスタの実装です。ライトデータバッファ機能はrctlブロックの中にあって、Cバス(C0,C1)を操作しています。
 レジスタの実体としては1個なので、記憶素子フリップフロップFFの数こそ、シングルスロット版と同数ですが、ご覧の通り、内部(A,B,C)バスへの入出力が2組存在するため、論理規模(LUT数)としては約×2倍に増大しています。

nop命令

 本節では、nop命令の注意点について触れておこうと思います。
 nop命令は、何もしない命令で1サイクル実行なので、例えば以下の様にして、決まった時間の経過を待つ時に使用していたのではないでしょうか?

  // wait 1us
  rept 24
  nop
  endr


 fcpu=24MHzで24回nop命令を実行すると、1μsウェイトしたのと同じことになります。数μsと言う短い時間を、システムタイマSYSTIMのポーリングなどで計測しようとすると、返って誤差が大きくなってしまうので、上コードの様に命令の実行時間で計測した方が正確です。

 ところがスーパスケイラでは、nop命令にリソースの依存関係が存在しないため、必ず2命令づつ同時発行されてしまうのです。上の例だと、24個のnop命令実行に要する時間は、1/2[μs]に短縮されてしまいます。

  例7−1)nop命令
a20a_pln_eg71.png


 この課題を解決するため、常にスロット0へ単独で発行される特殊な命令を準備してあります。

  例7−2)brn命令
a20a_pln_eg72.png

 以下のコードは、シングルスロットでもスーパスケイラでも、変わらず1μsのウェイトに相当します。

  // wait 1us
  rept 24
  brn
  endr


 brn命令は一応、分岐命令ですが、分岐することはないので、他の分岐命令の様なオペランド(ディスプレースメントdsp11)は取りません。

乗除算器

 Nihoniumには、演算器(ALU)に乗除算器が内蔵されています。
 ブロック図に示した様に、そのままの状態でALUが二重化されているため、乗除算器も×2倍の論理規模となっています。


 今回は、費用対効果を検証するため、敢えて、このままの構成としました。よって、Nihonium-SSでは、乗除算命令を2個同時に発行することができるようになっています。

  例7−3)除算命令の並列実行
a20a_pln_eg73.png


 一般的には、乗除算命令を2個並列で実行したいようなケースは、まれでしょうから、コプロセッサ命令と同様な扱いにしておくと、乗除算命令を必ずスロット0に発行するように制御することはできます。この場合、スロット1側のALUに乗除算回路が不要となるので、削減して論理規模を縮小する事が可能です。
 ただし、その様な回路構成にした場合、乗除算命令がスロット1に発行されようとするケースがすべて保留されてしまうため、命令の並列実行率を低下させてしまいます。
 コスト減少の効果を取るか?性能向上の効果を取るか?の取捨選択で、後者を選択しました。


 下図にALUのブロック図を示します。2スロットとも、同じ構成です。

a20a_alublk.png

※スーパスケイラ版Nihonium(CPU)全体のブロック図はこちら

スーパスケイラの有効/無効

 果たして、スーパスケイラの有効化/無効化を切り替えながら使いたいケースがあるかどうか、イメージが湧かなかったのですが、状態レジスタsrにSDビット(Super-scalar Disable)を追加して、SD=1と設定した時に無効化することができるようにしておきました。

a205_prgmdl.png

※スーパスケイラ有効/無効による高速化の度合いを検証するという目的のためには、必要なモード設定ビットです。後ほど実験やシミュレーションする時に使用します。それ以外の用途は、思いつきません……。

 リセット解除後のデフォルトはSD=0で、スーパスケイラ有効となります。
 同じく、Nihoniumの16ビット/32ビットモードを切り替えるNHビットは、リセット解除後NH=0(16ビットモード、Moscovium互換モード)ですので、Moscoviumで動作させていたバイナリコードをNihonium-SSに実行させると、高速版Moscoviumとして動作させることが可能です。

 ここまで、いくつか例を示して考察・解説してきた様に、シングルスロット版 → スーパスケイラ版に変更することで、処理速度が遅くなるケースはありません。必ず、同程度か高速化されます。

デザイン

 以下リンクより、オリジナル32ビットCPUコアNihonium-SSを搭載したマイコンNSOC113SD0408のデザインをダウンロードできます。FPGAボード(Sipeed Lichee Tang Primer)への書き込み手順はこちらを参照してください。

btn_moscovium.png

 マイコン型名(NSOC113SD0408等)の解釈については、こちらの表を更新しておきました。

コンパイル結果

 以下に、コンパイル(論理合成・配置配線)結果をまとめておきます。

 論理規模は18,000LUTを超えて来ました。FPGAボード(Sipeed Lichee Tang Primer)では最大19,600LUTまでしか使えないため、そろそろ上限に達してしまいます。この辺りから一気にコンパイル時間もかかるようになってきますので、あまり気楽に使えるCPUコア(Nihonium-SS)では無いかも知れないですね。

 スーパスケイラ版Nihonium-SSを単体でコンパイルしてみたところ、論理規模は約11,500LUTでした。通常のシングルスロット版Nihoniumが約4,700LUTでしたので、あらあら、×2倍では足りず、×2.4倍ほどに膨れていましたとさ。
 FPGAボードで使用可能な論理規模(最大19,600LUT)に対して、約11,500LUTなので、Nihonium-SSのデュアルコアって言うのは、サイズオーバでFPGAボードに載りきりません。そのくらいの巨大なCPUになってしまいました。

素因数分解

 スーパスケイラ化の恩恵を享受可能で最適なアプリケーションとして、素数判定プログラムに白羽の矢を立てました。これを少し応用して、素因数分解をやって見ようと思います。……素数を素因数分解すると、自分自身だけが素因数として表示されるので、素数判定プログラムとしても使用可能です。

 つまり、プログラムとしては、32ビット符号無除算を計算して、剰余が0になるかどうかで判定して行きます。除算器を2個持ちして、divu命令を2命令同時に実行できるNihonium-SSにとっては、かなり有利なアプリケーションです。
 除算命令を2個づつ並列実行できるよう、N÷KとN÷(K+1)の割り算を同時に実行して行きます。

※除算命令を2個づつ、並列実行することが肝なので、こちらの例7−3)を理解してプログラムしてください。


 素直に定義に則ったアルゴリズムで、N=int (N÷K)×Kとなる整数Kを探している事と等価です。
 K→Nとなった時N÷K→1であり、K→1の時N÷K→Nとなるため、KとN÷Kは表裏逆の関係にあります(∵Kと1÷Kが逆数同士)。つまり、K=N÷KとなるKまで試したら、その先はすでに試行済みなので、計算する意味がありません。
 Kについて解くとK=√Nですので、K=2,3,4,……,√Nまで試行して、割り切れるKが見つからなかった場合、Nは素数であると判ります。

 演算を終了する判定値√Nの計算が少し面倒だと思われるかもしれません。でも、少し考えてみると、√Nの計算は不要であることが解ります。
 √Nは、K=N÷Kの関係式からKについて解いた結果です。
 K=2,3,……と計算して行くので、スタートのK=2では、K<N÷Kです。そのままKを増加させていくと、どこかで大小が逆転して、K>N÷Kとなります。その時点でK=√Nの値を超えたことになるので、演算を終了して良いことになります。
 そもそも、素因数を判定するため、剰余N%Kを計算しており、この時、divu命令で商N÷Kも取得しているので、KとN÷Kの大小比較はオーバヘッドなしで比較可能です。


 今回は、Nを32ビット符号無整数の範囲に限定します。素因数の数が最多となるのは、一番小さい素数2のべき乗となるN=231=0x8000_0000=2,147,483,648のケースで、素因数の数は31個です。Nが素数の場合、素因数の数は最少となって1個です。
 このことから、素因数を保持する領域として、31個以上のバッファを確保しておけば、全ての素因数をリストアップできると解ります。
 素因数の数は1〜31個の間で分布するので、最後の素因数の次にデータ0を書き込んで、終端とします。終端コードまで含めたバッファ数は、32個以上あれば充足します。

 入力された32ビット符号無整数Nに対して、全ての素因数を計算したら、計算に要した時間を計測して終了します。


 プログラムとしては「素因数分解」ですが、実は、N=素数とした時が一番演算時間が長くかかります。32ビット符号無整数の中で、適切に大きい素数を素因数分解させてみて、演算速度を比較してみようと思います。
 231−1=0x7fff_ffff=2,147,483,647が素数だと知られているので、この値を使います。


 以下に、完成したプログラムをリンクしておきます。

  完成プログラム:prime_fact.asm

 アセンブルしてFPGAボードに書き込むと、シリアルモニタから以下のコマンドを受け付けています。

  シリアルモニタコマンド仕様:


 次回、シミュレーションで詳細に動作確認した後、実機(FPGAボード)に書き込んで実験してみようと思います。


btn_arduino.png

btn_prev.png btn_next.png

home