Veriloggenって何だろう(5)FSM

Verilog HDL を生成するための記述を python で行うことで FPGA 回路の生成を楽にする為のライブラリー Veriloggen を使ってみたいと思います。
前回までは 順序回路を記述するための Always と Seq に関して書いてきましたが、今回は状態遷移マシンを記述するFSMについて書いていきたいと思います。

状態遷移マシンFSM基本形

これもまた、モジュールにFSMを足しこみ、順番に状態を足していきます。
fsm = FSM(m, 'fsm', clk, rst)
fsm.add( *** ) でその状態での処理を加えていき、fsm.goto_next(cond= **** )で条件がそろった時に次の状態へ移行するようにします。
goto_nextは次の状態を作るとともに、そこへの移動を同時に行います。
これを繰り返すと、まっすぐに流れる状態遷移が作成できます。
最後には、初めに戻るためのgoto_initが書かれることになります。
一連の手続きを状態遷移マシンで記述する時には、これが基本形となるでしょう。
コードの前にこれも負論理非同期リセットが書けるように改造しておきます。

簡単なFSMの記述例とシミュレーション結果

状態3つを順番に繰り返すFSMを書いてみます。入力AがHighになると状態0から状態1に遷移し、さらに入力BがHighになると状態2に遷移。入力AとBがLOWに落ちると状態0に戻るというFSMになります。FSMを作るときにwidthが省略されると32bit分取られてもったいないのでFSMにwidth=2を追加して4状態まで表現できるようにしてあります。

def mkStateMachine():
    m = vg.Module('state_machine')
    clk = m.Input('CLK')
    rst = m.Input('RST')
    a = m.Input('A')
    b = m.Input('B')
    led = m.OutputReg('LED', 2,initval=0)

    fsm = vg.FSM(m, 'fsm', clk, rst, width = 2)

    #state0
    fsm.add(led(0))
    fsm.goto_next(cond=(a==1))
    
    #state1
    fsm.add(led(1))
    fsm.goto_next(cond=(b==1))
    
    #state2
    fsm.add(led(2))
    fsm.goto_init(cond=vg.AndList(a==0,b==0))

    #build always statement
    #reset時にled=0
    fsm.make_always(reset=[led(0)])

    return m

Verilog変換結果は次のようになります。

module state_machine
(
  input CLK,
  input RST,
  input A,
  input B,
  output reg [2-1:0] LED
);

  reg [2-1:0] fsm;
  localparam fsm_init = 0;
  localparam fsm_1 = 1;
  localparam fsm_2 = 2;

  always @(posedge CLK) begin
    if(RST) begin
      LED <= 0;
      fsm <= fsm_init;
    end else begin
      case(fsm)
        fsm_init: begin
          LED <= 0;
          if(A == 1) begin
            fsm <= fsm_1;
          end 
        end
        fsm_1: begin
          LED <= 1;
          if(B == 1) begin
            fsm <= fsm_2;
          end 
        end
        fsm_2: begin
          LED <= 2;
          if((A == 0) && (B == 0)) begin
            fsm <= fsm_init;
          end 
        end
      endcase
    end
  end

endmodule

シミュレーション結果
f:id:feynman911:20181102134839j:plain

任意の状態遷移の書き方

流れが一直線ではない時はどう書いたらいいのか試してみます。
次のような状態遷移を書いてみます。
f:id:feynman911:20181101001913j:plain
複数の行き先がある場合には、goto_nextで繋げられないので、下のような書き方になるでしょう。
inc( )で次の状態を作るのと、go_to( )で移動先の番号と移動の条件を記述します。

def mkStateMachine():
    m = vg.Module('state_machine')
    clk = m.Input('CLK')
    rst = m.Input('RST')
    start = m.Input('START')
    stop = m.Input('STOP')
    count = m.OutputReg('COUNT', 8,initval=0)

    fsm = vg.FSM(m, 'fsm', clk, rst, width = 2)

    #state0 Clear
    fsm.add(count(0))
    fsm.goto_next(cond=(start==1))
    
    #state1 Count up
    fsm.add(count.inc())
    fsm.goto(3,cond=(count>=30))
    fsm.goto(0,cond=vg.AndList(start==0,stop==0))
    fsm.goto_next(cond=(stop==1))
    
    #state2 Hold
    fsm.goto(0, cond=vg.AndList(start==0,stop==0))
    fsm.goto(1, cond=vg.AndList(start==1,stop==0))
    fsm.inc()
    
    #state3 End
    fsm.goto(0,cond=vg.AndList(start==0,stop==0))

    #build always statement
    #reset時にcount=0
    fsm.make_always(reset=[count(0)])

    return m

変換結果とシミュレーション結果は下記になります。

module state_machine
(
  input CLK,
  input RST,
  input START,
  input STOP,
  output reg [8-1:0] COUNT
);

  reg [2-1:0] fsm;
  localparam fsm_init = 0;
  localparam fsm_1 = 1;
  localparam fsm_2 = 2;
  localparam fsm_3 = 3;

  always @(posedge CLK) begin
    if(RST) begin
      COUNT <= 0;
      fsm <= fsm_init;
    end else begin
      case(fsm)
        fsm_init: begin
          COUNT <= 0;
          if(START == 1) begin
            fsm <= fsm_1;
          end 
        end
        fsm_1: begin
          COUNT <= COUNT + 1;
          if(COUNT >= 30) begin
            fsm <= fsm_3;
          end 
          if((START == 0) && (STOP == 0)) begin
            fsm <= fsm_init;
          end 
          if(STOP == 1) begin
            fsm <= fsm_2;
          end 
        end
        fsm_2: begin
          if((START == 0) && (STOP == 0)) begin
            fsm <= fsm_init;
          end 
          if((START == 1) && (STOP == 0)) begin
            fsm <= fsm_1;
          end 
        end
        fsm_3: begin
          if((START == 0) && (STOP == 0)) begin
            fsm <= fsm_init;
          end 
        end
      endcase
    end
  end

endmodule

f:id:feynman911:20181102133539j:plain

FSMでの負論理非同期リセット

負論理非同期リセットを使いたい場合には fsm.py に次の定義を追加して、使用します。

def make_always_n(self, reset=(), body=(), case=True):
        if self.done:
            #raise ValueError('make_always() has been already called.')
            return

        self.done = True

        part_reset = self.make_reset(reset)
        part_body = list(body) + list(self.make_case()
                                      if case else self.make_if())
        self.m.Always(vtypes.Posedge(self.clk),vtypes.Negedge(self.rst))(
            vtypes.If(~self.rst)(
                part_reset,
            )(
                part_body,
            ))

リセットの難しさ

同期リセットをするべきなのか、非同期リセットをするべきなのかは、使うFGPAのメーカにもよる話で、安定動作をさせるためには、かなり難しい話の様です。次のリンクが参考になりそうです。
dora.bk.tsukuba.ac.jp