乒乓操作原理简介与设计
一.在说乒乓操作之前我们先来了解一下关于输入输出缓存电路的三种一般形式:
- 双口ram结构:双口RAM是在一个SRAM存储器上具有两套完全独立的数据线、地址线、读写控制线、并允许两个独立的系统同时对该存储器进行随机访问。
- FIFO结构:FIFO(First In First Out)是一种先进先出的数据缓存器,可以进行双端操作,但是数据必须先进先出,不能进行随机性的访问。从容量大小来看,双口RAM比FIFO要大一些,但总的来说,这两种缓冲结构的存储容量还是相对较小,对高速图像处理系统而言,还不是特别适合。具体FIFO的基础知识核基本实现可参考https://blog.csdn.net/weixin_49495368/article/details/110008717
- 乒乓ram结构:这种结构是将输入数据流通过输入数据选择单元等时地将数据流分配到两个ram缓冲区。通过两个ram读和写的切换,来实现数据的流水式传输。
综上所述,乒乓缓存结构实际上相当于一个双口RAM,但它与普通的双口RAM又有所不同。普通双口RAM是单个存储体构成的IC,乒乓ram结构则由包含两个相互独立存储体的多片IC构成,从而使其在结构、速度、容量等方面具有更大的灵活性;若双口在访问同一地址时,普通双口SAM指向的必定是存储体内的同一存储单元,而乒乓ram结构则分别指向属于SRAM1和SRAM2的两个不同的存储单元,更易操作。乒乓缓存结构的上述特点决定了可以相对较便宜的高速大容量SRAM、外围逻辑器件构成比双口RAM以及高速FIFO更适合视频处理的系统所需要的缓冲存储器。
乒乓ram结构的上述特点决定了可以相对较便宜的高速大容量RAM、外围逻辑器件构成比双口RAM以及高速FIFO更适合大数据传输系统所需要的缓冲存储器。
二.乒乓ram控制原理
"乒乓操作"是一个常用的数据流控制处理技巧。典型的乒乓操作结构如图1所示。
大致的流程为:
输入数据流通过输入数据选择单元将数据流等时分配到两个数据缓冲区,数据缓冲模块一般为ram。在第一个缓冲周期,将输入的数据流缓存到数据缓冲模块ram A;在第2个缓冲周期,通过输入数据选择单元的切换,将输入的数据流缓存到数据缓冲模块ram B,同时将ram A缓存的第1个周期数据传给输出数据选择单元。在第3个缓冲周期通过输入数据选择单元的再次切换,将输入的数据流缓存到ram A同时将ram B缓存的第2个周期的数据传给输出数据选择单元。如此循环。(正如打乒乓一样,数据只在球拍上停留短暂的时间,随即就将数据‘’拍‘’出去)
乒乓操作的最大特点是通过“输入数据选择单元”和“输出数据选择单元”按节拍、相互配合的切换,将经过缓冲的数据流没有停顿地送到“数据流运算处理模块”进行运算与处理。把乒乓操作模块当做一个整体,站在这个模块的两端看数据,输入数据流和输出数据流都是连续不断的,没有任何停顿,因此非常适合对数据流进行流水线式处理。所以乒乓操作常常应用于流水线式算法,完成数据的无缝缓冲与处理。
其中有一点需要说明的是,在进行乒乓操作的时候,数据缓冲模块也就是图1中的ramA,它可以是任意的存储模块,也可以是FIFO,SRAM,甚至也可以是DDR3里面的BANK,这是根据用户自己定义的!!!
三.乒乓ram控制的FPGA设计
根据图1的大致架构可得到图2的设计流程如下:
四.程序设计
module pinpong(clk,rst,din,dout);
input clk;
input rst;
input [7:0] din;
output reg [7:0] dout;
reg [7:0] buffer1 [255:0];
reg [7:0] buffer2 [255:0];//定义两块存储区域
reg wr_flag;//写标志 wr_flag=0 写buffer1;wr_flag=1 写buffer2
reg rd_flag;//读标志 rd_flag=0 读buffer2;rd_flag=1 读buffer1
reg [1:0] cs,ns;//状态
parameter s0=2’b01,s1=2’b10;//两个状态,采用one-hot编码
parameter s0_pos=1’d0,s1_pos=1’d1;//描述有效位在state中的位置
reg [7:0] count;//计数
always @(posedge clk)
begin
if(rst)
begin
count <= 0;
end
else
begin
count <= count + 1’d1;
end
end
//时序逻辑描述状态转换
always @(posedge clk)
begin
if(rst)
begin
cs <= s0;
end
else
begin
cs <= ns;
end
end
//组合逻辑描述下一个状态
always @(*)
begin
if(rst)
begin
ns = s0;
end
else
begin
case(1’b1)
cs[s0_pos]:ns=(count == 8’hFF)?s1:s0;
cs[s1_pos]:ns=(count == 8’hFF)?s0:s1;
default:ns = s0;
endcase
end
end
//输出逻辑
always @(posedge clk)
begin
if(rst)
begin
wr_flag <= 1’d0;
rd_flag <= 1’d0;
end
else
begin
case(1’b1)
cs[s0_pos]:begin
wr_flag <= 1’d0;//写1读2
rd_flag <= 1’d0;
end
cs[s1_pos]:begin
wr_flag <= 1’d1;//写2读1
rd_flag <= 1’d1;
end
default:begin
wr_flag <= 1’d0;//写1读2
rd_flag <= 1’d0;
end
endcase
end
end
//写
always @(posedge clk)
begin
if(rst)
begin
buffer1[count] <= 8’d0;
buffer2[count] <= 8’d0;
end
else
begin
case(wr_flag)
1’d0:buffer1[count] <= din;//写1
1’d1:buffer2[count] <= din;//写2
default:begin
buffer1[count] <= 8’d0;
buffer2[count] <= 8’d0;
end
endcase
end
end
//读
always @(posedge clk)
begin
if(rst)
begin
dout <= 8’d0;
end
else
begin
case(rd_flag)
1’d0:dout <= buffer2[count];//读2
1’d1:dout <= buffer1[count];//读1
default: dout <= 8’d0;
endcase
end
end
endmodule