N-BASIC の内部ファンクションコール(VRAMアドレス計算)を解析してみたらビックリ

N-BASIC の内部ファンクションコール(VRAMアドレス計算)を解析してみたらビックリ

さて、だいぶご無沙汰してしまった PC-8001 用のゲーム開発 (OBSIDIAN) ですが、マイコン熱がだいぶ温まってきましたので、約1年ぶりに再開してみました。N-BASIC での実装もだいぶ進んで、2Dフィールドと 3Dダンジョンを自由に移動できるようになりました。

と、ここまでは大したことないのですが、実行しているとBASICの遅さにイライラしてくるのが世の常。

8bitマイコンは 最低限BASIC + マシン語で処理しないとゲーム的にはちょっともたついて、格好悪いということで。。。

「3Dダンジョンの書き換え」と「INFORMATION の書き換え」をマシン語にすることにしました。

といってもだいぶ久々の Z80 ということで、両方とも指定した矩形を高速クリアするだけにしてみます。

PC-8001mkII のユーザーズマニュアルが手元にあるので、

VRAMエリアを確認したところ、下図のような構成になっていることを確認。といってもすでにBASIC から VRAM に POKE で書き込んでいるので分かっていますが。。

PC-8001 のVRAM操作がめんどいのは、VRAMエリアに続いて アトリビュートエリアがあり、そこで文字の装飾 (色やブリンク、反転、グラフィックスなど) を設定することなんだよね。ここは 40byte しかなく、設定は 2byte 使うので、1行で 20回の色変更までという制限があります。。

今回は、このアトリビュートは横においといて、

矩形のクリアを考えると、高級言語で関数として考えると、必要なパラメータは左上の座標 (X,Y) と横幅、縦幅の4つのパラメータが一般的ですね。

まずは、ターゲットとなる座標のVRAMアドレスを取得する方法を考えます。

PC-8001 は40桁の場合は アドレス1個とばしなので、VRAM先頭アドレスF300H に X座標 * 2 を足せばいいですね。そして、Yは 1行 120 (78H) バイトですので Y * 78H を足せばいいですね。

VRAMアドレス = F300H + X*2 + Y*78H

とまあ、簡単なのですが、Z80で中途半端な掛け算をするのはちょっと面倒なんだよねぇ。。2の累乗であれば、ローテートするだけですむんだけどね。そういえば、PC88でもBASICの内部ルーチンコールでVRAMアドレスが取得できたな~と思いだしたので

森巧尚先生の昔の本 (PC-8801 マシン語プログラミング入門 ナツメ社 1985年) を調べたら、93ページに429DHをコールすればアドレス取得ができると書いてあったので、N-BASIC のモニターで対象の部分をダンプして逆アセンブルしたら、コードが意味不明だったので、やはり N88-BASICとは違うよねってあきらめて、N-BASICについて解析することに。。。

プログラムしているといつも脇道にいったきり、なかなか戻ってこられないんです。そんな性分。。

ってことで、google で調べたら N-BASIC の内部ルーチンを調べている人が情報を公開していました。良かった!! パチパチパチ。

N-BASICでは、03F3H をコールすればいいみたい。でも疑い深いのでもう盲目的には使えないということで、ダンプしてハンド逆アセンブル (笑)。

ハンド逆アセンブルには、 Z80マイコンプログラムテクニック (電波新聞社 昭和55年) 巻末の Z80インストラクションセットのリストが見やすかったので利用しました。

■03F3H : カーソル座標からアドレスに変換
 入力: H=X座標(左端が01)
     L=Y座標(上端が01)
 出力:HL=VRAM アドレス

03F3 CD 01 04     CALL 0401H ; Call オフセットアドレス変換
03F6 7D           LD A,L
03F7 E5           PUSH HL
03F8 CD 64 06     CALL 0664H ; VRAMの先頭アドレス計算
03FB E1           POP HL
03FC 6C           LD L,H
03FD 26 00        LD H,00H
03FF 19           ADD HL,DE
0400 C9           RET

解説

いきなり、 CALL で別のサブルーチンに飛んでしまいますが、HLレジスタにセットした X,Y座標 (左端、上端を1) を画面モードの判定を行って、 40桁モードだったら、 (X-1)*2 を Hレジスタに入れて、 Lレジスタには Y-1 をセットしてここに戻ってきます。Y座標がはいっている Lレジスタの内容を Aレジスタに入れて、また別のサブルーチンに飛んでしまいます。そこでは、DEレジスタに 指定したY座標に対応する VRAM の先頭アドレスをセットして戻ってきます。POP HL で XY座標を戻して、 X座標が入っている Hレジスタの内容を Lレジスタに格納して、Hには 0をセット これで HL = 00xx (xxはX座標*2) となりました。最後にHL + DE で無事にVRAMアドレスゲット!!

■0401H : オフセットアドレス変換
 入力: H=X座標(左端が01)
     L=Y座標(上端が01)
 出力: H=X座標から計算したオフセット アドレス
     L=Y座標(上端が00)
 使用:AF、HL
    EA61H カラー/白黒モード 0FFH=カラー、000H=白黒
    EA65H 白黒モードのときの1行の文字数 50H=80文字
                       48H=72文字
                       28H=40文字
                       24H =36文字

0401 25          DEC H          ; X座標 
0402 2D          DEC L          ; Y座標
0403 3A 65 EA    LD A,(EA65H)   
0406 FE 48       CP 48H         ; 72文字?
0408 28 15       JR Z,15H       ; 041D (+15H) アドレスへジャンプ
040A FE 24       CP 24H         ; 36文字?
040C 28 14       JR Z,14H       ; 041F (+14H) アドレスへジャンプ
040E 3A 61 EA    LD A,(EA61H)   ; 
0411 A7          AND A
0412 28 01       JR Z,01H
0414 24          INC H
0415 3A 65 EA    LD A,(EA65H)
0418 FE 50       CP 50H         ; 80文字かどうか
041A C8          RET Z          ; 80文字の時は戻る
041B 7C          LD A,H         ; 40文字が確定したので
041C 07          RLCA           ; Hレジスタを左にローテイトして
041D 67          LD H,A         ; 2倍にして40文字に対応
041E C9          RET
041F 24          INC H
0420 24          INC H
0421 C9          RET

このサブルーチンでは、XY座標を (0,0) 基準にして、 X座標については、40桁モードの場合は 2倍にしています。40桁の場合は、VRAMアドレス 1個飛ばしなので、そのようにしています。

■0664H : VRAM の先頭アドレス計算
 入力:A=Y座標(上端が00)
 出力:DE=そのラインの先頭アドレス
 使用:HL
 
 指定したY座標から、そのVRAMの先頭アドレスを計算してDEに出力します

0664 21 75 06    LD HL,0675H   ; 参照テーブルの先頭アドレス
0667 FE 19       CP 19H        ; Y>=25
0669 D2 A5 44    JP NC,44A5    ; アドレス 44A5 にジャンプ?
066C 07          RLCA
066D 5F          LD E,A        ; Y座標をEレジスタに格納
066E 16 00       LD D,0
0670 19          ADD HL,DE     ; 0675H + Y*2 
0671 5E          LD E,(HL)
0672 23          INC HL
0673 56          LD D,(HL)
0674 C9          RET
 ; Y座標の先頭アドレスはテーブル参照しているだけ
0675 00 F3       DEFW F3 00
0677 78 F3       DEFW F3 78
0679 F0 F3       DEFW F3 F0
067B 68 F4       DEFW F4 68
067D E0 F4       DEFN F4 E0
067F 58 F5       DEFN F5 58
0681 D0 F5       DEFN F5 D0
0683 48 F6       DEFN F6 48
0685 C0 F6       DEFN F6 C0
0687 38 F7       DEFN F7 38
0689 B0 F7       DEFN F7 B0
068B 28 F8       DEFN F8 28
068D A0 F8       DEFN F8 A0
068F 18 F9       DEFN F9 18
0691 90 F9       DEFN F9 90
0693 08 FA       DEFN FA 08
...

さてさて、一番興味のあった部分。Y座標は 120byte ずつ増えるけど、どのように計算しているのかな~。。。うん? 0675H ? その後のコードもだいぶ シンプルだな。。 0675H に Y座標を足して値が示すアドレスの内容を DEレジスタにいれているぞ。。もしや!!!

なんと、0675H~はべたにVRAMアドレスが書き込まれているではないか! 参照テーブルかい!! まぁ画面は25行なので、50バイト程度しか占有しないけどね。Z80は掛け算しようとするとコードが長くなるし遅くなるから、参照テーブルにしたんでしょうね。

Z80関連書籍 総動員で読書タイム。う~ん、落ち着く。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です