emu_nes

An NES emulator for learning how the 6502 and similar computers of the era worked
Log | Files | Refs | README | LICENSE

ppu.c (2951B)


      1 #include "ppu.h"
      2 
      3 struct ppu ppu = {0};
      4 
      5 static void
      6 vram_addr_inc(void)
      7 {
      8 	ppu.regs.v += (ppu.ctrl.inc_mode) ? 32 : 1;
      9 }
     10 
     11 static uint16_t
     12 vram_addr_mirror(uint16_t addr)
     13 {
     14 	uint8_t nametable;
     15 
     16 	addr &= 0x2fff;
     17 	addr -= 0x2000;
     18 	nametable = addr / 0x400;
     19 
     20 	switch (ppu.rom->mirror) {
     21 	case M_HORIZONTAL:
     22 		if (nametable == 1 || nametable == 2)
     23 			return addr - 0x400;
     24 		else if (nametable == 3)
     25 			return addr - 0x800;
     26 		break;
     27 	case M_VERTICAL:
     28 		if (nametable == 2 || nametable == 3)
     29 			return addr - 0x800;
     30 	default:
     31 		break;
     32 	};
     33 
     34 	return addr;
     35 }
     36 
     37 void
     38 ppu_tick(void)
     39 {
     40 	ppu.cycles++;
     41 
     42 	if (ppu.cycles > 256 && ppu.cycles <= 320)
     43 		ppu.oam_addr = 0;
     44 
     45 	if (ppu.cycles >= 341) {
     46 		ppu.cycles -= 341;
     47 		ppu.scanlines++;
     48 	}
     49 	if (ppu.scanlines >= 262)
     50 		ppu.scanlines -= 262;
     51 }
     52 
     53 uint8_t
     54 ppu_read(uint16_t addr)
     55 {
     56 	uint8_t tmp;
     57 
     58 	switch (addr) {
     59 	case 0x2002:
     60 		tmp = (ppu.status.vblank << 7)
     61 		    | (ppu.status.sprite0_hit << 6)
     62 		    | (ppu.status.sprite_overflow << 5);
     63 
     64 		ppu.status.vblank = 0;
     65 		ppu.regs.write_toggle = 0;
     66 
     67 		return tmp;
     68 	case 0x2004:
     69 		return ppu.oam[ppu.oam_addr];
     70 	case 0x2007:
     71 		tmp = ppu.last_read;
     72 
     73 		if (addr <= 0x1fff)
     74 			ppu.last_read = ppu.rom->chr_rom[ppu.regs.v];
     75 		else if (addr <= 0x2fff)
     76 			ppu.last_read = ppu.vram[vram_addr_mirror(addr)];
     77 
     78 		vram_addr_inc();
     79 
     80 		return tmp;
     81 	default:
     82 		fprintf(stderr, "Invalid PPU read at address $%04\n", addr);
     83 		return 0;
     84 	}
     85 }
     86 
     87 void
     88 ppu_write(uint16_t addr, uint8_t byte)
     89 {
     90 	switch (addr) {
     91 	case 0x2000:
     92 		ppu.ctrl.nametable_base = byte & 3;
     93 		ppu.ctrl.inc_mode = byte & (1 << 2);
     94 		ppu.ctrl.sprite_tile_sel = byte & (1 << 3);
     95 		ppu.ctrl.bg_tile_sel = byte & (1 << 4);
     96 		ppu.ctrl.sprite_height = byte & (1 << 5);
     97 		ppu.ctrl.master_slave = byte & (1 << 6);
     98 		ppu.ctrl.nmi_enable = byte & (1 << 7);
     99 
    100 		if (ppu.ctrl.nametable_base & 1)
    101 			ppu.regs.scroll_x += 256;
    102 		if (ppu.ctrl.nametable_base & 2)
    103 			ppu.regs.scroll_y += 240;
    104 
    105 		/*
    106 		 * TODO: send NMI if vblank flag still set and nmi_enable
    107 		 * changes from 0 to 1
    108 		 */
    109 		break;
    110 	case 0x2001:
    111 		ppu.mask.grayscale = byte & 1;
    112 		ppu.mask.bg_left_enable = byte & (1 << 1);
    113 		ppu.mask.sprite_left_enable = byte & (1 << 2);
    114 		ppu.mask.bg_enable = byte & (1 << 3);
    115 		ppu.mask.sprite_enable = byte & (1 << 4);
    116 		ppu.mask.colour_emphasis = byte & (1 << 5);
    117 
    118 		break;
    119 	case 0x2003:
    120 		ppu.oam_addr = byte;
    121 
    122 		break;
    123 	case 0x2004:
    124 		ppu.oam[ppu.oam_addr++] = byte;
    125 
    126 		break;
    127 	case 0x2005:
    128 		if (ppu.regs.write_toggle == 0)
    129 			ppu.regs.scroll_x = byte;
    130 		else
    131 			ppu.regs.scroll_y = byte;
    132 		ppu.regs.write_toggle = !ppu.regs.write_toggle;
    133 
    134 		break;
    135 	case 0x2006:
    136 		if (ppu.regs.write_toggle == 0)
    137 			ppu.regs.v = (byte << 8);
    138 		else {
    139 			ppu.regs.v |= byte;
    140 			ppu.regs.v &= 0x3fff;
    141 		}
    142 		ppu.regs.write_toggle = !ppu.regs.write_toggle;
    143 
    144 		break;
    145 	case 0x2007:
    146 		ppu.vram[ppu.regs.v] = byte;
    147 		vram_addr_inc();
    148 
    149 		break;
    150 	case 0x4014:
    151 		break;
    152 	default:
    153 		fprintf(stderr, "Invalid PPU write at address $%04X\n", addr);
    154 		return;
    155 	}
    156 }