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
相關文章
- Vivado使用技巧(26):HDL編寫技巧
- Vivado使用技巧(19):使用Vivado Simulator
- Vivado使用技巧(10):編輯與改寫IP核原始檔
- Vivado使用技巧(5):屬性編輯器的使用
- Vivado使用技巧(17):建立IBIS模型模型
- Vivado使用技巧(6):Messages視窗管理
- Vivado使用技巧(29):約束功能概述
- Vivado使用技巧(20):Waveform功能詳解ORM
- Vivado使用技巧(18):模擬功能概述
- Vivado使用技巧(33):時序異常
- Vivado使用技巧(25):Block Synthesis技術BloC
- Vivado使用技巧(9):COE檔案使用方法
- Vivado使用技巧(4):查詢功能詳解
- Vivado使用技巧(3):Force Up-to-Date功能
- Vivado使用技巧(11):設定FPGA配置模式FPGA模式
- Vivado使用技巧(8):Core Container打包IP核AI
- Vivado使用技巧(34):路徑分割現象
- Vivado使用技巧(28):支援的Verilog語法
- Vivado使用技巧(30):使用時序約束嚮導
- Vivado使用技巧(16):SSN轉換噪聲分析
- Vivado使用技巧(14):IO規劃方法詳解
- Vivado使用技巧(31):時鐘的約束方法
- Vivado使用技巧(23):綜合執行與OOC
- Vivado使用技巧(21):模擬中的Debug特性
- Vivado使用技巧(15):DRC設計規則檢查
- Vivado使用技巧(13):CSV檔案定義IO Ports
- Vivado使用技巧(32):IO延遲的約束方法
- Hbuilder快速程式碼編寫技巧UI
- Markdown 編寫技巧彙總(一)
- Vivado使用技巧(7):使用IP核自帶Testbench進行模擬
- VSCode使用技巧,程式碼編寫效率提升2倍以上!VSCode
- MathType數學公式編寫技巧分享公式
- 效能測試報告編寫技巧測試報告
- Vivado使用技巧(24):HDL/XDC中設定綜合屬性
- Vivado使用技巧(22):綜合策略與設定的選擇
- Vivado使用技巧(12):設定DCI與內部參考電壓
- Linux編寫Bash指令碼的10個技巧Linux指令碼
- Python 工匠:編寫條件分支程式碼的技巧Python