Problems with ppu_vbl_nmi...

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Problems with ppu_vbl_nmi...
by on (#111756)
I'm having trouble getting my (very rough) PPU implementation to pass some of blargg's PPU tests. I'm certain my implementation is to blame, but I'm not sure how to track down the problem.

When I run 01-vbl_basics.nes, it does some stuff for awhile and then gets stuck in an infinite loop:

Code:
E868  2C 02 20  BIT $2002 = 00                  A:00 X:00 Y:00 P:26 SP:FB
                            ^^
                            alternates between 00 and 80 depending on vblank
E86B  10 FB     BPL $E868                       A:00 X:00 Y:00 P:26 SP:FB


Presumably this loop is polling for v-blank somehow, but: I can't figure out how it could ever exit -- if the accumulator is $00, how could BPL ever do anything but loop back, regardless of the v-blank state...

I've tried to look into the source, but my understanding of 6502 assembly is rudimentary at best. Anyone have any ideas where I might be screwing things up?
Re: Problems with ppu_vbl_nmi...
by on (#111759)
samfoo wrote:
I'm having trouble getting my (very rough) PPU implementation to pass some of blargg's PPU tests. I'm certain my implementation is to blame, but I'm not sure how to track down the problem.

When I run 01-vbl_basics.nes, it does some stuff for awhile and then gets stuck in an infinite loop:

Code:
E868  2C 02 20  BIT $2002 = 00                  A:00 X:00 Y:00 P:26 SP:FB
                            ^^
                            alternates between 00 and 80 depending on vblank
E86B  10 FB     BPL $E868                       A:00 X:00 Y:00 P:26 SP:FB


Presumably this loop is polling for v-blank somehow, but: I can't figure out how it could ever exit -- if the accumulator is $00, how could BPL ever do anything but loop back, regardless of the v-blank state...

I've tried to look into the source, but my understanding of 6502 assembly is rudimentary at best. Anyone have any ideas where I might be screwing things up?


BPL does not look at the current value in A, but rather at the status flags. http://www.obelisk.demon.co.uk/6502/reference.html is a good reference for how instructions affect status flags. There you will see that BIT sets the the N (Negative) flag to bit 7 of the operand, which for $2002 is the VBlank flag. BPL then branches back to the test if the N flag is clear.
Re: Problems with ppu_vbl_nmi...
by on (#111761)
Quote:
The mask pattern in A is ANDed with the value in memory to set or clear the zero flag, but the result is not kept.


This was my confusion. I forgot that the operand being negative sets P regardless of what the result of the bitwise AND is. However: This wasn't the problem...

My debug output was basically doing:

Code:
Read($2002)


Which was VBlank before the CPU ever got a chance to see it. Doh! Fixing that seems to have at least gotten me to a different infinite loop that I'm looking into now.
Re: Problems with ppu_vbl_nmi...
by on (#111762)
Yeah, BIT is a bit tricky in that N and V are set directly from the operand instead of from the result of the AND operation. I would have mentioned that if only I had remembered that BIT ever ANDed (it'd be a weird bit test instruction if it didn't though :P). That's what you get for only having emulated 6502 and not coded much in it I guess. :wink:
Re: Problems with ppu_vbl_nmi...
by on (#111763)
It seems to me that the AND behavior of BIT is a lot more useful on the 65C02, where instruction $89 (a 2-byte NOP on 6502) becomes BIT #ii.
Re: Problems with ppu_vbl_nmi...
by on (#111764)
Be sure your CPU emulator passes the "official instructions" test before running my other test ROMs. :)
Re: Problems with ppu_vbl_nmi...
by on (#111806)
(My CPU is passing all of the official opcodes, I just had a screwed up understanding of BIT when reading code... ugg)

On to the next problem:

I'm passing ppu_vbl_nmi tests 1-4, but on the 5th test (05-nmi_timing.nes) I'm failing, but it's hard for me to understand exactly what the test is doing and why the failure is happening. I've spent all day reading through the timing documentation, and I think I have a reasonable understand of it.

The output of my test is:

Code:
Running tests...
Results:
00 4
01 4
02 4
03 4
04 3
05 3
06 3
07 3
08 3
09 3

ACB887C4
05-nmi_timing

Failed


So for 03, the result should be 3 instead of four... but I don't have any idea what that means :-/

Any ideas where to start looking?
Re: Problems with ppu_vbl_nmi...
by on (#111809)
Since this tests NMI timing, you could try having your NMI occur one cycle earlier (or later) in the frame and see what the test prints out.
Re: Problems with ppu_vbl_nmi...
by on (#112019)
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:
// 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:
// 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:
// 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.