Ocs package
OCS : Octave Circuit Simulator
History and Motivation
Problem Formulation
Data Structure
File Formats
There are several ways of setting up the data structure for an OCS simulation. The first approach is to just assign the fields of the data structure via Octave commands, otherwise one can parse an ascii file written in (a subste of) SPICE netlist language or in OCS's own netlist specification language called IFF (Interchange File Format)
IFF netlists
The name IFF stands for "Intermediate File Format" or "Interchange File Format" it represents an ASCII file format for describing coupled electrical circuits, devices and systems. The IFF syntx described here is version 0.1b1.
A circuit description is comprised of a set of files of three different types:
 1 CIR (Circuit) file: an ASCII text file with filename <circuitname>.cir
 1 NMS (Names) file: an ASCII text file with filename <circuitname>.nms
 N >= 1 SBN (Subnet) files: a set of Mfunctions or DLDfunctions following the template described below.
SBN files are not necessarily specific to one circuit and can be grouped in libraries as long as the directory containing the library is added to the path when the IFF parser is run.
CIR file
The CIR file is divided into two sections describing the linear time–independent (LCR = linear circuit) and the non–linear and/or time–dependent (NLC = non–linear circuit) partitions of the circuit respectively. The syntax for the LCR and NLC section is identical. NLC can also contain linear elements, in fact the whole circuit could be described only by the NLC section but this could result in the evaluator unnecessarily recomputing local matrices for linear time–independent elements The content of CIR files is organized as follows:
Code: CIR file format 
cir := header nlc separator lcr separator ;
header := '%' version_id '$\nl$'
comment* ;
comment:= '%' text '$\nl$' ;
nlc := block* ;
block := blockcomment? blockheader pv_matrix vnum_matrix ;
block_comment := '%' text '$\nl$' ;
block_header := func section n_extvar n_par '$\nl$'
n_rows n_parnames '$\nl$'
par_name*;
section := string ;
n_extvar := number ;
n_par := number ;
n_rows := number ;
n_parnames := number ;
par_name := string ;
pv_matrix := matrix ;
vnum_matrix := matrix ;
matrix := number+ ;
separator := 'END $\nl$' ;
lcr := block* ;

where
 "version_id" is a string identifying the version on IFF in which the file is encoded
 "\n" is the newline character string that represents anything that the Octave command "s=fscanf(file,%s)" would parse as a string i.e. any sequence of chars without whitespace
 "text" is any sequence of chars without a \n, this differs from string because it can contain white–space number represents anything that the Octave command "s=fscanf(file,%g)" would parse as a number
 "func" is the name of a function to evaluate the elements described in the block
 "n_extvar" Is the number of external variables for the elements of a block
 "n_par" Is the number of parameters for the elements of a block
 "n_rows" Is the number of elements in a block
 n_parnames" Is the number of parameter names for the elements of a block, it corresponds to the number of par name entries. If "n_parnames" is 0 the line with the "par_names" is missing.
 "pv_matrix" Is a list of n_rows x n_par numbers separated by any character the Octave command "s=fscanf(file,%g)" would consider whitespace (including "\n"). Every row (a set of n par contiguous entries) in "pv_matrix" refers to an element of the circuit. The "n_par" numbers in a row represent the values of the parameters to be passed to the function that evaluates that element.
 "vnum_matrix" Is a list of "n_rows" x "n_extvar" numbers separated by any character the Octave command "s=fscanf(file,%g)" would consider whitespace (including \n). Every row (a set of "n_extvar" contiguous entries) in "vnum_matrix" refers to an element of the circuit. The "n_extvar" numbers in the row represent the global numbering of the element external variables.
NMS files
NMS files are meant to contain the names of the circuit variables, the format of NMS is just a list of variable names one on each row preceded by the variable number:
Code: CIR file format 
nms := version id ’\n’ comment∗ line∗ ; line := var number var name ;
var number := number ;
var name := string ;

the variable are ordered as follows:
 first all external variables of all elements in the order given by the global numbering of external variables as explicitly written in the CIR files
 then the internal variables of the elements in the same order as the corresponding elements appear in the CIR file ( internal variables of nonlinear elements first, then those of linear elements)
Notice that the number of internal variables of each element is not included in the IFF files. This is because elements with a number of internal variables that is huge, that depends on the value of some parameter, or even that changes in time (for example distributed elements treated with a FEM with adaptive meshing, a large linear sub circuit that is reduce via MOR...) and therefore it is more convenient to compute the number of internal variables when initializing the system.
SPICE netlists
SPICE .spc netlists are parsed via the function "prs_spice", which currently supports the set of "Element Cards" shown below with their instantiating syntax.
Code: Model evaluator file for simple MOSFET models 
 Capacitors:
Cname n+ n cvalue
 Diodes:
Cname anode knode modelname <parameters>
 MOS:
Mname gnode dnode snode bnode modelname <parameters>
N.B.: one instance of a MOS element MUST be preceeded
(everywhere in the file) by the declaration of the related
model. For instance:
.MODEL mynmos NMOS( k=1e4 Vth=0.1 rd=1e6)
M2 Vgate 0 Vdrain 0 mynmos
 Resistors:
Rname n+ n rvalue
 Voltage sources:
Vname n+ n <dcvalue> <transvalue>
Transvalue specifies a transient voltage source
SIN(VO VA FREQ TD THETA)
where:
* VO (offset)
* VA (amplitude)
* FREQ (frequency)
* TD (delay)
* THETA (damping factor)
* 0 to TD: V0
* TD to TSTOP: VO +
VA*exp((timeTD)*THETA)*sine(twopi*FREQ*(time+TD))
Currently the damping factor has no effect.
Pulse
PULSE(V1 V2 TD TR TF PW PER)
parameters meaning
* V1 (initial value)
* V2 (pulsed value)
* TD (delay time)
* TR (rise time)
* TF (fall time)
* PW (pulse width)
* PER (period)
Currently rise and fall time are not implemented yet.
 .MODEL cards Defines a model for semiconductor devices
.MODEL MNAME TYPE(PNAME1=PVAL1 PNAME2=PVAL2 ... )
TYPE can be:
* NMOS Nchannel MOSFET model
* PMOS Pchannel MOSFET model
* D diode model
The parameter "LEVEL" is currently assigned to the field
"section" in the call of the element functions by the solver.
Currently supported values for the parameter LEVEL for NMOS
and PMOS are:
* simple
* lincap
(see documentation of function Mdiode).
Currently supported values for the parameter LEVEL for D are:
* simple
(see documentation of functions Mnmosfet and Mpmosfet).

Tutorials
A CMOS AND GATE
Here we show how to set up the simulation of the CMOS AND gate in the figure. The circuit has
 9 Elements
 6 MOSFETs (3 ntype + 3 ptype)
 3 Voltage sources
For the ntype MOSFETs we use a very simple algebraic model defined by the following code
Code: Model evaluator file for simple MOSFET models 
function [a,b,c] = Mnmosfet (string, parameters, parameternames, extvar, intvar, t)
switch string
case 'simple',
rd = 1e6;
for ii=1:length(parameternames)
eval([parameternames{ii} "=",...
num2str(parameters(ii)) " ;"])
endfor
vg = extvar(1);
vs = extvar(2);
vd = extvar(3);
vb = extvar(4);
vgs = vgvs;
vds = vdvs;
if (vgs < Vth)
gm = 0;
gd = 1/rd;
id = vds*gd;
elseif ((vgsVth)>=(vds))&(vds>=0)
id = k*((vgsVth)*vds(vds^2)/2)+vds/rd;
gm = k*vds;
gd = k*(vgsVthvds)+1/rd;
elseif ((vgsVth)>=(vds))&(vds<0)
gm = 0;
gd = 1/rd;
id = vds*gd;
else # (i.e. if 0 <= vgsvth <= vds)
id = k*(vgsVth)^2/2+vds/rd;
gm = k*(vgsVth);
gd = 1/rd;
endif
a = zeros(4);
b = [ 0 0 0 0;
gm (gm+gd) gd 0;
gm (gm+gd) gd 0;
0 0 0 0];
c = [0 id id 0]';
break;
otherwise
error(["Mnmosfet: unknown option " string]);
endswitch
endfunction

The model for the ptype devices is entirely analogous.
Below we show three methods for constructing the circuit data structure
Once the circuit data structure is loaded the simulation can be started by the following commands
Code: Run the AND gate simulation 
x = [.5 .5 .33 .66 .5 1 0 0 1 ]';
t = linspace (0, .5, 100);
pltvars = {"Va", "Vb", "Va_and_b"};
dmp = .2;
tol = 1e15;
maxit = 100;
out = tst_backward_euler (outstruct, x, t, tol, maxit, pltvars);

Click on the figure to the right to see the simulation results
Build the AND GATE structure directly
Code: Build the AND GATE structure via an Octave script 
## NLC
# ntype
outstruct.NLC(1).func = "Mnmosfet";
outstruct.NLC(1).section = "simple";
outstruct.NLC(1).nextvar = 4;
outstruct.NLC(1).npar = 3;
outstruct.NLC(1).nparnames = 3;
outstruct.NLC(1).parnames = { "k", "Vth", "rd"};
outstruct.NLC(1).pvmatrix = [1.0000e04 1.0000e01 1.0000e+07
1.0000e04 1.0000e01 1.0000e+07
1.0000e04 1.0000e01 1.0000e+07];
outstruct.NLC(1).vnmatrix = [1 3 4 0
2 0 3 0
4 0 5 0];
outstruct.NLC(1).nintvar = [0 0 0];
outstruct.NLC(1).osintvar = [0 0 0];
# ptype
outstruct.NLC(2).func = "Mpmosfet";
outstruct.NLC(2).section = "simple";
outstruct.NLC(2).nextvar = 4;
outstruct.NLC(2).npar = 3;
outstruct.NLC(2).nparnames = 3;
outstruct.NLC(2).parnames = { "k", "Vth", "rd"};
outstruct.NLC(2).pvmatrix = [1.0000e04 1.0000e01 1.0000e+07
1.0000e04 1.0000e01 1.0000e+07
1.0000e04 1.0000e01 1.0000e+07];
outstruct.NLC(2).vnmatrix = [ 1 6 4 6
2 6 4 6
4 6 5 6];
outstruct.NLC(2).nintvar = [0 0 0];
outstruct.NLC(2).osintvar = [0 0 0];
# Va and Vb
outstruct.NLC(3).func = "Mvoltagesources";
outstruct.NLC(3).section = "sinwave";
outstruct.NLC(3).nextvar = 2;
outstruct.NLC(3).npar = 4;
outstruct.NLC(3).nparnames = 4;
outstruct.NLC(3).parnames = {"Ampl", "f", "delay", "shift"};
outstruct.NLC(3).pvmatrix = [0.50000 1.00000 0.00000 0.50000
0.50000 2.00000 0.25000 0.50000];
outstruct.NLC(3).vnmatrix = [ 1 0
2 0];
outstruct.NLC(3).nintvar = [1 1];
outstruct.NLC(3).osintvar = [0 0];
## LCR
# Vdd
outstruct.LCR(1).func = "Mvoltagesources";
outstruct.LCR(1).section = "DC";
outstruct.LCR(1).nextvar = 2;
outstruct.LCR(1).npar = 1;
outstruct.LCR(1).nparnames = 1;
outstruct.LCR(1).parnames = {"V"};
outstruct.LCR(1).pvmatrix = 1;
outstruct.LCR(1).vnmatrix = [6 0];
outstruct.LCR(1).nintvar = 1;
outstruct.LCR(1).osintvar = 2;
##
outstruct.namesn = [1 2 5 6 7 8 9];
outstruct.namess = {"Va", "Vb", "Va_and_b", "Vdd", "I1", "I2", "I3"};
outstruct.totextvar = 6;
outstruct.totintvar = 3;

Build the AND gate circuit structure parsing an IFF netlist
To parse an IFF format netlist of the CMOS AND gate we can use the following command
Code: Load the AND circuit structure parsing an IFF netlist 
outstruct = prs_iff ("and");

The IFF netlist consists of the .cir file named "and.cir" shown below
Code: IFF netlist for the AND gate (.cir file) 
% 0.1b1
% A Simple CMOS AND GATE
%
% NMosfets
% There are 3 NMosfets
Mnmosfet simple 4 3
3 3
k Vth rd
1e4 0.1 1e7
1e4 0.1 1e7
1e4 0.1 1e7
1 3 4 0
2 0 3 0
4 0 5 0
%
% PMosfets
Mpmosfet simple 4 3
3 3
k Vth rd
1e4 0.1 1e7
1e4 0.1 1e7
1e4 0.1 1e7
1 6 4 6
2 6 4 6
4 6 5 6
%
% Input voltage sources
Mvoltagesources sinwave 2 4
2 4
Ampl f delay shift
0.5 1 0.0 0.5
0.5 2 0.25 0.5
1 0
2 0
END
%
% Power supply
Mvoltagesources DC 2 1
1 1
V
1
6 0
END

and of the .nms file named "and.nms shown below
Code: IFF netlist for the AND gate (.nms file) 
% 0.1b1
1 Va
2 Vb
5 Va_and_b
6 Vdd
7 I1
8 I2
9 I3

Build the AND gate circuit structure parsing a .spc file
Code: Load the AND circuit structure parsing a .spc file 
outstruct = prs_spice ("and");

Code: The .spc file for the CMOS AND gate 
* AND (simple Algebraic MOSFET model)
.MODEL mynmos NMOS(LEVEL=simple k=2.94e05 Vth=0.08 rd=.957e7)
.MODEL mypmos PMOS( k=2.94e05 Vth=0.08 rd=.957e7)
M1 Va 3 4 0 mynmos
M2 Vb 0 3 0 mynmos
* nside of the inverter
M3 4 0 Va_and_b 0 mynmos
M4 Va Vdd 4 Vdd mypmos
M5 Vb Vdd 4 Vdd mypmos
* pside of the inverter
M6 4 Vdd Va_and_b Vdd mypmos
V1 Va 0 SIN(0.5 0.5 1 0 0)
V2 Vb 0 SIN(0.5 0.5 2 0.25 0)
V3 Vdd 0 1
.END
