Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Реализация БПФ на ПЛИС
Форум разработчиков электроники ELECTRONIX.ru > Программируемая логика ПЛИС (FPGA,CPLD, PLD) > Языки проектирования на ПЛИС (FPGA)
Страницы: 1, 2, 3, 4, 5, 6, 7
Sefo
Цитата(ZED @ Sep 6 2009, 14:36) *
MODULE(8 downto 0) <= (others => '1')
MODULE(8 downto 0) <= "00" & MODULE(8 downto 2)
MODULE(8 downto 0) <= "0000" & MODULE(8 downto 4)
и т.д.

В Этом случае разве не понадобятся when?


Нет, т.к. я имел ввиду обычный сдвигорый регистр, что-то вроде этого:

Код
process(CLK)
begin

  if rising_edge(CLK) then
    if INIT = '1' then
      MODULE(8 downto 0) <= (others => '1');
    elsif SHIFT_STROBE = '1' then
      MODULE(8 downto 0) <= "00" & MODULE(8 downto 2);
    end if;
  end if;

end process;


т.е. MODULE(8 downto 0) <= (others => '1') делается 1 раз перед началом вычисления очередного БПФ над очнредной порцией данных,

а MODULE(8 downto 0) <= "00" & MODULE(8 downto 2) - это описание операции сдвига, которая делается либо по стробу шириной в 1 такт, формируемому при переходе от одного этапа к другому, либо непосредственно по условию, которое является истиной только 1 такт при смене этапов.
Sefo
Продолжим разбираться с чтением. Посмотрим на адреса.

У нас 4 банка памяти и каждый получает свой собственный адрес. Адреса каждого банка (независимо от этапа) всегда принимает все значения от 0 до 511 причем на этапе каждое из этих значений адрес принимает только 1 раз. Разрядность адреса 9 бит. Разберем свойства адреса на примере 3-го этапа.

Свойство первое: во всех блоках бабочек все адреса блоков памяти в один и тот же момент времени (такт) имеют одинаковые значения в битах (4 downto 0). Например при вычислении 2-ой бабочки (посчитаем их от 0) адрес в банке 0 = 2 (000000010b), адрес в банке 1 = 34 (000100010b), адрес в банке 2 = 66 (001000010b) и адрес в банке 3 = 98 (001100010b)

Свойство второе: Старшая часть адреса (8 downto 7) в течении вычисления 4 блоков бабочки остается константой и только при переходе к следующей четверке блоков бабочек меняет свое значение, причем просто инкрементирует его на 1. При этом, эти биты одинаковы у всех адресов банков памяти.

Свойство третье: Из первого и второго свойства следует, что адреса банков памяти на протяжении всего этапа имеют одинаковые значения в битах (8 downto 7) и (4 downto 0) и единственное, что их отличает это биты (6 downto 5)

Свойство четвертое (очень полезное): Выше было сказано, что нам потребуется "счетчик на 512 тактов, переполнение которого означает переход к следующему этапу БПФ" – счетчик бабочек, попросту говоря. Если хорошо подумать, то Вы должны будете заметить, что если этот счетчик сделать считающим вверх, то биты (8 downto 7) и (4 downto 0) один в один совпадут с такими же битами этого счетчика.

Таким образом на третьем этапе нас волнует только одно - как формировать биты (6 downto 5). Но с этим вопросом разберемся чуть позже.

Сначала давайте посмотрим на оствшиеся этапы. Обозначим бит, совпадающий с таким же битом счетчика бабочек и одинаковый для адресов всех банков памяти 'N', а бит, который у адресов разных банков отличается '#'.

Этап 1: на с хеме хоть и не нарисовано, но думаю очевидно, что все адреса во всех банках это просто счетчик бабочек. Формат адресов банков получается NNNNNNNNN
Этап 2: биты (8 downto 7) у всех адресов свои, но биты (6 downto 0) совпадают со счетчиком бабочек. Формат адресов ##NNNNNNN.
Этап 3: Формат адресов NN##NNNNN.
Этап 4: Формат адресов NNNN##NNN.
Этап 5: Формат адресов NNNNNN##N.
Этап 6: Казалось бы этот этап своими адресами вообще ни на что не похож, но ... Как это ни странно, он очень даже вписывается в общий алгоритм. Формат адресов NNNNNNNN#.

Итак, чтобы нагляднее было выпишем еще раз форматы адресов на этапах БПФ

NNNNNNNNN
##NNNNNNN
NN##NNNNN
NNNN##NNN
NNNNNN##N
NNNNNNNN#

Как без особых трудозатрат сформировать адреса, а точнее сформировать биты # и "расставить" их по местам (напомню, что все остальные биты это просто наш счетчик бабочек) напишу в ближайшие дни.

Вопросы есть? smile.gif
ZED
Хорошо, немного не понял пока тогда назначения счетчика BANK_COUNTER, где он нам пригодиться. А так вроде все понятно. Жду с нетерпением формирования адресов и "расстановки битов по местам".
Sefo
У нас выходы банков памяти подсоеденены к бабочке не на прямую, а через специальный мультиплексор, который "вращает" банки памяти относительно входов бабочки (т.е. данные на каждый вход бабочки подаются из нужного в данный момент банка памяти). BANK_COUNTER(1 downto 0) это сигнал управления этим "вращателем".
Sefo
Чтобы разобраться какими должны быть биты 'X' возьме опять 3-ий этап. Как видно, адреса должны вращаться относительно банков, на подобие как данные из банков памяти вращаются относительно входов бабочки. Конечно, можно сделать еще и отдельный блок "вращателя" для адресов, но лучше его прямо интегрировать в схему формирования адресов. Надеюсь, очевидно, что за "вращение" как раз и отвечают биты 'X'. Так же очевидно, что смена положения этих бит в адресе соответствует поведению обычного сдвигового регистра. Чтобы получить эффект "вращения" можно декрементировать значение битов 'X' по завершению каждого блока бабочек, но тот факт, что их положение и даже количество постоянно меняется усложнит реализацию. В данном случае, самое простое решение – лобовое. Видно, что биты сдвигаются – значит будем сдвигать, сказано, что должны вращаться – значит будем вращать. Я бы назвал эту конструкцию сдвиговым регистром барабанного типа (СРБТ) smile.gif (см. рисуонк).

Нажмите для просмотра прикрепленного файла

Рисунок отражает общий принцип действия, но нам нужно уточнить некоторые детали применительно к нашему БПФ. Разрядность. Она равна 11 (5 этапов по 2 бита + один этап 1 бит). При этом использоваться будут только младшие 9 бит. Дело в том, что на первом этапе биты X вообще не используются и, к тому же, у нас адрес всего 9-ти битный. Можно, конечно, сделать этот СРБТ и 9-ти разрядным, но тогда его инициализацию придется выполнять 2 раза – перед первым этапом и перед вторым, что выбивается из общего алгоритма, да и не сильно выигрывает по объему логики (а то и совсем выигрыша не будет). Так что сделаем его 11-ти разрядным. Т.к. количество вращений у нас кратно 4, то к каждому следующему этапу наш СРБТ будет сам возвращаться в нужное для след. этапа положение. Поэтому достаточно его инициализировать один раз перед вычислением БПФ, а дальше он будет работать по одной и той же схеме. Далее, нам надо учесть, что при переходе от одного этапа к другому нужно вращать и сдвигать одновременно. Подробная схема переходов приведена на рисунке. Чтобы не мучаться с рисованием перемещения битов по кругу, пунктиром обозначен адрес для банка №0. На рисунках представлены 2 схемы перемещения битов – как должен работать СРБТ в течение этапа и как при смене этапов и начальное значение, которое регистр должен получить при инициализации.

Нажмите для просмотра прикрепленного файла

Для размещения битов 'Х' в адресе воспользуемся маской ADDR_ROTATE_MASK, которая будет реализована так же в виде 11-ти разрядного сдвигового регистра. Начальное значение = "00111111111". При смене этапов этот регистр должен всегда сдвигаться на 2 бита вправо с заменой 2-ух старших бит '1' (ADDR_ROTATE_MASK <= "11" & ADDR_ROTATE_MASK(10 downto 2)).

Таким образом финальный адрес формируется следующим образом

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

где BUTTERFLY_COUNTER это упоминавшийся ранее счетчик бабочек (он "поставляет" биты 'n'), а ADDR_ROTATE это только что описанный СРБТ (он "поставляет" биты 'X')

Попробуйте сначала написать код только для СРБТ. Код должен быть очень простой – буквально несколько строчек.
ZED
Я не очень понял, что за биты 'X', предполагаю, что это ранее обозначенные #. Вращения я так понял это переход от одного блока бабочек к другому, а сдвиг это от этапа к этапу. Далее не совсем понял первый рисунок. Ну и не понял почему вдруг
Цитата
ADDR_ROTATE это только что описанный СРБТ

Почему он вдруг 8 разрядный получился, когда его мы хотели сделать 11 разрядным:
Цитата
Разрядность. Она равна 11 (5 этапов по 2 бита + один этап 1 бит). При этом использоваться будут только младшие 9 бит. Дело в том, что на первом этапе биты X вообще не используются и, к тому же, у нас адрес всего 9-ти битный. Можно, конечно, сделать этот СРБТ и 9-ти разрядным, но тогда его инициализацию придется выполнять 2 раза – перед первым этапом и перед вторым, что выбивается из общего алгоритма, да и не сильно выигрывает по объему логики (а то и совсем выигрыша не будет). Так что сделаем его 11-ти разрядным.

И не совсем ясна картинка №2, но я думаю с ней разберусь, когда уточню все вышеописанное.
Sefo
Цитата(ZED @ Sep 13 2009, 21:07) *
Я не очень понял, что за биты 'X', предполагаю, что это ранее обозначенные #.

Да, так и есть. Сорри. blush.gif

Цитата(ZED @ Sep 13 2009, 21:07) *
Вращения я так понял это переход от одного блока бабочек к другому, а сдвиг это от этапа к этапу.

Да.

Цитата(ZED @ Sep 13 2009, 21:07) *
Далее не совсем понял первый рисунок.

Имелось ввиду, что 4 сдвиговых регистра передают свои значения друг другу циклически (ну и т.к. они сдвиговые, то еще и сдвигать вправо могут).

Цитата(ZED @ Sep 13 2009, 21:07) *
И не совсем ясна картинка №2

Это то же самое что и первый, но по-другому нарисованый и если на первом показаны общие направления "движения" битов, то на втором рисунке это показано детально - так как нужно реализовать.

Попробуйте, представьте 2-ух мерный сдвиговый регистр (ввиде матрицы, например), который вправо (по оси X) выполняет обычный сдвиг, а вниз (по оси Y) выполняет циклический.

Цитата(ZED @ Sep 13 2009, 21:07) *
Ну и не понял почему вдруг он 8 разрядный получился, когда его мы хотели сделать 11 разрядным

Если Вы имеете ввиду

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

то, во-первых (8 downto 0) это 9 разрядов, а во-вторых эта строка не означает, что сам по себе ADDR_ROTATE стал 9-ти разрядным. Просто для формирования адреса нам от него нужно всего 9 младших бит (ведь шина адреса у памяти 9-ти разрядная), но сам он является 11-ти разрядным чтобы его реализация была проще и, так сказать, алгоритмичнее.

Цитата
При этом использоваться будут только младшие 9 бит. Дело в том, что на первом этапе биты X вообще не используются и, к тому же, у нас адрес всего 9-ти битный.
Sefo
Цитата(ZED @ Sep 13 2009, 21:07) *


Как у Вас дела с реализацией СРБТ?
ZED
Вот мои наработки, я там вывел некоторые сигналы для наглядности...
Sefo
Давайте разберем Ваш код. На будущее попрошу Вас писать хоть какие-нибудь комментарии в случае, если написанный код не формирует ожидаемую временную диаграмму. А то, как вот в данном случае, непонятно – то ли Вы старались, но у Вас не получилось и Вы понимаете что не правильно на временной диаграмме, но знаете как добиться нужного результата, то ли Вы даже не попытались проанализировать результаты симуляции. В первом случае, хотелось бы понять, что именно вызвало трудности и написать дополнительные разъяснения, а во втором сказать, что не нужно так лениться smile.gif
По вашей диаграмме видно, что все ADDR_ROTATE_xxx подозрительно быстро сходятся к 0 – уже в начале 3-го этапа (etap = 2). Это происходит потому, что Вы правильно написали в сообщении
Цитата(ZED @ Sep 13 2009, 21:07) *
Вращения я так понял это переход от одного блока бабочек к другому, а сдвиг это от этапа к этапу.

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

Код
ADDR_ROTATE(1) <= ADDR_ROTATE(0);
ADDR_ROTATE(2) <= ADDR_ROTATE(1);
ADDR_ROTATE(3) <= ADDR_ROTATE(2);
ADDR_ROTATE(0) <= ADDR_ROTATE_buf;


Нужно дополнить еще и сдвигом

Код
ADDR_ROTATE(1) <= SHIFT_RIGHT(ADDR_ROTATE(0), 2);
ADDR_ROTATE(2) <= SHIFT_RIGHT(ADDR_ROTATE(1), 2);
ADDR_ROTATE(3) <= SHIFT_RIGHT(ADDR_ROTATE(2), 2);
ADDR_ROTATE(0) <= SHIFT_RIGHT(ADDR_ROTATE_buf, 2);


Но и это не правильно т.к. SHIFT_RIGHT(ххх, 2) здесь не применим. A <= SHIFT_RIGHT(A, 2) эквивалентен коду A <= "00" & A(n downto 2); Но такой код не соответствует сдвигу нарисованному на диаграмме. Код, соответствующий диаграмме (без учета вращения) выглядит так A <= "00" & A(n downto 3) & A(1); Ну а если еще и учесть вращение, то код полностью соответствующий диаграмме будет таким
Код
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);


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


Код
...
if (count_but rem (TO_INTEGER(MODULE)+1) = MODULE) and (count_etap /= 0) then
...

Этим кодом, насколько я понимаю, Вы решили заменить счетчик блоков бабочек с переменным модулем счета. Сам по себе код правильный, но с точки зрения синтеза очень "дорогой" и медленный. Деление по модулю требует много логических элементов и работает достаточно медленно – простой счетчик по модулю во много раз лучше по обеим параметрам. Но если решить обойтись без счетчика, то правильнее воспользоваться свойствами MODULE – его можно использовать как маску. В нашем случае деление по модулю заменяется более простым маскированием.

Код
...
signal COUNT_BLOCK: unsigned(8 downto 0);

COUNT_BLOCK <= COUNT_BUT and MODULE;

if (COUNT_BLOCK = MODULE) and (COUNT_ETAP /= 0) then
ZED
Поправил.
Цитата
но при реализации все сделали наоборот. Кроме этого при смене этапа у нас должен происходить одновременно и сдвиг и вращение.

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

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

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

Жду с нетерпением Ваших комментариев! biggrin.gif
Sefo
Цитата(ZED @ Sep 20 2009, 16:06) *
Чтобы осуществить сдвиг по кругу. А иначе теряется значение 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 бит.
ZED
Поправил реализацию генератора адресов на чтение, сделал реализацию генератора дресов для ROM.

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

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


Мне казалось, что адреса записи на этапе n - это адреса чтения на этапе n+1. А вы говорите счетчик на 512, причем на всех этапах формируется одинаково.
Sefo
Цитата(ZED @ Sep 21 2009, 18:49) *
Мне казалось, что адреса записи на этапе n - это адреса чтения на этапе n+1.


А по-другому и быть не может smile.gif. Если точка №Х была записана на этапе 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). И т.д.

Вот и получается, что писать проще, чем читать. Можно сделать и наоборот.
Alexey86
огромной спасибо Sefo за обучение...
давно такое искал,
скоро буду также с нуля мастерить )

надеюсь инструкция дойдет до финиша)
ZED
Вот, доделал генератор с учетом маски, только там нужно было написать не
Код
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);

Файл прикрепляю.
Sefo
Цитата(ZED @ Sep 24 2009, 20:18) *
Файл прикрепляю.


Хорошо, но к сожалению, без ошибки не обошлось. Поскольку теперь все записано в одном 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;
ZED
Прикрепляю исправленный генератор адресов на чтение, а также генератор адресов на запись.
Sefo
Очень хорошо. Теперь нужно все эти разрозненные блоки собрать в едином модуле управления. Чтобы это правильно сделать нужно проанализировать блок-схему БПФ. Ваш последний вариант блок-схемы был очень близок к истине. В прицепе доработанная блок-схема (.pdf и архив - в архиве лежит блок-схема в Visio).

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Нажмите для просмотра прикрепленного файла
Нажмите для просмотра прикрепленного файла
ZED
Вот пока мои наработки, еще забыл добавить адресацию поворачивающих множителей...
Sefo
Цитата(ZED @ Oct 5 2009, 21:06) *
Вот пока мои наработки, еще забыл добавить адресацию поворачивающих множителей...


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

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

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

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

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

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

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

В общем, просто слить код из нескольких файлов в один совершенно недостаточно и результат слишком далек от правильно работающего блока управления.
ZED
Вот, немного переделал, правда еще не сделал паузы между этапами
Sefo
Цитата(ZED @ Oct 8 2009, 08:46) *
Вот, немного переделал, правда еще не сделал паузы между этапами


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

Вообще-то, ФИФО для выравнивания задержки слишком дорогое и неоправданное удовольствие. Чтобы задержать 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?
ZED
Цитата
Любопытно, а с чего это Вы вдруг в основном модуле с " 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
...............................................
Sefo
Цитата(ZED @ Oct 9 2009, 09:31) *
Просто прочитал, что лучше использовать wait until, чем список чувствительности при сопоставлении моделирования и синтеза.


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

Цитата(ZED @ Oct 9 2009, 09:31) *
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 к этому коду, про который я спрашивал, отношения не имеет.

Цитата(ZED @ Oct 9 2009, 09:31) *
Тут вопрос появился. Паузы между этапами делать, вводя какой-нибудь сигнал PAUSE, который будет 2 такта держать схему в состоянии ожидания?


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

Цитата(ZED @ Oct 9 2009, 09:31) *
А как еще выравнивать задержки? Вводить управляющие сигналы разрешения для каждого сигнала типа STATE?


Давайте сделаем паузу и рассмотрим некоторые типовые приемы и после этого вернемся к реализации блока управления.
ZED
Да, давайте, а то я уже путаться начинаю.
Sefo
А как насчет статейки про wait until? Любопытно все же...
ZED
Суворова Е.А. Шейнин Ю.Е. Проектирование цифровых систем на VHDL. Стр. 248. Но я, по-видимому, несколько ошибся, там это для сопоставления результатов моделирования и синтеза.
Sefo
Извиняюсь за долгое молчание. Совсем не было времени.
Я написал небольшую заметку о том, как формировать задержанные копии сигналов.

Нажмите для просмотра прикрепленного файла

Надеюсь, теперь будет понятно, как реализовать блок управления БПФ.
ZED
Я так понял, что сигналы для записи в память мы будем задерживать, основываясь на функциональной связи, т.е. вычитаем 5 из исходного значения счетчика бабочек. А как быть с сигналами для чтения коэффициентов? Там нужно вычитать 3*MODULE_ROM или их будем делать на FIFO?
Sefo
Цитата(ZED @ Oct 22 2009, 18:53) *
Я так понял, что сигналы для записи в память мы будем задерживать, основываясь на функциональной связи, т.е. вычитаем 5 из исходного значения счетчика бабочек. А как быть с сигналами для чтения коэффициентов? Там нужно вычитать 3*MODULE_ROM или их будем делать на FIFO?


Что касается адресов на запись, то так действительно можно сделать. Хорошее упражнение. Вопрос-подсказка: до скольки должен считать счетчик бабочек в этом случае?

Что касается остальных сигналов, то выгоднее использовать регистры задержи, вопрос только в том, чтобы поставить их с "правильной стороны".
ZED
Простите с ответом немного потяну, работы много надеюсь за неделю разрулю.
ZED
Цитата
Вопрос-подсказка: до скольки должен считать счетчик бабочек в этом случае?

Счетчик бабочек по-моему должен по-прежнему считать от 0 до 511, а функциональную связь организовать следующим образом:
Код
ADDR_WR <= std_logic_vector(BUTTERFLY_COUNTER - 5);
. Или я чего-то не так понял.

Кстати у меня еще появился вопрос по заметке. Если проанализировать схему на рис. 8. Там написано, что адрес записи WR_ADDR прекратит обновляться за 3 такта до окончания вычислений. Но ведь сигнал Enable запрещающий вычисления дойдет также за 3 такта, так что все компенсируется.

Вот еще у меня какая мысль увеличить счетчик бабочек на один разряд, чтоб он считал от 0 до 516 и вот по его значениям уже управлять.
Sefo
Цитата(ZED @ Oct 30 2009, 10:32) *
Счетчик бабочек по-моему должен по-прежнему считать от 0 до 511, а функциональную связь организовать следующим образом:
Код
ADDR_WR <= std_logic_vector(BUTTERFLY_COUNTER - 5);
. Или я чего-то не так понял.


При использовании функциональной связи, если счетчик бабочек будет считать до 511, то ничего не получится по тем же причинам, что и на рис. 8. Поэтому, объяснения см. ниже.

Цитата(ZED @ Oct 30 2009, 10:32) *
Кстати у меня еще появился вопрос по заметке. Если проанализировать схему на рис. 8. Там написано, что адрес записи WR_ADDR прекратит обновляться за 3 такта до окончания вычислений. Но ведь сигнал Enable запрещающий вычисления дойдет также за 3 такта, так что все компенсируется.


WR_ADDR прекратит обновляться за 3 такта до окончания записи (т.е. 3 такта адрес будет один и тот же), а Enable (к тому же не запрещающий, а разрешающий вычисления и запись) дойдет до памяти за 4 такта, в смысле через 4 такта после начала Enable на входе.

Одна и та же схема может работать в разных режимах. Это определяется управляющими сигналами. Поэтому 2 по-разному реализованные схемы (блока) в одном режиме могут давать эквивалентные результаты, а в другом режиме совершенно разные. Эквивалентные результаты не всегда означает одинаковые - нередко на временной диаграмме есть интервалы времени, на которых все равно какие значения будут принимать сигналы. В этих интервалах два по-разному реализованных блока, могут выдавать совершенно разные значения сигналов и это ни сколько не помешает работе устройства. Но зато, в те интервалы времени, где сигналы должны иметь строго определенное значение их выходы будут строго совпадать. В заметке описано, при каких условиях и в каких случаях работу схемы управления на рисунках 7, 8 и 9 можно считать эквивалентной, а в каких нет. Соответственно, в каких случаях можно использовать функциональную связь (или как правильно ее реализовать), а в каких это не получится.

Возьмем схемы на рисунках 7 и 8 и посмотрим, что получится, если подать на них одно и то же управление. Начнем со схемы на рис. 7, когда адрес записи формирует клон счетчика формирующего адрес чтения. Enable_n обозначены выходы соответствующих триггеров задержки. Enable_4 - выход последнего триггера, подается непосредственно на WE. Как видно из диаграммы, за время активности входного Enable читаются данные по адресам 0...9. Через 4 такта эти обработанные данные появятся на входе памяти. Вместе с ними на память придет задержанная на 4 такта копия сигнала Enable (на диаграмме Enable_4) и пока этот Enable_4 активен счетчик-клон сформирует такую же последовательность адресов записи 0...9, что и счетчик, формирующий адреса чтения.

Нажмите для просмотра прикрепленного файла

Теперь посмотрим, какая диаграмма получится у схемы на рис. 8 при таком же сигнале Enable на входе. Когда RD_ADDR станет равен 3, в этом же такте станет активен сигнал Enable_3. Вычитатель начнет работать - вычислять выражение "RD_ADDR - 3". Т.к вычитатель имеет регистр на выходе, то результат будет появляться с задержкой на 1 такт. Таким образом, когда на выходе вычитателя появится первый правильный адрес на память одновременно придут первые данные и разрешение записи (Enable_4). Но вот входной Enable закончился и RD_ADDR перестал обновляться - остановился на значении 10. Но Enable_3 будет активен еще 3 такта и вычитатель еще три такта будет вычислять выражение "RD_ADDR - 3". Но т.к. RD_ADDR больше не обновляется, то и WD_ADDR будет иметь одно и то же значение: 10 - 3 = 7. Т.е. 3 последних такта поступающие данные будут записаны по одному и тому же адресу памяти - 7.

Нажмите для просмотра прикрепленного файла

Такая же ситуация произойдет и в БПФ, если счетчик бабочек остановится на 511, а адрес записи будет реализован на основе функциональной связи.

Цитата(ZED @ Oct 30 2009, 10:32) *
Вот еще у меня какая мысль увеличить счетчик бабочек на один разряд, чтоб он считал от 0 до 516 и вот по его значениям уже управлять.


Мысль хорошая. Если бы Вы высказали только ее одну, то я бы ошибочно подумал, что Вы правильно поняли как реализовать адрес записи, опираясь на функциональную связь. smile.gif Чтобы правильно реализовать адрес записи, опираясь на функциональную связь, нужно чтобы счетчик бабочек не останавливался на 511 и считал дальше. Разумеется, что нет необходимости вводить еще один счетчик для отсчета тактов паузы, если у нас и так уже есть счетчик, по которому мы можем определить и начало и конец паузы.
ZED
Вот моя очередная попытка, жду Ваших замечаний...
Sefo
Внимательно разбираться в коде и диаграмме времени, к сожалению, не было. Поэтому пока скажу о том, что бросилось в глаза сразу.

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

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

Продолжение следует... smile.gif
ZED
Еще один вариант
Sefo
Так гораздо лучше.

Продолжим анализ кода. Вы, судя по всему, необоснованно используете обособленные операторы if в которых одному и тому же сигналу присваиваются значения. Возьмем первый же process.

Код
process
begin
  wait until (rising_edge(CLK));
    if (RST = '1') then
      STATE <= '1';
    elsif (START = '1') then
      STATE <= '0';
    end if;

    if (COUNT_ETAP = 5) and (BUTTERFLY_COUNTER = 516) then
      STATE <= '1';
    end if;

end process;


Сделали бы что-нибудь подобное за пределами process получили бы от синтезатора сообщение об ошибке, что сигнал имеет несколько драйверов. Но поскольку в теле process операторы "исполняются" последовательно, синтезатор опираясь на это преобразует ваш код в один единственный if ... elsif ... elsif ... end if, а условия и соответствующие им действия расставит в таком порядке приоритетов, который соответствует по поведению вашему коду. Для наглядности поменяем значения, присваиваемые STATE при сбросе и при старте.

Код
process
begin
  wait until (rising_edge(CLK));
    if (RST = '1') then
      STATE <= '0';         --было '1'
    elsif (START = '1') then
      STATE <= '1';         --было '0'
    end if;

    if (COUNT_ETAP = 5) and (BUTTERFLY_COUNTER = 516) then
      STATE <= '1';
    end if;

end process;


Предположим, что случился фронт CLK и все условные выражения истинны. Начинаем "исполнять" код. В первом if сигнал RST имеет высший приоритет по сравнению со START. Значит STATE получит значение '0'. Идем дальше, "исполняем" второй if. Т.к. условие истинно, то STATE получит значение '1' и, поскольку на этом "исполнение" приостанавливается до следующего фронта CLK, STATE будет хранить '1'. Синтезатор должен поставить триггер, работающие именно таким образом. Поэтому получается, что наибольшим приоритетом обладает самый последний if, а наименьшим - самый первый. Эквивалентный код, которым синтезатор заменит ваш получается следующий:

Код
process
begin
  wait until (rising_edge(CLK));
    if (COUNT_ETAP = 5) and (BUTTERFLY_COUNTER = 516) then
      STATE <= '1';
    elsif (RST = '1') then
      STATE <= '0';         --было '1'
    elsif (START = '1') then
      STATE <= '1';         --было '0'
    end if;

    end if;

end process;


Получается, что сигнал сброса RST не самый приоритетный! Сброс, поданный одновременно с истинным выражением "(COUNT_ETAP = 5) and (BUTTERFLY_COUNTER = 516)" будет проигнорирован.

В вашем коде по условиям "(COUNT_ETAP = 5) and (BUTTERFLY_COUNTER = 516)" и "(RST = '1')" STATE получает одно и тоже значение. Поэтому, в конечном счете, код будет работать правильно, но такие удачные совпадения будут далеко не всегда. В каком-нибудь проекте получите трудно обнаруживаемую ошибку, которая будет давать сбои редко и не стабильно.

Такая же ситуация у Вас с BANK_COUNTER_WR.

Я советую задавать приоритеты условий в явном виде.

Еще я обратил внимание, что у Вас довольно часто в разных process встречается условие "(STATE = '0' and BUTTERFLY_COUNTER = 516)". Я, обычно, в таких случаях добавляю еще один комбинаторный signal, который отражает истинность данного условия и имеет, по возможности, имя отражающее суть этого условия. Так же это удобно, если условие потребуется изменить - не придется выискивать все его экземпляры в коде. Например:

Код
END_OF_STAGE <= '1' when (STATE = '0' and BUTTERFLY_COUNTER = 516) else '0';

process
begin
  wait until (rising_edge(CLK));
    if END_OF_STAGE = '1' then
      ...
    end if;

end process;
...
process
begin
  wait until (rising_edge(CLK));
    if END_OF_STAGE = '1' then
      ...
    end if;

end process;



Формирование ADDR_ROM_COUNTER теперь, кажется, сделано так как я имел ввиду. Но это я еще посмотрю по-внимательнее. Диаграмму пока тоже еще не проверил, хотя выглядит правдоподобно.
ZED
Вот подправленный вариант.
Sefo
Пока я анализирую более детально что получилось с управлением, предлагаю написать код блока памяти. С управлением если какие-то ошибки и обнаружатся, то явно не серьезные. По стилю и избыточности вашего кодирования у меня еще остаются замечания, но уже не столь существенные. Их можно обсудить в фоновом режиме.
ZED
Вот ОЗУ.
Sefo
Цитата(ZED @ Nov 12 2009, 07:18) *
Вот ОЗУ.


Халтурите... однако smile.gif То, что в прицепе вашего сообщения - лишь кирпич (даже пол кирпича) из которого нужно построить наше ОЗУ для БПФ. Нам ведь нужно за 1 раз прочитать/записать 4 комплексных точки. А Ваше ОЗУ может хранить только половину одной единственной комплексной точки.
ZED
А я думал, что у нас будет отдельное ОЗУ для реальной части и отдельное для мнимой. 4 ОЗУ - типа банки. Итого просто 8 ОЗУ.
Sefo
Цитата(ZED @ Nov 14 2009, 12:06) *
А я думал, что у нас будет отдельное ОЗУ для реальной части и отдельное для мнимой. 4 ОЗУ - типа банки. Итого просто 8 ОЗУ.


Я не даром начал проект с разработки блок схемы устройства и схемы алгоритма - это очень важная часть разработки. Прежде чем кодировать надо понять что именно мы хотим сделать и как именно мы это будем делать. Откройте блок-схему нашего устройства. там есть деление на модули. Именно этого деления на модули мы и придерживаемся при кодировании.

Кстати, мы вот с вами не позаботились о том, чтобы разработать интерфейсы между модулями и в схеме управления Вы, к примеру, забыли про WRITE_EN_A и WRITE_EN_B, без которых памяти не обойтись - ведь на каждом этапе писать нужно только в один из двух модулей памяти. Про схему управления я еще напишу. Я решил, что лучше будет дать Вам пример реализации и на этом примере объяснить, какие в вашем коде есть ошибки и недостатки реализации. Но т.к. времени у меня сейчас мало, то происходит это все не быстро.

Вернемся к памяти. На блок схеме нарисовано 2 одинаковых модуля памяти. Согласно нашему алгоритму и схеме управления на модуль памяти приходит 4 адреса чтения, 1 адрес записи, разрешения записи, 4 комплексные точки и выходит 4 комплексные точки. Вот именно так и должен выглядеть наш модуль памяти. Внутри этот модуль может состоять из чего угодно - можете поставит 8 блоков памяти (по два на банк) и хранить мнимую и вещественную части отдельно. Можете поставить 4 блока памяти (по одному на банк) и хранить комплексное число в одной ячейке.

Так что придерживайтесь блок-схемы - мы ее не даром рисовали.
ZED
Вот тут два варианта: один простой, а второй с применением package.
Sefo
Цитата(ZED @ Nov 17 2009, 20:51) *
Вот тут два варианта: один простой, а второй с применением package.


Замечательно!

Только на вариант без package не нужно было тратить время.

Вы, я смотрю, попросили Квартус положить каждый блок памяти в M-RAM, но это слишком жирно для таких скромных (по меркам M-RAM) объемов данных. К тому же блоков M-RAM в стратиксе очень мало - на все блоки памяти БПФ не хватит.

У меня, к сожалению, до 6 декабря не будет времени на БПФ. Поэтому Вы пока соберите все наши отдельные блоки в модуль БПФ. Придерживайтесь блок-схемы и не забудьте про package. Еще поставьте ModelSim (даже если Вы с ним не умеете работать - вот заодно и научитесь) т.к. в Квартусе моделировать и отлаживать крайне не удобно. Если будут вопросы, то пишите - краткие ответы я дам и до 6 декабря.
ZED
Сегодня не успеваю, завтра думаю пришлю! Извините за задержку.
flipflop
Ребят, а вы делаете БПФ по Кули-Тьюки? Просто для действительных чисел есть более простые алгоритмы. К примеру, можно посчитать БПХ, а потом за один такт перевести его в БПФ (по-моему формула О'Нила) : никаких комплексных умножителей, в худшем случае для бабочки БПХ нужно два умножения действительных чисел + 4 операции сложения.
ZED
Вот, прикрепляю собранный проект, только я не подсоединил ROM память коэффициентов, т.к. она не написана.
Кстати и вопрос как мы ее будем реализовыватьwhen case?

Цитата
Ребят, а вы делаете БПФ по Кули-Тьюки?

Да именно по нему.
Цитата
Просто для действительных чисел есть более простые алгоритмы. К примеру, можно посчитать БПХ, а потом за один такт перевести его в БПФ (по-моему формула О'Нила) : никаких комплексных умножителей, в худшем случае для бабочки БПХ нужно два умножения действительных чисел + 4 операции сложения.

БПХ это по-моему быстрое преобразование Хартли. Если ссылочку на литературу, где написано по подробнее об этом преобразовании, желательно понятным языком и о его связи с БПФ? С удовольствием бы почитал. Еще бы с удовольствием прочитал бы про быстрое Вейвлет преобразование, как оно аппаратно реализуется.

FFT_2048
Александр77
Цитата(flipflop @ Dec 8 2009, 02:21) *
Ребят, а вы делаете БПФ по Кули-Тьюки? Просто для действительных чисел есть более простые алгоритмы. К примеру, можно посчитать БПХ, а потом за один такт перевести его в БПФ (по-моему формула О'Нила) : никаких комплексных умножителей, в худшем случае для бабочки БПХ нужно два умножения действительных чисел + 4 операции сложения.

Мне кажется что коррекция полета неуместна. Где-то в начале Мастер писал что это учебный проект. Так что постигаем. Все остальные алгоритмы - пусть подождут.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.