公開日:2022/04/16
title.png

20th Season


#20-7 32ビットに拡張するゾ(よ)

 FPGAボード(Sipeed Lichee Tang Primer)を使って設計したオリジナル16ビットCPUコアMoscoviumのパフォーマンスがちょっと気になった件。

 問題と感じたのは、SDRAMにキャッシュメモリを適用して実行したマーチングテストの話です。

  「#20-2 キャッシュメモリコントローラの設計(下)

 上記事中の実験では、キャッシュヒット率が85%を超えていたにもかかわらず、マーチングテストは10%程しか高速化できていませんでした。

 原因は、SDRAMアクセスに要していた時間よりも、Moscoviumの32ビットデータ処理の方が支配的だったため、でした。何という事でしょう!キャッシュメモリの速さを実感する前に、Moscoviumの遅さが目立ってしまうとは。
 なんとかせねば、と思ったのです。


 前回、オリジナル32ビットCPUコアNihoniumを完成させました。今回は、実物(FPGAボード)で動作確認して、どの程度高速化できたのか検証してみたいと思います。と、その前に、iverilogを使った論理シミュレーションを実行してみます。

シミュレーションによる動作確認

 最後にシミュレーション環境を整備しておきます。

 テストベンチtest.v(およびtest.lst)では、マイコンの最上位モジュールをインスタンスしています。test.vの中に、シミュレーションによるタイミング図確認を容易にするための、CPU内部レジスタをモニタする記述があり、MoscoviumとNihoniumで共通化することができません(汎用レジスタのビット幅が異なるため)。
 また、シミュレーションを実行した時に出力するVCD(タイミング図)ファイルも、CPUが異なればモニタすべき信号が異なるので、共通化しない方が便利です。

 このため、`defineマクロSIM_CORE_NHの有無によって、テストベンチtest.v内の記述を切り替えて使います。

  `define SIM_CORE_NH  // 有効にすると、Nihonium系テストベンチ
  //`define SIM_CORE_NH  // 無効にすると、Moscovium系テストベンチ


 マイコンの最上位モジュール(nhoc113sc0408.v等)を切り替えるのに併せて、SIM_CORE_NHを適切に設定して使って見てください。


 では、CPU全命令のセルフテストプログラムで動作を確認しておきましょう。

 iverilogを使ってシミュレーションを実行する方法についてはこちらをご確認ください。

 2本のセルフテストプログラムのシミュレーションによって、1)Moscoviumとのオブジェクトコードレベル後方互換性、2)Nihonium拡張機能の正常動作を確認しました。

Moscoviumのプログラムを移植

 NihoniumをNH=0のモードで使うなら、Moscoviumとの後方互換なので、基本的にそのまま動作します。移植と言う作業は不要です。当然、動作速度もMoscoviumと同じなので、全くパフォーマンス(実行速度)は向上しません。

 本節では、円周率πの計算プログラム(calc_pi.asm)をNihonium(NH=1、32ビットモード)用に移植してみようと思います。その過程で、移植に際して注意すべき点について解説したいと思います。

 と、上の様な点に注意しながら、円周率計算プログラム(calc_pi.asm)をNihonium用に移植しました。数1,000桁の円周率を計算して、期待値との比較ができるので、CPUが正常に機能しているかどうか見極めるには、良い題材だと思います。

完成プログラム

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

  円周率計算プログラム:calc_pi.asm


 実機(FPGAボード)で動作確認する前に、シミュレーションしてみました。iverilogを使ったシミュレーションの実行方法はこちらをご確認ください。

 シミュレーションなので、8桁で留めておきました。次節で実機(FPGAボード)を使い、数1,000桁の円周率を計算してみます。シミュレーションで8桁の円周率を求めるのに要したシミュレーション時間⌚は以下の通りでした。

 元々、Moscoviumも32ビット除算コプロセッサDIVC32を使用していたので、Nihonium(32ビット)化による高速化は2割ほどだったようです(7,695/6,045≒×1.27倍速)。まだ、たった8桁のシミュレーションですので、次節で実機(FPGAボード)に書き込んで、5,000桁以上の計算時間を計測します。


 なお、MoscoviumとNihoniumでは、実行すべきプログラムが異なるので、プログラム(calc_pi.asm)先頭付近の文字列マクロで切り替えられるようになっています。

  # Moscovium:  Nihonium(NH=0):  Nihonium(NH=1):
   # mc=""  mc=""  mc="#"
  # n0="#"  n0=""  n0="#"
  # n1="#"  n1="#"  n1=""
  def mc,"#"
def n0,"#"
def n1,""

// for Moscovium if ""
// for Nihonium (NH=0) if ""
// for Nihonium (NH=1) if ""

 デフォルトは、Moscovium用としてあるので、Nihonium (NH=1)用に設定を変更後、アセンブルしてください。

※NihoniumのNH=0モードは、「divlu; divlq」をソフトウェアでエミュレートしているので、実行速度は一番遅くなっています(非推奨)。

実験したらゴール?

 NHOC113SC0408を書き込んだFPGAボードにFTDI等のUSB⇔UART変換モジュールを接続して、電源を投入してください。

 以下図は、実験中の様子です(写真は使い回し)。

a158_expr.jpg


  実験サンプル:【 】内はシリアルモニタからの送信データ
  【pi,5116】
  OK+ start calculation
  OK+ Klingenstierna's formula
  OK+ pi=3.
  1415
  9265
   :
   :
  6314
  9648
  OK+ digits=5116 (size=1280)
  OK: pi time[us]=0x022c30fc

 約5,000桁の円周率を計算させました。こちらの期待値と比較して、最後の2桁を除き、期待値との一致を確認できました。これだけの円周率を計算するには、相当な数の命令と演算を実行していることになりますので、まず、Nihoniumの正常動作を確認できた、と言って良いでしょう。

※計算した円周率の最後の数桁は正しくありません。より下位桁からの桁上がりを計算できていないためです。アルゴリズム上の問題なので、最後の数桁が期待値と合わないことに関しては無視します。


 演算に要した時間をMoscoviumと比較してみましょう(以下表)。

円周率計算時間(calc_pi) 5,116桁
実測[s]
速度比 備考
マイコン マイコン [MHz]
MCOC115DC0416 Moscovium×2 24 27.577 1.78 Klingenstierna,16bit,MUL,DIV,DUAL
MCOC115CP0408 Moscovium 24 49.017 1.00 Klingenstierna,16bit,MUL,DIV
NHOC113SC0408 Nihonium 24 36.450 1.34 Klingenstierna,32bit,MUL,DIV

  考察:

 円周率計算プログラムの速度としては約34%の高速化を確認できました。
 円周率計算プログラム(calc_pi.asm)では、元々Moscoviumに32ビット除算コプロセッサを接続してあって、時間のかかる32ビット除算をハードウェアで計算していました。その分を含めて、32ビット化による速度が約34%向上でしたので、まずまず、と言った結果ではないでしょうか?

マーチングテストはどうなった?

 元々、本プロジェクトの起点となったSDRAMマーチングテストについても確認しておきましょう。
 以下、Nihonium(32ビットモード、NH=1)に対応したSDRAMマーチングテストプログラムをリンクしておきます。

  実験プログラム:test_sdram.asm

 上のプログラム(test_sdram.asm)では、アセンブルオプションでNihonium/Moscoviumを切り替えるのではなく、実行時動的にNihoniumとMoscoviumを判定して、実行するコード(マーチングテストサブルーチン)を切り替えるようにしてあります。
 Nihonium/Moscoviumの判定には、状態レジスタsrのNHビットに1を書き込み出来るかどうか?を使いました。Moscoviumでは、NHビット位置が0に固定されているので、NH=1に設定できるのはNihoniumコアだけです。その他、IDレジスタユニットのidrgcodeレジスタ=0x1130を確認する事でも判定できます。


 マーチングテストの一部、read1/write0部分のコードを以下に抜粋して、32ビット化による効果を予測しておきます。

処理 Moscovium (16ビット) ワード数 サイクル   Nihonium (32ビット) ワード数 サイクル
ループ開始 // read 1, write 0
ldwi r6,mch_top>>16
ldwi r7,mch_top&0xffff
mch_r1w0_loop:

4 4   // read 1, write 0
ldli r7,mch_top
mch_nh_r1w0_loop:

3 3
メモリ
アクセス
movtc tr,r6
ldwx r3,[r7]
stwx [r7],r0

3 4 ldw r3,[r7]
stw [r7],r0

2 3
アドレス
更新
add r7,r2
adc r6,r0

2 2 add r7,r2 1 1
期待値
比較
cmp r3,r1
bne mch_fail

2 2 cmp r3,r1
bne mch_nh_fail

2 2
終了判定 cmp r7,r5
cmb r6,r4
blo mch_r1w0_loop

3 4 cmp r7,r5
blo mch_nh_r1w0_loop

2 3
合計
(ループ内数)
Moscovium 14(10) 16(12)   Nihonium 10(7) 12(9)

 メモリアクセスのサイクル数は、1サイクルアクセスできた場合で記載してあります。
 まず、コードサイズはMoscovium=14ワードからNihonium=10ワードと、コード効率が30%向上しました。
 次に実行サイクル数ですが、上表でループ開始時の処理は1回しか実行されないので、ループ内の処理で比較してみましょう。合計欄の丸カッコ内数値がループ内のワード数と実行サイクル数です。
 実行サイクル数はMoscovium=12サイクル/ループからNihonium=9サイクル/ループと、約25%高速に処理できるようになりました。
 上々です。

※ループ内のCPIは、Moscovium=1.2CPI、Nihonium≒1.3CPIでした。上コードの中だと、分岐(blo)命令とロード(ldw)命令が、CPIを増加させる2サイクル実行となっています。それ以外の命令は1サイクル実行です。1サイクル実行命令の数が減って、相対的に2サイクル実行命令の密度が増加したNihoniumの方がCPI値は増加していますが、実行速度自体は高速化しています。この様に、CPIと言う指標は、同じCPU・同じプログラムで比較する時にしか機能しません。


 実機(FPGAボード)でも確認しておきます。実験前の準備は、前節円周率の実験と同じです。実験用プログラム(test_sdram.asm)の使い方は、こちらに説明があります。

  実験サンプル:【 】内はシリアルモニタからの送信データ
  【march】
  DBG: Nihonium mode
  DBG: write all 0
  DBG: read 0, write 1
  DBG: read 1, write 0
  DBG: read all 0
  OK: marching finished: 0x000019c1[ms]

 キャッシュメモリ無効状態でマーチングテストを実施しました。テストはパスしていますので、問題ありません。実行時間に注目しておいてください。後で比較します。


  【cche,1】
  OK: cache on1/off0=0001

  【march】
  DBG: Nihonium mode
  DBG: write all 0
  DBG: read 0, write 1
  DBG: read 1, write 0
  DBG: read all 0
  OK: marching finished: 0x0000166a[ms]

 キャッシュメモリ有効状態でマーチングテストを実施しました。こちらもテストはパスしました。SDRAMに対するアクセス手順は同じなので、キャッシュヒット率はこちらと同じ(87.5%)です。

 肝心なのは、Moscoviumの32ビット化によって、処理速度がどれくらい上がったか?なので、以下表で確認しておきます。

CPU 論理規模
LUT数
マーチング
キャッシュ無効
マーチング
キャッシュ有効
Moscovium(16ビット) 3,000LUT 8,668ms 7,814ms
Nihonium(32ビット) 4,700LUT 6,593ms 5,738ms

  考察:

 う〜ん、こんなのばっかり、ですね。以下の様なイタレーション……。

 どうやら、マイコン(CPU)の実行速度を上げるというテーマは、一朝一夕には行かないみたいです。


 ちょっと一度、高速化によるコスト増加を無視して、もう一押し、行き着くところまで行かないとスッキリできないみたいです。引き続きあがいてみたいと思います。


btn_arduino.png

btn_prev.png btn_next.png

home