APU suite - status (issue #398)
Snapshot of HDL/soc/icarus/apu. The suite drives the real APU netlist
(apu_merged.v, HDL/soc/apu.v with the a/d bus aliases merged) inside the
SoC context: real ClkGen + MMIO (mmio_weakbus) + Arbiter + Ser + behavioral
WaveRAM, with a CPU bus master (same conventions as ../soc, issue #396).
Status per test
tb_apu_regs - PASS (35 checks)
Register file map of the real APU (measured; wiki/soc/apu.md):
| Addr | Register | read-back semantics (measured) |
|---|---|---|
| $FF10 | NR10 | 0x80 \| (v & 0x7F) (sweep period echo, bit7 hard-1) |
| $FF11 | NR11 | 0x3F \| (v & 0xC0) (duty echo, length bits hard-1) |
| $FF12 | NR12 | v (8-bit envelope echo) |
| $FF13 | NR13 | no read-back (0xFF) - write-only (feeds the divider) |
| $FF14 | NR14 | 0xBF \| (v & 0x40) (bit6 length-enable echo) |
| $FF16-$FF19 | NR21-NR24 | same pattern as NR11-NR14 (ch2) |
| $FF1A | NR30 | 0x7F \| (v & 0x80) (DAC-enable echo, rest hard-1) |
| $FF1C | NR32 | 0x9F \| (v & 0x60) (volume echo, rest hard-1) |
| $FF1E | NR34 | 0xBF \| (v & 0x40) |
| $FF20-$FF23 | NR41-NR44 | NR41 no read-back; NR42/NR43 echo; NR44 0xBF\|(v&0x40) |
| $FF24 | NR50 | v (8-bit echo) |
| $FF25 | NR51 | v (8-bit echo) |
| $FF26 | NR52 | 0x70 \| (power<<7) \| (ch-active bits 3:0); 0xF0 after |
| power-on, 0x70 powered off. Power-off write resets the channel registers. | ||
| $FF27-$FF2F | - | unmapped (0xFF, no read driver) |
| $FF30-$FF3F | WaveRAM | CPU write/read window decoded by the APU itself |
(n_wave_wr/n_wave_rd + wave_a = a[3:0] while not playing; data on d |
||
re-driven from wave_rd by the notif1 stage g1255..g1262) |
Write decode map (verified): w695 = $FF30-$FF3F window; n_wave_wr =
~(soc_wr & w695); n_wave_rd = ~(ch3_active ?
tb_apu_ch1 + check_ch1.py - PASS
Channel 1 verified end-to-end on the real netlist:
-
output period =
(2048 - X) * 32oscillator cycles (measured exact at X=0x780 and X=0x700) - the DMG131072/(2048-X)relation (freq timer ticks at clk9 = osc/4, 8 duty steps per output period) -
duty fractions 12.5 / 25 / 50 / 75 % measured exact
- the high level equals the NR12 volume (4-bit)
- NR52 status bit0 set while running, cleared by power-off
tb_apu_ch2 + check_ch2.py - PASS
The same check set for channel 2 (no sweep). Only possible after the w548 bus-model fix (see below) - previously the divider preset captured x from write-bus contention and the channel never played.
tb_apu_ch3 + check_ch3.py - PASS
Channel 3 (wave) with a known wave-RAM image:
-
the wave-RAM address advances one step per
(2048 - X) * 2oscillator cycles (measured step counts at X=0x7F0 vs X=0x780 = 8x) - i.e. the DMG sample-rate relation 2^21/(2048-X) -
output amplitude = wave sample scaled by the NR32 volume code (01=100%, 10=50%, 11=25%, 00=mute), steady per sample (no duty)
-
CPU access to the wave-RAM window works (tb_apu_regs)
tb_apu_ch4 + check_ch4.py - PASS
Channel 4 (noise): with NR42 vol F the output toggles at level F while running; the toggle rate drops as the NR43 divisor r grows (measured: 15-bit r0/r7 ~ 50x over 500us windows; the exact divider ratio vs the pan-docs formula is an open cross-check - the LFSR output-run statistics depend on the polynomial, so rate ratios are measured, not assumed).
Open research items (author checklist)
-
ch2/ch3/ch4 output stage - SOLVED (bus-model issue). Root cause: the w548 decode (freq-hi/trigger access window of ch2) also enables five notif0 divider-state drivers (g869/g937/g939-941) which put the ch2 divider bits onto d. In the static gate-level model these drivers are open during CPU writes (soc_wr) and fight the write data with x; the level-sensitive divider preset latches then capture x and the whole ch2 divider goes x (divider never ticks -> ch2_out stuck 0). ch1 was not affected (its freq-lo read-back group w310 is soc_rd-gated). Fix:
apu_wc.v(generated bymake_wc.py) gates those five drivers read-only (w548_r = w548 | soc_wr). With it, ch2 PASSes the same duty/period checks as ch1 (tb_apu_ch2 + check_ch2.py), ch4's LFSR output runs, and ch3 plays (wave_a advances, samples appear). Remaining: formal ch3/ch4 regression tests. -
noise divider ratio cross-check (tb_apu_ch4 rates vs the DMG shift-clock formula; measure the shift clock directly off the LFSR clock net).
-
frame-sequencer / envelope cadence (measured, tb_env3-style probes): with the synthetic lfo the CH1 envelope decays one volume step every
8 * ratelfo pulses (rate = NR12 bits 2:0; rate 0 = no envelope change), i.e. at real 512 Hz lfo the volume step period israte / 64 s(rate 1 -> 1/64 s = 64 Hz). The earlier "glitch dips" on ch1_out at the 8-lfo cadence were an x-bus artifact fixed by apu_wc.v. (Note: the observed law differs from the (rate+1)/64 s in some literature - measured on this netlist it is rate/64 s.) -
length counter law (measured): with length-enable set, the channel stops after
(64 - L)ticks where L = NR11[5:0] and one tick = 2 lfo pulses = 1/256 s at the real lfo (i.e. trigger loads the counter with 0x40-L - the DMG trigger-reload quirk; L=0x3F stops almost immediately, L=0 sounds ~250 ms). Measured: stop lfo ~= 2*(64-L) (plus detection lag) for L = 1/8/32/63. -
sweep (NR10): behavioral checks pass (add-overflow disables the channel immediately; subtract keeps it running with the period changing), but the step cadence and the add/sub amount semantics still need a dedicated measurement (parked - lower priority for the suite).
-
joypad ($FF00) & serial pad pieces in the APU (n_p10..n_p13, DRV_LOW_p1x, n_sout_topad etc.): decode map started (w570 = FF00 write window capturing d0..d7 into the p10-p15/serial pad latches) - tests pending.
-
TEST1-mode a[7:0] arbitration piece of the APU (n_INPUT_a -> a via bufif0 g1081..g1099, addr_latch + dma mux g1066..g1073, DRV_LOW/n_DRV_ HIGH_a[7:0] pad drivers) - tests pending.
-
wave RAM CPU read: works; exact capture edge (posedge n_wave_wr) confirmed; per-byte 4-bit sample ordering for playback to measure in the ch3 test.
Tooling notes
-
compile (WSL-native iverilog):
iverilog -D ICARUS -o <t>.run ../../dmglib.v ../../clkgen.v ../soc/mmio_weakbus.v ../soc/arb_merged.v ../soc/ser_sharedq.v apu_merged.v wave_ram_model.v apu_env.v <t>.v -
apu_merged.vregenerated withpython3 ../soc/merge_bus_aliases.py ../../apu.v apu_merged.v --buses a d -
synthetic LFO:
apu_envhaslfo_override/lfo_ext- the frame sequencer can be clocked fast so envelope/length/sweep measurements do not take half a second of sim time at the real 512 Hz. -
netlist decode/cone questions:
python3 apu_trace.py apu_merged.v <net>or--readmap(lists every d-bus read driver group and its decode). -
check_ch1.py / check_ch2.py analyse the segment VCDs (waveform metrics: period formula, duty %, volume plateau).