Study UVM Source Code with Sublime3


UVM和Sublime3

学习UVM的很好的一个方法是读源码,去网上下载UVM 源码,然后就可以了。 很多主流的编辑器,对systemverilog的支持都很一般,我这里推荐使用 sublime3。 但是首先需要安装一个插件管理器 packagecontrol 。 之后,ctrol+shift+p,选择命令PackageControl:install package,搜索关键字systemverilog,安装好后,就大功告成了。 打开sublime3,然后在菜单栏里选择File->open folder,打开UVM的源码目录,好了开工。

<img src="../../images/uvmdriver.png" class="img-thumbnail" width="60%" >

比如我看到下面这行代码,想看看uvm component的定义,那就在uvm component的位置上按F12。

class uvm_driver #(type REQ=uvm_sequence_item,
		     type RSP=REQ) extends uvm_component;

我们就能找到它的位置了,如下图所示。

<img src="../../images/uvmcomp.png" class="img-thumbnail" width="60%" >

Edaplayground Introduction


免费的仿真器

如果你能访问google的话,大概也就能访问edaplayground 。很多的情况下,我想试写一些验证代码,但是个人PC上没有仿真器,那么edaplayground就派上用场了。最好是登录使用,这样可以保存代码了,以便将来再调试。当然也可以使用google账户或者facebook账户。

自己编辑过的代码,可以share给别人来看。 my simple test

<img src="../../images/edaplayground.png" class="img-thumbnail" width="60%" >

可以看到,左边的选项里,我们可以选择语言、UVM/OVM库、仿真器种类,基本满足日常的简单环境和case的调试。还有很多很多代码例子,我们不用从头去搭建。

<img src="../../images/edaplaygroundexample.png" class="img-thumbnail" width="60%" >

我们选择一个example,然后点击左上角的run,是不是很容易啊?

<img src="../../images/edaplaygroundrun.png" class="img-thumbnail" width="60%" >

另外,推荐一个使用edaplayground来学习UVM的网站,这个 真的是很详细,很容易上手!

XX Net Introduction


能上google非常重要

比如我们分别在baidu和goolge上搜索systemverilog。 百度的结果:

<img src="../../images/baidusv.png" class="img-thumbnail" width="60%" >

google的结果:

<img src="../../images/googlesv.png" class="img-thumbnail" width="60%" >

可以看出来,虽然都能搜到IC领域的systemverilog的介绍,但是baidu的结果一般是别人的一些经验总结,比较碎片化,主要是中文内容,初级的解释为主。对比来看,google的结果是,wiki上的详细介绍和语言标准的权威网站链接。当我们不再是systemverilog的初学者,显然google的结果对我们IC从业者的帮助更大。

通过xx-net上google

有很多商业化的VPN工具可以做到这一点,这里我不做介绍。我只来提一下一个免费版本,那就是xx-net。xx-net是一个开源的项目,其代码托管在github上,这个是连接 。上面有详细的介绍,和使用配置说明,提醒一句,使用xx-net的时候,最好不要挂QQ以及使用网银,安全考虑。 顺利的话,就可以访问真正的互联网了。

Perl Overview


Perl coding tips

打开pipe或者访问文件的时候,最好使用die

open FILE, "a_file" or die "Can not open file a_file: $!";
close FILE;

比die更好用的是croak

use Carp;
a("a_file"); #resume that a_file does exist
a("b_file"); #resume that b_file does not exist
sub a {
      my $filename = shift;
      open FILE, "$filename" or croak "Can not open file $filename: $!";
      return 0;
}

用dumper来print复杂的数据结构,便于debug

use Data::Dumper;

$var = {"name" => "hello", "action" => "speak" };
print Dumper($var);

用grep和map可以节省很多代码

my @foos = grep {!/^#/} @bars;    # weed out comments

my %hash = map {  lc($_) => 1  } @array

my @squares = map { $_ * $_ } grep { $_ > 5 } @numbers;

使用qq代替双引号,使用q代替单引号,list赋值使用qw

$a = qq{"}; #{} is used for boundary
$b = qq!"!; #!! is the same

$c = q{'};
@vars = qw{a b c};

使用list的内置函数,不要重新发明轮子

use List::Util qw(max);

my @vars = qw(1 2 3);
my $result = max(@vars);

打印多行的信息

$a = "hello";
print <<ENDOFPRINT;
Dear Lee,
    blablabla...
    $a
ENDOFPRINT

$b = <<ENDOFA;
Dear friend,
   I am here.
ENDOFA

只想做语法检查

perl -cwT test.pl

查看Perl默认的include路径

perl -e "print join(qq{\n}, @INC);"

使用自己的local路径下的库

use lib '/home/xx/eda_scripts/pm';

查看perl的pod格式的说明

perldoc rvp.pm
perldoc perldoc

Perl5.8之后的版本,可以在数据中间加下划线

$a = 111_222;
print "a = ", $a , "\n";

调用其他shell命令,同时获得shell命令的STDERR以及STDOUT信息

$pid = open $readme, "-|", "$cmd 2>&1";
while (<$readme>) {
    push(@out, $_);
}
close $readme;

Perl使用子线程,获得子线程的STDERR信息

pipe(READER, WRITER) or die "pipe no good: $!";
my $pid = fork();
die "Can no fork: $!" unless defined $pid;
if($pid) { #parent process
    close WRITER;
    while(<READER>) {
	push @out, $_;
    }    
}
else { #child process
    close READER;
    open STDERR, ">&WRITER";
    $parser = XML::LibXML->new;
    $parser->validation(1);
    exit 0;
}

多线程

因为fork是复制出一个完全一样的进程,所以“go on”会被print 2 次。

 my $pid = fork();
 if($pid) {
     #parent
     print "in parent\n";
 }
 else {
     #child
     print "in child\n";
 }
 print "go on\n";

child process中途退出了,所以 “go on”只被print 1次

 my $pid = fork();
 if($pid) {
     #parent
     print "in parent\n";
 }
 else {
     #child
     print "in child\n";
     exit;
 }
 print "go on\n";

child process通过exec,替换掉当前process,所以 “never print this”不会被print

  my $pid = fork();

  if($pid) {
      #parent
      print "in parent\n";
  }
  else {
      #child
      print "in child\n";
      exec("ls");
      print "never print this.\n";
  }
  print "go on\n";

eval{}是一种保护性写法。eval的运行结果放在$@里。可以结合alarm handler来完成很多应用

  print STDERR "type your password: ";
  my $password =
  eval {
  local $SIG{ALRM} = sub { die "timeout\n" };
  alarm (5); # five second timeout
  return <STDIN>;
  };
  alarm (0);
  print STDERR "you timed out\n" if $@ =~ /timeout/;

reaper函数,非阻塞式(WNOHANG)的处理所有子进程,$kid等于-1的时候,表示没有需要回收的进程,跳出reaper函数

  use POSIX 'WNOHANG';
  $SIG{CHLD} = \&reaper;
  sub reaper {
      while ((my $kid = waitpid(-1,WNOHANG)) > 0) {
      warn "Reaped child with PID $kid\n";
      }
  }

reference

类似c语言的指针

 @vars = ();
 $vars_ref = \@vars;

 $a_href = {};
 $b_href = { "name" => "b", "action" => "speak", };
 $c_href = {%{$b_href}};


 $tmp = "name";
 print $c_href->{$tmp}, "\n"; #This is called a symbolic reference

安装module

 cpan install Template
 cpan install XML::Rabbit

Perl 在验证中的应用

Perl 参考网站

Fibonacci Introduction


有趣的Fibonacci数列

0,1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946,...

这样的数字序列满足公式Fn = Fn-1 + Fn-2,其中Fn表示第n个数字,这就是大名鼎鼎的Fibonacci数列。 如果把每一个数字的当作正方形的边长,然后把这些正方形堆砌在一起,就可以绘制出美丽的螺旋曲线。

<img src="../../images/fibonacci-spiral.gif" class="img-thumbnail" width="60%" >

如果把这个序列变化成函数,比如系数依然满足Fn(z) = Fn-1(z) + Fn-2(z)。也就是说这样的函数: f(z) = 0 + z + z2 + 2z3 + 3z4 + 5z5 + 8z6 + …

那么我们能知道:

zf(z) = 0 + z2 + z3 + 2z4 + 3z5 + 5z5 + …

z2f(z) = 0 + z3 + z4 + 2z5 + 3z5 + …

由此我们得到关系式:

f(z) = z + zf(z) + z2f(z)

进一步推导,

<img src="../../images/fibonacci.png" class="img-thumbnail" width="60%" >

If Else in Perl


If Else in Perl

if-elsif-else这样的语句在我们的code里再常见不过了。

my $name = "John";

sub name_solute {
    my $item = shift;
    my $ret;
    if ($item eq "Tom") {
	$ret = "Hi Tom.";
    }
    elsif ($item eq "Jerry") {
	$ret = "Run! Run!";
    }
    elsif ($item eq "Lucy") {
	$ret = "Where is Lily?";
    }
    else {
	$ret = "I do not know you.";
    }
    return $ret;
}

my $solute = name_solute($name);

如果我们换一种方式,比如三元式,可能更好看一点。

my $solute = 
    $name eq "Tom"   ?  "Hi Tom."             :
    $name eq "Jerry" ?  "Run! Run!"           :
    $name eq "Lucy"  ?  "Where is Lily?"      :
			"I do not know you."  ;

另外提一种比较好的数据结构,可以用来实现比较复杂的逻辑或者小环境。

my $ref_var = {
     "name"               =>  [[],\&name_proc] ,
     "address"            =>  [[],\&addr_proc] ,
     "mail"               =>  [[],\&mail_proc] 
     };

 push( @{ $ref_var->{name}->[0] }, "John Smith");
 push( @{ $ref_var->{address}->[0] }, "Beijing, China");
 push( @{ $ref_var->{mail}->[0] }, "john\@gmail.com");

 &{ $ref_var->{name}->[1] }( @{ $ref_var->{name}->[0]} );
 &{ $ref_var->{address}->[1] }( @{ $ref_var->{address}->[0]} );
 &{ $ref_var->{mail}->[1] }( @{ $ref_var->{mail}->[0]} );

 sub name_proc {
     my $item = shift;
     print "Hello, $item\n";
 }

 sub addr_proc {
     my $item = shift;
     print "He lives in $item\n";
 }

 sub mail_proc {
     my $item = shift;
     print "You can contact to $item\n";
 }

首先,$ref-var是一个hash的reference ,它的各个key所对应的都是一个list的reference。每一个list里面,又分成一个空的list的reference,用来存input变量,以及一个函数指针。这个结构不错呦,我也是从大神那搬过来的,O(∩_∩)O哈哈~

Makefile Basic


Makefile

使用Makefile管理软件流程,轻松便利,但有一些基本的语法,总是忘记,写此博文,给将来的好忘的我。 直接上干货:

ifndef TEST_MK
  $(error enviornment variable TEST_MK should be set!)
endif

HHH?=1

ifeq ("$(HHH)","1")
  INFO="HHH is 1 by default"
else
  INFO="HHH is set to other value $(HHH)"
endif

compile: gen_test
ifeq ("$HHH","1")
	INFO=$(INFO) perl test.pl
else
	INFO=$(INFO) perl test.pl
endif

gen_test: clean
	@echo "my \$$info = \$$ENV{'INFO'};print \"We get in perl: \$$info\\n\";print \"We can get TEST_MK!\\n\" if (defined \$$ENV{'TEST_MK'});" > test.pl

clean:
	rm -rf test.pl

命令行输入:

setenv TEST_MK hello;make

结果是:

rm -rf test.pl
INFO="HHH is 1 by default" perl test.pl
We get in perl: HHH is 1 by default
We can get TEST_MK!

关键的点有下面几个:

  • 对环境变量的判断,使用ifndef,同时用error来输出错误信息,并停止。
  • 使用“?=”来设置default值,如果想在命令行对变量“HHH”赋值:
make HHH=2
  • 判断变量值用ifeq
  • 最重要的一点,Makefile和Perl天生八字不合,好好的“$”,在Makefile里要写成"\$$",我也是醉了。
  • 一般的情况下,执行的命令都会显示出来,除非在前面加上“@”。
  • “INFO=$(INFO) perl test.pl”表示的是,把环境变量INFO传到perl的命令里去。

Code Generation


Code Generation

生成代码或者小环境,我比较喜欢的方法,我总结如下。

  • 用JSON格式文件控制输入变量
  • 使用Template Toolkit,控制展示出来的代码样式

闲言少叙,我们举一个例子。 假设一个场景,我们需要根据很多简单的逻辑表达式,来生成一个verilog的module。

我们把这个任务划分成几部分考虑。

  • 逻辑表达式需要怎么样的格式呢?
  • 需要哪些变量控制我们最后生成的module的样式?
  • 用怎么样的方式,把这个流程串起来,同时能便于以后的维护呢?逻辑表达式的变化,要可以重新生成代码,同时不需要手动去修改。

第一个问题,逻辑表达式需要怎么样的格式?因为我对Emacs比较情有独钟,恰好我觉得使用org-mode 来书写简单的组合逻辑特别合适。先看一下下面的贴图感受一下。

<img src="../../images/org.png" class="img-thumbnail" width="60%" >

上图是Emacs编辑器显示出来的,其实里面纯文本,是这样的:

<img src="../../images/org_text.png" class="img-thumbnail" width="60%" >

处理这个不是很复杂,可以看作对树形节点的处理。

第二个问题,需要哪些变量控制我们最后生成的module的样式?我想到的有这么几个变量,我用JSON格式写出来:

{
    "prefix_name":"demo",
    "org_file":"doc/demo.org",
    "outputs":"ap_disp_light_sleep_req,ap_light_sleep_req"
}

可能你会说,其实可以通过命令行的option来把变量加进去一下,比如:

run.pl -prefix_name demo -org_file doc/demo.org -outputs ap_disp_light_sleep_req,ap_light_sleep_req

确实可以,但是这样我们的脚本对于option的处理不灵活,如果以后加的option更多,我们的命令也就越来越长。

第三个问题,用怎么样的方式,把这个流程串起来,同时能便于以后的维护呢?这就是核心了,Perl脚本来做这些工作。

use JSON;
use Template;
...
sub read_json {
...
}
sub template_proc {
...
}

我们的模板文件其实,比较简单:

module [% prefix_name %]_ref_model (
[%- FOREACH item IN inputs.sort %]
  input [% item %],
[%- END %]
[%- cnt =0 -%]
[%- total_cnt = outputs.size %]
[%- FOREACH item IN outputs.sort %]
  [%- cnt = cnt + 1 %]
  [%- IF (cnt == total_cnt) %]
  output wire [% item %]
  [%- ELSE %]
  output wire [% item %],
  [%- END%]
[%- END %]
);
[%- FOREACH item IN wires.sort %]
  wire [% item %];
[%- END %]

[%- FOREACH item IN lines_info_ref %]
  [%- IF (item.leafs.size == 0) %]
  [%- ELSE %]
  assign [% item.name %] = [% item.contents.join(' ') %];
  [%- END %]
[%- END %]
endmodule

perl的源代码可以来这里找 。Enjoy yourself!

Perl Template


关于print的一些事

某一些情况下,我们想要根据一些变量,然后生成一大段文本或者是代码。比较直接的代码风格就是:

my $name = "John";
my $day = "Friday";

print "Dear $name,\n";
print "Please come to my office on $day.\n";

幸好,我们还可以少打一些“print”,如果我们这样来写:

my $name = "John";
my $day = "Friday";

print <<END;
Dear $name,
Please come to my office on $day.
END

如果文本的内容更多,那么在我们的脚本里就会出现需要“print”的大段信息,不如我们单独把这些信息放到另外一个文件里,我们管它叫模板文件,比如“letter.tt”:

Dear [% name %],
Please come to my office on [% day %].

然后,我们在我们的Perl脚本里去处理这个模板,然后最后输出到“letter.txt”中:

use Template;
use Cwd;

my $dir = cwd();
my $config = {
    INCLUDE_PATH => $dir
};

my $tpl_name = "letter.tt";
my $tpl = Template->new($config);
chdir($dir);

my $var_ref = {
    name => "John",
    day  => "Friday"
};

my $out_file = "letter.txt";

$tpl->process("$tpl_name", $var_ref, "$dir/$out_file") || die $tpl->error();

把模板代码单独放到另外一个文件,便于以后的维护。即使维护的人,不懂Perl,但是模板本身具有极强的易读性。更多关于Template Toolkit的介绍,在这里。 当然,如果需要使用Template Toolkit的话,需要通过CPAN来额外安装,这一部分我会再另外写blog做介绍,今天先到此。

Text Processing


文本处理思路梳理

在日常的工作中,经常会遇到处理log或者文本的一些工作。但一时又想不到如果解决,提笔忘脚本。由此,我总结一些小的方法,给自己梳理一下思路,同时帮助别人。

按照文本处理的复杂程度,我们来决定用什么方法处理:

  • 如果文本本身size不大,只是关键字的替换或者列操作等,那么就用编辑器处理了,比如vim或者Emacs。
  • 不想用编辑器打开文本,并且需要集成到其他脚本或者Makefile中,操作多是单行的,便于以后重复使用,推荐sed或者awk。
  • 需要处理比较复杂,或者要生成一些代码,推荐使用Perl。

先说第一种情况,比如下面的代码。想要在下面的前四行代码,每行后面增加一些注释,比如"they are inputs"。

input a;
input b;
input c;
input d;
output e;

先以vim为例,按ESC进入命令模式,在第一行按下“ma”,第四行按下“mb”,标记了我们想要操作的区域。然后按“:”,输入命令“'a,'bs/;$/; \/\/ they are inputs”; “'a”和“'b”就是我们刚才标记的起点和终点,格式“s/xx/yy/”表示将“xx”替换成“yy”。注意这里,如果“yy”中包含“/”,需要使用反斜线进行转义。

<img src="../../images/textprocessing1vim.gif" class="img-thumbnail" width="60%" >

如果是Emacs,在第一行,组合键C-S-SPACE1标记区域的开始,光标来到在第五行的开头,可以看到我们选中了前四行,然后M-%,进行替换,最后的!表示全部替换。

<img src="../../images/textprocessing1emacs.gif" class="img-thumbnail" width="60%" >

再说第二种情况,文本比较大,不想打开,想把里面包含“delete me please!”的行都删掉。可以使用sed。

sed -e '/delete me please!/d' text.txt -i

-e表示执行的命令,其中“/xx/d”表示去匹配xx然后删除,-i表示处理结果写会到原文件。

最后是第三种情况,比如有一个文本文件如下:

module a;
   input i_a;
   input i_a_not_this;

   output o_a;
endmodule // a

module b;
   input i_b;
   input i_b_not_this;

   output o_b;
endmodule // b

我们想要把module b里的第一个input信号的名字换成module a里的第一个input信号的名字,这样比较适合Perl来做。

my $in_a_module = 0;
my $in_b_module = 0;
my $input_a;
my $end = 0;

while (my $line = <>) {
    if ($in_b_module == 1) {
	if ((defined ($input_a)) && ($end == 0)) {
	    $line =~s/input(\s+)\w+/input$1$input_a/;
	    $line .= " // The line above is changed.\n";
	    $end = 1;
	}
	if ($line =~/endmodule/) {
	    $in_b_module = 0;
	}
    } else {
	if ($in_a_module == 1) {
	    if ($line =~/input\s+(\w+)/) {
		if (! defined $input_a) {
		    $input_a = $1;
		} else {
		}
	    }
	    if ($line =~/endmodule/) {
		$in_a_module = 0;
	    }
	} else {
	    if ($line =~/module a/) {
		$in_a_module = 1;
	    }
	    if ($line =~/module b/) {
		$in_b_module = 1;
	    }
	}
    }
    print $line;
}

运行的结果是:

module a;
   input i_a;
   input i_a_not_this;

   output o_a;
endmodule // a

module b;
   input i_a;
   input i_b_not_this;

   output o_b;
endmodule // b

Footnotes:

1

C表示ctrl键,S表示shift键,SPACE表示空格键,中间的-表示同时按。