轉自:
https://blog.csdn.net/MaoChuangAn/article/details/85268231
想要自己學習MIG控制器已經很久了,剛開始學習的時候也是在網上到處搜索MIG控制器的資料,深知學習過程的不容易。因此本系列的教程一定會詳細的寫出關於MIG控制器的相關知識,方便大家一起學習。有問題的朋友可以在下方留言,一起學習和討論。
關於DDR3的基本知識在這里我就不詳細說了,只有在相關的地方會提上一嘴。本教程的目的只是教會大家如何使用MIG控制器,大家一定不要覺得MIG控制器有多難,其實很簡單的,跟着我在心里默念“MIG就像BRAM一樣簡單”。確實哈,當你回過頭來看,MIG控制器的使用基本和BRAM的使用方法很像。
話不多說了,那么這第一個系列,我就先交大家來例化一個MIG控制器。
VIVADO版本:2016.4
芯片:zynq7035
1、打開vivado,新建一個工程,工程路徑和名字自己定。
2、點擊左側的“IP Catalog”。
3、輸入“MIG”,搜索MIG控制器。
4、雙擊“MIG”控制器,對MIG控制器進行設置。
5、然后會出來一個MIG控制器編輯界面,如下。直接NEXT.
6、選擇“Create Design”,然后Next。(Number of Controllers 指的是你要幾個控制器,AXI4 Interface指的是MIG是AXI4接口的,其他的選項你不用了解了)
7、 這里問的是你要不要兼容其他芯片,這里不用兼容,直接Next。
8、 選擇“DDR3 SDRAM”,然后Next
9、這個界面很重要,有很多東西需要理解。按照如下設置,其他不變,然后Next。
解釋:1、這里的400MHz指的是在DDR3這個芯片里面實際跑的時鍾頻率
2、4:1,指的是 DDR3時鍾頻率 : MIG控制器給用戶的時鍾頻率 = 4:1。也就是,如果你設置DDR3的工作頻率是400MHz,那么MIG控制器會給你一個100MHz的用戶時鍾。那這個用戶時鍾拿來跟什么呢?這個用戶時鍾是用來作為讀MIG控制器地址生成的時鍾。
3、Components指的是DDR3的型號是元件類,而不是像筆記本那種的插條類。筆記本是SODIMMs。這里一共有四個選項。
4、這是DDR3芯片型號,根據你的DDR3芯片手冊來選。
5、電壓,根據你的DDR3芯片手冊來選。
6、DDR3的物理位寬,這個需要根據你自己的芯片來選擇。
10、按如下設置,這個頁面的選項也很重要。
1、這里的Input Clock Period 指的是輸入到MIG里面的時鍾是400MHz
2、這里設置讀寫為順序讀寫,並且burst的長度設置為8。注意界面的小字部分。
3、這個我也不太清楚。
11、這個界面也很重要。按照如下設置,然后Next.
第一個是系統時鍾System Clock: No Buffer, 為甚么選No Buffer,我也不太清楚,有知道的朋友可以在評論區下留言。
第二個是參考時鍾Reference Clock:No Buffer,為甚么選No Buffer,我也不太清楚,有知道的朋友可以在評論區下留言。
第三個是系統復位極性,是對MIG控制器復位。這里選擇低電平復位。
第四個是不需要系統調試信號。所以第五個Sample Data Depth 就不可選了。
第七個IO Power Reduction 是指打開低功耗。
第八個是XADC補償使能。
12、內部終端電阻設置50歐姆。然后Next.
13、選擇DDR3芯片引腳。這些引腳可以在電路原理圖上查到,但是這樣配置芯片引腳會很慢的。選擇第二個,然后Next
選擇第二個,直接讀取DDR3的引腳,這樣就不用進行配置了。
14、選“Read XDC/UCF” 直接讀取DDR3的引腳。然后再點“Validate”,驗證已經選擇的引腳是否正確。只有驗證正確了才可以點擊Next.
15、剩下的就一直Next,遇到Accept就Accept ,最后Generate。
這樣就配置完一個MIG控制器了。
關於DDR3的基本知識在這里我就不詳細說了,只有在相關的地方會提上一嘴。本教程的目的只是教會大家如何使用MIG控制器,大家一定不要覺得MIG控制器有多難,其實很簡單的,跟着我在心里默念“MIG就像BRAM一樣簡單”。確實哈,當你回過頭來看,MIG控制器的使用基本和BRAM的使用方法很像。
話不多說了,那么這第一個系列,我就先交大家來例化一個MIG控制器。
VIVADO:2016.4
芯片:zynq7035
上一節已經介紹了如何配置一個MIG控制器 Xilinx MIG 控制器使用詳解(一)
現在讓我們來看看生成的MIG控制器模塊。
-
mig_7series_0 u_mig_7series_0 (
-
-
-
// Memory interface ports
-
// 下面這些都是DDR3的物理管腳,這些已經約束好了,不需要我們自己約束,不用管。
-
.ddr3_addr(ddr3_addr), // output [14:0] ddr3_addr
-
.ddr3_ba(ddr3_ba), // output [2:0] ddr3_ba
-
.ddr3_cas_n(ddr3_cas_n), // output ddr3_cas_n
-
.ddr3_ck_n(ddr3_ck_n), // output [0:0] ddr3_ck_n
-
.ddr3_ck_p(ddr3_ck_p), // output [0:0] ddr3_ck_p
-
.ddr3_cke(ddr3_cke), // output [0:0] ddr3_cke
-
.ddr3_ras_n(ddr3_ras_n), // output ddr3_ras_n
-
.ddr3_reset_n(ddr3_reset_n), // output ddr3_reset_n
-
.ddr3_we_n(ddr3_we_n), // output ddr3_we_n
-
.ddr3_dq(ddr3_dq), // inout [31:0] ddr3_dq
-
.ddr3_dqs_n(ddr3_dqs_n), // inout [3:0] ddr3_dqs_n
-
.ddr3_dqs_p(ddr3_dqs_p), // inout [3:0] ddr3_dqs_p
-
-
//初始化完成信號,需要關注
-
.init_calib_complete(init_calib_complete), // outp init_calib_complete
-
-
//這也是DDR3的一些物理管腳,我們也不需要管
-
.ddr3_cs_n(ddr3_cs_n), // output [0:0] ddr3_cs_n
-
.ddr3_dm(ddr3_dm), // output [3:0] ddr3_dm
-
.ddr3_odt(ddr3_odt), // output [0:0] ddr3_odt
-
-
// Application interface ports
-
//下面的app開頭的信號才是我們使用MIG控制器需要關注的
-
.app_addr(app_addr), // input [28:0] app_addr
-
.app_cmd(app_cmd), // input [2:0] app_cmd
-
.app_en(app_en), // input app_en
-
.app_wdf_data(app_wdf_data), // input [255:0] app_wdf_data
-
.app_wdf_end(app_wdf_end), // input app_wdf_end
-
.app_wdf_wren(app_wdf_wren), // input app_wdf_wren
-
.app_rd_data(app_rd_data), // output [255:0] app_rd_data
-
.app_rd_data_end(app_rd_data_end), // output app_rd_data_end
-
.app_rd_data_valid(app_rd_data_valid), // output app_rd_data_valid
-
.app_rdy(app_rdy), // output app_rdy
-
.app_wdf_rdy(app_wdf_rdy), // output app_wdf_rdy
-
-
//下面這三個再例化的時候直接給0就可以
-
.app_sr_req(app_sr_req), // input app_sr_req
-
.app_ref_req(app_ref_req), // input app_ref_req
-
.app_zq_req(app_zq_req), // input app_zq_req
-
-
//下面三個是輸出,直接輸出到三個wire信號即可
-
.app_sr_active(app_sr_active), // output app_sr_active
-
.app_ref_ack(app_ref_ack), // output app_ref_ack
-
.app_zq_ack(app_zq_ack), // output app_zq_ack
-
-
//ui_clk就是MIG輸出給用戶的時鍾,在上一節中我們說的是100MHz
-
.ui_clk(ui_clk), // output ui_clk
-
-
//輸出的這個復位信號不用管,接到一個wire信號上就可以
-
.ui_clk_sync_rst(ui_clk_sync_rst), // output ui_clk_sync_rst
-
-
//這個是掩碼信號,直接賦0,不用管
-
.app_wdf_mask(app_wdf_mask), // input [31:0] app_wdf_mask
-
-
-
// System Clock Ports
-
//系統時鍾,上一節說的是400MHz
-
.sys_clk_i(sys_clk_i),
-
// Reference Clock Ports
-
//參考時鍾
-
.clk_ref_i(clk_ref_i),
-
//系統復位
-
.sys_rst(sys_rst) // input sys_rst
-
);
好,到這我們就來看看app打頭的這些信號是什么意思。
首先,對DDR3應該有讀寫兩種操作,我們先來看看寫操作。
寫操作相關信號:
-
app_cmd :操作命令,確定是讀還是寫。讀: app_cmd = 3'b001; 寫:app_cmd = 3'b000;
-
app_addr:操作地址(往哪寫,從哪讀)
-
app_en :操作地址app_addr的使能,只有它拉高的時候,對應的app_addr才是有效的
-
app_wdf_data:寫入數據的接口(往DDR3里面寫什么)
-
app_wdf_wren:寫入的數據接口app_wdf_data的使能,只有它拉高的時候。對應的app_wdf_data才是有效的
-
app_wdf_end:這里不需要管他。只需要使app_wdf_end = app_wdf_wren。
-
-
所以寫入數據的時候你只需要處理好這六個信號就可以
-
-
app_cmd
-
app_addr
-
app_en
-
app_wdf_data
-
app_wdf_wren
-
app_wdf_end
讀操作相關信號:
-
讀數據的時候只需要操作以下三個信號,是不是更簡單呢?(這里的三個信號和上面是一樣的)
-
app_cmd
-
app_addr
-
app_en
看完了相關的信號,我們就該來看看看讀寫操作是如何操作的了。
寫操作:
寫操作時序如下:(這個圖示從別人的文檔里面弄出來的不想去翻手冊了)
可以看到寫的時候可以有三種情況,分別對應1、2、3種情況。第一種是地址和數據嚴格對齊的,第二三種是數據和地址不對齊的,推薦曹勇第一種數據和地址對齊的方式。
在這個圖里面,我們發現有兩個信號之前沒有說明。就是
-
app_rdy:這個信號由DDR3輸出,告訴用戶在app_rdy拉高的時候拉高app_en。地址app_addr才是有效的。
-
-
app_wdf_rdy:這個信號由DDR3輸出,告訴用戶在app_wdf_rdy拉高的時候拉高app_wdf_wren,寫入的數據app_wdf_data才是有效的。
由上圖的第一種情況可以看出,在app_rdy和app_wdf_rdy都拉高的時候,把app_en拉高、再給出相應的地址、再寫入相應的數據、再給出對應的寫使能就可以把相應的數據寫入到DDR3中相應的地址。但是,需要注意的是,在app_rdy 或者app_wdf_rdy沒有拉高的時候,需要把相應的數據和地址保持不變。
讀操作:
讀操作就更簡單了,先把讀操作時序貼出來。(這個圖也是從別人的文檔里面截取的)
在讀的時候,只需要在app_rdy拉高的時候給出地址app_addr和使能app_en即可。然后就等着接數就可以,給幾個周期的使能就出幾個周期的數據。
所以,回到第一節Xilinx MIG 控制器使用詳解(一),當時說的MIG的使用就像BRAM一樣簡單。
你看,我沒說錯吧。BRAM在寫的時候也是給出寫地址和寫使能,MIG也是,不同的是MIG需要在app_rdy、app_wdf_rdy拉高的時候給出。BRAM在讀的時候,也是給出一個地址就可以了,MIG還需拉高app_en。
現在讓我們來算一下DDR3的帶寬吧。
DDR3的物理位寬是32bit的,DDR3跑的時鍾頻率是400MHz, 又因為是上下沿都采樣,所以帶寬應該為:400MHz*2*32bit=800MHz * 32bit。那么MIG控制器的讀寫數據端口的位寬是多少呢?也就是app_wdf_data 和app_rd_data的位寬是多少呢?答案是:256bit。 怎么算出來的呢?800MHz * 32 bit = 100MHz *32*8bit, 所以是32*8=256bit。
下一篇就介紹如何使用MIG控制器進行讀寫。
關於DDR3的基本知識在這里我就不詳細說了,只有在相關的地方會提上一嘴。本教程的目的只是教會大家如何使用MIG控制器,大家一定不要覺得MIG控制器有多難,其實很簡單的,跟着我在心里默念“MIG就像BRAM一樣簡單”。確實哈,當你回過頭來看,MIG控制器的使用基本和BRAM的使用方法很像。
VIVADO版本:2016.4
芯片:zynq7035
讀寫測試總模塊如下圖:
外部晶振輸入的是100MHz,但是MIG需要400MHz的輸入,所以需要經過一個始終管理模塊進行倍頻到400MHz。DDR_CTRL的主要作用就是產生第二節中說明的那些讀寫控制信號。
代碼結構如下:
現在讓我們來分析分析代碼吧,先從top.v開始。
top.v
-
`timescale 1ns / 1ps
-
-
module top(
-
inout [31:0] ddr3_dq ,
-
inout [3:0] ddr3_dqs_n ,
-
inout [3:0] ddr3_dqs_p ,
-
-
output [14:0] ddr3_addr ,
-
output [2:0] ddr3_ba ,
-
output ddr3_ras_n ,
-
output ddr3_cas_n ,
-
output ddr3_we_n ,
-
output ddr3_reset_n ,
-
output ddr3_ck_p ,
-
output ddr3_ck_n ,
-
output ddr3_cke ,
-
output ddr3_cs_n ,
-
output [3:0] ddr3_dm ,
-
output ddr3_odt ,
-
-
input sys_clk_i //外部輸入的100MHz時鍾
-
);
-
-
-
wire clk400;
-
wire rst_n;
-
clk_wiz_0 clk_wiz_0(
-
.clk_out1 ( clk400 ),
-
.locked ( rst_n ),
-
.clk_in1 ( sys_clk_i )
-
);
-
-
-
-
-
(*keep = "true"*)(*mark_debug = "true"*)wire [28:0] app_addr ;
-
(*keep = "true"*)(*mark_debug = "true"*)wire [2:0] app_cmd ;
-
(*keep = "true"*)(*mark_debug = "true"*)wire app_en ;
-
(*keep = "true"*)(*mark_debug = "true"*)wire [255:0] app_wdf_data ;
-
(*keep = "true"*)(*mark_debug = "true"*)wire app_wdf_end ;
-
(*keep = "true"*)(*mark_debug = "true"*)wire app_wdf_wren ;
-
(*keep = "true"*)(*mark_debug = "true"*)wire [255:0] app_rd_data ;
-
(*keep = "true"*)(*mark_debug = "true"*)wire app_rd_data_end ;
-
(*keep = "true"*)(*mark_debug = "true"*)wire app_rd_data_valid ;
-
(*keep = "true"*)(*mark_debug = "true"*)wire app_rdy ;
-
(*keep = "true"*)(*mark_debug = "true"*)wire app_wdf_rdy ;
-
-
(*keep = "true"*)(*mark_debug = "true"*)wire ddrdata_test_err ;
-
-
-
wire [31:0] app_wdf_mask ;
-
wire app_sr_req ;
-
wire app_ref_req ;
-
wire app_zq_req ;
-
wire app_sr_active ;
-
wire app_ref_ack ;
-
wire app_zq_ack ;
-
wire ui_clk ;
-
wire ui_clk_sync_rst ;
-
-
wire init_calib_complete ;
-
-
-
-
-
//例化ddr_ctrl模塊
-
ddr_ctrl ddr_ctrl(
-
.clk ( ui_clk ),
-
.rst_n ( rst_n ),
-
-
.app_addr ( app_addr ),
-
.app_cmd ( app_cmd ),
-
.app_en ( app_en ),
-
.app_wdf_data ( app_wdf_data ),
-
.app_wdf_mask ( app_wdf_mask ),
-
-
.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_wdf_rdy ( app_wdf_rdy ),
-
.app_wdf_end ( app_wdf_end ),
-
.app_wdf_wren ( app_wdf_wren ),
-
-
.init_calib_complete ( init_calib_complete ),
-
.ddrdata_test_err ( ddrdata_test_err )
-
);
-
-
-
-
-
-
//例化MIG控制器
-
-
mig_7series_0 u_mig_7series_0 (
-
// Memory interface ports
-
.ddr3_addr (ddr3_addr), // output [14:0] ddr3_addr
-
.ddr3_ba (ddr3_ba), // output [2:0] ddr3_ba
-
.ddr3_cas_n (ddr3_cas_n), // output ddr3_cas_n
-
.ddr3_ck_n (ddr3_ck_n), // output [0:0] ddr3_ck_n
-
.ddr3_ck_p (ddr3_ck_p), // output [0:0] ddr3_ck_p
-
.ddr3_cke (ddr3_cke), // output [0:0] ddr3_cke
-
.ddr3_ras_n (ddr3_ras_n), // output ddr3_ras_n
-
.ddr3_reset_n (ddr3_reset_n), // output ddr3_reset_n
-
.ddr3_we_n (ddr3_we_n), // output ddr3_we_n
-
.ddr3_dq (ddr3_dq), // inout [31:0] ddr3_dq
-
.ddr3_dqs_n (ddr3_dqs_n), // inout [3:0] ddr3_dqs_n
-
.ddr3_dqs_p (ddr3_dqs_p), // inout [3:0] ddr3_dqs_p
-
.init_calib_complete (init_calib_complete), // output init_calib_complete
-
-
.ddr3_cs_n (ddr3_cs_n), // output [0:0] ddr3_cs_n
-
.ddr3_dm (ddr3_dm), // output [3:0] ddr3_dm
-
.ddr3_odt (ddr3_odt), // output [0:0] ddr3_odt
-
// Application interface ports
-
.app_addr (app_addr), // input [28:0] app_addr
-
.app_cmd (app_cmd), // input [2:0] app_cmd
-
.app_en (app_en), // input app_en
-
.app_wdf_data (app_wdf_data), // input [255:0] app_wdf_data
-
.app_wdf_end (app_wdf_end), // input app_wdf_end
-
.app_wdf_wren (app_wdf_wren), // input app_wdf_wren
-
.app_rd_data (app_rd_data), // output [255:0] app_rd_data
-
.app_rd_data_end (app_rd_data_end), // output app_rd_data_end
-
.app_rd_data_valid (app_rd_data_valid), // output app_rd_data_valid
-
.app_rdy (app_rdy), // output app_rdy
-
.app_wdf_rdy (app_wdf_rdy), // output app_wdf_rdy
-
.app_sr_req (0), // input app_sr_req
-
.app_ref_req (0), // input app_ref_req
-
.app_zq_req (0), // input app_zq_req
-
.app_sr_active (app_sr_active), // output app_sr_active
-
.app_ref_ack (app_ref_ack), // output app_ref_ack
-
.app_zq_ack (app_zq_ack), // output app_zq_ack
-
.ui_clk (ui_clk), // output ui_clk
-
.ui_clk_sync_rst (ui_clk_sync_rst), // output ui_clk_sync_rst
-
.app_wdf_mask (app_wdf_mask), // input [31:0] app_wdf_mask
-
// System Clock Ports
-
.sys_clk_i (clk400),
-
// Reference Clock Ports
-
.clk_ref_i (clk400),
-
.sys_rst (rst_n) // input sys_rst
-
);
-
-
endmodule
ddr_ctrl.v模塊
-
`timescale 1ns / 1ps
-
-
module ddr_ctrl(
-
input clk ,
-
input rst_n ,
-
-
output [28:0] app_addr ,
-
output reg [2:0] app_cmd ,
-
output reg app_en ,
-
output reg [255:0] app_wdf_data ,
-
output [31:0] app_wdf_mask ,
-
-
input [255:0] app_rd_data ,
-
input app_rd_data_end ,
-
input app_rd_data_valid ,
-
-
input app_rdy ,
-
input app_wdf_rdy ,
-
output app_wdf_end ,
-
output app_wdf_wren ,
-
input init_calib_complete , // DDR3 鍒濆鍖栧畬鎴�
-
output reg ddrdata_test_err
-
);
-
-
-
reg [3:0] test_state ;
-
reg [15:0] send_cnt ;
-
reg [28:0] write_addr ;
-
reg [28:0] read_addr ;
-
reg [255:0] data_buff ;
-
-
-
assign app_wdf_wren = app_en & app_wdf_rdy & app_rdy & (app_cmd == 3'd0);
-
assign app_wdf_end = app_wdf_wren;
-
-
assign app_addr = (app_cmd == 3'd0) ? write_addr : read_addr;
-
-
assign app_wdf_mask = 32'd0;//這個直賦0
-
-
-
-
always@(posedge clk or negedge rst_n)
-
begin
-
if(!rst_n)
-
begin
-
test_state <= 4'd0;
-
send_cnt <= 16'd0;
-
write_addr <= 29'd0;
-
read_addr <= 29'd0;
-
app_cmd <= 3'd0;
-
app_en <= 1'b0;
-
app_wdf_data <= 256'd0;
-
end
-
else
-
begin
-
case (test_state)
-
4'd0 :
-
begin
-
app_cmd <= 3'd0;
-
app_en <= 1'b0;
-
app_wdf_data <= 256'd0;
-
send_cnt <= 16'd0;
-
write_addr <= 29'd0;
-
read_addr <= 29'd0;
-
if(init_calib_complete)
-
test_state <= 4'd1; //如果DDR初始化完成就進入下一個狀態
-
else
-
test_state <= 4'd0; //否則等待DDR初始化完成
-
end
-
-
4'd1 :
-
begin
-
if(app_rdy & app_wdf_rdy) //等待這兩個信號拉高就使命令有效
-
begin
-
app_cmd <= 3'd0;
-
app_en <= 1'b1;
-
send_cnt <= send_cnt + 1'b1;
-
-
test_state <= 4'd2;
-
end
-
end
-
-
4'd2 :
-
begin
-
if(app_rdy & app_wdf_rdy)
-
begin
-
if(send_cnt == 16'd199)
-
begin
-
app_wdf_data <= 256'd0;
-
write_addr <= 29'd0;
-
send_cnt <= 16'd0;
-
test_state <= 4'd3; //進入讀狀態
-
app_en <= 1'b0;
-
end
-
else
-
begin
-
send_cnt <= send_cnt + 1'b1;
-
app_cmd <= 3'd0;
-
app_en <= 1'b1;
-
write_addr <= write_addr + 29'd8;
-
app_wdf_data <= app_wdf_data + 256'd1;//寫入的數據,從1到198
-
end
-
end
-
end
-
-
4'd3 :
-
begin
-
if(app_rdy & app_wdf_rdy)
-
begin
-
app_cmd <= 3'd1; //讀命令有效,實際上這時候已經有數據出來了
-
app_en <= 1'b1;
-
send_cnt <= send_cnt + 1'b1;
-
test_state <= 4'd4;
-
end
-
end
-
-
4'd4 :
-
begin
-
if(app_rdy & app_wdf_rdy)
-
begin
-
if(send_cnt == 16'd199)
-
begin
-
read_addr <= 29'd0;
-
send_cnt <= 16'd0;
-
test_state <= 4'd5;
-
app_en <= 1'b0;
-
end
-
else
-
begin
-
send_cnt <= send_cnt + 1'b1;
-
app_cmd <= 3'd1;
-
app_en <= 1'b1;
-
read_addr <= read_addr + 29'd8; //讀地址
-
-
end
-
end
-
end
-
-
4'd5 ://不讀也不寫
-
begin
-
app_cmd <= 3'd0;
-
app_en <= 1'b0;
-
send_cnt <= send_cnt + 1'b1;
-
if(send_cnt == 16'd200)
-
begin
-
send_cnt <= 16'd0;
-
test_state <= 4'd1;
-
end
-
end
-
-
default : test_state <= 4'd0;
-
-
endcase
-
end
-
end
-
//比較寫入與獨處的數據是否相等
-
always@(posedge clk or negedge rst_n)
-
begin
-
if(!rst_n)
-
begin
-
data_buff <= 256'd0;
-
ddrdata_test_err <= 1'b0;
-
end
-
else if (test_state == 4'd3)
-
begin
-
data_buff <= 256'd0;
-
end
-
else
-
begin
-
if(app_rd_data_valid)
-
begin
-
data_buff <= data_buff + 256'd1;
-
if(data_buff != app_rd_data) //如果寫入與獨處的數據不相等就拉高
-
ddrdata_test_err <= 1'b1;
-
end
-
end
-
end
-
-
endmodule
到此程序已經分析完畢了,想到什么問題再補充吧。
工程代碼。