几乎所有的FPGA芯片内部都有存储单元BLOCK RAM,ZYNQ也不例外,本文将主要介绍BRAM IP核的使用
单片机和STM32这样的微处理器内部有RAM用来缓存变量,FPGA也不例外,用FPGA的设计过程中我们也需要经常性的缓存一些数据(如中间变量,图像的临时文件,或者矩阵运算的模板等),那我们在FPGA的设计中又该如何实现数据的暂存呢,有很多方法。 我们可以将这些数据存储在芯片外部的DDR和SDRAM中,也可以存储在FPGA的芯片内部。
FPGA的芯片内部有两种方式来实现数据的缓存功能,一种是基于硬件的BLOCK RAM(可以理解为FPGA内部放了一个RAM芯片,相当于是物理存储模块),另一种是DRAM,(Distributed RAM)由fpga的逻辑资源拼接出来的
DRAM 和BRAM 各有优缺点,最明显的区别是 BRAM不占用FPGA逻辑资源,DRAM需要占用LUT及查找表,BRAM 使用起来复杂(需要时钟同步并且给一些控制信号),DRAM 只需要简单调用查找表就好(可以不用时钟,异步存储和读取,相当于组合逻辑), BRAM 可以存储比较多的数据, 而DRAM受FPGA逻辑资源闲置较大(通常仅少量数据如几个字节的时候推荐用DRAM,),当数据较多如缓存一行图像数据或者存储卷积模板的时候会用BRAM(几K 到几百K), 当存储非常大的数据 如整张图像(几M 甚至几十M)时 ,这时候推荐使用外部存储芯片(SDRAM 或者 DDR) 当然如何去使用方法不是固定的,只要项目的程序能跑通,哪怕外部挂个内存条都没事。
我们可以利用BRAM 生成多种不同形式的RAM,比方说 ROM FIFO 等。
项目创建
1)首先正常创建一个工程(可参考前面的例子),
2)添加PLL IP核。在Vivado软件的选中“IP Catalog”
![](http://www.hellofpga.com/wp-content/uploads/2022/10/image-95.png)
在弹出的窗口中搜索 BRAM 并在搜索出的结果中双击Block Me。进入时钟模块设置向导
![](http://www.hellofpga.com/wp-content/uploads/2022/10/image-107.png)
Xilinx 7系列FPGA 内部的BRAM 可以配置成 真双口RAM(True Dual-Port ram,能同时负责读写),简单双口RAM(Simple Dual-Port ram,一个只能负责读 一个只能负责写)单端口RAM(只有一个端口)以及ROM,如下图所示。
![](http://www.hellofpga.com/wp-content/uploads/2022/10/image-108.png)
各种RAM使用起来大同小异, 我们这边选择一个简单双口RAM来进行演示。
![](http://www.hellofpga.com/wp-content/uploads/2022/10/image-109.png)
接下来 设置 A端口 (这里我们创建一个8bit 位宽,16深度的一个RAM) 深度代表有多少个 8bit位宽的RAM单元, 这里用16 也是为了方便演示
![](http://www.hellofpga.com/wp-content/uploads/2022/10/image-121.png)
关于Operating Mode选项 有三种模式,读优先,写优先,和保持模式, 因为我们用的simple dual port ram 属于 两个端口 一个负责写 一个负责读,所以这边 operating Mode选保持模式就好
接下来是B端口设置, 位宽和深度系统默认会和A端口匹配,这里不需要进行修改。这里有个输出寄存器选项 Primitives Output Register需要注意系统默认是打开的,这样BRAM 的B端口读出的数据会经过一个寄存器,在高速流水线的设计中,可以提高时序性能,让数据和输出时钟同步,但是这会使得BRAM输出的数据延迟一拍,所以需要特别注意(下面会提到),之后选择OK 生成IP 模块
![](http://www.hellofpga.com/wp-content/uploads/2022/10/image-122.png)
在其他选项里 可以导入初始化文件 来给RAM赋初值, 这里我们用不到 所以留空
![](http://www.hellofpga.com/wp-content/uploads/2022/10/image-123.png)
之后点选确认保存并生成bram资源
为了让BRAM 的调用结果更直观, 这里我们增加一个ILA模块(ILA模块相当于FPGA内部的逻辑分析仪功能),ILA的具体使用可以参考下一个工程(工程十一)http://www.hellofpga.com/index.php/2022/10/12/ila/
在IP菜单里添加ILA 模块
![](http://www.hellofpga.com/wp-content/uploads/2022/11/image-5.png)
将探针的数量修改成6(这里我们要抓取6个信号)
![](http://www.hellofpga.com/wp-content/uploads/2022/11/image-6.png)
在ILA的第二页设置栏,分别设置每一个探针的位宽
![](http://www.hellofpga.com/wp-content/uploads/2022/11/image-7.png)
之后点选OK 保存并生成ILA资源
程序设计
接下来是本次试验的程序设计部分
本次程序设计 共分3个阶段
- 第一个阶段(mode=0)对系统中的寄存器(包括bram 调用的地址,读写使能等寄存器)进行初始化(mode 0)
- 第二个阶段(mode=1)(port a口的地址从0-15进行自增,并且将对应的0-15存储到bram内,存储完16个数字后,对port a的读写功能切换成读,即不再写入数据)
- 第三阶段 (mode=2)(port b口的地址从0-15进行自增,并且读取port b 的输出数据,当自增到15时,将mode的值赋值为3)
- 第四阶段 (mode=3)不做任何操作 给mode赋值0 让四个阶段循环起来(为什么这里要循环,因为如果不增加循环,上电瞬间你还没用ila开始抓波形,人家FPGA已经完成了全部的读写操作,你就看不到完整的过程了,所以这里需要对整个过程进行循环可以方便观察)
以下是完整程序
`timescale 1ns / 1ps module BRAM_TEST( input CLK ); reg [3:0]addr_a; reg [3:0]addr_b; reg wr_en_a; reg [1:0]mode=2'd0; reg [7:0]din_a; wire [7:0]dout_b; always@(posedge CLK)begin if(mode==0)begin addr_a<=4'd0; din_a<=8'd0; addr_b<=4'd0; wr_en_a<=1'b1; mode<=2'd1; end else if(mode==1)begin if(addr_a==4'd15)begin mode<=2'd2; wr_en_a<=1'b0; end else begin addr_a<=addr_a+1'b1; din_a<=din_a+1'b1; end end else if(mode==2)begin if(addr_b==4'd15)begin mode<=2'd3; end else addr_b<=addr_b+1'b1; end else mode<=2'd0; end blk_mem_gen_0 u_bram ( .clka(CLK), .ena(1'b1), .wea(wr_en_a), .addra(addr_a), .dina(din_a), .clkb(CLK), .enb(1'b1), .addrb(addr_b), .doutb(dout_b) ); ila_0 ila_u ( .clk(CLK), .probe0(mode), .probe1(wr_en_a), .probe2(addr_a), .probe3(din_a), .probe4(addr_b), .probe5(dout_b) ); endmodule
本程序中一共只有一个always块,根据mode寄存器值的不同分别执行上面提到的4个阶段的功能,其中 din_a和addr_a的值一直相同; 代表每个地址存入的内容即该地址的地址号,也即是对应(0-15)之间的数字
添加约束文件
set_property PACKAGE_PIN H16 [get_ports CLK] set_property IOSTANDARD LVCMOS33 [get_ports CLK]
之后进行编译综合
在线ILA 观察波形看试验结果
将刚才编译综合好的程序 对FPGA进行PROGRAM
![](http://www.hellofpga.com/wp-content/uploads/2022/11/image-8.png)
保持默认选项,点选program
![](http://www.hellofpga.com/wp-content/uploads/2022/11/image-9.png)
之后系统会自动增加一个ILA窗口(窗口中包含着我们之前程序中用探针观察的所有信号)
![](http://www.hellofpga.com/wp-content/uploads/2022/11/image-10.png)
点选箭头,开始抓取信号
![](http://www.hellofpga.com/wp-content/uploads/2022/11/image-11.png)
之后我们要观察的信号就被抓取出来了
![](http://www.hellofpga.com/wp-content/uploads/2022/11/image-12.png)
如果数据是十六进制的 ,可以将观察的信号全部选中,切换数据进制
![](http://www.hellofpga.com/wp-content/uploads/2022/11/image-13.png)
下图为抓出来的波形结果,这里有几个地方备注下,输出输出是跟地址有两个时钟节拍的延迟(ram块读入地址,对地址数据进行输出这个过程是需要时钟同步的,这里有一个时钟,而输出的数据需要经过ram口的寄存器,这里就是第二个时钟) 第二个时钟延迟可以在之前设置bram的界面把输出寄存器关闭来取消,但是在高速访问的情况下不利于系统的稳定性。
![](http://www.hellofpga.com/wp-content/uploads/2022/11/image-14-1024x344-1.png)
程序比较简单,在实际调用BRAM模块的时候,除了用ILA来验证,也可以用仿真的方式来验证。 另外BRAM模块在使用的过程中对输出延时需要格外注意(这里在实际项目中很容易忽视输出数据的延时,而造成实际的bug)
以下是本次实验的完整工程: