所使用EDA软件:VIVADO2018.3
FPGA型号:xc7a35tcsg325-2
注意:看懂这篇文章的某些概念可能需要一点点systemverilog的基础
在上一节中我大概讲了下有关BRAM的内容,不过翻回那篇文章,一行代码都木有,不太像我的风格( •̀ ω •́ )y。恰好有小伙伴私信我有关AXI IP验证的有关内容,我就大概结合下BRAM 的AXI IP 来讲讲相关内容。
通常情况下,我们要验AXI的IP都不会一步步的去搭激励,而是用官方提供给我们的AXI VIP(AXI Verification IP)或者AXI BFM(AXI Bus Function Model)。貌似新的Vivado(2016.4版本后)把AXI BFM给删了,所以今天我主要讲的是AXI VIP。
好的,开始搭环境。
步骤1:先搭个AXI BRAM CONTROLER IP ,为了方便,我直接就用Vivado的block design(图1所示)。
图1:BRAM IP的创建
步骤2:配置下BRAM IP,位宽设置为32,其他按照默认的来就好(图2所示)。
图2:BRAM IP的配置
步骤3:创建个AXI VIP(右键Add IP,搜索AXI VIP,双击就好,这里就不配图了)
步骤4:配置AXI VIP,由于BRAM AXI CONTROLER IP是个SLAVE IP ,所以我们需要把INTERFACE MODE选为MASTER,其它按照默认的配置就好(图3所示)。
图3:AXI VIP的配置
步骤5:IP生成好后,按Run Connection Automation,全选里面的内容后点OK,会自动产生我们上一节课所讲的BRAM IP。再按需求连连线,将端口引出即可,最终结果如图4所示。
图4:AXI VIP 验证环境
步骤6:打开Address Editor(默认会有,如果一不小心关了,可以在Window->Address Editor中打开),点击Auto Assign Address,给BRAM配置AXI的初始偏移地址,再配置下Range为8K(如图5所示)。最终的BRAM为宽度32,深度2048(一个32位数据占4个byte,即一个数据占4个地址,4*2048=8K,深度2048是这样来的)。注意:这里BRAM IP的内存空间大小是再Address Editor中给的,双击BRAM IP是一片灰啥都配置不了的(如图6所示)。
图5:Address Editor 配置
图6:BRAM IP 一片灰,啥都改不了
步骤7:环境搭好后,我们右键自己的Block Design设计,点击Generate Output Products,选择Out of context per IP ,Generate单独编译,步骤如图7所示。
图7:编译Block Design
步骤8:Generate Output Products完成后,右键自己的Block Design ,Create HDL Wrapper, 生成相应的verilog 的Block Design HDL顶层,步骤如图8所示。
图8:生成Block Design HDL顶层
生成完的顶层(后面我会把它叫做DUT)如代码1所示,然后我们就要给它写相应的Testbench
//代码1 module design_1_wrapper (aclk_0, aresetn_0); input aclk_0; input aresetn_0; wire aclk_0; wire aresetn_0; design_1 design_1_i (.aclk_0(aclk_0), .aresetn_0(aresetn_0)); endmodule
在写Testbench前,我们大概看下AXI VIP 的Test Bench大概的架构,如图9所示。刚才通过步骤1-8我们已经把DUT给搭好了,Master Agent的内容(未实例化的对象)官方已经帮我们封好了,将它导进来,实例化,然后调用相应的API就好。下面我们最主要要做的就是写一下User Environment,细点来说就给时钟,复位,transaction,收发包就好。
图9:AXI VIP Test Bench的架构图
然后我们就开始写Testbench 吧,注意:这个Testbench必须要是Systemverilog,verilog和VHDL都不支持
第一步:在Simulation sources中新建个top_tb.sv,如图10所示
图10:新建top_tb.sv
第二步:开始写testbench ,先导入两个package,如代码2所示。其中第一个package的格式固定为axi_vip_pkg::;第二个package的格式为componentname_pkg::*,其中componentname需要在blockdesign中查找,方法如图11所示(当然也有很多的方法,就不一一列举了)。
//代码2 import axi_vip_pkg::*; import design_1_axi_vip_0_2_pkg::*;
图11:Component_name的查找方式
第三步:搭testbench的老套路了,给个时钟,给个复位然后将DUT实例化,结果如代码3所示
//代码3 localparam CLK_PERIOD = 10; reg clk ; reg rstn; always begin #(CLK_PERIOD/2) clk = ~clk; end design_1_wrapper dut( .aclk_0 (clk ), .aresetn_0(rstn) );
第四步:声明个agent(如代码4所示),不同的agent声明的命名方法不同,具体参考图12本次我们用的是master axi vip,所以声明成design_1_axi_vip_0_2_mst_t
//代码4 design_1_axi_vip_0_2_mst_t master_agent;
图12:不同agent声明的命名方法
第五步:实例化agent(代码5),实例化格式如agent = new("起个VIP名字", <hierarchypath>.IF),hierarchy_path的查找方式如图13所示。
//代码5 master_agent = new("Master_AXI_VIP",dut.design_1_i.axi_vip_0.inst.IF);
图13:hierarchy_path查找方法(就是一层层实例化名往下找,最顶层可忽略)
第六步:启动agent如代码6所示 。注:master用.start_master启动,slave用.start_slave启动,passthrough要先设模式再start。
//代码6 master_agent.start_master();
第七步:先创建个write transaction,给它赋相应值,再将其发送,等待突发写完。过程如代码7所示。(按照PG267共有三种方式,我选了个灵活性较强的方式,读者感兴趣可以用别的方式尝试)
//代码7 wr_transaction = master_agent.wr_driver.create_transaction("write transaction"); wr_transaction.set_write_cmd(mtestADDR,mtestBurstType,mtestID,mtestBurstLength,mtestDataSize); for(int beat=0; beat<wr_transaction.get_len()+1; beat++) begin wr_transaction.set_data_beat(beat, beat); compare_data_in[beat] = beat; end wr_transaction.set_driver_return_item_policy (XIL_AXI_WLAST_RETURN); master_agent.wr_driver.send(wr_transaction); master_agent.wr_driver.wait_rsp(wr_transaction);
其中set_write_cmd函数(图14),set_data_beat函数(图15,突发写数据时应注意4K边界的问题),和return_item_policy的类型(图16),我们需要重点关注下。
图14:set_write_cmd 函数说明
图15:set_data_beat函数说明
图16:return_item_policy的类型
第八步:先创建个read transaction,配置它,再将其发送,等待回应,最后将发送和接收的数据进行比较,过程如代码8。这段和写类似,我就不多做说明啦。
rd_transaction = master_agent.rd_driver.create_transaction("read transaction"); rd_transaction.set_read_cmd(mtestADDR,mtestBurstType,mtestID,mtestBurstLength,mtestDataSize); rd_transaction.set_driver_return_item_policy (XIL_AXI_PAYLOAD_RETURN); master_agent.rd_driver.send(rd_transaction); master_agent.rd_driver.wait_rsp(rd_transaction); for(int beat=0; beat<rd_transaction.get_len()+1; beat++) begin compare_data_out[beat] = rd_transaction.get_data_beat(beat); if(compare_data_in[beat]==compare_data_in[beat]) $display("No_%0d beat chk successful,the value is %0d ",beat,compare_data_in[beat]); else $display("No_%0d beat chk failed ,the input value is %0d , the output value is%0d ",beat,compare_data_in[beat],compare_data_out[beat]); end
第九步:观察读写波形(图17、图18)和窗口打印的数据(图19)来判断该次AXI读写操作是否成功。
图17:AXI burst write操作波形
图18:AXI burst read操作波形
图19:窗口打印的数据
好的,AXI VIP的使用就讲到这里啦 。 对AXI还不太了解的同学,我推荐去看看 @ljgibbs 写的几章AXI相关的文章,写的很不错。
参考文档:
-
PG267 AXI Verification IP v1.1
-
XILINX_VIP_2019_1
附:要获得top_tb.sv源码的同学可以关注我的公众号 FPGA说 ,后台回复AXIVIP即可。强行打广告,Yes。