前回の記事では、FPGAボード(Sipeed Lichee Tang Primer)を使って設計しているオリジナル32ビットCPUコアNihoniumを32ビットバスに対応させ、漸くネイティブな32ビットMCUに昇華させることができました。
前回時点のNihoniumコアでは、オリジナル16ビットCPUコアMoscoviumとの後方互換性維持のため、奇数ワードアドレスに対するロングワードアクセスを許容して、ワードスタック方式の仕様を是としました。このままでは、32ビットデータをスタックする際にパフォーマンスが落ちる場合があります。
今回は、Nihoniumのロングワードスタック命令を整備して、この問題を解決します。
一般的なCPUでは、異なるサイズのデータをスタックしてしまうと不具合が生じるため、スタックサイズが固定されています。例えば以下の如くなります。
以下図に(16ビット)ワードスタックの場合と、(32ビット)ロングワードスタックの場合の様子を示します。
ワードスタック(上図左側)では、スタックするデータの最小単位を16ビットとして、16ビット毎にデータをスタックします。ロングワードスタック(上図右側)の最小単位は32ビットです。
従って、ロングワードスタックに於いて、初期設定でスタックポインタspをロングワード境界に乗せておくと、Spは4の倍数で変化するので、ロングワード境界から外れることはありません。
Nihoniumをロングワードスタックとして使うためには、Spがロングワード境界から外れない様、ワードサイズでスタックする命令を使わない様にプログラムしなければなりません。
現時点のNihoniumには、ワードスタック命令と、ロングワードスタック命令が混在しています。
以下の命令は、スタックに対して16ビットワードデータをスタックする、ワードスタック命令です。
ワードスタックを制御するのに必要十分な命令が備わっています。
以下の命令は、スタックに対して32ビットワードデータをスタックする、ロングワードスタック命令です。
実は、こちらの命令セットは不完全で、ロングワードスタックとして制御するために必要な命令が揃っていませんでした。
例えば、制御レジスタをスタックするpushc命令/popc命令は、制御レジスタの下位16ビットだけをワードデータとしてスタックします。制御レジスタを32ビットデータとしてスタックする命令が有りません。
サブルーチン分岐命令jal命令/rtn命令に関しても、戻り先のプログラムカウンタpcを16ビットサイズでスタックします。ロングワードスタックを使う場合、Pcを32ビットサイズでスタックする命令が必要ですが、ありません。
ロングワードスタックとして制御するために追加が必要な新規命令は以下の通りです。
つまり、現時点のNihoniumでは、スタックはあくまで「ワードスタック」であって、スタックするデータは16ビットワードサイズ単位となります。ワードサイズのデータ×2個をスタックする事に、ロングワードスタック命令が使われているだけです。
Nihoniumでは、奇数ワードアドレスに対するロングワードアクセスを許容してあるので、スタックはワードサイズとロングワードサイズを混在することが可能なのです。
Nihoniumでは、現状、以下の使い方ができます。
折角メモリバスを32ビット幅に拡張したのに、この使い方では、スタック操作でロングワードデータを使用すると、パフォーマンスが上がらないケースが発生してしまいます。32ビットバスを活用して、スタック操作のパフォーマンスを最大限に引き出すためには、ロングワードスタック命令だけを使って、ロングワードスタックとして使用しなければなりません。
これを実現するためには、ロングワードスタック命令に足りない、pushc命令/popc命令/jal命令/rtn命令に対応する32ビットデータ版新規命令を追加する必要が生じます。
Nihoniumで、ロングワードスタック制御を阻害している要因(以下)を取り除いて行きます。
現在のサブルーチン分岐命令(jal命令/rtn命令)では、プログラムカウンタが16ビットでスタックされるため、スタックポインタspが2の倍数で変化してしまい、ロングワードスタックとして使用することができません。制御レジスタのスタック命令(pushc命令/popc命令)も同様です(下図)。
プログラムカウンタpcを含めて、ほとんどの制御レジスタは16ビット長なので、32ビットデータをスタックする命令を作らなかったことが原因です。しかし、ロングワードスタックとして使うためには、16ビットの制御レジスタを32ビット長でスタックする命令が必要であると判りました。
サブルーチン分岐制御(jal命令/rtn命令)に関しては、プログラムカウンタpcを32ビットロングワードサイズでスタックする、jall命令及びrtnl命令を追加します。
新規命令名には、ロングワードスタック命令を意味する「〜l(エル)」を末尾に加えます。
これに伴って、従来のワードスタック命令の末尾にも、ワードスタック命令を意味する「〜w」を末尾に加えて正式名称(jalw命令/rtnw命令)とします。なお、過去のプログラムコードが変更なく使用できる様、「〜w」の付かない旧命令(jal命令/rtn命令)も別名として残します。
※別名はアセンブラの表記上だけの問題で、アセンブル後の命令コードは同じです。
新規命令(jall命令/rtnl命令)の特長は以下の通りです。
下図の通り、Pcのサイズは変わらず16ビット長ですが、スタックされるときのデータ長は、16ビット⇔32ビットと、異なります。
従って、jalw命令/rtnw命令と、jall命令/rtnl命令は、必ず対応する対で使わないと、誤動作します。
プログラム凡ミス要因の温床となりかねませんが、ロングワードスタック対応のため、致し方なしです。
pushc命令/popc命令に関しても同様に、制御レジスタを32ビットロングワードサイズでスタックする、pushcl命令及びpopcl命令を追加します。新規命令(pushcl命令/popcl命令)の特長は以下の通りです。
※jmp命令/rtn命令と同様、従来のワードスタック命令の正式名称をpushcw命令/popcw命令に変更します。元のpushc命令/popc命令は別名として継続使用可能です(命令コードは同じ、アセンブラ表記上の互換性維持対応です)。
従来通り、ワードスタックとして使用する場合、ワードスタック命令とロングワードスタック命令を混在使用しても、機能的には問題ありません。
ロングワードデータのスタック操作で最大パフォーマンスを得たい場合には、ロングワードスタック命令だけを使って、ロングワードスタックとして使用可能となるのが、今回の新規命令追加による恩恵です。
前回、メモリバスを32ビットに拡張した時に気が付いて、追加しておくべきだった命令でしたね。「もう少し深く検討しなさいよ」ってことかな?反省……。
実行時、16ビット長のプログラムカウンタpcをスタックするサブルーチン分岐命令( jall命令/rtnl命令)に対処が必要でした。同様に、Pcをスタックする割り込みに関してはどうでしょうか?確認しておきましょう。
割り込みとrti命令に関しては、下図の通り、戻り先アドレスPcと状態レジスタsrの2個をワードサイズでスタックします。
割り込み処理では、16ビット長の状態レジスタsrおよび、プログラムカウンタpcを、常に2個対でスタックするため、スタックポインタspの変化は4の倍数であり、ロングワードスタックで使用しても矛盾しません。幸い、割り込みに関しては、ロングワードスタック対応のための特別な対処は必要ないと判りました。
ロングワードスタックを使うために、割り込みにまで手を入れないとならない状況は、ちょっと対処が困難だったので、変更なしで使えたのはラッキー👍でした。
今回はNihoniumコア(CPU)が差し替わるだけですので、前回作成したNHOC113LB4408等を使い、シミュレーションで動作確認して行きます。
以下リンクより、最新のデザインをダウンロードすることができます。
今回はシミュレーションのみで、FPGAボードへは書き込みませんが、必要に応じて、FPGAボード(Sipeed Lichee Tang Primer)への書き込み手順はこちらを参照してください。
以下に、TD IDEでのコンパイル(論理合成・配置配線)結果をまとめておきます。
前回と同等な結果でした、問題なしです。
iverilogを使ってシミュレーション検証をしていきます。検証に使うプログラムを以下にリンクしておきます。
プログラム:niho_lng_stk.asm
今更ですが、シミュレーションの実行手順を以下に示しておきます。最初に使った時に各々説明済みのハズですが、まとめた記事が無かった様なので……。
実験を進めて行きます。
実験サンプル:【 】内はCygwinシェルウインドウからの実行コマンド
【iverilog -f test.lst ; if ( ! $status) vvp a.out】
VCD info: dumpfile test_nh.vcd opened for output.
Test Pass
CPI: 40/ 31( 0)= 1290/1000
finish: 3160 ns
【gtkwave test_nh.vcd】
以下に、シミュレーションタイミング図を示して解説します。
上から、クロック・リセット、メモリバス信号、内部レジスタ(汎用レジスタ、制御レジスタ)と、観測しています。
上図に示した範囲で実行されている命令コードを、以下アセンブルリストで確認しながら追跡して見てください。
アセンブルリスト:niho_lng_stk.asmをアセンブルしたniho_lng_stk.datwから抜粋
// jump and link long word stack
// OPC ADDR ASM
@000c c700 // 0018 ldbih r7,(lab_test_sub)>>8
@000d bf2e // 001a ldbil r7,(lab_test_sub)&0xff
@000e 7f7f // 001c jall r7
@000f 794a // 001e movfc r1,sp
:
: (中略)
:
// test_sub:
@0017 7f61 // 002e pushcl pc
@0018 7f6c // 0030 popcl tr
@0019 000b // 0032 rtnl
動作を簡単に説明しておきます。タイミング図と照らし合わせながらご確認ください。
この解説で、上に示したタイミング図を追えましたか?
スタック操作は全てロングワードデータなので、スタックポインタspも必ず4の倍数で変化していて、常にロングワード境界(偶数ワード)アドレスに乗っています。32ビット化したメモリバスを活用して、最大のパフォーマンスを発揮できる事が確認できました。
この他、CPUセルフテストプログラム(niho_self.asm)にも、新規追加した命令を加えて動作確認済みです。
前回のメモリバス32ビット拡張で、やり残していたテーマでした。後付けっぽくなりましたが、つじつまは合って、後方互換性を維持したまま、32ビットデータのハンドリング(スタック)でもパフォーマンスを落とすことなく実現できたのはGoodでした。