DDR2(5):DDR2自動讀寫控制器


  本講整理一下,如何利用上一講的 DDR2_burst 打造一個可以自動讀寫的 DDR2 控制器,讓其能夠方便的使用於我們的工程中。以攝像頭ov7725 采集 640x480 分辨率的顯示為例,整理這次的設計過程。

 

一、模塊例化

DDR2_driver  u_DDR2_driver
(
    //時鍾和復位 ------------------------------------
    .pll_ref_clk            (clk_100m               ),  //DDR2輸入時鍾
    .global_reset_n         (sys_rst_n              ),  //全局復位信號
    //DDR2端口 -------------------------------------- 
    .mem_odt                (mem_odt                ),  //DDR2片上終結信號
    .mem_cs_n               (mem_cs_n               ),  //DDR2片選信號
    .mem_cke                (mem_cke                ),  //DDR2時鍾使能信號
    .mem_addr               (mem_addr               ),  //DDR2地址總線
    .mem_ba                 (mem_ba                 ),  //DDR2BANK信號
    .mem_ras_n              (mem_ras_n              ),  //DDR2行地址選擇信號
    .mem_cas_n              (mem_cas_n              ),  //DDR2列地址選擇信號
    .mem_we_n               (mem_we_n               ),  //DDR2寫使能信號
    .mem_dm                 (mem_dm                 ),  //DDR2數據掩膜信號
    .mem_clk                (mem_clk                ),  //DDR2時鍾信號
    .mem_clk_n              (mem_clk_n              ),  //DDR2時鍾反相信號
    .mem_dq                 (mem_dq                 ),  //DDR2數據總線
    .mem_dqs                (mem_dqs                ),  //DDR2數據源同步信號
    //DDR2控制 --------------------------------------     
    .DDR2_init_done         (                       ),  //DDR2 IP核初始化信號
    .DDR2_phy_clk           (                       ),  //DDR2 IP核輸出時鍾
    .DDR2_phy_rst_n         (                       ),  //DDR2 IP核輸出的同步復位信號
    //讀寫 ------------------------------------------ 
    .width                  (WIDTH                  ),  //寬度
    .height                 (HEIGHT                 ),  //高度
    .wr_clk                 (wr_clk                 ),  //寫時鍾
    .wr_data                (wr_data                ),  //輸入數據
    .wr_vld                 (wr_vld                 ),  //輸入數據有效信號
    .wr_vsync               (wr_vsync               ),  //場信號(寫)
    .wr_en                  (1'b1                   ),  //寫使能
    .rd_clk                 (clk_VGA                ),  //讀時鍾
    .rd_data                (VGA_din                ),  //輸出數據
    .rd_req                 (VGA_req                ),  //輸出請求
    .rd_vsync               (VGA_vsync              ),  //場信號(讀)
    .rd_en                  (1'b1                   )   //讀使能
);

  從例化可以看出,本次 DDR2 設計外部給如的只有100Mhz時鍾、復位、讀列表、寫列表,參數列表,非常簡潔。其中參數的寬度和高度即這次圖像采集的 640 和 480。

 

二、端口和信號

`include "DDR2_param.v"
//**************************************************************************
// *** 名稱 : DDR2_driver.v
// *** 作者 : xianyu_FPGA
// *** 博客 : https://www.cnblogs.com/xianyufpga/
// *** 日期 : 2020年6月
// *** 描述 : 視頻讀寫突發的控制模塊,wrFIFO深度1024,rdFIFO深度256
//**************************************************************************

module DDR2_driver
//============================< 端口 >======================================
(
//時鍾和復位 ----------------------------------------
input                           pll_ref_clk         ,   //DDR2輸入時鍾
input                           global_reset_n      ,   //全局復位信號
//DDR2端口 ------------------------------------------
output                          mem_odt             ,   //DDR2片上終結信號
output                          mem_cs_n            ,   //DDR2片選信號
output                          mem_cke             ,   //DDR2時鍾使能信號
output  [`MEM_ADDR_W    -1:0]   mem_addr            ,   //DDR2地址總線
output  [`MEM_BANK_W    -1:0]   mem_ba              ,   //DDR2BANK信號
output                          mem_ras_n           ,   //DDR2行地址選擇信號
output                          mem_cas_n           ,   //DDR2列地址選擇信號
output                          mem_we_n            ,   //DDR2寫使能信號
output  [`MEM_DM_W      -1:0]   mem_dm              ,   //DDR2數據掩膜信號
inout                           mem_clk             ,   //DDR2時鍾信號
inout                           mem_clk_n           ,   //DDR2時鍾反相信號
inout   [`MEM_DQ_W      -1:0]   mem_dq              ,   //DDR2數據總線
inout   [`MEM_DQS_W     -1:0]   mem_dqs             ,   //DDR2數據源同步信號
//DDR2控制 ------------------------------------------ 
output                          DDR2_init_done      ,   //DDR2 IP核初始化信號
output                          DDR2_phy_clk        ,   //DDR2 IP核輸出時鍾
output                          DDR2_phy_rst_n      ,   //DDR2 IP核輸出的同步復位信號
//讀寫 ----------------------------------------------
input   [15:0]                  width               ,   //寬度
input   [15:0]                  height              ,   //高度
input                           wr_clk              ,   //寫時鍾
input   [15:0]                  wr_data             ,   //輸入數據
input                           wr_vld              ,   //輸入數據有效信號
input                           wr_vsync            ,   //場信號(寫)
input                           wr_en               ,   //寫使能
input                           rd_clk              ,   //讀時鍾
output  [15:0]                  rd_data             ,   //輸出數據
input                           rd_req              ,   //輸出請求
input                           rd_vsync            ,   //場信號(讀)
input                           rd_en                   //讀使能
);
//============================< 信號 >======================================
wire    [13:0]                  burst_len           ;   //讀寫突發長度
wire    [`LOCAL_DATA_W  -1:0]   burst_rddata        ;   //讀突發數據
wire    [`LOCAL_DATA_W  -1:0]   burst_wrdata        ;   //寫突發數據
wire                            burst_rdack         ;   //讀突發應答信號
wire                            burst_wrack         ;   //寫突發應答信號
wire                            burst_rddone        ;   //突發讀完成信號
wire                            burst_wrdone        ;   //突發寫完成信號
//---------------------------------------------------
wire                            phy_clk             ;   //DDR2 IP核工作時鍾
wire                            reset_phy_clk_n     ;   //DDR2 IP核同步后的復位信號
wire                            local_init_done     ;   //DDR2 IP核初始化完成信號
wire                            rst_n               ;   //本模塊使用的復位信號
//---------------------------------------------------
reg                             wr_vsync_r          ;   //場信號打一拍
reg                             wr_rst              ;   //場復位信號
reg                             rd_vsync_r          ;   //場信號打一拍
reg                             rd_rst              ;   //場復位信號
wire    [ 8:0]                  wrFIFO_rdusedw      ;   //寫FIFO剩余數據個數
wire    [ 8:0]                  rdFIFO_wrusedw      ;   //讀FIFO剩余數據個數
reg     [ 3:0]                  fsm_cs              ;   //狀態機的當前狀態
reg     [ 3:0]                  fsm_ns              ;   //狀態機的下一個狀態
reg     [15:0]                  wr_vcnt             ;   //寫行計數  
reg     [15:0]                  rd_vcnt             ;   //讀行計數
reg     [`LOCAL_ADDR_W  -1:0]   burst_wraddr        ;   //寫突發地址
reg     [`LOCAL_ADDR_W  -1:0]   burst_rdaddr        ;   //讀突發地址
reg                             burst_wrreq         ;   //突發寫請求
reg                             burst_rdreq         ;   //突發讀請求
reg     [ 1:0]                  wraddr_msb          ;   //乒乓操作寫分區
reg     [ 1:0]                  rdaddr_msb          ;   //乒乓操作讀分區
//============================< 參數 >======================================
parameter   FSM_IDLE            = 4'h0              ;   //空閑狀態
parameter   FSM_ARBIT           = 4'h1              ;   //仲裁狀態
parameter   FSM_WRITE           = 4'h2              ;   //寫狀態
parameter   FSM_WREND           = 4'h3              ;   //寫完成狀態
parameter   FSM_READ            = 4'h4              ;   //讀狀態
parameter   FSM_RDEND           = 4'h5              ;   //讀完成狀態

 

三、DDR2_burst 例化

  上一講整理了 DDR2_burst.v,因此這一講將它例化進來。

//==========================================================================
//==    DDR2突發讀寫模塊,實現一段長度的突發讀寫
//==========================================================================
DDR2_burst u_DDR2_burst
(
    //IP核引出接口 ----------------------------------
    .pll_ref_clk            (pll_ref_clk            ),  //DDR2 參考時鍾 
    .global_reset_n         (global_reset_n         ),  //全局復位信號,連接外部復位
    .phy_clk                (phy_clk                ),  //DDR2 IP核工作時鍾
    .reset_phy_clk_n        (reset_phy_clk_n        ),  //DDR2 IP核同步后的復位信號  
    .local_init_done        (local_init_done        ),  //DDR2 IP核初始化完成信號
    //突發讀寫接口 ----------------------------------
    .burst_rdreq            (burst_rdreq            ),  //突發讀請求
    .burst_wrreq            (burst_wrreq            ),  //突發寫請求
    .burst_rdlen            (burst_len              ),  //突發讀長度
    .burst_wrlen            (burst_len              ),  //突發寫長度
    .burst_rdaddr           (burst_rdaddr           ),  //突發讀地址
    .burst_wraddr           (burst_wraddr           ),  //突發寫地址
    .burst_rddata           (burst_rddata           ),  //突發讀數據
    .burst_wrdata           (burst_wrdata           ),  //突發寫數據
    .burst_rdack            (burst_rdack            ),  //突發讀應答,連接FIFO
    .burst_wrack            (burst_wrack            ),  //突發寫應答,連接FIFO
    .burst_rddone           (burst_rddone           ),  //突發讀完成信號
    .burst_wrdone           (burst_wrdone           ),  //突發寫完成信號   
    //芯片接口 --------------------------------------
    .mem_odt                (mem_odt                ),  //DDR2片上終結信號
    .mem_cs_n               (mem_cs_n               ),  //DDR2片選信號
    .mem_cke                (mem_cke                ),  //DDR2時鍾使能信號
    .mem_addr               (mem_addr               ),  //DDR2地址總線  
    .mem_ba                 (mem_ba                 ),  //DDR2bank信號
    .mem_ras_n              (mem_ras_n              ),  //DDR2行地址選擇信號
    .mem_cas_n              (mem_cas_n              ),  //DDR2列地址選擇信號
    .mem_we_n               (mem_we_n               ),  //DDR2寫使能信號
    .mem_dm                 (mem_dm                 ),  //DDR2數據掩膜信號
    .mem_clk                (mem_clk                ),  //DDR2時鍾信號
    .mem_clk_n              (mem_clk_n              ),  //DDR2時鍾反相信號
    .mem_dq                 (mem_dq                 ),  //DDR2數據總線
    .mem_dqs                (mem_dqs                )   //DDR2數據源同步信號
);

 

三、簡單信號

//==========================================================================
//==    簡單信號
//==========================================================================
//讀寫突發長度,16和64互轉,長度/4
assign burst_len = width[15:2];

//DDR2初始化完成信號
assign DDR2_init_done = local_init_done;

//DDR2輸出時鍾信號
assign DDR2_phy_clk = phy_clk;

//DDR2復位信號
assign DDR2_phy_rst_n = reset_phy_clk_n;

//本模塊復合復位信號
assign rst_n = reset_phy_clk_n && local_init_done;

  將圖像的寬度舍去低 2 位,其實就是寬度除以4,這個結果作為讀寫突發長度。其原因是因為寫入和讀出 DDR2 的數據是 16 位的,而 DDR2 內部數據位寬是 64 位的,下面的 FIFO 會起到 16 轉 64、64 轉 16的作用,這是一個 4 倍的關系,因此這里除以4,而一次突發長度其實剛好就是圖像的一行寬度:寬度x16 = (寬度/4)x64 。因為舍去了低 2 位,因此 burst_len 的位寬為 14 位,這就是上一講遺留的問題了。

 

四、場同步信號處理

  從上面的模塊例化可以看到,我將攝像頭的場同步信號和 VGA 的場同步信號引入了進來,其目的是用於寫讀 FIFO 的異步清 0,避免圖像移位或錯誤。

//==========================================================================
//==    利用寫側場同步信號設計寫FIFO的異步復位
//==========================================================================
//打拍
always @ (posedge phy_clk or negedge rst_n) begin
    if(!rst_n) begin
        wr_vsync_r <= 1'h0;
    end
    else begin
        wr_vsync_r <= wr_vsync;
    end
end

//場起始信號當作場復位信號,高有效  
always @ (posedge phy_clk or negedge rst_n) begin
    if(!rst_n) begin
        wr_rst <= 1'h0;
    end
    else if(!wr_vsync_r && wr_vsync) begin
        wr_rst <= 1'b1;
    end
    else begin
        wr_rst <= 1'b0;
    end
end
//==========================================================================
//==    利用讀側場同步信號設計讀FIFO的異步復位
//==========================================================================
//打拍
always @ (posedge phy_clk or negedge rst_n) begin
    if(!rst_n) begin
        rd_vsync_r <= 1'h0;
    end
    else begin
        rd_vsync_r <= rd_vsync;
    end
end

//場起始信號當作場復位信號,高有效  
always @ (posedge phy_clk or negedge rst_n) begin
    if(!rst_n) begin
        rd_rst <= 1'h0;
    end
    else if(!rd_vsync_r && rd_vsync) begin
        rd_rst <= 1'b1;
    end
    else begin
        rd_rst <= 1'b0;
    end
end

 

五、FIFO例化

  玩過攝像頭的都知道 FIFO 的作用,這里不再贅述。寫 FIFO 的寫位寬為16,讀位寬為64,因為攝像頭數據是16位,而 DDR2 的數據位寬是 64 位,所以這是一個 16 轉 64 的過程,寫側深度設置為 1024,那最多可以支持一行的分辨率為 1024個像素點。讀 FIFO 的寫位寬為 64,寫 FIFO 的讀位寬為 16,寫側深度設置為 256,剛好和前面反過來,因為 VGA 的輸入是 16 位,所以這是一個 64 轉 16 的過程。

//==========================================================================
//==    FIFO
//==========================================================================
//寫FIFO
//---------------------------------------------------
wrFIFO_wr16_rd64_1024 wrFIFO
(
    .aclr           (!rst_n || wr_rst   ),
    .data           (wr_data            ),
    .rdclk          (phy_clk            ),
    .rdreq          (burst_wrack        ),
    .wrclk          (wr_clk             ),
    .wrreq          (wr_vld             ),
    .q              (burst_wrdata       ),
    .rdempty        (                   ),
    .rdusedw        (wrFIFO_rdusedw     ),
    .wrfull         (                   ),
    .wrusedw        (                   )
);

//讀FIFO
//---------------------------------------------------
rdFIFO_wr64_rd16_256 rdFIFO
(
    .aclr           (!rst_n || rd_rst   ),
    .data           (burst_rddata       ),
    .rdclk          (rd_clk             ),
    .rdreq          (rd_req             ),
    .wrclk          (phy_clk            ),
    .wrreq          (burst_rdack        ),
    .q              (rd_data            ),  //輸出到端口
    .rdempty        (                   ),
    .rdusedw        (                   ),
    .wrfull         (                   ),
    .wrusedw        (rdFIFO_wrusedw     )
);

  利用上面場同步產生的場同步起始信號進行 FIFO 的異步清0,其他信號都好理解了,聯系上下代碼后就知道了。

 

六、狀態機

//==========================================================================
//==    狀態機
//==========================================================================
always @ (posedge phy_clk or negedge rst_n) begin
    if(!rst_n)
        fsm_cs <= FSM_IDLE;
    else
        fsm_cs <= fsm_ns;
end

always @ (*) begin
    case(fsm_cs)
        //--------------------------------------------------- 空閑
        FSM_IDLE:
                        fsm_ns = FSM_ARBIT;
        //--------------------------------------------------- 仲裁
        FSM_ARBIT:
                    if(wr_en && wrFIFO_rdusedw >= burst_len)
                        fsm_ns = FSM_WRITE;
                    else if(rd_en && 9'd256 - rdFIFO_wrusedw >= burst_len)
                        fsm_ns = FSM_READ;
                    else if(!wr_en && !rd_en)
                        fsm_ns = FSM_IDLE;
                    else
                        fsm_ns = fsm_cs;
        //--------------------------------------------------- 寫
        FSM_WRITE:
                    if(burst_wrdone)
                        fsm_ns = FSM_WREND;
                    else
                        fsm_ns = fsm_cs;
        //--------------------------------------------------- 寫完成
        FSM_WREND:
                        fsm_ns = FSM_IDLE;
        //--------------------------------------------------- 讀
        FSM_READ:
                    if(burst_rddone)
                        fsm_ns = FSM_RDEND;
                    else
                        fsm_ns = fsm_cs;
        //--------------------------------------------------- 讀完成
        FSM_RDEND:
                        fsm_ns = FSM_IDLE;
        default:
                        fsm_ns = FSM_IDLE;
            
    endcase
end

  注意一下讀寫 FIFO 的 usedw,要及時判斷 FIFO 里數據的個數來確定是進行寫還是讀,同時寫和讀又是寫的優先級更高。

 

七、讀寫請求

//==========================================================================
//==    讀寫請求
//==========================================================================
always @ (posedge phy_clk or negedge rst_n) begin
    if(!rst_n) begin
        burst_wrreq <= 1'h0;
    end
    else if(burst_wrreq == 1'h0 && fsm_cs == FSM_WRITE) begin
        burst_wrreq <= 1'b1;
    end
    else if(burst_wrreq == 1'h1 && fsm_cs == FSM_WRITE && burst_wrdone) begin
        burst_wrreq <= 1'b0;
    end
end

always @ (posedge phy_clk or negedge rst_n) begin
    if(!rst_n) begin
        burst_rdreq <= 1'h0;
    end
    else if(burst_rdreq == 1'h0 && fsm_cs == FSM_READ) begin
        burst_rdreq <= 1'b1;
    end
    else if(burst_rdreq == 1'h1 && fsm_cs == FSM_READ && burst_rddone) begin
        burst_rdreq <= 1'b0;
    end
end

  跟着前面狀態機來的,到了誰的狀態就給誰請求,完了歸 0。

 

八、寫和讀的列計數

  計算一下寫和讀的列計數,計滿一幀就清0,這兩個信號的目的是為了后面的信號使用的。

//==========================================================================
//==    完成一次行突發后,寫列的計數遞增
//==========================================================================
always @ (posedge phy_clk or negedge rst_n) begin
    if(!rst_n) begin
        wr_vcnt <= 16'h0;
    end
    else if((fsm_cs == FSM_WRITE && burst_wrdone && wr_vcnt == height - 16'h1) || wr_rst) begin
        wr_vcnt <= 16'h0;
    end
    else if(fsm_cs == FSM_WRITE && burst_wrdone) begin
        wr_vcnt <= wr_vcnt + 16'h1;
    end
end
//==========================================================================
//==    完成一次行突發后,讀列的計數遞增
//==========================================================================
always @ (posedge phy_clk or negedge rst_n) begin
    if(!rst_n) begin
        rd_vcnt <= 16'h0;
    end
    else if((fsm_cs == FSM_READ && burst_rddone && rd_vcnt == height - 16'h1) || rd_rst) begin
        rd_vcnt <= 16'h0;
    end
    else if(fsm_cs == FSM_READ && burst_rddone) begin
        rd_vcnt <= rd_vcnt + 16'h1;
    end
end

 

八、讀寫地址

//==========================================================================
//==    讀寫地址設計
//==========================================================================
always @ (posedge phy_clk or negedge rst_n) begin
    if(!rst_n) begin
        burst_wraddr <= `LOCAL_ADDR_W'h0;
        burst_rdaddr <= `LOCAL_ADDR_W'h0;
    end
    else begin
        burst_wraddr <= {wraddr_msb,23'h0} + {wr_vcnt[11:0],11'h0};
        burst_rdaddr <= {rdaddr_msb,23'h0} + {rd_vcnt[11:0],11'h0};
    end
end

  上面的列計數在這里起到了作用,這樣每一行都寫在了 DDR2 不同的地址區域,同時留有 11 位寬作為行數據本身,非常清晰。高 2 位為 msb,是用於乒乓操作的。

 

九、乒乓操作

  在之前的博客中,介紹了乒乓操作的原理,當時沒有貼乒乓操作的代碼,因為當時寫的原理直接變成代碼雖然可以用,但總的來說比較復雜。這次的乒乓操作代碼就簡單多了。

//==========================================================================
//==    乒乓操作,寫完一幀圖像乒乓操作切換分區
//==========================================================================
always @ (posedge phy_clk or negedge rst_n) begin
    if(!rst_n) begin
        wraddr_msb <= 2'h1;
    end
    else if(burst_wrdone && wr_vcnt == height - 16'h1 && rdaddr_msb != wraddr_msb + 2'h1) begin
        wraddr_msb <= wraddr_msb + 2'h1;
    end
end

always @ (posedge phy_clk or negedge rst_n) begin
    if(!rst_n) begin
        rdaddr_msb <= 2'h0;
    end
    else if(burst_rddone && rd_vcnt == height - 16'h1 && wraddr_msb != rdaddr_msb + 2'h1) begin
        rdaddr_msb <= rdaddr_msb + 2'h1;
    end
end

  乒乓操作的原理不再贅述,可以查看博客《OV7670/OV7725/OV5640開發記錄(4):SDRAM和乒乓操作》,這次的設計也符合當時的思想,但代碼簡單了。其原理就是划分出 4 片區域來做乒乓(00,01,10,11)。以這個工程為例,讀端是 VGA 需要 60 幀每秒,寫端攝像頭只有 30 幀每秒。

  一開始讀在 0 區,寫在 1 區。讀在 0 區,讀完一幀后如果判定寫端在 1 區,則下一次的讀還是保持在自己的 0 區,直到寫端不在 1 區了,讀才能跨到 1 區去。寫在 1 區,寫完一幀后如果判定讀不在 2 區,則下一次寫就跳轉到 2 區。這樣循環反復,讀不斷的追寫,但永不碰面。msb 構成 4 個站台,而讀和寫相當於兩輛車,永遠不會撞車,並且每次的讀和寫都是完整的一幀,達到了乒乓操作的目的。

  

十、基於 DDR2 的攝像頭采集項目

  仿真測試的過程就不貼了,來看看效果吧。

1、單目攝像頭

  上面的代碼沒有什么遺漏,可以直接復制組合成 DDR2_driver.v 代碼,並且組裝成自己的攝像頭采集項目,攝像頭的使用前面寫過了就不再贅述,來看看效果。

  唉,攝像頭好像有問題,出現了一些波紋一樣的東西,但是 DDR2 控制器沒問題,圖像正確的顯示了,而且沒有出現圖像撕裂的情況,乒乓操作還是不錯的。

2、8 目攝像頭

  這是我畢業設計的一部分,就是將上面的代碼改成獨立的 8 通道,最后的圖像再通過 VGA 進行拼接。這需要一些技巧,由於是畢業項目的一部分,這里就不介紹細節了。

 

  OK,DDR2 自動讀寫控制器的整理就到這。終於搞完這個項目了,接下來得復習前面的項目和基礎知識,差不多准備秋招了。

 


免責聲明!

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



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