ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

基于FPGA的AM调制系统实现:从数字信号处理到硬件设计实战

2026/8/7 9:24:42 拓冰建站 浏览量
基于FPGA的AM调制系统实现:从数字信号处理到硬件设计实战 1. 从零到一为什么选择FPGA来实现AM调制如果你对无线电通信或者数字信号处理有点兴趣大概率听说过“调制”这个词。简单说调制就是把我们要传递的信息比如一段音乐、一段语音“加载”到一个高频的载波信号上这样才能通过天线有效地发射出去。AMAmplitude Modulation幅度调制就是其中最经典、最基础的一种方式它的核心思想是让载波的幅度随着我们信息信号的变化而变化。你可能觉得这听起来像是上个世纪收音机时代的技术用个模拟电路搭一下不就行了确实用运放、乘法器、电容电感也能做。但今天我想跟你聊聊用FPGA现场可编程门阵列来做这件事的独特价值以及它如何从一个“课程设计”级别的项目演变成一个能让你深入理解数字通信和硬件设计精髓的实战平台。首先FPGA的本质是一块可以通过编程来定义其内部硬件连接和逻辑功能的芯片。这意味着我们不是在用软件“模拟”一个AM调制器而是在芯片内部用数字逻辑门、寄存器、查找表等“真实”地搭建出一个硬件电路。这个电路一旦生成其运行速度、确定性和并行处理能力是任何基于通用处理器的软件方案都无法比拟的。对于AM调制这种需要实时、高速处理信号的应用FPGA的并行架构可以轻松应对。比如载波生成、乘法运算、数据搬移这些操作在FPGA里可以同时进行而软件则需要一条指令一条指令地串行执行。那么为什么我们要“多此一举”地用数字化的FPGA去实现一个经典的模拟调制呢这里有几个关键点灵活性、可重构性和系统集成度。用FPGA你可以通过修改几行硬件描述语言如Verilog或VHDL代码轻易地将AM改成FM频率调制或PM相位调制或者调整载波频率、调制深度等参数而无需动烙铁更换任何一个物理元件。这对于教学、原型验证和需要快速迭代的研发场景来说价值巨大。其次一个完整的通信系统远不止一个调制器还包括信源编码、滤波、数字上变频、数模转换等。FPGA允许你将所有这些模块集成到同一颗芯片内部构建一个片上系统SoC极大地缩小了体积降低了功耗并提高了可靠性。最后这个过程本身是对数字信号处理DSP和硬件描述语言HDL的绝佳训练。你会亲手处理数字世界里的“正弦波”理解采样、量化、截位、溢出这些在模拟领域不存在的问题这是从理论走向工程实践的必经之路。所以这个“基于FPGA的AM调制系统”项目绝不仅仅是为了点亮一个LED或者让喇叭发出声音。它是一个窗口让你能窥见现代数字通信系统的底层硬件实现逻辑。接下来我将带你从理论分析、工具准备、核心模块设计、仿真验证到最后的硬件部署完整地走一遍这个流程。你会发现其中每一步都有值得深究的细节和容易踩坑的地方。2. 核心原理拆解从模拟公式到数字电路实现在动手写代码之前我们必须把AM调制的数学本质和它的数字化实现路径搞清楚。这是保证我们设计正确性的基石也能帮助你在调试时快速定位问题是出在算法理解上还是代码实现上。2.1 AM调制的数学表达与数字化标准的AM调制信号公式如下s_am(t) [A m(t)] * cos(2π f_c t)其中A是载波的幅度一个常数。m(t)是调制信号也就是我们要发送的信息比如一段音频。为了保证调制后不会出现过调幅失真即载波幅度被减到零甚至反向通常要求|m(t)| A。m(t)/A被称为调制指数。f_c是载波频率远高于m(t)的最高频率。cos(2π f_c t)就是载波。我们的目标是在FPGA里用数字逻辑实现这个公式。第一步是数字化。FPGA内部处理的是离散的数字信号所以我们需要对调制信号m(t)进行采样和量化通过一个ADC模数转换器将连续的模拟信号m(t)变成离散的数字序列m[n]。这是系统的一个输入。在数字域生成载波我们需要在FPGA内部产生一个数字化的余弦波序列cos[2π f_c n T_s]其中T_s是我们的系统采样周期。这里通常采用DDS直接数字频率合成技术。执行乘法与加法在数字域完成(A m[n]) * cos[...]的运算得到数字化的AM信号序列s_am[n]。数模转换最后通过一个DAC数模转换器将s_am[n]转换回模拟信号s_am(t)才能送到天线或功率放大器。注意在实际的FPGA系统中A和m[n]都需要用固定位宽的二进制数来表示例如16位有符号整数。A的选择至关重要它决定了系统的动态范围和是否会产生溢出。例如如果m[n]用16位有符号数表示范围-32768到32767那么A必须至少为32767才能保证A m[n]始终为非负数如果设计为非负调制。但在数字电路中我们更常用的是将整个表达式视为有符号数的运算最后对结果进行截位或饱和处理。2.2 关键数字模块规划根据上述流程我们可以在FPGA内部规划出几个核心的功能模块调制信号接口模块负责从外部ADC接收m[n]数据流。这可能是一个SPI、I2C或并行接口。这个模块需要处理好时钟域同步、数据有效信号生成等问题。载波生成模块DDS这是项目的核心之一。DDS通过一个相位累加器和一个波形查找表LUT来产生高精度的正弦/余弦波。你需要确定系统时钟频率你的FPGA主时钟clk是多少这决定了DDS能输出的最高无杂散动态范围SFDR的频率。载波频率f_c你想产生多少Hz的载波这关系到相位累加器的步进值频率控制字FTW的计算FTW (f_c * 2^N) / f_clk其中N是相位累加器的位宽。幅度和相位分辨率查找表的大小深度和输出数据的位宽决定了载波波形的精度。一个常见的配置是使用10位地址的查找表1024个点和16位输出的幅度值。调制核心运算模块这个模块接收m[n]和cos[n]执行(A m[n]) * cos[n]。这里必须仔细考虑数据位宽的扩展。假设m[n]是16位A等效为16位那么Am[n]可能需要17位来表示以防溢出。再乘以16位的cos[n]乘法结果可能达到33位。你需要决定最终输出s_am[n]保留多少位通常需要截取高16位或18位这涉及到截位Truncation与四舍五入Rounding的策略对输出信号的信噪比有直接影响。输出接口模块负责将s_am[n]送到DAC。如果是并行DAC直接连接数据线如果是SPI或I2C接口的DAC则需要编写对应的串行通信协议控制器。整个数据流需要在统一的时钟驱动下像流水线一样工作每个时钟周期完成一步处理从而实现实时调制。3. 开发环境搭建与工程管理Vivado/VSCode高效工作流工欲善其事必先利其器。FPGA开发离不开强大的工具链。虽然各大厂商如Xilinx的Vivado、Intel的Quartus、国产高云的Gowin都有自己的集成开发环境IDE但我想分享一种更灵活、更适合代码编写和版本管理的混合工作流Vivado VSCode。3.1 核心工具Vivado的安装与项目创建Xilinx Vivado以它为例其他IDE逻辑类似是完成综合、实现、生成比特流的必备工具。建议安装最新版本的Vivado HLx WebPACK这是免费版本对于学习和小型项目完全足够。安装后创建一个新项目选择项目类型RTL项目推荐。这意味着我们从编写RTL代码Verilog/VHDL开始保持对设计的最大控制权。添加源文件先不添加我们会在VSCode中编写。选择目标器件这是关键一步。你需要根据你手头的开发板来确定。例如如果使用经典的Nexys 4 DDRArtix-7系列就选择对应的xc7a100tcsg324-1。准确选择器件能确保后续的引脚约束、时序模型是正确的。完成创建这样你就得到了一个空的Vivado工程框架。3.2 使用VSCode进行代码编写Vivado自带的文本编辑器功能较弱。我们可以用VSCode作为主力代码编辑器。在VSCode中安装以下插件Verilog-HDL/SystemVerilog/Bluespec SystemVerilog提供语法高亮、代码片段、简单的语法检查。Vivado TCL如果你需要编写或阅读TCL脚本用于自动化流程。Even Better TOML用于编辑约束文件.xdc。在磁盘上创建一个清晰的目录结构例如fpga_am_modulator/ ├── src/ # 存放所有Verilog源文件 │ ├── dds.v │ ├── modulator.v │ └── top.v ├── sim/ # 存放仿真测试文件 │ └── tb_modulator.v ├── constr/ # 存放约束文件 │ └── pins.xdc ├── script/ # 存放TCL脚本 └── vivado/ # Vivado工程目录由Vivado生成在VSCode中打开fpga_am_modulator文件夹愉快地编写你的Verilog代码。VSCode的智能提示和项目管理体验远好于Vivado内嵌编辑器。3.3 将VSCode项目与Vivado工程关联这里有两种高效的工作模式模式一Vivado管理源文件在Vivado中将src/、constr/目录下的文件“添加”到工程中。注意选择“Copy sources into project”时谨慎建议不勾选而是使用“Add Files”并指向原位置。这样你在VSCode里保存修改Vivado工程会自动检测到更新。模式二纯TCL脚本驱动进阶你可以完全不使用Vivado的GUI。编写一个TCL脚本build.tcl在其中用命令创建项目、添加源文件、设置约束、运行综合实现。然后在VSCode的终端里直接运行vivado -mode tcl -source build.tcl。这种方式非常适合自动化、版本管理如Git和持续集成。实操心得对于初学者我推荐模式一GUI操作更直观。但尽早了解TCL脚本的价值是很有必要的。例如当你需要增量编译以节省漫长的实现时间时或者需要批量处理多个约束场景时TCL脚本是不可或缺的。另外Vivado的VIOVirtual Input/Output和ILAIntegrated Logic Analyzer在线调试工具其插入和触发设置也可以通过TCL脚本自动化极大提升调试效率。4. 核心模块Verilog实现详解DDS与调制器现在我们进入最核心的编码环节。我将以Verilog为例展示关键模块的实现并解释其中的设计考量。4.1 DDS载波生成模块DDS的核心是相位累加器和正弦查找表。这里我们设计一个参数化的DDS模块。module dds_core #( parameter PHASE_WIDTH 32, // 相位累加器位宽决定频率分辨率 parameter ADDR_WIDTH 10, // 查找表地址位宽决定相位截断精度 parameter DATA_WIDTH 16 // 输出数据位宽 )( input wire clk, input wire rst_n, input wire [PHASE_WIDTH-1:0] freq_word, // 频率控制字FTW output reg [DATA_WIDTH-1:0] sine_out // 正弦波输出 ); // 相位累加器 reg [PHASE_WIDTH-1:0] phase_acc; always (posedge clk or negedge rst_n) begin if (!rst_n) phase_acc 0; else phase_acc phase_acc freq_word; end // 取相位累加器的高位作为查找表地址相位截断 wire [ADDR_WIDTH-1:0] lut_addr phase_acc[PHASE_WIDTH-1:PHASE_WIDTH-ADDR_WIDTH]; // 正弦查找表ROM // 实际工程中这个ROM应该通过Vivado的IP Catalog生成或者用$readmemh初始化一个寄存器数组。 // 这里为了示例用一个简单的函数表示。实际ROM内容应包含一个完整正弦周期的量化值。 reg [DATA_WIDTH-1:0] sine_lut [0:(1ADDR_WIDTH)-1]; initial begin // 此处应使用$readmemh从文件加载正弦波数据例如 // $readmemh(sine_table.hex, sine_lut); // 为简化示例我们假设lut已正确初始化。 end always (posedge clk) begin sine_out sine_lut[lut_addr]; end endmodule关键点解析频率控制字FTW计算freq_word f_c * 2^PHASE_WIDTH / f_clk。例如系统时钟f_clk100MHzPHASE_WIDTH32想要产生f_c1MHz的载波则freq_word 1e6 * 2^32 / 100e6 ≈ 42949673。这个计算需要在外部比如ARM处理器或另一个模块完成并配置给DDS。相位截断与杂散我们只用相位累加器的高ADDR_WIDTH位去寻址ROM低位的相位信息被丢弃了这引入了相位截断误差是DDS输出频谱中产生杂散Spur的主要原因之一。增加ADDR_WIDTH可以减少这种误差但会增大ROM面积。ROM初始化在实际项目中绝对不要在Verilog代码里用循环计算正弦值来初始化ROM这会导致不可综合的综合过程。正确做法是1用Matlab或Python生成一个正弦波数据的文本文件.hex或.coe2在Vivado中调用Block Memory GeneratorIP核选择ROM并将数据文件关联进去3在代码中实例化这个IP核。或者对于小容量ROM也可以使用$readmemh系统任务在仿真中初始化一个寄存器数组但综合工具可能无法正确推断为ROM。4.2 AM调制核心运算模块这个模块执行(A m(t)) * cos(ωt)。在数字域我们需要仔细处理数据位宽和符号。module am_modulator #( parameter IN_WIDTH 16, // 输入调制信号位宽 parameter CARRIER_WIDTH 16, // 载波输入位宽 parameter OUT_WIDTH 16 // 输出信号位宽 )( input wire clk, input wire rst_n, input wire signed [IN_WIDTH-1:0] mod_signal, // 有符号调制信号 m[n] input wire signed [CARRIER_WIDTH-1:0] carrier, // 有符号载波 cos[n] input wire [IN_WIDTH-1:0] carrier_amp, // 载波幅度常数 A通常为最大值如16‘d32767 output reg signed [OUT_WIDTH-1:0] am_out // 有符号AM输出 ); // 将常数A扩展为有符号数以便与有符号的mod_signal相加 wire signed [IN_WIDTH:0] carrier_amp_signed {1b0, carrier_amp}; // 零扩展为有符号 // 计算 A m[n]注意位宽扩展1位以防溢出 wire signed [IN_WIDTH:0] amp_plus_mod carrier_amp_signed mod_signal; // 乘法 (Am[n]) * cos[n] // 乘法结果位宽为 (IN_WIDTH1) CARRIER_WIDTH wire signed [(IN_WIDTHCARRIER_WIDTH):0] mult_result; assign mult_result amp_plus_mod * carrier; // 截位处理取乘法结果的高位作为输出 // 这里采用简单的截取高位也可以实现四舍五入 always (posedge clk or negedge rst_n) begin if (!rst_n) am_out 0; else // 假设我们希望输出位宽为OUT_WIDTH从乘法结果的合适位置截取 // 例如乘法结果是33位输出要16位我们可以取[30:15]中间对齐 // 具体截取位置需要根据A和信号的动态范围调整避免溢出或精度损失过大。 am_out mult_result[(IN_WIDTHCARRIER_WIDTH)-:OUT_WIDTH]; end endmodule关键点解析与避坑指南有符号数运算Verilog中reg或wire默认是无符号数。进行加减乘除时如果混合有符号和无符号数结果会出乎意料。因此我明确使用了signed关键字声明所有涉及运算的信号。这是避免诡异计算错误的第一步。位宽管理这是数字信号处理中的重中之重。A m[n]可能比m[n]多1位考虑进位。乘法结果的位宽是两个操作数位宽之和。我们必须为中间结果预留足够的位宽防止溢出Overflow。溢出会导致信号严重失真。截位Truncation与舍入Rounding乘法结果位数很多但最终输出给DAC的位数是有限的比如16位。直接丢弃低位截位会引入量化噪声且噪声是均匀分布的。更优的方法是四舍五入即在截位前给结果加上一个1 (被丢弃的位数 - 1)的偏移量。例如如果丢弃低15位就加上14d8192即114然后再取高16位。这能将量化误差的统计均值降到零改善信噪比。调制深度控制常数A的大小直接决定了调制深度。A越大未调制载波分量越大调制深度越浅。在实际系统中A可能需要根据输入信号m[n]的幅度动态调整即自动增益控制AGC以防止过调幅。在我们的简单实现中A是一个固定参数需要保证输入m[n]的峰值不超过A。5. 仿真验证用ModelSim/ Vivado Simulator确保逻辑正确代码写完了千万别急着上板子。充分的仿真Simulation能帮你提前发现90%的逻辑错误和时序问题。我们使用一个简单的测试平台Testbench来验证AM调制器。timescale 1ns / 1ps module tb_am_modulator(); // 参数定义 localparam CLK_PERIOD 10; // 100MHz时钟 localparam IN_WIDTH 16; localparam CARRIER_WIDTH 16; localparam OUT_WIDTH 16; // 激励信号 reg clk; reg rst_n; reg signed [IN_WIDTH-1:0] mod_signal; reg signed [CARRIER_WIDTH-1:0] carrier; reg [IN_WIDTH-1:0] carrier_amp; // 观测信号 wire signed [OUT_WIDTH-1:0] am_out; // 实例化被测模块 am_modulator #( .IN_WIDTH(IN_WIDTH), .CARRIER_WIDTH(CARRIER_WIDTH), .OUT_WIDTH(OUT_WIDTH) ) uut ( .clk(clk), .rst_n(rst_n), .mod_signal(mod_signal), .carrier(carrier), .carrier_amp(carrier_amp), .am_out(am_out) ); // 生成时钟 always #(CLK_PERIOD/2) clk ~clk; // 生成载波简单计数器模拟 integer carrier_phase; always (posedge clk or negedge rst_n) begin if (!rst_n) begin carrier_phase 0; carrier 0; end else begin carrier_phase carrier_phase 64; // 粗略模拟一个低频载波 carrier $sin(carrier_phase * 2.0 * 3.1415926 / 1024.0) * 32767; // 使用$sin系统函数仅仿真可用 end end // 生成调制信号低频正弦波 integer mod_phase; always (posedge clk or negedge rst_n) begin if (!rst_n) begin mod_phase 0; mod_signal 0; end else begin mod_phase mod_phase 8; // 更低的频率 mod_signal $sin(mod_phase * 2.0 * 3.1415926 / 1024.0) * 16384; // 幅度为载波幅度的一半 end end // 初始化与测试过程 initial begin // 初始化 clk 0; rst_n 0; carrier_amp 32767; // A设为最大值 // 复位 #100; rst_n 1; // 运行一段时间观察波形 #10000; // 仿真10us // 可以在这里添加一些自动检查的语句例如检查输出幅度范围 // 如果am_out超过了16位有符号数的范围-32768~32767则报错 // ... $finish; end endmodule仿真要点使用$sin函数在Testbench中我们可以方便地使用$sin等系统函数来生成理想的测试信号这比用DDS模块更简单直接。但记住这些函数是不可综合的只能用于仿真。观察波形在Vivado或ModelSim中运行仿真后将关键信号mod_signal,carrier,am_out添加到波形窗口。你应该能看到am_out的包络即其幅度变化与mod_signal的波形一致而内部的高频振荡频率与carrier一致。这就是AM调制的时域特征。自动化检查可以在Testbench的initial块中加入assert语句自动检查输出是否溢出、调制深度是否合理等实现回归测试。文件IO更真实的测试是用Matlab生成一段真实的音频数据样本保存为文本文件然后在Testbench中使用$readmemh读取作为mod_signal的输入。同时也可以将am_out的结果写入文件再用Matlab读取并做频谱分析查看调制后的频谱是否正确出现载频、上下边带。6. 上板调试与实战问题排查从比特流到真实波形仿真通过后就可以生成比特流文件.bit并下载到FPGA开发板了。这是最激动人心也最容易遇到问题的环节。6.1 引脚约束与时序约束在Vivado中你需要创建一个约束文件.xdc告诉工具你的输入输出信号对应到FPGA芯片的哪个物理引脚以及这些信号的电气标准和时序要求。# 示例时钟引脚约束 set_property PACKAGE_PIN E3 [get_ports clk] # 假设clk连接到E3引脚 set_property IOSTANDARD LVCMOS33 [get_ports clk] create_clock -period 10.000 -name sys_clk [get_ports clk] # 100MHz时钟周期10ns # 示例DAC数据输出引脚约束假设是并行12位DAC set_property PACKAGE_PIN {A10 B10 C10 ...} [get_ports {dac_data[11:0]}] set_property IOSTANDARD LVCMOS33 [get_ports {dac_data[11:0]}] # 对于DAC输出通常还需要设置输出延迟约束这更复杂取决于DAC芯片的时序要求。 # 示例ADC数据输入引脚约束 set_property PACKAGE_PIN {D5 E5 F5 ...} [get_ports {adc_data[7:0]}] set_property IOSTANDARD LVCMOS33 [get_ports {adc_data[7:0]}] set_input_delay -clock sys_clk -max 4.000 [get_ports {adc_data[*]}] # 设置输入延迟约束时序约束是保证系统稳定工作的关键。create_clock定义了系统时钟的性能目标。set_input_delay和set_output_delay则定义了外部器件如ADC、DAC与FPGA接口之间的时序关系。如果这些约束没设对即使功能仿真正确上板后也可能出现数据采样错误。对于DDR接口这类高速接口时序约束如set_input_delay针对建立/保持时间的设置更是至关重要需要仔细阅读器件数据手册。6.2 在线调试ILA与VIO的使用代码上板后不工作怎么办别慌FPGA提供了强大的在线逻辑分析仪ILA和虚拟输入输出VIO工具。ILA你可以将内部任何你想观察的信号如am_out,phase_acc[31:24]等标记为调试探头Debug Probe。Vivado在综合实现后会将这些探针连接到FPGA内部的逻辑分析仪IP核上。下载比特流后可以在Vivado的Hardware Manager中设置触发条件例如当am_out大于某个值时然后捕获这些信号的实际波形。这就像在芯片内部接了一台示波器是定位问题的终极武器。VIO你可以创建一个虚拟的输入输出接口用来在运行时动态地修改FPGA内部的某些寄存器值比如DDS的频率控制字freq_word或者调制深度常数A或者读取某些状态值。这避免了为了修改一个参数而重新综合实现整个工程的漫长等待。添加ILA的简易步骤在Vivado的IP Catalog中搜索并添加ILA (Integrated Logic Analyzer)IP核。配置探针数量和深度深度决定了能捕获多长时间的波形。在代码中将需要观察的信号连接到ILA IP核的probe端口。重新综合、实现、生成比特流并下载。打开Hardware Manager连接设备设置触发条件运行捕获。6.3 常见上板问题与排查思路没有输出/输出全零检查复位确保复位信号rst_n已正确释放上拉至高电平。这是最常见的问题。检查时钟用ILA抓取系统时钟clk看是否真的在跳变。检查约束文件中的时钟引脚是否正确。检查数据通路用ILA从源头ADC输入开始逐级检查信号如mod_signal,carrier,amp_plus_mod,mult_result看在哪一级数据丢失或异常。输出波形失真严重检查溢出用ILA观察mult_result的完整位宽看其值是否超出了你为am_out预留的位数范围。如果溢出需要调整位宽或减小输入幅度。检查截位策略尝试将截位改为四舍五入看失真是否改善。检查DDS频谱如果载波本身失真由于ROM精度不够或相位截断噪声过大调制输出肯定失真。可以单独测试DDS模块的输出用频谱分析仪或通过高速DAC接示波器做FFT查看。系统运行不稳定偶尔出错时序违例Timing Violation在Vivado的实现报告中仔细查看“Timing”摘要。如果有建立时间Setup或保持时间Hold违例说明你的设计跑不到指定的时钟频率。需要优化代码如增加流水线级数、放松时钟约束或检查跨时钟域处理是否正确。亚稳态Metastability如果mod_signal来自另一个时钟域如ADC的采样时钟而你没有使用同步器如两级寄存器进行跨时钟域处理就可能发生亚稳态导致随机错误。务必对跨时钟域信号进行正确处理。7. 性能优化与进阶探索让系统更专业一个能工作的基础系统搭建完成后我们可以从以下几个方面进行优化和深化这能让你的项目从“实验”级别提升到“准产品”级别。7.1 提高载波质量DDS优化技巧基础DDS的频谱纯度受限于相位截断误差和幅度量化误差。我们可以使用抖动注入Dithering在相位累加器的最低有效位LSB添加一个伪随机序列可以将相位截断误差的离散频谱能量“打散”成底噪从而降低特定频率的杂散幅度。采用压缩ROM技术利用正弦波的对称性只存储1/4周期的正弦波数据然后通过逻辑电路还原出完整的周期。这可以节省大量的ROM资源。使用CORDIC算法CORDIC是一种通过迭代旋转向量来计算三角函数的方法它不需要ROM只用到移位和加法。虽然速度比查表法慢需要多个时钟周期但能节省大量逻辑资源并且精度可配置。对于资源紧张或需要极高频率分辨率无相位截断误差的应用CORDIC是很好的选择。7.2 集成数字上变频与滤波我们的基础设计直接输出了基带AM信号。在实际的无线电发射中这个信号需要被上变频到更高的射频频段。我们可以在FPGA内先完成数字上变频DUC用另一个DDS产生一个高频的数字本振信号比如70MHz。将我们的AM基带信号与该本振信号进行数字混频复数乘法需要用到希尔伯特变换生成正交分量。混频后会产生和频与差频分量用一个高速数字滤波器如FIR滤波器滤出需要的边带如上边带。将滤波后的数字信号送给高速DAC。这样DAC输出的直接就是射频信号了后续只需要简单的模拟滤波和功率放大即可。整个流程完全在数字域完成稳定且灵活。7.3 资源利用与功耗考量使用Vivado的实现后报告关注以下资源的使用情况查找表LUT用于实现组合逻辑。触发器FF用于实现寄存器。块RAMBRAM用于存储DDS的波形表。DSP Slice用于实现乘法器我们的amp_plus_mod * carrier乘法器会映射到这里。如果资源紧张可以考虑降低DDS输出位宽或查找表深度。如果调制信号带宽很低可以降低系统处理时钟频率以节省动态功耗。使用增量编译功能当你只修改了某个小模块时可以只重新编译该模块及其相关部分大幅节省编译时间。7.4 系统级集成与微处理器协同一个更完整的系统可能包含用户界面、参数配置、模式切换等功能。这可以通过在FPGA内部嵌入一个软核处理器如Xilinx的MicroBlaze来实现或者外接一个硬核处理器如Zynq芯片的ARM Cortex-A核。让处理器通过AXI总线来配置DDS的频率字、调制深度读取ADC数据或者控制工作模式。这就构成了一个软硬件协同的片上系统SoC功能更加强大和灵活。从最初的一个数学公式到最终在示波器上看到清晰的AM调制波形这个过程充满了挑战和乐趣。它强迫你去理解从模拟到数字的转换、数字信号处理的精度问题、硬件描述语言的并行思维、以及软硬件协同的调试方法。这个“基于FPGA的AM调制系统”项目就像一把钥匙为你打开了数字通信和可编程逻辑设计这两扇大门。当你成功调通它的那一刻你所获得的远不止一个能工作的调制器而是一整套解决复杂工程问题的思维方式和工具链使用经验。