明德扬论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

微信扫一扫,快捷登录!

查看: 4644|回复: 0

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

[复制链接]
发表于 2020-3-20 11:20:17 | 显示全部楼层 |阅读模式

马上注册,看完整文章,学更多FPGA知识。

您需要 登录 才可以下载或查看,没有帐号?立即注册

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


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

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


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

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


1.1.2 读、写时序图

70.png

图表 1写时序图

71.png

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

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


1.2 FPGA实现

1.2.1模块架构
72.png
注:信号方向请看箭头

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_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)信号。


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 [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


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 [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


&#216; 读模块

    读模块主要完成读突发,对于读模块来说,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


&#216; 仲裁模块

     由于加入了写模块和读模块,所以仲裁模块也要做出相应的修改,当收到刷新请求时,即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 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正确读出了数据。(波形显示的是十六进制,报告是十进制)

73.png
74.png




FPGA视频课程  培训班 FPGA学习资料
吴老师 18022857217(微信同号) Q1241003385
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|小黑屋|MDYBBS ( 粤ICP备16061416号 )

GMT+8, 2024-4-19 22:43 , Processed in 0.069536 second(s), 25 queries .

Powered by Discuz! X3.4

本论坛由广州健飞通信有限公司所有

© 2001-2019 Comsenz Inc.

快速回复 返回顶部 返回列表