Chapter.1A 自機移動ルーチン製作に使用しているアセンブラ命令

■はじめに
ここでは、chapter1 の sca_player.asm を上から順によみ、見つかった未説明の命令と、それに関連する命令を順次説明していきます。
「Z80 の命令なんか、既に知っているから、そんな説明不要」という人は、このページは読み飛ばし、次に進んでかまいません。

■ ; (セミコロン)
セミコロンから右側は、すべて無視されます。
コメントを記述して、プログラムをわかりやすくする目的で利用します。

■ .area CODE (ABS)
アセンブラが解釈する「疑似命令」の一つです。
疑似命令とは、「対応する機械語が存在しない命令」で、アセンブラに対する指示を記述するものです。
.area xxx (yyy) は、「ここから下のエリアは、xxx という名前のブロックですよ」という指示です。
リンカは、同じ名前 xxx のエリアを連続アドレスに集めてリンクします。
たとえば、プログラムの部分を CODE, 書き込みも伴うデータメモリの部分を DATA と名付けたとして、
リンカの指定で CODE は ROM のアドレス、DATA は RAM のアドレスを指定するといったことができます。

まぁ、よく分からない場合は、ここでは気にせずに「必ず頭に記述するおまじない」くらいに思って貰ってかまいません (^_^;

■push XX
スタックメモリへ XX の値を書き込みます。
XX には、レジスタ af, bc, de, hl, ix, iy を指定できます。

図1に動作イメージを示します。


図1 push hl の動作

CPU内部に SP (スタックポインタ) というレジスタがあり、RAMのアドレスを示してます。
push XX すると、レジスタXX に格納されている内容を、[SP-2] と [SP-1] の2バイトのRAMに書き込み、SPレジスタの値自身も
SP←SP-2 として、ずらします。
こうすることで、push をたくさん実行した場合に、直前の push の値を上書きせずに「積み込むこと」ができます。
対になる命令として、pop が用意されているので、併せて覚えてください。

スタックポインタが指し示すメモリを、特に「スタックメモリ」と呼ぶことがあります。
プログラムやデータを置いているRAMと同じメモリですが、スタックメモリは、push/pop だけでなく call/ret の戻りアドレスの
保持、割り込み処理の戻りアドレスの保持にも利用されます。

■pop XX
スタックメモリから XX へ値を読み出します。
XX には、レジスタ af, bc, de, hl, ix, iy を指定できます。

図2に動作イメージを示します。


図2 pop hl の動作

[SP+0] と [SP+1] の2バイトのRAMから値を読み出し、それぞれ XX の下位8bit, 上位8bit に格納します。

push したものを pop で読み出すというのが普通の使い方です。
push したレジスタと、pop するレジスタが同じである必要はないので、

push hl
pop bc

こんな風に実行すると、bc に hl の内容をコピーすることが出来ます。

■ld XX,YY
ld は、load の略です。その名の通り、XX に YY の内容を書き込みます。

XX には、a,b,c,d,e,h,l,bc,de,hl,ix,iy,(hl),n(ix),n(iy),sp,r,i が指定できます。
YY には、a,b,c,d,e,h,l,bc,de,hl,ix,iy,(hl),n(ix),n(iy),#n,r,i が指定できます。
(??) と、括弧で囲まれたモノは、?? の値が示すアドレスのメモリを示しています。
n(ix), n(iy) は、(ix+n), (iy+n) と読み替えてください。
n は定数です。
※ザイログ社のニーモニックでは、(ix+n) のような記述ですが、AS-Z80 では、何故か n(ix) というわかりにくい表記に変わってます。
ただし、組み合わせにはいくらかの制限があります。 その辺は、きっとググれば出てくるでしょうから、ここでは記述しません (^_^;

■inc XX
inc は、increment の略です。XX に 1を加算します。
加算命令は別途用意されていますが、+1という演算は特によく使う(ループカウンタなど)ので、専用命令が用意されています。

XX には、a,b,c,d,e,b,c,bc,de,hl,ix,iy を指定できます。

■ret
ret は、return の略です。サブルーチンから抜けて、その呼び出し元へ戻ります。

図3に動作イメージを示します。


図3 ret の動作

見ての通り、pop と同じイメージです。スタックメモリから、戻るアドレスを読み出して、PC(プログラムカウンタ、CPU実行している命令の場所)
へ読み出します。pop PC のようなイメージの動作ですね。
pop に対する push のように、ret に対しても call という命令があります。対で覚える方が良いです。

■call AAAA
call は、subroutine call の call です。

AAAA には、呼び出したいサブルーチンのアドレスを記述します。
call AAAA すると、PC の値を、[SP-2] と [SP-1] の2バイトのRAMに書き込み、SPレジスタの値自身も
SP←SP-2 として、ずらします。そして、PC ← AAAA の代入を実施します。

図4に動作イメージを示します。


図4 call 0x0059 の動作

図4 は、仮に call 0x0059 というプログラムが、0xC000〜 に書き込まれていて、まさにそれを実行しようとしている瞬間だと思ってください。
実行するために、CPU の PCレジスタを 0xC000 にします。
すると、CPU は、0xC000 番地の内容 0xCD を読み取ります。
0xCD は、「call AAAA」を示す機械語なので、CPU は、「call AAAA 命令だな!」と解釈します。
続く2byte が、AAAA (つまり飛び先アドレス)なので、CPU は 2byte 読み取ります。
読み取るために、PCレジスタは順次インクリメントしていきます。
call 0x0059 を全て読み終えた時点で、PC は 0xC003 を示しています。
全て読み終えてから、CPU は命令を実行に移します。
push PC のような動作をして、まず「サブルーチンの処理が終わったら ret で戻るためのアドレス」をスタックメモリに積み込みます。
スタックメモリに積み込んでおくことで、call 0x0059 の次の命令へ復帰することが出来るわけです。
スタックメモリへ積み込むのと同時に、PC レジスタへ、飛び先アドレス 0x0059 を代入します。

と、このような感じで、サブルーチン呼び出しが実行されるわけです。

■xor XX
a ← a xor XX を実行する命令です。ビット毎の排他的論理和です。
XX には、a,b,c,d,e,h,l,(hl),n(ix),n(iy) などが指定できます。

図5に動作イメージを示します。


図5 xor b の動作

aレジスタの内容と、XXの内容で、同一のビット番号のビット同士で xor (排他的論理和) を求め、そのビット番号の出力とします。
それによって求められた 8bit の値を aレジスタに代入する命令です。

xor a は、a ← a xor a であり、a ← 0 と同じ効果を持っています。
なぜならば、0 xor 0 及び 1 xor 1 は、どちらも 0 であるからです。
ld a, #0 と異なる部分は、「演算をしていること」です。

Z80 は、演算を実施すると、演算の結果に応じてフラグレジスタの内容を変化させます。
フラグレジスタの中には、S,Z,H,P/V,N,C の各フラグが存在しています。
条件分岐(BASICでいうところの IF文など)は、このフラグを見ることで実施することになります。

xor XX 命令では、S,Zフラグは演算結果によって変化。H,N,Cフラグは無条件に 0 に。P/Vは演算結果のパリティービットが現れます。

フラグの意味や、分岐の方法については、別の命令のところで説明します。

■or XX
a ← a or XX を実行する命令です。ビット毎の論理和です。
XX には、a,b,c,d,e,h,l,(hl),n(ix),n(iy) などが指定できます。

図6に動作イメージを示します。


図6 or b の動作

aレジスタの内容と、XXの内容で、同一のビット番号のビット同士で or (論理和) を求め、そのビット番号の出力とします。
それによって求められた 8bit の値を aレジスタに代入する命令です。

or a は、a ← a or a であり、a レジスタを変化させません。
しかしフラグは変化するので、a レジスタがゼロであるか確認する場合や、キャリーフラグを 0 にしたい場合などに好んで使われます。

■and XX
or, xor と似ているので、ついでに and も説明しておきます。
a ← a and XX を実行する命令です。ビット毎の論理積です。
XX には、a,b,c,d,e,h,l,(hl),n(ix),n(iy) などが指定できます。

図7に動作イメージを示します。


図7 and b の動作

aレジスタの内容と、XXの内容で、同一のビット番号のビット同士で and (論理積) を求め、そのビット番号の出力とします。
それによって求められた 8bit の値を aレジスタに代入する命令です。

and a は、a ← a and a であり、a レジスタを変化させません。
しかしフラグは変化するので、a レジスタがゼロであるか確認する場合や、キャリーフラグを 0 にしたい場合などに好んで使われます。

■フラグについて
Z80 では、演算命令を実行すると、その結果に応じてフラグレジスタの内容が変化します。
フラグレジスタ(F) は、8bit あり、ビット毎に意味が異なってます。それぞれのビットには名前が付けられています。

表1にフラグレジスタの内容を示します。


表1 フラグレジスタの内容

だいたい表1の意味で示すように動作しますが、命令によっては値が一意でないビット・変化しないビットもあります。
たとえば、inc hl/dec hl などの 16bit inc/dec 命令は、まったくフラグを変化させませんので、注意が必要です。

フラグは、条件分岐命令で利用します。
たとえば、jp z, AAAA の場合、Zフラグが 1 の場合に AAAA へジャンプしますが、Zフラグが 0 の場合は何もしません。
これは、「直前の演算の結果がゼロならば、AAAA へジャンプする条件分岐」です。
call, ret, jr なども同様にフラグによる判断を付加できます。

HフラグとNフラグについては、DAAという命令でのみ利用するもので、今は覚えなくて結構です。

■add XX
8bit加算命令です。a ← a + XX を実行します。
XX には、a,b,c,d,e,h,l,(hl),n(ix),n(iy) などが指定できます。

桁上がりが発生した場合は、cフラグが 1 になります。
演算結果がゼロの場合は、zフラグが 1 になります。
P/Vフラグはオーバーフローフラグとして機能します。

符号付き8bit (2の補数)であっても、符号無しであっても、演算は同じなので共通して使えます。
ただし、符号無しのオーバーフローは、cフラグで判断。
符号有りのオーバーフローは、P/Vフラグで判断することになります。

■adc XX
add XX に似た命令として、adc XX があります。
a ← a + XX + cフラグ を実行します。
XX には、a,b,c,d,e,h,l,(hl),n(ix),n(iy) などが指定できます。

8bit ではおさまらない加算を実現したいことがあります。
16bit までなら別の別の命令で実現することも出来ますが、24bit とか、32bit など、さらに桁が多い場合は、
複数の加算を組み合わせなければなりません。
そのとき、bit7 から bit8 への桁上がりを示すキャリーフラグをいっしょに加算することによって、上の桁の処理
を実現できるわけです。
このあたりのキャリーフラグの使い方については、後々スコア計算で必要になりますので、その部分のコーディング時に
改めて詳しく説明したいと思います。

■cp XX
8bit比較命令です。a - XX を実行します。演算結果はどこにも代入しません。捨ててしまいます。
演算の結果によるフラグ変化が目的の命令です。

桁借りが発生した場合は、cフラグが 1 になります。
演算結果がゼロの場合は、zフラグが 1 になります。
P/Vフラグはオーバーフローフラグとして機能します。

なのですが、もう少し演算の意味を分かりやすく解釈すると、下記のようになります。
a < XX の場合 c フラグが 1 になります。
a = XX の場合 z フラグが 1 になります。

このくらいを覚えておけばいいです。

■jr AAAA
相対ジャンプ命令です。
AAAA へジャンプするのですが、AAAA は、jr AAAA が記述してある場所から -128byte〜127byte の範囲に無ければなりません。

アセンブルの結果、機械語に変換されると、AAAA のアドレス値が直接指定されるわけではなく、現在位置からの差分値で指定されます。
この差分値として 8bit (2の補数) しか用意されてないので、-128〜127 の範囲しか指定できません。

差分でジャンプするので「相対ジャンプ命令」という名前になってます。
ただ、その差分は、間に命令を挿入したり、別の命令に書き換えるだけですぐに変化してしまいます。
これをプログラマがいちいちメンテしていると大変なので、バグの原因になりやすい。
なので、アセンブラが自動的に差分に変換してくれます。
差分値が表現不可能な範囲に行くとアセンブル時にエラーになるので、それだけを意識して近い位置へのジャンプに用いるように
してください。

アドレス値を直接指定できる絶対ジャンプ命令も存在しますが、本命令は絶対ジャンプ命令よりも 1byte 少ない命令語長で表現
できるので、プログラムサイズを小さくしたい場合にも効果があります。

分岐命令なので、フラグ判定による条件分岐もできます。

jr z, AAAA → zフラグが 1 の場合 AAAA にジャンプする
jr nz, AAAA → zフラグが 0 の場合 AAAA にジャンプする
jr c, AAAA → cフラグが 1 の場合 AAAA にジャンプする
jr nc, AAAA → cフラグが 0 の場合 AAAA にジャンプする

よく使う命令です。

■jp AAAA
絶対ジャンプ命令です。
AAAA へジャンプするのですが、jr と異なり、アドレス値 AAAA がそのまま機械語コードに置き換わります。
たとえば、jp 0x1234 なら、0xC3 0x34 0x12 という機械語コードになります。
Z80 はリトルエンディアンなので、0x1234 が 0x34 0x12 という「下位が若いアドレス」になる順番で記述します。

jr のような範囲制限は無いので、遠くへジャンプする場合に利用します。
もちろん、jr と同様に条件分岐も使えます。

■add hl, XX
16bit加算命令です。
hl ← hl + XX を実行します。
XX には、bc,de,hl,sp を指定できます。

とくに hl ← hl + hl は、hl ← hl * 2 や、hl の左 1bitシフト と同じ結果になるので、それらの代わりに用いることもあります。

類似する命令で、add ix, XX や add iy, XX もあります。
基本的に、ix や iy は、hl と同じような演算ができますが、上位(h)と下位(l)に分離することはできません。
また、add ix,hl や add ix,iy のようなものも存在しません。
これは、Z80 が内部的に hl/ix/iy を切り替えて、同じ演算回路に投入してるからです。

■.dw XXXX
16bit の値をメモリ配置するための疑似命令です。
XXXX は、必ず定数でなければならないので、.dw #0 のように、必ず # が付く記述になります。

■.db XX
8bit の値をメモリ配置するための疑似命令です。
XX は、必ず定数でなければならないので、.db #0 のように、必ず # が付く記述になります。

■.globl XXXX
別のソース内に記述してあるラベル名で、player_init:: のように :: が付いているラベル名を、指定できるようにする疑似命令です。
つまり、「XXXX と言う名前は、別のソースで定義済みなので、エラーじゃないですよ」とアセンブラに教えてあげる命令です。
アセンブラは、アセンブル結果に名前をそのまま出力し、リンカが具体的なアドレス値に置き換えてくれます。

■.org XXXX
アドレス指定のための疑似命令です。

org #0xC0000
ld a,#0

たとえば、上記のような記述にすると、ld a, #0 の命令は 0xC000番地から置かれることを想定してアセンブルされます。


以上が chapter1 で利用する機械語命令/疑似命令になります。
最初なので、必須となる基本的な命令がほぼ登場しており、少し多めになってます。
Z80 がサポートする命令はたくさんありますが、実際にその中で使うのは 10種類前後です。
なので今後の chapter で新たに覚えなければならない命令は、断然少なくなりますので、ご安心ください。

[▲トップページへ]