当前位置: 代码迷 >> 综合 >> 【第二届集成电路EDA备赛】二、Icarus Verilog(iverilog)快速上手使用(基于Linux)
  详细解决方案

【第二届集成电路EDA备赛】二、Icarus Verilog(iverilog)快速上手使用(基于Linux)

热度:68   发布时间:2023-12-13 01:45:33.0

一、iverilog源码下载编译安装

安装一些必要的依赖:

sudo apt-get install autoconf -y
sudo apt-get install gperf -y
sudo apt-get install bison -y
sudo apt-get install flex -y

新建几个文件夹:

cd /home/clay/linux
mkdir EDA
cd EDA
mkdir install

下载iverilog源码:

cd /home/clay/linux/EDA
git clone https://gitee.com/ReCclay/iverilog.git

编译iverilog:

总体步骤是:配置 -> 编译 -> 安装

cd /home/clay/linux/EDA/iverilog
sh autoconf.sh
./configure --prefix=/home/clay/linux/EDA/install CFLAGS=-g
make -j4
sudo make install

./configure -h命令:查看更多配置信息
--prefix=xxx参数:决定make install 时可执行文件安装到哪个地方
CFLAGS=-g参数:gcc或g++编译时方便debug
make -j4命令:j4表示4核编译,需要注意第一次make会比较慢

可以在/home/clay/linux/EDA/install/bin目录看到生成的可执行文件!

在这里插入图片描述

二、iverilog修改与运行

2.1、iverilog编译流程介绍与分析

1、介绍

iverilog也是一个编译器,而一个编译器通常可分为以下5个阶段:

  • ①、Preprocess:写入宏等变量到程序
  • ②、Parse:语法分析、词法分析,由verilog文件生成pform文件【参考pform.h】
  • ③、Elaboration:输入pform,生成网表文件netlist【参考netlist.h或者netlist.txt】【竞赛要求做的东西在这一步之后
  • ④、Optimization:输入netlist,输出仍是netlist。删除无用的边、node、变量等
  • ⑤、Code generation:生成可执行代码vvp code【参考vvp/README.txt】

虽然主程序的名字是iverilog,但是iverilog仅仅是生成ivlpp和ivl的命令(cmd),并没有做具体的工作, ivlpp和ivl是顺序执行的。 (Ivlpp的输出作为ivl的输入)

对于编译阶段,程序分为两个可执行文件ivlpp和ivl, ivlpp是预处理部分(对应上述步骤1),ivl是处理的上述步骤2-5。

2、分析

首先打开源码根目录下的main.cc,是ivl的主函数!

ivl主函数对应到代码,关系如下所示【包含②~⑤】:

  • main函数中的前面是处理参数和option,跳过。
  • Parse:1124行:rc += pform_parse(source_files[idx]);,输入一些sourcefile,输出pform,接着中间代码是打印一些信息。
  • Elaboration:1225行:Design*des = elaborate(roots);,生成netlist。netlist是Design类型,该类型是一个整个structure,可以拿到verilog里的任何信息:module、变量、always、initial、assign、expression…
  • Optimization:接下来做了一些优化,不必关心,跳过。
  • Code generation:1315行:if (int emit_rc = des->emit(&dll_target_obj)) { ,做的是一个code generation。最后在1348行:EOC_cleanup();,程序执行结束,释放内存。

3、测试

跑一个简单的示例:

cd /home/clay/linux/EDA/iverilog/examples
/home/clay/linux/EDA/install/bin/iverilog hello.vl
./a.out

在这里插入图片描述

iverilog是一个解释型的,生成的a.out是可读的。

cat a.out

在这里插入图片描述
最终是用/home/clay/linux/EDA/install/bin/vvp这个可执行文件执行的!

编写一个驱动冲突文件test.v用以接下来的测试,具体代码如下:

module dut (input a,b,c,e, output q);
wire a,b,c;
reg r;
reg clk;
reg q;
reg d;
always @(a,b,c)d = a & (b | c);
always @(e)q = e;
always @(posedge clk)q = d;
endmodule

2.2、iverilog修改

1、修改

接下来才是到真正修改的时候了

/home/clay/linux/EDA/iverilog/main.cc中的1315行,添加des->examples();调用。

/home/clay/linux/EDA/iverilog/net_design.cc中的1023行,添加如下代码:

void Design::examples(void)
{
    std::map<NetNet*, int> net_map;for (NetProcTop* cur = procs_; cur; cur = cur->next_){
    NetEvWait* np = dynamic_cast<NetEvWait *>(cur->statement());if(!np) continue;NetAssign* ass = dynamic_cast<NetAssign *>(np->statement());if(!ass) continue;unsigned cnt = ass->l_val_count();assert(cnt > 0);NetAssign_* lval = ass->l_val(0);NetNet* net = lval->sig();if (net_map.find(net) == net_map.end())net_map[net] = 1;else{
    printf("Var %s has multi-driver!\n", (net->name().str()));}}
}

上述代码思路讲解:

  • std::map<NetNet*, int> net_map;:定义了一个c++中的map数据类型,map格式是键值对(key-value)的形式,其中key对应的是NetNet类型,value对应的是int类型。NetNet是iverilog中表示verilog中reg wire等类型的类,NetNet区分是reg还是wire通过其成员函数type。int用来进行driver计数(当然计数这只是其中一种思路)。
  • for (NetProcTop* cur = procs_; cur; cur = cur->next_){ :其中的procs_Design数据结构下的成员变量,全称是process,对应verilog中的一个always过程块!当然了,一个initial也叫procs_procs是一个链表,这个链表通过其成员next_便可以遍历整个procs_,即遍历所有的always(或initial)过程块。举例对应verilog代码如下:
always @(a,b,c)d = a & (b | c);
  • NetEvWait* np = dynamic_cast<NetEvWait *>(cur->statement());:cur是链表procs_中的某一个,procs_下的statement成员函数,返回的类型是NetEvWait,对应到verilog就是always块中除了always关键字剩余的部分!举例对应verilog代码如下:
	  @(a,b,c)d = a & (b | c);
  • NetAssign* ass = dynamic_cast<NetAssign *>(np->statement());:拿到赋值语句,举例对应verilog代码如下:
    d = a & (b | c);
  • unsigned cnt = ass->l_val_count();:计算赋值表达式中左值个数,需要注意对于这个例子中左值只有1个,但是对于其他case,左值可能有两个。如{a, b} = 2'b01,即a=0,b=1。

  • NetAssign_* lval = ass->l_val(0);:在这个例子中,只拿了1个左值,其返回值是NetAssign_类型!

  • NetNet* net = lval->sig();:我们需要的是NetNet,通过sig()这个函数就可以从NetAssign_类型拿到NetNet类型了!

  • if (net_map.find(net) == net_map.end()):判断map中是否有该net,若没有则放进去,若有则提示冲突!

注意:

  • 1、/home/clay/linux/EDA/iverilog/net_design.cc里用到了c的printf函数,别忘了在最上面加上stdio.h文件,即# include <stdio.h>
  • 2、net_design.cc文件中只是类函数examples的实现,还需要在/home/clay/linux/EDA/iverilog/netlist.h文件中5076行添加类函数examples的声明,即void examples(void);
  • 3、修改好iverilog之后,还需要重新再走一次上面的“配置->编译->安装”流程:
cd /home/clay/linux/EDA/iverilog
./configure --prefix=/home/clay/linux/EDA/install CFLAGS=-g
make -j4
sudo make install

2、测试

切换到目录:

cd /home/clay/linux/EDA/iverilog/examples

运行test.v:

/home/clay/linux/EDA/install/bin/iverilog test.v

由于驱动冲突,会出现如下提示:

Var q has multi-driver!

当然以上示例是在Elaboration之后进行处理,当然也可以在Parse之后进行处理。

如果在Elaboration之后进行处理,要了解清楚netlist,如果在Parse之后进行处理,要了解清楚pform

三、iverilog的Debug

GDB使用快速入门,可以参考<这里>。

要了解清楚iverilog的数据结构对应verilog的哪条语句(哪种数据类型),debug是一个非常好的手段。ivl是我们主要debug的地方!

iverilog在/home/clay/linux/EDA/iverilog/driver/main.c中,从主函数中找到其中的t_compile,然后跳转到函数的具体实现(425行)!从该函数的注释中可以得出,该函数主要准备了Preprocess command 和 ivl command。在493行rc = system(cmd);准备完成,其中变量cmd里面包含了ivlpp和ivl,system就是执行系统调用。

在490行有printf打印信息,可以通过加入-v选项,将489行的verbose_flag标志打开!

495行~502行,是删除一些临时文件,在DEBUG的时候我们应该将其保留,如何保留?
IVERILOG_ICONFIG环境变量设上去即可。如何设置?

在终端输入export IVERILOG_ICONFIG=1来设置环境变量,输入echo $IVERILOG_ICONFIG打印,验证是否设置成功。

当然啦,也可以使用setenv IVERILOG_ICONFIG=1来设置环境变量!

接着再次运行test.v,需要注意这次得加上-v选项,可以打印一些有用的信息,:

cd /home/clay/linux/EDA/iverilog/examples
/home/clay/linux/EDA/install/bin/iverilog test.v -v

运行效果如下图:

在这里插入图片描述

其中我们可以看到translate,他有两个命令,分别对应ivlpp和ivl(两者用竖线分隔),内容如下

#ivlpp
/home/clay/linux/EDA/install/lib/ivl/ivlpp  -v -L -F"/tmp/ivrlg26e52384a" -f"/tmp/ivrlg6e52384a" -p"/tmp/ivrli6e52384a"#ivl
/home/clay/linux/EDA/install/lib/ivl/ivl -v -C"1" -C"/home/clay/linux/EDA/install/lib/ivl/vvp.conf"

我们来debug上述的ivl,输入以下命令:

当然,别忘了先安装cgdb:sudo apt-get install cgdb -y

cgdb --args /home/clay/linux/EDA/install/lib/ivl/ivl -v -C"1" -C"/home/clay/linux/EDA/install/lib/ivl/vvp.conf" test.v

运行效果如下图:

在这里插入图片描述

我们想停在Design::examples这个函数,可以设置断点,方法如下:

b Design::examples

然后输入r(即run),来运行程序到断点,效果如下图所示:

在这里插入图片描述

接着输入n(即next)可以单步调试,输入p procs_可以查看procs_变量的内容。(p即表示print),如下图所示:

在这里插入图片描述
上图中,由于procs_是一个链表,所以我们打印出来是一个指针的形式。

可以输入p *procs_查看具体内容,如下图所示:

在这里插入图片描述
其中next_表示链表下一个单元地址,type表示它的类型,从上图可以看出是always类型。

输入set p o o命令后,再输入p *procs_,可以更清楚地看到具体的数据类型,效果如下图:

在这里插入图片描述

其中,直观看到是一个NetProcTop类型的,然后基类是LineInfo。

同样的方法,继续运行,使用p *lval查看lval的值,效果如下图:

在这里插入图片描述

其中的sig_就是源码中1034行对应的sig(),输出类型是NetNet类型!

还有一些word_lwid_nest_base_对于这个case来说是空的,其他case来说他们不一定为空。这些对于做题时需要的,在debug其他case过程中,需要明白以上三个值的含义!