Eine kombinatorische Schleife ist die Rückkopplung z.B. eines Zählerausgangs auf den Zählereingang. In der trivialsten und am leichtesten erkennbaren Form sieht das z.B. so aus:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
entity KS1 is
Port ( onebit : out STD_LOGIC;
dout : out STD_LOGIC_VECTOR (3 downto 0));
end KS1;
architecture Behavioral of KS1 is
signal counter : unsigned (3 downto 0) := "0000";
signal toggle : std_logic := '0';
begin
process (counter) begin
counter <= counter+1;
end process;
toggle <= not toggle;
dout <= std_logic_vector(counter);
onebit <= toggle;
end Behavioral;
Bei der Synthese kommen ein paar Warnungen, die auf das Problem hindeuten:
WARNING:Xst:2170 - Unit KS1 : the following signal(s) form a combinatorial loop: Madd_counter6.
WARNING:Xst:2170 - Unit KS1 : the following signal(s) form a combinatorial loop: Madd_counter4.
WARNING:Xst:2170 - Unit KS1 : the following signal(s) form a combinatorial loop: Madd_counter2.
WARNING:Xst:2170 - Unit KS1 : the following signal(s) form a combinatorial loop: Madd_counter_cy<0>.
WARNING:Xst:2170 - Unit KS1 : the following signal(s) form a combinatorial loop: onebit.
Und das macht die Synthese dann daraus:
Theoretisch wäre das also ein Zähler, der mit maximaler Geschwindigkeit hochzählt, bzw. ein Inverter, der mit maximaler Geschwindigkeit vor sich hintoggelt.
In der Praxis ist ein solcher Zähler allerdings eher ein Rauschgenerator, weil die Addition etliche Glitches hervorbringt, die wiederum den nächsten Zählerzustand beeinflussen. Und der rückgekoppelte Invertierer bringt in der Realität ein eher analoges Signal hervor, das in der Praxis kaum verwendbar ist.
Sowas wird also niemand bewusst machen. Kombinatorische Schleifen entstehen allerdings oft unbeabsichtigt bei der Zwei-Prozess-Schreibweise einer FSM.
Hier ein kleines, eher praxisnahes Beispiel:
Ein Zustandsautomat mit 2 Zuständen (idle und work) soll im Zustand work auf 8 zählen. Das ist z.B. nötig für irgendeine Verzögerung. Nach Erreichen des Wertes 8 wird auf den nächsten Zustand umgeschaltet, hier wieder zurück auf idle. Getaktet wird der Folgezustand next_state auf den Zustand state abgebildet, aus dem im kombinatorischen Prozess wiederum der Folgezustand ermittelt wird.
In der Kombinatorik werden also die Weiterschaltbedingungen zwischen idle und work ausgewertet und (jetzt kommts) weil dort schon
case state is ... when work =>
steht, der Einfachheit halber gleich noch der Zähler in den Zustand work eingebaut. Heraus kommt also sowas:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
entity KS is
Port ( clk : in STD_LOGIC;
dout : out STD_LOGIC_VECTOR (3 downto 0));
end KS;
architecture Behavioral of KS is
type state_t is (idle, work);
signal state, next_state : state_t := idle;
signal counter : unsigned (3 downto 0) := "0000";
begin
process (clk) begin
if rising_edge(clk) then
state <= next_state;
end if;
end process;
process (state, counter)
begin
case state is
when idle => next_state <= work;
counter <= "0000";
when work => counter <= counter+1;
if (counter=8) then
next_state <= idle;
else
next_state <= work;
end if;
end case;
end process;
dout <= std_logic_vector(counter);
end Behavioral;
Bei der Synthese werde ich nun wieder freundlich mit einer Warnung auf meinen Fehler hingewiesen:
WARNING:Xst:2170 - Unit KS : the following signal(s) form a combinatorial loop: Madd_counter_addsub00006, counter_addsub0000<3>.
WARNING:Xst:2170 - Unit KS : the following signal(s) form a combinatorial loop: Madd_counter_addsub00004, counter_addsub0000<2>.
WARNING:Xst:2170 - Unit KS : the following signal(s) form a combinatorial loop: counter_addsub0000<1>, Madd_counter_addsub00002.
WARNING:Xst:2170 - Unit KS : the following signal(s) form a combinatorial loop: Madd_counter_addsub0000_cy<0>.
Der Simulator sagt dann aber schon deutlicher, dass es so nicht geht:
# ** Error: (vsim-3601) Iteration limit reached at time 10 ns.
Was ist passiert?
Weil der Zähler in den kombinatorischen Prozess eingebaut wurde, und zum Glück die Sensitiv-Liste des Prozesses vollständig ist, wird bei der ersten steigenden Flanke von clk der Zustand work auf statework übernommen. Im Zustand zählt der Zähler counter um eins hoch. Nach Abarbeiten des Prozesses wird erkannt, dass sich counter geändert hat. Bei Erreichen des Wertes 8 wird der Folgezustand idle auf next_state geschrieben.
Allerdings hat sich in diesem Durchlauf counter wieder geändert, was eine sofortige Neuberechnung des kombinatorischen Prozesses nach sich zieht. Also wird wieder gerechnet counter <= counter+1; und das geht bis zum bitteren Ende so weiter...
Wenn man sich das Syntheseergebnis im Schaltplan ansieht, erkennt man die kombinatorische Rückkopplung sofort wieder.
Die Wahrscheinlichkeit, dass hier beim nächsten Takt richtig weitergemacht wird (also bei idle) liegt bestenfalls bei 1:16.
Interessant wird es, wenn das Hochzählen in die if-Abfrage verlagert wird:
when work => if (counter=8) then
next_state <= idle;
else
counter <= counter+1;
next_state <= work;
end if;
Dann wird in der Simulation mit jedem Takt zwischen den Zuständen idle und work hin- und hergeschaltet, allerdings tauchen die counter-Werte 1-7 niemals auf. Sie werden in der (theoretischen) Zeit 0 durchlaufen.
In der Praxis versteckt sich die kombinatorsche Schleife jetzt wesentlich besser in einem Latch
WARNING:Xst:737 - Found 4-bit latch for signal <counter>
Allerdings wird sich dieses Design in der Realität nicht so verhalten wie in der Simulation,
weil hier Glitches und Laufzeitverzögerungen einen Strich durch die Rechnung machen werden.
Wie Zähler in der Zwei-Prozess-Schreibweise richtig aussehen müssen, ist zu finden im Kapitel
Ein- oder Zwei-Prozess-Schreibweise von FSM
Darüber hinaus sind auch Variablen gern mal die Ursache für eine kombinatorische Schleife. Nehmen wir mal eine Variable cnt, die hier die Anzahl der im Vektor data gesetzten Bits zählen soll:
data : in std_logic_vector(3 downto 0) bits : out std_logic_vector(2 downto 0) process (data) variable cnt : integer range 0 to 4 := 0; begin -- cnt := 0; -- hoppala, vergessen for i in 0 to 3 loop if data(i)='1' then cnt := cnt + 1; end if; end loop; bits <= std_logic_vector(to_unsigned(cnt,3)); end process;
Hier wurde vergessen, die Variable cnt vor Verwendung auf 0 zu setzen. Diese Variable muss also speichernd sein, weil auf sie im Verlauf des Prozesses mit cnt+1 zuerst lesend zugegriffen wird. Weil die Variable nicht in die Sensitivliste aufgenommen werden kann, sieht die Simulation erst mal noch ganz gut und sogar richtig aus. Nur die Synthese meckert:
WARNING:Xst:2170 - Unit bitcounter :
the following signal(s) form a combinatorial loop: cnt<0>, cnt<1>, cnt<2>
Zum Hintergrund verweise ich auf den Beitrag "Variable vs. Signal" auf dem uC.net.
Eine Möglichkeit, eine kombinatorische Schleife gewinnbringend anzuwenden, findet sich unter Ringoszillator.