目录
- [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 硬件通路(数据线、地址线、控制线等)的好坏。核心需求如下:
- 程序可通过烧写器进行在线调试,能够通过烧写器抓取 DDR3 自检结果;
- 需要设计 DDR3 的测试方式 (读写数据的格式、规律等),程序需要遍历 DDR3 的所有地址;
- 程序只支持 DDR3 自检功能,不包含其他功能;
- 针对不同硬件版本,只需做简单修改即可验证硬件。
2. 系统架构
顶层模块 ddr3_top(src/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_end与app_wdf_wren两个信号相同(见代码注释)。
3.2 读写数据格式与规律
- 数据格式 :写数据按 16bit 一组,初始为
0x0000~0x0007(app_wdf_data_buf <= x"0007_0006_0005_0004_0003_0002_0001_0000"),每次写操作后 8 个 16bit 数据同时自加 8 (for 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_rw对pstate / 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_mask、app_sr_req、app_ref_req、app_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 仿真注意事项(踩坑记录)
- 仿真时间精度 :模型要求仿真器分辨率达到 ps 级(2023/5/12 将 timescale 修改为
1ps/1fs后 Modelsim 可正常仿真); - 地址位宽对齐 :
ddr3_model中addr位宽可能与 MIG IP 核接口不一致,需修改ddr3_model_parameters.vh/ 模型端口,本工程已把 X16 的 addr 位宽由 13 改为 14 以对齐 IP 核接口; - 模型参数一致性 :
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):
- 需要重新生成/修改 MIG IP 核(数据位宽改为 32bit);
- 使用 example 工程重新生成对应的
ddr3_model文件,并修改ddr3_model_parameters.vh中参数,使其与 Memory Detail 一致、接口对齐(两块内存型号不一致); - 主要变化为 DM、DQ、DQS 的位宽。
5.5 仿真结果
我们准备好工程以及testbench之后,vivado点击run simulation之后运行仿真(前提是vivado和modelsim关联)会出现以下界面

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

我们可以看到仿真结果与我们设计代码逻辑吻合。
6. DDR3 带宽有效利用率(实测)
以 ui_clk 为基准统计:
| 场景 | 占用时钟周期 | 传输数据数 | 利用率 |
|---|---|---|---|
| 写 | 9717 | 8192 | 84% |
| 读 | 8916 | 8192 | 91% |
7. 使用说明(硬件验证流程)
- 在 Vivado 中打开工程
prj/ddr3_top.xpr,综合、实现并生成比特流; - 若更换硬件版本,仅需修改:MIG IP 核配置(内存型号/位宽)、
ddr3.xdc引脚约束(或导入新开发板 XDC)、顶层参数(WR_LEN、DATA_WIDTH等); - 下载比特流,通过烧写器/ILA 抓取
error_flag、error_num:error_flag = 0且error_num = 0:DDR3 读写通路正常;error_flag = 1:读回数据不符合递增规律,DDR3 硬件或连接存在问题;
- 仿真验证使用 Modelsim(或 Vivado Simulator),按第 5 节步骤加入内存模型即可复现功能。
总结
上述内容是将自己对DDR3使用都其中一种应用场景按照自己的理解整理出来供刚接触FPGA和DDR3的伙伴们做一个参考,希望感兴趣的小伙伴能亲自上手去操作实现一边,自己体会。
需要完整工程的伙伴可以私信或留言。

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