BCD命令の修正
まず結論から言いますが、BCD命令(DAA)を修正しました。
github上のソースコードを修正しています。
二進化十進(BCD)用命令について
二進化十進とは読んでごとく2進数の値を10進数にする命令です。
コンピュータは2進数を基準に計算しています。
人間は10進数の方が扱いやすいので2進数から10進数に変換する必要があります。
1バイトは0~255です。
この値を画面に表示する場合は桁ごとに数字を取り出す必要があります。
以下は255を表示する例です。
百の桁 | 十の桁 | 一の桁 |
---|---|---|
2 | 5 | 5 |
255÷100の商 | 余りの25を÷10した商 | 最後の余り |
この方法は8bitCPUでは重たい処理になります。
(Z80には除算命令がないため)
Z80ではニーモニックではDAAという命令を使います。
DAAは1バイトにつき2桁のみですが、10進数に変換してくれるありがたい命令です。
実は世界最初の4ビットCPUの4004から存在していた命令です。
4004は電卓専用に作られた経緯があるので電卓のボタンで打ち込める数値0~9を4ビットに入れて計算していました。
10桁を計算するなら4ビットを10個(計5バイト)用意すればいいのです。
そのCPUの流れを継承している8080やZ80もDAA命令はあります。
(4004とZ80を設計した人が同じだからとも言えるが・・。)
Z80では1バイトを上位4ビット、下位4ビットで区切ってそれぞれ10の桁と1の桁で表現します。
例えば以下のケースを考えます。
8+8=16
この16は10進数ですが、16進数で表現すると010hとなります。
先ほど上位4ビットと下位4ビットで分けると説明しましたが、010hは10の桁が1、1の桁が0となります。
つまり8+8を計算したのにBCD表記の結果は10になってしまうのです。
この計算結果を補正するためにDAA命令を使います。
詳しい仕組みは後述しますが、DAA命令を使うとBCD表記で16になります。
16は16進数の016hと同じで10進では22になりますので元の数値に+6されたことになります。
つまり
「16進数を10進数のように扱う。」
を実現するのが二進化十進の仕組みです。(16進化十進と言った方がイメージが付きやすいが、昔から説明されていることなのでこの表記が正しい)
ちなみにどこでこれを使うかというとゲーム中のスコア表示とかパラメータ表示とかに使います。
4ビットずつに分けて数字のキャラコード'0'を加算して表示するだけなので処理が速くなります。
もし2進数でスコア管理していると10で割り算を繰り返さないといけなくなります。(8ビットは割り算がないのでめっちゃ遅い)
自作のDAA処理の問題
不完全なのは分かっていました。
1の桁の処理は+6したり-6したりやっていたのですが、10の桁も桁が上がったり下がったりするときの処理が実装できていませんでした。
詳しく説明しているサイトがあり以下が参考になります。
この条件で6や60を足したり引いたりしています。
この表をそのまま実装すると5段階の判定が必要になるのでかなり面倒です。
どうしたものかと考えていたのですが、結局横着しちゃいました。
既存のエミュレータのロジックを参考にしました。
有名なmameのエミュレータなので処理は問題ないでしょう。
このコードの911行目の部分がDAA処理なのでこれを自分のソースに組み込みました。
フラグ処理は違うので手を入れていますが、以下のような感じです。
temp8=A_REG;
if (N_FLAG){
if ((H_FLAG==1) | ((A_REG&0xf)>9)) temp8-=6;
if ((C_FLAG==1) | (A_REG>0x99)) temp8-=0x60;
}else{
if ((H_FLAG==1) | ((A_REG&0xf)>9)) temp8+=6;
if ((C_FLAG==1) | (A_REG>0x99)) temp8+=0x60;
}
H_FLAG=((A_REG ^ temp8)& 0x10)>>4;
if(A_REG>0x99){C_FLAG=1;}
if(temp8==0){Z_FLAG=1;}else{Z_FLAG=0;}
S_FLAG=(temp8 >>7)&1;
A_REG = temp8;
PV_FLAG=ParityCheck(A_REG);
変数とフラグに色んな値を入れて試しましたが問題はなかったです。
エミュレータ作りの参考資料
CPUの命令ごとの動作を再現するために以下を参考にしています。
一部表記が間違っていることろがあるので以下の資料でも確認します。
あとコードから命令を探すならここを見ています。
この資料でも不明な所があればMSXPenでテストですね。
こんな感じのコードを実行してレジスタの値をウォッチすると・・。
org 0c000h
main:
ld h,0
ld l,00010000b
PUSH HL
POP AF
ld a,16
DAA
loop:
jp loop
よそのエミュレータについて
うちと設計思想が違うので構造も処理も違うのですが、参考にすると勉強になります。
次の2つを紹介します。
1.MAME
色んなハードのエミュレータとして有名なのでZ80以外のCPUも実装しています。
性能重視の実装がされているので無駄な条件判定を省くためテーブルを多用しています。
つまりメモリを多く使うのですが、リソースの少ない環境では向かない かもしれません。
2.z80エミュレータ
ここの作者もz80エミュレータ作ってMSXを動かしているようです。
MMUに自由度を持たせているのでZ80を使う他のコンピュータも再現することができるでしょう。
しかし自由度=性能低下のトレードオフがあるのでホストCPUの性能次第ですかね。
MMUの実装についてはまだ悩んでいるのでどうするかは今後検討と実装実験します。