Atari POKEY Dual-Channel Timers: Audio Dividers, Polynomial Clocks, and IRQs
Model POKEY channel pairing, AUDF/AUDC dividers, polynomial sources, timer IRQs, and AUDCTL changes as observable clocked hardware.
POKEY’s audio registers are also a programmable timing network. Four channel counters can produce tones and noise, but selected counters can instead clock other channels or raise timer interrupts. The shared AUDCTL register changes the base clock, polynomial source, high-pass relationships, and whether channel pairs are cascaded. The same byte written to AUDFx therefore does not have one universal frequency meaning: its effect depends on the clock path selected at that moment.
This is why a waveform-only implementation often sounds plausible but fails software that uses POKEY as a timer. The device’s counter underflows, channel links, audio-control modes, and interrupt status are observable independently of the analog-like output. A robust model tracks those state transitions explicitly, then derives audio edges from them. It also records which machine clock is being used; a host sample rate is not a substitute for the POKEY clock domain.
Separate the channel divider from the output circuit
Each of the four channels has an AUDF frequency divisor and an AUDC control byte. AUDC selects behavior for the channel output: polynomial/noise selection, a pure tone path, volume-only behavior, and a four-bit volume field. It is not itself the period register. AUDF changes the countdown interval, while AUDCTL selects the source clock and special relationships among counters.
The Atari 8-bit POKEY reference implementation describes base audio clocks as exact divisions of its approximately 1.79 MHz source: division by 28 gives the roughly 63.9 kHz clock, and division by 114 gives the roughly 15.7 kHz clock. The period is a hardware count, not a host audio buffer length. The Altirra hardware reference further describes an eight-bit timer divisor range from one through 256, so an AUDF value of zero means the minimum programmed divisor, not a disabled channel.
Do not hard-code a convenient formula such as sample_rate / AUDF. It omits the selected base clock, zero-as-256 interpretation, fast-clock options, and pair mode. It also confuses the timer event stream with the later choice of output polarity, volume, and polynomial gating. Keep the clock source and counter stage visible in traces so a wrong pitch and a missed IRQ can be distinguished.
AUDCTL makes channel pairs into longer counters
AUDCTL is shared by all four channels. Its channel-pair bits couple channel 1 to channel 2 and channel 3 to channel 4. In a joined pair, the lower-numbered counter supplies the low part and the adjacent channel contributes the high part; updates to either divisor can therefore change the effective period. The individual-channel view is no longer enough to explain the frequency. The control byte also offers fast clock selection for channel 1 or 3 and a choice between the 15 kHz and 64 kHz base clocks.
The POKEY definitions in the Atari800 source make the register bit assignments auditable: AUDCTL bit 7 chooses the nine-bit polynomial rather than the seventeen-bit one, bits 6 and 5 select the fast source for channels 1 and 3, bits 4 and 3 enable the two pairings, bits 2 and 1 select high-pass filtering, and bit 0 selects the base clock. Treat that as a register map, not a blanket promise that every counter and every polynomial advances on every CPU cycle. The timing rules belong to their own clocked state.
High-pass behavior also creates a dependency between channels. The selected lower channel can control filtering of a higher channel, so silencing one channel’s audible output does not necessarily mean its timing role is irrelevant. Separate “counter generates edge,” “channel contributes a waveform,” and “filter removes or retains a component” in the implementation. That decomposition is especially helpful when a game uses one channel as a clock source and another as the audible voice.
Polynomial sources are finite-state generators
POKEY supports polynomial sequences rather than a single generic random-noise function. The Atari800 header records the table lengths used by the implementation: 15 states for a four-bit polynomial, 31 for a five-bit polynomial, 511 for a nine-bit polynomial, and 131,071 for a seventeen-bit polynomial. The control bits choose combinations of those sources for the channel output. Their phase is part of device state: resetting or advancing a generator at the wrong event changes the exact bit pattern even if the average noise spectrum sounds similar.
A faithful emulator should not call a host RNG for each output sample. It should maintain the shift-register state and advance it at the hardware-defined clock edge. Saving only the current audio accumulator is insufficient for deterministic state restore if the polynomial phase is lost. A test can seed a short state, clock exactly N eligible events, and compare the next output bits with an established reference implementation. The test should specify chip revision and initialization behavior where those are known, since startup state can matter.
Volume-only output deserves separate testing. It forces a constant channel level rather than producing a periodic waveform, and is used by software for digital-style mixing. A renderer that treats every channel as a square wave will add unwanted edges. Conversely, a mute or zero-volume setting must not automatically stop the divider if the counter still participates in a pair, timer, or filter relationship.
Timer interrupts are not four interchangeable IRQs
POKEY exposes timer interrupts associated with channels 1, 2, and 4. The channel 3 counter can still participate in audio and pair-mode behavior, but it is not a peer timer IRQ source in the three-source set. Software-visible IRQEN and IRQST state must be modeled in addition to the divider. An underflow can be useful to audio logic even if its interrupt is disabled; enabling an interrupt affects CPU notification, not the underlying counter itself.
The ordering of writes matters. A write that changes AUDCTL, a divisor, interrupt enable, or the serial subsystem’s control can occur near a pending underflow. Model register writes at their emulated bus time and define when the new value feeds the timer. Do not recompute a counter’s already elapsed fraction retroactively unless a hardware test demonstrates that behavior. For debugging, log cycle, channel, selected clock, current count, reload value, pair mode, IRQ enable, and IRQ status transition.
POKEY also contains keyboard, potentiometer, and serial interfaces, but they are separate functions with interactions and timing of their own. This article focuses on audio-divider and timer pathways. If an application uses serial completion or keyboard scanning as the apparent trigger, inspect those peripheral registers rather than attributing every interrupt to an audio channel.
A small divider fixture with explicit limits
This Python fixture demonstrates just the zero-to-256 divisor convention and underflow event. It is not a full POKEY model: it omits AUDCTL clock selection, joined channels, polynomial phase, audio output gating, IRQ register semantics, and CPU-cycle synchronization. Keeping that limitation explicit prevents a useful unit test from being mistaken for a complete chip implementation.
class PokeyDivider:
def __init__(self, audf):
if not 0 <= audf <= 0xFF:
raise ValueError("AUDF is an unsigned byte")
self.reload = 256 if audf == 0 else audf
self.count = self.reload
self.underflows = 0
def clock(self, pulses=1):
if pulses < 0:
raise ValueError("clock pulses cannot be negative")
events = 0
for _ in range(pulses):
self.count -= 1
if self.count == 0:
events += 1
self.underflows += 1
self.count = self.reload
return events
timer = PokeyDivider(0)
assert timer.clock(255) == 0
assert timer.clock(1) == 1
assert timer.count == 256
In a real implementation, replace the per-pulse loop with a cycle-accurate event scheduler if needed, but preserve the same externally testable boundary. Add a second fixture for a paired 16-bit count only after the reference’s exact reload and edge order have been verified. Tests should cover divisor values zero, one, and 255; clock changes; writes one pulse before underflow; pair/unpair transitions; interrupt enable before and after an event; and state restoration between two edges.
A forensic test plan for sound and timing
Start with the simplest observable path: one unpaired channel, one base clock, fixed volume, no polynomial mixing. Record the time between output transitions as an integer number of POKEY input clocks. Then vary only AUDF and check the expected divider behavior. Repeat with the alternate base clock and fast clock so tests exercise each source instead of merely confirming the default.
Next join channels 1/2 and 3/4 independently. Sweep low and high divisor bytes around carry boundaries and capture both waveform edge times and CPU-visible timer events. Change a divisor while the counter is mid-period and document the reference result. Test high-pass relationships with the channel that supplies the filter source muted at the output; verify that the filter still sees its timing if the hardware contract requires it.
Finally test interrupt state: disable all three timer IRQs, let counters underflow, and assert that no CPU request is raised; enable one source and verify the correct pending bit and clear/acknowledge path. Exercise simultaneous underflows and serialize the pending event state. Save states should preserve every counter, pair topology, polynomial LFSR, output phase, and IRQ latch needed to generate an identical continuation.
Acceptance criteria
A production-grade POKEY model keeps four divider states separate from four output circuits, then applies AUDCTL clock selection, channel-pair coupling, polynomial selection, filtering, volume, and the three timer IRQ sources. It can explain each audio edge or interrupt from the selected clock and current register state. It also labels Atari800/Altirra-derived behavior as a reference model, not as proof that every silicon revision has identical analog output. With these boundaries explicit, engineers can diagnose pitch drift, noisy waveforms, and missing timer interrupts without patching unrelated audio code.
Related:
- Atari ST MC68901 MFP: Timer Modes, Interrupt Priority, and In-Service State
- Atari 8-Bit ANTIC Display Lists: LMS, Scrolling, DLI, and DMA Steals
Sources: