Quartus® II Tcl の例: 任意パス・タイミング・レポートの作成

author-image

投稿者:

list_pathreport_timing Tcl コマンドは非常に強力ですが、いくつかの制限があります。パスのエンドポイントは、クロック、ピン、またはレジスターでなければなりません。また、これらのコマンドは、エンドポイント間のすべての組み合わせパスのレポートを作成するものではありません。この高度なスクリプト例は、デザイン内の任意のパス (組み合わせエンドポイントを含む) のタイミングレポートをサポートし、エンドポイント間のすべての組み合わせパスをレポートします。このスクリプトは、反復検索アルゴリズムを使用してパスを見つけます。このアルゴリズムはピンとレジスタで停止して、実行時間が過剰になるのを防ぎます。

ソースとデスティネーションには、ノード名、ワイルドカード、タイムグループ名を指定できます。このスクリプトでは、タイムグループの除外はサポートされていません。エンドポイントの除外を含むタイムグループを指定すると警告が表示され、除外は無視されます。

スクリプトの出力をコンマ区切り値 (.csv) ファイルにできます。デフォルトのファイル名は p2p_timing.csv です。さらに、スクリプトの出力をプロジェクトのタイミングレポートのパネルにすることもできます。デフォルトのパネル名は、Point-to-point Timing です。

quartus_tan -t p2p_timing.tcl -project <project name> -from <node name|wildcard|timegroup name> -to <node name|wildcard|timegroup name> [-write_file] [-file <output file name>] [-write_panel] [-panel <report panel name>]

デフォルトのファイル名とは異なるファイルに出力を指示する場合は、-write_file と -file <出力ファイル名> オプションの両方を指定する必要があります。デフォルトのレポートパネル名とは異なるレポートパネルに出力を指示する場合は、-write_panel と -panel <レポートパネル名> オプションの両方を指定する必要があります。

以下の Tcl コマンドをファイルにコピーして、p2p_timing.tcl という名前にします。

package require cmdline 
load_package advanced_timing 
load_package report 

global quartus 
variable ::argv0 $::quartus(args) 

set options { \ 
   { "from.arg" "" "Source node name" } \ 
   { "to.arg" "" "Destination node name" } \ 
   { "project.arg" "" "Project name" } \ 
   { "file.arg" "p2p_timing.csv" "Output csv file name" } \ 
   { "write_file" "" "Write the output to a file" } \ 
   { "panel.arg" "Point-to-point Timing" "Report panel name" } \ 
   { "write_panel" "" "Write the output to a report panel" } \ 
} 

array set opts [::cmdline::getoptions ::argv0 $options "Bad option"] 
############################################################## 
# 
# パターン引数に一致するデザイン名のノード名と
# 対応するノード ID のリストを返します。 
# デザイン上の名前にマッチしないパターンでは、 
# 空のリストが返されます 
# 例: "reset" を渡すと { reset 3 } が返されます 
# 
############################################################## 
proc get_node_ids { pattern } { 
   array set name_to_node [list] 

   if { [string equal "" $pattern] } { 
      return [list] 
   }

   # そのパターンは、実際にタイムグループの名前ですか? 
   # そうであれば、スクリプトは再帰的にタイムグループのメンバーを 
   # 取得します。 
   set members [get_all_global_assignments -name TIMEGROUP_MEMBER -section_id $pattern] 

   # コレクションのメンバーが存在する場合、 
   # パターンはタイムグループになります 
   if { 0 < [get_collection_size $members]} { 
      # スクリプトは除外をスキップするため 
      # 除外がある場合は警告します 
      if {0 < [get_collection_size [get_all_global_assignments -name TIMEGROUP_EXCLUSION -section_id $pattern]] } { 
         post_message -type warning "Skipping exclusions in timegroup $pattern" 
      } 

      # タイムグループの各項目を検討します。 
      foreach_in_collection assignment $members { 
         # コレクションの各項目は、以下のようなリストになります。 
         # {$pattern} {TIMEGROUP_MEMBER} {node/real pattern} 
         array set sub_collection_names [get_node_ids [lindex $assignment 2]] 

         foreach node_name [array names sub_collection_names] { 
            set name_to_node($node_name) $sub_collection_names($node_name) 
         } 
      } 
   } else { 
      # これはタイムグループではありません 
      # デザイン内のすべてのタイミングノードを反復して、 
      # 名前が指定されたパターンと一致するかどうかをチェックします 
      foreach_in_collection node_id [get_timing_nodes -type all] { 
         set node_name [get_timing_node_info -info name $node_id] 
         if { [string match [escape_brackets $pattern] $node_name] } { 
            set name_to_node($node_name) $node_id 
         } 
      } 
   } 
   return [array get name_to_node]
} 

############################################################## 
# 
# このプロシージャーは,ソースノードとデスティネーション・ノードのリストの間の 
# 組み合わせパスを見つけます。ノード間の 
# パスのリストを返します。各パスは、ノード ID と、 
# 前のノードからのインターコネクト遅延とセル遅延のトリプレットで 
# 構成されています。 
# このプロシージャーでは、ネットリストのトラバースがレジスターやピンで停止するため、 
# レジスタを通過するパスは見つかりません。 
#
############################################################## 
proc find_combinational_paths_between {queue dest_nodes} { 
   set num_iterations 0 
   set paths [list] 
   
   while {0 < [llength $queue]} { 
      # 1000 回の反復ごとにループの進捗状況をレポートする 
      # 反復 incr num_iterations 
      if { 1000 == $num_iterations } { 
         set num_iterations 0 
         post_message "Checking [llength $queue] paths." 
      } 
      
      # キューからの最初のパスをポップします。 
      # 最初にプロシージャーが呼ばれたとき、キューには 1 つの数字、 
      # つまりソース・ノードが入っています。 
      set path [lindex $queue 0] 
      set queue [lrange $queue 1 end] 
      
      # パスの最後のノードを取得し、foreach ループで 
      # そのノードのファンアウトを取得します 
      set last_triplet_in_path [lindex $path end] 
      set last_node_in_path [lindex $last_triplet_in_path 0] 
 
      # 現在のパス内のノード ID だけを抽出します。 
      # 後でこれを使って、ループがトラバースされるのを防ぎます。 
      set nodes_in_path [collapse_triplets_to_node_list $path] 

      # このパスの最後のノードのファンアウトをすべて取得し、 
      # それらを使って新しいパスを作り、キューにプッシュします。 
      foreach n [get_timing_node_fanout $last_node_in_path] { 
         foreach { node_id ic_delay cell_delay } $n { 
            break 
         }
 
         if { -1 != [lsearch $dest_nodes $node_id] } { 
            # パス内のこのノードがデスティネーション・ノードの 
            # リストにあれば、パスは存在します。 
            # これをノード間のパスのリストに追加します 
            set new_path $path lappend 
            new_path $n 
            lappend paths $new_path 
         } 

         if { -1 == [lsearch $nodes_in_path $node_id] } { 
            # パス内のこのノードがパスの中にない場合、 
            # これはループではありません。組み合わせノードや 
            # クロックノードであれば、キューにプッシュします。 
            # このノードがレジスタやピンである場合、
            # パスはプッシュされません。 
            # このように新しいパスをキューにプッシュすることで、 
            # パス内のノードが末端のノードと 
            # 一致する可能性があっても、 
            # 可能な限り最長のパスを見つけることができます。 
            set node_type [get_timing_node_info -info type $node_id] 
            switch -exact -- $node_type { 
               comb - 
               clk { 
                  set next_path $path 
                  lappend next_path $n 
                  lappend queue $next_path 
               } 
               default { 
               } 
            } 
         } 
      }
   }
   return $paths 
} 

############################################################## 
# 
# 2 つの遅延番号を加算し、結果を返します。 
# 遅延番号は「値 単位」という形式です。 
# ここで、単位はナノ秒 (ns) またはピコ秒 (ps) であり、 
# 値はピコ秒の場合は x{1,3}、ナノ秒の 
# 場合は x+.y{1,3} となります。このプロシージャーでは、遅延をナノ秒に 
# 正規化して、値を加算します。 
# 例: add_delays "1.234 ns" "56 ps" # 
############################################################## 
proc add_delays { a b } { 
   if { ![regexp {^([\d\.]+)\s+([np]s)$} $a match a_value a_unit] } { 
      post_message -type error "Couldn't determine parts of time: $a" 
   } 

   if { ![regexp {^([\d\.]+)\s+([np]s)$} $b match b_value b_unit] } { 
      post_message -type error "Couldn't determine parts of time: $b" 
   } 
  
   # Convert everything to nanoseconds if required 
   if { [string equal -nocase ps $a_unit] } { 
      set a_value_ps [format "%.3f" $a_value] 
      set a_value [format "%.3f" [expr { $a_value_ps / 1000 }]] 
   } 

   if { [string equal -nocase ps $b_unit] } { 
      set b_value_ps [format "%.3f" $b_value] 
      set b_value [format "%.3f" [expr { $b_value_ps / 1000 }]] 
   } 

   # 今度は単位が等しく、ナノ秒。 
   # 数字を加算していくだけです。 
   set sum_value [format "%.3f" [expr { $a_value + $b_value }]] 
  
   return "$sum_value ns" 
} 

############################################################## 
# 
# パス内のノード名とノード間の遅延を。  
# フォーマットして出力します。 
# 
############################################################## 
proc print_path_delays { path {iteration first}} { 
   set source_triplet [lindex $path 0] 
   set source_node [lindex $source_triplet 0] 
   set source_node_name [get_timing_node_info -info name $source_node] 
   set source_node_loc [get_timing_node_info -info location $source_node] 
   
   # Print the delays first 
   if { [string equal "first" $iteration] } { 
      accumulate_data [list "IC(0.000 ns)" "CELL(0.000 ns)"] 
   } else { 
      set ic_delay [lindex $source_triplet 1] 
      set cell_delay [lindex $source_triplet 2] 
      accumulate_data [list "IC($ic_delay)" "CELL($cell_delay)"] 
   } 
   accumulate_data [list $source_node_loc $source_node_name] 
   print_accumulated_data 

   # Recurse on the rest of the path 
   if { 1 < [llength $path] } { 
      print_path_delays [lrange $path 1 end] other 
   } 
} 

############################################################## 
# 
# 指定したパスの IC とセル遅延を合計し、 
# 総インターコネクト遅延と総セル遅延の 
# リストを返します。 
# 
############################################################## 
proc end_to_end_delay { path } { 
   set ic_total "0.000 ns" 
   set cell_total "0.000 ns" 
   
   # パスの最初のノードがソースであり、 
   # パスの各ノードにはその前のノードからの遅延が含まれているため、 
   # パスのノード 1 から最後までを検討します。  
   # ソースには先行するノードがないため、遅延はありません。 
   foreach n [lrange $path 1 end] { 
      foreach { node_id ic_delay cell_delay } $n { 
         break 
      } 
      set ic_total [add_delays $ic_total $ic_delay] 
      set cell_total [add_delays $cell_total $cell_delay] 
   } 

   return [list $ic_total $cell_total] 
} 

##############################################################
# 
# 指定されたソースおよびデスティネーションがデザイン内に存在することを確認し、 
# それらの間の組み合わせパスを検索して、 
# パスを出力します。 
# 
############################################################## 
proc find_paths_and_display { source dest } { 
   array set sources [get_node_ids $source] 
   array set dests [get_node_ids $dest] 

   set nodes_exist 1 

   # 名前付きのノードが存在することを確認します 
   if { 0 == [llength [array get sources]] } { 
      set nodes_exist 0 
      post_message -type error "No nodes matching $source were found in your design." 
   } 
   if { 0 == [llength [array get dests]] } { 
      set nodes_exist 0 
      post_message -type error "No nodes matching $dest were found in your design." 
   } 

   # そうであれば、パスを見つけます。 
   if { $nodes_exist } { 
      # デスティネーション・ノード ID のリストを取得します 
      set dest_node_ids [list] 
      foreach d [array names dests] { 
         lappend dest_node_ids $dests($d) 
      } 

      # すべての from ノードをウォークスルーします 
      foreach s [array names sources] { 
         set paths [find_combinational_paths_between $sources($s) $dest_node_ids] 
         if { 0 == [llength $paths] } {  
            post_message "No combinational path exists from $s to $dest" 
         } else { 
            foreach path $paths { 
               # パスを出力します 
               print_path_delays $path 

               # インターコネクトとセルの遅延を合計し、 
               # パスの下に出力します。 
               foreach {total_ic_delay total_cell_delay } [end_to_end_delay $path] { 
                  break 
               } 
               accumulate_data [list $total_ic_delay $total_cell_delay] 
               accumulate_data [list [add_delays $total_ic_delay $total_cell_delay]] 

               # print_accumulated_data には 2 つの呼び出しがあります。
               # 1 つはインターコネクトとセルの遅延の合計を出力するため、 
               # もう 1 つは出力に空行を 
               # 生成するためです。 
               print_accumulated_data print_accumulated_data 
            } 
         } 
      }
   } 
} 

############################################################## 
# 
# パスは、ノードID、インターコネクト遅延、 
# セル遅延の 3 つの情報で構成されています。このプロシージャは, 
# 各トリプレットから順にノード ID を抽出して 
# ノード ID のリストを返します 
# 
############################################################## 
proc collapse_triplets_to_node_list { l } { 
   set to_return [list] 
   foreach triplet $l { 
      lappend to_return [lindex $triplet 0] 
   } 
   return $to_return 
} 

############################################################## 
# 
# 情報をグローバル変数に連結して、 
# 出力する準備をします。 
# 
############################################################## 
proc accumulate_data { data } { 
   global accum set accum [concat $accum $data] 
}
 
############################################################## 
# 
# 蓄積されたデータを出力します。 
# これは標準出力に出力され、ファイルハンドルが存在する場合は 
# オプションで CSV 形式のファイルに出力され、レポートパネルが存在する場合は 
#  (値が -1 ではない)、オプションでレポートパネルに出力されます
# 
############################################################## 
proc print_accumulated_data {} { 
   global accum fh panel_id 
   puts [join $accum ","] 

   # ファイルに書き出しますか? 
   if { [info exists fh] } { 
      puts $fh [join $accum ","] 
   } 

   # レポートパネルに追加しますか? 
   if { -1 != $panel_id } { 
      # レポートパネル行に 4 つの項目がない場合は、 
      # 4 にパッドします。 
      while { 4 > [llength $accum] } { 
         lappend accum [list] 
      } 
      add_row_to_table -id $panel_id $accum 
   } 
   # グローバル変数の情報を消去します。 
   set accum [list] 
}

############################################################## 
############################################################## 
# 
# プロシージャーの終わり、スクリプトの始まり 
# 
############################################################## 
##############################################################
# 
# 出力用のデータを格納するグローバル変数と、 
# オプションのレポートパネルのパネル ID 

set accum [list] 
set panel_id -1 

if { [string equal "" $opts(project)] } { 
   # Print usage options if the script is called without 
   # arguments 
   puts [::cmdline::usage $options] 
} elseif { [string equal "" $opts(project)] } { 
   post_message -type error "Specify a project with the -project option." 
} elseif { ! [project_exists $opts(project)] } { 
   post_message -type error "The project $opts(project) does not exist in this directory." 
} elseif { [string equal "" $opts(from)] } { 
   post_message -type error "Specify a name or wildcard pattern with the -from option." 
} elseif { [string equal "" $opts(to)] } { 
   post_message -type error "Specify a name or wildcard pattern with the -to option." 
} else { 
   set cur_revision [get_current_revision $opts(project)] 
   project_open $opts(project) -revision $cur_revision 

   # タイミングネットリストの作成を試みます。例えば、quartus_fit がまだ実行されていない場合、 
   # このコマンドは失敗します。 
   if { [catch { create_timing_netlist } msg ] } { 
      post_message -type error $msg 
   } else { 

      # Prepare to write the output to a file if required 
      if { 1 == $opts(write_file) } { 
         if { [catch {open $opts(file) w} fh] } { 
            post_message -type error "Couldn't open $opts(write_file): $fh" unset fh 
         } else { 
            post_message "Writing output to $opts(file)" 
            # 出力ファイルに予備的情報を追加します 
            puts $fh "Report of paths from $opts(from) to $opts(to)" 
            puts $fh "Generated on [clock format [clock seconds]]" 
            puts $fh "" puts $fh "IC delay,Cell delay,Node location,Node name" 
         } 
      } 

      # Prepare to write the output to a report panel if required 
      if { 1 == $opts(write_panel) } { 
         # レポートを読み込み、パネルが既に存在する場合は削除し、 
         # 新しいパネルを作成して、見出し行を追加します。 
         load_report 
         set panel_id [get_report_panel_id "Timing Analyzer||$opts(panel)"] 
         if { -1 != $panel_id } { 
            delete_report_panel -id $panel_id 
         } 
        set panel_id [create_report_panel -table "Timing Analyzer||$opts(panel)"] 
        add_row_to_table -id $panel_id [list "IC delay" "Cell delay" "Node location" "Node name"] 
      } 
      find_paths_and_display $opts(from) $opts(to) 

      # 必要に応じて出力ファイルを閉じます 
      if { [info exists fh] } { close $fh } 

      # 必要に応じてレポートパネルを保存します 
      if { -1 != $panel_id } { 
         save_report_database 
         unload_report 
      } 
   } 
   project_close 
}