01signal.com

Tcl コマンドで論理エレメントを選択する

このページは、タイミングに関する連載ページの一部です。これまでのページでは、タイミング計算の背後にある理論、クロック周期制約、タイミングクロージャ(timing closure)の原則を説明し、Tcl 環境についても調べ始めました。このページでは、タイミング制約(timing constraints)を特定のパス(path)に絞り込むためのコマンドを説明します。

概要

タイミング制約の目的は、設計内のすべてのパスのタイミング要件が満たされることを保証することでしたね。パスごとに要件が異なる場合があるため、各タイミング制約は関連するパスのグループだけを対象とし、それ以外を含んではいけません。

パスを定義する唯一の方法は、そのパスに関連する論理エレメントを参照することです。したがって、正しい論理エレメントのグループを選択する正確な式を書く能力が重要になります。言い換えれば、get_clocks、get_ports、get_cells などのコマンドを正しく使い、その結果がまさに必要なものになるようにする必要があります。

このページでは、論理エレメントのグループを記述するための基本的なテクニックを一通り説明します。ここで説明する内容のほとんどは、SDC 構文固有のものです。また、Tcl のコマンドラインインターフェースが使えることを前提とします。これは Vivado と Quartus、および最近のほとんどの FPGA ツールに当てはまります。

一部の FPGA ベンダーは、自社ツールに Synopsys のソフトウェアが統合されていることを公然と宣言しています。したがって、当然それらのツールは同じ Tcl コマンドを使います。一方、Vivado は Synopsys 由来とは見なされていません。それにもかかわらず、特に Tcl インターフェースに関して、両者にはいくつか顕著な類似点があります。Quartus は独立に開発されたようで、その Tcl インターフェースは少し異なります。

一見すると、このページは Tcl スクリプト(script)の詳細に入り込みすぎているように見えるかもしれません。しかし、実際はその逆です。正確さは極めて重要であり、それはコマンドがどのように解釈されるかを正確に理解することでのみ達成できます。実際、このページでは基本的な原則を説明するだけです。ドキュメントを読むことに代わるものはありません。

FPGA ツールから支援を得る

タイミング制約の作成は、気を配るべき詳細が非常に多いため、最初の一歩を踏み出すのが難しいことがよくあります。FPGA ツールはこの点で支援してくれます。

「help」コマンドは、Tcl コンソールで Tcl コマンドのドキュメントを表示するために使えます。これは、多くの場合、公式ドキュメント(PDF 形式など)と同じ情報です。

ほとんどの FPGA ツールには、タイミング制約を自動的に作成するための GUI インターフェースがあります。この方法は、必要なタイミング制約の種類を選択するために一般的に使われます。次のステップは、リストから関連する論理エレメントを選択することです。その結果、1 つまたは複数のタイミング制約が SDC ファイルに追加されます。

この種のウィザードを使うことは、Tcl 構文のヒントを得るのに役立ちます。自動生成された制約がそのまま使えることもあります。しかし、ほとんどの場合、ウィザードの出力を使いたい誘惑を抑え、タイミング制約の目的を最も良く達成する方法を注意深く考える価値があります。また、プロジェクトが今後どう発展するかを考慮し、タイミング制約が長期間にわたって正しくあり続けることを確認することも重要です。GUI ウィザードでの短い作業では、これを達成できる可能性は低いでしょう。

一部の FPGA ツールには、設計内の論理エレメントを見つけるための GUI インターフェースもあります。このインターフェースを使うと、要求した検索に対応する Tcl コマンドが表示されることがよくあります。これは、特定の論理エレメントを見つけるための Tcl 式を得る便利な方法です。繰り返しますが、これらの式はさらなる作業の出発点として扱うべきです。

ほとんどの FPGA ツールの 3 つ目の機能は、Tcl コマンドラインコンソールです。これは、コマンドを入力し(またはコピー&ペーストし)、結果を画面で確認できます。これにより、コマンドとその検索パターンをテストし、どのオブジェクトが見つかるかを確認できます。これは、検索パターンが正しいことを検証するのに役立ちます。

結論として、FPGA ツールは Tcl コマンドの作成を支援できます。しかし、前述のとおり、ツールが生成した Tcl コマンドは、将来にわたって確実に正しく動作する正確な検索パターンを書くための基礎にすぎないものと考えるべきです。

さて、タイミング制約を作成する怠惰な方法を知ったところで、これをきちんと行う方法を学びましょう。

ネットリスト:簡単なおさらい

Tcl 環境についてより具体的に説明する前に、ネットリストについて簡単におさらいしておきたいと思います。

ネットリストの最も一般的なファイル形式は EDIF ですが、多くの FPGA ツールには独自の形式もあります。通常、ネットリストを作成するのは合成ツール(synthesizer)ですが、その後の段階でツールがネットリストを変更することもあります。このファイルは、論理設計をその基本コンポーネントと、それらのコンポーネント間の接続の観点から記述します。配線図のようなものですが、画像ではなくテキスト表現です。

ネットリスト内のコンポーネントは「セル(cell)」と呼ばれます。セルの大部分は、LUT、または他の小さな組み合わせロジック(combinatorial logic)の要素、フリップフロップのようなものです。また、Verilog コード内のブラックボックスのインスタンシエーション(instantiation)(例:IP コア)も、ネットリスト内ではセルとして表現されます。他のセルには、PLL、ブロック RAM、大きな論理エレメント(「ハード IP」)があります。PCIe ブロック、MGT トランシーバー、プロセッサなどです。

各セルにはいくつかのピン(pin)があります。これらのピンは、物理的な電子部品の外部接続点のようなものです。しかし、これを FPGA の外部 I/O と混同しないでください。セルもピンも、どちらも FPGA の内部に存在します。

ネットリスト内の相互接続は、ネット(net)で構成されます。これは物理的な配線のようなものです。たとえば、Verilog で「wire」を使って信号を定義すると、それはネットになります。ネットは 2 つ以上のピン間を接続し、それによってこれらのピンが常に同じ論理レベルになることが保証されます。

論理エレメントのオブジェクトとしての表現

FPGA ツールがプロジェクトのインプリメンテーションを実行するとき、実際に起こっているのは Tcl スクリプトの実行です。これは Vivado と Quartus、および他のいくつかの FPGA ツールに当てはまります。異なる動作をするソフトウェアであっても、この前提は正しいと見なして問題ありません。すべてが 1 つの大きな Tcl スクリプトであるかのような錯覚は、制約用および他のスクリプトファイル用の API によって作られています。

この Tcl スクリプト(仮想的であるかどうかにかかわらず)の環境では、すべての論理エレメントが、異なるクラスから作成されたオブジェクトとして表現されます。これらのオブジェクトには、SDC ファイル(または Xilinx の XDC ファイル)内のタイミング制約からアクセスできます。同様に、Tcl コマンドラインコンソールや Tcl スクリプト内の Tcl コマンドもこれらのオブジェクトにアクセスできます。

SDC 構文で動作するすべての FPGA ツールがサポートする Tcl コマンドが 5 つあります。これらのコマンドは、さまざまなタイプ(つまりさまざまなクラス)のオブジェクトを見つけるために使われます。これらは、これまでのタイミング制約の例ですでに使ってきました。実際、これらのコマンドなしで意味のあるタイミング制約を書くことはほぼ不可能です。

引数なしで使うと、これらのコマンドは関連するタイプのすべてのオブジェクトを見つけます。後で、検索を絞り込む方法を見ていきます。

これら 5 つのコマンドに加えて、各 FPGA ツールには独自の追加コマンドと追加オブジェクトがあります。たとえば、Vivado には all_ffs、all_registers、all_inputs、all_outputs、all_rams などの追加コマンドがあります。Quartus はこれらの一部をサポートしており、また get_registers、get_keepers、get_nodes、get_fanins、get_fanouts なども持っています。

これらのオブジェクトが重要なのは、タイミング制約の中で FPGA 設計の論理エレメントを参照するために使われるからです。また、情報を得るため(たとえばクロックの周波数など)に Tcl スクリプトで使うこともできます。各 FPGA ツールには、Tcl スクリプトでこれらのオブジェクトにアクセスするための独自の API があります。たとえば、Vivado では次の Tcl コマンドでクロック周期を取得できます。

> get_property PERIOD [get_clocks clk]
4.000

Quartus でも同様です。

> get_clock_info -period [get_clocks clk]
4.000

これらの違いにもかかわらず、ほとんどの FPGA ツールは SDC 形式のタイミング制約の構文と意味に関しては共通しています。各ツールがサポートする API の情報は、通常、「Tcl スクリプト」や「タイミングクロージャ」に関連するタイトルのユーザーガイドに記載されています。

このページの例は、特に断りのない限り Vivado に基づいています。

Tcl に関するいくつかのメモ

Tcl は古い言語ですが、論理設計の分野で確立されているため、この言語がすぐにどこかへ消えてしまうとは考えられません。幸いなことに、Tcl をあまり詳しく知らなくても、Tcl で役立つことを行うことは可能です。

まず最初に、すでに簡単に述べた角括弧(「[」と「]」)についてです。Tcl では、これは角括弧の中のコマンドを実行し、その結果をその位置に置くことを意味します。シェルスクリプトや Perl に慣れている人には、これはバッククォートと同じです。たとえば、次のコマンドでは、get_ports が「clk」という名前のポートオブジェクトに置き換えられます。

create_clock -period 4.000 -name clk [get_ports clk]

同様に、これは次のように書くこともできました。

set the_clk_port [get_ports clk]
create_clock -period 4.000 -name clk $the_clk_port

この 2 番目の例に示すように、変数は「set」コマンドで定義され、値が代入されます。変数の値にアクセスするには、ドル記号($)を使います。これもシェルスクリプトや Perl と同様です。

波括弧(「{」と「}」)については、話は別です。他のいくつかの言語と同様、その意味は文脈に大きく依存します。Tcl における波括弧のあまり期待されていない意味の 1 つは、囲まれた文字列はそのままにすべきであるということです。つまり、置換は一切行われず、空白も他の文字と同じように扱われます。たとえば、同じタイミング制約は次のように書くこともできました。

create_clock -period {4.000} -name {clk} [get_ports {clk}]

この例では、波括弧はまったく不要であり、このコマンドは以前とまったく同じ意味です。不要な波括弧は残念ながらよく見かけますが、多くの場合、この例のように何の意味もありません。

Tcl コンソールを使うためのヒント

見つかったオブジェクトの数が多くなり、検索コマンドの出力を読みにくくなることがよくあります。これは簡単な Tcl コマンドで解決できます。正確な方法は使用するツールによって異なります。Vivado では、次のコマンドで設計内のすべてのセルを出力します。各セルは別々の行に出力されるため、セルが多くても出力は読みやすくなります。

> join [get_cells -hierarchical] \n
GND
VCC
bar__0_i_1
bar_reg_OBUF_inst
bar_reg__0
[ ... ]

「join」コマンドは、get_cells が生成する配列の各要素の間に改行を入れます。

Quartus では、同じ結果は次のようにして得られます。

join [query_collection -all [get_cells -hierarchical] ] \n

または、query_collection をより賢く使って次のようにします。

query_collection -all -report_format [get_cells -hierarchical]

特定のエレメントを見つける

長い前置きの後、ようやく本当に興味深い話題に入ります。以下の例では、次の Verilog コードを参照します。これは後でも使われます

module top(
    input clk,
    input foo,
    output reg bar_reg,
    output reg baz
);
    reg foo_reg;
    reg bar;
    reg baz_metaguard;
    wire pll_clk_8, pll_clk_6;

   clk_wiz_1 pll_i
   (.clk_in1(clk),
    .clk_out1(pll_clk_8),
    .clk_out2(pll_clk_6));

always @(posedge pll_clk_8)
  foo_reg <= foo;

always @(posedge pll_clk_6)
  begin
    bar <= !foo_reg;
    bar_reg <= bar;
  end

always @(posedge clk)
  begin
    baz_metaguard <= bar;
    baz <= baz_metaguard;
  end

このページの冒頭で述べたように、タイミング制約の正確さは、正しい論理エレメントのグループを選択することに依存します。したがって、上で挙げた 5 つの get_* コマンドの検索結果を絞り込むことが可能であり、必要でもあります。それを行うにはいくつかの方法がありますが、最も一般的な方法はオブジェクトの名前に基づくものです。最も単純なパターンは、探している正確な名前を持つ 1 つのオブジェクトを見つけることです。たとえば、Vivado の Tcl コンソールで次のようにします。

> get_ports clk
clk

同様に、特定のパターンに一致する名前を持つすべてのオブジェクトを見つけることもできます。

> get_pins pll_i/*
pll_i/clk_in1 pll_i/clk_out1 pll_i/clk_out2

これらの例の両方で、出力はオブジェクトであることに注意してください。Vivado の Tcl コンソールでは、便宜上、これらのオブジェクトの名前が出力されます。

さらに重要なのは、これらの検索パターンに関して、FPGA ツールごとに動作が少しずつ異なることです。ここでは Vivado を使った例を示しています。他のツールにも同じ原則が当てはまります。

アスタリスク("*")はワイルドカード文字であり、任意の数の文字に置き換わります。クエスチョンマーク("?")は 1 文字に置き換わります。これはファイル名のワイルドカードとまったく同じように機能します。

ファイル名と同様に、ワイルドカードは階層の区切り文字(Vivado では「/」、Quartus では「|」)とは一致しないことに注意してください。

また、階層パスとファイルのディレクトリとの間には類似点があります。検索はトップレベルの階層を基準にして行われます。これはファイルシステムのルートディレクトリに似ています。したがって、たとえば次のようになります。

> get_pins */clk_*
pll_i/clk_in1 pll_i/clk_out1 pll_i/clk_out2
> get_pins pll_i/clk_out?
pll_i/clk_out1 pll_i/clk_out2

各論理エレメントの階層内の正確な位置を指定する必要があることは、しばしば大きな欠点になります。見つけたい論理エレメントは、異なる位置にあることがよくあるからです。これは「-hierarchical」を使用することで解決されます。このフラグがあると、パターンの検索は階層内のすべての位置で行われます。

一般に、ワイルドカードを使って論理エレメントを見つけることは推奨されません。唯一の例外は、検索パターンが非常に単純な場合、または他に選択肢がない場合です。ワイルドカードと「-hierarchical」の使い方を説明し、この方法の限界も示す専用ページがあります。

-filter を使う

ワイルドカードに基づく検索パターンを使うことには、いくつかの欠点があります。最も重大な欠点は、階層を正確に定義するか、まったく定義しないかのどちらかでなければならないことです。問題の 1 つは、特定のサブ階層に属するオブジェクトだけに検索を限定したいことがよくあることです。しかし、これはワイルドカードでは不可能であり、「-hierarchical」オプションでもこの問題は解決しません。

この理由などから、検索パターンを定義する好ましい方法は -filter オプションを使うことです。このオプションにはブール式が伴います。このオプションを使うと、その式が真になる検索結果だけが残ります。

たとえば、

> get_pins */clk_*
pll_i/clk_in1 pll_i/clk_out1 pll_i/clk_out2
> get_pins */clk_* -filter {name =~ *2*}
pll_i/clk_out2

この例では、「name」というプロパティが、ワイルドカードによって見つかった各オブジェクトに対して調べられました。このプロパティが「*2*」と一致した場合にのみ、そのオブジェクトは検索結果に残りました。つまり、オブジェクトの名前に「2」が含まれている場合だけです。

この例は実際的な観点では面白くありません。「-hierarchical」を使うとはるかに興味深くなります。検索パターンなしで使うと、すべてのオブジェクトが見つかります。つまり、次のコマンドはすべての階層のすべてのピンを見つけます。

get_pins -hierarchical

ここから、-filter で検索結果を絞り込むことができます。

> get_pins -hierarchical -filter {name =~ pll_i/*/*out1 }
pll_i/inst/clk_out1
> get_pins -hierarchical -filter {name =~ pll_i/*out1 }
pll_i/clk_out1 pll_i/inst/clk_out1
> get_pins -hierarchical -filter {name =~ *out1 }
pll_i/clk_out1 pll_i/inst/clk_out1

波括弧(「{」と「}」)の意味は、単にその中身が Tcl インタープリタによって変更されないようにするためであることに注意してください。

また、検索パターンは -filter オプションに属しており、その動作は異なることにも注意してください。これは「name」をオブジェクトのプロパティとして扱います。したがって、すべての文字は平等に扱われます。「/」は特別な意味を持ちません。「/」が階層の区切り文字であることは関係ありません。「/」を含むすべての文字がワイルドカード("*")と一致し得ます。同様に、「/」を含むすべての文字を検索パターンで使用できます。もちろん、-filter なしではこれは当てはまりません。

任意の文字が "*" と一致し得るという事実は、-filter をより強力なツールにしますが、この利点は間違いの可能性でもあります。無邪気な "*" が、上記の例のように「pll_i/clk_」と「pll_i/inst/clk_」の両方に誤って一致し得ることを忘れがちです。

この機能の正しい使い方は、階層内のどこかにある既知の名前の論理エレメントを見つけることです。

> get_pins -hierarchical -filter {name =~ */clkout2_buf/O }
pll_i/inst/clkout2_buf/O

これは、設計内に「clkout2_buf」という名前のセルが 1 つだけあると確信できる場合に正しいです。このコマンドを使うことで、この論理エレメントを含むモジュールがプロジェクトの階層内で移動されたとしても、このセルの出力ピンは常に見つかります。実際のシナリオでは、「clkout2_buf」よりも一意性の高い名前を選ぶ方が良いでしょう。

同じ方法が get_cells や get_nets でも機能します。たとえば、

> get_cells -hierarchical -filter {name =~ */clk*_buf}
pll_i/inst/clkf_buf pll_i/inst/clkout1_buf pll_i/inst/clkout2_buf

しかし、-filter は名前だけでなく、すべてのプロパティに対して機能します。したがって、次のコマンドは、名前に「bar」を含むすべてのレジスタを見つけます。

get_cells -hier -filter {primitive_type =~ register.*.* && name =~ *bar*}

「&&」演算子に注意してください。これは論理 AND を意味します(Verilog や C と同様です)。

このコマンドでは、「primitive_type」と「name」は単なるプロパティ名です。「=~」演算子は比較を行い、ワイルドカードを許可します。

オブジェクトのプロパティは、「report_property」コマンド(Vivado)で一覧表示できることを思い出してください。

つまり、-filter は柔軟に使えます。残念ながら、すべての FPGA ツールで利用できるわけではありません。

Vivado には filter というコマンドがあり、-filter と同じ操作を行うことに注意してください。したがって、次の 2 つのコマンドは同等です。

set result [get_cells -hierarchical -filter {name =~ *_reg}]
set result [filter [get_cells -hierarchical] {name =~ *_reg}]

2 番目の形式は、オブジェクトのリストが変数に格納されている場合に便利です。したがって、上記の 2 つのコマンドは次と同等でもあります。

set all_cells [get_cells -hierarchical]
set result [filter $all_cells {name =~ *_reg}]

-regex を使う

正規表現に慣れている人は、このオプションを使いたくなるかもしれません。しかし、これを行うのは通常良い考えではありません。主に、タイミング制約を他の人にとって理解しにくくするからです。正規表現をサポートする FPGA ツールは、通常 -filter オプションもサポートしているため、ほとんどの場合 -filter を使う方が良いです。

通常の検索パターンの主な問題は、階層の区切り文字がワイルドカードと一致しないことであることを思い出してください。

-regexp はこれを解決できます。次の 2 つの例で示します。

> get_pins -hierarchical -regexp {.+/clk_out[123]}
pll_i/clk_out1 pll_i/clk_out2 pll_i/inst/clk_out1 pll_i/inst/clk_out2
> get_pins {pll_i/[^/]+/clk[^/]+} -hierarchical -regexp
pll_i/inst/clk_in1 pll_i/inst/clk_out1 pll_i/inst/clk_out2

最初のコマンドは、「.」が任意の文字と一致することを示しています。これには「/」(階層の区切り文字)も含まれます。

2 番目のコマンドは、「[^/]+」が階層の区切り文字以外の任意の文字列と一致するために使われることを示しています。これにより、検索結果の階層内の正確な深さを制御できます。このコマンドはまた、検索パターンが -regexp の引数ではなく、-regexp が検索パターンの構文を変更するものであることも示しています。

正規表現はオブジェクトの名前全体と一致しなければならないことに注意してください。つまり、ツールは正規表現の先頭に暗黙的に「^」を追加し、末尾に「$」を追加します。

しかし、同じ結果を達成する他の方法があるなら、-regex を使わないでください。他のほとんどの FPGA 設計者は、その検索パターンを理解できないでしょう。

-of_objects を使う

名前によってオブジェクトを見つける代わりに、-of_objects は他のオブジェクトとの関係によってオブジェクトを見つけることを可能にします。ほとんどの場合、「-of_objects」はおおよそ「〜に接続されている」という意味です。

たとえば、あるネットに接続されているすべてのピンを見つけるには、次のようにします。

> get_pins -of_objects [get_nets bar]
bar_reg_reg/D baz_metaguard_reg/D bar_reg__0/Q

または、あるセルのすべてのピンを見つけるには、次のようにします。

> get_pins -of_objects [get_cells bar_reg_reg]
bar_reg_reg/Q bar_reg_reg/C bar_reg_reg/CE bar_reg_reg/D bar_reg_reg/R

または、クロックの発生元であるピンを見つけるには、次のようにします。

> get_pins -of_objects [get_clocks clk_out1_clk_wiz_1]
pll_i/inst/mmcme3_adv_inst/CLKOUT0

同じ方法はネットを見つけるのにも機能します。たとえば、特定のピンに接続されているネットはどれでしょうか。

> get_nets -of_objects [get_pins bar_reg_reg/C]
pll_clk_6

または、関連するセルに接続されているネットはどれでしょうか。

> get_nets -of_objects [get_cells bar_reg_reg]
bar_reg_OBUF pll_clk_6 <const1> bar <const0>

同様に、この方法でセルを検索することもできます。たとえば、@pll_clk_6 に接続されているセルはどれでしょうか。

> get_cells -of_objects [get_nets pll_clk_6]
bar_reg__0 bar_reg_reg pll_i

「pll_i」は Clocking Wizard IP のインスタンシエーション名であることに注意してください。これはおそらく検索の望ましい結果ではありません。では、検索結果をフリップフロップだけに絞り込みましょうか?

> get_cells -of_objects [get_nets pll_clk_6] -filter {primitive_type =~ register.*.*}
bar_reg__0 bar_reg_reg

これまでの -of_objects の例では、ピン、ネット、セルが互いにどのように参照できるかを示しました。しかし、このオプションでクロックオブジェクトを見つけることも可能です。

> get_clocks -of_objects [get_nets pll_clk_6]
clk_out2_clk_wiz_1
> get_clocks -of_objects [get_cells bar_reg_reg]
clk_out2_clk_wiz_1
> get_clocks -of_objects [get_pins bar_reg_reg/C]
clk_out2_clk_wiz_1

これらの 3 つのコマンドは、接続されている論理エレメントに基づいてクロックがどのように見つかるかを示しています。この論理エレメントがセルの場合、結果が複数になることがあることに注意してください。たとえば、

> get_clocks -of_objects [get_cells pll_i]
clk clk_out1_clk_wiz_1 clk_out2_clk_wiz_1

「pll_i」は IP なので、そのピンはこのモジュールのポートであることを思い出してください。

> get_pins -of_objects [get_cells pll_i]
pll_i/clk_in1 pll_i/clk_out1 pll_i/clk_out2

したがって、特定のクロックを見つけるには、get_pins を使う方が安全です。

> get_clocks -of_objects [get_pins pll_i/clk_out2]
clk_out2_clk_wiz_1

もちろん、外部 I/O ポート上のクロックオブジェクトは、次のコマンドで最もよく見つかります。

> get_clocks -of_objects [get_ports clk]
clk

または、同等の短いコマンドで次のようにします。

> get_clocks [get_ports clk]
clk

get_ports について言えば、これも -of_objects と一緒に使えます。get_ports に固有の可能性についてはドキュメントを参照してください。他のコマンドと同様の可能性はほとんど意味がありません。たとえば、

> get_ports -of_objects [get_nets clk]
clk

要約すると、-of_objects は特定の論理エレメントを選択するための優れた方法です。これは特に、次に説明するオブジェクトの名前による検索の困難さを考えると、当てはまります。

残念ながら、多くの FPGA ツールは -of_objects をサポートしておらず、信頼性の低い方法に頼らざるを得ません。

名前による検索の問題

上で説明したように、タイミング制約の正確さは論理エレメントを見つける正確さに依存します。これらの検索結果の少なくとも一部は、オブジェクトの名前に依存します。これがどのように問題を引き起こすかを見てみましょう。

たとえば、「get_ports clk」は、クロックオブジェクトと特定の I/O ピンに存在する信号との間の接続を作るために使われます。この I/O ピンは、「clk」という名前を持つポートオブジェクトによって表されます。

create_clock -period 4.000 -name clk [get_ports clk]

しかし、なぜこのポートオブジェクトは「clk」という名前になったのでしょうか。答えは、合成ツールがネットリストを作成した方法に関係しています。Verilog コード内のトップレベルポートの名前が、ネットリストのトップレベルポートの名前としても選ばれました。これは当然の選択であり、まともな合成ツールならどれでも同じことをします。

しかし、セルの名前はどうでしょうか。上記の Verilog コードで「foo_reg」という名前のレジスタを取り上げましょう。このレジスタのフリップフロップを表すセルオブジェクトの名前は何でしょうか。Vivado の合成ツールは、このオブジェクトに「foo_reg_reg」という名前を選びました。つまり、この合成ツールは Verilog コードの名前に「_reg」という接尾辞を付ける傾向があることがわかります。それは頼りにできる規則のように聞こえます。しかし、別の合成ツールならおそらく異なることをするでしょう。

では、「bar」という名前のレジスタはどうでしょうか。関連するセルオブジェクトの名前は「bar_reg」になるはずでしたが、合成ツールは別の選択をしました。「bar_reg__0」です。これは、Verilog コードに「bar_reg」という名前のレジスタがあるためです。名前の衝突を避けるために、合成ツールは少し異なる名前を選びました。単に「_reg」を追加する代わりに「_reg__0」を追加したのです。この単純な例は、オブジェクトの名前に依存することの問題を示しています。

さらに悪いことに、タイミング制約が、「bar_reg」という名前のレジスタが Verilog コードに追加される前に書かれたと仮定しましょう。この場合、@bar に関連するセルオブジェクトは、通常どおり「bar_reg」という名前になります。したがって、タイミング制約はこの名前をオブジェクトに使用します。後日、「bar_reg」という名前のレジスタが設計に追加されます。その結果、対象のセルオブジェクトの名前は「bar_reg」から「bar_reg__0」に変わります。「bar_reg」という名前に依存しているタイミング制約は、突然正しくなくなります。運が良ければ、ツールはこれに関する警告を発行するでしょう。

オブジェクトの名前を使うことが問題を引き起こす理由は、他にもあります。たとえば、ツールはファンアウト(fan-out)が制限値を超えると、レジスタを自動的に複製することがあります。これが起こると、追加のレジスタがタイミング制約に含まれない可能性があります。新しいレジスタの名前が検索パターンと一致しないからです。

より深刻な問題は、論理エレメントが偶然タイミング制約に含まれてしまうことです。たとえば、これは IP ブロックに属する論理エレメントで発生し得ます。これらの論理エレメントの名前を制御することはできないため、その名前がタイミング制約のパターンと偶然一致する可能性があります。

タイミング制約への論理エレメントの偶発的な包含は、怠惰の結果であることもあります。タイミング制約は通常、論理設計に新しい機能を追加すると同時に書かれます。検索パターンが試行錯誤で書かれた場合、将来の論理エレメントの名前が考慮されないかもしれません。したがって、新しいロジックが追加されたとき、その論理エレメントの名前の一部が既存のタイミング制約と意図せず一致することがあります。

タイミング制約の誤りを避ける方法

最初のそして最も重要な規則は、タイミング制約の検索パターンに一致する論理エレメントをテストするだけでは不十分だということです。これらの論理エレメントの完全なリストを作成し、そのリストを注意深くレビューしたとしても、後で追加されるロジックに関して何も保証されません。また、そのようなレビューは、合成ツールやインプリメンテーションの別の段階でオブジェクトの名前が変更された場合に、タイミング制約が期待どおりに機能し続けることを保証するものでもありません。

したがって、検索パターンを数学的な式のように扱うことが重要です。これらの検索パターンが現時点で期待どおりに機能するだけでは不十分です。また、期待どおりに機能しない場合に、機能するように小さな修正を加えるだけでは不十分です。むしろ、検索パターンがなぜ正しいのか、そしてなぜ将来にわたって正しくあり続ける可能性が高いのかについて、論理的な説明が存在しなければなりません。

また、検索パターンが変更される可能性の低いものに依存することも重要です。たとえば、ツールは Verilog コード内のインスタンシエーションの名前を変更しません。これは、モジュール、IP、プリミティブ(primitive)のインスタンシエーションに当てはまります。したがって、階層パスが Verilog コードに書かれたインスタンシエーション名だけで構成されている場合、その階層パスに依存することは安全です。IP ブロック内で作成された名前を使うことは、それほど安全ではありません。これを説明するために、次のコマンドを見てみましょう。

get_clocks -of_objects [get_pins pll_i/inst/clkout1_buf/O]

これは、クロックを分配するグローバルクロックバッファの出力ピンを参照して、クロックオブジェクトを取得する方法です。問題は、これが長期的に機能するかどうかを確信できるかどうかです。

この方法の問題は、「inst」と「clkout1_buf」が、Clocking Wizard IP の内部で作成されたインスタンシエーションの名前であることです。これらの名前が変更される可能性は低いですが、変更されないという保証はありません。

考えられる解決策の 1 つは、IP を実装する Verilog コードを見つけ、その Verilog をプロジェクトに直接含めることです。これにより、将来何も変更されないことが保証されます。

別の方法は、Verilog での IP のインスタンシエーションを見ることです。それが次のようであったことを思い出してください。

 clk_wiz_1 pll_i
   (.clk_in1(clk),
    .clk_out1(pll_clk_8),
    .clk_out2(pll_clk_6));

これはブラックボックスのインスタンシエーションであるため、各ポートにはネットリスト内にピンがあります。名前は変更できません。これは IP のポートを識別する方法だからです。したがって、「pll_i/clk_out1」という名前のピンが @pll_clk_8 への接続を行うことは保証されています。これはクロックオブジェクトを取得する安全な方法です。

get_clocks -of_object [get_pins pll_i/clk_out1]

clk_wiz_1 がプロジェクト内の単なる別の Verilog モジュールである場合、これはおそらく機能しないことに注意してください。その場合、このモジュールのインスタンシエーションに代わってピンは作成されません(合成ツールは通常、ポートの接続をネットの統合によって実装するため)。可能な解決策は、階層のより低いレベルに現れる名前を使うことかもしれません。

つまり、頼りにできる名前にはいくつかの種類があります。

大きく失敗したほうが良い

最悪の状況は、タイミング制約がほぼ正しい場合です。含まれていないパスがほんのわずかしかない場合。または、タイミング制約に誤って含まれているパスがほんのわずかしかない場合。この種の誤りは、最も発見が困難です。

これが、短く簡潔で数学的なスタイルのタイミング制約が優れている主な理由です。論理エレメントの小さなグループごとにタイミング制約があると、この長い規則のリストに誤りが忍び込みやすくなります。

この考え方を示すために、上で示したクロックオブジェクトを見つける例に戻りましょう。

get_clocks -of_objects [get_pins pll_i/inst/clkout1_buf/O]

前述のように、この式の問題は、pll_i のインスタンシエーションが階層内の別の位置に移動すると、クロックオブジェクトが見つからないことです。それはどれほど悪いことでしょうか。

このクロックオブジェクトが次のように Tcl 変数に格納されていると仮定します。

set my_clock [get_clocks -of_objects [get_pins pll_i/inst/clkout1_buf/O]]

そして、$my_clock が多くのタイミング制約で使われ、多くのパスが関与しているとします。この場合、バグが原因で $my_clock が突然クロックオブジェクトを含まなくなっても、実際には大きな問題ではありません。多くのことがおかしくなるため、この誤りはすぐに気づかれる可能性が高いからです。

しかし、$my_clock が 1 つのタイミング制約でしか使われず、その制約がめったに影響を及ぼさない小さな問題を解決するためのものであれば、これは悪い状況です。この誤りは気づかれない可能性が高いでしょう。

結論:適切に書かれたタイミング制約は、完全に機能するか、まったく機能しないかのどちらかであるべきです。


このページでは、論理エレメントを正確に選択する方法を示しました。次のページでは、この知識を使って特定のパスだけを対象とするタイミング制約を定義します。

このページは英語から機械翻訳されたものです。不明な点があれば、原文を参照してください。
Copyright © 2021-2026. All rights reserved. (dcc38493)