【正点原子FPGA连载】第三十八章SDRAM读写测试实验 -摘自【正点原子】新起点之FPGA开发指南_V2.1

1)实验平台:正点原子新起点V2开发板
2)平台购买地址:https://detail.tmall.com/item.htm?id=609758951113
2)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-300792-1-1.html
3)对正点原子FPGA感兴趣的同学可以加群讨论:994244016
4)关注正点原子公众号,获取最新资料更新
在这里插入图片描述

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

SDRAM是一种可以指定任意地址进行读写的存储器,它具有存储容量大,读写速度快的特点,同时价格也相对低廉。因此,SDRAM常作为缓存,应用于数据存储量大,同时速度要求较高的场合,如复杂嵌入式设备的存储器等。本章我们将利用FPGA实现SDRAM控制器,并完成开发板上SDRAM芯片的读写测试。
本章包括以下几个部分:
3737.1简介
37.2实验任务
37.3硬件设计
37.4软件设计
37.5下载验证

38.1简介
SDRAM(Synchronous Dynamic Random Access Memory),同步动态随机存储器。同步是指内存工作需要同步时钟,内部的命令的发送与数据的传输都以它为基准;动态是指存储阵列需要不断的刷新来保证数据不丢失;随机是指数据不是线性依次存储,而是自由指定地址进行数据读写。
SDRAM具有空间存储量大、读写速度快、价格相对便宜等优点。然而由于SDRAM内部利用电容来存储数据,为保证数据不丢失,需要持续对各存储电容进行刷新操作;同时在读写过程中需要考虑行列管理、各种操作延时等,由此导致了其控制逻辑复杂的特点。
SDRAM的内部是一个存储阵列,你可以把它想象成一张表格。我们在向这个表格中写入数据的时候,需要先指定一个行(Row),再指定一个列(Column),就可以准确地找到所需要的“单元格”,这就是SDRAM寻址的基本原理。如图 38.1.1所示:
在这里插入图片描述

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

图 38.1.2 SDRAM存储单元结构示意图
图 38.1.3是SDRAM的结构框图,从框图中我们可以看到SDRAM主要是由时钟缓冲器(CLK BUFFER)、指令解码器(COMMAND DECODER)、地址缓冲器(ADDRESS BUFFER)、模式寄存器(MODE REGISTER)、控制信号生成器(CONTROL SIGNAL GENERATOR)、四个存储BANK(BANK#0~ BANK#3)和数据控制电路(DATA CONTROL CIRCUIT)构成。SDRAM接收外部输入的控制命令,并在指令解码器的控制下进行寻址、读写、刷新、预充电等操作。
在这里插入图片描述

图 38.1.3 SDRAM功能框图
下面我们就来详细的分析一下SDRAM的结构框图,首先来看时钟缓冲器和指令解码器,它俩构成了整个SDRAM的核心控制器,我们可以通过这个核心控制器给SDRAM发送指令,让SDRAM执行对应的功能。从上图中我们可以看到时钟缓冲器和指令解码器共有六根线,一根时钟线(CLK),主要是给整个SDRAM提供工作时钟;一根时钟使能线(CKE),当CKE为低电平时整个SDRAM进入休眠模式,所有指令无效,当CKE为高电平时SDRAM才能进入正常工作模式;四根指令线(CS、WE、CAS、RAS),其中CS是SDRAM芯片的片选信号,低电平有效。WE是读写指令切换信号,低电平表示写指令,高电平表示读指令。RAS和CAS分别是行地址选通信号和列地址选通信号,都是低电平有效(SDRAM的行列地址线是复用的所以需要使用RAS和CAS来区分地址是行地址还是列地址)。我们驱动SDRAM的时候就用这四根指令线组合成不同的指令发送给SDRAM,SDRAM正确接收到指令后就会执行对应的功能。具体的指令组合如下图所示:
在这里插入图片描述

图 38.1.4 SDRAM指令一览表
从上图中可以看到指令组合还是非常多的,但是并不是每一个指令我们都会用到,具体使用了哪些指令我们在下文程序设计部分会给大家详细的讲解。
讲完了功能框图中的时钟缓冲器和指令解码器后我们接着来看ADRAM的地址缓冲器,SDRAM共有13根行列地址线(A0A12)和两根BANK地址线(BS0BS1),其中BANK地址线主要用来定位BANK 地址,整片SDRAM共有四个BANK,例如我现在想要对BANK1进行读写操作那么就可以将BANK地址线设置为“2’b01”;13根行列地址线是用来定位行地址和列地址的,这里尤其需要注意的是地址线10,它还参与了自动预充电的指令,当我们在进行读写指令操作的时候拉高A10可以让SDRAM进入自动预充电模式,具体的操作在下文预充电操作内容部分会有详细的讲解。
看完地址缓冲器后我们再来看看模式寄存器,模式寄存器是SDRAM非常重要的控制器,在对SDRAM上电初始化的时候就要配置模式寄存器,主要是配置突发长度、突发传输方式、CAS潜伏期和操作模式。一旦配置完成,之后SDRAM就会以配置好的模式去运行,具体如何配置,下文会有详细讲解。
最后我们再来看看控制信号生成器(CONTROL SIGNAL GENERATOR)、四个存储BANK(BANK#0~ BANK#3)和数据控制电路(DATA CONTROL CIRCUIT),这几个部分功能就比较简单了,控制信号生成器主要是把指令和模式寄存器的相关配置生成对应的控制信号,用来控制SDRAM的存储BANK,而存储BANK就更不用说了,就是用来存储数据的存储容器;数据控制电路主要就是用来接收和发送数据的,需要注意的是它可以配合数据掩码器(DQM)来使用,在吞吐数据的过程中可以把一些我们不想要的数据给屏蔽掉。
对SDRAM的整体结构就给大家简单的讲解到这了,如果大家想了解的更细致也可以去阅读SDRAM的数据手册,下面我们来讲解SDRAM的几个重要操作。
1、芯片初始化
SDRAM芯片上电之后需要一个初始化的过程,以保证芯片能够按照预期方式正常工作,初始化流程如图 38.1.5所示:
在这里插入图片描述

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

图 38.1.6 模式寄存器
如上图所示,用于配置模式寄存器的参数由地址线提供,地址线不同的位分别用于表示不同的参数。SDRAM通过配置模式寄存器来确定芯片的工作方式,包括突发长度(Burst Length)、潜伏期(CAS Latency)以及操作模式等。其时序图如下所示:
在这里插入图片描述

图 38.1.7 配置模式寄存器
从上图中可以看到配置模式寄存器还是比较简单的,只需要将四根指令线全部拉低即发送配置模式寄存器指令(4’b0000),同时在地址线上给出对应的配置数值就可以了。然后需要注意的是,在模式寄存器设置指令发出之后,需要等待一段时间才能够向SDRAM发送新的指令,这个时间我们称之为模式寄存器设置周期tRSC(Register Set Cycle),查看数据手册我们可以知道tRSC至少要2个时钟周期的时间。
2、行激活
初始化完成后,无论是读操作还是写操作,都要先激活(Active)SDRAM中的一行,使之处于活动状态(又称行有效)。在此之前还要进行SDRAM芯片的片选和Bank的定址,不过它们与行激活可以同时进行。
在这里插入图片描述

图 38.1.8 行激活时序图
从上图可以看出,在片选CS#(#表示低电平有效)、Bank定址的同时,RAS(Row Address Strobe,行地址选通脉冲)也处于有效状态。此时An地址线则发送具体的行地址。我们板载的SDRAM共有共有13个地址线,由于是二进制表示法,所以共有8192个行(2^13=4096),A0-A12的不同数值就确定了具体的行地址。由于行激活的同时也会激活相应的Bank,所以行激活也可称为Bank有效。
3、列读写
行地址激活之后,就要对列地址进行寻址了。由于在SDRAM中,地址线是行列共用的,因此列寻址时地址线仍然是A0-A12。在寻址时,利用RAS(Row Address Strobe,行地址选通脉冲)与CAS(Column Address Strobe,列地址选通脉冲)来区分行寻址与列寻址,简单的来讲就是RAS拉低CAS拉高代表行地址,反过来就代表列地址,如图 38.1.9所示。还有一点很重要就是在列寻址的时候地址线A10可以用来控制是否进行预充电(列地址最大只能用到地址线第9位,所以不用担心A10会不会和列地址冲突),因为已经都进入列寻址了那么此时就可以发送读写指令进行读写操作了,但是读写操作结束后我想读写下一行的时候就必须先进行预充电(关于预充电下文会有详细描述),因此我们一般会在列寻址的时候直接拉高A10地址线,这样每完成一次读写操作就会自动进行一次预充电,这样读写下一行之前就不用再特地去进行预充电操作了。
另外,列寻址信号与读写命令是同时发出的,读/写命令是通过WE(Write Enable,写使能)信号来控制的,WE为低时是写命令,为高时是读命令。
在这里插入图片描述

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

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

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

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

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

图 38.1.14 突发连续读操作
至于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)。在读写过程中,工作行内的存储体由于“行激活”而使存储电容受到干扰,因此在关闭工作行前需要对本行所有存储体进行重写。预充电实际上就是对工作行中所有存储体进行数据重写,并对行地址进行复位,以准备新行工作的过程。
预充电可以通过命令控制,也可以通过辅助设定让芯片在每次读写操作之后自动进行预充电。现在我们再回过头看看读写操作时的命令时序图(图 38.1.9),从中可以发现地址线A10控制着是否进行在读写之后对当前L-Bank自动进行预充电,这就是上文所说的“辅助设定”。而在单独的预充电命令中,A10则控制着是对指定的L-Bank还是所有的L-Bank(当有多个L-Bank处于有效/活动状态时)进行预充电,前者需要提供L-Bank的地址,后者只需将A10信号置于高电平。
在发出预充电命令之后,要经过一段时间才能发送行激活命令打开新的工作行,这个间隔被称为tRP(Precharge command Period,预充电有效周期),如图 38.1.15所示。和tRCD、CL一样,tRP的单位也是时钟周期数,具体值视时钟频率而定。
在这里插入图片描述

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

图 38.1.16 写入时预充电时序图(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发出两个时钟周期后生效,如图 38.1.17所示。而在写入时,DQM与写入命令一样是立即成效,如图 38.1.18所示。
在这里插入图片描述

图 38.1.17 读取时DQM信号时序图
在这里插入图片描述

图 38.1.18 写入时DQM信号时序图
38.2实验任务
本节实验任务是向新起点开发板上的SDRAM中写入1024个数据,从SDRAM存储空间的起始地址写起,写完后再将数据读出,并验证读出数据是否正确。
38.3硬件设计
SDRAM的原理图如图 38.3.1所示。
在这里插入图片描述

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

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

图 38.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)。各模块端口及信号连接如图 38.4.2所示:
在这里插入图片描述

图 38.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 [12:0] sdram_addr,               //SDRAM 行/列地址
13      inout  [15:0] 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 [15:0] wr_data;                        //SDRAM 写端口:写入的数据
26  wire        rd_en;                          //SDRAM 读端口:读使能
27  wire [15:0] 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                                           //SDRAM初始化失败或者读写错误都认为是实验失败
71      .error_flag         (~sdram_init_done||error_flag), 
72      .led                (led)             
73      );
74  
75  //SDRAM 控制器顶层模块,封装成FIFO接口
76  //SDRAM 控制器地址组成: {bank_addr[1:0],row_addr[12:0],col_addr[8:0]}
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[1:0], row_addr[12:0], col_addr[8:0]}。
程序第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 [15:0] wr_data,          //SDRAM 写入的数据
7       output reg        rd_en,            //SDRAM 读使能
8       input      [15:0] rd_data,          //SDRAM 读出的数据
9       
10      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 [10:0] wr_cnt;                      //写操作计数器
18  reg [10:0] 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 [24:0] 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  [15:0] wr_data,                  //写端口FIFO: 写数据
10      input  [23:0] wr_min_addr,              //写SDRAM的起始地址
11      input  [23:0] 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 [15:0] rd_data,                  //读端口FIFO: 读数据
19      input  [23:0] rd_min_addr,              //读SDRAM的起始地址
20      input  [23:0] 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 [12:0] sdram_addr,               //SDRAM 行/列地址
37      inout  [15:0] 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 [23:0] sdram_wr_addr;                  //sdram 写地址
45  wire [15:0] sdram_din;                      //写入sdram中的数据
46  
47  wire        sdram_rd_req;                   //sdram 读请求
48  wire        sdram_rd_ack;                   //sdram 读响应
49  wire [23:0] sdram_rd_addr;                   //sdram 读地址
50  wire [15:0] 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      [15:0] wrf_din,           //写端口FIFO: 写数据 
9       input      [23:0] wr_min_addr,       //写SDRAM的起始地址
10      input      [23:0] 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     [15:0] rdf_dout,          //读端口FIFO: 读数据
18      input      [23:0] rd_min_addr,       //读SDRAM的起始地址
19      input      [23:0] 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 [23:0] sdram_wr_addr,     //sdram 写地址
31      output     [15:0] sdram_din,         //写入SDRAM中的数据 
32                                          
33      //SDRAM 控制器读端口                 
34      output reg        sdram_rd_req,      //sdram 读请求
35      input             sdram_rd_ack,      //sdram 读响应
36      output reg [23:0] sdram_rd_addr,     //sdram 读地址 
37      input      [15:0] 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 [9:0] wrf_use;                      //写端口FIFO中的数据量
58  wire [9:0] 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控制器。
代码的132~160行用来生成SDRAM的读写地址,要注意的是这里受到wr_load和rd_load信号控制,测到wr_load的上升沿
如程序中第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  [23:0] sdram_wr_addr,    //SDRAM写操作的地址
9       input  [ 9:0] sdram_wr_burst,   //写sdram时数据突发长度
10      input  [15:0] sdram_din,        //写入SDRAM的数据
11      
12      //SDRAM 控制器读端口  
13      input         sdram_rd_req,     //读SDRAM请求信号
14      output        sdram_rd_ack,     //读SDRAM响应信号
15      input  [23:0] sdram_rd_addr,    //SDRAM写操作的地址
16      input  [ 9:0] sdram_rd_burst,   //读sdram时数据突发长度
17      output [15:0] 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 [12:0] sdram_addr,       // SDRAM 地址总线
29      inout  [15:0] sdram_data        // SDRAM 数据总线
30      );
31  
32  //wire define
33  wire [4:0] init_state;              // SDRAM初始化状态
34  wire [3:0] work_state;              // SDRAM工作状态
35  wire [9:0] 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控制器的功能框图:

图 38.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 [9:0] sdram_wr_burst, //突发写SDRAM字节数(1-512个)
10 input [9:0] sdram_rd_burst, //突发读SDRAM字节数(1-256个)
11 output sdram_init_done, //SDRAM系统初始化完毕信号
12
13 output reg [4:0] init_state, //SDRAM初始化状态
14 output reg [3:0] work_state, //SDRAM工作状态
15 output reg [9:0] 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; //列潜伏期(cl) 27 parameter TWR_CLK = 10'd2; //写入校正 28 29 //reg define 30 reg [14:0] cnt_200us; //SDRAM 上电稳定期200us计数器 31 reg [10:0] 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 //预充电状态 119I_PRE: init_state <= I_TRP; 120 //预充电等待,TRP_CLK个时钟周期 121I_TRP: init_state <= (end_trp) ?I_AR : I_TRP; 122 //自动刷新 123I_AR : init_state <= I_TRF; 124 //等待自动刷新结束,TRC_CLK个时钟周期 125I_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 165W_ACTIVE: //行有效,跳转到行有效等待状态
166 work_state <= W_TRCD; 167W_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) 205I_NOP: cnt_rst_n <= 1’b0; //延时计数器清零(cnt_rst_n低电平复位)
206
207 I_PRE: cnt_rst_n <= 1'b1; //预充电:延时计数器启动(cnt_rst_n高电平启动) 208 //等待预充电延时计数结束后,清零计数器 209I_TRP: cnt_rst_n <= (end_trp) ? 1'b0 : 1'b1; 210 //自动刷新:延时计数器启动 211I_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 //等待模式寄存器设置延时计数结束后,清零计数器 219I_TRSC: cnt_rst_n <= (end_trsc) ? 1'b0:1'b1; 220 221I_DONE: begin //初始化完成后,判断工作状态
222 case (work_state)
223 W_IDLE: cnt_rst_n <= 1'b0; 224 //行有效:延时计数器启动 225W_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的初始化流程(图 38.4.4)作了简单介绍,由此我们可以画出初始化状态机的状态转换图如下所示:
在这里插入图片描述

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

图 38.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                           //等待预充电完成       tRP
5   `define     I_AR            5'd3                           //自动刷新            
6   `define     I_TRF           5'd4                           //等待自动刷新结束    tRC
7   `define     I_MRS           5'd5                           //模式寄存器设置
8   `define     I_TRSC          5'd6                           //等待模式寄存器设置完成 tRSC
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-3     //读突发终止
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      [23:0] sys_wraddr,       //写SDRAM时地址
6       input      [23:0] sys_rdaddr,       //读SDRAM时地址
7       input      [ 9:0] sdram_wr_burst,   //突发写SDRAM字节数
8       input      [ 9:0] sdram_rd_burst,   //突发读SDRAM字节数
9       
10      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 [12:0] 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 [23:0] 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[23:22];
90                                  sdram_addr  <= sys_addr[21:9];
91                              end
92                          `W_READ: begin  //读操作指令
93                                  sdram_cmd_r <= `CMD_READ;
94                                  sdram_ba    <= sys_addr[23:22];
95                                  sdram_addr  <= {4'b0000,sys_addr[8:0]};
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[23:22];
109                                 sdram_addr  <= {4'b0000,sys_addr[8:0]};
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[23:22];
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代表不关心):
在这里插入图片描述

图 38.4.6 SDRAM操作指令
SDRAM数据读写模块代码如下:

1   module sdram_data(
2       input             clk,              //系统时钟
3       input             rst_n,            //低电平复位信号
4   
5       input   [15:0]    sdram_data_in,    //写入SDRAM中的数据
6       output  [15:0]    sdram_data_out,   //从SDRAM中读取的数据
7       input   [ 3:0]    work_state,       //SDRAM工作状态寄存器
8       input   [ 9:0]    cnt_clk,          //时钟计数
9       
10      inout   [15:0]    sdram_data        //SDRAM数据总线
11      );
12  
13  `include "sdram_para.v"                 //包含SDRAM参数定义模块
14  
15  //reg define
16  reg        sdram_out_en;                //SDRAM数据总线输出使能
17  reg [15:0] sdram_din_r;                 //寄存写入SDRAM中的数据
18  reg [15:0] 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数据总线上的数据。
图 38.4.7为SDRAM读写测试程序运行时SignalTap抓取的波形图,图中包含了一个完整的读周期,其中rd_valid为低时读数据无效,rd_valid为高时error_flag一直保持低电平,说明数据读写测试正确。
在这里插入图片描述

图 38.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 [12:0] sdram_addr;    
18 wire [15:0] 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

38.5下载验证
首先我们将下载器与新起点开发板上的JTAG接口连接,下载器另外一端与电脑连接。然后连接开发板的电源,下载sof文件,现象如下图所示。
在这里插入图片描述

图 38.5.1 硬件连接
从上图中可以看到LED0常亮,说明SDRAM读写实验成功。

  • 5
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值