DDR2(3):自定義讀寫控制器和DDR2 IP信號說明


  官方的例程還是比較難懂,現在試着在上次的工程上進行修改,做一個簡單的讀寫測試。

一、新建頂層工程

  建立工程 top.v,其效果即原先的 DDR2_example_top.v,記得右鍵設置為頂層模塊,主要修改了以下幾點:

(1)端口信號名字;

(2)增加 PLL 生成 100Mhz 時鍾供給 DDR2 IP 用;

(3)增加自己寫的 DDR2_ctrl.v 代替之前的 DDR2_example_driver.v;

  代碼如下所示:

  1 //**************************************************************************
  2 // *** 名稱 : top.v
  3 // *** 作者 : xianyu_FPGA
  4 // *** 博客 : https://www.cnblogs.com/xianyufpga/
  5 // *** 日期 : 2020-6-10
  6 // *** 工具 : Quartus 13.0
  7 // *** 芯片 : Cyclone IV E
  8 // *** 型號 : EP4CE10F30C6
  9 // *** 描述 : DDR2 控制器的頂層文件
 10 //**************************************************************************
 11 
 12 module top
 13 //========================< 端口 >==========================================
 14 (
 15 //system --------------------------------------------
 16 input   wire                CLK_50M                 , //時鍾
 17 input   wire                RST_N                   , //復位
 18 //DDR2 ----------------------------------------------
 19 output  wire                DDR2_ODT                , //DDR2片內終止控制
 20 output  wire                DDR2_CS_N               , //DDR2片選
 21 output  wire                DDR2_CKE                , //DDR2時鍾使能
 22 output  wire    [15:0]      DDR2_ADDR               , //DDR2地址總線
 23 output  wire    [ 2:0]      DDR2_BA                 , //DDR2組地址總線
 24 output  wire                DDR2_RAS_N              , //DDR2行地址選擇
 25 output  wire                DDR2_CAS_N              , //DDR2列地址選擇
 26 output  wire                DDR2_WE_N               , //DDR2寫使能
 27 output  wire    [ 1:0]      DDR2_DM                 , //DDR2數據屏蔽
 28 inout   wire                DDR2_CLK                , //DDR2時鍾
 29 inout   wire                DDR2_CLK_N              , //DDR2時鍾反相
 30 inout   wire    [15:0]      DDR2_DQ                 , //DDR2數據總線
 31 inout   wire    [ 1:0]      DDR2_DQS                  //DDR2數據源同步
 32 );
 33 //========================< 信號 >==========================================
 34 wire                        CLK_100M                ; //PLL分出100M時鍾
 35 wire                        phy_clk                 ; //讀寫DDR2的工作時鍾
 36 wire                        local_init_done         ; //初始化完成
 37 wire            [27:0]      local_address           ; //地址總線
 38 wire                        local_write_req         ; //數據寫入請求
 39 wire                        local_read_req          ; //數據讀出請求
 40 wire                        local_burstbegin        ; //突發起始
 41 wire            [31:0]      local_wdata             ; //寫數據總線
 42 wire            [ 3:0]      local_be                ; //字節使能標志
 43 wire            [ 2:0]      local_size              ; //突發大小
 44 wire                        local_ready             ; //讀寫請求被接收指示
 45 wire            [31:0]      local_rdata             ; //讀數據總線
 46 //==========================================================================
 47 //==                        PLL
 48 //==========================================================================
 49 pll u_pll
 50 (
 51     .inclk0                 (CLK_50M                ), //時鍾輸入端口
 52     .c0                     (CLK_100M               )  //100M時鍾輸出
 53 );
 54 //==========================================================================
 55 //==                        DDR2 IP
 56 //==========================================================================
 57 DDR2 u_DDR2
 58 (
 59     .pll_ref_clk            (CLK_100M               ), //IP核中的PLL輸入參考時鍾
 60     .global_reset_n         (RST_N                  ), //全局異步復位
 61     .soft_reset_n           (RST_N                  ), //全局異步復位(不復位PLL)
 62     .phy_clk                (phy_clk                ), //讀寫DDR2的工作時鍾
 63     .reset_phy_clk_n        (                       ), //IP核提供的復位
 64     .reset_request_n        (                       ), //IP核中的PLL鎖定
 65     .aux_full_rate_clk      (                       ), //全速率時鍾
 66     .aux_half_rate_clk      (                       ), //半速率時鍾
 67     //用戶控制 --------------------------------------
 68     .local_address          (local_address          ), //地址總線
 69     .local_write_req        (local_write_req        ), //數據寫入請求
 70     .local_read_req         (local_read_req         ), //數據讀出請求
 71     .local_burstbegin       (local_burstbegin       ), //突發起始
 72     .local_wdata            (local_wdata            ), //寫數據總線
 73     .local_be               (local_be               ), //字節使能標志
 74     .local_size             (local_size             ), //突發大小
 75     .local_ready            (local_ready            ), //讀寫請求被接收指示
 76     .local_rdata            (local_rdata            ), //讀數據總線
 77     .local_rdata_valid      (local_rdata_valid      ), //讀數據有效
 78     .local_refresh_ack      (                       ), //刷新請求
 79     .local_init_done        (local_init_done        ), //初始化完成
 80     //外部引腳 --------------------------------------
 81     .mem_odt                (DDR2_ODT               ), //DDR2片內終止控制
 82     .mem_cs_n               (DDR2_CS_N              ), //DDR2片選
 83     .mem_cke                (DDR2_CKE               ), //DDR2時鍾使能
 84     .mem_addr               (DDR2_ADDR              ), //DDR2地址總線
 85     .mem_ba                 (DDR2_BA                ), //DDR2組地址總線
 86     .mem_ras_n              (DDR2_RAS_N             ), //DDR2行地址選擇
 87     .mem_cas_n              (DDR2_CAS_N             ), //DDR2列地址選擇
 88     .mem_we_n               (DDR2_WE_N              ), //DDR2寫使能
 89     .mem_dm                 (DDR2_DM                ), //DDR2數據屏蔽
 90     .mem_clk                (DDR2_CLK               ), //DDR2時鍾
 91     .mem_clk_n              (DDR2_CLK_N             ), //DDR2時鍾反相
 92     .mem_dq                 (DDR2_DQ                ), //DDR2數據總線
 93     .mem_dqs                (DDR2_DQS               )  //DDR2數據源同步
 94 );
 95 //==========================================================================
 96 //==                        DDR2 IP核控制模塊
 97 //==========================================================================
 98 DDR2_ctrl u_DDR2_ctrl
 99 (
100     .phy_clk                (phy_clk                ), //讀寫DDR2的工作時鍾
101     .rst_n                  (local_init_done        ), //初始化完成
102     .local_address          (local_address          ), //地址總線
103     .local_write_req        (local_write_req        ), //數據寫入請求
104     .local_read_req         (local_read_req         ), //數據讀出請求
105     .local_burstbegin       (local_burstbegin       ), //突發起始
106     .local_wdata            (local_wdata            ), //寫數據總線
107     .local_be               (local_be               ), //字節使能標志
108     .local_size             (local_size             ), //突發大小
109     .local_ready            (local_ready            ), //讀寫請求被接收指示
110     .local_rdata            (local_rdata            )  //讀數據總線
111 );
112 
113 endmodule

 

二、DDR2_ctrl 讀寫測試

  DDR2_ctrl.v 文件用於 DDR2 IP 的讀寫測試,替換之前的 DDR2_example_driver.v。實現的功能將 16 個偶數依次寫入地址 0-15,然后再讀出,循環反復。

  代碼如下所示:

 1 //**************************************************************************
 2 // *** 名稱 : DDR2_ctrl.v
 3 // *** 作者 : xianyu_FPGA
 4 // *** 博客 : https://www.cnblogs.com/xianyufpga/
 5 // *** 日期 : 2020年6月
 6 // *** 描述 : DDR2控制模塊,寫入16個偶數,再讀出來
 7 //**************************************************************************
 8 
 9 module DDR2_ctrl
10 //========================< 端口 >==========================================
11 (
12 input   wire                phy_clk                 , //時鍾
13 input   wire                rst_n                   , //復位
14 //---------------------------------------------------
15 input   wire                local_ready             , //讀寫請求被接收指示
16 input   wire    [31:0]      local_rdata             , //讀數據總線
17 output  wire                local_write_req         , //數據寫入請求
18 output  wire                local_read_req          , //數據讀出請求
19 output  wire                local_burstbegin        , //突發起始
20 output  reg     [27:0]      local_address           , //地址總線
21 output  reg     [31:0]      local_wdata             , //寫數據總線
22 output  wire    [ 3:0]      local_be                , //字節使能標志
23 output  wire    [ 2:0]      local_size                //突發大小
24 );
25 //========================< 信號 >==========================================
26 reg             [ 7:0]      time_cnt                ; //計數器
27 //==========================================================================
28 //==    用戶信號
29 //==========================================================================
30 assign local_be         = 4'hF;  //始終使能全部數據
31 assign local_size       = 3'h1;  //突發長度始終為1
32 //==========================================================================
33 //==    計數器,local_ready為高時加1,計100下
34 //==========================================================================
35 always @ (posedge phy_clk or negedge rst_n) begin
36     if(!rst_n) begin
37         time_cnt <= 8'h0;
38     end
39     else if(time_cnt == 8'd99 && local_ready) begin
40         time_cnt <= 8'h0;
41     end
42     else if(local_ready) begin
43         time_cnt <= time_cnt + 8'h1;
44     end
45 end
46 //==========================================================================
47 //==    設計讀寫請求
48 //==========================================================================
49 assign local_write_req  = (time_cnt >= 8'h0  && time_cnt <= 8'd15); //寫請求
50 assign local_read_req   = (time_cnt >= 8'd30 && time_cnt <= 8'd45); //讀請求
51 assign local_burstbegin = local_write_req || local_read_req;        //突發開始
52 //==========================================================================
53 //==    設計地址
54 //==========================================================================
55 always @ (posedge phy_clk or negedge rst_n) begin
56     if(!rst_n) begin
57         local_address <= 28'h0;
58     end
59     else if(local_ready) begin
60         if(time_cnt == 8'd15 || time_cnt == 8'd45) //清0,注意時序對齊
61             local_address <= 28'h0;
62         else if(local_write_req || local_read_req)
63             local_address <= local_address + 28'h1;
64     end
65 end
66 //==========================================================================
67 //==    設計寫數據,遞增2,剛好對齊地址的0-15
68 //==========================================================================
69 always @ (posedge phy_clk or negedge rst_n) begin
70     if(!rst_n) begin
71         local_wdata <= 32'h0;
72     end
73     else if(local_ready) begin
74         if(time_cnt < 8'd15)
75             local_wdata <= local_wdata + 32'h2;
76         else
77             local_wdata <= 32'h0;
78     end
79 end
80 
81 
82 endmodule

 

三、testbench

1、將之前工程的 DDR2_example_top_tb.v 文件復制一份命名為 top_tb.v,打開將里面的模塊名和例化名改成和 top.v 一致即可。

 2、修改 QuartusII 的 testbench 文件,將原先的刪除,引入這次的 top_tb.v 和仿真模型 DDR2_mem_model.v。

 

四、打開仿真查看波形

1、點擊仿真打開仿真軟件,出波形界面后點 stop,將所有信號刪除,轉而將 top 模塊和 DDR2_ctrl 模塊的信號加到波形那,然后仿真時間改為 302us,run。

2、總體波形如下所示:

3、寫過程細節

4、讀過程細節

  讀過程需要注意一點,local_read_req拉高時,local_rdata 數據不是立刻出來的,而是延時一段時間后和 local_rdata_valid 信號一起出來。從波形中可以看出,本次 DDR2 IP 讀寫測試成功。

  經過自己寫一遍讀寫控制,雖然簡單,但對理解信號還是有很大幫助的。上板就不做了,編譯太慢了。

ps:自己開發 DDR2 時需要仿真,不會像上面那樣寫設計文件測試,而是自己寫仿真文件testbench,即自己建立一個 top_tb,將仿真模型引入即可,詳細的可以看 DDR3 部分的開發過程。

 

五、DDR2 IP 核信號說明

   特權同學寫了這樣一段解釋:

  用戶邏輯和 DDR2 IP 核之間的接口並不是什么新發明的特殊接口,不過是 Avalon-MM 總線而已。有人說這個美眉(Memory-Map) 會不會太慢了,關鍵時刻耽誤事?非也,MM 總線的 burst 模式也可以流水線式連續傳輸數據,絲毫不遜色於ST(stream)傳輸方式。
  這里我們可以簡單了解一下帶【local_*】的 Avalon-MM 總線 burst 模式傳輸協議的使用方法。可以比較簡單山寨的理解前面已經給出的帶【local_*】的 Avalon-MM 信號接口:

  • local_size:burst 讀寫的最大數據數量。通常 IP 核內部有 FIFO 用於支持這樣的連續數據讀寫,在Megafunction中設定好的最大數據數量是 Avl_size 的上限值。
  • local_be:byte enable 信號,用於使能或說是屏蔽讀寫數據的各個高低字節。
  • local_ready:總線當前狀態指示。這里高電平表示 ready,此時的 local_read_req 和 local_write_req 能夠被鎖存。
  • local_burstbegin:突發傳輸起始標志位。它不受 local_ready 的影響,在發起一次讀或寫操作的第一個時鍾周期,只需保持一個時鍾周期的 local_burstbegin 為高電平狀態,並且不用管此時的local_ ready狀態如何。
  • local_addr:讀寫共用的總線地址,位寬由 DDR2 的存儲總量和總線上讀寫數據的位寬來決定。如1Gbit的DDR2,外部芯片的數據位寬為 16bit,Avalon-MM 讀寫的數據位寬 64bit,那么它的地址不是以 16bit 位寬來計算的,而是以 64bit 位寬來計算的,即16M(24位)。
  • local_read_req:讀請求, 配合地址local_addr 和突發傳輸起始標志位 local_burstbegin 發起一次 burst 讀操作。在 local_burstbegin 拉高后,只需要確保在同一個時鍾周期或其后第一次 local_ready 有效的時鍾周期拉高一次 local_read_req 信號即可。
  • local_rdata_valid:讀出數據的有效標志位。IP 核在收到 burst 讀請求(local_read_req) 后的若千個時鍾周期開始連續送出數據(數據可能分多次連續送出),該信號和讀出數據配合,高電平表示當前讀出數據有效。
  • local_rdata:讀出數據。和local_rdata_valid 配合送給用戶邏輯。
  • local_write_req:寫請求信號。若發起一次 n 個數據寫入的 burst 傳輸,第一個傳輸時鍾周期首先拉高 local_burstbegin 以及 local_write_req,且local_write_req 必須保持到 n 個數據寫入完成。只有在 local_ready 有效時,當前的 local_write_req、local_addr 和 local_wdata 才是有效的。
  • local_wdata:寫入數據。

  其實我覺得吧,還是看官方英文原版手冊更好一點,《emi_ddr_ug》對這些說得很清楚。

 

六、local_size再說明

  這里還要重點說一下【local_size】,該數據的最大突發長度在 DDR2 IP 里的設置如下所示:

 

  • Local Maximum Burst Count:指定突發數來配置控制器從端口能接收的最大 Avalon 突發數,選擇 4(100),則外部 local_size 位寬為3。

  上篇博客說過,full rate 下,IP 核突發長度只能是4,half rate下,IP核突發長度只能是8:

  而 local_size 一般翻譯為本地突發長度,即連續讀或寫到 IP 里的字的個數。手冊中有如下一段話:

  • 如果選擇memory burst length=4,half rate,local burst lenth(我需要的突發長度) 是1,所以 local_zise 總是1。
  • 如果選擇memory burst length=4,full rate,local burst lenth(我需要的突發長度)是2,所以 local_size 應該設置為 1 或 2 對應每次的寫讀請求。

  另一篇手冊中,話變得不一樣了:

  所以除了上述給的兩個說明,別的組合應該也是可以的,例如特權同學給出的案例為 half rate 下 IP 核突發長度為8,而 local_size 取的是 4 。
 
  說簡單一點,即 local_size 和 IP 突發長度的概念是不一樣的!local_size 是 Avalon 的突發,而 IP 突發長度是固定死的 4 或 8。

 

 七、DDR2 讀寫時序

  DDR2 IP 核的出現讓我們不需要再像 SDRAM 一樣關注它的初始化時序、刷新時序、各種命令組成等,只需要關心讀寫時序即可。

1、寫時序(by鋯石科技,4突發,full速率)

  從上圖可以看出,除了 phy_clk 時鍾信號外,寫時序涉及到了 7 個信號,分別是 local_ready、local_burstbegin、local_write_req、local_size、local_address、local_be 和 local_wdata。在這 7 個信號中只有 local_ready 是輸出信號,這意味着我們需要通過 local_ready 來得知控制器是否准備好了接收我們的指令。如果 local_ready 為高,則拉高 local_burstbegin 和 local_write_req 可以向控制器發出一次突發寫指令,由於一次突發指令可能不止傳輸一個數據,因此 local_burstbegin 只需在突發開始時拉高一個時鍾周期,而 local_write_req 在整個寫數據期間都需拉高。在一次突發開始時需要指定突發的起始地址 local_address、突發大小 local_size,而在整個突發寫期間,將每個數據以及它對應的字節使能信號順序放在local_wdata 和 local_be 總線上。如果 local_ready 為低,則表示控制器不能接收指令,在此期間除了 local_burstbegin 其他的信號都必須保持原來的狀態,直至 local_ready 為高。

2、讀時序(by鋯石科技,4突發,full速率)

  從上圖可以看出,讀時序和寫時序類似,也有 8 個信號,除了 phy_clk 時鍾信號,其他的信號有 local_ready、local_burstbegin、local_read_req 、local_size 、local_address、local_rdata_valid 和 local_rdata。如果 local_ready 為高,則拉高local_burstbegin 和 local_read_req 一個時鍾周期可以向控制器發出一次讀突發指令,與此同時,必須給定突發起始地址 local_address 和突發大小 local_size。由於不需要像寫時序那樣逐個把數據放在 local_wdata 上,因此通常我們會連續發出讀指令,當然前提是 local_ready 一直為高。如果 local_ready 為低,則除了 local_ burstbegin 其他的信號都必須保持原狀態,直到 local_ready 變為高。發出讀指令一段時間后,local_rdata_valid 才會被拉高,表示 local_rdata 總線上的數據被讀出。最后需要說明的是,實際測試發現,local_ burstbegin 信號和控制器內部邏輯沒有任何關聯,也就是說不管 local_burstbegin 信號是什么狀態都不影響讀寫數據過程。為此可以這么理解,local_ burstbegin信號的存在僅僅是為了兼容Avalon-MM總線,可實際上它卻沒有起到作用。

 3、正常 4 個數據的 burst 寫操作(by特權同學,8突發,half速率)

  默認情況下,local_addr 設定的是寫入的首個數據對應的地址,隨后每次寫入數據后地址自動遞增。
4、遇到 local_ready 拉低的 burst 寫操作(by特權同學,8突發,half速率)
  Local_ready 拉高時,local_write_req、local_addr 和 local_wdata 所對應的地址和數據才是有效的。
5、正常 4 個數據的 burst 讀操作(by特權同學,8突發,half速率)
  默認情況下,local_addr 為讀出的首個數據對應的地址,隨后將讀出遞增地址的數據。

6、遇到 local_ready 拉低的 burst 讀操作(by特權同學,8突發,half速率)

  必須保持 local_read_req、local_size 和 local_addr 到 local_ready 拉高為止。 

  貼了兩家的時序解釋,結合上面的實驗,對 DDR2 IP 的讀寫時序應該就比較清楚了。

 

參考資料:

1、鋯石科技FPGA教程

2、特權同學《vip_ex2 DDR2控制器讀寫測試》

3、emi_ddr_ug

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM