今回は、減算・乗算系のコードとZeroフラグ系の制御コード(比較コードとZeroフラグがセットされていたら指定アドレスへジャンプするコード)を実装してみます。また、ジャンプ系コードもアドレッシング周りを整理しておきましょう。
今回追加する減算・乗算系コードは、以下の通りです。subが減算、mulが乗算で、形式は加算と全く同じですから演算の意味はすぐにわかっていただけるでしょう。
コード値 | 定数 | ニーモック | コードサイズ |
20 | SUBRR32 | sub32 r1,r2 | 4 (0020r1r2) |
---|---|---|---|
21 | SUBRI8 | sub8 r1,Imm8 | 4 (0021r1Imm8) |
22 | SUBRI32 | sub32 r1,Imm32 | 8 (0022r100,Imm32) |
23 | SUBRM8 | sub8 r1,(r2) | 4 (0023r1r2) |
24 | SUBRM16 | sub16 r1,(r2) | 4 (0024r1r2) |
25 | SUBRM32 | sub32 r1,(r2) | 4 (0025r1r2) |
26 | SUBMR32 | sub32 (r1),r2 | 4 (0026r1r2) |
30 | MULRR32 | mul32 r1,r2 | 4 (0020r1r2) |
31 | MULRI8 | mul8 r1,Imm8 | 4 (0021r1Imm8) |
32 | MULRI32 | mul32 r1,Imm32 | 8 (0022r100,Imm32) |
33 | MULRM8 | mul8 r1,(r2) | 4 (0023r1r2) |
34 | MULRM16 | mul16 r1,(r2) | 4 (0024r1r2) |
35 | MULRM32 | mul32 r1,(r2) | 4 (0025r1r2) |
36 | MULMR32 | mul32 (r1),r2 | 4 (0026r1r2) |
減算系のコードでは、演算を実行してその結果を格納すると同時にSigフラグ(結果がマイナス時にセット)とZeroフラグ(結果が0の時にセット)を設定します。
case SUBRR32: u32Wrk1=getRegister((u32Code & 0xff00) >> 8); u32Wrk2=getRegister(u32Code & 0xff); setSRSig(u32Wrk1<u32Wrk2); setSRZero(u32Wrk1==u32Wrk2); setRegister((u32Code & 0xff00) >> 8,u32Wrk1-u32Wrk2); u32Addr=u32Arg+4; break;
乗算系のコードでは、OFフラグ(オーバーフロー時にセット)とZeroフラグを設定します。関数u32MulOF()は、渡された2つの数値の乗算結果が32ビットの範囲を越えるか判定するものです。
case MULRR32: u32Wrk1=getRegister((u32Code & 0xff00) >> 8); u32Wrk2=getRegister(u32Code & 0xff); if (u32Wrk2>0) setSROF(u32MulOF(u32Wrk1,u32Wrk2)); else setSROF(false); u32Wrk=u32Wrk1*u32Wrk2; setRegister((u32Code & 0xff00) >> 8,u32Wrk); setSRZero(u32Wrk==0); u32Addr=u32Arg+4; break;
比較系コードは、数値の比較を行い等しければZeroフラグをセットし、結果がマイナスになればSigフラグをセットするcpを実装します。これは
第一オペラント−第二オペラント
という演算を行い、その結果を格納せずにフラグだけを変更することに相当します。
コード値 | 定数 | ニーモック | コードサイズ |
70 | CPRR8 | cp8 r1,r1 | 4 (0070r1r2) |
---|---|---|---|
71 | CPRR16 | cp16 r1,r1 | 4 (0071r1r2) |
72 | CPRR32 | cp32 r1,r1 | 4 (0072r1r2) |
73 | CPRI8 | cp8 r1,Imm8 | 4 (0073r1Imm8) |
74 | CPRI32 | cp32 r1,Imm32 | 8 (0074r100,Imm32) |
75 | CPRM8 | cp8 r1,(r2) | 4 (0075r1r2) |
76 | CPRM16 | cp16 r1,(r2) | 4 (0075r1r2) |
77 | CPRM32 | cp32 r1,(r2) | 4 (0075r1r2) |
8・16ビットの比較は、「比較対照(2番目のオペラント)の下位8・16ビットとレジスタの全ビットで」比較します。1番目のオペラントで指定されたレジスタは32ビットのまま比較されますので(つまり、「下位8・16ビット同士」の比較ではない)注意してください。
例えば、レジスタ同士の比較を8ビットで行うCPRR8は以下のような実装になります。
case CPRR8: u32Wrk1=getRegister((u32Code & 0xff00) >> 8); u32Wrk2=getRegister((u32Code & 0xff) & 0xff); setSRSig(u32Wrk1<u32Wrk2); setSRZero(u32Wrk1==u32Wrk2); u32Addr=u32Arg+4; break;
一方のレジスタは、32ビットの値そのまま(u32Wrk1)、もう一方は下位8ビット(u32Wrk2)の値を取り出し、u32Wrk1とu32Wrk2を比較していますね。メモリについても同様で、r1レジスタの値とr2レジスタの指すメモリの値を8ビットで比較するCPRM8は以下のようになります。
case CPRM8: u32Wrk1=getRegister((u32Code & 0xff00) >> 8); u32Wrk2=pmMemory->read8(getRegister(u32Code & 0xff)); setSRSig(u32Wrk1<u32Wrk2); setSRZero(u32Wrk1==u32Wrk2); u32Addr=u32Arg+4; break;
ジャンプコードは、前回指定の即値アドレスにジャンプするコードを実装しましたが、少し整理して以下のようにしましょう。
コード値 | 定数 | ニーモック | コードサイズ |
80 | JUMPI32 | jump Imm32 | 8 (00800000,Imm32) |
---|---|---|---|
81 | JUMPR32 | jump r1 | 4 (0080r100) |
82 | JUMPRR32 | jump r1,r2 | 4 (0080r1r2) |
83 | JUMPM32 | jump (r1) | 4 (0080r100) |
84 | JPZEI32 | jpze Imm32 | 8 (00800000,Imm32) |
85 | JPZER32 | jpze r1 | 4 (0080r100) |
86 | JPZERR32 | jpze r1,r2 | 4 (0080r1r2) |
87 | JPZEM32 | jpze (r1) | 4 (0080r100) |
ジャンプ先の指定は32ビットで行いますが、その指定を即値、レジスタ、レジスタ+レジスタ、メモリの内容、で行うわけですね。表のjumpが単純ジャンプで、jpzeがZeroフラグがセットされているときにジャンプする条件ジャンプです。
jpze系コードの実装はごく簡単で、Zeroフラグがセットされていれば指定アドレスに、セットされていなければコードの後のアドレスにPCをセットするだけです。例えば、JPZERR32は以下のようになります。
case JPZERR32: if (getSRZero()) { u32Wrk1=getRegister((u32Code & 0xff00) >> 8); u32Wrk2=getRegister(u32Code & 0xff); u32Addr=u32Wrk1+u32Wrk2; } else u32Addr=u32Arg+4; break;
今回は、乗算・比較・条件ジャンプコードを使用して、以下のような二重ループの中で8×4ピクセルの長方形を描くプログラムを組んでみました。
0000 ld32 r0,0x00ff0000 0008 ld8 r1,128 000c ld32 r2,129+512*56+56 0014 ld8 r3,0 0018 ld8 r4,0 001c ld32 r5,r2 0020 ld32 r6,r4 0024 mul8 r6,4 0028 add32 r5,r6 002c ld32 r6,r3 0030 mul32 r6,512 0038 add32 r5,r6 003c ld32 (r5),r0 0040 ld8 (r1),1 0044 cp8 r4,7 0048 jze 005c 0050 add8 r4,1 0054 jump 0x001c 005c cp8 r3,3 0060 jze 0074 0068 add8 r3,1 006c jump 0018
このアセンブリコードは、Cで書けば以下のような感じになります。
/* ピクセルとして書き込む値 */ r0=0x00ff0000; /* ビデオシステムI/Oアドレス */ r1=128 /* 長方形の先頭アドレス */ r2=129+512*56+56 for (r3=0;r3<=3;r3++) for (r4=0;r4<=7;r4++) { r5=r2+r4*4+r3*512; memory[r5]=r0; memory[r1]=1; }
ループ内では、長方形の各ピクセルのアドレスを算出し、そこにr0で指定された色の値を書き込んでから、ビデオシステムのI/Oアドレスに1を書き込んで表示する処理を行っています。アセンブリのアドレス計算は、一度レジスタに値を入れてから乗算したりしているのでかなり煩雑ですね。この辺りは、「変数による演算」を簡単に行える高級言語(Cは、高級アセンブラ(^^;と言われることも多いが)のありがたみを実感させられる部分でしょう。
ループの終了判定は、ループの末端でレジスタに1ずつ値を足していってそのレジスタの値を予め決めておいたループ回数とcpし、ゼロならjpzeでループを抜けることで行っています。
進行ボタンをクリックし続けると、画面上の表示領域の中央部に長方形が描かれていきますので、レジスタの値を確認しながらコードを実行していってみましょう。