☰
FPGA上FSK调制解调实战:VHDL代码、Quartus仿真与避坑指南
2026/10/21 5:46:38 网站建设 项目流程

简介:这份PDF是一份基于FPGA的FSK调制解调课程设计报告,面向电子信息工程、通信工程等专业的本科生及FPGA初学者,帮助读者理解数字调制原理并完成VHDL硬件实现。报告以Quartus II为开发平台,系统讲解FSK调制与解调的基本原理,给出载频f1=12.5MHz、f2=2MHz、调制信号1MHz的具体参数设计,并附有VHDL调制与解调程序、仿真测试结果及结论分析,涵盖绪论、原理、设计方案、测试与参考文献等完整章节。资源包为单个PDF文件,大小约936KB,内容结构清晰,便于按章节查阅与课程设计参考。目前已有142人学习浏览。读者可从中获取完整的FSK调制解调设计思路、VHDL代码框架与仿真验证方法,理解无需载波恢复、抗幅度非线性干扰强等系统优势,适合作为课设报告模板或FPGA通信系统入门实践资料。

1. 从一份课程设计报告说起:FPGA 上跑 FSK 到底能落地到什么程度

一份基于 FPGA 的 FSK 调制解调课程设计报告,核心参数写得明明白白:载频 f1=12.5MHz、f2=2MHz,调制信号频率 1MHz,用 VHDL 在 Quartus II 里完成调制与解调的全流程设计。很多人看到“课程设计”四个字就划走了,觉得不过是学生作业。但如果你手头正好有一块 FPGA 开发板,想找一个结构清晰、代码量可控、能完整跑通调制解调链路的入门项目,这份资料的价值恰恰在于它把 FSK 的调制、解调、仿真、RTL 综合这条链路走完了,而且每一步都有波形和电路图佐证。

它适合三类人:一是正在学数字通信或 EDA 课程、需要一份可复现参考的在校生;二是从嵌入式软件转 FPGA 逻辑、想拿一个通信模块练手的工程师;三是需要快速验证 FSK 方案可行性、不想从零搭 testbench 的从业者。不适合指望拿它直接做产品级通信基带的人——它的定位是教学与原型验证,不是量产 IP 核。下面按“原理选型 → 代码拆解 → 仿真验证 → 避坑 → 进阶”的顺序,把这份资料拆开讲透。

2. FSK 调制解调的原理与方案选型:为什么是键控法而不是直接调频

2.1 调制端:两个独立分频器 + 二选一开关

FSK 调制的本质是用基带数字信号去控制载波频率的切换。这份设计采用的是频率键控法,也叫频率选择法:用两个独立的分频器分别产生 f1 和 f2 两路载波,再由基带信号 X 控制一个二选一选通开关,X=0 时输出 f1,X=1 时输出 f2。

为什么不用直接调频法?直接调频法是用基带信号去改变振荡器谐振回路的电容或电感值,从而改变振荡频率。这种方法产生的 FSK 信号相位连续,但频率稳定度不高,频率转换速度也受限。在 FPGA 内部,你没法直接去“改变谐振回路参数”——那是模拟电路的做法。FPGA 里能做的就是数字分频和逻辑选通,所以键控法是更自然的选择。键控法产生的 FSK 信号频率稳定度取决于系统时钟的精度,转换速度快,波形干净,代价是相邻码元之间可能存在相位跳变,也就是所谓的“相位不连续”。对于教学验证和大多数远程自控场景,这个代价完全可以接受。

从调制方框图来看,整个链路是:系统时钟 CLK 分别送入两个分频器,分频器 1 产生载波 f1,分频器 2 产生载波 f2,基带信号 X 作为二选一开关的选择端,最终输出调制信号 Y。这个结构在 VHDL 里对应三个进程:两个分频进程加一个选通进程。

2.2 解调端:计数器 + 寄存器 + 判决器

解调端的设计思路更值得细看。这份资料没有采用传统的带通滤波 + 包络检波方案,而是用了一个纯数字的过零计数法:在固定时间窗口内统计输入信号的脉冲个数,脉冲多说明频率高,对应 f1,判决为“0”;脉冲少说明频率低,对应 f2,判决为“1”。

具体实现是:系统时钟 CLK 驱动一个 0 到 11 的循环计数器 Q,当 Q 计到 11 时,对寄存器 M 清零,同时根据 M 的当前值做判决输出。M 在输入信号 XX 的上升沿递增,也就是说 M 记录的是一个计数周期内输入信号出现了多少个上升沿。如果 M 大于 3,说明输入频率较高,判决 Y=‘1’;否则 Y=‘0’。这里有一个细节:判决发生在 Q=10 的时刻,而不是 Q=11,这是为了在计数器回绕之前留出一个时钟周期的判决窗口,避免竞争冒险。

这种解调方式的优点是纯数字、无需模拟器件、易于在 FPGA 内部实现。缺点是对时钟频率和输入信号频率的比值有要求——如果输入频率太接近系统时钟,计数器就数不准了。在这份设计里,系统时钟频率远高于 f1 和 f2,所以计数法能正常工作。

2.3 参数选择背后的约束

f1=12.5MHz、f2=2MHz、调制信号 1MHz,这组参数不是随便定的。f1 和 f2 的比值约为 6.25:1,差距足够大,解调端的计数器能明显区分。如果两个载频太接近,比如 f1=10MHz、f2=9MHz,在有限的计数窗口内脉冲个数差异只有一两个,判决就容易出错。调制信号 1MHz 意味着码元宽度 1μs,而 f2 的周期是 0.5μs,也就是说一个“0”码元内包含两个 f2 周期,一个“1”码元内包含约 12.5 个 f1 周期。这个比例关系决定了解调端计数窗口的长度——窗口太短,f2 的脉冲数太少,统计意义不足;窗口太长,码元边界会模糊。

提示:如果你要改参数,先确认三个频率之间的比值关系。经验法则是 f1/f2 至少大于 4,系统时钟至少是 f1 的 4 倍以上,否则解调计数器会数不准。

3. VHDL 代码拆解:调制与解调进程的逐段分析

3.1 调制模块 FSKTZ 的三个进程

调制模块的实体声明很简洁,四个端口:CLK 系统时钟、START 开始调制信号、X 基带信号输入、Y 调制信号输出。架构体里定义了三个信号:Q1 是 f1 的分频计数器(0 到 11),Q2 是 f2 的分频计数器(0 到 3),F1 和 F2 是两路载波信号。

第一个进程产生载波 F1:

PROCESS(CLK) BEGIN IF (CLK'EVENT AND CLK='1') THEN IF START='0' THEN Q1 <= 0; ELSIF Q1 <= 5 THEN F1 <= '1'; Q1 <= Q1 + 1; ELSIF Q1 = 11 THEN F1 <= '0'; Q1 <= 0; ELSE F1 <= '0'; Q1 <= Q1 + 1; END IF; END IF; END PROCESS;

Q1 从 0 计到 11 循环,其中 0 到 5 期间 F1 输出高电平,6 到 11 期间输出低电平。所以 F1 的占空比是 6/12=50%,分频比是 12。如果系统时钟是 150MHz,F1 就是 12.5MHz,正好对应设计参数。

第二个进程产生载波 F2:

PROCESS(CLK) BEGIN IF (CLK'EVENT AND CLK='1') THEN IF START='0' THEN Q2 <= 0; ELSIF Q2 = 1 THEN F2 <= '0'; Q2 <= 0; ELSIF Q2 <= 0 THEN F2 <= '1'; Q2 <= Q2 + 1; ELSE F2 <= '0'; Q2 <= Q2 + 1; END IF; END IF; END PROCESS;

Q2 只有 0 和 1 两个状态,F2 在 Q2=0 时输出高,Q2=1 时输出低,分频比是 2。如果系统时钟是 4MHz,F2 就是 2MHz。注意这里两个进程用的是同一个 CLK,但分频比不同,所以实际系统中需要根据目标频率选择合适的系统时钟。如果 f1=12.5MHz、f2=2MHz 共用同一个时钟,那系统时钟必须是两者的公倍数关系——12.5MHz 的 12 分频和 2MHz 的 2 分频对应的时钟频率分别是 150MHz 和 4MHz,这两个值不一致。实际实现时要么用两个时钟域,要么调整分频比。这是这份代码里一个容易被忽略的细节,后面避坑章节会展开。

第三个进程完成基带信号的 FSK 调制:

PROCESS(CLK, X) BEGIN IF (CLK'EVENT AND CLK='1') THEN IF X = '0' THEN Y <= F1; ELSE Y <= F2; END IF; END IF; END PROCESS;

每个时钟上升沿根据 X 的当前值选择输出 F1 或 F2。X=‘0’ 时输出 F1,X=‘1’ 时输出 F2。注意这里 X 的采样是在时钟上升沿完成的,所以基带信号的跳变必须与系统时钟同步,否则会出现亚稳态问题。

3.2 解调模块 FSKJT 的计数判决逻辑

解调模块的端口和调制模块类似,但内部逻辑不同。信号定义包括:Q 是 0 到 11 的循环计数器,XX 是输入信号的同步寄存器,M 是 0 到 5 的脉冲计数器。

第一个进程做两件事:对输入信号 X 打一拍得到 XX,以及对 Q 做循环计数:

PROCESS(CLK) BEGIN IF (CLK'EVENT AND CLK='1') THEN XX <= X; IF START='0' THEN Q <= 0; ELSIF Q = 11 THEN Q <= 0; ELSE Q <= Q + 1; END IF; END IF; END PROCESS;

XX <= X 这行代码看起来简单,但它是整个解调逻辑的关键。输入信号 X 是异步的,直接用它去驱动计数器 M 会引入亚稳态风险。打一拍之后,XX 就是与系统时钟同步的信号,后续逻辑可以安全使用。

第二个进程完成脉冲计数和判决输出:

PROCESS(XX, Q) BEGIN IF Q = 11 THEN M <= 0; ELSIF Q = 10 THEN IF M <= 3 THEN Y <= '0'; ELSE Y <= '1'; END IF; ELSIF XX'EVENT AND XX='1' THEN M <= M + 1; END IF; END PROCESS;

当 Q=11 时,M 清零,准备下一个计数周期。当 Q=10 时,根据 M 的当前值做判决:M<=3 输出 Y=‘0’,M>3 输出 Y=‘1’。其余时刻,只要 XX 出现上升沿,M 就加一。判决阈值设为 3 是基于 f1 和 f2 的频率比——在一个 Q 周期内,f1 的脉冲数大约是 f2 的 6 倍,取中间值 3 作为分界能可靠区分。

注意:判决时刻选在 Q=10 而不是 Q=11,是为了避免 M 在清零和判决之间产生竞争。如果你把判决条件改成 Q=11,仿真时可能会看到 Y 输出出现毛刺。

4. Quartus II 仿真验证:从波形文件到 RTL 电路图

4.1 调制仿真:激励设置与波形分析

仿真流程在 Quartus II 里是标准操作:新建 Vector Waveform File,设置仿真时间区域,添加信号节点,编辑激励波形,存盘,运行仿真器。这份资料里给出的参数是仿真时间 6μs、时间轴周期 40ns,测试序列为 [1 1 0 1 0 0 1 0 1 1 0 0 1],码元宽度 480ns。

具体操作步骤:

第一步,新建波形文件。File → New → Other Files → Vector Waveform File,确定。

第二步,设置仿真时间。Edit → End Time,填入 6,单位选 μs;Grid Size 的 Time period 填入 40ns。

第三步,添加信号节点。View → Utility Windows → Node Finder,Filter 选 Pins:all & Registers:Post-fitting,点 List,把 clk、start、x、q1、f1、q2、f2、y 拖到波形窗口。

第四步,编辑激励。clk 设为 Count Value 连续二进制,初始值 0;start 设为 Forcing High;x 按测试序列手动编辑高低电平。

第五步,存盘为 FSKTZ.vwf,然后 Processing → Start Simulation,等待 “Simulation was successful”。

仿真波形出来后重点看三处:一是 F1 和 F2 的频率比是否接近 6:1;二是 Y 在 X 跳变时是否正确切换;三是 Q1 和 Q2 的计数范围是否符合预期。如果 F1 或 F2 的频率不对,先检查系统时钟设置和分频计数器的初值。

4.2 解调仿真:以调制输出作为输入

解调仿真的流程和调制仿真几乎一样,关键区别在于输入信号 X 的来源——它应该是调制模块的输出 Y。实际操作时,你可以把调制仿真的输出波形导出,或者直接在解调波形文件里手动构造一个与调制输出一致的 FSK 信号。

解调仿真的信号节点包括 clk、start、x、y、q、m、xx。仿真时间同样设为 6μs,时间轴周期 40ns。运行仿真后,重点观察 M 的计数值和 Y 的判决输出是否与原始基带信号一致。如果 Y 的输出与调制端的 X 存在延迟,那是正常的——解调本身就需要一个计数周期的处理时间。但如果 Y 的电平与 X 完全对不上,就要检查判决阈值 M<=3 是否适合当前的频率参数。

4.3 RTL 电路图查看

Quartus II 的 RTL Viewer 可以把 VHDL 代码综合成门级电路图。操作路径是 Tools → RTL Viewer,打开后可以看到 FSKTZ 或 FSKJT 工程的层次结构。双击模块可以逐层深入,观察分频器、计数器、比较器、选择器的实际连接关系。

这一步对理解代码和硬件的对应关系很有帮助。比如你会看到 Q1 计数器实际上是一组触发器和加法器的组合,二选一开关对应一个多路选择器,判决逻辑对应一个比较器。如果你发现 RTL 图里有锁存器(Latch)而不是触发器(Flip-Flop),说明代码里存在不完整的条件赋值,需要回去检查进程里的 if-else 分支是否覆盖了所有情况。

仿真阶段关键信号检查要点常见异常
调制仿真F1、F2频率比是否约 6:1分频计数器初值错误
调制仿真YX 跳变时是否正确切换选通进程敏感列表遗漏 X
解调仿真M计数范围是否在 0-5输入信号未同步导致亚稳态
解调仿真Y判决输出是否还原基带判决阈值设置不当
RTL 查看整体是否有意外锁存器进程条件分支不完整

5. 避坑与排查:这份设计里最容易翻车的五个地方

5.1 两个载波频率共用时钟的矛盾

现象:调制仿真时 F1 和 F2 的频率比与设计值不符,或者其中一个频率明显偏离预期。

原因:代码里 F1 的分频比是 12,F2 的分频比是 2,但两者共用同一个 CLK。如果系统时钟是 150MHz,F1 得到 12.5MHz,但 F2 会变成 75MHz,而不是 2MHz。反过来,如果系统时钟是 4MHz,F2 得到 2MHz,但 F1 只有 333kHz。

解决:要么在代码里为 F1 和 F2 设置不同的分频比,使它们在同一时钟下产生目标频率;要么在仿真时分别设置不同的时钟频率。实际 FPGA 实现中,更常见的做法是用一个高频系统时钟,通过不同的分频计数器产生两路目标频率,分频比根据目标频率和系统时钟频率分别计算。

5.2 输入信号未同步导致的亚稳态

现象:解调仿真时 Y 输出出现随机跳变或毛刺,M 的计数值不稳定。

原因:解调模块的输入信号 X 来自调制模块的输出,两者可能处于不同的时钟域,或者 X 的跳变与 CLK 上升沿太接近,导致 XX <= X 采样时出现亚稳态。

解决:在解调模块的输入端口加两级触发器做同步化处理,也就是常说的“打两拍”。代码里已经有一级 XX <= X,如果仿真仍有问题,可以再加一级。另外,确保调制和解调使用同一个时钟源,或者在 testbench 里把 X 的跳变对齐到 CLK 的下降沿。

5.3 判决阈值 M<=3 的参数敏感性

现象:解调输出 Y 与原始基带信号存在偶发性误判,某些码元判决错误。

原因:判决阈值 M<=3 是基于 f1 和 f2 的特定频率比算出来的。如果系统时钟频率、分频比或输入信号频率发生任何变化,这个阈值就不再适用。比如 f2 的频率升高,一个计数周期内 M 的计数值增大,原本应该判为“0”的码元可能被误判为“1”。

解决:根据实际参数重新计算阈值。计算方法是:在一个 Q 计数周期内,分别统计 f1 和 f2 对应的脉冲个数 N1 和 N2,取 (N1+N2)/2 作为判决门限。如果 N1 和 N2 太接近,说明频率比不够大,需要调整载波频率参数。

5.4 START 信号的复位逻辑不完整

现象:仿真开始时输出信号出现不确定状态,或者 START 从低变高后系统没有正常启动。

原因:代码里 START=‘0’ 时对 Q1、Q2、Q 清零,但 F1、F2、Y 等输出信号没有在复位时赋初值。VHDL 仿真器会给未初始化的信号赋默认值(通常是 ‘0’ 或 ‘U’),但综合后的硬件行为可能不同。

解决:在复位分支里显式给所有输出信号赋初值。比如在 START=‘0’ 时,除了 Q1<=0,还要加 F1<=‘0’;Q2<=0 时加 F2<=‘0’;解调模块里 Q<=0 时加 Y<=‘0’、M<=0。这样仿真和综合的行为才一致。

5.5 仿真时间区域设置不当导致波形截断

现象:仿真波形只显示了一部分,后面的码元看不到,或者仿真报告提示时间不够。

原因:仿真时间设为 6μs,但测试序列有 13 个码元,每个码元 480ns,总时长 6.24μs,超过了仿真时间窗口。

解决:把 End Time 设为至少 8μs,留出余量。另外,Grid Size 设为 40ns 对于 480ns 的码元宽度来说分辨率足够,但如果码元宽度更小,需要相应减小 Grid Size。仿真时间区域和 Grid Size 是两个独立参数,前者决定仿真跑多久,后者决定波形的时间精度。

6. 进阶用法:把这份设计改成可参数化的 FSK 核

这份课程设计的代码是硬编码的——分频比写死在计数器范围里,判决阈值写死在 if 条件里。如果你想把它变成一个能复用的 FSK 调制解调核,最直接的办法是用 generic 参数化。

ENTITY FSKTZ IS GENERIC( DIV_F1 : INTEGER := 12; -- f1 分频比 DIV_F2 : INTEGER := 2; -- f2 分频比 DUTY : INTEGER := 6 -- f1 占空比计数 ); PORT( CLK : IN STD_LOGIC; START : IN STD_LOGIC; X : IN STD_LOGIC; Y : OUT STD_LOGIC ); END FSKTZ;

把 Q1 的范围改成 0 到 DIV_F1-1,Q2 的范围改成 0 到 DIV_F2-1,占空比判断条件改成 Q1 <= DUTY。这样换一组参数只需要改 generic 映射,不用动架构体。解调端同理,把 Q 的范围和判决阈值都参数化。

验证参数化是否正确的办法是跑一组回归仿真:用不同的 DIV_F1、DIV_F2 组合,检查输出频率是否等于 CLK 频率除以分频比,以及解调输出是否与输入基带一致。我一般会写一个简单的 testbench,用 for 循环遍历几组参数,自动对比预期值和实际值。

还有一个实用技巧:把调制和解调串起来做环回测试。在 testbench 里实例化一个 FSKTZ 和一个 FSKJT,把 FSKTZ 的 Y 接到 FSKJT 的 X,然后对比 FSKJT 的 Y 和原始基带信号。如果两者一致(允许若干时钟周期的延迟),说明整个链路是通的。这个环回测试比单独仿真调制或解调更能暴露问题,尤其是跨模块的信号同步问题。

从那以后我每次拿到一份通信相关的 FPGA 设计,都会先做三件事:确认时钟域和分频比是否自洽、检查输入信号有没有同步化处理、跑一遍环回测试。这三步走完,大部分玄学问题都能定位到具体原因。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询