New PPU, comments.

This commit is contained in:
Andrea Orrù 2013-11-29 21:40:06 +01:00
parent f745c83b1c
commit 3e3fcce6f4
7 changed files with 255 additions and 87 deletions

View file

@ -1 +0,0 @@
-std=c++11

View file

@ -5,8 +5,8 @@ namespace Cartridge {
int banksMap[8]; // Map virtual memory to ROM.
u8* rom;
u8* vRam;
u8* rom; // ROM data.
u8* vRam; // VRAM/VROM data.
/* PRG-ROM access */
template <bool wr> u8 access(u16 addr, u8 v)
@ -19,7 +19,7 @@ template u8 access<0>(u16, u8); template u8 access<1>(u16, u8);
/* CHR-ROM/RAM access */
template <bool wr> u8 chr_access(u16 addr, u8 v)
{
return vRam[addr];
return vRam[addr]; // TODO: support CHR-RAM.
}
template u8 chr_access<0>(u16, u8); template u8 chr_access<1>(u16, u8);
@ -35,7 +35,7 @@ void load(const char* fname)
// Read PRG-ROM and CHR-ROM:
rom = new u8[0x4000 * prgRom_16k];
vRam = new u8[0x2000 * chrRom_8k];
vRam = new u8[0x2000 * chrRom_8k]; // TODO: CHR-RAM.
fread( rom, 0x4000, prgRom_16k, f);
fread(vRam, 0x2000, chrRom_8k , f);

View file

@ -1,6 +1,7 @@
#include <cstdio>
#include <cstdlib>
#include "cartridge.hpp"
#include "ppu.hpp"
#include "cpu.hpp"
namespace CPU {
@ -14,7 +15,7 @@ Flags P;
/* Cycle emulation */
#define T tick()
inline void tick() { return; }
inline void tick() { PPU::step(); PPU::step(); PPU::step(); }
/* Flags updating */
inline void upd_cv(u8 x, u8 y, s16 r) { P.c = (r>0xFF); P.v = ~(x^y) & (x^r) & 0x80; }
@ -27,6 +28,7 @@ template <bool wr> inline u8 access(u16 addr, u8 v = 0)
{
T; u8* r;
if (addr < 0x2000) { r = &RAM[addr % 0x800]; if (wr) *r = v; return *r; } // RAM.
else if (addr < 0x4000) { return PPU::access<wr>(addr % 8, v); } // PPU.
else if (addr < 0x8000) { return 0; }
else { return Cartridge::access<wr>(addr, v); } // ROM.
}

View file

@ -11,14 +11,14 @@ union Flags
{
struct
{
bool c : 1;
bool z : 1;
bool i : 1;
bool d : 1;
bool c : 1; // Carry.
bool z : 1; // Zero.
bool i : 1; // Interrupt priority.
bool d : 1; // Decimal (useless).
bool b : 1;
bool unused : 1;
bool v : 1;
bool n : 1;
bool v : 1; // Overflow.
bool n : 1; // Negative.
};
u8 reg;

View file

@ -4,68 +4,81 @@
namespace PPU {
u8 ciRam[0x800];
u8 palette[0x20];
u8 oam[0x100];
u8 ciRam[0x800]; // VRAM for nametables.
u8 palette[0x20]; // VRAM for palettes.
u8 oam[0x100]; // VRAM for sprites.
Addr vAddr, tAddr;
u8 fX;
u8 oamAddr;
Ctrl ctrl;
Mask mask;
Status status;
Addr vAddr, tAddr; // Loopy V, T.
u8 fX; // Fine X.
u8 oamAddr; // OAM address.
static u8 rd(u16 addr)
Ctrl ctrl; // PPUCTRL ($2000) register.
Mask mask; // PPUMASK ($2001) register.
Status status; // PPUSTATUS ($2002) register.
// Background latches:
u8 nt, at, bgL, bgH;
// Background shift registers:
u8 atShift; u16 bgShiftL, bgShiftH;
// Rendering counters:
int scanline, cycle;
bool frameOdd;
/* Access PPU memory */
template <bool wr> u8 mem_access(u16 addr, u8 v = 0)
{
if (addr < 0x2000) return Cartridge::chr_access<0>(addr);
else if (addr < 0x2800) return ciRam[addr - 0x2000];
else if (addr < 0x3000) return 0x00;
else if (addr < 0x3F00) return ciRam[addr - 0x3000];
else return palette[addr % 0x20];
}
u8* ref;
static u8 wr(u16 addr, u8 v)
{
if (addr < 0x2000) return Cartridge::chr_access<1>(addr, v);
else if (addr < 0x2800) return ciRam[addr - 0x2000] = v;
if (addr < 0x2000) return Cartridge::chr_access<wr>(addr, v); // CHR-ROM/RAM.
else if (addr < 0x2800) ref = &ciRam[addr - 0x2000]; // Nametables.
else if (addr < 0x3000) return 0x00;
else if (addr < 0x3F00) return ciRam[addr - 0x3000] = v;
else return palette[addr % 0x20] = v;
}
else if (addr < 0x3F00) ref = &ciRam[addr - 0x3000]; // Nametables (mirror).
else ref = &palette[addr % 0x20]; // Palettes.
if (wr) return *ref = v;
else return *ref;
}
inline u8 rd(u16 addr) { return mem_access<0>(addr); }
inline u8 wr(u16 addr, u8 v) { return mem_access<1>(addr, v); }
/* Access PPU through registers. */
template <bool write> u8 access(u16 index, u8 v)
{
static u8 res, buffer;
static bool latch;
static u8 res; // Result of the operation.
static u8 buffer; // VRAM read buffer.
static bool latch; // Detect second reading.
/* Write into register */
if (write)
{
res = v;
switch (index)
{
case 0: ctrl.r = v; tAddr.nt = ctrl.nt; break;
case 1: mask.r = v; break;
case 3: oamAddr = v; break;
case 4: oam[oamAddr++] = v; break;
case 5:
if (!latch) { fX = v & 7; tAddr.cX = v >> 3; }
else { tAddr.fY = v & 7; tAddr.cY = v & 0x1F; }
case 0: ctrl.r = v; tAddr.nt = ctrl.nt; break; // PPUCTRL ($2000).
case 1: mask.r = v; break; // PPUMASK ($2001).
case 3: oamAddr = v; break; // OAMADDR ($2003).
case 4: oam[oamAddr++] = v; break; // OAMDATA ($2004).
case 5: // PPUSCROLL ($2005).
if (!latch) { fX = v & 7; tAddr.cX = v >> 3; } // First write.
else { tAddr.fY = v & 7; tAddr.cY = v & 0x1F; } // Second write.
latch = !latch;
case 6:
if (!latch) { tAddr.h = v & 0x3F; }
else { tAddr.l = v; vAddr.r = tAddr.r; }
case 6: // PPUADDR ($2006).
if (!latch) { tAddr.h = v & 0x3F; } // First write.
else { tAddr.l = v; vAddr.r = tAddr.r; } // Second write.
latch = !latch;
case 7: wr(vAddr.addr, v); vAddr.addr += ctrl.incr; break;
case 7: wr(vAddr.addr, v); vAddr.addr += ctrl.incr ? 32 : 1; // PPUDATA ($2007).
}
}
/* Read from register */
else
switch (index)
{
// PPUSTATUS ($2002):
case 2: res = (res & 0x1F) | status.r; status.vBlank = 0; latch = 0; break;
case 4: res = oam[oamAddr]; break;
case 7:
case 4: res = oam[oamAddr]; break; // OAMDATA ($2004).
case 7: // PPUDATA ($2007).
if (vAddr.addr <= 0x3EFF)
{
res = buffer;
@ -73,13 +86,104 @@ template <bool write> u8 access(u16 index, u8 v)
}
else
res = buffer = rd(vAddr.addr);
vAddr.addr += ctrl.incr;
break;
vAddr.addr += ctrl.incr ? 32 : 1;
}
return res;
}
template u8 access<0>(u16, u8); template u8 access<1>(u16, u8);
/* Calculate graphics addresses */
inline u16 nt_addr() { return 0x2000 | (vAddr.addr); }
inline u16 at_addr() { return 0x23C0 | (vAddr.nt << 10) | ((vAddr.cY / 4) << 3) | (vAddr.cX / 4); }
inline u16 bg_addr() { return (ctrl.bgTbl * 0x1000) + (nt * 16) + vAddr.fY; }
/* Increment the scroll by one pixel */
inline void h_scroll() { if (vAddr.cX == 31) vAddr.r ^= 0x41F; else vAddr.cX++; }
inline void v_scroll()
{
if (vAddr.fY < 7) vAddr.fY++;
else
{
vAddr.fY = 0;
if (vAddr.cY == 31) vAddr.cY = 0;
else if (vAddr.cY == 29) { vAddr.cY = 0; vAddr.nt ^= 0b10; }
else vAddr.cY++;
}
}
/* Copy scrolling data from loopy T to loopy V */
inline void h_update() { vAddr.r = (vAddr.r & ~0x041F) | (tAddr.r & 0x041F); }
inline void v_update() { vAddr.r = (vAddr.r & ~0x7BE0) | (tAddr.r & 0x7BE0); }
// Put new data into the shift registers:
inline void reload_shift()
{
bgShiftL = (bgShiftL & 0xFF00) | bgL;
bgShiftH = (bgShiftH & 0xFF00) | bgH;
atShift = (atShift << 2) | (at & 0b11);
}
void pixel()
{
u8 px = ((atShift << 2) & 3) | ((bgShiftH << 1) & 1) | (bgShiftL & 1);
bgShiftL <<= 1;
bgShiftH <<= 1;
}
/* Execute a cycle of a scanline (prerender or visible) */
template <bool pre> void scanline_()
{
static u16 addr;
switch (cycle)
{
// Nametable fetching:
TILE_PHASE_1: if (pre) status.vBlank = false; // Disable VBlank in prerender.
addr = nt_addr(); break;
TILE_PHASE_2: nt = rd(addr); break;
// Attribute fetching:
TILE_PHASE_3: addr = at_addr(); break;
TILE_PHASE_4: at = rd(addr); if (vAddr.cY & 2) at >>= 4;
if (vAddr.cX & 2) at >>= 2; break;
// Background fetching (low bits):
TILE_PHASE_5: addr = bg_addr(); break;
TILE_PHASE_6: bgL = rd(addr); break;
// Attribute fetching (high bits):
TILE_PHASE_7: addr += 8; break;
TILE_PHASE_8: bgH = rd(addr); h_scroll(); reload_shift(); break;
case 256: bgH = rd(addr); v_scroll(); reload_shift(); break; // End of line.
case 257: h_update(); // Update horizontal scrolling info.
case 280 ... 304: // Vertical scrolling update in prerender scanline.
if (pre) v_update();
}
}
inline void scanline_visible() { scanline_<0>(); if (cycle >= 1 && cycle <= 256) pixel(); }
inline void scanline_prerender() { scanline_<1>(); }
/* Execute a PPU cycle. */
void step()
{
switch (scanline)
{
case 0 ... 238: scanline_visible(); break; // Visible scanline.
case 239: scanline_visible(); /*SDL_Flip(s);*/ break; // Last visible scanline.
case 241: if (cycle == 1) status.vBlank = true; break; // Signal VBlank.
case 261: // Pre-render scanline.
scanline_prerender();
if (cycle == 340)
{
// Restart from the first pixel on the screen:
cycle = frameOdd ? 1 : 0; // Skip one cicle on odd frames.
scanline = 0; frameOdd ^= 1;
return; // Don't update scanline and cycle.
}
}
// Update current cycle and scanline:
cycle = (cycle + 1) % 341;
scanline = cycle ? scanline : (scanline + 1);
}
}

View file

@ -1,69 +1,78 @@
#include "types.hpp"
#ifndef PPU_HPP
#define PPU_HPP
namespace PPU {
union Status
{
struct
{
unsigned bus : 5;
unsigned sprOvf : 1;
unsigned sprHit : 1;
unsigned vBlank : 1;
};
u8 r;
};
/* PPUCTRL ($2000) register */
union Ctrl
{
struct
{
unsigned nt : 2;
unsigned incr : 1;
unsigned sprTbl : 1;
unsigned bgTbl : 1;
unsigned sprSz : 1;
unsigned slave : 1;
unsigned nmi : 1;
unsigned nt : 2; // Nametable ($2000 / $2400 / $2800 / $2C00).
unsigned incr : 1; // Address increment (1 / 32).
unsigned sprTbl : 1; // Sprite pattern table ($0000 / $1000).
unsigned bgTbl : 1; // BG pattern table ($0000 / $1000).
unsigned sprSz : 1; // Sprite size (8x8 / 16x8).
unsigned slave : 1; // PPU master/slave.
unsigned nmi : 1; // Enable NMI.
};
u8 r;
};
/* PPUMASK ($2001) register */
union Mask
{
struct
{
unsigned gray : 1;
unsigned bgLeft : 1;
unsigned sprLeft : 1;
unsigned bg : 1;
unsigned spr : 1;
unsigned red : 1;
unsigned green : 1;
unsigned blue : 1;
unsigned gray : 1; // Grayscale.
unsigned bgLeft : 1; // Show background in leftmost 8 pixels.
unsigned sprLeft : 1; // Show sprite in leftmost 8 pixels.
unsigned bg : 1; // Show background.
unsigned spr : 1; // Show sprites.
unsigned red : 1; // Intensify reds.
unsigned green : 1; // Intensify greens.
unsigned blue : 1; // Intensify blues.
};
u8 r;
};
/* PPUSTATUS ($2002) register */
union Status
{
struct
{
unsigned bus : 5; // Not significant.
unsigned sprOvf : 1; // Sprite overflow.
unsigned sprHit : 1; // Sprite 0 Hit.
unsigned vBlank : 1; // In VBlank?
};
u8 r;
};
/* Loopy's VRAM address */
union Addr
{
struct
{
unsigned cX : 5;
unsigned cY : 5;
unsigned nt : 2;
unsigned fY : 3;
unsigned cX : 5; // Coarse X.
unsigned cY : 5; // Coarse Y.
unsigned nt : 2; // Nametable.
unsigned fY : 3; // Fine Y.
};
struct
{
unsigned l : 8;
unsigned h : 8;
unsigned h : 7;
};
unsigned addr : 12;
unsigned addr : 12; // Address part.
unsigned r : 15;
};
void step();
template <bool write> u8 access(u16 index, u8 v = 0);
}
#endif // PPU_HPP

54
src/ppu_case.hpp Normal file
View file

@ -0,0 +1,54 @@
#ifndef PPUCASE_HPP
#define PPUCASE_HPP
#define TILE_PHASE_1 \
case 1: case 9: case 17: case 25: case 33: case 41: case 49: case 57: case 65: \
case 73: case 81: case 89: case 97: case 105: case 113: case 121: case 129: case 137: \
case 145: case 153: case 161: case 169: case 177: case 185: case 193: case 201: case 209: \
case 217: case 225: case 233: case 241: case 249: case 321: case 329: case 337: case 339
#define TILE_PHASE_2 \
case 2: case 10: case 18: case 26: case 34: case 42: case 50: case 58: case 66: \
case 74: case 82: case 90: case 98: case 106: case 114: case 122: case 130: case 138: \
case 146: case 154: case 162: case 170: case 178: case 186: case 194: case 202: case 210: \
case 218: case 226: case 234: case 242: case 250: case 322: case 330: case 338: case 340
#define TILE_PHASE_3 \
case 3: case 11: case 19: case 27: case 35: case 43: case 51: case 59: case 67: \
case 75: case 83: case 91: case 99: case 107: case 115: case 123: case 131: case 139: \
case 147: case 155: case 163: case 171: case 179: case 187: case 195: case 203: case 211: \
case 219: case 227: case 235: case 243: case 251: case 323: case 331
#define TILE_PHASE_4 \
case 4: case 12: case 20: case 28: case 36: case 44: case 52: case 60: case 68: \
case 76: case 84: case 92: case 100: case 108: case 116: case 124: case 132: case 140: \
case 148: case 156: case 164: case 172: case 180: case 188: case 196: case 204: case 212: \
case 220: case 228: case 236: case 244: case 252: case 324: case 332
#define TILE_PHASE_5 \
case 5: case 13: case 21: case 29: case 37: case 45: case 53: case 61: case 69: \
case 77: case 85: case 93: case 101: case 109: case 117: case 125: case 133: case 141: \
case 149: case 157: case 165: case 173: case 181: case 189: case 197: case 205: case 213: \
case 221: case 229: case 237: case 245: case 253: case 325: case 333
#define TILE_PHASE_6 \
case 6: case 14: case 22: case 30: case 38: case 46: case 54: case 62: case 70: \
case 78: case 86: case 94: case 102: case 110: case 118: case 126: case 134: case 142: \
case 150: case 158: case 166: case 174: case 182: case 190: case 198: case 206: case 214: \
case 222: case 230: case 238: case 246: case 254: case 326: case 334
#define TILE_PHASE_7 \
case 7: case 15: case 23: case 31: case 39: case 47: case 55: case 63: case 71: \
case 79: case 87: case 95: case 103: case 111: case 119: case 127: case 135: case 143: \
case 151: case 159: case 167: case 175: case 183: case 191: case 199: case 207: case 215: \
case 223: case 231: case 239: case 247: case 255: case 327: case 335
#define TILE_PHASE_8 \
case 8: case 16: case 24: case 32: case 40: case 48: case 56: case 64: case 72: \
case 80: case 88: case 96: case 104: case 112: case 120: case 128: case 136: case 144: \
case 152: case 160: case 168: case 176: case 184: case 192: case 200: case 208: case 216: \
case 224: case 232: case 240: case 248: case 328: case 336
#endif // PPUCASE_HPP