So-net無料ブログ作成
English Version

Z80での10の高速除算方法 [Z80]

 今回はtwitterで見かけたZ80で10で割る処理に関するお題について書いてみます。

 お題としてはZ80で100未満の数値を10で除算処理する場合、如何に高速にできるか?というもので

    Breg ÷ 10 = Hreg ... Areg

という処理です。

 具体的な処理例も挙げられていて次の処理を高速化できるか? という問題です。
 100要素分の回答の配列から引っ張るのが一番速いですがそれは無しという条件付きです。
 尚、以下に示す各処理は0..99の入力に対するリターン値(AregとHreg)が全て同じであることを確認済みのものです。

処理例
; +++ asking method +++ 78 chk10: ld a,b ; input value 21 00A0 ld hl, 10 SHL 4 BD cp l 38 03 jr c,chk0 95 sub l 26 10 ld h,16 CB 0D chk0: rrc l BD cp l 38 03 jr c,chk1 95 sub l CB DC set 3,h CB 2D chk1: sra l BD cp l 38 03 jr c,chk2 95 sub l CB D4 set 2,h CB 2D chk2: sra l BD cp l 38 03 jr c,chk3 95 sub l CB CC set 1,h CB 2D chk3: sra l BD cp l 38 03 jr c,put2 95 sub l CB C4 set 0,h C9 put2: ret


 上記の処理内容を見ると通常の割算処理のループを展開したような形になっています。
 ループが展開されているので被除数が小さい場合には上位桁の演算をスキップするショートカット方式を最初に思いつきました。
 ショートカットすることで

    0~39なら -54clk
   40~99なら+18clk

となり平均すれば-10.8clk分速くなりそうです。

最初に思いついたショートカット方式
; +++ my first method +++ ; when 40..99 0..39 78 my1_10: ld a,b ; input value 4 4 21 0028 ld hl, 10 SHL 2 ; 10 10 BD cp l ; add 4 4 38 18 jr c,my1_2 ; add 7 12 2E A0 ld l,10 SHL 4 ; add 7 BD cp l ; 0a0h ; 4 38 03 jr c,my1_0 ; 7 12 ave 15 95 sub l ; 4 26 10 ld h,16 ; 7 CB 0D my1_0: rrc l ; 4 BD cp l ; 050h ; 4 38 03 jr c,my1_1 ; 7 12 ave 15.5 95 sub l ; 4 CB DC set 3,h ; 8 CB 2D my1_1: sra l ; 4 BD cp l ; 028h ; 4 38 03 jr c,my1_2 ; 7 12 ave 15.5 95 sub l ; 4 CB D4 set 2,h ; 8 CB 2D my1_2: sra l ; 4 4 BD cp l ; 014h ; 4 4 38 03 jr c,my1_3 ; 7 12 ave 15.5 15.5 95 sub l ; 4 4 CB CC set 1,h ; 8 8 CB 2D my1_3: sra l ; 4 4 BD cp l ; 00ah ; 4 4 38 03 jr c,my1_4 ; 7 12 ave 15.5 15.5 95 sub l ; 4 CB C4 set 0,h ; 8 C9 my1_4: ret


 しかし、既に下記のバイナリサーチのような処理案(「引き放し法」と言うそうです)が出ていて処理内容が複雑そうですが確かに速そうです。

引き放し法
; +++ twitter's solution +++ DIV10: 78 LD A,B ; 4 D6 50 SUB 80 ; 7 38 15 JR C,ADD40 ; 7,12 26 08 LD H,8 ; 7 D6 28 SUB 40 ; 7 38 15 JR C,ADD20 ; 7,12 SUB20: CB D4 SET 2,H ; 8 D6 14 SUB 20 ; 7 38 13 JR C,ADD10 ; 7,12 SUB10: CB CC SET 1,H ; 8 D6 0A SUB 10 ; 7 38 11 JR C,NEG_EX ; 7,12 POS_EX: CB C4 SET 0,H ; 8 C9 RET ; 10 ADD40: 26 00 LD H,0 ; 7 C6 28 ADD A,40 ; 7 38 EB JR C,SUB20 ; 7,12 ADD20: C6 14 ADD A,20 ; 7 38 ED JR C,SUB10 ; 7,12 ADD10: C6 0A ADD A,10 ; 7 38 EF JR C,POS_EX ; 7,12 NEG_EX: C6 0A ADD A,10 ; 7 C9 RET ; 10


 ぱっと見は処理内容が解り辛いですが、10 x (2のべき乗) の大きい方(80)から引いていき(キャリが出たら次は足す)、10未満になったら終了するような処理が見えてきます。
 最初に書いた処理例では比較(CP)で確認後減算(SUB)していますが、比較による確認無しに減算し、マイナスになっても元に戻さず(だから「引き放し法」)次に半分の値を足すことで結果として半分の値を引いたのと等価になるというものです。

 しかし、入力値の条件が100未満なので80を引けた場合、直後に40を引いているのは無駄なように見えます。
 80を引いた後は0..19の範囲内なので10を引けるかのチェックをした方が早く収束するはずなので改善してみたものが下の処理です。

引き放し法の改善案
; +++ my improve method +++ MDIV10: 78 LD A,B ; 4 D6 50 SUB 80 ; 7 38 14 JR C,MADD40 ; 7,12 26 08 LD H,8 ; 7 ; SUB 40 ; 7 del ; JR C,ADD20 ; 7,12 del C3 0512 JP MS10 ; 10 add MSUB20: CB D4 SET 2,H ; 8 D6 14 SUB 20 ; 7 38 13 JR C,MADD10 ; 7,12 MSUB10: CB CC SET 1,H ; 8 D6 0A MS10: SUB 10 ; 7 38 11 JR C,MNEG_EX ; 7,12 MPOS_EX: CB C4 SET 0,H ; 8 C9 RET ; 10 MADD40: 26 00 LD H,0 ; 7 C6 28 ADD A,40 ; 7 38 EB JR C,MSUB20 ; 7,12 MADD20: C6 14 ADD A,20 ; 7 38 ED JR C,MSUB10 ; 7,12 MADD10: C6 0A ADD A,10 ; 7 38 EF JR C,MPOS_EX ; 7,12 MNEG_EX: C6 0A ADD A,10 ; 7 C9 RET ; 10


 発想を変えて、お題がZ80なのでBCD関連の命令をうまく利用できないか考えてみた結果が次の処理です。
★2019/07/13 追記 {
 思考の順番としては100要素の回答テーブルの各要素の規則性は単純なのでもっと短いテーブルにできないものか?という発想から考えた結果として上位ニブルに対するBCDテーブルに行き着きました。
}
 RRD命令のクロック数が多いのが気になりますがこれも結構速そうですね^^

 気合を入れれば平均クロック数を算出して上の処理と比較できますが夜の作業としては他の作業をしたい(上の処理は分岐確率が50%でないところがあるので更にややこしい)

 今時のマイコンであれば統合開発環境内で実行時のクロック数はシミュレータですぐに確認できるのですが、Z80のシミュレータで実行クロック数を確認できるものを少し探しましたが見当たりませんでした。
 0..99をループで回し開始/終了時にI/O命令を実行してロジアナで確認することはある程度容易にできますが、それもやぼったいので、クロック数をモニタできるシミュレータが見つかったら比較してみたいと思います。

現時点で最新の高速処理案
; +++ my method +++ mydiv: 21 0707 ld hl,wrk ; 10 70 ld (hl),b ; 7 set input value AF xor a ; 4 ED 67 rrd ; 18 6E ld l,(hl) ; 7 set high nible 86 add a,(hl) ; 7 add high nible's BCD to low nible 27 daa ; 4 BCD B7 or a ; 4 clear flag 27 daa ; 4 BCD(for high nible) 2E 07 ld l,wrk and 00ffh ; 7 77 ld (hl),a ; 7 AF xor a ; 4 ED 67 rrd ; 18 66 ld h,(hl) ; 7 C9 ret ; 10 org ($ + 255) and 0ff00h ; BCD 00 tbl: db 000h ; 00h 16 db 016h ; 10h 32 db 032h ; 20h 48 db 048h ; 30h 64 db 064h ; 40h 80 db 080h ; 50h 96 db 096h ; 60h wrk: ds 1


★2019/08/01 追記
 このブログの読者にはあまり人気が無かったこのページのアクセスが最近多くなりました。

yasuokaの日記: Z80における定数10の除算は、商と余りのどちらを先に求めるべきか

関連の書込み記事からのアクセスのおかげのようです^^

 51/512倍することで商を出す手法で末尾に ret を加えると108clk(ステート)で上記のもの(118clk)より高速です(上記のものもRRD命令部をシフト化して数clkの短縮は可能ですが)。

 今回の問題では入力範囲が0..99なのでこのような分数演算の手法では誤差を1%未満にする必要があります。私も13/128でやってみたところ誤差が1%以上なので回答が途中からズレた結果になりました。
 また、整数演算なので誤差が1%未満であっても誤差の範囲内の変動で回答が影響を受けないように inc a で微調整されています。

 普通に考えると16bitでシフトしたいところですが、なんと8bitのシフトや演算で処理を完結しています・・すばらしい!

 私も新たなアイディアを思いついたら追記したいと思います。


★2019/08/02 追記
 アイディアが浮かんだので試してみた結果をメモしておきます。
 今回、複数のアイディアを盛り込み、結果として上記の分数方式(108clk)より4clk短縮(104clk)できました。(^^)/
★2019/08/03 変更 {
yasuokaの日記: Re:Z80における定数10の除算は、商と余りのどちらを先に求めるべきかの記事がアップされ剰余を先に求める方法で103clk(最後にretを追加した場合)達成とのこと。
 今回追記した処理に関してもまだ少しのりしろがあったので一部見直し103clkになりました^^
}

 今回適用した主要なアイディアとしては、少数演算の使用と上位/下位のnibleを逆転することによる4bitシフト処理の省略です。
 後者についてはピンと来ないかも知れませんが以下に概要を説明します。

  1. 小数点で処理
     分数方式だと前回記載したように誤差が1%未満の条件で分子と分母の組合せを色々考えることになりますが、0.1を2進数で表現し、その乗算値を求めればいいのでは?と考えました。
     小数点であれば分数のように組合せを考える必要が無いし、誤差は桁数に依存し一意に決まります。
     また、シフトが一方向なので往復するようなシフト処理が発生しません。

     下表に示すように 0.1を2進数で表すと2進数の循環小数になります。
     下表の1~9(橙色部分)の合計は 0.099609 なのでここまで算出すれば1%未満という条件を満たす誤差範囲になるはずです。


  2. 小数点の桁のアサイン
     速度優先で計算は8bitで行うので8bit分の有効桁数を維持するために表中のNo.1~3のゼロの部分は省略してNo4をMSBに割り振ります。
     この場合、16倍したことになるので最終的に上位nibleに結果が求まることになり、結果を取り出すために4回のシフト処理が必要になりますが、この問題は後述の対策で解決します。

  3. 循環小数の求め方
     表中のNo4と5の合計を求め、1/16倍(4bitシフト)すればNo8と9の合計になります。

  4. 上位/下位ニブルの逆転処理
     上記の循環小数のための4bitシフトでRRCAを使用することで、上位のnibleと下位のnibleが入代ります。
     ここでLSB側のnibleの方を上位、MSB側のnibleを下位と考えるとシフト前の値がシフト後の値の1/16と考えることができます。
     また、0.1倍した結果がLSB側のnibleに入るようになるので、前述した結果を取り出す際の4回のシフト処理が不要になります。

  5. 加算部の処理
     表中のNo.4,5にNo.8,9を加算する際は、MSB側の下位nibleから上位nibleへ桁上り(キャリー)が発生するか否かが必要な情報であり、キャリーが発生した場合、LSB側の上位nibleに反映するために、No.8,9を加算後にキャリ付き加算(ADC)を実行しています。

  6. 剰余の計算
     商が求まったら、商の10倍を入力値から減算して余りを算出しています。


小数点で0.1倍した改善処理
      ; +++ my method2 +++ 78 MYDEV2: LD A,B ; 4 F6 01 OR 1 ; 7 1F RRA ; 4 88 ADC A,B ; 4 4F LD C,A ; 4 0F RRCA ; 4 0F RRCA ; 4 0F RRCA ; 4 0F RRCA ; 4 5F LD E,A ; 4 81 ADD A,C ; 4 3E 00 LD A,0 ; 7 8B ADC A,E ; 4 E6 0F AND 0FH ; 7 67 LD H,A ; 4 87 ADD A,A ; 4 87 ADD A,A ; 4 84 ADD A,H ; 4 2F CPL ; 4 07 RLCA ; 4 88 ADC A,B ; 4 C9 RET ; 10


★2019/08/07 追記
 分数方式についての考察を少し書いてみます。

 初めに前回の小数点方式について動作を判り易く図解してみます。
 下図で一番上に書いてある「x 1.5」の行は被除数を1.5倍したものです。
 0.1を2進数で表すと上記のように「0001100110011・・・」の循環小数になるので「1.5倍」の数を4ビットずつシフトしながら無限回並べたものの合計になります。

 上の小数点方式の説明で「上位/下位ニブルの逆転処理」と書いた部分でキャリーを足している対象は正確に言うと下図の「整数部分」になります。

小数点方式の処理イメージ図


 因みに4bit周期の循環小数なのでうまくやれば誤差無しに計算可能で(循環部で桁上りした場合、下の桁からも必ず桁上りしてくるので)、小数点部分を正確に求められれば、小数点の上位3bitの2倍と8倍を足すことで余りの値も計算結果から求められるはずなのですが、
  • 8bitでは処理が難しい
    シフトする塊が被除数の3倍なので桁落ちせずに計算するには 99 x 3 = 297 まで扱う必要があり、1バイト処理では難しい
  • 小数部からの余りの取り出し処理
    現状の商から余りを求める部分と比較し大きな効果が出るとは思えない。

の理由から断念しました。

 話を分数方式に戻すと分母が2のべき乗の場合、分子のビットパターンは結局上記の0.1の循環小数と同様になり(分母が2のべき乗なら分子は0.1の循環小数のパターンをシフトしたものの近似になるのは当然)、処理内容は前回書いた小数点方式とほぼ同様になります。

 試しに 25.5 / 256(1バイトなので判り易い)を例にすると下図のように1.5倍したものを RLCA で4回左シフトし、整数部は下位4bitに折り返した形になります(折り返し部分は不要なのですが、上の小数点方式の観点で見れば、演算精度を上げる方向に作用するのでワザワザ削除する必要はない)。
 シフト前とシフト後の値の合計を取り、発生したキャリーを整数部に加える処理となり、前回書いた小数点方式と処理内容は同じになります。
 小数点方式も同様ですが(ウルトラCのアイディアがあれば)循環小数点部分の処理の高速化については検討の余地があるかもしれません。

分数方式(25.5/256)の処理イメージ図


★2019/09/16 追記
 「Z80における2進→BCD変換」というお題を見つけたのでWeb上のMSX環境で凸撃兵さんが作られたのものを改造してMSXPen環境で確認してみました。


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

3チップ構成Pic24CPMマイコン(その7)GAMEコンパイラ [Z80]

 ネットで久々にGAME言語ネタを見つけた^^ので平成最後のカキコはGAME言語ネタです。
 Old68funさんが6809 / 6800とFLEXのブログで書かれている「SBC8080でGAME80がようやく動作」の記事です。TK-80BS用のGAME80インタプリタをSBC8080上で動かされています。

 TK-80BSと言えば中島さんにより開発されたGAME80コンパイラが発表された環境であり、Jun's Homepageの「GAME80コンパイラ解説 (2003/08/05)」に解説とともにソースが掲載されています。

 久々にGAMEコンパイラを動かしてみたいものですねぇ^^

 最初にGAME80をCP/M-80に移植してみました。GAMEインタプリタ自体はコンソール入出力関連の処理をハード環境に合わせて変更すれば動作するのでCP/M-80用に先頭にコールドスタートへのジャンプ命令を追加し、末尾にコンソール関連の処理を追加しました。使用した環境はマイクロソフトのMACRO80/LINK80です。
 全体をリロケートしなかったのは上記のコンパイラを動かしてみたかったからです。

GAMEインタプリタのCP/M-80へのインプリメント
;******************************** ; GAME interpreter ; TK-80BS version on CP/M-80 ; 2019/05/01 ;******************************** 8600 PRGST EQU 8600H ; program start addres CBFF RAMEND EQU 0CBFFH ; for CP/M-80 64K 001B K_STOP EQU 01BH ; stop key 0020 K_PAUSE EQU ' ' ; pause key 8E00 SRCSTA EQU 08E00H ; sorce start addres 8DC4 STACK2 EQU 08DC4H ; second stack 8D42 Y8D42 EQU 08D42H 8D46 Y8D46 EQU 08D46H 8D4A Y8D4A EQU 08D4AH 8D4B Y8D4B EQU 08D4BH 8D4E Y8D4E EQU 08D4EH 8D50 Y8D50 EQU 08D50H 8D52 Y8D52 EQU 08D52H 8D58 Y8D58 EQU 08D58H 8D5A Y8D5A EQU 08D5AH 8D68 Y8D68 EQU 08D68H 8D6A Y8D6A EQU 08D6AH 8D70 Y8D70 EQU 08D70H 8D72 Y8D72 EQU 08D72H 8D74 Y8D74 EQU 08D74H 8D76 Y8D76 EQU 08D76H 8D77 Y8D77 EQU 08D77H 8D7E Y8D7E EQU 08D7EH 8D88 Y8D88 EQU 08D88H 8D8A Y8D8A EQU 08D8AH .Z80 0000' ASEG ORG 00100H 0100 C3 8600 JP START ORG PRGST 8600 C3 8606 START: JP A8606 ; cold start 8603 C3 8611 JP A8611 ; hot start ; 8606 21 8E00 A8606: LD HL,SRCSTA 8609 22 8D7E LD (Y8D7E),HL 860C 22 8D50 LD (Y8D50),HL 860F 36 FF LD (HL),0FFH 8611 21 CBFF A8611: LD HL,RAMEND 8614 22 8D58 LD (Y8D58),HL   ~~~ 途中省略 ~~~ 8C7B C2 8C4E JP NZ,A8C4E 8C7E 36 00 LD (HL),000H 8C80 21 8D8A LD HL,Y8D8A 8C83 3E 0D A8C83: LD A,00DH 8C85 CD 8C99 CALL PUTCH 8C88 3E 0A LD A,00AH 8C8A C3 8C99 JP PUTCH ; 8C8D 0D 0A 2A 52 T8C8D: DB 0DH,0AH,'*READY',00H 8C91 45 41 44 59 8C95 00 ;*** added by skyriver 2019/04/30 *** 8C96 CD 8CA7 GETCH: CALL CONIN 8C99 C3 8CB1 PUTCH: JP CONOUT 8C9C E5 KBHIT: PUSH HL 8C9D D5 PUSH DE 8C9E 1E 03 LD E,03H ; CONST 8CA0 CD 8CBE CALL GOBIOS 8CA3 0F RRCA ; if exist then set carry 8CA4 D1 POP DE 8CA5 E1 POP HL 8CA6 C9 RET 8CA7 E5 CONIN: PUSH HL 8CA8 D5 PUSH DE 8CA9 1E 06 LD E,06H ; CONIN 8CAB CD 8CBE CALL GOBIOS 8CAE D1 POP DE 8CAF E1 POP HL 8CB0 C9 RET 8CB1 E5 CONOUT: PUSH HL 8CB2 D5 PUSH DE 8CB3 F5 PUSH AF 8CB4 1E 09 LD E,09H ; CONOUT 8CB6 4F LD C,A 8CB7 CD 8CBE CALL GOBIOS 8CBA F1 POP AF 8CBB D1 POP DE 8CBC E1 POP HL 8CBD C9 RET 8CBE 2A 0001 GOBIOS: LD HL,(0001H) ;WBOOT addres 8CC1 16 00 LD D,0 8CC3 19 ADD HL,DE 8CC4 E9 JP (HL) END

★2019/04/30
 連続表示中にスペースでポーズできなかったので修正
 オリジナルは'!'キーで中断ですが、ESCで中断するように変更
★2019/05/01
 コール先やワークのアドレスをラベル名に変更


 GAME80インタプリタがCP/M-80上で動いたので上記のGAME80コンパイラの解説ブログページからコンパイラのソースを頂いて動かしてみます。
 ブログにも「リストは昔に記事を読んで打ち込んだもので、実行して確認していません。ミスがあるかもしれませんが、その場合はご容赦下さい。」と書かれているように実際に動かそうとすると数ヶ所修正が必要です(手元にエンサイクロペディアアスキーの該当ページのコピーがあったので突合チェックした)。
 オリジナルとの差分を以下にメモしておきます。

No.修正箇所
1 225 ?(5)=V .=5 ??=A .=5 ??=G /
2 3270 ;=H="+" #=3300
3 3410 ;=G:0)="=" G=G+1 !=4000 H=$FA #=3600
4 5620 A:0)=$CD A=A+3 A(-1)=A A:0)=$D5 A=A+1


 これで何十年かぶりにGAMEコンパイラのコンパイルぶりを見れる(私が当時動かしていたのはTRS-80用のGAME-Z80を自作マイコンに移植したものだったのでTK-80BSのGAMEは初体験)と思いきやコンパイルしたコードが全く動きません・・・
 コンパイラのソースを確認したところ、文字列出力部で$FA52をコールしているコードを生成しています。
 そこで1文字出力ルーチンを使ったループ処理のコードを生成するように変更し、機種依存性を無くしました。

オリジナル 2410 @=(G:I-1)=""") A(0)=A+I
2420 G=G+I A=A+I A:0)=$3E
2430 A:1)=I-2 A:2)=$32 A=A+5 A(-1)=$847A
2440 A:0)=$21 A=A+3 A(-1)=Q
2445 A:0)=$22 A=A+3 A(-1)=$847B
2450 A:0)=$CD A=A+3 A(-1)=$FA52
2460 #=700
変更後 2410 @=(G:I-1)=""") A(0)=A+I
2420 G=G+I A=A+I A:0)=$06
2430 A:1)=I-2 A:2)=$21 A=A+3
2432 A(0)=Q A(1)=$CD7E A(2)=$8C99
2433 A(3)=$0523 A=A+11 A:-3)=$C2 A(-1)=A-9
2460 #=700


 ソースを見ると生成するコードが何となく判ると思いますが、文字列生成処理のコンパイル結果を確認してみます。
 サンプルソースとしては単に"Hello"と出力するだけのものを使用しました。

文字列出力処理のコンパイル例
:0 10 "Hello" *READY :=$8E00 *READY :#=1 GAME PROGRAM FROM:$B000 START ADRRESS:$4000 WORK AREA FROM:$100 10 4000 B000 10 4000 B000 PROGRAM SIZE: 25(4000-4018) END *READY :>=0 b>


 0x4000以降にコンパイルされたコードが書かれているので、一旦GAMEを抜けてCP/Mのディバッガ(ZSID)を起動し、確認しました。
 ZSIDDはZSIDに小文字シンボル対応や1行ダンプ等のパッチをあてたものです。

生成されたコード
b>a:zsidd ZSID VERS 1.4 #l4000,4018 4000 JP 4008 4003 LD C,B 4004 LD H,L 4005 LD L,H 4006 LD L,H 4007 LD L,A 4008 LD B,05 400A LD HL,4003 400D LD A,(HL) 400E CALL 8C99 4011 INC HL 4012 DEC B 4013 JP NZ,400D 4016 JP 8603 4019 #d4000 4000: C3 08 40 48 65 6C 6C 6F 06 05 21 03 40 7E CD 99 ..@Hello..!.@~.. 4010: 8C 23 05 C2 0D 40 C3 03 86 00 00 00 00 00 00 00 .#...@.......... 4020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 4030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 4040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 4050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 4060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 4070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 4080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 4090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 40A0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ #


 想定通りのコードが生成されていますね。当然実行結果もokでした。
 当初の目的であるGAMEコンパイラをインタプリタ動作でコンパイラ自身をコンパイルし、コンパイルされたコンパイラで更にコンパイラをコンパイルすることも問題なくできました(^^)/

 GAME言語のインタプリタやコンパイラのソースを公開していいか判らないのでCP/M-80に移植したバイナリだけ置いておきます。
 冒頭で紹介したブログと本ブログを参照すれば、CP/M環境でGAMEコンパイラのセルフコンパイル(通常とは違う意味での「セルフ」)まで試せると思います。

  • GameOnCpm80_20190518_003.zip
    ★2019/05/18 Ver0.03
     コンパイル時の表示をGAME-Z80版のように見易くした
    ★2019/05/09 Ver0.02
     スペースキーでのポーズ及びESCでのブレーク時にエコーバックしないようにした
    ★2019/05/06 Ver0.01
     コンパイラを0200H~に包括した( >=$200 でコンパイラ起動)
     版数特定ができるようにVer付きオープニングメッセージを表示するようにした
    ★2019/05/04
     起動時にGAME本体を8600Hへ転送するようにして起動時間短縮
    ★2019/05/01
     コンソール関連処理を2バイト軽量化しました(機能的には変わらず)


★2019/04/30 追記
 twitterにポストした動画付きコメントを貼り付けておきます。


★2019/05/02 追記
 コメントに書いたように文字列出力処理部のコンパイルでインタプリタ内の'\0'で終端された文字列の出力処理である0x8785を呼び出すようにして生成コードを短縮し、更にキーチェックだけを行う長さゼロの文字列出力 "" に対応してみました。

 GAMEコンパイラの修正箇所は下記になります。ソース自体も短くなりました。

オリジナル 2300 A:0)=$C3 A=A+1 I=2
2400 Q=A+2 @ A:I)=G:I-1) I=I+1
2410 @=(G:I-1)=""") A(0)=A+I
2420 G=G+I A=A+I A:0)=$3E
2430 A:1)=I-2 A:2)=$32 A=A+5 A(-1)=$847A
2440 A:0)=$21 A=A+3 A(-1)=Q
2445 A:0)=$22 A=A+3 A(-1)=$847B
2450 A:0)=$CD A=A+3 A(-1)=$FA52
2460 #=700
変更後 2300 A:0)=$C3 Q=A+1 A=A+2
2400 @ G=G+1 A=A+1 A:0)=G:0) @=(G:0)=""")
2410 G=G+1 A(0)=$2100 A(1)=Q+2 Q(0)=A+1
2420 A=A+7 A:-3)=$CD A(-1)=$8785
2460 #=700


 それではコンパイル結果を確認してみます。

コンパイル結果確認
*READY :=$B000 *READY :&=0 *READY :10 "Hello" :20 "" :0 10 "Hello" 20 "" *READY :=$8E00 *READY :#=1 GAME PROGRAM FROM:$B000 START ADRRESS:$4000 WORK AREA FROM:$100 10 4000 B000 20 400F B00B 10 4000 B000 20 400F B00B PROGRAM SIZE: 28(4000-401B) END *READY :>=$4000 Hello *READY :>=0 b>a:zsidd ZSID VERS 1.4 #l4000,401b 4000 JP 4009 4003 LD C,B 4004 LD H,L 4005 LD L,H 4006 LD L,H 4007 LD L,A 4008 NOP 4009 LD HL,4003 400C CALL 8785 400F JP 4013 4012 NOP 4013 LD HL,4012 4016 CALL 8785 4019 JP 8603 401C #d4000,401f 4000: C3 09 40 48 65 6C 6C 6F 00 21 03 40 CD 85 87 C3 ..@Hello.!.@.... 4010: 13 40 00 21 12 40 CD 85 87 C3 03 86 00 00 00 00 .@.!.@.......... #g0 b>


 想定通りですね^^
 GAMEコンパイラはコンパイラ自身をコンパイルし、拡張していけるのでマイコン環境でC言語等の開発環境が無かった時代にブートストラップ方式で言語仕様を拡張することでいくつかのコンパイラが作られました。独自言語が色々出てきて楽しい時代でしたね。


★2019/05/03 追記
 インタープリタ ⇒ インタプリタ に変更


★2019/05/03 追記
 CP/MでGAME言語が動くようになったので以前、「PIC24FJ64GAでGAME言語(その5)」で記載した迷路生成/探索のプログラムを動かしてみました。

GAME80ではPIC24FJ版GAMEとの相違点として
  • &(AND)演算子が無い
  • 配列のインデックス部で計算ができない配列が参照できない※1
  • 乱数 'N の範囲が0~N-1ではなく、1~Nである
  • タイマ管理変数の '¥' が無い

等がありましたが容易に移植できました。
★2019/05/03 追記 {
※1 インデックス部で計算はできるようです。下記の事象が発生しました。
W=B:P+D(Z)) ⇒ NG
C=D(Z) W=B:P+C) ⇒ OK
★2019/05/09 追記 {配列値評価時に8D70HにHLレジスタを保存しているため}
}
 ソースを以下に貼りました。TeraTermのWindowサイズは80カラム×32行で動かしてください(と言ってもTeraTerm接続で動くCP/M-80環境を持っている人は希少だとは思いますが)。

Maze for GAME80
1' Maze for GAME80 2019/05/03 by skyriver 2 S=78 T=30 #=100 8' move cursor 9 Y=P/S X=%(0) 10 $=$1B "[" ?=Y+1 ";" ?=X+1 "H" ] 18' set wall 19 Y=P/S X=%(0) 20 B:Y*S+X)=8 ] 29' wait 30 C=1,100 "" @=C+1 ] 100 D=&+1 B=D+8 '=157 102 D(0)=1 D(1)=S D(2)=-1 D(3)=-S U=S*T-1 110 @ 119' create maze 120 I=S*T @ I=I-1 B:I)=0 @=(I=0) 130 Y=0,T 140 X=0 !=20 150 @=Y+1 160 X=0,S 170 Y=0 !=20 Y=T !=20 180 @=X+1 190 G=2,T-2 200 F=2,S-2 210 X=F Y=G !=20 P=S*G+F 220 @ R=P+D('(4-(G>2))-1) @=(B:R)=0) 230 P=R !=19 240 @=F+2 250 @=G+2 259' display maze 260 $=$1B "[2J" X=0 Y=0 !=10 $=$1B "[36m" 270 Y=0,T 280 X=0,S 290 Z=" " ;=B:Y*S+X) Z="#" 300 $=Z 310 @=X+1 320 / 330 @=Y+1 340 $=$1B "[m" 350 X=S-1 Y=T-1 !=10 $="@" 360 !=30 369' explore 370 P=S+1 Z=0 380 @ 390 !=9 "+" $=8 Z=%((Z+2)/4) 400 @ "" 410 Z=%((Z+1)/4) R=P+D(Z) 420 @=(B:R)<8) 430 P=R B:P)=B:P)+1 440 !=30 450 @=(P=U) 459' best route 460 $=$1B "[43m" P=S+1 470 @ 480 !=9 "+" $=8 490 N=0 E=8 500 Z=0,3 510 C=P+D(Z) W=B:C) 520 ;=(W>0)*(W<E) E=W N=Z 530 @=Z+1 540 B:P)=7 P=P+D(N) 550 !=30 560 @=(P=U) 570 $=$1B "[m" 580 @=(0)


 参考にtwitterにポストした動画付きコメントを貼ります。


★2019/05/10 追記
 GAME80のソースのリナンバプログラムを作ったので貼っておきます。
 GAME68等でも動作すると思います。

renumber for GAME80
1' renumber for GAME Ver0.03 2019/05/11 by skyriver 2 "src address " S=? 3 "dst address " D=? 4 "line start " B=? 5 "line step " E=? 6 Z=&+1 TBL=Z+6 7 P=S IDX=0 8 @ 9 TBL(IDX)=P:0)*256+P:1) IDX=IDX+1 ;=%(IDX/10)=0 "*" 10 P=P+2 @ P=P+1 @=(P:-1)=0) 11 @=(P:0)>=$F0) 12 P=S IDX=0 13 @ 14 NEW=E*IDX+B 15 D:0)=NEW/256 D:1)=NEW D=D+2 P=P+2 16 / ?(5)=TBL(IDX) " :" ?(5)=NEW 17 ;=P:0)<>" " @ D:0)=P:0) D=D+1 P=P+1 @=(P:0)=0) 18 @ 19 ;=(P:0)=""")*(P:1)=""")*(P:2)=""") D:0)=""" D=D+1 P=P+1 20 ;=(P:0)=""") @ D:0)=P:0) D=D+1 P=P+1 @=(P:0)=""") 21 ;=(P:0)="#")+(P:0)="!") !=30 22 D:0)=P:0) 23 D=D+1 P=P+1 24 @=(P:-1)=0) 25 IDX=IDX+1 26 @=(P:0)>=$F0) 27 D:0)=P:0) 28 #=-1 29' goto,gosub 30 ;=(P:1)<>"=")+(P:2)="-") ] 31 D:0)=P:0) D:1)="=" D=D+2 P=P+2 32 LINE=0 @ LINE=LINE*10+(P:0)-"0") P=P+1 @=((P:0)<"0")+(P:0)>"9")) 33 " " $=D:-2) "=" ?=LINE " -> " 34 F=-1 @ F=F+1 @=(TBL(F)>=LINE) 35 LINE=E*F+B X=Z-1 ?=LINE 36 @ X=X+1 LINE=LINE/10 X:0)=%(0)+"0" @=(LINE=0) 37 @ D:0)=X:0) D=D+1 X=X-1 @=(X<Z) 38 ]

★2019/05/11 追記
 Ver 0.03 """処理部簡略化
 Ver 0.02 行短縮しました

[TOP] [ 前へ ] 連載記事 [ 次へ ]
nice!(2)  コメント(11) 
共通テーマ:趣味・カルチャー

3チップ構成Pic24CPMマイコン(その6)自作スクリーンエディタ [Z80]

 久々にCP/M-80を使ったらWordStarの2ストローク方式のコマンドは非常に気に入っているのですが、起動のたびにヘルプレベルを変更してエディット対象の表示領域を広げたり、ページ境目の表示を消す等の操作が必要なのでちょっと面倒です・・

 そこで「3チップ構成68Kマイコンの構想(その14)スクリーンエディタの開発」の記事で紹介したCP/M-68K用に開発したスクリーンエディタのsked(Simple Kitten EDitor)をCP/M-80に移植しました。
 使用したコンパイラ環境はHI-TECH Cです。CP/M-68KのCコンパイラよりもHI-TECH Cの方がANCI規格にも準拠していて使い易です。

 本エディタのコマンドは下記のヘルプ表示時のキャプチャで判るようにWordStarライクにしているので(私にとっては)使い易いです^^
 画面制御のエスケープシーケンスはANSIに対応しているのでTeraTermの標準設定(VT100)で使用できます(画面サイズは80カラム x 24行)。

sked for CP/M-80


 下記からCP/M-80用のskedをダウンロード可能です。(商用利用以外であれば自由に使用できます)

★2019/04/08 Ver0.01f 新規ファイルで途中でセーブ後終了でファイルが消える問題に対処^^;
★2019/04/08 Ver0.01e 新規ファイル中断時に新規ファイルが残らないようにした
★2019/04/06 Ver0.01d ファイル内のコントロールコードを表示
★2019/03/18 Ver0.01c 起動時のメモリ確保ロジックを改善

[TOP] [ 前へ ] 連載記事 [ 次へ ]

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

8080/Z80 リロケータブルコードの実験 [Z80]

 Z80は8080には無かった相対ジャンプ命令が追加されましたが、6809と違い相対コールができません。なのでOS9のようなポジションインディペンデントコード(PIC)なモジュールが書きづらいです。
 昔から議論されていることではありますが、PICは面白いのか?wちょっと考えてみました。

 PICなコール(相対コール)をソフトで実現するためにはプログラムカウンタ(PC)の値を参照する必要があります。
 Z80の命令セット上、コール時のスタックからPCの値を拾えます(それ以外にPCの値を参照する方法はないと思う)

 PCの値さえ参照できれば相対値をコール時のコードに埋め込めば相対コールが実現できます。下記が相対コールのコード例です。'+'が付いている行はマクロ展開で生成された命令です。
Z80 relocatable call
MACRO-80 3.44 09-Dec-81 PAGE 1 ;********************************* ; 8080/Z80 relocatable call test ; 2018/12/24 by skyriver ;********************************* 0028 RELRST equ 028h ; using RST for reloc code ; *** reloc call macro *** rcall macro dest ld de,dest - $ - 4 rst RELRST endm .z80 0000' aseg org RELRST 0028 E1 pop hl 0029 E5 push hl 002A 19 add hl,de 002B E9 jp (hl) org 0100h 0100 test: rcall proc 0100 11 0003 + ld de,proc - $ - 4 0103 EF + rst RELRST 0104 C3 0104 jp $ 0107 00 proc: nop 0108 C9 ret end MACRO-80 3.44 09-Dec-81 PAGE S Macros: RCALL Symbols: 0107 PROC 0028 RELRST 0100 TEST No Fatal error(s)


 机上検討段階でほとんど動作検証ができてしまっているようなものですが実機での確認結果が下記です。ZSIDDはZSIDに小文字シンボル対応と1行ダンプ表示のパッチを適用したものです。
参照)
3チップ構成Pic24CPMマイコン(その4)ZSIDで小文字のシンボルを使う方法その2

 下記のダンプ表示のようにZSIDがRST 38Hを使っているので上のソースから生成したHEXファイルをZSIDではロードできないためSコマンドで手入力しています。

relocatable call code test
a>zsidd ZSID VERS 1.4 #s28 0028 00 e1 0029 00 e5 002A 00 19 002B 00 e9 002C 00 . #s100 0100 01 11 0101 F9 03 0102 21 00 0103 C3 ef 0104 3D c3 0105 01 04 0106 43 01 0107 4F 00 0108 50 c9 0109 59 . #l28 0028 POP HL 0029 PUSH HL 002A ADD HL,DE 002B JP (HL) 002C NOP 002D NOP 002E NOP 002F NOP 0030 NOP 0031 NOP 0032 NOP #l100 0100 LD DE,0003 0103 RST 28H 0104 JP 0104 0107 NOP 0108 RET 0109 LD E,C 010A LD D,D 010B LD C,C 010C LD B,A 010D LD C,B 010E LD D,H #d0 0000: C3 03 FA 00 00 C3 00 CA 00 00 00 00 00 00 00 00 ................ 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0020: 00 00 00 00 00 00 00 00 E1 E5 19 E9 00 00 00 00 ................ 0030: 00 00 00 00 00 00 00 00 C3 86 D8 00 00 00 00 00 ................ 0040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0050: 00 00 00 00 00 00 00 00 00 00 00 00 00 20 20 20 ............. 0060: 20 20 20 20 20 20 20 20 00 00 00 50 00 20 20 20 ...P. 0070: 20 20 20 20 20 20 20 20 00 00 00 00 00 FB EA FA ........ 0080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00A0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ #x ----- A=00 B=0000 D=0000 H=0000 S=0100 P=0100 ----- A'00 B'0000 D'0000 H'0000 X=0000 Y=0000 LD DE,0003 #t ----- A=00 B=0000 D=0000 H=0000 S=0100 P=0100 ----- A'00 B'0000 D'0000 H'0000 X=0000 Y=0000 LD DE,0003 *0103 #t ----- A=00 B=0000 D=0003 H=0000 S=0100 P=0103 ----- A'00 B'0000 D'0000 H'0000 X=0000 Y=0000 RST 28H *0028 #t ----- A=00 B=0000 D=0003 H=0000 S=00FE P=0028 ----- A'00 B'0000 D'0000 H'0000 X=0000 Y=0000 POP HL *0029 #t ----- A=00 B=0000 D=0003 H=0104 S=0100 P=0029 ----- A'00 B'0000 D'0000 H'0000 X=0000 Y=0000 PUSH HL *002A #t ----- A=00 B=0000 D=0003 H=0104 S=00FE P=002A ----- A'00 B'0000 D'0000 H'0000 X=0000 Y=0000 ADD HL,DE *002B #t ----- A=00 B=0000 D=0003 H=0107 S=00FE P=002B ----- A'00 B'0000 D'0000 H'0000 X=0000 Y=0000 JP (HL) *0107 #t ----- A=00 B=0000 D=0003 H=0107 S=00FE P=0107 ----- A'00 B'0000 D'0000 H'0000 X=0000 Y=0000 NOP *0108 #t ----- A=00 B=0000 D=0003 H=0107 S=00FE P=0108 ----- A'00 B'0000 D'0000 H'0000 X=0000 Y=0000 RET *0104 #t ----- A=00 B=0000 D=0003 H=0107 S=0100 P=0104 ----- A'00 B'0000 D'0000 H'0000 X=0000 Y=0000 JP 0104 *0104 #t ----- A=00 B=0000 D=0003 H=0107 S=0100 P=0104 ----- A'00 B'0000 D'0000 H'0000 X=0000 Y=0000 JP 0104 *0104 #


 想定通り動作することが実機で確認できました(すぐに動かせるCP/Mマシンがあると便利ですね^^)。
 DEとHLレジスタを破壊してしまうし、コール命令が4バイトになってしまいますが、リロケータブルなコールが実現できます(必要な場面があまり思い浮かびませんがw)
 また、上記の例ではRST命令を使っていますが、固定番地へのコール命令でも同様なことが可能です(この場合rcallは6バイト長になるけど)

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

3チップ構成Pic24CPMマイコン(その5)XMODEMでファイル送受信 [Z80]

 「レトロマイコンZ80ボードの構想(その14)CP/MでのWM動作実験」の記事のコメントに書いたようにCP/Mが動作するワンボードマイコンPic24CPMとPCとのファイルのやり取りはSDカード経由では面倒なのでコンソール接続のシリアル通信を利用して XMODEM プロトコルで行うのが便利です。

 上記のコメントにも書いたようにCP/M用の XMODEM 処理のソース(アセンブリ言語)を見つけて使ってみましたが、ある程度サイズの大きな(20KBくらい以上)ファイルをパソコンへ送信する際、私の環境では送信処理がすぐに完了してしまい、うまく動きませんでした(PCからの受信は問題なく動く)

 入手したアセンブリソースを解析して動かしてみるのも面白そうですが、今回は「3チップ構成Pic24CPMマイコン(その4)ZSIDで小文字のシンボルを使う方法その2」の記事に書いたように HI-TECH C でコンパイル時に生成される 小文字を含むシンボル情報を ZSID で使えるようにする ZSID 用のパッチも作ったので HI-TECH C を使ってスクラッチで CP/M 用の XMODEM アプリを作ってみました。パソコン側は TeraTerm の XMODEM 機能を利用します。

 XMODEMのプロトコル自体は ここ に書いてあるようにシンプルなもので実装するのは簡単そうです。

今回、HI-TECH C を使ってみて感じたことは
  • 構造体/共用体に対応
     CP/MのCコンパイラの中では未対応なものが多いですが HI-TECH C では普通に使えます。今回も2バイト変数をバイトに分割する部分でstructとunionを使いました。
  • ループ処理内での"break"に未対応
     for文等のループ内でループから抜け出すための"break"が使えない^^;。switch文では当然"break"が使えます。
     "break"は自作のpicle言語でも最初は未対応でしたが、使い勝手が悪いので途中で追加しています("continue"は未対応ですがなくてもあまり困らない)。
  • ANSI準拠
     CP/M-80用のCコンパイラでは珍しく、ANSI対応していて使い易い。従ってK&Rの構文へのトランスレータも必要ありません。
    ★2018/06/26 追記
     学生のころから「リッチー&カーニハン」と覚えていたのでR&Kと書いたけどK&Rが正解みたいなので修正しました^^;
  • コンパイル速度
     小さなソースでも1分弱程度時間が掛かります。私の環境では今回作成した xmodem.cをコンパイルするのに57秒掛かりました。BDS C のコンパイル速度がうらやましいw。
     因みにpicleコンパイラのコンパイル速度はメチャ速です。
  • ライブラリ
     まだ、ほんの一部しか使っていませんが、toupper()が予期せぬ動作でしたw
    CTYPE.H(23): #define toupper(c) ((c)-'a'+'A')
  • コンパイル後のマシン語
     関数への引数はレジスタ渡しではなくIXレジスタ経由でのアクセスになるので関数中にアセンブリ言語を埋め込む際にアクセスし辛いです。グローバル変数を使った方が実行速度は速そうです。


 以前、LSI-C80の使っていたこともありますが、関数をリカーシブ対応にしない場合には関数内変数を極力レジスタに割り振るという素晴らしいコードを吐き出していました(でもコンパイル速度はかなり遅い)
 今回の XMODEM の初期バージョンではブロック送信でデータ間に隙間が空いてしまうので(シリアル通信速度は38400bps)1文字入出力処理を軽量なBIOS/BDOSコールにしたり、CRC計算をアセンブリ言語での埋め込みにし、隙間なく通信できるようになりました。

 下記は今回開発した XMODEM のソースをパソコン側から受信し、HI-TECH C でコンパイル後、実行ファイルをパソコンへ送信している操作ログです。
 Pic24CPMで使う場合は、OPEスイッチ操作により、エスケープシーケンスOFFの状態で使用してください(ONだとコンソール出力データが変換されるため)

XMODEMアプリ使用例


 尚、今回開発した CP/M-80用の xmodem のC言語ソースと実行ファイルは下記からダウンロード可能です。商用利用以外であれば自由に使用可能です。


★追記 2018/06/25
 xmodemでの転送とWordStarを一緒に使う場合等にエスケープシーケンスのON/OFF設定の変更が面倒なので xmodem 起動時の /p オプションで PUN/RDRディバイス指定可能にしました。(上記の画面キャプチャもアップデートしました)
 PIC24CPMでは /p 指定した場合でもコンソール用のシリアル通信を使いますが、PUNではエスケープシーケンスの変換処理がないので通信データの透過性が保証されます。
 また、RDRではBIOSコールの仕様上、データ有無のステータス確認ができないため、データがない場合にはゼロフラグをONにしてリターンするように拡張しています(普及率は不明ですがネット上に同様に拡張実装している例があったので採用)。


★追記 2018/06/28
 ネット上で昔の雑誌のスキャンデータがアップされているところを見つけ、1991年10月号の「MSX FUN」(表紙には「今月からディスクが付録!」と書いてある)があったので見ていたら「パソ通天国」というコーナーがあってそこに XMODEM についての解説?があった。残念ながらASCIIは一冊も無かった・・



[TOP] [ 前へ ] 連載記事 [ 次へ ]

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