OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 7055|回复: 3

[ALTERA] 《新起点之FPGA开发指南 V2.1》第三十八章 SDRAM读写测试实验

[复制链接]

1118

主题

1129

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4672
金钱
4672
注册时间
2019-5-8
在线时间
1224 小时
发表于 2021-10-22 12:51:11 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 2021-10-30 10:18 编辑

1)实验平台:正点原子新起点V2FPGA开发板
2)  章节摘自【正点原子】《新起点之FPGA开发指南 V2.1》
3)购买链接:https://detail.tmall.com/item.htm?id=609758951113
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz_xinqidian(V2).html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子FPGA技术交流QQ群:712557122 QQ群.png

原子哥.jpg

微信公众号.png




第三十八章 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所示:
第三十八章 SDRAM读写测试实验630.png
图 38.1.1 SDRAM寻址原理
上图中的“单元格”就是SDRAM存储芯片中的存储单元,而这个“表格”(存储阵列)我们称之为L-Bank。通常SDRAM的存储空间被划分为4个L-Bank,在寻址时需要先指定其中一个L-Bank,然后在这个选定的L-Bank中选择相应的行与列进行寻址(寻址就是指定存储单元地址的过程)。
对SDRAM的读写是针对存储单元进行的,对SDRAM来说一个存储单元的容量等于数据总线的位宽,单位是bit。那么SDRAM芯片的总存储容量我们就可以通过下面的公式计算出来:
SDRAM总存储容量 = L-Bank的数量×行数×列数×存储单元的容量
SDRAM存储数据是利用了电容的充放电特性以及能够保持电荷的能力。一个大小为1bit的存储单元的结构如下图所示,它主要由行列选通三极管,存储电容,刷新放大器组成。行地址与列地址选通使得存储电容与数据线导通,从而可进行放电(读取)与充电(写入)操作。
第三十八章 SDRAM读写测试实验1087.png
图 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接收外部输入的控制命令,并在指令解码器的控制下进行寻址、读写、刷新、预充电等操作。
第三十八章 SDRAM读写测试实验1457.png
图 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正确接收到指令后就会执行对应的功能。具体的指令组合如下图所示:
第三十八章 SDRAM读写测试实验1969.png
图 38.1.4 SDRAM指令一览表
从上图中可以看到指令组合还是非常多的,但是并不是每一个指令我们都会用到,具体使用了哪些指令我们在下文程序设计部分会给大家详细的讲解。
讲完了功能框图中的时钟缓冲器和指令解码器后我们接着来看ADRAM的地址缓冲器,SDRAM共有13根行列地址线(A0~A12)和两根BANK地址线(BS0~BS1),其中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所示:
第三十八章 SDRAM读写测试实验2963.png
图 38.1.5 SDRAM初始化
SDRAM上电后要有200us的输入稳定期,在这个时间内不可以对SDRAM的接口做任何操作;200us结束以后给所有Bank预充电,然后是连续8次刷新操作,最后设置模式寄存器。初始化最关键的阶段就在于模式寄存器(MR,Mode Register)的设置,简称MRS(MR Set)。
第三十八章 SDRAM读写测试实验3168.png
图 38.1.6 模式寄存器
如上图所示,用于配置模式寄存器的参数由地址线提供,地址线不同的位分别用于表示不同的参数。SDRAM通过配置模式寄存器来确定芯片的工作方式,包括突发长度(Burst Length)、潜伏期(CAS Latency)以及操作模式等。其时序图如下所示:
第三十八章 SDRAM读写测试实验3352.png
图 38.1.7 配置模式寄存器
从上图中可以看到配置模式寄存器还是比较简单的,只需要将四根指令线全部拉低即发送配置模式寄存器指令(4’b0000),同时在地址线上给出对应的配置数值就可以了。然后需要注意的是,在模式寄存器设置指令发出之后,需要等待一段时间才能够向SDRAM发送新的指令,这个时间我们称之为模式寄存器设置周期tRSC(Register Set Cycle),查看数据手册我们可以知道tRSC至少要2个时钟周期的时间。
2、行激活
初始化完成后,无论是读操作还是写操作,都要先激活(Active)SDRAM中的一行,使之处于活动状态(又称行有效)。在此之前还要进行SDRAM芯片的片选和Bank的定址,不过它们与行激活可以同时进行。
第三十八章 SDRAM读写测试实验3721.png
图 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为低时是写命令,为高时是读命令。
第三十八章 SDRAM读写测试实验4517.png
图 38.1.9 列选通与读操作时序图
然而,在发送列读写命令时必须要与行激活命令有一个时间间隔(其实就是行激活命令和列激活命令之间要间隔一段时间),这个间隔被定义为tRCD,即RAS to CAS Delay(RAS至CAS延迟)。这是因为在行激活命令发出之后,芯片存储阵列电子元件响应需要一定的时间。tRCD是SDRAM的一个重要时序参数,广义的tRCD以时钟周期(tCK,Clock Time)数为单位,比如tRCD=3,就代表RAS至CAS延迟为三个时钟周期,如图 38.1.10所示。具体到确切的时间,则要根据时钟频率而定。
第三十八章 SDRAM读写测试实验4871.png
图 38.1.10 tRCD = 3时序图
4、 数据输出(读)
在选定列地址后,就已经确定了具体的存储单元,剩下的事情就是数据通过数据I/O通道(DQ)输出到内存总线上了。但是在CAS发出之后,仍要经过一定的时间才能有数据输出,从CAS与读取命令发出到第一笔数据输出的这段时间,被定义为CL(CAS Latency,CAS潜伏期)。CL时间越短,读数据时SDRAM响应就越快。由于CL只在读取时出现,所以CL又被称为读取潜伏期(RL,Read Latency)。CL的单位与tRCD一样,为时钟周期数,具体耗时由时钟频率决定。
第三十八章 SDRAM读写测试实验5181.png
图 38.1.11 CL = 2 时序图
5、数据输入(写)
数据写入的操作也是在tRCD之后进行,但此时没有了CL(记住,CL只出现在读取操作中),行寻址与列寻址的时序图和上文一样,只是在列寻址时,WE#为有效状态。
第三十八章 SDRAM读写测试实验5337.png
图 38.1.12 数据写入的时序图
从上图中可见,数据与写指令同时发送。不过,数据并不是即时地写入存储单元,数据的真正写入需要一定的周期。为了保证数据的可靠写入,都会留出足够的写入/校正时间(tWR,Write Recovery Time),这个操作也被称作写回(Write Back)。tWR至少占用一个时钟周期或再多一点(时钟频率越高,tWR占用周期越多)。
6、突发长度
突发(Burst)是指在同一行中相邻的存储单元连续进行数据传输的方式,连续传输所涉及到存储单元(列)的数量就是突发长度(Burst Lengths,简称BL)。
上文讲到的读/写操作,都是一次对一个存储单元进行寻址。然而在现实中很少只对SDRAM中的单个存储空间进行读写,一般都需要完成连续存储空间中的数据传输。在连续读/写操作时,为了对当前存储单元的下一个单元进行寻址,需要不断的发送列地址与读/写命令(行地址不变,所以不用再对行寻址),如图 38.1.13所示:
第三十八章 SDRAM读写测试实验5847.png
图 38.1.13 非突发连续读操作
由上图可知,虽然由于读延迟相同可以让数据的传输在I/O端是连续的,但它占用了大量的内存控制资源,在数据进行连续传输时无法输入新的命令,效率很低。为此,人们开发了突发传输技术,只要指定起始列地址与突发长度,内存就会依次地自动对后面相应数量的存储单元进行读/写操作而不再需要控制器连续地提供列地址。这样,除了第一笔数据的传输需要若干个周期(主要是之前的延迟,一般的是tRCD+CL)外,其后每个数据只需一个周期的延时即可获得。如图 38.1.14所示:
第三十八章 SDRAM读写测试实验6177.png
图 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的单位也是时钟周期数,具体值视时钟频率而定。
第三十八章 SDRAM读写测试实验7377.png
图 38.1.15 读取时预充电时序图(CL=2、BL=4、tRP=2)
自动预充电的开始时间与上图一样,只是没有了单独的预充电命令,并在发出读取命令时,A10地址线要设为高电平(允许自动预充电)。可见控制好预充电启动时间很重要,它可以在读取操作结束后立刻进入新行的寻址,保证运行效率。
写操作时,由于每笔数据的真正写入则需要一个足够的周期来保证,这段时间就是写回周期(tWR)。所以预充电不能与写操作同时进行,必须要在tWR之后才能发出预充电命令,以确保数据的可靠写入,否则重写的数据可能出错,如图 38.1.16所示。
第三十八章 SDRAM读写测试实验7727.png
图 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所示。
第三十八章 SDRAM读写测试实验9334.png
图 38.1.17 读取时DQM信号时序图
第三十八章 SDRAM读写测试实验9401.png
图 38.1.18 写入时DQM信号时序图
38.2实验任务
本节实验任务是向新起点开发板上的SDRAM中写入1024个数据,从SDRAM存储空间的起始地址写起,写完后再将数据读出,并验证读出数据是否正确。
38.3硬件设计
SDRAM的原理图如图 38.3.1所示。
第三十八章 SDRAM读写测试实验9614.png
图 38.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次刷新操作。其他时序参数请大家参考该芯片的数据手册。
本实验中,各端口信号的管脚分配如下表所示:
表 38.3.1 SDRAM读写测试实验管脚分配
信号名        方向        管脚        端口说明
clk        input        M2        系统时钟,50M
rst_n        input        M1        系统复位,低有效
led        output        D11        LED灯
sdram_clk        output        B14        SDRAM 芯片时钟
sdram_cke        output        F16        SDRAM 时钟有效
sdram_cs_n        output        K10        SDRAM 片选
sdram_ras_n        output        K11        SDRAM 行有效
sdram_cas_n        output        J12        SDRAM 列有效
sdram_we_n        output        J13        SDRAM 写有效
sdram_ba[1]        output        F13        SDRAM Bank地址
sdram_ba[0]        output        G11        SDRAM Bank地址
sdram_addr[12]        output        F15        SDRAM 行/列地址
sdram_addr[11]        output        D16        SDRAM 行/列地址
sdram_addr[10]        output        F14        SDRAM 行/列地址
sdram_addr[9]        output        D15        SDRAM 行/列地址
sdram_addr[8]        output        C16        SDRAM 行/列地址
sdram_addr[7]        output        C15        SDRAM 行/列地址
sdram_addr[6]        output        B16        SDRAM 行/列地址
sdram_addr[5]        output        A15        SDRAM 行/列地址
sdram_addr[4]        output        A14        SDRAM 行/列地址
sdram_addr[3]        output        C14        SDRAM 行/列地址
sdram_addr[2]        output        D14        SDRAM 行/列地址
sdram_addr[1]        output        E11        SDRAM 行/列地址
sdram_addr[0]        output        F11        SDRAM 行/列地址
sdram_data[15]        inout        L15        SDRAM 数据
sdram_data[14]        inout        L16        SDRAM 数据
sdram_data[13]        inout        K15        SDRAM 数据
sdram_data[12]        inout        K16        SDRAM 数据
sdram_data[11]        inout        J15        SDRAM 数据
sdram_data[10]        inout        J16        SDRAM 数据
sdram_data[9]        inout        J11        SDRAM 数据
sdram_data[8]        inout        G16        SDRAM 数据
sdram_data[7]        inout        K12        SDRAM 数据
sdram_data[6]        inout        L11        SDRAM 数据
sdram_data[5]        inout        L14        SDRAM 数据
sdram_data[4]        inout        L13        SDRAM 数据
sdram_data[3]        inout        L12        SDRAM 数据
sdram_data[2]        inout        N14        SDRAM 数据
sdram_data[1]        inout        M12        SDRAM 数据
sdram_data[0]        inout        P14        SDRAM 数据
sdram_dqm[1]        output        G15        SDRAM 数据掩码
sdram_dqm[0]        output        J14        SDRAM 数据掩码
对应的TCL约束如下所示:
  1. set_location_assignment PIN_B14 -to sdram_clk
  2. set_location_assignment PIN_G11 -to sdram_ba[0]
  3. set_location_assignment PIN_F13 -to sdram_ba[1]
  4. set_location_assignment PIN_J12 -to sdram_cas_n
  5. set_location_assignment PIN_F16 -to sdram_cke
  6. set_location_assignment PIN_K11 -to sdram_ras_n
  7. set_location_assignment PIN_J13 -to sdram_we_n
  8. set_location_assignment PIN_K10 -to sdram_cs_n
  9. set_location_assignment PIN_J14 -to sdram_dqm[0]
  10. set_location_assignment PIN_G15 -to sdram_dqm[1]
  11. set_location_assignment PIN_F11 -to sdram_addr[0]
  12. set_location_assignment PIN_E11 -to sdram_addr[1]
  13. set_location_assignment PIN_D14 -to sdram_addr[2]
  14. set_location_assignment PIN_C14 -to sdram_addr[3]
  15. set_location_assignment PIN_A14 -to sdram_addr[4]
  16. set_location_assignment PIN_A15 -to sdram_addr[5]
  17. set_location_assignment PIN_B16 -to sdram_addr[6]
  18. set_location_assignment PIN_C15 -to sdram_addr[7]
  19. set_location_assignment PIN_C16 -to sdram_addr[8]
  20. set_location_assignment PIN_D15 -to sdram_addr[9]
  21. set_location_assignment PIN_F14 -to sdram_addr[10]
  22. set_location_assignment PIN_D16 -to sdram_addr[11]
  23. set_location_assignment PIN_F15 -to sdram_addr[12]
  24. set_location_assignment PIN_P14 -to sdram_data[0]
  25. set_location_assignment PIN_M12 -to sdram_data[1]
  26. set_location_assignment PIN_N14 -to sdram_data[2]
  27. set_location_assignment PIN_L12 -to sdram_data[3]
  28. set_location_assignment PIN_L13 -to sdram_data[4]
  29. set_location_assignment PIN_L14 -to sdram_data[5]
  30. set_location_assignment PIN_L11 -to sdram_data[6]
  31. set_location_assignment PIN_K12 -to sdram_data[7]
  32. set_location_assignment PIN_G16 -to sdram_data[8]
  33. set_location_assignment PIN_J11 -to sdram_data[9]
  34. set_location_assignment PIN_J16 -to sdram_data[10]
  35. set_location_assignment PIN_J15 -to sdram_data[11]
  36. set_location_assignment PIN_K16 -to sdram_data[12]
  37. set_location_assignment PIN_K15 -to sdram_data[13]
  38. set_location_assignment PIN_L16 -to sdram_data[14]
  39. set_location_assignment PIN_L15 -to sdram_data[15]
  40. set_location_assignment PIN_D11 -to led
  41. set_location_assignment PIN_M1 -to rst_n
  42. set_location_assignment PIN_M2 -to clk
复制代码


38.4程序设计
在本次实验中,由于SDRAM的控制时序较为复杂,为方便用户调用,我们将SDRAM控制器封装成FIFO接口,这样我们操作SDRAM就像读写FIFO一样简单。整个系统的功能框图如图 38.4.1所示:
第三十八章 SDRAM读写测试实验13688.png
图 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所示:
第三十八章 SDRAM读写测试实验14311.png
图 38.4.2 顶层模块原理图
SDRAM测试模块(sdram_test)输出读写使能信号及写数据,通过SDRAM控制器将数据写入SDARM中地址为0~1023的存储空间中。在写过程结束后进行读操作,检测读出的数据是否与写入数据一致,检测结果由标志信号error_flag指示。LED显示模块根据error_flag的值驱动LED以不同的状态显示。当SDRAM读写测试正确时,LED灯常亮;读写测试结果不正确时,LED灯闪烁。
顶层模块的代码如下:
  1. 1   module sdram_rw_test(
  2. 2       input         clk,                      //FPGA外部时钟,50M
  3. 3       input         rst_n,                    //按键复位,低电平有效
  4. 4       //SDRAM 芯片接口
  5. 5       output        sdram_clk,                //SDRAM 芯片时钟
  6. 6       output        sdram_cke,                //SDRAM 时钟有效
  7. 7       output        sdram_cs_n,               //SDRAM 片选
  8. 8       output        sdram_ras_n,              //SDRAM 行有效
  9. 9       output        sdram_cas_n,              //SDRAM 列有效
  10. 10      output        sdram_we_n,               //SDRAM 写有效
  11. 11      output [ 1:0] sdram_ba,                 //SDRAM Bank地址
  12. 12      output [12:0] sdram_addr,               //SDRAM 行/列地址
  13. 13      inout  [15:0] sdram_data,               //SDRAM 数据
  14. 14      output [ 1:0] sdram_dqm,                //SDRAM 数据掩码
  15. 15      //LED
  16. 16      output        led                       //状态指示灯
  17. 17      );
  18. 18      
  19. 19  //wire define
  20. 20  wire        clk_50m;                        //SDRAM 读写测试时钟
  21. 21  wire        clk_100m;                       //SDRAM 控制器时钟
  22. 22  wire        clk_100m_shift;                 //相位偏移时钟
  23. 23      
  24. 24  wire        wr_en;                          //SDRAM 写端口:写使能
  25. 25  wire [15:0] wr_data;                        //SDRAM 写端口:写入的数据
  26. 26  wire        rd_en;                          //SDRAM 读端口:读使能
  27. 27  wire [15:0] rd_data;                        //SDRAM 读端口:读出的数据
  28. 28  wire        sdram_init_done;                //SDRAM 初始化完成信号
  29. 29  
  30. 30  wire        locked;                         //PLL输出有效标志
  31. 31  wire        sys_rst_n;                      //系统复位信号
  32. 32  wire        error_flag;                     //读写测试错误标志
  33. 33  
  34. 34  //*****************************************************
  35. 35  //**                    main code
  36. 36  //*****************************************************
  37. 37  
  38. 38  //待PLL输出稳定之后,停止系统复位
  39. 39  assign sys_rst_n = rst_n & locked;
  40. 40  
  41. 41  //例化PLL, 产生各模块所需要的时钟
  42. 42  pll_clk u_pll_clk(
  43. 43      .inclk0             (clk),
  44. 44      .areset             (~rst_n),
  45. 45      
  46. 46      .c0                 (clk_50m),
  47. 47      .c1                 (clk_100m),
  48. 48      .c2                 (clk_100m_shift),
  49. 49      .locked             (locked)
  50. 50      );
  51. 51  
  52. 52  //SDRAM测试模块,对SDRAM进行读写测试
  53. 53  sdram_test u_sdram_test(
  54. 54      .clk_50m            (clk_50m),
  55. 55      .rst_n              (sys_rst_n),
  56. 56      
  57. 57      .wr_en              (wr_en),
  58. 58      .wr_data            (wr_data),
  59. 59      .rd_en              (rd_en),
  60. 60      .rd_data            (rd_data),   
  61. 61      
  62. 62      .sdram_init_done    (sdram_init_done),   
  63. 63      .error_flag         (error_flag)
  64. 64      );
  65. 65  
  66. 66  //利用LED灯指示SDRAM读写测试的结果
  67. 67  led_disp u_led_disp(
  68. 68      .clk_50m            (clk_50m),
  69. 69      .rst_n              (sys_rst_n),
  70. 70                                           //SDRAM初始化失败或者读写错误都认为是实验失败
  71. 71      .error_flag         (~sdram_init_done||error_flag),
  72. 72      .led                (led)            
  73. 73      );
  74. 74  
  75. 75  //SDRAM 控制器顶层模块,封装成FIFO接口
  76. 76  //SDRAM 控制器地址组成: {bank_addr[1:0],row_addr[12:0],col_addr[8:0]}
  77. 77  sdram_top u_sdram_top(
  78. 78      .ref_clk            (clk_100m),        //sdram 控制器参考时钟
  79. 79      .out_clk            (clk_100m_shift),  //用于输出的相位偏移时钟
  80. 80      .rst_n              (sys_rst_n),       //系统复位
  81. 81      
  82. 82      //用户写端口
  83. 83      .wr_clk             (clk_50m),         //写端口FIFO: 写时钟
  84. 84      .wr_en              (wr_en),           //写端口FIFO: 写使能
  85. 85      .wr_data            (wr_data),         //写端口FIFO: 写数据
  86. 86      .wr_min_addr        (24'd0),           //写SDRAM的起始地址
  87. 87      .wr_max_addr        (24'd1024),        //写SDRAM的结束地址
  88. 88      .wr_len             (10'd512),         //写SDRAM时的数据突发长度
  89. 89      .wr_load            (~sys_rst_n),      //写端口复位: 复位写地址,清空写FIFO
  90. 90  
  91. 91      //用户读端口
  92. 92      .rd_clk             (clk_50m),         //读端口FIFO: 读时钟
  93. 93      .rd_en              (rd_en),           //读端口FIFO: 读使能
  94. 94      .rd_data            (rd_data),         //读端口FIFO: 读数据
  95. 95      .rd_min_addr        (24'd0),           //读SDRAM的起始地址
  96. 96      .rd_max_addr        (24'd1024),        //读SDRAM的结束地址
  97. 97      .rd_len             (10'd512),         //从SDRAM中读数据时的突发长度
  98. 98      .rd_load            (~sys_rst_n),      //读端口复位: 复位读地址,清空读FIFO
  99. 99      
  100. 100     //用户控制端口  
  101. 101     .sdram_read_valid   (1'b1),            //SDRAM 读使能
  102. 102     .sdram_init_done    (sdram_init_done), //SDRAM 初始化完成标志
  103. 103
  104. 104     //SDRAM 芯片接口
  105. 105     .sdram_clk          (sdram_clk),        //SDRAM 芯片时钟
  106. 106     .sdram_cke          (sdram_cke),        //SDRAM 时钟有效
  107. 107     .sdram_cs_n         (sdram_cs_n),       //SDRAM 片选
  108. 108     .sdram_ras_n        (sdram_ras_n),      //SDRAM 行有效
  109. 109     .sdram_cas_n        (sdram_cas_n),      //SDRAM 列有效
  110. 110     .sdram_we_n         (sdram_we_n),       //SDRAM 写有效
  111. 111     .sdram_ba           (sdram_ba),         //SDRAM Bank地址
  112. 112     .sdram_addr         (sdram_addr),       //SDRAM 行/列地址
  113. 113     .sdram_data         (sdram_data),       //SDRAM 数据
  114. 114     .sdram_dqm          (sdram_dqm)         //SDRAM 数据掩码
  115. 115     );
  116. 116
  117. 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. 1   module sdram_test(
  2. 2       input             clk_50m,          //时钟
  3. 3       input             rst_n,            //复位,低有效
  4. 4      
  5. 5       output reg        wr_en,            //SDRAM 写使能
  6. 6       output reg [15:0] wr_data,          //SDRAM 写入的数据
  7. 7       output reg        rd_en,            //SDRAM 读使能
  8. 8       input      [15:0] rd_data,          //SDRAM 读出的数据
  9. 9      
  10. 10      input             sdram_init_done,  //SDRAM 初始化完成标志
  11. 11      output reg        error_flag        //SDRAM 读写测试错误标志
  12. 12      );
  13. 13  
  14. 14  //reg define
  15. 15  reg        init_done_d0;                //寄存SDRAM初始化完成信号
  16. 16  reg        init_done_d1;                //寄存SDRAM初始化完成信号
  17. 17  reg [10:0] wr_cnt;                      //写操作计数器
  18. 18  reg [10:0] rd_cnt;                      //读操作计数器
  19. 19  reg        rd_valid;                    //读数据有效标志
  20. 20  
  21. 21  //*****************************************************
  22. 22  //**                    main code
  23. 23  //*****************************************************
  24. 24  
  25. 25  //同步SDRAM初始化完成信号
  26. 26  always @(posedge clk_50m or negedge rst_n) begin
  27. 27      if(!rst_n) begin
  28. 28          init_done_d0 <= 1'b0;
  29. 29          init_done_d1 <= 1'b0;
  30. 30      end
  31. 31      else begin
  32. 32          init_done_d0 <= sdram_init_done;
  33. 33          init_done_d1 <= init_done_d0;
  34. 34      end
  35. 35  end            
  36. 36  
  37. 37  //SDRAM初始化完成之后,写操作计数器开始计数
  38. 38  always @(posedge clk_50m or negedge rst_n) begin
  39. 39      if(!rst_n)
  40. 40          wr_cnt <= 11'd0;  
  41. 41      else if(init_done_d1 && (wr_cnt <= 11'd1024))
  42. 42          wr_cnt <= wr_cnt + 1'b1;
  43. 43      else
  44. 44          wr_cnt <= wr_cnt;
  45. 45  end   
  46. 46  
  47. 47  //SDRAM写端口FIFO的写使能、写数据(1~1024)
  48. 48  always @(posedge clk_50m or negedge rst_n) begin
  49. 49      if(!rst_n) begin      
  50. 50          wr_en   <= 1'b0;
  51. 51          wr_data <= 16'd0;
  52. 52      end
  53. 53      else if(wr_cnt >= 11'd1 && (wr_cnt <= 11'd1024)) begin
  54. 54              wr_en   <= 1'b1;            //写使能拉高
  55. 55              wr_data <= wr_cnt;          //写入数据1~1024
  56. 56          end   
  57. 57      else begin
  58. 58              wr_en   <= 1'b0;
  59. 59              wr_data <= 16'd0;
  60. 60          end               
  61. 61  end        
  62. 62  
  63. 63  //写入数据完成后,开始读操作   
  64. 64  always @(posedge clk_50m or negedge rst_n) begin
  65. 65      if(!rst_n)
  66. 66          rd_en <= 1'b0;
  67. 67      else if(wr_cnt > 11'd1024)          //写数据完成
  68. 68          rd_en <= 1'b1;                  //读使能拉高
  69. 69  end
  70. 70  
  71. 71  //对读操作计数     
  72. 72  always @(posedge clk_50m or negedge rst_n) begin
  73. 73      if(!rst_n)
  74. 74          rd_cnt <= 11'd0;
  75. 75      else if(rd_en) begin
  76. 76          if(rd_cnt < 11'd1024)
  77. 77              rd_cnt <= rd_cnt + 1'b1;
  78. 78          else
  79. 79              rd_cnt <= 11'd1;
  80. 80      end
  81. 81  end
  82. 82  
  83. 83  //第一次读取的数据无效,后续读操作所读取的数据才有效
  84. 84  always @(posedge clk_50m or negedge rst_n) begin
  85. 85      if(!rst_n)
  86. 86          rd_valid <= 1'b0;
  87. 87      else if(rd_cnt == 11'd1024)         //等待第一次读操作结束
  88. 88          rd_valid <= 1'b1;               //后续读取的数据有效
  89. 89      else
  90. 90          rd_valid <= rd_valid;
  91. 91  end            
  92. 92  
  93. 93  //读数据有效时,若读取数据错误,给出标志信号
  94. 94  always @(posedge clk_50m or negedge rst_n) begin
  95. 95      if(!rst_n)
  96. 96          error_flag <= 1'b0;
  97. 97      else if(rd_valid && (rd_data != rd_cnt))
  98. 98          error_flag <= 1'b1;             //若读取的数据错误,将错误标志位拉高
  99. 99      else
  100. 100         error_flag <= error_flag;
  101. 101 end
  102. 102
  103. 103 endmodule
复制代码


SDRAM读写测试模块从写起始地址开始,连续向1024个存储空间中写入数据1~1024。写完成后一直进行读操作,持续将该存储空间的数据读出。需要注意的是程序中第97行通过变量rd_valid将第一次读出的1024个数据排除,并未参与读写测试。这是由于SDRAM控制器为了保证读FIFO时刻有数据,在读使能拉高之前就已经将SDRAM中的数据“预读”一部分(突发读长度)到读FIFO中;而此时写SDRAM尚未完成,因此第一次从FIFO中读出的512个数据是无效的。第一次读操作结束后,读FIFO中的无效数据被读出并丢弃,后续读SDRAM得到的数据才用于验证读写过程是否正确。
LED显示模块的代码如下:
  1. 1   module led_disp(
  2. 2       input      clk_50m,     //系统时钟
  3. 3       input      rst_n,       //系统复位
  4. 4      
  5. 5       input      error_flag,  //错误标志信号
  6. 6       output reg led          //LED灯            
  7. 7       );
  8. 8   
  9. 9   //reg define
  10. 10  reg [24:0] led_cnt;         //控制LED闪烁周期的计数器
  11. 11  
  12. 12  //*****************************************************
  13. 13  //**                    main code
  14. 14  //*****************************************************
  15. 15  
  16. 16  //计数器对50MHz时钟计数,计数周期为0.5s
  17. 17  always @(posedge clk_50m or negedge rst_n) begin
  18. 18      if(!rst_n)
  19. 19          led_cnt <= 25'd0;
  20. 20      else if(led_cnt < 25'd25000000)
  21. 21          led_cnt <= led_cnt + 25'd1;
  22. 22      else
  23. 23          led_cnt <= 25'd0;
  24. 24  end
  25. 25  
  26. 26  //利用LED灯不同的显示状态指示错误标志的高低
  27. 27  always @(posedge clk_50m or negedge rst_n) begin
  28. 28      if(rst_n == 1'b0)
  29. 29          led <= 1'b0;
  30. 30      else if(error_flag) begin
  31. 31          if(led_cnt == 25'd25000000)
  32. 32              led <= ~led;    //错误标志为高时,LED灯每隔0.5s闪烁一次
  33. 33          else
  34. 34              led <= led;
  35. 35      end   
  36. 36      else
  37. 37          led <= 1'b1;        //错误标志为低时,LED灯常亮
  38. 38  end
  39. 39  
  40. 40  endmodule
复制代码


LED显示模块用LED不同的显示状态指示SDRAM读写测试的结果:若读写测试正确无误,则LED常亮;若出现错误(读出的数据与写入的数据不一致),则LED灯以0.5s为周期闪烁。
SDRAM控制器顶层模块如下:
  1. 1   module  sdram_top(
  2. 2       input         ref_clk,                  //sdram 控制器参考时钟
  3. 3       input         out_clk,                  //用于输出的相位偏移时钟
  4. 4       input         rst_n,                    //系统复位
  5. 5      
  6. 6       //用户写端口           
  7. 7       input         wr_clk,                   //写端口FIFO: 写时钟
  8. 8       input         wr_en,                    //写端口FIFO: 写使能
  9. 9       input  [15:0] wr_data,                  //写端口FIFO: 写数据
  10. 10      input  [23:0] wr_min_addr,              //写SDRAM的起始地址
  11. 11      input  [23:0] wr_max_addr,              //写SDRAM的结束地址
  12. 12      input  [ 9:0] wr_len,                   //写SDRAM时的数据突发长度
  13. 13      input         wr_load,                  //写端口复位: 复位写地址,清空写FIFO
  14. 14      
  15. 15      //用户读端口
  16. 16      input         rd_clk,                   //读端口FIFO: 读时钟
  17. 17      input         rd_en,                    //读端口FIFO: 读使能
  18. 18      output [15:0] rd_data,                  //读端口FIFO: 读数据
  19. 19      input  [23:0] rd_min_addr,              //读SDRAM的起始地址
  20. 20      input  [23:0] rd_max_addr,              //读SDRAM的结束地址
  21. 21      input  [ 9:0] rd_len,                   //从SDRAM中读数据时的突发长度
  22. 22      input         rd_load,                  //读端口复位: 复位读地址,清空读FIFO
  23. 23      
  24. 24      //用户控制端口  
  25. 25      input         sdram_read_valid,         //SDRAM 读使能
  26. 26      output        sdram_init_done,          //SDRAM 初始化完成标志
  27. 27      
  28. 28      //SDRAM 芯片接口
  29. 29      output        sdram_clk,                //SDRAM 芯片时钟
  30. 30      output        sdram_cke,                //SDRAM 时钟有效
  31. 31      output        sdram_cs_n,               //SDRAM 片选
  32. 32      output        sdram_ras_n,              //SDRAM 行有效
  33. 33      output        sdram_cas_n,              //SDRAM 列有效
  34. 34      output        sdram_we_n,               //SDRAM 写有效
  35. 35      output [ 1:0] sdram_ba,                 //SDRAM Bank地址
  36. 36      output [12:0] sdram_addr,               //SDRAM 行/列地址
  37. 37      inout  [15:0] sdram_data,               //SDRAM 数据
  38. 38      output [ 1:0] sdram_dqm                 //SDRAM 数据掩码
  39. 39      );
  40. 40  
  41. 41  //wire define
  42. 42  wire        sdram_wr_req;                   //sdram 写请求
  43. 43  wire        sdram_wr_ack;                   //sdram 写响应
  44. 44  wire [23:0] sdram_wr_addr;                  //sdram 写地址
  45. 45  wire [15:0] sdram_din;                      //写入sdram中的数据
  46. 46  
  47. 47  wire        sdram_rd_req;                   //sdram 读请求
  48. 48  wire        sdram_rd_ack;                   //sdram 读响应
  49. 49  wire [23:0] sdram_rd_addr;                   //sdram 读地址
  50. 50  wire [15:0] sdram_dout;                     //从sdram中读出的数据
  51. 51  
  52. 52  //*****************************************************
  53. 53  //**                    main code
  54. 54  //*****************************************************
  55. 55  assign  sdram_clk = out_clk;                //将相位偏移时钟输出给sdram芯片
  56. 56  assign  sdram_dqm = 2'b00;                  //读写过程中均不屏蔽数据线
  57. 57              
  58. 58  //SDRAM 读写端口FIFO控制模块
  59. 59  sdram_fifo_ctrl u_sdram_fifo_ctrl(
  60. 60      .clk_ref            (ref_clk),          //SDRAM控制器时钟
  61. 61      .rst_n              (rst_n),            //系统复位
  62. 62  
  63. 63      //用户写端口
  64. 64      .clk_write          (wr_clk),           //写端口FIFO: 写时钟
  65. 65      .wrf_wrreq          (wr_en),            //写端口FIFO: 写请求
  66. 66      .wrf_din            (wr_data),          //写端口FIFO: 写数据  
  67. 67      .wr_min_addr        (wr_min_addr),      //写SDRAM的起始地址
  68. 68      .wr_max_addr        (wr_max_addr),      //写SDRAM的结束地址
  69. 69      .wr_length          (wr_len),           //写SDRAM时的数据突发长度
  70. 70      .wr_load            (wr_load),          //写端口复位: 复位写地址,清空写FIFO   
  71. 71      
  72. 72      //用户读端口
  73. 73      .clk_read           (rd_clk),           //读端口FIFO: 读时钟
  74. 74      .rdf_rdreq          (rd_en),            //读端口FIFO: 读请求
  75. 75      .rdf_dout           (rd_data),          //读端口FIFO: 读数据
  76. 76      .rd_min_addr        (rd_min_addr),      //读SDRAM的起始地址
  77. 77      .rd_max_addr        (rd_max_addr),      //读SDRAM的结束地址
  78. 78      .rd_length          (rd_len),           //从SDRAM中读数据时的突发长度
  79. 79      .rd_load            (rd_load),          //读端口复位: 复位读地址,清空读FIFO
  80. 80     
  81. 81      //用户控制端口   
  82. 82      .sdram_read_valid   (sdram_read_valid), //sdram 读使能
  83. 83      .sdram_init_done    (sdram_init_done),  //sdram 初始化完成标志
  84. 84  
  85. 85      //SDRAM 控制器写端口
  86. 86      .sdram_wr_req       (sdram_wr_req),     //sdram 写请求
  87. 87      .sdram_wr_ack       (sdram_wr_ack),     //sdram 写响应
  88. 88      .sdram_wr_addr      (sdram_wr_addr),    //sdram 写地址
  89. 89      .sdram_din          (sdram_din),        //写入sdram中的数据
  90. 90      
  91. 91      //SDRAM 控制器读端口
  92. 92      .sdram_rd_req       (sdram_rd_req),     //sdram 读请求
  93. 93      .sdram_rd_ack       (sdram_rd_ack),     //sdram 读响应
  94. 94      .sdram_rd_addr      (sdram_rd_addr),    //sdram 读地址
  95. 95      .sdram_dout         (sdram_dout)        //从sdram中读出的数据
  96. 96      );
  97. 97  
  98. 98  //SDRAM控制器
  99. 99  sdram_controller u_sdram_controller(
  100. 100     .clk                (ref_clk),          //sdram 控制器时钟
  101. 101     .rst_n              (rst_n),            //系统复位
  102. 102     
  103. 103     //SDRAM 控制器写端口  
  104. 104     .sdram_wr_req       (sdram_wr_req),     //sdram 写请求
  105. 105     .sdram_wr_ack       (sdram_wr_ack),     //sdram 写响应
  106. 106     .sdram_wr_addr      (sdram_wr_addr),    //sdram 写地址
  107. 107     .sdram_wr_burst     (wr_len),           //写sdram时数据突发长度
  108. 108     .sdram_din          (sdram_din),        //写入sdram中的数据
  109. 109     
  110. 110     //SDRAM 控制器读端口
  111. 111     .sdram_rd_req       (sdram_rd_req),     //sdram 读请求
  112. 112     .sdram_rd_ack       (sdram_rd_ack),     //sdram 读响应
  113. 113     .sdram_rd_addr      (sdram_rd_addr),    //sdram 读地址
  114. 114     .sdram_rd_burst     (rd_len),           //读sdram时数据突发长度
  115. 115     .sdram_dout         (sdram_dout),       //从sdram中读出的数据
  116. 116     
  117. 117     .sdram_init_done    (sdram_init_done),  //sdram 初始化完成标志
  118. 118
  119. 119     //SDRAM 芯片接口
  120. 120     .sdram_cke          (sdram_cke),        //SDRAM 时钟有效
  121. 121     .sdram_cs_n         (sdram_cs_n),       //SDRAM 片选
  122. 122     .sdram_ras_n        (sdram_ras_n),      //SDRAM 行有效   
  123. 123     .sdram_cas_n        (sdram_cas_n),      //SDRAM 列有效
  124. 124     .sdram_we_n         (sdram_we_n),       //SDRAM 写有效
  125. 125     .sdram_ba           (sdram_ba),         //SDRAM Bank地址
  126. 126     .sdram_addr         (sdram_addr),       //SDRAM 行/列地址
  127. 127     .sdram_data         (sdram_data)        //SDRAM 数据  
  128. 128     );
  129. 129     
  130. 130 endmodule
复制代码


SDRAM控制器顶层模块主要完成SDRAM控制器及SDRAM读写端口FIFO控制模块的例化。代码中56行给SDRAM数据掩码赋值,因为我们读写测试过程中,数据线的高字节和低字节均一直有效,因此整个SDRAM读写过程中不需要屏蔽数据线。
SDRAM FIFO控制模块代码如下所示:
  1. 1   module sdram_fifo_ctrl(
  2. 2       input             clk_ref,           //SDRAM控制器时钟
  3. 3       input             rst_n,             //系统复位
  4. 4                                          
  5. 5       //用户写端口                        
  6. 6       input             clk_write,         //写端口FIFO: 写时钟
  7. 7       input             wrf_wrreq,         //写端口FIFO: 写请求
  8. 8       input      [15:0] wrf_din,           //写端口FIFO: 写数据
  9. 9       input      [23:0] wr_min_addr,       //写SDRAM的起始地址
  10. 10      input      [23:0] wr_max_addr,       //写SDRAM的结束地址
  11. 11      input      [ 9:0] wr_length,         //写SDRAM时的数据突发长度
  12. 12      input             wr_load,           //写端口复位: 复位写地址,清空写FIFO
  13. 13                                          
  14. 14      //用户读端口                        
  15. 15      input             clk_read,          //读端口FIFO: 读时钟
  16. 16      input             rdf_rdreq,         //读端口FIFO: 读请求
  17. 17      output     [15:0] rdf_dout,          //读端口FIFO: 读数据
  18. 18      input      [23:0] rd_min_addr,       //读SDRAM的起始地址
  19. 19      input      [23:0] rd_max_addr,       //读SDRAM的结束地址
  20. 20      input      [ 9:0] rd_length,         //从SDRAM中读数据时的突发长度
  21. 21      input             rd_load,           //读端口复位: 复位读地址,清空读FIFO
  22. 22                                          
  23. 23      //用户控制端口                        
  24. 24      input             sdram_read_valid,  //SDRAM 读使能
  25. 25      input             sdram_init_done,   //SDRAM 初始化完成标志
  26. 26                                          
  27. 27      //SDRAM 控制器写端口                 
  28. 28      output reg        sdram_wr_req,      //sdram 写请求
  29. 29      input             sdram_wr_ack,      //sdram 写响应
  30. 30      output reg [23:0] sdram_wr_addr,     //sdram 写地址
  31. 31      output     [15:0] sdram_din,         //写入SDRAM中的数据
  32. 32                                          
  33. 33      //SDRAM 控制器读端口                 
  34. 34      output reg        sdram_rd_req,      //sdram 读请求
  35. 35      input             sdram_rd_ack,      //sdram 读响应
  36. 36      output reg [23:0] sdram_rd_addr,     //sdram 读地址
  37. 37      input      [15:0] sdram_dout         //从SDRAM中读出的数据
  38. 38      );
  39. 39  
  40. 40  //reg define
  41. 41  reg        wr_ack_r1;                    //sdram写响应寄存器      
  42. 42  reg        wr_ack_r2;                    
  43. 43  reg        rd_ack_r1;                    //sdram读响应寄存器      
  44. 44  reg        rd_ack_r2;                    
  45. 45  reg        wr_load_r1;                   //写端口复位寄存器      
  46. 46  reg        wr_load_r2;                  
  47. 47  reg        rd_load_r1;                   //读端口复位寄存器      
  48. 48  reg        rd_load_r2;                  
  49. 49  reg        read_valid_r1;                //sdram读使能寄存器      
  50. 50  reg        read_valid_r2;               
  51. 51                                          
  52. 52  //wire define                           
  53. 53  wire       write_done_flag;              //sdram_wr_ack 下降沿标志位      
  54. 54  wire       read_done_flag;               //sdram_rd_ack 下降沿标志位      
  55. 55  wire       wr_load_flag;                 //wr_load      上升沿标志位      
  56. 56  wire       rd_load_flag;                 //rd_load      上升沿标志位      
  57. 57  wire [9:0] wrf_use;                      //写端口FIFO中的数据量
  58. 58  wire [9:0] rdf_use;                      //读端口FIFO中的数据量
  59. 59  
  60. 60  //*****************************************************
  61. 61  //**                    main code
  62. 62  //*****************************************************
  63. 63  
  64. 64  //检测下降沿
  65. 65  assign write_done_flag = wr_ack_r2   & ~wr_ack_r1;  
  66. 66  assign read_done_flag  = rd_ack_r2   & ~rd_ack_r1;
  67. 67  
  68. 68  //检测上升沿
  69. 69  assign wr_load_flag    = ~wr_load_r2 & wr_load_r1;
  70. 70  assign rd_load_flag    = ~rd_load_r2 & rd_load_r1;
  71. 71  
  72. 72  //寄存sdram写响应信号,用于捕获sdram_wr_ack下降沿
  73. 73  always @(posedge clk_ref or negedge rst_n) begin
  74. 74      if(!rst_n) begin
  75. 75          wr_ack_r1 <= 1'b0;
  76. 76          wr_ack_r2 <= 1'b0;
  77. 77      end
  78. 78      else begin
  79. 79          wr_ack_r1 <= sdram_wr_ack;
  80. 80          wr_ack_r2 <= wr_ack_r1;     
  81. 81      end
  82. 82  end
  83. 83  
  84. 84  //寄存sdram读响应信号,用于捕获sdram_rd_ack下降沿
  85. 85  always @(posedge clk_ref or negedge rst_n) begin
  86. 86      if(!rst_n) begin
  87. 87          rd_ack_r1 <= 1'b0;
  88. 88          rd_ack_r2 <= 1'b0;
  89. 89      end
  90. 90      else begin
  91. 91          rd_ack_r1 <= sdram_rd_ack;
  92. 92          rd_ack_r2 <= rd_ack_r1;
  93. 93      end
  94. 94  end
  95. 95  
  96. 96  //同步写端口复位信号,用于捕获wr_load上升沿
  97. 97  always @(posedge clk_ref or negedge rst_n) begin
  98. 98      if(!rst_n) begin
  99. 99          wr_load_r1 <= 1'b0;
  100. 100         wr_load_r2 <= 1'b0;
  101. 101     end
  102. 102     else begin
  103. 103         wr_load_r1 <= wr_load;
  104. 104         wr_load_r2 <= wr_load_r1;
  105. 105     end
  106. 106 end
  107. 107
  108. 108 //同步读端口复位信号,同时用于捕获rd_load上升沿
  109. 109 always @(posedge clk_ref or negedge rst_n) begin
  110. 110     if(!rst_n) begin
  111. 111         rd_load_r1 <= 1'b0;
  112. 112         rd_load_r2 <= 1'b0;
  113. 113     end
  114. 114     else begin
  115. 115         rd_load_r1 <= rd_load;
  116. 116         rd_load_r2 <= rd_load_r1;
  117. 117     end
  118. 118 end
  119. 119
  120. 120 //同步sdram读使能信号
  121. 121 always @(posedge clk_ref or negedge rst_n) begin
  122. 122     if(!rst_n) begin
  123. 123         read_valid_r1 <= 1'b0;
  124. 124         read_valid_r2 <= 1'b0;
  125. 125     end
  126. 126     else begin
  127. 127         read_valid_r1 <= sdram_read_valid;
  128. 128         read_valid_r2 <= read_valid_r1;
  129. 129     end
  130. 130 end
  131. 131
  132. 132 //sdram写地址产生模块
  133. 133 always @(posedge clk_ref or negedge rst_n) begin
  134. 134     if(!rst_n)
  135. 135         sdram_wr_addr <= 24'd0;
  136. 136     else if(wr_load_flag)                //检测到写端口复位信号时,写地址复位
  137. 137         sdram_wr_addr <= wr_min_addr;   
  138. 138     else if(write_done_flag) begin       //若突发写SDRAM结束,更改写地址
  139. 139                                         //若未到达写SDRAM的结束地址,则写地址累加
  140. 140         if(sdram_wr_addr < wr_max_addr - wr_length)
  141. 141             sdram_wr_addr <= sdram_wr_addr + wr_length;
  142. 142             else                         //若已到达写SDRAM的结束地址,则回到写起始地址
  143. 143             sdram_wr_addr <= wr_min_addr;
  144. 144     end
  145. 145 end
  146. 146
  147. 147 //sdram读地址产生模块
  148. 148 always @(posedge clk_ref or negedge rst_n) begin
  149. 149     if(!rst_n)
  150. 150         sdram_rd_addr <= 24'd0;
  151. 151     else if(rd_load_flag)                //检测到读端口复位信号时,读地址复位
  152. 152         sdram_rd_addr <= rd_min_addr;
  153. 153     else if(read_done_flag) begin        //突发读SDRAM结束,更改读地址
  154. 154                                         //若未到达读SDRAM的结束地址,则读地址累加
  155. 155         if(sdram_rd_addr < rd_max_addr - rd_length)
  156. 156             sdram_rd_addr <= sdram_rd_addr + rd_length;
  157. 157         else                             //若已到达读SDRAM的结束地址,则回到读起始地址
  158. 158             sdram_rd_addr <= rd_min_addr;
  159. 159     end
  160. 160 end
  161. 161
  162. 162 //sdram 读写请求信号产生模块
  163. 163 always@(posedge clk_ref or negedge rst_n) begin
  164. 164     if(!rst_n) begin
  165. 165         sdram_wr_req <= 0;
  166. 166         sdram_rd_req <= 0;
  167. 167     end
  168. 168     else if(sdram_init_done) begin       //SDRAM初始化完成后才能响应读写请求
  169. 169                                          //优先执行写操作,防止写入SDRAM中的数据丢失
  170. 170         if(wrf_use >= wr_length) begin   //若写端口FIFO中的数据量达到了写突发长度
  171. 171             sdram_wr_req <= 1;           //发出写sdarm请求
  172. 172             sdram_rd_req <= 0;           
  173. 173         end
  174. 174         else if((rdf_use < rd_length)    //若读端口FIFO中的数据量小于读突发长度,
  175. 175                 && read_valid_r2) begin //同时sdram读使能信号为高
  176. 176             sdram_wr_req <= 0;           
  177. 177             sdram_rd_req <= 1;           //发出读sdarm请求
  178. 178         end
  179. 179         else begin
  180. 180             sdram_wr_req <= 0;
  181. 181             sdram_rd_req <= 0;
  182. 182         end
  183. 183     end
  184. 184     else begin
  185. 185         sdram_wr_req <= 0;
  186. 186         sdram_rd_req <= 0;
  187. 187     end
  188. 188 end
  189. 189
  190. 190 //例化写端口FIFO
  191. 191 wrfifo  u_wrfifo(
  192. 192     //用户接口
  193. 193     .wrclk      (clk_write),             //写时钟
  194. 194     .wrreq      (wrf_wrreq),             //写请求
  195. 195     .data       (wrf_din),               //写数据
  196. 196     
  197. 197     //sdram接口
  198. 198     .rdclk      (clk_ref),               //读时钟
  199. 199     .rdreq      (sdram_wr_ack),          //读请求
  200. 200     .q          (sdram_din),             //读数据
  201. 201
  202. 202     .rdusedw    (wrf_use),               //FIFO中的数据量
  203. 203     .aclr       (~rst_n | wr_load_flag)  //异步清零信号
  204. 204     );  
  205. 205
  206. 206 //例化读端口FIFO
  207. 207 rdfifo  u_rdfifo(
  208. 208     //sdram接口
  209. 209     .wrclk      (clk_ref),               //写时钟
  210. 210     .wrreq      (sdram_rd_ack),          //写请求
  211. 211     .data       (sdram_dout),            //写数据
  212. 212     
  213. 213     //用户接口
  214. 214     .rdclk      (clk_read),              //读时钟
  215. 215     .rdreq      (rdf_rdreq),             //读请求
  216. 216     .q          (rdf_dout),              //读数据
  217. 217
  218. 218     .wrusedw    (rdf_use),               //FIFO中的数据量
  219. 219     .aclr       (~rst_n | rd_load_flag)  //异步清零信号   
  220. 220     );
  221. 221     
  222. 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. 1   module sdram_controller(
  2. 2       input         clk,              //SDRAM控制器时钟,100MHz
  3. 3       input         rst_n,            //系统复位信号,低电平有效
  4. 4      
  5. 5       //SDRAM 控制器写端口  
  6. 6       input         sdram_wr_req,     //写SDRAM请求信号
  7. 7       output        sdram_wr_ack,     //写SDRAM响应信号
  8. 8       input  [23:0] sdram_wr_addr,    //SDRAM写操作的地址
  9. 9       input  [ 9:0] sdram_wr_burst,   //写sdram时数据突发长度
  10. 10      input  [15:0] sdram_din,        //写入SDRAM的数据
  11. 11      
  12. 12      //SDRAM 控制器读端口  
  13. 13      input         sdram_rd_req,     //读SDRAM请求信号
  14. 14      output        sdram_rd_ack,     //读SDRAM响应信号
  15. 15      input  [23:0] sdram_rd_addr,    //SDRAM写操作的地址
  16. 16      input  [ 9:0] sdram_rd_burst,   //读sdram时数据突发长度
  17. 17      output [15:0] sdram_dout,       //从SDRAM读出的数据
  18. 18      
  19. 19      output        sdram_init_done,  //SDRAM 初始化完成标志
  20. 20                                      
  21. 21      // FPGA与SDRAM硬件接口
  22. 22      output        sdram_cke,        // SDRAM 时钟有效信号
  23. 23      output        sdram_cs_n,       // SDRAM 片选信号
  24. 24      output        sdram_ras_n,      // SDRAM 行地址选通脉冲
  25. 25      output        sdram_cas_n,      // SDRAM 列地址选通脉冲
  26. 26      output        sdram_we_n,       // SDRAM 写允许位
  27. 27      output [ 1:0] sdram_ba,         // SDRAM L-Bank地址线
  28. 28      output [12:0] sdram_addr,       // SDRAM 地址总线
  29. 29      inout  [15:0] sdram_data        // SDRAM 数据总线
  30. 30      );
  31. 31  
  32. 32  //wire define
  33. 33  wire [4:0] init_state;              // SDRAM初始化状态
  34. 34  wire [3:0] work_state;              // SDRAM工作状态
  35. 35  wire [9:0] cnt_clk;                 // 延时计数器
  36. 36  wire       sdram_rd_wr;             // SDRAM读/写控制信号,低电平为写,高电平为读
  37. 37  
  38. 38  //*****************************************************
  39. 39  //**                    main code
  40. 40  //*****************************************************     
  41. 41  
  42. 42  // SDRAM 状态控制模块               
  43. 43  sdram_ctrl u_sdram_ctrl(            
  44. 44      .clk                (clk),                     
  45. 45      .rst_n              (rst_n),
  46. 46  
  47. 47      .sdram_wr_req       (sdram_wr_req),
  48. 48      .sdram_rd_req       (sdram_rd_req),
  49. 49      .sdram_wr_ack       (sdram_wr_ack),
  50. 50      .sdram_rd_ack       (sdram_rd_ack),                     
  51. 51      .sdram_wr_burst     (sdram_wr_burst),
  52. 52      .sdram_rd_burst     (sdram_rd_burst),
  53. 53      .sdram_init_done    (sdram_init_done),
  54. 54      
  55. 55      .init_state         (init_state),
  56. 56      .work_state         (work_state),
  57. 57      .cnt_clk            (cnt_clk),
  58. 58      .sdram_rd_wr        (sdram_rd_wr)
  59. 59      );
  60. 60  
  61. 61  // SDRAM 命令控制模块
  62. 62  sdram_cmd u_sdram_cmd(              
  63. 63      .clk                (clk),
  64. 64      .rst_n              (rst_n),
  65. 65  
  66. 66      .sys_wraddr         (sdram_wr_addr),            
  67. 67      .sys_rdaddr         (sdram_rd_addr),
  68. 68      .sdram_wr_burst     (sdram_wr_burst),
  69. 69      .sdram_rd_burst     (sdram_rd_burst),
  70. 70      
  71. 71      .init_state         (init_state),   
  72. 72      .work_state         (work_state),
  73. 73      .cnt_clk            (cnt_clk),
  74. 74      .sdram_rd_wr        (sdram_rd_wr),
  75. 75      
  76. 76      .sdram_cke          (sdram_cke),        
  77. 77      .sdram_cs_n         (sdram_cs_n),   
  78. 78      .sdram_ras_n        (sdram_ras_n),  
  79. 79      .sdram_cas_n        (sdram_cas_n),  
  80. 80      .sdram_we_n         (sdram_we_n),   
  81. 81      .sdram_ba           (sdram_ba),         
  82. 82      .sdram_addr         (sdram_addr)
  83. 83      );
  84. 84  
  85. 85  // SDRAM 数据读写模块
  86. 86  sdram_data u_sdram_data(        
  87. 87      .clk                (clk),
  88. 88      .rst_n              (rst_n),
  89. 89      
  90. 90      .sdram_data_in      (sdram_din),
  91. 91      .sdram_data_out     (sdram_dout),
  92. 92      .work_state         (work_state),
  93. 93      .cnt_clk            (cnt_clk),
  94. 94      
  95. 95      .sdram_data         (sdram_data)
  96. 96      );
  97. 97  
  98. 98  endmodule
复制代码


SDRAM控制器主要例化了三个模块:SDRAM状态控制模块、SDRAM命令控制模块、SDRAM数据读写模块。下图为SDRAM控制器的功能框图:
第三十八章 SDRAM读写测试实验43596.png
图 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                                         //预充电状态
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的初始化流程(图 38.4.4)作了简单介绍,由此我们可以画出初始化状态机的状态转换图如下所示:
第三十八章 SDRAM读写测试实验55348.png
图 38.4.4 初始化状态机——状态转换图
如上图所示,SDRAM在上电后要有200us的输入稳定期。200us结束后对所有L-Bank预充电,然后等待预充电有效周期(tRP)结束后连续进行8次自动刷新操作,每次刷新操作都要等待自动刷新周期(tRC)。最后对SDRAM的模式寄存器进行设置,并等待模式寄存器设置周期(tRSC)结束。到这里SDRAM的初始化也就完成了,接下来SDRAM进入正常的工作状态。
由于SDRAM需要定时进行刷新操作以保存存储体中的数据,所以工作状态机不仅要根据外部的读写请求来进行读写操作,还要处理模块内部产生的刷新请求。那么当多个请求信号同时到达时,工作状态机该如何进行仲裁呢?
首先,为了保存SDRAM中的数据,刷新请求的优先级最高;写请求次之,这是为了避免准备写入SDRAM中的数据丢失;而读请求的优先级最低。因此,当刷新请求与读写请求同时产生时,优先执行刷新操作;而读请求与写请求同时产生时,优先执行写操作。
另外,由于刷新操作需要等待刷新周期(tRC)结束,而读写操作同样需要一定的时间(特别是突发模式下需要等待所有数据突发传输结束)。因此在上一个请求操作执行的过程中接收到新的请求信号是很有可能的,这种情况下,新的请求信号必须等待当前执行过程结束才能得到工作状态机的响应。
工作状态机的状态转换图如下所示:
第三十八章 SDRAM读写测试实验55967.png
图 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. 1   // SDRAM 初始化过程各个状态
  2. 2   `define     I_NOP           5'd0                           //等待上电200us稳定期结束
  3. 3   `define     I_PRE           5'd1                           //预充电状态
  4. 4   `define     I_TRP           5'd2                           //等待预充电完成       tRP
  5. 5   `define     I_AR            5'd3                           //自动刷新            
  6. 6   `define     I_TRF           5'd4                           //等待自动刷新结束    tRC
  7. 7   `define     I_MRS           5'd5                           //模式寄存器设置
  8. 8   `define     I_TRSC          5'd6                           //等待模式寄存器设置完成 tRSC
  9. 9   `define     I_DONE          5'd7                           //初始化完成
  10. 10  
  11. 11  // SDRAM 工作过程各个状态
  12. 12  `define     W_IDLE          4'd0                            //空闲
  13. 13  `define     W_ACTIVE        4'd1                            //行有效
  14. 14  `define     W_TRCD          4'd2                            //行有效等待
  15. 15  `define     W_READ          4'd3                            //读操作
  16. 16  `define     W_CL            4'd4                            //潜伏期
  17. 17  `define     W_RD            4'd5                            //读数据
  18. 18  `define     W_WRITE         4'd6                            //写操作
  19. 19  `define     W_WD            4'd7                            //写数据
  20. 20  `define     W_TWR           4'd8                            //写回
  21. 21  `define     W_PRE           4'd9                            //预充电
  22. 22  `define     W_TRP           4'd10                           //预充电等待
  23. 23  `define     W_AR            4'd11                           //自动刷新
  24. 24  `define     W_TRFC          4'd12                           //自动刷新等待
  25. 25  
  26. 26  //延时参数
  27. 27  `define     end_trp         cnt_clk == TRP_CLK              //预充电有效周期结束
  28. 28  `define     end_trfc        cnt_clk == TRC_CLK              //自动刷新周期结束
  29. 29  `define     end_trsc        cnt_clk == TRSC_CLK             //模式寄存器设置时钟周期结束
  30. 30  `define     end_trcd        cnt_clk == TRCD_CLK-1           //行选通周期结束
  31. 31  `define     end_tcl         cnt_clk == TCL_CLK-1            //潜伏期结束
  32. 32  `define     end_rdburst     cnt_clk == sdram_rd_burst-3     //读突发终止
  33. 33  `define     end_tread       cnt_clk == sdram_rd_burst+2     //突发读结束     
  34. 34  `define     end_wrburst     cnt_clk == sdram_wr_burst-1     //写突发终止
  35. 35  `define     end_twrite      cnt_clk == sdram_wr_burst-1     //突发写结束
  36. 36  `define     end_twr         cnt_clk == TWR_CLK              //写回周期结束
  37. 37  
  38. 38  //SDRAM控制信号命令
  39. 39  `define     CMD_INIT        5'b01111                        // INITIATE
  40. 40  `define     CMD_NOP         5'b10111                        // NOP COMMAND
  41. 41  `define     CMD_ACTIVE      5'b10011                        // ACTIVE COMMAND
  42. 42  `define     CMD_READ        5'b10101                        // READ COMMADN
  43. 43  `define     CMD_WRITE       5'b10100                        // WRITE COMMAND
  44. 44  `define     CMD_B_STOP      5'b10110                        // BURST STOP
  45. 45  `define     CMD_PRGE        5'b10010                        // PRECHARGE
  46. 46  `define     CMD_A_REF       5'b10001                        // AOTO REFRESH
  47. 47  `define     CMD_LMR         5'b10000                        // LODE MODE REGISTER
  48. SDRAM命令控制模块的代码如下所示:
  49. 1   module sdram_cmd(
  50. 2       input             clk,              //系统时钟
  51. 3       input             rst_n,            //低电平复位信号
  52. 4   
  53. 5       input      [23:0] sys_wraddr,       //写SDRAM时地址
  54. 6       input      [23:0] sys_rdaddr,       //读SDRAM时地址
  55. 7       input      [ 9:0] sdram_wr_burst,   //突发写SDRAM字节数
  56. 8       input      [ 9:0] sdram_rd_burst,   //突发读SDRAM字节数
  57. 9      
  58. 10      input      [ 4:0] init_state,       //SDRAM初始化状态
  59. 11      input      [ 3:0] work_state,       //SDRAM工作状态
  60. 12      input      [ 9:0] cnt_clk,          //延时计数器   
  61. 13      input             sdram_rd_wr,      //SDRAM读/写控制信号,低电平为写
  62. 14      
  63. 15      output            sdram_cke,        //SDRAM时钟有效信号
  64. 16      output            sdram_cs_n,       //SDRAM片选信号
  65. 17      output            sdram_ras_n,      //SDRAM行地址选通脉冲
  66. 18      output            sdram_cas_n,      //SDRAM列地址选通脉冲
  67. 19      output            sdram_we_n,       //SDRAM写允许位
  68. 20      output reg [ 1:0] sdram_ba,         //SDRAM的L-Bank地址线
  69. 21      output reg [12:0] sdram_addr        //SDRAM地址总线
  70. 22      );
  71. 23  
  72. 24  `include "sdram_para.v"                 //包含SDRAM参数定义模块
  73. 25  
  74. 26  //reg define
  75. 27  reg  [ 4:0] sdram_cmd_r;                //SDRAM操作指令
  76. 28  
  77. 29  //wire define
  78. 30  wire [23:0] sys_addr;                   //SDRAM读写地址
  79. 31  
  80. 32  //*****************************************************
  81. 33  //**                    main code
  82. 34  //*****************************************************
  83. 35  
  84. 36  //SDRAM 控制信号线赋值
  85. 37  assign {sdram_cke,sdram_cs_n,sdram_ras_n,sdram_cas_n,sdram_we_n} = sdram_cmd_r;
  86. 38  
  87. 39  //SDRAM 读/写地址总线控制
  88. 40  assign sys_addr = sdram_rd_wr ? sys_rdaddr : sys_wraddr;
  89. 41      
  90. 42  //SDRAM 操作指令控制
  91. 43  always @ (posedge clk or negedge rst_n) begin
  92. 44      if(!rst_n) begin
  93. 45              sdram_cmd_r <= `CMD_INIT;
  94. 46              sdram_ba    <= 2'b11;
  95. 47              sdram_addr  <= 13'h1fff;
  96. 48      end
  97. 49      else
  98. 50          case(init_state)
  99. 51                                          //初始化过程中,以下状态不执行任何指令
  100. 52              `I_NOP,`I_TRP,`I_TRF,`I_TRSC: begin
  101. 53                      sdram_cmd_r <= `CMD_NOP;
  102. 54                      sdram_ba    <= 2'b11;
  103. 55                      sdram_addr  <= 13'h1fff;   
  104. 56                  end
  105. 57              `I_PRE: begin               //预充电指令
  106. 58                      sdram_cmd_r <= `CMD_PRGE;
  107. 59                      sdram_ba    <= 2'b11;
  108. 60                      sdram_addr  <= 13'h1fff;
  109. 61                  end
  110. 62              `I_AR: begin
  111. 63                                          //自动刷新指令
  112. 64                      sdram_cmd_r <= `CMD_A_REF;
  113. 65                      sdram_ba    <= 2'b11;
  114. 66                      sdram_addr  <= 13'h1fff;                        
  115. 67                  end                 
  116. 68              `I_MRS: begin               //模式寄存器设置指令
  117. 69                      sdram_cmd_r <= `CMD_LMR;
  118. 70                      sdram_ba    <= 2'b00;
  119. 71                      sdram_addr  <= {   //利用地址线设置模式寄存器,可根据实际需要进行修改
  120. 72                          3'b000,         //预留
  121. 73                          1'b0,           //读写方式 A9=0,突发读&突发写
  122. 74                          2'b00,          //默认,{A8,A7}=00
  123. 75                          3'b011,         //CAS潜伏期设置,这里设置为3,{A6,A5,A4}=011
  124. 76                          1'b0,           //突发传输方式,这里设置为顺序,A3=0
  125. 77                          3'b111          //突发长度,这里设置为页突发,{A2,A1,A0}=011
  126. 78                      };
  127. 79                  end
  128. 80              `I_DONE:                    //SDRAM初始化完成
  129. 81                      case(work_state)    //以下工作状态不执行任何指令
  130. 82                          `W_IDLE,`W_TRCD,`W_CL,`W_TWR,`W_TRP,`W_TRFC: begin
  131. 83                                  sdram_cmd_r <= `CMD_NOP;
  132. 84                                  sdram_ba    <= 2'b11;
  133. 85                                  sdram_addr  <= 13'h1fff;
  134. 86                              end
  135. 87                          `W_ACTIVE: begin//行有效指令
  136. 88                                  sdram_cmd_r <= `CMD_ACTIVE;
  137. 89                                  sdram_ba    <= sys_addr[23:22];
  138. 90                                  sdram_addr  <= sys_addr[21:9];
  139. 91                              end
  140. 92                          `W_READ: begin  //读操作指令
  141. 93                                  sdram_cmd_r <= `CMD_READ;
  142. 94                                  sdram_ba    <= sys_addr[23:22];
  143. 95                                  sdram_addr  <= {4'b0000,sys_addr[8:0]};
  144. 96                              end
  145. 97                          `W_RD: begin    //突发传输终止指令
  146. 98                                  if(`end_rdburst)
  147. 99                                      sdram_cmd_r <= `CMD_B_STOP;
  148. 100                                 else begin
  149. 101                                     sdram_cmd_r <= `CMD_NOP;
  150. 102                                     sdram_ba    <= 2'b11;
  151. 103                                     sdram_addr  <= 13'h1fff;
  152. 104                                 end
  153. 105                             end                             
  154. 106                         `W_WRITE: begin //写操作指令
  155. 107                                 sdram_cmd_r <= `CMD_WRITE;
  156. 108                                 sdram_ba    <= sys_addr[23:22];
  157. 109                                 sdram_addr  <= {4'b0000,sys_addr[8:0]};
  158. 110                             end     
  159. 111                         `W_WD: begin    //突发传输终止指令
  160. 112                                 if(`end_wrburst)
  161. 113                                     sdram_cmd_r <= `CMD_B_STOP;
  162. 114                                 else begin
  163. 115                                     sdram_cmd_r <= `CMD_NOP;
  164. 116                                     sdram_ba    <= 2'b11;
  165. 117                                     sdram_addr  <= 13'h1fff;
  166. 118                                 end
  167. 119                             end
  168. 120                         `W_PRE:begin    //预充电指令
  169. 121                                 sdram_cmd_r <= `CMD_PRGE;
  170. 122                                 sdram_ba    <= sys_addr[23:22];
  171. 123                                 sdram_addr  <= 13'h0000;
  172. 124                             end            
  173. 125                         `W_AR: begin    //自动刷新指令
  174. 126                                 sdram_cmd_r <= `CMD_A_REF;
  175. 127                                 sdram_ba    <= 2'b11;
  176. 128                                 sdram_addr  <= 13'h1fff;
  177. 129                             end
  178. 130                         default: begin
  179. 131                                 sdram_cmd_r <= `CMD_NOP;
  180. 132                                 sdram_ba    <= 2'b11;
  181. 133                                 sdram_addr  <= 13'h1fff;
  182. 134                             end
  183. 135                     endcase
  184. 136             default: begin
  185. 137                     sdram_cmd_r <= `CMD_NOP;
  186. 138                     sdram_ba    <= 2'b11;
  187. 139                     sdram_addr  <= 13'h1fff;
  188. 140                 end
  189. 141         endcase
  190. 142 end
  191. 143
  192. 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代表不关心):
第三十八章 SDRAM读写测试实验66879.png
图 38.4.6 SDRAM操作指令
SDRAM数据读写模块代码如下:
  1. 1   module sdram_data(
  2. 2       input             clk,              //系统时钟
  3. 3       input             rst_n,            //低电平复位信号
  4. 4   
  5. 5       input   [15:0]    sdram_data_in,    //写入SDRAM中的数据
  6. 6       output  [15:0]    sdram_data_out,   //从SDRAM中读取的数据
  7. 7       input   [ 3:0]    work_state,       //SDRAM工作状态寄存器
  8. 8       input   [ 9:0]    cnt_clk,          //时钟计数
  9. 9      
  10. 10      inout   [15:0]    sdram_data        //SDRAM数据总线
  11. 11      );
  12. 12  
  13. 13  `include "sdram_para.v"                 //包含SDRAM参数定义模块
  14. 14  
  15. 15  //reg define
  16. 16  reg        sdram_out_en;                //SDRAM数据总线输出使能
  17. 17  reg [15:0] sdram_din_r;                 //寄存写入SDRAM中的数据
  18. 18  reg [15:0] sdram_dout_r;                //寄存从SDRAM中读取的数据
  19. 19  
  20. 20  //*****************************************************
  21. 21  //**                    main code
  22. 22  //*****************************************************
  23. 23  
  24. 24  //SDRAM 双向数据线作为输入时保持高阻态
  25. 25  assign sdram_data = sdram_out_en ? sdram_din_r : 16'hzzzz;
  26. 26  
  27. 27  //输出SDRAM中读取的数据
  28. 28  assign sdram_data_out = sdram_dout_r;
  29. 29  
  30. 30  //SDRAM 数据总线输出使能
  31. 31  always @ (posedge clk or negedge rst_n) begin
  32. 32      if(!rst_n)
  33. 33      sdram_out_en <= 1'b0;
  34. 34  else if((work_state == `W_WRITE) | (work_state == `W_WD))
  35. 35      sdram_out_en <= 1'b1;            //向SDRAM中写数据时,输出使能拉高
  36. 36  else
  37. 37      sdram_out_en <= 1'b0;
  38. 38  end
  39. 39  
  40. 40  //将待写入数据送到SDRAM数据总线上
  41. 41  always @ (posedge clk or negedge rst_n) begin
  42. 42      if(!rst_n)
  43. 43          sdram_din_r <= 16'd0;
  44. 44      else if((work_state == `W_WRITE) | (work_state == `W_WD))
  45. 45          sdram_din_r <= sdram_data_in;   //寄存写入SDRAM中的数据
  46. 46  end
  47. 47  
  48. 48  //读数据时,寄存SDRAM数据线上的数据
  49. 49  always @ (posedge clk or negedge rst_n) begin
  50. 50      if(!rst_n)
  51. 51          sdram_dout_r <= 16'd0;
  52. 52      else if(work_state == `W_RD)
  53. 53          sdram_dout_r <= sdram_data;     //寄存从SDRAM中读取的数据
  54. 54  end
  55. 55  
  56. 56  endmodule
复制代码


SDRAM数据读写模块通过数据总线输出使能信号sdram_out_en控制SDRAM双向数据总线的输入输出,如程序第25行所示。同时根据工作状态机的状态,在写数据时将写入SDRAM中的数据送到SDRAM数据总线上,在读数据时寄存SDRAM数据总线上的数据。
图 38.4.7为SDRAM读写测试程序运行时SignalTap抓取的波形图,图中包含了一个完整的读周期,其中rd_valid为低时读数据无效,rd_valid为高时error_flag一直保持低电平,说明数据读写测试正确。
第三十八章 SDRAM读写测试实验69105.png
图 38.4.7 SignalTap波形图
完成SDRAM初始化后可对其进行仿真验证,利用SDRAM仿真模型和设计testbench文件可对设计的SDRAM初始化模块进行验证正确性。仿真需要用到是sim文件夹中的sdr.v和 sdr_parameters.h两个文件,sdr_parameters.h文件主要是包含SDRAM模型的一些全局化参数和宏定义。
testbench仿真文件代码如下:
  1. 1  `timescale 1ns/1ns
  2. 2  
  3. 3  module sdram_tb;
  4. 4  
  5. 5  //reg define
  6. 6  reg         clock_50m;     
  7. 7  reg         rst_n;         
  8. 8                             
  9. 9  //wire define              
  10. 10 wire        sdram_clk;     
  11. 11 wire        sdram_cke;     
  12. 12 wire        sdram_cs_n;   
  13. 13 wire        sdram_ras_n;   
  14. 14 wire        sdram_cas_n;   
  15. 15 wire        sdram_we_n;   
  16. 16 wire [ 1:0] sdram_ba;      
  17. 17 wire [12:0] sdram_addr;   
  18. 18 wire [15:0] sdram_data;   
  19. 19 wire [ 1:0] sdram_dqm;     
  20. 20                           
  21. 21 wire        led;           
  22. 22
  23. 23 //*****************************************************
  24. 24 //**                    main code
  25. 25 //*****************************************************
  26. 26
  27. 27 //初始化
  28. 28 initial begin
  29. 29   clock_50m = 0;
  30. 30   rst_n     = 0;                     
  31. 31   #100                                   
  32. 32   rst_n     = 1;
  33. 33 end
  34. 34
  35. 35 //产生50Mhz时钟
  36. 36 always #10 clock_50m = ~clock_50m;
  37. 37
  38. 38 //例化SDRAM读写测试模块
  39. 39 sdram_rw_test u_sdram_rw_test(
  40. 40     .clk            (clock_50m),         
  41. 41     .rst_n          (rst_n),            
  42. 42         
  43. 43     .sdram_clk      (sdram_clk),         
  44. 44     .sdram_cke      (sdram_cke),         
  45. 45     .sdram_cs_n     (sdram_cs_n),        
  46. 46     .sdram_ras_n    (sdram_ras_n),      
  47. 47     .sdram_cas_n    (sdram_cas_n),      
  48. 48     .sdram_we_n     (sdram_we_n),        
  49. 49     .sdram_ba       (sdram_ba),         
  50. 50     .sdram_addr     (sdram_addr),        
  51. 51     .sdram_data     (sdram_data),        
  52. 52     .sdram_dqm      (sdram_dqm),         
  53. 53     
  54. 54     .led            (led)               
  55. 55     );  
  56. 56     
  57. 57 //例化SDRAM仿真模型
  58. 58 sdr u_sdram(   
  59. 59     .Clk            (sdram_clk),         
  60. 60     .Cke            (sdram_cke),         
  61. 61     .Cs_n           (sdram_cs_n),        
  62. 62     .Ras_n          (sdram_ras_n),      
  63. 63     .Cas_n          (sdram_cas_n),      
  64. 64     .We_n           (sdram_we_n),        
  65. 65     .Ba             (sdram_ba),         
  66. 66     .Addr           (sdram_addr),        
  67. 67     .Dq             (sdram_data),        
  68. 68     .Dqm            (sdram_dqm)         
  69. 69     );
  70. 70     
  71. 71 endmodule
复制代码


38.5下载验证
首先我们将下载器与新起点开发板上的JTAG接口连接,下载器另外一端与电脑连接。然后连接开发板的电源,下载sof文件,现象如下图所示。
第三十八章 SDRAM读写测试实验71565.png
图 38.5.1 硬件连接
从上图中可以看到LED0常亮,说明SDRAM读写实验成功。
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

2

主题

9

帖子

0

精华

新手上路

积分
32
金钱
32
注册时间
2023-4-13
在线时间
16 小时
发表于 2023-4-23 15:01:29 | 显示全部楼层
回复 支持 反对

使用道具 举报

3

主题

2016

帖子

0

精华

资深版主

Rank: 8Rank: 8

积分
5622
金钱
5622
注册时间
2018-10-21
在线时间
1592 小时
发表于 2023-4-24 08:53:14 | 显示全部楼层
枯寂2 发表于 2023-4-23 15:01
求解为啥结果不对呀

哪里不对
回复 支持 反对

使用道具 举报

2

主题

9

帖子

0

精华

新手上路

积分
32
金钱
32
注册时间
2023-4-13
在线时间
16 小时
发表于 2023-4-26 20:54:43 | 显示全部楼层

就是输出的数据是只有偶数这种
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-11-26 09:36

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表