本帖最后由 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时。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- `timescale 1ps/1ps
-
- module rw_ctrl_128bit
- (
- input clk ,
- input rst_n ,
- input ddr_init_done ,
- 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 ,
- output axi_awpoison ,
- 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 ,
- 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 ,
- output wire axi_arurgent ,
- input axi_arready ,
- output reg axi_arvalid ,
- input axi_rlast ,
- input axi_rvalid ,
- output wire axi_rready ,
- input [10:0 ] wfifo_rcount ,
- input [10:0 ] rfifo_wcount ,
- input [27:0 ] app_addr_rd_min ,
- input [27:0 ] app_addr_rd_max ,
- input [7:0 ] rd_bust_len ,
- input [27:0 ] app_addr_wr_min ,
- input [27:0 ] app_addr_wr_max ,
- input [7:0 ] wr_bust_len
- );
-
-
- localparam IDLE = 4'd1 ; //空闲状态
- localparam DDR3_DONE = 4'd2 ;
- localparam WRITE_ADDR = 4'd3 ; //写地址
- localparam WRITE_DATA = 4'd4 ;
- localparam READ_ADDR = 4'd5 ; //读地址
- localparam READ_DATA = 4'd6 ;
-
-
- 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 [9:0 ] lenth_cnt_max;
-
-
-
-
-
- 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);
-
-
- assign axi_araddr = {6'b0,axi_araddr_n[24:0],1'b0};
- assign axi_awaddr = {6'b0,axi_awaddr_n[24:0],1'b0};
-
-
- 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
-
- if (axi_awvalid && axi_awready) begin
- axi_awvalid <= 1'b0; //拉低写地址有效信号
- //写地址计数加一个突发长度所需的地址
- axi_awaddr_n <= axi_awaddr_n + wr_bust_len * 5'd16;
- end
-
- 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
-
- 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
-
- 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。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- `timescale 1ns / 1ps
- module ddr3_fifo_ctrl(
- input rst_n ,
- input wr_clk ,
- input rd_clk ,
- input clk_100 ,
- input datain_valid ,
- input [15:0] datain ,
- input [127:0] rfifo_din ,
- input rdata_req ,
- input rfifo_wren ,
- input wfifo_rden ,
- output [127:0] wfifo_dout ,
- output [10:0] wfifo_rcount ,
- output [10:0] rfifo_wcount ,
- output [15:0] pic_data
- );
-
- rd_fifo u_rd_fifo (
- .wr_clk (clk_100 ),
- .wr_rst (~rst_n ),
- .wr_en (rfifo_wren ),
- .wr_data (rfifo_din ),
- .wr_full ( ),
- .wr_water_level (rfifo_wcount),
- .almost_full ( ),
- .rd_clk (rd_clk ),
- .rd_rst (~rst_n ),
- .rd_en (rdata_req ),
- .rd_data (pic_data ),
- .rd_empty ( ),
- .rd_water_level ( ),
- .almost_empty ( )
- );
-
- wr_fifo u_wr_fifo (
- .wr_clk (wr_clk ),
- .wr_rst (~rst_n ),
- .wr_en (datain_valid),
- .wr_data (datain ),
- .wr_full ( ),
- .wr_water_level ( ),
- .almost_full ( ),
- .rd_clk (clk_100 ),
- .rd_rst (~rst_n ),
- .rd_en (wfifo_rden ),
- .rd_data (wfifo_dout ),
- .rd_empty ( ),
- .rd_water_level (wfifo_rcount),
- .almost_empty ( )
- );
- endmodule
ddr 测试数据模块(ddr_test):ddr 测试数据模块从起始地址开始,连续向 5120 个存储空间中写入数据 0~5119。写完成后一直进行读操作,持续将该存储空间的数据读出。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 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 ,
-
- input ddr3_init_done,
- output reg error_flag
-
- );
-
-
- 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 ;
-
-
-
-
-
-
- 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 读写测试实验验证成功: