FPGA学习笔记——SDRAM驱动实现

SDRAM驱动接口

SDRAM驱动模块接口大概设计为这种形式,用户侧发送过来操作的指令(写/读),地址(包括bank地址和行列地址),长度以及握手信号,驱动模块解析完成后发送到芯片引脚上。其中读写数据用的同一个inout接口。写数据是突发写模式,字节连续被写入,所以是数据流的形式,就不用ready信号,只用一个last信号指示数据流发送完成。

参数方面设置了几个重要的时序参数,比如TRCD,CL,TWR等。行数为4096。

module SDRAM_Drive#(
    parameter       P_SDRAM_ROW_NUM = 4096  ,
                    P_TIME_INIT     = 20000 ,//10nS
                    P_TIME_TRP      = 2     ,//10ns  预充电时间
                    P_TIME_TRC      = 6     ,//10ns  刷新时间间隔
                    P_TIME_TRSC     = 2     ,//cycle
                    P_TIME_TRCD     = 2     ,
                    P_TIME_CL       = 2     ,
                    P_TIME_TWR      = 2     
)(
    input           i_clk                   ,
    input           i_rst                   ,

    input   [1 :0]  i_operation_cmd         ,//操作指令 1-读 2-写
    input   [23:0]  i_operation_addr        ,//操作地址-SDRAM中的存储地址 bank+row+col = 2+13+12
    input   [9 :0]  i_operation_len         ,//操作长度-读写数据的长度
    input           i_operation_valid       ,//操作有效信号-握手
    output          o_operation_ready       ,//操作准备信号-准备

    input   [15:0]  i_write_data            ,//写数据
    input           i_write_last            ,//写数据last
    input           i_write_valid           ,//写数据有效

    output  [15:0]  o_read_data             ,//读数据
    output          o_read_valid            ,//读数据有效

    output          o_sdram_cke             ,//sdram的时钟有效
    output          o_sdram_cs_n            ,//sdram的片选
    output          o_sdram_ras_n           ,//sdram的行有效
    output          o_sdram_cas_n           ,//sdram的列有效
    output          o_sdram_we_n            ,//sdram的写使能
    output  [12:0]  o_sdram_addr            ,//sdram的操作地址
    output  [1 :0]  o_sdram_ba              ,//sdram的Bank地址
    inout   [15:0]  io_sdram_data           ,//sdram的读写数据
    output  [1 :0]  o_sdram_dqm
);
	wire 			w_operation_active  ;//握手信号
assign w_operation_active   = i_operation_valid & o_operation_ready;

状态机参数设置

状态转移图:
在这里插入图片描述
状态机跳转:

always@(*)
begin
    case(r_st_current)
        P_INIT_WAIT     : r_st_next  = r_st_cnt == P_TIME_INIT          ? P_ALL_PRE     : P_INIT_WAIT   ;
        P_ALL_PRE       : r_st_next  = r_st_cnt == P_TIME_TRP           ? P_AREFRESH    : P_ALL_PRE     ;
        P_AREFRESH      : r_st_next  = r_st_cnt == P_TIME_TRC           ? P_AR_CHECK    : P_AREFRESH    ;
        P_AR_CHECK      : r_st_next  = r_ar_cnt == 7                    ? P_MR_SET      : P_AREFRESH    ;
        P_MR_SET        : r_st_next  = r_st_cnt == P_TIME_TRSC          ? P_IDLE        : P_MR_SET      ;
        P_IDLE          : r_st_next  = r_ap_req_cnt == P_AP_CNT         ? P_IDLE_AR     : 
                                       w_operation_active               ? P_ROW_ACT : P_IDLE;
        P_IDLE_AR       : r_st_next  = r_st_cnt == P_TIME_TRC           ? P_IDLE        : P_IDLE_AR     ;
        P_ROW_ACT       : r_st_next  = r_st_cnt == P_TIME_TRCD          ? 
                                       ri_operation_cmd == 'd1          ? P_READ_CMD    : P_WRITE_CMD
                                       : P_ROW_ACT     ;
        P_READ_CMD      : r_st_next  = r_st_cnt == P_TIME_CL            ? P_READ_DATA   : P_READ_CMD    ;
        P_READ_DATA     : r_st_next  = r_st_cnt == ri_operation_len - 1 ? P_BURST_STOP  : P_READ_DATA   ;
        P_WRITE_CMD     : r_st_next  = P_WRITE_DATA;
        P_WRITE_DATA    : r_st_next  = r_st_cnt == ri_operation_len     ? P_BURST_STOP  : P_WRITE_DATA  ;
        P_WRITE_WAIT    : r_st_next  = r_st_cnt == P_TIME_TWR + 1       ? P_PER_WAIT    : P_WRITE_WAIT  ;
        P_BURST_STOP    : r_st_next  = r_st_cnt == P_TIME_CL            ? P_PER_WAIT    : P_BURST_STOP  ;
        P_PER_WAIT      : r_st_next  = r_st_cnt == P_TIME_TRP + 5       ? P_IDLE        : P_PER_WAIT    ;
        default         : r_st_next  = 'd0;
    endcase
end

always@(posedge i_clk,posedge i_rst)
begin
    if(i_rst)
        r_st_cnt <= 'd0;
    else if(r_st_current != r_st_next)
        r_st_cnt <= 'd0;
    else        
        r_st_cnt <= r_st_cnt + 1;
end

always@(posedge i_clk,posedge i_rst)
begin
    if(i_rst)
        r_ar_cnt <= 'd0;
    else if(r_st_current == P_AR_CHECK)
        r_ar_cnt <= r_ar_cnt + 1;
    else  
        r_ar_cnt <= r_ar_cnt;
end

r_st_cnt为状态计数器,主要是用来计数比如静默200us,预充电时间,自动刷新周期这些,所以状态跳转时清零。r_ar_cnt为刷新计数器,用来计数刷新次数是否到了8次。

r_ap_req_cnt为刷新请求计数器,它有什么作用呢?因为目前SDRAM的存储电容的刷新周期最大为64ms,由于自刷新指令一次只能刷新一行,因此我们要在64ms内刷新完所有行。SDRAM的行数为4096,那么就需要在64ms内刷新4096次,我们在64ms内均匀的刷新4096行,那么自刷新的指令间隔:64ms/4096 = 15.6us。所以这个计数器计数到15.6us后控制状态机跳转到刷新状态。

localparam          P_AP_CNT        = (6400000/P_SDRAM_ROW_NUM) - 600;//15.6US - 6US = 13.6US 10nS
always@(posedge i_clk,posedge i_rst)
begin
    if(i_rst)   
        r_ap_req_cnt <= 'd0;
    else if(r_st_current == P_IDLE_AR)   
        r_ap_req_cnt <= 'd0;
    else if(r_st_current > P_MR_SET && r_ap_req_cnt < P_AP_CNT)
        r_ap_req_cnt <= r_ap_req_cnt + 1;
    else  
        r_ap_req_cnt <= r_ap_req_cnt;
end

值得注意的是,如果计数到15.6us时,此时SDRAM正工作在写或者读状态,那么就跳不到刷新状态。解决方法是将计数器计数值降低,减去读写最大耗费的时间。比如假设此时工作在读状态,读完数据最多需要100ns,那就把值设置为15.6us - 100ns,这样即使等待读写完成,也不会超出最大刷新周期。

指令和地址控制

localparam          P_CMD_INIT      = 5'b01111,
                    P_CMD_NOP       = 5'b10111,
                    P_CMD_RACT      = 5'b10011,
                    P_CMD_READ      = 5'b10101,//A10 = 1;
                    P_CMD_WRITE     = 5'b10100,//A10 = 1;
                    P_CMD_PER       = 5'b10010,//A10 = 1:ALL;
                    P_CMD_AP        = 5'b10001,
                    P_CMD_MR        = 5'b10000,
                    P_CMD_B_STOP    = 5'b10110;

assign o_sdram_cke          = r_sdram_cmd[4]        ;
assign o_sdram_cs_n         = r_sdram_cmd[3]        ;
assign o_sdram_ras_n        = r_sdram_cmd[2]        ;
assign o_sdram_cas_n        = r_sdram_cmd[1]        ;
assign o_sdram_we_n         = r_sdram_cmd[0]        ;
assign o_sdram_addr         = ro_sdram_addr[12: 0]  ;
assign o_sdram_ba           = ro_sdram_addr[14:13]  ;

always@(posedge i_clk,posedge i_rst)
begin
    if(i_rst)
        r_sdram_cmd <= 'd0;
    else if(r_st_current == P_INIT_WAIT)
        r_sdram_cmd <= P_CMD_INIT;
    else if(r_st_current == P_ALL_PRE && r_st_cnt == 0)
        r_sdram_cmd <= P_CMD_PER;
    else if((r_st_current == P_AREFRESH || r_st_current == P_IDLE_AR) && r_st_cnt == 0)
        r_sdram_cmd <= P_CMD_AP;
    else if(r_st_current == P_ROW_ACT && r_st_cnt == 0)
        r_sdram_cmd <= P_CMD_RACT;
    else if(r_st_current == P_READ_CMD && r_st_cnt == 0)
        r_sdram_cmd <= P_CMD_READ;
    else if(r_st_current == P_WRITE_CMD && r_st_cnt == 0)
        r_sdram_cmd <= P_CMD_WRITE;
    else if(r_st_current == P_MR_SET && r_st_cnt == 0)
        r_sdram_cmd <= P_CMD_MR;
    else if(r_st_current == P_BURST_STOP && r_st_cnt == 0)
        r_sdram_cmd <= P_CMD_B_STOP;
    else  
        r_sdram_cmd <= P_CMD_NOP;
end

always@(posedge i_clk,posedge i_rst)
begin
    if(i_rst)
        ro_sdram_addr <= 'd0;
    else if(r_st_current == P_ALL_PRE && r_st_cnt == 0)
        ro_sdram_addr <= {4'd0,1'b1,10'd0};
    else if(r_st_current == P_MR_SET && r_st_cnt == 0)
        ro_sdram_addr <= {5'd0,1'b0,2'b00,3'b010,1'b0,3'b111};
    else if(r_st_current == P_ROW_ACT && r_st_cnt == 0)
        ro_sdram_addr <= ri_operation_addr[23:9];
    else if((r_st_current == P_READ_CMD || r_st_current == P_WRITE_CMD) && r_st_cnt == 0)
        ro_sdram_addr <= {ri_operation_addr[23:22],2'd0,1'b1,1'b0,ri_operation_addr[8:0]};
    else  
        ro_sdram_addr <= 15'h7fff;
end

大概思路是因为DRAM的指令主要由CKE、CS、RAS、CAS、WE、A10这几个引脚控制,所以可以参数化设置一下,将不同状态下产生的指令存到r_sdram_cmd这个寄存器中,然后把这个寄存器的不同位接到CKE、CS、RAS、CAS、WE、A10这几个引脚上。

读写数据

读写数据线用了三态门控制。

assign io_sdram_data        = r_sdata_wH_rL ? ri_write_data_3d : {16{1'bz}}    ;
assign w_sdata_r            = r_sdata_wH_rL ? 'd0       : io_sdram_data ;

always@(posedge i_clk,posedge i_rst)
begin
    if(i_rst)
        r_sdata_wH_rL <= 'd0;
    else if(r_st_current == P_WRITE_DATA && r_st_cnt == ri_operation_len - 1)
        r_sdata_wH_rL <= 'd0;
    else if(r_st_current == P_WRITE_CMD)
        r_sdata_wH_rL <= 'd1;
    else  
        r_sdata_wH_rL <= r_sdata_wH_rL;
end

大概实现思路就是这些,剩下的就是对几个寄存器处理,按照时序调试,完整代码篇幅过长就不贴了。SDRAM驱动不难,主要是其中有很多时间要求比如TRCD,TWR等需要控制一下。

Testbench

我们用两个task按照最大边界和最小边界以及中间情况取三种情况:

  1. 做一个读写512Byte数据的仿真
  2. 做一个读写1Byte数据的仿真
  3. 做一个读写8Byte数据的仿真
initial begin
    ri_operation_cmd    = 'd0;
    ri_operation_addr   = 'd0;
    ri_operation_len    = 'd0;
    ri_operation_valid  = 'd0;
    ri_write_data       = 'd0; 
    ri_write_last       = 'd0; 
    ri_write_valid      = 'd0; 
    wait(!rst);
    repeat(10)@(posedge clk);
    /****最大边界****/
    wirte_data(512,10);
    read_data(512 ,10);
    /****最小边界****/
    wirte_data(1,10);
    read_data(1 ,10);
    /****中间情况****/
    wirte_data(8,10);
    read_data (8 ,10);
end

task wirte_data(input [9:0] op_len,input [26:0] op_addr);
begin:wirte_data
    integer i;
    ri_operation_cmd   <= 'd0;
    ri_operation_addr  <= 'd0;
    ri_operation_len   <= 'd0;
    ri_operation_valid <= 'd0;
    @(posedge clk);
    wait(wo_operation_ready);
    ri_operation_cmd   <= 'd2;
    ri_operation_addr  <= op_addr;
    ri_operation_len   <= op_len;
    ri_operation_valid <= 'd1;
    @(posedge clk);
    ri_operation_cmd   <= 'd0;
    ri_operation_addr  <= 'd0;
    ri_operation_len   <= 'd0;
    ri_operation_valid <= 'd0;
    for(i = 0;i < op_len;i = i + 1)
    begin
        ri_write_data      <= i;
        if(i == op_len - 1)ri_write_last <= 'd1; else ri_write_last <= 'd0;
        ri_write_valid     <= 'd1;
        @(posedge clk);
    end
    ri_write_data   <= 'd0;
    ri_write_last   <= 'd0;
    ri_write_valid  <= 'd0;
    @(posedge clk);
end
endtask
task read_data(input [9:0] op_len,input [26:0] op_addr);
begin:read
    ri_operation_cmd   <= 'd0;
    ri_operation_addr  <= 'd0;
    ri_operation_len   <= 'd0;
    ri_operation_valid <= 'd0;
    @(posedge clk);
    wait(wo_operation_ready);
    ri_operation_cmd   <= 'd1;
    ri_operation_addr  <= op_addr;
    ri_operation_len   <= op_len;
    ri_operation_valid <= 'd1;
    @(posedge clk);
    ri_operation_cmd   <= 'd0;
    ri_operation_addr  <= 'd0;
    ri_operation_len   <= 'd0;
    ri_operation_valid <= 'd0;
    @(posedge clk);
end
endtask
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值