コンパイラ系言語では for という便利なループ命令があります。アセンブラはほぼそんな便利な命令はないので、自分でループをカウントする必要があります。大変基本的な処理の部分ですが、今回はこのループ記述の種類について考察してみます。
※ 実行ステート数は AILZ80ASM で確認しています。


  • 8ビットループ
カウンタに 8ビットレジスタを使う場合のループの記述です。まず、Z80 唯一と言って良い、ループ専用命令 DJNZ を使ってみます。
                ; 8 ビットループ ①
TEST8BIT_1::
0610 7 ld b, 16
00 4 .loop nop
10FD 13 DJNZ .loop
B レジスタに入れた数だけ DJNZ でループします。ループ速度は13ステートとなかなか高速です。B レジスタが空いていれば、あるいは B レジスタを空ける事が出来るのであれば、迷わず使いたい基本中の基本となります。どうでも良いトリビアですが、DJNZ はデクリメント・ジャンプ・ノン・ゼロの略です
                ; 8 ビットループ ②
TEST8BIT_2::
1610 7 ld d, 16
00 4 .loop nop
15 4 dec d
C20700 10 jp nz, .loop
B レジスタが空かない場合は、他の 8ビットレジスタを使います。この場合はループ直前で、自分でデクリメント dec として、その直後に ZF=0 ならループするように記述します。実行速度は14ステートと DJNZ 専用命令と比べると 1 しか違いません。ですが、メモリウエイトが2回かかりますので、実際にはもう少しだけ差が出てくると思います。
※ メモリウエイトについては機種によって条件が異なります。
                ; 8 ビットループ ③
TEST8BIT_3::
3E10 7 ld a, 16
08 4 .loop ex af, af'
00 4 nop
08 4 ex af, af'
3D 4 dec a
C20E00 10 jp nz, .loop
どのレジスタにも空きがない場合は、Acc の裏レジスタを使います。表裏の交換はめっちゃ速いので、オーバーヘッドとしては最小限となります。実行速度は22ステートとなります。メモリウエイトはちょっと大きくなったかもしれません。
                ; 8 ビットループ ④
TEST8BIT_4::
FD2E10 10 ld iyl, 16
00 4 .loop nop
FD2D 10 dec iyl
C21800 10 jp nz, .loop
最後は禁断の未定義命令で、インデックスレジスタを 8 ビットとして使ってしまいます。一部の Z80 互換 CPU はこれで動かなくなります。ただ、実行速度は20ステートと、先の裏レジを使うよりは高速になります。しかも、2命令で済んでいますので、上記③の方法よりも高速に動作します。インデックスレジスタを使うと遅いという印象はありますが、このように実際のステートを数えると、意外な使い道があったりします。



  • 16ビットループ
カウンタに16ビットレジスタを使う場合のループの記述です。最初はオーソドックスな方法から。
                ; 16 ビットループ ①
TEST16BIT_1::
014006 10 ld bc, 1600
00 4 .loop nop
0B 6 dec bc
78 4 ld a, b
B1 4 or c
C22100 10 jp nz, .loop
16 ビットレジスタなら何でも良いのですが、ここでは BC を使ってみます。16 ビットのデクリメントでは、フラグが変化しないので、上位と下位を Acc で OR して、ZF=0 ならループという手法を採ります。実行速度は24ステートと、これが基準となります。
                ; 16 ビットループ ②
TEST16BIT_2::
010740 10 ld bc, (1600 % 256) * 256 + (1600 - 1) / 256 + 1
00 4 .loop nop
10FD 13 DJNZ .loop
0D 4 dec c
C22B00 10 jp nz, .loop
※2021/12/23 19:00 追記:256で割り切れる場合でも対応出来る計算式に変更しました。
16ビットを 8ビット毎に分解してフラグ判定する方法です。B レジスタには 256で割った余りの端数を入れます。C には 256で割った数 + 1 を入れます。最初の B ループで端数分のループを行った後は、C レジスタの値 - 1 回分の 256ループをします。通常は 13ステートで回りますが、凡そ 256回に 1度ずつ 27ステートとなります。1600回ループさせた場合の平均実行速度は13.06ステートと、普通に 16ビットレジスタでループさせるよりかなり高速となります。
                ; 16 ビットループ ③
TEST16BIT_3::
014006 10 ld bc, 1600
00 4 .loop nop
2B 6 dec hl
EDA1 16 cpi
EA3500 10 jp pe, .loop
こんな方法もあるという事でご紹介。CPI は HLの示すメモリの内容と Accを連続比較する命令ですが、このカウンタとして BCを使います。ZF や CF はメモリとの比較結果に使われますが、BC のカウンタ値の状態を P/V に反映するので、PE なせループとする事で 16ビットループカウンタとして使用する事が出来ます。ただこれ、32ステートとさほど速くないです。HLが壊れても良いとしても 26ステート。あまり速くないので、まあ参考程度に。
                ; 16 ビットループ ④
TEST16BIT_4::
014006 14 ld iy, 1600
00 4 .loop nop
2B 10 dec iy
78 8 ld a, iyh
B1 8 or iyl
C22100 10 jp nz, .loop
最後はインデックスレジスタをループカウンタに使う方法です。…36ステート(吐血