最近項(xiàng)目需要用到DDR,于是在網(wǎng)上找相關(guān)資料,發(fā)現(xiàn)網(wǎng)上關(guān)于Xilinx DDR的資料不多,而且比較老,官方文檔又是純英文,且超級(jí)長。所以筆者寫了這篇文章,為像筆者一樣的初學(xué)者介紹一下DDR的使用。
在此不介紹DDR是什么了,請(qǐng)自行查資料。(相信用到這篇文章的人不會(huì)不知道DDR是啥吧。。。)
好了,閑話休提言歸正傳。
本文使用Vivado 2015.4在Nexys4 DDR(以下簡稱N4DDR)開發(fā)板上實(shí)現(xiàn)DDR的讀寫。
FPGA如果需要對(duì)DDR進(jìn)行讀寫,則需要一個(gè)DDR的控制器。根據(jù)官方的文檔(UG586,下載鏈接在文末),DDR控制器的時(shí)序主要有三:
(1)首先是控制信號(hào),如下圖:
從上圖可以看出,只有當(dāng)app_rdy信號(hào)有效時(shí),程序所發(fā)出的讀寫命令才會(huì)被控制器接收。這點(diǎn)必須注意。
(2)然后是寫操作時(shí)序,如下圖:
由圖可知,在向DDR寫數(shù)據(jù)時(shí),需要提供寫命令app_cmd、地址app_addr、數(shù)據(jù)app_wdf_data等信號(hào),且寫入的數(shù)據(jù)最多可以比app_cmd提前一個(gè)時(shí)鐘周期有效,最遲可以比app_cmd晚兩個(gè)時(shí)鐘周期有效。
【特別注意】在寫數(shù)據(jù)的時(shí)候必須檢測(cè)app_rdy和app_wdf_rdy信號(hào)是否同時(shí)有效,否則寫入命令無法成功寫入到DDR控制器的命令FIFO中,從而導(dǎo)致寫操作失敗。
(3)最后是讀操作時(shí)序,如下圖所示:
讀操作的時(shí)序比較簡單,只需要注意app_rdy是否有效即可,其余不再贅述。
Xilinx在Vivado中提供的Memory Interface Generator的IP核就是我們需要的DDR控制器,如下圖所示。
這里我們可以直接雙擊上面的MIG的IP核,開始例化我們所需的DDR控制器。(此時(shí)Win7以后的Windows版本(不含Win7)打開此IP核會(huì)報(bào)錯(cuò),解決方法見 )
打開后是如下圖所示的界面,點(diǎn)Next。
給模塊起個(gè)名字,根據(jù)實(shí)際情況選擇控制器數(shù)量(這里筆者選擇1),繼續(xù)Next,如下圖所示。
在開發(fā)板芯片型號(hào)所對(duì)應(yīng)的方框前打勾,如下圖所示。
根據(jù)開發(fā)板上的DDR芯片選擇DDR的種類,如N4DDR的開發(fā)板上的DDR芯片是DDR2的,因此如下圖選擇。
然后在Clock Period中輸入合適的時(shí)鐘周期長度(N4DDR的官方文檔建議DDR的時(shí)鐘為325MHz,故此處填3077ps);
接著在Memory Part中選擇開發(fā)板上的DDR芯片的具體型號(hào)(N4DDR官方文檔上說明為MT47H64M16HR-25E);
然后輸入Data Width,此處以16為例。如下圖所示。
選擇Input Clock Period,這里填開發(fā)板的系統(tǒng)時(shí)鐘(N4DDR為100MHz)。根據(jù)應(yīng)用需要選擇地址映射方式(這里保持默認(rèn)的Bank-Row-Column)。
然后,這里的System Clock、Reference Clock建議選擇No Buffer,System Reset Polarity則根據(jù)應(yīng)用需要靈活選擇(這里設(shè)置為低電平有效),如下圖所示。
Internal Termination Impedence的選取應(yīng)當(dāng)參考開發(fā)板的官方文檔說明,這里選50歐姆即可,繼續(xù)Next。
選擇Fixed Pin Out。
接下來是DDR芯片的引腳分配。官網(wǎng)應(yīng)該能找到,這里直接給出。文末會(huì)給出與此對(duì)應(yīng)的引腳約束文件(n4ddr_ddr2_io_assign.ucf)。
耐心填完之后點(diǎn)擊Validate按鈕,沒有錯(cuò)誤的話會(huì)彈出一個(gè)對(duì)話框提示“Current Pinout is valid.”
然后的3個(gè)信號(hào)建議選擇No connect,后面由我們自己根據(jù)需要連接到板上的相應(yīng)引腳。
后面一直Next下去,點(diǎn)Accept,然后就可以點(diǎn)擊Generate了。后面會(huì)再彈出一個(gè)對(duì)話框,直接點(diǎn)默認(rèn)選中的按鈕即可。
好了,下面是筆者自己編寫的測(cè)試DDR2讀寫的程序。文末將提供對(duì)應(yīng)工程的下載鏈接。
//*****************************************************************************
// 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
保存后直接生成比特流就可以下板驗(yàn)證了。
在摸索過程中筆者發(fā)現(xiàn),寫入了數(shù)據(jù)之后最快要到發(fā)出寫命令的第8個(gè)系統(tǒng)時(shí)鐘才能讀出所寫入的數(shù)據(jù),且讀操作必須在寫操作后經(jīng)過8的整數(shù)倍個(gè)時(shí)鐘后進(jìn)行。有時(shí)將比特流下載到N4DDR上面之后讀寫的數(shù)據(jù)有誤,但是重啟開發(fā)板再重新下載即可解決問題,知道個(gè)中緣由的朋友歡迎在評(píng)論中告知筆者,筆者在此先行謝過。
需要說明的是,此處突發(fā)長度(BL)為8,因此app_addr必須是8對(duì)齊的地址。同時(shí),由于前面選擇的Data Width為16,因此每次讀寫數(shù)據(jù)的長度為8*16bit==128bit。
-
DDR
+關(guān)注
關(guān)注
11文章
731瀏覽量
66362 -
Vivado
+關(guān)注
關(guān)注
19文章
828瀏覽量
68198
發(fā)布評(píng)論請(qǐng)先 登錄
基于Digilent的Arty Artix-35T FPGA開發(fā)板的DDR3讀寫控制

基于Arty Artix-35T FPGA開發(fā)板的DDR3和mig介紹

開發(fā)板的PL端有4片的DDR,怎么管理數(shù)據(jù)的傳輸
如何在Kintex7 KC705評(píng)估板上實(shí)現(xiàn)一個(gè)UART模塊?
使用Vivado VHDL無法運(yùn)行第一個(gè)bitfile
如何在Nexys4ddr中使用DDR內(nèi)存模塊
verilog實(shí)現(xiàn)步進(jìn)電機(jī)脈沖分配器(三相六拍)基于Nexys4DDR開發(fā)板 相關(guān)資料下載
電腦無法識(shí)別nexys4開發(fā)板
基于Nexys4開發(fā)板的VGA顯示實(shí)驗(yàn)(使用ISE 14.3)
7 FPGA進(jìn)階級(jí)智能互聯(lián)開發(fā)板
Digilent公司發(fā)布新款FPGA開發(fā)板
一般涉及DDR讀寫仲裁的控制邏輯需要注意哪些方面
Virtex7上DDR3的測(cè)試例程
AXI通道讀寫DDR的阻塞問題?

評(píng)論