本文設計思想採用明德揚至簡設計法。在高速信號處理場合下,很短期內就要緩存大量的數據,這時片內存儲資源已經遠遠不夠了。DDR SDRAM因其極高的性價比幾乎是每一款中高檔FPGA開發板的首選外部存儲芯片。DDR操做時序很是複雜,之因此在FPGA開發中用途如此普遍,都要得意於MIG IP核。網上關於MIG控制DDR的資料不少,所以本文只講述我的認爲較重要的內容。因爲MIG IP核用戶接口時序較複雜,這裏給出擴展接口模塊用於進一步簡化接口時序。緩存
先來看看MIG IP核的架構:架構
瞭解下存儲芯片側重要接口:app
ddr_addr DDR3的行列地址測試
ddr_ba DDR3的bank地址
spa
ddr_cas_n ddr_ras_n ddr_we_n 命令控制設計
ddr_ck ddr_ck_n 差分時鐘3d
ddr_dm 數據輸入屏蔽code
ddr_o_dt 片上終端使能,用於使能和禁止片內終端電阻blog
ddr_reset_n DDR3復位接口
ddr_dqs ddr_dqs_n 數據同步信號
ddr_dq 傳輸數據
以後咱們從IP核配置開始提及。Controller Options這頁最爲重要,其中包括時鐘策略和外部DDR芯片參數配置。首先時鐘週期選擇爲400MHz,此時PHY to Controller Clock Ratio只能是4:1,也就是說MIG用戶側時鐘爲100MHz。下半部分是選擇合適的DDR芯片型號和參數,要再三確認無誤。
Memory Options這頁輸入時鐘週期選擇爲200MHz,根據Controller Options頁的選項,該時鐘通過PLL分頻和倍頻後的時鐘分別做爲用戶側時鐘100MHz和DDR接口時鐘400MHz。
這裏有個參考時鐘選項,若是Memory Options頁PLL輸入時鐘頻率選爲200MHz,此處能夠直接選擇Use System Clock,從而簡化接口。
以上是MIG IP核配置過程當中較爲重要的部分,實際上上述配置也可經過修改工程代碼中參數來重定義。IP核配置完成,打開example design工程頂層文件,咱們來重點關注下用戶側接口功能和時序。
這是本人寫的註釋,更具體清晰的說明仍是要查看官方文檔UG586.接下來看看寫數據和讀數據的接口時序圖(時鐘比例4:1,burst length = 8爲例):
指令通道:
寫數據:
從時序圖能夠看出,指令地址和數據使用兩套時序,彼此相互獨立。爲了便於設計,直接將兩套時序嚴格對齊(狀況1)也能夠正常工做。
讀數據:
爲何說「時鐘比例4:1,burst length = 8爲例」?這一點特別關鍵。此時用戶時鐘週期是DDR接口時鐘週期的4倍,也就是一個用戶時鐘信號上升沿對應8個DDR時鐘邊沿。burst length能夠理解爲MIG連續操做DDR地址的個數,故在4:1時鐘比例下,一個用戶時鐘週期正好對8個地址進行了讀/寫操做,256bit數據分8次(32bit)寫入DDR中。由此分析,在寫數據時讓app_wdf_end = app_wdf_wren便可,而且讀/寫操做時地址遞增步長爲8.
雖然MIG IP核提供了用戶接口,但讀寫指令通道複用且須要實時關注兩個rdy信號形成了時序操做上的不方便。爲此咱們須要對接口進一步封裝,保證寫操做時只關注:寫使能user_wdata_en 寫地址user_waddr 寫數據user_wdata和寫準備就緒信號user_wdata_rdy,讀操做時只關注:讀使能user_rdata_en 讀地址user_raddr 讀數據user_rdata 讀數據有效user_rdata_vld和讀操做準備就緒user_rdata_rdy。
利用擴展接口模塊,將讀通道和寫通道接口分離,並分別例化一個FIFO緩存地址和數據。當讀/寫指令同時有效時,經過MIG側的優先級輪換邏輯輪流讀取其中一個FIFO,每次選一個FIFO讀取直至FIFO爲空再從新選擇。其工程結構和核心代碼以下:
讀側邏輯核心代碼:
1 //讀側-------------------------------------------------------------- 2 3 always @(posedge clk or negedge rst_n )begin 4 if(rst_n==0) begin 5 rd_flag <= (0) ; 6 end 7 else if(rd_flag == 0 && mig_rdy && mig_wdf_rdy && !rdempty1 && (rdempty0 || (!rdempty0 && priority == 0)))begin 8 rd_flag <= (2'b01) ;//讀取 寫指令FIFO 9 end 10 else if(rd_flag == 0 && mig_rdy && !rdempty0 && (rdempty1 || (!rdempty1 && priority == 1)))begin 11 rd_flag <= (2'b10) ;//讀取 讀指令FIFO 12 end 13 else if((rd_flag == 2'b01 && rdempty1)||(rd_flag == 2'b10 && rdempty0)) 14 rd_flag <= 0; 15 end 16 17 //同時非空時輪換優先級 18 always @(posedge clk or negedge rst_n )begin 19 if(rst_n==0) begin 20 priority <= (0) ; 21 end 22 else if(rd_flag == 0 && !rdempty0 && !rdempty1)begin 23 priority <= (!priority) ; 24 end 25 end
爲了方便測試,設計樣式生成模塊與擴展接口模塊用戶側鏈接,不斷向一段地址寫入固定數據序列並在一段時間後讀回。
1 `timescale 1ns / 1ps 2 /* 3 該模塊功能: 4 週期性向一段地址執行讀寫操做 產生固定樣式待寫入數據用戶測試目的 5 測試完畢後刪除該模塊,開發用戶接口 6 7 具體爲: 8 1 寫從0開始以後的10個用戶地址(80個DDR地址):0~9遞增序列 9 2 等待20個時鐘週期 10 3 讀取寫入的10個用戶地址 11 4 等待20個時鐘週期 12 5 重複上述步驟 13 14 說明: 15 1 每一個步驟之間有一個時鐘週期空閒 16 2 因爲burst_len = 8 4:1時鐘模式下一個用戶時鐘週期寫入數據對應一樣時間內8個DDR時鐘邊沿寫入數據, 17 所以地址遞增步長爲8 18 */ 19 module traffic_gen 20 #(parameter DATA_WIDTH = 32, 21 ADDR_WIDTH = 29) 22 ( 23 input clk , 24 input rst_n , 25 26 output reg gen_wdata_en , 27 output reg [ ADDR_WIDTH-1:0] gen_waddr , 28 output reg [ DATA_WIDTH-1:0] gen_wdata , 29 input gen_wdata_rdy ,//寫指令和數據通道準備就緒 30 31 output reg gen_rdata_en , 32 output reg [ ADDR_WIDTH-1:0] gen_raddr , 33 input [ DATA_WIDTH-1:0] gen_rdata , 34 input gen_rdata_vld , 35 input gen_rdata_rdy //讀指令通道準備就緒 36 ); 37 38 39 reg [ (8-1):0] cnt0 ; 40 wire add_cnt0 ; 41 wire end_cnt0 ; 42 reg [ (2-1):0] cnt1 ; 43 wire add_cnt1 ; 44 wire end_cnt1 ; 45 46 reg [ DATA_WIDTH-1:0] gen_rdata_r ; 47 reg gen_rdata_vld_r ; 48 reg com_flag ; 49 50 wire wri_state; 51 wire rd_state; 52 wire com_change_t; 53 54 //操做週期計數器,計數值爲欲操做用戶地址段長度+1(須要一個時鐘週期空閒) 55 always @(posedge clk or negedge rst_n) begin 56 if (rst_n==0) begin 57 cnt0 <= 0; 58 end 59 else if(add_cnt0) begin 60 if(end_cnt0) 61 cnt0 <= 0; 62 else 63 cnt0 <= cnt0+1 ; 64 end 65 end 66 assign add_cnt0 = (com_flag == 0 && gen_wdata_rdy) || (com_flag == 1 && gen_rdata_rdy); 67 assign end_cnt0 = add_cnt0 && cnt0 == (30)-1 ; 68 69 //指令標誌位 先是0--寫 再是1--讀 70 always @(posedge clk or negedge rst_n )begin 71 if(rst_n==0) begin 72 com_flag <= (0) ; 73 end 74 else if(com_change_t)begin 75 com_flag <= (!com_flag) ; 76 end 77 end 78 79 assign com_change_t = add_cnt0 && cnt0 == 10 - 1; 80 81 //寫操做--------------------------------------------- 82 always @(posedge clk or negedge rst_n )begin 83 if(rst_n==0) begin 84 gen_wdata_en <= (0) ; 85 end 86 else if(wri_state)begin 87 gen_wdata_en <= (1'b1) ; 88 end 89 else begin 90 gen_wdata_en <= (0) ; 91 end 92 end 93 94 assign wri_state = add_cnt0 && cnt0 <= 10-1 && com_flag == 0; 95 assign rd_state = add_cnt0 && cnt0 <= 10-1 && com_flag == 1; 96 97 always @(posedge clk or negedge rst_n )begin 98 if(rst_n==0) begin 99 gen_wdata <= (0) ; 100 end 101 else begin 102 gen_wdata <= (cnt0) ; 103 end 104 end 105 106 always@(posedge clk or negedge rst_n)begin 107 if(rst_n == 0) 108 gen_waddr <= 0; 109 else if(wri_state) 110 gen_waddr <= gen_waddr + 29'd8; 111 else 112 gen_waddr <= 0; 113 end 114 //讀操做---------------------------------------------- 115 116 always @(posedge clk or negedge rst_n )begin 117 if(rst_n==0) begin 118 gen_rdata_en <= (0) ; 119 end 120 else if(rd_state)begin 121 gen_rdata_en <= (1'b1) ; 122 end 123 else begin 124 gen_rdata_en <= (0) ; 125 end 126 end 127 128 always@(posedge clk or negedge rst_n)begin 129 if(rst_n == 0) 130 gen_raddr <= 0; 131 else if(rd_state) 132 gen_raddr <= gen_raddr + 29'd8; 133 else 134 gen_raddr <= 0; 135 end 136 137 always @(posedge clk or negedge rst_n )begin 138 if(rst_n==0) begin 139 gen_rdata_r <= (0) ; 140 end 141 else begin 142 gen_rdata_r <= (gen_rdata) ; 143 end 144 end 145 146 always @(posedge clk or negedge rst_n )begin 147 if(rst_n==0) begin 148 gen_rdata_vld_r <= (0) ; 149 end 150 else if(gen_rdata_vld)begin 151 gen_rdata_vld_r <= (1'b1) ; 152 end 153 else begin 154 gen_rdata_vld_r <= (0) ; 155 end 156 end 157 158 endmodule
將traffic_gen和extend_interface模塊例化在MIG的example design中,利用ILA抓取MIG IP核用戶接口信號。
向地址8~80寫入數據0~9,再今後段地址中讀回數據,0~9被正確讀出,MIG IP核控制DDR3讀寫測試完畢。