Ring Modulator — HLE reconstruction and DSP bytecode

DSPHLE selector 0x14 · program image prog54_ring_modulator · Preview (topology decoded and reconstructed; most panel→cell role mappings are position-decoded).

carrier oscillator x signal; carrier rate=cell 0x00 MEASURED (~1 kHz).

This page pairs the bytecode the effect runs on the NEC µPD6383GF with the high-level reconstruction that makes it audible in MAME. The reconstruction is not the chip’s microcode — it is a textbook DSP block built from the decoded meaning of that microcode, and reading the two together is how the bytecode is understood (and, in time, driven toward a faithful low-level emulation). See also the signal-flow flowchart.

The bytecode below is the source of truth; the HLE reconstruction is not. The HLE is our best current interpretation and may contain mistakes — where the two disagree, the bytecode wins, and a better HLE should be updated here. This page is the permanent archive of the reference HLE: it is kept here even after the code is eventually removed from the MAME sources.

DSP bytecode (reverse-engineered microprogram)

The disassembled image the chip executes for this effect. Source (regenerable): dsp/disasm/prog54_ring_modulator.dsm.

; Reverse-engineered disassembly of the KN5000 effects-DSP microcode
; (NEC uPD6383GF), recovered from the original firmware.  The microcode is the
; work of its original authors; this is a disassembly for preservation and
; interoperability, and no claim of copyright is made over the disassembled program.
; KN5000 effects-DSP program -- RING MODULATOR
; image rep algo 54  |  slots 54  |  unit 0 (I-RAM load 84)
; family am  |  confidence high  |  46 words, 6 class-A multiplies (0 named)
; role: ring modulator: audio-rate quadrature AM
; coefficient cursor base 0x00
;
; GENERATED by dsp/tools/gen_dsp_disasm.py -- DO NOT EDIT.
; Put labels/comments in the matching dsp/sym/*.sym; analysis in dsp/algorithms/.

  w0    00400008BC   ?word   0x00400008BC   ; 040.0.00.8BC  hi12{?6 res=040}  [SPECULATIVE: lo12 bit-11 modifier word + pointer-mode (bit11-family); the base selector/register is OPEN]
  w1    000020B1CD   ld      (p),(p)+11
  w2    000020040E   ld      acc,(p)+0
  w3    0212200000   mac.b   (p)0,(p)+0 ; mem[p]<-acc, acc=0
  w4    002A200000   ?word   0x002A200000   ; 02A.2.00.000  hi12{f31=5 ?5 res=020}  [SPECULATIVE (prospective, not measured): SRC 0x00 = mem[ptr]/delay-RAM read]
  w5    00002FE1CD   ld      (p),(p)-2
  w6    000020040E   ld      acc,(p)+0
  w7    0212200000   mac.b   (p)0,(p)+0 ; mem[p]<-acc, acc=0
  w8    0028200000   ?word   0x0028200000   ; 028.2.00.000  hi12{f31=4 ?5 res=020}  [SPECULATIVE (prospective, not measured): SRC 0x00 = mem[ptr]/delay-RAM read]
  w9    0880130407   dly.r  dsc[k],p+48
  w10   0092A04200   mac.b   c,c+,(p)+4 ; store SUPPRESSED (bit7)
        ; C-RAM[0x00] (coeff, base 0x00 MEASURED)
  w11   00822001C0   mac.b   (p),(p)+0
  w12   0094A00200   wrap    acc,c+          ; acc <- datum(acc) & coef  (LFO modulus)
        ; C-RAM[0x01] (coeff, base 0x00 MEASURED)
  w13   0000200447   ?word   0x0000200447   ; 000.2.00.447  hi12{-}  [SPECULATIVE (prospective, not measured): SRC 0x11 = ACCB (2nd accumulator)]
  w14   0092200700   ?word   0x0092200700   ; 092.2.00.700  hi12{ST f31=1 ?7 res=080}  [SPECULATIVE (prospective, not measured): SRC 0x1C = control/mod source into MAC (100% MAC-consumed; LFO in mod fx, envelope/AGC in dynamics) -- NOT LFO-only: present in 19 non-LFO programs (dsp_datapath_fingerprint)]
  w15   0000A001D5   ld      (p),c+,(p)+0
        ; C-RAM[0x02] (coeff, base 0x00 MEASURED)
  w16   0182200000   mac.b   (p)0,(p)+0
  w17   0040000C63   ?word   0x0040000C63   ; 040.0.00.C63  hi12{?6 res=040}  [SPECULATIVE (prospective, not measured): SRC 0x11 = ACCB (2nd accumulator)]
  w18   00006184CD   ?word   0x00006184CD   ; 000.6.18.4CD  hi12{-}  [table-lookup idiom, class-6 addr8 = table selector (INFERRED)]
  w19   00124011CE   ?word   0x00124011CE   ; 012.4.01.1CE  hi12{ST f31=1}  [table-lookup idiom, third word (INFERRED)]
  w20   01042FE1CE   post    (p),(p)-2
  w21   0102200000   mac.b   (p)0,(p)+0
  w22   0000203407   ld.st   acc,(p)+3
  w23   0092A00200   mac.b   c,c+,(p)+0 ; store SUPPRESSED (bit7)
        ; C-RAM[0x03] (coeff, base 0x00 MEASURED)
  w24   00822001C0   mac.b   (p),(p)+0
  w25   0094A00200   wrap    acc,c+          ; acc <- datum(acc) & coef  (LFO modulus)
        ; C-RAM[0x04] (coeff, base 0x00 MEASURED)
  w26   0000200447   ?word   0x0000200447   ; 000.2.00.447  hi12{-}  [SPECULATIVE (prospective, not measured): SRC 0x11 = ACCB (2nd accumulator)]
  w27   0092200700   ?word   0x0092200700   ; 092.2.00.700  hi12{ST f31=1 ?7 res=080}  [SPECULATIVE (prospective, not measured): SRC 0x1C = control/mod source into MAC (100% MAC-consumed; LFO in mod fx, envelope/AGC in dynamics) -- NOT LFO-only: present in 19 non-LFO programs (dsp_datapath_fingerprint)]
  w28   0000A001D5   ld      (p),c+,(p)+0
        ; C-RAM[0x05] (coeff, base 0x00 MEASURED)
  w29   0182200000   mac.b   (p)0,(p)+0
  w30   0040000C63   ?word   0x0040000C63   ; 040.0.00.C63  hi12{?6 res=040}  [SPECULATIVE (prospective, not measured): SRC 0x11 = ACCB (2nd accumulator)]
  w31   00006184CD   ?word   0x00006184CD   ; 000.6.18.4CD  hi12{-}  [table-lookup idiom, class-6 addr8 = table selector (INFERRED)]
  w32   00124011CE   ?word   0x00124011CE   ; 012.4.01.1CE  hi12{ST f31=1}  [table-lookup idiom, third word (INFERRED)]
  w33   01042FE1CE   post    (p),(p)-2
  w34   0102200000   mac.b   (p)0,(p)+0
  w35   0880130407   dly.r  dsc[k],p+48
  w36   00002F4000   nop
  w37   000020A1CD   ld      (p),(p)+10
  w38   00002021CE   ld      (p),(p)+2
  w39   0212200000   mac.b   (p)0,(p)+0 ; mem[p]<-acc, acc=0
  w40   002A200000   ?word   0x002A200000   ; 02A.2.00.000  hi12{f31=5 ?5 res=020}  [SPECULATIVE (prospective, not measured): SRC 0x00 = mem[ptr]/delay-RAM read]
  w41   00002EF1CD   ld      (p),(p)-17
  w42   000020040E   ld      acc,(p)+0
  w43   0212200000   mac.b   (p)0,(p)+0 ; mem[p]<-acc, acc=0
  w44   0880160000   dly.w  dsc[k],p+96
  w45   042810E000   ?word   0x042810E000   ; 428.1.0E.000  hi12{END f31=4 ?5 res=020}  [END OF BLOCK, unit 0 -- CALL/RETURN -- and still performs the rest of the word]

HLE reconstruction (MAME, kn5000_tonegen.cpp)

The decode of the bytecode above, rebuilt as audible DSP. Two parts: the parameter refresh (reads the decoded C-RAM coefficient cells once per update) and the per-sample insert (the signal processing). Default OFF, behind the DSPHLE research port. Source: src/mame/matsushita/kn5000_tonegen.cpp.

Parameter refresh (decode the C-RAM coefficients):

	const bool ring_hle = (dsphle == 0x14);
	double ring_inc = 0.0;
	if (ring_hle) { ring_inc = std::clamp(rate_hz(0x00), 20.0, 4000.0) / double(STREAM_RATE); }

Per-sample insert (the reconstructed signal path):

		if (ring_hle)  // RING MODULATOR: multiply by a carrier oscillator (~1 kHz) -> sidebands
		{
			const double xl = double(mix_l) / 32768.0, xr = double(mix_r) / 32768.0;
			const double c = std::sin(TWO_PI * m_ring_phase);
			m_ring_phase += ring_inc; if (m_ring_phase >= 1.0) m_ring_phase -= 1.0;
			mix_l = int32_t(std::clamp(xl * c, -1.0, 1.0) * 32767.0);
			mix_r = int32_t(std::clamp(xr * c, -1.0, 1.0) * 32767.0);
		}