Zynq——PL_BRAM_PS数据传输

前言

开发环境:vivado 2020.2 vitis windows10 黑金开发板

基于板厂给的模板,修改文件,实现以下功能:

PS端向PL端发送数据(写至BRAM),PL端对数据进行处理,处理后将数据写回BRAM,接着PS端读取BRAM并通过串口发送至win10,win10下使用串口接收数据并利用python导出数据保存。

1.原板厂例程

https://download.csdn.net/download/wxkhturfun/77297149
内容包含vivado工程、vitis工程、说明文档

2.PL端

PL端的vivado工程没什么好讲的,厂家自己封了一个IP,这个IP自己可以改:

    case(state)
	IDLE            : begin
			            if (start)
						begin
			              state <= READ_RAM     ;
						  addr  <= start_addr   ;
						  start_addr_tmp <= start_addr ;
						  len_tmp <= len ;
						  dout <= init_data ;
						  en    <= 1'b1 ;
						  start_clr <= 1'b1 ;
						end			
						if (intr_clr)
							intr <= 1'b0 ;
			          end

    
    READ_RAM        : begin
	                    if ((addr - start_addr_tmp) == len_tmp - 4)      //read completed
						begin
						  state <= READ_END ;
						  en    <= 1'b0     ;
						end
						else
						begin
						  addr <= addr + 32'd4 ;				  //address is byte based, for 32bit data width, adding 4		  
						end
						start_clr <= 1'b0 ;
					  end
					  
    READ_END        : begin
	                    addr  <= start_addr_tmp ;
	                    en <= 1'b1 ;
                        we <= 4'hf ;
					    state <= WRITE_RAM  ;					    
					  end
    
	WRITE_RAM       : begin
	                    if ((addr - start_addr_tmp) == len_tmp - 4)   //write completed
						begin
						  state <= WRITE_END ;
						  dout  <= 32'd0 ;
						  en    <= 1'b0  ;
						  we    <= 4'd0  ;
						end
						else
						begin
						  addr <= addr + 32'd4 ;
						  dout <= dout + 32'd1 ;						  
						end
					  end
					  
	WRITE_END       : begin
	                    addr <= 32'd0 ;
						intr <= 1'b1 ;
					    state <= IDLE ;					    
					  end	
	default         : state <= IDLE ;
	endcase
  end
end	

intr是中断,它与Zynq的中断信号相连,这里的READ_RAM是指PS端向BRAM写数据,WRITE_RAM是指PL端向BRAM写数据,read write是相对于PL端来讲的,总体控制代码很简单。synthesis->implementation->generate Bitstream
生成Bit流后,将硬件信息进行Export,具体操作如下:
Zynq——PL_BRAM_PS数据传输
如果只用到PS端的话,只用pre-synthesis即可,这里用到了PL端,还是要include bitstream的
Zynq——PL_BRAM_PS数据传输
最后会生成一个 .xsa文件,用解压软件打开会发现如下内容,这个东西后续要交给vitis开发,即软件工程师的内容。
Zynq——PL_BRAM_PS数据传输

3. Vitis

2018之后的vivado已经没有SDK这个选项了,所以放弃吧别找了,Xilinx把他给集成到Vitis里了。

之后按下列图选择(未出现的图自己默认起个文件名或直接点击next)
Zynq——PL_BRAM_PS数据传输
Zynq——PL_BRAM_PS数据传输
最后会生成一个Helloworld工程,自己可以基于这个模板写对就的PS端操作,但是BRAM的相关操作什么的一定要和vivado的地址等相关信息一致。
Zynq——PL_BRAM_PS数据传输
把helloworld.c进行魔改,改成板厂的提供的文件,在此基础上在进行些修订,方便后续 python串口处理

#include "xil_printf.h"
#include "xil_printf.h"
#include "xbram.h"
#include <stdio.h>
#include "pl_bram_ctrl.h"
#include "xscugic.h"

#define BRAM_CTRL_BASE      XPAR_AXI_BRAM_CTRL_0_S_AXI_BASEADDR
#define BRAM_CTRL_HIGH      XPAR_AXI_BRAM_CTRL_0_S_AXI_HIGHADDR
#define PL_RAM_BASE         XPAR_PL_BRAM_CTRL_1_S00_AXI_BASEADDR
#define PL_RAM_CTRL         PL_BRAM_CTRL_S00_AXI_SLV_REG0_OFFSET
#define PL_RAM_INIT_DATA    PL_BRAM_CTRL_S00_AXI_SLV_REG1_OFFSET
#define PL_RAM_LEN          PL_BRAM_CTRL_S00_AXI_SLV_REG2_OFFSET
#define PL_RAM_ST_ADDR      PL_BRAM_CTRL_S00_AXI_SLV_REG3_OFFSET

#define START_MASK   0x00000001
#define INTRCLR_MASK 0x00000002


#define INTC_DEVICE_ID	    XPAR_SCUGIC_SINGLE_DEVICE_ID
#define INTR_ID             XPAR_FABRIC_PL_BRAM_CTRL_1_INTR_INTR

#define TEST_START_VAL      0xC
/*
 * BRAM bytes number
 */
#define BRAM_BYTENUM        4


XScuGic INTCInst;

int Len  ;
int Start_Addr ;
int Intr_flag ;
/*
 * Function declaration
 */
int bram_read_write() ;
int IntrInitFuntion(u16 DeviceId);
void IntrHandler(void *InstancePtr);

int main()
{

	int Status;
	Intr_flag = 1 ;

    IntrInitFuntion(INTC_DEVICE_ID) ;

	while(1)
	{
		if (Intr_flag)
		{
			Intr_flag = 0 ;
			printf("Please provide start address\t\n") ;
			//scanf("%d", &Start_Addr) ;
			Start_Addr = 0;
			printf("Start address is %d\t\n", Start_Addr) ;
			printf("Please provide length\t\n") ;
			//scanf("%d", &Len) ;
			Len = 100;
			printf("Length is %d\t\n", Len) ;
			Status = bram_read_write() ;
			if (Status != XST_SUCCESS)
			{
				xil_printf("Bram Test Failed!\r\n") ;
				xil_printf("******************************************\r\n");
				Intr_flag = 1 ;
			}
		}
	}
}


int bram_read_write()
{

	u32 Write_Data = TEST_START_VAL ;
	int i ;

	/*
	 * if exceed BRAM address range, assert error
	 */
	if ((Start_Addr + Len) > (BRAM_CTRL_HIGH - BRAM_CTRL_BASE + 1)/4)
	{
		xil_printf("******************************************\r\n");
		xil_printf("Error! Exceed Bram Control Address Range!\r\n");
		return XST_FAILURE ;
	}
	/*
	 * Write data to BRAM
	 */
	for(i = BRAM_BYTENUM*Start_Addr ; i < BRAM_BYTENUM*(Start_Addr + Len) ; i += BRAM_BYTENUM)
	{
		XBram_WriteReg(XPAR_BRAM_0_BASEADDR, i , Write_Data) ;
		Write_Data += 1 ;
	}
	//Set ram read and write length
	PL_BRAM_CTRL_mWriteReg(PL_RAM_BASE, PL_RAM_LEN , BRAM_BYTENUM*Len) ;
	//Set ram start address
	PL_BRAM_CTRL_mWriteReg(PL_RAM_BASE, PL_RAM_ST_ADDR , BRAM_BYTENUM*Start_Addr) ;
	//Set pl initial data
	//PL_BRAM_CTRL_mWriteReg(PL_RAM_BASE, PL_RAM_INIT_DATA , (Start_Addr+9)) ;
	PL_BRAM_CTRL_mWriteReg(PL_RAM_BASE, PL_RAM_INIT_DATA , (Start_Addr)) ;
	//Set ram start signal
	PL_BRAM_CTRL_mWriteReg(PL_RAM_BASE, PL_RAM_CTRL , START_MASK) ;

	return XST_SUCCESS ;
}



int IntrInitFuntion(u16 DeviceId)
{
	XScuGic_Config *IntcConfig;
	int Status ;


	//check device id
	IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
	//intialization
	Status = XScuGic_CfgInitialize(&INTCInst, IntcConfig, IntcConfig->CpuBaseAddress) ;
	if (Status != XST_SUCCESS)
		return XST_FAILURE ;


	XScuGic_SetPriorityTriggerType(&INTCInst, INTR_ID,
			0xA0, 0x3);

	Status = XScuGic_Connect(&INTCInst, INTR_ID,
			(Xil_ExceptionHandler)IntrHandler,
			(void *)NULL) ;
	if (Status != XST_SUCCESS)
		return XST_FAILURE ;

	XScuGic_Enable(&INTCInst, INTR_ID) ;


	Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
			(Xil_ExceptionHandler)XScuGic_InterruptHandler,
			&INTCInst);
	Xil_ExceptionEnable();


	return XST_SUCCESS ;

}


void IntrHandler(void *CallbackRef)
{
	int Read_Data ;

	int i ;
	printf("Enter interrupt\t\n");
	//clear interrupt status
	PL_BRAM_CTRL_mWriteReg(PL_RAM_BASE, PL_RAM_CTRL , INTRCLR_MASK) ;

	for(i = BRAM_BYTENUM*Start_Addr ; i < BRAM_BYTENUM*(Start_Addr + Len) ; i += BRAM_BYTENUM)
	{
		Read_Data = XBram_ReadReg(XPAR_BRAM_0_BASEADDR , i) ;
		printf("Address:@%d$%d@over\t\n",  i/BRAM_BYTENUM ,Read_Data) ;
	}
	printf("^exit^\n");
	Intr_flag = 1 ;
}

代码很简单,一次PS写BRAM、一次PL写BRAM,PL写完后引发Zynq中断。

4. win10端接收数据

不需要数据导出的,直接在putty里看就行了:
安装驱动
Zynq——PL_BRAM_PS数据传输
Zynq——PL_BRAM_PS数据传输
Zynq——PL_BRAM_PS数据传输
将串口打开后,vitis将工程烧进FPGA(烧前,先编译)
Zynq——PL_BRAM_PS数据传输
以下是Helloworld
Zynq——PL_BRAM_PS数据传输

5. 数据导出

好吧,我需要将串口的数据导出到

环境:python 3.8.10 64-bits
pip3 install pyserial
我不是软件工程师,python写的很菜,好歹最后能用了。

因为接收到的是二进制数据,所以需要先进行utf-8转码,最后我要的16进制数据,所以还需要转一下(当然你也可以在printf中直接发送16进制数),在vitis中我在PL端最后一次写BRAM之后还printf("^ exit^\n");,我也是根据是否接收到exit来决定是否跳出While循环.
因为串口接收的数据可能是跨行的,或者说不连续的,我无法确定是数据的相关连的,所以只好先将接收的数据通过:dataline = dataline + data全部拼接到一起,最后才处理,由于需要对字符进行split操作,为了方便起见,我在vitis工程的的printf函数中对数据的两端进行添加了若干符号:

printf("Address:@%d$%d@over\t\n",  i/BRAM_BYTENUM ,Read_Data) ;//@与$就是我用来spilt的符号

以下是python3代码(由于各种原因,只能展示部分代码),并不难写,框架和思路我都写好的,自己处理导出数据就行了。

#此处略去部分代码

if __name__ == '__main__'
#此处略去部分代码
    #COM3
    serial = serial.Serial('COM3', 115200)#serial = serial.Serial('COM3', 115200, timeout=0.01)  
    print(serial)
    if serial.isOpen() :
        print("open success")
    else :
        print("open failed")
    ex = False
    while True:
        data =recv(serial)
        if  data != b'' :
                dataline = dataline + data
                print("receive : ",data)
                #print("receive : ",data.decode('utf-8'))
                w_data = b'12\r\n'
                #serial.write(w_data) #数据写回
                f_data = data.decode('utf-8')
                f_data = f_data.strip('\n')  #去掉列表中每一个元素的换行符
                f_p_split = f_data.split('^')   #按'@'进行切片
                #此处略去部分代码          
    result = splitData(dataline,len)
	print(result)
	#此处略去部分代码
上一篇:题解-CF1616G


下一篇:Leetcode-15-三数之和为0