Forum: FPGA, VHDL & Co. State Machine in VHDL - Design


von Daniel K. (daniel_k80)


Lesenswert?

Hallo zusammen,

ich habe ein mehr oder weniger großes Verständigungsproblem bei State 
Machines in VHDL. Im Studium haben wir dieses Design gelernt.
1
library ieee;
2
use ieee.std_logic_1164.all;
3
4
entity moore_4s is
5
6
  port(
7
    clk     : in  std_logic;
8
    data_in   : in  std_logic;
9
    reset   : in  std_logic;
10
    data_out   : out  std_logic_vector(1 downto 0)
11
  );
12
  
13
end entity;
14
15
architecture rtl of moore_4s is
16
17
  type state_type is (s0, s1, s2, s3);
18
  
19
  signal state   : state_type;
20
21
begin
22
  process (clk, reset)
23
             variable next : state_type;
24
  begin
25
    if reset = '1' then
26
      next := s0;
27
    elsif (rising_edge(clk)) then
28
      case state is
29
        when s0=>
30
          if data_in = '1' then
31
            next := s1;
32
          else
33
            next := s0;
34
          end if;
35
        when s1=>
36
          if data_in = '1' then
37
            next := s2;
38
          else
39
            next := s1;
40
          end if;
41
        when s2=>
42
          if data_in = '1' then
43
            next := s3;
44
          else
45
            next := s2;
46
          end if;
47
        when s3 =>
48
          if data_in = '1' then
49
            next := s0;
50
          else
51
            next := s3;
52
          end if;
53
      end case;
54
    end if;
55
                state <= next;
56
  end process;
57
  
58
  -- Output depends solely on the current state
59
  process (state)
60
  begin
61
  
62
    case state is
63
      when s0 =>
64
        data_out <= "00";
65
      when s1 =>
66
        data_out <= "01";
67
      when s2 =>
68
        data_out <= "10";
69
      when s3 =>
70
        data_out <= "11";
71
    end case;
72
  end process;
73
  
74
end rtl;

Dieses Design habe ich nun mal übernommen und mein Design funktioniert 
jetzt in der Simulation wunderbar, aber wenn ich dann eine 
Post-Implementation Timing Simulation mache, stimmen die übertragenen 
Daten nicht mehr. Bei der Synthese bekomme ich zudem die Meldung

[Synth 8-327] inferring latch for variable 'TLAST_reg'

Wobei das Signal im nicht getakteten Teil der State Machine steht (also 
im Ausgangsnetzwerk). Die Ursache der Warnung verstehe ich schon, nur 
frage ich mich gerade ob dieser Designentwurf für State Machines 
überhaupt sinnvoll oder eher veraltet ist, da diese Warnung bei jedem 
Ausgangssignal auftritt, welches im Ausgangsnetzwerk geschaltet wird.

: Bearbeitet durch User
von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Daniel K. schrieb:
> Im Studium haben wir dieses Design gelernt.
Übel, sowas...

Probiers mal so:
1
  process (clk, reset)
2
             variable next : state_type;
3
  begin
4
5
    next := state; --<<< Defaultzuweisung an die Variable muss sein!!!
6
7
    if reset = '1' then
8
      next := s0;
9
    elsif (rising_edge(clk)) then

Und was lernen wir daraus: lass die ersten paar Monate Variablen aus dem 
Spiel. Denn durch diese Variable wird das hier zum wilden Hack, weil im 
selben Prozess völlig unerwartet ausserhalb des getakteten Bereichs noch 
Zuweisung erfolgen. Der Synthesizer müsste einem sowas als Fehler 
ankreiden.


> nur frage ich mich gerade ob dieser Designentwurf für State Machines
> überhaupt sinnvoll oder eher veraltet ist
Es ist irgendein zusammengeflickter akademischer Ansatz. Im echten Leben 
sieht bei mir eine FSM z.B. so aus wie im 
Beitrag "Re: Hilfe für Code - VHDL" die 
Kaffeeautomat_FSM0b.vhd

: Bearbeitet durch Moderator
von Daniel K. (daniel_k80)


Angehängte Dateien:

Lesenswert?

Es ist halt gerade ziemlich frustrierend, weil anscheinend auch noch 
falsche Zustände in der Timing-Analyse dazu kommen (siehe Bild) :)

Ich schau mir dein Beispiel mal an, werf meinen Code in die Tonne und 
mach es neu ^^

von Sigi (Gast)


Lesenswert?

Gute Entscheidung,es neuzumachen:
1. signal state : state_type := S0; --<<< Startzustand!
(oder beliebigen anderen Startzustand). Muss nicht
sein, aber heutzutage motzt da kein Tool mehr.
2. Reset innerhalb des RisingEdge-Blocks
(bei einigen Herstellern ist es egal (Altera),
bei anderen ist ein syncr. Reset besser und wird
glaube ich auch von allen Herstellern akzeptiert)
3. Die next-Variable kann komplett weggelassen
und durch das state-Signal ersetzt werden
(aber mit der Zuweisung <= statt :=)
4. die next-Zuweisung am Schluss ist ja wohl das
gefährlichste, inklusive der fehlenden
Default-Zuweisung ein Latch-Generator!
5. Der nachfolgende Block für die IO-Zuweisung
ist asynchron (die asynchr. next-Zuweisung im
ersten Prozess macht's auch nicht schlechter),
daher deine illegalen Ausgabezustände.
Wandle diesen Prozess auch ich einen synchronen um.

Insgesammt: Lerne auf jeden Fall die Formulierung
von Mealy/Moore/Medwedew-FSMs in Ein- als auch
Zweiprozessschreibweise, und zwar in und auswendig.
Sowas muss in der Praxis blind sitzen!

von Daniel K. (daniel_k80)


Lesenswert?

Hab auch gerade bei Intel ein paar Templates gefunden:

https://www.intel.com/content/www/us/en/programmable/support/support-resources/design-examples/design-software/vhdl/vhd-state-machine.html

Ggf. noch für den ein oder anderen recht interessant...

von Sigi (Gast)


Lesenswert?

.. und was ich noch vergessen habe:
Nach der Substitution von next durch state
sind in deinem Code alle Zuweisungen im
ELSE-Teil innerhalb des CASE-Blocks
überflüssig. state ist ja registriert.

Mein Punkt 5 ist natürlich gefährlich,
denn er führt ja zu einer Latanz von einem
Takt!

Zu den Intel-Templates: Das Mealy-Beispiel
ist ja grausam: einmal steht da
1
  ..
2
  if data_in = '1' then
3
    state <= s1;
4
  else
5
    state <= s0;
6
  end if;
7
  ..
(fur S0,S1,S2), und für S3
1
  ..
2
  if data_in = '1' then
3
    state <= s3;
4
  else
5
    state <= s1;
6
  end if;
7
  ..
statt besser und damit einheitlich
1
  ..
2
  if data_in /= '1' then
3
    state <= s1;
4
  else
5
    state <= s3;
6
  end if;
7
  ..
und damit lassen sich wieder die ELSE-Teile
weglassen. Ist einfach viel lesbarer/weniger
irreführend (find' ich zumindestens).

von Daniel K. (daniel_k80)


Lesenswert?

Hallo,

ich habe noch etwas weiter geschaut und sogar ein Beispiel von Xilinx 
für FSM gefunden (ich nutze selber Xilinx).

https://www.xilinx.com/support/documentation/university/Vivado-Teaching/HDL-Design/2015x/VHDL/docs-pdf/lab10.pdf

Xilinx nutzt dort einen asynchronen Prozess für die Ausgabe. Jetzt ist 
auch da wieder die Frage: Ist das sinnvoll? Immerhin empfiehlt es 
Xilinx?

Und dann noch eine Frage am Rande...
Ich bin gerade auch am Überlegen welcher Automat für meine Anwendung 
sinnvoll ist. Ich möchte ein ROM (BRAM) mit einem AXI4-Stream Interface 
ausstatten. Das ganze Ding soll nachher als Master an was anderes dran 
gemacht werden und Daten senden (u. a. zum Debuggen in der Hardware, 
weshalb die Stream Verification IPs leider weg fallen). Für die 
Implementierung des Stream-Interfaces wollte ich dann den 
Zustandsautomaten nehmen. Wenn ich es jetzt richtig verstanden habe, 
muss es sich dabei um einen Mealy-Automaten handeln, da der Zustand des 
Automaten für den Bus auch von dem TREADY-Signal des Slaves abhängt. 
Sehe ich das so richtig?

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Daniel K. schrieb:
> Xilinx nutzt dort einen asynchronen Prozess für die Ausgabe. Jetzt ist
> auch da wieder die Frage: Ist das sinnvoll?
Ist halt umständlich. Und je nach Geschmack des Entwicklers.

> Ist das sinnvoll?
Wenns drauf ankommt, dann implementiere einfach mal beide Varianten und 
vergleiche das Syntheseergebnis.

> Immerhin empfiehlt es Xilinx?
In irgendeinem Lab-Workbook für VHDL-Anfänger...

Man darf zudem nicht alles bedenkenlos glauben, was irgendwer irgendwo 
schreibt:
Beitrag "Re: Variable vs Signal"

> Immerhin empfiehlt es Xilinx?
Fazit: bei FSM muss jeder seinen Stil finden. Die suche kann dabei 
durchaus ein paar Jahre dauern. Ich schäme mich heute fast, wenn ich 
meine ersten Designs so ansehe... ;-)

von daniel__m (Gast)


Lesenswert?

Lothar M. schrieb:
> bei FSM muss jeder seinen Stil finden.

Hi,

wobei der Stil auch von den Vorgaben abhängt. "Langsame" FSMs mache ich 
oft in einem synchronen Process (Moore FSM). Jedoch habe ich oft mit 
Handshaking-Signalen zu tun und möchte aus Performance-Gründen 
"back-to-back" übertragen, dann brauche ich eine Mealy FSM, machmal im 
2-Process-Stil, machmal 1-Process + concurrent Statements, jenachdem was 
dokumentativer bzw. verständlicher ist.

von Sigi (Gast)


Lesenswert?

Daniel K. schrieb:
> ich habe noch etwas weiter geschaut und sogar ein Beispiel von Xilinx
> für FSM gefunden (ich nutze selber Xilinx).
>
> 
https://www.xilinx.com/support/documentation/university/Vivado-Teaching/HDL-Design/2015x/VHDL/docs-pdf/lab10.pdf
>
> Xilinx nutzt dort einen asynchronen Prozess für die Ausgabe. Jetzt ist
> auch da wieder die Frage: Ist das sinnvoll? Immerhin empfiehlt es
> Xilinx?

Als kleines Katastrophenbeispiel sicherlich..
1. Beispiel: Wie schon Oben von mir beschrieben
können die ELSE-Teile im Case-Block entfallen
(enfach durch next_state <= state als Default-Zuweisung
ersetzen).
2. Beispiel: next_state <= S0: grausam! Du hast hier
eine 3-Prozess-Schreibweise. Kombinatorische Prozesse
dienen eiglich der Bestimmung des nächsten Zustands,
falls sich nichts ändert, dann bleibt der aktuelle
Zustand. Also ist die Default-Zuweisung next_state<=state
die beste Wahl und nicht next_state<=S0.
3. "when others"-Fälle sind bei selbstdefinierten Datentypen
total überflüssig.
4. Startzustand-Zuweisung fehlt in der Deklaration
(akzeptiert jedes Tool, seit wann eigentlich? 15 Jahren?)

Standard-Frame für die 1-Prozess-Schreibweise:
1
type FSM is (S0,S1);
2
3
const STATE_START  : FSM := S0;
4
const STATE_OUTSIG : std_logic := '0';
5
6
signal state  : FSM := STATE_START;
7
signal outsig : std_logic := STATE_OUTSIG;
8
9
process(clk)
10
  if rising_edge(clk) then
11
    if reset = '1' then
12
      state  <= STATE_START;
13
      outsig <= STATE_OUTSIG;
14
    else
15
      case reg_state is
16
        when S0 => -- State S0
17
          if input = ..whatever.. then 
18
            state  <= S1;
19
            outsig <= '1';
20
          else
21
            outsig <= '0';
22
          endif;
23
        when S1 => -- State S1
24
          if input = ..whatever.. then 
25
            state  <= S0;
26
            nxt_outsig <= '0';
27
          else
28
            outsig <= '1';
29
          endif;
30
      end case;
31
    endif
32
  endif
33
end process;

Standard-Frame für die 2-Prozess-Schreibweise:
1
type FSM is (S0,S1);
2
3
const STATE_START  : FSM := S0;
4
const STATE_OUTSIG : std_logic := '0';
5
6
signal reg_state  : FSM := STATE_START;
7
signal nxt_state  : FSM; 
8
signal reg_outsig : std_logic := STATE_OUTSIG;
9
signal nxt_outsig : std_logic;
10
11
process(clk)
12
  if rising_edge(clk) then
13
    if reset = '1' then
14
      reg_state  <= STATE_START;
15
      reg_outsig <= STATE_OUTSIG;
16
    else
17
      reg_state  <= nxt_state;
18
      reg_outsig <= nxt_outsig;
19
    endif
20
  endif
21
end process;
22
23
process(reg_state,reg_outsig,input)
24
begin
25
  -- default assignments
26
  nxt_state  <= reg_state;
27
  nxt_outsig <= reg_outsig;
28
29
  case reg_state is
30
    when S0 => -- State S0
31
      if input = ..whatever.. then 
32
        nxt_state  <= S1;
33
        nxt_outsig <= '1';
34
        mealy_out  <= "01";
35
      else
36
        nxt_outsig <= '0';
37
        mealy_out  <= "10";
38
      endif;
39
    when S1 => -- State S1
40
      if input = ..whatever.. then 
41
        nxt_state  <= S0;
42
        nxt_outsig <= '0';
43
        mealy_out  <= "11";
44
      else
45
        nxt_outsig <= '1';
46
        mealy_out  <= "00";
47
      endif;
48
  end case;
49
end process;
50
51
moore_out <= reg_outsig;

von Sigi (Gast)


Lesenswert?

..ups, es fehlt noch die Default-Mealy-Zuweisung
in der 2-Prozess-Schreibweise
1
  ..
2
  -- default assignments
3
  nxt_state  <= reg_state;
4
  nxt_outsig <= reg_outsig;
5
6
  -- default mealy assignments
7
  mealy_out <= "00"; -- or whatever
8
  ..

.. und es ist natürlich überflüssig zu erwähenen,
dass in der 1-Prozess-Schreibweise keine
Mealy-Signale beschrieben werden können. Dafür
ist zu dem 1-Prozess ein zusätzlicher kombinatorischer
Prozess bzw. concurrent-Zuweisungen erforderlich.

von Daniel K. (daniel_k80)


Lesenswert?

Danke für die Anmerkungen. Dann werde ich das Dokument mal schnell 
wieder schließen :)
Ich habe nun mal den ersten Teil meines verpfuschten Projektes neu 
gemacht und mich dabei an das Kaffeeautomatbeispiel von Lothar 
orientiert.
1
library IEEE;
2
use IEEE.STD_LOGIC_1164.ALL;
3
use IEEE.NUMERIC_STD.ALL;
4
5
entity AXI4S_ROM is
6
    Port ( ACLK     : in STD_LOGIC;
7
           ARESETN  : in STD_LOGIC;
8
           
9
           -- AXI4S interface
10
           TDATA    : out STD_LOGIC_VECTOR(15 downto 0);
11
           TID      : out STD_LOGIC_VECTOR(7 downto 0);
12
           TREADY   : in STD_LOGIC;
13
           TVALID   : out STD_LOGIC;
14
           TLAST    : out STD_LOGIC
15
           );
16
end AXI4S_ROM;
17
18
architecture AXI4S_ROM_Arch of AXI4S_ROM is
19
20
    type STATE_t is (Reset, EndOfReset, WaitForReady, SetData, WaitForHandShake);
21
    signal CurrentState : STATE_t := Reset;
22
23
    signal Address      : INTEGER := 0;
24
25
    signal ROM_Address  : STD_LOGIC_VECTOR(10 downto 0) := (others => '0');
26
    signal ROM_Data     : STD_LOGIC_VECTOR(15 downto 0) := (others => '0');
27
    signal DataBuffer   : STD_LOGIC_VECTOR(15 downto 0) := (others => '0');
28
29
    component ROM is
30
        Port (  Address : in STD_LOGIC_VECTOR(10 downto 0);
31
                Clock : in STD_LOGIC;
32
                DataOut : out STD_LOGIC_VECTOR(15 downto 0)
33
                );
34
    end component;
35
36
begin
37
38
    DataROM : ROM port map (Address => ROM_Address,
39
                            DataOut => ROM_Data,
40
                            Clock => ACLK
41
                            );
42
43
    process(ACLK, ARESETN, TREADY, DataBuffer, CurrentState, Address)
44
    begin
45
        if(rising_edge(ACLK)) then
46
            case CurrentState is
47
48
                when Reset =>
49
                    TVALID <= '0';
50
                    Address <= 0;
51
52
                    if      ARESETN = '1' then CurrentState <= EndOfReset;
53
                    elsif   ARESETN = '0' then CurrentState <= Reset;
54
                    end if;
55
56
                when EndOfReset =>
57
                    CurrentState <= WaitForReady;
58
59
                when WaitForReady =>
60
                    TVALID <= '0';
61
                    DataBuffer <= ROM_Data;
62
63
                    -- Wait until TREADY from the slave
64
                    if      TREADY = '1' then CurrentState <= WaitForReady;
65
                    else    CurrentState <= SetData;
66
                    end if;
67
                    
68
                when SetData =>
69
                    TVALID <= '1';
70
                    TDATA <= DataBuffer;
71
                    TID <= STD_LOGIC_VECTOR(to_unsigned(Address, 8));
72
                    
73
                    -- Set TLAST to indicate the end of the package
74
                    if      Address = 99 then TLAST <= '1';
75
                    else    TLAST <= '0';
76
                    end if;
77
78
                    -- Reset the address counter if the end is reached
79
                    if      Address < 99 then Address <= Address + 1;
80
                    else    Address <= 0;
81
                    end if;
82
                    
83
                    CurrentState <= WaitForHandShake;
84
85
                when WaitForHandShake =>
86
                    
87
                    -- Wait until the slave has handshaked the data, signaled by pulling TREADY high
88
                    if      TREADY = '1' then CurrentState <= WaitForReady;
89
                                              TVALID <= '0';
90
                    end if;
91
92
            end case;
93
        end if;
94
    end process;
95
96
    ROM_Address <= STD_LOGIC_VECTOR(to_unsigned(Address, ROM_Address'length));
97
98
end AXI4S_ROM_Arch;

Das Ziel war es hier ein ROM mit einem AXI-Stream Interface 
auszustatten, damit mir das ROM als Datenquelle für einen AXI-Stream 
Slave dienen kann. Getestet habe ich das Ding mit der Stream 
Verification IP von Xilinx und da scheint es (in der normalen) 
Simulation zu funktionieren. Eine Post-Implementation Timing Simulation 
kann ich mit der Testbench aber leider nicht machen, da dort der 
Verification-Core nicht funktioniert - was in meinen Augen irgendwie 
Blödsinn ist. Ich dachte die Post-Implementation Timing Simulation nimmt 
die Testbench und steckt diese Signale in das erzeugte Design...

: Bearbeitet durch User
von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Daniel K. schrieb:
> process(ACLK, ARESETN, TREADY, DataBuffer, CurrentState, Address)
Da muss nur ACLK rein. Denn nur bei einer Änderung von ACLK muss der 
Simulator den Prozess neu berechnen. Der Synthesizer schert sich sowieso 
nicht um die Sensitvliste.

> Eine Post-Implementation Timing Simulation kann ich mit der Testbench
> aber leider nicht machen
Du brauchst bei einem korrekten synchronem Design (asynchrone externe 
signale werden über 2 FF einsynchronisiert) und korrekten Constraints 
keine Timing-Simulation. Sie bringt keinen Erkenntnisgewinn. Ich z.B. 
habe seit Jahren keine mehr gemacht.

Ein Wort zum Thema Latency.
Dein Kommentar hier lässt vermuten, dass das Signal TLAST aktiv sein 
soll, wenn die Adresse=99 ist:
1
                    -- Set TLAST to indicate the end of the package
2
                    if      Address = 99 then TLAST <= '1';
3
                    else    TLAST <= '0';
4
                    end if;
5
6
                    -- Reset the address counter if the end is reached
7
                    if      Address < 99 then Address <= Address + 1;
8
                    else    Address <= 0;
9
                    end if;
Das wird nicht der Fall sein, weil mit dem selben Takt, mit dem TLAST 
gesetzt wird, auch der Adress Zähler auf 0 gesetzt wird. TLAST ist also 
genau dann aktiv, wenn Adress 0 ist.

Du erkennst es recht einfach, wenn du den Code mal umschreibst:
1
                    -- Set TLAST to indicate the end of the package
2
                    if      Address = 99 then TLAST <= '1';
3
                    else    TLAST <= '0';
4
                    end if;
5
6
                    -- Reset the address counter if the end is reached
7
                    if      Address = 99 then Address <= 0;
8
                    else     Address <= Address + 1;
9
                    end if;
Was ja gleich ist wie das hier:
1
                    -- Set TLAST to indicate the end of the package
2
                    -- And reset the address counter if the end is reached
3
                    if      Address = 99 then 
4
                        TLAST <= '1';  Address <= 0;
5
                    else
6
                        TLAST <= '0';  Address <= Address + 1;
7
                    end if;

von Daniel K. (daniel_k80)


Angehängte Dateien:

Lesenswert?

Lothar M. schrieb:
> Was ja gleich ist wie das hier:

Stimmt...

Lothar M. schrieb:
> Ein Wort zum Thema Latency.
> Dein Kommentar hier lässt vermuten, dass das Signal TLAST aktiv sein
> soll, wenn die Adresse=99 ist:

Das stimmt schon, aber da die Signale ohnehin alle verzögert kommen, 
passt TLAST auch wieder (siehe Screenshot). Die Ausgabedaten sind eine 
Sinuskurve mit Offset 0x8000. Der letzte Wert ist 0x77F6 und der wird 
korrekt mit TLAST markiert :)

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.