Gated Reverb — HLE reconstruction and DSP bytecode

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

reverb tank (MEASURED line lengths) + envelope gate chopping the tail; GATE TIME=0x15.

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/prog08_gated_reverb.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 -- GATED REVERB
; image rep algo 8  |  slots 8  |  unit 0 (I-RAM load 84)
; family reverb  |  confidence medium  |  102 words, 22 class-A multiplies (7 named)
; role: gated reverb: all-pass ring + hold gate
; 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    088013000B   ?word   0x088013000B   ; 880.1.30.00B  hi12{ESC ?7 res=080}  [external delay-DRAM READ (FORCED, adjudication-round5 sect. 3 -- addr8 bit 6 is the direction field and 0x60 is the WRITE; this REVERSES R1 F1, which bounded the read latency to one repetition when the descriptors need twenty words); this end moves with the user's DELAY (ms) knob, and the delay is READ_CELL - WRITE_CELL; addr8 0x30 also marks the FIRST DRAM access of a body, 37 of 38 distinct images (R3 sect. 6.2). external delay-DRAM access; address = DESCRIPTOR_CELL[k] + G, from the host bank behind pointer ...825 / tag 0x4C (R3, PROVEN BY CONSTRUCTION) -- the k-th class-1 escape word of a body takes the k-th cell of that body's own descriptor block (the IDENTITY map, FORCED in adjudication-round5 sect. 1), so the address is NOT in this word]
  w1    0000A0A415   ld      acc,c+,(p)+10
        ; C-RAM[0x00] (coeff, base 0x00 MEASURED)
  w2    0212AF61D5   mac     (p),c+,(p)-10 ; mem[p]<-acc, acc=0
        ; C-RAM[0x01] (coeff, base 0x00 MEASURED)
  w3    020220C1CD   mac     (p),(p)+12
  w4    000020040E   ld      acc,(p)+0
  w5    08801602D9   dly.w  dsc[k],p+96
  w6    0212A00655   mac     ta,c+,(p)+0 ; mem[p]<-acc, acc=0
        ; C-RAM[0x02] (coeff, base 0x00 MEASURED)
  w7    00002F9407   ld.st   acc,(p)-7
  w8    0212200419   mac.ta2 acc,(p)+0 ; mem[p]<-acc, acc=0
  w9    088012064B   ?word   0x088012064B   ; 880.1.20.64B  hi12{ESC ?7 res=080}  [external delay-DRAM READ (FORCED, adjudication-round5 sect. 3 -- addr8 bit 6 is the direction field and 0x60 is the WRITE; this REVERSES R1 F1, which bounded the read latency to one repetition when the descriptors need twenty words); this end moves with the user's DELAY (ms) knob, and the delay is READ_CELL - WRITE_CELL. external delay-DRAM access; address = DESCRIPTOR_CELL[k] + G, from the host bank behind pointer ...825 / tag 0x4C (R3, PROVEN BY CONSTRUCTION) -- the k-th class-1 escape word of a body takes the k-th cell of that body's own descriptor block (the IDENTITY map, FORCED in adjudication-round5 sect. 1), so the address is NOT in this word]
  w10   0000200000   nop
  w11   0000A461D5   ld      (p),c+,(p)+70
        ; C-RAM[0x03] (coeff, base 0x00 MEASURED)
  w12   0202200000   mac.b   (p)0,(p)+0
  w13   08801602D4   dly.w  dsc[k],p+96
  w14   0104200000   post.b  (p)0,(p)+0
  w15   0000200419   ld.ta2  acc,(p)+0
  w16   0012200680   mac.b   tb,(p)+0 ; mem[p]<-acc, acc=0
  w17   0880120655   dly.r  dsc[k],p+32
  w18   0102A0064B   mac     ta,c+,(p)+0
        ; C-RAM[0x04] (coeff, base 0x00 MEASURED)
  w19   0000200000   nop
  w20   0000200000   nop
  w21   08801602D4   dly.w  dsc[k],p+96
  w22   0104200000   post.b  (p)0,(p)+0
  w23   0000200419   ld.ta2  acc,(p)+0
  w24   0012200680   mac.b   tb,(p)+0 ; mem[p]<-acc, acc=0
  w25   0880120655   dly.r  dsc[k],p+32
  w26   0102A0064B   mac     ta,c+,(p)+0
        ; C-RAM[0x05] (coeff, base 0x00 MEASURED)
  w27   0000200000   nop
  w28   0000200000   nop
  w29   08801602D4   dly.w  dsc[k],p+96
  w30   0104200000   post.b  (p)0,(p)+0
  w31   0000200419   ld.ta2  acc,(p)+0
  w32   0012200680   mac.b   tb,(p)+0 ; mem[p]<-acc, acc=0
  w33   0880120655   dly.r  dsc[k],p+32
  w34   0102A0064B   mac     ta,c+,(p)+0
        ; C-RAM[0x06] (coeff, base 0x00 MEASURED)
  w35   0000A001D5   ld      (p),c+,(p)+0
        ; C-RAM[0x07] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x07] = damping filter tap 0 (role damping, PROVEN)
  w36   0212A00415   mac     acc,c+,(p)+0 ; mem[p]<-acc, acc=0
        ; C-RAM[0x08] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x08] = damping filter tap 1 (role damping, PROVEN)
  w37   0202A001D5   mac     (p),c+,(p)+0
        ; C-RAM[0x09] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x09] = damping filter tap 2 (role damping, PROVEN)
  w38   0202200407   mac.st  acc,(p)+0
  w39   08801602D4   dly.w  dsc[k],p+96
  w40   0104200000   post.b  (p)0,(p)+0
  w41   0000200419   ld.ta2  acc,(p)+0
  w42   0012200680   mac.b   tb,(p)+0 ; mem[p]<-acc, acc=0
  w43   0880120655   dly.r  dsc[k],p+32
  w44   0102AC464B   mac     ta,c+,(p)-60
        ; C-RAM[0x0A] (coeff, base 0x00 MEASURED)
  w45   0000200000   nop
  w46   0000200000   nop
  w47   08801602D4   dly.w  dsc[k],p+96
  w48   0104200000   post.b  (p)0,(p)+0
  w49   0000200419   ld.ta2  acc,(p)+0
  w50   0012200680   mac.b   tb,(p)+0 ; mem[p]<-acc, acc=0
  w51   0880120655   dly.r  dsc[k],p+32
  w52   0102A0064B   mac     ta,c+,(p)+0
        ; C-RAM[0x0B] (coeff, base 0x00 MEASURED)
  w53   0000200000   nop
  w54   0000200000   nop
  w55   08801602D4   dly.w  dsc[k],p+96
  w56   0104200000   post.b  (p)0,(p)+0
  w57   0000200419   ld.ta2  acc,(p)+0
  w58   0012200680   mac.b   tb,(p)+0 ; mem[p]<-acc, acc=0
  w59   0880120655   dly.r  dsc[k],p+32
  w60   0102A0064B   mac     ta,c+,(p)+0
        ; C-RAM[0x0C] (coeff, base 0x00 MEASURED)
  w61   0000200000   nop
  w62   0000200000   nop
  w63   08801602D4   dly.w  dsc[k],p+96
  w64   0104200407   post.st acc,(p)+0
  w65   0000200419   ld.ta2  acc,(p)+0
  w66   0012200680   mac.b   tb,(p)+0 ; mem[p]<-acc, acc=0
  w67   0880120655   dly.r  dsc[k],p+32
  w68   0102AF764B   mac     ta,c+,(p)-9
        ; C-RAM[0x0D] (coeff, base 0x00 MEASURED)
  w69   0000246407   ld.st   acc,(p)+70
  w70   0000A001D5   ld      (p),c+,(p)+0
        ; C-RAM[0x0E] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x0E] = damping filter tap 0 (role damping, PROVEN)
  w71   0212A00415   mac     acc,c+,(p)+0 ; mem[p]<-acc, acc=0
        ; C-RAM[0x0F] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x0F] = damping filter tap 1 (role damping, PROVEN)
  w72   0202A001D5   mac     (p),c+,(p)+0
        ; C-RAM[0x10] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x10] = damping filter tap 2 (role damping, PROVEN)
  w73   02022C0407   mac.st  acc,(p)-64
  w74   088016041A   ?word   0x088016041A   ; 880.1.60.41A  hi12{ESC ?7 res=080}  [external delay-DRAM WRITE (FORCED, adjudication-round5 sect. 3 -- addr8 bit 6 is the direction field and 0x60 is the WRITE; this REVERSES R1 F1, which bounded the read latency to one repetition when the descriptors need twenty words); the line BASE -- MULTI TAP DELAY's four taps share exactly one of these, which is what forces the polarity. external delay-DRAM access; address = DESCRIPTOR_CELL[k] + G, from the host bank behind pointer ...825 / tag 0x4C (R3, PROVEN BY CONSTRUCTION) -- the k-th class-1 escape word of a body takes the k-th cell of that body's own descriptor block (the IDENTITY map, FORCED in adjudication-round5 sect. 1), so the address is NOT in this word]
  w75   00122001C0   mac.b   (p),(p)+0 ; mem[p]<-acc, acc=0
  w76   002E200000   ?word   0x002E200000   ; 02E.2.00.000  hi12{f31=7 ?5 res=020}  [SPECULATIVE (prospective, not measured): SRC 0x00 = mem[ptr]/delay-RAM read]
  w77   00202F9000   ld.b    (p)0,(p)-7
  w78   08801202C7   dly.r  dsc[k],p+32
  w79   000020868B   ?word   0x000020868B   ; 000.2.08.68B  hi12{-}  [SPECULATIVE (prospective, not measured): ACT 0x0B = delay-line access (READ/WRITE class-borne)]
  w80   0018A001D5   ?word   0x0018A001D5   ; 018.A.00.1D5  hi12{ST f31=4} cur+  [SPECULATIVE: class-A multiply (P = coef x source 0x07); the source id / accumulator-combine f31=4 / ACT 0x15 may be OPEN]
        ; C-RAM[0x11] (coeff, base 0x00 MEASURED)
  w81   0104A001D5   post    (p),c+,(p)+0
        ; C-RAM[0x12] (coeff, base 0x00 MEASURED)
  w82   0C402C0000   ldreg   r00,#22          ; immediate -> the register lo12 selects
  w83   0182A00000   mac.b   (p)0,c+,(p)+0
        ; C-RAM[0x13] (coeff, base 0x00 MEASURED)
  w84   00002F7447   ?word   0x00002F7447   ; 000.2.F7.447  hi12{-}  [SPECULATIVE (prospective, not measured): SRC 0x11 = ACCB (2nd accumulator)]
  w85   0012A001D5   mac     (p),c+,(p)+0 ; mem[p]<-acc, acc=0
        ; C-RAM[0x14] (coeff, base 0x00 MEASURED)
  w86   0C403A0359   ldreg   r59,#29          ; immediate -> the register lo12 selects
  w87   00922FF1D5   mac     (p),(p)-1 ; store SUPPRESSED (bit7)
  w88   0184A011D5   post    (p),c+,(p)+1
        ; C-RAM[0x15] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x15] = op0x6F[0] (role coeff, INFERRED)
  w89   01822FF407   mac.st  acc,(p)-1
  w90   00002041CD   ld      (p),(p)+4
  w91   00002FA1CE   ld      (p),(p)-6
  w92   02122021CD   mac     (p),(p)+2 ; mem[p]<-acc, acc=0
  w93   000020040E   ld      acc,(p)+0
  w94   0212207447   ?word   0x0212207447   ; 212.2.07.447  hi12{ST f98=2 f31=1}  [writes mem[ptr] (bit 4); mode 2, so the target IS the pointer]
  w95   00002F9407   ld.st   acc,(p)-7
  w96   000020D1CD   ld      (p),(p)+13
  w97   00002F11CE   ld      (p),(p)-15
  w98   02122FB1CD   mac     (p),(p)-5 ; mem[p]<-acc, acc=0
  w99   000020040E   ld      acc,(p)+0
  w100  0880160000   dly.w  dsc[k],p+96
  w101  061210E000   endblk  #0E             ; END OF BLOCK -- the last word of a block

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 grev_hle = (dsphle == 0x17);
	double grev_fb = 0.0, grev_apg = 0.0; int grev_cd[4] = { 0, 0, 0, 0 }, grev_ad[2] = { 0, 0 };
	double grev_gatethr = 0.0, grev_rel = 0.0;
	if (grev_hle)
	{
		const double cb[4] = { 680.0, 800.0, 1440.0, 1800.0 }, ab[2] = { 458.0, 100.0 };  // measured @44.1k
		for (int i = 0; i < 4; i++) grev_cd[i] = std::clamp(int(cb[i] * sr48 + 0.5), 1, 2099);
		for (int i = 0; i < 2; i++) grev_ad[i] = std::clamp(int(ab[i] * sr48 + 0.5), 1, 559);
		grev_fb  = 0.78;                                         // fixed short-tail feedback (gate cuts it)
		grev_apg = 0.5;
		const double damp = std::clamp(std::fabs(q22x(m_dsp1->cram_read(0x07)) * x_cs) * 0.5, 0.0, 0.7);
		for (int i = 0; i < 4; i++) { m_grev_d_l[i].set_damping(damp); m_grev_d_r[i].set_damping(damp); }
		grev_gatethr = 0.02;                                    // input level that holds the gate open
		// GATE TIME cell 0x15 -> gate release time (how fast the tail is chopped)
		const double gt = std::clamp(std::fabs(q22x(m_dsp1->cram_read(0x15)) * x_cs), 0.0, 1.0);
		grev_rel = std::exp(-1.0 / (double(STREAM_RATE) * (0.02 + 0.15 * gt)));        // 20..170 ms
	}

Per-sample insert (the reconstructed signal path):

		if (grev_hle)  // GATED REVERB: reverb tank (measured lengths) + envelope gate chopping the tail
		{
			const double xl = double(mix_l) / 32768.0, xr = double(mix_r) / 32768.0;
			const double ein = std::max(std::fabs(xl), std::fabs(xr));
			m_grev_env = std::max(ein, m_grev_env * 0.999);
			const double target = (m_grev_env > grev_gatethr) ? 1.0 : 0.0;
			if (target >= m_grev_gain) m_grev_gain = target;                      // open instantly
			else m_grev_gain = target + grev_rel * (m_grev_gain - target);        // GATE TIME release
			double wl = 0.0, wr = 0.0;
			for (int c = 0; c < 4; c++)
			{
				const double tl = m_grev_c_l[c].read(double(grev_cd[c])), tr = m_grev_c_r[c].read(double(grev_cd[c]));
				m_grev_c_l[c].write(xl + grev_fb * m_grev_d_l[c].process_one(tl));
				m_grev_c_r[c].write(xr + grev_fb * m_grev_d_r[c].process_one(tr));
				wl += tl; wr += tr;
			}
			for (int a = 0; a < 2; a++)
			{
				const double dl = m_grev_a_l[a].read(double(grev_ad[a])), dr = m_grev_a_r[a].read(double(grev_ad[a]));
				const double yl = -grev_apg * wl + dl, yr = -grev_apg * wr + dr;
				m_grev_a_l[a].write(wl + grev_apg * yl); m_grev_a_r[a].write(wr + grev_apg * yr);
				wl = yl; wr = yr;
			}
			mix_l = int32_t(std::clamp(xl + 0.6 * m_grev_gain * wl, -1.0, 1.0) * 32767.0);
			mix_r = int32_t(std::clamp(xr + 0.6 * m_grev_gain * wr, -1.0, 1.0) * 32767.0);
		}