本文完整嵌入三套方案的全部可工程化代码,逐模块对应原理、考点与设计细节,所有习题均基于真实代码出题,覆盖原理理解、参数计算、代码改错、方案优化全维度考点。


第一部分 三种方案完整代码与深度解析

方案一:纯 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 代码核心考点
  1. 资源优化设计:1024 阶 FIR 仅用 1 个乘法器分时复用,通过 BRAM 存储历史数据,是电赛中高阶 FIR 的标准低成本实现;
  2. 定点数运算:全链路 Q1.15 格式统一,乘累加后高位截断输出,符合 FPGA 数字信号处理规范;
  3. 溢出保护:加法器采用饱和截断,避免信号削顶失真,是模拟 + 数字混合设计的必备细节;
  4. 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 代码设计要点与已知问题
  1. 时钟选型技巧:8.192MHz 匹配 16384 点 FFT,频率分辨率刚好 500Hz,是电赛 FFT 方案的经典参数搭配;
  2. 锁相环实现方式:用 DDS 频率字叠加误差信号实现压控,无需额外 NCO,是 FPGA 数字锁相的标准工程写法;
  3. 已知 Bug1(已修复)P_and_F_outpow_out1pow_out2做了交换,但频率未同步交换,导致频率与功率对应关系颠倒,修复后频率与功率同步交换;
  4. 已知 Bug2(已修复)datasum = (data1+data2)<<<1,10 位有符号数相加后左移会溢出,大信号下波形削顶,修复后扩展位宽至 11 位,取消左移;
  5. 局限:频率查表仅 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 代码设计要点
  1. 跨时钟域处理:异步 FIFO 采用格雷码指针 + 两级同步器,解决 FPGA 高速时钟与 UART 低速时钟的跨域问题,是异构系统的核心考点;
  2. 算法分工:STM32 用硬件 DSP 指令做 FFT,开发调试便捷;FPGA 做实时锁相和高速接口,发挥各自优势;
  3. 三角波识别优化:通过三次谐波占比判断,比单纯基波功率阈值法准确率更高,抗幅度波动能力更强。

1.4 三种方案综合对比表

表格

对比维度 纯 FPGA FIR 滤波 纯 FPGA FFT 重构 + 锁相 FPGA+STM32 异构
核心思路 直接频域滤波分离 频域识别 + 锁相波形重构 软件识别 + 硬件重构锁相
频率适应性 差,仅支持固定频率 中,支持预设步进频率 强,支持连续自适应识别
输出波形质量 较好,受限于滤波器阻带衰减 高,DDS 生成无杂波 高,DDS 生成无杂波
硬件复杂度 最低,单 FPGA 低,单 FPGA 高,双芯片 + 配套电路
开发难度 最低,单语言单时钟域 中等,多 IP 核联调 最高,Verilog+C 联合开发
相位稳定性 最佳,同源时钟无漂移 良好,锁相后稳定 良好,锁相后稳定
适合赛题范围 仅基本要求(固定频率) 基本 + 部分发挥部分 全题目要求,冲满分
电赛调试周期 最短,一次调试完成 中等,需调锁相参数 最长,软硬件联调

第二部分 精品习题与深度解析

一、单项选择题(共 35 题,全代码覆盖)

  1. 方案一 FIR 滤波方案中,1024 阶滤波器仅使用 1 个乘法器,其核心优化架构是? A. 并行全乘加架构 B. 单 MAC 分时复用 + BRAM 延迟线 C. 分布式算法 D. 半带滤波器折叠 答案:B 解析:代码中通过环形 BRAM 存储历史采样数据,单个乘累加器逐次遍历 1024 个抽头完成运算,分时复用大幅减少乘法器资源占用。

  2. 方案二代码中,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 位高位对齐。

  3. 方案二代码中,tri_judge = 2代表的波形组合是? A. 两路都是正弦波 B. 第一路正弦波、第二路三角波 C. 第一路三角波、第二路正弦波 D. 两路都是三角波 答案:B 解析:代码中judge1==1 && judge2==0tri_judge_reg=2,对应第一路判定为三角波、第二路为正弦波。

  4. 方案三异步 FIFO 中,使用格雷码指针的核心目的是? A. 提升读写速度 B. 减少资源占用 C. 避免跨时钟域地址传输的亚稳态 D. 简化空满判断逻辑 答案:C 解析:二进制地址多位同时跳变,跨时钟域采样容易出错;格雷码每次仅 1 位跳变,配合两级同步器可有效避免亚稳态。

  5. 方案一中 DDS 频率控制字计算公式为 fword = f_out * 2^32 / Fclk,100MHz 时钟下 50kHz 对应的控制字约为? A. 21474836 B. 42949673 C. 8192000 D. 102400 答案:A 解析:代入公式:50000 × 4294967296 / 100000000 ≈ 21474836,与代码中FWORD_50K定义一致。

  6. 方案二代码中,锁相环的环路滤波器采用的是? A. RC 模拟低通 B. FIR 数字低通 C. IIR 一阶低通 D. 滑动平均滤波 答案:B 解析:代码中调用fir_compiler_0 IP 核实现环路低通滤波,滤除鉴相输出的高频和频分量。

  7. 方案一加法器中采用饱和截断的主要作用是? A. 提升运算速度 B. 防止信号溢出削顶失真 C. 减少位宽节省资源 D. 提升信号信噪比 答案:B 解析:两路 16 位信号相加可能超出 16 位范围,饱和截断将超出范围的值钳位在最大 / 最小值,避免溢出导致的波形畸变。

  8. 方案二代码中,8.192MHz 采样率搭配 16384 点 FFT,能够分辨的最小频率差是? A. 100Hz B. 500Hz C. 1kHz D. 5kHz 答案:B 解析:频率分辨率 = 采样率 / FFT 点数 = 8192000 / 16384 = 500Hz。

  9. 方案三 STM32 端 FFT 采用 CMSIS-DSP 库的arm_cfft_f32函数,其输入数组的排列方式是? A. 全部实部在前,全部虚部在后 B. 实部虚部交替排列:实部、虚部、实部、虚部 C. 全部虚部在前,全部实部在后 D. 仅实部,虚部自动补 0 答案:B 解析:CMSIS-DSP 复数 FFT 要求输入为交替排列的复数数组,偶数下标为实部,奇数下标为虚部。

  10. 方案二outdelay模块实现延迟补偿的核心载体是? A. 移位寄存器 B. 双端口 BRAM 环形缓冲区 C. FIFO D. 计数器打拍 答案:B 解析:代码中实例化两个双端口delayram,写指针自增循环写入,读地址 = 写指针 - 延迟值,实现任意长度的相位延迟补偿。

  11. 方案一 FIR 滤波器采用 Q1.15 定点格式,乘累加后输出取高 16 位,其本质是? A. 左移 15 位 B. 右移 15 位 C. 四舍五入 D. 饱和截断 答案:B 解析:两个 Q1.15 格式数相乘得到 Q2.30 格式,累加后为 Q2.30,取高 16 位相当于右移 15 位,还原为 Q1.15 格式。

  12. 方案二代码中,FFT IP 核配置字为8'd1,代表的含义是? A. 1 点 FFT B. 正向 FFT 变换 C. 反向 FFT 变换 D. 缩放因子为 1 答案:B 解析:Xilinx FFT IP 核配置字最低位为变换方向,1 代表正向 FFT,0 代表反向 IFFT。

  13. 下列关于 FIR 滤波器的说法,错误的是? A. FIR 滤波器可以实现严格的线性相位 B. FIR 滤波器系统一定是稳定的 C. 相同阶数下 FIR 比 IIR 阻带衰减更大 D. FIR 滤波器可以用分时复用架构降低资源 答案:C 解析:相同阶数下 IIR 滤波器的选频特性更好,阻带衰减更大;FIR 需要更高阶数才能达到相同的阻带衰减。

  14. 方案二锁相环中,鉴相器使用乘法器实现,当两路信号相位差为 90 度时,输出的直流分量为? A. 最大值 B. 最小值 C. 0 D. 不确定 答案:C 解析:根据三角恒等式,sinA * sinB = [cos (A-B)-cos (A+B)]/2,当相位差为 90 度时,cos (90°)=0,因此直流分量为 0。

  15. 方案三异步 FIFO 中,空信号判断的依据是? A. 写指针等于读指针 B. 写指针比读指针大一圈 C. 读指针追上写指针 D. 写指针追上读指针 答案:A 解析:异步 FIFO 中,当读指针和写指针完全相等时,代表 FIFO 中没有数据,为空状态。

  16. 方案一 1024 阶线性相位 FIR 滤波器,群延迟约为多少个时钟周期? A. 512 B. 511.5 C. 1024 D. 2048 答案:B 解析:N 阶线性相位 FIR 滤波器的群延迟为 (N-1)/2 个采样周期,1024 阶即 (1024-1)/2=511.5 个周期。

  17. 方案二代码中,ADC 数据减去 512 的目的是? A. 提升信号幅值 B. 将无符号数转为有符号交流信号 C. 降低信号噪声 D. 做直流增益 答案:B 解析:10 位 ADC 输出范围 0~1023,减去中间值 512 后,范围变为 - 512~+511,转为以 0 为中心的有符号交流信号,符合数字信号处理的运算规范。

  18. 下列哪种窗函数设计的 FIR 滤波器阻带衰减最大? A. 矩形窗 B. 汉宁窗 C. 汉明窗 D. 布莱克曼窗 答案:D 解析:布莱克曼窗阻带衰减约 74dB,汉明窗约 53dB,汉宁窗约 44dB,矩形窗约 21dB。

  19. 方案二代码中,功率计算模块没有对结果开根号,主要原因是? A. 开根号误差大 B. 不影响峰值大小比较,节省资源 C. 功率本身不需要开根号 D. 开根号延迟太大 答案:B 解析:峰值检测只需要比较相对大小,平方值的大小关系和模值一致,不开根号可以节省开根号运算的大量逻辑资源,同时减少运算延迟。

  20. DDS 频率合成技术中,相位累加器位宽主要影响哪个指标? A. 输出幅值精度 B. 频率分辨率 C. 谐波失真 D. 最高输出频率 答案:B 解析:DDS 频率分辨率 = 系统时钟 / 2^N,N 为相位累加器位宽,位宽越大,频率分辨率越高。

  21. 方案三 UART 串口通信中,波特率 115200 的含义是? A. 每秒传输 115200 个字节 B. 每秒传输 115200 个比特 C. 每秒传输 115200 个数据帧 D. 时钟频率为 115.2kHz 答案:B 解析:波特率是单位时间内传输的码元数,UART 中 1 个码元对应 1 个比特,因此 115200 波特代表每秒传输 115200 个比特。

  22. 方案二outdelay模块中,不同频率对应不同延迟值的原因是? A. 频率越高延迟越大 B. 相同时钟延迟下,不同频率对应的相位差不同 C. 系统延迟随频率变化 D. DAC 转换延迟随频率变化 答案:B 解析:系统固有延迟是固定的时钟周期数,但不同频率下,相同时钟周期对应的相位角度不同,因此需要根据频率调整延迟值,保证相位对齐。

  23. 数字锁相环进入锁定状态后,下列说法正确的是? A. 输出频率等于输入频率,相位差为 0 B. 输出频率等于输入频率,相位差恒定 C. 输出频率大于输入频率,相位差恒定 D. 输出频率小于输入频率,相位差为 0 答案:B 解析:锁相环锁定后,输出与输入同频,但存在固定的稳态相位差,相位差保持恒定不变。

  24. 方案一 BRAM 延迟线采用环形缓冲区,其写指针的变化规律是? A. 递增到最大值后复位为 0 B. 递增到最大值后递减 C. 保持不变 D. 随读指针变化 答案:A 解析:环形缓冲区写指针持续自增,位宽自动溢出归零,实现 0~1023 循环写入,始终覆盖最早的数据。

  25. 实信号做 FFT 变换后,频谱的有效范围是? A. 0~ 采样率 B. 0~ 采样率 / 2 C. 0~ 采样率 / 4 D. 采样率 / 4~ 采样率 / 2 答案:B 解析:根据奈奎斯特采样定理,实信号 FFT 频谱具有共轭对称性,有效信息仅在 0~Fs/2 范围内,后半部分是前半部分的镜像。

  26. 方案二三角波识别采用基波功率阈值法,其最大的缺陷是? A. 运算量大 B. 受信号幅度影响大 C. 识别速度慢 D. 容易受噪声干扰 答案:B 解析:信号幅度变化会直接导致基波功率变化,固定阈值在信号小时容易误判为三角波,信号大时容易误判为正弦波。

  27. 异步 FIFO 中,两级同步器的作用是? A. 提升读写速度 B. 消除亚稳态 C. 降低亚稳态发生的概率 D. 同步数据 答案:C 解析:两级同步器无法完全消除亚稳态,但可以将亚稳态出现的概率降低到可忽略的程度,是跨时钟域处理的标准方法。

  28. 方案一加法器采用饱和截断而不是直接截断,主要是为了? A. 节省资源 B. 避免溢出后波形严重失真 C. 提升运算速度 D. 减小量化误差 答案:B 解析:直接截断溢出后会出现严重的 wrap-around 失真,波形完全畸变;饱和截断将值钳位在最大值 / 最小值,波形仅削顶,失真程度远小于直接截断。

  29. 下列关于 FFT 栅栏效应的描述,正确的是? A. 增加采样率可以消除栅栏效应 B. 增加 FFT 点数可以减小栅栏效应 C. 加窗可以消除栅栏效应 D. 栅栏效应只影响高频信号 答案:B 解析:FFT 点数越多,频率分辨率越高,栅栏效应越弱;增加采样率不改变频率分辨率,无法减小栅栏效应。

  30. 方案二PllOneOrder模块中,误差信号右移 6 位再叠加到频率字上,其作用是? A. 放大误差信号,加快锁相速度 B. 减小误差增益,保证环路稳定 C. 提高频率精度 D. 扩展频率调节范围 答案:B 解析:右移 6 位相当于除以 64,减小环路增益,避免环路振荡失锁,保证锁相环稳定工作,相当于比例控制器的比例系数。

  31. 方案一sin_rom_1024x16模块使用$readmemh初始化 ROM,综合后会被推断为? A. 分布式 RAM B. 块 RAM(BRAM) C. 寄存器数组 D. 查找表 答案:B 解析:1024x16 位的 ROM 深度和宽度符合 BRAM 的规格,综合工具会自动推断为块 RAM 资源,不占用逻辑单元。

  32. 方案二代码中,FFT 点数为 16384,完成一帧 FFT 运算需要多少个采样周期? A. 1024 B. 8192 C. 16384 D. 32768 答案:C 解析:流水线架构的 FFT IP 核,输入 16384 个采样点后输出结果,因此完成一帧需要 16384 个采样周期。

  33. 三角波的频谱中,三次谐波的幅度约为基波的多少倍? A. 1/2 B. 1/3 C. 1/9 D. 1/27 答案:C 解析:三角波傅里叶级数展开式中,奇次谐波幅度与谐波次数的平方成反比,三次谐波幅度是基波的 1/9。

  34. 方案三 STM32 端使用 CMSIS-DSP 库做 FFT,其核心优势是? A. 精度更高 B. 调用 ARM 硬件 DSP 指令,运算速度快 C. 代码更简洁 D. 支持更大点数 FFT 答案:B 解析:CMSIS-DSP 库针对 ARM 内核做了优化,调用硬件 DSP 指令,FFT 运算速度比纯 C 语言实现快数倍,适合嵌入式实时处理。

  35. 电赛 H 题基本要求为固定频率正弦波分离,从开发难度和稳定性角度,最优方案是? A. 纯 FPGA FIR 滤波方案 B. 纯 FPGA FFT 重构方案 C. FPGA+STM32 异构方案 D. 纯模拟滤波方案 答案:A 解析:固定频率场景下,FIR 滤波方案逻辑最简单,同源时钟无同步问题,稳定性最高,开发调试最快,最适合保底拿基本分。


二、填空题(共 15 题)

  1. 1024 阶线性相位 FIR 滤波器的群延迟为____个采样周期。 答案:511.5

  2. 方案二代码中,系统采样率 8.192MHz,16384 点 FFT,奈奎斯特频率为____MHz。 答案:4.096

  3. Q1.15 格式 16 位有符号数,能表示的最小负数为____。 答案:-32768

  4. 数字锁相环的三个核心组成部分是鉴相器、____、压控振荡器。 答案:环路滤波器

  5. 异步 FIFO 中使用格雷码指针的核心目的是避免____。 答案:亚稳态

  6. 方案二代码中,鉴相乘法器输出的两个频率分量是和频与____。 答案:差频

  7. 实信号的 FFT 频谱具有____对称性,因此只需要分析前半部分。 答案:共轭

  8. 三角波的频谱只包含____次谐波。 答案:奇

  9. FIR 滤波器设计中,阶数越高,过渡带越____。 答案:窄

  10. DDS 频率合成中,相位累加器位宽越____,频率精度越高。 答案:大

  11. FPGA 中推断块 RAM 的关键字是____(初始化语句)。 答案:$readmemh / $readmemb

  12. 饱和截断的核心作用是防止信号____。 答案:溢出削顶失真

  13. 一阶数字锁相环的捕获范围____锁定范围(填 “大于 / 小于 / 等于”)。 答案:等于

  14. UART 串口一帧数据包含 1 位起始位、8 位数据位、1 位停止位,波特率 115200 时,每秒最多传输____个字节。 答案:11520

  15. 汉宁窗设计的 FIR 滤波器阻带衰减约为____dB。 答案:44


三、判断题(共 15 题)

  1. FIR 滤波器一定是稳定的。( ) 答案:√ 解析:FIR 滤波器全部极点都在原点,必然稳定。

  2. 方案二锁相环上电后立即进入锁定状态,无需等待。( ) 答案:× 解析:锁相环需要捕获过程,经过若干周期才能进入锁定状态。

  3. 异步 FIFO 读写时钟频率相同时,不会产生亚稳态。( ) 答案:× 解析:只要是不同时钟域,即使频率相同相位不同,也可能产生亚稳态。

  4. 高阶 FIR 滤波器的阻带衰减一定比低阶的大。( ) 答案:× 解析:阻带衰减由窗函数类型决定,阶数只影响过渡带宽度,不影响阻带衰减。

  5. 三角波含有偶次谐波。( ) 答案:× 解析:三角波是奇对称信号,只含有奇次谐波。

  6. 方案一 FIR 滤波方案输出信号与原始信号是同源的。( ) 答案:√ 解析:所有模块共用一个系统时钟,输出是输入经过延迟和滤波的结果,同源同频。

  7. FFT 点数越多,频率分辨率越高,运算量越小。( ) 答案:× 解析:点数越多分辨率越高,但运算量也越大。

  8. 饱和截断可以防止信号溢出削顶。( ) 答案:√

  9. 格雷码相邻两个码组之间只有 1 位不同。( ) 答案:√

  10. DDS 输出信号的最高频率可以等于系统时钟频率。( ) 答案:× 解析:根据奈奎斯特定理,DDS 最高输出频率应小于系统时钟的 1/2,工程上通常取 1/3 以下。

  11. 实信号 FFT 的正负频率部分互为共轭。( ) 答案:√

  12. 数字锁相环锁定后,输出相位与输入相位完全相同。( ) 答案:× 解析:锁定后相位差恒定,但存在稳态相位差,不一定为 0。

  13. 窗函数可以减小频谱泄漏,同时会提高频率分辨率。( ) 答案:× 解析:加窗减小频谱泄漏的同时,主瓣变宽,频率分辨率会降低。

  14. 方案三异构方案中,FPGA 和 STM32 之间可以直接用数据线并行传输,速度更快。( ) 答案:× 解析:跨芯片并行传输存在同步问题,串口等串行协议更稳定可靠。

  15. FIR 滤波器的线性相位特性意味着所有频率分量的群延迟相同。( ) 答案:√


四、解答题(共 10 题,深度考察代码与原理)

1. 代码改错题

请指出方案二P_and_F_out模块中的一处逻辑错误,说明影响并给出修正方案。 解答

  • 错误点:模块末尾输出赋值中,pow_out1 = pow_out2_regpow_out2 = pow_out1_reg做了功率交换,但freq_out1freq_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. 原理分析题

对比三种方案,分别说明它们解决 “信号分离” 问题的核心思路与本质区别。 解答

  1. FIR 滤波方案频域直接选频,通过滤波器的幅频响应,保留目标频率分量、衰减非目标分量,信号本身没有被改变,只是做了 “筛选”;本质是线性时不变系统的频率选择特性。
  2. FFT + 重构方案参数识别 + 波形再生,不保留原始信号,而是先测量出原始信号的频率、波形类型,再用 DDS 重新生成一个一模一样的纯净信号;本质是 “测量 + 复刻”。
  3. 异构方案:核心思路与方案二一致,区别是将算法计算部分交给 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 步进的信号分离,三种方案分别需要如何修改? 解答

  1. 方案一(FIR):无法动态适配,需要为每个频率对重新设计滤波器系数,编译多个比特流切换,不适合现场动态调整;
  2. 方案二:修改apart模块的频率查表,将 10kHz 一档加密为 5kHz 一档,同时调整P_and_F_out的峰值判断逻辑即可,无需修改硬件架构;
  3. 方案三:无需修改 FPGA 逻辑,仅需修改 STM32 软件的识别逻辑,支持连续频率识别,适配性最强。
7. 时序分析题

方案一 1024 阶 FIR 滤波器,100MHz 时钟下,每个输入数据需要多少个时钟周期完成滤波计算?最高支持多高的采样率? 解答

  • 单 MAC 分时复用架构下,每个数据需要遍历 1024 个抽头,因此需要 1024 个时钟周期完成一次滤波计算;
  • 100MHz 时钟下,1024 个周期为 10.24μs,对应最高采样率约为 97.6kHz;
  • 若要支持更高采样率,可采用多 MAC 并行架构,以资源换速度。
8. 锁相原理题

说明方案二锁相环中 “负反馈锁定” 的完整物理过程。 解答

  1. 初始状态:DDS 自由运行,频率与输入信号存在偏差;
  2. 鉴相:输入信号与 DDS 信号相乘,输出包含和频与差频;差频的幅值随相位差变化;
  3. 滤波:FIR 低通滤除高频和频,输出与相位差成正比的直流误差信号;
  4. 调节:误差信号叠加到 DDS 频率控制字上:若 DDS 频率偏低,误差为正,频率升高;若 DDS 频率偏高,误差为负,频率降低;
  5. 锁定:经过多次反馈调节,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 + 锁相重构),理由:

  1. 单芯片硬件简单,不需要双芯片联调,减少故障点,调试周期短;
  2. 自适应频率识别,无需为不同频率重新编译,适配基本要求和发挥部分的多频率要求;
  3. 官方 IP 核稳定性高,锁相环参数一次调试完成后可靠性强,适合电赛现场紧张的调试环境;
  4. 相比方案一功能更强,相比方案三开发调试更简单,是性价比最高的折中方案。

五、综合设计题(共 5 题,电赛发挥题风格)

1. 相位差可调设计

题目要求两路输出信号的相位差在 0°~180° 范围内连续可调,步进 5°。分别针对三种方案说明实现方法。 解答

  • 方案一(FIR):在 DDS 的 B 路相位累加器上叠加相位控制字,每 5° 对应相位字为 5/360×2³²,通过拨码开关输入相位值,DDS 输出即带指定相位差,滤波后相位差保持不变。
  • 方案二(锁相):在锁相环 DDS 的相位累加器端叠加固定相位偏移量,不影响锁相环路,只改变输出相位。注意偏移量不能加在频率控制字上,否则会影响锁相。
  • 方案三(异构):STM32 识别频率后,将相位差指令通过串口发给 FPGA,FPGA 在 DDS 端叠加相位偏移,实现上位机可调。
2. 幅度自适应设计

题目要求输入信号幅度在 0.5~2 倍范围内变化时,输出幅度保持恒定。说明实现方法。 解答: 在输出端增加自动增益控制(AGC)环路:

  1. 检测输出信号的峰值或有效值;
  2. 与目标幅值比较得到误差;
  3. 通过低通滤波后控制数字乘法器的增益;
  4. 形成负反馈,保持输出幅度稳定。 三种方案都可在 DAC 前插入 AGC 模块,不影响核心分离逻辑。
3. 谐波抑制提升

题目要求输出信号谐波抑制比优于 60dB,针对方案二提出优化方法。 解答

  1. DDS 端优化:增加 DDS 输出位宽,使用更大的波形 ROM,减少量化噪声和谐波失真;
  2. 后端滤波:在 DDS 输出后增加高阶低通 FIR 滤波器,滤除 DDS 的杂散和谐波;
  3. 三角波优化:三角波本身谐波丰富,若要高抑制比,应改用纯正弦 DDS 输出,三角波模式也通过数字滤波生成,而非直接 ROM 查表。
4. 多信号扩展

题目要求支持三路信号混合与分离,说明三种方案的修改量。 解答

  • 方案一:增加一路 DDS,加法器扩展为三路相加,再增加一路对应频率的 FIR 滤波器,修改量最小;
  • 方案二:峰值检测模块需找出前三大峰值,增加一路锁相环 DDS 和延迟补偿通道,修改量中等;
  • 方案三:仅修改 STM32 软件的峰值排序逻辑,FPGA 端增加一路输出,软件修改为主,扩展性最好。
5. 低功耗优化

题目要求系统静态功耗降低 30%,针对纯 FPGA 方案提出优化方法。 解答

  1. 时钟门控:FFT 运算间隙关闭 FFT 模块时钟,只在采样一帧后开启一次运算;
  2. 资源共享:两路锁相环可共享一个乘法器和 FIR 滤波器,分时复用,减少 DSP 数量;
  3. 降低系统时钟:若性能允许,将 8.192MHz 进一步降低,动态功耗与频率成正比;
  4. 使用低功耗系列 FPGA:选择 Artix-7 而非 Kintex-7 系列
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐