Hog v10.13.0
hierarchy.tcl
Go to the documentation of this file.
1 #
2 # data structure:
3 # hier_meta {
4 # all_modules {
5 # module_key {
6 # name {}
7 # library {}
8 # type {}
9 # file_path {}
10 # references {} # list of module_keys
11 # properties {}
12 # }
13 # }
14 #
15 # proj_files {
16 # file_path {
17 # file_path {}
18 # ext {}
19 # library {}
20 # properties {}
21 # }
22 # }
23 # }
24 
25 # if {![info exists tcl_path]} {
26 # set tcl_path [file normalize "[file dirname [info script]]/.."]
27 # }
28 
29 source $tcl_path/utils/hdl_parser.tcl
30 
31 proc PrintOrWrite {output_file msg} {
32  if {$output_file ne ""} {
33  puts $output_file $msg
34  } else {
35  puts $msg
36  }
37 }
38 
39 proc _create_hier_meta {} {
40  set hier_meta [dict create]
41  dict set hier_meta all_modules {}
42  dict set hier_meta proj_files {}
43  dict set hier_meta parsed_files_cache {}
44  return $hier_meta
45 }
46 
47 proc _compute_file_checksum {file_path} {
48  if {![file exists $file_path]} {
49  return ""
50  }
51 
52  if {[catch {package require md5 2.0.7} result]} {
53  # Fall back to command line md5sum
54  if {[catch {exec md5sum $file_path} output]} {
55  return ""
56  }
57  return [lindex $output 0]
58  } else {
59  return [string tolower [md5::md5 -hex -file $file_path]]
60  }
61 }
62 
63 
64 proc is_known_library {hier_meta_ref lib_name} {
65  upvar 1 $hier_meta_ref hier_meta
66  return [dict exists $hier_meta libraries known_libs $lib_name]
67 }
68 
69 proc is_ignored_module {module_key ignore_patterns} {
70  foreach pattern $ignore_patterns {
71  if {[string match $pattern $module_key]} {
72  return 1
73  }
74  }
75  return 0
76 }
77 
78 proc _create_proj_file_info {file_path library properties} {
79  set file_info [dict create]
80  dict set file_info file_path $file_path
81  dict set file_info library $library
82  dict set file_info properties $properties
83  dict set file_info ext [file extension $file_path]
84  return $file_info
85 }
86 
87 proc file_info_path {file_info} {return [dict get $file_info file_path] }
88 proc file_info_library {file_info} { return [dict get $file_info library] }
89 proc file_info_properties {file_info} { return [dict get $file_info properties] }
90 proc file_info_ext {file_info} { return [dict get $file_info ext] }
91 
92 proc _store_module {hier_meta_ref mod_name mod_library mod_type file_path references_list props} {
93  upvar 1 $hier_meta_ref hier_meta
94 
95  set key "${mod_library}.${mod_type}.${mod_name}"
96  set mod [dict create]
97  dict set mod name $mod_name
98  dict set mod library $mod_library
99  dict set mod type $mod_type
100  dict set mod file_path $file_path
101  dict set mod references $references_list
102  dict set mod properties $props
103  dict set mod color "white"
104 
105  dict set hier_meta all_modules $key $mod
106 }
107 
108 proc _hier_parse_hdl {hier_meta_ref file_info} {
109  upvar 1 $hier_meta_ref hier_meta
110 
111  set top_control 0
112  if {[string first "top_control" $file_info] != -1} {
113  set top_control 1
114  }
115 
116  set top_l0mdt 0
117  if {[string first "top_l0mdt" $file_info] != -1} {
118  set top_l0mdt 1
119  }
120 
121  #Msg Info "Parsing HDL file: [file_info_path $file_info]"
122  set f [file_info_path $file_info]
123  if {![file exists $f]} { return }
124 
125  set library [file_info_library $file_info]
126  set file_properties [file_info_properties $file_info]
127  set ext [string tolower [file extension $f]]
128 
129  Msg Debug "Parsing $f"
130  set discovered_modules [list]
131  set hdl_constructs [parse_hdl_file $f]
132  foreach node $hdl_constructs {
133  Msg Debug "[hdl_node_string $node]"
134  # if {$top_control == 1 || $top_l0mdt == 1} {
135  # puts "Node: "
136  # puts "$node"
137  # }
138  set node_type [dict get $node type]
139  set node_name [dict get $node name]
140 
141  set references [list]
142  set sv_include_files [list]
143  foreach component [dict get $node components_declared] {
144  lappend references "unknown.component.[dict get $component name]"
145  }
146 
147  foreach inst [dict get $node instantiations] {
148  if { [dict get $inst type] == "component_inst"} {
149  lappend references "unknown.component.[dict get $inst mod_name]"
150  } elseif { [dict get $inst type] == "entity_inst"} {
151  set split_name [split [dict get $inst mod_name] "."]
152  lappend references "[lindex $split_name 0].component.[lindex $split_name 1]"
153  } elseif { [dict get $inst type] == "sv_import"} {
154  lappend references "unknown.sv_package.[dict get $inst mod_name]"
155  } elseif { [dict get $inst type] == "sv_include"} {
156  lappend sv_include_files [dict get $inst mod_name]
157  } elseif { [dict get $inst type] == "vhdl_pkg_inst"} {
158  set split_name [split [dict get $inst mod_name] "."]
159  lappend references "[lindex $split_name 0].vhdl_package.[lindex $split_name 1]"
160  }
161  }
162 
163  foreach lib [dict get $node libraries ] {
164  foreach use [dict get $lib uses] {
165  set split_name [split $use "."]
166  lappend references "[lindex $split_name 0].vhdl_package.[lindex $split_name 1]"
167  }
168  }
169 
170  set references [lsort -unique $references]
171  dict set node references $references
172  dict set node sv_includes $sv_include_files
173 
174  set mod_properties [dict create]
175  if {$ext eq ".v" || $ext eq ".vh" || $ext eq ".sv" || $ext eq ".svh" ||
176  [lsearch -exact $file_properties "SystemVerilog"] != -1} {
177  if {$ext eq ".sv" || $ext eq ".svh" ||
178  [lsearch -exact $file_properties "SystemVerilog"] != -1} {
179  dict set mod_properties filetype "SystemVerilog"
180  } else {
181  dict set mod_properties filetype "Verilog"
182  }
183  } elseif {$ext eq ".vhd" || $ext eq ".vhdl"} {
184  set knownYears {93 2008 2019}
185  set year 2008
186  foreach y $knownYears {
187  if {[lsearch -exact $file_properties $y] != -1} {
188  set year $y
189  break
190  }
191  }
192  dict set mod_properties filetype "VHDL$year"
193  }
194 
195  dict set node properties $mod_properties
196  dict set node library $library
197  dict set node color "white"
198 
199  if {$node_type == "vhdl_architecture"} {
200  set key "${library}.${node_type}.[dict get $node entity].${node_name}"
201  } else {
202  set key "${library}.${node_type}.${node_name}"
203  }
204 
205  dict set hier_meta all_modules $key $node
206  lappend discovered_modules $key
207  }
208  return $discovered_modules
209 }
210 
211 proc _hier_parse_ip {hier_meta_ref file_info {include_gen_prods 0}} {
212  upvar 1 $hier_meta_ref hier_meta
213 
214  set f [file_info_path $file_info]
215  if {![file exists $f]} { return }
216 
217  set library [file_info_library $file_info]
218  set name [file rootname [file tail $f]]
219  set mod_properties [dict create]
220  dict set mod_properties filetype "XCI"
221 
222  set output_dir "."
223  if {[catch {open $f r} fid]} {
224  Msg Warning "Warning: Could not open XCI file: $f"
225  } else {
226  set content [read $fid]
227  close $fid
228 
229  if {[regexp {"OUTPUTDIR":\s*\[\s*\{\s*"value":\s*"([^"]+)"} $content -> dir_value]} {
230  set output_dir $dir_value
231  }
232  }
233 
234  set xci_dir [file dirname $f]
235  set resolved_output_dir [file normalize [file join $xci_dir $output_dir]]
236 
237  # Recursively scan for HDL files (max 5 levels deep)
238  set hdl_files [list]
239  set dirs_to_scan [list [list $resolved_output_dir 0]]
240 
241  if { $include_gen_prods == 1} {
242  while {[llength $dirs_to_scan] > 0} {
243  set current [lindex $dirs_to_scan 0]
244  set dirs_to_scan [lrange $dirs_to_scan 1 end]
245  set current_dir [lindex $current 0]
246  set current_depth [lindex $current 1]
247 
248  if {$current_depth >= 5} { continue }
249  if {![file isdirectory $current_dir]} { continue }
250 
251  set entries [glob -nocomplain -directory $current_dir *]
252  foreach entry $entries {
253  if {[file isfile $entry]} {
254  set ext [string tolower [file extension $entry]]
255  if {$ext eq ".vhd" || $ext eq ".v" || $ext eq ".sv"} {
256  lappend hdl_files $entry
257  }
258  } elseif {[file isdirectory $entry]} {
259  set dir_name [file tail $entry]
260  # Skip hdl and synth directories
261  if {$dir_name ne "synth"} {
262  lappend dirs_to_scan [list $entry [expr {$current_depth + 1}]]
263  }
264  }
265  }
266  }
267  }
268 
269  set file_properties [file_info_properties $file_info]
270  set all_subs [list]
271 
272  foreach hdl_file $hdl_files {
273  set ext [string tolower [file extension $hdl_file]]
274 
275  # a lot of xci's share submodules, so we can just reuse the ones we already parsed
276  set file_checksum [_compute_file_checksum $hdl_file]
277  if {$file_checksum ne "" && [dict exists $hier_meta parsed_files_cache $file_checksum]} {
278  Msg Debug "Reusing previously parsed modules for $hdl_file"
279  set discovered_modules [dict get $hier_meta parsed_files_cache $file_checksum]
280  set all_subs [concat $all_subs $discovered_modules]
281  continue
282  }
283 
284 
285 
286  set hdl_file_info [_create_proj_file_info $hdl_file $library $file_properties]
287  set discovered_modules [list]
288 
289  if {$ext eq ".vhd" || $ext eq ".vhdl" || $ext eq ".v" || $ext eq ".sv"} {
290  set discovered_modules [_hier_parse_hdl hier_meta $hdl_file_info]
291  }
292 
293  if {$file_checksum ne ""} {
294  dict set hier_meta parsed_files_cache $file_checksum $discovered_modules
295  }
296 
297  set all_subs [concat $all_subs $discovered_modules]
298  }
299 
300  set all_subs [lsort -unique $all_subs]
301 
302  if {[llength $all_subs] == 0} {
303  # if no submodules, we probably haven't generated the output, so store xci as component
304  # so other rtl can find it
305  _store_module hier_meta $name $library "component" $f $all_subs $mod_properties
306  }
307  _store_module hier_meta $name $library "xci" $f $all_subs $mod_properties
308 
309 
310 
311 }
312 
313 
314 proc _hier_parse_file {hier_meta_ref file_info {include_gen_prods 0}} {
315  upvar 1 $hier_meta_ref hier_meta
316 
317  set ext [string tolower [file_info_ext $file_info]]
318  if {$ext eq ".vhd" || $ext eq ".vhdl" || $ext eq ".v" || $ext eq ".sv" ||
319  $ext eq ".vh" || $ext eq ".svh"} {
320  _hier_parse_hdl hier_meta $file_info
321  } elseif {$ext eq ".xci"} {
322  _hier_parse_ip hier_meta $file_info $include_gen_prods
323  } elseif {$ext eq ".bd"} {
324  _hier_parse_bd hier_meta $file_info
325  } else {
326  Msg Warning "Warning: unrecognized file type for [file_info_path $file_info]"
327  }
328 
329 
330 }
331 
332 proc _hier_submodule_append {hier_meta_ref parent_key sub_key} {
333  upvar 1 $hier_meta_ref hier_meta
334 
335  if {[dict exists $hier_meta all_modules $parent_key]} {
336  set mod [dict get $hier_meta all_modules $parent_key]
337  dict lappend mod references $sub_key
338  dict set hier_meta all_modules $parent_key $mod
339  }
340 }
341 
342 
343 proc _reference_resolver {hier_meta_ref} {
344  upvar 1 $hier_meta_ref hier_meta
345 
346  set package_bodies [ dict filter [dict get $hier_meta all_modules] script {k v} {expr {[dict get $v type] eq "vhdl_package_body"}}]
347  set architectures [ dict filter [dict get $hier_meta all_modules] script {k v} {expr {[dict get $v type] eq "vhdl_architecture"}}]
348 
349  dict for {package_body body_info} $package_bodies {
350  set entity_key [split $package_body "."]
351  set entity_key "[lindex $entity_key 0].vhdl_package.[lindex $entity_key 2]"
352  if {[dict exists [dict get $hier_meta all_modules] $entity_key]} {
353  _hier_submodule_append hier_meta $entity_key $package_body
354  continue
355  }
356  }
357 
358  dict for {architecture arch_info} $architectures {
359  # if {[string first "top_l0mdt" $architecture] != -1} {
360  # puts $architecture
361  # puts $arch_info
362  # # exit 0
363  # }
364  # if {[string first "top_control" $architecture] != -1} {
365  # puts $architecture
366  # puts $arch_info
367  # # exit 0
368  # }
369  set entity_key [split $architecture "."]
370  set entity_key "[lindex $entity_key 0].vhdl_entity.[lindex $entity_key 2]"
371  if {[dict exists [dict get $hier_meta all_modules] $entity_key]} {
372  _hier_submodule_append hier_meta $entity_key $architecture
373  continue
374  }
375  }
376 
377 
378  set total_resolved 0
379  set resolution_list [list]
380 
381  dict for {mod_key mod} [dict get $hier_meta all_modules] {
382  set references_data [dict get $mod references]
383  set new_references [list]
384  set mod_lib [dict get $mod library]
385  set top_control 0
386  if { [string first "top_control" $mod_key] != -1 } {
387  set top_control 1
388  }
389 
390  foreach ref $references_data {
391  # puts "Processing ref: $ref in mod: $mod_key"
392  set parts [split $ref "."]
393  set library [lindex $parts 0]
394  set type [lindex $parts 1]
395  set name [lindex $parts 2]
396 
397 
398  set found 0
399  set resolved_mod_name ""
400 
401  if {$library == "unknown" && $type == "sv_package"} {
402  set pattern ".*\\.sv_package\\.$name\$"
403  set matches [dict filter [dict get $hier_meta all_modules] \
404  script {k v} {expr {[regexp $pattern $k]}}]
405  if {[dict size $matches] == 0} {
406  lappend new_references $ref
407  Msg Debug "No sv_package match found for $ref in $mod_key"
408  } else {
409  dict for {k v} $matches {
410  lappend new_references $k
411  Msg Debug "Mod: $mod_key resolved $ref to $k"
412  incr total_resolved
413  }
414  }
415  continue
416  }
417 
418  if { ($library != "unknown" && $library != "work" && $type != "component") || ($library == "unknown" && $type != "component") } {
419  # puts "Keeping reference as-is: $ref"
420  lappend new_references $ref
421  continue;
422  }
423 
424  set ref_lib ""
425  if { $library != "unknown"} {
426  set ref_lib $library
427  } else {
428  set ref_lib $mod_lib
429  }
430 
431 
432  set pattern "${ref_lib}\\.(vhdl_entity|verilog_module)\\.$name\$"
433  set matches [dict filter [dict get $hier_meta all_modules] script {k v} {expr {[regexp $pattern $k]}}]
434  if {[dict size $matches] == 0} {
435  set pattern ".*\\.(vhdl_entity|verilog_module)\\.$name\$"
436  set matches [dict filter [dict get $hier_meta all_modules] script {k v} {expr {[regexp $pattern $k]}}]
437  if {[dict size $matches] > 1} {
438  Msg Warning "Ambiguous component reference '$name' in '$mod_key': multiple libraries match, picking first found"
439  }
440  }
441 
442  if {[dict size $matches] == 0} {
443  lappend new_references $ref
444  Msg Debug "No match found for $ref in $mod_key"
445  } else {
446  dict for {k v} $matches {
447  lappend new_references $k
448  Msg Debug "Mod: $mod_key resolved $ref to $k"
449  incr total_resolved
450  }
451  }
452  }
453 
454  dict set hier_meta all_modules $mod_key references $new_references
455  }
456 
457  # Second pass: resolve `include file dependencies.
458  # For each module that recorded sv_includes, find the project file matching
459  # each included basename and add all modules defined in that file as references.
460  dict for {mod_key mod} [dict get $hier_meta all_modules] {
461  if {![dict exists $mod sv_includes]} { continue }
462  set sv_includes [dict get $mod sv_includes]
463  if {[llength $sv_includes] == 0} { continue }
464 
465  set new_refs [dict get $mod references]
466  foreach include_file $sv_includes {
467  dict for {proj_file _} [dict get $hier_meta proj_files] {
468  if {[file tail $proj_file] ne $include_file} { continue }
469  dict for {k m} [dict get $hier_meta all_modules] {
470  if {[dict get $m file_path] eq $proj_file &&
471  [lsearch -exact $new_refs $k] == -1} {
472  lappend new_refs $k
473  incr total_resolved
474  Msg Debug "Mod: $mod_key include-depends on $k (via $include_file)"
475  }
476  }
477  }
478  }
479  dict set hier_meta all_modules $mod_key references $new_refs
480  }
481 
482  return [dict create total $total_resolved resolutions $resolution_list]
483 }
484 
485 proc dfs_sort {hier_meta_ref top_module} {
486  upvar 1 $hier_meta_ref hier_meta
487 
488 
489  proc _dfs_visit {hier_meta_ref node sorted_ref bad_nodes_ref} {
490  upvar 1 $hier_meta_ref hier_meta
491  upvar 1 $sorted_ref sorted
492  upvar 1 $bad_nodes_ref bad_nodes
493 
494  if {![dict exists $hier_meta all_modules $node]} {
495  return
496  }
497 
498  set mod [dict get $hier_meta all_modules $node]
499  set color [dict get $mod color]
500 
501  if {$color eq "gray"} {
502  Msg Warning "Warning: Circular dependency detected at $node"
503  if {[lsearch -exact $bad_nodes $node] == -1} {
504  lappend bad_nodes $node
505  }
506  return
507  }
508 
509  if {$color eq "black"} {
510  return
511  }
512 
513  dict set mod color "gray"
514  dict set hier_meta all_modules $node $mod
515 
516 
517  set references [dict get $mod references]
518  foreach child $references {
519  _dfs_visit hier_meta $child sorted bad_nodes
520  }
521 
522  set mod [dict get $hier_meta all_modules $node]
523  dict set mod color "black"
524  dict set hier_meta all_modules $node $mod
525  lappend sorted $node
526  }
527 
528 
529  set sorted [list]
530  set bad_nodes [list]
531 
532  _dfs_visit hier_meta $top_module sorted bad_nodes
533 
534  if {[llength $bad_nodes] > 0} {
535  return [dict create success 0 sorted {} cycles 1 bad_nodes $bad_nodes]
536  }
537 
538  return [dict create success 1 sorted $sorted cycles 0 bad_nodes {}]
539 }
540 
541 
542 proc _debug_string_hier_meta {hier_meta_ref {indent 0}} {
543  upvar 1 $hier_meta_ref hier_meta
544 
545  set ind [string repeat " " $indent]
546  set s "${ind}=== ALL MODULES ==="
547  dict for {key mod} [dict get $hier_meta all_modules] {
548  append s "\n${ind}$key:"
549  dict for {field value} $mod {
550  append s "\n${ind} $field: $value"
551  }
552  append s "\n"
553  }
554 
555  append s "\n${ind}=== PROJECT FILES ==="
556  dict for {file finfo} [dict get $hier_meta proj_files] {
557  append s "\n${ind}$file:"
558  dict for {field value} $finfo {
559  append s "\n${ind} $field: $value"
560  }
561  append s "\n"
562  }
563  return $s
564 }
565 
566 
567 
568 proc Hierarchy {listProperties listLibraries repo_path {output_path ""} \
569 {compile_order 0} {light ""} {top_module_override ""} {ignore_opt_list ""} {include_ieee 0} {include_gen_prods 0} {quiet 0}} {
570  set hier_meta [_create_hier_meta]
571 
572  set top_module ""
573 
574  set ignore_list [list]
575 
576  if {$include_ieee == 0} {
577  lappend ignore_list "ieee.*.*"
578  lappend ignore_list "std.*.*"
579  }
580 
581  foreach pat [split $ignore_opt_list ","] {
582  set pat [string trim $pat]
583  if {$pat ne ""} {
584  if {![regexp {^[\w*]+\.[\w*]+\.[\w*]+$} $pat]} {
585  Msg Warning "Warning: ignore pattern '$pat' does not match expected format <lib>.<type>.<name> (wildcards * allowed), ignoring"
586  } else {
587  lappend ignore_list $pat
588  }
589  }
590  }
591 
592  if {$top_module_override ne ""} {
593  set top_module $top_module_override
594  Msg Warning "Using specified top module: $top_module"
595  }
596 
597  dict for {lib files} $listLibraries {
598  set lib [file rootname $lib]
599 
600  foreach f $files {
601  set props ""
602  if {[dict exists $listProperties $f]} {
603 
604  set fprops [dict get $listProperties $f]
605  if {$top_module eq ""} {
606  set top [lindex [regexp -inline {\ytop\s*=\s*(.+?)\y.*} $fprops] 1]
607  if {$top != ""} {
608  set ext [file extension $f]
609  if {$ext eq ".vhd" || $ext eq ".vhdl"} {
610  set top_module "${lib}.vhdl_entity.${top}"
611  } elseif { $ext eq ".v" || $ext eq ".sv"} {
612  set top_module "${lib}.verilog_module.${top}"
613  } else {
614  set top_module "${lib}.component.${top}"
615  }
616  }
617  }
618 
619  set props $fprops
620  }
621  dict set hier_meta proj_files $f [_create_proj_file_info $f $lib $props]
622  }
623  }
624 
625  if {$top_module_override eq ""} {
626  Msg Info "Top module from properties: $top_module"
627  }
628 
629  set t_parse [time {
630  dict for {file file_info} [dict get $hier_meta proj_files] {
631  _hier_parse_file hier_meta $file_info $include_gen_prods
632  }
633  } 1]
634  set parse_us [lindex $t_parse 0]
635  set parse_ms [expr {$parse_us / 1000.0}]
636 
637  Msg Info "Completed initial parsing in $parse_ms ms"
638 
639  set t_resolve [time {
640  set resolve_result [_reference_resolver hier_meta]
641  } 1]
642  set resolve_us [lindex $t_resolve 0]
643  set resolve_ms [expr {$resolve_us / 1000.0}]
644 
645  set total [dict get $resolve_result total]
646  set resolutions [dict get $resolve_result resolutions]
647  Msg Info "Completed reference resolution: $total references resolved in $resolve_ms ms"
648 
649 
650  if {$output_path != ""} {
651  set output_file [open $repo_path/$output_path "w"]
652  } else {
653  set output_file ""
654  puts ""
655  }
656 
657 
658  set sorted_modules [dfs_sort hier_meta $top_module]
659  set bad_nodes [dict get $sorted_modules bad_nodes]
660 
661  #Msg Debug "[_debug_string_hier_meta hier_meta]"
662  set p [dict create]
663  if {$compile_order} {
664  set compile_order_dict [print_compile_order hier_meta [dict get $sorted_modules sorted] $output_file $quiet]
665  if {$output_path != ""} {
666  close $output_file
667  }
668  return $compile_order_dict
669  } else {
670  set p [print_hierarchy hier_meta $top_module $output_file $ignore_list $bad_nodes $light]
671  PrintOrWrite $output_file "\n\n=====Packages in project:====="
672  dict for {lib pkg_list} $p {
673  if {[llength $pkg_list] == 0} {
674  continue
675  }
676  PrintOrWrite $output_file "Library: $lib"
677  foreach pkg_entry $pkg_list {
678  PrintOrWrite $output_file " Package: $pkg_entry"
679  }
680  }
681  }
682 
683  if {$output_path != ""} {
684  close $output_file
685  }
686 }
687 
688 proc print_compile_order {hier_meta_ref sorted_list {output_file ""} {quiet 0}} {
689  upvar 1 $hier_meta_ref hier_meta
690 
691  # Build an ordered flat list {file_path library ...}, deduplicating by
692  # {file_path, library} pair so the same file compiled into two libraries
693  # both appear.
694  # Pass 1: DFS-reachable modules in topological (dependency-first) order.
695  # Pass 2: remaining project files not reachable from the top module (e.g.
696  # packages, header files, orphaned modules) so every listed file
697  # gets compiled.
698  set result [list]
699  set seen [dict create]
700 
701  foreach mod_key $sorted_list {
702  set mod [dict get $hier_meta all_modules $mod_key]
703  set file_path [dict get $mod file_path]
704  set library [dict get $mod library]
705  set pair "${file_path}\t${library}"
706 
707  if {$file_path eq "" || [dict exists $seen $pair]} {
708  continue
709  }
710  dict set seen $pair 1
711  lappend result $file_path $library
712  }
713 
714  dict for {file file_info} [dict get $hier_meta proj_files] {
715  set library [file_info_library $file_info]
716  set pair "${file}\t${library}"
717  if {[dict exists $seen $pair]} {
718  continue
719  }
720  dict set seen $pair 1
721  lappend result $file $library
722  }
723 
724  if {$quiet == 0} {
725  foreach {file lib} $result {
726  PrintOrWrite $output_file "$file $lib"
727  }
728  }
729 
730  return $result
731 }
732 
733 proc print_hierarchy {hier_meta_ref module {output_file ""} {ignore_list ""} \
734 {bad_nodes ""} {light 0} {indent 0} {stack_ref ""} {last_properties_ref ""} {is_last 1}} {
735  upvar 1 $hier_meta_ref hier_meta
736 
737  set package_dict {}
738 
739  if {[is_ignored_module $module $ignore_list]} {
740  return
741  }
742 
743  if {$stack_ref eq ""} {
744  set stack [list]
745  set last_properties [list]
746  } else {
747  upvar 1 $stack_ref stack
748  upvar 1 $last_properties_ref last_properties
749  }
750 
751 
752  if {![dict exists $hier_meta all_modules $module]} {
753  set parts [split $module "."]
754  set lib [lindex $parts 0]
755  set type [lindex $parts 1]
756  set name [lindex $parts 2]
757  set file_path ""
758  set module_exists 0
759  } else {
760 
761  set mod [dict get $hier_meta all_modules $module]
762  # puts $mod
763  set name [dict get $mod name]
764  set type [dict get $mod type]
765  set lib [dict get $mod library]
766  set file_path [dict get $mod file_path]
767  set module_exists 1
768 
769 
770  # for vhdl entities with 1 architecture in the same file, just use that architecture
771  if {$type eq "vhdl_entity" && $module_exists} {
772  set references [dict get $mod references]
773  set arch_refs [list]
774  foreach ref $references {
775  if {[string match "${lib}.vhdl_architecture.${name}.*" $ref]} {
776  lappend arch_refs $ref
777  }
778  }
779  if {[llength $arch_refs] == 1} {
780  set arch_key [lindex $arch_refs 0]
781  if {[dict exists $hier_meta all_modules $arch_key]} {
782  set arch_mod [dict get $hier_meta all_modules $arch_key]
783  set arch_file_path [dict get $arch_mod file_path]
784  if {$arch_file_path eq $file_path} {
785  set p [print_hierarchy hier_meta $arch_key $output_file $ignore_list $bad_nodes $light $indent stack last_properties $is_last]
786  set package_dict [MergeDict $p $package_dict]
787  return $package_dict
788  }
789  }
790  }
791  }
792  }
793 
794  set is_circular 0
795  if {[lsearch -exact $stack $module] != -1} {
796  if {[lsearch -exact $bad_nodes $module] != -1} {
797  set is_circular 1
798  }
799  }
800 
801  if {!$is_circular} {
802  lappend stack $module
803  }
804 
805  set indent_str ""
806  for {set i 0} {$i < [llength $last_properties]} {incr i} {
807  if {[lindex $last_properties $i]} {
808  append indent_str " "
809  } else {
810  append indent_str "│ "
811  }
812  }
813 
814  if {$indent > 0} {
815  if {$is_last} {
816  set connector "└─ "
817  } else {
818  set connector "├─ "
819  }
820  } else {
821  set connector ""
822  }
823 
824  if {$light} {
825  set path_str ""
826  } else {
827  set path_str " - ${file_path}"
828  }
829 
830  if {$type == "vhdl_architecture"} {
831  set name "[dict get $mod entity].$name"
832  }
833 
834 
835  if {[string first "vhdl_package" $type] == -1} {
836  if {!$module_exists} {
837  set msg "${indent_str}${connector}${lib}.${name} (${type})"
838  } elseif {$is_circular} {
839  set msg "${indent_str}${connector}${lib}.${name} (${type})${path_str} \[WARNING: circular reference detected\]"
840  } else {
841  set msg "${indent_str}${connector}${lib}.${name} (${type})${path_str}"
842  }
843  } else {
844  if {[DictGet $package_dict "$lib"] == ""} {
845  dict set package_dict "$lib" [list]
846  }
847  set package_list [DictGet $package_dict "$lib"]
848  if {![IsInList $package_list "${name} ${path_str}"] } {
849  lappend package_list "${name} ${path_str}"
850  dict set package_dict "$lib" $package_list
851  }
852  return $package_dict
853  }
854 
855  PrintOrWrite $output_file $msg
856 
857  if {$is_circular || !$module_exists} {
858  return $package_dict
859  }
860 
861  set references [dict get $mod references]
862  set all_subs [lsort -unique $references]
863 
864  # Filter out ignored modules before processing
865  set filtered_subs [list]
866  foreach sub $all_subs {
867  if {![is_ignored_module $sub $ignore_list]} {
868  lappend filtered_subs $sub
869  }
870  }
871 
872  set num_subs [llength $filtered_subs]
873  set sub_idx 0
874  foreach sub $filtered_subs {
875  incr sub_idx
876  set is_last_child [expr {$sub_idx == $num_subs}]
877 
878  lappend last_properties $is_last
879  set p [print_hierarchy hier_meta $sub $output_file $ignore_list $bad_nodes $light [expr {$indent + 1}] stack last_properties $is_last_child]
880  set package_dict [MergeDict $p $package_dict]
881  set last_properties [lrange $last_properties 0 end-1]
882  }
883 
884  set stack [lrange $stack 0 end-1]
885  return $package_dict
886 }
887 
888 
889 
890 proc get_rtl_refs {node {name ""}} {
891  set out [dict create]
892 
893  if {$name ne "" && ![catch {dict get $node reference_info} refinfo]} {
894  set rt ""; set rn ""
895  catch {set rt [dict get $refinfo ref_type]}
896  catch {set rn [dict get $refinfo ref_name]}
897  if {[string equal $rt "hdl"] && $rn ne ""} {
898  dict set out $name $rn
899  }
900  }
901 
902  if {![catch {dict get $node components} comps]} {
903  dict for {cname cnode} $comps {
904  set childMap [get_rtl_refs $cnode $cname]
905  set out [dict merge $out $childMap]
906  }
907  }
908 
909  dict for {k v} $node {
910  if {$k eq "components" || $k eq "reference_info"} {continue}
911  if {[catch {dict size $v}]} {continue}
912  set childMap [get_rtl_refs $v $k]
913  set out [dict merge $out $childMap]
914  }
915 
916  return $out
917 }
918 
919 proc _hier_parse_bd {hier_meta_ref file_info} {
920  upvar 1 $hier_meta_ref hier_meta
921 
922  set f [file_info_path $file_info]
923 
924  if {![file exists $f]} { return }
925 
926  set library [file_info_library $file_info]
927  set file_properties [file_info_properties $file_info]
928 
929  set name [file rootname [file tail $f]]
930  set mod_properties [dict create]
931  dict set mod_properties filetype "BD"
932 
933  set bd_file [open $f r]
934  set bd_json [read $bd_file]
935  close $bd_file
936  set bd_design $bd_json
937 
938  set lines [split $bd_design "\n"]
939  set filtered_lines {}
940  foreach line $lines {
941  if {[string first "\\" $line] == -1} {
942  lappend filtered_lines $line
943  }
944  }
945  set bd_design [join $filtered_lines "\n"]
946 
947  regsub -all {":\s*\{} $bd_design " \{" bd_design
948  regsub -all {:\s*("(?:[^"\\]|\\.)*")} $bd_design { {\1}} bd_design
949  regsub -all {"} $bd_design {} bd_design
950  regsub -all {,} $bd_design {} bd_design
951  regsub -all {:\s* \{} $bd_design {\{} bd_design
952  regsub -all {\[} $bd_design "\{" bd_design
953  regsub -all {\]} $bd_design "\}" bd_design
954 
955  set bd_design [string range $bd_design 1 end-1]
956 
957  if {[catch {dict size $bd_design} err]} {
958  Msg Warning "Warning: malformed bd_design in $f, skipping"
959  return {}
960  }
961 
962  set bd_design [lindex $bd_design 1]
963 
964  set unknown_modules {}
965  dict for {m v} [get_rtl_refs $bd_design] {
966  if {[lsearch -exact $unknown_modules $v] == -1} {
967  lappend unknown_modules "unknown.component.$v"
968  }
969  }
970  _store_module hier_meta $name $library component $f $unknown_modules $mod_properties
971 
972 }