そう言えば、以下の記事で設計した乗除算コプロセッサの実機動作確認を忘れていました……😱。
「#16-2 Moscoviumで四則演算する」
「#16-3 除算器の設計」
なんとなく、半導体集積回路の設計って、シミュレーションがメインなのです。シミュレーションで散々動作検証かけて、「動いた〜、バグ無し」って言って払い出すと、一区切り。そこから配置配線、フォトマスク作成、と次のステージに進み、工場で試作が流れる、という手筈となります。
論理設計者にとっては、シミュレーション検証が終わって払い出したら、実物が上がってくるまで暫しの休息タイムです。
今回の乗除算コプロセッサも、「シミュレーション検証終了時点で休息タイムに突入していた」、と言う事で、今更ながら、FPGAボード(Sipeed Lichee Tang Primer)に焼き付けて、実機動作確認しておきたいと思います。
整数の乗除算を多用するアプリケーションとして、円周率の計算プログラム(calc_pi.ino)を使おうと思います。
「#10-6 328duinoをクロックアップ(完結編)」: 328duino,UNO+calc_pi.ino
「#11-9 一の次は二」: DUE+calc_pi.ino
「#14-10 Arduino IDEのESP32開発環境」: ESP32+calc_pi.ino
円周率計算プログラム(calc_pi.ino)をMoscoviumのアセンブラに移植して、円周率を計算させてみます。万が一、乗除算コプロセッサにバグが有って、正しい演算結果を返さないことが有れば、数1,000桁計算した円周率のどこかの桁から全く異なる値になって、期待値と一致しなくなることでしょう。
こちら(#10-6)の解説の通り、あるいはプログラム(calc_pi.ino)をご覧になって解る通り、円周率の計算は32ビット整数演算の塊です。32ビットの加減乗除算が必要となります。
16ビットCPUであるMoscoviumで32ビットの加減乗除算を実行するには、いくつかの命令を組み合わせて実行してください。
Moscoviumには、16ビットデータの加減算命令(add命令、sub命令)が備わっています。これを使って32ビットの加減算を実行するには、下位桁から上位桁に向かって順に加減算する必要があります。上位桁の加減算実行時、下位桁からのキャリ(またはボロウ)を一緒に算入しなくてはなりません(以下コード)。
// レジスタペアr0:r1と、r2:r3の加減算
// いずれも、偶数レジスタが上位ワード、奇数レジスタが下位ワード
// 加算
add r1,r3
adc r0,r2 // 下位桁r1+=r3におけるキャリを一緒に加算するadc命令
// 減算
sub r1,r3
sbb r0,r2 // 下位桁r1−=r3におけるボロウを一緒に減算するsbb命令
16ビットCPUの命令を使って、32ビットの加減算が2命令2サイクル実行ですから、16ビット→32ビットのビット数増加分のオーバヘッドですね。まだましな方。
当然、32ビットCPUなら1サイクル実行できる演算です。
32ビット乗算ルーチンについては、こちらで紹介済みです。コード(サブルーチン)を再掲しておきます。
// ================================
// unsigned 32*32=32 bits multiply routine:
// r0:r1=r0:r1*r2:r3 ; disturbed r2,r3,Sr[ML]
// ================================
# r0:r1
# r2:r3
# *)-------
# r3:r1 : 3) ML=1
# r3:r0 : 1) ML=0
# r1:r2 : 2) ML=0
# r0:r2 : not calculate
# +)-----------
# r0:r1 : 4)
# ================================
mulu32:
clsr sreg_b_ml // ML=0
mulu r0,r3 // 1)
mulu r2,r1 // 2)
sesr sreg_b_ml // ML=1
mulu r1,r3 // 3)
add r0,r2 // 4)
add r0,r3
rtn
32ビット乗算を実行するために、7命令8サイクル実行+サブルーチン分岐(jal命令+rtn命令)ですので、少しオーバヘッドが大きいです。32ビット乗算器を搭載したCortex-M3(DUE,STM32)やXtensa LX6(ESP32)などでは、1サイクルで実行可能な演算です。
もし、乗算コプロセッサMULC16が無かったら、ソフトウェアでシフト乗算ルーチンをコールすることになるので、数100サイクル掛かることになっていたでしょう。16ビットのシフト乗算プログラムはこちらです。16ビットでも約130サイクル掛かりました。32ビットだと、もっと遅くなるはずです。
除算については、除算コプロセッサDIVC32で32ビット除算可能なため、以下の命令列で計算可能です。
// レジスタペアr0:r1÷r2:r3の除算
// いずれも、偶数レジスタが上位ワード、奇数レジスタが下位ワード
divls
divlq
2命令14〜17[サイクル]実行です。内、32ビットデータの転送に6サイクルも要しているので、その分のオーバヘッドは少し痛いですが、Cortex-M3やXtensa LX6と比べても、それほど引けは取らないはずです。
やっぱり、32ビット除算できるようにしておいて正解でした。ソフトウェアで32÷32ビットシフト除算ルーチンを作ったら、軽く数100サイクル掛かってしまうところでした。
C言語版calc_pi.inoからの移植は、いつもの通り力業の手作業です(1^−^)v。
C言語のソースと同じ動作になるようにアセンブラソースを記述して行きました。MoscoviumにはC言語など高級言語(コンパイラ)のサポートが無いので、アセンブラで組むしか手がありません。筆者は久々のアセンブラプログラミングを楽しんでいますが、高級言語しか使ったことの無い方々にとっては苦痛かも知れません。
ただ、やっぱりプログラムのデバッグはC言語で実施しておきたいです。プログラムその物、のデバッグが済んでいるかどうか?は、とても重要だからです。プログラムその物、の動作が確認できていれば、後はその通りに動作するよう、アセンブラコードに置き換えて行くだけですが、スクラッチからアセンブラでプログラムしようとすると、そりゃあもう大変です。
今回は、プログラムとしては完成しているcalc_pi.ino(C言語ソースコード)をアセンブラに変換するだけなので、楽勝でした。
楽勝で余裕があったから、という訳ではありませんが、多倍長固定小数atan()関数の実装では、内部で使用するローカル変数をスタック上に確保して、ldwsp命令、stwsp命令を使ってアクセスする様にコーディングしてあります。
こちらも、遅ればせながら、Moscovium ver.1.00で拡張した命令の実機動作確認を兼ねます。
使ってみて気づいた乗算命令の注意点を以下に記しておきます。
デバッグ時、3.1415…の計算結果が全く出てこなくて、少し嵌まりました。r1を2乗するコードをML=1で実行していたため、「何で乗算結果がゼロなの?!」って、一瞬パニックになりましたよ。
初期動作確認はシミュレーションで実施しました。
数100桁〜数1,000桁のシミュレーションは時間がかかりすぎるので、4〜8[桁]程度計算させてみて、3.141592…くらいが出力されることを確認できるまでは、シミュレーションでデバッグしました。それらしい値が出力されるようになったら、その先は実機でデバッグします。
4〜8[桁]程度なら、シミュレーション時間も大して掛からないし、しかも、Moscoviumの内部信号をモニタしながら動作確認(デバッグ)可能なので、デバッグ効率はすこぶる良かったと思います。
シミュレーション実行状況:【 】内はCygwinシェルウインドウからの実行コマンド
【iverilog -f test.lst ; if ( ! $status ) vvp a.out】
:
VCD info: dumpfile test.vcd opened for output.
OK+ pi=3.
1415
9267
OK+ digits= 8 (size= 3)
Test Pass
calc_pi.inoのアルゴリズムだと、より下位桁からのキャリが伝搬しない分、最後の数桁は正しくありません。上のシミュレーションでも、小数点以下8桁分計算させて、「3.1415926」(7桁)までが正しい値となりました。
※「シミュレーションでソフトウェアのデバッグ?」っと、思うかもしれませんが、スマホアプリだって初期デバッグは「シミュレータ」ですよね?アプリ開発したことが無いので良く知りませんけど……。そう考えると、そんなに変な事ではないでしょう?
前節のシミュレーションサンプルの様に、演算結果をVerilog-HDLの$display()システムタスクで容易に確認する手段として、シミュレーション用の「プリントユニット(ビヘイビアモデル)」を作りました。以下、シミュレーション用プリントユニットのI/Oレジスタ一覧です。
Addr | Name | R/W | Bit:15〜0 | Memo | |||||||||||||||
0xfff0 | princtl | W | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − |
NL D |
print unit control |
0xfff2 | princhr | W | − | − | − | − | − | − | − | − | − | CHR[6:0] | print character | ||||||
0xfff4 | prindec | W | DEC[15:0] | print decimal word | |||||||||||||||
0xfff6 | prinhex | W | HEX[15:0] | print hexadecimal word | |||||||||||||||
0xfff8 | prinhlf | W | HLF[15:0] | print half floating point | |||||||||||||||
0xfffa | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | reserved |
0xfffc | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | reserved |
0xfffe | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | reserved |
もちろんこれはシミュレーション用の機能なので、実機(FPGAボード)では使用できませんが、シミュレーションによるソフトウェアデバッグ効率の向上に一役買ってくれました。
プリントユニットのビヘイビアモデルは、テストベンチ(test.v)に仕込んであるので、そちらを参照して見てください。
シミュレーションデバッグでそれらしい値が出力されるようになったら、実機(FPGAボード)でデバッグを実行しました。
FPGAボードではシミュレーション用のビヘイビアモデルは使用できないので、UART8N1を使い、PCとシリアル接続して計算結果を確認しました。そのために必要だったのが、16ビット符号無整数値を4桁の10進数として表示するルーチンです。
アルゴリズムは、以下の通りです。
除算コプロセッサDIVC32を搭載しないエディションでも使用可能な様に、「割り算」は除算命令を使わずに実装してあります(10進数のシフト除算)。具体的なコーディングについては、完成版プログラム(↓)でご確認ください。
完成版プログラム:calc_pi.asm
C言語版(calc_pi.ino)では、4桁毎に計算した円周率を10桁毎の区切りに変更して、以下の様なフォーマットで出力していました。
実験サンプル:【 】内はシリアルモニタからの送信データ
【pi,5116】
OK+ start calculation
OK+ pi=3.
1415926535 8979323846 2643383279 5028841971 6939937510
5820974944 5923078164 0628620899 8628034825 3421170679
8214808651 3282306647 0938446095 5058223172 5359408128
:
ですが、その処理のコーディングは、さすがにアセンブラでは面倒すぎたので、以下の様に4桁毎に改行して出力しています。
実験サンプル:【 】内はシリアルモニタからの送信データ
【pi,5116】
OK+ start calculation
OK+ pi=3.
1415
9265
3589
7932
3846
2643
:
このままだと、用意した期待値テキストデータと比較できないので、Olive+のスクリプトで成形できるようにしました。
Olive+スクリプト:calc_pi_mcvm.olv
上のスクリプトを使ってフォーマットを合わせ、小数点以下5,000桁までの円周率に対して期待値を比較、一致を確認しました。相当数の乗除算が実施されたはずですので、乗除算コプロセッサの動作確認としては十分な役割を果たせたと思います。
以下に完成したブログラムをリンクしておきます。
完成版プログラム:calc_pi.asm
さすがに5,000桁のシミュレーションは時間がかかりすぎて実行する気になれないし、FPGAボード(実機)で動作確認しましたよ〜。以下、詳細にレポートします。
以下、FPGAボードに書き込む手順です。
最新のデザインは、「#16-6 半精度浮動小数演算ユニット(弐)」で作ったFPU搭載版MCOC115FP0440(FP版)ですが、古いデザインもちゃんと保守してあるので、CP版のプロジェクトファイル(MCOC_CP.al)と最上位モジュール(mcoc115cp0408.v)をダウンロードしてくれば、コンパイル可能です。
過去の(古くなった)プロジェクトデータは、「ダウンスケール版」と呼んでいます(下図)。
これまでに、上図上側の通り、Moscoviumを搭載したマイコンを拡張してきました。過去のデザインを全て保守するのは大変面倒なので、上図下側の様に、最新版のデザインから不要なユニットを外して、過去のデザインと同等な機能となるようにダウンスケールしたデザインを保守しています。
これだと、拡張によって変更せざるを得なかったCPUやBUSC等の回路は最新回路に一本化できるので、保守が容易になります。
「#16-7 Moscoviumにブートローダを」にて追加したブートモードなど、基本機能は全デザインに適用されるので、過去のデザインでもブートローダによるプログラムの書き込みが可能となる等、利点が大きいです。
calc_pi.asmとMCOC115CP0408を書き込んだFPGAボード、FTDI等のUSB⇔UART変換モジュール、PCをこちらの様に接続してください(下図は実験中の様子、ただし写真は流用)。
calc_pi.asmは、シリアルモニタから以下のコマンドを受け付けています。
実験サンプル:【 】内はシリアルモニタからの送信データ
【pi,5116】
OK+ start calculation
OK+ pi=3.
1415
9265
3589
:
0141
6314
9665
OK+ digits=5116 (size=1280)
OK: pi time[us]=0x02b89869
5,000桁超まで、期待値との比較が合致しました。良い様です。
以下表に、円周率の計算に要した時間をまとめておきます。
円周率計算時間(calc_pi) | 1,020桁 | 5,116桁 | 速度比 | 備考 | ||||
ボード | マイコン [MHz] | 実測 | @1MHz | 実測 | @1MHz | @1MHz | ||
ESP32 | Xtensa LX6 | 240 | 0.063 | 15 | 1.579 | 379 | 2.9 | 32bit,MUL,DIV |
Arduino DUE | SAM3X8E | 84 | 0.258 | 22 | 6.473 | 544 | 2.0 | 32bit,MUL,DIV |
STM32 | STM32F103C | 72 | 0.309 | 22 | 7.757 | 558 | 2.0 | 32bit,MUL,DIV |
FPGAボード | MCOC115CP0408 | 24 | 1.821 | 44 | 45.652 | 1,096 | 1.0 | 16bit,MUL,DIV |
328duino(第一形態) | ATmega328 | 25 | 15.324 | 383 |
計算不能 (メモリ不足) |
-8.8 | 8bit,MUL | |
Arduino UNO | ATmega328 | 16 | 23.944 | 383 | -8.8 | 8bit,MUL |
サイクル性能では、32ビットCPUと比較してもそんなに見劣りしなかったのですが、MCOC115の動作周波数は現在24MHzで、精々30MHzくらいまでしか上げられないことがSTAの結果から判っているし、ESP32 @240MHz等の実測値と比べると、全く勝負になりませんでした。