Verilog--带双向握手的乒乓BUFFER

  网上没什么比较好的乒乓sram设计,有的还需要收费,于是自己写了一个Verilog源码,与大家讨论与学习。

 

一:介绍

“ 乒乓操作” 是一个常常应用于数据流控制的处理技巧, 典型的乒乓操作方法如图 1 所示。

 

 

乒乓操作的处理流程为:输入数据流通过“ 输入数据选择单元” 将数据流等时分配到两个数据缓冲区, 数据缓冲模块可以为任何存储模块, 比较常用的存储单元为双口RAM(DPRAM)、单口RAM(SPRAM)、FIFO等。

 

在第 1个缓冲周期,将输入的数据流缓存到“ 数据缓冲模块1” ;

在第2 个缓冲周期, 通过“ 输入数据选择单元” 的切换, 将输入的数据流缓存到“ 数据缓冲模块2” , 同时将“ 数据缓冲模块1” 缓存的第1 个周期数据通过“ 输入数据选择单元” 的选择, 送到“ 数据流运算处理模块” 进行运算处理;

在第3 个缓冲周期通过“ 输入数据选择单元” 的再次切换,将输入的数据流缓存到“ 数据缓冲模块1” ,同时将“ 数据缓冲模块2”缓存的第2 个周期的数据通过“ 输入数据选择单元” 切换,送到“ 数据流运算处理模块” 进行运算处理。 如此循环。

 

乒乓操作的最大特点是通过“ 输入数据选择单元” 和“ 输出数据选择单元” 按节拍、相互配合的切换, 将经过缓冲的数据流没有停顿地送到“ 数据流运算处理模块” 进行运算与处理。


把乒乓操作模块当做一个整体, 站在这个模块的两端看数据, 输入数据流和输出数据流都是连续不断的, 没有任何停顿, 因此非常适合对数据流进行流水线式处理。 所以乒乓操作常常应用于流水线式算法, 完成数据的无缝缓冲与处理。

 

二:设计思想

本次设计的乒乓buffer因为需要存储的数据量较大,使用了两个单端口的sram作为存储,而不是reg(当然要改为reg岂不是更简单)。sram前后有两个2选1MUX做选择,附带有前后级ready&valid握手信号,使用灵活。

在这里就不给sram model的源码了,自己在vivado中插入IP就好了。

源码如下:

module pp_buffer(
//Output 
rd_rdy , wr_rdy , rd_vld , rdata,
//Input
clk , rst_n , rd_en , wr_en , wr_be, raddr , wdata ,waddr);

parameter DEPTH = 64;
parameter DATAWIDTH = 128;

localparam ADDRWIDTH = clogb2(DEPTH-1);
localparam IDLE = 4'b0001;
localparam WRAM1 = 4'b0010;
localparam WRAM2_RRAM1 = 4'b0100;
localparam WRAM1_RRAM2 = 4'b1000;

//----------INPUT/OUTPUT--------------
input clk;
input rst_n;
input rd_en;
input wr_en;
input [DATAWIDTH/8-1:0] wr_be;

output reg rd_rdy;
output reg wr_rdy;
output reg rd_vld;

output reg [DATAWIDTH-1:0] rdata;
input [ADDRWIDTH-1:0] raddr;
input [DATAWIDTH-1:0] wdata;
input [ADDRWIDTH-1:0] waddr;
//---------------reg definitions----------------//
wire [DATAWIDTH-1:0] rdata1;
reg cen1;
reg [DATAWIDTH/8-1:0] wen1;
reg [ADDRWIDTH-1:0] addr1;
reg [DATAWIDTH-1:0] wdata1;

wire [DATAWIDTH-1:0] rdata2;
reg cen2;
reg [DATAWIDTH/8-1:0] wen2;
reg [ADDRWIDTH-1:0] addr2;
reg [DATAWIDTH-1:0] wdata2;

reg [3:0] state,next_state;
reg rd_vld_r;
reg [3:0] state_r;
reg wr_en_r;
//---------------FSM-----------------------//
always@(*)begin
    case(state)
        IDLE:begin
            if(wr_en == 1'b1)
                next_state = WRAM1;
            else
                next_state = IDLE;
        end
        WRAM1:begin
            if(addr1 == {(ADDRWIDTH){1'b1}})
                next_state = WRAM2_RRAM1;
            else
                next_state = WRAM1;
        end
        WRAM2_RRAM1:begin
            if((addr1 == {(ADDRWIDTH){1'b1}}) && (addr2 == {(ADDRWIDTH){1'b1}}) && wr_en_r)
                next_state = WRAM1_RRAM2;
            else
                next_state = WRAM2_RRAM1;                
        end
        WRAM1_RRAM2:begin
            if((addr1 == {(ADDRWIDTH){1'b1}}) && (addr2 == {(ADDRWIDTH){1'b1}}) && wr_en_r)
                next_state = WRAM2_RRAM1;
            else
                next_state = WRAM1_RRAM2;            
        end
        default:next_state = IDLE;
    endcase
end

always@(posedge clk or negedge rst_n)begin
    if(!rst_n)
        state <= IDLE;
    else begin
        state <= next_state; 
end

//rdata
always@(*)begin
    if(state_r == WRAM2_RRAM1) begin
        rdata = rdata1;
    end
    else if(state_r == WRAM1_RRAM2) begin
        rdata = rdata2;
    end
end
//RAM1 wr_be 

//RAM2 wr_be 

//RAM1 cen waddr wdata

//RAM1 cen waddr wdata

//---------------sub module----------------//
sp_mem#(.DEPTH(DEPTH), .DATAWIDTH(DATAWIDTH))
u_sram0(
.Q(rdata1),
.CLK(clk),
.CEN(cen1),
.WEN(wen1),
.A(addr1),
.D(wdata1)
);

sp_mem#(.DEPTH(DEPTH), .DATAWIDTH(DATAWIDTH))
u_sram1(
.Q(rdata2),
.CLK(clk),
.CEN(cen2),
.WEN(wen2),
.A(addr2),
.D(wdata2)
);

function integer clogb2 (input integer depth);
    integer depth_t;
    begin
        depth_t = depth;
            for(clogb2 = 0; depth_t>0; clogb2 = clogb2+1)
                depth_t = depth_t >>1;
    end
endfunction

endmodule

 上述代码的缺陷在于状态机控制逻辑还是写复杂了,并且入sram时进行了打拍,出sram时没有打拍,对Timing不友好。这里我还在后级input 了rd_en信号,如果要简化其实可以砍掉,直接valid输出,out_rdy握手就好。

 

待有时间了补全源码

 

还有种使用fifo控制乒乓buffer读写的逻辑,大大简化了这版代码~,大家可以思考一下~

posted @ 2021-10-18 14:44  will_w  阅读(4864)  评论(0编辑  收藏  举报