FPGA教程系列-滤波器的仿真分析(CIC+FIR)

FPGA教程系列-滤波器的仿真分析(CIC+FIR)

本实验的目的是生成一个带有噪声的信号,通过ROM存储,最终经过滤波器检查滤波器的效果,并验证。

Matlab生成波形

Matlab生成了一个100Hz的带有噪声(3KHz)的信号:

% --- 1. 参数定义 ---
fs = 10000;          % 采样率10KHz
t = 0:1/fs:0.01;     % 时间向量 (0.01秒,共100个点)
f_signal = 100;       % 有用信号频率
f_noise = 3000;       % 噪声信号频率

% --- 2. 生成信号 ---
clean_signal = sin(2*pi*f_signal*t);
noise = 0.5 * sin(2*pi*f_noise*t);
noisy_signal = clean_signal + noise;

% --- 3. 可视化原始信号 ---
figure;
subplot(2,1,1);
plot(t, noisy_signal);
title('时域波形 (带噪声)');
xlabel('时间');
ylabel('幅度');

subplot(2,1,2);
% 进行FFT分析频谱
N = length(noisy_signal);
f = (-N/2:N/2-1)*(fs/N);
y = fft(noisy_signal);
plot(f, abs(fftshift(y))/N);
title('频域频谱 (带噪声)');
xlabel('频率')
ylabel('幅度');
grid on;

% --- 4. 量化并生成 .coe 文件 ---
N_bits = 12; % 量化位宽
max_val = 2^(N_bits-1) - 1; % 12位有符号数的最大值

% 归一化并量化
quantized_signal = round(noisy_signal / max(abs(noisy_signal)) * max_val);

% 转换为无符号整数以写入 .coe 文件 (因为 .coe 文件通常处理正数)
% 对于负数,需要转换为其二进制补码对应的正数
unsigned_signal = quantized_signal;
unsigned_signal(unsigned_signal < 0) = unsigned_signal(unsigned_signal < 0) + 2^N_bits;

% 写入 .coe 文件
fid = fopen('signal_data.coe', 'w');
fprintf(fid, 'memory_initialization_radix=2;\n');
fprintf(fid, 'memory_initialization_vector=\n');
for i = 1:length(unsigned_signal)
    fprintf(fid, '%s', dec2bin(unsigned_signal(i), N_bits));
    if i == length(unsigned_signal)
        fprintf(fid, ';\n');
    else
        fprintf(fid, ',\n');
    end
end
fclose(fid);

disp('signal_data.coe 文件已生成!');

image

可以看到时域与频率的信号如图,并且生成了一个signal_data.coe文件,用于存储到ROM内。

ROM配置

配置ROM的IP核:matlab生成的是12位的信号,这里对应的输入。

image

CIC核配置

image

image

PS:

生成IP核以后,输入是16位的,应该是跟AXI协议有关。

Top文件

主要例化2个IP核,注意下输入输出的位数。

module cic_filter_top (
    input               aclk,
    input               aresetn,
    output wire [11:0]   rom_data_out,
    output wire          cic_valid_out,
    output reg [23:0]   cic_data_out
);

    // 地址计数器,用于读取ROM
    reg [6:0] rom_addr;
    wire rom_valid_out; // ROM的tvalid信号

    // ROM实例化
    signal_rom signal_rom_u (
        .clka  (aclk),
        .rsta(rsta),
        .addra (rom_addr),
        .douta (rom_data_out),
        .rsta_busy()
    );

    // CIC实例化
    wire [11:0] s_axis_data_tdata;
    wire        s_axis_data_tvalid;
    wire [23:0] m_axis_data_tdata;
    wire        m_axis_data_tvalid;

    assign s_axis_data_tdata = rom_data_out;
    assign s_axis_data_tvalid = 1'b1; // ROM数据一直有效

cic_compiler_0 your_instance_name (
  .aclk(aclk),                              // input wire aclk
  .aresetn(aresetn),                        // input wire aresetn
  .s_axis_data_tdata({4'd0,s_axis_data_tdata}),    // input wire [15 : 0] s_axis_data_tdata
  .s_axis_data_tvalid(s_axis_data_tvalid),  // input wire s_axis_data_tvalid
  .s_axis_data_tready(),  // output wire s_axis_data_tready
  .m_axis_data_tdata(m_axis_data_tdata),    // output wire [23 : 0] m_axis_data_tdata
  .m_axis_data_tvalid(cic_valid_out)  // output wire m_axis_data_tvalid
);


    // 地址生成逻辑
    always @(posedge aclk or negedge aresetn) begin
        if (!aresetn) begin
            rom_addr <= 0;
        end else begin
            // 简单循环读取ROM
            if (rom_addr == 7'd99) // 100个点
                rom_addr <= 0;
            else
                rom_addr <= rom_addr + 1;
        end
    end

    // 输出赋值
    always @(posedge aclk) begin
            cic_data_out <= m_axis_data_tdata;
    end

endmodule

Testbench

位宽与寄存器要对应,模拟输入时钟100MHz。

Ps:TB可以直接写入文件,把滤波的数据直接进行存储。

`timescale 1ns / 1ps

module cic_filter_tb();

    reg aclk;
    reg aresetn;
    wire [11:0] rom_data_out;
    wire cic_valid_out;
    wire [23:0] cic_data_out;
    integer file_handle;
    // 实例化顶层模块
    cic_filter_top uut (
        .aclk            (aclk),
        .aresetn         (aresetn),
        .rom_data_out    (rom_data_out),
        .cic_valid_out   (cic_valid_out),
        .cic_data_out    (cic_data_out)
    );

    // 时钟生成 (100MHz)
    initial begin
        aclk = 0;
        forever #5 aclk = ~aclk;
    end

    // 复位和仿真控制
    
    initial begin
        // 初始化
        aresetn = 0;
        #100;
        aresetn = 1;
        file_handle = $fopen("cic_output.txt", "w");
        if (file_handle == 0) begin
            $display("Error: Could not open file.");
            $finish;
        end
        
        // 等待足够长的时间让仿真完成
        wait (cic_valid_out); 
        // (100个输入点 / 4倍抽取) * (CIC延迟) ≈ 50个输出点
        repeat(300) @(posedge aclk); 
        
        $fclose(file_handle);
        $display("Simulation finished, data saved to cic_output.txt");
        $finish;
    end
    
    always @(posedge aclk) begin
        if (cic_valid_out) begin
            $fdisplay(file_handle, "%d",$signed(cic_data_out));
        end
    end

endmodule

仿真分析:

image

  • CIC滤波器有一定的延时
  • 抽取滤波,抽取系数为4,可以看到每4个周期产生一个cic_valid_out信号,表示数据有效
  • ROM读取的频率是100MHz,输入的信号原本是100Hz,但是由于系统时钟的问题,实际输出的波形应该是1MHz。

频谱分析

Matlab分析一下,读取cic_output.txt文件

%% 1. 读回 Vivado 仿真结果
output_data = load('cic_output.txt');   % 有符号整数文本
M = length(output_data);                % 数据点数

%% 2. 基本参数(按真实硬件时钟)
R = 4;                                  % CIC 抽取因子
fs_real = 100e6/R ;                    % 物理采样率 = 100 MHz / R
                                        % 例:R=4 → 25 MHz

%% 3. 归一化(可选)
normalized_output = double(output_data) / max(abs(output_data));

%% 4. 时间轴 & 频率轴
t_out = (0:M-1) / fs_real;              % 单位:秒
f_out = (-M/2:M/2-1) * fs_real / M;     % 单位:Hz,FFT 双边谱

%% 5. 画图
figure;
subplot(2,1,1);
plot(t_out*1e6, normalized_output);      % 横轴用 us 显示
title('时域波形(CIC 滤波后)');
xlabel('时间 (μs)');
ylabel('幅度');

subplot(2,1,2);
Y = fft(normalized_output);
plot(f_out/1e6, abs(fftshift(Y))/M, 'LineWidth', 1);
title('频域频谱(CIC 滤波后)');
xlabel('频率 (MHz)');
ylabel('幅度');
grid on;

image

FIR滤波器

有兴趣的也可以用FIR滤波器看一下

image

需要生成一个FIR的IP核,调用如下,可以在一个工程里,可以看到这个滤波器valid信号一直为1,没有抽取。

fir_compiler_0 fir_compiler_u (
  .aresetn(aresetn),                        // input wire aresetn
  .aclk(aclk),                              // input wire aclk
  .s_axis_data_tvalid(s_axis_data_tvalid),  // input wire s_axis_data_tvalid
  .s_axis_data_tready(),  // output wire s_axis_data_tready
  .s_axis_data_tdata({4'd0,s_axis_data_tdata}),    // input wire [15 : 0] s_axis_data_tdata
  .m_axis_data_tvalid(cic_valid_out),  // output wire m_axis_data_tvalid
  .m_axis_data_tdata(m_axis_data_tdata)    // output wire [23 : 0] m_axis_data_tdata
);
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值