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读到的值:(图糊了)

 

 

posted @ 2020-05-29 16:28  ygpygp1234  阅读(6308)  评论(0编辑  收藏  举报