AXI VIP使用总结
西安交通大学 微电子学院 gmx 邮箱:1074240784@qq.com
转载引用请注明出处 https://www.cnblogs.com/Gmx-cnblogs/articles/16887008.html
AXI VIP使用总结
目录
说明:
使用verilog重写TVM中的VTA加速器的fetch和load模块后,会使用AXI VIP对两个模块进行测试,本文档是在测试过程中对AXI VIP的学习和总结,可以帮助需要的人避免一些错误的发生,加快上手速度。如想深入了解还需要看手册和示例代码,以及API源码,这些资料都在文中有介绍
AXI VIP相关的资料
认识AXI VIP需要有以下三样东西:
1. API文档和源码如下图中1所示AXI Verification IP (VIP) (xilinx.com)。

2. PG267手册AXI Verification IP (VIP) (xilinx.com)

3. 以及example design,如何打开example design手册中P39 Chapter 6有介绍,经观察example design中部分sim集可能并不能直接运行(里面的agent名称需要实际的替换),其出现的目的是教学。
注意: 更改block design后需要重新右键generate out put才能更新到实际的wrapper。
AXI4-Stream VIP与AXI VIP类似,且更简单,所以详细信息可以参考pg277手册。
建议先了解AXI VIP的agent、driver机制,这些机制与UVM验证非常相似,本人对UVM不是很了解,不过了解这些机制的话应该能更好上手AXI VIP,随后再去看示例代码了解API,API代码看不明白输入输出含义的可以结合文档和源码,就能够知道怎么能够规范使用、更好使用。
工程建立步骤
1. 首先新建vivado工程。
2. 随后添加ip 仓库的路径,准备将待测试模块导入(此处待测试模块被封装成了ip,您也可以将rtl代码加入到block design中)。

3. 新建block design,并依次导入待测试的模块和AXI VIP ip,并设置好各模块的axi地址。如下图所示。

分辨率更高的图见随文档pdf。
其中主要包括了待测试的fetch模块,load模块,负责读写控制寄存器的axi4lite_master AXI VIP模块,模仿ddr存储器的axi4_slave_ps AXI VIP模块,负责接收load指令并传递下去的axi4stream_load_pass AXI VIP模块,负责接收计算指令的axi4stream_gemm_slave AXI VIP模块,负责接收store指令的axi4stream_store_slave AXI VIP模块,以及8+1个双端口ram ip、复位时钟ip和AXI互联结构ip。同时将需要的信号输出到外部供testbench使用。
4. 随后新建testbench,在其中只对时钟和复位以及dut进行例化,如下图所示

(中间省略了一些信号定义)

然后运行testbench行为级仿真,就会输出AXI VIP的位置。此步骤的目的是使block design中的AXI VIP模块生成对应component name的库,随后才可以使用库中的定义。运行该仿真后可以在Tcl Console窗口看到对应的生成的xilinx AXI VIP实例,如下图所示:

发现了5个AXI VIP,其中一个处于pass through模式(是负责监测load队列指令的模块),Path在之后的testbench中需要使用。同时有一些类名字和模块名字相关,这也是为什么要先生成后使用的原因。
其中关于Block Memory Generator的信息说的是使用的行为及模型不支持读写冲突。

5. 然后关闭仿真,开始编写testbench。
Testbench编写注意事项
1. 首先根据AXI VIP手册文档pg267中第46信息,testbench需要必须包括的有
1)完整的testbench模块开头和结尾。
2)两个需要的包axi_vip_pkg 和 <component_name>_pkg。文档中写到:The <component_name>_pkg includes agent classes and its subclasses for AXI VIP. For each VIP instance, it has a component package which is automatically generated when the outputs are created.
说明<component_name>_pkg包是在创建输出的时候自动生成的,而运行行为级仿真是会自动创建输出的。这个包中定义了代理agent的类和子类。每一个AXI VIP的实例都有一个对应的<component_name>_pkg名字。我们以工程中name为axi4lite_master的模块为例,则其对应的包为design_1_axi_vip_0_0_pkg。
3)然后便是声明AXI VIp的agent,该代理用来连接动态和静态区域,具体相关内容可以看手册。不同功能类型的AXI VIP对应着不同的类,而且类的名字与component_name相关,如下图所示。同样以axi4lite_master为例,对应的声明为design_1_axi_vip_0_0_mst_t axi4lite_master_mst_agent;

4)然后便是为agent绑定AXI VIP,可以理解为赋值,如下图所示。此处的<hierarchy_path>便是之前在Tcl窗口下输出的Path,以axi4lite_master为例,此处是tb_top.dut.design_1_i.axi4lite_master.inst。tb_top不加。
axi4lite_master_mst_agent = new("axi4lite_master_mst_agent", dut.design_1_i.axi4lite_master.inst.IF);

5)最后便是启动AXI VIP,对应不同功能的AXI VIP,其启动的方法也不同,有start_master、start_slave。以axi4lite_master为例,是axi4lite_master_mst_agent.start_master();
6)之后便是参考example design进行功能代码的编写。
注意:
1. <hierarchy_path>在tcl输出中可以看到,当然根据规律可以推算出,<hierarchy_path>指的就是路径位置,可以从source中tb的目录组成中看到。只需要从dut开始到axi4lite_master结束,dut.design_1_i.axi4lite_master然后加上inst.IF便是dut.design_1_i.axi4lite_master.inst.IF,其中忽略了tb_top一级和design_1一级。

2. 需要注意的是component_name和name是不一样的,在本人的vivado2020.1版本中,pg267手册中给出的第一个寻找component_name的方法是不对的。以下是寻找component_name的几种方法,此处的component_name应该是design_1_axi_vip_0_0。
首先是手册中的第一种方法,在重定义ip界面,如下两图,可以看到在该界面中虽然显示为component_name,但是却是灰色不可更改,而且该名字与Block Properties窗口中的name一致,且可以在该窗口中更改名字,所以这里的component_name不正确。


第二种方法如下图所示,是正确的值。

第三种方法是在源文件中可以看到,如下图所示,前面是name后面跟的是component_name。

3. 出现如下的错误是正常的,设计到xilinx AXI VIP包的未定义错误都是由于vivado此版本存在的bug,可以忽略类似问题。


本工程功能代码编写示例
在本fetch和load联合仿真代码中共有2个AXI VIP和3个AXI-Stream VIP,其中axi4lite_master是AXI master VIP,负责对fetch和load模块的控制寄存器进行读写,axi4_slave_ps是AXI slave VIP且带有mem模型,模仿的是PS侧ddr存储器,axi4stream_store_slave是AXI-Stream slave VIP,负责接收来自store_queue接口的数据(store指令),axi4stream_gemm_slave与axi4stream_store_slave类似,负责接收来自gemm_queue的数据,axi4stream_load_pass是AXI-Stream passthrough VIP,负责将load_queue数据(load指令)传送到下级load模块,load模块负责执行该指令,axi4stream_load_pass存在的意义是检测fetch到load的数据是否符合要求(检测送下来的指令是不是load模块执行的指令)。
1. 首先是在顶层模块外导入对应的包,包括前两个通用包,只要使用了AXI VIP就要导入axi_vip_pkg包,只要使用了AXI-Stream slave VIP就要导入axi4stream_vip_pkg,和后面的5个component package,每一个AXI VIP实例都有一个与之对应的自动生成的component package。这些包的名字是<component_name>_pkg,见本文之前的注意事项。

2. 随后是在顶层模块中声明代理,此处代理的类名称取决于component_name和AXI VIP的接口类型(master、slave、passthrough/with or without mem model)。具体策略见本文之前的注意事项。

3. 随后新建一个块语句负责初始化,给代理赋值,也就是将变量和block design 中的AXI VIP进行绑定。这里new的第二个参数是AXI VIP的路径<hierarchy_path>.IF,具体见本文上一部分的注意事项。

4. 随后使用函数设置各个VIP模块空闲时驱动总线为0。(如下红线报错信息说变量未定义,此类型错误可以忽略,是由于vivado bug造成的实际上是可以识别和运行的)

在AXI-Stream 手册pg277的32页,有如下介绍,而在AXI VIP手册 pg267中没有这些介绍,但是有相似的函数,AXI VIP不加此部分依然可以正常运行,AXI-Stream VIP建议直接加上,因为此部分出现在pg277手册中且列为了AXI-Stream VIP必须拥有的选项。

5. 随后设置一些属性,便于调试信息的输出。

6. 启动各个agent,作为slave功能的VIP代理使用start_slave, master功能的VIP代理使用start_master, passthrough功能的AXI-Stream VIP需要设置为passthrough模式,然后使用start_monitor启动监测功能。这里的启动并不是意味着立马就会工作,只是处于了可工作状态,对于slave来说就是可以接受数据了,具体的接收数据还有专门的函数负责,对于master来说就是可以发送数据了,具体的发送、等待回应还会有具体的函数实现。
最后为两个AXI-Stream VIP设置了ready信号的策略,AXI VIP可以比较细致的控制ready信号的产生逻辑和策略,能够让ready信号拉高和拉低具有一定的规律和自动性,具体策略可以查看pg267手册85页。Ready信号出现在每个通道中,会有默认策略,可以不设置。

7. 针对带有mem 模型的AXI Slave VIP,需要对其进行初始化,此处axi_slave_ps_mem_agent负责模拟ddr存储数据,可以使用VIP提供的函数进行固定数据的初始化,也可以使用随机初始化。需要注意的是AXI VIP中几乎所有操作都是使用agent进行的。Mem功能的slave不需要产生读写响应数据,只需要将数据初始化到mem model中,读取时返回的数据和写时候的响应AXI VIP会自动处理。


8. 通过写模块寄存器,启动fetch模块和load模块,随后初始化部分完成。


写寄存器函数的具体实现可以看代码,此函数来自AXI VIP提供的example design中,用来对固定地址进行写。并且可以设置为等待设备响应(阻塞)和不等待响应(非阻塞)。

9. 随后使用AXI-Stream Slave VIP提供的monitor来获取主端口发来的数据,然后压入队列。

10. 然后在新的块语句中监测三个AXI-Stream Slave接收数据队列的变化,一旦三个队列中的一个不为0,则从ddr中指定地址处读取出一条指令,并与非空队列中(slave接收到的)的指令进行对比,正确代表指令下发无误。此处使用了backdoor_memory_read函数,这是带有mem的AXI Slave的特有功能,可以从指定地址处读出AXI VIP设定位宽(本例中为128bit)的数据内容。(指令下发:fetch模块负责从ddr中读取指令,根据其中的opcode将其下发给不同的模块,load、gemm个store)

11. 其余部分则是与AXI VIP 相关性不大的逻辑代码。
AXI VIP不同模式的具体使用步骤
本文中的AXI master VIP对寄存器进行访问,AXI Slave VIP充当的是ddr,是带有mem model的slave,只需要初始化其内部的存储模型即可使用,不需要对读写行为作出显式回应,AXI-Stream VIP中有两个slave只负责接收数据,一个passthrough只负责监控数据流。
由于本项目没有使用到AXI VIP的部分功能,所以缺少了对没有mem model的AXI Slave VIP如何应对响应,以及master如何获取rsp内容的介绍,这部分内容现在介绍如下,该部分内容参考自pg267手册中p50的Optional Test Bench Controls。Pg267同时手册中p46开始的Useful Coding Guidelines and Examples部分的非常值得仔细阅读,同时结合示例代码便能够很好地理解AXI VIP的使用。
1. Master写
1)需要新建一个待写的数据,
2)然后将该数据和agent绑定
3)设置返回策略。如果需要等待返回结果的话,读的话等待读取的数据,写的话等待写响应通道的响应
4)这个数据是axi_transaction类型的(一个类),该类里面包含了数据和AXI总线相关的一系列参数,需要将关键的参数赋值,手册强烈建议使用随机赋值,是怕用户出现某些值未赋值而导致出错,可以先采取随机赋值,随后固定赋值覆盖的方式来解决,或者采用example design中提供的打包好的API来设置其中的内容。
可以设置响应策略,如果等待响应返回,则后面可以使用wait_rsp来等待读写结果的返回。
5)通过agent的wr_driver将数据发送出去,这里指的是写请求
6)随后可以选择等待rsp,rsp信息返回后会赋值在同一个axi_transaction实例的参数bresp中,或者直接不等待响应,具体的类中元素需要查看示例代码和源码。

2. Master读有两种方式
一种是使用read driver,一种是使用monitor。
使用monitor
1)首先使用item_collected_port.get()获取监控到的数据,这是一个阻塞的函数调用,会一直阻塞直到出现一个传输,所以要放在forever循环中,这里的agent是master,当然slave和passthrough模式的agent同样可以监控到数据,同样的操作依然试用,
2)随后监控到的数据会自动放到axi_monitor_transaction(继承自axi_transaction类)中,其中存放了数据和指令等相关的一切数据,只需要从中提取就可以。如果是获取读回来的数据使用get_data_beat和get_data_block分别获取一个数据和一个4kB的数据,观看源码可以知道一个数据指的是axi传输的size大小的数据,由于数据可以突发传输,所以数据会有不止一个,使用index来指定获取哪一个beat。


可以看到全部是调用了类内部的自建函数来实现的数据提取。

使用read driver如下图所示,

1)首先是新建一个传输并与agent绑定
axi_transaction rd_trans; // Read transaction
rd_trans = agent.rd_driver.create_transaction("read transaction with randomization for getting data back");
2)然后使赋值,随机或者固定都有对应的函数
随机的话执行如下操作fill_transaction_with_fully_randomization(rd_trans);

固定赋值的话执行如下操作,直接使用trans.set_xxxx来设置传输对应的值,下图的函数是一个完整的赋值发送函数,使用此函数的话其他操作就不需要了。

3)设置返回策略,只有这样才会将数据返回,具体的返回策略可以看pg267手册的64页Transaction Return Item Policy Implementation,返回策略会影响响应返回的时刻和次数,比如对于读数据来说是在最后一个数据握手成功后返回还是在指令握手成功后就返回。
rd_trans.set_driver_return_item_policy(XIL_AXI_PAYLOAD_RETURN);
4)发送读取传输,读取传输发送,相当于发送了读地址等信息
agent.rd_driver.send(rd_trans);
5)等待响应,这是一个阻塞的函数,会一直等到函数返回,也就是数据返回,手册中还可以设置返回时刻是在第一个数据拿到后还是拿到全部数据后一块返回,详细信息可以查询手册和源码。
agent.rd_driver.wait_rsp(rd_trans);
6)使用rd_trans.get_data_beat和rd_trans.get_data_block函数获取返回的数据。
以上的步骤是具体拆分出来的步骤,实际中使用会有些步骤省略,比如示例代码中的single_read_transaction_api函数是一个非阻塞的因为缺少了3)5)两步,数据不会返回来,可以对其进行修改使其具有返回功能,更多高级的功能也可以查询示例代码。
3. Slave响应
如果slave具有mem模型的话,我们不需要处理slave的响应问题数据的读写和响应会自动处理,这也是本工程中名为axi_slave_ps_mem_agent的工作模式。如果slave没有mem model,则必须处理读写响应问题。响应处理方式如下,在主块中吊起多线程,分别处理读写响应和awready信号的产生策略。

写响应
1)首先创建一个axi_transaction 变量wr_reactive
2)agent使用wr_drive的get_wr_reactive函数与该变量绑定,该函数会阻塞,直到接收到了一个传输,并将该传输赋值给wr_reactive变量
3)随后对axi_transaction 变量wr_reactive的buser和bresp赋值
4)通过wr_drive将响应发送回去


读响应
1)首先创建一个axi_transaction 变量rd_reactive
2)agent使用rd_drive的get_rd_reactive函数与该变量绑定,该函数会阻塞,直到接收到了一个读请求传输,并将该传输赋值给wr_reactive变量
3)随后通过函数对axi_transaction 的data部分进行赋值
4)通过rd_drive将响应发送回去


4. Passthrough 模式下的AXI VIP
Passthrough模式下的AXI VIP默认工作在monitor模式下,也就是只监测路径上的信号,使用之前master通过monitor读数据中介绍的方式获取流过的数据。
同时Passthrough模式下的VIP可以切换为slave和master模式,接管上游和下游的信号,这样会阻断路径上原来的slave和master。
切换到slave时使用如下代码

切换到master时使用如下代码

切换到passthrough时使用如下代码

同时需要注意手册中如下的描述,切换时候必须先停止对应的模式再切换,比如从master切换到slave必须必须先agent.stop_master,然后再设置为slave模式,再启动slave,手册中说切换是可以实时进行的,但是不保证正在传输的数据会完成传输。
切换完成后就与正常的master和slave一样了。

5. Ready信号的产生
Ready信号表明该通道的数据接收方已经准备好接受数据了,该部分的具体内容在pg267的85页。

对于master来说,需要设置的ready信号是Rready和Bready,对于slave来说需要设置的信号是AWready、RAready和WRready,这些ready信号可以设置,也可以不设置,不设置时候会有对应的默认策略来产生ready信号,具体见pg267手册,主要有两种设置方式。
设置方式1来自example design中的*_slv_stimulus.sv
1)新建一个axi_ready_gen类型的变量,在这里是awready_gen
2)与对应的通道驱动绑定,在这里AWready需要与写通道绑定
3)设置awready_gen的策略,也就是awready神什么时候拉高,拉高多久,持续多久,什么时候拉低
4)设置策略中需要参考的数据,拉高拉低的次数、拉高后握手几次拉低,拉高后多少个周期没握手就拉低等参数值。
5)将ready信号发送给对应通道驱动的对应ready,这里用的是wr_driver的send_awready函数,如果是WREADY信号则要使用wr_driver的send_wready函数其他情况可以自行类比。

设置方式2来自example design中的axi_vip_0_exdes_ready_tb.sv
1) 新建一个axi_ready_gen类型的变量,在这里是rgen
2) 使用new给rgen赋值,字符串应该是备注或者输出值
3)设置awready策略
4)使用agent的对应通道的对应awready的函数将rgen与其绑定即可,此处是agent的写驱动的设置awready函数set_awready_gen()


AXI VIP使用总结
浙公网安备 33010602011771号