Skip to main content
NESdev Wiki
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

APU Pulse

The NES APU pulse (square) channels generate a pulse wave with variable pulse width.

              Sweep Unit ---> Timer
                   |            |
                   |            |
                   |            v 
                   |        Sequencer   Length Counter
                   |            |             |
                   |            |             |
                   v            v             v
Envelope -------> Gate -----> Gate -------> Gate ---> (to mixer)

Registers

Register $4000, $4001, $4002 and $4003 correspond to the first Pulse channel, $4004, $4005, $4006, $4007 correspond to the second Pulse channel.

Pulse settings ($4000 / $4004, write)

7  bit  0
---- ----
DDLC VVVV
|||| ||||
|||| ++++- When C is set: channel volume value
||||       When C is clear: envelope divider reload value
|||+------ Constant volume flag (override envelope with V)
||+------- Length counter halt flag and envelope loop flag
++-------- Pulse width
               0 = 12.5%
               1 = 25.0%
               2 = 50.0%
               3 = 75.0%

Sweep control ($4001 / $4005, write)

7  bit  0
---- ----
EPPP NSSS
|||| ||||
|||| |+++- Bit shift count
|||| +---- Negate flag
||||           0: add to period, sweeping toward lower frequencies
||||           1: subtract from period, sweeping toward higher frequencies
|+++------ Clock rate
+--------- Sweep enable flag

Writing to this register also sets sweep reload flag (the write does not have immediate effect, see below).

Each of the two pulse channels contain sweep units, which can automatically change the period by up or down without needing CPU’s help to calculate it.

Sweep unit operation

Whenever the current period changes (either from the period register writes or update from the sweep unit), the target period is also calculated immediately, in this way:

  1. The change amount is calculated by shifting the current period’s bits right by the shift count (S bits). Becomes negative if the negate flag (N bit) is set.
  2. The target period is the sum of the current period and the change amount, clamped to zero if this sum is negative.
    • The two pulse channels have their adders’ carry inputs wired differently, which produces different results when each channel’s change amount is made negative:
      • Pulse 1 adds the ones’ complement (−c − 1). Making 20 negative produces a change amount of −21.
      • Pulse 2 adds the two’s complement (−c). Making 20 negative produces a change amount of −20.
  • If the target period is larger than $7FF, it silences the channel.
    • This muting happens regardless of the state of the sweep unit. Meaning, even if sweep unit isn’t enabled and the current period itself does’t exceed $7FF, the sweep unit mutes the channel if the calculated result overflows $7FF. Thus to fully disable the sweep unit, a program must additionally turn on the negate flag (N bit), such as by writing $08. This ensures that the target period is not greater than the current period and therefore not greater than $7FF.

When the frame counter generates a half-frame clock (at ≈120 (NTSC) or ≈96Hz (PAL)), following occur:

  1. If-
    • the sweep clock counter is zero,
    • the sweep is enabled (via E flag),
    • the shift count (S bits) is nonzero
    • and the sweep unit is not muting the channel, the pulse’s current period is updated (set) to the target period. (see above)
  2. If the sweep clock counter is zero or if the sweep reload flag is set, the counter is reloaded to P and the sweep reload flag is cleared. Otherwise, the counter is decremented.

Timer period lower 8 bits ($4002 / $4006, write)

7  bit  0
---- ----
TTTT TTTT
|||| ||||
++++-++++- Lower 8 bits of timer period value

Length counter load value and timer period upper 3 bits ($4003 / $4007, write)

7  bit  0
---- ----
LLLL LTTT
|||| ||||
|||| |+++- Upper 3 bits of timer period value
++++-+---- Length counter load value

Writing to this register also resets the sequencer index (i.e., the pulse phase) to the first value, restart envelope.

Sequencer operation

The sequencer is clocked by a timer where the period is the current 11-bit period value plus one. Given “t” as the timer value, this timer is updated every APU cycles (i.e., every second CPU cycles), and counts t, t-1, ..., 0, t, t-1, ..., clocking the waveform generator when it goes from 0 to t.

Since the period of the timer is t+1 APU cycles and the sequencer has 8 steps, the period of the waveform is 8*(t+1) APU cycles, or equivalently 16*(t+1) CPU cycles.

With:

  • fCPU = the clock rate of the CPU
  • tval = the current period value
  • fpulse = the frequency of the wave generated by this channel

Resulting:

  • fpulse = fCPU/(16*(tval+1))
  • tval = fCPU/(16*fpulse) - 1

Note: A period of t < 8, either set explicitly or via a sweep period update, silences the corresponding pulse channel.

Duty Cycle Sequences

DutyOutput waveform
00 1 0 0 0 0 0 0 (12.5%)
10 1 1 0 0 0 0 0 (25%)
20 1 1 1 1 0 0 0 (50%)
31 0 0 1 1 1 1 1 (25% negated)

Notice that a few Famiclone units have swapped APU duty cycles, works as if the meaning of the two V bits are swapped each other.

Implementation details

The reason for the odd output from the sequencer is that the counter is initialized to zero but counts downward rather than upward. Thus it reads the sequence lookup table in the order 0, 7, 6, 5, 4, 3, 2, 1.

DutySequence lookup tableOutput waveform
00 0 0 0 0 0 0 10 1 0 0 0 0 0 0 (12.5%)
10 0 0 0 0 0 1 10 1 1 0 0 0 0 0 (25%)
20 0 0 0 1 1 1 10 1 1 1 1 0 0 0 (50%)
31 1 1 1 1 1 0 01 0 0 1 1 1 1 1 (25% negated)

Mixer output

The mixer receives the pulse channel’s current envelope volume (lower 4 bits from $4000 / $4004) except when

If any of the above are true, then the pulse channel sends zero (silence) to the mixer.

Edit this page on GitHub