明德扬吴老师 发表于 2020-3-20 11:20:17

【干货推荐】基于FPGA的SDRAM控制器设计(三)读写

基于FPGA的SDRAM控制器设计(三)读写作者:小周     本文为明德扬原创及录用文章,转载请注明出处!

      SDRAM控制器设计的主要功能是能对SDRAM进行读写操作,本工程实现了SDRAM的初始化、自动刷新、读、写等功能。
    初始化功能和刷新功能在前一章的分享中已经进行了比较详细的描述,感兴趣的同学可以搜索学习下,这里不再赘述。今天我们主要讨论SDRAM读写的功能以及实现

1.1原理功能
1.1.1 读写突发模式

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

1.1.2 读、写时序图

图表 1写时序图

图表 2读时序图1.1.2 时序图解读

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

1.2 FPGA实现

1.2.1模块架构
注:信号方向请看箭头
1.2.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_dqSDRAM数据总线双向端口,对于写模块是输出,读模块是输入
读模块
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)信号。

1.2.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 local_addr;    input local_data;    output 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 sdr_ba;    output sdr_a;    inout sdr_dq;    output 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 sel_sm;    wire sdr_bus;    wire init_bus;    wire ref_bus;    wire wr_bus;    wire 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


1.2.4模块功能

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

    主要完成写突发,采用线性序列机设计,当检测到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 wr_bus;    inout sdr_dq;    input local_data;    input local_addr;
    parameter CNT_MAX = 8;    parameter NOP = 4'b0111;    parameter ACT = 4'b0011;    parameter WR= 4'b0100;
    reg cnt;    reg sdr_cmd;    reg sdr_ba;    reg sdr_a;    reg temp;    reg out_en;
    wire col;    wire row;    wire 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;      end      else if(add_cnt && cnt == 5 - 1)begin            temp <= local_data;      end      else if(add_cnt && cnt == 6 - 1)begin            temp <= local_data;      end      else if(add_cnt && cnt == 7 - 1)begin            temp <= local_data;      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


Ø 读模块

    读模块主要完成读突发,对于读模块来说,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 rd_bus;    input sdr_dq;    output reg local_q;    input local_addr;
    parameter CNT_MAX = 14;    parameter NOP = 4'b0111;    parameter ACT = 4'b0011;    parameter RD= 4'b0101;

    reg cnt;    reg sdr_cmd;    reg sdr_ba;    reg sdr_a;    reg temp0, temp1;
    wire col;    wire row;    wire 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 <= temp1;      end      else if(add_cnt && cnt == 10 - 1)begin            local_q <= temp1;      end      else if(add_cnt && cnt == 11 - 1)begin            local_q <= temp1;      end      else if(add_cnt && cnt == 12 - 1)begin            local_q <= 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


Ø 仲裁模块

   由于加入了写模块和读模块,所以仲裁模块也要做出相应的修改,当收到刷新请求时,即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 sel_sm;      output reg local_rdata_valid;       output reg local_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 clk or negedge rst_n)begin            if(!rst_n)begin                  rt_en <= 0;            end            else if(init_done)begin                  rt_en <= 1;            end            else begin                  rt_en <= rt_en;            end      end            always @(posedge clk or negedge rst_n)begin            if(!rst_n)begin                  rt_clear <= 0;            end            else if(ref_en)begin                  rt_clear <= 1;            end            else if(ref_done)begin                  rt_clear <= 0;            end            else begin                  rt_clear <= rt_clear;            end      end            always @(posedge clk or negedge rst_n)begin            if(!rst_n)begin                  ref_en <= 0;            end            else if(rt_flag)begin                   ref_en <= 1;            end            else if(ref_done)begin                  ref_en <= 0;            end            else begin                  ref_en <= ref_en;            end      end            always @(posedge clk or negedge rst_n)begin            if(!rst_n)begin                  sel_sm <= SM_INIT;            end            else if(!init_done)begin                   sel_sm <= SM_INIT;            end            else if(rt_flag)begin                  sel_sm <= SM_REF;            end            else if(local_wrreq && !rt_flag)begin                  sel_sm <= SM_WR;            end            else if(local_rdreq && !rt_flag)begin                  sel_sm <= SM_RD;            end            else begin                  sel_sm <= sel_sm;            end      end            always @(posedge clk or negedge rst_n)begin            if(!rst_n)begin                  wr_en <= 0;            end            else if(local_wrreq && !rt_flag)begin                   wr_en <= 1;            end            else if(wr_done)begin                  wr_en <= 0;            end            else begin                  wr_en <= wr_en;            end      end            always @(posedge clk or negedge rst_n)begin            if(!rst_n)begin                  rd_en <= 0;            end            else if(local_rdreq && !local_wrreq && !rt_flag)begin                   rd_en <= 1;            end            else if(rd_done)begin                  rd_en <= 0;            end            else begin                  rd_en <= rd_en;            end      end            always @(posedge clk or negedge rst_n)begin            if(!rst_n)begin                  local_ready <= 1;            end            else if(wr_en || rd_en)begin                   local_ready <= 0;            end            else begin                  local_ready <= 1;            end      end            always @(posedge clk or negedge rst_n)begin            if(!rst_n)begin                  local_rdata_valid <= 0;            end            else if(rd_done)begin                   local_rdata_valid <= 1;            end            else begin                  local_rdata_valid <= 0;            end      end endmodule



仿真验证

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




页: [1]
查看完整版本: 【干货推荐】基于FPGA的SDRAM控制器设计(三)读写