00001 // mixer.cc 00002 // 00003 // a trivially simple, 1 SIS junction mixer simulation 00004 // (with way too many comments, probably) 00005 00006 #include "supermix.h" 00007 00008 // Since SIS junctions are not linear devices, SuperMix does not 00009 // simulate an SIS junction as a circuit element directly. Instead, 00010 // SuperMix includes the class "mixer" which combines nonlinear 00011 // junction elements with the linear RF, IF, and DC bias embedding 00012 // networks to synthesize a single, multi-frequency nport which models 00013 // the response of the circuitry as a harmonic mixer. 00014 00015 // This program illustrates the basic procedure for building a mixer 00016 // model. 00017 00018 00019 00020 int main() 00021 { 00022 // ----------------------------------------------------------------- 00023 // THE GLOBAL VALUES 00024 00025 device::T = 4.2*Kelvin; // our superconducting receiver 00026 device::f = 1*GHz; // the IF output frequency 00027 device::Z0 = 50.*Ohm; // the S matrix normalizing impedance 00028 00029 00030 // ----------------------------------------------------------------- 00031 // SIS DC IV CHARACTERISTIC CURVE DATA 00032 00033 // The most important defining feature of an SIS device is its DC 00034 // current-voltage characteristic. SuperMix requires this 00035 // characteristic to be specified by providing two properly 00036 // normalized IV curve data files. The two required IV curves are: 00037 // 00038 // (1) DC I-V characteristic with voltages normalized so that the 00039 // gap voltage ~ 1.0, and the slope of the I(V) curve far 00040 // above the gap == 1.0. 00041 // 00042 // (2) Kramers-Kronig transform of the DC I-V characteristic using 00043 // the same voltage and current normalization scale factors as 00044 // used in the DC I-V characteristic. 00045 // 00046 // The files should start with Voltage = 0.0 and extend through 00047 // positive values up to at least twice the gap voltage for curve 00048 // (1) and four times the gap voltage for curve (2). The files 00049 // should contain sorted voltage - current pairs, one per line. The 00050 // voltages need not be equally spaced. 00051 // 00052 // Examine the files "idc.dat" and "ikk.dat" included with this 00053 // program file for examples. Note how comment lines can be included 00054 // in the files. These files were produced using the example 00055 // programs makeiv.cc and makeikk.cc 00056 00057 // The class "ivcurve", defined in "junction.h", is used to access 00058 // the IV data: 00059 00060 ivcurve IV("idc.dat","ikk.dat"); 00061 00062 00063 // ----------------------------------------------------------------- 00064 // DEFINING AN SIS DEVICE 00065 00066 // An SIS junction device is created by declaring an object of type 00067 // sis_device, which is derived from class junction. The definition 00068 // of sis_device is in "sisdevice.h", the definition of junction is 00069 // in "junction.h". There are several member parameters of the 00070 // device which must be set to fully define the device: 00071 00072 sis_device SIS; 00073 SIS.set_iv(IV); // the IV curve object for this junction 00074 SIS.Vn = 2.8*mVolt; // the voltage normalization of the IV curves 00075 SIS.Rn = 10*Ohm; // the current normalization is Vn/Rn 00076 SIS.Cap = 140*fFarad; // the junction capacitance 00077 00078 // Vn is nominally the gap voltage of the SIS; this is how the IV 00079 // curves should have their voltages normalized. Rn is the SIS 00080 // normal resistance, so that 1/Rn is the slope of the real SIS DC 00081 // IV curve far above the gap. The normalized IV curve has slope = 1 00082 // above the gap. 00083 // 00084 // If the sis_device is given a nonzero capacitance, be sure you do 00085 // not include its capacitance as part of the linear embedding 00086 // network to be described below; either include it there or in the 00087 // sis_device, but not both. 00088 00089 // If we were going to simulate a multi-SIS circuit, we would need 00090 // more sis_device declarations; each SIS in a multi-SIS circuit 00091 // needs its own sis_device object. 00092 00093 00094 // ----------------------------------------------------------------- 00095 // THE RF AND IF EMBEDDING NETWORKS 00096 00097 // We'll use an especially simple RF embedding network: The antenna 00098 // impedance, which we assume to be real, in parallel with a tuning 00099 // inductance to tune out the SIS junction capacitance. Assume we 00100 // want the receiver to operate at 345 GHz. 00101 00102 double Ftune = 345*GHz; // receiver design frequency 00103 double Zant = 8*Ohm; // the antenna impedance 00104 00105 // Here's the tuning inductance: 00106 00107 inductor Tune; 00108 Tune.parallel(); 00109 Tune.L = 1.0/(2*Pi*Ftune * 2*Pi*Ftune * SIS.Cap); 00110 00111 // Here's the antenna. We use a transformer to transform the RF 00112 // input to the antenna impedance. We'll use port 1 as the RF input 00113 // and port 2 to present the correct impedance to the SIS. The 00114 // definition for transformer is in "transformer.h" 00115 00116 transformer Ant; 00117 Ant.Z2 = Zant; // Z2 is the impedance for port 2. 00118 00119 // The complete RF embedding network combines these two 00120 // components. When we build a mixer using the embedding networks, 00121 // we have to connect the SIS junction to port 1 of the network, so 00122 // we must be careful to add ports in the right order using 00123 // add_port(): 00124 00125 circuit RF; 00126 RF.connect(Ant, 2, Tune, 1); 00127 RF.add_port(Tune, 2); // port 1 must be for the SIS 00128 RF.add_port(Ant, 1); // so port 2 is the RF input 00129 00130 // The IF embedding network will be a simple transformer presenting 00131 // the IF embedding impedance to the junction (of course, the SIS 00132 // has a nonzero capacitance, so that automatically gets included in 00133 // the calculations at the IF frequency as well). 00134 00135 double ZIF = 20*Ohm; // IF presents 2*Rn to the SIS 00136 00137 transformer IF; 00138 IF.Z1 = ZIF; // port 1 must be reserved for the SIS 00139 00140 // Port 2 of device IF will be the IF ouput port. 00141 00142 00143 // ----------------------------------------------------------------- 00144 // THE DC BIAS NETWORK 00145 00146 // The DC bias network must have exactly as many ports as there are 00147 // SIS junctions in the mixer circuit, which is only 1 in this 00148 // case. We'll use a perfect voltage source for the bias 00149 // supply. Sources, all 1-ports, are defined in "sources.h". 00150 00151 voltage_source BIAS; 00152 BIAS.source_f = 0.0; // source frequency is DC 00153 BIAS.source_voltage = 2.2*mVolt; // about the middle of the step 00154 00155 00156 // ----------------------------------------------------------------- 00157 // THE LOCAL OSCILLATOR POWER SOURCE 00158 00159 // We'll create a local oscillator source which we can tell the 00160 // mixer to connect to the RF input during large signal, harmonic 00161 // balance calculations. The particular source used, a generator, is 00162 // also defined in "sources.h". 00163 00164 generator LO; 00165 LO.source_f = Ftune; // source frequency is 345 GHz 00166 LO.source_power = 100*Nano*Watt; // for about 1.4 mV peak at SIS 00167 LO.Temp = 0; // useful for mixer noise calc's 00168 00169 00170 // ----------------------------------------------------------------- 00171 // BUILDING THE MIXER 00172 00173 // Class mixer, defined in "mixer.h", connects nonlinear devices 00174 // like our SIS into linear embedding networks and allows us to 00175 // perform harmonic balance calculations and small signal 00176 // analyses. Here's how to set one up: 00177 00178 mixer Mix; 00179 00180 Mix.add_junction(SIS); // call once for each SIS device 00181 00182 Mix.set_rf(RF); // we provide the linear networks 00183 Mix.set_if(IF); 00184 Mix.set_bias(BIAS); 00185 00186 // the LO source is only connected during harmonic balance; here's 00187 // how we tell the mixer to use it (it's called a balance 00188 // terminator since it terminates the RF input port during 00189 // harmonic balance calculations): 00190 00191 Mix.set_balance_terminator(LO, 2); // terminates port 2 of RF 00192 00193 // Mixers need to know two frequencies: the IF frequency and the 00194 // LO frequency. The global frequency parameter device::f will 00195 // provide the IF frequency, but a special mixer call must set the 00196 // mixer's internal LO frequency parameter. Here we tell that 00197 // parameter to shadow our LO source frequency by passing the 00198 // address of its frequency parameter. 00199 00200 Mix.set_LO(& LO.source_f); // we shadow the LO source frequency 00201 00202 // Now the description of our mixer is complete. The only thing 00203 // remaining is to tell the mixer how many harmonics to use in its 00204 // calculations (we can change this number at any time by calling 00205 // harmonics() again): 00206 00207 Mix.harmonics(3); // use fundamental + 2 harmonics 00208 00209 // Let's confirm that our mixer is complete by asking it: 00210 cout << "# Mixer complete? " 00211 << ( ( Mix.flag_mixer_incomplete() ) ? "No" : "Yes" ) 00212 << endl; 00213 00214 00215 // ----------------------------------------------------------------- 00216 // USING THE MIXER: PORTS 00217 00218 // Mixers are nports, but complicated ones, especially if the number 00219 // of harmonics is large. The mixer has ports for the IF and for the 00220 // RF at each sideband, upper and lower, at each harmonic. Keeping 00221 // track of the port indexing for a mixer can be difficult, so the 00222 // mixer class provides a member function to tell you the port index 00223 // you need. Let's use it now: 00224 00225 // The IF circuit's output is at its port 2; the IF is harmonic 0: 00226 00227 int IF_PORT = Mix.port(2, 0); 00228 00229 // The RF circuit's input is at its port 2, the Upper Side Band of 00230 // the fundamental RF frequency is harmonic +1, the Lower Side Band 00231 // is harmonic -1: 00232 00233 int USB_PORT = Mix.port(2, 1); 00234 00235 // Here are the port index values: 00236 cout << "# IF port: " << IF_PORT 00237 << " , USB port: " << USB_PORT << endl; 00238 00239 00240 // ----------------------------------------------------------------- 00241 // USING THE MIXER: HARMONIC BALANCE 00242 00243 // The operating state of the nonlinear SIS junction must be 00244 // determined before we can perform a circuit (small signal) 00245 // analysis of our receiver. This is the purpose of the mixer 00246 // harmonic balance routine: 00247 00248 Mix.initialize_operating_state(); // call this before the first balance 00249 Mix.balance(); // that's all there is to it 00250 00251 // Whenever you change bias voltage or LO power or frequency, call 00252 // balance() to recalculate the proper SIS operating state. If the 00253 // change is more than just a small incremental change, it can speed 00254 // up the balance slightly if you call initialize_operating_state() 00255 // first. 00256 00257 // To look at the SIS operating state, class junction provides the 00258 // member functions V() and I(), which output vectors of the DC and 00259 // RMS harmonic voltages and currents across the juction: 00260 00261 cout << endl; 00262 complex::out_degree(); 00263 complex::out_delimited(); 00264 cout << "SIS Operating State (mag,deg) at DC and 1, 2, 3 harmonic:" 00265 << endl; 00266 cout << "V (mVolt): " << SIS.V()/mVolt << endl; 00267 cout << "I (mAmp): " << SIS.I()/mAmp << endl; 00268 00269 00270 // ----------------------------------------------------------------- 00271 // USING THE MIXER: SMALL SIGNAL ANALYSIS 00272 00273 // Once the operating state of the mixer has been set, just use 00274 // get_data() like you would for any other nport in order to 00275 // calculate the small signal response: 00276 00277 sdata s = Mix.get_data(); // s holds the small-signal response 00278 00279 // Let's look at the mixer gain in dB: 00280 double gain_db = s.SdB(IF_PORT, USB_PORT); 00281 cout << endl; 00282 cout << "Gain: " << gain_db << " dB" << endl; 00283 00284 // Mixer noise is trickier. Since a mixer is not a 2-port (all those 00285 // sidebands, remember), just calling s.tn() will give a misleading 00286 // answer. We use another small-signal analysis function of mixer, 00287 // get_term_data(). get_term_data() terminates all RF ports, as in a 00288 // harmonic balance calculation, and then performs a small signal 00289 // analysis. The result is just a 1-port sdata, if there is only a 00290 // single IF output port. 00291 00292 sdata sterm = Mix.get_term_data(); 00293 00294 // Since we set the temperature of our LO source to zero, all our RF 00295 // inputs are terminated with matched, 0 Kelvin terminations. The 00296 // output noise power at the IF is now the noise due to the RF quantum 00297 // noise limit + any additional noise added by the receiver. 00298 00299 double output_noise = sterm.C[IF_PORT][IF_PORT].real; // C is complex 00300 00301 // If we take this noise and divide by the mixer gain to refer it 00302 // back to the RF input, then we get the proper single sideband 00303 // mixer noise temperature. Since the mixer gain is the magnitude 00304 // squared of the S parameter connecting IF to USB: 00305 00306 double input_noise = output_noise/norm(s.S[IF_PORT][USB_PORT]); 00307 cout << "SSB Tn: " << input_noise/Kelvin << " Kelvin" << endl; 00308 00309 // If we were just to use the tn() member function of the sdata s, 00310 // we would get: 00311 00312 cout << "Wrong noise: " << s.tn(IF_PORT, USB_PORT) << " Kelvin" << endl; 00313 00314 // This is only the excess noise of the SIS mixer over the much 00315 // greater quantum noise being introduced from all sidebands. 00316 00317 00318 }
Please direct comments and corrections to
supermix@submm.caltech.edu
Go to the supermix home page
Generated by
1.2.7