ZYNQ裸板简单实战-BRAM篇

前言

  在 ZYNQ SOC 开发过程中, PL 和 PS 之间经常需要做数据交互。对于传输速度要求较高、数据量大、地址连续的场合,可以通过 AXI DMA 来完成。而对于数据量较少、地址不连续、长度不规则的情况,此时 AXIDMA 便不再适用了。针对这种情况,可以通过 BRAM 来进行数据的交互。(还不是因为DMA麻烦一点才先写这个)
  Block RAM是PL部分的存储器阵列,为了与DRAM(分布式RAM)区分开,所以叫BLOCK RAM。ZYNQ的每一个BRAM 36KB,7020的BRAM有140个(4.9M),7030有265个(9.3M),7045有545个(19.2M)。每一个BRAM都有两个共享数据的独立端口,当然是可以配置的,可用于片内数据缓存、FIFO缓冲。
  在Vivado里有一个IP核叫Block Memory Generator,它使用FPGA的BRAM资源为我们提供可编程的RAM。
读和写由时钟控制,
数据宽度是可编程的。

与DRAM相比补充
1.BRAM是使用FPGA中的整块双口RAM资源
2.DRAM是FPGA中的查找表(LUT)拼凑出来的,要占用逻辑资源。
3.物理上看,BRAM 是单纯的存储资源,但是要一块一块的用,不像DRAM 想要多少bit都可以。
4.DRAM可以是纯组合逻辑,即给出地址马上出数据,BRAM是有时钟的。

再补充个使用条件吧
1、较大的存储应用,建议用BRAM;零星的小RAM,一般就用DRAM。(小于或等于64bit容量的用分布式实现,深度在64~128之间的,若无额外的block可用分布式RAM。数据宽度大于16时用Block RAM)
2、dram可以是纯组合逻辑,即给出地址马上出数据,也可以加上register变成有时钟的ram。而bram一定是有时钟的。(要求异步读取就使用分布式RAM)
3、如果要产生大的FIFO或timing要求较高,就用Block RAM。否则,就可以用Distributed RAM。块RAM是比较大块的RAM,即使用了它的一小部分,那么整个Block RAM就不能再用了。所以,当你要用的RAM比较小,时序要求不高时,可以用分布式RAM,节省资源。
FPGA中的资源位置是固定的,例如BRAM就是一列一列分布的(存储器阵列),会产生较大的布线时延。在大规模FPGA中,如果用光所有的BRAM,性能一般会下降,甚至出现route不通的情况,就是这个原因。
4.用户申请存储资源,FPGA先提供Block RAM ,当Block RAM 数量不够时再用分布式RAM补充。
再说回到BRAM,在 PL 中,通过输出时钟、地址、读写控制等信号来对 BRAM 进行读写操作;而在 PS 中,处理器并不需要直接驱动 BRAM 的端口,而是通过 AXIBRAM 控制器来对 BRAM 进行读写操作。 AXI BRAM 控制器是集成在 Vivado 设计软件中的软核,可以配置成 AXI4-lite 接口模式或者 AXI4 接口模式。

  AXI4 接口模式的 BRAM 控制器支持的数据位宽为 32 位、 64 位、 128 位、 512 位和 1024 位,而 AXI4-Lite 接口仅支持 32 位数据位宽。由图 14.1.1 可知, PS 通过 AXI4-Lite 接口访问 BRAM,当使能 ECC 选项时, ECC 允许 AXI 主接口检测和纠正 BRAM 块中的单位和双位错误。 AXI BRAM 控制器作为 AXI 总线的从接口,和 AXI 主接口实现互联,来对 BRAM 进行读写操作。针对不同的应用场合,该 IP 核支持单次传输和突发传输两种方式
对BRAM的操作是很好理解的,一切通过地址:
  代码部分借鉴下原子的吧还是

#include "xil_printf.h"
2 #include "stdio.h"
3 #include "pl_bram_rd.h"
4 #include "xbram.h"
5 6
#define PL_BRAM_BASE XPAR_PL_BRAM_RD_0_S00_AXI_BASEADDR //PL_RAM_RD 基地址
7 #define PL_BRAM_START PL_BRAM_RD_S00_AXI_SLV_REG0_OFFSET //RAM 读开始寄存器地址
8 #define PL_BRAM_START_ADDR PL_BRAM_RD_S00_AXI_SLV_REG1_OFFSET //RAM 起始寄存器地址
9 #define PL_BRAM_LEN PL_BRAM_RD_S00_AXI_SLV_REG2_OFFSET //PL 读 RAM 的深度
10
11 #define START_ADDR 0 //RAM 起始地址 范围:0~1023
12 #define BRAM_DATA_BYTE 4 //BRAM 数据字节个数
13
14 char ch_data[1024]; //写入 BRAM 的字符数组
15 int ch_data_len; //写入 BRAM 的字符个数
16
17 //函数声明
18 void str_wr_bram();
19 void str_rd_bram();
20
21 //main 函数
22 int main()
23 {
    
    
24 while(1)
25 {
    
    
26 printf("Please enter data to read and write BRAM\n") ;
27 scanf("%1024s", ch_data); //用户输入字符串
28 ch_data_len = strlen(ch_data); //计算字符串的长度
29
30 str_wr_bram(); //将用户输入的字符串写入 BRAM 中
31 str_rd_bram(); //从 BRAM 中读出数据
32 }
33 }
34
35 //将字符串写入 BRAM
36 void str_wr_bram()
37 {
    
    
38 int i=0,wr_cnt = 0;
39 //每次循环向 BRAM 中写入 1 个字符
40 for(i = BRAM_DATA_BYTE*START_ADDR ; i < BRAM_DATA_BYTE*(START_ADDR + ch_data_len) ;
41 i += BRAM_DATA_BYTE){
    
    
42 XBram_WriteReg(XPAR_BRAM_0_BASEADDR,i,ch_data[wr_cnt]) ;
43 wr_cnt++;
44 }
45 //设置 BRAM 写入的字符串长度
46 PL_BRAM_RD_mWriteReg(PL_BRAM_BASE, PL_BRAM_LEN , BRAM_DATA_BYTE*ch_data_len) ;
47 //设置 BRAM 的起始地址
48 PL_BRAM_RD_mWriteReg(PL_BRAM_BASE, PL_BRAM_START_ADDR, BRAM_DATA_BYTE*START_ADDR) ;
49 //拉高 BRAM 开始信号
50 PL_BRAM_RD_mWriteReg(PL_BRAM_BASE, PL_BRAM_START , 1) ;
51 //拉低 BRAM 开始信号
52 PL_BRAM_RD_mWriteReg(PL_BRAM_BASE, PL_BRAM_START , 0) ;
53 }
54
55 //从 BRAM 中读出数据
56 void str_rd_bram()
57 {
    
    
58 int read_data=0,i=0;
59 //循环从 BRAM 中读出数据
60 for(i = BRAM_DATA_BYTE*START_ADDR ; i < BRAM_DATA_BYTE*(START_ADDR + ch_data_len) ;
61 i += BRAM_DATA_BYTE){
    
    
62 read_data = XBram_ReadReg(XPAR_BRAM_0_BASEADDR,i) ;
63 printf("BRAM address is %d\t,Read data is %c\n",i/BRAM_DATA_BYTE ,read_data) ;
64 }
65 }

  主函数实现了通过串口接收用户数据,然后将用户数据写入 BRAM,并从 BRAM 中读出数据的功能。主函数包含一个无限循环 while 语句,程序首先打印一串字符串来提示用户开始输入数据,然后通过 scanf函数来接收串口的数据,存放在字符串数组 ch_data 中。这里的“ %1024s”是指单次最大接收 1024 个字符,这是因为 BRAM 的深度为 1024,防止写入 BRAM 的数据个数超出 BRAM 的深度。然后计算接收到的数据个数,并调用自己编写的 str_wr_bram 函数将数据写入 BRAM。
  数据写完后,调用自己编写的 str_rd_bram()函数从 BRAM 中读出数据。接下来在程序的第 35 至 53 行,将接收到的数据写入 BRAM 中,并配置 PL 端开始从 BRAM 中读取数据。
  在写 BRAM 的过程中,通过 XBram_WriteReg()函数将接收到的数据按照起始地址,依次写入 BRAM中。在数据写入完成后,通过 AXI 总线,配置 PL 端读取 BRAM 的数据个数和起始地址,并驱动 PL 读开始信号输出一个脉冲信号。
  在程序第 55 至 65 行 PS 端读取 BRAM 中的数据。通过 XBram_ReadReg()函数按照 BRAM 的起始地址,依次从 BRAM 中读出数据,并通过串口打印出来
这一部分没能自己亲自尝试,也借鉴了chaunjiezhu博主的文章,原文链接如下,有机会继续深入https://blog.csdn.net/u014485485/article/details/78882027

猜你喜欢

转载自blog.csdn.net/qq_42330920/article/details/113769715