diff --git a/ChangeLog b/ChangeLog index e00a1cd60..3ac8a7abf 100644 --- a/ChangeLog +++ b/ChangeLog @@ -7,7 +7,11 @@ include for Tcl version of perror * src/main.c: - Add dummy INPretrieve() symbol so ngnutmeg and co link with xspice enabled + Add dummy INPretrieve() symbol so ngnutmeg and co link with + xspice enabled. + + * src/measure_spice.pl src/spicepp.pl.in Makefile.am: + Add .measure statement support 2004-06-22 Adrian Dawe @@ -15,6 +19,9 @@ src/Makefile.am: Added spicepp integration and spicewish command line options. + * src/spicewish: + Added -cm switch to load a codemodel + 2004-06-10 Stefan Jones * include/ngspice.h: diff --git a/src/Makefile.am b/src/Makefile.am index ab1952cf0..90dfbc524 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -171,14 +171,20 @@ TCL_PKG_PATH = @TCL_PACKAGE_PATH@ TCLSPICE_VERSION = @VERSION@ -TCL_FILES = libspice.so pkgIndex.tcl +TCL_FILES = libspice.so pkgIndex.tcl measure_spice.pl spicepp.pl \ + tclspice_source.tcl spicepp.tcl LIBSPICE_OBJS = tclspice.o pkgIndex.tcl: pkgIndex.tcl.in - rm -f pkgIndex.tcl - sed -e 's;%LIB_DIR%;$(TCL_PKG_PATH);g' pkgIndex.tcl.in | \ - sed -e 's;%VERSION%;$(TCLSPICE_VERSION);g' > pkgIndex.tcl + rm -f $@ + sed -e 's;%LIB_DIR%;$(TCL_PKG_PATH);g' $< | \ + sed -e 's;%VERSION%;$(TCLSPICE_VERSION);g' > $@ + +spicepp.pl: spicepp.pl.in + rm -f $@ + sed -e 's;%LIB_DIR%;$(TCL_PKG_PATH);g' $< | \ + sed -e 's;%VERSION%;$(TCLSPICE_VERSION);g' > $@ install-tcl: install-tcl-recursive install-tclspice install-data-am @@ -197,6 +203,7 @@ install-tclspice: ${TCL_FILES} $(INSTALL_DATA) tclspice_source.tcl $(DESTDIR)$(TCL_PKG_PATH)/spice $(INSTALL_DATA) spicepp.tcl $(DESTDIR)$(TCL_PKG_PATH)/spice $(INSTALL_PROGRAM) spicepp.pl $(DESTDIR)$(TCL_PKG_PATH)/spice + $(INSTALL_PROGRAM) measure_spice.pl $(DESTDIR)$(TCL_PKG_PATH)/spice $(mkinstalldirs) $(DESTDIR)$(bindir) $(INSTALL_PROGRAM) spicewish $(DESTDIR)$(bindir) diff --git a/src/measure_spice.pl b/src/measure_spice.pl new file mode 100755 index 000000000..3aa8ed5c5 --- /dev/null +++ b/src/measure_spice.pl @@ -0,0 +1,184 @@ +eval '(exit $?0)' && + eval 'exec perl -S $0 ${1+"$@"}' && + eval 'exec perl -S $0 $argv:q' + if 0; +# $Id$ +# Copyright 2000 by John Sheahan +# +# This program is free software; you may redistribute and/or modify it under +# the terms of the GNU General Public License Version 2 as published by the +# Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY, without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for complete details. +# +# measure utility for spice +# call to 'meas' inserted into spicedeck by spicepp +# preceeded by write of a data and control file +# copyright John Sheahan taudelta@integritynet.com.au +$ver='$id$ '; + +# data is in meas.data in pwd +############# control information if=nfo is in meas.ctl +# output is appended to spice.log +$data="meas.data"; +$ctl="meas.ctl"; +$log="spice.log"; + +$date = `date`; chop($date); +$user=$ENV{"USER"}; + +open CTL, $ctl || die "cannot open control file $ctl"; +$_=; +if (/^\.measu?r?e? (tran|dc|ac) (\S+) trig\s+(\S+) (.*)targ\s+(\S+)(.*)/) { + $name=$2; + $trig=$3; + $trigstr=$4; + $targ=$5; + $targstr=$6; + if ($trigstr =~ /\bval=(\S+)/) { $trigval=&unit($1)} + else { print "ERROR: no trigger value\n"; } + if ($trigstr =~ /\b(fall|rise|count)=(\S+)/) {$trigdrn=$1 ; $trigcount=&unit($2); } + else { print "ERROR: no trigger rise/ fall / cross\n"; } + if ($trigstr =~ /\btd=(\S+)/) {$trigdelay=&unit($1)} + else {$trigdelay=0} + if ($targstr =~ /\bval=(\S+)/) { $targval=&unit($1)} + else { print "ERROR: no target value\n"; } + if ($targstr =~ /\b(fall|rise|count)=(\S+)/) {$targdrn=$1 ; $targcount=&unit($2); } + else { print "ERROR: no target rise/ fall / cross\n"; } + if ($targstr =~ /\btd=(\S+)/) {$targdelay=&unit($1)} + else {$targdelay=0} + /^\.measu?r?e? (tran|dc|ac) \S+ (.*)/; $remainder=$2; +} +else { + print "ERROR cannot parse command line\n"; +} +close CTL; +#($name, $a_level,$a_drn,$b_level,$b_drn)=split; + +open DATA, $data || die "cannot open data file $data"; +$title=; +while () { + if (/^(\d+)\s+([0-9\.e\+\-]+)\s+([0-9\.e\+\-]+)\s+([0-9\.e\+\-]+)/) { + $time[$1]=$2; + $a[$1]=$3; + $b[$1]=$4; + } +} +close DATA; + +$first=&get_index($trigdelay,@time); +if ($trigdrn eq 'rise') { + $aindex = &get_rise($trigval,$trigcount,$first,@a); +} +elsif ($trigdrn eq 'fall') { + $aindex = &get_fall($trigval,$trigcount,$first,@a); +} +else { + $aindex = &get_cross($trigval,$trigcount,$first,@a); +} +$atime = &get_time($aindex,@time); +#print "atime=$atime aindex=$aindex first=$first tdelay=$trigdelay count=$trigcount drn=$trigdrn\n"; + +$first=&get_index($targdelay,@time); +if ($targdrn eq 'rise') { + $bindex = &get_rise($targval,$targcount,$first,@b); +} +elsif ($targdrn eq 'fall') { + $bindex = &get_fall($targval,$targcount,$first,@b); +} +else { + $bindex = &get_cross($targval,$targcount,$first,@b); +} +$btime = &get_time($bindex,@time); +#print "btime=$btime bindex=$bindex\n"; +#print "btime=$btime bindex=$bindex first=$first tdelay=$targdelay count=$targcount drn=$targdrn\n"; +$ans=$btime-$atime; + +open LOG, ">>$log" || die "cannot open logfile $log"; +print LOG "$title"; +print LOG "$user $date\n"; +print LOG "$name=$ans trig=$atime targ=$btime $remainder\n\n"; +print "$user $date\n"; +print "$name=$ans trig=$atime targ=$btime $remainder\n"; +close LOG; + + +################### get_fall ################ +sub get_fall { + $level=shift(@_); + $count=shift(@_); + $first=shift(@_); + for ($i=$first;$i<@_-1;$i++) { + if (($_[$i] >= $level) && ($_[$i+1] < $level)) { + $count--; + return $i + (($_[$i]-$level)/($_[$i]-$_[$i+1])) if ($count==0); + } + } + return undef; +} +sub get_rise { + $level=shift(@_); + $count=shift(@_); + $first=shift(@_); + for ($i=$first;$i<@_-1;$i++) { + if (($_[$i] <= $level) && ($_[$i+1] > $level)) { + $count--; + return $i + (($level-$_[$i])/($_[$i+1]-$_[$i])) if ($count==0); + } + } + return undef; +} +sub get_cross { + $level=shift(@_); + $count=shift(@_); + $first=shift(@_); + for ($i=$first;$i<@_-1;$i++) { + if ((($_[$i] <= $level) && ($_[$i+1] > $level)) || + (($_[$i] >= $level) && ($_[$i+1] < $level))) { + $count--; + return $i + (($level-$_[$i])/($_[$i+1]-$_[$i])) if ($count==0); + } + } + return undef; +} +# return the interpolated time corresponding to the (fractional) index +sub get_time { + $level=shift(@_); + $index=int($level); + $time = $_[$index] + (($_[$index+1] - $_[$index]) * ($level-$index)); + return $time; +} + +# return the first index at or after the given time +sub get_index { + $time=shift(@_); + for ($i=0;$i<@_-1;$i++) { + return $i if ($_[$i] >= $time) + } + return undef; +} + + +sub unit { + if ($_[0] =~ /([0-9e\+\-\.]+)(t|g|meg|k|mil|m|u|n|p|f)?(v|a|s)?/) { + if ($2 eq 't') { $mult = 1e12 } + elsif ($2 eq 'g') { $mult = 1e9 } + elsif ($2 eq 'meg') { $mult = 1e6 } + elsif ($2 eq 'k') { $mult = 1e3 } + elsif ($2 eq 'm') { $mult = 1e-3 } + elsif ($2 eq 'u') { $mult = 1e-6 } + elsif ($2 eq 'n') { $mult = 1e-9 } + elsif ($2 eq 'p') { $mult = 1e-12 } + elsif ($2 eq 'f') { $mult = 1e-15 } + elsif ($2 eq 'mil') { $mult = 25.4e-6} + else { $mult = 1 } + return $1 * $mult; + } + else { + print "ERROR: cannot result value $_[0]\n"; + } + return $_; # maybe perl does it better?? +} diff --git a/src/spicepp.pl b/src/spicepp.pl index 7376b438f..dcd06b70b 100755 --- a/src/spicepp.pl +++ b/src/spicepp.pl @@ -307,7 +307,7 @@ sub expand_meas { push(@xtra, ".control"); push(@xtra, "print time $trig $targ > meas.data"); push(@xtra, "echo $_ > meas.ctl"); - push(@xtra, "shell measure_spice $name"); + push(@xtra, "shell /usr/lib/spice/measure_spice.pl $name"); push(@xtra, ".endc"); splice(@deck,$i,1,@xtra); @xtra=(); diff --git a/src/spicepp.pl.in b/src/spicepp.pl.in new file mode 100755 index 000000000..2ebf3a97e --- /dev/null +++ b/src/spicepp.pl.in @@ -0,0 +1,855 @@ +eval '(exit $?0)' && + eval 'exec perl -S $0 ${1+"$@"}' && + eval 'exec perl -S $0 $argv:q' + if 0; +# Copyright 2000 by John Sheahan +# +# This program is free software; you may redistribute and/or modify it under +# the terms of the GNU General Public License Version 2 as published by the +# Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY, without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for complete details. +# + +$ver= '$Id$ '; + +require "getopts.pl"; +&Getopts("hi"); +(defined($opt_i)) ? ($interactive=1) : ($interactive=0) ; +if (defined($opt_h)) { + print "$0 $ver\n"; + print " -i interactive\n"; + + print " hspice-like spice preprocessor for berkeley spice\n"; + die; +} +# preprocess a hspice-like deck for berkeley spice 3f5 +# expand .lib .param .temp .global +# expoand .meas +# support $ for comment to lineend + +# $debug=1; + +# first pass read in deck, +# inline continuation lines, remove comment lines + +$_=<>;chop;@deck=" " . $_; # heading/comment line passthrough + +while (<>) { + chop; + # dont lowercase stuff in quotes + if (/^(.*)([\'\"])(.*)([\"\'])(.*)/) { + $_=lc($1) . $2 . $3 . $4 . lc($5); + } + else { $_ = lc($_); } + s/\$.*//; # no trailing comments; + s/^\*.*//; # no comments; + s/^\s*$//; # no bl lines + s/^\s+(.*)/$1/g; #get rid of blankspace + if (/^\s*\+(.*)/) { # continuation + $_ = pop (@deck) . " " . $1; + } + push @deck,$_ if (length($_) >0); +} + +while(&read_libs){} + +&read_parameters; +&eval_parameters; +&expand_parameters; + +&expand_eqns; + +#&remove_vbracket; + +&read_globals; +&expand_global_subs; +&expand_global_calls; + +&fix_temp; + +&expand_linearize; +&expand_meas; +&expand_control; + +&process_models; + +&printdeck; + + +if ($debug) { + foreach $param (sort keys %param) { + print STDERR "param $param = $param{$param}\n"; + } + foreach $global (sort keys %global) { + print STDERR " global $global\n"; + } +} + + + +################# subroutines ############# + +# include the specified model from the library. complain if not found. +sub libinclude { + local ($file,$model,$found,@lib ); + $file=$_[0]; + $model=$_[1]; + @lib=(); + open (INCLUDE, "$file") || die "include file $file cannot be opened"; + $found=0; + while () { + #get rid of blankspace, also allows you to optimise matches below + s/^\s+(.*)/$1/g; + last if ($found && /^\.endl/i); + if ($found) { + chop; + # dont lowercase stuff in quotes + if (/^(.*)([\'\"])(.*)([\"\'])(.*)/) { + $_=lc($1) . $2 . $3 . $4 . lc($5); + } + else { $_ = lc($_); } + s/\$.*//; # no trailing comments; + s/^\*.*//; # no comments; + s/\s\s+/ /g; # shrink multiple whitespaces + s/\s*=\s*/=/g; # compress around = + if (/^\+(.*)/) { # continuation + $_ = pop (@lib) . " " . $1; + } + push @lib,$_ if (length($_)>0); + } + if (/^\.lib\s+$model\b/i) { $found=1} + } + print STDERR "did not find model $model in library $file\n" unless ($found); + return @lib; +} +################## &read_libs ############ +sub read_libs { + local ($i,$file,$model,$flag); + $flag=0; + for ($i=0;$i<@deck;$i++) { + $_=$deck[$i]; + if (/^\.lib\s+[\'\"](.*)[\'\"]\s+(\S+)/i) { + $flag=1; + $file=$1; + $model=$2; + @lib=&libinclude($file,$model); + $_=@lib; + splice(@deck,$i,1,@lib); + } } + return $flag; +} + + +############## read globals ############ +# record a hash of all global signals. kill the line. +sub read_globals { + local ($i,$file,$model,@kill); + for ($i=0;$i<@deck;$i++) { + if ($deck[$i] =~ /^\.globals?\s+(.*)/) { + for (split(" ",$1)) { $global{$_}=1 } + push @kill,$i; + } + } + &zapdeck(@kill); +} + +############## kill line(s) in deck ################ +sub zapdeck { + # give it a list of lines to remove in increasing order!!! + while ($_=pop(@_)) { + splice @deck,$_,1; + } +} +######### parameters ################# +# find parameter define lines, remeber and delete them +sub read_parameters { + local ($i,$j,@kill); + for ($i=0;$i<@deck;$i++) { + if ($deck[$i] =~/^\.param\s+(.*)/) { # record param/value pairs +# print STDERR "found param line $1\n"; + push @kill,$i; + $_=$1; + $done=0; + until ($done) { + if (/^\s*(\S+)\s*\=\s*\'([^\']*)\'(.*)/) { +# print STDERR "PARAM $1 = $2\n"; + $param{$1}=&unit($2); + $_=$3; + } + elsif (/^\s*(\S+)\s*\=\s*(\S+)(.*)/) { +# print STDERR "PARAM(2) $1 = $2\n"; + $param{$1}=&unit($2); +# print STDERR "PARAM(2) $1 = $param{$1}\n"; + $_=$3; + } + else { $done=1; } + } +# @param=split(" ",$1); +# for ($j=0;$j<@param;$j++) { +# if ($param[$j] =~ /(\S*)\s*=\s*(\S+)/) { +# $param{$1}=$2; +# } +# else { +# print STDERR "parameter parse error in line $_\n"; + } } # } } + &zapdeck(@kill); +} +# replace parameters with defined string +# if the line starts with . - sub most places +# if not ., sub only last item +sub expand_parameters { + local ($i,$j,$nline,$skip,$tmptoken); + for ($i=0;$i<@deck;$i++) { + $skip=&skipnumber($deck[$i]); + if ($i==0) {$skip=0} # replace the first line +# print STDERR "skipping $skip fields $i $deck[$i]\n"; + $nline=''; + $_=$deck[$i]; + s/\(/ \( /g; # delimit openbracket + s/\)/ \) /g; # delimit closebracket + s/=/ = /g; + @_=split; + for ($j=0;$j<$skip;$j++) { + $nline .= $_[$j] . " "; + } + for ($j=$skip;$j<@_;$j++) { + $tmptoken = $_[$j]; + while (defined($param{$tmptoken})) { + $tmptoken = $param{$tmptoken}; + } + $nline .= $tmptoken . " "; +# print STDERR "first $_[$j]\n" if ($j == $skip); + + } +# print STDERR "$nline \n" if ($nline =~ /pwl/); + $_=$nline; + # undelimit openbracket for v(??) only + s/([vi]) \(\s*([^\)\s]*)\s*\)/$1\($2\)/g; + s/ = /=/g; # + s/ $//; # trailing space + $deck[$i]=$_; +} } + +################## add globals to subckt IO line ###################### + +# accept subroutine data +# add used global signals to the io defn line +# remember them in $globalsub{sub} to fix other instantiate lines +sub expand_global_subs { + local ($i,$subroutine,$extras,%nodes,$subckt); + for ($i=0;$i<@deck;$i++) { + $_=$deck[$i]; + if (defined($subckt)) { # remember nodes if in a subckt + @_=split; + for ($j=1;$j<@_-1;$j++) { # not first or last + $nodes{$_[$j]}++ if (defined($global{$_[$j]})); + } + } + if (/^\.subckt\s+(\S+)/) { + $subckt=$1; + $line=$i; + } + elsif (/^\.ends/) { + $extras=join(" ", keys %nodes); + $deck[$line] =~ s/^(.subckt.*)$/$1 $extras/; + $globalsub{$subckt}=$extras; + undef $subckt; +} } } + +sub expand_global_calls { + local ($i); + for ($i=0;$i<@deck;$i++) { + $_=$deck[$i]; + if (/^(x.*)\s+(\S+)\s*$/ && defined($globalsub{$2})) { + $deck[$i]="$1 $globalsub{$2} $2\n"; +} } } +############ measure #################### +# appears you have to linearize it all the first time +sub expand_linearize { + local ($i,@xtra,$first); + for ($i=0;$i<@deck;$i++) { + $_=$deck[$i]; + if (/^\.(tran|ac|dc)/) { + $analysis=$1; + } + elsif (/^\.measu?r?e?\s+(\S+)\s+trig(.*)/) { + $_=".meas $analysis $1 trig$2"; + $deck[$i]=$_; + # fill in the missing type so I don't have to think later + } + if (/^\.measu?r?e? (tran|dc|ac)\s+(\S+)\s+trig\s+(\S+) .* targ\s+(\S+) .*/) { + $first=$i unless (defined($first)); + $linearize{$3}=1; + $linearize{$4}=1; + } + } + if (defined($first)) { + push(@xtra, ".control"); + $list=join(" ", keys %linearize); + push(@xtra, "linearize time $list"); + push(@xtra, ".endc"); + splice(@deck,$first,0,@xtra); + @xtra=(); + } +} +sub expand_meas { + local ($i,@xtra); + for ($i=0;$i<@deck;$i++) { + $_=$deck[$i]; + if (/^\.measu?r?e? (tran|dc|ac)\s+(\S+)\s+trig\s+(\S+) .* targ\s+(\S+) .*/) { + $name=$2; + $trig=$3; + $targ=$4; + push(@xtra, ".control"); + push(@xtra, "print time $trig $targ > meas.data"); + push(@xtra, "echo $_ > meas.ctl"); + push(@xtra, "shell %LIB_DIR%/spice/measure_spice.pl $name"); + push(@xtra, ".endc"); + splice(@deck,$i,1,@xtra); + @xtra=(); + } + } +} +####################### control codes ############# +sub expand_control { + local ($i,@xtra); + for ($i=0;$i<@deck;$i++) { + $_=$deck[$i]; + if (/^\.(plot|print|tran|end)\b/) { + $cmd=$1; + s/^\.//; # drop initial dot + s/\stran\s/ /; + s/start=\S+//; # starts annow it + push(@xtra, ".control"); + if ($cmd eq "end") { + push(@xtra, "destroy all" ) unless ($interactive); + push(@xtra, "quit") unless ($interactive); + } + else { + push(@xtra, $_); + } + push(@xtra, ".endc"); + splice(@deck,$i,1,@xtra); + @xtra=(); + } + } +} + +################# translate model level ####### +# bsim3 (v3) +sub fix_temp { + for ($i=0;$i<@deck;$i++) { + if ($deck[$i] =~ /^\.tempe?r?a?t?u?r?e?\s*\=\s*(\S+)/) { + $deck[$i] = ".options temp=$1"; +} } } + +sub printdeck { + for (@deck) { + print &wrapline(70,"","+","\n"," ",split(" ",$_)); + } +} + +######################### wrapline ############### + +# wrapline( linelen, header, header2, trailer, +# separator, list ) +# where +# linelen max number of chars per line eg 70 +# header is a string stuck on to the beginning +# header2 is a string to add to the second and all subsequent lines, +# {if they exist] +# trailer is a string to tack on to the end of the structure +# separator is put between multiple elements in the list (NOT last) +# list +# returns string + +sub wrapline { + local($maxlen, $header, $header2, $trailer, + $separator, $linelen, @list, $output, $term, $last_term); + + ($maxlen, $header, $header2, $trailer, $separator, @list)=@_; + $output=$header; + $linelen=length($output); + $last_term=pop(@list); + foreach $term (@list) { + if ((length($term)+$linelen+length($separator))>$maxlen) { + $output .= "\n$header2" ; + $linelen = length($header2); + } + $output .= "$term$separator"; + $linelen += length($term)+ length($separator); + } + if ((length($last_term)+$linelen+length($trailer))>$maxlen) { + $output .= "\n$header2" ; + } + $output .= "$last_term$trailer"; +return $output; +} + +######################### equation support ######## +sub isnumber { + if (($_[0] =~ /\s*[\+\-]?[0-9\.]+\s*/) || + ($_[0] =~ /\s*[\+\-]?[0-9\.]+e[\+\-]?\d+/)) { + return 1; + } + else { + return 0; + } +} + +sub process { +# print STDERR "mapped $_[0] "; + local ($nline,$r,$i,$tmptoken); + $_=$_[0]; + s/([\(\)\*\/])/ $1 /g; # space delimit operators + s/([^eE])([\+\-])/$1 $2 /g; # special case for 1e-10 etc. + s/\^\^/ ^^ /g; # space delimit exponent +# print STDERR " delim $_ "; + @_=split; + for ($i=0;$i<@_;$i++) { + $tmptoken = $_[$i]; + while (defined($param{$tmptoken})) { + $tmptoken = $param{$tmptoken}; + } + $nline .= $tmptoken . " "; + } +# print STDERR " $nline " ; + $r=eval($nline); + print STDERR $@ if ($@ ne ''); +# print STDERR "to $r\n"; + return $r; +} + +# probably only one level deep.. +sub eval_parameters { + local ($val, $nval, $param); + for $param (keys %param) { +# print STDERR "checking param $param \n"; + $val=$param{$param}; + if ($val =~ /[\'\"](.*)[\'\"]/) { + $nval=&process($1); +# print STDERR "eval: processed $val to $nval\n"; + $param{$param}=$nval; + } + else { + $nval = &unit($val); +# print STDERR "eval: united $val to $nval\n"; + $param{$param}=$nval; + } + } +} +sub expand_eqns { + local ($i,$head,$tail,$p); + for ($i=0;$i<@deck;$i++) { + $_=$deck[$i]; + while (/(.*)\'([^\']*)\'(.*)/) { + $head=$1; $tail=$3; + $p=&process($2); + $_=$head . " $p " . $tail; + s/= /=/g; +# print STDERR "translated $deck[$i]\n to $_\n"; + } + $deck[$i]=$_; + } +} +# if it looks like v(pin) replace with pin +sub remove_vbracket { + local ($i,$nline); + for ($i=0;$i<@deck;$i++) { + $_=$deck[$i]; + $nline=''; + for (split) { + if (/^v\((.*)\)$/) { +# print STDERR "found vbracket $_ to $1 line $i\n"; + $_=$1; +} + $nline .= $_ . " "; + } + $deck[$i]=$nline; +} } + + + + +sub unit { + if ($_[0] =~ /^([0-9e\+\-\.]+)(t|g|meg|k|mil|m|u|n|p|f)?(v|a|s)?$/) { + if ($2 eq 't') { $mult = 1e12 } + elsif ($2 eq 'g') { $mult = 1e9 } + elsif ($2 eq 'meg') { $mult = 1e6 } + elsif ($2 eq 'k') { $mult = 1e3 } + elsif ($2 eq 'm') { $mult = 1e-3 } + elsif ($2 eq 'u') { $mult = 1e-6 } + elsif ($2 eq 'n') { $mult = 1e-9 } + elsif ($2 eq 'p') { $mult = 1e-12 } + elsif ($2 eq 'f') { $mult = 1e-15 } + elsif ($2 eq 'mil') { $mult = 25.4e-6} + else { $mult = 1 } +# print STDERR "unit $_[0] mult $mult val $1 letter $2 unit $3\n"; + return $1 * $mult; + } + return $_[0]; # maybe perl does it better?? +} + + +# when substituting parameters , skip this number from the start +sub skipnumber { + local ($in,$f); + $in=$_[0]; + $_=substr($in,0,1); # get the first char in the line + $f=$_; + # apart from the bjt - which I treat as a 3node device - all these + # have a fixed number of nodes + $n=tr/cdefghijklmoqrstuvw/2244442322443244424/; # element nodes +# print STDERR "first char $f matched $_ skip $_ \n"; + if ($n == 1) { + return $_+1; # add the name of the instance to the number of nodes + } + @_=split(" ",$in); + $_=@_; + if ($f eq 'x') { # only the last on ein a subckt call + return $_-1; + } + $first=shift(@_); + if ($first =~ /^\.meas/) { return 1 } # process this line + elsif ($first =~ /^\.tran/) { return 1 } # process this line + elsif ($first =~ /^\.lib/) { return 2 } # process last one + elsif ($first =~ /^\.model/) { return 3 } + else { return $_ } # skip the line +} + +################Fix all model incompatabilites ##################### + +sub process_models { + $spice_scale = 1; + + &change_models; + &change_instances; +} + +################# reads all avalible models #################### +######### Translate the hspice levels to ones for ng-spice ########## +# The wise men at multigig have decided the following: +#if level= 28 then use level=4 +# if level=49 or 53 +# Check BSIM version, +# if version = 3.2 use level 50 (3v3.2) +# if version = 3.1 use level 49 (3v3.1) +# if version = 4.2 use level 14 (4v2.1) +# else use level 8 +# (most up to date model, 3v3.2.2 at the moment) +# The parameters are also modified as follows: +#always remove the following: +#hspver, acm, calcasm, ldif, hdif, rd, rs, rsc, rdc (not used) +# Remove: NQSMOD if version >= 3v3.2 + +sub change_models { + local ($i,$model,@params,%default,$bin,$p); + %default = (acm => 0, calcacm => 0, lmlt => 1, wmlt => 1, xl => 0, + xw => 0, php => 1, ldif => 0, hdif => 0, lmin => 0, + lmax => 0, wmin => 0, wmax => 0, hspver => 0, + rd => 0, rs => 0, rsc => 0, rdc => 0, geo => 0, + tlev => 0, nlev => 0, n => 0, tt => 0, binflag => 1, + # There are from Bsim4.3 which ngspice does not have + stimod => -1, sa0 => 0, sb0 => 0, kvsat => 0, wlod => 0, + tku0 => 0, ku0 => 0, llodku0 => 0, wlodku0 => 0, lku0 => 0, wku0 => 0, + pku0 => 0, kvth0 => 0, stk2 => 0, steta0 => 0, lkvth0 => 0, wkvth0 => 0, + pkvth0 => 0, llodvth => 0, wlodvth => 0, lodk2 => 1, lodeta0 => 1); + for ($i=0;$i<@deck;$i++) { + $_ = $deck[$i]; + if(/^\.model\s+(\S+)\s+[np]mos/) { +# print STDERR "got ".$_."\n"; + + split /\./, $1 ; + $model = $_[0]; + $bin = $1; + push @$model, $bin; + + #First read level and version info and set the level + #and put acm to default value + + $_ = $deck[$i]; + /\blevel\s*=\s*([0-9]+)\b/; + + if($1 == 49){ + $$bin{"acm"} = 0;} + elsif($1 == 53){ + $$bin{"acm"} = 10;} + else{ + $$bin{"acm"} = 0; } + + if(/\bversion\s*=\s*([0-9\.]+)\b/){ + + if($1 eq "3.2"){ + $deck[$i] =~ s/\blevel\s*=\s*[0-9]+\b/level=50/; + $deck[$i] =~ s/(.*)\bnqsmod\s*=\s*\S*\b(.*)/$1$2/;} + + elsif($1 eq "3.1"){ + $deck[$i] =~ s/\blevel\s*=\s*[0-9]+\b/level=49/;} + + elsif($1 eq "4.2"){ + $deck[$i] =~ s/\blevel\s*=\s*[0-9]+\b/level=14/; + $deck[$i] =~ s/\bversion\s*=\s*[0-9\.]+\b/version=4.2.1/; + } else { + $deck[$i] =~ s/\blevel\s*=\s*[0-9]+\b/level=8/; + $deck[$i] =~ s/(.*)\bnqsmod\s*=\s*\S*\b(.*)/$1$2/;} + } + + + # get rid of hspice only variables, and store them + foreach $p (keys %default){ + if ($p ne "acm"){ + $$bin{$p} = $default{$p};} + } + $newline = ""; + @list = split /[\s\(\)]/,$deck[$i]; + # zap ".model name type" + $newline = shift @list; + $newline .= " " . shift @list; + $newline .= " " . shift @list; + foreach $item (@list) { + ($name,$value) = split /=/,$item; + if(exists($default{$name})){ + $$bin{$name} = $value; + } else { + $newline .= " " . $item; + } + } + $deck[$i] = $newline; + + #translate some vars + $deck[$i] =~ s/(\b)tref(\s*=)/${1}tnom$2/; + + #HDIFeff needed not HDIF + $$bin{"hdif"} *= $$bin{"wmlt"}; + } +#end if mosfet model + if (/\.opt\S*\s+scale=(\S+)/) { + $spice_scale = &unit($1); + } + } +#end for +} +#end sub + +#### selects the correct model and does l, w scaling and acm##### + + +sub change_instances { + local($i,$model,$l,$w,$p,$m,$ad,$as,$pd,$ps); + for ($i=0;$i<@deck;$i++) { + $_ = $deck[$i]; + if(/^m\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+(\S+)/) { + + #print STDERR "processing ".$_."\n"; + $model = $1; + + + #Model binning + if(/.*\bl\s*=\s*(\S+)\b.*/){ + $l = &unit($1); + $l *= $spice_scale; + }else {print STDERR "no l found\n";die;} + + if(/.*\bw\s*=\s*(\S+)\b.*/){ + $w = &unit($1); + $w *= $spice_scale; + }else {print STDERR "no w found\n";die;} + + #Loops though all bins and selects one + foreach $p (@$model){ + if( + $l <= $$p{'lmax'} && + $l >= $$p{'lmin'} && + $w <= $$p{'wmax'} && + $w >= $$p{'wmin'} + ){ + $deck[$i] =~ + s/(^\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+)(\S+)/$1$p/; + $model = $p; + goto model_found; + } + } + print STDERR "Model or bin not found for model=$model, w=$w, l=$l\n"; + next; + model_found: + #W & L mangling and acm stuff + #Get m + if($deck[$i] =~ s/(.*)\bm\s*=\s*([0-9]+)\b(.*)/$1$3/){ + $m = $2;}else {$m = 1;} + + #get and remove pd, ps, ad, as + if($deck[$i] =~ s/(.*)\bpd\s*=\s*(\S+)\b(.*)/$1$3/){ + $pd = &unit($2); + $pd *= $spice_scale; + }else{ $pd = 0;} + + if($deck[$i] =~ s/(.*)\bps\s*=\s*(\S+)\b(.*)/$1$3/){ + $ps = &unit($2); + $ps *= $spice_scale; + }else{ $ps = 0;} + + if($deck[$i] =~ s/(.*)\bas\s*=\s*(\S+)\b(.*)/$1$3/){ + $as = &unit($2); + $as *= $spice_scale * $spice_scale; + }else{ $as = 0;} + + if($deck[$i] =~ s/(.*)\bad\s*=\s*(\S+)\b(.*)/$1$3/){ + $ad = &unit($2); + $ad *= $spice_scale * $spice_scale; + }else{ $ad = 0;} + + #Calculate Leff and Weff + $w = $m * ($w * $$model{"wmlt"} + $$model{"xw"}); + $l = $l * $$model{"lmlt"} + $$model{"xl"}; + + #Now do the ACM stuff...... + #gathered from Hspice manual release 1999.4 December 1999 + + #Now the perimeters and areas + #calculated using hspice methods.. + if( $$model{"acm"} == 0){ + #pg 869, 19-33 + $ad = $m * $ad * $$model{"wmlt"} * $$model{"wmlt"}; + $as = $m * $as * $$model{"wmlt"} * $$model{"wmlt"}; + $pd = $m * $pd * $$model{"wmlt"}; + $ps = $m * $ps * $$model{"wmlt"}; + + } + + elsif( $$model{"acm"} == 1){ + #pg 871, 19-35 + $as = $ad = $$model{"wmlt"} * $w; + $ps = $pd = $w; + } + + elsif( $$model{"acm"} == 2){ + #pg 874, 19-39 + if($ad){ + $ad = $ad * $m * $$model{"wmlt"} * $$model{"wmlt"}; + }else { + $ad = 2 * $$model{"hdif"} * $w;} + + if($as){ + $as = $m * $as * $$model{"wmlt"} * $$model{"wmlt"}; + }else{ + $as = 2 * $$model{"hdif"} * $w;} + + if($pd){ + $pd = $m * $pd * $$model{"wmlt"}; + }else { + $pd = 4 * $$model{"hdif"} + 2 * $w; + } + + if($ps){ + $ps = $m * $ps * $$model{"wmlt"}; + }else{ + $ps = 4 * $$model{"hdif"} + 2 * $w; + } + } + + elsif( $$model{"acm"} == 3){ + #pg 879, 19-43 + if($ad){ + $ad = $m * $ad * $$model{"wmlt"} * $$model{"wmlt"};} + elsif($$model{"geo"} == 0 || $$model{"geo"} == 2){ + $ad = 2 * $$model{"hdif"} * $w; + }else{ + $ad = $$model{"hdif"} * $w; + } + + if($as){ + $as = $m * $as * $$model{"wmlt"} * $$model{"wmlt"};} + elsif($$model{"geo"} == 0 || $$model{"geo"} == 1){ + $as = 2 * $$model{"hdif"} * $w; + }else{ + $as = $$model{"hdif"} * $w; + } + + if($pd){ + $pd = $m * $pd * $$model{"wmlt"};} + elsif($$model{"geo"} == 0 || $$model{"geo"} == 2){ + $pd = 4 * $$model{"hdif"} + $w; + }else{ + $pd = 2 * $$model{"hdif"}; + } + + if($ps){ + $ps = $m * $ps * $$model{"wmlt"};} + elsif($$model{"geo"} == 0 || $$model{"geo"} == 1){ + $ps = 4 * $$model{"hdif"} + $w; + }else{ + $ps = 2 * $$model{"hdif"}; + } + } + + #following acm's from pg 1198, 26-246 + + elsif( $$model{"acm"} == 10){ + + } + + elsif( $$model{"acm"} == 11){ + + } + + elsif( $$model{"acm"} == 12 ){ + if( $$model{"calcacm"} == 1 ) { + if($ad){ + $ad = $ad * $$model{"wmlt"} * $$model{"wmlt"}; + }else{ + $ad = 2 * $$model{"hdif"} * $w; + } + + if($as){ + $as = $as * $$model{"wmlt"} * $$model{"wmlt"}; + }else{ + $as = 2 * $$model{"hdif"} * $w; + } + + if($ps){ + $ps = $ps * $$model{"wmlt"}; + }else{ + $ps = 4 * $$model{"hdif"} + 2 * $w; + } + + if($pd){ + $pd = $pd * $$model{"wmlt"}; + }else{ + $pd = 4 * $$model{"hdif"} + 2 * $w; + } + } + } + + elsif( $$model{"acm"} == 13){ + + } + else { + print STDERR "Bad acm number in ".$model." ".$acm."\n"; + die; + } + + #Now write the values into the file...... + $l /= $spice_scale; + $w /= $spice_scale; + + $pd /= $spice_scale; + $ps /= $spice_scale; + + $as /= $spice_scale * $spice_scale; + $ad /= $spice_scale * $spice_scale; + + $deck[$i] =~ s/\b(l\s*=\s*)(\S+)\b/$1$l/; + + $deck[$i] =~ s/\b(w\s*=\s*)(\S+)\b/$1$w/; + + $deck[$i] .= " pd=".$pd." ps=".$ps." as=".$as." ad=".$ad; + } + #end if mosfet + } + #end for +} +#end of sub diff --git a/src/spicewish b/src/spicewish index 9a4dcfb9a..706ef460c 100755 --- a/src/spicewish +++ b/src/spicewish @@ -14,6 +14,7 @@ namespace eval spicewish { puts "usage: spicewish \[options ...\] \[inputfile ...\]" puts " -b Run in batch mode." puts " -pp Execute spicePP on given file." + puts " -cm load Xspice codemodel." puts " -version Display version number." puts "" exit @@ -52,6 +53,7 @@ namespace eval spicewish { set fileName "" set mode_batch 0 set mode_spicePP 0 + set codeModels {} for {set i 0} {$i < $argc} {incr i} { set arg [lindex $argv $i] @@ -69,6 +71,10 @@ namespace eval spicewish { -b { set mode_batch 1 } -version { version } -pp { set mode_spicePP 1 } + -cm { + set val [lindex $argv [incr i]] + lappend codeModels $val + } default { usage } } } elseif {[file isfile $arg]} { @@ -78,6 +84,12 @@ namespace eval spicewish { if {$fileName == "" } { usage } + if {[llength $codeModels] != 0} { + foreach codeModel $codeModels { + spice::codemodel $codeModel + } + } + if {$mode_spicePP} { set fileName [ run_spicepp $fileName ] }