1. 项目概述:FPGA与SPI Flash存储控制器的深度整合
在嵌入式系统和FPGA开发中,外部存储器的可靠控制一直是硬件设计的核心挑战之一。我最近完成了一个基于Altera FPGA的SPI Flash控制器项目,通过Verilog实现了对W25Q系列存储器的完整操作支持。这个设计最特别之处在于,它不仅仅是一个简单的SPI控制器,而是整合了UART通信、双FIFO缓冲和时钟管理系统的完整解决方案。
这个项目的实际价值在于:当你的FPGA系统需要存储配置数据、日志信息或固件代码时,W25Qxx系列SPI Flash提供了高性价比的解决方案。但市面上大多数现成IP核要么功能单一,要么不够灵活。我这个设计通过模块化架构,既保证了基础读写功能的稳定性,又为特殊需求预留了扩展空间。在工业控制、物联网终端等场景中,这种设计已经验证了其可靠性。
2. 系统架构设计与核心模块解析
2.1 整体架构设计思路
整个系统采用分层设计理念,从下到上分为物理层、协议层和应用层:
- 物理层:处理SPI信号时序和电气特性
- 协议层:实现W25Qxx的命令集和状态机
- 应用层:提供UART命令接口和缓冲区管理
这种架构的优势在于:
- 各层可以独立优化,比如调整SPI时钟频率不会影响上层逻辑
- 便于移植到不同FPGA平台
- 故障隔离性好,某一层的问题不会扩散到整个系统
注意:在设计SPI控制器时,我特别保留了时钟极性和相位的可配置性。虽然W25Qxx标准要求模式0(CPOL=0, CPHA=0),但预留这个灵活性可以让代码兼容其他SPI设备。
2.2 时钟系统实现细节
时钟管理是FPGA设计中最容易出问题的环节之一。本系统采用24MHz外部晶振输入,通过PLL生成两个时钟域:
- 100MHz主时钟:用于UART和FIFO控制等高速逻辑
- 16MHz辅助时钟:专供SPI控制器使用
PLL配置参数的计算过程:
verilog复制// 输入时钟24MHz
// 输出clk0 = 24MHz × 25 / 6 = 100MHz
// 输出clk1 = 24MHz × 2 / 3 = 16MHz
altpll pll_inst (
.inclk0(clk_24m),
.c0(clk_100m),
.c1(clk_16m),
.locked(pll_locked)
);
实际调试中发现,当SPI时钟超过16MHz时,在长距离布线情况下会出现数据采样错误。因此最终将SPI时钟限定在8MHz以内,这个经验值对PCB布局很有参考意义。
2.3 双FIFO缓冲设计
系统采用两个独立的8KB FIFO实现读写分离:
- 写FIFO:缓存来自UART的待写入数据
- 读FIFO:暂存从Flash读取的数据
这种设计带来了三个明显优势:
- 解决UART和SPI速度不匹配问题(115200bps vs 8Mbps)
- 支持突发传输,提高整体吞吐量
- 简化流量控制,防止数据丢失
FIFO的关键参数配置:
verilog复制// 例化写FIFO
fifo_8k wr_fifo (
.data(uart_rx_data),
.wrreq(uart_rx_valid),
.rdreq(flash_wr_req),
.clock(clk_100m),
.q(flash_data_in),
.full(wr_fifo_full),
.empty(wr_fifo_empty)
);
3. SPI通信控制器的实现技巧
3.1 可配置的SPI状态机
SPI控制器的核心是一个精心设计的状态机,支持六种操作模式:
- 单字节写(用于命令发送)
- 四字节写(地址+命令)
- 多字节写(数据写入)
- 多字节读(数据读取)
- 先写后读(1+1字节)
- 先写四字节后读两字节(用于读取ID)
状态机转换图的关键部分如下:
verilog复制always @(posedge clk_16m or negedge reset_n) begin
if(!reset_n) begin
state <= IDLE;
end else begin
case(state)
IDLE: if(cmd_valid) state <= CMD_DECODE;
CMD_DECODE:
case(cmd_type)
1: state <= WR_1BYTE;
4: state <= WR_4BYTE;
// 其他状态转换...
endcase
// 其他状态处理...
endcase
end
end
3.2 SPI时序的精确控制
为了保证SPI信号的严格时序,我采用了时钟使能技术而非直接分频:
verilog复制// SPI时钟生成逻辑
reg [3:0] clk_div;
reg spi_clk_en;
always @(posedge clk_16m) begin
clk_div <= clk_div + 1;
spi_clk_en <= (clk_div == 4'd0); // 每8个周期产生一次使能
end
assign spi_clk = spi_clk_en & ~spi_clk_phase;
这种方法的优势是:
- 避免使用额外的时钟域
- 精确控制SCK的占空比
- 便于动态调整SPI速度
4. W25Qxx操作命令集的实现
4.1 基本命令封装
所有Flash操作都遵循标准的命令序列:
- 拉低CS片选
- 发送命令码
- 发送地址(视命令而定)
- 传输数据(读/写操作)
- 释放CS
关键命令的实现示例:
verilog复制// 扇区擦除命令
task sector_erase;
input [23:0] addr;
begin
spi_write_1byte(8'h06); // WREN
spi_write_1byte(8'h20); // SE
spi_write_1byte(addr[23:16]);
spi_write_1byte(addr[15:8]);
spi_write_1byte(addr[7:0]);
wait_ready();
end
endtask
4.2 状态轮询与超时处理
Flash操作如编程和擦除需要等待内部操作完成。我实现了两种检测方式:
- 状态寄存器轮询(推荐)
verilog复制task wait_ready;
begin
spi_write_1byte(8'h05); // RDSR
do begin
spi_read_1byte(status);
end while(status[0]); // BUSY位
end
endtask
- 超时保护机制(备用)
verilog复制reg [23:0] timeout;
always @(posedge clk_100m) begin
if(operation_start) begin
timeout <= 24'd10_000_000; // 约100ms @100MHz
end else if(timeout > 0) begin
timeout <= timeout - 1;
if(timeout == 1) operation_timeout <= 1;
end
end
5. UART命令协议设计
5.1 帧结构定义
为了提高通信可靠性,设计了包含校验的帧结构:
code复制[前导码1][前导码2][命令码][长度L][地址H][地址L][数据...][校验和]
其中:
- 前导码:0xCA固定值,用于帧同步
- 校验和:所有数据字节的累加和取反
5.2 典型命令解析
以扇区擦除命令为例:
code复制CA 20 00 12 34 00 CB
解释:
CA - 前导码
20 - 擦除命令
00 - 长度(无数据)
12 34 00 - 扇区地址(00123400h)
CB - 校验和(0xCA+0x20+0x00+0x12+0x34+0x00=0x130, 取反得0xCFF, 取低字节0xCB)
UART接收状态机的关键部分:
verilog复制case(rx_state)
WAIT_SYNC:
if(rx_byte == 8'hCA) rx_state <= CMD_CODE;
CMD_CODE:
begin
cmd_code <= rx_byte;
rx_state <= LENGTH;
end
// 其他状态...
endcase
6. 调试经验与性能优化
6.1 常见问题排查指南
在实际调试中遇到的典型问题及解决方案:
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| SPI无响应 | 1. CS信号接反 2. 时钟极性错误 |
1. 检查PCB布线 2. 确认CPOL/CPHA设置 |
| 写入失败 | 1. 未发送WREN 2. 保护位未解除 |
1. 检查命令序列 2. 读取状态寄存器 |
| 数据错位 | FIFO读写时钟不同步 | 添加跨时钟域同步器 |
6.2 性能优化技巧
通过以下手段将系统吞吐量提升了3倍:
- 流水线操作:在SPI传输当前页时,UART已经开始接收下一页数据
- 批量操作:将多个小写入合并为大页写入
- 预取技术:读取时提前获取下一扇区数据
实测性能数据:
- 连续写入速度:从56KB/s提升到182KB/s
- 连续读取速度:从128KB/s提升到412KB/s
7. 工程移植与适配建议
7.1 跨平台移植要点
将工程从Altera Quartus移植到Xilinx ISE时需要注意:
- PLL配置差异:Altera的altpll与Xilinx的DCM/MMCM语法不同
- FIFO实现:ISE需要使用Core Generator生成的FIFO核
- 时序约束:ISE的UCF约束文件语法与Quartus的SDC不同
7.2 资源占用评估
在Cyclone IV EP4CE10上的资源使用情况:
- 逻辑单元:2,103/10,320 (20%)
- 存储器位:36,864/423,936 (9%)
- PLL:1/2 (50%)
这个实现相比纯软核方案节省了约35%的逻辑资源,主要得益于硬核PLL和存储器的使用。
8. 扩展功能与未来改进
目前的系统已经支持W25Q系列的基本操作,但还可以进一步扩展:
- 添加QSPI支持:利用四线模式提高传输速率
- 实现磨损均衡:延长Flash使用寿命
- 增加加密功能:保护存储数据安全
- 支持XIP(就地执行):直接从Flash运行代码
一个正在开发中的改进是双Bank切换功能,可以在写入一个Bank时从另一个Bank读取,实现真正的零等待操作。初步测试显示这将把系统响应时间缩短60%以上。