クロックデザインをどのように設計しているのか見てみることにした。以下のようなVerilogファイルをコンパイルして生成されるファイルを観察する。
simple_ff.sv
module simple_ff ( input logic clk, input logic [ 4: 0] in, output logic [ 4: 0] out ); logic [ 4: 0] tmp_ff; always_ff @ (posedge clk) begin tmp_ff <= in; out <= tmp_ff; end
void Vsimple_ff::_eval(Vsimple_ff__Syms* __restrict vlSymsp) { VL_DEBUG_IF(VL_DBG_MSGF("+ Vsimple_ff::_eval\n"); ); Vsimple_ff* const __restrict vlTOPp VL_ATTR_UNUSED = vlSymsp->TOPp; // Body if (((IData)(vlTOPp->clk) & (~ (IData)(vlTOPp->__Vclklast__TOP__clk)))) { vlTOPp->_sequent__TOP__1(vlSymsp); } // Final vlTOPp->__Vclklast__TOP__clk = vlTOPp->clk; }
こんなんでいいのか(笑)。clk
信号がタイムスライス中で反転した場合、_sequent__TOP__1
が実行される。
VL_INLINE_OPT void Vsimple_ff::_sequent__TOP__1(Vsimple_ff__Syms* __restrict vlSymsp) { VL_DEBUG_IF(VL_DBG_MSGF("+ Vsimple_ff::_sequent__TOP__1\n"); ); Vsimple_ff* const __restrict vlTOPp VL_ATTR_UNUSED = vlSymsp->TOPp; // Body vlTOPp->out = vlTOPp->simple_ff__DOT__tmp_ff; vlTOPp->simple_ff__DOT__tmp_ff = vlTOPp->in; }
この論理が少し妙だ。これはout
から順に
vlTOPp->simple_ff__DOT__tmp_ff
からvlTOPp->out
へvlTOPp->in
からvlTOPp->simple_ff__DOT__tmp_ff
へ
となっているがもしこれが逆に配置されていれば普通に信号が通過してin
からout
まで一気通貫で実行されてしまう。これはデータフローを解析して、出力に近い側から評価するようになっているのか?
代入の順番を逆にしてもやはり結果は同じだった。容易に想像できるのは、FFを含むデータフローを作って、出力側から追いかけていき代入するフローを作っていくことだ。ソフトウェアパイプラインのような技法に近い。
色々調べてみると、--debug
オプションを付けるとデータフローグラフを出力してくれるらしい。
verilator --cc --debug simple_ff.sv
とりあえず出てきたdot
ファイルを片っ端から画像に変換する。
for dot in `ls -1 *.dot` do dot -Tjpg -O ${dot} done
最終的なグラフは以下のようになった。これではまだ良く分からないなあ...