跳转至

Block RAM

进阶功能

本章节属于拓展功能,其主要目的是给大家一个做一个简单的进阶参考。进阶实验的目的是锻炼大家的自学能力。因此对本章节内容的答疑可能不会被接受。特此声明。

在实验过程中可能会遇到这样一个问题:由于 Wishbone 总线的介入,访问板子上提供的 SRAM 的速度较慢。但是寄存器的资源又较为有限。我们需要一些更快速的存储设备,同时也需要它能存一定量的数据,来完成 ICache, DCache, BTB, 显存等功能。这时候就需要使用 FPGA 上提供的 Block RAM 了。

有兴趣的同学也可以去参考一下 xpm 的相关内容,这个在编写代码时会更加方便。

引入 IP 核

  1. 在 IP 目录中找到 Block Memory Generator

  2. Memory Type 处选择合适的 BRAM 种类

    • Single Port RAM: 只有 1 个口用于读写 RAM。
    • Simple Dual Port RAM: 有两个端口,一个端口只写,另一个端口只读。
    • True Dual Port RAM: 两个端口可读可写。
    • ROM 为只读存储器

    Write Enable 栏打开字节使能,并字节大小为 8 bit。如果不需要可以不打开。

    其他选项不要更改,这里以 True Dual Port RAM 为例

  3. 进入第二页,设置 1 号口的相关参数:

    • Write Width: 写宽度,表示每次写入 data 的 bit 数 (即 DATA_WIDTH )。
    • Read Width: 读宽度,即读取宽度。若打开字节使能,则这两个参数必须为字节大小的倍数。为了方便,请设置读写宽度为相同的值。
    • Write Depth: 写深度,即该 RAM 中有多少个长度为 Write Width 的项。如图中配置,则 RAM 的空间大小为 16 * 16 / 8 = 32 Bytes
    • Read Depth 自动设置
    • Operating Mode 的 3 个选项我们将在后面解释。
    • Enable Port Type:若选择 Use ENA pin 则表示需要使能端 (类似于 SRAM 中的 ce 信号),Always Enabled 则表示一直开启。
    • Primitive Output Register 和 Core Output Register 表示 输出数据是否需要寄存器,如输出需要寄存器,选择 Primitive 和 Core 中的一个即可,这会让输出延迟一拍,但会让信号更加稳定。 剩下的选项不用修改。

    如选择双端口 RAM,则继续配置 2 端口的相关参数,注意这里只能改 Width 但是不能改 Depth,因为 RAM 的大小已经由 1 号口的参数确定。

  4. Other Options 页中可以设置 BRAM 的初始化文件,从而让 BRAM 预载对应的值。这会使用到 coe 文件,这部分的内容请自行学习。

  5. Summary 页中可以看到你设置的相关参数,以及实际 BRAM 的使用数量等信息。注意 FPGA 上的 BRAM 资源有限,不要开太大(一般 KB 级别已经相当够用了)。同时记得给你的 BRAM 改一个好名字,默认的名字比较奇怪。

信号和时序

这里以一个 Simple Dual Port RAM 为例讲解信号:

写口有以下信号:

  • addr(a/b): 地址,输入,编址方式为按照 Write Width 为单位编址。即 addr 为 0 对应第一个 Write Width bits,addr 为 1 对应第二个 Write Width bits,以此类推。
  • clk(a/b): 时钟信号,输入
  • din(a/b): 即 data in , 输入,表示写的数据
  • en(a/b): 对应的使能端,不开启则没有这个信号
  • we(a/b): 对应的字节使能端,不开启则没有这个信号

读口有以下信号:

  • addr(a/b)
  • clk(a/b)
  • dout(a/b): 输出,表示读出的数据
  • en(a/b)

如果是读写口,不开启字节使能时,仍会有一位的 we(a/b) 信号,此时,这个信号表示整体的写使能:当 en & we 时写 BRAM, 当 en & ~we 时读 BRAM。

Read First , 不开启 Output Register 时的时序图如下:

用 Verilog 代码描述如下:

always_ff @(posedge clk) begin
   if(en) begin
      if(we) begin
         ram[addr] <= din;
         dout <= ram[addr];
      end else begin
         dout <= ram[addr];
      end
   end      
end

Write First , 不开启 Output Register 时的时序图如下:

用 Verilog 代码描述如下:

always_ff @(posedge clk) begin
   if(en) begin
      if(we) begin
         ram[addr] <= din;
         dout <= din;
      end else begin
         dout <= ram[addr];
      end
   end      
end

No Change , 不开启 Output Register 时的时序图如下:

用 Verilog 代码描述如下:

always_ff @(posedge clk) begin
   if(en) begin
      if(we) begin
         ram[addr] <= din;
      end else begin
         dout <= ram[addr];
      end 
   end      
end

总结如下:

  1. addr 需要 setup 时间,即这周期给地址和信号,下周期拿结果。而且 BRAM 支持连续的读写。
  2. Read First, Write First, No Change 会影响写入时读出的数据。注意这会影响 Dual Port RAM 在读写地址相同时的读取情况。

更详细的说明和使用方法可以去 Xilinx 官方文档网站查看: Block RAM 文档


最后更新: 2023年7月14日
作者:Jiajie Chen (11.54%), cuibst (88.46%)