mirror of
https://git.code.sf.net/p/ngspice/ngspice
synced 2026-08-22 13:57:32 +02:00
CMC test suite
This commit is contained in:
Executable
+247
@@ -0,0 +1,247 @@
|
||||
#!/bin/sh
|
||||
eval 'exec perl -S -x -w $0 ${1+"$@"}'
|
||||
#!perl
|
||||
|
||||
#
|
||||
# compareSimulationResults.pl: program to do a toleranced comparison of compact model simulation results
|
||||
#
|
||||
# Rel Date Who Comments
|
||||
# ==== ========== ============= ========
|
||||
# 1.0 04/13/06 Colin McAndrew Initial version
|
||||
#
|
||||
|
||||
sub usage() {
|
||||
print "
|
||||
$prog: compare simulation results between two files
|
||||
|
||||
Usage: $prog [options] refFile simFile
|
||||
|
||||
Files:
|
||||
refFile reference results file
|
||||
simFile simulated results file
|
||||
|
||||
Options:
|
||||
-c CLIP match numbers n1 and n2 with abs(n1)<CLIP and abs(n2)<CLIP
|
||||
-n NDIGIT match numbers n1 and n2 if they are within 1 of the NDIGITth digit
|
||||
-r REL match any numbers n1 and n2 with abs(n1-n2)/(0.5*(abs(n1)+abs(n2)+abs(n1-n2)))<REL
|
||||
-d debug mode
|
||||
-h print this help message
|
||||
-i print info on file formats and structure
|
||||
-v verbose mode
|
||||
";
|
||||
} # End of usage
|
||||
|
||||
sub info() {
|
||||
print "
|
||||
This program numerically compares simulation results and returns a string
|
||||
that indicates the result of the comparision. Possible return values are:
|
||||
ERROR: cannot open file nameOfFile
|
||||
FAIL (probably from some simulation failure)
|
||||
FAIL (simulation output quantities differ)
|
||||
FAIL (number of results is different)
|
||||
FAIL (non-numeric results)
|
||||
DIFFER (max rel error is relErr)
|
||||
MATCH (within specified tolerances)
|
||||
MATCH (exact)
|
||||
|
||||
It is expected that each file is a columnar list of simulation results,
|
||||
with the first line being a title line that indicates column contents,
|
||||
and every other line being numerical simulation results.
|
||||
|
||||
The comparisons are done in the order
|
||||
exact
|
||||
clip
|
||||
nDigits
|
||||
relErr
|
||||
and passing one test means the results are considered to be the same.
|
||||
|
||||
Different tolerancing should be used for different types of simulations,
|
||||
and because of this mixing different types of simulation results in
|
||||
the one file is not recommended. Reasonable tolerances are:
|
||||
Quantity Clip nDigtis relTol
|
||||
DC current 1.0e-13 6 1.0e-06
|
||||
AC conductance 1.0e-20 6 1.0e-06
|
||||
capacitance 1.0e-20 6 1.0e-06
|
||||
noise 1.0e-30 5 1.0e-05
|
||||
Note that these numbers should be adjusted based on the precision
|
||||
to which the numbers to be compared are printed.
|
||||
|
||||
If no tolerances are specified, then the refFile name must contain
|
||||
one of the strings \"Dc\", \"Ac\", or \"Noise\" and the above values
|
||||
are used as default tolerances.
|
||||
|
||||
The UNIX utilities spiff and ndiff so toleranced numerical comparisons,
|
||||
but they seem not generally available now. Hence this simplified
|
||||
numerical comparison program is provided for verifying simulation
|
||||
results against reference test results. Also, clipping and comparison
|
||||
to a certain number of digits are more relevant for comparison of
|
||||
numbers printed in output (as compared to results held in memory),
|
||||
and these are not considered in other toleranced numerical comparisons.
|
||||
";
|
||||
} # End of info
|
||||
|
||||
#
|
||||
# Set program names and variables
|
||||
#
|
||||
|
||||
$\="\n";
|
||||
$,=" ";
|
||||
$debug=0;
|
||||
$verbose=0;
|
||||
@prog=split("/",$0);
|
||||
$prog=$prog[$#prog];
|
||||
$number='[+-]?\d+[\.]?\d*[eE][+-]?\d+|[+-]?[\.]\d+[eE][+-]?\d+|[+-]?\d+[\.]?\d*|[+-]?[\.]\d+';
|
||||
|
||||
for (;;) {
|
||||
if (!defined($ARGV[0])) {
|
||||
last;
|
||||
} elsif ($ARGV[0] =~ /^-c/i) {
|
||||
shift(@ARGV);
|
||||
die("ERROR: no clip value specified for -c option, stopped") if ($#ARGV < 0);
|
||||
$clip=$ARGV[0];
|
||||
die("ERROR: clip must be a positive number, stopped") if ($clip !~ /^$number$/ || $clip <= 0);
|
||||
} elsif ($ARGV[0] =~ /^-n/i) {
|
||||
shift(@ARGV);
|
||||
die("ERROR: no number of digits value specified for -n option, stopped") if ($#ARGV < 0);
|
||||
$nDigits=$ARGV[0];
|
||||
die("ERROR: nDigits must be a positive integer, stopped") if ($nDigits !~ /^[1-9][0-9]*$/);
|
||||
} elsif ($ARGV[0] =~ /^-r/i) {
|
||||
shift(@ARGV);
|
||||
die("ERROR: no relTol value specified for -r option, stopped") if ($#ARGV < 0);
|
||||
$relTol=$ARGV[0];
|
||||
die("ERROR: relTol must be a positive number, stopped") if ($relTol !~ /^$number$/ || $relTol <= 0);
|
||||
} elsif ($ARGV[0] =~ /^-d/i) {
|
||||
$debug=1;$verbose=1;
|
||||
} elsif ($ARGV[0] =~ /^-h/i) {
|
||||
&usage();exit(0);
|
||||
} elsif ($ARGV[0] =~ /^-i/i) {
|
||||
&usage();&info();exit(0);
|
||||
} elsif ($ARGV[0] =~ /^-v/i) {
|
||||
$verbose=1;
|
||||
} elsif ($ARGV[0] =~ /^-/) {
|
||||
&usage();
|
||||
die("ERROR: unknown flag $ARGV[0], stopped");
|
||||
} else {
|
||||
last;
|
||||
}
|
||||
shift(@ARGV);
|
||||
}
|
||||
if ($#ARGV<1) {
|
||||
&usage();exit(0);
|
||||
}
|
||||
|
||||
if (!defined($clip)) {
|
||||
if ($ARGV[0] =~ /Dc/i) {
|
||||
$clip=1.0e-13;
|
||||
} elsif ($ARGV[0] =~ /Ac/i) {
|
||||
$clip=1.0e-20;
|
||||
} elsif ($ARGV[0] =~ /Noise/i) {
|
||||
$clip=1.0e-30;
|
||||
} else {
|
||||
die("ERROR: must specify -c CLIP value if file is not Dc, Ac, or noise, stopped");
|
||||
}
|
||||
}
|
||||
if (!defined($relTol)) {
|
||||
if ($ARGV[0] =~ /Dc/i) {
|
||||
$relTol=1.0e-6;
|
||||
} elsif ($ARGV[0] =~ /Ac/i) {
|
||||
$relTol=1.0e-6;
|
||||
} elsif ($ARGV[0] =~ /Noise/i) {
|
||||
$relTol=1.0e-5;
|
||||
} else {
|
||||
die("ERROR: must specify -r RELTOL value if file is not Dc, Ac, or noise, stopped");
|
||||
}
|
||||
}
|
||||
if (!defined($nDigits)) {
|
||||
if ($ARGV[0] =~ /Dc/i) {
|
||||
$nDigits=6;
|
||||
} elsif ($ARGV[0] =~ /Ac/i) {
|
||||
$nDigits=6;
|
||||
} elsif ($ARGV[0] =~ /Noise/i) {
|
||||
$nDigits=5;
|
||||
} else {
|
||||
die("ERROR: must specify -n NDIGITS value if file is not Dc, Ac, or noise, stopped");
|
||||
}
|
||||
}
|
||||
|
||||
if ($ARGV[0] =~ /reference/i) {
|
||||
$reference="reference";
|
||||
} else {
|
||||
($reference=$ARGV[0])=~s/^.*\.//;
|
||||
}
|
||||
($variant=$ARGV[1])=~s/^.*\.//;
|
||||
$result=&compareResults($ARGV[0],$ARGV[1],$clip,$nDigits,$relTol);
|
||||
printf(" variant: %-20s(compared to: %-9s) %s\n",$variant,$reference,$result);
|
||||
|
||||
sub compareResults {
|
||||
use strict;
|
||||
my($refFile,$simFile,$clip,$nDigits,$relTol)=@_;
|
||||
my(@Ref,@Sim,$i,$j,$relErr,$maxRelErr,$absErr,$maxAbsErr);
|
||||
my(@RefRes,@SimRes,$matchType,$mag,$lo,$hi);
|
||||
|
||||
return("ERROR: cannot open file $refFile") if (!open(IF,"$refFile"));
|
||||
while (<IF>) {
|
||||
s/\s+$//;
|
||||
push(@Ref,$_);
|
||||
}
|
||||
close(IF);
|
||||
return("ERROR: cannot open file $simFile") if (!open(IF,"$simFile"));
|
||||
while (<IF>) {
|
||||
s/\s+$//;
|
||||
push(@Sim,$_);
|
||||
}
|
||||
close(IF);
|
||||
return("FAIL (probably from some simulation failure)") if ($#Ref != $#Sim || $#Sim<1);
|
||||
return("FAIL (simulation output quantities differ)") if ($Ref[0] ne $Sim[0]);
|
||||
$maxAbsErr=0;$maxRelErr=0;$matchType=0;
|
||||
for ($j=1;$j<=$#Ref;++$j) {
|
||||
@RefRes=split(/\s+/,$Ref[$j]);
|
||||
@SimRes=split(/\s+/,$Sim[$j]);
|
||||
return("FAIL (number of quantities simulated are different") if ($#RefRes != $#SimRes);
|
||||
for ($i=1;$i<=$#RefRes;++$i) { # ignore first column, this is the sweep variable
|
||||
if ($RefRes[$i] !~ /^$main::number$/ || $SimRes[$i] !~ /^$main::number$/) {
|
||||
return("FAIL (non-numeric results");
|
||||
}
|
||||
next if ($RefRes[$i] == $SimRes[$i]);
|
||||
$matchType=1 if ($matchType<1);
|
||||
#next if (abs($RefRes[$i]) < $clip && abs($SimRes[$i]) < $clip);
|
||||
next if (abs($RefRes[$i]) < $clip || abs($SimRes[$i]) < $clip);
|
||||
if ($RefRes[$i]*$SimRes[$i] <= 0.0) {
|
||||
$matchType=2 if ($matchType<2);
|
||||
$absErr=abs($RefRes[$i]-$SimRes[$i]);
|
||||
$relErr=$absErr/(0.5*(abs($RefRes[$i])+abs($SimRes[$i])+$absErr));
|
||||
$maxRelErr=$relErr if ($relErr > $maxRelErr);
|
||||
if ($main::verbose) {print STDERR $RefRes[$i],$SimRes[$i],100*$relErr."\%"}
|
||||
next;
|
||||
}
|
||||
$lo=abs($RefRes[$i]);
|
||||
if (abs($SimRes[$i]) < $lo) {
|
||||
$hi=$lo;
|
||||
$lo=abs($SimRes[$i]);
|
||||
} else {
|
||||
$hi=abs($SimRes[$i]);
|
||||
}
|
||||
$mag=int(log($lo)/log(10))+1;
|
||||
if ($lo < 1) {$mag-=1}
|
||||
$lo=int(0.5+$lo*10**($nDigits+1-$mag));
|
||||
$hi=int(0.5+$hi*10**($nDigits+1-$mag));
|
||||
next if (abs($lo-$hi)<=10);
|
||||
$absErr=abs($RefRes[$i]-$SimRes[$i]);
|
||||
$relErr=$absErr/(0.5*(abs($RefRes[$i])+abs($SimRes[$i])+$absErr));
|
||||
next if ($relErr<$relTol);
|
||||
if ($main::verbose) {print STDERR $RefRes[$i],$SimRes[$i],100*$relErr."\%"}
|
||||
$matchType=2 if ($matchType<2);
|
||||
$maxRelErr=$relErr if ($relErr > $maxRelErr);
|
||||
}
|
||||
}
|
||||
if ($matchType==0) {
|
||||
return("MATCH (exact)");
|
||||
} elsif ($matchType==1) {
|
||||
return("MATCH (within specified tolerances)");
|
||||
} elsif ($matchType==2) {
|
||||
$mag=int(log($maxRelErr)/log(10));
|
||||
$i=$maxRelErr/10**$mag;
|
||||
$maxRelErr=100*10**$mag*int(0.5+1e3*$i)/1e3;
|
||||
return("DIFFER (max rel error is $maxRelErr\%)");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,704 @@
|
||||
|
||||
#
|
||||
# perl module with subroutines used as part of compact model QA (runQaTests.pl)
|
||||
#
|
||||
|
||||
#
|
||||
# Rel Date Who Comments
|
||||
# ==== ========== ============= ========
|
||||
# 1.2 06/30/06 Colin McAndrew Floating node support added
|
||||
# Noise simulation added
|
||||
# Other general cleanup
|
||||
# 1.0 04/13/06 Colin McAndrew Initial version
|
||||
#
|
||||
|
||||
package modelQa;
|
||||
#use strict; # hirearchical read cannot be done while "strict refs" is in use
|
||||
|
||||
#
|
||||
# This subroutine processes the generic (not test specific) setup information.
|
||||
# It sets the information in global variables.
|
||||
#
|
||||
|
||||
sub processSetup {
|
||||
my(@Setup)=@_;
|
||||
my(@Field,$pin,$temperature);
|
||||
|
||||
undef(%main::isLinearScale);
|
||||
undef(%main::isAreaScale);
|
||||
undef(%main::DefaultTemperature);
|
||||
$main::doMfactorTest=0;
|
||||
$main::doScaleTest =0;
|
||||
$main::doShrinkTest =0;
|
||||
$main::doPinFlipTest=0;
|
||||
$main::doPNFlipTest =0;
|
||||
@main::Pin=();
|
||||
foreach (@Setup) {
|
||||
@Field=split(/[\s,]+/,$_);
|
||||
if (s/^keyLetter\s+//i) {
|
||||
if ($_ !~ /^[a-zA-Z]$/) {
|
||||
die("ERROR: bad keyLetter specification, stopped");
|
||||
}
|
||||
$main::keyLetter=$_;
|
||||
next;
|
||||
}
|
||||
if (s/^verilogaFile\s+//i) {
|
||||
$main::verilogaFile=$_;
|
||||
if (! -f $main::verilogaFile) {
|
||||
die("ERROR: cannot find file $main::verilogaFile, stopped");
|
||||
}
|
||||
next;
|
||||
}
|
||||
if (s/^(pins|terminals)\s+//i) {
|
||||
push(@main::Pin,@Field[1..$#Field]);
|
||||
foreach $pin (@main::Pin) {
|
||||
$main::isPin{$pin}=1;
|
||||
if ($pin !~ /^[a-zA-Z][a-zA-Z0-9]*$/) { # underscores are not allowed
|
||||
die("ERROR: bad pin name specification $pin, stopped");
|
||||
}
|
||||
}
|
||||
next;
|
||||
}
|
||||
if (/^symmetric(Pins|Terminals)/i) {
|
||||
if ($#Field != 2) {
|
||||
die("ERROR: bad symmetricPins specification, stopped");
|
||||
}
|
||||
$main::isSymmetryPin{$Field[1]}=1;
|
||||
$main::isSymmetryPin{$Field[2]}=1;
|
||||
$main::flipPin{$Field[1]}=$Field[2];
|
||||
$main::flipPin{$Field[2]}=$Field[1];
|
||||
$main::doPinFlipTest=1;
|
||||
next;
|
||||
}
|
||||
if (s/^pTypeSelectionArguments\s+//i) {
|
||||
s/\s*=\s*/=/g;
|
||||
$main::pTypeSelectionArguments=$_;
|
||||
next;
|
||||
}
|
||||
if (s/^(nType|type|model)SelectionArguments\s+//i) {
|
||||
s/\s*=\s*/=/g;
|
||||
$main::nTypeSelectionArguments=$_;
|
||||
next;
|
||||
}
|
||||
if (/^checkPolarity/i) {
|
||||
if ($#Field<1 || ($Field[1] !~ /^[yn01]/i)) {
|
||||
die("ERROR: bad checkPolarity specification, stopped");
|
||||
}
|
||||
if ($Field[1] =~ /^[y1]/i) {
|
||||
$main::doPNFlipTest=1;
|
||||
} else {
|
||||
$main::doPNFlipTest=0;
|
||||
}
|
||||
next;
|
||||
}
|
||||
if (/^scaleParameters/i) {
|
||||
foreach (@Field[1..$#Field]) {
|
||||
if (/^m$/i) {$main::doMfactorTest=1}
|
||||
if (/^scale$/i) {$main::doScaleTest =1}
|
||||
if (/^shrink$/i) {$main::doShrinkTest =1}
|
||||
}
|
||||
next;
|
||||
}
|
||||
if (/^linearScale/i) {
|
||||
foreach (@Field[1..$#Field]) {
|
||||
$main::isLinearScale{$_}=1;
|
||||
}
|
||||
next;
|
||||
}
|
||||
if (/^areaScale/i) {
|
||||
foreach (@Field[1..$#Field]) {
|
||||
$main::isAreaScale{$_}=1;
|
||||
}
|
||||
next;
|
||||
}
|
||||
if (/^temperature/i) {
|
||||
push(@main::DefaultTemperature,@Field[1..$#Field]);
|
||||
next;
|
||||
}
|
||||
if (/^float/i) {
|
||||
foreach (@Field[1..$#Field]) {
|
||||
$main::isGeneralFloatingPin{$_}=1;
|
||||
}
|
||||
next;
|
||||
}
|
||||
die("ERROR: unknown setup directive $Field[0], stopped");
|
||||
}
|
||||
if ($#main::Pin < 1) {
|
||||
die("ERROR: there must be two or more device pins, stopped");
|
||||
}
|
||||
foreach $pin (keys(%main::isSymmetryPin)) {
|
||||
if (!$main::isPin{$pin}) {
|
||||
die("ERROR: symmetry pin $pin is not a specified device pin, stopped");
|
||||
}
|
||||
}
|
||||
foreach $pin (keys(%main::isGeneralFloatingPin)) {
|
||||
if (!$main::isPin{$pin}) {
|
||||
die("ERROR: floating pin $pin is not a specified device pin, stopped");
|
||||
}
|
||||
}
|
||||
if (!defined(@main::DefaultTemperature)) {
|
||||
@main::DefaultTemperature=(27);
|
||||
}
|
||||
foreach $temperature (@main::DefaultTemperature) {
|
||||
if ($temperature!~/^$main::number$/) {
|
||||
die("ERROR: bad temperature value specified, stopped");
|
||||
}
|
||||
}
|
||||
if ($main::simulatorName =~ /mica|ads/i && defined($main::verilogaFile)) {
|
||||
$main::keyLetter="a";
|
||||
}
|
||||
if ($main::simulatorName =~ /hspice/i && defined($main::verilogaFile)) {
|
||||
$main::keyLetter="X";
|
||||
}
|
||||
if ($main::simulatorName =~ /spectre/i && !defined($main::keyLetter)) {
|
||||
$main::keyLetter="x";
|
||||
}
|
||||
if (!defined($main::keyLetter)) {
|
||||
die("ERROR: no keyLetter specified, stopped");
|
||||
}
|
||||
if (!defined($main::nTypeSelectionArguments)) {
|
||||
die("ERROR: no model selection arguments specified, stopped");
|
||||
}
|
||||
@main::Variants=("standard");
|
||||
if ($main::doPinFlipTest&&$main::doPNFlipTest) {
|
||||
push(@main::Variants,"Flip_N");
|
||||
push(@main::Variants,"noFlip_P");
|
||||
push(@main::Variants,"Flip_P");
|
||||
if (!defined($main::pTypeSelectionArguments)) {
|
||||
die("ERROR: no pType model selection arguments specified, stopped");
|
||||
}
|
||||
} elsif ($main::doPinFlipTest) {
|
||||
push(@main::Variants,"Flip_N");
|
||||
} elsif ($main::doPNFlipTest) {
|
||||
push(@main::Variants,"noFlip_P");
|
||||
if (!defined($main::pTypeSelectionArguments)) {
|
||||
die("ERROR: no pType model selection arguments specified, stopped");
|
||||
}
|
||||
}
|
||||
if ($main::doShrinkTest ) {push(@main::Variants,"shrink")}
|
||||
if ($main::doScaleTest ) {push(@main::Variants,"scale")}
|
||||
if ($main::doMfactorTest) {push(@main::Variants,"m")}
|
||||
}
|
||||
|
||||
#
|
||||
# This subroutine processes test specific setup information.
|
||||
# It sets the information in global variables.
|
||||
#
|
||||
|
||||
sub processTestSpec {
|
||||
my(@Spec)=@_;
|
||||
my($i,$arg,$temperature,$bias,$pin,@Field,$oneOverTwoPi,$name,$value,%isAnalysisPin,%AlreadyHave,%IndexFor);
|
||||
|
||||
$main::outputDc=0;$main::outputAc=0;$main::outputNoise=0;
|
||||
undef($main::biasSweepPin);undef($main::biasSweepSpec);undef(@main::BiasSweepList);
|
||||
undef($main::biasListPin);undef($main::biasListSpec);
|
||||
undef($main::frequencySpec);
|
||||
undef($main::Temperature);
|
||||
@main::InstanceParameters=();
|
||||
@main::Outputs=();
|
||||
@main::ModelParameters=();
|
||||
undef(%main::BiasFor);
|
||||
if (defined(%main::isGeneralFloatingPin)) {
|
||||
%main::isFloatingPin=%main::isGeneralFloatingPin;
|
||||
} else {
|
||||
undef(%main::isFloatingPin);
|
||||
}
|
||||
undef(%isAnalysisPin);
|
||||
undef(@main::Temperature);
|
||||
foreach (@Spec) {
|
||||
if (s/^output[s]?\s+//i) {
|
||||
s/\(/ /g;s/\)//g;
|
||||
@Field=split(/[\s,]+/,$_);
|
||||
for ($i=0;$i<=$#Field;++$i) {
|
||||
if ($Field[$i] =~ /^[IV]$/) {
|
||||
$main::outputDc=1;
|
||||
++$i;
|
||||
if (!$main::isPin{$Field[$i]}) {
|
||||
die("ERROR: pin $Field[$i] listed for DC output is not a specified pin, stopped");
|
||||
}
|
||||
push(@main::Outputs,$Field[$i]);
|
||||
}
|
||||
if ($Field[$i] =~ /^[CG]$/) {
|
||||
$main::outputAc=1;
|
||||
push(@main::Outputs,lc($Field[$i]));
|
||||
++$i;
|
||||
if (!$main::isPin{$Field[$i]}) {
|
||||
die("ERROR: pin $Field[$i] listed for AC output is not a specified pin, stopped");
|
||||
}
|
||||
$main::Outputs[$#main::Outputs].=" $Field[$i]";
|
||||
$isAnalysisPin{$Field[$i]}=1;
|
||||
++$i;
|
||||
if (!$main::isPin{$Field[$i]}) {
|
||||
die("ERROR: pin $Field[$i] listed for AC output is not a specified pin, stopped");
|
||||
}
|
||||
$main::Outputs[$#main::Outputs].=" $Field[$i]";
|
||||
$isAnalysisPin{$Field[$i]}=1;
|
||||
}
|
||||
if ($Field[$i] =~ /^N$/) {
|
||||
$main::outputNoise=1;
|
||||
++$i;
|
||||
if (!$main::isPin{$Field[$i]}) {
|
||||
die("ERROR: pin $Field[$i] listed for noise output is not a specified pin, stopped");
|
||||
}
|
||||
if ($#main::Outputs==0) {
|
||||
die("ERROR: can only specify one pin for noise output, stopped");
|
||||
}
|
||||
push(@main::Outputs,$Field[$i]);
|
||||
$isAnalysisPin{$Field[$i]}=1;
|
||||
}
|
||||
}
|
||||
next;
|
||||
}
|
||||
if (/^biases\s+/i) {
|
||||
@Field=split(/[\s,]+/,$_);
|
||||
for ($i=1;$i<=$#Field;++$i) {
|
||||
if ($Field[$i] !~ /=/) {
|
||||
die("ERROR: biases specifications must be V(pin)=number, stopped");
|
||||
}
|
||||
$Field[$i]=~s/V\s*\(\s*//;$Field[$i]=~s/\s*\)//;
|
||||
($pin,$bias)=split("=",$Field[$i]);
|
||||
if ($bias !~ /^$main::number$/) {
|
||||
die("ERROR: biases specifications must be V(pin)=number, stopped");
|
||||
}
|
||||
$main::BiasFor{$pin}=$bias;
|
||||
}
|
||||
next;
|
||||
}
|
||||
if (s/^(biasSweep|sweepBias)\s+//i) {
|
||||
if (defined($main::biasSweepSpec)) {
|
||||
die("ERROR: can only have one biasSweep specification, stopped");
|
||||
}
|
||||
s/V\s*\(\s*//i;s/\s*\)//;
|
||||
@Field=split(/[=,\s]+/,$_);
|
||||
if ($#Field!=3) {
|
||||
die("ERROR: biasSweep specification must be V(pin)=start,stop,step, stopped");
|
||||
}
|
||||
$main::biasSweepPin=$Field[0];
|
||||
if (($Field[1] !~ /^$main::number$/) || ($Field[2] !~ /^$main::number$/) || ($Field[3] !~ /^$main::number$/)) {
|
||||
die("ERROR: biasSweep start,stop,step must be numbers, stopped");
|
||||
}
|
||||
if ($Field[1] == $Field[2]) {
|
||||
die("ERROR: biasSweep start and stop must be different, stopped");
|
||||
}
|
||||
if ($Field[3] == 0.0) {
|
||||
die("ERROR: biasStep must be non-zero, stopped");
|
||||
}
|
||||
@main::BiasSweepList=();
|
||||
if ($Field[2] > $Field[1]) {
|
||||
$Field[3]=abs($Field[3]);
|
||||
for ($bias=$Field[1];$bias<=$Field[2]+0.1*$Field[3];$bias+=$Field[3]) {
|
||||
push(@main::BiasSweepList,$bias);
|
||||
}
|
||||
} else {
|
||||
$Field[3]=-1.0*abs($Field[3]);
|
||||
for ($bias=$Field[1];$bias>=$Field[2]+0.1*$Field[3];$bias+=$Field[3]) {
|
||||
push(@main::BiasSweepList,$bias);
|
||||
}
|
||||
}
|
||||
$main::biasSweepSpec=join(" ",@Field[1..3]);
|
||||
$main::BiasFor{$main::biasSweepPin}=$Field[1];
|
||||
next;
|
||||
}
|
||||
if (s/^(biasList|listBias)\s+//i) {
|
||||
if (defined($main::biasListSpec)) {
|
||||
die("ERROR: can only have one biasList specification, stopped");
|
||||
}
|
||||
s/V\s*\(\s*//i;s/\s*\)//;
|
||||
@Field=split(/[=,\s]+/,$_);
|
||||
if ($#Field < 2) {
|
||||
die("ERROR: biasList specification must be V(pin)=val1,val2,..., stopped");
|
||||
}
|
||||
$main::biasListPin=$Field[0];
|
||||
for ($i=1;$i<=$#Field;++$i) {
|
||||
if ($Field[$i] !~ /^$main::number$/) {
|
||||
die("ERROR: biasList values must be numbers, stopped");
|
||||
}
|
||||
}
|
||||
$main::biasListSpec=join(" ",@Field[1..$#Field]);
|
||||
$main::BiasFor{$main::biasListPin}=$Field[1];
|
||||
next;
|
||||
}
|
||||
if (s/^instanceParameters\s+//i) {
|
||||
foreach $arg (split(/\s+/,$_)) {
|
||||
if ($arg !~ /.=./) {
|
||||
die("ERROR: instance parameters must be name=value pairs, stopped");
|
||||
}
|
||||
($name,$value)=split(/=/,$arg);
|
||||
$value=~s/\(//;$value=~s/\)//; # get rid of possible parens
|
||||
if ($value !~ /^$main::number$/) {
|
||||
die("ERROR: instance parameter value in $arg is not a number, stopped");
|
||||
}
|
||||
push(@main::InstanceParameters,$arg);
|
||||
}
|
||||
next;
|
||||
}
|
||||
if (s/^modelParameters\s+//i) {
|
||||
foreach $arg (split(/\s+/,$_)) {
|
||||
if ($arg !~ /.=./ && ! -r $arg) {
|
||||
die("ERROR: model parameters must be name=value pairs or a file name, stopped");
|
||||
}
|
||||
if (-r $arg) {
|
||||
if (!open(IF,"$arg")) {
|
||||
die("ERROR: cannot open file $arg, stopped");
|
||||
}
|
||||
while (<IF>) {
|
||||
chomp;s/\s*=\s*/=/g;
|
||||
s/^\+\s*//;s/^\s+//;s/\s+$//;
|
||||
($name,$value)=split(/=/,$_);
|
||||
$value=~s/\(//;$value=~s/\)//; # get rid of possible parens
|
||||
if ($value !~ /^$main::number$/) {
|
||||
die("ERROR: model parameter value in $_ is not a number, stopped");
|
||||
}
|
||||
if (!defined($AlreadyHave{$name})) {
|
||||
push(@main::ModelParameters,"$name=$value");
|
||||
$AlreadyHave{$name}=1;
|
||||
$IndexFor{$name}=$#main::ModelParameters;
|
||||
} else {
|
||||
if ($AlreadyHave{$name} == 1 && $main::printWarnings) {
|
||||
printf("WARNING: parameter $name defined more than once, last value specified will be used\n");
|
||||
}
|
||||
$AlreadyHave{$name}=2;
|
||||
$main::ModelParameters[$IndexFor{$name}]="$name=$value";
|
||||
}
|
||||
}
|
||||
close(IF);
|
||||
} else {
|
||||
($name,$value)=split(/=/,$arg);
|
||||
$value=~s/\(//;$value=~s/\)//; # get rid of possible parens
|
||||
if ($value !~ /^$main::number$/) {
|
||||
die("ERROR: model parameter value in $arg is not a number, stopped");
|
||||
}
|
||||
if (!defined($AlreadyHave{$name})) {
|
||||
push(@main::ModelParameters,"$name=$value");
|
||||
$AlreadyHave{$name}=1;
|
||||
$IndexFor{$name}=$#main::ModelParameters;
|
||||
} else {
|
||||
if ($AlreadyHave{$name} == 1 && $main::printWarnings) {
|
||||
printf("WARNING: parameter $name defined more than once, last value specified will be used\n");
|
||||
}
|
||||
$AlreadyHave{$name}=2;
|
||||
$main::ModelParameters[$IndexFor{$name}]="$name=$value";
|
||||
}
|
||||
}
|
||||
}
|
||||
next;
|
||||
}
|
||||
if (s/^freq[uency]*\s+//i) {
|
||||
if (!/^(lin|oct|dec)\s+(\d+)\s+($main::number)\s+($main::number)$/) {
|
||||
die("ERROR: bad frequency sweep specification, stopped");
|
||||
}
|
||||
$main::fType=$1;$main::fSteps=$2;$main::fMin=$3;$main::fMax=$4;
|
||||
$main::frequencySpec=$_;
|
||||
next;
|
||||
}
|
||||
if (s/^temperature\s+//i) {
|
||||
push(@main::Temperature,split(/[,\s]+/,$_));
|
||||
foreach $temperature (@main::Temperature) {
|
||||
if ($temperature !~ /^$main::number$/) {
|
||||
die("ERROR: bad temperature value specified, stopped");
|
||||
}
|
||||
}
|
||||
next;
|
||||
}
|
||||
if (s/^float[ingpinode]*\s+//i) {
|
||||
@Field=split(/[\s,]+/,$_);
|
||||
if ($#Field < 0) {
|
||||
die("ERROR: bad floating pin specification, stopped");
|
||||
}
|
||||
foreach (@Field) {
|
||||
if (!$main::isPin{$_}) {
|
||||
die("ERROR: floating pin $_ is not a specified device pin, stopped");
|
||||
}
|
||||
$main::isFloatingPin{$_}=1;
|
||||
}
|
||||
next;
|
||||
}
|
||||
die("ERROR: unknown test directive\n$_\nstopped");
|
||||
}
|
||||
if (!defined(@main::Temperature)) {
|
||||
@main::Temperature=@main::DefaultTemperature;
|
||||
}
|
||||
if (abs($main::outputDc+$main::outputAc+$main::outputNoise-1) > 0.001) {
|
||||
die("ERROR: outputs specified must be one of DC, AC or noise, stopped");
|
||||
}
|
||||
if ($main::outputDc && !defined($main::biasSweepSpec)) {
|
||||
die("ERROR: no bias sweep spec defined for DC testing, stopped");
|
||||
}
|
||||
if ($main::outputNoise && !defined($main::frequencySpec)) {
|
||||
die("ERROR: no frequency spec defined for noise testing, stopped");
|
||||
}
|
||||
if ($main::outputAc && !defined($main::frequencySpec)) { # default for AC is omega=1
|
||||
$oneOverTwoPi=1.0/(8.0*atan2(1.0,1.0));
|
||||
$main::frequencySpec="lin 1 $oneOverTwoPi $oneOverTwoPi";
|
||||
$main::fType="lin";$main::fSteps=1;$main::fMin=$oneOverTwoPi;$main::fMax=$oneOverTwoPi;
|
||||
}
|
||||
if ($main::outputAc && ($main::frequencySpec eq "lin") && ($main::simulatorName =~ /hspice|ads/i)) {
|
||||
# AC spec is number of points, not number of steps, for hspice and ads
|
||||
++$main::fSteps if ($main::fMin != $main::fMax);
|
||||
$main::frequencySpec="$main::fType $main::fSteps $main::fMin $main::fMax";
|
||||
}
|
||||
foreach $pin (@main::Pin) {
|
||||
if (!defined($main::BiasFor{$pin}) && !defined($main::isFloatingPin{$pin})) {
|
||||
die("ERROR: a bias must be specified for all non-floating pins, stopped");
|
||||
}
|
||||
if ($main::isSymmetryPin{$pin} && $main::isFloatingPin{$pin}) {
|
||||
die("ERROR: a floating pin cannot be specified as a symmetry pin, stopped");
|
||||
}
|
||||
if ($isAnalysisPin{$pin} && $main::isFloatingPin{$pin}) {
|
||||
die("ERROR: a floating pin can only have its voltage measured in DC analyses, stopped");
|
||||
}
|
||||
}
|
||||
if (!defined($main::biasListPin)) { # if not specified make a dummy bias list, to simplify processing later
|
||||
$main::biasListPin="dummyPinNameThatIsNeverUsed";
|
||||
$main::biasListSpec="0";
|
||||
} elsif (defined($main::isFloatingPin{$main::biasListPin})) {
|
||||
die("ERROR: a bias list cannot be specified for a floating pin, stopped");
|
||||
}
|
||||
if (!defined($main::biasSweepPin)) { # if not specified make a dummy bias sweep, to simplify processing later
|
||||
if($main::biasListPin eq $main::Pin[0]) {
|
||||
$main::biasSweepPin=$main::Pin[1];
|
||||
} else {
|
||||
$main::biasSweepPin=$main::Pin[0];
|
||||
}
|
||||
$main::biasSweepSpec="$main::BiasFor{$main::biasSweepPin} $main::BiasFor{$main::biasSweepPin} 0";
|
||||
@main::BiasSweepList=($main::BiasFor{$main::biasSweepPin});
|
||||
} elsif (defined($main::isFloatingPin{$main::biasSweepPin})) {
|
||||
die("ERROR: a bias sweep cannot be specified for a floating pin, stopped");
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# This subroutine reads in a test specification file.
|
||||
# It cleans up the syntax by getting rid of comments
|
||||
# and continutation lines, processing conditionals,
|
||||
# and splitting up the contents of the file into
|
||||
# global specifications and individual test specifications.
|
||||
#
|
||||
# On call:
|
||||
# $main::qaSpecFile must be set to the name of the file that
|
||||
# contains the qaSpec information
|
||||
# On return:
|
||||
# @main::Setup contains general, test-nonspecific information
|
||||
# @main::Test contains a list of the test defined in the qaSpec file
|
||||
# %main::TestSpec contains each test specification, hash keys are @main::Test elements
|
||||
#
|
||||
|
||||
sub readQaSpecFile {
|
||||
my(@File,@RawFile,@Field);
|
||||
|
||||
@RawFile=&readHierarchicalFile($main::qaSpecFile);
|
||||
foreach (@RawFile) {
|
||||
s%\s*//.*%%; # eliminate C++ style comments
|
||||
s/^\s+//;s/\s+$//; # eliminate leading and trailing white space
|
||||
next if (/^$/); # ignore blank lines
|
||||
s/\s*=\s*/=/g; # eliminate space around "=" in name=value pairs
|
||||
if (/^\+/) { # process a continuation line
|
||||
s/^\+\s*//; # eliminate continuation "+" and any following whitespace
|
||||
$File[$#File]=~s/\s*\\$//; # get rid of possible additional continuation on previous line
|
||||
$File[$#File].=" $_"; # add to previous line
|
||||
next;
|
||||
}
|
||||
if (($#File >= 0) && ($File[$#File] =~ /\\$/)) { # add to previous line if that had an end-of-line continuation
|
||||
$File[$#File]=~s/\s*\\$//;
|
||||
$File[$#File].=" $_";
|
||||
} else {
|
||||
push(@File,$_);
|
||||
}
|
||||
}
|
||||
@File=&processIfdefs(\%main::Defined,@File); # process ifdef's to get conditional-free qaSpec
|
||||
@main::Test=();@main::Setup=();
|
||||
foreach (@File) { # process the qaSpec
|
||||
if (/^test(name)?\s+/i) {
|
||||
@Field=split;
|
||||
if ($#Field < 1) {
|
||||
die("ERROR: no test name specified for a test directive, stopped");
|
||||
}
|
||||
push(@main::Test,$Field[1]);
|
||||
@{$main::TestSpec{$main::Test[$#main::Test]}}=();
|
||||
next;
|
||||
}
|
||||
if ($#main::Test >= 0) {
|
||||
push(@{$main::TestSpec{$main::Test[$#main::Test]}},$_);
|
||||
} else {
|
||||
push(@main::Setup,$_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub readHierarchicalFile {
|
||||
my($fileName,$hierarchyLevel)=@_;
|
||||
my(@File,$FH,$includeFileName);
|
||||
|
||||
if (!defined($hierarchyLevel)) {
|
||||
$hierarchyLevel=0;
|
||||
}
|
||||
$FH="file".$hierarchyLevel;
|
||||
if (!open($FH,$fileName)) {
|
||||
die("ERROR: cannot open file $fileName, stopped");
|
||||
}
|
||||
@File=();
|
||||
while (<$FH>) {
|
||||
chomp;
|
||||
if (/^\s*\`include\s+/) {
|
||||
($includeFileName=$')=~s/"//g;
|
||||
++$hierarchyLevel;
|
||||
push(@File,&readHierarchicalFile($includeFileName,$hierarchyLevel));
|
||||
--$hierarchyLevel;
|
||||
} else {
|
||||
push(@File,$_);
|
||||
}
|
||||
}
|
||||
close($FH);
|
||||
return(@File);
|
||||
}
|
||||
|
||||
#
|
||||
# This subroutine processes the `ifdef statements (recursively, so nested `ifdef's are handled)
|
||||
# and returns the test specification with the appropriate blocks included and excluded.
|
||||
# Note that the simulator name is defined, so that
|
||||
# simulator specific directives are automatically included.
|
||||
#
|
||||
|
||||
sub processIfdefs {
|
||||
my($defRef,@Input)=@_;
|
||||
my(%Defined,$i,$block,@Field,$start,$middle,$end,$ifdefLevel,$maxIfdefLevel);
|
||||
my(@Insert);
|
||||
|
||||
%Defined=%$defRef;
|
||||
for ($i=0;$i<=$#Input;++$i) {
|
||||
if ($Input[$i] =~ /^`(define|undef)/) {
|
||||
@Field=split(/\s+/,$Input[$i]);
|
||||
if ($#Field > 0) {
|
||||
if ($Input[$i] =~ /^`define/) {
|
||||
$Defined{$Field[1]}=1;
|
||||
} else {
|
||||
$Defined{$Field[1]}=0;
|
||||
}
|
||||
}
|
||||
splice(@Input,$i,1);
|
||||
--$i;
|
||||
next;
|
||||
}
|
||||
if ($Input[$i] =~ /^`ifdef\s+/) {
|
||||
$start=$i;
|
||||
$ifdefLevel=1;$maxIfdefLevel=1;
|
||||
undef($middle);
|
||||
for ($end=$start+1;$end<=$#Input;++$end) {
|
||||
if ($Input[$end] =~ /^`ifdef/) {
|
||||
++$ifdefLevel;
|
||||
if ($ifdefLevel > $maxIfdefLevel) {$maxIfdefLevel=$ifdefLevel}
|
||||
}
|
||||
if ($Input[$end] =~ /^`end/) {--$ifdefLevel}
|
||||
if ($Input[$end] =~ /^`else/) {$middle=$end}
|
||||
last if ($ifdefLevel == 0);
|
||||
}
|
||||
if (($end > $#Input) && ($ifdefLevel > 0)) {
|
||||
die("ERROR: `ifdef not terminated, stopped");
|
||||
}
|
||||
($block=$Input[$i])=~s/^`ifdef\s+//;
|
||||
if ($maxIfdefLevel > 1) {
|
||||
if (!defined($middle)) {$middle=$end}
|
||||
@Insert=();
|
||||
if ($Defined{$block}) {
|
||||
if ($start+1 <= $middle-1) {
|
||||
@Insert=&processIfdefs(\%Defined,@Input[$start+1..$middle-1]);
|
||||
}
|
||||
} else {
|
||||
if ($middle+1 <= $end-1) {
|
||||
@Insert=&processIfdefs(\%Defined,@Input[$middle+1..$end-1]);
|
||||
}
|
||||
}
|
||||
splice(@Input,$start,$end-$start+1,@Insert);
|
||||
} else {
|
||||
if (!defined($middle)) {$middle=$end}
|
||||
@Insert=();
|
||||
if ($Defined{$block}) {
|
||||
if ($start+1 <= $middle-1) {
|
||||
@Insert=@Input[$start+1..$middle-1]
|
||||
}
|
||||
} else {
|
||||
if ($middle+1 <= $end-1) {
|
||||
@Insert=@Input[$middle+1..$end-1];
|
||||
}
|
||||
}
|
||||
splice(@Input,$start,$end-$start+1,@Insert);
|
||||
}
|
||||
--$i;
|
||||
next;
|
||||
}
|
||||
if ($Input[$i] =~ /^`/) {die("ERROR: bad directive\n$Input[$i]\nstopped")}
|
||||
}
|
||||
return(@Input);
|
||||
}
|
||||
|
||||
sub unScale {
|
||||
|
||||
#
|
||||
# call: $Result=&unScale($Scalar);
|
||||
#
|
||||
# If $Scalar is a SPICE-like scaled number then $Result is the value
|
||||
# of that number, else $Result is just $Scalar.
|
||||
#
|
||||
my($String)=@_;
|
||||
my($Result);
|
||||
|
||||
$Result=$String;
|
||||
if ($String =~ /^([+-]?[0-9]+[.]?[0-9]*|[+-]?[.][0-9]+)T/i) {
|
||||
$Result=$1*1e12;
|
||||
} elsif ($String =~ /^([+-]?[0-9]+[.]?[0-9]*|[+-]?[.][0-9]+)G/i) {
|
||||
$Result=$1*1e9;
|
||||
} elsif ($String =~ /^([+-]?[0-9]+[.]?[0-9]*|[+-]?[.][0-9]+)(M|meg|x)/) {
|
||||
$Result=$1*1e6;
|
||||
} elsif ($String =~ /^([+-]?[0-9]+[.]?[0-9]*|[+-]?[.][0-9]+)K/i) {
|
||||
$Result=$1*1e3;
|
||||
} elsif ($String =~ /^([+-]?[0-9]+[.]?[0-9]*|[+-]?[.][0-9]+)m/) {
|
||||
$Result=$1*1e-3;
|
||||
} if ($String =~ /^([+-]?[0-9]+[.]?[0-9]*|[+-]?[.][0-9]+)u/) {
|
||||
$Result=$1*1e-6;
|
||||
} elsif ($String =~ /^([+-]?[0-9]+[.]?[0-9]*|[+-]?[.][0-9]+)n/) {
|
||||
$Result=$1*1e-9;
|
||||
} elsif ($String =~ /^([+-]?[0-9]+[.]?[0-9]*|[+-]?[.][0-9]+)p/) {
|
||||
$Result=$1*1e-12;
|
||||
} elsif ($String =~ /^([+-]?[0-9]+[.]?[0-9]*|[+-]?[.][0-9]+)f/) {
|
||||
$Result=$1*1e-15;
|
||||
} elsif ($String =~ /^([+-]?[0-9]+[.]?[0-9]*|[+-]?[.][0-9]+)a/) {
|
||||
$Result=$1*1e-18;
|
||||
}
|
||||
return($Result);
|
||||
}
|
||||
|
||||
sub platform {
|
||||
|
||||
#
|
||||
# This subroutines returns a string that includes the processor
|
||||
# type, OS name, and OS version. This string is used as one level
|
||||
# of the directory hierarchy for storing test results, because
|
||||
# simulation results can vary with processor and OS.
|
||||
#
|
||||
# The UNIX uname command is used to get the appropriate information.
|
||||
# If the system appears to be Windows, then the perl Config module
|
||||
# information is used instead. However this information is generated
|
||||
# as part of the Perl build, and so may not relate to the machine
|
||||
# on which it is being run.
|
||||
#
|
||||
|
||||
use Config;
|
||||
my($osName,$osVer,$archName)=($modelQa::Config{osname},$modelQa::Config{osvers},$modelQa::Config{archname});
|
||||
my($platform);
|
||||
|
||||
if ($osName !~ /win/i) {
|
||||
open(UNAME,"uname -p|") or die("ERROR: cannot determine processore and OS information, stopped");
|
||||
chomp($archName=<UNAME>);close(UNAME);
|
||||
if ($archName eq "unknown") {
|
||||
open(UNAME,"uname -m|");chomp($archName=<UNAME>);close(UNAME);
|
||||
}
|
||||
open(UNAME,"uname -s|");chomp($osName=<UNAME>);close(UNAME);
|
||||
open(UNAME,"uname -r|");chomp($osVer =<UNAME>);close(UNAME);
|
||||
}
|
||||
$platform = "${archName}_${osName}_${osVer}";
|
||||
$platform =~ s/\(//;
|
||||
$platform =~ s/\)//;
|
||||
return($platform);
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -0,0 +1,474 @@
|
||||
|
||||
#
|
||||
# ngspice DC, AC and noise test routines
|
||||
#
|
||||
|
||||
#
|
||||
# Rel Date Who Comments
|
||||
# ==== ========== ============= ========
|
||||
# 1.0 05/13/11 Dietmar Warning Initial version
|
||||
#
|
||||
|
||||
package simulate;
|
||||
$simulatorCommand="ngspice";
|
||||
$netlistFile="ngspiceCkt";
|
||||
use strict;
|
||||
|
||||
sub version {
|
||||
return("22"); # the version only seems to be printed in interactive mode
|
||||
}
|
||||
|
||||
sub runNoiseTest {
|
||||
my($variant,$outputFile)=@_;
|
||||
my($arg,$name,$value,$type,$pin,$noisePin);
|
||||
my(@BiasList,$i,@Field);
|
||||
my(@X,@Noise,$temperature,$biasVoltage,$sweepVoltage,$inData);
|
||||
|
||||
#
|
||||
# Make up the netlist, using a subckt to encapsulate the
|
||||
# instance. This simplifies handling of the variants as
|
||||
# the actual instance is driven by voltage-controlled
|
||||
# voltage sources from the subckt pins, and the currents
|
||||
# are fed back to the subckt pins using current-controlled
|
||||
# current sources. Pin swapping, polarity reversal, and
|
||||
# m-factor scaling can all be handled by simple modifications
|
||||
# of this subckt.
|
||||
#
|
||||
|
||||
@X=();@Noise=();
|
||||
$noisePin=$main::Outputs[0];
|
||||
if ($main::fMin == $main::fMax) {
|
||||
$main::frequencySpec="lin 0 $main::fMin ".(10*$main::fMin); # spice3f5 bug workaround
|
||||
}
|
||||
foreach $temperature (@main::Temperature) {
|
||||
foreach $biasVoltage (split(/\s+/,$main::biasListSpec)) {
|
||||
if ($main::fMin == $main::fMax) {
|
||||
push(@X,@main::BiasSweepList);
|
||||
}
|
||||
foreach $sweepVoltage (@main::BiasSweepList) {
|
||||
if (!open(OF,">$simulate::netlistFile")) {
|
||||
die("ERROR: cannot open file $simulate::netlistFile, stopped");
|
||||
}
|
||||
print OF "* Noise simulation for $main::simulatorName";
|
||||
&generateCommonNetlistInfo($variant,$temperature);
|
||||
print OF "vin dummy 0 0 ac 1";
|
||||
print OF "rin dummy 0 1";
|
||||
foreach $pin (@main::Pin) {
|
||||
if ($main::isFloatingPin{$pin}) {
|
||||
print OF "i_$pin $pin 0 0";
|
||||
} elsif ($pin eq $main::biasListPin) {
|
||||
print OF "v_$pin $pin 0 $biasVoltage";
|
||||
} elsif ($pin eq $main::biasSweepPin) {
|
||||
print OF "v_$pin $pin 0 $sweepVoltage";
|
||||
} else {
|
||||
print OF "v_$pin $pin 0 $main::BiasFor{$pin}";
|
||||
}
|
||||
}
|
||||
print OF "x1 ".join(" ",@main::Pin)." mysub";
|
||||
print OF "hn 0 n_$noisePin v_$noisePin 1";
|
||||
print OF ".noise v(n_$noisePin) vin $main::frequencySpec";
|
||||
print OF ".print noise all";
|
||||
print OF ".end";
|
||||
close(OF);
|
||||
|
||||
#
|
||||
# Run simulations and get the results
|
||||
#
|
||||
|
||||
if (!open(SIMULATE,"$simulate::simulatorCommand < $simulate::netlistFile 2>/dev/null|")) {
|
||||
die("ERROR: cannot run $main::simulatorName, stopped");
|
||||
}
|
||||
$inData=0;
|
||||
while (<SIMULATE>) {
|
||||
chomp;s/^\s+//;s/\s+$//;s/,/ /g;
|
||||
if (/Index\s+frequency\s+inoise_spectrum\s+onoise_spectrum/i) {
|
||||
$inData=1;<SIMULATE>;next;
|
||||
}
|
||||
@Field=split;
|
||||
if (/\*/ || ($#Field != 3)) {$inData=0}
|
||||
next if (!$inData);
|
||||
if ($main::fMin == $main::fMax) {
|
||||
push(@Noise,1*$Field[3]);$inData=0;next; # spice3f5 bug workaround
|
||||
}
|
||||
push(@X,1*$Field[1]);
|
||||
push(@Noise,1*$Field[3]);
|
||||
}
|
||||
close(SIMULATE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Write the results to a file
|
||||
#
|
||||
|
||||
if (!open(OF,">$outputFile")) {
|
||||
die("ERROR: cannot open file $outputFile, stopped");
|
||||
}
|
||||
if ($main::fMin == $main::fMax) {
|
||||
printf OF ("V($main::biasSweepPin)");
|
||||
} else {
|
||||
printf OF ("Freq");
|
||||
}
|
||||
foreach (@main::Outputs) {
|
||||
printf OF (" N($_)");
|
||||
}
|
||||
printf OF ("\n");
|
||||
for ($i=0;$i<=$#X;++$i) {
|
||||
if (defined($Noise[$i])) {printf OF ("$X[$i] $Noise[$i]\n")}
|
||||
}
|
||||
close(OF);
|
||||
|
||||
#
|
||||
# Clean up, unless the debug flag was specified
|
||||
#
|
||||
|
||||
if (! $main::debug) {
|
||||
unlink($simulate::netlistFile);
|
||||
unlink("$simulate::netlistFile.st0");
|
||||
if (!opendir(DIRQA,".")) {
|
||||
die("ERROR: cannot open directory ., stopped");
|
||||
}
|
||||
foreach (grep(/^$simulate::netlistFile\.ic/,readdir(DIRQA))) {unlink($_)}
|
||||
closedir(DIRQA);
|
||||
}
|
||||
}
|
||||
|
||||
sub runAcTest {
|
||||
my($variant,$outputFile)=@_;
|
||||
my($arg,$name,$value,$type,$pin,$mPin,$fPin,%NextPin);
|
||||
my(@BiasList,$acStim,$i,@Field);
|
||||
my(@X,$omega,$twoPi,%g,%c,$temperature,$biasVoltage,$sweepVoltage,$inData,$outputLine);
|
||||
$twoPi=8.0*atan2(1.0,1.0);
|
||||
|
||||
#
|
||||
# Make up the netlist, using a subckt to encapsulate the
|
||||
# instance. This simplifies handling of the variants as
|
||||
# the actual instance is driven by voltage-controlled
|
||||
# voltage sources from the subckt pins, and the currents
|
||||
# are fed back to the subckt pins using current-controlled
|
||||
# current sources. Pin swapping, polarity reversal, and
|
||||
# m-factor scaling can all be handled by simple modifications
|
||||
# of this subckt.
|
||||
#
|
||||
|
||||
foreach $mPin (@main::Pin) {
|
||||
foreach $fPin (@main::Pin) {
|
||||
@{$g{$mPin,$fPin}}=();
|
||||
@{$c{$mPin,$fPin}}=();
|
||||
}
|
||||
}
|
||||
@X=();
|
||||
foreach $temperature (@main::Temperature) {
|
||||
foreach $biasVoltage (split(/\s+/,$main::biasListSpec)) {
|
||||
if ($main::fMin == $main::fMax) {
|
||||
push(@X,@main::BiasSweepList);
|
||||
}
|
||||
foreach $sweepVoltage (@main::BiasSweepList) {
|
||||
if (!open(OF,">$simulate::netlistFile")) {
|
||||
die("ERROR: cannot open file $simulate::netlistFile, stopped");
|
||||
}
|
||||
print OF "* AC simulation for $main::simulatorName";
|
||||
&generateCommonNetlistInfo($variant,$temperature);
|
||||
foreach $fPin (@main::Pin) {
|
||||
foreach $mPin (@main::Pin) {
|
||||
if ($mPin eq $fPin) {
|
||||
$acStim=" ac 1";
|
||||
} else {
|
||||
$acStim="";
|
||||
}
|
||||
if ($main::isFloatingPin{$mPin}) {
|
||||
print OF "i_${mPin}_$fPin ${mPin}_$fPin 0 0";
|
||||
} elsif ($mPin eq $main::biasListPin) {
|
||||
print OF "v_${mPin}_$fPin ${mPin}_$fPin 0 $biasVoltage$acStim";
|
||||
} elsif ($mPin eq $main::biasSweepPin) {
|
||||
print OF "v_${mPin}_$fPin ${mPin}_$fPin 0 $sweepVoltage$acStim";
|
||||
} else {
|
||||
print OF "v_${mPin}_$fPin ${mPin}_$fPin 0 $main::BiasFor{$mPin}$acStim";
|
||||
}
|
||||
}
|
||||
print OF "x_$fPin ".join("_$fPin ",@main::Pin)."_$fPin mysub";
|
||||
}
|
||||
print OF ".ac $main::frequencySpec";
|
||||
foreach $mPin (@main::Pin) {
|
||||
foreach $fPin (@main::Pin) {
|
||||
print OF ".print ac i(v_${mPin}_$fPin)";
|
||||
}
|
||||
}
|
||||
print OF ".end";
|
||||
close(OF);
|
||||
|
||||
#
|
||||
# Run simulations and get the results
|
||||
#
|
||||
|
||||
if (!open(SIMULATE,"$simulate::simulatorCommand < $simulate::netlistFile 2>/dev/null|")) {
|
||||
die("ERROR: cannot run $main::simulatorName, stopped");
|
||||
}
|
||||
$inData=0;
|
||||
while (<SIMULATE>) {
|
||||
chomp;s/^\s+//;s/\s+$//;s/,/ /g;
|
||||
if (/^Index\s+frequency\s+v_([a-zA-Z][a-zA-Z0-9]*)_([a-zA-Z][a-zA-Z0-9]*)#branch/i) {
|
||||
$mPin=$1;$fPin=$2;<SIMULATE>;$inData=1;next;
|
||||
}
|
||||
@Field=split;
|
||||
if (/^\*/ || ($#Field != 3)) {$inData=0;}
|
||||
next if (!$inData);
|
||||
if (($main::fMin != $main::fMax) && ($mPin eq $fPin) && ($mPin eq $main::Pin[0])) {
|
||||
push(@X,1*$Field[1]);
|
||||
}
|
||||
push(@{$g{$mPin,$fPin}},$Field[2]);
|
||||
$omega=$twoPi*$Field[1];
|
||||
if ($mPin eq $fPin) {
|
||||
push(@{$c{$mPin,$fPin}},$Field[3]/$omega);
|
||||
} else {
|
||||
push(@{$c{$mPin,$fPin}},-1*$Field[3]/$omega);
|
||||
}
|
||||
}
|
||||
close(SIMULATE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Write the results to a file
|
||||
#
|
||||
|
||||
if (!open(OF,">$outputFile")) {
|
||||
die("ERROR: cannot open file $outputFile, stopped");
|
||||
}
|
||||
if ($main::fMin == $main::fMax) {
|
||||
printf OF ("V($main::biasSweepPin)");
|
||||
} else {
|
||||
printf OF ("Freq");
|
||||
}
|
||||
foreach (@main::Outputs) {
|
||||
($type,$mPin,$fPin)=split(/\s+/,$_);
|
||||
printf OF (" $type($mPin,$fPin)");
|
||||
}
|
||||
printf OF ("\n");
|
||||
for ($i=0;$i<=$#X;++$i) {
|
||||
$outputLine="$X[$i]";
|
||||
foreach (@main::Outputs) {
|
||||
($type,$mPin,$fPin)=split(/\s+/,$_);
|
||||
if ($type eq "g") {
|
||||
if (defined(${$g{$mPin,$fPin}}[$i])) {
|
||||
$outputLine.=" ${$g{$mPin,$fPin}}[$i]";
|
||||
} else {
|
||||
undef($outputLine);last;
|
||||
}
|
||||
} else {
|
||||
if (defined(${$c{$mPin,$fPin}}[$i])) {
|
||||
$outputLine.=" ${$c{$mPin,$fPin}}[$i]";
|
||||
} else {
|
||||
undef($outputLine);last;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (defined($outputLine)) {printf OF ("$outputLine\n")}
|
||||
}
|
||||
close(OF);
|
||||
|
||||
#
|
||||
# Clean up, unless the debug flag was specified
|
||||
#
|
||||
|
||||
if (! $main::debug) {
|
||||
unlink($simulate::netlistFile);
|
||||
unlink("$simulate::netlistFile.st0");
|
||||
if (!opendir(DIRQA,".")) {
|
||||
die("ERROR: cannot open directory ., stopped");
|
||||
}
|
||||
foreach (grep(/^$simulate::netlistFile\.ic/,readdir(DIRQA))) {unlink($_)}
|
||||
closedir(DIRQA);
|
||||
}
|
||||
}
|
||||
|
||||
sub runDcTest {
|
||||
my($variant,$outputFile)=@_;
|
||||
my($arg,$name,$value,$i,@Field,$pin);
|
||||
my($start,$stop,$step);
|
||||
my(@V,%DC,$temperature,$biasVoltage);
|
||||
my($inData,$inResults);
|
||||
|
||||
#
|
||||
# Make up the netlist, using a subckt to encapsulate the
|
||||
# instance. This simplifies handling of the variants as
|
||||
# the actual instance is driven by voltage-controlled
|
||||
# voltage sources from the subckt pins, and the currents
|
||||
# are fed back to the subckt pins using current-controlled
|
||||
# current sources. Pin swapping, polarity reversal, and
|
||||
# m-factor scaling can all be handled by simple modifications
|
||||
# of this subckt.
|
||||
#
|
||||
|
||||
@V=();
|
||||
foreach $pin (@main::Outputs) {@{$DC{$pin}}=()}
|
||||
($start,$stop,$step)=split(/\s+/,$main::biasSweepSpec);
|
||||
$start-=$step;
|
||||
foreach $temperature (@main::Temperature) {
|
||||
foreach $biasVoltage (split(/\s+/,$main::biasListSpec)) {
|
||||
if (!open(OF,">$simulate::netlistFile")) {
|
||||
die("ERROR: cannot open file $simulate::netlistFile, stopped");
|
||||
}
|
||||
print OF "* DC simulation for $main::simulatorName";
|
||||
&generateCommonNetlistInfo($variant,$temperature);
|
||||
foreach $pin (@main::Pin) {
|
||||
if ($main::isFloatingPin{$pin}) {
|
||||
print OF "i_$pin $pin 0 0";
|
||||
} elsif ($pin eq $main::biasListPin) {
|
||||
print OF "v_$pin $pin 0 $biasVoltage";
|
||||
} elsif ($pin eq $main::biasSweepPin) {
|
||||
print OF "v_$pin $pin 0 $start";
|
||||
} else {
|
||||
print OF "v_$pin $pin 0 $main::BiasFor{$pin}";
|
||||
}
|
||||
}
|
||||
print OF "x1 ".join(" ",@main::Pin)." mysub";
|
||||
print OF ".dc v_$main::biasSweepPin $main::biasSweepSpec";
|
||||
foreach $pin (@main::Outputs) {
|
||||
if ($main::isFloatingPin{$pin}) {
|
||||
print OF ".print dc v($pin)";
|
||||
} else {
|
||||
print OF ".print dc i(v_$pin)";
|
||||
}
|
||||
}
|
||||
print OF ".end";
|
||||
close(OF);
|
||||
|
||||
#
|
||||
# Run simulations and get the results
|
||||
#
|
||||
|
||||
if (!open(SIMULATE,"$simulate::simulatorCommand < $simulate::netlistFile 2>/dev/null|")) {
|
||||
die("ERROR: cannot run $main::simulatorName, stopped");
|
||||
}
|
||||
$inResults=0;
|
||||
while (<SIMULATE>) {
|
||||
chomp;s/^\s+//;s/\s+$//;s/#branch//;s/\(/_/;s/\)//;
|
||||
if (/^Index\s+v-sweep\s+v_/i) {$inResults=1;($pin=$');<SIMULATE>;next}
|
||||
@Field=split;
|
||||
if ($#Field != 2) {$inResults=0}
|
||||
next if (!$inResults);
|
||||
if ($pin eq $main::Outputs[0]) {
|
||||
push(@V,$Field[1]);
|
||||
}
|
||||
push(@{$DC{$pin}},$Field[2]);
|
||||
}
|
||||
close(SIMULATE);
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Write the results to a file
|
||||
#
|
||||
|
||||
if (!open(OF,">$outputFile")) {
|
||||
die("ERROR: cannot open file $outputFile, stopped");
|
||||
}
|
||||
printf OF ("V($main::biasSweepPin)");
|
||||
foreach $pin (@main::Outputs) {
|
||||
if ($main::isFloatingPin{$pin}) {
|
||||
printf OF (" V($pin)");
|
||||
} else {
|
||||
printf OF (" I($pin)");
|
||||
}
|
||||
}
|
||||
printf OF ("\n");
|
||||
for ($i=0;$i<=$#V;++$i) {
|
||||
next if (abs($V[$i]-$start) < abs(0.1*$step)); # this is dummy first bias point
|
||||
printf OF ("$V[$i]");
|
||||
foreach $pin (@main::Outputs) {printf OF (" ${$DC{$pin}}[$i]")}
|
||||
printf OF ("\n");
|
||||
}
|
||||
close(OF);
|
||||
|
||||
#
|
||||
# Clean up, unless the debug flag was specified
|
||||
#
|
||||
|
||||
if (! $main::debug) {
|
||||
unlink($simulate::netlistFile);
|
||||
unlink("$simulate::netlistFile.st0");
|
||||
if (!opendir(DIRQA,".")) {
|
||||
die("ERROR: cannot open directory ., stopped");
|
||||
}
|
||||
foreach (grep(/^$simulate::netlistFile\.ic/,readdir(DIRQA))) {unlink($_)}
|
||||
closedir(DIRQA);
|
||||
}
|
||||
}
|
||||
|
||||
sub generateCommonNetlistInfo {
|
||||
my($variant,$temperature)=@_;
|
||||
my(@Pin_x,$arg,$name,$value,$eFactor,$fFactor,$pin);
|
||||
foreach $pin (@main::Pin) {push(@Pin_x,"${pin}_x")}
|
||||
print OF ".options temp=$temperature gmin=1e-15 abstol=1e-14 reltol=1e-8";
|
||||
if ($variant=~/^scale$/) {
|
||||
die("ERROR: there is no scale or shrink option for ngspice, stopped");
|
||||
}
|
||||
if ($variant=~/^shrink$/) {
|
||||
die("ERROR: there is no scale or shrink option for ngspice, stopped");
|
||||
}
|
||||
if ($variant=~/_P/) {
|
||||
$eFactor=-1;$fFactor=1;
|
||||
} else {
|
||||
$eFactor=1;$fFactor=-1;
|
||||
}
|
||||
if ($variant=~/^m$/) {
|
||||
if ($main::outputNoise) {
|
||||
$fFactor/=sqrt($main::mFactor);
|
||||
} else {
|
||||
$fFactor/=$main::mFactor;
|
||||
}
|
||||
}
|
||||
if (defined($main::verilogaFile)) {
|
||||
die("ERROR: Verilog-A model support is not implemented for ngspice, stopped");
|
||||
}
|
||||
print OF ".subckt mysub ".join(" ",@Pin_x);
|
||||
foreach $pin (@main::Pin) {
|
||||
if ($main::isFloatingPin{$pin}) { # assumed "dt" thermal pin, no scaling sign change
|
||||
print OF "v_$pin ${pin} ${pin}_x 0";
|
||||
} elsif ($variant=~/^Flip/ && defined($main::flipPin{$pin})) {
|
||||
print OF "e_$pin ${pin}_v 0 $main::flipPin{$pin}_x 0 $eFactor";
|
||||
print OF "v_$pin ${pin}_v ${pin} 0";
|
||||
print OF "f_$pin $main::flipPin{$pin}_x 0 v_$pin $fFactor";
|
||||
} else {
|
||||
print OF "e_$pin ${pin}_v 0 ${pin}_x 0 $eFactor";
|
||||
print OF "v_$pin ${pin}_v ${pin} 0";
|
||||
print OF "f_$pin ${pin}_x 0 v_$pin $fFactor";
|
||||
}
|
||||
}
|
||||
print OF "${main::keyLetter}1 ".join(" ",@main::Pin)." mymodel";
|
||||
foreach $arg (@main::InstanceParameters) {
|
||||
($name,$value)=split(/=/,$arg);
|
||||
if ($variant=~/^scale$/) {
|
||||
if ($main::isLinearScale{$name}) {
|
||||
$value/=$main::scaleFactor;
|
||||
} elsif ($main::isAreaScale{$name}) {
|
||||
$value/=$main::scaleFactor**2;
|
||||
}
|
||||
}
|
||||
if ($variant=~/^shrink$/) {
|
||||
if ($main::isLinearScale{$name}) {
|
||||
$value/=(1.0-$main::shrinkPercent*0.01);
|
||||
} elsif ($main::isAreaScale{$name}) {
|
||||
$value/=(1.0-$main::shrinkPercent*0.01)**2;
|
||||
}
|
||||
}
|
||||
print OF "+ $name=$value";
|
||||
}
|
||||
if ($variant eq "m") {
|
||||
print OF "+ m=$main::mFactor";
|
||||
}
|
||||
if ($variant=~/_P/) {
|
||||
print OF ".model mymodel $main::pTypeSelectionArguments";
|
||||
} else {
|
||||
print OF ".model mymodel $main::nTypeSelectionArguments";
|
||||
}
|
||||
foreach $arg (@main::ModelParameters) {
|
||||
print OF "+ $arg";
|
||||
}
|
||||
print OF ".ends";
|
||||
}
|
||||
|
||||
1;
|
||||
Executable
+335
@@ -0,0 +1,335 @@
|
||||
#!/bin/sh
|
||||
eval 'exec perl -S -x -w $0 ${1+"$@"}'
|
||||
#!perl
|
||||
|
||||
#
|
||||
# runQaTests.pl: program to run automated QA tests on compact models
|
||||
#
|
||||
# Rel Date Who Comments
|
||||
# ==== ========== ============= ========
|
||||
# 1.2 06/30/06 Colin McAndrew Floating node support added
|
||||
# 1.0 04/13/06 Colin McAndrew Initial version
|
||||
#
|
||||
|
||||
sub usage() {
|
||||
print "
|
||||
$prog: run model QA tests
|
||||
|
||||
Usage: $prog [options] -s simulatorName qaSpecificationFile
|
||||
|
||||
Files:
|
||||
qaSpecificationFile file with specifications for QA tests
|
||||
simulatorName name of simulator to be tested
|
||||
|
||||
Options:
|
||||
-c version platform do not try to simulate, only compare results for version and platform
|
||||
-d debug mode (leave intermediate files around)
|
||||
-h print this help message
|
||||
-i print info on file formats and structure
|
||||
-l list tests and variants that are defined
|
||||
-lt list tests that are defined
|
||||
-lv list test variants that are defined
|
||||
-nw do not print warning messages
|
||||
-platform prints the hardware platform and operating system version
|
||||
-p plot results (limited, only standard test variant)
|
||||
-P plot results (complete, for all test variants)
|
||||
-r re-use previously simulated results if they exist
|
||||
(default is to resimulate, even if results exist)
|
||||
-sv prints the simulator version being run
|
||||
-t TEST only run test TEST (can be a comma delimited list)
|
||||
-var VAR only run variant VAR (can be a comma delimited list)
|
||||
-v verbose mode
|
||||
-V really verbose mode, print out each difference detected
|
||||
";
|
||||
} # End of usage
|
||||
|
||||
sub info() {
|
||||
print "
|
||||
This program runs automated QA tests on a model.
|
||||
The test specifications are defined in the qaSpecificationFile
|
||||
Each test is run by setting up a netlist, running this is in
|
||||
the simulator in which the implementation of a model is being
|
||||
tested, and then collating the simulation results. Because the
|
||||
netlist formats, simulator commands, and output formats
|
||||
vary between simulators, a specific set of routines that
|
||||
run the tests must be provided for each simulator.
|
||||
|
||||
Please see the documentation for more details.
|
||||
";
|
||||
} # End of info
|
||||
|
||||
#
|
||||
# Set program names and variables
|
||||
#
|
||||
|
||||
$\="\n";
|
||||
$,=" ";
|
||||
undef($number);
|
||||
$number='[+-]?\d+[\.]?\d*[eE][+-]?\d+|[+-]?[\.]\d+[eE][+-]?\d+|[+-]?\d+[\.]?\d*|[+-]?[\.]\d+';
|
||||
undef($qaSpecFile);
|
||||
undef(@Setup);
|
||||
undef(@Test);
|
||||
undef(@Variants);
|
||||
$debug=0;
|
||||
$verbose=0;
|
||||
$reallyVerbose=0;
|
||||
$doPlot=0;
|
||||
$listTests=0;
|
||||
$listVariants=0;
|
||||
$onlyDoSimulatorVersion=0;
|
||||
$onlyDoPlatformVersion=0;
|
||||
$onlyDoComparison=0;
|
||||
$forceSimulation=1;
|
||||
$printWarnings=1;
|
||||
@prog=split("/",$0);
|
||||
$programDirectory=join("/",@prog[0..$#prog-1]);
|
||||
$prog=$prog[$#prog];
|
||||
|
||||
#
|
||||
# These variables are only defined once in this file,
|
||||
# and so generate unsightly warnings from the -w option
|
||||
# to perl, these undef's stop those warnings.
|
||||
#
|
||||
|
||||
undef($dcClip);undef($dcNdigit);undef($dcRelTol);
|
||||
undef($acClip);undef($acNdigit);undef($acRelTol);
|
||||
undef($noiseClip);undef($noiseNdigit);undef($noiseRelTol);
|
||||
undef($mFactor);undef($shrinkPercent);undef($scaleFactor);undef(%TestSpec);
|
||||
undef($refrnceDirectory);
|
||||
|
||||
#
|
||||
# These are the tolerances used to compare results
|
||||
#
|
||||
|
||||
$dcClip=1.0e-13;
|
||||
$dcNdigit=6;
|
||||
$dcRelTol=1.0e-6;
|
||||
|
||||
$acClip=1.0e-20;
|
||||
$acNdigit=6;
|
||||
$acRelTol=1.0e-6;
|
||||
|
||||
$noiseClip=1.0e-30;
|
||||
$noiseNdigit=5;
|
||||
$noiseRelTol=1.0e-5;
|
||||
|
||||
#
|
||||
# These are the values used to test shrink, scale, and m
|
||||
# (if they are requested to be tested).
|
||||
#
|
||||
|
||||
$scaleFactor=1.0e-6;
|
||||
$shrinkPercent=50;
|
||||
$sqrt_mFactor=10;
|
||||
$mFactor=$sqrt_mFactor*$sqrt_mFactor;
|
||||
|
||||
#
|
||||
# Parse the command line arguments
|
||||
#
|
||||
|
||||
for (;;) {
|
||||
if (!defined($ARGV[0])) {
|
||||
last;
|
||||
} elsif ($ARGV[0] =~ /^-c/) {
|
||||
shift(@ARGV);
|
||||
if ($#ARGV<0) {die("ERROR: no simulator version specified for -c option, stopped")}
|
||||
$version=$ARGV[0];
|
||||
shift(@ARGV);
|
||||
if ($#ARGV<0) {die("ERROR: no platform specified for -c option, stopped")}
|
||||
$platform=$ARGV[0];
|
||||
$onlyDoComparison=1;
|
||||
} elsif ($ARGV[0] =~ /^-d/i) {
|
||||
$debug=1;$verbose=1;
|
||||
} elsif ($ARGV[0] =~ /^-h/i) {
|
||||
&usage();exit(0);
|
||||
} elsif ($ARGV[0] =~ /^-i/i) {
|
||||
&usage();&info();exit(0);
|
||||
} elsif ($ARGV[0] =~ /^-lv/i) {
|
||||
$listVariants=1;
|
||||
} elsif ($ARGV[0] =~ /^-lt/i) {
|
||||
$listTests=1;
|
||||
} elsif ($ARGV[0] =~ /^-l/i) {
|
||||
$listTests=1;$listVariants=1;
|
||||
} elsif ($ARGV[0] =~ /^-platform/i) {
|
||||
$onlyDoPlatformVersion=1;
|
||||
} elsif ($ARGV[0] =~ /^-nw/i) {
|
||||
$printWarnings=0;
|
||||
} elsif ($ARGV[0] =~ /^-p/i) {
|
||||
$doPlot=1;
|
||||
} elsif ($ARGV[0] =~ /^-r/i) {
|
||||
$forceSimulation=0;
|
||||
} elsif ($ARGV[0] =~ /^-sv/i) {
|
||||
$onlyDoSimulatorVersion=1;
|
||||
} elsif ($ARGV[0] =~ /^-s/) {
|
||||
shift(@ARGV);
|
||||
if ($#ARGV<0) {die("ERROR: no simulator specified for -s option, stopped")}
|
||||
$simulatorName=$ARGV[0];
|
||||
} elsif ($ARGV[0] =~ /^-t/) {
|
||||
shift(@ARGV);
|
||||
if ($#ARGV<0) {die("ERROR: no test(s) specified for -t option, stopped")}
|
||||
foreach (split(/,/,$ARGV[0])) {$doTest{$_}=1}
|
||||
} elsif ($ARGV[0] =~ /^-var/) {
|
||||
shift(@ARGV);
|
||||
if ($#ARGV<0) {die("ERROR: no variant(s) specified for -var option, stopped")}
|
||||
foreach (split(/,/,$ARGV[0])) {$doVariant{$_}=1}
|
||||
} elsif ($ARGV[0] =~ /^-v/) {
|
||||
$verbose=1;
|
||||
} elsif ($ARGV[0] =~ /^-V/) {
|
||||
$verbose=1;$reallyVerbose=1;
|
||||
} elsif ($ARGV[0] =~ /^-/) {
|
||||
&usage();
|
||||
die("ERROR: unknown flag $ARGV[0], stopped");
|
||||
} else {
|
||||
last;
|
||||
}
|
||||
shift(@ARGV);
|
||||
}
|
||||
if ($onlyDoSimulatorVersion && !defined($simulatorName) && defined($ARGV[0])) {
|
||||
$simulatorName=$ARGV[0]; # assume -sv simulatorName was specified
|
||||
}
|
||||
if ($#ARGV<0 && !$onlyDoPlatformVersion && !($onlyDoSimulatorVersion && defined($simulatorName))) {
|
||||
&usage();exit(0);
|
||||
}
|
||||
if (!$onlyDoPlatformVersion && !defined($simulatorName)) {
|
||||
&usage();exit(0);
|
||||
}
|
||||
|
||||
#
|
||||
# Source perl modules with subroutines that are called to do all the work
|
||||
#
|
||||
|
||||
if (! require "$programDirectory/modelQaTestRoutines.pm") {
|
||||
die("ERROR: problem sourcing modelQaTestRoutines.pm, stopped");
|
||||
}
|
||||
if (!$onlyDoComparison) {
|
||||
$platform=&modelQa::platform();
|
||||
if ($onlyDoPlatformVersion) {
|
||||
print $platform;exit(0);
|
||||
}
|
||||
if (! -r "$programDirectory/$simulatorName.pm") {
|
||||
die("ERROR: there is no test routine Perl module for simulator $simulatorName, stopped");
|
||||
}
|
||||
if (! require "$programDirectory/$simulatorName.pm") {
|
||||
die("ERROR: problem sourcing test routine Perl module for simulator $simulatorName, stopped");
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Initial processing, set up directory names and process the QA specification file
|
||||
#
|
||||
|
||||
if (!$onlyDoComparison) {
|
||||
$version=&simulate::version();
|
||||
if ($onlyDoSimulatorVersion) {
|
||||
print $version;exit(0);
|
||||
}
|
||||
}
|
||||
$qaSpecFile=$ARGV[0];
|
||||
$resultsDirectory="results";
|
||||
$refrnceDirectory="reference";
|
||||
|
||||
undef(%Defined);
|
||||
$Defined{$simulatorName}=1; # any `ifdef's in the QA spec file for $simulatorName are automatically inlcuded
|
||||
&modelQa::readQaSpecFile();
|
||||
|
||||
&modelQa::processSetup(@Setup);
|
||||
|
||||
#
|
||||
# List tests and variants, if that was all that was requested
|
||||
# (note that the Makefile uses output from -lt and -lv
|
||||
# options to loop over the tests and variants individually,
|
||||
# so the output for those needs to be on a single line)
|
||||
#
|
||||
|
||||
if ($listTests || $listVariants) {
|
||||
if ($listTests && $listVariants) {
|
||||
print "\nTests:";
|
||||
foreach (@Test) {print " ".$_}
|
||||
print "\nVariants:";
|
||||
foreach (@Variants) {print " ".$_}
|
||||
} elsif ($listTests) {
|
||||
print @Test;
|
||||
} else {
|
||||
print @Variants;
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
|
||||
#
|
||||
# Loop over and run all tests
|
||||
# Note that the "standard" variant test is compared
|
||||
# to the reference results, whereas the other variant
|
||||
# tests are compared to the "standard" variant result.
|
||||
# This is because there may be some slight differences
|
||||
# between implementations, which get flagged when standard
|
||||
# is compared to reference, however for the other variants
|
||||
# this would generate a sequence of identical and in-exact
|
||||
# comparison messages. Each variant should *exactly* match
|
||||
# the standard, hence this is checked, and gives cleaner
|
||||
# looking output when the standard differs from the reference.
|
||||
#
|
||||
|
||||
if (!$onlyDoComparison) {
|
||||
if (! -d $resultsDirectory) {mkdir($resultsDirectory,0775)}
|
||||
$resultsDirectory.="/$simulatorName";
|
||||
if (! -d $resultsDirectory) {mkdir($resultsDirectory,0775)}
|
||||
$resultsDirectory.="/$version";
|
||||
if (! -d $resultsDirectory) {mkdir($resultsDirectory,0775)}
|
||||
$resultsDirectory.="/$platform";
|
||||
if (! -d $resultsDirectory) {mkdir($resultsDirectory,0775)}
|
||||
} else {
|
||||
$resultsDirectory.="/$simulatorName/$version/$platform";
|
||||
}
|
||||
|
||||
if ($reallyVerbose) {
|
||||
$flag="-v";
|
||||
} else {
|
||||
$flag="";
|
||||
}
|
||||
foreach $test (@Test) {
|
||||
next if (defined(%doTest) && !$doTest{$test});
|
||||
|
||||
if ($verbose) {print "\n****** Running test ($simulatorName): $test"}
|
||||
|
||||
undef($outputDc);
|
||||
undef($outputAc);
|
||||
undef($outputNoise);
|
||||
&modelQa::processTestSpec(@{$TestSpec{$test}});
|
||||
foreach $variant (@Variants) {
|
||||
if ($variant eq "standard") {
|
||||
$refFile="$refrnceDirectory/$test.standard";
|
||||
} else {
|
||||
$refFile="$resultsDirectory/$test.standard";
|
||||
}
|
||||
next if (defined(%doVariant) && !$doVariant{$variant});
|
||||
$simFile="$resultsDirectory/$test.$variant";
|
||||
if ($outputDc) {
|
||||
if (($forceSimulation || ! -r $simFile) && !$onlyDoComparison) {
|
||||
&simulate::runDcTest($variant,$simFile);
|
||||
}
|
||||
$clip=$dcClip;$relTol=$dcRelTol;$ndigit=$dcNdigit;
|
||||
}
|
||||
if ($outputAc) {
|
||||
if (($forceSimulation || ! -r $simFile) && !$onlyDoComparison) {
|
||||
&simulate::runAcTest($variant,$simFile);
|
||||
}
|
||||
$clip=$acClip;$relTol=$acRelTol;$ndigit=$acNdigit;
|
||||
}
|
||||
if ($outputNoise) {
|
||||
if (($forceSimulation || ! -r $simFile) && !$onlyDoComparison) {
|
||||
&simulate::runNoiseTest($variant,$simFile);
|
||||
}
|
||||
$clip=$noiseClip;$relTol=$noiseRelTol;$ndigit=$noiseNdigit;
|
||||
}
|
||||
if (-r $refFile && -r $simFile) {
|
||||
$message=sprintf(" variant: %-20s************************ comparison failed\n",$variant);
|
||||
if (open(IF,"$programDirectory/compareSimulationResults.pl $flag -c $clip -r $relTol -n $ndigit $refFile $simFile|")) {
|
||||
while (<IF>) {chomp;$message=$_;}
|
||||
close(IF);
|
||||
}
|
||||
print $message;
|
||||
} else {
|
||||
printf(" variant: %-20s************************ no results to compare to\n",$variant);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,476 @@
|
||||
|
||||
#
|
||||
# spice3f5 DC, AC and noise test routines
|
||||
#
|
||||
|
||||
#
|
||||
# Rel Date Who Comments
|
||||
# ==== ========== ============= ========
|
||||
# 1.2 06/30/06 Colin McAndrew Floating node support added
|
||||
# Noise simulation added
|
||||
# 1.0 04/13/06 Colin McAndrew Initial version
|
||||
#
|
||||
|
||||
package simulate;
|
||||
$simulatorCommand="spice3";
|
||||
$netlistFile="spiceCkt";
|
||||
use strict;
|
||||
|
||||
sub version {
|
||||
return("3f5"); # the version only seems to be printed in interactive mode
|
||||
}
|
||||
|
||||
sub runNoiseTest {
|
||||
my($variant,$outputFile)=@_;
|
||||
my($arg,$name,$value,$type,$pin,$noisePin);
|
||||
my(@BiasList,$i,@Field);
|
||||
my(@X,@Noise,$temperature,$biasVoltage,$sweepVoltage,$inData);
|
||||
|
||||
#
|
||||
# Make up the netlist, using a subckt to encapsulate the
|
||||
# instance. This simplifies handling of the variants as
|
||||
# the actual instance is driven by voltage-controlled
|
||||
# voltage sources from the subckt pins, and the currents
|
||||
# are fed back to the subckt pins using current-controlled
|
||||
# current sources. Pin swapping, polarity reversal, and
|
||||
# m-factor scaling can all be handled by simple modifications
|
||||
# of this subckt.
|
||||
#
|
||||
|
||||
@X=();@Noise=();
|
||||
$noisePin=$main::Outputs[0];
|
||||
if ($main::fMin == $main::fMax) {
|
||||
$main::frequencySpec="lin 0 $main::fMin ".(10*$main::fMin); # spice3f5 bug workaround
|
||||
}
|
||||
foreach $temperature (@main::Temperature) {
|
||||
foreach $biasVoltage (split(/\s+/,$main::biasListSpec)) {
|
||||
if ($main::fMin == $main::fMax) {
|
||||
push(@X,@main::BiasSweepList);
|
||||
}
|
||||
foreach $sweepVoltage (@main::BiasSweepList) {
|
||||
if (!open(OF,">$simulate::netlistFile")) {
|
||||
die("ERROR: cannot open file $simulate::netlistFile, stopped");
|
||||
}
|
||||
print OF "* Noise simulation for $main::simulatorName";
|
||||
&generateCommonNetlistInfo($variant,$temperature);
|
||||
print OF "vin dummy 0 0 ac 1";
|
||||
print OF "rin dummy 0 1";
|
||||
foreach $pin (@main::Pin) {
|
||||
if ($main::isFloatingPin{$pin}) {
|
||||
print OF "i_$pin $pin 0 0";
|
||||
} elsif ($pin eq $main::biasListPin) {
|
||||
print OF "v_$pin $pin 0 $biasVoltage";
|
||||
} elsif ($pin eq $main::biasSweepPin) {
|
||||
print OF "v_$pin $pin 0 $sweepVoltage";
|
||||
} else {
|
||||
print OF "v_$pin $pin 0 $main::BiasFor{$pin}";
|
||||
}
|
||||
}
|
||||
print OF "x1 ".join(" ",@main::Pin)." mysub";
|
||||
print OF "hn 0 n_$noisePin v_$noisePin 1";
|
||||
print OF ".noise v(n_$noisePin) vin $main::frequencySpec";
|
||||
print OF ".print noise all";
|
||||
print OF ".end";
|
||||
close(OF);
|
||||
|
||||
#
|
||||
# Run simulations and get the results
|
||||
#
|
||||
|
||||
if (!open(SIMULATE,"$simulate::simulatorCommand < $simulate::netlistFile 2>/dev/null|")) {
|
||||
die("ERROR: cannot run $main::simulatorName, stopped");
|
||||
}
|
||||
$inData=0;
|
||||
while (<SIMULATE>) {
|
||||
chomp;s/^\s+//;s/\s+$//;s/,/ /g;
|
||||
if (/Index\s+frequency\s+inoise_spectrum\s+onoise_spectrum/i) {
|
||||
$inData=1;<SIMULATE>;next;
|
||||
}
|
||||
@Field=split;
|
||||
if (/\*/ || ($#Field != 3)) {$inData=0}
|
||||
next if (!$inData);
|
||||
if ($main::fMin == $main::fMax) {
|
||||
push(@Noise,1*$Field[3]);$inData=0;next; # spice3f5 bug workaround
|
||||
}
|
||||
push(@X,1*$Field[1]);
|
||||
push(@Noise,1*$Field[3]);
|
||||
}
|
||||
close(SIMULATE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Write the results to a file
|
||||
#
|
||||
|
||||
if (!open(OF,">$outputFile")) {
|
||||
die("ERROR: cannot open file $outputFile, stopped");
|
||||
}
|
||||
if ($main::fMin == $main::fMax) {
|
||||
printf OF ("V($main::biasSweepPin)");
|
||||
} else {
|
||||
printf OF ("Freq");
|
||||
}
|
||||
foreach (@main::Outputs) {
|
||||
printf OF (" N($_)");
|
||||
}
|
||||
printf OF ("\n");
|
||||
for ($i=0;$i<=$#X;++$i) {
|
||||
if (defined($Noise[$i])) {printf OF ("$X[$i] $Noise[$i]\n")}
|
||||
}
|
||||
close(OF);
|
||||
|
||||
#
|
||||
# Clean up, unless the debug flag was specified
|
||||
#
|
||||
|
||||
if (! $main::debug) {
|
||||
unlink($simulate::netlistFile);
|
||||
unlink("$simulate::netlistFile.st0");
|
||||
if (!opendir(DIRQA,".")) {
|
||||
die("ERROR: cannot open directory ., stopped");
|
||||
}
|
||||
foreach (grep(/^$simulate::netlistFile\.ic/,readdir(DIRQA))) {unlink($_)}
|
||||
closedir(DIRQA);
|
||||
}
|
||||
}
|
||||
|
||||
sub runAcTest {
|
||||
my($variant,$outputFile)=@_;
|
||||
my($arg,$name,$value,$type,$pin,$mPin,$fPin,%NextPin);
|
||||
my(@BiasList,$acStim,$i,@Field);
|
||||
my(@X,$omega,$twoPi,%g,%c,$temperature,$biasVoltage,$sweepVoltage,$inData,$outputLine);
|
||||
$twoPi=8.0*atan2(1.0,1.0);
|
||||
|
||||
#
|
||||
# Make up the netlist, using a subckt to encapsulate the
|
||||
# instance. This simplifies handling of the variants as
|
||||
# the actual instance is driven by voltage-controlled
|
||||
# voltage sources from the subckt pins, and the currents
|
||||
# are fed back to the subckt pins using current-controlled
|
||||
# current sources. Pin swapping, polarity reversal, and
|
||||
# m-factor scaling can all be handled by simple modifications
|
||||
# of this subckt.
|
||||
#
|
||||
|
||||
foreach $mPin (@main::Pin) {
|
||||
foreach $fPin (@main::Pin) {
|
||||
@{$g{$mPin,$fPin}}=();
|
||||
@{$c{$mPin,$fPin}}=();
|
||||
}
|
||||
}
|
||||
@X=();
|
||||
foreach $temperature (@main::Temperature) {
|
||||
foreach $biasVoltage (split(/\s+/,$main::biasListSpec)) {
|
||||
if ($main::fMin == $main::fMax) {
|
||||
push(@X,@main::BiasSweepList);
|
||||
}
|
||||
foreach $sweepVoltage (@main::BiasSweepList) {
|
||||
if (!open(OF,">$simulate::netlistFile")) {
|
||||
die("ERROR: cannot open file $simulate::netlistFile, stopped");
|
||||
}
|
||||
print OF "* AC simulation for $main::simulatorName";
|
||||
&generateCommonNetlistInfo($variant,$temperature);
|
||||
foreach $fPin (@main::Pin) {
|
||||
foreach $mPin (@main::Pin) {
|
||||
if ($mPin eq $fPin) {
|
||||
$acStim=" ac 1";
|
||||
} else {
|
||||
$acStim="";
|
||||
}
|
||||
if ($main::isFloatingPin{$mPin}) {
|
||||
print OF "i_${mPin}_$fPin ${mPin}_$fPin 0 0";
|
||||
} elsif ($mPin eq $main::biasListPin) {
|
||||
print OF "v_${mPin}_$fPin ${mPin}_$fPin 0 $biasVoltage$acStim";
|
||||
} elsif ($mPin eq $main::biasSweepPin) {
|
||||
print OF "v_${mPin}_$fPin ${mPin}_$fPin 0 $sweepVoltage$acStim";
|
||||
} else {
|
||||
print OF "v_${mPin}_$fPin ${mPin}_$fPin 0 $main::BiasFor{$mPin}$acStim";
|
||||
}
|
||||
}
|
||||
print OF "x_$fPin ".join("_$fPin ",@main::Pin)."_$fPin mysub";
|
||||
}
|
||||
print OF ".ac $main::frequencySpec";
|
||||
foreach $mPin (@main::Pin) {
|
||||
foreach $fPin (@main::Pin) {
|
||||
print OF ".print ac i(v_${mPin}_$fPin)";
|
||||
}
|
||||
}
|
||||
print OF ".end";
|
||||
close(OF);
|
||||
|
||||
#
|
||||
# Run simulations and get the results
|
||||
#
|
||||
|
||||
if (!open(SIMULATE,"$simulate::simulatorCommand < $simulate::netlistFile 2>/dev/null|")) {
|
||||
die("ERROR: cannot run $main::simulatorName, stopped");
|
||||
}
|
||||
$inData=0;
|
||||
while (<SIMULATE>) {
|
||||
chomp;s/^\s+//;s/\s+$//;s/,/ /g;
|
||||
if (/^Index\s+frequency\s+v_([a-zA-z][a-zA-Z0-9]*)_([a-zA-z][a-zA-Z0-9]*)#branch/i) {
|
||||
$mPin=$1;$fPin=$2;<SIMULATE>;$inData=1;next;
|
||||
}
|
||||
@Field=split;
|
||||
if (/^\*/ || ($#Field != 4)) {$inData=0}
|
||||
next if (!$inData);
|
||||
if (($main::fMin != $main::fMax) && ($mPin eq $fPin) && ($mPin eq $main::Pin[0])) {
|
||||
push(@X,1*$Field[1]);
|
||||
}
|
||||
push(@{$g{$mPin,$fPin}},$Field[3]);
|
||||
$omega=$twoPi*$Field[1];
|
||||
if ($mPin eq $fPin) {
|
||||
push(@{$c{$mPin,$fPin}},$Field[4]/$omega);
|
||||
} else {
|
||||
push(@{$c{$mPin,$fPin}},-1*$Field[4]/$omega);
|
||||
}
|
||||
}
|
||||
close(SIMULATE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Write the results to a file
|
||||
#
|
||||
|
||||
if (!open(OF,">$outputFile")) {
|
||||
die("ERROR: cannot open file $outputFile, stopped");
|
||||
}
|
||||
if ($main::fMin == $main::fMax) {
|
||||
printf OF ("V($main::biasSweepPin)");
|
||||
} else {
|
||||
printf OF ("Freq");
|
||||
}
|
||||
foreach (@main::Outputs) {
|
||||
($type,$mPin,$fPin)=split(/\s+/,$_);
|
||||
printf OF (" $type($mPin,$fPin)");
|
||||
}
|
||||
printf OF ("\n");
|
||||
for ($i=0;$i<=$#X;++$i) {
|
||||
$outputLine="$X[$i]";
|
||||
foreach (@main::Outputs) {
|
||||
($type,$mPin,$fPin)=split(/\s+/,$_);
|
||||
if ($type eq "g") {
|
||||
if (defined(${$g{$mPin,$fPin}}[$i])) {
|
||||
$outputLine.=" ${$g{$mPin,$fPin}}[$i]";
|
||||
} else {
|
||||
undef($outputLine);last;
|
||||
}
|
||||
} else {
|
||||
if (defined(${$c{$mPin,$fPin}}[$i])) {
|
||||
$outputLine.=" ${$c{$mPin,$fPin}}[$i]";
|
||||
} else {
|
||||
undef($outputLine);last;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (defined($outputLine)) {printf OF ("$outputLine\n")}
|
||||
}
|
||||
close(OF);
|
||||
|
||||
#
|
||||
# Clean up, unless the debug flag was specified
|
||||
#
|
||||
|
||||
if (! $main::debug) {
|
||||
unlink($simulate::netlistFile);
|
||||
unlink("$simulate::netlistFile.st0");
|
||||
if (!opendir(DIRQA,".")) {
|
||||
die("ERROR: cannot open directory ., stopped");
|
||||
}
|
||||
foreach (grep(/^$simulate::netlistFile\.ic/,readdir(DIRQA))) {unlink($_)}
|
||||
closedir(DIRQA);
|
||||
}
|
||||
}
|
||||
|
||||
sub runDcTest {
|
||||
my($variant,$outputFile)=@_;
|
||||
my($arg,$name,$value,$i,@Field,$pin);
|
||||
my($start,$stop,$step);
|
||||
my(@V,%DC,$temperature,$biasVoltage);
|
||||
my($inData,$inResults);
|
||||
|
||||
#
|
||||
# Make up the netlist, using a subckt to encapsulate the
|
||||
# instance. This simplifies handling of the variants as
|
||||
# the actual instance is driven by voltage-controlled
|
||||
# voltage sources from the subckt pins, and the currents
|
||||
# are fed back to the subckt pins using current-controlled
|
||||
# current sources. Pin swapping, polarity reversal, and
|
||||
# m-factor scaling can all be handled by simple modifications
|
||||
# of this subckt.
|
||||
#
|
||||
|
||||
@V=();
|
||||
foreach $pin (@main::Outputs) {@{$DC{$pin}}=()}
|
||||
($start,$stop,$step)=split(/\s+/,$main::biasSweepSpec);
|
||||
$start-=$step;
|
||||
foreach $temperature (@main::Temperature) {
|
||||
foreach $biasVoltage (split(/\s+/,$main::biasListSpec)) {
|
||||
if (!open(OF,">$simulate::netlistFile")) {
|
||||
die("ERROR: cannot open file $simulate::netlistFile, stopped");
|
||||
}
|
||||
print OF "* DC simulation for $main::simulatorName";
|
||||
&generateCommonNetlistInfo($variant,$temperature);
|
||||
foreach $pin (@main::Pin) {
|
||||
if ($main::isFloatingPin{$pin}) {
|
||||
print OF "i_$pin $pin 0 0";
|
||||
} elsif ($pin eq $main::biasListPin) {
|
||||
print OF "v_$pin $pin 0 $biasVoltage";
|
||||
} elsif ($pin eq $main::biasSweepPin) {
|
||||
print OF "v_$pin $pin 0 $start";
|
||||
} else {
|
||||
print OF "v_$pin $pin 0 $main::BiasFor{$pin}";
|
||||
}
|
||||
}
|
||||
print OF "x1 ".join(" ",@main::Pin)." mysub";
|
||||
print OF ".dc v_$main::biasSweepPin $main::biasSweepSpec";
|
||||
foreach $pin (@main::Outputs) {
|
||||
if ($main::isFloatingPin{$pin}) {
|
||||
print OF ".print dc v($pin)";
|
||||
} else {
|
||||
print OF ".print dc i(v_$pin)";
|
||||
}
|
||||
}
|
||||
print OF ".end";
|
||||
close(OF);
|
||||
|
||||
#
|
||||
# Run simulations and get the results
|
||||
#
|
||||
|
||||
if (!open(SIMULATE,"$simulate::simulatorCommand < $simulate::netlistFile 2>/dev/null|")) {
|
||||
die("ERROR: cannot run $main::simulatorName, stopped");
|
||||
}
|
||||
$inResults=0;
|
||||
while (<SIMULATE>) {
|
||||
chomp;s/^\s+//;s/\s+$//;s/#branch//;s/\(/_/;s/\)//;
|
||||
if (/^Index\s+sweep\s+v_/i) {$inResults=1;($pin=$');<SIMULATE>;next}
|
||||
@Field=split;
|
||||
if ($#Field != 2) {$inResults=0}
|
||||
next if (!$inResults);
|
||||
if ($pin eq $main::Outputs[0]) {
|
||||
push(@V,$Field[1]);
|
||||
}
|
||||
push(@{$DC{$pin}},$Field[2]);
|
||||
}
|
||||
close(SIMULATE);
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Write the results to a file
|
||||
#
|
||||
|
||||
if (!open(OF,">$outputFile")) {
|
||||
die("ERROR: cannot open file $outputFile, stopped");
|
||||
}
|
||||
printf OF ("V($main::biasSweepPin)");
|
||||
foreach $pin (@main::Outputs) {
|
||||
if ($main::isFloatingPin{$pin}) {
|
||||
printf OF (" V($pin)");
|
||||
} else {
|
||||
printf OF (" I($pin)");
|
||||
}
|
||||
}
|
||||
printf OF ("\n");
|
||||
for ($i=0;$i<=$#V;++$i) {
|
||||
next if (abs($V[$i]-$start) < abs(0.1*$step)); # this is dummy first bias point
|
||||
printf OF ("$V[$i]");
|
||||
foreach $pin (@main::Outputs) {printf OF (" ${$DC{$pin}}[$i]")}
|
||||
printf OF ("\n");
|
||||
}
|
||||
close(OF);
|
||||
|
||||
#
|
||||
# Clean up, unless the debug flag was specified
|
||||
#
|
||||
|
||||
if (! $main::debug) {
|
||||
unlink($simulate::netlistFile);
|
||||
unlink("$simulate::netlistFile.st0");
|
||||
if (!opendir(DIRQA,".")) {
|
||||
die("ERROR: cannot open directory ., stopped");
|
||||
}
|
||||
foreach (grep(/^$simulate::netlistFile\.ic/,readdir(DIRQA))) {unlink($_)}
|
||||
closedir(DIRQA);
|
||||
}
|
||||
}
|
||||
|
||||
sub generateCommonNetlistInfo {
|
||||
my($variant,$temperature)=@_;
|
||||
my(@Pin_x,$arg,$name,$value,$eFactor,$fFactor,$pin);
|
||||
foreach $pin (@main::Pin) {push(@Pin_x,"${pin}_x")}
|
||||
print OF ".options temp=$temperature gmin=1e-15 abstol=1e-14 reltol=1e-8";
|
||||
if ($variant=~/^scale$/) {
|
||||
die("ERROR: there is no scale or shrink option for spice, stopped");
|
||||
}
|
||||
if ($variant=~/^shrink$/) {
|
||||
die("ERROR: there is no scale or shrink option for spice, stopped");
|
||||
}
|
||||
if ($variant=~/_P/) {
|
||||
$eFactor=-1;$fFactor=1;
|
||||
} else {
|
||||
$eFactor=1;$fFactor=-1;
|
||||
}
|
||||
if ($variant=~/^m$/) {
|
||||
if ($main::outputNoise) {
|
||||
$fFactor/=sqrt($main::mFactor);
|
||||
} else {
|
||||
$fFactor/=$main::mFactor;
|
||||
}
|
||||
}
|
||||
if (defined($main::verilogaFile)) {
|
||||
die("ERROR: Verilog-A model support is not implemented for spice, stopped");
|
||||
}
|
||||
print OF ".subckt mysub ".join(" ",@Pin_x);
|
||||
foreach $pin (@main::Pin) {
|
||||
if ($main::isFloatingPin{$pin}) { # assumed "dt" thermal pin, no scaling sign change
|
||||
print OF "v_$pin ${pin} ${pin}_x 0";
|
||||
} elsif ($variant=~/^Flip/ && defined($main::flipPin{$pin})) {
|
||||
print OF "e_$pin ${pin}_v 0 $main::flipPin{$pin}_x 0 $eFactor";
|
||||
print OF "v_$pin ${pin}_v ${pin} 0";
|
||||
print OF "f_$pin $main::flipPin{$pin}_x 0 v_$pin $fFactor";
|
||||
} else {
|
||||
print OF "e_$pin ${pin}_v 0 ${pin}_x 0 $eFactor";
|
||||
print OF "v_$pin ${pin}_v ${pin} 0";
|
||||
print OF "f_$pin ${pin}_x 0 v_$pin $fFactor";
|
||||
}
|
||||
}
|
||||
print OF "${main::keyLetter}1 ".join(" ",@main::Pin)." mymodel";
|
||||
foreach $arg (@main::InstanceParameters) {
|
||||
($name,$value)=split(/=/,$arg);
|
||||
if ($variant=~/^scale$/) {
|
||||
if ($main::isLinearScale{$name}) {
|
||||
$value/=$main::scaleFactor;
|
||||
} elsif ($main::isAreaScale{$name}) {
|
||||
$value/=$main::scaleFactor**2;
|
||||
}
|
||||
}
|
||||
if ($variant=~/^shrink$/) {
|
||||
if ($main::isLinearScale{$name}) {
|
||||
$value/=(1.0-$main::shrinkPercent*0.01);
|
||||
} elsif ($main::isAreaScale{$name}) {
|
||||
$value/=(1.0-$main::shrinkPercent*0.01)**2;
|
||||
}
|
||||
}
|
||||
print OF "+ $name=$value";
|
||||
}
|
||||
if ($variant eq "m") {
|
||||
print OF "+ m=$main::mFactor";
|
||||
}
|
||||
if ($variant=~/_P/) {
|
||||
print OF ".model mymodel $main::pTypeSelectionArguments";
|
||||
} else {
|
||||
print OF ".model mymodel $main::nTypeSelectionArguments";
|
||||
}
|
||||
foreach $arg (@main::ModelParameters) {
|
||||
print OF "+ $arg";
|
||||
}
|
||||
print OF ".ends";
|
||||
}
|
||||
|
||||
1;
|
||||
Reference in New Issue
Block a user