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:

Einfachste kombinatorische Schleifen
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.
Der rückgekoppelte Invertierer bringt auch 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.

Der kombinatorische Zähler in der Simulation
In der Praxis versteckt sich die kombinatorsche Schleife jetzt wesentlich besser in einem Latch

Kombinatorischer gegateter Zähler
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
Wie eine kombinatorische Schleife gewinnbringend angewendet werden kann, findet sich unter Ringoszillator.