Commit 7600920f by Piotr Mitros

CJT's new schematic editor

parent 25389b5a
......@@ -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 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;
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];
}
}
// find the operating point
var iterations = this.find_solution(load_ac);
// Compute the small signal response
var solac = solve_linear_system(matrixac);
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]);
// 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 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 A
Circuit.prototype.add_to_A = function(i,j,v) {
// add individual conductance to Gl matrix
Circuit.prototype.add_to_G = function(i,j,g) {
if (i >=0 && j >= 0)
this.matrix[i][j] += v;
this.G[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 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 [A | b]
// 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 <value> or <fun>(<value>,...)
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,81 +1128,92 @@ cktsim = (function() {
this.g = 1.0/v;
}
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
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) {
this.load_dc(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);
this.n1 = n1;
this.n2 = n2;
this.value = v;
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
}
Capacitor.prototype = new Device();
Capacitor.prototype.construction = Capacitor;
Diode.prototype = new Device();
Diode.prototype.constructor = Diode;
// 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;
Diode.prototype.load_linear = function(ckt) {
// Diode is not linear, has no linear piece.
}
Capacitor.prototype.finalize = function(ckt) {
// call us at the end of each timestep
ckt.end_of_timestep.push(this);
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);
}
Capacitor.prototype.end_of_timestep = function(ckt) {
// update state when timestep is accepted
this.prev_q = this.q;
this.prev_i = this.i;
Diode.prototype.load_tran = function(ckt,soln,rhs,time) {
this.load_dc(ckt,soln,rhs);
}
Capacitor.prototype.load_dc = function(ckt) {
// open circuit
Diode.prototype.load_ac = function(ckt) {
}
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;
///////////////////////////////////////////////////////////////////////////////
//
// Capacitor
//
///////////////////////////////////////////////////////////////////////////////
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
ckt.add_conductance(this.n1,this.n2,geq);
Capacitor.prototype.load_linear = function(ckt) {
// MNA stamp for capacitance matrix
ckt.add_capacitance(this.n1,this.n2,this.value);
}
// 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 is modeled as a voltage source (veq) with impedance (geq)
Inductor.prototype.constructor = Inductor;
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() {
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_dc = function(ckt,soln,rhs) {
// Inductor is a short at dc, so is linear.
}
Inductor.prototype.finalize = function(ckt) {
// call us at the end of each timestep
ckt.end_of_timestep.push(this);
Inductor.prototype.load_ac = function(ckt) {
}
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_tran = function(ckt) {
}
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);
///////////////////////////////////////////////////////////////////////////////
//
// 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;
Inductor.prototype.load_tran = function(ckt,soln) {
this.flux = this.value * soln[this.branch]; // set flux
Fet.prototype.load_linear = function(ckt) {
// FET's are nonlinear, just like javascript progammers
}
// 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;
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);
}
}
}
// 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);
Fet.prototype.load_tran = function(ckt,soln,rhs) {
this.load_dc(ckt,soln,rhs);
}
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);
Fet.prototype.load_ac = function(ckt) {
}
///////////////////////////////////////////////////////////////////////////////
//
// Module definition
......@@ -1021,6 +1331,7 @@ cktsim = (function() {
///////////////////////////////////////////////////////////////////////////////
var module = {
'Circuit': Circuit,
'parse_number': parse_number,
}
return module;
}());
//////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
//
// 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
//
// <input type="hidden" class="schematic" name="unique_form_id" value="JSON netlist..." .../>
......@@ -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 <input> 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) {
......@@ -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
//
////////////////////////////////////////////////////////////////////////////////
......@@ -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 '<Diode ('+this.x+','+this.y+')>';
return '<Diode '+this.properties['area']+' ('+this.x+','+this.y+')>';
}
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 '<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) {
......@@ -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 '<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) {
......@@ -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 '<OpAmp ('+this.x+','+this.y+')>';
return '<OpAmp'+this.properties['A']+' ('+this.x+','+this.y+')>';
}
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);
......@@ -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
//
///////////////////////////////////////////////////////////////////////////////
var module = {
'Schematic': Schematic,
'component_slider': component_slider,
}
return module;
}());
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