#!/usr/local/bin/perl -w
#
# Copyright 1998, 1999, 2000, 2001, 2002 Providenza & Boekelheide, Inc
# Feel free to use this, let me know of any bugs or enhancements
# John Providenza
#
#
# Create a Vi style tags file for Verilog files
# Scan Verilog files to extract function/task/module keywords
#
# 4-1-99 added support for -y flag to specify library directories
# it can be used on the command line ("-y /proj/lib")
# or in the file_of_files
#
# 11-1-00 fixed Function detection to support function size info, ie
# function [10:0] foo ;
#
# 6-1-01 changed from hashes to an array to store the module info.
# this allows us to remember identical tag names that could
# be in different files
#
# 7-18-01 fixed 2 problems with functions.
# 1) a scalar declaration (function foo;) failed
# 2) functions burried in funny comments failed
#
# 9-25-01 1) added scanning of +incdir+ directories specified in a -f file.
# 2) deleted duplicate filenames from processing
#
# 10-4-01 process `include directive
#
# 10-16-01 1) cleanup on 'function' scanning
# 2) for `include, auto-add directory that calling file is in
#
# 2-22-02 1) mods to add the basename of `include files as a tag so that
# simple includes files with only parameters can be pushed in to.
#
# 3-29-02 Yet another fix to function finding
#
# Note that the "file_of_file_names" may also include lines like
# -y /proj/lib_dir
# +incdir+foo/bar
# This can be used to suck in all verilog files from a libray directory
$ARGV[0] || die "usage:\n vtags.pl [-f file_of_file_names] [-y lib_dir] [file] ...\n" ;
$file_cnt = 0 ;
my @inc_dirs = ();
my @inc_files = ();
my @missing_includes = ();
my $file_types = "[vV][tT]*"; # allow either .v or .vt files
sub read_lib_dir {
my $lib_dir_name = shift ;
my $type = shift;
print "including $type directory $lib_dir_name\n" ;
opendir (LIBDIR, $lib_dir_name) || die "can't open $type $lib_dir_name\n" ;
my @lib_files = readdir(LIBDIR) ;
foreach $lib_file (@lib_files) {
push(@files, "$lib_dir_name/$lib_file") if ($lib_file =~ /\.v$/) ;
}
closedir (LIBDIR) ;
}
# look for the include file in our path....
# this includes our current directory as well as the include paths
# called with the include file name to look for as well as the calling
# file path
sub do_include {
my $ifile = shift;
my $call_file = shift;
my $call_dir = $call_file;
$call_dir =~ s,/\w[\w.]+$,, ;
my $found = "";
foreach my $dir (($call_dir, @inc_dirs)) {
if (-r "$dir/$ifile") {
$found = "$dir/$ifile";
last;
}
}
if ($found ne "") {
# print "do_include: adding $found\n";
push (@inc_files, $found);
# also add the basename of the file as a tag JIC the include
# file doesn't have any modules
$ifile =~ s/\.\S+$//;
push (@tags, "$ifile\t$found\t1");
} else {
push (@missing_includes, $ifile);
}
}
# check a line for interesting things to process:
# function, task, module, `include
sub proc_line {
# get the input line and line number
$_ = shift;
my $line_num = shift;
if (/^\s*`include\s+"(\S+)"/) {
# look for the include file in our path....
&do_include ($1, $file);
} else {
# remove strings
s,\\.,,g; # no \ style escapes...
s,"[^"]*",,g; # remove matched "
s,'[^']*',,g; # remove matched '
# check for keywords, remember them
if (/\b(module|task)\s+([a-zA-Z]\w+)/) {
# print "Got $1 $2\n";
push (@tags, "$2\t$file\t$line_num");
} elsif (/\bfunction(\s*\[\s*\d+\s*:\s*\d+\s*]\s*|\s+real\s+|\s+integer\s+|\s+)([a-zA-Z]\w*)/) {
#print "Got function $2\n";
push (@tags, "$2\t$file\t$line_num");
}
}
}
# read all the files scanning for module/task/function keywords
sub scan_files {
my (@files) = @_;
$last_file = "";
foreach $file (sort(@files)) {
# skip duplicate file names
next if ($last_file eq $file);
$last_file = $file;
open (INPUT, $file) || (warn "can't open verilog file $file\n", next) ;
$file_cnt++ ;
$in_comment = 0 ;
while () {
# Scan each line for keywords while keeping track of comments
# This can be written much more concisely, but this tries to
# minimize the code for each common case
if (!$in_comment) {
# not currently in a block comment
if (m,/[/*],) {
# some kind of start of comment
s,//.*$|/\*.*\*/,, ; # strip //.... and /*....*/ comments
$in_comment = s,/\*.*$,, ; # remove /*...$ comment text
}
&proc_line ($_, $.) if (/include|module|task|function/);
} else {
# we're in a comment already
if ( !($in_comment = !s,^.*\*/,,) ) {
s,//.*$|/\*.*\*/,, ; # strip //.... and /*....*/ comments
$in_comment = s,/\*.*$,, ; # remove /*...$ comment text
&proc_line ($_, $.) if (/include|module|task|function/);
}
}
}
close (INPUT) ;
}
}
# expand command line to remove '-f' flags
while ($n_arg = shift) {
if ($n_arg eq "-f") {
# process the list of files
$n_arg = shift ;
open(FILES, $n_arg) || die "can't open cmd line file $n_arg\n" ;
while () {
chomp ;
if (/^-y\s+(.*$)/) {
# we got a -y argument specifying a library directory.
read_lib_dir ($1, "lib_dir") ;
} elsif (/\+incdir\+(\S+)/) {
push (@inc_dirs, $1) ;
} else {
s/\s*$// ; # remove trailing blanks
s/^.* // ; # remove any leading -flags
/\.$file_types\s*$/ && push(@files, $_) ; # save only .v files
}
}
} elsif ($n_arg eq "-y") {
# we got a -y argument specifying a library directory.
read_lib_dir (shift, "lib_dir") ;
} elsif ($n_arg =~ /\.$file_types\s*$/ ) {
push (@files, $n_arg) ; # save .v files from cmd line
}
}
# print "all Verilog input files are: @files\n" ;
# scan all the specified input files for modules, tasks, functions, `includes
# when done, we'll have added tags and created two arrays for `includes:
# one if for includes in the same directory as the calling file or in the
# include path, the other for missing includes
&scan_files (@files) ;
print "looking for missing include files...\n";
# these were not found in the include_path or in the directory of the
# calling file
foreach $file (@files) {
$file =~ s,/[^/]+$,,; # remove trailing file name to get path
push (@dirs, $file);
}
foreach $missing (@missing_includes) {
$last_dir = "";
my $found = 0;
foreach $dir (sort(@dirs)) {
if ($last_dir ne $dir) {
my $target = "$dir/$missing";
if (-f $target) {
print "adding include file $missing from $dir\n";
push(@inc_files, $target);
$found = 1;
# also add the basename of the file as a tag JIC the include
# file doesn't have any modules
my $base_name = $missing;
$base_name =~ s/\.\S+$//;
push (@tags, "$base_name\t$target\t1");
}
}
$last_dir = $dir;
}
if ($found == 0) {
print "NOTE: couldn't find include file $missing\n";
}
}
# now process the include files
&scan_files (@inc_files) ;
# send out the tags file
open(TAGS, ">tags") || die "can't open tags file\n" ;
my $tag_cnt = 0;
my $last_tag = "";
foreach $i (sort (@tags) ) {
print TAGS "$i\n" if ($i ne $last_tag);
$last_tag = $i;
$tag_cnt++;
}
print "$file_cnt files scanned, $tag_cnt tags found\n" ;