Z80では、LD A,IまたはLD A,Rを実行するとP/Vフラグに割り込み許可状態が入るのですが、ここで問題があります。
LD A,I(もしくはLD A,R)実行中に割り込みがかかると割り込み禁止になり、その状態がPフラグに取り込まれる、というバグがあります。
こちらはNMOS品で発生し、CMOS品やR800では発生しないとのことですが、MSXだと普通にNMOS品を積んでいる物も結構ありますので、対策は知っておいた方が良さそうです。
特に最近はMSX1用の個人開発ソフトも熱いですし。
この問題について、Zilog公式の対応方法があるとの事なので、調べてみました。
きっかけはこちらのツイート。
MSXでIレジスタを扱う時の注意事項☆https://t.co/KJJtMdMZ8J
— 黑い人健 كين (@Ken_in_black) 2022年5月23日
引用>LD A,I(もしくはLD A,R)実行中に割り込みがかかると割り込み禁止になり、その状態がPフラグに取り込まれる、というバグがあります。
— 黑い人健 كين (@Ken_in_black) 2022年5月23日
→意圖せずに割込み禁止に成る事がある
→んぢゃ、どう對處すれば好いの?
ツイートの記事を見ていくとコメント欄に対策方法の紹介がありました。
Zilog『Z80 FAMILY DATA BOOK ,JANUARY 1989』
という本に、公式の対策プログラムが紹介されているとの事で、ご紹介したいと思います。
実は以前この話を聞いた事がありまして、その時に考えた対策とほぼ同じでした。
ただ、こちらはもっと合理的なコードで、ルーチンの実行アドレスが00XXh以外であれば短縮できるのは勉強になりました。マシン語は面白いですね。
このルーチンの仕様として、
- P/VフラグではなくCyフラグで取得
- Iレジスタの値そのものは保証せず、割り込み状態だけ取得
というのは一緒でした。(P/Vフラグだけ変更するのはややこしいので)
割り込み許可フラグを知りたい時以外は、ややこしい事は必要ないですしね。
https://github.com/uniskie/MSX_GAME_TOOL/blob/main/GETIFF_ENG.asm
"Zilog Z80 FAMILY DATA BOOK JANUARY 1989" 413ページより
Caution, these routines presume that the service routine for any acceptable interrupt will re-enable interrupts before it terminates. This is almost always the case. They may not return the correct result if an interrupt service routine, which does not re-enable interrupts, is entered after the execution of LD A,I (or LD A,R).
;================================================================
;Listing 1: This rouline may not be loaded in page zero
; (not 0000h to 00FFh).
;================================================================
GETIFF:
XOR A ;C flag, acc. := 0
PUSH AF ;stack bottom := 00xxh
POP AF ;Restore SP
LD A,I ;P flag := IFF2
RET PE ;Exit if enabled
DEC SP ;May be disabled.
DEC SP ;Has stack bottom been
POP AF ;overwritten ?
AND A ;lt not 00xxh, INTs were
RET NZ ;actually enabled.
SCF ;Otherwise, they really are
RET ;disabled
END
;
;================================================================
;Listing 2: This routine may be loaded anywhere in memory.
;================================================================
GETIFF2:
PUSH HL ;Save HL contents
XOR A ;C flag, acc. :=0
LD H,A ;HL:=0000h
LD L,A
PUSH HL ;Stack bottom := 0000h
POP HL ;Restore SP
LD A,I ;P flag := IFF2
JP PE,_POPHL ;Exit if isn't enabled
DEC SP ;May be disabled.
DEC SP ;Let's see if slack bottom
POP HL ;is still 0000h.
LD A,H ;Are any bits set in H
OR L ;or in L ?
POP HL ;Restore old contents.
RET NZ ;HL <> 0: isn't enabled.
SCF ;Otherwise, they really are
RET ;disabled.
_POPHL:
POP HL ;Exit when P flag is
RET ;set by LD A,I
END
参考までに日本語訳を乗せるとこんな感じです。
https://github.com/uniskie/MSX_GAME_TOOL/blob/main/GETIFF.asm
;================================================================
; "Zilog Z80 FAMILY DATA BOOK JANUARY 1989" page 413 より、
;
; LD A,I (or LD A,R) IFF2 flag のバグ対策
;
;注意1: これらのルーチンは、全ての割り込みのサービスルーチンが、
; 終了する前に割り込みを有効にすることを前提としています。
; ほとんどの場合、これが当てはまります。
; LD A,I(またはLD A,R)の実行後に、割り込みを有効にしない
; 割り込みサービスルーチンがあると、正しい結果が返されない
; 場合があります。
;
;注意2: 返り値は割り込み状態の取得のみが保証されます。
; (Iレジスタの値は保証されません)
;
;注意3: 割り込み状態の判定はP/Vフラグではなく、
; Cyフラグで返されます。
;================================================================
;
;================================================================
;リスト1:実行アドレスが00xx以外専用
; (このルーチンは0000h~00FFh以外に置く事)
;
;GETIFF: 割り込み状態をCyフラグで返す
;
;入力: なし
;返り値:Cy==0 : EI(割り込み許可)である
; Cy==1 : DI(割り込み禁止)である
;使用: AF
;================================================================
GETIFF:
XOR A ;CyフラグとAレジスタを0にする
PUSH AF ;スタックに00xxhを書き込む
POP AF ;SPを戻す->(SP-2)が00xxh
LD A,I ;P flag = IFF2
RET PE ;EIなら戻る(Cyは0)
DEC SP ;DIなら
DEC SP ;LD A,(SP-2)
POP AF ;が
AND A ;0でなければ
RET NZ ;EIである(Cyは0)
SCF ;そうでなければDI(Cy=1)
RET ;返す
END
;
;================================================================
;リスト2:どこに配置してもOK
; (ただし、リロケータブルには出来ない)
;
;GETIFF2: 割り込み状態をCyフラグで返す
;
;入力: なし
;返り値:Cy==0 : EI(割り込み許可)である
; Cy==1 : DI(割り込み禁止)である
;使用: AF
;================================================================
GETIFF2:
PUSH HL ;HLの値をスタックに退避
XOR A ;CyフラグとAレジスタを0にする
LD H,A ;HL=0000h
LD L,A
PUSH HL ;スタックに0000hを積む
POP HL ;SPを戻す->(SP-2)が0000h
LD A,I ;P flag = IFF2
JP PE,_POPHL ;EIならこのまま終了処理へ飛ぶ
DEC SP ;DIの場合
DEC SP ;LD HL,(SP-2)
POP HL ;が0000hか
LD A,H ;調べる
OR L ;
POP HL ;スタックに退避したHLの値を復帰
RET NZ ;LD HL,(SP-2)が0000hでなければEI(Cyは0)
SCF ;そうでないならDI(Cy=1)
RET ;返す
_POPHL:
POP HL ;退避したHLの値を復帰
RET ;LD A,IでCyは変化しないのでCyは0
END
基本的には短い方のGETIFFで事足りそうですね。
STACKに門番を置いて、書き換えられていたら割り込みがあった=割り込み許可状態*1だと判断する方式が使えたのは不幸中の幸いでしょうかね。
これが逆に「間違ってEIが入る」現象だったらどういうコードが必要になったのやら。
今回の件で、もう少しちゃんとマシン語を勉強しようと思いました。
*1:割り込み許可=Enable Interrupt=EI / 割り込み禁止=Disable Interrupt=DI