当前位置:网站首页>SDRAM的数据存储实现并对其数据进行读写操作

SDRAM的数据存储实现并对其数据进行读写操作

2022-08-09 07:25:00 an-ning

SDRAM的数据存储实现并对其数据进行读写操作

1 SDRAM

1.1 简介

​ SDRAM(Synchronous Dynamic Random Access Memory),即同步动态随机访问存储器。同步是指内存工作需要同步时钟,内部命令的发送和数据的传输都以它为基准;而动态是指存储阵列需要不断地刷新来保证数据不丢失;随机指数据不是依次存储的,而随机的指定地址进行数据的读写操作。SDARM的读写是一个比较复杂的流程,其中包括行激活、列读写、预充电、刷新等一系列操作。

1.2 SDRAM结构和原理

​ 1、结构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DdQaYTvm-1659576614933)(SDRAM的数据存储实现并对其数据进行读写操作.assets/1658315590878.png)]

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时钟100MHz
    input            clk_in            ,                    //fifo的数据输入时钟50MHz
    input            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) 
    begin
        if (!rst_n)
            cstate <= IDLE;
        else 
            cstate <= nstate;
    end

    always @(*) 
    begin
        case (cstate)
            IDLE:
            begin
                if (idle_to_write)
                    nstate = WRITE;
                else if (idle_to_read)
                    nstate = READ;
                else
                    nstate = cstate;
            end
            WRITE:
            begin
                if (write_to_idle)
                    nstate = IDLE;
                else
                    nstate = cstate;
            end
            READ:
            begin
                if (read_to_idle)
                    nstate = IDLE;
                else
                    nstate = cstate;
            end
            default:  ;
        endcase
    end

//状态机转移条件判断
    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_flag
    always @(posedge clk or negedge rst_n) 
    begin
        if (!rst_n)
            wr_flag <= 0;
        else if (wrfifo_usedw > `BURST_LEN)
            wr_flag <= 1'b1;
        else
            wr_flag <= 1'b0;
    end

    //rd_flag
    always @(posedge clk or negedge rst_n) 
    begin
        if (!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_flag
    always @(posedge clk or negedge rst_n) 
    begin
        if (!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_flag
    always @(posedge clk or negedge rst_n) 
    begin
        if (!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) 
    begin
        if (!rst_n)
            cnt_burst <= 0;
        else if (add_cnt_burst)
        begin
            if (end_cnt_burst) 
                cnt_burst <= 0;
            else
                cnt_burst <= cnt_burst + 1'b1;
        end
    end
    assign  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) 
    begin
        if (!rst_n)
            cnt_wraddr <= 0;
        else if (add_cnt_wraddr)
        begin
            if (end_cnt_wraddr) 
                cnt_wraddr <= 0;
            else
                cnt_wraddr <= cnt_wraddr + 1'b1;
        end
    end
    assign  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) 
    begin
        if (!rst_n)
            cnt_rdaddr <= 0;
        else if (add_cnt_rdaddr)
        begin
            if (end_cnt_rdaddr) 
                cnt_rdaddr <= 0;
            else
                cnt_rdaddr <= cnt_rdaddr + 1'b1;
        end
    end
    assign  add_cnt_rdaddr = cstate == READ && ~avs_waitrequest;
    assign  end_cnt_rdaddr = add_cnt_rdaddr && cnt_rdaddr == `BURST_MAX - 1;

//dout_r
    always @(posedge clk_out or negedge rst_n) 
    begin
        if (!rst_n)
        begin
            dout_r <= 0;
            dout_r_vld = 0;
        end
        begin
            dout_r <= rdfifo_q;
            dout_r_vld <= rdfifo_rdreq;
        end
    end

//例化写fifo
    wrfifo	            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 ;

//例化读fifo
    rdfifo	            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)

​		还有点问题,第一次接收时总是会先接收三个占空符,目前还没有找到问题在哪里。
原网站

版权声明
本文为[an-ning]所创,转载请带上原文链接,感谢
https://blog.csdn.net/qq_46689721/article/details/126153248