FPGA之旅设计99例之第二十例---SDRAM存储器实现

一. 简介

本例将介绍SDRAM的使用。SDRAM是一个存储器件,存储容量大,存储速度比较快,速度可达100M,特别适合用来当中视频或者音频中的存储器件。

在采集到OV5640传输过来的图像数据的时候,FPGA的片上资源是没有那么大的存储空间进行存储的,必须通过外部的存储器件进行存储。恰好开发板上有一片SDRAM,所以用此来进行存储,一般而言入门级的FPGA开发板上都是配置的SDRAM,中高级一点的是DDR2(alter开发板),DDR3(xilinx开发板)。

所以本例将实现一个完善的SDRAM存储控制器,供大家查看。

关注微信 公众号 FPGA之旅 回复 FPGA之旅设计99例之第二十例 获取全部工程文件 包括sdram仿真模型

二. SDRAM接口信号

从下面框图中可以看出,SDRAM接口信号可以分为四大类:控制信号,地址信号,数据输入输出信号,掩码信号。下面将详细介绍各个命令的作用。

请添加图片描述

CKE时钟使能信号,高电平有效,一般上电后,就一直为高了
CLK时钟信号,由外部输入
CS片选信号,低电平有效,一般上电后,就一直为低了
WE,CAS,RAS命令信号,一般将CS组合在一起,组成4bit的命令
A地址信号,行列地址分时有效
BAbank区域选择
DQML/DQMH掩码,高电平有有效,DQMH对应DQ[15:8]bit,DQML对应DQ[7:0],DQMx置高,在读写时,对应的DQ一直为高阻态
DQ数据输入输出

以上就是SDRAM的全部接口信号了,并没有特别复杂。

下图是SDRAM的所有命令,在对SDRAM进行操作的时候,需要使用到。

请添加图片描述

然后在.v文件中先将其定义出来,方便后续使用

请添加图片描述

三. SDRAM上电初始化

上电后,没有任何时序上的操作,只需要延时100us(手册上要求最小为100us),使输入输出电平达到稳定,即可,在此期间,发送的命令最好为NOP

这里初始化包括了初始化和加载模式寄存器我认为初始化,就是加载模式寄存器。

(1)模式寄存器

模式寄存器的定义如下,通过地址线给出,每位都有其具体的含义。

0-2 bit:定义突发长度,每给一个读/写命令后,输出/输入的数据大小

4-6bit :定义潜伏期,发出读命令后,延时多少个周期给读数据,仅对读操作有效

10-12bit:保留,始终置高即可

其余位始终保持为0即可。模式寄存器的内容就这么多。

请添加图片描述

(2)初始化

下图是初始化的过程,按照图示要求依次发送对应的命令即可,命令与命令之间的间隔时间手册上都有说明,取的时候,可以适当取大。模式寄存器的A信号被分成了两部分A10和其他,可以看到A10在PRECHARGE阶段有特殊作用,一般为1,对所有的bank都进行预充电。

在模式寄存器中A10也是为1的,所以在整个初始化过程中,A可以直接赋值为模式寄存器的值。

请添加图片描述

实现过程如下,也是非常简介的,编写好初始化模块看,可以直接仿真,这里多亏了大佬写的sdram模型(就是一个.v文件),不用上板,可以直接仿真看代码编写是否正确。

请添加图片描述

仿真输出如下,可以看到和时序图中,命令发送过程是一样,同时也可以看到,模式寄存器配置的具体参数,非常方便,初始化模块就顺利的编写完成了。

请添加图片描述

四. 刷新模块

由于sdram的特殊结构,sdram在使用的过程中,需要每隔一段时间,对所有的存储区域进行一次刷新操作(充电),否则内部存储的数据会丢失,这将会成为后面设计的一大难点

根据手册得知每64ms需要完成8192次刷新操作,也就是下面的时序图需要在64ms内运行8192次,平均下来7us就要进行一次,这个时间需要记住非常重要。

同样也是根据手册给出的时序图进行编写代码,一共需要发送三个命令

请添加图片描述

代码实现如下,也是非常容易实现的。

请添加图片描述

从图中可以看出,每个7090ns进行一次刷新,满足要求。
请添加图片描述

至此,初始化和刷新模块编写完成,这两部分只需要按照手册上给出的时序图来编写代码即可,比较容易即可完成,后面的读写模块会复杂一些。

五. 写模块

读模块的时序图如下,截取的是没有auto precharge操作的,也就是在数据写完后,需要手动发送一个precharge命令。同样可以让sdram自动完成这个操作,只需要在发送write命令的时候,将A10拉高即可,这样在发送完数据后,就可以直接结束了,不用发送precharge命令。本次介绍的是需要发送precharge命令。

设计是需要清楚以下两个问题

  • 发送过程中,需要切换行地址或者bank的时候,应该怎样操作
  • 发送过程中,突然来了刷新请求时,该如何处理

请添加图片描述

先对第一个问题进行说明一下,在sdram中,行地址和bank是发送ACTIVE命令时指定的,发送write命令时,就可以指定列地址了,如下BL=1。也就是说切换行地址或bank时需要重新发送ACTIVE命令。ps: 写操作是没有潜伏期的

请添加图片描述

手册中也给出了这部分的时序图,如下。需要注意的一点是,它这是使能了auto precharge,所以数据发送完成后,没有发送precharge命令,就发送了ACTIVE命令来切换行地址或bank了。没有使能的情况下,需要加上precharge地址,然后再延时tRCD,发送ACTIVE命令,这点需要注意。

请添加图片描述

第二个问题,当刷新请求来时,这个时候当然是要暂停发送数据,需要保存已经发送数据的个数,以及当前发送的地址和bank。然后在刷新结束后,继续发送数据。

模块框图和状态机如下。在write_data_en使能的情况下,外部输入数据进来,其余时刻输入的数据无效,相当于一个握手信号。sdram模式寄存器配置的是突发长度为1,所以这里单次写突发长度是没有大小限制的

sdram_write
(   
    input               sdram_clk,
    input               rst_n,

    input               sdram_write_req,            //写请求
    output              sdram_write_ack,            //写响应
    input               sdram_write_pause,          //写暂停信号,转去刷新操作
    output  reg         sdram_write_pause_ack,      //写暂停响应,成功暂停    

    input[24:0]         write_addr,                 //写入地址
    input[15:0]         write_data,                 //写入数据      {bank[1:0s],row[12:0],clo[9:0]}
    input[9:0]          write_burst_length,         //单次写突发长度    //可以为sdram大小
    output              write_data_en,              //写入数据有效输出

    //sdram接口
    output[3:0]         sdram_write_cmd,
    output[12:0]        sdram_write_addr,
    output[1:0]         sdram_write_ba,
    output[15:0]        sdram_write_data
);
localparam      S_IDEL                  =   'd0;        //空闲态
localparam      S_ACTIVE                =   'd1;        //激活态
localparam      S_WRITE                 =   'd2;        //写数据
localparam      S_PAUSE                 =   'd3;        //写暂停
localparam      S_ALTERNATE             =   'd4;        //换行换bank缓存
localparam      S_PRECHARGE             =   'd5;        //写结束后或切换行列地址,发送precharge命令
localparam      S_END                   =   'd6;        //写结束

always@(*)
begin
    case(state)
    S_IDEL:
        if( sdram_write_req == 1'b1 )
            next_state <= S_ACTIVE;
        else
            next_state <= S_IDEL;
    S_ACTIVE:
        if( sdram_write_pause == 1'b1 )     //写暂停信号,转去刷新操作
            next_state <= S_PAUSE;
        else if( time_cnt == `tRCD )
            next_state <= S_WRITE;
        else
            next_state <= S_ACTIVE;
    S_WRITE:
        if( sdram_write_pause == 1'b1 )     //写暂停信号,转去刷新操作
            next_state <= S_PAUSE;
        else if( alternating_bank_row_en == 1'b1)
            next_state <= S_PRECHARGE;
        else if( write_burst_length_cnt == write_burst_length )
            next_state <= S_PRECHARGE;
        else
            next_state <= S_WRITE;
    S_PAUSE:
        if( sdram_write_pause == 1'b0 )     //刷新操作结束
            next_state <= S_ACTIVE;
        else
            next_state <= S_PAUSE;
    S_ALTERNATE:
        if( time_cnt == `tRCD)
            next_state <= S_ACTIVE;
        else
            next_state <= S_ALTERNATE;
    S_PRECHARGE:
        if( time_cnt == `tRP + `tWR)
            if( write_burst_length_cnt >= write_burst_length)
                next_state <= S_END;
            else
                next_state <= S_ALTERNATE;
        else
            next_state <= S_PRECHARGE;
    S_END:
        next_state <= S_IDEL;
    default :  next_state <= S_IDEL;
    endcase
end

最后通过仿真,确认实现正确,第一幅图是写过程进行刷新操作,第二幅图是,写过程切换行地址

请添加图片描述
请添加图片描述

六. 读模块

读模块过程的编写和写模块是一模一样的,不过需要注意的是读模块有潜伏期,命令发送和数据输出相差CL个时钟周期,读数据的时候,需要将这个延时加入其中,可以看到接口信号和写模块是一样。不过对数据进行采样的时候,需要使用输入到sdram中的时钟,这需要注意。

请添加图片描述

sdram_read
(
    input               sdram_clk,
    input               rst_n,

    input               sdram_read_req,            //读请求
    output              sdram_read_ack,            //读响应
    input               sdram_read_pause,          //读暂停信号,转去刷新操作
    output  reg         sdram_read_pause_ack,      //读暂停响应,成功暂停    

    input[24:0]         read_addr,                 //读入地址
    output[15:0]        read_data,                 //读出数据      {bank[1:0s],row[12:0],clo[9:0]}
    input[9:0]          read_burst_length,         //单次读突发长度    //可以为sdram大小
    output              read_data_en,              //读数据有效输出

    //sdram接口
    output[3:0]         sdram_read_cmd,
    output[12:0]        sdram_read_addr,
    output[1:0]         sdram_read_ba,
    input[15:0]        sdram_read_data
);

通过仿真输出,确定突发读期间,换行以及刷新完全正确

请添加图片描述
请添加图片描述

至此SDRAM模块的编写就完成了,顶层框图如下,至于在外部如何进行封装,那就看不同的需求了。

有任何设计疑问可以关注 公众号 FPGA之旅 与我交流。

关注微信 公众号 FPGA之旅 回复 FPGA之旅设计99例之第二十例 获取全部工程文件 包括sdram仿真模型

SDRAM_TOP(
    input                       sys_clk,                    //sdram的系统时钟 100M
    input                       rst_n,                      //异步复位信号

    //读接口
    input                       read_req,
    output                      read_ack,

    input[24:0]                 read_addr,
    input[9:0]                  read_burst_length,
    output[15:0]                read_data,
    output                      read_data_en,


    //写接口
    input                       write_req,
    output                      write_ack,

    input[24:0]                 write_addr,
    input[9:0]                  write_burst_length,
    input[15:0]                 write_data,
    output                      write_data_en,


    //sdram接口
    output                      sdram_clk,         //sdram clock
	output                      sdram_cke,         //sdram clock enable
	output                      sdram_cs_n,        //sdram chip select
	output                      sdram_we_n,        //sdram write enable
	output                      sdram_cas_n,       //sdram column address strobe
	output                      sdram_ras_n,       //sdram row address strobe
	output[1:0]                 sdram_dqm,         //sdram data enable
	output[1:0]                 sdram_ba,          //sdram bank address
	output[12:0]                sdram_addr,        //sdram address
	inout[15:0]                 sdram_dq           //sdram data
);
  • 5
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

FPGA之旅

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

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

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

打赏作者

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

抵扣说明:

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

余额充值