見出し画像

Z80レジスタ交換と裏レジスタ

今回もZ80関連です。つまらないかもです(汗)
Z80のマシン語ではレジスタというものを使いますね。まあ、Z80に限った話ではないですが。。。
通常、使うレジスタは
A、B、C、D、E、H、Lの7種類の8ビットレジスタと
AF、BC、DE、HL、IX、IYの6種類の16ビットレジスタ
あわせて
条件分岐などでの参照しかできない8ビットレジスタのF
筆者はほとんど使わないSP(汗)
その存在を忘れがちなPC(滝汗)
です。
でも、実はZ80には俗称:裏レジスタ(たぶん正式名称は補助レジスター、Alternative Register)というものが存在します。
裏ってあやしげな言葉ですね・・
Aレジスタの裏はA'
Bレジスタの裏はB'
Cレジスタの裏はC'
Dレジスタの裏はD'
Hレジスタの裏はH'
Lレジスタの裏はL'
Fレジスタの裏はF'
という感じで存在します。
裏レジスタが存在するのは8ビットレジスタだけなので、IXやIYには存在しません。今回はその裏レジスターにも関連するレジスタ操作あれこれの説明です。

レジスタそれぞれの特徴

Aレジスタはアキュムレータと呼ばれます。基本的にアキュムレータ以外での演算はできません。
ADDもAレジスタにしか結果は入りませんし、SUBもです。
ORもANDもそうですね。
Z80ではHLレジスタもアキュムレータみたいに使えますね。ADDやSBCが使えます。IXやIYもADDが使えたりします。
それ以外は基本的に値をセットしたり値を参照したりするだけの用途です。
あ、INCやDECはAレジスタ以外も使えます。
ですが16ビットレジスタを使ったINCやDECはフラグレジスタが更新されません。これ、ときどき筆者は「あれ?DEC HLで0になったけどフラグレジスタが変化しないぞ??」って、はまります(汗)

レジスタ(表)の退避方法あれこれ

レジスタは数に限りがあるので値を退避させたり復帰させたりして効率よく使うしかありません。
そして、レジスタの退避方法にはいくつか種類があります。
下記例以外にもRAMアドレスにレジスタの値を退避させる方法もありますね。

; Aレジスタの値をBレジスタに退避して元に戻して使う
LD A, 100
LD B, A  ; BレジスタにAレジスタの値を退避
LD A, 0
LD A, B  ; 退避した値をAレジスタに戻す
 
; BCレジスタの値をDEレジスタに退避して元に戻して使う
LD B, $00
LD C, $80
LD D, B  ; BCレジスタの値をDEレジスタに退避
LD E, C
LD B, 0
LD C, 0
LD B, D  ; 退避した値をBCレジスタに戻す
LD C, E
 
; DEレジスタの値をPUSHして元に戻して使う
LD D, $00
LD E, $80
PUSH DE  ; DEレジスタの値をスタックに退避
LD D, 0
LD E, 0
POP DE   ; スタックの値をDEレジスタに戻す

裏レジスタの役割

裏レジスタは、コインの裏表と同じようなイメージです。ひっくり返すだけで今まで表で使っていたレジスタが裏にひっくり返り、裏だったレジスタが表に現れます。なにができるかっていうと、レジスタの値の退避ができるのです。ちなみに裏レジスタを直接プログラムで使うことはできません。裏レジスタの値を使いたい場合は必ず表にしてからでしか使えません。
PUSH、POPはスタックを必要としますが、表裏をひっくり返すぶんにはスタックポインタも必要としません。

レジスタの交換

交換命令を使ったレジスタ退避方法というのがあります。
こんな感じです。

; AFそれぞれのレジスタの表裏をひっくり返す
EX AF, AF'
 
; BC,DE,HLの全レジスタの表裏をひっくり返す
EXX

EXは交換命令と呼ばれます。交換命令は裏だけじゃなくって表レジスタ同士の交換もできます。

; 表DEと表HLの交換
EX DE, HL ; D <-> H, E <-> L

SPが指し示すアドレスの値とも交換できます。

; SPが指すアドレスの値とHLの交換
EX (SP), HL  ; (SP) <-> L, (SP+1) <-> H
 
; SPが指すアドレスの値とIXの交換
EX (SP), IX
 
; SPが指すアドレスの値とIYの交換
EX (SP), IY

交換命令の用途

レジスタの退避を交換命令で代替するとこんな感じになります。

; Aレジスタの値を退避して元に戻して使う
LD A, 100
EX AF, AF'   ; AFレジスタを裏表ひっくり返す
LD A, 0
EX AF, AF'   ; こうするとAレジスタの値は100になる
 
; DEレジスタの値とHLレジスタの値をひっくり返す
LD E, $34
LD D, $12
LD L, $00
LD H, $80
EX DE, HL    ; こうするとDEレジスタの値は$8000、HLレジスタの値は$1234になる

交換命令のなにが便利かっていうとですね。。。
・余計なレジスタを使わなくて済む。
・ステート数が短く済む場合がある。(速くなる場合がある)
・バイト数も少なくて済む場合がある。(プログラム領域の節約になる場合がある)

それなりにいいことだらけだったりします。
上記例を参考に交換命令を使わない場合と使う場合を比較してみましょう。

; LD命令で実現させる場合: 6バイト,26ステート
; この例では退避用にBレジスタを使うため、DJNZなどに影響あり
LD A, 100    ; 3E 00 (8)
LD B, A      ; 47    (5)
LD A, 0      ; 3E 00 (8)
LD B, A      ; 47    (5)

; PUSH, POPで実現させる場合: 6バイト,39ステート
; 余計なレジスタは使わないがスタックを必要とする。アホみたいに遅い。
LD A, 100    ; 3E 00 (8)
PUSH AF      ; F5    (12)
LD A, 0      ; 3E 00 (8)
POP AF       ; F1    (11)

; 交換命令の場合: 6バイト,26ステート
; 速度やバイト数はLDで退避させる方式と変わらないが余計なレジスタを使わない
LD A, 100    ; 3E 00 (8)
EX AF, AF'   ; 08    (5)
LD A, 0      ; 3E 00 (8)
EX AF, AF'   ; 08    (5)

; LD命令、PUSH/POPの組み合わせでDEレジスタの値とHLレジスタの値をひっくり返す
; 10バイト、68ステート ・・遅すぎる・・
LD DE, $1234 ; 11 34 12 (11)
LD HL, $8000 ; 21 00 80 (11)
PUSH HL      ; E5       (12)
PUSH DE      ; D5       (12)
POP HL       ; E1       (11)
POP DE       ; D1       (11)

; 交換命令の場合: DEレジスタの値とHLレジスタの値をひっくり返す
; 7バイト、27ステート。バイト数も少ない、速度も速い
LD DE, $1234 ; 11 34 12 (11)
LD HL, $8000 ; 21 00 80 (11)
EX DE, HL    ; EB       (5)

と、まあ、こんな感じです。
マシン語ではできるだけ余計なレジスタや余計なメモリ(=変数領域)を使わずプログラミングすることがベストです。BレジスタはDJNZなどで使うし、CレジスタはIN命令とOUT命令で使ったりします。そういう他のレジスタのお邪魔にならないようにプログラミングすると、効率的なプログラム(バイト数が少なくて、なおかつ速い)を実現させることができるようになるわけです。
また、裏レジスタ(AF’)はフラグレジスタをあとで参照する。というような場合に使えます。こんな感じ。

;------------------------------------
; スタックに退避させる方法の場合
; スタック操作は重い!!非推奨コード・・
;------------------------------------

; AにはGTSTCKの状態が入ってると仮定
; DにはGTTRIGの状態が入ってると仮定
PUSH AF        ; AFレジスタをスタックに退避
LD A, D 
CP $FF         ; スペースキーが押されていたらZフラグが成立する
JR Z, FIRE     ; スペースキーが押されていたらFIREを呼び出す
POP AF         ; スタックにPUSHしているのでとりあえず空POP(バギーコード!)
PROC_END:

FIRE:
POP AF         ; AFレジスタの値を復帰
CP 1
RET NZ         ; 上方向が押されていなければRET
CALL UP_FIRE   ; 上方向に弾を発射する
JR PROC_END

;------------------------------------
; 交換命令で裏に退避させる方法の場合
;------------------------------------

; AにはGTSTCKの状態が入ってると仮定
; DにはGTTRIGの状態が入ってると仮定
CP 1           ; 上方向が押されていたらZフラグが成立する
EX AF, AF'     ; フラグレジスタの状態を裏に退避させる
LD A, D
CP $FF         ; スペースキーが押されていたらZフラグが成立する
CALL Z, FIRE   ; スペースキーが押されていたらFIREを呼び出す
PROC_END:

FIRE:
EX AF, AF'     ; CP 1の結果のフラグレジスタを戻す
; このサンプルではこの時点でAとFが呼び出し時から変化してるので注意
RET NZ         ; 上方向が押されていなければRET
CALL UP_FIRE   ; 上方向に弾を発射する
RET            

フラグレジスタの状態はLDとかで他のレジスタやメモリ上に退避できないので便利です。スタックに退避させる場合、激オモになりますし・・。
交換命令の存在は昔から知ってはいましたが「どんなときに使うの?」という解説が少なくて、最初は使ってませんでした。
でも、いまではEX AF, AF'とEX DE, HLは筆者も結構使います。
(それ以外は使ったことはないですね・・)
デメリットとしては、「あれ?いま表だっけ?裏だっけ?」ってなることくらいです。遠く離れた場所で裏表をひっくり返すのではなく、割と近い場所で使うとそういう混乱は少なくなります。
最後に@TKZ80さんからの助言いただいたので、注意点だけ。

・裏表を知る手段はない(自己管理)
・BIOS に裏レジスタを使う(壊す)ものがあるので注意。
・割り込み処理等で裏レジスタを壊してはいけない


まあ、自己責任ってやつです!レジスタ壊したら壊れる。
あたりまえですね。
(マシン語はなんでもそうですが)

ということで今回は交換命令っていうのがあって覚えると便利だよ!というお話でした。
これからもみなさんのマシン語ライフが快適になりますように!

では、また!ノシ





セーラー服が似合うおじさんです。猫好き、酒好き、ガジェット好き、楽しいことならなんでも好き。そんな「好き」をつらつらと書き留めていきます。