Veriloggenで条件・遅延付き代入を含むVerilog HDLのステートマシンを作る
引き続きVeriloggenのお話です.今回はVeriloggenのFSMライブラリの条件付き代入や遅延付き代入を紹介します.
VeriloggenはPythonでVerilog HDLのソースコードを組み立てることができるライブラリです. 今回の例はGitHubのここにあります.
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
シミュレーション結果
ちゃんと遅延付き代入や条件付き代入が動作している.