介绍
无论何时,在复杂的 FPGA 设计过程中,都不可避免地需要在模块之间发送数据,实现这一点的常用的是 FIFO。
FIFO
写入:当写入 FIFO 时,需要确保不要写入太多数据以致 FIFO 溢出。为了帮助解决这个问题,FIFO 通常有一个完整的计数标志,有时还可以使用一个watermark端口。
watermark:本质上告诉 FIFO 中的项目何时超过一定数量,这时候应该放慢速度或不放入数据。但如果想发送特定数量的数据,将需要添加额外的步骤在状态机中管理“above watermark”的情况。在状态机上工作时,可能需要添加状态和寄存器来管理边缘情况(空满情况)。
full flag:比较棘手的信号,因为 full flag 可能会在输入数据的同一时钟变高。如果有流水线设计,则需要在检测到“full”状态时缓冲这些数据。
count:计数可以大致了解可以进入 FIFO 的数据量。计数的更新比watermark和full flag慢,并且会给你一个保守的 FIFO 内的空间计数。我很想经常使用它,但我发现我需要在状态机中添加一些状态来管理它。
Reading:从 FIFO 读取通常不会那么糟糕,只要在空标志不置位时不读取即可。
Double Buffer
我师傅让我考虑使用双端口block-ram 作为双缓冲器。就像 FIFO 一样,类似具有如下行为的读取器和写入器:
- 写入器:将数据写入block-ram,然后使用跨时钟域技术将数据的大小和状态发送给读取器。
- 读取器:读取写入器放入 RAM 的已知数据量。
这种方法的好处在于,写入器知道它可以写入多少空间,而读取器知道它可以读取多少数据。这非常适合流水线设计。另一个方面是写入器可以在读取器读取数据时开始处理block-ram 的后半部分。不过,这种方法并不是自由操作。以下是现在需要由写入器和读取器管理的一些事情:
写入器
- ram中有多少空间(或ram的一半)
- 开始/结束地址指针
- 写入了多少数据
读取器
- 有多少数据可供读取
- 开始/结束地址指针
我喜欢这种双缓冲区给我的数据量的预知。这允许我编写内核,将已知数量的数据从双缓冲区的输出转储到另一个位置,如音频或视频缓冲区。不幸的是,每个使用双缓冲器的模块都必须设计为能够处理上述所有问题以及更多的跨时钟域标志。
如果不需要担心 FIFO(满/空)的边缘情况,这将是最容易使用的机制。下面将这两种机制结合起来可能是最佳方案。
Ping Pong FIFO
Ping Pong FIFO 本质上是一个上面描述的双缓冲区,包裹起来看起来像一个 FIFO。所有地址指针和跨时钟域通信都包含在一个简单的模块中。模块如下所示:
module PPFIFO
#(parameter DATA_WIDTH = 8,
ADDRESS_WIDTH = 4
)(
//universal input
input reset,
//write side
input write_clock,
output reg [1:0] write_ready,
input [1:0] write_activate,
output [23:0] write_fifo_size,
input write_strobe,
input [DATA_WIDTH - 1: 0] write_data,
output starved,
//read side
input read_clock,
input read_strobe,
output reg read_ready,
input read_activate,
output reg [23:0] read_count,
output [DATA_WIDTH - 1: 0] read_data,
output inactive
);
有单独的写入端和读取端时钟,选通用于写入和读取数据。不过也有一些新的信号:
- write_ready:这与双缓冲区有关,需要管理缓冲区的两侧。这2 bit信号告诉双缓冲区的哪一侧已准备好。
0:缓冲区的下半部分准备好
1:上半部分准备好
- write_activate:用户告诉 Ping Pong FIFO 它想要拥有缓冲区的一侧
- write_fifo_size:表示用户可以写入 Ping Pong FIFO(PPFIFO) 的字数。
注意:不需要在完成之前填充写入端,PPFIFO 将跟踪写入的元素数量并将此信息发送到读取端,作为将递增数字模式写入 PPFIFO 的简单模块示例
/* Module: ppfifo_source
*
* Description: Populate a Ping Pong FIFO with an incrementing number pattern
*/
module ppfifo_source #(
parameter DATA_WIDTH = 8
)(
input clk,
input rst,
input i_enable,
//Ping Pong FIFO Interface
input [1:0] i_wr_rdy,
output reg [1:0] o_wr_act,
input [23:0] i_wr_size,
output reg o_wr_stb,
output reg [DATA_WIDTH - 1:0] o_wr_data
);
//Local Parameters
//Registers/Wires
reg [23:0] r_count;
//Submodules
//Asynchronous Logic
//Synchronous Logic
always @ (posedge clk) begin
//De-assert Strobes
o_wr_stb <= 0;
if (rst) begin
o_wr_act <= 0;
o_wr_stb <= 0;
o_wr_data <= 0;
r_count <= 0;
end
else begin
if (i_enable) begin
if ((i_wr_rdy > 0) && (o_wr_act == 0))begin
r_count <= 0;
if (i_wr_rdy[0]) begin
//Channel 0 is open
o_wr_act[0] <= 1;
end
else begin
//Channel 1 is open
o_wr_act[1] <= 1;
end
end
else if (o_wr_act > 0) begin
if (r_count < i_wr_size) begin
//More room left in the buffer
r_count <= r_count + 1;
o_wr_stb <= 1;
//put the count in the data
o_wr_data <= r_count;
end
else begin
//Filled up the buffer, release it
o_wr_act <= 0;
end
end
end
end
end
endmodule
正如所看到的,通过添加一个额外的寄存器来跟踪添加到 PPFIFO 的数据量,不必担心full flags、water marks 或者counts。
阅读方面更容易。PPFIFO 知道首先写入哪个缓冲区,因此用户只需要观察一个read_ready标志,然后使用read_activate告诉它我们有控制权。以下是从 PPFIFO 读取数据的示例:
这里有更具体的细节:
- 用户监视“read_ready”位:当“read_ready”信号为 1 时,ppfifo 为用户准备好一个数据块。
- 用户使用“read_activate”信号激活该块并使用“read_strobe”读取 NEXT 数据
- “read_count”是缓冲区中数据元素的总数。
- 用户必须在将“read_activate”设置为低之前读取所有数据
/* Module: ppfifo_sink
*
* Description: Whenever data is available within the FIFO activate it and read it all
*/
module ppfifo_sink #(
parameter DATA_WIDTH = 8
)(
input clk,
input rst,
//Ping Pong FIFO Interface
input i_rd_rdy,
output reg o_rd_act,
input [23:0] i_rd_size,
output reg o_rd_stb,
input [DATA_WIDTH - 1:0] i_rd_data
);
//Local Parameters
//Registers/Wires
reg [23:0] r_count;
//Submodules
//Asynchronous Logic
//Synchronous Logic
always @ (posedge clk) begin
//De-Assert Strobes
o_rd_stb <= 0;
if (rst) begin
o_rd_act <= 0;
r_count <= 0;
o_rd_stb <= 0;
end
else begin
if (i_rd_rdy && !o_rd_act) begin
r_count <= 0;
o_rd_act <= 1;
end
else if (o_rd_act) begin
if (r_count < i_rd_size) begin
o_rd_stb <= 1;
r_count <= r_count + 1;
end
else begin
o_rd_act <= 0;
end
end
end
end
endmodule
下面设计一个简单的测试模块来演示 Ping Pong FIFO。
源代码地址:
https://github.com/CospanDesign/verilog_ppfifo_demo
下面是几个简单模拟的截图:
在读事务开始和下一个写事务开始时放大仿真区域:
在读取事务之间放大
半放大
截图可能不清晰,建议自己仿真。
总结
PPFIFO除了上面用于解决FIFO的“痛处”外,常见的还是处理高速数据流处理,下面是一个10M数据流分成两个5M数据流的例子。
代码可以参考:
https://github.com/DOOKNET/Pingpang_RAM
图片来源:CSDN @我的天不可能
原文:OpenFPGA
作者:碎碎思
相关文章推荐
- 使用Vitis HLS创建属于自己的IP
- 为你的FPGA设计加加速,NIC、Router、Switch任意实现
- HLS最全知识库
- 优秀的IC/FPGA开源项目(一)-FPGA+CMOS+USB/SD架构开源项目
- FPGA便捷开发-TCL商店(开源)
更多FPGA技术干货请关注FPGA 的逻辑技术专栏。欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。