最近刷牛客和CSDN发现,很多公司2026届FPGA/数字IC笔试都爱考SPI主机读写Flash。UART收发状态机只有起始、数据、停止几个状态,而SPI要处理片选、模式选择、8位数据移位、等待Flash忙状态、读ID和页写时序,状态跳转多、跨时钟域处理也多。我总在片选拉低和第一个时钟沿之间写错,想问问这题到底考什么能力,怎么准备才能不丢分?
2026年FPGA校招笔试Verilog代码题,为什么「用状态机实现SPI主机读写Flash」比「写UART」更容易暴露设计能力差距?
11 个回答

先说结论:这题之所以比UART更容易拉开差距,是因为UART基本只考一个「串并转换+波特率分频」,而SPI主机读写Flash是一个小型协议控制器,考的是你能不能把一堆时序约束翻译成可综合的状态机。
你提到的片选拉低到第一个时钟沿写错,这恰恰是暴露点之一。UART没有片选,你不需要考虑CS的建立时间,SPI里CS是给从机的异步信号,Flash的数据手册一般会规定CS下降沿到第一个SCLK有效沿的最小时间,如果你在状态机里CS拉低和时钟使能在同一个时钟周期输出,仿真可能刚好过,上板就可能读不到ID。常见做法是把CS拉低单独放一个状态,或者至少打一拍再进移位状态。
另一个暴露点是模式。CPOL/CPHA四种组合,很多人只背了「模式0是空闲低、第一个沿采样」,但真写代码时采样沿和移位沿搞混,读出来全是FF。建议你先画状态转移图,把空闲、片选、命令、地址、数据、等待、结束分开,再用一个参数化的采样沿选择逻辑,而不是硬编码。
仿真时别只测自己发的数据,挂一个Flash行为模型,哪怕是你自己写的简化模型,验证读ID和页写两个流程,能帮你抓出大部分时序对齐问题。
你现在是准备笔试还是已经在做板级调试了?如果是笔试,先把状态图和时间轴画熟比背代码有用。

从企业一线做FPGA的视角看,SPI主机读写Flash这道题其实是一道「浓缩版项目题」,面试官想看的不是你能不能背出一段Verilog,而是你有没有把协议、时序和硬件行为串起来的能力。
UART为什么相对简单?因为它的状态机是线性的,起始位、8位数据、停止位,跳转关系几乎是单链,跨时钟域问题也少。SPI不一样,它同时有片选、时钟极性相位、双向数据线、从机忙状态、命令和地址的分段,任何一个环节没对齐,整体就读不出来。这就是能力差距的来源:UART考的是「会不会写状态机」,SPI考的是「会不会做协议控制器」。
具体到准备路径,我建议分三层。第一层是协议层,把Flash手册里读ID、页写、读状态寄存器的时序图看懂,尤其是CS建立保持、SCLK频率上限、命令字节后跟地址的位数。第二层是架构层,把SPI主机拆成时钟生成、移位寄存器、状态机和片选控制四个部分,状态机只负责流程,移位寄存器负责收发,别把所有逻辑塞进一个always块。第三层是验证层,写testbench时用行为级Flash模型,模拟忙状态和返回数据,这样你能验证等待状态是否正确。
关于你总在片选和第一个时钟沿之间出错,根因往往是你把SCLK当成自由运行的时钟,而不是状态机产生的门控时钟。更稳的做法是用系统时钟分频产生SCLK,片选拉低和第一个边沿之间用计数器或状态隔开,确保满足建立时间。CPOL/CPHA也别死记,画一张时间轴图,标出采样沿和移位沿,代码自然就出来了。
工程上的取舍是:笔试时优先保证状态机清晰、时序正确,别追求参数化到极致;实际项目里再考虑多从机、DMA和跨时钟域同步。你现在手头有具体Flash型号的手册吗?不同厂家的时序参数差别不小,对着手册练比刷通用题更有效。

UART考的是「你会不会写状态机」,SPI考的是「你会不会看时序图并把约束翻译成电路」。片选和第一个沿之间写错,本质是没把建立时间当成状态机的一个状态来处理。

很多人把这题理解成「SPI比UART多几个状态」,其实方向偏了。真正的差别在于:UART是一个自洽的闭环,收发两端都在你的掌控里,波特率分频对了、起始停止位对齐了,基本就能通。SPI主机不一样,你面对的是一个你无法修改行为的从机——Flash芯片。它的时序是别人定的,你只能去适配。
这就带来一个本质问题:你的状态机不是在描述一个算法,而是在描述一个「你和外部器件之间的契约」。片选拉低到第一个时钟沿之间为什么容易写错?因为你在写UART时养成的习惯是「状态一到就立刻输出动作」,而SPI要求你区分「状态」和「时序事件」。CS拉低是一个事件,第一个采样沿是另一个事件,两者之间隔着Flash手册规定的建立时间tSLCH。如果你把它们塞进同一个always块、同一个时钟周期,仿真波形看着对,上板读出来就是FF或者随机值。
所以准备这题,我建议按「先画图、再写码、后验证」的顺序来。画图不是画个方框加箭头就完事,而是要把每个状态里「哪些信号是组合输出、哪些是寄存器输出、跳转条件是什么」都标清楚。空闲、CS拉低、发命令、发地址、收发数据、等忙、结束,这几个状态里最容易出问题的是CS拉低单独成状态,很多人觉得浪费一个周期,但恰恰是这个周期保证了建立时间。
第二个容易翻车的是CPOL/CPHA。模式0和模式3的区别不只是空闲电平,采样沿和移位沿的关系也变了。我的笨办法是先固定用模式0把读ID跑通,再改成模式3验证,如果代码里采样和移位是同一个沿触发的,换模式必挂。
第三个是等待Flash忙。页写之后要轮询状态寄存器,这里涉及「发命令-收数据」的半双工切换,状态机要能回到命令状态重发,而不是简单地线性往下走。
验证阶段强烈建议写一个简化的Flash行为模型,用task模拟它的响应,比对着真实芯片瞎调快得多。
你现在是卡在画状态图上,还是卡在仿真和上板结果对不上?

其实这题考的就是"你会不会把时序图当需求文档看"。UART你闭着眼都能写,因为收发都是你自己定的规矩;SPI不行,规矩是Flash定的,你只能照着做。片选和第一个沿之间写错,说明你还在用写UART的思维——状态一到就出动作,没有把建立时间当成一个独立的等待阶段。把CS拉低单独放一个状态,或者让时钟使能晚一拍,基本就稳了。模式0先跑通,再扩其他三种。

我换个角度说这事。UART和SPI最大的区别不在状态数量,而在"谁是主"。UART两边都是你自己写的,收发对称,你能同时看到两端代码,出问题可以两边一起改。SPI主机不一样,你面对的是Flash,它的行为你改不了,你只能把自己的状态机做成能去适配它。这就决定了:UART考的是你能不能写出一个能跑的状态机,SPI考的是你能不能读别人的时序手册,然后把里面的约束一条条翻译成你自己的电路行为。
片选拉低到第一个时钟沿写错,本质是你把"拉低CS"和"开始移位"当成了同一件事。UART里起始位来了就采样,状态和动作是绑定的,这个习惯带到SPI就出问题。Flash手册一般会规定CS下降沿到第一个有效SCLK沿之间要满足某个建立时间tSLCH,如果你在同一个时钟周期里既输出CS又输出时钟使能,仿真波形看着没事,上板可能就读到FF。正确做法是把CS拉低单独作为一个状态,或者至少让时钟使能比CS晚一个周期。
再往深一层,这题真正拉开差距的是状态机拆分粒度。有人把命令、地址、数据全塞一个大状态里用计数器切,代码短,但一改模式或者加等待就崩。比较稳的做法是按协议语义拆:空闲、片选建立、命令、地址、数据、等待忙、收尾释放。每个状态只干一件事,跳转条件写清楚是计满还是收到标志位。这样即使你忘了某个Flash要等忙,加一个等待状态也不用动别的。
准备路径我建议这样走:先拿一份Flash手册,把读ID那条时序图画在纸上,标出每个事件之间的时间关系;再画状态转移图,标出每个状态的进入条件和退出条件;最后才是写Verilog。写的时候把采样沿和移位沿分清楚,CPOL/CPHA四种模式先只实现模式0,跑通再扩。仿真时别只用自己写的testbench,找个Flash行为模型对着跑,能省很多上板debug的时间。
你现在是在校阶段还是已经在准备笔试了?如果是前者,建议拿一块带Flash的板子实际连一次,比纯看手册理解深很多。

一句话:UART是自己跟自己握手,SPI是去跟别人握手,别人不迁就你。片选那拍写错,就是没把建立时间当状态看。先画状态图再写代码,能少踩一半坑。

UART你写的是自己跟自己握手,SPI是你去敲别人家的门。门什么时候开、开多久,Flash说了算。片选拉低和第一个沿之间写错,就是忘了中间还夹着一段建立时间。把CS单独做成一个状态,先这么改,八成能通。

UART是自问自答,SPI是去别人家做客还得按人家的规矩来。片选那拍错,说白了就是没把tSLCH当成一个独立状态。先画图再写代码,CS单独一拍,基本就通了。你仿真时用的Flash模型是哪来的?

其实这题的核心不是SPI比UART难多少,而是你有没有意识到自己在跟一个不能改的对手打交道。UART你写收发两端,波特率分频随便定,起始位、数据位、停止位对齐了就能通,本质上是你跟自己握手,规则你说了算。SPI主机不一样,对面是Flash,时序手册是它写的,你只能照着它的规矩来。所以片选拉低和第一个时钟沿之间那一段,不是随便一拍就行的,手册里通常有个tSLCH建立时间要求。你老在这儿写错,多半是因为写UART时养成了状态一到就出动作的习惯,把拉低片选和开始移位当成同一件事。改法很简单:把片选拉低单独做成一个状态,哪怕只停一个时钟周期,让它和第一个SCLK沿之间隔开,再进移位状态。建议先画状态转移图,把空闲、片选、命令、地址、数据、等待、结束这些状态一个一个列出来,模式0先跑通再扩其他三种。你仿真时Flash行为模型是自己写的还是找的现成的?
发表回答
登录后可在本页底部提交回答