1. DDS的快速上手

DDS(直接数字频率合成器)作为电子设计竞赛中信号生成类题目最常用的核心工具,凭借其频率切换速度快、精度高、相位可控等优势,在波形生成、频率调制等场景中占据不可替代的地位。借助 Digital-IDE(DIDE)的自建 IP 库及配套视频教程,开发者可在 VS Code 环境中高效完成 DDS IP 核的调用与参数配置,显著提升开发效率。

[在 vscode 下用 DIDE 优雅的使用 vivado || All your need for FPGA&IC: Digital-IDE v0.4.3_哔哩哔哩_bilibili]

[DIDE 自建 IP 库之 DDS 讲解_哔哩哔哩_bilibili]

在导入 DDS 库及相关模块文件后,核心工作是配置 DDS 的两个关键参数 ——频率控制字(PINC) 和相位控制字(PHASE_OFFSET,简称 POOF),两者的计算公式及物理意义如下:

PINC = (生成频率*2^PWIDTH)/主频

输出频率= (FREQ_WORD *时钟频率) / 2 的相位累加器位宽次方

通过上述参数的准确配置,DDS 可稳定输出高精度、高灵活性的波形信号,满足电子设计竞赛中对信号源的多样化需求。

代码示例

在本次代码中,设置输出数据位宽为12,相位累加器位宽为32

module DDS #(
        // Output signal width.
        parameter OUTPUT_WIDTH = 12,
        // Phase width.
        parameter PHASE_WIDTH  = 32
    ) (
        // Clock input
        input                       clock,
        // Asynchronous reset with active high
        input                       reset, 

        // Frequency control word.
        // (out clk frequency * 2^PHASE_WIDTH)/clock frequency
        input  [PHASE_WIDTH-1 : 0]  fre_word, 
    
        // Phase control word.
        // (out phase * 2^PHASE_WIDTH) / (clock frequency * 360°)
        input  [PHASE_WIDTH-1 : 0]  pha_word, 

        // Out Sine wave.
        output [OUTPUT_WIDTH-1 : 0] wave_sin
    ); 

    wire [PHASE_WIDTH-1:0] Q;

    accuml #(
        .WIDTH 		( PHASE_WIDTH 		))
    u_accuml(
        //ports
        .clock   		( clock   		),
        .reset   		( reset   		),
        .clr     		( 1'b0     		),
        .add_sub 		( 1'b0  		),
        .D       		( fre_word      ),
        .Q       		( Q       		)
    );

    reg [PHASE_WIDTH-1:0] phase;
    always @(posedge clock or posedge reset) begin
        if (reset) begin
            phase <= 0;
        end
        else begin
            phase <= Q + pha_word;
        end
    end

    reg [9:0] addr;
    always @(posedge clock or posedge reset) begin
        if (reset) begin
            addr <= 0;
        end
        else begin
            addr <= phase[PHASE_WIDTH-1:PHASE_WIDTH-10];
        end
    end

    wire [15:0]	douta;
    Sin u_Sin(
        //ports
        .clka  		( clock  		),
        .rsta  		( reset  		),
        .addra 		( addr  		),
        .douta 		( douta 		)
    );

    assign wave_sin = douta >>> (16-OUTPUT_WIDTH);

endmodule

同时,为测试双路输入信号的相加,设计了加法器模块,将输入信号位宽设置为12

module adder #(
        // Input/Output signal width.
        parameter DATA_WIDTH = 12
    ) (
        // Clock input
        input                        clock,
        // Asynchronous reset with active high
        input                        reset,
    
        // First sine wave input
        input  [DATA_WIDTH-1 : 0]    sin_wave1,
    
        // Second sine wave input  
        input  [DATA_WIDTH-1 : 0]    sin_wave2,
    
        // Added sine wave output (with unity gain)
        output reg [DATA_WIDTH : 0]  wave_out
    );

    // Internal signal for addition result
    wire [DATA_WIDTH : 0] sum_result;
  
    // Perform addition (unity gain = 1)
    assign sum_result = {sin_wave1[DATA_WIDTH-1], sin_wave1} + {sin_wave2[DATA_WIDTH-1], sin_wave2};
  
    // Register the output
    always @(posedge clock or posedge reset) begin
        if (reset) begin
            wave_out <= 0;
        end
        else begin
            wave_out <= sum_result;
        end
    end

endmodule

在完成两个模块设计后,编写仿真文件,对其进行实例化仿真

image.png

2. FFT的初步探索

在 Vivado 中使用 FFT IP 核时,初学者的主要难点在于准确理解各端口的功能逻辑与配置参数的关联性。对于初步探索阶段,核心任务是完成基础配置与调用验证。

IP 核的引脚配置

首先需根据信号特性配置关键参数,包括输入数据位宽(如 16 位,通常实部和虚部拼接)、输出数据位宽(考虑 FFT 运算增益,建议比输入宽 4-5 位以避免溢出)、相位寄存器位宽(即 FFT 点数的对数表示,如 10 对应 1024 点,决定频率分辨率)以及时钟频率(需与系统时钟一致,如 50MHz,且确保信号稳定无毛刺)。

接着是基础端口的调用与连接,控制端口方面,时钟(aclk)和复位(resetn,通常低电平有效)需与系统时钟和复位信号正确连接以保证 IP 核初始化正常;输入数据端口包括输入数据总线(s_axis_data_tdata,高 16 位实部、低 16 位虚部等格式)、数据有效信号(s_axis_data_tvalid,高电平表示数据有效)、IP 核就绪信号(s_axis_data_tready,高电平表示可接收数据);输出数据端口则有输出数据总线(m_axis_data_tdata,格式与输入对应,注意量化处理)、输出有效信号(m_axis_data_tvalid,高电平表示结果有效)、帧结束信号(m_axis_data_tlast,标记一帧数据运算完成)。

在顶层仿真文件中,需生成符合位宽要求的输入数据(如正弦波采样数据,实部为信号值、虚部为 0),按时序规则驱动输入数据有效信号并检测 IP 核就绪信号以确保数据正确送入,同时捕获输出有效信号,提取输出数据并与理论 FFT 结果(可通过 MATLAB 预计算参考值)对比,以此验证配置有效性,为后续深入理解窗函数选择、流水线模式、数据格式化等高级特性奠定基础。

代码及仿真结果

对FFT ip核的端口配置如下

xfft_0 u_fft (
    .aclk(clock),
    .s_axis_config_tdata(8'h01), // FFT mode
    .s_axis_config_tvalid(1'b1),
    .s_axis_config_tready(),
    .s_axis_data_tdata({16'b0, wave_combined}), // Real input (zero-padded)
    .s_axis_data_tvalid(1'b1),
    .s_axis_data_tready(),
    .s_axis_data_tlast(1'b0),
    .m_axis_data_tdata({fft_imag_out, fft_real_out}), // {imag, real}
    .m_axis_data_tvalid(fft_done),
    .m_axis_data_tready(1'b1),
    .m_axis_data_tlast(),
    .event_frame_started(),
    .event_tlast_unexpected(),
    .event_tlast_missing(),
    .event_status_channel_halt(),
    .event_data_in_channel_halt(),
    .event_data_out_channel_halt()
);

将上文提到的Adder输出信号wave_combined接入FFT的输入端口,并根据数据位宽补充上虚部(默认为0),即可实现FFT操作

image.png