Jump to content
IGNORED

How can POKEY IRQ Timers mess up NMI timing?


Sheddy

Recommended Posts

From the Synertek datasheet:

 

Inputs /IRQ and /NMI are hardware interrupt lines that are sampled during 02 (phase 2) and will begin the appropriate interrupt routine on the 01 (phase 1) following the completion of the current instruction.

 

So both lines are sampled once per clock, and a transition on /NMI should get precedence since the IRQ will still be there until it is cleared. Somehow either the NMI isn't going low (unlikely, since Antic knows nothing of pending IRQ's) or something about the overall system timing is causing the 6502 to get confused and ignore it sometimes. I'd like to see it all on a logic analyzer.

 

It (6502) may sample at 1.79Mhz but when does the other end deliver the signals-- it seems the rest of the hardware is sampling at least reset key at 15Khz.

Link to comment
Share on other sites

From the Synertek datasheet:

 

Inputs /IRQ and /NMI are hardware interrupt lines that are sampled during 02 (phase 2) and will begin the appropriate interrupt routine on the 01 (phase 1) following the completion of the current instruction.

 

So both lines are sampled once per clock, and a transition on /NMI should get precedence since the IRQ will still be there until it is cleared. Somehow either the NMI isn't going low (unlikely, since Antic knows nothing of pending IRQ's) or something about the overall system timing is causing the 6502 to get confused and ignore it sometimes. I'd like to see it all on a logic analyzer.

 

It (6502) may sample at 1.79Mhz but when does the other end deliver the signals-- it seems the rest of the hardware is sampling at least reset key at 15Khz.

 

Oh yeah, it wouldn't surprise me if Antic only looks at reset periodically.

Link to comment
Share on other sites

Atari's don't "stop the clock"... C= Plus/4 uses that technique (stretches one phase of the clock cycle as viewed by the CPU), Antic only ever uses HALT/DMA line to automatically stall the 6502, the clocks are never messed with.

 

I was under the impression that the original Atari 800s did use circuitry to halt the phase 0 clock input to the CPU in order to implement DMA and that it wasn't until later that the 6502C with integrated HALT circuitry was used. The Hardware Manual shows the halt circuit on the CPU board schematic.

Link to comment
Share on other sites

Atari's don't "stop the clock"... C= Plus/4 uses that technique (stretches one phase of the clock cycle as viewed by the CPU), Antic only ever uses HALT/DMA line to automatically stall the 6502, the clocks are never messed with.

The whole purpose of the extra circuitry on the 400/800 CPU board is to isolate the bus and hold the clock in a constant state during DMA. The clock circuitry is a combination of a 7474 and a 7402. When Antic's /HALT goes low, ph0 to the CPU is stopped on the next cycle and the address bus is decoupled by 74LS244's. All this circuitry was moved into Sally/6502C, so the CPU now stretches its own clock.

Link to comment
Share on other sites

By the way, since the sample code can collide with a DLI or a VBI, it might be a good idea to check for both possibilities in the IRQ code, or remove the VBI altogether and make the last DLI do end-of-screen duties.

 

It might also be possible to find a playback speed that divides into a line such that it never hits the DLI once aligned.

Link to comment
Share on other sites

Realised that not long after posting... (the 400/800 detached bus stuff)...

 

Maybe it could be that Antic is flawed in design in that it only holds NMI down for a certain number of cycles. Maybe they designed it only with the expectation of encountering a long running instruction like "INC abs,X" with possibly one or two DMA cycles added on top.

Could it be that if the 6502 is in the early stages of processing an IRQ, that the NMI line is ignored for a short period?

 

In theory, a best-case IRQ situation takes 7 cycles. A worst-case IRQ situation could take 13 cycles, plus whatever cycles remain in the instruction executing when the IRQ is called, plus however many DMA steals take place.

 

Does anyone know what happens when an NMI occurs e.g. one or two cycles after an IRQ? Is the first instruction of the IRQ executed, or does the NMI servicing occur straight after the IRQ's "P/PC" info is pushed to the Stack?

Edited by Rybags
Link to comment
Share on other sites

Maybe it could be that Antic is flawed in design in that it only holds NMI down for a certain number of cycles.

 

Yeah, I always wondered when the NMI's get around to clearing themselves, since NMIRST isn't always used.

 

There's probably a more elegant work-around...

 

Here's an idea, how about changing the Pokey divisor dynamically in the IRQ to simulate having the ideal divisor. In other words, control how much it can scoot around by correcting it every so often.

Edited by Bryan
Link to comment
Share on other sites

I think that might be the best solution.

 

In the real world, 90% of the time when dealing with sample data, we would want to use 64 KHz mode. 16 KHz mode affects all voices which in most cases we don't want. 1.79 MHz mode gives a delay that's just too short, 256 cycles is barely over 2 scanlines, 16-bit mode steals an otherwise useful voice.

 

So, we'd probably want to reload AUDF during early VBlank. We can easily align that to an exact cycle by using a WSync. Of course, using STIMER is an absolute no-go since it affects all voices.

 

For game design, then all we need is to make sure we only use AUDF values that don't hit that sour spot where they collide with an NMI.

 

Things could get very confusing though, once you throw in such combinations like VScrolling or Display Lists that move the whole screen (see Sea Dragon).

Link to comment
Share on other sites

Atari's don't "stop the clock"... C= Plus/4 uses that technique (stretches one phase of the clock cycle as viewed by the CPU), Antic only ever uses HALT/DMA line to automatically stall the 6502, the clocks are never messed with.

The whole purpose of the extra circuitry on the 400/800 CPU board is to isolate the bus and hold the clock in a constant state during DMA. The clock circuitry is a combination of a 7474 and a 7402. When Antic's /HALT goes low, ph0 to the CPU is stopped on the next cycle and the address bus is decoupled by 74LS244's. All this circuitry was moved into Sally/6502C, so the CPU now stretches its own clock.

 

The clock to POKEY and ANTIC is certainly not shut down at any time on any 8-bit system as the divisors to the counters even at 1.79Mhz frequency remain constant to produce same frequency interrupt despite the graphics mode with various DMA cycles. If you set divisor to (116,165) in 16-bit mode and @1.79Mhz, the interrupt occurs at same point on screen regardless of graphics mode assuming the point of interrupt is a free CPU cycle.

Link to comment
Share on other sites

So, if we use 64 KHz mode Timers, we're experiencing creep backwards by 2 cycles each IRQ...

If we're doing ~4 KHz playback, that's one IRQ every 4 scanlines. 312/4 = 78 per frame in PAL.

 

Total creep = 78*2 = 156 cycles.

That's not so great.

 

Maybe the solution could be to alternate AUDF between two close values each Timer occurrence, as well as performing a re-align in early VBlank.

Link to comment
Share on other sites

Maybe the solution could be to alternate AUDF between two close values each Timer occurrence, as well as performing a re-align in early VBlank.

 

Or maybe not...

 

the problem here is that our IRQ routine can drift all over the place, so resetting the voice midscreen might occur anywhere on a scanline.

Maybe have a z-page counter that's initialized each VBlank.

Decrement it each Timer routine. When it hits zero, do a WSync and reset the Timer by reloading AUDF.

Link to comment
Share on other sites

Maybe the solution could be to alternate AUDF between two close values each Timer occurrence, as well as performing a re-align in early VBlank.

 

Or maybe not...

 

the problem here is that our IRQ routine can drift all over the place, so resetting the voice midscreen might occur anywhere on a scanline.

Maybe have a z-page counter that's initialized each VBlank.

Decrement it each Timer routine. When it hits zero, do a WSync and reset the Timer by reloading AUDF.

 

Yeah, always trying to hit the right cycle to mask the NMI system reset is as hard as always trying to miss the right cycle. Looks like 9th cycle after WSYNC is one to avoid on NTSC. It's easier to avoid if you write cycle-exact code and don't have to deal with the fluctuating IRQ trigger point.

Link to comment
Share on other sites

Well, I've tried a VBI that puts Pokey into INIT then back to normal... not really any good.

The problem there is that it also affects the Poly-counters which we don't want.

 

STIMER ruled out also since it affects other sounds.

 

Not a great deal of joy being had varying AUDF of our Timer, but really I think it's the only workable solution.

 

Writing main loop code to behave a certain way is also a non-option... we're talking games here so we don't want a big limitation like that.

 

Maybe the solution is to do something similar to what the C64 programmers do - have 2 DLIs. Check VCount on entry. If VCOUNT = the line we want our changes, then do the changes. If we're on the earlier DLI, then just wait around a bit, then do the change.

 

Of course, this is a right pain too especially if we're in character mode and/or have scrolling.

 

 

I think the workable solution/s will be adjusting AUDF along the way, or having checks inside the Timer IRQ itself that disables the Timer IRQ when a DLI is imminent. The DLI can take over sample-playing duties, then re-enable the Timer IRQ before it exits.

 

We'd probably also need to cater for VBlank - because Timers can upset the VBI too.

Link to comment
Share on other sites

Maybe the solution could be to alternate AUDF between two close values each Timer occurrence, as well as performing a re-align in early VBlank.

 

Or maybe not...

 

the problem here is that our IRQ routine can drift all over the place, so resetting the voice midscreen might occur anywhere on a scanline.

Maybe have a z-page counter that's initialized each VBlank.

Decrement it each Timer routine. When it hits zero, do a WSync and reset the Timer by reloading AUDF.

 

Yeah, always trying to hit the right cycle to mask the NMI system reset is as hard as always trying to miss the right cycle. Looks like 9th cycle after WSYNC is one to avoid on NTSC. It's easier to avoid if you write cycle-exact code and don't have to deal with the fluctuating IRQ trigger point.

 

I wrote a test app (attached) to check interference between IRQs and DLIs, and indeed there is exactly one cycle at which a IRQ will block an NMI. In my test app it happens if IRQEN is flipped on eight cycles after STA WSYNC (LDA abs + STX IRQEN). With the one free cycle you sometimes get with WSYNC that's 7 cycles. I have an LDA #imm afterward which might also be executing, which would make it 9 cycles as you say. The NMI is also definitely getting lost, instead of just being delayed. If you press the joystick button, you can see a whole bunch of DLIs get blocked. What I can't tell from this is whether this behavior is due to a problem in ANTIC or in the 6502, although it seems more likely that it's an ANTIC problem.

 

As a side note, Atari800WinPlus 4.0 doesn't support the serial transmit complete interrupt properly, which I'm using to pin the IRQ line here. :(

post-16457-1249798336_thumb.jpg

post-16457-1249798342_thumb.jpg

missdli.zip

Link to comment
Share on other sites

Looks like Antic can't help then... for a moment I thought "Hey, turn on missile DMA" but that doesn't work.

 

I also tried various stuff to try and sync STIMER so that it was aligned with the /28 clock.

That's useless though because it will spoil stuff like chords or near notes in phasing/flanging arrangements.

 

But, I've come up with 2 possible solutions, possibly both might have to be used together:

 

1. During VBlank, momentarily set AUDF1 to 00 and change AUDCTL to put Voice 1 in 1.79 MHz mode.

Wait a little bit, then put the normal Timer constant back into AUDF1 and set AUDCTL back so Voice 1 is in 64 Khz mode again.

 

2. In our Timer routine, altenate AUDF1 between 2 adjacent frequencies... in theory that should cause the timing jitter to just alternate between 2 differing values.

 

In theory, by going to 1.79 Mhz mode, and setting AUDF1 to 00 we should accomplish something similar to using STIMER.

We reset voice 1 to a known state, although given that the docs say there's 4 cycles overhead for an AUDF reload in 1.79 MHz mode, that might be an acceptable solution.

Link to comment
Share on other sites

Nice work Avery, that pins it down nicely.

 

 

Looks like Antic can't help then... for a moment I thought "Hey, turn on missile DMA" but that doesn't work.

 

I also tried various stuff to try and sync STIMER so that it was aligned with the /28 clock.

That's useless though because it will spoil stuff like chords or near notes in phasing/flanging arrangements.

 

But, I've come up with 2 possible solutions, possibly both might have to be used together:

 

1. During VBlank, momentarily set AUDF1 to 00 and change AUDCTL to put Voice 1 in 1.79 MHz mode.

Wait a little bit, then put the normal Timer constant back into AUDF1 and set AUDCTL back so Voice 1 is in 64 Khz mode again.

 

2. In our Timer routine, altenate AUDF1 between 2 adjacent frequencies... in theory that should cause the timing jitter to just alternate between 2 differing values.

 

In theory, by going to 1.79 Mhz mode, and setting AUDF1 to 00 we should accomplish something similar to using STIMER.

We reset voice 1 to a known state, although given that the docs say there's 4 cycles overhead for an AUDF reload in 1.79 MHz mode, that might be an acceptable solution.

 

Don't think no. 2 will work. You'll just be jumping back and forth by 28 cycles. But the 64KHz clock still ticks at 2 cycles less every scanline, which is when the Timer is decremented. Unless the 64KHz clock itself is reset, the timer will hit the bad cycle eventually, I think.

 

No. 1 might work if it can be to a precise cycle. The 64KHz clock will still already hit half the cycles in a scanline position in just one frame though. I don't think you can guarantee that an IRQ alway happens on an odd (or even) cycle though, (even with c64 methods?) which I think is what may be needed to avoid the bad cycle.

 

Looks like this will be a lot of hassle. I think I'll stick with the 1.79MHz Timers on Space Harrier for now ;)

Edited by Sheddy
Link to comment
Share on other sites

This is a pain.

 

We could momentarily change to 1.79 and back to 64 Khz in the Timer IRQ, but:

- we'd probably need a WSYNC to get it aligned/known state.

- we're now chewing through so much CPU that we may as well just be doing sample playback in the main loop.

Link to comment
Share on other sites

I've just confirmed the behaviour using similar technique (enable SEROC IRQ at known times).

 

Burn 5 cycles after WSYNC then Store 8 to IRQEN. It blocks any DLI, except those types that are on single scanline modes.

 

To block single-scanline DLIs, strangely enough, you just need to burn 4 cycles after the DLI.

Might be just the way Antic does DLIs... if it's a single scanline mode line then it must start the NMI one cycle earlier.

 

I also tried the same technique but executing a 7-cycle 6502 Instruction... no problematic effects caused there at all.

 

Also tried varying the delay after WSYNC - no effect. It would seem that you need to store to IRQEN exactly on cycle 112 (or cycle 111 if dealing with single-line modes). But it could be that POKEY has some delay before triggering the IRQ once we've enabled it.

Link to comment
Share on other sites

The clock to POKEY and ANTIC is certainly not shut down at any time on any 8-bit system as the divisors to the counters even at 1.79Mhz frequency remain constant to produce same frequency interrupt despite the graphics mode with various DMA cycles. If you set divisor to (116,165) in 16-bit mode and @1.79Mhz, the interrupt occurs at same point on screen regardless of graphics mode assuming the point of interrupt is a free CPU cycle.

 

Only the CPU's clock is stopped:

 

When Antic's /HALT goes low, ph0 to the CPU is stopped on the next cycle and the address bus is decoupled by 74LS244's. All this circuitry was moved into Sally/6502C, so the CPU now stretches its own clock.

 

When I was talking about correcting for the 64KHz clock, I meant doing something like this:

 

set for 64KHz (28 cycles per tick), 16 counts

desired line position is called '0'

 

IRQ1 - line position is +8 cycles

IRQ2 - line position is +16

IRQ3 - line position is +24 - set for 15 counts

IRQ4 - line position is -4 (24-28) set for 16 counts

IRQ5 - line position is +4

IRQ6 - line position is +12

IRQ7 - line position is +20

IRQ8 - line position is +28 - set for 15 counts

IRQ9 - line position is +0 - set for 16 counts

etc...

 

This would just involve an additional counter in the interrupt handler.

 

Another thought I had is if the IRQ routine is handling DLI's, both DLI's and IRQ's can be directed to a single routine.

Edited by Bryan
Link to comment
Share on other sites

Here is a solution that works without any extra mucking about in the interrupt code.

 

AUDF is changed from 15 to 14, which creates a repeating interrupt pattern that can avoid the NMI (but the pitch is a little higher). The timer is aligned with WSYNC and the sample plays without issues.

 

There are only changes to the init routine (marked with asterisks).

 

EDIT: I revised the attachment. The first one had the wrong source file.

s64_14.zip

Edited by Bryan
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...