【FPGA图像处理实战】- 图像行缓存设计实现方式二(BRAM)

本文转载自:FPGA入门到精通

上一节,我们介绍了基于FIFO实现图像行缓存设计的方式,今天介绍一下如何使用BRAM实现图像行缓存的设计。

一、BRAM资源介绍

BRAM是Block RAM,也就是FPGA中专用RAM资源,固定分布在FPGA内部的特定位置,是随机访问数据的存储器,RAM的内部是一个一个小内存单元(可以看成是一个小格子)组成。

BRAM在FPGA实际对应的资源是RAMB36E1和RAMB18E1。

RAMB36E1是一种具有36Kb容量的Block RAM,由两个独立的18Kb BRAM(Block RAM,RAMB18E1)组成。

RAMB18E1是一种具有18Kb容量的Block RAM,即位宽为18bit、深度为1024,可将多片BRAM级联构成更大规模RAM。

BRAM资源是FPGA中非常重要的资源,不同型号FPGA芯片的BRAM数量不一样,且是有限的,所以需要合理高效的利用BRAM资源。

BRAM的应用方式:单端口ROM(Single-port ROM)、双端口ROM(Dual-port ROM)、单端口RAM(Single-port RAM)、简单双端口RAM(Simple Dual-port RAM)、真双端口RAM(Simple Dual-port RAM)

更详细的资料,请查看xilinx官方资料PG058

二、简单双端口RAM

今天我们基于简单双口BRAM的模式来实现图像行缓存。

简单双端口RAM如下图所示,就是一个A端口写、一个B端口读。

每个端口都有读写模式设置:

(1)写入优先模式(Write First Mode)

在WRITE_FIRST模式下,输入数据同时写入内存并在数据输出上驱动,如图3-9所示。这种透明模式在相同端口的写入操作期间提供了使用数据输出总线的灵活性。

(2)读优先模式(Read First Mode)

在READ_FIRST模式下,写入地址之前存储的旧数据出现在数据输出上,同时输入数据正在存储在内存中。

(3)无变化模式(No Change Mode)

在NO_CHANGE模式下,写入操作期间输出锁存器保持不变。如图3-11所示,数据输出仍然是之前的读数据,并且不受同一端口的写入操作影响。

注意“读写冲突”:

A端口和B端口要尽量避免同时操作一个地址,如果实在不能避免,发送地址冲突的情况,具体处理建议查看PG058第51页。

如果一个端口尝试写入一个内存位置,而另一个端口读取相同的位置,可能会发生同步写读碰撞。虽然在写读碰撞中内存内容不会损坏,但输出数据的有效性取决于写端口的工作模式。

  • 如果写端口处于READ_FIRST模式,另一个端口可以可靠地读取旧的内存内容。
  • 如果写端口处于WRITE_FIRST或NO_CHANGE模式,读端口的输出数据是无效的。
  • 在字节写入的情况下,只有更新的字节在读端口输出上是无效的。
  • 同步时钟下,Xilinx建议在配置为简单双端口RAM时始终使用READ_FIRST。

    异步时钟下,Xilinx建议将端口A的写模式设置为WRITE_FIRST以确保碰撞安全。

    三、FPGA实现代码

    基于BRAM实现的行缓存设计,整体功能结构与基于FIFO实现的行缓存设计一样。

    使用BRAM的优点就是:控制随机读写地址、使用xilinx ultrascale系列芯片时也可以将BRAM资源替换成URAM资源。

    这里分享下行缓存子模块的设计

    通过对写入数据计数,在缓存完一行数据后,每输入一个数据,就输出一个数据。

    这里有两种方案:

    (1)设计不出现读写同地址的情况,也就是写比读始终要晚一个周期。

    (2)直接利用A端口的读写模式设置为Read_First模式,这样就可以读写同一个地址了。

    module line_buffer(
    input wire clk,
    input wire reset,

    input wire [10:0] img_width,

    input wire valid_i,
    input wire [23:0] data_i,

    output wire valid_o,
    output wire [23:0] data_o
    );

    //变量声明
    reg [10:0] wr_data_cnt;
    reg [10:0] addra;
    reg [10:0] addrb;
    wire rd_en;
    reg rd_en_d;

    //写入数据计数
    always@(posedge clk or posedge reset) begin
    if(reset) begin
    wr_data_cnt <= 0;
    end else begin
    wr_data_cnt <= valid_i&&(wr_data_cnt < img_width) ? (wr_data_cnt + 1'b1) : wr_data_cnt;
    end
    end

    always@(posedge clk or posedge reset) begin
    if(reset) begin
    addra <= 0;
    end else begin
    addra <= valid_i ? ((addra == img_width - 1) ? 'b0 : addra + 1'b1) : addra;
    end
    end

    assign rd_en = valid_i&&(wr_data_cnt == img_width) ? 1'b1 : 1'b0;

    always@(posedge clk or posedge reset) begin
    if(reset) begin
    addrb <= 0;
    rd_en_d <= 0;
    end else begin
    addrb <= rd_en ? ((addrb == img_width - 1) ? 'b0 : addrb + 1'b1) : addrb;
    rd_en_d <= rd_en;
    end
    end

    assign valid_o = rd_en_d;

    blk_mem_line_buffer u_blk_mem_line_buffer (
    .clka(clk), // input wire clka
    .wea(valid_i), // input wire [0 : 0] wea
    .addra(addra), // input wire [10 : 0] addra
    .dina(data_i), // input wire [23 : 0] dina
    .clkb(clk), // input wire clkb
    .addrb(addrb), // input wire [10 : 0] addrb
    .doutb(data_o) // output wire [23 : 0] doutb
    );

    endmodule

    如果需要工程源码,想要掌握更多的FPGA图像处理算法,学习FPGA图像算法的实现。

    请阅读下面这篇文章:

    FPGA入门进阶真的难吗?看这里,少走弯路,少踩坑

    FPGA图像处理入门基础系列文章汇总

    最新文章

    最新文章