網上沒什么比較好的乒乓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讀寫的邏輯,大大簡化了這版代碼~,大家可以思考一下~