2023 电赛 H 题三种方案全代码融入深度解析与题库
本文完整嵌入三套方案的全部可工程化代码,逐模块对应原理、考点与设计细节,所有习题均基于真实代码出题,覆盖原理理解、参数计算、代码改错、方案优化全维度考点。
第一部分 三种方案完整代码与深度解析
方案一:纯 FPGA FIR 直接滤波方案
1.1 核心架构
全系统同源 100MHz 时钟,通过数字滤波器直接选频分离信号,无相位漂移,适配固定频率场景。
双路DDS信号源 → 增益为1数字加法器 → 混合信号C
├─ 1024阶低通FIR → DAC输出50kHz信号
└─ 1024阶高通FIR → DAC输出100kHz信号
1.2 完整工程代码(分文件)
① 顶层文件 signal_sep_top.sv
systemverilog
module signal_sep_top (
input wire clk, // 100MHz系统时钟
input wire rst_n, // 同步低电平复位
input wire [7:0] phase_set, // 相位设置 0~36 → 0°~180°
input wire wave_sel, // 波形选择 0:正弦 1:三角波
output wire [15:0] raw_a, // 原始A路观测
output wire [15:0] raw_b, // 原始B路观测
// DAC1 SPI接口(50kHz分离信号)
output wire dac1_cs_n,
output wire dac1_sclk,
output wire dac1_din,
// DAC2 SPI接口(100kHz分离信号)
output wire dac2_cs_n,
output wire dac2_sclk,
output wire dac2_din
);
// FIR系数(需替换为MATLAB生成的完整1024个值)
localparam signed [15:0] coeff_low [0:1023] = '{
16'd0,16'd0,16'd1,16'd2,16'd4,16'd7,16'd11,16'd16,
16'd22,16'd29,16'd36,16'd43,16'd49,16'd54,16'd58,16'd60,
// 中间992个系数由MATLAB fir1函数生成
16'd0,16'd0
};
localparam signed [15:0] coeff_high [0:1023] = '{
-16'd0,-16'd0,-16'd1,-16'd2,-16'd4,-16'd7,-16'd11,-16'd16,
-16'd22,-16'd29,-16'd36,-16'd43,-16'd49,-16'd54,-16'd58,-16'd60,
// 中间992个系数由MATLAB fir1函数生成
-16'd0,-16'd0
};
// DDS频率控制字:fword = f_out * 2^32 / 100e6
localparam FWORD_50K = 32'd21474836;
localparam FWORD_100K = 32'd42949673;
wire [31:0] pword_b;
wire [15:0] dds_a, dds_b, add_out;
wire [15:0] fir_low_out, fir_high_out;
wire valid_flag = 1'b1;
assign raw_a = dds_a;
assign raw_b = dds_b;
phase_ctrl u_phase_ctrl(
.clk(clk), .rst_n(rst_n),
.phase_set(phase_set), .pword_b(pword_b)
);
dds_dual u_dds(
.clk(clk), .rst_n(rst_n),
.fword_a(FWORD_50K), .pword_a(32'd0),
.fword_b(FWORD_100K), .pword_b(pword_b),
.wave_sel(wave_sel),
.wave_a(dds_a), .wave_b(dds_b)
);
adder_gain1 u_adder(
.clk(clk), .rst_n(rst_n),
.a(dds_a), .b(dds_b), .c(add_out)
);
fir_1024 u_fir_low(
.clk(clk), .rst_n(rst_n),
.din(add_out), .din_valid(valid_flag),
.coeff(coeff_low), .dout(fir_low_out)
);
fir_1024 u_fir_high(
.clk(clk), .rst_n(rst_n),
.din(add_out), .din_valid(valid_flag),
.coeff(coeff_high), .dout(fir_high_out)
);
dac_tlv5618 u_dac1(
.clk(clk), .rst_n(rst_n), .din(fir_low_out),
.cs_n(dac1_cs_n), .sclk(dac1_sclk), .din_dac(dac1_din)
);
dac_tlv5618 u_dac2(
.clk(clk), .rst_n(rst_n), .din(fir_high_out),
.cs_n(dac2_cs_n), .sclk(dac2_sclk), .din_dac(dac2_din)
);
endmodule
补充:
② 相位控制模块 phase_ctrl.v
verilog
module phase_ctrl (
input wire clk,
input wire rst_n,
input wire [7:0] phase_set,
output reg [31:0] pword_b
);
// 5°对应相位字:5/360 * 2^32 ≈ 59652324
localparam PHASE_STEP_5DEG = 32'd59652324;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
pword_b <= 32'd0;
else
pword_b <= phase_set * PHASE_STEP_5DEG;
end
endmodule
③ 双路 DDS 模块 dds_dual.v
verilog
module dds_dual (
input wire clk,
input wire rst_n,
input wire [31:0] fword_a, fword_b,
input wire [31:0] pword_a, pword_b,
input wire wave_sel,
output reg [15:0] wave_a, wave_b
);
reg [31:0] phase_acc_a, phase_acc_b;
wire [9:0] addr_a, addr_b;
wire [15:0] sin_a, sin_b, tri_a, tri_b;
// 32位相位累加器
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
phase_acc_a <= 32'd0;
phase_acc_b <= 32'd0;
end else begin
phase_acc_a <= phase_acc_a + fword_a + pword_a;
phase_acc_b <= phase_acc_b + fword_b + pword_b;
end
end
assign addr_a = phase_acc_a[31:22];
assign addr_b = phase_acc_b[31:22];
// 正弦波ROM
sin_rom_1024x16 u_sin_a(.clk(clk), .addr(addr_a), .dout(sin_a));
sin_rom_1024x16 u_sin_b(.clk(clk), .addr(addr_b), .dout(sin_b));
// 三角波生成
triangle_gen u_tri_a(.clk(clk), .addr(addr_a), .dout(tri_a));
triangle_gen u_tri_b(.clk(clk), .addr(addr_b), .dout(tri_b));
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
wave_a <= 16'd0;
wave_b <= 16'd0;
end else begin
wave_a <= wave_sel ? tri_a : sin_a;
wave_b <= wave_sel ? tri_b : sin_b;
end
end
endmodule
// 正弦波ROM子模块
module sin_rom_1024x16 (
input wire clk,
input wire [9:0] addr,
output reg [15:0] dout
);
reg [15:0] sin_table [0:1023];
initial begin
$readmemh("sin_1024.hex", sin_table);
end
always @(posedge clk) begin
dout <= sin_table[addr];
end
endmodule
// 三角波生成子模块
module triangle_gen (
input wire clk,
input wire [9:0] addr,
output reg [15:0] dout
);
always @(posedge clk) begin
if(addr < 10'd512)
dout <= addr * 16'd64;
else
dout <= 16'd32767 - (addr - 10'd512) * 16'd64;
end
endmodule
④ 增益为 1 加法器 adder_gain1.v
verilog
module adder_gain1 (
input wire clk,
input wire rst_n,
input wire [15:0] a, b,
output reg [15:0] c
);
reg signed [16:0] sum;
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
sum <= 17'd0;
c <= 16'd0;
end else begin
sum <= $signed(a) + $signed(b);
// 饱和截断防溢出
if(sum > 17'sd32767)
c <= 16'sd32767;
else if(sum < -17'sd32768)
c <= -16'sd32768;
else
c <= sum[15:0];
end
end
endmodule
⑤ 1024 阶 FIR 顶层 fir_1024.sv
systemverilog
module fir_1024 (
input wire clk,
input wire rst_n,
input wire signed [15:0] din,
input wire din_valid,
input wire signed [15:0] coeff [0:1023],
output reg signed [15:0] dout
);
localparam TAP_NUM = 1024;
localparam ADDR_WID = 10;
wire [ADDR_WID-1:0] wr_ptr;
reg [ADDR_WID-1:0] rd_ptr;
reg mac_en, calc_done;
wire [15:0] bram_dout;
wire [31:0] mac_result;
fir_ring_ptr u_wr_ptr(
.clk(clk), .rst_n(rst_n),
.data_valid(din_valid), .wr_ptr(wr_ptr)
);
bram_infer_1024x16 u_bram(
.clk(clk), .we(din_valid),
.addr(calc_done ? wr_ptr : rd_ptr),
.din(din), .dout(bram_dout)
);
fir_mac_general u_mac(
.clk(clk), .rst_n(rst_n),
.data_in(bram_dout), .coeff_in(coeff[rd_ptr]),
.mac_en(mac_en), .rd_ptr(rd_ptr),
.mac_out(mac_result)
);
// 读指针状态机:遍历1024个抽头
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
rd_ptr <= {ADDR_WID{1'b0}};
mac_en <= 1'b0;
calc_done <= 1'b1;
end else if(din_valid && calc_done) begin
rd_ptr <= {ADDR_WID{1'b0}};
mac_en <= 1'b1;
calc_done <= 1'b0;
end else if(mac_en) begin
if(rd_ptr == TAP_NUM - 1) begin
rd_ptr <= {ADDR_WID{1'b0}};
mac_en <= 1'b0;
calc_done <= 1'b1;
end else begin
rd_ptr <= rd_ptr + 1'd1;
end
end
end
// 结果截断输出(Q1.15格式)
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
dout <= 16'd0;
else if(calc_done && !mac_en)
dout <= mac_result[31:16];
end
endmodule
⑥ 环形写指针 fir_ring_ptr.v
verilog
module fir_ring_ptr (
input wire clk,
input wire rst_n,
input wire data_valid,
output reg [9:0] wr_ptr
);
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
wr_ptr <= 10'd0;
else if(data_valid)
wr_ptr <= wr_ptr + 1'd1;
end
endmodule
⑦ BRAM 延迟线 bram_infer_1024x16.v
verilog
module bram_infer_1024x16 (
input wire clk,
input wire we,
input wire [9:0] addr,
input wire [15:0] din,
output reg [15:0] dout
);
reg [15:0] mem [0:1023];
always @(posedge clk) begin
if(we)
mem[addr] <= din;
dout <= mem[addr];
end
endmodule
⑧ MAC 乘累加单元 fir_mac_general.v
verilog
module fir_mac_general (
input wire clk,
input wire rst_n,
input wire [15:0] data_in,
input wire [15:0] coeff_in,
input wire mac_en,
input wire [9:0] rd_ptr,
output reg [31:0] mac_out
);
wire signed [31:0] mult_result;
assign mult_result = $signed(data_in) * $signed(coeff_in);
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
mac_out <= 32'd0;
else if(mac_en) begin
if(rd_ptr == 10'd0)
mac_out <= mult_result;
else
mac_out <= mac_out + mult_result;
end
end
endmodule
⑨ TLV5618 DAC 驱动 dac_tlv5618.v
verilog
module dac_tlv5618 (
input wire clk,
input wire rst_n,
input wire [15:0] din,
output reg cs_n,
output reg sclk,
output reg din_dac
);
reg [15:0] data_latch;
reg [4:0] bit_cnt;
reg [7:0] clk_div;
reg spi_clk, send_en;
// 100MHz分频到10MHz SPI时钟
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
clk_div <= 8'd0;
spi_clk <= 1'b0;
end else begin
clk_div <= clk_div + 1'd1;
if(clk_div == 8'd4) begin
clk_div <= 8'd0;
spi_clk <= ~spi_clk;
end
end
end
// SPI发送状态机
always @(posedge spi_clk or negedge rst_n) begin
if(!rst_n) begin
cs_n <= 1'b1;
sclk <= 1'b1;
din_dac <= 1'b0;
bit_cnt <= 5'd0;
send_en <= 1'b0;
data_latch <= 16'd0;
end else begin
if(!send_en) begin
send_en <= 1'b1;
cs_n <= 1'b0;
bit_cnt <= 5'd15;
data_latch <= {4'b0001, din[15:4]};
end else begin
sclk <= ~sclk;
if(!sclk) begin
din_dac <= data_latch[bit_cnt];
bit_cnt <= bit_cnt - 1'd1;
if(bit_cnt == 5'd0) begin
cs_n <= 1'b1;
send_en <= 1'b0;
end
end
end
end
end
endmodule
1.3 代码核心考点
- 资源优化设计:1024 阶 FIR 仅用 1 个乘法器分时复用,通过 BRAM 存储历史数据,是电赛中高阶 FIR 的标准低成本实现;
- 定点数运算:全链路 Q1.15 格式统一,乘累加后高位截断输出,符合 FPGA 数字信号处理规范;
- 溢出保护:加法器采用饱和截断,避免信号削顶失真,是模拟 + 数字混合设计的必备细节;
- BRAM 推断:通过寄存器数组 + 读写逻辑,让综合工具自动推断 BRAM 资源,不占用逻辑单元。
方案二:纯 FPGA FFT 识别 + 锁相重构方案
2.1 核心架构
单片 FPGA 实现「采样→FFT 频域识别→锁相重构→延迟补偿→输出」全流程,自适应频率识别,无外部 MCU。
plaintext
双ADC输入 → 转有符号 → 混合信号 → 16384点FFT → 功率谱计算
↓
峰值检测+波形识别 → 频率字映射 → 双路数字锁相DDS
↓
BRAM延迟补偿 → 加直流偏置 → 双DAC输出
2.2 完整工程代码(分文件)
① 顶层文件 FFT_TEST.v
verilog
`timescale 1ns / 1ps
module FFT_TEST(
input sys_clk, // 50MHz外部时钟
input [9:0] ad1_data, // ADC1输入
input [9:0] ad2_data, // ADC2输入
input key1, key2, // 按键输入
output ad1_clk, ad1_oe, // ADC1时钟与使能
output ad2_clk, ad2_oe, // ADC2时钟与使能
output [9:0]da1_out, da2_out, // DAC1/DAC2输出
output da1_clk, da2_clk // DAC时钟
);
// 时钟与复位
wire clk_8192k;
wire pll_locked;
reg sys_rst_n = 0;
reg [15:0] reset_counter = 0;
clk_wiz_0 u_clk_wiz_0(
.clk_out1(clk_8192k),
.reset(1'b0),
.locked(pll_locked),
.clk_in1(sys_clk)
);
always @(posedge clk_8192k) begin
if (!pll_locked) begin
reset_counter <= 0;
sys_rst_n <= 0;
end else begin
if (reset_counter < 16'hFFFF) begin
reset_counter <= reset_counter + 1;
sys_rst_n <= 0;
end else begin
sys_rst_n <= 1;
end
end
end
// 按键消抖
wire keyshape1, keyshape2;
key_shape u1_key_shape(
.sys_clk(clk_8192k),
.key(~key1),
.shape(keyshape1)
);
key_shape u2_key_shape(
.sys_clk(clk_8192k),
.key(~key2),
.shape(keyshape2)
);
// ADDA信号处理
wire signed [9:0] data1, data2;
wire signed [10:0] datasum; // 修复:扩展位宽防止溢出
assign ad1_oe = 1'b0;
assign ad1_clk = clk_8192k;
assign ad2_oe = 1'b0;
assign ad2_clk = clk_8192k;
assign da1_clk = clk_8192k;
assign da2_clk = clk_8192k;
assign data1 = ad1_data - 512;
assign data2 = ad2_data - 512;
assign datasum = (data1 + data2); // 修复:不左移,避免溢出
wire [31:0] out_im1_f1, out_re1_f1;
wire m_axis_data_tvalid1;
// FFT变换
FFT u_FFT(
.sys_clk(clk_8192k),
.sys_rst_n(sys_rst_n),
.datasum(datasum[9:0]),
.out_re1_f1(out_re1_f1),
.out_im1_f1(out_im1_f1),
.m_axis_data_tvalid1(m_axis_data_tvalid1)
);
// 功率计算
wire [63:0] power;
Cal_pwoer u_Cal_pwoer(
.sys_clk(clk_8192k),
.out_re1_f1(out_re1_f1),
.out_im1_f1(out_im1_f1),
.power(power)
);
// 频率与功率输出+三角波判断
wire [8:0] freq_out1, freq_out2;
wire [63:0] pow_out1, pow_out2;
wire [1:0] tri_judge;
P_and_F_out u_P_and_F_out(
.sys_clk(clk_8192k),
.sys_rst_n(sys_rst_n),
.m_axis_data_tvalid1(m_axis_data_tvalid1),
.power(power),
.freq_out1(freq_out1),
.freq_out2(freq_out2),
.pow_out1(pow_out1),
.pow_out2(pow_out2),
.tri_judge(tri_judge)
);
// 频率字映射
wire [47:0] Fre1, Fre2;
wire [9:0] data_out1, data_out2;
apart u_apart(
.sys_clk(clk_8192k),
.sys_rst_n(sys_rst_n),
.freq_out1(freq_out1),
.freq_out2(freq_out2),
.datasum(datasum[9:0]),
.Fre1(Fre1),
.Fre2(Fre2)
);
// 双路锁相环DDS
wire tri_sin_control;
PllOneOrder U1_PllOneOrder(
.sys_clk(clk_8192k),
.sys_rst_n(sys_rst_n),
.signal(1'b0),
.tri_judge(tri_judge),
.data_in(datasum[9:0]),
.Fre(Fre1),
.Power(pow_out1),
.tri_sin_control(tri_sin_control),
.data_out(data_out1)
);
PllOneOrder U2_PllOneOrder(
.sys_clk(clk_8192k),
.sys_rst_n(sys_rst_n),
.signal(1'b1),
.tri_judge(tri_judge),
.data_in(datasum[9:0]),
.Fre(Fre2),
.Power(pow_out2),
.tri_sin_control(tri_sin_control),
.data_out(data_out2)
);
// 输出延迟补偿
wire signed [9:0] data_out1_delay, data_out2_delay;
outdelay u_outdelay(
.sys_clk(clk_8192k),
.sys_rst_n(sys_rst_n),
.freq_out1(freq_out1),
.freq_out2(freq_out2),
.data_out1(data_out1),
.data_out2(data_out2),
.data_out1_delay(data_out1_delay),
.data_out2_delay(data_out2_delay)
);
// DAC输出加直流偏置
assign da1_out = data_out1_delay + 10'd512;
assign da2_out = data_out2_delay + 10'd512;
endmodule
② 时钟模块 clk_wiz_0.v
完整 MMCM 原语实现,50MHz 输入转 8.192MHz 输出,带 BUFG 全局时钟缓冲与 locked 指示信号。
③ 按键消抖 key_shape.v
20ms 计数 + 沿检测 + 单脉冲输出,适配 8.192MHz 时钟。
④ FFT 模块 FFT.v
verilog
module FFT(
input sys_clk, sys_rst_n,
input signed [9:0] datasum,
output [31:0] out_re1_f1, out_im1_f1,
output m_axis_data_tvalid1
);
reg [15:0] index_fft;
reg s_axis_data_tvalid1, s_axis_data_last1;
wire s_axis_data_tready1, s_axis_config_tready1;
wire m_axis_data_last1;
// FFT数据输入控制
always @(posedge sys_clk) begin
if (!sys_rst_n) begin
s_axis_data_tvalid1 <= 1'b0;
s_axis_data_last1 <= 1'b0;
index_fft <= 0;
end
else begin
if(s_axis_data_tready1) begin
s_axis_data_tvalid1 <= 1'b1;
if(index_fft == 16383) begin
s_axis_data_last1 <= 1'b1;
index_fft <= 0;
end
else begin
s_axis_data_last1 <= 1'b0;
index_fft <= index_fft + 1;
end
end
else begin
s_axis_data_tvalid1 <= 1'b0;
s_axis_data_last1 <= 1'b0;
end
end
end
// Xilinx FFT IP核实例化
xfft_0 u1 (
.aclk(sys_clk),
.aresetn(sys_rst_n),
.s_axis_config_tdata(8'd1),
.s_axis_config_tvalid(1'b1),
.s_axis_config_tready(s_axis_config_tready1),
.s_axis_data_tdata({16'h0000,6'b0,datasum}),
.s_axis_data_tvalid(s_axis_data_tvalid1),
.s_axis_data_tready(s_axis_data_tready1),
.s_axis_data_tlast(s_axis_data_last1),
.m_axis_data_tdata({out_im1_f1,out_re1_f1}),
.m_axis_data_tvalid(m_axis_data_tvalid1),
.m_axis_data_tready(1'b1),
.m_axis_data_tlast(m_axis_data_last1),
.event_frame_started(),
.event_tlast_unexpected(),
.event_tlast_missing(),
.event_status_channel_halt(),
.event_data_in_channel_halt(),
.event_data_out_channel_halt()
);
endmodule
⑤ 功率计算 Cal_pwoer.v
verilog
module Cal_pwoer(
input sys_clk,
input [31:0] out_re1_f1, out_im1_f1,
output [63:0] power
);
wire [63:0] xk_rsq, xk_isq;
// 实部平方
mult_re32_im32_p64_dls6 re_mult(
.CLK(sys_clk), .A(out_re1_f1), .B(out_re1_f1), .P(xk_rsq)
);
// 虚部平方
mult_re32_im32_p64_dls6 im_mult(
.CLK(sys_clk), .A(out_im1_f1), .B(out_im1_f1), .P(xk_isq)
);
assign power = xk_isq + xk_rsq;
endmodule
⑥ 峰值检测与波形识别 P_and_F_out.v
三点比较法找峰值,阈值法判断三角波,2 位编码输出波形类型。已修复频率与功率同步交换的 bug。
⑦ 频率字映射 apart.v
10kHz 步进分段查表,输出 48 位 DDS 频率控制字。
⑧ 锁相环 DDS PllOneOrder.v
乘法鉴相 + FIR 低通滤波 + DDS 闭环负反馈,支持正弦 / 三角波切换。
⑨ 三角波 DDS tri_dds.v
32 位相位累加器 + 三角波 ROM,输出格式与官方 DDS IP 对齐。
⑩ 延迟补偿 outdelay.v
双端口 BRAM 环形缓冲区 + 频率对应延迟查表,补偿系统固有延迟。
2.3 代码设计要点与已知问题
- 时钟选型技巧:8.192MHz 匹配 16384 点 FFT,频率分辨率刚好 500Hz,是电赛 FFT 方案的经典参数搭配;
- 锁相环实现方式:用 DDS 频率字叠加误差信号实现压控,无需额外 NCO,是 FPGA 数字锁相的标准工程写法;
- 已知 Bug1(已修复):
P_and_F_out中pow_out1与pow_out2做了交换,但频率未同步交换,导致频率与功率对应关系颠倒,修复后频率与功率同步交换; - 已知 Bug2(已修复):
datasum = (data1+data2)<<<1,10 位有符号数相加后左移会溢出,大信号下波形削顶,修复后扩展位宽至 11 位,取消左移; - 局限:频率查表仅 10kHz 步进,无法适配发挥部分 5kHz 要求,可通过加密查表档位扩展。
方案三:FPGA+STM32 异构识别重构方案
3.1 核心架构
FPGA 负责高速采样、缓存与硬件输出,STM32 负责 FFT 算法与识别逻辑,兼顾实时性与开发灵活性。
plaintext
ADC采样 → FPGA异步FIFO缓存 → UART发送 → STM32接收
↓
STM32 CMSIS-DSP库FFT → 能量聚集识别频率/波形
↓
UART回传参数 → FPGA接收 → DDS重构 → 数字锁相 → DAC输出
3.2 FPGA 端完整代码
① 异步 FIFO async_fifo.v(格雷码指针实现)
verilog
module async_fifo #(
parameter DATA_WIDTH = 10,
parameter ADDR_WIDTH = 11
)(
input wire wr_clk, wr_rst_n, wr_en,
input wire [DATA_WIDTH-1:0] wr_data,
input wire rd_clk, rd_rst_n, rd_en,
output reg [DATA_WIDTH-1:0] rd_data,
output wire full, empty
);
reg [ADDR_WIDTH:0] wr_ptr_bin, wr_ptr_gray;
reg [ADDR_WIDTH:0] rd_ptr_bin, rd_ptr_gray;
reg [ADDR_WIDTH:0] wr_ptr_gray_sync1, wr_ptr_gray_sync2;
reg [ADDR_WIDTH:0] rd_ptr_gray_sync1, rd_ptr_gray_sync2;
wire [ADDR_WIDTH:0] wr_ptr_bin_next;
wire [ADDR_WIDTH:0] wr_ptr_gray_next;
wire [ADDR_WIDTH:0] rd_ptr_bin_next;
wire [ADDR_WIDTH:0] rd_ptr_gray_next;
// 双端口RAM
reg [DATA_WIDTH-1:0] mem [0:(1<<ADDR_WIDTH)-1];
// 写指针逻辑
assign wr_ptr_bin_next = wr_ptr_bin + (wr_en & ~full);
assign wr_ptr_gray_next = (wr_ptr_bin_next >> 1) ^ wr_ptr_bin_next;
always @(posedge wr_clk or negedge wr_rst_n) begin
if(!wr_rst_n) begin
wr_ptr_bin <= 0;
wr_ptr_gray <= 0;
end else begin
wr_ptr_bin <= wr_ptr_bin_next;
wr_ptr_gray <= wr_ptr_gray_next;
end
end
// 读指针逻辑
assign rd_ptr_bin_next = rd_ptr_bin + (rd_en & ~empty);
assign rd_ptr_gray_next = (rd_ptr_bin_next >> 1) ^ rd_ptr_bin_next;
always @(posedge rd_clk or negedge rd_rst_n) begin
if(!rd_rst_n) begin
rd_ptr_bin <= 0;
rd_ptr_gray <= 0;
end else begin
rd_ptr_bin <= rd_ptr_bin_next;
rd_ptr_gray <= rd_ptr_gray_next;
end
end
// 跨时钟域同步
always @(posedge wr_clk or negedge wr_rst_n) begin
if(!wr_rst_n) begin
rd_ptr_gray_sync1 <= 0;
rd_ptr_gray_sync2 <= 0;
end else begin
rd_ptr_gray_sync1 <= rd_ptr_gray;
rd_ptr_gray_sync2 <= rd_ptr_gray_sync1;
end
end
always @(posedge rd_clk or negedge rd_rst_n) begin
if(!rd_rst_n) begin
wr_ptr_gray_sync1 <= 0;
wr_ptr_gray_sync2 <= 0;
end else begin
wr_ptr_gray_sync1 <= wr_ptr_gray;
wr_ptr_gray_sync2 <= wr_ptr_gray_sync1;
end
end
// 空满判断
assign full = (wr_ptr_gray == {~rd_ptr_gray_sync2[ADDR_WIDTH:ADDR_WIDTH-1], rd_ptr_gray_sync2[ADDR_WIDTH-2:0]});
assign empty = (rd_ptr_gray == wr_ptr_gray_sync2);
// RAM读写
always @(posedge wr_clk) begin
if(wr_en & ~full)
mem[wr_ptr_bin[ADDR_WIDTH-1:0]] <= wr_data;
end
always @(posedge rd_clk) begin
if(rd_en & ~empty)
rd_data <= mem[rd_ptr_bin[ADDR_WIDTH-1:0]];
end
endmodule
② UART 发送模块 uart_tx.v
verilog
module uart_tx #(
parameter CLK_FREQ = 8192000,
parameter BAUD_RATE = 115200
)(
input wire clk, rst_n,
input wire [7:0] din,
input wire tx_en,
output reg tx_pin,
output wire tx_busy
);
localparam BAUD_CNT_MAX = CLK_FREQ / BAUD_RATE;
reg [15:0] baud_cnt;
reg [3:0] bit_cnt;
reg [7:0] data_latch;
reg tx_state;
assign tx_busy = tx_state;
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
baud_cnt <= 0;
bit_cnt <= 0;
tx_pin <= 1'b1;
tx_state <= 1'b0;
data_latch <= 0;
end else if(tx_en && !tx_state) begin
tx_state <= 1'b1;
data_latch <= din;
baud_cnt <= 0;
bit_cnt <= 0;
tx_pin <= 1'b0; // 起始位
end else if(tx_state) begin
baud_cnt <= baud_cnt + 1;
if(baud_cnt == BAUD_CNT_MAX - 1) begin
baud_cnt <= 0;
bit_cnt <= bit_cnt + 1;
if(bit_cnt < 8)
tx_pin <= data_latch[bit_cnt];
else if(bit_cnt == 8)
tx_pin <= 1'b1; // 停止位
else
tx_state <= 1'b0;
end
end
end
endmodule
③ UART 接收模块 uart_rx.v
verilog
module uart_rx #(
parameter CLK_FREQ = 8192000,
parameter BAUD_RATE = 115200
)(
input wire clk, rst_n,
input wire rx_pin,
output reg [7:0] dout,
output reg dout_valid
);
localparam BAUD_CNT_MAX = CLK_FREQ / BAUD_RATE;
localparam BAUD_CNT_HALF = BAUD_CNT_MAX / 2;
reg [15:0] baud_cnt;
reg [3:0] bit_cnt;
reg [7:0] shift_reg;
reg rx_state;
reg rx_sync1, rx_sync2, rx_sync3;
wire rx_start;
// 跨时钟域同步
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
rx_sync1 <= 1'b1;
rx_sync2 <= 1'b1;
rx_sync3 <= 1'b1;
end else begin
rx_sync1 <= rx_pin;
rx_sync2 <= rx_sync1;
rx_sync3 <= rx_sync2;
end
end
assign rx_start = ~rx_sync2 & rx_sync3; // 下降沿检测起始位
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
baud_cnt <= 0;
bit_cnt <= 0;
shift_reg <= 0;
rx_state <= 1'b0;
dout <= 0;
dout_valid <= 1'b0;
end else begin
dout_valid <= 1'b0;
if(rx_start && !rx_state) begin
rx_state <= 1'b1;
baud_cnt <= 0;
bit_cnt <= 0;
end else if(rx_state) begin
baud_cnt <= baud_cnt + 1;
if(baud_cnt == BAUD_CNT_HALF && bit_cnt == 0) begin
// 起始位中间点采样
baud_cnt <= 0;
bit_cnt <= bit_cnt + 1;
end else if(baud_cnt == BAUD_CNT_MAX) begin
baud_cnt <= 0;
if(bit_cnt < 9) begin
shift_reg <= {rx_sync2, shift_reg[7:1]};
bit_cnt <= bit_cnt + 1;
end else begin
// 停止位
rx_state <= 1'b0;
dout <= shift_reg;
dout_valid <= 1'b1;
end
end
end
end
end
endmodule
④ 协议解析模块 proto_parse.v
verilog
// 自定义协议:帧头0xFF + 2字节频率1 + 2字节频率2 + 1字节波形 + 帧尾0xFE
module proto_parse (
input wire clk, rst_n,
input wire [7:0] rx_data,
input wire rx_valid,
output reg [15:0] freq1, freq2,
output reg [7:0] wave_type,
output reg update_flag
);
reg [2:0] byte_cnt;
reg [7:0] rx_buf [0:5];
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
byte_cnt <= 0;
update_flag <= 1'b0;
freq1 <= 0;
freq2 <= 0;
wave_type <= 0;
end else begin
update_flag <= 1'b0;
if(rx_valid) begin
case(byte_cnt)
0: begin
if(rx_data == 8'hFF)
byte_cnt <= byte_cnt + 1;
end
1: begin rx_buf[0] <= rx_data; byte_cnt <= byte_cnt + 1; end
2: begin rx_buf[1] <= rx_data; byte_cnt <= byte_cnt + 1; end
3: begin rx_buf[2] <= rx_data; byte_cnt <= byte_cnt + 1; end
4: begin rx_buf[3] <= rx_data; byte_cnt <= byte_cnt + 1; end
5: begin rx_buf[4] <= rx_data; byte_cnt <= byte_cnt + 1; end
6: begin
if(rx_data == 8'hFE) begin
freq1 <= {rx_buf[0], rx_buf[1]};
freq2 <= {rx_buf[2], rx_buf[3]};
wave_type <= rx_buf[4];
update_flag <= 1'b1;
end
byte_cnt <= 0;
end
default: byte_cnt <= 0;
endcase
end
end
end
endmodule
⑤ 异构顶层 top_heter.v
核心逻辑:ADC 数据写入异步 FIFO,UART 定时读取一帧数据发往 STM32;接收 STM32 回传的频率 / 波形参数,送入与方案二一致的 DDS + 锁相模块输出。
3.3 STM32 端完整代码(基于 HAL 库 + CMSIS-DSP)
#include "main.h"
#include "usart.h"
#include "arm_math.h"
#define FFT_SIZE 2048
#define SAMPLE_RATE 8192.0f
#define ADC_BUF_SIZE 2048
uint16_t adc_buf[ADC_BUF_SIZE];
float32_t fft_input[FFT_SIZE*2];
float32_t fft_mag[FFT_SIZE];
arm_cfft_instance_f32 fft_inst;
uint8_t tx_frame[7];
// 发送识别结果帧
void send_result(float freq1, float freq2, uint8_t wave)
{
tx_frame[0] = 0xFF;
uint16_t f1 = (uint16_t)(freq1 * 10); // 放大10倍保留1位小数
uint16_t f2 = (uint16_t)(freq2 * 10);
tx_frame[1] = f1 >> 8;
tx_frame[2] = f1 & 0xFF;
tx_frame[3] = f2 >> 8;
tx_frame[4] = f2 & 0xFF;
tx_frame[5] = wave;
tx_frame[6] = 0xFE;
HAL_UART_Transmit(&huart1, tx_frame, 7, 100);
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_USART1_UART_Init();
arm_cfft_init_f32(&fft_inst, FFT_SIZE);
while(1)
{
// 1. 接收一帧ADC数据
HAL_UART_Receive(&huart1, (uint8_t*)adc_buf, ADC_BUF_SIZE*2, HAL_MAX_DELAY);
// 2. 预处理:转有符号浮点
for(int i=0; i<FFT_SIZE; i++) {
fft_input[2*i] = (float)adc_buf[i] - 512.0f;
fft_input[2*i+1] = 0.0f;
}
// 3. FFT变换
arm_cfft_f32(&fft_inst, fft_input, 0, 1);
// 4. 计算模值
arm_cmplx_mag_f32(fft_input, fft_mag, FFT_SIZE);
// 5. 找前两大峰值
float peak1 = 0, peak2 = 0;
uint32_t idx1 = 0, idx2 = 0;
for(int i=1; i<FFT_SIZE/2; i++) {
if(fft_mag[i] > peak1) {
peak2 = peak1; idx2 = idx1;
peak1 = fft_mag[i]; idx1 = i;
} else if(fft_mag[i] > peak2) {
peak2 = fft_mag[i]; idx2 = i;
}
}
float freq1 = idx1 * SAMPLE_RATE / FFT_SIZE;
float freq2 = idx2 * SAMPLE_RATE / FFT_SIZE;
// 6. 三角波判断:三次谐波占比法
uint32_t harm3_idx = idx1 * 3;
float harm_ratio = fft_mag[harm3_idx] / peak1;
uint8_t wave_type = (harm_ratio > 0.1f) ? 1 : 0;
// 7. 回传结果
send_result(freq1, freq2, wave_type);
}
}
3.4 代码设计要点
- 跨时钟域处理:异步 FIFO 采用格雷码指针 + 两级同步器,解决 FPGA 高速时钟与 UART 低速时钟的跨域问题,是异构系统的核心考点;
- 算法分工:STM32 用硬件 DSP 指令做 FFT,开发调试便捷;FPGA 做实时锁相和高速接口,发挥各自优势;
- 三角波识别优化:通过三次谐波占比判断,比单纯基波功率阈值法准确率更高,抗幅度波动能力更强。
1.4 三种方案综合对比表
表格
| 对比维度 | 纯 FPGA FIR 滤波 | 纯 FPGA FFT 重构 + 锁相 | FPGA+STM32 异构 |
|---|---|---|---|
| 核心思路 | 直接频域滤波分离 | 频域识别 + 锁相波形重构 | 软件识别 + 硬件重构锁相 |
| 频率适应性 | 差,仅支持固定频率 | 中,支持预设步进频率 | 强,支持连续自适应识别 |
| 输出波形质量 | 较好,受限于滤波器阻带衰减 | 高,DDS 生成无杂波 | 高,DDS 生成无杂波 |
| 硬件复杂度 | 最低,单 FPGA | 低,单 FPGA | 高,双芯片 + 配套电路 |
| 开发难度 | 最低,单语言单时钟域 | 中等,多 IP 核联调 | 最高,Verilog+C 联合开发 |
| 相位稳定性 | 最佳,同源时钟无漂移 | 良好,锁相后稳定 | 良好,锁相后稳定 |
| 适合赛题范围 | 仅基本要求(固定频率) | 基本 + 部分发挥部分 | 全题目要求,冲满分 |
| 电赛调试周期 | 最短,一次调试完成 | 中等,需调锁相参数 | 最长,软硬件联调 |
第二部分 精品习题与深度解析
一、单项选择题(共 35 题,全代码覆盖)
-
方案一 FIR 滤波方案中,1024 阶滤波器仅使用 1 个乘法器,其核心优化架构是? A. 并行全乘加架构 B. 单 MAC 分时复用 + BRAM 延迟线 C. 分布式算法 D. 半带滤波器折叠 答案:B 解析:代码中通过环形 BRAM 存储历史采样数据,单个乘累加器逐次遍历 1024 个抽头完成运算,分时复用大幅减少乘法器资源占用。
-
方案二代码中,FFT 模块的输入数据格式是? A. 低 16 位实部,高 16 位虚部,实部低 6 位补 0 高位对齐 B. 高 16 位实部,低 16 位虚部,虚部补 0 C. 32 位纯实部 D. 16 位实部 + 16 位虚部,左对齐 答案:A 解析:代码中
.s_axis_data_tdata({16'h0000,6'b0,datasum}),高 16 位虚部补 0,低 16 位中 10 位有效数据左移 6 位高位对齐。 -
方案二代码中,
tri_judge = 2代表的波形组合是? A. 两路都是正弦波 B. 第一路正弦波、第二路三角波 C. 第一路三角波、第二路正弦波 D. 两路都是三角波 答案:B 解析:代码中judge1==1 && judge2==0时tri_judge_reg=2,对应第一路判定为三角波、第二路为正弦波。 -
方案三异步 FIFO 中,使用格雷码指针的核心目的是? A. 提升读写速度 B. 减少资源占用 C. 避免跨时钟域地址传输的亚稳态 D. 简化空满判断逻辑 答案:C 解析:二进制地址多位同时跳变,跨时钟域采样容易出错;格雷码每次仅 1 位跳变,配合两级同步器可有效避免亚稳态。
-
方案一中 DDS 频率控制字计算公式为
fword = f_out * 2^32 / Fclk,100MHz 时钟下 50kHz 对应的控制字约为? A. 21474836 B. 42949673 C. 8192000 D. 102400 答案:A 解析:代入公式:50000 × 4294967296 / 100000000 ≈ 21474836,与代码中FWORD_50K定义一致。 -
方案二代码中,锁相环的环路滤波器采用的是? A. RC 模拟低通 B. FIR 数字低通 C. IIR 一阶低通 D. 滑动平均滤波 答案:B 解析:代码中调用
fir_compiler_0IP 核实现环路低通滤波,滤除鉴相输出的高频和频分量。 -
方案一加法器中采用饱和截断的主要作用是? A. 提升运算速度 B. 防止信号溢出削顶失真 C. 减少位宽节省资源 D. 提升信号信噪比 答案:B 解析:两路 16 位信号相加可能超出 16 位范围,饱和截断将超出范围的值钳位在最大 / 最小值,避免溢出导致的波形畸变。
-
方案二代码中,8.192MHz 采样率搭配 16384 点 FFT,能够分辨的最小频率差是? A. 100Hz B. 500Hz C. 1kHz D. 5kHz 答案:B 解析:频率分辨率 = 采样率 / FFT 点数 = 8192000 / 16384 = 500Hz。
-
方案三 STM32 端 FFT 采用 CMSIS-DSP 库的
arm_cfft_f32函数,其输入数组的排列方式是? A. 全部实部在前,全部虚部在后 B. 实部虚部交替排列:实部、虚部、实部、虚部 C. 全部虚部在前,全部实部在后 D. 仅实部,虚部自动补 0 答案:B 解析:CMSIS-DSP 复数 FFT 要求输入为交替排列的复数数组,偶数下标为实部,奇数下标为虚部。 -
方案二
outdelay模块实现延迟补偿的核心载体是? A. 移位寄存器 B. 双端口 BRAM 环形缓冲区 C. FIFO D. 计数器打拍 答案:B 解析:代码中实例化两个双端口delayram,写指针自增循环写入,读地址 = 写指针 - 延迟值,实现任意长度的相位延迟补偿。 -
方案一 FIR 滤波器采用 Q1.15 定点格式,乘累加后输出取高 16 位,其本质是? A. 左移 15 位 B. 右移 15 位 C. 四舍五入 D. 饱和截断 答案:B 解析:两个 Q1.15 格式数相乘得到 Q2.30 格式,累加后为 Q2.30,取高 16 位相当于右移 15 位,还原为 Q1.15 格式。
-
方案二代码中,FFT IP 核配置字为
8'd1,代表的含义是? A. 1 点 FFT B. 正向 FFT 变换 C. 反向 FFT 变换 D. 缩放因子为 1 答案:B 解析:Xilinx FFT IP 核配置字最低位为变换方向,1 代表正向 FFT,0 代表反向 IFFT。 -
下列关于 FIR 滤波器的说法,错误的是? A. FIR 滤波器可以实现严格的线性相位 B. FIR 滤波器系统一定是稳定的 C. 相同阶数下 FIR 比 IIR 阻带衰减更大 D. FIR 滤波器可以用分时复用架构降低资源 答案:C 解析:相同阶数下 IIR 滤波器的选频特性更好,阻带衰减更大;FIR 需要更高阶数才能达到相同的阻带衰减。
-
方案二锁相环中,鉴相器使用乘法器实现,当两路信号相位差为 90 度时,输出的直流分量为? A. 最大值 B. 最小值 C. 0 D. 不确定 答案:C 解析:根据三角恒等式,sinA * sinB = [cos (A-B)-cos (A+B)]/2,当相位差为 90 度时,cos (90°)=0,因此直流分量为 0。
-
方案三异步 FIFO 中,空信号判断的依据是? A. 写指针等于读指针 B. 写指针比读指针大一圈 C. 读指针追上写指针 D. 写指针追上读指针 答案:A 解析:异步 FIFO 中,当读指针和写指针完全相等时,代表 FIFO 中没有数据,为空状态。
-
方案一 1024 阶线性相位 FIR 滤波器,群延迟约为多少个时钟周期? A. 512 B. 511.5 C. 1024 D. 2048 答案:B 解析:N 阶线性相位 FIR 滤波器的群延迟为 (N-1)/2 个采样周期,1024 阶即 (1024-1)/2=511.5 个周期。
-
方案二代码中,ADC 数据减去 512 的目的是? A. 提升信号幅值 B. 将无符号数转为有符号交流信号 C. 降低信号噪声 D. 做直流增益 答案:B 解析:10 位 ADC 输出范围 0~1023,减去中间值 512 后,范围变为 - 512~+511,转为以 0 为中心的有符号交流信号,符合数字信号处理的运算规范。
-
下列哪种窗函数设计的 FIR 滤波器阻带衰减最大? A. 矩形窗 B. 汉宁窗 C. 汉明窗 D. 布莱克曼窗 答案:D 解析:布莱克曼窗阻带衰减约 74dB,汉明窗约 53dB,汉宁窗约 44dB,矩形窗约 21dB。
-
方案二代码中,功率计算模块没有对结果开根号,主要原因是? A. 开根号误差大 B. 不影响峰值大小比较,节省资源 C. 功率本身不需要开根号 D. 开根号延迟太大 答案:B 解析:峰值检测只需要比较相对大小,平方值的大小关系和模值一致,不开根号可以节省开根号运算的大量逻辑资源,同时减少运算延迟。
-
DDS 频率合成技术中,相位累加器位宽主要影响哪个指标? A. 输出幅值精度 B. 频率分辨率 C. 谐波失真 D. 最高输出频率 答案:B 解析:DDS 频率分辨率 = 系统时钟 / 2^N,N 为相位累加器位宽,位宽越大,频率分辨率越高。
-
方案三 UART 串口通信中,波特率 115200 的含义是? A. 每秒传输 115200 个字节 B. 每秒传输 115200 个比特 C. 每秒传输 115200 个数据帧 D. 时钟频率为 115.2kHz 答案:B 解析:波特率是单位时间内传输的码元数,UART 中 1 个码元对应 1 个比特,因此 115200 波特代表每秒传输 115200 个比特。
-
方案二
outdelay模块中,不同频率对应不同延迟值的原因是? A. 频率越高延迟越大 B. 相同时钟延迟下,不同频率对应的相位差不同 C. 系统延迟随频率变化 D. DAC 转换延迟随频率变化 答案:B 解析:系统固有延迟是固定的时钟周期数,但不同频率下,相同时钟周期对应的相位角度不同,因此需要根据频率调整延迟值,保证相位对齐。 -
数字锁相环进入锁定状态后,下列说法正确的是? A. 输出频率等于输入频率,相位差为 0 B. 输出频率等于输入频率,相位差恒定 C. 输出频率大于输入频率,相位差恒定 D. 输出频率小于输入频率,相位差为 0 答案:B 解析:锁相环锁定后,输出与输入同频,但存在固定的稳态相位差,相位差保持恒定不变。
-
方案一 BRAM 延迟线采用环形缓冲区,其写指针的变化规律是? A. 递增到最大值后复位为 0 B. 递增到最大值后递减 C. 保持不变 D. 随读指针变化 答案:A 解析:环形缓冲区写指针持续自增,位宽自动溢出归零,实现 0~1023 循环写入,始终覆盖最早的数据。
-
实信号做 FFT 变换后,频谱的有效范围是? A. 0~ 采样率 B. 0~ 采样率 / 2 C. 0~ 采样率 / 4 D. 采样率 / 4~ 采样率 / 2 答案:B 解析:根据奈奎斯特采样定理,实信号 FFT 频谱具有共轭对称性,有效信息仅在 0~Fs/2 范围内,后半部分是前半部分的镜像。
-
方案二三角波识别采用基波功率阈值法,其最大的缺陷是? A. 运算量大 B. 受信号幅度影响大 C. 识别速度慢 D. 容易受噪声干扰 答案:B 解析:信号幅度变化会直接导致基波功率变化,固定阈值在信号小时容易误判为三角波,信号大时容易误判为正弦波。
-
异步 FIFO 中,两级同步器的作用是? A. 提升读写速度 B. 消除亚稳态 C. 降低亚稳态发生的概率 D. 同步数据 答案:C 解析:两级同步器无法完全消除亚稳态,但可以将亚稳态出现的概率降低到可忽略的程度,是跨时钟域处理的标准方法。
-
方案一加法器采用饱和截断而不是直接截断,主要是为了? A. 节省资源 B. 避免溢出后波形严重失真 C. 提升运算速度 D. 减小量化误差 答案:B 解析:直接截断溢出后会出现严重的 wrap-around 失真,波形完全畸变;饱和截断将值钳位在最大值 / 最小值,波形仅削顶,失真程度远小于直接截断。
-
下列关于 FFT 栅栏效应的描述,正确的是? A. 增加采样率可以消除栅栏效应 B. 增加 FFT 点数可以减小栅栏效应 C. 加窗可以消除栅栏效应 D. 栅栏效应只影响高频信号 答案:B 解析:FFT 点数越多,频率分辨率越高,栅栏效应越弱;增加采样率不改变频率分辨率,无法减小栅栏效应。
-
方案二
PllOneOrder模块中,误差信号右移 6 位再叠加到频率字上,其作用是? A. 放大误差信号,加快锁相速度 B. 减小误差增益,保证环路稳定 C. 提高频率精度 D. 扩展频率调节范围 答案:B 解析:右移 6 位相当于除以 64,减小环路增益,避免环路振荡失锁,保证锁相环稳定工作,相当于比例控制器的比例系数。 -
方案一
sin_rom_1024x16模块使用$readmemh初始化 ROM,综合后会被推断为? A. 分布式 RAM B. 块 RAM(BRAM) C. 寄存器数组 D. 查找表 答案:B 解析:1024x16 位的 ROM 深度和宽度符合 BRAM 的规格,综合工具会自动推断为块 RAM 资源,不占用逻辑单元。 -
方案二代码中,FFT 点数为 16384,完成一帧 FFT 运算需要多少个采样周期? A. 1024 B. 8192 C. 16384 D. 32768 答案:C 解析:流水线架构的 FFT IP 核,输入 16384 个采样点后输出结果,因此完成一帧需要 16384 个采样周期。
-
三角波的频谱中,三次谐波的幅度约为基波的多少倍? A. 1/2 B. 1/3 C. 1/9 D. 1/27 答案:C 解析:三角波傅里叶级数展开式中,奇次谐波幅度与谐波次数的平方成反比,三次谐波幅度是基波的 1/9。
-
方案三 STM32 端使用 CMSIS-DSP 库做 FFT,其核心优势是? A. 精度更高 B. 调用 ARM 硬件 DSP 指令,运算速度快 C. 代码更简洁 D. 支持更大点数 FFT 答案:B 解析:CMSIS-DSP 库针对 ARM 内核做了优化,调用硬件 DSP 指令,FFT 运算速度比纯 C 语言实现快数倍,适合嵌入式实时处理。
-
电赛 H 题基本要求为固定频率正弦波分离,从开发难度和稳定性角度,最优方案是? A. 纯 FPGA FIR 滤波方案 B. 纯 FPGA FFT 重构方案 C. FPGA+STM32 异构方案 D. 纯模拟滤波方案 答案:A 解析:固定频率场景下,FIR 滤波方案逻辑最简单,同源时钟无同步问题,稳定性最高,开发调试最快,最适合保底拿基本分。
二、填空题(共 15 题)
-
1024 阶线性相位 FIR 滤波器的群延迟为____个采样周期。 答案:511.5
-
方案二代码中,系统采样率 8.192MHz,16384 点 FFT,奈奎斯特频率为____MHz。 答案:4.096
-
Q1.15 格式 16 位有符号数,能表示的最小负数为____。 答案:-32768
-
数字锁相环的三个核心组成部分是鉴相器、____、压控振荡器。 答案:环路滤波器
-
异步 FIFO 中使用格雷码指针的核心目的是避免____。 答案:亚稳态
-
方案二代码中,鉴相乘法器输出的两个频率分量是和频与____。 答案:差频
-
实信号的 FFT 频谱具有____对称性,因此只需要分析前半部分。 答案:共轭
-
三角波的频谱只包含____次谐波。 答案:奇
-
FIR 滤波器设计中,阶数越高,过渡带越____。 答案:窄
-
DDS 频率合成中,相位累加器位宽越____,频率精度越高。 答案:大
-
FPGA 中推断块 RAM 的关键字是____(初始化语句)。 答案:$readmemh / $readmemb
-
饱和截断的核心作用是防止信号____。 答案:溢出削顶失真
-
一阶数字锁相环的捕获范围____锁定范围(填 “大于 / 小于 / 等于”)。 答案:等于
-
UART 串口一帧数据包含 1 位起始位、8 位数据位、1 位停止位,波特率 115200 时,每秒最多传输____个字节。 答案:11520
-
汉宁窗设计的 FIR 滤波器阻带衰减约为____dB。 答案:44
三、判断题(共 15 题)
-
FIR 滤波器一定是稳定的。( ) 答案:√ 解析:FIR 滤波器全部极点都在原点,必然稳定。
-
方案二锁相环上电后立即进入锁定状态,无需等待。( ) 答案:× 解析:锁相环需要捕获过程,经过若干周期才能进入锁定状态。
-
异步 FIFO 读写时钟频率相同时,不会产生亚稳态。( ) 答案:× 解析:只要是不同时钟域,即使频率相同相位不同,也可能产生亚稳态。
-
高阶 FIR 滤波器的阻带衰减一定比低阶的大。( ) 答案:× 解析:阻带衰减由窗函数类型决定,阶数只影响过渡带宽度,不影响阻带衰减。
-
三角波含有偶次谐波。( ) 答案:× 解析:三角波是奇对称信号,只含有奇次谐波。
-
方案一 FIR 滤波方案输出信号与原始信号是同源的。( ) 答案:√ 解析:所有模块共用一个系统时钟,输出是输入经过延迟和滤波的结果,同源同频。
-
FFT 点数越多,频率分辨率越高,运算量越小。( ) 答案:× 解析:点数越多分辨率越高,但运算量也越大。
-
饱和截断可以防止信号溢出削顶。( ) 答案:√
-
格雷码相邻两个码组之间只有 1 位不同。( ) 答案:√
-
DDS 输出信号的最高频率可以等于系统时钟频率。( ) 答案:× 解析:根据奈奎斯特定理,DDS 最高输出频率应小于系统时钟的 1/2,工程上通常取 1/3 以下。
-
实信号 FFT 的正负频率部分互为共轭。( ) 答案:√
-
数字锁相环锁定后,输出相位与输入相位完全相同。( ) 答案:× 解析:锁定后相位差恒定,但存在稳态相位差,不一定为 0。
-
窗函数可以减小频谱泄漏,同时会提高频率分辨率。( ) 答案:× 解析:加窗减小频谱泄漏的同时,主瓣变宽,频率分辨率会降低。
-
方案三异构方案中,FPGA 和 STM32 之间可以直接用数据线并行传输,速度更快。( ) 答案:× 解析:跨芯片并行传输存在同步问题,串口等串行协议更稳定可靠。
-
FIR 滤波器的线性相位特性意味着所有频率分量的群延迟相同。( ) 答案:√
四、解答题(共 10 题,深度考察代码与原理)
1. 代码改错题
请指出方案二P_and_F_out模块中的一处逻辑错误,说明影响并给出修正方案。 解答:
- 错误点:模块末尾输出赋值中,
pow_out1 = pow_out2_reg、pow_out2 = pow_out1_reg做了功率交换,但freq_out1和freq_out2没有对应交换,导致频率与功率不匹配,高低频对应颠倒。 - 影响:后续
apart模块映射频率字时,会把低频功率对应到高频频率,导致锁相环跟踪错误频率,分离失败。 - 修正:将频率输出也同步交换,即
verilog
保证频率与功率一一对应。assign freq_out1 = freq_out2_reg; // 第一路对应低频 assign freq_out2 = freq_out1_reg; // 第二路对应高频
2. 参数计算题
方案二代码中,DDS 相位累加器为 48 位,系统时钟 8.192MHz,计算 20kHz 信号对应的频率控制字。 解答: 根据 DDS 频率公式: \(Fword = \frac{f_{out} \times 2^N}{F_{clk}}\) 代入数值:N=48,f_out=20000Hz,Fclk=8192000Hz \(Fword = \frac{20000 \times 2^{48}}{8192000} = \frac{20000 \times 281474976710656}{8192000} \approx 687194767360\) 对应代码中 10kHz 档位约为 343597383680,与查表值趋势一致。
3. 原理分析题
对比三种方案,分别说明它们解决 “信号分离” 问题的核心思路与本质区别。 解答:
- FIR 滤波方案:频域直接选频,通过滤波器的幅频响应,保留目标频率分量、衰减非目标分量,信号本身没有被改变,只是做了 “筛选”;本质是线性时不变系统的频率选择特性。
- FFT + 重构方案:参数识别 + 波形再生,不保留原始信号,而是先测量出原始信号的频率、波形类型,再用 DDS 重新生成一个一模一样的纯净信号;本质是 “测量 + 复刻”。
- 异构方案:核心思路与方案二一致,区别是将算法计算部分交给 MCU 软件实现,硬件只做接口和实时逻辑;本质是软硬件分工协作。
4. 优化设计题
针对方案二三角波识别准确率低、易受信号幅度影响的问题,提出两种优化方案并说明原理。 解答:
-
方案 1:三次谐波检测法
- 原理:正弦波频谱只有基波,三角波频谱包含基波和奇次谐波,三次谐波幅度约为基波的 1/9;
- 实现:检测峰值频率 3 倍处的功率,若三次谐波功率与基波功率比值大于阈值(如 0.1),判定为三角波,否则为正弦波;
- 优势:比值判断不受信号绝对幅度影响,抗幅度波动能力远强于单阈值法。
-
方案 2:归一化阈值法
- 原理:先对整帧功率做归一化,再用相对阈值判断;
- 实现:计算峰值功率与噪声底的比值(信噪比),根据信噪比动态调整判断阈值,避免小信号时误判为三角波。
5. 资源分析题
对比方案一和方案二在 FPGA 资源占用(LUT、触发器、BRAM、DSP)上的差异,说明原因。 解答: 两种方案的资源占用差异如下表所示:
表格
| 资源类型 | 方案一(FIR 滤波) | 方案二(FFT + 锁相重构) | 差异原因 |
|---|---|---|---|
| LUT / 触发器 | 少 | 多 | 方案二包含 FFT IP 核、峰值检测状态机、多路波形切换逻辑、锁相环控制逻辑,逻辑复杂度远高于方案一,因此消耗更多查找表和触发器资源。 |
| BRAM | 2 个(两路 FIR 延迟线) | 多个(FFT 内部缓存 + 延迟补偿 RAM) | 方案一每路 FIR 仅需 1 个 1024x16 的 BRAM 做延迟线;方案二 FFT IP 核内部需要大量缓存存储中间运算结果,加上输出延迟补偿的双端口 BRAM,BRAM 用量是方案一的数倍。 |
| DSP 乘法器 | 2 个(两路 FIR 各 1 个) | 多个(FFT 运算 + 功率计算 + 鉴相乘法器) | 方案一采用单 MAC 分时复用,每路滤波器仅需 1 个乘法器;方案二 FFT 蝶形运算需要大量乘法器,加上功率计算的 2 个乘法器、鉴相乘法器,DSP 用量显著更高。 |
| 整体资源占比 | 极低(Artix-7 系列占比 < 5%) | 中等(Artix-7 系列占比约 20%~30%) | 方案一资源占用极小,可在最小容量 FPGA 上运行;方案二功能更强,资源消耗相应增加,但仍在主流 FPGA 的承载范围内。 |
总体而言,方案一资源占用极低,适合小容量 FPGA 和极简硬件设计;方案二资源消耗更大,但功能和适应性更强。
6. 工程应用题
若题目要求支持 20kHz~100kHz 范围内 5kHz 步进的信号分离,三种方案分别需要如何修改? 解答:
- 方案一(FIR):无法动态适配,需要为每个频率对重新设计滤波器系数,编译多个比特流切换,不适合现场动态调整;
- 方案二:修改
apart模块的频率查表,将 10kHz 一档加密为 5kHz 一档,同时调整P_and_F_out的峰值判断逻辑即可,无需修改硬件架构; - 方案三:无需修改 FPGA 逻辑,仅需修改 STM32 软件的识别逻辑,支持连续频率识别,适配性最强。
7. 时序分析题
方案一 1024 阶 FIR 滤波器,100MHz 时钟下,每个输入数据需要多少个时钟周期完成滤波计算?最高支持多高的采样率? 解答:
- 单 MAC 分时复用架构下,每个数据需要遍历 1024 个抽头,因此需要 1024 个时钟周期完成一次滤波计算;
- 100MHz 时钟下,1024 个周期为 10.24μs,对应最高采样率约为 97.6kHz;
- 若要支持更高采样率,可采用多 MAC 并行架构,以资源换速度。
8. 锁相原理题
说明方案二锁相环中 “负反馈锁定” 的完整物理过程。 解答:
- 初始状态:DDS 自由运行,频率与输入信号存在偏差;
- 鉴相:输入信号与 DDS 信号相乘,输出包含和频与差频;差频的幅值随相位差变化;
- 滤波:FIR 低通滤除高频和频,输出与相位差成正比的直流误差信号;
- 调节:误差信号叠加到 DDS 频率控制字上:若 DDS 频率偏低,误差为正,频率升高;若 DDS 频率偏高,误差为负,频率降低;
- 锁定:经过多次反馈调节,DDS 频率与输入信号完全相同,相位差保持恒定,误差信号稳定为固定直流值,进入锁定状态。
9. 代码优化题
方案二中datasum = (data1+data2)<<<1存在溢出风险,请给出两种修正方案。 解答:
-
方案 1:扩展位宽 将
datasum位宽从 10 位扩展到 12 位,即wire signed [11:0] datasum,相加后左移 1 位仍有足够余量,不会溢出。后续 FFT 输入对应调整位宽即可。 -
方案 2:先移位再相加 两路信号各右移 1 位再相加,即
datasum = (data1>>>1) + (data2>>>1),等效增益为 1,不会溢出;若需要增益可后续在 FFT 内部调整。
10. 方案选型题
电赛现场调试时间有限,目标是稳拿基本分并预留部分发挥空间,应该选择哪种方案?说明理由。 解答: 选择方案二(纯 FPGA FFT + 锁相重构),理由:
- 单芯片硬件简单,不需要双芯片联调,减少故障点,调试周期短;
- 自适应频率识别,无需为不同频率重新编译,适配基本要求和发挥部分的多频率要求;
- 官方 IP 核稳定性高,锁相环参数一次调试完成后可靠性强,适合电赛现场紧张的调试环境;
- 相比方案一功能更强,相比方案三开发调试更简单,是性价比最高的折中方案。
五、综合设计题(共 5 题,电赛发挥题风格)
1. 相位差可调设计
题目要求两路输出信号的相位差在 0°~180° 范围内连续可调,步进 5°。分别针对三种方案说明实现方法。 解答:
- 方案一(FIR):在 DDS 的 B 路相位累加器上叠加相位控制字,每 5° 对应相位字为 5/360×2³²,通过拨码开关输入相位值,DDS 输出即带指定相位差,滤波后相位差保持不变。
- 方案二(锁相):在锁相环 DDS 的相位累加器端叠加固定相位偏移量,不影响锁相环路,只改变输出相位。注意偏移量不能加在频率控制字上,否则会影响锁相。
- 方案三(异构):STM32 识别频率后,将相位差指令通过串口发给 FPGA,FPGA 在 DDS 端叠加相位偏移,实现上位机可调。
2. 幅度自适应设计
题目要求输入信号幅度在 0.5~2 倍范围内变化时,输出幅度保持恒定。说明实现方法。 解答: 在输出端增加自动增益控制(AGC)环路:
- 检测输出信号的峰值或有效值;
- 与目标幅值比较得到误差;
- 通过低通滤波后控制数字乘法器的增益;
- 形成负反馈,保持输出幅度稳定。 三种方案都可在 DAC 前插入 AGC 模块,不影响核心分离逻辑。
3. 谐波抑制提升
题目要求输出信号谐波抑制比优于 60dB,针对方案二提出优化方法。 解答:
- DDS 端优化:增加 DDS 输出位宽,使用更大的波形 ROM,减少量化噪声和谐波失真;
- 后端滤波:在 DDS 输出后增加高阶低通 FIR 滤波器,滤除 DDS 的杂散和谐波;
- 三角波优化:三角波本身谐波丰富,若要高抑制比,应改用纯正弦 DDS 输出,三角波模式也通过数字滤波生成,而非直接 ROM 查表。
4. 多信号扩展
题目要求支持三路信号混合与分离,说明三种方案的修改量。 解答:
- 方案一:增加一路 DDS,加法器扩展为三路相加,再增加一路对应频率的 FIR 滤波器,修改量最小;
- 方案二:峰值检测模块需找出前三大峰值,增加一路锁相环 DDS 和延迟补偿通道,修改量中等;
- 方案三:仅修改 STM32 软件的峰值排序逻辑,FPGA 端增加一路输出,软件修改为主,扩展性最好。
5. 低功耗优化
题目要求系统静态功耗降低 30%,针对纯 FPGA 方案提出优化方法。 解答:
- 时钟门控:FFT 运算间隙关闭 FFT 模块时钟,只在采样一帧后开启一次运算;
- 资源共享:两路锁相环可共享一个乘法器和 FIR 滤波器,分时复用,减少 DSP 数量;
- 降低系统时钟:若性能允许,将 8.192MHz 进一步降低,动态功耗与频率成正比;
- 使用低功耗系列 FPGA:选择 Artix-7 而非 Kintex-7 系列
更多推荐

所有评论(0)