Высокочастотный цифровой фильтр в последовательной форме на ПЛИС

Содержание

Задача

Необходимо реализовать цифровой фильтр со следующими свойствами:

  1. Низкая чувствительность при высоких порядках фильтра
  2. Сохранить быстродействие
  3. Максимальное отклонения n% от максимальной амплитуды
  4. Максимальная высота гармоники m% от от амплитуды
  5. Максимальная точность при округлении

Решение

Под все выше сказанные параметры подходит эллиптический фильтр в последовательной формы.

Цифровые фильтры, реализованные в последовательной(каскадной) форме уменьшают нежелательные эффекты:

  1. Разделение полюсов и нулей: В последовательной форме сложный фильтр разбивается на каскад нескольких более простых фильтров малого порядка. При таком подходе амплитуды коэффициентов становятся меньше, что снижает влияние ошибок округления. При реализации фильтра в последовательной форме, коэффициенты каждого блока могут быть более простыми и лучше масштабированы. Это уменьшает требование к точности вычислений и, следовательно, уменьшает ошибки округления.
  2. Численная стабильность: Более простые блоки малого порядка, из которых строится каскад, имеют лучшее соотношение чисел при представлении в фиксированной точке. Это улучшает численную стабильность вычислений и снижает вероятность накопления ошибок округления. В последовательной форме ошибки округления влияют на выход одного блока, прежде чем перейти к следующему блоку.

Максимальные отклонения от максимальной амплитуды и высоту гармоник можно регулировать с помощью эллиптического фильтра.

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

Пояснения:

  1. N = 9: Порядок фильтра.
  2. n: Максимальное отклонение от амплитуды в процентах. Преобразуется в децибелы для использования в Rp.
  3. m: Максимальное подавление в полосе задерживания в процентах. Преобразуется в децибелы для использования в Rs.
  4. Fs: Частота дискретизации.
  5. Fc: Частота среза.
  6. Wp = Fc/(Fs/2): Нормализованная частота среза. Частота среза нормализуется относительно частоты Найквиста (которая равна половине частоты дискретизации).

Этот код преобразует процентные значения отклонений и подавлений в децибелы, нормализует частоту среза и вычисляет коэффициенты эллиптического фильтра с использованием функции ellip.

Код для ПЛИС

У меня получился 3-х составной фильтр в последовательной форме. Ниже приведу пояснения для реализации на высоких частотах.

Параметры Модуля верхнего уровня:

  1. DW — битовая ширина линии данных
  2. CW — битовая ширина коэффициентов
  3. CL — общее кол-во коэффициентов
  4. OW — битовая ширина выходной линии
  5. LREGS — дополнительные биты против потери
  6. C{i}P — количество коэффициентов в i составной части
  7. C{i}PK — кол-во b коэффициентов в i составной части
  8. C{i}PK{j} — коэффициент в i составной части j порядковым номером
Необязательно задавать коэффициенты через параметры! Можно объявить массив для i составной части и через начальный процедурный блок присвоить им значения:
initial begin $readmemh("config1patch.hex", coef1p); $readmemh("config2patch.hex", coef2p); $readmemh("config3patch.hex", coef3p); end

Обход по составным частям переделать через блок generate и for.

Сигналы Модуля верхнего уровня:

  1. o_result — результирующий сигнал
  2. i_arst — сброс всех регистров
  3. i_clk — тактовый сигнал
  4. i_data_wr — сигнал записи в регистры
  5. i_data — информационный сигнал

Параметры модуля firacc:

  1. DW — битовая ширина линии данных
  2. CW — битовая ширина коэффициентов
  3. SUBREGS — дополнительные биты против потери
  4. KICH — Наличие коэффициентов b составной части
  5. BICH — Наличие коэффициентов a составной части
  6. FIRST — Первое объявление составной части

Сигналы модуля firacc:

  1. o_acc — рассчитанная сумма
  2. o_datak — передача линии z^(-n + 1) в коэф b
  3. o_datab — передача линии в z^(-n + 1) в коэф a
  4. i_datak — предыдущая линия z^(-n — 1) в коэф b
  5. i_datab — предыдущая линия z^(-n — 1) в коэф a
  6. i_data_wr — сигнал записи
  7. i_data_rst — сигнал общего сброса
  8. i_clk — тактовый сигнал
  9. i_koefk — коэф b
  10. i_koefb — коэф a
  11. i_patch_acc — прошлая сумма
Транспонированная форма цифрового фильтра
Рисунок 1. RTL Каноническая форма составной часть фильтра в последовательной форме

Код написан на языке 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
Результат компиляции последовательного фильтра
Рисунок 1. Результат компиляции фильтра.