[PR]人気の着メロ着うたフルも無料:掲示板で話題の曲を今すぐダウンロード!

修練場

- μPD780のちょっとした使い方のコツ(って程のものでもない) -



μPD780余多話に戻る


JP (BC)を実現する
 これは別にDEレジスタでも同じなのだが、その場合は必殺技 EX DE,HL があるので、ここまで遅くはならない。

        PUSH  BC
        RET              ; 11+10clk
 JP (HL) の4clk や EX DE,HL | JP (HL) の8clkに比べるとがくっと遅くなるが、他のレジスタを破壊してはいけない場合にはこれしかない。HLレジスタを破壊してもよいなら、素直にBCレジスタをHLレジスタに転送してからJP (HL) すれば12clkで済む。
 しかし、この JP (HL) という表記は頂けないよな。この命令の挙動は LD PC,HL なんだし、インテルニモニックでもズバリ PCHL なんだから、素直に JP HL という表記にしてくれないと、JP (IX+n) なんてのが出来てしまいそうで嫌だ。
 四半世紀も前に定義されたニモニックに文句を言ってもしかたないが。
 これはおまけの CALL (HL)。
        CALL  _CLHL
        [処理]
;
_CLHL:  JP    (HL)       ; 17+4clk
 普通のCALL命令に1クッション加わるだけ。上のJP (xx)と組み合わせて、CALL (xx)をも実現できることは言うまでもない。

LD HL,PCを実現する
 リロケータブル(ポジションインディペンデント)なオブジェクトで、自身のPCを知るにはちょっと反則気味だがこんな方法しか思いつかない。

        ORG   0038H
        POP   HL
        JP    (HL)       ; HL=next PC
;
        ORG   [任意]
        [処理]
        RST   38H        ; 11+14clk
        [処理]           ; HL=PC
 CALL命令でも同様の事はできるが、当然next PCを取得するモジュールは自身に組み込めない。組み込んでしまうと、CALL先アドレスが確定した時点でリロケータブルではなくなってしまうので、意味がない。
 よくよく考えれば、Z80で完全リロケータブルのコードを書くこと自体が非常に難しい。

16ビット相対ジャンプを実現する
 反則第2弾。

        ORG   0030H
        POP   HL
        LD    E,(HL)
        INC   HL
        LD    D,(HL)
        INC   HL
;       PUSH  HL
        ADD   HL,DE
        JP    (HL)       ; HL=next PC
;
        ORG   [任意]
        [処理]
        RST   30H        ; 11+51clk
        DW    [飛び先]-xxx
xxx:
 いわゆる8086のnearジャンプと同じである。実現シリーズの中で一番遅い上に、一番使い道がないであろう事は想像に難くない。
 コメントアウトしてあるPUSH HLを復帰させれば、16ビット相対コールになる。

HL > DE ならcf=1 とする
 16ビット減算で、毎度cf(キャリーフラグ)のリセットが面倒だとお嘆きの貴兄に。こうすれば、未満とか以超の判定も一度の分岐で行えるようになる。

        SCF
        SBC   HL,DE
        CCF
 ADD HL,xxが1バイトなのに、SBC HL,xxが(SUB HL,xxとして使う訳だが)実質3バイトになってしまうあたりがZ80追加命令の悲しさ。
 もっとも、cfをクリアしてから実行するSBC HL,xxは、減算値nが判っているならば即値定義で
        LD    DE,-n
        ADD   HL,DE
とすればSUB HL,nが実現する。つまりインデックスレジスタでも減算が可能になる。これらの場合、cfを始めとする全てのフラグが、減算結果としてはでたらめに変化するので注意。

HL と DE を比較する
 上の例はHLレジスタに減算結果を求めるが、HLレジスタとDEレジスタの比較結果をフラグにのみ反映させたい場合には、

        PUSH  HL
        OR    A
        SBC   HL,DE
        POP   HL         ; 40clk, 5bytes
などとしなければならず、冗長な気がしなくもない。そこで、
        LD    A,L
        SUB   E
        LD    A,H
        SBC   A,D        ; 16clk, 4bytes
としてみる。さすが8ビットプロセッサ、Aレジスタを破壊するが上の倍以上速くなる。おまけに被減算ペアレジスタを選ばない。但し、比較結果として正しく変更されるのはcfのみだ。そこで、
        LD    A,H
        SUB   D
        JR    NZ,_S1
        LD    A,L
        SUB   E
_S1:                     ; 20/23clk 6bytes
これで_S1:の時点でzf(ゼロフラグ)とcfが正しく変更される。上位8ビットと下位8ビットの評価順が逆になっている点に注意。
 Aレジスタを破壊できない場合には、
        OR    A
        SBC   HL,DE
        ADD   HL,DE      ; 30clk 4bytes
cfのみADD HL,xx命令の影響を受けるが、必ず直前のSBC HL,xx命令と同じ変化をするのでめでたくsf(サインフラグ),zf,cfが変更される。最近の(16ビットALUを持つ)高速互換プロセッサの場合はこれがお勧め。
 速いとはいっても気持ち程度のものなので、サブルーチンコールなどにせす、直コーディング(あるいはマクロ)で使う事。

ADD HL,Aを実現する
 Z280では実装されている命令だが、他ではこんな感じ。

        ADD   A,L
        LD    L,A
        ADC   A,H
        SUB   L
        LD    H,A        ; 20clk
 BC、DEレジスタでも同様。インデックスレジスタでは、普通に16ビットADD命令を使う方が速い。
 更に上位プロセッサでの使用を考えるならば
        ADD   A,L
        LD    L,A
        JR    NC,_Z1
        INC   H          ; 20/19clk
_Z1:
とすればバイト数は変わらないが、命令数が少ないので気持ち早くなる。分岐が嫌いな人は素直に上を使う事。

EXTS Aを実現する
 Z280、Z380では実装されている命令だが、他ではこんな感じ。

        LD    L,A
        RLCA
        SBC   A,A        ; sign expand
        LD    H,A
 要は拡張元ビットをcfに代入するだけ。

パック値の2分割
 ひとつのレジスタに入っている値をa+bビットのフィールドに分割する場合には、

        LD    L,A
        AND   0F0H       ; mask value#1
        LD    H,A        ; upper 4bits
        LD    A,L
        AND   0FH        ; mask value#2(complement value#1)
        LD    L,A        ; lower 4bits
などとするが、途中をちょっとアレして
        LD    L,A
        AND   0F0H
        LD    H,A        ; upper 4bits
        XOR   L
        LD    L,A        ; lower 4bits
 とすれば、2バイトの短縮になる。マスク値#2の代わりに摘出値#1のNOT値を使うというだけ。3分割以上やビットフィールドに隙間がある(2つのAND値の和がFFhでない)場合には使えない。

NEG HLを実現する
 Z280、Z380では実装されている命令だが、他ではこんな感じ。

        LD    A,L
        CPL
        LD    L,A
        LD    A,H
        CPL
        LD    H,A
        INC   HL         ; 30clk
 最後のINC HLを取れば、CPL HL(NOT HL)になる。
 NEGateは0からの減算なので、以下のような方法もある。

 1) 素直に減算する。

        EX    DE,HL
        XOR   A          ; cf=0
        LD    H,A
        LD    L,A
        SBC   HL,DE      ; 31clk
 cf=0が確定しているなら、中3行はLD HL,0とした方が何かとよい。

 2) 8ビット演算に分解する。

        XOR   A
        SUB   L
        LD    L,A
        SBC   A,H
        SUB   L
        LD    H,A        ; 24clk
 Aレジスタは破壊され見通しも悪くなるが、こちらはレジスタを選ばない。
 どちらも6バイト。

ペアレジスタのカウンタ値を8ビットレジスタに分割する
 具体的には、下位バイトが0でなければ上位バイトを+1する。ループ構造を後述のように変形する為の布石。

        DEC   HL
        INC   H
        INC   L
 Z80はペアレジスタINC/DECではフラグが一切変化しないので、カウンタとして使用する場合には、毎度
xxx:
        [処理]
        DEC   HL
        LD    A,H        ; HL=0?
        OR    L
        JP    NZ,xxx     ; 24clk
 てな処理を行わないといけないが、上の処理でこれを8ビットカウンタの二重ループに変形すると、ループ256回に付き(24-14)*256-16=2544clk速くなる。
xxx:
       [処理]
        DEC   L
        JP    NZ,xxx     ; 14clk
        DEC   H
        JR    NZ,xxx     ; 30clk
 この変形により、カウンタを任意の2つの8ビットレジスタに振り分ける事もできる。例えば下位のカウンタをBレジスタに代入してDJNZで回せば、ループ256回に付き更に261clk速くなる。ついでに上位カウンタにインデックスレジスタ(の8ビット分)を使っても、同4clkしか遅くならない。Aレジスタも(カウンタとして使用しない限り)破壊しないので、レジスタのやりくりが厳しい時にも便利。

Aレジスタの中央付近ビットを手早くcfに押し出す
 アキュムレータのビット4もしくはビット3をcfにコピーするには、通常ローテートかシフトを使って該当ビットを押し出す。

        LD    B,8
xxx:    IN    A,(device)
        RLCA
        RLCA
        RLCA
        RLCA             ; cf=device:bit4
        RL    C
        DJNZ  xxx
 よほどの事がない限りこれで充分なのだが、最短命令とはいえ同じものが4つも並ぶのは我慢ならーんという場合は、ある程度条件が揃えばこのように書き換える事ができる。
        LD    B,8
xxx:    IN    A,(device)
        OR    11101111b  ; bit4 get
        ADD   A,B        ; B=内容が1〜16であることが判っているレジスタ
        RL    C
        DJNZ  xxx
 ソース2行、1バイト5clkの短縮だが、OR値を8ビットレジスタに代入しておけば、さらに1バイト3clk短縮できる。ビット4もしくはビット3限定の手法で、ホントに気持ちの問題。ビット3の抽出時は、加算レジスタの許容範囲が1〜8になる。
 ビットを反転したい場合にはこうなる。
        LD    B,8
xxx:    IN    A,(device)
        AND   00010000b  ; bit4 get
        CP    B          ; B=内容が1〜16であることが判っているレジスタ
        RL    C
        DJNZ  xxx
 ORをANDに、ADDをSUB(CP)にと逆にしただけ。

1バイトを16進アスキーコード2文字に変換する
 アキュムレータ下位4ビット 0〜Fh を、アスキーコードキャラクタ '0'〜'9'、'A'〜'F'に変換する。下記の例では (DE) を変換して (HL),(HL+1) の2バイトに格納する。

        LD    A,(DE)
        RRCA
        RRCA
        RRCA
        RRCA
        CALL  _BTOH
        LD    A,(DE)
        INC   DE
_BTOH:  OR    0F0H       ; low nibble convert
        DAA
        CP    5FH
        SBC   A,1FH      ;
        LD    (HL),A
        INC   HL
        RET
 このルーチンの目玉は _BTOH: からの 7バイト。Z80がパズルとはよく言ったものだが、これはZ80が分岐を苦手にしている為の副産物で、すき好んでこういうコードを書いている訳ではない(と思う)。
 変換元の内容を破壊しても構わなければ、HLレジスタとDEレジスタを入れ替えて少々短く書ける(速くはならない)。
        RLD
        CALL  _BTOH
        RLD
        INC   HL
_BTOH:  OR    0F0H       ; low nibble convert
        DAA
        CP    5FH
        SBC   A,1FH      ;
        LD    (DE),A
        INC   DE
        RET

シフトJISコードをJISコードに変換する
 範囲ノーチェックなので注意。

; IN    HL =  shift JIS code
; OUT   HL =  JIS code
;
        LD    A,H
        AND   0BFH
        ADD   A,8FH
        ADC   A,A
        LD    H,A
        LD    A,L
        OR    A
        JP    P,_USKP
        CP    9FH
        JR    C,_USKP
        SUB   5FH        ; SUB  7Eh
        INC   H
_USKP:  SBC   A,1FH
        LD    L,A

 これもある意味パズル然としていなくもないが、変換式を比較的素直にコード化してある。後半の条件分岐命令連続部分がちょっと美しくないが、これ以上変形させるとかえって遅くなってしまいそうだ。

8×8ビットの符号無し乗算
 アキュムレータと Bまたは Dレジスタ間で乗算を行い、結果をHLレジスタに格納する。最初のHLクリアを省けば、積の加算(HL+=A*r)になる。下記の例では Bレジスタを対象としている。

; HL=AxB
        LD    HL,0
        LD    C,L        ; C=0
        JR    _LMS1
_LML1:  ADD   HL,BC
_LMS1:  SRL   B          ; SRA B なら符号付き
        RR    C
        ADD   A,A
        JR    C,_LML1
        JR    NZ,_LMS1
 ループチェックに乗数の一方のビットマップをそのまま使っている為、破壊レジスタが少ないのが特徴。所要クロックには60〜370clkと幅があり、Aレジスタ(被乗算値)のLSBから0が連続している程速くなる。
 乗数の一方が符号付きの場合、それをBレジスタに代入し SRL B を SRA B に変更すれば符号付き乗算になる。
 BCレジスタを使用する場合のみ、Aレジスタのゼロチェックをコード、速度共にロス無しで行えるように変形できるのだが、お判りだろうか?

16×16ビット符号無し乗算(積32ビット)(Z80)

; IN    HL =  mult value1
;       DE =  mult value2
; OUT   DE:HL answer
;
        LD   A,H
        LD   C,L
        LD   HL,0
        LD   B,16
_LM32:  RRA
        RR   C
        JR   NC,_LS32
        ADD  HL,DE
_LS32:  SRL  H
        RR   L
        DJNZ _LM32
        RRA
        RR   C
        LD   E,C
        LD   D,A
        EX   DE,HL       ; 25bytes
 2進数の筆算タイプという奴。
 ループ部だけで843〜939clk掛かる為、1秒間に4200回余り(4MHz時)の演算性能。

16×16ビット符号無し乗算(積32ビット)(Z180)
 MLT命令が羨ましかったので試しに書いてみた。
 まさかこのコードをそのまま使う人がいるとも思わないが、このルーチンは検証未済なので(だったら載せるなよ)念のため。

; IN    HL =  mult value1
;       DE =  mult value2
; OUT   DE:HL answer
;
        XOR   A
        LD    B,H
        LD    C,D
        PUSH  BC
        LD    D,B
        LD    H,C
        LD    B,E
        LD    C,L
        MLT   BC         ; L x E
        MLT   HL         ; L x D
        MLT   DE         ; H x E
        ADD   HL,DE
        LD    E,C
        LD    C,B
        LD    B,A
        ADC   A,B        ; get cf
        ADD   HL,BC
        ADC   A,B        ; get cf
        LD    D,L
        POP   BC
        MLT   BC         ; H x D
        LD    L,H
        LD    H,A
        ADD   HL,BC
        EX    DE,HL      ; 29bytes
 MLT命令は、入力値を全部破壊してしまうので、値のリロードにPUSH/POPを使っている。それでも170clk程度しか掛からず、同一クロック動作Z80比4〜5倍の速度だ。MLT命令様々である。

16×16ビット符号無し乗算(積32ビット)(i8080/i8085/Z80)
 番外。外付けハードウェアを利用する場合。このルーチンも検証未済。

; IN    HL =  mult value1
;       DE =  mult value2
; OUT   DE:HL answer
;
DATA0   EQU   0
DATA1   EQU   DATA0+1

        LD    A,E
        OUT   (DATA0),A
        LD    A,L
        OUT   (DATA1),A  ; L x E
        IN    A,(DATA0)
        LD    C,A
        IN    A,(DATA1)
        LD    L,A
        LD    A,D
        OUT   (DATA0),A  ; L x D
        IN    A,(DATA0)
        ADD   A,L
        LD    L,A
        IN    A,(DATA1)
        ADC   A,0
        LD    B,A
        LD    A,E
        OUT   (DATA0),A
        LD    A,H
        OUT   (DATA1),A  ; H x E
        LD    E,C
        IN    A,(DATA0)
        LD    C,A
        IN    A,(DATA1)
        LD    H,A
        ADD   HL,BC
        LD    A,D
        OUT   (DATA0),A  ; H x D
        LD    D,L
        IN    A,(DATA0)
        LD    C,A
        IN    A,(DATA1)
        LD    B,A
        LD    L,H
        RLA              ; get cf
        AND   1
        LD    H,A
        ADD   HL,BC
        EX    DE,HL      ; 55bytes
 それぞれ8ビットの乗数と被乗数をOUTすると、16ビットの積をINできると想定した例。約270clkで積を獲得できる。しかし見通し悪いねこりゃ。
 8080の時代には、小規模な補助演算回路をI/O空間に接続して運用していた実例は多いが、この例だと実際には4ビット乗算器と4ビット加算器を組み合わせた回路を外付けしていたはずである。さすがに128KBものメモリをI/O空間に配置する使い方はしなかっただろう。
 速度を追求しないなら、2つ上のコードの相対ジャンプを絶対ジャンプにして、ローテート・シフトを総てAレジスタ経由に書き換ればいいだけだが…、あ、レジスタが足りないわ。

16÷16ビット符号無し除算(商16ビット)
 除算も書いてみたが、実はこれ、余だけを求めるルーチンに無理やり商を求める部分を加えたもの。

; IN    HL =  divide value
;       BC =  by value
; OUT   DE =  answer
;       HL =  remain
;
        LD    DE,0
        LD    A,B
        OR    C
        JR    Z,_DVS1    ; /0 skip
        LD    A,1
        BIT   7,B
        JR    NZ,_DVA1
_DVL1:  INC   A
        SLA   C
        RL    B
        JP    P,_DVL1
_DVA1:  SBC   HL,BC
        JR    NC,_DVN1
        ADD   HL,BC
_DVN1:  CCF
        RL    E
        RL    D
        SRL   B
        RR    C
        DEC   A
        JR    NZ,_DVA1
_DVS1:
 DEレジスタに関する命令を削除すれば、商を求めずに余だけをHLレジスタに求める本来の動作になるが、応用はきかないと思う。

プロセッサを判定する
 CP/M-80用などの汎用プログラムでは、自身がどのプロセッサ上で実行されているか判定しなければならない場合もある(かも知れない)。CPUIDなんて便利な命令はないので、お下品なプログラムが嫌いな人は、ここから先は見てはいけない。

        XOR   A
        DEC   A
        DAA
        CP    65H
        JP    Z,P8080
        SUB   99H
        JR    NZ,PZ180   ; if A=F9h then Z180/Z380
        OR    40H
        DB    0CBH,37H   ; SLL A/TSET A
        JP    M,PZ080    ; if sf=1 then Z80
        CP    0FFH
        JR    Z,PZ280
        XOR   A
        LD    C,A
        INC   A
        LD    H,A
        LD    L,A
        DB    0EDH,0C9H  ; MULUB A,C
        DB    0,0
        LD    A,H
        OR    L
        JR    Z,PR800    ; if zf=1 then R800
PZ180:
        XOR   A
        LD    HL,PTR01+1
PTR01:  DB    0EDH,27H   ; ##TRAP/EX  A,H/LD  HL,(HL)
        NOP
        OR    A
        JR    NZ,PZ380
        ADD   A,H
        JR    NZ,P64180
; end of check(eZ80)
PZ280:
; end of check(Z280)
PR800:
; end of check(R800)
P64180:
; end of check(Z180)
PZ380:
; end of check(Z380)
PZ080:
; end of check(Z80)
P8080:
; end of check(i8080/i8085)

 美しくないなぁ。i8080とi8085を判別していないし。i8085にはほぼすべてのセカンドソースで動作する未定義命令が存在するらしいので、どうしても判定が必要ならそれを利用するのも手だろう。
 PZ180:からのモジュールは、システムに於けるTRAP割り込みルーチン(0000h〜)の処理内容によっては正常に動作しないし、ゼロページに置いてもいけない。CP/M-80下などで動作させる場合には、後者は問題にならない。
 完全互換プロセッサは判定する方法がない(判定する意味もない)ので、その際の判別は目視などソフトウェア以外の方法でお願いしたい。


μPD780余多話に戻る



[PR]生年月日で2010年運命占い:初回無料!貴女の悩みを占い師に相談