IT系メモ

興味のあったことや、勉強したことなどをメモしていきます。

FSMの記述

FSMがよくわかっていないので調べてみた。

  1 module fsm_cc4_2
  2        (output reg gnt,
  3         input dly, done, req, clk, rst_n);
  4 
  5     parameter [1:0] IDLE = 2'b00,
  6                     BBUSY = 2'b01,
  7                     BWAIT = 2'b10,
  8                     BFREE = 2'b11;
  9 
 10     reg [1:0] state, next;
 11 
 12     always @(posedge clk or negedge rst_n)
 13         if (!rst_n) state <= IDLE;
 14         else        state <= next;
 15 
 16     always @(state or dly or done or req) begin
 17         next = 2'bx;
 18         gnt = 1'b0;
 19         case (state)
 20             IDLE :  if (req)        next = BBUSY;
 21                     else            next = IDLE;
 22             BBUSY: begin
 23                     gnt = 1'b1;
 24                     if (!done)      next = BBUSY;
 25                     else if ( dly)  next = BWAIT;
 26                     else            next = BFREE;
 27                    end
 28             BWAIT: begin
 29                     gnt = 1'b1;
 30                     if (!dly)       next = BFREE;
 31                     else            next = BWAIT;
 32                    end
 33             BFREE:  if (req)        next = BBUSY;
 34                     else            next = IDLE;
 35         endcase
 36     end
 37 endmodule


書き方はポート宣言を見てもらえばわかるとおり、verilog2001。ステートはparameter宣言で。理由はあとから値を変更したいときにラッパーを作ればできるからのようだ。defineはコンパイラ依存なのでIEEEは使わないように言ってたはず。

12〜14行目の順序回路の部分はノンブロッキングで記述。FFへの入力は一つの信号だけ。ここの論理は簡単に。

16行目〜は組み合わせ回路。ここで次のステートを決める論理を記述する。センシビリティリストにはすべての入力を記述。ここではブロッキングで記述する。

17行目でnextを不定で初期化している理由がわからなかった。
定数で初期化した場合( next = 2'b11; )、タイミングパスが長くなってしまうため、できる限り行わないのが良かったはず。
そもそも組み合わせ回路を意図して記述するのだから、初期化する必要性があるの?
論理合成ツールによってAREAが小さくなるの?などの疑問があった。

17行目のnextの記述方法としては、

  1. すべて X で初期化する
  2. 定数を入れる
  3. ステートレジスタの値を入れる

の3種類が考えらる。

①の場合では、論理合成前のRTLシミュレーションで、case文の中でnextへの代入が抜けている状態があると、ここで入力した不定が伝搬するはずなので気づくだろうとのこと。たしかにcase文が長くなったときに、nextへの代入が抜けていないかは不安になるので、この記述方法は良いかもしれない。

また論理合成時には論理合成ツールにdon't careであることを明示する。これによって論理合成ツールがどういうことをするのかは勉強不足でわからないのだけど、想像するにタイミングを収束するようにがんばらない、もしくは不定が入力されているので無視する(回路を作らない)ってことだと思う。これだとシミュレーション時には記述漏れを検証できるけど、論理合成後の回路は同じってことになる。

ただRTL記述で不定を入力することに抵抗がある。特に上の記述ではcase文にdefault値が記入されていない。case文のすべての場合分けが記述されているとはいえ、実際の回路ではステート数が40を超える場合もあるかと思う。不定が回って動かなくなるよりかは、defaultに行った方が変な動作こそすれ止まらない方を選ぶんじゃないかな。

18行目ではoutputにデフォルト値を代入し、case文の中(23行目、29行目)でデフォルトとは異なる値を入れる記述方法を取っている。18行目に記述しない場合はすべてのcase文の中でoutputに値を代入する必要になるため、コードが長くなる=論理を追いかけるのが面倒になる。たしかにこの記述の方がわかりやすい。
(デフォルトと変化する場合だけ記述するので)

またこのalways文の出力はいくつあるのかわかり易い。module宣言ではoutput宣言があるのでわかり易いのだけど、always文の中ではコメントで書くしか無い。コードを追いかけていけばoutput信号がいくつあるのかは分かるのだけど、この組み合わせ回路の出力はこれだけです!と言っておくのは保守性を考えると大事!moduleの構造に倣ってalwaysの構造を決めるのは良いことだと思います。

各ステートに入った場合も、ステートを先に記述してしまいがちですが、output信号を記述するとmodule宣言の構造と同じになりますね。

24〜26行目ですが、if-elseの記述方法によって優先順位が決定してしまいますが、等価であるべきですね。論理合成ツールがFSMだと理解して等価になるように論理合成してくれるのでしょうか。


他にもFSM設計するときには、バイナリコード、グレイコード、ワンホットのどれを使うのかといったことがありますが、長くなったので一度ここで切ります。