Hog Hog2026.2-1
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 _xml_extract_blocks {xml_content tag} {
212  set blocks [list]
213  set open_tag "<$tag>"
214  set close_tag "</$tag>"
215  set cursor 0
216  while {1} {
217  set start [string first $open_tag $xml_content $cursor]
218  if {$start == -1} { break }
219  set content_start [expr {$start + [string length $open_tag]}]
220  set end [string first $close_tag $xml_content $content_start]
221  if {$end == -1} { break }
222  set block [string range $xml_content $content_start [expr {$end - 1}]]
223  lappend blocks [string trim $block]
224  set cursor [expr {$end + [string length $close_tag]}]
225  }
226  return $blocks
227 }
228 
229 
230 proc _parse_xci_xml {xml_content} {
231  set view_list [list]
232 
233  set filesets_dict [dict create]
234  foreach filesets_block [_xml_extract_blocks $xml_content "spirit:fileSets"] {
235  foreach fs_block [_xml_extract_blocks $filesets_block "spirit:fileSet"] {
236  set fs_name [lindex [_xml_extract_blocks $fs_block "spirit:name"] 0]
237  if {$fs_name eq ""} { continue }
238  set fs_files [list]
239  foreach file_block [_xml_extract_blocks $fs_block "spirit:file"] {
240  set file_name [lindex [_xml_extract_blocks $file_block "spirit:name"] 0]
241  set logical_name [lindex [_xml_extract_blocks $file_block "spirit:logicalName"] 0]
242  if {$file_name ne ""} {
243  set file_dict [dict create name $file_name logicalName $logical_name]
244  lappend fs_files $file_dict
245  }
246  }
247  dict set filesets_dict $fs_name $fs_files
248  }
249  }
250 
251  foreach views_block [_xml_extract_blocks $xml_content "spirit:views"] {
252  foreach view_block [_xml_extract_blocks $views_block "spirit:view"] {
253  set view_name [lindex [_xml_extract_blocks $view_block "spirit:name"] 0]
254  set model_name [lindex [_xml_extract_blocks $view_block "spirit:modelName"] 0]
255  set view_files [list]
256  foreach fileSetRef [_xml_extract_blocks $view_block "spirit:fileSetRef"] {
257  set fs_name [lindex [_xml_extract_blocks $fileSetRef "spirit:localName"] 0]
258  if {$fs_name ne "" && [dict exists $filesets_dict $fs_name]} {
259  set view_files [concat $view_files [dict get $filesets_dict $fs_name]]
260  }
261  }
262 
263  set view_type ""
264  if {[string match "xilinx_*behavioralsimulation" $view_name]} {
265  set view_type "behav"
266  } elseif {[string match "xilinx_*simulationwrapper" $view_name]} {
267  set view_type "wrapper"
268  }
269 
270  if {$view_type ne "" && [llength $view_files] > 0 && $model_name ne ""} {
271  set view_dict [dict create name $view_name type $view_type modelName $model_name files $view_files]
272  lappend view_list $view_dict
273  }
274  }
275  }
276 
277  set wrapper_views [list]
278  set non_wrapper_views [list]
279 
280  foreach view $view_list {
281  set view_type [dict get $view type]
282  if {$view_type eq "wrapper"} {
283  lappend wrapper_views $view
284  } else {
285  lappend non_wrapper_views $view
286  }
287  }
288 
289  return [dict create wrappers $wrapper_views nonwrappers $non_wrapper_views]
290 }
291 
292 proc _hier_parse_ip {hier_meta_ref file_info {include_gen_prods 0}} {
293  upvar 1 $hier_meta_ref hier_meta
294 
295  set f [file_info_path $file_info]
296  if {![file exists $f]} { return }
297 
298  set library [file_info_library $file_info]
299  set name [file rootname [file tail $f]]
300  set mod_properties [dict create]
301  dict set mod_properties filetype "XCI"
302 
303  set output_dir "."
304  if {[catch {open $f r} fid]} {
305  Msg Warning "Warning: Could not open XCI file: $f"
306  } else {
307  set content [read $fid]
308  close $fid
309 
310  if {[regexp {"OUTPUTDIR":\s*\[\s*\{\s*"value":\s*"([^"]+)"} $content -> dir_value]} {
311  set output_dir $dir_value
312  }
313  }
314 
315  set xci_dir [file dirname $f]
316  set resolved_output_dir [file normalize [file join $xci_dir $output_dir]]
317 
318  set hdl_files [list]
319  set all_subs [list]
320 
321  if { $include_gen_prods == 1} {
322  set xml_path [file join $xci_dir "${name}.xml"]
323  if {[file exists $xml_path]} {
324  if {[catch {open $xml_path r} xml_fid]} {
325  Msg Warning "Warning: Could not open XML file: $xml_path"
326  } else {
327  set xml_content [read $xml_fid]
328  close $xml_fid
329 
330  set view_data [_parse_xci_xml $xml_content]
331  set wrapper_views [dict get $view_data wrappers]
332  set non_wrapper_views [dict get $view_data nonwrappers]
333 
334  # For vivado ips, it seems we have a wrapper view and behav simulation views
335  # Wrapper will only have 1 file (I think), and will depend upon the behav simulation views, so
336  # for compile order, wrapper --> view --> view_files
337  # where view is a dummy node that is used to link the files back to the wrapper
338 
339  foreach view $non_wrapper_views {
340  set view_name [dict get $view name]
341  set view_type [dict get $view type]
342  set model_name [dict get $view modelName]
343  set view_files [dict get $view files]
344 
345 
346  # Vivado will generate duplicate files for different langauges<
347  # we should discover the duplicates first so we can link them to each other
348  # that we in compile_order we can determine which one to compile based on user_pref(?) or wrapper
349  set unique_files [dict create]
350  foreach file_dict $view_files {
351  set file_name [dict get $file_dict name]
352  set logical_name [dict get $file_dict logicalName]
353  set file_base [file rootname [file tail $file_name]]
354 
355  if {![dict exists $unique_files $file_base]} {
356  dict set unique_files $file_base [list]
357  }
358  dict lappend unique_files $file_base [dict create name $file_name logicalName $logical_name]
359  }
360 
361 
362  set view_file_refs [list]
363  dict for {base_name file_list} $unique_files {
364  set all_node_keys [list]
365  foreach file_dict $file_list {
366  set file_name [dict get $file_dict name]
367  set logical_name [dict get $file_dict logicalName]
368  lappend all_node_keys "${logical_name}.xci_gen_file.${file_name}"
369  }
370 
371  foreach file_dict $file_list {
372  set file_name [dict get $file_dict name]
373  set logical_name [dict get $file_dict logicalName]
374  set full_file_path [file normalize [file join $resolved_output_dir $file_name]]
375  set file_node_key "${logical_name}.xci_gen_file.${file_name}"
376 
377  set file_ext [string tolower [file extension $file_name]]
378  set filetype "Unknown"
379  if {$file_ext eq ".v"} {
380  set filetype "Verilog"
381  } elseif {$file_ext eq ".vhd" || $file_ext eq ".vhdl"} {
382  set filetype "VHDL"
383  } elseif {$file_ext eq ".sv"} {
384  set filetype "SystemVerilog"
385  }
386 
387  set file_props [dict create filetype $filetype logicalName $logical_name xciRole "generated"]
388 
389  # Add duplicates
390  if {[llength $file_list] > 1} {
391  set other_keys [lsearch -all -inline -not -exact $all_node_keys $file_node_key]
392  dict set file_props duplicates $other_keys
393  }
394 
395  _store_module hier_meta $file_name $logical_name "xci_gen_file" $full_file_path [list] $file_props
396  lappend view_file_refs $file_node_key
397  }
398  }
399 
400  set view_node_key "${library}.${view_name}.${model_name}"
401  set view_props [dict create filetype "XCI_VIEW" xciRole "view" viewType $view_type]
402  _store_module hier_meta $model_name $library $view_name "" $view_file_refs $view_props
403  }
404 
405  foreach wrapper $wrapper_views {
406  set wrapper_model_name [dict get $wrapper modelName]
407  set wrapper_name [dict get $wrapper name]
408 
409  set wrapper_refs [list]
410  foreach view $non_wrapper_views {
411  set model_name [dict get $view modelName]
412  set view_name [dict get $view name]
413  set view_node_key "${library}.${view_name}.${model_name}"
414  lappend wrapper_refs $view_node_key
415  }
416 
417  # let's use the first file in the list as the wrapper file
418  set wrapper_file [dict get [lindex [dict get $wrapper files] 0] name]
419  set wrapper_file [file normalize [file join $resolved_output_dir $wrapper_file]]
420  set file_ext [string tolower [file extension $wrapper_file]]
421 
422  set wrapper_node_key "${library}.xci_wrapper.${wrapper_model_name}"
423  set filetype "Unknown"
424  if {$file_ext eq ".v"} {
425  set filetype "Verilog"
426  } elseif {$file_ext eq ".vhd" || $file_ext eq ".vhdl"} {
427  set filetype "VHDL"
428  } elseif {$file_ext eq ".sv"} {
429  set filetype "SystemVerilog"
430  }
431  set wrapper_props [dict create filetype $filetype xciRole "wrapper"]
432  _store_module hier_meta $wrapper_model_name $library "xci_wrapper" $wrapper_file $wrapper_refs $wrapper_props
433 
434  lappend all_subs $wrapper_node_key
435  }
436  }
437  }
438  }
439 
440  set file_properties [file_info_properties $file_info]
441 
442  set all_subs [lsort -unique $all_subs]
443 
444  _store_module hier_meta $name $library "xci" $f $all_subs $mod_properties
445 
446 
447 
448 }
449 
450 
451 proc _hier_parse_file {hier_meta_ref file_info {include_gen_prods 0}} {
452  upvar 1 $hier_meta_ref hier_meta
453 
454  set ext [string tolower [file_info_ext $file_info]]
455  if {$ext eq ".vhd" || $ext eq ".vhdl" || $ext eq ".v" || $ext eq ".sv" ||
456  $ext eq ".vh" || $ext eq ".svh"} {
457  _hier_parse_hdl hier_meta $file_info
458  } elseif {$ext eq ".xci"} {
459  _hier_parse_ip hier_meta $file_info $include_gen_prods
460  } elseif {$ext eq ".bd"} {
461  _hier_parse_bd hier_meta $file_info
462  } else {
463  Msg Warning "Warning: unrecognized file type for [file_info_path $file_info]"
464  }
465 
466 
467 }
468 
469 proc _hier_submodule_append {hier_meta_ref parent_key sub_key} {
470  upvar 1 $hier_meta_ref hier_meta
471 
472  if {[dict exists $hier_meta all_modules $parent_key]} {
473  set mod [dict get $hier_meta all_modules $parent_key]
474  dict lappend mod references $sub_key
475  dict set hier_meta all_modules $parent_key $mod
476  }
477 }
478 
479 
480 proc _reference_resolver {hier_meta_ref} {
481  upvar 1 $hier_meta_ref hier_meta
482 
483  set package_bodies [ dict filter [dict get $hier_meta all_modules] script {k v} {expr {[dict get $v type] eq "vhdl_package_body"}}]
484  set architectures [ dict filter [dict get $hier_meta all_modules] script {k v} {expr {[dict get $v type] eq "vhdl_architecture"}}]
485 
486  dict for {package_body body_info} $package_bodies {
487  set entity_key [split $package_body "."]
488  set entity_key "[lindex $entity_key 0].vhdl_package.[lindex $entity_key 2]"
489  if {[dict exists [dict get $hier_meta all_modules] $entity_key]} {
490  _hier_submodule_append hier_meta $entity_key $package_body
491  continue
492  }
493  }
494 
495  dict for {architecture arch_info} $architectures {
496  # if {[string first "top_l0mdt" $architecture] != -1} {
497  # puts $architecture
498  # puts $arch_info
499  # # exit 0
500  # }
501  # if {[string first "top_control" $architecture] != -1} {
502  # puts $architecture
503  # puts $arch_info
504  # # exit 0
505  # }
506  set entity_key [split $architecture "."]
507  set entity_key "[lindex $entity_key 0].vhdl_entity.[lindex $entity_key 2]"
508  if {[dict exists [dict get $hier_meta all_modules] $entity_key]} {
509  _hier_submodule_append hier_meta $entity_key $architecture
510  continue
511  }
512  }
513 
514 
515  set total_resolved 0
516  set resolution_list [list]
517 
518  dict for {mod_key mod} [dict get $hier_meta all_modules] {
519  set references_data [dict get $mod references]
520  set new_references [list]
521  set mod_lib [dict get $mod library]
522  set top_control 0
523  if { [string first "top_control" $mod_key] != -1 } {
524  set top_control 1
525  }
526 
527  foreach ref $references_data {
528  # puts "Processing ref: $ref in mod: $mod_key"
529  set parts [split $ref "."]
530  set library [lindex $parts 0]
531  set type [lindex $parts 1]
532  set name [lindex $parts 2]
533 
534 
535  set found 0
536  set resolved_mod_name ""
537 
538  if {$library == "unknown" && $type == "sv_package"} {
539  set pattern ".*\\.sv_package\\.$name\$"
540  set matches [dict filter [dict get $hier_meta all_modules] \
541  script {k v} {expr {[regexp $pattern $k]}}]
542  if {[dict size $matches] == 0} {
543  lappend new_references $ref
544  Msg Debug "No sv_package match found for $ref in $mod_key"
545  } else {
546  dict for {k v} $matches {
547  lappend new_references $k
548  Msg Debug "Mod: $mod_key resolved $ref to $k"
549  incr total_resolved
550  }
551  }
552  continue
553  }
554 
555  if { ($library != "unknown" && $library != "work" && $type != "component") || ($library == "unknown" && $type != "component") } {
556  # puts "Keeping reference as-is: $ref"
557  lappend new_references $ref
558  continue;
559  }
560 
561  set ref_lib ""
562  if { $library != "unknown"} {
563  set ref_lib $library
564  } else {
565  set ref_lib $mod_lib
566  }
567 
568 
569  set pattern "${ref_lib}\\.(vhdl_entity|verilog_module)\\.$name\$"
570  set matches [dict filter [dict get $hier_meta all_modules] script {k v} {expr {[regexp $pattern $k]}}]
571  if {[dict size $matches] == 0} {
572  set pattern ".*\\.(vhdl_entity|verilog_module)\\.$name\$"
573  set matches [dict filter [dict get $hier_meta all_modules] script {k v} {expr {[regexp $pattern $k]}}]
574  if {[dict size $matches] > 1} {
575  Msg Warning "Ambiguous component reference '$name' in '$mod_key': multiple libraries match, picking first found"
576  }
577  }
578 
579  # Also resolve to XCI wrapper/IP types (e.g. Vivado-generated IPs instantiated as components)
580  if {[dict size $matches] == 0} {
581  set pattern ".*\\.(xci_wrapper|xci)\\.$name\$"
582  set matches [dict filter [dict get $hier_meta all_modules] script {k v} {expr {[regexp $pattern $k]}}]
583  }
584 
585  if {[dict size $matches] == 0} {
586  lappend new_references $ref
587  Msg Debug "No match found for $ref in $mod_key"
588  } else {
589  dict for {k v} $matches {
590  lappend new_references $k
591  Msg Debug "Mod: $mod_key resolved $ref to $k"
592  incr total_resolved
593  }
594  }
595  }
596 
597  dict set hier_meta all_modules $mod_key references $new_references
598  }
599 
600  # Second pass: resolve `include file dependencies.
601  # For each module that recorded sv_includes, find the project file matching
602  # each included basename and add all modules defined in that file as references.
603  dict for {mod_key mod} [dict get $hier_meta all_modules] {
604  if {![dict exists $mod sv_includes]} { continue }
605  set sv_includes [dict get $mod sv_includes]
606  if {[llength $sv_includes] == 0} { continue }
607 
608  set new_refs [dict get $mod references]
609  foreach include_file $sv_includes {
610  dict for {proj_file _} [dict get $hier_meta proj_files] {
611  if {[file tail $proj_file] ne $include_file} { continue }
612  dict for {k m} [dict get $hier_meta all_modules] {
613  if {[dict get $m file_path] eq $proj_file &&
614  [lsearch -exact $new_refs $k] == -1} {
615  lappend new_refs $k
616  incr total_resolved
617  Msg Debug "Mod: $mod_key include-depends on $k (via $include_file)"
618  }
619  }
620  }
621  }
622  dict set hier_meta all_modules $mod_key references $new_refs
623  }
624 
625  return [dict create total $total_resolved resolutions $resolution_list]
626 }
627 
628 proc dfs_sort {hier_meta_ref top_module} {
629  upvar 1 $hier_meta_ref hier_meta
630 
631 
632  proc _dfs_visit {hier_meta_ref node sorted_ref bad_nodes_ref} {
633  upvar 1 $hier_meta_ref hier_meta
634  upvar 1 $sorted_ref sorted
635  upvar 1 $bad_nodes_ref bad_nodes
636 
637  if {![dict exists $hier_meta all_modules $node]} {
638  return
639  }
640 
641  set mod [dict get $hier_meta all_modules $node]
642  set color [dict get $mod color]
643 
644  if {$color eq "gray"} {
645  Msg Warning "Warning: Circular dependency detected at $node"
646  if {[lsearch -exact $bad_nodes $node] == -1} {
647  lappend bad_nodes $node
648  }
649  return
650  }
651 
652  if {$color eq "black"} {
653  return
654  }
655 
656  dict set mod color "gray"
657  dict set hier_meta all_modules $node $mod
658 
659 
660  set references [dict get $mod references]
661  foreach child $references {
662  _dfs_visit hier_meta $child sorted bad_nodes
663  }
664 
665  set mod [dict get $hier_meta all_modules $node]
666  dict set mod color "black"
667  dict set hier_meta all_modules $node $mod
668  lappend sorted $node
669  }
670 
671 
672  set sorted [list]
673  set bad_nodes [list]
674 
675  _dfs_visit hier_meta $top_module sorted bad_nodes
676 
677  if {[llength $bad_nodes] > 0} {
678  return [dict create success 0 sorted {} cycles 1 bad_nodes $bad_nodes]
679  }
680 
681  return [dict create success 1 sorted $sorted cycles 0 bad_nodes {}]
682 }
683 
684 
685 proc _debug_string_hier_meta {hier_meta_ref {indent 0}} {
686  upvar 1 $hier_meta_ref hier_meta
687 
688  set ind [string repeat " " $indent]
689  set s "${ind}=== ALL MODULES ==="
690  dict for {key mod} [dict get $hier_meta all_modules] {
691  append s "\n${ind}$key:"
692  dict for {field value} $mod {
693  append s "\n${ind} $field: $value"
694  }
695  append s "\n"
696  }
697 
698  append s "\n${ind}=== PROJECT FILES ==="
699  dict for {file finfo} [dict get $hier_meta proj_files] {
700  append s "\n${ind}$file:"
701  dict for {field value} $finfo {
702  append s "\n${ind} $field: $value"
703  }
704  append s "\n"
705  }
706  return $s
707 }
708 
709 
710 
711 proc Hierarchy {listProperties listLibraries repo_path {output_path ""} \
712 {compile_order 0} {light ""} {top_module_override ""} {ignore_opt_list ""} {include_ieee 0} {include_gen_prods 0} {quiet 0}} {
713  set hier_meta [_create_hier_meta]
714 
715  set top_module ""
716 
717  set ignore_list [list]
718 
719  if {$include_ieee == 0} {
720  lappend ignore_list "ieee.*.*"
721  lappend ignore_list "std.*.*"
722  }
723 
724  foreach pat [split $ignore_opt_list ","] {
725  set pat [string trim $pat]
726  if {$pat ne ""} {
727  if {![regexp {^[\w*]+\.[\w*]+\.[\w*]+$} $pat]} {
728  Msg Warning "Warning: ignore pattern '$pat' does not match expected format <lib>.<type>.<name> (wildcards * allowed), ignoring"
729  } else {
730  lappend ignore_list $pat
731  }
732  }
733  }
734 
735  if {$top_module_override ne ""} {
736  set top_module $top_module_override
737  Msg Warning "Using specified top module: $top_module"
738  }
739 
740  dict for {lib files} $listLibraries {
741  set lib [file rootname $lib]
742 
743  foreach f $files {
744  set props ""
745  if {[dict exists $listProperties $f]} {
746 
747  set fprops [dict get $listProperties $f]
748  if {$top_module eq ""} {
749  set top [lindex [regexp -inline {\ytop\s*=\s*(.+?)\y.*} $fprops] 1]
750  if {$top != ""} {
751  set ext [file extension $f]
752  if {$ext eq ".vhd" || $ext eq ".vhdl"} {
753  set top_module "${lib}.vhdl_entity.${top}"
754  } elseif { $ext eq ".v" || $ext eq ".sv"} {
755  set top_module "${lib}.verilog_module.${top}"
756  } else {
757  set top_module "${lib}.component.${top}"
758  }
759  }
760  }
761 
762  set props $fprops
763  }
764  dict set hier_meta proj_files $f [_create_proj_file_info $f $lib $props]
765  }
766  }
767 
768  if {$top_module_override eq ""} {
769  Msg Info "Top module from properties: $top_module"
770  }
771 
772  set t_parse [time {
773  dict for {file file_info} [dict get $hier_meta proj_files] {
774  _hier_parse_file hier_meta $file_info $include_gen_prods
775  }
776  } 1]
777  set parse_us [lindex $t_parse 0]
778  set parse_ms [expr {$parse_us / 1000.0}]
779 
780  Msg Info "Completed initial parsing in $parse_ms ms"
781 
782  set t_resolve [time {
783  set resolve_result [_reference_resolver hier_meta]
784  } 1]
785  set resolve_us [lindex $t_resolve 0]
786  set resolve_ms [expr {$resolve_us / 1000.0}]
787 
788  set total [dict get $resolve_result total]
789  set resolutions [dict get $resolve_result resolutions]
790  Msg Info "Completed reference resolution: $total references resolved in $resolve_ms ms"
791 
792 
793  if {$output_path != ""} {
794  set output_file [open $repo_path/$output_path "w"]
795  } else {
796  set output_file ""
797  puts ""
798  }
799 
800 
801  set sorted_modules [dfs_sort hier_meta $top_module]
802  set bad_nodes [dict get $sorted_modules bad_nodes]
803 
804  #Msg Debug "[_debug_string_hier_meta hier_meta]"
805  set p [dict create]
806  if {$compile_order} {
807  set compile_order_dict [print_compile_order hier_meta [dict get $sorted_modules sorted] $output_file $quiet]
808  if {$output_path != ""} {
809  close $output_file
810  }
811  return $compile_order_dict
812  } else {
813  set p [print_hierarchy hier_meta $top_module $output_file $ignore_list $bad_nodes $light]
814  if {[llength $p] != 0} { PrintOrWrite $output_file "\n\n=====Packages in project:=====" }
815  dict for {lib pkg_list} $p {
816  if {[llength $pkg_list] == 0} {
817  continue
818  }
819  PrintOrWrite $output_file "Library: $lib"
820  foreach pkg_entry $pkg_list {
821  PrintOrWrite $output_file " Package: $pkg_entry"
822  }
823  }
824  }
825 
826  if {$output_path != ""} {
827  close $output_file
828  }
829 }
830 
831 proc print_compile_order {hier_meta_ref sorted_list {output_file ""} {quiet 0}} {
832  upvar 1 $hier_meta_ref hier_meta
833 
834  # Build an ordered flat list {file_path library ...}, deduplicating by
835  # {file_path, library} pair so the same file compiled into two libraries
836  # both appear.
837  # Pass 1: DFS-reachable modules in topological (dependency-first) order.
838  # Pass 2: remaining project files not reachable from the top module (e.g.
839  # packages, header files, orphaned modules) so every listed file
840  # gets compiled.
841  set result [list]
842  set seen [dict create]
843 
844  foreach mod_key $sorted_list {
845  set mod [dict get $hier_meta all_modules $mod_key]
846  set file_path [dict get $mod file_path]
847  set library [dict get $mod library]
848  set pair "${file_path}\t${library}"
849 
850  if {$file_path eq "" || [dict exists $seen $pair]} {
851  continue
852  }
853  dict set seen $pair 1
854  lappend result $file_path $library
855  }
856 
857  dict for {file file_info} [dict get $hier_meta proj_files] {
858  set library [file_info_library $file_info]
859  set pair "${file}\t${library}"
860  if {[dict exists $seen $pair]} {
861  continue
862  }
863  dict set seen $pair 1
864  lappend result $file $library
865  }
866 
867  if {$quiet == 0} {
868  foreach {file lib} $result {
869  PrintOrWrite $output_file "$file $lib"
870  }
871  }
872 
873  return $result
874 }
875 
876 
877 proc print_hierarchy {hier_meta_ref module {output_file ""} {ignore_list ""} \
878 {bad_nodes ""} {light 0} {indent 0} {stack_ref ""} {last_properties_ref ""} {is_last 1}} {
879  upvar 1 $hier_meta_ref hier_meta
880 
881  set package_dict {}
882 
883  if {[is_ignored_module $module $ignore_list]} {
884  return
885  }
886 
887  if {$stack_ref eq ""} {
888  set stack [list]
889  set last_properties [list]
890  } else {
891  upvar 1 $stack_ref stack
892  upvar 1 $last_properties_ref last_properties
893  }
894 
895 
896  if {![dict exists $hier_meta all_modules $module]} {
897  set parts [split $module "."]
898  set lib [lindex $parts 0]
899  set type [lindex $parts 1]
900  set name [lindex $parts 2]
901  set file_path ""
902  set module_exists 0
903  } else {
904 
905  set mod [dict get $hier_meta all_modules $module]
906  # puts $mod
907  set name [dict get $mod name]
908  set type [dict get $mod type]
909  set lib [dict get $mod library]
910  set file_path [dict get $mod file_path]
911  set module_exists 1
912 
913 
914  # for vhdl entities with 1 architecture in the same file, just use that architecture
915  if {$type eq "vhdl_entity" && $module_exists} {
916  set references [dict get $mod references]
917  set arch_refs [list]
918  foreach ref $references {
919  if {[string match "${lib}.vhdl_architecture.${name}.*" $ref]} {
920  lappend arch_refs $ref
921  }
922  }
923  if {[llength $arch_refs] == 1} {
924  set arch_key [lindex $arch_refs 0]
925  if {[dict exists $hier_meta all_modules $arch_key]} {
926  set arch_mod [dict get $hier_meta all_modules $arch_key]
927  set arch_file_path [dict get $arch_mod file_path]
928  if {$arch_file_path eq $file_path} {
929  set p [print_hierarchy hier_meta $arch_key $output_file $ignore_list $bad_nodes $light $indent stack last_properties $is_last]
930  set package_dict [MergeDict $p $package_dict]
931  return $package_dict
932  }
933  }
934  }
935  }
936  }
937 
938  set is_circular 0
939  if {[lsearch -exact $stack $module] != -1} {
940  if {[lsearch -exact $bad_nodes $module] != -1} {
941  set is_circular 1
942  }
943  }
944 
945  if {!$is_circular} {
946  lappend stack $module
947  }
948 
949  set indent_str ""
950  for {set i 0} {$i < [llength $last_properties]} {incr i} {
951  if {[lindex $last_properties $i]} {
952  append indent_str " "
953  } else {
954  append indent_str "│ "
955  }
956  }
957 
958  if {$indent > 0} {
959  if {$is_last} {
960  set connector "└─ "
961  } else {
962  set connector "├─ "
963  }
964  } else {
965  set connector ""
966  }
967 
968  if {$light} {
969  set path_str ""
970  } else {
971  set path_str " - ${file_path}"
972  }
973 
974  if {$type == "vhdl_architecture"} {
975  set name "[dict get $mod entity].$name"
976  }
977 
978 
979  if {[string first "vhdl_package" $type] == -1} {
980  if {!$module_exists} {
981  set msg "${indent_str}${connector}${lib}.${name} (${type})"
982  } elseif {$is_circular} {
983  set msg "${indent_str}${connector}${lib}.${name} (${type})${path_str} \[WARNING: circular reference detected\]"
984  } else {
985  set msg "${indent_str}${connector}${lib}.${name} (${type})${path_str}"
986  }
987  } else {
988  if {[DictGet $package_dict "$lib"] == ""} {
989  dict set package_dict "$lib" [list]
990  }
991  set package_list [DictGet $package_dict "$lib"]
992  if {![IsInList $package_list "${name} ${path_str}"] } {
993  lappend package_list "${name} ${path_str}"
994  dict set package_dict "$lib" $package_list
995  }
996  return $package_dict
997  }
998 
999  PrintOrWrite $output_file $msg
1000 
1001  if {$is_circular || !$module_exists} {
1002  return $package_dict
1003  }
1004 
1005  set references [dict get $mod references]
1006  set all_subs [lsort -unique $references]
1007 
1008  # Filter out ignored modules before processing
1009  set filtered_subs [list]
1010  foreach sub $all_subs {
1011  if {![is_ignored_module $sub $ignore_list]} {
1012  lappend filtered_subs $sub
1013  }
1014  }
1015 
1016  set num_subs [llength $filtered_subs]
1017  set sub_idx 0
1018  foreach sub $filtered_subs {
1019  incr sub_idx
1020  set is_last_child [expr {$sub_idx == $num_subs}]
1021 
1022  lappend last_properties $is_last
1023  set p [print_hierarchy hier_meta $sub $output_file $ignore_list $bad_nodes $light [expr {$indent + 1}] stack last_properties $is_last_child]
1024  set package_dict [MergeDict $p $package_dict]
1025  set last_properties [lrange $last_properties 0 end-1]
1026  }
1027 
1028  set stack [lrange $stack 0 end-1]
1029  return $package_dict
1030 }
1031 
1032 
1033 
1034 proc get_rtl_refs {node {name ""}} {
1035  set out [dict create]
1036 
1037  if {$name ne "" && ![catch {dict get $node reference_info} refinfo]} {
1038  set rt ""; set rn ""
1039  catch {set rt [dict get $refinfo ref_type]}
1040  catch {set rn [dict get $refinfo ref_name]}
1041  if {[string equal $rt "hdl"] && $rn ne ""} {
1042  dict set out $name $rn
1043  }
1044  }
1045 
1046  if {![catch {dict get $node components} comps]} {
1047  dict for {cname cnode} $comps {
1048  set childMap [get_rtl_refs $cnode $cname]
1049  set out [dict merge $out $childMap]
1050  }
1051  }
1052 
1053  dict for {k v} $node {
1054  if {$k eq "components" || $k eq "reference_info"} {continue}
1055  if {[catch {dict size $v}]} {continue}
1056  set childMap [get_rtl_refs $v $k]
1057  set out [dict merge $out $childMap]
1058  }
1059 
1060  return $out
1061 }
1062 
1063 proc _hier_parse_bd {hier_meta_ref file_info} {
1064  upvar 1 $hier_meta_ref hier_meta
1065 
1066  set f [file_info_path $file_info]
1067 
1068  if {![file exists $f]} { return }
1069 
1070  set library [file_info_library $file_info]
1071  set file_properties [file_info_properties $file_info]
1072 
1073  set name [file rootname [file tail $f]]
1074  set mod_properties [dict create]
1075  dict set mod_properties filetype "BD"
1076 
1077  set bd_file [open $f r]
1078  set bd_json [read $bd_file]
1079  close $bd_file
1080  set bd_design $bd_json
1081 
1082  set lines [split $bd_design "\n"]
1083  set filtered_lines {}
1084  foreach line $lines {
1085  if {[string first "\\" $line] == -1} {
1086  lappend filtered_lines $line
1087  }
1088  }
1089  set bd_design [join $filtered_lines "\n"]
1090 
1091  regsub -all {":\s*\{} $bd_design " \{" bd_design
1092  regsub -all {:\s*("(?:[^"\\]|\\.)*")} $bd_design { {\1}} bd_design
1093  regsub -all {"} $bd_design {} bd_design
1094  regsub -all {,} $bd_design {} bd_design
1095  regsub -all {:\s* \{} $bd_design {\{} bd_design
1096  regsub -all {\[} $bd_design "\{" bd_design
1097  regsub -all {\]} $bd_design "\}" bd_design
1098 
1099  set bd_design [string range $bd_design 1 end-1]
1100 
1101  if {[catch {dict size $bd_design} err]} {
1102  Msg Warning "Warning: malformed bd_design in $f, skipping"
1103  return {}
1104  }
1105 
1106  set bd_design [lindex $bd_design 1]
1107 
1108  set unknown_modules {}
1109  dict for {m v} [get_rtl_refs $bd_design] {
1110  if {[lsearch -exact $unknown_modules $v] == -1} {
1111  lappend unknown_modules "unknown.component.$v"
1112  }
1113  }
1114  _store_module hier_meta $name $library component $f $unknown_modules $mod_properties
1115 
1116 }