Tiny monitorの作成(1)

HD6301/HD6303

ようやく入出力ができるようになりました。
毎回EEPROMを焼くのは大変なので、RAMにプログラムを転送できるように機械語モニタを導入しようと思います。
ネット検索すると世の中にはすでに多くの機械語モニタがあります。その中で興味深いページを見つけました。

Bequest333様はPIC16F18857に電大版TinyBASICを移植なさっています。
その中でBASICランチャーという簡易的なデバッグモニタをPIC上に構築しているのですが、シンプルな上に実用的で非常に使いやすそうです。

そこで勉強がてら、BASICランチャーを参考にオリジナルの機械語モニタを作成してみることにしました。

“Tiny monitor”と名付けました。
とりあえず”Tiny”と付けておけばいいかな、という安直な理由です

Tiny monitorの実装

今後SBC6303を使っていくにあたって、下記の機能があると便利です。

  1. モトローラSレコードファイルの読み込み
  2. プログラムの実行
  3. メモリ内容のダンプ
  4. ブレークポイント機能

私の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レコードの処理は下記の手順で大丈夫そうです。

  1. バイト数(Byte Count)取得、-3してデータ数を保存
  2. 書き込み開始番地(Address)取得
  3. データ読み込み・書き込みをデータ数分繰り返す
  4. チェックサム取得・比較

そのため、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になるのでゼロフラグを見て条件分岐できます。

なぜIntel HEXのように2の補数にしなかったんでしょうか?

メモリダンプ

入力した16進数字により、$0x00-$0xffまでダンプします。よってダンプできるのは$0000-$0fffまでの4KB分のみです。

ちょうど仮想RAMの範囲ですのでこれで十分です

入力されたのが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 #$3asuba #$30suba #$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で追加されたTRAP割り込みでも同じ動作をさせます

割り込みベクタのフック

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のソースコード

完成したソースコードはこちらです。

monitor.asm – Tiny monitor version 1.0

HD6303R_chip.defと同じ階層に置いてアセンブルしてください。

次回は実際にTiny monitorを使ってみます

コメント

タイトルとURLをコピーしました