From 39510c80fb11e7746f851fe7d9e7d0b7589da0db Mon Sep 17 00:00:00 2001 From: tyfkda Date: Fri, 15 Mar 2024 09:02:33 +0900 Subject: [PATCH 1/3] Set V-blank on line 241 https://www.nesdev.org/wiki/PPU_rendering > ### Post-render scanline (240) > The PPU just idles during this scanline. Even though accessing PPU memory > from the program would be safe here, the VBlank flag isn't set until after > this scanline. > > ### Vertical blanking lines (241-260) > The VBlank flag of the PPU is set at tick 1 (the second tick) of scanline > 241, where the VBlank NMI also occurs. The PPU makes no memory accesses > during these scanlines, so PPU memory can be freely accessed by the program. --- src/app/js_powered_app.ts | 2 +- src/nes/mapper/mapper005.ts | 2 +- src/nes/nes.ts | 2 +- src/nes/ppu/ppu.ts | 14 +++++++++++--- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/app/js_powered_app.ts b/src/app/js_powered_app.ts index 03e6d3e..d7e0560 100644 --- a/src/app/js_powered_app.ts +++ b/src/app/js_powered_app.ts @@ -158,7 +158,7 @@ export class JsApp extends App { this.jsNes.update() this.updateAudio() - for (let i = VBlank.START; i <= VBlank.END; ++i) { + for (let i = VBlank.NMI; i <= VBlank.END; ++i) { this.jsNes.setHcount(i) } } diff --git a/src/nes/mapper/mapper005.ts b/src/nes/mapper/mapper005.ts index 2a39acb..709e7ed 100644 --- a/src/nes/mapper/mapper005.ts +++ b/src/nes/mapper/mapper005.ts @@ -124,7 +124,7 @@ export class Mapper005 extends Mapper { // Note: BGs OR sprites MUST be enabled in $2001 (bits 3 and 4) // in order for the countdown to occur. const regs = this.options.getPpuRegs() - this.ppuInFrame = hcount < VBlank.START && (regs[PpuReg.MASK] & (PpuMaskBit.SHOW_SPRITE | PpuMaskBit.SHOW_BG)) !== 0 + this.ppuInFrame = hcount < VBlank.NMI && (regs[PpuReg.MASK] & (PpuMaskBit.SHOW_SPRITE | PpuMaskBit.SHOW_BG)) !== 0 if (this.ppuInFrame && this.irqHlineEnable && this.irqHlineCompare === hcount && hcount !== 0) { this.options.requestIrq(IrqType.EXTERNAL) } diff --git a/src/nes/nes.ts b/src/nes/nes.ts index 01e226f..6a2e001 100644 --- a/src/nes/nes.ts +++ b/src/nes/nes.ts @@ -301,7 +301,7 @@ export class Nes { this.apu.onHblank(hcount) this.mapper.onHblank(hcount) - if (hcount === VBlank.START) + if (hcount === VBlank.NMI) this.eventCallback(NesEvent.VBlank, (leftCycles / VCYCLE) | 0) } this.cycleCount = nextCycleCount diff --git a/src/nes/ppu/ppu.ts b/src/nes/ppu/ppu.ts index ca194d8..68c7c51 100644 --- a/src/nes/ppu/ppu.ts +++ b/src/nes/ppu/ppu.ts @@ -373,10 +373,18 @@ export class Ppu { this.checkSprite0Hit(hcount) switch (hcount) { - case VBlank.START: - this.setVBlank() - break + // case VBlank.START: + // > Post-render scanline + // > The PPU just idles during this scanline. Even though accessing PPU memory + // > from the program would be safe here, the VBlank flag isn't set until after + // > this scanline. + // break case VBlank.NMI: + // > Vertical blanking lines (241-260) + // > The VBlank flag of the PPU is set at tick 1 (the second tick) of scanline + // > 241, where the VBlank NMI also occurs. The PPU makes no memory accesses + // > during these scanlines, so PPU memory can be freely accessed by the program. + this.setVBlank() if ((this.regs[PpuReg.CTRL] & PpuCtrlBit.VINT_ENABLE) !== 0) this.triggerNmi() break From fb71757fee1aae91c10d45d86d8b0af20ad61617 Mon Sep 17 00:00:00 2001 From: tyfkda Date: Sun, 17 Mar 2024 08:42:33 +0900 Subject: [PATCH 2/3] Delay NMI for a while Some games fails to run if this patch is not applied. --- src/nes/cpu/cpu.ts | 29 ++++++++++++++++++----------- src/nes/nes.ts | 2 +- test/nes/cpu/cpu.spec.ts | 3 ++- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/nes/cpu/cpu.ts b/src/nes/cpu/cpu.ts index e983c1d..e6fb175 100644 --- a/src/nes/cpu/cpu.ts +++ b/src/nes/cpu/cpu.ts @@ -65,6 +65,7 @@ export class Cpu { private carry: Bit = 0 private pc: Address // Program counter + private nmiRequest = -1 private irqRequest = 0 private stallCycles = 0 @@ -128,17 +129,8 @@ export class Cpu { } // Non-maskable interrupt - public nmi(): void { - const vector = this.read16(VEC_NMI) - if (this.breakPoints.has(BreakType.NMI)) { - this.paused = true - console.warn(`paused because NMI: ${Util.hex(this.pc, 4)}, ${Util.hex(vector, 4)}`) - } - - this.push16(this.pc) - this.push(this.getStatusReg() & ~BREAK_FLAG) - this.pc = vector - this.irqBlocked = 1 + public requestNmi(): void { + this.nmiRequest = 2 // TODO: confirm. } public requestIrq(type: IrqType): void { @@ -154,6 +146,21 @@ export class Cpu { } public step(): number { + if (this.nmiRequest >= 0) { + if (--this.nmiRequest < 0) { + const vector = this.read16(VEC_NMI) + this.push16(this.pc) + this.push(this.getStatusReg() & ~BREAK_FLAG) + this.pc = vector + this.irqBlocked = 1 + + if (this.breakPoints.has(BreakType.NMI)) { + this.paused = true + console.warn(`paused because NMI: ${Util.hex(this.pc, 4)}, ${Util.hex(vector, 4)}`) + return 0 + } + } + } if (this.irqRequest !== 0 && this.irqBlocked === 0) { this.irqRequest = 0 this.handleIrq() diff --git a/src/nes/nes.ts b/src/nes/nes.ts index 6a2e001..a9a17ba 100644 --- a/src/nes/nes.ts +++ b/src/nes/nes.ts @@ -60,7 +60,7 @@ export class Nes { constructor(opt?: NesOption) { this.bus = new Bus() this.cpu = new Cpu(this.bus) - this.ppu = new Ppu(opt?.nmiFn || this.cpu.nmi.bind(this.cpu)) + this.ppu = new Ppu(opt?.nmiFn || this.cpu.requestNmi.bind(this.cpu)) this.apu = new Apu(this.gamePads, opt?.apuIrqFn || (() => this.cpu.requestIrq(IrqType.APU))) this.eventCallback = (_e, _p) => {} this.breakPointCallback = () => {} diff --git a/test/nes/cpu/cpu.spec.ts b/test/nes/cpu/cpu.spec.ts index 76d228a..aa31d94 100644 --- a/test/nes/cpu/cpu.spec.ts +++ b/test/nes/cpu/cpu.spec.ts @@ -33,7 +33,8 @@ describe('cpu', () => { 0xfffd: 0xab, })) cpu.reset() - cpu.nmi() + cpu.requestNmi() + cpu.step() expect(cpu.getRegs().pc).toBe(0x9876) }) From 5e88bce801692ea65b4a9dd32d38fefa3c6096c8 Mon Sep 17 00:00:00 2001 From: tyfkda Date: Sun, 17 Mar 2024 09:50:12 +0900 Subject: [PATCH 3/3] Triger NMI on writing PPU Ctrl register https://www.nesdev.org/wiki/NMI --- src/nes/ppu/ppu.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/nes/ppu/ppu.ts b/src/nes/ppu/ppu.ts index 68c7c51..181a6dc 100644 --- a/src/nes/ppu/ppu.ts +++ b/src/nes/ppu/ppu.ts @@ -285,6 +285,7 @@ export class Ppu { value &= ~(PpuStatusBit.VBLANK | PpuStatusBit.SPRITE0HIT | PpuStatusBit.SPRITE_OVERFLOW) } + const before = this.regs[reg] this.regs[reg] = value switch (reg as PpuReg) { @@ -295,6 +296,11 @@ export class Ppu { this.ppuAddr = ((this.ppuAddr & ~0x0c00) | ((value & PpuCtrlBit.BASE_NAMETABLE_ADDRESS) << 10)) this.updateCoarseX() + + if ((value & PpuCtrlBit.VINT_ENABLE) !== 0 && + (before & PpuCtrlBit.VINT_ENABLE) === 0 && + (this.regs[PpuReg.STATUS] & PpuStatusBit.VBLANK)) + this.triggerNmi() } break case PpuReg.MASK: @@ -385,7 +391,8 @@ export class Ppu { // > 241, where the VBlank NMI also occurs. The PPU makes no memory accesses // > during these scanlines, so PPU memory can be freely accessed by the program. this.setVBlank() - if ((this.regs[PpuReg.CTRL] & PpuCtrlBit.VINT_ENABLE) !== 0) + if ((this.regs[PpuReg.CTRL] & PpuCtrlBit.VINT_ENABLE) !== 0 && + (this.regs[PpuReg.STATUS] & PpuStatusBit.VBLANK) !== 0) this.triggerNmi() break case VBlank.END: