реклама на сайте
подробности

 
 
> Боремся с защёлками, то бишь latch'ами
Vincent Vega
сообщение Dec 11 2004, 22:26
Сообщение #1


Участник
*

Группа: Свой
Сообщений: 46
Регистрация: 26-09-04
Пользователь №: 721



Введение (можно не читать smile.gif):

к ПЛИС подключено статическое асинхронное ОЗУ 32 кБ (имена выводов начинаются с MEM_)
имеется два счётчика адреса addr1 и addr2. По первому адресу при наступлении некоторых условий нужно читать данные из ОЗУ (после чего адрес инкрементировать), при наступлении другого условия нужно записывать
данные в ОЗУ по второму адресу.
сигнал write_MEM управляет тристабильным буфером и когда он равен '1' MEM_D настроена на запись (т.е. вывод данных из ПЛИС)


Суть вопроса (желательно ознакомиться перед ответом):

После компиляции в Quartus получаю, что MEM_A, MEM_CS, MEM_RD, MEM_WR
являются выходами защёлок и, соотвественно, предупреждение об этом от design Assistant: "Design should not contain combinational loops". Как побороть эту неприятность (на форуме неоднократно слышал высказывания, что люди умудряются делать проекты вообще без защёлок).

Собственно, кусок кода с пояснениями:

Схему описываю как автомат, изменение состояний которого происходит по положительному фронту синхросигнала CLK.
Ниже приводится часть кода процесса, формирующего значение выходов автомата, в зависимости от его текущего состояния.
if (CLR = '1') then
MEM_CS <= '1';
MEM_RD <= '1';
MEM_WR <= '1';
inc_addr1 <= '0';
write_MEM <= '1';
else
case CurState is
when sStartReadMem =>
write_MEM <= '0';
MEM_A(14 downto 0) <= addr1 (14 downto 0);
MEM_CS <= '0';
MEM_RD <= '0';
when sEndReadMem =>
MEM_CS <= '1';
MEM_RD <= '1';
data(7 downto 0) <= MEM_D(7 downto 0);
inc_addr1 <= '1';
when sSaveData =>
write_MEM <= '0';
inc_addr1 <= '0';
-------

when sStartWriteMem =>
MEM_A(14 downto 0) <= addr2(14 downto 0);
MEM_CS <= '0';
MEM_WR <= '0';
when sEndWriteMem =>
MEM_CS <= '1';
MEM_WR <= '1';

Сопутствующий вопрос:
Как наиболее глюкобезопасно сделать инкрементацию addr1 после чтения? Сейчас для этого (см. выше) я изменяю состояние сигнала inc_addr1, который подан на вход разрешения счёта счётчика, реализованного на базе lpm_counter. Синхросигналом для счётчика является инвертированная частота CLK. Т.е. всё построено на том, что изменение состояния автомата (а значит и inc_addr1) происходит по положительному фронту CLK, а инкремент счётчика addr1 по отрицательному.
Существуют опасения, что при некоторых вариантах разводки кристалла установление значения inc_addr1 может произойти позднее, чем через пол-такта CLK=60MГц, и соотвественно инкремент не произойдёт.

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

Спасибо
Go to the top of the page
 
+Quote Post
 
Start new topic
Ответов
oval
сообщение Aug 19 2005, 09:40
Сообщение #2


Местный
***

Группа: Свой
Сообщений: 265
Регистрация: 15-03-05
Из: Москва
Пользователь №: 3 367



Цитата(Vincent Vega @ Dec 12 2004, 01:26)
Введение (можно не читать smile.gif):

к ПЛИС подключено статическое асинхронное ОЗУ 32 кБ (имена выводов  начинаются с MEM_)
имеется два счётчика адреса addr1 и addr2. По первому адресу при наступлении некоторых условий нужно читать данные из ОЗУ (после чего адрес инкрементировать), при наступлении другого условия нужно записывать
данные в ОЗУ по второму адресу.
сигнал write_MEM управляет тристабильным буфером и когда он равен '1' MEM_D настроена на запись (т.е. вывод данных из ПЛИС)


Суть вопроса (желательно ознакомиться перед ответом):

После компиляции в Quartus получаю, что MEM_A, MEM_CS, MEM_RD, MEM_WR
являются выходами защёлок и, соотвественно, предупреждение об этом от design Assistant: "Design should not contain combinational loops".  Как побороть эту неприятность (на форуме неоднократно слышал высказывания, что люди умудряются делать проекты вообще без защёлок).

Собственно, кусок кода с пояснениями:

Схему описываю как автомат, изменение состояний которого происходит по положительному фронту синхросигнала CLK.
Ниже приводится часть кода процесса, формирующего значение выходов автомата, в зависимости от его текущего состояния.
if (CLR = '1') then
    MEM_CS <= '1';
    MEM_RD <= '1';
    MEM_WR <= '1';
    inc_addr1 <= '0';
    write_MEM <= '1';
else
    case CurState is
        when sStartReadMem =>
            write_MEM <= '0';
            MEM_A(14 downto 0) <= addr1 (14 downto 0);
            MEM_CS <= '0';
            MEM_RD <= '0';
        when sEndReadMem =>
            MEM_CS <= '1';
            MEM_RD <= '1';
            data(7 downto 0) <= MEM_D(7 downto 0);
            inc_addr1 <= '1';
        when sSaveData =>
            write_MEM <= '0';
            inc_addr1 <= '0';
            -------

        when sStartWriteMem =>
            MEM_A(14 downto 0) <= addr2(14 downto 0);
            MEM_CS <= '0';
            MEM_WR <= '0';
        when sEndWriteMem =>
            MEM_CS <= '1';
            MEM_WR <= '1';

Сопутствующий вопрос:
Как наиболее глюкобезопасно сделать инкрементацию addr1 после чтения? Сейчас для этого (см. выше) я изменяю состояние сигнала inc_addr1, который подан на вход разрешения счёта счётчика, реализованного на базе lpm_counter. Синхросигналом для счётчика является инвертированная частота CLK. Т.е. всё построено на том, что изменение состояния автомата (а значит и inc_addr1) происходит по положительному фронту CLK, а инкремент счётчика addr1 по отрицательному.
Существуют опасения, что при некоторых вариантах разводки кристалла установление значения inc_addr1 может произойти позднее, чем через пол-такта CLK=60MГц, и соотвественно инкремент не произойдёт.

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

Спасибо
*

Для начала, как образуются защелки: допустим, мы хотим сформировать некоторый сигнал B, состояние которого изменяется в зависимости от сигнала A следующим образом, к примеру, пусть когда A высокий, то инвертируем B. Это описывается так:
Код
if A = 1 then
   B <= not B;
end if;

Дальше все просто: мы не указываем, какое значение присвоить B, если A не равен '1', то есть если A не равен '1', то состояние сигнала B изменять не требуется, и требуется сохранение его (cool.gif текущего состояния, это и образует защелку.
В вашем случае, при формировании выходных сигналов автомата состояний, чтобы избежать образования защелок, требуется назначать значение каждого выходного сигнала в каждой ветви условий, либо, как обычно делают, чтобы не забыть, при входе в процесс назначают каждому сигналу значение по умолчанию. Попробую продемонстрировать:
Код
if (CLR = '1') then
   MEM_CS <= '1';
   MEM_RD <= '1';
   MEM_WR <= '1';
   inc_addr1 <= '0';
   write_MEM <= '1';

   MEM_A(14 downto 0) <= addr1 (14 downto 0); -- отсутствует в данной ветви
   data(7 downto 0) <= MEM_D(7 downto 0); -- отсутствует в данной ветви

else
   case CurState is
        when sStartReadMem =>
            write_MEM <= '0';
            MEM_A(14 downto 0) <= addr1 (14 downto 0);
            MEM_CS <= '0';
            MEM_RD <= '0';

            MEM_WR <= '1'; -- отсутствует в данной ветви
            inc_addr1 <= '0'; -- отсутствует в данной ветви
            data(7 downto 0) <= MEM_D(7 downto 0); -- отсутствует в данной ветви

        when sEndReadMem =>
            MEM_CS <= '1';
            MEM_RD <= '1';
            data(7 downto 0) <= MEM_D(7 downto 0);
            inc_addr1 <= '1';

            write_MEM <= '0'; -- отсутствует в данной ветви
            MEM_A(14 downto 0) <= addr1 (14 downto 0); -- отсутствует в данной ветви
            MEM_WR <= '1'; -- отсутствует в данной ветви

        when sSaveData =>    
            write_MEM <= '0';
            inc_addr1 <= '0';
            -------

            MEM_CS <= '1'; -- отсутствует в данной ветви
            MEM_RD <= '1'; -- отсутствует в данной ветви
            data(7 downto 0) <= MEM_D(7 downto 0); -- отсутствует в данной ветви
            MEM_A(14 downto 0) <= addr1 (14 downto 0); -- отсутствует в данной ветви
            MEM_WR <= '1'; -- отсутствует в данной ветви

        when sStartWriteMem =>
            MEM_A(14 downto 0) <= addr2(14 downto 0);
            MEM_CS <= '0';
            MEM_WR <= '0';

            MEM_RD <= '1'; -- отсутствует в данной ветви
            data(7 downto 0) <= MEM_D(7 downto 0); -- отсутствует в данной ветви
            write_MEM <= '0'; -- отсутствует в данной ветви
            inc_addr1 <= '0'; -- отсутствует в данной ветви

        when sEndWriteMem =>
            MEM_CS <= '1';
            MEM_WR <= '1';

            MEM_A(14 downto 0) <= addr2(14 downto 0); -- отсутствует в данной ветви
            MEM_RD <= '1'; -- отсутствует в данной ветви
            data(7 downto 0) <= MEM_D(7 downto 0); -- отсутствует в данной ветви
            write_MEM <= '0'; -- отсутствует в данной ветви
            inc_addr1 <= '0'; -- отсутствует в данной ветви
   end case;
   -- здесь тоже надо присваивать
   MEM_CS <= '1'; -- отсутствует в данной ветви
   MEM_WR <= '1'; -- отсутствует в данной ветви
   MEM_A(14 downto 0) <= addr2(14 downto 0); -- отсутствует в данной ветви
   MEM_RD <= '1'; -- отсутствует в данной ветви
   data(7 downto 0) <= MEM_D(7 downto 0); -- отсутствует в данной ветви
   write_MEM <= '0'; -- отсутствует в данной ветви
   inc_addr1 <= '0'; -- отсутствует в данной ветви

end if;

Это первый вариант, не самый удачный, надо сказать, обычно так не делают. Второй вариант:
Код
-- присваиваем всем сигналам значения по умолчанию и дальше ни разу не паримся, о том, что что-то забыли
MEM_CS <= '1';
MEM_WR <= '1';
MEM_A(14 downto 0) <= addr2(14 downto 0);
MEM_RD <= '1';
data(7 downto 0) <= MEM_D(7 downto 0);
write_MEM <= '0';
inc_addr1 <= '0';

if (CLR = '1') then
   MEM_CS <= '1';
   MEM_RD <= '1';
   MEM_WR <= '1';
   inc_addr1 <= '0';
   write_MEM <= '1';
else
   case CurState is
        when sStartReadMem =>
            write_MEM <= '0';
            MEM_A(14 downto 0) <= addr1 (14 downto 0);
            MEM_CS <= '0';
            MEM_RD <= '0';
        when sEndReadMem =>
            MEM_CS <= '1';
            MEM_RD <= '1';
            data(7 downto 0) <= MEM_D(7 downto 0);
            inc_addr1 <= '1';
        when sSaveData =>    
            write_MEM <= '0';
            inc_addr1 <= '0';
            -------

        when sStartWriteMem =>
            MEM_A(14 downto 0) <= addr2(14 downto 0);
            MEM_CS <= '0';
            MEM_WR <= '0';
        when sEndWriteMem =>
            MEM_CS <= '1';
            MEM_WR <= '1';
end if;

Второй, широко используемый вариант. Его и рекомендую.

Относительно инкремента счетчика адреса: по тому фронту, по которому защелкиваете прочитанные из памяти данные, по нему же и инкрементируете адрес. Здесь ничего выдумывать не надо.

Удачи!

P.S. Не особо заморачивался, возможны опечатки
Go to the top of the page
 
+Quote Post



Reply to this topicStart new topic
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0

 


RSS Текстовая версия Сейчас: 21st July 2025 - 14:20
Рейтинг@Mail.ru


Страница сгенерированна за 0.01411 секунд с 7
ELECTRONIX ©2004-2016