Задача
Необходимо реализовать цифровой фильтр со следующими свойствами:
- Низкая чувствительность при высоких порядках фильтра
- Сохранить быстродействие
- Максимальное отклонения n% от максимальной амплитуды
- Максимальная высота гармоники m% от от амплитуды
- Максимальная точность при округлении
Решение
Под все выше сказанные параметры подходит эллиптический фильтр в последовательной формы.
Цифровые фильтры, реализованные в последовательной(каскадной) форме уменьшают нежелательные эффекты:
- Разделение полюсов и нулей: В последовательной форме сложный фильтр разбивается на каскад нескольких более простых фильтров малого порядка. При таком подходе амплитуды коэффициентов становятся меньше, что снижает влияние ошибок округления. При реализации фильтра в последовательной форме, коэффициенты каждого блока могут быть более простыми и лучше масштабированы. Это уменьшает требование к точности вычислений и, следовательно, уменьшает ошибки округления.
- Численная стабильность: Более простые блоки малого порядка, из которых строится каскад, имеют лучшее соотношение чисел при представлении в фиксированной точке. Это улучшает численную стабильность вычислений и снижает вероятность накопления ошибок округления. В последовательной форме ошибки округления влияют на выход одного блока, прежде чем перейти к следующему блоку.
Максимальные отклонения от максимальной амплитуды и высоту гармоник можно регулировать с помощью эллиптического фильтра.
MATLAB
Чтобы создать MATLAB код для расчета коэффициентов 9-го порядка эллиптического фильтра с учетом максимального отклонения от амплитуды и подавления гармоник в процентах от амплитуды, необходимо преобразовать эти процентные значения в децибелы, которые используются в функции ellip.
% Порядок фильтра
N = 9;
% Максимальное отклонение от амплитуды в полосе пропускания в процентах
n = 1; % например, 1%
Rp = -20*log10(1 - n/100); % преобразование процента в децибелы
% Максимальное подавление в полосе задерживания в процентах
m = 0.1; % например, 0.1%
Rs = -20*log10(m/100); % преобразование процента в децибелы
% Частота среза
Fs = 10; % частота дискретизации
Fc = 2; % частота среза
% Нормализованная частота среза (относительно частоты Найквиста)
Wp = Fc/(Fs/2);
% Расчет коэффициентов
[b, a] = ellip(N, Rp, Rs, Wp);
% Отображение результатов
fprintf('Коэффициенты числителя (b):\n');
disp(b);
fprintf('Коэффициенты знаменателя (a):\n');
disp(a);
Пояснения:
- N = 9: Порядок фильтра.
- n: Максимальное отклонение от амплитуды в процентах. Преобразуется в децибелы для использования в Rp.
- m: Максимальное подавление в полосе задерживания в процентах. Преобразуется в децибелы для использования в Rs.
- Fs: Частота дискретизации.
- Fc: Частота среза.
- Wp = Fc/(Fs/2): Нормализованная частота среза. Частота среза нормализуется относительно частоты Найквиста (которая равна половине частоты дискретизации).
Этот код преобразует процентные значения отклонений и подавлений в децибелы, нормализует частоту среза и вычисляет коэффициенты эллиптического фильтра с использованием функции ellip.
Код для ПЛИС
У меня получился 3-х составной фильтр в последовательной форме. Ниже приведу пояснения для реализации на высоких частотах.
Параметры Модуля верхнего уровня:
- DW — битовая ширина линии данных
- CW — битовая ширина коэффициентов
- CL — общее кол-во коэффициентов
- OW — битовая ширина выходной линии
- LREGS — дополнительные биты против потери
- C{i}P — количество коэффициентов в i составной части
- C{i}PK — кол-во b коэффициентов в i составной части
- C{i}PK{j} — коэффициент в i составной части j порядковым номером
initial begin
$readmemh("config1patch.hex", coef1p);
$readmemh("config2patch.hex", coef2p);
$readmemh("config3patch.hex", coef3p);
end
Обход по составным частям переделать через блок generate и for.
Сигналы Модуля верхнего уровня:
- o_result — результирующий сигнал
- i_arst — сброс всех регистров
- i_clk — тактовый сигнал
- i_data_wr — сигнал записи в регистры
- i_data — информационный сигнал
Параметры модуля firacc:
- DW — битовая ширина линии данных
- CW — битовая ширина коэффициентов
- SUBREGS — дополнительные биты против потери
- KICH — Наличие коэффициентов b составной части
- BICH — Наличие коэффициентов a составной части
- FIRST — Первое объявление составной части
Сигналы модуля firacc:
- o_acc — рассчитанная сумма
- o_datak — передача линии z^(-n + 1) в коэф b
- o_datab — передача линии в z^(-n + 1) в коэф a
- i_datak — предыдущая линия z^(-n — 1) в коэф b
- i_datab — предыдущая линия z^(-n — 1) в коэф a
- i_data_wr — сигнал записи
- i_data_rst — сигнал общего сброса
- i_clk — тактовый сигнал
- i_koefk — коэф b
- i_koefb — коэф a
- i_patch_acc — прошлая сумма

Код написан на языке Verilog:
`default_nettype none
module ffir #(
parameter DW = 8, CW = 8, CL = 9
) ( o_result, i_arst, i_clk, i_data_wr, i_data);
localparam OW = DW + CW + $clog2(CL);
localparam LREGS = OW - (DW + CW);
localparam C1P = 6;
localparam C1PK = 3;
localparam C2P = 3;
localparam C2PK = 1;
localparam C1PK0 = 63;
localparam C1PK1 = 36;
localparam C1PK2 = 24;
localparam C1PK3 = 36;
localparam C1PK4 = 82;
localparam C1PK5 = 94;
localparam C2PK0 = 3;
localparam C2PK1 = 73;
localparam C2PK2 = 86;
output wire signed [(OW - 1): 0] o_result;
input wire i_arst, i_clk, i_data_wr;
input wire signed [(DW - 1) : 0] i_data;
wire signed [(OW): 0] o_acc_f1 [(C1P - C1PK + 1): 0];
wire signed [(DW): 0] o_data_fk1 [(C1P - C1PK + 1): 0];
wire signed [(DW): 0] o_data_fb1 [(C1P - C1PK + 1): 0];
wire signed [(OW): 0] o_acc_f2 [(C2P - C2PK + 1): 0];
wire signed [(OW): 0] o_data_fk2 [(C2P - C2PK + 1): 0];
wire signed [(OW): 0] o_data_fb2 [(C2P - C2PK + 1): 0];
assign o_acc_f1[0] = 0;
assign o_data_fk1[0] = i_data;
assign o_data_fb1[0] = 0;
assign o_acc_f2[0] = 0;
assign o_data_fk2[0] = o_acc_f1[(C1P - C1PK)];
assign o_data_fb2[0] = 0;
assign o_result = o_acc_f2[(C2P - C2PK)];
firacc #(
.DW(DW), .CW(CW), .SUBREGS(LREGS), .KICH(1), .BICH(1), .FIRST(1), .OW(OW)
) accpath1_1 (
.o_acc(o_acc_f1[1]), .o_datak(o_data_fk1[1]), .o_datab(o_data_fb1[1]),
.i_datak(o_data_fk1[0]), .i_datab(o_data_fb1[0]), .i_data_wr(i_data_wr), .i_data_rst(i_arst), .i_clk(i_clk),
.i_koefk(C1PK0), .i_koefb(C1PK3), .i_patch_acc(o_acc_f1[0])
);
firacc #(
.DW(DW), .CW(CW), .SUBREGS(LREGS), .KICH(1), .BICH(1), .FIRST(0), .OW(OW)
) accpath1_2 (
.o_acc(o_acc_f1[2]), .o_datak(o_data_fk1[2]), .o_datab(o_data_fb1[2]),
.i_datak(o_data_fk1[1]), .i_datab(o_data_fb1[1]), .i_data_wr(i_data_wr), .i_data_rst(i_arst), .i_clk(i_clk),
.i_koefk(C1PK1), .i_koefb(C1PK4), .i_patch_acc(o_acc_f1[1])
);
firacc #(
.DW(DW), .CW(CW), .SUBREGS(LREGS), .KICH(1), .BICH(1), .FIRST(0), .OW(OW)
) accpath1_3 (
.o_acc(o_acc_f1[3]), .o_datak(o_data_fk1[3]), .o_datab(o_data_fb1[3]),
.i_datak(o_data_fk1[2]), .i_datab(o_data_fb1[2]), .i_data_wr(i_data_wr), .i_data_rst(i_arst), .i_clk(i_clk),
.i_koefk(C1PK2), .i_koefb(C1PK5), .i_patch_acc(o_acc_f1[2])
);
firacc #(
.DW(OW), .CW(CW), .SUBREGS(0), .KICH(1), .BICH(1), .FIRST(1), .OW(OW)
) accpath2_1 (
.o_acc(o_acc_f2[1]), .o_datak(o_data_fk2[1]), .o_datab(o_data_fb2[1]),
.i_datak(o_data_fk2[0]), .i_datab(o_data_fb2[0]), .i_data_wr(i_data_wr), .i_data_rst(i_arst), .i_clk(i_clk),
.i_koefk(C2PK0), .i_koefb(C2PK1), .i_patch_acc(o_acc_f2[0])
);
firacc #(
.DW(OW), .CW(CW), .SUBREGS(0), .KICH(0), .BICH(1), .FIRST(0), .OW(OW)
) accpath2_2 (
.o_acc(o_acc_f2[2]), .o_datak(o_data_fk2[2]), .o_datab(o_data_fb2[2]),
.i_datak(o_data_fk2[1]), .i_datab(o_data_fb2[1]), .i_data_wr(i_data_wr), .i_data_rst(i_arst), .i_clk(i_clk),
.i_koefk(0), .i_koefb(C2PK2), .i_patch_acc(o_acc_f2[1])
);
endmodule
module firacc #(
parameter DW = 8, CW = 8, SUBREGS = 5, KICH = 1, BICH = 1, FIRST = 0, OW = MW + SUBREGS
) ( o_acc, o_datak, o_datab, i_datak, i_datab, i_clk, i_data_wr, i_data_rst, i_koefk, i_koefb, i_patch_acc);
localparam MW = DW + CW;
output reg signed [(OW - 1): 0] o_acc;
output reg signed [(DW - 1): 0] o_datak;
output reg signed [(DW - 1): 0] o_datab;
input wire signed [(CW - 1): 0] i_koefk;
input wire signed [(CW - 1): 0] i_koefb;
input wire signed [(DW - 1): 0] i_datak;
input wire signed [(DW - 1): 0] i_datab;
input wire i_data_wr, i_data_rst, i_clk;
input wire signed [(OW - 1): 0] i_patch_acc;
wire signed [(MW - 1): 0] m_data;
wire signed [(OW - 1): 0] accum;
reg signed [(OW - 1): 0] delay_trig1;
reg signed [(DW - 1): 0] delay_trig2;
initial begin
delay_trig1 <= 0;
delay_trig2 <= 0;
o_acc <= 0;
end
// data kich
generate
if (KICH == 1) begin
always @(posedge i_clk, posedge i_data_rst)
if (i_data_rst) begin
delay_trig1 <= 0;
o_datak <= 0;
end else begin
if (i_data_wr) begin
delay_trig1 <= i_datak;
o_datak <= delay_trig1;
end
end
end
endgenerate
//data bich
generate
if (BICH == 1) begin
always @(posedge i_clk, posedge i_data_rst)
if (i_data_rst) begin
delay_trig2 <= 0;
o_datab <= 0;
end else begin
if (i_data_wr) begin
if (FIRST)
delay_trig2 <= accum;
else
delay_trig2 <= i_datab;
o_datab <= delay_trig2;
end
end
end
endgenerate
always @(posedge i_clk, posedge i_data_rst)
if (i_data_rst)
o_acc <= 0;
else if (i_data_wr)
o_acc <= accum;
generate
if (FIRST == 1)
assign m_data = i_koefk * i_datak;
else
if (KICH == 1) begin
if (BICH == 1)
assign m_data = i_koefk * i_datak + i_koefb * i_datab;
else
assign m_data = i_koefk * i_datak;
end else
assign m_data = i_koefb * i_datab;
endgenerate
assign accum = i_patch_acc + { {(OW - MW){m_data[(MW-1)]}}, m_data };
endmodule
