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 文件已生成!');

可以看到时域与频率的信号如图,并且生成了一个signal_data.coe文件,用于存储到ROM内。
ROM配置
配置ROM的IP核:matlab生成的是12位的信号,这里对应的输入。

CIC核配置


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
仿真分析:

- 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;

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

需要生成一个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
);
1396

被折叠的 条评论
为什么被折叠?



