/* * This file is part of libsidplayfp, a SID player engine. * * Copyright 2011-2015 Leandro Nini * Copyright 2007-2010 Antti Lankila * Copyright 2004,2010 Dag Lem * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef FILTER6581_H #define FILTER6581_H #include "siddefs-fp.h" #include #include "Filter.h" #include "FilterModelConfig.h" #include "sidcxx11.h" namespace reSIDfp { class Integrator; /** * The SID filter is modeled with a two-integrator-loop biquadratic filter, * which has been confirmed by Bob Yannes to be the actual circuit used in * the SID chip. * * Measurements show that excellent emulation of the SID filter is achieved, * except when high resonance is combined with high sustain levels. * In this case the SID op-amps are performing less than ideally and are * causing some peculiar behavior of the SID filter. This however seems to * have more effect on the overall amplitude than on the color of the sound. * * The theory for the filter circuit can be found in "Microelectric Circuits" * by Adel S. Sedra and Kenneth C. Smith. * The circuit is modeled based on the explanation found there except that * an additional inverter is used in the feedback from the bandpass output, * allowing the summer op-amp to operate in single-ended mode. This yields * filter outputs with levels independent of Q, which corresponds with the * results obtained from a real SID. * * We have been able to model the summer and the two integrators of the circuit * to form components of an IIR filter. * Vhp is the output of the summer, Vbp is the output of the first integrator, * and Vlp is the output of the second integrator in the filter circuit. * * According to Bob Yannes, the active stages of the SID filter are not really * op-amps. Rather, simple NMOS inverters are used. By biasing an inverter * into its region of quasi-linear operation using a feedback resistor from * input to output, a MOS inverter can be made to act like an op-amp for * small signals centered around the switching threshold. * * In 2008, Michael Huth facilitated closer investigation of the SID 6581 * filter circuit by publishing high quality microscope photographs of the die. * Tommi Lempinen has done an impressive work on re-vectorizing and annotating * the die photographs, substantially simplifying further analysis of the * filter circuit. * * The filter schematics below are reverse engineered from these re-vectorized * and annotated die photographs. While the filter first depicted in reSID 0.9 * is a correct model of the basic filter, the schematics are now completed * with the audio mixer and output stage, including details on intended * relative resistor values. Also included are schematics for the NMOS FET * voltage controlled resistors (VCRs) used to control cutoff frequency, the * DAC which controls the VCRs, the NMOS op-amps, and the output buffer. * * * SID filter / mixer / output * --------------------------- * ~~~ * --------------------------------------------------- * | | * | --1R1-- \-- D7 | * | ---R1-- | | | * | | | |--2R1-- \--| D6 | * | --------------|--Rw-----[A>--|--Rw-----[A>--| * ve (EXT IN) | | | | * D3 \ ---------------R8--| | | (CAP2A) | (CAP1A) * | v3 | | vhp | vbp | vlp * D2 | \ -----------R8--| ----- | | * | | v2 | | | | * D1 | | \ -------R8--| | ---------------- | * | | | v1 | | | | * D0 | | | \ ---R8-- | | --------------------------- * | | | | | | | * R6 R6 R6 R6 R6 R6 R6 * | | | | $18 | | | $18 * | \ | | D7: 1=open \ \ \ D6 - D4: 0=open * | | | | | | | * --------------------------------- 12V * | * | D3 --/ --1R2-- | * | ---R8-- | | ---R2-- | * | | | D2 |--/ --2R2--| | | ||-- * ------[A>---------| |-----[A>-----|| * D1 |--/ --4R2--| (4.25R2) ||-- * $18 | | | * 0=open D0 --/ --8R2-- (8.75R2) | * * vo (AUDIO * OUT) * * * v1 - voice 1 * v2 - voice 2 * v3 - voice 3 * ve - ext in * vhp - highpass output * vbp - bandpass output * vlp - lowpass output * vo - audio out * [A> - single ended inverting op-amp (self-biased NMOS inverter) * Rn - "resistors", implemented with custom NMOS FETs * Rw - cutoff frequency resistor (VCR) * C - capacitor * ~~~ * Notes: * * R2 ~ 2.0*R1 * R6 ~ 6.0*R1 * R8 ~ 8.0*R1 * R24 ~ 24.0*R1 * * The Rn "resistors" in the circuit are implemented with custom NMOS FETs, * probably because of space constraints on the SID die. The silicon substrate * is laid out in a narrow strip or "snake", with a strip length proportional * to the intended resistance. The polysilicon gate electrode covers the entire * silicon substrate and is fixed at 12V in order for the NMOS FET to operate * in triode mode (a.k.a. linear mode or ohmic mode). * * Even in "linear mode", an NMOS FET is only an approximation of a resistor, * as the apparant resistance increases with increasing drain-to-source * voltage. If the drain-to-source voltage should approach the gate voltage * of 12V, the NMOS FET will enter saturation mode (a.k.a. active mode), and * the NMOS FET will not operate anywhere like a resistor. * * * * NMOS FET voltage controlled resistor (VCR) * ------------------------------------------ * ~~~ * Vw * * | * | * R1 * | * --R1--| * | __|__ * | ----- * | | | * vi ---------- -------- vo * | | * ----R24---- * * * vi - input * vo - output * Rn - "resistors", implemented with custom NMOS FETs * Vw - voltage from 11-bit DAC (frequency cutoff control) * ~~~ * Notes: * * An approximate value for R24 can be found by using the formula for the * filter cutoff frequency: * * FCmin = 1/(2*pi*Rmax*C) * * Assuming that a the setting for minimum cutoff frequency in combination with * a low level input signal ensures that only negligible current will flow * through the transistor in the schematics above, values for FCmin and C can * be substituted in this formula to find Rmax. * Using C = 470pF and FCmin = 220Hz (measured value), we get: * * FCmin = 1/(2*pi*Rmax*C) * Rmax = 1/(2*pi*FCmin*C) = 1/(2*pi*220*470e-12) ~ 1.5MOhm * * From this it follows that: * R24 = Rmax ~ 1.5MOhm * R1 ~ R24/24 ~ 64kOhm * R2 ~ 2.0*R1 ~ 128kOhm * R6 ~ 6.0*R1 ~ 384kOhm * R8 ~ 8.0*R1 ~ 512kOhm * * Note that these are only approximate values for one particular SID chip, * due to process variations the values can be substantially different in * other chips. * * * * Filter frequency cutoff DAC * --------------------------- * * ~~~ * 12V 10 9 8 7 6 5 4 3 2 1 0 VGND * | | | | | | | | | | | | | Missing * 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R termination * | | | | | | | | | | | | | * Vw ----R---R---R---R---R---R---R---R---R---R---R-- --- * * * Bit on: 12V * Bit off: 5V (VGND) * ~~~ * As is the case with all MOS 6581 DACs, the termination to (virtual) ground * at bit 0 is missing. * * Furthermore, the control of the two VCRs imposes a load on the DAC output * which varies with the input signals to the VCRs. This can be seen from the * VCR figure above. * * * * "Op-amp" (self-biased NMOS inverter) * ------------------------------------ * ~~~ * * 12V * * | * -----------| * | | * | ------| * | | | * | | ||-- * | --|| * | ||-- * ||-- | * vi -----|| |--------- vo * ||-- | | * | ||-- | * |-------|| | * | ||-- | * ||-- | | * --|| | | * | ||-- | | * | | | | * | -----------| | * | | | * | | * | GND | * | | * ---------------------- * * * vi - input * vo - output * ~~~ * Notes: * * The schematics above are laid out to show that the "op-amp" logically * consists of two building blocks; a saturated load NMOS inverter (on the * right hand side of the schematics) with a buffer / bias input stage * consisting of a variable saturated load NMOS inverter (on the left hand * side of the schematics). * * Provided a reasonably high input impedance and a reasonably low output * impedance, the "op-amp" can be modeled as a voltage transfer function * mapping input voltage to output voltage. * * * * Output buffer (NMOS voltage follower) * ------------------------------------- * ~~~ * * 12V * * | * | * ||-- * vi -----|| * ||-- * | * |------ vo * | (AUDIO * Rext OUT) * | * | * * GND * * vi - input * vo - output * Rext - external resistor, 1kOhm * ~~~ * Notes: * * The external resistor Rext is needed to complete the NMOS voltage follower, * this resistor has a recommended value of 1kOhm. * * Die photographs show that actually, two NMOS transistors are used in the * voltage follower. However the two transistors are coupled in parallel (all * terminals are pairwise common), which implies that we can model the two * transistors as one. */ class Filter6581 final : public Filter { private: /// Current volume amplifier setting. unsigned short* currentGain; /// Current filter/voice mixer setting. unsigned short* currentMixer; /// Filter input summer setting. unsigned short* currentSummer; /// Filter resonance value. unsigned short* currentResonance; const unsigned short* f0_dac; unsigned short** mixer; unsigned short** summer; unsigned short** gain; /// Filter highpass state. int Vhp; /// Filter bandpass state. int Vbp; /// Filter lowpass state. int Vlp; /// Filter external input. int ve; const int voiceScaleS14; const int voiceDC; /// VCR + associated capacitor connected to highpass output. std::unique_ptr const hpIntegrator; /// VCR + associated capacitor connected to lowpass output. std::unique_ptr const bpIntegrator; public: Filter6581() : currentGain(nullptr), currentMixer(nullptr), currentSummer(nullptr), currentResonance(nullptr), f0_dac(FilterModelConfig::getInstance()->getDAC(0.5)), mixer(FilterModelConfig::getInstance()->getMixer()), summer(FilterModelConfig::getInstance()->getSummer()), gain(FilterModelConfig::getInstance()->getGain()), Vhp(0), Vbp(0), Vlp(0), ve(0), voiceScaleS14(FilterModelConfig::getInstance()->getVoiceScaleS14()), voiceDC(FilterModelConfig::getInstance()->getVoiceDC()), hpIntegrator(FilterModelConfig::getInstance()->buildIntegrator()), bpIntegrator(FilterModelConfig::getInstance()->buildIntegrator()) { input(0); } ~Filter6581(); int clock(int voice1, int voice2, int voice3) override; void input(int sample) override { ve = (sample * voiceScaleS14 * 3 >> 10) + mixer[0][0]; } /** * Set filter cutoff frequency. */ void updatedCenterFrequency() override; /** * Set filter resonance. * * In the MOS 6581, 1/Q is controlled linearly by res. */ void updateResonance(unsigned char res) override { currentResonance = gain[~res & 0xf]; } void updatedMixing() override; public: /** * Set filter curve type based on single parameter. * * @param curvePosition 0 .. 1, where 0 sets center frequency high ("light") and 1 sets it low ("dark"), default is 0.5 */ void setFilterCurve(double curvePosition); }; } // namespace reSIDfp #if RESID_INLINING || defined(FILTER6581_CPP) #include "Integrator.h" namespace reSIDfp { RESID_INLINE int Filter6581::clock(int voice1, int voice2, int voice3) { voice1 = (voice1 * voiceScaleS14 >> 18) + voiceDC; voice2 = (voice2 * voiceScaleS14 >> 18) + voiceDC; voice3 = (voice3 * voiceScaleS14 >> 18) + voiceDC; int Vi = 0; int Vo = 0; (filt1 ? Vi : Vo) += voice1; (filt2 ? Vi : Vo) += voice2; // NB! Voice 3 is not silenced by voice3off if it is routed // through the filter. if (filt3) Vi += voice3; else if (!voice3off) Vo += voice3; (filtE ? Vi : Vo) += ve; Vlp = bpIntegrator->solve(Vbp); Vbp = hpIntegrator->solve(Vhp); Vhp = currentSummer[currentResonance[Vbp] + Vlp + Vi]; if (lp) Vo += Vlp; if (bp) Vo += Vbp; if (hp) Vo += Vhp; return currentGain[currentMixer[Vo]] - (1 << 15); } } // namespace reSIDfp #endif #endif