Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

CH1/CH2: Pulse Channels

The square channels generate tone with two counters. An 11-bit period divider overflows every 0x800 − period ticks. A 3-bit duty step counter advances once per overflow. The behavioural surface is Pan Docs territory; what the netlist supplies is the load network that makes the trigger quirks fall out. The netlist also supplies the exact silicon meaning of "the low two bits of the frequency timer are not modified".

At a glance

  • The "frequency timer" is a 13-bit object: a free-running 2-bit prescaler plus the 11-bit divider. Triggers reload only the upper 11 bits.
  • Trigger and natural overflow share one load net — there is no trigger-only load path, and the load is level-sensitive.
  • A channel-enabling trigger's first overflow comes one chN_1mhz cycle late. A retrigger of a running channel runs at the steady period, as does every natural overflow (Rule below).
  • The duty counter clocks on chN_frst falling and resets only on apu_reset. Fast retriggers reload the divider before it can overflow, so the counter starves rather than resetting.

The period divider

apu_4mhz → prescaler /2 → prescaler /2 → chN_1mhz ─(gated by chN_fdis)→ toggle clock
   (CH1: AJER → CALO;  CH2: ATEP → CEMO)
                ↓
  11-bit divider: 11 × tffnl in three ripple sub-chains (4 + 4 + 3),
  joined by single inverters; all stages share one load enable:
      CH1: epyk = NOR(ch1_frst, ch1_restart) → fume/dega/dako
      CH2: duju = NOR(ch2_frst, ch2_restart) → cogu/erog/gypa
Gate (CH1 / CH2)RoleTypeClock / TriggerNotes
AJER / ATEPPrescaler /2 stage 1dffrapu_4mhz (per-channel buffer)Toggle; free-running 2 MHz; only reset is apu_reset — never reloaded by triggers
CALO / CEMOPrescaler /2 stage 2dffrstage-1 rippleToggle; free-running 1 MHz; output (buffered / directly) is chN_1mhz
ch1_fdis / ch2_fdisChannel-disable latchnand_latchSet: DAC-off / apu_reset; Reset: trigger (one cycle delayed)While set, gates the divider toggle clock low
FULO→GEKU / CAMA→DOCADivider toggle clocknor2 + notchN_1mhz gated by chN_fdisBit 0 toggles on the active-high rise
GAXE HYFE JYTY KYNA / DONE DYNU EZOF CYVODivider bits 0–3tffnlbit-to-bit rippleLoad enable fume / cogu
JEMA HYKE FEVA EKOV / FUXO GANO GOCA GANEDivider bits 4–7tffnlripple via inverter (KYPE / sibling)Load enable dega / erog
EMUS EVAK COPU / HEVY HEPU HERODivider bits 8–10tffnlripple via inverter (DERU / sibling)MSB q = chN_ftick — the overflow edge
CALA + COMY / sibling pairOverflow detectornot + dffr (/2 toggle)clk: NOT(chN_ftick)Produces the one-cycle self-clearing pulse chN_frst; also async-reset by triggers
ch1_restart / ch2_restartTrigger synchroniserdffrchN_1mhzCaptures the NRx4-bit-7 write; aligns it to the next chN_1mhz↑
EPYK / DUJUDivider load enablenor2chN_frst, chN_restartActive-low; fanned through fume/dega/dako / cogu/erog/gypa (fan-out split only — no functional difference between sub-chains)

Each divider stage is a toggle-with-load cell: l=1 → q ← d combinationally (level-sensitive); l=0 → toggles on its ripple clock.

Rule: the 2-bit prescaler is untouchable

AJER/CALO (ATEP/CEMO) have no input from the trigger or the overflow; their only reset is apu_reset. This is the silicon form of "the low two bits of the frequency timer are NOT modified on trigger". The "frequency timer" is the 13-bit concatenation of prescaler + divider. Triggers reload only the upper 11 bits. Under fast retriggers, the free-running prescaler keeps carrying into the divider, so the duty step eventually advances.

The remaining structural facts:

  • The divider counts up to 0x7FF; the MSB's fall marks overflow. The one-stage detector (CALA + COMY) produces the one-chN_1mhz-cycle pulse chN_frst that opens the reload.
  • Trigger and overflow share the same load net. chN_restart and chN_frst are the two NOR inputs — there is no trigger-only load path.
  • The reload is level-sensitive. During the window the divider tracks the period source combinationally. The captured value is whatever the source held when the load fell.

Period sources. CH2 (and CH3) load directly from the latched NRx3/NRx4 bits. CH1 loads from acc_d0..10 — the sweep shadow accumulator. That accumulator has three write paths: the sweep adder's sum (clocked per sweep fire), and async per-bit set/reset from NR13/NR14 writes. A trigger immediately after an NR13/NR14 write therefore loads the just-written period (the async path has settled long before the synchroniser fires).

Measured: the natural-overflow reload

A natural overflow reloads the divider to exactly {NR14[2:0], NR13} for one ch1_1mhz cycle, then resumes counting (dmg-sim measurement, purpose-built ch1_retrigger ROM).

The reload window, edge by edge

EdgeWhat happens
Divider MSB falls (overflow)chN_ftick↓ → CALA↑ → COMY toggles → chN_frst↑
chN_frst↑Load enable falls → all 11 stages enter level-sensitive load (q ← period source)
Next chN_1mhz↑AND(COMY, chN_1mhz) asserts COMY's async reset → chN_frst↓
chN_frst↓Load enable rises → stages return to toggle mode holding the loaded value
NRx4 trigger writeAsync-loads the period source (CH1: acc_d8..10; CH2: NR24 latches); the strobe arms the synchroniser
Next chN_1mhz↑ after the writechN_restart↑ — the same load path opens, held while restart is high
chN_restart_dly↑ (one cycle later)chN_fdis clears — the channel becomes audible

Trigger-vs-overflow on the same edge

When the trigger's synchroniser edge coincides with a would-be overflow, the race resolves at the overflow-capture DFF (COMY). COMY's async reset arrives through DYRU = NOR(apu_reset, ch1_restart, DOKA). That reset asserts ~2 ns after the edge, before any overflow ripple or load-induced MSB fall can reach COMY's clock pin (measured: reset at +1,970 ps, earliest possible clock attempt at +2,830 ps). Across all five sub-cases (divider at 0x7FE, 0x7FF, or lower, period MSB set or clear): no chN_frst pulse, no duty advance. The trigger reloads the divider and opens the channel one cycle later, but the duty step is preserved (dmg-sim measurement, 41 trigger events).

The one distinct case is a trigger landing while an overflow window is already active (COMY=1 from the prior edge). That trigger forces the same chN_frst fall the natural self-clear would have produced. The counter advances once, indistinguishably from the natural path.

A retrigger that merely follows a completed overflow is likewise unremarkable. The divider counts the chN_1mhz ticks between the overflow's reload and chN_restart↑ as it would any interval. That count is period-independent, with no suppression and no extra hold (dmg-sim measurement).

Trigger-to-first-overflow: the load-settle cycle

The divider's toggle clock (GEKU / DOCA) is chN_1mhz gated by chN_fdis, so while the channel is disabled the divider holds its value. A channel-enabling trigger loads the divider (chN_restart↑, above) while chN_fdis is still set. The loaded value is then held one extra chN_1mhz tick, because the divider cannot count until chN_fdis clears. A trigger of an already-running channel (chN_fdis already 0) reloads while the toggle clock is still running. The divider then counts on the next tick, with no extra hold.

Rule: only the channel-enabling trigger is one cycle late

Channel-enabling trigger → first overflow = (0x800 − period) + 1 chN_1mhz cycles. A retrigger of a running channel — like every natural overflow — takes the steady 0x800 − period. Measured at 0x800 − period = 63: a channel-enabling trigger's first interval is +64.4 cycles, then exactly +63. A retrigger's first interval at that period is +63. In the same measurement, toggling the DAC off then on restores the +1 on the following trigger (dmg-sim measurement).

For implementors

Apply the two-tick hold (the loaded value held for two consecutive ticks before the first toggle) only on the trigger that re-enables the channel (chN_fdis 1→0). Applying it to every trigger makes a retrigger's first interval one tick too long. The boot chime's second trigger retriggers the running channel, so that spurious +1 leaves the post-boot CH1 divider at 0x7F8 instead of 0x7F9. Collapsing the two ticks onto one edge makes the channel-enabling trigger's interval one tick too short — the off-by-one behind several hardware-verified duty/envelope race outcomes.

The duty step counter

A 3-bit ripple counter clocked by chN_frst falling — the end of each natural-overflow window. Its only reset is apu_reset: triggers, DAC-off, and channel-stop never touch it.

Gate (CH1 / CH2)RoleTypeClock / TriggerNotes
DAJO / CULECounter clocknot_x1NOT(chN_frst)Rising edge = chN_frst falling
ESUT / CANODuty counter bit 0dffrDAJO / CULEToggle; reset only by apu_reset
EROS / CAGYDuty counter bit 1dffr_ccbit-0 ripple
DAPE / DYVEDuty counter bit 2 (MSB)dffr_ccbit-1 ripple
COSO CAVA CEVU CAXO / DOMO DYTA DOJU DOVEDuty-pattern selectsnor2 ×4—NRx1[7:6] decode, one per duty value
ENEK EZOZ CODO / EGOG DYMU DARECounter-state decodesand2 / not—counter=6 / counter ∈ {6,7} / counter ∈ {0..5}
ch1_pwm / ch2_pwmDuty waveform outputao2222—4:1 mux: OR of four (decode ∧ select) pairs
DUWO / DOMEPWM latchdffrchN_frst↑Captures the pre-advance step; reset to 0 by apu_reset
COWE / CYSEPWM gated by channel-runningand2—ANDed with the envelope to form the 4-bit DAC input

The duty decode:

NRx1[7:6]Pattern (high at step k)Decode
0000000010 (12.5%)counter = 6
0100000011 (25%)counter ∈ {6,7}
1000001111 (50%)counter bit 2
1111111100 (75%)counter ∈ {0..5} — the 25% decode negated

The capture-on-rise / advance-on-fall split means each overflow plays the pre-advance step.

First-trigger gating, decoded. "Duty clocking is disabled until the first trigger" is implemented one stage upstream. The chN_fdis disable latch (set by DAC-off or apu_reset, cleared one cycle after a trigger) gates the divider's toggle clock. No divider ticks means no overflows and no duty clocks. "The first duty step plays as if it were 0" is the PWM latch. apu_reset resets that latch to 0, and only the first real overflow re-captures it.

Fast retriggers never reset the duty counter. Each trigger reloads the divider before it can overflow, removing the only advance path — the counter starves rather than resetting. It freezes at its pre-loop value. That freeze is Pan Docs' "duty step never advances" with the mechanism made precise.

Post-boot state: the boot chime leaves CH1's duty counter at 2. The DAC is on (NR12 = 0xF3 retained), the running latch is set, and the period divider is mid-count at 0x7F9. Only NR52 clears the duty counter, so it persists into the handoff. CH2 stays at 0. See post-boot state. This post-boot step is the baseline the fast-retrigger staircase below counts up from.

The fast-retrigger phase staircase

A fast-retrigger loop freezes the duty counter at its running value, and which value is a staircase in the trigger phase. While the divider free-runs the counter advances once per overflow. The pinned step is therefore just how many overflows landed before the loop's first reload caught the counter. Each extra overflow interval (0x800 − period ticks) of delay adds +1 step.

Measured: the pinned-step staircase

Sweeping the loop's first trigger one M-cycle later across 80 steps (period 0x7F0, a 64-T overflow interval) walks the pinned counter 0→1→2→3→4→5. That is one step per 64 T of added delay. The boundary is sharp to one chN_1mhz tick: adjacent triggers 4 T apart give a clean one-step change. That sharpness holds at counter 0→1 and at the silent→audible 4→5 edge of the 50% pattern (dmg-sim measurement, purpose-built apu_ch1_duty_phase_sweep ROM).

The emitted bit is the pre-advance step. DUWO captures on chN_frst↑ and the counter advances on the following chN_frst↓, so a counter pinned at k ≥ 1 plays step k − 1. A counter pinned at 0 has seen no overflow, so the PWM latch still holds its apu_reset 0. The flip is therefore one step past the pattern edge. For the 50% pattern (high at steps 4–7) the emitted bit is 0 for a counter pinned at 0–4. The bit goes high at 5 (measured chN_out 0 → full-scale across 4→5).

Rule: the trigger phase quantum is one tick

A retrigger write lands on an M-cycle (4-T) boundary, and chN_restart re-synchronises it to the next chN_1mhz↑. The trigger therefore moves only in whole chN_1mhz ticks, never a sub-tick. The pinned step changes once per overflow interval. At a step boundary a single 4-T shift carries the pin across that overflow's chN_frst fall, which flips the captured count. The cause is one overflow, not a sub-chN_1mhz parity effect. Which side of the edge a boundary trigger lands on is the overflow-capture race of Trigger-vs-overflow on the same edge above.

The inter-trigger audible window

With trigger spacing longer than the overflow interval, the full chain — overflow → PWM capture → mixer — runs between triggers. Measured across a 7-overflow inter-trigger window (dmg-sim measurement, gambatte duty3_pattern_pos7 ROM): the duty counter walks 0→7. The PWM latch holds 1 through six overflows of the 75% pattern, then captures 0 at the seventh, where counter 6 decodes low. That window produces a sustained ~375 µs audible pulse and then silence. Across that window every digital gate operated exactly as the cell map says, with no hidden muting.

A sweep period change rescales the duty capture cadence

The staircase and audible window above hold the period fixed. CH1's period is the sweep shadow accumulator acc_d. A sweep fire that commits a new acc_d changes the rate at which the emitted duty level updates. DUWO re-samples the duty waveform only at each overflow's chN_frst↑ (The duty step counter above), so level updates are one overflow interval — 0x800 − period ticks — apart. A period change rescales that spacing from the next overflow. The counter goes on climbing. The emitted bit holds its last capture until the new period's first overflow re-samples it. A retrigger before that overflow does not shorten the wait. chN_restart async-clears COMY through DYRU, so the retrigger produces no chN_frst pulse of its own (Trigger-vs-overflow on the same edge above). The retrigger only reloads the divider to the new period, leaving the stale capture in place.

Measured: a sweep period cut defers the next duty capture

At the maximum period 0x7FF the divider overflows every chN_1mhz cycle, so DUWO follows the climbing duty step cycle by cycle. One decrease-sweep fire (NR10 = 0x1F: pace 1, decrease, shift 7) cuts the period to 0x7F0. The every-cycle overflow train stops. The divider, reloaded to 0x7F0, now counts a full 16-tick interval to its next overflow. A retrigger placed just after the fire finds the duty counter already at step 4, high in the 50% pattern. DUWO still holds the low capture from the previous overflow. The next chN_frst↑ that would re-sample DUWO lands ≈16 ticks later, past the SameSuite channel_1_sweep_restart round-1 probe window. That probe window reads silent throughout. Holding the period at 0x7FF instead (no sweep, otherwise identical) keeps the every-cycle overflow train running. An overflow one cycle before the retrigger captures the now-high step 4, so the channel is audible. The duty counter advances identically either way — only the capture cadence differs (dmg-sim measurement, purpose-built 0x7FF→0x7F0 and constant-0x7FF ROMs).

Rule: after a period change the emitted level lags the counter by one new-period overflow

A sweep fire — or any acc_d write — takes effect at the next overflow's reload and rescales the overflow interval from there. Because DUWO re-samples only at an overflow, the channel keeps playing the pre-change level for one full overflow interval of the new period (0x800 − new_period ticks) afterwards. That holds wherever the counter has reached. A retrigger inside that interval does not shorten it. The retrigger produces no chN_frst pulse, and it reloads the divider to the new period. The first level update therefore still waits for that period's first overflow.

The commit, the overflow that first reloads the new period, and the retrigger never share one chN_1mhz edge. They fall in separate cycles, so they never contend on the divider-load net (EPYK). The commit acts on the duty path purely through the period it sets, and hence the capture cadence.