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

):
к ПЛИС подключено статическое асинхронное ОЗУ 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 изменять не требуется, и требуется сохранение его (

текущего состояния, это и образует защелку.
В вашем случае, при формировании выходных сигналов автомата состояний, чтобы избежать образования защелок, требуется назначать значение каждого выходного сигнала в каждой ветви условий, либо, как обычно делают, чтобы не забыть, при входе в процесс назначают каждому сигналу значение по умолчанию. Попробую продемонстрировать:
Код
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. Не особо заморачивался, возможны опечатки