#! /usr/local/bin/perl

# creates an xy scatter plot of slack ratios
# from two sets of endpoint timing data

# Steve Golson -- Trilobyte Systems -- sgolson@trilobyte.com
# @(#)scatter_plot	1.9 01/18/05 21:46:54

require 5.004;
use Getopt::Long;
use POSIX;

# TODO
# 1. plot multiple xy file pairs with different colors

###########################################################################
# check arguments

$myname = `basename $0`;
chop $myname ;
$argstring = $myname;
foreach $arg (@ARGV) {
    if ( split(" ",$arg) > 1) { $argstring .= " ".quotemeta($arg); }
    else                      { $argstring .= " ".$arg; }
}
$datestring = `date`;
chop $datestring ;

##### subroutine for handling non-option arguments

$number_of_files=0;
sub getfile {
	$number_of_files++ ;
	unless ($x_timing_report_file) {
		$x_timing_report_file = $_[0] ;
		return; 
		}
	unless ($y_timing_report_file) {
		$y_timing_report_file = $_[0] ;
		return; 
		}
	warn "Too many filenames";
	return 0 ;
	}

##### get arguments and options

$result = &GetOptions(
	"title=s",\$title,
	"number",\$title_number,
	"xlabel=s",\$xlabel,
	"ylabel=s",\$ylabel,
	"period=f",\$period,
	"type=s",\$plot_type,
	"style=s",\$plot_style,
	"date",\$date,
	"xmin=f",\$xmin,
	"xmax=f",\$xmax,
	"ymin=f",\$ymin,
	"ymax=f",\$ymax,
	"<>",\&getfile) ;

##### set option defaults

$xlabel = $x_timing_report_file unless $xlabel ;
$ylabel = $y_timing_report_file unless $ylabel ;
$plot_type = "x" unless $plot_type ;
$plot_style = "dots" unless $plot_style ;

##### print usage on error

unless ($result and
	($number_of_files==2) and
	($plot_type eq "ps" or $plot_type eq "eps" or $plot_type eq "x") and
	($plot_style eq "dots" or $plot_style eq "points")
	) {

	select STDERR ;
	print <<EOF;
Usage: $myname x_timing_report_file y_timing_report_file
  Options:
    -title    <string>      defaults to no title
    -number                 title shows number of endpoints
    -xlabel   <string>      defaults to x_timing_report_file
    -ylabel   <string>      defaults to y_timing_report_file
    -period   <real>        overrides "Path Required" values
    -type     <ps|eps|x>    defaults to x
    -style    <dots|points> defaults to dots
    -date                   prints time and date
    The following four options select the area to be plotted:
    -xmin     <real>        defaults to larger of -1.0 or min x value
    -xmax     <real>        defaults to 1.0
    -ymin     <real>        defaults to larger of -1.0 or min y value
    -ymax     <real>        defaults to 1.0
EOF
	exit ;
	}

##### check file status

open (XFILE, "$x_timing_report_file") or die "Could not open file $x_timing_report_file : $!\n" ;
open (YFILE, "$y_timing_report_file") or die "Could not open file $y_timing_report_file : $!\n" ;

###########################################################################

##### process the x-axis timing report file

# skip the preamble
while (<XFILE>) {
	if ($_ =~ /^Endpoint\s+Path Delay\s+Path Required\s+Slack/) { last ; }
	}

# skip one more line
$_ = <XFILE> ;

while (<XFILE>) {

	@line = split ;

	$endpoint = $line[0] ;
	$celltype = $line[1] ;
	$delay = $line[2] ;
	$risefall = $line[3] ;
	$required = $line[4] ;
	$slack = $line[5] ;

	$required = $period if (defined($period));

	if ($required==0) { next ; }

	$slack_ratio = $slack / $required ;

	push @{ $xfile_ratio{$endpoint} }, [ $slack_ratio, $required ]  ;
	}

###########################################################################

##### process the y-axis timing report file

# skip the preamble
while (<YFILE>) {
	if ($_ =~ /^Endpoint\s+Path Delay\s+Path Required\s+Slack/) { last ; }
	}

# skip one more line
$_ = <YFILE> ;

while (<YFILE>) {

	@line = split ;

	$endpoint = $line[0] ;
	$celltype = $line[1] ;
	$delay = $line[2] ;
	$risefall = $line[3] ;
	$required = $line[4] ;
	$slack = $line[5] ;

	$required = $period if (defined($period));

	if ($required==0) { next ; }

	$slack_ratio = $slack / $required ;

	push @{ $yfile_ratio{$endpoint} }, [ $slack_ratio, $required ] ;
	}

###########################################################################

##### print the data file

$number_of_endpoints = 0 ;
$smallest_xratio = 1.0 ;
$smallest_yratio = 1.0 ;

# loop over all yfile endpoints, and find the matching xfile endpoints

for $endpoint (keys %yfile_ratio) {

	@xfile = @{ $xfile_ratio{$endpoint}} ;
	@yfile = @{ $yfile_ratio{$endpoint}} ;

	delete $xfile_ratio{$endpoint} ;
	delete $yfile_ratio{$endpoint} ;

	$number_of_xfile_paths = scalar @xfile ;
	$number_of_yfile_paths = scalar @yfile ;

	# Because of path grouping, it is possible to have >1 path to a given
	# endpoint. We need to have some way of sorting by required path value.
	# But for now, just print a warning.

	# print warning if >1 paths to a given endpoint
	# or if xfile and yfile have different numbers of paths to a given endpoint
	if ($number_of_xfile_paths > 1
	    or
	    $number_of_yfile_paths > 1
	    or
	    $number_of_yfile_paths != $number_of_xfile_paths) {
		push( @datafile, sprintf("# Warning! xfile paths %d yfile paths %d for endpoint %s\n",
				$number_of_xfile_paths,
				$number_of_yfile_paths,
				$endpoint)) ;
		next ;
		}

	# print the ratios

	push( @datafile, sprintf("%f\t%f\t%s\n",
				 $xfile[0][0],
				 $yfile[0][0],
				 $endpoint));
	$number_of_endpoints++ ;
	if ($xfile[0][0] < $smallest_xratio) {
	    $smallest_xratio = $xfile[0][0] ;
	    }
	if ($yfile[0][0] < $smallest_yratio) {
	    $smallest_yratio = $yfile[0][0] ;
	    }
        }

# warn if any xfile endpoints remain

for $endpoint (keys %xfile_ratio) {

	@xfile = @{ $xfile_ratio{$endpoint}} ;
	@yfile = @{ $yfile_ratio{$endpoint}} ;

	delete $xfile_ratio{$endpoint} ;
	delete $yfile_ratio{$endpoint} ;

	$number_of_xfile_paths = scalar @xfile ;
	$number_of_yfile_paths = scalar @yfile ;

	push( @datafile, sprintf("# Warning! xfile paths %d yfile paths %d for endpoint %s\n",
				$number_of_xfile_paths,
				$number_of_yfile_paths,
				$endpoint)) ;
	}

# print a summary

unshift( @datafile, sprintf( "# %f is smallest y ratio\n", $smallest_yratio)) ;
unshift( @datafile, sprintf( "# %f is smallest x ratio\n", $smallest_xratio)) ;
unshift( @datafile, "# $number_of_endpoints endpoints\n" ) ;

###########################################################################

##### print the gnuplot script

select STDOUT ;

$xmax = 1.0 unless defined($xmax);
$ymax = 1.0 unless defined($ymax);

if (!defined($xmin)) {
    $xmin = floor($smallest_xratio) ;
    if ($xmin > -1.0) { $xmin = -1.0 ; }
}
if (!defined($ymin)) {
    $ymin = floor($smallest_yratio) ;
    if ($ymin > -1.0) { $ymin = -1.0 ; }
}

$orientation = "portrait" ;
if ($xmin < $ymin) { $orientation = "landscape" ; }

$xmin = sprintf("%.1f",$xmin) ;
$xmax = sprintf("%.1f",$xmax) ;
$ymin = sprintf("%.1f",$ymin) ;
$ymax = sprintf("%.1f",$ymax) ;

##### first print a header

print <<EOF;
# gnuplot script file to make a scatter plot of slack ratios
# generated by
#   $argstring
# at
#   $datestring

set size ratio -1

set xlabel "$xlabel"
set ylabel "$ylabel"

set xrange [$xmin:$xmax]
set yrange [$ymin:$ymax]

set zeroaxis

EOF

# print the optional title

if ($title_number) {
	if ($title) { $title .= "\\n" ; }
	$title .= commify($number_of_endpoints)." endpoints" ;
	}

if ($title) {
	print <<EOF
set title "$title"

EOF
	}

# print the optional timestamp

if ($date) {
	print <<EOF
set timestamp "plotted: $datestring" 0,-3 "Helvetica,10"

EOF
	}

##### variable stuff depending on type of plot

if ($plot_type eq "x") {
	print <<EOF;
# output type is x
EOF
	}
elsif ($plot_type eq "ps") {
	print <<EOF;
# output type is ps
set terminal postscript $orientation
EOF
	}
else { # assume plot_type "eps"
	print <<EOF;
# output type is eps
set terminal postscript eps
EOF
	}

##### issue the plot command

print <<EOF;
plot \\
        x notitle with lines, \\
        "-" using 1:2 notitle with $plot_style
EOF

##### print the datafile

while ($line = shift @datafile) {
    print $line;
}

print "e\n";

###########################################################################

sub commify {
	my $text = reverse $_[0] ;
	$text =~ s/(\d\d\d)(?=\d)(?!\d*\.)/$1,/g;
	return scalar reverse $text ;
	}

###########################################################################

