FPGAボード(Sipeed Lichee Tang Primer)を使って設計しているオリジナル32ビットCPUコアNihonium(下図)に関して、CPUの内部バス(A,B,Cバス)と演算器(ALU)は32ビット化したのですが、外部バス(Memory bus)は16ビットのままになっています。
内部32ビット化したのだから、32ビット演算を実行できるわけで、RAM上に確保した変数領域に32ビットデータを読み/書きする機会も増加しているにもかかわらず、外部バスは16ビットでした。32ビットデータを読み/書きする時には、CPUが16ビットの上位データと下位データに分割してアクセスするような状態と、なっています。
昔使っていたX68000というPCの、MC68000というCPUもまた、内部32ビット/外部16ビットのCPUだったのですが、そんな名器たちと一緒だったのです。
ただ、これではパフォーマンスが上がらないので、今回はNihoniumの外部バス(上図のMemory bus)を32ビットに拡張しようと思います。
※筆者が使っていたのはX68000 EXPERTという機体で、CPUは日立製HD68HC000PS10(10MHz定格、互換品)が載っていました。自己責任のクロックアップで18MHzまで動いた記憶があります。余談でした(1^−^)v。
メモリバスを32ビットへ拡張すると、ROM/RAMやI/Oユニット等も32ビットに拡張しなくてはなりません。対象となるユニットは、ROM/RAM/I-RAMのメモリに加え、多数I/OユニットのI/Oレジスタ、SDRAM(+キャッシュメモリ)等、膨大な数に及びます。全てを32ビット化するのは非現実的なので、以下表の対処とします。
メモリマップ | メモリ空間 | アクセス |
ROM | 32ビットアクセス空間 | |
I-RAM (命令RAM) | 16ビットアクセス空間 | |
RAM | 32ビットアクセス空間 | |
I/Oレジスタ | 16ビットアクセス空間 | |
SDRAM (キャッシュ) | 16ビットアクセス空間 |
SDRAMも、本格的に使うとなれば32ビットバス化してパフォーマンスを向上したいところですが、8Mバイトもの大容量メモリを必要とするアプリケーションが直近思い当たらないため、今回は16ビットのまま変更しません。
結果、使用頻度が高くてパフォーマンスに直結するであろう、内蔵ROM/RAMだけを32ビットバス化します。
16ビットアクセス空間に設定したユニットは、バスステートコントローラBUSCが今まで通り16ビットアクセスするので、一切変更の必要がありません。
CPUをデュアルコア化した時、CPUとBUSCのトポロジは、以下図の様にしてあるので、CPU⇔BUSC間は32ビットバス、BUSC⇔I/Oユニット間は16ビットバス、BUSC⇔ROM/RAM間は32ビットバス、と言う様に、柔軟に接続することができます。
16ビットアクセス空間に設定したユニットへは、今までと同じメモリバス仕様でアクセスするので、従来のメモリバス仕様(以下図およびリンク)を踏襲して、後方互換性を維持しなくてはなりません。
従来のメモリバス仕様から、バスコマンドbcmdを4ビットに拡張します(下表)。
bcmd | badr | bdatr/bdatw | command | ||||||
bcmdl | bcmdb | bcmdw | bcmdr | ||||||
[3] | [2] | [1] | [0] | [1] | [0] | [31:16] | [15:8] | [7:0] | |
0 | 0 | 0 | 0 | - | - | - | - | - | NOP |
0 | 0 | 0 | 1 | A[1] | - | - | D[15:8] | D[7:0] | READ WORD |
0 | 0 | 1 | 0 | A[1] | - | - | D[15:8] | D[7:0] | WRITE WORD |
0 | 0 | 1 | 1 | - | - | - | - | - | inhibit |
0 | 1 | 0 | 0 | - | - | - | - | - | NOP |
0 | 1 | 0 | 1 | A[1] | 0 | - | D[15:8] | - | READ BYTE |
1 | - | - | D[7:0] | ||||||
0 | 1 | 1 | 0 | A[1] | 0 | - | D[15:8] | - | WRITE BYTE |
1 | - | - | D[7:0] | ||||||
0 | 1 | 1 | 1 | - | - | - | - | - | inhibit |
1 | 0 | 0 | 0 | - | - | - | - | - | NOP |
1 | 0 | 0 | 1 | A[1] | - | D[31:16] | D[15:8] | D[7:0] | READ LONG |
1 | 0 | 1 | 0 | A[1] | - | D[31:16] | D[15:8] | D[7:0] | WRITE LONG |
1 | 0 | 1 | 1 | - | - | - | - | - | inhibit |
1 | 1 | 0 | 0 | - | - | - | - | - | inhibit |
1 | 1 | 0 | 1 | - | - | - | - | - | inhibit |
1 | 1 | 1 | 0 | - | - | - | - | - | inhibit |
1 | 1 | 1 | 1 | - | - | - | - | - | inhibit |
拡張したbcmd[3]は別名をbcmdl(エル)と言って、ロングワードサイズのアクセスを示す信号線です。
上表では、4ビットbcmdの全組み合わせ(16通り)を網羅してありますが、bcmd[0]=bcmd[1]=1および、bcmd[2]=bcmd[3]=1と、なる組み合わせは出力禁止です。
表の上半分、bcmdl=0の部分は、従来のメモリバス仕様と同じで、変更有りません。この範囲では、データバス(bdatr/bdatw)の下位16ビットしか使わないため、従来通りのバスアクセスが保証されます。bcmdl=1となる場合だけ、データバスの上位16ビットが有効となって32ビットバスアクセスが実行される仕様です。
後方互換性を保った状態で、32ビットバスアクセスに対応することができました。
内蔵ROMは、メモリバスとフェッチバスの2系統からアクセスがあります。フェッチバス側は、スーパスケイラ版CPUで32ビットフェッチするために作ったROMが有るので、これをベースに改造して行きます。メモリバス側も32ビットアクセスできるように改造すればOKです。
今まで、スーパスケイラ版と通常版では、フェッチバスのビット幅が異なっていたので、ROMラッパを2種類作って維持してきました。2種類維持するのは面倒なので、これを機にロングワードフェッチコマンドfcmdl(エル)を新設して、16/32ビットフェッチ可能なROMにして統合しておきます。
内蔵ROMにシリアルモニタからプログラムをダウンロードする「ブートモード」に関しては、ロングワードで書き込む必要を感じないため、「ブートモードでのロングワード書き込みは禁止」とする仕様制限を課しておきます。勿論、現在、デザインデータページに上がっているブートローダプログラム(mcoc_boot.asm)では、ワード書き込みしか実行していないため、動作に支障ありません。
今まで16ビット幅だったデータバスを32ビット幅に拡張します。
以下図の様にTD IDEのIP Generatorを使い、16ビット幅のRAMマクロをデュアルポートRAMで作り直して、2つのポート(A,B)を使って32ビットアクセスします。
ポートの割り当ては以下としました。
順当にMemory SizeのWidth(上図)を32ビットにして、普通のシングルポートRAMで作成しても良かったのだけど、そうすると、必要な32KビットRAMマクロ数が4個に増えて、最小のRAM容量が16Kバイトに増加してしまいます。Nihoniumの最小RAM容量8Kバイト版が作れなくなってしまうので、デュアルポートRAMを使いました。
CPU本体(Nihonium)の方は、読み出しデータバスbdatrおよび、書き込みデータバスbdatwを16ビット→32ビットに拡張します。
オリジナル16ビットCPUコアMoscoviumの方には32ビットアクセスする命令が無いので、今回は修正不要です。後方互換性を維持した状態でメモリバス仕様を拡張した恩恵です。
一般の32ビットCPUでは、ロングワードアクセスはロングワード境界に対して、しか許されていません。「ロングワード境界」と言うのは、アドレスが4の倍数に乗っていることを指します。
例)ロングワード境界アドレス=0x0000,0x0004,0x0008,0x000c,0x0010,……
本HPでは、ロングワード境界アドレスの事を「偶数ワードアドレス」と呼ぶ場合があります。
例)偶数ワードアドレス=0x0000,0x0004,0x0008,0x000c,0x0010,……
例)奇数ワードアドレス=0x0002,0x0006,0x000a,0x000e,0x0012,……
話を元に戻すと、ロングワードアクセスがロングワード境界(偶数ワードアドレス)に対してのみ許される場合、自分でRAM上に割り当てした変数領域などへの読み/書きは、ちゃんと、偶数ワードアドレスに割り当てておけば良い事です。
では、スタックはどうなるでしょうか?
Nihoniumの既存命令で、汎用レジスタの下位16ビットをスタックに読み/書きする、popw命令/pushw命令があります。Nihoniumの汎用レジスタは32ビット幅なので、32ビットデータをスタックに読み/書きする、popl命令/pushl命令も、当然のことながら存在しています。
さて困りました。スタックに16ビットデータを書き込んだ後、32ビットデータを書き込みに行くと、どうなるでしょうか?
例)
pushw r0
pushl r1
上のコード例では、pushl命令を実行する時のスタックポインタspの値が、ロングワード境界に乗っていることを保証できません。
回避策を検討してみましょう。
上の二案では、回避できないことが判りました。
Nihoniumの後方互換性を維持して、前節のスタック問題を回避する方法は、以下の手段しか思いつきませんでした。
元々、16ビットアクセス空間を残した段階で、16ビットアクセス空間に対するロングワードアクセスは、ワードアクセス×2回に分割して実行する必要が生じていました(下表)。
アクセス空間 | アドレス | アクセス | 処置 |
16ビット | − | ロングワード | ワードアクセス×2回に分割する |
32ビット | 偶数ワード | ロングワード | ロングワードアクセスする |
32ビット | 奇数ワード | ロングワード | ワードアクセス×2回に分割する |
上表に無いバイト/ワードアクセスについては、従来と変わらないので省略しました。
今まで、ロングワードアクセス命令の実行時、CPUがワードアクセス×2回に分割して実行していたのですが、今回はアクセス先の空間によって挙動が変わるので、この処理をBUSCに委ねます。
回避策3)を選択することで、奇数ワードアドレス(非ロングワード境界)へのロングワードアクセスが許容され、スタックにワードスタック命令とロングワードスタック命令を混在して使うことが可能となりました。今までのCPU仕様からのデグレイド無しで32ビットバスへ拡張することができます。
後方互換性維持のため、この様な回避策を取ったのですが、32ビットアクセス空間のRAM等へロングワードアクセスする場合、奇数ワードアドレスに対してアクセスするようなプログラムを組んでしまうと、パフォーマンスが落ちてしまいます。
動作としては正常なので、パフォーマンスの低下に気が付かずにプログラムが完成してしまう場合も、あり得ます。
パフォーマンスが重要なケースであれば、アセンブラのorg疑似命令alignオプションや、align疑似命令などを活用して、ロングワードアクセスがロングワード境界に対して実行されるように配慮してください。
改造したCPUやBUSC等の動作検証用として、16ビットアクセス空間に、極小容量(16バイト)のRAMを配置しておこうと思います。以下にI/OメモリIOMEM16ユニットのレジスタ表を示し、説明を続けます。
Addr | Name | R/W | Bit:15〜0 | Memo | |||||||||||||||
0xf150 | iomedat0 | RW | DAT0[7:0] | DAT1[7:0] | i/o memory data 0 | ||||||||||||||
0xf152 | iomedat1 | RW | DAT2[7:0] | DAT3[7:0] | i/o memory data 1 | ||||||||||||||
0xf154 | iomedat2 | RW | DAT4[7:0] | DAT5[7:0] | i/o memory data 2 | ||||||||||||||
0xf156 | iomedat3 | RW | DAT6[7:0] | DAT7[7:0] | i/o memory data 3 | ||||||||||||||
0xf158 | iomedat4 | RW | DAT8[7:0] | DAT9[7:0] | i/o memory data 4 | ||||||||||||||
0xf15a | iomedat5 | RW | DATA[7:0] | DATB[7:0] | i/o memory data 5 | ||||||||||||||
0xf15c | iomedat6 | RW | DATC[7:0] | DATD[7:0] | i/o memory data 6 | ||||||||||||||
0xf15e | iomedat7 | RW | DATE[7:0] | DATF[7:0] | i/o memory data 7 |
BUSC出力のセレクト信号はbcs_iome_nです。上表の範囲でアサートされるので、IOMEM16ユニット内では、アドレスbadr[3:0]を復号してレジスタを特定しています。このI/Oユニットはバイト/ワードアクセスが可能です。
I/Oレジスタ仕様:
他のI/Oユニットと同じ、I/Oレジスタ空間に割り当てられています。I/Oレジスタ空間は16ビットアクセス空間なので、IOMEM16ユニットにロングワードアクセスすると、ワードアクセス×2回に分割してアクセスが実行されます。単に、その様なアクセスの動作検証用として使うユニットです。
小容量ですが普通に16バイトのRAMですので、何か使い道があるなら、ユーザ使用してOKです。すべてのエディションに共通してインスタンスしてあるので、エディションが変わっても、IOMEM16ユニットの16バイトは、変わらずアクセスすることが可能です。
メモリバスを32ビットに拡張したマイコンNHOC113LB0408を作成しました。と言っても、32ビットに拡張したBUSCやROM/RAMは全エディション共通なので、過去の全エディションが32ビットバスに拡張されています。
以下リンクより、最新のデザインをダウンロードすることができます。
FPGAボード(Sipeed Lichee Tang Primer)への書き込み手順はこちらを参照してください。
以下に、TD IDEでのコンパイル(論理合成・配置配線)結果をまとめておきます。
おっと。今まで、あまり見かけなかったクリティカルパスがレポートされました。今回の改造で手を入れたパスなので、なんぞ、良からぬクリティカルパスを作ってしまったのかもしれません……。タイミングはメットしていますので、様子を見ることにしました。
32ビットバスに拡張した回路が、意図した通りに動いているかどうか?内部信号をモニタしながら動作検証する必要があるので、iverilogを使ったシミュレーションで確認します。
各回路の動作確認は、別途テストパタンを作成して確認してあるので、ここでは、以下のプログラムで代表してシミュレーション確認しておきます。
確認用プログラム:nhoc_ram_march.asm
内蔵RAMの先頭アドレス0x5000番地から256バイトの範囲に対して、ロングワードアクセスでマーチングテストを実行するプログラムです。シミュレーション用なので、FPGAボードで実行しても意味がありません。
では、32ビットバス拡張後のデザインでシミュレーションしてみましょう。
実験サンプル:【 】内はCygwinシェルウインドウからの実行コマンド
【iverilog -f test.lst ; if ( ! $status ) vvp a.out】
RAM Marching test
Test Pass
CPI: 2162/ 1678( 0)= 1288/1000
finish: 88080 ns
同じプログラムを32ビットバス拡張前のデザインで実行すると、以下の様になります。
【iverilog -f test.lst ; if ( ! $status ) vvp a.out】
RAM Marching test
Test Pass
CPI: 2546/ 1678( 0)= 1517/1000
finish: 103440 ns
シミュレーションログから明確に解るのは、シミュレーション終了時間「finish:〜ns」の偏差です。
32ビットバス拡張後: 88,080ns
32ビットバス拡張前: 103,440ns
32ビットに拡張したことで、約17%高速に実行することができました。CPI(Clocks Per Instruction)も低下して、高速化していることが見て取れます。
これを、シミュレーション波形で確認してみると、以下図となります。
上半分が32ビットバス拡張後、下半分が拡張前のシミュレーション波形です。
アドレスbadr、バスコマンドbcmd等に注目すると、0x5000番地から0x0000_0000を読んで、0xffff_ffffを書き込んでいることが判ります。
32ビットバス拡張前のシミュレーション(下半分)では、ワードアクセス×2回に分割されているため、各々2サイクルで読み/書きが実行されて、遅いです。32ビットバス拡張後のシミュレーション(上半分)では、1サイクルで32ビットデータの読み/書きが終了していて、高速化されています。
橙色🍊に着色しておいた命令レジスタirの変化に注目すると、0x5000番地のアクセスを実行しているのが、以下の命令だと解ります。
ir[15:0]=0x761f: ldl r3,[r7] (ldl命令)
ir[15:0]=0x7679: stl [r7],r1 (stl命令)
ロングワードアクセスがワードアクセス×2回に分割されている分、32ビットバス拡張前のシミュレーションでは、2つの命令の実行サイクル数が間延びしていますが、32ビットバス拡張後のシミュレーションでは、短縮されています。
以上シミュレーションによって、32ビットバス拡張後の正常動作と、パフォーマンスも上がったことを確認できました。
漸く、Nihoniumをネイティブな32ビットMCUに拡張できました。16ビットMCUだったMoscoviumの設計着手から数えて2年以上も、かかってしまいました。まあ、楽しみながら、チマチマやっているので良いのですけれど……(1^−^)d。