I've spent some time trying to move the NMI interrupt handler & emitter around, and still haven't been able to get things aligned according to the test. I've also made sure to go through the frame timing article in detail, as well as examining the frame timing SVG to make sure that I seem to be doing things on the right cycle. I've written a fair number of unit tests that test all the edge cases that I'm aware of, and do their best to test timing.
My emulator is using cycle based timing where the PPU is always aligned with the CPU. Every CPU cycle steps three PPU cycles before executing whatever read/write caused the CPU to tick. I am not currently special-casing things like an NMI that happens during a BRK, so I'm not sure if this is perhaps this might be the cause?
I suspect I'm misunderstanding something on the wiki or in the docs, or maybe just making a really simple error. One thing I'm unclear about: If the NMI occurs on the final cycle of the CPU's current instruction... does the handler wait until after the next *instruction* or after the next *cycle* to execute the interrupt? Right now, my implementation waits until the next instruction finishes, which I *think* is right.
I'm hoping some of the relevant code (I've tried to keep it brief) might help someone to point out my (presumably obvious) error.
(full code is here:
https://github.com/samfoo/gones/blob/ma ... ppu/ppu.go)
Code: Select all
// Reading PPUSTATUS
func (p *PPU) Read(location cpu.Address) byte {
switch p.normalize(location) {
case PPUSTATUS:
p.AddressLatch = true
if p.Scanline == POSTRENDER_SCANLINE + 1 && p.Cycle == 1 {
p.suppressVBlankStarted = true
}
if p.Scanline == POSTRENDER_SCANLINE + 1 {
if p.Cycle == 2 {
p.Status.VBlankStarted = true
p.suppressVBlankStarted = true
p.CancelNMI() // If it's already occurred
}
if p.Cycle == 3 {
p.Status.VBlankStarted = true
p.CancelNMI() // If it's already occurred
}
}
serialized := p.Status.Value()
p.Status.VBlankStarted = false
return serialized
// ...
}
}
Code: Select all
// The full PPU cycle code...
func (p *PPU) Step() {
// ...
switch {
// ...
case p.Scanline == POSTRENDER_SCANLINE + 1 && p.Cycle == 1:
if !p.suppressVBlankStarted {
p.Status.VBlankStarted = true
p.GenerateNMI()
}
}
if p.Scanline == POSTRENDER_SCANLINE + 1 && p.Cycle >= 3 {
p.suppressVBlankStarted = false
}
// ...
}
Code: Select all
// Handling instructions and the NMI in the CPU...
func (p *CPU) Step() int {
opcode := Opcode(p.Read(p.PC))
op := p.Operations()[opcode]
p.PC++
if p.Debug { p.Debugf(opcode, op) }
p.Execute(op)
if p.nmi.Occurred && p.nmi.Cycle < (p.cycles - 1) {
p.HandleNMI()
}
// ...
}
Thanks for any further advice/thoughts, and sorry for the long post -- this problem has been driving me crazy for days.