Verilog_perl是Perl解析和处理Verilog文件的工具。
详细细节见:https://www.veripool.org/verilog-perl
官网的示例晦涩难懂,这里主要介绍典型的使用方式。
下面是一个verilog代码的示例,是后面的测试需要的verilog代码源文件。
module test#(
parameter END_OF_LIST = 0
)(
input i_clk ,
input i_rest_n ,
output reg[3:0]sum ,
input [2:0] a,b
);
logic [2:0] a_ff,b_ff;
always@(posedge i_clk)begin
if(i_rest_n == 1'b0)begin
sum <= '0 ;
end
else begin
sum <= a_ff+b_ff;
end
end
sync_cell U_A_SYNC_CELL[2:0](
.clk ( i_clk ) ,
.rst_n ( i_rest_n ) ,
.in ( a ) ,
.out ( a_ff )
);
sync_cell U_B_SYNC_CELL[2:0](
.clk ( i_clk ) ,
.rst_n ( i_rest_n ) ,
.in ( b ) ,
.out ( b_ff )
);
endmodule
一、Verilog::Getopt
提供了标准化的选项处理方式,类似于 Verilog/VCS 和 cc/GCC。
use Verilog::Getopt ;
my $opt= new Verilog::Getopt ;
my @Opts = $opt->parameter("+incdri+verilog","-F","test.lst","+define+MEM_CTRL_BUS_WIDTH=96") ;
for my $i(@Opts){
print($i);
}
如上的例子,Getopt模块会帮助解析输入的参数,值得注意的是,不同的参数的影响是不同的。
例如+incdir+定义的目录可以存储在内部的变量中,用于后面解析文件确定查找文件目录,-F则会被当做filelist解析,然后递归返回filelist中的文件,文件列表作为返回值返回,上面的例子中会存储与$Opts中,还有一些Getopt不识别的参数,也会被原样的座位返回值返回存储与$Opts中。
二、Verilog::Parser
package MyParser;
use Verilog::Parser;
@ISA = qw(Verilog::Parser);
# parse, parse_file, etc are inherited from Verilog::Parser
sub new {
my $class = shift;
#print "Class $class\n";
my $self = $class->SUPER::new();
bless $self, $class;
return $self;
}
sub symbol {
my $self = shift;
my $token = shift;
$self->{symbols}{$token}++;
}
sub report {
my $self = shift;
foreach my $sym (sort keys %{$self->{symbols}}) {
printf "Symbol %-30s occurs %4d times\n",
$sym, $self->{symbols}{$sym};
}
}
package main;
my $parser = MyParser->new();
$parser->parse_file("test.v");
$parser->report();
上面是官网的例子,在文件中检索非关键字的单词,并统计每个词出现的次数,结果如下所示。
Verilog::Parser中有个方法,lineno()可以帮助我们定位具体元素的位置,例如每当遇到symbols,打印出具体的行号,如下面的示例:
package MyParser;
use Verilog::Parser;
@ISA = qw(Verilog::Parser);
# parse, parse_file, etc are inherited from Verilog::Parser
sub new {
my $class = shift;
#print "Class $class\n";
my $self = $class->SUPER::new();
bless $self, $class;
return $self;
}
sub symbol {
my $self = shift;
my $token = shift;
$self->{symbols}{$token}++;
printf("%-14s occurs at line:%d",$token,$self->lineno()) ;
print("\n") ;
}
package main;
my $parser = MyParser->new();
$parser->parse_file("test.v");
运行后输出结果如下所示:
三、Verilog::SigParser
当Verilog中具体类型匹配时,会调用对应的回调函数。
$\="\n" ;
package Myparser ;
use Verilog::SigParser ;
@ISA=qw(Verilog::SigParser) ;
sub new{
my $class=shift ;
my $self=$class->SUPER::new() ;
bless $self,$class ;
return $self ;
}
sub port{
my $self=shift ;
my ($name, $objof, $direction, $data_type, $array, $pinnum)=@_ ;
print($name.":".$direction) ;
}
package main;
my $parse=Myparser->new();
$parse->parse_file("test.v") ;
这个例子是解析代码中的port信息,然后放回port名和对应的端口方向。
运行上面的程序输出:
同样的,如果想获得instant信息,程序如下:
$\="\n" ;
package Myparser ;
use Verilog::SigParser ;
@ISA=qw(Verilog::SigParser) ;
sub new{
my $class=shift ;
my $self=$class->SUPER::new() ;
bless $self,$class ;
return $self ;
}
sub instant{
my $self=shift ;
print(shift,shift,shift) ;
}
package main;
my $parse=Myparser->new();
$parse->parse_file("test.v") ;
运行后获取如下输出:
四、Verilog::Preproc
SigParser无法处理含有宏的或者预处理命令(如`ifdef ... `else ... `endif)的代码,将源文件通过Preproc处理后在传递给SigParser处理就可以正确的处理了。
在下面的例子中,假设要处理的verilog源文件如下所示:
module test#(
parameter END_OF_LIST=0
)(
input logic i_clk ,
input logic i_rest_n ,
output logic [`WIDTH:0] sum ,
input [`WIDTH-1:0] a,b
);
logic [`WIDTH-1:0] a_ff,b_ff;
`ifdef REGOUT
always@(posedge i_clk)begin
if(i_rest_n == 1'b0)begin
sum <= '0 ;
end
else begin
sum <= a_ff+b_ff;
end
end
`else
always@*begin
sum = a_ff + b_ff ;
end
`endif
sync_cell U_A_SYNC_CELL[2:0](
.clk ( i_clk ) ,
.rst_n ( i_rest_n ) ,
.in ( a ) ,
.out ( a_ff )
);
sync_cell U_B_SYNC_CELL[2:0](
.clk ( i_clk ) ,
.rst_n ( i_rest_n ) ,
.in ( b ) ,
.out ( b_ff )
);
endmodule
如果直接用SigParser解析会报如下错误:
下面的例子,相关的参数(如宏的定义,include文件的查找目录等)通过Getopt模块处理,传递给Preproc,Preproc模块对源代码处理后再通过SigParser处理就可以了。
先通过下面的程序看一下Preproc处理后的文件是什么样的。
use Verilog::Preproc ;
use Verilog::Getopt ;
my $opt=new Verilog::Getopt ;
$opt->parameter(qw/+define+WIDTH=3/) ;
my $vp=Verilog::Preproc-> new(options=>$opt,keep_whitespace=>0,line_directives=>0) ;
$vp->open("test.v") ;
while(my $line=$vp->getline()){
print($line) ;
}
运行后结果如下所示,可以看到`WIDTH被替换为定义的3了,由于REGOUT没有定义,所以`ifdef中的内容也不在了。
下面是一个完整的解析代码的例子:
$\="\n" ;
package Myparser ;
use Verilog::SigParser ;
@ISA=qw(Verilog::SigParser) ;
sub new{
my $class=shift ;
my $self=$class->SUPER::new() ;
bless $self,$class ;
return $self ;
}
sub instant{
my $self=shift ;
print(shift,shift,shift) ;
}
package main;
use Verilog::Getopt ;
use Verilog::Preproc ;
my $opt=new Verilog::Getopt ;
$opt->parameter(@ARGV);
my $parse=Myparser->new();
my $vp=Verilog::PReproc->new(optinons=>$opt);
$vp->open("test.v");
while(my $line=$vp->getline()){
$parser->parse($line);
}
$parser->eof ;
上面的例子,Preproc根据输入的参数对源文件进行处理,然后将处理后的文件传递给SigParser,当传递完所有字符后,使用$parser->eof对字符进行解析。
五、Verilog::Netlist
Verilog::Netlist 用于读取和存储整个设计数据库中的互连信息。
该模块可以解析Verilog代码,获取模块的例化与连接关系。
use Verilog::Netlist;
# Setup options so files can be found
use Verilog::Getopt;
my $opt = new Verilog::Getopt;
$opt->parameter( "+incdir+verilog",
"-y","verilog",
"-f","test.lst"
);
# Prepare netlist
my $nl = new Verilog::Netlist(options => $opt,link_read_nonfatal=>1);
$nl->read_file(filename=>"test.v");
my $top=$nl->find_module("test");
$top->is_top(1);
# Read in any sub-modules
$nl->link();
#$nl->lint(); # Optional, see docs; probably not wanted
$nl->exit_if_error();
foreach my $mod ($nl->top_modules_sorted) {
show_hier($mod, " ", "", "");
}
sub show_hier {
my $mod = shift;
my $indent = shift;
my $hier = shift;
my $cellname = shift;
if (!$cellname) {$hier = $mod->name;} #top modules get the design name
else {$hier .= ".$cellname";} #append the cellname
printf("%-45s %s\n", $indent."Module ".$mod->name,$hier);
foreach my $sig ($mod->ports_sorted) {
printf($indent." %sput %s\n", $sig->direction, $sig->name);
}
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->netname);
}
show_hier($cell->submod, $indent." ", $hier, $cell->name) if $cell->submod;
}
}
上面的例子递归打印了模块的例化关系,以及模块的端口信息。
顶层的模块需要通过read_file读取的,如果下层有例化的模块,只需要加在filelist(上例中为test.lst)中即可。