====== 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 [-dot ] [-analyze ] [-allpaths -driver -path ] [-reduce -all_layer -dagling] or usage: sp2dot -spef [-net ""] [-dot] [-analyze []] [-allpaths -reduce -all_layer -dagling] where options include: Spice Input options: -------------------- -spice list of spice files -dot Create dot file of the resistance network -analyze Calculate point to point resistances -path dest pairs (valid for espf derived spice files) Spef input opitons: ------------------- -spef spef file -net "" net names to be analyzed (simple reg expression) - default "*" (all nets) -analyze [] Calculate point to point resistances data if specified then resistance data for all nets written to file else resistance data written to .res for each net. -dot generate a .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 = ; 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 && ($_ = ) || $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 () { $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 ":", which was passed to 'NodeName'. # SPEF generated circa 2009.06-2011.06 separates from a "Z" pin with whitespace. # - only "" 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 } # 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 () { 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; }