# See copyright, etc in below POD section.
######################################################################
package Verilog::EditFiles;
use Config;
use IO::File;
use File::Path;
use Carp;
use strict;
use vars qw ($VERSION $Debug);
######################################################################
#### Configuration Section
$VERSION = '3.423';
#######################################################################
# CONSTRUCTORS
sub new {
my $class = shift;
my $self = {
# Options
program => "Verilog::EditFiles",
outdir => ".",
translate_synthesis => 0, # Name of define or "1"
lint_header => undef,
celldefine => undef,
timescale_header => undef,
timescale_removal => undef,
lint_command => 'vlint --brief',
v_suffix => ".v",
verbose => 1,
# Internals
_files => {}, # Hash of module name, contains list of lines
@_,
};
$self->{verbose} = 1 if $Debug;
$self->{debug} = 1 if $Debug;
bless $self, $class;
return $self;
}
######################################################################
sub read_and_split {
my $self = shift;
foreach my $filename (@_) {
$self->_read_split_file($filename);
}
}
sub _read_split_file {
my $self = shift;
my $filename = shift;
print "Reading $filename...\n" if $self->{verbose};
my $fh = IO::File->new("<$filename") or die "%Error: $! $filename\n";
(my $basename = $filename) =~ s!^.*/!!;
(my $basemod = $basename) =~ s!\.(v|inc)$!!;
my @header = "// Created by $self->{program} from $basename\n";
my @trailer = "\n";
my @lines = (@header);
my $modname;
my $ever_module;
my $commented;
while (defined(my $line = $fh->getline)) {
$line =~ s!\r!!mg;
$line =~ s![ \t]+\n$!\n!;
if ($self->{translate_synthesis}) {
my $define = $self->{translate_synthesis};
$define = "SYNTHESIS" if $define eq "1";
$line =~ s!^\s*//\s*(ambit|synopsys|synthesis)\s*translate_off\s*$!`ifndef ${define}\n!;
$line =~ s!^\s*//\s*(ambit|synopsys|synthesis)\s*translate_on\s*$!`endif //${define}\n!;
if ($line =~ m!(ambit|synopsys|synthesis)\s*translate!) {
die "%Error: Unhandled translate comment: $line\n";
}
}
while ($line =~ m!.*?(/\*|//|\*/)!g) {
if (!$commented && $1 eq '//') {
last;
} elsif (!$commented && $1 eq '/*') {
$commented = 1;
} elsif ($commented && $1 eq '*/') {
$commented = 0;
}
}
if (!$commented
&& $line =~ /^\s*(module|primitive)\s+([A-Za-z0-9_]+)/) {
my $newmodname = $2;
if ($modname) { # Already in a module
# Support code like this
# `ifdef x
# module x (...)
# `else
# module x (...)
($newmodname eq $modname)
or die "%Error: $filename:$.: module without previous endmodule\n";
print "$basename:$.: continue module $1\n" if $self->{debug};
} else {
$modname = $newmodname;
$ever_module = 1;
print "$basename:$.: module $1\n" if $self->{debug};
my @afterif;
my @oldlines = (@lines);
@lines = (@header);
# Insert our new header before any `ifdef's or `includes
my $gotifdef;
foreach my $oline (@oldlines) {
$gotifdef = 1 if $oline =~ /`ifdef\b|`include\b/;
if (!$gotifdef) {
push @lines, $oline;
} else{
push @afterif, $oline;
}
}
push @lines, $self->{include_header} if $self->{include_header};
push @lines, $self->{timescale_header} if $self->{timescale_header};
push @lines, "`celldefine\n" if $self->{celldefine};
push @lines, $self->{lint_header} if $self->{lint_header};
push @lines, @afterif;
}
push @lines, $line;
}
elsif (!$commented && $line =~ /^\s*end(module|primitive)\b/) {
print "$basename:$.: endmodule $modname\n" if $self->{debug};
$modname or die "%Error: $filename:$.: endmodule without previous module\n";
push @lines, $line;
push @lines, "`endcelldefine\n" if $self->{celldefine};
push @lines, @trailer;
$self->{_files}{$modname}{created} = 1;
$self->{_files}{$modname}{modname} = $modname;
$self->{_files}{$modname}{lines} = [@lines];
@lines = ();
# Prep for next
$modname = undef;
}
elsif (!$commented
&& $line =~ /^\s*\`timescale\s.*/
&& $self->{timescale_removal}) {
# Strip existing timescale
}
elsif (!$commented
&& $line =~ /^\s*\`(end)?celldefine\b/
&& $self->{celldefine}) {
# Strip existing celldefine, we'll add a new one
}
else {
push @lines, $line;
}
}
$fh->close;
if (!$ever_module) {
print "$basename:1: No module, must be include file: $basemod\n" if $self->{debug};
push @lines, @trailer;
$self->{_files}{$basemod}{created} = 1;
$self->{_files}{$basemod}{modname} = $basemod;
$self->{_files}{$basemod}{lines} = [@lines];
$self->{_files}{$basemod}{is_include} = 1;
}
}
#######################################################################
sub write_files {
my $self = shift;
mkpath($self->{outdir});
foreach my $file (sort (keys %{$self->{_files}})) {
my $fileref = $self->{_files}{$file};
next if !$fileref->{created};
$self->_write_file ($self->{outdir}."/".$fileref->{modname}.$self->{v_suffix}, $fileref);
}
}
sub _write_file {
my $self = shift;
my $filename = shift;
my $fileref = shift;
print "Writing $filename...\n" if $self->{verbose};
my $fh = IO::File->new(">$filename") or die "%Error: $! $filename\n";
foreach my $line (@{$fileref->{lines}}) {
print $fh $line;
}
$fh->close;
}
sub write_lint {
my $self = shift;
my %params = (filename => $self->{outdir}."/0LINT.sh",
@_);
print "Writing $params{filename}...\n" if $self->{verbose};
my $fh = IO::File->new(">$params{filename}") or die "%Error: $! $params{filename}\n";
print $fh "#!/bin/bash\n";
print $fh "# Created by $self->{program}\n";
foreach my $fileref (sort {$a->{modname} cmp $b->{modname}} values %{$self->{_files}}) {
next if $fileref->{is_include};
next if $fileref->{skip_lint};
print $fh "echo \"".("*"x70),"\"\n";
print $fh "echo Lint ".$fileref->{modname},"\n";
print $fh $self->{lint_command}." \$* ".$fileref->{modname}.$self->{v_suffix},"\n";
}
$fh->close;
chmod 0777, $params{filename};
}
#######################################################################
sub edit_file {
my $self = shift;
my %params = (filename => undef,
write_filename => undef,
cb => sub {},
verbose => $self->{verbose},
@_);
defined $params{filename} or croak "%Error: edit_file not passed filename=>,";
ref $params{cb} or croak "%Error: edit_file cb=> callback is not code,";
$params{write_filename} = $params{filename} if !defined $params{write_filename};
my $wholefile;
my $origwholefile;
{ # Read it
my $fh = IO::File->new ("<$params{filename}")
or croak "%Error: $! $params{filename},";
local $/; undef $/;
$wholefile = <$fh>;
$origwholefile = $wholefile;
$fh->close();
}
# Edit
$wholefile = &{$params{cb}}($wholefile);
# Writeback
if ($wholefile ne $origwholefile) {
print " $params{write_filename} (Changed)\n" if $params{verbose};
my ($dev,$ino,$mode) = stat($params{write_filename});
chmod 0777, $params{filename};
my $fh = IO::File->new (">$params{write_filename}")
or croak "%Error: $! writing $params{write_filename},";
print $fh $wholefile;
$fh->close();
chmod $mode, $params{write_filename} if $mode; # Preserve mode
}
}
#######################################################################
1;
__END__
=pod
=head1 NAME
Verilog::EditFiles - Split Verilog modules into separate files.
=head1 SYNOPSIS
See splitmodule command.
use Verilog::EditFiles;
my $split = Verilog::EditFiles->new
(outdir => "processed_rtl",
translate_synthesis => 0,
lint_header => undef,
celldefine => 1,
);
$split->read_and_split(glob("inbound_rtl/*.v"));
$split->write_files();
$split->edit_file(filename=>"foo", cb => sub { return $_[0]; });
=head1 DESCRIPTION
Verilog::EditFiles provides a easy way to split library Verilog files that
contain multiple modules into many files with one module per file.
=head1 FUNCTIONS
=over 4
=item new (...)
Create a new Verilog::EditFiles object. Named parameters may be specified:
=over 4
=item celldefine
If true, add "`celldefine" before every module statement.
=item lint_command
For the write_lint method, the name of the linter to use. Defaults to
"vlint --brief".
=item lint_header
If defined, add the provided text before every module statement. Generally
used to insert lint off pragmas.
=item outdir
Name of the directory to write the output modules to. Defaults to ".".
=item program
Name of the program to add to comments. Defaults to
"Verilog::EditFiles".
=item timescale_header
If defined, add the provided text before every module statement. Generally
set to the next needed to #include a timescale file. Use with
timescale_removal.
=item timescale_removal
If set, remove any `timescales.
=item translate_synthesis
If 1, replace any synopsys translate on/offs with "`ifdef SYNTHESIS" and
"`endif"s. If set to a string, use that string instead of "SYNTHESIS".
=item v_suffix
The suffix to add to convert a module name into a filename. Defaults to
".v".
=item verbose
If true, print what files are being read and written.
=back
=item $self->read_and_split ([filenames])
Read from the specified filenames.
If there is no module statement in the file, assume it is a include file,
and when write_files is called, place all of the file contents into the
output. If there is a module statement, when write_files is called place
all following output into a file named based on the module, with .v added.
=item $self->write_files()
Write all of the files created by read_and_split to the outdir.
=item $self->write_lint([filename=>...])
Create a shell script that will lint every file created by write_files. If
a "filename" parameter is not provided, "0LINT.sh" will be written in the
default outdir.
=item $self->edit_file(filename=>..., cb=>sub{...})
Read a file, edit it with the provided callback, and save it if it has
changed. The "filename" parameter is the filename to read. The
"write_filename" parameter is the filename to write, defaulting to the same
name as the filename to read. The "cb" parameter is a reference to a
callback which takes the string of file contents and returns the string to
write back. Often the callback will simply perform a search and replace.
=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 2006-2017 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>
=cut
######################################################################