有限ステートマシン(FSM) / single-processとtwo-processと

有限はマシンにかかるのではなくステートにかかる。

pp.508-511

有限ステートマシン(FSM) 非公式訳

(略) FSMを設計するためには、各ステート、入力信号、出力信号、次ステート函数、出力函数を記述しなければなりません。(略) 。

FSMは同期ディジタル回路として実装できます。このディジタル回路には、現在のステートを保つためのレジスタが含まれるほか、次ステートと出力函数とを実装するための組み合わせ論理回路も含まれます。

FSMは、回路レベルで見ると、図19.7に示した各ブロックに分けられます。「次ステートロジック」ブロックは、現在のステートと現在の入力とに応じて次ステートを決定する組み合わせ論理回路ブロックです。「ステートレジスタ」ブロックは、現在のステートを保存するレジスタです。ほかに、出力値を生成する論理回路ブロックがあります(組み合わせ論理回路の場合もあれば、レジスタを介して出力する場合もあります)。用途によっては出力レジスタも必要です。

(略)記述のスタイルは主に2つあって、1つは「1プロセス」、もう1つは「2プロセス」として一般に知られています。(略) 2プロセスFSMは一般に、ステートレジスタをクロック同期回路プロセス内に記述し、次ステートブロックと出力論理回路ブロックとを組み合わせ論理回路プロセス内に記述します(組み合わせ論理回路プロセスを複数に分割する場合も2プロセススタイルと見なします)。

図19.8に、同じFSMを2通りの手段で実装した例を示します。この回路は、2入力、1出力の回転式改札口コントローラーです。このFSMにはlockedunlockedという2つのステートがあります。lockedステートのとき、回転式改札口は入力ticked_acceptedtrueになるまで待ちます。unlockedステートのとき、回転式改札口は入力pushed_throughtrueになるまで待ちます。lockという1つだけある出力は、FSMがlockedステートにあるときはtrueです。

1プロセスと2プロセスのどちらのスタイルにも長所、短所があります。まずは1プロセススタイル(図19.8a)の長所から始めましょう。長所の1つはステートに変数が使えることです。そのためFSMの細かな実装内容全体がプロセス内にカプセル化できます。type state_typeおよびvariable stateという宣言がプロセスレベルで行えるため、architecutreの宣言部がごちゃごちゃしません。2つ目の長所は、ステートを保存しておくためのオブジェクトが1つだけで済むことです(すなわち、現在ステート用、次ステート用に別々のオブジェクトを用意せずに済むということです)。3つ目の長所は、プロセス全体がクロックに同期するためラッチが不用意に生成されるリスクのないことです。しかしデフォルトではすべての出力がレジスタを介して出力されます。これは絶対に必要というわけではありません。(略)。

2プロセススタイルにも長所はいくつかあります。1つ目は、出力をレジスタに通すか通さないかの選択がしやすいことです。すなわち、組み合わせ論理回路プロセス、クロック同期回路プロセス、それらとは別のプロセスのどれに出力論理回路を配置するかが選択できるということです。2つ目の長所は、ブロック図に示した構成にきわめて近い形で実装できることです。プロセスごとに明確な役割が与えられるため、多くの設計者は2プロセススタイルを好みます。しかし、次ステート論理回路組み合わせ論理回路プロセスであるため、考えられうるすべての実行経路においてすべての出力と次ステート信号とを割り当てない限り、不要なラッチの生成されるおそれがあります。(略)。

(略)。

図19.8に示した2つの例は、記述スタイルとは無関係に必要以上に冗長です。単純は変更を2つ加えるだけで、もっとすっきり書けます。

まず、今居るステートに居続けるための冗長な割り当ては記述が不要です。今回の例でいえば、if文のelse分岐に記述したstateへの割り当てもnext_stateへの割り当てもすべて不要です。1プロセススタイルの場合は、単に冗長な割り当てを削除するだけでよい。2プロセススタイルの場合は、不要なラッチを防ぐ手段として、case文の前にnext_state <= current_stateというデフォルト割り当てを追加する必要もあります。

また、出力にデフォルト値がある場合、あるいはほとんどのステートで同じ値を出力する場合は、case文の前に出力にデフォルト値を割り当てるという方法があります。この方法を用いるときは、出力する値がデフォルト値と異なるときにだけcase文の内側で出力に割り当てる必要があります。この方法だと、次ステート信号だけでなくすべての出力に対するデフォルト割り当てを追加すれば、不要なラッチの生成されるおそれがなくなります。しかしステートの挙動を定義する文が分散してしまうため、ステートごとに分析するのが少し難しい。(略)。

library ieee;
use ieee.std_logic_1164.all;

entity single_proc_test is
    port (
        clock: in std_logic;
        reset: in std_logic;
        ticket_accepted: in std_logic;
        pushed_through: in std_logic;
        lock: out std_logic
    );
end entity;

architecture rtl of single_proc_test is
    begin
    process (clock, reset)
        type state_type is (locked, unlocked);
        variable state: state_type;
    begin
        if reset then
            state := locked;
            lock <= '1';
        elsif rising_edge(clock) then
            case state is
                when locked =>
                    if ticket_accepted then
                        lock <= '0';
                        state := unlocked;
                    else
                        lock <= '1';
                        state := locked; -- これが不要
                    end if;
                when unlocked =>
                    if pushed_through then
                        lock <= '1';
                        state := locked;
                    else
                        lock <= '0';
                        state := unlocked; -- これが不要
                    end if;
            end case;
        end if;
    end process;
end architecture;
library ieee;
use ieee.std_logic_1164.all;

entity two_proc_test is
    port (
        clock: in std_logic;
        reset: in std_logic;
        ticket_accepted: in std_logic;
        pushed_through: in std_logic;
        lock: out std_logic
    );
end entity;

architecture rtl of two_proc_test is
    type state_type is (locked, unlocked);
    signal current_state, next_state: state_type;
begin
    process (clock, reset)
    begin
        if reset then
            current_state <= locked;
        elsif rising_edge(clock) then
            current_state <= next_state;
        end if;
    end process;
    
    process (all)
    begin
   --next_state <= current_state; -- 下の不要行を削除した場合はこの行が必要。
        case current_state is
            when locked =>
                if ticket_accepted then
                    lock <= '0';
                    next_state <= unlocked;
                else
                    lock <= '1';
                    next_state <= locked; -- これが不要
                end if;
            when unlocked =>
                if pushed_through then
                    lock <= '1';
                    next_state <= locked;
                else
                    lock <= '0';
                    next_state <= unlocked; -- これが不要
                end if;
        end case;
    end process;
end architecture;