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:
Simulation: counter mit delay
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