1097|0

845

帖子

3

TA的资源

版主

楼主
 

08、国产FPGA 正点原子DFPGL22G开发板测评【学习篇】DDR读写实验 [复制链接]

 
本帖最后由 1nnocent 于 2023-2-18 10:55 编辑

1、简介

    DDR3 SDRAM(Double-Data-Rate Three Synchronous Dynamic Random Access Memory)是 DDRSDRAM 的第三代产品,相较于 DDR 和 DDR2, DDR3 有更高的运行性能与更低的电压。 DDR SDRAM 是在 SDRAM 技术的基础上发展改进而来的,同 SDRAM 相比, DDR SDRAM 的最大特点是双沿触发,即在时钟的上升沿和下降沿都能进行数据采集和发送。同样的工作时钟, DDR SDRAM 的读写速度可以比传统的 SDRAM 快一倍。

    PGL22 系列 FPGA 自带了 DDR3 控制器的硬核,用户可以直接借助 IP 核来实现对 DDR3 的读写操作,从而大大降低了 DDR3 的开发难度。HMIC_H IP 是深圳市紫光同创电子有限公司 FPGA 产品中用于实现对 SDRAM 读写而设计的 IP,以下为HMIC_H IP 系统框图:

     HMIC_H IP 包括了 DDR Controller、 DDR PHY 和 PLL,用户通过 AXI4 接口实现数据的读写,通过APB 接口可配置 DDR Controller 内部寄存器, PLL 用于产生需要的各种时钟。

    AXI4 接口: HMIC_H IP 提供三组 AXI4 Host Port: AXI4 Port0(128bit)、 AXI4 Port1(64bit)、 AXI4Port2(64bit)。用户通过 HMIC_H IP 界面可以选择使能这三组 AXI4 Port。三组 AXI4 Host Port 均为标准AXI4 接口。
    APB 接口: HMIC_H IP 提供一个 APB 配置接口,通过该接口,可配置 DDR Controller 内部寄存器。HMIC_H IP 可通过 APB 接口对内部 DDRC 配置寄存器进行读写,在初始化阶段, IP 将配置 DDRC 内部的配置寄存器,如果用户需要读写 DDRC 内部寄存器,需要在初始化完成后进行操作。 由于 IP 初始化阶段已将 DDRC 内部寄存器进行了正确的配置,因此不建议用户在初始化完成后随意更改配置寄存器的值。

    AXI 总线共有 5 个独立的通道,分别是 read address channel (ARxxx), write address channel(AWxxx),read data channel(Rxxx), write data channel(Wxxx), write response channel(Bxxx)。 每一个 AXI 传输通道都是单方向的,且都包含一个信息信号和一个双路的 VALID、 READY 握手机制。信息源通过 VALID 信号来指示通道中的数据和控制信息什么时候有效。目地源用 READY 信号来表示何时能够接收数据。读数据和写数据通道都包括一个 LAST 信号,用来指明一个事物传输的最后一个数据。

    主机/设备之间的握手过程以及 READY 和 VALID 握手信号的关系如下:全部 5 个通道使用相同的 VALID/READY 握手机制传输数据及控制信息。传输源产生 VALID 信号来指明何时数据或控制信息有效。而目地源产生 READY 信号来指明已经准备好接受数据或控制信息。传输发生在 VALID 和 READY 信号同时为高的时候。 VALID 和 READY 信号的出现有三种关系。 图中箭头处信息传输发生。分别为VALID 先变高 READY 后变高:

 

READY 先变高 VALID 后变高:

 

VALID 和 READY 信号同时变高:

 

2、试验任务

    先向 DDR3 的存储器中写入 5120 个数据,写完之后再从存储器中读取相同地址的数据。若初始化成功, 则 LED0 常亮,否则 LED0 不亮; 若读取的值全部正确则 LED1 常亮,否则 LED1闪烁。

3、硬件设计

    ATK-DFPGL22G 开发板上使用了一片南亚的 DDR3 颗粒 NT5CC256M16,硬件原理图如下图所示。

 

4、程序设计

    FPGA 调用 ddr3 测试数据模块向 ddr3 控制模块写入数据,写完之后 ddr 测试数据模块从 ddr3 控制模块读出所写入的数据,并判断读出的数据与写入的数据是否相同,如果相同则 LED1 灯常亮,否则 LED1 灯闪烁。总体框图:

 

    顶层模块(ddr3_rw_top):ddr3 控制器顶层模块主要完成 ddr3 读写控制器模块、 FIFO 控制模块和 ddr3 IP 核的例化。 ddr3读写控制器模块负责与 ddr3 IP 核模块的命令和地址的交互,根据 FIFO 控制模块中 fifo 的剩余数据量来切换 DDR3 的读写命令和地址。 ddr3 IP 核模块一边与用户端进行交互,另一边对芯片进行操作,以实现数据的存储。 FIFO 控制模块负责对输入和输出的数据进行时钟域的切换和位宽的转换。

//****************************************Copyright (c)***********************************//
//原子哥在线教学平台:www.yuanzige.com
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取ZYNQ & FPGA & STM32 & LINUX资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2018-2028
//All rights reserved
//----------------------------------------------------------------------------------------
// File name:           ddr3_rw_top
// Last modified Date:  2020/05/04 9:19:08
// Last Version:        V1.0
// Descriptions:        ddr3读写测试顶层模块
//                      
//----------------------------------------------------------------------------------------
// Created by:          正点原子
// Created date:        2019/05/04 9:19:08
// Version:             V1.0
// Descriptions:        The original version
//
//----------------------------------------------------------------------------------------
//****************************************************************************************//

module ddr3_rw_top(
    input             sys_clk          , //系统时钟50M
    input             sys_rst_n        , //系统复位
    output            led_error        , //读写错误led灯
    output            led_ddr_init_done, //ddr3初始化完成led灯

    //DDR3接口
    input             pad_loop_in      , //低位温度补偿输入
    input             pad_loop_in_h    , //高位温度补偿输入
    output            pad_rstn_ch0     , //Memory复位
    output            pad_ddr_clk_w    , //Memory差分时钟正端
    output            pad_ddr_clkn_w   , //Memory差分时钟负端
    output            pad_csn_ch0      , //Memory片选
    output [15:0]     pad_addr_ch0     , //Memory地址总线
    inout  [16-1:0]   pad_dq_ch0       , //数据总线
    inout  [16/8-1:0] pad_dqs_ch0      , //数据时钟正端
    inout  [16/8-1:0] pad_dqsn_ch0     , //数据时钟负端
    output [16/8-1:0] pad_dm_rdqs_ch0  , //数据Mask
    output            pad_cke_ch0      , //Memory差分时钟使能
    output            pad_odt_ch0      , //On Die Termination
    output            pad_rasn_ch0     , //行地址strobe
    output            pad_casn_ch0     , //列地址strobe
    output            pad_wen_ch0      , //写使能
    output [2:0]      pad_ba_ch0       , //Bank地址总线
    output            pad_loop_out     , //低位温度补偿输出
    output            pad_loop_out_h     //高位温度补偿输出    
   );

//parameter define 
parameter  APP_ADDR_MIN = 28'd0  ;  //ddr3读写起始地址,以一个16bit的数据为一个单位
//APP_ADDR_MAX = BURST_LENGTH * 8 * (n+1)(n表示突发次数)
parameter  APP_ADDR_MAX = 28'd5120 ;  //ddr3读写结束地址,以一个16bit的数据为一个单位
parameter  BURST_LENGTH = 8'd64    ;  //ddr3读写突发长度,64个128bit的数据
parameter  DATA_MAX = APP_ADDR_MAX - APP_ADDR_MIN;  //读写ddr3的最大数据量

//wire define
wire  [15:0]  wr_data        ;  //DDR3控制器模块写数据
wire  [15:0]  rd_data        ;  //DDR3控制器模块读数据
wire          wr_en          ;  //DDR3控制器模块写使能
wire          rd_en          ;  //DDR3控制器模块读使能
wire          ddr_init_done  ;  //ddr3初始化完成信号
wire          error_flag     ;  //ddr3读写错误标志

////*****************************************************
////**                    main code
////***************************************************** 
//ddr3控制器顶层模块
ddr3_top u_ddr3_top(
 .refclk_in             (sys_clk         ),
 .rst_n                 (sys_rst_n       ),
 .app_addr_rd_min       (APP_ADDR_MIN    ),
 .app_addr_rd_max       (APP_ADDR_MAX    ),
 .rd_bust_len           (BURST_LENGTH    ),
 .app_addr_wr_min       (APP_ADDR_MIN    ),
 .app_addr_wr_max       (APP_ADDR_MAX    ),
 .wr_bust_len           (BURST_LENGTH    ),
 .wr_clk                (sys_clk         ),
 .rd_clk                (sys_clk         ),
 .datain_valid          (wr_en           ),
 .datain                (wr_data         ),
 .rdata_req             (rd_en           ),
 .dataout               (rd_data         ),
 .ddr_init_done         (ddr_init_done   ),
 //DDR3接口
 .pad_loop_in           (pad_loop_in     ),
 .pad_loop_in_h         (pad_loop_in_h   ),
 .pad_rstn_ch0          (pad_rstn_ch0    ),
 .pad_ddr_clk_w         (pad_ddr_clk_w   ),
 .pad_ddr_clkn_w        (pad_ddr_clkn_w  ),
 .pad_csn_ch0           (pad_csn_ch0     ),
 .pad_addr_ch0          (pad_addr_ch0    ),
 .pad_dq_ch0            (pad_dq_ch0      ),
 .pad_dqs_ch0           (pad_dqs_ch0     ),
 .pad_dqsn_ch0          (pad_dqsn_ch0    ),
 .pad_dm_rdqs_ch0       (pad_dm_rdqs_ch0 ),
 .pad_cke_ch0           (pad_cke_ch0     ),
 .pad_odt_ch0           (pad_odt_ch0     ),
 .pad_rasn_ch0          (pad_rasn_ch0    ),
 .pad_casn_ch0          (pad_casn_ch0    ),
 .pad_wen_ch0           (pad_wen_ch0     ),
 .pad_ba_ch0            (pad_ba_ch0      ),
 .pad_loop_out          (pad_loop_out    ),
 .pad_loop_out_h        (pad_loop_out_h  )
 );  

//ddr3测试数据模块  
ddr_test u_ddr_test(
    .clk_50m       (sys_clk         ),    //时钟
    .rst_n         (sys_rst_n       ),    //复位,低有效
    .wr_en         (wr_en           ),    //写使能
    .wr_data       (wr_data         ),    //写数据
    .rd_en         (rd_en           ),    //读使能
    .rd_data       (rd_data         ),    //读数据  
    .data_max      (DATA_MAX        ),    //读写ddr的最大数据量
    .ddr3_init_done(ddr_init_done   ),    //ddr3初始化完成信号
    .error_flag    (error_flag      )     //ddr3读写错误
    );

//利用LED灯指示ddr3读写测试的结果及ddr3是否初始化完成
led_disp u_led_disp(
    .clk_50m            (sys_clk          ),
    .rst_n              (sys_rst_n        ),
    .ddr3_init_done     (ddr_init_done    ),
    .error_flag         (error_flag       ),
    .led_error          (led_error        ),
    .led_ddr_init_done  (led_ddr_init_done)
    );

endmodule

ddr3 读写控制器模块(rw_ctrl_128bit):该模块主要对读写地址操作的信号跳转与读写地址操作。读写操作的状态转换图:

 

    在复位结束后,如果 DDR3 没有初始化完成,那么状态一直在空闲状态(IDLE),否则跳到 DDR3 空闲状态(DDR3_DONE)。

    处理 DDR3 写请求时判断wfifo_rcount,以免写 FIFO 溢出,造成写入 DDR3 的数据丢失。当写 FIFO中的数据量大于一次突发写长度时,执行 DDR3 写地址操作(WRITE_ADDR)。

    处理 DDR3 读请求时判断rfifo_rcount,以免读 FIFO 读空,造成空读现象。当读 FIFO 中的数据量小于一次读突发长度时,执行 DDR3 读地址操作(READ_ADDR)。
    当写地址有效信号和写地址准备信号同时为高时,状态机由写地址状态(WRITE_ADDR) 跳转到写数据状态(WRITE_DATA);当执行完一次突发写长度后,状态机由写数据状态跳转到 DDR3 空闲状态(DDR3_DONE)。

    处理 DDR3 读地址跳转到读数据状态的过程,跳转机制与写状态类似,有别处在于读数据状态(READ_DATA)跳转到 DDR3 空闲状态(DDR3_DONE)的条件是最后一次读信号(axi_rlast)为 1时。
 

//****************************************Copyright (c)***********************************//
//原子哥在线教学平台:www.yuanzige.com
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取ZYNQ & FPGA & STM32 & LINUX资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2018-2028
//All rights reserved
//----------------------------------------------------------------------------------------
// File name:           rw_ctrl_128bit
// Last modified Date:  2020/05/04 9:19:08
// Last Version:        V1.0
// Descriptions:        ddr3读写控制器模块
//                      
//----------------------------------------------------------------------------------------
// Created by:          正点原子
// Created date:        2019/05/04 9:19:08
// Version:             V1.0
// Descriptions:        The original version
//
//----------------------------------------------------------------------------------------
//****************************************************************************************//

`timescale 1ps/1ps

module rw_ctrl_128bit 
 (
   input                   clk             , //时钟
   input                   rst_n           , //复位
   input                   ddr_init_done   , //DDR初始化完成
   output      [32-1:0 ]   axi_awaddr      , //写地址
   output reg  [7:0    ]   axi_awlen       , //写突发长度
   output wire [2:0    ]   axi_awsize      , //写突发大小
   output wire [1:0    ]   axi_awburst     , //写突发类型
   output                  axi_awlock      , //写锁定类型
   input                   axi_awready     , //写地址准备信号
   output reg              axi_awvalid     , //写地址有效信号
   output                  axi_awurgent    , //写紧急信号,1:Write address指令优先执行
   output                  axi_awpoison    , //写抑制信号,1:Write address指令无效
   output wire [15:0   ]   axi_wstrb       , //写选通
   output reg              axi_wvalid      , //写数据有效信号
   input                   axi_wready      , //写数据准备信号
   output reg              axi_wlast       , //最后一次写信号
   output wire             axi_bready      , //写回应准备信号
   output reg              wrfifo_en_ctrl  , //写FIFO数据读使能控制位
   output      [32-1:0 ]   axi_araddr      , //读地址
   output reg  [7:0    ]   axi_arlen       , //读突发长度
   output wire [2:0    ]   axi_arsize      , //读突发大小
   output wire [1:0    ]   axi_arburst     , //读突发类型
   output wire             axi_arlock      , //读锁定类型
   output wire             axi_arpoison    , //读抑制信号,1:Read address指令无效
   output wire             axi_arurgent    , //读紧急信号,1:Read address指令优先执行
   input                   axi_arready     , //读地址准备信号
   output reg              axi_arvalid     , //读地址有效信号
   input                   axi_rlast       , //最后一次读信号
   input                   axi_rvalid      , //读数据有效信号
   output wire             axi_rready      , //读数据准备信号
   input       [10:0   ]   wfifo_rcount    , //写端口FIFO中的数据量
   input       [10:0   ]   rfifo_wcount    , //读端口FIFO中的数据量
   input       [27:0   ]   app_addr_rd_min , //读DDR3的起始地址
   input       [27:0   ]   app_addr_rd_max , //读DDR3的结束地址
   input       [7:0    ]   rd_bust_len     , //从DDR3中读数据时的突发长度
   input       [27:0   ]   app_addr_wr_min , //写DDR3的起始地址
   input       [27:0   ]   app_addr_wr_max , //写DDR3的结束地址
   input       [7:0    ]   wr_bust_len       //从DDR3中写数据时的突发长度
);

//localparam define 
localparam IDLE        = 4'd1 ; //空闲状态
localparam DDR3_DONE   = 4'd2 ; //DDR3初始化完成状态
localparam WRITE_ADDR  = 4'd3 ; //写地址
localparam WRITE_DATA  = 4'd4 ; //写数据
localparam READ_ADDR   = 4'd5 ; //读地址
localparam READ_DATA   = 4'd6 ; //读数据

//reg define
reg        init_start   ; //初始化完成信号
reg [31:0] init_addr    ; //突发长度计数器
reg [31:0] axi_araddr_n ; //读地址计数
reg [31:0] axi_awaddr_n ; //写地址计数
reg [3:0 ] state_cnt    ; //状态计数器
reg [9:0 ] lenth_cnt    ; //突发写次数计数器

//wire define
wire [9:0 ] lenth_cnt_max; //最大突发次数

//*****************************************************
//**                    main code
//*****************************************************

assign  axi_awlock   = 1'b0      ;
assign  axi_awurgent = 1'b0      ;
assign  axi_awpoison = 1'b0      ;
assign  axi_bready   = 1'b1      ;
assign  axi_wstrb    = {16{1'b1}};
assign  axi_awsize   = 3'b100    ;
assign  axi_awburst  = 2'd1      ;
assign  axi_arlock   = 1'b0      ;
assign  axi_arurgent = 1'b0      ;
assign  axi_arpoison = 1'b0      ;
assign  axi_arsize   = 3'b100    ;
assign  axi_arburst  = 2'd1      ;
assign  axi_rready   = 1'b1      ;

//计算最大突发次数
assign  lenth_cnt_max = app_addr_wr_max / (wr_bust_len * 4'd8);

//读写地址,因为第0位无效,所以读写地址数据从第1位开始填入
assign  axi_araddr = {6'b0,axi_araddr_n[24:0],1'b0};
assign  axi_awaddr = {6'b0,axi_awaddr_n[24:0],1'b0};

//稳定ddr3初始化信号
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) 
        init_start <= 1'b0;
    else if (ddr_init_done)
        init_start <= ddr_init_done;
    else
        init_start <= init_start;
end

//写地址模块
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        axi_awaddr_n <= app_addr_wr_min;
        axi_awlen    <= 8'b0;
        axi_awvalid  <= 1'b0;
    end
    //DDR3初始化完成 
    else if (init_start) begin
        axi_awlen <= wr_bust_len - 1'b1;
        //当写地址计数小于最后一次写地址起始位时
        if (axi_awaddr_n < {app_addr_wr_max , 1'b0} - wr_bust_len * 5'd16) begin
            //写地址有效信号和写地址准备信号都为1时
            if (axi_awvalid && axi_awready) begin
                axi_awvalid  <= 1'b0;         //拉低写地址有效信号
                //写地址计数加一个突发长度所需的地址
                axi_awaddr_n <= axi_awaddr_n + wr_bust_len * 5'd16;//wr_bust_len*128/8
            end
            //状态机处于写地址状态且写地址准备信号为1时
            else if (state_cnt == WRITE_ADDR && axi_awready)
                axi_awvalid  <= 1'b1;    //拉高写地址有效信号
        end
        //当写地址计数等于最后一次写地址起始位时
        else if (axi_awaddr_n == {app_addr_wr_max , 1'b0} - wr_bust_len * 5'd16) begin
            if (axi_awvalid && axi_awready) begin
                axi_awvalid  <= 1'b0;
                axi_awaddr_n <= app_addr_wr_min; //写地址计数清零(回到写起始地址)
            end
            else if (state_cnt == WRITE_ADDR && axi_awready)
                axi_awvalid  <= 1'b1;
        end
        else
            axi_awvalid <= 1'b0;
    end 
    else begin
            axi_awaddr_n <= axi_awaddr_n;
            axi_awlen    <= 8'b0;
            axi_awvalid  <= 1'b0;
    end
end

//写数据模块
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        axi_wvalid <= 1'b0  ;
        axi_wlast  <= 1'b0  ;
        init_addr  <= 32'd0 ;
        lenth_cnt  <= 8'd0  ;
        wrfifo_en_ctrl <= 1'b0;
    end
    else begin
        //DDR3初始化完成
        if (init_start) begin
            //当突发写次数计数器小于最大突发次数时
            if (lenth_cnt < lenth_cnt_max) begin
                if (axi_wvalid && axi_wready && init_addr < wr_bust_len - 2'd2) begin
                    init_addr      <= init_addr + 1'b1;
                    wrfifo_en_ctrl <= 1'b0;
                end 
                //因为写DDR时已经提前让FIFO准备好第一个数据,所以使能在写结尾要减少一个使能周期
                else if (axi_wvalid && axi_wready && init_addr == wr_bust_len - 2'd2) begin
                    axi_wlast      <= 1'b1;
                    wrfifo_en_ctrl <= 1'b1;              //提前一个时钟周期拉高
                    init_addr      <= init_addr + 1'b1;
                end
                //当突发长度计数器等于一次突发长度时
                else if (axi_wvalid && axi_wready && init_addr == wr_bust_len - 2'd1) begin
                    axi_wvalid     <= 1'b0;
                    axi_wlast      <= 1'b0;
                    wrfifo_en_ctrl <= 1'b0;
                    lenth_cnt      <= lenth_cnt + 1'b1;  //突发写次数计数器加1
                    init_addr      <= 32'd0;
                end         
                else if (state_cnt == WRITE_DATA && axi_wready)
                    axi_wvalid     <= 1'b1;
                else 
                    lenth_cnt      <= lenth_cnt;
            end
            else begin
                axi_wvalid <= 1'b0     ;
                axi_wlast  <= 1'b0     ;
                init_addr  <= init_addr;
                lenth_cnt  <= 8'd0     ;
            end
        end
        else begin
            axi_wvalid <= 1'b0 ;
            axi_wlast  <= 1'b0 ;
            init_addr  <= 32'd0;
            lenth_cnt  <= 8'd0 ;
        end
    end
end 

//读地址模块
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      axi_araddr_n <= app_addr_rd_min;
      axi_arlen    <= 8'b0;
      axi_arvalid  <= 1'b0;
    end
    //DDR3初始化完成
    else if(init_start) begin
        axi_arlen <= rd_bust_len - 1'b1;
        //当读地址计数小于最后一次读地址起始位时
        if (axi_araddr_n < {app_addr_rd_max , 1'b0} - rd_bust_len * 5'd16) begin
            if (axi_arready && axi_arvalid) begin
                axi_arvalid  <= 1'b0;
                axi_araddr_n <= axi_araddr_n + rd_bust_len * 5'd16;
            end
            else if(axi_arready && state_cnt == READ_ADDR)
                axi_arvalid  <= 1'b1;
        end 
        //当读地址计数等于最后一次读地址起始位时
        else if (axi_araddr_n == {app_addr_rd_max , 1'b0} - rd_bust_len * 5'd16) begin
            if (axi_arready && axi_arvalid) begin
                axi_arvalid  <= 1'b0;
                axi_araddr_n <= app_addr_rd_min;
            end
            else if(axi_arready && state_cnt==READ_ADDR)
                axi_arvalid  <= 1'b1;
        end             
        else
            axi_arvalid <= 1'b0;
    end
    else begin  
            axi_araddr_n   <= app_addr_rd_min;
            axi_arlen      <= 8'b0;
            axi_arvalid    <= 1'b0;
    end     
end 

//DDR3读写逻辑实现模块
always @(posedge clk or negedge rst_n) begin
    if(~rst_n) begin 
        state_cnt <= IDLE;
    end
    else begin
        case(state_cnt)
            IDLE:begin
                if(init_start)
                    state_cnt <= DDR3_DONE ;
                else
                    state_cnt <= IDLE;
            end
            DDR3_DONE:begin
                if(wfifo_rcount >= wr_bust_len)
                    state_cnt <= WRITE_ADDR;         //跳到写地址操作
                else if(rfifo_wcount < rd_bust_len)
                    state_cnt <= READ_ADDR;          //跳到读地址操作
                else 
                    state_cnt <= state_cnt; 
            end             
            WRITE_ADDR:begin
                if(axi_awvalid && axi_awready)
                    state_cnt <= WRITE_DATA;        //跳到写数据操作 
                else
                    state_cnt <= state_cnt;         //条件不满足,保持当前值
            end
            WRITE_DATA:begin 
                if(axi_wvalid && axi_wready && init_addr == wr_bust_len - 1)
                    state_cnt <= DDR3_DONE;        //写到设定的长度跳到等待状态
                else
                    state_cnt <= state_cnt;        //写条件不满足,保持当前值
            end         
            READ_ADDR:begin
                if(axi_arvalid && axi_arready)
                    state_cnt <= READ_DATA;        //跳到写数据操作
                else
                    state_cnt <= state_cnt;        //条件不满足,保持当前值
            end
            READ_DATA:begin
                if(axi_rlast)                      //读到设定的地址长度
                    state_cnt <= DDR3_DONE;        //则跳到空闲状态
                else
                    state_cnt <= state_cnt;        //否则保持当前值
            end
            default:begin
                state_cnt <= IDLE;
            end
        endcase
    end
end

endmodule

    

    ddr3 控制器 fifo 控制模块(ddr3_fifo_ctrl):该模块例化了两个 FIFO IP 核,分别为 128 位进 16 位出的读 FIFO 和 16 位进 128 位出的写 FIFO。读FIFO 是将 DDR3 输出的 128 位宽的数据转为 16 位宽的数据后输出给用户;写 FIFO 是将用户输入的 16 位宽的数据转为 128 位宽的数据后输出给 DDR3。

//****************************************Copyright (c)***********************************//
//原子哥在线教学平台:www.yuanzige.com
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取ZYNQ & FPGA & STM32 & LINUX资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2018-2028
//All rights reserved
//----------------------------------------------------------------------------------------
// File name:           ddr3_fifo_ctrl
// Last modified Date:  2020/05/04 9:19:08
// Last Version:        V1.0
// Descriptions:        ddr3控制器fifo控制模块
//                      
//----------------------------------------------------------------------------------------
// Created by:          正点原子
// Created date:        2019/05/04 9:19:08
// Version:             V1.0
// Descriptions:        The original version
//
//----------------------------------------------------------------------------------------
//****************************************************************************************//

`timescale 1ns / 1ps
module ddr3_fifo_ctrl(
    input           rst_n            ,  //复位信号
    input           wr_clk           ,  //wfifo时钟
    input           rd_clk           ,  //rfifo时钟
    input           clk_100          ,  //用户时钟
    input           datain_valid     ,  //数据有效使能信号
    input  [15:0]   datain           ,  //有效数据
    input  [127:0]  rfifo_din        ,  //用户读数据
    input           rdata_req        ,  //请求像素点颜色数据输入
    input           rfifo_wren       ,  //从ddr3读出数据的有效使能
    input           wfifo_rden       ,  //wfifo读使能
    output [127:0]  wfifo_dout       ,  //用户写数据
    output [10:0]   wfifo_rcount     ,  //rfifo剩余数据计数
    output [10:0]   rfifo_wcount     ,  //wfifo写进数据计数
    output [15:0]   pic_data            //有效数据
    );

rd_fifo u_rd_fifo  (
  .wr_clk         (clk_100     ),  // input
  .wr_rst         (~rst_n      ),  // input
  .wr_en          (rfifo_wren  ),  // input
  .wr_data        (rfifo_din   ),  // input [127:0]
  .wr_full        (            ),  // output
  .wr_water_level (rfifo_wcount),  // output
  .almost_full    (            ),  // output
  .rd_clk         (rd_clk      ),  // input
  .rd_rst         (~rst_n      ),  // input
  .rd_en          (rdata_req   ),
  .rd_data        (pic_data    ),  // output [15:0]
  .rd_empty       (            ),  // output
  .rd_water_level (            ),  // output
  .almost_empty   (            )   // output
);

wr_fifo u_wr_fifo  (
  .wr_clk         (wr_clk      ),    // input
  .wr_rst         (~rst_n      ),    // input
  .wr_en          (datain_valid), 
  .wr_data        (datain      ),    //input [15:0]
  .wr_full        (            ),    // output
  .wr_water_level (            ),    // output
  .almost_full    (            ),    // output
  .rd_clk         (clk_100     ),    // input 
  .rd_rst         (~rst_n      ),    // input
  .rd_en          (wfifo_rden  ),    // input
  .rd_data        (wfifo_dout  ),    // output [127:0]
  .rd_empty       (            ),    // output
  .rd_water_level (wfifo_rcount),    // output
  .almost_empty   (            )     // output
);
endmodule

    ddr 测试数据模块(ddr_test):ddr 测试数据模块从起始地址开始,连续向 5120 个存储空间中写入数据 0~5119。写完成后一直进行读操作,持续将该存储空间的数据读出。
 

//****************************************Copyright (c)***********************************//
//原子哥在线教学平台:www.yuanzige.com
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取ZYNQ & FPGA & STM32 & LINUX资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2018-2028
//All rights reserved
//----------------------------------------------------------------------------------------
// File name:           ddr_test
// Last modified Date:  2020/09/04 9:19:08
// Last Version:        V1.0
// Descriptions:        ddr测试数据模块 
//                      
//----------------------------------------------------------------------------------------
// Created by:          正点原子
// Created date:        2020/09/04 9:19:08
// Version:             V1.0
// Descriptions:        The original version
//
//----------------------------------------------------------------------------------------
//****************************************************************************************//

module ddr_test(
    input             clk_50m       ,   //时钟
    input             rst_n         ,   //复位,低有效
                                        
    output reg        wr_en         ,   //写使能
    output reg [15:0] wr_data       ,   //写数据
    output reg        rd_en         ,   //读使能
    input      [15:0] rd_data       ,   //读数据
    input      [27:0] data_max      ,   //写入ddr的最大数据量
    
    input             ddr3_init_done,   //ddr3初始化完成信号
    output reg        error_flag        //ddr3读写错误
    
    );

//reg define
reg        init_done_d0;
reg        init_done_d1;
reg [27:0] wr_cnt      ;   //写操作计数器
reg [27:0] rd_cnt      ;   //读操作计数器
reg        rd_valid    ;   //读数据有效标志
reg [27:0] rd_cnt_d0   ;
  
//*****************************************************
//**                    main code
//***************************************************** 

//同步ddr3初始化完成信号
always @(posedge clk_50m or negedge rst_n) begin
    if(!rst_n) begin
        init_done_d0 <= 1'b0 ;
        init_done_d1 <= 1'b0 ;
    end
    else begin
        init_done_d0 <= ddr3_init_done;
        init_done_d1 <= init_done_d0;
    end
end

//对读计数器做一拍延时使数据对齐
always @(posedge clk_50m or negedge rst_n) begin
    if(!rst_n)
        rd_cnt_d0    <= 28'd0;
    else
        rd_cnt_d0 <= rd_cnt;
end 

//ddr3初始化完成之后,写操作计数器开始计数
always @(posedge clk_50m or negedge rst_n) begin
    if(!rst_n) 
        wr_cnt <= 28'd0;
    else if(init_done_d1 && (wr_cnt < data_max ))
        wr_cnt <= wr_cnt + 1'b1;
    else 
        wr_cnt <= wr_cnt;
end    

//ddr3写端口FIFO的写使能、写数据
always @(posedge clk_50m or negedge rst_n) begin
    if(!rst_n) begin
        wr_en   <= 1'b0;
        wr_data <= 16'd0;
    end
    else if(wr_cnt >= 11'd0 && (wr_cnt < data_max )&&init_done_d1) begin
            wr_en   <= 1'b1;            //写使能拉高
            wr_data <= wr_cnt[15:0];    //写入数据
    end    
    else begin
            wr_en   <= 1'b0;
            wr_data <= 16'd0;
    end
end

//写入数据完成后,开始读操作
always @(posedge clk_50m or negedge rst_n) begin
    if(!rst_n) 
        rd_en <= 1'b0;
    else if(wr_cnt >= data_max )         //写数据完成
        rd_en <= 1'b1;                   //读使能
    else
        rd_en <= rd_en;
end

//对读操作计数
always @(posedge clk_50m or negedge rst_n) begin
    if(!rst_n) 
        rd_cnt <= 28'd0;
    else if(rd_en) begin
        if(rd_cnt < data_max - 1'd1)
            rd_cnt <= rd_cnt + 1'd1;
        else
            rd_cnt <= 28'd0;
    end
end

//第一次读取的数据无效,后续读操作所读取的数据才有效
always @(posedge clk_50m or negedge rst_n) begin
    if(!rst_n) 
        rd_valid <= 1'b0;
    else if(rd_cnt >= data_max - 1'd1 )  //等待第一次读操作结束
        rd_valid <= 1'b1;                //后续读取的数据有效
    else
        rd_valid <= rd_valid;
end

//读数据有效时,若读取数据错误,给出标志信号
always @(posedge clk_50m or negedge rst_n) begin
    if(!rst_n)
        error_flag <= 1'b0; 
    else if(wr_en)       
        error_flag <= 1'b0;      
    else if(rd_valid && ((rd_data[15:0] != rd_cnt_d0[15:0])) )
        error_flag <= 1'b1;             //若读取的数据错误,将错误标志位拉高
    else
        error_flag <= error_flag;
end

endmodule

    LED 显示模块(led_display):LED 显示模块用 LED 不同的显示状态指示 ddr3 初始完成情况(LED0 常亮表示 ddr3 初始化完成)和ddr3 读写测试的结果:若读写测试正确无误,则 LED1 常亮;若出现错误(读出的数据与写入的数据不一致),则 LED1 以 0.5s 为周期闪烁。

 

5、下载验证

    LED0 在短暂延时之后,开始处于常亮的状态表明 DDR3 初始化完成,若 LED1 保持常亮说明读数据正确, DDR3 读写测试实验验证成功:

 

 

点赞(1) 关注
 
 

回复
举报
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/8 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表