エピ6.3 メインループを解読しましょう - 直接実行命令文
***
エピ6.3 メインループを解読しましょう - 直接実行命令文
***
前回のエピ6.2ではメインループの(3)と(5)を解読しました。
今回は(4)の「直接実行命令文として実行」に取り組みます。
***
メインループの処理(1)〜(5)を復習しましょう。
(1)「READY」 を表示
(2)4565h 番地が 00h ならスタックポインタを復旧、47FFh〜4800h(実行中の文番号)が 0000h 以外なら 47FDh〜4802h の6バイトを退避
(3)47FDh〜4800h の4バイトをゼロクリア、改行して入力待ち。入力を調べて、文番号があれば 4457h〜4458h に格納、命令文は中間コードに変換して 4459h〜 に格納、文番号の有無により(4)または(5)に行く
(4)文番号無しなら、直接実行命令文として実行、(3)に行く
(5)文番号付きなら、4577h 番地と 4565h 番地を 00h にし、プログラムとして記憶、(2)に行く
*
メインループの動きをもう少し感じるために、機械語の1命令を1ステップとして、メインループのステップ数をカウントしてみます。逆アセンブルリストの行数を数えるだけですけど。ループ回数も考慮してカウントします。なお、ROMルーチンはカウントに含めないです。
入力は文番号付きのプログラムで、"1 A=0" です。メモリは空の場合です。先頭の [〜] がステップ数です。
[000000](1)124Dh〜 READY を表示
[000006](2)1259h〜 SP を復旧([4565h]==00h)
[000011](3)1278h〜 ゼロクリア、改行、入力("1 A=0")
[000033] _13C9h() 変換1
[009731] _143Dh() 変換2
[010885] 文番号を判定
[010888] jr NZ 129Fh(5)にジャンプ
[010889](5)129Fh〜 入力("1 A=0")をメモリに記憶
[011275] jp 1259h(2)にジャンプ
変換1と変換2が重いです。変換1で 9731-33 = 9698 ステップ、変換2で 10885-9731 = 1154 ステップです。この2つで全ステップ中の96%を占めます。
プログラムのメモリへの記憶は挿入だけなこともあり、11275-10889 = 386 ステップです。
入力が "1 ABC=123" なら、変換1は 24411-33 = 24378 ステップです。
変換2は 25669-24411 = 1258 ステップ。メモリ記憶は 402 ステップと幾らか増える程度。
変換1では、ABC はキーワードとは一致しないので中間コードのリストを最後まで探して、それを文字分だけ繰り返すのです。だから爆増します。
スペースを削った場合とそうでない場合、
120 FORX=0TO700:POKEV+X+200,109:NEXT
120 FOR X=0 TO 700 : POKE V+X+200,109 : NEXT
の2つを比較してみますと、ギチギチの場合、変換1は 69692-33 = 69659 で、変換2は 75329-69692 = 5637 です。スペースを入れた場合は、69804-33 = 69771 と 75553-69804 = 5749 です。
ギチギチの方が少ないですけど、さほどの違いはないですね。
*
直接実行命令文の場合は、入力は "BYE" として、
[000000](1)124Dh〜 READY を表示
[000006](2)1259h〜 SP を復旧([4565h]==00h)
[000011](3)1278h〜 ゼロクリア、改行、入力("BYE")
[000033] _13C9h() 変換1
[000495] _143Dh() 変換2
[002276] 文番号を判定
[002279] jr NZ 1291h(4)にジャンプ
[002280](4)1291h〜 直接実行命令文を実行
[002333] "BYE" 命令を実行
変換1は "B" で始まるキーワードを探して、"BYE" に到達すると残り2文字が一致することを確認するだけだから速やかです。逆に変換2では中間コード(A0h)からキーワード(BYE)を求めるために、A0h-80h = 32 個目の終端(MSB=1)を探すのです。この処理のためにステップ数が増えます。変換2はイラナイのではないかな。
ここで、2333-2280 = 53 ステップが直接実行命令文を実行する直前までの処理です。
何をしているのでしょうか? 53 ステップですからね。
*
**
***
さて。直接実行命令文です。
(4)のコードは 1291h 〜 129Eh にあり、次のようになっています。
1291: HL = 0x4459
1294: [0x4801] = HL
1297: _168Ch(0Dh,19CBh)
129D: JP _1278h
1291h 番地と 1294h 番地のコードで、4801h〜4802h 番地に 4459h が入ります。4459h とは(3)の処理で中間コードに変換された命令文が入っている番地です。ここでは命令文は "BYE" の中間コード A0h と終端 0Dh です。
更に調べてみると("RUN" 命令を調べていたら)、47FDh〜4802h 番地の6バイトとは、
47FDh〜47FEh : 次文への番地 ・・・プログラム実行中
47FFh〜4800h : 文番号 ・・・プログラム実行中
4801h〜4802h : 命令文への番地
であることが分かって来ました。命令文への番地はプログラム実行中なら 480Ah 等のプログラム内の番地を指して、直接実行命令文なら 4459h です。
このように次文・文番号・命令文の3点で、プログラムや直接実行命令文の実行状態を表すのです。
なるほどですね。
*
次の 1297h 番地では 168Ch のルーチンをコールしています。
このルーチンは [HL]!=0Dh なら 19CBh にジャンプします。コールしているのに別の番地にジャンプして戻って来ないとはどういうことなのでしょうか? この謎は次回で!
と。ともかく。ここでは HL=4459h で、4459h 番地は A0h が入っていますから 19CBh 番地にジャンプです。
*
19CBh 番地からのコードを解読します。ここまでに 2299-2280 = 19 ステップですから残るは 34 ステップです。
まずは 2299 ステップからの6ステップです。
19CB: HL = E000h
19CE: [HL] = F8h # F8h = 1111000
19D0: HL++
19D1: A = [HL] # HL = E001h, A = FFh
19D2: A++ # A = 00h, Zf = 1
19D3: jr(Z,19E0h) # jp if Zf==1
MZ80の取説によると、E000h と E001h 番地はメモリマップッドIOで、E000h の下位4ビットと E001h の8ビットでキーボードの状態を取得できるそうです。
何もキーが押されていなければ E001h 番地からは FFh が読み出せて、その場合は 19E0h 番地にジャンプして、次の4ステップはスキップです。
19D5: _001Eh() # ROM "BR KEY"
19D8: jr(NZ,19E0h) # jp if Zf==0
19DA: _13C3h() # [4565h] = 02h
19DD: JP _1382h # msg BREAK
ROMルーチンの 001Eh はブレークキーの検出です。押されていなければ 19E0h 番地にジャンプです。ブレークキーが押下されていた場合は、13C3h 番地のルーチンをコールして 4565h 番地を 02h にして、1382h 番地にジャンプして "BREAK" を表示してメインループに戻ります。
*
SP5030の解説書によれば、プログラムの実行を中断したいときには SHIFT キーと BREAK キーを同時に押すことになっています。ですが、このコードからは INS/DEL キーや CR キーなどと同時に BREAK キーを押下してもブレークできそうです。...、これは?
調べてみるとROMルーチンの 001Eh では SHIFT キーの判定をしていて、ここでは最小減の処理でROMコールの要否を判定しているのですね。
*
次は 2305 ステップからの7ステップ。
19E0: HL = [4642h] # HL = 4816h
19E3: A = 00h
19E4: [HL] = A # HL = 4816h, A = 00h
19E5: HL++
19E6: [HL] = A # HL = 4817h, A = 00h
19E7: HL++
19E8: [4644h] = HL # HL = 4818h
ちょっと謎ですけど。4642h 番地の値は初期化処理で 4816h になっていて、4816h〜4817h 番地を 0000h にします。それから 4644h 番地の値(初期化処理で 4818h)を 4818h にします。やはり謎。
*
4642h と 4644h についてはエピ11にて解明されました。文字列用と数値用の作業領域をリセットしているのですね。
*
次は 2312 ステップからの9−1=8ステップ。
19EB: HL = [4801h] # HL = 4459
19EE: A = [HL] # A = A0h
19EF: or(A)
19F0: jp(P,1B25h) # "LET"
19F3: cp(F2h) # A = A0h, Zf = 0
19F5: jr(NZ,19F9h) # jp if Zf==0
19F7: A = A8h # skip
19F9: cp(AAh) # A = A0h, Cf = 1
19FB: jp(NC,138Ah) # err-msg SYNTAX ERROR
19EBh と 19EEh で直接実行命令文を読み出します。
4801h 番地の値は 4459h で、4459h 番地に直接実行命令文("BYE" の 中間コード A0h)が入っていて、これを A レジスタに入れます。
A レジスタの値が 00h〜7Fh の範囲なら中間コード以外なので、1B25h にジャンプして "LET" 文として処理します。
A レジスタが F2h なら A8h にしますが、今回は A0h なので 19F7h 番地はスキップです。A8h は "CURSOR" 命令の中間コードですが、メインループで F2h に変換しているのです。これを元に戻すのは良いけど何故に F2h にしたのか意味不明。
それから A レジスタが AAh 以上ならば、138Ah にジャンプして SYNTAX ERROR を表示してメインループに戻ります。AAh 以上の中間コードは直接実行可能な命令文ではないからエラーなのです。でもね。A9h だと(未定義なので出てこないはずだけど)暴走するね。隠れたバグ?
A レジスタは AAh 未満なので続行です。
*
最後は、2320 ステップからの 13 ステップです。
19FE: DE = 1A0Eh
1A01: HL++
1A02: ex(DE,HL) # DE = 445Ah, HL = 1A0Eh
1A03: A += A # A = 40h
1A04: C = A # C = 40h
1A05: B = 00h
1A07: HL += BC # HL = 1A4Eh
1A08: C = [HL] # C = C6h
1A09: HL++
1A0A: B = [HL] # B = 27h
1A0B: ex(DE,HL) # DE = 1A4Fh, HL = 445Ah
1A0C: push(BC) # BC = 27C6h
1A0D: ret # RET 27C6h
1A0Eh に中間コードの2倍を加算して、その番地の値を BC レジスタに転送し、スタックを操作(push BC)して RET するのです。
中間コードが 80h なら 1A0Eh + (80h*2)%256 = 1A0Eh 番地の値、中間コードが 81h なら 1A10h + (81h*2)%256 = 1A10h 番地の値です。
そして、A0h なら 1A0Eh + (A0h*2)%256 = 1A4Eh 番地の値、それが 27C6h です。ここに RET 命令でジャンプして、27C6h 番地からのコードで "BYE" 命令が実行されます。
*
*
*
次が "BYE" 命令の実行コードです。
27C6: JP _0000h
ROMの 0000h にジャンプします。SP5030を終了してROMに制御を移します。これが BYE かとちょっと感動した。
*
*
*
ROMに戻った後は(ROMのコマンドで)1200h 番地にジャンプすればSP5030に戻りますが、初期化処理でプログラムは消えてしまいます。
消えて欲しくないときには、メインループの先頭 124Dh にジャンプすればよさそう。
***
**
*
友だちと別れるときには、バイバイ (^.^)/~~~ って手を振って別れますよね。
バイバイ〜
またねー
***
間違いの指摘とか疑問とか、ご意見・ご感想とかありましたら、どうぞ感想欄に!
***
2026.4.9 推敲




