ステートマシンを回路で書く
(1)ここで学ぶ内容
- CDトレイの制御回路を例に、記述の詳細やステートレジスタの構成方法について学ぶ
目標
以下の項目を理解し説明できる
- ステートマシンの回路構造を理解して説明できる
- ステートマシンの回路を記述できる
- ステートマシンを用いて制御回路を設計できる
- ステートレジスタの構成方法を理解し説明できる
修了判定1
2種類のステートマシン記述をシミュレーションして、動作を確認する
修了判定2
2種類のステートマシン記述を論理合成して、回路規模を比較する
ステートマシンをHDLで記述すると簡単にかけます。HDLは便利だと感じると思います。もし、ステートマシンの基本を覚えていない場合はL4ユニットをみてください。
まず実験:使っていない状態に入ったら、戻ってこられる?
3つの状態を2ビットで表すと、残る1パターンは設計が自分では通らない状態になります。そこへ入ってしまったとき、戻ってこられるかどうかを、動かせるサンプルで確かめます。考え方はこのページの補足にまとめてあるよ。
2つのステートマシンと自動判定テストベンチをダウンロード(ZIP)
回路の仕様
- 状態は IDLE(
00)、RUN(01)、DONE(10)の3つ。11は使いません。 - 遷移は IDLE →(start=1)→ RUN → DONE → IDLE です。
state_stuck側のcaseにはdefaultがありません。state_safe側にはdefault: q_safe <= IDLE;があります。- それ以外は同じです。リセットはどちらもIDLEへ戻します。
回路本体 fsm_recovery.v を見る
`timescale 1ns/1ps
`default_nettype none
// Three states held in two bits, so 2'b11 is never reached by the design.
// The two machines differ only in whether the case has a default branch.
module fsm_recovery (
input wire clk,
input wire rst_n,
input wire start,
output wire [1:0] state_stuck,
output wire [1:0] state_safe,
output wire done_stuck,
output wire done_safe
);
localparam [1:0] IDLE = 2'b00,
RUN = 2'b01,
DONE = 2'b10;
reg [1:0] q_stuck, q_safe;
// No default: an unused state has no transition, so the value stays.
always @(posedge clk or negedge rst_n) begin
if (!rst_n) q_stuck <= IDLE;
else begin
case (q_stuck)
IDLE: if (start) q_stuck <= RUN;
RUN: q_stuck <= DONE;
DONE: q_stuck <= IDLE;
endcase
end
end
// With default: anything unexpected goes back to IDLE.
always @(posedge clk or negedge rst_n) begin
if (!rst_n) q_safe <= IDLE;
else begin
case (q_safe)
IDLE: if (start) q_safe <= RUN;
RUN: q_safe <= DONE;
DONE: q_safe <= IDLE;
default: q_safe <= IDLE;
endcase
end
end
assign state_stuck = q_stuck;
assign state_safe = q_safe;
assign done_stuck = (q_stuck == DONE);
assign done_safe = (q_safe == DONE);
endmodule
`default_nettype wire
テストベンチ tb_fsm_recovery.v を見る
`timescale 1ns/1ps
`default_nettype none
// Runs both machines through a normal sequence, then writes the unused state
// 2'b11 straight into each state register and watches what happens.
module tb_fsm_recovery;
reg clk = 0;
reg rst_n, start;
wire [1:0] state_stuck, state_safe;
wire done_stuck, done_safe;
integer checks = 0;
integer i;
localparam [1:0] IDLE = 2'b00, RUN = 2'b01, DONE = 2'b10, UNUSED = 2'b11;
fsm_recovery dut(.clk(clk), .rst_n(rst_n), .start(start),
.state_stuck(state_stuck), .state_safe(state_safe),
.done_stuck(done_stuck), .done_safe(done_safe));
always #5 clk = ~clk;
task check;
input [1:0] actual, expected;
input [8*14-1:0] label;
begin
checks = checks + 1;
if (actual !== expected)
$fatal(1, "FAIL: check=%0d %0s actual=%b expected=%b time=%0t",
checks, label, actual, expected, $time);
end
endtask
task step; // one clock, sampled after the update
begin
@(posedge clk);
#1;
end
endtask
initial begin
$dumpfile("fsm_recovery.vcd");
$dumpvars(0, tb_fsm_recovery);
rst_n = 1; start = 0;
#1 rst_n = 0;
#1 check(state_stuck, IDLE, "reset stuck");
check(state_safe, IDLE, "reset safe");
@(negedge clk); rst_n = 1;
// Normal sequence: IDLE -> RUN -> DONE -> IDLE
@(negedge clk); start = 1;
step; check(state_stuck, RUN, "to RUN"); check(state_safe, RUN, "to RUN");
@(negedge clk); start = 0;
step; check(state_stuck, DONE, "to DONE"); check(state_safe, DONE, "to DONE");
step; check(state_stuck, IDLE, "to IDLE"); check(state_safe, IDLE, "to IDLE");
// Write the unused state into both registers from the testbench.
@(negedge clk);
dut.q_stuck = UNUSED;
dut.q_safe = UNUSED;
#1;
check(state_stuck, UNUSED, "injected");
check(state_safe, UNUSED, "injected");
$display(" injected 2'b11 -> stuck=%b safe=%b", state_stuck, state_safe);
// The machine without a default never leaves it.
for (i = 0; i < 5; i = i + 1) begin
step;
check(state_stuck, UNUSED, "still stuck");
if (i == 0) begin
check(state_safe, IDLE, "recovered");
$display(" after 1 clock -> safe=%b (recovered)", state_safe);
end
end
$display(" after 5 clocks -> stuck=%b (held) done_stuck=%b", state_stuck, done_stuck);
// ...and it works normally again.
@(negedge clk); start = 1;
step; check(state_safe, RUN, "restart RUN");
@(negedge clk); start = 0;
step; check(state_safe, DONE, "restart DONE");
step; check(state_safe, IDLE, "restart IDLE");
$display("PASS: fsm recovery (%0d checks)", checks);
$finish;
end
initial begin
#2000;
$fatal(1, "FAIL: testbench timeout");
end
endmodule
`default_nettype wire
実行方法
iverilog -g2012 -Wall -s tb_fsm_recovery -o sim.vvp fsm_recovery.v tb_fsm_recovery.v
vvp sim.vvp
まず通常の遷移を両方で確かめ、そのあと状態レジスタへ直接 2'b11 を書き込みます。テストベンチから dut.q_stuck = 2'b11; のように階層参照で代入する方法です。Windows / Icarus Verilog 13.0で2026年10月27日に実行した結果です。
injected 2'b11 -> stuck=11 safe=11
after 1 clock -> safe=00 (recovered)
after 5 clocks -> stuck=11 (held) done_stuck=0
PASS: fsm recovery (19 checks)
読み取れること
default の無い側は、5クロック経っても 11 のままです。どの分岐にも当てはまらないので次の状態が決まらず、値が残り続けるんだ。出力も立たないので、外から見ると「固まった」ように見えます。default のある側は次のクロックでIDLEへ戻り、そのあと通常の遷移も再開できました。
この試験で確かめているのはRTLモデルの復帰だよ。合成後のFSM再符号化や到達不能状態の最適化、実機の故障からの復旧までを保証するものではないんだ。対象ツールのsafe-state設定と実装結果は別に確認してね。
到達する状態だけ試すと、見つからない
同じ回路に対して、設計が自分で通る状態(IDLE→RUN→DONE→IDLE)だけを試すテストベンチを実行すると、こうなります。
PASS: partial check (reachable states only)
合格表示です。未使用の状態へは自分では行かないのだから、普通に動かしているだけでは永久に見つかりません。意図的に注入して初めて確かめられる種類の問題なんだ。※この簡易テストベンチは、サイト側の検証スクリプトで実行したもので、配布ZIPには入っていません。
わざと壊して、テストの働きも確かめる
| 壊した場所 | 結果 |
|---|---|
default を消す | check=12 recovered actual=11 expected=00(復帰しない) |
| RUNからの遷移先をIDLEに間違える | check=6 to DONE actual=00 expected=10 |
確認問題
状態を4つ(2ビットを使い切る)にすれば、この問題は起きなくなるでしょうか。
答え
2ビットをすべて使えば「どの値も定義済みの状態」になるので、未定義の値に入る余地は減ります。ただし、状態の数が2のべき乗にならない設計ではまた余りが出ますし、ワンホット方式ならビット数ぶんの組合せのうち有効なのはごく一部です(4状態なら16通りのうち4通り)。余りが出る以上、「余った値に入ったらどうするか」を決めておく話は残ります。ワンホットの考え方はこのページの(7)(8)、電子錠での確認は実習集にあります。
この後の節では、トレイ制御回路を題材に、ステートマシンの記述方法と状態レジスタの構成を見ていきます。
(2)ステートマシンの例
[ ■ ] [ ▶ ] [ ⏸ ] [ ⏮ ] [ ⏭ ] [ ⏏ ]
このユニットでは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になっているビットが移動することで実現します。
ステートマシンの本体の記述はエンコード/デコード方式と同じです。これ以外のステートレジスタとモータ制御回路の記述は変わりません。
🐾 もふねこの現場メモ:
実習中の学生から「自動販売機の制御回路が、長時間動かすと突然フリーズする」という相談を受けたことがあるんだ。2ビットで3状態しか使っていないのに、残る1つの状態から出る道を書いていなかった例だよ。その状態に入る原因までRTLでは決められないけれど、入ったときに戻れるかどうかはRTLで決まるし、値を注入すれば確かめられる。いっぽう、このあと出てくるトレイ制御は4状態すべてを使うので、2ビットに余りはないよ。そこで使っている default: NEXT <= UNKNOWN;(2'bxx)は復帰先の指定ではなく、「この条件は起こらないので最適化してよい」と合成ツールへ伝える書き方なんだ。同じ default でも目的が違うから、戻したいときは初期状態のような具体的な値を書こう!
※未使用の状態からの復帰は このページの実習 で実際に注入して確かめられるよ。考え方は 補足 にまとめてあるよ🐾
(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] が 00 → 01 → 10 → 11 → 00 の順に遷移していることが確認できます。
|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] が 0001 → 0010 → 0100 → 1000 → 0001 の順に遷移していることが確認できます。
|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
回路 : TRAY_ENCDEC
****************************************
ポート数: 7
ネット数: 18
セル数: 11
セル種類: 7
組み合わせ回路: 12
非組み合わせ回路: 18
合計: 30
****************************************
レポート : 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
回路 : TRAY_ONEHOT
****************************************
ポート数: 7
ネット数: 22
セル数: 14
セル種類: 9
組み合わせ回路: 22
非組み合わせ回路: 35
合計: 57
****************************************
レポート : 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ゲートとなります。タイミング余裕はエンコード方式と同じですが、回路が大きくなるほど速度優位が現れます。
| 項目 | エンコード/デコード方式 | ワンホット方式 |
|---|---|---|
| ポート数 | 7 | 7 |
| ネット数 | 18 | 22 |
| セル数 | 11 | 14 |
| セル種類 | 7 | 9 |
| 組み合わせ回路 | 12 | 22 |
| 非組み合わせ回路(FF) | 18 | 35 |
| 合計 | 30 | 57 |
回路規模の比較
登録出力と状態デコードを混同しない
入門テンプレートのIDLE→RUN→DONE→IDLEで、RUNの枝に active_led <= 1; と state <= DONE; を並べると、更新後はstateがDONE、active_ledが1になるよ。代入時に参照しているのは更新前のRUNだからだね。assign active_led = (state == RUN); に変えると点灯するサイクルが変わる。1プロセスと2プロセスの書き換えでは、状態名だけでなく出力の時刻も表にして比べよう。
startが1のままだとIDLEに戻ったあと再び開始する仕様なのか、1パルスだけ受け付けるのかも決めてね。電子錠の実習では、入力を1クロックのパルスとし、全入力の状態遷移を別の表で照合しているよ。
実習で確かめる、つまずきの補足
未使用の状態からの復帰を確認する
2ビットで3状態を表すなら、残る1パターンの扱いも決めよう。次状態の組合せ処理では、先頭に既定値を置き、default で初期状態へ戻す方法があるよ。状態FFはリセットで初期状態へ設定する。
ただしRTLの default だけで実機のあらゆる故障からの復旧を保証できるわけではない。合成のFSM再符号化や到達不能状態の最適化が関係するから、必要な場合は対象ツールのsafe-state設定と実装結果も確認しよう。RTLで不正状態を注入する試験は、あくまでRTLモデルの復帰確認だよ。VivadoにはFSM_SAFE_STATEという回復ロジックを挿入する属性があるけれど、対象版の仕様を確認して使ってね。
非同期入力の同期化、ボタンのチャタリング除去、状態遷移の論理は別の役割。状態数を増やす前に、入力が1サイクルのイベントなのか、押している間のレベルなのかを決めてね。電子錠の実習 で、同時押し・連続入力・途中リセットまで確認しよう。