sp2dot

#!/tools/cfr/bin/perl
# 4/17/07:  PDL/Slatech.pm supported on 32bit Linux only
# 10/10/07: Increase robustness of 'checkbadgraphs' functionality to find
#             broken nets in addition to hanging nodes.
#           Improved handling of name-mapped SPEF.
# 7/29/10:  Allow a list of nets passed in a file
# 2/6/12:   Added 'S' directive to force source pin in SPEF
 
  #! /lsi/home/ateene/perl/bin/perl
use lib '/home/danp/bin/perl/lib' ;
 
########################################
## Rev 6.1 - 2003 Aug 5
## Added Spef Reader
##########################################
require 5.0;
 
# Always require the lightweight version of 'ResGraph.pm'.  The full
# version will only be loaded if the user specifies that analysis must
# be done.
require ResGraph_lite;
 
#########################################
$AllLayers = 0;
$ReduceGraph = 0;
@Path = ();
$AllPaths = 0;
 
# Parse command options
while (@ARGV && $ARGV[0] =~ /^-/) {
   $_ = $ARGV[0];
   shift;
   if      (/^--$/)	     { &usage; }
   elsif   (/^-spef/i)       { $SpefFile       = &get_option("-spef"); }
   elsif   (/^-spf/i)        { $SpfFile       = &get_option("-spef"); }
   elsif   (/^-net/i)        { $Net            = &get_option("-net"); }
   elsif   (/^-sp/i)         { push(@SpiceFiles, &get_list("-sp")); }
   elsif   (/^-path/i)       { push(@Path,       &get_list("-path")); }
   elsif   (/^-dot/i)        { $DotFile        = &get_option("-dot"); }
   elsif   (/^-analyze/i)    { $AnalyzeFile    = &get_option("-analyze"); }
   elsif   (/^-reduce/i)     { $ReduceGraph = 1; }
   elsif   (/^-allpaths/i)   { $AllPaths    = 1; }
   elsif   (/^-driver/i)     { $DriverPaths = 1; }
   elsif   (/^-all/i)        { $AllLayers   = 1; }
   elsif   (/^-dan/i)        { $Dangling    = 1; }
   elsif   (/^-ver/i)        { $Verbose    = 1; }
   elsif   (/^-checkbad/i)   { $CheckBadGraph=1; }
   elsif   (/^-debug/i)      { $Debug    = 1; }
   else			     { &usage("unkown argument: $_"); }
}
 
############################################################################
sub get_option {
	if ($#ARGV != -1 && $ARGV[0] !~ /^-/) {
	    $result = $ARGV[0];
	    shift @ARGV;	 
	    return $result;
	}
	else { return "1";  }
}
 
sub get_list {
    local(@result);
	&usage("missing argument for $_[0]") if ($#ARGV==-1) ;
        while ($ARGV[0] && $ARGV[0] !~ /^-/) {
	    push(@result,$ARGV[0]);
	    shift @ARGV;
	}
	return @result;
}
 
sub usage {
  my ($mess) = @_;
 
  print "$mess\n\n";
  print STDERR <<"_EOF_";
usage: sp2dot -spice <spice_files ...> [-dot <dot_file>] [-analyze <res_data_out>]  
              [-allpaths -driver -path <list_of_terms ...>]  
              [-reduce -all_layer -dagling]
or
 
usage: sp2dot -spef <spef_file> [-net "<net_name>"]  [-dot] [-analyze [<res_data_out>]]  
              [-allpaths -reduce -all_layer -dagling]
 
 
where options include:
 
Spice Input options:
--------------------
  -spice    <spice_files ... >  list of spice files
  -dot      <dot_file>          Create dot file of the resistance network
  -analyze  <res_data>          Calculate point to point resistances
  -path     <list_of_terms)     List of terminals to be used for the resistor network analysis
  -driver                       include only driver -> dest pairs (valid for espf derived spice files)
 
Spef input opitons:
-------------------
  -spef     <spef_file>          spef file 
  -net      "<net_name>"         net names to be analyzed (simple reg expression) - default "*" (all nets)
  -analyze  [<res_data>]         Calculate point to point resistances data 
                                   if <res_data> specified then resistance data for all nets written to <res_data> file
                                   else resistance data written to <net_name>.res for each net.
  -dot                           generate a <net_name>.dot file for each net specified net
  -checkbadgraphs                Check SPEF for nets with graphs that end on non-terminal nodes
 
general options (valid for Spice and Spef inputs):
--------------------------------------------------
  -reduce                     simplfied series parallel resistance reduction
  -all_layer                  reduce all layers (merge all layers otherwise only reduce common layers)
  -dangling                   reduce dangling nodes
 
  -allpaths                   include all posible source->destination paths (can be big)
                              (default is to use a single arbritary source)
 
_EOF_
 
  die;
}
 
sub OpenOutput {
    local (*FH,$file) = @_;
 
  if ($file ne "") { 
    open(FH,">$file") || die "Can't open output file $file";
  }
  else {
    open(FH,">-") || die "Can't open STDOUT";
  }
}
 
$AID = 0;
$pinDelim = ":";   # default 
 
############################################################################
## Main program
#&OpenOutput(*OUT,$outfile);
(@SpiceFiles || $SpefFile || $SpfFile) || &usage("Not all arguments specified");
 
#########################################
 
 
if ($SpefFile) {
 
    @Nets = $Net ? ($Net) : ("*");
 
    print "data file - $AnalyzeFile, dot - $DotFile\n";
 
    ## Check Analysis output option
    if    ($AnalyzeFile eq "1") { $Analyze = 1 }
    elsif ($AnalyzeFile) {
	open(RDAT,">$AnalyzeFile") || die "Can't open output file $AnalyzeFile";
    }
 
    # '$Net' can actually be the name of a file containing a list of nets
    # If so, overwrite @Nets with the contents of the file.
    if (-f $Net) {
	open (NET, $Net) or die "open $net $!\n";
	@Nets = <NET>;
	close (NET);
	chomp @Nets;
	print STDERR "... reading spef file - $SpefFile, nets from file \"$Net\"\n";
    }
    else {
	print STDERR "... reading spef file - $SpefFile, net \"$Net\"\n";
    }
 
    &ReadSpef($SpefFile,@Nets);
    exit;
}
elsif ($SpfFile) {
 
    if (!$Net) { $Net = "*" }
    print STDERR "... reading spef file - $SpefFile, net \"$Net\"\n";
 
    print "data file - $AnalyzeFile, dot - $DotFile\n";
 
    ## Check Analysis output option
    if    ($AnalyzeFile eq "1") { $Analyze = 1 }
    elsif ($AnalyzeFile) {
	open(RDAT,">$AnalyzeFile") || die "Can't open output file $AnalyzeFile";
    }
 
    &ReadSpf($Net,$SpfFile);
    exit;
}
 
else {
    # Read Spice
    $sub = "_main_";
    foreach $file (@SpiceFiles) {
	print STDERR "... reading spice file - $file\n";
 
	$Graph = ResGraph->new;
	&ReadSpice($file);
    }
}
 
if (@Path) {
    $Source = $Path[0];
}
else {
    $Source = (sort keys %Term)[0];
    @Path = sort keys %Term;
}
 
 
##########################################
# Reduce the network
if ($ReduceGraph) { $Graph->ResGraph::ReduceGraph($AllLayers,$Dangling,@Path); }
 
 
if ($Debug) {	    
    print STDERR "... finding articulation points\n";	
    @A = $Graph->articulation_points();
    print STDERR "... articulation points : @A\n";
 
    @source = $Graph->source_vertices;
    print STDERR "... source vertices     : @source\n";
 
    @sink = $Graph->sink_vertices;
    print STDERR "... sink vertices       : @sink\n";
 
    @ext = $Graph->exterior_vertices;
    print STDERR "... exterior vertices   : @ext\n";
 
    @int = $Graph->interior_vertices;
    print STDERR "... interior vertices   : @int\n";
 
    @iso = $Graph->isolated_vertices;
    print STDERR "... isolated vertices   : @iso\n";
}
 
##########################################
# Visualize the network
if ($DotFile) { 
    print STDERR "... generate dot file - $DotFile\n";
    $Graph->ResGraph::print_dot($Source,$DotFile); 
}
 
##########################################
# Analyze the network
if ($AnalyzeFile) {
    # For analysis, need the full ResGraph module.
    require ResGraph;
 
    open(ANA,">$AnalyzeFile") || die "Can't open output file $AnalyzeFile";
 
    print STDERR "... Network analysis\n";    
 
 
    print ANA "# Net $sub\n";
    print ANA "#\n";
    print ANA "# Source           Destination     Resistance\n";
    print ANA "#--------------------------------------------\n";
 
    if ($DriverPaths) {
	## Find all the source drivers
	foreach $term (sort keys %Term) {
	    if ($term =~ /_[DS]$/) { push(@src,$term) }
	    elsif ($term eq "VDD") { push(@src,$term) }
	    else { push(@dest,$term) }
	}
	@res = $Graph->ResGraph::AnalyzeResPaths_fast($src[0],@dest); 
	for $i (0 .. $#dest) {
	    printf (ANA "%-16s %-24s  %.3f ohm\n",$src[0], $dest[$i], $res[$i]); 
	}
 
    }
    else {
	if ($AllPaths) { $n = $#Path-1; }
	else { $n = 0; }
	for $i (0 .. $n) {
	    $src = $Path[$i];
	    @dest = @Path[$i+1 .. $#Path];
	    @res = $Graph->ResGraph::AnalyzeResPaths_fast($src,@dest); 
	    for $j (0 .. $#dest) {
		printf (ANA "%-16s %-16s  %.3f ohm\n",$src, $dest[$j], $res[$j]); 
	    }
	}
    }
}
 
 
################################################################################
############################################################################
sub ReadSpice {
    my  ($file) = @_;
    local (*SPICE);
    my ($string, $nextstring,@list);
 
    #print STDERR "... reading spice file $file\n";
    open(SPICE,$file) || die "Can't open $file";
 
 
    $flush = 0; $eof = 0; $nextstring = "";
    while (!$eof && ($_ = <SPICE>) || $flush ) {
        ## procsss input, including line continuation
	## the resulting input is stored $string
        chop;
        if (/^\+(.*)$/) { 
            $nextstring .= $1;
            next;
        }
	elsif (/^\*/) { next; }
        ## flush last record
        elsif ($flush) {
            $string = $nextstring;
            $eof = 1; $flush = 0;
        }
        else {
            $string = $nextstring;
            $nextstring = $_;
            if (eof) { $flush = 1; }
        }
 
	$string =~ s/:/_/g;
	if ($string =~ /^.SUBCKT\s+(\w+)/i) {
	    $sub = $1;
 
	    $Graph->ResGraph::net_name($sub);
 
	    (@list) = split(" ",$string);
	    foreach $port (@list[2 .. $#list]) {
		$port =~ s/\./_/g; 
		$port =~ s/^(\d+\D)/I$1/;
		$Graph->ResGraph::add_term($port);
		$Term{$port} = 1; 
	    }
	}
 
	if ($string =~ /^s*(R\w+)\s+(\S+)\s+(\S+)\s+(\S+)/i) {
	    $rname = $1; $t1 = $2; $t2 = $3; $rval = &GetDim($4);
	    $t1 =~ s/\./_/g; $t2 =~ s/\./_/g;
	    $t1 =~ s/^(\d+\D)/I$1/; $t2 =~ s/^(\d+\D)/I$1/;
 
	    $layer = "";
 
	    # arcadia info
	    if ($string =~ /\*_\d+_\d+_(\d+)_(\d+)_(\d+)_(\d+)_(\d+)/) { 
		$layer = $1; $x1= 0.01 * $2; $y1 = 0.01 * $3; $x2 = 0.01 * $4; $y2 = 0.01 * $5;
		$Pos{$t1} = "$x1,$y1"; $Pos{$t2} = "$x2,$y2";
	    }
	    # caliber info
	    elsif ($string =~ /\$layer=(\w+)/) { 
		$layer = $1; 
		if ($string =~ /\$x=([\d\-\.]+)/) { $x = $1; }
		if ($string =~ /\$y=([\d\-\.]+)/) { $y = $1; }
		$Pos{$t1} = "$x,$y"; $Pos{$t2} = "$x,$y";
	    }
 
	    ## add the resistor to the network graph
	    $Graph->ResGraph::add_arc($t1,$t2,$layer,$rval,$rname);
	    if ($layer) { 
		$Graph->ResGraph::set_layer($t1, $layer);
		$Graph->ResGraph::set_layer($t2, $layer);
	    }
	}
    }
    close (SPICE)
}
 
 
 
sub ReadSpef {
    my  ($file, @nets) = @_;
    local (*SPEF);
 
    my ($cid, $rid, $dnet, $node, $t1, $t2, $cap, $rval, $rname);
    my (%Port);
    my (@FoundHangingNets, @BrokenGraphs);
 
    @nets{@nets}=();  # create a lookup hash for matching names
 
    open(SPEF,$file) || die "Can't open $file";
 
    while (<SPEF>) {
 
	$pinDelim = $1 if /^\*DELIMITER\s+(\S+)/;
 
	if (/\*R_UNIT\s+([\d\.]+)/) {
	    $rscale = $1;  
	    if ($rscale != 1.0) { print STDERR "... spef resistance scaled by $rscale\n" }
	}
 
	# This original flip-flop regex can include the *PORTS section,
	#   *POWER_NETS, *GROUND_NETS, etc
	# Unfortunately cannot assume there will be whitespace to
	# separate sections.
#	if (/\*NAME_MAP/ .. /\*D_NET/) {
	if (/\*NAME_MAP/ .. /\*[A-MO-Za-mo-z]/) {  # Any letter but 'N'
	    die "unexpected number of fields in NAME_MAP section\n$_\n" if scalar(split)>2;
	    if (/^(\*\d+)\s+(\S+)/) { $Map{$1} = $2; }
	}
 
	if (/\*D_NET/ .. /\*END/) {
	    if (/\*D_NET\s+(\S+)/) {
		$dnet = MapName($1); 
		if ($Debug) { print "... net $dnet\n"; $dnet = ""; next }
		if (exists $nets{$dnet} or $nets[0] eq "*" ) {
		    print "... net $dnet\n";
		    print STDERR "\n... resistance graph  - net $dnet\n";
		    $Graph = ResGraph->new;
		    $Graph->ResGraph::net_name($dnet);
		}
		else { $dnet = ""; $line = "" }
		%Port = ();
		%Term = ();
	    }
 
	    if (! $dnet) { next }
 
	    ## find all the terminals
	    if (/\*CONN/ .. /\*CAP/) {
		if (/\*I\s+(\S+)$pinDelim(\S+)\s+(\w)/) {
		    $dir = $3;
		    $node = NodeName("$1$pinDelim$2"); 
		    $Term{$node} = $dir;
		    $Graph->ResGraph::add_term($node);
		}
		elsif (/\*P\s+(\S+)\s+(\w)/) {
		    $node = $1; $dir = $2;
		    $node = MapName($node);
		    # special handling of Ports dropped, because logical ports do not always have
		    # a predictable physical location within the net.
		    #$Port{$node} = $dir;   
		    $Term{$node} = $dir; 
		    #print "debug: top-level Port, node='$node', term=$Term{$node}\n";
		    $Graph->ResGraph::add_term($node);
		}
		elsif (/\*\*O[IP]/) {
		    my @F=split;
		    # Early SPEF using PROBE_TEXT had string "<PROBE_TEXT>:<pin>", which was passed to 'NodeName'.
		    # SPEF generated circa 2009.06-2011.06 separates <PROBE_TEXT> from a "Z" pin with whitespace.
		    #   - only "<PROBE_TEXT>" is passed to NodeName.
		    $node = NodeName($F[1]);
		    $Term{$node} = ($node =~ /_o$/i) ? "O" : "I";
		    #print "debug: OP, node='$node', term=$Term{$node}\n";
		    $Graph->ResGraph::add_term($node);
		}
	    }
 
	    if (/\*CAP/ .. /\*RES/) {
		## lumped cap
		if (/^(\d+)\s+(\S+)\s+(\S+)$/) {
		    $cid = $1; $t1 = NodeName($2); $t2 = "vss"; $cap = $3;
		    		}
 
		## mutual  cap
		if (/^(\d+)\s+(\S+)\s+(\S+)\s+(\S+)$/) {
		    $cid = $1; $t1 = NodeName($2); $t2 = NodeName($3); $cap = $4;
		}
	    }
 
	    if (/\*RES/ .. /\*END/) {
		if (/^(\d+)\s+(\S+)\s+(\S+)\s+(\S+)/) {
		    $rid = $1; $t1 = NodeName($2); $t2 = NodeName($3); $rval = GetDim($4);
		    $rname = "R${rid}";
 
		    $Graph->ResGraph::add_arc($t1,$t2,"",$rval,$rname);
		}
	    }
 
	    if (/\*END/) {
		## Analyze graph
		@src = @dst = @special = ();
		foreach $node (keys %Port) {
		    if    ($Port{$node} eq "I") { push(@src,$node) }
		    elsif ($Port{$node} eq "B") { push(@src,$node); push(@dst,$node) }
		    elsif ($Port{$node} eq "S") { push(@special,$node) }
		    else { push(@dst,$node) }
		}
		foreach $node (keys %Term) {
		    if    ($Term{$node} eq "O") { push(@src,$node) }
		    elsif ($Term{$node} eq "B") { push(@src,$node); push(@dst,$node) }
		    elsif ($Term{$node} eq "S") { push(@special,$node) }
		    else { push(@dst,$node) }
		}
		if (!@src) { $src[0] = $dst[0] }
 
		# Special handling for manually configured ports
		# The user has manually edited the *CONN section to pick one pin as the Source,
		#   by replacing the 'B', 'O', or 'I' direction indicator with the special string 'S'.
		# The 'special' pin becomes the sole source, all others pins are treated as destinations.
		if (@special) {
		    if (@special > 1) {
			print STDERR "ERROR: only a single source may be specified.  Currently have specified\n\t", join ("\n\t", @special), "\n";
			die "\n";
		    };
		    @temp = (@src,@dst);  # all non-S pins will become the @dst list
		    foreach my $p (@temp) {
			push @dst, $p unless grep {$_ eq $p} @dst;  # uniquify @dst 
		    }
		    @src = @special;
		}
 
		print "... src @src\n";
		print "... dst @dst\n";
 
		# Come on...be reasonable about the nets to process.
		if (@dst>1000) {
		    printf STDERR "... ERROR: Net $dnet has fanout of %d...skipping\n", scalar @dst;
		    next;
		}
 
		if ($ReduceGraph) { 	
		    print STDERR "... network reduction - net $dnet\n";
		    $Graph->ResGraph::ReduceGraph($AllLayers,$Dangling,(keys %Port, keys %Term)) ;
		}
 
		# Find ends of graph that are not terminals (an error condition)
		my $forceDot=0;
		my $BrokenGraph=0;
		if ($CheckBadGraph) {
		    ($nodesAref, $BrokenGraph) = $Graph->ResGraph::find_hanging();
		    if (@$nodesAref) {
			# BrokenGraphs are a subset of hanging nets.  Log all broken
			# graphs then others that are hanging but not broken.
			# This keeps the logfile from generating volumes of redundant info.
			if ($BrokenGraph) { push @BrokenGraphs, "$BrokenGraph\t$dnet"; }
			else              { push @FoundHangingNets, $dnet }
 
			print STDERR "... warning: net '$dnet' has ", scalar @$nodesAref, 
			    " hanging nodes...generating dot (nodes = ", join (", ", @$nodesAref), ")\n" ;
			#$forceDot=1;
		    }
		}		
 
		## Generate dot file
		if ($DotFile || $forceDot) {
		    ($dotfile = "$dnet.dot") =~ s/[\/\#]/_/g;
		    print STDERR "... generate dot file - net $dnet file $dotfile\n";
		    $Graph->ResGraph::print_dot($src[0],$dotfile); 
		}
		## Analyze network
		if ($Analyze) {
		    ($rdat = "$dnet.res") =~ s/[\/\#]/_/g;
		    print STDERR "... network analysis  - file $rdat\n"; 
		    ($rdat) = ($rdat =~ /((.){100})$/) if (length($rdat) > 100);  # limit filename length
		    open(RDAT,">$rdat") || die "Can't open output file $rdat";
		    print RDAT "# Net $dnet\n";
		    print RDAT "#\n";
		    print RDAT "# Source           Destination     Resistance\n";
		    print RDAT "#--------------------------------------------\n";
		}
		elsif($AnalyzeFile) {
		    print STDERR "... network analysis  - net $dnet file $AnalyzeFile\n";
		    print RDAT "# Net $dnet\n";
		}
 
		# Honor the user intent to analyze paths, unless a broken graph
		# would cause the 'AnalyzeResPaths' function to break.
		if ( ($Analyze || $AnalyzeFile) && !$BrokenGraph ) {
		    # For analysis, need the full ResGraph module.
		    require ResGraph;
 
		    # Allow the PDL module to process large networks.
		    # The limit on the number of nodes is very coarsely set.  One
		    #   observed failure was when #nodes>2513.  
		    if (@$nodesAref>2500) { $PDL::BIGPDL=1 }
		    else                  { undef $PDL::BIGPDL }
		    # It may be necessary in the future to simply stop 
		    # analysis on networks with too many hanging nodes.
 
		    if (! $AllPaths) { @src = ($src[0]) }
		    for $src (@src) {
			@res = $Graph->ResGraph::AnalyzeResPaths($src,@dst);
			for $j (0 .. $#dst) {
			    if ($src eq $dst[$j]) { next }
			    printf (RDAT "%-16s %-16s  %.3f ohm\n",$src, $dst[$j], $res[$j]); 
			}
		    }
		} # $BrokenGraph
 
		print RDAT "\n";
		if ($Analyze) { close (RDAT) }
	    } # end of *D_NET processing at line '*END'
	} # in a *D_NET
    } # <SPEF>
    close (SPEF);
 
    if (@FoundHangingNets) {
	print STDERR "\n... ERROR: Nets found with hanging nodes.\n";
	print "\n... ERROR: These nets have hanging nodes\n\t", join ("\n\t", @FoundHangingNets), "\n";
    }
    if (@BrokenGraphs) {
	print STDERR "\n... ERROR: Nets found with broken graphs.\n";
	print "\n... ERROR: These nets have hanging nodes and are also broken graphs\n\t", join ("\n\t", @BrokenGraphs), "\n";
    }
    exit(1) if (@FoundHangingNets || @BrokenGraphs);
 
    exit;
}
 
sub ReadSpf {
    my  ($net,$file) = @_;
    local (*SPF);
 
    my ($cid, $rid, $dnet, $node, $t1, $t2, $cap, $rval, $rname);
    my (%Port);
 
    ## make the net name re friendly
    $net =~ s/\*/.*/g;
    $net =~ s/\//\\\//g;
    $net =~ s/([\[\]])/\\$1/g;
 
    print "... net $net\n";
 
    open(SPF,$file) || die "Can't open $file";
 
    while (<SPF>) {
 
	if (/\*R_UNIT\s+([\d\.]+)/) {
	    $rscale = $1;  
	    if ($rscale != 1.0) { print STDERR "... spef resistance scaled by $rscale\n" }
	}
 
	# This original flip-flop regex can include the *PORTS section,
	#   *POWER_NETS, *GROUND_NETS, etc
	# Unfortunately cannot assume there will be whitespace to
	# separate sections.
	#if (/\*NAME_MAP/ .. /\*D_NET/) {
	if (/\*NAME_MAP/ .. /\*[A-MO-Za-mo-z]/) {  # Any letter but 'N'
	    die "unexpected number of fields in NAME_MAP section\n$_\n" if scalar(split)>2;
	    if (/^(\*\d+)\s+(\S+)/) { $Map{$1} = $2; }
	}
 
 
	if (/\*\|NET/ .. /^\s$/i) {
	    if (/\*\|NET\s+(\S+)/) {
		$dnet = MapName($1); 
		if ($Debug) { print "... net $dnet\n"; $dnet = ""; next }
		if ($dnet =~ /^$net$/i) { 
		    print STDERR "\n... resistance graph  - net $dnet\n";
		    $Graph = ResGraph->new;
		    $Graph->ResGraph::net_name($dnet);
		}
		else { $dnet = ""; $line = "" }
		%Port = ();
		%Term = ();
	    }
 
	    if (! $dnet) { next }
 
	    ## find all the terminals
	    if (/\*\|I\s+\(?(\S+) (\S+)\s+(\w)/) {
		$dir = $3;
		$node = NodeName("$1"); 
                print "... term $node $dir\n";
		$Term{$node} = $dir;
		$Graph->ResGraph::add_term($node);
	    }
 
	    if (/^(c\S+)\s+(\S+)\s+(\S+)$/i) {
		$cid = $1; $t1 = NodeName($2); $t2 = "vss"; $cap = $3;
	    }
 
	    ## mutual  cap
	    if (/^(c\S+)\s+(\S+)\s+(\S+)\s+(\S+)$/i) {
		$cid = $1; $t1 = NodeName($2); $t2 = NodeName($3); $cap = $4;
		print "... cap $t1 $t2 $cap\n";
	    }
 
	    if (/^(r\S+)\s+(\S+)\s+(\S+)\s+(\S+)/i) {
		$rid = $1; $t1 = NodeName($2); $t2 = NodeName($3); $rval = GetDim($4);
		$rname = "R${rid}";
		print "... res $t1 $t2 $rval\n";
		$Graph->ResGraph::add_arc($t1,$t2,"",$rval,$rname);
	    }
 
	    if (/^\s*$/) {
		## Analyze graph
		@src = @dst = ();
		foreach $node (keys %Port) {
		    if    ($Port{$node} eq "I") { push(@src,$node) }
		    elsif ($Port{$node} eq "B") { push(@src,$node); push(@dst,$node) }
		    else { push(@dst,$node) }
		}
		foreach $node (keys %Term) {
		    if    ($Term{$node} eq "O") { push(@src,$node) }
		    elsif ($Term{$node} eq "B") { push(@src,$node); push(@dst,$node) }
		    else { push(@dst,$node) }
		}
		if (!@src) { $src[0] = $dst[0] }
 
		#print "... src @src\n\n";
		#print "... dst @dst\n\n";
 
		if ($ReduceGraph) { 	
		    print STDERR "... network reduction - net $dnet\n";
		    $Graph->ResGraph::ReduceGraph($AllLayers,$Dangling,(keys %Port, keys %Term)) ;
		}
 
		## Generate dot file
		if ($DotFile) {
		    ($dotfile = "$dnet.dot") =~ s/[\/\#]/_/g;
		    print STDERR "... generate dot file - net $dnet file $dotfile\n";
		    $Graph->ResGraph::print_dot($src[0],$dotfile); 
		}
		## Analyze network
		if ($Analyze) {
		    ($rdat = "$dnet.res") =~ s/[\/\#]/_/g;
		    print STDERR "... network analysis  - file $rdat\n"; 
		    open(RDAT,">$rdat") || die "Can't open output file $rdat";
		    print RDAT "# Net $dnet\n";
		    print RDAT "#\n";
		    print RDAT "# Source           Destination     Resistance\n";
		    print RDAT "#--------------------------------------------\n";
		}
		elsif($AnalyzeFile) {
		    print STDERR "... network analysis  - net $dnet file $AnalyzeFile\n";
		    print RDAT "# Net $dnet\n";
		}
 
		if ($Analyze || $AnalyzeFile) {
		    # For analysis, need the full ResGraph module.
		    require ResGraph;
 
		    if (! $AllPaths) { @src = ($src[0]) }
		    for $src (@src) {
			@res = $Graph->ResGraph::AnalyzeResPaths($src,@dst);
			for $j (0 .. $#dst) {
			    if ($src eq $dst[$j]) { next }
			    printf (RDAT "%-16s %-16s  %.3f ohm\n",$src, $dst[$j], $res[$j]); 
			}
		    }
		    print RDAT "\n";
		    if ($Analyze) { close (RDAT) }
		}
 
	    }
	}
    }
}
 
sub MapName {
    my  ($name) = @_;
 
    ##print "map $name = $Map{$name}\n";
    if (defined $Map{$name}) { return $Map{$name} }
    else { return $name }
}
 
sub NodeName {
    my ($node) = @_;
 
    my ($inst, $pin) = split /$pinDelim/, $node;
    if ($inst && $pin) {
	$inst = MapName($inst);
 
	if ($pin =~ /^\d+$/) { return  "n$pin" }
	else { return "${inst}${pinDelim}${pin}" }
    }
    else { return MapName($node) }
}
 
#############################################################
 
sub GetDim {
    my ($input) = @_;
    my ($val);
 
    if    ($input =~ /^([\-\.\d]+)$/i) { $val = $1; }
    elsif ($input =~ /^([\-\.\d]+)g/i) { $val = $1 * 1e+9; }
    elsif ($input =~ /^([\-\.\d]+)x/i) { $val = $1 * 1e+6; }
    elsif ($input =~ /^([\-\.\d]+)k/i) { $val = $1 * 1e+3; }
    elsif ($input =~ /^([\-\.\d]+)m/i) { $val = $1 * 1e-3; }
    elsif ($input =~ /^([\-\.\d]+)u/i) { $val = $1 * 1e-6; }
    elsif ($input =~ /^([\-\.\d]+)n/i) { $val = $1 * 1e-9; }
    elsif ($input =~ /^([\-\.\d]+)p/i) { $val = $1 * 1e-12; }
    elsif ($input =~ /^([\-\.\d]+)f/i) { $val = $1 * 1e-15; }
    else { return $input; }
    #else { print STDERR "... warning problem interperting value of $input\n"; }
 
    return $val;
}