Skip to content →

interrupts, vblank and nametables

nmi

For the NES to be able to populate our nametables with the proper values, aside to the OAM DMA, we need at least to be able to handle the NMI interrupt. NMI stands for Non-Maskable Interrupt, an interrupt the CPU always has to handle, no matter what.

For an NMI to be flagged, we need two flags to be true: NMI_occured and NMI_output

Start of VBLANK NMI_occurredtrue
End of VBLANK (after) NMI_occurredfalse
Read of PPUSTATUS NMI_occurred(NMI_occured to bit 7 of PPUSTATUS) then false
Write to PPUCTRLNMI_output= bit 7 of PPUCTRL

When the CPU handles the NMI, it is supposed to push the Program Counter (PC) onto the stack, then push the Status Register (P) onto the stack. [PC-high-byte -> PC-low-byte -> P]

Then the PC will then be set to memory[0xfffb] << 8 | memory[0xfffa]. So, fetching the new high-byte for PC from 0xfffb and the new low-byte for PC from 0xfffa.

vblank

The VBlank (vertical blanking – the time where the PPU isn’t occupied with drawing pixels), appears from scanlines 241, ppuCyle 1 up until scanline 261, ppuCycle 1. Each scanline consists of 340 ppuCycles.

We acknowledge the VBlank by setting bit 7 of PPUSTATUS to 1 or 0.

void stepPPU() {

	//	stepPPU
	ppuCycles++;
	if (ppuCycles > 340) {
		ppuCycles -= 341;
		ppuScanline++;
	}

	if (0 <= ppuScanline && ppuScanline <= 239) {	//	drawing

	} 
	else if (ppuScanline == 241 && ppuCycles == 1) {	//	VBlank 
		PPU_STATUS.setVBlank();
		NMI_occured = true;
		drawNameTables();
		handleWindowEvents();
	}
	else if (ppuScanline == 261 && ppuCycles == 1) {	//	VBlank off / pre-render line
		PPU_STATUS.clearVBlank();
		NMI_occured = false;
		ppuScanline = 0;
	}

}

nametables

With the NMI and VBlank working, we are now able to let the code populate our nametables correctly.

The 4 nametables are part of the VRAM, are of 256 byte size each and are located at 0x2000, 0x2400, 0x2800 and 0x2c00. In some games, nametables are mirrored to other nametables, but we will get to that later on.

Each byte in the nametables represents a tile number, corresponding to the Pattern Tables we finished earlier. Which of the nametables (0 or 1) the tile number is a part of, is dictated by bit 4 of PPUCTRL.

With this information, and a little modification of our drawing routine from the Pattern Tables, we can already display all 4 nametables, and see the demo of “Donkey Kong”.

for (int r = 0; r < 1024; r++) {
	for (int col = 0; col < 256; col++) {
		uint16_t tile_nr = VRAM[0x2000 + (r / 8 * 32) + (col / 8)];
		uint16_t tile_attr = VRAM[0];

		uint16_t adr = PPU_CTRL.background_pattern_table_adr_value + (tile_nr * 0x10) + (r % 8);
		uint8_t pixel = ((VRAM[adr] >> (7 - (col % 8))) & 1) + (((VRAM[adr + 8] >> (7 - (col % 8))) & 1) * 2);
		framebuffer[(r * 256 * 3) + (col * 3)] = COLORS[pixel];
		framebuffer[(r * 256 * 3) + (col * 3) + 1] = COLORS[pixel];
		framebuffer[(r * 256 * 3) + (col * 3) + 2] = COLORS[pixel];
	}
}

SDL_UpdateTexture(texture_nt, NULL, framebuffer, 256 * sizeof(unsigned char) * 3);
SDL_RenderCopy(renderer_nt, texture_nt, NULL, NULL);
SDL_RenderPresent(renderer_nt);
Note: For simplicity's sake, the 4 nametables are drawn below each other, and not in the 2x2 pattern, but we will change this later on. The following gif is cropped to the first / top nametable.

first nametable – Donkey Kong – no palettes

Comments

Leave a Reply