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

В этой статье рассматривается проблема реализации интерфейсов в которых присутствуют входные сигналы  изменяющие своё значение асинхронно по отношению к внутреннему синхросигналу в ПЛИС. Примером таких интерфейсов есть: ведомый модуль SPI (Slave), приемник UART, входы GPIO и т.п.

Для начала попробуем понять в чем состоит проблема захвата асинхронных сигналов в ПЛИС.

Для этого в Xilinx ISE создадим проект для чипа семейства Spartan6, назовем его "syncer". Добавим в него VHDL модуль syncer.vhdl:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity syncer is
port(
  clk: in std_logic;
  async_in: in std_logic;
  sync_out: out std_logic
);
end syncer;
architecture Behavioral of async is
begin
  process(clk)
  begin
    if rising_edge(clk) then
      sync_out <= async_in;
    end if;
  end process;
end Behavioral;

Этот модуль имеет три сигнала:

  • clk - синхросигнал на котором работает ПЛИС
  • async_in - сигнал который может изменятся в абсолютно любой момент.
  • sync_out - сигнал который получается путем захвата сигнала async_in по переднему фронту синхросигнала. Действие захвата описано в процессе. По факту подобный захват в ПЛИС реализуется триггером, что мы увидим далее.

Посмотрим во что синтезировался модуль, выполнив I /O Pin Planning (Plan Ahead) - Post Synthesis :

fd_trig_capasync(1).jpg

Для чего нужны элементы IBUF и OBUF, мы вкратце рассматривали. Элемент BUFG служит для ввода в ПЛИС сигнал на специальные физические линии, которые имеют большое разветвление (fanout), что позволяет им поступать в любые точки на кристалле ПЛИС с минимальными задержками. Обычно именно такие линии используются для синхросигналов.

Основной элемент схемы - это синхронный D триггер FD. Именно он выполняет захват сигнала D на выход Q по переднему фронту сигнала С. Казалось бы все просто, и должно работать всегда - пришел передний фронт сигнала clk - триггер защелкнул сигнал async_in (сохранил его в себя), то есть сигнал зафиксирован  на выходе Q триггера, где он сохранится вплоть до следующего захвата (в данном случае захваты будут каждый период синхросигнала). Но тут есть одно большое "НО": у каждого триггера есть характеристики времени установки сигнала и удержания сигнала, которые нужно выдержать чтобы он работал правильно.

  • Время установки сигнала (Setup Time T_su) - интервал времени перед наступлением фронта, в течении которого сигнал на входе (в нашем случае это вход D) не должен меняться.
  • Время удержания сигнала (Hold Time T_hd) - интервал времени после наступления фронта, в течении которого сигнал на входе не должен меняться.

На диаграмме это можно показать так:

Красным обозначена временная зона, сигнал в которой не должен изменятся, можно назвать её зоной стабильности. А что произойдет если сигнал все же изменится в моменты времени установки или удержания? Предлагаю это увидеть на реальном примере. Но сначала нам нужно узнать чему это время равно. Для начала узнаем где наш триггер размещается физически. Для этого заходим в Analyze Timing  / Floorplan Design и найдем там наш триггер:

ologic2fortrig.jpg

Как видно он разместился на элементе типа OLOGIC2. Выбрав этот элемент слева можем посмотреть какие его входы/выходы использованы:

Как видим это пины D1 CK0 и Q. Временные характеристики элементов для семейства Spartan6 http://www.xilinx.com/support/documentation/data_sheets/ds162.pdf , там на странице 45 можем видеть Table 36: OLOGIC2 Switching Characteristics:

ologi2switchchar(2).jpg

Так как у нас используется лишь вход D, нас интересует только первая строка. Однако вы должны знать что время установки и удержание характерно не только для входа D а и для остальных входов триггеров, если они используеются (например это сигналы разрешения работы триггера CE, сигналы установки/сброса SR и т.д.). Итак мы тут видим что время установки и удержания зависит от Speed grade чипа, для самого быстрого чипа -3, который я указывал в свойстве проекта время сетапа и холда минимальные, это указывает на большее быстродействие триггера. По этому и время установки/удержания у -3 меньше.

Обратите внимание, что время удержание здесь отрицательное, а это значит что оно отсчитывается не вперед от фронта (не вправо) а назад (влево), а значит Hold time здесь выдержан будет автоматически всегда. Для чипа -3 можно представить диаграмму так:

spartan6tsuhd.jpg

Ну что ж пора переходить к эксперементам. Создадим VHDL-тестбенч async_test.vhd. Попробуем смоделировать ситуацию когда сигнал на входе D зменится в то время когда долженоставаться стабильным (то есть нарушить требования Setup/Hold). Проще всего сделать это перебором различных задержек между изменением сигнала и синхросигнала, скажем от 0.1 нс до 10нс, с шагом 0.1 нс. В общей сложности сгенерируем 100 различных задержек изменив сначала D а потом сгенеририовав фронт CLK, и еще 100 обратных - сначала изменим CLK, а потом D:

 

LIBRARY ieee;
USE ieee.std_logic_1164.ALL;
 
ENTITY async_test IS
END async_test;
 
ARCHITECTURE behavior OF async_test IS 
 
    -- Component Declaration for the Unit Under Test (UUT)
 
    COMPONENT syncer
    PORT(
         clk : IN  std_logic;
         async_in : IN  std_logic;
         sync_out : OUT  std_logic
        );
    END COMPONENT;
     
 
   --Inputs
   signal clk : std_logic := '0';
	
	signal async_in : std_logic := '0';

 	--Outputs
   signal sync_out : std_logic;
	

BEGIN
 
	-- Instantiate the Unit Under Test (UUT)
   uut: syncer PORT MAP (
          clk => clk,
          async_in => async_in,
          sync_out => sync_out
        );


   -- Stimulus process
   stim_proc: process
	variable i: integer;
   begin		
		async_in <= '0';	
		clk <= '0';
		wait for 200 ns;
		
		FOR i IN 1 to 100 LOOP
			async_in <= not async_in; 
			wait for i * 0.1 ns;
			clk <= '1';
			wait for 10 ns;	
			clk <= '0';
			wait for 10 ns;	
		end loop;
		
		
		FOR i IN 1 to 100 LOOP
			clk <= '1';
			wait for i * 0.1 ns;
			async_in <= not async_in; 
			wait for 10 ns;	
			clk <= '0';
			wait for 10 ns;	
		end loop;


      wait;
   end process;

END;

 

Только тут нужно обязательно понимать, что в тестбенче мы изменяем значения на входе схемы, но при этом на входы триггера они попадут не сразу, поскольку они должны будут сначала пройти через элементы IBUF / BUFG а также по проводникам самой ПЛИС. Кроме того задержка зменения clk на триггере может отличаться от задержки на D. Например сигнал CLK на входе триггера может оставать от сигнала на ходе схемы на 3 нс, а оставание на входе D может быть на 2 нс, именно поэтому стоит перебрать различные самые разные варианты изменения сигналов друг относительно друга, как и сделано в тестбенче.

Запустить симуляцию в режиме Post-Route. Для начала добавим на диаграмму сигналы самого триггера, для этого найдем его в окне инстансов:

И перетащим направно в окно сигналов. Затем перезапустим сессию нажав Re-launch, что бы просчитать только что добавленные сигналы триггера.

Теперь можем изучить первый период, во первых вот отставание сигнала CLK на триггера от CLK на входе:

На входе D (тут он подписан как i - IN):

А вот это и есть та самая разница которая определяет правильность работы триггера:

Тут время от изменение сигнала на I до изсенения CLK составляет 1.152 ns

Запустив симуляцию на 3 us, в консоле мы увидим самое главное - предупреждения SETUP High VIOLATION, которые говорят о том что в некоторых местах мы действительно нарушили Setup Time:

 

ISim>  run 3 us
at 2748686 ps(1), Instance /async_test/uut/sync_out_13/ : Warning: /X_SFF SETUP High VIOLATION ON I WITH RESPECT TO CLK;
  Expected := 0.895 ns; Observed := 0.85 ns; At : 2748.686 ns
at 2768986 ps(1), Instance /async_test/uut/sync_out_13/ : Warning: /X_SFF SETUP  Low VIOLATION ON I WITH RESPECT TO CLK;
  Expected := 0.895 ns; Observed := 0.75 ns; At : 2768.986 ns
at 2789386 ps(1), Instance /async_test/uut/sync_out_13/ : Warning: /X_SFF SETUP High VIOLATION ON I WITH RESPECT TO CLK;
  Expected := 0.895 ns; Observed := 0.65 ns; At : 2789.386 ns
at 2809886 ps(1), Instance /async_test/uut/sync_out_13/ : Warning: /X_SFF SETUP  Low VIOLATION ON I WITH RESPECT TO CLK;
  Expected := 0.895 ns; Observed := 0.55 ns; At : 2809.886 ns
at 2830486 ps(1), Instance /async_test/uut/sync_out_13/ : Warning: /X_SFF SETUP High VIOLATION ON I WITH RESPECT TO CLK;
  Expected := 0.895 ns; Observed := 0.45 ns; At : 2830.486 ns

На диаграмме первое нарушение видно так:

Действительно, 0.752 нс это меньше чем должно 0.81. Единственное, что для меня остается не понятным в этой истории это то как сам симулятор считает времена сетапа и холда. По этой причине я долго не публиковал статью так как скорее всего сам не до конца разобрался в его поведении. Однако не найдя ответ долгое время я все же виложил статье - возможно она поможит хотя бы понять базовые принципи. И так что же тут не понятно:

Во-первых симулятор ожидает что время сетапа должно быть 0.895 нс, хотя в документации указано 0.81 для элемента OLOGIC2 и спидгрейда -3, второе это то что он говорит что в данном случае время сетапа было 0.85, что не соответствует измеренному 0.752 маркерами на диаграмме. Вероятнее всего первое вызвано особенностями характеристик моделей примитивов в ISIM. Второе вызвано тем что точку фронта он выбрал на 2747.836 (чуть позже чем она видна на диаграмме), действительно 2748.686 - 2747.836 = 0.85, почему именно так - не знаю. Если кто-то знает ответы на эти вопросы - обязательно пишите. В любом случае мы можем полагаться только на то что симулятор действительно покажет нам возможные проблемы. В данном случае видно что выход триггера стал неопределенным, не "0", не "1", а "Х". Также мы можем заметить что на 6ую попытку когда симулятор посчитал что нарушения уже нет триггер отлично восстанавливается и дальше работает правильно.

Неопределенное состояние 'X' на выходе sync_out может испортить логику работы схемы если она будет определена за этим выходом (например если вы захотите подать на async_in тактовую кнопку а значение с async_out использовать для выполнения других действий). По этому пропускать такое значение дальше нельзя. Пример решения данной проблемы я покажу в статье про асинхронный интерфейс PCM Slave