拡張BASICとはなにか
MSXではBASICの命令を自作して追加することができます。
文法的には
CALL xxxx
とか
CALL xxxx(n,n ....)
という形式になります。
マシン語を使うときはUSR(n)で呼び出すことはできますが、引数が1つしかないという制約があります。
CALL文は複数の引数を持てるというメリットがありますが、デメリットとしては
記述が難しいことと、戻り値がないことです。
どうしても戻り値を取得したい場合は、変数のアドレスを引数として渡してそのアドレスに結果を代入するという方法もあります。
CALL文の作り方
以下の文献を参考にしました。
実装サンプルはありがたいのですが、一部間違っているのでそのままでは動かないと思われます。
実装方法
CALL文の本体はPAGE1に置くのが標準のようですが、PAGE2や3に置くことも可能のようです。
今回はWEBMSX上で開発するにあたって次のようにしました。
① マシン語はページ3の0c000h~に配置する。
②ページ1のメモリマッパーをページ3と同じにする。
③CALL拡張したフラグを&hfcc9+48+1のbit5に設定する。
こうすればRAMをROMのように振る舞うことができます。
注意する点は実際の実行はページ1(4000h~)なので絶対番地でアクセスするには-8000Hしないといけないことです。
なるべくJPではなくJRを使っていれば-8000Hをしなくてもいいのですが・・。
サンプルコード
アセンブラ
; External variables & routines
CHPUT EQU 0A2h
CALBAS EQU 0159h
ERRHAND EQU 0406Fh
FRMEVL EQU 04C64h
FRMQNT EQU 0542Fh ;2byte DE
GETBYT EQU 0521Ch ;1byte A or E
FRESTR EQU 067D0h
CHRGTR EQU 04666h
VALTYP EQU 0F663h
USR EQU 0F7F8h
PROCNM EQU 0FD89h
org 0c000h
BEEP EQU 0c0h
init:
db 041h,042h
db 0,0
dw main - 8000h
db 0,0,0,0
org 0c010h
main:
PUSH IX
PUSH BC
PUSH DE
PUSH HL
LD HL,MYCMD - 8000h
CALL CHKCMD - 8000h
JR NC,END2S
LD HL,MYCMD1 - 8000h
CALL CHKCMD - 8000h
JR NC,END3S
LD HL,MYCMD2 - 8000h
CALL CHKCMD - 8000h
JR NC,END4S
LD HL,MYCMD3 - 8000h
CALL CHKCMD - 8000h
JR NC,END2S
JR END1E
END2S: ;SUCCESS
POP HL
CALL CHKCHAR -8000h
DEFB "("
LD IX,GETBYT
CALL CALBAS
LD (0d002h),A
CALL CHKCHAR -8000h
DEFB ")"
POP DE
POP BC
POP IX
XOR A
RET
END3S: ;SUCCESS
POP HL
CALL CHKCHAR -8000h
DEFB "("
LD IX,FRMQNT
CALL CALBAS
LD (0d000h),DE
CALL CHKCHAR -8000h
DEFB ")"
POP DE
POP BC
POP IX
XOR A
RET
END4S: ;SUCCESS
POP HL
CALL CHKCHAR -8000h
DEFB "("
LD IX,FRMQNT
CALL CALBAS
PUSH DE
CALL CHKCHAR -8000h
DEFB ","
LD IX,FRMQNT
CALL CALBAS
POP IX
ADD IX,DE
LD (0d000h),IX
CALL CHKCHAR -8000h
DEFB ")"
POP DE
POP BC
POP IX
XOR A
RET
END1E: ;NG
LD A,'N'
CALL 0a2h
POP HL
POP DE
POP BC
POP IX
SCF
RET
MYCMD:DB "A",0
MYCMD1:DB "B",0
MYCMD2:DB "C",0
MYCMD3:DB "D",0
CHKCMD:
LD IX,PROCNM
LD BC,16
LOOP:
LD A,(IX+0)
CP 0
JR Z,NEXT
CPI
JR NZ,NEXT
JP PO,NEXT - 8000h
INC IX
JR LOOP
NEXT:
LD A,(IX)
CP 0
JR NZ,ERROR1
LD A,(HL)
CP 0
JR NZ,ERROR1
HIT:
XOR A
RET
ERROR1:
SCF
RET
CHKCHAR:
CALL GETPREVCHAR -8000h
EX (SP),HL
CP (HL)
JR NZ,SYNTAX_ERROR
INC HL
EX (SP),HL
INC HL
GETPREVCHAR:
DEC HL
LD IX,CHRGTR
JP CALBAS
TYPE_MISMATCH:
LD E,13
LD IX,ERRHAND
JP CALBAS
SYNTAX_ERROR:
LD E,2
LD IX,ERRHAND
JP CALBAS
BASIC
10 clear 200,&hbfff
20 bload"program.bin"
30 out&hfd,&hc0
35 poke&hfcc9+48+1,32 'ROM ENABLED
36 goto 300
37 END
300 ' BENCH
310 time=0:fora=0to999
320 _A(1)
330 NEXT
340 print TIME
350 time=0:fora=0to999
360 _B(1)
370 NEXT
380 print TIME
390 time=0:fora=0to999
400 _B(1+2)
410 NEXT
420 print TIME
430 time=0:fora=0to999
440 _C(1,2)
450 NEXT
460 print TIME
実装した命令
以下の命令が使えます。
命令書式 | 内容 |
---|---|
CALL A(n) | nの値をd002hに書き込む |
CALL B(nn) | nnの値をd000hに書き込む |
CALL C(nn,mm) | nn+mmの値をd000hに書き込む |
CALL Aは1バイトの値を取得します。
CALL Bは2バイトの値を取得します。
値が範囲を超えるとエラーがでます。
CALL Cは2バイトの値2つを加算しています。
引数はBASIC内部で式として処理しているのですが、これをマシン語内で演算したら速くなるのか気になりました。
実行した結果は以下の通りです。
実行内容 | 時間(Z80) | 時間(R800) |
---|---|---|
1バイトの値を渡す | 358 | 107 |
2バイトの値を渡す | 360 | 103 |
式の中で足し算する | 398 | 110 |
2つの値をマシン語で足し算する | 432 | 132 |
意外な結果になりました。
マシン語で足し算した方が速いと思っていたのにBASIC式の方が速かった。
もしかすると引数の取得にオーバーヘッドがかかっているのかもしれない。
また2バイトの値渡しはZ80とR800で逆転していますね。
16ビットレジスタはR800の方が得意なのでこういう結果なのでしょう。
拡張命令で何をするか?
今回のプログラムですが、ディスクにprogram.binが保存されています。
そのディスクイメージをwindowsでダウンロードしてprogram.binを取り出したのち、先頭の7バイトを削除すればROMイメージになります。
この開発方法でROMイメージを作るとしてゲームに利用できる命令を拡張したいですね。
具体的にはデータの圧縮展開とか複雑な計算とか。
例)
CALL OBJ(0,X,Y) '自分の座標
CALL OBJ(1,EX(0),EY(0)) '敵1の座標
CALL OBJ(2,EX(1),EY(1)) '敵2の座標
CALL OBJ(3,EX(2),EY(2)) '敵3の座標
CALL HIT(H) '自分と敵が当たっていればHに1を入れる
IF文をたくさん書くよりは高速化できそうな感じもする。
あとメモリ上にXY座標を置くからスプライトのまとめ表示命令を作るとさらなる高速化が期待できるか。
またフラッシュメモリに書き込む命令を拡張してもよい。
例)
CALL MINIT 'ROMに書き込むための準備
CALL MSAVE 'ページ2のBASICプログラムをROMに書き込む
CALL MLOAD 'ROMプログラムをページ2に書き込む
CALL MWRITE(ROMアドレス,RAMアドレス,バイト数)
CALL MREAD(RAMアドレス,ROMアドレス,バイト数)
これがあるとMSX-DOS無しで読み書きができるんだが・・。
最後に
BASICを拡張するというのは先人達もやっているようですが、自作命令は夢がありますね。