今回フォーカスを当てるのは、オリジナル16ビットCPUコアMoscoviumの比較命令です。
2進数の大小を比較する方法として、一番理解しやすいのは、「引き算」です。2つの数値AとBがあった場合、まず、S=A−Bを計算してみます。
2の補数表現を使って符号付2進数の減算を実行した場合、S≧0とS<0に関しては、演算結果Sの最上位ビットが0か1か?で知ることが出来ます。Moscoviumの場合なら、状態レジスタsrのNフラグが立つので、このフラグをチェックするbpl命令、bmi命令で切り分けることができます。
等しいかどうかの切り分けは、演算結果S=0かどうかが判れば良いので、SrレジスタのZフラグをチェックするbeq命令、bne命令で切り分けることが出来ます。
論理回路の場合も同様に、引き算して見て正/ゼロ/負によって判定することは可能ですが、マグニチュードコンパレータ(比較回路)と呼ばれる回路が有って、こっちを使った方が小さくて速い回路になります。
TD IDEの論理合成ツールは、減算回路も比較回路もマクロで展開してくれるので、以下の様に記述(RTL)すると、自動的に必要な回路を生成してくれます。
// 引き算
wire [7:0] sub=A[7:0] - B[7:0];
wire sub_equal=|sub[7:0];
wire sub_higher=( !sub[7] || !sub_equal );
wire sub_lower=sub[7];
// 比較回路
wire cmp_equal=( A[7:0]==B[7:0] );
wire cmp_higher=( A[7:0]>B[7:0] );
wire cmp_lower=( A[7:0]<B[7:0] );
ちなみに、論理合成ツールはCコンパイラほど賢くないので、上の様な記述にすると、「引き算」を使う回路の方が小さくなるかもしれません。理由は、「比較」する方は比較回路が3つ(=と、>と、<)生成されてしまうためです(↓)。
論理圧縮できないのかな?論理合成ツールの性能によるかも?です。
RTLの場合、記述方法は一通りではなく、使い所によって最適な記述は変わるため、色々試して、「一番小さくなる記述」、「一番速くなる記述」など、レパートリを持っておくのも大切です。
16ビットCPUであるMoscoviumで32ビット整数演算をする場合、複数命令を組み合わせる必要があることは、こちらで解説済みです。
今日のテーマである「比較」はどうするか?と言うと……。sub命令とsbb命令を組み合わせて減算はできますね(↓)。
// レジスタペアr0:r1−r2:r3の減算
// いずれも、偶数レジスタが上位ワード、奇数レジスタが下位ワード
// 減算
sub r1,r3
sbb r0,r2 // 下位桁r1−=r3におけるボロウを一緒に減算するsbb命令
引き算した後、前節の様に、演算結果r0:r1レジスタペアの正/ゼロ/負をチェックしたら、大小比較可能です。でも演算結果も32ビットなので、チェックするのも大変です(↓)。
// r0:r1=0?
cmpi r0,0
bne pcnt+4
cmpi r1,0
beq equal_zero
そう言えば……。32ビットの引き算をした結果、ゼロだったかどうか?をZフラグで確認できないのだろうか?
例)sub=0xffff_8000−0xffff_7fff の計算:
−0xffff_7fff=0x0000_8001 であるから、
sub=0xffff_8000+0x0000_8001=0x1_0000_0001 であるが、
32ビット分の結果しかレジスタには残らないので、
sub=0x0000_0001である(32ビット整数としては非ゼロ、Z=0となるべき)。
これをMoscoviumの命令で計算すると、
ldwi r0,0xffff
ldwi r1,0x8000
ldwi r2,0xffff
ldwi r3,0x7fff
sub r1,r3 // r1−r3=0x8000−0x7fff=0x0001≠0なので、Z=0
sbb r0,r2 // r0−r2−Sr[C]=0xffff−0xffff−0=0x0000なので、Z=1
この時、r0−r2−Sr[C]=0x0000(sbb命令の演算)なので、sbb命令の実行でSrレジスタのZフラグ=1が立ってしまいます。つまり、sbb命令実行後のZフラグ値は、最後に実行した上位ワード演算結果がゼロだった時にZ=1となってしまうため、全体の32ビット演算結果がゼロだったかどうかはわからないことが判明しました。
確かに、「16ビットCPUだから、そういう仕様なの!」って言い訳も成立するような気はするけど、折角Zフラグがあるのに使えないのはもったいないな……。そう思って、ふとAVRの命令セットマニュアルを確認してみたところ、キャリフラグを含めて減算するCPC命令とSBC命令のZフラグ値は、命令実行前の直前値Z’を継承していることが解りました。
H8/300Hのマニュアルも確認してみましたが、こちらも同様な命令では、直前値Z’を継承していました。
※AVRでは、ADC命令のZフラグ値は直前値Z’を継承していなかったけど、理由は不明です。
直前のZフラグを継承すれば、下位ワードの減算sub命令と、上位ワードの減算sbb命令が共にゼロとなった時だけ、Z=1となります。Moscoviumにはこの論理が入っていなかったので、今回追加することにします。
他にも問題点が有って、そもそも、sub命令、sbb命令を使ってしまうと、比較だけをしたい時にも減算が実行されて、レジスタ値が変化してしまうのはもったいないです。16ビット整数ならcmp命令が有って、減算を実施するけど、Srレジスタのフラグだけを更新して、減算結果をレジスタに戻さない処理が可能です。
しかしcmp命令には、sbb命令に対応するような命令が存在しないため、32ビット整数値の比較を単純に実施することが出来ませんでした。
以上をまとめて、今回は以下の通り、Moscoviumに命令の追加と仕様変更を実施します。
Moscoviumの拡張仕様:
※一般的な「CPUコア」では、一度確定した仕様は、おいそれと変更することが出来ません。「後方互換性の維持」とかいう「しがらみ」です。Moscoviumは、「教科書で解説されている様な、シンプルなCPU」をコンセプトとしているので、その様なしがらみに縛られることなく、あるべき形に進化させようと思います。勿論、程度問題なのですが、今回の変更は許容範囲でしょう。
半精度浮動小数演算ユニット(H-FPU)を作った時に、浮動小数値の比較について少し議論しました。ただ、比較方法はかなり複雑で、最後は「十分注意してプログラミングしてください」と、ユーザ側に投げてしまった感がありました……m(1__)m。
あれから少し整理が進んで、やはりH-FPU命令として浮動小数の比較を実行する命令を新設しておきたいと思いました。
浮動小数値比較の問題点(課題)
そこで、half型の数値比較を容易にするため、H-FPUを使った浮動小数値の比較hcmp命令を新設しようと思います。
hcmp命令の仕様:
half型浮動小数値の比較については、hcmp命令でずいぶんと楽になります(↓)。
hcmp r0,r1
bcs equal_inf_nan // r0=r1の時、分岐(INF、NaNの一致を含む)
bvs exception_nan // r0−r1=NaNだった時、分岐
beq equal // r0=r1の時、分岐
bpl larger_or_equal //r0≧r1の時、分岐
bmi smaller // r0<r1の時、分岐
bne not_equal // r0≠r1の時、分岐
よほど特別な状態を判定したいのでない限り、hcmp命令1つで比較判定を完了できるはずです。
浮動小数比較hcmp命令とキャリ付き整数比較cmb命令などを拡張したデザインデータを以下リンクページに登録しておきました。
双方(cmb命令、hcmp命令)とも、具体的な用途があったわけでは無いので、MCOC115FP0440のデザインを使って、シミュレーションで動作確認だけしてあります。
キャリ(ボロウ)付き整数加減算命令のZフラグ条件は、完全に見落としていました🙈。なかなか奥が深いですね……。