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


    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269

    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]};
    
    • 1
    • 2
    • 3

    endmodule //sdram_ctrl

    
    
    
    #### 3.2  运行结果
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/390bc8ff345a40a6a7373c1ac5ed957d.png)
    
    ​		还有点问题,第一次接收时总是会先接收三个占空符,目前还没有找到问题在哪里。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
  • 相关阅读:
    ABAP 效率优化 FOR ALL ENTRIES IN和JOIN ITAB对比
    ChatGPT 基本用法!ChatGPT4的prompt的使用例子!
    docker 跨平台构建镜像
    K Shortest Paths算法之Yen algorithm
    MySQL进阶——触发器
    区块链技术以太坊简介
    收集表单数据Vue
    【C++杂货铺】一文带你走进哈希:哈希冲突 | 哈希函数 | 闭散列 | 开散列
    c#输入和输出
    Iterable、Collection、List等接口
  • 原文地址:https://blog.csdn.net/qq_46689721/article/details/126153248