该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README

test docs platform

FPGA DDR-SDRAM

在低端FPGA设计中用 DDR-SDRAM(DDR1)替换 SDR-SDRAM。

简介

                         |-------------------------|          |--------------|
                         |                         |          |              |
                   ----->| 驱动时钟和复位          |          |              |
                         |                         |          |              |
------------|            |           DDR-SDRAM接口 |--------->|              |
            |   AXI4     |                         |          |              |
AXI4 master |----------->| AXI4 slave              |          |              |
            |            |                         |          |              |
------------|            |-------------------------|          |--------------|
  用户逻辑                       DDR1 控制器                       DDR1 芯片

很多低端 FPGA 开发板(例如 DE0-Nano)使用 SDR-SDRAM 作为片外存储,而 DDR-SDRAM (DDR1) 比 SDR-SDRAM 容量更大,价格更低。且与SDR-SDRAM一样,DDR1也能使用低端FPGA的普通的IO管脚直接驱动。我编写了一个软核的 AXI4 接口的 DDR1 控制器(见 RTL/ddr_sdram_ctrl.sv )。该控制器的特点有:

  • 平台无关 :纯 RTL 编写,可以在 Altera 和 Xilinx 等各种 FPGA 上运行。
  • 兼容性强 :支持各种位宽和容量的 DDR1 (已在MICRON所有位宽和容量的DDR1型号上仿真通过)。

为了展示该控制器的使用方法,我提供了两个示例程序:

  • 自测程序:通过DDR1控制器,将数据写入 DDR1 ,然后读出,比较读出的数据是否与写入的吻合。
  • UART读写程序:将 UART 命令转化成 AXI4 总线动作,通过DDR1控制器读写DDR1。

另外,由于各代 DDR-SDRAM(例如DDR3、DDR2、DDR1)的接口时序大同小异,本库也可以方便那些熟悉 Verilog 的人来学习 DDR-SDRAM 接口。

目录

硬件设计指南

对于 FPGA 的选型,只需要有足够数量的普通 IO 的 FPGA 就可以驱动 DDR1。DDR1 的 IO 电平标准往往是 SSTL-2 ,与 2.5V LVTTL 或 2.5V LVCMOS 兼容,因此相应的 FPGA 的 IO bank 的电源应该是 2.5V,且应在开发软件配置为 2.5V LVTTL 或 2.5V LVCMOS 。

下表展示了 DDR1 颗粒 的引脚,以及其与 FPGA 连接时应该注意的点。

引脚名称 方向 位宽 说明 连接方法 注意事项
ddr_ck_p FPGA output 1 差分时钟正,≥75MHz 应与 FPGA 的 2.5V LVTTL管脚连接 布线尽量短,与 ddr_ck_n 差分布线
ddr_ck_n FPGA output 1 差分时钟负,≥75MHz 应与 FPGA 的 2.5V LVTTL管脚连接 布线尽量短,与 ddr_ck_p 差分布线
ddr_cke、ddr_cs_n FPGA output 1 低速 应与 FPGA 的 2.5V LVTTL管脚连接
ddr_ras_n、ddr_cas_n、ddr_we_n FPGA output 1 与 ddr_ck_p 下降沿同步 应与 FPGA 的 2.5V LVTTL管脚连接 布线尽量短,与 ddr_ck_p 大致等长(长度别太离谱即可)
ddr_ba FPGA output 2 与 ddr_ck_p 下降沿同步 应与 FPGA 的 2.5V LVTTL管脚连接 布线尽量短,与 ddr_ck_p 大致等长(别太离谱即可)
ddr_a FPGA output 取决于芯片型号 与 ddr_ck_p 下降沿同步 应与 FPGA 的 2.5V LVTTL管脚连接 布线尽量短,与 ddr_ck_p 大致等长(别太离谱即可)
ddr_dqs inout 取决于芯片型号 应与 FPGA 的 2.5V LVTTL管脚连接 布线尽量短
ddr_dm、ddr_dq inout 取决于芯片型号 与 ddr_dqs 双沿同步 应与 FPGA 的 2.5V LVTTL管脚连接 布线尽量短,与 ddr_dqs 大致等长(别太离谱即可)

硬件设计示例

我用 Altera Cyclone IV 的一个非常低端和廉价的 FPGA (型号:EP4CE6E22C8N) 和 MICRON 的 64MB DDR1 (型号 MT46V64M8TG-6T)画了一个小板子,本库的所有例子可以直接在该板子上直接运行。

board-image
图:FPGA + DDR1 测试板

原理图见 hardware/sch.pdf,PCB制造文件见 hardware/gerber.zip ,在布局布线时,使用双层板即可,不需像 DDR2 以上的设计那样刻意注意等长和阻抗匹配,因为该电路工作频率为双边沿 75MHz,不是特别高,只需注意让FPGA与DDR距离尽量近,布线尽量短即可。比如我在布局时,把DDR1芯片放在了FPGA芯片正对的背面,从而保证布线都较短。

该板子的设计在立创EDA中开放,见这里

DDR1控制器

DDR1 控制器代码见 RTL/ddr_sdram_ctrl.sv ,是一个用 SystemVerilog 编写的模块,它能自动对 DDR1 进行初始化,并定时进行刷新(Refresh)。该模块有一个简化但完备的 AXI4 从接口,通过它可以完成对 DDR1 的读写。本节详细解释该模块的使用方法。

模块参数

模块参数和默认值在 Verilog 中定义如下:

module ddr_sdram_ctrl #(
    parameter   READ_BUFFER   = 1,
    parameter       BA_BITS   = 2,
    parameter       ROW_BITS  = 13,
    parameter       COL_BITS  = 11,
    parameter       DQ_LEVEL  = 1,
    parameter [9:0] tREFC     = 10'd256,
    parameter [7:0] tW2I      = 8'd7,
    parameter [7:0] tR2I      = 8'd7
)

对这些参数说明如下:

参数名 类型 取值范围 默认值 说明
READ_BUFFER 配置参数 0 或 1 1 若设为0,DDR1控制器内将没有读数据缓冲,读出的数据将不会等待AXI4主机是否能接受,即rvalid信号不会等待rready信号,直接以最高速率灌出去,此时AXI4是不完备的,但可以降低读出的时延。若设为1,则DDR1控制器内会有一个足够大的读数据缓冲,rvalid信号会与rready信号握手,确认AXI4主机准备好后再读出数据。
BA_BITS 位宽参数 1~4 2 规定了 DDR BANK ADDRESS (ddr_ba) 的宽度,常规的 DDR1 的 BANK ADDRESS 都是 2bit,因此该参数通常固定为默认值不用改
ROW_BITS 位宽参数 1~15 13 规定了 DDR ROW ADDRESS 的宽度,同时也决定了 DDR1 芯片的地址线引脚(ddr_a)的宽度。该参数取决于DDR1芯片的选型。例如 MT46V64M8 每个 BANK 有 8192 个 COL,考虑到2^11=8192,该参数应为13。同理,对于 MT46V128M8,该参数应为14
COL_BITS 位宽参数 1~14 11 规定了 DDR COL ADDRESS 的宽度。该参数取决于DDR1芯片的选型。例如 MT46V64M8 每个 ROW 有 2048 个 COL,考虑到2^11=2048,因此该参数应该为11。同理,对于MT46V32M16,该参数应为10。
DQ_LEVEL 位宽参数 0~7 1 规定了 DDR1 芯片的数据位宽,对于位宽x4的芯片(例如MT46V64M4),该参数应取0;对于位宽x8的芯片(例如MT46V64M8),该参数应取1;对于位宽x16的芯片(例如MT46V64M16),该参数应取2;对于位宽x32的情况(例如两片MT46V64M16扩展位宽),该参数应取3;以此类推。
tREFC 时序参数 10'd1 ~ 10'd1023 10'd256 该控制器会周期性地刷新 DDR1,该参数规定了刷新的间隔。详见下文时序参数的确定
tW2I 时序参数 8'd1~8'd255 8'd7 该参数规定了一个写动作的最后一个写命令到下一个动作的激活命令(ACT)的间隔。详见下文时序参数的确定
tR2I 时序参数 8'd1~8'd255 8'd7 该参数规定了一个读动作的最后一个读命令到下一个动作的激活命令(ACT)的间隔。详见下文时序参数的确定

模块接口:驱动时钟和复位

该模块需要一个时钟和一个复位进行驱动,如下:

    input  wire          rstn,
    input  wire          clk,

rstn 是低电平复位信号,正常工作时应该置高。clk 是驱动时钟,频率是用户时钟的 4 倍。

模块开始工作前,rstn信号应该置低,让模块复位,当 clk 的频率稳定后,可以把 rstn 置高,让模块处于工作状态。

如果 clk 一开始就是稳定的(例如直接来自晶振输入),那么 rstn 可以直接置为 1'b1 。

如果 clk 经过一段时间后才能稳定(例如来自 FPGA 的 PLL 或 MMCM),那么 rstn 应该接 PLL/MMCM 的 locked 信号,保证 PLL 锁相成功(稳定)后,模块再开始工作。

时钟频率的选取

模块内使用寄存器分频 4 倍产生 DDR1 驱动时钟(ddr_ck_p/ddr_ck_n)和 AXI4 总线用户时钟(aclk)。本节讲述如何决定驱动时钟 clk 的频率。

首先,时钟频率受限于 DDR1 芯片。考虑到所有的 DDR1 的接口频率至少为 75MHz ,则 clk 的下限是 75*4=300MHz。而 clk 的上限就取决于 DDR1 的芯片型号,例如对于 MT46V64M8P-5B ,查芯片手册可知,-5B 后缀的 DDR1 在 CAS Latency (CL)=2 时最高时钟频率是 133MHz,则 clk 的上限是 133*4=532MHz 。

注意:本控制器固定 CAS Latency (CL) = 2。

另外,时钟频率的上限还受限于 FPGA 的速度,太高的时钟频率容易导致时序不收敛。该控制器充分考虑时序安全设计,大多数寄存器工作在频率较低用户时钟域;个别寄存器工作在用户时钟的 2 倍频率的时钟下,且输入端口的组合逻辑非常短;还有一个寄存器工作在高频的 clk 下,但输入端口直接来自于上一级的寄存器输出(没有组合逻辑)。因此,即使在速度级别很低的 EP4CE6E22C8N 上,在 300MHz 的驱动时钟下也能保证模块正确运行。在速度等级更高的 FPGA (例如 EP4CE22F17C6N)上,驱动时钟的频率可以更高(例如400MHz)。

模块接口:DDR1接口

以下是该模块的 DDR1 接口。这些接口应该直接从 FPGA 引出,连接到 DDR1 芯片上。

    output wire                            ddr_ck_p, ddr_ck_n,
    output wire                            ddr_cke,
    output reg                             ddr_cs_n,
    output reg                             ddr_ras_n,
    output reg                             ddr_cas_n,
    output reg                             ddr_we_n,
    output reg   [            BA_BITS-1:0] ddr_ba,
    output reg   [           ROW_BITS-1:0] ddr_a,
    output wire  [((1<<DQ_LEVEL)+1)/2-1:0] ddr_dm,
    inout        [((1<<DQ_LEVEL)+1)/2-1:0] ddr_dqs,
    inout        [      (4<<DQ_LEVEL)-1:0] ddr_dq

可以看出 DDR1 接口的一些信号的位宽是和参数有关的,用户需要根据 DDR1 的芯片选型来确定模块参数。详见 位宽参数的确定

想了解 DDR1 接口在初始化、读写、刷新时的波形,请进行 Verilog仿真

模块接口:AXI4从接口

DDR1 控制器对外提供 AXI4 从接口(AXI4 slave)的时钟和复位信号,如下。AXI4 主机应该用它们作为自己的时钟和复位。

    output reg                                            aresetn,
    output reg                                            aclk,

以下是该模块的 AXI4 从接口,它们都与 aclk 时钟的上升沿同步,应该连接到 FPGA 内的的 AXI4 主机。

    input  wire                                           awvalid,
    output wire                                           awready,
    input  wire  [BA_BITS+ROW_BITS+COL_BITS+DQ_LEVEL-2:0] awaddr,
    input  wire                                    [ 7:0] awlen,
    input  wire                                           wvalid,
    output wire                                           wready,
    input  wire                                           wlast,
    input  wire                       [(8<<DQ_LEVEL)-1:0] wdata,
    output wire                                           bvalid,
    input  wire                                           bready,
    input  wire                                           arvalid,
    output wire                                           arready,
    input  wire  [BA_BITS+ROW_BITS+COL_BITS+DQ_LEVEL-2:0] araddr,
    input  wire                                    [ 7:0] arlen,
    output wire                                           rvalid,
    input  wire                                           rready,
    output wire                                           rlast,
    output wire                       [(8<<DQ_LEVEL)-1:0] rdata,

AXI4 总线的数据(wdata和rdata)的位宽是 DDR1 数据位宽的 2 倍。例如,对于位宽为 8bit 的 MT46V64M8,wdata 和 rdata 的位宽是 16bit 。

AXI4 总线的地址(awaddr和araddr)统一是字节地址,模块会根据用户指定的参数来计算 awaddr 和 araddr 的位宽 = BA_BITS+ROW_BITS+COL_BITS+DQ_LEVEL-1 。例如对于 64MB 的 MT46V64M8 ,awaddr/araddr 位宽为 26,而 2^26 等于 64MB。

每次读写访问时,地址值(awaddr和araddr)都要和整个字对齐,例如若 wdata 和 rdata 的位宽是 16bit(2字节),那么 wdata 和 rdata 要按 2 字节对齐,即能整除 2。

该模块的 AXI4 接口不支持字节选通写入,没有 wstrb 信号,每次至少写入一整个数据位宽,例如若 wdata 的位宽是 16,那么一次至少写入2字节。

该模块的 AXI4 接口支持突发读写,突发长度可取 1~256 之间的任意值,即 awlen 和 arlen 取值范围为 0~255 。例如,在一次写入动作中,awlen=12,则突发长度为 13,若 wdata 的位宽是 16 ,那么该次突发写一共写了 13*2=26B。

另外,每次突发读写不能超越 DDR1 内的一个行的边界。例如对于 MT46V64M8,每个行有 8*2^11/8 = 2048B,那么每次突发读写就不能超越 2048B 的边界。

AXI4 写操作时序

           __    __    __    __    __    __    __    __    __    __    __    __    _
aclk    __/  \__/  \__/  \__/  \__/  \__/  \__/  \__/  \__/  \__/  \__/  \__/  \__/
            ___________
awvalid ___/           \____________________________________________________________
                  _____
awready _________/     \____________________________________________________________
            ___________
awaddr  XXXX____ADDR___XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
            ___________
awlen   XXXX____8'd3___XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
                        ___________       ___________       _____
wvalid  _______________/           \_____/           \_____/     \__________________
                              ___________________________________
wready  _____________________/                                   \__________________
                                                            _____
wlast   ___________________________________________________/     \__________________
                        ___________       _____ _____       _____
wdata   XXXXXXXXXXXXXXXX_____D0____XXXXXXX__D1_X__D2_XXXXXXX__D3_XXXXXXXXXXXXXXXXXXX
                                                                  ___________
bvalid  _________________________________________________________/           \______
                                                                        _____
bready  _______________________________________________________________/     \______

一个典型的,突发长度为 4 (awlen=3) 的 AXI4 写操作如上图。分为 3 步:

  • 地址通道握手:AXI4 主机把 awvalid 信号拉高,指示想要发起写动作,图中经过一周期后,DDR1 控制器才把 awready 信号拉高,说明地址通道握手成功(在此之前,DDR1 控制器可能在处理上一次读写动作,或者在进行刷新,因此暂时没把 awready 拉高)。握手成功的同时,DDR1 控制器收到待写地址(awaddr)和突发长度(awlen),awlen=8'd3 说明突发长度为 4。
  • 数据传送:AXI4 总线上的 wvalid 和 wready 进行 4 次握手,把突发传输的 4 个数据写入 DDR1。在握手时,若 AXI4 主机还没准备好数据,可以把 wvalid 拉低,若 DDR1 控制器还没准备好接受数据,会把 wready 拉低。注意,根据 AXI4 总线的规定,在突发传输的最后一个数据传输的同时需要把 wlast 信号置高(实际上即使不置高,DDR1 控制器也能根据突发长度自动结束数据传送,进入写响应状态)。
  • 写响应:数据传送结束后,DDR1 控制器还需要和 AXI4 主机进行一次握手,才能彻底结束本次写动作。DDR1 控制器在数据传送结束后立即把 wvalid 信号拉高,并等待 wready 为高。完成该握手后,DDR1才能响应下一次读写操作。

AXI4 读操作时序

           __    __    __    __    __    __    __    __    __    __    __    __    _
aclk    __/  \__/  \__/  \__/  \__/  \__/  \__/  \__/  \__/  \__/  \__/  \__/  \__/
            ___________
arvalid ___/           \____________________________________________________________
                  _____
arready _________/     \____________________________________________________________
            ___________
araddr  XXXX____ADDR___XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
            ___________
arlen   XXXX____8'd4___XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
                                          ___________________________________
rvalid  _________________________________/                                   \______
        ___________________________________________________       __________________
rready                                                     \_____/
                                                                        _____
rlast   _______________________________________________________________/     \______
                                          _____ _____ _____ ___________ _____
rdata   XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX__D0_X__D1_X__D2_X_____D3____X__D4_XXXXXXX

一个典型的,突发长度为 5 (arlen=4) 的 AXI4 读操作如上图。分为 2 步:

  • 地址通道握手:AXI4 主机把 arvalid 信号拉高,指示想要发起写动作,图中经过一周期后,DDR1 控制器才把 arready 信号拉高,说明地址通道握手成功(在此之前,DDR1 控制器可能在处理上一次读写动作,或者在进行刷新,因此暂时没把 arready 拉高)。握手成功的同时,DDR1 控制器收到待读地址(araddr)和突发长度(arlen),arlen=8'd4 说明突发长度为 5。
  • 数据传送:AXI4 总线上的 rvalid 和 rready 进行 5 次握手,把突发传输的 5 个读出。在握手时,若 AXI4 主机还没准备好接受数据,可以把 rready 拉低,则 DDR1 控制器会保持当前数据,直到 AXI4主机能接受此数据(即把rready拉高)为止。在传送最后一个数据时,DDR1 控制器会把 rlast 拉高。

注:当模块参数 READ_BUFFER = 0 时,模块将节省一个 BRAM 资源,同时还能降低地址通道握手和数据传送之间的延迟。但 DDR1 控制器会忽略 rready=0 的情况,不等待 AXI4 主机是否准备好接受数据。这样将会破坏 AXI4 协议的完备性,但在一些简单的场合或许有用。

位宽参数的确定

本节讲述如何确定 BA_BITSROW_BITSCOL_BITSDQ_LEVEL 这 4 个参数。

模块接口:DDR1接口一节可以看出 DDR1 接口的一些信号的位宽是和参数有关的,用户需要根据 DDR1 的芯片选型来确定模块参数。

MICRON 公司的 DDR-SDRAM 系列芯片为例,不同芯片有不同的 ROW ADDRESS BITS (行地址宽度)、COL ADDRESS BITS(列地址宽度)、DATA BITS(数据宽度),决定了他们的位宽参数也不同,如下表(注:这些参数都能从芯片datasheet中查到)。

芯片名称 ddr_dq 位宽 ddr_dm 和 ddr_dqs 位宽 DQ_LEVEL 取值 BA_BITS ROW_BITS COL_BITS 每行字节数 容量 awaddr/araddr 位宽
MT46V64M4 4 1 0 2 13 11 1024 4*2^(2+13+11)=256Mb=32MB 25
MT46V128M4 4 1 0 2 13 12 2048 4*2^(2+13+12)=512Mb=64MB 26
MT46V256M4 4 1 0 2 14 12 4096 4*2^(2+14+12)=1Gb=128MB 27
MT46V32M8 8 1 1 2 13 10 1024 8*2^(2+13+10)=256Mb=32MB 25
MT46V64M8 8 1 1 2 13 11 2048 8*2^(2+13+11)=512Mb=64MB 26
MT46V128M8 8 1 1 2 14 11 4096 8*2^(2+14+11)=1Gb=128MB 27
MT46V16M16 16 2 2 2 13 9 1024 16*2^(2+13+9)=256Mb=32MB 25
MT46V32M16 16 2 2 2 13 10 2048 16*2^(2+13+10)=512Mb=64MB 26
MT46V64M16 16 2 2 2 14 10 4096 16*2^(2+14+10)=1Gb=128MB 27

时序参数的确定

本节讲述如何确定 tREFCtW2ItR2I 这 3 个时序参数。

我们知道,DDR1 需要周期性的刷新动作,tREFC 就规定了刷新的时钟周期间隔(以 aclk为准)。例如,若用户时钟为 75MHz,根据 MT46V64M8 的芯片手册,需要至多 7.8125us 刷新一次,考虑到 75MHz * 7.8125us = 585.9,则该参数可以设置为一个小于 585 的值,例如 10'd512。

tW2I 规定了一个写操作的最后一个写命令到下一个操作的激活命令(ACT)的时钟周期数(以 aclk 为准)的最小值。下图展示了一个 DDR1 接口上的写操作,ddr_cas_n 的第一个上升沿代表了一个写操作的最后一个写命令结束,ddr_ras_n 的第二个下降沿代表了下一个操作(可能是读、写、刷新)的开始,它们之间有 5 个时钟周期,tW2I 就是用来规定该周期数的下限的。tW2I 的默认值 8'd7 是一个兼容绝大多数 DDR1 的保守值,对于不同的 DDR1 芯片,有不同的缩小的余地(详见 DDR1 芯片 datasheet)。

             __    __    __    __    __    __    __    __    __    __    __    __
ddr_ck_p  __/  \__/  \__/  \__/  \__/  \__/  \__/  \__/  \__/  \__/  \__/  \__/  \__
          _____       _______________________________________________       ________
ddr_ras_n      \_____/                                               \_____/
          _________________             ____________________________________________
ddr_cas_n                  \___________/
          _________________             ____________________________________________
ddr_we_n                   \___________/
                                  _____
ddr_a[10] XXXXXXXXXXXXXXXXXX_____/     XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
                _______________________
ddr_ba    XXXXXX__________BA___________XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
                _______________________
ddr_a     XXXXXX__RA_XXXXXXX_CA0_X_CA1_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

tR2I 规定了一个读操作的最后一个读命令到下一个操作的激活命令(ACT)的时钟周期数(以 aclk 为准)的最小值。下图展示了一个 DDR1 接口上的读操作,ddr_cas_n 的第一个上升沿代表了一个读操作的最后一个读命令结束,ddr_ras_n 的第二个下降沿代表了下一个操作(可能是读、写、刷新)的开始,它们之间有 5 个时钟周期,tR2I 就是用来规定该周期数的下限的。tR2I 的默认值 8'd7 是一个兼容绝大多数 DDR1 的保守值,对于不同的 DDR1 芯片,有不同的缩小的余地(详见 DDR1 芯片 datasheet)。

             __    __    __    __    __    __    __    __    __    __    __    __
ddr_ck_p  __/  \__/  \__/  \__/  \__/  \__/  \__/  \__/  \__/  \__/  \__/  \__/  \__
          _____       _______________________________________________       ________
ddr_ras_n      \_____/                                               \_____/
          _________________             ____________________________________________
ddr_cas_n                  \___________/
          __________________________________________________________________________
ddr_we_n
                                  _____
ddr_a[10] XXXXXXXXXXXXXXXXXX_____/     XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
                _______________________
ddr_ba    XXXXXX__________BA___________XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
                _______________________
ddr_a     XXXXXX__RA_XXXXXXX_CA0_X_CA1_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

示例程序

示例程序:自测

我基于我画的 FPGA+DDR1测试板 做了一个 DDR1 读写自测程序,工程目录是 example-selftest,请用 Quartus II 13.1 打开它。

该工程包含以下文件:

文件名称 用途
top.sv 顶层
axi_self_test_master.sv 是 AXI4 主机,通过 AXI4 先把有规律的数据写入 DDR1,然后读回,比较读回的数据是否符合规律,并对不匹配的情况进行计数。
ddr_sdram_ctrl.sv DDR1 控制器
pll.v 使用板上 50MHz 晶振产生 300MHz 时钟,输出给 DDR1 控制器

写入:该工程开始运行后,会先通过 AXI4 把整个 DDR1 都写一遍,直接把地址字写入相应的地址。例如,如果 AXI4 的数据宽度是 16bit,那么地址 0x000002 处写 0x0001。地址 0x123456 处写 0x3456。

读取&错误校验:写完整个 DDR1 后,该工程会一轮一轮地反复读取整个 DDR1,若读出的数据不符合上述规律,就认为出现错误,在错误信号(error)上产生一个高电平脉冲,并把错误计数信号(error_cnt)+1。如果 DDR1 配置正确,是不该出现 error 信号的。你可以测量 error_cnt 对应的引脚,若为0(全低电平),说明不存在错误。

SignalTap抓波形:该工程包含一个 SignalTap 文件(stp1.stp),在程序运行时,可以用它查看 DDR1 接口上的波形。它以 error=1 为触发信号,因此如果读写自测没有出错,它就不会被触发。因为该工程随时都在读取 DDR1,要想看 DDR1 接口上的波形,直接按“停止”按钮即可。

修改 AXI4 突发长度:在 top.sv 的第 82,83 行可以修改 WBURST_LEN 和 RBURST_LEN,从而修改自测时的写/读突发长度,该自测程序只支持 2^n-1 这种突发长度,即 WBURST_LEN 和 RBURST_LEN 必须取形如 0,1,3,7,15,31,…… 的值(注意,这只是我编写的自测程序的限制,DDR1 控制器是支持 0~255 之间的任意突发长度的。

WBURST_LEN 和 RBURST_LEN 可以设置的不一样。

示例程序:UART读写

我基于我画的 FPGA+DDR1测试板 做了一个 UART 读写程序,使用该程序,你可以通过 UART 命令,以不同的突发长度来读写 DDR1。工程目录是 example-uart-read-write,请用 Quartus II 13.1 打开它。

该工程包含以下文件:

文件名称 用途
top.sv 顶层
uart2axi4.sv 是 AXI4 主机,能把 UART RX 收到的命令转换成 AXI4 读写操作,并把读操作读出的数据通过 UART TX 发送出去
uart_rx.sv UART RX 接收器,被 uart2axi4.sv 调用
axis2uarttx.sv UART TX 发送器,被 uart2axi4.sv 调用
ddr_sdram_ctrl.sv DDR1 控制器
pll.v 使用板上 50MHz 晶振产生 300MHz 时钟,输出给 DDR1 控制器

FPGA+DDR1测试板上有一个 CH340E 芯片(USB 转 UART),因此插上 USB 线后就可以在电脑上看见 UART 对应的 COM 口(需要先在这里下载安装 CH341 的驱动)。

工程上传 FPGA 后,双击打开我编写的一个串口小工具 UartSession.exe ,根据提示打开板子对应的 COM 口,然后打如下的命令+回车,可以把 0x0123 0x4567 0x89ab 0xcdef 这 4 个数据写入起始地址0x12345。(AXI4总线上会产生一个突发长度为 4 的写操作)。

w12345 0123 4567 89ab cdef

然后用以下命令+回车,可以以 0x12345 为起始地址,以 7 为突发长度,读取 8 个数据。

r12345 7

效果如下图,前4个数据 (0123 4567 89ab cdef) 就是我们已经写入 DDR1 的,后4个数据我们没写过,是 DDR1 初始化后自带的随机数据。

UART读写
图:使用 UartSession.exe 测试 DDR1 读写

写命令里有多少数据,写突发长度就是多少,例如以下写命令的突发长度是 9,将 10 个数据写入起始地址 0x00000

w0 1234 2345 3456 4567 5678 6789 789a 89ab 9abc abcd

读命令则直接指定突发长度,例如以下命令的突发长度为 30 (0x1e),从起始地址 0x00000 将 31 个数据读出

r0 1e

Verilog仿真

建立仿真工程

仿真所需要的文件在目录 sim-selftest 里,你可以用以下 Verilog 文件建立仿真工程:

文件路径 用途
sim-selftest/SRC/tb_ddr_ctrl.sv 仿真顶层
sim-selftest/SRC/axi_self_test_master.sv 是 AXI4 主机,通过 AXI4 先把有规律的数据写入 DDR1,然后读回,比较读回的数据是否符合规律,并对不匹配的情况进行计数。
RTL/ddr_sdram_ctrl.sv DDR1 控制器
sim-selftest/DDR_MODEL/ddr.v MICRON 公司提供的 DDR1 仿真模型
sim-selftest/DDR_MODEL/ddr_parameters.vh DDR1 仿真模型的参数配置文件

该仿真工程的行为类似自测程序axi_self_test_master.sv 作为 AXI4 主机,将有规律的数据写入 DDR1 中,只不过不是全部写入,而是只写入 DDR1 的前 16KB (因为仿真模型的存储空间有限),然后一轮一轮地反复读出数据,比较是否有不匹配的数据,若有,则在 error 信号上产生一个时钟周期的高电平。

运行仿真

建立工程后,直接运行仿真,得到如下波形。前 294us,AXI4 主机在进行前 16KB 的写入;294us 之后,AXI4 主机在不断的读出数据。error 信号一直为低电平说明读出的数据无误。

仿真波形
图:仿真波形

你可以展开查看 AXI4 总线和 DDR1 接口的波形细节。这里不做赘述。

修改仿真参数

以上仿真默认配置的参数是使用 MT46V64M8 ,即 ROW_BITS=13,COL_BITS=11,DQ_BITS=8。如果想对其它型号的 DDR1 芯片进行仿真,需要修改的参数如下:

对于 MICRON 公司的 DDR1 系列,这些参数应该这样修改:

芯片名称 BA_BITS ROW_BITS COL_BITS DQ_LEVEL DQ_BITS
MT46V64M4 2 13 11 0 4
MT46V128M4 2 13 12 0 4
MT46V256M4 2 14 12 0 4
MT46V32M8 2 13 10 1 8
MT46V64M8 2 13 11 1 8
MT46V128M8 2 14 11 1 8
MT46V16M16 2 13 9 2 16
MT46V32M16 2 13 10 2 16
MT46V64M16 2 14 10 2 16

另外,你可以修改 tb_ddr_ctrl.sv 的第 18 和 19 行来修改仿真时的突发读写的长度。

参考资料

空文件

简介

Adopt DDR-SDRAM(DDR1) in low-end FPGA designs. 展开 收起
SystemVerilog 等 2 种语言
取消

发行版

暂无发行版

贡献者

全部

近期动态

不能加载更多了
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化