FPGA应用技巧之DDR3读写自检方案设计与IP仿真总结

目录

  • [1. 项目概述](#1. 项目概述)
  • [2. 系统架构](#2. 系统架构)
    • [2.1 顶层参数](#2.1 顶层参数)
  • [3. DDR3 读写自检方案设计](#3. DDR3 读写自检方案设计)
    • [3.1 状态机设计](#3.1 状态机设计)
    • [3.2 读写数据格式与规律](#3.2 读写数据格式与规律)
    • [3.3 读写时序(MIG Native 接口)](#3.3 读写时序(MIG Native 接口))
    • [3.4 校验与错误统计](#3.4 校验与错误统计)
    • [3.5 读写控制代码实现](#3.5 读写控制代码实现)
    • [3.6 在线调试手段](#3.6 在线调试手段)
  • [4. DDR3 IP(MIG)核配置](#4. DDR3 IP(MIG)核配置)
  • [5. DDR3 IP 仿真方案](#5. DDR3 IP 仿真方案)
    • [5.1 仿真模型来源](#5.1 仿真模型来源)
    • [5.2 Testbench 设计(`src/ddr3_top_tb.vhd`)](#5.2 Testbench 设计(src/ddr3_top_tb.vhd))
    • [5.3 仿真注意事项(踩坑记录)](#5.3 仿真注意事项(踩坑记录))
    • [5.4 32bit DDR 仿真扩展](#5.4 32bit DDR 仿真扩展)
    • [5.5 仿真结果](#5.5 仿真结果)
  • [6. DDR3 带宽有效利用率(实测)](#6. DDR3 带宽有效利用率(实测))
  • [7. 使用说明(硬件验证流程)](#7. 使用说明(硬件验证流程))
  • 总结

1. 项目概述

本项目基于现有的FPGA开发板上对 DDR3 存储器进行读写自检,验证 DDR3 硬件通路(数据线、地址线、控制线等)的好坏。核心需求如下:

  1. 程序可通过烧写器进行在线调试,能够通过烧写器抓取 DDR3 自检结果;
  2. 需要设计 DDR3 的测试方式 (读写数据的格式、规律等),程序需要遍历 DDR3 的所有地址
  3. 程序只支持 DDR3 自检功能,不包含其他功能;
  4. 针对不同硬件版本,只需做简单修改即可验证硬件。

2. 系统架构

顶层模块 ddr3_topsrc/ddr3_top.vhd)由三部分组成:

复制代码
                    ┌─────────────────────────────────────────┐
 50MHz 系统时钟 ────►│ clk_wiz_0 (PLL) ──200MHz──► mig_7series_0│──► DDR3 芯片
                    │      ▲                     (MIG IP核)    │    (MT41K128M16XX-15E)
                    │      │                        ▲           │
                    │      └────────┬───────────────┘           │
                    │        ddr3_rw(读写自检模块)            │
                    └─────────────────────────────────────────┘
模块 作用
clk_wiz_0(PLL) 开发板主频 50MHz,经 PLL 分频/倍频产生 200MHz 时钟,作为 MIG IP 核的系统时钟(频率根据 DDR3 需要而定)
mig_7series_0(MIG IP 核) Xilinx DDR3 内存控制器 IP,负责 DDR3 初始化校准与读写命令调度,对外提供 Native 用户接口
ddr3_rw(自检模块) 产生读写测试数据、读写时序与校验逻辑,是自检方案的核心

2.1 顶层参数

参数 默认值 说明
WR_LEN 8192 128 对应一行数据;8192 对应 64 行
DATA_WIDTH 128 一组数据长度:8 个 16bit = 128bit
ADDR_WIDTH 28 MIG IP 核用户地址位宽
ddr3_addr_WIDTH 14 DDR3 物理地址位宽(本工程已由 13 对齐为 14)

3. DDR3 读写自检方案设计

自检方案由 ddr3_rw 模块(src/ddr3_rw.vhd)实现,核心思想是:向 DDR3 写入有规律的递增数据,再读出并校验数据之间的差值,若不满足规律则判定硬件出错

3.1 状态机设计

状态机包含 5 个状态:init → wr_ddr → wait_dur → rd_ddr → addr_load → init(循环)

状态 功能
init 初始状态。等待 MIG IP 核 init_calib_complete 拉高(DDR3 初始化校准完成)后跳转到 wr_ddr
wr_ddr 写数据状态。向 MIG IP 核写入 64 行 的数据(65536 个 16bit 数据,即写 8192 次、每次 8 个 16bit)。写入最后一个数据(wr_last)后跳转到 wait_dur
wait_dur 过渡状态,仅维持一个周期,随后复位读地址、进入 rd_ddr
rd_ddr 读数据状态。从 MIG IP 核读出上轮写入的数据(测试为 8192 个)。读到最后一个数据(rd_addr_last)后跳转到 addr_load
addr_load 写完 64 行后 bank 和 row 高位地址加一 、低 16 位列地址清零,然后回到 init,开始新一轮写/读过程

写命令与写数据发生在同一时钟周期 ;由于 MIG 采用 4:1 分频、突发长度 BL=8,app_wdf_endapp_wdf_wren 两个信号相同(见代码注释)。

3.2 读写数据格式与规律

  • 数据格式 :写数据按 16bit 一组,初始为 0x0000~0x0007app_wdf_data_buf <= x"0007_0006_0005_0004_0003_0002_0001_0000"),每次写操作后 8 个 16bit 数据同时自加 8for i in 0 to 7 循环,每个 16bit 段 +8),按 0~65535 递增规律写满 64 行(对应 65536 个 16bit 空间)。
  • 地址规律 :DDR3 突发长度为 8,每写一次数据需要地址 +8 处理;用户地址低 16 位为列地址(每次 +8),app_addr_buf 共 28 位,格式为 rank(1) + bank(3) + row(14) + col(10)
  • 遍历所有地址 :写完 64 行(一个 bank/row 区段)后,addr_load 状态将 app_addr_buf(27 downto 16) 高位 +1(bank、row 切换),低 16 位清零,从而遍历整个 DDR3 地址空间。

3.3 读写时序(MIG Native 接口)

  • 写命令时序 :先检查 app_rdy,为高表示 IP 核命令接口就绪;同时检查 app_wdf_rdy,为高表示写数据通道就绪。就绪后拉高 app_en,同时发送命令(app_cmd)和地址(app_addr),命令与地址被写入;app_wdf_wren 拉高使能数据写入。
  • 写操作命令 app_cmd = 000读操作命令 app_cmd = 001
  • 读时序 :发出读命令后,只需等待 app_rd_data_valid 拉高,为高表示数据总线上的数据有效。注意:发出读命令后,有效读数据会晚若干周期 才出现在数据总线上(延迟时间不定),因此校验逻辑以 app_rd_data_valid 为节拍。

3.4 校验与错误统计

  • 读出数据按 16bit 分组,计算相邻两组数据的差值check_point,112bit = 7 组差值),与常量 diff_num = x"0001_0001_0001_0001_0001_0001_0001" 比较;
  • 若任一组差值不为 1,说明读回数据不符合递增规律,error_flag 拉高,同时 error_num 计数器加一;
  • 每次读有效时重新计算差值并判断,error_flag / error_num 均为 mark_debug 信号,供 ILA 在线抓取观察。

3.5 读写控制代码实现

c 复制代码
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_arith.all;
use IEEE.std_logic_unsigned.all;

entity ddr3_rw is
generic(
    WR_LEN			: integer:= 512; 
	DATA_WIDTH		: integer:= 128; 
	ADDR_WIDTH		: integer:= 28
);
port(
--user interface
--cmd 
      app_addr	 			: out  std_logic_vector(ADDR_WIDTH-1 downto 0):=(others => '0');  
      app_cmd               : out  std_logic_vector(2 downto 0);                              
      app_en                : out    std_logic;
	  app_rdy               : in     std_logic;
--write	  
      app_wdf_data          : out  std_logic_vector(DATA_WIDTH-1 downto 0);                   
      app_wdf_end           : out    std_logic;
      app_wdf_rdy 		    : in     std_logic;
      app_wdf_wren          : out  std_logic;                                                 
--read	  
      app_rd_data 		    : in     std_logic_vector(DATA_WIDTH-1 downto 0);
      app_rd_data_end       : in     std_logic;
      app_rd_data_valid     : in     std_logic;
--no use
      app_wdf_mask          : out    std_logic_vector(15 downto 0):=(others => '0');	  
      app_sr_req            : out    std_logic;
      app_ref_req           : out    std_logic;
      app_zq_req            : out    std_logic;
      app_sr_active         : in     std_logic;
      app_ref_ack           : in     std_logic;
      app_zq_ack            : in     std_logic;
      ui_clk                : in     std_logic;
      ui_clk_sync_rst       : in     std_logic;
      init_calib_complete   : in     std_logic  
);
end entity;

architecture behav of ddr3_rw is

type STATE is (
		init,
		wr_ddr,
		rd_ddr,
		wait_dur,
		addr_load
	);
	
signal   pstate: STATE := init;	
signal   nrst  	      :std_logic;
signal   app_addr_buf     :std_logic_vector(ADDR_WIDTH-1 downto 0):=(others => '0');
signal   app_cmd_buf      :std_logic_vector(2 downto 0);                
signal   app_wdf_data_buf :std_logic_vector(DATA_WIDTH-1 downto 0);                   
signal   app_wdf_wren_buf :std_logic;                                                 
	  
signal   rd_addr_cnt  :std_logic_vector(ADDR_WIDTH-1 downto 0);
signal   wr_addr_cnt  :std_logic_vector(ADDR_WIDTH-1 downto 0);
signal   rd_cnt       :std_logic_vector(ADDR_WIDTH-1 downto 0);

signal   wr_proc 	  :std_logic;
signal   wr_last 	  :std_logic;
signal   rd_addr_last :std_logic;

signal   sysclk       :std_logic;

signal   check_point  :std_logic_vector(111 downto 0);
signal   error_flag   :std_logic;
signal 	 error_num	  :std_logic_vector(15  downto 0):=(others => '0');

constant diff_num	  :std_logic_vector(111 downto 0):= x"0001_0001_0001_0001_0001_0001_0001";
constant MAX_addr_num :std_logic_vector(ADDR_WIDTH-2 downto 0):= (others =>'1');

--for debug
attribute mark_debug: string;
attribute mark_debug of pstate : signal is "true";
attribute mark_debug of error_flag  : signal is "true";
attribute mark_debug of error_num   : signal is "true";

begin 

sysclk <= ui_clk;
nrst   <= not ui_clk_sync_rst;
app_wdf_end  <= app_wdf_wren_buf; 		--由于DDR3芯片时钟和用户时钟的分频选择4:1,突发长度为8,故两个信号相同
app_addr   	  <=  app_addr_buf;        
app_cmd       <=  app_cmd_buf;     
app_wdf_data  <=  app_wdf_data_buf;
app_wdf_wren  <=  app_wdf_wren_buf;
app_sr_req    <= '0';
app_ref_req   <= '0';
app_zq_req    <= '0';
app_wdf_mask  <= x"0000";

process(app_rdy,pstate,app_wdf_rdy,wr_proc,app_cmd_buf,app_wdf_wren_buf,wr_addr_cnt,rd_addr_cnt,rd_cnt)
 begin

			                                                                        --当MIG准备好后,用户同步准备好
		if app_rdy = '1' and ((pstate = wr_ddr and app_wdf_rdy = '1') or pstate = rd_ddr) then
			app_en <= '1';
		else
			app_en <= '0';
		end if;
		
		if (pstate = wr_ddr) and wr_proc = '1' then 					           --写指令,命令接收和数据接收都准备好,此时拉高写使能
			app_wdf_wren_buf <= '1';
		else
			app_wdf_wren_buf <= '0';
		end if;
											
		if pstate = rd_ddr then 
			app_cmd_buf <= "001"; 
		else 
			app_cmd_buf <= "000";				               --处于读的时候命令值为1,其他时候命令值为0	
		end if;
		
		if ( app_cmd_buf = "000") and app_rdy = '1' and app_wdf_rdy = '1' then		   --拉高表示写过程进行
			wr_proc      <=  '1';
		else
			wr_proc      <=  '0';
		end if;
		
		if app_wdf_wren_buf = '1' and (wr_addr_cnt = WR_LEN - 1) then	 	           --处于写使能且是最后一个数据
		    wr_last      <= '1';
		else
			wr_last      <= '0';
		end if;
		
		
		if (rd_addr_cnt = WR_LEN - 1)and app_rdy = '1' and app_cmd_buf = "001" then         	   --处于读指令、读有效且是最后一个数据
			rd_addr_last <=  '1';
		else
			rd_addr_last <=  '0';
		end if;
	
end process;

process(sysclk,nrst)
begin
    if (nrst = '0') then                     
        app_wdf_data_buf <= (others => '0');     
        wr_addr_cnt  <= (others => '0');      
        rd_addr_cnt  <= (others => '0');       
        app_addr_buf     <= (others => '0'); 
		pstate 		 <= init;
    elsif(rising_edge(sysclk)) then
		case(pstate) is
			when init =>
                --app_wdf_data_buf <= (others => '0');
				app_wdf_data_buf <= x"0007_0006_0005_0004_0003_0002_0001_0000";				
                wr_addr_cnt  <= (others => '0');     
                rd_addr_cnt  <= (others => '0');       
				app_addr_buf(15 downto 0) <= (others => '0');
				if init_calib_complete = '1' then
					pstate <= wr_ddr;
				else
					pstate <= init;
				end if;
				
			when wr_ddr =>
				if(wr_last = '1') then
					pstate <= wait_dur;
				else
					pstate <= wr_ddr;
				end if;
				
                if wr_proc = '1' then				--写条件满足
				for i in 0 to 7 loop 				--写数据自加  --从右到左写入 低位对应低位地址
                    app_wdf_data_buf(DATA_WIDTH-i*16-1 downto DATA_WIDTH-(i+1)*16) <= app_wdf_data_buf(DATA_WIDTH-i*16-1 downto DATA_WIDTH-(i+1)*16) + 8;  
				end loop;	
                    wr_addr_cnt  <= wr_addr_cnt + '1';   				   --写地址自加
                    app_addr_buf(15 downto 0)     <= app_addr_buf(15 downto 0) + 8;  --DDR3 地址加8
                else                              		
                    app_wdf_data_buf <= app_wdf_data_buf;      	   				   --写条件不满足,保持当前值
                    wr_addr_cnt  <= wr_addr_cnt;
                    app_addr_buf(15 downto 0)     <= app_addr_buf(15 downto 0); 
				end if;
				
			when wait_dur =>
					pstate <= rd_ddr;
					rd_addr_cnt <= (others => '0');            			 --读地址复位
					app_addr_buf(15 downto 0) <= (others => '0');             --DDR3读从地址0开始
				
			when rd_ddr =>
				if(rd_addr_last = '1') then
					pstate <= addr_load;
				else
					pstate <= rd_ddr;
				end if;
														
                if(app_rdy = '1') then                 	--读到设定的地址长度     
                    rd_addr_cnt <= rd_addr_cnt + 1; 	--若MIG已经准备好,则开始读
                    app_addr_buf(15 downto 0)    <= app_addr_buf(15 downto 0) + 8;       	--用户地址每次加一,DDR3地址加8
                else                           			
                    rd_addr_cnt <= rd_addr_cnt;         --若MIG没准备好,则保持原值
                    app_addr_buf(15 downto 0)    <= app_addr_buf(15 downto 0); 
                end if;
				
            when addr_load =>  -- because app_addr_buf = rank + bank (2 downto 0)+ row (13 downto 0)+ col(15 downto 0) 1+3+14+10 = 28
				if app_addr_buf(26 downto 0) = MAX_addr_num then
				    app_addr_buf <= (others => '0');
				else
					app_addr_buf(27 downto 16) <= app_addr_buf(27 downto 16) + '1'; 
				end if;	
				
				pstate <= init;	
				
			when others => null;
		end case;
	end if;

end process;

process(sysclk,nrst)
begin
    if (nrst = '0') then
		error_flag <= '0';
		check_point <= diff_num; --reset
		
    elsif(rising_edge(sysclk)) then
		if (app_rd_data_valid = '1') and (pstate = rd_ddr) then --读信号有效
			for i in 1 to 7 loop   -- The difference between the two numbers
				check_point(i*16-1 downto (i-1)*16) <= app_rd_data(DATA_WIDTH-(i-1)*16-1 downto DATA_WIDTH-i*16) - app_rd_data(DATA_WIDTH-i*16-1 downto DATA_WIDTH-(i+1)*16);
			end loop;
	
			if(check_point /= diff_num)then
				error_flag <= '1';
			else 
				error_flag <= '0';
			end if;
			
			if error_flag = '1' then
				error_num <= error_num + '1';
			else 
				error_num <= error_num;
			end if;
		else
			check_point <= diff_num;
		end if;
	end if;
end process;

process(sysclk,nrst)
begin
    if (nrst = '0') then
        rd_cnt <= (others => '0');
    elsif(rising_edge(sysclk)) then	 
		if(app_rd_data_valid = '1' and (rd_cnt = WR_LEN - 1))then	--若计数到读写长度,且读有效,地址计数器则置0   	
			rd_cnt <= (others=> '0');              
		elsif (app_rd_data_valid = '1')	then						--读有效情况下每个时钟+1
			rd_cnt <= rd_cnt + 1;
		else 
			rd_cnt <= rd_cnt;
		end if;
	end if;
end process;

end architecture behav;	

3.6 在线调试手段

  • 顶层对 app_addr / app_cmd / app_en / app_rdy / app_wdf_* / app_rd_data* / init_calib_complete 等全部用户接口信号添加 mark_debug 属性;
  • ddr3_rwpstate / error_flag / error_num 添加 mark_debug
  • 通过 ILA(集成逻辑分析仪,见 prj/ddr3_top.hw 下的抓取工程)在线观测状态跳转、读写波形与错误计数,实现硬件验证。

具体的ILA使用可以参考博主之前的文章链接: FPGA应用技巧之Xilinx ILA在线调试方法总结及应用说明


4. DDR3 IP(MIG)核配置

MIG 核(mig_7series_0)依据开发板(A7 核心板)配置,关键项如下:

配置项 取值
FPGA 系列 Artix-7
器件型号 XC7A50T-FTG256,速度等级 -1
内存型号 DDR3_SDRAM → Components → MT41K128M16XX-15E(x16,2Gb)
接口频率 400 MHz(周期 2500 ps),时钟分频比 4:1
数据位宽 16 bit,Data Mask:1
系统时钟 50MHz 输入,经 PLL 倍频至 200MHz 后接入 sys_clk_i
电气标准 数据/地址/控制:SSTL15,DQS/CK:DIFF_SSTL15,ddr3_reset_n:LVCMOS15
内部参考电压 INTERNAL_VREF 0.750(bank 15)
  • 约束文件:src/ddr3.xdc(由 MIG 4.0 生成,含全部 DDR3 引脚约束与多周期/伪路径时序约束);工程约束 prj/.../AT7.xdc 为开发板 DDR3 XDC。
  • 保留信号处理app_wdf_maskapp_sr_reqapp_ref_reqapp_zq_req 按 Xilinx 手册推荐赋 0
  • 配置完成后 Generate 生成 IP 即可。

5. DDR3 IP 仿真方案

5.1 仿真模型来源

  • 使用 Xilinx 官方参考工程 example_project 生成参考工程;
  • 从 example 中取出两个文件加入仿真工程:
    • ddr3_model.sv ------ Micron DDR3 行为级(BUS Functional)仿真模型 (v1.72,timescale 1ps/1ps,要求仿真器时间精度为 ps);
    • ddr3_model_parameters.vh ------ 时序参数头文件(当前配置 x2Gb / sg15E / x16,对应 2Gb、DDR3-1600(15E 速度级)、x16 位宽的内存)。
  • 在 testbench 中例化 ddr3_model,将其引脚与 ddr3_top 的物理接口对接,即可仿真 MIG 与真实内存颗粒的行为。

5.2 Testbench 设计(src/ddr3_top_tb.vhd

内容
时钟 50MHz(周期 20ns,10ns 高低电平各半)
复位 上电后 80000 ns(80μs)释放 sys_nrst,满足 DDR3 上电初始化时间要求
例化 ddr3_top(DUT)+ ddr3_model(内存模型)
模型接口 cs_n 直接接 "0"(片选常有效),其余引脚与 DUT 一一对接
c 复制代码
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_arith.all;
use IEEE.std_logic_unsigned.all;

entity ddr3_top_tb is
generic(
    WR_LEN			: integer:= 8192; -- Set this parameter to control how many write data bursts can be stored in memory  8*8192 = 65536 for 64 row
	DATA_WIDTH		: integer:= 128; 
	ADDR_WIDTH		: integer:= 28;
	ddr3_addr_WIDTH : integer:= 14
);

end entity;

architecture behav of ddr3_top_tb is

component ddr3_top is
generic(
    WR_LEN			: integer:= 512; 
	DATA_WIDTH		: integer:= 128; 
	ADDR_WIDTH		: integer:= 28;
	ddr3_addr_WIDTH : integer:= 14
);
port(
	 sys_clk_i     : in     std_logic;
	 ext_rst_n     : in     std_logic;
--physical interface        
	 ddr3_dq 	   : inout  std_logic_vector(15 downto 0);
	 ddr3_dqs_p    : inout  std_logic_vector(1  downto 0);
	 ddr3_dqs_n    : inout  std_logic_vector(1  downto 0);	
	 ddr3_addr     : out    std_logic_vector(ddr3_addr_WIDTH-1 downto 0);
	 ddr3_ba       : out    std_logic_vector(2  downto 0);
	 ddr3_ras_n    : out    std_logic;
	 ddr3_cas_n    : out    std_logic;
	 ddr3_we_n     : out    std_logic;
	 ddr3_reset_n  : out    std_logic;
	 ddr3_ck_p     : out std_logic_vector(0 downto 0);
	 ddr3_ck_n     : out std_logic_vector(0 downto 0);
	 ddr3_cke      : out std_logic_vector(0 downto 0);
	 --ddr3_cs_n     : out std_logic_vector(0 downto 0);
	 ddr3_dm       : out std_logic_vector(1 downto 0);
	 ddr3_odt      : out std_logic_vector(0 downto 0)

);
end component;

component ddr3_model is
port(
	  rst_n     : in    std_logic;
      ck        : in    std_logic_vector(0 downto 0);
      ck_n      : in    std_logic_vector(0 downto 0);
      cke       : in    std_logic_vector(0 downto 0);
      cs_n      : in    std_logic_vector(0 downto 0);
      ras_n     : in    std_logic;
      cas_n     : in    std_logic;
      we_n      : in    std_logic;
	  dm_tdqs	: inout std_logic_vector(1  downto 0);
	  ba        : in    std_logic_vector(2  downto 0);  
	  addr      : in    std_logic_vector(ddr3_addr_WIDTH-1 downto 0);  
	  dq        : inout std_logic_vector(15 downto 0);	  
	  dqs       : inout std_logic_vector(1  downto 0);	  
	  dqs_n     : inout std_logic_vector(1  downto 0);	  
	  tdqs_n    : out   std_logic_vector(3  downto 0);
      odt       : in    std_logic_vector(0 downto 0)
);
end component;

signal	 clkin         :  std_logic;
signal	 sys_nrst      :  std_logic:= '0';
signal	 ddr3_dq 	   :  std_logic_vector(15 downto 0);
signal	 ddr3_dqs_p    :  std_logic_vector(1 downto 0);
signal	 ddr3_dqs_n    :  std_logic_vector(1 downto 0);	
signal	 ddr3_addr     :  std_logic_vector(ddr3_addr_WIDTH-1 downto 0);
signal	 ddr3_ba       :  std_logic_vector(2 downto 0);
signal	 ddr3_ras_n    :  std_logic;
signal	 ddr3_cas_n    :  std_logic;
signal	 ddr3_we_n     :  std_logic;
signal	 ddr3_reset_n  :  std_logic;
signal	 ddr3_ck_p     :  std_logic_vector(0 downto 0);
signal	 ddr3_ck_n     :  std_logic_vector(0 downto 0);
signal	 ddr3_cke      :  std_logic_vector(0 downto 0);
signal	 ddr3_cs_n     :  std_logic_vector(0 downto 0);
signal	 ddr3_dm       :  std_logic_vector(1 downto 0);
signal	 ddr3_odt      :  std_logic_vector(0 downto 0);

begin

DUT : ddr3_top
generic map (
    WR_LEN			=>    WR_LEN	,	
	DATA_WIDTH		=>    DATA_WIDTH,
	ADDR_WIDTH		=>    ADDR_WIDTH,
	ddr3_addr_WIDTH	=> 	  ddr3_addr_WIDTH
)
port map(
	 sys_clk_i           =>   	 clkin        ,        
	 ext_rst_n           =>   	 sys_nrst     ,  
--physical interface                          
	 ddr3_dq 	        =>   	 ddr3_dq 	  ,  
	 ddr3_dqs_p         =>   	 ddr3_dqs_p   ,  
	 ddr3_dqs_n         =>   	 ddr3_dqs_n   ,  
	 ddr3_addr          =>   	 ddr3_addr    ,  
	 ddr3_ba            =>   	 ddr3_ba      ,  
	 ddr3_ras_n         =>   	 ddr3_ras_n   ,  
	 ddr3_cas_n         =>   	 ddr3_cas_n   ,  
	 ddr3_we_n          =>   	 ddr3_we_n    ,  
	 ddr3_reset_n       =>   	 ddr3_reset_n ,  
	 ddr3_ck_p          =>   	 ddr3_ck_p    ,  
	 ddr3_ck_n          =>   	 ddr3_ck_n    ,  
	 ddr3_cke           =>   	 ddr3_cke     ,  
	 --ddr3_cs_n          =>   	 ddr3_cs_n    ,  
	 ddr3_dm            =>   	 ddr3_dm      ,  
	 ddr3_odt           =>   	 ddr3_odt      
);
ddr3_model_inst: ddr3_model
port map(
	  rst_n     =>    sys_nrst        ,
      ck        =>    ddr3_ck_p      ,
      ck_n      =>    ddr3_ck_n      ,
      cke       =>    ddr3_cke       ,
      cs_n      =>    "0"      ,
      ras_n     =>    ddr3_ras_n     ,
      cas_n     =>    ddr3_cas_n     ,
      we_n      =>    ddr3_we_n      ,
      dm_tdqs   =>    ddr3_dm        ,
      ba        =>    ddr3_ba        ,
      addr      =>    ddr3_addr      ,
      dq        =>    ddr3_dq        ,
      dqs       =>    ddr3_dqs_p     ,
      dqs_n     =>    ddr3_dqs_n     ,
      tdqs_n    =>    open           ,
      odt       =>    ddr3_odt   
);

process
begin
wait for 80000 ns;   --rst for FPGA
sys_nrst <= '1';
end process;  
process  
begin     
  wait for 10 ns;   --50M clk for FPGA
  clkin<='1';  
  wait for 10 ns;  
  clkin<='0';  
end process;  
end architecture behav;	

5.3 仿真注意事项(踩坑记录)

  1. 仿真时间精度 :模型要求仿真器分辨率达到 ps 级(2023/5/12 将 timescale 修改为 1ps/1fs 后 Modelsim 可正常仿真);
  2. 地址位宽对齐ddr3_modeladdr 位宽可能与 MIG IP 核接口不一致,需修改 ddr3_model_parameters.vh / 模型端口,本工程已把 X16 的 addr 位宽由 13 改为 14 以对齐 IP 核接口;
  3. 模型参数一致性ddr3_model 中的 define(x2Gb、sg15E、x16)必须与 MIG IP 核所选内存颗粒(MT41K128M16XX-15E,x16、2Gb、15E)一致,否则时序校验会误报。

5.4 32bit DDR 仿真扩展

对应 32bit DDR 内存(例如 MT41K256M16XX-107 由 2 片 16bit DDR3 组成 32bit):

  1. 需要重新生成/修改 MIG IP 核(数据位宽改为 32bit);
  2. 使用 example 工程重新生成对应的 ddr3_model 文件,并修改 ddr3_model_parameters.vh 中参数,使其与 Memory Detail 一致、接口对齐(两块内存型号不一致);
  3. 主要变化为 DM、DQ、DQS 的位宽

5.5 仿真结果

我们准备好工程以及testbench之后,vivado点击run simulation之后运行仿真(前提是vivado和modelsim关联)会出现以下界面

在wave波形显示界面我们将要观察的模块中的信号添加进去并按照group进行分组。

开始仿真。

我们可以看到仿真结果与我们设计代码逻辑吻合。

6. DDR3 带宽有效利用率(实测)

以 ui_clk 为基准统计:

场景 占用时钟周期 传输数据数 利用率
9717 8192 84%
8916 8192 91%

7. 使用说明(硬件验证流程)

  1. 在 Vivado 中打开工程 prj/ddr3_top.xpr,综合、实现并生成比特流;
  2. 若更换硬件版本,仅需修改:MIG IP 核配置(内存型号/位宽)、ddr3.xdc 引脚约束(或导入新开发板 XDC)、顶层参数(WR_LENDATA_WIDTH 等);
  3. 下载比特流,通过烧写器/ILA 抓取 error_flagerror_num
    • error_flag = 0error_num = 0:DDR3 读写通路正常;
    • error_flag = 1:读回数据不符合递增规律,DDR3 硬件或连接存在问题;
  4. 仿真验证使用 Modelsim(或 Vivado Simulator),按第 5 节步骤加入内存模型即可复现功能。

总结

上述内容是将自己对DDR3使用都其中一种应用场景按照自己的理解整理出来供刚接触FPGA和DDR3的伙伴们做一个参考,希望感兴趣的小伙伴能亲自上手去操作实现一边,自己体会。

需要完整工程的伙伴可以私信或留言。


不要忘记关注我,点赞收藏,我会为你带来更多优质内容!

相关推荐
vickycheung332 分钟前
基于RV1126B核心板监护摄像机应用解决方案
经验分享·嵌入式硬件·arm 嵌入式开发
bingcheby1 小时前
PS通过AXI总线控制PL的LED灯实验之四---编程下载调试
驱动开发·fpga开发·iar
梦里藍天1 小时前
WebView2 应用突然白屏崩溃?可能是 DLP 加密软件的钩子注入导致的 FAIL_FAST(天锐绿盾排查实录)
经验分享·hermes
国科安芯1 小时前
小卫星综合电子系统中RISC-V抗辐射MCU的功能安全与多接口集成架构分析
单片机·嵌入式硬件·安全·fpga开发·架构·risc-v
GoppViper2 小时前
旅游行业GEO引擎优化:精准关键词布局策略与实操指南
经验分享·旅游
小HANN2 小时前
保姆级实战:CentOS7 搭建 LAMP 环境部署 WordPress 个人博客
linux·运维·服务器·经验分享
wjcroom2 小时前
在PVE中的直通和共享文件访问
网络·经验分享·虚拟化
德思特2 小时前
国产GNSS模拟器崛起:高端性能如何突破技术壁垒实现国产替代?
经验分享
从零开始的嵌入式之旅3 小时前
day31
linux·c语言·经验分享·笔记·嵌入式硬件