正点原子 发表于 2019-7-13 12:49:10

【正点原子FPGA连载】第三十三章 SDRAM读写测试实验--摘自【正点原子】开拓者 FPGA 开发指南

本帖最后由 正点原子 于 2020-10-23 16:50 编辑

1)实验平台:正点原子开拓者FPGA开发板
2)平台购买地址:https://item.taobao.com/item.htm?id=579749209820
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-281143-1-1.html
4)本实例源码下载:
5)对正点原子FPGA感兴趣的同学可以加群讨论:712557122点击加入:
6)关注正点原子公众号,获取最新资料更新

第三十三章 SDRAM读写测试实验

SDRAM是一种可以指定任意地址进行读写的存储器, 它具有存储容量大, 读写速度快的特点, 同时价格也相对低廉。 因此, SDRAM常作为缓存, 应用于数据存储量大, 同时速度要求较高的场合, 如复杂嵌入式设备的存储器等。 本章我们将利用FPGA实现SDRAM控制器, 并完成开
发板上SDRAM芯片的读写测试。
本章包括以下几个部分:
33.1 SDRAM简介
33.2 实验任务
33.3 硬件设计
33.4 程序设计
33.5 下载验证
33.1 SDRAM简介
SDRAM(Synchronous Dynamic Random Access Memory) , 同步动态随机存储器。 同步是指内存工作需要同步时钟, 内部的命令的发送与数据的传输都以它为基准; 动态是指存储阵列需要不断的刷新来保证数据不丢失; 随机是指数据不是线性依次存储, 而是自由指定地址进行
数据读写。SDRAM具有空间存储量大、 读写速度快、 价格相对便宜等优点。 然而由于SDRAM内部利用电容来存储数据, 为保证数据不丢失, 需要持续对各存储电容进行刷新操作; 同时在读写过程中需要考虑行列管理、 各种操作延时等, 由此导致了其控制逻辑复杂的特点。
SDRAM的内部是一个存储阵列, 你可以把它想象成一张表格。 我们在向这个表格中写入数据的时候, 需要先指定一个行(Row) , 再指定一个列(Column) , 就可以准确地找到所需要的“单元格” , 这就是SDRAM寻址的基本原理。 如图 33.1.1所示:


图 33.1.1 SDRAM寻址原理

图 31.1.1中的“单元格” 就是SDRAM存储芯片中的存储单元, 而这个“表格”(存储阵列)我们称之为L-Bank。 通常SDRAM的存储空间被划分为4个L-Bank, 在寻址时需要先指定其中一个L-Bank, 然后在这个选定的L-Bank中选择相应的行与列进行寻址(寻址就是指定存储单元地址
的过程) 。对SDRAM的读写是针对存储单元进行的, 对SDRAM来说一个存储单元的容量等于数据总线的位宽, 单位是bit。 那么SDRAM芯片的总存储容量我们就可以通过下面的公式计算出来:
SDRAM总存储容量 = L-Bank的数量×行数×列数×存储单元的容量SDRAM存储数据是利用了电容的充放电特性以及能够保持电荷的能力。 一个大小为1bit的存储单元的结构如下图所示, 它主要由行列选通三极管, 存储电容, 刷新放大器组成。 行地址与列地址选通使得存储电容与数据线导通, 从而可进行放电(读取) 与充电(写入) 操作。


图 33.1.2 SDRAM存储单元结构示意图

图 33.1.3为SDRAM的功能框图, SDRAM内部有一个逻辑控制单元, 并且有一个模式寄存器为其提供控制参数。 SDRAM接收外部输入的控制命令, 并在逻辑控制单元的控制下进行寻址、读写、 刷新、 预充电等操作。


图 33.1.3 SDRAM功能框图


在了解SDRAM的寻址原理及存储结构之后, 我们来看下如何实现SDRAM的读写。 首先, 在对SDRAM进行读写操作之前需要先对芯片进行初始化; 其次, SDRAM读写是一个较为复杂的控制流程, 其中包括行激活、 列读写、 预充电、 刷新等一系列操作。 大家需要熟练掌握每一个操作所对应的时序要求, 才能够正确地对SDRAM进行读写操作。
1、 芯片初始化
SDRAM芯片上电之后需要一个初始化的过程, 以保证芯片能够按照预期方式正常工作, 初始化流程如图 33.1.4所示:


图 33.1.4 SDRAM初始化

SDRAM上电后要有200us的输入稳定期, 在这个时间内不可以对SDRAM的接口做任何操作;200us结束以后给所有L-Bank预充电, 然后是连续8次刷新操作; 最后设置模式寄存器。 初始化最关键的阶段就在于模式寄存器(MR, Mode Register) 的设置, 简称MRS(MR Set) 。


图 33.1.5 模式寄存器

如上图所示, 用于配置模式寄存器的参数由地址线提供, 地址线不同的位分别用于表示不同的参数。 SDRAM通过配置模式寄存器来确定芯片的工作方式, 包括突发长度(Burst Length)、潜伏期(CAS Latency) 以及操作模式等。
需要注意的是, 在模式寄存器设置指令发出之后, 需要等待一段时间才能够向SDRAM发送新的指令, 这个时间我们称之为模式寄存器设置周期tRSC(Register Set Cycle) 。
2、 行激活
初始化完成后, 无论是读操作还是写操作, 都要先激活(Active) SDRAM中的一行, 使之处于活动状态(又称行有效) 。 在此之前还要进行SDRAM芯片的片选和L-Bank的定址, 不过它们与行激活可以同时进行。


图 33.1.6 行激活时序图

从上图可以看出, 在片选CS#(#表示低电平有效) 、 L-Bank定址的同时, RAS(Row AddressStrobe, 行地址选通脉冲) 也处于有效状态。 此时An地址线则发送具体的行地址。 如图中是A0-A11, 共有12个地址线, 由于是二进制表示法, 所以共有4096个行(2^12=4096) , A0-A11
的不同数值就确定了具体的行地址。 由于行激活的同时也是相应L-Bank有效, 所以行激活也可称为L-Bank有效。
3、 列读写
行地址激活之后, 就要对列地址进行寻址了。 由于在SDRAM中, 地址线是行列共用的, 因此列寻址时地址线仍然是A0-A11。 在寻址时, 利用RAS(Row Address Strobe, 行地址选通脉冲) 与CAS(Column Address Strobe, 列地址选通脉冲) 来区分行寻址与列寻址, 如图 33.1.7
所示。图 33.1.7中“x16” 表示存储单元容量为16bit。 一般来说, 在SDRAM中存储阵列(L-Bank)的列数小于行数, 即列地址位宽小于行地址, 因此在列地址选通时地址线高位可能未用到, 如下图中的A9、 A11。另外, 列寻址信号与读写命令是同时发出的, 读/写命令是通过WE(Write Enable, 写使
能) 信号来控制的, WE为低时是写命令, 为高时是读命令。


图 33.1.7 列选通与读操作时序图

然而,在发送列读写命令时必须要与行激活命令有一个时间间隔,这个间隔被定义为tRCD,即RAS to CAS Delay(RAS至CAS延迟) 。 这是因为在行激活命令发出之后, 芯片存储阵列电子元件响应需要一定的时间。 tRCD是SDRAM的一个重要时序参数, 广义的tRCD以时钟周期(tCK,Clock Time) 数为单位, 比如tRCD=3, 就代表RAS至CAS延迟为三个时钟周期, 如图 33.1.8所示。 具体到确切的时间, 则要根据时钟频率而定。


图 33.1.8 tRCD = 3时序图

4、 数据输出(读)
在选定列地址后, 就已经确定了具体的存储单元, 剩下的事情就是数据通过数据I/O通道(DQ) 输出到内存总线上了。 但是在CAS发出之后, 仍要经过一定的时间才能有数据输出, 从CAS与读取命令发出到第一笔数据输出的这段时间, 被定义为CL(CAS Latency, CAS潜伏期) 。CL时间越短, 读数据时SDRAM响应就越快。 由于CL只在读取时出现, 所以CL又被称为读取潜伏期(RL, Read Latency) 。 CL的单位与tRCD一样, 为时钟周期数, 具体耗时由时钟频率决定。


图 33.1.9 CL = 2 时序图

5、 数据输入(写)
数据写入的操作也是在tRCD之后进行, 但此时没有了CL(记住, CL只出现在读取操作中) ,行寻址与列寻址的时序图和上文一样, 只是在列寻址时, WE#为有效状态。


图 33.1.10 数据写入的时序图

从上图中可见, 数据与写指令同时发送。 不过, 数据并不是即时地写入存储单元, 数据的真正写入需要一定的周期。 为了保证数据的可靠写入, 都会留出足够的写入/校正时间(tWR,Write Recovery Time) , 这个操作也被称作写回(Write Back) 。 tWR至少占用一个时钟周期
或再多一点(时钟频率越高, tWR占用周期越多) 。
6、 突发长度
突发(Burst) 是指在同一行中相邻的存储单元连续进行数据传输的方式, 连续传输所涉及到存储单元(列) 的数量就是突发长度(Burst Lengths, 简称BL) 。上文讲到的读/写操作, 都是一次对一个存储单元进行寻址。 然而在现实中很少只对SDRAM
中的单个存储空间进行读写, 一般都需要完成连续存储空间中的数据传输。 在连续读/写操作时, 为了对当前存储单元的下一个单元进行寻址, 需要不断的发送列地址与读/写命令(行地址不变, 所以不用再对行寻址) , 如图 33.1.11所示:


图 33.1.11 非突发连续读操作

由上图可知, 虽然由于读延迟相同可以让数据的传输在I/O端是连续的, 但它占用了大量的内存控制资源, 在数据进行连续传输时无法输入新的命令, 效率很低。 为此, 人们开发了突发传输技术, 只要指定起始列地址与突发长度, 内存就会依次地自动对后面相应数量的存储单元进行读/写操作而不再需要控制器连续地提供列地址。 这样, 除了第一笔数据的传输需要若干个周期(主要是之前的延迟, 一般的是tRCD+CL) 外, 其后每个数据只需一个周期的延时即可获得。 如图 33.1.12所示:


图 33.1.12 突发连续读操作

至于BL的数值, 也是不能随便设或在数据进行传输前临时决定。 在上文讲到的初始化过程中的模式寄存器配置(MRS) 阶段就要对BL进行设置。 突发长度(BL) 可以为1、 2、 4、 8和“全页(Full Page) ” , 其中“全页” 是指突发传输一整行的数据量。
另外, 在MRS阶段除了要设定BL数值之外, 还需要确定“读/写操作模式” 以及“突发传输模式” 。 读/写操作模式分为“突发读/突发写” 和“突发读/单一写” 。 突发读/突发写表示读和写操作都是突发传输的, 每次读/写操作持续BL所设定的长度, 这也是常规的设定。 突发读/单一写表示读操作是突发传输, 写操作则只是一个个单独进行。突发传输模式代表着突发周期内所涉及到的存储单元的传输顺序。顺序传输是指从起始单元开始顺序读取。 假如BL=4, 起始存储单元编号是n, 突发传输顺序就是n、 n+1、 n+2、 n+3。交错传输就是打乱正常的顺序进行数据传输(比如第一个进行传输的单元是n, 而第二个进行
传输的单元是n+2而不是n+1)。 由于交错传输很少用到, 它的传输规则在这里就不详细介绍了,大家可以参考所选用的SDRAM芯片手册。
7、 预充电
在对SDRAM某一存储地址进行读写操作结束后, 如果要对同一L-Bank的另一行进行寻址,就要将原来有效(工作) 的行关闭, 重新发送行/列地址。 L-Bank关闭现有工作行, 准备打开新行的操作就是预充电(Precharge) 。 在读写过程中, 工作行内的存储体由于“行激活” 而
使存储电容受到干扰, 因此在关闭工作行前需要对本行所有存储体进行重写。 预充电实际上就是对工作行中所有存储体进行数据重写, 并对行地址进行复位, 以准备新行工作的过程。预充电可以通过命令控制, 也可以通过辅助设定让芯片在每次读写操作之后自动进行预充
电。 现在我们再回过头看看读写操作时的命令时序图(图 33.1.7) , 从中可以发现地址线A10控制着是否进行在读写之后对当前L-Bank自动进行预充电, 这就是上文所说的“辅助设定” 。而在单独的预充电命令中,A10则控制着是对指定的L-Bank还是所有的L-Bank(当有多个L-Bank
处于有效/活动状态时) 进行预充电, 前者需要提供L-Bank的地址, 后者只需将A10信号置于高电平。在发出预充电命令之后, 要经过一段时间才能发送行激活命令打开新的工作行, 这个间隔被称为tRP(Precharge command Period, 预充电有效周期) , 如图 33.1.13所示。 和tRCD、

图 33.1.13 读取时预充电时序图(CL=2、 BL=4、 tRP=2)

自动预充电的开始时间与上图一样,只是没有了单独的预充电命令,并在发出读取命令时,CL一样, tRP的单位也是时钟周期数, 具体值视时钟频率而定。A10地址线要设为高电平(允许自动预充电) 。 可见控制好预充电启动时间很重要, 它可以在读取操作结束后立刻进入新行的寻址, 保证运行效率。写操作时, 由于每笔数据的真正写入则需要一个足够的周期来保证, 这段时间就是写回周期(tWR) 。 所以预充电不能与写操作同时进行, 必须要在tWR之后才能发出预充电命令, 以确保数据的可靠写入, 否则重写的数据可能出错, 如图 33.1.14所示


图 33.1.14 写入时预充电时序图(BL=4、 tWR=1、 tRP=2)

8、 刷新
SDRAM之所以称为同步“动态” 随机存储器, 就是因为它要不断进行刷新(Refresh) 才能保留住数据, 因此刷新是SDRAM最重要的操作。刷新操作与预充电类似, 都是重写存储体中的数据。 但为什么有预充电操作还要进行刷新呢? 因为预充电是对一个或所有L-Bank中的工作行(处于激活状态的行) 操作, 并且是不定期的; 而刷新则是有固定的周期, 并依次对所有行进行操作, 以保留那些久久没经历重写的存储体中的数据。 但与所有L-Bank预充电不同的是, 这里的行是指所有L-Bank中地址相同的行, 而预充电中各L-Bank中的工作行地址并不是一定是相同的。
那么要隔多长时间重复一次刷新呢? 目前公认的标准是, 存储体中电容的数据有效保存期上限是64ms, 也就是说每一行刷新的循环周期是64ms。 我们在看SDRAM芯片参数时, 经常会看到4096 Refresh Cycles/64ms或8192 Refresh Cycles/64ms的标识, 这里的4096与8192就代表
这个芯片中每个L-Bank的行数。 刷新命令一次仅对一行有效, 也就是说在64ms内这两种规格的芯片分别需要完成4096次和8192次刷新操作。 因此, L-Bank为4096行时刷新命令的发送间隔为15.625μs(64ms/4096) , 8192行时为7.8125μs(64ms/8192) 。
刷新操作分为两种: 自动刷新(Auto Refresh, 简称AR) 与自刷新(Self Refresh, 简称SR) 。 不论是何种刷新方式, 都不需要外部提供行地址信息, 因为这是一个内部的自动操作。对于自动刷新(AR) , SDRAM内部有一个行地址生成器(也称刷新计数器) 用来自动生成行地
址。 由于刷新是针对一行中的所有存储体进行, 所以无需列寻址, 或者说CAS在RAS之前有效。所以, AR又称CBR(CAS Before RAS, 列提前于行定位) 式刷新。在自动刷新过程中, 所有L-Bank都停止工作。 每次刷新操作所需要的时间为自动刷新周期
(tRC) , 在自动刷新指令发出后需要等待tRC才能发送其他指令。 64ms之后再次对同一行进行刷新操作, 如此周而复始进行循环刷新。 显然, 刷新操作肯定会对SDRAM的性能造成影响, 但这是没办法的事情, 也是DRAM相对于SRAM(静态内存, 无需刷新仍能保留数据) 取得成本优势的同时所付出的代价。自刷新(SR) 主要用于休眠模式低功耗状态下的数据保存。 在发出AR命令时, 将CKE置于无效状态, 就进入了SR模式, 此时不再依靠系统时钟工作, 而是根据内部的时钟进行刷新操作。在SR期间除了CKE之外的所有外部信号都是无效的(无需外部提供刷新指令) , 只有重新使CKE有效才能退出自刷新模式并进入正常工作状态。
9、 数据掩码
在讲述读/写操作时, 我们谈到了突发长度。 如果BL=4, 那么也就是说一次就传送4笔数据。但是, 如果其中的第二笔数据是不需要的, 怎么办? 还要传输吗? 为了屏蔽不需要的数据, 人们采用了数据掩码(Data I/O Mask, 简称DQM) 技术。 通过DQM, 内存可以控制I/O端口取消哪些输出或输入的数据。 为了精确屏蔽一个数据总线位宽中的每个字节, 每个DQM信号线对应一个字节(8bit) 。 因此, 对于数据总线为16bit的SDRAM芯片, 就需要两个DQM引脚。SDRAM官方规定, 在读取时DQM发出两个时钟周期后生效, 如图 33.1.15所示。 而在写入时,
DQM与写入命令一样是立即成效, 如图 33.1.16所示。


图 33.1.15 读取时DQM信号时序图



图 33.1.16 写入时DQM信号时序图

33.2 实验任务
本节实验任务是向开拓者开发板上的SDRAM中写入1024个数据, 从SDRAM存储空间的起始地址写起, 写完后再将数据读出, 并验证读出数据是否正确
33.3 硬件设计
开拓者开发板上SDRAM部分的原理图如图 33.3.1所示



图 33.3.1 SDRAM原理图

开拓者开发板上的SDRAM芯片型号为W9825G6DH-6, 内部分为4个L-Bank, 行地址为13位,列地址为9位, 数据总线位宽为16bit。 故该SDRAM总的存储空间为4×(2^13)×(2^9)×16 bit =256Mbit, 即32MB。W9825G6DH-6工作时钟频率最高可达166MHz, 潜伏期(CAS Latency) 可选为2或3, 突发长
度支持1、 2、 4、 8或全页, 64ms内需要完成8K次刷新操作。 其他时序参数请大家参考该芯片的数据手册。本实验中, 各端口信号的管脚分配如下表所示:
表 33.3.1 SDRAM读写测试实验管脚分配









33.4 程序设计
在本次实验中, 由于SDRAM的控制时序较为复杂, 为方便用户调用, 我们将SDRAM控制器封装成FIFO接口, 这样我们操作SDRAM就像读写FIFO一样简单。 整个系统的功能框图如图 33.4.1所示:


图 33.4.1 SDRAM读写测试系统框图

PLL时钟模块: 本实验中SDRAM读写测试及LED显示模块输入时钟均为50MHz, 而SDRAM控制器工作在100MHz时钟频率下, 另外还需要一个输出给SDRAM芯片的100MHz时钟。 因此需要一个PLL时钟模块用于产生系统各个模块所需的时钟。
SDRAM测试模块: 产生测试数据及读写使能, 写使能将1024个数据(1~1024) 写入SDRAM,写操作完成后读使能拉高, 持续进行读操作, 并检测读出的数据是否正确。FIFO控制模块: 作为SDRAM控制器与用户的交互接口, 该模块在写FIFO中的数据量到达用
户指定的突发长度后将数据自动写入SDRAM; 并在读FIFO中的数据量小于突发长度时将SDRAM中的数据读出。SDRAM控制器: 负责完成外部SDRAM存储芯片的初始化、 读写及刷新等一系列操作。
Led显示模块: 通过控制LED灯的显示状态来指示SDRAM读写测试结果。由系统框图可知, FPGA顶层例化了以下四个模块: PLL时钟模块(pll_clk) 、 SDRAM测试模块(sdram_test) 、 LED灯指示模块(led_disp) 以及SDRAM控制器顶层模块(sdram_top) 。
各模块端口及信号连接如图 33.4.2所示:


图 33.4.2 顶层模块原理图

SDRAM测试模块(sdram_test) 输出读写使能信号及写数据, 通过SDRAM控制器将数据写入SDARM中地址为0~1023的存储空间中。 在写过程结束后进行读操作, 检测读出的数据是否与写入数据一致, 检测结果由标志信号error_flag指示。 LED显示模块根据error_flag的值驱动LED
以不同的状态显示。 当SDRAM读写测试正确时, LED灯常亮; 读写测试结果不正确时, LED灯闪烁。
顶层模块的代码如下:
1 module sdram_rw_test(
2 input clk, //FPGA外部时钟, 50M
3 input rst_n, //按键复位, 低电平有效
4 //SDRAM 芯片接口
5 output sdram_clk, //SDRAM 芯片时钟
6 output sdram_cke, //SDRAM 时钟有效
7 output sdram_cs_n, //SDRAM 片选
8 output sdram_ras_n, //SDRAM 行有效
9 output sdram_cas_n, //SDRAM 列有效
10 output sdram_we_n, //SDRAM 写有效
11 output [ 1:0] sdram_ba, //SDRAM Bank地址
12 output sdram_addr, //SDRAM 行/列地址
13 inout sdram_data, //SDRAM 数据
14 output [ 1:0] sdram_dqm, //SDRAM 数据掩码
15 //LED
16 output led //状态指示灯
17 );
18
19 //wire define
20 wire clk_50m; //SDRAM 读写测试时钟
21 wire clk_100m; //SDRAM 控制器时钟
22 wire clk_100m_shift; //相位偏移时钟
23
24 wire wr_en; //SDRAM 写端口:写使能
25 wire wr_data; //SDRAM 写端口:写入的数据
26 wire rd_en; //SDRAM 读端口:读使能
27 wire rd_data; //SDRAM 读端口:读出的数据
28 wire sdram_init_done; //SDRAM 初始化完成信号
29
30 wire locked; //PLL输出有效标志
31 wire sys_rst_n; //系统复位信号
32 wire error_flag; //读写测试错误标志
33
34 //*****************************************************
35 //** main code
36 //*****************************************************
37
38 //待PLL输出稳定之后, 停止系统复位
39 assign sys_rst_n = rst_n & locked;
40
41 //例化PLL, 产生各模块所需要的时钟
42 pll_clk u_pll_clk(
43 .inclk0 (clk),
44 .areset (~rst_n),
45
46 .c0 (clk_50m),
47 .c1 (clk_100m),
48 .c2 (clk_100m_shift),
49 .locked (locked)
50 );
51
52 //SDRAM测试模块, 对SDRAM进行读写测试
53 sdram_test u_sdram_test(
54 .clk_50m (clk_50m),
55 .rst_n (sys_rst_n),
56
57 .wr_en (wr_en),
58 .wr_data (wr_data),
59 .rd_en (rd_en),
60 .rd_data (rd_data),
61
62 .sdram_init_done (sdram_init_done),
63 .error_flag (error_flag)
64 );
65
66 //利用LED灯指示SDRAM读写测试的结果
67 led_disp u_led_disp(
68 .clk_50m (clk_50m),
69 .rst_n (sys_rst_n),
70
71 .error_flag (error_flag),
72 .led (led)
73 );
74
75 //SDRAM 控制器顶层模块,封装成FIFO接口
76 //SDRAM 控制器地址组成: {bank_addr,row_addr,col_addr}
77 sdram_top u_sdram_top(
78 .ref_clk (clk_100m), //sdram 控制器参考时钟
79 .out_clk (clk_100m_shift), //用于输出的相位偏移时钟
80 .rst_n (sys_rst_n), //系统复位
81
82 //用户写端口
83 .wr_clk (clk_50m), //写端口FIFO: 写时钟
84 .wr_en (wr_en), //写端口FIFO: 写使能
85 .wr_data (wr_data), //写端口FIFO: 写数据
86 .wr_min_addr (24'd0), //写SDRAM的起始地址
87 .wr_max_addr (24'd1024), //写SDRAM的结束地址
88 .wr_len (10'd512), //写SDRAM时的数据突发长度
89 .wr_load (~sys_rst_n), //写端口复位: 复位写地址,清空写FIFO
90
91 //用户读端口
92 .rd_clk (clk_50m), //读端口FIFO: 读时钟
93 .rd_en (rd_en), //读端口FIFO: 读使能
94 .rd_data (rd_data), //读端口FIFO: 读数据
95 .rd_min_addr (24'd0), //读SDRAM的起始地址
96 .rd_max_addr (24'd1024), //读SDRAM的结束地址
97 .rd_len (10'd512), //从SDRAM中读数据时的突发长度
98 .rd_load (~sys_rst_n), //读端口复位: 复位读地址,清空读FIFO
99
100 //用户控制端口
101 .sdram_read_valid (1'b1), //SDRAM 读使能
102 .sdram_init_done (sdram_init_done), //SDRAM 初始化完成标志
103
104 //SDRAM 芯片接口
105 .sdram_clk (sdram_clk), //SDRAM 芯片时钟
106 .sdram_cke (sdram_cke), //SDRAM 时钟有效
107 .sdram_cs_n (sdram_cs_n), //SDRAM 片选
108 .sdram_ras_n (sdram_ras_n), //SDRAM 行有效
109 .sdram_cas_n (sdram_cas_n), //SDRAM 列有效
110 .sdram_we_n (sdram_we_n), //SDRAM 写有效
111 .sdram_ba (sdram_ba), //SDRAM Bank地址
112 .sdram_addr (sdram_addr), //SDRAM 行/列地址
113 .sdram_data (sdram_data), //SDRAM 数据
114 .sdram_dqm (sdram_dqm) //SDRAM 数据掩码
115 );
116
117 endmodule
顶层模块中主要完成对其余模块的例化, 需要注意的是由于SDRAM工作时钟频率较高, 且对时序要求比较严格, 考虑到FPGA内部以及开发板上的走线延时, 为保证SDRAM能够准确的读写数据, 我们输出给SDRAM芯片的100MHz时钟相对于SDRAM控制器时钟有一个相位偏移。 程序中
的相位偏移时钟为clk_100m_shift(第48行) , 相位偏移量在这里设置为-75deg。由于SDRAM控制器被封装成FIFO接口, 在使用时只需要像读写FIFO那样给出读/写使能即可,如代码82~98行所示。 同时控制器将SDRAM的阵列地址映射为线性地址, 在调用时将其当作连续
存储空间进行读写。 因此读写过程不需要指定Bank地址及行列地址, 只需要给出起始地址和结束地址即可, 数据在该地址空间中连续读写。 线性地址的位宽为SDRAM的Bank地址、 行地址和列地址位宽的总和, 也可以理解成线性地址的组成结构为{ bank_addr, row_addr,
col_addr}。程序第88行及第92行指定SDRAM控制器的数据突发长度, 由于W9825G6DH-6的全页突发长度为512, 因此控制器的突发长度不能大于512。
SDRAM读写测试模块的代码如下所示:
1 module sdram_test(
2 input clk_50m, //时钟
3 input rst_n, //复位,低有效
4
5 output reg wr_en, //SDRAM 写使能
6 output reg wr_data, //SDRAM 写入的数据
7 output reg rd_en, //SDRAM 读使能
8 input rd_data, //SDRAM 读出的数据
9 1
0 input sdram_init_done, //SDRAM 初始化完成标志
11 output reg error_flag //SDRAM 读写测试错误标志
12 );
13
14 //reg define
15 reg init_done_d0; //寄存SDRAM初始化完成信号
16 reg init_done_d1; //寄存SDRAM初始化完成信号
17 reg wr_cnt; //写操作计数器
18 reg rd_cnt; //读操作计数器
19 reg rd_valid; //读数据有效标志
20
21 //*****************************************************
22 //** main code
23 //*****************************************************
24
25 //同步SDRAM初始化完成信号
26 always @(posedge clk_50m or negedge rst_n) begin
27 if(!rst_n) begin
28 init_done_d0 <= 1'b0;
29 init_done_d1 <= 1'b0;
30 end
31 else begin
32 init_done_d0 <= sdram_init_done;
33 init_done_d1 <= init_done_d0;
34 end
35 end
36
37 //SDRAM初始化完成之后,写操作计数器开始计数
38 always @(posedge clk_50m or negedge rst_n) begin
39 if(!rst_n)
40 wr_cnt <= 11'd0;
41 else if(init_done_d1 && (wr_cnt <= 11'd1024))
42 wr_cnt <= wr_cnt + 1'b1;
43 else
44 wr_cnt <= wr_cnt;
45 end
46
47 //SDRAM写端口FIFO的写使能、 写数据(1~1024)
48 always @(posedge clk_50m or negedge rst_n) begin
49 if(!rst_n) begin
50 wr_en <= 1'b0;
51 wr_data <= 16'd0;
52 end
53 else if(wr_cnt >= 11'd1 && (wr_cnt <= 11'd1024)) begin
54 wr_en <= 1'b1; //写使能拉高
55 wr_data <= wr_cnt; //写入数据1~1024
56 end
57 else begin
58 wr_en <= 1'b0;
59 wr_data <= 16'd0;
60 end
61 end
62
63 //写入数据完成后,开始读操作
64 always @(posedge clk_50m or negedge rst_n) begin
65 if(!rst_n)
66 rd_en <= 1'b0;
67 else if(wr_cnt > 11'd1024) //写数据完成
68 rd_en <= 1'b1; //读使能拉高
69 end
70
71 //对读操作计数
72 always @(posedge clk_50m or negedge rst_n) begin
73 if(!rst_n)
74 rd_cnt <= 11'd0;
75 else if(rd_en) begin
76 if(rd_cnt < 11'd1024)
77 rd_cnt <= rd_cnt + 1'b1;
78 else
79 rd_cnt <= 11'd1;
80 end
81 end
82
83 //第一次读取的数据无效,后续读操作所读取的数据才有效
84 always @(posedge clk_50m or negedge rst_n) begin
85 if(!rst_n)
86 rd_valid <= 1'b0;
87 else if(rd_cnt == 11'd1024) //等待第一次读操作结束
88 rd_valid <= 1'b1; //后续读取的数据有效
89 else
90 rd_valid <= rd_valid;
91 end
92
93 //读数据有效时,若读取数据错误,给出标志信号
94 always @(posedge clk_50m or negedge rst_n) begin
95 if(!rst_n)
96 error_flag <= 1'b0;
97 else if(rd_valid && (rd_data != rd_cnt))
98 error_flag <= 1'b1; //若读取的数据错误,将错误标志位拉高
99 else
100 error_flag <= error_flag;
101 end
102
103 endmodule
SDRAM读写测试模块从写起始地址开始, 连续向1024个存储空间中写入数据1~1024。 写完成后一直进行读操作, 持续将该存储空间的数据读出。 需要注意的是程序中第97行通过变量rd_valid将第一次读出的1024个数据排除, 并未参与读写测试。 这是由于SDRAM控制器为了保
证读FIFO时刻有数据, 在读使能拉高之前就已经将SDRAM中的数据“预读” 一部分(突发读长度) 到读FIFO中; 而此时写SDRAM尚未完成, 因此第一次从FIFO中读出的512个数据是无效的。第一次读操作结束后, 读FIFO中的无效数据被读出并丢弃, 后续读SDRAM得到的数据才用于验
证读写过程是否正确。LED显示模块的代码如下:
1 module led_disp(
2 input clk_50m, //系统时钟
3 input rst_n, //系统复位
4 5
input error_flag, //错误标志信号
6 output reg led //LED灯
7 );
8 9
//reg define
10 reg led_cnt; //控制LED闪烁周期的计数器
11
12 //*****************************************************
13 //** main code
14 //*****************************************************
15
16 //计数器对50MHz时钟计数, 计数周期为0.5s
17 always @(posedge clk_50m or negedge rst_n) begin
18 if(!rst_n)
19 led_cnt <= 25'd0;
20 else if(led_cnt < 25'd25000000)
21 led_cnt <= led_cnt + 25'd1;
22 else
23 led_cnt <= 25'd0;
24 end
25
26 //利用LED灯不同的显示状态指示错误标志的高低
27 always @(posedge clk_50m or negedge rst_n) begin
28 if(rst_n == 1'b0)
29 led <= 1'b0;
30 else if(error_flag) begin
31 if(led_cnt == 25'd25000000)
32 led <= ~led; //错误标志为高时, LED灯每隔0.5s闪烁一次
33 else
34 led <= led;
35 end
36 else
37 led <= 1'b1; //错误标志为低时, LED灯常亮
38 end
39
40 endmodule
LED显示模块用LED不同的显示状态指示SDRAM读写测试的结果: 若读写测试正确无误, 则LED常亮; 若出现错误(读出的数据与写入的数据不一致) , 则LED灯以0.5s为周期闪烁。SDRAM控制器顶层模块如下:
1 module sdram_top(
2 input ref_clk, //sdram 控制器参考时钟
3 input out_clk, //用于输出的相位偏移时钟
4 input rst_n, //系统复位
5 6
//用户写端口
7 input wr_clk, //写端口FIFO: 写时钟
8 input wr_en, //写端口FIFO: 写使能
9 input wr_data, //写端口FIFO: 写数据
10 input wr_min_addr, //写SDRAM的起始地址
11 input wr_max_addr, //写SDRAM的结束地址
12 input [ 9:0] wr_len, //写SDRAM时的数据突发长度
13 input wr_load, //写端口复位: 复位写地址,清空写FIFO
14
15 //用户读端口
16 input rd_clk, //读端口FIFO: 读时钟
17 input rd_en, //读端口FIFO: 读使能
18 output rd_data, //读端口FIFO: 读数据
19 input rd_min_addr, //读SDRAM的起始地址
20 input rd_max_addr, //读SDRAM的结束地址
21 input [ 9:0] rd_len, //从SDRAM中读数据时的突发长度
22 input rd_load, //读端口复位: 复位读地址,清空读FIFO
23
24 //用户控制端口
25 input sdram_read_valid, //SDRAM 读使能
26 output sdram_init_done, //SDRAM 初始化完成标志
27
28 //SDRAM 芯片接口
29 output sdram_clk, //SDRAM 芯片时钟
30 output sdram_cke, //SDRAM 时钟有效
31 output sdram_cs_n, //SDRAM 片选
32 output sdram_ras_n, //SDRAM 行有效
33 output sdram_cas_n, //SDRAM 列有效
34 output sdram_we_n, //SDRAM 写有效
35 output [ 1:0] sdram_ba, //SDRAM Bank地址
36 output sdram_addr, //SDRAM 行/列地址
37 inout sdram_data, //SDRAM 数据
38 output [ 1:0] sdram_dqm //SDRAM 数据掩码
39 );
40
41 //wire define
42 wire sdram_wr_req; //sdram 写请求
43 wire sdram_wr_ack; //sdram 写响应
44 wire sdram_wr_addr; //sdram 写地址
45 wire sdram_din; //写入sdram中的数据
46
47 wire sdram_rd_req; //sdram 读请求
48 wire sdram_rd_ack; //sdram 读响应
49 wire sdram_rd_addr; //sdram 读地址
50 wire sdram_dout; //从sdram中读出的数据
51
52 //*****************************************************
53 //** main code
54 //*****************************************************
55 assign sdram_clk = out_clk; //将相位偏移时钟输出给sdram芯片
56 assign sdram_dqm = 2'b00; //读写过程中均不屏蔽数据线
57
58 //SDRAM 读写端口FIFO控制模块
59 sdram_fifo_ctrl u_sdram_fifo_ctrl(
60 .clk_ref (ref_clk), //SDRAM控制器时钟
61 .rst_n (rst_n), //系统复位
62
63 //用户写端口
64 .clk_write (wr_clk), //写端口FIFO: 写时钟
65 .wrf_wrreq (wr_en), //写端口FIFO: 写请求
66 .wrf_din (wr_data), //写端口FIFO: 写数据
67 .wr_min_addr (wr_min_addr), //写SDRAM的起始地址
68 .wr_max_addr (wr_max_addr), //写SDRAM的结束地址
69 .wr_length (wr_len), //写SDRAM时的数据突发长度
70 .wr_load (wr_load), //写端口复位: 复位写地址,清空写FIFO
71
72 //用户读端口
73 .clk_read (rd_clk), //读端口FIFO: 读时钟
74 .rdf_rdreq (rd_en), //读端口FIFO: 读请求
75 .rdf_dout (rd_data), //读端口FIFO: 读数据
76 .rd_min_addr (rd_min_addr), //读SDRAM的起始地址
77 .rd_max_addr (rd_max_addr), //读SDRAM的结束地址
78 .rd_length (rd_len), //从SDRAM中读数据时的突发长度
79 .rd_load (rd_load), //读端口复位: 复位读地址,清空读FIFO
80
81 //用户控制端口
82 .sdram_read_valid (sdram_read_valid), //sdram 读使能
83 .sdram_init_done (sdram_init_done), //sdram 初始化完成标志
84
85 //SDRAM 控制器写端口
86 .sdram_wr_req (sdram_wr_req), //sdram 写请求
87 .sdram_wr_ack (sdram_wr_ack), //sdram 写响应
88 .sdram_wr_addr (sdram_wr_addr), //sdram 写地址
89 .sdram_din (sdram_din), //写入sdram中的数据
90
91 //SDRAM 控制器读端口
92 .sdram_rd_req (sdram_rd_req), //sdram 读请求
93 .sdram_rd_ack (sdram_rd_ack), //sdram 读响应
94 .sdram_rd_addr (sdram_rd_addr), //sdram 读地址
95 .sdram_dout (sdram_dout) //从sdram中读出的数据
96 );
97
98 //SDRAM控制器
99 sdram_controller u_sdram_controller(
100 .clk (ref_clk), //sdram 控制器时钟
101 .rst_n (rst_n), //系统复位
102
103 //SDRAM 控制器写端口
104 .sdram_wr_req (sdram_wr_req), //sdram 写请求
105 .sdram_wr_ack (sdram_wr_ack), //sdram 写响应
106 .sdram_wr_addr (sdram_wr_addr), //sdram 写地址
107 .sdram_wr_burst (wr_len), //写sdram时数据突发长度
108 .sdram_din (sdram_din), //写入sdram中的数据
109
110 //SDRAM 控制器读端口
111 .sdram_rd_req (sdram_rd_req), //sdram 读请求
112 .sdram_rd_ack (sdram_rd_ack), //sdram 读响应
113 .sdram_rd_addr (sdram_rd_addr), //sdram 读地址
114 .sdram_rd_burst (rd_len), //读sdram时数据突发长度
115 .sdram_dout (sdram_dout), //从sdram中读出的数据
116
117 .sdram_init_done (sdram_init_done), //sdram 初始化完成标志
118
119 //SDRAM 芯片接口
120 .sdram_cke (sdram_cke), //SDRAM 时钟有效
121 .sdram_cs_n (sdram_cs_n), //SDRAM 片选
122 .sdram_ras_n (sdram_ras_n), //SDRAM 行有效
123 .sdram_cas_n (sdram_cas_n), //SDRAM 列有效
124 .sdram_we_n (sdram_we_n), //SDRAM 写有效
125 .sdram_ba (sdram_ba), //SDRAM Bank地址
126 .sdram_addr (sdram_addr), //SDRAM 行/列地址
127 .sdram_data (sdram_data) //SDRAM 数据
128 );
129
130 endmodule
SDRAM控制器顶层模块主要完成SDRAM控制器及SDRAM读写端口FIFO控制模块的例化。 代码中56行给SDRAM数据掩码赋值, 因为我们读写测试过程中, 数据线的高字节和低字节均一直有效, 因此整个SDRAM读写过程中不需要屏蔽数据线。SDRAM FIFO控制模块代码如下所示:
1 module sdram_fifo_ctrl(
2 input clk_ref, //SDRAM控制器时钟
3 input rst_n, //系统复位
4 5
//用户写端口
6 input clk_write, //写端口FIFO: 写时钟
7 input wrf_wrreq, //写端口FIFO: 写请求
8 input wrf_din, //写端口FIFO: 写数据
9 input wr_min_addr, //写SDRAM的起始地址
10 input wr_max_addr, //写SDRAM的结束地址
11 input [ 9:0] wr_length, //写SDRAM时的数据突发长度
12 input wr_load, //写端口复位: 复位写地址,清空写FIFO
13
14 //用户读端口
15 input clk_read, //读端口FIFO: 读时钟
16 input rdf_rdreq, //读端口FIFO: 读请求
17 output rdf_dout, //读端口FIFO: 读数据
18 input rd_min_addr, //读SDRAM的起始地址
19 input rd_max_addr, //读SDRAM的结束地址
20 input [ 9:0] rd_length, //从SDRAM中读数据时的突发长度
21 input rd_load, //读端口复位: 复位读地址,清空读FIFO
22
23 //用户控制端口
24 input sdram_read_valid, //SDRAM 读使能
25 input sdram_init_done, //SDRAM 初始化完成标志
26
27 //SDRAM 控制器写端口
28 output reg sdram_wr_req, //sdram 写请求
29 input sdram_wr_ack, //sdram 写响应
30 output reg sdram_wr_addr, //sdram 写地址
31 output sdram_din, //写入SDRAM中的数据
32
33 //SDRAM 控制器读端口
34 output reg sdram_rd_req, //sdram 读请求
35 input sdram_rd_ack, //sdram 读响应
36 output reg sdram_rd_addr, //sdram 读地址
37 input sdram_dout //从SDRAM中读出的数据
38 );
39
40 //reg define
41 reg wr_ack_r1; //sdram写响应寄存器
42 reg wr_ack_r2;
43 reg rd_ack_r1; //sdram读响应寄存器
44 reg rd_ack_r2;
45 reg wr_load_r1; //写端口复位寄存器
46 reg wr_load_r2;
47 reg rd_load_r1; //读端口复位寄存器
48 reg rd_load_r2;
49 reg read_valid_r1; //sdram读使能寄存器
50 reg read_valid_r2;
51
52 //wire define
53 wire write_done_flag; //sdram_wr_ack 下降沿标志位
54 wire read_done_flag; //sdram_rd_ack 下降沿标志位
55 wire wr_load_flag; //wr_load 上升沿标志位
56 wire rd_load_flag; //rd_load 上升沿标志位
57 wire wrf_use; //写端口FIFO中的数据量
58 wire rdf_use; //读端口FIFO中的数据量
59
60 //*****************************************************
61 //** main code
62 //*****************************************************
63
64 //检测下降沿
65 assign write_done_flag = wr_ack_r2 & ~wr_ack_r1;
66 assign read_done_flag = rd_ack_r2 & ~rd_ack_r1;
67
68 //检测上升沿
69 assign wr_load_flag = ~wr_load_r2 & wr_load_r1;
70 assign rd_load_flag = ~rd_load_r2 & rd_load_r1;
71
72 //寄存sdram写响应信号,用于捕获sdram_wr_ack下降沿
73 always @(posedge clk_ref or negedge rst_n) begin
74 if(!rst_n) begin
75 wr_ack_r1 <= 1'b0;
76 wr_ack_r2 <= 1'b0;
77 end
78 else begin
79 wr_ack_r1 <= sdram_wr_ack;
80 wr_ack_r2 <= wr_ack_r1;
81 end
82 end
83
84 //寄存sdram读响应信号,用于捕获sdram_rd_ack下降沿
85 always @(posedge clk_ref or negedge rst_n) begin
86 if(!rst_n) begin
87 rd_ack_r1 <= 1'b0;
88 rd_ack_r2 <= 1'b0;
89 end
90 else begin
91 rd_ack_r1 <= sdram_rd_ack;
92 rd_ack_r2 <= rd_ack_r1;
93 end
94 end
95
96 //同步写端口复位信号, 用于捕获wr_load上升沿
97 always @(posedge clk_ref or negedge rst_n) begin
98 if(!rst_n) begin
99 wr_load_r1 <= 1'b0;
100 wr_load_r2 <= 1'b0;
101 end
102 else begin
103 wr_load_r1 <= wr_load;
104 wr_load_r2 <= wr_load_r1;
105 end
106 end
107
108 //同步读端口复位信号, 同时用于捕获rd_load上升沿
109 always @(posedge clk_ref or negedge rst_n) begin
110 if(!rst_n) begin
111 rd_load_r1 <= 1'b0;
112 rd_load_r2 <= 1'b0;
113 end
114 else begin
115 rd_load_r1 <= rd_load;
116 rd_load_r2 <= rd_load_r1;
117 end
118 end
119
120 //同步sdram读使能信号
121 always @(posedge clk_ref or negedge rst_n) begin
122 if(!rst_n) begin
123 read_valid_r1 <= 1'b0;
124 read_valid_r2 <= 1'b0;
125 end
126 else begin
127 read_valid_r1 <= sdram_read_valid;
128 read_valid_r2 <= read_valid_r1;
129 end
130 end
131
132 //sdram写地址产生模块
133 always @(posedge clk_ref or negedge rst_n) begin
134 if(!rst_n)
135 sdram_wr_addr <= 24'd0;
136 else if(wr_load_flag) //检测到写端口复位信号时, 写地址复位
137 sdram_wr_addr <= wr_min_addr;
138 else if(write_done_flag) begin //若突发写SDRAM结束, 更改写地址
139 //若未到达写SDRAM的结束地址, 则写地址累加
140 if(sdram_wr_addr < wr_max_addr - wr_length)
141 sdram_wr_addr <= sdram_wr_addr + wr_length;
142 else //若已到达写SDRAM的结束地址, 则回到写起始地址
143 sdram_wr_addr <= wr_min_addr;
144 end
145 end
146
147 //sdram读地址产生模块
148 always @(posedge clk_ref or negedge rst_n) begin
149 if(!rst_n)
150 sdram_rd_addr <= 24'd0;
151 else if(rd_load_flag) //检测到读端口复位信号时, 读地址复位
152 sdram_rd_addr <= rd_min_addr;
153 else if(read_done_flag) begin //突发读SDRAM结束, 更改读地址
154 //若未到达读SDRAM的结束地址, 则读地址累加
155 if(sdram_rd_addr < rd_max_addr - rd_length)
156 sdram_rd_addr <= sdram_rd_addr + rd_length;
157 else //若已到达读SDRAM的结束地址, 则回到读起始地址
158 sdram_rd_addr <= rd_min_addr;
159 end
160 end
161
162 //sdram 读写请求信号产生模块
163 always@(posedge clk_ref or negedge rst_n) begin
164 if(!rst_n) begin
165 sdram_wr_req <= 0;
166 sdram_rd_req <= 0;
167 end
168 else if(sdram_init_done) begin //SDRAM初始化完成后才能响应读写请求
169 //优先执行写操作, 防止写入SDRAM中的数据丢失
170 if(wrf_use >= wr_length) begin //若写端口FIFO中的数据量达到了写突发长度
171 sdram_wr_req <= 1; //发出写sdarm请求
172 sdram_rd_req <= 0;
173 end
174 else if((rdf_use < rd_length) //若读端口FIFO中的数据量小于读突发长度,
175 && read_valid_r2) begin //同时sdram读使能信号为高
176 sdram_wr_req <= 0;
177 sdram_rd_req <= 1; //发出读sdarm请求
178 end
179 else begin
180 sdram_wr_req <= 0;
181 sdram_rd_req <= 0;
182 end
183 end
184 else begin
185 sdram_wr_req <= 0;
186 sdram_rd_req <= 0;
187 end
188 end
189
190 //例化写端口FIFO
191 wrfifo u_wrfifo(
192 //用户接口
193 .wrclk (clk_write), //写时钟
194 .wrreq (wrf_wrreq), //写请求
195 .data (wrf_din), //写数据
196
197 //sdram接口
198 .rdclk (clk_ref), //读时钟
199 .rdreq (sdram_wr_ack), //读请求
200 .q (sdram_din), //读数据
201
202 .rdusedw (wrf_use), //FIFO中的数据量
203 .aclr (~rst_n | wr_load_flag) //异步清零信号
204 );
205
206 //例化读端口FIFO
207 rdfifo u_rdfifo(
208 //sdram接口
209 .wrclk (clk_ref), //写时钟
210 .wrreq (sdram_rd_ack), //写请求
211 .data (sdram_dout), //写数据
212
213 //用户接口
214 .rdclk (clk_read), //读时钟
215 .rdreq (rdf_rdreq), //读请求
216 .q (rdf_dout), //读数据
217
218 .wrusedw (rdf_use), //FIFO中的数据量
219 .aclr (~rst_n | rd_load_flag) //异步清零信号
220 );
221
222 endmodule
SDRAM读写FIFO控制模块在SDRAM控制器的使用过程中起到非常重要的作用, 它一方面通过用户接口处理读写请求, 另一方面通过控制器接口完成SDRAM控制器的操作。 它的存在为用户屏蔽了相对复杂的SDRAM控制器接口, 使我们可以像读写FIFO一样操作SDRAM控制器。如程序中第162~188行所示, FIFO控制模块优先处理SDRAM写请求, 以免写FIFO溢出时, 用于写入SDRAM的数据丢失。当写FIFO中的数据量大于写突发长度时,执行写SDRAM操作;当读FIFO中的数据量小于读突发长度时, 执行读SDRAM操作。SDRAM控制器代码如下:
1 module sdram_controller(
2 input clk, //SDRAM控制器时钟, 100MHz
3 input rst_n, //系统复位信号, 低电平有效
4 5
//SDRAM 控制器写端口
6 input sdram_wr_req, //写SDRAM请求信号
7 output sdram_wr_ack, //写SDRAM响应信号
8 input sdram_wr_addr, //SDRAM写操作的地址
9 input [ 9:0] sdram_wr_burst, //写sdram时数据突发长度
10 input sdram_din, //写入SDRAM的数据
11
12 //SDRAM 控制器读端口
13 input sdram_rd_req, //读SDRAM请求信号
14 output sdram_rd_ack, //读SDRAM响应信号
15 input sdram_rd_addr, //SDRAM写操作的地址
16 input [ 9:0] sdram_rd_burst, //读sdram时数据突发长度
17 output sdram_dout, //从SDRAM读出的数据
18
19 output sdram_init_done, //SDRAM 初始化完成标志
20
21 // FPGA与SDRAM硬件接口
22 output sdram_cke, // SDRAM 时钟有效信号
23 output sdram_cs_n, // SDRAM 片选信号
24 output sdram_ras_n, // SDRAM 行地址选通脉冲
25 output sdram_cas_n, // SDRAM 列地址选通脉冲
26 output sdram_we_n, // SDRAM 写允许位
27 output [ 1:0] sdram_ba, // SDRAM L-Bank地址线
28 output sdram_addr, // SDRAM 地址总线
29 inout sdram_data // SDRAM 数据总线
30 );
31
32 //wire define
33 wire init_state; // SDRAM初始化状态
34 wire work_state; // SDRAM工作状态
35 wire cnt_clk; // 延时计数器
36 wire sdram_rd_wr; // SDRAM读/写控制信号,低电平为写, 高电平为读
37
38 //*****************************************************
39 //** main code
40 //*****************************************************
41
42 // SDRAM 状态控制模块
43 sdram_ctrl u_sdram_ctrl(
44 .clk (clk),
45 .rst_n (rst_n),
46
47 .sdram_wr_req (sdram_wr_req),
48 .sdram_rd_req (sdram_rd_req),
49 .sdram_wr_ack (sdram_wr_ack),
50 .sdram_rd_ack (sdram_rd_ack),
51 .sdram_wr_burst (sdram_wr_burst),
52 .sdram_rd_burst (sdram_rd_burst),
53 .sdram_init_done (sdram_init_done),
54
55 .init_state (init_state),
56 .work_state (work_state),
57 .cnt_clk (cnt_clk),
58 .sdram_rd_wr (sdram_rd_wr)
59 );
60
61 // SDRAM 命令控制模块
62 sdram_cmd u_sdram_cmd(
63 .clk (clk),
64 .rst_n (rst_n),
65
66 .sys_wraddr (sdram_wr_addr),
67 .sys_rdaddr (sdram_rd_addr),
68 .sdram_wr_burst (sdram_wr_burst),
69 .sdram_rd_burst (sdram_rd_burst),
70
71 .init_state (init_state),
72 .work_state (work_state),
73 .cnt_clk (cnt_clk),
74 .sdram_rd_wr (sdram_rd_wr),
75
76 .sdram_cke (sdram_cke),
77 .sdram_cs_n (sdram_cs_n),
78 .sdram_ras_n (sdram_ras_n),
79 .sdram_cas_n (sdram_cas_n),
80 .sdram_we_n (sdram_we_n),
81 .sdram_ba (sdram_ba),
82 .sdram_addr (sdram_addr)
83 );
84
85 // SDRAM 数据读写模块
86 sdram_data u_sdram_data(
87 .clk (clk),
88 .rst_n (rst_n),
89
90 .sdram_data_in (sdram_din),
91 .sdram_data_out (sdram_dout),
92 .work_state (work_state),
93 .cnt_clk (cnt_clk),
94
95 .sdram_data (sdram_data)
96 );
97
98 endmodule
SDRAM控制器主要例化了三个模块: SDRAM状态控制模块、 SDRAM命令控制模块、 SDRAM数据读写模块。 下图为SDRAM控制器的功能框图:


图 33.4.3 SDRAM控制器功能框图


SDRAM状态控制模块根据SDRAM内部及外部操作指令控制初始化状态机和工作状态机;SDRAM命令控制模块根据两个状态机的状态给SDRAM输出相应的控制命令; 而SDRAM数据读写模块则负责根据工作状态机控制SDRAM数据线的输入输出。SDRAM状态控制模块代码如下所示:
1 module sdram_ctrl(
2 input clk, //系统时钟
3 input rst_n, //复位信号, 低电平有效
4 5
input sdram_wr_req, //写SDRAM请求信号
6 input sdram_rd_req, //读SDRAM请求信号
7 output sdram_wr_ack, //写SDRAM响应信号
8 output sdram_rd_ack, //读SDRAM响应信号
9 input sdram_wr_burst, //突发写SDRAM字节数(1-512个)
10 input sdram_rd_burst, //突发读SDRAM字节数(1-256个)
11 output sdram_init_done, //SDRAM系统初始化完毕信号
12
13 output reg init_state, //SDRAM初始化状态
14 output reg work_state, //SDRAM工作状态
15 output reg cnt_clk, //时钟计数器
16 output reg sdram_rd_wr //SDRAM读/写控制信号, 低电平为写, 高电平为读
17 );
18
19 `include "sdram_para.v" //包含SDRAM参数定义模块
20
21 //parameter define
22 parameter TRP_CLK = 10'd4; //预充电有效周期
23 parameter TRC_CLK = 10'd6; //自动刷新周期
24 parameter TRSC_CLK = 10'd6; //模式寄存器设置时钟周期
25 parameter TRCD_CLK = 10'd2; //行选通周期
26 parameter TCL_CLK = 10'd3; //列潜伏期
27 parameter TWR_CLK = 10'd2; //写入校正
28
29 //reg define
30 reg cnt_200us; //SDRAM 上电稳定期200us计数器
31 reg cnt_refresh; //刷新计数寄存器
32 reg sdram_ref_req; //SDRAM 自动刷新请求信号
33 reg cnt_rst_n; //延时计数器复位信号, 低有效
34 reg [ 3:0] init_ar_cnt; //初始化过程自动刷新计数器
35
36 //wire define
37 wire done_200us; //上电后200us输入稳定期结束标志位
38 wire sdram_ref_ack; //SDRAM自动刷新请求应答信号
39
40 //*****************************************************
41 //** main code
42 //*****************************************************
43
44 //SDRAM上电后200us稳定期结束后,将标志信号拉高
45 assign done_200us = (cnt_200us == 15'd20_000);
46
47 //SDRAM初始化完成标志
48 assign sdram_init_done = (init_state == `I_DONE);
49
50 //SDRAM 自动刷新应答信号
51 assign sdram_ref_ack = (work_state == `W_AR);
52
53 //写SDRAM响应信号
54 assign sdram_wr_ack = ((work_state == `W_TRCD) & ~sdram_rd_wr) |
55 ( work_state == `W_WRITE)|
56 ((work_state == `W_WD) & (cnt_clk < sdram_wr_burst - 2'd2));
57
58 //读SDRAM响应信号
59 assign sdram_rd_ack = (work_state == `W_RD) &
60 (cnt_clk >= 10'd1) & (cnt_clk < sdram_rd_burst + 2'd1);
61
62 //上电后计时200us,等待SDRAM状态稳定
63 always @ (posedge clk or negedge rst_n) begin
64 if(!rst_n)
65 cnt_200us <= 15'd0;
66 else if(cnt_200us < 15'd20_000)
67 cnt_200us <= cnt_200us + 1'b1;
68 else
69 cnt_200us <= cnt_200us;
70 end
71
72 //刷新计数器循环计数7812ns (60ms内完成全部8192行刷新操作)
73 always @ (posedge clk or negedge rst_n)
74 if(!rst_n)
75 cnt_refresh <= 11'd0;
76 else if(cnt_refresh < 11'd781) // 64ms/8192 =7812ns
77 cnt_refresh <= cnt_refresh + 1'b1;
78 else
79 cnt_refresh <= 11'd0;
80
81 //SDRAM 刷新请求
82 always @ (posedge clk or negedge rst_n)
83 if(!rst_n)
84 sdram_ref_req <= 1'b0;
85 else if(cnt_refresh == 11'd780)
86 sdram_ref_req <= 1'b1; //刷新计数器计时达7812ns时产生刷新请求
87 else if(sdram_ref_ack)
88 sdram_ref_req <= 1'b0; //收到刷新请求响应信号后取消刷新请求
89
90 //延时计数器对时钟计数
91 always @ (posedge clk or negedge rst_n)
92 if(!rst_n)
93 cnt_clk <= 10'd0;
94 else if(!cnt_rst_n) //在cnt_rst_n为低电平时延时计数器清零
95 cnt_clk <= 10'd0;
96 else
97 cnt_clk <= cnt_clk + 1'b1;
98
99 //初始化过程中对自动刷新操作计数
100 always @ (posedge clk or negedge rst_n)
101 if(!rst_n)
102 init_ar_cnt <= 4'd0;
103 else if(init_state == `I_NOP)
104 init_ar_cnt <= 4'd0;
105 else if(init_state == `I_AR)
106 init_ar_cnt <= init_ar_cnt + 1'b1;
107 else
108 init_ar_cnt <= init_ar_cnt;
109
110 //SDRAM的初始化状态机
111 always @ (posedge clk or negedge rst_n) begin
112 if(!rst_n)
113 init_state <= `I_NOP;
114 else
115 case (init_state)
116 //上电复位后200us结束则进入下一状态
117 `I_NOP: init_state <= done_200us ? `I_PRE : `I_NOP;
118 //预充电状态
119 `I_PRE: init_state <= `I_TRP;
120 //预充电等待, TRP_CLK个时钟周期
121 `I_TRP: init_state <= (`end_trp) ? `I_AR : `I_TRP;
122 //自动刷新
123 `I_AR : init_state <= `I_TRF;
124 //等待自动刷新结束,TRC_CLK个时钟周期
125 `I_TRF: init_state <= (`end_trfc) ?
126 //连续8次自动刷新操作
127 ((init_ar_cnt == 4'd8) ? `I_MRS : `I_AR) : `I_TRF;
128 //模式寄存器设置
129 `I_MRS: init_state <= `I_TRSC;
130 //等待模式寄存器设置完成, TRSC_CLK个时钟周期
131 `I_TRSC: init_state <= (`end_trsc) ? `I_DONE : `I_TRSC;
132 //SDRAM的初始化设置完成标志
133 `I_DONE: init_state <= `I_DONE;
134 default: init_state <= `I_NOP;
135 endcase
136 end
137
138 //SDRAM的工作状态机,工作包括读、 写以及自动刷新操作
139 always @ (posedge clk or negedge rst_n) begin
140 if(!rst_n)
141 work_state <= `W_IDLE; //空闲状态
142 else
143 case(work_state)
144 //定时自动刷新请求, 跳转到自动刷新状态
145 `W_IDLE: if(sdram_ref_req & sdram_init_done) begin
146 work_state <= `W_AR;
147 sdram_rd_wr <= 1'b1;
148 end
149 //写SDRAM请求, 跳转到行有效状态
150 else if(sdram_wr_req & sdram_init_done) begin
151 work_state <= `W_ACTIVE;
152 sdram_rd_wr <= 1'b0;
153 end
154 //读SDRAM请求, 跳转到行有效状态
155 else if(sdram_rd_req && sdram_init_done) begin
156 work_state <= `W_ACTIVE;
157 sdram_rd_wr <= 1'b1;
158 end
159 //无操作请求, 保持空闲状态
160 else begin
161 work_state <= `W_IDLE;
162 sdram_rd_wr <= 1'b1;
163 end
164
165 `W_ACTIVE: //行有效, 跳转到行有效等待状态
166 work_state <= `W_TRCD;
167 `W_TRCD: if(`end_trcd) //行有效等待结束, 判断当前是读还是写
168 if(sdram_rd_wr)//读: 进入读操作状态
169 work_state <= `W_READ;
170 else //写: 进入写操作状态
171 work_state <= `W_WRITE;
172 else
173 work_state <= `W_TRCD;
174
175 `W_READ: //读操作, 跳转到潜伏期
176 work_state <= `W_CL;
177 `W_CL: //潜伏期: 等待潜伏期结束, 跳转到读数据状态
178 work_state <= (`end_tcl) ? `W_RD:`W_CL;
179 `W_RD: //读数据: 等待读数据结束, 跳转到预充电状态
180 work_state <= (`end_tread) ? `W_PRE:`W_RD;
181
182 `W_WRITE: //写操作: 跳转到写数据状态
183 work_state <= `W_WD;
184 `W_WD: //写数据: 等待写数据结束, 跳转到写回周期状态
185 work_state <= (`end_twrite) ? `W_TWR:`W_WD;
186 `W_TWR: //写回周期: 写回周期结束, 跳转到预充电状态
187 work_state <= (`end_twr) ? `W_PRE:`W_TWR;
188
189 `W_PRE: //预充电: 跳转到预充电等待状态
190 work_state <= `W_TRP;
191 `W_TRP: //预充电等待: 预充电等待结束, 进入空闲状态
192 work_state <= (`end_trp) ? `W_IDLE:`W_TRP;
193
194 `W_AR: //自动刷新操作, 跳转到自动刷新等待
195 work_state <= `W_TRFC;
196 `W_TRFC: //自动刷新等待: 自动刷新等待结束, 进入空闲状态
197 work_state <= (`end_trfc) ? `W_IDLE:`W_TRFC;
198 default: work_state <= `W_IDLE;
199 endcase
200 end
201
202 //延时计数器控制逻辑
203 always @ (*) begin
204 case (init_state)
205 `I_NOP: cnt_rst_n <= 1'b0; //延时计数器清零(cnt_rst_n低电平复位)
206
207 `I_PRE: cnt_rst_n <= 1'b1; //预充电: 延时计数器启动(cnt_rst_n高电平启动)
208 //等待预充电延时计数结束后, 清零计数器
209 `I_TRP: cnt_rst_n <= (`end_trp) ? 1'b0 : 1'b1;
210 //自动刷新: 延时计数器启动
211 `I_AR:
212 cnt_rst_n <= 1'b1;
213 //等待自动刷新延时计数结束后, 清零计数器
214 `I_TRF:
215 cnt_rst_n <= (`end_trfc) ? 1'b0 : 1'b1;
216
217 `I_MRS: cnt_rst_n <= 1'b1; //模式寄存器设置: 延时计数器启动
218 //等待模式寄存器设置延时计数结束后, 清零计数器
219 `I_TRSC: cnt_rst_n <= (`end_trsc) ? 1'b0:1'b1;
220
221 `I_DONE: begin //初始化完成后,判断工作状态
222 case (work_state)
223 `W_IDLE: cnt_rst_n <= 1'b0;
224 //行有效: 延时计数器启动
225 `W_ACTIVE: cnt_rst_n <= 1'b1;
226 //行有效延时计数结束后, 清零计数器
227 `W_TRCD: cnt_rst_n <= (`end_trcd) ? 1'b0 : 1'b1;
228 //潜伏期延时计数结束后, 清零计数器
229 `W_CL: cnt_rst_n <= (`end_tcl) ? 1'b0 : 1'b1;
230 //读数据延时计数结束后, 清零计数器
231 `W_RD: cnt_rst_n <= (`end_tread) ? 1'b0 : 1'b1;
232 //写数据延时计数结束后, 清零计数器
233 `W_WD: cnt_rst_n <= (`end_twrite) ? 1'b0 : 1'b1;
234 //写回周期延时计数结束后, 清零计数器
235 `W_TWR: cnt_rst_n <= (`end_twr) ? 1'b0 : 1'b1;
236 //预充电等待延时计数结束后, 清零计数器
237 `W_TRP: cnt_rst_n <= (`end_trp) ? 1'b0 : 1'b1;
238 //自动刷新等待延时计数结束后, 清零计数器
239 `W_TRFC: cnt_rst_n <= (`end_trfc) ? 1'b0 : 1'b1;
240 default: cnt_rst_n <= 1'b0;
241 endcase
242 end
243 default: cnt_rst_n <= 1'b0;
244 endcase
245 end
246
247 endmodule
由于SDRAM控制器参数较多,我们将常用的参数放在了一个单独的文件(sdram_para.v) ,并在相应的模块中引用该文件,如代码中第19行所示。SDRAM状态控制模块的任务可以划分为三部分: SDRAM的初始化、 SDRAM的自动刷新、 以及SDRAM的读写。 在本模块中我们使用两个状态机来完成上述任务, 其中“初始化状态机” 负责SDRAM的初始化过程; 而“工作状态机” 则用于处理自动刷新以及外部的读写请求。本章的简介部分对SDRAM的初始化流程(图 33.4.4)作了简单介绍,由此我们可以画出初始化状态机的状态转换图如下所示:


图 33.4.4 初始化状态机——状态转换图

如上图所示, SDRAM在上电后要有200us的输入稳定期。 200us结束后对所有L-Bank预充电,然后等待预充电有效周期(tRP) 结束后连续进行8次自动刷新操作, 每次刷新操作都要等待自动刷新周期(tRC) 。 最后对SDRAM的模式寄存器进行设置, 并等待模式寄存器设置周期(tRSC)
结束。 到这里SDRAM的初始化也就完成了, 接下来SDRAM进入正常的工作状态。由于SDRAM需要定时进行刷新操作以保存存储体中的数据, 所以工作状态机不仅要根据外部的读写请求来进行读写操作, 还要处理模块内部产生的刷新请求。 那么当多个请求信号同时到达时, 工作状态机该如何进行仲裁呢?首先, 为了保存SDRAM中的数据, 刷新请求的优先级最高; 写请求次之, 这是为了避免准备写入SDRAM中的数据丢失; 而读请求的优先级最低。 因此, 当刷新请求与读写请求同时产生时, 优先执行刷新操作; 而读请求与写请求同时产生时, 优先执行写操作。
另外, 由于刷新操作需要等待刷新周期(tRC) 结束, 而读写操作同样需要一定的时间(特别是突发模式下需要等待所有数据突发传输结束) 。 因此在上一个请求操作执行的过程中接收到新的请求信号是很有可能的, 这种情况下, 新的请求信号必须等待当前执行过程结束才能得
到工作状态机的响应。工作状态机的状态转换图如下所示:


图 33.4.5 工作状态机——状态转换图

工作状态机在空闲状态时接收自动刷新请求和读写请求, 并根据相应的操作时序在各个状态之间跳转。 例如, 在接收到自动刷新请求后, 跳转到自动刷新状态(此时SDRAM命令控制模块sdram_cmd会向SDRAM芯片发送自动刷新命令) , 随即进入等待过程, 等自动刷新周期(tRC)
结束后刷新操作完成, 工作状态机回到空闲状态。由本章简介部分可知, 无论读操作还是写操作首先都要进行“行激活” , 因此工作状态机在空闲状态时接收到读请求或写请求都会跳转到行激活状态, 然后等待行选通周期(tRCD) 结束。 接下来判断当前执行的是读操作还是写操作, 如果是读操作, 需要在等待读潜伏期结束后连续读取数据线上的数据, 数据量由读突发长度指定; 如果是写操作, 则不存在潜伏期, 直接将要写入SDRAM中的数据放到数据线上, 但是在最后一个数据放到数据线上之后, 需要等待写
入周期(tWR) 结束。需要注意的是, 由于W9825G6DH-6在页突发模式下不支持自动预充电, 上述读写操作过程中都选择了禁止自动预充电(地址线A10为低电平) 。 因此在读写操作结束后, 都要对SDRAM进行预充电操作, 并等待预充电周期结束才回到空闲状态。
由于SDRAM的操作时序涉及到大量的延时、 等待周期, 程序中设置了延时计数器对时钟进行计数, 如程序第90至97行所示。 而初始化状态机和工作状态机不同状态下延时或等待时间不同, 程序中第202至245行利用延时计数器复位信号cnt_rst_n来实现对延时计数器的控制。
为了使程序的简洁易懂, SDRAM状态控制模块中状态机的跳转及延时参数的控制条件使用了变量声明的方式。 为了方便大家对程序的理解, 我们将sdram_para.v中的内容也列在这里,大家可以对照其中的“延时参数” 重新回顾SDRAM状态控制模块相应部分的代码:
1 // SDRAM 初始化过程各个状态
2 `define I_NOP 5'd0 //等待上电200us稳定期结束
3 `define I_PRE 5'd1 //预充电状态
4 `define I_TRP 5'd2 //等待预充电完成
5 `define I_AR 5'd3 //自动刷新
6 `define I_TRF 5'd4 //等待自动刷新结束
7 `define I_MRS 5'd5 //模式寄存器设置
8 `define I_TRSC 5'd6 //等待模式寄存器设置完成
9 `define I_DONE 5'd7 //初始化完成
10
11 // SDRAM 工作过程各个状态
12 `define W_IDLE 4'd0 //空闲
13 `define W_ACTIVE 4'd1 //行有效
14 `define W_TRCD 4'd2 //行有效等待
15 `define W_READ 4'd3 //读操作
16 `define W_CL 4'd4 //潜伏期
17 `define W_RD 4'd5 //读数据
18 `define W_WRITE 4'd6 //写操作
19 `define W_WD 4'd7 //写数据
20 `define W_TWR 4'd8 //写回
21 `define W_PRE 4'd9 //预充电
22 `define W_TRP 4'd10 //预充电等待
23 `define W_AR 4'd11 //自动刷新
24 `define W_TRFC 4'd12 //自动刷新等待
25
26 //延时参数
27 `define end_trp cnt_clk == TRP_CLK //预充电有效周期结束
28 `define end_trfc cnt_clk == TRC_CLK //自动刷新周期结束
29 `define end_trsc cnt_clk == TRSC_CLK //模式寄存器设置时钟周期结束
30 `define end_trcd cnt_clk == TRCD_CLK-1 //行选通周期结束
31 `define end_tcl cnt_clk == TCL_CLK-1 //潜伏期结束
32 `define end_rdburst cnt_clk == sdram_rd_burst-4 //读突发终止
33 `define end_tread cnt_clk == sdram_rd_burst+2 //突发读结束
34 `define end_wrburst cnt_clk == sdram_wr_burst-1 //写突发终止
35 `define end_twrite cnt_clk == sdram_wr_burst-1 //突发写结束
36 `define end_twr cnt_clk == TWR_CLK //写回周期结束
37
38 //SDRAM控制信号命令
39 `define CMD_INIT 5'b01111 // INITIATE
40 `define CMD_NOP 5'b10111 // NOP COMMAND
41 `define CMD_ACTIVE 5'b10011 // ACTIVE COMMAND
42 `define CMD_READ 5'b10101 // READ COMMADN
43 `define CMD_WRITE 5'b10100 // WRITE COMMAND
44 `define CMD_B_STOP 5'b10110 // BURST STOP
45 `define CMD_PRGE 5'b10010 // PRECHARGE
46 `define CMD_A_REF 5'b10001 // AOTO REFRESH
47 `define CMD_LMR 5'b10000 // LODE MODE REGISTER
SDRAM命令控制模块的代码如下所示:
1 module sdram_cmd(
2 input clk, //系统时钟
3 input rst_n, //低电平复位信号
4 5
input sys_wraddr, //写SDRAM时地址
6 input sys_rdaddr, //读SDRAM时地址
7 input [ 9:0] sdram_wr_burst, //突发写SDRAM字节数
8 input [ 9:0] sdram_rd_burst, //突发读SDRAM字节数
9 1
0 input [ 4:0] init_state, //SDRAM初始化状态
11 input [ 3:0] work_state, //SDRAM工作状态
12 input [ 9:0] cnt_clk, //延时计数器
13 input sdram_rd_wr, //SDRAM读/写控制信号, 低电平为写
14
15 output sdram_cke, //SDRAM时钟有效信号
16 output sdram_cs_n, //SDRAM片选信号
17 output sdram_ras_n, //SDRAM行地址选通脉冲
18 output sdram_cas_n, //SDRAM列地址选通脉冲
19 output sdram_we_n, //SDRAM写允许位
20 output reg [ 1:0] sdram_ba, //SDRAM的L-Bank地址线
21 output reg sdram_addr //SDRAM地址总线
22 );
23
24 `include "sdram_para.v" //包含SDRAM参数定义模块
25
26 //reg define
27 reg [ 4:0] sdram_cmd_r; //SDRAM操作指令
28
29 //wire define
30 wire sys_addr; //SDRAM读写地址
31
32 //*****************************************************
33 //** main code
34 //*****************************************************
35
36 //SDRAM 控制信号线赋值
37 assign {sdram_cke,sdram_cs_n,sdram_ras_n,sdram_cas_n,sdram_we_n} = sdram_cmd_r;
38
39 //SDRAM 读/写地址总线控制
40 assign sys_addr = sdram_rd_wr ? sys_rdaddr : sys_wraddr;
41
42 //SDRAM 操作指令控制
43 always @ (posedge clk or negedge rst_n) begin
44 if(!rst_n) begin
45 sdram_cmd_r <= `CMD_INIT;
46 sdram_ba <= 2'b11;
47 sdram_addr <= 13'h1fff;
48 end
49 else
50 case(init_state)
51 //初始化过程中,以下状态不执行任何指令
52 `I_NOP,`I_TRP,`I_TRF,`I_TRSC: begin
53 sdram_cmd_r <= `CMD_NOP;
54 sdram_ba <= 2'b11;
55 sdram_addr <= 13'h1fff;
56 end
57 `I_PRE: begin //预充电指令
58 sdram_cmd_r <= `CMD_PRGE;
59 sdram_ba <= 2'b11;
60 sdram_addr <= 13'h1fff;
61 end
62 `I_AR: begin
63 //自动刷新指令
64 sdram_cmd_r <= `CMD_A_REF;
65 sdram_ba <= 2'b11;
66 sdram_addr <= 13'h1fff;
67 end
68 `I_MRS: begin //模式寄存器设置指令
69 sdram_cmd_r <= `CMD_LMR;
70 sdram_ba <= 2'b00;
71 sdram_addr <= { //利用地址线设置模式寄存器,可根据实际需要进行修改
72 3'b000, //预留
73 1'b0, //读写方式 A9=0, 突发读&突发写
74 2'b00, //默认, {A8,A7}=00
75 3'b011, //CAS潜伏期设置, 这里设置为3, {A6,A5,A4}=011
76 1'b0, //突发传输方式, 这里设置为顺序, A3=0
77 3'b111 //突发长度, 这里设置为页突发, {A2,A1,A0}=011
78 };
79 end
80 `I_DONE: //SDRAM初始化完成
81 case(work_state) //以下工作状态不执行任何指令
82 `W_IDLE,`W_TRCD,`W_CL,`W_TWR,`W_TRP,`W_TRFC: begin
83 sdram_cmd_r <= `CMD_NOP;
84 sdram_ba <= 2'b11;
85 sdram_addr <= 13'h1fff;
86 end
87 `W_ACTIVE: begin//行有效指令
88 sdram_cmd_r <= `CMD_ACTIVE;
89 sdram_ba <= sys_addr;
90 sdram_addr <= sys_addr;
91 end
92 `W_READ: begin //读操作指令
93 sdram_cmd_r <= `CMD_READ;
94 sdram_ba <= sys_addr;
95 sdram_addr <= {4'b0000,sys_addr};
96 end
97 `W_RD: begin //突发传输终止指令
98 if(`end_rdburst)
99 sdram_cmd_r <= `CMD_B_STOP;
100 else begin
101 sdram_cmd_r <= `CMD_NOP;
102 sdram_ba <= 2'b11;
103 sdram_addr <= 13'h1fff;
104 end
105 end
106 `W_WRITE: begin //写操作指令
107 sdram_cmd_r <= `CMD_WRITE;
108 sdram_ba <= sys_addr;
109 sdram_addr <= {4'b0000,sys_addr};
110 end
111 `W_WD: begin //突发传输终止指令
112 if(`end_wrburst)
113 sdram_cmd_r <= `CMD_B_STOP;
114 else begin
115 sdram_cmd_r <= `CMD_NOP;
116 sdram_ba <= 2'b11;
117 sdram_addr <= 13'h1fff;
118 end
119 end
120 `W_PRE:begin //预充电指令
121 sdram_cmd_r <= `CMD_PRGE;
122 sdram_ba <= sys_addr;
123 sdram_addr <= 13'h0000;
124 end
125 `W_AR: begin //自动刷新指令
126 sdram_cmd_r <= `CMD_A_REF;
127 sdram_ba <= 2'b11;
128 sdram_addr <= 13'h1fff;
129 end
130 default: begin
131 sdram_cmd_r <= `CMD_NOP;
132 sdram_ba <= 2'b11;
133 sdram_addr <= 13'h1fff;
134 end
135 endcase
136 default: begin
137 sdram_cmd_r <= `CMD_NOP;
138 sdram_ba <= 2'b11;
139 sdram_addr <= 13'h1fff;
140 end
141 endcase
142 end
143
144 endmodule
SDRAM命令控制模块根据状态控制模块里初始化状态机和工作状态机的状态对SDRAM的控制信号线及地址线进行赋值,发送相应的操作命令。SDRAM的操作命令是sdram_cke、sdram_cs_n、sdram_ras_n、 sdram_cas_n、 sdram_we_n等控制信号的组合, 不同的数值代表不同的指令。
W9825G6DH-6不同的操作命令与其对应的各信号的数值如下图所示(其中字母H代表高电平, L代表低电平, v代表有效, x代表不关心) :


图 33.4.6 SDRAM操作指令

SDRAM数据读写模块代码如下:
1 module sdram_data(
2 input clk, //系统时钟
3 input rst_n, //低电平复位信号
4 5
input sdram_data_in, //写入SDRAM中的数据
6 output sdram_data_out, //从SDRAM中读取的数据
7 input [ 3:0] work_state, //SDRAM工作状态寄存器
8 input [ 9:0] cnt_clk, //时钟计数
9 1
0 inout sdram_data //SDRAM数据总线
11 );
12
13 `include "sdram_para.v" //包含SDRAM参数定义模块
14
15 //reg define
16 reg sdram_out_en; //SDRAM数据总线输出使能
17 reg sdram_din_r; //寄存写入SDRAM中的数据
18 reg sdram_dout_r; //寄存从SDRAM中读取的数据
19
20 //*****************************************************
21 //** main code
22 //*****************************************************
23
24 //SDRAM 双向数据线作为输入时保持高阻态
25 assign sdram_data = sdram_out_en ? sdram_din_r : 16'hzzzz;
26
27 //输出SDRAM中读取的数据
28 assign sdram_data_out = sdram_dout_r;
29
30 //SDRAM 数据总线输出使能
31 always @ (posedge clk or negedge rst_n) begin
32 if(!rst_n)
33 sdram_out_en <= 1'b0;
34 else if((work_state == `W_WRITE) | (work_state == `W_WD))
35 sdram_out_en <= 1'b1; //向SDRAM中写数据时,输出使能拉高
36 else
37 sdram_out_en <= 1'b0;
38 end
39
40 //将待写入数据送到SDRAM数据总线上
41 always @ (posedge clk or negedge rst_n) begin
42 if(!rst_n)
43 sdram_din_r <= 16'd0;
44 else if((work_state == `W_WRITE) | (work_state == `W_WD))
45 sdram_din_r <= sdram_data_in; //寄存写入SDRAM中的数据
46 end
47
48 //读数据时,寄存SDRAM数据线上的数据
49 always @ (posedge clk or negedge rst_n) begin
50 if(!rst_n)
51 sdram_dout_r <= 16'd0;
52 else if(work_state == `W_RD)
53 sdram_dout_r <= sdram_data; //寄存从SDRAM中读取的数据
54 end
55
56 endmodule
SDRAM数据读写模块通过数据总线输出使能信号sdram_out_en控制SDRAM双向数据总线的输入输出, 如程序第25行所示。 同时根据工作状态机的状态, 在写数据时将写入SDRAM中的数据送到SDRAM数据总线上, 在读数据时寄存SDRAM数据总线上的数据。
图 33.4.7为SDRAM读写测试程序运行时SignalTap抓取的波形图, 图中包含了一个完整的读周期, 其中rd_valid为低时读数据无效, rd_valid为高时error_flag一直保持低电平, 说明数据读写测试正确。


图 33.4.7 SignalTap波形图

完成SDRAM初始化后可对其进行仿真验证, 利用SDRAM仿真模型和设计testbench文件可对设计 的SDRAM 初始 化模 块进 行验 证正 确性 。 仿 真需 要用 到是sim 文件 夹中 的sdr.v 和sdr_parameters.h两个文件, sdr_parameters.h文件主要是包含SDRAM模型的一些全局化参数
和宏定义。testbench仿真文件代码如下:
1 `timescale 1ns/1ns
2 3
module sdram_tb;
4 5
//reg define
6 reg clock_50m;
7 reg rst_n;
8 9
//wire define
10 wire sdram_clk;
11 wire sdram_cke;
12 wire sdram_cs_n;
13 wire sdram_ras_n;
14 wire sdram_cas_n;
15 wire sdram_we_n;
16 wire [ 1:0] sdram_ba;
17 wire sdram_addr;
18 wire sdram_data;
19 wire [ 1:0] sdram_dqm;
20
21 wire led;
22
23 //*****************************************************
24 //** main code
25 //*****************************************************
26
27 //初始化
28 initial begin
29 clock_50m = 0;
30 rst_n = 0;
31 #100
32 rst_n = 1;
33 end
34
35 //产生50Mhz时钟
36 always #10 clock_50m = ~clock_50m;
37
38 //例化SDRAM读写测试模块
39 sdram_rw_test u_sdram_rw_test(
40 .clk (clock_50m),
41 .rst_n (rst_n),
42
43 .sdram_clk (sdram_clk),
44 .sdram_cke (sdram_cke),
45 .sdram_cs_n (sdram_cs_n),
46 .sdram_ras_n (sdram_ras_n),
47 .sdram_cas_n (sdram_cas_n),
48 .sdram_we_n (sdram_we_n),
49 .sdram_ba (sdram_ba),
50 .sdram_addr (sdram_addr),
51 .sdram_data (sdram_data),
52 .sdram_dqm (sdram_dqm),
53
54 .led (led)
55 );
56
57 //例化SDRAM仿真模型
58 sdr u_sdram(
59 .Clk (sdram_clk),
60 .Cke (sdram_cke),
61 .Cs_n (sdram_cs_n),
62 .Ras_n (sdram_ras_n),
63 .Cas_n (sdram_cas_n),
64 .We_n (sdram_we_n),
65 .Ba (sdram_ba),
66 .Addr (sdram_addr),
67 .Dq (sdram_data),
68 .Dqm (sdram_dqm)
69 );
70
71 endmodule
33.5 下载验证
首先我们打开SDRAM读写测试工程, 在工程所在的路径下打开sdram_rw_test/par文件夹,在里面找到“sdram_rw_test.qpf” 并双击打开。 注意工程所在的路径名只能由字母、 数字以及下划线组成, 不能出现中文、 空格以及特殊字符等。 工程打开后如图 33.5.1所示。


图 33.5.1 SDRAM读写测试工程

将下载器一端连电脑,另一端与开发板上对应端口连接,最后连接电源线并打开电源开关。
开拓者开发板实物图如下所示:


图 33.5.2 开拓者开发板图

接下来我们下载程序, 验证SDRAM读写测试。工程打开后通过点击工具栏中的“Programmer” 图标打开下载界面, 通过“Add File” 按钮选择sdram_rw_test/par/output_files目录下的“sdram_rw_test.sof” 文件。 开发板电源打开后, 在程序下载界面点击“Hardware Setup” , 在弹出的对话框中选择当前的硬件连接为“USB-Blaster” 。 然后点击“Start” 将工程编译完成后得到的sof文件下载到开发板中, 如图 33.5.3所示


图 33.5.3 程序下载界面

下载完成后开发板上最右侧的LED灯常亮, 说明从SDRAM读出的1024个数据与写入的数据相同, SDRAM读写测试程序下载验证成功。





页: [1]
查看完整版本: 【正点原子FPGA连载】第三十三章 SDRAM读写测试实验--摘自【正点原子】开拓者 FPGA 开发指南