Приклад коду для фільтрів FIR / IIR у VHDL?


11

Я намагаюся розпочати роботу з DSP на своїй дошці Spartan-3. Я зробив плату AC97 з чіпом зі старої материнської плати, і поки я змусив це зробити АЦП, помножити зразки на число <1 (зменшити гучність), а потім ЦАП.

Тепер я хотів би зробити деякі основні DSP речі, такі як фільтр низьких частот, високопропускний і т. Д. Але я дуже заплутаний щодо числового представлення (цілі числа? Фіксована точка? Q0.15? Переповнення або насичення?).

Я просто хочу, щоб якийсь приклад код фактичного простого фільтра почав мене починати. Ні високої ефективності, ні швидкого, ні подібного. Просто теоретичний фільтр, реалізований у VHDL.

Я шукав, але я просто знаходжу теоретичні формули - я розумію, що я не розумію, як обробити підписані 16-бітні, 48 кГц аудіо-зразки, які я отримую від АЦП. Я використовував ці бібліотеки: http://www.vhdl.org/fphdl/ . Якщо я помножую свої зразки на 0,5, 0,25 тощо, я чую різницю. Але більший фільтр видає мені просто шум.

Спасибі.


2
Хоча я все використовую все, що у вас є під рукою для навчальних речей, я хотів би зазначити, що робити аудіофільтри на FPGA - це не дуже ефективний або економічно ефективний спосіб зробити це. Отже, якщо ви робите реальний проект, тоді я рекомендую замість цього використовувати недорогий DSP. Винятки: коли ви одночасно робите нечестиву кількість аудіоканалів або робите FIR з абсурдною кількістю дотиків.

Відповіді:


8

Здається, вам потрібно спочатку розібратися з аспектами DSP, а потім здійснити реалізацію в FPGA.

  • Сортуйте DSP в C, Matlab, Excel або будь-де іншому
  • Спробуйте і подумайте, як ви перенесете те, що ви дізналися з цього, у FPGA-land
  • Виявіть, що ви зробили певне припущення щодо того, що реалізація не працює добре (наприклад, використання плаваючої точки)
  • Поверніться та оновіть свої офлайн-файли DSP, щоб врахувати це.
  • Повторити n разів :)

Що стосується типів даних, ви можете використовувати цілі числа добре.

ось декілька зразкових кодів, щоб ви рухалися. Зауважте, що в ньому пропущено багато реальних проблем (наприклад, скидання, управління переповненням) - але, сподіваємось, це повчально:

library ieee;
use ieee.std_logic_1164.all;
entity simple_fir is
    generic (taps : integer_vector); 
    port (
        clk      : in  std_logic;
        sample   : in  integer;
        filtered : out integer := 0);
end entity simple_fir;
----------------------------------------------------------------------------------------------------------------------------------
architecture a1 of simple_fir is
begin  -- architecture a1
    process (clk) is
        variable delay_line : integer_vector(0 to taps'length-1) := (others => 0);
        variable sum : integer;
    begin  -- process
        if rising_edge(clk) then  -- rising clock edge
            delay_line := sample & delay_line(0 to taps'length-2);
            sum := 0;
            for i in 0 to taps'length-1 loop
                sum := sum + delay_line(i)*taps(taps'high-i);
            end loop;
            filtered <= sum;
        end if;
    end process;
end architecture a1;
----------------------------------------------------------------------------------------------------------------------------------
-- testbench
----------------------------------------------------------------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
entity tb_simple_fir is
end entity tb_simple_fir;
architecture test of tb_simple_fir is
    -- component generics
    constant lp_taps : integer_vector := ( 1, 1, 1, 1, 1);
    constant hp_taps : integer_vector := (-1, 0, 1);

    constant samples : integer_vector := (0,0,0,0,1,1,1,1,1);

    signal sample   : integer;
    signal filtered : integer;
    signal Clk : std_logic := '1';
    signal finished : std_logic;
begin  -- architecture test
    DUT: entity work.simple_fir
        generic map (taps => lp_taps)  -- try other taps in here
        port map (
            clk      => clk,
            sample   => sample,
            filtered => filtered);

    -- waveform generation
    WaveGen_Proc: process
    begin
        finished <= '0';
        for i in samples'range loop
            sample <= samples(i);
            wait until rising_edge(clk);
        end loop;
        -- allow pipeline to empty - input will stay constant
        for i in 0 to 5 loop
            wait until rising_edge(clk);
        end loop;
        finished <= '1';
        report (time'image(now) & " Finished");
        wait;
    end process WaveGen_Proc;

    -- clock generation
    Clk <= not Clk after 10 ns when finished /= '1' else '0';
end architecture test;

Дякую за вашу відповідь. Це більш-менш те, що я зробив, але у мене виникають проблеми з представленням номерів. Мій АЦП дає мені значення в -32k до + 32k (підписаний 16-бітний). У мене також є проблема постійної фільтра - як я це представляю? А результат множення між вибіркою і постійною? Ось що мене найбільше бентежить.
hjf

@hjf - це все лише цілі числа. Поки все залишається в межах 32 біт, ви все в порядку. ЯКЩО вам потрібна більша ширина, ніж ви можете використовувати вектори НЕЗНАЧЕННІ або ЗНАЧЕНІ настільки широкі, як вам подобається. Або використовуйте типи фіксованих точок з VHDL2008 (див. Тут: vhdl.org/fphdl )
Мартін Томпсон,

5

Найпростіший низькопрохідний фільтр FIR, який ви можете спробувати, це y (n) = x (n) + x (n-1). Ви можете реалізувати це досить легко у VHDL. Нижче наведена дуже проста блок-схема обладнання, яке ви хочете реалізувати.

Блок-схема для простого фільтра низьких частот

Відповідно до формули, вам потрібні поточні та попередні зразки АЦП, щоб отримати відповідний вихід. Що потрібно зробити, це зафіксувати вхідні зразки АЦП на падаючому краю годинника та виконати відповідні обчислення на зростаючому краю, щоб отримати відповідний вихід. Оскільки ви додаєте два 16-бітні значення разом, можливо, ви отримаєте 17-бітну відповідь. Ви повинні зберігати вхідні дані в 17-бітові регістри та використовувати 17-бітове додавання. Однак ваш вихід буде нижчим 16 біт відповіді. Код може виглядати приблизно так, але я не можу гарантувати, що він буде працювати повністю, оскільки я його не перевірив, не кажучи вже про синтезований.

IEEE.numeric_std.all;
...
    signal x_prev, x_curr, y_n: signed(16 downto 0);
    signal filter_out: std_logic_vector(15 downto 0);
...
process (clk) is
begin
    if falling_edge(clk) then
        --Latch Data
        x_prev <= x_curr;
        x_curr <= signed('0' & ADC_output); --since ADC is 16 bits
    end if;
end process;

process (clk) is
begin
    if rising_edge(clk) then
        --Calculate y(n)
        y_n <= x_curr + x_prev;
    end if;
end process;

filter_out <= std_logic_vector(y_n(15 downto 0));  --only use the lower 16 bits of answer

Як бачите, ви можете використовувати цю загальну ідею для додавання у складніші формули, наприклад, з коефіцієнтами. Більш складні формули, як-от фільтри IIR, можуть вимагати використання змінних, щоб коректувати логіку алгоритму. Нарешті, найпростіший спосіб обійти фільтри, у яких реальні числа є коефіцієнтами, - знайти коефіцієнт масштабу, щоб усі цифри були максимально близькими до цілих чисел. Ваш кінцевий результат доведеться зменшити тим самим фактором, щоб отримати правильний результат.

Я сподіваюся, що це може вам стати в нагоді і допомогти вам прокатати м'яч.

* Це було відредаговано таким чином, що фіксація даних та фіксація вихідних даних проходять в окремих процесах. Також використовуються підписані типи замість std_logic_vector. Я припускаю, що ваш вхід АЦП буде сигналом std_logic_vector.


2
Процеси, які запускають обидва краї (як ви описали), малоймовірно синтезувати
Мартін Томпсон,

@Martin Я припускаю, що ви знаєте набагато більше про FPGA, ніж я, але я зав'язав вхідні дані по падаючому краю і засунув вихід на зростаючому краю для завдання класу, тому я подумав, що це спрацювало б. Чи можете ви пояснити, чому такі процеси не працюють?
dhsieh2

3
Це буде добре працювати в тренажері. Синтезатори будуть задихатися від цього, хоча (на мій досвід), так як відкидні пристрої в пристрої можуть працювати лише на одному краю.
Мартін Томпсон

@ dhsieh2 Дякую, це відповідь, яку я шукав. Ще одне питання, як би я це зробив, якби я використовував підписані номери (мій АЦП дає мені значення від -32k до + 32k).
hjf

4
@Martin Я відключаю речі з обох країв годинника весь час у Xilinx FPGA, немає проблем. Ви просто не можете відключити однаковий FF обох країв. Якщо ви подивитеся на вихід аналізатора синхронізації, то насправді стає зрозумілим, що ви робите протилежні краї, і відповідно коригує бюджет часу.

5

Ще один простий фрагмент коду (лише кишки). Примітка: Я не писав VHDL безпосередньо, я використовував MyHDL для створення VHDL.

-- VHDL code snip
architecture MyHDL of sflt is

type t_array_taps is array(0 to 6-1) of signed (15 downto 0);
signal taps: t_array_taps;

begin

SFLT_RTL_FILTER: process (clk) is
    variable sum: integer;
begin
    if rising_edge(clk) then
        sum := to_integer(x * 5580);
        sum := to_integer(sum + (taps(0) * 5750));
        sum := to_integer(sum + (taps(1) * 6936));
        sum := to_integer(sum + (taps(2) * 6936));
        sum := to_integer(sum + (taps(3) * 5750));
        sum := to_integer(sum + (taps(4) * 5580));
        taps(0) <= x;
        for ii in 1 to 5-1 loop
            taps(ii) <= taps((ii - 1));
        end loop;
        y <= to_signed(sum, 16);
    end if;
end process SFLT_RTL_FILTER;

end architecture MyHDL;

синтезований контур

Це пряма реалізація. Для цього знадобляться множники. Синтез цієї схеми, орієнтований на Altera Cyclone III, не використовував явних множників, але вимагав 350 логічних елементів.

Це невеликий фільтр FIR і матиме наступний відгук (не такий великий), але повинен бути корисним як приклад.

відповідь фільтра

Крім того, у мене є кілька прикладів, тут і тут , які можуть бути корисними.

Крім того, у вашому запитанні з'являється запитання: "що є релевантним представленням фіксованої точки?" Часто при реалізації функцій DSP використовується представлення з фіксованою точкою, оскільки воно спрощує аналіз фільтрів. Як згадувалося, фіксованою точкою є просто ціла артиметика. Фактична реалізація просто працює з цілими числами, але наше попереднє подання є дробовим.
Зазвичай проблеми виникають при перетворенні цілого числа (фіксованої точки) на / з плаваючою точкою проектування.

Я не знаю, наскільки добре підтримуються типи VHDL з фіксованою точкою та плаваючою комою. Вони будуть добре працювати в моделюванні, але я не знаю, чи вони синтезуватимуться з більшістю інструментів синтезу. Я створив для цього окреме питання .


3

OpenCores має ряд прикладів DSP, IIR та FIR, включаючи BiQuad. Вам доведеться зареєструватися, щоб завантажити файли.

редагувати
Я розумію коментар Кортука щодо мертвих посилань, і дійсно, якщо посилання на OpenCores вмирає, відповідь стане марною. Я впевнений, що цього не станеться; моє посилання є загальним, і воно помре лише тоді, коли повний домен OpenCores зникне.
Я намагався шукати кілька прикладів, які міг би використати для цієї відповіді, але вони занадто довгі, щоб бути представленими тут. Тож я буду дотримуватися своєї ради зареєструватися на сайті самостійно (мені довелося переїхати до Нью-Йорка, оскільки моє рідне місто не було прийнято) і перегляну код, представлений там.


Як і у всіх речах, посилання розриваються. Ми вже обговорювали, що посилання самостійно не дає відповіді. Чи можете ви донести щось із того, що там, і скласти м'ясну відповідь, що має це посилання як орієнтир, щоб дізнатися більше?
Кортук

@Kortuk - я хотів це зробити вчора. Я вчора зареєструвався на відкритих майданчиках, щоб отримати детальну інформацію, але їм потрібно кілька днів, щоб подумати, чи матимуть мене
stevenvh

Рада це почути, мені чесно було цікаво, чи щось на твоєму шляху сталося. З нетерпінням чекайте докладніше про це.
Кортук

1

Я спробував реалізувати скрипти для автоматичної реалізації фільтрів IIR, де ви можете визначити, чи має бути конструкція якомога швидшою (тому кожне множення виконується із виділеним множником) або якомога менше (тому кожен множник повторно використовується).

Джерела були опубліковані в alt.sources як "Поведінкова, але синтезована реалізація фільтрів IIR у VHDL" (ви також можете знайти їх в архіві google: https://groups.google.com/group/alt.sources/msg/c8cf038b9b8ceeec ? dmode = джерело )

Публікації до alt.sources є у форматі "шар", тому вам потрібно зберегти повідомлення як текст та скасувати його (з утилітою "unhar"), щоб отримати джерела.


0

Як щодо цього? https://github.com/MauererM/VIIRF

Він реалізує двоквадровий (SOS, секції другого порядку) на базі IIR фільтр, який піклується про реалізацію фіксованої точки. Він також містить сценарії Python для проектування та перевірки фільтра. У ньому не використовуються конкретні для виробника конструкції FPGA, і ви можете вибрати компроміс між швидкісним та низьким рівнем використання.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.