FPGA 39 SDRAM 控制器驱动设计(优化:实现连续读写功能的操作)

FPGA 39 SDRAM 控制器驱动设计

一、基本知识和概念: SDRAM 和 SRAM 的了解和学习

SDRAM : 同步动态随机存储器(Synchronous Dynamic Random Access Memory)

1、同步的意思是 :时钟频率与对应控制器(CPU/FPGA)的系统时钟频率相同,并且内部命令的发送与数据传输都是以该时钟为基准

2、动态的意思是 :不断的刷新来保证数据不丢失;

3、随机存储器的意思是: 读取和写入可以随机指定地址,而不是必须按照严格的线性次序变化。

还有一种器件叫做SRAM(Asynchronous Static RAM ) : 器件名字和相比 SDRAM 少了一个 D, 也就是叫做同步随机存储器,但是就是不需要动态刷新。此外;S 也不是代表同步的意思。相反, SRAM 属于异步器件,在工作时是【不需要】外部提供时钟的 。https://zhuanlan.zhihu.com/p/410903212

​ 现在的单片机里面使用的RAM,属于静态RAM或SRAM,这个和电脑用的内存条有所不同。只要你把数据写入SRAM后,不断电或者不清除掉,这个数据就一直保存在那里。电脑用的是动态RAM,要不断给它加刷新脉冲才能保存数据。

​ 【注:单片机 fpga dsp 内部自带的ram 应该都是 SRAM 或者叫做静态RAM ,个人感觉可以这么理解,因为是基于 CMOS 来实现的,掉电以后数据还是在里面,而且也不需要额外配置时钟,我理解的FPGA 里面的ram在我们使用IP核的是时候,既可以作为ram也可以作为rom,但是使用的资源都是fpga里面的ram资源来实现这些功能的】

SDRAM 和SRAM 的比较 :

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yC8ZJRs7-1632748635690)(E:/Blog_Template/source/_posts/img/blog_img/fpga/image-20210918202311748.png)]

SDRAM 存取原理(流程):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bfZNZKRx-1632748635693)(E:/Blog_Template/source/_posts/img/blog_img/fpga/image-20210918202506055.png)]

数据存取内部电路结构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xOHAkqc8-1632748635695)(E:/Blog_Template/source/_posts/img/blog_img/fpga/image-20210918203558391.png)]

【说人话】: 右边器件有一个电容(个人定义为数据存储电容),假设电容如果充满电,那么该电平就是 ‘1 ’,也就是数据bit存取的内容是 ‘ 1 ’ ,

第一步:先选通行地址,然后刷新放大器就会接收到此时电容的电压值。

第二步:接收到电压值以后,有一个刷新放大器(不知道怎么理解这个东西,就简单理解是一个信号比较器就行,当电容的电压小于 1/2 是,认为是存储的0,> 1/2 时,列地址线的电压就是高电平)

第三步:选中列地址线,当列地址线选通后,数据就可以读出来了。

tRCD : 激活行时间间隔。(说人话: 就是行选通以后,cmos 导通会存在一定的时间延时,电容那边的信号才能传输到三极管的左边,这个就叫做 激活行_时间间隔)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-slWA5DyY-1632748635697)(img/blog_img/fpga/image-20210926202024046.png)]

只有经过了 tRCD 时间,我们选通列地址线,这样才能保证输出的数据才是我们存取的数据。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dU9x4Ljv-1632748635698)(E:/Blog_Template/source/_posts/img/blog_img/fpga/image-20210918205304373.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FFeaGPhY-1632748635699)(E:/Blog_Template/source/_posts/img/blog_img/fpga/image-20210918205426047.png)]

CL : 列选通潜伏期延时时间 (说人话 :就是左边竖着的 三极管的到信号的一定的传输延时,最后到输出端口的时间延时时间)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j9LL1iWU-1632748635700)(E:/Blog_Template/source/_posts/img/blog_img/fpga/image-20210918210057611.png)]

二、SDRAM 【器件】引脚,注:不是驱动引脚。

SDRAM内部基本结构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YAnMgQG9-1632748635701)(E:/Blog_Template/source/_posts/img/blog_img/fpga/image-20210918210827250.png)]

通常SDRAM的存储空间被划分为4个L-Bank,在寻址时需要【先】指定其中一个L-Bank,【然后】在这个【选定】的L-Bank中选择相应的【行】与【列】进行寻址(寻址就是指定存储单元地址的过程)

SDRAM总存储容量 = L-Bank的数量(一般为4)×行数×列数×数据位宽DQ[x:0]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eQzezyRx-1632748635702)(E:/Blog_Template/source/_posts/img/blog_img/fpga/image-20210918210531939.png)]

引脚功能分析,4大部分

第一部分:(时钟功能引脚)

CLK : 时钟

CKE : 时钟使能引脚, CKE =1 时,时钟CLK才能正常工作。

第二部分:(功能逻辑控制引脚)

CS_N : 片选引脚 CS=0 时,器件片选成功(就理解为 spi 时序的片选就行)

RAS_N: 行地址选通引脚 RAS_N =0 时,可以预充电,同时 可以读取 【行地址】

CAS_N: 列地址选通引脚 CAS_N=0 时, 可以读取 【行地址】

WE_N : 写使能,WE_N =0 时,进行写使能 和预充电。

注: 这部分都是低电平有效,这个部分主要实现的SDRAM 的逻辑控制操作。

第三部分:(数据地址部分引脚)

BA[1:0] : bank 地址,一个SDRAM 中,一般是有 4个bank,想把数据读写在某个SDRAM中,首先就要确定选择的哪个bank,通过BA[1:0] 来确定选中的bank。

A[12:0]:地址总线, 关于地址线上的数据,在不同的命令中有不同的意义,详 见下文对 A[12:0]的功能描述。【注: 主要是根据不同的 行地址选通RAS_N 和 列地址选通CAS_N,看后面发送的是什么行地址信号或者是列地址信号】

DQM[1:0] :数据掩码

(说人话: DQM[1:0]表示的是数据输出位宽的选择:【0 0】 表示16bit都选通,【0 1】高8位有效,低8位无效。 【1 0】低8位有效 ,屏蔽高8位 , 【1 1】是无效的或者是错误的信号。,一般来说,就设置 0 0 就可以了。 )

备注: 传输低宽度数据的时候,我们不希望其它数据线表示的数据被录入。如传输8位数据的时候,我们只需要DQ[7:0]表示的数据,而DQ[15:8]数据线表示的数据必须忽略,否则会修改非目标存储空间的内容。所以数据输入输出时,还会使用DQM[1:0]线来配合,每根DQM线对应8位数据,如"DQM0"=0,“DQM1”=1时,数据线DQ[7:0]表示的数据有效,而DQ[15:8]表示的数据无效。

第四部分:(数据输入输出引脚)

DQ[15:0] : 数据总线, 数据输入输出复用,可以作为数据输入端口,也可以作为数据输出端口。

本次使用和学习的SDRAM 器件基本参数:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RM7VQVSp-1632748635703)(img/blog_img/fpga/image-20210918215342357.png)]

三、SDRAM 的操作和控制学习:

SDRAM 的读写方式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IEjAfUjr-1632748635703)(FPGA 39 SDRAM 控制器设计.assets/image-20210918215709076.png)]

读写指令基本概念:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yZiUVrPm-1632748635704)(img/blog_img/fpga/image-20210918215112491.png)]



[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n5vhjF0I-1632748635705)(img/blog_img/fpga/image-20210918220007506.png)]

SDRAM 操作命令:(总共对这个器件的操作命令一共就11个,所以花点时间就可以学会如何使用SDRAM )

为什么要学SDRAM ? : 这样你的内存就够用了,基本的功能实现起来就很舒服,虽然学的时候很痛苦,但是我认为是非常有必要搞一搞的。

而且指令不多,看着有点头大(至少看第一遍的时候,我说懵逼的,看第二次,第三次就好很多了)

第1个命令:禁止命令 (禁止片选信号指令)

控制信号组成{CS_N, RAS_N, CAS_N, WE}=4’b1xxx
解析CS_N =1 的时候,器件不工作,其他信号没有用

第2个命令:空闲命令(空闲状态命令)

控制信号组成{CS_N, RAS_N, CAS_N, WE}=4’b0111
解释CS_N =0 的时候,器件被选中,但是不进行读和写操作

第3个命令:**加载模式寄存器命令 ** 【注:这个命令还要配和 ADDR 来完成内部的一些配置操作】

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9ed9XWBr-1632748635705)(img/blog_img/fpga/image-20210918220949150.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5lbeBBiP-1632748635706)(img/blog_img/fpga/image-20210918221130133.png)]

这里面会涉及到加载模式寄存器命令的时候,ADDR[11:0] 的配置,会执行不同的模式。

在 **加载模式寄存器命令 ** 下 : {CS_N, RAS_N, CAS_N, WE}=4’b0000 的时候

ADDR[11:0] 各个bit 位对应的关系(即:通过addr 来完成对器件内部的工作状态进行配置)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-62gD08WP-1632748635707)(img/blog_img/fpga/image-20210918221521452.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rNc4DX0v-1632748635708)(img/blog_img/fpga/image-20210918221456477.png)]

加载模式寄存器命令下一共有4个模式(功能参数)的配置(就是对 ADDR[11:0] 的配置,主要有4种配置)

接下来对寄存器配置下的–> 4种不同模式进行详细的说明:

第1种:运行模式(Operating Mode) :

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u8OvhtKT-1632748635708)(FPGA 39 SDRAM 控制器设计.assets/image-20210918221909346.png)]

第2种:写突发模式(Write Burst Mode)

写突发模式设置位是第 9 位(A9),其控制着 SDRAM 的写突发模式。

​ 如果 A9 = 0,那么对于 SDRAM 的读和写都将采用突发方式,具体突发长度由突发长度寄存器 [A0:A2] 设定。

​ A9 = 1, 那么对于 SDRAM 的读,依然遵从突发长度寄存器设定的值进行,而对于SDRAM 的写,则不再具备突发属性,每个写入命令只能写入一个数据。

​ 注: 这里{A9} 的设置看情况,我理解的是 {A9 } = 0 即可

第3种:突发类型(Burst Type) : 这个是配合写突发模式来设置的。暂时还看不明白

由A3控制这个模式主要用来适应一些嵌入式系统的大端小端模式。

大端和小端: https://www.cnblogs.com/little-white/p/3236548.html

简单来说: 大端就是,存储方式和数字是一样的,地址有小到大自加 ,先存放高位数据,在存放低位数据。(和我们正常理解的数据显示是一样的)

小端就是,地址有小到大自加 ,先存放低位数据,在存高位数据。

eg: 串口字符串发送和接收就是典型的大端模式:发送 “12345789”

大端数据(正常串口)接收的时候: 也是 “12345678”,有点类似与FIFO,先进先出

小端接收的数据是:“87654321”,有点类似与堆栈,先进后出。

突发类型分为两类,顺序和隔行:(具体解释看下面)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wtf8qs9k-1632748635709)(img/blog_img/fpga/image-20210918232206308.png)]

注:看这个图来说, [A0:A2] = 000 顺序 , [A0:A2] = 1 逆序

【A3 =0 : 大端(高位在前) A3 =1 : 小端(低位在前) ,我理解的是没什么卵用,数据反正都是一下 16bit直接存。】

第4种: 设置列选通潜伏延时周期时钟个数(简称:列潜伏期)(CAS (READ) latency,简称 CL)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fCkdNgpJ-1632748635710)(img/blog_img/fpga/image-20210918233554013.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MmWgy4xZ-1632748635710)(img/blog_img/fpga/image-20210918233800003.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0wnLfvlu-1632748635711)(img/blog_img/fpga/image-20210918233820642.png)]

这个是根据 [A4:A6] 列选通潜伏期设置寄存器 ,进行设置,一般为了方便,其实可以直接设置位3个潜伏期(就是延时3个时钟周期读写)拉倒,这样可能更方便(个人理解)

第5个命令:激活命令(其实就是一个行启动命令)

控制信号组成 :{CS_N, RAS_N, CAS_N, WE}=4’b0011

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U5XcHZD6-1632748635712)(img/blog_img/fpga/image-20210918235311798.png)]

注: 发送这个指令的时候,同时配置

bank寄存器 : [BA0:BA1]

地址线 : [A0:A12] 来选择行。

第6个命令:读数据命令

{CS_N, RAS_N, CAS_N, WE}=4’b0101

读命令(READ)用来启动对一个已经激活行的突发读取操作。

BA0 和 BA1 指定需要读取的 BANK

地址线上某些位 A0–A9, A11(x4 4bit数据位宽的SDRAM),A0– A9(x8 8bit数据位宽的SDRAM), A0–A8(x16 16bit数据位宽的SDRAM))指定需要读取的数据起始【列地址】(这个是看数据位宽DQ是多少来进行配置)

A10 控制是 否在突发读取完成之后立即执行预充电,即关闭当前(读/写)操作。

若A10 =1 则在读取完当前行以后立即自动执行预充电操作(关闭当前行),即本次读取以后,关闭数据读取操作。

若为A10 =0 ,则不关闭该行,使其继续处于激活状态,以便于紧接着对该行执行新 的读写操作。即可以继续执行读数据命令,然后写一个新的列地址,可以连续执行,而不需要再次执行激活命令

DQM 控制该位对应的数据是否被正常的读出,(例如, 16 位的 SDRAM 芯片, DQM0 对应控制 DQ[7:0], DQM1 对应控制 DQ[15:8]。(一般来说:DQM[1:0] = 0 0 即可,即高8位和低8位数据都有效。)

第7个命令:写数据命令

{CS_N, RAS_N, CAS_N, WE}=4’b0100

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JsANpf0O-1632748635712)(img/blog_img/fpga/image-20210919000751203.png)]

注: 读命令和写命令的BA[1:0] ADDR[10:0] DQ[15:8] 的配置是一样的,仅仅是指令不宜一样,就不多重复讲解了。

第8个命令:**预充电命令(PRECHARGE,用于关闭行) **

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XXPfhqqb-1632748635713)(img/blog_img/fpga/image-20210919001117271.png)]

关闭以后,需要等待一定的时间,tRP时延(预充电指令的时间周期个数延时)以后,就可以再次正常的读和写操作。

第9个指令:自动预充电命令,这个命令感觉和读写命令,对A10操作是一样,几乎不用这个,或者说,在空闲状态下,将 A10 = 1即可,其他时候不用管。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9qtj3WSG-1632748635713)(img/blog_img/fpga/image-20210919001349915.png)]

第10个指令:**突发终止命令 **

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oPXNaeV6-1632748635714)(img/blog_img/fpga/image-20210919002040804.png)]

注: 暂时不知道要用在哪个地方,可能是中断会用,写一半的时候,其他任务优先级更高,需要读写数据到SDRAM的某个地址中,这样的话,执行的任务可能需要使用到突发终止命令。

第11个指令:**自动刷新命令 **

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rOvCP3fN-1632748635715)(img/blog_img/fpga/image-20210919003325850.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gbjNz4bP-1632748635715)(img/blog_img/fpga/image-20210919003450950.png)]

自动刷新命令是:在一个时间段内,必须要执行8192次自动刷新命令,我也不知道他是怎么操作的,文章后面可以看下如何使用的。(这个再内部有一个定时器,没个一个固定的时间就开始发送一个标志信号,随后在不忙的时候,开始启动自动刷新命令,来保证数据不丢失)

下面是另外一个命令(下面的是自刷新命令)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xd27Xbkg-1632748635716)(FPGA 39 SDRAM 控制器设计.assets/image-20210919002958662.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VWNmXzEb-1632748635716)(img/blog_img/fpga/image-20210919003556135.png)]

注: 这个是为了在低功耗的情况下使用的,CKE 都直接拉低了,然后也不对数据进行读和写操作,但是SDRAM 的数据是可以保存下来的。(一般情况下,我们不用这个命令)

四、SDRAM 操作时序

1、上电初始化(注:上电时候,必须先执行该时序操作一次(每次上电仅执行一次),只有该时序完成后,才能进行其他操作)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wQYlRoUx-1632748635717)(img/blog_img/fpga/image-20210919004059196.png)]
2、

第一步: 当 SDRAM 的 VDD 和 VDDQ 上电,并且时钟稳定后 ,SDRAM 首先需要延时【等待 100us】

第二步:在等待时间内, 对于 SDRAM 只能赋给禁止命令(INHIBIT ={1000})或者空操作命令(NOP ={0111}),【就是第1个片选状态禁止命令和 第2个空闲状态命令,差点给我整懵逼了.】

第三步:100us 的延时后,需要对 SDRAM 首先执行(发送)一个预充电命令PRECHARGE ={0010}, 即 第8个命令PRECHARGE ={0010} && 且A10=1 && BA[1:0] = 1 1 ,所有的bank 都必须被预充电 ,以使器件所有的 BANK 都处于空闲状态 。

第四步:bank 进入空闲状态以后,至少需要执行2个周期的自动刷新命令={0001} 。

第五步:自动刷新命令完成之后,就可以对 SDRAM 进行加载模式寄存器命令={ 0000 } +

​ ADDR[11:0] ={OP_CODE,2’b00,SDR_CL,SDR_BT,SDR_BL};l来完成寄存器的配置了 。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eJ0NjkZd-1632748635720)(img/blog_img/fpga/image-20210919005526165.png)]

第六步:在模式加载完成后的第2个时钟周期(时延),就可以执行激活命令或者空闲命令了【如果只是初始化完成,后面执行空闲命令即可,如果是要开始读写数据,则要开始写激活命令,然后等2个时钟周期,开始写入读写命令即可】。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uzFicAd0-1632748635720)(img/blog_img/fpga/image-20210919010552645.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gPCFOTho-1632748635721)(img/blog_img/fpga/image-20210919010655559.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k4m6D8Tz-1632748635722)(img/blog_img/fpga/image-20210919010713574.png)]

实现方式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GLaCsAnM-1632748635722)(img/blog_img/fpga/image-20210919010754665.png)]

注: 线性序列机的使用,用于驱动或者是模块的初始化操作。(这里体现的很明显,流程是一步一步自上而下走过来的。)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mFIz0j6v-1632748635723)(img/blog_img/fpga/image-20210919011103471.png)]

Verilog语法中parameter与localparam的区别 :https://blog.csdn.net/yang2011079080010/article/details/51507904

简单来讲:

parameter : 是整个项目的全局定义的变量(参数),项目种中所有的文件都会遵循这个变量(参数)的定义。

localparam :是模块内部的定义局部变量(参数),不会影响到其他文件。

上电完成以后,我们开始设计整个系统的大(主)状态机:

主状态机实现:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m0TOys0d-1632748635724)(img/blog_img/fpga/image-20210920011447956.png)]

可以看到,我们只有在上电初始化完成以后,才能完成对系统的读、写、自动刷新这3个大的状态机切换。

然后,我们对在突发情况下的三种模式内部的时序(有限状态机)逻辑操作进行编写:

在上电初始化完成以后,我们就要开始设计电路的刷新状态时序、读状态时序、写状态时序的功能编写(我们认为是内部的状态机的实现编写):

一、自动刷新状态时序实现逻辑:

//自动刷新操作任务,采用线性序列机方法
localparam 	ref_PRE_TIME  = 1'b1,
            ref_REF1_TIME = REF_PRE+1,
            ref_REF2_TIME = REF_PRE+REF_REF+1,
            ref_END = REF_PRE+REF_REF*2;

//自动刷新过程时间计数器
always@(posedge Clk or negedge Rst_n)
begin
    if(!Rst_n)
    	ref_cnt <= 16'd0;
    else if(ref_cnt == ref_END) //自动刷新状态任务到了最后时刻
    	ref_cnt <= 16'd0;
    else if(ref_req || ref_cnt>1'b0) // 如果有 ref_req =1 (自动刷新请求信号过来)|| ref_cnt>1'b0 (已经在执行自动刷新任务的过程中)
    	ref_cnt <= ref_cnt + 16'd1;
    else
    	ref_cnt <= ref_cnt;
end

//自动刷新操作,线性序列机
task auto_ref;
begin
    case(ref_cnt)
        ref_PRE_TIME:begin //开始时刻
        Command <= C_PRE; //预充电命令
        Sa[10] <= 1'b1;  // 同时设置addr[10] =1 电平信号  
        end
        ref_REF1_TIME:begin 
        Command <= C_AREF; //第一个自动刷新命令
        end
        ref_REF2_TIME:begin
        Command <= C_AREF; //第二个自动刷新命令
        end
        ref_END:begin
        FF <= 1'b1;       //发送本次任务完成标志信号
        Command <= C_NOP; //切换到空闲状态命令
        end
        default:
        Command <= C_NOP; //其余时刻都是空闲状态指令
    endcase
end
endtask

//为了便于其他功能对当前状态的了解(就是给一些反馈信号出来,让其他模块或者组件知道你现在是在干什么)
//这个是方便给其他人做判断,你就可以理解为,你现在计划去打篮球,别人告诉你现在有场地或者没有场地,你才能决定下一步的计划
//所以我们这里给出了这个自动刷新的两个输出的寄存器标志信号,给其他模块来做判断

// 标志信号1 : 本次自动刷新操作完成标志信号(是一个脉冲信号)
// ref_opt_done <= 1'b0; 表示在非完成状态(有两种情况:不在执行该任务,或者是在执行该任务,但是没完成) 
// ref_opt_done <= 1'b1; 表示刷新完成.
always@(posedge Clk or negedge Rst_n)
begin
    if(!Rst_n)
    	ref_opt_done <= 1'b0;
    else if(ref_cnt == ref_END)
    	ref_opt_done <= 1'b1;
    else
    	ref_opt_done <= 1'b0;
end

//一次突发写操作过程状态标识信号 ??
//  ref_opt <= 1'b1; 刷新过程在【忙状态】, ref_opt <= 1'b0; 本次刷新完成 ,刷新处于【空闲状态】
reg ref_opt;
always@(posedge Clk or negedge Rst_n)
begin
    if(!Rst_n)
    	ref_opt <= 1'b0;
    else if(ref_req == 1'b1)     //如果出现了自动刷新请求信号,表示处于【忙】状态
    	ref_opt <= 1'b1;
    else if(ref_opt_done == 1'b1) //自动刷新完成时刻到了,表示处于【空闲状态】
    	ref_opt <= 1'b0;
    else
    	ref_opt <= ref_opt;
end

//-------------一次写操作任务------------------------


二、读状态时序实现逻辑:

//一次突发写操作任务,线性序列机方法
localparam 	wr_ACT_TIME = 1'b1,
            wr_WRITE_TIME = SC_RCD+1,
            wr_PRE_TIME = SC_RCD+SC_BL+WR_PRE+1,
            wr_END_TIME = SC_RCD+SC_BL+WR_PRE+REF_PRE;

//一次突发写操作过程时间计数器
always@(posedge Clk or negedge Rst_n)
begin
    if(!Rst_n)
    	wr_cnt <= 16'd0;
    else if(wr_cnt == wr_END_TIME)
    	wr_cnt <= 16'd0;
    else if(wr_req||wr_cnt>1'b0)
    	wr_cnt <= wr_cnt + 16'd1;
    else
    	wr_cnt <= 16'd0;
end

//一次突发写操作任务,类似线性序列机方法
task write_data;
begin
    case(wr_cnt)
    wr_ACT_TIME:begin
        Command <= C_ACT; //写激活行命令
        Sa <= raddr_r; 	  //同时发激活行地址
        Ba <= baddr_r;    //同时发送bank地址
    end
    wr_WRITE_TIME:begin
        Command <= C_WR;  //写数据命令
        Sa <= {1'b0,caddr_r[8:0]}; //同时发送列地址
        Ba <= baddr_r;    //同时发送bank地址
    end
        wr_PRE_TIME:begin
        Command <= C_PRE; //预充电
        Sa[10] <= 1'b1;   //同时发送 Sa[10] =1 信号
    end
    wr_END_TIME:begin     //进入结束时刻
        Command <= C_NOP; //发送空闲状态命令
        FF <= 1'b1;       //发送本次任务完成标志信号
    end
    default:
        Command <= C_NOP;
    endcase
end
endtask



//一次写操作过程完成标志位
always@(posedge Clk or negedge Rst_n)
begin
	if(!Rst_n)
    	wr_opt_done <= 1'b0;
    else if(wr_cnt == wr_END_TIME)
    	wr_opt_done <= 1'b1;
    else
    	wr_opt_done <= 1'b0;
end

//一次突发写操作过程状态标识信号
reg wr_opt;
always@(posedge Clk or negedge Rst_n)
begin
    if(!Rst_n)
    	wr_opt <= 1'b0;
    else if(
        
        == 1'b1)
    	wr_opt <= 1'b1;
    else if(wr_opt_done == 1'b1)
    	wr_opt <= 1'b0;
    else
    	wr_opt <= wr_opt;
end



//写数据操作,数据写入(改变)时刻有效区间
always@(posedge Clk or negedge Rst_n)
begin
if(!Rst_n)
	Wr_data_vaild <= 1'b0;
else if((wr_cnt > SC_RCD)&&(wr_cnt <= SC_RCD+SC_BL))
	Wr_data_vaild <= 1'b1;
else
	Wr_data_vaild <= 1'b0;
end

//一次突发写操作数据写完成标志位 ?? 这里是写数据成功,但是完整任务的还没执行完成 ??
assign Wdata_done = (wr_cnt == SC_RCD+SC_BL)?1'b1:1'b0;


//SDRAM 数据线,采用三态输出
//一次突发写操作数据写完成标志位(只有Wr_data_vaild ==1时刻 ,我们把要写的数据传输给Dq)
//注: assing 是组合逻辑电路
assign Dq = Wr_data_vaild ? Wr_data:16'bz;


三、读状态时序实现逻辑:

//一次突发读操作任务,线性序列机方法
localparam 	rd_ACT_TIME = 1'b1,
            rd_READ_TIME = SC_RCD+1,
            rd_PRE_TIME = SC_RCD+SC_CL+SC_BL+1,
            rd_END_TIME = SC_RCD+SC_CL+SC_BL;

//一次突发读操作过程时间计数器
always@(posedge Clk or negedge Rst_n)
begin
    if(!Rst_n)
    	rd_cnt <= 16'd0;
    else if(rd_cnt == rd_END_TIME)
    	rd_cnt <= 16'd0;
    else if(rd_req ||rd_cnt>1'b0)
    	rd_cnt <= rd_cnt + 16'd1;
    else
    	rd_cnt <= 16'd0;
end


//一次突发读操作任务,类似线性序列机方法
task read_data;
begin
    case(rd_cnt)
        rd_ACT_TIME:begin //激活命令
        Command <= C_ACT;
        Sa <= raddr_r;
        Ba <= baddr_r;
    end
        rd_READ_TIME:begin //读命令
        Command <= C_RD;
        Sa <= {1'b0,caddr_r[8:0]};
        Ba <= baddr_r;
    end
        rd_PRE_TIME:begin
        Command <= C_PRE; //预充电
        Sa[10] <= 1'b1;
    end
        rd_END_TIME:begin
        FF <= 1'b1;
        Command <= C_NOP;
    end
    default:
    	Command <= C_NOP;
    endcase
end
endtask


//一次突发读操作过程完成标志位
always@(posedge Clk or negedge Rst_n)
begin
if(!Rst_n)
	rd_opt_done <= 1'b0;
else if(rd_cnt == rd_END_TIME)
	rd_opt_done <= 1'b1;
else
	rd_opt_done <= 1'b0;
end

//一次突发读操作过程状态标识信号
reg rd_opt;
always@(posedge Clk or negedge Rst_n)
begin
if(!Rst_n)
	rd_opt <= 1'b0;
else if(rd_req == 1'b1)
	rd_opt <= 1'b1;
else if(rd_opt_done == 1'b1)
	rd_opt <= 1'b0;
else
	rd_opt <= rd_opt;
end


//一次突发读操作过程中数据读完标志位
assign Rdata_done = (rd_cnt == rd_END_TIME)?1'b1:1'b0;

//读数据操作,数据有效区
always@(posedge Clk or negedge Rst_n)
begin
if(!Rst_n)
    Rd_data_vaild <= 1'b0;
else if((rd_cnt > SC_RCD+SC_CL)&&(rd_cnt <= SC_RCD+SC_CL+SC_BL))
    Rd_data_vaild <= 1'b1;
else
	Rd_data_vaild <= 1'b0;
end

//读数据 ??
assign Rd_data = Dq;

三个状态设置完成以后,可以开始编写各个主状态之间的切换逻辑控制的设计了;

主:也就是设计主状态机的工作模式的逻辑

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KBf2DeHJ-1632748635725)(img/blog_img/fpga/image-20210926223140094.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GIrNQWTe-1632748635726)(img/blog_img/fpga/image-20210926223238379.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NiUdo39L-1632748635726)(img/blog_img/fpga/image-20210926223253415.png)]

注:这个优先级可以多理解一下,优先级越高的,if-else 就越要摆在越前面,我在看的时候还没仔细看到这个。

//主状态机状态
localparam IDLE = 4'b0001,
AREF = 4'b0010,
WRITE = 4'b0100,
READ = 4'b1000;


//主状态机
always@(posedge Clk or negedge Rst_n)
begin
	if(!Rst_n)begin
        main_state <= IDLE;
        FF <= 1'b1;
    end
else 
begin
case(main_state)
    IDLE:begin
        Command <= init_cmd;
        Sa <= init_addr;   // Sa 就是 Sd_addr[]
    if(init_done)
    	main_state <= AREF;
    else
    	main_state <= IDLE;
    end
    AREF:begin
    if(FF==1'b0)
    	auto_ref;
    else 
    begin
        if(ref_req)begin  // 有自动刷新请求
            main_state <= AREF; // 切换到自动刷新状态
            FF <= 1'b0;   // 同时启动(执行)自动刷新任务,任务完成以后 寄存器FF <= 1'b1;
        end
        else if(wr_req)begin
            main_state <= WRITE;
            FF <= 1'b0;
        end
        else if(rd_req)begin
            main_state <= READ;
            FF <= 1'b0;
        end
        else
            main_state <= AREF;
        end
    end
    WRITE:
    begin
        if(FF==1'b0)
            write_data;
        else begin
        if(ref_req == 1'b1)begin
            main_state <= AREF;
            FF <=1'b0;
        end
        else if(wr_opt_done & wr_req)begin
            main_state <= WRITE;
            FF <= 1'b0;
        end
        else if(wr_opt_done & rd_req)begin
            main_state <= READ;
            FF <=1'b0;
        end
        else if(wr_opt_done&!wr_req&!rd_req)
            main_state <= AREF;
        else
            main_state <= WRITE;
        end
    end
    READ:
    begin
        if(FF==1'b0)
            read_data;
        else begin
        if(ref_req == 1'b1)begin
            main_state <= AREF;
            FF <=1'b0;
        end
        else if(rd_opt_done & wr_req)begin
            main_state <= WRITE;
            FF <=1'b0;
        end
        else if(rd_opt_done & rd_req)begin
            main_state <= READ;
            FF <=1'b0;
        end
        else if( rd_opt_done&!wr_req&!rd_req)
            main_state <= AREF;
        else
            main_state <= READ;
        end
    end
    endcase
end
end



[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BwUr9qfi-1632748635727)(img/blog_img/fpga/image-20210926225658210.png)]

//SDRAM 命令信号组合
assign {Cs_n,Ras_n,Cas_n,We_n} = Command;
//时钟使能信号
assign Cke = Rst_n;

上面的大状态机已经设置完成了:接下来就是各个信号的产生以及信号之间的逻辑关系控制了

我们现在有的只有每个控制信号的变化导致状态的变化,但是哪些信号是由我们输入的信号来的得到的,所以,我们要建立输入信号和逻辑控制信号的关系,将他们之间给串联起来,完成整个的系统的一个输入输出的闭环控制。

第一个: 自动刷新定时器的【标志信号 ref_time_flag 来源】和产生的逻辑代码

//刷新定时计数器
always@(posedge Clk or negedge Rst_n)
begin
if(!Rst_n)
    ref_time_cnt <= 0;
else if(ref_time_cnt == AUTO_REF)
	ref_time_cnt <= 1;
else if(init_done || ref_time_cnt > 0)
	ref_time_cnt <= ref_time_cnt + 10'd1;
else
	ref_time_cnt <= ref_time_cnt;
end

//刷新定时时间标志位,定时到达时置 1
assign ref_time_flag = (ref_time_cnt == AUTO_REF);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m6jajdoo-1632748635728)(img/blog_img/fpga/image-20210926230033655.png)]

第二个: 地址线(行地址线、列地址线、块ram 的地址线)的寄存操作。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ylym5Wdo-1632748635729)(img/blog_img/fpga/image-20210926230323147.png)]

//读写行列地址寄存器
always@(posedge Clk or negedge Reset_n)
begin
if(!Reset_n)
begin
    raddr_r <= 0;
    caddr_r <= 0;
    baddr_r <= 0;
end
    else if(rd_req || wr_req) // 只有出现了读请求和写请求的时候,我们才对输入的地址进行寄存,其他时候不care
begin
    raddr_r <= Raddr;
    caddr_r <= Caddr;
    baddr_r <= Baddr;
end
else
	;
end

第三个:上电初始化模块的例化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w1uZH0K8-1632748635729)(img/blog_img/fpga/image-20210926230839816.png)]

//SDRAM 前期初始化模块例化
sdram_init sdram_init(
.Clk(Clk),
.Rst_n(Rst_n),
.Command(init_cmd),
.Saddr(init_addr),
.Init_done(init_done)
);

第四个: 自动刷新状态的请求信号的产生

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zlJyVBUv-1632748635730)(img/blog_img/fpga/image-20210926230950529.png)]

//写操作过程中出现了自动刷新请求
assign ref_break_wr = (ref_time_flag&&wr_opt)?1'b1:((!wr_opt)?1'b0:ref_break_wr);
//写操作过程中出现了自动刷新请求
assign ref_break_rd = (ref_time_flag&&rd_opt)?1'b1:((!rd_opt)?1'b0:ref_break_rd);

//刷新请求信号的逻辑控制
always@(*)
begin
    case(main_state)
    AREF:begin
        if(ref_time_flag)
        	ref_req = 1'b1;
        else
        	ref_req = 1'b0;
    end
    WRITE:begin
        if(ref_break_wr && wr_opt_done)
        	ref_req = 1'b1;
        else
        	ref_req = 1'b0;
    end
    READ:begin
        if(ref_break_rd && rd_opt_done)
        	ref_req = 1'b1;
        else
        	ref_req = 1'b0;
    end
    default:
    	ref_req = 1'b0;
endcase
end

第五个: 写数据操作请求信号的产生

//自动刷新过程中出现了写请求
assign wr_break_ref = ((Wr && ref_opt)?1'b1:((!ref_opt)?1'b0:wr_break_ref));

//写操作请求信号的产生
always@(*)
begin
    case(main_state)
    AREF:begin
        if((!wr_break_ref)&& Wr &&!ref_time_flag) //刷新过程中没出现写请求信号 且  Wr =1(写操作) 且  ref_time_flag=0(没有自动刷新标志信号) 
    	wr_req = 1'b1;
        else if(wr_break_ref && ref_opt_done) // 刷新过程中有写请求信号,且ref_opt_done =1(自动刷新任务完成)
    	wr_req = 1'b1;
    else
    	wr_req = 1'b0;
    end
    WRITE:begin
        if(wr_opt_done && Wr && !ref_break_wr)  //刷新过程中没出现写请求信号 且  Wr =1(写操作) 且  wr_opt_done写操作刚刚好完成
    	wr_req = 1'b1;
    else
    	wr_req = 1'b0;
    end
    READ:begin
        if(rd_opt_done && Wr && !ref_break_rd) // 刷新过程中没出现写请求信号 且  Wr =1(写操作) 且  rd_opt_done读操作刚刚好完成
    	wr_req = 1'b1;
    else
    	wr_req = 1'b0;
    end
    default:
    	wr_req = 1'b0;
    endcase
end

第六个: 写数据操作请求信号的产生

//自动刷新过程中出现了读请求
assign rd_break_ref = ((Rd && ref_opt)?1'b1:((!ref_opt)?1'b0:rd_break_ref));

// 注:因为读请求信号的优先级最低,所以判断(出现)读请求的条件(约束也就越多),明显能感受到写读请求更加的麻烦

//读操作请求信号
always@(*)
begin
case(main_state)
    AREF:begin
        if((!rd_break_ref)&&(!wr_break_ref)&&(!ref_time_flag)&& !Wr && Rd ) //刷新过程中没出现读请求信号 且  Wr =0(没有写操作) 且  ref_time_flag=0(没有自动刷新标志信号)  且 Rd =1(读操作)
    	rd_req = 1'b1;
        else if(ref_opt_done &&!wr_break_ref&&rd_break_ref) // 刷新过程中出现读请求信号 且 !wr_break_ref 刷新过程没有出现写信号 且 ref_opt_done=1 自动刷新完成标志信号
    	rd_req = 1'b1;
    else
    	rd_req = 1'b0;
    end
    WRITE:begin
        if(wr_opt_done &&(!ref_break_wr)&&!Wr && Rd) // 写数据刚刚完成 且 在写过程状态中没有出现自动刷新请求信号 且 出现了 Rd=1 读操作 
    	rd_req = 1'b1;
    else
    	rd_req = 1'b0;
    end
    READ:begin
        if(rd_opt_done &&(!ref_break_rd)&&!Wr && Rd) //读数据刚刚完成 且 在读过程状态中没有出现自动刷新请求信号 且 出现了 Rd=1 读操作
    	rd_req = 1'b1;
    else
    	rd_req = 1'b0;
    end
    default:
    	rd_req = 1'b0;
endcase
end


上述就完成了 sdram_control 控制器模块的设计 sdram_control.v,也可以理解为sdram的驱动文件。

​ 虽然有了一个大致的操作状态,但是我们在操作的时候还是不是很方便,因为读写操作手动控制会很麻烦,所以,我们可以考虑一种符合我们大脑的思想的操作方案,即:连续写和读数据的时候,最好和芯片内部的静态sram一样来实现,那就很方便了,对吧。

​ 所以,接下来的任务,就是基于这个控制模块,加上一些控制逻辑和内部的部分fifo资源,实现一个自动化连续数据的读和写的功能,这样就会很方便。大概的设计结构如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-feJLtyhy-1632748635731)(img/blog_img/fpga/image-20210927000053207.png)]


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rqDyoPEI-1632748635731)(img/blog_img/fpga/image-20210920003327030.png)]

接下来我们就对该结构进行分析:

首先明显能看到的是两个fifo模块,上面那个是fifo数据写入,下面的数fifo数据读取。

先分析数据写入,fifo我们认为数据一次是连续写入的,

module sdram_control_top(
Clk,
Rst_n,
Sd_clk,
Wr_data,
Wr_en,
Wr_addr,
Wr_max_addr,
Wr_load,
Wr_clk,
Wr_full,
Wr_use,
Rd_data,
Rd_en,
Rd_addr,
Rd_max_addr,
Rd_load,
Rd_clk,
Rd_empty,
Rd_use,
Sa,
Ba,
Cs_n,
Cke,
Ras_n,
Cas_n,
We_n,
Dq,
Dqm
);
`include "../src/Sdram_Params.h"
input Clk; //系统时钟
input Rst_n; //复位信号,低电平有效
input Sd_clk; //SDRAM 时钟信号

    input [`DSIZE-1:0] Wr_data; //待写入数据
input Wr_en; //写数据使能信号
input [23:0] Wr_addr; //写数据起始地址
input [23:0] Wr_max_addr; //写数据最大地址(SC_BL 的整数倍)
input Wr_load; //写 FIFO 清零信号
input Wr_clk; //写 FIFO 数据时钟信号
output Wr_full; //写 FIFO 数据满信号
output[7:0] Wr_use; //写 FIFO 数据可用数据量
output[`DSIZE-1:0] Rd_data; //读出的数据
input Rd_en; //读数据使能信号
input [23:0] Rd_addr; //读数据起始地址
input [23:0] Rd_max_addr; //读数据最大地址(SC_BL 的整数倍)
input Rd_load; //读 FIFO 清零信号
input Rd_clk; //读 FIFO 数据时钟信号
output Rd_empty; //读 FIFO 数据空信号
output[7:0] Rd_use; //读 FIFO 数据可用数据量
output[`ASIZE-1:0] Sa; //SDRAM 地址总线
output[`BSIZE-1:0] Ba; //SDRAMBank 地址
output Cs_n; //SDRAM 片选信号
output Cke; //SDRAM 时钟使能
output Ras_n; //SDRAM 行地址选
output Cas_n; //SDRAM 列地址选
output We_n; //SDRAM 写使能
inout [`DSIZE-1:0] Dq; //SDRAM 数据总线
output[`DSIZE/8-1:0]Dqm; //SDRAM 数据掩码
reg sd_wr; //写 SDRAM 使能信号
reg sd_rd; //读 SDRAM 使能信号
reg [`ASIZE-1:0] sd_caddr; //写 SDRAM 时列地址
reg [`ASIZE-1:0] sd_raddr; //写 SDRAM 时行地址
reg [`BSIZE-1:0] sd_baddr; //写 SDRAM 时 Bank 地址
wire [`DSIZE-1:0] sd_wr_data; //待写入 SDRAM 数据
wire [`DSIZE-1:0] sd_rd_data; //读出 SDRAM 的数据
wire sd_rdata_vaild; //读 SDRAM 时数据有效区
wire sd_wdata_vaild; //写 SDRAM 时数据有效区
wire sd_wdata_done; //一次写突发完成标识位
wire sd_rdata_done; //一次读突发完成标识位
wire [7:0] fifo_rduse; //写 FIFO 模块可读数据量
wire [7:0] fifo_wruse; //读 FIFO 模块已写数据量
    
    
reg [23:0] wr_sdram_addr; //写 SDRAM 的地址
reg [23:0] rd_sdram_addr; //读 SDRAM 的地址
wire sd_wr_req; //请求写数据到 SDRAM
wire sd_rd_req; //请求向 SDRAM 读数据
//SDRAM 控制器模块例化
sdram_control sdram_control(
.Clk(Clk),
.Rst_n(Rst_n),
.Wr(sd_wr),
.Rd(sd_rd),
.Caddr(sd_caddr),
.Raddr(sd_raddr),
.Baddr(sd_baddr),
.Wr_data(sd_wr_data),
.Rd_data(sd_rd_data),
.Rd_data_vaild(sd_rdata_vaild),
.Wr_data_vaild(sd_wdata_vaild),
.Wdata_done(sd_wdata_done),
.Rdata_done(sd_rdata_done),
.Sa(Sa),
.Ba(Ba),
.Cs_n(Cs_n),
.Cke(Cke),
.Ras_n(Ras_n),
.Cas_n(Cas_n),
.We_n(We_n),
.Dq(Dq),
.Dqm(Dqm)
);
//写 FIFO 模块例化
fifo_wr sd_wr_fifo(
.aclr(Wr_load),
.data(Wr_data),
.rdclk(Clk),
.rdreq(sd_wdata_vaild),
.wrclk(Wr_clk),
.wrreq(Wr_en),
.q(sd_wr_data),
.rdempty(),
.rdusedw(fifo_rduse),
.wrfull(Wr_full),
.wrusedw(Wr_use)
);
    
//读 FIFO 模块例化
fifo_rd sd_rd_fifo(
.aclr(Rd_load),
.data(sd_rd_data),
.rdclk(Rd_clk),
.rdreq(Rd_en),
.wrclk(Sd_clk),
.wrreq(sd_rdata_vaild),
.q(Rd_data),
.rdempty(Rd_empty),
.rdusedw(Rd_use),
.wrfull(),
.wrusedw(fifo_wruse)
);
    
//写 SDRAM 数据的地址,数据写完一次增加一次突发长度
always@(posedge Clk or negedge Rst_n)
begin
if(!Rst_n)
	wr_sdram_addr <= Wr_addr;   //数据地址
 else if(Wr_load == 1’b1)    //写数据请求
	wr_sdram_addr <= Wr_addr;   //输入的 写数据地址赋值给 写地址寄存器
 else if(sd_wdata_done)begin //本次突发数据已经写入完成
    if(wr_sdram_addr == Wr_max_addr-SC_BL)  //判断地址是否用全部写完
		wr_sdram_addr <= Wr_addr;    // 重头开始写
	else
		wr_sdram_addr <= wr_sdram_addr + SC_BL; //没有全部写完,地址加上写入突发数据的长度SC_BL
end
else
	wr_sdram_addr <= wr_sdram_addr;
end
    
//读 SDRAM 数据的地址,数据读完一次增加一次突发长度
always@(posedge Clk or negedge Rst_n)
begin
if(!Rst_n)
	rd_sdram_addr <= Rd_addr;
else if(Rd_load == 1'b1)   // 出现读请求
	rd_sdram_addr <= Rd_addr; //读请求的输入地址 写入到读寄存器地址
    else if(sd_rdata_done) //读数据已经完成
begin
    if(rd_sdram_addr == Rd_max_addr-SC_BL) //已经读到了最大地址值
		rd_sdram_addr <= Rd_addr; //地址重新开始
	else // 没读取完所有的数据,地址+SC_BL 下次继续读
		rd_sdram_addr <= rd_sdram_addr + SC_BL;
end
else
	rd_sdram_addr <= rd_sdram_addr;
end
    
//写 SDRAM 请求信号  ,写fifo 中剩余的代写入sdram的数据大于一次突发长度时候,将产生写sdram 请求信号
assign sd_wr_req = (fifo_rduse >= SC_BL)?1'b1:1'b0;
//读 SDRAM 请求信号 , 读请求信号,当 没有再次出现读请求信号时,且fifo待写入的数据为0时,表示可以开始进行读请求
assign sd_rd_req = (!Rd_load)&&(fifo_wruse[7]==1'b0)?1'b1:1'b0;
    
//写 SDRAM 使能信号
always@(posedge Clk or negedge Rst_n)
begin
if(!Rst_n)
	sd_wr <= 1'b0;
else if(sd_wr_req) // 只要写入的fifo模块中,有些fifo的数据,那么就开始有写请求信号
	sd_wr <= 1'b1; // 开启写使能
else  // 全部写完了,那么就不产生写数据信号
	sd_wr <= 1'b0; // 没有数据,不开启写使能
end
    
//读 SDRAM 使能信号
always@(posedge Clk or negedge Rst_n)
begin
if(!Rst_n)
	sd_rd <= 1'b0;
    else if(sd_rd_req) 
	sd_rd <= 1'b1;
else
	sd_rd <= 1'b0;
end
    
//SDRAM 的列地址
always@(*)
begin    
if(!Rst_n)
	sd_caddr <= 9'd0;
else if(sd_wr_req)
	sd_caddr <= wr_sdram_addr[8:0];
else if(sd_rd_req)
	sd_caddr <= rd_sdram_addr[8:0];
else
	sd_caddr <= sd_caddr;
end
    
//SDRAM 的行地址
always@(*)
begin
if(!Rst_n)
	sd_raddr <= 13'd0;
else if(sd_wr_req)
	sd_raddr <= wr_sdram_addr[21:9];
else if(sd_rd_req)
	sd_raddr <= rd_sdram_addr[21:9];
else
	sd_raddr <= sd_raddr;
end
    
//SDRAM 的 bank 地址
always@(*)
begin
if(!Rst_n)
	sd_baddr <= 2'd0;
else if(sd_wr_req)
	sd_baddr <= wr_sdram_addr[23:22];
else if(sd_rd_req)
	sd_baddr <= rd_sdram_addr[23:22];
else
	sd_baddr <= sd_baddr;
end
    
endmodule
    
  • 7
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值