Immer wieder kommt die Diskussion auf, ob denn jetzt die Ein- oder Zwei-Prozess-Darstellung von Zustandsautomaten einfacher zu verstehen und implementieren sei.
Wenn man aus der schulmäßigen Logik herkommt, dann wird ein Automat auf eine Weiterschaltlogik (die den Folgezustand ermittelt) und einen Zustandsspeicher abgebildet.
Es wird also eine Abbildung in zwei Prozesse nahegelegt:
1) der eine Prozess übernimmt taktgesteuert den Folgezustand, der
2) vom anderen Prozess aus den Eingangssignalen und/oder dem aktuellen Zustand ermittelt wird.
Damit könnte ein einfacher Automat mit den Zuständen
reset,
idle,
work und
done so aussehen:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
entity FSM is
Port ( clk : in STD_LOGIC;
rst : in STD_LOGIC;
start : in STD_LOGIC;
stop : in STD_LOGIC;
dout : out STD_LOGIC_VECTOR (3 downto 0));
end FSM;
architecture Verhalten of FSM is
type state_t is (reset, idle, work, done);
signal state, next_state : state_t := idle;
signal counter, next_counter : unsigned(3 downto 0) := "0000";
begin
process begin
wait until rising_edge(clk);
state <= next_state;
counter <= next_counter;
end process;
process (state, rst, start, stop, delay, counter)
begin
next_counter <= (others=>'0');
case state is
when reset => next_state <= idle;
when idle => if (start='1') then
next_state <= work;
end if;
when work => if (stop='1') then
next_state <= done;
end if;
next_counter <= counter+1;
when done => if (stop='0') then
next_state <= idle;
end if;
end case;
if (rst='1') then
next_state <= reset;
end if;
end process;
dout <= std_logic_vector(counter);
end Verhalten;
Aus dem Zustand
idle kommend, wird mit dem Signal
start der Zustand
work angesprungen. Im Zustand
work wird der Zähler solange hochgezählt, bis das Signal
stop aktiv wird. Danach geht es über den Zustand
done zurück nach
idle. Wird der Eingang
rst aktiv, so geht es ohne Umleitung in den Zustand
reset. Parallel wird der Zähler
counter concurrent auf
dout ausgegeben.
Weil ein Zähler auch nur eine FSM ist, muß für das Hochzählen von
counter in der Kombinatorik der Folge-Zählerstand
next_counter ermittelt werden. Dieser wird dann mit dem nächsten Takt auf
counter übernommen.
Bis hierher war es eigentlich noch übersichtlich.
Jetzt möchte ich aber gerne das Hochzählen von
counter im Zustand
work etwas gemächlicher gestalten. Ein Verzögerungszähler für 10 Takte
delay (von 0 bis 9) vor dem Hochzählen von
counter muß her.
Jetzt könnte ich das Spiel genauso wie mit
counter und
next_counter machen, aber diesmal bin ich zu faul und nehme den Verzögerungszähler
delay einfach gleich in den getakteten Teil:
architecture Verhalten of FSM is
type state_t is (reset, idle, work, done);
signal state, next_state : state_t := idle;
signal delay : integer range 0 to 9 := 0;
signal counter, next_counter : unsigned(3 downto 0) := "0000";
begin
process begin
wait until rising_edge(clk);
state <= next_state;
counter <= next_counter;
if(state=idle) then -- in idle: Delay-Zähler zurücksetzen
delay <= 0;
end if;
if(state=work) then -- in work: hier wird gezählt
if (delay < 9) then -- solange kleiner 9:
delay <= delay+1; -- zählen
else
delay <= 0; -- sonst zurücksetzen
end if;
end if;
end process;
process (state, rst, start, stop, delay, counter)
begin
next_counter <= (others=>'0');
case state is
when reset => next_state <= idle;
when idle => if (start='1') then
next_state <= work;
end if;
when work => if (delay = 9) then -- wenn das Delay abgelaufen ist:
next_counter <= counter+1; -- counter um eins hochzählen
end if;
if (stop='1') then
next_state <= done;
end if;
when done => if (stop='0') then
next_state <= idle;
end if;
end case;
if (rst='1') then
next_state <= reset;
end if;
end process;
dout <= std_logic_vector(counter);
end Verhalten;
Fertig. Hurra, die Simulation zeigt, dass es geht:
Aber wer sich den Code ein halbes Jahr später anschaut, wird den Kopf schütteln, und sich fragen, wie und was da wann wozu verwendet wird.
Ich schreibe deshalb meine State-Machines nur noch in der Ein-Prozess-Darstellung. In dieser Darstellungsart wandert einfach nur die Weiterschaltlogik in den getakteten Teil. Das obige Beispiel sieht also so aus:
architecture Verhalten of FSM is
type state_t is (reset, idle, work, done);
signal state : state_t := idle;
signal delay : integer range 0 to 9 := 0;
signal counter : unsigned(3 downto 0) := "0000";
begin
process begin
wait until rising_edge(clk);
case state is
when reset => counter <= (others=>'0');
state <= idle;
when idle => delay <= 0;
if (start='1') then
state <= work;
end if;
when work => if (delay < 9) then -- solange kleiner 9:
delay <= delay+1; -- zählen
else -- sonst:
delay <= 0; -- zurücksetzen
counter <= counter+1; -- und counter hochzählen
end if;
if (stop='1') then
state <= done;
end if;
when done => if (stop='0') then
state <= idle;
end if;
end case;
if (rst='1') then
state <= reset;
end if;
end process;
dout <= std_logic_vector(counter);
end Verhalten;
Diese Ein-Prozess-Beschreibung ergibt genau das selbe Simulationsergebnis, sie hat aber deutliche Vorteile:
1) Deutlich weniger Schreibaufwand und bessere Übersicht
2) Ich brauche weniger Signale (next_counter, next_state)
3) Das Ganze ist garantiert synchron
4) Die Ausgangssignale sind registriert und damit synchron
5) Es werden garantiert keine
Kombinatorischen Schleifen erzeugt