It is currently Tue Nov 21, 2017 7:06 pm

All times are UTC - 7 hours





Post new topic Reply to topic  [ 9 posts ] 
Author Message
PostPosted: Sat May 04, 2013 4:46 am 
Offline

Joined: Thu Mar 14, 2013 3:37 am
Posts: 6
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?


Top
 Profile  
 
PostPosted: Sat May 04, 2013 5:29 am 
Offline
User avatar

Joined: Fri Mar 08, 2013 9:55 pm
Posts: 349
Location: Linköping, Sweden
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.


Top
 Profile  
 
PostPosted: Sat May 04, 2013 5:36 am 
Offline

Joined: Thu Mar 14, 2013 3:37 am
Posts: 6
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.


Top
 Profile  
 
PostPosted: Sat May 04, 2013 5:47 am 
Offline
User avatar

Joined: Fri Mar 08, 2013 9:55 pm
Posts: 349
Location: Linköping, Sweden
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:


Top
 Profile  
 
PostPosted: Sat May 04, 2013 5:54 am 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 19238
Location: NE Indiana, USA (NTSC)
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.


Top
 Profile  
 
PostPosted: Sat May 04, 2013 8:48 am 
Offline
User avatar

Joined: Mon Sep 27, 2004 8:33 am
Posts: 3715
Location: Central Texas, USA
Be sure your CPU emulator passes the "official instructions" test before running my other test ROMs. :)


Top
 Profile  
 
PostPosted: Sat May 04, 2013 10:33 pm 
Offline

Joined: Thu Mar 14, 2013 3:37 am
Posts: 6
(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?


Top
 Profile  
 
PostPosted: Sun May 05, 2013 6:18 am 
Offline
User avatar

Joined: Mon Sep 27, 2004 8:33 am
Posts: 3715
Location: Central Texas, USA
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.


Top
 Profile  
 
PostPosted: Fri May 10, 2013 9:10 pm 
Offline

Joined: Thu Mar 14, 2013 3:37 am
Posts: 6
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.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 9 posts ] 

All times are UTC - 7 hours


Who is online

Users browsing this forum: Yahoo [Bot] and 8 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group