基本概念
ping-pong buffer 也叫双缓存 double buffer, (必须是两个)就是一个缓存在写入的时候, 另一个缓存同时在处理的结构. 用来提高计算机运行速度, 在显示数据处理中常常用到。
乒乓操作的处理流程为:输入数据流通过“ 输入数据选择单元”将数据流等时分配到两个“数据缓冲模块”, 数据缓冲模块可以为任何存储模块,比较常用的存储单元为双口RAM(DPRAM)、单口RAM(SPRAM)、FIFO等。
在低速处理高速数据流时,可以使用乒乓操作,10M的数据流用乒乓操作,分流成两个FIFO,一个FIFO的吞吐速度只有原来的一半5M,就可以满足低速的处理方法处理高速的数据,处理后在用合并成一个10M的数据流,top层看起就是10M的处理速度而且数据就被不丢失且高速的处理过
乒乓操作的最大特点是通过 “ 输入数据选择单元 ” 和 “ 输出数据选择单元 ” 按节拍、相互配合的切换,将经过缓冲的数据流没有停顿地送到 “ 数据流运算处理模块 ” 进行运算与处理。把乒乓操作模块当作一个整体,站在这个模块的两端看数据,输入数据流和输出数据流都是连续不断的,没有任何停顿,因此非常适合对数据流进行流水线式处理。所以乒乓操作常常应用于流水线式算法,完成数据的无缝缓冲与处理。
综上所述,乒乓缓存结构实际上相当于一个双口RAM,但它与普通的双口RAM又有所不同。普通双口RAM是单个存储体构成的IC,乒乓ram结构则由包含两个相互独立存储体的多片IC构成,从而使其在结构、速度、容量等方面具有更大的灵活性;若双口在访问同一地址时,普通双口SAM指向的必定是存储体内的同一存储单元,而乒乓ram结构则分别指向属于SRAM1和SRAM2的两个不同的存储单元,更易操作。乒乓缓存结构的上述特点决定了可以相对较便宜的高速大容量SRAM、外围逻辑器件构成比双口RAM以及高速FIFO更适合视频处理的系统所需要的缓冲存储器。
下图为乒乓RAM的模块示意图。先在时钟控制下输入两路信号,经过粗略处理,产生两路数据线和地址线,以及两个RAM模块的控制线,分别控制两个RAM的读和写,并且两个RAM的读(或者写)互锁,即一个若处于读状态,则另一个处于写状态。最后把另一个RAM保存的数据经过一个二选一模块输出,分时复用,产生在时间上连续的数据流输出
代码编写
我们采用乒乓ram结构编写一个模块,ping-pong buffer不是独立的模块,需要根据具体的使用场景进行设计输入输出缓存电路。系统主要根据sel,rden和wren进行操作,当sel拉高时,a、b模块在wren和rden有效时分别写和读;当sel拉低时,a、b模块在rdenwren有效时分别读和写
ram模块
// Engineer: Reborn Lee
// Module Name: single_port_syn_ram
module sram#(
parameter ADDR_WIDTH = 4, //地址位宽
parameter DATA_WIDTH = 8, // 数据位宽
parameter DEPTH = 2**ADDR_WIDTH //ram 列表长度
)(
input i_clk, //时钟信号
input [ADDR_WIDTH - 1 : 0] addr, // ram 地址参数
input [DATA_WIDTH - 1 : 0] din, //这个是一个零时变量法,位宽与ram 一样,用于存放缓存的数据
input wr_en, // 写使能
input rd_en , // output enable,输出使能时RAM读取的结果才能输出
output reg[DATA_WIDTH - 1 : 0] dout
);
reg [DATA_WIDTH - 1 : 0] mem[0 : DEPTH - 1]; //ram 的位宽 是 16 DATA_WIDTH ,列表的长度是 DEPTH 16
// write part //写数据部分
always@(posedge i_clk) begin //在时钟上升沿
if(wr_en&&!rd_en) begin //如果 cs=1 并且 写 wr=1 使能 ,则操作数据的写
mem[addr] <= din; //数据放在缓存寄存器
end
else ;
end
// read part
always@(posedge i_clk) begin //在时钟上升沿
if(rd_en&&!wr_en) begin //如果 cs=1 并且 写 wr=0 使能 ,则操作数据的读数据,这里一共只有两个状态,
//读和写,这里并没有 读使能 read_enable
dout <= mem[addr]; //数据放在缓存寄存器
end
else ;
end
endmodule
乒乓顶层模块:
module m_pp_fifo#(
parameter addr_width = 4, //地址位宽
parameter DATA_WIDTH = 8, // 数据位宽
parameter DEPTH = 2**addr_width //ram 列表长度
)
(input clk,input rstn,
input rden, input wren,
input [addr_width-1:0]raddr,input [addr_width-1:0]waddr,
input [DATA_WIDTH-1:0]din,input sel,
output [DATA_WIDTH - 1:0] dout,dout1,dout2
);
wire[DATA_WIDTH-1:0] im_dout1,im_dout2,rm_dout1,rm_dout2;
wire wr1en,wr2en,rd1en,rd2en;
wire [addr_width-1:0] addr1, addr2;
assign im_dout1=sel?din:'dx;
assign im_dout2=sel?'dx:din;//set input mux
assign wr1en=(sel&&wren);
assign rd1en=(!sel&&rden);
assign wr2en=(!sel&&wren);
assign rd2en=(sel&&rden);//set data operate signal. when sel wr r1 and rd r2,else rd r1 and wr r2
assign addr1 = sel ? waddr : raddr;
assign addr2 = sel ? raddr : waddr;//set data operate addr
reg sel_dl1;
assign dout=(sel_dl1)?rm_dout2:rm_dout1;
assign dout1=rm_dout1,dout2=rm_dout2;
always @(posedge clk or negedge rstn) begin
if(!rstn)
begin
sel_dl1<='d0;
end
else begin
sel_dl1<=sel;
end
end
sram sram_u1(
.i_clk(clk),
.wr_en(wr1en),
.rd_en(rd1en),
.addr(addr1),
.din(im_dout1),
.dout(rm_dout1)
);
sram sram_u2(
.i_clk(clk),
.wr_en(wr2en),
.rd_en(rd2en),
.addr(addr2),
.din(im_dout2),
.dout(rm_dout2)
);
endmodule
testbench文件:
`timescale 1ns/1ps
module m_pp_fifo_tb();
parameter addr_width = 4; //地址位宽
parameter DATA_WIDTH = 8; // 数据位宽
parameter DEPTH = 2**addr_width ; //ram 列表长度
reg clk, rstn,rden, sel,wren;
reg[addr_width-1:0]raddr,waddr;
reg [DATA_WIDTH-1:0]din;
wire [DATA_WIDTH - 1:0] dout,dout1,dout2;
m_pp_fifo pp_fifo_u(
.clk(clk),
.rstn(rstn),
.rden(rden),
.wren(wren),
.sel(sel),
.din(din),
.raddr(raddr),
.waddr(waddr),
.dout(dout),
.dout1(dout1),
.dout2(dout2)
);
initial begin
#0;
clk = 0;
rstn = 1;
#10;
rstn = 0;
#10;
rstn = 1;
end
always
#5 clk = ~clk;
integer i,j;
initial
begin
#0;
wren = 0;
#20;
wren = 1;
repeat(1)
begin
for(i=0;i<16;i=i+1)
begin
@(posedge clk)
begin
waddr = i;
din = i;
end
end
for(i=0;i<16;i=i+1)
begin
@(posedge clk)
begin
waddr = i;
din = 15 - i;
end
end
end
@(posedge clk);
#70 wren = 0;
end
initial
begin
#0;
rden = 0;
#190;
rden = 1;
repeat(3)
for(j=0;j<16;j=j+1)
@(posedge clk)
raddr = j;
@(posedge clk)
rden = 0;
#50;
end
initial
begin
#0;
sel = 1;
#190;
sel = 0;
repeat(2)
#160 sel = ~sel;
end
endmodule
这里对各个端口进行说明 clk是系统时钟, rstn是复位,rden是读使能, sel是选择ram模块,wren是写使能,raddr是读地址,waddr是写地址,din是输入数据, dou是输出数据t,dout1是ram1的输出,dout2是ram2的输出,dout1和dout2可以去掉不影响功能这里保留是为了观察各个信号之间的变化关系
modelsim仿真结果: