官方论坛
官方淘宝
官方博客
微信公众号
点击联系吴工 点击联系周老师
您的当前位置:主页-old > 新闻中心 > FPGA技术教程 > SDRAM和DDR >

【文章】基于FPGA的SDRAM控制器读写(三)

发布时间:2021-07-01   作者:admin 浏览量:

本文为明德扬原创及录用文章,转载请注明出处!


SDRAM控制器设计的主要功能是能对SDRAM进行读写操作,本工程实现了SDRAM的初始化、自动刷新、读、写等功能。


初始化功能和刷新功能在前一章的分享中已经进行了比较详细的描述,感兴趣的同学可以搜索学习下,这里不再赘述。今天我们主要讨论SDRAM读写的功能以及实现。


一、原理功能


1、读写突发模式



在初始化里的模式寄存器配置中我们将读写的突发长度(BL)设置为4,列选通潜伏期(CL)设为3,突发类型为顺序模式(每一次突发只给出起始地址即可)。对于预充电选择(Auto Precharge)自动预充电,即外部不需要发送预充电命令


2、读、写时序图


null

                                                图表1写时序图

null

                                                      图表2读时序图

3、时序图解读

由时序图可知,读写模块采用线性序列机设计比较简单。在每次读写突发之前都需要发送ACTIVE命令,同时给出bank地址和row地址,2拍后发出读或写命令,同时将A10拉高并给出col地址。对于写操作没有潜伏期,由于DQ是双向端口,所以需要一个三态门控制DQ总线方向,当写操作时为输出方向,读操作时为输入方向。对于读操作,发出读命令后会有CL拍的潜伏期,本实验里CL=3,即发出读命令后3拍,数据才会出现在DQ总线上。


二、FPGA实现


1、模块架构


null

注:信号方向请看箭头


2、模块架构解读


要完整实现SDRAM控制器必须要完成初始化,刷新,读写这四部分功能。所以模块划分大体依照次为指导。由于SDRAM控制器工作时钟为100MHz,且要输出一个频率相同相位相差180°的时钟给SDRAM,所以要有一个锁相环模块。刷新需要计时刷新间隔,所以要加入一个刷新定时器模块,由于初始化,刷新,读,写等模块都要输出sdr_cke,sdr_cs_n,sdr_cas_n,sdr_ras_n,sdr_we_n,sdr_ba,sdr_a等信号到SDRAM,所以需要一个选择模块。将sdr_cke,sdr_cs_n,sdr_cas_n,sdr_ras_n,sdr_we_n,sdr_ba,sdr_a等信号组合成bus信号输入到选择模块。


新加入的信号说明,其他信号在前两篇中已经详细说明,读者可以参考。


信号

功能

说明

顶层



local_data

写突发数据输入

外部输入

local_addr

地址总线

外部输入

local_q

读突发数据输出

输出

local_wrreq

写请求

外部输入

local_rdreq

读请求

外部输入

local_reday

可以进行读写操作信号

输出

local_rdata_vaild

输出数据有效信号

输出

写模块



wr_en

写使能

仲裁模块输出到写模块

wr_done

写完成

写模块输出到仲裁模块

wr_bus

写操作总线

写模块输出到选择模块

sdr_dq

SDRAM数据总线

双向端口,对于写模块是输出,读模块是输入

读模块



rd_bus

读操作总线

读模块输出到选择模块

rd_en

读使能

仲裁模块输出到读模块

rd_done

读完成

读模块输出到仲裁模块


新加入了写模块和读模块。


写模块主要是完成一次写突发操作,将local_data信号写入到SDRAM中的指定地址local_addr中。local_addr主要由bank地址,行地址和列地址组合而成。在读和写模块代码中均有体现,可以参考。


读模块主要完成一次读突发操作,将SDRAM中指定的地址中的数据读出来,并赋值给local_q信号。


由于加入了写模块和读模块,所以仲裁模块也要做出相应的修改,当收到刷新请求时,即rt_flag信号后,在结束本次读突发或者写突发后,拉高刷新使能(ref_en)信号,当收到写请求且没有刷新请求时,拉高写使能(wr_en)信号,当收到读请求且没有刷新请求和写请求时,拉高读使能(rd_en)信号。


3、顶层模块参考代码


module sdram_top(

   clk   ,

   sys_rst_n ,

   //其他信号,举例dout

   local_addr,

   local_data,

   local_q,

   local_rdreq,

   local_wrreq,

   local_ready,

   local_rdata_valid,

   init_done,

   sdr_cke,

   sdr_cs_n,

   sdr_ras_n,

   sdr_cas_n,

   sdr_we_n,

   sdr_ba,

   sdr_a,

   sdr_dq,

   sdr_dqm,

   sdr_clk

   );

   

   input clk;

   input sys_rst_n;

   input [24:0] local_addr;

   input [63:0] local_data;

   output [63:0] local_q;

   input local_rdreq;

   input local_wrreq;

   output local_ready;

   output local_rdata_valid;

   output init_done;

   output sdr_cke;

   output sdr_cs_n;

   output sdr_ras_n;

   output sdr_cas_n;

   output sdr_we_n;

   output [1:0] sdr_ba;

   output [12:0] sdr_a;

   inout [15:0] sdr_dq;

   output [1:0] sdr_dqm;

   output sdr_clk;

   

   wire phy_clk;

   wire rst_n;

   wire rt_flag;

   wire rt_clear;

   wire rt_en;

   wire ref_en;

   wire ref_done;

   wire wr_done;

   wire wr_en;

   wire rd_en;

   wire rd_done;

   wire [1:0] sel_sm;

   wire [19:0] sdr_bus;

   wire [19:0] init_bus;

   wire [19:0] ref_bus;

   wire [19:0] wr_bus;

   wire [19:0] rd_bus;

   

   assign {sdr_cke, sdr_cs_n, sdr_ras_n, sdr_cas_n, sdr_we_n, sdr_ba, sdr_a} = sdr_bus;

   assign sdr_dqm = 2'b00;

       

   sdram_init sdram_init_inst(

       .clk           (phy_clk)       ,

       .rst_n         (rst_n)       ,

       //其他信号,举例dout

       .init_done     (init_done)       ,

       .init_bus      (init_bus)

   );

   

   arbitrate arbitrate_inst(

       .clk(phy_clk),

       .rst_n(rst_n),

       .rt_flag(rt_flag),

       .rt_en(rt_en),

       .rt_clear(rt_clear),

       .ref_done(ref_done),

       .ref_en(ref_en),

       .init_done(init_done),

       .sel_sm(sel_sm),

      .local_rdata_valid(local_rdata_valid),

       .local_ready(local_ready),

       .local_wrreq(local_wrreq),

       .local_rdreq(local_rdreq),

       .wr_done(wr_done),

       .wr_en(wr_en),

       .rd_en(rd_en),

       .rd_done(rd_done)

   );

   

   ref_timer ref_timer_inst(

       .clk(phy_clk),

       .rst_n(rst_n),

       .rt_en(rt_en),

       .rt_clear(rt_clear),

       .rt_flag(rt_flag)

   );

   

   sdram_ref sdram_ref_inst(

       .clk(phy_clk),

       .rst_n(rst_n),

       .ref_en(ref_en),

       .ref_done(ref_done),

       .ref_bus(ref_bus)

   );

   

   sdram_write sdram_write_inst(

       .clk(phy_clk),

       .rst_n(rst_n),

       .wr_en(wr_en),

       .wr_done(wr_done),

       .wr_bus(wr_bus),

       .sdr_dq(sdr_dq),

       .local_data(local_data),

       .local_addr(local_addr)

   );

   

   sdram_read sdram_read_inst(

       .clk(phy_clk),

       .rst_n(rst_n),

       .rd_en(rd_en),

       .rd_done(rd_done),

       .rd_bus(rd_bus),

       .sdr_dq(sdr_dq),

       .local_q(local_q),

       .local_addr(local_addr)

   );

   

   sdram_mux sdram_mux_inst(

       .clk(phy_clk),

       .rst_n(rst_n),

       .init_bus(init_bus),

       .ref_bus(ref_bus),

       .wr_bus(wr_bus),

       .rd_bus(rd_bus),

       .sdr_bus(sdr_bus),

       .sel_sm(sel_sm)

   );

   

   my_pll PLL(

       .areset    (~sys_rst_n)        ,

       .inclk0    (clk)               ,

       .c0        (phy_clk)           ,

       .c1        (sdr_clk)           ,

       .locked    (rst_n)

   );


endmodule


4、模块功能


PLL模块,初始化模块,刷新模块在前面两篇文章中已经讨论过,这里不再描述。


(1)写模块


主要完成写突发,采用线性序列机设计,当检测到wr_en为高电平时,计数器开始计时,并发出开ACT命令,并将row地址赋值给sdr_a。两拍之后,发出写命令,并将A10拉高,然后开始将数据赋值给sdr_dq信号。然后计数到8-1时将写完成信号wr_done拉高。


可以对照时序图阅读代码,其代码如下:


module sdram_write(clk, rst_n, wr_en, wr_done, wr_bus, sdr_dq, local_data, local_addr);


   input clk;

   input rst_n;

   input wr_en;

   output reg wr_done;

   output [19:0] wr_bus;

   inout [15:0] sdr_dq;

   input [63:0] local_data;

   input [24:0] local_addr;

   

   parameter CNT_MAX = 8;

   parameter NOP = 4'b0111;

   parameter ACT = 4'b0011;

   parameter WR  = 4'b0100;

   

   reg [3:0] cnt;

   reg [3:0] sdr_cmd;

   reg [1:0] sdr_ba;

   reg [12:0] sdr_a;

   reg [15:0] temp;

   reg out_en;

   

   wire [9:0] col;

   wire [12:0] row;

   wire [1:0] ba;

   wire sdr_cke;

   wire add_cnt;

   wire end_cnt;

   

   assign {ba, row, col} = local_addr;

   assign sdr_dq = out_en ? temp : 16'dz;

   assign sdr_cke = 1'b1;

   assign wr_bus = {sdr_cke, sdr_cmd, sdr_ba, sdr_a};

   

   always @(posedge clk or negedge rst_n)begin

       if(!rst_n)begin

           cnt <= 0;

       end

       else if(add_cnt)begin

           if(end_cnt)

               cnt <= 0;

           else

               cnt <= cnt + 1;

       end

   end


   assign add_cnt = wr_en;      

   assign end_cnt = add_cnt && cnt==CNT_MAX - 1 ;


   always @ (posedge clk or negedge rst_n)begin

       if(!rst_n)begin

           sdr_cmd <= NOP;

       end

       else if(add_cnt && cnt == 1 - 1)begin

           sdr_cmd <= ACT;

       end

       else if(add_cnt && cnt == 3 - 1)begin

           sdr_cmd <= WR;

       end

       else begin

           sdr_cmd <= NOP;

       end

   end

   

   always @(posedge clk or negedge rst_n)begin

       if(!rst_n)begin

           sdr_ba <= 2'd0;

       end

       else begin

           sdr_ba <= ba;

       end

   end

   

   always @(posedge clk or negedge rst_n)begin

       if(!rst_n)begin

           sdr_a <= 13'd0;

       end

       else if(add_cnt && cnt == 1 - 1)begin

           sdr_a <= row;

       end

       else if(add_cnt && cnt == 3 - 1)begin

           sdr_a <= {2'd0, 1'b1, col};

       end

       else begin

           sdr_a <= 13'd0;

       end

   end

   

   always @(posedge clk or negedge rst_n)begin

       if(!rst_n)begin

           temp <= 16'd0;

       end

       else if(add_cnt && cnt == 4 - 1)begin

           temp <= local_data[15:0];

       end

       else if(add_cnt && cnt == 5 - 1)begin

           temp <= local_data[31:16];

       end

       else if(add_cnt && cnt == 6 - 1)begin

           temp <= local_data[47:32];

       end

       else if(add_cnt && cnt == 7 - 1)begin

           temp <= local_data[63:48];

       end

       else begin

           temp <= 16'd0;

       end

   end

   

   always @(posedge clk or negedge rst_n)begin

       if(!rst_n)begin

           out_en <= 1'b0;

       end

       else if(add_cnt && cnt == 4 - 1)begin

           out_en <= 1'b1;

       end

       else if(add_cnt && cnt == 8 - 1)begin

           out_en <= 1'b0;

       end

       else begin

           out_en <= out_en;

       end

   end

   

   always @(posedge clk or negedge rst_n)begin

       if(!rst_n)begin

           wr_done <= 1'b0;

       end

       else if(add_cnt && cnt == 1 - 1)begin

           wr_done <= 1'b0;

       end

       else if(add_cnt && cnt == 8 - 1)begin

           wr_done <= 1'b1;

       end

       else begin

           wr_done <= wr_done;

       end

   end


endmodule


(2)读模块


读模块主要完成读突发,对于读模块来说,sdr_dq信号是输入信号,且不是同一时钟域信号,所以要加两级同步寄存器。当检测到读使能rd_en为高时,计数器开始计数,并发出ACT命令,同时给出row地址,两拍之后发出读命令,并将A10拉高,由于加入了两级同步寄存器且读潜伏期为3,所以5拍之后才可以采集数据,即计数到9-1时采集数据,4拍之后数据采集完成,下一拍将读完成信号rd_done拉高。


可以对照时序图阅读代码,代码如下


module sdram_read(clk, rst_n, rd_en, rd_done, rd_bus, sdr_dq, local_q, local_addr);


   input clk;

   input rst_n;

   input rd_en;

   output reg rd_done;

   output [19:0] rd_bus;

   input [15:0] sdr_dq;

   output reg [63:0] local_q;

   input [24:0] local_addr;

   

   parameter CNT_MAX = 14;

   parameter NOP = 4'b0111;

   parameter ACT = 4'b0011;

   parameter RD  = 4'b0101;



   reg [3:0] cnt;

   reg [3:0] sdr_cmd;

   reg [1:0] sdr_ba;

   reg [12:0] sdr_a;

   reg [15:0] temp0, temp1;

   

   wire [9:0] col;

   wire [12:0] row;

   wire [1:0] ba;

   wire sdr_cke;

   wire add_cnt;

   wire end_cnt;

   

   assign {ba, row, col} = local_addr;

   assign sdr_cke = 1'b1;

   assign rd_bus = {sdr_cke, sdr_cmd, sdr_ba, sdr_a};

   

   always @(posedge clk)begin

       temp0 <= sdr_dq;

       temp1 <= temp0;

   end

   

   always @(posedge clk or negedge rst_n)begin

       if(!rst_n)begin

           cnt <= 0;

       end

       else if(add_cnt)begin

           if(end_cnt)

               cnt <= 0;

           else

               cnt <= cnt + 1;

       end

   end


   assign add_cnt = rd_en;      

   assign end_cnt = add_cnt && cnt==CNT_MAX - 1 ;

   

   always @ (posedge clk or negedge rst_n)begin

       if(!rst_n)begin

           sdr_cmd <= NOP;

       end

       else if(add_cnt && cnt == 1 - 1)begin

           sdr_cmd <= ACT;

       end

       else if(add_cnt && cnt == 3 - 1)begin

           sdr_cmd <= RD;

       end

       else begin

           sdr_cmd <= NOP;

       end

   end

   

   always @(posedge clk or negedge rst_n)begin

       if(!rst_n)begin

           sdr_ba <= 2'd0;

       end

       else begin

           sdr_ba <= ba;

       end

   end

   

   always @(posedge clk or negedge rst_n)begin

       if(!rst_n)begin

           sdr_a <= 13'd0;

       end

       else if(add_cnt && cnt == 1 - 1)begin

           sdr_a <= row;

       end

       else if(add_cnt && cnt == 3 - 1)begin

           sdr_a <= {2'd0, 1'b1, col};

       end

       else begin

           sdr_a <= 13'd0;

       end

   end

   

   always @(posedge clk or negedge rst_n)begin

       if(!rst_n)begin

           local_q <= 64'd0;

       end

       else if(add_cnt && cnt == 9 - 1)begin

           local_q[15:0] <= temp1;

       end

       else if(add_cnt && cnt == 10 - 1)begin

           local_q[31:16] <= temp1;

       end

       else if(add_cnt && cnt == 11 - 1)begin

           local_q[47:32] <= temp1;

       end

       else if(add_cnt && cnt == 12 - 1)begin

           local_q[63:48] <= temp1;

       end

       else begin

           local_q <= local_q;

       end

   end

   

   always @(posedge clk or negedge rst_n)begin

       if(!rst_n)begin

           rd_done <= 1'b0;

       end

       else if(add_cnt && cnt == 1 - 1)begin

           rd_done <= 1'b0;

       end

       else if(add_cnt && cnt == 13 - 1)begin

           rd_done <= 1'b1;

       end

       else begin

           rd_done <= rd_done;

       end

   end


endmodule


(3)仲裁模块


由于加入了写模块和读模块,所以仲裁模块也要做出相应的修改,当收到刷新请求时,即rt_flag信号后,在结束本次读突发或者写突发后,拉高刷新使能(ref_en)信号,当收到写请求且没有刷新请求时,拉高写使能(wr_en)信号,当收到读请求且没有刷新请求和写请求时,拉高读使能(rd_en)信号。


主要代码如下:


module arbitrate(clk, rst_n, rt_flag, rt_en, rt_clear, ref_done,ref_en, init_done, sel_sm, local_rdata_valid,

         local_ready,local_wrreq, local_rdreq, wr_done, wr_en, rd_en, rd_done);

 

         input clk, rst_n;

         input rt_flag;

         output reg rt_en,rt_clear;

         input ref_done;

         output reg ref_en;

         input init_done;

         output reg [1:0]sel_sm;

         output reglocal_rdata_valid;

         output reglocal_ready;

         input local_wrreq,local_rdreq;

         input wr_done;

         output reg wr_en;

         output reg rd_en;

         input rd_done;

        

         localparam SM_INIT =2'd0;

         localparam SM_REF  = 2'd1;

         localparam SM_WR   = 2'd2;

         localparam SM_RD   = 2'd3;

        

         always @(posedge clkor negedge rst_n)begin

                   if(!rst_n)begin

                            rt_en<= 0;

                   end

                   elseif(init_done)begin

                            rt_en<= 1;

                   end

                   else begin

                            rt_en<= rt_en;

                   end

         end

        

         always @(posedge clkor negedge rst_n)begin

                   if(!rst_n)begin

                            rt_clear<= 0;

                   end

                   elseif(ref_en)begin

                            rt_clear<= 1;

                   end

                   elseif(ref_done)begin

                            rt_clear<= 0;

                   end

                   else begin

                            rt_clear<= rt_clear;

                   end

         end

        

         always @(posedge clkor negedge rst_n)begin

                   if(!rst_n)begin

                            ref_en<= 0;

                   end

                   elseif(rt_flag)begin

                            ref_en<= 1;

                   end

                   elseif(ref_done)begin

                            ref_en<= 0;

                   end

                   else begin

                            ref_en<= ref_en;

                   end

         end

        

         always @(posedge clkor negedge rst_n)begin

                   if(!rst_n)begin

                            sel_sm<= SM_INIT;

                   end

                   elseif(!init_done)begin

                            sel_sm<= SM_INIT;

                   end

                   elseif(rt_flag)begin

                            sel_sm<= SM_REF;

                   end

                   elseif(local_wrreq && !rt_flag)begin

                            sel_sm<= SM_WR;

                   end

                   elseif(local_rdreq && !rt_flag)begin

                            sel_sm<= SM_RD;

                   end

                   else begin

                            sel_sm<= sel_sm;

                   end

         end

        

         always @(posedge clkor negedge rst_n)begin

                   if(!rst_n)begin

                            wr_en<= 0;

                   end

                   elseif(local_wrreq && !rt_flag)begin

                            wr_en<= 1;

                   end

                   elseif(wr_done)begin

                            wr_en<= 0;

                   end

                   else begin

                            wr_en<= wr_en;

                   end

         end

        

         always @(posedge clkor negedge rst_n)begin

                   if(!rst_n)begin

                            rd_en<= 0;

                   end

                   elseif(local_rdreq && !local_wrreq && !rt_flag)begin

                            rd_en<= 1;

                   end

                   elseif(rd_done)begin

                            rd_en<= 0;

                   end

                   else begin

                            rd_en<= rd_en;

                   end

         end

        

         always @(posedge clkor negedge rst_n)begin

                   if(!rst_n)begin

                            local_ready<= 1;

                   end

                   elseif(wr_en || rd_en)begin

                            local_ready<= 0;

                   end

                   else begin

                            local_ready<= 1;

                   end

         end

        

         always @(posedge clkor negedge rst_n)begin

                   if(!rst_n)begin

                            local_rdata_valid<= 0;

                   end

                   elseif(rd_done)begin

                            local_rdata_valid<= 1;

                   end

                   else begin

                            local_rdata_valid<= 0;

                   end

         end

 

endmodule



(5)仿真验证


向SDRAM中写入64’h1122334455667788,然后读出来。由图可知SDRAM正确读出了数据。(波形显示的是十六进制,报告是十进制)

null


null

下一篇:没有了
   拓展阅读