FPGA学习历程(五):SDRAM 控制器(写)


以下内容学习自开源骚客教程: SDRAM那些事儿第一季。
为什么写模块要单独开一篇博客?博主原计划写、读和进一步完善 SDRAM 控制器是放在一起的,结果写完写模块的时候发现目前的篇幅甚至已经超过一般情况下的博客长度了 Orz… SDRAM 的主要控制也就是读和写了,写操作理解以后读操作镜像一下就差不多了。

一、模块时序图与状态机(写入两行数据)

  这里的写时序图是不使用 Auto Precharge 的时序图,因为 WRITEA(Write with Auto Precharge) 在完成一次写操作之后会自动退出写状态,要想继续写入数据必须重新进行行激活、指定写入地址,无法连续写入数据,所以一般情况下使用 WRITE(Write without Auto Precharge),写入数据的速率会比 WRITEA 稍快一些。
在这里插入图片描述
  接下来是实际设计中的写模块内部状态机设计图:
在这里插入图片描述

  • IDLE 状态是起始状态,如果接收到 Wr_trig(写触发)信号则跳转到 REQ 状态;
  • REQ 状态是写请求状态,此时向仲裁模块发送写请求信号,如果接收到仲裁模块发出的 Wr_en(写使能)信号则跳转到 ACTIVE 状态;
  • ACTIVE 状态是行激活状态,提供 ACTIVE 命令,给完命令以后使用 Flag_act_end 信号作为标志,跳转到 WRITE_S 状态;
  • WRITE_S 状态是写状态,退出写状态一共有三种情况:一是数据已经写完(data_end 信号标识),二是 SDRAM 需要进行刷新(Ref_req 信号标识,加入 burst_end 信号标识保证至少要写完当前的 burst 再退出写状态),三是数据未写完,需要激活下一行继续写(sd_row_end 信号标识);
  • BREAK 状态是退出写状态后的刷新状态,在刷新完成后根据退出写状态的原因进行状态恢复或者复位。

  最后是实际设计中的写模块内部时序图:
在这里插入图片描述

  • Wr_trig:写触发信号,由外部模块提供;
  • Flag_wr:收到写触发信号后,模块进入写状态的标志,完成写操作后拉低;
  • S_PRE 状态下由于 sd_row_end 导致的写状态退出情况下,Flag_wr_end 实际上并不需要拉高(因为要换行继续写)。
    在这里插入图片描述
  • act_cnt 用来进行行激活状态计时,溢出则说明行激活结束;
  • burst_cnt 用来对 burst 的数据个数进行计数,配合 CMD_WR 与 col_cnt 列地址计数器使用,即每写入 4 个数据就重新给一次写命令与列地址
  • col_cnt 设定为 7bit 的寄存器,这样就可以与 burst_cnt 拼接成 9bit 的 col_addr,但是考虑到给出写命令时 burst_cnt 的值已经为 1,所以并非直接使用 burst_cnt,而是使用其延拍 burst_cnt_t 来与 col_cnt 进行拼接;
  • break_cnt 功能与 act_cnt 类似,用来进行预充电状态的计时。

二、模块源码

2.1 sdram_write.v

  首先是 sdram_write.v 的源码:

// sdram_write.v 源码
module sdram_write
(
    // system signals
    input 				    sclk	    ,       // 板载系统时钟 50MHz
	input                   s_rst_n     ,       // 复位信号,低电平有效
    
    // communicate with ARBIT
    input                   wr_en       ,
    output  wire            wr_req      ,
    output  reg             flag_wr_end ,
    
    // others
    input                   ref_req     ,
    input                   wr_trig     ,
    output  reg [3:0]       wr_cmd      ,
    output  reg [12:0]      wr_addr     ,
    output  wire [1:0]      bank_addr   ,
    output  reg [15:0]      wr_data
);

/**************************************************************************/
/***************** Define Parameter and Internal Signals ******************/
/**************************************************************************/
// 状态定义
localparam      S_IDLE  =   5'b0_0001       ;
localparam      S_REQ   =   5'b0_0010       ;
localparam      S_ACT   =   5'b0_0100       ;
localparam      S_WR    =   5'b0_1000       ;
localparam      S_PRE   =   5'b1_0000       ;

// SDRAM Command
localparam                  CMD_NOP         =   4'b0111 ;
localparam                  CMD_PRE         =   4'b0010 ;
localparam                  CMD_AREF        =   4'b0001 ;
localparam                  CMD_ACT         =   4'b0011 ;
localparam                  CMD_WR          =   4'b0100 ;

reg                         flag_wr         ;
reg     [4:0]               state           ;

// 大框架信号定义
reg                         flag_act_end    ;
reg                         flag_pre_end    ;
reg                         sd_row_end      ;
reg     [1:0]               burst_cnt       ;
reg     [1:0]               burst_cnt_t     ;
reg                         wr_data_end     ;

// 写命令产生信号定义
reg     [3:0]               act_cnt         ;
reg     [3:0]               break_cnt       ;
reg     [6:0]               col_cnt         ;
reg     [12:0]              row_addr        ;
wire    [8:0]               col_addr        ;

/**************************************************************************/
/******************************* Main Code ********************************/
/**************************************************************************/
// flag_wr:外部提供写触发,且本身并不在模块写状态时进入模块写状态,数据写完时退出模块写状态
always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        flag_wr <= 1'b0;
    else if(wr_trig == 1'b1 && flag_wr == 1'b0)
        flag_wr <= 1'b1;
    else if(wr_data_end == 1'b1)
        flag_wr <= 1'b0;
end

// burst_cnt:状态机写状态下才进行自加,2 位位宽(一个 CLK 对应一个数据)
always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        burst_cnt <= 'd0;
    else if(state == S_WR)
        burst_cnt <= burst_cnt + 1'b1;
    else
        burst_cnt <= 'd0;
end

// burst_cnt_t
always  @(posedge sclk)
begin
    burst_cnt_t <= burst_cnt;
end

always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        state <= S_IDLE;
    else
        case(state)
            S_IDLE:
                if(wr_trig == 1'b1)
                    state <= S_REQ;
                else
                    state <= S_IDLE;
            S_REQ:
                if(wr_en == 1'b1)
                    state <= S_ACT;
                else
                    state <= S_REQ;
            S_ACT:
                if(flag_act_end == 1'b1)
                    state <= S_WR;
                else
                    state <= S_ACT;
            S_WR:
                if(wr_data_end == 1'b1)     // 数据写完了
                    state <= S_PRE;
                else if(ref_req == 1'b1 && burst_cnt == 'd3 && flag_wr == 1'b1)   // 模块写状态下需要进行刷新,且已经写完了当前批次的 burst
                    state <= S_PRE;
                else if(sd_row_end == 1'b1 && flag_wr == 1'b1)      // 写完一行了但是还在模块写状态(数据没写完)
                    state <= S_PRE;
            S_PRE:
                if(ref_req == 1'b1 && flag_wr == 1'b1)     // 这里可以继续使用 ref_req 信号来判断的原因是顶层仲裁模块的状态还没有进行切换,刷新请求就会一直保持
                    state <= S_REQ;
                    /* 这个位置的跳转条件不够严谨,且此处状态机设计与写模块内部时序图设计实际上存在相悖之处。
                    ** 状态机下从 S_PRE 跳转到 S_ACT 的条件是 sd_row_end 信号,而在时序图中 sd_row_end 信
                    ** 号在从 S_WR 跳出的时刻就动作了,所以不能作为这里的直接判断依据。而 flag_pre_end 信号
                    ** 只是 S_PRE 结束的标志信号而已,并非是 “换行退出状态机写状态” 的唯一标志。
                    */
                else if(flag_pre_end == 1'b1 && flag_wr == 1'b1)
                    state <= S_ACT;
                else if(wr_data_end == 1'b1)
                    state <= S_IDLE;
            default:
                state <= S_IDLE;
        endcase
end

// wr_cmd:只在各状态的一开始才给命令
always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        wr_cmd <= CMD_NOP;
    else
        case(state)
            S_ACT:
                if(act_cnt == 'd0)
                    wr_cmd <= CMD_ACT;
                else
                    wr_cmd <= CMD_NOP;
            S_WR:
                if(burst_cnt == 'd0)
                    wr_cmd <= CMD_WR;
                else
                    wr_cmd <= CMD_NOP;
            S_PRE:
                if(break_cnt == 'd0)
                    wr_cmd <= CMD_PRE;
                else
                    wr_cmd <= CMD_NOP;
            default:
                wr_cmd <= CMD_NOP;
        endcase
end

// wr_addr:S_ACT 状态给行地址,S_WR 状态给列地址
always  @(*)
begin
    case(state)
        S_ACT:
            if(act_cnt == 'd0)
                wr_addr <= row_addr;
        S_WR:
            wr_addr <= {4'b0000,col_addr};
        S_PRE:
            if(break_cnt == 'd0)
                wr_addr <= 13'b0_0100_0000_0000;
    endcase
end

// flag_act_end:在 S_ACT 状态计时 3 个 CLK
always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        flag_act_end <= 1'b0;
    else if(act_cnt == 'd3)
        flag_act_end <= 1'b1;
    else
        flag_act_end <= 1'b0;
end

// act_cnt
always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        act_cnt <= 'd0;
    else if(state == S_ACT)
        act_cnt <= act_cnt + 1'b1;
    else
        act_cnt <= 'd0;
end

// flag_pre_end:在 S_PRE 状态计时 3 个 CLK
always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        flag_pre_end <= 1'b0;
    else if(break_cnt == 'd3)
        flag_pre_end <= 1'b1;
    else
        flag_pre_end <= 1'b0;
end

// flag_wr_end
always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        flag_wr_end <= 1'b0;
    else if((state == S_PRE && ref_req == 1'b1) ||      // 仿真调试后的判断条件
            (state == S_PRE && wr_data_end == 1'b1))      
        flag_wr_end <= 1'b1;
    else
        flag_wr_end <= 1'b0;
end

// break_cnt
always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        break_cnt <= 'd0;
    else if(state == S_PRE)
        break_cnt <= break_cnt + 1'b1;
    else
        break_cnt <= 'd0;
end

// wr_data_end
always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        wr_data_end <= 1'b0;
    else if(row_addr == 'd2 && col_addr == 'd511)       // 写两行数据特有标志(第二行也写完了才算数据写完)
        wr_data_end <= 1'b1;
    else
        wr_data_end <= 1'b0;
end

// col_cnt:实际上被视为一个四进制计数器的高位(低位是 burst_cnt_t 的 0~3)
always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        col_cnt <= 'd0;
    else if(col_addr == 'd511)
        col_cnt <= 'd0;
    else if(burst_cnt_t == 'd3)
        col_cnt <= col_cnt + 1'b1;
end

// sd_row_end
always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        sd_row_end <= 1'b0;
    else if(col_addr == 'd509)  // 这里提前是因为 col_addr 使用的是 burst_cnt 延拍
        sd_row_end <= 1'b1;
    else
        sd_row_end <= 1'b0;
end

// row_addr
always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        row_addr <= 'd0;
    else if(sd_row_end == 1'b1)
        row_addr <= row_addr + 1'b1;
end

// 测试数据
always  @(*)
begin
    case(burst_cnt_t)
        0:  wr_data <=  'd3;
        1:  wr_data <=  'd5;
        2:  wr_data <=  'd7;
        3:  wr_data <=  'd9;
    endcase
end

assign col_addr     =   {col_cnt,burst_cnt_t};
assign bank_addr    =   2'b00;
assign wr_req       =   state[1];

endmodule 

PS:对于 Kevin 大佬视频中写模块的代码部分,博主实际上在一开始是存在疑问的,具体位置已经在代码中作了批注。但是最终仔细分析完仿真波形后心情复杂…批注还是留下来了。

2.2 sdram_top.v

  接着是 sdram_top.v ,因为同时加入了写模块与刷新模块,执行优先级的问题不得不开始纳入考虑:

// tb_sdram_top.v 源码
module sdram_top
(
    // system signals
    input 				    sclk	    ,       // 板载系统时钟 50MHz
	input                   s_rst_n     ,       // 复位信号,低电平有效
    
    // SDRAM Interfaces
    output  wire            sdram_clk   ,
    output  wire            sdram_cke   ,
    output  wire            sdram_cs_n  ,
    output  wire            sdram_cas_n ,
    output  wire            sdram_ras_n ,
    output  wire            sdram_we_n  ,
    output  wire [1:0]      sdram_bank  ,
    output  reg  [12:0]     sdram_addr  ,
    output  wire [1:0]      sdram_dqm   ,
    inout        [15:0]     sdram_dq    ,
    
    //others
    input                   wr_trig             // 仿真测试用
);

/**************************************************************************/
/***************** Define Parameter and Internal Signals ******************/
/**************************************************************************/
localparam          IDLE    =   5'b0_0001   ;   // 空闲状态
localparam          ARBIT   =   5'b0_0010   ;   // 仲裁状态
localparam          AREF    =   5'b0_0100   ;   // 刷新状态
localparam          WRITE   =   5'b0_1000   ;   // 写状态
localparam          READ    =   5'b1_0000   ;   // 读状态

reg     [3:0]               sd_cmd          ;

// init module
wire                        flag_init_end   ;
wire    [3:0]               init_cmd        ;
wire    [12:0]              init_addr       ;

// 仲裁模块
reg     [4:0]               state           ;

// refresh module
wire                        ref_req         ;   // 刷新请求(刷新模块产生)
wire                        flag_ref_end    ;   // 刷新结束标志(刷新模块产生)
reg                         ref_en          ;   // 刷新使能(仲裁模块产生)
wire    [3:0]               ref_cmd         ;
wire    [12:0]              ref_addr        ;

// write module
reg                         wr_en           ;
wire                        wr_req          ;
wire                        flag_wr_end     ;
wire    [3:0]               wr_cmd          ;
wire    [12:0]              wr_addr         ;
wire    [1:0]               wr_bank_addr    ;
wire    [15:0]              wr_data         ;

/**************************************************************************/
/******************************* Main Code ********************************/
/**************************************************************************/
always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        state <= IDLE;
    else 
        case(state)
            IDLE:
                if(flag_init_end == 1'b1)
                    state <= ARBIT;
                else
                    state <= IDLE;
            ARBIT:
                if(ref_en == 1'b1)          // 优先刷新
                    state <= AREF;
                else if(wr_en == 1'b1)
                    state <= WRITE;
                else
                    state <= ARBIT;
            AREF:
                if(flag_ref_end == 1'b1)
                    state <= ARBIT;
                else
                    state <= AREF;
            WRITE:
                if(flag_wr_end == 1'b1)
                    state <= ARBIT;
                else
                    state <= WRITE;
            default:
                state <= IDLE;
        endcase
end

// ref_en
always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        ref_en <= 1'b0;
    else if(state == ARBIT && ref_req == 1'b1)
        ref_en <= 1'b1;
    else
        ref_en <= 1'b0;
end

// wr_en
always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        wr_en <= 1'b0;
        else if(state == ARBIT && ref_req == 1'b0 && wr_req == 1'b1)    // 只有在不和刷新操作冲突的时候才允许进行写操作
        wr_en <= 1'b1;
    else
        wr_en <= 1'b0;
end

// sd_cmd
always  @(*)
begin
    case(state)
        IDLE:
        begin
            sd_cmd <= init_cmd;
            sdram_addr <=   init_addr;
        end
        AREF:
        begin
            sd_cmd <= ref_cmd;
            sdram_addr <=   ref_addr;
        end
        WRITE:
        begin
            sd_cmd  <=  wr_cmd;
            sdram_addr  <=  wr_addr;
        end
        default:
        begin
            sd_cmd  <= 4'b0111;         // NOP
            sdram_addr  <=  'd0;
        end
    endcase
end

assign  sdram_cke       =   1'b1;
assign  {sdram_cs_n, sdram_ras_n, sdram_cas_n, sdram_we_n} =    sd_cmd;
assign  sdram_dqm       =   2'b00;
assign  sdram_clk       =   ~sclk;      // sdram 命令生成时钟与 sdram 命令采集时钟反向,保证命令采集时命令已经稳定生成
assign  sdram_dq        =   (state == WRITE) ? wr_data : {16{1'bz}};
assign  sdram_bank      =   (state == WRITE) ? wr_bank_addr : 2'b00;

sdram_init sdram_init_inst
(
    // system signals
    .sclk	        (sclk),             // 板载系统时钟 50MHz
	.s_rst_n        (s_rst_n),          // 复位信号,低电平有效
    
    // others
    .cmd_reg        (init_cmd),         // 输出的命令(即 CS、RAS、CAS、WE 这四位)
    .sdram_addr     (init_addr),        // SDRAM 地址
    .flag_init_end  (flag_init_end)     // 初始化结束标志
);

sdram_aref sdram_aref_inst
(
    // system signals
    .sclk	        (sclk),             // 板载系统时钟 50MHz
	.s_rst_n        (s_rst_n),          // 复位信号,低电平有效
    
    // communicate with ARBIT
    .ref_en         (ref_en),
    .ref_req        (ref_req),
    .flag_ref_end   (flag_ref_end),     // 刷新结束标志
    
    // others
    .aref_cmd       (ref_cmd),          // 刷新命令
    .sdram_addr     (ref_addr),
    .flag_init_end  (flag_init_end)
);

sdram_write sdram_write_inst
(
    // system signals
    .sclk	        (sclk),             // 板载系统时钟 50MHz
	.s_rst_n        (s_rst_n),          // 复位信号,低电平有效
    
    // communicate with ARBIT
    .wr_en          (wr_en),
    .wr_req         (wr_req),
    .flag_wr_end    (flag_wr_end),
    
    // others
    .ref_req        (ref_req),
    .wr_trig        (wr_trig),
    .wr_cmd         (wr_cmd),
    .wr_addr        (wr_addr),
    .bank_addr      (wr_bank_addr),
    .wr_data        (wr_data)
);
endmodule 

2.3 sdram_aref.v

  sdram_aref.v 也需要进行修改,将 ref_req 信号改为时序逻辑:

// sdram_aref.v 源码
module sdram_aref
(
    // system signals
    input 				    sclk	        ,       // 板载系统时钟 50MHz
	input                   s_rst_n         ,       // 复位信号,低电平有效
    
    // communicate with ARBIT
    input                   ref_en          ,
    output  reg             ref_req         ,
    output  wire            flag_ref_end    ,       // 刷新结束标志
    
    // others
    output  reg     [3:0]   aref_cmd        ,       // 刷新命令
    output  wire    [12:0]  sdram_addr      ,
    input                   flag_init_end
);

/**************************************************************************/
/***************** Define Parameter and Internal Signals ******************/
/**************************************************************************/
localparam      DELAY_7_8US     =       389     ;       // 64ms 刷新 8192 行,刷新周期为 64 * 1000 / 8192 = 7.8us,转换为 20ns 的时钟周期数约为 390 个

localparam      CMD_AREF        =       4'b0001 ;
localparam      CMD_NOP         =       4'b0111 ;
localparam      CMD_PRE         =       4'b0010 ;

reg [3:0]                   cmd_cnt             ;
reg [8:0]                   ref_cnt             ;       // 刷新时钟周期计数器
reg                         flag_ref            ;       // 刷新标志,表示刷新模块内部正处于刷新阶段

/**************************************************************************/
/******************************* Main Code ********************************/
/**************************************************************************/
// ref_cnt:初始化结束开始计数,计满即清零
always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        ref_cnt <= 'd0;
    else if(ref_cnt >= DELAY_7_8US)
        ref_cnt <= 'd0;
    else if(flag_init_end == 1'b1)
        ref_cnt <= ref_cnt + 1'b1;
end

// flag_ref:刷新结束后清零,仲裁允许刷新时进入刷新状态
always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        flag_ref <= 1'b0;
    else if(flag_ref_end == 1'b1)
        flag_ref <= 1'b0;
    else if(ref_en == 1'b1)
        flag_ref <= 1'b1;
end

// cmd_cnt:刷新状态下保持自加
always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        cmd_cnt <= 'd0;
    else if(flag_ref == 1'b1)
        cmd_cnt <= cmd_cnt + 1'b1;
    else
        cmd_cnt <= 'd0;
end

// aref_cmd
always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        aref_cmd <= CMD_NOP;
    else
        case(cmd_cnt)
            1:          aref_cmd <=  CMD_PRE;
            2:          aref_cmd <=  CMD_AREF;
            default:    aref_cmd <=  CMD_NOP;
        endcase
end

// ref_req:7.8us 的计时周期到了就拉高,除非仲裁允许刷新才拉低
always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        ref_req <= 1'b0;
    else if(ref_en == 1'b1)
        ref_req <= 1'b0;
    else if(ref_cnt >= DELAY_7_8US)
        ref_req <= 1'b1;
end

assign  flag_ref_end    =   (cmd_cnt >= 'd5) ? 1'b1 : 1'b0;
assign  sdram_addr  =   13'b0_0100_0000_0000;

endmodule 

2.4 tb_sdram_top.v

  最后修改 tb_sdram_top.v,在里面加上 wr_trig 信号的模拟:

// tb_sdram_top.v 源码
`timescale 1ns/1ns

module tb_sdram_top;

reg             sclk        ;
reg             s_rst_n     ;

wire            sdram_clk   ;
wire            sdram_cke   ;
wire            sdram_cs_n  ;
wire            sdram_cas_n ;
wire            sdram_ras_n ;
wire            sdram_we_n  ;
wire [1:0]      sdram_bank  ;
wire [12:0]     sdram_addr  ;
wire [1:0]      sdram_dqm   ;
wire [15:0]     sdram_dq    ;

reg             wr_trig     ;

initial
begin
    wr_trig <=  0;
    #205000
    wr_trig <=  1;
    #20
    wr_trig <=  0;
end

initial 
begin
    sclk    =   1;
    s_rst_n <=  0;
    #100
    s_rst_n <=  1;
end

always  #10     sclk    =   ~sclk;

sdram_top sdram_top_inst
(
    // system signals
    .sclk	        (sclk),             // 板载系统时钟 50MHz
	.s_rst_n        (s_rst_n),          // 复位信号,低电平有效
    
    // SDRAM Interfaces
    .sdram_clk      (sdram_clk),
    .sdram_cke      (sdram_cke),
    .sdram_cs_n     (sdram_cs_n),
    .sdram_cas_n    (sdram_cas_n),
    .sdram_ras_n    (sdram_ras_n),
    .sdram_we_n     (sdram_we_n),
    .sdram_bank     (sdram_bank),
    .sdram_addr     (sdram_addr),
    .sdram_dqm      (sdram_dqm),
    .sdram_dq       (sdram_dq),
    
    //ohters
    .wr_trig        (wr_trig)
);

defparam	sdram_model_plus_inst.addr_bits = 13;					// 13
defparam    sdram_model_plus_inst.data_bits = 16;
defparam	sdram_model_plus_inst.col_bits	= 9;
defparam    sdram_model_plus_inst.mem_sizes = 2*1024*1024;		    // 2M

sdram_model_plus sdram_model_plus_inst
(
    .Dq              (sdram_dq), 
    .Addr            (sdram_addr), 
    .Ba              (sdram_bank), 
    .Clk             (sdram_clk), 
    .Cke             (sdram_cke), 
    .Cs_n            (sdram_cs_n), 
    .Ras_n           (sdram_ras_n), 
    .Cas_n           (sdram_cas_n), 
    .We_n            (sdram_we_n), 
    .Dqm             (sdram_dqm),
    .Debug           (1'b1)
);

endmodule 

三、Modelsim仿真

3.1 run.do 脚本

  这次新建完工程后不再手动添加文件、编译、添加波形了,用一个 run.do 脚本来跑:

// run.do 源码
# 在当前路径下创建 work 文件夹
vlib work

# 编译下列文件
vlog	"./tb_sdram_top.v"
vlog	"./sdram_model_plus.v"
vlog	"../../rtl/sdram/*.v"

# 优化部分参数(-voptargs=+acc),链接到默认的 work 库,启动仿真顶层测试逻辑库(work)里面的 tb_sdram_top 文件
vsim	-voptargs=+acc work.tb_sdram_top

# Set the window types
view wave
view structure
view signals

# 添加分割线,在不同的信号之间进行分割,语法格式是:add wave -divider {分割线的名字}
add wave -divider {sdram_top}
# 添加指定例化模块下的所有信号
add wave tb_sdram_top/sdram_top_inst/*
add wave -divider {sdram_write}
add wave tb_sdram_top/sdram_top_inst/sdram_write_inst/*
add wave -divider {sdram_model}
add wave tb_sdram_top/sdram_model_plus_inst/*
# 清屏
.main clear

run 205us

  之后在 Modelsim 的调试信息栏下输入指令就可以了:

do run.do

  调试信息栏里的东西比较多就不贴了,接下来展开分析一下仿真波形图,刚好和之前对写模块源码的疑问相印证。

3.2 写模块开始工作

注意:时序逻辑驱动的信号并不是拉高或者拉低的瞬间就能立刻起作用的(组合逻辑才是),因为时序逻辑非阻塞赋值的特性,信号的状态变化实际上需要往后推一个 CLK 才能被其他时序逻辑模块识别到而开始发挥作用。
在这里插入图片描述
  205000ns 时刻,wr_trig 触发信号传入写模块,之后 wr_reqwr_en 信号依次响应,状态机的状态由 S_IDLE(00001)跳转至 S_REQ(00010),再跳转至 S_ACT(00100),此时 act_cnt 开始计数,wr_cmd 给出了 active 命令(0011),wr_addr 此时为 0,即指定地址为 BANK0 的第 0 行。
  205180ns 时刻,flag_act_end 拉低信号被状态机检测到,状态机由 S_ACT 跳转至 S_WR(01000),开始依次写入数据。

3.3 写数据时出现刷新请求

在这里插入图片描述
  208120ns 时刻,出现 ref_reg 信号打断数据写入,但是当状态机识别到时,当前的 burst_cnt 还没有计满,所以继续写直到 burst_cnt 计到 3 才跳转到 S_PRE(10000),之后检测到 ref_req 为高电平,因此跳转到 S_REQ 并拉高 flag_wr_end 使得顶层的仲裁模块能从写状态回退到仲裁状态去处理刷新请求(在此期间 break_cntflag_pre_end 没有发挥任何作用)。
  208420ns 时刻,完成刷新后重新进入写状态,此时波形与 205000ns 时刻类似。

3.4 写完一行需要换行

在这里插入图片描述
  215720ns 时刻,sd_row_end 信号拉高,使状态机跳转到 S_PRE,同时 row_addr 自加 1。而在 S_PRE 下没有显著的信号可以作为状态跳转的判断依据,此时 break_cntflag_pre_end 才开始起作用,计数到一定程度后 “强制” 状态机跳转至 S_ACT。
  后面刚好又出现了一次刷新请求,现在已经清楚出现刷新请求时能够正常处理,这里也不重复赘述。

3.5 写完两行数据

在这里插入图片描述
  226840ns 时刻,第二个 sd_row_end 高电位信号出现,之后就是一系列收尾处理,如果前面的都能理解,那么这个自然也不成问题。

PS:现在回过头去看之前的时序设计,大概只有 break_cntflag_pre_end 这两个信号的定义出现了偏差吧。原本以为这俩在每次 S_PRE 的时候都会产生作用,而非 “换行退出状态机写状态” 的唯一标志,结果最后调试把它们调成了唯一标志。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值