Jump to content
IGNORED

How can POKEY IRQ Timers mess up NMI timing?


Sheddy

Recommended Posts

From the CPU perspective, kind of. HALT can cause an Interrupt to be seen a cycle or more later. I don't think that's a problem though.

 

Where we're having trouble is around the end of the scanline, so far as our Cycle numbering is concerned. We can have next to no DMA in that area but still have problems.

 

I think that if we can align the Timer IRQ to an odd cycle, then it might eliminate any cases of DLIs being missed in "big mode" cases, or "single scanline mode" cases. Remembering here that since the divisor is 28, it will always remain aligned to either an odd or even cycle.

Link to comment
Share on other sites

That's the conclusion so far... I've encountered 2 cycles that can mess them up, there could be more due to all the different cirumstances due to DMA (PMG, scrolling etc).

 

You'd reckon the C64 guys might have encountered it if it's a 6502 thing... I'd say it's reasonably common to play Digi sound with their NMIs while also having Raster IRQs.

 

On the NMI System Reset, it's always cycle 9 after WSYNC on all NTSC 400/800 machines I tried. I haven't tried with various graphics mode to see if they move this cycle back and forth.

Link to comment
Share on other sites

Now that I think of it, if I needed to play samples from time to time without touching SKCTL, I'd probably use the earlier method of checking for a missed DLI early in the IRQ code:

 

irq
pha			; save a
tya
pha			; save y
lda NMIST		; check for a pending DLI
nop			; replace NOP with something useful, but the delay is necessary for some reason
adc NMIST		; If DLI was present both times, this will result in a carry
bcc continue

--perform DLI duties or jump down to continue

 

This method works just as well as any other, but requires a little extra code. It seems the DLI flag is present in NMIST before the NMI happens because the double check with an instruction in between is necessary to make it stable.

Link to comment
Share on other sites

I had an idea I tried earlier... use Serial Output, as you can control the Interrupt more easily.

 

Sadly... the only useful Serial Output modes generate a high pitched noise you can't mask, and you also get a bit of noise from the data transmission.

 

Which serial output IRQ were you using? There's two serial output IRQs-- serial output done (bit 3) and serial output data needed (bit 4).

Link to comment
Share on other sites

With that test I was using "Data Needed"... "Output Done" generates an IRQ automatically if you don't have SIO happening.

 

I tried some of the "not useful" serial modes... they don't generate unwanted noise, but it seems they don't generate IRQs either (if in fact they even do SIO at all).

Link to comment
Share on other sites

With that test I was using "Data Needed"... "Output Done" generates an IRQ automatically if you don't have SIO happening.

 

I tried some of the "not useful" serial modes... they don't generate unwanted noise, but it seems they don't generate IRQs either (if in fact they even do SIO at all).

 

So how many cycles after SIO completion does the IRQ occur. For Output Done, it's the very next cycle.

Link to comment
Share on other sites

Something like that sounds feasible.

 

I just wonder, though, if we could align the divisor on an odd cycle (or whatever may be needed) in the first place, maybe we could reduce the conflicts to only be those that coincide with single-line DLIs.

 

I've not done a lot of incorporating DLIs into disk loaders... the main problem I found was that the DLI activity would cause the occasional glitch in SIO, so I stayed away from doing it.

 

Hmm... once the 64KHz timer is started, it can potentially hit every even or odd cycle depending on alignment. It should be possible to pick one or the other with SKCTL. If I monitor a 64KHz oscillation on Pokey I can see if I can shift it one cycle relative to sync. This can help us determine if one state is better than the other.

 

I'm still getting a nagging feeling that there's something specific to the timer IRQs that we're missing here. Why don't we see glitching due to SIO interrupts or the keyboard? Both are unpredictable with regard to scan line timing, and you'd think that someone would have noticed it if it happened at all. There are definitely cases where people use DLIs during SIO transfers -- the demo Joyride uses them. At 1,920 IRQs a second you're bound to hit a specific cycle at some point. That goes double if it is also able to mask the VBI.

 

Incidentally, I found out during some cassette emulation work that the kernel does re-init POKEY at times other than just startup. In particular, it also does so when reading a cassette record, immediately after determining baud rate. If you're writing a program that is sensitive to the slow timer phase, you definitely need to re-init it yourself to ensure consistency.

Link to comment
Share on other sites

You can get glitching from SIO ... it was mentioned earlier.

 

Keyboard would be a really rare case though. It's fairly common for the VBlank to occur during the key IRQ, which means Stage 2 gets skipped.

For the case of the keyboard interfering with a DLI, the odds are just too low.

I suppose if you had a screen absolutely full of DLIs, you'd have a good chance of having a conflict.

 

Timers probably show up better since they're an exact event, where keys are very random.

Link to comment
Share on other sites

You can get glitching from SIO ... it was mentioned earlier.

 

Keyboard would be a really rare case though. It's fairly common for the VBlank to occur during the key IRQ, which means Stage 2 gets skipped.

For the case of the keyboard interfering with a DLI, the odds are just too low.

I suppose if you had a screen absolutely full of DLIs, you'd have a good chance of having a conflict.

 

Timers probably show up better since they're an exact event, where keys are very random.

 

Oh, it may not be that rare....

 

Try the attached program. On my 800XL, the keyboard glitches the screen very reliably. See, it turns out there's a gotcha... the keyboard scanning circuit is based off of the 15KHz clock, which runs at scan line rate. Normally, this clock is offset from the screen such that keyboard IRQs can never fall onto the bad cycle. However, if by chance SKCTL is used to reset POKEY at exactly the right cycle -- which is what this program does -- then every keyboard IRQ will land on that cycle!

 

I wonder how I should document this in the emulator change list... "added emulation for Atari's IRQ/NMI #$&*(up"

missdli3.zip

Link to comment
Share on other sites

Something like that sounds feasible.

 

I just wonder, though, if we could align the divisor on an odd cycle (or whatever may be needed) in the first place, maybe we could reduce the conflicts to only be those that coincide with single-line DLIs.

 

I've not done a lot of incorporating DLIs into disk loaders... the main problem I found was that the DLI activity would cause the occasional glitch in SIO, so I stayed away from doing it.

 

Hmm... once the 64KHz timer is started, it can potentially hit every even or odd cycle depending on alignment. It should be possible to pick one or the other with SKCTL. If I monitor a 64KHz oscillation on Pokey I can see if I can shift it one cycle relative to sync. This can help us determine if one state is better than the other.

 

I'm still getting a nagging feeling that there's something specific to the timer IRQs that we're missing here. Why don't we see glitching due to SIO interrupts or the keyboard? Both are unpredictable with regard to scan line timing, and you'd think that someone would have noticed it if it happened at all. There are definitely cases where people use DLIs during SIO transfers -- the demo Joyride uses them. At 1,920 IRQs a second you're bound to hit a specific cycle at some point. That goes double if it is also able to mask the VBI.

 

Incidentally, I found out during some cassette emulation work that the kernel does re-init POKEY at times other than just startup. In particular, it also does so when reading a cassette record, immediately after determining baud rate. If you're writing a program that is sensitive to the slow timer phase, you definitely need to re-init it yourself to ensure consistency.

 

My memories concerning Joyride could be bad but where are DLIs while loading? Joyride does have the starfield while loading but I don't see any DLIs, same to Overmind or Total Daze?

 

I only can remember 7 Cities of Gold and Alternate Reality but there you can see glitches while streaming data from discs...

Link to comment
Share on other sites

Here's the subroutine I've developed to release Pokey from INIT status on the cycle of your choosing. It also adjusts for Refresh, so you don't need to work it out yourself.

e.g. if you specify Cycle 25, it will adjust it to Cycle 24 since 25 is a Refresh cycle.

It uses a delay loop if necessary, and adjusts 2 JMP instructions for the remainder of the required delay. Single-cycle resolution is provided by changing the STA SKCTL instruction to STA SKCTL,X as needed (that takes 5 instead of 4 cycles).

Best to put the routine on/near a Page Boundary, it only calculates the low byte of the JMP instructions.

 

Calling procedure: Put the cycle # in X Register. Store 00 in SKCTL. JSR to the routine.

 

skctl	= $d20f
wsync	= $d40a
storea_abs = $8D; Machine instruction STA $nnnn
storea_absx = $9D; Machine instruction STA $nnnn,X
;
; Routine to release INIT on Pokey at horizontal cycle passed in X
;
*= $4000
;
lda #25; Cycle # for first Refresh
sta temp1
lda #storea_abs
sta set_skctl; Set STA instuction to absolute
lda #0
sta adjust
ldy #9
adj1
cpx temp1
bcc set_adjust
inc adjust
lda temp1
clc
adc #4
sta temp1
dey
bne adj1; Adjust delay for Refresh cycles
set_adjust
txa
sec
sbc adjust
tax; Put adjusted value back into X
;
txa; A=cycle # to Init Pokey
lsr a
lsr a
lsr a; /8
sta del_loop_count
txa
and #7
lsr a
sta temp1
bcc init2
lda #storea_absx
sta set_skctl; Set STA instruction to absolute,X
init2
lda #<set_skctl
sec
sbc temp1
sta jump_instruction1
sta jump_instruction2
lda #3; SKCTL Normal operating mode
ldx #0
lda #2; VCOUNT wait line #
waitvc
cmp $d40b
bne waitvc
lda #3; SKCTL Normal operating mode
ldy del_loop_count
beq init3
sta wsync
cpy 0; 3 cycle NOP - extra cycle needed because last instruction of loop is only 7 cycles on final iteration
nop
delay_loop
cpy 0; 3
dey; 5
bne delay_loop; 8 (7)
jump_instruction1=*+1
jmp set_skctl
nop
nop
nop
nop
set_skctl
sta skctl; Put Pokey back in normal operating state
rts
init3
sta wsync
nop; 2
nop; 2
jump_instruction2=*+1
jmp set_skctl; 7
;
;
;
temp1	.byte 0
del_loop_count  .byte 0
adjust	.byte 0

Edited by Rybags
Link to comment
Share on other sites

great! going to use it.. Have you tested it on any emulator?

 

I wonder how I should document this in the emulator change list... "added emulation for Atari's IRQ/NMI #$&*(up"

haha

 

Can we hope "perfect IRQ emulation" from Altirra? ( pleeeeeeeease :) )

 

Regards

Link to comment
Share on other sites

Something like that sounds feasible.

 

I just wonder, though, if we could align the divisor on an odd cycle (or whatever may be needed) in the first place, maybe we could reduce the conflicts to only be those that coincide with single-line DLIs.

 

I've not done a lot of incorporating DLIs into disk loaders... the main problem I found was that the DLI activity would cause the occasional glitch in SIO, so I stayed away from doing it.

 

Hmm... once the 64KHz timer is started, it can potentially hit every even or odd cycle depending on alignment. It should be possible to pick one or the other with SKCTL. If I monitor a 64KHz oscillation on Pokey I can see if I can shift it one cycle relative to sync. This can help us determine if one state is better than the other.

 

I'm still getting a nagging feeling that there's something specific to the timer IRQs that we're missing here. Why don't we see glitching due to SIO interrupts or the keyboard? Both are unpredictable with regard to scan line timing, and you'd think that someone would have noticed it if it happened at all. There are definitely cases where people use DLIs during SIO transfers -- the demo Joyride uses them. At 1,920 IRQs a second you're bound to hit a specific cycle at some point. That goes double if it is also able to mask the VBI.

 

Incidentally, I found out during some cassette emulation work that the kernel does re-init POKEY at times other than just startup. In particular, it also does so when reading a cassette record, immediately after determining baud rate. If you're writing a program that is sensitive to the slow timer phase, you definitely need to re-init it yourself to ensure consistency.

 

My memories concerning Joyride could be bad but where are DLIs while loading? Joyride does have the starfield while loading but I don't see any DLIs, same to Overmind or Total Daze?

 

I only can remember 7 Cities of Gold and Alternate Reality but there you can see glitches while streaming data from discs...

 

One thing to realize is that low-frequency IRQs have almost zero probability of hitting same cycle as NMI and affecting them assuming routines aren't playing with 54286 directly.

Link to comment
Share on other sites

I tested the routine by 1> Trace in Atari AsmEd (for some values 0 - 18).

2> Change the routine slightly, put a store #$FE in $D01A instead of STA SKTCL, followed by LDA #0, STA $D01A. Alter the VCOUNT wait so it's just onscreen (VCOUNT=7 or so). Wrote a routine which lets you alter the X parameter with joystick.

 

I haven't tested it under real Timer IRQ conditions, that'll be a bit more exhaustive.

 

Once a sweet spot is found, the routine could probably just be swapped out with a short hard-coded wait + loop + set SKCTL.

Link to comment
Share on other sites

great! going to use it.. Have you tested it on any emulator?

 

I wonder how I should document this in the emulator change list... "added emulation for Atari's IRQ/NMI #$&*(up"

haha

 

Can we hope "perfect IRQ emulation" from Altirra? ( pleeeeeeeease :) )

 

Working on it, but I keep hitting annoying side issues while trying to debug this. Like, for instance, I had no idea that the 6502 executes one instruction after CLI even if the IRQ line has already been long active. I do have POKEY INIT skewing the clocks working, although the timing probably needs adjustment.

 

I also have a sneaky suspicion that there's information about this out there somewhere and I'd know about it if I could read Polish....

 

The newest update in our continuing interrupt WTF is that enabling missile DMA decreases the magic number of required delay cycles by one and enabling player+missile DMA decreases it by five. This means that the critical event happens after P/M DMA, which occurs on cycles 0 and 2-5. It's looking like the critical event is the point at which the IRQ interrupt sequence ends relative to when NMI is pulled (either cycle 0/1, relative to cycle 8). 9 cycles after WSYNC puts this sequence at cycles 0-6, and with P/M DMA active you would have to start the sequence earlier to finish it at the same time. It also explains Rybags' single scan line case, because in that case there is one cycle of display list DMA at cycle 0. Finally, it turns out that BRK also works to trigger this problem -- which completely rules out POKEY. Perhaps it's actually WDC that we should blame.

Link to comment
Share on other sites

Might still be an Antic problem, but I did try with 7-cycle instructions and it didn't worry it.

 

Knowing how long Antic holds NMI low for would help... and also if there's any official specs that describe it, although the common word seems to be that NMI sources should hold it low until the "peripheral" is serviced/cleared.

Link to comment
Share on other sites

From the CPU perspective, kind of. HALT can cause an Interrupt to be seen a cycle or more later. I don't think that's a problem though.

 

Where we're having trouble is around the end of the scanline, so far as our Cycle numbering is concerned. We can have next to no DMA in that area but still have problems.

 

I think that if we can align the Timer IRQ to an odd cycle, then it might eliminate any cases of DLIs being missed in "big mode" cases, or "single scanline mode" cases. Remembering here that since the divisor is 28, it will always remain aligned to either an odd or even cycle.

 

Overall with or without the NMI suppressing cycle, it's more problematic in having a high frequency IRQ running and a whole bunch of DLIs since IRQ latency will be greatly impacted by the DLIs. They have to be interleaved to begin with anyway. Better off using an IRQ that simulates the DLI and for audio playback.

Link to comment
Share on other sites

Alright, at the risk of beating a dead horse, I'm going to announce what I believe to be the real condition, based on a lot of testing with various delays, mode lines, and interrupting methods:

 

>> The last cycle of the seven-cycle IRQ/BRK sequence must land on exactly cycle 10 to block an NMI. <<

 

The following sequence should accomplish this once, regardless of any DMA (assuming no intervening interrupts and no display list or P/M DMA on the scan line):

 

STA WSYNC   ;clear existing DMA cycles
STA WSYNC   ;sync to cycle 105
PHA         ;*, 105, 106
PLA         ;107, 108, 109, 110
PHA         ;111, 112, 113
PLA         ;0, 1, 2, 3
BRK         ;4, 5, 6, 7, 8, 9, 10

 

Additional factors:

  • You must ensure that the starting cycle for your delay is known exactly. STA WSYNC in uncontrolled conditions is not enough as you still have three cycles of uncertainty: a possible DMA cycle immediately after STA WSYNC, a possible playfield DMA cycle at 105, and a possible refresh DMA cycle at 106.
  • For each cycle of DMA in cycles 0-8 (missile, display list, players, display list LMS) you must initiate the delay or interrupt sequence one cycle earlier for it to hit at the right time.
  • Playfield DMA cycles starting at cycle 12 don't matter.
  • The instruction pattern at the beginning of the IRQ routine doesn't matter.
  • If you are initiating an immediate IRQ by CLI with the IRQ line active, the 6502 will execute one more instruction before initiating the IRQ sequence.
  • If you are initiating an immediate IRQ by writing IRQEN with a POKEY interrupt active, the 6502 will execute two more instructions before initiating the IRQ sequence, at least if the next instruction is short (two cycles).
  • If you are strobing STIMER to sync a 16-bit, 1.79MHz timer (atariksi's example), the write needs to happen 7 cycles in advance of the desired interrupt sequence start. This implies that all timers roll over immediately after STIMER instead of waiting one period.

 

What all of this also implies is that you cannot reliably avoid this problem with a 64KHz clock. The reason is that, while you can align the clock itself to fire the IRQ on an odd cycle, you're still screwed if instruction completion delays the 6502's interrupt response so that it lands on an even boundary. Therefore, either the 15KHz or the 1.79MHz clocks are the way to go for interference-free POKEY timers. (Which, embarrassingly, is the way the OP was doing it before I sent him off on this particular tangent by suggesting the 64KHz clock. Oops.)

 

There is one pertinent case that I haven't tested, which is if playfield DMA is occurring on cycle 10. This can only occur on a mode 2-7 line with wide playfield fetch that has been shortened to one scan line. It's difficult to test this case, though.

Link to comment
Share on other sites

From the CPU perspective, kind of. HALT can cause an Interrupt to be seen a cycle or more later. I don't think that's a problem though.

 

If the CPU is stopped by HALT, when does ANTIC uses the bus/ how does ANTIC know the CPU has finished it's instruction?

Does ANTIC wait seven static cycles?

Link to comment
Share on other sites

One possible solution if we want to use 64 KHz mode is to test if we're interrupting a DLI, within our Timer routine.

VCount only having 2-line resolution doesn't help us out much there, though.

When V-Scrolling is thrown into the mix, it confuses things even more.

 

The other solution might be similar to "Stable rasters" on the C-64... have a DLI setup a bit earlier. Have it idle for a specific time, even performing the duties of the Timer IRQ for that period. That guarantees the next DLI can fire off.

 

In the event that the first DLI is suppressed, we should rest assured that the second one isn't suppressed.

 

A bit kludge-o-matic, but workable. The exact solution needed would be different for the specific game/demo we're working on.

Link to comment
Share on other sites

From the CPU perspective, kind of. HALT can cause an Interrupt to be seen a cycle or more later. I don't think that's a problem though.

 

If the CPU is stopped by HALT, when does ANTIC uses the bus/ how does ANTIC know the CPU has finished it's instruction?

Does ANTIC wait seven static cycles?

 

HALT is immediate, it doesn't matter what the CPU is doing.

 

RDY takes one cycle, that's why we usually get the "free" cycle 104, in fact the first cycle of a subsequent instruction occurs straight after a STA WSYNC.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...