ZYNQ例程搭建 + PS-PL数据传递
实验环境:Win10-64bit,Vivado + Xilinx SDK 2019.1,硬件平台非官方开发板,板上器件包含:ZYNQ7020,DDR3 SDRAM 4Gbit两颗,RTL8211E千兆PHY芯片等。
主要任务:使用Xilinx的LwIP Echo例程工程,在开发板上部署TCP/IP服务器,上位机向板子发送字符串消息,板子完成消息回显,并且将消息转存到PL中的BRAM,我们自定义的Verilog Module可以读出该段消息。
实验中的部件:
先整理一下ZYNQ器件在Vivado和Xilinx SDK环境中的开发流程:
注意:每次改动Block Design并综合布线生成比特流后,必须将生成的.bit和.hdf导入到SDK工作目录下,并且在HDF工程和BSP工程中设定好对应的文件。
比如这样的目录结构:(图中只标出了关键文件)
在HDF工程中,需要设定好.hdf文件和.bit文件(保持文件路径、名称正确和文件版本更新)。
在BSP工程中,需要设定好程序固件需要用到的外设库、库与组件的基本配置。(在.mss页面中使用Modify进行修改,或在Project Explorer中右键修改)
在C/C++ Application工程中,进行自定义应用的开发。
//********************************************************************************************************************************
//********************************************************************************************************************************
//********************************************************************************************************************************
一、设计Block Design。
Block Design中的核心模块有:PS7、Block RAM和BRAM Reader。其中PS7即ZYNQ7 Processing System,Block RAM是由Block Memory Generator例化的存储单元,BRAM Reader是Verilog Module进行IP封装打包产生的模块。
1、PS7参数配置。
启用ETH0外设(根据硬件连接,直接用MIO引脚),同时开启其MDIO接口(同样用MIO引脚),将速率调整为1000Mbps。
开启UART0外设,使用EMIO引脚(UART0的MIO引脚没有在板子上引出)。
开启PS7的AXI GP0 Master Port。(PS7通过AXI GP接口对BRAM进行操作)
ARM Core频率配置为200MHz,DDR频率配置为200MHz。
PL Fabric Clock配置为100MHz。
2、Block RAM。
BRAM不能直接与PS7相连,需要用AXI BRAM Controller进行转换。AXI读写接口规范、时序和BRAM的不兼容,所以用控制器进行转接。
BRAM Controller选用AXI4规范,位宽32位,不用纠错码(ECC)。它将驱动BRAM的Port.A。
BRAM驱动方式选择BRAM Controller模式,配置为True Dual Port RAM。RAM的存储宽度为32bit,存储深度/容量在Address Editor中(Addr Map环节)进行调整。
3、BRAM Reader。
这是一个Verilog Module打包成的模块,它驱动BRAM的Port.B。它按照0~20的地址顺序对Port.B进行读操作。
4、AXI互联模块(Smart Connect,相比与AXI InnerConnect它只面向存储接口)。
这个模块相当于是AXI总线的开关矩阵,它决定了AXI Master将会驱动哪些AXI Slave,同时决定了AXI Slave的驱动源。
5、ILA例化。
ILA在Block Design中需要指定探头端口(Probe)数目,而端口的数据位宽可以设定成自动调整。
6、系统复位。
PS7向可以芯片内其他部分发送全局复位信号,这样可以使逻辑进入正常的初状态。
7、反相器(非门)。
各个模块复位信号的极性不同,在PS7复位输出加反相器就可以得到高有效和低有效的复位信号(也可以在模块的复位输入端加反相器)。
说明:Block Design编辑完成后,需要生成对应的HDL Wrapper,而后进行综合、布线,这样就生成了.bit文件和.hdf文件。这两个文件进行导出,为SDK提供底层支持。
二、SDK工程设定。
1、在创建Application Project时,需要指定HDF和BSP,如图:(这里我们选择Vivado Launch SDK自动建立的HDF工程,选择创建新的BSP工程)
2、使用一个Xilinx例程:lwIP Echo Server。
这个例程默认将板子地址设定为192.168.1.10(或IPv6地址FE80:0:0:0:20A:35FF:FE00:102),MAC地址设定为00:0a:35:00:01:02。例程中会建立一个lwIP服务器,在服务器端侦听端口7收到的数据,并将其从这个端口转发回去。
3、更改BSP设定。
选择stdin和stdout为ps7_uart_0,这样程序就能驱动UART0进行串口打印消息。
根据实际需要,我们选择取消DHCP功能。(实际环境中没有给板子分配一个DHCP服务,所以取消以减小TCP连接前的物理层初始化时间)
注意:更改BSP设定更改后需要进行Re-generate BSP Sources操作。从而更新.h和.c中的工作参数。
三、程序说明和实现。
程序中应用LwIP协议搭建TCP服务器,建立连接后可以对客户端发来的消息进行回显,并将消息存到BRAM中。(回显是例程自带功能,存BRAM是我们自定义功能)
程序中指定了回调函数,这个概念很迷,还不懂。只了解它的运行是和一个“母函数”相关联的。
1、消息回显。
在recv_callback回调函数中,有这样的语句:
err = tcp_write(tpcb, p->payload, p->len, 1);
这个语句将当前的payload写到了发送区,关于TCP协议后面如何处理,这里暂且搁置不谈。
2、存BRAM并在PL端读出。
在上述的回调函数里面,对payload进行转存:
//*** AXI Operation flush_str_buf(top_buf, N_BUF); update_buf(top_buf, (char*)p->payload, p->len); // Load Received String to char String N_PLD_BYTE = p->len; N_LOAD = N_PLD_BYTE / 4; N_REM = N_PLD_BYTE % 4; axi_addr_offset = 0; for(ite=0; ite<N_LOAD; ite+=1) { for(u8 ite_ch_temp=0; ite_ch_temp<N_BYTE_PER_U32; ite_ch_temp+=1) // Get the 4 Byte { ch_temp[ite_ch_temp] = top_buf[ite*N_BYTE_PER_U32 + ite_ch_temp]; } axi_data_temp = (u32)0; // Clear axi_data_temp ^= ch_temp[0]; axi_data_temp = axi_data_temp << 8; axi_data_temp ^= ch_temp[1]; axi_data_temp = axi_data_temp << 8; axi_data_temp ^= ch_temp[2]; axi_data_temp = axi_data_temp << 8; axi_data_temp ^= ch_temp[3]; axi_addr = XPAR_BRAM_0_BASEADDR + axi_addr_offset; // Xil_Out32(axi_addr, axi_data_temp); XBram_Out32(axi_addr, axi_data_temp); axi_addr_offset = axi_addr_offset + 4; } u32 base_byte = ite << 2; axi_data_temp = (u32)0; // Clear for(ite=0; ite<N_REM; ite+=1) { axi_data_temp ^= top_buf[base_byte + ite]; if(ite < (N_REM-1)) { axi_data_temp = axi_data_temp << 8; } } axi_addr = XPAR_BRAM_0_BASEADDR + axi_addr_offset; // Xil_Out32(axi_addr, axi_data_temp); XBram_Out32(axi_addr, axi_data_temp); axi_addr_offset = axi_addr_offset + 4;
上述程序完成了载荷的备份,并将载荷存入BRAM。
完成转存函数是
XBram_Out32(axi_addr, axi_data_temp);
它也是xil_io.h中的写内存函数:
#define XBram_Out32 Xil_Out32
Xil_Out32()函数定义如下:
/*****************************************************************************/ /** * * @brief Performs an output operation for a memory location by writing the * 32 bit Value to the the specified address. * * @param Addr contains the address to perform the output operation * @param Value contains the 32 bit Value to be written at the specified * address. * * @return None. * ******************************************************************************/ static INLINE void Xil_Out32(UINTPTR Addr, u32 Value) { #ifndef ENABLE_SAFETY volatile u32 *LocalAddr = (volatile u32 *)Addr; *LocalAddr = Value; #else XStl_RegUpdate(Addr, Value); #endif }
四、实验结果。
1、命令行发送信息:GOOD MORNING MY NEIGHBOR
键入消息的下一行得到回显结果。
2、处理器内存中的BRAM的存储单元:(消息以ASCII保存)
3、bram_Reader读到的值:(图糊了)