#!/usr/local/bin/perl
#
# Process spice netlist, adding parasitics (area, perimeter, s/d)
# to mosfet instancess that don't have them.
# Used to get reasonable values when hand-typing a netlist 
# to evaluate circuit ideas prior to layout.
#
#

# constants specific to the generic08 technology
# they'll almost always get specified on the command line.
$lambda = 0.35;
$len_sd = 5;     # length of source and drain, in lambda
$foldthresh = 8; # width above which transistors are assumed to be folded,
		# in lambda.

use Getopt::Long;
use IO::File;

# Usage
sub usage
{
print STDERR <<EOH;
Usage: $0 [options]
  -- add parasitic parameters to mosfets in spice netlist
Options:
  -v    verbose
EOH
}

#############################################################################
# Options and arguments
#

%optctl = ("D|debug!"  	 	=> \$debug,
           "l|lambda=f" 	=> \$lambda,
           "f|fold-threshold=f" => \$foldthresh,
           "s|sd-length=f"  	=> \$len_sd,
           "v|verbose!" 	=> \$verbose,
);

if(!GetOptions(%optctl)) {
	&usage;
	exit 1;
}

if($#ARGV < 0) {
          $fh = new IO::Handle;
          if(!($fh->fdopen(fileno(STDIN),"r"))) {
		  print STDERR "STDIN: $!\n";
		exit(1);
          }
} elsif($#ARGV == 0) {
	$fh = new IO::File $ARGV[0], "r";
	if(!defined($fh)) {
		print STDERR "$ARGV[0]: $!\n";
		exit(1);
	}
} else {
	print STDERR "usage: $0 [options] [file]\n";
        exit(1);

}

card: while($_ = &get_line_cont($fh)) {
	if($_ =~ m/^m/i) {
		chomp($_);
		($ref, $s, $g, $d, $b, $np, $href) = &parse_fet_line($_);
		if(!$ref) {
			print $_, "\n";
			next card;
		}
		
		if($opt_v) {
			print STDERR "ref=$ref s=$s\n";
		}
		if(!&process_fet($ref, $s, $g, $d, $b, $np, $href)) {
			print $_, "\n";
		}
	} else {
		print $_;
	}
}

exit 0;

#############################################################################
# subroutines

# break mosfet line into parts.
# Returns a list of 7 elements, 6 scalars and a hash reference.
sub parse_fet_line {
	my($line) = @_;

	my($ref, $s, $g, $d, $b, $np, @rest, $params, $p);
	my($pname, $pval);
	
	($ref, $s, $g, $d, $b, $np, @rest) = split(/\s+/, $line);
	return 0 if(!$rest[0]);   # syntax error or no parameters

	$params = {};
	foreach $p (@rest) {
		if($p =~ m/^([^=])=([0-9.e+-]+)([a-zA-Z]*)$/) {
			$pname = $1;
			$pval = $2;
			$pname =~ tr/[A-Z]/[a-z]/;
			$params->{$pname} = $pval;
		}
	}
	return ($ref, $s, $g, $d, $b, $np, $params);
}

sub process_fet {
	my($ref, $s, $g, $d, $b, $np, $params) = @_;
	my($l, $w, $foldscale);

	if(!defined($params->{l}) || !defined($params->{w})) {
		return 0;
	}
	$l = $params->{l};
	$w = $params->{w};

	if($w > $foldthresh * $lambda) {
		$foldscale = 0.5
	} else {
		$foldscale = 1;
	}

	if(!defined($params->{as})) {
		$params->{as} = $w * $len_sd * $lambda * $foldscale;
	}
	if(!defined($params->{ad})) {
		$params->{ad} = $w * $len_sd * $lambda * $foldscale;
	}
	if(!defined($params->{ps})) {
		$params->{ps} = (2*$w + 2 * $len_sd * $lambda) * $foldscale;
	}
	if(!defined($params->{pd})) {
		$params->{pd} = (2*$w + 2 * $len_sd * $lambda) * $foldscale;
	}
	if(!defined($params->{nrs})) {
		$params->{nrs} = ($params->{as} / $w**2) * $foldscale;
	}
	if(!defined($params->{nrd})) {
		$params->{nrd} = ($params->{ad} / $w**2) * $foldscale;
	}

	printf "%s %s %s %s %s %s ", $ref, $s, $g, $d, $b, $np;
	printf "l=%.2fu w=%.1fu ", $l, $w;
	printf "as=%.1fp ad=%.1fp ps=%.1fu pd=%.1fu ",
		$params->{as},
		$params->{ad},
		$params->{ps},
		$params->{pd};
	printf "nrs=%.2f nrd=%.2f",
		$params->{nrs},
		$params->{nrd};
	print "\n";
	return 1;
}


#
# 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;
}
