ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SPI通信FPGA实现全解析:从协议到Verilog代码与调试

SPI通信FPGA实现全解析:从协议到Verilog代码与调试 1. 从芯片手册到Verilog为什么SPI在FPGA里没有想象中简单做FPGA开发的人几乎绕不开SPI。无论是配置ADC、驱动Flash、读写SD卡还是和MCU做板级通信SPI都是最常用的串行接口之一。很多刚入门的朋友会有一个感觉SPI协议那么简单四根线一个时钟一个片选两根数据不就几行代码的事情吗真到自己用Verilog写起来才发现各种问题接踵而至——时序对不上、数据移位错位、跨时钟域采样采飞了、多设备挂总线上莫名其妙冲突。这篇文章我就围绕SPI通信的FPGA实现把从协议理解到代码落地再到板上调试的完整链路讲透把这个“看起来简单、做起来有讲究”的接口说清楚。先说结论SPI在FPGA里难的不是协议本身而是三件事——时钟极性和相位的正确理解、片选信号和字节边界的对齐关系、以及异步时钟域之间数据传输的安全性。这三件事处理好了SPI就真的简单了处理不好基本就是“调三天发现是片选多拉了一个时钟周期”这种故事。这篇文章适合几类读者刚学FPGA、想自己写SPI控制器而不是整天调IP核的入门开发者在工作中需要对接各种SPI外设、经常被时序图折磨的工程师以及想深入理解串行接口本质、为以后写IIC、UART、甚至PCIe打基础的人。我会给出可直接落地的Verilog代码框架、时序约束建议和调试方法也会把那些文档里不会写的踩坑经验一并说出来。2. 动手之前必须想清楚的四件事2.1 你是主设备还是从设备决定了整个设计架构写SPI之前第一件事不是打开编辑器写代码而是问自己这个模块在系统里是主还是从主设备和从设备的设计难度完全不是一个级别。主设备负责产生SCLK时钟、控制片选信号、决定数据传输节奏整个通信的主动权在自己手里时序上天然占优势——因为时钟是自己产生的数据和时钟之间的相位关系是确定的只要保证发送端时序满足要求、接收端正确采样就行。从设备则麻烦得多。你的时钟是外部给的什么时候来你不知道来了多少个脉冲你也不知道片选什么时候拉低你更不知道。你需要在完全被动的条件下把外部主设备发来的数据正确接收、把要回的数据在正确的时机放到MISO线上。更难受的是如果外部主设备的时钟频率很高而你内部还有跨时钟域处理的负担设计复杂度会直线上升。我见过不少朋友一上来就想写一个“万能SPI控制器”既能当主又能当从结果两边都没做好。实际上大部分应用场景是固定的——要么你驱动外设要么外设驱动你。先确定角色再设计架构这是第一条经验。2.2 时钟极性CPOL和相位CPHA教科书和现实之间的差距SPI的四种工作模式Mode 0/1/2/3是所有教材都会讲的但真正理解的人不多。CPOL决定空闲时SCLK是高还是低CPHA决定数据在SCLK的哪个边沿被采样。四个模式本质上是主设备和从设备之间约定的“什么时候读数据”的规则。这里我建议不要死记硬背“Mode 0是CPOL0, CPHA0”这种表格而是抓住两个要点第一看芯片手册里的时序图重点关注数据在SCLK哪个沿变化、哪个沿稳定。数据变化的那个沿对面的沿就是采样沿。第二FPGA里写代码时永远明确指定“我是发送端还是接收端”。发送端在变化沿更新数据接收端在采样沿读取数据。这两个沿是相反的搞反了读到的就是错位数据。一个很容易踩的坑是主设备发送和接收用的是同一个SCLK但有些从设备要求数据在SCLK上升沿发送、下降沿采样另一些正好相反。如果你只按一种模式写死换一个外设就要改一堆代码。比较稳妥的做法是把模式做成参数用parameter或者寄存器配置方便适配不同芯片。2.3 数据位宽和字节边界SPI没有“帧”的概念SPI协议本身只管“一个字节接一个字节地传”它没有定义“一帧数据从哪里开始、到哪里结束”。帧的边界完全靠片选信号来划分——CS拉低表示一帧开始CS拉高表示一帧结束。这就带来一个很实际的问题如果你的数据位宽不是8的倍数比如某个传感器输出的转换结果是12位你怎么跟它通信两种方式一种是把CS的行为和要传的bit数关联起来一个CS低电平期间传12个SCLK脉冲另一种是统一按字节传但约定只取前12个bit有效。第一种更规范第二种更简单看你的系统怎么取舍。我在实际项目里更倾向于把发送和接收的长度做成可配置的。比如一个SPI控制器支持4到32比特的任意长度传输CS拉低后严格按照配置的bit数来产生时钟。这样一个控制器能适配各种不同位宽的外设不至于每个芯片都要单独写一个状态机。2.4 速率匹配SPI到底能跑多快很多初学者觉得SPI速率越高越好上来就想跑到50MHz甚至100MHz。但SPI的速率上限不取决于FPGA本身而取决于三个因素从设备支持的最高SCLK频率、PCB布线的信号完整性、以及FPGA内部逻辑和IO的时序约束能否满足。对于一般的板级通信几十MHz问题不大但对于跨板连接、排线连接、或者从设备本身速率有限的情况就得适当降速。还有一个容易忽略的点从设备对SCLK的占空比有要求有些芯片要求高电平最少持续多少纳秒你光看频率还不够还得看高低电平的绝对时间。另外如果你的SPI速率超过了一定阈值比如50MHz以上FIFO的读写时序、跨时钟域处理、ILA调试的采样率都会成为瓶颈。我个人的经验是能跑低绝不跑高SPI通信带宽通常不是系统瓶颈稳定可靠才是第一位的。3. 从头写一个SPI主设备控制器状态机设计详解3.1 模块划分与接口定义下面我给一个实际可用的SPI主设备控制器的Verilog设计。先看模块接口这个接口的设计思路是“控制器内部只负责时序产生不关心业务逻辑”——你只要告诉它“往哪个地址发什么数据、要读多少字节”它给你把时序跑好。module spi_master #( parameter CLK_FREQ 50_000_000, // 系统时钟频率 parameter SCLK_FREQ 10_000_000, // SCLK频率 parameter DATA_WIDTH 8 // 数据位宽 )( input wire clk, input wire rst_n, // 用户接口AXI-Lite风格 input wire start, input wire [DATA_WIDTH-1:0] tx_data, output reg [DATA_WIDTH-1:0] rx_data, output reg done, output reg busy, // SPI物理接口 output reg sclk, output reg cs_n, output reg mosi, input wire miso );这个接口思想很朴素把SPI时序细节封装在模块内部对外只暴露start、tx_data、rx_data、done这些信号。上层业务逻辑不需要关心SCLK怎么翻转、CS什么时候拉低只要发一个start脉冲然后把要发的数据放上去等着done回来就行。3.2 核心状态机的四种状态SPI主设备的时序可以用一个很简洁的状态机实现。这里用基于系统时钟的分频计数方式来产生SCLK而不是直接使用PLL好处是灵活性高改参数就能换速率布局布线也简单。localparam IDLE 2d0; localparam CS_LOW 2d1; localparam TRANSFER 2d2; localparam CS_HIGH 2d3; // SCLK分频计数 localparam DIV_CNT CLK_FREQ / (2 * SCLK_FREQ) - 1;四个状态的含义是IDLE空闲状态CS为高SCLK为设定的空闲电平等待start信号。CS_LOW拉低CS同时给从设备一个准备时间。这个状态的时间不能太短有些从设备需要CS拉低到第一个SCLK上升沿之间至少有几百纳秒的稳定时间。TRANSFER在位数计数器的控制下一对一地产生SCLK脉冲同时在正确的边沿发送和采样数据。CS_HIGH最后一个bit传输完成后CS拉高的同时要保证SCLK的最后一个沿已经结束。这个状态同样需要一点等待时间确保从设备正确识别到帧结束。很多失败的设计就栽在CS_LOW和CS_HIGH这两个“过渡状态”上。有些芯片对时序的容忍度极低CS拉低后必须等够时间SCLK才允许动传完后CS拉高前SCLK必须已经停在高/低电平上。如果你直接让状态机从IDLE跳TRANSFER从TRANSFER跳IDLE数据偶尔错就是大概率事件。3.3 数据移位和采样边沿的控制方法数据收发是整个状态机最核心的部分。我用一个统一的bit计数器来管理每个bit的生命周期reg [3:0] bit_cnt; reg [DATA_WIDTH-1:0] tx_shift; reg [DATA_WIDTH-1:0] rx_shift; // 在SCLK上升沿驱动数据变化在SCLK下降沿采样 always (posedge clk or negedge rst_n) begin if (!rst_n) begin sclk 1b0; end else if (state TRANSFER) begin // 在分频计数器的中间点翻转SCLK if (div_cnt d0) begin sclk ~sclk; end end end这里有一个设计细节SCLK的翻转用分频计数器控制而数据的变化和采样要卡在SCLK的不同沿上。我最开始写的时候直接把数据和SCLK的翻转放在同一个always块里结果因为非阻塞赋值数据和时钟沿之间总是差出一点奇怪的延迟读回的数据经常是错的。后来改成把数据变化放在SCLK的边沿事件里处理用边沿检测来做时序一下就干净了。发送和接收的黄金法则是发送数据在SCLK边沿到来前变化接收数据在SCLK边沿到来后采样。对应到代码上就是用同一个系统时钟节拍先判断“现在是不是SCLK要翻转的拍子”如果是就先更新MOSI数据然后用另一个计数状态在读采样窗口里锁存MISO。3.4 完整代码框架一个经过板级验证的SPI主控制器下面是实际跑过的完整框架代码只保留了核心逻辑FIFO和AXI接口部分先不展开always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; cs_n 1b1; sclk 1b0; // CPOL0,空闲低电平 mosi 1b0; bit_cnt 4d0; done 1b0; busy 1b0; end else begin case (state) IDLE: begin done 1b0; cs_n 1b1; if (start) begin busy 1b1; tx_shift tx_data; bit_cnt 4d0; div_cnt DIV_CNT; state CS_LOW; end end CS_LOW: begin cs_n 1b0; // 在这里等待一个完整的SCLK周期给从设备准备时间 if (div_cnt d0) begin div_cnt DIV_CNT; state TRANSFER; end else begin div_cnt div_cnt - 1b1; end end TRANSFER: begin // SCLK翻转控制 if (div_cnt d0) begin div_cnt DIV_CNT; sclk ~sclk; // 在SCLK上升沿前更新MOSI if (!sclk) begin mosi tx_shift[DATA_WIDTH-1-bit_cnt]; end // 在SCLK下降沿采样MISO if (sclk) begin rx_shift[bit_cnt] miso; bit_cnt bit_cnt 1b1; end // 所有bit传完 if (bit_cnt DATA_WIDTH-1 sclk) begin state CS_HIGH; end end else begin div_cnt div_cnt - 1b1; end end CS_HIGH: begin cs_n 1b1; sclk 1b0; // 回到空闲电平 rx_data rx_shift; done 1b1; busy 1b0; state IDLE; end endcase end end说实话这段代码不是最优美也不是最快的但它是逻辑最清晰、最容易在板上跑通的一个版本。如果用低延迟高吞吐的SPI接口可以改成用PLL生成SCLK、用移位寄存器做并行转串行的流水线架构但那属于下一阶段的优化话题了先把基础打牢更重要。4. 从设备视角SPI Slave的设计难点与应对4.1 外部时钟的异步处理跨时钟域绕不过去的坑如果说写SPI主设备是“从容指挥”那写SPI从设备就是“被动接招”。主设备的SCLK是自己产生的所有时序都是可控的从设备则完全不一样——SCLK从外部引脚进来跟你内部的系统时钟没有确定的相位关系。这就引出SPI从设备设计里最核心的问题跨时钟域。你不能直接用外部SCLK去驱动内部逻辑因为内部逻辑是跑在系统时钟上的两者之间没有同步关系。如果强行用外部SCLK给内部寄存器打拍仿真可能是好的上板大概率会出现亚稳态导致的随机错误。我的做法是对进来的信号全部做两级同步包括SCLK、CS_N、MOSI。然后基于同步后的信号做边沿检测用“检测到上升沿/下降沿”来模拟SCLK的边沿事件驱动数据的采样和移位。这个方案的代价是引入了两个系统时钟周期的延迟但对绝大多数SPI从设备来说完全可接受——主设备的SCLK又不会快到几个周期都等不了。4.2 从设备的输出时序MISO线什么时候该释放从设备在MISO上输出数据有一个很多新手会忽略的问题什么时候开始驱动MISO什么时候释放MISO回到高阻态。正常的做法是CS拉低后、第一个SCLK采样沿到来之前从设备就要把第一个bit放到MISO上此后在每个SCLK变化沿更新MISOCS拉高后、进入高阻态。如果MISO是三态的必须用高阻态控制信号把输出置为z否则多个从设备挂在同一根线上时没被片选的从设备也会驱动MISO总线直接冲突。// MISO三态控制 assign miso (cs_n_sync 1b0) ? miso_out : 1bz;这个简单写法就是多设备SPI总线的命根子。很多人做完单设备通信一切正常一挂第二个从设备就出鬼影数据十有八九是MISO高阻态逻辑没写好。4.3 连续传输还是单次传输CS的处理方式不同SPI从设备还有一种常见需求主设备不拉高CS连续发很多个字节从设备要一直接收。这种情况下从设备内部要对“第几个字节”有边界感。有的芯片通过某些命令码来区分“这是新一包数据还是同一包的后续字节”有的芯片则完全依赖CS的上升沿来结束一包。你的从设备设计要明确支持哪种模式。如果只支持单次传输而主设备又喜欢用连续模式那你的状态机会在字节之间发疯。遇到过这样一个问题某主控芯片的SPI控制器默认就是连续模式CS从头到尾一个低电平而我从设备的接收状态机在每个字节结束后就复位结果第二个字节开始数据全是乱的。最后就是改从设备逻辑让它不依赖CS来复位字节计数器而是用字节宽度计数器自动对齐问题才解决。这类“外围主设备太霸道”的情况在工作中不算少见设计从设备时最好把兼容性想足一点。5. 工程化落地FIFO、DMA与中断的配合5.1 大量数据搬运时为什么不能靠CPU一字节一字节喂如果你只是在FPGA内部通过SPI读一个传感器寄存器级别的接口就够了。但一旦涉及大量数据搬运——比如SPI DAC要做连续波形输出、SPI ADC要持续采集几兆字节的数据、或者SPI Flash要读写几百KB固件——那单靠寄存器接口配合状态机轮询就完全扛不住了。原因很简单每次传输都要CPU或者软核参与频繁地等待done信号、读数据、写数据大量时间浪费在握手开销上。这时候就需要引入FIFO做缓冲。发送方向先把要发的一整块数据写入FIFO然后SPI控制器自动从FIFO取数、逐个字节发送发完一个自动取下一个接收方向SPI控制器把收到的每个字节写入FIFOCPU或DMA按需批量读取。这样CPU只负责在FIFO半满或半空的时候做一次批量搬运通信效率大幅提升。5.2 FIFO深度怎么定不要拍脑袋算一下就明白了FIFO深度不是越大越好也不是越小越省资源要按数据速率和消费速率的差值来算。打个比方你的SPI接收速率是10MB/sCPU或者DMA的搬移速率是20MB/s理论上FIFO只要保证搬移间隙的少量缓存就行。但实际系统中CPU还要处理中断、跑其他任务、响应别的外设实际的消费速率往往是不稳定的。我一般按“SPI连续发送/接收一个最大数据块所需的时间内消费端能搬走多少数据”来估算。比如一次要连续接收1000字节消费端每100字节需要额外等待5个时钟周期那FIFO深度至少要覆盖这个等待间隙。经验值是中等速率SPI10-30MHz配256字节FIFO已经能覆盖绝大多数场景如果速率更高或者数据块更大用512或1024也不过分。5.3 Xilinx AXI SPI IP核与自研控制器的取舍用FPGA厂家的SPI IP核还是自己写控制器这个问题很多时候没有标准答案取决于项目定位。Xilinx提供AXI Quad SPI IP核功能完善支持标准SPI、双通道、四通道还能配合XIP模式直接映射Flash。用IP核的最大好处是省时间、经过大量验证、跟AXI总线生态无缝衔接。但它的缺点是配置流程繁琐某些特殊时序比如非标准位宽、特殊的CS行为不好定制调试时也像个黑盒出了问题不好排查内部状态。自研SPI控制器的优势在于透明可控。你可以精确控制每一个时钟沿、每一个片选行为适配各种古怪的外设。反过来缺点也很明显——你自己写的代码就是最大的bug来源需要投入更多时间做验证和测试。我的建议是如果你的系统已经用了AXI总线和软核SPI速率要求不高、时序标准直接用IP核如果你需要严格控制时序、对接非标协议、或者SPI操作特别频繁对性能有要求自研更划算。我自己在高速ADC数据采集项目里选择了自研因为IP核在突发传输模式下的时序跟我想要的差那么一点改起来反而更麻烦。6. 板级调试从逻辑分析仪到ILA的完整排查链路6.1 第一步永远是看波形不是改代码SPI通信出问题最常见的心态是打开代码一顿猜改个参数下载上去试不行再改再试。这种“盲调”的效率和成功率都很低正确做法永远是第一步看波形。调试手段分两种逻辑分析仪看物理引脚上的真实信号或者用FPGA内部ILAIntegrated Logic Analyzer抓内部信号。逻辑分析仪的优势是能看到真实引脚的电平时序、毛刺、信号完整性问题ILA的优势是不用外接仪器能看到内部总线数据、状态机跳转、FIFO空满信号这些“看不见”的信号。实际调试时推荐双管齐下先用ILA抓内部状态机确认状态跳转和你要发的数据没问题再上逻辑分析仪看物理层的CS、SCLK、MOSI/MISO波形确定从设备实际收到的信号质量。两个层面都确认了基本就能把问题缩小到特定环节。6.2 我踩过的一个典型问题SCLK毛刺导致的数据错位有一次调一块ADC芯片现象是随机读到错误的转换结果而且错得毫无规律。用ILA抓内部信号状态机跳转正常、发送数据正确、MISO采样点的逻辑看起来也没问题。换上逻辑分析仪抓真实引脚才发现SCLK线上有明显的毛刺——在每个SCLK周期的边沿位置会有一个短暂的抖动。排查下来发现是代码里SCLK信号在多个always块里被赋值了产生了多驱动竞争。这在Verilog里是个很隐蔽的bug综合工具可能没报错但实际电路上多个驱动源会互相竞争产生毛刺。改进方法是保证一个信号只在一个always块里赋值尤其是SCLK这种对外输出的敏感信号绝不能让两个逻辑块同时驱动它。这类问题提醒我调试SPI这类低速串行接口时一定要分清楚问题是出在逻辑层还是物理层。逻辑层问题用ILA抓内部信号就能看到物理层问题只有逻辑分析仪能暴露出来。很多初学者一上来拿着ILA抓几天也找不到问题就是因为根本没怀疑过物理层的信号质量。6.3 调试检查清单每次SPI通信失败按这个顺序查根据几年来的调试经验我整理了一个排查清单按顺序执行绝大多数问题都能快速定位CS片选时序是否正确拉低时间是否满足从设备要求帧间隔是否够长CPOL和CPHA模式是否和从设备匹配MOSI数据是否在SCLK变化沿之前完成更新MISO采样窗口是否落在数据稳定区从设备的MISO是否在CS拉高后正确释放为高阻态SCLK是否有毛刺或占空比异常跨时钟域同步是否做充分有没有亚稳态风险位序是MSB first还是LSB first这个最容易忽略这个清单看着简单但每一条背后都是一个真实的坑。我见过有人在位序上栽了整整一天因为某厂商手册里写的时序图是LSB first但他从网上抄了一段MSB first的代码。所以拿到任何一颗新芯片第一件事永远是读时序图把位序、模式、片选行为三个参数确认好然后再动手写代码。7. 进阶应用从标准SPI到QSPI、多设备总线与高吞吐优化7.1 硬件片选和软件片选多设备SPI总线的两种风格当SPI总线上挂了多个从设备时片选信号的设计就变得很重要了。硬件片选的思路是每个从设备一条独立的CS线主设备通过拉低对应的CS来选择设备。这种方式的优点是简单可靠从设备不需要额外逻辑缺点是FPGA引脚占用多。软件片选则是所有设备共用一条CS逻辑通过在MOSI线上先发送一个设备地址前缀从设备自己判断这帧数据是不是发给自己的。这种方式节省引脚但要求所有从设备都支持地址匹配机制而且总线上天然存在“所有设备同时听到数据”的风险——如果某个从设备的地址解码逻辑有bug它可能错误响应。我个人在FPGA项目里更偏好硬件片选原因很简单SPI本身就是为低成本、少引脚设计的接口挂几个设备不算多引脚预算通常充足硬件片选把问题隔离在物理层调试起来一目了然而软件片选的地址冲突问题往往特别隐蔽排查成本高。7.2 什么时候考虑QSPI标准SPI不够用时的破局方式标准SPI只有一根MOSI和一根MISO全双工同时收发。但有些场景下比如要从外部Flash读取大量固件或者图像数据标准SPI的吞吐量就成了瓶颈。这时候QSPIQuad SPI就派上用场了——它把MOSI和MISO变成双向的DQ0/DQ1再加上DQ2和DQ3两根线一个时钟周期能传4个bit等效速率直接翻4倍。QSPI的FPGA实现比标准SPI复杂一个档次主要体现在两个地方第一数据线是双向的需要在FPGA内部正确管理IO方向切换时机第二QSPI有命令阶段、地址阶段、空周期阶段和数据阶段各个阶段的IO方向和数据宽度可能都不一样状态机设计粒度更细。如果你只是偶尔读一次Flash启动代码标准SPI完全够用没必要上QSPI。但如果你做的是需要实时加载大量数据的系统比如图像处理、信号采集回放QSPI几乎是个必选项。Xilinx的AXI Quad SPI IP核在这方面支持得比较完善建议优先评估IP方案。7.3 SPI和IIC为什么SPI总能在FPGA项目里胜出热词里出现了“spi和iic的区别”这确实是很多FPGA初学者纠结的问题。一句话概括SPI是高速、简单、引脚多一点的“冲劲型选手”IIC是低速、复杂、引脚少一点的“稳健型选手”。SPI的优势在于高速率、低协议开销、全双工每个方向都有独立的数据线硬件实现简单。缺点是引脚占用多至少4根、没有内置应答机制你不知道从设备是否成功收下数据、多设备需要额外片选线。IIC的优势在于只用两根线SDA和SCL、内置应答和仲裁机制、可以轻松挂几十个设备。缺点是速率低标准模式100kHz快速模式400kHz协议复杂而且开漏结构需要上拉电阻时序要求比SPI更严格。在FPGA项目里如果是在板内做中高速数据传输、对接ADC/DAC/Flash这些外设SPI几乎是默认选择如果是要跟一堆低速传感器通信、或者跟主控板上的其他芯片共享总线IIC反而更合适。看需求选型别因为“SPI更高级”就盲目选它。8. 性能和生产效率的平衡FPGA开发中的整体思考把SPI这个点放大到整个FPGA开发流程里看我发现很多项目失败或者延期不是因为某个技术点攻克不了而是在开发方式上出了问题。SPI控制器其实是一个绝佳的“练手项目”——它足够简单让你能关注细节又足够复杂能暴露各种FPGA开发的典型问题。做FPGA开发尤其是做SPI这类接口模块时我总结了一套适合自己团队的工作方式先读透芯片手册的时序图画出状态机和时序草图再用仿真验证逻辑正确性然后上板用ILA和逻辑分析仪实测最后根据实测结果回头调整代码和时序约束。这套流程看着笨但几乎能定位所有问题比“边写代码边猜测”高效得多。还有一点不要忽视SPI控制器的验证一定要仿真先行。哪怕逻辑很简单也要至少覆盖正常读、正常写、连续读写、CS恢复时间不足这几个case。因为很多时序问题在仿真阶段一眼就能看到一旦上板排查成本成倍增加。9. 写在后面关于SPI和FPGA我最后的几点建议SPI的FPGA实现不是什么玄学它就是“把协议变成状态机把时序变成寄存器行为”的一个基本功。但基本功扎实的人和只会调IP的人做出来的系统在稳定性、可维护性、可扩展性上是有明显差距的。如果你正在学习FPGA我强烈建议你亲手写一遍SPI主设备、SPI从设备、混合FIFO的完整控制器。哪怕只是仿真级别这个过程带给你的对时序的理解是任何IP核都给不了的。如果你已经在项目中用IP核了也建议花点时间读读IP核自动生成的代码看看官方是怎么处理跨时钟域、怎么管理FIFO的这些实现里藏着大量优秀的设计习惯。最后分享一个小技巧调试SPI问题的时候不要只盯着SPI信号本身回头看看复位逻辑。我遇到过一个非常诡异的问题——SPI在系统刚上电时正常跑一段时间后彻底卡死最后发现是复位信号在系统时钟域里没有做充分同步导致某个寄存器被随机复位了。像这种问题不是SPI的锅但只能在SPI的现象里被发现。嵌入式开发就是这样很多坑是跨模块的保持整体视野比死磕某一个模块更容易找到答案。希望这篇文章能帮你把SPI通信的FPGA实现这条路走得更顺。按照上面这些方法从协议分析、状态机设计、跨时钟域处理到板级调试一步一步来这块硬骨头啃下来之后你再看IIC、UART、甚至PCIe这些接口都会多一份底气。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进