第一次参加蓝桥杯FPGA赛道积分赛那会儿,我在ModelSim里把一个计数器工程仿真得干干净净,波形拉出来一点问题没有,心想这波稳了。结果程序下载到板子上,数码管直接给你表演一团乱码,按键轻轻一碰,数值自己连跳好几下。那天晚上我坐在实验室里反复查代码,最后才发现问题不在功能逻辑,而在复位、消抖和引脚约束这些“仿真里看不见”的东西。也就是从那次之后我才真正意识到:FPGA赛道拷问人的,根本不是你会不会写Verilog,而是你愿不愿意拿真实硬件当对手,把仿真思维切换成工程思维。
这篇文章写给正在备赛蓝桥杯FPGA赛道积分赛的同学,尤其是“代码能写、仿真能过、一上板就懵”的那批人。我会从赛制评分逻辑、备赛知识地图、上板调试方法、典型赛题拆解、时间规划与避坑经验五个方面,把这两个月里值得做、值得学、值得注意的事情一次性讲透。
1. 先把积分赛的评分逻辑摸透,再决定怎么刷题
1.1 积分赛的定位:比的是覆盖面和熟练度
我问过不少准备参赛的同学,大部分人连“积分赛到底怎么算分”都没搞明白,上来就闷头刷题,这是最容易跑偏的地方。不同年份的积分赛规则会有调整,但总体逻辑通常是在省赛或正式决赛之前增加一个积分排名环节,用客观题、上板题等形式筛选出更有综合能力的选手。如果当年的赛程里没有积分赛,也会有一套对应的评分筛选流程。
真正影响备赛策略的,是积分赛的出题逻辑。和决赛动辄给你一个完整应用场景不同,积分赛更像“广撒网”,题量不低,知识点覆盖很杂——今天考你一个数码管动态显示,明天考你一个SPI时序,后天突然问一道关于FPGA内部资源的客观题。这就要求备赛思路不能是“我擅长做某个模块就只做某个模块”,而是把所有基础模块都过一遍,做到看到题就能反应出大概的RTL结构。
我的建议是:不要押题。把历年真题按“基础语法、时序逻辑、外设接口、片上资源、进阶方向”五类去归类,哪类题量少就补哪类。刷完题目之后,再去复盘官方公布的评分办法,看看到底更看重功能完成度,还是更看重上板稳定性,然后再调整做每一道题时花的时间比例。
1.2 你拿分的四个隐藏维度
很多人以为积分赛只要功能正确就能拿高分,实际上评分维度往往比你想的要多。根据我的参赛和帮学弟复盘的经验,下面这四个维度最容易丢分:
| 评分维度 | 常见丢分点 | 应对策略 |
|---|---|---|
| 功能性 | 上板后个别按键失灵、数据显示范围不对、边界情况没处理 | 用边界值测试法,把最小值、最大值、临界值都测一遍 |
| 时序与接口指标 | 通信时序不满足手册要求、刷新率过低导致明显闪烁 | 严格按器件手册时序读写外设,不要凭感觉编程 |
| 资源与代码规范 | 综合报告资源占用过高、锁存器被大量推断、代码无注释 | 写模块时就知道大概会占多少逻辑单元,避免到处是latch |
| 稳定性 | 连续运行几分钟后状态错乱、复位频繁按下后恢复不了 | 上板连续跑稳定性测试,重点验证复位和时钟正常 |
功能性是最基本的,但很多同学在“功能正确”之后就不再优化了。积分赛如果想拿高分,一定得在时序和稳定性上下功夫。比如UART的波特率计算,误差超过2%就可能传错,尤其当FPGA系统时钟不是整数倍波特率时钟时,误差问题会被直接放大。
1.3 客观题不能临时抱佛脚
积分赛里通常包含一部分客观题,范围从数电基础到FPGA内部机制都有。客观题靠的完全是平时的知识积累,临时背几个题根本没用。常考的点包括:
- 组合逻辑与时序逻辑的区别、阻塞赋值与非阻塞赋值的混用后果
- 锁存器与触发器的结构区别,以及为什么组合逻辑里写不全if/else会综合出锁存器
- 二进制、十六进制、BCD码的转换
- 竞争冒险与毛刺的成因
- 静态时序分析里的建立时间、保持时间概念
- FPGA内部基本结构:查找表(LUT)、触发器(FF)、BRAM、DSP、PLL、IO单元
- 布局与布线的区别,以及它们对时序收敛的影响
- 跨时钟域同步、亚稳态处理
这个列表越往后越是很多人的盲区。尤其是“布局和布线有什么区别”这类题,经常在客观题里出现。简单说,布局(Fitting)是把设计中的逻辑单元放置到FPGA内部合适位置的过程,布线(Routing)则是将这些放置好的单元之间的信号连接起来。布局不好会导致布线绕远路,建立时间很难满足。
2. 备赛知识地图:从数电冲到图像处理,控制在两周内扫完
2.1 让你快速上手的四块地基
FPGA赛道的知识体系说大不大,说小不小。我看到不少同学一上来就想学图像处理、MIPI、LVDS这些高级内容,结果连基础的寄存器流水都没写熟练,做题时处处卡壳。我建议先把下面四块地基一次性打牢,再谈进阶:
- Verilog建模能力。要能不查书写出模块、assign组合逻辑、always时序逻辑、参数的传递、generate语句的简单用法。
- 时序逻辑基本功。计数器、分频器、移位寄存器、拨码开关边沿检测,这些必须形成肌肉记忆。
- 状态机。掌握“一段式、二段式、三段式”之间的区别,能用三段式写一个负责的通信时序状态机。比赛中很多外设驱动本质上就是一个状态机,比如SPI读写、UART接收、ADC采样控制。
- 避免隐式锁存器。在组合逻辑always块里,每个条件分支都要赋值;时序逻辑尽量使用非阻塞赋值。这些是综合题和上板稳定性共同需要的基本素养。
这四块内容用两周时间完全可以过完,关键别停留在看书,要自己动手写、自己仿真、自己上板。
2.2 外设驱动是积分赛真正的主战场
积分赛上板题的大头在外设驱动,而且花样翻新。我把高频考点整理了一下,按出题热度排序:
闪烁不同的频率是否对指定寄存器控制有效。
按下按键能让一个计数器开始、暂停、清零。
数码管是必考项,无论是显示计数、显示温度、显示频率,都离不开它。动态显示的刷新频率、位选段选时序,以及消隐处理,都是容易出细节的地方。
按键消抖和边沿检测,看着简单,但很多人在实际工程里做得并不严谨。
UART收发。比赛经常要求把FPGA的数据发到上位机显示,或者接收上位机指令来控制硬件。
SPI接口的ADC/DAC/EEPROM读写,以及I2C接口的温度传感器读取。
PWM调光、调速、驱动蜂鸣器发声。
如果时间紧张,建议把精力按这个优先级分配。数码管、按键、UART是三个绝对绕不开的模块,SPI和I2C是拉开差距的分水岭,PWM是很多综合性题目的基础。
2.3 工程能力层:约束文件、PLL、ADC和内部机制
外设驱动之外,有三个工程能力很容易被忽略,但几乎每道上板题都会用到:
一是引脚约束文件(QSF)。下载到板子上的工程,必须把顶层端口映射到物理引脚上。很多同学第一次上板不亮,就是没有写QSF文件,或者写错引脚导致Quartus报错。关于QSF的具体写法,我放在第3章详细讲。
二是时钟管理。系统时钟进去之后,经常需要PLL分频或倍频。如果不使用PLL,直接拿主时钟分频,有时候会因为时钟偏斜导致时序问题。注意蓝桥杯赛用板卡(通常是DE10-Lite这类Max 10平台)里,PLL的使用要在Quartus的IP Catalog里配置,配置完会生成一个pll.v例化文件。
三是片上ADC和内部结构认知。Max 10芯片自带模数转换器,通过IP核例化后可以直接采集外部模拟电压,这在温控、信号采集类赛题里非常关键。同时,理解LUT、FF、BRAM、DSP这些内部资源,既能做客观题,也能帮助你估计算法实现会占多大资源。
2.4 这么多人卡住的定点数,其实没那么玄
很多备赛er看到“定点数”三个字就头大,尤其是看到网上说DDS、PID、图像处理都要用定点数时,更是直接劝退。其实定点数的核心就一句话:用整数运算模拟小数运算。
举一个最常用的例子。假设我们要在FPGA里表示一个温度25.5度,直接用整数,只能表示25或26,丢了0.5。怎么办?一种办法是放大:把25.5乘以10,得到255,在硬件里传输处理,显示时再除以10。另一种更通用的办法是Q格式,比如Q8.8,表示整数占8位、小数占8位,寄存器里存的数值除以256才是真实值,所以25.5度存到寄存器里约等于6528(25.5×256)。
做定点数运算时,最关键的是三个操作:乘法后要对齐小数点位置、加减法要保证两个数的小数点位置一致、最终输出时要正确截位。很多人算着算着就出问题,大多是忘了这些规则。备赛阶段可以专门写一个简单的定点数乘法模块练手,比如把0.1到0.9和3.3相乘,用Q8.8格式做一遍,再对比真实结果。
定点数本身不难,难的是没理解“为什么硬件里不直接写浮点数”。其实FPGA的DSP硬核往往不支持任意浮点数运算,而浮点IP核又消耗资源和时间。定点数只涉及整数加减乘,速度更快、资源更省,这也是竞赛题目和实际工程普遍采用定点化的原因。
3. 上板调试是第一生产力:从数码管工程拆出完整链路
3.1 从原理图到QSF引脚约束,第一道门槛
我第一次把工程下载到板子上时,LED一个都不亮。折腾了一晚上,最后发现是引脚约束没写对。很多同学用开发板自带的示例工程时,Quartus已经帮你做好了管脚分配,一旦自己新建工程,就完全忽略这一步,结果板子没有任何反应。
要做引脚约束,核心是学会查开发板原理图。别怕原理图几百页,实际只需要找到你用的几个外设对应的网络号。比如DE10-Lite板卡上,SW[0]连接在某个FPGA引脚,HEX0的SEG0连接在另一个引脚,这些信息都在原理图的原理图页面里标注。找到网络名后,在QSF文件里写两行即可:
set_location_assignment PIN_AB28 -to sw[0] set_location_assignment PIN_AA24 -to hex0_seg[0]如果板卡需要配置IO标准(比如3.3V),还需要加上:
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to sw[0]关于QSF,最容易踩的坑是:
- 引脚名和顶层端口名不一致,导致引脚分配不生效
- 复用了板卡上已经被其他功能占用的引脚
- 漏配输入时钟引脚,导致PLL没有参考时钟
- 同时配置了普通IO和ADC模拟输入,造成冲突
每次新建工程后,第一件事就是把原理图里用到的引脚全部在QSF里写清楚,编译时再仔细看Quartus的Pin Planner报告里有没有冲突或未分配警告。
3.2 按键消抖和数码管扫描:细节在哪里
仿真软件里看不到按键的机械特性,但在真实板子上,按键按下去的瞬间会产几十毫秒的抖动,如果不做消抖,计数器就会跳好几下。最稳妥、最容易理解的消抖方法是“稳定计数”:当检测到按键电平变化时开始计时,比如持续20ms都是稳定电平,才认为按键真的被按下,同时只产生一个脉冲。
reg [19:0] cnt; reg key_reg1, key_reg2; wire key_sync = key_reg2; always @(posedge clk) begin key_reg1 <= key_in; key_reg2 <= key_reg1; end always @(posedge clk) begin if (key_sync != key_idle) cnt <= 20'd0; else if (cnt < 20'd999_999) cnt <= cnt + 1'b1; end // 当cnt计数到稳定时间后,再检测一次边沿,产生单脉冲这里我先做了两级触发器同步,目的是消除亚稳态带来的随机值,然后再做消抖。很多人直接把按键接进计数器,仿真没问题,上板必翻车,原因就在这里。
数码管动态显示同样有细节。四位数码管共享一组段选信号,靠位选信号在很短时间里轮流点亮。常见错误有两种:一种是扫描频率太低,肉眼能看到明显的闪烁;另一种是切换位选瞬间没有先关闭段选,导致“鬼影”——相邻的数码管会短暂显示出上一个数字的残影。
合理的扫描频率是每位数码管刷新间隔1ms左右,整体四个数码管循环一遍需要约4ms,即刷新率约250Hz,肉眼完全感觉不到闪烁。切换位选时,先给段选送一个全灭值(比如8’hFF),再切换位选,再送数据,就能避免鬼影。
3.3 一个典型故障的完整排查链路
上板调试最怕的不是报错,而是“编译正常、功能不对”。我印象很深的一次是帮一个学弟查问题:程序下载后,数码管显示的数字每隔几秒莫名其妙跳一次,有时还跳回零。那位学弟在Quartus里把代码检查了好多遍,始终没找到原因。
实际上那个问题在综合报告里就出现过提示:assign语句中存在组合回路,而且分频模块没有连接到PLL输出,进而导致内部计数器在某一时刻因为建立时间不够产生了亚稳态。我把排查过程完整复述一下,你可以直接复制这套链路去查自己的工程:
- 先看Quartus编译报告的Critical Warnings,重点找有关时钟、同步复位、锁存器的警告。
- 再用SignalTap II或者Quartus自带的In-System Memory Content Editor观察内部信号的实际值,尤其要看计数器、状态机当前状态。
- 对照原理图,确认引脚约束正确,再用万用表或逻辑分析仪确认外部输入信号确实到了FPGA引脚。
- 把“可能是异步复位导致的问题”作为重点怀疑对象,看代码里是否有直接使用外部按键作为复位的写法。
- 修复后写一个最简单的LED翻转测试,验证板子和下载链路的健康。
最后一步看起来很基础,但非常有用。很多人排查不下去,是因为连“FPGA到底有没有正常跑起来”都没确认,就急着查复杂逻辑。先把最小系统点亮,再逐步加入复杂功能,永远是最快的调试路径。
3.4 工程文件怎么组织才能不加班
备赛后期要做的题目越来越多,如果所有代码都堆在一个顶层文件里,调试会非常痛苦,改一个功能就要重新编译十几分钟。
我的建议是建立一套固定的工程目录结构:
- 顶层文件:只做例化和端口连接,不放具体逻辑
- 时钟模块:PLL/分频输出各时钟,统一管理复位信号
- 外设驱动目录:每个外设一个模块,例如
key_debounce.v、seg_display.v、uart_tx.v、spi_master.v - 功能逻辑目录:状态机、计数器、算法模块
- 约束目录:QSF和SDC文件,统一命名方便查找
模块之间通过接口信号连接,尽量少用全局变量。这样做还有一个好处:同一套数码管驱动、按键消抖模块可以在多个赛题里复用,考试或积分赛时直接“搬过去”就能用,省下大量重复调试时间。
4. 三道高频赛题拆解:频率计、温控风扇、DDS信号发生器
4.1 数字频率计:闸门、计数和BCD转换的取舍
频率计是考察计数器、定时器、数码管显示以及数值转换的综合题,出题率相当高。基本需求是测量外部方波频率,并在数码管上显示,测量范围可能到几十kHz甚至MHz量级。
先讲测量原理。最简单的方法是“固定闸门计数法”:用一个基准闸门信号(比如1秒)控制计数窗口,闸门为高电平时让被测信号导通到计数器,闸门结束后锁存计数值,这个数值就等于频率。为了提高显示刷新率,有时会把闸门时间设为0.1秒,计数值乘以10再显示。
但直接乘10有讲究。如果被测频率比较低,0.1秒内只计了几个脉冲,乘10后误差依然存在,所以更可靠的做法是“等精度测量”:同时对被测信号和基准时钟计数,通过基准时钟的时间间隔来推算频率。在积分赛的题量压力下,等精度测量未必一下子写出来,但作为优化方向值得掌握——如果赛题要求高精度,这个设计就是加分项。
显示部分最容易踩坑的是进制转换。计数器里默认是二进制,而数码管显示需要十进制,不能直接把二进制数劈开,得做二进制到BCD码的转换。常用两种方法:
- 多次除以10取余,用除法器IP或自己写长除法,资源消耗较大
- 用移位加3算法(Double Dabble),代码量不大,适合FPGA实现
BCD转换做完之后再送数码管模块显示,这时还要注意显示位宽和数据范围。若测量范围超过9999,数码管至少要5位以上,否则高位数据会直接丢失。
4.2 温控风扇:ADC测温+PWM调速的闭环套路
温控风扇类题目几乎每年都有变体:读取板载或外挂温度传感器,根据温度区间控制风扇转动,并动态调节风扇转速。它把ADC、比较器、PWM、数码管显示集中到了一起,是最典型的“外设全家桶”综合题。
第一步是获取温度数据。如果用板载ADC,需要在Quartus里例化ADC模块。Max 10的ADC输入通道是专用的模拟引脚,不能当作普通IO使用,配置时要注意区分。ADC采样出来的原始数据往往是二进制电压值,比如0到4095对应0V到3.3V,再用电压值和温度传感器的比例关系换算成实际温度。很多同学卡在“为什么ADC读出来全是0或满量程”,其实多半是模拟引脚配置错误,或者采样时序没满足建立时间。
由于ADC本身有噪声,直接拿单次采样值做判断,风扇转速会在阈值附近来回跳动。工程上常见的做法是做“去抖动”:要么对连续多次采样求平均,要么对温度比较结果加滞回区间。滞回逻辑的代码其实很简单:
if (temp_raw > 26_500) begin // 26.5度的Q8.8表示 fan_en <= 1'b1; // 超过阈值开启风扇 end else if (temp_raw < 24_000) begin // 低于24度才关闭 fan_en <= 1'b0; end这样风扇开启阈值和关闭阈值错开,避免在25度附近反复开关。
PWM调速部分,核心是产生占空比可调的方波:用一个计数器做PWM周期,另一个比较值寄存器决定高电平时间。PWM频率不宜太低,否则风扇会有明显的嗡嗡声,一般选择20kHz以上人耳不太敏感的频率。占空比根据温度线性或分段映射,映射关系要提前在表格里算好,直接提供一个查找表给比较器使用。
4.3 DDS信号发生器:相位累加器+ROM查找表的经典结构
信号发生器是很多FPGA赛区决赛和积分赛的压轴题目,热度一直很高,热搜里的“fpga信号发生器ego1”也能看出大家对这个题目的关注。核心需求是通过按键设置频率,输出指定波形(正弦波、方波、三角波等),经过板上DAC或数字输出模块送到示波器。
DDS(直接数字频率合成)的核心思想特别“省”:先在ROM里存好一个完整周期的波形采样点,比如256个点,然后在每个时钟周期里让相位累加器增加一个频率控制字,相位累加器的高位作为ROM地址去查表,查出来的数据就是波形输出。
频率计算公式是:
F_out = F_word × F_clk / 2^N其中N是相位累加器的位宽,F_word是频率控制字。反过来,想输出指定频率,频率控制字就是:
F_word = round(F_out × 2^N / F_clk)比如系统时钟50MHz,相位累加器32位,要输出1kHz正弦波,F_word = 1000 × 2^32 / 50_000_000 ≈ 85900。这个计算必须用定点数或整数运算来完成,也就是我前面提到的定点数实际应用场景。
ROM里存储的波形是定点数格式的采样值。如果DAC是8位,则ROM数据范围是0到255,设计时把正弦波偏移到中间值128,幅度设为127。如果DAC是16位,数据范围会对应到-32768到32767,同样需要定点化处理。关键是ROM读出来的数据和DAC位宽、输出极性对齐,否则显示波形会整体偏移或截断。
DDS架构本身不难,难的是扩展功能:通过键盘修改频率、切换波形、调整幅度。修改频率时注意频率控制字在运行时更新,需要做跨时钟域同步;切换波形时,ROM地址对应的波形表也要切换。备赛时建议先设计好整体模块框图再动手写代码,否则很容易在“波形切换的瞬间出现毛刺”这类问题上浪费大量时间。
5. 备赛节奏、常见翻车现场和一套适合自己的资源清单
5.1 两个月的备赛时间怎么排
积分类备赛最怕“前松后紧”,前面看教程看了一个月,后面发现真题根本做不完。我按两个月倒排了一个比较可行的时间表:
- 第1~2周:完成基础语法复习,把组合逻辑、时序逻辑、状态机、按键消抖、数码管、UART收发各写一遍可运行的工程。这个阶段以“能上板点亮”为完成标准。
- 第3~4周:集中做历年真题,优先做省赛和积分赛真题。每个题目先自己构思模块划分,再写代码、仿真、上板,最后再对照优秀例程优化。
- 第5~6周:专项突破。根据真题暴露的薄弱点,集中练习SPI、I2C、ADC/PWM、DDS、频率计等中高阶题,每道题都留一个可复用的模块。
- 第7周:全真模拟。按比赛时长掐表做题,不做完不允许看答案。模拟的意义不在分数,而在发现“自己卡在哪个环节耗时最长”。
- 第8周:整理错误本,重新做一遍做错或做慢的题。
竞赛准备不是看谁看的教程多,而是看谁踩过的坑多。认真做十套真题,比反复看二十个教程有用得多。
5.2 我亲眼见过的六种常见翻车现场
下表是帮同学调试时收集的高频问题,每一条我都排过或协助排查过,对照着查能省很多时间:
| 症状 | 根因 | 解决思路 |
|---|---|---|
| 编译成功但下载后无反应 | 引脚约束缺少或错误、时钟引脚未接入 | 检查QSF约束,用LED闪烁测试验证基础链路 |
| 数码管闪烁或亮度不均 | 扫描频率太低、位选切换时段选未消隐 | 缩短每位置刷新周期,先灭段选再切位选 |
| 按键触发一次却连续反应 | 未做消抖、消抖时间太短 | 保持20ms以上稳定计时,只产生单脉冲 |
| SPI读回数据全为0或全为1 | 时钟极性和相位配置错误、片选时序不对 | 对照器件手册,逐个检查CPOL/CPHA和CS拉低时间 |
| 程序运行几分钟后状态错乱 | 跨时钟域信号未同步、复位存在亚稳态 | 两级触发器同步,异步复位同步释放 |
| ADC读数异常跳变 | 模拟引脚未正确配置、采样周期过短 | 确认引脚类型,延长采样间隔或对结果取平均 |
第六种问题尤其常见。ADC模块在初始化后不能立刻使用,需要等待内部稳定;连续采样也要留出足够的转换时间,否则读到的就是上一次或无效数据。
5.3 资源清单:真题、工具、板卡和补充教程
最后整理一下我实际用下来比较顺手的资源组合:
- 开发环境:Quartus Prime Lite + SignalTap II逻辑分析仪。不要用太新的版本,稳定的版本最适合比赛,环境不折腾比什么都重要。
- 仿真工具:ModelSim或Quartus自带的仿真工具,前者看波形更方便,但学会用SignalTap调试上板问题才是关键。
- 板卡:以当年赛方指定板卡为准,常见的是DE10-Lite这类Max 10平台。官方提供的Board Test System和例程是最好起点,先把示例编译下载成功,等于确认了环境和板卡都是健康的。
- 历年真题:蓝桥杯官网历年赛题、各平台公开的省赛题和模拟题。收集渠道可以是官方公众号、社区分享、备赛群,但注意别只存不看。
- 补充教程:黑金FPGA开发板的配套教程对学习Verilog和常用外设非常友好,虽然板子和赛板不完全一样,但数码管、UART、SPI这些知识完全相通。可以在自己板卡上动手验证时,再对照官方板卡手册确认具体引脚和资源差异。
- 进阶方向:如果精力允许,可以看一点图像处理(灰度化、二值化、中值滤波)和高速接口(LVDS接收、MIPI)的知识。国赛题偶尔会朝这个方向出,但积分赛阶段优先级不高,不要因此挤占基础学习时间。
最后再分享一点自己的感受。积分赛最大的特点,是它逼着你在有限时间里快速接触大量硬件细节。你会在一个晚上因为一根引脚线不对而抓狂,也会在凌晨因为终于让DDS输出一个漂亮正弦波而兴奋。备赛期间一定要养成记录的习惯——每个错在哪个地方、卡了多久、怎么解决的,都记下来。等到比赛时翻看自己的记录,会发现那些曾经让你头疼的坑,早就变成你最顺手的捷径了。