ゲームボーイを作るその15。少しずつCPUの実装を進めている所なのだが、DAAという命令の挙動がよくわからん!となったので、調べたことをまとめておく。
DAA (Decimal Adjust Accumulator)命令
この命令Pan Docsには"decimal adjust A"とあるだけで、詳細な動きなどが何も書いていなかった。 もちろんエミュレータでは動作するので、適当なアセンブラのコードを書いて動かせば詳細についても把握はできるのだが面倒。
という事で、他に何か資料が無いかと探して見つけたのがIntelの8080Aの資料だ。
INTEL 8080 Microcomputer Systems User's Manual
p.56に次のような説明があった。
The eight-bit number in the accumulator is adjusted to form two four-bit Binary-Coded-Decimal digits by the following process: 1. If the value of the least significant 4 bits of the accumulator is greater than 9 or if the AC flag is set, 6 is added to the accumulator. 2. If the value of the most significant 4 bits of the accumulator is now greater than 9, or if the CY flag is set, 6 is added to the most significant 4 bits of the accumulator
命令の説明としては「アキュムレータの8bitの数4bitx2のBCDに補正する」という感じか。
ここのAC
/ CY
FlagはPan Docsの説明で言う H
/ C
flagに相当する。なのでA
レジスタの値を上位/下位4bitずつに分割して、その4bitの値が10以上、もしくはH
/C
フラグが1の場合に6を足す、という演算を行えば良いようだ。
注意すべきなのは、この演算は同時に上位/下位4bitの演算を行うのではなく、1→2の順で行う必要があるという点だ。
なので、1の演算で6
を加算して4bitのオーバーフローが発生した場合は上位の4bitの計算時にはこのキャリーを考慮する必要がある。
で、大体の処理内容が把握できたところで、テストのためのコードを書いてみた。
.memoryMap defaultSlot 0 slot 0 $0000 size $4000 slot 1 $C000 size $4000 .endMe .romBankSize $4000 ; generates $8000 byte ROM .romBanks 2 ;; 0x100 - 0x14f is enrtry point and cartridge haeder. ;; So skip this region for cpu instruction test. .org $150 ;; initialize flagregister ld a, $10 ; a = $0a add a, $01 ; b = $02 ;; initialize register ld a, $00 ; a = $0a ld b, $00 ; b = $02 ld c, $00 ; c = $02 ld d, $00 ; d = $02 ld e, $00 ; e = $02 ld h, $00 ; h = $02 ld l, $00 ; l = $02 ;;; The case of adding $06 ;; a[3:0] = 0 ~ 3 / h = 1 => a[3:0] + $06 / h = 0 ld a, $0f ; a = $0f inc a ; a = $10 / h = 1 ld a, $00 ; a = $03 daa ; a = $06 ld a, $0f ; a = $0f inc a ; a = $10 / h = 1 ld a, $09 ; a = $09 daa ; a = $0f ;; a[3:0] = $a ~ $f / h = 0 / c = 0 => a[3:0] + $06 ld a, $0a ; a = $0a daa ; a = $10 ld a, $0f ; a = $0f daa ; a = $15 ;; The case of adding $60 ;; a[7:4] = 0 ~ 9 / c = 1 => a[7:4] + $60 / c = 1 / When C Flag is high, this flag keep after DAA. ld a, $20 ; a = $ff add a, $f0 ; a = $01 / c = 1 ld a, $00 ; a = $00 daa ; a = $60 ld a, $20 ; a = $ff add a, $f0 ; a = $01 / c = 1 ld a, $90 ; a = $30 daa ; a = $f0 ;; a[7:4] = a ~ f / c = 0 => a[7:4] + $60 / c = 1 ld a, $10 ; a = $0a add a, $01 ; b = $02 / c = 0 ld a, $a0 ; a = $a0 daa ; a = $00 / z = 1 / c = 1 ld a, $10 ; a = $0a add a, $01 ; b = $02 / c = 0 ld a, $f0 ; a = $f0 daa ; a = $50 / c = 1 ;;; The case of adding $66 ;; a[7:4] = 0x8, a[3:0] = 0xf ld a, $10 ; a = $ff add a, $e0 ; a = $01 / c = 0 ld a, $9f ; a = $00 daa ; a = $05 / c = 1
このコードをエミュレータで動かしてみた所、どうもc=1
の場合の扱いが自分の理解した演算と一致しなかった。
具体的には次の部分だ。
ld a, $20 ; a = $ff add a, $f0 ; a = $01 / c = 1 ld a, $00 ; a = $00 daa ; a = $60
上記のコードでは a=0
で c=1
なので、上位4bitは0→6
に変化して、加算結果としてはオーバーフローが発生していない。そのためキャリーはクリアされると考えていた。、、、がエミュレータで実行すると、フラグビットはそのままc=1
で保持される結果となった。
という事で、もう一度調べ直して見つけたのが次のサイト。
このサイトのよるとc
の扱いは「c: 変換によって算術オーバーフローが発生した(変換後の値が十進数で100を超える)場合にセットされる。DAAの実行前にセットされている場合はセットされたままとなる。」とのこと。ということで、引っかかっていたフラグビットの扱いもエミュレータに従えば良いということが確認できた。
この結果にしたがい自作のCPUブロックの実装を行った。
なお今回の各種確認において、最終的にPASSさせたいblargg-gb-testのDAA命令のテスト内容も確認したのだが、このてすとでは全ての組み合わせがテストされているとのことなので、何か認識不足があればそちらで引っかかるだろうという事でひとまず、自作テストをPASSしたところでDAAについてはここまでで一区切りとしておく。