VHDLでのステートマシン記述 (if文とcase文の違い)

FPGAのロジックをVHDL(やverilog-HDL)で設計していると、どのようなコーディングスタイルを採用するか悩むことがあります。(設計人口も公開された情報も格段に少ないので、C言語やpythonといったプログラミング言語よりも悩みは深いかもしれません)

## ステートマシンの記述法
VHDLやverilog-HDLでステートマシン(Finite State Machine:FSM)を記述する場合、case文で記述することが一般的とされていますがif文で記述することもできます。
(入門書でもネットで検索しても、ほぼcase文の例しか出てこないので、多くの人はif文による記述を試したことがないかもしれません)

if文とcase文それぞれの記述例を下記に示します。どちらも等価な動作をするよう意図しています。しかし、必ずしも同じロジックが合成されるわけではありません。
if文記述例では、else節の記述により、それまでのif-elsifで記述された条件以外の全ての条件でIDLE状態に戻すようにしています。この場合、signal ST0 に定義された状態だけでなく全ての条件が対象になります。
case文記述例では、when ohters節がありますが、この例ではすでにsignal ST0 に定義された3状態を記述しているのでwhen ohters節は無視されます(合成/シミュレーションツールの設定によってはエラーや警告が出る場合もあります)。

```VHDL:if文による記述例
library IEEE;
use IEEE.std_logic_1164.all;

entity FSM is
    port(
        RST_I_N	: in  std_logic;
        CLK_I	: in  std_logic;
        RUN_I   : in  std_logic;
        IDLE_O  : out std_logic;
        ST_O    : out std_logic;
        BSY_O   : out std_logic
    );
end FSM;
architecture RTL of FSM is
    type T_ST is (
        IDLE
        ,ST
        ,BSY
    );
    signal ST0   : T_ST;
begin
	P_ST: process(RST_I_N, CLK_I)
	begin
		if (RST_I_N = '0') then
			ST0 <= IDLE;
		elsif (rising_edge(CLK_I)) then
			if (ST0 = IDLE) then
                if (RUN_I = '1') then
                    ST0 <= ST;
                end if;
            elsif (ST0 = ST) then
                ST0 <= BSY;
            elsif (ST0 = BSY) then
                if (RUN_I /= '1') then
                    ST0 <= IDLE;
                end if;
            else
                ST0 <= IDLE;
            end if;
		end if;
	end process;

    IDLE_O <= '1' when (ST0 = IDLE) else '0';
    ST_O <= '1' when (ST0 = ST) else '0';
    BSY_O <= '1' when (ST0 = BSY) else '0';

end RTL;
```

```VHDL:case文による記述例
library IEEE;
use IEEE.std_logic_1164.all;

entity FSM is
    port(
        RST_I_N	: in  std_logic;
        CLK_I	: in  std_logic;
        RUN_I   : in  std_logic;
        IDLE_O  : out std_logic;
        ST_O    : out std_logic;
        BSY_O   : out std_logic
    );
end FSM;
architecture RTL of FSM is
    type T_ST is (
        IDLE
        ,ST
        ,BSY
    );
    signal ST0   : T_ST;
begin
    P_ST: process(RST_I_N, CLK_I)
    begin
        if (RST_I_N = '0') then
            ST0 <= IDLE;
        elsif (rising_edge(CLK_I)) then
            case ST0 is
                when IDLE =>
                    if (RUN_I = '1') then
                        ST0 <= ST;
                    end if;
                when ST =>
                    ST0 <= BSY;
                when BSY =>
                    if (RUN_I /= '1') then
                        ST0 <= IDLE;
                    end if;
                when others =>  -- この節は無視される
                    ST0 <= IDLE;
            end case;
        end if;
    end process;

    IDLE_O <= '1' when (ST0 = IDLE) else '0';
    ST_O <= '1' when (ST0 = ST) else '0';
    BSY_O <= '1' when (ST0 = BSY) else '0';

end RTL;
```

## 合成・シミュレーションしてみる
これらのステートマシン記述を実際のFPGAに適用するとどうなるでしょうか。
IntelのMAX10をターゲットデバイスとして、Quartus Primeで論理合成し、その結果(.vhoファイル)に対してQuestaでシミュレーションしてみました。



> Quartus Prime Version ; 22.1std.2 Build 922 07/20/2023 SC Lite Edition
> Family                ; MAX 10                           
> Device                ; 10M02SCE144C8G                                
> Questa Intel Starter FPGA Edition-64  Version 2021.2 win64 Apr 14 2021

多くのFPGA合成ツールでそうであるように、Quartus Primeでもデフォルト設定ではステートマシンエンコーディングはAutoとなっており、結果としてOne-Hotエンコーディングになります。
```
Encoding Type:  One-Hot
+----------------------------------------+
; State Machine - |FSM|ST0               ;
+----------+---------+--------+----------+
; Name     ; ST0.BSY ; ST0.ST ; ST0.IDLE ;
+----------+---------+--------+----------+
; ST0.IDLE ; 0       ; 0      ; 0        ;
; ST0.ST   ; 0       ; 1      ; 1        ;
; ST0.BSY  ; 1       ; 0      ; 1        ;
+----------+---------+--------+----------+
```
今回は3状態のステートマシンなので3ビットで構成されています。3ビットのレジスターは 2<sup>3</sup>=8 状態を取りうるので、定義された3状態以外の5状態は未定義状態ということになります。
Single Event Upset(SEU)のようなソフトエラーが発生した場合にこれらの未定義状態に陥る場合がありますので、そこから自動復帰できるか否かが信頼性を決める重要な要素になります。

今回は、未定義状態のシミュレーションのため、Questaの実行時に
```
run 150 ns
force -deposit sim:/tb/U0/\\ST0.BSY~q\\ 0 0
run 150 ns
```
として、BSYステート中、150nsの時点でST0.BSY~qを強制的に0にしてみました。

### if文記述の場合
150ns時点でST0.BSY~qが0になりすべての出力が0になりましたが、次のクロックエッジで(else節に救われて)IDLEステートに復帰し、それ以降は正常動作に戻りました。
![if文記述のシミュレーション波形](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1923193/5daa6a57-c544-b034-cb36-bbaa229cb334.png)

### case文記述の場合
150ns時点でST0.BSY~qが0になりすべての出力が0になり、その後復帰することはありませんでした。
![case文記述のシミュレーション波形](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1923193/643fa274-8a06-f83c-124d-c979d97b4eaa.png)


### case文記述で Safe State Machine = ON の場合
Quartus Primeには(Quartus IIの時代から)Safe State Machineというオプションがあり、デフォルトではオフになっています。これをオンにすると、case文記述でも、未定義状態からの自動復帰ができるようになります。
```
Encoding Type: Safe One-Hot
+----------------------------------------+
; State Machine - |FSM|ST0               ;
+----------+---------+--------+----------+
; Name     ; ST0.BSY ; ST0.ST ; ST0.IDLE ;
+----------+---------+--------+----------+
; ST0.IDLE ; 0       ; 0      ; 0        ;
; ST0.ST   ; 0       ; 1      ; 1        ;
; ST0.BSY  ; 1       ; 0      ; 1        ;
+----------+---------+--------+----------+
```

この場合、if文記述の場合と同様な動作となり、
150ns時点でST0.BSY~qが0になりすべての出力が0になりましたが、次のクロックエッジでIDLEステートに復帰し、それ以降は正常動作に戻りました。
![case文記述(Safe State Machine=ON)のシミュレーション波形](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1923193/7ae0038b-c2b3-90fb-5bb6-2fc82f356726.png)

### まとめ
これらのシミュレーション結果に、配置配線後の回路規模(logic elements)と動作周波数(Fmax)を加えて表にまとめてみました。
| |if文記述|case文記述|case文記述(Safe State Machine)|
|---|--:|--:|--:|
|Total logic elements|4|4|4|
|Fmax (Slow 1200mV 85C Model) [MHz]|549.15|1018.33|618.81|
|未定義状態からの復帰|OK|NG|OK|

回路規模は、今回のように小さいステートマシンではどの記述方法でもほとんど変わらないことがわかります。
動作周波数は、case文記述のほうが有利なようですが、if文記述でもこのデバイスのRestricted Fmax (250MHz)を大きく超えているので、実際には問題ありません。通常は、よほど巨大なひどいステートマシンを作らない限り、ステートマシンがボトルネックになることはないでしょう。
未定義状態からの復帰については、if文記述のほうが良いということがわかりました。case文記述で Safe State MachineオプションをONにしても良いですが、どのような設定でも安全なステートマシン作れるif文記述のほうがより良いといえるでしょう。