6800のCPX命令とエミュレーター(1)(ベーシックマスター開発 その26)
開発にはエミュレーターを使っているのだけど、CPX命令の処理が正しくないエミュレーターがあるようだ。そもそも6800のCPX命令は変なのである。
私がBASICMASTERを使っていたころは、世の中のプログラムはCPX命令の後は必ずBEQ/BNEで分岐していた。
BEQは比較の結果が等しい(Z=1)なら分岐し、BNEは等しくなければ(Z=0)分岐する命令である。
BASICMASTERのモニタやBASIC ROMを見ても、CPXの後に来るのは100% BEQかBNE命令である。
私もCPXは(他のフラグも変化するけど)Zフラグだけが使えると思い込んでいた。
そもそもIXレジスタを使うのは、メモリを舐めるループ用のポインタか、ループカウンタとして使う場合であり、前者はCPX、後者はDEXを使ってZフラグと比較すれば用は足りる。
他のフラグの出番はなかった。
IXレジスタ便利
さて、GAMEコンパイラを作っていると単純な代入が多いことに気づく。これはCなどの他の高級言語でも同じ。
例えば、小さな数値(多いのは0や1)の代入、変数のコピー、変数と小さな数値(±1が多い)の加算である。
最近のCPUアーキテクチャーだと小さな数値の設定が優遇されてたり、ゼロレジスタが存在するのはこれが理由である。
例えば Var=1 という代入文を、真面目にAccABを使うようにコンパイルすると 14cycle/10bytes 必要になる。
CLRA/STAA Var/INC A/STAA Var+1 とするトリックはあるけど、それでも14cycle/8bytesである。
LDAB #1 ; 2 2
LDAA #0 ; 2 2
STAB Var+1 ; 5 3
STAA Var ; 5 3
IXレジスタを使うと、9cycle/6bytesである。速くて小さい。素晴らしい。
LDX #1 ; 3 3
STX Var ; 6 3
Var = Var+1 の場合、AccABを使うと、22cycle/14bytes。INCBではCarryが立たないのでADDBを使う必要がある。
LDAB Var+1 ; 4 2
LDAA Var ; 4 2
ADDB #1 ; 2 2
ADCA #0 ; 2 2
STAB Var+1 ; 5 3
STAA Var ; 5 3
IXを使うと 15cycle/7bytes。やっぱり小さくて速い。+2/-2まではINX/DEXが速いし小さい。
LDX Var ; 5 3
INX ; 4 1
STX Var ; 6 3
IXで比較も実行する
どの言語でも、==0、!=0 の比較は多く使われる。CPXの出番だ。
MC6800では、AccABを使った比較は上位・下位バイトを別々に比べないといけないので非常に面倒くさい。
if(Var==0)… をコンパイルするとこうなる。これ、==0 だから4行で済んでいるけど、expr>expr だとSUBB/SUBAも入ってきて長大なプログラムが生成される。
(BNE/BRAで届く保証がない場合は更に奇怪なコードになるが、わかりやすくするためにBNE/BRAを使っている)
LDAB Var+1
BNE ELSE
LDAA Var
BNE ELSE
THEN: ...
BRA NEXT
ELSE: ...
NEXT: ...
IXだとこうなる。少し嬉しい。代入文で使った直後ならZフラグが残っているので、LDXも省略できる。
LDX Var
BNE ELSE
THEN: ...
BRA NEXT
ELSE: ...
NEXT: ...
Nフラグも使えるのでは?
2バイトの数値であっても、正(0以上)か負(0未満)であるかは、最上位ビットだけを見ればわかる。0かどうかは2バイト全部見ないといけないが、正負は1バイトで良い。
すると、Var>=0 はこう書ける。これは素晴らしい。A>B も A-B>0 と引き算にすれば、引いてみて最上位ビットを見るだけで良い※。
- ※オーバーフローがあるので、Vも見ないといけません。BPL/BMIじゃなくてBGE/BLTが必要。TST命令の場合はBPLで良い。
TST Var
BMI ELSE
THEN: ...
BRA NEXT
ELSE: ...
NEXT: ...
A>=10 のような場合も、A>9と置き換えればBMIBGT一発で分岐できる(もちろん整数型に限るけど、GAMEは整数型しかないのでこれで良い)。
では、CPXでNフラグを見るのはどうだろう?
代入演算などでIXレジスタに変数値が残っていることがある。これを流用できると嬉しい。
そもそも、TST Var は1命令で正負を判断できて便利なんだけど、実行に6cycleも必要である。AccABを壊して良いなら、LDAB Varとした方が4cycleなので2cycle速い。
(TST命令ではダイレクトページアクセスが無いのも痛いけど、その話はまた別の機会に)
IXを使うということは、以下のように書けるかということである(課題その1)
CPX #0
BMI ELSE
定数や変数の比較はどうだろうか(課題その2)
CPX #const
BMI ELSE
CPX命令のフラグ変化はマニュアルにはどう書かれているか
ベーシックマスターL2II(MB-6881)のマニュアルにはこう書かれている。Jr.(MB-6885)のマニュアルも同じ。
上位・下位バイトを別々に引き算していることはわかる。ではNはどうなるのか?
Nの説明はこうなってる。“上位8bitからの”、って何よ?
これは、Motrola の M6800 Microprocessor Applications Manual だとこう書かれている。なるほどわからん。
(Bit N) Test: Sign bit of most significant (MS) byte of result = I?
Motorola M6800 Programming Reference Manual がわかりやすい。結局、N・Vフラグは、上位バイト同士の演算結果だけを反映していて、下位バイトの演算は無視される。
たぶん、下位バイトからのキャリーを無視しているのだろう。これはMC6800の仕組み上仕方ないと思う(後述)。
6800 Assembly Language Programming だと例が挙げられている。Zは上位下位両方見るけど、NとVは上位しか見ない。この本はN,Vの代わりにS,Oを使っているんだけど、なぜだろう?
CPX後のN/Vフラグは使えるのか?
下位からのキャリーを見ていないので、下位が0以外の数値との比較は不正確になる。わかりやすい例。これ、CPX #0でもCPX #1でもCPX#255でもN,Vフラグの変化は同じ。
LDX #0
CPX #1
CPX #0, CPX #256, CPX #512… なら使える。一番使い勝手が良いのは CPX #0 かな。LDX/STXでも同様にフラグが立つので、正負の判断だけならIXレジスタも使えそうである。
だが、しかし…
ディスカッション
コメント一覧
まだ、コメントがありません