#!/usr/local/bin/perl
#
# modtrack - combine mosfet model files to produce model files for 
# two important cases:
#	NN - worst-case nfet, best-case pfet, best-case tracking parameters
#	NP - best-case nfet, worst-case pfet, worst-case tracking parameters
#
# This is intended for BSIM-2 (and HP-BSIM2) models; it hasn't been
# tested with others.
#
# TODO: needs a rewrite to take advantage of perl5 features in combining
# read_bestmod and read_worstmod into a single parsing routine that gets passed
# a reference to a hash.
#
# $Log: modtrack,v $
# Revision 1.2  1998/11/24 16:14:50  tell
# fixed to handle parameters in model statements of the form
# "param = value" in addition to "param=value"
# left with BSIM3V3 list of tracking parameters.
#
#

# these are the tracking parameters for hpcmos14
#@param_track=('RSDW', 'DLAC', 'DWAC', 'TOX', 
#	'DL', 'DW', 'RSH', 'RS', 'RD', 'XW');

# for nsc_cmos8
@param_track=('TOX', 'LINT', 'WINT');

if($#ARGV != 2) {
	print STDERR "usage: modtrack nn|np bestfile worstfile\n";
	exit 1;
} else {
	$case = $ARGV[0];
	$bestfile = $ARGV[1];
	$worstfile = $ARGV[2];
}

if($case eq 'nn') {
	$whichBest = 'p';
	$whichWorst = 'n';
	$track = 'b';
} elsif($case eq 'np') {
	$whichBest = 'n';
	$whichWorst = 'p';
	$track = 'w';
} else {
	print STDERR "Error: case must be one of nn or np\n";
	exit 2;
}

foreach $par (@param_track) {
	$param_track{$par} = 1;
}

open($bestfile, $bestfile) || die "$bestfile: $!\n";
open($worstfile, $worstfile) || die "$worstfile: $!\n";

print "* Processed by modtrack $case $bestfile $worstfile:\n";
print "*   Best ${whichBest}fets, Worst ${whichWorst}fets, ";
if($track eq 'b') {
	print "Except best tracking parameters\n";
} else {
	print "Except worst tracking parameters\n";
}
print "**\n";

$best_lineno = 0;
$worst_lineno = 0;
$nmod = 0;

while(!eof($bestfile) && !eof($worstfile)) {
	undef %best_params;
	undef %worst_params;
	
	$best_mod = &read_bestmod($bestfile, $whichBest);
	$worst_mod = &read_worstmod($worstfile, $whichWorst);
	$nmod++;

	if($best_mod ne $worst_mod) {
		print STDERR "$bestfile (line $best_lineno) and $worstfile (line $worst_lineo) are not in the same order\n";
		print STDERR "(best has .model $best_mod, worst has .model $worst_mod)\n";
		exit 3;
	}

	# Non-tracking parameters have already been printed; now print the
	# appropriate set of tracking ones.

	foreach $par (@param_track) {
		if($track eq 'b') {
			if(defined($best_params{$par})) {
				printf "+ $par=%g\n", $best_params{$par};
			}
		} else {
			if(defined($worst_params{$par})) {
				printf "+ $par=%g\n", $worst_params{$par};
			}
		}
	}
	print "*\n*\n*\n";
}

# spit out comments from both files.  Couldn't write them in-place because
# full-line comments can't come in the middle of a .model's + continuation
# lines.
exit 0;
print "*\n*\n* Original best-case comments follow:\n";
print $best_comments;
print "*\n*\n* Original worst-case comments follow:\n";
print $worst_comments;

print STDERR "$nmod models processed\n";




exit 0;
######################################################################
# end of main program; subroutines follow.

# read_bestmod and read_worstmod are very similar, but combining them
# would be a bit tricky.

# read_bestmod($file, $printwhich)
#
# Read one model out of $file, and stash all of the parameters in the
# global associative array %best_params.
#
# If $printwhich is 'n', Copy any nfet models to the output as we go,
# but omit tracking parameters.   Otherwise, just silently fill the
# associative array with parameters.
#
# Similar if $printwhich is 'p'.
#
# Input comments are accumulated in $best_comments
#
sub read_bestmod
{
	local($infile, $print) = @_;
	local($_, $par, $nparline, $modname, $typename, $type);
	local(@params); local(@outpars);

	modline: while($_ = <$infile> ) {
	$best_lineno++;
		if($_ =~ /^\*/) {
			$best_comments .= $_;
		} elsif($_ =~ /^\s*$/) {
			next modline;	# eat blank lines
		} elsif($_ =~ /^.model/) {
			($dotmod, $modname, $typename) = split(/[ \t\n]+/, $_);
			$nparline=0;

			$typename =~ tr/A-Z/a-z/;
			if($typename eq 'nmos') {
				$type = 'n';
			} elsif($typename eq 'pmos') {
				$type = 'p';
			} else {
				print STDERR "Warning: Unknown model type $typename at $best_lineo in $infile\n";
			}
		
			print ".model $modname $typename\n" if $type eq $print;

			contline: while($_ = <$infile> ) {
				# this counts on the fact that the line after
				# the last continuation line is a comment-line
				# in the model files we're using.
				# parsing correctly would require more work.
				if($_ !~ /^\+/) {
					last modline;
				}
				#print $_;
				$nparline++;
				$_ =~ s/^\+\s*//;

				@outpars=();
				while($_ =~ m/^\s*(\w+)\s*=\s*([-0-9.eE+]+)(.*)/) {
					$parname = $1;
					$parval = $2;
					$_ = $3;

					print STDERR "b $parname=$parval\n";
					$best_params{$parname} = $parval;
					if($param_track{$parname} != 1) {
						push(@outpars, "$parname=$parval");
					}
				}
				if($type eq $print && $#outpars >= 0) {
					print "+ ", join("\t", @outpars), "\n";
				}
			}
		} else {
			print STDERR "Unknown line at $infile line $best_lineno: $_";
			exit 1;
		}
	}
	return $modname;
}


# read_worstmod($file, $printwhich)
#
# Read one model out of $file, and stash all of the parameters in the
# global associative array %worst_params.
#
# If $printwhich is 'n', Copy any nfet models to the output as we go,
# but omit tracking parameters.   Otherwise, just silently fill the
# associative array with parameters.
#
# Similar if $printwhich is 'p'.
#
sub read_worstmod
{
	local($infile, $print) = @_;
	local($_, $par, $nparline);
	local(@params); local(@outpars);

	modline: while($_ = <$infile> ) {
		$worst_lineno++;
		if($_ =~ /^\*/) {
			$worst_comments .= $_;
		} elsif($_ =~ /^\s*$/) {
			next modline;
		} elsif($_ =~ /^.model/) {
			($dotmod, $modname, $typename) = split(/[ \t\n]+/, $_);
			$nparline=0;

			$typename =~ tr/A-Z/a-z/;
			if($typename eq 'nmos') {
				$type = 'n';
			} elsif($typename eq 'pmos') {
				$type = 'p';
			} else {
				print STDERR "Warning: Unknown model type $typename at $worst_lineo in $infile\n";
			}
		
			print ".model $modname $typename\n" if $type eq $print;

			contline: while($_ = <$infile> ) {
				# this counts on the fact that the line after
				# the last continuation line is a comment-line in
				# the model files we're using.
				if($_ !~ /^\+/) {
					last modline;
				}
				#print $_;
				$nparline++;

				@outpars=();
				$_ =~ s/^\+\s*//;
				while($_ =~ m/^\s*(\w+)\s*=\s*([-0-9.eE+]+)(.*)/) {
					$parname = $1;
					$parval = $2;
					$_ = $3;

					print STDERR "w $parname=$parval\n";
					$worst_params{$parname} = $parval;
					if($param_track{$parname} != 1) {
						push(@outpars, "$parname=$parval");
					}
				}
				if($type eq $print && $#outpars >= 0) {
					print "+ ", join("\t", @outpars), "\n";
				}
			}
		} else {
			print STDERR "Unknown line at $infile line $worst_lineno: $_";
			exit 1;
		}
	}
	return $modname;
}

#
# Return a logical line from a spice file, handling continuation lines with '+'
# For ease in writing spice-file filters, the <newline><+> sequences are
# preserved in this version.
# usage:
#	get_line_cont($fh)
# where $fh is an object of type IO::File  (see perldoc IO::File)
#

sub get_line_cont
{
	my($f) = @_;
	my($line, $nline);
	
	if(defined($pushback_line)) {
		$line = $pushback_line;
		undef $pushback_line;
	} else {
		$line = $f->getline;
	}
	if(! $line) {
		return $line;
	}
		
	$nline = $f->getline;
	while($nline =~ m/^\+/) {
		#$nline =~ s/^\+/ /;
		#$line =~ s/\n$//;
		$line .= $nline;
		$nline = $f->getline;
	}
	if($nline) {
		$pushback_line = $nline;
	}

	return $line;
}
