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

CH3: Wave Channel

CH3 reuses the square channels' divider topology with two changes. A single /2 prescaler runs the divider at 2 MHz, which halves the overflow interval — Pan Docs' (2048 − period) × 2 T-cycles. A 16-byte wave RAM stepped by a 5-bit wave-position counter replaces the duty machinery. The distinctive CH3 behaviours are the trigger delay, the locked CPU access, and the retrigger corruption. All three live in the small synchroniser and SRAM circuits this chapter maps.

At a glance

  • Same 4+4+3 divider topology as CH1/CH2, but clocked at 2 MHz by a single-prescaler chain. The trigger synchroniser samples on ch3_2mhz↓, the opposite edge family from CH1/CH2.
  • Triggers reset the wave-position counter to 0 (unlike the duty counter), and a wrap interlock dwells that counter at 0 for one extra overflow.
  • CPU wave-RAM access while CH3 runs goes wherever CH3 points; writes land unconditionally at CH3's current byte.
  • DAC-off freezes, never clears: the wave data latch keeps its last strobed byte through NR30 off/on — only NR52 power-off resets it. A retrigger replays the stale byte until its first strobe.
  • Retrigger corruption is a 4-byte row copy on DMG too — the single-byte-on-DMG rule is inaccurate against the gate-level model.

Period divider

Identical 4+4+3 tffnl topology to CH1/CH2 (CH1/CH2), at 2 MHz and with no sweep:

GateRoleTypeClock / TriggerNotes
CERYCH3 prescaler /2 stagedffrapu_4mhz (via CYBO buffer)Toggle; free-running; q = ch3_2mhz directly; only reset is apu_reset — never reloaded by triggers
GUGUChannel-disable latch (q_n = ch3_fdis)nand_latchEnable: FAJU = NOT(GYRA), the trigger pulse retimed one ch3_2mhz edge behind GYTA; Disable: FUVO = NOR(ch3_amp_en_n, apu_reset) — a held levelWhile ch3_fdis is high, gates the divider toggle clock low
HEFO → JUTYDivider toggle clocknor2 + notch3_2mhz gated by ch3_fdisOne toggle per T-cycle
KUTU KUPE KUNU KEMUDivider bits 0–3tffnlbit-to-bit rippleLoad enable kyko
KYGU KEPA KAFO KENODivider bits 4–7tffnlripple via KYRU inverterLoad enable kaso
KEJU KEZA JAPUDivider bits 8–10tffnlripple via KESE inverterMSB q = ch3_ftick — the overflow edge
HYFO + HUNOOverflow detectornot + dffr (/2 toggle)clk: NOT(ch3_ftick)One-ch3_2mhz-cycle self-clearing pulse ch3_frst; also async-reset by triggers
ch3_restartTrigger synchroniser qdffrFABO (= NOT(ch3_2mhz))Captures the NR34-bit-7 write on ch3_2mhz↓ — opposite-phase sample edge to CH1/CH2
HERACH3 divider load enable (active-low)nor2ch3_frst, ch3_restartFanned through jera/kaso/kyko (fan-out split only)

The period source is the latched NR33/NR34 bits (drlatch_ee cells written by the apu_wr ∧ ff1d/ff1e strobes). There is no sweep adder in the path. The reload is level-sensitive exactly as on CH1/CH2.

GUGU's disable side (FUVO) is a held level, so restoring the DAC (NR30 bit 7 back high) does not restart the divider. Only a trigger clears ch3_fdis, one ch3_2mhz edge after the ch3_restart pulse (measured). While FUVO holds the disable level it pins GUGU's q_n, so a trigger arriving during a DAC-off window leaves ch3_fdis set throughout.

Wave-position counter

A 5-bit ripple counter clocked by ch3_frst falling — one step per overflow. Bit 0 selects the nibble; bits 1–4 drive the wave-RAM byte address.

GateRoleTypeClock / TriggerNotes
DEROCH3 wave-position counter clocknot_x1NOT(ch3_frst)Rising edge = ch3_frst falling
EFARCH3 wave-position bit 0 (wave_nibble_sel)dffrDEROToggle; reset by ETAN
ERUS / EFUZ / EXEL / EFALBits 1–4 (wave-RAM byte address)dffrbit-to-bit rippleReset by ETAN; EFAL's q_n clocks FETY
ETANWave-position counter reset (active-low)nor2ch3_restart, FETYClears the counter on trigger or wrap
FETYCH3 counter-wrap interlock (/2 toggle)dffrEFAL↓ (counter wrap 31→0)Holds the counter at 0 for one extra overflow; cleared by the next ch3_frst↑
  • Triggers reset the wave-position counter to 0 — ch3_restart is an input to ETAN. Unlike the duty counter, the wave-position counter does not survive a retrigger. (This asymmetry is the netlist's answer to Pan Docs' CH3-vs-pulse retrigger contrast.)
  • The wrap interlock (FETY) holds the counter at 0 for one extra overflow after wrapping 31→0 before the next count resumes.

The trigger synchroniser and its delay

An NR34 trigger traverses four stages — with a fifth cell shaping the pulse:

StageCellTypeClock / EnableRole
1GAVUdrlatch_eeenable: apu_wr ∧ ff1eWrite-strobe latch — captures the trigger bit at T3/T4 of the write M-cycle
2FOBAdffrapu_phiM-cycle-boundary DFF
3GOFYnor_latch—Tag latch
4GARAdffrch3_2mhz↓q is ch3_restart
5GYTAdffrone edge behind GARAAsync-resets GARA — shapes the pulse to exactly one ch3_2mhz cycle

Measured end to end (dmg-sim measurement): write commits at T4-mid; boundary DFF at T1 of the next M-cycle; ch3_restart↑ on the next ch3_2mhz↓ (T2 under quickboot phase); cleared two T-cycles later. The aggregate from write strobe to first overflow at period 0x7FF is 9–10 T-cycles. The synchroniser accounts for 6–8 of those T-cycles, depending on phase. That is the silicon content of the community's "+6 T-cycle trigger delay". The T2-vs-T4 placement is apu_reset-locked, like every other APU phase (APU clocks).

Wave RAM: address mux, read strobe, CPU access

The wave RAM's 4-bit address is muxed between the CPU bus and the wave-position counter's bits 1–4. ch3_active steers that mux. ch3_active is a DFF re-timed copy of the trigger-armed FOZU latch, which FYGO = OR3(DAC-off, GEDO length-stop, power-off) clears.

CH3's own byte read is strobed by wave_data_latch. That strobe is ch3_frst pushed through a three-DFF apu_4mhz synchroniser (BUSA → BANO → AZUS) — the strobe chain. Measured: the strobe rises ~1.5 T-cycles after ch3_frst↑ and stays high ~1 T-cycle. The captured byte holds on eight drlatch_ee cells — the wave data latch — until the next strobe. Those cells are CYFO CESY BUDY BEGU (bits 0–3) and CUVO CEVO BORA BEPA (bits 4–7). Playback from the wave data latch is combinational. An inverting nibble mux steered by wave_nibble_sel feeds the NR32 volume shift, which feeds the ch3_active output gates (BARY BYKA BOPA BELY). Those gates drive the channel's 4-bit DAC input.

Measured: DAC-off never resets the data latch

The wave data latch's only reset is ACOR = NOT(apu_reset), an NR52 power-off. BAMA, the strobe chain's reset, is the same signal. Clearing NR30 bit 7 raises ch3_amp_en_n. That signal's complete fan-out is FUVO and FYGO (the divider-freeze and FOZU clears), the global DAC-off term (TACE), and the DAC power gate. ch3_amp_en_n never reaches the wave data latch or the strobe chain. Measured across a stop → NR30-on → retrigger sequence at period $700: the pre-stop byte survives on the cells throughout. The only zeroing at the DAC input is the ch3_active gate. Once the retrigger re-arms ch3_active, the DAC input replays the stale nibble for the full first sample period, until the first wave_data_latch strobe reloads byte 0. That first sample period is one 512-T overflow interval plus the strobe chain, measured at 257 ch3_2mhz cycles ≈ 514 T-cycles.

Rule: locked access goes wherever CH3 points

With ch3_active=1, a CPU read or write of FF30–FF3F ignores the CPU's address entirely — the mux is on the counter side. Reads return CH3's current byte when the CPU's strobe overlaps the wave_data_latch window, and an open bus value otherwise. Writes land unconditionally at CH3's current byte. The SRAM accepts a write whenever the CPU write strobe is high, with no gating by wave_data_latch.

Measured: the nine-subtest locked-write sweep

A hardware-verified nine-subtest sweep pins the write path (samesuite channel_3_wave_ram_locked_write, dmg-sim measurement). Each NOP of delay slides the landing byte by exactly one position. All nine landing addresses match the test's expected pattern. The same sweep pins a gating subtlety. Between subtests the DAC is off, ch3_fdis freezes the divider, and the strobe chain is idle. The retrigger corruption (below) therefore does not fire.

Retrigger corruption: a 4-byte row copy

Triggering CH3 while it is reading corrupts wave RAM. The netlist's SRAM produces a 4-byte row copy on DMG, not the single byte the widely-quoted rule describes.

When ch3_restart↑ lands in the window where the SRAM bit-line precharge is off — (AZUS | AZET) = 1, spanning from wave_data_latch↑ to one T-cycle past its fall (≈ 2 T-cycles per overflow) — then with row = wave_position >> 3: ram[0..3] ← ram[row*4 .. row*4+3], other bytes unchanged. Row 0 is naturally a no-op (source = destination — no special case).

The mechanism, in the SRAM cells: word lines are sticky until the word-line driver precharges. The trigger's address snap to 0 therefore leaves the old row's word line high while row 0's rises. The bit lines still hold the old row's data, because precharge is off. The write-back loop then commits that data to every enabled row, so the whole source row lands in row 0. Verified across 12 retrigger events spanning rows 1–3 and both nibble phases (dmg-sim measurements with the full SRAM model).

For implementors

Evaluate (AZUS | AZET) at the ch3_restart capture edge; if set, do the row copy. And gate the divider on ch3_fdis, or the window will be open when hardware has it closed.

Post-boot state

The boot ROM never touches CH3: divider, wave-position counter, overflow chain, and ch3_restart all at their reset defaults; ch3_fdis=1; DAC off. Wave RAM itself has no reset path — it survives NR52 power cycles, and its cold-boot content is undefined on silicon. See post-boot state.