#! /usr/athena/bin/perl -w
# make-timing
# Chris Laas, golem@mit.edu, 4/4/99
#
# Given an input file with a simple (undocumented :) format,
# produces a circuit timing diagram in EPS format.
#
# Copyright Chris Laas, 1999


if (@ARGV) {
  $title = "@ARGV";
} else {
  $title = "Timing";
}

$timescale = 1;
$height = 50;
$separation = 10;
$font = "Times-Roman";
$fontsize = 10;
$labelxoffset = 2;
$labelyoffset = 2;
$signaloffset = 100;
$bittransition = 0;
$bustransition = 5;

@signals = ();
%type = ();
%values = ();
%name = ();

$maxtime = 0;

while (<>) {
  if (/^\s*\#/ || /^\s*$/) {
	# Comment line.
  } elsif (/^\s*timescale\s+(\d+\.?\d*)$/i) {
	$timescale = $1;
  } elsif (/^\s*height\s+(\d+\.?\d*)$/i) {
	$height = $1;
  } elsif (/^\s*separation\s+(\d+\.?\d*)$/i) {
	$separation = $1;
  } elsif (/^\s*font\s+(\S+)$/i) {
	$font = $1;
  } elsif (/^\s*fontsize\s+(\d+\.?\d*)$/i) {
	$fontsize = $1;
  } elsif (/^\s*labelxoffset\s+(\d+\.?\d*)$/i) {
	$labelxoffset = $1;
  } elsif (/^\s*labelyoffset\s+(\d+\.?\d*)$/i) {
	$labelyoffset = $1;
  } elsif (/^\s*signaloffset\s+(\d+\.?\d*)$/i) {
	$signaloffset = $1;
  } elsif (/^\s*bittransition\s+(\d+\.?\d*)$/i) {
	$bittransition = $1;
  } elsif (/^\s*bustransition\s+(\d+\.?\d*)$/i) {
	$bustransition = $1;
  } elsif (/^\s*bit\s+(\S+)\s+(.*)$/i) {
	push @signals, $1;
	$type{$1} = "bit";
	$values{$1} = {};
	($name{$1} = $2) =~ s/[\)\(]/\\$1/g;
  } elsif (/^\s*bus\s+(\S+)\s+(.*)$/i) {
	push @signals, $1;
	$type{$1} = "bus";
	$values{$1} = {};
	($name{$1} = $2) =~ s/[\)\(]/\\$1/g;
  } elsif (s/^\s*(\d+\.?\d*)//) {
	$time = $1;
	$maxtime = $time if $time > $maxtime;
	while (s/^\s+([^=\s]+)=//) {
	  $sig = $1;
	  die "Error: $.: no such signal \"$sig\".\n" if !defined($type{$sig});
	  if ($type{$sig} eq "bit") {
		if (s/^([01])//) {
		  $values{$sig}{$time} = $1;
		} else {
		  die "Error: $.: value for bit \"$sig\" is neither 1 nor 0.\n";
		}
	  } elsif ($type{$sig} eq "bus") {
		if (s/^Z//) {
		  $values{$sig}{$time} = "Z";
		} elsif (/^\s/) {
		  $values{$sig}{$time} = "";
		} elsif (s/^\"([^\"]*)\"//) {
		  $values{$sig}{$time} = $1;
		} else {
		  die "Error: $.: value for bus \"$sig\" is malformed.\n";
		}
	  }
	}
	if (/\S/) { die "Syntax error on line $..  Aborting.\n"; }
  } elsif (/^\s*clock\s+(\S+)\s+(-?\d+\.?\d*)\s+(\d+\.?\d*)\s+(\d+\.?\d*)\s+([01])$/i) {
	die "Error: $.: no such signal $1\n" if !defined($type{$1});
	$maxtime = $3 if $3 > $maxtime;
	$v = $5;
	for ($t = $2; $t <= $3; $t += $4/2.) {
	  $values{$1}{$t} = $v if $t >= 0;
	  $v = 1 - $v;
	}
  } elsif (/^\s*clock\s+(\S+)\s+(-?\d+\.?\d*)\s+(\d+\.?\d*)\s+(\d+\.?\d*)\s+(\d+\.?\d*)\s+([01])$/i) {
	die "Error: $.: no such signal $1\n" if !defined($type{$1});
	$maxtime = $3 if $3 > $maxtime;
	$v = $6;
	for ($t = $2; $t <= $3; $t += $v ? $4 : $5) {
	  $values{$1}{$t} = $v if $t >= 0;
	  $v = 1 - $v;
	}
  } else {
	die "Syntax error on line $..  Aborting.\n";
  }
}


print "%!PS-Adobe-2.0 EPSF-2.0\n";
print "%%Title: $title\n";
print "%%Creator: make-timing.pl\n";
print "%%CreationDate: ", scalar(localtime), "\n";
print "%%For: $ENV{USER}\@$ENV{HOSTNAME}\n";
print "%%Orientation: Portrait\n";
print "%%BoundingBox: 0 0 ", int($signaloffset + $maxtime*$timescale) + 1, " ",
	int(($height+$separation)*@signals - $separation) + 1, "\n";
print "%%Pages: 0\n";
print "%%BeginSetup\n";
print "%%IncludeFeature: *PageSize Letter\n";
print "%%EndSetup\n";
print "%%EndComments\n\n";

print "%%EndProlog\n\n";

print "/$font findfont $fontsize scalefont setfont\n\n";

$y = 0;
foreach $sig (reverse @signals) {
  print "$labelxoffset ", $labelyoffset+$y+($height-$fontsize)/2., " moveto\n";
  print "($name{$sig}) show\n";

  if ($type{$sig} eq "bit") {
	@times = sort {$a <=> $b} keys %{$values{$sig}};
	if (@times) {
	  if (!exists($values{$sig}{$maxtime})) {
		$values{$sig}{$maxtime} = $values{$sig}{$times[$#times]};
		push @times, $maxtime;
	  }
	  if (exists($values{$sig}{0})) {
		$lastvalue = $values{$sig}{0};
	  } else {
		$lastvalue = $values{$sig}{$times[0]};
		shift @times;
	  }
	  $lasttime = 0;
	  print $signaloffset, " ", $lastvalue ? $height+$y : $y, " moveto\n";
	  foreach $t (@times) {
		print $t*$timescale + $signaloffset, " ";
		print $lastvalue ? $height+$y : $y, " lineto\n";
		if ($lastvalue != $values{$sig}{$t}) {
		  print $signaloffset + ($bittransition+$t)*$timescale, " ";
		  print $values{$sig}{$t} ? $height+$y : $y, " lineto\n";
		}
		$lastvalue = $values{$sig}{$t};
		$lasttime = $t;
	  }
	  print "stroke\n\n";
	}
  } elsif ($type{$sig} eq "bus") {
	@times = sort {$a <=> $b} keys %{$values{$sig}};
	if (@times) {
	  if (exists($values{$sig}{0})) {
		$lastvalue = $values{$sig}{0} eq "Z" ? 0.5 : 0;
		shift @times;
		if ($values{$sig}{0} ne "Z" && $values{$sig}{0} ne "") {
		  print $signaloffset + $labelxoffset, " ";
		  print $labelyoffset + $y + ($height-$fontsize)/2., " moveto\n";
		  ($text = $values{$sig}{0}) =~ s/[\)\(]/\\$1/g;
		  print "($text) show\n";
		}
	  } else {
		$lastvalue = 0.5;
	  }
	  $lasttime = 0;
	  @line1 = ($signaloffset, " ", $lastvalue*$height+$y , " moveto\n");
	  @line2 = ($signaloffset, " ", (1-$lastvalue)*$height+$y , " moveto\n");
	  foreach $t (@times) {
		push @line1, $t*$timescale + $signaloffset, " ";
		push @line1, $lastvalue*$height+$y, " lineto\n";
		push @line2, $t*$timescale + $signaloffset, " ";
		push @line2, (1-$lastvalue)*$height+$y, " lineto\n";
		if ($values{$sig}{$t} eq 'Z') {
		  if ($lastvalue != 0.5) {
			push @line1, ($t+$bustransition/2.)*$timescale+$signaloffset, " ";
			push @line1, 0.5*$height+$y, " lineto\n";
			push @line2, ($t+$bustransition/2.)*$timescale+$signaloffset, " ";
			push @line2, 0.5*$height+$y, " lineto\n";
			$lastvalue = 0.5;
		  }
		} else {
		  if ($lastvalue != 0.5) {
			$trans = $bustransition;
			push @line1, ($t+$bustransition)*$timescale + $signaloffset, " ";
			push @line1, (1-$lastvalue)*$height+$y, " lineto\n";
			push @line2, ($t+$bustransition)*$timescale + $signaloffset, " ";
			push @line2, $lastvalue*$height+$y, " lineto\n";
			$lastvalue = 1 - $lastvalue;
		  } else {
			$trans = $bustransition/2.;
			push @line1, ($t+$trans)*$timescale+$signaloffset, " ";
			push @line1, $height+$y, " lineto\n";
			push @line2, ($t+$trans)*$timescale+$signaloffset, " ";
			push @line2,         $y, " lineto\n";
			$lastvalue = 1;
		  }
		  if ($values{$sig}{$t} ne "") {
			print $signaloffset + $labelxoffset + ($t+$trans)*$timescale, " ";
			print $labelyoffset + $y + ($height-$fontsize)/2., " moveto\n";
			($text = $values{$sig}{$t}) =~ s/[\)\(]/\\$1/g;
			print "($text) show\n";
		  }
		}
		$lasttime = $t;
	  }
	  push @line1, $maxtime*$timescale + $signaloffset, " ";
	  push @line1, $lastvalue*$height+$y, " lineto\n";
	  push @line2, $maxtime*$timescale + $signaloffset, " ";
	  push @line2, (1-$lastvalue)*$height+$y, " lineto\n";
	  print @line1, "stroke\n", @line2, "stroke\n\n";
	}
  }

  $y += $height + $separation;
}
