ようやく入出力ができるようになりました。
毎回EEPROMを焼くのは大変なので、RAMにプログラムを転送できるように機械語モニタを導入しようと思います。
ネット検索すると世の中にはすでに多くの機械語モニタがあります。その中で興味深いページを見つけました。
Bequest333様はPIC16F18857に電大版TinyBASICを移植なさっています。
その中でBASICランチャーという簡易的なデバッグモニタをPIC上に構築しているのですが、シンプルな上に実用的で非常に使いやすそうです。
そこで勉強がてら、BASICランチャーを参考にオリジナルの機械語モニタを作成してみることにしました。
Tiny monitorの実装
今後SBC6303を使っていくにあたって、下記の機能があると便利です。
- モトローラSレコードファイルの読み込み
- プログラムの実行
- メモリ内容のダンプ
- ブレークポイント機能
私のSBC6303はRAMを8KB($0000-$1fff
)積んでいます。
開発時は前半($0000-$0fff
)を仮想RAM、後半($1000-$1fff
)を仮想ROMとして使用するつもりですので、とりあえずプログラムの開始番地は$1000
固定として、読み込みと実行は機能をまとめてしまいます。
ブレークポイント機能はswi割り込みを使えばうまく実装できそうです。
Sレコードの読み込みと実行の実装
Sレコードの構造は下記のようになっています。
+---+------+------------+---------+------+----------+ | S | Type | Byte Count | Address | Data | Checksum | +---+------+------------+---------+------+----------+
8bitCPUで使用するタイプはS0(ヘッダ)、S1(データ)、S9(終端)の3タイプですが、S0を出力するアセンブラは無いように思えます。S9も終端を表すのみで真面目にデコードする必要はなさそうですのでS1をしっかり読み取ってあげればよいわけです。
S1レコードの処理は下記の手順で大丈夫そうです。
- バイト数(Byte Count)取得、-3してデータ数を保存
- 書き込み開始番地(Address)取得
- データ読み込み・書き込みをデータ数分繰り返す
- チェックサム取得・比較
そのため、SCIから1バイト受信するルーチンを用意しました。
.read_srecord
jsr read_char
cmpb #'A' ; if B < "A"
bcs :1 ; then goto .1
subb #7
.1 subb #$30 ; 数値に変換
aslb
aslb
aslb
aslb
tba ; Aレジスタに上位4bitをコピー
jsr read_char
cmpb #'A' ; if B < "A"
bcs :2 ; then goto .2
subb #7
.2 subb #$30 ; 数値に変換
aba ; 上位4bitと下位4bitを加算
tab ; A -> B
rts
本来であれば16進数字かどうか、大文字か小文字かのチェックなども必要なのですが、手抜き&高速化のため、チェックなし・大文字のみとしました。
1バイト受信ごとにチェックサム用に加算してメモリに保存しておきます。
レコード最後に取得するチェックサムはこれまで加算した数値の1の補数なので、両者を合わせると$ff
になります。
さらに1を加算すると$00
になるのでゼロフラグを見て条件分岐できます。
メモリダンプ
入力した16進数字により、$0x00-$0xff
までダンプします。よってダンプできるのは$0000-$0fff
までの4KB分のみです。
入力されたのが16進数字かどうか判断するルーチンでは面白い方法を使っています。
例として数字("0"-"9"
)かどうかチェックし、真偽をキャリーフラグに反映させる処理は、普通に考えると下記のようになります。
is_decimal_char:
cmpb #$3a ; "9"より大きければC=0、"9"以下であればC=1。
bcc :end
cmpb #$30 ; "0"より小さければC=1、"0"以上であればC=0。
bcs :clrcy
sec ; "0"以上であればC=1にする。
rts
.clrcy clc ; "0"より小さければC=0にする。
.end rts
このように"0"
より小さいか、"9"
より大きいか、それぞれの判定でキャリーフラグの挙動が逆になるので、フラグを操作してあげないといけません。
ところが、FM-8のF-BASIC 1.0では下記のようなテクニックが使われていたそうです。
is_decimal_char:
cmpb #$3a ; "9"より大きければC=0、"9"以下であればC=1。
bcc :end
subb #$30 ; -$30-$d0(-$100)で一回り。
subb #$d0 ; "0-9"ならばC=1、それ以下のコードであればC=0
.end rts
あらかじめ$30
を引いてからさらに$d0
を引くことでキャリーフラグの挙動をコントロールしています!
全体では$100
を引くのできちんと元の数値に戻ってきています!
元のアスキーコード | cmpa #$3a | suba #$30 後 | suba #$d0 後 |
$2f ("/" ) | C=1 | $ff, C=1 | $2f, C=0 |
$30 ("0" ) | C=1 | $00, C=0 | $30, C=1 |
$39 ("9" ) | C=1 | $09, C=0 | $39, C=1 |
$3a (":" ) | C=0 | <-- | <-- |
アルファベットの判定など他にも使えるテクニックです。すごい!
ブレークポイント機能
ブレークポイントはswi割り込みを使用します。
モニタ側でswi命令を書き込めればカッコいいのですが、アセンブル→転送がお手軽にできる環境では必須ではないので、プログラム中にswi命令を書き込んでおく方式にします。
swi命令は全レジスタをスタックに保存してくれますので、レジスタ内容を表示後、モニタに戻るようにします。
CCRは値だけでは分かりにくいので、フラグごとに"1"
であれば大文字、"0"
であれば小文字にします。
ブレーク中のみ、プログラムに戻るrコマンドが使えるようにします。
割り込みベクタのフック
HD6303は豊富な割り込みを持っていますので、後から機能を追加できるようにゼロページにフックを用意することにします。
; Interrupt Vector Hooking
.or $20
VEC_TRAP: .bs 3
VEC_SCI: .bs 3
VEC_TOF: .bs 3
VEC_OCF: .bs 3
VEC_ICF: .bs 3
VEC_IRQ: .bs 3
VEC_SWI: .bs 3
VEC_NMI: .bs 3
; Interrupt Vector Hooking設定
; Swi,Trapはレジスタ値表示後、モニタに戻る
.or PROGRAM
ldaa #$7e ; '$7e' jmp
ldx #trap_routine
staa <VEC_TRAP
stx <VEC_TRAP+1
ldx #swi_routine
staa <VEC_SWI
stx <VEC_SWI+1
; その他の割り込みはそのままrti
ldaa #$3d ; '$3d' rti
staa <VEC_SCI
staa <VEC_TOF
staa <VEC_OCF
staa <VEC_ICF
staa <VEC_IRQ
staa <VEC_NMI
; Interrupt Vectors
.or $ffee
.dw VEC_TRAP ; trap
.dw VEC_SCI ; sci
.dw VEC_TOF ; tof
.dw VEC_OCF ; ocf
.dw VEC_ICF ; icf
.dw VEC_IRQ ; irq
.dw VEC_SWI ; swi
.dw VEC_NMI ; nmi
こんな感じで$0020
から3バイトずつ割り当ててjmp #$xxxxまたはrtiを書き込みます。
今のところSWI割り込みとTRAP割り込みをモニタで使用しています。それ以外は単純にRTIするだけです。
ROMの割り込みベクタはゼロページのアドレスを指すようにします。
Tiny monitorのソースコード
完成したソースコードはこちらです。
HD6303R_chip.defと同じ階層に置いてアセンブルしてください。
コメント