見出し画像

BCD(2進化10進)を使ってみる

8月です!夏本番ですね!
何十年も屋根の下で仕事をしているので皮膚が退化してしまい、ドラキュラのような青白い肌をしている筆者です。
外に出たら溶けます・・。
さて、外に出れないので今回の記事でも読んで勉強しましょう!
今回はZ80特有(?)の命令、DAAについてです。

DAA(Decimal Adjust Accumulator)命令

みなさんはBCDという単語を聞いたことはありますか?
BCDはBinary-coded Decimalの略で、IBM社が大昔に決めた仕様です。
どういうことかというと、4ビットで10進の数字を表現する。というものです。
例えば、1バイトの値が12Hだとすると、10進で12にしてしまおう。
というかんじ。
54321という10進の数値をBCDで表現すると
05H, 43H, 21H の3バイトで表現しちゃえばいいじゃん!っていうかんじです。
これ、なにが便利かっていうと10進の計算ですね。
例えばゲームのスコア計算とか
金額計算とか、経験値の加算とか。
そういうのに便利です。

と、いうことで今回のサンプルがありますよ。っと。。
https://github.com/sailorman-msx/games/tree/main/src/sample024

ダウンロードして動かしながら、まあ私の説明を聞いてくださいまし。

サンプルの内容

サンプルは 6/60秒(0.1秒)のタイミングごとに10進の数値をカウントアップしていき画面に表示させる。というただそれだけのものです。
BCDの計算部分は以下の箇所になります。

;--------------------------------------------
; BCD加算処理
;
; BCD(2進化10進)は
; 1バイトを上位4ビットと下位4ビットにわけて
; 10進数の数値を格納する方法
;
; 例1)数値の58の場合
;     58H
; 例2) 数値の7の場合
;     07H
; 例3) 数値の90の場合
;     90H
;
; BCD値同士を加算して桁上りするとCYフラグが立つ
; 例えば 99H(10進で99)に01H(10進で1)を加算すると
; Aレジスタには00Hになり、CYフラグが成立する
; CYフラグが成立していたら
; ADDを使って上位の桁に1を加算すれば良い
; もちろん上位の桁に加算したあとも
; その桁をDAAで補正する必要がある
;
; 2桁を超える場合は2バイト、3バイトで表現する
;
; 例4) 数値の100の場合
;     01H , 00H
; 例5) 数値の123の場合
;     01H , 23H
; 例6) 数値の9876の場合
;     98H , 76H
;
;--------------------------------------------
AddBCD:

    ; 一番位の低い桁のアドレスをHLにセット
    ; 今回は10桁(5バイト)なので
    ; NUMBER_DIGIT + 4 が一番桁が低い場所

    ld hl, NUMBER_DIGIT + 4

AddBCD_One:

    ; DAAを使ってNUMBER_DIGITを加算する
    ld a, (hl)
    ld e, 1

    ; A = A + 01H してBCDにする
    add a, e
    daa  ; BCDに補正

    ld (hl), a ; 補正値をセットしなおす

    ; CYが成立していたらひとつ手前のDIGITの値に加算する
    ; CYが成立していなければ何もせず処理を抜ける
    jr nc, AddBCDEnd

    ; HLがNUMBER_DIGITと同じアドレスであれば
    ; なにもしない

    push hl
    ld bc, NUMBER_DIGIT
    sbc hl, bc
    ex af, af'  ; フラグレジスタの退避
    pop hl

    ex af, af'  ; フラグレジスタの復旧
    jr z, AddBCDEnd

    ; 加算対象となる桁を減らして上位の桁に再度加算する
    dec hl
    jr AddBCD_One

AddBCDEnd:

    ret

NUMBER_DIGITというアドレスには10進で10桁ぶん、つまりBCDで5バイトぶんの変数が格納してあります。
0.1秒ごとにこの処理が呼び出されBCDの数値が1ずつ加算されていきます。
表示処理は以下のとおりです。

    ; BCDの値を文字列に変換する

    ld hl, NUMBER_DIGIT
    ld de, NUMBER_DIGIT_STR
    ld b, 5 ; NUMBER_DIGITに5バイトぶん(10桁)のBCD値が格納されている

DigitToStr:

    ld a, (hl)
    ld c, a

    ; 上位4ビットの値を抽出
    srl a
    srl a
    srl a
    srl a

    ; 値に$30を加算して文字列にする
    add a, $30

    ; 文字列を変数にセット
    ld (de), a
    inc de

    ld a, c

    ; 下位4ビットの値を抽出
    and $0F

    ; 値に$30を加算して文字列にする
    add a, $30

    ; 文字列を変数にセット
    ld (de), a
    inc de

    inc hl

    ; Bをデクリメントして0になるまでループする
    djnz DigitToStr

DispDigit:

    ; 画面に文字列を表示する
    ld hl, NUMBER_DIGIT_STR
    ld de, $1840
    ld bc, 10
    call WRTVRMSERIAL

NUMBER_DIGITにセットされている数値はBCDなので上位4ビット、下位4ビットをそれぞれ分割してひとつの数字(文字)に変換しています。
数値に30Hを加算すると0〜9までの文字コードになるので $30を足してます。

ちょっとしたテク:裏レジスタでフラグを保持する

裏レジスタについては過去記事で説明したとおりですが
今回のサンプルでも使ってます。
SBC HL, BC
の結果(フラグレジスタ)を退避させるために使っています。

    ; HLがNUMBER_DIGITと同じアドレスであれば
    ; なにもしない

    push hl
    ld bc, NUMBER_DIGIT
    sbc hl, bc
    ex af, af'  ; フラグレジスタの退避
    pop hl

    ex af, af'  ; フラグレジスタの復旧
    jr z, AddBCDEnd
 
    ; 加算対象となる桁を減らして上位の桁に再度加算する
    dec hl
    jr AddBCD_One
 
AddBCDEnd:

裏レジスタを使わない方法だとこんな感じの書き方になります。
jr z, … でジャンプしたのはいいけれど push hlのPOP忘れをしてしまうと大変なので・・

    ; HLがNUMBER_DIGITと同じアドレスであれば
    ; なにもしない

    push hl
    ld bc, NUMBER_DIGIT
    sbc hl, bc
    jr z, AddBCDPopEnd
    pop hl
 
    ; 加算対象となる桁を減らして上位の桁に再度加算する
    dec hl
    jr AddBCD_One

AddBCDPopEnd:
    pop hl ; POP忘れをふせぐためわざとPOP
 
AddBCDEnd:

うーーんん・・・これけっこうスパゲティです・・
裏レジスタを使うことで無駄POPを省くことができました。やったね!

exなんて使わなくてもこれでいいじゃん。
と、いま思いました。。。

push hl
ld bc, NUMBER_DIGIT
sbc hl, bc
pop hl
jr z, AddBCDEnd


DAAのなにが便利なの?

なにが便利かって、別に加算だけじゃなく減算でも使えます。
あと、ステート数が速いんです。
当記事の最初のほうでは自力で16進ダンプするプログラムを書いてましたがDAA使うともっと楽にかけるはず。
「DAA、そのうち使おうかなーでもいまでも十分なんだよなあー」と思って記事にしていませんでしたが、まあ、便利だと思いますよ。はい。

MSXPenでもサンプルを公開しました。

ということで。
ゲーム作んないとなー・・・と思いつつ逃避するような記事でした!(汗)

ではまた!ノシ

セーラー服が似合うおじさんです。猫好き、酒好き、ガジェット好き、楽しいことならなんでも好き。そんな「好き」をつらつらと書き留めていきます。