FPGAボード(Sipeed Lichee Tang Primer)を使って設計したオリジナル16ビットCPUコアMoscoviumのパフォーマンスを向上しようと、内部演算器(ALU)と汎用レジスタを32ビットに拡張した、オリジナル32ビットCPUコアNihoniumを設計しました。
出来上がったNihoniumのパフォーマンスを、実機(FPGAボード)やシミュレーションにて検証したところ、かけた費用(コスト)に対して、効果(パフォーマンス)が伸びていないことが判りました。同時に、「きっと、こういう物なのだな」と、悟りを開いてしまいました(1^−^;)。
つまり、コスト→論理の増加量なので、50%論理が増加する工夫によって、パフォーマンス→実行速度が50%高速化すると思ってはいけないのだと。マイコンのパフォーマンスを向上しようとする目論見には、それ相応の大きな代償(コスト)がかかるものなのだという事。
そこで、「コスト増加量が大きすぎる」として一旦保留したスーパスケイラを実施して、費用対効果がどのように見えるものなのか?検証してみたいと思います。
まずはスーパスケイラ(Super-Scalar)とは?と言う所から始めましょう。
CPUの中で、命令を実行するユニットの事を「スロット」🎰と呼びます。
通常の(スーパスケイラではない)CPUは、実行ユニットを1個しか持っていないので、これを特に「シングルスロットCPU」と呼びます。
スーパスケイラ方式のCPUは、複数のスロットを持っています。本ページでは2個のスロットを持ったスーパスケイラ方式CPUを設計して行こうと思います。→ 以降、2個のスロットに番号を付けて、「スロット0」と「スロット1」と呼びます。
スロットに命令を投入することを、「命令を発行する」と表現します。
スーパスケイラ方式CPUは、概ね以下の様な動作となります。
この様な方式で動作するCPUをスーパスケイラ(Super-Scalar)方式と呼びます。
詳しくは知らないのですが、スケイラ(scalar)の対義語はベクタ(vector)です(多分(1^^;))。英語読みだとスケイラが近いと思われますが、「スカラ」って使う方もいらっしゃるようで、どこか別な言語での読み方でしょうか?ベクタも、「ベクトル」って発音する場合があるので、これも同じ言語での発音なのでしょうね。(イメージ的にはドイツ語読み?)
複数の成分を持って、大きさと方向を扱う事の出来るベクタに対して、スケイラは物理量の大きさだけを表すことができます。成分数が1個のベクタと言う捉え方もできます。
成分数⇔スロット数という対応で考えると、スロットを1個しか持たないシングルスロットCPUの事を、単にスケイラ方式のCPUと呼ぶことがあります。そこからスーパスケイラという言葉を読み解くと、「スケイラ方式は超越している(スーパ)けれど、ベクタ方式ではない」って感じでしょうか?
あまり使われませんが、デュアルコアなどのマルチCPUシステムがベクタ方式に相当するとも考えられます。
余談でした。
本ページでスーパスケイラ化するのは、「#20-4 32ビットに拡張するゾ(ひ)(ふ)(み)(よ)」にて設計したオリジナル32ビットCPUコアNihoniumです。
そのスーパスケイラ版なので、Nihonium-SS(Super-Scalar)と呼んで区別します。
基本的には、2個のCPUが存在して、そこに一連の命令列を発行して行く方式となるため、ブロック図は以下図のようになります。
シングルスロット版Nihoniumのブロック図(下図)と比べて、CTLとALUブロックが多重化され、MEM,RGF,FCHブロックは共通で1個だけど、スロット0,1からの同時要求に対応するための調停論理等が追加となっています。
2個のスロットから別々に2組の内部バスI/Fがあるため、MEM,RGF,FCHブロックの箱は1個ですが、内部の論理規模はほぼ×2倍になっています。つまり、CPUが丸々2個あるのと同じです。これが、今回の高速化に対して支払う費用(コスト)です。
「CPU×2個」と言えば、「#17-1 双頭の狼」で設計した、Moscoviumのデュアルコアがあります。
デュアルコアは、うまく処理を分割できて2個のCPUに分散処理すると、単純に×2倍速となるので、とても効率の良いシステムです。しかし、マイコンなどが実行する処理には、うまく処理を分散できず、CPUが2個あっても、それを使いきれない例が、間々あります。
※円周率の計算プログラムで2個のCPUに分散処理させたのですが、「#17-8 円周率とデュアルコア」で最適化した結果、漸く×1.78倍速と言う結果でした(×2倍速には届かず)。
その点、スーパスケイラなら、連続する命令の依存関係をCPUが自動的に判定して並列実行してくれるため、分散処理を意識せず、人間が普通に組んだプログラムでも高速処理してくれることが魅力です。
現在のNihoniumは、16ビットフェッチバスを使って、16ビット/1サイクルフェッチとなっています。Nihoniumは基本的に16ビット固定長命令なので、1サイクルで16ビット→1個の命令しかフェッチできません。
2スロットのスーパスケイラとする場合、最低でも、1サイクルで2命令分→32ビットをフェッチできなくてはなりません。Nihonium-SSでは、フェッチバスを32ビットに拡張する必要があります。32ビットでフェッチすることで、命令0と命令1に相当する2個の命令を1サイクルでフェッチ可能となります。
ここ、何気に重要です。16ビット固定長命令に拘って設計してきたのが奏功しています。
命令によって16ビット長だったり、32ビット長だったりするThumb ARM (T2)みたいなコードになっていると、32ビット以上先までプリフェッチしないと、2命令分をフェッチすることができません。多ワード長の命令を復号するため、デコーダが深くなってクリティカルパスを強化してしまうことにも繋がります。
Nihoniumにも、多ワード長命令は存在しますが、現時点では2個(ldli命令,link命令)だけなので、影響は軽微だと考えられます。ldli命令に至っては、32ビットフェッチバス化によって、32ビット即値を1サイクルでフェッチできるようになるため、シングルスロット版Nihoniumより1サイクル高速に実行できると言うおまけまでついてきます(次回解説)。
ここで、命令フェッチ用のROMを設計しておきます。
TD IDEを使ってコンパイル(論理合成・配置配線)してFPGAボードに書き込む場合、MIFファイルと言うROMデータを作成しておく必要があります。
iverilogでシミュレーションする場合も、16ビット16進数データDATファイルを作成しておいて、$readmemh()システムタスクで読み込んでシミュレーションしています。
MIFファイルもDATファイルも、Moscovium/Nihoniumアセンブラ(asm_mcvm.olv)を使ってアセンブルした時に生成していますが、現時点では16ビット用の出力しかありません。
※アセンブラ(asm_mcvm.olv)はOlive+のスクリプトですので、実行するためには、WindowsにOlive+のインストールが必要となります。
フェッチROMを単純に32ビット化してしまうと、MIFファイルやDATファイル等の出力に32ビットデータバス版を作る必要があります。
と、切り替えて使用するのは面倒極まりないので、16ビットのまま使いたいと考えました。
現在、フェッチ用ROMに使っているTD IDEのRAMマクロは、16ビット幅のデュアルポートRAMマクロを使っています(下図)。
デュアルポートと言う事は、読み・書きできるポートが2組(ポートA,B)あるので、
から実行して、1サイクルで32ビットデータをフェッチすれば、ROMマクロとしては16ビットバスのままだけど、1サイクルで32ビットの読み出しが可能となります。
RAMマクロとしては16ビットのままなので、今まで通り16ビットのMIFファイルとDATファイルが使用できます。TD IDEのRAMマクロ自体(上図)も、現在のNihonium/Moscoviumで使用しているものがそのまま使えます。
そもそも今まで、何で、デュアルポートRAMマクロを使っていたのか?と言うと、デュアルコアを設計した時、2個のCPUから同時にフェッチできるようにするため、でした。
今回、その機能を32ビットフェッチ用に流用してしまうため、「スーパスケイラのデュアルコア」には、非対応とします。論理規模がおよそ×2倍となるスーパスケイラで、かつ、デュアルコアとなれば、CPU×4個分の論理規模となってしまうので、非対応の仕様制限は妥当でしょう。
※「#21-3 スーパスケイラのデュアルコア」にて、スーパスケイラのデュアルコアを実現可能としました。それに伴う、命令フェッチROMの改造についてはこちらをご覧ください。
また、命令フェッチ用RAM(I-RAM)にも、当面の間、非対応としておきます。
※現在のI-RAMは16ビットバスなので、Nihonium-SSで使いたい場合は、32ビット版I-RAMを設計する必要があります。具体的な用途が明確となって、必要に迫られた時に設計しようと思います。
スーパスケイラとは、命令を実行するスロットを複数持っていて、同時に複数命令を複数スロットに発行することで高速化する技術です。
しかし、残念ながら、全ての命令が並列実行できるわけではありません。本節以降では、どのような条件を満たすと並列実行できて、どのような条件を満たすと並列実行できないのか?具体的な例を示しながら、考察して行きたいと思います。
まず最初に、並列実行できない命令について説明した方が理解し易いと思われますので、その様な例からスタートしようと思います。
例1−1)リソースに依存関係のある命令
以上、本節では、典型的な例を示して考察しました。
次回、同様に具体的な例を想定しながら、命令の同時発行可否について事例研究を進めたいと思います。