SDRAM发展至今已历经五代,具有单位存储量大、高数据带宽、读写速度快、价格相对便宜等优点。同时,作为内存条中不可缺少的有一部分,SDRAM在计算机领域也占有一席之地。
(SDRAM的内容以及操作时序比较复杂,本文已经尽可能不影响阅读的情况下精简内容,请各位看官静下心细嗦,就可以熟练操作SDRAM了)
1 理论介绍
1.1 SDRAM基本概念
SDRAM英文全称“Synchronous Dynamic Random Access Memory”,译为“同步动态随机存取内存”或“同步动态随机存储器”,是动态随机存储器(Dynamic Random Access Memory,简称DRAM)家族的一份子。同步、动态、随机是其性能特点的外在说明,也是其区别其他存储器的特色标签。
同步(Synchronous):与通常的异步DRAM不同, SDRAM存在一个同步接口,其工作时钟的时钟频率与对应控制器(CPU/FPGA)的时钟频率相同,并且SDRAM内部的命令发送与数据传输均以此时钟为基准,实现指令或数据的同步操作;
动态(Dynamic): SDRAM需要不断的刷新来保证存储阵列内数据不丢失;
随机(Random):数据在SDRAM中并不是按照线性依次存储,而是可以自由指定地址进行数据的读写。
空间存储量大、读写速度快以及价格相对便宜等优点使其在存储界屹立不倒、经久不衰,广泛应用在计算机中。随着时代的不断发展、技术的不断更新,SDRAM使用至今已过数十载,产品更新历经五代,分别是:第一代SDR SDRAM,第二代DDR SDRAM,第三代DDR2 SDRAM,第四代DDR3 SDRAM,第五代,DDR4 SDRAM。
第一代SDR SDRAM采用单端时钟信号,SDRAM只在时钟的上升沿进行数据采样;而后面的四代SDRAM由于工作频率比较快,所以采用可降低干扰的差分时钟信号作为同步时钟,双沿采样,速度更快,且功耗更低。同时技术的不断发展、制造工艺的不断提高,使得五代SDRAM的更新过程中,集成度越来越高、内核电压越来越低(SDR:3.3V 、DDR:2.5V、DDR2:1.8V、DDR3:1.5V、DDR4:1.2V),这也是SDRAM速度提高、功耗降低的重要原因。
经历多次更新换代,使得SDRAM速度越来越快,功耗越来越低,性能更加优良,但其内部架构相差无几。后文中的相关讲解与设计实验均是以第一代SDR SDRAM为例,读者若对五代SDRAM有了解兴趣,可自行查阅相关资料。
任何事物的存在都具有有两面性,SDRAM因其空间存储量大、读写速度快以及价格相对便宜等优点使SDRAM在存储界占有一席之地,但由于SDRAM需要不断刷新来保证数据的可靠性,以及行列地址线分时复用等原因,使其对操作时序的要求较为严格,进而导致控制逻辑较为复杂。
1.2 SDRAM存储原理
SDRAM内部可以理解为一个存储阵列,这是SDRAM区别于管道式存储,实现随机地址存取的结构特点。我们将SDRAM内部存储阵列类比于一张表格,表格中的每一个单元格可以类比为存储阵列的单个存储单元。若想要实现存储阵列中的某一存储单元的数据读写操作,我们要通过行地址(Row Address)和列地址(Column Address)(先行后列)精确定位到这一存储单元,进而进行数据的读写操作,这就是所谓的随机地址存取。SDRAM存储阵列类比图,具体见图:
对于SDRAM,我们将类比于单元格的存储空间称之为存储单元,N(行列个数乘积)个存储单元构成一个存储阵列,这个存储阵列我们称之为一个逻辑Bank(Logical Bank,简称L-Bank、Bank)。SDRAM内部并不是一个全容量的L-Bank,而是分割为若干个L- Bank,目前大多为4个。若干L-Bank的分割,原因有二,一是技术、成本等诸多因素;二是由于SDRAM的工作原理限制,单一L-Bank可能会造成非常严重的寻址冲突,大幅度降低内存效率。
在对SDRAM进行数据存取时,要先确定L-Bank地址,定位到指定逻辑Bank,再依次确定行地址和列地址,选中存储单元,进而进行数据的存取操作,而且一次只能对一个L-Bank的一个存储单元进行操作。
SDRAM的基本存储单位是存储单元,而一个存储单元的容量为若干个Bit,对于SDRAM而言就是芯片的位宽,每个Bit存放于一个单独的存储体中,存储体是利用电容能够保持电荷以及可充放电的特性制成,主要由行选通三极管、列选通三极管、存储电容以及刷新放大器构成。电容所存储的电荷会随时间慢慢释放,这就需要不断刷新为电容充电,以保证存储数据可靠性。存储体示意图,具体见图:
将每个存储单元简化为单Bit的存储体,再将若干存储体排列为矩阵,同一行将行地址线相连,同一列将列地址线相连,就构成了一个存储阵列的简化模型。SDRAM内部存储阵列的简化模型,具体见图 :
1.3 SDRAM器件引脚说明
SDRAM有着较为复杂的操作时序,且输入输出信号较多,在进行SDRAM操作指令和操作时序之前,先对芯片的输入输出引脚、引脚功能以及内部功能框图做一下介绍,SDRAM引脚示意图,具体见图:
注:x4、x8、x16分别表示位宽4bit、8bit、16bit;#符号表示信号为低电平有效;短划线 - 表示x8和x4引脚功能与x16引脚功能相同。
由于SDRAM在容量、位宽以及生产厂家存在差异,所以SDRAM在Bank地址、地址总线、数据总线和数据掩码可能存在位宽的差异,但各输入输出管脚的名称和实现功能并无出入。上图已较为详细展示不同位宽的SDRAM 芯片的引脚示意图,针对其中较为重要的输入输出引脚,以镁光公司生产的、容量为4 Meg x 16 x 4 Banks的SDRAM芯片为例,对其做一下功能介绍。SDRAM引脚功能描述,具体见表格。
引脚 | 位宽类型 | 功能描述 | |
CLK | 1Bit | Input | 系统时钟:SDRAM由系统时钟驱动,所有SDRAM输入信号都在时钟上升沿采样,同时CLK还递增内部突发计数器并控制输出寄存器。 |
CKE | 1Bit | Input | 时钟使能:屏蔽系统时钟,冻结当前操作,高电平有效,信号有效时,所有信号才能被正确送入SDRAM。 |
CS#(CS_N) | 1Bit | Input | 片选信号:屏蔽和使能所有输入输出端口,CLK、CKE、DQM除外,低电平有效。为高电平时,屏蔽所有命令,但已经突发的读/写操作不受影响。 |
CAS#(CAS_N) | 1Bit | Input | 列选通信号:低电平有效,为低电平时,A[8:0]输入的为列地址。 |
RAS#(RAS_N) | 1Bit | Input | 行选题信号:低电平有效,为低电平时,A[12:0]输入的为行地址。 |
WE#(WE_N) | 1Bit | Input | 写使能信号,低电平有效,为低电平时,使能写操作和预充电。 {CS# 、CAS#、RAS#、WE#}构成SDRAM操作命令。 |
DQM[1:0] | 1Bit | Input | 数据掩码:DQML(H),低(高)字节掩码, 若信号为高电平,在下一个时钟周期的时钟上升沿,数据总线的低(高)字节为高阻态。 |
BA[1:0] | 2Bit | Input | L-Bank地址 :选择不同的逻辑Bank进行相关数据操作。 |
A[12:0] | 13Bit | Input | 地址总线:不同命令下含义不同,后文中会有详细介绍。 |
DQ[15:0] | 16Bit | Inout | 数据总线:数据输入/输出复用。 |
注:表格中某些信号只介绍了后文设计实验中所涉及到的功能,更多功能介绍请查阅芯片对应数据手册。
上文中对SDRAM各引脚进行了简要功能描述,接下来简单说明一下SDRAM内部功能框图。SDRAM内部功能框图:
由图可知,SDRAM内部包含一个逻辑控制单元,内部包含模式寄存器和命令解码器。外部通过CS_N、RAC_N、CAS_N、WE_N以及地址总线向逻辑控制单元输入命令,命令经过命令解码器进行译码后,将控制参数保存到模式寄存器中,逻辑控制单元进而控制逻辑运行。
外部通过地址总线输入地址信息,地址信息在逻辑控制单元进行逻辑控制时起到辅助作用,除此之外,复用的地址总线与Bank控制逻辑、行地址复用器、列地址计数锁存器、列地址解码器等内部器件共同作用,精确选定存储阵列中与行列地址相对应的存储单元,进而进行数据存取操作。
1.4 SDRAM的存储容量和速度等级
以镁光公司生产的3款SDRAM芯片为例,为读者说明一下SDRAM芯片存储容量相关参数含义,以及存储容量计算方法。SDRAM存储容量参数截图 ,具体见图:
图中列举了镁光公司的3款SDRAM芯片。例“MT48LC16M16A2”,由图可知,“Micron”“MT48LC16M16A2”分别表示此SDRAM芯片的生产商和产品型号。“4Meg × 16 × 4Banks”才是表示SDRAM存储容量的具体参数,其中“4Meg”表示单个L-Bank包含的存储单元的个数,计算方法为单个存储阵列行地址数和列地址数的乘积,以此芯片为例,行地址总线为A0-A12,行地址位宽为13位,行地址数为 8192 (2^13)行,列地址位宽为9位,列地址数为512(2^9)列,单个L-Bank中包含的存储单元个数为行地址数(8192)与列地址数(512)相乘,乘积为4M(8192 × 512 = 4194306); “16”表示数据位宽,即每个存储单元存储数据的bit数;4Meg与16相乘表示单个L- Bank中可存储的Bit数;“4BANKS”表示一片SDRAM中包含的L-Bank个数,此SDRAM芯片包含4个L-Bank;由此可得SDRAM芯片的存储容量为:256MBit(4Meg × 16 × 4BANKS)。
容量计算方法可简化为:
存储容量(Bit) = L-Bank存储单元数 ×数据位宽(Bit) × L-Bank个数
同理,读者可根据此计算方法计算其他SDRAM芯片的存储容量。
接下来我们来说明一下SDRAM芯片的另一个概念:速度等级。SDRAM速度等级相关参数截图,具体见图 :
上图列举了包括速度等级在内的6个相关参数。时钟频率(Clock Frequency),单位MHz,所列举的具体参数为SDRAM正常工作的最高时钟频率,SDRAM工作时只能等于或低于这一时钟频率;tRCD表示写入自激活命令到开始进行数据读写,中间所需的等待时间,列举的数值表示等待时间的最小值,单位为ns; tRP表示自预充电指令写入到预充电完成所需的等待时间,列举的数值表示等待时间的最小值,单位为ns;CL(CAS(READ) latency)列选通潜伏期,表示自数据读指令写入到第一个有效数据输出所需等待时间,单位ns;Target tRCD-tRP-CL表示最大工作频率下,tRCD、tRP、CL等待的最小时钟周期数。
所以相同型号的SDRAM芯片被分为不同的速度等级,这与FPGA速度等级的划分颇为类似,因此在挑选或使用SDRAM芯片时,要注意这些参数,以免出现问题。
1.5 SDRAM的操作指令
合理使用操作指令可以控制SDRAM实现特定功能。在前文SDRAM的引脚功能介绍的表格中,本文提到CS_N、RAS_N、CAS_N、WE_N 四路控制信号构成SDRAM指令集。除构成指令集的4路信号之外,CKE、BA[1:0]、A[12:0]等信号,在SDRAM的操作中,也起到辅助作用。
接下来我们介绍一下常用的SDRAM操作指令。SDRAM操作指令集截图,具体见图:
注:H表示高电平;L表示低电平;X表示无需关心。
根据上图,我们分条列举一下SDRAM常用的操作指令,并对各操作指令做详细说明,说明如下;
1.5.1 禁止命令(Command Inhibit)
禁止命令(Command Inhibit),其他数据手册也称为取消设备选择命令(Device Deselect),控制指令为{CS_N,RAS_N,CAS_N,WE_N} = 4’b1XXX。不论SDRAM处于何种状态,此命令均可被执行,无需顾及CKE是否有效,即CLK是否使能,无需关心DQM、ADDR、DQ的信号输入;执行此命令后,SDRAM芯片不被选择,新的命令无法写入,但已经执行的命令不受影响。
1.5.2. 无操作命令(No-operation)
无操作命令(No-operation),也可称为空命令,简称为NOP命令,控制指令为{CS_N,RAS_N,CAS_N,WE_N} = 4’b0111。不论SDRAM处于何种状态,此命令均可被写入,该命令给被选中的SDRAM芯片传递一个空操作信息,目的是为了防止SDRAM处于空闲或等待状态时,其他命令被写入,此命令对正在执行的命令无影响。
1.5.3. 配置模式寄存器命令(Load Mode Register)
配置模式寄存器命令(Load Mode Register),也被称为Mode Reigister Set,简称LMR命令,控制指令为{CS_N,RAS_N,CAS_N,WE_N} = 4’b0000,此命令只有所有L- Bank均处于空闲状态时才可被写入,否则配置出错,而且在执行此命令后,SDRAM必须等待相应的响应时间tRSC(Register Set Cycle),模式寄存器配置周期)后,才可写入新的命令。
在写入此命令对SDRAM进行模式寄存器配置时,需要地址总线A0-A11辅助寄存器的模式设置,A0-A11赋值不同对应寄存器配置不同模式,未使用的地址总线设置为低电平。下面介绍一下A0-A11的不同赋值所对应的寄存器不同模式,具体见图:
突发长度(Burst Length)
突发(Burst)是指在同一行中相邻的存储单元连续进行数据传输的方式,连续传输所涉及到存储单元(列)的数量就是突发长度(Burst Length,简称 BL)。
地址总线的低三位A0-A2是突发长度的控制位,SDRAM芯片的突发长度可设置为1、2、4、8和整页,单位为字节,整页表示一次突发传输一整行的数据量,具体数值视芯片而定。
若在数据读/写操作时不使用突发传输,此时可等效为突发长度为1字节,每次读/写数据时,都要对存储单元进行寻址,如果要实现连续的读/写操作,就要不断地发送列地址和读/写命令,这种方法控制资源占用极大,效率较低。非突发连续读操作时序图,具体见图
若使用突发传输,只要指定起始列地址和突发长度,内存就会依次地自动对后面相应数量的存储单元进行读/写操作而不再需要控制器连续地提供列地址,这样,除了第一笔数据传输需要若干个周期外,其后的每个数据只要一个周期即可获得。突发的数据长度一般是4或8,如果传输时实际需要的数据长度小于设定的BL值,则调用“突发 停止”命令结束传输。突发连续度操作时序图,具体见图 :
突发类型
突发类型的设置位为A3,可将突发类型设置为两类,顺序和隔行。一般将A3设置为低电平,选择顺序类型,但也要结合实际情况进行选择。不同突发类型所对应的情况,具体见图:
列选通潜伏期(CAS Latency)
列选通潜伏期是指从读命令被寄存到数据总线上到出现第一个有效数据之间的时钟周期间隔,列选通潜伏期可被设置为2个或3个时钟周期,设置位为A6,A5,A4,当{ A6A5A4}=3’b010时,潜伏期为2个时钟周期;当{ A6A5A4}=3’b011时,潜伏期为3个时钟周期,时序图具体见图
运行模式(Operating Mode)
运行模式设置位为A7,A8,SDRAM存在标准模式、测试模式等多种模式,但对于普通用户,只开放了标准模式,在使用SDRAM时只需将A7,A8设置为低电平进入标准模式即可。
写模式
写模式设置位为A9,控制SDRAM的写模式。当A9为低电平时,SDRAM的读/写操作均采用突发方式,突发长度由突发长度寄存器(A0-A2)设定;当A9位高电平时,SDRAM的读操作依然采用突发方式,突发长度由突发长度寄存器(A0-A2)设定,但SDRAM的写操作不在使用突发方式,每一个写命令只能写入 一个数据。
A10-A12为保留位,对模式寄存器的配置不起作用,赋值为0即可。
1.5.4 预充电命令
预充电的作用就是关闭指定的L-Bank或者全部L-Bank中激活的行,预充电命令执行后,必须等待对应的等待时间tRP(tRP(Precharge command Period),预充电命令周期),相对应的L-Bank将可以被重新操作。
预充电命令(Precharge)命令包括两类:全部预充电(Precharge All)和指定L-Bank预充电(Precharge Bank),控制指令均为{CS_N,RAS_N,CAS_N,WE_N} = 4’b0010,通过地址总线中的A10和L-Bank地址线BA[1:0]辅助控制预充电类型, 当A10 为高电平时,执行全部预充电命令,对所有的L-Bank进行预充电,无需关心BA[1:0]信号的输入;当A10为低电平时,只对由BA[1:0]选定的L-Bank进行预充电。当某个L-Bank执行预充电操作后,该L-Bank处于空闲状态,在下次读写操作之前必须重新激活。
预充电命令示意图,具体见图
1.5.5 刷新指令
SDRAM只有通过刷新操作才能保证数据的可靠性,当然不能一直进行刷新,那将变得毫无意义,SDRAM的刷新操作是周期性的,在两次刷新的间隔可以进行数据的相关操作,那我们不禁要问,刷新周期是多少呢?
目前国际公认的标准是,存储体中电容的数据有效保存期上限是 64ms,也就是说每一行刷新的循环周期最大为 64ms,那么刷新速度就是:行数/64ms。我们在SDRAM的数据手册中经常会看到4096 Refresh Cycles/64ms 或 8192 Refresh Cycles/64ms的相关介绍,这里的4096 与 8192 就代表SDRAM芯片中单个 L-Bank 的行数。刷新命令一次对一行有效,发送间隔也是随总行数而变化, 当单个L-Bank为4096 行时,刷新间隔最大为 15.625μs, 单个L-Bank为8192 行时,刷新间隔最大为7.8125μs。
刷新命令(Refresh)分为两种:自动刷新命令(Auto Refresh)和自刷新(Self Refresh),控制指令相同,为{CS_N,RAS_N,CAS_N,WE_N} = 4’b0001,两种刷新方式均不需要外部提供地址信息。
当CKE为高电平时,写入刷新指令,执行自动刷新操作。在执行自动刷新命令前,必须先要执行预充电命令,将所有L-Bank关闭,SDRAM内部刷新计数器会依次自动生成行地址,刷新是针对一行中的所有存储单元,所以无需列寻址。由于刷新涉及到所有 L-Bank,因此在刷新过程中,所有 L-Bank 都停止工作,而每次刷新需要等待对应的时钟周期,之后就可进入正常的工作状态,在此期间,所有工作指令只能等待而无法执行。 64ms 之后则再次对同一行进行刷新,如此周而复始进行循环刷新。
当CKE为低电平时,写入刷新指令,执行自刷新操作。自刷新操作主要用于休眠模式低功耗状态下的数据保存,在发出刷新命令时,将 CKE 置于无效状态,就进入了自刷新模式,此时不再依靠系统时钟工作,而是根据内部的时钟进行刷新操作。在自刷新期间除了 CKE 之外的所有外部信号都是无效的,只有重新使 CKE 有效才能退出自刷新模式并进入正常操作状态。
1.5.6 激活指令
激活命令(Bank Active),控制命令为{CS_N,RAS_N,CAS_N,WE_N} = 4’b0011,只有SDRAM处于空闲状态下才可被响应。激活命令是为后续操作激活某一特定L-Bank中的某一行,逻辑Bank地址线BA[1:0]和地址总线A0-A12选择要激活的特定L- Bank的特定行,激活该行后,该行一直保持激活状态,并可以进行后续读/写操作,操作完成后,只有执行一次预充电命令(Precharge)后,被激活的特定行被关闭。每次激活只能激活一个L-Bank,同一个L-Bank中每次只能激活一行,当需要对同一L-Bank中其他行进行操作时, 必须先执行一个预充电命令关闭当前行,再激活另一行进行操作。
激活命令示意图,具体见图
1.5.6 写命令
写命令(Write),控制命令为{CS_N,RAS_N,CAS_N,WE_N} = 4’b0100,用来实现对已激活的特定L-Bank的特定行的数据突发写入操作,BA[1:0]指定需要写入数据的特定L-Bank,地址总线A0-A9指定需要写入存储单元的起始列地址,A10的电平变化控制突发写操作完成后 是否立即执行自动预充电操作,若A10为高电平,突发写操作完成后,立即执行自动预充电操作,关闭当前行;若A10为低电平,突发写操作完成后,当前行依然处于激活状态,以便对当前行执行新的读/写操作,想要关闭当前激活行,需写入预充电指令。
1.5.7 读命令
读命令(Read),控制命令为{CS_N,RAS_N,CAS_N,WE_N} = 4’b0101,用来实现对已激活的特定L-Bank的特定行的数据突发读取操作,BA[1:0]指定需要读取数据的特定L-Bank,地址总线A0-A9指定需要读取存储单元的起始列地址,A10的电平变化控制突发读操作完成后是否立即执行自动预充电操作,若A10为高电平,突发读操作完成后,立即执行自动预充电操作,关闭当前行;若A10为低电平,突发读操作完成后,当前行依然处于激活状态,以便对当前行执行新的读/写操作,想要关闭当前激活行,需写入预充电指令。
1.5.8 突发中止命令
突发终止(Burst Terminate),也称为Burst Stop,控制命令为{CS_N,RAS_N,CAS_N,WE_N} = 4’b0110,SDRAM处于读/写操作过程中可被写入,突发停止操作被用来截断固定长度或者整页长度的突发,执行突发停止命令后,最近执行的数据读/写操作被终止,此命令操作并不会通过预充电关闭当前激活行,需通过预充电操作关闭被激活行。
2 实验实践
2.1 实验目标
设计并实现一个SDRAM数据读写控制器,使用PC机通过串口向SDRAM写入10字节数据,并将写入的10字节数据读出,通过串口回传至PC机,在串口助手上位机上打印显示回传数据。
2.2 硬件资源
EP4CE15F23开发板使用的SDRAM型号为W9825G6KH,存储容量为256 Mbit(32MByte),地址位宽13位,数据位宽16位。SDRAM原理图如图:
2.3 程序设计
2.3.1 整体模块设计
第一部分:SDRAM的基本操作的实现
SDRAM控制器的实现,最核心的部分就是对SDRAM进行数据的读写操作,而为了保证数据正确的写入和读取,以及写入数据的可靠性,SDRAM的初始化和刷新操作必不可少。在此将SDRAM控制器中初始化、自动刷新和数据读写的实现部分定为第一部分。此部分的整体框图,具体见图 :
由图可知,第一部分共调用6个模块,是三个部分中调用模块较多的部分,是本实验工程核心中的核心。顶层模块为sdram_ctrl模块,内部实例化5个功能子模块,连接各子模块对应信号,外部接收数据读写请求、读写地址和读写数据,输出SDRAM控制信号、地址和数据;顶层模块内部实例化的5个功能子模块分别为 sdram_init、sdram_aref、sdram_write、sdram_read和sdram_arbit。各部分功能描述,具体见表格 :
模块名称 | 功能描述 |
---|---|
sdram_ctrl | SDRAM控制部分顶层模块 |
sdram_init | SDRAM初始化模块,实现SDRAM的初始化 |
sdram_aref | SDRAM自动刷新模块,实现SDRAM的自动刷新操作 |
sdram_write | SDRAM数据写模块,实现SDRAM的数据写入 |
sdram_read | SDRAM数据读模块,实现SDRAM的数据读出 |
sdram_arbit | SDRAM仲裁模块,实现各操作优先级的仲裁 |
为什么地址输入23位,输出13位?
主要原因是存储器的行列地址复用机制:
- 输入的23位地址指令是外部逻辑地址,用来访问一个存储单元。因为SDRAM的存储单元被组织成二维阵列(行和列),控制器将地址拆分为行地址和列地址。
- 在写操作时,控制器会首先输出13位行地址(通过地址总线),然后在接下来的时钟周期中输出剩下的列地址。
这是因为SDRAM的设计可以通过行列复用减少地址总线的位宽。输出的13位地址是在行地址激活阶段被发送的地址,列地址则在列选通阶段发送。
第二部分:SDRAM控制器
SDRAM控制器作为本工程的核心,对上与串口收发模块相连,对下与外部SDRAM相连,是数据写入和读出SDRAM的纽带。 第二部分整体框图,具体见图:
模块名称 | 功能描述 |
---|---|
sdram_top | SDRAM控制器顶层模块 |
fifo_ctrl | FIFO控制模块,实现SDRAM读写数据的跨时钟域处理 |
sdram_ctrl | 实现SDRAM的初始化、自动刷新和数据读写操作 |
第三部分:串口读写SDRAM控制器
本实验工程通过串口和SDRAM控制器实现对SDRAM的数据读写操作,所以最顶层由串口收发功能实现部分和SDRAM控制器构成,此为第三部分。 第三部分整体框图,具体见图:
模块名称 | 功能描述 |
---|---|
uart_sdram | 串口读写SDRAM顶层模块 |
clk_gen | 时钟生成模块,为整个工程提供工作时钟 |
uart_rx | 串口数据接收模块,接收串口发送过来的数据 |
sdram_top | SDRAM控制器,实现SDRAM数据读写 |
fifo_read | SDRAM读出数据缓存 |
uart_tx | 串口数据发送模块,发送读出SDRAM的数据 |
2.3.2. 初始化模块
2.3.2.1 模块框图
SDRAM在上电之后,执行正常操作之前需要被初始化,实际上就是对上文提到的SDRAM内部逻辑控制单元进行初始化,初始化成功的SDRAM才可进行后续的其他操作。
信号 | 位宽 | 输入输出 | 功能 |
sys_clk | 1Bit | Input | 系统时钟,频率100MHz |
sys_rst_n | 1Bit | Input | 复位信号,低有效 |
init_cmd | 4Bit | Output | 输出初始化阶段指令信号 |
init_ba | 2Bit | Output | 初始化阶段L-Bank地址 |
init_addr | 13Bit | Output | 初始化阶段地址总线,辅助预充电操作和模式寄存器的配置 |
init_end | 1Bit | Output | 初始化结束标志,标志着初始化完成,SDRAM可进行其他操作 |
初始化操作时序具体见图:
结合SDRAM初始化时序图,列出SDRAM初始化参考流程如下:
-
对SDRAM上电,加载稳定时钟信号,CKE设置为高电平;
-
等待T=100us的时间,此过程中操作命令保持为空操作命令;
-
100us等待结束后,写入预充电命令,A10设置为高电平,对所有L-Bank进行预充电;
-
预充电指令写入后,等待tRP时间,此过程中操作命令保持为空操作命令;
-
tRP等待时间结束后,写入自动刷新命令;
-
自动刷新命令写入后,等待tRC时间,此过程中操作命令保持为空操作命令;
-
tRC等待时间结束后,再次写入自动刷新命令;
-
自动刷新命令写入后,等待tRC时间,此过程中操作命令保持为空操作命令;
-
tRC等待时间结束后,写入模式寄存器配置指令,地址总线A0-A11参数不同辅助模式寄存器不同模式的设置;
-
模式寄存器配置指令写入后,等待tMRD时间,此过程中操作命令保持为空操作命令;
-
tMRD等待时间结束后,SDRAM初始化完成。
注:1.对于tRP、tRC、tMRD等时间参数,不同芯片或速度等级可能存在差异,读者需查阅芯片对应数据手册进行参数设置;
2.T=100us为最小等待时间,我们在使用SDRAM时,等待时间T可适当延长;
3.初始化过程中,至少进行两次自动刷新,也可适当增加刷新次数。
2.3.2.2 波形图讲解
第一部分:cnt_200us、wait_end信号的波形设计与实现
由SDRAM初始化参考流程可知,SDRAM初始化的第一步就是进行T≥100us的等待,等待的目的是初始化SDRAM内部逻辑控制单元。本工程选择等待时间T=200us,等待时间确定了,就必须声明计数器,还需要声明标志信号告知等待结束。所以,内部声明等待时间计数器cnt_200us和等待结束标志信号wait_end。
内部声明reg型计数器变量cnt_200us,为实现上电后,正常操作前的等待时间计数,计数时钟为系统时钟,系统上电后,每个时钟周期自加1,计数范围为0 - T_POWER(具体数值视时钟频率及计数时间而定),本模块中计数时间为200us,当计数器变量计数到最大值T_POWER,cnt_200us保持当前值不变;
内部声明wire型变量wait_end,只在计数器cnt_200us计数到(T_POWER-1)时保持一个时钟高电平,作为等待时间结束的标志信号。两信号参考波形图如下。
第二部分:状态机相关信号的波形设计与实现
参照SDRAM初始化参考流程可知,T≥100us的等待时间过后,要对SDRAM所有的逻辑Bank进行预充电操作,预充电过后,还要进行不少于两次的自动刷新操作,最后还需要进行相关寄存器的配置,且每次操作过后都会有规定的等待时间,可以使用状态机实现这一操作流程。
首先声明变量,首先声明状态机状态变量init_state;根据SDRAM 初始化的参考流程,状态机各状态如下:INIT_IDLE(初始状态)、INIT_PRE(预充电状态)、INIT_TRP(预充电等待 状态)、INIT_AR(自动刷新状态)、INIT_TRF(自动刷新等待状态)、INIT_MRS(配置模式寄存器状态)、INIT_TMRD(配置模式寄存器等待状态)、INIT_END(初始化完成状态)。
状态机跳转流程为:系统上电后,init_state变量保持在初始状态(INIT_IDLE),当200us等待结束,即wait_end信号有效时,状态机状态跳转到预充电状态(INIT_PRE),随即跳转到预充电等待状态(INIT_TRP),在此状态等待TRP_CLK(设定值为2)个时钟周期,预充电完成 ,状态跳转到自动刷新状态(INIT_AR),随即跳转到自动刷新等待状态(INIT_TRF),在此状态等待TRC_CLK(设定值为7)个时钟周期,一次自动刷新完成,完成8次自动刷新后,状态跳转到配置模式寄存器状态(INIT_MRS),随即状态跳转到配置模式寄存器等待状态(INIT_TMRD),在此状态等待TMRD_CLK(设定值为3)个时钟周期,模式寄存器配置完成,状态跳转到初始化完成状态(INIT_END),初始化完成,状态机保持在此状态。
通过观察发现,状态机的各等待状态,虽然停留时间各不相同,但每个状态的停留时间都是整数个时钟周期,且最长停留时间都不超过10个时钟周期。我们想到可以利用计数器,计数各等待状态停留的时钟周期数来确定各状态保持时间。这里,我们声明两个变量,时钟周期计数器cnt_clk和时钟周期计数清零信号cnt_clk_rst。
变量cnt _clk,目的是记录各等待状态保持时钟周期数,只有在各等待状态才进行计数,其他状态均为0;而声明的时钟周期计数清零信号cnt_clk_rst,就是为了等待状态计数完成后cnt_clk进行清零。
到这里,已经可以使用时钟周期计数器cnt_clk信号来作为状态跳转的约束条件,但为了代码更加直观,我们声明各等待状态对应的跳转标志信号,作为状态跳转的约束条件。声明trp_end为预充电等待状态结束标志信号;trc_end为自动刷新等待状态结束标志信号;tmrd_end为模式寄存器配置等待结束信号。现在开始各信号波形图的绘制,各信号参考波形图如下图所示。
第三部分: 自动刷新计数器cnt_init_aref信号的波形设计与实现
SDRAM的初始化过程中,需要进行不小于两次的自动刷新操作,那么如何确定自动刷新次数呢?由上一部分可知,一个完整的自动刷新操作需要经过两个状态,INIT_AR(自动刷新状态)、INIT_TRF(自动刷新等待状态),所以我们可以对自动刷新状态进行计数,每当状态机跳转到INIT_AR(自动刷新状态)状态 时,计数一次,达到设定的刷新次数,且自动刷新等待状态完成,才可进行模式寄存器的配置。所以,声明自动刷新状态计数器cnt_init_aref,初值为0,只有状态处于自动刷新状态,计数器加1,其他状态保持计数值不变,信号波形如下。
第四部分:各输出信号的波形设计与实现
SDRAM初始化过程,在对应的操作状态下需要写入对应的操作指令,有的操作还需要写入逻辑Bank地址和行、列地址辅助操作,所以输出的指令信号、逻辑Bank地址和地址总线是必不可少的。
同时,当初始化完成后,需要告知其他模块初始化已完成,可以进行其他操作,所以初始化结束信号的输出也是很有必要的。
综上所述,输出信号有初始化阶段指令信号(init_cmd)、初始化阶段逻辑Bank地址(init_ba)、初始化阶段地址总线(init_addr)和初始化结束信号(init_end)。
初始化阶段指令信号init_cmd,当状态机处于预充电状态(INIT_PRE)、自动刷新状态(INIT_AR)和配置模式寄存器状态(INIT_MRS)时,分别写入预充电指令、自动刷新指令和模式寄存器配置指令。其他状态均写入空操作指令。
初始化阶段逻辑Bank地址init_ba、初始化阶段地址总线init_addr,当状态机处于配置模式寄存器状态(INIT_MRS)时,分别写入逻辑Bank地址和模式寄存器配置的相关参数。其他状态二者均写入全1。
初始化结束信号init_end,当状态机处于初始化完成状态时,赋值为高电平,其他状态均为低电平,初始化结束信号拉高,表示初始化完成,SDRAM可以进行其他操作。由此可得,各输出信号波形图如下。
2.3.2.3 程序设计
`timescale 1ns/1ns
module sdram_init
(input wire sys_clk , //系统时钟,频率100MHzinput wire sys_rst_n , //复位信号,低电平有效output reg [3:0] init_cmd , //初始化阶段写入sdram的指令,{cs_n,ras_n,cas_n,we_n}output reg [1:0] init_ba , //初始化阶段Bank地址output reg [12:0] init_addr , //初始化阶段地址数据,辅助预充电操作//和配置模式寄存器操作,A12-A0,共13位output wire init_end //初始化结束信号
);// parameter define
parameter T_POWER = 15'd20_000 ; //上电后等待时钟数(200us)
//SDRAM初始化用到的控制信号命令
parameter P_CHARGE = 4'b0010 , //预充电指令AUTO_REF = 4'b0001 , //自动刷新指令NOP = 4'b0111 , //空操作指令M_REG_SET = 4'b0000 ; //模式寄存器设置指令
//SDRAM初始化过程各个状态
parameter INIT_IDLE = 3'b000 , //初始状态INIT_PRE = 3'b001 , //预充电状态INIT_TRP = 3'b011 , //预充电等待 tRPINIT_AR = 3'b010 , //自动刷新INIT_TRF = 3'b100 , //自动刷新等待 tRCINIT_MRS = 3'b101 , //模式寄存器设置 INIT_TMRD = 3'b111 , //模式寄存器设置等待 tMRDINIT_END = 3'b110 ; //初始化完成
parameter TRP_CLK = 3'd2 , //预充电等待周期,20nsTRC_CLK = 3'd7 , //自动刷新等待,70nsTMRD_CLK = 3'd3 ; //模式寄存器设置等待周期,30ns// wire define
wire wait_end ; //上电后200us等待结束标志
wire trp_end ; //预充电等待结束标志
wire trc_end ; //自动刷新等待结束标志
wire tmrd_end ; //模式寄存器设置等待结束标志// reg define
reg [14:0] cnt_200us ; //SDRAM上电后200us稳定期计数器
reg [2:0] init_state ; //SDRAM初始化状态
reg [2:0] cnt_clk ; //时钟周期计数,记录初始化各状态等待周期数
reg cnt_clk_rst ; //时钟周期计数复位标志
reg [3:0] cnt_init_aref ; //初始化过程自动刷新次数计数器//cnt_200us:SDRAM上电后200us稳定期计数器
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)cnt_200us <= 15'd0;else if(cnt_200us == T_POWER)cnt_200us <= T_POWER;elsecnt_200us <= cnt_200us + 1'b1;//wait_end:上电后200us等待结束标志
assign wait_end = (cnt_200us == (T_POWER - 1'b1)) ? 1'b1 : 1'b0;//init_end:SDRAM初始化完毕信号
assign init_end = (init_state == INIT_END) ? 1'b1 : 1'b0;//cnt_clk:时钟周期计数,记录初始化各状态等待时间
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)cnt_clk <= 3'd0;else if(cnt_clk_rst == 1'b1)cnt_clk <= 3'd0;elsecnt_clk <= cnt_clk + 1'b1;//cnt_init_aref:初始化过程自动刷新次数计数器
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)cnt_init_aref <= 4'd0;else if(init_state == INIT_IDLE)cnt_init_aref <= 4'd0;else if(init_state == INIT_AR)cnt_init_aref <= cnt_init_aref + 1'b1;elsecnt_init_aref <= cnt_init_aref;//trp_end,trc_end,tmrd_end:等待结束标志
assign trp_end = ((init_state == INIT_TRP )&& (cnt_clk == TRP_CLK )) ? 1'b1 : 1'b0;
assign trc_end = ((init_state == INIT_TRF )&& (cnt_clk == TRC_CLK )) ? 1'b1 : 1'b0;
assign tmrd_end = ((init_state == INIT_TMRD)&& (cnt_clk == TMRD_CLK)) ? 1'b1 : 1'b0;//SDRAM的初始化状态机
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)init_state <= INIT_IDLE;elsecase(init_state)INIT_IDLE: //系统上电后,在初始状态等待200us跳转到预充电状态if(wait_end == 1'b1)init_state <= INIT_PRE;elseinit_state <= init_state;INIT_PRE: //预充电状态,直接跳转到预充电等待状态init_state <= INIT_TRP;INIT_TRP: //预充电等待状态,等待结束,跳转到自动刷新状态if(trp_end == 1'b1)init_state <= INIT_AR;elseinit_state <= init_state;INIT_AR : //自动刷新状态,直接跳转到自动刷新等待状态init_state <= INIT_TRF;INIT_TRF: //自动刷新等待状态,等待结束,自动跳转到模式寄存器设置状态if(trc_end == 1'b1)if(cnt_init_aref == 4'd8)init_state <= INIT_MRS;elseinit_state <= INIT_AR;elseinit_state <= init_state;INIT_MRS: //模式寄存器设置状态,直接跳转到模式寄存器设置等待状态init_state <= INIT_TMRD;INIT_TMRD: //模式寄存器设置等待状态,等待结束,跳到初始化完成状态if(tmrd_end == 1'b1)init_state <= INIT_END;elseinit_state <= init_state;INIT_END: //初始化完成状态,保持此状态init_state <= INIT_END;default: init_state <= INIT_IDLE;endcase//cnt_clk_rst:时钟周期计数复位标志
always@(*)begincase (init_state)INIT_IDLE: cnt_clk_rst <= 1'b1; //时钟周期计数复位信号,高有效,时钟周期计数清零INIT_TRP: cnt_clk_rst <= (trp_end == 1'b1) ? 1'b1 : 1'b0;//等待结束标志有效,计数器清零INIT_TRF: cnt_clk_rst <= (trc_end == 1'b1) ? 1'b1 : 1'b0; //等待结束标志有效,计数器清零INIT_TMRD: cnt_clk_rst <= (tmrd_end == 1'b1) ? 1'b1 : 1'b0;//等待结束标志有效,计数器清零INIT_END: cnt_clk_rst <= 1'b1; //初始化完成,计数器清零default: cnt_clk_rst <= 1'b0;endcaseend//SDRAM操作指令控制
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)begininit_cmd <= NOP;init_ba <= 2'b11;init_addr <= 13'h1fff;endelsecase(init_state)INIT_IDLE,INIT_TRP,INIT_TRF,INIT_TMRD: //执行空操作指令begininit_cmd <= NOP;init_ba <= 2'b11;init_addr <= 13'h1fff;endINIT_PRE: //预充电指令begininit_cmd <= P_CHARGE;init_ba <= 2'b11;init_addr <= 13'h1fff;end INIT_AR: //自动刷新指令begininit_cmd <= AUTO_REF;init_ba <= 2'b11;init_addr <= 13'h1fff;endINIT_MRS: //模式寄存器设置指令begininit_cmd <= M_REG_SET;init_ba <= 2'b00;init_addr <={ //地址辅助配置模式寄存器,参数不同,配置的模式不同3'b000, //A12-A10:预留1'b0, //A9=0:读写方式,0:突发读&突发写,1:突发读&单写2'b00, //{A8,A7}=00:标准模式,默认3'b011, //{A6,A5,A4}=011:CAS潜伏期,010:2,011:3,其他:保留1'b0, //A3=0:突发传输方式,0:顺序,1:隔行3'b111 //{A2,A1,A0}=111:突发长度,000:单字节,001:2字节//010:4字节,011:8字节,111:整页,其他:保留};end INIT_END: //SDRAM初始化完成begininit_cmd <= NOP;init_ba <= 2'b11;init_addr <= 13'h1fff;enddefault:begininit_cmd <= NOP;init_ba <= 2'b11;init_addr <= 13'h1fff;end endcaseendmodule
2.3.3 自动刷新模块
SDRAM内部存储体是利用电容能够保持电荷以及可充放电的特性制成,而电容所存储的电荷会随时间不断流失,会造成存储数据的丢失。为保证SDRAM中数据的可靠性,需要对SDRAM进行不断刷新。
自动刷新操作时序:SDRAM刷新操作的两种方式。SDRAM的刷新方式分为自刷新和自动刷新两种:
自动刷新模式:作用是在SDRAM的正常操作过程中,保证数据不丢失,自动刷新过程需要外部时钟的参与,但刷新行地址由内部刷新计数器控制,无需外部写入。自刷新模式则主要用于休眠模式低功耗状态下的数据保存,自刷新过程无需外部时钟参与,与自动刷新相同的是,刷新行地址由内部刷新计算器控制,无需外部写入。
两者的操作命令相同,当CKE信号保持高电平时,写入刷新指令,进入自动刷新模式;当CKE信号为低电平时,写入刷新指令,进入自刷新模式。
自刷新模式下,除CKE之外的其他外部信号均无效,当CKE再次拉高时,退出自刷新模式,进入正常操作状态。两种刷新方式时序图,具体见图。
在本实验中,是在SDRAM正常模式下进行刷新操作,要使用自动刷新方式。在此我们只对SDRAM的自动刷新操作的时序图和参考流程进行讲解。
由SDRAM自动刷新时序图可知,SDRAM的自动刷新类似于简化版的初始化操作,只是缺少了上电后的等待时间和模式寄存器配置部分,只包含一次预充电操作和两次自动刷新操作,自动刷新操作参考流程如下。
-
写入预充电命令,A10设置为高电平,对所有L-Bank进行预充电;
-
预充电指令写入后,等待tRP时间,此过程中操作命令保持为空操作命令;
-
tRP等待时间结束后,写入自动刷新命令;
-
自动刷新命令写入后,等待tRC时间,此过程中操作命令保持为空操作命令;
-
tRC等待时间结束后,再次写入自动刷新命令;
-
自动刷新命令写入后,等待tRC时间,此过程中操作命令保持为空操作命令;
-
tRC等待时间结束后,自动刷新操作完成。
2.3.3.1 模块框图
信号 | 位宽 | 类型 | 功能描述 |
---|---|---|---|
sys_clk | 1Bit | Input | 系统时钟,频率100MHz |
sys_rst_n | 1Bit | Input | 复位信号,低有效 |
init_end | 1Bit | Input | 初始化完成标志信号 |
aref_en | 1Bit | Input | 自动刷新使能信号 |
aref_req | 1Bit | Output | 自动刷新请求信号 |
aref_cmd | 4Bit | Output | 自动刷新阶段指令信号 |
aref_ba | 2Bit | Output | 自动刷新阶段L-Bank地址 |
aref_addr | 13Bit | Output | 自动刷新阶段地址总线 |
aref_end | 1Bit | Output | 自动刷新结束标志 |
2.3.3.2 波形图讲解
SDRAM自动刷新操作的刷新时序和参考流程类似于简化版的初始化操作,只保留预充电部分和自动刷新部分。但不同之处在于,初始化操作是一上电就开始执行,且只执行一次;而自动刷新操作是周期性执行,且开始执行时间不定。
第一部分:自动刷新周期计数器cnt_ref信号的波形设计与实现
SDRAM的自动刷新操作是周期性执行,那么周期时间的计数就需要一个计数器来实现,所以我们定义一个自动刷新周期计数器cnt_ref。对于计数周期,这里SDRAM的时钟为100MHz,周期计数器的计数范围设置为0-749,计数时间为7.5us。周期计数器自初始化结束开始循环计数,计数到最大值清零后重新计数。自动刷新周期计数器cnt_ref的信号波形图如下。
第二部分:状态机相关信号的波形设计与实现
参照初始化模块,我们依然使用状态机的方式来实现自动刷新功能。声明状态机状态变量aref_state,定义各状态名称:初始状态(AREF_IDLE)、预充电状态(AREF_PCHA)、预充电等待状态(AREF_TRP)、自动刷新状态(AUTO_REF)、自动刷新等待状态(AREF_TRF)、自动刷新结束状态(AREF_END)。
对于状态机的跳转条件,依然参照初始化模块的处理方式。声明时钟周期计数器cnt_clk、时钟周期计数清零信号cnt_clk_rst、预充电等待状态结束标志信号trp_end和自动刷新等待状态结束标志信号trc_end,使用这些信号实现状态机的状态跳转。
SDRAM的自动刷新操作是周期性的,当自动刷新周期计数器计数到最大值,表示SDRAM需要进行自动刷新操作,但如果此时SDRAM正在进行其他操作,直接进行自动刷新操作是不可以的,所以我们需要先向仲裁模块请求执行自动刷新操作,如果SDRAM正处于空闲状态,仲裁模块会响应我们的自动刷新请求,回传自动刷新使能信号,允许自动刷新操作。
当自动刷新模块接收到有效的自动刷新使能信号后,开始执行自动刷新操作。状态机状态由一直保持的初始状态(AREF_IDLE)跳转到预充电状态(AREF_PCHA),随即跳转到预充电等待状态(AREF_TRP),在此状态等待TRP_CLK(设定值为2)个时钟周期,预充电完成,状态跳转到自动刷新状态(AUT O_REF),随即跳转到自动刷新等待状态(AREF_TRF),在此状态等待TRC_CLK(设定值为7)个时钟周期,一次自动刷新完成,完成2次自动刷新后,状态跳转到自动刷新结束状态(AREF_END),随即状态跳转初始状态 (AREF_IDLE),等待下一次自动刷新操作。
当状态机由初始状态(AREF_IDLE)跳转到预充电状态(AREF_PCHA),表示自动刷新模块已经响应自动刷新使能信号,所以我们要以此作为条件,拉低之前的自动刷新请求信号。
综上所述,我们对外要输出自动刷新请求信号aref_req,内部声明状态机状态信号aref_state;自动刷新响应信号aref_ack,用来拉低自动刷新请求信号;声明时钟周期计数器cnt_clk、时钟周期计数清零信号cnt_clk_rst、预充电等待状态结束标志信号trp_end和自动刷新等待状态结 束标志信号trc_end,实现状态机的状态跳转;自动刷新次数计数器 cnt_aref_aref,记录自动刷新次数。各信号波形图如下。
第四部分:各输出信号的波形设计与实现
与SDRAM初始化过程相同,自动刷新操作过程中在对应的操作状态下需要写入对应的操作指令,虽然自动刷新操作不需要写入逻辑Bank地址和行、列地址辅助操作,但为了保证输出信号的完整,所以自动刷新模块需要输出指令信号、逻辑Bank地址和地址总线。
当自动刷新完成后,需要告知仲裁模块自动刷新操作已完成,可以进行其他操作,所以自动刷新结束信号的输出也是是很有必要的。
综上所述,输出信号有自动刷新阶段指令信号(aref_cmd)、自动刷新阶段逻辑Bank地址(aref _ba)、自动刷新阶段地址总线(aref _addr)和自动刷新结束信号(aref _end)。
自动刷新阶段指令信号aref _cmd,当状态机处于预充电状态(AREF_PRE)、自动刷新状态(AREF_AR)时,分别写入预充电指令、自动刷新指令。其他状态均写入空操作指令。
自动刷新操作不需要逻辑Bank地址aref _ba和地址总线aref _addr的辅助操作,二者均写入全1。
自动刷新结束信号aref_end,当状态机处于自动刷新结束状态时,赋值为高电平,其他状态均为低电平,自动刷新结束信号有效,表示自动刷新操作完成,SDRAM可以进行其他操作。
由此可得,输出信号波形图如下。
2.3.3.3 程序设计
`timescale 1ns/1ns
module sdram_a_ref
(input wire sys_clk , //系统时钟,频率100MHzinput wire sys_rst_n , //复位信号,低电平有效input wire init_end , //初始化结束信号input wire aref_en , //自动刷新使能output reg aref_req , //自动刷新请求output reg [3:0] aref_cmd , //自动刷新阶段写入sdram的指令,{cs_n,ras_n,cas_n,we_n}output reg [1:0] aref_ba , //自动刷新阶段Bank地址output reg [12:0] aref_addr , //地址数据,辅助预充电操作,A12-A0,13位地址output wire aref_end //自动刷新结束标志
);//parameter define
parameter CNT_REF_MAX = 10'd749 ; //自动刷新等待时钟数(7.5us)
parameter TRP_CLK = 3'd2 , //预充电等待周期TRC_CLK = 3'd7 ; //自动刷新等待周期
parameter P_CHARGE = 4'b0010 , //预充电指令A_REF = 4'b0001 , //自动刷新指令NOP = 4'b0111 ; //空操作指令
parameter AREF_IDLE = 3'b000 , //初始状态,等待自动刷新使能AREF_PCHA = 3'b001 , //预充电状态AREF_TRP = 3'b011 , //预充电等待 tRPAUTO_REF = 3'b010 , //自动刷新状态AREF_TRF = 3'b100 , //自动刷新等待 tRCAREF_END = 3'b101 ; //自动刷新结束//wire define
wire trp_end ; //预充电等待结束标志
wire trc_end ; //自动刷新等待结束标志
wire aref_ack ; //自动刷新应答信号//reg define
reg [9:0] cnt_aref ; //自动刷新计数器
reg [2:0] aref_state ; //SDRAM自动刷新状态
reg [2:0] cnt_clk ; //时钟周期计数,记录自刷新阶段各状态等待时间
reg cnt_clk_rst ; //时钟周期计数复位标志
reg [1:0] cnt_aref_aref ; //自动刷新次数计数器//cnt_ref:刷新计数器
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)cnt_aref <= 10'd0;else if(cnt_aref >= CNT_REF_MAX)cnt_aref <= 10'd0;else if(init_end == 1'b1)cnt_aref <= cnt_aref + 1'b1;//aref_req:自动刷新请求
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)aref_req <= 1'b0;else if(cnt_aref == (CNT_REF_MAX - 1'b1))aref_req <= 1'b1;else if(aref_ack == 1'b1)aref_req <= 1'b0;//aref_ack:自动刷新应答信号
assign aref_ack = (aref_state == AREF_PCHA ) ? 1'b1 : 1'b0;//aref_end:自动刷新结束标志
assign aref_end = (aref_state == AREF_END ) ? 1'b1 : 1'b0;//cnt_clk:时钟周期计数,记录初始化各状态等待时间
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)cnt_clk <= 3'd0;else if(cnt_clk_rst == 1'b1)cnt_clk <= 3'd0;elsecnt_clk <= cnt_clk + 1'b1;//trp_end,trc_end,tmrd_end:等待结束标志
assign trp_end = ((aref_state == AREF_TRP)&& (cnt_clk == TRP_CLK )) ? 1'b1 : 1'b0;
assign trc_end = ((aref_state == AREF_TRF)&& (cnt_clk == TRC_CLK )) ? 1'b1 : 1'b0;//cnt_aref_aref:初始化过程自动刷新次数计数器
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)cnt_aref_aref <= 2'd0;else if(aref_state == AREF_IDLE)cnt_aref_aref <= 2'd0;else if(aref_state == AUTO_REF)cnt_aref_aref <= cnt_aref_aref + 1'b1;elsecnt_aref_aref <= cnt_aref_aref;//SDRAM自动刷新状态机
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)aref_state <= AREF_IDLE;elsecase(aref_state)AREF_IDLE:if((aref_en == 1'b1) && (init_end == 1'b1))aref_state <= AREF_PCHA;elsearef_state <= aref_state;AREF_PCHA:aref_state <= AREF_TRP;AREF_TRP:if(trp_end == 1'b1)aref_state <= AUTO_REF;elsearef_state <= aref_state;AUTO_REF:aref_state <= AREF_TRF;AREF_TRF:if(trc_end == 1'b1)if(cnt_aref_aref == 2'd2)aref_state <= AREF_END;elsearef_state <= AUTO_REF;elsearef_state <= aref_state;AREF_END:aref_state <= AREF_IDLE;default:aref_state <= AREF_IDLE;endcase//cnt_clk_rst:时钟周期计数复位标志
always@(*)begincase (aref_state)AREF_IDLE: cnt_clk_rst <= 1'b1; //时钟周期计数器清零AREF_TRP: cnt_clk_rst <= (trp_end == 1'b1) ? 1'b1 : 1'b0;//等待结束标志有效,计数器清零AREF_TRF: cnt_clk_rst <= (trc_end == 1'b1) ? 1'b1 : 1'b0;//等待结束标志有效,计数器清零AREF_END: cnt_clk_rst <= 1'b1;default: cnt_clk_rst <= 1'b0;endcaseend//SDRAM操作指令控制
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)beginaref_cmd <= NOP;aref_ba <= 2'b11;aref_addr <= 13'h1fff;endelsecase(aref_state)AREF_IDLE,AREF_TRP,AREF_TRF: //执行空操作指令beginaref_cmd <= NOP;aref_ba <= 2'b11;aref_addr <= 13'h1fff;endAREF_PCHA: //预充电指令beginaref_cmd <= P_CHARGE;aref_ba <= 2'b11;aref_addr <= 13'h1fff;end AUTO_REF: //自动刷新指令beginaref_cmd <= A_REF;aref_ba <= 2'b11;aref_addr <= 13'h1fff;endAREF_END: //一次自动刷新完成beginaref_cmd <= NOP;aref_ba <= 2'b11;aref_addr <= 13'h1fff;end default:beginaref_cmd <= NOP;aref_ba <= 2'b11;aref_addr <= 13'h1fff;end endcaseendmodule
持续更新中。。。。