ステートマシンを回路で書く

(1)ここで学ぶ内容

graph LR subgraph ミーリ型 \(Mealy Machine) direction LR IN1[入力] --> NS1(次状態生成論理) NS1 --> REG1[状態レジスタ] REG1 --> NS1 IN1 --> OUT_LOGIC1(出力生成論理) REG1 --> OUT_LOGIC1 OUT_LOGIC1 --> OUT1[出力] end style NS1 fill:#e3f2fd,stroke:#1976d2; style REG1 fill:#f3e5f5,stroke:#9c27b0; style OUT_LOGIC1 fill:#e8f5e9,stroke:#388e3c;
graph LR subgraph ムーア型 \(Moore Machine) direction LR IN2[入力] --> NS2(次状態生成論理) NS2 --> REG2[状態レジスタ] REG2 --> NS2 REG2 --> OUT_LOGIC2(出力生成論理) OUT_LOGIC2 --> OUT2[出力] end style NS2 fill:#e3f2fd,stroke:#1976d2; style REG2 fill:#f3e5f5,stroke:#9c27b0; style OUT_LOGIC2 fill:#fff3e0,stroke:#f57c00;
概要
  • CDトレイの制御回路を例に、記述の詳細やステートレジスタの構成方法について学ぶ

目標

以下の項目を理解し説明できる

  • ステートマシンの回路構造を理解して説明できる
  • ステートマシンの回路を記述できる
  • ステートマシンを用いて制御回路を設計できる
  • ステートレジスタの構成方法を理解し説明できる

修了判定1

2種類のステートマシン記述をシミュレーションして、動作を確認する


修了判定2

2種類のステートマシン記述を論理合成して、回路規模を比較する

ステートマシンをHDLで記述すると簡単にかけます。HDLは便利だと感じると思います。もし、ステートマシンの基本を覚えていない場合はL4ユニットをみてください。

(2)ステートマシンの例

CDの出し入れ
[ ■ ] [ ▶ ] [ ⏸ ] [ ⏮ ] [ ⏭ ] [ ⏏ ]
stateDiagram-v2
    closed : 閉じた状態
    opening : open中
    opened : トレイが出た状態
    closing : close中

    closed --> closed
    closed --> opening : ボタンが押された
    opening --> opening
    opening --> opened : 完全に開いた
    opened --> opened
    opened --> closing : ボタンが押された
    closing --> closing
    closing --> closed : 完全に閉じた
                  

このユニットではL4ユニットで紹介したCDプレイヤーのトレイ制御を例にステートマシンの回路記述を解説します。
動作を簡単に紹介します。

このステートマシンは4つの状態があります。入力はイジェクトボタンで、出力はモータのON、OFFと回転方向です。

閉じた状態でボタンを押されるとモータをONします。完全に開けばトレイが出た状態になり、モータをOFFします。ここでボタンが押されるとモータをONします。
完全に閉じればトレイが閉じた状態になり、モータをOFFします。

次からのページではこのステートマシンをHDLで記述してみます。

(3)トレイ制御回路の入出力とステート名

トレイ制御回路を設計するために、回路仕様を整理してみます。まず入出力信号です。

信号名 機 能
入力 BUTTON イジェクトボタンの入力
FINOPN 完全に開いた
FINCLS 完全に閉じた
出力 MOTOR モータ制御
DIR 回転方向( 1でトレイが開く、0でトレイが閉じる )

入力は3種類あります。これらにより回路の内部状態が変化します。
出力は2種類あります。モータの制御信号と回転方向を示す信号です。

  • すべて Highアクティブ( 1で動作 )
  • 入力はクロックに同期した 1周期分のパルス
  • 上記以外に、 クロック CLK 、 リセット RES がある

次にステートマシンの各状態に対し、ステート名をつけます。

ステート名 状 態
CLOSED 閉じた状態
OPENING open中
OPENED 開いた状態
CLOSING close中

4つの状態に対しこのように名前をつけました。
トレイ制御回路をリセットしたときは、トレイが閉じた状態を初期値とします。
以上の信号名、ステート名をつかってトレイ制御回路を記述します。

(4)ステートマシンの記述

ステートマシンは現在の状態を保持するフリップフロップと、次の状態を決める組み合わせ回路から構成されます。

                         現在の状態
               ┌───────────────────────────────┐
               │                               │
               │  ┌────────────┐               │  リセット
               │  │            │               │     │
               └─>│ 組み合わせ │  次の状態     V     V    出力
          入力 ──>│   回路     ├──────────> ┌──────┐ ──>
                  │            │             │  FF  │
                  └────────────┘             │      │
                                               └─┬────┘
                                                 │
                                              クロック

このフリップフロップをステートレジスタと呼びます。さらにこの組合せ回路をステート生成回路と呼ぶこととします。

ステート生成回路は現在の状態と入力から次の状態をつくります。したがってalways文をつかってこのように記述します。@のあとにはステート生成回路の入力信号をすべて記述します。

// ステート生成回路の記述
always @ ( <現在の状態> or <入力> )
begin
    <現在の状態> による多方向分岐
    <次の状態> への代入
end
// ステートレジスタの記述
always @ ( <クロック> or <リセット> )
begin
    if ( <リセット> )
        <現在の状態> の初期化
    else
        <現在の状態> へ <次の状態> を代入
end

always文の本体には現在の状態による多方向分岐を記述し、分岐先で次の状態を決めます。通常多方向分岐にはcase文を使います。

現在の状態を保持するステートレジスタはステート生成回路で作成した次の信号を入力とするフリップフロップです。クロックは常に与えますので、ステートレジスタの出力はフィードバックして入力にもどる構造になっています。

ステートレジスタの記述はこのように記述します。リセット信号で現在の状態を初期化し、リセット信号がなければクロックのエッジで、現在の状態を次の状態に更新します。

(5)トレイ制御回路の記述 1

まず最初にステート生成回路を記述してみます。

ステート生成回路
 現在の状態
       2 ┌─────────┐
      / ─┤CURRENT  │ 次の状態
         │    NEXT ├─ / ─
  ┌─────┤BUTTON   │  2
入力┼─────┤FINOPN   │
  └─────┤FINCLS   │
         └─────────┘
parameter CLOSED=2'b00, OPENING=2'b01, //ステート値
          OPENED=2'b10, CLOSING=2'b11, //ステート値
          UNKNOWN=2'bxx;
reg [1:0] CURRENT, NEXT; //ステートレジスタの出力と入力

always @( CURRENT or BUTTON or FINOPN or FINCLS )
begin
    case ( CURRENT )    //ステートの移動
        CLOSED: if ( BUTTON )
                    NEXT <= OPENING;
                else
                    NEXT <= CLOSED;
        OPENING:if ( FINOPN )
                    NEXT <= OPENED;
                else
                    NEXT <= OPENING;
        OPENED: if ( BUTTON )
                    NEXT <= CLOSING;
                else
                    NEXT <= OPENED;
        CLOSING:if ( FINCLS )
                    NEXT <= CLOSED;
                else
                    NEXT <= CLOSING;
        default:NEXT <= UNKNOWN;
    endcase
end

入力は現在の状態CURRENTと、これらの3つの信号(BUTTON、FINOPN、FINCLS)です。出力は次の状態NEXTです。記述を示します。ここでは主要な部分だけ示します。

parameter宣言を使って4つの状態に値を割り当てます。0から3までの値を単純に割り当てました。
CURRENTとNEXTを2ビットでレジスタ宣言します。CURRENTはステートレジスタですので、論理合成後はフリップフロップになります。NEXTは組み合わせ回路の出力になります。

ステートの移動はcase文を使って記述します。現在の状態CURRENTの値により、4方向に分岐します。

例えばトレイが閉じた状態のとき、イジェクトボタンが押されていれば次のステートをトレイのオープン中にします。押されていなければ次のステートをトレイの閉じた状態にします。これは現在の状態を保持することになります。

以上と同じようにステートの移動を記述しています。case文で記述しましたので、defaultでは不定値を代入しています。

(6)トレイ制御回路の記述 2

前ページに続いて、ステートレジスタとモータ制御出力を記述します。

ステートレジスタ
         ┌─────────┐
         │RES      │
    2  / │         │  2
   ─── ─┤NEXT     │─ / ─┬─
         │ CURRENT ├─────┘
         │         │ │
   ──────┤>CLK     │ │
         └─────────┘ │
                     │
  ┌──────────────────┘
  │
  │      ┌─────────┐
  │      │  MOTOR  ├─────
  └──────┤CURRENT  │
         │    DIR  ├─────
         └─────────┘
        モータ制御出力
// ステートレジスタ ( CURRENT )
always @( posedge CLK or posedge RES )
begin
    if ( RES )
        CURRENT <= CLOSED;
    else
        CURRENT <= NEXT;
end
// モータ制御出力
assign MOTOR = (CURRENT==OPENING) ||
               (CURRENT==CLOSING) ;
assign DIR   = (CURRENT==OPENING) ;

ステートレジスタはステート生成回路の出力NEXTを入力し、現在の状態CURRENTを出力します。したがって記述はこのようにとてもシンプルです。
リセット信号で出力CURRENTを初期状態にします。リセット信号がなければ出力CURRENTに入力NEXTを代入しています。

トレイ制御回路として最終的に必要なのはモータ制御出力です。この回路の場合、ステートレジスタの出力からこれらの信号をつくりだしています。

モータがONするのはトレイのオープン中とクローズ中です。したがってこの二つのステートでモータ出力が1になるように等号演算を用いて記述しています。
モータの回転方向を示す信号は1がトレイを開く方向、0がトレイを閉じる方向です。したがってオープン中のステートだけ1にしています。

(7)ステートレジスタの構成

ステートレジスタの構成にはいろいろな方法があります。

エンコード/デコード方式

           2
       ┌── / ──────────────────┐
       │                       │
       V   ┌────────┐          │
     ┌───┤ ステート │          │  ┌────────┐
     │   │ レジスタ │     2  │  │        │─> ステート0
入力 │生 │          │D    Q ├─/─┤ステート│─> ステート1
───>│成 │ 2    ┌─>│         │    │デコーダ│─> ステート2
     │回 ├─ / ─┤  └────────┘    │        │─> ステート3
     │路 │      │                └────────┘
     └───┘      └─ クロック
  • ステート値をエンコードして保持する
  • FFは少ないが、ステート数が多いと動作速度が遅くなる
  • ステート値の割り当てが動作速度に影響する

ワンホット方式

           4           ステートレジスタ
       ┌── / ───────┬────────────┐
       │            │  ┌──────┐  │
       V            │  │D CK Q├─┼─> ステート0
     ┌───┐        │  └─┬────┘  │
入力 │生 │        │  ┌─┴────┐  │
───>│成 ├────────┼─>│D CK Q├─┼─> ステート1
     │回 │        │  └─┬────┘  │
     │路 │        │  ┌─┴────┐  │
     └───┘        ├─>│D CK Q├─┼─> ステート2
                    │  └─┬────┘  │
                    │  ┌─┴────┐  │
                    └─>│D CK Q├─┴─> ステート3
                       └─┬────┘
 クロック ────────────────┘
  • 1ステートに1つのFFを割り当てる
  • FFが多数必要だが、動作速度は速い
  • ステートレジスタのうち1つだけが1になる

前ページで紹介した記述例はエンコード/デコード方式です。この方式はステート値をエンコードして保持します。したがって4ステートのトレイ制御回路ならステートレジスタは2ビットで足ります。個々のステートの信号が必要ならステートレジスタの出力をデコードします。

エンコード/デコード方式は使用するフリップフロップは少なくてすみます。しかし、ステートの数が多くなるとステートデコーダなどの遅延が大きくなりので動作速度が遅くなります。また、ステート値の割り当てによって動作速度が変わることもあります。

もう一つの構成がワンホット方式です。これは1つのステートに1つのフリップフロップを割り当てたものです。4ステートなら4つのフリップフロップが必要になります。

ステートの数が増えるとエンコード/デコード方式に比べ、多数のフリップフロップを必要とします。しかしデコード回路などが不要ですので動作速度は速くなります。動作時にはステートレジスタのうち1つだけが1になり、他はすべて0になります。状態の移動により1になるレジスタが移動します。

もふねこ

もふねこ:
エンコード方式とワンホット方式、どっちがいいの?ってよく聞かれるんだけど、「回路を小さくしたいならエンコード」「速度を重視するならワンホット」って覚えておくと便利だよ🐾
最近のFPGAはFF(フリップフロップ)がたくさんあるから、ワンホット方式が好まれることも多いんだよ!

(8)ワンホット方式の記述

トレイ制御回路をワンホット方式で書き換えてみます。

ステート生成回路
 現在の状態
       4 ┌─────────┐
      / ─┤CURRENT  │ 次の状態
         │    NEXT ├─ / ─
  ┌─────┤BUTTON   │  4
入力┼─────┤FINOPN   │
  └─────┤FINCLS   │
         └─────────┘
parameter CLOSED=4'b0001, OPENING=4'b0010,
          OPENED=4'b0100, CLOSING=4'b1000,
          UNKNOWN=4'bxxxx;
reg [3:0] CURRENT, NEXT; //ステートレジスタの出力と入力

always @( CURRENT or BUTTON or FINOPN or FINCLS )
begin
    case ( CURRENT )    //ステートの遷移
        CLOSED: if ( BUTTON )
                    NEXT <= OPENING;
                else
                    NEXT <= CLOSED;
        OPENING:if ( FINOPN )
                    NEXT <= OPENED;
                else
                    NEXT <= OPENING;
        OPENED: if ( BUTTON )
                    NEXT <= CLOSING;
                else
                    NEXT <= OPENED;
        CLOSING:if ( FINCLS )
                    NEXT <= CLOSED;
                else
                    NEXT <= CLOSING;
        default:NEXT <= UNKNOWN;
    endcase
end

ステートレジスタが2ビットから4ビットに変わりますので、回路全体が影響を受けます。しかし、この記述スタイルでは宣言部分を書き換えるだけです。回路記述の本体には変更するところはありません。

まずCURRENTとNEXTのビット幅を4ビットにします。次にステート値を4ビットの値にします。ステート値は4ビットのうち、1つだけを1にした値にします。ステートの移動は1になっているビットが移動することで実現します。

ステートマシンの本体の記述はエンコード/デコード方式と同じです。これ以外のステートレジスタとモータ制御回路の記述は変わりません。

(9)修了判定 1

設問
  • 本文の中で紹介した 2種類のステートマシンをシミュレーションして動作確認する。
  • ファイル名は以下の通り。
回路記述テストベンチ
エンコード/デコード方式 tray_encdec.v encdec_test.v
ワンホット方式 tray_onehot.v onehot_test.v
                CURRENT
           ┌───────────────────────┐
           │          RES          │
           │           │           │
           V  ┌────┐   V   ┌────┐  │    ┌────┐
BUTTON ──────>│ステ│ NEXT  │ステ│  │    │組み│──> MOTOR
FINOPN ──────>│ート├──────>│ート├──┴───>│合わ│
FINCLS ──────>│生成│       │レジ│       │せ回│──> DIR
              │回路│    ┌─>│スタ│       │路  │
              └────┘    │  └────┘       └────┘
                          │
                         CLK

シミュレーション結果(エンコード/デコード方式)

Compiling source file "tray_encdec.v"
Compiling source file "encdec_test.v"

       0 RES=0 BUTTON=0 FINOPN=0 FINCLS=0 STATE=UNKNOWN MOTOR=x DIR=x
    1000 RES=1 BUTTON=0 FINOPN=0 FINCLS=0 STATE= CLOSED MOTOR=0 DIR=0
    2000 RES=0 BUTTON=0 FINOPN=0 FINCLS=0 STATE= CLOSED MOTOR=0 DIR=0
    3000 RES=0 BUTTON=1 FINOPN=0 FINCLS=0 STATE= CLOSED MOTOR=0 DIR=0
    3500 RES=0 BUTTON=1 FINOPN=0 FINCLS=0 STATE=OPENING MOTOR=1 DIR=1
    4000 RES=0 BUTTON=0 FINOPN=0 FINCLS=0 STATE=OPENING MOTOR=1 DIR=1
    8000 RES=0 BUTTON=0 FINOPN=1 FINCLS=0 STATE=OPENING MOTOR=1 DIR=1
    8500 RES=0 BUTTON=0 FINOPN=1 FINCLS=0 STATE= OPENED MOTOR=0 DIR=0
    9000 RES=0 BUTTON=0 FINOPN=0 FINCLS=0 STATE= OPENED MOTOR=0 DIR=0
   13000 RES=0 BUTTON=1 FINOPN=0 FINCLS=0 STATE= OPENED MOTOR=0 DIR=0
   13500 RES=0 BUTTON=1 FINOPN=0 FINCLS=0 STATE=CLOSING MOTOR=1 DIR=0
   14000 RES=0 BUTTON=0 FINOPN=0 FINCLS=0 STATE=CLOSING MOTOR=1 DIR=0
   18000 RES=0 BUTTON=0 FINOPN=0 FINCLS=1 STATE=CLOSING MOTOR=1 DIR=0
   18500 RES=0 BUTTON=0 FINOPN=0 FINCLS=1 STATE= CLOSED MOTOR=0 DIR=0
   19000 RES=0 BUTTON=0 FINOPN=0 FINCLS=0 STATE= CLOSED MOTOR=0 DIR=0
End of simulation 15/10/30 14:18:09

波形では、CURRENT[1:0] および NEXT[1:0]0001101100 の順に遷移していることが確認できます。

波形図(エンコード/デコード方式)
                     |0        |5000     |10000    |15000    |20000
CLK        ─┐_┌─┐_┌─┐_┌─┐_┌─┐_┌─┐_┌─┐_┌─┐_┌─┐_┌─┐_┌─┐_┌─┐_
RES        ──┐           └──────────────────────────────────────
BUTTON     ─────┐   └──────────────┐   └──────────────────────
FINOPN     ─────────────────┐ └────────────────────────────────
FINCLS     ─────────────────────────────────────────────┐ └────
CURRENT[1:0]  xx│  00  │  01      │  10              │  11  │ 00
NEXT[1:0]     xx│  00  │  01      │  10              │  11  │ 00
MOTOR      ─────────────┐         └──────────┐         └──────
DIR        ─────────────┐         └───────────────────────────

CLOSED(00) → OPENING(01) → OPENED(10) → CLOSING(11) → CLOSED(00)

シミュレーション結果(ワンホット方式)

Compiling source file "tray_onehot.v"
Compiling source file "onehot_test.v"

       0 RES=0 BUTTON=0 FINOPN=0 FINCLS=0 STATE=UNKNOWN MOTOR=x DIR=x
    1000 RES=1 BUTTON=0 FINOPN=0 FINCLS=0 STATE= CLOSED MOTOR=0 DIR=0
    2000 RES=0 BUTTON=0 FINOPN=0 FINCLS=0 STATE= CLOSED MOTOR=0 DIR=0
    3000 RES=0 BUTTON=1 FINOPN=0 FINCLS=0 STATE= CLOSED MOTOR=0 DIR=0
    3500 RES=0 BUTTON=1 FINOPN=0 FINCLS=0 STATE=OPENING MOTOR=1 DIR=1
    4000 RES=0 BUTTON=0 FINOPN=0 FINCLS=0 STATE=OPENING MOTOR=1 DIR=1
    8000 RES=0 BUTTON=0 FINOPN=1 FINCLS=0 STATE=OPENING MOTOR=1 DIR=1
    8500 RES=0 BUTTON=0 FINOPN=1 FINCLS=0 STATE= OPENED MOTOR=0 DIR=0
    9000 RES=0 BUTTON=0 FINOPN=0 FINCLS=0 STATE= OPENED MOTOR=0 DIR=0
   13000 RES=0 BUTTON=1 FINOPN=0 FINCLS=0 STATE= OPENED MOTOR=0 DIR=0
   13500 RES=0 BUTTON=1 FINOPN=0 FINCLS=0 STATE=CLOSING MOTOR=1 DIR=0
   14000 RES=0 BUTTON=0 FINOPN=0 FINCLS=0 STATE=CLOSING MOTOR=1 DIR=0
   18000 RES=0 BUTTON=0 FINOPN=0 FINCLS=1 STATE=CLOSING MOTOR=1 DIR=0
   18500 RES=0 BUTTON=0 FINOPN=0 FINCLS=1 STATE= CLOSED MOTOR=0 DIR=0
   19000 RES=0 BUTTON=0 FINOPN=0 FINCLS=0 STATE= CLOSED MOTOR=0 DIR=0
End of simulation 15/10/30 14:20:22

波形では、CURRENT[3:0] および NEXT[3:0]00010010010010000001 の順に遷移していることが確認できます。

波形図(ワンホット方式)
                     |0        |5000     |10000    |15000    |20000
CLK        ─┐_┌─┐_┌─┐_┌─┐_┌─┐_┌─┐_┌─┐_┌─┐_┌─┐_┌─┐_┌─┐_┌─┐_
RES        ──┐           └──────────────────────────────────────
BUTTON     ─────┐   └──────────────┐   └──────────────────────
FINOPN     ─────────────────┐ └────────────────────────────────
FINCLS     ─────────────────────────────────────────────┐ └────
CURRENT[3:0] xx│ 0001 │ 0010     │ 0100             │ 1000 │ 0001
NEXT[3:0]    xx│ 0001 │ 0010     │ 0100             │ 1000 │ 0001
MOTOR      ─────────────┐         └──────────┐         └──────
DIR        ─────────────┐         └───────────────────────────

CLOSED(0001) → OPENING(0010) → OPENED(0100) → CLOSING(1000) → CLOSED(0001)

(10)修了判定 2

設問
  • 本文の中で紹介した2種類のステートマシンを論理合成し、回路規模を比較する。
  • 論理合成後、レポートファイルにより回路規模を調べ、以下の空欄に記入する。

エンコード/デコード方式 (TRAY_ENCDEC)

Areaレポート
****************************************
レポート : area
回路     : TRAY_ENCDEC
****************************************
ポート数:           7
ネット数:          18
セル数:            11
セル種類:           7

組み合わせ回路:      12
非組み合わせ回路:    18

合計:              30
Timingレポート(TRAY_ENCDEC)
****************************************
レポート : timing
回路     : TRAY_ENCDEC
****************************************

ライブラリ:        hd350s
配線遅延モデル:    hd350s_05k
コンディション:    MAX567

クロック CLK (rise edge)        0.00    0.00
CURRENT_reg[1]/D (FD2)          0.00    3.97
データ到着時刻                           3.97

クロック CLK (rise edge)       20.00   20.00
クロックスキュー                -1.00   19.00
ライブラリのセットアップタイム  -0.80   18.20
データ到着要求時刻                      18.20
--------------------------------------------
データ到着要求時刻                      18.20
データ到着時刻                          -3.97
--------------------------------------------
タイミング余裕                          14.23
🔍 合成後ゲートネットリスト(エンコード/デコード方式)

論理合成後、BUTTON, FINOPN, FINCLS → 複数の AND/OR/NOT ゲート → フリップフロップ2個 (FD2) → MOTOR, DIR という回路構造に最適化されます。ステートデコーダを含む比較的コンパクトな回路です(合計30ゲート)。

ワンホット方式 (TRAY_ONEHOT)

Areaレポート
****************************************
レポート : area
回路     : TRAY_ONEHOT
****************************************
ポート数:           7
ネット数:          22
セル数:            14
セル種類:           9

組み合わせ回路:      22
非組み合わせ回路:    35

合計:              57
Timingレポート(TRAY_ONEHOT)
****************************************
レポート : timing
回路     : TRAY_ONEHOT
****************************************

ライブラリ:        hd350s
配線遅延モデル:    hd350s_05k
コンディション:    MAX567

クロック CLK (rise edge)        0.00    0.00
CURRENT_reg[0]/D (FD4)          0.00    3.97
データ到着時刻                           3.97

クロック CLK (rise edge)       20.00   20.00
クロックスキュー                -1.00   19.00
ライブラリのセットアップタイム  -0.80   18.20
データ到着要求時刻                      18.20
--------------------------------------------
データ到着要求時刻                      18.20
データ到着時刻                          -3.97
--------------------------------------------
タイミング余裕                          14.23
🔍 合成後ゲートネットリスト(ワンホット方式)

論理合成後、フリップフロップが4個 (FD4) となり、各ステートに1つのFFが対応します。デコーダが不要になる代わりに組み合わせ回路も増加し、合計57ゲートとなります。タイミング余裕はエンコード方式と同じですが、回路が大きくなるほど速度優位が現れます。

📊 回路規模比較まとめ
項目エンコード/デコード方式ワンホット方式
ポート数77
ネット数1822
セル数1114
セル種類79
組み合わせ回路1222
非組み合わせ回路(FF)1835
合計3057

回路規模の比較

エンコード/デコード方式
30
ワンホット方式
57
🌸
たいへん
よく
できました