在 ZYNQ 开发过程中,我们经常遇到需要 PS 和 PL 数据交互的场合,通常使用的方法有 DMA、BRAM 等。对于速度要求高、数据量大、地址连续的数据,可以通过 DMA 来实现,但对于地址不连续,数据量少的场合,DMA 就不适用了,针对这种情况,可以通过 BRAM 来实现。
BRAM(Block RAM)是 PL 端的存储器阵列,PL 可通过输出时钟、地址、读写控制等信号来对其进行读写操作,在 PS 端,处理器则可通过 AXI BRAM 控制器来实现对 BRAM 的读写,这样可以很方便的实现 PS 与 PL 之间的数据交互。
module bram_rd(
input clk , //时钟信号
input rst_n , //复位信号
input start_rd , //读开始信号
input [31:0] start_addr , //读开始地址
input [31:0] rd_len , //读数据的长度
input [31:0] rd_freq , //读数据频率
//RAM端口
output ram_clk , //RAM时钟
input [31:0] ram_rd_data, //RAM中读出的数据
output reg ram_en , //RAM使能信号
output reg [31:0] ram_addr , //RAM地址
output [3:0] ram_we , //RAM读写控制信号
output reg [31:0] ram_wr_data, //RAM写数据
output ram_rst //RAM复位信号,高电平有效
);
wire pos_start_rd;
assign ram_rst = 1'b0;
assign ram_we = 4'b0;
assign pos_start_rd = start_rd;
assign ram_clk=clk;
wire add_cnt0 ;
wire end_cnt0 ;
wire add_cnt1 ;
wire end_cnt1 ;
reg [31:0] cnt0;
reg [31:0] cnt1;
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
ram_en <= 1'b0;
end
else if(pos_start_rd)
ram_en <= 1'b1;
else if(!pos_start_rd)
ram_en <= 1'b0;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n) begin
cnt0 <= 0;
end
else if(add_cnt0) begin
if(end_cnt0)
cnt0 <= 0;
else
cnt0 <= cnt0 + 1;
end
end
assign add_cnt0 =ram_en ;
assign end_cnt0 = add_cnt0 && cnt0==rd_freq-1;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt1 <= 0;
ram_addr <= start_addr;
end
else if(add_cnt1)begin
if(end_cnt1)begin
cnt1 <= 0;
ram_addr <=start_addr;
end
else begin
cnt1 <= cnt1 + 1;
ram_addr <= ram_addr + 'd4;
end
end
end
assign add_cnt1 = end_cnt0;
assign end_cnt1 = add_cnt1 && cnt1>=(rd_len>>2)-1;
endmodule
/**
* Copyright (c) 2022-2023,HelloAlpha
*
* Change Logs:
* Date Author Notes
*/
#include "bram.h"
/* 将数据写入 BRAM */
int BramPsWrite_uint32(uint32_t *pdata, uint32_t offset, uint32_t length)
{
uint32_t i = 0, wr_cnt = 0;
/* 循环向 BRAM 中写入 */
for(i = BRAM_DATA_BYTE * (START_ADDR + offset) ; i < BRAM_DATA_BYTE * (START_ADDR + offset + length) ;
i += BRAM_DATA_BYTE) {
XBram_WriteReg(BRAM_BASE, i, pdata[wr_cnt]) ;
wr_cnt++;
}
return 0;
}
/* 从 BRAM 中读出数据 */
int BramPsRead_uint32(uint32_t *pbuff, uint32_t offset, uint32_t length)
{
uint32_t rd_cnt = 0, i = 0;
/* 循环从 BRAM 中读出数据 */
for(i = BRAM_DATA_BYTE * (START_ADDR + offset); i < BRAM_DATA_BYTE * (START_ADDR + offset + length) ;
i += BRAM_DATA_BYTE) {
pbuff[rd_cnt] = XBram_ReadReg(BRAM_BASE, i) ;
rd_cnt++;
}
return 0;
}
int BramPlReadSet(uint32_t length, uint32_t freq)
{
/* 设置 BRAM 读出的起始地址 */
PL_BRAM_RD_mWriteReg(PL_BRAM_BASE, PL_BRAM_START_ADDR, BRAM_DATA_BYTE * START_ADDR) ;
/* 设置 BRAM 读出的字符串长度 */
PL_BRAM_RD_mWriteReg(PL_BRAM_BASE, PL_BRAM_LEN, BRAM_DATA_BYTE * length) ;
/* 设置 BRAM 读数据频率 */
PL_BRAM_RD_mWriteReg(PL_BRAM_BASE, PL_BRAM_RD_FREQ , freq) ;
return 0;
}
int BramPlReadStart(void)
{
/* 拉高 BRAM 读开始信号 */
PL_BRAM_RD_mWriteReg(PL_BRAM_BASE, PL_BRAM_START , 1) ;
return 0;
}
int BramPlReadStop(void)
{
/* 拉低 BRAM 读开始信号 */
PL_BRAM_RD_mWriteReg(PL_BRAM_BASE, PL_BRAM_START , 0) ;
return 0;
}
/**
* Copyright (c) 2022-2023,HelloAlpha
*
* Change Logs:
* Date Author Notes
*/
#ifndef __BRAM_H__
#define __BRAM_H__
#include "xbram.h"
#include "pl_bram_rd.h"
#define PL_BRAM_BASE XPAR_PL_BRAM_RD_0_S00_AXI_BASEADDR // PL_RAM_RD 基地址
#define PL_BRAM_START PL_BRAM_RD_S00_AXI_SLV_REG0_OFFSET // RAM 读开始寄存器地址
#define PL_BRAM_START_ADDR PL_BRAM_RD_S00_AXI_SLV_REG1_OFFSET // RAM 起始寄存器地址
#define PL_BRAM_LEN PL_BRAM_RD_S00_AXI_SLV_REG2_OFFSET // PL 读 RAM 的深度
#define PL_BRAM_RD_FREQ PL_BRAM_RD_S00_AXI_SLV_REG3_OFFSET // PL 读 RAM 的频率
#define BRAM_BASE XPAR_BRAM_0_BASEADDR
#define BRAM_HIGH XPAR_BRAM_0_HIGHADDR
#define START_ADDR 0 // RAM 起始地址 范围:0 ~ 4095
#define BRAM_DATA_BYTE 4 // BRAM 数据字节个数
//函数声明
int BramPsWrite_uint32(uint32_t *pdata, uint32_t offset, uint32_t length);
int BramPsRead_uint32(uint32_t *pbuff, uint32_t offset, uint32_t length);
int BramPlReadSet(uint32_t length, uint32_t freq);
int BramPlReadStart(void);
int BramPlReadStop(void);
#endif
#include "bram.h"
#include "xil_printf.h"
#define kprintf xil_printf
#define SIZE 16
#define BRAM_MAX_SIZE 4096
#define BRAM_OFFSET 0
int bram_wr_test(void)
{
uint32_t wr_value[SIZE], rd_value[SIZE];
for(uint32_t i = 0; i < SIZE; i++)
{
wr_value[i] = 0x01 << i;
}
BramPlReadStop(); // 停止 PL 读取数据
BramPsWrite_uint32(wr_value, BRAM_OFFSET, SIZE); // PS 写数据
BramPsRead_uint32(rd_value, BRAM_OFFSET, SIZE); // PS 读数据
BramPlReadSet(SIZE, 1); // PL 读 16 个数据,1 个时间单位(与时钟频率有关)读一次
BramPlReadStart(); // PL 开始连续、循环读取数据
kprintf("--- BRAM WR TEST ---\r\n");
for(uint32_t i = 0; i < SIZE; i++)
{
kprintf("Address: %4ld \t Data: %08lx \r\n", i, rd_value[i]); // 将读取的数据输出到串口
}
return 0;
}