
接触FPGA一段时间的工程师应该都有同感写Verilog本身不是最劝退的最劝退的是拿到一块陌生板子、一张原理图完全不知道从哪里下手。我这套QSPI公开课讲到这里正好是第三讲主题也从怎么握笔变成了怎么读图、怎么开工。这一篇要解决的是两件很具体的事第一把QSPI Flash相关的原理图吃透搞清楚每个引脚连到哪里、为什么这样连第二在Quartus里从头搭起一个可用工程把原理图上的网络名翻译成FPGA的物理引脚约束。适合已经有Verilog基础、但对从图纸到工程这条链路还不熟的朋友这一篇可以当作一份完整的操作笔记来跟。1. 从一张原理图说起QSPI Flash在FPGA系统里到底是干什么的1.1 为什么FPGA需要外挂一颗Flash常规FPGA比如Altera的Cyclone系列、Xilinx的Artix系列核心是基于SRAM工艺的SRAM的特点是读写快、但掉电即失。所以FPGA上电后第一件事就是从外部非易失存储介质里把配置文件读进来然后才能进入用户逻辑工作状态。这颗外部存储介质绝大多数低成本板子选的就是QSPI Flash也就是一颗Nor Flash芯片容量从8Mbit到256Mbit不等常见的如W25Q16、W25Q64、W25Q128。它承担两个角色一是存放FPGA的bitstream配置文件掉电不丢二是在系统运行起来之后剩余空间还可以用来存日志、存校准参数、存用户自定义数据。这就意味着你写的QSPI控制器既可能用在上电自加载阶段也可能用在运行时任意读写阶段。两者的时序要求不太一样自加载阶段由FPGA内部硬件逻辑自动完成不需要用户操心但运行时读写就需要你自己实现一套可读、可写、可擦除的控制器逻辑。这也是为什么这个系列第四讲开始就是手写代码第三讲把它之前的原理图工作和工程搭建先讲透。1.2 QSPI和普通SPI的差别在哪里很多初学者会把QSPI直接当成快一点的SPI其实不够准确。标准SPI是四根线的通信CS片选、SCK时钟、MOSI主出从入、MISO主入从出一个时钟沿只能传1bit属于单通道传输。QSPI里的Q是Quad的意思它把数据通道扩成了四根DQ0、DQ1、DQ2、DQ3在时钟沿上可以一次传输4bit。同样是50MHz的时钟QSPI在x4模式下的理论吞吐率能达到标准SPI的四倍。这个速度差距在加载几十Mbit的bitstream时非常明显。但注意一个细节QSPI不是改变物理引脚数量而是复用引脚。以常见的SOIC-8封装的W25Q Flash为例引脚功能是这样的引脚序号标准SPI功能QSPI功能1CS_n 片选CS_n2DO 数据输出DQ13WP_n 写保护DQ2复用4GNDGND5DI 数据输入DQ06CLK 时钟CLK7HOLD_n 保持DQ3复用8VCCVCC也就是说如果只做普通SPI读WP_n和HOLD_n可以直接拉高或拉低不占FPGA的IO但如果要用QSPI的x4模式这两个引脚就必须被FPGA控制因为它们在x4命令下变成了数据线DQ2、DQ3。这个判断会直接影响你对原理图的理解——当你看到原理图上WP_n和HOLD_n接的是10k电阻拉到电源基本可以确定设计者当初只打算用标准SPI或者x1模式如果看到这两个引脚直接连到FPGA的普通IO上那说明这个设计预留了x4高速读写的可能。我在实际项目里就吃过这上面的亏误判了连接关系导致引脚约束少分配了两根后面返工换板才解决。2. 原理图重点信息提取先看芯片再看网络最后看电源2.1 芯片型号与封装后缀这一步不能跳过解读原理图第一步不是看信号走线而是确认芯片具体型号和完整丝印。原理图上通常只会标一个元件符号旁边写型号比如W25Q128JVSIQ。这个型号的每一段都有含义W25Q128代表容量是128Mbit也就是16MBJV代表工作电压是3.3VJW则是1.8VS代表SOIC封装I代表工业级温度范围尾缀Q代表新的封装版本。电压版本不同IO电平就可能完全不同这会直接影响FPGA侧的IO标准选择。有些国产替代芯片型号规则不一样比如GD25Q128、XM25QH128前段数字一样是128Mbit电压和封装的后缀各有差异。拿到原理图后最好养成一个习惯把Flash的完整型号抄下来去官网下载对应的数据手册翻到DC Characteristics和Pin Configuration这两页确认工作电压和引脚定义。原理图本身有时候会画错数据手册才是最终依据。这一步花十分钟能省掉后面排查电平不匹配的几天时间。2.2 连接关系片选、时钟、四根数据线的来龙去脉在原理图中找到Flash芯片后沿着每一根信号网络去追看它最终到达FPGA的哪个引脚。最典型的连接结构包括CS_n片选通常网名是FLASH_CS_n、QSPI_CS或SPI_CS连到FPGA的某个bank的普通IO。原理图上有时会在CS_n上画一个上拉电阻到VCC这是为了防止FPGA未初始化时Flash被误选中。SCK时钟网名通常是FLASH_CLK、QSPI_SCK同样连到FPGA IO。有些设计会在CLK线上串联一个22欧姆或33欧姆的匹配电阻目的是减小信号反射。这个电阻对FPGA内部的逻辑没有影响但看原理图时心里要有数。DQ0/DQ1/DQ2/DQ3网名五花八门常见有FLASH_DQ0到FLASH_DQ3也有按SPI习惯写成FLASH_DI/FLASH_DO/FLASH_WP/FLASH_HOLD。如果是后一种命名说明画图的人按标准SPI思维画的你要自己意识到后两根在QSPI模式下会被复用。这里最容易出差错的是复用引脚的最终去向。我看到过不少板子DQ0和DQ1确实连到了FPGA但DQ2和DQ3只接了上拉电阻没有走线到FPGA。这种板子从硬件上就不支持x4模式软件写得再好也没用。所以你在解读原理图时一定要把四根数据线逐一追完确定它们都到了FPGA的IO才可以在代码里启用Quad模式。2.3 电源与去耦电容三个不起眼但致命的细节Flash的电源引脚通常网名叫FLASH_VCC或者和板上3.3V主电源共用旁边并联着0.1uF和10uF的去耦电容。这里有几个容易被忽略的点第一Flash供电必须和所在FPGA bank的供电电压保持一致。如果Flash是3.3V供电那它接的那个FPGA bank的VCCIO也必须是3.3VIO标准才能设置为LVCMOS33。如果两者不一致轻则逻辑电平不识别重则长时间工作后IO损坏。原理图上一般会标注bank电压比如标注BANK3_VCCIO_3V3这时候就能确定IO标准了。第二去耦电容的位置。对于100Mbit以上的Flash数据翻转时瞬间电流比较大如果去耦电容距离VCC引脚太远或者容值不足高速读写时可能出现电压跌落表现就是偶发性读错误。原理图设计阶段很难改但你在做板级验证时心里要有这根弦。第三某些板子会把Flash的VCC和FPGA配置相关的专用电源如VCCIO_PLL分得很细。如果原理图上有跳线帽或者磁珠隔离先确认跳线帽默认状态是否让电源真正到达了Flash。我遇到过一块板子Flash单独用一路LDO供电LDO输出默认是关断的要软件拉高使能脚才有电。这种隐性开关在原理图上往往只有一行标注不仔细看就会漏掉。3. 从原理图到引脚约束把图纸上的网络名翻译成FPGA管脚3.1 为什么必须核对Bank电压与IO标准FPGA的IO口是按bank分组的每个bank有独立的供电引脚VCCIO同一个bank里所有IO只能使用同一种IO电平标准。QSPI Flash信号如果接在3.3V的bank上那这些引脚的IO标准就必须是3.3V LVCMOS或者3.3V LVTTL不能在一个bank里混用3.3V和2.5V。这个规则看似基础但实际项目里翻车率极高因为有些FPGA开发板为了兼容各种外设会把Flash信号分散在不同bank。另外还要注意not all good pins。FPGA的很多引脚有多重复用功能比如专用时钟引脚CCLK、配置状态引脚nSTATUS、nCONFIG、JTAG引脚TMS、TCK这些引脚尽管也可以当作普通用户IO用但有条件限制比如必须经过配置或禁止用于某些功能。如果你的QSPI Flash信号恰好占用了这些特殊引脚Pin Planner里会给出警告严重时编译都过不了。所以最稳妥的做法是先打开Quartus的Pin Planner导入原理图上的引脚分配然后让软件自己跑一次引脚合法性检查。3.2 引脚分配实操Quartus Pin Planner与qsf文件写法引脚分配有两种方式工程小的时候直接图形界面操作工程大了或者要版本管理直接写qsf文件更高效。所谓qsf文件就是Quartus工程设置文件里面每一行是一条设置命令。典型的QSPI约束片段如下set_location_assignment PIN_AB23 -to qspi_cs_n set_location_assignment PIN_AA25 -to qspi_clk set_location_assignment PIN_AC24 -to qspi_dq[0] set_location_assignment PIN_AD25 -to qspi_dq[1] set_location_assignment PIN_AE26 -to qspi_dq[2] set_location_assignment PIN_AF25 -to qspi_dq[3] set_instance_assignment -name IO_STANDARD 3.3-V LVCMOS -to qspi_cs_n set_instance_assignment -name IO_STANDARD 3.3-V LVCMOS -to qspi_clk set_instance_assignment -name IO_STANDARD 3.3-V LVCMOS -to qspi_dq[0] set_instance_assignment -name IO_STANDARD 3.3-V LVCMOS -to qspi_dq[1] set_instance_assignment -name IO_STANDARD 3.3-V LVCMOS -to qspi_dq[2] set_instance_assignment -name IO_STANDARD 3.3-V LVCMOS -to qspi_dq[3]这里的PIN_AB23是一个示例实际操作时以你的原理图为准。注意qspi_dq[3:0]做成总线比分开取名干净很多后续在Verilog代码里用dq[3:0]直接索引qsf和RTL模块的端口命名保持完全一致能少掉很多低级错误。从原理图反查引脚时我建议按表格整理一份中间清单包含四列网络名原理图上的、FPGA引脚编号原理图上标注的、FPGA所在Bank、IO电平标准。整理完再往qsf里抄而不是看一眼原理图抄一个引脚那样容易漏。3.3 容易被忽略的配置脚冲突在Altera/Intel FPGA里AS配置接口和QSPI Flash关系密切。特别是当Flash承担着FPGA配置功能时DCLK、DATA0、CS_n这几个信号在原理图上的命名经常是AS_DCLK、AS_DATA0、AS_CS_n它们不仅要连到Flash还要连到FPGA的专用配置引脚。这种情况下如果用户代码里想复用同一颗Flash做数据存储读写引脚配置就要特别小心。我的经验是配置用的Flash它的DCLK和DATA0引脚在用户设计里尽量不要当作普通IO去读因为FPGA配置完成后这些引脚可能仍处于专用模式或者受nCEO、MSEL等状态影响。如果你想做的产品既要配置又要运行时读硬件设计时就应该把Flash放在普通IO挂载配置则通过其他方式比如主动串行或者干脆选用支持配置数据分时复用的专用Flash控制器方案。这些在原理图阶段就要想清楚而不是等到代码阶段再改。4. 工程搭建全流程Quartus里从零建起一个可综合的项目4.1 新建工程的器件选择与EDA设置工程搭建这一步不同版本的Quartus界面略有差异但核心流程一致File - New Project Wizard先设置工程目录和工作目录然后选择器件。器件选择时如果你手头是Cyclone系列最好按具体型号、封装、速度等级来选比如EP4CE15F17C8选错封装后续引脚分配会对不上。工程目录方面我的习惯是新建一个主文件夹下面分srcRTL源文件、sim仿真文件、con约束文件、doc原理图、数据手册四个子目录。这样工程一多不会乱而且Quartus工程文件.qpf、.qsf放在根目录生成的编译产物不要混在src里。有些初学者喜欢把所有文件堆在一个文件夹吃过几次版本混乱的亏后会明白这个习惯有多重要。工程向导里还有EDA Tool Settings。如果你装了ModelSim或QuestaSim可以在这里指定仿真工具和语言标准Verilog建议选SystemVerilog或者Verilog 2001。不指定也可以后面每次手动打开仿真器也行但指定了Quartus能在编译后一键启动仿真省时间。语言标准这里有个坑选Verilog-1995会不支持一些现代语法比如always_comb、带符号的$signed所以尽量选Verilog-2001以上。4.2 顶层模块端口设计先画好模块框图再写代码工程建好之后别急着敲代码。我强烈建议先在纸上或者任意画图工具里画出顶层模块的框图左边是外部输入右边是外部输出中间是你的控制器。针对QSPI Flash顶层端口通常是下面这些module qspi_ctrl_top ( input wire clk, // 系统时钟例如50MHz input wire rst_n, // 异步复位低有效 // 用户接口 input wire rd_start, // 读请求 input wire [23:0] rd_addr, // 读地址 output wire [31:0] rd_data, // 读数据 output wire rd_done, // 读完成 // 物理接口 output wire qspi_cs_n, output wire qspi_clk, inout wire [3:0] qspi_dq );这里qspi_dq[3:0]是三态总线内部必须有一个输出使能信号控制何时把数据驱动到总线上、何时释放总线让Flash输出。很多新手在这里犯迷糊以为inout就是双向引脚直接赋值就行。实际上三态总线的驱动逻辑是assign qspi_dq[0] dq_oe ? tx_data : 1bz; assign rx_data qspi_dq[0];读Flash时dq_oe拉低把FPGA输出置为高阻让Flash驱动总线写命令和写地址时dq_oe拉高FPGA把数据推出去。这个三态切换是QSPI控制器的灵魂后面写代码时几乎每个状态都在处理它。4.3 例化与约束让Quartus认识你的QSPI引脚顶层模块写好后把它设为工程顶层实体右键工程 - Set as Top-Level Entity。然后打开Pin PlannerAssignments - Pin Planner会看到左边列出了你顶层模块的端口名右边是器件封装图。根据qsf文件里的位置约束软件会自动把端口和封装引脚对应起来如果没有写qsf也可以在Pin Planner里手动点击一个引脚然后分配。这一步有个验证技巧先编译一次看Fit/Place Route有没有报引脚不合法。Quartus的Fitter会检查你的引脚是否与专用功能冲突、IO标准是否和bank电压一致。如果有错误会明确告诉你Pin XX is reserved for JTAG或者IO standard LVCMOS18 is not supported on this bank这些都是宝贵的信息比你自己瞎猜快得多。约束文件在Quartus里就是qsf默认在工程根目录。你既可以通过Pin Planner图形操作也可以直接用文本编辑器打开qsf追加设置两种方式等效。但要注意qsf文件的端口名必须和顶层模块端口名完全一致多了或少了一个[0]都不行。我有一次把qspi_dq[0]写成了qspi_dq0编译直接报端口不存在排查了半天非常低级的错误。5. 读写流程的思路状态机比你想的更简单5.1 QSPI Flash的指令集读、写、擦除、状态寄存器原理图和工程都搞定后接下来就是为第四讲手写代码做逻辑准备。QSPI Flash的控制并不复杂核心是掌握几条常用命令命令操作码功能说明读JEDEC ID0x9F读回厂商ID和器件ID上板后第一个要跑的命令写使能0x06把状态寄存器WEL位bit1置1写、擦除前必须发读状态寄存器0x05轮询bit0BUSY位判断是否忙读数据0x03普通读x1模式一次可读任意字节快速读0x0B带dummy周期速度更快扇区擦除0x20按4KB扇区擦除把数据位从0变1页编程0x02按页写一页通常256字节只能把1变0命令格式基本都是先发1字节操作码再发3字节地址高字节在前然后是需要的数据。写入前必须先擦除因为Flash的物理特性决定了编程操作只能把1写成0只有擦除操作能把0恢复成1。这是一个反复被忽视的细节。5.2 状态机框架发送命令、地址、数据的字节序设计QSPI控制器的常见实现方式是Mealy型状态机状态跳转围绕着当前发的是什么内容来组织。最简单的一组状态包括IDLE等待用户发起读写请求同时监控rd_start或wr_start信号。SEND_CMD把操作码按位送到DQ上一个时钟发1bitx1模式或4bitx4模式。这里用到的移位逻辑就是最核心的部分。SEND_ADDR按地址从高位到低位逐bit/逐4bit发送。TRANSFER_DATA读写数据阶段。WAIT_IDLE发完命令后轮询状态寄存器的BUSY位直到Flash空闲。以x1模式写使能为例指令是单字节0x06状态机核心发送逻辑大致是这样// 8bit命令逐位发送 reg [2:0] bit_cnt; wire [7:0] cmd 8h06; always (posedge clk or negedge rst_n) begin if (!rst_n) begin bit_cnt 3d0; dq_oe 1b0; end else if (state SEND_CMD) begin dq_oe 1b1; qspi_dq[0] cmd[7 - bit_cnt]; if (bit_cnt 3d7) begin bit_cnt 3d0; state IDLE; end else begin bit_cnt bit_cnt 1b1; end end end这里要注意的是数据方向每bit数据需要在时钟的某个边沿驱动出去。QSPI Flash一般在时钟上升沿采样输入数据所以我们可以在上升沿到来之前把数据准备好或者用下降沿更新数据。这个对准沿的问题如果在仿真里发现数据错位优先查这里。5.3 关于时钟频率和分频的选择QSPI Flash的SCK最高频率和具体的操作模式相关。W25Q128在普通读0x03模式下最高50MHz在快速读0x0B模式下最高133MHz不同厂商有差异但工程上不要顶着极限跑我一般选择把系统时钟分频后给SCK。比如系统是50MHzFlash规格是104MHzSCK直接给50MHz就完全够用如果是100MHz系统时钟Flash跑50MHz模式那SCK就要二分频。分频在RTL里实现很简单但有个关键点SCK和系统时钟的相位关系。如果你用系统时钟做状态机然后输出SCK系统时钟的二分频那么发送数据时一定要保证状态机的信号变化发生在SCK的下降沿附近给Flash留出足够的数据建立时间。一个偷懒且稳定的做法是让状态机在系统时钟下降沿更新数据SCK用系统时钟上升沿产生。这样数据在SCK上升沿到来前就已经稳定了。6. 上板调试实录我踩过的三个典型坑6.1 引脚分对了但读回全FF这是QSPI调试最典型的现象ID读不回来读什么都是0xFF。我遇到过几次原因各不相同但排查顺序高度一致。第一步用万用表和示波器确认芯片供电正常特别是确认LDO或者DC-DC是否真的输出3.3V。第二步检查片选信号——CS_n必须严格低有效而且整个通信期间要保持低通信结束再拉高。如果状态机在发送命令和地址之间不小心把CS_n拉高了Flash会认为这次通信已经终止后面的数据全部无效。第三步检查数据线是否正确连到Flash对应引脚尤其是DQ0和DQ1有没有接反。很多原理图上Flash引脚标的是DOpin2和DIpin5在做QSPI约束时容易把DO和DQ1对应错其实DO就是DQ1复用DI才是DQ0。6.2 写数据后读出来不对问题出在擦除时序写操作失败特别容易让人崩溃因为现象千奇百怪写入0x5A读出来是0x5B某几个位不对或者写入后立刻读是对的过一会儿再读又不对。说到底都是同一个根本原因没有先擦除或者擦除后没有等待BUSY位清零。Nor Flash固化写入时只能将1编程为0如果你在已有数据的扇区上直接编程有些1无法变成0或者保持不想要的0读出来就是错乱的。正确流程是发写使能0x06、发扇区擦除0x20加地址、轮询状态寄存器直到BUSY位为0、再发写使能、发页编程0x02加地址加数据、再轮询BUSY位。这串流程少了任何一步结果都不保证。我在自己项目里还碰到过一种特殊情况擦除命令发出去后状态寄存器BUSY位确实在变但等待时间不够又继续读状态导致读到的是一个中间的0x06值误判为busy已结束。解决方法是读状态后不要急着判断连续读两次确认两次结果一致再继续下一步。6.3 用SignalTap抓内部信号是最高效的排错方式很多FPGA工程师调试时过度依赖板级示波器和逻辑分析仪其实Altera/Intel的SignalTap II逻辑分析仪IP核比外部仪器好用得多因为它直接挂在内部逻辑节点上能看到的是RTL内部信号的真实时序。配置SignalTap时需要加入想要观察的信号列表添加触发条件然后重新全编译并下载到板子。比较常用的触发方式是state信号等于某个关键状态或者rd_start上升沿。抓到的波形可以导出成VCD文件在ModelSim里和仿真波形对比定位是哪一步状态跳转出了问题。用SignalTap有一个注意点它会占用片内RAM资源信号数量最好控制在32个以内采样深度也不用设太大2K或4K一般够用。抓QSPI时序时把SCK、CS_n、状态机状态、数据字节都加进去一次抓完整个读操作流程基本能看出所有时序问题。在这里分享一个个人习惯拿到新板子的第一件事不要写复杂的读写控制器而是先写一个最简单的状态机只发送读JEDEC ID命令0x9F并把读到的3字节数据回传到LED或串口。这个功能跑通了就证明原理图连接、引脚约束、工程配置、时钟复位全链路都是通的。后续再开发页编程、扇区擦除、x4快速读这些高级功能都建立在这个小功能的地基上。很多新手一上来就写完整读写器出问题了都不知道是底层连接的问题还是逻辑实现的问题排查成本反而更高。