1)实验平台:正点原子达芬奇FPGA开发板
2) 摘自【正点原子】达芬奇之Microblaze 开发指南
3)购买链接:https://detail.tmall.com/item.htm?id=624335496505
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz_dafenqi.html
5) 正点原子官方B站:https://space.bilibili.com/394620890
6)对正点原子FPGA感兴趣的同学可以加群讨论:876744900
第九章AXI4接口之DDR读写实验
Xilinx从Spartan-6和Virtex-6系列开始使用AXI协议来连接IP核。在7系列器件中,Xilinx在IP核中继续使用AXI协议。本章我们对AXI协议作一个简单介绍,并在Vivado中实现一个AXI4接口的IP核,用于对DDR3进行读写测试。
本章包括以下几个部分:
99.1简介
9.2实验任务
9.3硬件设计
9.4软件设计
9.5下载验证
9.1简介
我们在前面的实验中介绍了一些带AXI接口的IP核的使用,比如《AXI GPIO控制LED实验》中的AXI4 GPIO IP核和《按键中断实验》中的AXI中断控制器等,这些IP核都带有AXI4接口。其中AXI4-Lite接口属于AXI4总线协议,接下来我们将对该协议作一个更具体的介绍。
AXI的英文全称是Advanced eXtensible Interface,即高级可扩展接口,它是ARM公司所提出的AMBA(Advanced Microcontroller Bus Architecture)协议的一部分。在介绍AXI协议之前,我们首先要对通信协议有一个基本的概念。
简单来说,通信协议就是指双方进行信息传递所遵循的规则和约定。其实在我们的生活当中,比如在打电话的时候,就遵循着一些基本的“通信协议”。为了更形象的说明这一概念,我们首先来看一个通话记录:
《少林寺的通话记录》
老方丈:下午张三丰和灭绝师太要来参观,你去机场接一下,我把他俩手机号给你 //主机发送控制信号
小和尚:好的,稍等,我找张纸记一下。
老方丈:嗯。 //主机等待
小和尚:我准备好了,您说吧。 //从机返回Ready信号
老方丈:张三丰的是“123-321-34567”,灭绝师太的是“123-456-56789”。 //主机突发传输数据
小和尚:记下来了。 //从机返回响应信号
上面的通话记录是一次完整的通信过程,传输的信息是两个手机号。我们把“老方丈”当成主机,“小和尚”当成从机,那么这一通信过程由主机发起,最终向从机写入两组数据(手机号)。我们需要注意的是整个过程中二者的协调配合:为了确保数据传输无误,主机需要等从机准备好之后才能发送数据;另外从机在接收数据完成后,会发送响应信号,表示传输完成。
然后再来看另外一个通话记录:
《武当山的通话记录》
张三丰:下午我要去趟少林寺,你把方丈的手机号找给我 //主机发送控制信号
小道士:找到了,188-666…… //从机返回有效数据
张三丰:等一下,我找支笔。好了,你说吧 //主机发送Ready信号
小道士:188-666-66666,念完了 //从机返回有效数据,以及响应信号
张三丰:好的。
在武当山的通话记录中,张三丰是主机,小道士是从机。通信过程同样是由主机发起,向从机请求数据。主机准备好之后发送Ready信号,接下来从机开始发送数据。从机在数据发送完成后给出响应信号,表明本次传输结束。
对比上述两个通话记录可以发现,少林寺的通话是一次主机向从机写数据的过程,而武当山的通话则是主机向从机读数据的过程。在通信过程中,主从之间会进行协调,只有等接收方准备好之后,才能开始数据传输,这种机制我们称之为“握手”。
在打电话的时候,通话双方能够理解彼此的语言,进而从中筛选有效信息。而在数字电路中,通信双方就没有那么智能了,主设备和从设备需要按照约定好的数据传输方式来发送和接收数据。AXI协议就是描述了主设备和从设备之间的数据传输方式,在该协议中,主设备和从设备之间通过握手信号建立连接。
AXI协议是一种高性能、高带宽、低延迟的片内总线,具有如下特点:
1、总线的地址/控制和数据通道是分离的;
2、支持不对齐的数据传输;
3、支持突发传输,突发传输过程中只需要首地址;
4、具有分离的读/写数据通道;
5、支持显著传输访问和乱序访问;
6、更加容易进行时序收敛。
在数字电路中只能传输二进制数0和1,因此可能需要一组信号才能高效地传输信息,这一组信号就组成了接口。AXI4协议支持以下三种类型的接口:
1、AXI4:高性能存储映射接口。
2、AXI4-Lite:简化版的AXI4接口,用于较少数据量的存储映射通信。
3、AXI4-Stream:用于高速数据流传输,非存储映射接口。
在这里我们首先解释一下存储映射(Meamory Map)这一概念。如果一个协议是存储映射的,那么主机所发出的会话(无论读或写)就会标明一个地址。这个地址对应于系统存储空间中的一个地址,表明是针对该存储空间的读写操作。
AXI4协议支持突发传输,主要用于处理器访问存储器等需要指定地址的高速数据传输场景。AXI-Lite为外设提供单个数据传输,主要用于访问一些低速外设中的寄存器。而AXI-Stream接口则像FIFO一样,数据传输时不需要地址,在主从设备之间直接连续读写数据,主要用于如视频、高速AD、PCIe、DMA接口等需要高速数据传输的场合。
在本章我们重点介绍AXI4接口,它由五个独立的通道构成:
1、读地址
2、读数据
3、写地址
4、写数据
5、写响应
下面是使用读地址和读数据通道实现读传输过程的示意图:
图 9.1.1 读传输过程示意图
从上图中可以看到,在一个读传输过程中,主机首先在读地址通道给出读地址和控制信号,然后从机由读数据通道返回读出的数据。另外我们需要注意的是,这是一次突发读操作,主机只给出一个地址,从该地址连续突发读出四个数据。
写传输过程如图 9.1.2所示,它用到了写地址、写数据和写响应三个通道。主机在写地址通道给出写地址和控制信号,然后在写数据通道连续突发写四个数据。从机在接收数据之后,在写响应通道给出响应信号。
图 9.1.2 写传输过程示意图
AXI总线中的每个通道都包含了一组信息信号,还有一个VALID和一个READY信号。VALID信号由源端(source)产生,表示当前地址或者数据线上的信息是有效的;而READY信号由目的端(destination)产生,则表示已经准备好接收地址、数据以及控制信息。VALID和READY信号提供了AXI总线中的握手机制,如下图所示:
图 9.1.3 VALID和READY握手机制
在图 9.1.3中,ACLK为时钟信号,在AXI协议中,所有的输入信号都在是ACLK的上升沿采样,所有的输出信号必须在ACLK的上升沿之后才能改变。在T1之后,源端将VALID拉高,表明INFORMATION信号线上传输的是有效的地址、数据或者控制信息。目的端在T2之后将READY拉高,表明它已经准备好接收数据,此时源端必须保持INFORMATION数据稳定不变,直到T3时刻进行数据传输。
需要注意的是,源端不允许等目的端的READY信号拉高之后,才将VALID信号置为有效状态。而且,一旦VALID拉高,源端必须保持其处于有效状态,直至成功握手(在时钟上升沿检测到VALID和READY同时为有效状态)。
AXI协议的五个通道都有各自的VALID/READY握手信号对,每个通道握手信号对的名称如下图所示:
图 9.1.4 各通道握手信号名称
到这里,我们已经简单介绍了AXI4协议的读写过程,以及握手协议。关于如何实现AXI4通信协议,以及如何在设计中使用该协议进行通信,我们将硬件设计部分进行讲解。
9.2实验任务
本章的实验任务是通过自定义一个AXI4接口的IP核,通过AXI接口对DDR3进行读写测试。
9.3硬件设计
根据实验任务我们可以画出本次实验的系统框图,如下图所示:
图 9.3.1 系统框图
在上图中,DDR3_TEST是我们自定义的IP核,具有AXI4 Master端口,该端口通过AXI Interconnect模块,最终连接MIG IP核。AXI UART IP核打印从DDR3中读出的数据。DDR3_TEST IP核在检测到按键按下后会启动读写过程,并将读出的数据与写入的数据作比较,比较完成后点亮LED1。另外,如果在读写过程中出错,或者在比较的过程中发现读出的数据与写入的数据不一致,LED2会点亮。
在进行硬件设计之前,我们需要先自定义一个带有AXI4 Master端口的IP核,并将其添加到工程的IP库中。我们在《自定义IP核-呼吸灯实验》中介绍了如何定义一个带有AXI-Lite Slave接口的IP核,在本次实验中定义IP的方法与之相同,只是这次我们要选择AXI4 Master接口。本次实验我们将在《Hello World》实验的基础上进行。
打开《Hello World》实验的工程,我们先打开《Hello World》实验的Vivado工程,打开后依次点击菜单栏的“File-> Project->Save As...”,将工程名改为“axi4_ddr_rw”。
在菜单栏中点击“Tools”,然后在下拉列表中选择“Create and Package New IP”,如图 9.3.2所示:
图 9.3.2 创建IP
在弹出的对话框中直接点击“Next”,如图 9.3.3所示:
图 9.3.3 创建和封装IP
在弹出的界面中选择“Create a new AXI4 peripheral”,点击“Next”。
接下来设置IP核的名称为“DDR3_TEST”,并将IP的路径修改为当前工程路径下的“ip_repo”文件夹,最后点击“Next”,如图 9.3.4所示:
图 9.3.4 设置IP名称和路径
在“Add Interfaces”界面中修改接口名称为“M_AXI”,选择接口类型为“Full”,接口模式为“Master”,数据位宽为“32”。如图 9.3.5所示:
图 9.3.5 自定义IP核接口配置
上图中对IP核接口的配置与《自定义IP核—呼吸灯实验》不同,在这里我们新建的IP核作为主机(Master),除此之外我们使用的接口类型变成了AXI-Full,而不再是AXI-Lite。
设置完成后点击上图中右下角的“Next”。
接下来选择“Edit IP”,最后点击“Finish”。如图 9.3.6所示:
图 9.3.6 选择Edit IP
在上图中点击“Finish”后会自动打开一个新的Vivado工程,工程名为“edit_DDR3_TEST_v1_0”,如图 9.3.7所示。我们可以在这个工程中对创建的IP核——DDR3_TEST进行编辑。
图 9.3.7 Edit IP工程
AXI4接口共有五个独立的通道,每个通道又有少则几个,多则十几个信号,如果让我们自己来实现这样一个接口还是比较复杂的。不过大家不用担心,我们在上图中创建AXI4接口的IP时,Vivado提供的IP封装工具已经自动帮我们实现了这样一个接口,并提供了一个示例程序。
DDR3_TEST_v1_0文件实现了AXI4协议下的读写测试模块,我们甚至都不用对代码作任何修改,即可实现对DDR的读写测试功能。虽然该模块的代码看上去比较长(900多行),但是大多是一些注释,非常详尽。大家可以通过阅读代码及注释,来学习AXI4协议主机的实现方式。
在这里我们只贴出部分代码:
- 735 //implement master command interface state machine
- 736
- 737 always @ ( posedge M_AXI_ACLK)
- 738 begin
- 739 if (M_AXI_ARESETN == 1'b0 )
- 740 begin
- 741 // reset condition
- 742 // All the signals are assigned default values under reset condition
- 743 mst_exec_state <= IDLE;
- 744 start_single_burst_write <= 1'b0;
- 745 start_single_burst_read <= 1'b0;
- 746 compare_done <= 1'b0;
- 747 ERROR <= 1'b0;
- 748 end
- 749 else
- 750 begin
- 751
- 752 // state transition
- 753 case (mst_exec_state)
- 754
- 755 IDLE:
- 756 // This state is responsible to wait for user defined C_M_START_COUNT
- 757 // number of clock cycles.
- 758 if ( init_txn_pulse == 1'b1)
- 759 begin
- 760 mst_exec_state <= INIT_WRITE;
- 761 ERROR <= 1'b0;
- 762 compare_done <= 1'b0;
- 763 end
- 764 else
- 765 begin
- 766 mst_exec_state <= IDLE;
- 767 end
- 768
- 769 INIT_WRITE:
- 770 // This state is responsible to issue start_single_write pulse to
- 771 // initiate a write transaction. Write transactions will be
- 772 // issued until burst_write_active signal is asserted.
- 773 // write controller
- 774 if (writes_done)
- 775 begin
- 776 mst_exec_state <= INIT_READ;
- 777 end
- 778 else
- 779 begin
- 780 mst_exec_state <= INIT_WRITE;
- 781
- 782 if (~axi_awvalid && ~start_single_burst_write && ~burst_write_active)
- 783 begin
- 784 start_single_burst_write <= 1'b1;
- 785 end
- 786 else
- 787 begin
- 788 start_single_burst_write <= 1'b0; //Negate to generate a pulse
- 789 end
- 790 end
- 791
- 792 INIT_READ:
- 793 // This state is responsible to issue start_single_read pulse to
- 794 // initiate a read transaction. Read transactions will be
- 795 // issued until burst_read_active signal is asserted.
- 796 // read controller
- 797 if (reads_done)
- 798 begin
- 799 mst_exec_state <= INIT_COMPARE;
- 800 end
- 801 else
- 802 begin
- 803 mst_exec_state <= INIT_READ;
- 804
- 805 if (~axi_arvalid && ~burst_read_active && ~start_single_burst_read)
- 806 begin
- 807 start_single_burst_read <= 1'b1;
- 808 end
- 809 else
- 810 begin
- 811 start_single_burst_read <= 1'b0; //Negate to generate a pulse
- 812 end
- 813 end
- 814
- 815 INIT_COMPARE:
- 816 // This state is responsible to issue the state of comparison
- 817 // of written data with the read data. If no error flags are set,
- 818 // compare_done signal will be asseted to indicate success.
- 819 //if (~error_reg)
- 820 begin
- 821 ERROR <= error_reg;
- 822 mst_exec_state <= IDLE;
- 823 compare_done <= 1'b1;
- 824 end
- 825 default :
- 826 begin
- 827 mst_exec_state <= IDLE;
- 828 end
- 829 endcase
- 830 end
- 831 end //MASTER_EXECUTION_PROC
上面的代码实现了一个状态机,其状态转换图如下所示:
图 9.3.8 状态转换图
系统复位后,状态机处于初始状态,在该状态下等待外部输入的启动传输脉冲init_txn_pulse。一旦检测到init_txn_pulse为高电平,状态机跳转到INIT_WRITE状态。
在INIT_WRITE状态下,状态机拉高start_single_burst_write信号,来不断地启动AXI4 Master接口对Slave端大小为4KB的存储空间进行突发写操作。写操作完成后,write_done信号会拉高,状态机进入INIT_READ状态。
在INIT_READ状态下,状态机拉高start_single_burst_read信号,不断地启动AXI4 Master接口对Slave端同一存储空间进行突发读操作,同时将读出的数据与写入的数据进行对比。读操作完成后,read_done信号拉高,状态机进入INIT_COMPARE状态。
在INIT_COMPARE状态下,判断AXI4接口在读写过程中是否发生错误,并将错误状态赋值给ERROR信号,然后将compare_done信号拉高,表示一次读写测试完成。最后跳转到IDLE状态,等待下一次读写操作的启动信号。
我们在查看DDR3_TEST IP核源码后,不需要再对IP作任何修改,直接关闭名为edit_DDR3_TEST_v1_0的工程。最终我们创建的IP核将通过AXI4 Master端口向Slave端指定的4K存储空间中连续写入1024个数据,写入的数值从1累加到1024,每个数据占32bit。
IP核创建完成后,我们在工程目录下的ip_repo文件夹中可以找到IP相关的文件,如图 9.3.9所示:
图 9.3.9 IP核相关的文件
需要注意的是,在图 9.3.9中我们只需要保留红色方框中的文件夹即可,其余文件及文件夹是用于对IP进行编辑的工程文件,我们可以直接删除。
回到axi4_ddr_rw工程界面,在左侧“Flow Navigator”一栏点击“IP Catalog”,然后在右侧的IP目录中可以看到我们前面所创建的IP核——DDR3_TEST,该IP已经自动添加到了当前工程的IP库中。
图 9.3.10 IP目录
在前面的实验中,都是不带DDR的系统结构,本次实验将学习带DDR的MicroBlaze系统搭建过程。
点击“Open Block Design”,在Diagram窗口中进行硬件设计。
首先打开MicroBlaze IP核,点击该IP进行配置,这里启用存储于指令优化选项,如图 9.3.11所示:
图 9.3.11 MicroBlaze设置
继续点击“Next”至第3页Cache页,将地址设置如图 9.3.12所示:
图 9.3.12 地址设置
上处地址值必须与IP INITRATOR->Adress Editor->microblaze_0->的mig_7series_0数据和指令的地址值保持一致,如图 9.3.13所示:
图 9.3.13 MicroBlaze地址设置参考值
MicroBlaze IP核设置完毕。
双击Clocking Wizard时钟IP核,在Output Clocks页面,选中clk_out1和clk_out2,时钟频率分别设置为100和200,复位设为低电平,如图 9.3.14所示:
图 9.3.14 设置输出时钟
clk_out1是给microblaze_0、uart、spi等模块提供时钟;clk_out2给mig_7series_0模块提供时钟。
接着添加MIG(Memory Interface Generalor) IP核,点击“+”按钮,输入“MIG”,双击Memory Interface Generator,添加IP核,如图 9.3.15所示:
图 9.3.15 添加MIG IP核
双击添加的MIG IP,进入配置页面,我们在达芬奇开发板FPGA设计部分,已经对MIG的配置操作有过详细讲解,这里我们简略说明一下配置需主要的配置步骤。
在Controller Options目录,DDR芯片的运行时钟设为“400M”,DDR3芯片型号选择“MT41K128M16XX-15E”,数据位宽这里设为“16”。如图 9.3.16所示:
图 9.3.16 时钟及物理芯片配置
在Memory Options目录, Input Clock Period我们选择“200M”。如图 9.3.17所示:
图 9.3.17 储存器配置
在FPGA Options目录,按照图 9.3.18所示进行配置。
图 9.3.18 系统时钟配置
“Pin/Bank Selection Mode”我们选择第二项,后面我们会使用.xdc文件。
图 9.3.19 Pin/Bank Selection Mode选项卡
在Pin Selection目录我们选择第二个选项“Read XDC/UCF”直接导入管脚分配文件,如图 9.3.20所示。
图 9.3.20 管脚分配导入选项卡
如下图所示,在工程目录下,我们已经为大家准备好了一个ddr3_rw_test.xdc文件,用户只要直接导入这个.xdc文件就可以完成ddr3的管脚分配。ddr3_rw_test.xdc文件的路径为G:/tis_pro/axi4_ddr_rw。
导入后我们点击“Validate”,此时会跳出对话框,表明已经验证通过,我们点击“OK”,此时“Next”变成可选,点击“Next”完成管脚分配。如图 9.3.21所示:
图 9.3.21 分配验证管脚
后续操作就不一一说明了,MIG IP设置完成后,将sys_rst与sys_rst_n相连,clk_out2与sys_clk_i、clk_ref_i相连,ext_reset_in与sys_rst_n相连,如图 9.3.22所示:
图 9.3.22 手动连线
连线完成后,点击“Run Connection Automation”,弹出提示页面后,选中所有信号,点击“OK”,如图 9.3.23所示:
图 9.3.23 自动连线
我们再把自定义的IP添加进来,添加完成后如图 9.3.24所示:
图 9.3.24 添加DDR3_TEST IP核
在图 9.3.24中,M_AXI是AXI-Full类型的主机接口,我们将通过这个接口对DDR3进行读写操作。DDR3_TEST IP核在检测到m_axi_init_axi_txn端口的上升沿后会启动读写过程,并将读出的数据与写入的数据作比较,比较完成后m_axi_txn_done输出高电平。另外,在比较完成后,m_axi_error信号会指示整个过程是否出错。如果在读写过程中出错,或者在比较的过程中发现读出的数据与写入的数据不一致,那么m_axi_error将会拉高。
添加完自定义IP核之后,双击该IP核对其进行配置,如图 9.3.25所示:
图 9.3.25 配置DDR_TEST IP核
在图 9.3.25中,我们将变量C M AXI TARGET SLAVE BASE ADDR的值修改为0x88000_0000(DDR3的基地址为0x80000000),它位于DDR3存储器的地址空间,是DDR3_TEST IP核进行读写操作的起始地址。
点击“Run Connection Automation”,连接DDR3_TEST IP核。
接下来,我们还要添加Utility Vector Logic IP核。然后将其配置成非门,位宽为1,作为反向器使用。这是因为我们需要使用按键来作为DDR_TEST IP核的启动信号,达芬奇开发板上的按键在按下的时候为低电平,因此我们通过添加一个反向器,将其修改为按下时输出高电平。
最后,添加一个AXI_Quad_SPI核,用来存储要固化的程序(若不需要固化,该IP核可不添加)。搜索“spi”并添加该IP核后如图 9.3.26:
图 9.3.26 AXI_Quad_SPI核
双击AXI_Quad_SPI核进行配置,Mode选择“Quad”,Slave Device选择“Micron”,FIFO Depth选择“256”,勾选“Enable STARTUP Primitive”。如图 9.3.27所示:
图 9.3.27 配置AXI_Quad_SPI核
点击“Run Connection Automation”,选中所有连线,点击“OK”,自动连接AXI_Quad_SPI核。将ext_spi_clk 与s_axi_aclk连接,管脚spi_rtl_0改名为“qspi_flash”。
图 9.3.28 最终布局图
另手动添加上图中4个橙色的外部接口,其中“key_init”用于连接达芬奇开发板上的按键,“error_flag”和“compare_done”两个接口用于连接开发板上的LED。
到这里我们的Block Design就设计完成了,在Diagram窗口空白处右击,然后选择“Validate Design”验证设计。验证完成后弹出对话框提示“Validation Successful”表明设计无误,点击“OK”确认。最后按快捷键“Ctrl + S”保存设计。
接下来在Source窗口中右键点击Block Design设计文件“system.bd”,然后依次执行“Generate Output Products”和“Create HDL Wrapper”。
添加约束文件:打开system_wrapper.xdc文件删除之前的约束文件,添加如下管脚约束:
create_clock -period 20.000 -name sys_clk [get_ports sys_clk]
- set_property PACKAGE_PIN R4 [get_ports sys_clk]
- set_property IOSTANDARD LVCMOS33 [get_ports sys_clk]
- set_property IOSTANDARD LVCMOS33 [get_ports sys_rst_n]
- set_property IOSTANDARD LVCMOS33 [get_ports UART_rxd]
- set_property IOSTANDARD LVCMOS33 [get_ports UART_txd]
- set_property IOSTANDARD LVCMOS33 [get_ports {qspi_flash_ss_io[0]}]
- set_property IOSTANDARD LVCMOS33 [get_ports qspi_flash_io0_io]
- set_property IOSTANDARD LVCMOS33 [get_ports qspi_flash_io1_io]
- set_property IOSTANDARD LVCMOS33 [get_ports qspi_flash_io2_io]
- set_property IOSTANDARD LVCMOS33 [get_ports qspi_flash_io3_io]
- set_property IOSTANDARD LVCMOS33 [get_ports {key_init[0]}]
- set_property IOSTANDARD LVCMOS33 [get_ports compare_done]
- set_property IOSTANDARD LVCMOS33 [get_ports error_flag]
- set_property PACKAGE_PIN T19 [get_ports {qspi_flash_ss_io[0]}]
- set_property PACKAGE_PIN P22 [get_ports qspi_flash_io0_io]
- set_property PACKAGE_PIN R22 [get_ports qspi_flash_io1_io]
- set_property PACKAGE_PIN P21 [get_ports qspi_flash_io2_io]
- set_property PACKAGE_PIN R21 [get_ports qspi_flash_io3_io]
- set_property PACKAGE_PIN T1 [get_ports {key_init[0]}]
- set_property PACKAGE_PIN U5 [get_ports UART_rxd]
- set_property PACKAGE_PIN T6 [get_ports UART_txd]
- set_property PACKAGE_PIN U2 [get_ports sys_rst_n]
- set_property PACKAGE_PIN R2 [get_ports compare_done]
- set_property PACKAGE_PIN R3 [get_ports error_flag]
- set_property CFGBVS VCCO [current_design]
- set_property CONFIG_VOLTAGE 3.3 [current_design]
- set_property BITSTREAM.GENERAL.COMPRESS true [current_design]
- set_property BITSTREAM.CONFIG.CONFIGRATE 50 [current_design]
- set_property BITSTREAM.CONFIG.SPI_BUSWIDTH 4 [current_design]
- set_property BITSTREAM.CONFIG.SPI_FALL_EDGE Yes [current_design]
管脚分配完成后按快捷键“Ctrl+S”保存管脚约束。
最后在左侧Flow Navigator导航栏中找到PROGRAM AND DEBUG,点击该选项中的“Generate Bitstream”,对设计进行综合、实现、并生成Bit(Bitstream)文件。
在生成Bitstream之后,在菜单栏中依次点击“File->Export->Export hardware”导出硬件,并在弹出的对话框中,勾选“Include bitstream”,在导出路径最后添加“/vitis”。如图 9.3.29所示:
图 9.3.29 设置硬件导出地址
然后在菜单栏依次点击“Tools->Launch Vitis”,启动vitis软件。
9.4软件设计
在Vitis软件中新建一个BSP工程和一个空的应用工程,应用工程名为“axi4_ddr_rw”。然后为应用工程新建一个源文件“main.c”,我们在新建的main.c文件中输入本次实验的代码。代码如下所示:
- 1 #include <stdio.h>
- 2 #include "xil_cache.h"
- 3 #include "xil_printf.h"
- 4 #include "xil_io.h"
- 5
- 6 int main()
- 7 {
- 8 int i;
- 9 char c;
- 10
- 11 Xil_DCacheDisable();
- 12 print("AXI4 DDR TEST!nr");
- 13
- 14 while(1){
- 15 scanf("%c",&c);
- 16 if(c=='c'){
- 17 printf("startnr");
- 18 for(i=0;i<4096;i=i+4){
- 19 printf("%d is %dn",i,(int)Xil_In32(0x88000000+i));
- 20 }
- 21 }
- 22 }
- 23
- 24 return 0;
- 25 }
可以看出,我们的软件程序特别简单。在代码的第14行至22行,通过一个while(1)死循环,连续判断用户输入的字符。当输入字符“c”时,程序通过一个for循环开始从地址0x8800_0000读取DDR3存储器中的数据,读取的存储空间大小为4KB。需要注意的是,变量i每次累加4,这是因为我们调用了函数Xil_In32( )来读取内存数据,每次读取32bit。而内存地址是以字节(1字节==8bit)为单位的,那么操作完成后地址应该累加4。
可以看出,我们在软件中读取的内存地址与硬件设计过程中DDR_TEST IP核所写入的地址是一致的。我们将软件读出的数据通过串口打印出来,与DDR_TEST IP核写入的数据进行对比,即可验证我们通过AXI4接口对DDR进行的读写操作是否成功。
另外,在代码的第11行,我们通过调用函数Xil_DCacheDisable( )来关闭数据缓存(Data Cache),以避免从缓存中读取数据。这是因为在对同一地址进行读操作时,读出的有可能是Data Cache中缓存的数据,而不是DDR中真正的数据。
程序设计完成后,按快捷键Ctrl+S保存main.c文件,接着编译工程。编译完成后控制台(Console)中会出现提示信息“Build Finished”,同时在应用工程的Binaries目录下可以看到生成的elf文件。
9.5下载验证和固化
下载验证
首先我们将下载器与达芬奇开发板上的JTAG接口连接,下载器另外一端与电脑连接。然后使用USB连接线将开发板左侧的USB_UART接口与电脑连接,用于串口通信。最后连接开发板的电源,并打开电源开关。
在Vitis软件中设置并连接串口。然后下载本次实验硬件设计过程中所生成的bit文件。最后下载软件程序,下载完成后,在右下方的Terminal中可以看到应用程序打印的信息“AXI4 DDR TEST!”。
图 9.5.1 程序下载完成
然后点击Terminal窗口空白处,Terminal窗口就会出现一个黑色的光标,此时键盘输入字符“c”。程序会打印从DDR3中读出的数据,如图 9.5.2所示:
图 9.5.2 第一次从DDR3中读出的数据
如图 9.5.2所示,串口打印出了DDR3存储空间中从地址0x8800_0000开始的1024个数据,每个数据占4个字节。从图中可以看出,开发板上电后,在没对该地址空间进行写操作之前,DDR3中的数据是随机的。
按下开发板上的按键KEY0然后释放,该动作会启动DDR3_TEST IP核对DDR3的读写操作。然后开发板上的LED0会点亮,表示读写操作完成。如图 9.5.3所示:
图 9.5.3 达芬奇开发板实物图
在图 9.5.3中,如果LED1也点亮了,这表明在对DDR3进行读写操作过程中出现了错误。但这个错误指示灯点亮的原因并不唯一,通过分析DDR3_TEST IP核的源码可以看出,在AXI4通信过程中写响应出错、读响应出错、以及读出与写入的数据不一致均会导致错误指示灯点亮。
因此,要判断我们自定义的IP核究竟有没有成功地向DDR3指定地址中写入数据,还是要通过查看该内存空间中的数据来判断。
在Terminal窗口中重新输入字符“c”并发送,程序会再次打印从DDR3中读出的数据,如下图所示:
图 9.5.4 第二次从DDR3中读出的数据
从图 9.5.4中可以看出,从DDR3中指定的4KB存储空间中读出的数据依次为1到1024,与DDR3_TEST IP核写入的数据一致,这时再按下KEY0然后释放,只有LED0亮。说明本次实验在达芬奇开发板上面下载验证成功。
程序固化
在《AXI GPIO控制LED实验》中,固化时,将硬件设计生成的bit流文件和软件应用程序合并成一个download.bit文件,然后再将download.bit文件写入QSPI Flash芯片中。Flash芯片内的数据在断电之后不会丢失的,再次上电后,QSPI Flash内的数据被写入BRAM中,程序就会再次执行。
BRAM为FPGA内部的资源,程序写入后无需引导即可运行,然而,有些程序是需要引导的,如本章实验DDR3读写程序,该程序占用空间资源较大,不宜在BRAM内运行,所以将该程序放在DDR RAM中运行。DDR RAM是FPGA的外部资源而非内部资源,所以程序想要在DDR RAM中运行程序就必须先进行引导,本章实验采用Bootloader引导程序对其进行引导。
Bootloader:简单地说,Bootloader就是在应用程序运行之前,运行的一段小程序。系统在上电或复位时通常都从地址0x00000000开始执行,这个地址安排的通常就是系统的Bootloader程序。通过这段小程序,我们可以初始化硬件设备,建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为调用应用程序准备好正确的环境。
本章实验中,先将硬件配置文件system_wrapper.bit及Bootloader引导程序文件bootloader.elf合并为download.bit文件,然后将download.bit文件和用户的应用程序axi4_ddr_rw.elf文件固化到QSPI Flash中。板子断电再次上电后进行加载,FPGA自动从QSPI Flash中读取download.bit文件,将硬件配置和引导程序信息配置到片内BRAM中。加载后进行引导,FPGA内部逻辑启动,运行Bootloader引导程序读取QSPI Flash中的用户应用程序,并写到外部DDR3的相应位置。引导完成后,Bootloader程序切换指针到用户应用程序开始运行的指定位置,在外部的DDR3中开始执行用户的应用程序。
接下来我们就开始采用Bootloader的引导方式进行程序的固化。
设置板级支持包信息
按图 9.5.5指示依次点击,最后点击“Modify BSP Setting”。
图 9.5.5 设置板级支持包信息
选中“xilisf”,即Flash库(如果不选则无法固化QSPI Flash),如图 9.5.6所示:
图 9.5.6 选中xilisf
点击左上角的“xilisf”,在serial_flash_family将原先的值“1”改为“5”(板载Flash芯片MT41K128M16XX-15E属于镁光,对应值为5)。如图 9.5.7所示:
图 9.5.7 选择串行flash对应值
右键system_wraper,点击“Build Project”,编译工程。
建立BootLoader工程
新建工程,工程名设置为“bootloader”,点击“Next”。如图 9.5.8所示:
图 9.5.8 新建工程bootloader
选择当前的硬件平台,点击“Next”。如图 9.5.9所示:
图 9.5.9 选择硬件平台
继续点击“Next”,选择模板“SERC SPI Bootloader”,点击“Finish”。如图 9.5.10所示:
图 9.5.10 选择SERC SPI Bootloader
点击修改blconfig.h文件,将MicroBlaze软件文件在Flash中的加载点修改为“0x00800000”:Flash前面的8MB空间用来存储Bitstream文件,从地址0x00800000开始保存软件,如图 9.5.11所示。(这里设置加载点的值可以根据比特流文件的大小进行调整)。
图 9.5.11 修改软件存储位置
右键bootloader_system,点击“Build Project”,编译工程,生成引导程序bootloader.elf文件。
生成download.bit文件
点击菜单栏“Xilinx”,选择“Program FPGA”,将硬件设计生成的bit文件和bootloader.elf文件合成为一个download.bit文件,用于烧录到QSPI Flash芯片中。如图 9.5.12所示:
图 9.5.12 Program FPGA
点击“microblaze_0”,选择编译生成的bootloader.elf文件,路径G:vitis_proaxi4_ddr_rwvitisbootloaderDebug。点击“Program”,生成download.bit文件。
图 9.5.13 加载bootloader.elf
download.bit文件路径为G:vitis_proaxi4_ddr_rwvitisbootloader_idebitstreamdownload.bit。
生成download.bit文件后,点击菜单栏Xilinx,选择“Program Flash”。如图 9.5.14所示:
图 9.5.14 Program Flash
在Project Type栏,选中Application。在Image File栏,点击“Browse…”,选择上一步生成的download.bit文件,选择器件“mt25ql128-spi_x1_x2_x4”,勾选“Verify after flash”,点击“Program”。如图 9.5.15所示:
图 9.5.15 烧录download.bit文件
烧录download.bit成功,现象如图 9.5.16所示:
图 9.5.16 烧录download.bit成功
烧写应用程序
再次点击菜单栏“Xilinx”,选择“Program Flash”。
将用户的应用程序文件axi4_ddr_rw.elf烧写到地址“0x00800000”,elf路径G:vitis_proaxi4_ddr_rwvitisaxi4_ddr_rwDebug。选择器件“mt25ql128-spi_x1_x2_x4”,勾选“Convert ELF to bootloadable SREC format and program”和“Verify after flash”,点击“Program”。如图 9.5.17所示:
图 9.5.17 烧录axi4_ddr_rw.elf
烧录应用程序成功,现象如图 9.5.18所示:
图 9.5.18 烧录应用程序成功
关掉电源,拔出JTAG连接线,重新上电,板子自动运行。
重新连接Terminal,等待引导完毕,引导过程耗费的时间与用户程序的大小成正比(优化技巧详见文章末尾)。程序引导完毕,如下图所示:
图 9.5.19 用户程序引导完成
按下按键,LED0亮;输入字符“c”,Terminal中会打印值1到1024;说明本实验的程序固化成功,如下图所示:
图 9.5.20 LED0亮
优化技巧:如果用户程序比较大,图 9.5.19中的引导时间会相应变长,我们可以使用“//”号屏蔽VERBOSE宏定义,省去不必要的打印信息,进而缩短引导时间。如下图所示:
图 9.5.21 屏蔽宏