SSブログ
English Version

メモリ上で実際に動くプログラム(その2) [Z80]

 メモリ上で実際に動き(移動し)通過した後にはNOP命令のみを残してひたすらメモリ上を駆け回る自走プログラムで新たな展開があったので記録しておきたいと思います。
 前回の記事で追記したブロック転送のみによる自走プログラムはアイディアは面白いのですが、前回の記事に書いたように今回の自走プログラムの条件の一つである「一回のコピーでのメモリ上の移動量はなるべく小さくする(数バイト程度)」という点では初版のものより劣っていたのでもっと小刻みに移動するものはできないか考えてみました。

 前回記事に追記したプログラムは PUSH を使わずブロック転送のみで自走プログラムを実現したので逆の発想で PUSH のみで自走プログラムができないかも試みてみます。
 これは結構難しく、できそうではあるけれどもう一歩のところでなかなかできません。
 Z80 はレジスタは多いのですが、PUSH 対象のレジスタペア数は半減しそれほど多くないので 裏レジスタと AF レジスタペアまで利用しました。

 AF レジスタペアを利用するにあたり、フラグレジスタの未使用ビットに値を設定できるのかを検索してみると「Z80のフラグレジスタについて」がヒットしましたが回答は「~~の場合もありうる」という内容で質問に対する回答(できるかできないか)が書いてありません。
 海外のネット情報では例えば「What is the purpose of the reserved/undefined bit in the flag register?」にあるように Z80 のフラグの未使用ビットには値を設定できる旨が書いてありました。

 念のためにザイログ製の Z80 を使って下記のプログラムで確認してみた結果、出力は Ok だったのでフラグの未使用ビットも値を保持できることを確認できました。

POP AF でのフラグの未使用ビットの値の確認(Z80アセンブラ)
;++++++++++++++++++++++++++++++++++++ ; check POP AF flag value ; Ver 0.01 2024/01/25 by skyriver ; Ver 0.02 2024/02/04 by skyriver ;++++++++++++++++++++++++++++++++++++ 0005 BDOS EQU 0005H 0002 COUT EQU 02H ; E<-data 0000' ASEG ORG 0100H 0100 2E 00 START: LD L,0 0102 E5 LOOP: PUSH HL 0103 F1 POP AF 0104 F5 PUSH AF 0105 D1 POP DE 0106 7B LD A,E 0107 BD CP L 0108 20 14 JR NZ,ERR 010A 2C INC L 010B 20 F5 JR NZ,LOOP 010D 21 0123 LD HL,MgOk ; +++ go through +++ 0110 7E Puts: LD A,(HL) 0111 B7 OR A 0112 C8 RET Z 0113 E5 PUSH HL 0114 0E 02 LD C,COUT 0116 5F LD E,A 0117 CD 0005 CALL BDOS 011A E1 POP HL 011B 23 INC HL 011C 18 F2 JR PUTS 011E 21 0126 ERR: LD HL,MgNg 0121 18 ED JR Puts 0123 4F 6B 00 MgOK: "Ok", 0 0126 4E 67 00 MgNg: "Ng", 0 END
※2024/02/04 improved slightly

 今回のプログラムも自身のコードを上書きするのでディバッガでのステップ動作等は使えず、机上ディバッグメインで動かしました。通常のプログラムを組む時の思考とは違う考え方が必要なので結構大変でした。普通に考えると PUSH 対象のレジスタペアが足りないのでレフレックスラジオが一つのトランジスタを高周波と低周波で2回使うように二つのレジスタペアを2回づつ使っています。この共通コードができるようにし、レジスタの使用を節約するように調整するのが難しいパズルでした。

 完成したソースを以下に示します。メモリ上の移動幅は究極の1バイトで、メモリを逆走します。このソースを見て机上で動作を追えるでしょうか? PC と SP がクロスする部分の仕掛け等を堪能してください。

完成した自走プログラム3(Z80アセンブラ)
;+++++++++++++++++++++++++++++++++++++ ; runner code on memory ; Ver 0.03 2024/01/26 by skyriver ;+++++++++++++++++++++++++++++++++++++ 0000' ASEG ORG 0100H 0100 31 0100 START: LD SP,START 0103 21 DB 21H ; LD HL,xxxx 0104 F5 PUSH AF 0105 E5 PUSH HL 0106 E5 PUSH HL 0107 F1 POP AF ; set AF 0108 21 DB 21H ; LD HL,xxxx 0109 D9 EXX 010A C5 PUSH BC 010B 11 DB 11H ; LD DE,xxxx 010C F9 LD SP,HL 010D 2B DEC HL 010E 01 DB 01H ; LD BC,xxxx 010F C5 PUSH BC 0110 D5 PUSH DE 0111 D9 EXX 0112 01 DB 01H ; LD BC,xxxx 0113 F1 DB -(HLVAL - TOP) 0114 00 NOP 0115 11 DB 11H ; LD DE,xxxx 0116 D9 EXX 0117 18 DB 18H ; JR 0118 21 012B LD HL,HLVAL 011B F3 DI 011C F9 TOP: LD SP,HL 011D 2B DEC HL 011E C5 PUSH BC ; NOP : xx 011F D5 PUSH DE ; JR : EXX 0120 D9 EXX 0121 C5 PUSH BC ; PUSH DE : PUSH BC 0122 F5 PUSH AF ; PUSH HL : PUSH AF ; ----------------------- 0123 F5 PUSH AF ; PUSH HL : PUSH AF 0124 E5 PUSH HL ; PUSH BC : EXX 0125 C5 PUSH BC ; PUSH DE : PUSH BC 0126 D5 PUSH DE ; DEC HL : LD SP,HL 0127 D9 EXX 0128 18 FE JR $ 012A 00 NOP 012B HLVAL EQU $ END


★追記 2024/01/28 {
 コード生成に必要な PUSH 回数について考察してみると
  • PUSH 命令(1バイト)で2バイトのメモリを設定できるので PUSH 命令1回につき1バイトのコードを生成できる
  • 今回の自走プログラムでは先頭の LD SP,HL と DEC HL の2バイトと末尾の JR xx と NOP の3バイト及び前進するための伸長分の1バイトの合計6バイトのコード生成が必要となる
  • PUSH 用に使用できるレジスタペアが AF BC DE の3つ(HL レジスタはスタック再設定用に使用)では不足するので更に3つの裏レジスタペアを使用する必要がある
  • 裏レジスタを使用するためには EXX を2回実行する必要があり、必要コードにこの2バイトを加えると8バイトのコード生成(8回の PUSH)が必要となる
以上から8回の PUSH が必要となるのに対して実際に PUSH 可能なレジスタペアは6つ( AF BC DE BC' DE' HL')なので生成コード中に共通部分を作り2回分の PUSH は使用済みのレジスタペアを再利用できるようにする必要があります。
 しかし、生成コードの中には順番を逆転できない制約がある部分があり、今回の自走プログラムはかなり厳しい条件の中で辛うじて達成できたと言えると思います。
}
 前回の記事と同様に自作の Z80 ボードである Z80GalCompact を使って動作確認してみました。
 今回作成した自走プログラムは runcode3 で実行後にリセットし、メモリ上に残っているプログラムのコードを GAME 言語で作成したサーチプログラムで探しています。

Z80GalCompact での自走プログラム3の検証時のログ
Z80GAL Ver 0.01 2020/11/26 by skyriver CP/M loading ... ok 62k CP/M Version 2.2 COPYRIGHT (C) 1979, DIGITAL RESEARCH a>zsidm ZSIDM Ver 1.4 #f100 cfff e5 #d9000 9000: E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 ................ 9010: E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 ................ 9020: E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 ................ 9030: E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 ................ 9040: E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 ................ 9050: E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 ................ 9060: E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 ................ 9070: E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 ................ 9080: E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 ................ 9090: E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 ................ 90A0: E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 ................ #g0 a>c:runcode3 Z80GAL Ver 0.01 2020/11/26 by skyriver CP/M loading ... ok 62k CP/M Version 2.2 COPYRIGHT (C) 1979, DIGITAL RESEARCH a>gamec GAME80 for CP/M-80(compiler 0200-1B38) Ver0.05d *READY :\<c:findrun3 8E00-8F36 *READY :0 1' *** find runner code 3 10 A=0 100 @ 110 ;=(A:0)=$F9)*(A:1)=$2B)*(A:2)=$C5) " *** find at $" ??=A #=1000 120 ;=%(A/$1000)=0 / "Searching " ??=A "H" 130 A=A+1 140 @=(A=0) 150 #=-1 1000 B=A/256*256 ;=B<0 B=B-$100 1010 I=0,$10 1020 / ??=B ":" 1030 J=0,15 1032 ;=J=8 " -" 1040 " " ?$=B:J) 1050 @=J+1 1060 B=B+16 1070 @=I+1 *READY :#=1 Searching 0000H Searching 1000H Searching 2000H Searching 3000H Searching 4000H Searching 5000H Searching 6000H Searching 7000H *** find at $7A89 7A00: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 7A10: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 7A20: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 7A30: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 7A40: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 7A50: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 7A60: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 7A70: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 7A80: 00 00 00 00 00 00 00 00 - 00 F9 2B C5 D5 D9 C5 F5 7A90: E5 F5 E5 C5 D5 D9 18 F1 - 00 00 00 00 00 00 00 00 7AA0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 7AB0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 7AC0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 7AD0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 7AE0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 7AF0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 7B00: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 *READY :


 X(旧Twitter)で教えてもらったメモリの全空間が RAM で メモリ上に VRAM をアサインできる MZ-2200 のエミュレータで自走プログラムが VRAM 上を通る時の画面キャプチャを貼っておきます。

EmyuZ-2200 エミュレータでの自走プログラム2表示



★追記 2024/01/27
 EmyuZ-2200 エミュレータのディバッガは RST 命令を使用したソフトウェアディバッガよりも ICE に近いので実行命令直下のメモリを書き換えてもステップ動作が行えたり、スタック直下のメモリを壊したりしません。
 今後ディバッグに活用する場面がありそうで有用なソフトを公開された作者の方に感謝です。
 しかし、上記の自走プログラムで使用しなかったのは奇妙な動きがあったからです。改めて動作を確認してみましたので記録しておきます。

 下記の例では PUSH 命令によって PUSH 命令自身を書き換えています。

PUSH 命令による自己書き換えプログラム
;+++++++++++++++++++++++++++++++++++++ ; runner code on memory ; Ver 0.03 2024/01/26 by skyriver ;+++++++++++++++++++++++++++++++++++++ 0000' ASEG ORG 0 0000 F3 START: DI 0001 31 000A LD SP,SPVAL 0004 AF XOR A 0005 21 DB 21H ; LD HL,xxxx 0006 3C INC A 0007 3C INC A 0008 00 NOP 0009 E5 PUSH HL 000A 00 SPVAL: NOP 000B 00 NOP 000C 00 NOP 000D 00 NOP END


 以下がステップ動作時の画面表示です。「done 00000009 PUSH HL」と表示されずに「done 00000009 INC A」と表示されていますね。Aregはゼロのままでメモリは HL の値に書き換わっているので実際は PUSH HL を実行し、done 表示ではメモリから再読みこみして表示しているようです(つまり実行動作は問題なくて表示が違っているだけ)。極めてレアなケースなので通常の使用には問題ないと思われます。

EmyuZ-2200 のディバッガでのステップ動作結果



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

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

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。