frame IRQ / APU discussion

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
frame IRQ / APU discussion
by on (#30825)
- Short question: What PPU cycle does the VBlank flag rise (NTSC NES)?

- Long question: I am doing an insane trace of CPU/PPU because all the previous test ROMs were OK, but the recent "NMI under IRQ/BRK" is giving me an early NMI triggering on its 4th sequence of numbers (21 00, should be 20 00). Anyway, I read an old topic regarding the exact time the CPU checks the NMI/IRQ flags (request). If an instruction is 4 bytes long, the flags should be checked during the 3rd/4th byte fetching, or during the last byte fetching. Nintendulator checks every time the CPU reads/writes, but it fails in most of those test ROMs. Nestopia passes, but it uses a cycle counter to define the time when a flag should be set/clear, as far as I understood it.

- My problem: during the 4th sequence, an NMI is requested during a LDA #$imm instruction. Depending of the time this flag is checked, a test ROM passes (nmi_during_irq) and another fails. It's happening during a LDX #$imm too in another test ROM (nmi_timing).

- I'd like some help.
Re: VBlank rising time?
by on (#30828)
Fx3 wrote:
- Short question: What PPU cycle does the VBlank flag rise (NTSC NES)?


cycle 0 of the very first scanline of VBlank

Quote:
- My problem: during the 4th sequence, an NMI is requested during a LDA #$imm instruction. Depending of the time this flag is checked, a test ROM passes (nmi_during_irq) and another fails. It's happening during a LDX #$imm too in another test ROM (nmi_timing).


I'm not sure if this is your problem... but it takes a full cycle for the CPU to see that there's an NMI, and the perform it... so if the NMI happens on the last cycle of an instruction... the NMI won't occur until after the next instruction. Example:

Code:
if you have the following:

LDA $6000
STA $6001


you have 8 cycles:

cyc - rd/wr
0     (LDA opcode)        - NMI here happens before STA
1     (low addr:  $00)    - NMI here happens before STA
2     (high addr:  $60)   - NMI here happens before STA
3     ($6000)             - NMI here happens *AFTER* STA
                                 <---  NMI here if happened before cyc 3
4     (STA opcode)        - NMI here happens after STA
5     (low addr: $01)     - NMI here happens after STA
6     (high addr: $60)    - NMI here happens after STA
7     ($6001)             - NMI here happens after *next instruction*
                                 <---  NMI here if happened before cyc 7



Quote:
- I'd like some help. Perhaps it's me, but the "emulation support" is almost null here... why?


I think it's largely a language problem =(

Sometimes I have a hard time understanding what you're asking because of the language barrier. I realize you're not a native English speaker, so I try my best to understand your questions, but sometimes it can be hard to. I'm guessing others on the board are the same way =(

Sorry
Re: VBlank rising time?
by on (#30831)
Disch wrote:
cycle 0 of the very first scanline of VBlank

- Hmm... Was this tested in the NES?
Quote:
I'm not sure if this is your problem... but it takes a full cycle for the CPU to see that there's an NMI, and the perform it... so if the NMI happens on the last cycle of an instruction... the NMI won't occur until after the next instruction.


- Nintendulator verifies if a NMI is pending at every CPU read/write, including fetching the instruction opcode/operands. I don't know if this is correct. You wrote about the last cycle, I'll give a try.

Quote:
I think it's largely a language problem =(


- Heh, so you are c)...
- Maybe. Well, I speak Portuguese. I'm sorry for my bad writting.
- I don't wish to drift the topic but I'm inside the NES emulation since 1998. Yes, I already was a pathetic newbie... but still an "all your base" man around? :(
Re: VBlank rising time?
by on (#30832)
Fx3 wrote:
- Hmm... Was this tested in the NES?


Yes. Blargg's PPU test ROMs verify its timing right down to the PPU cycle.

Quote:
- Nintendulator verifies if a NMI is pending at every CPU read/write, including fetching the instruction opcode/operands. I don't know if this is correct. You wrote about the last cycle, I'll give a try.


Nintendulator emulates things on a lower level. It may check for pending NMI every CPU cycle because the NES does -- however it will not actually make the NMI happen until the CPU is between instructions -- so the only cycle where a pending NMI really matters is the 2nd-to-last cycle of an instruction.

I suddenly remembered that CLI latency problem... here, this thread:
http://nesdev.com/bbs/viewtopi ... 9655#19655

NMIs are exactly the same. They have that exact same delay for the exact same reason. To recreate a similar example diagram:

Code:
LDA $6000   (cyc 0)
            (cyc 1)
            (cyc 2)
                   --->  is NMI pending?  If so... NMI  -+
            (cyc 3)                                      |
                   <------------- here <-----------------+
STA $6001   (cyc 0)
            (cyc 1)
            (cyc 2)
                   --->  is NMI pending?  If so... NMI  -+
            (cyc 3)                                      |
                   <------------- here <-----------------+



Quote:
- Maybe. Well, I speak Portuguese. I'm sorry for my bad writting.
- I don't wish to drift the topic but I'm inside the NES emulation since 1998. Yes, I already was a pathetic newbie... but still an "all your base" man around? :(


Your English isn't that bad. In fact... as far as general conversation goes, it's actually very good. I can easily get the jist of what you're saying, but when it comes down to understanding the finer details, sometimes there are just little things here and there that are slightly off which can make it hard. And when giving technical explanations of emulator workings and stuff like that, details are everything.

Don't be hard on yourself. I have a lot of respect for bilingual people. I know how hard it is to learn another language (especially one as crazy as English). I am actually very impressed with how well you speak =)
Re: VBlank rising time?
by on (#30840)
Disch wrote:
Nintendulator emulates things on a lower level. It may check for pending NMI every CPU cycle because the NES does -- however it will not actually make the NMI happen until the CPU is between instructions -- so the only cycle where a pending NMI really matters is the 2nd-to-last cycle of an instruction.


- What a coincidence. RockNES does that too... ^_^;; Hehehe
- I will explain it a bit:

1. verify the flags (nmi_trigger = cpu_nmi_request).
2. fetch a byte
- 2a: ppu runs for 3 cycles
- 2b: byte is read
3. Instruction is executed.

- OK, if a NMI occurs during 2a (cpu_nmi_request = 1), it will not be triggered after that instruction. The nmi_trigger flag is verified right when an instruction is finished; if true, then a NMI is triggered.

by on (#30846)
I'm going to back up and try to address your original problem. If blargg's PPU test ROMs all pass, then the problem isn't going to be easily identified by using them. Since the problem you're having is with NMI during IRQ, let's look at what that ROM is supposed to be doing.


From the source:
Code:
   clv
   sec
   ; Z and C set, others clear
   
; NMI occurs here first,
   lda #1          ; clear Z flag
; then here for two clocks,
   clc             ; clear C flag
; then here.
   
; IRQ always occurs here.
   nop
   
   ; Read interrupt flags
   ldx nmi_flag
   ldy irq_flag


You get an error on the 4th sequence of numbers, which is when the NMI is supposed to happen *after* that CLC... but in your emu it's happening before it. Here's a diagram (I hope these diagrams are helpful, heh):

Code:
CLV       (cyc 0)
          (cyc 1)

SEC       (cyc 0)  <---  1st NMI
          (cyc 1)  <---  2nd NMI
     [NMI 1 occurs here]
LDA #1    (cyc 0)  <---  3rd NMI
          (cyc 1)  <---  4th NMI (your problem)
     [NMI 2,3 occur here]
CLC       (cyc 0)
          (cyc 1)
     [NMI 4 occurs here]


For whatever reason, the NMI is occuring early in your emulator. If you can make a tracelog to find out exactly when those NMIs are occuring, that might give you some clue as to what's going on.

by on (#30994)
- OK, I traced it but cannot think about a correct solution...

1. PPU timing is correct, VBlank rising/down is okay.
2. NMI timing looks correct, but it isn't the problem.
3. IRQ timing looks correct when I run the test rom, no errors, but it's an IRQ that's causing errors.

- Now, the trace result of "nmi_during_irq.nes" test ROM:

a) My emu gets 23 00, 21 00, 21 00, 20 00, but the next is 21 00 instead of 20 00 (the rest is irrelevant).
- There's a frame IRQ being requested during the immediate byte of a LDA. It is triggered when LDA ends.
- Hack: do not verify the IRQ flag during the immediate byte fetching.

b) With this hack, the two last lines are 20 00 and 25 00, instead of 25 00 and 25 00.
- During the 7 cycles sequence for IRQ, a NMI is being requested during the 5th cycle.
- Hack: verify the NMI flag during the cycles 6 and 7.

- I'm stuck.

by on (#30995)
Quote:
a) My emu gets 23 00, 21 00, 21 00, 20 00, but the next is 21 00 instead of 20 00 (the rest is irrelevant).


looks like the NMI (or IRQ) is occuring early. From the looks of that, it's occuring before the CLC when it should be occuring after.

Quote:
- There's a frame IRQ being requested during the immediate byte of a LDA. It is triggered when LDA ends.


The immediate byte of LDA? You mean the last cycle?

If it happens during the last cycle, you need to push it back another instruction:

Code:
LDA #1    (cyc 0)  <--- NMI/IRQ here happens before CLC
          (cyc 1)  <--- NMI/IRQ here happens *AFTER* CLC
CLC       (cyc 0)  <--- NMI/IRQ here happens after CLC
          (cyc 1)  <--- NMI/IRQ here happens after next instruction


Remember the CPU has to see the interrupt as pending before it starts the last cycle of an instruction. So you could say that NMI/IRQs get pushed back 1 CPU cycle.


I made a tracelog of the behavior I get in my emu (which passes). You can ignore most of the numbers:

Code:
E4E8:B8   CLV                        00 00 00 [...Z.]  FA --   0, -7 -- <0060,0862>
E4E9:38   SEC                        00 00 00 [...Z.]  FA --   0, -1 -- <0060,0862>
*** NMI ***  (1st)
E4AD:85   STA  $11        [0011=00]  00 00 00 [..IZC]  F7 --   0, 26 -- <0060,0862>

E4E8:B8   CLV                        00 00 00 [...Z.]  FA --   0,-10 -- <0080,0000>
E4E9:38   SEC                        00 00 00 [...Z.]  FA --   0, -4 -- <0080,0000>
E4EA:A9   LDA  #$01                  00 00 00 [...ZC]  FA --   0,  2 -- <0080,0000>
*** NMI ***  (2nd)
E4AD:85   STA  $11        [0011=00]  01 00 00 [..I.C]  F7 --   0, 29 -- <0080,0000>

E4E8:B8   CLV                        00 00 00 [...Z.]  FA --   0,-13 -- <00A0,0000>
E4E9:38   SEC                        00 00 00 [...Z.]  FA --   0, -7 -- <00A0,0000>
E4EA:A9   LDA  #$01                  00 00 00 [...ZC]  FA --   0, -1 -- <00A0,0000>
*** NMI ***  (3rd)
E4AD:85   STA  $11        [0011=00]  01 00 00 [..I.C]  F7 --   0, 26 -- <00A0,0000>

E4E8:B8   CLV                        00 00 00 [...Z.]  FA --   0,-16 -- <00C0,0000>
E4E9:38   SEC                        00 00 00 [...Z.]  FA --   0,-10 -- <00C0,0000>
E4EA:A9   LDA  #$01                  00 00 00 [...ZC]  FA --   0, -4 -- <00C0,0000>
E4EC:18   CLC                        01 00 00 [....C]  FA --   0,  2 -- <00C0,0000>
*** NMI ***  (4th)
E4AD:85   STA  $11        [0011=00]  01 00 00 [..I..]  F7 --   0, 29 -- <00C0,0000>

E4E8:B8   CLV                        00 00 00 [...Z.]  FA --   0,-19 -- <00E0,0000>
E4E9:38   SEC                        00 00 00 [...Z.]  FA --   0,-13 -- <00E0,0000>
E4EA:A9   LDA  #$01                  00 00 00 [...ZC]  FA --   0, -7 -- <00E0,0000>
E4EC:18   CLC                        01 00 00 [....C]  FA --   0, -1 -- <00E0,0000>
*** NMI ***  (5th)
E4AD:85   STA  $11        [0011=00]  01 00 00 [..I..]  F7 --   0, 26 -- <00E0,0000>

E4E8:B8   CLV                        00 00 00 [...Z.]  FA --   0,-22 -- <0100,0000>
E4E9:38   SEC                        00 00 00 [...Z.]  FA --   0,-16 -- <0100,0000>
E4EA:A9   LDA  #$01                  00 00 00 [...ZC]  FA --   0,-10 -- <0100,0000>
E4EC:18   CLC                        01 00 00 [....C]  FA --   0, -4 -- <0100,0000>
*** IRQ ***  (6th) -- interrupted by NMI
E4AD:85   STA  $11        [0011=00]  01 00 00 [..I..]  F7 --   0, 23 -- <0100,0000>

E4E8:B8   CLV                        00 00 00 [...Z.]  FA --   0,-25 -- <0120,0000>
E4E9:38   SEC                        00 00 00 [...Z.]  FA --   0,-19 -- <0120,0000>
E4EA:A9   LDA  #$01                  00 00 00 [...ZC]  FA --   0,-13 -- <0120,0000>
E4EC:18   CLC                        01 00 00 [....C]  FA --   0, -7 -- <0120,0000>
*** IRQ ***  (7th) -- interrupted by NMI
E4AD:85   STA  $11        [0011=00]  01 00 00 [..I..]  F7 --   0, 20 -- <0120,0000>


Notice that IRQ doesn't happen at all until the 6th test. Before that, NMI happens first.

by on (#31006)
EDIT: I used "7.nmi_timing.nes" and still error 7. With my new VBlank flag code (edge case), the NMI must trigger right when the instruction finishes. Next, I got error 8, it enables NMI (2000h write) when the VBlank flag is already set. So, I added a check in the 2000h write and the test has passed, including a private nmi testing sent by blargg. However, the nmi_during_irq.nes still fails. The last possibility is to check the APU frame IRQ timing, even if passes in the private test rom. :(:(:(
==========================

- Actually, you look confused. :) Anyway, the thing is quite simple: for each CPU clock, the PPU clocks 3 times; plus, for each CPU clock, the APU clocks.

- Ok, I believe the method used by *ahem*Nintendulator requires fine tuning. The IRQ/NMI requests are verified at every CPU read/write, no matter what's being read/written. My emu uses the same "idea" as reference.

- Here, when I mentioned "the immediate byte of a LDA", I mean "the byte that follows A9", or 01h for a "LDA #$01", right? The instruction (A9h) has an immediate byte addressing mode. So, I'm having problems right here. I'm sure you know how *ahem*Nintendulator does, so there's no need to explain. Nestopia passes through all the test, but its source code looks cryptic.

by on (#31039)
Fx3 wrote:
- Actually, you look confused. :) Anyway, the thing is quite simple: for each CPU clock, the PPU clocks 3 times; plus, for each CPU clock, the APU clocks.

- Ok, I believe the method used by *ahem*Nintendulator requires fine tuning. The IRQ/NMI requests are verified at every CPU read/write, no matter what's being read/written. My emu uses the same "idea" as reference.


Well your approach doesn't matter as long as you have the desired resulting behavior. :P

Whether or not you examine the NMI every cycle really doesn't matter as long as you perform the NMI at the right time.

Quote:
- Here, when I mentioned "the immediate byte of a LDA", I mean "the byte that follows A9", or 01h for a "LDA #$01", right?


That's what I thought you meant -- just wasn't sure.

So yeah... it's like I said. If the NMI/IRQ is occuring on that cycle, it does NOT happen after the LDA -- instead it happens after the instruction after the LDA (after the CLC).

Anyway -- I provided my tracelogs because they're what should be happening. You can compare them to your emu's tracelogs to find out where you're going wrong. Randomly guessing and moving around the NMI time likely won't solve your problem.

Basically I think your problem is this:

- you have an IRQ tripping on the 4th test (this is what it sounds like to me, anyway)
- IRQs should not trip at all until the 6th test (see my tracelog)
- on the first 5 tests, the NMI happens before IRQ, so IRQs get disabled before one occurs

If the tracelog is too cryptic... here's it in plain English:

Code:
test 1:    NMI after SEC
           no IRQ

test 2,3:  NMI after LDA #1
           no IRQ

test 4,5:  NMI after CLC
           no IRQ

test 6,7:  IRQ after CLC, but it's interrupted by NMI



EDIT:

I just thought I'd mention again that I don't think this is an NMI problem. Judging from your emu passing the first few tests, I'd wager your NMI timing is correct. It's your IRQ timing that needs work. IRQs are occuring too early, and the NMI is interrupting them so it looks like the NMI is occuring early when it really isn't.

I'd bet you just need to push your IRQ back 1 CPU cycle and you'd pass everything.