Planning a test for BRK and IRQ concurrency glitch

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Planning a test for BRK and IRQ concurrency glitch
by on (#91858)
I am planning a test for the BRK and IRQ concurrency glitch.
My initial attempt, however, failed, because I was assuming that when the IRQ supersedes the BRK, the B flag will be cleared, which is not actually the case.
Apparently, when the IRQ supersedes the BRK, the actual interrupt will be indistinguishable from a BRK, if not for the fact that the device generating the IRQ (such as the APU) will give out a status flag indicating an IRQ is being signalled.
However, I cannot test this. If I check the status flag within the BRK/IRQ handler, it is possible that the IRQ was actually set after the BRK handler was already landed at. Any ideas how to perform this test reliably?
Would checking for the IRQ return address work?

There is another test I want to make here: I want to see whether, when an IRQ is signalled in the middle of a PHP instruction, the PHP pushes the B flag cleared. In my emulator, it currently does (because it shares code with the BRK/IRQ generating instruction), but I want to see if it happens also on the hardware and/or on any other emulator.

by on (#91862)
Will this video I made some time back help you at all? It doesn't test concurrency, but it might be helpful in other ways. Original thread which prompted me to make the video. Tepples' hardware IRQ ideas might be doable, but that'd take me some time to read about + rig up.

by on (#91863)
http://bisqwit.iki.fi/kala/test1.zip If someone has a real NES, I would be thankful of reports of this test ROM being run. Its purpose is to collect the reference data for the actual test. It works on both NTSC and PAL systems, but I am now focusing on NTSC.
EDIT: Test results collected (again).
Code:
Nintendulator:
29823
   36 *   #INTs=1, f=$30, $4015=$00, B=1 <- Only BRK happened (IRQ was not asserted before test was over)
   12 *   #INTs=2, f=$20, $4015=$40, B=0 <- BRK occurring, followed by IRQ before SEI has caught effect
   79 *   #INTs=2, f=$20, $4015=$40, B=1 <- BRK occurring, reads $4015 BEFORE the IRQ is asserted, immediately followed by IRQ before the next instruction in mainline
   16 *   #INTs=1, f=$30, $4015=$40, B=1 <- BRK occurring, reads $4015 AFTER IRQ is asserted, IRQ never occurs
   107*   #INTs=2, f=$30, $4015=$00, B=1 <- IRQ occurs before BRK, followed by BRK occurring
   second flag value always $30
Nestopia:
29823
   Same as Nintedulator
   second flag value always $30
FCEUX:
29822
   Same as Nintendulator, except +1/-1 variances in some timings
   second flag value always $30
BSNES:
29822
   Same as FCEUX
   second flag value always $30
FAMICOM NTSC:
29823
   Same as Nintendulator, except $4015 always has bit 5 set (i.e. $20 and $60 instead of $00 and $40).
   second flag value always $30
USA NTSC:
29823
   RESULTS PENDING   

Still no sign of BRK being superseded by IRQ...

#INTs counts the number of times the IRQ handler was entered. B is 1, if the return address in the latest invoked interrupt pointed to the next instruction after the BRK (which was a double-byte NOP that did not occur anywhere else, for easy recognition).

If the BRK was superseded by an IRQ, there would be a line where #INTs=1 and B=0. Yet, such line never occurred :-/

Also no sign of the PHP bits being changed by an oncoming IRQ.

Curiously, bit 5 in $4015 always read back as 1. It always reads back as 0 in every emulator I tested. Looks like another previously unknown open bus case.

by on (#91864)
That $4015 thing is strange.

What happens if you read $4015 right after a BRK? (without triggering an IRQ)

by on (#91865)
On my NES, with my PowerPak:

Code:
Offset #INTs Flags   4015 B n
01     1     $30,$30 $20  1 2
13     2     $20,$30 $60  0 12
29     2     $20,$30 $60  1 16
- IRQ trigger timing: 29823

This result remained stable across multiple resets and even a couple power cycles.

by on (#91866)
You make use of unofficial opcodes...?

by on (#91868)
Zepper wrote:
You make use of unofficial opcodes...?

Where they are entirely consistent and a consequence of processor's logical design rather than subject to temperature-variant analog processes, yes. They are an unseparable part of the NES hardware.
In this test, I used the two-byte NOP opcode $F4 (which is really a "CPX zp,X" but which for some reason does nothing productive -- probably related to the fact that it refers to X twice).
EDIT: But I replaced it now with $59, "EOR abs,y". The point is just that the opcode must be sufficiently unique so that the return address is identified by looking at the opcode at that address alone.

by on (#91870)
With RockNES:
Image

by on (#91871)
Zepper wrote:
With RockNES:

Same results as on Nintendulator and Famicom (the HVC-001, not an emulator by that name), except that the it looks like in your emulator, an early read of $4015 cancels the upcoming IRQ even 5 cycles before the fact.

But why a 100 kilobyte JPG for something that compresses into less than 4 kilobytes as a lossless PNG? Know your file formats.

by on (#91872)
And this with puNES:
Image

by on (#91873)
From my emulator:
Quote:
TEST:test_cpu_flag_concurrency
Verifying that basic CPU flag operations work properly
DFL:30; 30 OK
NMI:20->30 OK
IRQ:20->30 OK
BRK:30->30 OK
Finding APU IRQ timings
OK, 29823<=29823<29823
Invoking a BRK-IRQ collision
Offs #INTs Flags 4015 B n
033 1 $30,$30 $00 1 34 <- Only BRK happened (IRQ was not asserted before test was over)
043 2 $20,$30 $40 0 10 <- BRK occurring, followed by IRQ before SEI has caught effect
124 2 $20,$30 $40 1 81 <- BRK occurring, reads $4015 BEFORE the IRQ is asserted, immediately followed by IRQ before the next instruction in mainline
136 1 $30,$30 $40 1 12 <- BRK occurring, reads $4015 AFTER IRQ is asserted, IRQ never occurs
138 1 $20,$30 $40 1 2 <- BRK superseded by IRQ (BRK-style return address, IRQ-style flags)
249 2 $30,$30 $00 1 111 <- IRQ occurs, followed by BRK occurring

This is obviously wrong in more than one aspect.
Also, I do not get $20 at $4015, even though I implemented the open bus feature in there. What could be causing it to happen on NES and Famicom?


FHorse wrote:
And this with puNES:

You have a one-cycle difference between IRQ occurring and a read from $4015 preventing that happening compared to the real NES. Are you possibly fetching the contents of the register at wrong cycle relative to the instruction? Or do you have an extra buffer somewhere for IRQs?

by on (#91875)
Do you know if the IRQ is acknowledged only in the last cycle of the current instruction? Do you write to $4017 before writing to $4015? How many cycles should be elapsed?

by on (#91877)
Zepper wrote:
Do you know if the IRQ is acknowledged only in the last cycle of the current instruction? Do you write to $4017 before writing to $4015? How many cycles should be elapsed?

At no point do I write to $4015 in this test, not even in Blargg's library code (except in the end thereof).
I do write to $4017 whenever I want to toggle IRQ enable / disable, and/or to force IRQ to be happen at a certain time. I may read $4015 even when IRQ is not being triggered.
I can't answer the other questions.

by on (#91882)
Results from updated test on NTSC NES + PowerPak:
Code:
Offs #INTs Flags   4015 B n
035  1     $30,$30 $20  1 36
047  2     $20,$30 $60  0 12
126  2     $20,$30 $60  1 79
142  1     $30,$30 $60  1 16
249  2     $30,$30 $20  1 107
- IRQ trigger timing: 29823

by on (#91889)
Code:
Offs #INTs Flags   4015 B n
035  1     $30,$30 $20  1 36
047  2     $20,$30 $60  0 12
126  2     $20,$30 $60  1 79
142  1     $30,$30 $60  1 16
249  2     $30,$30 $20  1 107
- IRQ trigger timing: 29823

and after a debug session I've the same result with puNES. Thx Bisqwit for the test.

by on (#91890)
Bisqwit wrote:
At no point do I write to $4015 in this test, not even in Blargg's library code (except in the end thereof).


My bad, I mean read from $4015.

Quote:
I do write to $4017 whenever I want to toggle IRQ enable / disable, and/or to force IRQ to be happen at a certain time. I may read $4015 even when IRQ is not being triggered.


Hmm... but you could count how many cycles were elapsed since $4017 write and right before $4015 read (I remember of blargg counting cycles this way), so I can search for timing errors here.

by on (#91891)
Unrelated to previous questions:

Hmm, I tried to make the test automatically find the proper timings for the APU IRQ timing, so it works not only on both NTSC and PAL, but on any broken emulator that might not have its timings right (since the focus of the test is not at accurate timings, but in the BRK-IRQ overriding effect that I have dubbed Higgs boson for its observation evading nature).

But turns out that blargg's sync_apu function actually relies on hard-coded NTSC timings, which makes the whole synchronization loop ("IRQ timing") pointless. If the emulator does not have accurate NTSC timings, the loop may yield random values.

I tried discarding the sync_apu function and instead, making my own synchronization code (still relying on the binary search), but it does not work nicely. I tried for example, delay N cycles, read IRQ status (clearing it), delay N cycles again, read IRQ status, and see if IRQ status was set on both times. (Two times necessary to overcome the unknown jitter factor.) I tried looping ten times and checking if IRQ status was set each time. Or ANDing the statuses of individual loops together.
None of these produced anything that would not produce completely random results (that were right sometimes, but often off by 1 or off by 40).

Any ideas? Here is the (partial) source code of one of these attempts.
Code:
        sei

        ; Disable IRQ
        delay 8
        setb SNDMODE, $40
        delay 8
        lda SNDCHN
        delay 8   

        ; Run one IRQ.
        setb SNDMODE, 0 ; Enable IRQ (but the CPU will still block them)

        ; Wait
        lda num_cycles_plan+0      ; 3 cycles
        sec                        ; 2 cycles
        sbc #40                    ; 2 cycles
        tax                        ; 2 cycles
        lda num_cycles_plan+1      ; 3 cycles
        sbc #0                     ; 2 cycles
        jsr delay_256a_x_26_clocks ; OVERHEAD: 3+2+2+2+3+2+26

        ; Check whether APU is ready to trigger an IRQ
        lda SNDCHN      ; 4 cycles
        sta somewhere   ; 3 cycles

        ; Do it again to counteract the odd/even frames effect. Subtract those 7  cycles.
        lda num_cycles_plan+0      ; 3 cycles
        sec                        ; 2 cycles
        sbc #(40-7)                ; 2 cycles
        tax                        ; 2 cycles
        lda num_cycles_plan+1      ; 3 cycles
        sbc #0                     ; 2 cycles
        jsr delay_256a_x_26_clocks ; OVERHEAD: 3+2+2+2+3+2+26

        lda SNDCHN
        and somewhere
        ; The bits now tell if the IRQ happened on both times.

        and #$40
        ; Binary search condition: A = nonzero.


The code below gets 29820 cycles in Nintendulator, 29813 cycles in Nestopia.

Code:
   sei
   
   ; Disable IRQ
   delay 8
   setb SNDMODE, $40
   delay 8
   lda SNDCHN
   delay 8

   ; Run one IRQ.
   setb SNDMODE, 0 ; Enable IRQ (but block them at CPU)

   ; Wait
   lda num_cycles_plan+0      ; 3 cycles
   sec            ; 2 cycles
   sbc #40            ; 2 cycles
   tax            ; 2 cycles
   lda num_cycles_plan+1      ; 3 cycles
   sbc #0            ; 2 cycles
   jsr delay_256a_x_26_clocks ; OVERHEAD: 3+2+2+2+3+2+26
   
   ldx SNDCHN      ; 4 cycles
   lda SNDCHN      ; 4 cycles
   ; X should be #$00, A should be #$40.
   and #$40      ; 2 cycles
   sta timing_temp_1   ; 3 cycles
   txa         ; 2 cycles
   and #$40      ; 2 cycles
   sec         ; 2 cycles
   sbc timing_temp_1   ; 3 cycles
   sta timing_temp_1   ; 3 cycles --total: 4+4+2+3+2+2+2+3+3 =  25 cycles
   ; X=$00, A=$00 (too early)   result: $00
   ; X=$00, A=$40 (right time)  result: $C0
   ; X=$40, A=$00 (too late)    result: $40
   ; X=$40, A=$40 (impossible)  result: $00
   ; Do it again to counteract the odd/even frames effect. Subtract those 25 cycles.
   lda num_cycles_plan+0      ; 3 cycles
   sec            ; 2 cycles
   sbc #(40-25)         ; 2 cycles
   tax            ; 2 cycles
   lda num_cycles_plan+1      ; 3 cycles
   sbc #0            ; 2 cycles
   jsr delay_256a_x_26_clocks ; OVERHEAD: 3+2+2+2+3+2+26

   ldx SNDCHN
   lda SNDCHN
   and #$40      ; 2 cycles
   sta timing_temp_2   ; 3 cycles
   txa         ; 2 cycles
   and #$40      ; 2 cycles
   sec         ; 2 cycles
   sbc timing_temp_2   ; 3 cycles
   sta timing_temp_2   ; 3 cycles
   
   ; Now:
   ;   timing_temp_1 timing_temp_2
   ;             $00           $00 too early  (1)
   ;             $00           $40 too early  (1)
   ;             $00           $C0 too early  (1)
   ;             $40           $00 too late   (0)
   ;             $40           $40 too late   (0)
   ;             $40           $C0 too late   (0)
   ;             $C0           $00 too early  (1)
   ;             $C0           $40 too late   (0)
   ;             $C0           $C0 RIGHT TIME (0)
   ldx #0
   lda timing_temp_1
   beq @make_1
   cmp #$40
   beq @make_0
   lda timing_temp_2
   beq @make_1
@make_0:
   .byte $A9 ; lda #imm
@make_1:
   inx

   ; Translate the 0/1 value into carry flag for function return.
   cpx #$01
   rts

I'll have to doublecheck whether my delay function works as intended, too.
EDIT: Yeah, I had a bug in the delay code. (Which means that all previous test results may have been bogus!) But I'm still getting differences of 0/1 cycles in this loop.

by on (#91893)
Sorry. I was drifting your focus... :(

by on (#91949)
My result:

Image

It's off by one instruction at the end it seems, now I need to understand what all these numbers mean so I can get it to pass :P

by on (#91951)
Just to repeat for clarity, this is not a pass/fail test (aside from the flags bit in the beginning).

This was an attempt to collect information to make a BRK-IRQ overriding test.
The actual pass/fail test will be coming later when/if the means to implement the test are devised and proven reliable.

I suggest everyone disregard these test results for now.

by on (#91952)
Oh ok, I was simply comparing to the expected NES output, which is different. Thanks!

by on (#92106)
It's all about how welcome/magnificent the test ROMs are here... :)

by on (#93502)
Well thanks to the spam I didn't completely miss this post. I'm guessing you didn't see this: http://nesdev.com/bbs/viewtopic.php?t=8688 based on your attempts to find out how this actually works on the 6502. They actually discuss why the BRK/IRQ concurrency issue exists, as a hardware design. Just thought I'd make sure you saw that, sorry if you already had.

EDIT: The whole thing's worth watching but it's specifically discussed around 19:30

by on (#93505)
infiniteneslives wrote:
I'm guessing you didn't see this: http://nesdev.com/bbs/viewtopic.php?t=8688 based on your attempts to find out how this actually works on the 6502. They actually discuss why the BRK/IRQ concurrency issue exists, as a hardware design. Just thought I'd make sure you saw that, sorry if you already had.

EDIT: The whole thing's worth watching but it's specifically discussed around 19:30

I have seen it in its entirety.
And I still don't know exact means to do the test. And yes, a moderator do please notice the spamming account & posts of "lanshan75".