From f0786bbec1e320dbc007cb5340024c47154f9038 Mon Sep 17 00:00:00 2001 From: Piotr Mitros Date: Fri, 13 Jan 2012 23:44:59 -0500 Subject: [PATCH 1/4] Page close event logged --- js/video_player.js | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/js/video_player.js b/js/video_player.js index f898b6e469..1e5b843147 100644 --- a/js/video_player.js +++ b/js/video_player.js @@ -1,5 +1,19 @@ // Things to abstract out to another file +var close_event_logged = false; + +function log_close() { + close_event_logged = "waiting"; + log_event('page_close', {}); + // Google Chrome will close without letting the event go through. + // This causes the page close to be delayed until we've hit the + // server. + while(close_event_logged != "done") { + } +} + +window.onbeforeunload = log_close; + function getCookie(name) { var cookieValue = null; if (document.cookie && document.cookie != '') { @@ -199,15 +213,6 @@ function videoDestroy() { } function log_event(e, d) { - //$("#eventlog").append("
"); - //$("#eventlog").append(JSON.stringify(e)); - - // TODO: Decide if we want seperate tracking server. - // If so, we need to resolve: - // * AJAX from different domain (XMLHttpRequest cannot load http://localhost:7000/userlog. Origin http://localhost:8000 is not allowed by Access-Control-Allow-Origin.) - // * Verifying sessions/authentication - - /*window['console'].log(JSON.stringify(e));*/ $.get("/event", { "event_type" : e, @@ -215,6 +220,9 @@ function log_event(e, d) { "page" : document.URL }, function(data) { + if (close_event_logged == "waiting") { + close_event_logged == "done"; + } }); } From c598b6de6863355f6cd02eca6e0bfac1307e8474 Mon Sep 17 00:00:00 2001 From: Piotr Mitros Date: Sat, 14 Jan 2012 00:05:37 -0500 Subject: [PATCH 2/4] Logging has a timeout so it doesn't crash Chrome --- js/video_player.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/js/video_player.js b/js/video_player.js index 1e5b843147..03f3bd310d 100644 --- a/js/video_player.js +++ b/js/video_player.js @@ -3,12 +3,16 @@ var close_event_logged = false; function log_close() { + var d=new Date(); + var t=d.getTime(); close_event_logged = "waiting"; log_event('page_close', {}); // Google Chrome will close without letting the event go through. // This causes the page close to be delayed until we've hit the // server. - while(close_event_logged != "done") { + // TODO: Check what happens with no network. + while((close_event_logged != "done") && (d.getTime() < t+500)) { + console.log(close_event_logged); } } @@ -220,8 +224,10 @@ function log_event(e, d) { "page" : document.URL }, function(data) { + console.log("closing"); if (close_event_logged == "waiting") { - close_event_logged == "done"; + close_event_logged = "done"; + console.log("closed"); } }); } From 25389b5a2d29510d6e9066232a64719f9bd7b3eb Mon Sep 17 00:00:00 2001 From: Piotr Mitros Date: Mon, 16 Jan 2012 13:24:52 -0500 Subject: [PATCH 3/4] Close page event logging works in FF and Chrome --- js/video_player.js | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/js/video_player.js b/js/video_player.js index 03f3bd310d..152a1b0974 100644 --- a/js/video_player.js +++ b/js/video_player.js @@ -1,19 +1,23 @@ // Things to abstract out to another file -var close_event_logged = false; +// We do sync AJAX for just the page close event. +// TODO: This should _really_ not be a global. +var log_close_event = false; function log_close() { var d=new Date(); var t=d.getTime(); - close_event_logged = "waiting"; + //close_event_logged = "waiting"; + log_close_event = true; log_event('page_close', {}); + log_close_event = false; // Google Chrome will close without letting the event go through. // This causes the page close to be delayed until we've hit the - // server. + // server. The code below fixes it, but breaks Firefox. // TODO: Check what happens with no network. - while((close_event_logged != "done") && (d.getTime() < t+500)) { + /*while((close_event_logged != "done") && (d.getTime() < t+500)) { console.log(close_event_logged); - } + }*/ } window.onbeforeunload = log_close; @@ -217,19 +221,28 @@ function videoDestroy() { } function log_event(e, d) { - $.get("/event", - { - "event_type" : e, - "event" : JSON.stringify(d), - "page" : document.URL - }, + data = { + "event_type" : e, + "event" : JSON.stringify(d), + "page" : document.URL + } + $.ajax({type:'GET', + url: '/event', + dataType: 'json', + data: data, + async: !log_close_event, // HACK: See comment on log_close_event + success: function(){}, + headers : {'X-CSRFToken':getCookie('csrftoken')} + }); + + /*, // Commenting out Chrome bug fix, since it breaks FF function(data) { console.log("closing"); if (close_event_logged == "waiting") { close_event_logged = "done"; console.log("closed"); } - }); + });*/ } function seek_slide(type,oe,value) { From 7600920f76d629551c10dae7f453e9e37ad388d4 Mon Sep 17 00:00:00 2001 From: Piotr Mitros Date: Mon, 16 Jan 2012 13:29:57 -0500 Subject: [PATCH 4/4] CJT's new schematic editor --- js/cktsim.js | 825 +++++++++++++++++++++++++++++++++--------------- js/schematic.js | 440 +++++++++++++++++++------- 2 files changed, 896 insertions(+), 369 deletions(-) diff --git a/js/cktsim.js b/js/cktsim.js index ca289a6b8e..ddb327cfb7 100644 --- a/js/cktsim.js +++ b/js/cktsim.js @@ -6,31 +6,13 @@ // Copyright (C) 2011 Massachusetts Institute of Technology -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -// of the Software, and to permit persons to whom the Software is furnished to do -// so, subject to the following conditions: - -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. - -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - // create a circuit for simulation using "new cktsim.Circuit()" // for modified nodal analysis (MNA) stamps see // http://www.analog-electronics.eu/analog-electronics/modified-nodal-analysis/modified-nodal-analysis.xhtml cktsim = (function() { - + /////////////////////////////////////////////////////////////////////////////// // // Circuit @@ -41,10 +23,12 @@ cktsim = (function() { T_VOLTAGE = 0; T_CURRENT = 1; + v_newt_lim = 0.3; // Voltage limited Newton great for Mos/diodes v_abstol = 1e-6; // criterion for absolute convergence (voltage) i_abstol = 1e-12; // criterion for absolute convergence (current) min_time_step = 1e-18; // smallest possible time step - max_iterations = 50; // max iterations before giving up + max_dc_iters = 200; // max iterations before giving pu + max_tran_iters = 10; // max iterations before giving up increase_limit = 4; // if we converge in this many iterations, increase time step time_step_increase_factor = 2.0; time_step_decrease_factor = 0.3; @@ -57,9 +41,9 @@ cktsim = (function() { this.devices = []; // list of devices this.device_map = new Array(); // map name -> device - this.end_of_timestep = []; // list of devices to be called at end of each timestep this.finalized = false; + this.diddc = false; this.node_index = -1; } @@ -88,23 +72,21 @@ cktsim = (function() { this.devices[i].finalize(this); // set up augmented matrix and various temp vectors - this.matrix = new Array(this.N); + this.matrix = this.make_mat(this.N, this.N+1); + this.Gl = this.make_mat(this.N, this.N); // Matrix for linear conductances + this.G = this.make_mat(this.N, this.N); // Complete conductance matrix + this.C = this.make_mat(this.N, this.N); // Matrix for linear L's and C's + this.soln_max = new Array(this.N); // max abs value seen for each unknown - this.rtol = new Array(this.N); // soln_max * reltol this.abstol = new Array(this.N); this.solution = new Array(this.N); - for (var i = this.N - 1; i >= 0; --i) { - this.matrix[i] = new Array(this.N + 1); + this.rhs = new Array(this.N); + for (var i = this.N - 1; i >= 0; --i) { this.soln_max[i] = 0.0; - this.rtol[i] = 0.0; this.abstol[i] = this.ntypes[i] == T_VOLTAGE ? v_abstol : i_abstol; this.solution[i] = 0.0; + this.rhs[i] = 0.0; } - - // for backward Euler: coeff0 = 1/timestep, coeff1 = 0 - // for trapezoidal: coeff0 = 2/timestep, coeff1 = 1 - this.coeff0 = undefined; - this.coeff1 = undefined; } } @@ -119,7 +101,7 @@ cktsim = (function() { var type = component[0]; // ignore wires, ground connections, scope probes and view info - if (type == 'view' || type == 'w' || type == 'g' || type == 's') continue; + if (type == 'view' || type == 'w' || type == 'g' || type == 's' || type == 'L') continue; var properties = component[2]; var name = properties['name']; @@ -136,6 +118,8 @@ cktsim = (function() { // process the component if (type == 'r') // resistor this.r(connections[0],connections[1],properties['r'],name); + else if (type == 'd') // diode + this.d(connections[0],connections[1],properties['area'],name); else if (type == 'c') // capacitor this.c(connections[0],connections[1],properties['c'],name); else if (type == 'l') // inductor @@ -147,57 +131,51 @@ cktsim = (function() { else if (type == 'o') // op amp this.opamp(connections[0],connections[1],connections[2],properties['A'],name); else if (type == 'n') // n fet - this.fet('n',connections[0],connections[1],connections[2], - properties['sw'],properties['sl'],name); + this.n(connections[0],connections[1],connections[2], + properties['W/L'],name); else if (type == 'p') // p fet - this.fet('p',connections[0],connections[1],connections[2], - properties['sw'],properties['sl'],name); + this.p(connections[0],connections[1],connections[2], + properties['W/L'],name); } } // if converges: updates this.solution, this.soln_max, returns iter count // otherwise: return undefined and set this.problem_node // The argument should be a function that sets up the linear system. - Circuit.prototype.find_solution = function(load) { + Circuit.prototype.find_solution = function(load,maxiters) { var soln = this.solution; - var old_soln,temp,converged; + var rhs = this.rhs; + var d_sol,temp,converged; // iteratively solve until values convere or iteration limit exceeded - for (var iter = 0; iter < max_iterations; i++) { + for (var iter = 0; iter < maxiters; iter++) { // set up equations - this.initialize_linear_system(); - load(this); + // no longer needed this.initialize_linear_system(); + load(this,soln,rhs); - // solve for node voltages and branch currents - old_soln = soln; - soln = solve_linear_system(this.matrix); + // Compute the Newton delta + d_sol = solve_linear_system(this.matrix,rhs); - // check convergence: abs(new-old) <= abstol + reltol*max; + // Update solution and check convergence. converged = true; for (var i = this.N - 1; i >= 0; --i) { - temp = Math.abs(soln[i] - old_soln); - if (temp > this.abstol[i] + this.rtol[i]) { + // Simple voltage step limiting to encourage Newton convergence + if (this.ntypes[i] == T_VOLTAGE) { + d_sol[i] = (d_sol[i] > v_newt_lim) ? v_newt_lim : d_sol[i]; + d_sol[i] = (d_sol[i] < -v_newt_lim) ? -v_newt_lim : d_sol[i]; + } + soln[i] += d_sol[i]; + if (Math.abs(soln[i]) > this.soln_max[i]) + this.soln_max[i] = Math.abs(soln[i]); + thresh = this.abstol[i] + reltol*this.soln_max[i]; + if (Math.abs(d_sol[i]) > thresh) { converged = false; this.problem_node = i; - break; } } - if (!converged) continue; - - // other convergence checks here? - - // update solution and maximum - this.solution = soln; - for (var i = this.N - 1; i >= 0; --i) { - temp = Math.abs(soln[i]); - if (temp > this.soln_max[i]) { - this.soln_max[i] = temp; - this.rtol[i] = temp * reltol; - } - } - return iter+1; + // alert(numeric.prettyPrint(this.solution)); + if (converged == true) return iter+1; } - // too many iterations return undefined; } @@ -206,43 +184,165 @@ cktsim = (function() { Circuit.prototype.dc = function() { this.finalize(); - // this function calls load_dc for all devices - function load_dc(ckt) { + // Load up the linear part. + for (var i = this.devices.length - 1; i >= 0; --i) { + this.devices[i].load_linear(this) + } + + // Define f and df/dx for Newton solver + function load_dc(ckt,soln,rhs) { + // rhs is initialized to -Gl * soln + ckt.matv_mult(ckt.Gl, soln, rhs, -1.0); + // G matrix is initialized with linear Gl + ckt.copy_mat(ckt.Gl,ckt.G); + // Now load up the nonlinear parts of rhs and G for (var i = ckt.devices.length - 1; i >= 0; --i) - ckt.devices[i].load_dc(ckt); + ckt.devices[i].load_dc(ckt,soln,rhs); + // G matrix is initialized with linear Gl + ckt.copy_mat(ckt.G,ckt.matrix); } // find the operating point - var iterations = this.find_solution(load_dc); + var iterations = this.find_solution(load_dc,max_dc_iters); - if (typeof iterations == 'undefined') - return 'Node '+this.node_map[this.problem_node]+' did not converge'; + if (typeof iterations == 'undefined') { + return 'Node '+this.node_map[this.problem_node]+' unconverged'; + } else { + // Note that a dc solution was computed + this.diddc = true; + // create solution dictionary + var result = new Array(); + for (var name in this.node_map) { + var index = this.node_map[name]; + result[name] = (index == -1) ? 0 : this.solution[index]; + } + return result; + } + } + + // Transient analysis (needs work!) + Circuit.prototype.tran = function(ntpts, tstart, tstop, no_dc) { + // Standard to do a dc analysis before transient + // Otherwise, do the setup also done in dc. + if ((this.diddc == false) && (no_dc == false)) this.dc(); + else { + // Allocate matrices and vectors. + this.finalize(); + + // Load up the linear elements once and for all + for (var i = this.devices.length - 1; i >= 0; --i) + this.devices[i].load_linear(this) + } + + // Tired of typing this, and using "with" generates hate mail. + var N = this.N; + + // build array to hold list of results for each variable + // last entry is for timepoints. + var response = new Array(N + 1); + for (var i = N; i >= 0; --i) response[i] = new Array(); + + // Allocate space to put previous charge and current + this.oldc = new Array(this.N); + this.oldq = new Array(this.N); + this.c = new Array(this.N); + this.q = new Array(this.N); + this.alpha0 = 1.0; + + // Define f and df/dx for Newton solver + function load_tran(ckt,soln,rhs) { + // rhs is initialized to -Gl * soln + ckt.matv_mult(ckt.Gl, soln, ckt.c,-1.0); + // G matrix is initialized with linear Gl + ckt.copy_mat(ckt.Gl,ckt.G); + // Now load up the nonlinear parts of rhs and G + for (var i = ckt.devices.length - 1; i >= 0; --i) + ckt.devices[i].load_tran(ckt,soln,ckt.c,ckt.time); + + // Exploit the fact that storage elements are linear + ckt.matv_mult(ckt.C, soln, ckt.q, 1.0); + for (var i = ckt.N-1; i >= 0; --i) + rhs[i] = ckt.alpha0 *(ckt.oldq[i] - ckt.q[i]) + ckt.c[i] + + // rhs is initialized to -Gl * soln + ckt.matv_mult(ckt.Gl, soln, ckt.c,-1.0); + + // system matrix is G - alpha0 C. + ckt.mat_scale_add(ckt.G,ckt.C,ckt.alpha0,ckt.matrix); + + } + + this.time = tstart; + var dt = (tstop - tstart)/ntpts; + + // Initialize this.c and this.q + load_tran(this,this.solution,this.rhs) + + for(var tindex = 0; tindex < ntpts; tindex++) { + + // Save the just computed solution, and move back q and c. + for (var i = this.N - 1; i >= 0; --i) { + response[i].push(this.solution[i]); + this.oldc[i] = this.c[i]; + this.oldq[i] = this.q[i]; + } + response[this.N].push(this.time); + this.oldtime = this.time; + + if (this.time >= tstop) break; + // Pick a timestep and an integration method + if (this.time + 1.1*dt > tstop) + this.time = tstop; + else + this.time += dt; + + // Set the timestep + this.alpha0 = 1.0/(this.time - this.oldtime); + + // Predict the solution, nah maybe later. + + // Use Newton to compute the solution. + var iterations = this.find_solution(load_tran,max_tran_iters); + + if (iterations == undefined) + alert('timestep nonconvergence, try more time points'); + } // create solution dictionary var result = new Array(); for (var name in this.node_map) { var index = this.node_map[name]; - result[name] = (index == -1) ? 0 : this.solution[index]; + result[name] = (index == -1) ? 0 : response[index]; } + result['time'] = response[this.N]; return result; } // AC analysis: npts/decade for freqs in range [fstart,fstop] // result['frequencies'] = vector of log10(sample freqs) // result['xxx'] = vector of dB(response for node xxx) - Circuit.prototype.ac = function(npts,fstart,fstop) { - this.finalize(); + // NOTE: Normalization removed in schematic.js, jkw. + Circuit.prototype.ac = function(npts,fstart,fstop,source_name) { + if (this.diddc == false) this.dc(); - // this function calls load_ac for all devices - function load_ac(ckt) { - for (var i = ckt.devices.length - 1; i >= 0; --i) - ckt.devices[i].load_ac(ckt); + var N = this.N; + var G = this.G; + var C = this.C; + + // Complex numbers, we're going to need a bigger boat + var matrixac = this.make_mat(2*N, (2*N)+1); + + // Get the source used for ac + if (this.device_map[source_name] === undefined) { + alert('AC analysis refers to unknown source ' + source_name); + return 'AC analysis failed, unknown source'; } + this.device_map[source_name].load_ac(this,this.rhs); // build array to hold list of results for each node // last entry is for frequency values - var response = new Array(this.N + 1); - for (var i = this.N; i >= 0; --i) response[i] = new Array(); + var response = new Array(N + 1); + for (var i = N; i >= 0; --i) response[i] = new Array(); // multiplicative frequency increase between freq points var delta_f = Math.exp(Math.LN10/npts); @@ -250,19 +350,34 @@ cktsim = (function() { var f = fstart; fstop *= 1.0001; // capture that last time point! while (f <= fstop) { - this.omega = 2 * Math.PI * f; + var omega = 2 * Math.PI * f; + response[this.N].push(f); - // find the operating point - var iterations = this.find_solution(load_ac); + // Find complex x+jy that sats Gx-omega*Cy=rhs; omega*Cx+Gy=0 + // Note: solac[0:N-1]=x, solac[N:2N-1]=y + for (var i = N-1; i >= 0; --i) + { + // First the rhs, replicated for real and imaginary + matrixac[i][2*N] = this.rhs[i]; + matrixac[i+N][2*N] = 0; - if (typeof iterations == 'undefined') - return 'Node '+this.node_map[this.problem_node]+' did not converge'; - else { - response[this.N].push(f); - for (var i = this.N - 1; i >= 0; --i) - response[i].push(this.solution[i]); + for (var j = N-1; j >= 0; --j) + { + matrixac[i][j] = G[i][j]; + matrixac[i+N][j+N] = G[i][j]; + matrixac[i][j+N] = -omega*C[i][j]; + matrixac[i+N][j] = omega*C[i][j]; + } } + // Compute the small signal response + var solac = solve_linear_system(matrixac); + + // Save just the magnitude for now + for (var i = this.N - 1; i >= 0; --i) { + var mag = Math.sqrt(solac[i]*solac[i] + solac[i+N]*solac[i+N]); + response[i].push(mag); + } f *= delta_f; // increment frequency } @@ -276,6 +391,22 @@ cktsim = (function() { return result; } + + // Helper for adding devices to a circuit, warns on duplicate device names. + Circuit.prototype.add_device = function(d,name) { + // Add device to list of devices and to device map + this.devices.push(d); + if (name) { + if (this.device_map[name] === undefined) + this.device_map[name] = d; + else { + alert('Warning: two circuit elements share the same name ' + name); + this.device_map[name] = d; + } + } + return d; + } + Circuit.prototype.r = function(n1,n2,v,name) { // try to convert string value into numeric value, barf if we can't if ((typeof v) == 'string') { @@ -283,14 +414,26 @@ cktsim = (function() { if (v === undefined) return undefined; } - var d; if (v != 0) { - d = new Resistor(n1,n2,v); - this.devices.push(d); - if (name) this.device_map[name] = d; + var d = new Resistor(n1,n2,v); + return this.add_device(d, name); } else return this.v(n1,n2,0,name); // zero resistance == 0V voltage source } + Circuit.prototype.d = function(n1,n2,area,name) { + // try to convert string value into numeric value, barf if we can't + if ((typeof area) == 'string') { + area = parse_number(area,undefined); + if (area === undefined) return undefined; + } + + if (area != 0) { + var d = new Diode(n1,n2,area); + return this.add_device(d, name); + } // zero area diodes discarded. + } + + Circuit.prototype.c = function(n1,n2,v,name) { // try to convert string value into numeric value, barf if we can't if ((typeof v) == 'string') { @@ -298,9 +441,7 @@ cktsim = (function() { if (v === undefined) return undefined; } var d = new Capacitor(n1,n2,v); - this.devices.push(d); - if (name) this.device_map[name] = d; - return d; + return this.add_device(d, name); } Circuit.prototype.l = function(n1,n2,v,name) { @@ -311,26 +452,41 @@ cktsim = (function() { } var branch = this.node(undefined,T_CURRENT); var d = new Inductor(n1,n2,branch,v); - this.devices.push(d); - if (name) this.device_map[name] = d; - return d; + return this.add_device(d, name); } - Circuit.prototype.v = function(n1,n2,v,name) { + Circuit.prototype.v = function(n1,n2,v,name) { var branch = this.node(undefined,T_CURRENT); var d = new VSource(n1,n2,branch,v); - this.devices.push(d); - if (name) this.device_map[name] = d; - return d; + return this.add_device(d, name); } Circuit.prototype.i = function(n1,n2,v,name) { var d = new ISource(n1,n2,v); - this.devices.push(d); - if (name) this.device_map[name] = d; - return d; + return this.add_device(d, name); } + Circuit.prototype.n = function(d,g,s, ratio, name) { + // try to convert string value into numeric value, barf if we can't + if ((typeof ratio) == 'string') { + ratio = parse_number(ratio,undefined); + if (ratio === undefined) return undefined; + } + var d = new Fet(d,g,s,ratio,name,'n'); + return this.add_device(d, name); + } + + Circuit.prototype.p = function(d,g,s, ratio, name) { + // try to convert string value into numeric value, barf if we can't + if ((typeof ratio) == 'string') { + ratio = parse_number(ratio,undefined); + if (ratio === undefined) return undefined; + } + var d = new Fet(d,g,s,ratio,name,'p'); + return this.add_device(d, name); + } + + /////////////////////////////////////////////////////////////////////////////// // // Support for creating and solving a system of linear equations @@ -354,37 +510,146 @@ cktsim = (function() { } } - // add conductance between two nodes to matrix A. + // Allocate an NxM matrix + Circuit.prototype.make_mat = function(N,M) { + var mat = new Array(N); + for (var i = N - 1; i >= 0; --i) { + mat[i] = new Array(M); + for (var j = M - 1; j >= 0; --j) { + mat[i][j] = 0.0; + } + } + return mat; + } + + // Form b = scale*Mx + Circuit.prototype.matv_mult = function(M,x,b,scale) { + var n = M.length; + var m = M[0].length; + + if (n != b.length || m != x.length) + { throw 'Rows of M mismatched to b or cols mismatch to x.'; + } + for (var i = 0; i < n; i++) + { + var temp = 0; + for (var j = 0; j < m; j++) + { + temp += M[i][j]*x[j]; + } + b[i] = scale*temp; // Recall the neg in the name + } + } + + // Form C = A + scale*B + Circuit.prototype.mat_scale_add = function(A, B, scale, C) { + var n = A.length; + var m = A[0].length; + + if (n > B.length || m > B[0].length) + { throw 'Row or columns of A to large for B'; + } + if (n > C.length || m > C[0].length) + { throw 'Row or columns of A to large for C'; + } + for (var i = 0; i < n; i++) + { + for (var j = 0; j < m; j++) + { + C[i][j] = A[i][j] + scale * B[i][j]; + } + } + } + + + // Copy A -> using the bounds of A + Circuit.prototype.copy_mat = function(src,dest) { + var n = src.length; + var m = src[0].length; + if (n > dest.length || m > dest[0].length) + { throw 'Rows or cols > rows or cols of dest'; + } + + for (var i = 0; i < n; i++) + { + for (var j = 0; j < m; j++) + { + dest[i][j] = src[i][j]; + } + } + } + + // add val component between two nodes to matrix M // Index of -1 refers to ground node - Circuit.prototype.add_conductance = function(i,j,g) { + Circuit.prototype.add_two_terminal = function(i,j,g,M) { if (i >= 0) { - this.matrix[i][i] += g; + M[i][i] += g; if (j >= 0) { - this.matrix[i][j] -= g; - this.matrix[j][i] -= g; - this.matrix[j][j] += g; + M[i][j] -= g; + M[j][i] -= g; + M[j][j] += g; } } else if (j >= 0) - this.matrix[j][j] += g; + M[j][j] += g; } - // add individual conductance to A - Circuit.prototype.add_to_A = function(i,j,v) { + // add val component between two nodes to matrix M + // Index of -1 refers to ground node + Circuit.prototype.get_two_terminal = function(i,j,x) { + var xi_minus_xj = 0; + if (i >= 0) xi_minus_xj = x[i]; + if (j >= 0) xi_minus_xj -= x[j]; + return xi_minus_xj + } + + Circuit.prototype.add_conductance_l = function(i,j,g) { + this.add_two_terminal(i,j,g, this.Gl) + } + + Circuit.prototype.add_conductance = function(i,j,g) { + this.add_two_terminal(i,j,g, this.G) + } + + Circuit.prototype.add_capacitance = function(i,j,c) { + this.add_two_terminal(i,j,c,this.C) + } + + // add individual conductance to Gl matrix + Circuit.prototype.add_to_Gl = function(i,j,g) { if (i >=0 && j >= 0) - this.matrix[i][j] += v; + this.Gl[i][j] += g; } - // add source info to vector b - Circuit.prototype.add_to_b = function(i,v) { - if (i >= 0) this.matrix[i][this.N] += v; + // add individual conductance to Gl matrix + Circuit.prototype.add_to_G = function(i,j,g) { + if (i >=0 && j >= 0) + this.G[i][j] += g; } - // solve Ax=b and return vector x given augmented matrix [A | b] + // add individual capacitance to C matrix + Circuit.prototype.add_to_C = function(i,j,c) { + if (i >=0 && j >= 0) + this.C[i][j] += c; + } + + // add source info to rhs + Circuit.prototype.add_to_rhs = function(i,v,rhs) { + if (i >= 0) rhs[i] += v; + } + + // solve Ax=b and return vector x given augmented matrix M = [A | b] // Uses Gaussian elimination with partial pivoting - function solve_linear_system(M) { + function solve_linear_system(M,rhs) { var N = M.length; // augmented matrix M has N rows, N+1 columns var temp,i,j; + // Copy the rhs in to the last column of M if one is given. + if (rhs != null) { + for (var row = 0; row < N ; row++) { + M[row][N] = rhs[row]; + } + } + // gaussian elimination for (var col = 0; col < N ; col++) { // find pivot: largest abs(v) in this column of remaining rows @@ -447,13 +712,13 @@ cktsim = (function() { Device.prototype.finalize = function() { } - // reset internal state of the device to initial value - Device.prototype.reset = function() { + // Load the linear elements in to Gl and C + Device.prototype.load_linear = function(ckt) { } // load linear system equations for dc analysis // (inductors shorted and capacitors opened) - Device.prototype.load_dc = function(ckt) { + Device.prototype.load_dc = function(ckt,soln,rhs) { } // load linear system equations for tran analysis @@ -463,11 +728,7 @@ cktsim = (function() { // load linear system equations for ac analysis: // current sources open, voltage sources shorted // linear models at operating point for everyone else - Device.prototype.load_ac = function(ckt) { - } - - // called with there's an accepted time step - Device.prototype.end_of_timestep = function(ckt) { + Device.prototype.load_ac = function(ckt,rhs) { } // return time of next breakpoint for the device @@ -616,7 +877,7 @@ cktsim = (function() { else if (s.charAt(index) == 'i' && s.charAt(index+1) == 'l') result *= 25.4e-6; } else result *= 1e-3; - } else return default_v; + } } // ignore any remaining chars, eg, 1kohms returns 1000 return result; @@ -640,7 +901,7 @@ cktsim = (function() { // value(t) -- compute source value at time t // inflection_point(t) -- compute time after t when a time point is needed // dc -- value at time 0 - + function parse_source(v) { // generic parser: parse v as either or (,...) var src = new Object(); @@ -731,10 +992,10 @@ cktsim = (function() { // return value of source at time t src.value = function(t) { // closure - if (t < td) return voffset + Math.sin(2*Math.PI*phase); + if (t < td) return voffset + va*Math.sin(2*Math.PI*phase); else { t -= td; - return voffset + Math.sin(2*Math.PI*(freq*(t - td) + phase)); + return voffset + va*Math.sin(2*Math.PI*(freq*(t - td) + phase)); } } @@ -774,35 +1035,34 @@ cktsim = (function() { // /////////////////////////////////////////////////////////////////////////////// - function VSource(npos,nneg,branch,v) { + function VSource(npos,nneg,branch,v) { Device.call(this); - + this.src = parse_source(v); this.npos = npos; this.nneg = nneg; this.branch = branch; } VSource.prototype = new Device(); - VSource.prototype.construction = VSource; + VSource.prototype.constructor = VSource; - // load linear system equations for dc analysis - VSource.prototype.load_dc = function(ckt) { + // load linear part for source evaluation + VSource.prototype.load_linear = function(ckt) { // MNA stamp for independent voltage source - ckt.add_to_A(this.branch,this.npos,1.0); - ckt.add_to_A(this.branch,this.nneg,-1.0); - ckt.add_to_A(this.npos,this.branch,1.0); - ckt.add_to_A(this.nneg,this.branch,-1.0); - ckt.add_to_b(this.branch,this.src.dc); + ckt.add_to_Gl(this.branch,this.npos,1.0); + ckt.add_to_Gl(this.branch,this.nneg,-1.0); + ckt.add_to_Gl(this.npos,this.branch,1.0); + ckt.add_to_Gl(this.nneg,this.branch,-1.0); } - // load linear system equations for tran analysis (just like DC) - VSource.prototype.load_tran = function(ckt,soln) { - // MNA stamp for independent voltage source - ckt.add_to_A(this.branch,this.npos,1.0); - ckt.add_to_A(this.branch,this.nneg,-1.0); - ckt.add_to_A(this.npos,this.branch,1.0); - ckt.add_to_A(this.nneg,this.branch,-1.0); - ckt.add_to_b(this.branch,this.src.value(ckt.time)); + // Source voltage added to b. + VSource.prototype.load_dc = function(ckt,soln,rhs) { + ckt.add_to_rhs(this.branch,this.src.dc,rhs); + } + + // Load time-dependent value for voltage source for tran + VSource.prototype.load_tran = function(ckt,soln,rhs,time) { + ckt.add_to_rhs(this.branch,this.src.value(time),rhs); } // return time of next breakpoint for the device @@ -810,9 +1070,9 @@ cktsim = (function() { return this.src.inflection_point(time); } - // small signal model: short circuit - VSource.prototype.load_ac = function(ckt) { - this.load_dc(ckt); + // small signal model ac value + VSource.prototype.load_ac = function(ckt,rhs) { + ckt.add_to_rhs(this.branch,1.0,rhs); } function ISource(npos,nneg,v) { @@ -823,24 +1083,24 @@ cktsim = (function() { this.nneg = nneg; } ISource.prototype = new Device(); - ISource.prototype.construction = ISource; + ISource.prototype.constructor = ISource; // load linear system equations for dc analysis - ISource.prototype.load_dc = function(ckt) { - var i = this.src.dc; + ISource.prototype.load_dc = function(ckt,soln,rhs) { + var is = this.src.dc; // MNA stamp for independent current source - ckt.add_to_b(this.npos,-i); // current flow into npos - ckt.add_to_b(this.nneg,i); // and out of nneg + ckt.add_to_rhs(this.npos,-is,rhs); // current flow into npos + ckt.add_to_rhs(this.nneg,is,rhs); // and out of nneg } // load linear system equations for tran analysis (just like DC) - ISource.prototype.load_tran = function(ckt,soln) { - var i = this.src.value(ckt.time); + ISource.prototype.load_tran = function(ckt,soln,rhs,time) { + var is = this.src.value(time); // MNA stamp for independent current source - ckt.add_to_b(this.npos,-i); // current flow into npos - ckt.add_to_b(this.nneg,i); // and out of nneg + ckt.add_to_rhs(this.npos,-is,rhs); // current flow into npos + ckt.add_to_rhs(this.nneg,is,rhs); // and out of nneg } // return time of next breakpoint for the device @@ -850,7 +1110,9 @@ cktsim = (function() { // small signal model: open circuit ISource.prototype.load_ac = function(ckt) { - this.load_dc(ckt); + // MNA stamp for independent current source + ckt.add_to_rhs(this.npos,-1.0,rhs); // current flow into npos + ckt.add_to_rhs(this.nneg,1.0,rhs); // and out of nneg } /////////////////////////////////////////////////////////////////////////////// @@ -866,21 +1128,65 @@ cktsim = (function() { this.g = 1.0/v; } Resistor.prototype = new Device(); - Resistor.prototype.construction = Resistor; + Resistor.prototype.constructor = Resistor; + + Resistor.prototype.load_linear = function(ckt) { + // MNA stamp for admittance g + ckt.add_conductance_l(this.n1,this.n2,this.g); + } Resistor.prototype.load_dc = function(ckt) { - // MNA stamp for admittance g - ckt.add_conductance(this.n1,this.n2,this.g); + // Nothing to see here, move along. } Resistor.prototype.load_tran = function(ckt,soln) { - this.load_dc(ckt); } Resistor.prototype.load_ac = function(ckt) { - this.load_dc(ckt); } + /////////////////////////////////////////////////////////////////////////////// + // + // Diode + // + /////////////////////////////////////////////////////////////////////////////// + + function Diode(n1,n2,v) { + Device.call(this); + this.anode = n1; + this.cathode = n2; + this.area = v; + this.is = 1.0e-14; + this.ais = this.area * this.is; + this.vt = 2.58e-2; // 26 millivolts + } + Diode.prototype = new Device(); + Diode.prototype.constructor = Diode; + + Diode.prototype.load_linear = function(ckt) { + // Diode is not linear, has no linear piece. + } + + Diode.prototype.load_dc = function(ckt,soln,rhs) { + var vd = ckt.get_two_terminal(this.anode, this.cathode, soln); + var temp1 = this.ais * Math.exp(vd / this.vt); + var id = temp1 - this.ais; + var gd = temp1 / this.vt + + // MNA stamp for independent current source + ckt.add_to_rhs(this.anode,-id,rhs); // current flows into anode + ckt.add_to_rhs(this.cathode,id,rhs); // and out of cathode + ckt.add_conductance(this.anode,this.cathode,gd); + } + + Diode.prototype.load_tran = function(ckt,soln,rhs,time) { + this.load_dc(ckt,soln,rhs); + } + + Diode.prototype.load_ac = function(ckt) { + } + + /////////////////////////////////////////////////////////////////////////////// // // Capacitor @@ -894,53 +1200,20 @@ cktsim = (function() { this.value = v; } Capacitor.prototype = new Device(); - Capacitor.prototype.construction = Capacitor; + Capacitor.prototype.constructor = Capacitor; - // capacitor is modeled as a current source (ieq) in parallel with a conductance (geq) - - Capacitor.prototype.reset = function() { - this.q = 0; // state variable (charge) - this.i = 0; // dstate/dt (current) - this.prev_q = 0; // last iteration - this.prev_i = 0; + Capacitor.prototype.load_linear = function(ckt) { + // MNA stamp for capacitance matrix + ckt.add_capacitance(this.n1,this.n2,this.value); } - Capacitor.prototype.finalize = function(ckt) { - // call us at the end of each timestep - ckt.end_of_timestep.push(this); - } - - Capacitor.prototype.end_of_timestep = function(ckt) { - // update state when timestep is accepted - this.prev_q = this.q; - this.prev_i = this.i; - } - - Capacitor.prototype.load_dc = function(ckt) { - // open circuit - } - - Capacitor.prototype.load_tran = function(ckt,soln) { - var vcap = ((this.n1 >= 0) ? soln[this.n1] : 0) - ((this.n2 >= 0) ? soln[this.n2] : 0); - this.q = this.value * vcap; // set charge - - // integrate - // for backward Euler: coeff0 = 1/timestep, coeff1 = 0 - // for trapezoidal: coeff0 = 2/timestep, coeff1 = 1 - this.i = ckt.coeff0*(this.q - this.prev_q) - ckt.coeff1*this.prev_i; - var ieq = this.i - ckt.coeff0*this.q; - var geq = ckt.coeff0 * this.value; - - // MNA stamp for admittance geq - ckt.add_conductance(this.n1,this.n2,geq); - - // MNA stamp for current source ieq - ckt.add_to_b(this.n1,-ieq); - ckt.add_to_b(this.n2,ieq); + Capacitor.prototype.load_dc = function(ckt,soln,rhs) { } Capacitor.prototype.load_ac = function(ckt) { - ckt.add_conductance(this.n1,this.n2,ckt.omega * this.value); + } + + Capacitor.prototype.load_tran = function(ckt) { } /////////////////////////////////////////////////////////////////////////////// @@ -957,63 +1230,100 @@ cktsim = (function() { this.value = v; } Inductor.prototype = new Device(); - Inductor.prototype.construction = Inductor; + Inductor.prototype.constructor = Inductor; - // inductor is modeled as a voltage source (veq) with impedance (geq) - - Inductor.prototype.reset = function() { - this.flux = 0; // state variable (flux) - this.v = 0; // dstate/dt (voltage) - this.prev_flux = 0; // last iteration - this.prev_v = 0; + Inductor.prototype.load_linear = function(ckt) { + // MNA stamp for inductor linear part + // L on diag of C because L di/dt = v(n1) - v(n2) + ckt.add_to_Gl(this.n1,this.branch,1); + ckt.add_to_Gl(this.branch,this.n1,1); + ckt.add_to_Gl(this.n2,this.branch,-1); + ckt.add_to_Gl(this.branch,this.n2,-1); + ckt.add_to_C(this.branch,this.branch,this.value) } - Inductor.prototype.finalize = function(ckt) { - // call us at the end of each timestep - ckt.end_of_timestep.push(this); - } - - Inductor.prototype.end_of_timestep = function(ckt) { - // update state when timestep is accepted - this.prev_flux = this.flux; - this.prev_v = this.v; - } - - Inductor.prototype.load_dc = function(ckt) { - // short circuit: veq = 0, req = 0 - ckt.add_to_A(this.n1,this.branch,1); - ckt.add_to_A(this.branch,this.n1,1); - ckt.add_to_A(this.n2,this.branch,-1); - ckt.add_to_A(this.branch,this.n2,-1); - } - - Inductor.prototype.load_tran = function(ckt,soln) { - this.flux = this.value * soln[this.branch]; // set flux - - // integrate - // for backward Euler: coeff0 = 1/timestep, coeff1 = 0 - // for trapezoidal: coeff0 = 2/timestep, coeff1 = 1 - this.v = ckt.coeff0*(this.flux - this.prev_flux) - ckt.coeff1*this.prev_v; - var veq = this.v - ckt.coeff0*this.flux; - var req = ckt.coeff0 * this.value; - - // MNA stamp for voltage source with impedance - ckt.add_to_b(this.branch,veq); - ckt.add_to_A(this.branch,this.branch,-req); - ckt.add_to_A(this.n1,this.branch,1); - ckt.add_to_A(this.branch,this.n1,1); - ckt.add_to_A(this.n2,this.branch,-1); - ckt.add_to_A(this.branch,this.n2,-1); + Inductor.prototype.load_dc = function(ckt,soln,rhs) { + // Inductor is a short at dc, so is linear. } Inductor.prototype.load_ac = function(ckt) { - ckt.add_to_A(this.branch,this.branch,-ckt.omega * this.value); - ckt.add_to_A(this.n1,this.branch,1); - ckt.add_to_A(this.branch,this.n1,1); - ckt.add_to_A(this.n2,this.branch,-1); - ckt.add_to_A(this.branch,this.n2,-1); } + Inductor.prototype.load_tran = function(ckt) { + } + + /////////////////////////////////////////////////////////////////////////////// + // + // Simplified MOS FET with no bulk connection and no body effect. + // + /////////////////////////////////////////////////////////////////////////////// + + + function Fet(d,g,s,ratio,name,type) { + Device.call(this); + this.d = d; + this.g = g; + this.s = s; + this.name = name; + this.ratio = ratio; + if (type != 'n' && type != 'p') + { throw 'fet type is not n or p'; + } + this.type_sign = (type == 'n') ? 1 : -1; + this.vt = 0.5; + this.kp = 20e-6; + this.beta = this.kp * this.ratio; + this.lambda = 0.05; + } + Fet.prototype = new Device(); + Fet.prototype.constructor = Fet; + + Fet.prototype.load_linear = function(ckt) { + // FET's are nonlinear, just like javascript progammers + } + + Fet.prototype.load_dc = function(ckt,soln,rhs) { + var vds = this.type_sign * ckt.get_two_terminal(this.d, this.s, soln); + if (vds < 0) { // Drain and source have swapped roles + var temp = this.d; + this.d = this.s; + this.s = temp; + vds = this.type_sign * ckt.get_two_terminal(this.d, this.s, soln); + } + var vgs = this.type_sign * ckt.get_two_terminal(this.g, this.s, soln); + var vgst = vgs - this.vt; + with (this) { + var gmgs,ids,gds; + if (vgst > 0.0 ) { // vgst < 0, transistor off, no subthreshold here. + if (vgst < vds) { /* Saturation. */ + gmgs = beta * (1 + (lambda * vds)) * vgst; + ids = type_sign * 0.5 * gmgs * vgst; + gds = 0.5 * beta * vgst * vgst * lambda; + } else { /* Linear region */ + gmgs = beta * (1 + lambda * vds); + ids = type_sign * gmgs * vds * (vgst - 0.50 * vds); + gds = gmgs * (vgst - vds) + beta * lambda * vds * (vgst - 0.5 * vds); + gmgs *= vds; + } + ckt.add_to_rhs(d,-ids,rhs); // current flows into the drain + ckt.add_to_rhs(s, ids,rhs); // and out the source + ckt.add_conductance(d,s,gds); + ckt.add_to_G(s,s, gmgs); + ckt.add_to_G(d,s,-gmgs); + ckt.add_to_G(d,g, gmgs); + ckt.add_to_G(s,g,-gmgs); + } + } + } + + Fet.prototype.load_tran = function(ckt,soln,rhs) { + this.load_dc(ckt,soln,rhs); + } + + Fet.prototype.load_ac = function(ckt) { + } + + /////////////////////////////////////////////////////////////////////////////// // // Module definition @@ -1021,6 +1331,7 @@ cktsim = (function() { /////////////////////////////////////////////////////////////////////////////// var module = { 'Circuit': Circuit, + 'parse_number': parse_number, } return module; }()); diff --git a/js/schematic.js b/js/schematic.js index 2febce050d..ed02627002 100644 --- a/js/schematic.js +++ b/js/schematic.js @@ -1,4 +1,4 @@ -////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////// // // Simple schematic capture // @@ -6,24 +6,6 @@ // Copyright (C) 2011 Massachusetts Institute of Technology -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -// of the Software, and to permit persons to whom the Software is furnished to do -// so, subject to the following conditions: - -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. - -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - // add schematics to a document with // // @@ -72,6 +54,12 @@ function add_schematic_handler(other_onload) { } window.onload = add_schematic_handler(window.onload); +// ask each schematic input widget to update its value field for submission +function prepare_schematics() { + var schematics = document.getElementsByClassName('schematic'); + for (var i = schematics.length - 1; i >= 0; i--) + schematics[i].schematic.update_value(); +} schematic = (function() { background_style = 'rgb(220,220,220)'; @@ -88,6 +76,7 @@ schematic = (function() { // list of all the defined parts parts_map = { 'g': [Ground, 'Ground connection'], + 'L': [Label, 'Node label'], 'v': [VSource, 'Voltage source'], 'i': [ISource, 'Current source'], 'r': [Resistor, 'Resistor'], @@ -100,10 +89,6 @@ schematic = (function() { 's': [Probe, 'Scope Probe'], }; - // fix cursor bug in Chrome (default behavior: change to text cursor - // whenever a drag is initiated). - //document.onselectstart = function() { return false; }; - /////////////////////////////////////////////////////////////////////////////// // // Schematic = diagram + parts bin + status area @@ -170,16 +155,19 @@ schematic = (function() { } if (analyses.indexOf('ac') != -1) { - this.tools['ac'] = this.add_tool('AC','AC Small-Signal Analysis',this.ac_analysis); + this.tools['ac'] = this.add_tool('AC','AC Small-Signal Analysis',this.setup_ac_analysis); this.enable_tool('ac',true); this.ac_npts = '5'; // default values for AC Analysis this.ac_fstart = '10'; this.ac_fstop = '10MEG'; + this.ac_source_name = undefined; } if (analyses.indexOf('tran') != -1) { - //this.tools['tran'] = this.add_tool('TRAN','Transient Analysis',this.tran_analysis); - //this.enable_tool('tran',true); + this.tools['tran'] = this.add_tool('TRAN','Transient Analysis',this.transient_analysis); + this.enable_tool('tran',true); + this.tran_npts = '100'; // default values for transient analysis + this.tran_tstop = '1'; } } @@ -238,6 +226,9 @@ schematic = (function() { this.wire = undefined; this.operating_point = undefined; // result from DC analysis + this.dc_results = undefined; // saved analysis results for submission + this.ac_results = undefined; // saved analysis results for submission + this.transient_results = undefined; // saved analysis results for submission // state of modifier keys this.ctrlKey = false; @@ -252,10 +243,12 @@ schematic = (function() { // set up DOM -- use nested tables to do the layout var table,tr,td; table = document.createElement('table'); + table.rules = 'none'; if (!this.diagram_only) { + table.frame = 'box'; table.style.borderStyle = 'solid'; table.style.borderWidth = '2px'; - table.style.padding = '5px'; + table.style.borderColor = normal_style; table.style.backgroundColor = background_style; } @@ -264,8 +257,7 @@ schematic = (function() { tr = document.createElement('tr'); table.appendChild(tr); td = document.createElement('td'); - td.colspan = 2; - td.vAlign = 'baseline'; + td.colSpan = 2; tr.appendChild(td); for (var i = 0; i < this.toolbar.length; ++i) { var tool = this.toolbar[i]; @@ -284,9 +276,13 @@ schematic = (function() { tr.appendChild(td); var parts_table = document.createElement('table'); td.appendChild(parts_table); + parts_table.rules = 'none'; + parts_table.frame = 'void'; + parts_table.cellPadding = '0'; + parts_table.cellSpacing = '0'; - // fill in parts_table here!!! - var parts_per_column = Math.floor(this.height / part_h); + // fill in parts_table + var parts_per_column = Math.floor(this.height / (part_h + 5)); // mysterious extra padding for (var i = 0; i < parts_per_column; ++i) { tr = document.createElement('tr'); parts_table.appendChild(tr); @@ -302,7 +298,7 @@ schematic = (function() { table.appendChild(tr); td = document.createElement('td'); tr.appendChild(td); - td.colspan = 2; + td.colSpan = 2; td.appendChild(this.status_div); } @@ -502,9 +498,17 @@ schematic = (function() { this.origin_x = c[1]; this.origin_y = c[2]; this.scale = c[3]; + this.ac_npts = c[4]; + this.ac_fstart = c[5]; + this.ac_fstop = c[6]; + this.ac_source_name = c[7]; + this.tran_npts = c[8]; + this.tran_tstop = c[9]; } else if (c[0] == 'w') { // wire this.add_wire(c[1][0],c[1][1],c[1][2],c[1][3]); + } else if (c[0] == 'dc' || c[0] == 'ac' || c[0] == 'transient') { + // ignore analysis results } else { // ordinary component // c := [type, coords, properties, connections] @@ -520,8 +524,8 @@ schematic = (function() { part.properties[name] = properties[name]; // add component to the diagram - part.add(this) - } + part.add(this); + } } } @@ -571,7 +575,7 @@ schematic = (function() { // build JSON data structure, convert to string value for // input field - this.input.value = JSON.stringify(this.json()); + this.input.value = JSON.stringify(this.json_with_analyses()); } // produce a JSON representation of the diagram @@ -583,7 +587,20 @@ schematic = (function() { json.push(this.components[i].json()); // capture the current view parameters - json.push(['view',this.origin_x,this.origin_y,this.scale]); + json.push(['view',this.origin_x,this.origin_y,this.scale, + this.ac_npts,this.ac_fstart,this.ac_fstop,this.ac_source_name, + this.tran_npts,this.tran_tstop]); + + return json; + } + + // produce a JSON representation of the diagram + Schematic.prototype.json_with_analyses = function() { + var json = this.json(); + + if (this.dc_results != undefined) json.push(['dc',this.dc_results]); + if (this.ac_results != undefined) json.push(['ac',this.ac_results]); + if (this.transient_results != undefined) json.push(['transient',this.transient_results]); return json; } @@ -599,6 +616,10 @@ schematic = (function() { this.label_connection_points(); var netlist = this.json(); + // since we've done the heavy lifting, update input field value + // so user can grab diagram if they want + this.input.value = JSON.stringify(netlist); + // create a circuit from the netlist var ckt = new cktsim.Circuit(); ckt.load_netlist(netlist); @@ -616,6 +637,10 @@ schematic = (function() { // run the analysis this.operating_point = ckt.dc(); + // save a copy of the results for submission + this.dc_results = {}; + for (var i in this.operating_point) this.dc_results[i] = this.operating_point[i]; + // display results on diagram this.redraw(); } @@ -630,16 +655,18 @@ schematic = (function() { return result; } - Schematic.prototype.ac_analysis = function() { + // use a dialog to get AC analysis parameters + Schematic.prototype.setup_ac_analysis = function() { this.unselect_all(-1); this.redraw_background(); var npts_lbl = 'Number of points/decade'; var fstart_lbl = 'Starting frequency (Hz)'; var fstop_lbl = 'Ending frequency (Hz)'; + var source_name_lbl = 'Name of V or I source for ac' if (this.find_probes().length == 0) { - this.message("AC Analysis: there are no scope probes in the diagram!"); + alert("AC Analysis: there are no scope probes in the diagram!"); return; } @@ -647,6 +674,7 @@ schematic = (function() { fields[npts_lbl] = build_input('text',10,this.ac_npts); fields[fstart_lbl] = build_input('text',10,this.ac_fstart); fields[fstop_lbl] = build_input('text',10,this.ac_fstop); + fields[source_name_lbl] = build_input('text',10,this.ac_source_name); var content = build_table(fields); content.fields = fields; @@ -654,26 +682,103 @@ schematic = (function() { this.dialog('AC Analysis',content,function(content) { var sch = content.sch; - var ckt = sch.extract_circuit(); // retrieve parameters, remember for next time sch.ac_npts = content.fields[npts_lbl].value; sch.ac_fstart = content.fields[fstart_lbl].value; sch.ac_fstop = content.fields[fstop_lbl].value; + sch.ac_source_name = content.fields[source_name_lbl].value; + + sch.ac_analysis(cktsim.parse_number(sch.ac_npts), + cktsim.parse_number(sch.ac_fstart), + cktsim.parse_number(sch.ac_fstop), + sch.ac_source_name); + }); + } + + // perform ac analysis + Schematic.prototype.ac_analysis = function(npts,fstart,fstop,ac_source_name) { + // run the analysis + var ckt = this.extract_circuit(); + var results = ckt.ac(npts,fstart,fstop,ac_source_name); + + // save a copy of the results for submission + this.ac_results = {}; + for (var i in results) this.ac_results[i] = results[i]; + + if (typeof results == 'string') + this.message(results); + else { + var x_values = results['frequencies']; + + // x axis will be a log scale + for (var i = x_values.length - 1; i >= 0; --i) + x_values[i] = Math.log(x_values[i])/Math.LN10; + + // set up plot values for each node with a probe + var y_values = []; // list of [color, result_array] + var probes = this.find_probes(); + + for (var i = probes.length - 1; i >= 0; --i) { + var color = probes[i][0]; + var label = probes[i][1]; + var v = results[label]; + + // convert values into dB relative to source amplitude + var v_max = 1; + for (var j = v.length - 1; j >= 0; --j) + // convert each value to dB relative to max + v[j] = 20.0 * Math.log(v[j]/v_max)/Math.LN10; + + y_values.push([color,v]); + } + + // graph the result and display in a window + var graph = this.graph(x_values,y_values,'log(Frequency)','dB'); + this.window('Results of AC Analysis',graph); + } + } + + Schematic.prototype.transient_analysis = function() { + this.unselect_all(-1); + this.redraw_background(); + + var npts_lbl = 'Minimum number of timepoints'; + var tstop_lbl = 'Stop Time (seconds)'; + + if (this.find_probes().length == 0) { + alert("Transient Analysis: there are no probes in the diagram!"); + return; + } + + var fields = new Array(); + fields[npts_lbl] = build_input('text',10,this.tran_npts); + fields[tstop_lbl] = build_input('text',10,this.tran_tstop); + + var content = build_table(fields); + content.fields = fields; + content.sch = this; + + this.dialog('Transient Analysis',content,function(content) { + var sch = content.sch; + var ckt = sch.extract_circuit(); + + // retrieve parameters, remember for next time + sch.tran_npts = content.fields[npts_lbl].value; + sch.tran_tstop = content.fields[tstop_lbl].value; // run the analysis - var results = ckt.ac(ckt.parse_number(sch.ac_npts), - ckt.parse_number(sch.ac_fstart), - ckt.parse_number(sch.ac_fstop)); + var results = ckt.tran(ckt.parse_number(sch.tran_npts), 0, + ckt.parse_number(sch.tran_tstop), false); + + // save a copy of the results for submission + this.transient_results = {}; + for (var i in results) this.transient_results[i] = results[i]; if (typeof results == 'string') sch.message(results); else { - var x_values = results['frequencies']; - - // x axis will be a log scale - for (var i = x_values.length - 1; i >= 0; --i) - x_values[i] = Math.log(x_values[i])/Math.LN10; + var x_values = results['time']; // set up plot values for each node with a probe var y_values = []; // list of [color, result_array] @@ -683,28 +788,30 @@ schematic = (function() { var color = probes[i][0]; var label = probes[i][1]; var v = results[label]; - - // convert values into dB relative to max value - var v_max = -Infinity; - for (var j = v.length - 1; j >= 0; --j) { - v[j] = Math.abs(v[j]); - if (v[j] > v_max) v_max = v[j]; - } - for (var j = v.length - 1; j >= 0; --j) - // convert each value to dB relative to max - v[j] = 20.0 * Math.log(v[j]/v_max)/Math.LN10; - y_values.push([color,v]); } // graph the result and display in a window - var graph = sch.graph(x_values,y_values,'log(Frequency)','dB'); - sch.window('Results of AC Analysis',graph); + var graph = sch.graph(x_values,y_values,'Time','Voltage'); + sch.window('Results of Transient Analysis',graph); } - }) + }) } - Schematic.prototype.transient_analysis = function() { + // external interface for setting the property value of a named component + Schematic.prototype.set_property = function(component_name,property,value) { + this.unselect_all(-1); + + for (var i = this.components.length - 1; i >= 0; --i) { + var component = this.components[i]; + if (component.properties['name'] == component_name) { + component.properties[property] = value.toString(); + break; + } + } + + // update diagram + this.redraw_background(); } /////////////////////////////////////////////////////////////////////////////// @@ -720,11 +827,11 @@ schematic = (function() { c.lineCap = 'round'; - if (!this.diagram_only) { - // paint background color - c.fillStyle = element_style; - c.fillRect(0,0,this.width,this.height); + // paint background color + c.fillStyle = element_style; + c.fillRect(0,0,this.width,this.height); + if (!this.diagram_only) { // grid c.strokeStyle = grid_style; var first_x = 0; @@ -1204,6 +1311,7 @@ schematic = (function() { // div to hold the two buttons var buttons = document.createElement('div'); + buttons.style.textAlign = 'center'; buttons.appendChild(ok_button); buttons.appendChild(cancel_button); buttons.style.padding = '5px'; @@ -1315,9 +1423,9 @@ schematic = (function() { head.addEventListener('mousemove',window_mouse_move,false); // div to hold the content - var body = document.createElement('div'); - body.appendChild(content); - win.appendChild(body); + //var body = document.createElement('div'); + //body.appendChild(content); + win.appendChild(content); content.win = win; // so content can contact us // compute location in top-level div @@ -1415,7 +1523,6 @@ schematic = (function() { tool.style.borderWidth = '1px'; tool.style.borderStyle = 'solid'; tool.style.borderColor = background_style; - //tool.style.position = 'absolute'; tool.style.padding = '2px'; // set up event processing @@ -1552,6 +1659,45 @@ schematic = (function() { return [vmin,vmax,1.0/scale]; } + function engineering_notation(n,nplaces) { + if (n == 0) return("0"); + + var sign = n < 0 ? -1 : 1; + var log10 = Math.log(sign*n)/Math.LN10; + var exp = Math.floor(log10/3); // powers of 1000 + var mantissa = sign*Math.pow(10,log10 - 3*exp); + + // keep specified number of places following decimal point + var mstring = (mantissa + sign*0.5*Math.pow(10,-nplaces)).toString(); + var mlen = mstring.length; + var endindex = mstring.indexOf('.'); + if (endindex != -1) { + if (nplaces > 0) { + endindex += nplaces + 1; + if (endindex > mlen) endindex = mlen; + while (mstring.charAt(endindex-1) == '0') endindex -= 1; + if (mstring.charAt(endindex-1) == '.') endindex -= 1; + } + if (endindex < mlen) + mstring = mstring.substring(0,endindex); + } + + switch(exp) { + case -5: return mstring+"f"; + case -4: return mstring+"p"; + case -3: return mstring+"n"; + case -2: return mstring+"u"; + case -1: return mstring+"m"; + case 0: return mstring; + case 1: return mstring+"K"; + case 2: return mstring+"Meg"; + case 3: return mstring+"G"; + } + + // don't have a good suffix, so just print the number + return n.toString(); + } + // x_values is an array of x coordinates for each of the plots // y_values is an array of [color, value_array], one entry for each plot Schematic.prototype.graph = function(x_values,y_values,x_legend,y_legend) { @@ -1622,7 +1768,7 @@ schematic = (function() { c.moveTo(temp,end); c.lineTo(temp,end + tick_length); c.stroke(); - c.fillText(x.toString(),temp,end + tick_length); + c.fillText(engineering_notation(x,2),temp,end + tick_length); } var y_min = Infinity; @@ -1664,7 +1810,7 @@ schematic = (function() { c.moveTo(left_margin - tick_length,temp); c.lineTo(left_margin,temp); c.stroke(); - c.fillText(y.toString(),left_margin - tick_length -2,temp); + c.fillText(engineering_notation(y,2),left_margin - tick_length -2,temp); } // now draw each plot @@ -2149,7 +2295,7 @@ schematic = (function() { Component.prototype.select = function(x,y,shiftKey) { this.was_previously_selected = this.selected; - if (inside(this.bbox,x,y)) { + if (this.near(x,y)) { this.set_select(shiftKey ? !this.selected : true); return true; } else return false; @@ -2168,8 +2314,13 @@ schematic = (function() { return null; } + // does mouse click fall on this component? + Component.prototype.near = function(x,y) { + return inside(this.bbox,x,y); + } + Component.prototype.edit_properties = function(x,y) { - if (inside(this.bbox,x,y)) { + if (this.near(x,y)) { // make an widget for each property var fields = new Array(); for (var i in this.properties) @@ -2361,14 +2512,6 @@ schematic = (function() { return false; } - Wire.prototype.select = function(x,y,shiftKey) { - this.was_previously_selected = this.selected; - if (this.near(x,y)) { - this.set_select(shiftKey ? !this.selected : true); - return true; - } else return false; - } - // selection rectangle selects wire only if it includes // one of the end points Wire.prototype.select_rect = function(s) { @@ -2449,6 +2592,41 @@ schematic = (function() { this.connections[0].propagate_label('0'); // canonical label for GND node } + //////////////////////////////////////////////////////////////////////////////// + // + // Label + // + //////////////////////////////////////////////////////////////////////////////// + + function Label(x,y,rotation,label) { + Component.call(this,'L',x,y,rotation); + this.properties['label'] = label ? label : '???'; + this.add_connection(0,0); + this.bounding_box = [-2,0,2,8]; + this.update_coords(); + } + Label.prototype = new Component(); + Label.prototype.constructor = Label; + + Label.prototype.toString = function() { + return ''; + } + + Label.prototype.draw = function(c) { + this.draw_line(c,0,0,0,8); + this.draw_text(c,this.properties['label'],0,9,1,property_size); + } + + Label.prototype.clone = function(x,y) { + return new Label(x,y,this.rotation,this.properties['label']); + } + + // give components a chance to generate a label for their connection(s) + // default action: do nothing + Label.prototype.add_default_labels = function() { + this.connections[0].propagate_label(this.properties['label']); + } + //////////////////////////////////////////////////////////////////////////////// // // Scope Probe @@ -2538,7 +2716,7 @@ schematic = (function() { this.properties['r'] = r ? r : '1'; this.add_connection(0,0); this.add_connection(0,48); - this.bounding_box = [-4,0,4,48]; + this.bounding_box = [-5,0,5,48]; this.update_coords(); } Resistor.prototype = new Component(); @@ -2650,9 +2828,10 @@ schematic = (function() { // //////////////////////////////////////////////////////////////////////////////// - function Diode(x,y,rotation,name) { + function Diode(x,y,rotation,name,area) { Component.call(this,'d',x,y,rotation); this.properties['name'] = name; + this.properties['area'] = area ? area : '1'; this.add_connection(0,0); // anode this.add_connection(0,48); // cathode this.bounding_box = [-8,0,8,48]; @@ -2662,7 +2841,7 @@ schematic = (function() { Diode.prototype.constructor = Diode; Diode.prototype.toString = function() { - return ''; + return ''; } Diode.prototype.draw = function(c) { @@ -2673,12 +2852,14 @@ schematic = (function() { this.draw_line(c,-8,32,8,32); this.draw_line(c,0,32,0,48); + if (this.properties['area']) + this.draw_text(c,this.properties['area'],10,24,3,property_size); if (this.properties['name']) this.draw_text(c,this.properties['name'],-10,24,5,property_size); } Diode.prototype.clone = function(x,y) { - return new Diode(x,y,this.rotation,this.properties['name']); + return new Diode(x,y,this.rotation,this.properties['name'],this.properties['area']); } //////////////////////////////////////////////////////////////////////////////// @@ -2687,14 +2868,13 @@ schematic = (function() { // //////////////////////////////////////////////////////////////////////////////// - function NFet(x,y,rotation,name,sw,sl) { + function NFet(x,y,rotation,name,w_over_l) { Component.call(this,'n',x,y,rotation); this.properties['name'] = name; - this.properties['scaled width'] = sw ? sw : '2'; - this.properties['scaled length'] = sl ? sl : '1'; + this.properties['W/L'] = w_over_l ? w_over_l : '2'; this.add_connection(0,0); // drain - this.add_connection(0,48); // source this.add_connection(-24,24); // gate + this.add_connection(0,48); // source this.bounding_box = [-24,0,8,48]; this.update_coords(); } @@ -2702,7 +2882,7 @@ schematic = (function() { NFet.prototype.constructor = NFet; NFet.prototype.toString = function() { - return ''; + return ''; } NFet.prototype.draw = function(c) { @@ -2715,7 +2895,7 @@ schematic = (function() { this.draw_line(c,-24,24,-12,24); this.draw_line(c,-12,16,-12,32); - var dim = this.properties['scaled width']+'/'+this.properties['scaled length']; + var dim = this.properties['W/L']; if (this.properties['name']) { this.draw_text(c,this.properties['name'],2,22,6,property_size); this.draw_text(c,dim,2,26,0,property_size); @@ -2724,8 +2904,7 @@ schematic = (function() { } NFet.prototype.clone = function(x,y) { - return new NFet(x,y,this.rotation,this.properties['name'], - this.properties['scaled width'],this.properties['scaled length']); + return new NFet(x,y,this.rotation,this.properties['name'],this.properties['W/L']); } //////////////////////////////////////////////////////////////////////////////// @@ -2734,14 +2913,13 @@ schematic = (function() { // //////////////////////////////////////////////////////////////////////////////// - function PFet(x,y,rotation,name,sw,sl) { + function PFet(x,y,rotation,name,w_over_l) { Component.call(this,'p',x,y,rotation); this.properties['name'] = name; - this.properties['scaled width'] = sw ? sw : '2'; - this.properties['scaled length'] = sl ? sl : '1'; + this.properties['W/L'] = w_over_l ? w_over_l : '2'; this.add_connection(0,0); // drain - this.add_connection(0,48); // source this.add_connection(-24,24); // gate + this.add_connection(0,48); // source this.bounding_box = [-24,0,8,48]; this.update_coords(); } @@ -2749,7 +2927,7 @@ schematic = (function() { PFet.prototype.constructor = PFet; PFet.prototype.toString = function() { - return ''; + return ''; } PFet.prototype.draw = function(c) { @@ -2764,7 +2942,7 @@ schematic = (function() { this.draw_circle(c,-14,24,2,false); this.draw_line(c,-12,16,-12,32); - var dim = this.properties['scaled width']+'/'+this.properties['scaled length']; + var dim = this.properties['W/L']; if (this.properties['name']) { this.draw_text(c,this.properties['name'],2,22,6,property_size); this.draw_text(c,dim,2,26,0,property_size); @@ -2773,8 +2951,7 @@ schematic = (function() { } PFet.prototype.clone = function(x,y) { - return new PFet(x,y,this.rotation,this.properties['name'], - this.properties['scaled width'],this.properties['scaled length']); + return new PFet(x,y,this.rotation,this.properties['name'],this.properties['W/L']); } //////////////////////////////////////////////////////////////////////////////// @@ -2783,9 +2960,10 @@ schematic = (function() { // //////////////////////////////////////////////////////////////////////////////// - function OpAmp(x,y,rotation,name,sw,sl) { + function OpAmp(x,y,rotation,name,A) { Component.call(this,'o',x,y,rotation); this.properties['name'] = name; + this.properties['A'] = A ? A : '300000'; this.add_connection(0,0); // + this.add_connection(0,16); // - this.add_connection(48,8); // output @@ -2796,7 +2974,7 @@ schematic = (function() { OpAmp.prototype.constructor = OpAmp; OpAmp.prototype.toString = function() { - return ''; + return ''; } OpAmp.prototype.draw = function(c) { @@ -2818,7 +2996,7 @@ schematic = (function() { } OpAmp.prototype.clone = function(x,y) { - return new OpAmp(x,y,this.rotation,this.properties['name']); + return new OpAmp(x,y,this.rotation,this.properties['name'],this.properties['A']); } //////////////////////////////////////////////////////////////////////////////// @@ -2849,15 +3027,15 @@ schematic = (function() { this.draw_line(c,0,36,0,48); if (this.type == 'v') { // voltage source - this.draw_text(c,'+',0,12,1,property_size); - this.draw_text(c,'\u2013',0,36,7,property_size); // minus sign + //this.draw_text(c,'+',0,12,1,property_size); + //this.draw_text(c,'\u2013',0,36,7,property_size); // minus sign // draw + and - - //this.draw_line(c,8,5,8,11); - //this.draw_line(c,5,8,11,8); - //this.draw_line(c,5,40,11,40); + this.draw_line(c,0,15,0,21); + this.draw_line(c,-3,18,3,18); + this.draw_line(c,-3,30,3,30); // draw V - this.draw_line(c,-3,20,0,28); - this.draw_line(c,3,20,0,28); + //this.draw_line(c,-3,20,0,28); + //this.draw_line(c,3,20,0,28); } else if (this.type == 'i') { // current source // draw arrow: pos to neg this.draw_line(c,0,16,0,32); @@ -2895,13 +3073,51 @@ schematic = (function() { ISource.prototype.draw = Source.prototype.draw; ISource.prototype.clone = Source.prototype.clone; + /////////////////////////////////////////////////////////////////////////////// + // + // JQuery slider support for setting a component value + // + /////////////////////////////////////////////////////////////////////////////// + + function component_slider(event,ui) { + var sname = $(this).slider("option","schematic"); + + // set value of specified component + var cname = $(this).slider("option","component"); + var pname = $(this).slider("option","property"); + var suffix = $(this).slider("option","suffix"); + if (typeof suffix != "string") suffix = ""; + + var v = ui.value; + $(this).slider("value",v); // move slider's indicator + + var choices = $(this).slider("option","choices"); + if (choices instanceof Array) v = choices[v]; + + // selector may match several schematics + $("." + sname).each(function(index,element) { + element.schematic.set_property(cname,pname,v.toString() + suffix); + }) + + // perform requested analysis + var analysis = $(this).slider("option","analysis"); + if (analysis == "dc") + $("." + sname).each(function(index,element) { + element.schematic.dc_analysis(); + }) + + return false; + } + /////////////////////////////////////////////////////////////////////////////// // // Module definition // /////////////////////////////////////////////////////////////////////////////// + var module = { 'Schematic': Schematic, + 'component_slider': component_slider, } return module; }());