SSブログ
English Version

Z80でのDAA命令によるHEX変換テクニック [Z80]

 Z80 等で BCD 演算のための補正命令である DAA を使ってバイナリ(00H..0FH)をヘキサの文字コード('0'..'9','A'..'F')に巧妙に変換する処理が知られています。

 今回はこのバイナリ to HEX 変換処理でヘキサ文字を小文字にしたい場合の処理を考えたのでブログに記録しておきたいと思います。

 最初に結論から書いてしまうと今回考えた下記のコードで 00H..0FH のバイナリを '0'..'9','a'..'f' に変換できます。


  D6  0A    SUB    10  
  27DAA
  27DAA
  27DAA
  DE  9FSBC    A,9FH



 それでは上記のコードに辿り着いた経緯について以下に書きます。

  1. DAA 使用の一般的なヘキサ変換
     例えば居酒屋ガレージ日記の「プログラムテクニック「DAA」」の記事に書かれているように


      ADD    A,90H  
    DAA
      ADC    A,40H  
    DAA


    の6バイトのコードで 00H..0FH を '0'..'9','A'..'F' に変換できます。
     入力が 0AH 以上の場合は 最初の DAA で上位ニブルに桁上りし、更に上位ニブルが桁上りして 0 になり、キャリーが立つので2回目の DAA で上位ニブルは 4 となり、下位ニブルには キャリーも加算されるので最初の値から 9 が引かれた値になります。
     入力が 09H 以下の場合は最初の DAA では上位ニブルが 9 となることで 2回目の DAA で上位ニブルが 4-1=3 になります。
     BCD の桁上りとキャリーの効果を巧妙に組み合わせた処理ですね。

    ★追記 2023/05/24 {
     このような6バイトの変換コードは他にも存在します。例えば先頭の"ADD A,90H"は"OR 90H"でもいいし、"ADC A,40H"が"ADC A,3AH"でも結果は同じです。
     下記は今回考えたコードで引き算方式にしたもので、ここ以外では多分見かけないのではないかと思います。

    my idea 1

      SUB    0AH  
    DAA
      SBC    A,59H  
    DAA


    my idea 2

      SUB    0AH  
    SBC    A,25H
      CCF      
    DAA

    ※追記 2023/05/27 CCFではハーフキャリーが不定なので上記は環境依存性があります
    }
    ★追記 2023/05/26 {
     最初に OR を実行してハーフキャリーをクリアすれば普通に変換できます。

    my idea 3

      OR     A  
    DAA
    CP    10  
    SBC   0CFH


    my idea 4

      OR     A  
    DAA
    ADD    A,0F0H  
    ADC    A,40H

    }


  2. 更に短いコード
     上記の様なヘキサ変換で必要な処理の要素を列挙すると

    1. 下位ニブルの桁上り
       入力が 0AH 以上の場合は下位ニブルから上位ニブルに桁上りが発生するようにして上位ニブルを +1 する。
    2. 下位ニブルへの+1
       入力が 0AH 以上の場合は下位ニブルに +1 されるようにする('A'のアスキーコードは 41H なので xAH を x1H にする)

    となることが判ります。
     例えば Twitterのタイムラインで見つけた


      CP    10  
    SBCA,69H  
    DAA


    の5バイトのコードでもヘキサ変換が可能です。SBC を絶妙に組み合わせることで上記の2つの必要な要素が実現されています。

     キャリーを加算することで同じようなことが出来ないか考えてみました。入力が 0AH 以上の場合にキャリーを発生させる必要があるので


      ADD    A,0F6H  
    ADCA,3AH
    DAA


    としてみましたが、ADC 後も入力が 0AH以上の場合と 09H 以下の場合でキャリーの値が異なるので DAA の作用が不統一になり破城しましたw
     CP 命令は減算処理はするが A-reg は変更しないという命令ですが加算でも同様にフラグは更新するが A-reg を変更しない命令があればいいのですが・・・
     1バイト増えてしまいますが下記の様にキャリーの操作を加えると上手く行きました(処理内容も判り易いですね)。


      CP    10  
    CCF
    ADCA,30H  
    DAA



  3. 小文字のヘキサ変換
     上記の様に ADD や SUB でキャリーを操作した場合、次の加算または減算で 入力が 0AH 以上の場合と 09H 以下の場合でどちらかがバイトのバウンダリーを超えるので、キャリーが不一致な状態となり、DAA で不要に +6 や -6 されるので、5バイトでのヘキサ変換処理は上記の方法以外は無いのではないかと推測します。

     と言うわけで趣向を変えて小文字のヘキサ変換について挑戦してみました。DAA 命令の複雑な挙動に関しては「Zilog Z80 DAA実行結果」にまとめられているのを見つけたので、これを参考にして辿り着いたのが冒頭に書いたコードになります。

     下記のような検証用のプログラムで全てのケースについて確認を行いました。

    1バイトの小文字ヘキサ変換試験プログラム(Z80アセンブラ)
    ;+++++++++++++++++++++++++++++++++++++++++++++++ ; translate 0x00-0x0f to hex ascii code '0'-'f' ; Ver 0.01 2023/05/22 by skyriver ;+++++++++++++++++++++++++++++++++++++++++++++++ 000D CR EQU 13 000A LF EQU 10 0001 SMALL EQU 1 ; lower case ToHex macro if SMALL EQ 0 CP 10 SBC A,069H DAA else SUB 10 DAA DAA DAA SBC A,09FH endif endm 0000' ASEG ORG 0100H 0100 AF XOR A 0101 F5 LOOP: PUSH AF 0102 CD 0116 CALL TRAN 0105 3E 20 LD A,' ' 0107 CD 0135 CALL PUTC 010A F1 POP AF 010B 3C INC A 010C F5 PUSH AF 010D E6 0F AND 0FH 010F CC 013C CALL Z,NEWLN 0112 F1 POP AF 0113 20 EC JR NZ,LOOP 0115 C9 RET ; translate to hex ascii code ; A <- data(00-0f) 0116 F5 TRAN: PUSH AF 0117 0F RRCA 0118 0F RRCA 0119 0F RRCA 011A 0F RRCA 011B E6 0F AND 0FH ToHex 011D D6 0A + SUB 10 011F 27 + DAA 0120 27 + DAA 0121 27 + DAA 0122 DE 9F + SBC A,09FH 0124 CD 0135 CALL PUTC 0127 F1 POP AF 0128 E6 0F AND 0FH ToHex 012A D6 0A + SUB 10 012C 27 + DAA 012D 27 + DAA 012E 27 + DAA 012F DE 9F + SBC A,09FH 0131 CD 0135 CALL PUTC 0134 C9 RET ; put chara on screen ; A <- data 0135 PUTC: 0135 5F LD E,A 0136 0E 02 LD C,2 0138 CD 0005 CALL 0005H 013B C9 RET ; new line 013C 3E 0D NEWLN: LD A,CR 013E CD 0135 CALL PUTC 0141 3E 0A LD A,LF 0143 CD 0135 CALL PUTC 0146 C9 RET END Macros: TOHEX Symbols: 000D CR 000A LF 0101 LOOP 013C NEWLN 0135 PUTC 0001 SMALL 0116 TRAN No Fatal error(s)

     実機の CP/M-80 環境での確認結果が下図になります。

    CP/M-80 環境での確認結果(OK)

     ところが Windows の DOS 窓での CPM エミュ環境では下図のように NG でした。尚、プロンプトで表示されているパス情報はわざとぼかしています。

    CP/M エミュ環境での確認結果(NG)

     CP/M エミュでは DAA を実行した後のフラグの挙動が Z80 と違うことが原因の様です。下図は Z80 の実機(Z84C0020PEC)を使って CP/M 環境で ZSID でステップ動作した結果です。

    Z80 実機でのステップ動作

     下図が CP/M エミュでのステップ動作になります。DAA 命令での 'I'フラグの挙動が上記の実機と違いますね。尚、'I'フラグはハーフキャリーです。

    CP/M エミュでのステップ動作(NG)

     GAME 言語を使って 00H..0FH のどの範囲が NG なのか確認してみた結果が下図になります。上記の結果からも判っていましたが、00H..09H の範囲がNGの様です。

    CP/M エミュ環境での確認結果(NG)

     エミュということで試しに MSXPen 環境で確認した結果が下図になります。MSXPen 環境では流石に問題無いようです。

    MSXPen 環境での確認結果(OK)


★追記 2023/05/26
 その後も少し気になったので下記のパターンで自動スキャンしてみました。xxとyyは 00H..FFH の範囲でフルスキャンし、ニーモニックの部分はそれぞれのパタンの全組合せを自動チェックしています。

CP/OR/ADD/SUB xx; NOP/DAA; ADC/SBC yy; NOP/DAA

 結果は下図の通りで上記で既に記述したもの以外はありませんでした。「*** Find!!」以降の部分が見つけた解を表示している部分でそれ以外の表示はスキャンの進捗が判るように表示しているものです。
 尚、各ニーモニックのオペコードは

CP:FE,OR:F6,ADD:C6,SUB:D6,NOP:00,DAA:27,ADC:CE,SBC:DE

です。

変換コードパターンのスキャン結果


 2バイト分のフルスキャンとオペコード切替えなので全チェックパターンは 210 万(=4*256*2*2*256*2)程ですが、上記の画面で最後に表示されているようにほぼ4分でスキャンが完了しました。環境は Z80 20MHz で GAME コンパイラを使用しています。ソースはこれです。

★追記 2023/05/26
 ヘキサ文字への変換は最小単位としては1バイトを2桁のヘキサ文字に変換するケースが殆どなのではないかと思います。下位のニブルを取り出す時に AND 0FH を使用するとハーフキャリーが立ってしまうので厄介なのですが、OR 0F0H であればハーフキャリーが立たない(当然キャリーもゼロ)ので最初から DAA が使えます。入力データが F0H、FAH の場合、DAA 後は期待通りにそれぞれが 50H、60H になります。後は下記の様にキャリーを立てて加算/減算で調整すれば実質5バイトのコードで変換できます。
 以上より、現時点ではヘキサ文字に変換する5バイトコードは3通りあるということが判りました。

OR 0F0H method 1
  DAA      
CP60H
  SBC    A,1FH  

OR 0F0H method 2
  DAA      
ADDA,0A0H
  ADC    A,40H  



★追記 2023/08/18
 Twitter(X?)で関連するメッセージを見かけたのでこのページを紹介して、自分でも再度見てみました。最初に OR する方式で AND 方式の様に CP から始めるパターンもできることに気が付きました。

OR 0F0H method 3
  CP    0FAH  
SBCA,0B9H
  DAA      


nice!(0)  コメント(0) 
共通テーマ:趣味・カルチャー