FPGA实现SPI通信协议:从时序设计到仿真验证的完整指南

发布时间:2026/9/5 6:37:11
FPGA实现SPI通信协议:从时序设计到仿真验证的完整指南 前言做FPGA开发这几年SPI是我用的最多的低速通信接口之一。ADC采样、Flash读写、DAC配置、传感器采集甚至和MCU对接做数据交互SPI几乎无处不在。在FPGA里实现SPI技术上不算复杂但它涉及时序设计、状态机规划、跨时钟域处理、代码仿真验证等多个环节稍不留神就会踩坑。我刚入职那会儿第一次自己写SPI主机控制器因为没处理好片选时序调了一整天才发现是CS拉高过早导致从机数据没采上。今天就把这个项目的完整实现过程、设计思路和踩坑经验整理出来希望能给你一个可以直接参考复现的路径。这篇文章适合正在学FPGA的初学者也适合已经写过基本逻辑但想系统梳理SPI协议实现的工程师。不管你是要做SPI从机、主机还是主从一体的控制器这里面的状态机设计思路和验证方法都能直接套用。1. SPI通信协议再梳理实现前的必备功课1.1 SPI协议本质与四种工作模式很多教程一上来就让你写代码但这恰恰是新手最容易翻车的地方没弄懂协议就直接开写后面反复改。SPI全称Serial Peripheral Interface本质是一种同步串行通信协议靠SCK时钟同步数据。它由四根线组成SCK时钟、MOSI主机输出从机输入、MISO主机输入从机输出、CS/SS片选。SPI协议最核心的难点就是四种工作模式的选择。CPOL决定空闲时SCK的电平CPOL0表示空闲低电平CPOL1表示空闲高电平。CPHA决定采样时刻CPHA0表示第一个时钟沿采样CPHA1表示第二个时钟沿采样。组合起来就是Mode0到Mode3。![四种SPI模式时序对照表]模式CPOLCPHA采样沿移位沿典型应用场景Mode000上升沿下降沿大部分Flash、SD卡Mode101下降沿上升沿部分ADC芯片Mode210下降沿上升沿部分EEPROMMode311上升沿下降沿低速外设、工业级芯片说实话四种模式本身理解起来不难但真正到代码实现层面很多人会在这里绕晕。我在项目里经常遇到的情况是代码看起来是对的就是样板不对。后来总结出一个笨办法——先看从机芯片数据手册里的时序图数清楚它在哪个沿采数据再对照上面这张表选模式。实际开发中有个更简单的技巧碰到不确定的芯片优先选Mode0或Mode3。因为这两种模式在相同CPOL下数据变化沿和采样沿间隔半拍时序余量比较充裕对PCB走线和FPGA IO延迟容忍度更高。我自己用过的芯片里Mode0占了六成以上。1.2 主从角色与片选策略选择SPI通信里主机负责产生SCK和CS从机被动响应。FPGA既可以做主机也可以做从机但两种角色对资源消耗和设计复杂度影响很大。项目里最常见的场景是FPGA做SPI主机去控制外部从机芯片。这时候FPGA要主动产生时钟需要对系统时钟做分频还要考虑SCK频率与外设要求是否匹配。如果是做从机FPGA要接收外部主机送来的SCK这里就涉及跨时钟域问题——外部SCK和自己内部时钟是异步关系必须做同步处理否则亚稳态会把你折腾疯。片选也分硬件片选和软件片选。软件片选指的是FPGA内部用寄存器控制CS信号想拉高就拉高、想拉低就拉低灵活性最高。硬件片选则是在FPGA内部用状态机自动管理CS时序适合那种对CS时序要求精确的外设比如某些ADC要求CS拉低后必须等几个时钟周期才能开始通信。我个人的经验是如果只是接一块Flash或者一个传感器软件片选完全够用但如果你的SPI总线挂给多个从机或者从机对CS时序有严格约束建议把CS管理做进状态机里别靠手动拉引脚。你去看市面上大多数IP核的实现CS时序都是状态机自动处理的手动拉CS在高频下极容易出问题。2. FPGA端SPI主机的整体架构设计2.1 顶层模块划分与端口规划设计一个FPGA的SPI控制器前先理清模块划分。不要一上来就写一个巨大的模块把逻辑全塞进去。我惯用的做法是把整个SPI控制器拆成三个子模块波特率发生器、SPI核心状态机、发送/接收数据缓冲。波特率发生器负责把FPGA系统时钟分频成SCK时钟。SPI核心状态机是心脏负责控制片选、移位、采样所有动作。数据缓冲对接外部逻辑用FIFO或者寄存器组把待发送数据准备好把接收数据存好后续逻辑直接取用即可。顶层模块的端口规划也很重要。我建议把时钟和复位单独拿出来Clk和Rst_n统一从外面接入。SPI相关端口直接用SCK、MOSI、MISO、CS命名数据端口用Data_Out、Data_In_Rdy这类清晰命名的信号。千万别用a、b、c这种缩写名等代码量上来以后调试时光追信号就够你受的。对于位宽选择我通常会做成可配置的最常见的是8位和16位。用参数化的方式定义比如参数DATA_WIDTH8这样以后复用的时候只需改一个参数不用动逻辑主体。2.2 核心状态机设计思路SPI控制器的状态机设计是整个项目的灵魂。一个SPI主机的一次完整通信过程从左到右大概经历这几个状态空闲、启动、传输、结束。空闲状态是默认停留状态此时SCK保持空闲电平、CS为高、数据线不驱动。检测到发送请求信号后进入启动状态把CS拉低等待一个周期让从机做好准备。传输状态下按照SPI模式的时钟相位关系逐位搬移数据。结束状态把CS拉高通知后续逻辑本次传输完成。这里有个细节值得注意如果外部从机是Flash或者ADC这类设备CS拉低后往往需要几十纳秒的建立时间才能开始时钟信号。这个时间在低频时钟下可以忽略但如果你把SPI跑到了几十兆或者上百兆赫兹这个延迟就需要在状态机里显式加一个等待周期。还有一个状态机编码的选择问题。我强烈建议在FPGA里面用独热码one-hot即每个状态只有一个bit为1。独热码的优点是译码逻辑简单、寄存器输出延迟小、时序收敛容易。虽然多用了几个寄存器但FPGA寄存器资源不缺时序问题才是大麻烦。我之前在一颗老款芯片上用过二进制编码的状态机结果频率一往上提就出现状态跳变错乱换成独热码后问题消失从此再也没换过别的编码方式。2.3 时钟策略与波特率生成SPI通信的SCK频率取决于两个因素从机支持的极限频率和FPGA系统时钟能分频出的合适频率。比如系统时钟50MHz你想得到1MHz的SCK就是50分频。但分频得到的占空比也要考虑某些从机对SCK占空比有要求需要尽量做到50%占空比。波特率发生器的实现有两种常见方式计数器分频和时钟使能。计数器分频直接得到SCK信号做法简单但会产生一个额外时钟域。在 FPGA 设计里时钟域越少越好所以我更推荐用时钟使能的方式系统时钟始终不变用一个使能信号标记“SCK翻转时刻”。时钟使能法和计数器分频的本质区别在于前者的整个设计时钟域只有一个逻辑全部用系统时钟驱动SCK只是普通输出信号后者则引入了新的时钟域约束、同步都会变得复杂。实测下来用时钟使能方式写的SPI控制器时序收敛容易得多尤其在高速收发场景这算是我比较坚持的一个设计习惯。3. Verilog代码实现与要点拆解3.1 SPI主机发送通路实现附代码先给出一版简洁、可复用的SPI主机发送模块代码。这里以Mode0为例即空闲低电平、上升沿采样。module spi_master_tx #( parameter DATA_WIDTH 8 )( input wire clk, input wire rst_n, input wire start, input wire [DATA_WIDTH-1:0] data_in, output reg sck, output reg mosi, output reg cs_n, output reg done ); localparam IDLE 3d0; localparam START 3d1; localparam SHIFT 3d2; localparam FINISH 3d3; reg [2:0] state; reg [3:0] bit_cnt; reg sck_buf; reg [DATA_WIDTH-1:0] shift_reg; always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; sck 1b0; mosi 1b0; cs_n 1b1; done 1b0; end else begin done 1b0; case (state) IDLE: begin cs_n 1b1; sck 1b0; if (start) begin shift_reg data_in; bit_cnt DATA_WIDTH - 1; cs_n 1b0; state SHIFT; end end SHIFT: begin if (sck 1b0) begin sck 1b1; mosi shift_reg[bit_cnt]; end else begin sck 1b0; if (bit_cnt 0) state FINISH; else bit_cnt bit_cnt - 1; end end FINISH: begin cs_n 1b1; sck 1b0; done 1b1; state IDLE; end endcase end end endmodule这段代码里有很多需要注意的点。首先MOSI在SCK低电平期间改变SCK上升沿时从机采样正好满足Mode0要求。我在SHIFT状态里面用了两个if分支上半拍把出发沿打出去下半拍完成移位和计数。其次done信号只拉高一个周期方便后续状态机判断。如果你对接的上级逻辑用的是握手协议也可以在此基础上扩展成valid-ready形式。CS拉低发生在start信号有效的那个周期SCK第一个上升沿至少要隔一个时钟周期才来这个间隙满足了大多数从机的建立时间要求。这种做法很实用的一点是你只需要改一个参数就可以实现16位、32位的SPI传输。数据位宽的扩展不需要改动状态机结构。之前有个项目需要和ADC芯片通信默认8位模式后来换了个24位ADC我把DATA_WIDTH改成24就完事了底层逻辑完全没动。3.2 SPI主机接收通路与双工处理很多人写SPI只写了发送通路但在真实项目中SPI基本都是全双工通信的——你在发指令的同时从机也在往MISO线上吐数据。如果只处理发送接收数据就丢了。接收通路的关键是在正确的时钟沿对MISO采样。以Mode0为例从机在SCK下降沿改变数据、上升沿保持数据稳定那主机就应该在SCK上升沿采MISO。因此接收逻辑和发送逻辑共用一个时钟在SCK上升沿把MISO的值打进移位寄存器。收发怎么做统一处理我的建议是设计一个统一的SPI Core同时包含发送移位寄存器和接收移位寄存器每次SCK时钟沿同时完成「发送一位」和「接收一位」两个动作。这样一次通信结束后发送缓冲区和接收缓冲区同时更新最高效也最自然。// 双工核心逻辑同一状态机同时处理收发 SHIFT: begin if (sck 1b0) begin sck 1b1; mosi shift_tx[bit_cnt]; // 发送数据 end else begin shift_rx[bit_cnt] miso; // 接收数据 sck 1b0; if (bit_cnt 0) state FINISH; else bit_cnt bit_cnt - 1; end end注意这里接收数据的赋值时机。在SCK高电平期间从机的MISO已经稳定我在SCK由高转低的那个时刻把MISO采进来逻辑上和上升沿采样等价但时序余量更好。实际测试下来这种方法在100MHz系统时钟下跑20MHz的SPI都没有问题。3.3 与数据缓冲/FIFO的对接实践SPI控制器本身只是搬运工真正有价值的是上层逻辑怎么跟它对接。常见的对接方式有寄存器直连和FIFO缓冲两种。寄存器直连适合单次、低速的配置操作。比如你要给外部ADC写一个配置寄存器直接给SPI模块发一个脉冲把数据放到data_in上等done信号回来就好。这种方式的优点是逻辑简单、延迟低缺点是CPU或者上层逻辑会被阻塞等待不适合大批量数据传输。FIFO缓冲适合连续传输场景比如从Flash连续读取一页数据或者把传感器数据连续写进外部存储。做法是在SPI Core前面加一个异步FIFO上层逻辑把数据连续写入FIFOSPI Core启动后自动消费FIFO里的数据读回来的数据再写入另一个FIFO供上层读取。我在做ADC连续采样项目时就用到了这个思路。ADC每次转换完产生一个中断中断服务函数把转换结果写入FIFOSPI Core自动启动传输把数据写到外置Flash。这样CPU几乎不参与SPI时序管理效率提升非常明显。当时实测的数据吞吐率比裸寄存器直连方式高了接近一个数量级。4. 仿真验证方法与常见信号检查4.1 Testbench搭建核心步骤写完代码不能直接上板一定要先仿真。实际开发中,仿真能解决九成以上的逻辑问题。SPI控制器的仿真其实就是搭建一个模拟从机和你的SPI主机互联。模拟从机的行为模型很关键它要在正确的时钟沿“看”MOSI线上的数据,并且按协议要求返回MISO数据。// 模拟从机行为模型Mode0 always (posedge sck or negedge cs_n) begin if (!cs_n) // 在SCK上升沿锁存MOSI rx_data_from_master {rx_data_from_master[6:0], mosi}; end always (negedge sck) begin if (!cs_n bit_cnt_slave 8) // 在SCK下降沿切换MISO miso mem_read_data[7 - bit_cnt_slave]; end注意模拟从机的建模要和实际协议匹配。比如从机在上升沿采样那你就应该在上升沿采集MOSI在下降沿更新MISO。仿真需要把从机建模写准确否则可能出现主机和从机都“报错”但代码实际没问题的假象。Testbench里面还要重点关注几个信号的检查点。CS时序是否符合协议要求CS拉低后到第一个SCK沿的时间是否满足从机建立时间SCK频率和占空比是否为预期值发送数据的位序是否正确你要的是MSB优先还是LSB优先接收数据是否在每个SCK周期都正确采样。4.2 仿真波形分析要点跑完仿真后大家最常犯的错误就是看不懂波形、不知道重点看哪里。其实SPI相关信号就那几个sck、cs_n、mosi、miso、state。先把这些信号加到波形窗口然后缩放到一个完整通信周期。我检查波形时习惯按这个顺序来。第一步看CS时序CS拉低到SCK第一个跳变之间有多少延迟CS拉高时SCK是否已经回到空闲状态。第二步看MOSI的相位关系以sck上升沿为基准MOSI变化沿是否离采样沿足够远。第三步看MISO采样用光标定位到SCK上升沿确认MISO此时已经是稳定电平。第四步看状态机跳转,确保状态按照预期序列执行没有异常跳变或卡死。曾经调一个SPI Flash读ID的模块发送完全正确就是读回来的ID不对。波形看了半天也没发现逻辑问题后来把光标精确放到SCK上升沿才发现MISO上电平恰好在这个沿附近还在翻转本质是仿真模型里从机数据更新时机写得不严谨导致采样点落在数据边沿上。从那以后我对仿真波形采样点的判断就非常小心了。4.3 仿真覆盖的几个典型场景很多初学FPGA的人跑了一次仿真发现波形对了就以为万事大吉其实这是远远不够的。SPI控制器至少要覆盖以下几种场景才能保证可靠性。单字节收发是最基本的确认CS拉低、发送8位、CS拉高整个流程没问题。连续多字节收发要验证每个字节的CS间隔是否符合要求。发送和接收同时进行时要确认双工模式下MISO数据没有丢失。异常场景也要测一测比如在SPI控制器忙的时候再发start信号系统应该忽略或者等待而不能造成逻辑混乱。我一般会在Testbench里加几个断言assertion来自动检查结果比如接收到预期数据就打印pass不匹配就报错。这样比人工盯着波形判断可靠得多尤其当数据量上去以后人力根本看不过来。断言配合波形定位的方式帮我在一个多从机SPI项目中快速定位到了时序冲突问题。5. 综合约束、上板调试的实战经验5.1 引脚约束与时序约束的基本要求仿真通过只是第一步。真正上板调试时引脚约束和时序约束才是决定设计能不能跑起来的关键。引脚约束要把FPGA顶层模块的输入输出端口一一映射到物理引脚上。这里常被忽视的是引脚的电平标准SPI信号通常和外部芯片供电电压一致FPGA的Bank电压也需要对应。比如你外部芯片是3.3V供电那FPGA接SPI信号的Bank必须工作在3.3V否则电平不匹配会导致通信不稳定甚至烧毁。时序约束里最基础的是主时钟约束把系统时钟频率约束进去比如50MHz对应period 20ns。如果你用了PLL/MMCM生成时钟最好也加上时钟约束。SPI属于低速接口通常不需要严格的input/output delay约束就能工作。但当SCK频率提升到20MHz以上时建议还是加上虚拟时钟和输入输出延迟约束否则极端温度下时序可能直接崩溃。上板调试中我遇到过一个印象深刻的坑把SPI引脚分配到了FPGA的差分IO上仿真怎么跑都对上板怎么调都不通。查了半天才意识到那组引脚是差分对单端信号根本出不来。所以引脚分配前一定要仔细看芯片手册确认哪些引脚支持单端IO、哪些是专用差分IO。5.2 实际调试中的常见信号质量问题上板后信号质量问题是让很多人头疼的尤其是SCK频率较高时。SPI信号在PCB上走线较长、或者排线连接时信号反射、串扰、过冲、振铃都会出现。我遇到过一次挺典型的情况20MHz的SCK下MOSI波形出现严重过冲从机直接通信失败。示波器一看上升沿过冲超过1V原因是驱动能力太强、PCB走线阻抗不匹配。解决办法是降低FPGA IO的驱动强度slew rate改为slow同时在靠近从机端加一个33欧姆左右的串联电阻吸收反射问题解决。除了硬件层面的信号质量问题逻辑层面的毛刺也可能让通信出错。SPI的CS信号如果因为综合优化产生了毛刺从机会误判通信开始或结束导致数据完全错乱。对付毛刺的常见手段是给CS做两级寄存器同步打拍或者用状态机里寄存输出信号避免组合逻辑毛刺直接驱动外部引脚。5.3 跨时钟域处理的注意事项SPI控制器如果做从机外部SCK和内部系统时钟异步这个跨时钟域问题必须处理。很多设计在这上面栽过跟头。最简单的做法是给外部SCK做两级同步消除亚稳态后再使用。但这有一个隐患同步后SCK会延迟两个内部时钟周期导致采样时序偏离。所以在从机设计中我常用的做法是检测SCK的边沿信号代替直接使用SCK。边沿检测法的思路是用系统时钟同步SCK获得sck_sync同时缓存sck_sync延迟一拍得到sck_sync_dly然后通过异或逻辑生成sck_rise和sck_fall脉冲。状态机在sck_rise脉冲时采样MOSI在sck_fall脉冲时更新MISO既消除了亚稳态又保证了采样点清晰。这个方法我在多个从机项目中用过,非常稳定。对于主机内部如果SCK由内部时钟分频产生不存在跨时钟域问题但需要注意SCK输出到引脚前也加一个寄存输出避免组合逻辑输出毛刺。6. SPI、IIC和UART怎么选6.1 SPI与IIC的关键差异不少初学者搞不清SPI和IIC的区别接触混合信号系统设计时更是经常要在这两者之间二选一。SPI是全双工、高速接口线多但时序简单。IIC是半双工、低速接口用两根线就能挂很多设备但时序复杂。从使用场景来说如果对吞吐率要求高、不介意多几根线SPI是首选如果系统里外围设备多但速率要求不高IIC更合适节省IO资源。6.2 SPI与UART、UART的应用边界SPI和UART其实是完全不同性质的东西。UART是异步串口靠波特率同步不需要时钟线SPI是同步串口必须有时钟线同步。UART每次传一个起始位加数据位加停止位有帧格式SPI没有帧格式靠CS划分通信边界。实际项目中我经常遇到这样的选择芯片只有UART想提速怎么办答案无非是换SPI接口的芯片或者用FPGA在中间做个协议转换。之前在某个项目里传感器只支持UART输出但后端设备只收SPI于是我在FPGA里写了个UART转SPI的桥接逻辑把UART收到的数据解析后按SPI时序发给后端两边速率匹配得很好。6.3 结合项目选型的决策建议在做选型决策时我一般把问题拆成三步来看。第一步看速度需求。以SPI能轻松跑到几十兆为单位而IIC通常工作在几百千赫兹到几兆。如果每秒需要传几兆字节IIC几乎不用考虑直接选SPI。第二步看硬件资源。SPI需要4根线IIC只要2根线。如果PCB面积紧张、MCU引脚不够用IIC更具优势。FPGA内部资源相对充裕这两种接口的资源消耗差异倒不大。第三步看功耗和从机数量。从机挂得多、功耗受限IIC更适合它带地址机制一主多从少两根线就行SPI一主多从要么用多根片选线要么用菊花链接线复杂度和逻辑复杂度都会提升。其实没有绝对的好坏关键是搞清楚自己的瓶颈在哪里。拿不准的时候我之前有个习惯把可能的方案列一张对比表写清楚每种方案的速度上限、引脚需求、IP成熟度、开发周期然后再做选择。7. 基于FPGA的SPI应用扩展方向7.1 SPI Flash配置与固化SPI接口在FPGA系统里最常见的一个应用场景之一就是配置Flash常和系统固件关系密切。尤其是小规模FPGA程序固化通常就靠一块SPI Flash。FPGA上电后从SPI Flash加载配置比特流整个过程由FPGA内部的专用配置逻辑自动完成。如果你用的FPGA芯片带SPI配置引脚需要做的就是选对Flash型号、设计好PCB走线。更重要的是Flash的时钟频率需要和FPGA配置要求匹配。比如有些Flash在快速读取模式下能跑到100MHz以上但FPGA的配置时钟可能只有几十兆过分追求高频反而会引入信号完整性问题。我在项目中遇到过配置偶尔失败的情况排查到最后发现是SPI Flash的供电纹波过大导致高压编程时Flash工作不稳定。后来在Flash的电源引脚旁边加了一个10uF去耦电容问题就好了。这提醒我们SPI配置Flash出问题时不要只盯时序电源质量往往是隐形杀手。7.2 ADC数据采集与传感器读取FPGA做数据采集时SPI读取ADC转换结果是我最常用的方案之一。很多高精度ADC采样率并不高但分辨率很高比如24位的Sigma-Delta ADC就是靠SPI接口把转换结果送出来。这种场景下SPI控制器通常是主机要主动发起读操作等待ADC转换完成后把数据搬回来。时序上需要注意有些ADC在转换完成后会拉低DRDY引脚FPGA需要等这个信号有效后再启动SPI读取。如果用轮询方式读SPICPU开销很大我一般会加一个中断在DRDY下降沿触发FPGA的SPI读取逻辑。之前做一个多通道压力传感器采集系统用的就是这种架构。FPGA作为管理中枢当传感器采完数据后自动通过SPI把数据收回并处理全程不需要外部干预。整套系统提高了集成度也降低了CPU负载一直是多年来比较稳妥的做法。7.3 与MCU/SoC的联合通信方案FPGA和MCU之间通过SPI通信也是非常常见的应用。在做电机控制项目时我经常把FPGA作为实时控制核心MCU负责界面显示和参数设置两者之间用SPI传输控制指令和状态反馈。这种架构的通信数据量通常不大但实时性要求高。SPI接口正好匹配主机发起一次传输从机立刻响应延迟很低。如果把MCU作为主机FPGA作为从机FPGA 从机端需要在收到指令后及时回传状态数据。反过来如果FPGA作为主机那么FPGA主动周期性读取MCU内部的状态寄存器也是一种可靠的低延迟方案。在FPGA从机实现上有一点需要特别提醒由于外部SCK和FPGA内部时钟异步所有输入信号过两级寄存器是必备动作发送的数据需要在内部时钟域先行寄存。和我前面说的边沿检测法结合使用可以让MCU和FPGA之间的SPI通信既高效又稳定。8. 踩坑笔记与实用速查8.1 典型问题、根源排查与解决方案做SPI FPGA实现这么久我把最常见的问题整理成了一张速查表方便以后项目复用的时候直接查。现象可能原因排查手段通信完全没反应CS未拉低/电平不匹配示波器看CS波形检查Bank电压数据错位整体移位位序设置错误MSB/LSB对比数据手册检查移位方向偶发数据错误采样沿和数据变化沿时序过近调整SPI模式或加等待周期高速通信时出错信号完整性过冲、振铃降低IO驱动强度、加串阻配置Flash偶尔失败电源纹波过大加强去耦检查Flash供电仿真正确、上板失败引脚分配错误或跨时钟域问题核对引脚约束检查同步设计这张表不是万能的但很多时候能让你少走很多弯路。我以前遇到问题总想着重写代码后来发现大部分通信问题的根源其实在物理层和时序约束层代码本身反而是最可靠的。8.2 提高开发效率的几点心得写SPI控制器这类接口模块最大的经验就是模块化、参数化、可复用。我建议每个FPGA工程师都建一个自己的IP库把SPI、IIC、UART这些常用接口都写好、仿真好、上板验证好以后新项目直接调用。不要每次项目都从头写既费时间又容易引入新bug。另外仿真一定要认真做。很多人觉得仿真浪费时间直接上板调结果一次上板调几小时甚至几天。实际上一个规范的Testbench半个小时就能写好能省下来的debug时间远超半小时。如果项目时间紧也至少要把正常收发场景和CS时序测一遍。最后一个小建议多看芯片数据手册的时序图。各家的SPI外设细节差异很大有些芯片要求CS拉低后至少等1us才能给SCK有些芯片要求在通信结束前必须保持CS低电平若干ns。这些信息代码里体现不出来但恰恰是决定通信成败的关键。9. 项目收尾与真实感受项目全部完成、上板跑通的那一刻我照着逻辑分析仪的波形把整个通信过程重新捋了一遍。从最开始的FPGA发出CS和SCK到从机在高电平上稳定输出数据再到FPGA把读到的数据准确恢复出来每一拍都清清楚楚。那种把一个接口从零开始写到真正跑通的满足感是直接用别人IP核体会不到的。回看整个SPI FPGA实现的经过我最大的感受是通信接口设计考验的不是某个瞬间的灵感而是对整个协议细节的持续专注和反复验证。从协议的四种模式选择、状态机的每一拍跳转、仿真波形的每一个采样点到最后上板的每一次示波器测量每一环都连在一起任何一个环节出了问题通信都会直接拉垮。如果你正在做类似的SPI和其他FPGA通信接口别急着上板。先把协议吃透把状态机画清楚多花半小时把Testbench写完善上板调试会轻松很多。真碰到问题也别慌回头去翻数据手册里的时序图大部分答案都在里面。最后再分享一个小技巧吧SPI控制器这类代码做完以后一定记得加版本号和关键参数注释比如模式、数据位宽、时钟分频系数两个月后你再回头用这段代码时会发现这些注释比任何文档都管用。