1.SDRAM简介
简介:SDRAM为同步动态随机存储内存,同步指的是时钟与外部输入的时钟保持一致,也就是与外部共用一个时钟;动态指的是每个时间段内,都要进行一次刷新操作,否则里面的数据会丢失,这也是由存储特性决定的;随机指的是数据存储地址可由我们自己任意指定。
bank与地址: 在SDRAM中,有多个bank,每个bank都有自己的行列地址,相互独立,在读写的时候,我们需要指定那个bank的那行,那列进行读写,其中行列地址线是共用的,下面会具体的说明
命令:我认为SDRAM是响应式的,对任何SDRAM的操作都要给出对应的命令,否则是无法得到想要的结果的。
数据: SDRAM输入输出的数据线是共用的
本文章使用的SDRAM是黑金AX4010上的
2. SDRAM的操作流程
上电延时,然后初始化
进入仲裁状态机,优先执行刷新请求,读写请求的优先级可自行决定
ps:是不是感觉挺简单的٩(๑>◡<๑)۶
3. SDRAM 初始化
找到说明文档,查阅初始化时序图
CKE为SDRAM使能线,在整个运行过程中,一直为高即可
COMMAND,command是由4根线共同组成的,分别是:CS,RAS,CAS,WE,在手册的可以方便的找到不同的命令,其4根线上高低电平分别是怎样的
DQM为掩码,设置dqm的值,在读写sdram时,可以只对数据的高八位或者低八位或者十六位都感兴趣,一般一直设为00,即十六位都感兴趣
A 为行列地址线,row地址时,A全部有效,col地址时,其第九位有效
BA 为bank,指定对那个bank进行操作
DQ: 为sdram的数据线
从时序图可以看到,上电后,我们需要延时最少100us,然后给个NOP命令,一般为了稳定,都会延时200us;在NOP命令后,然后再给PRECHARGE(预充电)命令,此时需要指定bank,如果A10为高的话,就是对所有的bank,如果A10为低的话,就按照BA指定的来,一般设置A10为高,后面的一些延时和对应的命令按照时序图来即可
直接来到LOAD Mode Register这个命令,此时需要指定A的值,不同的A值,在后面对SDMRA的操作会不同,阅读手册可知
A0,A1,A2:控制突发长度,给一个读写命令,我可以连续读写多少个数据,我设置的是000,突发长度为1,我认为这样便于书写程序
A3: 读写是连续的,还是随机的,仅对突发长度不为1时有效;读写的需要给定地址,也就是说写入的第一个数据的地址是以给定的,如果突发长度不为1的话,后面的数据的地址是在给定地址的基础上依次加一,还是说是随机的。
A4,A5,A6:仅对读数据有效,给了读命令后,在接下来的几个周期内,数据才会出来,俗称潜伏期
其他位就不作介绍了,都为0即可,
至此SDRAM的初始化就完成了
4. SDRAM的写
老规矩,先查阅手册,找到时序图,可惜没有我想要的完整的,那分开吧,
激活命令,激活对应的row地址和bank
延时两个周期
写命令,指定对应的col行地址和数据(A10最好为0,不然给一个命令后,又得给激活命令,重复1,2步骤)
如果想继续写,写数据达到突发长度后,然后继续再给一个写命令和新的col地址
如果不想写了,写数据达到突发长度后,给一个PRECHARGE命令,然后就写完成了。
ps : 此步骤只是写SDRAM的一种,我认为是最简单,也是比较高效的一种
可以参考下面的写时序,与我说的,只是A10给定的值不同,可以看出,如果A10给1了的话,它会自动进行一个PRECHARGE操作,而为0的话,则需要我们自己给定,以结束这次的写操作。
5. SDRAM读和刷新
SDRAM的读与SDRAM的写基本上一样,同样刷新操作更简单,大家可以按照读时序图自己琢磨,下面给出对应的时序图
6. 注意事项
1. 读写的时候,A10的值是非常值得关注的。 2. 读时,何时接收数据;写时,何时获取数据。 3. 仲裁模块的书写,确保读写数据后,没有超过刷新周期,这里可以用读写速度换取稳定性
7. 异步FIFO
最后来介绍一下异步FIFO的实现,我们都知道FIFO是一种先进先出的一种结构,可以说是很简单的,但是设计起来还是有点难度的,难点如下
1. 由于使用的是同一块内存,如何做到读与写如何不冲突 2. 如何判断是否写满了,以及是否为空 3. 如何计算FIFO里面读写的数据量
接下来,我还是结合代码来说明吧
1. 读写
curr表示当前读写的地址,next表示下一次的读写地址只需要合理地对next地址进行更新即可做到合理性
reg[8:0] curr_read_ptr,next_read_ptr; //读指针 reg[8:0] curr_write_ptr,next_write_ptr; //写指针
对读地址进行更新,可以看到,只要不为空的时候,我们就可以一直更新next读地址的值,同样,只要没有写满的时候,就可以一直更新next写地址的值。
always@(posedge rclk or negedge rst) begin if(rst == 1'b0) next_read_ptr <= 'd1; else if(r_en == 1'b1) if(r_empty == 1'b0) if(next_read_ptr == 'd511) next_read_ptr <= 'd0; else next_read_ptr <= next_read_ptr + 1'b1; else next_read_ptr <= next_read_ptr; else next_read_ptr <= next_read_ptr; end
2. 判断是否为空和满
满和空其实很好判断,只要当下一个写的地址等于当前读的地址,即可判断为满,
空也是如此,当下一个读的地址等于当前写的地址,即为空,还要考虑初始情况,next_read等于next_write的时候也是为空的
assign w_full = (next_write_ptr == curr_read_ptr ) ? 1'b1 : 1'b0; assign r_empty = ( next_read_ptr == curr_write_ptr || (next_read_ptr == next_write_ptr)) ? 1'b1 : 1'b0;
3. 计算FIFO里面的数据量
FIFO里面的数据量其实读写地址的差值,但是当写的地址在读的地址后面的时候,又不仅仅是差值了,而是数据总长度减去这个差值了。
always@(posedge rclk or negedge rst) begin if(rst == 1'b0) data_count <= 'd0; else if(curr_read_ptr >= curr_write_ptr && next_read_ptr != next_write_ptr) data_count <= 'd511 - curr_read_ptr + curr_write_ptr; else data_count <= curr_write_ptr - curr_read_ptr; end
最后给出我自己实现的SDRAM_FIFO控制器,其中包括sdram_pluse仿真模块,以及testbench;FIFO没有调用IP核,利用SDRAM和FIFO将sdram封装成一个可以方便实用的异步sdram_fifo.
欢迎大家下载学习,写的很通俗易懂。下载地址.
公众号:FPGA之旅