一、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过程中,需要明白以上三个值的含义!