パッド入力
VRAM アクセスを想定したプログラムで、ゲームの「出力」は見られたので、次は「入力」を考慮したプログラムを作ってみようと思います。
いろいろやってみようと思ったのですが、長いプログラムになってしまうため、「入力パッドの読み取り」「キャラクター座標の移動」「コマンド入力」の3つに分けて考えます。
目次
Z80 vs 6502 トップページ(別ページ)
概要
入力パッドを読み取って、「新たに押されたボタン」を簡単に読み取れるようにします。
パッドの読み取りは、MSX でもファミコンでも面倒だったのですが、ここでは GAMEPAD という1バイトのメモリにすでに読み取られたものが入っているものとします。
今はデータ内容について考える必要はありませんが、BIT ごとにスイッチに対応していて、入力があった BIT が 1 になるものと考えてください。
GAMEPAD の値は「現在の」状態です。これを元に、「新たにボタンが押された」部分だけがすぐに読み取れる PAD_NEW を作り出してください。
1回前の状態の GAMEPAD を PAD_OLD に保存してあれば
PAD_NEW = (GAMEPAD XOR PAD_OLD) AND GAMEPAD
で作り出せます。
作りだした PAD_NEW が 0 ではない場合(なにか新たな入力があった場合)には、過去 16回分の入力を PADHIST に記録してください。
記録方式は自由とします。これは、後にコマンド入力の検査に使うためのものです。
・ローカルルール
分岐がある場合「一番時間がかかる」ルートのクロック数を計測します。
入力はゲームの基本処理であり、最悪の状況で速いプログラムが良いためです。
Z80 基本
LD HL,(GAMEPAD) ; 16 (17)
LD A,L ; 4 (5)
XOR H ; 4 (5)
AND L ; 4 (5)
LD H,A ; 4 (5)
LD (PAD_NEW),HL ; 16 (17)
JR Z,END ; 12 / 7 (13 / 8)
LD B,H ; 4 (5)
LD A,(BUFPTR) ; 13 (14)
INC A ; 4 (5)
AND 0FH ; 7 (8)
LD (BUFPTR),A ; 13 (14)
LD E,A ; 4 (5)
LD D,0 ; 7 (8)
LD HL,PADHIST ; 10 (11)
ADD HL,DE ; 11 (12)
LD (HL),B ; 7 (8)
END:
GAMEPAD: DB 0
PAD_OLD: DB 0
PAD_NEW: DB 0
BUFPTR : DB 0
PADHIST:
GAMEPAD と PAD_OLD を並べることで、処理に必要な2つのデータを1回の 16bit アクセスで取り出しています。
そして、PAD_OLD と PAD_NEW を並べることで、処理結果と次のために必要なデータの2つを、1回の 16bit アクセスで書きこんでいます。
あとは基本に忠実な形でリングバッファに書き込んでいるだけです。
リングバッファ書き込み時に一番時間がかかります。
総計で 152 クロックです。
6502 基本
LDA GAMEPAD ; 4
TAX ; 2
EOR PAD_OLD ; 4
AND GAMEPAD ; 4
STA PAD_NEW ; 4
STX PAD_OLD ; 4
BEQ END ; 3 / 2
LDX BUFPTR ; 4
STA PADHIST,X ; 5
INC X ; 2
TXA ; 2
AND #$0F ; 2
STA BUFPTR ; 4
END:
ゼロページを使っていません。GAMEPAD や PAD_NEW は重要な情報なのでゼロページに持ってもいいように思うのだけど、6502 の方が速そうなので、Z80 に対するハンデです。
(Z80 が劇的に高速化されることがあったら、ゼロページ使用を解禁しようかと思います)
Z80 とほぼ同じプログラム内容ですが、メモリの読み書きは 8bit づつです。(16bit は扱えないので)
やはりリングバッファ書き込み時に一番時間がかかり、43クロックです。
速度判定
6502 は 43クロック。周波数を考慮して倍にすると、MSX での 86クロック相当の実時間になります。
Z80 は 152クロックなので、差は 66クロック 43% ファミコンが速いです。
単純でメモリ参照が多いプログラムなので、メモリ操作の苦手な Z80 にはかわいそうな例題かもしれません。
余談
MSX ではジョイスティックの読出しは、BIOS を使用することが推奨されていました。
でも、I/O アクセス4回ほどで直接知ることができます。
LD A,0FH ; 7 (8)
OUT (A0H),A ; 11 (12)
LD A,2FH ; 7 (8)
OUT (A1H),A ; 11 (12)
LD A,0EH ; 7 (8)
OUT (A0H),A ; 11 (12)
IN A,(A2H) ; 11 (12)
72クロックで PAD の状態がわかります。BIOS はこの後に、結果を使いやすくする変換処理が入るのでもっと遅かったろうけど。
(A の必要なところだけ AND で取り出して、テーブルアドレスに足してテーブルの値取り出して…たぶん、BIOS コールで 150クロックくらいは行くのではないかと思う。BIOS 読んだわけではないけど。)
これに対し、ファミコンではジョイパッドは 1bit づつ読み出す必要がありました。
LDA #$01 ; 2
STA $4016 ; 4
LDA #$00 ; 2
STA $4016 ; 4
LDX #8 ; 2
LOOP:
LDA $4016 ; 4
ROR A; 2
ROL >0 ; 5
DEX ; 2
BNE LOOP ; 3 / 2
141 クロックです。まぁ、8回ループ展開すれば 100 クロックです。
今回は CPU 勝負なので実際の I/O 速度は想定していませんが、これを足せばファミコンは 150クロック程度、MSX は BIOS 使用で 300 クロック程度だったかな、と思っています。
つまり、周波数を考慮すれば、ここでの勝負も互角。I/O アクセスってかなり遅い処理なので、実際にゲームを作る際も GAMEPAD のようにメモリに一旦置いといて、必要な際にはメモリを参照するようにするのが普通でした。