如何在 zynq 中进行 PL 端与 PS 端的数据交互?
在zynq的使用中,高效的进行 BRAM 与 zynq 硬核的数据交换至关重要,当我们需要进行小批量的数据交换时,可以考虑采用 BRAM 作为数据交换的媒介,在 PS 端,将数据通过 AXI 总线写入 PL端的 BRAM 里面去,并且再读出来。整体的设计图如下所示:
PL 端硬件的设计
在 vivado 2017.4 下,可以通过 block design 快速进行模块的添加与设计。设计出来的架构如图所示:
注意事项:
- BRAM 使用真实双端口 RAM ,一个端口连接 axi BRAM controller(用于 PS 端控制 BRAM),一个连接 PL BRAM controller(一个自定义的 PL 端 BRAM 控制器,可以由 PS 端给一些控制信号)。
- BRAM需要选择 BRAM controller 模式。
- zynq 端要打开中断
在经过上面的 block design 设计之后,可将硬件导出,然后打开 sdk ide 开始进行 PS 端程序的开发。
程序的整体流程如下:
- 在 PS 端输入起始地址和长度
- CPU通过 axi BRAM controller 写入BRAM数据
- 通知 PL BRAM controller 读取数据
- PL 内部读完后向相同的位置写入数据,初始数据有CPU告知
- 写完后使能 write_end 信号,出发GPIO 中断
- 中断读取 BRAM 数据,打印显示
关于自定义 IP 的设计:
自定义 IP 实现了对 PL 端 BRAM 的读写操作,并且在外部封装的有 AXI 总线的端口,核心的读写逻辑如下:
module ram_read_write
(
input clk,
input rst_n,
//bram port
input [31:0] din,
output reg [31:0] dout,
output reg en,
output reg [3:0] we,
output rst,
output reg [31:0] addr,
//control signal
input start, //start to read and write bram
input [31:0] init_data, //initial data defined by software
output reg start_clr, //clear start register
input [31:0] len, //data count
input [31:0] start_addr, //start bram address
//Interrupt
input intr_clr, //clear interrupt
output reg intr //interrupt
);
assign rst = 1'b0 ;
localparam IDLE = 3'd0 ;
localparam READ_RAM = 3'd1 ;
localparam READ_END = 3'd2 ;
localparam WRITE_RAM = 3'd3 ;
localparam WRITE_END = 3'd4 ;
reg [2:0] state ;
reg [31:0] len_tmp ;
reg [31:0] start_addr_tmp ;
//Main statement
always @(posedge clk or negedge rst_n)
begin
if (~rst_n)
begin
state <= IDLE ;
dout <= 32'd0 ;
en <= 1'b0 ;
we <= 4'd0 ;
addr <= 32'd0 ;
intr <= 1'b0 ;
start_clr <= 1'b0 ;
len_tmp <= 32'd0 ;
start_addr_tmp <= 32'd0 ;
end
else
begin
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
endmodule
这个自定义 IP 的实现是为了令实例更为完整,方便在逻辑分析仪中进行观察。
PS 端软件的设计
打开 sdk 工具,开始进行软件开发。
代码如下:
/* ------------------------------------------------------------ */
/* Include File Definitions */
/* ------------------------------------------------------------ */
#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_0_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_0_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) ;
printf("Start address is %d\t\n", Start_Addr) ;
printf("Please provide length\t\n") ;
scanf("%d", &Len) ;
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 ;
}
}
}
}
// 对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
*/
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+1)) ;
//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 is %d\t Read data is %d\t\n", i/BRAM_BYTENUM ,Read_Data) ;
}
Intr_flag = 1 ;
}
最终实现效果如下图所示:
需要完整项目代码的可以留言。