FPGA驱动MT25QL FLASH:SPI模式0下的Verilog状态机设计与时序优化

1. MT25QL FLASH芯片与SPI模式0基础

MT25QL系列FLASH芯片是美光推出的高性能串行闪存,支持标准SPI、双SPI和四线SPI通信。在实际项目中,我们经常需要FPGA通过SPI接口与这类存储芯片交互。SPI协议有四种工作模式,区别在于时钟极性(CPOL)和时钟相位(CPHA)的组合。模式0(CPOL=0, CPHA=0)是最常用的配置,其特点是:

  • 时钟空闲时为低电平
  • 数据在时钟上升沿采样
  • 数据在时钟下降沿更新

这种模式下,FPGA作为主设备需要严格遵循芯片手册中的时序参数。以MT25QL128ABA为例,关键时序包括:

  • tSLCH(片选有效到时钟激活的最小时间):≥5ns
  • tCHSH(时钟停用到片选无效的最小时间):≥5ns
  • tSHSL(连续指令间片选保持时间):≥100ns

2. Verilog状态机设计要点

2.1 状态划分与跳转逻辑

对于FLASH操作,典型的状态机应包含以下状态:

parameter IDLE = 4'b0001; // 空闲状态 parameter WR_EN = 4'b0010; // 写使能状态 parameter DELAY = 4'b0100; // 指令间延迟 parameter PP = 4'b1000; // 页编程状态 parameter SE = 4'b1001; // 扇区擦除状态

状态跳转的核心逻辑如下:

  1. 上电后进入IDLE状态
  2. 收到操作触发信号后跳转到WR_EN状态,拉低片选
  3. 完成写使能指令后进入DELAY状态,满足tSHSL时间
  4. 根据操作类型跳转到PP或SE状态
  5. 操作完成后返回IDLE

2.2 时序计数器设计

为精确控制时序,需要设计两级计数器:

reg [4:0] cnt_clk; // 系统时钟计数器(50MHz下32计数=640ns) reg [7:0] cnt_byte; // 字节操作计数器 always @(posedge sys_clk) begin if(state != IDLE) cnt_clk <= (cnt_clk == 31) ? 0 : cnt_clk + 1; else cnt_clk <= 0; end

计数器的典型应用场景:

  • cnt_clk=0: 满足tSLCH时间
  • cnt_clk=1: 指令/数据写入周期
  • cnt_clk=31: 满足tCHSH时间

3. 关键操作实现细节

3.1 写使能(06h)操作

写使能是任何编程/擦除操作的前置步骤,其Verilog实现要点:

always @(posedge sys_clk) begin if(state == WR_EN && cnt_byte == 1) begin if(cnt_clk == 0) mosi <= WR_EN_INST[7]; // 高位先出 else if(cnt_clk[1:0] == 2'b10) mosi <= WR_EN_INST[7 - cnt_bit]; end end

对应的时序控制:

  1. 拉低片选后等待tSLCH(cnt_clk=0)
  2. 在接下来的8个SCK周期发送06h指令
  3. 完成后等待tCHSH(cnt_clk=31)
  4. 拉高片选

3.2 页编程(02h)操作

页编程需要依次发送:

  1. 指令字节02h
  2. 3字节地址(A23-A0)
  3. 最多256字节数据

关键代码段:

always @(posedge sys_clk) begin case(cnt_byte) 5: mosi <= PP_INST[7 - cnt_bit]; // 指令 6: mosi <= ADDR[23:16][7 - cnt_bit]; // 地址高位 7: mosi <= ADDR[15:8][7 - cnt_bit]; 8: mosi <= ADDR[7:0][7 - cnt_bit]; default: if(cnt_byte >= 9) mosi <= DATA[cnt_byte-9][7 - cnt_bit]; endcase end

3.3 扇区擦除(20h)操作

扇区擦除时序与页编程类似,但只需发送指令和3字节地址。特别注意:

  • 典型4KB扇区擦除时间约0.5s
  • 需通过状态轮询(05h)确认操作完成

4. 时序优化技巧

4.1 时钟生成优化

在50MHz系统时钟下,SPI时钟建议采用四分频(12.5MHz):

always @(posedge sys_clk) begin if(cnt_sck == 3) cnt_sck <= 0; else cnt_sck <= cnt_sck + 1; sck <= (cnt_sck == 0) ? 0 : (cnt_sck == 2) ? 1 : sck; end

4.2 流水线优化

对于连续读取操作,可采用预取地址机制:

generate if(OPT_PIPE) begin always @(posedge sys_clk) begin if(!o_wb_stall) next_addr <= current_addr + 1; end end endgenerate

4.3 关键路径优化

通过寄存器打拍改善时序:

always @(posedge sys_clk) begin mosi_dly <= mosi_next; cs_n_dly <= cs_n_next; end assign mosi = mosi_dly; assign cs_n = cs_n_dly;

5. 调试与验证方法

5.1 仿真模型配置

建议在ModelSim中修改FLASH模型参数以加速仿真:

// parameter.v parameter tPP = 100; // 页编程时间(原为3ms) parameter tSE = 200; // 扇区擦除时间(原为0.5s)

5.2 状态机监控

添加调试输出观察状态跳转:

always @(state) begin case(state) IDLE: $display("Enter IDLE"); WR_EN: $display("Enter WR_EN"); // ...其他状态 endcase end

5.3 实际测量要点

使用示波器检查:

  • SCK占空比(目标45%-55%)
  • CS#下降沿到第一个SCK上升沿时间(>5ns)
  • 数据建立/保持时间(参考芯片手册)

在最近的一个工业控制项目中,我们采用这种设计实现了对MT25QL256ABA的可靠操作。实测表明,在12.5MHz时钟频率下,连续写入256字节数据仅需约210μs,比传统GPIO模拟SPI方案快8倍以上。关键是要确保状态机在每个操作阶段都严格遵循芯片时序要求,特别是在跨时钟域操作时做好同步处理。