Jump to content

    
Sign in to follow this  
ZED

Реализация БПФ на ПЛИС

Recommended Posts

Поправил.

но при реализации все сделали наоборот. Кроме этого при смене этапа у нас должен происходить одновременно и сдвиг и вращение.

Да, действительно перепутал.

 

Зачем Вы ввели ADDR_ROTATE_buf? Почему не написали просто ADDR_ROTATE(0) <= SHIFT_RIGHT(ADDR_ROTATE_(3), 2); ?

Чтобы осуществить сдвиг по кругу. А иначе теряется значение ADDR_ROTATE(3).

 

Жду с нетерпением Ваших комментариев! :biggrin:

SBRT.rar

Share this post


Link to post
Share on other sites
Чтобы осуществить сдвиг по кругу. А иначе теряется значение ADDR_ROTATE(3).

Если бы это была программа на Cи, то потерялось бы, но VHDL это не Си - поскольку ADDR_ROTATE это триггер, то никуда оно не потеряется ни в железе, ни при симуляции. Так что ADDR_ROTATE_buf можете убрать - он совсем не нужен.

 

COUNT_BLOCK нельзя делать регистром (в вашем варианте реализации). Получается, что COUNT_BLOCK отстает на один такт от значения COUNT_BUT, из-за этого условие if(COUNT_BLOCK = MODULE) and (count_etap /= 0) then будет "срабатывать" на такт позже во время этапа и вообще не будет "срабатывать" при смене этапов. Посмотрите на временную диаграмму - при смене этапа (например 2 --> 3 (значение etap 1 --> 2) ) у вас не происходит инкремента BANK_COUNTER, а во время этапа инкремент всегда на 1 такт позже.

 

Еще лично мне не нравится, что в коде, с точки зрения действий над ADDR_ROTATE, у вас 2 независимых оператора if.

 

if (COUNT_BLOCK = MODULE) and (count_etap /= 0) then
            ...
  ADDR_ROTATE(1) <= ...
end if

if count_but = 511 then
  ...
  ADDR_ROTATE(1) <= ...
end if

 

Это сейчас у вас условие if (COUNT_BLOCK = MODULE) and (count_etap /= 0) then выполняется не тогда когда надо, но когда Вы исправите ошибку, то получится, что "(COUNT_BLOCK = MODULE) and (count_etap /= 0)" и "count_but = 511" будут истинными одновременно. И что тогда произойдет с ADDR_ROTATE? Лично я предпочитаю не усложнять задачу синтезатору и не упражняться в умозаключениях как же синтезатор должен синтезировать этот код согласно стандарту и будет ли он правильно работать. Проще и надежнее действия с ADDR_ROTATE вынести в отдельный if ... elseif ... end if. При этом "count_but = 511" наиболее приоритетное условие.

 

-----------------------------------

 

Теперь посмотрим на процесс записи данных в память после вычислений. Здесь все гораздо проще, чем при чтении. Адрес для всех банков памяти на всех этапах формируется одинаково – это счетчик от 0 до 511 без каких-либо особенностей. Банки вращаются точно также, как и при чтении, но в 4 раза быстрее, чем при чтении на том же этапе. Так что тут над реализацией думать совсем не надо.

 

Теперь про коэффициенты. Т.к. у нас три умножителя, работающие одновременно, то и банков ROM памяти потребуется 3 штуки. Размер каждого 512 коэффициентов. В первом банке коэффициентов будут располагаться коэффициенты 0 – 511 с шагом 1. Во втором банке 0 – 1022 с шагом 2. В третьем 0 – 1533 с шагом 3. Вообщем содержимое банков соответствует первому этапу на схеме БПФ. Правда у меня там в самом конце обнаружилась опечатка – начиная с точки 2043 написаны коэффициенты W1511, W1514, W1517, W1520, W1523, а должно было быть W1521, W1524, W1527, W1530, W1533. При таком заполнении банков коэффициентами адреса для всех банков на всех этапах будут совпадать. Само же формирование адреса реализуется на счетчике с переменным инкрементом и сбросом в 0 вначале каждого этапа. Инкремент равен 1 на первом этапе, 4 на втором, и т.д. до 5-го этапа, на 6-ом этапе он равен 0 – т.е. на все умножители на протяжении всего этапа подается коэффициент W0, расположенный по адресу 0 во всех банках коэффициентов. Инкремент, как Вы уже догадались, реализуется на сдвиговом регистре. Разрядность регистра 8 бит.

Share this post


Link to post
Share on other sites

Поправил реализацию генератора адресов на чтение, сделал реализацию генератора дресов для ROM.

 

Правда не совсем понял про формирование адресов на запись:

 

Теперь посмотрим на процесс записи данных в память после вычислений. Здесь все гораздо проще, чем при чтении. Адрес для всех банков памяти на всех этапах формируется одинаково – это счетчик от 0 до 511 без каких-либо особенностей. Банки вращаются точно также, как и при чтении, но в 4 раза быстрее, чем при чтении на том же этапе. Так что тут над реализацией

думать совсем не надо.

 

Мне казалось, что адреса записи на этапе n - это адреса чтения на этапе n+1. А вы говорите счетчик на 512, причем на всех этапах формируется одинаково.

SBRT.rar

gen_addr_ROM.rar

Share this post


Link to post
Share on other sites
Мне казалось, что адреса записи на этапе n - это адреса чтения на этапе n+1.

 

А по-другому и быть не может :). Если точка №Х была записана на этапе n в банк B по адресу A, то на следующем этапе n+1 эта точка, когда она потребуется, должна быть прочитана из банка B по адресу A. Задача схемы генерации адресов и номеров банков обеспечить извлечение из памяти в нужный момент нужных данных. Не забывайте - на каждом этапе у нас меняется порядок выборки точек. Посмотрите, к примеру на 2-ой этап. Самая первая бабочка должна получить на вход точки №0, 128, 256, 384. После вычислений точки с такими же номерами записываются в память по адресу 0 в соответствующий банк памяти (см. схему БПФ). Но на следующем этапе для первой же бабочки нам нужны уже точки №0, 32, 64, 96, а они на предыдущем этапе были записаны по адресам (№банка, адрес): (0,0), (1, 32), (2, 64), (3, 96). И т.д.

 

Вот и получается, что писать проще, чем читать. Можно сделать и наоборот.

Share this post


Link to post
Share on other sites

Вот, доделал генератор с учетом маски, только там нужно было написать не

ADDR(i) <= (BUTTERFLY_COUNTER and ADDR_ROTATE_MASK) or ADDR_ROTATE(i)(8 downto 0);

А вот так:

ADDR(i) <= (BUTTERFLY_COUNTER and ADDR_ROTATE_MASK(8 downto 0) or ADDR_ROTATE(i)(8 downto 0);

Файл прикрепляю.

gen_addr.rar

Share this post


Link to post
Share on other sites
Файл прикрепляю.

 

Хорошо, но к сожалению, без ошибки не обошлось. Поскольку теперь все записано в одном if ... elsif ... end if нарушился инкремент BANK_COUNTER. BANK_COUNTER, за исключением 1-го этапа, должен инкрементироваться в том числе и при переходе от этапа к этапу. Это-то у Вас сейчас и не происходит т.к. под условием "BUTTERFLY_COUNTER = 511" BANK_COUNTER не упоминается.

 

Поскольку BANK_COUNTER, все же, на первом этапе выбился из общего алгоритма, то проще уже при смене этапа его не инкрементировать, а сбросить в 0. В этом случае отпадает необходимость в дополнительной проверке "and (COUNT_ETAP /= 0)".

 

Еще заметил, что на рисунке при вращении ADDR_ROTATE в ходе этапа биты 10 и 9 остаются "неподвижными", а в Вашем коде они вращаются.

 

С учетом сказанного код получается такой

 

if BUTTERFLY_COUNTER = 511 then

  COUNT_ETAP <= COUNT_ETAP + 1;

  BANK_COUNTER <= (others => '0');

  -- Вращение и сдвиг
  ADDR_ROTATE_MASK <= "11" & ADDR_ROTATE_MASK(10 downto 2);

  ADDR_ROTATE(1) <= "00" & ADDR_ROTATE(1)(10 downto 9) & ADDR_ROTATE(0)(8 downto 3) & ADDR_ROTATE(0)(1);
  ADDR_ROTATE(2) <= "00" & ADDR_ROTATE(2)(10 downto 9) & ADDR_ROTATE(1)(8 downto 3) & ADDR_ROTATE(1)(1);
  ADDR_ROTATE(3) <= "00" & ADDR_ROTATE(3)(10 downto 9) & ADDR_ROTATE(2)(8 downto 3) & ADDR_ROTATE(2)(1);
  ADDR_ROTATE(0) <= "00" & ADDR_ROTATE(0)(10 downto 9) & ADDR_ROTATE(3)(8 downto 3) & ADDR_ROTATE(3)(1);

elsif (COUNT_BLOCK = MODULE) then

  BANK_COUNTER <= BANK_COUNTER + 1;

  -- Вращение
  ADDR_ROTATE(1)(8 downto 0) <= ADDR_ROTATE(0)(8 downto 0);
  ADDR_ROTATE(2)(8 downto 0) <= ADDR_ROTATE(1)(8 downto 0);
  ADDR_ROTATE(3)(8 downto 0) <= ADDR_ROTATE(2)(8 downto 0);
  ADDR_ROTATE(0)(8 downto 0) <= ADDR_ROTATE(3)(8 downto 0);

end if;

Share this post


Link to post
Share on other sites

Очень хорошо. Теперь нужно все эти разрозненные блоки собрать в едином модуле управления. Чтобы это правильно сделать нужно проанализировать блок-схему БПФ. Ваш последний вариант блок-схемы был очень близок к истине. В прицепе доработанная блок-схема (.pdf и архив - в архиве лежит блок-схема в Visio).

 

На стр. 3 приведена блок схема учитывающая особенность конкретного варианта БПФ – на 2048 точек, 6 этапов. В этом случае внешние модули имеют дело только с блоком памяти А т.к. только туда загружаются данные и только оттуда они будут выгружаться.

 

На стр 2 представлена блок схема на случай, если точек будет не только 2048, а этапов, соответственно, не только 6. Тогда загрузка данных по-прежнему будет осуществляться только в блок памяти А, а вот откуда выгружать будет зависеть от количества этапов. Поэтому внешние модули должны иметь доступ по управлению к обоим блокам памяти, а данные на выход должны идти с мультиплексора.

 

Проанализируем блок-схему и распределим какие блоки БПФ являются чисто комбинаторными, а какие содержат регистры и на сколько тактов задерживают данные. Регистры на выходе я обозначил на блок-схеме небольшими прямоугольниками.

 

С памятью должно быть все очевидно.

 

Если мультиплексоры делать с регистрами на выходе, то нужно не забыть поставить регистр задержки на 1 такт на входные данные блока памяти B (в случае схемы на стр. 3 и на управление). Либо учесть "перекос" в блоке управления, что не так уж и тривиально, поскольку "перекос" будет возникать поочередно то на управлении чтением, то на управлении записью. Проще, конечно, поставить регистр. Пока сделаем все мультиплексоры чисто комбинаторными.

 

Что касается вращателей, то им как раз на выход имеет смысл поставить регистры. Путь от выхода памяти через мультиплексор, входной вращатель и бабочку до регистра на выходе бабочки слишком длинный.

 

Путь от умножителя через выходной вращатель и мультиплексор до входа памяти короче, но не слишком. Здесь все зависит от частоты, разводки и заполнености ПЛИС. Пока давайте поставим.

 

Бабочка и умножитель у нас с регистрами на выходе.

 

Теперь, зная где какие задержки, нужно посмотреть на сколько тактов должны отстоять друг от друга различные сигналы управления, относящиеся к вычислению одной и той же бабочки. Временная диаграма приведена на стр. 4. Чтобы вычислить одну бабочку нужно подать на блок памяти источника данных адреса чтения AR и только на следующем такте мы получим данные D на выходе памяти/входе вращателя. Именно в этот момент на вращатель нужно подать управление, относящееся к этим данным. На следующем такте данные появятся на выходе вращателя/входе бабочки. Именно в этот момент нужно подать управление на бабочку (4-х точечную считать или две 2-х точечные). Т.к. памяти требуется 1 такт на чтение, то чтобы нужный коэффициент попал на вход умножителя вовремя надо подать адрес этого коэффициента за 1 такт до прихода данных – т.е. сейчас, когда данные на входе бабочки. И т.д.

 

Нужно учесть, что при переходе от этапа к этапу нам необходимо подождать пока последние считанные данные пройдут конвеер и запишуться в память.

 

По сигналу сброса и по окончании вычислений сигналы управления мультиплексорами должны быть в состоянии, обеспечивающем доступ к памяти внешним модулям.

 

Выходы блока управления целесообразно делать регистровыми (что и обозначено на блок-схеме). В последнем варианте кода ADDR_х комбинаторные сигналы. Надо бы их "зарегистрировать" :).

 

Кроме сигналов управления блоками самого БПФ блок управления должен еще как-то общаться с внешним "миром". Внешние модули должны знать когда можно загружать данные, когда забирать результаты и иметь возможность запускать вычисление после загрузки данных. Для этого блок управления должен иметь как минимум 3 простых сигнала

 

Start (in) – однотактовый импульс запускающий вычисление после загрузки входных отсчетов.

 

Stop (out) – однотактовый импульс, подается за 1 такт до окончания вычислений.

 

Ready (out) – опускается в ноль по сигналу старт и держится в 0 на протяжении вычислений. Поднимается в 1 когда вычисления закончены (по сигналу Stop).

 

-----------------------

 

FFT_Diagram.pdf

FFT_Diagram.rar

Share this post


Link to post
Share on other sites
Вот пока мои наработки, еще забыл добавить адресацию поворачивающих множителей...

 

Увы... Глянул я на первый же process и понял, что временную диаграмму смотреть еще рано. Правда дома мне их все равно и не посмотреть - система рухнула, все переустанавливаю. До HW тулов еще не добрался.

 

Сигнал Start должен был быть не сигналом сброса, а сигналом запускающим вычисления. Разумеется, по нему можно все выставить в начальное состояние, но сброс это его вторичная функция. У Вас получилось, что первичная и, к сожалению, единственная.

 

Сценарий предполагался примерно такой. Общий сброс приводит БПФ в состояние ожидания запуска. При этом управление мультиплексорами должно "отдать" память внешним (по отношению к БПФ) модулям. Внешний модуль загружает данные от АЦП в память БПФ, управляя ею напрямую. После этого, он подает однотактовый импульс на вход Start и БПФ начинает работать. После того, как модуль БПФ все посчитает он останавливается, отдает управление памятью внешним модулям и ждет. Внешний модуль вычитывает результаты БПФ, загружает новые данные и подает сигнал Start. И т.д.

 

Что получилось у Вас. Сигнал сброса приводит БПФ в начальное состояние, но как только он закончится модуль ничего ждать не будет - он сразу начнет считать. Правда доступа к памяти он не получит т.к. управление памятью будет отдано внешним модулям. Пока БПФ молотит вхолостую можно загрузить данные. После этого сигнал Start все сбросит и БПФ начнет считать по-настоящему. Когда вычисления закончатся, не дожидаясь, пока досчитаются несколько последних бабочек и их результаты запишутся в память "отрубаем" память от БПФ, который, разогнавшись, уже не может остановиться и продолжает считать и плевать ему, что память у него опять отобрали. :) В общем БПФ без тормозов :)

 

Если дождаться когда досчитаются последние бабочки и их результаты попадут в память, то так, конечно сделать можно. Но уж слишком это... скажем... непрофессионально.

 

Я не увидел в коде ничего, что могло бы хоть как-то разнести во времени сигналы управления друг от друга и подать их на разные блоки БПФ строго в нужное время.

 

Еще я не увидел никаких пауз между этапами для того, чтобы подождать пока завершат вычисления последние бабочки и их результаты запишутся в память.

 

В общем, просто слить код из нескольких файлов в один совершенно недостаточно и результат слишком далек от правильно работающего блока управления.

Share this post


Link to post
Share on other sites
Вот, немного переделал, правда еще не сделал паузы между этапами

 

Любопытно, а с чего это Вы вдруг в основном модуле с " if (rising_edge(CLK)) then " перешли на " wait until (rising_edge(CLK)); ", а в ФИФО по-прежнему " if (rising_edge(CLK)) then " ? :)

 

Вообще-то, ФИФО для выравнивания задержки слишком дорогое и неоправданное удовольствие. Чтобы задержать 9-ти разрядный ADDR_WR потребуется 9*6 = 54 триггера !

Нет. ФИФО не нужен.

 

 

Еще я не понимаю код

-- Адреса коэффициентов:
ADDR_ROM_1_FIFO: fifo
generic map (N => 3, b_size => 11)
port map (CLK => CLK, D => std_logic_vector(ADDR_ROM), Q => ADDR_ROM_1);

ADDR_ROM_2_FIFO: fifo
generic map (N => 3, b_size => 11)
port map (CLK => CLK, D => std_logic_vector(SHIFT_LEFT(ADDR_ROM, 1)), Q => ADDR_ROM_2);

ADDR_ROM_3_FIFO: fifo
generic map (N => 3, b_size => 11)
port map (CLK => CLK, D => std_logic_vector(SHIFT_LEFT(ADDR_ROM, 1) + ADDR_ROM), Q => ADDR_ROM_3);

 

1) Зачем Вам 3 адреса ROM?

2) Зачем SHIFT_LEFT?

Share this post


Link to post
Share on other sites
Любопытно, а с чего это Вы вдруг в основном модуле с " if (rising_edge(CLK)) then " перешли на " wait until (rising_edge(CLK)); ", а в ФИФО по-прежнему " if (rising_edge(CLK)) then " ? smile.gif

Просто прочитал, что лучше использовать wait until, чем список чувствительности при сопоставлении моделирования и синтеза.

 

Вообще-то, ФИФО для выравнивания задержки слишком дорогое и неоправданное удовольствие. Чтобы задержать 9-ти разрядный ADDR_WR потребуется 9*6 = 54 триггера !

Нет. ФИФО не нужен.

А как еще выравнивать задержки? Вводить управляющие сигналы разрешения для каждого сигнала типа STATE?

 

1) Зачем Вам 3 адреса ROM?

2) Зачем SHIFT_LEFT?

1)Понял просто счетчик.

2)SHIFT_LEFT, чтобы модуль увеличивать в 4 раза каждый этап.

 

Тут вопрос появился. Паузы между этапами делать, вводя какой-нибудь сигнал PAUSE, который будет 2 такта держать схему в состоянии ожидания?

elsif (STATE = '0') and (PAUSE = '0') then
ADDR_ROM <= ADDR_ROM + MODULE_ROM;
if BUTTERFLY_COUNTER = 511 then
...............................................

Share this post


Link to post
Share on other sites
Просто прочитал, что лучше использовать wait until, чем список чувствительности при сопоставлении моделирования и синтеза.

 

Выложите, пожалуйста, статью сюда или ссылку на нее. Любопытно глянуть на обоснования такого подхода.

 

1)Понял просто счетчик.

2)SHIFT_LEFT, чтобы модуль увеличивать в 4 раза каждый этап.

 

По-моему Вы немного запутались. Прочитайте еще раз мое сообщение про формирование адреса для коэффициентов. Нужен всего 1 адрес на все блоки ROM. Этот адрес у Вас формируется выражением ADDR_ROM <= ADDR_ROM + MODULE_ROM. MODULE_ROM у Вас тоже успешно формируется выражением MODULE_ROM <= SHIFT_LEFT(MODULE_ROM, 2). Код

 

ADDR_ROM_2_FIFO: fifo
generic map (N => 3, b_size => 11)
port map (CLK => CLK, D => std_logic_vector(SHIFT_LEFT(ADDR_ROM, 1)), Q => ADDR_ROM_2);

ADDR_ROM_3_FIFO: fifo
generic map (N => 3, b_size => 11)
port map (CLK => CLK, D => std_logic_vector(SHIFT_LEFT(ADDR_ROM, 1) + ADDR_ROM), Q => ADDR_ROM_3);

 

к этому отношения не имеет, к тому же сдвиг на 1 эквивалентен умножению на 2, а не 4. Т.е. этими конструкциями Вы формируете с задержкой ADDR_ROM_2 = ADDR_ROM*2 и ADDR_ROM_3 = ADDR_ROM*3. Происходит это на каждом такте. Таким образом ответ №2 к этому коду, про который я спрашивал, отношения не имеет.

 

Тут вопрос появился. Паузы между этапами делать, вводя какой-нибудь сигнал PAUSE, который будет 2 такта держать схему в состоянии ожидания?

 

Да, но только не 2 такта, а 5 или 6 (точно не помню)

 

А как еще выравнивать задержки? Вводить управляющие сигналы разрешения для каждого сигнала типа STATE?

 

Давайте сделаем паузу и рассмотрим некоторые типовые приемы и после этого вернемся к реализации блока управления.

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Sign in to follow this