VDPを直接操作する その2+
BIOSのVRAM処理を自作する
前回の記事で直接VDPを操作することが可能になりました。
今回のサンプルでは今まで使っていたBIOSのVRAM処理を完全に自作化しています。とりあえず、ソースコードをゲットだぜ!
https://github.com/sailorman-msx/games/tree/main/src/sample017
今回のサンプルでは前回記事で説明したスプライトの並び替えロジックを使って、ボールが9個同時に画面内を跳ね回るサンプルになっています。
どのように変更したの?
vram.asm を参照してみましょう。前々回までの記事で使っていたBIOSのLDIRVMやLDIRMV、WRTVRM、REDVRMなどの代替サブルーチンを作成してみました。
(vram.asm 抜粋)
;---------------------------------------------
; VRAM関連サブルーチン群
; ()内はMSX1でのステート数
; MSX1とそれ以外ではステート数が異なる
; 参考:https://taku.izumisawa.jp/Msx/ktecho2
;---------------------------------------------
;--------------------------------------------
; SUB-ROUTINE: VDPRED
; VDPポート#1に対してREAD宣言を行う
; HLレジスタにVRAMアドレスをセットして呼び出す
;--------------------------------------------
VDPRED:
;--------------------------------------------
; SUB-ROUTINE: VDPWRT
; VDPポート#1に対してWRITE宣言を行う
; HLレジスタにVRAMアドレスをセットして呼び出す
;--------------------------------------------
VDPWRT:
;--------------------------------------------
; SUB-ROUTINE: REDVRM
; HLレジスタにセットされたVRAMアドレスの
; 値を1バイトだけ読み込みAレジスタに格納する
; HLレジスタは読み込み後インクリメントされる
;--------------------------------------------
REDVRM:
;--------------------------------------------
; SUB-ROUTINE: WRTVRM
; HLレジスタにセットされたVRAMアドレスに
; Aレジスタの値を1バイトだけ書き込む
; HLレジスタは書き込み後インクリメントされる
;--------------------------------------------
WRTVRM:
;---------------------------------------------------
; SUB-ROUTINE: REDVRMSERIAL
; HLレジスタにセットされたメモリアドレスに
; DEレジスタで指定されたVRAMアドレスの内容を
; BCレジスタの値だけ連続して読み込む
; HLレジスタとDEレジスタは
; 書き込み後BCレジスタの数だけ加算される
;---------------------------------------------------
REDVRMSERIAL:
;---------------------------------------------------
; SUB-ROUTINE: WRTVRMFIL
; HLレジスタにセットされたVRAMアドレスに
; Aレジスタの値をBCレジスタの値だけ連続して書き込む
; HLレジスタは書き込み後BCレジスタの数だけ加算される
;---------------------------------------------------
WRTVRMFIL:
;---------------------------------------------------
; SUB-ROUTINE: WRTVRMSERIAL
; HLレジスタにセットされたメモリアドレスの値を
; DEレジスタにセットされたVRAMアドレスに
; BCレジスタの値だけ連続して書き込む
; HLレジスタとDEレジスタは書き込み後
; BCレジスタの数だけ加算される
;---------------------------------------------------
WRTVRMSERIAL:
具体的には以下のBIOSサブルーチンを書き換えています。
; BIOSルーチン
REDVRM:equ $004A ; VRAMの内容をAレジスタに読み込む
→ 自作のREDVRMに書き換え
WRTVRM:equ $004D ; VRAMのアドレスにAレジスタの値を書き込む
→ 自作のWRTVRMに書き換え
SETRED:equ $0050 ; VRAMからデータを読み込める状態にする
→ 自作のVDPREDに書き換え
SETWRT:equ $0053 ; VRAMにデータを書き込める状態にする
→ 自作のVDPWRTに書き換え
FILVRM:equ $0056 ; VRAMの指定領域を同一のデータで埋める
→ 自作のWRTVRMFILに書き換え
LDIRMV:equ $0059 ; VRAMからRAMにブロック転送する
→ 自作のREDVRMSERIALに書き換え
LDIRVM:equ $005C ; RAMからVRAMにブロック転送する
→ 自作のWRTVRMSERIALに書き換え
MSXのステート数とMSX2以降のステート数は違う
ステート数(まあ、クロック数みたいなもんです)については過去記事で説明していますが、MSX1の場合M1サイクルというものが付加されるらしく前回記事でのサンプルコードでのステート数に間違いがありました。お詫びして訂正いたします。
今回のvram.asmに各命令のステート数を記述しています。ステート数の全容は以下のURLに記載されています。
とりあえずはこれで、BIOSを介さずVRAMアクセスできるようなサブルーチン群が出来たことになりますね。やったぜ!
2の補数とマシン語での負の値について
さて、ここからはVDPの話ではなくマシン語の話になります。
面倒かもしれませんが重要なので説明しますね。
ここまででいくつかのコードを記載していますが、2の補数についてはまったく考慮しないコードになっていました。
ところで「2の補数って何よ?」というところから説明します。
マシン語での負の値
ここでは8ビットの値を対象として説明します。8ビットは0-255までの数字です。これは正の値になりますね。符号なし整数とも呼ばれます。
じゃあ、負の値ってどう表現するの?という点ですが、コンピュータには負の値なんて存在しない。というのが答えで、どの値なら正なのか、どの値なら負なのか?というのはプログラミングによって決定されます。
基本的には第7ビットに1が立ってたら負の値、0なら正の値として切り分けます。この切り分けを行う場合、第7ビットは「符号ビット」と呼ばれます。
切り分けを行わない場合は、正の値になります(0-255の値)。
正の値であっても255(11111111B)に1(1B)を加算すると、00000000Bになります。256以上は存在しないので255を超えると0に戻るというイメージです。
さて、今回の記事では符号ビットを使った負の値の表現について説明します。
0から1を引くと何になる?
0(00000000B)から1(00000001B)を減算すると、値は255(11111111B)になります。さらに1を減算すると、254(11111110B)になります。
「なんだよ、そんなの当たり前じゃん?」って思うかもしれませんね。
たしかにその通りなのですが、それでは符号つきの場合は、どの値からどの値まで使えるでしょうか?
答えは、-128(10000000B)から+127(01111111B)です。
プラス1は00000001B、プラス2は00000010Bです。
マイナス1は11111111B、マイナス2は11111110Bです。
わかりますか?ついてきてください(汗)
2の補数とはなんぞ?
「2の補数」というのは2になると桁上がりする数値のことです。
0だと2を足せば桁上がりします。(0Bが10Bになる)
1だと1を足せば桁上がりしますね。(1Bが10Bになる)
この「2を足せば」「1を足せば」というこの数を「2の補数」と呼びます。
もっとコンピュータ的に表現するならば
「元の数値のビットを反転させて、その数字に1を足した値が2の補数」です。
2の補数の例
それでは、0という数字の2の補数を見てみましょう。
0という数値をビットに変換します。00000000Bになりますね。
この数のビットをすべて反転します。11111111Bになります。
この値に1を加算します。255を超えるので00000000Bになりますね。
0に対する2の補数は「0」です。
次に1という数字の2の補数を見てみましょう。
1という数値をビットに変換します。00000001Bになりますね。
この数のビットをすべて反転します。11111110Bになります。
この値に1を加算します。11111111Bになりますね。
1に対する2の補数は11111111Bということです。(ここ重要)
次に-1という数字の2の補数を見てみましょう。
-1という数値をビットに変換します。マイナス1は11111111Bです。
この数のビットをすべて反転します。00000000Bになります。
この数値に1を足します。00000001Bになりますね。
-1に対する2の補数は00000001Bということです。(ここ重要)
1に対する2の補数は-1
-1に対する2の補数は1
これ、符号が逆転するだけの結果になるってことです。
プラス1をマイナス1に逆転させたければ1の2の補数を使えばいいし、逆にマイナス1をプラス1に逆転させたければ-1の2の補数を使えばいい。ということになります。
わかりますか?ついてきてください(汗)
Z80のマシン語ではどう書くの?NEG命令
ご安心ください。そのものずばり「2の補数を取得する」という命令があるのです。それがNEG命令です。
NEG
と書くとAレジスタに格納されている値の2の補数がAレジスタに格納されます。便利です。
LD A, 1
NEG
と書くと、NEGのあとのAレジスタの値は-1(11111111B)になります。
今回のサンプルコードではball.asmで使っています。
(ball.asm 抜粋)
MoveSpriteReverseXMove:
ld (hl), a
call GetSpriteColor
inc l
ld (hl), a
ld hl, (WK_HLREGBACK)
inc l
inc l
inc l
inc l
ld a, (hl)
; Xの移動方向の符号を反転する
neg
ld (hl), a
ret
MoveSpriteReverseYMove:
ld (hl), a
call GetSpriteColor
inc l
ld (hl), a
ld hl, (WK_HLREGBACK)
inc l
inc l
inc l
inc l
inc l
ld a, (hl)
; Yの移動方向の符号を反転する
neg
ld (hl), a
ret
これらのサブルーチンはボールが壁にぶつかったときに進行方向を逆転するためにNEGを使ってプラマイを逆転させています。
コードをじっくり読んだらわかるかも??
とりあえず、第7ビットを使うと数値に符号をつけることができる。2の補数を使うとプラマイ逆転できる。ということだけ理解できればそれでOKです。
今回の記事では、マシン語での負の値の取り扱いについて説明したいがためにサンプルコードを作ったようなもんです(汗)
閑話休題:拙作が世界デビュー
以前作り上げた拙作のゲーム「HEAVEN DOOR」ですが、作者の私が知らないうちに世界デビューしてました。
うれしいやらはずかしいやら、でも嬉しいです。
中学生のときに実現できなかったことが実現できました。
継続は力なりですね!これからも精進いたします!!
ではまた!ノシ
セーラー服が似合うおじさんです。猫好き、酒好き、ガジェット好き、楽しいことならなんでも好き。そんな「好き」をつらつらと書き留めていきます。