Aller au contenu

Logique concurrente et séquentielle

La compétence la plus importante en VHDL est de prévoir le matériel inféré par chaque patron de code.

Logique combinatoire

Une sortie combinatoire dépend uniquement des entrées présentes et ne mémorise rien.

Affectation directe

y_o <= (a_i and b_i) or c_i;

Processus complet

process(all)
begin
  y_o <= '0'; -- valeur par défaut
  if enable_i = '1' then
    y_o <= a_i xor b_i;
  end if;
end process;

La valeur par défaut couvre tous les chemins. L'affectation suivante devient prioritaire lorsque la condition est vraie.

Latch accidentel

process(all)
begin
  if enable_i = '1' then
    y_o <= data_i;
  end if;
end process;

Quand enable_i='0', le processus demande de conserver la valeur précédente. Cette mémorisation exige un latch transparent.

Warning

Les latches ne sont pas interdits, mais ils sont rarement voulus dans un premier projet FPGA et compliquent le timing. Pour mémoriser un état, préférez un registre sur front.

Logique cadencée

process(clk_i)
begin
  if rising_edge(clk_i) then
    q_o <= d_i;
  end if;
end process;

Reset synchrone et validation

process(clk_i)
begin
  if rising_edge(clk_i) then
    if reset_i = '1' then
      count_q <= (others => '0');
    elsif enable_i = '1' then
      count_q <= count_q + 1;
    end if;
  end if;
end process;

Les priorités sont reset, validation, puis conservation implicite.

Reset asynchrone

process(clk_i, reset_i)
begin
  if reset_i = '1' then
    q_o <= '0';
  elsif rising_edge(clk_i) then
    q_o <= d_i;
  end if;
end process;

Il agit sans attendre l'horloge. Sa désactivation près d'un front peut violer les temps de recovery/removal : on préfère souvent un reset entièrement synchrone ou une assertion asynchrone avec libération synchronisée.

Validation plutôt qu'horloge créée dans la logique

Évitez :

slow_clock <= divider_q(25);
process(slow_clock)
begin
  if rising_edge(slow_clock) then
    -- ...
  end if;
end process;

Cette écriture transforme un signal logique en horloge et crée un nouveau domaine.

Préférez :

process(clk_i)
begin
  if rising_edge(clk_i) then
    if tick_i = '1' then
      -- L'état avance tout en utilisant la vraie horloge.
    end if;
  end if;
end process;

Sémantique des mises à jour

process(clk_i)
begin
  if rising_edge(clk_i) then
    first_q  <= data_i;
    second_q <= first_q;
  end if;
end process;

Les deux expressions de droite utilisent les valeurs antérieures au front. Deux étages de pipeline sont correctement inférés.

Une variable permet un calcul intermédiaire immédiat :

process(clk_i)
  variable next_count : unsigned(count_q'range);
begin
  if rising_edge(clk_i) then
    next_count := count_q;
    if enable_i = '1' then
      next_count := next_count + 1;
    end if;
    count_q <= next_count;
    zero_q  <= '1' when next_count = 0 else '0';
  end if;
end process;

Une variable n'est pas forcément du matériel combinatoire : c'est la manière dont elle est affectée et utilisée qui détermine le circuit.

Pilotes multiples

Un std_logic résolu accepte techniquement plusieurs pilotes, mais deux valeurs opposées donnent X en simulation et ne conviennent généralement pas à la logique interne.

Règle pratique :

Chaque signal RTL interne appartient à un seul processus ou à une seule affectation concurrente.

Construisez un multiplexeur explicite lorsqu'il faut choisir une source.

Cycles delta

Une affectation sans délai est prévue pour un cycle delta ultérieur au même instant de simulation. Les cycles delta permettent aux processus concurrents de réagir sans faire avancer le temps physique.

Pour un DUT combinatoire, un petit délai non nul comme wait for 1 ns rend le test clair. Pour un DUT synchrone, alignez les vérifications sur l'horloge et tenez compte du delta suivant la mise à jour des registres.

Patrons

Combinatoire :

process(all)
  variable result_v : unsigned(result_o'range);
begin
  result_v := (others => '0');
  case operation_i is
    when OP_ADD  => result_v := a_i + b_i;
    when OP_XOR  => result_v := a_i xor b_i;
    when OP_PASS => result_v := a_i;
    when others  => null;
  end case;
  result_o <= result_v;
end process;

Séquentiel :

process(clk_i)
begin
  if rising_edge(clk_i) then
    if reset_i = '1' then
      state_q <= RESET_VALUE;
    else
      state_q <= state_d;
    end if;
  end if;
end process;

Relecture

  • Puis-je nommer les portes et registres attendus ?
  • Chaque sortie combinatoire est-elle affectée sur tous les chemins ?
  • Chaque état mémorisé appartient-il à un processus cadencé clair ?
  • Existe-t-il plusieurs pilotes ?
  • Chaque horloge est-elle distribuée par les ressources dédiées ?
  • Ai-je utilisé une validation au lieu de fabriquer une horloge lente ?
  • Le comportement du reset est-il défini et testé ?