FPGA视角--SDRAM

SDRAM是个啥?

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

同步

是指 Memory工作需要同步时钟,内部的命令的发送与数据的传输都以它为基准。

动态

是指存储阵列需要不断的刷新来保证存储的数据不丢失,因为SDRAM是通过电容来中存储数据,而电容自然放置状态是会有放电的,为了保证SDRAM中的数据不丢失,所以SDRAM需要在电容的电量放完之前进行刷新。

随机

是指数据不是线性依次存储,而是自由指定地址进行数据的读写。

SDRAM 结构长啥样?

以IS42S86400B型号的SDRAM举例说明。下图为其内部结构
SDRAM内部结构
我们关注外围的PIN以及Bank就可以了,下面逐一说明PIN定义及Bank。

PIN定义

PIN释义
A0 - A12地址输入,行地址A0-12,列地址A0-A9,A10用于控制Auto-precharge(自动预充电)
BA0,BA1Bank地址输入,选择不同的BANK
DQ0 - DQ15数据I/O
CLK系统时钟输入
CKE时钟使能,高有效
CS_n芯片选通,低有效
RAS_n行选通,低有效
CAS_n列选通,低有效
WE_n写使能,低有效
DQML低字节数据输入/输出掩码
DQMH高字节数据输入/输出掩码

Bank

一块Bank可以理解为一片RAM,通过行列地址操作其内部的存储单元。
在这里插入图片描述

SDRAM容量计算

SDRAM容量 = 数据位宽x存储单元数量(BANK数x行地址x列地址)
即:16(DQ) x 4(Bank) x 8192(行地址范围) x 1024(列地址范围)= 512Mb

SDRAM如何用起来

了解基本的SDRAM的指令动作

依据IS42S86400B的器件手册,得到基本的指令动作有:
在这里插入图片描述
上图为指令的真值表,具体时序状态,参考相应的芯片手册。

命令动作释义说明
Device deselect (DESL)未指定设备未选中该设备
No operation (NOP)空操作在空闲或等待状态下发出的指令
Burst stop (BST)突发停止中断当前执行的突发写或者读操作
Read读指令
Read with auto precharge读指令相对于上面的读指令,A10被置为高,代表读完自动进行预充电
Write写指令
Write with auto precharge写指令相对于上面的写指令,A10被置为高,代表读完自动进行预充电
Bank activate(ACT)bank激活执行激活Bank指令时,会激活该Bank和对应的行地址
Precharge select bank(PRE)Bank预充电选择性的为Bank进行充电
Precharge all banks(PALL)Bank预充电为所有bank进行预充电
CBR Auto-Refresh(REF)自动刷新内存刷新指令,固定周期,在此期间不能执行任何其他命令,该命令每64ms至少执行8192次
Self_Refresh(SELF)自刷新自刷新在无有效时钟输入时使用,维持存储设备在低功耗状态,且可以保持数据
Mode register set(MBS)模式寄存器设置该模式下配置内存参数,突发类型及长度等

通过指令动作组装一个简单SDRAM读写流程

凡是预则立,不预则废,首先我们先规划一个大致的操作流程,在针对每个流程去看其中具体需要执行的指令动作,且在不同的指令动作间,有具体的操作周期间隔,这部分需依据手册中标识去编写代码。
sdram 状态流程

初始化 init_s

这个状态需要配置一切SDRAM工作所需要的参数,通过MBS指令动作完成,进行该指令动作时,要依据手册中的动作时序进行。

  1. 上电后,执行NOP指令,维持至少200us。
  2. 然后进行所有bank的预充电动作-PALL。
  3. 执行至少8次的自动刷新指令-REF,间隔期执行NOP。
  4. 执行MBS指令,配置参数。
  5. 配置完参数进入正常操作流程,空闲时执行NOP,需要进行读写操作时,激活相应bank和行地址。
    Sdram Init
    MBS指令的配置,按照芯片手册进行配置sdram- mode
    这里我们配置为
  Register_Map <= "000" & '0' & "00" & "010" & '0' & "010";
  --注意,这里值包含了地址段;BA0,BA1应配置为0。

由此,这是一个可编程突发长度的标准操作模式下的SDRAM芯片,其中CAS的潜伏期为2,突发类型为有序突发,长度为4。

空闲态 Idle_s

该状态下默认执行NOP操作,同时等待跳入自动刷新和读写操作。

-------------------------------------------------------------------------------
-- NOP时的信号状态
-------------------------------------------------------------------------------
        sdram_RAS_n <= '1';
        sdram_CAS_n <= '1';
        sdram_WE_n  <= '1';
        sdram_Addr  <= (others => '0');
        sdram_BA    <= "00";
        sdram_DQM   <= "1111";

自动刷新 Auto_Ref_s

该状态下执行自动刷新指令,手册中标注,该命令每64ms至少执行8192次,可以看到自动刷新按照行地址扫描,64ms内要把所有的行地址刷新完毕。这里刷新的间隔没必要卡的这么死,设置5us执行一次,64ms内可以执行12800次,足够,也能为读写操作和自动刷新的指令冲突提供缓冲。

-------------------------------------------------------------------------------
-- 自动刷新时的信号状态
-------------------------------------------------------------------------------
  process(sys_clk)
  begin
    if(rising_edge(sys_clk))then
      if(sys_rst = '1')then
        auto_ref_RAS_n <= '1';
        auto_ref_CAS_n <= '1';
        auto_ref_WE_n  <= '1';
      elsif(ref_pstate = auto_ref_s)then
        auto_ref_RAS_n <= '0';
        auto_ref_CAS_n <= '0';
        auto_ref_WE_n  <= '1';
      else
        auto_ref_RAS_n <= '1';
        auto_ref_CAS_n <= '1';
        auto_ref_WE_n  <= '1';           
      end if;
    end if;
  end process;

  auto_ref_DQM  <= "1111";
  auto_ref_DQ   <= (others => '0');
  auto_ref_BA   <= "00";
  auto_ref_Addr <= (others => '0');

读/写操作(Active_s + Read_op_s / Write_op_s + Percharge_s)

关于读写操作,有很多组合,这里举了一种比较简单的方式,更多复杂的组合可以在手册的引导下完成。
本章节所说的读或是写操作过程如下:

  1. 抬手就是激活对应的bank和行地址。
  2. 然后通过WE_n信号的状态,决定读还是写指令。
  3. 接一个预充电指令就可以回到空闲态。
  4. 持续输出NOP指令,等待下一次读写操作,或者自动刷新指令。

上述过程转变时,存在一定的时间间隔的要求,具体查看相应手册。

简单的读指令操作

在T0前需要执行激活指令,图中没有展示该过程,而是着重展示了预充电后,需要等待Trp时间后才能继续操作下一次激活指令。
sdram read to percharge

-------------------------------------------------------------------------------
--激活+读操作+预充电的信号状态
-------------------------------------------------------------------------------
      elsif(task_pstate = Active_s)then
        r_sdram_RAS_n <= '0';
        r_sdram_CAS_n <= '1';
        r_sdram_WE_n  <= '1';
        r_sdram_Addr  <= usr_sdram_row_addr;
        r_sdram_BA    <= usr_sdram_ba;
      elsif(task_pstate = Read_s and sta_cnt = 0)then
        r_sdram_RAS_n <= '1';
        r_sdram_CAS_n <= '0';
        r_sdram_WE_n  <= '1';
        r_sdram_Addr  <= "0000" & usr_sdram_col_addr(8 downto 0);
        r_sdram_BA    <= usr_sdram_ba;
      elsif(task_pstate = Precharge_s)then
        r_sdram_RAS_n <= '0';
        r_sdram_CAS_n <= '1';
        r_sdram_WE_n  <= '0';
        r_sdram_Addr  <= "00100" & x"00";  --A10-All Back
        r_sdram_BA    <= usr_sdram_ba;
简单的写指令操作

在T0前需要执行激活指令,图中没有展示该过程,而是着重展示了预充电后,需要等待Trp时间后才能继续操作下一次激活指令。
在这里插入图片描述

-------------------------------------------------------------------------------
--激活+写操作+预充电的信号状态
-------------------------------------------------------------------------------
      elsif(task_pstate = Active_s)then
        w_sdram_RAS_n <= '0';
        w_sdram_CAS_n <= '1';
        w_sdram_WE_n  <= '1';
        w_sdram_Addr  <= wr_sdram_row_addr;
        w_sdram_BA    <= wr_sdram_ba_addr;
      elsif(task_pstate = Write_s and sta_cnt = 0)then
        w_sdram_RAS_n <= '1';
        w_sdram_CAS_n <= '0';
        w_sdram_WE_n  <= '0';
        w_sdram_Addr  <= wr_sdram_col_addr;
        w_sdram_BA    <= wr_sdram_ba_addr;
      elsif(task_pstate = Precharge_s)then
        w_sdram_RAS_n <= '0';
        w_sdram_CAS_n <= '1';
        w_sdram_WE_n  <= '0';
        w_sdram_Addr  <= "00100" & x"00";  --A10-All Back
        w_sdram_BA    <= wr_sdram_ba_addr;

SDRAM代码编写的结构建议

建议分为五个模块,即:
顶层模块
仲裁模块
写模块
读模块
自动刷新模块
使模块间各司其职。

Sdram_Auto_Ref.vhd
Sdram_Inital.vhd
Sdram_Read.vhd 
Sdram_Top.vhd  
Sdram_Write.vhd
Sdram_arbit.vhd

仲裁输出结构

  sdram_CLK  <= not sys_clk;                --100Mhz
  sdram_CS_n <= '0';
  sdram_CKE  <= '1';
  
  process(sys_clk)
  begin
    if(rising_edge(sys_clk))then
      if(sys_rst = '1')then
        sdram_RAS_n <= '1';
        sdram_CAS_n <= '1';
        sdram_WE_n  <= '1';
        sdram_Addr  <= (others => '0');
        sdram_BA    <= "00";
        sdram_dout  <= (others => '0');
        sdram_DQM   <= x"0";
      elsif(task_pstate = idle_s and init_sdram_done = '0')then
        sdram_RAS_n <= init_sdram_RAS_n;
        sdram_CAS_n <= init_sdram_CAS_n;
        sdram_WE_n  <= init_sdram_WE_n;
        sdram_Addr  <= init_sdram_Addr;
        sdram_BA    <= init_sdram_BA;
        sdram_dout  <= init_sdram_DQ;
        sdram_DQM   <= init_sdram_DQM;
        
      elsif(task_pstate = sdram_auto_ref_s)then
        sdram_RAS_n <= auto_ref_RAS_n;
        sdram_CAS_n <= auto_ref_CAS_n;
        sdram_WE_n  <= auto_ref_WE_n;
        sdram_Addr  <= auto_ref_Addr;
        sdram_BA    <= auto_ref_BA;
        sdram_dout  <= auto_ref_DQ;
        sdram_DQM   <= "1111";
        
      elsif(task_pstate = sdram_read_s)then
        sdram_RAS_n <= r_sdram_RAS_n;
        sdram_CAS_n <= r_sdram_CAS_n;
        sdram_WE_n  <= r_sdram_WE_n;
        sdram_Addr  <= r_sdram_Addr;
        sdram_BA    <= r_sdram_BA;
        sdram_DQM   <= r_sdram_DQM;
        
      elsif(task_pstate = sdram_write_s)then
        sdram_RAS_n <= w_sdram_RAS_n;
        sdram_CAS_n <= w_sdram_CAS_n;
        sdram_WE_n  <= w_sdram_WE_n;
        sdram_Addr  <= w_sdram_Addr;
        sdram_BA    <= w_sdram_BA;
        sdram_dout  <= w_sdram_DQ;
        sdram_DQM   <= w_sdram_DQM;
      else
        sdram_RAS_n <= '1';
        sdram_CAS_n <= '1';
        sdram_WE_n  <= '1';
        sdram_Addr  <= (others => '0');
        sdram_BA    <= "00";
        sdram_DQM   <= "1111";
      end if;
    end if;
  end process;

  r_sdram_DQ  <= sdram_DQ;
  
  sdram_DQ   <= sdram_dout when task_pstate = sdram_write_s or init_sdram_done = '0'
                else "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ";

资料分享

IS42S86400B器件手册: https://download.csdn.net/download/sgxb2028/12747768
SDRAM控制器代码-VHDL: https://download.csdn.net/download/sgxb2028/12747783

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三一九六

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值