ステートマシンとデータパス

[] [] []


デジタル回路は小さなカウンタからFPGA全体までいろいろあるように見えるが、同期設計されたどんなデジタル回路も1個のステートマシンと等価である。しかし実際の設計では、制御信号を作るステートマシンと制御信号にしたがってデータを処理するデータパスに分けた方が見通しがよくなる

ステートマシン(Finite State Machineを略してFSMともいう)とは、出力である制御信号が各状態ごとに決まっており、状態を示すステートレジスタが遷移条件を示す入力信号にしたがって値を変えてゆくことにより制御信号を変化させる回路である。

ステートマシンの仕様は状態遷移図で書く。図において、状態をブロックで書き、状態間の遷移を示す矢印の横に遷移条件を書く。遷移条件が成り立たなければその状態にとどまるという意味である。

この図はそのままRTL記述に書き下せるので、いきなりRTL記述で書いてもよいが、図の方が一目で理解できるのでよい。

/* FSM */

reg[2:0] state_reg;   // ステートレジスタ

localparam IDLE=3'd0;

localparam SA1 =3'd1;

localparam SA2 =3'd2;

localparam SB1 =3'd3;

localparam SB2 =3'd4;

localparam SB3 =3'd5;

always @(posedge clk or posedge rst)

    begin

    if(rst)

        state_reg <= IDLE;

    else

        case(state_reg)

        IDLE:if(start_a) state_reg <= SA1;

             else if(start_b) state_reg <= SB1;

        SA1: state_reg <= SA2;

        SA2: if(done) state_reg <= IDLE;

        SB1: state_reg <= SB2;

        SB2: state_reg <= SB3;

        SB3: if(done) state_reg <= IDLE;

        default: state_reg <= IDLE;

        endcase

    end

各状態に対応する制御信号は、信号を出力する場合は名前をつけて対応を示す真理値表か論理式を書くが、内部で使う分には制御信号にいちいち名前を付ける必要はなくstate_regをそのまま使ってRTL記述を書けばよい。

 

valid

...

IDLE

0

 

SA1

0

 

SA2

1

 

SB1

0

 

SB2

0

 

SB3

1

 

 

// 制御信号に名前をつける場合

assign valid = ((state_reg==SA2) | (state_reg==SB3));

 

// 制御信号に名前をつけずに使う場合

always @(posedge clk or posedge rst)

    begin

    if(rst)

        count_reg <= 0;

    else if((state_reg==SA1) | (state_reg==SB1))

        count_reg <= dati;

    else if(state_reg!=IDLE)

        count_reg <= count_reg +1'b1;

    end

上記のRTL記述では状態state_regだけを記述しているが、同じcase文の中に各状態におけるデータパスの動作を記述してもよい。ただし、1つのcase文の中にいろいろなレジスタの代入文を記述してしまうと、遷移が読みにくくなってしまうので、もしステートマシンのalways文の中に動作を書くとしても遷移条件に関わる動作に限った方がよい

例えば、ある処理をstart信号で開始してN回繰り返し1回の処理はdone信号が来るまで待つというシーケンスを、状態遷移図に表すと次のようになる。

これのRTL記述はデータパスのレジスタcount_regの代入文もすべての状態において記述することになるので、やや冗長になる。

/* FSM - データパスの動作も記述した場合 */

reg[2:0] state_reg;   // ステートレジスタ

reg[3:0] count_reg;   // カウンタ

localparam IDLE=3'd0;

localparam S1 =3'd1;

localparam S2 =3'd2;

always @(posedge clk or posedge rst)

    begin

    if(rst)

        begin

        state_reg <= IDLE;

        count_reg <= 4'd0;

        end

    else

        case(state_reg)

        IDLE:begin

             if(start) state_reg <= S1;

             count_reg <= 4'd0;

             end

        S1:  begin

             state_reg <= S2;

             count_reg <= count_reg +1'b1;

             end

        S2:  begin

             if(done)

             state_reg <=(count_reg==N)? IDLE:S1;

             count_reg <= 4'd0;

             end

        default: begin

             state_reg <= IDLE;

             count_reg <= 4'd0;

             end

        endcase

    end

この例では、count_regの代入文を次のように分離した方がよいと思う。

always @(posedge clk or posedge rst)

    begin

    if(rst)

        count_reg <= 4'd0;

    else if(state_reg==S1)

        count_reg <= count_reg +1'b1;

    else

        count_reg <= 4'd0;

    end

例えば、もしdefault項のcount_regの代入文を書き忘れると、次のように通常の動作は同じだが冗長な回路になってしまうので注意。

always @(posedge clk or posedge rst)

    begin

    if(rst)

        count_reg <= 4'd0;

    else if(state_reg==S1)

        count_reg <= count_reg +1'b1;

    else if(state_reg==S2)

        count_reg <= 4'd0;

    else

        count_reg <= count_reg;

    end


[] [] []