Problems with ppu_vbl_nmi...

Discuss emulation of the Nintendo Entertainment System and Famicom.

Moderator: Moderators

Post Reply
samfoo
Posts: 6
Joined: Thu Mar 14, 2013 3:37 am

Problems with ppu_vbl_nmi...

Post by samfoo » Sat May 04, 2013 4:46 am

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: Select all

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?

User avatar
ulfalizer
Posts: 349
Joined: Fri Mar 08, 2013 9:55 pm
Location: Linköping, Sweden

Re: Problems with ppu_vbl_nmi...

Post by ulfalizer » Sat May 04, 2013 5:29 am

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: Select all

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.

samfoo
Posts: 6
Joined: Thu Mar 14, 2013 3:37 am

Re: Problems with ppu_vbl_nmi...

Post by samfoo » Sat May 04, 2013 5:36 am

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: Select all

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.

User avatar
ulfalizer
Posts: 349
Joined: Fri Mar 08, 2013 9:55 pm
Location: Linköping, Sweden

Re: Problems with ppu_vbl_nmi...

Post by ulfalizer » Sat May 04, 2013 5:47 am

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:

tepples
Posts: 22277
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Problems with ppu_vbl_nmi...

Post by tepples » Sat May 04, 2013 5:54 am

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.

User avatar
blargg
Posts: 3715
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

Re: Problems with ppu_vbl_nmi...

Post by blargg » Sat May 04, 2013 8:48 am

Be sure your CPU emulator passes the "official instructions" test before running my other test ROMs. :)

samfoo
Posts: 6
Joined: Thu Mar 14, 2013 3:37 am

Re: Problems with ppu_vbl_nmi...

Post by samfoo » Sat May 04, 2013 10:33 pm

(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: Select all

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?

User avatar
blargg
Posts: 3715
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

Re: Problems with ppu_vbl_nmi...

Post by blargg » Sun May 05, 2013 6:18 am

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.

samfoo
Posts: 6
Joined: Thu Mar 14, 2013 3:37 am

Re: Problems with ppu_vbl_nmi...

Post by samfoo » Fri May 10, 2013 9:10 pm

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.

Post Reply