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

 
 
> Боремся с защёлками, то бишь 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
Ответов (1 - 11)
AB27
сообщение Dec 11 2004, 23:58
Сообщение #2


Участник
*

Группа: Свой
Сообщений: 29
Регистрация: 5-11-04
Пользователь №: 1 062



По поводу защелок.

В Xilinx XST User Guide есть такой пример:

FSM with 2 Processes

To eliminate a register from the "outputs", you can remove all assignments “outp <=…” from the Clock synchronization section.
This can be done by introducing two processes as shown in the following figure.

Following is VHDL code for an FSM with two processes.

library IEEE;
use IEEE.std_logic_1164.all;
entity fsm is
port (
clk, reset, x1 : IN std_logic;
outp : OUT std_logic
);
end entity;
architecture beh1 of fsm is
type state_type is (s1,s2,s3,s4);
signal state: state_type;
begin
process1: process (clk, reset)
begin
if (reset =’1’) then
state <=s1;
elsif (clk=’1’ and clk’Event) then
case state is
when s1 =>
if x1=’1’ then
state <= s2;
else
state <= s3;
end if;
when s2 => state <= s4;
when s3 => state <= s4;
when s4 => state <= s1;
end case;
end if;
end process process1;

process2 : process (state)
begin
case state is
when s1 => outp <= ’1’;
when s2 => outp <= ’1’;
when s3 => outp <= ’0’;
when s4 => outp <= ’0’;
end case;
end process process2;
end beh1;

http://www.xilinx.com/support/sw_manuals/xilinx6/download/
Go to the top of the page
 
+Quote Post
Vincent Vega
сообщение Dec 12 2004, 10:52
Сообщение #3


Участник
*

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



но у меня и так состояния выходов автомата и логика его переходов описаны в разных процессах
видимо, нужно что-то другое...
да я, собственно, не против регистров на выходе. Мне бы только хотелось, чтобы их состояние опеределялось по фронту синхросигнала
Go to the top of the page
 
+Quote Post
vetal
сообщение Dec 12 2004, 13:28
Сообщение #4


Гуру
******

Группа: Модераторы
Сообщений: 2 095
Регистрация: 27-08-04
Из: Россия, СПб
Пользователь №: 553



По всей видимости имеет место недопределенная логика, определите ее до конца и не будет латчей.

Если нужна фиксация сигналов, то используйте Dff:

if (clk'event and clk='1') then
... user logic area...
end if;
Go to the top of the page
 
+Quote Post
Vincent Vega
сообщение Dec 12 2004, 13:36
Сообщение #5


Участник
*

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



не совсем понятно, что такое "неопределённая логика"

вообще говоря, что получится, если написать

if (clk'event and clk='1') then
... user logic area...
end if;

я примерно представляю. Интересует как это применить к конкретному примеру (см.выше). В чём в данном случае должно заключаться это "доопределение логики"? (Пример-то вроде простой.)
Go to the top of the page
 
+Quote Post
vvvvv
сообщение Dec 13 2004, 04:28
Сообщение #6


Местный
***

Группа: Свой
Сообщений: 342
Регистрация: 21-06-04
Пользователь №: 85



Насколько я понял , "доопределить логику ", значит просто сразу после
else все остальное закрыть в
if(clk'event and clk='1' )
.....
end if
Вот это самое clk'event и заставляет выполнять все дальнейшие операции по
фронту, а не по уровню. Кстати об этом хорошо пишет Cummings, он же кстати настоятельно рекомендует избавляться от лэтчей везде, где только можно.

if(clk'event and clk='1' )
.......
case CurState is
when sStartReadMem =>
write_MEM <= '0';
MEM_A(14 downto 0) <= addr1 (14 downto 0);
MEM_CS <= '0';
when .....
--самое главное при case не забыть добавить
when others => и то что нужно сделать по умолчанию
без этого others схема может встать в ступор

end case ;
end if;
Мне кажется так.
Go to the top of the page
 
+Quote Post
vetal
сообщение Dec 13 2004, 14:38
Сообщение #7


Гуру
******

Группа: Модераторы
Сообщений: 2 095
Регистрация: 27-08-04
Из: Россия, СПб
Пользователь №: 553



Определить логику до конца - описать вае возможные ее комбинации.
Один из выходов - поставить нечто вроде "MEM_CS<='0'" или "MEM_A(14 downto 0)<=(others=>'-')", для всех сикналов срвзу после "process (..)\n\tbegin\n\t\t", и это будут значения принимаемые логикой, если она не описана. Желательно в каждом case->when описывать все сигналы и переменные используемые в process'е(это расширение указанного выше). настоятельно рекомендую устранить все латчи(приведенная конструкция с клоком просто заменит латчи на синхронные триггеры), т.к. рано или поздно они вас укусят.
В вашем примере достадочно будет сделать асинхронные выходы автомата, и не плодить регистры, для этого можно исролизовать -
MEM_CS<= '1' when state=sEndReadMem else '0';. естественно вне процесса, это пожалуй будет самый простой для вас вариант.
Go to the top of the page
 
+Quote Post
des00
сообщение Aug 19 2005, 05:50
Сообщение #8


Вечный ламер
******

Группа: Модераторы
Сообщений: 7 248
Регистрация: 18-03-05
Из: Томск
Пользователь №: 3 453



Цитата(vetal @ Dec 13 2004, 09:38)
В вашем примере достадочно будет сделать асинхронные выходы автомата, и не плодить регистры, для этого можно исролизовать -
MEM_CS<= '1' when state=sEndReadMem else '0';. естественно вне процесса, это пожалуй будет самый простой для вас вариант.
*


ХМм глупый вопрос, почему этот выход будет асинхронным ? если кодирование one hot и состояния храняться на регистрах, то класический Мур с синхронным по клоку выходом.
Конечно это нужно учесть при таймингах блока.


--------------------
Go to the top of the page
 
+Quote Post
oval
сообщение Aug 19 2005, 09:40
Сообщение #9


Местный
***

Группа: Свой
Сообщений: 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
maxus
сообщение Aug 23 2005, 17:57
Сообщение #10


Частый гость
**

Группа: Свой
Сообщений: 113
Регистрация: 5-04-05
Пользователь №: 3 864



Обычно операции разносят по разным тактам (например изменение состояния автомата и увеличение счетчика). Делают модуль, который после прихода управляющего сигнала выдает последовательные импульсы. И уже от этих импульсов ты синхронизируешься.

in __|``|______________
out_1 ______|``|___________
out_2 __________|``|_______
out_3 ______________|``|___

например по out_1 меняешь состояние автомата, по out_2 - инкрементируешь счетчик. Работает железно - проверенно.
Go to the top of the page
 
+Quote Post
oval
сообщение Aug 24 2005, 08:01
Сообщение #11


Местный
***

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



Цитата(maxus @ Aug 23 2005, 20:57)
Обычно операции разносят по разным тактам (например изменение состояния автомата и увеличение счетчика). Делают модуль, который после прихода управляющего сигнала выдает последовательные импульсы. И уже от этих импульсов ты синхронизируешься.

in        __|``|______________
out_1  ______|``|___________
out_2  __________|``|_______
out_3  ______________|``|___

например по out_1 меняешь состояние автомата, по out_2 - инкрементируешь счетчик. Работает железно - проверенно.
*

Не думаю, что подобная идея подойдет для данной конкретной задачи, ибо, как минимум, растянется цикл доступа к памяти. Что, если мы не имеем возможности разнести операции по последовательным тактам? Что, если этих операций 100? Чем повышается надежность работы схемы при таком подходе? Модуль, который после прихода управляющего сигнала выдает последовательные импульсы, это тот же автомат состояний. Идея понятна, но на мой взгляд, никакой надежности не добавит.

P.S. Господа, при проектировании автоматов "не опускайтесь" до уровня импульсов, думайте "выше", на уровне действий, операций. Про управляющие сигналы, конечно, не забывайте.
Go to the top of the page
 
+Quote Post
maxus
сообщение Aug 25 2005, 06:18
Сообщение #12


Частый гость
**

Группа: Свой
Сообщений: 113
Регистрация: 5-04-05
Пользователь №: 3 864



Как раз в данной конкретной задаче не 100 операций а две - изменение состояния и инкремент адреса. Бало бы 100, никто не предлагал бы вариант с последовательными импульсами...
А надежность будет выше, тк не надо будет думать - успеет ли счетчик посчитать, потому что со слов автора: "Т.е. всё построено на том, что изменение состояния автомата (а значит и inc_addr1) происходит по положительному фронту CLK, а инкремент счётчика addr1 по отрицательному." А так все будет разнесено по отдельным тактам. Что может быть надежней? Если схема позволяет по времени растянуть операцию на два такта - то это будет хороший вариант.
Go to the top of the page
 
+Quote Post

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

 


RSS Текстовая версия Сейчас: 19th July 2025 - 19:31
Рейтинг@Mail.ru


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