實驗環境: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讀到的值:(圖糊了)