#!/usr/local/bin/perl -w
# vrename - Rename Verilog signals across multiple files
# $Id: vrename,v 1.3 1999/06/02 17:30:16 wsnyder Exp $
# Author: Wilson Snyder <wsnyder@ultranet.com>
################ Introduction ################
#
# This program allows signals to be renamed across many files.
# 
# This program is Copyright 1998 by Wilson Snyder.
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# If you do not have a copy of the GNU General Public License write to
# the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, 
# MA 02139, USA.

require 5.005;
use English;
use Getopt::Long;
use IO::File;
use Pod::Text;
use Verilog::Language;
use Verilog::Parser;

( $VERSION ) = '$Revision: 1.3 $ ' =~ /\$Revision:\s+([^\s]+)/;

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

# List of signals to never crypt
# (Just put temporaries into signals.vrename)
%Vrename_Dont_Crypt =
    (
     "unused_ok"  => "",
     "__FILE__"  => "",
     "__LINE__"  => "",
     );
    
# These defines contain a preprocessor directive
# Thus when crypting we have to keep them at left edge
%Vrename_Left_Edge_Define =
    ('`time_scale' => "",
     );

######################################################################
# main

# capitalized are globals
my $Debug = 0;
$do_change = 0;
$do_list = 0;
$Do_Xref = 0;
$Do_Crypt = 0;
$do_read = 0;
$change_filename = "signals.vrename";
$output_dir = "";
@Files = ();

$result = &GetOptions (
		       "help"		=> \&usage,
		       "debug"		=> \&debug,
		       "version"	=> \&version,
		       "crypt!"		=> \$Do_Crypt,
		       "change!"	=> \$do_change,
		       "changefile=s"	=> \$change_filename,
		       "o=s"		=> \$output_dir,
		       "read!"		=> \$do_read,
		       "list!"		=> \$do_list,
		       "xref!"		=> \$Do_Xref,
		       "<>"		=> \&parameter,
		       );

if ($output_dir ne ""
    && $output_dir !~ /[\/\\]$/) {
    $output_dir .= "/";
}

if (!$result || !@Files) { &usage(); }

if ($output_dir eq "" && $Do_Crypt && $do_change) {
    print STDERR "You must use -o with -crypt or your uncrypted files will be overwritten.\n";
    exit (10);
}

if ($do_read || $do_change) {
    changes_read ($change_filename);
}

if ($do_change) {
    foreach $file (@Files) {
	verilog_change_sig ($file);
    }
}

if ($do_list) {
    foreach $file (@Files) {
	my $parser = Verilog::Vrename::Reader->new();
	$parser->parse_file ($file);
    }
    if ($Do_Crypt) {
	changes_crypt();
    }
    changes_from_loc ();
    changes_write ($change_filename);
}

exit (0);

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

sub nop{}

sub usage {
    print "Version: $VERSION\n";
    print '$Id: vrename,v 1.3 1999/06/02 17:30:16 wsnyder Exp $ ', "\n";
    $SIG{__WARN__} = \&nop;	#pod2text isn't clean.
    pod2text($0);
    exit (1);
}

sub version {
    print "Version: $VERSION\n";
    print '$Id: vrename,v 1.3 1999/06/02 17:30:16 wsnyder Exp $ ', "\n";
    exit (1);
}

sub debug {
    $Debug = 1;
    $Verilog::Parser::Debug = $Debug;
}

sub parameter {
    my $param = shift;
    push @Files, $param;
    (-r $param) or die "Can't open $param";
}

######################################################################
	      
sub changes_from_loc {
    # If a signal was found, but doesn't have change information, make it
    # default to have a change record with replacement same as basename.
    foreach $sig (sort (keys %Signal_Locs)) {
	if (! defined $Signal_Newname{$sig}) {
	    $Signal_Newname{$sig} = $sig;
	}
    }
}

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

sub changes_crypt {
    # Make random names for signals

    my $sig;
    my %used_rand = ();
    foreach $sig (keys %Signal_Locs) {
	if (! defined $Signal_Newname{$sig}
	    && $sig !~ /\$/
	    && (! defined $Vrename_Dont_Crypt{$sig})
	    ) {
	    my $has_encry = 0;
	    my $has_uncry = 0;
	    $has_uncry ||= $main::Dont_Decrypt{$sig};
	    foreach $loc (@{$Signal_Locs{$sig}}) {
		$has_encry ||= defined $Encrypt{$loc};
		$has_uncry ||= ! (defined $Encrypt{$loc});
	    }
	    if ($has_encry && !$has_uncry) {
		$rand = random_string();
		while (defined $used_rand{$rand}) {
		    $rand = random_string();
		}
		$used_rand{$rand} = 1;
		$Signal_Newname{$sig} = $rand;
	    }
	}
    }
}

sub random_string {
    return  sprintf ("%c%c%c%c%c%c", 
		     (rand (26)) + 65,
		     (rand (26)) + 65,
		     (rand (26)) + 65,
		     (rand (26)) + 65,
		     (rand (26)) + 65,
		     (rand (26)) + 65,
		     (rand (26)) + 65,
		     (rand (26)) + 65);
}
######################################################################

sub changes_write {
    # Read in the list of signal names to change
    my $filename = shift;

    my $fh = new IO::File;
    $fh->open (">$filename") or die "%Error: $! $filename.\n";

    my $sigcnt=0;
    print $fh "# Generated by vrename on ", scalar(localtime), "\n";
    print $fh "#\n";
    print $fh "# Files read for this analysis:\n";
    my $file;
    foreach $file (@Files) {
	print $fh "vfile\t\"$file\"";
	if ($Encrypt{$file}) {
	    print $fh "\t-crypt";
	}
	print $fh "\n";
    }
    print $fh "#\n";
    print $fh "#\t\Original Signal Name\t\tName to change to\n";
    print $fh "#\t\--------------------\t\t-----------------\n";
    print $fh "#\n";

    my $sig;
    foreach $sig (sort (keys %Signal_Newname)) {
	$sigcnt++;
	print $fh "sigren\t\"$sig\"";
	my $len = 8 + 2 + length $sig;
	while ($len < 32) {
	    print $fh "\t";
	    $len += 8;
	}
	print $fh "\t\"$Signal_Newname{$sig}\"";
	if ($Do_Xref) {
	    my $len = 40 + 2 + length $sig;
	    while ($len < 64) {
		print $fh "\t";
		$len += 8;
	    }
	    print $fh "\t#";
	    foreach $loc (@{$Signal_Locs{$sig}}) {
		print $fh "$loc ";
	    }
	}
	print $fh "\n";
    }

    print $fh "#\n";
    print $fh "# Use M-x compile in emacs to automatically perform the changes:\n";
    print $fh "## Local Variables: ***\n";
    print $fh "## compile-command: \"$0 -change ";
    foreach $file (@Files) {
	print $fh $file, " ";
    }
    print $fh "\" ***\n";
    print $fh "## End: ***\n";

    $fh->close();
    print "Wrote $filename  (Changes list, $sigcnt signals)\n";
}

sub changes_read {
    # Write out the list of signals in a format for easy editing
    my $filename = shift;

    my $fh = new IO::File;
    $fh->open ($filename) or die "%Error: $! $filename.\n";

    while (my $line = $fh->getline()) {
	chomp $line;

	$line =~ s/#.*$//;
	$line =~ s/^[ \t]+//;

	if ($line =~ /^$/ ) {
	    # comment
	}
	elsif ($line =~ /^sigren\s+\"([^\"]+)\"\s+\"([^\"]+)\"/ ) {
	    print "Rename got $1  $2\n" if ($Debug);
	    $Signal_Newname {$1} = $2;
	}
	elsif ($line =~ /^vfile/ ) {
	    # ignore
	}
	else {
	    die "$filename $.: Can't parse \"$line\"\n";
	}
    }

    $fh->close();
}

######################################################################
	      
sub crypt_string {
    my $filestrg = shift;

    my $magicb = "@@@@@!~SAVEVLB~!@@@@@";
    my $magice = "@@@@@!~SAVEVLE~!@@@@@";
    $filestrg =~ s/(\/[*\/]\s*)[Vv]erilint\s*([0-9]+)\s*off/$magicb Verilint $2 off $magice$1/g;
    $filestrg =~ s/(\/[*\/]\s*)([Ss]ynopsys\s*\S+)/$magicb $2 $magice$1/g;
    $filestrg =~ s/\/\/.*\n//g;
    $filestrg =~ s/\/\*[\000-\377]*?\*\///g;
    $filestrg =~ s/[ \t]+/ /g;
    $filestrg =~ s/^[ \t]+//g;
    $filestrg =~ s/[ \t]+$//g;
    my $oldstrg = $filestrg;
    $filestrg = "/*ENCRYPTED:VRENAME*/";
    my $pos = 0;
    my $oldpos;
    my $literal = 0;
    my $define = 0;
    for ($oldpos = 0; $oldpos < length $oldstrg; $oldpos++) {
	my $char = substr $oldstrg, $oldpos, 1;
	if ($char eq "\n") {
	    if ($define || $literal) {
		$filestrg .= $char;
		$pos = 0;
		$define = 0;
	    } else {
		$filestrg .= " "; $pos++;
	    }
	} 
	elsif ($char eq "`" && !$literal) {
	    my $defkwd = (substr $oldstrg, $oldpos);
            $defkwd =~ /^(\`[a-z0-9_]*)/;
	    $defkwd = $1;
	    if (Verilog::Language::is_keyword ($defkwd)
		|| (defined $Vrename_Left_Edge_Define {$defkwd})) {
		$filestrg .= "\n"; $pos = 0;
		$filestrg .= $char; $pos++;
		$define = 1;
	    }
	    else {
		$filestrg .= $char; $pos++;
	    }
	}
	elsif ($char eq '"') {
	    $filestrg .= $char; $pos++;
	    $literal = ! $literal;
	}
	elsif ($char eq " " && !$literal && !$define) {
	    if ($pos > 80) {
		$filestrg .= "\n"; $pos = 0;
	    }
	    elsif ($pos != 0) {
		$filestrg .= $char; $pos++;
	    }
	}
	else {
	    $filestrg .= $char; $pos++;
	}
    }
    $filestrg =~ s/[ \t]+/ /g;
    while ($filestrg =~ /$magicb([\000-\377]*?)$magice/) {
	my $rep = $1;
	$rep =~ s/[\n]/ /g;
	$filestrg =~ s/$magicb([\000-\377]*?)$magice/\n\/\*$rep\*\/\n/;
    }
    $filestrg .= "\n";
    return $filestrg;
}
    
######################################################################

sub verilog_change_sig {
    # Rename signals in this filename
    my $filename = shift;

    # Read in the whole file in a swath
    local $INPUT_RECORD_SEPARATOR = undef;
    open (VFILE, "<$filename") or die "Can't read $filename.";
    my $filestrg = <VFILE>;
    close VFILE;

    if ($Do_Crypt) {
	if ($filestrg =~ /ENCRYPT_ME/) {
	    $Encrypt{$filename} = 1;
	}
    }

    # If crypting, strip comments
    if ($Encrypt{$filename}) {
	$filestrg = crypt_string ($filestrg);
    }

    # Replace any changed signals
    my $hadrepl = 0;
    my $sig;
    my %signal_magic = ();
    # pass1: replace with magic replacement string
    # (two steps so renaming a->b and b->a at the same time doesn't screw up) 
    foreach $sig (keys %Signal_Newname) {
	my $new = $Signal_Newname{$sig};
	if ($new ne $sig) {
	    my $magic = "@@@@@!~${hadrepl}~!@@@@@";
	    if ($filestrg =~ s/([^a-zA-Z0-9_\$])$sig(?=[^a-zA-Z0-9_])/$1$magic/g) {
		print "match s$sig n$new m$magic\n" if $Debug;
		$hadrepl ++;
		$signal_magic{$sig} = $magic;
	    }
	}
    }
    # pass2: magic->new
    foreach $sig (keys %Signal_Newname) {
	if (defined $signal_magic{$sig}) {
	    my $magic = $signal_magic{$sig};
	    my $new = $Signal_Newname{$sig};
	    if ($filestrg =~ s/$magic/$new/g) {
		print "match s$sig n$new m$magic\n" if $Debug;
	    }
	}
    }

    # Save it
    if ($hadrepl || $Do_Crypt) {
	open (VFILE, ">$output_dir$filename") or die "%Error: $! $output_dir$filename.\n";
	print VFILE $filestrg;
	close VFILE;
	if ($Encrypt{$filename}) {print "Encrypted ";} else {print "Wrote ";}
	print "$filename  ($hadrepl signals matched)\n";
    }
}

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

package Verilog::Vrename::Reader;
require Exporter;

use strict;
use Carp;
use English;
use vars qw( $Debug @ISA @EXPORT
	     $Last_Keyword $Last_Filename
	     %Modules_Sigs );
use Verilog::Parser;

BEGIN {
    @ISA = qw( Verilog::Parser );
    @EXPORT = qw( $Debug );
}

sub new {
    my $class = shift;
    my $self = $class->SUPER::new();
    bless $self, $class; 
    return $self;
}

sub keyword {
    # Callback from parser when a keyword occurs
    my $self = shift;	# Parser invoked
    my $token = shift;	# What token was parsed

    $Last_Keyword = $token;
}

sub symbol {
    # Callback from parser when a symbol occurs
    my $self = shift;	# Parser invoked
    my $sig = shift;	# What token was parsed

    #print "Signal callback $self $token\n" if ($Debug);
    $sig =~ s/\`//g;	# Remove `s from define usages else won't match define declaration

    if (!$Modules_Sigs{$sig}) {
	push @{$main::Signal_Locs{$sig}}, $Last_Filename;
    }
    $Modules_Sigs{$sig} = 1;
    if ($main::Do_Crypt && ($Last_Keyword eq "module"
			    || $Last_Keyword eq "function"
			    || $Last_Keyword eq "task")) {
	$main::Dont_Decrypt{$sig} = 1;
	$Last_Keyword = "";
    }
}

sub parse_file {
    # Read all signals in this filename
    # Overloads Verilog::Parse::parse_file
    @_ == 2 or croak 'usage: $parser->parse_file($filename)';
    my $self = shift;
    my $filename = shift;

    local %Modules_Sigs = ();	# Signals already found in module

    my $fh = new IO::File;
    local $Last_Keyword = "";

    local $Last_Filename = $filename;

    $fh->open($filename) or die "%Error: $! $filename\n";
    while (my $line = $fh->getline() ) {
	Verilog::Parser::parse ($self, $line);
    }

    if ($main::Do_Crypt) {
	seek ($fh, 0, 0);
	local $INPUT_RECORD_SEPARATOR = undef;
	my $filestrg = <$fh>;
	# Same test below
	if ($filestrg =~ /ENCRYPT_ME/) {
	    $main::Encrypt{$filename} = 1;
	}
    }
    $fh->close;
}

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

__END__

=head1 NAME

vrename - change signal names across many verilog files

=head1 SYNOPSIS

C<vrename> I<filename.v> I<filename.v> ...

=head1 DESCRIPTION

Vrename will allow a signal to be changed across all levels of the
design hiearchy using a three step process.  (It actually includes
module names, macros, and other definitions, so those can be changed too.)


First, use C<vrename --list file.v file2.v ....>

This creates a signals.vrename file which is edited manually.

Last, use C<vrename --change file.v file2.v ....>

=head1 ARGUMENTS

vrename takes the following arguments:

=over 4

=item --help

Displays this message and program version and exits.

=item --version

Displays program version and exits.

=item --change

Take the signals file signals.dat in the current directory
and change the signals in the design as specified by the
signals file.  Either --list or --change must be specified.

=item --list

Create a list of signals in the design and write to 
signals.dat.  Either --list or --change must be specified.

=item --xref

Include a cross reference of where the signals are used.
--list must also be specified.

=item --read

Read the changes list, allows --list to append to the
changes already read.

=item --crypt

With --list, randomize the signal renames.  With --change, compress spaces
and comments and apply those renames listed in the file (presumably created
with vrename --list --crypt).  The comment /*ENCRYPT_ME*/ must be included
in all files that need to be encrypted.

=item --o {dir}

Use the given directory for output instead of the current directory.

=item --changefile {file}

Use the given filename instead of "signals.vrename".

=back

=head1 SEE ALSO

C<Verilog::Parser>

=head1 DISTRIBUTION

The latest version is available from CPAN or
C<http://www.ultranet.com/~wsnyder/veripool/verilog-perl.html>.

=head1 AUTHORS

Wilson Snyder <wsnyder@ultranet.com>

=cut
