Single Delay — HLE reconstruction and DSP bytecode

DSPHLE selector 0x02 · program image prog09_single_delay · Intervention-pinned (a panel control was driven and the moving C-RAM cell identified).

feedback delay; feedback=C-RAM 0x00/0x09, length=descriptor 0x26 (350 ms); temporal A/B 350.6 ms.

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/prog09_single_delay.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 -- SINGLE DELAY
; image rep algo 9  |  slots 9  |  unit 0 (I-RAM load 84)
; family delay  |  confidence high  |  48 words, 18 class-A multiplies (18 named)
; role: single delay: 0.5 mix, 0.15/0.3 feedback
; 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    00002021CD   ld      (p),(p)+2
  w2    000020040E   ld      acc,(p)+0
  w3    0212A011D5   mac     (p),c+,(p)+1 ; mem[p]<-acc, acc=0
        ; C-RAM[0x00] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x00] = filter section cell 0 (role filter, INFERRED)
  w4    0202A481D5   mac     (p),c+,(p)+72
        ; C-RAM[0x01] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x01] = filter section cell 1 (role filter, INFERRED)
  w5    08801602D9   dly.w  dsc[k],p+96
  w6    0202AB8655   mac     ta,c+,(p)-72
        ; C-RAM[0x02] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x02] = filter section cell 2 (role filter, INFERRED)
        ; 202.A.**.655 = op0x73 filter-section coefficient (INFERRED, 19/20 corpus)
  w7    0000248000   nop
  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   0000A001D5   ld      (p),c+,(p)+0
        ; C-RAM[0x03] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x03] = damping filter tap 0 (role damping, PROVEN)
  w11   0212A00415   mac     acc,c+,(p)+0 ; mem[p]<-acc, acc=0
        ; C-RAM[0x04] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x04] = damping filter tap 1 (role damping, PROVEN)
  w12   0202A001D5   mac     (p),c+,(p)+0
        ; C-RAM[0x05] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x05] = damping filter tap 2 (role damping, PROVEN)
  w13   0202200407   mac.st  acc,(p)+0
  w14   0000201000   nop
  w15   0000A001D5   ld      (p),c+,(p)+0
        ; C-RAM[0x06] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x06] = damping filter tap 0 (role damping, PROVEN)
  w16   0212A00415   mac     acc,c+,(p)+0 ; mem[p]<-acc, acc=0
        ; C-RAM[0x07] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x07] = damping filter tap 1 (role damping, PROVEN)
  w17   0202A001D5   mac     (p),c+,(p)+0
        ; C-RAM[0x08] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x08] = damping filter tap 2 (role damping, PROVEN)
  w18   0202200407   mac.st  acc,(p)+0
  w19   00002B6000   nop
  w20   00002FE407   ld.st   acc,(p)-2
  w21   00002091CD   ld      (p),(p)+9
  w22   00002F740E   ld      acc,(p)-9
  w23   021220A1CD   mac     (p),(p)+10 ; mem[p]<-acc, acc=0
  w24   00002FF1CE   ld      (p),(p)-1
  w25   02122FA407   mac.st  acc,(p)-6 ; mem[p]<-acc, acc=0
  w26   0000AFF1D5   ld      (p),c+,(p)-1
        ; C-RAM[0x09] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x09] = filter section cell 0 (role filter, INFERRED)
  w27   0202A4B1D5   mac     (p),c+,(p)+75
        ; C-RAM[0x0A] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x0A] = filter section cell 1 (role filter, INFERRED)
  w28   08801602D9   dly.w  dsc[k],p+96
  w29   0202AB5655   mac     ta,c+,(p)-75
        ; C-RAM[0x0B] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x0B] = filter section cell 2 (role filter, INFERRED)
        ; 202.A.**.655 = op0x73 filter-section coefficient (INFERRED, 19/20 corpus)
  w30   000024B000   nop
  w31   0212200419   mac.ta2 acc,(p)+0 ; mem[p]<-acc, acc=0
  w32   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]
  w33   0000A001D5   ld      (p),c+,(p)+0
        ; C-RAM[0x0C] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x0C] = damping filter tap 0 (role damping, PROVEN)
  w34   0212A00415   mac     acc,c+,(p)+0 ; mem[p]<-acc, acc=0
        ; C-RAM[0x0D] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x0D] = damping filter tap 1 (role damping, PROVEN)
  w35   0202A001D5   mac     (p),c+,(p)+0
        ; C-RAM[0x0E] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x0E] = damping filter tap 2 (role damping, PROVEN)
  w36   0202200407   mac.st  acc,(p)+0
  w37   0000201000   nop
  w38   0000A001D5   ld      (p),c+,(p)+0
        ; C-RAM[0x0F] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x0F] = damping filter tap 0 (role damping, PROVEN)
  w39   0212A00415   mac     acc,c+,(p)+0 ; mem[p]<-acc, acc=0
        ; C-RAM[0x10] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x10] = damping filter tap 1 (role damping, PROVEN)
  w40   0202A001D5   mac     (p),c+,(p)+0
        ; C-RAM[0x11] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x11] = damping filter tap 2 (role damping, PROVEN)
  w41   0202200407   mac.st  acc,(p)+0
  w42   00002B5000   nop
  w43   00002FD407   ld.st   acc,(p)-3
  w44   00002FB1CD   ld      (p),(p)-5
  w45   000020040E   ld      acc,(p)+0
  w46   0880160000   dly.w  dsc[k],p+96
  w47   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 dly_hle = (dsphle == 2);
	int    dly_N   = 0;
	double dly_gl  = 0.0, dly_gr = 0.0;
	const double dly_mix = 0.5;
	if (dly_hle)
	{
		auto q22d = [](u32 v) -> double {
			int32_t s = (v & 0x800000) ? int32_t(v) - 0x1000000 : int32_t(v);
			return double(s) / 4194304.0; };
		const bool bit1 = (dspcfg & 2) != 0;
		const u16 draw = m_dsp1->dsc_read(0x26);
		// The descriptor counts samples at the DSP's 44.1 kHz; the delay line runs at the
		// tonegen STREAM_RATE (48 kHz), so convert or the echo lands ~8% early. With DSPCFG
		// bit 1 ON the bank holds the true 44.1k count (R3 x2 applied at write); with it OFF
		// it holds half. Use bit 1 ON (DSPCFG=3) for a correct delay time.
		const double n441 = bit1 ? double(draw) : 2.0 * double(draw);
		dly_N = int(n441 * double(STREAM_RATE) / 44100.0 + 0.5); // 44.1k samples -> stream samples
		dly_N = std::clamp(dly_N, 1, 34999);                     // fit the delay line
		const double fscale = bit1 ? 0.5 : 1.0;                  // cell -> operand (actual gain)
		dly_gl = std::clamp(q22d(m_dsp1->cram_read(0x00)) * fscale, -0.97, 0.97);
		dly_gr = std::clamp(q22d(m_dsp1->cram_read(0x09)) * fscale, -0.97, 0.97);
		// ★ in-loop HIGH DAMP (prog09 PROVES it: C-RAM 0x03..0x05 = damping filter, role PROVEN).
		// Read cell 0x03 (operand scale) as a one-pole low-pass coefficient so the fed-back tap
		// darkens each repeat, as the bytecode's damping section does. |coeff|->[0,0.7] damping.
		const double damp = std::clamp(std::fabs(q22d(m_dsp1->cram_read(0x03))) * fscale, 0.0, 0.7);
		m_dly_dmp_l.set_damping(damp); m_dly_dmp_r.set_damping(damp);
	}

Per-sample insert (the reconstructed signal path):

		if (dly_hle)
		{
			const double sl = double(mix_l) / 32768.0, sr = double(mix_r) / 32768.0;
			const double tl = m_dly_l.read(double(dly_N)), tr = m_dly_r.read(double(dly_N));
			// in-loop HIGH DAMP (prog09 cells 0x03..0x05): low-pass the fed-back tap so repeats darken
			m_dly_l.write(sl + dly_gl * m_dly_dmp_l.process_one(tl));
			m_dly_r.write(sr + dly_gr * m_dly_dmp_r.process_one(tr));
			mix_l = int32_t(((1.0 - dly_mix) * sl + dly_mix * tl) * 32768.0);
			mix_r = int32_t(((1.0 - dly_mix) * sr + dly_mix * tr) * 32768.0);
		}