Commit a8e03a24 by Ernie Park

merge

parents c877e538 7600920f
...@@ -6,24 +6,6 @@ ...@@ -6,24 +6,6 @@
// Copyright (C) 2011 Massachusetts Institute of Technology // 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()" // create a circuit for simulation using "new cktsim.Circuit()"
// for modified nodal analysis (MNA) stamps see // for modified nodal analysis (MNA) stamps see
...@@ -41,10 +23,12 @@ cktsim = (function() { ...@@ -41,10 +23,12 @@ cktsim = (function() {
T_VOLTAGE = 0; T_VOLTAGE = 0;
T_CURRENT = 1; T_CURRENT = 1;
v_newt_lim = 0.3; // Voltage limited Newton great for Mos/diodes
v_abstol = 1e-6; // criterion for absolute convergence (voltage) v_abstol = 1e-6; // criterion for absolute convergence (voltage)
i_abstol = 1e-12; // criterion for absolute convergence (current) i_abstol = 1e-12; // criterion for absolute convergence (current)
min_time_step = 1e-18; // smallest possible time step 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 increase_limit = 4; // if we converge in this many iterations, increase time step
time_step_increase_factor = 2.0; time_step_increase_factor = 2.0;
time_step_decrease_factor = 0.3; time_step_decrease_factor = 0.3;
...@@ -57,9 +41,9 @@ cktsim = (function() { ...@@ -57,9 +41,9 @@ cktsim = (function() {
this.devices = []; // list of devices this.devices = []; // list of devices
this.device_map = new Array(); // map name -> device 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.finalized = false;
this.diddc = false;
this.node_index = -1; this.node_index = -1;
} }
...@@ -88,23 +72,21 @@ cktsim = (function() { ...@@ -88,23 +72,21 @@ cktsim = (function() {
this.devices[i].finalize(this); this.devices[i].finalize(this);
// set up augmented matrix and various temp vectors // 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.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.abstol = new Array(this.N);
this.solution = new Array(this.N); this.solution = new Array(this.N);
this.rhs = new Array(this.N);
for (var i = this.N - 1; i >= 0; --i) { for (var i = this.N - 1; i >= 0; --i) {
this.matrix[i] = new Array(this.N + 1);
this.soln_max[i] = 0.0; this.soln_max[i] = 0.0;
this.rtol[i] = 0.0;
this.abstol[i] = this.ntypes[i] == T_VOLTAGE ? v_abstol : i_abstol; this.abstol[i] = this.ntypes[i] == T_VOLTAGE ? v_abstol : i_abstol;
this.solution[i] = 0.0; 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() { ...@@ -119,7 +101,7 @@ cktsim = (function() {
var type = component[0]; var type = component[0];
// ignore wires, ground connections, scope probes and view info // 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 properties = component[2];
var name = properties['name']; var name = properties['name'];
...@@ -136,6 +118,8 @@ cktsim = (function() { ...@@ -136,6 +118,8 @@ cktsim = (function() {
// process the component // process the component
if (type == 'r') // resistor if (type == 'r') // resistor
this.r(connections[0],connections[1],properties['r'],name); 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 else if (type == 'c') // capacitor
this.c(connections[0],connections[1],properties['c'],name); this.c(connections[0],connections[1],properties['c'],name);
else if (type == 'l') // inductor else if (type == 'l') // inductor
...@@ -147,57 +131,51 @@ cktsim = (function() { ...@@ -147,57 +131,51 @@ cktsim = (function() {
else if (type == 'o') // op amp else if (type == 'o') // op amp
this.opamp(connections[0],connections[1],connections[2],properties['A'],name); this.opamp(connections[0],connections[1],connections[2],properties['A'],name);
else if (type == 'n') // n fet else if (type == 'n') // n fet
this.fet('n',connections[0],connections[1],connections[2], this.n(connections[0],connections[1],connections[2],
properties['sw'],properties['sl'],name); properties['W/L'],name);
else if (type == 'p') // p fet else if (type == 'p') // p fet
this.fet('p',connections[0],connections[1],connections[2], this.p(connections[0],connections[1],connections[2],
properties['sw'],properties['sl'],name); properties['W/L'],name);
} }
} }
// if converges: updates this.solution, this.soln_max, returns iter count // if converges: updates this.solution, this.soln_max, returns iter count
// otherwise: return undefined and set this.problem_node // otherwise: return undefined and set this.problem_node
// The argument should be a function that sets up the linear system. // 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 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 // 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 // set up equations
this.initialize_linear_system(); // no longer needed this.initialize_linear_system();
load(this); load(this,soln,rhs);
// solve for node voltages and branch currents // Compute the Newton delta
old_soln = soln; d_sol = solve_linear_system(this.matrix,rhs);
soln = solve_linear_system(this.matrix);
// check convergence: abs(new-old) <= abstol + reltol*max; // Update solution and check convergence.
converged = true; converged = true;
for (var i = this.N - 1; i >= 0; --i) { for (var i = this.N - 1; i >= 0; --i) {
temp = Math.abs(soln[i] - old_soln); // Simple voltage step limiting to encourage Newton convergence
if (temp > this.abstol[i] + this.rtol[i]) { 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; converged = false;
this.problem_node = i; 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;
} }
// alert(numeric.prettyPrint(this.solution));
if (converged == true) return iter+1;
} }
return iter+1;
}
// too many iterations // too many iterations
return undefined; return undefined;
} }
...@@ -206,18 +184,32 @@ cktsim = (function() { ...@@ -206,18 +184,32 @@ cktsim = (function() {
Circuit.prototype.dc = function() { Circuit.prototype.dc = function() {
this.finalize(); this.finalize();
// this function calls load_dc for all devices // Load up the linear part.
function load_dc(ckt) { 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) 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 // 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 // create solution dictionary
var result = new Array(); var result = new Array();
for (var name in this.node_map) { for (var name in this.node_map) {
...@@ -226,23 +218,131 @@ cktsim = (function() { ...@@ -226,23 +218,131 @@ cktsim = (function() {
} }
return result; 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 : response[index];
}
result['time'] = response[this.N];
return result;
}
// AC analysis: npts/decade for freqs in range [fstart,fstop] // AC analysis: npts/decade for freqs in range [fstart,fstop]
// result['frequencies'] = vector of log10(sample freqs) // result['frequencies'] = vector of log10(sample freqs)
// result['xxx'] = vector of dB(response for node xxx) // result['xxx'] = vector of dB(response for node xxx)
Circuit.prototype.ac = function(npts,fstart,fstop) { // NOTE: Normalization removed in schematic.js, jkw.
this.finalize(); Circuit.prototype.ac = function(npts,fstart,fstop,source_name) {
if (this.diddc == false) this.dc();
// this function calls load_ac for all devices var N = this.N;
function load_ac(ckt) { var G = this.G;
for (var i = ckt.devices.length - 1; i >= 0; --i) var C = this.C;
ckt.devices[i].load_ac(ckt);
// 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 // build array to hold list of results for each node
// last entry is for frequency values // last entry is for frequency values
var response = new Array(this.N + 1); var response = new Array(N + 1);
for (var i = this.N; i >= 0; --i) response[i] = new Array(); for (var i = N; i >= 0; --i) response[i] = new Array();
// multiplicative frequency increase between freq points // multiplicative frequency increase between freq points
var delta_f = Math.exp(Math.LN10/npts); var delta_f = Math.exp(Math.LN10/npts);
...@@ -250,19 +350,34 @@ cktsim = (function() { ...@@ -250,19 +350,34 @@ cktsim = (function() {
var f = fstart; var f = fstart;
fstop *= 1.0001; // capture that last time point! fstop *= 1.0001; // capture that last time point!
while (f <= fstop) { while (f <= fstop) {
this.omega = 2 * Math.PI * f; var omega = 2 * Math.PI * f;
response[this.N].push(f);
// find the operating point // Find complex x+jy that sats Gx-omega*Cy=rhs; omega*Cx+Gy=0
var iterations = this.find_solution(load_ac); // 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') for (var j = N-1; j >= 0; --j)
return 'Node '+this.node_map[this.problem_node]+' did not converge'; {
else { matrixac[i][j] = G[i][j];
response[this.N].push(f); matrixac[i+N][j+N] = G[i][j];
for (var i = this.N - 1; i >= 0; --i) matrixac[i][j+N] = -omega*C[i][j];
response[i].push(this.solution[i]); 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 f *= delta_f; // increment frequency
} }
...@@ -276,6 +391,22 @@ cktsim = (function() { ...@@ -276,6 +391,22 @@ cktsim = (function() {
return result; 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) { Circuit.prototype.r = function(n1,n2,v,name) {
// try to convert string value into numeric value, barf if we can't // try to convert string value into numeric value, barf if we can't
if ((typeof v) == 'string') { if ((typeof v) == 'string') {
...@@ -283,14 +414,26 @@ cktsim = (function() { ...@@ -283,14 +414,26 @@ cktsim = (function() {
if (v === undefined) return undefined; if (v === undefined) return undefined;
} }
var d;
if (v != 0) { if (v != 0) {
d = new Resistor(n1,n2,v); var d = new Resistor(n1,n2,v);
this.devices.push(d); return this.add_device(d, name);
if (name) this.device_map[name] = d;
} else return this.v(n1,n2,0,name); // zero resistance == 0V voltage source } 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) { Circuit.prototype.c = function(n1,n2,v,name) {
// try to convert string value into numeric value, barf if we can't // try to convert string value into numeric value, barf if we can't
if ((typeof v) == 'string') { if ((typeof v) == 'string') {
...@@ -298,9 +441,7 @@ cktsim = (function() { ...@@ -298,9 +441,7 @@ cktsim = (function() {
if (v === undefined) return undefined; if (v === undefined) return undefined;
} }
var d = new Capacitor(n1,n2,v); var d = new Capacitor(n1,n2,v);
this.devices.push(d); return this.add_device(d, name);
if (name) this.device_map[name] = d;
return d;
} }
Circuit.prototype.l = function(n1,n2,v,name) { Circuit.prototype.l = function(n1,n2,v,name) {
...@@ -311,26 +452,41 @@ cktsim = (function() { ...@@ -311,26 +452,41 @@ cktsim = (function() {
} }
var branch = this.node(undefined,T_CURRENT); var branch = this.node(undefined,T_CURRENT);
var d = new Inductor(n1,n2,branch,v); var d = new Inductor(n1,n2,branch,v);
this.devices.push(d); return this.add_device(d, name);
if (name) this.device_map[name] = d;
return d;
} }
Circuit.prototype.v = function(n1,n2,v,name) { Circuit.prototype.v = function(n1,n2,v,name) {
var branch = this.node(undefined,T_CURRENT); var branch = this.node(undefined,T_CURRENT);
var d = new VSource(n1,n2,branch,v); var d = new VSource(n1,n2,branch,v);
this.devices.push(d); return this.add_device(d, name);
if (name) this.device_map[name] = d;
return d;
} }
Circuit.prototype.i = function(n1,n2,v,name) { Circuit.prototype.i = function(n1,n2,v,name) {
var d = new ISource(n1,n2,v); var d = new ISource(n1,n2,v);
this.devices.push(d); return this.add_device(d, name);
if (name) this.device_map[name] = d; }
return d;
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 // Support for creating and solving a system of linear equations
...@@ -354,37 +510,146 @@ cktsim = (function() { ...@@ -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 // 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) { if (i >= 0) {
this.matrix[i][i] += g; M[i][i] += g;
if (j >= 0) { if (j >= 0) {
this.matrix[i][j] -= g; M[i][j] -= g;
this.matrix[j][i] -= g; M[j][i] -= g;
this.matrix[j][j] += g; M[j][j] += g;
} }
} else if (j >= 0) } else if (j >= 0)
this.matrix[j][j] += g; M[j][j] += g;
}
// 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.Gl[i][j] += g;
}
// 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;
} }
// add individual conductance to A // add individual capacitance to C matrix
Circuit.prototype.add_to_A = function(i,j,v) { Circuit.prototype.add_to_C = function(i,j,c) {
if (i >=0 && j >= 0) if (i >=0 && j >= 0)
this.matrix[i][j] += v; this.C[i][j] += c;
} }
// add source info to vector b // add source info to rhs
Circuit.prototype.add_to_b = function(i,v) { Circuit.prototype.add_to_rhs = function(i,v,rhs) {
if (i >= 0) this.matrix[i][this.N] += v; if (i >= 0) rhs[i] += v;
} }
// solve Ax=b and return vector x given augmented matrix [A | b] // solve Ax=b and return vector x given augmented matrix M = [A | b]
// Uses Gaussian elimination with partial pivoting // 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 N = M.length; // augmented matrix M has N rows, N+1 columns
var temp,i,j; 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 // gaussian elimination
for (var col = 0; col < N ; col++) { for (var col = 0; col < N ; col++) {
// find pivot: largest abs(v) in this column of remaining rows // find pivot: largest abs(v) in this column of remaining rows
...@@ -447,13 +712,13 @@ cktsim = (function() { ...@@ -447,13 +712,13 @@ cktsim = (function() {
Device.prototype.finalize = function() { Device.prototype.finalize = function() {
} }
// reset internal state of the device to initial value // Load the linear elements in to Gl and C
Device.prototype.reset = function() { Device.prototype.load_linear = function(ckt) {
} }
// load linear system equations for dc analysis // load linear system equations for dc analysis
// (inductors shorted and capacitors opened) // (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 // load linear system equations for tran analysis
...@@ -463,11 +728,7 @@ cktsim = (function() { ...@@ -463,11 +728,7 @@ cktsim = (function() {
// load linear system equations for ac analysis: // load linear system equations for ac analysis:
// current sources open, voltage sources shorted // current sources open, voltage sources shorted
// linear models at operating point for everyone else // linear models at operating point for everyone else
Device.prototype.load_ac = function(ckt) { Device.prototype.load_ac = function(ckt,rhs) {
}
// called with there's an accepted time step
Device.prototype.end_of_timestep = function(ckt) {
} }
// return time of next breakpoint for the device // return time of next breakpoint for the device
...@@ -616,7 +877,7 @@ cktsim = (function() { ...@@ -616,7 +877,7 @@ cktsim = (function() {
else if (s.charAt(index) == 'i' && s.charAt(index+1) == 'l') else if (s.charAt(index) == 'i' && s.charAt(index+1) == 'l')
result *= 25.4e-6; result *= 25.4e-6;
} else result *= 1e-3; } else result *= 1e-3;
} else return default_v; }
} }
// ignore any remaining chars, eg, 1kohms returns 1000 // ignore any remaining chars, eg, 1kohms returns 1000
return result; return result;
...@@ -731,10 +992,10 @@ cktsim = (function() { ...@@ -731,10 +992,10 @@ cktsim = (function() {
// return value of source at time t // return value of source at time t
src.value = function(t) { // closure 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 { else {
t -= td; 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));
} }
} }
...@@ -783,26 +1044,25 @@ cktsim = (function() { ...@@ -783,26 +1044,25 @@ cktsim = (function() {
this.branch = branch; this.branch = branch;
} }
VSource.prototype = new Device(); VSource.prototype = new Device();
VSource.prototype.construction = VSource; VSource.prototype.constructor = VSource;
// load linear system equations for dc analysis // load linear part for source evaluation
VSource.prototype.load_dc = function(ckt) { VSource.prototype.load_linear = function(ckt) {
// MNA stamp for independent voltage source // MNA stamp for independent voltage source
ckt.add_to_A(this.branch,this.npos,1.0); ckt.add_to_Gl(this.branch,this.npos,1.0);
ckt.add_to_A(this.branch,this.nneg,-1.0); ckt.add_to_Gl(this.branch,this.nneg,-1.0);
ckt.add_to_A(this.npos,this.branch,1.0); ckt.add_to_Gl(this.npos,this.branch,1.0);
ckt.add_to_A(this.nneg,this.branch,-1.0); ckt.add_to_Gl(this.nneg,this.branch,-1.0);
ckt.add_to_b(this.branch,this.src.dc);
} }
// load linear system equations for tran analysis (just like DC) // Source voltage added to b.
VSource.prototype.load_tran = function(ckt,soln) { VSource.prototype.load_dc = function(ckt,soln,rhs) {
// MNA stamp for independent voltage source ckt.add_to_rhs(this.branch,this.src.dc,rhs);
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); // Load time-dependent value for voltage source for tran
ckt.add_to_A(this.nneg,this.branch,-1.0); VSource.prototype.load_tran = function(ckt,soln,rhs,time) {
ckt.add_to_b(this.branch,this.src.value(ckt.time)); ckt.add_to_rhs(this.branch,this.src.value(time),rhs);
} }
// return time of next breakpoint for the device // return time of next breakpoint for the device
...@@ -810,9 +1070,9 @@ cktsim = (function() { ...@@ -810,9 +1070,9 @@ cktsim = (function() {
return this.src.inflection_point(time); return this.src.inflection_point(time);
} }
// small signal model: short circuit // small signal model ac value
VSource.prototype.load_ac = function(ckt) { VSource.prototype.load_ac = function(ckt,rhs) {
this.load_dc(ckt); ckt.add_to_rhs(this.branch,1.0,rhs);
} }
function ISource(npos,nneg,v) { function ISource(npos,nneg,v) {
...@@ -823,24 +1083,24 @@ cktsim = (function() { ...@@ -823,24 +1083,24 @@ cktsim = (function() {
this.nneg = nneg; this.nneg = nneg;
} }
ISource.prototype = new Device(); ISource.prototype = new Device();
ISource.prototype.construction = ISource; ISource.prototype.constructor = ISource;
// load linear system equations for dc analysis // load linear system equations for dc analysis
ISource.prototype.load_dc = function(ckt) { ISource.prototype.load_dc = function(ckt,soln,rhs) {
var i = this.src.dc; var is = this.src.dc;
// MNA stamp for independent current source // MNA stamp for independent current source
ckt.add_to_b(this.npos,-i); // current flow into npos ckt.add_to_rhs(this.npos,-is,rhs); // current flow into npos
ckt.add_to_b(this.nneg,i); // and out of nneg ckt.add_to_rhs(this.nneg,is,rhs); // and out of nneg
} }
// load linear system equations for tran analysis (just like DC) // load linear system equations for tran analysis (just like DC)
ISource.prototype.load_tran = function(ckt,soln) { ISource.prototype.load_tran = function(ckt,soln,rhs,time) {
var i = this.src.value(ckt.time); var is = this.src.value(time);
// MNA stamp for independent current source // MNA stamp for independent current source
ckt.add_to_b(this.npos,-i); // current flow into npos ckt.add_to_rhs(this.npos,-is,rhs); // current flow into npos
ckt.add_to_b(this.nneg,i); // and out of nneg ckt.add_to_rhs(this.nneg,is,rhs); // and out of nneg
} }
// return time of next breakpoint for the device // return time of next breakpoint for the device
...@@ -850,7 +1110,9 @@ cktsim = (function() { ...@@ -850,7 +1110,9 @@ cktsim = (function() {
// small signal model: open circuit // small signal model: open circuit
ISource.prototype.load_ac = function(ckt) { 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,81 +1128,92 @@ cktsim = (function() { ...@@ -866,81 +1128,92 @@ cktsim = (function() {
this.g = 1.0/v; this.g = 1.0/v;
} }
Resistor.prototype = new Device(); Resistor.prototype = new Device();
Resistor.prototype.construction = Resistor; Resistor.prototype.constructor = Resistor;
Resistor.prototype.load_dc = function(ckt) { Resistor.prototype.load_linear = function(ckt) {
// MNA stamp for admittance g // MNA stamp for admittance g
ckt.add_conductance(this.n1,this.n2,this.g); ckt.add_conductance_l(this.n1,this.n2,this.g);
}
Resistor.prototype.load_dc = function(ckt) {
// Nothing to see here, move along.
} }
Resistor.prototype.load_tran = function(ckt,soln) { Resistor.prototype.load_tran = function(ckt,soln) {
this.load_dc(ckt);
} }
Resistor.prototype.load_ac = function(ckt) { Resistor.prototype.load_ac = function(ckt) {
this.load_dc(ckt);
} }
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// //
// Capacitor // Diode
// //
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
function Capacitor(n1,n2,v) { function Diode(n1,n2,v) {
Device.call(this); Device.call(this);
this.n1 = n1; this.anode = n1;
this.n2 = n2; this.cathode = n2;
this.value = v; this.area = v;
this.is = 1.0e-14;
this.ais = this.area * this.is;
this.vt = 2.58e-2; // 26 millivolts
} }
Capacitor.prototype = new Device(); Diode.prototype = new Device();
Capacitor.prototype.construction = Capacitor; Diode.prototype.constructor = Diode;
// capacitor is modeled as a current source (ieq) in parallel with a conductance (geq)
Capacitor.prototype.reset = function() { Diode.prototype.load_linear = function(ckt) {
this.q = 0; // state variable (charge) // Diode is not linear, has no linear piece.
this.i = 0; // dstate/dt (current)
this.prev_q = 0; // last iteration
this.prev_i = 0;
} }
Capacitor.prototype.finalize = function(ckt) { Diode.prototype.load_dc = function(ckt,soln,rhs) {
// call us at the end of each timestep var vd = ckt.get_two_terminal(this.anode, this.cathode, soln);
ckt.end_of_timestep.push(this); 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);
} }
Capacitor.prototype.end_of_timestep = function(ckt) { Diode.prototype.load_tran = function(ckt,soln,rhs,time) {
// update state when timestep is accepted this.load_dc(ckt,soln,rhs);
this.prev_q = this.q;
this.prev_i = this.i;
} }
Capacitor.prototype.load_dc = function(ckt) { Diode.prototype.load_ac = 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 // Capacitor
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;
function Capacitor(n1,n2,v) {
Device.call(this);
this.n1 = n1;
this.n2 = n2;
this.value = v;
}
Capacitor.prototype = new Device();
Capacitor.prototype.constructor = Capacitor;
// MNA stamp for admittance geq Capacitor.prototype.load_linear = function(ckt) {
ckt.add_conductance(this.n1,this.n2,geq); // MNA stamp for capacitance matrix
ckt.add_capacitance(this.n1,this.n2,this.value);
}
// MNA stamp for current source ieq Capacitor.prototype.load_dc = function(ckt,soln,rhs) {
ckt.add_to_b(this.n1,-ieq);
ckt.add_to_b(this.n2,ieq);
} }
Capacitor.prototype.load_ac = function(ckt) { 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() { ...@@ -957,63 +1230,100 @@ cktsim = (function() {
this.value = v; this.value = v;
} }
Inductor.prototype = new Device(); 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.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.reset = function() { Inductor.prototype.load_dc = function(ckt,soln,rhs) {
this.flux = 0; // state variable (flux) // Inductor is a short at dc, so is linear.
this.v = 0; // dstate/dt (voltage)
this.prev_flux = 0; // last iteration
this.prev_v = 0;
} }
Inductor.prototype.finalize = function(ckt) { Inductor.prototype.load_ac = function(ckt) {
// call us at the end of each timestep
ckt.end_of_timestep.push(this);
} }
Inductor.prototype.end_of_timestep = function(ckt) { Inductor.prototype.load_tran = 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); // Simplified MOS FET with no bulk connection and no body effect.
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);
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;
Inductor.prototype.load_tran = function(ckt,soln) { Fet.prototype.load_linear = function(ckt) {
this.flux = this.value * soln[this.branch]; // set flux // FET's are nonlinear, just like javascript progammers
}
// integrate Fet.prototype.load_dc = function(ckt,soln,rhs) {
// for backward Euler: coeff0 = 1/timestep, coeff1 = 0 var vds = this.type_sign * ckt.get_two_terminal(this.d, this.s, soln);
// for trapezoidal: coeff0 = 2/timestep, coeff1 = 1 if (vds < 0) { // Drain and source have swapped roles
this.v = ckt.coeff0*(this.flux - this.prev_flux) - ckt.coeff1*this.prev_v; var temp = this.d;
var veq = this.v - ckt.coeff0*this.flux; this.d = this.s;
var req = ckt.coeff0 * this.value; 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);
}
}
}
// MNA stamp for voltage source with impedance Fet.prototype.load_tran = function(ckt,soln,rhs) {
ckt.add_to_b(this.branch,veq); this.load_dc(ckt,soln,rhs);
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_ac = function(ckt) { Fet.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);
} }
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// //
// Module definition // Module definition
...@@ -1021,6 +1331,7 @@ cktsim = (function() { ...@@ -1021,6 +1331,7 @@ cktsim = (function() {
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
var module = { var module = {
'Circuit': Circuit, 'Circuit': Circuit,
'parse_number': parse_number,
} }
return module; return module;
}()); }());
////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
// //
// Simple schematic capture // Simple schematic capture
// //
...@@ -6,24 +6,6 @@ ...@@ -6,24 +6,6 @@
// Copyright (C) 2011 Massachusetts Institute of Technology // 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 // add schematics to a document with
// //
// <input type="hidden" class="schematic" name="unique_form_id" value="JSON netlist..." .../> // <input type="hidden" class="schematic" name="unique_form_id" value="JSON netlist..." .../>
...@@ -72,6 +54,12 @@ function add_schematic_handler(other_onload) { ...@@ -72,6 +54,12 @@ function add_schematic_handler(other_onload) {
} }
window.onload = add_schematic_handler(window.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() { schematic = (function() {
background_style = 'rgb(220,220,220)'; background_style = 'rgb(220,220,220)';
...@@ -88,6 +76,7 @@ schematic = (function() { ...@@ -88,6 +76,7 @@ schematic = (function() {
// list of all the defined parts // list of all the defined parts
parts_map = { parts_map = {
'g': [Ground, 'Ground connection'], 'g': [Ground, 'Ground connection'],
'L': [Label, 'Node label'],
'v': [VSource, 'Voltage source'], 'v': [VSource, 'Voltage source'],
'i': [ISource, 'Current source'], 'i': [ISource, 'Current source'],
'r': [Resistor, 'Resistor'], 'r': [Resistor, 'Resistor'],
...@@ -100,10 +89,6 @@ schematic = (function() { ...@@ -100,10 +89,6 @@ schematic = (function() {
's': [Probe, 'Scope Probe'], '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 // Schematic = diagram + parts bin + status area
...@@ -170,16 +155,19 @@ schematic = (function() { ...@@ -170,16 +155,19 @@ schematic = (function() {
} }
if (analyses.indexOf('ac') != -1) { 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.enable_tool('ac',true);
this.ac_npts = '5'; // default values for AC Analysis this.ac_npts = '5'; // default values for AC Analysis
this.ac_fstart = '10'; this.ac_fstart = '10';
this.ac_fstop = '10MEG'; this.ac_fstop = '10MEG';
this.ac_source_name = undefined;
} }
if (analyses.indexOf('tran') != -1) { if (analyses.indexOf('tran') != -1) {
//this.tools['tran'] = this.add_tool('TRAN','Transient Analysis',this.tran_analysis); this.tools['tran'] = this.add_tool('TRAN','Transient Analysis',this.transient_analysis);
//this.enable_tool('tran',true); this.enable_tool('tran',true);
this.tran_npts = '100'; // default values for transient analysis
this.tran_tstop = '1';
} }
} }
...@@ -238,6 +226,9 @@ schematic = (function() { ...@@ -238,6 +226,9 @@ schematic = (function() {
this.wire = undefined; this.wire = undefined;
this.operating_point = undefined; // result from DC analysis 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 // state of modifier keys
this.ctrlKey = false; this.ctrlKey = false;
...@@ -252,10 +243,12 @@ schematic = (function() { ...@@ -252,10 +243,12 @@ schematic = (function() {
// set up DOM -- use nested tables to do the layout // set up DOM -- use nested tables to do the layout
var table,tr,td; var table,tr,td;
table = document.createElement('table'); table = document.createElement('table');
table.rules = 'none';
if (!this.diagram_only) { if (!this.diagram_only) {
table.frame = 'box';
table.style.borderStyle = 'solid'; table.style.borderStyle = 'solid';
table.style.borderWidth = '2px'; table.style.borderWidth = '2px';
table.style.padding = '5px'; table.style.borderColor = normal_style;
table.style.backgroundColor = background_style; table.style.backgroundColor = background_style;
} }
...@@ -264,8 +257,7 @@ schematic = (function() { ...@@ -264,8 +257,7 @@ schematic = (function() {
tr = document.createElement('tr'); tr = document.createElement('tr');
table.appendChild(tr); table.appendChild(tr);
td = document.createElement('td'); td = document.createElement('td');
td.colspan = 2; td.colSpan = 2;
td.vAlign = 'baseline';
tr.appendChild(td); tr.appendChild(td);
for (var i = 0; i < this.toolbar.length; ++i) { for (var i = 0; i < this.toolbar.length; ++i) {
var tool = this.toolbar[i]; var tool = this.toolbar[i];
...@@ -284,9 +276,13 @@ schematic = (function() { ...@@ -284,9 +276,13 @@ schematic = (function() {
tr.appendChild(td); tr.appendChild(td);
var parts_table = document.createElement('table'); var parts_table = document.createElement('table');
td.appendChild(parts_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!!! // fill in parts_table
var parts_per_column = Math.floor(this.height / part_h); var parts_per_column = Math.floor(this.height / (part_h + 5)); // mysterious extra padding
for (var i = 0; i < parts_per_column; ++i) { for (var i = 0; i < parts_per_column; ++i) {
tr = document.createElement('tr'); tr = document.createElement('tr');
parts_table.appendChild(tr); parts_table.appendChild(tr);
...@@ -302,7 +298,7 @@ schematic = (function() { ...@@ -302,7 +298,7 @@ schematic = (function() {
table.appendChild(tr); table.appendChild(tr);
td = document.createElement('td'); td = document.createElement('td');
tr.appendChild(td); tr.appendChild(td);
td.colspan = 2; td.colSpan = 2;
td.appendChild(this.status_div); td.appendChild(this.status_div);
} }
...@@ -502,9 +498,17 @@ schematic = (function() { ...@@ -502,9 +498,17 @@ schematic = (function() {
this.origin_x = c[1]; this.origin_x = c[1];
this.origin_y = c[2]; this.origin_y = c[2];
this.scale = c[3]; 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') { } else if (c[0] == 'w') {
// wire // wire
this.add_wire(c[1][0],c[1][1],c[1][2],c[1][3]); 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 { } else {
// ordinary component // ordinary component
// c := [type, coords, properties, connections] // c := [type, coords, properties, connections]
...@@ -520,7 +524,7 @@ schematic = (function() { ...@@ -520,7 +524,7 @@ schematic = (function() {
part.properties[name] = properties[name]; part.properties[name] = properties[name];
// add component to the diagram // add component to the diagram
part.add(this) part.add(this);
} }
} }
} }
...@@ -571,7 +575,7 @@ schematic = (function() { ...@@ -571,7 +575,7 @@ schematic = (function() {
// build JSON data structure, convert to string value for // build JSON data structure, convert to string value for
// input field // 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 // produce a JSON representation of the diagram
...@@ -583,7 +587,20 @@ schematic = (function() { ...@@ -583,7 +587,20 @@ schematic = (function() {
json.push(this.components[i].json()); json.push(this.components[i].json());
// capture the current view parameters // 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; return json;
} }
...@@ -599,6 +616,10 @@ schematic = (function() { ...@@ -599,6 +616,10 @@ schematic = (function() {
this.label_connection_points(); this.label_connection_points();
var netlist = this.json(); 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 // create a circuit from the netlist
var ckt = new cktsim.Circuit(); var ckt = new cktsim.Circuit();
ckt.load_netlist(netlist); ckt.load_netlist(netlist);
...@@ -616,6 +637,10 @@ schematic = (function() { ...@@ -616,6 +637,10 @@ schematic = (function() {
// run the analysis // run the analysis
this.operating_point = ckt.dc(); 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 // display results on diagram
this.redraw(); this.redraw();
} }
...@@ -630,16 +655,18 @@ schematic = (function() { ...@@ -630,16 +655,18 @@ schematic = (function() {
return result; 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.unselect_all(-1);
this.redraw_background(); this.redraw_background();
var npts_lbl = 'Number of points/decade'; var npts_lbl = 'Number of points/decade';
var fstart_lbl = 'Starting frequency (Hz)'; var fstart_lbl = 'Starting frequency (Hz)';
var fstop_lbl = 'Ending 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) { 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; return;
} }
...@@ -647,6 +674,7 @@ schematic = (function() { ...@@ -647,6 +674,7 @@ schematic = (function() {
fields[npts_lbl] = build_input('text',10,this.ac_npts); fields[npts_lbl] = build_input('text',10,this.ac_npts);
fields[fstart_lbl] = build_input('text',10,this.ac_fstart); fields[fstart_lbl] = build_input('text',10,this.ac_fstart);
fields[fstop_lbl] = build_input('text',10,this.ac_fstop); 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); var content = build_table(fields);
content.fields = fields; content.fields = fields;
...@@ -654,20 +682,32 @@ schematic = (function() { ...@@ -654,20 +682,32 @@ schematic = (function() {
this.dialog('AC Analysis',content,function(content) { this.dialog('AC Analysis',content,function(content) {
var sch = content.sch; var sch = content.sch;
var ckt = sch.extract_circuit();
// retrieve parameters, remember for next time // retrieve parameters, remember for next time
sch.ac_npts = content.fields[npts_lbl].value; sch.ac_npts = content.fields[npts_lbl].value;
sch.ac_fstart = content.fields[fstart_lbl].value; sch.ac_fstart = content.fields[fstart_lbl].value;
sch.ac_fstop = content.fields[fstop_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 // run the analysis
var results = ckt.ac(ckt.parse_number(sch.ac_npts), var ckt = this.extract_circuit();
ckt.parse_number(sch.ac_fstart), var results = ckt.ac(npts,fstart,fstop,ac_source_name);
ckt.parse_number(sch.ac_fstop));
// 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') if (typeof results == 'string')
sch.message(results); this.message(results);
else { else {
var x_values = results['frequencies']; var x_values = results['frequencies'];
...@@ -677,19 +717,15 @@ schematic = (function() { ...@@ -677,19 +717,15 @@ schematic = (function() {
// set up plot values for each node with a probe // set up plot values for each node with a probe
var y_values = []; // list of [color, result_array] var y_values = []; // list of [color, result_array]
var probes = sch.find_probes(); var probes = this.find_probes();
for (var i = probes.length - 1; i >= 0; --i) { for (var i = probes.length - 1; i >= 0; --i) {
var color = probes[i][0]; var color = probes[i][0];
var label = probes[i][1]; var label = probes[i][1];
var v = results[label]; var v = results[label];
// convert values into dB relative to max value // convert values into dB relative to source amplitude
var v_max = -Infinity; var v_max = 1;
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) for (var j = v.length - 1; j >= 0; --j)
// convert each value to dB relative to max // convert each value to dB relative to max
v[j] = 20.0 * Math.log(v[j]/v_max)/Math.LN10; v[j] = 20.0 * Math.log(v[j]/v_max)/Math.LN10;
...@@ -698,13 +734,84 @@ schematic = (function() { ...@@ -698,13 +734,84 @@ schematic = (function() {
} }
// graph the result and display in a window // graph the result and display in a window
var graph = sch.graph(x_values,y_values,'log(Frequency)','dB'); var graph = this.graph(x_values,y_values,'log(Frequency)','dB');
sch.window('Results of AC Analysis',graph); this.window('Results of AC Analysis',graph);
} }
})
} }
Schematic.prototype.transient_analysis = function() { 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.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['time'];
// set up plot values for each node with a probe
var y_values = []; // list of [color, result_array]
var probes = sch.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];
y_values.push([color,v]);
}
// graph the result and display in a window
var graph = sch.graph(x_values,y_values,'Time','Voltage');
sch.window('Results of Transient Analysis',graph);
}
})
}
// 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() { ...@@ -720,11 +827,11 @@ schematic = (function() {
c.lineCap = 'round'; c.lineCap = 'round';
if (!this.diagram_only) {
// paint background color // paint background color
c.fillStyle = element_style; c.fillStyle = element_style;
c.fillRect(0,0,this.width,this.height); c.fillRect(0,0,this.width,this.height);
if (!this.diagram_only) {
// grid // grid
c.strokeStyle = grid_style; c.strokeStyle = grid_style;
var first_x = 0; var first_x = 0;
...@@ -1204,6 +1311,7 @@ schematic = (function() { ...@@ -1204,6 +1311,7 @@ schematic = (function() {
// div to hold the two buttons // div to hold the two buttons
var buttons = document.createElement('div'); var buttons = document.createElement('div');
buttons.style.textAlign = 'center';
buttons.appendChild(ok_button); buttons.appendChild(ok_button);
buttons.appendChild(cancel_button); buttons.appendChild(cancel_button);
buttons.style.padding = '5px'; buttons.style.padding = '5px';
...@@ -1315,9 +1423,9 @@ schematic = (function() { ...@@ -1315,9 +1423,9 @@ schematic = (function() {
head.addEventListener('mousemove',window_mouse_move,false); head.addEventListener('mousemove',window_mouse_move,false);
// div to hold the content // div to hold the content
var body = document.createElement('div'); //var body = document.createElement('div');
body.appendChild(content); //body.appendChild(content);
win.appendChild(body); win.appendChild(content);
content.win = win; // so content can contact us content.win = win; // so content can contact us
// compute location in top-level div // compute location in top-level div
...@@ -1415,7 +1523,6 @@ schematic = (function() { ...@@ -1415,7 +1523,6 @@ schematic = (function() {
tool.style.borderWidth = '1px'; tool.style.borderWidth = '1px';
tool.style.borderStyle = 'solid'; tool.style.borderStyle = 'solid';
tool.style.borderColor = background_style; tool.style.borderColor = background_style;
//tool.style.position = 'absolute';
tool.style.padding = '2px'; tool.style.padding = '2px';
// set up event processing // set up event processing
...@@ -1552,6 +1659,45 @@ schematic = (function() { ...@@ -1552,6 +1659,45 @@ schematic = (function() {
return [vmin,vmax,1.0/scale]; 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 // 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 // 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) { Schematic.prototype.graph = function(x_values,y_values,x_legend,y_legend) {
...@@ -1622,7 +1768,7 @@ schematic = (function() { ...@@ -1622,7 +1768,7 @@ schematic = (function() {
c.moveTo(temp,end); c.moveTo(temp,end);
c.lineTo(temp,end + tick_length); c.lineTo(temp,end + tick_length);
c.stroke(); c.stroke();
c.fillText(x.toString(),temp,end + tick_length); c.fillText(engineering_notation(x,2),temp,end + tick_length);
} }
var y_min = Infinity; var y_min = Infinity;
...@@ -1664,7 +1810,7 @@ schematic = (function() { ...@@ -1664,7 +1810,7 @@ schematic = (function() {
c.moveTo(left_margin - tick_length,temp); c.moveTo(left_margin - tick_length,temp);
c.lineTo(left_margin,temp); c.lineTo(left_margin,temp);
c.stroke(); 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 // now draw each plot
...@@ -2149,7 +2295,7 @@ schematic = (function() { ...@@ -2149,7 +2295,7 @@ schematic = (function() {
Component.prototype.select = function(x,y,shiftKey) { Component.prototype.select = function(x,y,shiftKey) {
this.was_previously_selected = this.selected; this.was_previously_selected = this.selected;
if (inside(this.bbox,x,y)) { if (this.near(x,y)) {
this.set_select(shiftKey ? !this.selected : true); this.set_select(shiftKey ? !this.selected : true);
return true; return true;
} else return false; } else return false;
...@@ -2168,8 +2314,13 @@ schematic = (function() { ...@@ -2168,8 +2314,13 @@ schematic = (function() {
return null; 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) { Component.prototype.edit_properties = function(x,y) {
if (inside(this.bbox,x,y)) { if (this.near(x,y)) {
// make an <input> widget for each property // make an <input> widget for each property
var fields = new Array(); var fields = new Array();
for (var i in this.properties) for (var i in this.properties)
...@@ -2361,14 +2512,6 @@ schematic = (function() { ...@@ -2361,14 +2512,6 @@ schematic = (function() {
return false; 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 // selection rectangle selects wire only if it includes
// one of the end points // one of the end points
Wire.prototype.select_rect = function(s) { Wire.prototype.select_rect = function(s) {
...@@ -2451,6 +2594,41 @@ schematic = (function() { ...@@ -2451,6 +2594,41 @@ schematic = (function() {
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// //
// 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'+' ('+this.x+','+this.y+')>';
}
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 // Scope Probe
// //
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
...@@ -2538,7 +2716,7 @@ schematic = (function() { ...@@ -2538,7 +2716,7 @@ schematic = (function() {
this.properties['r'] = r ? r : '1'; this.properties['r'] = r ? r : '1';
this.add_connection(0,0); this.add_connection(0,0);
this.add_connection(0,48); this.add_connection(0,48);
this.bounding_box = [-4,0,4,48]; this.bounding_box = [-5,0,5,48];
this.update_coords(); this.update_coords();
} }
Resistor.prototype = new Component(); Resistor.prototype = new Component();
...@@ -2650,9 +2828,10 @@ schematic = (function() { ...@@ -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); Component.call(this,'d',x,y,rotation);
this.properties['name'] = name; this.properties['name'] = name;
this.properties['area'] = area ? area : '1';
this.add_connection(0,0); // anode this.add_connection(0,0); // anode
this.add_connection(0,48); // cathode this.add_connection(0,48); // cathode
this.bounding_box = [-8,0,8,48]; this.bounding_box = [-8,0,8,48];
...@@ -2662,7 +2841,7 @@ schematic = (function() { ...@@ -2662,7 +2841,7 @@ schematic = (function() {
Diode.prototype.constructor = Diode; Diode.prototype.constructor = Diode;
Diode.prototype.toString = function() { Diode.prototype.toString = function() {
return '<Diode ('+this.x+','+this.y+')>'; return '<Diode '+this.properties['area']+' ('+this.x+','+this.y+')>';
} }
Diode.prototype.draw = function(c) { Diode.prototype.draw = function(c) {
...@@ -2673,12 +2852,14 @@ schematic = (function() { ...@@ -2673,12 +2852,14 @@ schematic = (function() {
this.draw_line(c,-8,32,8,32); this.draw_line(c,-8,32,8,32);
this.draw_line(c,0,32,0,48); 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']) if (this.properties['name'])
this.draw_text(c,this.properties['name'],-10,24,5,property_size); this.draw_text(c,this.properties['name'],-10,24,5,property_size);
} }
Diode.prototype.clone = function(x,y) { 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() { ...@@ -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); Component.call(this,'n',x,y,rotation);
this.properties['name'] = name; this.properties['name'] = name;
this.properties['scaled width'] = sw ? sw : '2'; this.properties['W/L'] = w_over_l ? w_over_l : '2';
this.properties['scaled length'] = sl ? sl : '1';
this.add_connection(0,0); // drain this.add_connection(0,0); // drain
this.add_connection(0,48); // source
this.add_connection(-24,24); // gate this.add_connection(-24,24); // gate
this.add_connection(0,48); // source
this.bounding_box = [-24,0,8,48]; this.bounding_box = [-24,0,8,48];
this.update_coords(); this.update_coords();
} }
...@@ -2702,7 +2882,7 @@ schematic = (function() { ...@@ -2702,7 +2882,7 @@ schematic = (function() {
NFet.prototype.constructor = NFet; NFet.prototype.constructor = NFet;
NFet.prototype.toString = function() { NFet.prototype.toString = function() {
return '<NFet '+this.properties['scaled width']+'/'+this.properties['scaled length']+' ('+this.x+','+this.y+')>'; return '<NFet '+this.properties['W/L']+' ('+this.x+','+this.y+')>';
} }
NFet.prototype.draw = function(c) { NFet.prototype.draw = function(c) {
...@@ -2715,7 +2895,7 @@ schematic = (function() { ...@@ -2715,7 +2895,7 @@ schematic = (function() {
this.draw_line(c,-24,24,-12,24); this.draw_line(c,-24,24,-12,24);
this.draw_line(c,-12,16,-12,32); 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']) { if (this.properties['name']) {
this.draw_text(c,this.properties['name'],2,22,6,property_size); this.draw_text(c,this.properties['name'],2,22,6,property_size);
this.draw_text(c,dim,2,26,0,property_size); this.draw_text(c,dim,2,26,0,property_size);
...@@ -2724,8 +2904,7 @@ schematic = (function() { ...@@ -2724,8 +2904,7 @@ schematic = (function() {
} }
NFet.prototype.clone = function(x,y) { NFet.prototype.clone = function(x,y) {
return new NFet(x,y,this.rotation,this.properties['name'], return new NFet(x,y,this.rotation,this.properties['name'],this.properties['W/L']);
this.properties['scaled width'],this.properties['scaled length']);
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
...@@ -2734,14 +2913,13 @@ schematic = (function() { ...@@ -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); Component.call(this,'p',x,y,rotation);
this.properties['name'] = name; this.properties['name'] = name;
this.properties['scaled width'] = sw ? sw : '2'; this.properties['W/L'] = w_over_l ? w_over_l : '2';
this.properties['scaled length'] = sl ? sl : '1';
this.add_connection(0,0); // drain this.add_connection(0,0); // drain
this.add_connection(0,48); // source
this.add_connection(-24,24); // gate this.add_connection(-24,24); // gate
this.add_connection(0,48); // source
this.bounding_box = [-24,0,8,48]; this.bounding_box = [-24,0,8,48];
this.update_coords(); this.update_coords();
} }
...@@ -2749,7 +2927,7 @@ schematic = (function() { ...@@ -2749,7 +2927,7 @@ schematic = (function() {
PFet.prototype.constructor = PFet; PFet.prototype.constructor = PFet;
PFet.prototype.toString = function() { PFet.prototype.toString = function() {
return '<PFet '+this.properties['scaled width']+'/'+this.properties['scaled length']+' ('+this.x+','+this.y+')>'; return '<PFet '+this.properties['W/L']+' ('+this.x+','+this.y+')>';
} }
PFet.prototype.draw = function(c) { PFet.prototype.draw = function(c) {
...@@ -2764,7 +2942,7 @@ schematic = (function() { ...@@ -2764,7 +2942,7 @@ schematic = (function() {
this.draw_circle(c,-14,24,2,false); this.draw_circle(c,-14,24,2,false);
this.draw_line(c,-12,16,-12,32); 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']) { if (this.properties['name']) {
this.draw_text(c,this.properties['name'],2,22,6,property_size); this.draw_text(c,this.properties['name'],2,22,6,property_size);
this.draw_text(c,dim,2,26,0,property_size); this.draw_text(c,dim,2,26,0,property_size);
...@@ -2773,8 +2951,7 @@ schematic = (function() { ...@@ -2773,8 +2951,7 @@ schematic = (function() {
} }
PFet.prototype.clone = function(x,y) { PFet.prototype.clone = function(x,y) {
return new PFet(x,y,this.rotation,this.properties['name'], return new PFet(x,y,this.rotation,this.properties['name'],this.properties['W/L']);
this.properties['scaled width'],this.properties['scaled length']);
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
...@@ -2783,9 +2960,10 @@ schematic = (function() { ...@@ -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); Component.call(this,'o',x,y,rotation);
this.properties['name'] = name; this.properties['name'] = name;
this.properties['A'] = A ? A : '300000';
this.add_connection(0,0); // + this.add_connection(0,0); // +
this.add_connection(0,16); // - this.add_connection(0,16); // -
this.add_connection(48,8); // output this.add_connection(48,8); // output
...@@ -2796,7 +2974,7 @@ schematic = (function() { ...@@ -2796,7 +2974,7 @@ schematic = (function() {
OpAmp.prototype.constructor = OpAmp; OpAmp.prototype.constructor = OpAmp;
OpAmp.prototype.toString = function() { OpAmp.prototype.toString = function() {
return '<OpAmp ('+this.x+','+this.y+')>'; return '<OpAmp'+this.properties['A']+' ('+this.x+','+this.y+')>';
} }
OpAmp.prototype.draw = function(c) { OpAmp.prototype.draw = function(c) {
...@@ -2818,7 +2996,7 @@ schematic = (function() { ...@@ -2818,7 +2996,7 @@ schematic = (function() {
} }
OpAmp.prototype.clone = function(x,y) { 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() { ...@@ -2849,15 +3027,15 @@ schematic = (function() {
this.draw_line(c,0,36,0,48); this.draw_line(c,0,36,0,48);
if (this.type == 'v') { // voltage source if (this.type == 'v') { // voltage source
this.draw_text(c,'+',0,12,1,property_size); //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,'\u2013',0,36,7,property_size); // minus sign
// draw + and - // draw + and -
//this.draw_line(c,8,5,8,11); this.draw_line(c,0,15,0,21);
//this.draw_line(c,5,8,11,8); this.draw_line(c,-3,18,3,18);
//this.draw_line(c,5,40,11,40); this.draw_line(c,-3,30,3,30);
// draw V // 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 } else if (this.type == 'i') { // current source
// draw arrow: pos to neg // draw arrow: pos to neg
this.draw_line(c,0,16,0,32); this.draw_line(c,0,16,0,32);
...@@ -2897,11 +3075,49 @@ schematic = (function() { ...@@ -2897,11 +3075,49 @@ schematic = (function() {
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// //
// 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 // Module definition
// //
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
var module = { var module = {
'Schematic': Schematic, 'Schematic': Schematic,
'component_slider': component_slider,
} }
return module; return module;
}()); }());
// Things to abstract out to another file // Things to abstract out to another file
// 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";
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. The code below fixes it, but breaks Firefox.
// TODO: Check what happens with no network.
/*while((close_event_logged != "done") && (d.getTime() < t+500)) {
console.log(close_event_logged);
}*/
}
window.onbeforeunload = log_close;
function getCookie(name) { function getCookie(name) {
var cookieValue = null; var cookieValue = null;
if (document.cookie && document.cookie != '') { if (document.cookie && document.cookie != '') {
...@@ -199,23 +221,28 @@ function videoDestroy() { ...@@ -199,23 +221,28 @@ function videoDestroy() {
} }
function log_event(e, d) { function log_event(e, d) {
//$("#eventlog").append("<br>"); data = {
//$("#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, "event_type" : e,
"event" : JSON.stringify(d), "event" : JSON.stringify(d),
"page" : document.URL "page" : document.URL
}, }
function(data) { $.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) { function seek_slide(type,oe,value) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment