最近項目需要用到DDR,于是在網(wǎng)上找相關資料,發(fā)現(xiàn)網(wǎng)上關于Xilinx DDR的資料不多,而且比較老,官方文檔又是純英文,且超級長。所以筆者寫了這篇文章,為像筆者一樣的初學者介紹一下DDR的使用。
在此不介紹DDR是什么了,請自行查資料。(相信用到這篇文章的人不會不知道DDR是啥吧。。。)
好了,閑話休提言歸正傳。
本文使用Vivado 2015.4在Nexys4 DDR(以下簡稱N4DDR)開發(fā)板上實現(xiàn)DDR的讀寫。
FPGA如果需要對DDR進行讀寫,則需要一個DDR的控制器。根據(jù)官方的文檔(UG586,下載鏈接在文末),DDR控制器的時序主要有三:
(1)首先是控制信號,如下圖:
從上圖可以看出,只有當app_rdy信號有效時,程序所發(fā)出的讀寫命令才會被控制器接收。這點必須注意。
(2)然后是寫操作時序,如下圖:
由圖可知,在向DDR寫數(shù)據(jù)時,需要提供寫命令app_cmd、地址app_addr、數(shù)據(jù)app_wdf_data等信號,且寫入的數(shù)據(jù)最多可以比app_cmd提前一個時鐘周期有效,最遲可以比app_cmd晚兩個時鐘周期有效。
【特別注意】在寫數(shù)據(jù)的時候必須檢測app_rdy和app_wdf_rdy信號是否同時有效,否則寫入命令無法成功寫入到DDR控制器的命令FIFO中,從而導致寫操作失敗。
(3)最后是讀操作時序,如下圖所示:
讀操作的時序比較簡單,只需要注意app_rdy是否有效即可,其余不再贅述。
Xilinx在Vivado中提供的Memory Interface Generator的IP核就是我們需要的DDR控制器,如下圖所示。
這里我們可以直接雙擊上面的MIG的IP核,開始例化我們所需的DDR控制器。(此時Win7以后的Windows版本(不含Win7)打開此IP核會報錯,解決方法見 )
打開后是如下圖所示的界面,點Next。
給模塊起個名字,根據(jù)實際情況選擇控制器數(shù)量(這里筆者選擇1),繼續(xù)Next,如下圖所示。
在開發(fā)板芯片型號所對應的方框前打勾,如下圖所示。
根據(jù)開發(fā)板上的DDR芯片選擇DDR的種類,如N4DDR的開發(fā)板上的DDR芯片是DDR2的,因此如下圖選擇。
然后在Clock Period中輸入合適的時鐘周期長度(N4DDR的官方文檔建議DDR的時鐘為325MHz,故此處填3077ps);
接著在Memory Part中選擇開發(fā)板上的DDR芯片的具體型號(N4DDR官方文檔上說明為MT47H64M16HR-25E);
然后輸入Data Width,此處以16為例。如下圖所示。
選擇Input Clock Period,這里填開發(fā)板的系統(tǒng)時鐘(N4DDR為100MHz)。根據(jù)應用需要選擇地址映射方式(這里保持默認的Bank-Row-Column)。
然后,這里的System Clock、Reference Clock建議選擇No Buffer,System Reset Polarity則根據(jù)應用需要靈活選擇(這里設置為低電平有效),如下圖所示。
Internal Termination Impedence的選取應當參考開發(fā)板的官方文檔說明,這里選50歐姆即可,繼續(xù)Next。
選擇Fixed Pin Out。
接下來是DDR芯片的引腳分配。官網(wǎng)應該能找到,這里直接給出。文末會給出與此對應的引腳約束文件(n4ddr_ddr2_io_assign.ucf)。
耐心填完之后點擊Validate按鈕,沒有錯誤的話會彈出一個對話框提示“Current Pinout is valid.”
然后的3個信號建議選擇No connect,后面由我們自己根據(jù)需要連接到板上的相應引腳。
后面一直Next下去,點Accept,然后就可以點擊Generate了。后面會再彈出一個對話框,直接點默認選中的按鈕即可。
好了,下面是筆者自己編寫的測試DDR2讀寫的程序。文末將提供對應工程的下載鏈接。
//*****************************************************************************
// Author : Z.M.J. @ CSE, SEU
// Application : MIG v2.4
// Filename : example_top.v
// Date Created : Fri Dec 30 2016
//
// Device : 7 Series (Nexys 4 DDR)
// Design Name : DDR2 SDRAM
// Purpose : A demo of DDR2's read and write
// Reference : ug586_7Series_MIS_v2.4.pdf
//*****************************************************************************
`timescale 1ps/1ps
module example_top (
// system signals
input sys_rst,
input sys_clk_i,
// application signals
input [15:0] switch_i,
output [15:0] led,
output [7:0] an,
output [7:0] select_seg,
// DDR2 chip signals
inout [15:0] ddr2_dq,
inout [1:0] ddr2_dqs_n,
inout [1:0] ddr2_dqs_p,
output [12:0] ddr2_addr,
output [2:0] ddr2_ba,
output ddr2_ras_n,
output ddr2_cas_n,
output ddr2_we_n,
output [0:0] ddr2_ck_p,
output [0:0] ddr2_ck_n,
output [0:0] ddr2_cke,
output [0:0] ddr2_cs_n,
output [1:0] ddr2_dm,
output [0:0] ddr2_odt
);
parameter DQ_WIDTH = 16;
parameter ECC_TEST = "OFF";
parameter ADDR_WIDTH = 27;
parameter nCK_PER_CLK = 4;
localparam DATA_WIDTH = 16;
localparam PAYLOAD_WIDTH = (ECC_TEST == "OFF") ? DATA_WIDTH : DQ_WIDTH;
localparam APP_DATA_WIDTH = 2 * nCK_PER_CLK * PAYLOAD_WIDTH;
localparam APP_MASK_WIDTH = APP_DATA_WIDTH / 8;
// Wire declarations
reg app_en, app_wdf_wren, app_wdf_end;
reg [2:0] app_cmd;
reg [ADDR_WIDTH-1:0] app_addr;
reg [APP_DATA_WIDTH-1:0] app_wdf_data;
wire [APP_DATA_WIDTH-1:0] app_rd_data;
wire [APP_MASK_WIDTH-1:0] app_wdf_mask;
wire app_rdy, app_rd_data_end, app_rd_data_valid, app_wdf_rdy;
//***************************************************************************
wire [7:0] an;
wire [7:0] select_seg;
reg [31:0] digit_data;
always@ (posedge sys_clk_i) begin
if (switch_i[3])
digit_data <= app_addr;
else case (switch_i[1:0])
2'b00 : digit_data <= read_data[31:0];
2'b01 : digit_data <= read_data[63:32];
2'b10 : digit_data <= read_data[95:64];
2'b11 : digit_data <= read_data[127:96];
endcase
end
digit U2(
.wb_clk_i(sys_clk_i),
.wb_rst_i(~sys_rst),
.wb_dat_i(digit_data),
.an(an),
.select_seg(select_seg)
);
reg [1:0] read_valid = 2'b0;
reg [127:0] read_data = 128'h0;
always@ (posedge app_rd_data_valid) begin
read_data = app_rd_data;
read_valid[0] = (app_rd_data == data0);
read_valid[1] = (app_rd_data == data1);
end
assign led[15] = app_en;
assign led[14] = init_calib_complete;
assign led[13] = app_rdy;
assign led[12] = app_wdf_rdy;
assign led[4] = sys_rst ? read_valid[1] : 1'b0;
assign led[3] = sys_rst ? read_valid[0] : 1'b0;
assign led[2] = stop_w[1];
assign led[1] = stop_w[0];
assign led[0] = app_cmd[0];
reg [15:0] counter = 16'h0;
parameter cnt_init = 16'h1; // minimum: 1
reg [26:0] addr0 = 27'h000_0008;
reg [26:0] addr1 = 27'h003_0100;
reg [127:0] data0 = 128'h1111_2222_3333_4444_5555_6666_7777_8888;
reg [127:0] data1 = 128'h9999_0000_aaaa_bbbb_cccc_dddd_eeee_ffff;
reg [1:0] stop_w = 2'b00;
always@ (posedge sys_clk_i or negedge sys_rst) begin
if (sys_rst == 1'b0) begin
counter = 12'b0;
stop_w = 2'b0;
app_en = 1'b0;
app_addr = 27'h0;
app_cmd = 3'b1;
app_wdf_data = 128'h0;
app_wdf_end = 1'b0;
app_wdf_wren = 1'b0;
end else begin
if (counter == cnt_init && ~stop_w[0])
if (app_rdy & app_wdf_rdy) begin
app_wdf_data = data0;
app_addr = addr0;
app_cmd = 3'b0;
app_wdf_wren = 1'b1;
app_wdf_end = 1'b1;
app_en = 1'b1;
end else // Hold specific signals until app_wdf_rdy is asserted.
counter = counter - 16'h1;
else if (counter == cnt_init + 1 && ~stop_w[0])
if (app_rdy & app_wdf_rdy) begin
app_wdf_end = 1'b0;
app_wdf_wren = 1'b0;
app_en = 1'b0;
app_cmd = 3'b1;
stop_w[0] = 1'b1;
end else // Hold specific signals until app_wdf_rdy is asserted.
counter = counter - 16'h1;
else if (counter == cnt_init + 8 && ~stop_w[1])
if (app_rdy & app_wdf_rdy) begin
app_wdf_data = data1;
app_addr = addr1;
app_cmd = 3'b0;
app_wdf_wren = 1'b1;
app_wdf_end = 1'b1;
app_en = 1'b1;
end else // Hold specific signals until app_wdf_rdy is asserted.
counter = counter - 16'h1;
else if (counter == cnt_init + 9 && ~stop_w[1])
if (app_rdy & app_wdf_rdy) begin
app_wdf_end = 1'b0;
app_wdf_wren = 1'b0;
app_en = 1'b0;
app_cmd = 3'b1;
stop_w[1] = 1'b1;
end else // Hold specific signals until app_wdf_rdy is asserted.
counter = counter - 16'h1;
else if (counter == cnt_init + 88) begin
app_addr = switch_i[2] ? addr1 : addr0;
app_en = 1'b1;
if (~app_rdy) counter = counter - 16'h1;
end else if (counter == cnt_init + 89)
app_en = 1'b0;
counter = counter + 16'h1;
end
end
// Start of User Design top instance
//***************************************************************************
// The User design is instantiated below. The memory interface ports are
// connected to the top-level and the application interface ports are
// connected to the traffic generator module. This provides a reference
// for connecting the memory controller to system.
//***************************************************************************
my_ddr u_my_ddr (
// Memory interface ports
.ddr2_cs_n (ddr2_cs_n),
.ddr2_addr (ddr2_addr),
.ddr2_ba (ddr2_ba),
.ddr2_we_n (ddr2_we_n),
.ddr2_ras_n (ddr2_ras_n),
.ddr2_cas_n (ddr2_cas_n),
.ddr2_ck_n (ddr2_ck_n),
.ddr2_ck_p (ddr2_ck_p),
.ddr2_cke (ddr2_cke),
.ddr2_dq (ddr2_dq),
.ddr2_dqs_n (ddr2_dqs_n),
.ddr2_dqs_p (ddr2_dqs_p),
.ddr2_dm (ddr2_dm),
.ddr2_odt (ddr2_odt),
// Application interface ports
.app_addr (app_addr),
.app_cmd (app_cmd),
.app_en (app_en),
.app_wdf_rdy (app_wdf_rdy),
.app_wdf_data (app_wdf_data),
.app_wdf_end (app_wdf_end),
.app_wdf_wren (app_wdf_wren),
.app_rd_data (app_rd_data),
.app_rd_data_end (app_rd_data_end),
.app_rd_data_valid (app_rd_data_valid),
.app_rdy (app_rdy),
.app_sr_req (1'b0),
.app_ref_req (1'b0),
.app_zq_req (1'b0),
.app_wdf_mask (16'h0000),
.init_calib_complete (init_calib_complete),
// System Clock Ports
.sys_clk_i (sys_clk_i),
// Reference Clock Ports
.clk_ref_i (sys_clk_i),
.sys_rst (sys_rst)
);
endmodule
保存后直接生成比特流就可以下板驗證了。
在摸索過程中筆者發(fā)現(xiàn),寫入了數(shù)據(jù)之后最快要到發(fā)出寫命令的第8個系統(tǒng)時鐘才能讀出所寫入的數(shù)據(jù),且讀操作必須在寫操作后經(jīng)過8的整數(shù)倍個時鐘后進行。有時將比特流下載到N4DDR上面之后讀寫的數(shù)據(jù)有誤,但是重啟開發(fā)板再重新下載即可解決問題,知道個中緣由的朋友歡迎在評論中告知筆者,筆者在此先行謝過。
需要說明的是,此處突發(fā)長度(BL)為8,因此app_addr必須是8對齊的地址。同時,由于前面選擇的Data Width為16,因此每次讀寫數(shù)據(jù)的長度為8*16bit==128bit。
-
DDR
+關注
關注
11文章
704瀏覽量
65164 -
Vivado
+關注
關注
19文章
804瀏覽量
66224
發(fā)布評論請先 登錄
相關推薦
評論