SDRAM是个啥?
SDRAM(Synchronous Dynamic Random Access Memory),同步动态随机存储器。
同步
是指 Memory工作需要同步时钟,内部的命令的发送与数据的传输都以它为基准。
动态
是指存储阵列需要不断的刷新来保证存储的数据不丢失,因为SDRAM是通过电容来中存储数据,而电容自然放置状态是会有放电的,为了保证SDRAM中的数据不丢失,所以SDRAM需要在电容的电量放完之前进行刷新。
随机
是指数据不是线性依次存储,而是自由指定地址进行数据的读写。
SDRAM 结构长啥样?
以IS42S86400B型号的SDRAM举例说明。下图为其内部结构
我们关注外围的PIN以及Bank就可以了,下面逐一说明PIN定义及Bank。
PIN定义
PIN | 释义 |
---|---|
A0 - A12 | 地址输入,行地址A0-12,列地址A0-A9,A10用于控制Auto-precharge(自动预充电) |
BA0,BA1 | Bank地址输入,选择不同的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读写流程
凡是预则立,不预则废,首先我们先规划一个大致的操作流程,在针对每个流程去看其中具体需要执行的指令动作,且在不同的指令动作间,有具体的操作周期间隔,这部分需依据手册中标识去编写代码。
初始化 init_s
这个状态需要配置一切SDRAM工作所需要的参数,通过MBS指令动作完成,进行该指令动作时,要依据手册中的动作时序进行。
- 上电后,执行NOP指令,维持至少200us。
- 然后进行所有bank的预充电动作-PALL。
- 执行至少8次的自动刷新指令-REF,间隔期执行NOP。
- 执行MBS指令,配置参数。
- 配置完参数进入正常操作流程,空闲时执行NOP,需要进行读写操作时,激活相应bank和行地址。
MBS指令的配置,按照芯片手册进行配置
这里我们配置为
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)
关于读写操作,有很多组合,这里举了一种比较简单的方式,更多复杂的组合可以在手册的引导下完成。
本章节所说的读或是写操作过程如下:
- 抬手就是激活对应的bank和行地址。
- 然后通过WE_n信号的状态,决定读还是写指令。
- 接一个预充电指令就可以回到空闲态。
- 持续输出NOP指令,等待下一次读写操作,或者自动刷新指令。
上述过程转变时,存在一定的时间间隔的要求,具体查看相应手册。
简单的读指令操作
在T0前需要执行激活指令,图中没有展示该过程,而是着重展示了预充电后,需要等待Trp时间后才能继续操作下一次激活指令。
-------------------------------------------------------------------------------
--激活+读操作+预充电的信号状态
-------------------------------------------------------------------------------
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