目录
以下内容学习自开源骚客教程: 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_req、wr_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_cnt 和 flag_pre_end 没有发挥任何作用)。
208420ns 时刻,完成刷新后重新进入写状态,此时波形与 205000ns 时刻类似。
3.4 写完一行需要换行
215720ns 时刻,sd_row_end 信号拉高,使状态机跳转到 S_PRE,同时 row_addr 自加 1。而在 S_PRE 下没有显著的信号可以作为状态跳转的判断依据,此时 break_cnt 和 flag_pre_end 才开始起作用,计数到一定程度后 “强制” 状态机跳转至 S_ACT。
后面刚好又出现了一次刷新请求,现在已经清楚出现刷新请求时能够正常处理,这里也不重复赘述。
3.5 写完两行数据
226840ns 时刻,第二个 sd_row_end 高电位信号出现,之后就是一系列收尾处理,如果前面的都能理解,那么这个自然也不成问题。
PS:现在回过头去看之前的时序设计,大概只有 break_cnt 和 flag_pre_end 这两个信号的定义出现了偏差吧。原本以为这俩在每次 S_PRE 的时候都会产生作用,而非 “换行退出状态机写状态” 的唯一标志,结果最后调试把它们调成了唯一标志。