2021-09-24 20:53 — asano
カテゴリー:
INS8070ボードでROMのBASICの1文字入出力ルーチンを独自のものにしようとしてうまくいかなかったと書きましたが、モニタを移植したことでROM内を読めるようになったので関連箇所を調べてみることにしました。
まずはPコマンドでROMのアドレス範囲(0x0000~0x09FF)をPCに持ってきます。
手頃な逆アセンブラがないかネットを探してみたのですが見当たらなかったので簡単なものを書いてしまいました。
さて、どこから手を付けるかですが、先頭から読んでいくのはしんどいです。
今回調べたいのは文字入出力だけなのでメッセージから追っていくことにします。
メモリダンプを見ていくとASCII文字列がいくつもありますが、多くはBASICの予約語なので今回の目的には使えません。その中で良さそうなものが見つかりました。
0900 : D0 03 26 16 09 1E 85 C3 14 24 FB 04 45 52 52 4F : & $ ERRO
0910 : D2 53 54 4F 50 A0 41 D4 52 45 54 59 50 C5 C5 E6 : STOP A RETYP
エラーメッセージならかなり高い確率でそのまま表示するはずです。
そこで先頭アドレスである0x090Cを探してみると2箇所ありました。その近辺を逆アセンブルしてみます。
-
081D : 1F CALL 15 081E : 0C SR EA 081F : 09 LD T,EA 0820 : C5 F7 LD A,0xFFF7
-
08CF : 56 PUSH P2 08D0 : 26 0C 09 LD P2,0x090C 08D3 : 1E CALL 14 08D4 : 85 EA LD EA,0xFFEA
1.は偶然「0C 09」というバイト列が現れたようですが、2.の方はポインタレジスタに入れてサブルーチン呼び出しを行っています。CALL 14
は文字列表示ルーチンの可能性が出てきます。
CALL
命令はベクタを利用した(8086系でのINT
のような)ものなのでベクタを見てみます。
0020 : 3B 06 EE 06 46 08 79 06 39 08 49 08 02 02 82 09 : ; F y 9 I
0030 : 7D 09 4C 05 01 08 13 06 5D 05 94 05 3E 05 CC 08 : } L ] >
CALL 14
のベクタアドレスは 0x0020 + 14×2 = 0x003C なので0x053F(SC/MPはフェッチ前にPCをインクリメントするので)から見てみます。
053F : C6 01 LD A,@+1,P2
0541 : E4 0D XOR A,=0x0D
0543 : 6C 05 BZ 0x054A
0545 : E4 0D XOR A,=0x0D
0547 : 17 CALL 7
0548 : 64 F5 BP 0x053F
054A : 5C RET
- アドレス
P2
から1バイトずつ取り出し(053F, 0540) - 0x0D(CR)だったら終了(0541~0544)
A
に入れてCALL 7
(0545~0547)- MSBが立っていなかったら繰り返す(0548, 0549)
- 戻る(054A)
たったこれだけのルーチンですが、いろいろと興味深いですね。
- 文字列の終端は[CR]か最後の文字のMSBを立てる
そういえば予約語の最後MSB立ててテーブル作るBASICは多いです。(N BASICやPalo Alto Tiny BASICなんかも) - 比較に
XOR
これはなるほどです。SUB
とADD
でもいいかな。モニタのswitch
的な分岐ではE
レジスタに退避していますが、元に戻すADD
と次の比較のSUB
を融合させれば短く(わかりにくく)なるな、とも考えていました。でも終端を0x00にしていれば面倒はないのになんで[CR]なのでしょう?
CALL 7
は1文字出力かな
出力時にはMSBを無視するらしいこと、MSBを含めてA
レジスタは保存されることもわかります。
さてCALL 7
のアドレスですが、CALL 14
と同様に求めると0x0983からであるとわかります。
097E : C4 0D LD A,=0x0D
0980 : 17 CALL 7
0981 : C4 0A LD A,=0x0A
0983 : 0A PUSH A
0984 : 22 00 FD PLI P2,=0xFD00
0987 : C2 00 LD A,0,P2
0989 : 64 32 BP 0x09BD
098B : 5E POP P2
098C : 38 POP A
ちょっと前の0x097Eから見たらこれ [CR] [LF] の出力ですね。
本題の0x0983から見ていくと0xFD00のMSBで分岐しています。この0xFD00は"INS8073 NSC Tiny BASIC Microinterpreter"に次のように書かれています。
When the INS8073 is initialized, the desired baud rate is automatically selected by reading the contents of memory location X'FD00. To program the baud rate, this location must be decoded by external logic, and the appropriate logic levels supplied on data lines 1, 2, and 7. The baud rate is selected as follow.
D2 D1 Baud Rate 1 1 110 1 0 300 0 1 1200 0 0 4800 If the user-supplied subroutines are to be used for I/O, bit 7 of location FD00 should be zero (the other bits don't matter), and, locations FD01-FD02 contain the address of the character output routine. (Locations FD03-FD04 contain the address of the character input routine.) These address should first be decremented by one, the stored in these locations low byte first.
分岐しない0x098BからはMSBが立っていた場合で標準のソフトウェアUARTのルーチンです。ビット2,3を無視しているので、おそらくボーレートは共通のウエイトルーチンがあるのではないかと思います。
MSBが落ちていた(ユーザ提供ルーチンを使用)場合の0x09BD以降を見てみましょう。
09BD : 82 03 LD EA,3,P2
09BF : 46 LD P2,EA
09C0 : 84 C7 09 LD EA,=0x09C7
09C3 : 08 PUSH EA
09C4 : C1 04 LD A,4,SP
09C6 : 76 FF BRA -1,P2
09C8 : 5E POP P2
09C9 : 38 POP A
09CA : 5C RET
P2
にはまだ0xFD00が入ったままなので、0xFD03~0xFD04を読んでP2
に入れます。(09BD~09BF)- ユーザルーチンからの戻りアドレスをスタックに積みます。(09C0~09C3)
- スタックの奥から保存されている
A
の値を読みます。(09C4, 09C5) P2
のアドレス(ユーザルーチン)に飛びます。(09C6, 09C7)- 最後にスタックからレジスタ復帰して戻ります。(09C8~09CA)
なんか資料がいろいろ間違っているようです。まず出力ルーチンのアドレスは0xFD01, 0xFD02ではなく、0xFD03, 0xFD04に書き込まなくてはいけないようです。次に資料ではアドレスから1引けとありますが、この補正は上記4.で行われているのでそのままの値で良いはずです。下位バイトを先にはあっています。
続いて1文字入力ですが、メッセージ出力のようなわかりやすい手がかりがありません。
1文字出力ルーチンは最初に0xFD00の値で分岐していたので1文字入力も同様なのではないかと推測して0xFD00を探してみます。すると3箇所見つかりました。うち1箇所はもちろん上記の1文字出力ルーチンです。
もう一つは0x09E6です。
09E5 : 22 00 FD PLI P2,=0xFD00
09E8 : 85 EA LD EA,0xFFEA
09EA : C2 00 LD A,0,P2
09EC : D4 06 AND A,=0x06
09EE : B4 F8 09 ADD EA,=0x09F8
09F1 : 46 LD P2,EA
09F2 : 82 00 LD EA,0,P2
09F4 : 8D EC ST EA,0xFFEC
09F6 : 5E POP P2
09F7 : 5C RET
0xFFEAに何が入っているのか未確認ですが、0xFD00のビット2,3でテーブルを引いているのでおそらくボーレートごとのループ回数か何かを切り替えているのではないかと思います。
残るは0x092Cです。
092B : 22 00 FD PLI P2,=0xFD00
092E : C2 00 LD A,0,P2
0930 : 64 43 BP 0x0975
0932 : 5E POP P2
1文字出力とパターンが似ていますね。
ユーザルーチン使用の場合の0x0975以下を見てみます。
0975 : 84 F5 09 LD EA,=0x09F5
0978 : 08 PUSH EA
0979 : 82 01 LD EA,1,P2
097B : BD E8 SUB EA,0xFFE8
097D : 44 LD PC,EA
- 戻りアドレスをスタックに積む。(0975~0978)
- 0xFD01からアドレスを読んで0xFFE8の値を引いてジャンプする。(0979~097D)
戻り先は前述のボーレートのところを共有していますね。アセンブリではよく使われるテクニックです。
0xFFE8には1が入っていると辻褄が合いますが、1文字出力のときのBRA -1,P2
では駄目なのでしょうか? BRA
の方が短いのですが。
0xFFE8は気になったので書き込んでいるところを探してみました。
00BC : 84 00 00 LD EA,=0x0000
00BF : 8D EA ST EA,0xFFEA
00C1 : CD C2 ST A,0xFFC2
00C3 : C4 01 LD A,=0x01
00C5 : 8D E8 ST EA,0xFFE8
0xFFEAには0、0xFFE8には1が固定で入れてあるようです。よく使う値はイミディエートで3バイト命令にするよりダイレクトで2バイト命令にするということでしょう。
ボーレートのところではE
=0にするのに使っていますね。
(続く)
コメントを追加