3. sdram读写控制器的设计与验证

SDRAM发展至今已历经五代,以其单位存储量大、高数据带宽、读写速度快、价格相对便宜等优点吸引了大批客户,占领市场较大份额。同时,作为内存条中不可缺少的有一部分,SDRAM在计算机领域也占有一席之地。

本章节我们将带领读者进行SDRAM相关知识的学习,在学习过程中读者要掌握SDRAM的基本概念、数据存取原理、操作命令以及操作时序等相关知识,在掌握SDRAM的相关理论知识后,结合之前学习的设计技巧和方法,设计一个可进行读写操作的SDRAM控制器,并上板验证。

3.1. 理论学习

知己知彼,百战不殆。在进行控制器设计之前,相关理论知识的学习是必不可少的,在本小节中,我们会对实验涉及到的SDRAM的理论知识做一下详细介绍,望读者认真学习,这对后文中的实验大有益处,初学者切莫好高骛远,强行跳过。

3.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需要不断刷新来保证数据的可靠性,以及行列地址线分时复用等原因,使其对操作时序的要求较为严格,进而导致控制逻辑较为复杂,很多初学者在进行SDRAM学习时,被复杂的 控制逻辑折磨的痛不欲生。

在本章中,我们会通过SDRAM读写控制器的设计实验带领读者由浅入深学习掌握SDRAM,望读者紧跟步伐,认真学习,战胜SDRAM。

3.1.2. SDRAM数据存取原理

对SDRAM的基本概念有了了解之后,我们来进行SDRAM存取原理的学习。

简单来说,SDRAM内部可以理解为一个存储阵列,这是SDRAM区别于管道式存储,实现随机地址存取的结构特点。为方便读者理解,我们将SDRAM内部存储阵列类比于一张表格,表格中的每一个单元格可以类比为存储阵列的单个存储单元。若想要实现存储阵列中的某一存储单元的数据读写操作,我们要通过行地址(Row Address)和列地址(Column Address)(先行后列)精确定位到这一存储单元,进而进行数据的读写操作,这就是所谓的随机地址存取。SDRAM存储阵列类比图,具体见图 53‑1。

SDRAM002

图 53‑1 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存放于一个单独的存储体中,存储体是利用电容能够保持电荷以及可充放电的特性制成,主要由行选通三极管、列选通三极管、存储电容以及刷新放大器构成。电容所存储的电荷会随时间慢慢释放,这就需要不 断刷新为电容充电,以保证存储数据可靠性。存储体示意图,具体见图 53‑2。

SDRAM003

图 53‑2 单比特数据存取电路

将每个存储单元简化为单Bit的存储体,再将若干存储体排列为矩阵,同一行将行地址线相连,同一列将列地址线相连,就构成了一个存储阵列的简化模型。SDRAM内部存储阵列的简化模型,具体见图 53‑3。

SDRAM004

图 53‑3 SDRAM内部存储阵列简化模型

3.1.3. SDRAM器件引脚说明

SDRAM作为一款存储芯片,有着较为复杂的操作时序,且输入输出信号较多,在进行SDRAM操作指令和操作时序的学习之前,我们先对芯片的输入输出引脚、引脚功能以及内部功能框图做一下介绍,读者应认真学习,理解掌握,这对后文操作命令和操作时序的学习至关重要。

SDRAM引脚示意图,具体见图 53‑4。

SDRAM005

图 53‑4 SDRAM引脚图

注:x4、x8、x16分别表示位宽4bit、8bit、16bit;#符号表示信号为低电平有效;短划线 - 表示x8和x4引脚功能与x16引脚功能相同。

由于SDRAM在容量、位宽以及生产厂家存在差异,所以SDRAM在Bank地址、地址总线、数据总线和数据掩码可能存在位宽的差异,但各输入输出管脚的名称和实现功能并无出入。由图 53‑4已较为详细的展示了不同位宽的SDRAM 芯片的引脚示意图,针对其中较为重要的输入输出引脚,我们以镁光公司生产的、容量为4 Meg x 16 x 4 Banks的SDRAM芯片为例,对其做一下功能介绍。SDRAM引脚功能描述,具体见表格 53‑1。

表格 53‑1 SDRAM引脚功能描述

引脚 |

宽 | 类型

功能描述

CLK

1Bit

Input

系统时钟:SDRAM由系统时钟驱动,所有SD | RAM输入信号都在时钟上升沿采样,同时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内部功能框图,具体见图 53‑5。

SDRAM006

图 53‑5 SDRAM器件功能框图

由图 53‑5可知,SDRAM内部包含一个逻辑控制单元,内部包含模式寄存器和命令解码器。外部通过CS_N、RAC_N、CAS_N、WE_N以及地址总线向逻辑控制单元输入命令,命令经过命令解码器进行译码后,将控制参数保存到模式寄存器中,逻辑控制单元进而控制逻辑运行。

外部通过地址总线输入地址信息,地址信息在逻辑控制单元进行逻辑控制时起到辅助作用,除此之外,复用的地址总线与Bank控制逻辑、行地址复用器、列地址计数锁存器、列地址解码器等内部器件共同作用,精确选定存储阵列中与行列地址相对应的存储单元,进而进行数据存取操作。

3.1.4. SDRAM的存储容量与速度等级

在前文的介绍中我们不止一次提及到SDRAM的容量、位宽等名词,不少读者可能心存疑虑,SDRAM的容量是如何计算的?以及各参数之间的关系又是什么?那么接下来,我们就以镁光公司生产的3款SDRAM芯片为例,为读者说明一下SDRAM芯片存储容量相关参数含义,以及存储容量计算方法。SDRAM存储容量参数截图 ,具体见图 53‑6。

SDRAM007

图 53‑6 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芯片的另一个概念:速度等级。SDRAM速度等级相关参数截图,具体见图 53‑7。

SDRAM008

图 53‑7 SDRAM速度等级相关参数截图

图 53‑7列举了包括速度等级在内的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芯片时,要注意这些参数,以免出现问题。

3.1.5. SDRAM的操作命令

上面几个小节对SDRAM的基本概念、存取原理、引脚功能以及容量、速度等级等相关特性做了详细介绍,在本小节中我们来介绍一下SDRAM的操作命令。

合理使用操作指令可以控制SDRAM实现特定功能。在前文SDRAM的引脚功能介绍的表格中,我们提到CS_N、RAS_N、CAS_N、WE_N 四路控制信号构成SDRAM指令集。除构成指令集的4路信号之外,CKE、BA[1:0]、A[12:0]等信号,在SDRAM的操作中,也起到辅助作用。

接下来我们介绍一下常用的SDRAM操作指令。SDRAM操作指令集截图,具体见图 53‑8。

SDRAM009

图 53‑8 SDRAM操作指令集

注:H表示高电平;L表示低电平;X表示无需关心。

根据图 53‑8,我们分条列举一下SDRAM常用的操作指令,并对各操作指令做详细说明,说明如下。

3.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芯片不被选择,新的命令无法写入,但已经执行的命令不受影响。

3.1.5.2. 无操作命令(No-operation)

无操作命令(No-operation),也可称为空命令,简称为NOP命令,控制指令为{CS_N,RAS_N,CAS_N,WE_N} = 4’b0111。不论SDRAM处于何种状态,此命令均可被写入,该命令给被选中的SDRAM芯片传递一个空操作信息,目的是为了防止SDRAM处于空闲或等待状态时,其他命令被写入,此命令对正在执行的命令无影响。

3.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的不同赋值所对应的寄存器不同模式,具体见图 53‑9。

SDRAM010

图 53‑9 模式寄存器配置参数

突发长度(Burst Length)

突发(Burst)是指在同一行中相邻的存储单元连续进行数据传输的方式,连续传输所涉及到存储单元(列)的数量就是突发长度(Burst Length,简称 BL)。

地址总线的低三位A0-A2是突发长度的控制位,SDRAM芯片的突发长度可设置为1、2、4、8和整页,单位为字节,整页表示一次突发传输一整行的数据量,具体数值视芯片而定。

若在数据读/写操作时不使用突发传输,此时可等效为突发长度为1字节,每次读/写数据时,都要对存储单元进行寻址,如果要实现连续的读/写操作,就要不断地发送列地址和读/写命令,这种方法控制资源占用极大,效率较低。非突发连续读操作时序图,具体见图 53‑10。

SDRAM011

图 53‑10 非突发连续读操作

若使用突发传输,只要指定起始列地址和突发长度,内存就会依次地自动对后面相应数量的存储单元进行读/写操作而不再需要控制器连续地提供列地址,这样,除了第一笔数据传输需要若干个周期外,其后的每个数据只要一个周期即可获得。突发的数据长度一般是4或8,如果传输时实际需要的数据长度小于设定的BL值,则调用“突发 停止”命令结束传输。突发连续度操作时序图,具体见图 53‑11。

SDRAM012

图 53‑11 突发连续读操作

突发类型

突发类型的设置位为A3,可将突发类型设置为两类,顺序和隔行。一般将A3设置为低电平,选择顺序类型,但也要结合实际情况进行选择。不同突发类型所对应的情况,具体见图 53‑12。

SDRAM013

图 53‑12 突发类型对照表

列选通潜伏期(CAS Latency)

列选通潜伏期是指从读命令被寄存到数据总线上到出现第一个有效数据之间的时钟周期间隔,列选通潜伏期可被设置为2个或3个时钟周期,设置位为A6,A5,A4,当{ A6A5A4}=3’b010时,潜伏期为2个时钟周期;当{ A6A5A4}=3’b011时,潜伏期为3个时钟周期,时序图具体见图 53‑13。

SDRAM014

图 53‑13 列选通潜伏期时序图

运行模式(Operating Mode)

运行模式设置位为A7,A8,SDRAM存在标准模式、测试模式等多种模式,但对于普通用户,只开放了标准模式,在使用SDRAM时只需将A7,A8设置为低电平进入标准模式即可。

写模式

写模式设置位为A9,控制SDRAM的写模式。当A9为低电平时,SDRAM的读/写操作均采用突发方式,突发长度由突发长度寄存器(A0-A2)设定;当A9位高电平时,SDRAM的读操作依然采用突发方式,突发长度由突发长度寄存器(A0-A2)设定,但SDRAM的写操作不在使用突发方式,每一个写命令只能写入 一个数据。

A10-A12为保留位,对模式寄存器的配置不起作用,赋值为0即可。

3.1.5.4. 预充电命令(Precharge)

预充电的作用就是关闭指定的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处于空闲状态,在下次读写操作之前必须重新激活。

预充电命令示意图,具体见图 53‑14。

SDRAM015

图 53‑14 预充电命令示意图

3.1.5.5. 刷新命令(Refresh)

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 有效才能退出自刷新模式并进入正常操作状态。

3.1.5.6. 激活命令(Bank Active)

激活命令(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中其他行进行操作时, 必须先执行一个预充电命令关闭当前行,再激活另一行进行操作。

激活命令示意图,具体见图 53‑15。

SDRAM016

图 53‑15 激活命令示意图

3.1.5.7. 写命令(Write)

写命令(Write),控制命令为{CS_N,RAS_N,CAS_N,WE_N} = 4’b0100,用来实现对已激活的特定L-Bank的特定行的数据突发写入操作,BA[1:0]指定需要写入数据的特定L-Bank,地址总线A0-A9指定需要写入存储单元的起始列地址,A10的电平变化控制突发写操作完成后 是否立即执行自动预充电操作,若A10为高电平,突发写操作完成后,立即执行自动预充电操作,关闭当前行;若A10为低电平,突发写操作完成后,当前行依然处于激活状态,以便对当前行执行新的读/写操作,想要关闭当前激活行,需写入预充电指令。

写命令示意图,具体见图 53‑16。

SDRAM017

图 53‑16 写命令示意图

3.1.5.8. 读命令(Read)

读命令(Read),控制命令为{CS_N,RAS_N,CAS_N,WE_N} = 4’b0101,用来实现对已激活的特定L-Bank的特定行的数据突发读取操作,BA[1:0]指定需要读取数据的特定L-Bank,地址总线A0-A9指定需要读取存储单元的起始列地址,A10的电平变化控制突发读操作完成后是 否立即执行自动预充电操作,若A10为高电平,突发读操作完成后,立即执行自动预充电操作,关闭当前行;若A10为低电平,突发读操作完成后,当前行依然处于激活状态,以便对当前行执行新的读/写操作,想要关闭当前激活行,需写入预充电指令。

读命令示意图,具体见图 53‑17。

SDRAM018

图 53‑17 读命令示意图

3.1.5.9. 突发终止(Burst Terminate)

突发终止(Burst Terminate),也称为Burst Stop,控制命令为{CS_N,RAS_N,CAS_N,WE_N} = 4’b0110,SDRAM处于读/写操作过程中可被写入,突发停止操作被用来截断固定长度或者整页长度的突发,执行突发停止命令后,最近执行的数据读/写操作被终止,此命令操作并不会通过预充电关闭当前激活行,需通过预充电操作关闭被激活行。

以上信息是对SDRAM常用操作指令的介绍,针对其中的某些指令,我们会在后文中做进一步详细说明,读者若想了解SDRAM操作指令的更多详细信息,可参阅SDRAM芯片数据手册。

3.1.6. SDRAM的操作时序

操作指令只有被正确的写入SDRAM才能达到预期的效果。如何正确的写入操作指令,就需要学习一下SDRAM的操作时序。

为读者更好的理解SDRAM的操作时序,我们将其移至后面章节与各模块的设计小节一同讲解。

3.2. 实战演练

在完成对SDRAM理论知识的系统性学习后,我们进入SDRAM控制器的设计实验环节,将所学理论知识与实际相结合,设计实现一个SDRAM读写控制器,并上板验证。

3.2.1. 实验目标

设计并实现一个SDRAM数据读写控制器,使用PC机通过串口向SDRAM写入10字节数据,并将写入的10字节数据读出,通过串口回传至PC机,在串口助手上位机上打印显示回传数据。

3.2.2. 硬件资源

征途Pro开发板使用的SDRAM型号为W9825G6KH,存储容量为256 Mbit(32MByte),地址位宽13位,数据位宽16位。SDRAM实物图与原理图,如图 53‑18所示。

SDRAM019

图 53‑18 板载SDRAM实物图

SDRAM020

图 53‑19 板载SDRAM原理图

3.2.3. 程序设计

在进行实验之前,我们要对整个实验工程的框架结构有一个整体的构思,将想要实现的功能拆分为若干个容易实现的小功能模块,针对每一个小的功能模块进行设计实现,最终实现整体功能。

3.2.3.1. 整体模块设计

本实验共调用大小13个模块,虽涉及模块较多,但设计思路较为清晰。为方便读者理解,将实验工程分三个部分,自下而上分层次讲解。

第一部分:SDRAM的基本操作的实现

SDRAM控制器的实现,最核心的部分就是对SDRAM进行数据的读写操作,而为了保证数据正确的写入和读取,以及写入数据的可靠性,SDRAM的初始化和刷新操作必不可少。在此将SDRAM控制器中初始化、自动刷新和数据读写的实现部分定为第一部分。此部分的整体框图,具体见图 53‑20。

SDRAM021

图 53‑20 sdram_ctrl模块整体框图

由图可知,第一部分共调用6个模块,是三个部分中调用模块较多的部分,是本实验工程核心中的核心。顶层模块为sdram_ctrl模块,内部实例化5个功能子模块,连接各子模块对应信号,外部接收数据读写请求、读写地址和读写数据,输出SDRAM控制信号、地址和数据;顶层模块内部实例化的5个功能子模块分别为 sdram_init、sdram_aref、sdram_write、sdram_read和sdram_arbit。各部分功能描述,具体见表格 53‑2。

表格 53‑2 第一部分各模块功能描述

模块名称

功能描述

sdram_ctrl

SDRAM控制部分顶层模块

sdram_init

SDRAM初始化模块,实现SDRAM的初始化

sdram_aref

SDRAM自动刷新模块,实现SDRAM的自动刷新操作

sdram_write

SDRAM数据写模块,实现SDRAM的数据写入

sdram_read

SDRAM数据读模块,实现SDRAM的数据读出

sdram_arbit

SDRAM仲裁模块,实现各操作优先级的仲裁

第二部分:SDRAM控制器

SDRAM控制器作为本工程的核心,对上与串口收发模块相连,对下与外部SDRAM相连,是数据写入和读出SDRAM的纽带。 第二部分整体框图,具体见图 53‑21。

SDRAM022

图 53‑21 sdram_top模块整体框图

表格 53‑3 第二部分各模块功能描述

模块名称

功能描述

sdram_top

SDRAM控制器顶层模块

fifo_ctrl

FIFO控制模块,实现SDRAM读写数据的跨时钟域处理

sdram_ctrl

实现SDRAM的初始化、自动刷新和数据读写操作

由图和表格可知,本部分调用3个模块(sdram_ctrl视为一个子模块),是本实验工程的核心部分。顶层模块sdram_top,对外输入地址、使能、数据等相关信号,输出SDRAM时钟、控制信号、地址和数据;内部实例化2个功能子模块,fifo_ctrl模块,对SDRAM的读写数据进行跨时钟域处理,为sd ram_ctrl模块提供读写SDRAM的数据读写地址;sdram_ctrl模块,实现对SDRAM的初始化、自动刷新和数据读写操作。

第三部分:串口读写SDRAM控制器

本实验工程通过串口和SDRAM控制器实现对SDRAM的数据读写操作,所以最顶层由串口收发功能实现部分和SDRAM控制器构成,此为第三部分。 第三部分整体框图,具体见图 53‑22。

SDRAM023

图 53‑22 uart_sdram模块整体框图

表格 53‑4 第三部分

模块名称

功能描述

uart_sdram

串口读写SDRAM顶层模块

clk_gen

时钟生成模块,为整个工程提供工作时钟

uart_rx

串口数据接收模块,接收串口发送过来的数据

sdram_top

SDRAM控制器,实现SDRAM数据读写

fifo_read

SDRAM读出数据缓存

uart_tx

串口数据发送模块,发送读出SDRAM的数据

由图和表格可知,本部分共调用6个子模块,是调用模块较多的部分。顶层模块uart_sdram,内部实例化5个子功能模块,连接各子模块对应信号,除此之外还包含读有效信号的约束代码(图中未画出),外部与PC机和SDRAM进行数据交互。clk_gen模块为调用的IP核,生成实验工程使用的倍频和相位偏移时钟; uart_rx模块、uart_tx模块为串口数据收发模块,实现工程与PC机之间的的互数据交互;sdram_top模块为SDRAM控制器,是PC机与SDRAM数据交互的纽带;fifo_read模块,将读出SDRAM的数据通过FIFO进行数据缓存和跨时钟域处理,使读出数据被正确传入串口数据接收模块。

通过3个部分的介绍,我们对本实验工程的整体框架应该有了深入的理解,对于各子模块的具体说明、实现方法和相关注意事项,在后文中我们会分别详细介绍。虽然实验工程涉及模块较多,但设计思路清晰,加之后文中我们会对各模块进行详细讲解,经过本章节的学习,相信读者可以理解并掌握SDRAM控制器的实现方法。

对于各子模块的讲解,我们依然按照上文划分的层次,由浅入深、自下而上进行说明,方便读者接受和和掌握。

3.2.3.2. 初始化模块

SDRAM在上电之后,执行正常操作之前需要被初始化,实际上就是对上文提到的SDRAM内部逻辑控制单元进行初始化,初始化成功的SDRAM才可进行后续的其他操作。接下来我们将要学习掌握初始化操作时序,设计、实现并仿真验证初始化模块功能。

初始化操作时序

SDRAM的初始化是芯片上电后必须进行的一项操作,只有进行了初始化操作的SDRAM芯片才可被正常使用。SDRAM的初始化是一套预先定义好的流程,除此之外的其他操作会导致SDRAM出现不可预知的后果。SDRAM初始化操作时序图,具体见图 53‑23。

SDRAM024

图 53‑23 SDRAM初始化时序图

结合SDRAM初始化时序图,列出SDRAM初始化参考流程如下:

  1. 对SDRAM上电,加载稳定时钟信号,CKE设置为高电平;

  2. 等待T=100us的时间,此过程中操作命令保持为空操作命令;

  3. 100us等待结束后,写入预充电命令,A10设置为高电平,对所有L-Bank进行预充电;

  4. 预充电指令写入后,等待tRP时间,此过程中操作命令保持为空操作命令;

  5. tRP等待时间结束后,写入自动刷新命令;

  6. 自动刷新命令写入后,等待tRC时间,此过程中操作命令保持为空操作命令;

  7. tRC等待时间结束后,再次写入自动刷新命令;

  8. 自动刷新命令写入后,等待tRC时间,此过程中操作命令保持为空操作命令;

  9. tRC等待时间结束后,写入模式寄存器配置指令,地址总线A0-A11参数不同辅助模式寄存器不同模式的设置;

  10. 模式寄存器配置指令写入后,等待tMRD时间,此过程中操作命令保持为空操作命令;

  11. tMRD等待时间结束后,SDRAM初始化完成。

注:1.对于tRP、tRC、tMRD等时间参数,不同芯片或速度等级可能存在差异,读者需查阅芯片对应数据手册进行参数设置;

2.T=100us为最小等待时间,我们在使用SDRAM时,等待时间T可适当延长;

3.初始化过程中,至少进行两次自动刷新,也可适当增加刷新次数。

初始化模块框图

在上一小节中,我们了解SDRAM的初始化的参考流程,本小节中我们介绍一下初始化模块的整体框图和输入输出信号的具体功能。模块框图和端口功能描述具体见图 53‑24、表格 53‑5。

SDRAM025

图 53‑24 初始化模块框图

表格 53‑5 初始化模块输入输出信号功能描述

信号 |

宽 | 类型

功能描述

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可进行其他操作 |

初始化模块波形图

在上一小节中,我们对初始化模块框图和输入输出信号做了相关介绍,对于模块的功能实现并未说明,那么在本小节中,我们结合初始化时序图和初始化流程绘制一下初始化模块的波形图,详细介绍一下模块功能的实现方法。初始化模块整体波形图,具体见图 53‑25。

SDRAM026

图 53‑25 初始化模块整体波形图

上图为SDRAM初始化模块整体波形图,看到此图,读者可能一头雾水,图中诸多信号的波形是如何绘制的,设计思路又是什么?读者不必惊慌,接下来我们会详细讲解各信号波形的设计思路 。

第一部分: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)时保持一个时钟高电平,作为等待时间结束的标志信号。两信号参考波形图如下。

SDRAM027

图 53‑26 cnt_200us、wait_end信号波形图

第二部分:状态机相关信号的波形设计与实现

参照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为模式寄存器配置等待结束信 号。

讲到这里,我们已经对上述声明的6路信号的设计思路有了具体的了解,现在开始各信号波形图的绘制,各信号参考波形图如下图所示。

SDRAM028

图 53‑27 状态机相关信号波形图

第三部分: 自动刷新计数器cnt_init_aref信号的波形设计与实现

SDRAM的初始化过程中,需要进行不小于两次的自动刷新操作,那么如何确定自动刷新次数呢?由上一部分可知,一个完整的自动刷新操作需要经过两个状态,INIT_AR(自动刷新状态)、INIT_TRF(自动刷新等待状态),所以我们可以对自动刷新状态进行计数,每当状态机跳转到INIT_AR(自动刷新状态)状态 时,计数一次,达到设定的刷新次数,且自动刷新等待状态完成,才可进行模式寄存器的配置。所以,声明自动刷新状态计数器cnt_init_aref,初值为0,只有状态处于自动刷新状态,计数器加1,其他状态保持计数值不变,信号波形如下。

SDRAM029

图 53‑28 cnt_init_aref信号波形图

第四部分:各输出信号的波形设计与实现

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可以进行其他操作。

由此可得,各输出信号波形图如下。

SDRAM030

图 53‑29 输出信号波形图

此处需要注意的是,指令信号init_cmd、逻辑Bank地址init_ba和地址总线init_addr均使用时序逻辑赋值,数据会在对应状态的下一个时钟写入。

将上述各部分的信号波形图整合到一起,就得到了图 53‑25,本模块的整体波形图。

注:因篇幅原因,波形图中只绘制了两次自动刷新过程。为保证SDRAM的成功初始化,在本实验对SDRAM初始化的过程中,我们将上电后的等待时间设置为200us,自动刷新次数定为8次。

代码编写

在上一小节,我们结合初始化时序图和初始化流程绘制了初始化的波形图,并对波形中的输入输出信号和内部声明变量做了详细说明。接下来,我们依照绘制的波形图,编写初始化模块的参考代码。初始化参考代码,具体见代码清单 53‑1。

代码清单 53‑1 初始化模块参考代码(sdram_init.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
module sdram_init
(
input wire sys_clk , //系统时钟,频率100MHz
input wire sys_rst_n , //复位信号,低电平有效

output reg [3:0] init_cmd , //初始化阶段写入sdram的指令
output reg [1:0] init_ba , //初始化阶段Bank地址
output reg [12:0] init_addr , //初始化阶段地址数据,辅助预充电
//和配置模式寄存器操作,A12-A0
output wire init_end //初始化结束信号
);

////
//\* Parameter and Internal Signal \//
////

// 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 , //预充电等待 tRP
INIT_AR = 3'b010 , //自动刷新
INIT_TRF = 3'b100 , //自动刷新等待 tRC
INIT_MRS = 3'b101 , //模式寄存器设置
INIT_TMRD = 3'b111 , //模式寄存器设置等待 tMRD
INIT_END = 3'b110 ; //初始化完成
parameter TRP_CLK = 3'd2 , //预充电等待周期,20ns
TRC_CLK = 3'd7 , //自动刷新等待,70ns
TMRD_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 ; //初始化过程自动刷新次数计数器

////
//\* Main Code \//
////

//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;
else
cnt_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;
else
cnt_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;
else
cnt_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;
else
case(init_state)
INIT_IDLE: //系统上电后,在初始状态等待200us跳转到预充电状态
if(wait_end == 1'b1)
init_state <= INIT_PRE;
else
init_state <= init_state;
INIT_PRE: //预充电状态,直接跳转到预充电等待状态
init_state <= INIT_TRP;
INIT_TRP: //预充电等待状态,等待结束,跳转到自动刷新状态
if(trp_end == 1'b1)
init_state <= INIT_AR;
else
init_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;
else
init_state <= INIT_AR;
else
init_state <= init_state;
INIT_MRS: //模式寄存器设置状态,直接跳转到模式寄存器设置等待状态
init_state <= INIT_TMRD;
INIT_TMRD: //模式寄存器设置等待状态,等待结束,跳到初始化完成状态
if(tmrd_end == 1'b1)
init_state <= INIT_END;
else
init_state <= init_state;
INIT_END: //初始化完成状态,保持此状态
init_state <= INIT_END;
default: init_state <= INIT_IDLE;
endcase

//cnt_clk_rst:时钟周期计数复位标志
always@(*)
begin
case (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;
endcase
end

//SDRAM操作指令控制
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
init_cmd <= NOP;
init_ba <= 2'b11;
init_addr <= 13'h1fff;
end
else
case(init_state)
INIT_IDLE,INIT_TRP,INIT_TRF,INIT_TMRD: //执行空操作指令
begin
init_cmd <= NOP;
init_ba <= 2'b11;
init_addr <= 13'h1fff;
end
INIT_PRE: //预充电指令
begin
init_cmd <= P_CHARGE;
init_ba <= 2'b11;
init_addr <= 13'h1fff;
end
INIT_AR: //自动刷新指令
begin
init_cmd <= AUTO_REF;
init_ba <= 2'b11;
init_addr <= 13'h1fff;
end
INIT_MRS: //模式寄存器设置指令
begin
init_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初始化完成
begin
init_cmd <= NOP;
init_ba <= 2'b11;
init_addr <= 13'h1fff;
end
default:
begin
init_cmd <= NOP;
init_ba <= 2'b11;
init_addr <= 13'h1fff;
end
endcase

endmodule

在波形图绘制小结中,我们已经对初始化模块各信号做了详细的介绍,参考代码是参照绘制的波形图编写而成,而且参考代码中有极为详细的注释说明,对于参考代码,相信读者能够很好理解,故在此不再对参考代码进行讲解。

仿真代码编写

初始化模块参考代码编写完成后,为验证参考代码是否能够到到预期效果实现SDRAM的初始化,编写仿真参考代码对初始化模块参考代码进行仿真验证。仿真参考代码,具体见代码清单 53‑2。

代码清单 53‑2 初始化仿真文件参考代码(tb_sdram_init.v)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
\`timescale 1ns/1ns
module tb_sdram_init();

////
//\* Internal Signal and Defparam \//
////

//wire define
//clk_gen
wire clk_50m ; //PLL输出50M时钟
wire clk_100m ; //PLL输出100M时钟
wire clk_100m_shift ; //PLL输出100M时钟,相位偏移-30deg
wire locked ; //PLL时钟锁定信号
wire rst_n ; //复位信号,低有效
//sdram_init
wire [3:0] init_cmd ; //初始化阶段指令
wire [1:0] init_ba ; //初始化阶段L-Bank地址
wire [12:0] init_addr ; //初始化阶段地址总线
wire init_end ; //初始化完成信号

//reg define
reg sys_clk ; //系统时钟
reg sys_rst_n ; //复位信号

//defparam
//重定义仿真模型中的相关参数
defparam sdram_model_plus_inst.addr_bits = 13; //地址位宽
defparam sdram_model_plus_inst.data_bits = 16; //数据位宽
defparam sdram_model_plus_inst.col_bits = 9; //列地址位宽
defparam sdram_model_plus_inst.mem_sizes = 2*1024*1024; //L-Bank容量

////
//\* Clk And Rst \//
////

//时钟、复位信号
initial
begin
sys_clk = 1'b1 ;
sys_rst_n <= 1'b0 ;
#200
sys_rst_n <= 1'b1 ;
end

always #10 sys_clk = ~sys_clk;

//rst_n:复位信号
assign rst_n = sys_rst_n & locked;

////
//\* Instantiation \//
////

//------------- clk_gen_inst -------------
clk_gen clk_gen_inst (
.inclk0 (sys_clk ),
.areset (~sys_rst_n ),
.c0 (clk_50m ),
.c1 (clk_100m ),
.c2 (clk_100m_shift ),

.locked (locked )
);

//------------- sdram_init_inst -------------
sdram_init sdram_init_inst(

.sys_clk (clk_100m ),
.sys_rst_n (rst_n ),

.init_cmd (init_cmd ),
.init_ba (init_ba ),
.init_addr (init_addr ),
.init_end (init_end )

);

//-------------sdram_model_plus_inst-------------
sdram_model_plus sdram_model_plus_inst(
.Dq ( ),
.Addr (init_addr ),
.Ba (init_ba ),
.Clk (clk_100m_shift ),
.Cke (1'b1 ),
.Cs_n (init_cmd[3] ),
.Ras_n (init_cmd[2] ),
.Cas_n (init_cmd[1] ),
.We_n (init_cmd[0] ),
.Dqm (2'b0 ),
.Debug (1'b1 )

);

endmodule

由仿真参考代码可知,仿真模块模拟生成50MHz系统时钟和低有效复位信号,实例化三个子模块。

clk_gen为调用的时钟IP核,输入仿真模块生成的模拟时钟信号,输出初始化模块系统时钟clk_100m和SDRAM仿真模型时钟clk_100m_shift,clk_100m_shift时钟频率与clk_100m相同,相位超前30度,以保证SDRAM时钟上升沿能够采集到稳定的数据、地址、指令等信息;

输出的locked信号与模拟生成的复位信号sys_rst_n取与,得到初始化模块复位信号rst_n,loclked信号有效说明IP核输出时钟稳定,使用rst_n做复位信号可以保证模块正常工作时,输入时钟稳定;初始化模块sdram_init为初始化模块,此处无需过多介绍; sdram_model_plus为SDRAM仿真模型,模拟SDRAM芯片工作,可打印SDRAM当前状态信息,检验SDRAM初始化是否成功。

仿真波形分析

仿真参考代码编写完成,使用ModelSim对仿真代码进行仿真,仿真结果如下。

SDRAM031

图 53‑30 SDRAM仿真模型打印信息

由SDRAM仿真模型打印信息显示,SDRAM仿真模型在初始化模块控制下,完成初始化操作,此过程中执行1次预充电,8次自动刷新,随后进行模式寄存器的配置,设置潜伏期参数为3,使用页突发方式,突发类型为顺序型,写突发模式为设置的突发长度,模式配置与参考代码中设置模式一致。

SDRAM032

图 53‑31 初始化模块整体仿真波形图

整体仿真波形图与绘制波形图各信号波形一致,而且从仿真波形图可以看出,模式寄存器配置等待状态过后,状态机跳转到初始化完成状态,初始化结束信号(int_end)拉高,初始化完成。与SDRAM仿真模型打印信息相结合,初始化成功且模式配置正确,参考代码可正确实现SDRAM的初始化。

初始化模块局部各状态仿真波形图,具体见图 53‑32、图 53‑33、图 53‑34。

SDRAM033

图 53‑32 预充电阶段仿真波形图

SDRAM034

图 53‑33 自动刷新阶段仿真波形图

SDRAM035

图 53‑34 模式寄存器配置阶段和初始化完成阶段仿真波形图

3.2.3.3. 自动刷新模块

在前文中我们提到,SDRAM内部存储体是利用电容能够保持电荷以及可充放电的特性制成,而电容所存储的电荷会随时间不断流失,会造成存储数据的丢失。为保证SDRAM中数据的可靠性,需要对SDRAM进行不断刷新。那么,在本小节我们将会学习掌握自动刷新的实现方法,设计并仿真验证自动刷新模块,以保证SDRAM存 储数据的可靠性。

自动刷新操作时序

在进行自动刷新模块设计之前,我们先来了解一下SDRAM刷新操作的两种方式。

SDRAM的刷新方式分为自刷新和自动刷新两种,这两种刷新方式,在实现和作用上存在差异。

自动刷新模式:作用是在SDRAM的正常操作过程中,保证数据不丢失,自动刷新过程需要外部时钟的参与,但刷新行地址由内部刷新计数器控制,无需外部写入。

自刷新模式则主要用于休眠模式低功耗状态下的数据保存,自刷新过程无需外部时钟参与,与自动刷新相同的是,刷新行地址由内部刷新计算器控制,无需外部写入。

两者的操作命令相同,当CKE信号保持高电平时,写入刷新指令,进入自动刷新模式;当CKE信号为低电平时,写入刷新指令,进入自刷新模式。

自刷新模式下,除CKE之外的其他外部信号均无效,当CKE再次拉高时,退出自刷新模式,进入正常操作状态。两种刷新方式时序图,具体见图 53‑35、图 53‑36。

SDRAM036

图 53‑35 SDRAM自动刷新时序图

SDRAM037

图 53‑36 SDRAM自刷新时序图

在本章节实验中,我们是在SDRAM正常模式下进行刷新操作,要使用自动刷新方式。在此我们只对SDRAM的自动刷新操作的时序图和参考流程进行讲解。若读者对自刷新操作的相关知识感兴趣,可自行查阅芯片数据手册或相关资料。

由SDRAM自动刷新时序图可知,SDRAM的自动刷新类似于简化版的初始化操作,只是缺少了上电后的等待时间和模式寄存器配置部分,只包含一次预充电操作和两次自动刷新操作,自动刷新操作参考流程如下。

  1. 写入预充电命令,A10设置为高电平,对所有L-Bank进行预充电;

  2. 预充电指令写入后,等待tRP时间,此过程中操作命令保持为空操作命令;

  3. tRP等待时间结束后,写入自动刷新命令;

  4. 自动刷新命令写入后,等待tRC时间,此过程中操作命令保持为空操作命令;

  5. tRC等待时间结束后,再次写入自动刷新命令;

  6. 自动刷新命令写入后,等待tRC时间,此过程中操作命令保持为空操作命令;

  7. tRC等待时间结束后,自动刷新操作完成。

注:对于tRP、tRC等时间参数,不同芯片可能存在差异,读者需查阅芯片对应数据手册。

自动刷新模块框图

在学习了SDRAM自动刷新时序和参考流程之后,我们来设计一下自动刷新模块的整体框架。自动刷新模块整体框图和输入输出信号的功能描述,具体见图 53‑37、表格 53‑6。

SDRAM038

图 53‑37 自动刷新模块框图

表格 53‑6 自动刷新模块输入输出信号功能描述

信号

位宽

类型

功能描述

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

自动刷新结束标志

输入信号:init_end为初始化模块传入的初始化完成标志信号,表示初始化完成,SDRAM进入正常模式,可进行其他操作;aref_en为自动刷新使能信号,表示仲裁模块响应自动刷新请求,自动刷新模块可以开始自动刷新操作。

输出信号:aref_cmd、aref_ba、aref_addr分别为自动刷新阶段的指令信号、逻辑Bank地址和地址总线;aref_req为自动刷新请求信号,向仲裁模块请求执行自动刷新操作;aref_end为自动刷新结束标志信号,告知仲裁模块自动刷新操作完成,SDRAM可进行其他操作。

自动刷新模块波形图

SDRAM自动刷新操作的刷新时序和参考流程类似于简化版的初始化操作,只保留预充电部分和自动刷新部分。但不同之处在于,初始化操作是一上电就开始执行,且只执行一次;而自动刷新操作是周期性执行,且开始执行时间不定。

由于执行时间的不确定,这就使自动刷新操作实现起来较初始化操作困难一点,接下来我们结合自动刷新时序图和自动刷新参考流程绘制一下初始化模块的波形图,详细说明一下模块功能的实现方法。自动刷新模块整体波形图,具体见图 53‑38。

SDRAM039

图 53‑38 自动刷新模块整体波形图

第一部分:自动刷新周期计数器cnt_ref信号的波形设计与实现

SDRAM的自动刷新操作是周期性执行,那么周期时间的计数就需要一个计数器来实现,所以我们定义一个自动刷新周期计数器cnt_ref。

对于计数周期,在理论知识章节我们详细介绍过,这里我们SDRAM的时钟为100MHz,周期计数器的计数范围设置为0-749,计数时间为7.5us。周期计数器自初始化结束开始循环计数,计数到最大值清零后重新计数。自动刷新周期计数器cnt_ref的信号波形图如下。

SDRAM040

图 53‑39 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,记录自动刷新次数。各信号波形图如下。

SDRAM041

图 53‑40 状态机相关信号波形图

第四部分:各输出信号的波形设计与实现

与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可以进行其他操作。

由此可得,输出信号波形图如下。

SDRAM042

图 53‑41 输出信号波形图

此处需要注意的是,指令信号aref_cmd使用时序逻辑赋值,数据会在对应状态的下一个时钟写入。

将上述各部分波形图整合起来,就得到图 53‑38,模块整体波形图。

代码编写

参照自动刷新模块波形图,编写本模块的参考代码。自动刷新模块参考代码,具体见代码清单 53‑3。

代码清单 53‑3 自动刷新模块参考代码(sdram_a_ref.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
module sdram_a_ref
(
input wire sys_clk , //系统时钟,频率100MHz
input wire sys_rst_n , //复位信号,低电平有效
input wire init_end , //初始化结束信号
input wire aref_en , //自动刷新使能

output reg aref_req , //自动刷新请求
output reg [3:0] aref_cmd , //自动刷新阶段写入sdram的指令
output reg [1:0] aref_ba , //自动刷新阶段Bank地址
output reg [12:0] aref_addr , //地址数据,辅助预充电操作,A12-A0
output wire aref_end //自动刷新结束标志
);

////
//\* Parameter and Internal Signal \//
////

//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 , //预充电等待 tRP
AUTO_REF = 3'b010 , //自动刷新状态
AREF_TRF = 3'b100 , //自动刷新等待 tRC
AREF_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 ; //自动刷新次数计数器

////
//\* Main Code \//
////

//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;
else
cnt_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;
else
cnt_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;
else
case(aref_state)
AREF_IDLE:
if((aref_en == 1'b1) && (init_end == 1'b1))
aref_state <= AREF_PCHA;
else
aref_state <= aref_state;
AREF_PCHA:
aref_state <= AREF_TRP;
AREF_TRP:
if(trp_end == 1'b1)
aref_state <= AUTO_REF;
else
aref_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;
else
aref_state <= AUTO_REF;
else
aref_state <= aref_state;
AREF_END:
aref_state <= AREF_IDLE;
default:
aref_state <= AREF_IDLE;
endcase

//cnt_clk_rst:时钟周期计数复位标志
always@(*)
begin
case (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;
endcase
end

//SDRAM操作指令控制
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
aref_cmd <= NOP;
aref_ba <= 2'b11;
aref_addr <= 13'h1fff;
end
else
case(aref_state)
AREF_IDLE,AREF_TRP,AREF_TRF: //执行空操作指令
begin
aref_cmd <= NOP;
aref_ba <= 2'b11;
aref_addr <= 13'h1fff;
end
AREF_PCHA: //预充电指令
begin
aref_cmd <= P_CHARGE;
aref_ba <= 2'b11;
aref_addr <= 13'h1fff;
end
AUTO_REF: //自动刷新指令
begin
aref_cmd <= A_REF;
aref_ba <= 2'b11;
aref_addr <= 13'h1fff;
end
AREF_END: //一次自动刷新完成
begin
aref_cmd <= NOP;
aref_ba <= 2'b11;
aref_addr <= 13'h1fff;
end
default:
begin
aref_cmd <= NOP;
aref_ba <= 2'b11;
aref_addr <= 13'h1fff;
end
endcase

endmodule

注:在本模块的参考代码中,自动刷新时钟计数的最大值CNT_AREF_MAX为749,系统时钟为100MHz,cnt_aref计数时间为7.5us,即两次自动刷新时间间隔为7.5us。

参考代码是参照模块波形图编写,对于代码内的诸多信号和实现方法,波形图绘制小节有详细说明,读者若有疑问可参阅波形图绘制章节,此处不再说明。

仿真代码编写

自动刷新模块参考代码编写完成后,为检验代码是否能够实现自动刷新功能,编写仿真代码对其进行仿真检验。仿真参考代码,具体见代码清单 53‑4。

代码清单 53‑4 自动刷新模块仿真参考代码(tb_sdram_a_ref.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
\`timescale 1ns/1ns
module tb_sdram_a_ref();

////
//\* Internal Signal and Defparam \//
////

//wire define
//sdram
wire [3:0] sdram_cmd ; //SDRAM操作指令
wire [1:0] sdram_ba ; //SDRAM L-Bank地址
wire [12:0] sdram_addr ; //SDRAM地址总线
//clk_gen
wire clk_50m ; //PLL输出50M时钟
wire clk_100m ; //PLL输出100M时钟
wire clk_100m_shift ; //PLL输出100M时钟,相位偏移-30deg
wire locked ; //PLL时钟锁定信号
wire rst_n ; //复位信号,低有效
//sdram_init
wire [3:0] init_cmd ; //初始化阶段指令
wire [1:0] init_ba ; //初始化阶段L-Bank地址
wire [12:0] init_addr ; //初始化阶段地址总线
wire init_end ; //初始化完成信号
//sdram_a_ref
wire aref_req ; //自动刷新请求
wire aref_end ; //自动刷新结束
wire [3:0] aref_cmd ; //自动刷新阶段指令
wire [1:0] aref_ba ; //自动刷新阶段L-Bank地址
wire [12:0] aref_addr ; //自动刷新阶段地址总线

//reg define
reg sys_clk ; //系统时钟
reg sys_rst_n ; //复位信号
reg aref_en ; //自动刷新使能

//defparam
//重定义仿真模型中的相关参数
defparam sdram_model_plus_inst.addr_bits = 13; //地址位宽
defparam sdram_model_plus_inst.data_bits = 16; //数据位宽
defparam sdram_model_plus_inst.col_bits = 9; //列地址位宽
defparam sdram_model_plus_inst.mem_sizes = 2*1024*1024; //L-Bank容量

////
//\* Clk And Rst \//
////

//时钟、复位信号
initial
begin
sys_clk = 1'b1 ;
sys_rst_n <= 1'b0 ;
#200
sys_rst_n <= 1'b1 ;
end

always #10 sys_clk = ~sys_clk;

//rst_n:复位信号
assign rst_n = sys_rst_n & locked;

//aref_en:自动刷新使能
always@(posedge clk_100m or negedge rst_n)
if(rst_n == 1'b0)
aref_en <= 1'b0;
else if((init_end == 1'b1) && (aref_req == 1'b1))
aref_en <= 1'b1;
else if(aref_end == 1'b1)
aref_en <= 1'b0;

//sdram_cmd,sdram_ba,sdram_addr
assign sdram_cmd = (init_end == 1'b1) ? aref_cmd : init_cmd;
assign sdram_ba = (init_end == 1'b1) ? aref_ba : init_ba;
assign sdram_addr = (init_end == 1'b1) ? aref_addr : init_addr;

////
//\* Instantiation \//
////

//------------- clk_gen_inst -------------
clk_gen clk_gen_inst (
.inclk0 (sys_clk ),
.areset (~sys_rst_n ),
.c0 (clk_50m ),
.c1 (clk_100m ),
.c2 (clk_100m_shift ),

.locked (locked )
);

//------------- sdram_init_inst -------------
sdram_init sdram_init_inst(

.sys_clk (clk_100m ),
.sys_rst_n (rst_n ),

.init_cmd (init_cmd ),
.init_ba (init_ba ),
.init_addr (init_addr ),
.init_end (init_end )

);

//------------- sdram_a_ref_inst -------------
sdram_a_ref sdram_a_ref_inst(

.sys_clk (clk_100m ),
.sys_rst_n (rst_n ),
.init_end (init_end ),
.aref_en (aref_en ),

.aref_req (aref_req ),
.aref_cmd (aref_cmd ),
.aref_ba (aref_ba ),
.aref_addr (aref_addr ),
.aref_end (aref_end )

);

//-------------sdram_model_plus_inst-------------
sdram_model_plus sdram_model_plus_inst(
.Dq ( ),
.Addr (sdram_addr ),
.Ba (sdram_ba ),
.Clk (clk_100m_shift ),
.Cke (1'b1 ),
.Cs_n (sdram_cmd[3] ),
.Ras_n (sdram_cmd[2] ),
.Cas_n (sdram_cmd[1] ),
.We_n (sdram_cmd[0] ),
.Dqm (2'b0 ),
.Debug (1'b1 )

);

endmodule

本模块的仿真参考代码,是以初始化模块的仿真参考代码为基础修改而来,内部增加了自动刷新模块实例化,自动刷新模块系统时钟与复位信号与初始化模块相同;以初始化完成信号为判断条件,在初始化阶段和自动刷新阶段分别写入SDRAM对应阶段的指令、地址信息。代码理解起来较为容易,不再过多叙述。

仿真波形分析

仿真参考代码编写完成后,我们使用ModelSim进行模块波形仿真,接下来我们结合部分截图分析和验证一下仿真结果。

SDRAM043

图 53‑42 SDRAM仿真模型打印信息

由仿真模型打印信息可知,SDRAM仿真模型在初始化模块的控制下完成初始化,初始化完成后,开始进行周期性自动刷新,两次自动刷新的时间间隔为7.5us,与参考代码设定的时间参数一致,自动刷新模块工作正常,参考代码正确。

SDRAM044

图 53‑43 自动刷新模块整体仿真波形图

结合图 53‑42、图 53‑43可知,仿真波形与绘制波形图个信号波形或参数一致,自动刷新模块通过仿真验证。

3.2.3.4. 数据写模块

数据写操作时序

本章节的目标就是通过所学知识设计一个简易的SDRAM读写控制器,那么对于SDRAM的读写操作必须要理解掌握。学习过SDRAM初始化和自动刷新操作后,在本小节我们开始数据写操作的学习。

由于突发长度、突发类型等参数可设置,以及可选取是否进行自动预充电操作等诸多原因,SDRAM数据写操作包含多种操作方式。在本章节的SDRAM控制器的实验中,我们使用的是SDRAM的不进行自动预充电的页突发写模式,所以我们只针对这一数据写入模式做一下讲解,其他写入模式不再说明,读者若有兴趣,请自行查阅S DRAM芯片数据手册。页突发写模式时序图,具体见图 53‑44。

SDRAM045

图 53‑44 SDRAM页突发写模式时序图

结合图 53‑44页突发写模式时序图,我们来说一下SDRAM页突发写操作的流程。

  1. 发送激活指令到SDRAM,同时BA0-BA1、A0-A12分别写入L-Bank地址、行地址,激活特定L-Bank的特定行;

  2. 激活指令写入后,等待tRCD时间,此过程中操作命令保持为空操作命令;

  3. tRCD等待时间结束后,写入写数据指令,同时A0-A8写入待数据的列首地址;

  4. 读数据指令写入同时,由DQ开始写入数据,在最后一个数据写入后的下一个时钟写入突发终止指令;

  5. 突发终止指令写入后, SDRAM的页突发写操作完成。

注:1.对于tRCD等时间参数,不同芯片可能存在差异,读者需查阅芯片对应数据手册;

2.页突发的突发长度数值上等于SDRAM一行包含的存储单元的个数,视芯片具体设计而定。

数据写模块框图

在上一小节中我们介绍了数据写操作的参考流程,接下来我们根据参考流程设计数据写模块。首先先来介绍一下数据写模块框图和输入输出信号的功能描述,数据写模块框图、输入输出信号功能描述,具体见图 53‑45、表格 53‑7。

SDRAM046

图 53‑45 数据写模块框图

表格 53‑7 数据写模块输入输出信号功能描述

信号

位宽

类型

功能描述

sys_clk

1Bit

Input

系统时钟,频率100MHz

sys_rst_n

1Bit

Input

复位信号,低有效

init_end

1Bit

Input

初始化完成信号

wr_en

1Bit

Input

数据写使能信号

wr_addr

24Bit

Input

数据写阶段地址输入

wr_data

16Bit

Input

数据写阶段数据输入

wr_burst_len

10Bit

Input

写突发长度

wr_ack

1Bit

Output

数据写操作响应

wr_end

1Bit

Output

一次突发写结束

write_cmd

4Bit

Output

数据写阶段指令

write_ba

2Bit

Output

数据写阶段逻辑Bank地址

write_addr

13Bit

Output

数据写阶段地址输出

wr_sdram_en

1Bit

Output

数据写阶段数据输出使能

wr_sdram_data

16Bit

Output

数据写阶段数据输出

输入信号:init_end为初始化模块传入的初始化完成标志信号,表示初始化完成,SDRAM进入正常模式,可进行其他操作;wr_en为数据写使能信号,表示仲裁模块响应数据写请求,数据写模块可以开始数据写入操作;wr_addr为24位宽的地址信号,由fifo_ctrl模块生成并传入,高2位为数据写入位置 的逻辑Bank地址,中间13位为数据写入位置行地址,后9位为数据突发写入首地址;wr_data为待写入数据,以数据写操作响应信号wr_ack信号为使能,自fifo_ctrl模块中写fifo读取;wr_burst_len为写入数据突发长度,具体数值可根据实际情况设定,但不能超过SDRAM芯片一行包含存 储单元的个数。

输出信号:wr_ack为数据写操作响应信号,表示数据写模块响应了wr_en写使能信号,同时wr_ack信号也作为fifo_ctrl模块中写fifo的读使能信号,读出数据为本模块输入数据信号wr_data;wr_end为一次写突发结束标志,告知仲裁模块一次写突发完成,SDRAM可进行其他操作;writ e_cmd、write_ba、write_addr分别为数据写阶段的指令信号、逻辑Bank地址和地址总线; wr_sdram_en、wr_sdram_data为写数据使能信号和待写入数据,只有在写数据使能信号有效时,才能将待写入数据写入SDRAM 数据总线。

数据写模块波形图

数据写模块设计完毕,下面开始波形图的绘制及波形讲解。数据写模块波形图,具体见图 53‑46。

SDRAM047

图 53‑46 数据写模块波形图

第一部分:状态机相关信号的设计与实现

在数据写模块,我们仍然使用状态机实现模块功能。首先声明状态机状态变量write_state,定义状态机个状态名称:初始化状态(WR_IDLE)、激活状态(WR_ACTIVE)、激活等待状态(WR_TRCD)、写指令状态(WR_WRITE)、写数据状态(WR_DATA)、预充电状态(WR_PRE)、预 充电等待状态 (WR_TRP)、写结束状态(WR_END)。

对于状态机跳转条件,我们仍然沿用之前的方法,声明时钟周期计数器cnt_clk、时钟周期计数清零信号cnt_clk_rst、预充电等待状态结束标志信号trp_end、激活等待状态结束标志信号trcd_end和写数据结束标志信号twrite_end,以此为约束条件,实现状态机的状态跳转。

系统上电后,write_state变量保持在初始状态(WR_IDLE),当初始化完成(init_end为高电平)且外部传入的数据写使能有效(wr_en为高电平),状态机跳转到激活状态(WR_ACTIVE),随即跳转到激活等待状态(WR_TRCD),在此状态等待TRCD_CLK(设定值为2)个时钟周期 ,激活完成,状态跳转到写指令状态(WR_WRITE),随即跳转到写数据状态(WR_DATA),在此状态等待wr_burst_len 个时钟周期,一次写突发完成,状态跳转到预充电状态(WR_PRE),随即状态跳转预充电等待状态 (WR_TRP),在此状态等待TRP_CLK(设定值为2)个时钟周期,预充电完成,激活行被关闭,状态跳转到写结束状态(WR_END),随机跳回初始状态(WR_IDLE)等待下一次数据写操作。

状态机相关信号波形图如下。

SDRAM048

图 53‑47 状态机相关信号波形图

第二部分:数据写响应信号wr_ack的设计与实现

在这里之所以声明数据写响应信号wr_ack,目的有二,一是要告知fifo_ctrl模块,一次突发写操作已经完成,页突发写操作首地址可以更新;二是数据写响应信号也作为fifo_ctrl模块中写fifo的读使能信号,读出数据为本模块输入数据信号wr_data。

因为fifo_ctrl模块中调用的写fifo为普通fifo,所以在送入读使能信号后,要在下一个时钟周期才会有数据输出,为了使输入的wr_data数据信号与数据写状态同步,防止错误发送,所以wr_ack信号超前数据写状态(WR_DATA)一个时钟周期。信号波形图如下。

SDRAM049

图 53‑48 wr_ack信号波形图

第三部分:各输出信号的设计与实现

想要实现对SDRAM 的数据写入,输出信号必须包含数据写阶段的指令信号(write_cmd)、逻辑Bank地址(write_ba)、地址总线(write_addr)以及待写入数据(wr_data)。

在激活状态,给指令信号写入激活指令,逻辑Bank地址、地址总线写入要激活的Bank地址和行地址,激活特定逻辑Bank的特定行;在写状态,给指令信号写入数据写指令,逻辑Bank地址、地址总线写入要进行突发写操作的Bank地址和列首地址,并写入第一个数据给wr_data;在写数据状态,数据写状态要保持写 突发参数(wr_burst_len)个时钟周期,逻辑Bank地址、地址总线均保持全1,前(wr_burst_len-1)个时钟周期,指令信号写入无操作指令,最后一个时钟写入突发终止指令,终止数据写操作;在预充电状态,指令信号写入预充电指令,对所有Bank进行预充电,关闭激活行。

输出数据写操作结束信号wr_end,告知仲裁模块数据写操作结束,可执行其他操作。同时要输出写数据使能信号wr_sdram_en,因为SDRAM的数据接口为输入/输出双向接口,在数据写入时,做输入端口;数据读取时,做输出端口。我们只在数据写入时对端口进行数据写入,其他时刻不做任何操作。各信号波形图如下 。

SDRAM050

图 53‑49 各输出信号波形

此处需要注意的是,指令信号、逻辑Bank地址和地址总线均使用时序逻辑赋值,数据会在对应状态的下一个时钟写入。

整合上述各部分波形图,即可得到本模块整体波形图。

代码编写

参照数据写操作波形图,编写数据写模块参考代码。数据写模块参考代码,具体见代码清单 53‑5。

代码清单 53‑5 数据写操作模块参考代码(sdram_write.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
module sdram_write
(
input wire sys_clk , //系统时钟,频率100MHz
input wire sys_rst_n , //复位信号,低电平有效
input wire init_end , //初始化结束信号
input wire wr_en , //写使能
input wire [23:0] wr_addr , //写SDRAM地址
input wire [15:0] wr_data , //待写入SDRAM的数据
input wire [9:0] wr_burst_len , //写突发SDRAM字节数

output wire wr_ack , //写SDRAM响应信号
output wire wr_end , //一次突发写结束
output reg [3:0] write_cmd , //写数据阶段写入sdram的指令
output reg [1:0] write_ba , //写数据阶段Bank地址
output reg [12:0] write_addr , //地址数据,辅助预充电操作
output reg wr_sdram_en , //数据总线输出使能
output wire [15:0] wr_sdram_data //写入SDRAM的数据
);

////
//\* Parameter and Internal Signal \//
////

//parameter define
parameter TRCD_CLK = 10'd2 , //激活周期
TRP_CLK = 10'd2 ; //预充电周期
parameter WR_IDLE = 4'b0000 , //初始状态
WR_ACTIVE = 4'b0001 , //激活
WR_TRCD = 4'b0011 , //激活等待
WR_WRITE = 4'b0010 , //写操作
WR_DATA = 4'b0100 , //写数据
WR_PRE = 4'b0101 , //预充电
WR_TRP = 4'b0111 , //预充电等待
WR_END = 4'b0110 ; //一次突发写结束
parameter NOP = 4'b0111 , //空操作指令
ACTIVE = 4'b0011 , //激活指令
WRITE = 4'b0100 , //数据写指令
B_STOP = 4'b0110 , //突发停止指令
P_CHARGE = 4'b0010 ; //预充电指令

//wire define
wire trcd_end ; //激活等待周期结束
wire twrite_end ; //突发写结束
wire trp_end ; //预充电有效周期结束

//reg define
reg [3:0] write_state ; //SDRAM写状态
reg [9:0] cnt_clk ; //时钟周期计数,记录写数据阶段各状态等待时间
reg cnt_clk_rst ; //时钟周期计数复位标志

////
//\* Main Code \//
////

//wr_end:一次突发写结束
assign wr_end = (write_state == WR_END) ? 1'b1 : 1'b0;

//wr_ack:写SDRAM响应信号
assign wr_ack = ( write_state == WR_WRITE)
\|\| ((write_state == WR_DATA)
&& (cnt_clk <= (wr_burst_len - 2'd2)));

//cnt_clk:时钟周期计数,记录初始化各状态等待时间
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_clk <= 10'd0;
else if(cnt_clk_rst == 1'b1)
cnt_clk <= 10'd0;
else
cnt_clk <= cnt_clk + 1'b1;

//trcd_end,twrite_end,trp_end:等待结束标志
assign trcd_end = ((write_state == WR_TRCD)
&&(cnt_clk == TRCD_CLK )) ? 1'b1 : 1'b0;//激活周期结束
assign twrite_end = ((write_state == WR_DATA)
&&(cnt_clk == wr_burst_len - 1)) ? 1'b1 : 1'b0;//突发写结束
assign trp_end = ((write_state == WR_TRP )
&&(cnt_clk == TRP_CLK )) ? 1'b1 : 1'b0;//预充电等待周期结束

//write_state:SDRAM的工作状态机
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
write_state <= WR_IDLE;
else
case(write_state)
WR_IDLE:
if((wr_en ==1'b1) && (init_end == 1'b1))
write_state <= WR_ACTIVE;
else
write_state <= write_state;
WR_ACTIVE:
write_state <= WR_TRCD;
WR_TRCD:
if(trcd_end == 1'b1)
write_state <= WR_WRITE;
else
write_state <= write_state;
WR_WRITE:
write_state <= WR_DATA;
WR_DATA:
if(twrite_end == 1'b1)
write_state <= WR_PRE;
else
write_state <= write_state;
WR_PRE:
write_state <= WR_TRP;
WR_TRP:
if(trp_end == 1'b1)
write_state <= WR_END;
else
write_state <= write_state;

WR_END:
write_state <= WR_IDLE;
default:
write_state <= WR_IDLE;
endcase

//计数器控制逻辑
always@(*)
begin
case(write_state)
WR_IDLE: cnt_clk_rst <= 1'b1;
WR_TRCD: cnt_clk_rst <= (trcd_end == 1'b1) ? 1'b1 : 1'b0;
WR_WRITE:cnt_clk_rst <= 1'b1;
WR_DATA: cnt_clk_rst <= (twrite_end == 1'b1) ? 1'b1 : 1'b0;
WR_TRP: cnt_clk_rst <= (trp_end == 1'b1) ? 1'b1 : 1'b0;
WR_END: cnt_clk_rst <= 1'b1;
default: cnt_clk_rst <= 1'b0;
endcase
end

//SDRAM操作指令控制
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
write_cmd <= NOP;
write_ba <= 2'b11;
write_addr <= 13'h1fff;
end
else
case(write_state)
WR_IDLE,WR_TRCD,WR_TRP:
begin
write_cmd <= NOP;
write_ba <= 2'b11;
write_addr <= 13'h1fff;
end
WR_ACTIVE: //激活指令
begin
write_cmd <= ACTIVE;
write_ba <= wr_addr[23:22];
write_addr <= wr_addr[21:9];
end
WR_WRITE: //写操作指令
begin
write_cmd <= WRITE;
write_ba <= wr_addr[23:22];
write_addr <= {4'b0000,wr_addr[8:0]};
end
WR_DATA: //突发传输终止指令
begin
if(twrite_end == 1'b1)
write_cmd <= B_STOP;
else
begin
write_cmd <= NOP;
write_ba <= 2'b11;
write_addr <= 13'h1fff;
end
end
WR_PRE: //预充电指令
begin
write_cmd <= P_CHARGE;
write_ba <= wr_addr[23:22];
write_addr <= 13'h0400;
end
WR_END:
begin
write_cmd <= NOP;
write_ba <= 2'b11;
write_addr <= 13'h1fff;
end
default:
begin
write_cmd <= NOP;
write_ba <= 2'b11;
write_addr <= 13'h1fff;
end
endcase

//wr_sdram_en:数据总线输出使能
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
wr_sdram_en <= 1'b0;
else
wr_sdram_en <= wr_ack;

//wr_sdram_data:写入SDRAM的数据
assign wr_sdram_data = (wr_sdram_en == 1'b1) ? wr_data : 16'd0;

endmodule

数据写模块参考代码是以上文绘制的数据写模块波形图为依据编写而成,对于内部的诸多信号以及各信号约束条件,在波形图绘制小节已详细说明,且参考代码中也有较为详尽的注释,在此不再过多叙述。

仿真代码编写

数据写模块参考代码编写完成后,为检验参考代码的正确性,编写仿真参考使用ModelSim软件对参考代码进行仿真。仿真参考代码,具体见代码清单 53‑6。

代码清单 53‑6 数据写模块仿真参考代码(tb_sdram_write.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
\`timescale 1ns/1ns
module tb_sdram_write();

////
//\* Internal Signal and Defparam \//
////

//wire define
//clk_gen
wire clk_50m ; //PLL输出50M时钟
wire clk_100m ; //PLL输出100M时钟
wire clk_100m_shift ; //PLL输出100M时钟,相位偏移-30deg
wire locked ; //PLL时钟锁定信号
wire rst_n ; //复位信号,低有效
//sdram_init
wire [3:0] init_cmd ; //初始化阶段指令
wire [1:0] init_ba ; //初始化阶段L-Bank地址
wire [12:0] init_addr ; //初始化阶段地址总线
wire init_end ; //初始化完成信号
//sdram_write
wire [12:0] write_addr ; //数据写阶段地址总线
wire [1:0] write_ba ; //数据写阶段L-Bank地址
wire [3:0] write_cmd ; //数据写阶段指令
wire [15:0] wr_sdram_data ; //数据写阶段写入SDRAM数据
wire wr_sdram_en ; //数据写阶段写数据有效使能信号
wire wr_end ; //数据写阶段一次突发写结束
wire sdram_wr_ack ; //数据写阶段写响应
//sdram_addr
wire [12:0] sdram_addr ; //SDRAM地址总线
wire [1:0] sdram_ba ; //SDRAML-Bank地址
wire [3:0] sdram_cmd ; //SDRAM指令
wire [15:0] sdram_dq ; //SDRAM数据总线
//reg define
reg sys_clk ; //系统时钟
reg sys_rst_n ; //复位信号
reg wr_en ; //写使能
reg [15:0] wr_data_in ; //写数据

//defparam
//重定义仿真模型中的相关参数
defparam sdram_model_plus_inst.addr_bits = 13; //地址位宽
defparam sdram_model_plus_inst.data_bits = 16; //数据位宽
defparam sdram_model_plus_inst.col_bits = 9; //列地址位宽
defparam sdram_model_plus_inst.mem_sizes = 2*1024*1024; //L-Bank容量

////
//\* Clk And Rst \//
////

//时钟、复位信号
initial
begin
sys_clk = 1'b1 ;
sys_rst_n <= 1'b0 ;
#200
sys_rst_n <= 1'b1 ;
end

always #10 sys_clk = ~sys_clk;

//rst_n:复位信号
assign rst_n = sys_rst_n & locked;

//wr_en:写数据使能
always@(posedge clk_100m or negedge rst_n)
if(rst_n == 1'b0)
wr_en <= 1'b0;
else if(wr_end == 1'b1)
wr_en <= 1'b0;
else if(init_end == 1'b1)
wr_en <= 1'b1;
else
wr_en <= wr_en;

//wr_data_in:写数据
always@(posedge clk_100m or negedge rst_n)
if(rst_n == 1'b0)
wr_data_in <= 16'd0;
else if(wr_data_in == 16'd10)
wr_data_in <= 16'd0;
else if(sdram_wr_ack == 1'b1)
wr_data_in <= wr_data_in + 1'b1;
else
wr_data_in <= wr_data_in;

//sdram_cmd,sdram_ba,sdram_addr
assign sdram_cmd = (init_end == 1'b1) ? write_cmd : init_cmd;
assign sdram_ba = (init_end == 1'b1) ? write_ba : init_ba;
assign sdram_addr = (init_end == 1'b1) ? write_addr : init_addr;

//wr_sdram_data
assign sdram_dq = (wr_sdram_en == 1'b1) ? wr_sdram_data : 16'hz;

////
//\* Instantiation \//
////

//------------- clk_gen_inst -------------
clk_gen clk_gen_inst (
.inclk0 (sys_clk ),
.areset (~sys_rst_n ),
.c0 (clk_50m ),
.c1 (clk_100m ),
.c2 (clk_100m_shift ),

.locked (locked )
);

//------------- sdram_init_inst -------------
sdram_init sdram_init_inst(

.sys_clk (clk_100m ),
.sys_rst_n (rst_n ),

.init_cmd (init_cmd ),
.init_ba (init_ba ),
.init_addr (init_addr ),
.init_end (init_end )

);

//------------- sdram_write_inst -------------
sdram_write sdram_write_inst(

.sys_clk (clk_100m ),
.sys_rst_n (rst_n ),
.init_end (init_end ),
.wr_en (wr_en ),

.wr_addr (24'h000_000 ),
.wr_data (wr_data_in ),
.wr_burst_len (10'd10 ),

.wr_ack (sdram_wr_ack ),
.wr_end (wr_end ),
.write_cmd (write_cmd ),
.write_ba (write_ba ),
.write_addr (write_addr ),
.wr_sdram_en (wr_sdram_en ),
.wr_sdram_data (wr_sdram_data )

);

//-------------sdram_model_plus_inst-------------
sdram_model_plus sdram_model_plus_inst(
.Dq (sdram_dq ),
.Addr (sdram_addr ),
.Ba (sdram_ba ),
.Clk (clk_100m_shift ),
.Cke (1'b1 ),
.Cs_n (sdram_cmd[3] ),
.Ras_n (sdram_cmd[2] ),
.Cas_n (sdram_cmd[1] ),
.We_n (sdram_cmd[0] ),
.Dqm (2'b0 ),
.Debug (1'b1 )

);

endmodule

数据写模块仿真参考代码是在初始化模块仿真参考代码基础上修改而成,加入数据写模块实例化;增加写使能信号wr_en和写数据信号wr_data_in,作为数据写使能和待写入数据输入数据写模块;以初始化完成信号为约束条件,在不同阶段给SDRAM写入该阶段对应操作指令和地址信息。

仿真波形分析

仿真代码编写完成后,运行ModelSim进行仿真,结合仿真结果进行分析。仿真结果截图,具体见图 53‑50、图 53‑51。

SDRAM051

图 53‑50 SDRAM仿真模型打印信息

SDRAM052

图 53‑51 数据写模块仿真波形图

由SDRAM仿真模型打印信息可知,SDRAM在完成芯片初始化操作后,数据写模块开始工作,并向SDRAM芯片的逻辑Bnak0的第0行的0-9列写入10字节数据,且写入数据与仿真代码提供的待写入数据相同。数据写模块仿真波形图与绘制波形图个信号变化一致,数据写模块参考代码能够正常工作,写入数据正确。数据写 模块设计完成,且仿真验证通过。

3.2.3.5. 数据读模块

数据读流程

SDRAM控制器的数据写操作已经实现,接下来我们来学习一下数据读操作。与SDRAM数据写操作类似,SDRAM的数据读操作也包含多种方式,在本章节的SDRAM控制器的实验中,我们使用的是SDRAM的不带自动预充电的页突发读模式,接下来我们只针对这一数据读取模式做一下时序介绍,其他数据读取模式不再讲解, 读者若有兴趣,请自行查阅数据手册。页突发读模式时序图,具体见图 53‑52。

SDRAM053

图 53‑52 SDRAM页突发读模式时序图

结合图 53‑52页突发读模式时序图,我们来说一下SDRAM页突发读操作的具体流程。

  1. 发送激活指令到SDRAM,同时BA0-BA1、A0-A12分别写入L-Bank地址、行地址,激活特定L-Bank的特定行;

  2. 激活指令写入后,等待tRCD时间,此过程中操作命令保持为空操作命令;

  3. tRCD等待时间结束后,写入读数据指令,同时A0-A8写入数据读取首地址;

  4. 读数据指令写入后,随机跳转到潜伏状态,等待潜伏期结束,DQ开始输出读取数据,设读取数据个数为N;

  5. 自读指令写入周期的下个时钟周期开始计数,N个时钟周期后,写入突发停止指令,终止数据读操作;

  6. 突发停止指令写入后,DQ数据输出,数据输出完成后,SDRAM的页突发读操作完成。

数据读模块框图

了解了数据读操作的时序和具体流程后,我们根据参考流程设计数据读模块。首先先来介绍一下数据写模块框图和输入输出信号的功能描述,数据读模块框图、输入输出信号功能描述,具体见图 53‑53、 表格 53‑8。

SDRAM054

图 53‑53 数据读模块框图

表格 53‑8 数据读模块输入输出信号功能描述

信号

位宽

类型

功能描述

sys_clk

1Bit

Input

系统时钟,频率100MHz

sys_rst_n

1Bit

Input

复位信号,低有效

init_end

1Bit

Input

初始化完成信号

rd_en

1Bit

Input

数据读使能信号

rd_addr

24Bit

Input

数据读阶段地址输入

rd_data

16Bit

Input

数据读阶段数据输入

rd_burst_len

10Bit

Input

读突发长度

rd_ack

1Bit

Output

数据读操作响应

rd_end

1Bit

Output

一次突发读结束

read_cmd

4Bit

Output

数据读阶段指令

read_ba

2Bit

Output

数据读阶段L-Bank地址

read_addr

13Bit

Output

数据读阶段地址输出

rd_sdram_data

16Bit

Output

数据读阶段数据输出

输入信号:init_end为初始化模块传入的初始化完成标志信号,表示初始化完成,SDRAM进入正常模式,可进行其他操作;rd_en为数据读使能信号,表示仲裁模块响应数据读请求,数据读模块可以开始数据读操作;rd_addr为24位宽的地址信号,高2位为数据读取位置的逻辑Bank地址,中间13位为数据读 取位置行地址,后9位为数据突发读取首地址;rd_data为自SDRAM读取数据;rd_burst_len为读取数据突发长度,具体数值可根据实际情况设定,但不能超过SDRAM芯片一行包含存储单元的个数。

输出信号:rd_ack为数据读操作响应信号,表示数据写模块响应了rd_en读使能信号,同时rd_ack信号也是fifo_ctrl模块中读fifo的数据写使能,将自SDRAM读出的数据写入读fifo;rd_end为一次读突发结束标志,表示一次读突发完成,SDRAM可进行其他操作;read_cmd、re ad_ba、read_addr分别为数据读阶段的指令信号、逻辑Bank地址和地址总线;rd_sdram_data为输出自SDRAM读取的数据,rd_ack信号有效时,数据写入fifo_ctrl模块中的读fifo。

数据读模块波形图

数据读模块设计完毕,下面开始波形图的绘制及波形讲解。数据读模块波形图,具体见图 53‑54。

SDRAM055

图 53‑54 数据读模块波形图

第一部分:状态机相关信号的波形设计与实现

数据读操作模块仍然使用状态机来实现。首先声明状态变量read_state,定义状态名称:初始状态(RD_IDLE)、激活状态(RD_ACTIVE)、激活等待状态(RD_TRCD)、读指令状态(RD_READ)、潜伏状态(RD_CL)、读数据状态(RD_DATA),、预充电状态(RD_PRE),随即状 态跳转预充电等待状态 (RD_TRP)、写结束状态(RD_END)。

对于状态机跳转条件,我们仍然沿用之前的方法,声明时钟周期计数器cnt_clk、时钟周期计数清零信号cnt_clk_rst、预充电等待状态结束标志信号trp_end、激活等待状态结束标志信号trcd_end、潜伏状态结束标志信号tcl_end和写数据结束标志信号tread_end,这些信号实现状态机的 状态跳转。

系统上电后,read_state变量保持在初始状态(RD_IDLE),当初始化完成(init_end为高电平)且数据读使能有效(rd_en为高电平),状态机跳转到激活状态(RD_ACTIVE),随即跳转到激活等待状态(RD_TRCD),在此状态等待TRCD_CLK(设定值为2)个时钟周期,激活完成, 状态跳转到读指令状态(RD_READ),随即跳转到潜伏状态(RD_CL),在此状态等待3(CL=3)个时钟周期随即跳转到读数据状态(RD_DATA),在此状态等待(wr_burst_len + CL)个时钟周期,一次读突发完成,状态跳转到预充电状态(RD_PRE),随即状态跳转预充电等待状态 (RD_TRP),在此状态等待TRP_CLK(设定值为2)个时钟周期,预充电完成,激活行被关闭,状态跳转到写结束状态(RD_END),随即跳回初始状态(RD_IDLE)等待下一次数据读操作。

状态机相关信号波形图如下。

SDRAM056

图 53‑55 状态机相关信号波形图

第二部分:读突发结束信号rdburst_end的设计与实现

因为SDRAM的页突发数据读操作在读指令写入后,到第一个有效数据输出,存在潜伏期,若一次突发想要读取N个数据,就需要在数据读指令写入后的第N个时钟周期写入突发终止指令。为控制突发终止指令的写入,我们声明读突发结束信号rdburst_end,信号波形如下。

SDRAM057

图 53‑56 rdburst_end信号波形图

第三部分:各输出信号的设计与实现

要实现对SDRAM 的数据读取,输出信号必须包含数据写阶段的指令信号(read_cmd)、逻辑Bank地址(read_ba)、地址总线(read_addr)。

在激活状态,给指令信号写入激活指令,逻辑Bank地址、地址总线写入要激活的Bank地址和行地址,激活特定逻辑Bank的特定行;在读状态,给指令信号写入数据读指令,逻辑Bank地址、地址总线写入要进行突发读操作的Bank地址和列首地址;在读数据状态,逻辑Bank地址、地址总线均保持全1,读突发结束信号 为高电平时,指令信号写入突发终止指令,终止数据读操作;在预充电状态,指令信号写入预充电指令,对所有Bank进行预充电,关闭激活行。

输出数据读操作结束信号rd_end,告知仲裁模块数据写操作结束,可执行其他操作。各信号波形图如下。

SDRAM058

图 53‑57 各输出信号波形图

此处需要注意的是,指令信号、逻辑Bank地址和地址总线均使用时序逻辑赋值,数据会在对应状态的下一个时钟写入。

第四部分:rd_data_reg、rd_ack、rd_sdram_data信号的设计与实现

声明rd_data_reg的目的是缓存自SDRAM读取的数据rd_data,因为SDRAM工作时钟超前模块系统时钟,为了使读取数据与模块系统时钟同步,声明此信号。

声明读响应信号rd_ack的目的有二:一是要告知fifo_ctrl模块,一次突发读操作已经完成,页突发读操作首地址可以更新;二是作为fifo_ctrl模块中读fifo的写使能信号将自SDRAM读取的数据读fifo。rd_sdram_data信号就是要写入读fifo的数据,在读响应信号rd_ack为高 电平时,将数据rd_sdarm_data写入fifo_ctrl模块的读fifo中。上述3路信号波形图如下。

SDRAM059

图 53‑58 rd_data_reg、rd_ack、rd_sdram_data信号波形图

整合上述个部分波形图,即可得到本模块完整波形图,具体见图 53‑54。

代码编写

数据读模块波形图绘制完成,涉及信号介绍完毕,参照绘制波形图,编写数据读模块参考代码。数据读模块参考代码,具体见代码清单 53‑7。

代码清单 53‑7 数据读模块参考代码(sdram_read.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
module sdram_read
(
input wire sys_clk , //系统时钟,频率100MHz
input wire sys_rst_n , //复位信号,低电平有效
input wire init_end , //初始化结束信号
input wire rd_en , //读使能
input wire [23:0] rd_addr , //读SDRAM地址
input wire [15:0] rd_data , //自SDRAM中读出的数据
input wire [9:0] rd_burst_len , //读突发SDRAM字节数

output wire rd_ack , //读SDRAM响应信号
output wire rd_end , //一次突发读结束
output reg [3:0] read_cmd , //读数据阶段写入sdram的指令
output reg [1:0] read_ba , //读数据阶段Bank地址
output reg [12:0] read_addr , //地址数据,辅助预充电操作
output wire [15:0] rd_sdram_data //SDRAM读出的数据
);

////
//\* Parameter and Internal Signal \//
////

//parameter define
parameter TRCD_CLK = 10'd2 , //激活等待周期
TCL_CLK = 10'd3 , //潜伏期
TRP_CLK = 10'd2 ; //预充电等待周期
parameter RD_IDLE = 4'b0000 , //空闲
RD_ACTIVE = 4'b0001 , //激活
RD_TRCD = 4'b0011 , //激活等待
RD_READ = 4'b0010 , //读操作
RD_CL = 4'b0100 , //潜伏期
RD_DATA = 4'b0101 , //读数据
RD_PRE = 4'b0111 , //预充电
RD_TRP = 4'b0110 , //预充电等待
RD_END = 4'b1100 ; //一次突发读结束
parameter NOP = 4'b0111 , //空操作指令
ACTIVE = 4'b0011 , //激活指令
READ = 4'b0101 , //数据读指令
B_STOP = 4'b0110 , //突发停止指令
P_CHARGE = 4'b0010 ; //预充电指令

//wire define
wire trcd_end ; //激活等待周期结束
wire trp_end ; //预充电等待周期结束
wire tcl_end ; //潜伏期结束标志
wire tread_end ; //突发读结束
wire rdburst_end ; //读突发终止

//reg define
reg [3:0] read_state ; //SDRAM写状态
reg [9:0] cnt_clk ; //时钟周期计数,记录初始化各状态等待时间
reg cnt_clk_rst ; //时钟周期计数复位标志
reg [15:0] rd_data_reg ;

////
//\* Main Code \//
////

//rd_data_reg
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rd_data_reg <= 16'd0;
else
rd_data_reg <= rd_data;

//rd_end:一次突发读结束
assign rd_end = (read_state == RD_END) ? 1'b1 : 1'b0;

//rd_ack:读SDRAM响应信号
assign rd_ack = (read_state == RD_DATA)
&& (cnt_clk >= 10'd1)
&& (cnt_clk < (rd_burst_len + 2'd1));

//cnt_clk:时钟周期计数,记录初始化各状态等待时间
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_clk <= 10'd0;
else if(cnt_clk_rst == 1'b1)
cnt_clk <= 10'd0;
else
cnt_clk <= cnt_clk + 1'b1;

//trcd_end,trp_end,tcl_end,tread_end,rdburst_end:等待结束标志
assign trcd_end = ((read_state == RD_TRCD)
&& (cnt_clk == TRCD_CLK )) ? 1'b1 : 1'b0;//行选通周期结束
assign trp_end = ((read_state == RD_TRP )
&& (cnt_clk == TRP_CLK )) ? 1'b1 : 1'b0;//预充电有效周期结束
assign tcl_end = ((read_state == RD_CL )
&& (cnt_clk == TCL_CLK - 1 )) ? 1'b1 : 1'b0;//潜伏期结束
assign tread_end = ((read_state == RD_DATA)
&& (cnt_clk == rd_burst_len + 2)) ? 1'b1 : 1'b0;//突发读结束
assign rdburst_end = ((read_state == RD_DATA)
&& (cnt_clk == rd_burst_len - 4)) ? 1'b1 : 1'b0;//读突发终止

//read_state:SDRAM的工作状态机
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
read_state <= RD_IDLE;
else
case(read_state)
RD_IDLE:
if((rd_en ==1'b1) && (init_end == 1'b1))
read_state <= RD_ACTIVE;
else
read_state <= RD_IDLE;
RD_ACTIVE:
read_state <= RD_TRCD;
RD_TRCD:
if(trcd_end == 1'b1)
read_state <= RD_READ;
else
read_state <= RD_TRCD;
RD_READ:
read_state <= RD_CL;
RD_CL:
read_state <= (tcl_end == 1'b1) ? RD_DATA : RD_CL;
RD_DATA:
read_state <= (tread_end == 1'b1) ? RD_PRE : RD_DATA;
RD_PRE:
read_state <= RD_TRP;
RD_TRP:
read_state <= (trp_end == 1'b1) ? RD_END : RD_TRP;
RD_END:
read_state <= RD_IDLE;
default:
read_state <= RD_IDLE;
endcase

//计数器控制逻辑
always@(*)
begin
case(read_state)
RD_IDLE: cnt_clk_rst <= 1'b1;
RD_TRCD: cnt_clk_rst <= (trcd_end == 1'b1) ? 1'b1 : 1'b0;
RD_READ: cnt_clk_rst <= 1'b1;
RD_CL: cnt_clk_rst <= (tcl_end == 1'b1) ? 1'b1 : 1'b0;
RD_DATA: cnt_clk_rst <= (tread_end == 1'b1) ? 1'b1 : 1'b0;
RD_TRP: cnt_clk_rst <= (trp_end == 1'b1) ? 1'b1 : 1'b0;
RD_END: cnt_clk_rst <= 1'b1;
default: cnt_clk_rst <= 1'b0;
endcase
end

//SDRAM操作指令控制
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
read_cmd <= NOP;
read_ba <= 2'b11;
read_addr <= 13'h1fff;
end
else
case(read_state)
RD_IDLE,RD_TRCD,RD_TRP:
begin
read_cmd <= NOP;
read_ba <= 2'b11;
read_addr <= 13'h1fff;
end
RD_ACTIVE: //激活指令
begin
read_cmd <= ACTIVE;
read_ba <= rd_addr[23:22];
read_addr <= rd_addr[21:9];
end
RD_READ: //读操作指令
begin
read_cmd <= READ;
read_ba <= rd_addr[23:22];
read_addr <= {4'b0000,rd_addr[8:0]};
end
RD_DATA: //突发传输终止指令
begin
if(rdburst_end == 1'b1)
read_cmd <= B_STOP;
else
begin
read_cmd <= NOP;
read_ba <= 2'b11;
read_addr <= 13'h1fff;
end
end
RD_PRE: //预充电指令
begin
read_cmd <= P_CHARGE;
read_ba <= rd_addr[23:22];
read_addr <= 13'h0400;
end
RD_END:
begin
read_cmd <= NOP;
read_ba <= 2'b11;
read_addr <= 13'h1fff;
end
default:
begin
read_cmd <= NOP;
read_ba <= 2'b11;
read_addr <= 13'h1fff;
end
endcase

//rd_sdram_data:SDRAM读出的数据
assign rd_sdram_data = (rd_ack == 1'b1) ? rd_data_reg : 16'b0;

endmodule

数据读模块参考代码是参照绘制波形图编写,输入输出信号及内部变量在上一小节已详细介绍,在此不再过多叙述。

仿真代码编写

数据读模块参考代码编写完成后,为验证代码正确性,在此编写仿真代码进行验证。数据读模块仿真参考代码,具体见代码清单 53‑8。

代码清单 53‑8 数据读模块仿真参考代码(tb_sdram_read.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
\`timescale 1ns/1ns
module tb_sdram_read();

////
//\* Internal Signal and Defparam \//
////

//wire define
//clk_gen
wire clk_50m ; //PLL输出50M时钟
wire clk_100m ; //PLL输出100M时钟
wire clk_100m_shift ; //PLL输出100M时钟,相位偏移-30deg
wire locked ; //PLL时钟锁定信号
wire rst_n ; //复位信号,低有效
//sdram_init
wire [3:0] init_cmd ; //初始化阶段指令
wire [1:0] init_ba ; //初始化阶段L-Bank地址
wire [12:0] init_addr ; //初始化阶段地址总线
wire init_end ; //初始化完成信号
//sdram_write
wire [12:0] write_addr ; //数据写阶段地址总线
wire [1:0] write_ba ; //数据写阶段L-Bank地址
wire [3:0] write_cmd ; //数据写阶段指令
wire [15:0] wr_sdram_data ; //数据写阶段写入SDRAM数据
wire wr_sdram_en ; //数据写阶段写数据有效使能信号
wire wr_end ; //数据写阶段一次突发写结束
//sdram_read
wire [12:0] read_addr ; //数据读阶段地址总线
wire [1:0] read_ba ; //数据读阶段L-Bank地址
wire [3:0] read_cmd ; //数据读阶段指令
wire [15:0] sdram_data_out ; //数据读阶段写入SDRAM数据
wire rd_end ; //数据读阶段一次突发写结束
wire sdram_wr_ack ; //数据写阶段写响应
//sdram_addr
wire [12:0] sdram_addr ; //SDRAM地址总线
wire [1:0] sdram_ba ; //SDRAML-Bank地址
wire [3:0] sdram_cmd ; //SDRAM指令
wire [15:0] sdram_dq ; //SDRAM数据总线

wire [12:0] w_r_addr ; //数据读阶段地址总线
wire [1:0] w_r_ba ; //数据读阶段L-Bank地址
wire [3:0] w_r_cmd ; //数据读阶段指令

//reg define
reg sys_clk ; //系统时钟
reg sys_rst_n ; //复位信号
reg wr_en ; //写使能
reg [15:0] wr_data_in ; //写数据
reg rd_en ; //读使能


//defparam
//重定义仿真模型中的相关参数
defparam sdram_model_plus_inst.addr_bits = 13; //地址位宽
defparam sdram_model_plus_inst.data_bits = 16; //数据位宽
defparam sdram_model_plus_inst.col_bits = 9; //列地址位宽
defparam sdram_model_plus_inst.mem_sizes = 2*1024*1024; //L-Bank容量

////
//\* Clk And Rst \//
////

//时钟、复位信号
initial
begin
sys_clk = 1'b1 ;
sys_rst_n <= 1'b0 ;
#200
sys_rst_n <= 1'b1 ;
end

always #10 sys_clk = ~sys_clk;

//rst_n:复位信号
assign rst_n = sys_rst_n & locked;

//wr_en:写数据使能
always@(posedge clk_100m or negedge rst_n)
if(rst_n == 1'b0)
wr_en <= 1'b1;
else if(wr_end == 1'b1)
wr_en <= 1'b0;
else
wr_en <= wr_en;

//wr_data_in:写数据
always@(posedge clk_100m or negedge rst_n)
if(rst_n == 1'b0)
wr_data_in <= 16'd0;
else if(wr_data_in == 16'd10)
wr_data_in <= 16'd0;
else if(sdram_wr_ack == 1'b1)
wr_data_in <= wr_data_in + 1'b1;
else
wr_data_in <= wr_data_in;

//rd_en:读数据使能
always@(posedge clk_100m or negedge rst_n)
if(rst_n == 1'b0)
rd_en <= 1'b0;
else if(rd_end == 1'b1)
rd_en <= 1'b0;
else if(wr_en == 1'b0)
rd_en <= 1'b1;
else
rd_en <= rd_en;

//sdram_cmd,sdram_ba,sdram_addr
assign sdram_cmd = (init_end == 1'b1) ? w_r_cmd : init_cmd;
assign sdram_ba = (init_end == 1'b1) ? w_r_ba : init_ba;
assign sdram_addr = (init_end == 1'b1) ? w_r_addr : init_addr;

//w_r_cmd,w_r_ba,w_r_addr
assign w_r_cmd = (wr_en == 1'b1) ? write_cmd : read_cmd;
assign w_r_ba = (wr_en == 1'b1) ? write_ba : read_ba;
assign w_r_addr = (wr_en == 1'b1) ? write_addr : read_addr;

//wr_sdram_data
assign sdram_dq = (wr_sdram_en == 1'b1) ? wr_sdram_data : 16'hz;

////
//\* Instantiation \//
////

//------------- clk_gen_inst -------------
clk_gen clk_gen_inst (
.inclk0 (sys_clk ),
.areset (~sys_rst_n ),
.c0 (clk_50m ),
.c1 (clk_100m ),
.c2 (clk_100m_shift ),

.locked (locked )
);

//------------- sdram_init_inst -------------
sdram_init sdram_init_inst(

.sys_clk (clk_100m ),
.sys_rst_n (rst_n ),

.init_cmd (init_cmd ),
.init_ba (init_ba ),
.init_addr (init_addr ),
.init_end (init_end )

);

//------------- sdram_write_inst -------------
sdram_write sdram_write_inst(

.sys_clk (clk_100m ),
.sys_rst_n (rst_n ),
.init_end (init_end ),
.wr_en (wr_en ),

.wr_addr (24'h000_000 ),
.wr_data (wr_data_in ),
.wr_burst_len (10'd10 ),

.wr_ack (sdram_wr_ack ),
.wr_end (wr_end ),
.write_cmd (write_cmd ),
.write_ba (write_ba ),
.write_addr (write_addr ),
.wr_sdram_en (wr_sdram_en ),
.wr_sdram_data (wr_sdram_data )

);

//------------- sdram_read_inst -------------
sdram_read sdram_read_inst(

.sys_clk (clk_100m ),
.sys_rst_n (rst_n ),
.init_end (init_end ),
.rd_en (rd_en ),

.rd_addr (24'h000_000 ),
.rd_data (sdram_dq ),
.rd_burst_len (10'd10 ),

.rd_ack ( ),
.rd_end (rd_end ),
.read_cmd (read_cmd ),
.read_ba (read_ba ),
.read_addr (read_addr ),
.rd_sdram_data (sdram_data_out )

);

//-------------sdram_model_plus_inst-------------
sdram_model_plus sdram_model_plus_inst(
.Dq (sdram_dq ),
.Addr (sdram_addr ),
.Ba (sdram_ba ),
.Clk (clk_100m_shift ),
.Cke (1'b1 ),
.Cs_n (sdram_cmd[3] ),
.Ras_n (sdram_cmd[2] ),
.Cas_n (sdram_cmd[1] ),
.We_n (sdram_cmd[0] ),
.Dqm (2'b0 ),
.Debug (1'b1 )

);

endmodule

数据读模块仿真参考代码是在数据写模块仿真参考代码基础上修改而成,加入数据读模块实例化;以初始化完成信号init_end和写使能信号wr_en为约束条件,在不同阶段给SDRAM写入该阶段对应操作指令和地址信息。

仿真波形分析

使用ModelSim对数据写模块进行仿真,仿真结果如下。

SDRAM060

图 53‑59 SDRAM 仿真模型打印信息

SDRAM061

图 53‑60 数据读模块仿真波形图

由图 53‑59、图 53‑60可知,SDRAM 初始化完成后,通过数据写模块向SDRAM写入1-10共10字节数据,数据写入完成后,通过数据读模块读出写入数据,写入数据与读出数据一致,且仿真波形与绘制波形图一致,数据读模块验证通过。

3.2.3.6. 仲裁模块

仲裁模块框图

在本章节的实验中,我们共涉及到SDRAM的4种操作方式,初始化操作、自动刷新操作、数据写操作和数据读操作,其中初始化操作优先级最高,SDRAM只有经过初始化操作后,才可被正常使用;SDRAM 初始化后,我们可以对其进行数据的写入和读取,同时SDRAM必须进行周期性的刷新才能保证数据的可靠性,那么如果数据写入、数据读取和自动刷新这三种操作的两项或三项恰巧同时需要被执行,或者出现某一操作正在执行,其他操作请求被执行等情况时,我们应该怎样处理。仲裁模块的出现就是为了解决这一问题。

了解了仲裁模块的功能作用后,我们进入仲裁模块的模块设计。首先先来介绍一下仲裁模块框图和输入输出信号的功能描述,仲裁模块框图、输入输出信号功能描述,具体见。

SDRAM062

图 53‑61 仲裁模块框图

表格 53‑9 仲裁模块输入输出信号功能描述

信号

位宽

类型

功能描述

sys_clk

1Bit

Input

系统时钟,频率100MHz

sys_rst_n

1Bit

Input

复位信号,低有效

init_cmd

4Bit

Input

初始化阶段指令

init_ba

2Bit

Input

初始化阶段逻辑Bank地址

init_addr

13Bit

Input

初始化阶段地址总线

init_end

1Bit

Input

初始化完成信号

aref_req

1Bit

Input

自动刷新请求信号

aref_cmd

4Bit

Input

自动刷新阶段指令

aref_ba

2Bit

Input

自动刷新阶段逻辑Bank地址

aref_addr

13Bit

Input

自动刷新阶段地址总线

aref_end

1Bit

Input

自动刷新结束信号

wr_req

1Bit

Input

数据写请求信号

wr_cmd

4Bit

Input

数据写阶段指令

wr_ba

2Bit

Input

数据写阶段逻辑Bank地址

wr_addr

13Bit

Input

数据写阶段地址总线

wr_sdram_en

1Bit

Input

SDRAM数据写入有效信号

wr_data

16Bit

Input

SDRAM待写入数据

wr_end

1Bit

Input

数据写结束信号

rd_req

1Bit

Input

数据读请求信号

rd_cmd

4Bit

Input

数据读阶段指令

rd_ba

2Bit

Input

数据读阶段逻辑Bank地址

rd_addr

13Bit

Input

数据读阶段地址总线

rd_end

1Bit

Input

数据读结束信号

aref_en

1Bit

Output

自动刷新使能

wr_en

1Bit

Output

数据写使能

rd_en

1Bit

Output

数据读使能

sdram_cke

1Bit

Output

SDRAM时钟使能信号

sdram_cs_n

1Bit

Output

SDRAM片选信号

sdram_cas_n

1Bit

Output

SDRAM列选通信号

sdram_ras_n

1Bit

Output

SDRAM行选通信号

sdram_we_n

1Bit

Output

SDRAM写使能信号

sdram_ba

2Bit

Output

SDRAM逻辑Bank地址

sdram_addr

13Bit

Output

SDRAM地址总线

sdram_dq

16Bit

Output

SDRAM数据总线

仲裁模块波形图

上面小结中,我们介绍了仲裁模块的具体功能、模块框图,对模块输入输出信号做了简单描述。本小节进行仲裁模块波形图的绘制,介绍一下仲裁模块的实现方法。

在绘制波形图之前,我们先通过仲裁模块的状态转移图,向读者说明仲裁模块具体功能的实现。仲裁模块状态转移图,具体见图 53‑62。

SDRAM063

图 53‑62 仲裁模块状态转移图

由图可知,SDRAM上电后处于初始状态(IDLE),初始化完成后,状态跳转到仲裁状态(ARBIT);当状态机处于仲裁状态时,若自动刷新请求、读请求、写请求中的任意一路请求有效,且其他两路请求信号无效,状态跳转到有效请求信号对应状态,对应操作完成后,对应结束信号有效,状态跳回到仲裁状态,等待下一次请求 信号。

若多路请求信号同时有效,优先执行优先级较高的操作,默认优先级为自动刷新操作>数据写操作>数据读操作。

读者还需注意的是,各路请求信号只有在仲裁状态才会被响应,这就保证某一操作正在执行时,不会被新的请求信号打断。

结合状态转移图,我们介绍了仲裁模块的具体功能实现,下面我们结合仲裁模块状态转移图和功能描述进行波形图的绘制。仲裁模块波形图,具体见图 53‑63。

SDRAM064

图 53‑63 仲裁模块波形图

由于篇幅原因,在波形图中,我们只画出了仲裁模块的部分输入信号,实验涉及的4种操作状态中的操作命令、逻辑Bank地址和地址总线未画出,在涉及到这些信号的位置我们会加以说明,读者不必担心。

第一部分:输入信号简介

输入信号:init_end为初始化模块传入的初始化完成信号,表示SDRAM初始化完成,可进行其他操作;

aref_req为自动刷新请求信号,由自动刷新模块传入,当SDRAM需要进行自动刷新操作时,拉高信号,向仲裁模块请求自动刷新操作,当仲裁模块响应其请求,且自动刷新模块已开始自动刷新,请求信号拉低,图中此信号上升沿和下降沿均用虚线表示,表明具体变化时间不定;

aref_end为自动刷新结束信号,表示一次自动刷新操作结束;对于数据写请求、写结束、数据读请求、读结束,和自动刷新请求、自动刷新结束类似,在此不再过多叙述;

wr_sdram_en和wr_data为一对信号,由数据写模块传入,wr_data为要写入SDRAM的数据信息,在wr_sdram_en写数据使能信号有效时,将wr_data写入SDRAM。

第二部分:状态机的设计与实现

对于仲裁模块的功能实现我们仍然使用状态机的实现方式,所以在此声明状态机状态变量state,参照仲裁模块状态转移图,声明状态机各状态:初始状态(IDLE)、仲裁状态(ARBIT)、数据写状态(WRITE)、数据读状态(READ)、自动刷新状态(A_REF)。

我们使用各功能子模块输入的操作请求信号和操作结束信号作为约束条件,控制状态机状态跳转。SDRAM上电后,仲裁模块状态机处于初始状态 (IDLE),初始化完成后,init_end初始化完成信号拉高,状态跳转到仲裁状态(ARBIT),当状态机处于仲裁状态时,若自动刷新请求、读请求、写请求中的任意一路请求 有效,且其他两路请求信号无效,状态跳转到有效请求信号对应状态,对应操作完成后,对应结束信号有效,状态跳回到仲裁状态,等待下一次请求信号。

若两路或多路请求信号同时有效,优先执行优先级较高的操作,默认优先级为自动刷新操作>数据写操作>数据读操作。读者还需注意的是,各路请求信号只有在仲裁状态才会被响应,这就保证某一操作正在执行时,不会被新的请求信号打断。信号波形图如下。

SDRAM065

图 53‑64 状态机信号波形图

第三部分:aref_en、wr_en、rd_en信号的的设计与实现

当某操作请求信号传入仲裁模块,且仲裁模块处于仲裁状态,仲裁模块要拉高对应操作的使能信号,以响应该操作,该操作结束后会拉高对应操作的结束信号,告知仲裁模块操作结束。

所以,仲裁模块需要输出自动刷新使能信号aref_en、数据写使能信号wr_en、数据读使能信号rd_en。各操作对应使能信号波形如下。

SDRAM066

图 53‑65 aref_en、wr_en、rd_en信号波形图

第四部分:输出指令信号的设计与实现

仲裁模块通过状态机协调各操作的正常进行,每项操作的实现都需要对SDRAM写入对应操作指令,且各项操作指令不同,为了实现各操作指令的正确写入,我们内部声明SDRAM指令信号sdram_cmd,在不同的操作状态写入对应操作指令。

在前文我们讲过,SDRAM的指令集是由CS_N、RAS_N、CAS_N、WE_N 四路控制信号构成,所以仲裁模块要输出4路控制信号来对实现SDRAM的指令写入。

指令信号sdram_cmd、控制信号sdram_cs_n、sdram_ras_n、sdram_cas_n、sdram_we_n波形图如下。

SDRAM067

图 53‑66 指令信号波形图

第五部分:输出地址、数据信号的设计与实现

上一部分对输出指令信号做了详细说明,但SDRAM若只有指令信号写入仍无法实现正常操作,还需要地址信息的写入,所以我们需要对逻辑Bank地址和地址总线进行输出。在此声明逻辑Bank地址sdram_ba和地址总线sdram_addr,当某项操作进行时,写入对应地址数据。

在对SDRAM进行数据写操作时,除了需要写入操作指令和地址数据,还需要对SDRAM进行数据写入,所以我们还需要声明数据信号。同时,在进行数据读操作时,还需要有数据信号传入。但SDRAM芯片的数据总线是inout型端口,所以 这里声明inout型数据总线sdram_dq,在不同的操作状态实现对SDRAM的数据读取。在进行数据写操作时,我们取得端口控制权,写入数据,数据读操作时,接收写入数据,其他时刻不对端口进行干预。

综上所述,设计输出地址、数据信号波形图如下。

SDRAM068

图 53‑67 输出地址、数据信号波形图

代码编写

参照绘制的仲裁模块波形图,编写仲裁模块参考代码。仲裁模块参考代码,具体见代码清单 53‑9。

代码清单 53‑9 仲裁模块参考代码(sdram_arbit.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
module sdram_arbit
(
input wire sys_clk , //系统时钟
input wire sys_rst_n , //复位信号
//sdram_init
input wire [3:0] init_cmd , //初始化阶段命令
input wire init_end , //初始化结束标志
input wire [1:0] init_ba , //初始化阶段Bank地址
input wire [12:0] init_addr , //初始化阶段数据地址
//sdram_auto_ref
input wire aref_req , //自刷新请求
input wire aref_end , //自刷新结束
input wire [3:0] aref_cmd , //自刷新阶段命令
input wire [1:0] aref_ba , //自动刷新阶段Bank地址
input wire [12:0] aref_addr , //自刷新阶段数据地址
//sdram_write
input wire wr_req , //写数据请求
input wire [1:0] wr_ba , //写阶段Bank地址
input wire [15:0] wr_data , //写入SDRAM的数据
input wire wr_end , //一次写结束信号
input wire [3:0] wr_cmd , //写阶段命令
input wire [12:0] wr_addr , //写阶段数据地址
input wire wr_sdram_en ,
//sdram_read
input wire rd_req , //读数据请求
input wire rd_end , //一次读结束
input wire [3:0] rd_cmd , //读阶段命令
input wire [12:0] rd_addr , //读阶段数据地址
input wire [1:0] rd_ba , //读阶段Bank地址

output reg aref_en , //自刷新使能
output reg wr_en , //写数据使能
output reg rd_en , //读数据使能

output wire sdram_cke , //SDRAM时钟使能
output wire sdram_cs_n , //SDRAM片选信号
output wire sdram_ras_n , //SDRAM行地址选通
output wire sdram_cas_n , //SDRAM列地址选通
output wire sdram_we_n , //SDRAM写使能
output reg [1:0] sdram_ba , //SDRAM Bank地址
output reg [12:0] sdram_addr , //SDRAM地址总线
inout wire [15:0] sdram_dq //SDRAM数据总线
);

////
//\* Parameter and Internal Signal \//
////

//parameter define
parameter IDLE = 5'b0_0001 , //初始状态
ARBIT = 5'b0_0010 , //仲裁状态
AREF = 5'b0_0100 , //自动刷新状态
WRITE = 5'b0_1000 , //写状态
READ = 5'b1_0000 ; //读状态
parameter CMD_NOP = 4'b0111 ; //空操作指令

//reg define
reg [3:0] sdram_cmd ; //写入SDRAM命令
reg [4:0] state ; //状态机状态

////
//\* Main Code \//
////

//state:状态机状态
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
state <= IDLE;
else case(state)
IDLE: if(init_end == 1'b1)
state <= ARBIT;
else
state <= IDLE;
ARBIT:if(aref_req == 1'b1)
state <= AREF;
else if(wr_req == 1'b1)
state <= WRITE;
else if(rd_req == 1'b1)
state <= READ;
else
state <= ARBIT;
AREF: if(aref_end == 1'b1)
state <= ARBIT;
else
state <= AREF;
WRITE: if(wr_end == 1'b1)
state <= ARBIT;
else
state <= WRITE;
READ: if(rd_end == 1'b1)
state <= ARBIT;
else
state <= READ;
default:state <= IDLE;
endcase

//aref_en:自动刷新使能
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
aref_en <= 1'b0;
else if((state == ARBIT) && (aref_req == 1'b1))
aref_en <= 1'b1;
else if(aref_end == 1'b1)
aref_en <= 1'b0;

//wr_en:写数据使能
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
wr_en <= 1'b0;
else if((state == ARBIT) && (aref_req == 1'b0) && (wr_req == 1'b1))
wr_en <= 1'b1;
else if(wr_end == 1'b1)
wr_en <= 1'b0;

//rd_en:读数据使能
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rd_en <= 1'b0;
else if((state == ARBIT) && (aref_req == 1'b0) && (rd_req == 1'b1))
rd_en <= 1'b1;
else if(rd_end == 1'b1)
rd_en <= 1'b0;

//sdram_cmd:写入SDRAM命令;sdram_ba:SDRAM Bank地址;sdram_addr:SDRAM地址总线
always@(*)
case(state)
IDLE: begin
sdram_cmd <= init_cmd;
sdram_ba <= init_ba;
sdram_addr <= init_addr;
end
AREF: begin
sdram_cmd <= aref_cmd;
sdram_ba <= aref_ba;
sdram_addr <= aref_addr;
end
WRITE: begin
sdram_cmd <= wr_cmd;
sdram_ba <= wr_ba;
sdram_addr <= wr_addr;
end
READ: begin
sdram_cmd <= rd_cmd;
sdram_ba <= rd_ba;
sdram_addr <= rd_addr;
end
default: begin
sdram_cmd <= CMD_NOP;
sdram_ba <= 2'b11;
sdram_addr <= 13'h1fff;
end
endcase

//SDRAM时钟使能
assign sdram_cke = 1'b1;
//SDRAM数据总线
assign sdram_dq = (wr_sdram_en == 1'b1) ? wr_data : 16'bz;
//片选信号,行地址选通信号,列地址选通信号,写使能信号
assign {sdram_cs_n, sdram_ras_n, sdram_cas_n, sdram_we_n} = sdram_cmd;

endmodule

参考代码中有详细注释,且所有信号在波形图绘制下接均有详细说明,读者可参照两处对代码进行学习理解,在此不再过多叙述。

在此不再单独对仲裁模块进行仿真,在介绍完sdram_ctrl模块后,对第一部分SDRAM控制部分整体仿真,查看仿真波形,对仲裁模块进行仿真验证。

3.2.3.7. SDRAM控制模块

介绍完仲裁模块,第一部分的子功能模块均已介绍完毕,下面介绍第一部分的顶层模块SDRAM控制模块sdram_ctrl。

sdram_ctrl模块作为第一部分的顶层模块,作用有二,一是实例化各功能子模块,将各子模块对应信号进行连接;二是做对外连接,接收fifo_ctrl模块传入的地址信号、数据信号、请求信号等,输出SDRAM控制指令、地址及数据。

sdram_ctrl模块内部代码较为简单,在此处,我们只介绍其整体框图和参考代码,不再进行波形图的绘制与讲解。

FIFO框图

sdram_ctrl模块模块框图和输入输出信号功能描述,具体见图 53‑68、表格 53‑10。

SDRAM069

图 53‑68 sdramctrl模块框图

表格 53‑10 sdram_ctrl模块输入输出信号功能描述

信号

位宽

类型

功能描述

sys_clk

1Bit

Input

系统时钟,频率100MHz

sys_rst_n

1Bit

Input

复位信号,低有效

sdram_wr_req

1Bit

Input

数据写请求信号

sdram_wr_addr

24Bit

Input

数据写地址

wr_burst_len

10Bit

Input

数据写突发长度

sdram_data_in

16Bit

Input

待写入SDRAM数据

sdram_rd_req

1Bit

Input

数据读请求信号

sdram_rd_addr

24Bit

Input

数据读地址

rd_burst_len

10Bit

Input

数据读突发长度

sdram_wr_ack

1Bit

Output

数据写响应信号

sdram_data_out

16Bit

Output

输出SDRAM读取数据

init_end

1Bit

Output

初始化完成信号

sdram_rd_ack

1Bit

Output

数据读响应信号

sdram_cke

1Bit

Output

SDRAM时钟使能信号

sdram_cs_n

1Bit

Output

SDRAM片选信号

sdram_ras_n

1Bit

Output

SDRAM行选通信号

sdram_cas_n

1Bit

Output

SDRAM列选通信号

sdram_we_n

1Bit

Output

SDRAM读使能信号

sdram_ba

2Bit

Output

SDRAM逻辑Bank地址

sdram_addr

13Bit

Output

SDRAM地址总线

sdram_dq

16Bit

Output

SDRAM数据总线

图 53‑69 FIFO控制模块波形图

代码编写

SDRAM控制模块参考代码,具体见代码清单 53‑10。

代码清单 53‑10 SDRAM控制模块参考代码(sdram_ctrl.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
module sdram_ctrl
(
input wire sys_clk , //系统时钟
input wire sys_rst_n , //复位信号,低电平有效
//SDRAM写端口
input wire sdram_wr_req , //写SDRAM请求信号
input wire [23:0] sdram_wr_addr , //SDRAM写操作的地址
input wire [9:0] wr_burst_len , //写sdram时数据突发长度
input wire [15:0] sdram_data_in , //写入SDRAM的数据
output wire sdram_wr_ack , //写SDRAM响应信号
//SDRAM读端口
input wire sdram_rd_req , //读SDRAM请求信号
input wire [23:0] sdram_rd_addr , //SDRAM读操作的地址
input wire [9:0] rd_burst_len , //读sdram时数据突发长度
output wire [15:0] sdram_data_out , //从SDRAM读出的数据
output wire init_end , //SDRAM 初始化完成标志
output wire sdram_rd_ack , //读SDRAM响应信号
//FPGA与SDRAM硬件接口
output wire sdram_cke , // SDRAM 时钟有效信号
output wire sdram_cs_n , // SDRAM 片选信号
output wire sdram_ras_n , // SDRAM 行地址选通
output wire sdram_cas_n , // SDRAM 列地址选通
output wire sdram_we_n , // SDRAM 写使能
output wire [1:0] sdram_ba , // SDRAM Bank地址
output wire [12:0] sdram_addr , // SDRAM 地址总线
inout wire [15:0] sdram_dq // SDRAM 数据总线
);

////
//\* Parameter and Internal Signal \//
////

//wire define
//sdram_init
wire [3:0] init_cmd ; //初始化阶段写入sdram的指令
wire [1:0] init_ba ; //初始化阶段Bank地址
wire [12:0] init_addr ; //初始化阶段地址数据,辅助预充电操作
//sdram_a_ref
wire aref_req ; //自动刷新请求
wire aref_end ; //自动刷新结束标志
wire [3:0] aref_cmd ; //自动刷新阶段写入sdram的指令
wire [1:0] aref_ba ; //自动刷新阶段Bank地址
wire [12:0] aref_addr ; //地址数据,辅助预充电操作
wire aref_en ; //自动刷新使能
//sdram_write
wire wr_en ; //写使能
wire wr_end ; //一次写结束信号
wire [3:0] write_cmd ; //写阶段命令
wire [1:0] write_ba ; //写数据阶段Bank地址
wire [12:0] write_addr ; //写阶段数据地址
wire wr_sdram_en ; //SDRAM写使能
wire [15:0] wr_sdram_data; //写入SDRAM的数据
//sdram_read
wire rd_en ; //读使能
wire rd_end ; //一次突发读结束
wire [3:0] read_cmd ; //读数据阶段写入sdram的指令
wire [1:0] read_ba ; //读阶段Bank地址
wire [12:0] read_addr ; //读阶段数据地址

////
//\* Instantiation \//
////
//------------- sdram_init_inst -------------
sdram_init sdram_init_inst
(
.sys_clk (sys_clk ), //系统时钟,频率100MHz
.sys_rst_n (sys_rst_n ), //复位信号,低电平有效

.init_cmd (init_cmd ), //初始化阶段写入sdram的指令
.init_ba (init_ba ), //初始化阶段Bank地址
.init_addr (init_addr ), //初始化阶段地址数据,辅助预充电操作
.init_end (init_end ) //初始化结束信号
);

//------------- sdram_arbit_inst -------------
sdram_arbit sdram_arbit_inst
(
.sys_clk (sys_clk ), //系统时钟
.sys_rst_n (sys_rst_n ), //复位信号
//sdram_init
.init_cmd (init_cmd ), //初始化阶段命令
.init_end (init_end ), //初始化结束标志
.init_ba (init_ba ), //初始化阶段Bank地址
.init_addr (init_addr ), //初始化阶段数据地址
//sdram_auto_ref
.aref_req (aref_req ), //自刷新请求
.aref_end (aref_end ), //自刷新结束
.aref_cmd (aref_cmd ), //自刷新阶段命令
.aref_ba (aref_ba ), //自动刷新阶段Bank地址
.aref_addr (aref_addr ), //自刷新阶段数据地址
//sdram_write
.wr_req (sdram_wr_req ), //写数据请求
.wr_end (wr_end ), //一次写结束信号
.wr_cmd (write_cmd ), //写阶段命令
.wr_ba (write_ba ), //写阶段Bank地址
.wr_addr (write_addr ), //写阶段数据地址
.wr_sdram_en(wr_sdram_en ), //SDRAM写使能
.wr_data (wr_sdram_data ), //写入SDRAM的数据
//sdram_read
.rd_req (sdram_rd_req ), //读数据请求
.rd_end (rd_end ), //一次读结束
.rd_cmd (read_cmd ), //读阶段命令
.rd_addr (read_addr ), //读阶段数据地址
.rd_ba (read_ba ), //读阶段Bank地址

.aref_en (aref_en ), //自刷新使能
.wr_en (wr_en ), //写数据使能
.rd_en (rd_en ), //读数据使能

.sdram_cke (sdram_cke ), //SDRAM时钟使能
.sdram_cs_n (sdram_cs_n ), //SDRAM片选信号
.sdram_ras_n(sdram_ras_n ), //SDRAM行地址选通
.sdram_cas_n(sdram_cas_n ), //SDRAM列地址选通
.sdram_we_n (sdram_we_n ), //SDRAM写使能
.sdram_ba (sdram_ba ), //SDRAM Bank地址
.sdram_addr (sdram_addr ), //SDRAM地址总线
.sdram_dq (sdram_dq ) //SDRAM数据总线
);

//------------- sdram_a_ref_inst -------------
sdram_a_ref sdram_a_ref_inst
(
.sys_clk (sys_clk ), //系统时钟,频率100MHz
.sys_rst_n (sys_rst_n ), //复位信号,低电平有效
.init_end (init_end ), //初始化结束信号
.aref_en (aref_en ), //自动刷新使能

.aref_req (aref_req ), //自动刷新请求
.aref_cmd (aref_cmd ), //自动刷新阶段写入sdram的指令
.aref_ba (aref_ba ), //自动刷新阶段Bank地址
.aref_addr (aref_addr ), //地址数据,辅助预充电操作
.aref_end (aref_end ) //自动刷新结束标志
);

//------------- sdram_write_inst -------------
sdram_write sdram_write_inst
(
.sys_clk (sys_clk ), //系统时钟,频率100MHz
.sys_rst_n (sys_rst_n ), //复位信号,低电平有效
.init_end (init_end ), //初始化结束信号
.wr_en (wr_en ), //写使能

.wr_addr (sdram_wr_addr ), //写SDRAM地址
.wr_data (sdram_data_in ), //待写入SDRAM的数据(写FIFO传入)
.wr_burst_len (wr_burst_len ), //写突发SDRAM字节数

.wr_ack (sdram_wr_ack ), //写SDRAM响应信号
.wr_end (wr_end ), //一次突发写结束
.write_cmd (write_cmd ), //写数据阶段写入sdram的指令
.write_ba (write_ba ), //写数据阶段Bank地址
.write_addr (write_addr ), //地址数据,辅助预充电操作
.wr_sdram_en (wr_sdram_en ), //数据总线输出使能
.wr_sdram_data (wr_sdram_data ) //写入SDRAM的数据
);

//------------- sdram_read_inst -------------
sdram_read sdram_read_inst
(
.sys_clk (sys_clk ), //系统时钟,频率100MHz
.sys_rst_n (sys_rst_n ), //复位信号,低电平有效
.init_end (init_end ), //初始化结束信号
.rd_en (rd_en ), //读使能

.rd_addr (sdram_rd_addr ), //读SDRAM地址
.rd_data (sdram_dq ), //自SDRAM中读出的数据
.rd_burst_len (rd_burst_len ), //读突发SDRAM字节数

.rd_ack (sdram_rd_ack ), //读SDRAM响应信号
.rd_end (rd_end ), //一次突发读结束
.read_cmd (read_cmd ), //读数据阶段写入sdram的指令
.read_ba (read_ba ), //读数据阶段Bank地址
.read_addr (read_addr ), //地址数据,辅助预充电操作
.rd_sdram_data (sdram_data_out ) //SDRAM读出的数据
);

endmodule

仿真代码编写

在此对SDRAM控制模块sdram_ctrl进行整体仿真,此模块内部实例化初始化模块、自动刷新模块、数据写模块 、数据读模块和仲裁模块,仿真此模块能够更好的测试验证仲裁模块的功能实现。SDRAM控制模块仿真参考代码,具体见代码清单 53‑11。

代码清单 53‑11 SDRAM控制模块模块仿真参考代码(tb_sdram_ctrl.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
\`timescale 1ns/1ns
module tb_sdram_ctrl();

////
//\* Internal Signal and Defparam \//
////

//wire define
//clk_gen
wire clk_50m ; //PLL输出50M时钟
wire clk_100m ; //PLL输出100M时钟
wire clk_100m_shift ; //PLL输出100M时钟,相位偏移-75deg
wire locked ; //PLL时钟锁定信号
wire rst_n ; //复位信号,低有效
//sdram
wire sdram_cke ; //SDRAM时钟使能信号
wire sdram_cs_n ; //SDRAM片选信号
wire sdram_ras_n ; //SDRAM行选通信号
wire sdram_cas_n ; //SDRAM列选题信号
wire sdram_we_n ; //SDRAM写使能信号
wire [1:0] sdram_ba ; //SDRAM L-Bank地址
wire [12:0] sdram_addr ; //SDRAM地址总线
wire [15:0] sdram_dq ; //SDRAM数据总线
//sdram_ctrl
wire init_end ; //初始化完成信号
wire sdram_wr_ack ; //数据写阶段写响应
wire sdram_rd_ack ; //数据读阶段响应

//reg define
reg sys_clk ; //系统时钟
reg sys_rst_n ; //复位信号
reg wr_en ; //写使能
reg [15:0] wr_data_in ; //写数据
reg rd_en ; //读使能

//defparam
//重定义仿真模型中的相关参数
defparam sdram_model_plus_inst.addr_bits = 13; //地址位宽
defparam sdram_model_plus_inst.data_bits = 16; //数据位宽
defparam sdram_model_plus_inst.col_bits = 9; //列地址位宽
defparam sdram_model_plus_inst.mem_sizes = 2*1024*1024; //L-Bank容量

//重定义自动刷新模块自动刷新间隔时间计数最大值
defparam sdram_ctrl_inst.sdram_a_ref_inst.CNT_REF_MAX = 39;

////
//\* Clk And Rst \//
////

//时钟、复位信号
initial
begin
sys_clk = 1'b1 ;
sys_rst_n <= 1'b0 ;
#200
sys_rst_n <= 1'b1 ;
end

always #10 sys_clk = ~sys_clk;

//rst_n:复位信号
assign rst_n = sys_rst_n & locked;

//wr_en:写数据使能
always@(posedge clk_100m or negedge rst_n)
if(rst_n == 1'b0)
wr_en <= 1'b1;
else if(wr_data_in == 10'd10)
wr_en <= 1'b0;
else
wr_en <= wr_en;

//wr_data_in:写数据
always@(posedge clk_100m or negedge rst_n)
if(rst_n == 1'b0)
wr_data_in <= 16'd0;
else if(wr_data_in == 16'd10)
wr_data_in <= 16'd0;
else if(sdram_wr_ack == 1'b1)
wr_data_in <= wr_data_in + 1'b1;
else
wr_data_in <= wr_data_in;

//rd_en:读数据使能
always@(posedge clk_100m or negedge rst_n)
if(rst_n == 1'b0)
rd_en <= 1'b0;
else if(wr_en == 1'b0)
rd_en <= 1'b1;
else
rd_en <= rd_en;

////
//\* Instantiation \//
////

//------------- clk_gen_inst -------------
clk_gen clk_gen_inst (
.inclk0 (sys_clk ),
.areset (~sys_rst_n ),
.c0 (clk_50m ),
.c1 (clk_100m ),
.c2 (clk_100m_shift ),

.locked (locked )
);

//------------- sdram_ctrl_inst -------------
sdram_ctrl sdram_ctrl_inst(

.sys_clk (clk_100m ), //系统时钟
.sys_rst_n (rst_n ), //复位信号,低电平有效
//SDRAM 控制器写端口
.sdram_wr_req (wr_en ), //写SDRAM请求信号
.sdram_wr_addr (24'h000_000 ), //SDRAM写操作的地址
.wr_burst_len (10'd10 ), //写sdram时数据突发长度
.sdram_data_in (wr_data_in ), //写入SDRAM的数据
.sdram_wr_ack (sdram_wr_ack ), //写SDRAM响应信号
//SDRAM 控制器读端口
.sdram_rd_req (rd_en ), //读SDRAM请求信号
.sdram_rd_addr (24'h000_000 ), //SDRAM写操作的地址
.rd_burst_len (10'd10 ), //读sdram时数据突发长度
.sdram_data_out (sdram_data_out ), //从SDRAM读出的数据
.init_end (init_end ), //SDRAM 初始化完成标志
.sdram_rd_ack (sdram_rd_ack ), //读SDRAM响应信号
//FPGA与SDRAM硬件接口
.sdram_cke (sdram_cke ), // SDRAM 时钟有效信号
.sdram_cs_n (sdram_cs_n ), // SDRAM 片选信号
.sdram_ras_n (sdram_ras_n ), // SDRAM 行地址选通脉冲
.sdram_cas_n (sdram_cas_n ), // SDRAM 列地址选通脉冲
.sdram_we_n (sdram_we_n ), // SDRAM 写允许位
.sdram_ba (sdram_ba ), // SDRAM L-Bank地址线
.sdram_addr (sdram_addr ), // SDRAM 地址总线
.sdram_dq (sdram_dq ) // SDRAM 数据总线

);

//-------------sdram_model_plus_inst-------------
sdram_model_plus sdram_model_plus_inst(
.Dq (sdram_dq ),
.Addr (sdram_addr ),
.Ba (sdram_ba ),
.Clk (clk_100m_shift ),
.Cke (sdram_cke ),
.Cs_n (sdram_cs_n ),
.Ras_n (sdram_ras_n ),
.Cas_n (sdram_cas_n ),
.We_n (sdram_we_n ),
.Dqm (2'b0 ),
.Debug (1'b1 )

);

endmodule

仿真代码中实例化了时钟生成模块,此模块为调用的IP核,为其他两模块提供工作时钟;实例化SDRAM控制模块,为本次测试验证模块;还实例化SDRAM仿真模型,辅助模块仿真验证;除此之外,内部仿真生成了读写使能rd_en、wr_en和写数据wr_data_in。读者还需注意的是,为更好的观察仿真波形,我们 使用重定义,缩短自动刷新时间间隔。

仿真波形分析

仿真参考代码编写完成后,我们使用ModelSim进行代码仿真,仿真结果如下。

SDRAM070

图 53‑70 SDRAM仿真模型打印信息

SDRAM071

图 53‑71 仲裁模块仿真波形图

由图 53‑70、图 53‑71可以看出,在各模块的协助下,SDRAM能够正常工作,能够正常初始化,数据读写无误,可进行周期性自动刷新,且在仲裁模块的控制下,各模块能够正常有序的协同工作,不会发生冲突。SDRAM控制模块整体仿真通过。

仲裁模块对不同操作响应波形图见下图。

SDRAM072

图 53‑72 仲裁模块响应初始化操作局部波形图(一)

SDRAM073

图 53‑73 仲裁模块响应初始化操作局部波形图(二)

SDRAM074

图 53‑74 仲裁模块响应数据写操作波形图

SDRAM075

图 53‑75 仲裁模块响应数据读操作波形图

SDRAM076

图 53‑76 仲裁模块响应自动刷新操作波形图

3.2.3.8. FIFO控制模块

SDRAM控制模块整体仿真通过,SDRAM控制器的设计已接近尾声,下面我们来设计并实现一下SDRAM控制器的的最后一个功能模块FIFO控制模块fifo_ctrl。

FIFO控制模块框图

fifo_ctrl模块的功能主要是:使用FIFO对传入的待写入SDRAM的数据和自SDRAM读出的数据进行进行缓存,实现跨时钟域处理;为数据读写模块提供SDRAM读写地址,产生读写请求。

先来说一下fifo_ctrl模块的框图设计,fifio_ctrl模块的模块框图和模块输入输出信号的功能描述,具体见图 53‑77、表格 53‑12。

SDRAM077

图 53‑77 FIFO控制模块框图

表格 53‑11 FIFO控制模块输入输出信号功能描述

信号

位宽

类型

功能描述

sys_clk

1Bit

Input

系统时钟,频率100MHz

sys_rst_n

1Bit

Input

复位信号,低有效

wr_fifo_wr_clk

1Bit

Input

写FIFO写时钟

wr_fifo_wr_req

1Bit

Input

写FIFO写请求

wr_fifo_wr_data

16Bit

Input

写FIFO写数据

sdram_wr_b_addr

24Bit

Input

SDRAM写数据首地址

sdram_wr_e_addr

24Bit

Input

SDRAM写数据末地址

wr_burst_len

10Bit

Input

写突发长度

wr_rst

1Bit

Input

写地址复位信号

rd_fifo_rd_clk

1Bit

Input

读FIFO读时钟

rd_fifo_rd_req

1Bit

Input

读FIFO读请求

sdram_rd_b_addr

24Bit

Input

SDRAM读数据首地址

sdram_rd_e_addr

24Bit

Input

SDRAM读数据末地址

rd_burst_len

10Bit

Input

读突发长度

rd_rst

1Bit

Input

读地址复位信号

read_valid

1Bit

Input

读有效信号

init_end

1Bit

Input

初始化完成信号

sdram_wr_ack

1Bit

Input

SDRAM数据写响应

sdram_rd_ack

1Bit

Input

SDRAM数据读响应

sdram_data_out

16Bit

Input

读出SDRAM数据

sdram_wr_req

1Bit

Output

SDRAM写操作请求

sdram_wr_addr

24Bit

Output

SDRAM写数据地址

sdram_data_in

16Bit

Output

SDRAM待写入数据

sdram_rd_req

1Bit

Output

SDRAM读操作请求

sdram_rd_addr

24Bit

Output

SDRAM读数据地址

rd_fifo_rd_data

16Bit

Output

读FIFO读数据

rd_fifo_num

10Bit

Output

读FIFO数据计数

FIFO控制模块波形图

通过模块框图和表格,我们已经对模块的输入输出信号有了简单的了解,但是模块是怎样通过输入信号,产生输出信号实现具体功能的呢,我们通过波形图的绘制,为大家讲一下各信号的设计与实现。FIFO控制模块整体波形图具体见图 53‑78。

SDRAM078

图 53‑78 FIFO控制模块波形图

第一部分:输入信号具体说明

写FIFO写时钟wr_fifo_wr_clk,位宽1Bit,由时钟生成模块clk_gen生成并传入,时钟频率为50MHz,无相位偏移,与串口数据接收模块系统时钟保持一致。

写FIFO写请求wr_fifo_wr_req,位宽1Bit,由串口数据接收模块传入,请求数据写入写FIFO模块。

写FIFO写数据wr_fifo_wr_data,位宽16Bit,由串口数据接收模块传入,为待写入SDRAM数据,在写FIFO中缓存,做跨时钟域处理。

SDRAM写数据首地址sdram_wr_b_addr、末地址sdram_wr_e_addr,位宽为24Bit,由顶层模块设置并传入,为SDRAM突发写操作首地址、末地址,高2位为数据写入位置的逻辑Bank地址,中间13位为数据写入位置行地址,后9位为数据突发写入首地址。

数据写突发长度wr_burst_len,位宽为10Bit,由顶层模块设置并传入,表示SDRAM一次写突发数据个数。

写地址复位信号wr_rst,高电平有效,信号有效时,将写地址复位为SDRAM写数据首地址。

读FIFO读时钟rd_fifo_rd_clk,位宽1Bit,由时钟生成模块clk_gen生成并传入,时钟频率为50MHz,无相位偏移,与串口数据发送模块系统时钟保持一致。

读FIFO读请求rd_fifo_rd_req,位宽1Bit,由串口数据发送模块传入,请求自读FIFO模块读取数据。

SDRAM读数据首地址sdram_rd_b_addr、末地址sdram_rd_e_addr,位宽为24Bit,由顶层模块设置并传入,为SDRAM突发读操作首地址、末地址,高2位为数据读取位置的逻辑Bank地址,中间13位为数据读取位置行地址,后9位为数据突发读取列地址。

数据读突发长度wr_burst_len,位宽为10Bit,由顶层模块设置并传入,表示SDRAM一次读突发数据个数。

读地址复位信号wr_rst,高电平有效,信号有效时,将读地址复位为SDRAM读数据首地址。

读有效信号read_valid,位宽为1Bit,由顶层模块传入,高电平有效,表示可进行SDRAM数据读取操作。

初始化完成信号init_end,位宽为1Bit,初始化模块产生并传入,高电平表示初始化完成 。

数据写响应信号sdram_wr_ack,位宽为1Bit,由数据写模块传入,SDRAM数据写操作响应。告知fifo_ctrl模块,一次突发写操作已经完成,页突发写操作首地址可以更新;同时,数据写响应信号也作为fifo_ctrl模块中写fifo的读使能信号。

数据读响应信号sdram_rd_ack,位宽为1Bit,由数据读模块传入,SDRAM数据读操作响应。告知fifo_ctrl模块,一次突发读操作已经完成,页突发读操作首地址可以更新;作为fifo_ctrl模块中读fifo的写使能信号。

SDRAM数据读出sdram_data_out,位宽为16Bit,由数据读模块传入,为自SDRAM读出数据。

第二部分:内部变量的设计与实现

在模块内部我们需要对输入的数据写响应信号和数据读响应进行打拍,目的是为了采集到数据写响应信号和数据读响应信号的下降沿。使用数据写响应信号下降沿复位写数据首地址sdram_wr _addr;使用数据读响应信号下降沿复位读数据首地址sdram_rd_addr。声明打拍信号wr_ack_dly、rd_ack_dly和下降沿信号wr_ack_fall、rd_ack_fall,信号波形图如下。

SDRAM079

图 53‑79 内部变量波形图

内部还需要声明变量写fifo数据量监测wr_fifo_num和读fifo数据量监测rd_fifo_num,使用这两个信号和初始化完成信号init_end、读有效信号read_valid作为约束条件,生成数据写请求sdram_wr_req和数据读请求sdram_rd_req。

SDRAM初始化完成后,当wr_fifo_num大于等于写突发长度,数据写请求为高电平,数据读请求信号为低电平;当rd_fifo_num小于读突发长度,且read_valid信号为高电平,数据写请求为低电平,数据读请求信号为高电平,其他时刻,两者均为低电平。波形图如下。

SDRAM080

图 53‑80 输出请求信号波形图

第三部分:数据波形设计与实现

数据读响应信号的另一个作用就是作为写使能信号,将自SDRAM 读出的数据写入读fifo中;数据写响应信号的另一个作用就是作为读使能信号,读取写fifo中暂存的将要写入SDRAM的数据,因为写fifo为普通fifo,输出数据会滞后读使能信号一个时钟周期,信号波形如下。

SDRAM081

图 53‑81 读入数据、输出数据信号波形图

自SDRAM读取的数据被暂存到读fifo,当读fifo中暂存的数据满足一次读突发数据量, fifo_read模块会发送读fifo读使能信号,读取读fifo中的数据,读取数据输出至fifo_read模块,暂存至其内部fifo中。相关信号波形如下。

SDRAM082

图 53‑82 读fifo读数据信号波形图

整合各部分波形图,可得模块整体波形图。

代码编写

参照模块波形图,编写模块参考代码。FIFO控制模块参考代码,具体见代码清单 53‑12。

代码清单 53‑12 FIFO控制模块参考代码(fifo_ctrl.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
module fifo_ctrl
(
input wire sys_clk , //系统时钟
input wire sys_rst_n , //复位信号
//写fifo信号
input wire wr_fifo_wr_clk , //写FIFO写时钟
input wire wr_fifo_wr_req , //写FIFO写请求
input wire [15:0] wr_fifo_wr_data , //写FIFO写数据
input wire [23:0] sdram_wr_b_addr , //写SDRAM首地址
input wire [23:0] sdram_wr_e_addr , //写SDRAM末地址
input wire [9:0] wr_burst_len , //写SDRAM数据突发长度
input wire wr_rst , //写复位信号
//读fifo信号
input wire rd_fifo_rd_clk , //读FIFO读时钟
input wire rd_fifo_rd_req , //读FIFO读请求
input wire [23:0] sdram_rd_b_addr , //读SDRAM首地址
input wire [23:0] sdram_rd_e_addr , //读SDRAM末地址
input wire [9:0] rd_burst_len , //读SDRAM数据突发长度
input wire rd_rst , //读复位信号
output wire [15:0] rd_fifo_rd_data , //读FIFO读数据
output wire [9:0] rd_fifo_num , //读fifo中的数据量

input wire read_valid , //SDRAM读使能
input wire init_end , //SDRAM初始化完成标志
//SDRAM写信号
input wire sdram_wr_ack , //SDRAM写响应
output reg sdram_wr_req , //SDRAM写请求
output reg [23:0] sdram_wr_addr , //SDRAM写地址
output wire [15:0] sdram_data_in , //写入SDRAM的数据
//SDRAM读信号
input wire sdram_rd_ack , //SDRAM读相应
input wire [15:0] sdram_data_out , //读出SDRAM数据
output reg sdram_rd_req , //SDRAM读请求
output reg [23:0] sdram_rd_addr //SDRAM读地址
);

////
//\* Parameter and Internal Signal \//
////

//wire define
wire wr_ack_fall ; //写响应信号下降沿
wire rd_ack_fall ; //读相应信号下降沿
wire [9:0] wr_fifo_num ; //写fifo中的数据量

//reg define
reg wr_ack_dly ; //写响应打拍
reg rd_ack_dly ; //读响应打拍

////
//\* Main Code \//
////

//wr_ack_dly:写响应信号打拍
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
wr_ack_dly <= 1'b0;
else
wr_ack_dly <= sdram_wr_ack;

//rd_ack_dly:读响应信号打拍
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rd_ack_dly <= 1'b0;
else
rd_ack_dly <= sdram_rd_ack;

//wr_ack_fall,rd_ack_fall:检测读写响应信号下降沿
assign wr_ack_fall = (wr_ack_dly & ~sdram_wr_ack);
assign rd_ack_fall = (rd_ack_dly & ~sdram_rd_ack);

//sdram_wr_addr:sdram写地址
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
sdram_wr_addr <= 24'd0;
else if(wr_rst == 1'b1)
sdram_wr_addr <= sdram_wr_b_addr;
else if(wr_ack_fall == 1'b1) //一次突发写结束,更改写地址
begin
if(sdram_wr_addr < (sdram_wr_e_addr - wr_burst_len))
//不使用乒乓操作,一次突发写结束,更改写地址,未达到末地址,写地址累加
sdram_wr_addr <= sdram_wr_addr + wr_burst_len;
else //不使用乒乓操作,到达末地址,回到写起始地址
sdram_wr_addr <= sdram_wr_b_addr;
end

//sdram_rd_addr:sdram读地址
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
sdram_rd_addr <= 24'd0;
else if(rd_rst == 1'b1)
sdram_rd_addr <= sdram_rd_b_addr;
else if(rd_ack_fall == 1'b1) //一次突发读结束,更改读地址
begin
if(sdram_rd_addr < (sdram_rd_e_addr - rd_burst_len))
//读地址未达到末地址,读地址累加
sdram_rd_addr <= sdram_rd_addr + rd_burst_len;
else //到达末地址,回到首地址
sdram_rd_addr <= sdram_rd_b_addr;
end

//sdram_wr_req,sdram_rd_req:读写请求信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
sdram_wr_req <= 1'b0;
sdram_rd_req <= 1'b0;
end
else if(init_end == 1'b1) //初始化完成后响应读写请求
begin //优先执行写操作,防止写入SDRAM中的数据丢失
if(wr_fifo_num >= wr_burst_len)
begin //写FIFO中的数据量达到写突发长度
sdram_wr_req <= 1'b1; //写请求有效
sdram_rd_req <= 1'b0;
end
else if((rd_fifo_num < rd_burst_len)&&(read_valid == 1'b1))
begin //读FIFO中的数据量小于读突发长度,且读使能信号有效
sdram_wr_req <= 1'b0;
sdram_rd_req <= 1'b1; //读请求有效
end
else
begin
sdram_wr_req <= 1'b0;
sdram_rd_req <= 1'b0;
end
end
else
begin
sdram_wr_req <= 1'b0;
sdram_rd_req <= 1'b0;
end

////
//\* Instantiation \//
////

//------------- wr_fifo_data -------------
fifo_data wr_fifo_data(
//用户接口
.wrclk (wr_fifo_wr_clk ), //写时钟
.wrreq (wr_fifo_wr_req ), //写请求
.data (wr_fifo_wr_data), //写数据
//SDRAM接口
.rdclk (sys_clk ), //读时钟
.rdreq (sdram_wr_ack ), //读请求
.q (sdram_data_in ), //读数据

.rdusedw (wr_fifo_num ), //FIFO中的数据量
.wrusedw ( ),
.aclr (~sys_rst_n \|\| wr_rst) //清零信号
);

//------------- rd_fifo_data -------------
fifo_data rd_fifo_data(
//sdram接口
.wrclk (sys_clk ), //写时钟
.wrreq (sdram_rd_ack ), //写请求
.data (sdram_data_out ), //写数据
//用户接口
.rdclk (rd_fifo_rd_clk ), //读时钟
.rdreq (rd_fifo_rd_req ), //读请求
.q (rd_fifo_rd_data), //读数据

.rdusedw ( ),
.wrusedw (rd_fifo_num ), //FIFO中的数据量
.aclr (~sys_rst_n \|\| rd_rst) //清零信号
);

endmodule

FIFO控制模块介绍完毕,SDRAM控制器的子功能模块介绍完了,对于FIFO控制模块的仿真验证,我们在介绍完SDRAM控制器顶层代码sdram_top之后,直接对SDRAM控制器进行整体仿真,到时再具体说明。

3.2.3.9. SDRAM控制器顶层模块

FIFO控制模块介绍完毕后, SDRAM控制器的子功能模块均已介绍完毕,下面来说明一下SDRAM控制器的最后一个模块,SDRAM控制器顶层模块sdram_top。

作为SDRAM控制器的顶层模块,模块作用较为简单,对内例化FIFO控制模块和SDRAM控制模块,连接子模块对应信号;对外接收地址、使能及数据信号,输出SDRAM指令信号、地址及数据。在此我们只对sdram_top模块的整体框图和参考代码做一下介绍,波形图不需要说明,最后对SDRAM控制器进行整体仿真 ,查看FIFO控制模块的仿真波形。

SDRAM控制器顶层模块框图

SDRAM控制器顶层模块框图设计和模块输入输出信号的功能描述,具体见图 53‑83、表格 53‑12。

SDRAM083

图 53‑83 SDRAM控制器顶层模块框图

表格 53‑12 SDRAM控制器顶层模块输入输出信号功能描述

信号

位宽

类型

功能描述

sys_clk

1Bit

Input

系统时钟,频率100MHz

sys_rst_n

1Bit

Input

复位信号,低有效

clk_out

1Bit

Input

写FIFO写时钟

wr_fifo_wr_clk

1Bit

Input

fifo_ctrl模块写FIFO写时钟

wr_fifo_wr_req

1bit

Input

fifo_ctrl模块写FIFO写请求

wr_fifo_wr_data

16Bit

Input

fifo_ctrl模块写FIFO写数据

sdram_wr_b_addr

24Bit

Input

SDRAM写数据首地址

wr_burst_len

10Bit

Input

写突发长度

rd_fifo_rd_clk

1Bit

Input

fifo_ctrl模块读FIFO读时钟

rd_fifo_rd_req

1Bit

Input

fifo_ctrl模块读FIFO读请求

sdram_rd_b_addr

24Bit

Input

SDRAM读数据首地址

rd_burst_len

10Bit

Input

读突发长度

read_valid

1Bit

Input

读有效信号

sdram_clk

1Bit

Output

SDRAM写时钟

sdram_cke

1Bit

Output

SDRAM时钟有效信号

sdram_cs_n

1Bit

Output

SDRAM片选信号

sdram_ras_n

1Bit

Output

SDRAM行选通信号

sdram_cas_n

1Bit

Output

SDRAM列选通信号

sdram_we_n

1Bit

Output

SDRAM写使能信号

sdram_ba

2Bit

Output

SDRAM逻辑Bank地址

sdram_addr

13Bit

Output

SDRAM地址总线

sdram_dqm

2Bit

Output

SDRAM数据掩码

sdram_dq

16Bit

Output

SDRAM数据总线

rd_fifo_rd_data

16Bit

Output

输出至fifo_read模块的读FIFO读数据

rd_fifo_num

10Bit

Output

fifo_ctrl模块中读FIFO内数据量

代码编写

SDRAM控制器顶层模块参考代码,具体见代码清单 53‑13。

代码清单 53‑13 SDRAM控制器顶层模块参考代码(sdram_top.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
\`timescale 1ns/1ns
module sdram_top
(
input wire sys_clk , //系统时钟
input wire clk_out , //相位偏移时钟
input wire sys_rst_n , //复位信号,低有效
//写FIFO信号
input wire wr_fifo_wr_clk , //写FIFO写时钟
input wire wr_fifo_wr_req , //写FIFO写请求
input wire [15:0] wr_fifo_wr_data , //写FIFO写数据
input wire [23:0] sdram_wr_b_addr , //写SDRAM首地址
input wire [23:0] sdram_wr_e_addr , //写SDRAM末地址
input wire [9:0] wr_burst_len , //写SDRAM数据突发长度
input wire wr_rst , //写复位信号
//读FIFO信号
input wire rd_fifo_rd_clk , //读FIFO读时钟
input wire rd_fifo_rd_req , //读FIFO读请求
input wire [23:0] sdram_rd_b_addr , //读SDRAM首地址
input wire [23:0] sdram_rd_e_addr , //读SDRAM末地址
input wire [9:0] rd_burst_len , //读SDRAM数据突发长度
input wire rd_rst , //读复位信号
output wire [15:0] rd_fifo_rd_data , //读FIFO读数据
output wire [9:0] rd_fifo_num , //读fifo中的数据量

input wire read_valid , //SDRAM读使能
output wire init_end , //SDRAM初始化完成标志
//SDRAM接口信号
output wire sdram_clk , //SDRAM芯片时钟
output wire sdram_cke , //SDRAM时钟有效信号
output wire sdram_cs_n , //SDRAM片选信号
output wire sdram_ras_n , //SDRAM行地址选通脉冲
output wire sdram_cas_n , //SDRAM列地址选通脉冲
output wire sdram_we_n , //SDRAM写允许位
output wire [1:0] sdram_ba , //SDRAM的L-Bank地址线
output wire [12:0] sdram_addr , //SDRAM地址总线
output wire [1:0] sdram_dqm , //SDRAM数据掩码
inout wire [15:0] sdram_dq //SDRAM数据总线
);

////
//\* Parameter and Internal Signal \//
////

//wire define
wire sdram_wr_req ; //sdram 写请求
wire sdram_wr_ack ; //sdram 写响应
wire [23:0] sdram_wr_addr ; //sdram 写地址
wire [15:0] sdram_data_in ; //写入sdram中的数据

wire sdram_rd_req ; //sdram 读请求
wire sdram_rd_ack ; //sdram 读响应
wire [23:0] sdram_rd_addr ; //sdram 读地址
wire [15:0] sdram_data_out ; //从sdram中读出的数据

//sdram_clk:SDRAM芯片时钟
assign sdram_clk = clk_out;
//sdram_dqm:SDRAM数据掩码
assign sdram_dqm = 2'b00;

////
//\* Instantiation \//
////

//------------- fifo_ctrl_inst -------------
fifo_ctrl fifo_ctrl_inst(

//system signal
.sys_clk (sys_clk ), //SDRAM控制时钟
.sys_rst_n (sys_rst_n ), //复位信号
//write fifo signal
.wr_fifo_wr_clk (wr_fifo_wr_clk ), //写FIFO写时钟
.wr_fifo_wr_req (wr_fifo_wr_req ), //写FIFO写请求
.wr_fifo_wr_data(wr_fifo_wr_data), //写FIFO写数据
.sdram_wr_b_addr(sdram_wr_b_addr), //写SDRAM首地址
.sdram_wr_e_addr(sdram_wr_e_addr), //写SDRAM末地址
.wr_burst_len (wr_burst_len ), //写SDRAM数据突发长度
.wr_rst (wr_rst ), //写清零信号
//read fifo signal
.rd_fifo_rd_clk (rd_fifo_rd_clk ), //读FIFO读时钟
.rd_fifo_rd_req (rd_fifo_rd_req ), //读FIFO读请求
.rd_fifo_rd_data(rd_fifo_rd_data), //读FIFO读数据
.rd_fifo_num (rd_fifo_num ), //读FIFO中的数据量
.sdram_rd_b_addr(sdram_rd_b_addr), //读SDRAM首地址
.sdram_rd_e_addr(sdram_rd_e_addr), //读SDRAM末地址
.rd_burst_len (rd_burst_len ), //读SDRAM数据突发长度
.rd_rst (rd_rst ), //读清零信号
//USER ctrl signal
.read_valid (read_valid ), //SDRAM读使能
.init_end (init_end ), //SDRAM初始化完成标志
//SDRAM ctrl of write
.sdram_wr_ack (sdram_wr_ack ), //SDRAM写响应
.sdram_wr_req (sdram_wr_req ), //SDRAM写请求
.sdram_wr_addr (sdram_wr_addr ), //SDRAM写地址
.sdram_data_in (sdram_data_in ), //写入SDRAM的数据
//SDRAM ctrl of read
.sdram_rd_ack (sdram_rd_ack ), //SDRAM读请求
.sdram_data_out (sdram_data_out ), //SDRAM读响应
.sdram_rd_req (sdram_rd_req ), //SDRAM读地址
.sdram_rd_addr (sdram_rd_addr ) //读出SDRAM数据

);

//------------- sdram_ctrl_inst -------------
sdram_ctrl sdram_ctrl_inst(

.sys_clk (sys_clk ), //系统时钟
.sys_rst_n (sys_rst_n ), //复位信号,低电平有效
//SDRAM 控制器写端口
.sdram_wr_req (sdram_wr_req ), //写SDRAM请求信号
.sdram_wr_addr (sdram_wr_addr ), //SDRAM写操作的地址
.wr_burst_len (wr_burst_len ), //写sdram时数据突发长度
.sdram_data_in (sdram_data_in ), //写入SDRAM的数据
.sdram_wr_ack (sdram_wr_ack ), //写SDRAM响应信号
//SDRAM 控制器读端口
.sdram_rd_req (sdram_rd_req ), //读SDRAM请求信号
.sdram_rd_addr (sdram_rd_addr ), //SDRAM写操作的地址
.rd_burst_len (rd_burst_len ), //读sdram时数据突发长度
.sdram_data_out (sdram_data_out ), //从SDRAM读出的数据
.init_end (init_end ), //SDRAM 初始化完成标志
.sdram_rd_ack (sdram_rd_ack ), //读SDRAM响应信号
//FPGA与SDRAM硬件接口
.sdram_cke (sdram_cke ), // SDRAM 时钟有效信号
.sdram_cs_n (sdram_cs_n ), // SDRAM 片选信号
.sdram_ras_n (sdram_ras_n ), // SDRAM 行地址选通脉冲
.sdram_cas_n (sdram_cas_n ), // SDRAM 列地址选通脉冲
.sdram_we_n (sdram_we_n ), // SDRAM 写允许位
.sdram_ba (sdram_ba ), // SDRAM L-Bank地址线
.sdram_addr (sdram_addr ), // SDRAM 地址总线
.sdram_dq (sdram_dq ) // SDRAM 数据总线

);

endmodule

仿真代码编写

对SDRAM控制器顶层模块进行仿真就是对SDRAM控制器进行整体仿真,前文中我们已对诸多子功能模块进行仿真验证,在本次仿真中,我们只查看FIFO控制模块波形图,进行仿真验证。SDRAM控制器仿真参考代码,具体见代码清单 53‑14。

代码清单 53‑14 SDRAM控制器仿真参考代码(tb_sdram_top.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
\`timescale 1ns/1ns
module tb_sdram_top();

////
//\* Internal Signal and Defparam \//
////
//wire define
//clk_gen
wire clk_50m ; //PLL输出50M时钟
wire clk_100m ; //PLL输出100M时钟
wire clk_100m_shift ; //PLL输出100M时钟,相位偏移-30deg
wire locked ; //PLL时钟锁定信号
wire rst_n ; //复位信号,低有效
//sdram
wire sdram_clk ; //SDRAM时钟
wire sdram_cke ; //SDRAM时钟使能信号
wire sdram_cs_n ; //SDRAM片选信号
wire sdram_ras_n ; //SDRAM行选通信号
wire sdram_cas_n ; //SDRAM列选题信号
wire sdram_we_n ; //SDRAM写使能信号
wire [1:0] sdram_ba ; //SDRAM L-Bank地址
wire [12:0] sdram_addr ; //SDRAM地址总线
wire [15:0] sdram_dq ; //SDRAM数据总线
wire sdram_dqm ; //SDRAM数据总线
//sdram_ctrl
wire init_end ; //初始化完成信号
wire sdram_wr_ack ; //数据写阶段写响应
wire sdram_rd_ack ; //数据读阶段响应

wire [9:0] rd_fifo_num ; //fifo_ctrl模块中读fifo中的数据量
wire [15:0] rfifo_rd_data ; //fifo_ctrl模块中读fifo读数据

//reg define
reg sys_clk ; //系统时钟
reg sys_rst_n ; //复位信号
reg wr_en ; //写使能
reg wr_en_dly ; //写使能打拍
reg [15:0] wr_data_in ; //写数据
reg rd_en ; //读使能
reg [2:0] cnt_wr_wait ; //数据写入间隔计数
reg [3:0] cnt_rd_data ; //读出数据计数
reg wr_data_flag ; //fifo_ctrl模块中写fifo写使能
reg read_valid ; //读有效信号

//defparam
//重定义仿真模型中的相关参数
defparam sdram_model_plus_inst.addr_bits = 13; //地址位宽
defparam sdram_model_plus_inst.data_bits = 16; //数据位宽
defparam sdram_model_plus_inst.col_bits = 9; //列地址位宽
defparam sdram_model_plus_inst.mem_sizes = 2*1024*1024; //L-Bank容量

//重定义自动刷新模块自动刷新间隔时间计数最大值
defparam sdram_top_inst.sdram_ctrl_inst.sdram_a_ref_inst.CNT_REF_MAX=40;

////
//\* Main Code \//
////

//时钟、复位信号
initial
begin
sys_clk = 1'b1 ;
sys_rst_n <= 1'b0 ;
#200
sys_rst_n <= 1'b1 ;
end

always #10 sys_clk = ~sys_clk;

//rst_n:复位信号
assign rst_n = sys_rst_n & locked;

//wr_en:写数据使能
always@(posedge clk_50m or negedge rst_n)
if(rst_n == 1'b0)
wr_en <= 1'b1;
else if(wr_data_in == 10'd10)
wr_en <= 1'b0;
else
wr_en <= wr_en;

//cnt_wr_wait:数据写入间隔计数
always@(posedge clk_50m or negedge rst_n)
if(rst_n == 1'b0)
cnt_wr_wait <= 3'd0;
else if(wr_en == 1'b1)
cnt_wr_wait <= cnt_wr_wait + 1'b1;
else
cnt_wr_wait <= 3'd0;

//wr_data_flag:fifo_ctrl模块中写fifo写使能
always@(posedge clk_50m or negedge rst_n)
if(rst_n == 1'b0)
wr_data_flag <= 1'b0;
else if(cnt_wr_wait == 3'd7)
wr_data_flag <= 1'b1;
else
wr_data_flag <= 1'b0;

//read_valid:数据读使能
always@(posedge clk_50m or negedge rst_n)
if(rst_n == 1'b0)
read_valid <= 1'b1;
else if(rd_fifo_num == 10'd10)
read_valid <= 1'b0;

//wr_en_dly:写数据使能打拍
always@(posedge clk_50m or negedge rst_n)
if(rst_n == 1'b0)
wr_en_dly <= 1'b0;
else
wr_en_dly <= wr_en;

//wr_data_in:写数据
always@(posedge clk_50m or negedge rst_n)
if(rst_n == 1'b0)
wr_data_in <= 16'd0;
else if(cnt_wr_wait == 3'd7)
wr_data_in <= wr_data_in + 1'b1;
else
wr_data_in <= wr_data_in;

//rd_en:读数据使能
always@(posedge clk_50m or negedge rst_n)
if(rst_n == 1'b0)
rd_en <= 1'b0;
else if(cnt_rd_data == 4'd9)
rd_en <= 1'd0;
else if((wr_en == 1'b0) && (rd_fifo_num == 10'd10))
rd_en <= 1'b1;
else
rd_en <= rd_en;

//cnt_rd_data:读出数据计数
always@(posedge clk_50m or negedge rst_n)
if(rst_n == 1'b0)
cnt_rd_data <= 4'd0;
else if(rd_en == 1'b1)
cnt_rd_data <= cnt_rd_data + 1'b1;
else
cnt_rd_data <= 4'd0;



////
//\* Instantiation \//
////

//------------- clk_gen_inst -------------
clk_gen clk_gen_inst (
.inclk0 (sys_clk ),
.areset (~sys_rst_n ),
.c0 (clk_50m ),
.c1 (clk_100m ),
.c2 (clk_100m_shift ),

.locked (locked )
);

//------------- sdram_top_inst -------------
sdram_top sdram_top_inst(
.sys_clk (clk_100m ), //sdram 控制器参考时钟
.clk_out (clk_100m_shift ), //用于输出的相位偏移时钟
.sys_rst_n (rst_n ), //系统复位
//用户写端口
.wr_fifo_wr_clk (clk_50m ), //写端口FIFO: 写时钟
.wr_fifo_wr_req (wr_data_flag ), //写端口FIFO: 写使能
.wr_fifo_wr_data (wr_data_in ), //写端口FIFO: 写数据
.sdram_wr_b_addr (24'd0 ), //写SDRAM的首地址
.sdram_wr_e_addr (24'd10 ), //写SDRAM的末地址
.wr_burst_len (10'd10 ), //写SDRAM时的数据突发长度
.wr_rst (~rst_n ), //写地址复位信号
//用户读端口
.rd_fifo_rd_clk (clk_50m ), //读端口FIFO: 读时钟
.rd_fifo_rd_req (rd_en ), //读端口FIFO: 读使能
.rd_fifo_rd_data (rfifo_rd_data ), //读端口FIFO: 读数据
.sdram_rd_b_addr (24'd0 ), //读SDRAM的首地址
.sdram_rd_e_addr (24'd10 ), //读SDRAM的末地址
.rd_burst_len (10'd10 ), //从SDRAM中读数据时的突发长度
.rd_rst (~rst_n ), //读地址复位信号
.rd_fifo_num (rd_fifo_num ), //读fifo中的数据量
//用户控制端口
.read_valid (read_valid ), //SDRAM 读使能
//SDRAM 芯片接口
.sdram_clk (sdram_clk ), //SDRAM 芯片时钟
.sdram_cke (sdram_cke ), //SDRAM 时钟有效
.sdram_cs_n (sdram_cs_n ), //SDRAM 片选
.sdram_ras_n (sdram_ras_n ), //SDRAM 行有效
.sdram_cas_n (sdram_cas_n ), //SDRAM 列有效
.sdram_we_n (sdram_we_n ), //SDRAM 写有效
.sdram_ba (sdram_ba ), //SDRAM Bank地址
.sdram_addr (sdram_addr ), //SDRAM 行/列地址
.sdram_dq (sdram_dq ), //SDRAM 数据
.sdram_dqm (sdram_dqm ) //SDRAM 数据掩码
);

//-------------sdram_model_plus_inst-------------
sdram_model_plus sdram_model_plus_inst(
.Dq (sdram_dq ),
.Addr (sdram_addr ),
.Ba (sdram_ba ),
.Clk (sdram_clk ),
.Cke (sdram_cke ),
.Cs_n (sdram_cs_n ),
.Ras_n (sdram_ras_n ),
.Cas_n (sdram_cas_n ),
.We_n (sdram_we_n ),
.Dqm (2'b0 ),
.Debug (1'b1 )

);

endmodule

仿真波形分析

使用Modelsim对代码进行仿真,查看FIFO控制模块仿真波形,由于仿真波形时间跨度较大,我们对FIFO控制模块仿真波形进行部分截取。通过对比仿真波形与绘制波形图,两者各信号波形一致,模块通过仿真验证。仿真波形图具体见图 53‑84、图 53‑85。

SDRAM084

图 53‑84 FIFO控制模块仿真波形图(一)

SDRAM085

图 53‑85 FIFO控制模块仿真波形图(二)

3.2.3.10. 时钟生成模块

在整个实验工程中,我们在多处调用FIFO对数据进行跨时钟域处理,之所以这样是因为串口、SDRAM控制器和输出至SDRAM的时钟各不相同,而这些时钟的生成离不开时钟生成模块。

本实验工程工作时钟是依靠调用的IP核生成,所以在本小节,我们只对时钟模块的框图和输入输出信号做一下介绍。时钟模块框图和输入输出信号功能描述具体见图 53‑86。

SDRAM086

图 53‑86 时钟生成模块框图

表格 53‑13 时钟生成模块输入输出引脚功能描述

信号

位宽

类型

功能描述

inclk0

1Bit

Input

系统时钟,频率50MHz

areset

1Bit

Input

复位信号,高有效

C0

1Bit

Output

输出50MHz时钟信号

C1

1Bit

Output

输出100MHz时钟信号

C2

1Bit

Output

输出100MHz时钟信号,时钟偏移-30度

locked

1Bit

Output

输出locked信号

时钟分频信号输入端口有两个,在inclk0端口输入待分频时钟sys_clk;复位端口areset为好有效复位端口,将复位信号sys_rst_n取反后输入。输出端口有四路,C1端口输出的时钟信号频率为50MHz,作为串口接收模块和fifo_read模块的工作时钟;C2端口输出的时钟信号频率为100MH z,作为SDRAM控制器的工作时钟;C3端口输出的时钟信号频率为100MHz,时钟相位偏移-30度,为SDRAM芯片工作时钟,时钟相位的偏移是为了保证SDRAM芯片能够读取到指令、数据的稳定状态。

时钟生成模块简单介绍到这里,对于串口收发模块,在此处我们不在进行讲解,读者若有疑问可参阅前面相关章节。

3.2.3.11. 数据回传缓存模块

在实验目标的小节中,我们说过要使用串口和SDRAM控制器实现数据回环,但串口收发数据的速率是由波特率决定的,而非时钟,为了将自SDRAM中读出的数据与串口数据发送速率相匹配,我们需要调用FIFO进行数据缓存,实现跨时钟域的处理,这就是我们本小节要讲的数据回传缓存模块。

数据回传缓存模块框图

数据回传缓存模块的具体功能介绍完毕后,我们来进行模块设计的具体讲解。下面我们会根据图 53‑87和表格 53‑14,对本模块的输入输出信号做一下介绍。

SDRAM087

图 53‑87 数据回传缓存模块框图

表格 53‑14 数据回传缓存模块输入输出引脚功能描述

信号

位宽

类型

功能描述

sys_clk

1Bit

Input

系统时钟,频率50MHz

sys_rst_n

1Bit

Input

复位信号,低有效

rd_fifo_num

10Bit

Input

读FIFO缓存数据量

pi_data

8Bit

Input

读FIFO读出数据

burst_num

10Bit

Input

数据读突发长度

read_en

1Bit

Output

读FIFO读使能

tx_flag

1Bit

Output

回传数据标志信号

tx_data

8Bit

Output

回传数据

输入信号具体说明如下:

系统时钟信号sys_clk,位宽1Bit,由时钟生成模块clk_gen生成并传入,时钟频率为50MHz,无相位偏移,为本模块工作时钟。

复位信号sys_rst_n,位宽1Bit,由顶层模块生成并传入,低电平有效。

读FIFO缓存数据量rd_fifo_num,由fifo_ctrl模块传入,表示fifo_ctrl模块中调用的读FIFO中缓存数据个数,在本模块中作为约束条件约束其他信号变化。

读FIFO读出数据pi_data,位宽8Bit,表示自fifo_ctrl模块中调用的读FIFO中读取的数据,也是自SDRAM读取的数据。串口收发数据的数据为宽只能为8Bit,虽然自SDRAM读出的数据为16Bit,但高8位数据无意义,低8位通过pi_data传入本模块。

数据读突发长度burst_num,为宽为10Bit,由顶层模块设置并传入,表示一次读取fifo_ctrl模块中调用的读FIFO中数据个数,要与数据读模块中数据读突发长度保持参数一致。

输出信号具体说明如下:

读FIFO读使能read_en,位宽1Bit,为fifo_ctrl模块中调用的读FIFO的读使能。

回传数据标志信号tx_flag,位宽为1Bit,与回传数据tx_data成对出现,作为串口接收模块数据接收的标志信号。

回传数据tx_data,位宽8Bit,数据来自本模块调用的FIFO模块的输出端口,与回传数据标志信号tx_flag,成对输入至串口接收模块,表示经过跨时钟域处理的SDRAM读出数据。

数据回传缓存模块波形图

对于模块的具体功能和输入输出信号,我们已经做了具体的讲解,那么如何利用输入信号产生输出信号,实现模块功能呢。接下来我们开始模块波形图的绘制,在此我们会对模块中各信号的设计与实现做详细讲解。数据回传缓存模块波形图,具体见图 53‑88、图 53‑89、图 53‑90。

SDRAM088

图 53‑88 数据回传缓存模块波形图(一)

SDRAM089

图 53‑89 数据回传缓存模块波形图(二)

SDRAM090

图 53‑90 数据回传缓存模块波形图(三)

由于篇幅限制,我们将本模块整体波形图一分为三,由图可知,本模块中除了之前介绍的8路输入输出信号外,内部还声明8个变量,诸多信号协同合作,实现模块的具体功能,下面我们结合波形图对各信号波形进行详细讲解。

输入信号包括5路,时钟和复位信号不再多说,后面3路信号再做一下进一步说明。

信号rd_fifo_num,由fifo_ctrl模块输入,表示其中调用读FIFO中数据缓存个数,前面1-10的的数据变化,表示自SDRAM读出数据写入读FIFO的过程,后面10-0的数据变化表示本模块读取读FIFO数据的过程。

信号pi_data,由fifo_ctrl模块输入,表示自其中调用读FIFO中读取数据,当本模块读使能read_en有效时,自读FIFO中读取数据,读取的数据写入本模块调用的FIFO中,因为数据读模块调用的为普通FIFO,所以读使能提前读数据pi_data一个时钟周期。

信号burst_num,顶层模块设置并输入,为固定参数,表示本模块一次读取读FIFO中数据个数,参数数值与数据读模块中读突发长度一致,防止数据读空。

本模块内部声明8个变量,参与模块功能的实现,下面对各变量的设计与实现做具体说明。

data_num,本模块调用FIFO的存储数据量参数,作用是作为约束条件控制read_en和rd_flag信号变化,进而控制FIFO数据的写入和读出。

read_en_dly,为本模块调用FIFO的写使能,延后read_en一个时钟,与pi_data数据时序一致,能够正确的将pi_data写入FIFO中。

baud_cnt、bit_cnt、bit_flag这三路信号的作用是为了是输出数据、数据标志信号匹配串口接收模块的波特率,在串口RS232章节有详细讲解,在此不再赘述。

rd_en为本模块调用FIFO的读使能,当bit_cnt计数到9、bit_flag为高电平是,表示可进行新数据的读取,rd_en信号拉高,其他时刻均保持低电平。

rd_flag为baud_cnt计数器划定计数范围,当FIFO中数据存储数据个数等于burst_num,rd_flag信号拉高;当cnt_read读出数据计数器计数值等于burst_num,rd_flag拉低,表示数据以读取完毕。

cnt_read为读出数据计数器,rd_en拉高一次代表从FIFO中读取一个字节数据,cnt_read自加1,当读取数据个数与固定参数burst_num相同是,cnt_read信号拉低。

代码编写

参照绘制的波形图进行参考代码的编写,在绘制波形图小节已经对各信号有了非常详细的介绍,在此只列出模块参考代码,不再进行过多叙述。模块参考代码,具体见代码清单 53‑15。

代码清单 53‑15 数据回传缓存模块(fifo_read.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
module fifo_read
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低电平有效
input wire [9:0] rd_fifo_num , //SDRAM中读fifo中数据个数
input wire [7:0] pi_data , //读出数据
input wire [9:0] burst_num , //一次突发数据个数

output reg read_en , //SDRAM中读fifo的读使能
output wire [7:0] tx_data , //输出数据
output reg tx_flag //输出数据标志信号
);

////
//\* Parameter and Internal Signal \//
////

//parameter define
parameter BAUD_CNT_END = 13'd5207 ,
BAUD_CNT_END_HALF = 13'd2603 ;
parameter CNT_WAIT_MAX = 24'd4_999_999 ;

//wire define
wire [9:0] data_num ; //fifo中数据个数

//reg define
reg read_en_dly ;
reg [12:0] baud_cnt ;
reg rd_en ;
reg rd_flag ;
reg [9:0] cnt_read ;
reg [3:0] bit_cnt ;
reg bit_flag ;

////
//\* Main Code \//
////
//read_en:SDRAM中读fifo的读使能
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
read_en <= 1'b0;
else if(rd_fifo_num == burst_num)
read_en <= 1'b1;
else if(data_num == burst_num - 2)
read_en <= 1'b0;

//read_en_dly:SDRAM中读fifo的读使能打拍
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
read_en_dly <= 1'b0;
else
read_en_dly <= read_en;

//rd_flag:向tx模块发送数据使能
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rd_flag <= 1'b0;
else if(cnt_read == burst_num)
rd_flag <= 1'b0;
else if(data_num == burst_num)
rd_flag <= 1'b1;

//baud_cnt:波特率计数器计数从0计数到BAUD_CNT_END
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
baud_cnt <= 13'd0;
else if(baud_cnt == BAUD_CNT_END)
baud_cnt <= 13'd0;
else if(rd_flag == 1'b1)
baud_cnt <= baud_cnt + 1'b1;

//bit_flag:bit计数器计数使能
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
bit_flag <= 1'b0;
else if(baud_cnt == BAUD_CNT_END_HALF)
bit_flag <= 1'b1;
else
bit_flag <= 1'b0;

//bit_cnt:bit计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
bit_cnt <= 4'b0;
else if((bit_cnt == 4'd9) && (bit_flag == 1'b1))
bit_cnt <= 4'b0;
else if(bit_flag == 1'b1)
bit_cnt <= bit_cnt + 1'b1;

//rd_en:读fifo的读使能
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rd_en <= 1'b0;
else if(bit_cnt == 4'd9 && bit_flag == 1'b1)
rd_en <= 1'b1;
else
rd_en <= 1'b0;

//cnt_read:读出数据计数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_read <= 10'd0;
else if(cnt_read == burst_num)
cnt_read <= 10'b0;
else if(rd_en == 1'b1)
cnt_read <= cnt_read + 1'b1;
else
cnt_read <= cnt_read;

//tx_flag:读出数据标志信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
tx_flag <= 1'b0;
else
tx_flag <= rd_en;

////
//\* Instantiation \//
////

//-------------fifo_read_inst--------------
read_fifo read_fifo_inst(
.clock (sys_clk ), //input clk
.data (pi_data ), //input [7 : 0] din
.wrreq (read_en_dly ), //input wr_en
.rdreq (rd_en ), //input rd_en

.q (tx_data ), //output [7 : 0] dout
.usedw (data_num )
);

endmodule

到这里整个实验工程已接近尾声,待我们讲解完实验工程的顶层模块uart_sdram,对整个实验工程进行整体仿真,到时再查看fifo_read模块的仿真波形。

3.2.3.12. 顶层模块

顶层模块uart_sdram中实例化了串口收发模块、时钟生成模块、数据回传缓存模块和最重要的SDRAM控制器,将各子模块对应信号相连,对外连接板卡的时钟信号、复位信号和串口数据接收端口,输出指令、地址、数据等信号到SDRAM芯片,回传数据到串口数据发送端口。

代码编写较为简单,在此只对模块框图、输入输出信号功能作一下简单描述,不需要波形图的绘制。

顶层模块框图

顶层模块框图和输入输出信号功能描述,具体见图 53‑91、表格 53‑15。

SDRAM091

图 53‑91 顶层模块框图

表格 53‑15 顶层模块输入输出引脚功能描述

信号

位宽

类型

功能描述

sys_clk

1Bit

Input

系统时钟,频率50MHz

sys_rst_n

1Bit

Input

复位信号,低有效

rx

1Bit

Input

串口数据接收端口

tx

1Bit

Output

串口数据发送端口

sdram_clk

1Bit

Output

SDRAM写时钟

sdram_cke

1Bit

Output

SDRAM时钟有效信号

sdram_cs_n

1Bit

Output

SDRAM片选信号

sdram_ras_n

1Bit

Output

SDRAM行选通信号

sdram_cas_n

1Bit

Output

SDRAM列选通信号

sdram_we_n

1Bit

Output

SDRAM写使能信号

sdram_ba

2Bit

Output

SDRAM逻辑Bank地址

sdram_addr

13Bit

Output

SDRAM地址总线

sdram_dqm

2Bit

Output

SDRAM数据掩码

sdram_dq

16Bit

Output

SDRAM数据总线

代码编写

顶层模块参考代码,具体见代码清单 53‑16。

代码清单 53‑16 顶层模块参考代码(uart_sdram.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
module uart_sdram
(
input wire sys_clk , //时钟信号
input wire sys_rst_n , //复位信号
input wire rx , //串口接收数据

output wire tx , //串口发送数据

output wire sdram_clk , //SDRAM 芯片时钟
output wire sdram_cke , //SDRAM 时钟有效
output wire sdram_cs_n , //SDRAM 片选
output wire sdram_cas_n , //SDRAM 行有效
output wire sdram_ras_n , //SDRAM 列有效
output wire sdram_we_n , //SDRAM 写有效
output wire [1:0] sdram_ba , //SDRAM Bank地址
output wire [12:0] sdram_addr , //SDRAM 行/列地址
output wire [1:0] sdram_dqm , //SDRAM 数据掩码
inout wire [15:0] sdram_dq //SDRAM 数据
);

////
//\* Parameter and Internal Signal \//
////
//parameter define
parameter DATA_NUM = 24'd10 ; //写入SDRAM数据个数
parameter WAIT_MAX = 16'd750 ; //等待计数最大值
parameter UART_BPS = 14'd9600 , //比特率
CLK_FREQ = 26'd50_000_000 ; //时钟频率

// wire define
//uart_rx
wire [ 7:0] rx_data ; //串口接收模块拼接后的8位数据
wire rx_flag ; //数据标志信号

//fifo_read
wire [ 7:0] rfifo_wr_data ; //读fifo发热写入数据
wire rfifo_wr_en ; //读fifo的写使能
wire [ 7:0] rfifo_rd_data ; //读fifo的读数据
wire rfifo_rd_en ; //读fifo的读使能
wire [9:0] rd_fifo_num ; //读fifo中的数据量

//clk_gen
wire clk_50m ;
wire clk_100m ;
wire clk_100m_shift ; //pll产生时钟
wire locked ; //pll锁定信号
wire rst_n ; //复位信号

//sdram_top_inst
reg [23:0] data_num ; //写入SDRAM数据个数计数
reg read_valid ; //数据读使能
reg [15:0] cnt_wait ; //等待计数器

////
//\* Main Code \//
////
//rst_n:复位信号
assign rst_n = sys_rst_n & locked;

//data_num:写入SDRAM数据个数计数
always@(posedge clk_50m or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_num <= 24'd0;
else if(read_valid == 1'b1)
data_num <= 24'd0;
else if(rx_flag == 1'b1)
data_num <= data_num + 1'b1;
else
data_num <= data_num;

//cnt_wait:等待计数器
always@(posedge clk_50m or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_wait <= 16'd0;
else if(cnt_wait == WAIT_MAX)
cnt_wait <= 16'd0;
else if(data_num == DATA_NUM)
cnt_wait <= cnt_wait + 1'b1;

//read_valid:数据读使能
always@(posedge clk_50m or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
read_valid <= 1'b0;
else if(cnt_wait == WAIT_MAX)
read_valid <= 1'b1;
else if(rd_fifo_num == DATA_NUM)
read_valid <= 1'b0;

////
//\* Instantiation \//
////
//------------- clk_gen_inst -------------
clk_gen clk_gen_inst (
.inclk0 (sys_clk ),
.areset (~sys_rst_n ),
.c0 (clk_50m ),
.c1 (clk_100m ),
.c2 (clk_100m_shift ),

.locked (locked )
);

//-------------uart_rx_inst-------------
uart_rx
#(
.UART_BPS (UART_BPS ), //串口波特率
.CLK_FREQ (CLK_FREQ ) //时钟频率
)
uart_rx_inst
(
.sys_clk (clk_50m ), //input sys_clk
.sys_rst_n (rst_n ), //input sys_rst_n
.rx (rx ), //input rx

.po_data (rx_data ), //output [7:0] rx_data
.po_flag (rx_flag ) //output rx_flag
);

//------------- sdram_top_inst -------------
sdram_top sdram_top_inst
(
.sys_clk (clk_100m ), //sdram 控制器参考时钟
.clk_out (clk_100m_shift ), //用于输出的相位偏移时钟
.sys_rst_n (rst_n ), //系统复位
//用户写端口
.wr_fifo_wr_clk (clk_50m ), //写端口FIFO: 写时钟
.wr_fifo_wr_req (rx_flag ), //写端口FIFO: 写使能
.wr_fifo_wr_data ({8'b0,rx_data} ), //写端口FIFO: 写数据
.sdram_wr_b_addr (24'd0 ), //写SDRAM的起始地址
.sdram_wr_e_addr (DATA_NUM ), //写SDRAM的结束地址
.wr_burst_len (DATA_NUM ), //写SDRAM时的数据突发长度
.wr_rst ( ), //写复位
//用户读端口
.rd_fifo_rd_clk (clk_50m ), //读端口FIFO: 读时钟
.rd_fifo_rd_req (rfifo_wr_en ), //读端口FIFO: 读使能
.rd_fifo_rd_data (rfifo_wr_data ), //读端口FIFO: 读数据
.sdram_rd_b_addr (24'd0 ), //读SDRAM的起始地址
.sdram_rd_e_addr (DATA_NUM ), //读SDRAM的结束地址
.rd_burst_len (DATA_NUM ), //从SDRAM中读数据时的突发长度
.rd_rst ( ), //读复位
.rd_fifo_num (rd_fifo_num ), //读fifo中的数据量
//用户控制端口
.read_valid (read_valid ), //SDRAM 读使能
.init_end ( ), //SDRAM 初始化完成标志
//SDRAM 芯片接口
.sdram_clk (sdram_clk ), //SDRAM 芯片时钟
.sdram_cke (sdram_cke ), //SDRAM 时钟有效
.sdram_cs_n (sdram_cs_n ), //SDRAM 片选
.sdram_ras_n (sdram_ras_n ), //SDRAM 行有效
.sdram_cas_n (sdram_cas_n ), //SDRAM 列有效
.sdram_we_n (sdram_we_n ), //SDRAM 写有效
.sdram_ba (sdram_ba ), //SDRAM Bank地址
.sdram_addr (sdram_addr ), //SDRAM 行/列地址
.sdram_dq (sdram_dq ), //SDRAM 数据
.sdram_dqm (sdram_dqm ) //SDRAM 数据掩码
);

//------------- fifo_read_inst --------------
fifo_read fifo_read_inst
(
.sys_clk (clk_50m ), //input sys_clk
.sys_rst_n (sys_rst_n ), //input sys_rst_n
.rd_fifo_num (rd_fifo_num ),
.pi_data (rfifo_wr_data ), //input [7:0] pi_data
.burst_num (DATA_NUM ),

.read_en (rfifo_wr_en ), //input pi_flag
.tx_data (rfifo_rd_data ), //output [7:0] tx_data
.tx_flag (rfifo_rd_en ) //output tx_flag

);

//-------------uart_tx_inst-------------
uart_tx
#(
.UART_BPS (UART_BPS ), //串口波特率
.CLK_FREQ (CLK_FREQ ) //时钟频率
)
uart_tx_inst
(
.sys_clk (sys_clk ), //input sys_clk
.sys_rst_n (sys_rst_n ), //input sys_rst_n
.pi_data (rfifo_rd_data ), //input [7:0] pi_data
.pi_flag (rfifo_rd_en ), //input pi_flag

.tx (tx ) //output tx
);

endmodule

仿真代码编写

实验工程至此已接近尾声,我们直接对整个工程进行仿真,查看数据回环缓存模块仿真波形。工程仿真参考代码,具体见代码清单 53‑17。

代码清单 53‑17 工程整体仿真参考代码(tb_uart_sdram.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
\`timescale 1ns/1ns
module tb_uart_sdram();

////
//\* Internal Signal and Defparam \//
////

//wire define
wire tx ;
wire sdram_clk ;
wire sdram_cke ;
wire sdram_cs_n ;
wire sdram_cas_n ;
wire sdram_ras_n ;
wire sdram_we_n ;
wire [1:0] sdram_ba ;
wire [12:0] sdram_addr ;
wire [1:0] sdram_dqm ;
wire [15:0] sdram_dq ;

//reg define
reg sys_clk ;
reg sys_rst_n ;
reg rx ;
reg [7:0] data_mem [9:0] ; //data_mem是一个存储器,相当于一个ram

////
//\* Clk And Rst \//
////

//读取sim文件夹下面的data.txt文件,并把读出的数据定义为data_mem
initial
$readmemh("E:/sources/sdram_test/uart_sdram/sim/test_data.txt",data_mem);

//时钟、复位信号
initial
begin
sys_clk = 1'b1 ;
sys_rst_n <= 1'b0 ;
#200
sys_rst_n <= 1'b1 ;
end

always #10 sys_clk = ~sys_clk;


initial
begin
rx <= 1'b1;
#200
rx_byte();
end

task rx_byte();
integer j;
for(j=0;j<10;j=j+1)
rx_bit(data_mem[j]);
endtask

task rx_bit(input[7:0] data); //data是data_mem[j]的值。
integer i;
for(i=0;i<10;i=i+1)
begin
case(i)
0: rx <= 1'b0 ; //起始位
1: rx <= data[0];
2: rx <= data[1];
3: rx <= data[2];
4: rx <= data[3];
5: rx <= data[4];
6: rx <= data[5];
7: rx <= data[6];
8: rx <= data[7]; //上面8个发送的是数据位
9: rx <= 1'b1 ; //停止位
endcase
#1040; //一个波特时间=ssys_clk周期*波特计数器
end
endtask

//重定义defparam,用于修改参数,缩短仿真时间
defparam uart_sdram_inst.uart_rx_inst.BAUD_CNT_END = 52;
defparam uart_sdram_inst.uart_rx_inst.BAUD_CNT_END_HALF = 26;
defparam uart_sdram_inst.uart_tx_inst.BAUD_CNT_END = 52;
defparam uart_sdram_inst.fifo_read_inst.BAUD_CNT_END_HALF = 26;
defparam uart_sdram_inst.fifo_read_inst.BAUD_CNT_END = 52;
defparam sdram_model_plus_inst.addr_bits = 13;
defparam sdram_model_plus_inst.data_bits = 16;
defparam sdram_model_plus_inst.col_bits = 9;
defparam sdram_model_plus_inst.mem_sizes = 2*1024*1024;

////
//\* Instantiation \//
////

//-------------uart_sdram_inst-------------
uart_sdram uart_sdram_inst(

.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.rx (rx ),

.tx (tx ),

.sdram_clk (sdram_clk ),
.sdram_cke (sdram_cke ),
.sdram_cs_n (sdram_cs_n ),
.sdram_cas_n (sdram_cas_n ),
.sdram_ras_n (sdram_ras_n ),
.sdram_we_n (sdram_we_n ),
.sdram_ba (sdram_ba ),
.sdram_addr (sdram_addr ),
.sdram_dqm (sdram_dqm ),
.sdram_dq (sdram_dq )

);

//-------------sdram_model_plus_inst-------------
sdram_model_plus sdram_model_plus_inst(
.Dq (sdram_dq ),
.Addr (sdram_addr ),
.Ba (sdram_ba ),
.Clk (sdram_clk ),
.Cke (sdram_cke ),
.Cs_n (sdram_cs_n ),
.Ras_n (sdram_ras_n ),
.Cas_n (sdram_cas_n ),
.We_n (sdram_we_n ),
.Dqm (sdram_dqm ),
.Debug (1'b1 )
);

endmodule

仿真波形分析

使用ModelSim进行波形仿真,数据回环缓存模块仿真结果如下,绘制波形图与仿真波形图各信号波形变化一致,模块仿真验证通过。

SDRAM092

图 53‑92 数据回环缓存模块整体仿真波形图

SDRAM093

图 53‑93 数据回环缓存模块局部仿真波形图(一)

SDRAM094

图 53‑94 数据回环缓存模块局部仿真波形图(二)

SDRAM095

图 53‑95 数据回环缓存模块局部仿真波形图(三)

SDRAM096

图 53‑96 数据回环缓存模块局部仿真波形图(四)

3.3. 上板调试

3.3.1. 引脚约束

仿真验证通过后,准备上板验证,上板验证之前先要进行引脚约束。工程中各输入输出信号与开发板引脚对应关系如表格 53‑16所示。

表格 53‑16 引脚分配表

信号名

信号类型

对应引脚

备注

sys_clk

Input

E1

时钟

sys_rst_n

Input

M15

复位

rx

Input

N6

串口接受数据

tx

Input

N5

串口发送数据

sdram_clk

Output

R4

SDRAM芯片时钟

sdram_cke

Output

R9

SDRAM时钟有效信号

sdram_cs_n

Output

R12

SDRAM片选信号

sdram_cas_n

Output

R10

SDRAM列地址选通脉冲

sdram_ras_n

Output

R11

SDRAM行地址选通脉冲

sdram_we_n

Output

L9

SDRAM写允许位

sdram_ba[0]

Output

R13

SDRAM的L-Bank地址线

sdram_ba[1]

Output

R14

SDRAM的L-Bank地址线

sdram_addr[0]

Output

P11

SDRAM地址总线

sdram_addr[1]

Output

P14

SDRAM地址总线

sdram_addr[2]

Output

N9

SDRAM地址总线

sdram_addr[3]

Output

N11

SDRAM地址总线

sdram_addr[4]

Output

T14

SDRAM地址总线

sdram_addr[5]

Output

T13

SDRAM地址总线

sdram_addr[6]

Output

T12

SDRAM地址总线

sdram_addr[7]

Output

T11

SDRAM地址总线

sdram_addr[8]

Output

T10

SDRAM地址总线

sdram_addr[9]

Output

P9

SDRAM地址总线

sdram_addr[10]

Output

T15

SDRAM地址总线

sdram_addr[11]

Output

N12

SDRAM地址总线

sdram_addr[12]

Output

M11

SDRAM地址总线

sdram_dqm[0]

Output

M10

SDRAM数据掩码

sdram_dqm[1]

Output

M9

SDRAM数据掩码

sdram_dq[0]

Output

R3

SDRAM数据总线

sdram_dq[1]

Output

T9

SDRAM数据总线

sdram_dq[2]

Output

R5

SDRAM数据总线

sdram_dq[3]

Output

R6

SDRAM数据总线

sdram_dq[4]

Output

R7

SDRAM数据总线

sdram_dq[5]

Output

M8

SDRAM数据总线

sdram_dq[6]

Output

R8

SDRAM数据总线

sdram_dq[7]

Output

N8

SDRAM数据总线

sdram_dq[8]

Output

P8

SDRAM数据总线

sdram_dq[9]

Output

T8

SDRAM数据总线

sdram_dq[10]

Output

T7

SDRAM数据总线

sdram_dq[11]

Output

T6

SDRAM数据总线

sdram_dq[12]

Output

T5

SDRAM数据总线

sdram_dq[13]

Output

T4

SDRAM数据总线

sdram_dq[14]

Output

T3

SDRAM数据总线

sdram_dq[15]

Output

T2

SDRAM数据总线

下面进行管脚分配,管脚的分配方法在前面章节已有所讲解,在此就不再过多叙述,管脚的分配如下图 53‑97所示。

SDRAM097

图 53‑97 管脚分配

3.3.1.1. 结果验证

如图 53‑98所示,开发板连接12V直流电源、USB-Blaster下载器JTAG端口、USB数据线和短路帽。线路正确连接后,打开开关为板卡上电。

SDRAM098

图 53‑98 程序下载连线图

如图 53‑99所示,使用“Programmer”为开发板下载程序。

SDRAM099

图 53‑99 程序下载图

程序下载完成后,使用串口助手向开发板发送连续10字节数据,数据写入SDRAM,随后写入SDRAM的10字节数据读出,并通过串口助手打印显示,如图 53‑100所示。写入数据与读出数据相同,验证通过。

SDRAM100

图 53‑100 串口读写SDRAM

3.4. 章末总结

经过本章节的学习,相信读者已经对SDRAM的相关知识、数据读写操作有了相当深刻的认识,本章节重点讲解了SDRAM页突发数据读写的方式,SDRAM的功能远不只次,读者可查阅相关资料,了解更多SDRAM知识。

3.5. 拓展训练

试着将乒乓操作加入SDRAM 控制器。