#!/usr/bin/perl -w
# -d:ptkdb
# Chuck Benz, Hollis, NH   Copyright (c)2003
# http://asics.chuckbenz.com
#
# The information and description contained herein is the
# property of Chuck Benz.
#
# Permission is granted for any reuse of this information
# and description as long as this copyright notice is
# preserved.  Modifications may be made as long as this
# notice is preserved.
#
# li aka li.pl
#
$version = '$Id: li,v 1.3 2003/06/11 03:40:13 cbenz Exp cbenz $ ' ;
@line = split ' ', $version ;
$version = $line[2] ;
#
# Logic Investigator - 'li'
# reads a netlist of a design, and then allows various checks to be run on the design.
#
# $Log: li,v $
# Revision 1.3  2003/06/11 03:40:13  cbenz
# add variables to test dump.
#
# Revision 1.2  2003/06/10 16:50:04  cbenz
# first fairly functional version.
#
# Revision 1.1  2003/06/08 21:56:25  cbenz
# Initial revision
#
#

my %inst ;
my %net ;
my @inputlist ;
my @outputlist ;
my @except_src ;
my @except_dst ;
my %eqn ;
my %targetflops ;

open (LOGFILE, ">li.log") or die "unable to open li.log\n" ;

print "Logic Investigator $version \n" ;
print LOGFILE "Logic Investigator $version \n" ;

$informat = "eqn" ;

while ($temp = shift) {
    if ($temp =~ /^-f/) { 
	$temp = shift ; 
	if ($temp =~ /^eqn/i) { $informat = "eqn" } 
	elsif ($temp =~ /^test/i) { $informat = "test" } 
	else { die "unknown format $temp.\n"} 
    }
    else { 
	unshift @ARGV, $temp;
	last ;
    }
}
if (! defined @ARGV) {die "no input file(s)\n" ;}

if ($informat eq "eqn") {
  net_property ("GND", 16) ;
  net_property ("VCC", 16) ;
  while (<>) {
    @linesp = split ;
    @line = eqnsplit($_) ;
    if (@line == 0) { next ; }
    if (($line[0] =~ /^--/) && ($line[1] eq "is")) {
	$temp = substr ($linesp[0], 2) ;
	$remap{$temp} = $linesp[2] ;
	$map{$linesp[2]} = $temp ;
	if ((@line > 4) && ($line[4] =~ "^Pin_")) {
	    $io{$temp} = 1 ;
	}
	# print "remap $temp as $remap{$temp}\n" ;
	next ;
    }
    elsif ($line[0] =~ /^--/) { next } ;

    # now we're past blank lines and comments, do we have an equation, flop, IO, PLL. or memory element ?

    $eqn{$line[0]} = $_ ;
    # flops first
    if ((($line[2] eq "DFFE") || ($line[2] eq "DFFEA")) && ($line[3] eq '(')) {
	# add instance
	# DFFE (D, clock, reset, set, CE)
	add_inst ($line[0], 1) ; # 1 is flop
	inst_o_net ($line[0], $line[0], 2) ; # Q output, 2 is flop output pin
	$inst{$line[0]}[3] = "nullclock" ;
	# add nets
	net_property ($line[0], 8) ; # 8 is flop output
	net_src_pin ($line[0], $line[0], 2) ; # 2 is flop output pin
	$i = 4 ; $pin = 3 ;
	while (($i < @line) && ($pin < 8) && ($line[$i] ne ')')) {
	    if ($line[$i] ne ',') { 
		$temp = $line[$i] ;
		if ($temp eq '!') { $temp = $line[$i+1] ; $i++ ;}
		if ($temp eq 'GLOBAL') { $temp = $line[2+$i] ; $i +=3 ;}
		inst_i_net ($line[0], $temp, $pin) ; # D input, then clk, then reset, then set, then CE
		net_dest_pin ($temp, $line[0], $pin) ;
		if ($pin == 4) { net_property ($temp, 1) ; $inst{$line[0]}[3] = $temp } ;
		if ($pin == 5) { net_property ($temp, 2) } ;
		if ($pin == 6) { net_property ($temp, 2) } ;
		$i ++ ;
	    }
	    $i++ ;
	    $pin++ ;
	}
    }
    elsif (($line[2] eq "INPUT") && ($line[3] eq '(')) {
	# input is simple - just a net property
	net_property ( $line[0], 16) ;
	push (@inputlist, $line[0]) ;
    }
    elsif (($line[2] eq "OUTPUT") && ($line[3] eq '(')) {
	# output is a buffer, and net property
	net_property ( $line[0], 32) ;
	add_inst ($line[0], 0) ;
	inst_o_net ($line[0], $line[0], 0) ;
	net_src_pin ($line[0], $line[0], 0) ;
	$i = 4 ;
	if ($line[$i] eq '!') { $i++ } ;
	inst_i_net ($line[0], $line[$i], 1) ;
	net_dest_pin ($line[$i], $line[0], 1) ;
	push (@outputlist, $line[0]) ;
    }
    elsif (($line[2] eq "BIDIR") && ($line[3] eq '(')) {
	# bidir is a buffer, and net property
	net_property ( $line[0], 48) ;
	add_inst ($line[0], 0) ;
	inst_o_net ($line[0], $line[0], 0) ;
	net_src_pin ($line[0], $line[0], 0) ;
	inst_i_net ($line[0], $line[4], 1) ;
	net_dest_pin ($line[4], $line[0], 1) ;
	push (@inputlist, $line[0]) ;
	push (@outputlist, $line[0]) ;
    }
    elsif (($line[2] eq "TRI") && ($line[3] eq '(')) {
	# tri is a buffer, and net property
	net_property ( $line[0], 32) ;
	add_inst ($line[0], 0) ;
	inst_o_net ($line[0], $line[0], 0) ;
	net_src_pin ($line[0], $line[0], 0) ;
	inst_i_net ($line[0], $line[4], 1) ;
	net_dest_pin ($line[4], $line[0], 1) ;
	inst_i_net ($line[0], $line[6], 1) ;
	net_dest_pin ($line[6], $line[0], 1) ;
    }
    elsif (($line[2] eq "PLL") && ($line[3] eq '(')) {
	# for PLL, just add combi - ignore other sigs for now
	net_property ( $line[0], 0) ;
	add_inst ($line[0], 0) ;
	inst_o_net ($line[0], $line[0], 0) ;
	net_src_pin ($line[0], $line[0], 0) ;
	inst_i_net ($line[0], $line[4], 1) ;
	net_dest_pin ($line[4], $line[0], 1) ;
    }
    elsif (($line[2] eq "MEMORY_SEGMENT") && ($line[3] eq '(')) {
	# for now, just mark as dest flop, so we're not propagating write data to read data
	# pins in order are:
	# wrclk, rdclk, ?, CE for wrclk, write enable, read enable, clear?, ?, wrdata, wraddr, rdaddr
	# add instance
	add_inst ($line[0], 2) ; # 2 is mem
	inst_o_net ($line[0], $line[0], 17) ; # mem output
	# add nets
	net_property ( $line[0], 64) ;
	net_src_pin ($line[0], $line[0], 17) ; # 
	$i = 4 ; $place = 1 ; $rdclk = 0 ;
	while (($i < @line) && ($place < 12) && ($line[$i] ne ')')) {
	    if ($line[$i] ne ',') { 
		$temp = $line[$i] ;
		if ($temp eq 'GLOBAL') { $temp = $line[2+$i] ; $i +=3 ;}
		if ($place == 1) { 
		    $pin = 10 ;
		    net_property ($temp, 1) ;
		    $inst{$line[0]}[3] = $temp;
		}
		elsif ($place == 2) { $pin = 11 ; net_property ($temp, 1) ; $rdclk = 1 ; $inst{$line[0]}[3] = $temp ; }
		elsif ($place == 4) { $pin = 12 ; }
		elsif ($place == 5) { $pin = 12 ; }
		elsif ($place == 6) { $pin = 13 ; }
		elsif ($place == 7) { $pin = 22 ; }
		elsif ($place == 9) { $pin = 12 ; }
		elsif ($place == 10) { $pin = 12 ; }
		elsif ($place == 11) {
		    if ($rdclk == 0) { $pin = 13 ; }
		    else { $pin = 1 ; } # for non-clocked reads, make rdaddr propagate to rddata
		}
		else { $pin = 1 ; myprint ("MEM pin: ", $place, " ", @line , "\n")} ;
		inst_i_net ($line[0], $temp, $pin) ;
		net_dest_pin ($temp, $line[0], $pin) ;
		$i ++ ;
	    }
	    $i++ ;
	    $place++ ;
	}
	if ($rdclk == 1) { push (@memWclkedRdlist, $line[0]) }
    }
    elsif (($line[2] eq "RAM_SLICE") && ($line[3] eq '(')) {
	# for now, just mark as dest flop, so we're not propagating write data to read data
	# pins in order are:
	# wrdata, wren, wrclk, rdclk, ?, ?, ?, ?, ?, wraddr, rdaddr) ;
	# add instance
	add_inst ($line[0], 2) ; # 2 is mem
	inst_o_net ($line[0], $line[0], 17) ; # mem output
	# add nets
	net_property ( $line[0], 64) ;
	net_src_pin ($line[0], $line[0], 17) ; # 
	$i = 4 ; $place = 1 ; $rdclk = 0 ;
	while (($i < @line) && ($place < 12) && ($line[$i] ne ')')) {
	    if ($line[$i] ne ',') { 
		$temp = $line[$i] ;
		if ($temp eq 'GLOBAL') { $temp = $line[2+$i] ; $i +=3 ;}
		if ($place == 3) { 
		    # wrclk
		    $pin = 10 ;
		    net_property ($temp, 1) ;
		    # but backup a level if this is just an already defined buffered clock
		    if ((@{$net{$temp}[1]}) == 2) { # one and only one driver
			$drv = ${net{$temp}[1]}[0] ;  # get driver
			if ((@{$inst{$drv}[1]}) == 2) { # one and only one input, it's a buffer
			    $src = ${$inst{$drv}[1]}[0] ;
			    #print "checking $src\n" ;
			    if (1 == (1 & ($net{$src}[0]))) { # it's a clock net
				#print " replacing $temp with $src\n" ;
				$temp = $src ;
			    }
			}
		    }
		    $inst{$line[0]}[3] = $temp;
		}
		elsif ($place == 4) { 
		    $pin = 11 ; 
		    net_property ($temp, 1) ;
		    $rdclk = 1 ; 
		    # but backup a level if this is just an already defined buffered clock
		    if ((@{$net{$temp}[1]}) == 2) { # one and only one driver
			$drv = ${net{$temp}[1]}[0] ;  # get driver
			if ((@{$inst{$drv}[1]}) == 2) { # one and only one input, it's a buffer
			    $src = ${$inst{$drv}[1]}[0] ;
			    #print "checking $src\n" ;
			    if (1 == (1 & ($net{$src}[0]))) { # it's a clock net
				#print " replacing $temp with $src\n" ;
				$temp = $src ;
			    }
			}
		    }
		    $inst{$line[0]}[3] = $temp ;
		}
		elsif ($place == 1) { $pin = 12 ; }
		elsif ($place == 2) { $pin = 12 ; }
		elsif ($place == 9) { $pin = 0 ; }
		elsif ($place == 10) { $pin = 12 ; }
		elsif ($place == 11) {
		    if ($rdclk == 0) { $pin = 13 ; }
		    else { $pin = 1 ; } # for non-clocked reads, make rdaddr propagate to rddata
		}
		else { $pin = 1 ; myprint ("RAM_SLICE pin: ", $place, " ", @line , "\n")} ;
		inst_i_net ($line[0], $temp, $pin) ;
		net_dest_pin ($temp, $line[0], $pin) ;
		$i ++ ;
	    }
	    $i++ ;
	    $place++ ;
	}
	if ($rdclk == 1) { push (@memWclkedRdlist, $line[0]) }
    }
    elsif (($line[1] eq "(") && ($line[2] eq 'n') && ($line[5] eq 'LVDS_INPUT')) {
	net_property ( $line[0], 16) ;
    }
    elsif (($line[1] eq "(") && ($line[2] eq 'n') && ($line[5] eq 'LVDS_OUTPUT')) {
	net_property ( $line[0], 32) ;
    }
    elsif (($line[1] ne "=") || (($line[3] eq '(') && ($line[2] ne "CARRY") && ($line[2] ne "BUS") && ($line[2] ne "OPNDRN") && ($line[2] ne "DATA") && ($line[2] ne "GLOBAL" && ($line[2] ne "LVDS_TX") && ($line[2] ne "LVDS_RX") && ($line[2] ne "RD_ADDR") && ($line[2] ne "WR_ADDR")))) {
	print "unrecognized in line # $. : ", $line[2], $line[3], "\n", @line, "\n" ;
	print LOGFILE "unrecognized in line # $. : ", $line[2], $line[3], "\n", @line, "\n" ;
    }
    else {
	# start working on combinational logic
	# add instance
	add_inst ($line[0], 0) ; 
	inst_o_net ($line[0], $line[0], 0) ;
	# add nets
	net_property ($line[0], 0) ;
	net_src_pin ($line[0], $line[0], 0) ;
	$i = 2 ;
	while (($i < @line) && ($line[$i] ne ';')) {
	    if ($line[$i] !~ /[\!\&\$\#\(\),]/) { 
		$temp = $line[$i] ;
		if ($temp eq 'GLOBAL') { $temp = $line[2+$i] ; $i +=3 ;}
		elsif ($temp eq 'CARRY') { $temp = $line[2+$i] ; $i +=2 ;}
		elsif ($temp eq 'CASCADE') { $temp = $line[2+$i] ; $i +=2 ;}
		elsif ($temp eq 'BUS') { $temp = $line[2+$i] ; $i +=2 ;}
		elsif ($temp eq 'OPNDRN') { $temp = $line[2+$i] ; $i +=2 ;}
		elsif ($temp eq 'RD_ADDR') { $temp = $line[2+$i] ; $i +=2 ;}
		elsif ($temp eq 'WR_ADDR') { $temp = $line[2+$i] ; $i +=2 ;}
		elsif ($temp eq 'DATA') { $temp = $line[2+$i] ; $i +=2 ;}
		elsif ($temp eq 'LVDS_TX') { $temp = $line[2+$i] ; $i +=2 ;}
		elsif ($temp eq 'LVDS_RX') { $temp = $line[2+$i] ; $i +=2 ;}
		elsif (($temp =~ /^[A-Z]*$/) && ($temp ne 'GND') && ($temp ne 'VCC')) {
		    print "new allcaps: ", $temp, "\n" ;
		    print LOGFILE "new allcaps: ", $temp, "\n" ;
		}
		inst_i_net ($line[0], $temp, 1) ;
		net_dest_pin ($temp, $line[0], 1) ;
		$i ++ ;
	    }
	    $i++ ;
	    $pin++ ;
	}
	
    }
  }
}
elsif ($informat eq "test") {
    # make the input record separator "" so it reads whole file (well, until it sees \n\n).
    # we're in {} so it goes back to normal.
    local $/ ;
    open (TESTFILE, "testnetlist") or die "can't find test file testnetlist\n" ;
    $testfile = <TESTFILE> ;
    my $VAR1 ;
    my $VAR2 ;
    eval $testfile ;
    die "bad file format for testnetlist\n" if ((! defined ($VAR1)) || (! defined ($VAR2)) || 
						(! defined ($VAR3)) || (! defined ($VAR4)) || 
						(! defined ($VAR5))) ;
    %inst = %{$VAR1} ; # yes, amazing
    %net = %{$VAR2} ;
    %remap = %{$VAR3} ;
    %eqn = %{$VAR4} ;
    %map = %{$VAR5} ;
    %resethash = %{$VAR6} ;
    # file format:
    #  $VAR1 = { 'instname1' => {....}, 'instname2' => {...}} ;
    #  $VAR2 = { 'netname1' => {....}, 'netname2' => {...}} ;
}
else {
    die "design not read.\n" ;
}

# Now present command prompt and await user's command
while (1){
    print "li> " ;
    $command = <STDIN> ;
    if ( ! defined $command) {print "exit\n" ; last } ; # handle ^D
    print LOGFILE "li> ", $command ;
    $_ = $command ;
    @line = split ;
    if ($command =~ /^\s*exit/i) { last ; }
    elsif ($command =~ /^\s*quit/i) { last ; }
    elsif ($command =~ /^\s*$/) { next }
    elsif ($command =~ /^\s*\#/) { next }
    elsif ($command =~ /^\s*exc[el]/i) {
	$exceptions = $line[1] ;
	open EXCEPT, $exceptions or die "can't open exceptions file $exceptions\n" ;
	while (<EXCEPT>) {
	    # file format is 2 values per line, or # commented line.
	    if (/^\s*\#/) { next } 
	    elsif (/^$/) { next }
	    elsif (/^\s*$/) { next }
	    else {
		@line = split ;
		if (@line < 2) { die "exception file error in line $., only 1 field\n" }
		push @except_src, $line[0] ;
		push @except_dst, $line[1] ;
		# print "exception: ", $line[0], " ", $line[1], "\n" ;
	    }
	}
    }
    elsif ($command =~ /^\s*dump/i) {
	use Data::Dumper ;
	$dfile = "outnetlist" ;
	if (@line > 1) { $dfile = $line[1] }
	open (DUMPFILE, ">$dfile") or die "can't open $dfile for writing\n" ;
	print DUMPFILE Dumper(\%inst, \%net, \%remap, \%eqn, \%map, \%resethash) ;
	    
    }
    elsif ($command =~ /^\s*res/i) {
	$laststring = "Checked all resets, none propagate to non-reset/set pins\n" ;
	foreach $reset (keys (%resethash)) {
	    @exceptlist = ();
	    $i = 0 ;
	    for ($i = 0 ; $i < @except_src; $i++) {
		if (($reset =~ /^$except_src[$i]$/) ||
		    ((defined $map{$reset}) && ($map{$reset} =~ /^$except_src[$i]$/))) {
		    push @exceptlist, $except_dst[$i] } 
	    }
	    $firststring = "In reset $reset\n" ;
	    trace_reset_net_fwd ($reset) ;
	}
	myprint ($laststring) ;
    }
    elsif ($command =~ /^\s*io_n/i) {
	# for inputs, do 2 checks:
	#  is list of dests just 1 dest (len == 2) ?
	#  is that 1 dest aflop and an IO ?
	$firststring = "Inputs without flops in IO location:\n" ;
	$laststring = "All inputs have flops in IO\n" ;
	foreach $input (@inputlist) {
	    if ((defined @{$net{$input}[2]}) && (@{$net{$input}[2]} == 2)) {
		# dest list is just 1 instance, do next test
		@destlist = @{$net{$input}[2]} ;
		#print "checking $input, type $destlist[1]\n" ;
		if (($destlist[1] == 3) && (defined $io{$destlist[0]})) { next ;} 
	    }
	    if (defined $remap{$input}) {$realinput = $remap{$input} } else {$realinput = $input} ;
	    myprint ($firststring, " $realinput\n") ;
	    $firststring = "" ;
	    $laststring = "" ;
	}
	myprint ($laststring) ;
	myprint ("*** Haven't implemented notflop for outputs yet\n") ;
    }
    elsif ($command =~ /^\s*io_r/i) {
	#walk an input forward and find all clocks on flops from that input
	$firststring = "Timing to inputs:\n" ;
	foreach $input (@inputlist) {
	    local %destclocks ;
	    trace_to_dest_clocks ($input) ;
	    myprint ("$firststring ${input}:\n") ;
	    $firststring = "" ;
	    $laststring = "  no dest clocks found\n" ;
	    foreach $clock (sort (keys %destclocks)) {
		$laststring = "" ;
		myprint ("  $clock, $destclocks{$clock} flop(s)\n") ;
	    }
	    myprint ($laststring) ;
	    undef %destclocks ;
	}
	#similar for outputs:
	$firststring = "Timing from outputs:\n" ;
	foreach $output (@outputlist) {
	    local %srcclocks ;
	    trace_to_src_clocks ($output) ;
	    myprint ("$firststring ${output}:\n") ;
	    $firststring = "" ;
	    $laststring = "  no src clocks found\n" ;
	    foreach $clock (sort (keys %srcclocks)) {
		$laststring = "" ;
		myprint ("  $clock, $srcclocks{$clock} flop(s)\n") ;
	    }
	    myprint ($laststring) ;
	    undef %srcclocks ;
	}
    }
    elsif ($command =~ /^\s*io_f/i) {
	$firststring = "IO locations with flops:\n" ;
	$laststring = "no flops found in IO locations\n" ;
	foreach $flop (sort (@floplist)) {
	    if (defined $io{$flop}) { 
		if (defined $remap{$flop}) {$realflop = $remap{$flop} } else {$realflop = $flop} ;
		myprint ($firststring, " $realflop\n") ;
		$firststring = "" ;
		$laststring = "" ;
	    }
	}
	myprint ($laststring) ;
    }
    elsif ($command =~ /^\s*cross/i) {
	do_flops (0) ;
    }
    elsif ($command =~ /^\s*dang/i) {
	do_flops (1) ;
	# now, for every target flop, trace backwards to check for it coming from more than 1 flop in a domain.
	$laststring = "No class II probs found" ;
	foreach $flop (sort (keys (%targetflops))) {
	    $thisclock = $inst{$flop}[3] ;
	    my @inputlist = @{$inst{$flop}[1]} ;
	    local %srcflops ;
	    local %srcclocks ;
	    @exceptlist = ();
	    $i = 0 ;
	    for ($i = 0 ; $i < @except_dst; $i++) {
		if (($flop =~ /^$except_dst[$i]$/) ||
		((defined $map{$flop}) && ($map{$flop} =~ /^$except_dst[$i]$/))) {
		push @exceptlist, $except_src[$i] 
		} ;
	    }
	    while ($temp = shift (@inputlist)) {
		trace_net_back ($temp) ;
		shift (@inputlist) ;
	    }
	    # now all src flops are in srcflops hash
	    if (defined $remap{$flop}) {$realflop = $remap{$flop} } else {$realflop = $flop} ;
	    if (defined $remap{$thisclock}) {$realclk = $remap{$thisclock} } else {$realclk = $thisclock} ;
	    $fstring = "source $realflop clock $realclk\n" ;
	    foreach $src (sort (keys (%srcflops))) {
		$srcclk = $inst{$src}[3] ;
		if (defined $srcclocks{$srcclk}) { $srcclocks{$srcclk} ++ }
		else { $srcclocks{$srcclk} = 1 } ;
	    }
	    foreach $srcclk (keys (%srcclocks)) {
		if ($srcclocks{$srcclk} > 1) {
		    myprint ("$realflop comes from $srcclocks{$srcclk} flops in $srcclk domain.\n");
		}
	    }
	}
    }
    elsif ($command =~ /^\trace_fwd/i) {
	# we make a list of strings, each string being a path:
	#   each recursive call includes the trace so far
	#   if we end at the right flop, add our string to the list.
	#   at end, report how many paths, and print each one...
	$start_pt = $line[1] ;
	$end_pt = $line[2] ;
	@fwdpath_print = ();
	# eqn format has inst name and net name identical, so we don't have to sort out which we have.
	# do have to allow for either remapped or direct name - get to direct name.
	# more general form would use just net names, and if instance names are given, convert
	# them to net names (might be a list).
	if (defined $map{$start_pt}) { $start_pt = $map{$start_pt} } ;
	if (defined $map{$end_pt}) { $end_pt = $map{$end_pt} } ;
	trace_net_fwd_print ($start_pt, $end_pt, " " . $eqn{$start_pt}) ;
	if (@fwdpath_print > 1) {
	    $count = @fwdpath_print ;
	    myprint ("Found $count paths from $start_pt to $end_pt:\n******\n", join "******\n", @fwdpath_print, "\n") ;
	}
	elsif (@fwdpath_print > 0) {
	    myprint ("Found 1 path from $start_pt to $end_pt:\n", @fwdpath_print, "\n") ;
	}
	else { myprint ("no paths found from $start_pt to $end_pt\n") }
    }
    elsif ($command =~ /^\trace_back/i) {
	$start_pt = $line[2] ;
	$end_pt = $line[1] ;
	@fwdpath_print = ();
	# eqn format has inst name and net name identical, so we don't have to sort out which we have.
	# do have to allow for either remapped or direct name - get to direct name.
	# more general form would use just net names, and if instance names are given, convert
	# them to net names (might be a list).
	if (defined $map{$start_pt}) { $start_pt = $map{$start_pt} } ;
	if (defined $map{$end_pt}) { $end_pt = $map{$end_pt} } ;
	#change this to rev when it's there....
	trace_net_fwd_print ($start_pt, $end_pt, " " . $eqn{$start_pt}) ;
	if (@fwdpath_print > 1) {
	    $count = @fwdpath_print ;
	    myprint ("Found $count paths from $start_pt to $end_pt:\n******\n", join "******\n", @fwdpath_print, "\n") ;
	}
	elsif (@fwdpath_print > 0) {
	    myprint ("Found 1 path from $start_pt to $end_pt:\n", @fwdpath_print, "\n") ;
	}
	else { myprint ("no paths found from $start_pt to $end_pt\n") }
    }
    else {
	myprint ("Unknown command: $command\n") ;
    }
}


# assoc array net has 3 elements per item - type, list of source instances and pin type, list of dest insts and pin type
# assoc array inst has 4 elements per item - type, list of input nets and pin type, list of output nets and pin types, clockname.
#
# net properties:
#   0 - intermediate
#   1 - clock
#   2 - reset
#   4 - flop input
#   8 - flop output
#   16 - chip input
#   32 - chip output
#
# pin types:
#   0 - comb output
#   1 - comb input
#   2 - flop output
#   3 - flop D input
#   4 - flop clock
#   5 - flop reset
#   6 - flop set
#   7 - flop CE input
#   10 - mem clock 1 (write)
#   11 - mem clock 2 (read)
#   12 - mem input, setup to clock 1
#   13 - mem input, setup to clock 2
#   14 - mem input, comb to outputs, set A
#   15 - mem input, comb to outputs, set B
#   16 - mem output, from clock 1
#   17 - mem output, from clock 2
#   18 - mem output, from input set A
#   19 - mem output, from input set B
#   20 - mem output, from input set A and clock 1
#   21 - mem output, from input set B and clock 2
#
# inst type:
#   0 - combinational logic
#   1 - flop
#   2 - memory

#split a line into tokens.
sub eqnsplit {
    # use split to separate everything based on spaces, then tokenize it
    my @ret ;
    foreach (split) {
	foreach (split /([\(\);,\!])/) {
	    if ($_ ne "") { push (@ret, $_) }
	}
    }
    return @ret ;
}

sub net_property {
    if (defined ($net{$_[0]}[0])) { # net already exists
	$net{$_[0]}[0] = $net{$_[0]}[0] | $_[1] ;
    }
    else { $net{$_[0]}[0] = $_[1] }
    if ($_[1] == 2) {$resethash{$_[0]} = $_[0]} ;
}
sub net_src_pin {
    push (@{$net{$_[0]}[1]}, $_[1], $_[2]) ;
}
sub net_dest_pin {
#    print "adding inst $_[1] to net $_[0], type $_[2]\n" ;
    push (@{$net{$_[0]}[2]}, $_[1], $_[2]) ;
}
sub inst_o_net {
#    print "adding $_[1] to $_[0], type $_[2]\n" ;
    push (@{$inst{$_[0]}[2]}, $_[1], $_[2]) ;
}
sub inst_i_net {
    push (@{$inst{$_[0]}[1]}, $_[1], $_[2]) ;
}
sub add_inst {
    $inst{$_[0]}[0] = $_[1] ;
    if ($_[1] == 1) { push (@floplist, $_[0]) }
}
sub trace_net_back {
    if (defined @{$net{$_[0]}[1]}) {
	my @srclist = @{$net{$_[0]}[1]} ; # get "list" of source instances
      SRC:
	while ($temp = shift (@srclist)) {
	    $pintype = shift (@srclist) ;
	    if ($pintype == 0) {  # combi, keep going
		my @inputlist = @{$inst{$temp}[1]} ; # get list of inputs for each instance
		while ($temp = shift (@inputlist)) {
		    trace_net_back ($temp) ;
		    shift (@inputlist) ;
		}
	    }
	    elsif (($pintype == 2) || ($pintype > 15)) { #flop or mem, add to hash
		if ($thisclock ne $inst{$temp}[3]) {
		    for ($i = 0 ; $i < @exceptlist; $i++) {
			if ($temp =~ /^$exceptlist[$i]$/) { next SRC ; } 
		    }
		    if (defined $remap{$temp}) { 
			$temp1 = $remap{$temp} ;
			for ($i = 0 ; $i < @exceptlist; $i++) {
			    if ($temp1 =~ /^$exceptlist[$i]$/) { next SRC ; } 
			}
		    }
		    if (defined $srcflops{$temp}) { $srcflops{$temp} ++ } 
		    else { $srcflops{$temp} = 1 } ;
		}
	    }
	}
    }
}
sub trace_net_fwd {
    # given a net, recurse on all outputs of instances connected to net, if they are combi. 
    # add all non-combi dest to a hash with all dest flops from our starting point.
#    print "tracing net $_[0]\n" ;
    if (defined @{$net{$_[0]}[2]}) {
	my @destlist = @{$net{$_[0]}[2]} ; # get list of destination instances
      DEST: 
	while ($temp = shift (@destlist)) {
	    $pintype = shift (@destlist) ;
	    if ($pintype == 1) {  # combi, keep going
		my @outputlist = @{$inst{$temp}[2]} ; # get "list" of outputs for each instance
		while ($temp = shift (@outputlist)) {
		    trace_net_fwd ($temp) ;
		    shift (@outputlist) ;
		}
	    }
	    elsif (($pintype == 3) || ($pintype == 7)) { #flop, add to hash
		if ($thisclock ne $inst{$temp}[3]) {
		    #print "exceptlist: ", @exceptlist, " compared to $temp\n";
		    for ($i = 0 ; $i < @exceptlist; $i++) {
			if ($temp =~ /^$exceptlist[$i]$/) { next DEST ; } 
		    }
		    if (defined $remap{$temp}) { 
			$temp1 = $remap{$temp} ;
			for ($i = 0 ; $i < @exceptlist; $i++) {
			    if ($temp1 =~ /^$exceptlist[$i]$/) { next DEST ; } 
			}
		    }
		    if (defined $desthash{$temp}) { $desthash{$temp} ++ } 
		    else { $desthash{$temp} = 1 } ;
		    $targetflops{$temp} = $temp ;
		    # print "added flop $temp\n" ;
		}
	    }
	}
    }
}

sub trace_reset_net_fwd {
    # given a net, recurse on all outputs of instances connected to net, if they are combi.
    if (defined @{$net{$_[0]}[2]}) {
	my @destlist = @{$net{$_[0]}[2]} ; # get list of destination instances
	while ($temp = shift (@destlist)) {
	    $pintype = shift (@destlist) ;
	    if ($pintype == 1) {  # combi, keep going
		my @outputlist = @{$inst{$temp}[2]} ; # get "list" of outputs for each instance
		while ($temp2 = shift (@outputlist)) {
		    trace_reset_net_fwd ($temp2) ;
		    #trace_reset_net_fwd ($temp2, $temp, @_) ; # call that includes "traceback"
		    shift (@outputlist) ;
		}
	    }
	    elsif (($pintype == 3) || ($pintype == 7)) { #flop, add to hash
		for ($i = 0 ; $i < @exceptlist; $i++) {
		    if ($temp =~ /^$exceptlist[$i]$/) { next ; } 
		}
		if (defined $remap{$temp}) { $temp = $remap{$temp} } ;
		for ($i = 0 ; $i < @exceptlist; $i++) {
		    if ($temp =~ /^$exceptlist[$i]$/) { next ; } 
		}
		myprint ("$firststring reset ends at flop $temp, pintype $pintype") ;
		# foreach (@_) { print " $_" } ;
		myprint ("\n") ;
		$firststring = "" ; $laststring = "" ;
	    }
	}
    }
}

sub trace_to_dest_clocks {
    # given a net, recurse on all outputs of instances connected to net, if they are combi. 
    # find all dest flop clocks, and count flops
#    print "tracing net $_[0]\n" ;
    if (defined @{$net{$_[0]}[2]}) {
	my @destlist = @{$net{$_[0]}[2]} ; # get list of destination instances
	while ($temp = shift (@destlist)) {
	    $pintype = shift (@destlist) ;
	    if ($pintype == 1) {  # combi, keep going
		my @outputlist = @{$inst{$temp}[2]} ; # get "list" of outputs for each instance
		while ($temp = shift (@outputlist)) {
		    trace_to_dest_clocks ($temp) ;
		    shift (@outputlist) ;
		}
	    }
	    elsif (($pintype == 3) || ($pintype == 7)) { #flop, add to hash
		if (defined $destclocks{$inst{$temp}[3]}) {
		    $destclocks{$inst{$temp}[3]} ++ ;
		}
		else { $destclocks{$inst{$temp}[3]} = 1 }
	    }
	}
    }
}
sub trace_to_src_clocks {
    # given a net, recurse on all inputs of instances connected to net, if they are combi. 
    # find all src flop clocks, and count flops
#    print "tracing net $_[0]\n" ;
    if (defined @{$net{$_[0]}[1]}) {
	my @srclist = @{$net{$_[0]}[1]} ; # get "list" of source instances
	while ($temp = shift (@srclist)) {
	    $pintype = shift (@srclist) ;
	    if ($pintype == 0) {  # combi, keep going
		my @inputlist = @{$inst{$temp}[1]} ; # get list of inputs for each instance
		while ($temp = shift (@inputlist)) {
		    trace_to_src_clocks ($temp) ;
		    shift (@inputlist) ;
		}
	    }
	    elsif ($pintype == 2) { #flop, add to hash
		if (defined $destclocks{$inst{$temp}[3]}) {
		    $srcclocks{$inst{$temp}[3]} ++ ;
		}
		else { $srcclocks{$inst{$temp}[3]} = 1 }
	    }
	}
    }
}

sub trace_net_fwd_print {
#parameters are current net, end net, trace so far.
    #print "tr_n_f_p: ", join ' ', @_, "\n" ;
    $curr = shift ;
    $end = shift ;
    $trace = shift ;
    if (defined @{$net{$curr}[2]}) {
	my @destlist = @{$net{$curr}[2]} ; # get list of destination instances
	#print "dests: ", join '. ', @destlist, "\n" ;
	while ($temp = shift (@destlist)) {
	    $pintype = shift (@destlist) ;
	    #print "dest $temp, check $end\n" ;
	    if ($temp eq $end) {
		push @fwdpath_print, $trace . " " . $eqn{$end} ;
		next ;
	    }
	    if ($pintype == 1) {  # combi, keep going
		my @outputlist = @{$inst{$temp}[2]} ; # get "list" of outputs for each instance
		while ($temp = shift (@outputlist)) {
		    trace_net_fwd_print ($temp, $end_pt, $trace . " " . $eqn{$temp}) ;
		    shift (@outputlist) ;
		}
	    }
	}
    }
}

sub do_flops {
    $laststring = "No clock crossings found (or all found matched exception list).\n" ;
    foreach $flop (sort (@floplist, @memWclkedRdlist)) {
#    print "walking foward from flop $flop.\n" ;
#    foreach (@{$inst{$flop}[2]}) {print $_} ; print "\n" ;
#    print @{$inst{$flop}[2]} ;
	$thisclock = $inst{$flop}[3] ;
	my @outputlist = @{$inst{$flop}[2]} ;
	local %desthash ;
	local %destclocks ;
	@exceptlist = ();
	$i = 0 ;
	for ($i = 0 ; $i < @except_src; $i++) {
	    if (($flop =~ /^$except_src[$i]$/) ||
		((defined $map{$flop}) && ($map{$flop} =~ /^$except_src[$i]$/))) {
		#print "matched ", $except_src[$i], " against $flop, adding ", $except_dst[$i], " to exceptlist\n" ;
		push @exceptlist, $except_dst[$i] 
		} ;
	}
	while ($temp = shift (@outputlist)) {
	    trace_net_fwd ($temp) ;
	    shift (@outputlist) ;
	}
	# now all term flops are in desthash, so print them out
	# then clear desthash
	if (defined $remap{$flop}) {$realflop = $remap{$flop} } else {$realflop = $flop} ;
	if (defined $remap{$thisclock}) {$realclk = $remap{$thisclock} } else {$realclk = $thisclock} ;
	$fstring = "source $realflop clock $realclk\n" ;
	foreach $dest (sort (keys (%desthash))) {
	    if (defined $remap{$dest}) {$realdest = $remap{$dest} } else {$realdest = $dest} ;
	    $destclk = $inst{$dest}[3] ;
	    if (defined $remap{$destclk}) {$destclk = $remap{$destclk} } ;
	    if (($_[0] == 0) || ($desthash{$dest} > 1)) {
		myprint ("$fstring  $realdest, $destclk, $desthash{$dest} path(s)\n") ;
		$fstring = "" ;
		$laststring = "" ;
	    }
	    if (defined $destclocks{$destclk}) { $destclocks{$destclk} ++ }
	    else { $destclocks{$destclk} = 1 } ;
	}
	if ($_[0]) {
	    # now see whether this flop propagated to more than 1 flop in any other clock domain
	    foreach $destclk (keys (%destclocks)) {
		if ($destclocks{$destclk} > 1) {
		    myprint ("$realflop propagates to $destclocks{$destclk} flops in $destclk domain.\n");
		}
	    }
	}
	undef %desthash ;
	undef %destclocks ;
    }
    myprint ($laststring) ;
}

sub myprint {
    print @_ ;
    print LOGFILE @_ ;
}
