01signal.com

multi-cycle paths用Timing constraints

このページは、 timingに関する一連のページに属しています。前のページでは、 timing 計算の背後にある理論を説明し、いくつかの timing constraints を記述する方法を示し、 timing closureの原理について説明しました。このページでは、multi-cycle pathsの timing constraints について説明します

序章

multi-cycle paths について最初に知っておくべきことは、通常は悪い考えだということです。このページでは multi-cycle path constraintsの使用方法を説明していますが、結論としては、この手法を完全に回避する必要があります。 ASIC の世界でこの手法を使用する理由はいくつかありますが、 FPGA design では通常、代わりに clock を追加する方が適切です。

とはいえ、この種の timing exception がいつ関連するか見てみましょう。

次の Verilog コードの例を考えてみましょう。

reg foo, bar;
   reg en, pre_en;

   always @(posedge clk)
     begin
	pre_en <= !pre_en;
	en <= pre_en;

	if (en)
	  begin
	     foo <= !foo;
	     bar <= foo;
	  end
     end

この例は不完全であることに注意してください。 おそらく、 synthesis attributes を @pre_en と @enに追加する必要があります。そうしないと、 synthesizerの最適化によって予期しないことが起こる可能性があります。詳細については、以下をご覧ください。

@pre_en は、 clock cycleごとに '0' と '1' の間を行ったり来たりします。 @en も同様ですが、わずかに delayが異なります。

"if (en)" の部分は、"begin" と "end" の間のすべてに対して @en が clock enable としての役割を果たすことを意味します。 @en が Low の場合、 Verilog コードのこの部分では何も起こりません。つまり、 @en が Low の場合、 @foo と @bar は clock edge が存在しないかのように動作します。

この例では、 @en は 2 つの clock cyclesごとに 1 回高くなります。したがって、 @foo と @bar は、 clockの frequency が実際の半分であるかのように動作します。したがって、 timing の要件はそれに応じて緩和できます。 tsetup の計算は、2 倍の大きさの clock period で実行できます。

tholdに関しては、変更はありません。 この timing 要件の計算では、同じ clock edge が両方の flip-flopsに到達することを前提としています。したがって、 thold 分析の例ですでに説明したように、 clock period には意味がありません。したがって、 clock が遅いという錯覚は、 tholdに関しては何の違いもありません。

clock enableを使用する場合

clock enableを使用する正当な理由は 2 つだけです。

clock enable が FPGAの消費電力を改善するかどうかは明らかではありません。おそらく、余分な clock は電力を浪費します。しかし、 clock enable は fan-outの高い signal でもあります。平均して、 clock enable は、代用する clock と同じくらい頻繁に値を変更します。したがって、 logic state (消費電力の主な要因) の変更に関しては、違いはありません。

multi-cycle pathsを使用しなければならない特別な理由がない限り、このページは読み飛ばしていただいてかまいません。技術的に適した design に clock enable がある場合でも、該当する timing exceptionを使用しない方がよい場合があります。特に、この timing exceptionなしで簡単に timing constraints を達成できるのであれば、失敗するリスクを負う価値はありません。

timing exception

上記の Verilog コードに関連して、これらは Vivado用の multi-cycle path constraints です。

set en_regs [all_fanout -endpoints_only -only_cells -flat [get_nets en]]
set_multicycle_path -setup -from $en_regs -to $en_regs 2
set_multicycle_path -hold -from $en_regs -to $en_regs 1

Quartusについても同様です。

set en_regs [get_fanouts en]
set_multicycle_path -setup -from $en_regs -to $en_regs 2
set_multicycle_path -hold -from $en_regs -to $en_regs 1

Vivado と Quartus の違いは最初の行だけです。 command "all_fanout" は Vivadoと一緒に使用され、「get_fanouts」は Quartusと一緒に使用されます。 constraints は、 SDCと一緒に動作する他のツールでも同様です。

最初の行は、 @enで始まるすべての path の末尾にあるすべての synchronous elements (cells) を検索します。この cell objects のリストは $en_regsに格納されます。次の数行は、このリストにある cells で始まり、終わる timing requirements を paths に変更します。

これには多くの説明が必要です。なぜ $en_regs の定義をこのように書いたのでしょうか。なぜ set_multicycle_path commandsが 2 つあるのでしょうか。 thold の timing requirements は multi-cycle pathsの影響を受けないと言ったのに、2 番目の commandに「-hold」と書かれているのはなぜでしょうか。

説明が簡単なので、まずは set_multicycle_path commandsから始めます。

set_multicycle_path commands

上図の通り、 commands は

set_multicycle_path -setup -from $en_regs -to $en_regs 2
set_multicycle_path -hold -from $en_regs -to $en_regs 1

上記の例をある程度一般化するために、これも考えてみましょう。 clock enable が 8 つの clock cyclesごとに 1 回アクティブだった場合、 Verilog code は次のようになります。

reg en;
   reg [2:0] pre_en;

   always @(posedge clk)
     begin
	pre_en <= pre_en + 1;
	en <= (pre_en == 0);
     end

@en は strobeのように動作し、毎回 1 つの clock cycle の間だけアクティブになることに注意してください。 clock dividerの MSb ではありません。

この可能性のある multi-cycle constraints は次のようになります。

set_multicycle_path -setup -from $en_regs -to $en_regs 8
set_multicycle_path -hold -from $en_regs -to $en_regs 7

これら 2 つの例を見ると、最初の set_multicycle_path command の数字は単に clock enableの分割比であることがわかります。

2 番目の commandについては、分割比は同じですが、マイナス 1 です。したがって、常に N と N-1になります。

最初の commandに関しては、説明するほどのことはありません。 Nのうちの 1 つの clock cycle で clock enable がアクティブな場合、許可された delay は Nで乗算されます。これは、 tsetupの timing 要件に関連しています。

しかし、なぜ 2 番目の commandがあるのでしょうか。なぜ tholdについて言及する必要があるのでしょうか。答えは、最初の command によって、最小の delayの要件も変更されるということです。言い換えると、 thold の計算は、「-setup」オプションを指定した command の影響も受けます。なぜでしょうか。おそらく、適切な説明はありません。

2 番目の command ではこれが修正されます。 最小の delay の要件を元の値に戻します。そのため、2 番目の commandの後は、 thold の計算が以前と同じように行われます。

2 番目の commandで N-1 が使用される理由については、ドキュメントに長い説明があります。しかし、実のところ、その追加情報には興味深いものは何もありません。 thold に関する set_multicycle_path の動作は奇妙であり、番号が N-1 である理由を理解しても奇妙さが軽減されるわけではありません。

しかし、 set_multicycle_path は簡単な部分でした。ここからが本当の難しさです。 $en_regsとして使用するための cell objects の正しいリストを生成します。

registersの選択

どの registers を $en_regsにリストするかを選択するには、 path が multi-cycle pathとして適格である理由を理解する必要があります。したがって、これがルールです。 clock enable が両側を制御している場合にのみ、 path の timing requirement を緩和できます。つまり、 clock enable が非アクティブな場合、2 つの sequential elements のどちらも clock edgeの後で値を変更しないことが保証されます。

clock domainsの観点から考えてみましょう: clock enable によって制御されるすべての sequential elements は、仮想 clock domainに属します。この仮想 clock domain 内の clock にはより低い frequencyがあるため、この clock domain 内の timing 要件を調整できます。

しかし、 pathの側面の 1 つがこの架空の clock domainに属していない場合、これは 2 つのrelated clocks間の架空の clock domain crossing です。このような pathについて特別なことをする必要はありません。既存の timing constraintsによって既に処理されているからです。しかし、この種の path に multi-cycle exception を適用するのは正しくありません。

上記の timing constraints は、この考えを反映しています。 @en によって制御されるすべての sequential elements は、 $en_regs に cell objectsとしてリストされます。次に、このリストに属する sequential elements で始まり、 sequential elements で終わる 2 つの set_multicycle_path commands が paths に適用されます。

multi-cycle paths に関する最も難しい部分は、この sequential elements のリストが正しいことを確認することです。 このリストには、 clock enableによって制御されるすべての sequential elements が含まれている必要があります。しかし、他の sequential elements をこのリストに載せるべきではありません。

sequential element がこのリストにない場合、関連する paths に対する timing の施行は必要以上に厳しくなります。これは大惨事ではありませんが、 timing exception の効率を低下させます。

ただし、リストにないはずの sequential element が誤ってリストに追加された場合、重大な結果が生じる可能性があります。 その結果、 timing requirements が十分に厳密でない paths が生まれます。言い換えると、ツールは sequential elementsの適切な動作の要件を保証しません。また、 timing の要件が満たされない場合、奇妙なことが起こる可能性があります

sequential elementsのリストを作成するために、 "all_fanout" command (または「get_fanouts」) を使用することにしました。これは必ずしも正しく動作するとは限りません。これについては次に説明します。その後、このリストを作成するための他のオプションについて説明します。これらの他のオプションは、 FPGA tools が「all_fanout」または同様の commandsをサポートしていない場合、特に関係します。

all_fanout および get_fanoutsで起こりうる問題

multi-cycle constraint で最もありそうな間違いは、 clock enable 自体 (つまり @en) がリスト (つまり $en_regs) に含まれていることです。この場合、 multi-cycle exception は、 @en 自体からそれが制御する sequential elements までのすべての paths に適用されます。これは事実上、これらの paths の timing 要件が保証されていないことを意味します。 clock enable は多くの場合、 fan-outが高い signal であるため、これは目に見える影響を与える可能性があります。

上記の例では、これは @pre_enで回避されます。 @en が次のように定義されていない理由を自問したかもしれません。

always @(posedge clk)
  en <= !en; // Wrong!

@en がこのように定義されていた場合、 @en からそれ自体への path が存在することになります。その結果、 @en は $en_regsに含まれていたはずです。

したがって、 @pre_en はこの問題を解決します。ただし、最適化のために synthesizer がこの register を排除しないようにすることが重要です。たとえば、 Quartusの synthesizer は、 @pre_en の唯一の用途が @en に値を与えることであることを検出します (このページの上部にある Verilog コードで)。したがって、 synthesizer は @pre_enを削除し、「en <= !en」と言うように続行します。その結果、 @en は $en_regsに含まれます。可能な解決策は、次のように @pre_en を宣言することです。

reg pre_en /* synthesis preserve */;

この単純な例は、 synthesizer による予想外の最適化が悲惨な結果をもたらす可能性があることを示しています。ソリューションは単純ですが、この最適化を回避する必要性を見落としがちです。

clock enable で考えられるもう 1 つの事故は、この signal の fan-outが高いことが多いという事実に関連しています。したがって、ツールは register を自動的にレプリケートし、各レプリカの fan-out が特定の制限よりも低くなるようにします。しかし、これは $en_regsにどのような影響を与えるでしょうか?このリストに含める基準は、特定の netに基づいています。したがって、 @en のレプリカによって制御される sequential elements は含まれません。

ただし、このような状況の結果は悲惨ではありません。 すでに述べたように、これは一部の paths に対する timing の施行が必要以上に厳しくなることを意味するだけです。 designの信頼性は影響を受けません。

registers の複製については、すでに高 fan-outsのコンテキストで説明しました。そこで述べたように、 synthesizer が複製するのを待つよりも、 @en を手動で複製する方がよいでしょう。驚きを避けるために、この registerの複製を許可しない synthesis attribute を常に追加してください。高 fan-out が後で timing closure で問題を引き起こす場合は、手動複製で解決してください。この方法なら、問題の原因を理解しやすくなります。プロジェクトが拡大したために synthesizer が突然 @en を複製した場合、 timing constraints が達成されなかった理由を理解するのはそれほど簡単ではありません。

いずれにしても、 $en_regs を定義する command を更新して、 @en のレプリカが含まれるようにする必要があります。

そういえば、 $en_regs の定義は netの名前に依存していることに注意してください。すでに説明したように、これは、 synthesizer が net の名前を「en」とは異なる名前に変更した場合、 $en_regs が空のリストになることを意味します。その結果、 multi-cycle path constraints は完全に役に立たなくなります。この可能性も災害ではありません。 design は依然として信頼性がありますが、 timing constraintsを実現するのはより困難です。

もう 1 つの考えられる問題は、 $en_regs の定義方法が原因で、 @en を clock enable以外の目的で使用してはならないことです。たとえば、次の Verilog コードを考えてみましょう。

reg [7:0] counter;

always @(posedge clk)
  if (en)
    counter <= counter + 1;

この例では、 @en は明らかに clock enableとして使用されています。したがって、 @counter に関連するすべての paths が multi-cycle pathsであっても問題ありません。しかし、これはどうですか?

reg [7:0] counter;

always @(posedge clk)
  if (en)
    counter <= counter + 1;
  else
    counter <= counter - 1;

ここで、 @en は他の registerと同じように使用されます。 @counter の値は、 clock cycleごとに変わります。したがって、 @counter が multi-cycle pathの候補になることは絶対にありません。それでも、 @counter のすべての flip-flops は $en_regsに含まれています。 これらすべての flip-flopsに @en から paths があります。

これは、 @enのレプリカを作成することで、比較的簡単に解決できます。

reg [7:0] counter;
reg non_ce_en;

always @(posedge clk)
  non_ce_en <= pre_en;

always @(posedge clk)
  if (non_ce_en)
    counter <= counter + 1;
  else
    counter <= counter + 2;

synthesizer が @en と @non_ce_en を 1 つの registerにマージするのを防ぐには、 synthesis attribute が必要であることに注意してください。

結論として、 @en で始まるすべての paths に基づく $en_regs の定義は単純で簡潔です。しかし、この定義は地雷原でもあります。それでは、いくつかの代替案を見てみましょう。

$en_regsを作成する別の方法

multi-cycle path timing exception の sequential elements のリストを作成する最も安全な方法は、 design hierarchyに依存することです。 clock enable によって制御されるすべての logic は、別の module (および場合によっては sub-modules) にある必要があります。これにより、 cell objectの完全な名前に基づいて cells を検索することにより、 $en_regs を作成できます。たとえば、 Vivadoの場合:

set all_sync [all_fanout -endpoints_only -only_cells -flat \
  [get_nets -of_objects [get_clocks clk]]]
set en_regs [filter $all_sync {name =~ module_ins/multicycle_ins/* }]

最初の command は、 clock buffer 自体を除いて、 @clk に接続されているすべての logic elements を検索します (この clock は、「clk」という名前の clock object によって表されます)。結果は $all_syncに保存されます。これは、関連する可能性のあるすべての synchronous elements を含むリストを作成する 1 つの方法です。2 番目の command は、前述の別の module内にある $all_sync 内のすべての logic elements のリストを作成します。

このメソッドは、 clock object の名前と instantiationsの名前に依存していることに注意してください。これらは変更される予定はありません。この方法では、 clock enable が複製されるかどうか、またはその名前が synthesizerによって変更されるかどうかは関係ありません。

module を別に用意するもう 1 つの利点は、 Verilog codeの操作が簡単になることです。 clock enable によって制御される sequential elements とそうでない sequential elements の間で混乱が生じる可能性はほとんどありません。

しかし、 clock enableに依存する logic を切り離し、別の moduleに配置することは必ずしも自然ではありません。また、 Verilog code がすでに作成されていて、正しく動作することがわかっている場合は、それを変更するのは得策ではないかもしれません。

また、状況によっては適切な別の代替手段についても言及します。 すべての registersの命名規則。たとえば、 clock enable によって制御されるすべての registers に、「MC_」で始まる名前を付けることができます。この選択により、 command で $en_regs を作成するのが簡単になります。 名前に従って cells objects を検索するだけです。他の logic elements (例: block RAMs) も、 instantiationの名前を適宜選択することで含めることができます。この方法では Verilog code が見苦しくなると言う人もいれば、作業が簡単になると言う人もいます。完璧な方法はありません。

-of_objectsを使用しない理由

単純な基準に従って $en_regs を定義するのは魅力的に思えるかもしれません。 「en」という名前の net を見つけ、この netに接続されているすべての registers を追加します。たとえば、 Vivado の場合は次のように記述できます。

set en_regs [get_cells -of_objects [get_nets en]]

これが間違っている理由はいくつかあります。 1 つ目の理由は、これには @en 自体が含まれているためです。したがって、 multi-cycle constraints は、 clock enable 自体からそれが制御する sequential elements までのすべての paths に適用されます。前述のとおり、これは重大な誤りです。

2 つ目の理由は、一部の sequential elements が見落とされる可能性があることです。上記の command によると、含めるための基準は、 cell が特定の net (@en) に接続されていることです。これは、この net が flip-flopの CE inputに直接接続されている場合に機能します。ただし、多くの場合、 synthesizer は代わりに @en に基づく combinatorial function を使用することを選択します。

たとえば、 synthesizer は、 Verilog コードが次のようであるかのように、 @foo を実装することを選択できます。

foo <= foo ^ en;

これは元の式と機能的に同等です。

if (en)
  foo <= !foo;

この種の最適化は正当であり、期待する必要があります。 synthesizer は、 NOT gateを実装するために、いずれにしても LUT を使用する必要があります。では、この LUT を使用して flip-flop の next value を直接取得しないのはなぜでしょうか。 flip-flopの CE inputに別の wire を追加するのはなぜでしょうか。

この最適化の副作用は、 flip-flop 自体が @enに直接接続されないことです。したがって、 $en_regsには含まれません。このような状況がもたらす影響については、すでに議論されています。

synthesizer に @en を synchronous elementsの clock enable inputとしてのみ使用するように指示することは、しばしば可能です。たとえば、一部の synthesizers は、「direct_enable」などと呼ばれる synthesis attribute をサポートしています。この機能を使用すると、 synthesizerが logic を最適化する自由度が低下することに注意してください。したがって、 designのパフォーマンスは、ツールの技術的な問題を解決するために悪影響を受ける可能性があります。

さらに、 @en を複製したり、名前を変更したりすると、上記と同じ問題が発生します。

したがって、これらすべての理由から、 net への直接接続を基準として使用するのは適切ではありません。

resetとの相互作用

上記の Verilog の例に synchronous reset を追加するとします。

always @(posedge clk)
     begin
	pre_en <= !pre_en;
	en <= pre_en;
     end

   always @(posedge clk)
     if (reset)
       begin
	  foo <= 0;
	  bar <= 0;
       end
     else if (en)
       begin
	  foo <= !foo;
	  bar <= foo;
       end

multi-cycle timing exception の背後にある考え方は、すべての synchronous elements が、あたかもすべて仮想の clock domainの一部であるかのように動作するというものだったことを思い出してください。この clock domain 内の仮想 clock には、 @clkの半分の frequency があります。したがって、 @en が低い場合、すべての registers は @clk を無視する必要があります。これは、この最後の Verilog コードの例では当てはまりません。 @reset は @enに関わらず効果があります。

たとえば、 @reset が次のように定義されているとどうなるかを考えてみましょう。

assign reset = foo;

これは正当な synchronous resetですが、実際にはほとんど使用されていません。しかし、この定義は multipath exceptionの問題を示しています。 @en がハイになると、その後の clock cycle で @foo がハイになります。しかし、それによって @reset もハイになります。そのため、次の clock cycleでは、 @foo が再びローになります。 @foo は、 clock cycleごとに値を変更します。そのため、 @foo から multicycle path 自体への timing requirement は、この pathで十分にタイトではありません。

これは、 @en が synchronous reset も制御するようにすることで簡単に解決できます。

always @(posedge clk)
     if (en && reset)
       begin
	  foo <= 0;
	  bar <= 0;
       end
     else if (en)
       begin
	  foo <= !foo;
	  bar <= foo;
       end

これは正しいですが、 @reset は @enと共にアクティブにする必要があります。簡単な解決策は、いくつかの clock cyclesに対して @reset を高く保持することです。

asynchronous resetにも同じ原則が適用されます。 asynchronous resetsに関するページに書かれていることはすべてここでも関連していますが、 multi-cycle path constraintsではさらに複雑です。最も簡単な解決策は、別のページで提案されているように、おそらく synchronizerを使用することです。

@en および asynchronous resets

@pre_en の利点の 1 つは、 @en のすべてのレプリカが常に同じ logic level を持つことを保証することです。これは当たり前のように聞こえるかもしれませんが、 asynchronous reset が誤って使用された場合は保証されません。たとえば、元の Verilog コードが次のとおりであるとします。

reg en;

always @(posedge clk or posedge reset)
  if (reset)
    en <= 0;
  else
    en <= !en; // This is not recommended!

@en がレプリケートされる場合、結果は次のようになります。

reg en, en_1, en_2;

always @(posedge clk or posedge reset)
  if (reset)
    begin
      en <= 0;
      en_1 <= 0;
      en_2 <= 0;
    end
  else
    begin
      en <= !en;
      en_1 <= !en_1;
      en_2 <= !en_2;
    end

@en_1 の next value は、 @enの値ではなく、自身の値に依存することに注意してください。これは、 registerの複製の現実的な結果です。

asynchronous reset が安全でない方法で非アクティブ化された場合はどうなりますか? @en の一部の複製が resetの後の最初の clock edge に反応し、他の複製がこの clock edgeを無視する可能性があります。その結果、レプリカの logic state が同じになることはありません (次の resetまで)。

すべてのレプリカが同じソースから next value をコピーするため、@pre_en はこれを解決します。これにより、ラフなスタートがあっても、長期的には適切な動作が保証されます。

概要

clock enableを使用するのは簡単です。 set_multicycle_path を timing exceptionの command として使用するのは簡単です。しかし、これを確実に動作させることは、決して簡単ではありません。問題が発生する可能性は多く、その原因は、 logic designの増大に応じて synthesizer の動作が変化することにある場合があります。

これらの予期しない問題の結果、 multi-cycle path がその目的を果たせなくなる可能性があります。 緩和された timing requirements が一部の pathsに適用されない場合、この方法の利点は疑わしいものになります。さらに悪いことに、影響を受けるはずのない paths に multi-cycle path exception が適用されると、間違いによって信頼性の低い logic designが発生する可能性があります。

したがって、予期しない事態が発生していないことを確認するために、関連する paths の timing reports を読み取ることが重要です。残念ながら、これは将来の驚きを防ぐものではありません: design が進化するにつれて、 synthesizerの動作を予測することは困難です。

したがって、可能であれば、 clock enableを使用する代わりに、同じ PLL から追加の clock を生成することをお勧めします。 2 つの clocks がrelated clocksであるため、この新しい clock を備えた clock domain crossings は信頼できます。この新しい clock の timing constraints は、ツールによって自動的に生成されます。この方法では、驚きのリスクはありません。

designに clock enable と multi-cycle exception を追加したいという理由でこれを読んでいるのであれば、このページで何か考えるきっかけになれば幸いです。


これで、 FPGAの内部にある paths の timing constraints に関する部分は終了です。しかし、 I/Oはどうでしょうか。それが、次のページでの議論の始まりです。

このページは英語から自動翻訳されています。 不明な点は元のページを参照してください。
Copyright © 2021-2024. All rights reserved. (b4b9813f)