本帖最后由 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 读写测试实验验证成功: