Oct 2, 2020
NES 基礎知識 - PPU がわかりやすい.
PPU's io register: $2000-$2007 $4014
PPUも64kBのアドレス空間をもちVRAMというが, 実質16kBで他はミラー. V$0000-V$3FFF, $2006,$2007でアドレス指定 $2000のbit2でインクリメント幅が1か32. カラーパレットではまた別. Image Palette $3F00-3F9F, Sprite Palette $3F10-3F1F.
$2002: PPU status register Read Only (Read OnlyとはCPU側から見て. CPUが中心の立場で考えている.) bit7 V-Blank, bit4 accept writes to VRAM
$4014: DMA stealing cpu cycle 直接PPUからCPUへ
Address | 内容 |
---|---|
$0000-$0FFF | Pattern Table 0 |
$1000-$1FFF | Pattern Table 1 |
$2000-$23BF | Name Table 0 |
$23C0-$23FF | Attribute Table 0 |
$2400-$27BF | Name Table 1 |
$27C0-$2800 | Attribute Table 1 |
$2800-$2BBF | Name Table 2 |
$2BC0-$2BFF | Attribute Table 2 |
$2C00-$2FBF | Name Table 3 |
$2FC0-$2FFF | Attribute Table 3 |
$3000-$3EFF | Mirrors $2000-$2EFF |
$3F00-$3F0F | Image Palette |
$3F10-$3F1F | Sprite Palette |
3F20-$3FFF | Mirrors $3F00-$3F1F(8倍) |
$4000-$FFFF | Mirrors $0000-$3FFF(4倍) |
覚え方: Pattern Tableの大きさそれぞれ0x1000, Name Table で0x400弱, 残りでAttribute Table, これが4回繰り返されて合計0x1000, さらにこれをミラーで0x1000弱($2000-$2EFFまでしかミラーされない). Palette 0x10ずつ, ミラーで8倍これで0x100, さらに巨大なミラーで$0000-$3FFFを4倍. よって実質は16kBしか持っていない. 実際, PPUアドレスバスも14bitだけとなる.
VBlank, つまりスキャンラインが終わりから次の開始までの隙間時間にVRAMに描画データを更新する. さもないと描画中に更新することになり千切りが起きる. PPUメモリは16bitだが, PPU IO レジスターは8-bitしかないのでアドレスを指定するために hi, lowの順番で$2006に書き込まなくてははならない. $2006を使って指定したPPUアドレスデータは$2007に同期され, ここからPPUアドレスのデータを読み込んだり書き込んだりできる. 書き込むたびに, アドレスは自動的にインクリメントされる. そのインクリメント幅は$2000のbit2で指定され, bit2=0ならば1, bit2=1ならば32ずつインクリメントされる.
PPUの IO レジスタは $2000-$2007, $4014 (DMA)である. $2000-$2007は$2008-$3FFFまでミラーされる. つまり, 2^13/8 = 2^10 = 1024倍ミラーとなっている. $2000,$2001はPPU Control Register 1,2であり,
$2000のbit7が1だとVBlankでNMI割り込みが発生する. ステータスレジスタPにも割り込み制御のフラグがあるが, これはこれはMaskable, つまり通常のIRQに対するdisableのフラグであり, NMI(Non Maskable Interrupt)はより強制的であるためPレジスタでは制御できない. $2000のbit7を0にすることでVBlankでNMI割り込みを発生させない.
$2000のbit5 = 0なら8x8, bit5 = 1なら8x16スプライト使用.
$2001のbit3 = 0/1でバックグラウンド無効/有効, bit4 = 0/1でスプライト無効/有効
$2002はPPU Status Registerでread only. PPUの状態をCPUに伝える. bit7 が1のときVBlankが発生中. bit4 = 0/1 でVRAMへの書き込み無効/有効
CPU IOレジスタを通してPPUとのやりとりの流れがわかったところで, 具体的にPPUのメモリの役割を説明する.
1つのタイルごとに8x8x2bit=16byte, それがBGで256個, スプライトで256個でPPUの$0000から$1FFFが使われる.
パレットは1つで4byteであり, 1byteで一色指定(0-52までの値で指定). BGで4パレット, スプライトで4パレット使う. しかし, 実際にはBGパレットの最初の色($3F00,$3F04,$3F08,$3F0C)は背景色で共通なため背景は16色でなく12+1=13色, スプライトパレットも最初の色($3F10,$3F14,$3F18,$3F1C)は透明色となるため16色でなく12色のみ使用可能.
ネームテーブルは各座標に対応するタイルを指定している. ネームテーブル一つで256x240pixel画面を構成できる. タイルは8x8であり, 32 * 30個並べる. BGタイルは256個であったので, 1byteでBGタイルを指定できる. 属性テーブルからは色情報. パレットからも色情報 30 * 32byte はちょうどネームテーブルの大きさに一致.
属性テーブルは64byteであり, 各byteで4x4 = 16 個のタイルに対して色情報を与える. この16個のタイルをタイルグループとして, このタイルグループはさらに2x2 = 4個のタイルをサブタイルグループとしてサブタイルグループを4つ並べたものと見ることができる. 1byteのうち各2bitで1つのサブタイルグループに使うパレットを指定する. BGに対して4つパレットが用意されている. つまりサブタイルグループでは同じパレットを共有することになる.
NESでは2つのネームテーブル, 属性テーブルしか用意できず, 残りの2つはミラーリングによるものである.
PPUはさらに256byteのObject Attrubte Memory というを持つ. $2003(OAMADDR)でアドレス, $2004(OAMDATA)でデータのやりとりをする. 4byteで1つのスプライトの情報が入る
byte0:y座標
byte1:パターンテーブルのインデックス
byte2:スプライトの属性:bit0-1 パレットインデックス, bit5:1のときBGよりも優先, bit6:スプライトを|反転, bit7:スプライトを-反転.
byte3:x座標
8x16のときは少し複雑になる. byte1が偶数の時, パターンテーブル$0000が使われ, 奇数の時$1000が使われる.
$4014(DMA)で直接書き込める.
SPR-RAM(OAM)の最初の位置に近いほど優先度が高い. 1つのラインに8個までスプライトを描画可能. $2002のbit5で満杯になったことを知らせる.
http://wiki.nesdev.com/w/index.php/PPU_palettes nesdev wiki がやや難しい解説.
Address | 内容 |
---|---|
$3F00 | Universal background color |
$3F01-$3F03 | Background palette 0 |
$3F05-$3F07 | Background palette 1 |
$3F09-$3F0B | Background palette 2 |
$3F0D-$3F0F | Background palette 3 |
$3F11-$3F13 | Sprite palette 0 |
$3F15-$3F17 | Sprite palette 1 |
$3F19-$3F1B | Sprite palette 2 |
$3F1D-$3F1F | Sprite palette 3 |
各パレットは3色持つ. BGの16x16の領域にbackdrop colorとBackground paletteどれか1つの3色が使える. パレットは属性テーブルで指定される.
$3F04/$3F08/$3F0Cはデータを持てるが, PPUでは通常使われない.
$3F10/$3F14/$3F18/$3F1Cは$3F00/$3F04/$3F08/$3F0Cのミラーとなっている. SMBでは$3F10にback drop colorを書き込むことで空色にしているので, ここを実装しないと真っ暗闇になる.
32byteなので次の5bitでその役割を示すと:
43210
|||||
|||++- Pixel value from tile data
|++--- Palette number from attribute table or OAM
+----- Background/Sprite select
NTSCではYIQの表色系を利用しており, nesではRGBではなく直接NTSC信号を生成するので再現はテレビによってまちまちだが, パレットの1色(1byte)では次で色の表示を決めている. これをRGBに変換するには6bit, つまり0x00-0x3Fに対してRGB色を対応させる64色をあらかじめ用意する, というのが一つの方法だ. エミュによってはこのRGB色の対応のさせ方を様々に選ぶことができる. この対応のさせ方をフィルターと言ったりする.
76543210
||||||||
||||++++- Hue (phase, determines NTSC/PAL chroma)
||++----- Value (voltage, determines NTSC/PAL luma)
++------- Unimplemented, reads back as 0
Hue $0 は明るいグレー, $1-$Cは青から赤へ, そして緑, シアンへ. $Dは暗いグレー, $E-$Fは$1D(黒)のミラー. 正式な黒は$0Fまたは$1Dであり, $0Dは黒より暗い色というシグナルとなり問題があるので使われない. https://nesdoug.com/2018/09/05/05-palettes/ で紹介されている動画で問題を確認できる.
2C02 RF Famicom, AV Famicom, NES (both front and top-loading), North American version of the Sharp Nintendo TVは2C02PPUを使用.
PPU関連のレジスタは$2000-$2007まである.
$2000 PPUCTRL write
bit | 内容 |
---|---|
1-0 | Base name table address(0=$2000; 1=$2400; 2=$2800; 3=$2C00) |
2 | VRAM address increment per CPU read/write of PPUDATA (0: add 1, going across; 1 : add 32, going down) |
3 | Sprite pattern table address for 8x8 sprite(0:$1000; 1:$1000; ignored in 8x16 mode) |
4 | Background pattern table address(0:$0000; 1:$10000) |
5 | Sprite size (0: 8x8 pixels; 1:8x16 pixels) |
6 | PPU master/slave select(0:read backdrop from EXT pins; 1:output color on EXT pins) |
7 | Generate an NMI at the start of the vertical blanking interval(0:off; 1:on) |
PPUがvertical blankでありPPUSTATUS($2002)のvblank flagがsetされている時, $2000のbit7 NMI flagを0から1にすると即座にNMI割り込みが発生する. vblank の残り時間が少ない時にこれをすると描画エラーを巻き起こす.
マッピングでPPUアドレス空間はさらに別の場所へ割り当てられたりする.
$0000-$1FFFはCHR-ROMやCHR-RAMへ. バンクスイッチを伴うことが多く広い空間で扱える.
$2000-$2FFFは2kB NES internal VRAMへマッピングされることが多い. $2000-$2FFFの半分はミラーであったことを思い出そう. 新たなマッピングによって, 4kBフルに使い, つまり4つのネームテーブルを使うことができる.
$3000-$3EFFは$2000-$2EFFのミラーであり, PPUのレンダリングでは使われないためカートリッジが自由な用途で使うことができる.
$3F00-$3FFFは変更できず通常通り.
[BBBBBBBB] - Next tile's pattern data,
[BBBBBBBB] - 2 bits per pixel
||||||||
vvvvvvvv
Serial-to-parallel - [AAAAAAAA] <- [BBBBBBBB] - Parallel-to-serial
shift registers - [AAAAAAAA] <- [BBBBBBBB] - shift registers
vvvvvvvv
|||||||| [Sprites 0..7]----+
|||||||| |
[fine_x selects a bit]---->[ Mux ]-------------------->[Priority mux]----->[Pixel]
||||||||
^^^^^^^^
[PPPPPPPP] <- [1-bit latch]
[PPPPPPPP] <- [1-bit latch]
^
|
[2-bit Palette Attribute for next tile (from attribute table)]
各サイクルで, 4つのBG shift registerからbitを取得. どのbitが取得されたかは$2005でセットされるfine X scrollに依存