一、项目背景(赛题需求)
2023 全国电赛 H 题为双频混合信号分离装置:
输入:单路模拟混合信号C = 低频A + 高频B
输出:两路独立 DAC 分别还原出 A、B 波形,且支持:
正弦 / 三角波可切换输出
输出相位 0~180° 连续可调(发挥题)
无失真分离高低频叠加波形
常规方案:STM32+FFT频域分解、滤波、逆变换重构,资源开销大、相位难控。
本文方案:FPGA 纯时域信号分离,无 FFT、无浮点、全整数运算、稳定不跑频。
二、整体核心原理(全文最关键)
1. 为什么模拟电路要加直流偏置?
ADC 是单电源 0~3.3V 采集,无法识别负压。
而交流正弦波天然存在负半周(-0.5V~+0.5V)。
因此模拟运放强制抬升直流电平,把波形整体抬高,全部变为正数可采集。
2. 为什么 FPGA 必须减掉直流偏置?
ADC 采到的数据 = 真实交流信号 + 人为直流偏置
后端所有滤波、鉴相、相关运算必须以 0 为中点,否则:
乘法相关直流饱和
相位匹配失效
无法区分正负半周
所以adc.v 第一行核心代码:i_data - DC_BIAS还原纯交流波形。
3. 整套分离算法核心思想
混合信号:C = 低频A + 高频B
低频A:直接通过长窗口均值低通滤波滤除高频得到
高频B:使用外部同频DDS参考 + 时域最小误差锁相,自动对齐相位提取
全程时域处理,不需要频域变换,FPGA资源极小、极其稳定。
三、整体工程架构(Top 层级)
整套工程一共 8 个模块,流水线结构:
adc.v:ADC 采集 + 去直流偏置
la_filter.v:128点滑动均值低通,提取低频A
sin_to_tri.v:正弦波实时转三角波
signal_mux.v:正弦/三角波形二选一输出
phase_adjust.v:按键可调数字移相(0~180°)
b_rescue.v:自动锁相、相位搜索、高频B对齐核心
lp_filter.v:乘法鉴相+低通,判断相位是否对齐
dac.v:DAC 时序输出
四、两条核心数据流(重中之重)
数据流1:低频信号 A 分离输出(简单、稳定)
ADC混合信号 → 去直流 → 128点均值低通滤波 → 滤干净高频B → 得到纯低频A
可选:正弦转三角波 → 波形切换 → DAC1 输出
原理:长窗口均值滤波天然压制高频,完美分离低频分量。
数据流2:高频信号 B 锁相分离输出(核心难点)
外部输入纯净 DDS 高频B参考波 + 混合信号
通过b_rescue 256级延迟线遍历所有相位
计算左、中、右三点误差,自动寻找误差最小相位点
锁定相位后输出与混合信号内B分量完全同步的纯净波形 → DAC2输出
同时支持按键手动微调相位,满足赛题发挥要求。
五、逐模块深度解析
1. adc.v —— 去直流还原交流信号
核心语句:
assign o_data = i_data - DC_BIAS;
作用:
模拟端:人为叠加直流抬压适配ADC
数字端:减掉偏置,还原正负对称交流波形
所有后续算法必须依赖正负对称波形才能正常工作。
2. la_filter.v —— 128点滑动均值低通滤波器
很多同学以为只是“平滑降噪”,实际是分离低频A的核心。
原理:
128点超长窗口平均
彻底抹平高频波动
保留稳定低频波形
内部采用三级加法树并行求和,速度快、无溢出、无时序压力。
输出:混合信号中纯净低频A波形。
3. sin_to_tri.v —— 正弦转三角波
实现方式非常巧妙,无ROM、无查表:
统计正弦波最大、最小值
计算波形中点(直流中轴线)
大于中点:输出递增(上坡)
小于中点:输出递减(下坡)
自动生成线性升降标准三角波。
4. signal_mux.v —— 波形选择器
1 行代码实现功能切换:
en=0:输出原始正弦波
en=1:输出转换后的三角波
5. phase_adjust.v —— 全数字可调移相(发挥题核心)
内部 365 级深度移位寄存器延迟线:
按键 UP/DOWN 调整读取索引
改变波形延迟时间 = 改变相位
支持 0~180° 连续可调
自带按键消抖、边沿检测,不抖动、不连跳
纯数字移相,无温漂、无误差、精度极高。
6. b_rescue.v —— 全文最核心:自动锁相相位搜索
功能:自动找到 DDS 参考波与混合信号中高频分量的最佳匹配相位
原理:
256级移位缓存,存储所有相位延迟版本的DDS波形
每次计算:左移1、当前、右移1 三处误差绝对值
对比误差大小,自动向误差更小的方向移动相位
最终收敛到全局最小误差点——相位完全对齐
只要相位对齐,即可完美分离出高频B信号。
7. lp_filter.v —— 乘法鉴相器
同步解调经典原理:
同频信号相乘 → 产生直流分量
异频/相位不对齐 → 只有交流、无直流
内部乘法+32点均值滤波,提取直流能量,判断是否锁相成功。
8. dac.v —— 高速DA输出
统一DAC时序,将数字波形转为模拟波形输出到示波器。
六、整体工作流程(极简总结)
ADC 采集混合波形,FPGA 去除模拟直流偏置,恢复纯交流信号
长均值滤波提取低频A,直接输出(支持正弦/三角切换)
外部DDS提供纯净高频参考波
phase_adjust 支持人工微调相位
两路DAC分别输出分离后的 A、B 波形
七、方案优势(为什么这套代码能拿高分)
无 FFT、无浮点:资源占用极低,稳定不爆错
纯时域算法:延迟低、实时性强
自动锁相+手动移相:基础题+发挥题全部拉满
自适应波形转换:自动识别波峰波谷,适配任意幅度输入
多级防抖、阈值滤波:抗干扰极强,赛场上波形极其干净
八、适用赛题
完全适配2023电赛H题 信号分离装置
可直接工程复现、可直接答辩、可直接跑分。
九、完整工程模块清单
top.v、adc.v、dac.v、la_filter.v、sin_to_tri.v、signal_mux.v、phase_adjust.v、b_rescue.v、lp_filter.v
module adc( input clk, input [9:0] i_data, output [9:0] o_data, output adc_clk, output oe ); parameter DC_BIAS =10'd380; wire adc_clk_oddr; assign adc_clk_oddr=~clk; assign oe =1'b0; ODDR2 #( .DDR_ALIGNMENT("NONE"), // Sets output alignment to "NONE", "C0" or "C1" .INIT(1'b0), // Sets initial state of the Q output to 1'b0 or 1'b1 .SRTYPE("SYNC") // Specifies "SYNC" or "ASYNC" set/reset ) ODDR2_inst ( .Q(adc_clk), // 1-bit DDR output data .C0(adc_clk_oddr), // 1-bit clock input .C1(~adc_clk_oddr), // 1-bit clock input .CE(1'b1), // 1-bit clock enable input .D0(1'b1), // 1-bit data input (associated with C0) .D1(1'b0), // 1-bit data input (associated with C1) .R(1'b0), // 1-bit reset input .S(1'b0) // 1-bit set input ); assign o_data =i_data -DC_BIAS; endmodulemodule b_rescue( input clk, input sys_rst_n, input [9:0] rv_signal, input [9:0] input_signalA, input [9:0] dds_input, output [9:0] dds_shift ); reg [9:0] dds_input_shift [255:0]; reg [7:0] shift_reg; reg [7:0] tim_delay; wire signed [15:0] R_error; wire signed [15:0] N_error; wire signed [15:0] L_error; wire [15:0] AbsR_error; wire [15:0] AbsN_error; wire [15:0] AbsL_error; assign R_error =dds_input_shift[shift_reg+'d1]+rv_signal -input_signalA; assign L_error =dds_input_shift[shift_reg-'d1] +rv_signal -input_signalA; assign AbsR_error=R_error[15] ==1'b1 ?~R_error+1'b1:R_error; assign AbsN_error=N_error[15] ==1'b1 ?~N_error+1'b1:N_error; assign AbsL_error=L_error[15] ==1'b1 ?~L_error+1'b1:L_error; localparam THOLD ='d11; //Adjustable .. integer reg_index; always @(posedge clk or negedge sys_rst_n) if(!sys_rst_n) begin for(reg_index =0;reg_index <256;reg_index =reg_index +1) begin dds_input_shift[reg_index] <='d0; end end else begin dds_input_shift[0] <=dds_input; for(reg_index =0;reg_index <255;reg_index =reg_index +1) begin dds_input_shift[reg_index +1] <=dds_input_shift[reg_index]; end end //Time_delay for 128_clks always @(posedge clk or negedge sys_rst_n) if(!sys_rst_n) tim_delay <='d0; else if(tim_delay !=240) begin tim_delay <=tim_delay +1'b1; end else begin tim_delay <=tim_delay; end always @(posedge clk or negedge sys_rst_n) if(!sys_rst_n) shift_reg <=8'h00; else if(tim_delay ==240) begin if(AbsR_error <AbsN_error &&AbsN_error -AbsR_error >THOLD) shift_reg <=shift_reg +'d1; else if(AbsL_error <AbsN_error &&AbsN_error -AbsL_error >THOLD) shift_reg <=shift_reg -'d1; else begin shift_reg <=shift_reg; end end else begin shift_reg <=8'h00; end assign dds_shift =dds_input_shift[shift_reg]; endmodule//---------------------------------------------------------------------------------------------------------- // FILE: dac.v // AUTHOR: Biggest_apple // // ABSTRACT: // KEYWORDS: fpga, basic module,signal process // // MODIFICATION HISTORY: // $Log$ // Biggest_apple 2023.7.25 create // Adding:DC_BIAS parameter //----------------------------------------------------------------------------------------------------------- module dac( input clk, input [9:0] i_data, output [9:0] o_data, output dac_clk ); wire dac_clk_oddr; assign dac_clk_oddr=~clk; ODDR2 #( .DDR_ALIGNMENT("NONE"), // Sets output alignment to "NONE", "C0" or "C1" .INIT(1'b0), // Sets initial state of the Q output to 1'b0 or 1'b1 .SRTYPE("SYNC") // Specifies "SYNC" or "ASYNC" set/reset ) ODDR2_inst ( .Q(dac_clk), // 1-bit DDR output data .C0(dac_clk_oddr), // 1-bit clock input .C1(~dac_clk_oddr), // 1-bit clock input .CE(1'b1), // 1-bit clock enable input .D0(1'b1), // 1-bit data input (associated with C0) .D1(1'b0), // 1-bit data input (associated with C1) .R(1'b0), // 1-bit reset input .S(1'b0) // 1-bit set input ); assign o_data =i_data; endmodule//---------------------------------------------------------------------------------------------------------- // FILE: La_filter.v // AUTHOR: Biggest_apple // // ABSTRACT: // KEYWORDS: fpga, basic module,signal process // // MODIFICATION HISTORY: // $Log$ // Biggest_apple 2023.7.25 create // Final_Tb //----------------------------------------------------------------------------------------------------------- module la_filter( input clk, input sys_rst_n, //This is a test project not for any "real use" input [9:0] input_signal, output [9:0] sigmaS_out, output [63:0] sum ); reg [9:0] xi[0:127]; reg [63:0] sum; reg [31:0] sum128_to_16[0:15]; reg [31:0] sum16_to_4[0:3]; reg [3:0] delay_cnt; //Adding trees delay integer reg_index; always @(posedge clk or negedge sys_rst_n) if(!sys_rst_n) begin for(reg_index =0;reg_index <128;reg_index =reg_index +1) begin xi[reg_index] <='d0; end delay_cnt <=3'd0; end else begin if(delay_cnt ==3'd2) begin delay_cnt <=3'd0; //Ultra_long shift_reg xi[0] <=input_signal; for(reg_index =0;reg_index <127;reg_index =reg_index +1) begin xi[reg_index +1] <=xi[reg_index]; end end else delay_cnt <=delay_cnt +1'b1; end always @(posedge clk or negedge sys_rst_n) //Adding trees construction if(!sys_rst_n) begin for(reg_index =0;reg_index <16;reg_index =reg_index +1) begin sum128_to_16[reg_index] <='d0; end end else begin for(reg_index =0;reg_index <16;reg_index =reg_index +1) begin sum128_to_16[reg_index] <=xi[reg_index*8]+xi[reg_index*8+1]+xi[reg_index*8+2]+xi[reg_index*8+3] +xi[reg_index*8+4]+xi[reg_index*8+5]+xi[reg_index*8+6]+xi[reg_index*8+7]; end end always @(posedge clk or negedge sys_rst_n) //Final adding trees if(!sys_rst_n) begin for(reg_index =0;reg_index <4;reg_index =reg_index +1) begin sum16_to_4[reg_index] <='d0; end end else begin for(reg_index =0;reg_index <4;reg_index =reg_index +1) begin sum16_to_4[reg_index] <=sum128_to_16[4*reg_index] +sum128_to_16[4*reg_index+1] +sum128_to_16[4*reg_index+2] +sum128_to_16[4*reg_index+3]; end end always @(posedge clk or negedge sys_rst_n) if(!sys_rst_n) sum <='d0; else sum <=sum16_to_4[0]+sum16_to_4[1]+sum16_to_4[2]+sum16_to_4[3]; assign sigmaS_out =sum[10:1]; //Make the code feasible is the key --Adjustable endmodule//---------------------------------------------------------------------------------------------------------- // FILE: Lp_filter.v // AUTHOR: Biggest_apple // // ABSTRACT: // KEYWORDS: fpga, basic module,signal process // // MODIFICATION HISTORY: // $Log$ // Biggest_apple 2023.8.6 create // Final_Tb //----------------------------------------------------------------------------------------------------------- module lp_filter( input clk, input sys_rst_n, //This is a test project not for any "real use" input signed [9:0] input_Refsignal, input signed [9:0] input_Sigsignal, output adjust ); //Pll error_up module --By IP core wire signed [19:0] o_p_multi; multix10b multix10b_dut( .clk (clk), .a (input_Refsignal), .b (input_Sigsignal), .p (o_p_multi) ); localparam THOLD_AD =32'h00ff_1111; //Unsigned... //Adding these vector --32_stages signed reg [19:0] xi [31:0] ; signed reg [27:0] sum32_to_4 [3:0] ; signed reg [31:0] sum4_to_1 ; unsigned wire sum4_to_1_abs; reg [3:0] delay_cnt; //Adding trees delay integer reg_index; always @(posedge clk or negedge sys_rst_n) if(!sys_rst_n) begin for(reg_index =0;reg_index <32;reg_index =reg_index +1) begin xi[reg_index] <='d0; end delay_cnt <=4'd0; //Adjustable end else begin if(delay_cnt ==4'd1) begin delay_cnt <=4'd0; //Ultra_long shift_reg xi[0] <=o_p_multi; for(reg_index =0;reg_index <31;reg_index =reg_index +1) begin xi[reg_index +1] <=xi[reg_index]; end end else delay_cnt <=delay_cnt +1'b1; end //Low_pass filters always @(posedge clk or negedge sys_rst_n) if(!sys_rst_n) begin sum32_to_4[0] <='d0; sum32_to_4[1] <='d0; sum32_to_4[2] <='d0; sum32_to_4[3] <='d0; end else begin sum32_to_4[0] <=xi[0] +xi[1] +xi[2] +xi[3] +xi[4] +xi[5] +xi[6] +xi[7]; sum32_to_4[1] <=xi[8] +xi[9] +xi[10] +xi[11] +xi[12] +xi[13] +xi[14] +xi[15]; sum32_to_4[2] <=xi[16] +xi[17] +xi[18] +xi[19] +xi[20] +xi[21] +xi[22] +xi[23]; sum32_to_4[3] <=xi[24] +xi[25] +xi[26] +xi[27] +xi[28] +xi[29] +xi[30] +xi[31]; end always @(posedge clk or negedge sys_rst_n) if(!sys_rst_n) begin sum4_to_1 <='d0; end else begin sum4_to_1 <=sum32_to_4[0] +sum32_to_4[1] +sum32_to_4[2] +sum32_to_4[3]; end assign sum4_to_1_abs =(sum4_to_1[31] ==1'b0) ?sum4_to_1 :~sum4_to_1+1'b1; assign adjust =(sum4_to_1_abs>=THOLD_AD); endmodule//---------------------------------------------------------------------------------------------------------- // FILE: phase_adjust.v // AUTHOR: Biggest_apple // // ABSTRACT: // Warning: // DESCRIBE: This is testbench file that will generater tri_wave in 8-bits formula // KEYWORDS: fpga, basic module, DSP.... // MODIFICATION HISTORY: // $Log$ // Biggest_apple 2023.7.12 create //----------------------------------------------------------------------------------------------------------- module phase_adjust( input clk, input sys_rst_n, input [9:0] input_signal, output [9:0] output_signal, input butt_phase_down, input butt_phase_up, input butt_phase_clr //Clear the register contents ); wire butt_phase_down_o; wire butt_phase_up_o; reg butt_phase_up_1,butt_phase_up_2; reg butt_phase_down_1,butt_phase_down_2; reg [8:0] OUTPHASE; parameter PHASEFACTER =16'd365; integer index; reg [9:0] signal_shiftR[PHASEFACTER-1:0]; always @(posedge clk or negedge sys_rst_n) if(!sys_rst_n) begin for(index =0;index <PHASEFACTER;index =index +1) begin signal_shiftR[index] <='d0; end end else begin signal_shiftR[0] <=input_signal; for(index =0;index <PHASEFACTER -1;index =index +1) begin signal_shiftR[index +1] <=signal_shiftR[index]; end end assign output_signal =signal_shiftR[OUTPHASE-1]; always @(posedge clk or negedge sys_rst_n) if(!sys_rst_n) begin OUTPHASE <='d256; end else begin if(~butt_phase_down_o &&butt_phase_up_o && !butt_phase_clr) OUTPHASE <=OUTPHASE +'d2; else if(butt_phase_down_o &&~butt_phase_up_o && !butt_phase_clr) OUTPHASE <=OUTPHASE -'d2; else if (butt_phase_clr) OUTPHASE <='d256; else begin OUTPHASE <=OUTPHASE; end end always @(posedge clk or negedge sys_rst_n) if(!sys_rst_n)begin butt_phase_down_1 <=1'b0; butt_phase_down_2 <=1'b0; butt_phase_up_1 <=1'b0; butt_phase_up_2 <=1'b0; end else begin butt_phase_down_1 <=butt_phase_down; butt_phase_down_2 <=butt_phase_down_1; butt_phase_up_1 <=butt_phase_up; butt_phase_up_2 <=butt_phase_up_1; end assign butt_phase_down_o =(butt_phase_down_2 &&~butt_phase_down_1); assign butt_phase_up_o =(butt_phase_up_2 &&~butt_phase_up_1); endmodule//---------------------------------------------------------------------------------------------------------- // FILE: signal_mux.v // AUTHOR: Biggest_apple // // ABSTRACT: // Warning: // DESCRIBE: This is testbench file that will generater tri_wave in 8-bits formula // KEYWORDS: fpga, basic module, DSP.... // MODIFICATION HISTORY: // $Log$ // Biggest_apple 2023.7.12 create //----------------------------------------------------------------------------------------------------------- module signal_mux( input en, input [9:0] input_signalA, input [9:0] input_signalB, output [9:0] output_signal ); assign output_signal =(en ==1'b0) ?input_signalA:input_signalB; endmodule//---------------------------------------------------------------------------------------------------------- // FILE: sin_to_tri.v // AUTHOR: Biggest_apple // // ABSTRACT: // Warning: // DESCRIBE: This is testbench file that will generater tri_wave in 8-bits formula // KEYWORDS: fpga, basic module, DSP.... // MODIFICATION HISTORY: // $Log$ // Biggest_apple 2023.7.12 create //----------------------------------------------------------------------------------------------------------- module sin_to_tri( input clk, input sys_rst_n, input [9:0] input_signal, output reg [9:0] o_signal, output [9:0] mean_sum ); reg [9:0] input_signalA; reg [9:0] input_signalN; reg [9:0] input_signalF; reg [9:0] max_e; reg [9:0] min_e; reg [9:0] st_dy; reg [9:0] end_dy; always @(posedge clk or negedge sys_rst_n) if(!sys_rst_n) begin input_signalA <='d0; input_signalN <='d0; input_signalF <='d0; end else begin input_signalF <=input_signal; input_signalN <=input_signalF; input_signalA <=input_signalN; end always @(posedge clk or negedge sys_rst_n) if(!sys_rst_n) begin max_e <='d0; min_e <='d0; end else if(st_dy ==10'd512) begin if(end_dy !=10'd1022) end_dy <=end_dy +1'b1; else end_dy <=end_dy; if((input_signalN >input_signalF || input_signalN >input_signalA )&&max_e <= input_signalN &&end_dy !=10'd1022) max_e <=input_signalN; else if((input_signalN<input_signalF || input_signalN<input_signalA )&&min_e >= input_signalN &&end_dy !=10'd1022) min_e <=input_signalN; else begin max_e <=max_e; min_e <=min_e; end end else begin max_e <=max_e; min_e <=min_e; end always @(posedge clk or negedge sys_rst_n) if(!sys_rst_n) st_dy <='d0; else if(st_dy !=10'd512) st_dy <=st_dy +1'b1; else st_dy <=st_dy; assign mean_sum =(max_e +min_e) >1; always @(posedge clk or negedge sys_rst_n) if(!sys_rst_n) o_signal <='d0; else if(input_signal >=mean_sum) o_signal <=o_signal +1'b1; else if(input_signal <mean_sum) o_signal <=o_signal -1'b1; else o_signal <=o_signal; endmodule`timescale 1ns/1ps //---------------------------------------------------------------------------------------------------------- // FILE: top.v // AUTHOR: Biggest_apple // // ABSTRACT: // KEYWORDS: fpga, basic module,signal process // // MODIFICATION HISTORY: // $Log$ // Biggest_apple 2023.7.25 create // 2023.8.5 update:Standard naming... //----------------------------------------------------------------------------------------------------------- module top( input sys_clk, //This signal is created by DDS module input sys_rst_n, input [9:0] i_adc, output oe, output adc_clk, input [9:0] i_adc_dds, output oe_dds, output adc_dds_clk, output dac_clk, output [9:0] o_dac, output dacP_clk, output [9:0] o_dacP, //input en_tri, //Enable the tri_wave output input phaseUpA, input phaseDownA //input phaseClrA ); wire en_tri; wire phaseClrA; assign en_tri =1'b0; assign phaseClrA =1'b0; wire [9:0] adc_data; wire [9:0] adc_dds_data; wire [9:0] dac_data; wire [9:0] la_filter_data; wire [9:0] sin_to_tri_data; wire [9:0] dacP_data; wire [9:0] dacPMM_data; (*KEEP="TRUE"*) wire [63:0] sum; (*KEEP="TRUE"*) wire [9:0] mean_sum; adc adc_dutO( .clk (sys_clk), .i_data (i_adc), .o_data (adc_data), .adc_clk (adc_clk), .oe (oe) ); adc adc_dutDds( .clk (sys_clk), .i_data (i_adc_dds), .o_data (adc_dds_data), .adc_clk (adc_dds_clk), .oe (oe_dds) ); dac dac_dut( .clk (sys_clk), .i_data (dac_data), .o_data (o_dac), .dac_clk (dac_clk) ); la_filter la_filter_dut( .clk (sys_clk), .sys_rst_n (sys_rst_n), //This is a test project not for any "real use" .input_signal (adc_data), .sigmaS_out (la_filter_data), .sum (sum) ); dac dac_dutPhase( .clk (sys_clk), .i_data (dacPMM_data), .o_data (o_dacP), .dac_clk (dacP_clk) ); phase_adjust phase_adjust_dut( .clk (sys_clk), .sys_rst_n (sys_rst_n), .input_signal (adc_data), .output_signal (dacP_data), .butt_phase_down(phaseUpA), .butt_phase_up (phaseDownA), .butt_phase_clr (phaseClrA) ); sin_to_tri sin_to_tri_dut( .clk (sys_clk), .sys_rst_n (sys_rst_n), .input_signal (la_filter_data), .o_signal (sin_to_tri_data), .mean_sum (mean_sum) ); //The stream signal selected... signal_mux signal_mux_dut( .en (en_tri), .input_signalA (la_filter_data), .input_signalB (sin_to_tri_data), .output_signal (dac_data) ); b_rescue b_rescue_dut( .clk (sys_clk), .sys_rst_n (sys_rst_n), .rv_signal (la_filter_data), .input_signalA (dacP_data), .dds_input (adc_dds_data), .dds_shift (dacPMM_data) ); endmodule