В литературе предлагаются различные реализации автоматов. Такая статья есть и на Хабре — «Описание цифровых автоматов на VHDL» от canny. Однако при практической реализации на ПЛИС, тем более на высоких частотах такая реализация неудобна. Предлагается следующий вариант.
Описание компонента:
entity ex_user is
port (
reset : in std_logic: -- 1 – сброс
clk : in std_logic; -- тактовая частота
a : in std_logic; -- вход автомата
q : out std_logic -- выход автомата
)
end ex_user;
Описание типа и сигналов:
type stp_type is ( s0, s1, s2, s3 );
signal stp : stp_type;
signal rstp0 : std_logic;
signal rstp : std_logic;
Синхронизация сброса:
rstp0 <= reset after 1 ns when rising_edge( clk );
rstp <= rstp0 after 1 ns when rising_edge( clk );
Собственно автомат, в данном случае – очень простой
pr_stp: process( clk ) begin
if( rising_edge( clk ) ) then
case( stp ) is
when s0 => -- это начальное значение
q <= ‘0’ after 1 ns;
if( a=’1’ ) then
stp <= s1 after 1 ns;
end if;
when s1 => -- что то делаем, например ждём a=0
if( a=’0’ ) then
stp <= s2 after 1 ns;
end if;
when s2 =>
q <= ‘1’ after 1 ns;
stp <= s0 after 1 ns;
end case;
--- А вот это сброс, он действует только на stp ---
if( rstp=’1’ ) then
stp <= s0 after 1 ns; -- действие только на stp
end if;
end if;
end process;
Особенности:
- Описание отдельного типа для сигнала состояния
- Синхронизация сигнала reset
- Reset действует только на сигнал состояния
- Все входные сигналы должны быть синхронны с тактовой частотой автомата
- Переходы и выходные сигналы описаны в одном процессе
- Наличие after 1 ns – для облегчения моделирования
- Может быть реализован как автомат Мура, так и автомат Мили
Здесь нет ничего революционного, всё это давно известно. Но всё вместе это как раз и образует стиль описания.
Более подробно про особенности описания. В первую очередь это введение отдельного типа для сигнала состояния. В данном случае описан тип перечисление. Это даёт возможность синтезатору выбрать тип сигнала. Это может быть one-hot, может быть двоичный счётчик или счётчик Грея. Тип реализации может быть указан директивами синтезатора. Это отделяет описание от реализации. Если кому-то это не нравиться, то вполне возможно задание типа std_logic_vector и отдельных констант s0, s1 и т.д. Иногда мне высказывают претензии, что названия s0, s1 не информативны, и лучше бы использовать константы связанные по смыслу с конкретным состоянием. Так я тоже пытался делать, но в процессе разработки очень часто логика состояния меняется но название остаётся, а это только запутывает дело.
Синхронизация сигнала reset – очень часто reset является асинхронным относительно тактовой частоты автомата. Что бы не проверять откуда он идёт лучше всегда поставить два триггера. В любом случае это облегчит трассировку. Сброс действует только на сигнал состояния. Это также облегчает трассировку, особенно при большом количестве выходных сигналов, но это требует что бы в начальном состоянии все выходные сигналы были описаны.
Автомат является синхронной схемой поэтому все входные сигналы должны быть синхронный с тактовой частотой, здесь без вариантов. Требуется обязательно знать откуда приходит сигнал. Если это сигнал формируется на другой тактовой частоте, то в обязательном порядке требуется поставить два триггера (также как для сигнала reset).
То что переходы и выходные сигналы описаны в одном процессе это визуально облегчает процесс разработки и повышает наглядность. Если сделать автомат в двух (а то и в трёх процессах) как нам советуют учебники, то это сложно охватить взглядом. Хотя бы потому что большой автомат на одном экране компьютера не поместится.
Самым спорным утверждением является запись after 1 ns при присваивании сигнала. На одном из форумов меня очень сильно за это критиковали. А также писали, что по мере накопления опыта я избавлюсь от этой вредной привычки. Мой опыт показывает что это очень полезная привычка. Наличие такой записи позволяет быть уверенным что результаты моделирования и результаты работы в реальной аппаратуре будут одинаковыми. Всё дело в понятии дельта задержки.
Рассмотрим простую конструкцию языка:
clk2 <= clk1;
b <= a when rising_edge( clk1 );
c <= b when rising_edge( clk1 );
d <= b when rising_edge( clk2 );
Результат работы представлен на рисунке:
На диаграмме визуально видно, что сигналы тактовой частоты clk1 и clk2 совпадают, но на самом деле clk2 задержан относительно clk1 на величину дельта задержки.Сигнал c отстаёт от сигнала b на один такт. Это правильно. Но вот сигнал d должен совпадать с сигналом c, а этого не происходит. Он срабатывает раньше. Это происходит из-за того, что он защёлкивается частотой clk2. При работе в аппаратуре clk2 будет полностью совпадать с clk1, это будет один и тот же сигнал на глобальной тактовой линии. В реальных проектах присваивания типа clk2<=clk1 встречаются достаточно часто. Конечно можно попробовать всем разработчикам строго настрого запретить это делать, но я сильно сомневаюсь в результате. Вместо присваивания можно сделать описание типа alias:
alias clk2 is clk1;
В этом случае clk2 будет просто ещё одним именем clk1 и результаты будут правильными. Но есть ещё один момент. Чисто визуально, на временной диаграмме непонятно когда происходит изменение сигналов относительно тактовой частоты.
А вот что происходит при добавлении after 1 ns:
Сигналы c и d формируются правильно. Изменение сигнала b прекрасно видно относительно фронта тактовой частоты.
Однажды много лет назад я потратил очень много времени на поиск причины различного поведения в модели и в реальной аппаратуре. Сдвиг был всего лишь на один такт. А причина именно эта – присваивание тактовой частоты и отсутствие after 1 ns. С тех пор я всегда добавляю задержку и ни разу об этом не пожалел.
И напоследок, в примере приведён автомат Мура. Выходные сигналы зависят только от состояния. Но код всегда можно изменить, например так:
when s1 => -- что то делаем, например ждём a=0
if( a=’0’ ) then
stp <= s2 after 1 ns;
q <= ‘1’ after 1 ns;
end if;
В этом случае сигнал q будет сформирован на один такт раньше, т.е. он будет зависеть от состояния и входного сигнала. А это уже автомат Мили.
В статье Описание цифровых автоматов на VHDL также приведёт вариант описания в одном процессе. На первый взгляд всё совпадает.
PROCESS (clk, reset)
BEGIN
IF reset = '1'
THEN state <= Init;
ELSIF (rising_edge(clk)) THEN
CASE state IS
WHEN Init =>
IF cnt = '1'
THEN state <= R;
ELSE
state <= Init;
END IF;
output <= "000";
WHEN R =>
IF cnt = '1'
THEN state <= RG;
ELSE
state <= R;
END IF;
output <= "100";
WHEN RG =>
IF cnt = '1'
THEN state <= G;
ELSE
state <= RG;
END IF;
output <= "010";
WHEN G =>
IF cnt = '1'
THEN state <= GR;
ELSE
state <= G;
END IF;
output <= "001";
WHEN GR =>
IF cnt = '1'
THEN state <= R;
ELSE
state <= GR;
END IF;
OUTPUT <= "010";
END CASE;
END IF;
END PROCESS;
В данном описании есть очень серьёзная ошибка. Во время действия сигнала reset выходной сигнал OUTPUT не определён, что бы его определить требуется внести назначение сигнала внутри сброса:
IF reset = '1'
THEN state <= Init;
OUTPUT <= "000";
ELSIF
В данном случае добавляются три линии сброса. При большом количестве выходных сигналов это ухудшает трассировку ПЛИС.
В моём случае сигнал reset действует только на сигнал состояния, но поскольку внутри процесса он находиться после case то в соответствии с правилами языка он имеет приоритет над назначением состояния внутри case. Особенностью этого решения является то, что перевод выходных сигналов в исходное состояние произойдёт только на второй такт сигнала reset. Однако в подавляющем количестве случаев это несущественно.
В заключение хочу отметить что такой стиль описания очень хорошо себя зарекомендовал, автомат хорошо разводится в ПЛИС. Легко получаются каскадные соединения нескольких автоматов и соединения с другими схемами внутри ПЛИС.
Комментарии (13)
Uint32
05.09.2016 01:50>> after 1 ns – для облегчения моделирования
Весьма спорно, т.к. сразу попадаем в не синтезируемое подмножество.
>> Наличие такой записи позволяет быть уверенным что результаты моделирования и результаты работы в реальной аппаратуре будут одинаковыми.
А что мешает провести верификацию прогнав tb после P&R?dsmv2014
05.09.2016 09:49По стандарту языка слово after синтезатором игнорируется. Когда ПЛИС были маленькими я проводил моделирование с реальными задержками после стадии P&R. Для современных ПЛИС это не реально. Слишком долго будет формироваться модель с задержками и будет слишком большое время моделирования.
b_oberon
05.09.2016 10:18Использование after в синтезируемом коде — это плохо, потому что:
1. Вы смешиваете functional simulation и gate-level simulation (который после P&R). Задача первого — убедиться, что Ваш алгоритм правильный, второго — что суровая физическая реальность не разрушит Ваш правильный алгоритм. Если gate-level simulation выполняется слишком медленно, то стоит посмотреть в сторону timing analysis (у Altera это, если не ошибаюсь, TimeQuest): он укажет Вам, в каких именно местах могут быть проблемы с временными характеристиками (плюс компилятор может использовать эту информацию для оптимизации схемы).
2. Возможности повторного использования Вашего кода сильно ограниченны, поскольку он не инвариантен относительно частоты: при 10 МГц задержка 1 ns — это почти дельта-задержка (в том плане, что она мала по сравнению с периодом), на частоте 100 МГц — уже чувствительно, а на 1 ГГц — целый такт. В зависимости от заданной частоты работы схемы functional simulation будет давать очень разные результаты, и это явно не то, к чему Вы стремитесь. Ну и никакого физического обоснования для константы 1 ns нет, все будет зависеть от модели ПЛИС.dsmv2014
05.09.2016 11:17На современных проектах слишком медленно выполняется даже functional simulation. Например мне приходится при помощи параметров удалять целые блоки из проекта ПЛИС. Это касается например блоков PCE Express и контроллеров DDR. Timig Analysis конечно используется.
По поводу повторного использования — этот код у нас постоянно повторно используется. До 300 МГц after 1 ns прекрасно выглядит, а выше уже можно писать after 0.1 ns.
Ещё раз, задача after 1 ns — показать что есть задержка. Она должна быть меньше периода. С этой точки зрения неважно сколько получиться в реальной аппаратуре: 0.1 или 2.2 ns. Если получиться меньше периода — результаты моделирования и реальной работы совпадут. Остальное неважно.
kurilkin
05.09.2016 11:09а можно попросить хоть один пример необходимости такого переназначения «clk2 <= clk1»?
dsmv2014
05.09.2016 11:20Это связано с повторным использованием кода. Если есть какой-то кусок кода, который перенесён из другого проекта, то там может использоваться другое название сигнала тактовой частоты. И напрашивается сделать присваивание вместо переименования, особенно если надо провести несколько экспериментов с разными тактовыми частотами.
kurilkin
05.09.2016 11:43+1а компилятор дает ли гарантию прямого объединения цепей, есть ли уверенность, что он не засадит их через буфер, скромно сообщив об этом в одном из варнингов?
Khort
06.09.2016 10:40Если делать микросхему (ASIC), то так и произойдет — бэк-енд тул вставляет буферы во все assign в схеме. Учитывая, что это клоковская цепь, а не сигнальная, могут быть и другие последствия — к примеру, криво построенное клоковое дерево, поскольку для разрыва assign используются одни буфера, а для клокового дерева — другие. В общем, на ПЛИС это может и прокатывает, но вот для ASIC я бы не советовал использовать подобные конструкции без лишней надобности — лишний риск словить ошибку на ровном месте.
kurilkin
06.09.2016 11:07да и в ПЛИСине это тоже приводит часто черт знает к чему)
в порядочных домах за это канделябром бьют)
firegurafiku
05.09.2016 21:15Я не суперспец в ПЛИС, но с каких пор копипаста называется повторным использованием кода? Разве не следует вынести общую функциональность в отдельный именованный модуль?
nerudo
Казалось бы, чего ради внутри одного модуля переназначать клок с одного сигнала на другой? И задержка сброса решит далеко не все проблемы такого переназначения. А так да, это одна из дыр VHDL, создающая принципиальное расхождение между симуляцией и аппаратурой. Поэтому в ряде coding rules запрещают использовать переназначенный клок в том же модуле и смешивать сигналы от нового и старого.