CPU address $2001 (write) では画面をモノクロかカラーを選択するレジスタがあり、オーラに見えるものはそのレジスタを描画中に頻繁に書き換えることによって実現している。
正しく表示されないエミュレータでは対象レジスタの取り込み頻度が実機と異なるために発生する。
正しく表示されないエミュレータでは対象レジスタの取り込み頻度が実機と異なるために発生する。
DA7F: jsr $DAD2 ;sequece end, wait vblank interrupt ;wait renderring start line ldx $10 ;3, skip line count DA84: ;uses 114 cycle (last 113 cycle) / loop jsr $DAF4 ;6 + 103 cycle dex ;2 bne $DA84 ;3 or 2 ;color -> monochrome renderring line ldx $11 ;3, effective line count DA8C: ;uses 116 cycle (last 115 cycle) / loop jsr $DAFF; 6+ 57 + 42 (monochorme) + 6 cycle dex ;2 bne $DA8C ;3 or 2
$dad2 は renderring frame の終了処理, vblank interrupt の割り込み待ちが入っている。スクロールレジスタの bit0 を毎フレーム 0 か 1 に切り換えているのは、画面をぶるぶるさせているものと思われる。
;renderring squence end, wait vblank DAD2: ;generate noice sound? lda $11 lsr a lsr a lsr a ora #$30 sta $400C sta $4004 ;update scroll register and #$01 eor $1F sta $2005 ;scroll register port ;wait next vblank interrupt jsr $FE00 lda #$02 sta $4014 ;sprite dma register ldy #$06 DAF0: dey bne $DAF0 rts
jsr $fe00 の先は、無限ループ -> 割り込み発生 -> jsr $fe00 から戻ってくる。3回の pla はステータスレジスタと割り込み発生アドレス ($fe00) を破棄するために pop を 3回やってから、 rti ではなく rts を使用している。
FEEE: jmp $FEEE FECF: lda $FF sta $2000 lda $2002 pla ;status pla ;pc low pla ;pc high rts
jsr $DAF4 を伴う最初の小ループはライン単位の時間つぶしと思われる。PC:$da84 から次の $da84 までの消費サイクル数は手動で数えたら 114 だった。解析文書によると 1 line 当たりの CPU 消費サイクル数は 113.66 (1364/12) らしいので、CPU レベルの精度では適切な設定値だと思われる。
;2+ (2+3) * 0x11 + 2+2 = 91 DAF4: ldy #$12 ;2 DAF6: dey ;2 bne $DAF6 ;3 or 2 ;2 + 2 + 2 + 6 nop ;2 nop ;2 nop ;2 rts ;6
jsr $DAFF を伴う2つ目の小ループは color/monochrome レジスタを切り換えるループで、モノクロになってる期間は 36 cycle。この小ループの消費サイクル数は 116 で、ライン単位待ちより 2 cycle 多い。このため、発生する時間が1ラインあたり 2 cycle ずれるので、1ラインあたりで右に約 1 pixel ずれたモノクロ領域が出せるものと思われる。
このループで消費サイクルを 114 にしたら長方形のモノクロ領域が表示できるかもしれないが、CPU から pixel 単位の計測は精度が粗いので、途中でずれる可能も多々あり、斜めにする方が違和感がないと思われる。
6502 のブランチ命令 (ここでは bne) は分岐発生時に 3 cycle, 分岐なしで 2 cycle となるが、分岐先のアドレス bit15:8 が異なると 4 cycle かかる。このため、 $DAFD での2つの nop はこれが発生しないように意図的に入れた命令である。これがないと 分岐先が $DAFF, 分岐元が $DB01 になるので、プログラマ(おそらく Nasir 氏)がCPU の命令実行カウントに気を配っていたと思われる。
このループで消費サイクルを 114 にしたら長方形のモノクロ領域が表示できるかもしれないが、CPU から pixel 単位の計測は精度が粗いので、途中でずれる可能も多々あり、斜めにする方が違和感がないと思われる。
DAFD: ;adjust cycle count for 'bne' opcode nop nop ;switch monochrome/color register ;2+ (2+3) * 9 + 2+2 = 51 DAFF: ldy #$0A; 2 DB01: dey ;2 bne $DB01 ;3 or 2 ;2+4 = 6 lda #$1F ;2 sta $2001 ;4, monochrome, display sprite and tile layer ;2*3 = 6 ldy #$1E ;2 nop ;2 nop ;2 ;6*4+2*3+6 = 36 jsr $DB19 ;6+6 jsr $DB19 ;6+6 nop ;2 nop ;2 nop ;2 sty $2001 ;6 color, display sprite and tile layer DB19: rts ;6
6502 のブランチ命令 (ここでは bne) は分岐発生時に 3 cycle, 分岐なしで 2 cycle となるが、分岐先のアドレス bit15:8 が異なると 4 cycle かかる。このため、 $DAFD での2つの nop はこれが発生しないように意図的に入れた命令である。これがないと 分岐先が $DAFF, 分岐元が $DB01 になるので、プログラマ(おそらく Nasir 氏)がCPU の命令実行カウントに気を配っていたと思われる。
PPU からの描画位置を CPU から推測することは、スキャンライン単位ではよく行われていて、初期のソフトウェアでは sprite の当たり判定から得ることが出来る。スコアの位置は動いてないものの、メインでは横スクロールするのはよく使われていて、表示途中に描画制御のレジスタを書き換えている。
今回の場合は特殊で、スキャンラインと横座標も計測していることになる。解析の部分にも記載したが、PPU と CPU ののクロックの扱いは異なるので厳密な位置は得られないので推測になってしまう。
今回の場合は特殊で、スキャンラインと横座標も計測していることになる。解析の部分にも記載したが、PPU と CPU ののクロックの扱いは異なるので厳密な位置は得られないので推測になってしまう。
スクロールレジスタの場合は描画途中とはいえども、取り込む頻度が1スキャンラインに1度だったりするので、時間的な余裕はあるのに対して、カラーレジスタはパレットの address を制御するレジスタなので描画中は常に参照する。
VirtuaNES では本来の動作では左隅からモノクロの場合はそのスキャンライン全てがモノクロというのはカラーレジスタの反映頻度をスクロールレジスタと同じにしているからだと思われる。
エミュレータでの修正方法はこのレジスタを書き換えた場合は PPU と同期を強制的にとるか、根本的に CPU と PPU の同期頻度を上げればよい。そもそもこのカラーレジスタ自体を活用しているソフトが少ないのかモノクロレジスタ自体を実装していないエミュレータも結構ある。即反映の描画関連レジスタは他にキャラクタバンクがある。
対応しているエミュレータ(Nestopia や Nintedulator)ではキャラクタバンクの書き換えと PPU の同期を実装したついでにモノクロレジスタもやっておいた、ぐらいかもしれない。
VirtuaNES では本来の動作では左隅からモノクロの場合はそのスキャンライン全てがモノクロというのはカラーレジスタの反映頻度をスクロールレジスタと同じにしているからだと思われる。
エミュレータでの修正方法はこのレジスタを書き換えた場合は PPU と同期を強制的にとるか、根本的に CPU と PPU の同期頻度を上げればよい。そもそもこのカラーレジスタ自体を活用しているソフトが少ないのかモノクロレジスタ自体を実装していないエミュレータも結構ある。即反映の描画関連レジスタは他にキャラクタバンクがある。
対応しているエミュレータ(Nestopia や Nintedulator)ではキャラクタバンクの書き換えと PPU の同期を実装したついでにモノクロレジスタもやっておいた、ぐらいかもしれない。