Mots-clés : machine d'état, distributeur automatique

La machine à états finis (FSM, Finite-State Machine), appelée machine d'état, est un modèle mathématique qui représente un nombre fini d'états et les comportements tels que les transitions et les actions entre ces états. La machine d'état n'est pas seulement un outil de description de circuit, mais aussi un mode de pensée, et elle est largement utilisée au niveau système et au niveau RTL dans la conception de circuits.

Types de machines d'état

En Verilog, les machines d'état sont principalement utilisées dans la conception de logique séquentielle synchrone, et peuvent faire basculer les états des circuits séquentiels entre un nombre fini d'états selon certaines exigences et règles. La direction du changement d'état dépend non seulement des valeurs d'entrée, mais aussi de l'état courant. Les machines d'état peuvent être divisées en 2 catégories : les machines d'état de Moore et les machines d'état de Mealy.

Machine d'état de type Moore

La sortie d'une machine d'état de Moore est liée uniquement à l'état courant, et non à l'entrée courante.

La sortie reste stable pendant un cycle d'horloge complet. Même si le signal d'entrée change à ce moment-là, la sortie ne changera pas. L'influence de l'entrée sur la sortie ne peut se refléter que dans le cycle d'horloge suivant. C'est également une caractéristique importante de la machine d'état de Moore : l'entrée et la sortie sont isolées.

Machine d'état de type Mealy

La sortie d'une machine d'état de Mealy dépend non seulement de l'état courant, mais aussi du signal d'entrée courant.

La sortie d'une machine d'état de Mealy change immédiatement après le changement du signal d'entrée, et le changement d'entrée peut se produire pendant le cycle d'horloge de n'importe quel état. Par conséquent, avec la même logique, la réponse de la sortie de la machine d'état de Mealy à l'entrée est en avance d'un cycle d'horloge par rapport à celle de la machine d'état de Moore.

Flot de conception d'une machine d'état

Dessinez le diagramme de transition d'état selon les exigences de conception, déterminez le type de machine à états à utiliser et étiquetez les divers signaux d'entrée et de sortie, ce qui est plus utile pour la programmation. Généralement, la plus couramment utilisée est la machine à états de type Mealy à 3 segments. Ce qui suit utilise un exemple spécifique de conception d'un distributeur automatique de boissons pour illustrer le processus de conception de la machine à états.

Distributeur automatique de boissons

La description fonctionnelle du distributeur automatique de boissons est la suivante :

Le prix de la boisson est de 2 yuan. Le distributeur automatique ne peut accepter que des pièces de 0.5 yuan et de 1 yuan. Tenir compte de la monnaie et de la distribution du produit. Les processus d'insertion de pièces et de distribution de produit sont effectués un à la fois ; il n'y aura pas de situation où plusieurs pièces sont insérées en même temps ou plusieurs bouteilles de boisson sont distribuées en même temps. Ce n'est qu'après que chaque cycle du distributeur automatique ait terminé l'insertion de pièces, la distribution du produit et le rendu de monnaie qu'il peut entrer dans un nouvel état de vente automatique.

Le diagramme de transition d'état de fonctionnement du distributeur automatique est présenté ci-dessous, incluant les états des signaux d'entrée et de sortie.

Ici, coin = 1 signifie qu'une pièce de 0.5 yuan est insérée, et coin = 2 signifie qu'une pièce de 1 yuan est insérée.

Conception de la machine à états : style à 3 segments (recommandé)

La conception de la machine à états est la suivante :

  • (0) Tout d'abord, déterminez l'encodage de la machine à états en fonction du nombre d'états. L'utilisation de l'encodage pour attribuer des valeurs aux registres d'état améliore la lisibilité du code.
  • (1) Le premier segment de la machine à états : logique séquentielle, affectation non bloquante, pour transférer l'état du registre.
  • (2) Le deuxième segment de la machine à états : logique combinatoire, affectation bloquante, pour déterminer l'état suivant de la machine à états en fonction de l'état actuel et de l'entrée actuelle.
  • (3) Le troisième segment de la machine à états : logique séquentielle, affectation non bloquante. Comme il s'agit d'une machine à états de Mealy, déterminez le signal de sortie en fonction de l'état actuel et de l'entrée actuelle.

Exemple

// distributeur automatique
// 2 yuan pour une bouteille de boisson
// seulement 2 pièces acceptées : 5 jiao et 1 yuan
// terminer la fonction de vente et de rendu de monnaie

module  vending_machine_p3  (
    input           clk ,
    input           rstn ,
    input [1:0]     coin ,     //01 pour 0.5 jiao, 10 pour 1 yuan

    output [1:0]    change ,
    output          sell    //sortir la boisson
    );

    //décodage de l'état de la machine
    parameter            IDLE   = 3'd0 ;
    parameter            GET05  = 3'd1 ;
    parameter            GET10  = 3'd2 ;
    parameter            GET15  = 3'd3 ;

    //variable de machine
    reg [2:0]            st_next ;
    reg [2:0]            st_cur ;

    //(1) transfert d'état
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            st_cur      <= 'b0 ;
        end
        else begin
            st_cur      <= st_next ;
        end
    end

    //(2) commutation d'état, en utilisant une affectation bloquante pour la logique combinatoire
    //tous les éléments du case doivent être affichés complètement    
    always @(*) begin
        //st_next = st_cur ;//Si les options de condition ne sont pas toutes prises en compte, vous pouvez affecter une valeur initiale pour éliminer le latch
        case(st_cur)
            IDLE:
                case (coin)
                    2'b01:     st_next = GET05 ;
                    2'b10:     st_next = GET10 ;
                    default:   st_next = IDLE ;
                endcase
            GET05:
                case (coin)
                    2'b01:     st_next = GET10 ;
                    2'b10:     st_next = GET15 ;
                    default:   st_next = GET05 ;
                endcase

            GET10:
                case (coin)
                    2'b01:     st_next = GET15 ;
                    2'b10:     st_next = IDLE ;
                    default:   st_next = GET10 ;
                endcase
            GET15:
                case (coin)
                    2'b01,2'b10:
                               st_next = IDLE ;
                    default:   st_next = GET15 ;
                endcase
            default:    st_next = IDLE ;
        endcase
    end

    //(3) logique de sortie, utilisant une affectation non bloquante
    reg  [1:0]   change_r ;
    reg          sell_r ;
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            change_r       <= 2'b0 ;
            sell_r         <= 1'b0 ;
        end
        else if ((st_cur == GET15 && coin ==2'h1)
               || (st_cur == GET10 && coin ==2'd2)) begin
            change_r       <= 2'b0 ;
            sell_r         <= 1'b1 ;
        end
        else if (st_cur == GET15 && coin == 2'h2) begin
            change_r       <= 2'b1 ;
            sell_r         <= 1'b1 ;
        end
        else begin
            change_r       <= 2'b0 ;
            sell_r         <= 1'b0 ;
        end
    end
    assign       sell    = sell_r ;
    assign       change  = change_r ;

endmodule

La conception du banc de test est la suivante. Quatre scénarios sont simulés dans la simulation, à savoir :

case1 correspond à l'insertion de quatre pièces de 0.5 yuan successivement ; case2 correspond à l'ordre d'insertion des pièces de 1 yuan - 0.5 yuan - 1 yuan ; case3 correspond à l'ordre de 0.5 yuan - 1 yuan - 0.5 yuan ; case4 correspond à l'ordre de trois pièces de 0.5 yuan consécutives suivies d'une pièce de 1 yuan.

Exemple

`timescale 1ns/1ps

module test ;
    reg          clk;
    reg          rstn ;
    reg [1:0]    coin ;
    wire [1:0]   change ;
    wire         sell ;

    //génération d'horloge
    parameter    CYCLE_200MHz = 10 ; //
    always begin
        clk = 0 ; #(CYCLE_200MHz/2) ;
        clk = 1 ; #(CYCLE_200MHz/2) ;
    end

    //génération de motivation
    reg [9:0]    buy_oper ; //stocke l'état de l'opération d'achat
    initial begin
        buy_oper  = 'h0 ;
        coin      = 2'h0 ;
        rstn      = 1'b0 ;
        #8 rstn   = 1'b1 ;
        @(negedge clk) ;

        //cas(1) 0.5 -> 0.5 -> 0.5 -> 0.5
        #16 ;
        buy_oper  = 10'b00_0101_0101 ;
        repeat(5) begin
            @(negedge clk) ;
            coin      = buy_oper[1:0] ;
            buy_oper  = buy_oper >> 2 ;
        end

        //case(2) 1 -> 0.5 -> 1, rendu de monnaie
        #16 ;
        buy_oper  = 10'b00_0010_0110 ;
        repeat(5) begin
            @(negedge clk) ;
            coin      = buy_oper[1:0] ;
            buy_oper  = buy_oper >> 2 ;
        end

        //case(3) 0.5 -> 1 -> 0.5
        #16 ;
        buy_oper  = 10'b00_0001_1001 ;
        repeat(5) begin
            @(negedge clk) ;
            coin      = buy_oper[1:0] ;
            buy_oper  = buy_oper >> 2 ;
        end

        //case(4) 0.5 -> 0.5 -> 0.5 -> 1, rendu de monnaie
        #16 ;
        buy_oper  = 10'b00_1001_0101 ;
        repeat(5) begin
            @(negedge clk) ;
            coin      = buy_oper[1:0] ;
            buy_oper  = buy_oper >> 2 ;
        end
    end

   //(1) état de Mealy à 3 étages
    vending_machine_p3 u_mealy_p3     (
        .clk              (clk),
        .rstn             (rstn),
        .coin             (coin),
        .change           (change),
        .sell             (sell)
        );

   //fin de simulation
   always begin
      #100;
      if ($time >= 10000)  $finish ;
   end

endmodule // test

Les résultats de simulation sont les suivants :

On peut voir sur la figure que le signal sell, qui représente l'action de distribution du produit, peut normalement être mis à l'état haut une fois l'insertion de pièces terminée, et que le signal change, qui représente l'action de rendu de monnaie, peut également émettre le signal correct de rendu de monnaie ou d'absence de rendu de monnaie selon le scénario de pièces en entrée.

Modification de la machine d'état : style à 2 segments

Fusionnez les descriptions des segments 2 et 3 de la machine d'état à 3 segments, conservez les autres parties inchangées, et la machine d'état devient une description à 2 segments.

Les parties modifiées sont les suivantes :

Exemple

//(2) changement d'état, et logique de sortie
//tout en utilisant l'affectation bloquante pour la logique combinatoire
reg  [1:0]   change_r ;
reg          sell_r ;
always @(*) begin //tous les éléments de case doivent être affichés complètement
    case(st_cur)
        IDLE: begin
            change_r     = 2'b0 ;
            sell_r       = 1'b0 ;
            case (coin)
                2'b01:     st_next = GET05 ;
                2'b10:     st_next = GET10 ;
                default:   st_next = IDLE ;
            endcase // case (coin)
        end
        GET05: begin
            change_r     = 2'b0 ;
            sell_r       = 1'b0 ;
            case (coin)
                2'b01:     st_next = GET10 ;
                2'b10:     st_next = GET15 ;
                default:   st_next = GET05 ;
            endcase // case (coin)
        end

        GET10:
            case (coin)
                2'b01:     begin
                    st_next      = GET15 ;
                    change_r     = 2'b0 ;
                    sell_r       = 1'b0 ;
                end
                2'b10:     begin
                    st_next      = IDLE ;
                    change_r     = 2'b0 ;
                    sell_r       = 1'b1 ;
                end
                default:   begin
                    st_next      = GET10 ;
                    change_r     = 2'b0 ;
                    sell_r       = 1'b0 ;
                end
            endcase // case (coin)

        GET15:
            case (coin)
                2'b01: begin
                    st_next     = IDLE ;
                    change_r    = 2'b0 ;
                    sell_r      = 1'b1 ;
                end
                2'b10:     begin
                    st_next     = IDLE ;
                    change_r    = 2'b1 ;
                    sell_r      = 1'b1 ;
                end
                default:   begin
                    st_next     = GET15 ;
                    change_r    = 2'b0 ;
                    sell_r      = 1'b0 ;
                end
            endcase
        default:  begin
            st_next     = IDLE ;
            change_r    = 2'b0 ;
            sell_r      = 1'b0 ;
        end

    endcase
end

Instanciez le module nouvellement modifié ci-dessus dans le banc de test à 3 segments pour effectuer la simulation. Les résultats sont les suivants :

On peut voir sur la figure que le signal de distribution de produit sell et le signal de monnaie change sont en avance d'un cycle d'horloge par rapport à la sortie de la machine à états à 3 segments. Cela est dû au fait que les signaux de sortie sont tous des affectations bloquantes.

Comme montré dans les parties entourées en rouge sur la figure, des impulsions parasites apparaissent sur les signaux de sortie. Cela est dû au fait que les signaux d'entrée sont asynchrones, et que les signaux de sortie sont des sorties de logique combinatoire sans pilotage d'horloge.

En pratique, si les signaux d'entrée sont synchronisés avec l'horloge, de telles impulsions parasites n'apparaîtront pas. Si les signaux d'entrée sont asynchrones, les signaux doivent d'abord être synchronisés.

Modification de la machine d'état : style à 1 segment (à utiliser avec prudence)

Fusionnez les descriptions des segments 1, 2 et 3 de la machine d'état à 3 segments, et la machine d'état devient une description à 1 segment.

Les parties modifiées sont les suivantes :

Exemple


    //(1) en utilisant une variable d'état pour décrire
    reg  [1:0]   change_r ;
    reg          sell_r ;
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            st_cur     <= 'b0 ;
            change_r   <= 2'b0 ;
            sell_r     <= 1'b0 ;
        end
        else begin
            case(st_cur)

            IDLE: begin
                change_r  <= 2'b0 ;
                sell_r    <= 1'b0 ;
                case (coin)
                    2'b01:     st_cur <= GET05 ;
                    2'b10:     st_cur <= GET10 ;
                endcase
            end
            GET05: begin
                case (coin)
                    2'b01:     st_cur <= GET10 ;
                    2'b10:     st_cur <= GET15 ;
                endcase
            end

            GET10:
                case (coin)
                    2'b01:     st_cur   <=  GET15 ;
                    2'b10:     begin
                        st_cur   <= IDLE ;
                        sell_r   <= 1'b1 ;
                    end
                endcase

            GET15:
                case (coin)
                    2'b01:     begin
                        st_cur   <= IDLE ;
                        sell_r   <= 1'b1 ;
                    end
                    2'b10:     begin
                        st_cur   <= IDLE ;
                        change_r <= 2'b1 ;
                        sell_r   <= 1'b1 ;
                    end
                endcase

            default:  begin
                  st_cur    <= IDLE ;
            end

            endcase // case (st_cur)
        end // else: !if(!rstn)
    end

Instanciez le module nouvellement modifié ci-dessus dans le banc de test à 3 segments pour effectuer la simulation. Les résultats sont les suivants :

On peut voir sur la figure que les signaux de sortie sont exactement les mêmes que ceux de la machine d'état à 3 segments.

L'inconvénient de la machine d'état à 1 segment est que de nombreux types de logique sont mélangés, ce qui rend la maintenance ultérieure difficile. Lorsque la machine d'état et les signaux de sortie sont peu nombreux, ce style de description peut être essayé.

Modification de la machine d'état : type Moore

Si une machine d'état de Moore est utilisée pour décrire le flux de fonctionnement du distributeur automatique, alors 2 encodages d'état supplémentaires doivent être ajoutés pour décrire le signal d'entrée et l'état de la machine d'état lorsque la machine d'état de Mealy produit des sorties.

Le code Verilog du distributeur automatique décrit par la machine d'état de Moore à 3 segments est le suivant :

Exemple

module  vending_machine_moore    (
    input           clk ,
    input           rstn ,
    input [1:0]     coin ,     //01 pour 0.5 jiao, 10 pour 1 yuan

    output [1:0]    change ,
    output          sell    //sortir la boisson
    );

    //décodage de l'état de la machine
    parameter            IDLE   = 3'd0 ;
    parameter            GET05  = 3'd1 ;
    parameter            GET10  = 3'd2 ;
    parameter            GET15  = 3'd3 ;
    // nouvel état pour la machine d'état de Moore
    parameter            GET20  = 3'd4 ;
    parameter            GET25  = 3'd5 ;

    //variable de machine
    reg [2:0]            st_next ;
    reg [2:0]            st_cur ;

    //(1) transfert d'état
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            st_cur      <= 'b0 ;
        end
        else begin
            st_cur      <= st_next ;
        end
    end

    //(2) commutation d'état, en utilisant une affectation bloquante pour la logique combinatoire
    always @(*) begin //tous les éléments de case doivent être affichés complètement
        case(st_cur)
            IDLE:
                case (coin)
                    2'b01:     st_next = GET05 ;
                    2'b10:     st_next = GET10 ;
                    default:   st_next = IDLE ;
                endcase
            GET05:
                case (coin)
                    2'b01:     st_next = GET10 ;
                    2'b10:     st_next = GET15 ;
                    default:   st_next = GET05 ;
                endcase

            GET10:
                case (coin)
                    2'b01:     st_next = GET15 ;
                    2'b10:     st_next = GET20 ;
                    default:   st_next = GET10 ;
                endcase
            GET15:
                case (coin)
                    2'b01:     st_next = GET20 ;
                    2'b10:     st_next = GET25 ;
                    default:   st_next = GET15 ;
                endcase
            GET20:         st_next = IDLE ;
            GET25:         st_next = IDLE ;
            default:       st_next = IDLE ;
        endcase // case (st_cur)
    end // always @ (*)

   // (3) logique de sortie,
   // un cycle de retard lors de l'utilisation d'une assignation non bloquante
    reg  [1:0]   change_r ;
    reg          sell_r ;
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            change_r       <= 2'b0 ;
            sell_r         <= 1'b0 ;
        end
        else if (st_cur == GET20 ) begin
            sell_r         <= 1'b1 ;
        end
        else if (st_cur == GET25) begin
            change_r       <= 2'b1 ;
            sell_r         <= 1'b1 ;
        end
        else begin
            change_r       <= 2'b0 ;
            sell_r         <= 1'b0 ;
        end
    end
    assign       sell    = sell_r ;
    assign       change  = change_r ;

endmodule

Instanciez la machine d'état Moore modifiée ci-dessus dans le banc de test à 3 segments pour effectuer la simulation. Les résultats sont les suivants :

On peut voir sur la figure que le signal de sortie est retardé d'un cycle d'horloge par rapport à la machine d'état de type Mealy à 3 segments. Cela est dû au fait que l'entrée dans la machine d'état d'encodage nouvellement ajoutée nécessite un retard d'un cycle d'horloge. À ce stade, l'utilisation d'une assignation non bloquante pour la sortie entraînera un retard du signal de sortie final d'un cycle d'horloge. C'est également une caractéristique de la machine d'état Moore.

Lors de l'assignation du signal de sortie, l'utilisation d'une assignation bloquante peut l'avancer d'un cycle d'horloge.

La logique de sortie est modifiée comme suit.

Exemple


    // (3.2) logique de sortie, utilisant une assignation bloquante
    reg  [1:0]   change_r ;
    reg          sell_r ;
    always @(*) begin
        change_r  = 'b0 ;
        sell_r    = 'b0 ; //ne liste pas toutes les conditions, les initialise
        if (st_cur == GET20 ) begin
            sell_r         = 1'b1 ;
        end
        else if (st_cur == GET25) begin
            change_r       = 2'b1 ;
            sell_r         = 1'b1 ;
        end
    end

Les résultats de simulation de l'assignation bloquante pour le signal de sortie sont les suivants :

On peut voir sur la figure que le signal de sortie est maintenant cohérent avec la machine d'état Mealy à 3 segments.

Téléchargement du code source

Téléchargement