Z80アセンブラの小技

Aレジスタと論理演算

AND/OR/XORは、オペランドとAレジスタの論理演算を行う際にオペランドをAレジスタにする(Aレジスタ同士の論理演算とする)ことで、他の命令の代替にできます。全く同じではないので注意が必要ですが、代わりとしては十分で、容量も少ないためよく用いられています。

  • AND / OR

    ”AND A”もしくは”OR A”とすることで、”CP 0”(Aレジスタが0か)の代わりとして使用できます。同じ値でANDやORを実行しても値が変化しないのがポイントです。また、Cフラグのリセットとしても利用されます。ただし、Zフラグ以外は正しく変化しないので注意が必要です。

            OR A
            JP Z,NEXT ;Aが0ならジャンプ

  • XOR

    ”XOR A”とすることで、”LD A,0”(Aレジスタを0にする)の代わりとして使用できます。同じ値同士でXORをすると、常に0になるのを利用しています。ただし、各種フラグが変化する(特にZフラグが必ずリセットされる)ので、場合によっては”LD A,0”との使い分けが必要になります。

            XOR A ;Aレジスタの値が0になる

レジスタが0かを調べる

Z80ではレジスタの値が0かを調べる機会が多くあります。調べたいレジスタの値を変化させずに、値が0かどうかを調べる方法として以下のものがあります。

  • Aレジスタの場合

    ”AND A”もしくは”OR A”とすることで、Zフラグが変化します。

            OR A ;Aが0ならZフラグが立つ

  • Aレジスタ以外で、Aレジスタを壊してよい場合

    いったんAレジスタに移せば「Aレジスタの場合」と同様に”AND A”か”OR A”が使用できます。

            LD A,B ;Aレジスタに移す
            OR A ;Aが0(A=BなのでB=0)ならZフラグが立つ

  • Aレジスタ以外で、Aレジスタが壊せない場合

    そのレジスタに対して”INC”と”DEC”を1回ずつ行います。順番は関係ありません。

            INC B ;Bレジスタの値を+1
            DEC B ;Bレジスタの値を-1
            ;差し引きは0だが、”INC”もしくは”DEC”でZフラグが変化する

ペアレジスタが0かを調べる

回数の多いループを作成する時は、ペアレジスタをカウンタにします。その際に、ペアレジスタの内容が0かを調べる命令はありませんが、Aレジスタを使えば以下のように実現できます。なお、例として使用しているBCレジスタは、他のペアレジスタに置換えが可能です。

  • BCレジスタの場合

    ペアレジスタの2つでORを取ったとき、双方が0の時だけZフラグが立つことを利用します。

            LD A,B ;Aレジスタに移す
            OR C ;B=C=0の時のみZフラグが立つ

”ADD HL,A”をするルーチン

テーブル処理ではHLレジスタの値にAレジスタの値を足す処理がよく使われます。以下のサブルーチンを作成しておくことで、処理を若干簡略化できます。

  • 最小限のレジスタで行う

    HLレジスタの値にAレジスタの値を加算します。HLは他のペアレジスタに置換え可能です。

    ;[入力]:HL = 数値, A = 数値
    ;[出力]:HL = HL + A
    ;[破壊]:AF

    TABLE:
            ;PUSH AF
            ADD A,L
            LD L,A
            JR NC,TABLENC
            INC H
    TABLENC:
            ;POP AF
            RET

  • 他のペアレジスタを使う

    DEレジスタを使って、HLレジスタの値にAレジスタの値を加算します。DEはBCに置換え可能です。上記の例とは違い分岐が無いため、実行時間は常に一定です。

    ;[入力]:HL = 数値, A = 数値
    ;[出力]:HL = HL + A
    ;[破壊]:DE, F

    TABLE2:
            ;PUSH DE
            LD D,0
            LD E,A
            ADD HL,DE
            ;POP DE
            RET

HLから2バイトの値を減算

Z80には、1バイトの減算命令として”SUB”がありますが、2バイトの”SUB”はありません。代わりに、以下のようにすることで2バイトの減算を実現できます。

  • ”SUB HL,DE”を行う

    ペアレジスタ同士の減算として”SBC”命令が使用できますが、この命令は減算と同時にCフラグの値も減算してしまいます。そこで、まずCフラグをリセットしてから減算を行うようにします。

            OR A ;Cフラグのクリア
            SBC HL,DE ;HLからDEを減算

ペアレジスタのCPLとNEG

1バイトの符号の反転として”NEG”、ビットの反転として”CPL”がありますが、2バイトの命令はありません。以下のようなサブルーチンを作成することで、それぞれの2バイト命令が実現できます。

  • HLレジスタのCPLとNEG

    ペアレジスタのCPLは、双方のレジスタの内容をそれぞれ”CPL”命令で反転するだけです。NEGは、ペアレジスタから1を引いてから反転することで実現できます。

    ;[入力]:HL = 数値
    ;[出力]:HL = CPLまたはNEGされた数値
    ;[破壊]:AF

    NEGHL:  DEC HL
    CPLHL:  LD A,H
            CPL
            LD H,A
            LD A,L
            CPL
            LD L,A
            RET

Aレジスタの値をn倍する

Z80で乗算を行う場合、命令が無いために加算をループさせることになります。何倍にするかが変動するのであればその方法しかありませんが、2倍や4倍など、固定されていれば以下の方法を使うことで、若干のスピードアップを図れる場合があります。ただしオーバーフローには注意してください。

  • Aを2の累乗倍する

    ある数に同じものを足せば、元の数は2倍になります。これを繰り返すことで、簡単に2倍、4倍、8倍、16倍...が実現できます。

            ADD A,A ;この時点で2倍
            ADD A,A ;この時点で4倍
            ADD A,A ;この時点で8倍
            ADD A,A ;この時点で16倍

  • Aをn倍する

    上記の方法と、空いているレジスタを1つ使うことで、何倍にすることもできます。例として、3〜10倍の方法を記載しておきます。

            LD B,A ;3倍
            ADD A,A
            ADD A,B ;A = 2A + A = 3A

            ADD A,A ;4倍
            ADD A,A ;A = 2(2A) = 4A

            LD B,A ;5倍
            ADD A,A
            ADD A,A
            ADD A,B ;A = 2(2A) + A = 5A

            LD B,A ;6倍
            ADD A,A
            ADD A,B
            ADD A,A ;A = 2(2A + A) = 6A

            LD B,A ;7倍
            ADD A,A
            ADD A,B
            ADD A,A
            ADD A,B ;A = 2(2A + A) + A = 7A

            ADD A,A ;8倍
            ADD A,A
            ADD A,A ;A = 2(2(2A)) = 8A

            LD B,A ;9倍
            ADD A,A
            ADD A,A
            ADD A,A
            ADD A,B ;A = 2(2(2A)) + A = 9A

            LD B,A ;10倍
            ADD A,A
            ADD A,A
            ADD A,B
            ADD A,A ;A = 2(2(2A) + A) = 10A

HLレジスタの値をn倍する

上記の「Aレジスタの値をn倍する」と同じ考え方で、HLレジスタをn倍することも可能です。同様にオーバーフローに注意してください。

  • HLを2の累乗倍する

    ある数に同じものを足せば、元の数は2倍になります。これを繰り返すことで、簡単に2倍、4倍、8倍、16倍...が実現できます。

            ADD HL,HL ;この時点で2倍
            ADD HL,HL ;この時点で4倍
            ADD HL,HL ;この時点で8倍
            ADD HL,HL ;この時点で16倍

  • HLをn倍する

    「Aレジスタの値をn倍する」と計算の方法は同じです。AをHLに、BをDEに置き換えることでそのまま使用できます。ただし、”LD B,A”の部分は”LD D,H” ”LD E,L”のように書き換える必要があります。例として、HLを7倍にしてみます。

    ;Aレジスタの場合
    ;       LD B,A ;7倍
    ;       ADD A,A
    ;       ADD A,B
    ;       ADD A,A
    ;       ADD A,B ;A = 2(2A + A) + A = 7A

    ;HLレジスタの場合
            LD D,H ;7倍
            LD E,L
            ADD HL,HL
            ADD HL,DE
            ADD HL,HL
            ADD HL,DE ;HL = 2(2HL + HL) + HL = 7HL

メモリを同じ値で埋める

DS(Define Storage)で確保した領域の初期化など、あるアドレスからあるバイト数を同じ値(0など)で埋める場合、通常はループを作成して代入を繰り返します。しかし、以下の方法を使用することで、代入に必要な時間と使用容量を大きく減らすことができます。

  • LDIRを使う

    LDIRは(DE)=(HL)をした後に、DEとHLを1ずつ増やしていきます。繰り返すことで、最初に(HL)に入っていたデータでメモリが埋められていきます。

    ;dataから30バイトを0xCCで埋める
            LD HL,data ;data=データの開始アドレス
            LD DE,data+1
            LD BC,30-1 ;データのバイト数-1
            LD A,$CC ;埋めるデータ
            LD (HL),A
            LDIR ;データの連続転送

Aレジスタのビットを左右反転する

ゲームで左右が反転したデータが必要になる場合、再定義すると単純に容量が2倍必要になってしまいます。そこで、元のデータから反転したデータを作り出すことで容量の節約ができます。この例ではAレジスタの中身を左右を反転(8ビット)するだけですが、ループなどで応用すれば大きなデータも扱えます。

  • 左右反転ルーチン

    Aレジスタを右にローテートした時のキャリーを右から左に並べることで、ビットの左右反転を実現できます。

    ;[入力]:A = 数値
    ;[出力]:A = Aのビットを左右反転した結果
    ;[破壊]:AF, BC

            LD B,8 ;1バイトは8ビット
    LOOP:   RRCA ;Aを右にローテート
            RL C ;その時のキャリーをCへ
            DJNZ LOOP
            LD A,C

SEO [PR] 爆速!無料ブログ 無料ホームページ開設 無料ライブ放送