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

11

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

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

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

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

Спасибо.

HJF
источник
2
Хотя я целиком и полностью использую все, что у вас есть для изучения, я хотел бы отметить, что использование аудиофильтров в FPGA не очень эффективный или экономически эффективный способ сделать это. Итак, если вы делаете реальный проект, то я бы рекомендовал вместо этого использовать недорогой DSP. Исключения: когда вы одновременно делаете невероятное количество аудиоканалов или делаете FIR с абсурдным количеством нажатий.

Ответы:

8

Похоже, вам нужно сначала выяснить аспекты DSP, а затем сделать реализацию в FPGA.

  • Сортировка DSP в C, Matlab, Excel или где-либо еще
  • Подумайте, как вы перенесете то, чему научились, в землю ПЛИС
  • Обнаружьте, что вы сделали некоторые предположения о реализации, которая не работает хорошо (например, использование плавающей запятой)
  • Вернитесь и обновите свой 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 бит, все в порядке. Если вам нужно больше ширины, чем вы, вы можете использовать векторы UNSIGNED или SIGNED так широко, как вам нравится. Или используйте типы fixed_point из 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

Как видите, вы можете использовать эту общую идею для добавления в более сложные формулы, например, с коэффициентами. Более сложные формулы, такие как БИХ-фильтры, могут потребовать использования переменных для получения правильной логики алгоритма. Наконец, простой способ обойти фильтры, которые имеют действительные числа в качестве коэффициентов, - это найти масштабный коэффициент, чтобы все числа оказались как можно ближе к целым числам. Ваш конечный результат должен быть уменьшен на тот же коэффициент, чтобы получить правильный результат.

Я надеюсь, что это может быть полезно для вас и поможет вам запустить мяч.

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

dhsieh2
источник
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. Вам нужно зарегистрироваться, чтобы скачать файлы.

edit
Я понимаю комментарий Кортука о мертвых ссылках, и, действительно, если ссылка на OpenCores умирает, ответ станет бесполезным. Я совершенно уверен, что этого не произойдет; Моя ссылка является общей, и она умрет, только если весь домен OpenCores исчезнет.
Я попытался найти некоторые примеры, которые я мог бы использовать для этого ответа, но они слишком длинные, чтобы быть представленными здесь. Поэтому я буду придерживаться моего совета, чтобы зарегистрироваться на сайте самостоятельно (мне пришлось переехать в Нью-Йорк, потому что мой родной город не был принят) и взглянуть на код, представленный там.

stevenvh
источник
Как и во всем, ссылки ломаются. Мы уже обсуждали, что ссылка сама по себе не дает ответа. Можете ли вы привести некоторые из того, что там есть, и сделать мясной ответ, который содержит эту ссылку в качестве ссылки, чтобы узнать больше?
Кортук
@Kortuk - Я хотел сделать это вчера. Вчера я зарегистрировался в opencores, чтобы получить некоторые детали, но им нужно несколько дней, чтобы подумать, получу ли я их
stevenvh
я рад это слышать, мне было честно интересно, что-то мешало тебе. С нетерпением ждем больше информации об этом.
Кортук
1

Я попытался реализовать сценарии для автоматической реализации фильтров БИХ, где вы можете определить, должен ли проект быть максимально быстрым (чтобы каждое умножение выполнялось с выделенным множителем) или как можно меньшим (чтобы каждый множитель использовался повторно).

Источники были опубликованы на alt.sources как «Поведенческая, но синтезируемая реализация БИХ-фильтров в VHDL» (вы также можете найти ее в архиве Google: https://groups.google.com/group/alt.sources/msg/c8cf038b9b8ceeec ? dmode = source )

Сообщения в alt.sources представлены в формате "shar", поэтому вам нужно сохранить сообщение в виде текста и разархивировать его (с помощью утилиты "unshar"), чтобы получить источники.

WZab
источник
0

Как насчет этого? https://github.com/MauererM/VIIRF

Он реализует БИХ-фильтр (SOS, секции второго порядка) на основе БИХ-фильтра, который заботится о реализации с фиксированной запятой. Он также имеет скрипты Python для разработки и проверки фильтра. Он не использует специфичные для поставщика FPGA-конструкции, и вы можете выбрать компромисс между быстрым и малым использованием.

KeyNuts
источник