I’ve come across numerous times where I needed to read in a Verilog file, and extract some features from it. For example, once I read in the top-level digital module and generated glitch checkers modules for each output port. Another time, I simply wanted to lint-check a generated module for obvious errors.

The current problem is to perform a diff between two versions of an extracted top-level netlist. This netlist is generated by Cadence from schematics, and thus we have very little control over how the ports are ordered and the order of the wire declarations. This makes it difficult to easily see differences between runs.

In comes Verilog-Perl. This Perl module, written by Wilson Snyder of Verilator fame, adds support for parsing Verilog files and generating a Perl-representation of the netlist. It’s exactly what we need.

After installing Verilog-Perl and using the module, the first step is to create a Verilog::Getopt object to hold the Verilog parameters. Specifically, we want to pass in the same options we pass to a normal simulator, such as +incdir+, -y, -v, and even +define+ options.

use Verilog::Getopt;
my $opt = new Verilog::Getopt;
$opt->parameter(
  "+incdir+rtl/inc",
  "-y", "rtl",
  "+define+EN_I2C",
);

Note that the Verilog::Getopt->parameter(\@params) subroutine will extract any recognized parameters from the referenced array, and return the rest in an array. With this, we could pass in the options through the command line, and keep everything else in @ARGV for later processing.

use Verilog::Getopt;
my $opt = new Verilog::Getopt;
@ARGV = $opt->parameter(@ARGV);

Next, we need to prepare the netlist by creating a Verilog::Netlist object. This object takes in an options parameter, which is simply a pointer to the Verilog::Getopt object created earlier. It can also take in various parameters to change how the netlister behaves.

use Verilog::Netlist;
my $nl = new Verilog::Netlist(
  options => $opt,
);

The next step is to read in the files using Verilog::Netlist->read_file().

foreach my $file (qw(file1.v file2.v)) {
  $nl->read_file(filename => $file);
}

Since we extracted all of the Verilog::Getopt options in the command line earlier, we can pass the file names through @ARGV instead of hard-coding them.

foreach my $file (@ARGV) {
  $nl->read_file(filename => $file);
}

Finally, we can link the Verilog::Netlist to resolve the references between the different modules.

$nl->link();

This could be the first place that trouble occurs – if a module is not already read by read_file, the linker will throw an error and fail. There are two ways to fix this:

  1. Set the link_read parameter to Verilog::Netlist->new to have the parser search for the files using the options in Verilog::Getopt.
  2. Set the link_read_nonfatal parameter to Verilog::Netlist->new to have the linker ignore undefined modules.

For this problem, I’m going to set link_read_nonfatal to ignore the errors and only parse the files that I explicitly pass in.

At this point, I have read in all files passed into my script into the Verilog::Netlist object, and can now parse through the modules to extract some information from them that would be useful for comparing.

The properties I chose to display are for each module include, the following. Luckily, each of these can be extracted by a Verilog::Netlist subroutine. I chose the “sorted” versions to solve the original diff problem.

Type Subroutine
Ports Verilog::Netlist::ports_sorted
Nets Verilog::Netlist::nets_sorted
Cells Verilog::Netlist::cells_sorted

This obviously points to using recursion to walk through each module in the design, with submodules located under the “Cells”.

Note: this is taken in large from the Verilog::Netlist example, but modified to add additional information.

Update: If a port of an instantiation is no connected, the original code will fail when trying to access $pin->net->name. The fix is to check if $pin->net is defined first before getting the name.

foreach my $mod ($nl->top_modules_sorted) {
  show_hier($mod, "", "", "");
}

sub show_hier {
  my ($mod, $indent, $hier, $cellname) = @_;

  if (!$cellname) {
    $hier = $mod->name;
  } else {
    $hier .= ".$cellname";
  }
  printf("\n");
  printf($indent . "%s [%s]\n", "Module: " . $mod->name, $hier);

  printf($indent . "  PORTS:\n");
  foreach my $port ($mod->ports_sorted) {
    my $dir =
      $port->direction eq "in"  ? "input" :
      $port->direction eq "out" ? "output" : $port->direction;
    printf($indent . "      %-6s %s\n", $dir, $port->name);
  }

  printf($indent . "  NETS:\n");
  foreach my $net ($mod->nets_sorted) {
    printf($indent . "      %s (%s)\n", $net->name, $net->decl_type . " " .$net->data_type);
  }

  printf($indent . "  CELLS:\n");
  foreach my $cell ($mod->cells_sorted) {
    printf($indent . "      Cell: %s\n", $cell->name);
    foreach my $pin ($cell->pins_sorted) {
      printf($indent . "        .%s (%s)\n", $pin->name, ($pin->net ? $pin->net->name : ""); # Updated 2022-05-10
    }
  }
  foreach my $cell ($mod->cells_sorted) {
    if ($cell->submod) {
      show_hier($cell->submod, $indent . "    ", $hier, $cell->name);
    }
  }
}

The full script can be found on GitHub.