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读写的逻辑,大大简化了这版代码~,大家可以思考一下~