FPGA基礎設計(7)雙口RAM乒乓操作


雙口RAM經常用於跨時鍾域處理,且比FIFO靈活性更大。本文給出一個具體的設計實例,讓大家理解雙口RAM在跨時鍾域處理中乒乓操作的用法。

輸入數據速率20MHz,輸出數據速率100Mhz,使用雙口RAM完成跨時鍾域處理。一次傳輸的數據為1024個,假設數據位寬為8bit,使用兩片寬度為8、深度為1024的雙口RAM完成數據傳輸。

使用乒乓操作提高讀寫效率,寫RAM1時,讀取RAM2中的數據;寫RAM2時,讀取RAM1中的數據。數據讀取速率為數據寫入速率的5倍,因此寫數據端可以一直保持數據寫入,而讀數據端按寫入一組數據時間的1/5進行,使用out_valid信號表示讀出的數據有效。

Vivado環境下,RAM使用Block Memory Generator IP核配置,存儲類型選擇為“True Dual Port RAM”。RAM在讀取數據時根據配置參數會有一定的延遲,設計時要注意時序對齊(否則會容易丟掉開頭或結尾的一些數據),代碼如下:

`timescale 1ns / 1ps

module DualRAM
(
    input clk_wr,      //寫時鍾速率20Mhz
    input clk_rd,      //讀時鍾速率100Mhz
    input rst_n,
    input [7:0] din,   
    output reg out_valid,
    output reg [7:0] dout
);

reg [9:0] addr_wr, addr_rd;
reg en_wr1, en_wr2, we_wr1, we_wr2, en_rd1, en_rd2;
wire [7:0] dout1, dout2;

dual_port_ram u1 (
  .clka(clk_wr),      //寫端口
  .ena(en_wr1),    
  .wea(we_wr1),      
  .addra(addr_wr),  
  .dina(din),   
  .douta(), 
  .clkb(clk_rd),      //讀端口
  .enb(en_rd1),      
  .web(1'b0),      
  .addrb(addr_rd),  
  .dinb(8'd0),    
  .doutb(dout1) 
);

dual_port_ram u2 (
  .clka(clk_wr),      //寫端口
  .ena(en_wr2),    
  .wea(we_wr2),      
  .addra(addr_wr),  
  .dina(din),   
  .douta(), 
  .clkb(clk_rd),      //讀端口
  .enb(en_rd2),      
  .web(1'b0),      
  .addrb(addr_rd),  
  .dinb(8'd0),    
  .doutb(dout2) 
);

//寫端口乒乓操作
always @ (posedge clk_wr)     //寫地址信號控制0~1023
    if (!rst_n) addr_wr <= 1023;
    else addr_wr <= addr_wr + 1'b1;

always @ (posedge clk_wr)     //輪流寫RAM1與RAM2
    if (!rst_n) begin we_wr1 <= 1'b1; we_wr2 <= 1'b0;
        en_wr1 <= 1'b1; en_wr2 <= 1'b0;  end
    else if (addr_wr == 1023) begin
        we_wr1 <= ~we_wr1; we_wr2 <= ~we_wr2;
        en_wr1 <= ~en_wr1; en_wr2 <= ~en_wr2;
    end 
 
//讀端口乒乓操作
always @ (posedge clk_rd)    //讀地址信號控制0~1023
    if (!rst_n) addr_rd <= 1021;  //匹配延遲
    else addr_rd <= addr_rd + 1'b1; 
    
reg [15:0] cnt;
always @ (posedge clk_rd)    //讀時鍾為寫時鍾的5倍
    if (!rst_n) cnt <= 16'hFFFE;  //匹配延遲
    else if (cnt == 5119) cnt <= 0;
    else cnt <= cnt + 1'b1;
    
reg flag1, flag2;
always @ (posedge clk_rd)    //讀RAM標志,RAM1或RAM2
    if (!rst_n) begin flag1 <= 1'b1; flag2 <= 1'b0; end
    else if (cnt == 5119) begin flag1 = ~flag1; flag2 = ~flag2; end    
    else begin flag1 <= flag1; flag2 <= flag2; end
    
always @ (posedge clk_rd)    //讀RAM使能,選擇cnt的前1/5時間讀取
    if (!rst_n) begin en_rd1 <= 1'b1; en_rd2 <= 1'b0; end 
    else if (cnt < 1024) begin en_rd1 <= flag1; en_rd2 <= flag2; end
    else begin en_rd1 <= 1'b0; en_rd2 <= 1'b0; end

reg en_rd1_reg, en_rd2_reg;
always @ (posedge clk_rd)    //延遲一級,匹配時序
    if (!rst_n) begin en_rd1_reg <= 0; en_rd1_reg <= en_rd1_reg; end
    else begin en_rd1_reg <= en_rd1; en_rd2_reg <= en_rd2; end

always @ (posedge clk_rd)    //輸出選擇,RAM1或RAM2;控制輸出使能信號
    if (!rst_n) begin dout <= 0; out_valid <= 0; end
    else if (en_rd1_reg) begin dout <= dout1; out_valid <= 1; end
    else if (en_rd2_reg) begin dout <= dout2; out_valid <= 1; end
    else begin dout <= 0; out_valid <= 0; end
    
endmodule

整體仿真結果如下圖所示,看以看到整個乒乓操作的流程。實際上由於時序問題,仿真時經歷了很多階段,總體而言首先要仿真寫時序能准確地把數據寫入到RAM中,然后再仿真讀時序能正確讀取數據。
在這里插入圖片描述
讀時鍾clk_rd是寫時鍾clk_wr的5倍。寫端口Write Port部分,一直保持數據寫入,不停在兩片RAM間切換:en_wr1和we_wr1控制RAM1的寫入,第一個douta為RAM1的A端口讀出的數據;en_wr2和we_wr2控制RAM2的寫入,第二個douta為RAM2的A端口讀出的數據。

讀端口Read Port部分,en_rd1和en_rd2為RAM1和RAM2的讀使能信號,輪流開啟。dout1和dout2為RAM1和RAM2的讀取數據,同樣輪流輸出有效。dout即為整體設計的輸出數據,在dout1和dout2之間切換選擇。當dout輸出有效時,out_valid信號置高。

查看前兩組寫入的數據,第一組為“24、81、09…2f、44、9b”寫入RAM2,第二組為“62、e9、2e…59、d7、87”寫入RAM1,如下圖所示:
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述
查看輸出dout和有效信號valid如下圖:
在這里插入圖片描述
在這里插入圖片描述
上圖為第一組數據從RAM2中讀取,讀出數據為“24、81、09…2f、44、9b”,同時out_valid信號置高。
在這里插入圖片描述
在這里插入圖片描述
上圖為第二組數據從RAM1中讀取,讀出數據為“62、e9、2e…59、d7、87”,同時out_valid信號置高。可以看到輸出數據保持連貫性,沒有漏掉數據,表明設計正確。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM