![見出し画像](https://assets.st-note.com/production/uploads/images/149469885/rectangle_large_type_2_7209f69198578bfeb920163e35bdda37.png?width=1200)
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
ダウンロードして動かしながら、まあ私の説明を聞いてくださいまし。
![](https://assets.st-note.com/img/1722677077315-DZ4P29yxxe.png?width=1200)
サンプルの内容
サンプルは 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でもサンプルを公開しました。
ということで。
ゲーム作んないとなー・・・と思いつつ逃避するような記事でした!(汗)
ではまた!ノシ
いいなと思ったら応援しよう!
![MSXのZ80で何か作る](https://assets.st-note.com/production/uploads/images/165443512/profile_71e620b4702dc58b8c67c13f72890394.jpeg?width=600&crop=1:1,smart)