Chorus — HLE + bytecode
Chorus — HLE reconstruction and DSP bytecode
DSPHLE selector 0x03 · program image prog01_chorus · Intervention-pinned (a panel control was driven and the moving C-RAM cell identified).
quadrature LFO-swept delay; rate=cell 0x00 (~0.6 Hz); A/B 0.62 Hz.
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/prog01_chorus.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 -- CHORUS
; image rep algo 1 | slots 1 | unit 0 (I-RAM load 84)
; family modulation | confidence high | 70 words, 19 class-A multiplies (2 named)
; role: quadrature 2-voice chorus (LFO-swept delay, wet 0.25/0.15)
; 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 08801308BC ?word 0x08801308BC ; 880.1.30.8BC 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 000020E1CD ld (p),(p)+14
w2 00002DE40E ld acc,(p)-34
w3 021222200B ?word 0x021222200B ; 212.2.22.00B hi12{ST f98=2 f31=1} [writes mem[ptr] (bit 4); mode 2, so the target IS the pointer]
w4 00002F4407 ld.st acc,(p)-12
w5 0092A00200 mac.b c,c+,(p)+0 ; store SUPPRESSED (bit7)
; C-RAM[0x00] (coeff, base 0x00 MEASURED)
w6 00822001C0 mac.b (p),(p)+0
w7 0094A00200 wrap acc,c+ ; acc <- datum(acc) & coef (LFO modulus)
; C-RAM[0x01] (coeff, base 0x00 MEASURED)
w8 0000209447 ?word 0x0000209447 ; 000.2.09.447 hi12{-} [SPECULATIVE (prospective, not measured): SRC 0x11 = ACCB (2nd accumulator)]
w9 09001601D5 dly.w dsc[k],p+96
w10 0192A40000 mac.b (p)0,c+,(p)+64 ; store SUPPRESSED (bit7)
; C-RAM[0x02] (coeff, base 0x00 MEASURED)
w11 00822001C0 mac.b (p),(p)+0
w12 0C4032044C ldreg r4C,#25 ; immediate -> the register lo12 selects
w13 0A00000041 ?word 0x0A00000041 ; A00.0.00.041 hi12{ESC f98=2} [head of a fixed 3-word template (S-5); operands SRC 0x01/ACT 0x01 dark]
w14 08801202C7 dly.r dsc[k],p+32
w15 0102AC34C8 ?word 0x0102AC34C8 ; 102.A.C3.4C8 hi12{f98=1 f31=1} cur+ [gain multiply (same op in phaser all-pass and reverb diffuser)]
; C-RAM[0x03] (coeff, base 0x00 MEASURED)
w16 00002FF407 ld.st acc,(p)-1
w17 00122FE1C0 mac.b (p),(p)-2 ; mem[p]<-acc, acc=0
w18 09001601D5 dly.w dsc[k],p+96
w19 0192A41000 mac.b (p)0,c+,(p)+65 ; store SUPPRESSED (bit7)
; C-RAM[0x04] (coeff, base 0x00 MEASURED)
w20 00822001C0 mac.b (p),(p)+0
w21 0C4032044C ldreg r4C,#25 ; immediate -> the register lo12 selects
w22 0A00000041 ?word 0x0A00000041 ; A00.0.00.041 hi12{ESC f98=2} [head of a fixed 3-word template (S-5); operands SRC 0x01/ACT 0x01 dark]
w23 08801202C7 dly.r dsc[k],p+32
w24 0102AC14C8 ?word 0x0102AC14C8 ; 102.A.C1.4C8 hi12{f98=1 f31=1} cur+ [gain multiply (same op in phaser all-pass and reverb diffuser)]
; C-RAM[0x05] (coeff, base 0x00 MEASURED)
w25 0000A00415 ld acc,c+,(p)+0
; C-RAM[0x06] (coeff, base 0x00 MEASURED)
w26 0212AF31D5 mac (p),c+,(p)-13 ; mem[p]<-acc, acc=0
; C-RAM[0x07] (coeff, base 0x00 MEASURED)
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 0202A071D5 mac (p),c+,(p)+7
; C-RAM[0x08] (coeff, base 0x00 MEASURED)
w29 0182200407 mac.st acc,(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 01042021CE post (p),(p)+2
w34 0142000C63 ?word 0x0142000C63 ; 142.0.00.C63 hi12{f98=1 f31=1 ?6 res=040} [SPECULATIVE (prospective, not measured): SRC 0x11 = ACCB (2nd accumulator)]
w35 0000620407 ?word 0x0000620407 ; 000.6.20.407 hi12{-} [table-lookup idiom, class-6 addr8 = table selector (INFERRED)]
w36 00124011CE ?word 0x00124011CE ; 012.4.01.1CE hi12{ST f31=1} [table-lookup idiom, third word (INFERRED)]
w37 01042011CE post (p),(p)+1
w38 0102200000 mac.b (p)0,(p)+0
w39 0000A00415 ld acc,c+,(p)+0
; C-RAM[0x09] (coeff, base 0x00 MEASURED)
; coeff C-RAM[0x09] = op0x66[0] (role mix/tap, INFERRED)
w40 0212200000 mac.b (p)0,(p)+0 ; mem[p]<-acc, acc=0
w41 00002FF407 ld.st acc,(p)-1
w42 0010A001D5 ld (p),c+,(p)+0 ; mem[p]<-acc, acc=0
; C-RAM[0x0A] (coeff, base 0x00 MEASURED)
; coeff C-RAM[0x0A] = op0x66[1] (role mix/tap, INFERRED)
w43 0202200000 mac.b (p)0,(p)+0
w44 0000A01412 ld acc,c+,(p)+1
; C-RAM[0x0B] (coeff, base 0x00 MEASURED)
w45 0212AF41D5 mac (p),c+,(p)-12 ; mem[p]<-acc, acc=0
; C-RAM[0x0C] (coeff, base 0x00 MEASURED)
w46 020220A1CD mac (p),(p)+10
w47 00002031CE ld (p),(p)+3
w48 0212201407 mac.st acc,(p)+1 ; mem[p]<-acc, acc=0
w49 00002FD407 ld.st acc,(p)-3
w50 09001601D5 dly.w dsc[k],p+96
w51 0192A44000 mac.b (p)0,c+,(p)+68 ; store SUPPRESSED (bit7)
; C-RAM[0x0D] (coeff, base 0x00 MEASURED)
w52 00822001C0 mac.b (p),(p)+0
w53 0C4032044C ldreg r4C,#25 ; immediate -> the register lo12 selects
w54 0A00000041 ?word 0x0A00000041 ; A00.0.00.041 hi12{ESC f98=2} [head of a fixed 3-word template (S-5); operands SRC 0x01/ACT 0x01 dark]
w55 08801202C7 dly.r dsc[k],p+32
w56 0102ABF4C8 ?word 0x0102ABF4C8 ; 102.A.BF.4C8 hi12{f98=1 f31=1} cur+ [gain multiply (same op in phaser all-pass and reverb diffuser)]
; C-RAM[0x0E] (coeff, base 0x00 MEASURED)
w57 00002FF407 ld.st acc,(p)-1
w58 00122FF1C0 mac.b (p),(p)-1 ; mem[p]<-acc, acc=0
w59 09001601D5 dly.w dsc[k],p+96
w60 0192A44000 mac.b (p)0,c+,(p)+68 ; store SUPPRESSED (bit7)
; C-RAM[0x0F] (coeff, base 0x00 MEASURED)
w61 00822001C0 mac.b (p),(p)+0
w62 0C4032044C ldreg r4C,#25 ; immediate -> the register lo12 selects
w63 0A00000041 ?word 0x0A00000041 ; A00.0.00.041 hi12{ESC f98=2} [head of a fixed 3-word template (S-5); operands SRC 0x01/ACT 0x01 dark]
w64 08801202C7 dly.r dsc[k],p+32
w65 0102ABD4C8 ?word 0x0102ABD4C8 ; 102.A.BD.4C8 hi12{f98=1 f31=1} cur+ [gain multiply (same op in phaser all-pass and reverb diffuser)]
; C-RAM[0x10] (coeff, base 0x00 MEASURED)
w66 0000A00415 ld acc,c+,(p)+0
; C-RAM[0x11] (coeff, base 0x00 MEASURED)
w67 0212AEC1D5 mac (p),c+,(p)-20 ; mem[p]<-acc, acc=0
; C-RAM[0x12] (coeff, base 0x00 MEASURED)
w68 0880160000 dly.w dsc[k],p+96
w69 060210E000 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 cho_hle = (dsphle == 3);
double cho_inc = 0.0, cho_depth = 0.0, cho_base = 0.0, cho_wet = 0.0;
if (cho_hle)
{
auto q22c = [](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 double cs = bit1 ? 1.0 : 2.0; // operand -> cell scale
const double inc = double(m_dsp1->cram_read(0x00) & 0xffffff) * cs; // phase incr (cell scale)
const double f_hz = inc * 44100.0 / 8388608.0; // 2^23 modulus
cho_inc = std::clamp(f_hz, 0.05, 12.0) / double(STREAM_RATE); // cycles per output sample
const double sweep44 = double(m_dsp1->cram_read(0x02) & 0xffffff) * cs; // sweep amp, 44.1k samples
cho_depth = std::clamp(sweep44, 4.0, 1500.0) * double(STREAM_RATE) / 44100.0; // -> stream samples
cho_base = cho_depth; // keep read tap >= 0 (short chorus)
// wet gain (panel DEPTH). The chip's coefficient field is Q23 (unity 0x7FFFFF): the seeded
// LLE traces damp the delay and keep the EQ biquad finite only at that scale
// (dsp/analysis/N-SINGLE-DELAY-RECURRENCE-2026-09-12 §6-§7), and the program header reads
// this cell as "wet 0.15" (0x1364D9 / 2^23). q22c * cs is the chip cell at Q22 -- the right
// convention for the INTEGER cells above, 2x hot for a gain -- so halve it.
cho_wet = std::clamp(q22c(m_dsp1->cram_read(0x09)) * cs * 0.5, 0.0, 0.95);
}
Per-sample insert (the reconstructed signal path):
if (cho_hle)
{
m_cho_phase += cho_inc;
if (m_cho_phase >= 1.0) m_cho_phase -= 1.0;
const double s = std::sin(2.0 * M_PI * m_cho_phase);
const double c = std::cos(2.0 * M_PI * m_cho_phase);
const double dA = cho_base + cho_depth * (0.5 + 0.5 * s);
const double dB = cho_base + cho_depth * (0.5 + 0.5 * c);
const double sl = double(mix_l) / 32768.0, sr = double(mix_r) / 32768.0;
m_cho_l.write(sl); m_cho_r.write(sr);
const double wl = 0.5 * (m_cho_l.read(dA) + m_cho_l.read(dB));
const double wr = 0.5 * (m_cho_r.read(dA) + m_cho_r.read(dB));
mix_l = int32_t(((1.0 - cho_wet) * sl + cho_wet * wl) * 32768.0);
mix_r = int32_t(((1.0 - cho_wet) * sr + cho_wet * wr) * 32768.0);
}