Как стать автором
Поиск
Написать публикацию
Обновить

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

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

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

В заключение хочу отметить что такой стиль описания очень хорошо себя зарекомендовал, автомат хорошо разводится в ПЛИС. Легко получаются каскадные соединения нескольких автоматов и соединения с другими схемами внутри ПЛИС.
Теги:
Хабы:
Всего голосов 23: ↑22 и ↓1+21
Комментарии13

Публикации

Ближайшие события