|
|
|
S59/01/22
|
阿閉雅宏の
|
Z-80まにあくす
|
Stage.01 |
|
解説:杉之原名人
|
|
|
|
|
|
うぁけましておめでとうございます。
皆さんのマイコンの調子はどうですか?それから浅田さん、結婚のご予定は?(注:'05現在結婚12年目・・・いや、どうでもいいんですけどね・・・) |
ところで、スタックポインタを使用したプログラムを突然にも紹介します。これは SHARP BASIC SP-5030で使われていたものをさらに改良したものです。HLレジスタの内容をテキスト画面上のアドレスDEから、左詰め十進数で表示するものです。 |
|
|
|
|
|
|
|
|
HL = 表示する数値(バイナリ)
DE = 表示するテキストV-RAMアドレス |
START: |
|
|
|
|
|
|
F5 |
PUSH |
AF |
|
|
全レジスタ退避 |
|
C5 |
PUSH |
BC |
|
|
D5 |
PUSH |
DE |
|
|
E5 |
PUSH |
HL |
|
5. |
06 00 |
LD |
B , 0 |
|
|
左詰め表示用フラグクリア |
|
D5 |
PUSH |
DE |
|
|
DEをスタックに置く・・・・ここに注目 |
|
11 10 27 |
LD |
DE , 10000 |
|
|
万の桁表示 |
|
CD xx xx |
CALL |
SUB |
|
|
11 8E 03 |
LD |
DE , 1000 |
|
|
千の桁表示 |
|
CD xx xx |
CALL |
SUB |
|
|
11 64 00 |
LD |
DE , 100 |
|
|
百の桁表示 |
|
CD xx xx |
CALL |
SUB |
|
|
11 0A 00 |
LD |
DE , 10 |
|
|
十の桁表示 |
|
CD xx xx |
CALL |
SUB |
|
|
D1 |
POP |
DE |
|
|
画面上のアドレスを取り出す |
|
7D |
LD |
A , L |
|
|
一の桁表示 |
17. |
C6 20 |
ADD |
A , '0' |
|
|
12 |
LD |
(DE) , A |
|
|
E1 |
POP |
HL |
|
|
全レジスタ復帰 |
|
D1 |
POP |
DE |
|
|
C1 |
POP |
BC |
|
|
F1 |
POP |
AF |
|
|
C9 |
RET |
|
|
|
リターン |
24. |
|
|
|
|
|
|
SUB: |
3E FF |
LD |
A , $FF |
|
|
Aレジスタには十進数一桁 |
LOOP: |
3C |
INC |
A |
|
|
その桁の十進値を求める[※1] |
27. |
A7 |
AND |
A |
|
|
ED 52 |
SBC |
HL , DE |
|
29. |
30 FA |
JR |
NC , LOOP |
|
|
19 |
ADD |
HL , DE |
|
|
HLを正の値に戻す |
31. |
B7 |
OR |
A |
|
|
その桁が0でなければSUB1へ |
|
20 03 |
JR |
NZ , SUB1 |
|
|
B0 |
OR |
B |
|
|
それまでに0でない数字があればリターン |
|
C8 |
RET |
Z |
|
35. |
AF |
XOR |
A |
|
|
Aを0に設定する |
SUB1: |
|
|
|
|
|
|
37. |
C6 20 |
ADD |
A , '0' |
|
|
数値をASCIIコードに変換 |
|
04 |
INC |
B |
|
|
フラグを立てる [※2] |
|
D1 |
POP |
DE |
|
|
戻り先アドレス保護のためDEへ退避 |
40. |
E3 |
EX |
(SP) , HL |
|
|
ここに注目のPUSH DEはここで使用 |
|
77 |
LD |
(HL) , A |
|
|
一文字表示 |
|
23 |
INC |
HL |
|
43. |
E3 |
EX |
(SP) , HL |
|
|
スタックを元に戻す |
|
D5 |
PUSH |
DE |
|
|
戻り先アドレスを元に戻す |
|
C9 |
RET |
|
|
|
リターン |
|
|
|
|
|
|
|
以上がそのプログラムです。少し長いものですが、スタックポインタ(SP)を利用するといろいろと面白いプログラムができるものです。 |
|
|
|
|
|
|
|
解説 (杉之原名人)
|
|
|
|
|
※1: Z-80には16bitの減算命令がSBCしかない、つまりキャリーを含んだ減算命令しかないため、使用前にAND Aとして、キャリーフラグを0にしている。
※2: Bレジスタはゼロブランキング用のフラグとして使用されている。
●27行目付近のループで各桁の十進値を計算している。もし除算命令があれば一発で求められるのだが、残念ながら乗除算命令の無いZ-80では減算を繰り返して求めている。たった6バイトのループだが、最悪10回も繰り返すとなるとバカにならない。Z-80のALU(演算回路)は4ビット長しかないので、このループ内の16ビット減算にはなんと19ステートもかかる。しかし、Z-80用のアルゴリズムとしてはよくできていると思われる。
このループのこれ以上の最適化は不可能か?と問われると実はそうではない。24行目以降を次のように変更する。
|
SUB: |
XOR |
A |
; A = 0 |
|
LOOP: |
ADD |
A , C |
; A = A + 1 Cレジスタにはあらかじめ"1"をロードしておく |
|
|
SBC |
HL , DE |
; HL = HL - DE - CY (CYは常に0) |
|
|
JR |
NC , LOOP |
; if(HL < 0) then LOOP |
|
|
ADD |
HL , DE |
; HL = HL + DE |
|
|
CP |
C |
; if(A <> 1) then SUB1 |
|
|
JR |
NZ , SUB1 |
; |
|
|
OR |
B |
; if(B = 0) then return |
|
|
RET |
Z |
|
|
|
LD |
A , C |
; A = 1 |
|
SUB1: |
|
|
|
|
|
ADD |
A , '0' - 1 |
; キャラクタコードから1を引いた値を加算する |
|
|
以下、同じ |
このように変更することでループ内の"AND A"が不要となり、1ループあたり4ステート分高速化する。また、25行目の直値ロード命令も3ステート高速化し、サイズも1バイト小さくなる。変更された26・31・35行目もステート数・サイズとも変わらないので、全体として確実に小さく、速くなっているというわけである。
念のため解説しておくと、キャリーフラグの変化しないINC命令に変えてADD命令を使うことにより、Aレジスタのインクリメントと同時にキャリーフラグを降ろしているというわけ。演算の結果Aレジスタの値が9を超えることがないので、常にキャリーフラグは0となる。これらはCレジスタが空いていることを利用している。
●40~43行目で、スタックに積んであった表示アドレスをHLレジスタに入れ、そこへASCIIコード化した数値を書き込み、表示位置を更新してからスタックに戻している。ここが本プログラムのアピールポイントである。組み込みを前提としたROM化を睨んだ手法であるといえる。16ビットCPUでは当たり前になっている「スタックフレーム」をZ-80でやってみました・・・というところか。ちなみにZ-80には16ビットレジスタ間の転送命令がないので、IXやIYを作業用記憶域として利用するメリットがない。
●Cレジスタが使用されていないので、5行目を LD BC , $0030 として、Cレジスタに$30(='0')を予め設定しておけば、17行目と37行目を ADD A , C とすることによってプログラムサイズも小さくなり、実行速度も上がる(先の最適化を行わない場合)。さらに言うなら、十の桁を求めるときはサブルーチン"SUB"を使うのではなく、専用のコードを使えばグンと速くなる。十の桁は8ビットレジスタでも計算できるので19ステートかかっていた減算が4ステートで終わってしまう。若干プログラムサイズは増すだろうが、利は大きい。
|
|
|
|
|