マシン語を使って画面に文字やグラフィックを表示する基本的な処理を解説します。


  • 画面はどこにある?
PC-8001 の VRAM は、標準では 0xF300 から存在しています。80x25 キャラクタの表示ですが、1ライン毎に 40バイトのアトリビュートエリアが付帯していますので、1行の長さは実質的には 120バイトとなります。そのため、VRAM のサイズは 120x25 = 3,000 となります。16進数で 0xBB8 であるため、VRAM の終端は 0xF300 + 0xBB8 - 1 = 0xFEB7 となります。


  • 画面を初期化する
BASIC で、CONSOLE 0,25,0,1:WIDTH 80,25:COLOR 7,0,0として 80文字モードにするのとほぼ同じ初期化をマシン語で行う一番簡単な方法は BASIC ROM内ルーチンを呼び出す事です。私は、特によく使う一部の ROMルーチンアドレスを以下のように定義しています。

BIOS:
.NBASIC_VER     equ 0x1850   ; N-BASIC Version番号
.FUNC_COLOR     equ 0x08F7   ; Function Key On/Off、カラーモノクロ指定
.WIDTH          equ 0x093A   ; CRT 画面表示文字数の設定
.CURSOR_OFF     equ 0x0BD2   ; カーソル消去
.CMT_OPEN_R     equ 0x0BF3   ; CMT 読み込み開始
.CMT_READ       equ 0x5F9E   ; CMT 1バイト読み込み(or $0C88)
.CMT_CLOSE      equ 0x0C2E   ; CMT 終了
.MONITOR equ 0x5C66 ; モニターモードに戻る
実際の画面の初期化は以下のようになります。ついでに SP も初期化してしまいます。私は SP = 0x0000 に設定する事が多いです。理由はだいたい空いているためです。

    ld      sp, 0
    call    BIOS.CURSOR_OFF     ; カーソル消去
    ld      bc, $00FF
    call    BIOS.FUNC_COLOR     ; ファンクションキーを消してカラーに
    ld      bc, 80 * 256 + 25
    call    BIOS.WIDTH          ; CRT 画面表示文字数の設定


  • まずはともかく文字を出す
VRAM が 0xF300 からなので、ここに文字コードを格納するとそのまま文字が出ます。テストするには、いきなりマシン語で組まず、PC-8001 のモニタモードから試してみましょう。
文字を入力する
このスクショは、0xF300 から 0x40, 0x41, 0x42, 0x43, 0x44, 0x45 と Sコマンドを使って書き込んだ結果です。画面の一番左上から @ABCDE と表示されている事が見えるかと思います。PC-8001 はこのように VRAM エリアに文字コードを書き込むだけで、画面に文字が出ます。

これで 0xF300 から文字コードを書き込むと、何かしら文字が出る事が分かりましたので、続いてマシン語で試してみましょう。画面は事前に初期化してある前提とします。

        org       0x9000        ; 実行開始アドレス
        ld        b, 80         ; 80文字だけ書き込む
        ld        hl, 0xF300    ; HL をVRAMアドレスに設定する
        ld        a, 0x00       ; Acc を 0x00 にする
.loop   ld        (hl), a       ; HL を示す場所に Acc の内容を書き込む
        inc       hl           ; VRAMアドレスを + 1 する
        inc       a            ; 書き込む値を +1 する
        dec       b            ; 描画カウンタを -1 する
        jr        nz, .loop     ; ゼロじゃなければ繰り返す
        ret                     ; 終了する(エラーで止まる)
ここで使っているのは、汎用レジスタ H,L,B とアキュムレーター Acc です。ld (hl),a は、HL が VRAMアドレスを指し示していますので、そこに Acc の値を書き込む動作をしています。inc が +1 の動作、dec が -1 の動作をします。そして、dec b の結果が 0 になれば ZF=1 とフラグが変化します。jr nz,.loop は「事前の計算結果がゼロじゃなければ.loopにジャンプ」という動作となります。これで1行分繰り返して動作するようになります。


  • アトリビュート
PC-8001は文字に色を付けるために、少し変わって仕様を採用しています。各行毎に<左からの距離, 属性情報>という2バイトのペアが20個並んでいます。論よりRUNで、まずはまたモニタから試してみます。
色を入力する
左半分が緑色、右半分が赤色になったのが見えるかと思います。0xF350 が一番上の行のアトリビュートの開始アドレスですので、そこから 0x00,0x88 で 0文字目(左端)に属性コード 0x88 を、続いて 0x03, 0x48 で3文字目に赤色を設定しています。

左からの位置は必ず左に設定した値より大きくします。 00,88,03,48,02,88 と書くと動作は保証されません。書き込む属性コードは以下のような意味になっています。

ATRB:
.BLACK      equ     %00001000    ; 黒
.BLUE       equ     %00101000    ; 青
.RED        equ     %01001000    ; 赤
.MAGENTA    equ     %01101000    ; 紫
.GREEN      equ     %10001000    ; 緑
.CYAN       equ     %10101000    ; 水
.YELLOW     equ     %11001000    ; 黄
.WHITE      equ     %11101000    ; 白
先ほど手入力した 0x88 は、ATRB.GREEN なので、画面の色が緑色になったというわけです。途中で緑色の範囲が広がってから後方の赤色が表示されていますが、このようなタイミングが悪いと書き換え途中が見えてしまう事があります。これが目立つとゲーム中でちらつきとして見えて0しまいます。防止するには一気に書き換えるか、垂直帰線が裏に行っている時を狙って書き換えるかとなります。
※ アトリビュート制御は高速に処理しようとするとかなりの手間がかかります。

では、マシン語でアトリビュートを書き換えてみましょう。

        org     0x9100          ; 執行開始アドレス
        ld      hl, .atrb       ; HL データの位置
        ld      de, 0xF350      ; DE 書き換えるアトリビュートアドレス
        ld      bc, 4           ; BC 書き換えるサイズ
        ldir                    ; (HL)→(DE) に BC サイズ分ブロック転送
        ret
.atrb   db      0x00, 0x88, 0x03, 0x48
今回は実行速度を気にしてブロック転送を使ってみました。実は4バイト転送であれば、BC に転送サイズを入れたりせず LDI 命令を並べて記述してしまう事が殆どです。理由は断然こちらの方が高速に動作するためです。

        org     0x9100          ; 実行開始アドレス
        ld      hl, .atrb       ; HL データの位置
        ld      de, 0xF350      ; DE 書き換えるアトリビュートアドレス
        ldi                     ; 転送1回目
        ldi                     ; 転送2回目
        ldi                     ; 転送3回目
        ldi                     ; 転送4回目
        ret
.atrb   db      0x00, 0x88, 0x03, 0x48
殆どのアセンブラには、マクロ展開が実装されています。それを使った記述は以下の通りとなります。※ 本内容は tools80 に準拠しています。

        org     0x9100          ; 実行開始アドレス
        ld      hl, .atrb       ; HL データの位置
        ld      de, 0xF350      ; DE 書き換えるアトリビュートアドレス
        REPT    4               ; 4回展開する
        ldi                     ; 1バイトブロック転送
        ENDM                    ; ここまでを展開する
        ret
.atrb   db        0x00, 0x88, 0x03, 0x48
上記3つのプログラム全てで動作結果は同じとなります。