超级版主
- 积分
- 4672
- 金钱
- 4672
- 注册时间
- 2019-5-8
- 在线时间
- 1224 小时
|
本帖最后由 正点原子运营 于 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
第三十八章 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根行列地址线(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所示:
图 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×(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约束如下所示:
- set_location_assignment PIN_B14 -to sdram_clk
- set_location_assignment PIN_G11 -to sdram_ba[0]
- set_location_assignment PIN_F13 -to sdram_ba[1]
- set_location_assignment PIN_J12 -to sdram_cas_n
- set_location_assignment PIN_F16 -to sdram_cke
- set_location_assignment PIN_K11 -to sdram_ras_n
- set_location_assignment PIN_J13 -to sdram_we_n
- set_location_assignment PIN_K10 -to sdram_cs_n
- set_location_assignment PIN_J14 -to sdram_dqm[0]
- set_location_assignment PIN_G15 -to sdram_dqm[1]
- set_location_assignment PIN_F11 -to sdram_addr[0]
- set_location_assignment PIN_E11 -to sdram_addr[1]
- set_location_assignment PIN_D14 -to sdram_addr[2]
- set_location_assignment PIN_C14 -to sdram_addr[3]
- set_location_assignment PIN_A14 -to sdram_addr[4]
- set_location_assignment PIN_A15 -to sdram_addr[5]
- set_location_assignment PIN_B16 -to sdram_addr[6]
- set_location_assignment PIN_C15 -to sdram_addr[7]
- set_location_assignment PIN_C16 -to sdram_addr[8]
- set_location_assignment PIN_D15 -to sdram_addr[9]
- set_location_assignment PIN_F14 -to sdram_addr[10]
- set_location_assignment PIN_D16 -to sdram_addr[11]
- set_location_assignment PIN_F15 -to sdram_addr[12]
- set_location_assignment PIN_P14 -to sdram_data[0]
- set_location_assignment PIN_M12 -to sdram_data[1]
- set_location_assignment PIN_N14 -to sdram_data[2]
- set_location_assignment PIN_L12 -to sdram_data[3]
- set_location_assignment PIN_L13 -to sdram_data[4]
- set_location_assignment PIN_L14 -to sdram_data[5]
- set_location_assignment PIN_L11 -to sdram_data[6]
- set_location_assignment PIN_K12 -to sdram_data[7]
- set_location_assignment PIN_G16 -to sdram_data[8]
- set_location_assignment PIN_J11 -to sdram_data[9]
- set_location_assignment PIN_J16 -to sdram_data[10]
- set_location_assignment PIN_J15 -to sdram_data[11]
- set_location_assignment PIN_K16 -to sdram_data[12]
- set_location_assignment PIN_K15 -to sdram_data[13]
- set_location_assignment PIN_L16 -to sdram_data[14]
- set_location_assignment PIN_L15 -to sdram_data[15]
- set_location_assignment PIN_D11 -to led
- set_location_assignment PIN_M1 -to rst_n
- set_location_assignment PIN_M2 -to clk
复制代码
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 //预充电状态
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)作了简单介绍,由此我们可以画出初始化状态机的状态转换图如下所示:
图 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读写实验成功。 |
|