#!/usr/bin/perl -w
# See copyright, etc in below POD section.
######################################################################

package main;
use FindBin qw($RealBin);
use lib "$RealBin/blib/arch";
use lib "$RealBin/blib/lib";
use lib "$RealBin";

use Getopt::Long;
use IO::Dir;
use IO::File;
use Pod::Usage;

use Verilog::Language;
use Verilog::Getopt;
use Verilog::Parser;
use strict;

use vars qw ($VERSION %Vrename_Dont_Crypt %Vrename_Left_Edge_Define
	     %Signal_Locs %Signal_Newname %Encrypt
	     $Debug $Opt_Xref $Opt_Crypt $Opt_Crypt_All $Opt_Write $Opt_Keywords
	     @Files);

$VERSION = '3.403';

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

# List of signals to never crypt
# (Just put temporaries into signals.vrename)
foreach (
	 'unused_ok',
	 '__FILE__',	# Verilator, proposed for Verilog 2005
	 '__LINE__',	# Verilator, proposed for Verilog 2005
	 'verilator',
	 'ms','us','ns','ps','fs',	# Time precision arguments used in `timescale
	 'a'..'z', 'A'..'Z',		# Single character names (cryptic enough!)
	 ) {
    $Vrename_Dont_Crypt{$_} = "";
}

# These defines contain a preprocessor directive
# Thus when crypting we have to keep them at left edge
%Vrename_Left_Edge_Define =
    ('`time_scale' => "",
     '`timescale' => "",
     );

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

# capitalized are globals
$Debug = 0;
my $opt_change = 0;
my $opt_list = 0;
$Opt_Xref = 0;
$Opt_Crypt = 0;   # Global scope, used in sub-package
$Opt_Crypt_All = 0;   # Global scope, used in sub-package
$Opt_Write = 1;
$Opt_Keywords = 0;
my $opt_read = 0;
my $opt_change_filename = "signals.vrename";
my $opt_change_lang;
my $output_dir = "";
@Files = ();

if (! GetOptions (
		  "help"	=> \&usage,
		  "debug"	=> \&debug,
		  "version"	=> sub { print "Version $VERSION\n"; exit(0); },
		  "change!"	=> \$opt_change,
		  "changefile=s"=> \$opt_change_filename,
		  "changelang!"	=> \$opt_change_lang,
		  "crypt!"	=> \$Opt_Crypt,
		  "cryptall!"	=> \$Opt_Crypt_All,
		  "keywords!"	=> \$Opt_Keywords,
		  "language=s"	=> sub { shift; Verilog::Language::language_standard(shift); },
		  "list!"	=> \$opt_list,
		  "o=s"		=> \$output_dir,
		  "read!"	=> \$opt_read,
		  "write!"	=> \$Opt_Write,
		  "xref!"	=> \$Opt_Xref,
		  "<>"		=> \&parameter,
		  )) {
    die "%Error: Bad usage, try 'vrename --help'\n";
}
$Opt_Crypt = 1 if $Opt_Crypt_All;

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

if (!@Files) { &usage(); }

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

if ($opt_read || $opt_change) {
    changes_read ($opt_change_filename);
}

if ($opt_change) {
    foreach my $file_or_dir (@Files) {
	verilog_change_sig ($file_or_dir);
    }
}

if ($opt_list) {
    foreach my $file_or_dir (@Files) {
	parse_file($file_or_dir);
    }
    if ($Opt_Crypt) {
	changes_crypt();
    }
    changes_from_loc ();
    if ($opt_change_lang) {
	changes_lang();
    }
    changes_write ($opt_change_filename);
}

exit (0);

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

sub nop{}

sub usage {
    print "Version: $VERSION\n";
    pod2usage(-verbose=>2, -exitval=>2, -output=>\*STDOUT, -noperldoc=>1);
    exit (1);
}

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

sub parameter {
    my $param = shift;
    push @Files, "$param"; # Must quote to convert Getopt to string, bug298
    (-r $param) or die "%Error: 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 my $sig (sort (keys %Signal_Locs)) {
	if (! defined $Signal_Newname{$sig}) {
	    $Signal_Newname{$sig} = $sig;
	}
    }
}

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

sub changes_crypt {
    # Make random names for signals

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

sub random_string {
    while (1) {
	my $sig = sprintf ("%c%c%c%c%c%c",
			   (rand (26)) + ord('a'),
			   (rand (26)) + ord('a'),
			   (rand (26)) + ord('a'),
			   (rand (26)) + ord('a'),
			   (rand (26)) + ord('a'),
			   (rand (26)) + ord('a'),
			   (rand (26)) + ord('a'),
			   (rand (26)) + ord('a'));
	return $sig if !Verilog::Language::is_keyword($sig);
    }
}
######################################################################

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

    my $fh = IO::File->new(">$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";
    foreach my $file (@Files) {
	print $fh "vfile\t\"$file\"";
	if ($Encrypt{$file}) {
	    print $fh "\t-crypt";
	}
	print $fh "\n";
    }
    print $fh "#\n";
    print $fh "#\tOriginal Signal Name\t\tName to change to\n";
    print $fh "#\t--------------------\t\t-----------------\n";
    print $fh "#\n";

    foreach my $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 ($Opt_Xref) {
	    my $len = 40 + 2 + length $sig;
	    while ($len < 64) {
		print $fh "\t";
		$len += 8;
	    }
	    print $fh "\t#";
	    foreach my $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 my $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;
    print "Read $filename\n";

    my $fh = IO::File->new("<$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 "%Error: $filename $.: Can't parse \"$line\"\n";
	}
    }

    $fh->close();
}

sub changes_lang {
    # Grab keywords from most recent spec, not current --language
    my %kwds = Verilog::Language::language_keywords(Verilog::Language::language_maximum());
    foreach my $kwd (sort keys %kwds) {
	next if Verilog::Language::is_keyword($kwd);  # Already a keyword in sources
	next if !$Signal_Newname{$kwd};  # Not needed for the current file
	$Signal_Newname{$kwd} = '\\'.$kwd.' ';
    }
}

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

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/\/\*[\000-\377]*?\*\///g;        # block comments
    $filestrg =~ s/^\s*\/\/.*\n//g;                 # lines beginning with '//'
    $filestrg =~ s/\/\/[^\"\n]*\n//g;               # inline comments with no '"' chars, saves '"////stuf"'
    $filestrg =~ s/\/\/[^\"\n]*\"[^\"\n]*\".*\n//g; # inline comments with 2 '"' chars, kills '// "stuff"'
    $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 _recurse_dir {
    my $dir = shift;
    my $callback = shift;

    my $dh = new IO::Dir($dir) or warn "%Warning: $! $dir\n";
    while (defined (my $basefile = $dh->read)) {
	next if Verilog::Getopt->file_skip_special($basefile);
	&$callback("$dir/$basefile");
    }
    $dh->close();
    return;
}

sub parse_file {
    my $filename = shift;

    print "parse file $filename\n";
    if (-d $filename) {
	_recurse_dir($filename, \&parse_file);
	return;
    }

    my $parser = Verilog::Vrename::Reader->new();
    $parser->parse_file ($filename);
}

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

    if (-d $filename) {
	_recurse_dir($filename, \&verilog_change_sig);
	return;
    }

    # Read in the whole file in a swath
    my $fh = IO::File->new ("<$filename") or die "%Error: $! $filename";
    my $filestrg = join('',$fh->getlines());
    $fh->close();

    if ($Opt_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 %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 my $sig (sort keys %Signal_Newname) {
	my $new = $Signal_Newname{$sig};
	if ($new ne $sig) {
	    my $magic = "@@@@@!~${hadrepl}~!@@@@@";
	    my $sig_quoted = quotemeta $sig;
	    if ($filestrg =~ s/([^a-zA-Z0-9_\$\%\'\"\\])$sig_quoted(?=[^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 my $sig (sort 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 || $Opt_Crypt) {
	if (!$Opt_Write) {
	    print "$filename  ($hadrepl signals matched) (-n: Not written)\n";
	} else {
	    my $fh = IO::File->new(">$output_dir$filename")
		or die "%Error: $! $output_dir$filename.\n";
	    $fh->print($filestrg);
	    $fh->close;
	    if ($Encrypt{$filename}) {print "Encrypted ";} else {print "Wrote ";}
	    print "$filename  ($hadrepl signals matched)\n";
	}
    }
}

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

package Verilog::Vrename::Reader;

use Carp;
use Verilog::Parser;
use base qw(Verilog::Parser);
use vars qw($Debug);
use strict;

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

sub _track_signal {
    my $self = shift;
    my $sig = shift;

    $sig =~ s/\`//g;	# Remove `s from define usages else won't match define declaration

    if (!$self->{_file_sigs}{$sig}) {
	push @{$main::Signal_Locs{$sig}}, $self->filename;
    }
    $self->{_file_sigs}{$sig} = 1;
    if ($main::Opt_Crypt && ($self->{_last_keyword} eq "module"
			     || $self->{_last_keyword} eq "function"
			     || $self->{_last_keyword} eq "task")) {
	$main::Dont_Decrypt{$sig} = 1;
	$self->{_last_keyword} = "";
    }
}

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

    $self->{_last_keyword} = $token;
    if ($main::Opt_Keywords) {
	$self->_track_signal($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);
    $self->_track_signal($sig);
}

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

    $self->{_file_sigs} = {}; # Signals already found in module
    $self->{_last_keyword} = "";

    $self->SUPER::parse_file($filename);

    if ($main::Opt_Crypt) {
	my $fh = IO::File->new("<$filename") or die "%Error: $! $filename\n";
	local $/ = undef;
	my $filestrg = <$fh>;
	# Same test below
	if ($filestrg =~ /ENCRYPT_ME/) {
	    $main::Encrypt{$filename} = 1;
	}
	$fh->close;
    }
}

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

__END__
=pod

=head1 NAME

vrename - change signal names across many Verilog files

=head1 SYNOPSIS

  vrename <filename_or_directory>...

=head1 DESCRIPTION

Vrename will allow a signal to be changed across all levels of the design
hierarchy, or to create a cross reference of signal names.  (It actually
includes module names, macros, and other definitions, so those can be
changed too.)

Vpm uses a three step process.  First, use

    vrename --list  [<file.v>...]  [<directory>....]

This reads the specified files, or all files below the specified directory,
and creates a signals.vrename file.

Now, edit the signals.vrename file manually to specify the new signal
names.  Then, use

    vrename --change [<file.v>...]  [<directory>....]

=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.vrename 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 --changefile {file}

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

=item --changelang

Include in the signals.vrename file the template needed to change the
language standard for the file.  For the first run, use "--list
--changelang" and --language to specify the file's original language, then
rerun with the "--change" option.  The files will get escaped identifiers
for the most recent Verilog standard.  For example with --language
1364-2005, "do" will become "\do ".

=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, or use the --cryptall flag.  If a signal should not be
encrypted, it can simply be set in the signals.vrename list to be changed
to itself.  After encrypting, you may want to save the signals.vrename file
so you have a key for decoding, and also so that it may be used for the
next encryption run.  When used in this way for the next encryption run,
only new signals will get new encryptions, all other encryptions will be
encrypted the same.

=item --cryptall

As with --crypt, but put cryptic names into signals.vrename even if the
file does not include ENCRYPT_ME.  Generally you will then need to edit the
signals.vrename file manually to exclude any top level signals that should
be preserved.

=item --keywords

Include keywords in the renaming list.  Default is to ignore keywords, as
changing a keyword will probably result in unrunnable code, however,
occasionally it may be necessary to rename signals which happen to match
the name of keywords recently added to the language (such as 'bit').

=item --language <1364-1995|1364-2001|1364-2005|1800-2005|1800-2009|1800-2012>

Set the language standard for the files.  This determines which tokens are
signals versus keywords, such as the ever-common "do" (data-out signal,
versus a do-while loop keyword).

=item --list

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

=item --nowrite

Don't write the actual changes, just report the files that would be changed.

=item --o {dir}

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

=item --read

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

=item --xref

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

=back

=head1 DISTRIBUTION

Verilog-Perl is part of the L<http://www.veripool.org/> free Verilog EDA
software tool suite.  The latest version is available from CPAN and from
L<http://www.veripool.org/verilog-perl>.

Copyright 2000-2014 by Wilson Snyder.  This package is free software; you
can redistribute it and/or modify it under the terms of either the GNU
Lesser General Public License Version 3 or the Perl Artistic License Version 2.0.

=head1 AUTHORS

Wilson Snyder <wsnyder@wsnyder.org>

=head1 SEE ALSO

L<Verilog-Perl>, L<Verilog::Parser>

=cut
