1. 程式人生 > >Vivado使用技巧(27):RAM編寫技巧

Vivado使用技巧(27):RAM編寫技巧

Vivado綜合可以理解多種多樣的RAM編寫方式,將其對映到分散式RAM塊RAM中。兩種實現方法在向RAM寫入資料時都是採取同步方式,區別在於從RAM讀取資料時,分散式RAM採用非同步方式,塊RAM採用同步方式。使用RAM_STYLE屬性可以強制規定使用哪種方法實現RAM。

Xilinx FPGA的記憶體介面具有如下特性:

  • 支援任意大小的深度和資料寬度(綜合時會使用一個或多個RAM原語實現);
  • 單端、偽雙端、真雙端三種模式;
  • 最多可以使用兩個寫埠;
  • 可以存在多個讀埠;
  • 支援寫使能訊號
  • 塊RAM支援RAM使能、資料輸出復位、可選的輸出暫存器和位元組寫使能;
  • 每個RAM埠可以由獨立的時鐘、埠使能、寫使能和資料輸出復位控制;
  • 按設定的值初始化RAM內容;
  • Vivado綜合可以將校驗位作為常規資料位,以適應描述的資料寬度。

另外目前最新的UltraScale架構的FPGA中還有專用的UltraRAM資源,這裡不做過多介紹。下面給出幾個各種實現方式的Verilog示例程式碼。

分散式RAM

下面給出一個非同步讀模式的雙口分散式RAM的示例:

// 非同步讀模式的雙口分散式RAM
module rams_dist (
    input clk, we, 
    input [5:0]a, dpra, 
    input [15:0]di, 
    output [15:0]spo, dpo
);

reg [15:0] ram [63:
0]; always @(posedge clk) if (we) ram[a] <= di; assign spo = ram[a]; assign dpo = ram[dpra]; endmodule

單端塊RAM

塊RAM支援3種不同的讀寫同步模式,解決同時讀寫同一地址的情況,每一個讀、寫埠都可以配置為:

  • Write-First模式:新內容載入時可以馬上被讀取;
  • Read-First模式:新內容載入時,先讀取舊的內容;
  • No-Change模式:新內容載入時,不讀取該地址的內容(即維持之前的值不變);

下面給出三種模式的單端塊RAM Verilog示例程式碼:

// 資料輸出可復位的單端塊RAM,Read_first
module rams_sp_rf_rst (
    input clk, en, we, rst, 
    input [9:0]addr, 
    input [15:0]di, 
    output reg [15:0]dout
);

reg [15:0] ram [1023:0];

always @(posedge clk)
    if (en) begin //塊RAM使能
        if (we) ram[addr] <= di; //寫使能
        if (rst) dout <= 0;   //輸出復位
        else dout <= ram[addr];
    end

endmodule


// 寫優先模式的單端塊RAM,Wrist_first
module rams_sp_wf (
    input clk, en, we, 
    input [9:0]addr, 
    input [15:0]di, 
    output reg [15:0]dout
);

reg [15:0] ram [1023:0];

always @(posedge clk)
    if (en) begin
        if (we) begin
            RAM[addr] <= di;
            dout <= di;
        end
        else dout <= RAM[addr];
    end

endmodule


// No-Change模式的單端塊RAM
module rams_sp_wf (
    input clk, en, we, 
    input [9:0]addr, 
    input [15:0]di, 
    output reg [15:0]dout
);

reg [15:0] ram [1023:0];

always @(posedge clk)
    if (en) begin
        if (we) RAM[addr] <= di;
        else dout <= RAM[addr];
    end

endmodule

偽雙端塊RAM

下面給出偽雙端塊RAM的Verilog 示例程式碼:

// 單時鐘控制,偽雙端塊RAM
module simple_dual_one_clock (
    input clk,ena,enb,wea,
    input [9:0]addra,addrb,
    input [15:0]dia,
    output reg [15:0] dob
);

reg [15:0] ram [1023:0];

always @(posedge clk)  //寫
    if (ena) 
        if (wea) ram[addra] <= dia;

always @(posedge clk) 
    if (enb) dob <= ram[addrb];  //讀

endmodule


// 雙時鐘控制,偽雙端塊RAM
module simple_dual_two_clocks (
    input clka,clkb,ena,enb,wea,
    input [9:0] addra,addrb,
    input [15:0]dia,
    output reg [15:0]dob
);

reg [15:0] ram [1023:0];

always @(posedge clka)  //寫
    if (ena)
        if (wea) ram[addra] <= dia;

always @(posedge clkb)
    if (enb) dob <= ram[addrb];  //讀

endmodule

真雙端塊RAM

下面給出兩個真雙端塊RAM的Verilog 示例程式碼:

// 帶有兩個寫埠的雙端塊RAM
module rams_tdp_rf_rf (
    input clka,clkb,ena,enb,wea,web,
    input [9:0] addra,addrb,
    input [15:0]dia,dib,
    output reg [15:0] doa,dob
);

reg [15:0] ram [1023:0];

always @(posedge clka)      //埠1
    if (ena) begin
        if (wea) ram[addra] <= dia;
        doa <= ram[addra];
    end

always @(posedge clkb)      //埠2
    if (enb) begin
        if (web) ram[addrb] <= dib;
        dob <= ram[addrb];
    end

endmodule


// 帶有可選輸出暫存器的塊RAM
module rams_pipeline (
    input clk1, clk2, we, en1, en2, 
    input [9:0] addr1, addr2, 
    input [15:0] di, 
    output reg [15:0] res1, res2
);

reg [15:0] RAM [1023:0];
reg [15:0] do1;
reg [15:0] do2;

always @(posedge clk1) begin  //埠1可讀可寫
    if (we == 1'b1) RAM[addr1] <= di;
    do1 <= RAM[addr1];
end

always @(posedge clk2)      //埠2只用於讀
    do2 <= RAM[addr2];

always @(posedge clk1)
    if (en1 == 1'b1) res1 <= do1;

always @(posedge clk2)
    if (en2 == 1'b1) res2 <= do2;

endmodule

初始化RAM內容

除了上面介紹的一些基本模式外,Xilinx的RAM還包括位元組使能模式、非對稱RAM(即讀寫資料的位寬不同)、3D RAM,等後面用到時再做補充。下面介紹一下如何初始化RAM的內容。初始化工作可以在HDL原始碼中進行,也可以利用外部資料檔案設定。

比如Verilog中可以使用下面程式碼將RAM全部初始化為一個值:

reg [DATA_WIDTH-1:0] ram [DEPTH-1:0];
integer i;
initial for (i=0; i < DEPTH; i=i+1) ram[i] = 0;

或者使用Verilog中的檔案讀取函式從外部資料檔案中獲取RAM初始化資料。資料檔案必須是ASCII文字檔案,每一行表示RAM中的一個地址,因此檔案的行數要與RAM的深度對應。檔案中資料應用2進位制或16進製表示,不能混合使用。除了資料外不能有其它任何內容(包括註釋)。一個檔案示例如下:

00001110110000011001111011000110
00101011001011010101001000100011
01110100010100011000011100001111
01000001010000100101001110010100

對應一個4*32bit的RAM初始化內容。通常將這種格式稱之為bit向量格式,與整數格式或hex格式作區別。Verilog程式碼中使用如下示例呼叫該檔案:

reg [31:0] ram [0:3];

initial begin
    $readmemb("ram.data", ram, 0, 3);
end

如果檔案用16進制定義資料則應使用$readmemh系統任務。下面給出一個直接在HDL中賦值初始化塊RAM的完整Verilog示例:

module rams_sp_rom (
    input clk, we, 
    input [5:0]addr, 
    input [19:0]di, 
    output reg [19:0] dout
);

reg [19:0] ram [63:0];

initial begin
ram[63] = 20'h0200A; ram[62] = 20'h00300; ram[61] = 20'h08101;
ram[60] = 20'h04000; ram[59] = 20'h08601; ram[58] = 20'h0233A;
ram[57] = 20'h00300; ram[56] = 20'h08602; ram[55] = 20'h02310;
ram[54] = 20'h0203B; ram[53] = 20'h08300; ram[52] = 20'h04002;
ram[51] = 20'h08201; ram[50] = 20'h00500; ram[49] = 20'h04001;
ram[48] = 20'h02500; ram[47] = 20'h00340; ram[46] = 20'h00241;
ram[45] = 20'h04002; ram[44] = 20'h08300; ram[43] = 20'h08201;
ram[42] = 20'h00500; ram[41] = 20'h08101; ram[40] = 20'h00602;
ram[39] = 20'h04003; ram[38] = 20'h0241E; ram[37] = 20'h00301;
ram[36] = 20'h00102; ram[35] = 20'h02122; ram[34] = 20'h02021;
ram[33] = 20'h00301; ram[32] = 20'h00102; ram[31] = 20'h02222;
ram[30] = 20'h04001; ram[29] = 20'h00342; ram[28] = 20'h0232B;
ram[27] = 20'h00900; ram[26] = 20'h00302; ram[25] = 20'h00102;
ram[24] = 20'h04002; ram[23] = 20'h00900; ram[22] = 20'h08201;
ram[21] = 20'h02023; ram[20] = 20'h00303; ram[19] = 20'h02433;
ram[18] = 20'h00301; ram[17] = 20'h04004; ram[16] = 20'h00301;
ram[15] = 20'h00102; ram[14] = 20'h02137; ram[13] = 20'h02036;
ram[12] = 20'h00301; ram[11] = 20'h00102; ram[10] = 20'h02237;
ram[9] = 20'h04004; ram[8] = 20'h00304; ram[7] = 20'h04040;
ram[6] = 20'h02500; ram[5] = 20'h02500; ram[4] = 20'h02500;
ram[3] = 20'h0030D; ram[2] = 20'h02341; ram[1] = 20'h08201;
ram[0] = 20'h0400D;
end

always @(posedge clk) begin
    if (we) 
        ram[addr] <= di;
    dout <= ram[addr];
end

endmodule

再給出一個用外部檔案初始化塊RAM的完整Verilog示例:

module rams_init_file (
    input clk, we, 
    input [5:0]addr, 
    input [31:0]din, 
    output reg [31:0]dout
);

reg [31:0] ram [0:63];

initial begin
    $readmemb("rams_init_file.data",ram);
end

always @(posedge clk) begin
    if (we)
        ram[addr] <= din;
    dout <= ram[addr];
end

endmodule