1)实验平台:正点原子新起点V2开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=609758951113 2)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-300792-1-1.html 3)对正点原子FPGA感兴趣的同学可以加群讨论:994244016 4)关注正点原子公众号,获取最新资料更新
SDRAM是一种可以指定任意地址进行读写的存储器,它具有存储容量大,读写速度快的特点,同时价格也相对低廉。因此,SDRAM常作为缓存,应用于数据存储量大,同时速度要求较高的场合,如复杂嵌入式设备的存储器等。本章我们将利用FPGA实现SDRAM控制器,并完成开发板上SDRAM芯片的读写测试。 本章包括以下几个部分: 3737.1简介 37.2实验任务 37.3硬件设计 37.4软件设计 37.5下载验证
38.1简介 SDRAM(Synchronous Dynamic Random Access Memory),同步动态随机存储器。同步是指内存工作需要同步时钟,内部的命令的发送与数据的传输都以它为基准;动态是指存储阵列需要不断的刷新来保证数据不丢失;随机是指数据不是线性依次存储,而是自由指定地址进行数据读写。 SDRAM具有空间存储量大、读写速度快、价格相对便宜等优点。然而由于SDRAM内部利用电容来存储数据,为保证数据不丢失,需要持续对各存储电容进行刷新操作;同时在读写过程中需要考虑行列管理、各种操作延时等,由此导致了其控制逻辑复杂的特点。 SDRAM的内部是一个存储阵列,你可以把它想象成一张表格。我们在向这个表格中写入数据的时候,需要先指定一个行(Row),再指定一个列(Column),就可以准确地找到所需要的“单元格”,这就是SDRAM寻址的基本原理。如图 38.1.1所示:
图 38.1.1 SDRAM寻址原理 上图中的“单元格”就是SDRAM存储芯片中的存储单元,而这个“表格”(存储阵列)我们称之为L-Bank。通常SDRAM的存储空间被划分为4个L-Bank,在寻址时需要先指定其中一个L-Bank,然后在这个选定的L-Bank中选择相应的行与列进行寻址(寻址就是指定存储单元地址的过程)。 对SDRAM的读写是针对存储单元进行的,对SDRAM来说一个存储单元的容量等于数据总线的位宽,单位是bit。那么SDRAM芯片的总存储容量我们就可以通过下面的公式计算出来: SDRAM总存储容量 = L-Bank的数量×行数×列数×存储单元的容量 SDRAM存储数据是利用了电容的充放电特性以及能够保持电荷的能力。一个大小为1bit的存储单元的结构如下图所示,它主要由行列选通三极管,存储电容,刷新放大器组成。行地址与列地址选通使得存储电容与数据线导通,从而可进行放电(读取)与充电(写入)操作。
图 38.1.2 SDRAM存储单元结构示意图 图 38.1.3是SDRAM的结构框图,从框图中我们可以看到SDRAM主要是由时钟缓冲器(CLK BUFFER)、指令解码器(COMMAND DECODER)、地址缓冲器(ADDRESS BUFFER)、模式寄存器(MODE REGISTER)、控制信号生成器(CONTROL SIGNAL GENERATOR)、四个存储BANK(BANK#0~ BANK#3)和数据控制电路(DATA CONTROL CIRCUIT)构成。SDRAM接收外部输入的控制命令,并在指令解码器的控制下进行寻址、读写、刷新、预充电等操作。
图 38.1.3 SDRAM功能框图 下面我们就来详细的分析一下SDRAM的结构框图,首先来看时钟缓冲器和指令解码器,它俩构成了整个SDRAM的核心控制器,我们可以通过这个核心控制器给SDRAM发送指令,让SDRAM执行对应的功能。从上图中我们可以看到时钟缓冲器和指令解码器共有六根线,一根时钟线(CLK),主要是给整个SDRAM提供工作时钟;一根时钟使能线(CKE),当CKE为低电平时整个SDRAM进入休眠模式,所有指令无效,当CKE为高电平时SDRAM才能进入正常工作模式;四根指令线(CS、WE、CAS、RAS),其中CS是SDRAM芯片的片选信号,低电平有效。WE是读写指令切换信号,低电平表示写指令,高电平表示读指令。RAS和CAS分别是行地址选通信号和列地址选通信号,都是低电平有效(SDRAM的行列地址线是复用的所以需要使用RAS和CAS来区分地址是行地址还是列地址)。我们驱动SDRAM的时候就用这四根指令线组合成不同的指令发送给SDRAM,SDRAM正确接收到指令后就会执行对应的功能。具体的指令组合如下图所示:
图 38.1.4 SDRAM指令一览表 从上图中可以看到指令组合还是非常多的,但是并不是每一个指令我们都会用到,具体使用了哪些指令我们在下文程序设计部分会给大家详细的讲解。 讲完了功能框图中的时钟缓冲器和指令解码器后我们接着来看ADRAM的地址缓冲器,SDRAM共有13根行列地址线(A0A12)和两根BANK地址线(BS0BS1),其中BANK地址线主要用来定位BANK 地址,整片SDRAM共有四个BANK,例如我现在想要对BANK1进行读写操作那么就可以将BANK地址线设置为“2’b01”;13根行列地址线是用来定位行地址和列地址的,这里尤其需要注意的是地址线10,它还参与了自动预充电的指令,当我们在进行读写指令操作的时候拉高A10可以让SDRAM进入自动预充电模式,具体的操作在下文预充电操作内容部分会有详细的讲解。 看完地址缓冲器后我们再来看看模式寄存器,模式寄存器是SDRAM非常重要的控制器,在对SDRAM上电初始化的时候就要配置模式寄存器,主要是配置突发长度、突发传输方式、CAS潜伏期和操作模式。一旦配置完成,之后SDRAM就会以配置好的模式去运行,具体如何配置,下文会有详细讲解。 最后我们再来看看控制信号生成器(CONTROL SIGNAL GENERATOR)、四个存储BANK(BANK#0~ BANK#3)和数据控制电路(DATA CONTROL CIRCUIT),这几个部分功能就比较简单了,控制信号生成器主要是把指令和模式寄存器的相关配置生成对应的控制信号,用来控制SDRAM的存储BANK,而存储BANK就更不用说了,就是用来存储数据的存储容器;数据控制电路主要就是用来接收和发送数据的,需要注意的是它可以配合数据掩码器(DQM)来使用,在吞吐数据的过程中可以把一些我们不想要的数据给屏蔽掉。 对SDRAM的整体结构就给大家简单的讲解到这了,如果大家想了解的更细致也可以去阅读SDRAM的数据手册,下面我们来讲解SDRAM的几个重要操作。 1、芯片初始化 SDRAM芯片上电之后需要一个初始化的过程,以保证芯片能够按照预期方式正常工作,初始化流程如图 38.1.5所示:
图 38.1.5 SDRAM初始化 SDRAM上电后要有200us的输入稳定期,在这个时间内不可以对SDRAM的接口做任何操作;200us结束以后给所有Bank预充电,然后是连续8次刷新操作,最后设置模式寄存器。初始化最关键的阶段就在于模式寄存器(MR,Mode Register)的设置,简称MRS(MR Set)。
图 38.1.6 模式寄存器 如上图所示,用于配置模式寄存器的参数由地址线提供,地址线不同的位分别用于表示不同的参数。SDRAM通过配置模式寄存器来确定芯片的工作方式,包括突发长度(Burst Length)、潜伏期(CAS Latency)以及操作模式等。其时序图如下所示:
图 38.1.7 配置模式寄存器 从上图中可以看到配置模式寄存器还是比较简单的,只需要将四根指令线全部拉低即发送配置模式寄存器指令(4’b0000),同时在地址线上给出对应的配置数值就可以了。然后需要注意的是,在模式寄存器设置指令发出之后,需要等待一段时间才能够向SDRAM发送新的指令,这个时间我们称之为模式寄存器设置周期tRSC(Register Set Cycle),查看数据手册我们可以知道tRSC至少要2个时钟周期的时间。 2、行激活 初始化完成后,无论是读操作还是写操作,都要先激活(Active)SDRAM中的一行,使之处于活动状态(又称行有效)。在此之前还要进行SDRAM芯片的片选和Bank的定址,不过它们与行激活可以同时进行。
图 38.1.8 行激活时序图 从上图可以看出,在片选CS#(#表示低电平有效)、Bank定址的同时,RAS(Row Address Strobe,行地址选通脉冲)也处于有效状态。此时An地址线则发送具体的行地址。我们板载的SDRAM共有共有13个地址线,由于是二进制表示法,所以共有8192个行(2^13=4096),A0-A12的不同数值就确定了具体的行地址。由于行激活的同时也会激活相应的Bank,所以行激活也可称为Bank有效。 3、列读写 行地址激活之后,就要对列地址进行寻址了。由于在SDRAM中,地址线是行列共用的,因此列寻址时地址线仍然是A0-A12。在寻址时,利用RAS(Row Address Strobe,行地址选通脉冲)与CAS(Column Address Strobe,列地址选通脉冲)来区分行寻址与列寻址,简单的来讲就是RAS拉低CAS拉高代表行地址,反过来就代表列地址,如图 38.1.9所示。还有一点很重要就是在列寻址的时候地址线A10可以用来控制是否进行预充电(列地址最大只能用到地址线第9位,所以不用担心A10会不会和列地址冲突),因为已经都进入列寻址了那么此时就可以发送读写指令进行读写操作了,但是读写操作结束后我想读写下一行的时候就必须先进行预充电(关于预充电下文会有详细描述),因此我们一般会在列寻址的时候直接拉高A10地址线,这样每完成一次读写操作就会自动进行一次预充电,这样读写下一行之前就不用再特地去进行预充电操作了。 另外,列寻址信号与读写命令是同时发出的,读/写命令是通过WE(Write Enable,写使能)信号来控制的,WE为低时是写命令,为高时是读命令。
图 38.1.9 列选通与读操作时序图 然而,在发送列读写命令时必须要与行激活命令有一个时间间隔(其实就是行激活命令和列激活命令之间要间隔一段时间),这个间隔被定义为tRCD,即RAS to CAS Delay(RAS至CAS延迟)。这是因为在行激活命令发出之后,芯片存储阵列电子元件响应需要一定的时间。tRCD是SDRAM的一个重要时序参数,广义的tRCD以时钟周期(tCK,Clock Time)数为单位,比如tRCD=3,就代表RAS至CAS延迟为三个时钟周期,如图 38.1.10所示。具体到确切的时间,则要根据时钟频率而定。
图 38.1.10 tRCD = 3时序图 4、 数据输出(读) 在选定列地址后,就已经确定了具体的存储单元,剩下的事情就是数据通过数据I/O通道(DQ)输出到内存总线上了。但是在CAS发出之后,仍要经过一定的时间才能有数据输出,从CAS与读取命令发出到第一笔数据输出的这段时间,被定义为CL(CAS Latency,CAS潜伏期)。CL时间越短,读数据时SDRAM响应就越快。由于CL只在读取时出现,所以CL又被称为读取潜伏期(RL,Read Latency)。CL的单位与tRCD一样,为时钟周期数,具体耗时由时钟频率决定。
图 38.1.11 CL = 2 时序图 5、数据输入(写) 数据写入的操作也是在tRCD之后进行,但此时没有了CL(记住,CL只出现在读取操作中),行寻址与列寻址的时序图和上文一样,只是在列寻址时,WE#为有效状态。
图 38.1.12 数据写入的时序图 从上图中可见,数据与写指令同时发送。不过,数据并不是即时地写入存储单元,数据的真正写入需要一定的周期。为了保证数据的可靠写入,都会留出足够的写入/校正时间(tWR,Write Recovery Time),这个操作也被称作写回(Write Back)。tWR至少占用一个时钟周期或再多一点(时钟频率越高,tWR占用周期越多)。 6、突发长度 突发(Burst)是指在同一行中相邻的存储单元连续进行数据传输的方式,连续传输所涉及到存储单元(列)的数量就是突发长度(Burst Lengths,简称BL)。 上文讲到的读/写操作,都是一次对一个存储单元进行寻址。然而在现实中很少只对SDRAM中的单个存储空间进行读写,一般都需要完成连续存储空间中的数据传输。在连续读/写操作时,为了对当前存储单元的下一个单元进行寻址,需要不断的发送列地址与读/写命令(行地址不变,所以不用再对行寻址),如图 38.1.13所示:
图 38.1.13 非突发连续读操作 由上图可知,虽然由于读延迟相同可以让数据的传输在I/O端是连续的,但它占用了大量的内存控制资源,在数据进行连续传输时无法输入新的命令,效率很低。为此,人们开发了突发传输技术,只要指定起始列地址与突发长度,内存就会依次地自动对后面相应数量的存储单元进行读/写操作而不再需要控制器连续地提供列地址。这样,除了第一笔数据的传输需要若干个周期(主要是之前的延迟,一般的是tRCD+CL)外,其后每个数据只需一个周期的延时即可获得。如图 38.1.14所示:
图 38.1.14 突发连续读操作 至于BL的数值,也是不能随便设或在数据进行传输前临时决定。在上文讲到的初始化过程中的模式寄存器配置(MRS)阶段就要对BL进行设置。突发长度(BL)可以为1、2、4、8和“全页(Full Page)”,其中“全页”是指突发传输一整行的数据量。 另外,在MRS阶段除了要设定BL数值之外,还需要确定“读/写操作模式”以及“突发传输模式”。读/写操作模式分为“突发读/突发写”和“突发读/单一写”。 突发读/突发写表示读和写操作都是突发传输的,每次读/写操作持续BL所设定的长度,这也是常规的设定。突发读/单一写表示读操作是突发传输,写操作则只是一个个单独进行。 突发传输模式代表着突发周期内所涉及到的存储单元的传输顺序。顺序传输是指从起始单元开始顺序读取。假如BL=4,起始存储单元编号是n,突发传输顺序就是n、n+1、n+2、n+3。交错传输就是打乱正常的顺序进行数据传输(比如第一个进行传输的单元是n,而第二个进行传输的单元是n+2而不是n+1)。由于交错传输很少用到,它的传输规则在这里就不详细介绍了,大家可以参考所选用的SDRAM芯片手册。 7、预充电 在对SDRAM某一存储地址进行读写操作结束后,如果要对同一L-Bank的另一行进行寻址,就要将原来有效(工作)的行关闭,重新发送行/列地址。L-Bank关闭现有工作行,准备打开新行的操作就是预充电(Precharge)。在读写过程中,工作行内的存储体由于“行激活”而使存储电容受到干扰,因此在关闭工作行前需要对本行所有存储体进行重写。预充电实际上就是对工作行中所有存储体进行数据重写,并对行地址进行复位,以准备新行工作的过程。 预充电可以通过命令控制,也可以通过辅助设定让芯片在每次读写操作之后自动进行预充电。现在我们再回过头看看读写操作时的命令时序图(图 38.1.9),从中可以发现地址线A10控制着是否进行在读写之后对当前L-Bank自动进行预充电,这就是上文所说的“辅助设定”。而在单独的预充电命令中,A10则控制着是对指定的L-Bank还是所有的L-Bank(当有多个L-Bank处于有效/活动状态时)进行预充电,前者需要提供L-Bank的地址,后者只需将A10信号置于高电平。 在发出预充电命令之后,要经过一段时间才能发送行激活命令打开新的工作行,这个间隔被称为tRP(Precharge command Period,预充电有效周期),如图 38.1.15所示。和tRCD、CL一样,tRP的单位也是时钟周期数,具体值视时钟频率而定。
图 38.1.15 读取时预充电时序图(CL=2、BL=4、tRP=2) 自动预充电的开始时间与上图一样,只是没有了单独的预充电命令,并在发出读取命令时,A10地址线要设为高电平(允许自动预充电)。可见控制好预充电启动时间很重要,它可以在读取操作结束后立刻进入新行的寻址,保证运行效率。 写操作时,由于每笔数据的真正写入则需要一个足够的周期来保证,这段时间就是写回周期(tWR)。所以预充电不能与写操作同时进行,必须要在tWR之后才能发出预充电命令,以确保数据的可靠写入,否则重写的数据可能出错,如图 38.1.16所示。
图 38.1.16 写入时预充电时序图(BL=4、tWR=1、tRP=2) 8、刷新 SDRAM之所以称为同步“动态”随机存储器,就是因为它要不断进行刷新(Refresh)才能保留住数据,因此刷新是SDRAM最重要的操作。 刷新操作与预充电类似,都是重写存储体中的数据。但为什么有预充电操作还要进行刷新呢?因为预充电是对一个或所有L-Bank中的工作行(处于激活状态的行)操作,并且是不定期的;而刷新则是有固定的周期,并依次对所有行进行操作,以保留那些久久没经历重写的存储体中的数据。但与所有L-Bank预充电不同的是,这里的行是指所有L-Bank中地址相同的行,而预充电中各L-Bank中的工作行地址并不是一定是相同的。 那么要隔多长时间重复一次刷新呢?目前公认的标准是,存储体中电容的数据有效保存期上限是64ms,也就是说每一行刷新的循环周期是64ms。我们在看SDRAM芯片参数时,经常会看到4096 Refresh Cycles/64ms或8192 Refresh Cycles/64ms的标识,这里的4096与8192就代表这个芯片中每个L-Bank的行数。刷新命令一次仅对一行有效,也就是说在64ms内这两种规格的芯片分别需要完成4096次和8192次刷新操作。因此,L-Bank为4096行时刷新命令的发送间隔为15.625μs(64ms/4096),8192行时为7.8125μs(64ms/8192)。 刷新操作分为两种:自动刷新(Auto Refresh,简称AR)与自刷新(Self Refresh,简称SR)。不论是何种刷新方式,都不需要外部提供行地址信息,因为这是一个内部的自动操作。对于自动刷新(AR),SDRAM内部有一个行地址生成器(也称刷新计数器)用来自动生成行地址。由于刷新是针对一行中的所有存储体进行,所以无需列寻址,或者说CAS在RAS之前有效。所以,AR又称CBR(CAS Before RAS,列提前于行定位)式刷新。 在自动刷新过程中,所有L-Bank都停止工作。每次刷新操作所需要的时间为自动刷新周期(tRC),在自动刷新指令发出后需要等待tRC才能发送其他指令。64ms之后再次对同一行进行刷新操作,如此周而复始进行循环刷新。显然,刷新操作肯定会对SDRAM的性能造成影响,但这是没办法的事情,也是DRAM相对于SRAM(静态内存,无需刷新仍能保留数据)取得成本优势的同时所付出的代价。 自刷新(SR)主要用于休眠模式低功耗状态下的数据保存。在发出AR命令时,将CKE置于无效状态,就进入了SR模式,此时不再依靠系统时钟工作,而是根据内部的时钟进行刷新操作。在SR期间除了CKE之外的所有外部信号都是无效的(无需外部提供刷新指令),只有重新使CKE有效才能退出自刷新模式并进入正常工作状态。 9、数据掩码 在讲述读/写操作时,我们谈到了突发长度。如果BL=4,那么也就是说一次就传送4笔数据。但是,如果其中的第二笔数据是不需要的,怎么办?还要传输吗?为了屏蔽不需要的数据,人们采用了数据掩码(Data I/O Mask,简称DQM)技术。通过DQM,内存可以控制I/O端口取消哪些输出或输入的数据。为了精确屏蔽一个数据总线位宽中的每个字节,每个DQM信号线对应一个字节(8bit)。因此,对于数据总线为16bit的SDRAM芯片,就需要两个DQM引脚。 SDRAM官方规定,在读取时DQM发出两个时钟周期后生效,如图 38.1.17所示。而在写入时,DQM与写入命令一样是立即成效,如图 38.1.18所示。
图 38.1.17 读取时DQM信号时序图
图 38.1.18 写入时DQM信号时序图 38.2实验任务 本节实验任务是向新起点开发板上的SDRAM中写入1024个数据,从SDRAM存储空间的起始地址写起,写完后再将数据读出,并验证读出数据是否正确。 38.3硬件设计 SDRAM的原理图如图 38.3.1所示。
图 38.3.1 SDRAM原理图 新起点开发板上的SDRAM芯片型号为W9825G6DH-6,内部分为4个L-Bank,行地址为13位,列地址为9位,数据总线位宽为16bit。故该SDRAM总的存储空间为4×(213)×(29)×16 bit = 256Mbit,即32MB。 W9825G6DH-6工作时钟频率最高可达166MHz,潜伏期(CAS Latency)可选为2或3,突发长度支持1、2、4、8或全页,64ms内需要完成8K次刷新操作。其他时序参数请大家参考该芯片的数据手册。 本实验中,各端口信号的管脚分配如下表所示: 表 38.3.1 SDRAM读写测试实验管脚分配
38.4程序设计 在本次实验中,由于SDRAM的控制时序较为复杂,为方便用户调用,我们将SDRAM控制器封装成FIFO接口,这样我们操作SDRAM就像读写FIFO一样简单。整个系统的功能框图如图 38.4.1所示:
图 38.4.1 SDRAM读写测试系统框图 PLL时钟模块:本实验中SDRAM读写测试及LED显示模块输入时钟均为50MHz,而SDRAM控制器工作在100MHz时钟频率下,另外还需要一个输出给SDRAM芯片的100MHz时钟。因此需要一个PLL时钟模块用于产生系统各个模块所需的时钟。 SDRAM测试模块:产生测试数据及读写使能,写使能将1024个数据(1~1024)写入SDRAM,写操作完成后读使能拉高,持续进行读操作,并检测读出的数据是否正确。 FIFO控制模块:作为SDRAM控制器与用户的交互接口,该模块在写FIFO中的数据量到达用户指定的突发长度后将数据自动写入SDRAM;并在读FIFO中的数据量小于突发长度时将SDRAM中的数据读出。 SDRAM控制器:负责完成外部SDRAM存储芯片的初始化、读写及刷新等一系列操作。 LED显示模块:通过控制LED灯的显示状态来指示SDRAM读写测试结果。 由系统框图可知,FPGA顶层例化了以下四个模块:PLL时钟模块(pll_clk)、SDRAM测试模块(sdram_test)、LED灯指示模块(led_disp)以及SDRAM控制器顶层模块(sdram_top)。各模块端口及信号连接如图 38.4.2所示:
图 38.4.2 顶层模块原理图 SDRAM测试模块(sdram_test)输出读写使能信号及写数据,通过SDRAM控制器将数据写入SDARM中地址为0~1023的存储空间中。在写过程结束后进行读操作,检测读出的数据是否与写入数据一致,检测结果由标志信号error_flag指示。LED显示模块根据error_flag的值驱动LED以不同的状态显示。当SDRAM读写测试正确时,LED灯常亮;读写测试结果不正确时,LED灯闪烁。 顶层模块的代码如下:
1 module sdram_rw_test(
2 input clk, //FPGA外部时钟,50M
3 input rst_n, //按键复位,低电平有效
4 //SDRAM 芯片接口
5 output sdram_clk, //SDRAM 芯片时钟
6 output sdram_cke, //SDRAM 时钟有效
7 output sdram_cs_n, //SDRAM 片选
8 output sdram_ras_n, //SDRAM 行有效
9 output sdram_cas_n, //SDRAM 列有效
10 output sdram_we_n, //SDRAM 写有效
11 output [ 1:0] sdram_ba, //SDRAM Bank地址
12 output [12:0] sdram_addr, //SDRAM 行/列地址
13 inout [15:0] sdram_data, //SDRAM 数据
14 output [ 1:0] sdram_dqm, //SDRAM 数据掩码
15 //LED
16 output led //状态指示灯
17 );
18
19 //wire define
20 wire clk_50m; //SDRAM 读写测试时钟
21 wire clk_100m; //SDRAM 控制器时钟
22 wire clk_100m_shift; //相位偏移时钟
23
24 wire wr_en; //SDRAM 写端口:写使能
25 wire [15:0] wr_data; //SDRAM 写端口:写入的数据
26 wire rd_en; //SDRAM 读端口:读使能
27 wire [15:0] rd_data; //SDRAM 读端口:读出的数据
28 wire sdram_init_done; //SDRAM 初始化完成信号
29
30 wire locked; //PLL输出有效标志
31 wire sys_rst_n; //系统复位信号
32 wire error_flag; //读写测试错误标志
33
34 //*****************************************************
35 //** main code
36 //*****************************************************
37
38 //待PLL输出稳定之后,停止系统复位
39 assign sys_rst_n = rst_n & locked;
40
41 //例化PLL, 产生各模块所需要的时钟
42 pll_clk u_pll_clk(
43 .inclk0 (clk),
44 .areset (~rst_n),
45
46 .c0 (clk_50m),
47 .c1 (clk_100m),
48 .c2 (clk_100m_shift),
49 .locked (locked)
50 );
51
52 //SDRAM测试模块,对SDRAM进行读写测试
53 sdram_test u_sdram_test(
54 .clk_50m (clk_50m),
55 .rst_n (sys_rst_n),
56
57 .wr_en (wr_en),
58 .wr_data (wr_data),
59 .rd_en (rd_en),
60 .rd_data (rd_data),
61
62 .sdram_init_done (sdram_init_done),
63 .error_flag (error_flag)
64 );
65
66 //利用LED灯指示SDRAM读写测试的结果
67 led_disp u_led_disp(
68 .clk_50m (clk_50m),
69 .rst_n (sys_rst_n),
70 //SDRAM初始化失败或者读写错误都认为是实验失败
71 .error_flag (~sdram_init_done||error_flag),
72 .led (led)
73 );
74
75 //SDRAM 控制器顶层模块,封装成FIFO接口
76 //SDRAM 控制器地址组成: {bank_addr[1:0],row_addr[12:0],col_addr[8:0]}
77 sdram_top u_sdram_top(
78 .ref_clk (clk_100m), //sdram 控制器参考时钟
79 .out_clk (clk_100m_shift), //用于输出的相位偏移时钟
80 .rst_n (sys_rst_n), //系统复位
81
82 //用户写端口
83 .wr_clk (clk_50m), //写端口FIFO: 写时钟
84 .wr_en (wr_en), //写端口FIFO: 写使能
85 .wr_data (wr_data), //写端口FIFO: 写数据
86 .wr_min_addr (24'd0), //写SDRAM的起始地址
87 .wr_max_addr (24'd1024), //写SDRAM的结束地址
88 .wr_len (10'd512), //写SDRAM时的数据突发长度
89 .wr_load (~sys_rst_n), //写端口复位: 复位写地址,清空写FIFO
90
91 //用户读端口
92 .rd_clk (clk_50m), //读端口FIFO: 读时钟
93 .rd_en (rd_en), //读端口FIFO: 读使能
94 .rd_data (rd_data), //读端口FIFO: 读数据
95 .rd_min_addr (24'd0), //读SDRAM的起始地址
96 .rd_max_addr (24'd1024), //读SDRAM的结束地址
97 .rd_len (10'd512), //从SDRAM中读数据时的突发长度
98 .rd_load (~sys_rst_n), //读端口复位: 复位读地址,清空读FIFO
99
100 //用户控制端口
101 .sdram_read_valid (1'b1), //SDRAM 读使能
102 .sdram_init_done (sdram_init_done), //SDRAM 初始化完成标志
103
104 //SDRAM 芯片接口
105 .sdram_clk (sdram_clk), //SDRAM 芯片时钟
106 .sdram_cke (sdram_cke), //SDRAM 时钟有效
107 .sdram_cs_n (sdram_cs_n), //SDRAM 片选
108 .sdram_ras_n (sdram_ras_n), //SDRAM 行有效
109 .sdram_cas_n (sdram_cas_n), //SDRAM 列有效
110 .sdram_we_n (sdram_we_n), //SDRAM 写有效
111 .sdram_ba (sdram_ba), //SDRAM Bank地址
112 .sdram_addr (sdram_addr), //SDRAM 行/列地址
113 .sdram_data (sdram_data), //SDRAM 数据
114 .sdram_dqm (sdram_dqm) //SDRAM 数据掩码
115 );
116
117 endmodule
顶层模块中主要完成对其余模块的例化,需要注意的是由于SDRAM工作时钟频率较高,且对时序要求比较严格,考虑到FPGA内部以及开发板上的走线延时,为保证SDRAM能够准确的读写数据,我们输出给SDRAM芯片的100MHz时钟相对于SDRAM控制器时钟有一个相位偏移。程序中的相位偏移时钟为clk_100m_shift(第48行),相位偏移量在这里设置为-75deg。 由于SDRAM控制器被封装成FIFO接口,在使用时只需要像读写FIFO那样给出读/写使能即可,如代码82~98行所示。同时控制器将SDRAM的阵列地址映射为线性地址,在调用时将其当作连续存储空间进行读写。因此读写过程不需要指定Bank地址及行列地址,只需要给出起始地址和结束地址即可,数据在该地址空间中连续读写。线性地址的位宽为SDRAM的Bank地址、行地址和列地址位宽的总和,也可以理解成线性地址的组成结构为{ bank_addr[1:0], row_addr[12:0], col_addr[8:0]}。 程序第88行及第92行指定SDRAM控制器的数据突发长度,由于W9825G6DH-6的全页突发长度为512,因此控制器的突发长度不能大于512。 SDRAM读写测试模块的代码如下所示:
1 module sdram_test(
2 input clk_50m, //时钟
3 input rst_n, //复位,低有效
4
5 output reg wr_en, //SDRAM 写使能
6 output reg [15:0] wr_data, //SDRAM 写入的数据
7 output reg rd_en, //SDRAM 读使能
8 input [15:0] rd_data, //SDRAM 读出的数据
9
10 input sdram_init_done, //SDRAM 初始化完成标志
11 output reg error_flag //SDRAM 读写测试错误标志
12 );
13
14 //reg define
15 reg init_done_d0; //寄存SDRAM初始化完成信号
16 reg init_done_d1; //寄存SDRAM初始化完成信号
17 reg [10:0] wr_cnt; //写操作计数器
18 reg [10:0] rd_cnt; //读操作计数器
19 reg rd_valid; //读数据有效标志
20
21 //*****************************************************
22 //** main code
23 //*****************************************************
24
25 //同步SDRAM初始化完成信号
26 always @(posedge clk_50m or negedge rst_n) begin
27 if(!rst_n) begin
28 init_done_d0
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?