При захвате любых входных сигналов которые могут измениться асинхронно по отношению к основному синхросигналу микросхемы могут возникать некоторые проблемы в работе микросхемы, а точнее триггеров которые выполняют захват.  Эти проблемы связаны с переходом триггера в некое метастабильное состояние при котором данные в нем могут принять вообще стать неопределенными (не 0 и не 1) и в результате приведут к неправильной работе схемы. Особенно часто эта проблема стоит при "стыковании" микросхем по различным интерфейсам. Например сигнал RX у приемника UART может изменится в любое время произвольное по отношению к синхросигналу тактирующего конечный автомат самого приемника, что может вывести из устойчевого состояния триггер который будет захватывать этот сигнал. Также это касается интерфейсов вроде SPI Slave либо даже обычной тактовой кнопки. Безусловно понимание проблемы метастабильности требует куда более глубокого рассмотренния и должно затрагивать  принципы работы триггера и его характеристики, однако в этом посте я приведу чисто практический пример решения этой проблемы.

Пример мы будем рассмотривать на интерфейсе PCM Slave который по сути представляет из себя SPI Slave с немного хитрым сигналом CS - он не опускается на время передачи а поднимается на один или несколько первых бит передачи, и називается он тут PCM_SYNC. Для точности замечу что PCM_SYNC ведет себя так как описано выше в режиме PCM с длинной синхронизацией, но бывает еще и режим с короткой, там немного по-другому. Такой интерфейс используется для приема цифрового аудио от неких других устройств которые выступают PCM - ведущими (Master). Изменение сигналов PCM в режиме длинной синхронизации выглядит так:

 

Тут  сигналами PCM_CLK, PCM_SYNC, PCM_IN управляет мастер, а слейв (который мы будем реализововать) лишь управляет линией PCM_OUT, выставляя на ней сигнал по переднему фронту PCM_CLK. Собственно и изменение сигнала PCM_CLK может вызвать проблемы. По-этому нужно предпринять все меры что бы сенхронизировать этот сигнал с основным clk схемы (например 50 МГц), либо как еще говорят "Перенести его в тактовый домен clk". Частота PCM_CLK бывает разная у разных мастеров, я встречал 2 МГц и 256 КГц.

Для синхронизации можно использовать некоторую небольшую схемку, представляющую из себя цепочку из триггеров, при этом если первый из триггеров переходит в метастабильное состояние, последующие просто останутся в старых состояниях без изменения данных, и после неудавшегося цикла вновь получат свои значения. Реализация на VHDL такой цепочки может быть такая:

 

library ieee;
use ieee.std_logic_1164.all;

entity edge_detect is
    port (
        async_sig : in std_logic;
        clk       : in std_logic;
        rise      : out std_logic;
        fall      : out std_logic;
        rst      : in std_logic
    );
end;

architecture edge_detect_arch of edge_detect is
begin
  sync1 : process(clk)
        variable resync : std_logic_vector(1 to 3);
        attribute ASYNC_REG : string;
        attribute ASYNC_REG of resync : variable is "TRUE";
  begin
    if rising_edge(clk) then
        if rst = '1' then
            resync := (others => '0');
            rise <= '0';
            fall <= '0';
        else
            -- detect rising and falling edges.
            rise <= resync(2) and not resync(3);
            fall <= resync(3) and not resync(2);
            -- update history shifter.
            resync := async_sig & resync(1 to 2);
        end if;
    end if;
  end process;

end architecture;

Этот компонент получает асинхронный сигнал а также основной синхросигнал и активный единицей сигнал сброса, и выставляет два выходных сигнала на одни период clk (синхронизированных):

  • rise - когда асинхронный сигнал меняется из 0 в 1,
  • fall - когда асинхронный сигнал падает из 1 в 0.

Синхронизировав сигнал PCM_CLK в наш домен clk мы можем смело счтивать по его изменению линии PCM_SYNC и PCM_IN и использовать эти состояния. Cигналы PCM_SYNC и PCM_IN также можно синхронизировать, однако это не обязательно так как захватывать мы из будем когда они уже будут точно стабильными потому что захват будет при падении  PCM_CLK в ноль, а изменение этого сигнала происходит при изменении PCM_CLK в один и при частоте PCM_CLK даже 2 МГц за пол его периода пройдет кучу периодов 50 МГц периодов clk. Однако тут стоит учитывать что в условиях возникновения помех (просадок и иголок) на асинхронных линиях в моменты близкие к захвату триггер опять же может "сломаться" так как помехи так же будут асинхронными, и тут уже желательно применять и их синхронизацию тоже.

Вот код pcms (PCM Slave):

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use WORK.definitions.ALL;

entity pcms is
     Port ( 
        --io ports
        pcmclk : in std_logic; -- input clock 
      pcmsyn : in std_logic; -- input sync
        pcmso : out std_logic; -- slave output
        pcmsi : in std_logic;  --slave input
        
        -- to pld
        clk : in std_logic; 
        reset : in std_logic;
        data_in : out std_logic_vector(15 downto 0);
        data_out : in std_logic_vector(15 downto 0);
        
        done : out std_logic
    );
end pcms;

architecture Behavioral of pcms is
    
   constant TOTAL_BITS : integer := 16; 
    
   signal bit_num : integer range 0 to TOTAL_BITS - 1;
   
    type machine is (PCM_WAIT, PCM_TRANSACTION);

    signal pcmclk_r, pcmclk_f : std_logic;  
    signal state: machine;
    signal done_i : std_logic;
begin

    -- PCMCLK Clock domain synchronizers
    ed_pc: edge_detect
        PORT MAP (
            CLK => clk,
            async_sig => pcmclk,
            rise => pcmclk_r,
            fall => pcmclk_f,
            rst => reset
        );
     
        --long sync  scheme
    process (clk, reset)
     begin
            if reset = '1' then
                state <= PCM_WAIT;
                pcmso <= '0';
                done_i <= '0';
                bit_num <= TOTAL_BITS - 1;
            elsif rising_edge(clk) then
                if done_i = '1' then
                    done_i <= '0';
                end if; 
                if pcmclk_f = '1' then
                    -- pcmclk falling
                    if state = PCM_WAIT then
                        if pcmsyn = '1' then
                            state <= PCM_TRANSACTION;
                            data_in(bit_num) <= pcmsi;
                            bit_num <= bit_num - 1;
                        end if;
                    else
                        data_in(bit_num) <= pcmsi;
                        bit_num <= bit_num - 1;
                        if bit_num = 0 then
                            state <= PCM_WAIT;
                            done_i <= '1';
                            bit_num <= TOTAL_BITS - 1;
                        end if;
                    end if;
                elsif pcmclk_r = '1' then
                    pcmso <= data_out(bit_num);
                end if;
            end if; 
     end process;
 
    done <= done_i;
end Behavioral;

 Тестирование

Симуляция асинхронных сигналов идеалогически не имеет ни чего сложного - все что нужно сделать это заставить работать схему в "жестоких" условиях, в которых триггеры будут "ломаться". Добиться этого можно либо тщательно подбирая задержки согластно характеристирам триггеров конкретной ПЛИС (SETUP Time и HOLD Time), либо намного проще сгенерировав рандомные задержки и выполнив большое колличество воздействий.

Тетст выглядит так (для Xilinx ISIM):

LIBRARY ieee;
USE ieee.std_logic_1164.ALL;
USE ieee.math_real.ALL;  
USE ieee.numeric_std.ALL;
USE std.textio.all;
use ieee.std_logic_textio.all;
 
ENTITY pcms_test IS
END pcms_test;
 
ARCHITECTURE behavior OF pcms_test IS 
 
    -- Component Declaration for the Unit Under Test (UUT)
 
    COMPONENT pcms
    PORT(
         pcmclk : IN  std_logic;
         pcmsyn : IN  std_logic;
         pcmso : OUT  std_logic;
         pcmsi : IN  std_logic;
         clk : IN  std_logic;
         reset : IN  std_logic;
         data_in : OUT  std_logic_vector(15 downto 0);
         data_out : IN  std_logic_vector(15 downto 0);
         done : OUT  std_logic
        );
    END COMPONENT;
    

   --Inputs
   signal pcmclk : std_logic := '0';
   signal pcmsyn : std_logic := '0';
   signal pcmsi : std_logic := '0';
   signal clk : std_logic := '0';
   signal reset : std_logic := '0';
   signal data_out : std_logic_vector(15 downto 0) := (others => '0');

    --Outputs
   signal pcmso : std_logic;
   signal data_in : std_logic_vector(15 downto 0);
   signal done : std_logic;

   -- Clock period definitions
   constant clk_period : time := 20 ns;
 
BEGIN
 
    -- Instantiate the Unit Under Test (UUT)
   uut: pcms PORT MAP (
          pcmclk => pcmclk,
          pcmsyn => pcmsyn,
          pcmso => pcmso,
          pcmsi => pcmsi,
          clk => clk,
          reset => reset,
          data_in => data_in,
          data_out => data_out,
          done => done
        );

   clk_process :process
   begin
        clk <= '0';
        wait for clk_period/2;
        clk <= '1';
        wait for clk_period/2;
   end process;

   -- Stimulus process
   stim_proc: process
        constant TRANSACTION_WIDTH : integer := 32; -- clocks in transaction
        constant SYNC_LENGTH : integer := 8; -- clocks in transaction
        constant DATA_WIDTH : integer := 16; -- bits in data (first xx clocks)
        
        procedure emu_pcmm_transact(
            constant prd : time; 
            constant si_data : std_logic_vector (DATA_WIDTH - 1 downto 0); 
            rez_so_dat :  inout  std_logic_vector (DATA_WIDTH - 1 downto 0) ) is 
            
            variable i: integer range TRANSACTION_WIDTH - 1 downto 0;
            variable w_diff: integer;
            
        begin 
            w_diff := TRANSACTION_WIDTH - DATA_WIDTH; 
            FOR i IN TRANSACTION_WIDTH - 1 downto 0 LOOP
                if i = TRANSACTION_WIDTH - 1 then
                    pcmsyn <= '1';
                end if;
                if i >= w_diff then
                    pcmsi <= si_data(i - w_diff);
                end if;
                pcmclk <= '1';
                wait for prd / 2; 
                pcmclk <= '0';
                if i >= w_diff then
                    rez_so_dat(i - w_diff) := pcmso;
                    
                end if;
                
                wait for  prd / 2;
                
                if i = TRANSACTION_WIDTH - SYNC_LENGTH then
                    pcmsyn <= '0';
                end if;
            END LOOP;
            
            
        end procedure; 
    
        variable i: natural;
        variable seed1, seed2: positive;  
        variable rand: real;
        variable int_rand, control_int: integer; 
        variable rez_so_dat, send_to_slave :  std_logic_vector (DATA_WIDTH - 1 downto 0) ;
        
        variable str: line;
   begin        
      -- hold reset state for 100 ns.
      pcmclk <= '0';
        pcmsi <= '0'; 
        pcmsyn <= '0';
        reset  <= '1';
        i := 0;
        wait for 100 ns;    
        reset  <= '0';
        loop
            i := i + 1;
            -- random delay up to 20 ns
            UNIFORM(seed1, seed2, rand);  
            int_rand := INTEGER(TRUNC(rand*20000.0)); 
            wait for int_rand * 1 ps;

            UNIFORM(seed1, seed2, rand); 
            int_rand := INTEGER(TRUNC(rand* 2.0**DATA_WIDTH)); 
            data_out <= std_logic_vector(to_unsigned(int_rand, DATA_WIDTH));
            
            write(str, i);
            write(str, string'(" PCMS send to master: "));
            write(str, std_logic_vector(to_unsigned(int_rand, DATA_WIDTH)));
            writeline(OUTPUT, str);

            UNIFORM(seed1, seed2, rand); 
            int_rand := INTEGER(TRUNC(rand* 2.0**DATA_WIDTH)); 
            send_to_slave := std_logic_vector(to_unsigned(int_rand, DATA_WIDTH));
            write(str, i);
            write(str, string'(" PCMM send to slave: "));
            write(str, send_to_slave);
            writeline(OUTPUT, str);
            
            emu_pcmm_transact(3.90625 * 1 us, send_to_slave, rez_so_dat);
            
            write(str, i);
            write(str, string'(" PCMS recvd from master: "));
            write(str, data_in);
            writeline(OUTPUT, str);
            
            write(str, i);
            write(str, string'(" PCMM recvd from slave: "));
            write(str, rez_so_dat);
            writeline(OUTPUT, str);
            
            assert rez_so_dat = data_out report "PCMS send to master ERROR " severity error;
            assert rez_so_dat /= data_out report "OK " severity note;
            
            assert data_in = send_to_slave report "PCMM send to slave ERROR" severity error;
            assert data_in /= send_to_slave report "OK " severity note;
            
        end loop;

      wait;
   end process;

END;

Когда произойдет нарушение времени Setup или Hold ISIM напишет в консоле warning:

 

Однако исходя из контрольных проверок данные все равно были переданы и получены верно.