shtaxxx日記

コンピュータアーキテクチャについて研究している研究者の日記や技術紹介

Veriloggenで条件・遅延付き代入を含むVerilog HDLのステートマシンを作る

引き続きVeriloggenのお話です.今回はVeriloggenのFSMライブラリの条件付き代入や遅延付き代入を紹介します.

VeriloggenはPythonVerilog HDLのソースコードを組み立てることができるライブラリです. 今回の例はGitHubここにあります.

github.com

lib.fsmでステートマシンを組み立てる

    m = Module('blinkled')
    fsm = lib.FSM(m, 'fsm')

ステートマシン (FSM) を作るにはlib.FSMを使うのが便利です.1つめの引数はModuleオブジェクト,2つめはFSM名のプレフィックスです.

    valid = m.Reg('valid', initval=0)
    fsm.add( valid(1) )
    fsm.goto_next()

FSMオブジェクトは状態を管理しており,addメソッドを呼び出すことにより,その状態での代入を追加することができます.addメソッド複数回呼び出すことで,複数の代入を追加することもできます.goto_nextメソッドを呼び出すことで,状態番号を1インクリメントし,次の状態に遷移します.この例は以下の様なVerilogのコードに対応します.

      case(fsm)
        // ...
        fsm_1: begin
          valid <= 1;
          fsm <= fsm_2;
        end
        // ...
     endcase

条件付き代入

各状態での代入には以下のように条件を付加することができます.この例では,ready==1が成立するときのみvalidに1が代入されます.同様に,goto_nextメソッドにも遷移条件を付加することができます.

    fsm.add( valid(1), cond=(ready==1) )
    fsm.goto_next(cond=(ready==1))

遅延付き代入

各状態での代入には更に遅延を付加することができます.以下の例では,delay=1を指定しているので,1サイクル後にvalidに0が代入されます.

    fsm.add( valid(0), delay=1 )

keepを指定することで,代入を繰り返すことができます.以下の例では,keep=3を指定しているので,3サイクル連続でvalidに1が代入されます.

    fsm.add( valid(1), keep=3)
    # 以下と等価
    fsm.add( valid(1), delay=0)
    fsm.add( valid(1), delay=1)
    fsm.add( valid(1), delay=2)

条件付き代入と遅延付き代入・繰り返し代入は併用することができます.遅延付きの場合の代入条件は即座に評価され保存されます.そして,指定されたサイクル数の遅延の後,評価済みの条件が成立していたのであれば,代入を行います.keepが指定されている場合にも条件は予め評価され保存されます.

    fsm.add( valid(0), cond=(ready==1), delay=1 )

以下のように,同一時刻で遅延付き代入と通常の代入など,複数の代入が競合する場合にはどうなるでしょうか.VeriloggenのFSMライブラリは,代入の指示が新しい方が優先されます.以下の例では,Aの代入とBの代入が競合しますが,Bが優先されます.

    fsm.add( valid(1) )
    fsm.add( valid(0), delay=1) # A
    fsm.goto_next()
    fsm.add( valid(1) ) # B
    fsm.add( valid(0), delay=1)
    fsm.goto_next()

これと条件付き代入を併用することで,BRAMを使ったFIFOからの読み出しなどが簡単に書けます.以下の例では,emptyが0のときにdeqに1を代入し,読み出しリクエストを発行します.そしてその次のサイクルでdeqを0にします.更に読み出した値を2サイクル後にdst_valに代入します.

    fsm.add( deq(1), cond=Not(empty) )
    fsm.add( deq(0), delay=1 )
    fsm.add( dst_val(rdata), delay=2 )
    fsm.goto_next(cond=Not(empty))

Verilog HDLでこのような処理を書く場合には,以下のように,現在の状態と条件を元に,明示的にdeq=0やdst_val=rdataなどの処理を未来の状態に追加しなければならないため,記述が煩雑になりがちです.Veriloggenの場合には,遅延を気にせず,現在の状態とそれに対応する処理を書けば良いため,記述がシンプルになります.

      case(fsm)
        // ...
        fsm_1: begin
          if(!empty) begin
            deq <= 1;
            fsm <= fsm_2;
          end
        end
        fsm_2: begin
          deq <= 0;
          // other operations
          fsm <= fsm_3;
        end
        fsm_3: begin
          dst_val <= rdata;
          // other operations
          fsm <= fsm_4;
        end
        // ...
     endcase

Veriloggenを用いたFSMと各種代入のサンプルコード

現在のVeriloggenはInitial文および関連する文法をサポートしているので,テストベンチも書くことができます.Pythonだけでハードウェアのメタプログラミング設計もできるし,検証ができるので,ちょっと楽しい.

import sys
import os

from veriloggen import *

def mkLed():
    m = Module('blinkled')
    clk = m.Input('CLK')
    rst = m.Input('RST')
    ready = m.Input('ready')
    valid = m.OutputReg('valid', initval=0)
    count = m.Reg('count', width=32, initval=0)
    
    fsm = lib.FSM(m, 'fsm')

    for i in range(2):
        fsm.goto_next()

    # assert valid, then de-assert at the next cycle
    fsm.add( valid(1) )
    fsm.add( valid(0), delay=1 )
    
    for i in range(2):
        fsm.goto_next()

    # assert valid and go to the next state if a condition is satisfied now
    # then de-assert at the next cycle with the same condition
    fsm.add( valid(1), cond=(ready==1) )
    fsm.add( valid(0), cond=(ready==1), delay=1 )
    fsm.goto_next(cond=(ready==1))
    
    for i in range(2):
        fsm.goto_next()

    # condition alias
    c = AndList((count >= 16), (ready==1))

    # assert valid 1 cycle later if a condition is satisfied now
    # then de-assert 3 cycles later with the same condition
    for i in range(4):
        fsm.add( valid(1), cond=c, delay=1, keep=2)
        fsm.add( valid(0), cond=c, delay=3 )
        fsm.goto_next(cond=c)

    # build always statement
    m.Always(Posedge(clk))(
        If(rst)(
            m.make_reset(),
        ).Else(
            count(count + 1),
            fsm.make_case()
        ))

    return m

def mkTest():
    m = Module('test')
    clk = m.Reg('CLK')
    rst = m.Reg('RST')
    ready = m.Reg('ready', initval=0)
    valid = m.Wire('valid')    

    uut = m.Instance(mkLed(), 'uut',
                     #ports=(('CLK', clk), ('RST', rst), ('ready', ready), ('valid', valid)))
                     ports=connect_same_name(clk, rst, ready, valid))

    lib.simulation.setup_waveform(m, uut)
    lib.simulation.setup_clock(m, clk, hperiod=5)
    init = lib.simulation.setup_reset(m, rst, m.make_reset(), period=100)

    init.add(
        [ lib.simulation.next_clock(clk) for i in range(8) ],
        ready(1),
        Delay(1000),
        Systask('finish'),
    )

    return m
    
if __name__ == '__main__':
    test = mkTest()
    verilog = test.to_verilog('tmp.v')
    print(verilog)

生成されるVerilog HDLコード

module test
(

);

  reg CLK;
  reg RST;
  reg ready;
  wire valid;

  blinkled
  uut
  (
    .CLK(CLK),
    .RST(RST),
    .ready(ready),
    .valid(valid)
  );


  initial begin
    $dumpfile("uut.vcd");
    $dumpvars(0, uut);
  end


  initial begin
    CLK = 0;
    forever begin
      #5 CLK = (!CLK);
    end
  end


  initial begin
    RST = 0;
    ready = 0;
    #100;
    RST = 1;
    #100;
    RST = 0;
    @(posedge CLK);
    #1;
    @(posedge CLK);
    #1;
    @(posedge CLK);
    #1;
    @(posedge CLK);
    #1;
    @(posedge CLK);
    #1;
    @(posedge CLK);
    #1;
    @(posedge CLK);
    #1;
    @(posedge CLK);
    #1;
    ready = 1;
    #1000;
    $finish;
  end


endmodule



module blinkled
(
  input CLK,
  input RST,
  input ready,
  output reg valid
);

  reg [(32 - 1):0] count;
  reg [(32 - 1):0] fsm;
  localparam fsm_init = 0;
  localparam fsm_1 = 1;
  localparam fsm_2 = 2;
  reg [(32 - 1):0] _d1_fsm;
  localparam fsm_3 = 3;
  localparam fsm_4 = 4;
  reg _fsm_cond_4_1_0;
  localparam fsm_5 = 5;
  localparam fsm_6 = 6;
  localparam fsm_7 = 7;
  reg _fsm_cond_7_1_1;
  reg [(32 - 1):0] _d2_fsm;
  reg _fsm_cond_7_2_2;
  reg _fsm_cond_7_2_3;
  reg [(32 - 1):0] _d3_fsm;
  reg _fsm_cond_7_3_4;
  reg _fsm_cond_7_3_5;
  reg _fsm_cond_7_3_6;
  localparam fsm_8 = 8;
  reg _fsm_cond_8_1_7;
  reg _fsm_cond_8_2_8;
  reg _fsm_cond_8_2_9;
  reg _fsm_cond_8_3_10;
  reg _fsm_cond_8_3_11;
  reg _fsm_cond_8_3_12;
  localparam fsm_9 = 9;
  reg _fsm_cond_9_1_13;
  reg _fsm_cond_9_2_14;
  reg _fsm_cond_9_2_15;
  reg _fsm_cond_9_3_16;
  reg _fsm_cond_9_3_17;
  reg _fsm_cond_9_3_18;
  localparam fsm_10 = 10;
  reg _fsm_cond_10_1_19;
  reg _fsm_cond_10_2_20;
  reg _fsm_cond_10_2_21;
  reg _fsm_cond_10_3_22;
  reg _fsm_cond_10_3_23;
  reg _fsm_cond_10_3_24;
  localparam fsm_11 = 11;

  always @(posedge CLK) begin
    if(RST) begin
      valid <= 0;
      count <= 0;
      fsm <= fsm_init;
      _d1_fsm <= fsm_init;
      _fsm_cond_4_1_0 <= 0;
      _fsm_cond_7_1_1 <= 0;
      _d2_fsm <= fsm_init;
      _fsm_cond_7_2_2 <= 0;
      _fsm_cond_7_2_3 <= 0;
      _d3_fsm <= fsm_init;
      _fsm_cond_7_3_4 <= 0;
      _fsm_cond_7_3_5 <= 0;
      _fsm_cond_7_3_6 <= 0;
      _fsm_cond_8_1_7 <= 0;
      _fsm_cond_8_2_8 <= 0;
      _fsm_cond_8_2_9 <= 0;
      _fsm_cond_8_3_10 <= 0;
      _fsm_cond_8_3_11 <= 0;
      _fsm_cond_8_3_12 <= 0;
      _fsm_cond_9_1_13 <= 0;
      _fsm_cond_9_2_14 <= 0;
      _fsm_cond_9_2_15 <= 0;
      _fsm_cond_9_3_16 <= 0;
      _fsm_cond_9_3_17 <= 0;
      _fsm_cond_9_3_18 <= 0;
      _fsm_cond_10_1_19 <= 0;
      _fsm_cond_10_2_20 <= 0;
      _fsm_cond_10_2_21 <= 0;
      _fsm_cond_10_3_22 <= 0;
      _fsm_cond_10_3_23 <= 0;
      _fsm_cond_10_3_24 <= 0;
    end else begin
      count <= (count + 1);
      _d1_fsm <= fsm;
      _d2_fsm <= _d1_fsm;
      _d3_fsm <= _d2_fsm;
      case(_d3_fsm)
        fsm_7: begin
          if(_fsm_cond_7_3_6) begin
            valid <= 0;
          end 
        end
        fsm_8: begin
          if(_fsm_cond_8_3_12) begin
            valid <= 0;
          end 
        end
        fsm_9: begin
          if(_fsm_cond_9_3_18) begin
            valid <= 0;
          end 
        end
        fsm_10: begin
          if(_fsm_cond_10_3_24) begin
            valid <= 0;
          end 
        end
      endcase
      case(_d2_fsm)
        fsm_7: begin
          if(_fsm_cond_7_2_3) begin
            valid <= 1;
          end 
          _fsm_cond_7_3_6 <= _fsm_cond_7_3_5;
        end
        fsm_8: begin
          if(_fsm_cond_8_2_9) begin
            valid <= 1;
          end 
          _fsm_cond_8_3_12 <= _fsm_cond_8_3_11;
        end
        fsm_9: begin
          if(_fsm_cond_9_2_15) begin
            valid <= 1;
          end 
          _fsm_cond_9_3_18 <= _fsm_cond_9_3_17;
        end
        fsm_10: begin
          if(_fsm_cond_10_2_21) begin
            valid <= 1;
          end 
          _fsm_cond_10_3_24 <= _fsm_cond_10_3_23;
        end
      endcase
      case(_d1_fsm)
        fsm_2: begin
          valid <= 0;
        end
        fsm_4: begin
          if(_fsm_cond_4_1_0) begin
            valid <= 0;
          end 
        end
        fsm_7: begin
          if(_fsm_cond_7_1_1) begin
            valid <= 1;
          end 
          _fsm_cond_7_2_3 <= _fsm_cond_7_2_2;
          _fsm_cond_7_3_5 <= _fsm_cond_7_3_4;
        end
        fsm_8: begin
          if(_fsm_cond_8_1_7) begin
            valid <= 1;
          end 
          _fsm_cond_8_2_9 <= _fsm_cond_8_2_8;
          _fsm_cond_8_3_11 <= _fsm_cond_8_3_10;
        end
        fsm_9: begin
          if(_fsm_cond_9_1_13) begin
            valid <= 1;
          end 
          _fsm_cond_9_2_15 <= _fsm_cond_9_2_14;
          _fsm_cond_9_3_17 <= _fsm_cond_9_3_16;
        end
        fsm_10: begin
          if(_fsm_cond_10_1_19) begin
            valid <= 1;
          end 
          _fsm_cond_10_2_21 <= _fsm_cond_10_2_20;
          _fsm_cond_10_3_23 <= _fsm_cond_10_3_22;
        end
      endcase
      case(fsm)
        fsm_init: begin
          fsm <= fsm_1;
        end
        fsm_1: begin
          fsm <= fsm_2;
        end
        fsm_2: begin
          valid <= 1;
          fsm <= fsm_3;
        end
        fsm_3: begin
          fsm <= fsm_4;
        end
        fsm_4: begin
          if((ready == 1)) begin
            valid <= 1;
          end 
          _fsm_cond_4_1_0 <= (ready == 1);
          if((ready == 1)) begin
            fsm <= fsm_5;
          end 
        end
        fsm_5: begin
          fsm <= fsm_6;
        end
        fsm_6: begin
          fsm <= fsm_7;
        end
        fsm_7: begin
          _fsm_cond_7_1_1 <= ((count >= 16) && (ready == 1));
          _fsm_cond_7_2_2 <= ((count >= 16) && (ready == 1));
          _fsm_cond_7_3_4 <= ((count >= 16) && (ready == 1));
          if(((count >= 16) && (ready == 1))) begin
            fsm <= fsm_8;
          end 
        end
        fsm_8: begin
          _fsm_cond_8_1_7 <= ((count >= 16) && (ready == 1));
          _fsm_cond_8_2_8 <= ((count >= 16) && (ready == 1));
          _fsm_cond_8_3_10 <= ((count >= 16) && (ready == 1));
          if(((count >= 16) && (ready == 1))) begin
            fsm <= fsm_9;
          end 
        end
        fsm_9: begin
          _fsm_cond_9_1_13 <= ((count >= 16) && (ready == 1));
          _fsm_cond_9_2_14 <= ((count >= 16) && (ready == 1));
          _fsm_cond_9_3_16 <= ((count >= 16) && (ready == 1));
          if(((count >= 16) && (ready == 1))) begin
            fsm <= fsm_10;
          end 
        end
        fsm_10: begin
          _fsm_cond_10_1_19 <= ((count >= 16) && (ready == 1));
          _fsm_cond_10_2_20 <= ((count >= 16) && (ready == 1));
          _fsm_cond_10_3_22 <= ((count >= 16) && (ready == 1));
          if(((count >= 16) && (ready == 1))) begin
            fsm <= fsm_11;
          end 
        end
      endcase
    end
  end


endmodule

シミュレーション結果

f:id:sxhxtxa:20150827002429p:plain

ちゃんと遅延付き代入や条件付き代入が動作している.