Реализация конечного автомата на языке VHDL

Конечные автоматы играют очень важную роль при разработке прошивок ПЛИС. Все слышали о двух классических типах автоматов: автомат Мили и автомат Мура, которые были предложены ещё до эпохи ПЛИС. Однако специфика построения ПЛИС вносит свои коррективы и в процессе работы у меня сложился вполне определённый стиль описания автомата.

В литературе предлагаются различные реализации автоматов. Такая статья есть и на Хабре — «Описание цифровых автоматов на 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 );

Результат работы представлен на рисунке:
image

На диаграмме визуально видно, что сигналы тактовой частоты clk1 и clk2 совпадают, но на самом деле clk2 задержан относительно clk1 на величину дельта задержки.Сигнал c отстаёт от сигнала b на один такт. Это правильно. Но вот сигнал d должен совпадать с сигналом c, а этого не происходит. Он срабатывает раньше. Это происходит из-за того, что он защёлкивается частотой clk2. При работе в аппаратуре clk2 будет полностью совпадать с clk1, это будет один и тот же сигнал на глобальной тактовой линии. В реальных проектах присваивания типа clk2<=clk1 встречаются достаточно часто. Конечно можно попробовать всем разработчикам строго настрого запретить это делать, но я сильно сомневаюсь в результате. Вместо присваивания можно сделать описание типа alias:
alias clk2 is clk1;

В этом случае clk2 будет просто ещё одним именем clk1 и результаты будут правильными. Но есть ещё один момент. Чисто визуально, на временной диаграмме непонятно когда происходит изменение сигналов относительно тактовой частоты.

А вот что происходит при добавлении after 1 ns:

image

Сигналы 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. Однако в подавляющем количестве случаев это несущественно.

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

Комментарии (0)

© Habrahabr.ru