SDRAM的数据存储实现并对其数据进行读写操作
SDRAM的数据存储实现并对其数据进行读写操作
文章目录
- SDRAM的数据存储实现并对其数据进行读写操作
- 1 SDRAM
- 1.1 简介
- 1.2 SDRAM结构和原理
- 1.3 SDRAM块
- 2 SDRAM的操作
- 2.1 SDRAM的芯片初始化
- 2.2 SDRAM行激活
- 2.3 SDRAM列读写
- 2.4 数据输入输出
- 2.5 突发长度
- 2.6 预充电
- 2.7 刷新
- 3 基于SDRAM的数据读写实现
- 3.1 主要实现过程
- 3.2 运行结果
1 SDRAM
1.1 简介
SDRAM(Synchronous Dynamic Random Access Memory),即同步动态随机访问存储器。同步是指内存工作需要同步时钟,内部命令的发送和数据的传输都以它为基准;而动态是指存储阵列需要不断地刷新来保证数据不丢失;随机指数据不是依次存储的,而随机的指定地址进行数据的读写操作。SDARM的读写是一个比较复杂的流程,其中包括行激活、列读写、预充电、刷新等一系列操作。
1.2 SDRAM结构和原理
1、结构:
SDRAM的内部是一个存储阵列,我们可以将其想象成一张表格
2、原理:当向表格中写入数据时,需要先指定行(Row),再指定列(Column),就可以准确地找到所需要的“单元格”。SDRAM存储数据是利用了电容的充放电特性以及能够保持电荷的能力。一个大小为1bit的存储单元的结构如下图所示:
它主要由行列选通三极管,存储电容,刷新放大器组成。行地址与列地址选通使得存储电容与数据线导通,从而可进行放电(读取)与充电(写入)操作。
3、特点:SDRAM具有空间存储量大、读写速度快、价格相对便宜等优点。然而由于SDRAM内部利用电容来存储数据,为保证数据不丢失,需要持续对各存储电容进行刷新操作;同时在读写过程中需要考虑行列管理、各种操作延时等,由此导致了其控制逻辑复杂的特点。
1.3 SDRAM块
1、“单元格”就是SDRAM存储芯片中的存储单元,而这个“表格”(存储阵列)我们称之为L-Bank。通常SDRAM的存储空间被划分为4个L-Bank,在寻址时需要先指定其中一个L-Bank,然后在这个选定的L-Bank中选择相应的行与列进行寻址(寻址就是指定存储单元地址的过程)。
2、容量计算:
对SDRAM的读写是针对存储单元进行的,对SDRAM来说一个存储单元的容量等于数据总线的位宽,单位是bit。那么SDRAM芯片的总存储容量我们就可以通过下面的公式计算出来:
SDRAM总存储容量 = L-Bank的数量×行数×列数×存储单元的容量(数据线的位宽)
2 SDRAM的操作
2.1 SDRAM的芯片初始化
SDRAM芯片上电之后需要一个初始化的过程,以保证芯片能够按照预期方式正常工作,初始化流程如图
SDRAM上电后要有200us的输入稳定期,在这个时间内不可以对SDRAM的接口做任何操作; 200us结束以后给所有L-Bank预充电,然后是连续8次刷新操作;最后设置模式寄存器。初始化最关键的阶段就在于模式寄存器(MR,Mode Register)的设置,简MRS(MR Set),模式寄存器设置主要用来确定芯片的工作方式,包括突发长度(Burst Length)、潜伏期(CAS Latency)以及操作模式等。
2.2 SDRAM行激活
初始化完成后,无论是读操作还是写操作,都要先激活(Active)SDRAM中的一行,使之处于活动状态(又称行有效)。在此之前还要进行SDRAM芯片的片选和L-Bank的定址,不过它们与行激活可以同时进行。
从上图可以看出,在片选CS#(#表示低电平有效)、L-Bank定址的同时,RAS(Row Address Strobe,行地址选通脉 冲)也处于有效状态。此时An地址线则发送具体的行地址。如图中是A0- A11,共有12个地址线,由于是二进制表示法, 所以共有4096个行(2^12=4096),A0-A11的不同数值就确定了具体的行地址。由于行激活的同时也是相应L-Bank有效,所以行激活也可称为L-Bank有效。
2.3 SDRAM列读写
行地址激活之后,就要对列地址进行寻址了。由于在SDRAM中,地址线是行列共用的,因此列寻址时地址线仍然是A0-A11。在寻址时,利用RAS(Row Address Strobe,行地址选通脉冲)与CAS(Column Address Strobe,列地址选通脉冲)来区分行寻址与列寻址,如图所示。
图中“x16”表示存储单元容量为16bit。一般来说,在SDRAM中存储阵列(L-Bank) 的列数小于行数,即列地址位宽小于行地址,因此在列地址选通时地址线高位可能未用到。
2.4 数据输入输出
1、数据输出(即读操作):在选定列地址后,就已经确定了具体的存储单元,剩下的事情就是数据通过数据I/O通道(DQ)输出到内存总线上了。但是在CAS发出之后,仍要经过一定的时间才能有数据输出,从CAS与读取命令发出到第一 笔数据输出的这段时间,被定义为CL(CAS Latency,CAS潜伏期)。CL时间越短,读数据时SDRAM响应就越快。由于CL只在读取时出现,所以CL又被称为读取潜伏期(RL,Read Latency)。CL的单位与tRCD一样,为时钟周期数,具体耗时由时钟频率决定。
2、数据输入(即写操作):数据写入的操作也是在tRCD之后进行,但此时没有了CL(记住,CL只出现在读取操作中),数据与写指令同时发送。不过,数据并不是即时地写入存储单元,数据的真正写入需要一定的周期。为了保证数据的可靠写入,都会留出足够的写入/校正时间(tWR, Write Recovery Time),这个操作也被称作写回(Write Back)。tWR至少占用一个时钟周期或再多一点(时钟频率越高,tWR占用周期越多)
2.5 突发长度
1、突发长度:
突发(Burst)是指在同一行中相邻的存储单元连续进行数据传输的方式,连续传输所涉及到存储单元(列)的数量就是突发长度(Burst Lengths,简称BL)。
2、读/写操作,都是一次对一个存储单元进行寻址。然而在现实中很少只对SDRAM 中的单个存储空间进行读写,一般都需要完成连续存储空间中的数据传输。在连续读/写操作时,为了对当前存储单元的下一个单元进行寻址,需要不断的发送列地址与读/写命令(行地址不变,所以不用再对行寻址)。
2.6 预充电
1、预充电:在对SDRAM某一存储地址进行读写操作结束后,如果要对同一L-Bank的另一行进行寻址, 就要将原来有效(工作)的行关闭,重新发送行/列地址。L-Bank关闭现有工作行,准备打开新行的操作就是预充电(Precharge)。
在读写过程中,工作行内的存储体由于“行激活”而使存储电容受到干扰,因此在关闭工作行前需要对本行所有存储体进行重写。预充电实际上就是对工作行中所有存储体进行数据重写,并对行地址进行复位,以准备新行工作的过程。 预充电可以通过命令控制,也可以通过辅助设定让芯片在每次读写操作之后自动进行预充电。现在我们再回过头看看读写操作时的命令时序图,从中可以发现地址线A10 控制着是否进行在读写之后对当前L-Bank自动进行预充电,这就是上文所说的“辅助设定”。而在单独的预充电命令中,A10则控制着是对指定的L-Bank还是所有的L-Bank(当有多个L-Bank处于有效/活动状态时)进行预充电,前者需要提供L-Bank的地址,后者只需将A10信号置于高电平。在发出预充电命令之后,要经过一段时间才能发送行激活命令打开新的工作行,这个间隔被称为tRP(Precharge command Period,预充电有效周期),和tRCD、CL一样,tRP的单位也是时钟周期数,具体值视时钟频率而定。读取时预充电时序图(CL=2、BL=4、tRP=2)写操作时,由于每笔数据的真正写入则需要一个足够的周期来保证,这段时间就是写回周期(tWR)。所以预充电不能与写操作同时进行,必须要在tWR之后才能发出预充电命令,以确保数据的可靠写入,否则重写的数据可能出错 。
2.7 刷新
1、SDRAM之所以称为同步“动态”随机存储器,就是因为它要不断进行刷新(Refresh)才能保留住数据,因此刷新是SDRAM最重要的操作。刷新操作与预充电类似,都是重写存储体中的数据。但为什么有预充电操作还要进行刷新呢?因为预充电是对一个或所有L-Bank中的工作行(处于激活状态的行)操作,并且是不定期的;而刷新则是有固定的周期,并依次对所有行进行操作,以保留那些久久没经历重写的存储体中的数据。但与所有L-Bank预充电不同的是,这里的行是指所有L-Bank中地址相同的 行,而预充电中各L-Bank中的工作行地址并不是一定是相同的。
2、目前公认的标准是,存储体中电容的数据有效保存期上限是64ms,也就是说每一行刷新的循环周期是64ms。我们在看SDRAM芯片参数时,经常会看到4096 Refresh Cycles/64ms或8192 Refresh Cycles/64ms的标识,这里的4096与8192就代表这个芯片中每个L-Bank的行数。刷新命令一次仅对一行有效,也就是说在64ms内这两种规格的芯片分别需要完成4096次和8192次刷新操作。因此,L-Bank为4096行时刷新命令的发送间隔为 15.625μs(64ms/4096),8192行时为7.8125μs(64ms/8192)。
3 基于SDRAM的数据读写实现
3.1 主要实现过程
1、思想:
PC机通过串口向SDRAM芯片突发写入数据,通过按键发起读请求,将写入的数据突发读出,并再次通过串口回传到PC端。SDRAM上电之后进入等待状态等待200us,200us之后对所有L-Bank进行预充电处理,预充电后进行自动刷新(一般持续8个周期),然后进行模式寄存器的初始化设置,主要设置SDRAM的突发长度、潜伏期以及操作模式等,至此sdram的初始化就完成了。sdram进入空闲状态,当有读写需求时,sdram先进行行激活操作,然后根据命令进行列读写,当突发读写完成后再次进行预充电,然后回到空闲状态等待,此时若需要自动刷新,则发送一个自动刷新命令并进入自动刷新状态,刷新完成后再回到空闲状态。状态图如下:
2、由于SDRAM的各种状态操作比较复杂,而Platform Designer提供的SDRAM IP 核接口极大的分辨率我们对于SDRAM的使用,因此实现时我们需要调用IP核来完成这些复杂的操作,我们只需要在sdram的控制模块完成突发读写操作即可。
3、sdram控制模块代码
module sdram_ctrl (input clk , //sdram时钟100MHzinput clk_in , //fifo的数据输入时钟50MHzinput clk_out , //fifo的数据输出时钟50MHz input rst_n , //复位信号 input [7:0] din , //写入数据,先存到写fifo中 input din_vld , //input rd_req , //读请求 input ready , //tx发送的握手信号output [7:0] dout , //读出的数据,由读fifo获取output dout_vld , //output [23:0] avm_address , //sdram地址2位bank地址+13位行地址+9位列地址 output [15:0] avm_writedata , //写数据,写fifo的输出output avm_read , //读使能 output avm_write , //写使能input [15:0] avs_readdata , //读数据,存到读fifo中 input avs_readdatavalid , input avs_waitrequest // );//状态机参数定义parameter IDLE = 3'b001 , //空闲状态WRITE = 3'b010 , //写数据状态READ = 3'b100 ; //读数据状态//信号定义reg [2:0] cstate ;reg [2:0] nstate ;reg [23:0] cnt_wraddr ; //写地址计数wire add_cnt_wraddr ;wire end_cnt_wraddr ;reg [23:0] cnt_rdaddr ; //读地址计数wire add_cnt_rdaddr ;wire end_cnt_rdaddr ;reg [7:0] cnt_burst ; //读写状态下突发长度计数wire add_cnt_burst ;wire end_cnt_burst ;reg wr_flag ; //写标志reg rd_flag ; //读标志reg last_flag ; //上一次执行的操作reg prior_flag ; //读写优先级判断,高读低写reg [7:0] dout_r ;reg dout_r_vld ;wire [7:0] wrfifo_data ;wire wrfifo_rdreq ;wire wrfifo_wrreq ;wire [15:0] wrfifo_q ;wire wrfifo_empty ;wire [6:0] wrfifo_usedw ;wire wrfifo_full ;wire [15:0] rdfifo_data ;wire rdfifo_rdreq ;wire rdfifo_wrreq ;wire [7:0] rdfifo_q ;wire rdfifo_empty ;wire rdfifo_full ;wire idle_to_write ;wire idle_to_read ;wire write_to_idle ;wire read_to_idle ; //状态机转移always @(posedge clk or negedge rst_n) beginif (!rst_n)cstate <= IDLE;else cstate <= nstate;endalways @(*) begincase (cstate)IDLE:beginif (idle_to_write)nstate = WRITE;else if (idle_to_read)nstate = READ;elsenstate = cstate;endWRITE:beginif (write_to_idle)nstate = IDLE;elsenstate = cstate;endREAD:beginif (read_to_idle)nstate = IDLE;elsenstate = cstate;enddefault: ;endcaseend//状态机转移条件判断assign idle_to_write = cstate == IDLE && (~prior_flag && wrfifo_usedw > `BURST_LEN) ;assign idle_to_read = cstate == IDLE && (rd_flag && prior_flag) ;assign write_to_idle = cstate == WRITE && end_cnt_burst ;assign read_to_idle = cstate == READ && end_cnt_burst ;/************************ 读写优先级仲裁 *************************///wr_flagalways @(posedge clk or negedge rst_n) beginif (!rst_n)wr_flag <= 0;else if (wrfifo_usedw > `BURST_LEN)wr_flag <= 1'b1;elsewr_flag <= 1'b0;end//rd_flagalways @(posedge clk or negedge rst_n) beginif (!rst_n)rd_flag <= 0;else if (~rd_flag && rd_req)rd_flag <= 1'b1;else if (rd_flag & idle_to_read)rd_flag <= 1'b0;end//last_flagalways @(posedge clk or negedge rst_n) beginif (!rst_n)last_flag <= 0;else if (write_to_idle)last_flag <= 1'b0;else if (read_to_idle)last_flag <= 1'b1;end//prior_flagalways @(posedge clk or negedge rst_n) beginif (!rst_n)prior_flag <= 0;else if (wr_flag && (last_flag || ~rd_flag))prior_flag <= 1'b0;else if (rd_flag && (~last_flag || ~wr_flag))prior_flag <= 1'b1;end/****************************************************************///突发计数always @(posedge clk or negedge rst_n) beginif (!rst_n)cnt_burst <= 0;else if (add_cnt_burst)beginif (end_cnt_burst) cnt_burst <= 0;elsecnt_burst <= cnt_burst + 1'b1;endendassign add_cnt_burst = (cstate == WRITE || cstate == READ) && ~avs_waitrequest;assign end_cnt_burst = add_cnt_burst && cnt_burst == `BURST_LEN - 1;//写地址计数always @(posedge clk or negedge rst_n) beginif (!rst_n)cnt_wraddr <= 0;else if (add_cnt_wraddr)beginif (end_cnt_wraddr) cnt_wraddr <= 0;elsecnt_wraddr <= cnt_wraddr + 1'b1;endendassign add_cnt_wraddr = cstate == WRITE && ~avs_waitrequest;assign end_cnt_wraddr = add_cnt_wraddr && cnt_wraddr == `BURST_MAX - 1;//读地址计数always @(posedge clk or negedge rst_n) beginif (!rst_n)cnt_rdaddr <= 0;else if (add_cnt_rdaddr)beginif (end_cnt_rdaddr) cnt_rdaddr <= 0;elsecnt_rdaddr <= cnt_rdaddr + 1'b1;endendassign add_cnt_rdaddr = cstate == READ && ~avs_waitrequest;assign end_cnt_rdaddr = add_cnt_rdaddr && cnt_rdaddr == `BURST_MAX - 1;//dout_ralways @(posedge clk_out or negedge rst_n) beginif (!rst_n)begindout_r <= 0;dout_r_vld = 0;endbegindout_r <= rdfifo_q;dout_r_vld <= rdfifo_rdreq;endend//例化写fifowrfifo wrfifo_inst (.aclr (~rst_n ),.data (wrfifo_data ),.rdclk (clk ),.rdreq (wrfifo_rdreq ),.wrclk (clk_in ),.wrreq (wrfifo_wrreq ),.q (wrfifo_q ),.rdempty (wrfifo_empty ),.rdusedw (wrfifo_usedw ),.wrfull (wrfifo_full ));assign wrfifo_data = din;assign wrfifo_wrreq = ~wrfifo_full & din_vld ;assign wrfifo_rdreq = ~wrfifo_empty & add_cnt_wraddr ;//例化读fifordfifo rdfifo_inst (.aclr (~rst_n ),.data (rdfifo_data ),.rdclk (clk_out ),.rdreq (rdfifo_rdreq ),.wrclk (clk ),.wrreq (rdfifo_wrreq ),.q (rdfifo_q ),.rdempty (rdfifo_empty ),.wrfull (rdfifo_full ));assign rdfifo_data = avs_readdata;assign rdfifo_wrreq = ~rdfifo_full & avs_readdatavalid ;assign rdfifo_rdreq = ~rdfifo_empty & ~ready ;//输出assign dout = dout_r ;assign dout_vld = dout_r_vld ;assign avm_writedata = wrfifo_q ;assign avm_read = ~(cstate == READ) ; //低电平有效assign avm_write = ~(cstate == WRITE) ;assign avm_address = cstate == WRITE ? {cnt_wraddr[23],cnt_wraddr[21:9],cnt_wraddr[22],cnt_wraddr[8:0]}:{cnt_rdaddr[23],cnt_rdaddr[21:9],cnt_rdaddr[22],cnt_rdaddr[8:0]};endmodule //sdram_ctrl
3.2 运行结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uPgASmaP-1659576614940)(result.png)]
assign avm_write = ~(cstate == WRITE) ;
assign avm_address = cstate == WRITE ? {cnt_wraddr[23],cnt_wraddr[21:9],cnt_wraddr[22],cnt_wraddr[8:0]}:{cnt_rdaddr[23],cnt_rdaddr[21:9],cnt_rdaddr[22],cnt_rdaddr[8:0]};
endmodule //sdram_ctrl
#### 3.2 运行结果
![在这里插入图片描述](https://img-blog.csdnimg.cn/390bc8ff345a40a6a7373c1ac5ed957d.png) 还有点问题,第一次接收时总是会先接收三个占空符,目前还没有找到问题在哪里。
SDRAM的数据存储实现并对其数据进行读写操作相关推荐
- 服务端指南 数据存储篇 | 选择合适的数据存储方案
在服务端会经常遇到数据存储的选型问题,是选择使用关系型数据库 MySQL,还是选择内存数据库 Redis,还是选择文档数据库 MongoDB,还是选择列族数据库 HBase, 还是选择全文搜索引擎 E ...
- mysql更改数据库数据存储目录_MySQL更改数据库数据存储目录
MySQL数据库默认的数据库文件位于/var/lib/mysql下,有时候由于存储规划等原因,需要更改MySQL数据库的数据存储目录.下文总结整理了实践过程的操作步骤. 1:确认MySQL数据库存储目 ...
- < Android数据存储> 任务二 应用程序数据文件夹里的文件读写
:zh]Android中提供了两个方法用来打开应用程序的数据文件夹IO流. 1.FileInputStream openFileInput(String name):参数name表示某个文件名,该方法 ...
- mysql 轨迹数据存储_python爬虫26 | 把数据爬取下来之后就存储到你的MySQL数据库...
小帅b说过 在这几篇中会着重说说将爬取下来的数据进行存储 上次我们说了一种 csv 的存储方式 这次主要来说说怎么将爬取下来的数据保存到 MySQL 数据库 接下来就是 学习python的正确姿势 真 ...
- 租户隔离怎么做MYSQL_基于JPA实现SaaS多租户模式的数据存储——共享数据库,隔离数据架构...
SaaS是Software-as-a-Service(软件即服务)的简称,这边具体的解释不介绍.多租户的系统可以应用这种模式的思想,将思想融入到系统的设计之中. 现在SaaS Multi-Tenant ...
- 计算机数据存储怎么转换,数据存储单位之间怎么换算 数据储存单位之间换算方法【图文】...
关于数据存储,你了解它是怎么一回事吗?所谓的数据存储就是指需要数据流找到的信息,这些信息多半是加工过程中需要的.或者是一些临时文件,而这些文件也是在加工过程中出现的.以不同的格式将数据记录在电脑的内存 ...
- 海量视频监控数据存储和管理是大数据最重要的命题
海量视频数据的存储和管理 维基百科全书的定义:"大数据是飞速增长的,用现有数据库管理工具难以管理的数据集合".这些数据包括:社交媒体.移动设备.科学计算和城市中部署的各类传感器等等 ...
- IOS数据存储 —— 1 沙盒(数据存储目录)
沙盒结构 沙盒的文件系统⽬录,如下图所示(假设应用的名称叫Layer) 1.Documents 目录 您应该将所有的应用程序数据文件写入到这个目录下. 这个目录用于存储用户数据或其它应该定期备份的信息 ...
- python数据存储系列教程——python(pandas)读写csv文件
全栈工程师开发手册 (作者:栾鹏) python教程全解 CSV文件的规范 1.使用回车换行(两个字符)作为行分隔符,最后一行数据可以没有这两个字符.2.标题行是否需要,要双方显示约定3.每行记录的字 ...
最新文章
- HSSFWorkbook 与 XSSFWorkbook
- 安装 Homebrew
- 为什么要学python语言_我们为什么要学习Python语言?
- Quartz.NET总结(四)Quartz 远程调度
- MYSQL数据库导入出错:#1046 - No database selected
- git 创建webpack项目_Webpack入门:从安装到配置
- 服装CAD计算机按钮在哪里,富仪服装CAD快捷键大全
- macOS苹果电脑下载m3u8、ts视频
- 【Pygame实战】超有趣的泡泡游戏来袭——愿你童心不泯,永远快乐简单哦~
- Tomcat 端口被javaw.exe占用 有效解决方法
- python爬虫 爬取视频 练习
- 玩家密无忧 v7.0.7 绿色
- 设计窗口模拟教室座位表java,基于web的考研自习教室座位管理(完整源码+论文全套+教学视频)...
- jenkins定时构建
- 计算机室内设计cad实践报告,cad室内设计实习报告
- 《计算机存储与外设》----2.3 DRAM
- Android基础篇 访问Assets文件夹里面的资源【文本、图片、音频、字体包】
- C++设计模式之状态模式(state)(行为型)
- mysql8.0.19中在navicat客户端中int、bigint等类型设置长度保存后为0
- 智慧城市概述:算法与数据链接下的未来设想