当前位置: 首页 > news >正文

ZYNQ-PS与PL端BRAM数据交互

        在 ZYNQ SOC 开发过程中,PL 和 PS 之间经常需要做数据交互,对于传输速度要求较高、数据量大、地址连续的场合,可以通过 AXI DMA 来完成。而对于数据量较少、地址不连续、长度不规则的情况,则通过AXI4_LITE协议进行交互,通过生成一个带有AXI4-Lite接口的IP核,实现PS和PL的数据通信,即可以把不同类型的数据从PS传给PL,也可以从PL传给PS。

任务: PS 将串口接收到的数据写入 PL端BRAM,然后从 BRAM 中读出数据,并通过串口打印
出来;与此同时,PL 从 BRAM 中同样读出数据,并通过 ILA 来观察读出的数据与串口打印的数据是否一致。

一、硬件设计:

上图中:PS 端的 M_AXI_GP0 作为主端口,与 PL 端的 AXI BRAM 控制器 IP 核和 PL 读 BRAM IP 核(pl_bram_rd)通过 AXI4 总线进行连接。其中,AXI 互联 IP(AXI Interconnect)用于连接 AXI 存储器映射(memory-mapped)的主器件和从器件;AXI BRAM 控制器作为 PS 端读写 BRAM 的 IP 核;PL 读BRAM IP 核是自定义的 IP 核,实现了 PL 端从 BRAM 中读出数据的功能,除此之外,PS 端通过 AXI总线来配置该 IP 核读取 BRAM 的起始地址和个数等。

 AXI BRAM Controller 是 Xilinx FPGA 和 Zynq SoC 中常用的 IP 核,用于通过 AXI 总线协议 访问 BRAM (Block RAM)。它允许 PS 端或 DMA 控制器通过标准 AXI 接口高效读写 BRAM,同时支持 PL(FPGA 逻辑)和 PS(处理器系统)之间的数据共享。

整个的数据回环如下:

PS端:除了基本的 DDR 和 UART 还使用到了 AXI 接口,因此如时钟、复位、AXI接口 都需要保留和配置。

PL 端:存储数据的 BRAM 的 IP 核、实现对 BRAM 写入数据的 AXI BRAM 控制器 IP 核、读取 BRAM IP 核数据的自定义的 IP 核(pl_bram_rd)。

具体详细配置可参考正点原子与XILINX官方例程,先看看效果:综合完成后set up debug需要观察的信号即PORTB的enb、addrb和doutb,PS端发送数据:xilinx

捕捉enb的上升沿(设为R值):

 可以看到读出来的数据和ILA抓出来的数据是一致的,说明符合任务说明。

 

二、上述是大部分厂家以及XILINX官方做的例程,但在学习过后发现其只是PS将数据读出来然后发送给PL后,PL并没有把数据读出来,而是做了个ILA抓取数据出来看。并没有实现PL将数据读出来过后然后再通过AXI总线将数据传输给PS端。

在网上阅读文章时学到了新的方法:可以给自定义PL端的IP核修改其功能,在bram_rd文件加入写的功能。同时给PL添加一个中断,使得PL数据给BRAM写好后触发中断给PS,PS捕获到该中断信号后开始读取BRAM的数据。ZYNQ—BRAM全双工PS_PL数据交互(开源)_zynq ps pl 数据传输-CSDN博客

实现捕获PS端输出的一个start脉冲,通过AXI-Lite接口得到start_addr起始地址和len数据长度,依次遍历后(保证数据的刷新),再进入后续状态机,将读数据暂存到reg变量,对读数据变量均+2,再写入到start_addr + len长度地址后的BRAM块,最终读写完成后,PL输出一个高电平脉冲intr触发PS中断。

记录一下过程:首先修改自定义IP核的三个.V文件:

pl_bram_rd_v1_0文件 :


`timescale 1 ns / 1 psmodule pl_bram_rd_v1_0 #( // Users to add parameters here// User parameters ends// Do not modify the parameters beyond this line// Parameters of Axi Slave Bus Interface S00_AXIparameter integer C_S00_AXI_DATA_WIDTH	= 32,parameter integer C_S00_AXI_ADDR_WIDTH	= 4)(// Users to add ports here//RAM端口input  wire [31:0]  din,	 output wire [31:0]  dout,output wire         en,output wire [3:0]   we,output wire [31:0]  addr,output wire         intr,         //interruptoutput wire         bramclk,output wire         bramrst_n,// User ports ends// Do not modify the ports beyond this line// Ports of Axi Slave Bus Interface S00_AXIinput wire  s00_axi_aclk,input wire  s00_axi_aresetn,input wire [C_S00_AXI_ADDR_WIDTH-1 : 0] s00_axi_awaddr,input wire [2 : 0] s00_axi_awprot,input wire  s00_axi_awvalid,output wire  s00_axi_awready,input wire [C_S00_AXI_DATA_WIDTH-1 : 0] s00_axi_wdata,input wire [(C_S00_AXI_DATA_WIDTH/8)-1 : 0] s00_axi_wstrb,input wire  s00_axi_wvalid,output wire  s00_axi_wready,output wire [1 : 0] s00_axi_bresp,output wire  s00_axi_bvalid,input wire  s00_axi_bready,input wire [C_S00_AXI_ADDR_WIDTH-1 : 0] s00_axi_araddr,input wire [2 : 0] s00_axi_arprot,input wire  s00_axi_arvalid,output wire  s00_axi_arready,output wire [C_S00_AXI_DATA_WIDTH-1 : 0] s00_axi_rdata,output wire [1 : 0] s00_axi_rresp,output wire  s00_axi_rvalid,input wire  s00_axi_rready);
// Instantiation of Axi Bus Interface S00_AXIpl_bram_rd_v1_0_S00_AXI # ( .C_S_AXI_DATA_WIDTH(C_S00_AXI_DATA_WIDTH),.C_S_AXI_ADDR_WIDTH(C_S00_AXI_ADDR_WIDTH)) pl_bram_rd_v1_0_S00_AXI_inst (//RAM端口   .din  (din),.en       (en     ),.addr     (addr   ),.we       (we     ),.dout  (dout),.bramclk(bramclk),.bramrst_n(bramrst_n),.intr(intr),       //start to read and write bram.S_AXI_ACLK(s00_axi_aclk),.S_AXI_ARESETN(s00_axi_aresetn),.S_AXI_AWADDR(s00_axi_awaddr),.S_AXI_AWPROT(s00_axi_awprot),.S_AXI_AWVALID(s00_axi_awvalid),.S_AXI_AWREADY(s00_axi_awready),.S_AXI_WDATA(s00_axi_wdata),.S_AXI_WSTRB(s00_axi_wstrb),.S_AXI_WVALID(s00_axi_wvalid),.S_AXI_WREADY(s00_axi_wready),.S_AXI_BRESP(s00_axi_bresp),.S_AXI_BVALID(s00_axi_bvalid),.S_AXI_BREADY(s00_axi_bready),.S_AXI_ARADDR(s00_axi_araddr),.S_AXI_ARPROT(s00_axi_arprot),.S_AXI_ARVALID(s00_axi_arvalid),.S_AXI_ARREADY(s00_axi_arready),.S_AXI_RDATA(s00_axi_rdata),.S_AXI_RRESP(s00_axi_rresp),.S_AXI_RVALID(s00_axi_rvalid),.S_AXI_RREADY(s00_axi_rready));// Add user logic here// User logic endsendmodule

pl_bram_rd_v1_0_S00_AXI修改接口和例化模块:

// Users to add ports here//bram portinput  wire    [31:0]  din,	 //写入BRAMoutput wire [31:0]  dout,//读出BRAMoutput wire         en,//BRAM使能output wire [3:0]   we,//写读选择output wire [31:0]  addr,//地址output wire         intr,         //interrupt输出给PS做中断output   wire           bramclk,//bram时钟output     wire         bramrst_n,//bram复位// Add user logic herebram_rd u_bram_rd(.clk          (S_AXI_ACLK),.rst_n        (S_AXI_ARESETN),.start        (slv_reg0[0]),//PS写完数据后输出的一个脉冲触发.init_data    (slv_reg1),//未用到.len          (slv_reg2),//PS写入数据的长度.start_addr   (slv_reg3),    //PS写BRAM的起始地址//RAM端口   .din  (din),.en       (en     ),.addr     (addr   ),.we       (we     ),.dout  (dout),.bramclk(bramclk),.bramrst_n(bramrst_n),//bram port	 //control signal.intr(intr)       //start to read and write bram);// User logic ends

 brambram_rd:

module bram_rd(input              clk,input              rst_n,//bram portinput      [31:0]  din,	 //readoutput reg [31:0]  dout,    //writeoutput reg         en,output reg [3:0]   we,output reg [31:0]  addr,   //RAM地址//control signalinput              start,       //start to read and write braminput      [31:0]  init_data,   //没有用到output reg         start_clr,   //没有用到input      [31:0]  len,         //data countinput      [31:0]  start_addr,   //start bram address//Interruptinput              intr_clr,    //clear interruptoutput reg         intr,         //interruptoutput              bramclk,output              bramrst_n);assign bramclk = clk ;
assign bramrst_n = 1'b0 ;localparam IDLE      = 4'd0 ;  //上电初始化
localparam READ_INIT      = 4'd1 ;   //每次循环读的初始化
localparam INIT      = 4'd2 ;   //每次循环的初始化
localparam READ_START      = 4'd3 ;  //准备读前的初始化
localparam READ_RAM  = 4'd4 ; //读
localparam READ_END  = 4'd5 ;//读结束
localparam WRITE_START  = 4'd6 ;//准备写的初始化
localparam WRITE_RAM = 4'd7 ; //写
localparam WRITE_END = 4'd8 ;//写结束
localparam END = 4'd9 ;//结束reg [3:0] state ;
reg [31:0] len_tmp ;
reg [31:0] start_addr_tmp ;
reg [31:0] start_addr_tmp2 ;
reg [31:0] read_data_temp;
reg [31:0] read_addr;
reg [31:0] write_addr;reg start_rd_d0;
reg start_rd_d1;
//wire define
wire pos_start_rd;
assign pos_start_rd = ~start_rd_d1 & start_rd_d0;
//延时两拍,采 start_rd 信号的上升沿  因为BRAM_B读取数据需要延迟两拍,即在PS写好数据,需要等一下才能读到RAM数据
always @(posedge clk or negedge rst_n) beginif(!rst_n) beginstart_rd_d0 <= 1'b0; start_rd_d1 <= 1'b0;endelse beginstart_rd_d0 <= start; start_rd_d1 <= start_rd_d0; endend//Main statement
always @(posedge clk or negedge rst_n)
beginif (!rst_n)beginstate      <= IDLE  ;dout       <= 32'd0 ;en         <= 1'b0  ;we         <= 4'd0  ;addr       <= 32'd0 ;intr       <= 1'b0  ;start_clr  <= 1'b0  ;len_tmp    <= 32'd0 ;						  end	elsebegincase(state)IDLE            : beginif (pos_start_rd)beginaddr<=start_addr;read_addr  <= start_addr;						  start_addr_tmp <= start_addr ;start_addr_tmp2<= start_addr+len ;  //从已有数据的后一位开始写write_addr<=start_addr+len ;  //从已有数据的后一位开始写len_tmp <= len ;	intr       <= 1'b0  ;  //读取到后取消触发state <= INIT     ; en    <= 1'b1;we   <= 4'd0;							  						  				  end			else begin state <= IDLE;intr  <= 1'b0;			              		en    <= 1'b0;addr  <= addr;we   <= 4'd0;						  						  			              				endendREAD_INIT        : beginif ((addr - start_addr_tmp) >= (len_tmp))   //当读取的遍历结束一遍beginstate <= INIT ;						  en    <= 1'b0  ;we    <= 4'd0  ;addr<=start_addr_tmp;  //获取读地址  提前两个周期read_addr<=start_addr_tmp; endelse beginstate <= READ_INIT;  //继续遍历addr<=read_addr;  //获取读地址 遍历read_addr<=read_addr+32'd4;read_data_temp<=din; 	end  endINIT        : beginstate <= READ_START     ;we    <= 4'b0000 ;en    <= 1'b1 ;  //先en1addr<=read_addr;  //获取读地址  提前两个周期//read_data_temp<=din; 	  end					  READ_START        : beginen    <= en;we    <= we;  //保持一个周期//read_data_temp<=din;state <= READ_RAM ;end  READ_RAM        : begin	 read_data_temp<=din;                   state <= READ_END  ;	                    					end					  READ_END        : beginread_addr<=read_addr+32'd4;en    <= 1'b0;		                                          state <= WRITE_START  ;					    end  WRITE_START        : beginen    <= 1'b1;we    <= 4'b1111;state <= WRITE_RAM  ;	addr  <= write_addr ;					     end							   	  					   WRITE_RAM       : beginif ((addr - start_addr_tmp2) >= (len_tmp))   //write completedbeginstate <= END ;						  en    <= 1'b0  ;we    <= 4'd0  ;endelsebegindout<=read_data_temp+32'd2; //到最后一位就不再写了  												  					  state <= WRITE_END ;endendWRITE_END       : beginwrite_addr  <= write_addr+32'd4 ;dout<=32'd0;addr<=read_addr;  //获取读地址  提前两个周期	en    <= 1'b0  ;we    <= 4'd0  ;state <= INIT ;					    endEND       : beginaddr <= 32'd0 ;dout  <= 32'd0; intr <= 1'b1 ;state <= IDLE ;					    end	default         : state <= IDLE ;endcaseend
end	
endmodule

block design参考正点原子的教程做修改,添加PL -> PS的中断:

添加一个EMIO,用 PL端做串口:

然后进行绑定管脚,用一个CH340的串口模块,通过连接GND、RX、TX后实现与电脑的通信 :

然后再生成bit流,并export,launch sdk。

sdk:

#include "xil_printf.h"
#include "xbram.h"
#include <stdio.h>
#include "pl_bram_rd.h"
#include "xscugic.h"#define BRAM_CTRL_BASE      XPAR_AXI_BRAM_CTRL_0_S_AXI_BASEADDR //BRAM 控制器的 基地址,表示 BRAM 控制器在 AXI 总线上的起始地址
#define BRAM_CTRL_HIGH      XPAR_AXI_BRAM_CTRL_0_S_AXI_HIGHADDR //BRAM 控制器的 最高地址,表示 BRAM 控制器在 AXI 总线上的结束地址。
#define PL_RAM_BASE         XPAR_PL_BRAM_RD_0_S00_AXI_BASEADDR	//PL_RAM_RD基地址
#define PL_RAM_CTRL         PL_BRAM_RD_S00_AXI_SLV_REG0_OFFSET  //RAM读开始寄存器地址
#define PL_RAM_INIT_DATA    PL_BRAM_RD_S00_AXI_SLV_REG1_OFFSET  //RAM起始寄存器地址(没用到)
#define PL_RAM_LEN          PL_BRAM_RD_S00_AXI_SLV_REG2_OFFSET	//PL读RAM的深度
#define PL_RAM_ST_ADDR      PL_BRAM_RD_S00_AXI_SLV_REG3_OFFSET	//data#define START_MASK   0x00000001   //b01  //表示启动信号的掩码
#define INTRCLR_MASK 0x00000002   //b10  //中断清除信号的掩码
#define INTC_DEVICE_ID	     XPAR_SCUGIC_SINGLE_DEVICE_ID        //用于标识PS端中断控制器(SCUGIC)的设备 ID
#define INTR_ID              XPAR_FABRIC_PL_BRAM_RD_0_INTR_INTR  //PL端的bram_rd模块产生的中断信号ID#define TEST_START_VAL      0x0
/** BRAM bytes number*/
#define BRAM_BYTENUM        4   //每个数据占的字节大小,一般默认用4字节即32bitXScuGic INTCInst;    //指向中断控制器SCUGIC实例的指针char ch_data[1024];            //写入BRAM的字符数组
int Len=10  ;//单次写入长度
int Start_Addr=0 ;//写地址起始位即偏移0
int Intr_flag ;
/** Function declaration*/
int bram_read_write() ;
int IntrInitFuntion(u16 DeviceId);   //中断初始化
void IntrHandler(void *InstancePtr); //响应处理来自PL端的中断信号,int main()
{int Status;Intr_flag = 1 ;IntrInitFuntion(INTC_DEVICE_ID) ;while(1){if (Intr_flag){Intr_flag = 0 ;Status = bram_read_write() ;if (Status != XST_SUCCESS){xil_printf("Bram Test Failed!\r\n") ;xil_printf("******************************************\r\n");Intr_flag = 1 ;}sleep(2);}}
}// 对BRAM的读写操作
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*/ //写地址长度0-9for(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 ;  //写0-9}printf("完成PS写入BRAM\t\n等待捕获PL写BRAM结束中断\t\n");//Set ram read and write lengthPL_BRAM_RD_mWriteReg(PL_RAM_BASE, PL_RAM_LEN , BRAM_BYTENUM*Len) ;//写寄存器,告诉PL数据长度//Set ram start addressPL_BRAM_RD_mWriteReg(PL_RAM_BASE, PL_RAM_ST_ADDR , BRAM_BYTENUM*Start_Addr) ;//写寄存器,告诉PL数据起始地址//Set pl initial data  没用到//PL_BRAM_RD_mWriteReg(PL_RAM_BASE, PL_RAM_INIT_DATA , (Start_Addr+1)) ;//Set ram start signalPL_BRAM_RD_mWriteReg(PL_RAM_BASE, PL_RAM_CTRL , START_MASK) ;  //输出高电平脉冲触发start:01return XST_SUCCESS ;
}int IntrInitFuntion(u16 DeviceId)//接收PL端的intr中断
{XScuGic_Config *IntcConfig;int Status ;//check device idIntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);//intializationStatus = 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 ;//启用PL端的中断响应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("捕获到PL写BRAM结束中断\t\n");//clear interrupt statusPL_BRAM_RD_mWriteReg(PL_RAM_BASE, PL_RAM_CTRL , INTRCLR_MASK) ;for(i = BRAM_BYTENUM*Start_Addr ; i < BRAM_BYTENUM*(Start_Addr + Len+15) ; i += BRAM_BYTENUM)  //len+10即可,只是多打几位,验证PL写的正确性{Read_Data = XBram_ReadReg(XPAR_BRAM_0_BASEADDR , i) ;printf("Address is %d\t Read data is %d\t\n",  i/BRAM_BYTENUM ,Read_Data) ;}Intr_flag = 1 ;
}

 效果:实现了PS写数据到BRAM的0-9位地址,触发PL读取,PL读取并+2分别写入到后面地址上(10-19位地址),触发PS中断读取。我觉得这才应该是PS --> PL\ PL --> PS的交互。

 

相关文章:

  • PortSwigger-03-点击劫持
  • 链路追踪神器zipkin安装详细教程教程
  • Redis击穿,穿透和雪崩详解以及解决方案
  • Polar编译码(SCL译码)和LDPC编译码(BP译码)的matlab性能仿真,并对比香浓限
  • BEVDepth- Acquisition of Reliable Depth for Multi-view 3D Object Detection
  • 数据库管理与高可用-MySQL数据库操作
  • C# Datatable筛选过滤各方式详解
  • 智变与重构:AI 赋能基础教育教学的范式转型研究报告
  • jmeter对数据库进行单独压测
  • 黑马程序员C++核心编程笔记--3 函数高级
  • 【前端】【css预处理器】Sass与Less全面对比与构建对应知识体系
  • Visual Studio 的下载安装
  • 22.代理模式:思考与解读
  • Spring AI 代理模式(Agent Agentic Patterns)
  • element ui 表格 勾选复选框后点击分页不保存之前的数据问题
  • React-native的新架构
  • MySQL 自增 ID 达到上限,如何巧妙化解危机
  • 力扣100题---字母异位词分组
  • Denoising Autoencoders 视频截图 DAEs简单实现 kaggle 去噪编码器
  • 计算机网络 | 1.1 计算机网络概述思维导图
  • 网站怎做/十大营销手段
  • 嘉兴做网站建设的公司/seminar
  • dreamweaver教程做网站/重庆seo哪个强
  • 建筑图片/seo网站推广企业
  • 阜宁做网站的公司/网络营销工作内容是什么
  • 做网站 做app/今日头条十大新闻