Hog v10.38.0
hog.tcl
Go to the documentation of this file.
1 # Copyright 2018-2026 The University of Birmingham
2 # Copyright 2018-2026 Max-Planck-Institute for Physics
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 
16 ## @file hog.tcl
17 
18 if {![info exists tcl_path]} {
19  set tcl_path [file normalize "[file dirname [info script]]"]
20 }
21 
22 set hog_path [file normalize "[file dirname [info script]]"]
23 
24 source "$hog_path/utils/Logger.tcl"
25 
26 
27 #### GLOBAL CONSTANTS
28 set CI_STAGES {"generate_project" "simulate_project"}
29 set CI_PROPS {"-synth_only"}
30 
31 #### FUNCTIONS
32 
33 ## @brief Add a new file to a fileset in Vivado
34 #
35 # @param[in] file The name of the file to add. NOTE: directories are not supported.
36 # @param[in] fileset The fileset name
37 #
38 proc AddFile {file fileset} {
39  if {[IsXilinx]} {
40  add_files -norecurse -fileset $fileset $file
41  }
42 }
43 
44 ## @brief Add libraries, properties and filesets to the project
45 #
46 # @param[in] libraries has library name as keys and a list of filenames as values
47 # @param[in] properties has as file names as keys and a list of properties as values
48 # @param[in] filesets has fileset name as keys and a list of libraries as values
49 #
50 proc AddHogFiles {libraries properties filesets} {
51  Msg Info "Adding source files to project..."
52  Msg Debug "Filesets: $filesets"
53  Msg Debug "Libraries: $libraries"
54  Msg Debug "Properties: $properties"
55 
56  if {[IsLibero]} {
57  set synth_conf_command "organize_tool_files -tool {SYNTHESIZE} -input_type {constraint}"
58  set synth_conf 0
59  set timing_conf_command "organize_tool_files -tool {VERIFYTIMING} -input_type {constraint}"
60  set timing_conf 0
61  set place_conf_command "organize_tool_files -tool {PLACEROUTE} -input_type {constraint}"
62  set place_conf 0
63  }
64 
65  foreach fileset [dict keys $filesets] {
66  Msg Debug "Fileset: $fileset"
67  # Create fileset if it doesn't exist yet
68  if {[IsVivado]} {
69  if {[string equal [get_filesets -quiet $fileset] ""]} {
70  # Simulation list files supported only by Vivado
71  create_fileset -simset $fileset
72  # Set active when creating, by default it will be the latest simset to be created,
73  # unless is specified in the sim.conf
74  current_fileset -simset [get_filesets $fileset]
75  set simulation [get_filesets $fileset]
76  foreach simulator [GetSimulators] {
77  set_property -name {$simulator.compile.vhdl_syntax} -value {2008} -objects $simulation
78  }
79  set_property SOURCE_SET sources_1 $simulation
80  }
81  }
82  # Check if ips.src is in $fileset
83  set libs_in_fileset [DictGet $filesets $fileset]
84  if {[IsInList "ips.src" $libs_in_fileset]} {
85  set libs_in_fileset [MoveElementToEnd $libs_in_fileset "ips.src"]
86  }
87 
88  # Vitis: Check if defined apps have a corresponding source file
89  if {[IsVitisClassic] || [IsVitisUnified]} {
90  # Get the workspace apps
91  if {[IsVitisClassic]} {
92  # TODO: "app list -dict" return wrong configuration parameters for Vitis Classic versions older than 2022.1
93  if {[catch {set ws_apps [app list -dict]}]} {set ws_apps ""}
94  } elseif {[IsVitisUnified]} {
95  # Get app list from Vitis Unified workspace using Python script
96  set vitis_workspace "$globalSettings::build_dir/vitis_unified"
97  set python_script "$globalSettings::repo_path/Hog/Other/Python/VitisUnified/AppCommands.py"
98  set json_output ""
99  if {![ExecuteVitisUnifiedCommand $python_script "app_list" [list $vitis_workspace] "Failed to get app list from Vitis Unified" json_output]} {
100  Msg Warning "Failed to get app list from Vitis Unified"
101  set ws_apps ""
102  } else {
103  if {[catch {package require json}]} {
104  Msg Warning "JSON package not available for parsing Vitis Unified app list"
105  set ws_apps ""
106  } else {
107  set json_output_filtered ""
108  if {[regexp -lineanchor {\{.*\}} $json_output json_output_filtered]} {
109  set ws_apps [json::json2dict $json_output_filtered]
110  } else {
111  set ws_apps [json::json2dict $json_output]
112  }
113  }
114  }
115  }
116 
117  # Check if each app has a corresponding source file
118  if {$ws_apps ne ""} {
119  dict for {app_name app_config} $ws_apps {
120  set app_lib [string tolower "app_$app_name\.src"]
121  if {![IsInList $app_lib $libs_in_fileset 0 1]} {
122  Msg Warning "App '$app_name' exists in workspace but no corresponding sourcefile '$app_lib' found. \
123  Make sure you have a list file with the correct naming convention: \[app_<app_name>\.src\]"
124  }
125  }
126  }
127  }
128 
129  # For Vitis Unified, create app_files_dict once before processing libraries
130  # This allows us to collect files from all libraries before importing
131  if {[IsVitisUnified]} {
132  set app_files_dict [dict create]
133  }
134 
135  # Loop over libraries in fileset
136  set reapply_targets [dict create]
137  foreach lib $libs_in_fileset {
138  Msg Debug "lib: $lib \n"
139  set lib_files [DictGet $libraries $lib]
140  Msg Debug "Files in $lib: $lib_files"
141  set rootlib [file rootname [file tail $lib]]
142  set ext [file extension $lib]
143  Msg Debug "lib: $lib ext: $ext fileset: $fileset"
144  # ADD NOW LISTS TO VIVADO PROJECT
145  if {[IsXilinx] && !([info exists globalSettings::vitis_only_pass] && $globalSettings::vitis_only_pass == 1)} {
146  # Skip Vitis application libraries
147  if {[string match "app_*" [string tolower $lib]]} {
148  continue
149  }
150  Msg Debug "Adding $lib to $fileset"
151  add_files -norecurse -fileset $fileset $lib_files
152  # Add Properties
153  foreach f $lib_files {
154  set file_obj [get_files -of_objects [get_filesets $fileset] [list "*$f"]]
155  #ADDING LIBRARY
156  if {[file extension $f] == ".vhd" || [file extension $f] == ".vhdl"} {
157  set_property -name "library" -value $rootlib -objects $file_obj
158  }
159 
160  # ADDING FILE PROPERTIES
161  set props [DictGet $properties $f]
162  if {[file extension $f] == ".vhd" || [file extension $f] == ".vhdl"} {
163  # VHDL 93 property
164  if {[lsearch -inline -regexp $props "93"] < 0} {
165  # ISE does not support vhdl2008
166  if {[IsVivado]} {
167  if {[lsearch -inline -regexp $props "2008"] >= 0} {
168  set vhdl_year "VHDL 2008"
169  } elseif {[lsearch -inline -regexp $props "2019"] >= 0} {
170  if {[GetIDEVersion] >= 2023.2} {
171  set vhdl_year "VHDL 2019"
172  } else {
173  Msg CriticalWarning "VHDL 2019 is not supported\
174  in Vivado version older than 2023.2.\
175  Using VHDL 2008, but this might not work."
176  set vhdl_year "VHDL 2008"
177  }
178  } else {
179  # The default VHDL year is 2008
180  set vhdl_year "VHDL 2008"
181  }
182  Msg Debug "File type for $f is $vhdl_year"
183  set_property -name "file_type" -value $vhdl_year -objects $file_obj
184  }
185  } else {
186  Msg Debug "Filetype is VHDL 93 for $f"
187  }
188  }
189 
190  # SystemVerilog property
191  if {[lsearch -inline -regexp $props "SystemVerilog"] > 0} {
192  # ISE does not support SystemVerilog
193  if {[IsVivado]} {
194  set_property -name "file_type" -value "SystemVerilog" -objects $file_obj
195  Msg Debug "Filetype is SystemVerilog for $f"
196  } else {
197  Msg Warning "Xilinx PlanAhead/ISE does not support SystemVerilog.\
198  Property not set for $f"
199  }
200  }
201 
202  # Top synthesis module
203  set top [lindex [regexp -inline {\ytop\s*=\s*(.+?)\y.*} $props] 1]
204  if {$top != ""} {
205  Msg Info "Setting $top as top module for file set $fileset..."
206  set globalSettings::synth_top_module $top
207  }
208 
209  # Verilog headers
210  if {[lsearch -inline -regexp $props "verilog_header"] >= 0} {
211  Msg Debug "Setting verilog header type for $f..."
212  set_property file_type {Verilog Header} [get_files $f]
213  } elseif {[lsearch -inline -regexp $props "verilog_template"] >= 0} {
214  # Verilog Template
215  Msg Debug "Setting verilog template type for $f..."
216  set_property file_type {Verilog Template} [get_files $f]
217  } elseif {[lsearch -inline -regexp $props "verilog"] >= 0} {
218  # Normal Verilog
219  Msg Debug "Setting verilog type for $f..."
220  set_property file_type {Verilog} [get_files $f]
221  }
222 
223  # Not used in synthesis
224  if {[lsearch -inline -regexp $props "nosynth"] >= 0} {
225  Msg Debug "Setting not used in synthesis for $f..."
226  set_property -name "used_in_synthesis" -value "false" -objects $file_obj
227  }
228 
229  # Not used in implementation
230  if {[lsearch -inline -regexp $props "noimpl"] >= 0} {
231  Msg Debug "Setting not used in implementation for $f..."
232  set_property -name "used_in_implementation" -value "false" -objects $file_obj
233  }
234 
235  # Not used in simulation
236  if {[lsearch -inline -regexp $props "nosim"] >= 0} {
237  Msg Debug "Setting not used in simulation for $f..."
238  set_property -name "used_in_simulation" -value "false" -objects $file_obj
239  }
240 
241  ## Simulation properties
242  # Top simulation module
243  set top_sim [lindex [regexp -inline {\ytopsim\s*=\s*(.+?)\y.*} $props] 1]
244  if {$top_sim != ""} {
245  Msg Warning "Setting the simulation top module with the topsim property is deprecated.\
246  Please set this property in the \[properties\] section of your .sim list file,
247  or in the \[$fileset\] section of your sim.conf,\
248  by adding the following line.\ntop=$top_sim"
249  }
250 
251  # Simulation runtime
252  set sim_runtime [lindex [regexp -inline {\yruntime\s*=\s*(.+?)\y.*} $props] 1]
253  if {$sim_runtime != ""} {
254  Msg Warning "Setting the simulation runtime using the runtime= property is deprecated.\
255  Please set this property in the \[properties\] section of your .sim list file,\
256  or in the \[$fileset\] section of your sim.conf,\
257  by adding the following line.\n<simulator_name>.simulate.runtime=$sim_runtime"
258  }
259 
260  # Wave do file
261  if {[lsearch -inline -regexp $props "wavefile"] >= 0} {
262  Msg Warning "Setting a wave do file using the wavefile property is deprecated.\
263  Set this property in the sim.conf file under the \[$fileset\] section,\
264  or in the \[properties\] section of the .sim list file,\
265  by adding the following line .\n<simulator_name>.simulate.custom_wave_do=[file tail $f]"
266  }
267 
268  # Do file
269  if {[lsearch -inline -regexp $props "dofile"] >= 0} {
270  Msg Warning "Setting a wave do file using the dofile property is deprecated.\
271  Set this property in the sim.conf file under the \[$fileset\] section,\
272  or in the \[properties\] section of the .sim list file,\
273  by adding the following line .\n<simulator_name>.simulate.custom_do=[file tail $f]"
274  }
275 
276  # Lock the IP
277  if {[lsearch -inline -regexp $props "locked"] >= 0 && $ext == ".ip"} {
278  Msg Info "Locking IP $f..."
279  set_property IS_MANAGED 0 [get_files $f]
280  }
281 
282  # Generating Target for BD File
283  if {[file extension $f] == ".bd"} {
284  Msg Info "Generating Target for [file tail $f],\
285  please remember to commit the (possible) changed file."
286  generate_target all [get_files $f]
287 
288  # If elf is saved in the bd, vivado appends to the props instead of setting
289  dict for {fo pd} $reapply_targets {
290  set ref [dict get $pd ref]
291  set cell [dict get $pd cell]
292  Msg Debug "Reapplying ref: $ref and cell:$cell to $fo"
293  set_property SCOPED_TO_REF $ref $fo
294  set_property SCOPED_TO_CELLS [split $cell ","] $fo
295  }
296  }
297 
298  # Tcl
299  if {[file extension $f] == ".tcl" && $ext != ".con"} {
300  if {[lsearch -inline -regexp $props "source"] >= 0} {
301  Msg Info "Sourcing Tcl script $f,\
302  and setting it not used in synthesis, implementation and simulation..."
303  source $f
304  set_property -name "used_in_synthesis" -value "false" -objects $file_obj
305  set_property -name "used_in_implementation" -value "false" -objects $file_obj
306  set_property -name "used_in_simulation" -value "false" -objects $file_obj
307  }
308  }
309 
310  # Constraint Properties
311  set ref [lindex [regexp -inline {\yscoped_to_ref\s*=\s*([^ ]+)} $props] 1]
312  set cell [lindex [regexp -inline {\yscoped_to_cells\s*=\s*([^ ]+)} $props] 1]
313  if {[file extension $f] == ".elf" || (([file extension $f] == ".tcl" || [file extension $f] == ".xdc") && $ext == ".con")} {
314  if {[file extension $f] == ".elf"} {dict set reapply_targets $file_obj [dict create ref $ref cell $cell]}
315  if {$ref != ""} {
316  set_property SCOPED_TO_REF $ref $file_obj
317  }
318  if {$cell != ""} {
319  set_property SCOPED_TO_CELLS [split $cell ","] $file_obj
320  }
321  }
322  }
323  Msg Info "[llength $lib_files] file/s added to library $rootlib..."
324  } elseif {[IsQuartus]} {
325  #QUARTUS ONLY
326  if {$ext == ".sim"} {
327  Msg Warning "Simulation files not supported in Quartus Prime mode... Skipping $lib"
328  } else {
329  if {![is_project_open]} {
330  Msg Error "Project is closed"
331  }
332  foreach cur_file $lib_files {
333  set file_type [FindFileType $cur_file]
334 
335  #ADDING FILE PROPERTIES
336  set props [DictGet $properties $cur_file]
337 
338  # Top synthesis module
339  set top [lindex [regexp -inline {\ytop\s*=\s*(.+?)\y.*} $props] 1]
340  if {$top != ""} {
341  Msg Info "Setting $top as top module for file set $fileset..."
342  set globalSettings::synth_top_module $top
343  }
344  # VHDL file properties
345  if {[string first "VHDL" $file_type] != -1} {
346  if {[string first "1987" $props] != -1} {
347  set hdl_version "VHDL_1987"
348  } elseif {[string first "1993" $props] != -1} {
349  set hdl_version "VHDL_1993"
350  } elseif {[string first "2008" $props] != -1} {
351  set hdl_version "VHDL_2008"
352  } else {
353  set hdl_version "default"
354  }
355  if {$hdl_version == "default"} {
356  set_global_assignment -name $file_type $cur_file -library $rootlib
357  } else {
358  set_global_assignment -name $file_type $cur_file -hdl_version $hdl_version -library $rootlib
359  }
360  } elseif {[string first "SYSTEMVERILOG" $file_type] != -1} {
361  # SystemVerilog file properties
362  if {[string first "2005" $props] != -1} {
363  set hdl_version "systemverilog_2005"
364  } elseif {[string first "2009" $props] != -1} {
365  set hdl_version "systemverilog_2009"
366  } else {
367  set hdl_version "default"
368  }
369  if {$hdl_version == "default"} {
370  set_global_assignment -name $file_type $cur_file
371  } else {
372  set_global_assignment -name $file_type $cur_file -hdl_version $hdl_version
373  }
374  } elseif {[string first "VERILOG" $file_type] != -1} {
375  # Verilog file properties
376  if {[string first "1995" $props] != -1} {
377  set hdl_version "verilog_1995"
378  } elseif {[string first "2001" $props] != -1} {
379  set hdl_version "verilog_2001"
380  } else {
381  set hdl_version "default"
382  }
383  if {$hdl_version == "default"} {
384  set_global_assignment -name $file_type $cur_file
385  } else {
386  set_global_assignment -name $file_type $cur_file -hdl_version $hdl_version
387  }
388  } elseif {[string first "SOURCE" $file_type] != -1 || [string first "COMMAND_MACRO" $file_type] != -1} {
389  set_global_assignment -name $file_type $cur_file
390  if {$ext == ".con"} {
391  source $cur_file
392  } elseif {$ext == ".src"} {
393  # If this is a Platform Designer file then generate the system
394  if {[string first "qsys" $props] != -1} {
395  # remove qsys from options since we used it
396  set emptyString ""
397  regsub -all {\{||qsys||\}} $props $emptyString props
398 
399  set qsysPath [file dirname $cur_file]
400  set qsysName "[file rootname [file tail $cur_file]].qsys"
401  set qsysFile "$qsysPath/$qsysName"
402  set qsysLogFile "$qsysPath/[file rootname [file tail $cur_file]].qsys-script.log"
403 
404  set qsys_rootdir ""
405  if {![info exists ::env(QSYS_ROOTDIR)]} {
406  if {[info exists ::env(QUARTUS_ROOTDIR)]} {
407  set qsys_rootdir "$::env(QUARTUS_ROOTDIR)/sopc_builder/bin"
408  Msg Warning "The QSYS_ROOTDIR environment variable is not set! I will use $qsys_rootdir"
409  } else {
410  Msg CriticalWarning "The QUARTUS_ROOTDIR environment variable is not set! Assuming all quartus executables are contained in your PATH!"
411  }
412  } else {
413  set qsys_rootdir $::env(QSYS_ROOTDIR)
414  }
415 
416  set cmd "$qsys_rootdir/qsys-script"
417  set cmd_options " --script=$cur_file"
418  if {![catch {"exec $cmd -version"}] || [lindex $::errorCode 0] eq "NONE"} {
419  Msg Info "Executing: $cmd $cmd_options"
420  Msg Info "Saving logfile in: $qsysLogFile"
421  if {[catch {eval exec -ignorestderr "$cmd $cmd_options >>& $qsysLogFile"} ret opt]} {
422  set makeRet [lindex [dict get $opt -errorcode] end]
423  Msg CriticalWarning "$cmd returned with $makeRet"
424  }
425  } else {
426  Msg Error " Could not execute command $cmd"
427  exit 1
428  }
429  # Check the system is generated correctly and move file to correct directory
430  if {[file exists $qsysName] != 0} {
431  file rename -force $qsysName $qsysFile
432  # Write checksum to file
433  set qsysMd5Sum [Md5Sum $qsysFile]
434  # open file for writing
435  set fileDir [file normalize "./hogTmp"]
436  set fileName "$fileDir/.hogQsys.md5"
437  if {![file exists $fileDir]} {
438  file mkdir $fileDir
439  }
440  set hogQsysFile [open $fileName "a"]
441  set fileEntry "$qsysFile\t$qsysMd5Sum"
442  puts $hogQsysFile $fileEntry
443  close $hogQsysFile
444  } else {
445  Msg ERROR "Error while moving the generated qsys file to final location: $qsysName not found!"
446  }
447  if {[file exists $qsysFile] != 0} {
448  if {[string first "noadd" $props] == -1} {
449  set qsysFileType [FindFileType $qsysFile]
450  set_global_assignment -name $qsysFileType $qsysFile
451  } else {
452  regsub -all {noadd} $props $emptyString props
453  }
454  if {[string first "nogenerate" $props] == -1} {
455  GenerateQsysSystem $qsysFile $props
456  }
457  } else {
458  Msg ERROR "Error while generating ip variations from qsys: $qsysFile not found!"
459  }
460  }
461  }
462  } elseif {[string first "QSYS" $file_type] != -1} {
463  set emptyString ""
464  regsub -all {\{||\}} $props $emptyString props
465  if {[string first "noadd" $props] == -1} {
466  set_global_assignment -name $file_type $cur_file
467  } else {
468  regsub -all {noadd} $props $emptyString props
469  }
470 
471  #Generate IPs
472  if {[string first "nogenerate" $props] == -1} {
473  GenerateQsysSystem $cur_file $props
474  }
475  } else {
476  set_global_assignment -name $file_type $cur_file -library $rootlib
477  }
478  }
479  }
480  } elseif {[IsLibero]} {
481  if {$ext == ".con"} {
482  set vld_exts {.sdc .pin .dcf .gcf .pdc .ndc .fdc .crt .vcd }
483  foreach con_file $lib_files {
484  # Check for valid constrain files
485  set con_ext [file extension $con_file]
486  if {[IsInList [file extension $con_file] $vld_exts]} {
487  set option [string map {. -} $con_ext]
488  set option [string map {fdc net_fdc} $option]
489  set option [string map {pdc io_pdc} $option]
490  create_links -convert_EDN_to_HDL 0 -library {work} $option $con_file
491 
492  set props [DictGet $properties $con_file]
493 
494  if {$con_ext == ".sdc"} {
495  if {[lsearch $props "notiming"] >= 0} {
496  Msg Info "Excluding $con_file from timing verification..."
497  } else {
498  Msg Info "Adding $con_file to time verification"
499  append timing_conf_command " -file $con_file"
500  set timing_conf 1
501  }
502 
503  if {[lsearch $props "nosynth"] >= 0} {
504  Msg Info "Excluding $con_file from synthesis..."
505  } else {
506  Msg Info "Adding $con_file to synthesis"
507  append synth_conf_command " -file $con_file"
508  set synth_conf 1
509  }
510  }
511 
512  if {$con_ext == ".pdc" || $con_ext == ".sdc"} {
513  if {[lsearch $props "noplace"] >= 0} {
514  Msg Info "Excluding $con_file from place and route..."
515  } else {
516  Msg Info "Adding $con_file to place and route"
517  append place_conf_command " -file $con_file"
518  set place_conf 1
519  }
520  }
521  } else {
522  Msg CriticalWarning "Constraint file $con_file does not have a valid extension. Allowed extensions are: \n $vld_exts"
523  }
524  }
525  } elseif {$ext == ".src"} {
526  foreach f $lib_files {
527  Msg Debug "Adding source $f to library $rootlib..."
528  create_links -library $rootlib -hdl_source $f
529  }
530  } elseif {$ext == ".sim"} {
531  Msg Debug "Adding stimulus file $f to library..."
532  create_links -library $rootlib -stimulus $f
533  }
534  build_design_hierarchy
535  foreach cur_file $lib_files {
536  set file_type [FindFileType $cur_file]
537 
538  # ADDING FILE PROPERTIES
539  set props [DictGet $properties $cur_file]
540 
541  # Top synthesis module
542  set top [lindex [regexp -inline {\ytop\s*=\s*(.+?)\y.*} $props] 1]
543  if {$top != ""} {
544  Msg Info "Setting $top as top module for file set $rootlib..."
545  set globalSettings::synth_top_module "${top}::$rootlib"
546  }
547  }
548  # Closing IDE if cascade
549  } elseif {[IsDiamond]} {
550  if {$ext == ".src" || $ext == ".con" || $ext == ".ext"} {
551  foreach f $lib_files {
552  Msg Debug "Diamond: adding source file $f to library $rootlib..."
553  prj_src add -work $rootlib $f
554  set props [DictGet $properties $f]
555  # Top synthesis module
556  set top [lindex [regexp -inline {\ytop\s*=\s*(.+?)\y.*} $props] 1]
557  if {$top != ""} {
558  Msg Info "Setting $top as top module for the project..."
559  set globalSettings::synth_top_module $top
560  }
561 
562  # Active LPF property
563  if {[lsearch -inline -regexp $props "enable"] >= 0} {
564  Msg Debug "Setting $f as active Logic Preference file"
565  prj_src enable $f
566  }
567  }
568  } elseif {$ext == ".sim"} {
569  foreach f $lib_files {
570  Msg Debug "Diamond Adding simulation file $f to library $rootlib..."
571  prj_src add -work $rootlib -simulate_only $f
572  }
573  }
574  } elseif {[IsVitisClassic] || [IsVitisUnified]} {
575  if {[IsVitisClassic]} {
576  # Vitis Classic: import files one by one
577  foreach app_name [dict keys $ws_apps] {
578  foreach f $lib_files {
579  if {[string tolower $rootlib] != [string tolower "app_$app_name"]} {
580  continue
581  }
582 
583  Msg Info "Adding source file $f from lib: $lib to vitis app \[$app_name\]..."
584  set proj_f_path [regsub "^$globalSettings::repo_path" $f ""]
585  set proj_f_path [regsub "[file tail $f]$" $proj_f_path ""]
586  Msg Debug "Project_f_path is $proj_f_path"
587 
588  importsources -name $app_name -soft-link -path $f -target $proj_f_path
589  }
590  }
591  } elseif {[IsVitisUnified]} {
592  Msg Debug "Vitis Unified: Collecting files for apps from library $rootlib..."
593  # For Vitis Unified, collect files per app from all libraries
594  # Files will be imported after all libraries are processed
595 
596  if {[dict size $ws_apps] == 0} {
597  Msg Debug "No apps found in workspace, skipping file collection"
598  } else {
599  Msg Debug "Found [dict size $ws_apps] app(s) in workspace"
600  Msg Debug "Processing library: $rootlib with [llength $lib_files] file(s)"
601  foreach app_name [dict keys $ws_apps] {
602  set expected_lib [string tolower "app_$app_name"]
603  Msg Debug "Checking files for app: $app_name (looking for lib: $expected_lib, current lib: [string tolower $rootlib])"
604  foreach f $lib_files {
605  if {[string tolower $rootlib] != $expected_lib} {
606  continue
607  }
608  Msg Debug "File $f matches app $app_name"
609  set proj_f_path [regsub "^$globalSettings::repo_path" $f ""]
610  set proj_f_path [regsub "[file tail $f]$" $proj_f_path ""]
611  set proj_f_path [string trimleft $proj_f_path "/"]
612  # Store file info for this app
613  if {![dict exists $app_files_dict $app_name]} {
614  dict set app_files_dict $app_name files [list]
615  dict set app_files_dict $app_name target $proj_f_path
616  Msg Debug "Initialized app_files_dict for $app_name with target: $proj_f_path"
617  }
618  # Get current files list, append new file, and set it back
619  set current_files [dict get $app_files_dict $app_name files]
620  lappend current_files $f
621  dict set app_files_dict $app_name files $current_files
622  set current_count [llength [dict get $app_files_dict $app_name files]]
623  Msg Debug "Added file $f to app_files_dict for $app_name, current count: $current_count"
624  }
625  }
626  }
627  }
628  }
629  }
630 
631  # For Vitis Unified, import all collected files after processing all libraries
632  if {[IsVitisUnified] && [dict size $app_files_dict] > 0} {
633  set python_script "$globalSettings::repo_path/Hog/Other/Python/VitisUnified/AppCommands.py"
634  set vitis_workspace "$globalSettings::build_dir/vitis_unified"
635 
636  # Get Vitis version and set as environment variable for Python script
637  set vitis_version [GetIDEVersion]
638  set env(HOG_VITIS_VER) $vitis_version
639  Msg Debug "Vitis version: $vitis_version (set in HOG_VITIS_VER environment variable)"
640 
641  dict for {app_name app_data} $app_files_dict {
642  set files_list [dict get $app_data files]
643  set target_path [dict get $app_data target]
644 
645  if {[llength $files_list] > 0} {
646  Msg Info "Adding [llength $files_list] source file(s) to vitis app \[$app_name\]..."
647  Msg Debug "Files: $files_list"
648  Msg Debug "Target path: $target_path"
649 
650  # Convert Tcl list to JSON array for Python
651  set files_json "\["
652  set first 1
653  foreach f $files_list {
654  if {!$first} {
655  append files_json ", "
656  }
657  # Escape backslashes and quotes in file paths
658  set escaped_f [string map {\\ \\\\ \" \\\"} $f]
659  append files_json "\"$escaped_f\""
660  set first 0
661  }
662  append files_json "\]"
663 
664  Msg Debug "JSON string: $files_json"
665 
666  set error_msg "Failed to add files to app $app_name"
667  if {
668  ![ExecuteVitisUnifiedCommand $python_script "add_app_files" \
669  [list $app_name $files_json $vitis_workspace $target_path] \
670  $error_msg]
671  } {
672  Msg Error "Failed to add files to Vitis Unified app '$app_name'"
673  exit 1
674  }
675  } else {
676  Msg Warning "No files to add for app '$app_name'"
677  }
678  }
679  }
680  }
681 
682  if {[IsVivado]} {
683  if {[DictGet $filesets "sim_1"] == ""} {
684  delete_fileset -quiet [get_filesets -quiet "sim_1"]
685  }
686  }
687 
688  # Add constraints to workflow in Libero
689  if {[IsLibero]} {
690  if {$synth_conf == 1} {
691  Msg Info $synth_conf_command
692  eval $synth_conf_command
693  }
694  if {$timing_conf == 1} {
695  Msg Info $timing_conf_command
696  eval $timing_conf_command
697  }
698  if {$place_conf == 1} {
699  Msg Info $place_conf_command
700  eval $place_conf_command
701  }
702  }
703 }
704 
705 ## @brief Returns a dictionary for the allowed properties for each file type
706 proc ALLOWED_PROPS {} {
707  return [dict create ".vhd" [list "93" "nosynth" "noimpl" "nosim" "1987" "1993" "2008" "2019"] \
708  ".vhdl" [list "93" "nosynth" "noimpl" "nosim" "1987" "1993" "2008" "2019"] \
709  ".bd" [list "nosim"] \
710  ".v" [list "SystemVerilog" "verilog_header" "nosynth" "noimpl" "nosim" "1995" "2001"] \
711  ".sv" [list "verilog" "verilog_header" "nosynth" "noimpl" "nosim" "2005" "2009"] \
712  ".svp" [list "verilog" "verilog_header" "nosynth" "noimpl" "nosim" "2005" "2009"] \
713  ".do" [list "nosim"] \
714  ".udo" [list "nosim"] \
715  ".xci" [list "nosynth" "noimpl" "nosim" "locked"] \
716  ".xdc" [list "nosynth" "noimpl" "scoped_to_ref" "scoped_to_cells"] \
717  ".tcl" [list "nosynth" "noimpl" "nosim" "scoped_to_ref" "scoped_to_cells" "source" "qsys" "noadd" \
718  "--block-symbol-file" "--clear-output-directory" "--example-design" \
719  "--export-qsys-script" "--family" "--greybox" "--ipxact" \
720  "--jvm-max-heap-size" "--parallel" "--part" "--search-path" \
721  "--simulation" "--synthesis" "--testbench" "--testbench-simulation" \
722  "--upgrade-ip-cores" "--upgrade-variation-file"] \
723  ".qsys" [list "nogenerate" "noadd" "--block-symbol-file" "--clear-output-directory" "--example-design" \
724  "--export-qsys-script" "--family" "--greybox" "--ipxact" "--jvm-max-heap-size" "--parallel" \
725  "--part" "--search-path" "--simulation" "--synthesis" "--testbench" "--testbench-simulation" \
726  "--upgrade-ip-cores" "--upgrade-variation-file"] \
727  ".sdc" [list "notiming" "nosynth" "noplace"] \
728  ".elf" [list "scoped_to_ref" "scoped_to_cells" "nosim" "noimpl"] \
729  ".pdc" [list "nosynth" "noplace"] \
730  ".lpf" [list "enable"]]
731 }
732 
733 ## @brief # Returns the step name for the stage that produces the binary file
734 #
735 # Projects using Versal chips have a different step for producing the
736 # binary file, we use this function to take that into account
737 #
738 # @param[out] 1 if it's Versal 0 if it's not
739 # @param[in] part The FPGA part
740 #
741 proc BinaryStepName {part} {
742  if {[IsVersal $part]} {
743  return "WRITE_DEVICE_IMAGE"
744  } elseif {[IsISE]} {
745  return "Bitgen"
746  } else {
747  return "WRITE_BITSTREAM"
748  }
749 }
750 
751 ## @brief Check common environmentatl variables to run the Hog-CI
752 proc CheckCIEnv {} {
753  global env
754  set essential_vars [dict create \
755  "HOG_USER" "NOT defined. This variable is essential for git to work properly. \
756  It should be set to the username for your service account (a valid git account)." \
757  "HOG_EMAIL" "NOT defined. This variable is essential for git to work properly. It should be set to your service's account email." \
758  "HOG_PUSH_TOKEN" "NOT defined. This variable is essential for git to work properly. \
759  It should be set to a Gitlab/GitHub API token for your service account."]
760 
761  set missing_vars 0
762  dict for {var msg} $essential_vars {
763  if {![info exists env($var)]} {
764  Msg CriticalWarning "Essential environment variable $var is $msg"
765  set missing_vars 1
766  } else {
767  Msg Info "Found environment variable $var."
768  }
769  }
770 
771  set additional_vars [dict create \
772  "HOG_CHECK_YAMLREF" "NOT defined. Set this variable to '1' to make CI fail if there is not coherence between the ref and the Hog." \
773  "HOG_TARGET_BRANCH" "NOT defined. Default branch for merge is \"master\"" \
774  "HOG_CREATE_OFFICIAL_RELEASE" "NOT defined. \
775  Set this variable to '1' to make Hog create an official release in GitHub/Gitlab with the binaries generated in the CI." \
776  "HOG_USE_DOXYGEN" "NOT defined. \
777  Set this variable to 1 to make Hog-CI run Doxygen and copy the official documentation over when you merge to the official branch."]
778 
779  if {
780  ([info exists env(HOG_OFFICIAL_BIN_EOS_PATH)] && $env(HOG_OFFICIAL_BIN_EOS_PATH) ne "") ||
781  ([info exists env(HOG_OFFICIAL_BIN_PATH)] && [string match "/eos/*" $env(HOG_OFFICIAL_BIN_PATH)])
782  } {
783  Msg Info "Official binary path points to EOS. Checking EOS environment variables for uploads..."
784  if {[info exists env(HOG_OFFICIAL_BIN_PATH)]} {
785  Msg CriticalWarning "Variable HOG_OFFICIAL_BIN_EOS_PATH is defined. \
786  From Hog2026.2 this variable will be deprecated. Please, use HOG_OFFICIAL_BIN_PATH instead."
787  }
788  if {![info exists env(EOS_PASSWORD)]} {
789  if {![info exists env(HOG_PASSWORD)]} {
790  Msg Warning "Neither EOS_PASSWORD nor HOG_PASSWORD environment variable is defined. \
791  This variable is essential for Hog to be able to upload files to EOS. Please set one of them to the password for your EOS account."
792  } else {
793  Msg Info "HOG_PASSWORD environment variable is defined and will be used as password for EOS uploads. \
794  If you want to use a different password for EOS uploads, please set the EOS_PASSWORD environment variable."
795  }
796  } else {
797  Msg Info "EOS_PASSWORD environment variable is defined and will be used as password for EOS uploads."
798  }
799 
800  if {![info exists env(EOS_USER)]} {
801  Msg Info "EOS_USER environment variable is not defined. Assuming EOS username is the same as HOG_USER."
802  } else {
803  Msg Info "EOS_USER environment variable is defined and will be used as username for EOS uploads."
804  }
805 
806  if {![info exists env(EOS_MGM_URL)]} {
807  Msg Info "EOS_MGM_URL environment variable is not defined. Assuming default value of root://eosuser.cern.ch."
808  } else {
809  Msg Info "EOS_MGM_URL environment variable is defined and will be used as MGM URL for EOS uploads."
810  }
811  } elseif {[info exists env(HOG_OFFICIAL_BIN_PATH)]} {
812  Msg Info "Variable HOG_OFFICIAL_BIN_PATH is defined. Hog will copy the official binary files to the path defined in this variable. \
813  Please make sure this path is correct and has enough space to store the binaries."
814  } else {
815  Msg Info "No official binary path defined. Hog will not be able to upload binaries."
816  }
817 
818 
819  if {$missing_vars} {
820  Msg Error "One or more essential environment variables are missing. Hog-CI cannot run!"
821  exit 1
822  }
823 }
824 
825 ## brief Check environment to execute chosen project
826 # @param[in] project_name The name of the project
827 # @param[in] ide The IDE to build the chosen project
828 proc CheckEnv {project_name ide} {
829  global env
830  set has_error 0
831  set essential_commands [dict create "git" "--version" "$ide" "-version"]
832  set additional_commands [dict create \
833  "vsim" "-version" \
834  "eos" "" \
835  "kinit" "" \
836  "rclone" "--version"]
837 
838  set additional_vars [dict create \
839  "HOG_PATH" "NOT defined. Hog might work as long as all the necessary executable are in the PATH variable." \
840  "HOG_XIL_LICENSE" "NOT defined. If this variable is not set to the license servers separated by comas, \
841  you need some alternative way of getting your Xilinx license (for example a license file on the machine)." \
842  "LM_LICENSE_FILE" "NOT defined. This variable should be set the Quartus/Libero license servers separated by semicolon. \
843  If not, you need an alternative way of getting your Quartus/Libero license." \
844  "HOG_LD_LIBRARY_PATH" "NOT defined. Hog might work as long as all the necessary library are found." \
845  "HOG_SIMULATION_LIB_PATH" "NOT defined. Hog-CI will not be able to run simulations using third-party simulators." \
846  "HOG_CHECK_PROJVER" "NOT defined. Hog will NOT check the CI project version. \
847  Set this variable to '1' if you want Hog to check the CI project version before creating the HDL project in Create_Project stage. \
848  If the project has not been changed with respect to the target branch, the CI will skip this project" \
849  "HOG_CHECK_SYNTAX" "NOT defined. Hog will NOT check the syntax. \
850  Set this variable to '1' if you want Hog to check the syntax after creating the HDL project in Create_Project stage." \
851  "HOG_NO_BITSTREAM" "NOT defined. Hog-CI will run the implementation up to the write_bitstream stage and create bit files." \
852  "HOG_NO_RESET_BD" "NOT defined or not equal to 1. Hog will reset .bd files (if any) before starting synthesis." \
853  "HOG_IP_PATH" "NOT defined. Hog-CI will NOT use an EOS/LOCAL IP repository to speed up the IP synthesis." \
854  "HOG_RESET_FILES" "NOT defined. Hog-CI will NOT reset any files." \
855  "HOG_NJOBS" "NOT defined. Hog-CI will build IPs with default number of jobs (4)." \
856  "HOG_SAVE_DCP" "NOT defined. Set this variable to 1, 2 or 3 to make Hog-CI save the run checkpoint DCP files (Vivado only) in the artifacts.\nCheck the official documentation for more details. https://cern.ch/hog"]
857  Msg Info "Checking environment to run Hog-CI for project $project_name with IDE $ide..."
858 
859  Msg Info "Checking essential commands..."
860  dict for {cmd ver} $essential_commands {
861  if {[catch {exec which $cmd}]} {
862  Msg CriticalWarning "$cmd executable not found. Hog-CI cannot run!"
863  set has_error 1
864  } else {
865  Msg Info "Found executable $cmd."
866  if {$cmd == "ghdl"} {
867  Msg Info [exec $cmd --version]
868  } elseif {$cmd != "diamond"} {
869  Msg Info [exec $cmd $ver]
870  }
871  }
872  }
873 
874  Msg Info "Checking additional commands..."
875  dict for {cmd ver} $additional_commands {
876  if {[catch {exec which $cmd}]} {
877  Msg Warning "$cmd executable not found."
878  } else {
879  Msg Info "Found executable $cmd."
880  if {$ver != ""} {
881  Msg Info [exec $cmd $ver]
882  }
883  }
884  }
885 
886  if {$ide == "libero"} {
887  Msg Info "Checking essential environment variables..."
888  # Check if HOG_TCLLIB_PATH is defined
889  if {![info exists env(HOG_TCLLIB_PATH)]} {
890  Msg Error "Environmnental variable HOG_TCLLIB_PATH is NOT defined. This variable is essential to run Hog with Tcllib and Libero. Please, refer to https://hog.readthedocs.io/en/latest/02-User-Manual/01-Hog-local/13-Libero.html."
891  set has_error 1
892  } else {
893  Msg Info "HOG_TCLLIB_PATH is set. Hog-CI can run with Libero."
894  }
895  }
896 
897  Msg Info "Checking additional environment variables..."
898  dict for {var msg} $additional_vars {
899  if {![info exists env($var)]} {
900  Msg Info "Environment variable $var is $msg"
901  } else {
902  Msg Info "Found environment variable $var."
903  }
904  }
905 
906  if {$has_error} {
907  Msg Error "One or more essential environment variables are missing. Hog-CI cannot run!"
908  exit 1
909  }
910 }
911 
912 ## @brief Check the settings for the remote IP path. It checks if the path is on EOS or Rclone and if the necessary tools are available.
913 # @param[in] repo_path The path to the repository
914 # @param[in] ip_path The path to the remote IP repository
915 #
916 # @return A list with the following elements:
917 # 1. on_eos: 1 if the path is on EOS, 0 otherwise
918 # 2. on_rclone: 1 if the path is on Rclone, 0 otherwise
919 # 3. config_path: The path to the rclone config file if on Rclone
920 proc CheckRemoteIpPath {repo_path ip_path} {
921  global env
922  set on_eos 0
923  set on_rclone 0
924  set config_path ""
925  set old_path [pwd]
926  cd $repo_path
927 
928  if {[regexp {^[^/]+:} $ip_path]} {
929  # Rclone path (e.g., dropbox:Project/IPs or eos:user/d/dcieri/...)
930  set on_rclone 1
931  # Check if rclone is available
932  lassign [ExecuteRet rclone --version] rclone_ret rclone_ver
933  if {$rclone_ret != 0} {
934  Msg Warning "Rclone path specified but rclone not found or failed: $rclone_ver"
935  cd $old_path
936  return 0
937  } else {
938  Msg Info "IP remote directory path, on Rclone, is set to: $ip_path"
939  # Check if RCLONE_CONFIG environment variable is set, if not set it to the default path
940  if {[info exists env(HOG_RCLONE_CONFIG)]} {
941  Msg Info "Using rclone config from environment variable HOG_RCLONE_CONFIG: $env(HOG_RCLONE_CONFIG)"
942  set config_path $env(HOG_RCLONE_CONFIG)
943  } else {
944  set config_path "/dev/null"
945  Msg Info "Environment variable HOG_RCLONE_CONFIG not set, using rclone environmental variables..."
946  }
947 
948  set remote_name "[lindex [split $ip_path ":"] 0]:"
949  lassign [ExecuteRet rclone listremotes --config $config_path] rclone_list_ret remotes
950  if {$rclone_list_ret != 0} {
951  Msg Warning "Could not list rclone remotes: $remotes"
952  cd $old_path
953  return 0
954  } else {
955  if {![IsInList $remote_name $remotes]} {
956  Msg Warning "Rclone remote $remote_name not found among available remotes: $remotes"
957  cd $old_path
958  return 0
959  }
960  }
961  }
962  } elseif {[string first "/eos/" $ip_path] == 0} {
963  # IP Path is on EOS
964  # Check if kinit is done
965  if {!([info exists ::env(ENABLE_EOS)] && $::env(ENABLE_EOS) == 1)} {
966  Msg Warning "IP remote directory path is on EOS but kinit was not successfull or not done. I will not copy IPs from/to EOS."
967  cd $old_path
968  return 0
969  }
970  # Check if eos is mounted
971  if {[file isdirectory $ip_path]} {
972  Msg Info "Eos is mounted in the current machine. Treating it as a normal directory..."
973  } else {
974  set on_eos 1
975  lassign [eos "ls $ip_path"] ret result
976  if {$ret != 0} {
977  Msg Warning "Could not run ls for for EOS path: $ip_path (error: $result). \
978  Either the drectory does not exist or there are (temporary) problem with EOS."
979  cd $old_path
980  return 0
981  } else {
982  Msg Info "IP remote directory path, on EOS, is set to: $ip_path"
983  }
984  }
985  } else {
986  file mkdir $ip_path
987  }
988 
989  cd $old_path
990  return [list 1 $on_eos $on_rclone $config_path]
991 }
992 
993 proc CheckProjVer {repo_path project {sim 0} {ext_path ""}} {
994  global env
995 
996  if {$sim == 1} {
997  Msg Info "Will check also the version of the simulation files..."
998  }
999 
1000  set ci_run 0
1001  if {[info exists env(HOG_PUSH_TOKEN)] && [info exist env(CI_PROJECT_ID)] && [info exist env(CI_API_V4_URL)]} {
1002  set token $env(HOG_PUSH_TOKEN)
1003  set api_url $env(CI_API_V4_URL)
1004  set project_id $env(CI_PROJECT_ID)
1005  set ci_run 1
1006  set curl_cmd [GetCurl $api_url]
1007  if {[info exist env(CI_JOB_NAME)] && $env(CI_JOB_NAME) == "check_branch_state"} {
1008  # Do not download artifacts from previous pipelines in check_branch_state job...
1009  set ci_run 0
1010  }
1011  } else {
1012  set curl_cmd [GetCurl]
1013  }
1014 
1015  cd $repo_path
1016  set project_dir $repo_path/Top/$project
1017  set ver [GetProjectVersion $project_dir $repo_path $ext_path $sim]
1018  if {$ver == 0} {
1019  Msg Info "$project was modified, continuing with the CI..."
1020  if {$ci_run == 1 && $curl_cmd != 0 && ![IsQuartus] && ![IsISE]} {
1021  Msg Info "Checking if the project has been already built in a previous CI run..."
1022  lassign [GetRepoVersions $project_dir $repo_path] sha
1023  if {$sha == [GetSHA $repo_path]} {
1024  Msg Info "Project was modified in the current commit, Hog will proceed with the build workflow."
1025  return 0
1026  }
1027  Msg Info "Checking if project $project has been built in a previous CI run with sha $sha..."
1028  set result [catch {package require json} JsonFound]
1029  if {"$result" != "0"} {
1030  Msg CriticalWarning "Cannot find JSON package equal or higher than 1.0.\n $JsonFound\n Exiting"
1031  return 0
1032  }
1033  lassign [ExecuteRet {*}$curl_cmd --header "PRIVATE-TOKEN: $token" "$api_url/projects/$project_id/pipelines"] ret content
1034  set pipeline_dict [json::json2dict $content]
1035  if {[llength $pipeline_dict] > 0} {
1036  foreach pip $pipeline_dict {
1037  set pip_sha [DictGet $pip sha]
1038  set source [DictGet $pip source]
1039  if {$source == "merge_request_event" && [string first $sha $pip_sha] != -1} {
1040  Msg Info "Found pipeline with sha $pip_sha for project $project"
1041  set pipeline_id [DictGet $pip id]
1042  # tclint-disable-next-line line-length
1043  lassign [ExecuteRet {*}$curl_cmd --header "PRIVATE-TOKEN: $token" "$api_url/projects/${project_id}/pipelines/${pipeline_id}/jobs?pagination=keyset&per_page=100"] ret2 content2
1044  set jobs_dict [json::json2dict $content2]
1045  if {[llength $jobs_dict] > 0} {
1046  foreach job $jobs_dict {
1047  set job_name [DictGet $job name]
1048  set job_id [DictGet $job id]
1049  set artifacts [DictGet $job artifacts_file]
1050  set status [DictGet $job status]
1051  set current_job_name $env(CI_JOB_NAME)
1052  if {$current_job_name == $job_name && $status == "success"} {
1053  # tclint-disable-next-line line-length
1054  lassign [ExecuteRet {*}$curl_cmd --location --output artifacts.zip --header "PRIVATE-TOKEN: $token" --url "$api_url/projects/$project_id/jobs/$job_id/artifacts"] ret3 content3
1055  if {$ret3 != 0} {
1056  Msg CriticalWarning "Cannot download artifacts for job $job_name with id $job_id"
1057  return 0
1058  } else {
1059  lassign [ExecuteRet unzip -o $repo_path/artifacts.zip] ret_zip
1060  if {$ret_zip != 0} {
1061 
1062  } else {
1063  Msg Info "Artifacts for job $job_name with id $job_id downloaded and unzipped."
1064  file mkdir $repo_path/Projects/$project
1065  set fp [open "$repo_path/Projects/$project/skip.me" w+]
1066  close $fp
1067  return 1
1068  }
1069  }
1070  }
1071  }
1072  }
1073  }
1074  }
1075  }
1076  }
1077  } elseif {$ver != -1} {
1078  Msg Info "$project was not modified since version: $ver."
1079  file mkdir $repo_path/Projects/$project
1080  set fp [open "$repo_path/Projects/$project/skip.me" w+]
1081  close $fp
1082  return 1
1083  } else {
1084  Msg Error "Impossible to check the project version. Most likely the repository is not clean. Please, commit your changes before running this command."
1085  return 0
1086  }
1087  return 0
1088 }
1089 
1090 # @brief Check the syntax of the source files in the
1091 #
1092 # @param[in] project_name the name of the project
1093 # @param[in] repo_path The main path of the git repository
1094 # @param[in] project_file The project file (for Libero)
1095 proc CheckSyntax {project_name repo_path {project_file ""}} {
1096  if {[IsVivado]} {
1097  update_compile_order
1098  set syntax [check_syntax -return_string]
1099  if {[string first "CRITICAL" $syntax] != -1} {
1100  check_syntax
1101  exit 1
1102  }
1103  } elseif {[IsQuartus]} {
1104  lassign [GetHogFiles -list_files "*.src" "$repo_path/Top/$project_name/list/" $repo_path] src_files dummy
1105  dict for {lib files} $src_files {
1106  foreach f $files {
1107  set file_extension [file extension $f]
1108  if {$file_extension == ".vhd" || $file_extension == ".vhdl" || $file_extension == ".v" || $file_extension == ".sv"} {
1109  if {[catch {execute_module -tool map -args "--analyze_file=$f"} result]} {
1110  Msg Error "\nResult: $result\n"
1111  Msg Error "Check syntax failed.\n"
1112  } else {
1113  if {$result == ""} {
1114  Msg Info "Check syntax was successful for $f.\n"
1115  } else {
1116  Msg Warning "Found syntax error in file $f:\n $result\n"
1117  }
1118  }
1119  }
1120  }
1121  }
1122  } elseif {[IsLibero]} {
1123  lassign [GetProjectFiles $project_file] prjLibraries prjProperties prjSimLibraries prjConstraints prjSrcSets prjSimSets prjConSets
1124  dict for {lib sources} $prjLibraries {
1125  if {[file extension $lib] == ".src"} {
1126  foreach f $sources {
1127  Msg Info "Checking Syntax of $f"
1128  check_hdl -file $f
1129  }
1130  }
1131  }
1132  } else {
1133  Msg Info "The Checking Syntax is not supported by this IDE. Skipping..."
1134  }
1135 }
1136 
1137 # @brief Close the open project (does nothing for Xilinx and Libero)
1138 proc CloseProject {} {
1139  if {[IsXilinx]} {
1140 
1141  } elseif {[IsQuartus]} {
1142  project_close
1143  } elseif {[IsLibero]} {
1144 
1145  } elseif {[IsDiamond]} {
1146  prj_project save
1147  prj_project close
1148  }
1149 }
1150 
1151 ## @brief Compare two semantic versions
1152 #
1153 # @param[in] ver1 a list of 3 numbers M m p
1154 # @param[in] ver2 a list of 3 numbers M m p
1155 #
1156 # In case the ver1 or ver2 are in the format vX.Y.Z rather than a list, they will be converted.
1157 # If one of the tags is an empty string it will be considered as 0.0.0
1158 #
1159 # @return Returns 1 ver1 is greater than ver2, 0 if they are equal, and -1 if ver2 is greater than ver1
1160 proc CompareVersions {ver1 ver2} {
1161  if {$ver1 eq ""} {
1162  set ver1 v0.0.0
1163  }
1164 
1165  if {$ver2 eq ""} {
1166  set ver2 v0.0.0
1167  }
1168 
1169  if {[regexp {v(\d+)\.(\d+)\.(\d+)} $ver1 - x y z]} {
1170  set ver1 [list $x $y $z]
1171  }
1172  if {[regexp {v(\d+)\.(\d+)\.(\d+)} $ver2 - x y z]} {
1173  set ver2 [list $x $y $z]
1174  }
1175 
1176  # Add 1 in front to avoid crazy Tcl behaviour with leading 0 being octal...
1177  set v1 [join $ver1 ""]
1178  set v1 "1$v1"
1179  set v2 [join $ver2 ""]
1180  set v2 "1$v2"
1181 
1182  if {[string is integer $v1] && [string is integer $v2]} {
1183  set ver1 [expr {[scan [lindex $ver1 0] %d] * 1000000 + [scan [lindex $ver1 1] %d] * 1000 + [scan [lindex $ver1 2] %d]}]
1184  set ver2 [expr {[scan [lindex $ver2 0] %d] * 1000000 + [scan [lindex $ver2 1] %d] * 1000 + [scan [lindex $ver2 2] %d]}]
1185 
1186  if {$ver1 > $ver2} {
1187  set ret 1
1188  } elseif {$ver1 == $ver2} {
1189  set ret 0
1190  } else {
1191  set ret -1
1192  }
1193  } else {
1194  Msg Warning "Version is not numeric: $ver1, $ver2"
1195  set ret 0
1196  }
1197  return [expr {$ret}]
1198 }
1199 
1200 # @brief Function searching for extra IP/BD files added at creation time using user scripts, and writing the list in
1201 # Project/proj/.hog/extra.files, with the correspondent md5sum
1202 #
1203 # @param[in] libraries The Hog libraries
1204 proc CheckExtraFiles {libraries constraints simlibraries} {
1205  ### CHECK NOW FOR IP OUTSIDE OF LIST FILE (Vivado only!)
1206  if {[IsVivado]} {
1207  lassign [GetProjectFiles] prjLibraries prjProperties prjSimLibraries prjConstraints
1208  set prj_dir [get_property DIRECTORY [current_project]]
1209  file mkdir "$prj_dir/.hog"
1210  set extra_file_name "$prj_dir/.hog/extra.files"
1211  set new_extra_file [open $extra_file_name "w"]
1212 
1213  dict for {prjLib prjFiles} $prjLibraries {
1214  foreach prjFile $prjFiles {
1215  if {[file extension $prjFile] == ".xcix"} {
1216  Msg Warning "IP $prjFile is packed in a .xcix core container. \
1217  This files are not suitable for version control systems. We recommend to use .xci files instead."
1218  continue
1219  }
1220  if {[file extension $prjFile] == ".xci" && [get_property CORE_CONTAINER [get_files $prjFile]] != ""} {
1221  Msg Info "$prjFile is a virtual IP file in a core container. Ignoring it..."
1222  continue
1223  }
1224 
1225  if {[IsInList $prjFile [DictGet $libraries $prjLib]] == 0} {
1226  if {[file extension $prjFile] == ".bd"} {
1227  # Generating BD products to save md5sum of already modified BD
1228  Msg Info "Generating targets of $prjFile..."
1229  generate_target all [get_files $prjFile]
1230  }
1231  puts $new_extra_file "$prjFile [Md5Sum $prjFile]"
1232  Msg Info "$prjFile (lib: $prjLib) has been generated by an external script. Adding to $extra_file_name..."
1233  }
1234  }
1235  }
1236  close $new_extra_file
1237  set extra_sim_file "$prj_dir/.hog/extrasim.files"
1238  set new_extra_file [open $extra_sim_file "w"]
1239 
1240  dict for {prjSimLib prjSimFiles} $prjSimLibraries {
1241  foreach prjSimFile $prjSimFiles {
1242  if {[IsInList $prjSimFile [DictGet $simlibraries $prjSimLib]] == 0} {
1243  puts $new_extra_file "$prjSimFile [Md5Sum $prjSimFile]"
1244  Msg Info "$prjSimFile (lib: $prjSimLib) has been generated by an external script. Adding to $extra_sim_file..."
1245  }
1246  }
1247  }
1248  close $new_extra_file
1249  set extra_con_file "$prj_dir/.hog/extracon.files"
1250  set new_extra_file [open $extra_con_file "w"]
1251 
1252  dict for {prjConLib prjConFiles} $prjConstraints {
1253  foreach prjConFile $prjConFiles {
1254  if {[IsInList $prjConFile [DictGet $constraints $prjConLib]] == 0} {
1255  puts $new_extra_file "$prjConFile [Md5Sum $prjConFile]"
1256  Msg Info "$prjConFile has been generated by an external script. Adding to $extra_con_file..."
1257  }
1258  }
1259  }
1260  close $new_extra_file
1261  }
1262 }
1263 
1264 # @brief Check if the running Hog version is the latest stable release
1265 #
1266 # @param[in] repo_path The main path of the git repository
1267 proc CheckLatestHogRelease {{repo_path .}} {
1268  if {[info exists ::env(CHECK_FOR_HOG_UPDATES)] && $::env(CHECK_FOR_HOG_UPDATES) eq "0"} {
1269  return
1270  }
1271 
1272 
1273  if {[catch {package require inifile 0.2.3} ERROR] == 0} {
1274  set repo_conf $repo_path/Top/repo.conf
1275  if {[file exists $repo_conf]} {
1276  set PROPERTIES [ReadConf $repo_conf]
1277  if {[dict exists $PROPERTIES main]} {
1278  set mainDict [dict get $PROPERTIES main]
1279  if {[dict exists $mainDict CHECK_FOR_HOG_UPDATES]} {
1280  if {[dict get $mainDict CHECK_FOR_HOG_UPDATES] == 0} {
1281  return
1282  }
1283  }
1284  }
1285  }
1286  }
1287 
1288  set old_path [pwd]
1289  cd $repo_path/Hog
1290  set current_ver [Git {describe --always}]
1291  Msg Debug "Current version: $current_ver"
1292  set current_sha [Git "log $current_ver -1 --format=format:%H"]
1293  Msg Debug "Current SHA: $current_sha"
1294 
1295  #We should find a proper way of checking for timeout using wait, this'll do for now
1296  if {[OS] == "windows"} {
1297  Msg Info "On windows we cannot set a timeout on 'git fetch', hopefully nothing will go wrong..."
1298  Git fetch
1299  } else {
1300  Msg Info "Checking for latest Hog release, can take up to 5 seconds..."
1301  ExecuteRet timeout 5s git fetch
1302  }
1303  set master_ver [Git "describe origin/master"]
1304  Msg Debug "Master version: $master_ver"
1305  set master_sha [Git "log $master_ver -1 --format=format:%H"]
1306  Msg Debug "Master SHA: $master_sha"
1307  lassign [GitRet "merge-base $current_sha $master_sha"] mb_ret merge_base
1308  if {$mb_ret != 0} {
1309  Msg Warning "Could not run git merge-base (shallow clone?). Skipping Hog update check."
1310  cd $old_path
1311  return
1312  }
1313  Msg Debug "merge base: $merge_base"
1314 
1315 
1316  if {$merge_base != $master_sha} {
1317  # If master_sha is NOT an ancestor of current_sha
1318  Msg Info "Version $master_ver has been released (https://gitlab.com/hog-cern/Hog/-/releases/$master_ver)"
1319  Msg Status "You should consider updating Hog submodule with the following instructions:"
1320  Msg Status ""
1321  Msg Status "cd Hog && git checkout master && git pull"
1322  Msg Status ""
1323  Msg Status "Also update the ref: in your .gitlab-ci.yml to $master_ver"
1324  Msg Status ""
1325  } else {
1326  # If it is
1327  Msg Info "Latest official version is $master_ver, nothing to do."
1328  }
1329 
1330  cd $old_path
1331 }
1332 
1333 
1334 ## @brief Checks that "ref" in .gitlab-ci.yml actually matches the hog.yml file in the
1335 #
1336 # @param[in] repo_path path to the repository root
1337 # @param[in] allow_failure if true throws CriticalWarnings instead of Errors
1338 #
1339 proc CheckYmlRef {repo_path allow_failure} {
1340  if {$allow_failure} {
1341  set MSG_TYPE CriticalWarning
1342  } else {
1343  set MSG_TYPE Error
1344  }
1345 
1346  if {[catch {package require yaml 0.3.3} YAMLPACKAGE]} {
1347  Msg CriticalWarning "Cannot find package YAML, skipping consistency check of \"ref\" in gilab-ci.yaml file.\n Error message: $YAMLPACKAGE
1348  You can fix this by installing package \"tcllib\""
1349  return
1350  }
1351 
1352  set thisPath [pwd]
1353 
1354  # Go to repository path
1355  cd "$repo_path"
1356  if {[file exists .gitlab-ci.yml]} {
1357  #get .gitlab-ci ref
1358  set YML_REF ""
1359  set YML_NAME ""
1360  if {[file exists .gitlab-ci.yml]} {
1361  set fp [open ".gitlab-ci.yml" r]
1362  set file_data [read $fp]
1363  close $fp
1364  } else {
1365  Msg $MSG_TYPE "Cannot open file .gitlab-ci.yml"
1366  cd $thisPath
1367  return
1368  }
1369  set file_data "\n$file_data\n\n"
1370 
1371  if {[catch {::yaml::yaml2dict -stream $file_data} yamlDict]} {
1372  Msg $MSG_TYPE "Parsing $repo_path/.gitlab-ci.yml failed. To fix this, check that yaml syntax is respected, remember not to use tabs."
1373  cd $thisPath
1374  return
1375  } else {
1376  dict for {dictKey dictValue} $yamlDict {
1377  #looking for Hog include in .gitlab-ci.yml
1378  if {
1379  "$dictKey" == "include" && (
1380  [lsearch [split $dictValue " {}"] "/hog.yml"] != "-1" ||
1381  [lsearch [split $dictValue " {}"] "/hog-dynamic.yml"] != "-1"
1382  )
1383  } {
1384  set YML_REF [lindex [split $dictValue " {}"] [expr {[lsearch -dictionary [split $dictValue " {}"] "ref"] + 1}]]
1385  set YML_NAME [lindex [split $dictValue " {}"] [expr {[lsearch -dictionary [split $dictValue " {}"] "file"] + 1}]]
1386  }
1387  }
1388  }
1389  if {$YML_REF == ""} {
1390  Msg Warning "Hog version not specified in the .gitlab-ci.yml. Assuming that master branch is used."
1391  cd Hog
1392  set YML_REF_F [Git {name-rev --tags --name-only origin/master}]
1393  cd ..
1394  } else {
1395  set YML_REF_F [regsub -all "'" $YML_REF ""]
1396  }
1397 
1398  if {$YML_NAME == ""} {
1399  Msg $MSG_TYPE "Hog included yml file not specified, assuming hog.yml"
1400  set YML_NAME_F hog.yml
1401  } else {
1402  set YML_NAME_F [regsub -all "^/" $YML_NAME ""]
1403  }
1404 
1405  lappend YML_FILES $YML_NAME_F
1406 
1407  #getting Hog repository tag and commit
1408  cd "Hog"
1409 
1410  #check if the yml file includes other files
1411  if {[catch {::yaml::yaml2dict -file $YML_NAME_F} yamlDict]} {
1412  Msg $MSG_TYPE "Parsing $YML_NAME_F failed."
1413  cd $thisPath
1414  return
1415  } else {
1416  dict for {dictKey dictValue} $yamlDict {
1417  #looking for included files
1418  if {"$dictKey" == "include"} {
1419  foreach v $dictValue {
1420  lappend YML_FILES [lindex [split $v " "] [expr {[lsearch -dictionary [split $v " "] "local"] + 1}]]
1421  }
1422  }
1423  }
1424  }
1425 
1426  Msg Info "Found the following yml files: $YML_FILES"
1427 
1428  set HOGYML_SHA [GetSHA $YML_FILES]
1429  lassign [GitRet "log --format=%h -1 --abbrev=7 $YML_REF_F" $YML_FILES] ret EXPECTEDYML_SHA
1430  if {$ret != 0} {
1431  lassign [GitRet "log --format=%h -1 --abbrev=7 origin/$YML_REF_F" $YML_FILES] ret EXPECTEDYML_SHA
1432  if {$ret != 0} {
1433  Msg $MSG_TYPE "Error in project .gitlab-ci.yml. ref: $YML_REF not found"
1434  set EXPECTEDYML_SHA ""
1435  }
1436  }
1437  if {!($EXPECTEDYML_SHA eq "")} {
1438  if {$HOGYML_SHA == $EXPECTEDYML_SHA} {
1439  Msg Info "Hog included file $YML_FILES matches with $YML_REF in .gitlab-ci.yml."
1440  } else {
1441  Msg $MSG_TYPE "HOG $YML_FILES SHA mismatch.
1442  From Hog submodule: $HOGYML_SHA
1443  From ref in .gitlab-ci.yml: $EXPECTEDYML_SHA
1444  You can fix this in 2 ways: by changing the ref in your repository or by changing the Hog submodule commit"
1445  }
1446  } else {
1447  Msg $MSG_TYPE "One or more of the following files could not be found $YML_FILES in Hog at $YML_REF"
1448  }
1449  } else {
1450  Msg Info ".gitlab-ci.yml not found in $repo_path. Skipping this step"
1451  }
1452 
1453  cd "$thisPath"
1454 }
1455 
1456 ## @brief Compare the contents of two dictionaries
1457 #
1458 # @param[in] proj_libs The dictionary of libraries in the project
1459 # @param[in] list_libs The dictionary of libraries in list files
1460 # @param[in] proj_sets The dictionary of filesets in the project
1461 # @param[in] list_sets The dictionary of filesets in list files
1462 # @param[in] proj_props The dictionary of file properties in the project
1463 # @param[in] list_props The dictionary of file pproperties in list files
1464 # @param[in] severity The severity of the message in case a file is not found (Default: CriticalWarning)
1465 # @param[in] outFile The output log file, to write the messages (Default "")
1466 # @param[in] extraFiles The dictionary of extra files generated a creation time (Default "")
1467 #
1468 # @return n_diffs The number of differences
1469 # @return extra_files Remaining list of extra files
1470 
1471 proc CompareLibDicts {proj_libs list_libs proj_sets list_sets proj_props list_props {severity "CriticalWarning"} {outFile ""} {extraFiles ""}} {
1472  set extra_files $extraFiles
1473  set n_diffs 0
1474  set out_prjlibs $proj_libs
1475  set out_prjprops $proj_props
1476  # Loop over filesets in project
1477  dict for {prjSet prjLibraries} $proj_sets {
1478  # Check if sets is also in list files
1479  if {[IsInList $prjSet $list_sets]} {
1480  set listLibraries [DictGet $list_sets $prjSet]
1481  # Loop over libraries in fileset
1482  foreach prjLib $prjLibraries {
1483  set prjFiles [DictGet $proj_libs $prjLib]
1484  # Check if library exists in list files
1485  if {[IsInList $prjLib $listLibraries]} {
1486  # Loop over files in library
1487  set listFiles [DictGet $list_libs $prjLib]
1488  foreach prjFile $prjFiles {
1489  set idx [lsearch -exact $listFiles $prjFile]
1490  set listFiles [lreplace $listFiles $idx $idx]
1491  if {$idx < 0} {
1492  # File is in project but not in list libraries, check if it was generated at creation time...
1493  if {[dict exists $extra_files $prjFile]} {
1494  # File was generated at creation time, checking the md5sum
1495  # Removing the file from the prjFiles list
1496  set idx2 [lsearch -exact $prjFiles $prjFile]
1497  set prjFiles [lreplace $prjFiles $idx2 $idx2]
1498  set new_md5sum [Md5Sum $prjFile]
1499  set old_md5sum [DictGet $extra_files $prjFile]
1500  if {$new_md5sum != $old_md5sum} {
1501  # tclint-disable-next-line line-length
1502  MsgAndLog "$prjFile in project has been modified from creation time. \Please update the script you used to create the file and regenerate the project, or save the file outside the Projects/ directory and add it to a project list file" $severity $outFile
1503  incr n_diffs
1504  }
1505  set extra_files [dict remove $extra_files $prjFile]
1506  } else {
1507  # File is neither in list files nor in extra_files
1508  MsgAndLog "$prjFile was found in project but not in list files or .hog/extra.files" $severity $outFile
1509  incr n_diffs
1510  }
1511  } else {
1512  # File is both in list files and project, checking properties...
1513  set prjProps [DictGet $proj_props $prjFile]
1514  set listProps [DictGet $list_props $prjFile]
1515  # Check if it is a potential sourced file
1516  if {[IsInList "nosynth" $prjProps] && [IsInList "noimpl" $prjProps] && [IsInList "nosim" $prjProps]} {
1517  # Check if it is sourced
1518  set idx_source [lsearch -exact $listProps "source"]
1519  if {$idx_source >= 0} {
1520  # It is sourced, let's replace the individual properties with source
1521  set idx [lsearch -exact $prjProps "noimpl"]
1522  set prjProps [lreplace $prjProps $idx $idx]
1523  set idx [lsearch -exact $prjProps "nosynth"]
1524  set prjProps [lreplace $prjProps $idx $idx]
1525  set idx [lsearch -exact $prjProps "nosim"]
1526  set prjProps [lreplace $prjProps $idx $idx]
1527  lappend prjProps "source"
1528  }
1529  }
1530 
1531  foreach prjProp $prjProps {
1532  set idx [lsearch -exact $listProps $prjProp]
1533  set listProps [lreplace $listProps $idx $idx]
1534  if {$idx < 0} {
1535  MsgAndLog "Property $prjProp of $prjFile was set in project but not in list files" $severity $outFile
1536  incr n_diffs
1537  }
1538  }
1539 
1540  foreach listProp $listProps {
1541  if {[string first $listProp "topsim="] == -1 && [string first $listProp "enable"] == -1} {
1542  MsgAndLog "Property $listProp of $prjFile was found in list files but not set in project." $severity $outFile
1543  incr n_diffs
1544  }
1545  }
1546 
1547  # Update project prjProps
1548  dict set out_prjprops $prjFile $prjProps
1549  }
1550  }
1551  # Loop over remaining files in list libraries
1552  foreach listFile $listFiles {
1553  MsgAndLog "$listFile was found in list files but not in project." $severity $outFile
1554  incr n_diffs
1555  }
1556  } else {
1557  # Check extra files again...
1558  foreach prjFile $prjFiles {
1559  if {[dict exists $extra_files $prjFile]} {
1560  # File was generated at creation time, checking the md5sum
1561  # Removing the file from the prjFiles list
1562  set idx2 [lsearch -exact $prjFiles $prjFile]
1563  set prjFiles [lreplace $prjFiles $idx2 $idx2]
1564  set new_md5sum [Md5Sum $prjFile]
1565  set old_md5sum [DictGet $extra_files $prjFile]
1566  if {$new_md5sum != $old_md5sum} {
1567  # tclint-disable-next-line line-length
1568  MsgAndLog "$prjFile in project has been modified from creation time. Please update the script you used to create the file and regenerate the project, or save the file outside the Projects/ directory and add it to a project list file" $severity $outFile
1569  incr n_diffs
1570  }
1571  set extra_files [dict remove $extra_files $prjFile]
1572  } else {
1573  # File is neither in list files nor in extra_files
1574  MsgAndLog "$prjFile was found in project but not in list files or .hog/extra.files" $severity $outFile
1575  incr n_diffs
1576  }
1577  }
1578  }
1579  # Update prjLibraries
1580  dict set out_prjlibs $prjLib $prjFiles
1581  }
1582  } else {
1583  MsgAndLog "Fileset $prjSet found in project but not in list files" $severity $outFile
1584  incr n_diffs
1585  }
1586  }
1587 
1588  return [list $n_diffs $extra_files $out_prjlibs $out_prjprops]
1589 }
1590 
1591 ## @brief Compare two VHDL files ignoring spaces and comments
1592 #
1593 # @param[in] file1 the first file
1594 # @param[in] file2 the second file
1595 #
1596 # @ return A string with the diff of the files
1597 #
1598 proc CompareVHDL {file1 file2} {
1599  set a [open $file1 r]
1600  set b [open $file2 r]
1601 
1602  while {[gets $a line] != -1} {
1603  set line [regsub {^[\t\s]*(.*)?\s*} $line "\\1"]
1604  if {![regexp {^$} $line] & ![regexp {^--} $line]} {
1605  #Exclude empty lines and comments
1606  lappend f1 $line
1607  }
1608  }
1609 
1610  while {[gets $b line] != -1} {
1611  set line [regsub {^[\t\s]*(.*)?\s*} $line "\\1"]
1612  if {![regexp {^$} $line] & ![regexp {^--} $line]} {
1613  #Exclude empty lines and comments
1614  lappend f2 $line
1615  }
1616  }
1617 
1618  close $a
1619  close $b
1620  set diff {}
1621  foreach x $f1 y $f2 {
1622  if {$x != $y} {
1623  lappend diff "> $x\n< $y\n\n"
1624  }
1625  }
1626 
1627  return $diff
1628 }
1629 
1630 ##
1631 ## Copy a file or folder into a new path, not throwing an error if the final path is not empty
1632 ##
1633 ## @param i_dirs The directory or file to copy
1634 ## @param o_dir The final destination
1635 ##
1636 proc Copy {i_dirs o_dir} {
1637  foreach i_dir $i_dirs {
1638  if {[file isdirectory $i_dir] && [file isdirectory $o_dir]} {
1639  if {([file tail $i_dir] == [file tail $o_dir]) || ([file exists $o_dir/[file tail $i_dir]] && [file isdirectory $o_dir/[file tail $i_dir]])} {
1640  file delete -force $o_dir/[file tail $i_dir]
1641  }
1642  }
1643 
1644  file copy -force $i_dir $o_dir
1645  }
1646 }
1647 
1648 ## @brief Read a XML list file and copy files to destination
1649 #
1650 # Additional information is provided with text separated from the file name with one or more spaces
1651 #
1652 # @param[in] proj_dir project path, path containing the ./list directory containing at least a list file with .ipb extention
1653 # @param[in] path the path the XML files are referred to in the list file
1654 # @param[in] dst the path the XML files must be copied to
1655 # @param[in] xml_version the M.m.p version to be used to replace the __VERSION__ placeholder in any of the xml files
1656 # @param[in] xml_sha the Git-SHA to be used to replace the __GIT_SHA__ placeholder in any of the xml files
1657 # @param[in] use_ipbus_sw if set to 1, use the IPbus sw to generate or check the vhdl files
1658 # @param[in] generate if set to 1, tells the function to generate the VHDL decode address files rather than check them
1659 proc CopyIPbusXMLs {proj_dir path dst {xml_version "0.0.0"} {xml_sha "00000000"} {use_ipbus_sw 0} {generate 0}} {
1660  if {$use_ipbus_sw == 1} {
1661  lassign [ExecuteRet python3 -c "from __future__ import print_function; from sys import path;print(':'.join(path\[1:\]))"] ret msg
1662  if {$ret == 0} {
1663  set ::env(PYTHONPATH) $msg
1664  lassign [ExecuteRet gen_ipbus_addr_decode -h] ret msg
1665  if {$ret != 0} {
1666  set can_generate 0
1667  } else {
1668  set can_generate 1
1669  }
1670  } else {
1671  Msg CriticalWarning "Problem while trying to run python: $msg"
1672  set can_generate 0
1673  }
1674  set dst [file normalize $dst]
1675  file mkdir $dst
1676  if {$can_generate == 0} {
1677  if {$generate == 1} {
1678  Msg Error "Cannot generate IPbus address files, IPbus executable gen_ipbus_addr_decode not found or not working: $msg"
1679  return -1
1680  } else {
1681  Msg Warning "IPbus executable gen_ipbus_addr_decode not found or not working, will not verify IPbus address tables."
1682  }
1683  }
1684  } else {
1685  set can_generate 0
1686  }
1687 
1688  set ipb_files [glob -nocomplain $proj_dir/list/*.ipb]
1689  set n_ipb_files [llength $ipb_files]
1690  if {$n_ipb_files == 0} {
1691  Msg CriticalWarning "No files with .ipb extension found in $proj_dir/list."
1692  return
1693  }
1694  set libraries [dict create]
1695  set vhdl_dict [dict create]
1696 
1697  foreach ipb_file $ipb_files {
1698  lassign [ReadListFile {*}"$ipb_file $path"] l p fs
1699  set libraries [MergeDict $l $libraries]
1700  set vhdl_dict [MergeDict $p $vhdl_dict]
1701  }
1702 
1703  set xmlfiles [dict get $libraries "xml.ipb"]
1704 
1705  set xml_list_error 0
1706  foreach xmlfile $xmlfiles {
1707  if {[file isdirectory $xmlfile]} {
1708  Msg CriticalWarning "Directory $xmlfile listed in xml list file $list_file. Directories are not supported!"
1709  set xml_list_error 1
1710  }
1711 
1712  if {[file exists $xmlfile]} {
1713  if {[dict exists $vhdl_dict $xmlfile]} {
1714  set vhdl_file [file normalize [dict get $vhdl_dict $xmlfile]]
1715  } else {
1716  set vhdl_file ""
1717  }
1718  lappend vhdls $vhdl_file
1719  set xmlfile [file normalize $xmlfile]
1720  Msg Info "Copying $xmlfile to $dst and replacing place holders..."
1721  set in [open $xmlfile r]
1722 
1723  if {[regexp \/xml\/+(.*)$ $xmlfile XXX out_with_dir]} {
1724  set out_file $dst/$out_with_dir
1725  lappend xmls $out_with_dir
1726  Msg Debug "xml file $xmlfile is contained in a directory called 'xml', so file will be copied to $out_file"
1727  set out_dir [file dir $out_file]
1728  if {![file exists $out_dir]} {
1729  file mkdir $out_dir
1730  }
1731  } else {
1732  set out_file $dst/[file tail $xmlfile]
1733  lappend xmls [file tail $xmlfile]
1734  }
1735 
1736  set out [open $out_file w]
1737  while {[gets $in line] != -1} {
1738  set new_line [regsub {(.*)__VERSION__(.*)} $line "\\1$xml_version\\2"]
1739  set new_line2 [regsub {(.*)__GIT_SHA__(.*)} $new_line "\\1$xml_sha\\2"]
1740  puts $out $new_line2
1741  }
1742  close $in
1743  close $out
1744  } else {
1745  Msg Warning "XML file $xmlfile not found"
1746  }
1747  }
1748  if {${xml_list_error}} {
1749  Msg Error "Invalid files added to $list_file!"
1750  }
1751 
1752  set cnt [llength $xmls]
1753  Msg Info "$cnt xml file/s copied"
1754 
1755 
1756  if {$can_generate == 1} {
1757  set old_dir [pwd]
1758  cd $dst
1759  file mkdir "address_decode"
1760  cd "address_decode"
1761  foreach x $xmls v $vhdls {
1762  if {$v ne ""} {
1763  set x [file normalize ../$x]
1764  if {[file exists $x]} {
1765  lassign [ExecuteRet gen_ipbus_addr_decode --no-timestamp $x 2>&1] status log
1766  if {$status == 0} {
1767  set generated_vhdl ./ipbus_decode_[file rootname [file tail $x]].vhd
1768  if {$generate == 1} {
1769  Msg Info "Copying generated VHDL file $generated_vhdl into $v (replacing if necessary)"
1770  file copy -force -- $generated_vhdl $v
1771  } else {
1772  if {[file exists $v]} {
1773  set diff [CompareVHDL $generated_vhdl $v]
1774  set n [llength $diff]
1775  if {$n > 0} {
1776  Msg CriticalWarning "$v does not correspond to its XML $x, [expr {$n / 3}] line/s differ:"
1777  Msg Status [join $diff "\n"]
1778  set diff_file [open ../diff_[file rootname [file tail $x]].txt w]
1779  puts $diff_file $diff
1780  close $diff_file
1781  } else {
1782  Msg Info "[file tail $x] and $v match."
1783  }
1784  } else {
1785  Msg Warning "VHDL address map file $v not found."
1786  }
1787  }
1788  } else {
1789  Msg Warning "Address map generation failed for [file tail $x]: $log"
1790  }
1791  } else {
1792  Msg Warning "Copied XML file $x not found."
1793  }
1794  } else {
1795  Msg Info "Skipped verification of [file tail $x] as no VHDL file was specified."
1796  }
1797  }
1798  cd ..
1799  file delete -force address_decode
1800  cd $old_dir
1801  }
1802 }
1803 
1804 ## @brief Returns the description from the hog.conf file.
1805 # The description is the comment in the second line stripped of the hashes
1806 # If the description contains the word test, Test or TEST, then "test" is simply returned.
1807 # This is used to avoid printing them in ListProjects unless -all is specified
1808 #
1809 # @param[in] conf_file the path to the hog.conf file
1810 #
1811 proc DescriptionFromConf {conf_file} {
1812  set f [open $conf_file "r"]
1813  set lines [split [read $f] "\n"]
1814  close $f
1815  set second_line [lindex $lines 1]
1816 
1817 
1818  if {![regexp {\#+ *(.+)} $second_line - description]} {
1819  set description ""
1820  }
1821 
1822  if {[regexp -all {test|Test|TEST} $description]} {
1823  set description "test"
1824  }
1825 
1826  return $description
1827 }
1828 
1829 ## @brief Returns the value in a Tcl dictionary corresponding to the chosen key
1830 #
1831 # @param[in] dictName the name of the dictionary
1832 # @param[in] keyName the name of the key
1833 # @param[in] default the default value to be returned if the key is not found (default "")
1834 #
1835 # @return The value in the dictionary corresponding to the provided key
1836 proc DictGet {dictName keyName {default ""}} {
1837  if {[dict exists $dictName $keyName]} {
1838  return [dict get $dictName $keyName]
1839  } else {
1840  return $default
1841  }
1842 }
1843 
1844 ## Sorts a dictionary
1845 #
1846 # @param[in] dict the dictionary
1847 # @param[in] args the arguments to pass to lsort, e.g. -ascii, -dictionary, -decreasing
1848 # @returns a new dictionary with the keys sorted according to the arguments
1849 proc DictSort {dict args} {
1850  set res {}
1851  foreach key [lsort {*}$args [dict keys $dict]] {
1852  dict set res $key [dict get $dict $key]
1853  }
1854  set res
1855 }
1856 
1857 ## @brief Checks the Doxygen version installed in this machine
1858 #
1859 # @param[in] target_version the version required by the current project
1860 #
1861 # @return Returns 1, if the system Doxygen version is greater or equal to the target
1862 proc DoxygenVersion {target_version} {
1863  set ver [split $target_version "."]
1864  set v [Execute doxygen --version]
1865  Msg Info "Found Doxygen version: $v"
1866  set current_ver [split $v ". "]
1867  set target [expr {[lindex $ver 0] * 100000 + [lindex $ver 1] * 100 + [lindex $ver 2]}]
1868  set current [expr {[lindex $current_ver 0] * 100000 + [lindex $current_ver 1] * 100 + [lindex $current_ver 2]}]
1869 
1870  return [expr {$target <= $current}]
1871 }
1872 
1873 ## @brief Handle eos commands
1874 #
1875 # It can be used with lassign like this: lassign [eos <eos command> ] ret result
1876 #
1877 # @param[in] command: the EOS command to be run, e.g. ls, cp, mv, rm
1878 # @param[in] attempt: (default 0) how many times the command should be attempted in case of failure
1879 #
1880 # @returns a list of 2 elements: the return value (0 if no error occurred) and the output of the EOS command
1881 proc eos {command {attempt 1}} {
1882  global env
1883  if {![info exists env(EOS_MGM_URL)]} {
1884  Msg Warning "Environment variable EOS_MGM_URL not set, setting it to default value root://eosuser.cern.ch"
1885  set ::env(EOS_MGM_URL) "root://eosuser.cern.ch"
1886  }
1887  if {$attempt < 1} {
1888  Msg Warning "The value of attempt should be 1 or more, not $attempt, setting it to 1 as default"
1889  set attempt 1
1890  }
1891  for {set i 0} {$i < $attempt} {incr i} {
1892  set ret [catch {exec -ignorestderr eos {*}$command} result]
1893  if {$ret == 0} {
1894  break
1895  } else {
1896  if {$attempt > 1} {
1897  set wait [expr {1 + int(rand() * 29)}]
1898  Msg Warning "Command $command failed ($i/$attempt): $result, trying again in $wait seconds..."
1899  after [expr {$wait * 1000}]
1900  }
1901  }
1902  }
1903  return [list $ret $result]
1904 }
1905 
1906 ## @brief Handle shell commands
1907 #
1908 # It can be used with lassign like this: lassign [Execute <command> ] ret result
1909 #
1910 # @param[in] args: the shell command
1911 #
1912 # @returns the output of the command
1913 proc Execute {args} {
1914  global env
1915  lassign [ExecuteRet {*}$args] ret result
1916  if {$ret != 0} {
1917  Msg Error "Command [join $args] returned error code: $ret"
1918  }
1919 
1920  return $result
1921 }
1922 
1923 
1924 ## @brief Handle shell commands
1925 #
1926 # It can be used with lassign like this: lassign [ExecuteRet <command> ] ret result
1927 #
1928 # @param[in] args: the shell command
1929 #
1930 # @returns a list of 2 elements: the return value (0 if no error occurred) and the output of the command
1931 proc ExecuteRet {args} {
1932  global env
1933  if {[llength $args] == 0} {
1934  Msg CriticalWarning "No argument given"
1935  set ret -1
1936  set result ""
1937  } else {
1938  set ret [catch {exec -ignorestderr {*}$args} result]
1939  }
1940 
1941  return [list $ret $result]
1942 }
1943 
1944 ## @brief Extract the [files] section from a sim list file
1945 #
1946 # @param[in] the content of the simulation list file to extract the [files] section from
1947 # @returns a list of files in the [files] section, or all lines if no [files] section is found
1948 proc ExtractFilesSection {file_data} {
1949  set in_files_section 0
1950  set result {}
1951 
1952  foreach line $file_data {
1953  if {[regexp {^ *\[ *files *\]} $line]} {
1954  set in_files_section 1
1955  continue
1956  }
1957  if {$in_files_section} {
1958  if {[regexp {^ *\[.*\]} $line]} {
1959  break
1960  }
1961  lappend result $line
1962  }
1963  }
1964 
1965  # If [files] was not found, return all file_data
1966  if {!$in_files_section} {
1967  return $file_data
1968  } else {
1969  return $result
1970  }
1971 }
1972 
1973 
1974 ## @brief Tags the repository with a new version calculated on the basis of the previous tags
1975 #
1976 # @param[in] tag a tag in the Hog format: v$M.$m.$p or b$(mr)v$M.$m.$p-$n
1977 #
1978 # @return a list containing: Major minor patch v.
1979 #
1980 proc ExtractVersionFromTag {tag} {
1981  if {[regexp {^(?:b(\d+))?v(\d+)\.(\d+).(\d+)(?:-\d+)?$} $tag -> mr M m p]} {
1982  if {$mr eq ""} {
1983  set mr 0
1984  }
1985  } else {
1986  Msg Warning "Repository tag $tag is not in a Hog-compatible format."
1987  set mr -1
1988  set M -1
1989  set m -1
1990  set p -1
1991  }
1992  return [list $M $m $p $mr]
1993 }
1994 
1995 
1996 ## @brief Checks if file was committed into the repository
1997 #
1998 #
1999 # @param[in] File: file name
2000 #
2001 # @returns 1 if file was committed and 0 if file was not committed
2002 proc FileCommitted {File} {
2003  set Ret 1
2004  set currentDir [pwd]
2005  cd [file dirname [file normalize $File]]
2006  set GitLog [Git ls-files [file tail $File]]
2007  if {$GitLog == ""} {
2008  Msg CriticalWarning "File [file normalize $File] is not in the git repository. Please add it with:\n git add [file normalize $File]\n"
2009  set Ret 0
2010  }
2011  cd $currentDir
2012  return $Ret
2013 }
2014 
2015 # @brief Returns the common child of two git commits
2016 #
2017 # @param[in] SHA1 The first commit
2018 # @param[in] SHA2 The second commit
2019 proc FindCommonGitChild {SHA1 SHA2} {
2020  # Get the list of all commits in the repository
2021  set commits [Git {log --oneline --merges}]
2022  set ancestor 0
2023  # Iterate over each commit
2024  foreach line [split $commits "\n"] {
2025  set commit [lindex [split $line] 0]
2026 
2027  # Check if both SHA1 and SHA2 are ancestors of the commit
2028  if {[IsCommitAncestor $SHA1 $commit] && [IsCommitAncestor $SHA2 $commit]} {
2029  set ancestor $commit
2030  break
2031  }
2032  }
2033  return $ancestor
2034 }
2035 
2036 
2037 # @brief Returns a list of files in a directory matching a pattern
2038 #
2039 # @param[in] basedir The directory to start looking in
2040 # @param[in pattern A pattern, as defined by the glob command, that the files must match
2041 # Credit: https://stackexchange.com/users/14219/jackson
2042 proc findFiles {basedir pattern} {
2043  # Fix the directory name, this ensures the directory name is in the
2044  # native format for the platform and contains a final directory seperator
2045  set basedir [string trimright [file join [file normalize $basedir] { }]]
2046  set fileList {}
2047 
2048  # Look in the current directory for matching files, -type {f r}
2049  # means ony readable normal files are looked at, -nocomplain stops
2050  # an error being thrown if the returned list is empty
2051  foreach fileName [glob -nocomplain -type {f r} -path $basedir $pattern] {
2052  lappend fileList $fileName
2053  }
2054 
2055  # Now look for any sub direcories in the current directory
2056  foreach dirName [glob -nocomplain -type {d r} -path $basedir *] {
2057  # Recusively call the routine on the sub directory and append any
2058  # new files to the results
2059  set subDirList [findFiles $dirName $pattern]
2060  if {[llength $subDirList] > 0} {
2061  foreach subDirFile $subDirList {
2062  lappend fileList $subDirFile
2063  }
2064  }
2065  }
2066  return $fileList
2067 }
2068 
2069 ## @brief determine file type from extension
2070 # Used only for Quartus
2071 #
2072 ## @return FILE_TYPE the file Type
2073 proc FindFileType {file_name} {
2074  set extension [file extension $file_name]
2075  switch $extension {
2076  .stp {
2077  set file_extension "USE_SIGNALTAP_FILE"
2078  }
2079  .vhd {
2080  set file_extension "VHDL_FILE"
2081  }
2082  .vhdl {
2083  set file_extension "VHDL_FILE"
2084  }
2085  .v {
2086  set file_extension "VERILOG_FILE"
2087  }
2088  .sv {
2089  set file_extension "SYSTEMVERILOG_FILE"
2090  }
2091  .sdc {
2092  set file_extension "SDC_FILE"
2093  }
2094  .pdc {
2095  set file_extension "PDC_FILE"
2096  }
2097  .ndc {
2098  set file_extension "NDC_FILE"
2099  }
2100  .fdc {
2101  set file_extension "FDC_FILE"
2102  }
2103  .qsf {
2104  set file_extension "SOURCE_FILE"
2105  }
2106  .ip {
2107  set file_extension "IP_FILE"
2108  }
2109  .qsys {
2110  set file_extension "QSYS_FILE"
2111  }
2112  .qip {
2113  set file_extension "QIP_FILE"
2114  }
2115  .sip {
2116  set file_extension "SIP_FILE"
2117  }
2118  .bsf {
2119  set file_extension "BSF_FILE"
2120  }
2121  .bdf {
2122  set file_extension "BDF_FILE"
2123  }
2124  .tcl {
2125  set file_extension "COMMAND_MACRO_FILE"
2126  }
2127  .vdm {
2128  set file_extension "VQM_FILE"
2129  }
2130  default {
2131  set file_extension "ERROR"
2132  Msg Error "Unknown file extension $extension"
2133  }
2134  }
2135  return $file_extension
2136 }
2137 
2138 
2139 # @brief Returns the newest version in a list of versions
2140 #
2141 # @param[in] versions The list of versions
2142 proc FindNewestVersion {versions} {
2143  set new_ver 00000000
2144  foreach ver $versions {
2145  # tclint-disable-next-line redundant-expr
2146  if {[expr 0x$ver > 0x$new_ver]} {
2147  set new_ver $ver
2148  }
2149  }
2150  return $new_ver
2151 }
2152 
2153 # Find repo root by walking up from a start dir until we see Top/ and Projects/.
2154 # For absolute paths we do not normalize, so the path form is preserved and [file exists]
2155 # sees the same view as the script was loaded from.
2156 proc FindRepoRoot {start_dir} {
2157  if {[file pathtype $start_dir] eq "relative"} {
2158  set dir [file normalize [file join [pwd] $start_dir]]
2159  } else {
2160  set dir $start_dir
2161  }
2162  while {1} {
2163  if {[file exists [file join $dir Top]] && [file exists [file join $dir Projects]]} {
2164  return $dir
2165  }
2166  set parent [file dirname $dir]
2167  if {$parent eq $dir} {
2168  return ""
2169  }
2170  set dir $parent
2171  }
2172 }
2173 
2174 ## @brief Set VHDL version to 2008 for *.vhd files
2175 #
2176 # @param[in] file_name the name of the HDL file
2177 #
2178 # @return "-hdl_version VHDL_2008" if the file is a *.vhd files else ""
2179 proc FindVhdlVersion {file_name} {
2180  set extension [file extension $file_name]
2181  switch $extension {
2182  .vhd {
2183  set vhdl_version "-hdl_version VHDL_2008"
2184  }
2185  .vhdl {
2186  set vhdl_version "-hdl_version VHDL_2008"
2187  }
2188  default {
2189  set vhdl_version ""
2190  }
2191  }
2192 
2193  return $vhdl_version
2194 }
2195 
2196 ## @brief Format a generic to a 32 bit verilog style hex number, e.g.
2197 # take in ea8394c and return 32'h0ea8394c
2198 #
2199 # @param[in] unformatted generic
2200 proc FormatGeneric {generic} {
2201  if {[string is integer "0x$generic"]} {
2202  return [format "32'h%08X" "0x$generic"]
2203  } else {
2204  # for non integers (e.g. blanks) just return 0
2205  return [format "32'h%08X" 0]
2206  }
2207 }
2208 
2209 # @brief Generate the bitstream
2210 #
2211 # @param[in] project_name The name of the project
2212 # @param[in] run_folder The path where to run the implementation
2213 # @param[in] repo_path The main path of the git repository
2214 # @param[in] njobs The number of CPU jobs to run in parallel
2215 proc GenerateBitstream {{run_folder ""} {repo_path .} {njobs 1}} {
2216  Msg Info "Starting write bitstream flow..."
2217  if {[IsQuartus]} {
2218  set revision [get_current_revision]
2219  if {[catch {execute_module -tool asm} result]} {
2220  Msg Error "Result: $result\n"
2221  Msg Error "Generate bitstream failed. See the report file.\n"
2222  } else {
2223  Msg Info "Generate bitstream was successful for revision $revision.\n"
2224  }
2225  } elseif {[IsLibero]} {
2226  Msg Info "Run GENERATEPROGRAMMINGDATA ..."
2227  if {[catch {run_tool -name {GENERATEPROGRAMMINGDATA}}]} {
2228  Msg Error "GENERATEPROGRAMMINGDATA FAILED!"
2229  } else {
2230  Msg Info "GENERATEPROGRAMMINGDATA PASSED."
2231  }
2232  Msg Info "Sourcing Hog/Tcl/integrated/post-bitstream.tcl"
2233  source $repo_path/Hog/Tcl/integrated/post-bitstream.tcl
2234  } elseif {[IsDiamond]} {
2235  prj_run Export -impl Implementation0 -task Bitgen
2236  }
2237 }
2238 
2239 ## @brief Function used to generate a qsys system from a .qsys file.
2240 # The procedure adds the generated IPs to the project.
2241 #
2242 # @param[in] qsysFile the Intel Platform Designed file (.qsys), containing the system to be generated
2243 # @param[in] commandOpts the command options to be used during system generation as they are in qsys-generate options
2244 #
2245 proc GenerateQsysSystem {qsysFile commandOpts} {
2246  global env
2247  if {[file exists $qsysFile] != 0} {
2248  set qsysPath [file dirname $qsysFile]
2249  set qsysName [file rootname [file tail $qsysFile]]
2250  set qsysIPDir "$qsysPath/$qsysName"
2251  set qsysLogFile "$qsysPath/$qsysName.qsys-generate.log"
2252 
2253  set qsys_rootdir ""
2254  if {![info exists ::env(QSYS_ROOTDIR)]} {
2255  if {[info exists ::env(QUARTUS_ROOTDIR)]} {
2256  set qsys_rootdir "$::env(QUARTUS_ROOTDIR)/sopc_builder/bin"
2257  Msg Warning "The QSYS_ROOTDIR environment variable is not set! I will use $qsys_rootdir"
2258  } else {
2259  Msg CriticalWarning "The QUARTUS_ROOTDIR environment variable is not set! Assuming all quartus executables are contained in your PATH!"
2260  }
2261  } else {
2262  set qsys_rootdir $::env(QSYS_ROOTDIR)
2263  }
2264 
2265  set cmd "$qsys_rootdir/qsys-generate"
2266  set cmd_options "$qsysFile --output-directory=$qsysIPDir $commandOpts"
2267  if {![catch {"exec $cmd -version"}] || [lindex $::errorCode 0] eq "NONE"} {
2268  Msg Info "Executing: $cmd $cmd_options"
2269  Msg Info "Saving logfile in: $qsysLogFile"
2270  if {[catch {eval exec -ignorestderr "$cmd $cmd_options >>& $qsysLogFile"} ret opt]} {
2271  set makeRet [lindex [dict get $opt -errorcode] end]
2272  Msg CriticalWarning "$cmd returned with $makeRet"
2273  }
2274  } else {
2275  Msg Error " Could not execute command $cmd"
2276  exit 1
2277  }
2278  #Add generated IPs to project
2279  set qsysIPFileList [concat \
2280  [glob -nocomplain -directory $qsysIPDir -types f *.ip *.qip] \
2281  [glob -nocomplain -directory "$qsysIPDir/synthesis" -types f *.ip *.qip *.vhd *.vhdl]]
2282  foreach qsysIPFile $qsysIPFileList {
2283  if {[file exists $qsysIPFile] != 0} {
2284  set qsysIPFileType [FindFileType $qsysIPFile]
2285  set_global_assignment -name $qsysIPFileType $qsysIPFile
2286  # Write checksum to file
2287  set IpMd5Sum [Md5Sum $qsysIPFile]
2288  # open file for writing
2289  set fileDir [file normalize "./hogTmp"]
2290  set fileName "$fileDir/.hogQsys.md5"
2291  if {![file exists $fileDir]} {
2292  file mkdir $fileDir
2293  }
2294  set hogQsysFile [open $fileName "a"]
2295  set fileEntry "$qsysIPFile\t$IpMd5Sum"
2296  puts $hogQsysFile $fileEntry
2297  close $hogQsysFile
2298  }
2299  }
2300  } else {
2301  Msg ERROR "Error while generating ip variations from qsys: $qsysFile not found!"
2302  }
2303 }
2304 
2305 
2306 ## Format generics from conf file to string that simulators accepts
2307 #
2308 # @param[in] dict containing generics from conf file
2309 # @param[in] target: software target(vivado, questa)
2310 # defines the output format of the string
2311 proc GenericToSimulatorString {prop_dict target} {
2312  set prj_generics ""
2313  dict for {theKey theValue} $prop_dict {
2314  set theValue [string trim $theValue]
2315  set valueHexFull ""
2316  set valueNumBits ""
2317  set valueHexFlag ""
2318  set valueHex ""
2319  set valueIntFull ""
2320  set ValueInt ""
2321  set valueStrFull ""
2322  set ValueStr ""
2323  regexp {([0-9]*)('h)([0-9a-fA-F]*)} $theValue valueHexFull valueNumBits valueHexFlag valueHex
2324  regexp {^([0-9]*)$} $theValue valueIntFull ValueInt
2325  regexp {(?!^\d+$)^.+$} $theValue valueStrFull ValueStr
2326  if {[string tolower $target] == "vivado" || [string tolower $target] == "xsim"} {
2327  if {[string tolower $theValue] == "true" || [string tolower $theValue] == "false"} {
2328  set prj_generics "$prj_generics $theKey=[string tolower $theValue]"
2329  } elseif {$valueNumBits != "" && $valueHexFlag != "" && $valueHex != ""} {
2330  set prj_generics "$prj_generics $theKey=$valueHexFull"
2331  } elseif {$valueIntFull != "" && $ValueInt != ""} {
2332  set prj_generics "$prj_generics $theKey=$ValueInt"
2333  } elseif {$valueStrFull != "" && $ValueStr != ""} {
2334  set prj_generics "$prj_generics $theKey=\"$ValueStr\""
2335  } else {
2336  set prj_generics "$prj_generics $theKey=\"$theValue\""
2337  }
2338  } elseif {[lsearch -exact [GetSimulators] [string tolower $target]] >= 0} {
2339  if {$valueNumBits != "" && $valueHexFlag != "" && $valueHex != ""} {
2340  set numBits 0
2341  scan $valueNumBits %d numBits
2342  set numHex 0
2343  scan $valueHex %x numHex
2344  binary scan [binary format "I" $numHex] "B*" binval
2345  set numBits [expr {$numBits - 1}]
2346  set numBin [string range $binval end-$numBits end]
2347  set prj_generics "$prj_generics $theKey=\"$numBin\""
2348  } elseif {$valueIntFull != "" && $ValueInt != ""} {
2349  set prj_generics "$prj_generics $theKey=$ValueInt"
2350  } elseif {$valueStrFull != "" && $ValueStr != ""} {
2351  set prj_generics "$prj_generics {$theKey=\"$ValueStr\"}"
2352  } else {
2353  set prj_generics "$prj_generics {$theKey=\"$theValue\"}"
2354  }
2355  } else {
2356  Msg Warning "Target : $target not implemented"
2357  }
2358  }
2359  return $prj_generics
2360 }
2361 
2362 ## Get the configuration files to create a Hog project
2363 #
2364 # @param[in] proj_dir: The project directory containing the conf file or the the tcl file
2365 #
2366 # @return[in] a list containing the full path of the hog.conf, sim.conf, pre-creation.tcl, post-creation.tcl, pre-rtl.tcl, and post-rtl.tcl files
2367 proc GetConfFiles {proj_dir} {
2368  Msg Debug "GetConfFiles called with proj_dir=$proj_dir"
2369  if {![file isdirectory $proj_dir]} {
2370  Msg Error "$proj_dir is supposed to be the top project directory"
2371  return -1
2372  }
2373  set conf_file [file normalize $proj_dir/hog.conf]
2374  set sim_file [file normalize $proj_dir/sim.conf]
2375  set pre_tcl [file normalize $proj_dir/pre-creation.tcl]
2376  set post_tcl [file normalize $proj_dir/post-creation.tcl]
2377  set pre_rtl [file normalize $proj_dir/pre-rtl.tcl]
2378  set post_rtl [file normalize $proj_dir/post-rtl.tcl]
2379 
2380  return [list $conf_file $sim_file $pre_tcl $post_tcl $pre_rtl $post_rtl]
2381 }
2382 
2383 
2384 # Searches directory for tcl scripts to add as custom commands to launch.tcl
2385 # Returns string of tcl scripts formatted as usage or switch statement
2386 #
2387 # @param[in] directory The directory where to look for custom tcl scripts (Default .)
2388 # @param[in] ret_commands if 1 returns commands as switch statement string instead of usage (Default 0)
2389 proc GetCustomCommands {parameters {directory .}} {
2390  set commands_dict [dict create]
2391  set commands_files [glob -nocomplain $directory/*.tcl]
2392 
2393  if {[llength $commands_files] == 0} {
2394  return ""
2395  }
2396 
2397  foreach file $commands_files {
2398  #Msg Info "do compile libe? $do_compile_lib"
2399  set custom_cmd [LoadCustomCommandFile $file $parameters]
2400 
2401  if {$custom_cmd eq ""} {
2402  continue
2403  }
2404 
2405  #Msg Info "Validating custom command $custom_cmd"
2406  set custom_cmd_name [dict get $custom_cmd NAME]
2407 
2408  Msg Debug "Loaded custom command '$custom_cmd_name' from $file"
2409 
2410  #Ensure command is not already defined
2411  if {[dict exists $commands_dict $custom_cmd_name]} {
2412  Msg Error "Custom command '$custom_cmd_name' in $file already defined as: \[dict get $commands_dict $custom_cmd_name\]. Skipping."
2413  continue
2414  }
2415 
2416 
2417  set custom_cmd_name [string toupper $custom_cmd_name]
2418  dict set commands_dict $custom_cmd_name $custom_cmd
2419  }
2420 
2421  return $commands_dict
2422 }
2423 
2424 proc SanitizeCustomCommand {cmdDict file parameters} {
2425  # Normalize all user-provided keys to uppercase so NAME/DESCRIPTION/etc are case-insensitive.
2426 
2427 
2428  set normalized {}
2429  foreach k [dict keys $cmdDict] {
2430  set K [string toupper $k]
2431  dict set normalized $K [dict get $cmdDict $k]
2432  }
2433 
2434  set cmdDict $normalized
2435  if {![dict exists $cmdDict NAME]} {
2436  Msg Error "Custom command in $file missing required key NAME. Skipping."
2437  return ""
2438  }
2439  if {![dict exists $cmdDict SCRIPT]} {
2440  Msg Error "Custom command '$[dict get $cmdDict NAME]' in $file missing SCRIPT. Skipping."
2441  return ""
2442  }
2443 
2444  # Allowed keys (uppercased). IDE is optional and will be validated if present.
2445  set allowed {NAME DESCRIPTION OPTIONS CUSTOM_OPTIONS SCRIPT IDE NO_EXIT}
2446  foreach k [dict keys $cmdDict] {
2447  if {[lsearch -exact $allowed $k] < 0} {
2448  Msg Warning "Custom command '[dict get $cmdDict NAME]' in $file: unknown key '$k'. Allowed: $allowed. Skipping."
2449  }
2450  }
2451 
2452  # NAME
2453  set name [string trim [dict get $cmdDict NAME]]
2454  if {$name eq ""} {
2455  Msg Error "Custom command in $file has empty NAME. Skipping."
2456  return ""
2457  }
2458 
2459  if {![regexp {^[a-zA-Z][a-zA-Z0-9_]+$} $name]} {
2460  Msg Error "Custom command NAME '$name' (file $file) contains invalid characters."
2461  }
2462 
2463  # DESCRIPTION
2464  if {![dict exists $cmdDict DESCRIPTION]} {
2465  dict set cmdDict DESCRIPTION "No description provided."
2466  }
2467 
2468 
2469  set hog_parameters {}
2470  foreach p $parameters {
2471  lappend hog_parameters [lindex $p 0]
2472  }
2473 
2474  # OPTIONS
2475  set hog_options {}
2476  if {[dict exists $cmdDict OPTIONS]} {
2477  set raw_opts [dict get $cmdDict OPTIONS]
2478  if {![llength $raw_opts]} {
2479  set raw_opts {}
2480  }
2481 
2482  foreach item $raw_opts {
2483  set found 0
2484  foreach p $parameters {
2485  set hog_parameter [lindex $p 0]
2486  if {$item eq $hog_parameter} {
2487  lappend hog_options $p
2488  set found 1
2489  break
2490  }
2491  }
2492  if {!$found} {
2493  Msg Warning "Custom command '$name' in $file: option '$item' not found in Hog parameters. Skipping."
2494  }
2495  }
2496  dict set cmdDict OPTIONS $hog_options
2497  } else {
2498  dict set cmdDict OPTIONS {}
2499  }
2500 
2501 
2502  # CUSTOM_OPTIONS
2503  set opt_defs {}
2504  if {[dict exists $cmdDict CUSTOM_OPTIONS]} {
2505  set raw_opts [dict get $cmdDict CUSTOM_OPTIONS]
2506  if {![llength $raw_opts]} {
2507  set raw_opts {}
2508  }
2509  foreach item $raw_opts {
2510  if {[llength $item] != 2 && [llength $item] != 3} {
2511  Msg Error "Bad custom option: \[$item\]. Custom command '$name' in $file: \
2512  each CUSTOM_OPTIONS entry must be {option \"help\"} for flags \
2513  and {option \"default_value\" \"help\"} for options with arguments. Skipping command."
2514  return ""
2515  }
2516 
2517  if {[llength $item] == 2} {
2518  lassign $item opt help
2519  set def ""
2520  } else {
2521  lassign $item opt def help
2522  }
2523 
2524  if {[IsInList $opt $hog_parameters] == 1} {
2525  Msg Warning "Custom command '$name' in $file: option '$opt' already defined in Hog parameters. Skipping."
2526  continue
2527  }
2528 
2529 
2530  #optional .arg in option regex
2531  if {![regexp {^[a-zA-Z][a-zA-Z0-9_]*(\.arg)?$} $opt]} {
2532  Msg Error "Custom command '$name' in $file: invalid option name '$opt'."
2533  return ""
2534  }
2535 
2536  if {$help eq ""} {
2537  Msg Warning "Custom command '$name' option '$opt' has empty help text."
2538  }
2539  }
2540  } else {
2541  dict set cmdDict CUSTOM_OPTIONS {}
2542  }
2543 
2544  # NO EXIT
2545  if {[dict exists $cmdDict NO_EXIT]} {
2546  set no_exit [dict get $cmdDict NO_EXIT]
2547  set no_exit [string tolower [string trim $no_exit]]
2548 
2549  if {$no_exit eq "1" || $no_exit eq "true"} {
2550  set no_exit 1
2551  } else {
2552  set no_exit 0
2553  }
2554 
2555  dict set cmdDict NO_EXIT $no_exit
2556  } else {
2557  dict set cmdDict NO_EXIT 0
2558  }
2559 
2560  return $cmdDict
2561 }
2562 
2563 proc LoadCustomCommandFile {file parameters} {
2564  set saved_pwd [pwd]
2565  set dir [file dirname $file]
2566  cd $dir
2567  unset -nocomplain ::hog_command
2568  set rc [catch {source $file} err]
2569  cd $saved_pwd
2570  if {$rc} {
2571  Msg Error "Error sourcing custom command file $file: $err"
2572  return ""
2573  }
2574  if {![info exists ::hog_command]} {
2575  Msg Warning "File $file did not define ::hog_command. Skipping."
2576  return ""
2577  }
2578  set cmdDict $::hog_command
2579  # Ensure it's a dict
2580  if {[catch {dict size $cmdDict}]} {
2581  Msg Error "In $file ::hog_command is not a valid dict. Skipping."
2582  return ""
2583  }
2584  return [SanitizeCustomCommand $cmdDict $file $parameters]
2585 }
2586 
2587 
2588 ## Get the Date and time of a commit (or current time if Git < 2.9.3)
2589 #
2590 # @param[in] commit The commit
2591 proc GetDateAndTime {commit} {
2592  set clock_seconds [clock seconds]
2593 
2594  if {[GitVersion 2.9.3]} {
2595  set date [Git "log -1 --format=%cd --date=format:%d%m%Y $commit"]
2596  set timee [Git "log -1 --format=%cd --date=format:00%H%M%S $commit"]
2597  } else {
2598  Msg Warning "Found Git version older than 2.9.3. Using current date and time instead of commit time."
2599  set date [clock format $clock_seconds -format {%d%m%Y}]
2600  set timee [clock format $clock_seconds -format {00%H%M%S}]
2601  }
2602  return [list $date $timee]
2603 }
2604 
2605 ## @brief Gets a list of files contained in the current fileset that match a file name (passed as parameter)
2606 #
2607 # The file name is matched against the input parameter.
2608 #
2609 # @param[in] file name (or part of it)
2610 # @param[in] fileset name
2611 #
2612 # @return a list of files matching the parameter in the chosen fileset
2613 #
2614 proc GetFile {file fileset} {
2615  if {[IsXilinx]} {
2616  # Vivado
2617  set Files [get_files -all $file -of_object [get_filesets $fileset]]
2618  set f [lindex $Files 0]
2619 
2620  return $f
2621  } elseif {[IsQuartus]} {
2622  # Quartus
2623  return ""
2624  } else {
2625  # Tcl Shell
2626  puts "***DEBUG Hog:GetFile $file"
2627  return "DEBUG_file"
2628  }
2629 }
2630 
2631 ## @brief Extract the generics from a file
2632 #
2633 # @param[in] filename The file from which to extract the generics
2634 # @param[in] entity The entity in the file from which to extract the generics (default "")
2635 proc GetFileGenerics {filename {entity ""}} {
2636  set file_type [FindFileType $filename]
2637  if {[string equal $file_type "VERILOG_FILE"] || [string equal $file_type "SYSTEMVERILOG_FILE"]} {
2638  return [GetVerilogGenerics $filename]
2639  } elseif {[string equal $file_type "VHDL_FILE"]} {
2640  return [GetVhdlGenerics $filename $entity]
2641  } else {
2642  Msg CriticalWarning "Could not determine extension of top level file."
2643  }
2644 }
2645 
2646 ## @brief Gets custom generics from hog
2647 #
2648 # @param[in] proj_dir: the top folder of the project
2649 # @return dict with generics
2650 #
2651 proc GetGenericsFromConf {proj_dir} {
2652  set generics_dict [dict create]
2653  set top_dir "Top/$proj_dir"
2654  set conf_file "$top_dir/hog.conf"
2655  set conf_index 0
2656  Msg Debug "GetGenericsFromConf called with proj_dir=$proj_dir, top_dir=$top_dir"
2657 
2658  if {[file exists $conf_file]} {
2659  set properties [ReadConf [lindex [GetConfFiles $top_dir] $conf_index]]
2660  if {[dict exists $properties generics]} {
2661  set generics_dict [dict get $properties generics]
2662  }
2663  } else {
2664  Msg Warning "File $conf_file not found."
2665  }
2666  return $generics_dict
2667 }
2668 
2669 ## @brief Gets the simulation sets from the project
2670 #
2671 # @param[in] project_name: the name of the project
2672 # @param[in] repo_path: the path to the repository
2673 # @param[in] simsets: a list of simulation sets to retrieve (default: all)
2674 # @param[in] ghdl: if 1, only GHDL simulation sets are returned (default: 0),
2675 # otherwise only non-GHDL simulation sets are returned
2676 # @param[in] no_conf: if 1, the simulation sets are returned without reading the sim.conf file (default: 0)
2677 # @return a dictionary with the simulation sets, where the keys are the simulation set names
2678 # and the values are dictionaries with the properties of each simulation set
2679 proc GetSimSets {project_name repo_path {simsets ""} {ghdl 0} {no_conf 0}} {
2680  set simsets_dict [dict create]
2681  set list_dir "$repo_path/Top/$project_name/list"
2682  set list_files []
2683  if {$simsets != ""} {
2684  foreach s $simsets {
2685  set list_file "$list_dir/$s.sim"
2686  if {[file exists $list_file]} {
2687  lappend list_files $list_file
2688  } elseif {$s != "sim_1"} {
2689  Msg CriticalWarning "Simulation set list file $list_file not found."
2690  return ""
2691  }
2692  }
2693  } else {
2694  set list_files [glob -nocomplain -directory $list_dir "*.sim"]
2695  }
2696 
2697  # Get simulation properties from conf file
2698  set proj_dir [file normalize $repo_path/Top/$project_name]
2699  set sim_file [file normalize $proj_dir/sim.conf]
2700 
2701  foreach list_file $list_files {
2702  set file_name [file tail $list_file]
2703  set simset_name [file rootname $file_name]
2704  set fp [open $list_file r]
2705  set file_data [read $fp]
2706  close $fp
2707  set data [split $file_data "\n"]
2708 
2709  set firstline [lindex $data 0]
2710  # Find simulator
2711  if {[regexp {^ *\# *Simulator} $firstline]} {
2712  set simulator_prop [regexp -all -inline {\S+} $firstline]
2713  set simulator [string tolower [lindex $simulator_prop end]]
2714  } else {
2715  Msg Warning "Simulator not set in $simset_name.sim. \
2716  The first line of $simset_name.sim should be #Simulator <SIMULATOR_NAME>,\
2717  where <SIMULATOR_NAME> can be xsim, questa, modelsim, ghdl, riviera, activehdl,\
2718  ies, or vcs, e.g. #Simulator questa.\
2719  Setting simulator by default to xsim."
2720  set simulator "xsim"
2721  }
2722  if {$simulator eq "skip_simulation"} {
2723  Msg Info "Skipping simulation for $simset_name"
2724  continue
2725  }
2726  if {($ghdl == 1 && $simulator != "ghdl") || ($ghdl == 0 && $simulator == "ghdl")} {
2727  continue
2728  }
2729 
2730  set SIM_PROPERTIES ""
2731  if {[file exists $sim_file] && $no_conf == 0} {
2732  set SIM_PROPERTIES [ReadConf $sim_file]
2733  }
2734 
2735  set global_sim_props [dict create]
2736  dict set global_sim_props "properties" [DictGet $SIM_PROPERTIES "sim"]
2737  dict set global_sim_props "generics" [DictGet $SIM_PROPERTIES "generics"]
2738  dict set global_sim_props "hog" [DictGet $SIM_PROPERTIES "hog"]
2739 
2740 
2741  set sim_dict [dict create]
2742  dict set sim_dict "simulator" $simulator
2743  if {[dict exists $SIM_PROPERTIES $simset_name]} {
2744  dict set sim_dict "properties" [DictGet $SIM_PROPERTIES $simset_name]
2745  dict set sim_dict "generics" [DictGet $SIM_PROPERTIES "$simset_name:generics"]
2746  dict set sim_dict "hog" [DictGet $SIM_PROPERTIES "$simset_name:hog"]
2747  } elseif {$no_conf == 0} {
2748  # Retrieve properties from .sim file
2749  set conf_dict [ReadConf $list_file]
2750  set sim_dict [MergeDict $sim_dict $conf_dict 0]
2751  }
2752  set sim_dict [MergeDict $sim_dict $global_sim_props 0]
2753  dict set simsets_dict $simset_name $sim_dict
2754  }
2755  return $simsets_dict
2756 }
2757 
2758 ## @brief Gets all custom <simset>:generics from sim.conf
2759 #
2760 # @param[in] proj_dir: the top folder of the project
2761 # @return nested dict with all <simset>:generics
2762 #
2763 proc GetSimsetGenericsFromConf {proj_dir} {
2764  set simsets_generics_dict [dict create]
2765  set top_dir "Top/$proj_dir"
2766  set conf_file "$top_dir/sim.conf"
2767  set conf_index 1
2768 
2769  if {[file exists $conf_file]} {
2770  set properties [ReadConf [lindex [GetConfFiles $top_dir] $conf_index]]
2771  # Filter the dictionary for keys ending with ":generics"
2772  set simsets_generics_dict [dict filter $properties key *:generics]
2773  } else {
2774  Msg Warning "File $conf_file not found."
2775  }
2776  return $simsets_generics_dict
2777 }
2778 
2779 
2780 ## Returns the group name from the project directory
2781 #
2782 # @param[in] proj_dir project directory
2783 # @param[in] repo_dir repository directory
2784 #
2785 # @return the group name without initial and final slashes
2786 #
2787 proc GetGroupName {proj_dir repo_dir} {
2788  if {[regexp {^(.*)/(Top|Projects)/+(.*?)/*$} $proj_dir dummy possible_repo_dir proj_or_top dir]} {
2789  # The Top or Project folder is in the root of a the git repository
2790  if {[file normalize $repo_dir] eq [file normalize $possible_repo_dir]} {
2791  set group [file dir $dir]
2792  if {$group == "."} {
2793  set group ""
2794  }
2795  } else {
2796  # The Top or Project folder is NOT in the root of a git repository
2797  Msg Warning "Project directory $proj_dir seems to be in $possible_repo_dir which is not a the main Git repository $repo_dir."
2798  }
2799  } else {
2800  Msg Warning "Could not parse project directory $proj_dir"
2801  set group ""
2802  }
2803  return $group
2804 }
2805 
2806 ## Get custom Hog describe for the project under investigation
2807 #
2808 # @param[in] proj_dir the top directory of the project (e.g. repo_path/Top/group/project)
2809 # @param[in] repo_path the main path of the repository
2810 #
2811 # @return the Hog describe of the project, with a "-dirty" suffix if the project files are not clean
2812 #
2813 proc GetHogDescribe {proj_dir {repo_path .}} {
2814  lassign [GetRepoVersions $proj_dir $repo_path] global_commit global_version
2815  if {$global_commit == 0} {
2816  # in case the repo is dirty, we use the last committed sha and add a -dirty suffix
2817  set new_sha "[string toupper [GetSHA]]"
2818  set suffix "-dirty"
2819  } else {
2820  set new_sha [string toupper $global_commit]
2821  set suffix ""
2822  }
2823  set describe "v[HexVersionToString $global_version]-$new_sha$suffix"
2824  return $describe
2825 }
2826 
2827 ## @brief Extract files, libraries and properties from the project's list files
2828 #
2829 # @param[in] args The arguments are <list_path> <repository path>[options]
2830 # * list_path path to the list file directory
2831 # Options:
2832 # * -list_files <List files> the file wildcard, if not specified all Hog list files will be looked for
2833 # * -sha_mode forwarded to ReadListFile, see there for info
2834 # * -ext_path <external path> path for external libraries forwarded to ReadListFile
2835 #
2836 # @return a list of 3 dictionaries: libraries and properties
2837 # - libraries has library name as keys and a list of filenames as values
2838 # - properties has file names as keys and a list of properties as values
2839 # - filesets has the fileset name as keys and the correspondent list of libraries as values (significant only for simulations)
2840 proc GetHogFiles {args} {
2841  if {[IsQuartus]} {
2842  load_package report
2843  if {[catch {package require cmdline} ERROR]} {
2844  puts "$ERROR\n If you are running this script on tclsh, you can fix this by installing 'tcllib'"
2845  return 0
2846  }
2847  }
2848 
2849 
2850  set parameters {
2851  {list_files.arg "" "The file wildcard, if not specified all Hog list files will be looked for."}
2852  {sha_mode "Forwarded to ReadListFile, see there for info."}
2853  {ext_path.arg "" "Path for the external libraries forwarded to ReadListFile."}
2854  {print_log "Forwarded to ReadListFile, see there for info."}
2855  }
2856  set usage "USAGE: GetHogFiles \[options\] <list path> <repository path>"
2857  if {[catch {array set options [cmdline::getoptions args $parameters $usage]}] || [llength $args] != 2} {
2858  Msg CriticalWarning [cmdline::usage $parameters $usage]
2859  return
2860  }
2861  set list_path [lindex $args 0]
2862  set repo_path [lindex $args 1]
2863 
2864  set list_files $options(list_files)
2865  set sha_mode $options(sha_mode)
2866  set ext_path $options(ext_path)
2867  set print_log $options(print_log)
2868 
2869  if {$sha_mode == 1} {
2870  set sha_mode_opt "-sha_mode"
2871  } else {
2872  set sha_mode_opt ""
2873  }
2874 
2875  if {$print_log == 1} {
2876  set print_log_opt "-print_log"
2877  } else {
2878  set print_log_opt ""
2879  }
2880 
2881 
2882  if {$list_files == ""} {
2883  set list_files {.src,.con,.sim,.ext}
2884  }
2885  set libraries [dict create]
2886  set properties [dict create]
2887  set list_files [glob -nocomplain -directory $list_path "*{$list_files}"]
2888  set filesets [dict create]
2889 
2890  foreach f $list_files {
2891  set ext [file extension $f]
2892  if {$ext == ".ext"} {
2893  lassign [ReadListFile {*}"$sha_mode_opt $print_log_opt $f $ext_path"] l p fs
2894  } else {
2895  lassign [ReadListFile {*}"$sha_mode_opt $print_log_opt $f $repo_path"] l p fs
2896  }
2897  set libraries [MergeDict $l $libraries]
2898  set properties [MergeDict $p $properties]
2899  Msg Debug "list file $f, filesets: $fs"
2900  set filesets [MergeDict $fs $filesets]
2901  Msg Debug "Merged filesets $filesets"
2902  }
2903 
2904  # Auto-discover HLS components from the project's hog.conf (no .src needed).
2905  # The list_path argument is conventionally <proj_dir>/list/, so hog.conf sits
2906  # one level up. For each [hls:<comp>] section with HLS_CONFIG=<path>, add the
2907  # cfg + everything it references to a synthesized "<comp>" library, and -- if
2908  # print_log is on -- show the file tree exactly like a .src would
2909  set proj_conf [file normalize [file join [file dirname $list_path] hog.conf]]
2910  if {[file exists $proj_conf]} {
2911  set hls_configs [GetHlsConfigsFromProjConf $proj_conf $repo_path]
2912  set already_tracked [dict create]
2913  dict for {libname libfiles} $libraries {
2914  foreach lf $libfiles {dict set already_tracked [file normalize $lf] 1}
2915  }
2916  dict for {comp_name cfg_abs} $hls_configs {
2917  if {[dict exists $already_tracked $cfg_abs]} {
2918  Msg Debug "HLS component '$comp_name': cfg $cfg_abs already tracked via a .src, skipping conf-based GetHogFiles discovery."
2919  continue
2920  }
2921  set lib_name "${comp_name}.src"
2922  dict lappend libraries $lib_name $cfg_abs
2923  dict set already_tracked $cfg_abs 1
2924  set hls_extras [ExpandHlsConfigFiles $cfg_abs]
2925  Msg Info "HLS component '$comp_name' (from hog.conf): tracking [expr {1 + [llength $hls_extras]}] file(s) via $cfg_abs"
2926  if {$print_log == 1} {
2927  Msg Status "\nhog.conf \[hls:$comp_name\] (auto-discovered)"
2928  set rel_cfg [Relative $repo_path $cfg_abs 1]
2929  if {$rel_cfg eq ""} {set rel_cfg $cfg_abs}
2930  if {[llength $hls_extras] == 0} {
2931  Msg Status "└── $rel_cfg"
2932  } else {
2933  Msg Status "├── $rel_cfg"
2934  Msg Status " Inside [file tail $cfg_abs] (HLS auto-expand):"
2935  }
2936  }
2937  set n_extras [llength $hls_extras]
2938  set i 0
2939  foreach hls_extra $hls_extras {
2940  incr i
2941  if {[dict exists $already_tracked $hls_extra]} {continue}
2942  dict lappend libraries $lib_name $hls_extra
2943  dict set already_tracked $hls_extra 1
2944  if {$print_log == 1} {
2945  if {$i == $n_extras} {set pad "└──"} else {set pad "├──"}
2946  set rel_extra [Relative [file dirname $cfg_abs] $hls_extra 1]
2947  if {$rel_extra eq ""} {set rel_extra $hls_extra}
2948  Msg Status " $pad $rel_extra"
2949  }
2950  }
2951  }
2952  }
2953 
2954  return [list $libraries $properties $filesets]
2955 }
2956 
2957 # @brief Get the IDE of a Hog project and returns the correct argument for the IDE cli command
2958 #
2959 # @param[in] proj_conf The project hog.conf file
2960 # @param[in] custom_ver If set, use this version instead of the one in the hog.conf
2961 proc GetIDECommand {proj_conf {custom_ver ""}} {
2962  if {$custom_ver ne ""} {
2963  set ide_name_and_ver [string tolower "$custom_ver"]
2964  } elseif {[file exists $proj_conf]} {
2965  set ide_name_and_ver [string tolower [GetIDEFromConf $proj_conf]]
2966  } else {
2967  Msg Error "Configuration file $proj_conf not found."
2968  }
2969 
2970  set ide_name [lindex [regexp -all -inline {\S+} $ide_name_and_ver] 0]
2971 
2972  if {$ide_name eq "vivado" || $ide_name eq "vivado_vitis_classic" || $ide_name eq "vivado_vitis_unified" || $ide_name eq "vitis_unified"} {
2973  set command "vivado"
2974  # A space after the before_tcl_script is important
2975  set before_tcl_script " -nojournal -nolog -mode batch -notrace -source "
2976  set after_tcl_script " -tclargs "
2977  set end_marker ""
2978  } elseif {$ide_name eq "planahead"} {
2979  set command "planAhead"
2980  # A space ater the before_tcl_script is important
2981  set before_tcl_script " -nojournal -nolog -mode batch -notrace -source "
2982  set after_tcl_script " -tclargs "
2983  set end_marker ""
2984  } elseif {$ide_name eq "quartus"} {
2985  set command "quartus_sh"
2986  # A space after the before_tcl_script is important
2987  set before_tcl_script " -t "
2988  set after_tcl_script " "
2989  set end_marker ""
2990  } elseif {$ide_name eq "libero"} {
2991  #I think we need quotes for libero, not sure...
2992 
2993  set command "libero"
2994  set before_tcl_script "SCRIPT:"
2995  set after_tcl_script " SCRIPT_ARGS:\""
2996  set end_marker "\""
2997  } elseif {$ide_name eq "diamond"} {
2998  set command "diamondc"
2999  set before_tcl_script " "
3000  set after_tcl_script " "
3001  set end_marker ""
3002  } elseif {$ide_name eq "vitis_classic"} {
3003  set command "xsct"
3004  # A space after the before_tcl_script is important
3005  set before_tcl_script ""
3006  set after_tcl_script " "
3007  set end_marker ""
3008  } elseif {$ide_name eq "ghdl"} {
3009  set command "ghdl"
3010  set before_tcl_script " "
3011  set after_tcl_script " "
3012  set end_marker ""
3013  } else {
3014  Msg Error "IDE: $ide_name not known."
3015  }
3016 
3017  return [list $command $before_tcl_script $after_tcl_script $end_marker]
3018 }
3019 
3020 ## Get the IDE (Vivado,Quartus,PlanAhead,Libero) version from the conf file she-bang
3021 #
3022 # @param[in] conf_file The hog.conf file
3023 proc GetIDEFromConf {conf_file} {
3024  set f [open $conf_file "r"]
3025  set line [gets $f]
3026  close $f
3027  if {[regexp -all {^\# *(\w*) *(vitis_(?:classic|unified))? *(\d+\.\d+(?:\.\d+)?(?:\.\d+)?)?(_.*)? *$} $line dummy ide vitisflag version patch]} {
3028  if {[info exists vitisflag] && $vitisflag != ""} {
3029  set ide "${ide}_${vitisflag}"
3030  }
3031 
3032  if {[info exists version] && $version != ""} {
3033  set ver $version
3034  } else {
3035  set ver 0.0.0
3036  }
3037  # what shall we do with $patch? ignored for the time being
3038  set ret [list $ide $ver]
3039  } else {
3040  Msg CriticalWarning "The first line of hog.conf should be \#<IDE name> <version>, \
3041  where <IDE name>. is quartus, vivado, planahead, libero, diamond or ghdl, \
3042  and <version> the tool version, e.g. \#vivado 2020.2. Will assume vivado."
3043  set ret [list "vivado" "0.0.0"]
3044  }
3045 
3046  return $ret
3047 }
3048 
3049 # @brief Returns the name of the running IDE
3050 proc GetIDEName {} {
3051  if {[IsISE]} {
3052  return "ISE/PlanAhead"
3053  } elseif {[IsVivado]} {
3054  return "Vivado"
3055  } elseif {[IsQuartus]} {
3056  return "Quartus"
3057  } elseif {[IsLibero]} {
3058  return "Libero"
3059  } elseif {[IsDiamond]} {
3060  return "Diamond"
3061  } else {
3062  return ""
3063  }
3064 }
3065 
3066 ## Returns the version of the IDE (Vivado,Quartus,PlanAhead,Libero) in use
3067 #
3068 # @return the version in string format, e.g. 2020.2
3069 #
3070 proc GetIDEVersion {} {
3071  if {[IsXilinx]} {
3072  # Vivado or planAhead
3073  regexp {\d+\.\d+(\.\d+)?} [version -short] ver
3074  # This regex will cut away anything after the numbers, useful for patched version 2020.1_AR75210
3075  } elseif {[IsQuartus]} {
3076  # Quartus
3077  global quartus
3078  regexp {[\.0-9]+} $quartus(version) ver
3079  } elseif {[IsLibero]} {
3080  # Libero
3081  set ver [get_libero_version]
3082  } elseif {[IsDiamond]} {
3083  # Diamond
3084  regexp {\d+\.\d+(\.\d+)?} [sys_install version] ver
3085  } elseif {[IsVitisClassic]} {
3086  # Vitis Classic
3087  regexp {\d+\.\d+(\.\d+)?} [version] ver
3088  } elseif {[IsVitisUnified]} {
3089  # Vitis Unified
3090  set vitis_output [exec vitis --version 2>@1]
3091  regexp {[Vv]itis\s+v?(\d+\.\d+(?:\.\d+)?)} $vitis_output -> ver
3092  } else {
3093  set ver "0.0.0"
3094  }
3095  return $ver
3096 }
3097 
3098 
3099 ## @brief Returns the real file linked by a soft link
3100 #
3101 # If the provided file is not a soft link, it will give a Warning and return an empty string.
3102 # If the link is broken, will give a warning but still return the linked file
3103 #
3104 # @param[in] link_file The soft link file
3105 proc GetLinkedFile {link_file} {
3106  if {[file type $link_file] eq "link"} {
3107  if {[OS] == "windows"} {
3108  #on windows we need to use readlink because Tcl is broken
3109  lassign [ExecuteRet realpath $link_file] ret msg
3110  lassign [ExecuteRet cygpath -m $msg] ret2 msg2
3111  if {$ret == 0 && $ret2 == 0} {
3112  set real_file $msg2
3113  Msg Debug "Found link file $link_file on Windows, the linked file is: $real_file"
3114  } else {
3115  Msg CriticalWarning "[file normalize $link_file] is a soft link. \
3116  Soft link are not supported on Windows and readlink.exe or cygpath.exe did not work: readlink=$ret: $msg, cygpath=$ret2: $msg2."
3117  set real_file $link_file
3118  }
3119  } else {
3120  #on linux Tcl just works
3121  set linked_file [file link $link_file]
3122  set real_file [file normalize [file dirname $link_file]/$linked_file]
3123  }
3124 
3125  if {![file exists $real_file]} {
3126  Msg Warning "$link_file is a broken link, because the linked file: $real_file does not exist."
3127  }
3128  } else {
3129  Msg Warning "$link file is not a soft link"
3130  set real_file $link_file
3131  }
3132  return $real_file
3133 }
3134 
3135 ## @brief Gets MAX number of Threads property from property.conf file in Top/$proj_name directory.
3136 #
3137 # If property is not set returns default = 1
3138 #
3139 # @param[in] proj_dir: the top folder of the project
3140 #
3141 # @return 1 if property is not set else the value of MaxThreads
3142 #
3143 proc GetMaxThreads {proj_dir} {
3144  set maxThreads 1
3145  if {[file exists $proj_dir/hog.conf]} {
3146  set properties [ReadConf [lindex [GetConfFiles $proj_dir] 0]]
3147  if {[dict exists $properties parameters]} {
3148  set propDict [dict get $properties parameters]
3149  if {[dict exists $propDict MAX_THREADS]} {
3150  set maxThreads [dict get $propDict MAX_THREADS]
3151  }
3152  }
3153  } else {
3154  Msg Warning "File $proj_dir/hog.conf not found. Max threads will be set to default value 1"
3155  }
3156  return $maxThreads
3157 }
3158 
3159 
3160 ## @brief Get a list of all modified the files matching then pattern
3161 #
3162 # @param[in] repo_path the path of the git repository
3163 # @param[in] pattern the pattern with wildcards that files should match
3164 #
3165 # @return a list of all modified files matching the pattern
3166 #
3167 proc GetModifiedFiles {{repo_path "."} {pattern "."}} {
3168  set old_path [pwd]
3169  cd $repo_path
3170  set ret [Git "ls-files --modified $pattern"]
3171  cd $old_path
3172  return $ret
3173 }
3174 
3175 # @brief Gets the command argv list and returns a list of
3176 # options and arguments
3177 # @param[in] argv The command input arguments
3178 # @param[in] parameters The command input parameters
3179 proc GetOptions {argv parameters} {
3180  # Get Options from argv
3181  set arg_list [list]
3182  set param_list [list]
3183  set option_list [list]
3184 
3185  foreach p $parameters {
3186  lappend param_list [lindex $p 0]
3187  }
3188 
3189  set index 0
3190  while {$index < [llength $argv]} {
3191  set arg [lindex $argv $index]
3192  if {[string first - $arg] == 0} {
3193  set option [string trimleft $arg "-"]
3194  incr index
3195  lappend option_list $arg
3196  if {[lsearch -regex $param_list "$option\[.arg]?"] >= 0} {
3197  if {[lsearch -regex $param_list "$option\[.arg]"] >= 0} {
3198  lappend option_list [lindex $argv $index]
3199  incr index
3200  }
3201  }
3202  } else {
3203  lappend arg_list $arg
3204  incr index
3205  }
3206  }
3207  Msg Debug "Argv: $argv"
3208  Msg Debug "Options: $option_list"
3209  Msg Debug "Arguments: $arg_list"
3210  return [list $option_list $arg_list]
3211 }
3212 
3213 # return [list $libraries $properties $simlibraries $constraints $srcsets $simsets $consets]
3214 ## @ brief Returns a list of 7 dictionaries: libraries, properties, constraints, and filesets for sources and simulations
3215 #
3216 # The returned dictionaries are libraries, properties, simlibraries, constraints, srcsets, simsets, consets
3217 # - libraries and simlibraries have the library name as keys and a list of filenames as values
3218 # - properties has as file names as keys and a list of properties as values
3219 # - constraints is a dictionary with a single key (sources.con) and a list of constraint files as value
3220 # - srcsets is a dictionary with a fileset name as a key (e.g. sources_1) and a list of libraries as value
3221 # - simsets is a dictionary with a simset name as a key (e.g. sim_1) and a list of libraries as value
3222 # - consets is a dictionary with a constraints file sets name as a key (e.g. constr_1) and a list of constraint "libraries" (sources.con)
3223 #
3224 # Files, libraries and properties are extracted from the current project
3225 #
3226 # @param[in] project_file The project file (for Libero and Diamond)
3227 # @return A list of 7 dictionaries: libraries, properties, constraints, and filesets for sources and simulations
3228 proc GetProjectFiles {{project_file ""}} {
3229  set libraries [dict create]
3230  set simlibraries [dict create]
3231  set constraints [dict create]
3232  set properties [dict create]
3233  set consets [dict create]
3234  set srcsets [dict create]
3235  set simsets [dict create]
3236 
3237  if {[IsVivado]} {
3238  set all_filesets [get_filesets]
3239  set simulator [get_property target_simulator [current_project]]
3240  set top [get_property "top" [current_fileset]]
3241  set topfile [GetTopFile]
3242  dict lappend properties $topfile "top=$top"
3243 
3244  foreach fs $all_filesets {
3245  if {$fs == "utils_1"} {
3246  # Skipping utility fileset
3247  continue
3248  }
3249 
3250  set all_files [get_files -quiet -of_objects [get_filesets $fs]]
3251  set fs_type [get_property FILESET_TYPE [get_filesets $fs]]
3252 
3253  if {$fs_type == "BlockSrcs"} {
3254  # Vivado creates for each ip a blockset... Let's redirect to sources_1
3255  set dict_fs "sources_1"
3256  } else {
3257  set dict_fs $fs
3258  }
3259  foreach f $all_files {
3260  # Ignore files that are part of the vivado/planahead project but would not be reflected
3261  # in list files (e.g. generated products from ip cores)
3262  set ignore 0
3263  # Generated files point to a parent composite file;
3264  # planahead does not have an IS_GENERATED property
3265  if {[IsInList "IS_GENERATED" [list_property [GetFile $f $fs]]]} {
3266  if {[lindex [get_property IS_GENERATED [GetFile $f $fs]] 0] != 0} {
3267  set ignore 1
3268  }
3269  }
3270 
3271  if {[get_property FILE_TYPE [GetFile $f $fs]] == "Configuration Files"} {
3272  set ignore 1
3273  }
3274 
3275 
3276  if {[IsInList "CORE_CONTAINER" [list_property [GetFile $f $fs]]]} {
3277  if {[get_property CORE_CONTAINER [GetFile $f $fs]] != ""} {
3278  if {[file extension $f] == ".xcix"} {
3279  set f [get_property CORE_CONTAINER [GetFile $f $fs]]
3280  } else {
3281  set ignore 1
3282  }
3283  }
3284  }
3285 
3286  if {[IsInList "SCOPED_TO_REF" [list_property [GetFile $f $fs]]]} {
3287  if {[get_property SCOPED_TO_REF [GetFile $f $fs]] != ""} {
3288  dict lappend properties $f "scoped_to_ref=[get_property SCOPED_TO_REF [GetFile $f $fs]]"
3289  }
3290  }
3291 
3292  if {[IsInList "SCOPED_TO_CELLS" [list_property [GetFile $f $fs]]]} {
3293  if {[get_property SCOPED_TO_CELLS [GetFile $f $fs]] != ""} {
3294  dict lappend properties $f "scoped_to_cells=[regsub " " [get_property SCOPED_TO_CELLS [GetFile $f $fs]] ","]"
3295  }
3296  }
3297 
3298  if {[IsInList "PARENT_COMPOSITE_FILE" [list_property [GetFile $f $fs]]]} {
3299  set ignore 1
3300  }
3301 
3302  # Ignore nocattrs.dat for Versal
3303  if {[file tail $f] == "nocattrs.dat"} {
3304  set ignore 1
3305  }
3306 
3307  if {!$ignore} {
3308  if {[file extension $f] != ".coe"} {
3309  set f [file normalize $f]
3310  }
3311  lappend files $f
3312  set type [get_property FILE_TYPE [GetFile $f $fs]]
3313  # Added a -quiet because some files (.v, .sv) don't have a library
3314  set lib [get_property -quiet LIBRARY [GetFile $f $fs]]
3315 
3316  # Type can be complex like VHDL 2008, in that case we want the second part to be a property
3317  Msg Debug "File $f Extension [file extension $f] Type [lindex $type 0]"
3318 
3319  if {[string equal [lindex $type 0] "VHDL"] && [llength $type] == 1} {
3320  set prop "93"
3321  } elseif {[string equal [lindex $type 0] "Block"] && [string equal [lindex $type 1] "Designs"]} {
3322  set type "IP"
3323  set prop ""
3324  } elseif {[string equal $type "SystemVerilog"] && [file extension $f] != ".sv" && [file extension $f] != ".svp"} {
3325  set prop "SystemVerilog"
3326  } elseif {[string equal [lindex $type 0] "XDC"] && [file extension $f] != ".xdc"} {
3327  set prop "XDC"
3328  } elseif {[string equal $type "Verilog Header"] && [file extension $f] != ".vh" && [file extension $f] != ".svh"} {
3329  set prop "verilog_header"
3330  } elseif {[string equal $type "Verilog Template"] && [file extension $f] == ".v" && [file extension $f] != ".sv"} {
3331  set prop "verilog_template"
3332  } else {
3333  set type [lindex $type 0]
3334  set prop ""
3335  }
3336  #If type is "VHDL 2008" we will keep only VHDL
3337  if {![string equal $prop ""]} {
3338  dict lappend properties $f $prop
3339  }
3340  # check where the file is used and add it to prop
3341  if {[string equal $fs_type "SimulationSrcs"]} {
3342  # Simulation sources
3343  if {[string equal $type "VHDL"]} {
3344  set library "${lib}.sim"
3345  } else {
3346  set library "others.sim"
3347  }
3348 
3349  if {[IsInList $library [DictGet $simsets $dict_fs]] == 0} {
3350  dict lappend simsets $dict_fs $library
3351  }
3352 
3353  dict lappend simlibraries $library $f
3354  } elseif {[string equal $type "VHDL"]} {
3355  # VHDL files (both 2008 and 93)
3356  if {[IsInList "${lib}.src" [DictGet $srcsets $dict_fs]] == 0} {
3357  dict lappend srcsets $dict_fs "${lib}.src"
3358  }
3359  dict lappend libraries "${lib}.src" $f
3360  } elseif {[string first "IP" $type] != -1} {
3361  # IPs
3362  if {[IsInList "ips.src" [DictGet $srcsets $dict_fs]] == 0} {
3363  dict lappend srcsets $dict_fs "ips.src"
3364  }
3365  dict lappend libraries "ips.src" $f
3366  Msg Debug "Appending $f to ips.src"
3367  } elseif {[string equal $fs_type "Constrs"]} {
3368  # Constraints
3369  if {[IsInList "sources.con" [DictGet $consets $dict_fs]] == 0} {
3370  dict lappend consets $dict_fs "sources.con"
3371  }
3372  dict lappend constraints "sources.con" $f
3373  } else {
3374  # Verilog and other files
3375  if {[IsInList "others.src" [DictGet $srcsets $dict_fs]] == 0} {
3376  dict lappend srcsets $dict_fs "others.src"
3377  }
3378  dict lappend libraries "others.src" $f
3379  Msg Debug "Appending $f to others.src"
3380  }
3381 
3382  if {[lindex [get_property -quiet used_in_synthesis [GetFile $f $fs]] 0] == 0} {
3383  dict lappend properties $f "nosynth"
3384  }
3385  if {[lindex [get_property -quiet used_in_implementation [GetFile $f $fs]] 0] == 0} {
3386  dict lappend properties $f "noimpl"
3387  }
3388  if {[lindex [get_property -quiet used_in_simulation [GetFile $f $fs]] 0] == 0} {
3389  dict lappend properties $f "nosim"
3390  }
3391  if {[lindex [get_property -quiet IS_MANAGED [GetFile $f $fs]] 0] == 0 && [file extension $f] != ".xcix"} {
3392  dict lappend properties $f "locked"
3393  }
3394  }
3395  }
3396  }
3397 
3398  dict lappend properties "Simulator" [get_property target_simulator [current_project]]
3399  } elseif {[IsLibero] || [IsSynplify]} {
3400  # Open the project file
3401  set file [open $project_file r]
3402  set in_file_manager 0
3403  set top ""
3404  while {[gets $file line] >= 0} {
3405  # Detect the ActiveRoot (Top) module
3406  if {[regexp {^KEY ActiveRoot \"([^\"]+)\"} $line -> value]} {
3407  set top [string range $value 0 [expr {[string first "::" $value] - 1}]]
3408  }
3409 
3410  # Detect the start of the FileManager section
3411  if {[regexp {^LIST FileManager} $line]} {
3412  set in_file_manager 1
3413  continue
3414  }
3415 
3416  # Detect the end of the FileManager section
3417  if {$in_file_manager && [regexp {^ENDLIST} $line]} {
3418  break
3419  }
3420 
3421  # Extract file paths from the VALUE entries
3422  if {$in_file_manager && [regexp {^VALUE \"([^\"]+)} $line -> value]} {
3423  # lappend source_files [remove_after_comma $filepath]
3424  # set file_path ""
3425  lassign [split $value ,] file_path file_type
3426  # Extract file properties
3427  set parent_file ""
3428  set library "others"
3429  while {[gets $file line] >= 0} {
3430  if {$line == "ENDFILE"} {
3431  break
3432  }
3433  regexp {^LIBRARY=\"([^\"]+)} $line -> library
3434  regexp {^PARENT=\"([^\"]+)} $line -> parent_file
3435  }
3436  Msg Debug "Found file ${file_path} in project.."
3437  if {$parent_file == ""} {
3438  if {$file_type == "hdl"} {
3439  # VHDL files (both 2008 and 93)
3440  if {[IsInList "${library}.src" [DictGet $srcsets "sources_1"]] == 0} {
3441  dict lappend srcsets "sources_1" "${library}.src"
3442  }
3443  dict lappend libraries "${library}.src" $file_path
3444  # Check if file is top_module in project
3445  Msg Debug "File $file_path module [GetModuleName $file_path]"
3446 
3447  if {[GetModuleName $file_path] == [string tolower $top] && $top != ""} {
3448  Msg Debug "Found top module $top in $file_path"
3449  dict lappend properties $file_path "top=$top"
3450  }
3451  } elseif {$file_type == "tb_hdl"} {
3452  if {[IsInList "${library}.sim" [DictGet $simsets "sim_1"]] == 0} {
3453  dict lappend simsets "sim_1" "${library}.sim"
3454  }
3455  dict lappend simlibraries "${library}.sim" $file_path
3456  } elseif {$file_type == "io_pdc" || $file_type == "sdc"} {
3457  if {[IsInList "sources.con" [DictGet $consets "constrs_1"]] == 0} {
3458  dict lappend consets "constrs_1" "sources.con"
3459  }
3460  dict lappend constraints "sources.con" $file_path
3461  }
3462  }
3463  }
3464  }
3465  } elseif {[IsDiamond]} {
3466  # Open the Diamond XML project file content
3467  set fileData [read [open $project_file]]
3468 
3469  set project_path [file dirname $project_file]
3470 
3471  # Remove XML declaration
3472  regsub {<\?xml.*\?>} $fileData "" fileData
3473 
3474  # Extract the Implementation block
3475  regexp {<Implementation.*?>(.*)</Implementation>} $fileData -> implementationContent
3476 
3477  # Extract each Source block one by one
3478  set sources {}
3479  set sourceRegex {<Source name="([^"]*?)" type="([^"]*?)" type_short="([^"]*?)".*?>(.*?)</Source>}
3480 
3481  set optionsRegex {<Options(.*?)\/>}
3482  regexp $optionsRegex $implementationContent -> prj_options
3483  foreach option $prj_options {
3484  if {[regexp {^top=\"([^\"]+)\"} $option match result]} {
3485  set top $result
3486  }
3487  }
3488 
3489  while {[regexp $sourceRegex $implementationContent match name type type_short optionsContent]} {
3490  Msg Debug "Found file ${name} in project..."
3491  set file_path [file normalize $project_path/$name]
3492  # Extract the Options attributes
3493  set optionsRegex {<Options(.*?)\/>}
3494  regexp $optionsRegex $optionsContent -> options
3495  set library "others"
3496  set isSV 0
3497  foreach option $options {
3498  if {[string first "System Verilog" $option]} {
3499  set isSV 1
3500  }
3501  if {[regexp {^lib=\"([^\"]+)\"} $option match1 result]} {
3502  set library $result
3503  }
3504  }
3505  set ext ".src"
3506  if {[regexp {syn_sim="([^"]*?)"} $match match_sim simonly]} {
3507  set ext ".sim"
3508  }
3509 
3510  # Append VHDL files
3511  if {$type_short == "VHDL" || $type_short == "Verilog" || $type_short == "IPX"} {
3512  if {$ext == ".src"} {
3513  if {[IsInList "${library}${ext}" [DictGet $srcsets "sources_1"]] == 0} {
3514  dict lappend srcsets "sources_1" "${library}${ext}"
3515  }
3516  dict lappend libraries "${library}${ext}" $file_path
3517  } elseif {$ext == ".sim"} {
3518  if {[IsInList "${library}.sim" [DictGet $simsets "sim_1"]] == 0} {
3519  dict lappend simsets "sim_1" "${library}.sim"
3520  }
3521  dict lappend simlibraries "${library}.sim" $file_path
3522  }
3523  # Check if file is top_module in project
3524  Msg Debug "File $file_path module [GetModuleName $file_path]"
3525 
3526  if {[GetModuleName $file_path] == $top && $top != ""} {
3527  Msg Debug "Found top module $top in $file_path"
3528  dict lappend properties $file_path "top=$top"
3529  }
3530  } elseif {$type_short == "SDC"} {
3531  if {[IsInList "sources.con" [DictGet $consets "constrs_1"]] == 0} {
3532  dict lappend consets "constrs_1" "sources.con"
3533  }
3534  dict lappend constraints "sources.con" $file_path
3535  }
3536 
3537  # Remove the processed Source block from the implementation content
3538  regsub -- $match $implementationContent "" implementationContent
3539  }
3540  }
3541  return [list $libraries $properties $simlibraries $constraints $srcsets $simsets $consets]
3542 }
3543 
3544 #"
3545 ## Get the Project flavour
3546 #
3547 # @param[in] proj_name The project name
3548 proc GetProjectFlavour {proj_name} {
3549  # Calculating flavour if any
3550  set flavour [string map {. ""} [file extension $proj_name]]
3551  if {$flavour != ""} {
3552  if {[string is integer $flavour]} {
3553  Msg Info "Project $proj_name has flavour = $flavour, the generic variable FLAVOUR will be set to $flavour"
3554  } else {
3555  Msg Warning "Project name has a unexpected non numeric extension, flavour will be set to -1"
3556  set flavour -1
3557  }
3558  } else {
3559  set flavour -1
3560  }
3561  return $flavour
3562 }
3563 
3564 ## Get the project version
3565 #
3566 # @param[in] proj_dir: The top folder of the project of which all the version must be calculated
3567 # @param[in] repo_path: The top folder of the repository
3568 # @param[in] ext_path: path for external libraries
3569 # @param[in] sim: if enabled, check the version also for the simulation files
3570 #
3571 # @return returns the project version
3572 proc GetProjectVersion {proj_dir repo_path {ext_path ""} {sim 0}} {
3573  if {![file exists $proj_dir]} {
3574  Msg CriticalWarning "$proj_dir not found"
3575  return -1
3576  }
3577  set old_dir [pwd]
3578  cd $proj_dir
3579 
3580  #The latest version the repository
3581  lassign [GitRet {describe --tags --abbrev=0 --match "v*"}] ret result
3582  if {$ret != 0} {
3583  Msg CriticalWarning "No Hog versioning tags (v*) in repo"
3584  return -1
3585  }
3586 
3587  set v_last [ExtractVersionFromTag $result]
3588  lassign [GetRepoVersions $proj_dir $repo_path $ext_path $sim] sha ver
3589  if {$sha == 0} {
3590  Msg Warning "Repository is not clean"
3591  cd $old_dir
3592  return -1
3593  }
3594 
3595  #The project version
3596  set v_proj [ExtractVersionFromTag v[HexVersionToString $ver]]
3597  set comp [CompareVersions $v_proj $v_last]
3598  Msg Debug "Project version $v_proj, latest tag $v_last"
3599  if {$comp == 1} {
3600  Msg Debug "The specified project was modified since official version."
3601  set ret 0
3602  } else {
3603  set ret v[HexVersionToString $ver]
3604  }
3605 
3606  if {$comp == 0} {
3607  Msg Info "The specified project was modified in the latest official version $ret"
3608  } elseif {$comp == -1} {
3609  Msg Info "The specified project was modified in a past official version $ret"
3610  }
3611 
3612  cd $old_dir
3613  return $ret
3614 }
3615 
3616 ## Get the versions for all libraries, submodules, etc. for a given project
3617 #
3618 # @param[in] proj_dir: The project directory containing the conf file or the the tcl file
3619 # @param[in] repo_path: top path of the repository
3620 # @param[in] ext_path: path for external libraries
3621 # @param[in] sim: if enabled, check the version also for the simulation files
3622 #
3623 # @return a list containing all the versions: global, top (hog.conf, pre and post tcl scripts, etc.), constraints,
3624 # libraries, submodules, external, ipbus xml, user ip repos
3625 proc GetRepoVersions {proj_dir repo_path {ext_path ""} {sim 0}} {
3626  if {[catch {package require cmdline} ERROR]} {
3627  puts "$ERROR\n If you are running this script on tclsh, you can fix this by installing 'tcllib'"
3628  return 1
3629  }
3630 
3631  set old_path [pwd]
3632  set conf_files [GetConfFiles $proj_dir]
3633 
3634  # This will be the list of all the SHAs of this project, the most recent will be picked up as GLOBAL SHA
3635  set SHAs ""
3636  set versions ""
3637 
3638  # Hog submodule
3639  cd $repo_path
3640 
3641  # Append the SHA in which Hog submodule was changed, not the submodule SHA
3642  lappend SHAs [GetSHA {Hog}]
3643  lappend versions [GetVerFromSHA $SHAs $repo_path]
3644 
3645  cd "$repo_path/Hog"
3646  if {[Git {status --untracked-files=no --porcelain}] eq ""} {
3647  Msg Debug "Hog submodule [pwd] clean."
3648  lassign [GetVer ./] hog_ver hog_hash
3649  } else {
3650  Msg CriticalWarning "Hog submodule [pwd] not clean, commit hash will be set to 0."
3651  set hog_hash "0000000"
3652  set hog_ver "00000000"
3653  }
3654 
3655  cd $repo_path
3656  # Collect all project-relevant files; the clean check will be deferred until all files are known
3657  # set project_files $conf_files
3658  foreach conf_file $conf_files {
3659  if {[string match "$repo_path/*" $conf_file]} {
3660  set conf_file [string replace $conf_file 0 [string length $repo_path]]
3661  }
3662  lappend project_files $conf_file
3663  }
3664 
3665  lappend project_files Hog
3666 
3667  # Top project directory
3668  lassign [GetVer [join $conf_files]] top_ver top_hash
3669  lappend SHAs $top_hash
3670  lappend versions $top_ver
3671 
3672  # Read list files
3673  set libs ""
3674  set vers ""
3675  set hashes ""
3676  # Specify sha_mode 1 for GetHogFiles to get all the files, including the list-files themselves
3677  lassign [GetHogFiles -list_files "*.src" -sha_mode "$proj_dir/list/" $repo_path] src_files dummy
3678  dict for {f files} $src_files {
3679  # library names have a .src extension in values returned by GetHogFiles
3680  set name [file rootname [file tail $f]]
3681  if {[file ext $f] == ".oth"} {
3682  set name "OTHERS"
3683  }
3684  lassign [GetVer $files] ver hash
3685  # Msg Info "Found source list file $f, version: $ver commit SHA: $hash"
3686  lappend libs $name
3687  lappend versions $ver
3688  lappend vers $ver
3689  lappend hashes $hash
3690  lappend SHAs $hash
3691  set relative_files ""
3692  foreach fil $files {
3693  if {[string match "$repo_path/*" $fil]} {
3694  set fil [string replace $fil 0 [string length $repo_path]]
3695  }
3696  lappend relative_files $fil
3697  }
3698 
3699  lappend project_files $f {*}$relative_files
3700  }
3701 
3702  # Read constraint list files
3703  set cons_hashes ""
3704  # Specify sha_mode 1 for GetHogFiles to get all the files, including the list-files themselves
3705  lassign [GetHogFiles -list_files "*.con" -sha_mode "$proj_dir/list/" $repo_path] cons_files dummy
3706  dict for {f files} $cons_files {
3707  #library names have a .con extension in values returned by GetHogFiles
3708  set name [file rootname [file tail $f]]
3709  lassign [GetVer $files] ver hash
3710  #Msg Info "Found constraint list file $f, version: $ver commit SHA: $hash"
3711  if {$hash eq ""} {
3712  Msg CriticalWarning "Constraints file $f not found in Git."
3713  }
3714  lappend cons_hashes $hash
3715  lappend SHAs $hash
3716  lappend versions $ver
3717  set relative_files ""
3718  foreach fil $files {
3719  if {[string match "$repo_path/*" $fil]} {
3720  set fil [string replace $fil 0 [string length $repo_path]]
3721  }
3722  lappend relative_files $fil
3723  }
3724  lappend project_files $f {*}$relative_files
3725  }
3726 
3727  # Read simulation list files
3728  if {$sim == 1} {
3729  set sim_hashes ""
3730  # Specify sha_mode 1 for GetHogFiles to get all the files, including the list-files themselves
3731  lassign [GetHogFiles -list_files "*.sim" -sha_mode "$proj_dir/list/" $repo_path] sim_files dummy
3732  dict for {f files} $sim_files {
3733  #library names have a .sim extension in values returned by GetHogFiles
3734  set name [file rootname [file tail $f]]
3735  lassign [GetVer $files] ver hash
3736  #Msg Info "Found simulation list file $f, version: $ver commit SHA: $hash"
3737  lappend sim_hashes $hash
3738  lappend SHAs $hash
3739  lappend versions $ver
3740  set relative_files ""
3741  foreach fil $files {
3742  if {[string match "$repo_path/*" $fil]} {
3743  set fil [string replace $fil 0 [string length $repo_path]]
3744  }
3745  lappend relative_files $fil
3746  }
3747  lappend project_files $f {*}$relative_files
3748  }
3749  }
3750 
3751 
3752  #Of all the constraints we get the most recent
3753  if {[IsInList {} $cons_hashes]} {
3754  #" Fake comment for Visual Code Studio
3755  Msg CriticalWarning "No hashes found for constraints files (not in git)"
3756  set cons_hash ""
3757  } else {
3758  set cons_hash [string tolower [Git "log --format=%h -1 $cons_hashes"]]
3759  }
3760  set cons_ver [GetVerFromSHA $cons_hash $repo_path]
3761  #Msg Info "Among all the constraint list files, if more than one, the most recent version was chosen: $cons_ver commit SHA: $cons_hash"
3762 
3763  # Read external library files
3764  set ext_hashes ""
3765  set ext_files [glob -nocomplain "$proj_dir/list/*.ext"]
3766  set ext_names ""
3767 
3768  foreach f $ext_files {
3769  set name [file rootname [file tail $f]]
3770  set hash [GetSHA $f]
3771  #Msg Info "Found source file $f, commit SHA: $hash"
3772  lappend ext_names $name
3773  lappend ext_hashes $hash
3774  lappend SHAs $hash
3775  set ext_ver [GetVerFromSHA $hash $repo_path]
3776  lappend versions $ext_ver
3777  lappend project_files $f
3778 
3779  set fp [open $f r]
3780  set file_data [read $fp]
3781  close $fp
3782  set data [split $file_data "\n"]
3783  #Msg Info "Checking checksums of external library files in $f"
3784  foreach line $data {
3785  if {![regexp {^ *$} $line] & ![regexp {^ *\#} $line]} {
3786  #Exclude empty lines and comments
3787  set file_and_prop [regexp -all -inline {\S+} $line]
3788  set hdlfile [lindex $file_and_prop 0]
3789  set hdlfile $ext_path/$hdlfile
3790  if {[file exists $hdlfile]} {
3791  set hash [lindex $file_and_prop 1]
3792  set current_hash [Md5Sum $hdlfile]
3793  if {[string first $hash $current_hash] == -1} {
3794  Msg CriticalWarning "File $hdlfile has a wrong hash. Current checksum: $current_hash, expected: $hash"
3795  }
3796  }
3797  }
3798  }
3799  }
3800 
3801  # Ipbus XML
3802  if {[llength [glob -nocomplain $proj_dir/list/*.ipb]] > 0} {
3803  #Msg Info "Found IPbus XML list file, evaluating version and SHA of listed files..."
3804  lassign [GetHogFiles -list_files "*.ipb" -sha_mode "$proj_dir/list/" $repo_path] xml_files dummy
3805  set xml_source_files [dict get $xml_files "xml.ipb"]
3806  lassign [GetVer $xml_source_files] xml_ver xml_hash
3807  lappend SHAs $xml_hash
3808  lappend versions $xml_ver
3809  set relative_files ""
3810  foreach fil $xml_source_files {
3811  if {[string match "$repo_path/*" $fil]} {
3812  set fil [string replace $fil 0 [string length $repo_path]]
3813  }
3814  lappend relative_files $fil
3815  }
3816  lappend project_files {*}[glob $proj_dir/list/*.ipb] {*}$relative_files
3817  #Msg Info "Found IPbus XML SHA: $xml_hash and version: $xml_ver."
3818  } else {
3819  Msg Debug "This project does not use IPbus XMLs"
3820  set xml_ver ""
3821  set xml_hash ""
3822  }
3823 
3824  set user_ip_repos ""
3825  set user_ip_repo_hashes ""
3826  set user_ip_repo_vers ""
3827  # User IP Repository (Vivado only, hog.conf only)
3828  if {[file exists [lindex $conf_files 0]]} {
3829  set PROPERTIES [ReadConf [lindex $conf_files 0]]
3830  if {[dict exists $PROPERTIES main]} {
3831  set main [dict get $PROPERTIES main]
3832  dict for {p v} $main {
3833  if {[string tolower $p] == "ip_repo_paths"} {
3834  foreach repo $v {
3835  if {[file isdirectory "$repo_path/$repo"]} {
3836  set repo_file_list [glob -nocomplain "$repo_path/$repo/*"]
3837  if {[llength $repo_file_list] == 0} {
3838  Msg Warning "IP_REPO_PATHS property set to $repo in hog.conf but directory is empty."
3839  } else {
3840  lappend user_ip_repos "$repo_path/$repo"
3841  }
3842  }
3843  }
3844  }
3845  }
3846  }
3847 
3848  # For each defined IP repository get hash and version if directory exists and not empty
3849  foreach repo $user_ip_repos {
3850  if {[file isdirectory $repo]} {
3851  set repo_file_list [glob -nocomplain "$repo/*"]
3852  if {[llength $repo_file_list] != 0} {
3853  lassign [GetVer $repo] ver sha
3854  lappend user_ip_repo_hashes $sha
3855  lappend user_ip_repo_vers $ver
3856  lappend versions $ver
3857  lappend project_files $repo
3858  } else {
3859  Msg Warning "IP_REPO_PATHS property set to $repo in hog.conf but directory is empty."
3860  }
3861  } else {
3862  Msg Warning "IP_REPO_PATHS property set to $repo in hog.conf but directory does not exist."
3863  }
3864  }
3865  }
3866 
3867  cd $repo_path
3868  # Check cleanliness only for the files that belong to this project
3869  set dirty ""
3870  # if {[OS] == "windows"} {
3871  # set chunk_size 10
3872  # for {set i 0} {$i < [llength $project_files]} {incr i $chunk_size} {
3873  # set chunk [lrange $project_files $i [expr {$i + $chunk_size - 1}]]
3874  # append dirty [Git "status --untracked-files=no --porcelain" $chunk]
3875  # }
3876  # } else {
3877  append dirty [Git "status --untracked-files=no --porcelain" $project_files]
3878  # }
3879 
3880  if {$dirty eq ""} {
3881  Msg Debug "Project-relevant files are clean."
3882  set clean 1
3883  } else {
3884  Msg CriticalWarning "Project-relevant files not clean, commit hash and version will be set to 0."
3885  set clean 0
3886  }
3887 
3888  #The global SHA and ver is the most recent among everything
3889  if {$clean == 1} {
3890  set found 0
3891  while {$found == 0} {
3892  set global_commit [Git "log --topo-order --format=%h -1 --abbrev=7 $SHAs"]
3893  foreach sha $SHAs {
3894  set found 1
3895  if {![IsCommitAncestor $sha $global_commit]} {
3896  set common_child [FindCommonGitChild $global_commit $sha]
3897  if {$common_child == 0} {
3898  Msg CriticalWarning "The commit $sha is not an ancestor of the global commit $global_commit, which is OK. \
3899  But $sha and $global_commit do not have any common child, which is NOT OK. \
3900  This is probably do to a REBASE that is forbidden in Hog methodology as it changes git history. \
3901  Hog cannot guarantee the accuracy of the SHAs. \
3902  A way to fix this is to make a commit that touches all the projects in the repositories (e.g. change the Hog version), \
3903  but please do not rebase in the official branches in the future."
3904  } else {
3905  Msg Info "The commit $sha is not an ancestor of the global commit $global_commit, adding the first common child $common_child instead..."
3906  lappend SHAs $common_child
3907  }
3908  set found 0
3909 
3910  break
3911  }
3912  }
3913  }
3914  set global_version [FindNewestVersion $versions]
3915  } else {
3916  set global_commit "0000000"
3917  set global_version "00000000"
3918  }
3919 
3920  cd $old_path
3921 
3922  set top_hash [format %+07s $top_hash]
3923  set cons_hash [format %+07s $cons_hash]
3924  return [list $global_commit $global_version \
3925  $hog_hash $hog_ver $top_hash $top_ver \
3926  $libs $hashes $vers $cons_ver $cons_hash \
3927  $ext_names $ext_hashes $xml_hash $xml_ver \
3928  $user_ip_repos $user_ip_repo_hashes $user_ip_repo_vers]
3929 }
3930 
3931 ## @brief Get git SHA of a subset of list file
3932 #
3933 # @param[in] path the file/path or list of files/path the git SHA should be evaluated from. If is not set, use the current path
3934 #
3935 # @return the value of the desired SHA
3936 #
3937 proc GetSHA {{path ""}} {
3938  set old_path [pwd]
3939  if {$path == ""} {
3940  lassign [GitRet {log --format=%h --abbrev=7 -1}] status result
3941  if {$status == 0} {
3942  return [string tolower $result]
3943  } else {
3944  Msg Error "Something went wrong while finding the latest SHA. Does the repository have a commit?"
3945  exit 1
3946  }
3947  }
3948 
3949  # Get repository top level
3950  set repo_path [lindex [Git {rev-parse --show-toplevel}] 0]
3951  cd $repo_path
3952  set paths {}
3953  # Retrieve the list of submodules in the repository
3954  foreach f $path {
3955  set file_in_module 0
3956  if {[file exists .gitmodules]} {
3957  lassign [GitRet "config --file .gitmodules --get-regexp path"] status result
3958  if {$status == 0} {
3959  set submodules [split $result "\n"]
3960  } else {
3961  set submodules ""
3962  Msg Warning "Something went wrong while trying to find submodules: $result"
3963  }
3964 
3965  foreach mod $submodules {
3966  set module [lindex $mod 1]
3967  if {[string first "$repo_path/$module" $f] == 0} {
3968  # File is in a submodule. Append
3969  set file_in_module 1
3970  lappend paths "$module"
3971  break
3972  }
3973  }
3974  }
3975  if {$file_in_module == 0} {
3976  #File is not in a submodule
3977  lappend paths $f
3978  }
3979  }
3980 
3981  lassign [GitRet {log --format=%h --abbrev=7 -1} $paths] status result
3982  if {$status == 0} {
3983  return [string tolower $result]
3984  } else {
3985  Msg Error "Something went wrong while finding the latest SHA. Does the repository have a commit?"
3986  exit 1
3987  }
3988  cd $old_path
3989  return [string tolower $result]
3990 }
3991 
3992 ## @brief Returns the list of Simulators supported by Vivado
3993 proc GetSimulators {} {
3994  set SIMULATORS [list "modelsim" "questa" "riviera" "activehdl" "ies" "vcs"]
3995  return $SIMULATORS
3996 }
3997 
3998 ## @brief Return the path to the active top file
3999 proc GetTopFile {} {
4000  if {[IsVivado]} {
4001  set compile_order_prop [get_property source_mgmt_mode [current_project]]
4002  if {$compile_order_prop ne "All"} {
4003  Msg CriticalWarning "Compile order is not set to automatic, setting it now..."
4004  set_property source_mgmt_mode All [current_project]
4005  update_compile_order -fileset sources_1
4006  }
4007  return [lindex [get_files -quiet -compile_order sources -used_in synthesis -filter {FILE_TYPE =~ "VHDL*" || FILE_TYPE =~ "*Verilog*" }] end]
4008  } elseif {[IsISE]} {
4009  debug::design_graph_mgr -create [current_fileset]
4010  debug::design_graph -add_fileset [current_fileset]
4011  debug::design_graph -update_all
4012  return [lindex [debug::design_graph -get_compile_order] end]
4013  } else {
4014  Msg Error "GetTopFile not yet implemented for this IDE"
4015  }
4016 }
4017 
4018 ## @brief Return the name of the active top module
4019 proc GetTopModule {} {
4020  if {[IsXilinx]} {
4021  return [get_property top [current_fileset]]
4022  } else {
4023  Msg Error "GetTopModule not yet implemented for this IDE"
4024  }
4025 }
4026 
4027 ## @brief Get git version and commit hash of a subset of files
4028 #
4029 # @param[in] path list file or path containing the subset of files whose latest commit hash will be returned
4030 #
4031 # @return a list: the git SHA, the version in hex format
4032 #
4033 proc GetVer {path {force_develop 0}} {
4034  set SHA [GetSHA $path]
4035  #oldest tag containing SHA
4036  if {$SHA eq ""} {
4037  Msg CriticalWarning "Empty SHA found for ${path}. Commit to Git to resolve this warning."
4038  }
4039  set old_path [pwd]
4040  set p [lindex $path 0]
4041  if {[file isdirectory $p]} {
4042  cd $p
4043  } else {
4044  cd [file dirname $p]
4045  }
4046  set repo_path [Git {rev-parse --show-toplevel}]
4047  cd $old_path
4048 
4049  return [list [GetVerFromSHA $SHA $repo_path $force_develop] $SHA]
4050 }
4051 
4052 ## @brief Get git version and commit hash of a specific commit give the SHA
4053 #
4054 # @param[in] SHA the git SHA of the commit
4055 # @param[in] repo_path the path of the repository, this is used to open the Top/repo.conf file
4056 # @param[in] force_develop Force a tag for the develop branch (increase m)
4057 #
4058 # @return a list: the git SHA, the version in hex format
4059 #
4060 proc GetVerFromSHA {SHA repo_path {force_develop 0}} {
4061  if {$SHA eq ""} {
4062  Msg CriticalWarning "Empty SHA found"
4063  set ver "v0.0.0"
4064  } else {
4065  lassign [GitRet "tag --sort=creatordate --contain $SHA -l v*.*.* -l b*v*.*.*"] status result
4066 
4067  if {$status == 0} {
4068  if {[regexp {^ *$} $result]} {
4069  # We do not want the most recent tag, we want the biggest value
4070  lassign [GitRet "log --oneline --pretty=\"%d\""] status2 tag_list
4071  #Msg Status "List of all tags including $SHA: $tag_list."
4072  #cleanup the list and get only the tags
4073  set pattern {tag: v\d+\.\d+\.\d+}
4074  set real_tag_list {}
4075  foreach x $tag_list {
4076  set x_untrimmed [regexp -all -inline $pattern $x]
4077  regsub "tag: " $x_untrimmed "" x_trimmed
4078  set tt [lindex $x_trimmed 0]
4079  if {![string equal $tt ""]} {
4080  lappend real_tag_list $tt
4081  #puts "<$tt>"
4082  }
4083  }
4084  Msg Debug "Cleaned up list: $real_tag_list."
4085  # Sort the tags in version order
4086  set sorted_tags [lsort -decreasing -command CompareVersions $real_tag_list]
4087 
4088  Msg Debug "Sorted Tag list: $sorted_tags"
4089  # Select the newest tag in terms of number, not time
4090  set tag [lindex $sorted_tags 0]
4091 
4092  # Msg Debug "Chosen Tag $tag"
4093  set pattern {v\d+\.\d+\.\d+}
4094  if {![regexp $pattern $tag]} {
4095  Msg CriticalWarning "No Hog version tags found in this repository."
4096  set ver v0.0.0
4097  } else {
4098  lassign [ExtractVersionFromTag $tag] M m p mr
4099  # Open repo.conf and check prefixes
4100  set repo_conf $repo_path/Top/repo.conf
4101 
4102  # Check if the develop/master scheme is used and where is the merge directed to
4103  # Default values
4104  set hotfix_prefix "hotfix/"
4105  set minor_prefix "minor_version/"
4106  set major_prefix "major_version/"
4107  set is_hotfix 0
4108  set enable_develop_branch $force_develop
4109 
4110  set branch_name [Git {rev-parse --abbrev-ref HEAD}]
4111 
4112  if {[file exists $repo_conf]} {
4113  set PROPERTIES [ReadConf $repo_conf]
4114  # [main] section
4115  if {[dict exists $PROPERTIES main]} {
4116  set mainDict [dict get $PROPERTIES main]
4117 
4118  # ENABLE_DEVELOP_ BRANCH property
4119  if {[dict exists $mainDict ENABLE_DEVELOP_BRANCH]} {
4120  set enable_develop_branch [dict get $mainDict ENABLE_DEVELOP_BRANCH]
4121  }
4122  # More properties in [main] here ...
4123  }
4124 
4125  # [prefixes] section
4126  if {[dict exists $PROPERTIES prefixes]} {
4127  set prefixDict [dict get $PROPERTIES prefixes]
4128 
4129  if {[dict exists $prefixDict HOTFIX]} {
4130  set hotfix_prefix [dict get $prefixDict HOTFIX]
4131  }
4132  if {[dict exists $prefixDict MINOR_VERSION]} {
4133  set minor_prefix [dict get $prefixDict MINOR_VERSION]
4134  }
4135  if {[dict exists $prefixDict MAJOR_VERSION]} {
4136  set major_prefix [dict get $prefixDict MAJOR_VERSION]
4137  }
4138  # More properties in [prefixes] here ...
4139  }
4140  }
4141 
4142  if {[string match "HEAD" $branch_name]} {
4143  Msg Debug "Detached HEAD detected - attempting to find branch name"
4144 
4145  # if the branch_name is HEAD (not a legal branch name btw)
4146  # then the branch has been checked out in a detached head state
4147  # this is a fallback condition to enable finding the branch name that the commit is linked too
4148  set log_refs [Git {show -s --pretty=%D HEAD}]
4149  set branch_list [split $log_refs ","]
4150  Msg Debug "list of possible branch refs $log_refs"
4151 
4152  # iterate over all possible refs and match against all prefix types
4153  # set branch name as matched prefix if and only if one match is found
4154 
4155  set match_count 0
4156  set match_prefixes [list $hotfix_prefix $minor_prefix $major_prefix]
4157  set prev_branch_name $branch_name
4158 
4159  foreach br $branch_list {
4160  foreach pr $match_prefixes {
4161  if {[string match "$pr*" [string trim $br]]} {
4162  set branch_name [string trim $br]
4163  incr match_count 1
4164  }
4165  }
4166  }
4167 
4168  if {!$match_count == 1} {
4169  set branch_name $prev_branch_name
4170  Msg Debug "Branch name not found. Using $branch_name"
4171  } else {
4172  Msg Debug "Branch name found: $branch_name"
4173  }
4174  }
4175 
4176  if {$enable_develop_branch == 1} {
4177  if {[string match "$hotfix_prefix*" $branch_name]} {
4178  set is_hotfix 1
4179  }
4180  }
4181 
4182  if {[string match "$major_prefix*" $branch_name]} {
4183  # If major prefix is used, we increase M regardless of anything else
4184  set version_level major
4185  } elseif {[string match "$minor_prefix*" $branch_name] || ($enable_develop_branch == 1 && $is_hotfix == 0)} {
4186  # This is tricky. We increase m if the minor prefix is used or if we are in develop mode and this IS NOT a hotfix
4187  set version_level minor
4188  } else {
4189  # This is even trickier... We increase p if no prefix is used AND we are not in develop mode or if we are in develop mode this IS a Hotfix
4190  set version_level patch
4191  }
4192 
4193  if {$M == -1} {
4194  Msg CriticalWarning "Tag $tag does not contain a Hog compatible version in this repository."
4195  exit
4196  #set ver v0.0.0
4197  } elseif {$mr == 0} {
4198  switch $version_level {
4199  minor {
4200  incr m
4201  set p 0
4202  }
4203  major {
4204  incr M
4205  set m 0
4206  set p 0
4207  }
4208  default {
4209  incr p
4210  }
4211  }
4212  } else {
4213  Msg Info "No tag contains $SHA, will use most recent tag $tag. As this is a candidate tag, the patch level will be kept at $p."
4214  }
4215  set ver v$M.$m.$p
4216  }
4217  } else {
4218  #The tag in $result contains the current SHA
4219  set vers [split $result "\n"]
4220  set ver [lindex $vers 0]
4221  foreach v $vers {
4222  if {[regexp {^v.*$} $v]} {
4223  set un_ver $ver
4224  set ver $v
4225  break
4226  }
4227  }
4228  }
4229  } else {
4230  Msg CriticalWarning "Error while trying to find tag for $SHA"
4231  set ver "v0.0.0"
4232  }
4233  }
4234  lassign [ExtractVersionFromTag $ver] M m c mr
4235 
4236  if {$mr > -1} {
4237  # Candidate tab
4238  set M [format %02X $M]
4239  set m [format %02X $m]
4240  set c [format %04X $c]
4241  } elseif {$M > -1} {
4242  # official tag
4243  set M [format %02X $M]
4244  set m [format %02X $m]
4245  set c [format %04X $c]
4246  } else {
4247  Msg Warning "Tag does not contain a properly formatted version: $ver"
4248  set M [format %02X 0]
4249  set m [format %02X 0]
4250  set c [format %04X 0]
4251  }
4252 
4253  return $M$m$c
4254 }
4255 
4256 ## @brief Handle git commands
4257 #
4258 #
4259 # @param[in] command: the git command to be run including refs (branch, tags, sha, etc.), except files.
4260 # @param[in] files: files given to git as argument. They will always be separated with -- to avoid weird accidents
4261 #
4262 # @returns the output of the git command
4263 proc Git {command {files ""}} {
4264  lassign [GitRet $command $files] ret result
4265  if {$ret != 0} {
4266  Msg Error "Code $ret returned by git running: $command -- $files"
4267  }
4268 
4269  return $result
4270 }
4271 
4272 
4273 # @brief Get the name of the module in a HDL file. If module is not found, it returns an empty string
4274 #
4275 # @param[in] filename The name of the hdl file
4276 
4277 proc GetModuleName {filename} {
4278  # Check if the file exists
4279  if {![file exists $filename]} {
4280  Msg CriticalWarning "Error: File $filename does not exist."
4281  return ""
4282  }
4283 
4284  # Open the file for reading
4285  set fileId [open $filename r]
4286 
4287  # Read the content of the file
4288  set file_content [read $fileId]
4289 
4290  # Close the file
4291  close $fileId
4292 
4293 
4294  if {[file extension $filename] == ".vhd" || [file extension $filename] == ".vhdl"} {
4295  # Convert the file content to lowercase for case-insensitive matching
4296  set file_content [string tolower $file_content]
4297  # Regular expression to match the entity name after the 'entity' keyword
4298  set pattern {(?m)^\s*entity\s+(\S+)\s+is}
4299  } elseif {[file extension $filename] == ".v" || [file extension $filename] == ".sv"} {
4300  # Regular expression to match the module name after the 'module' keyword
4301  set pattern {\n\s*module\s*(\w+)(\s*|\(|\n)}
4302  } else {
4303  Msg Debug "File is neither VHDL nor Verilog... Returning empty string..."
4304  return "'"
4305  }
4306 
4307  # Search for the module name using the regular expression
4308  if {[regexp $pattern $file_content match module_name]} {
4309  return $module_name
4310  } else {
4311  Msg Debug "No module was found in $filename. Returning an empty string..."
4312  return ""
4313  }
4314 }
4315 
4316 ## Get a dictionary of verilog generics with their types for a given file
4317 #
4318 # @param[in] file File to read Generics from
4319 proc GetVerilogGenerics {file} {
4320  set fp [open $file r]
4321  set data [read $fp]
4322  close $fp
4323  set lines []
4324 
4325  # read in the verilog file and remove comments
4326  foreach line [split $data "\n"] {
4327  regsub "^\\s*\/\/.*" $line "" line
4328  regsub "(.*)\/\/.*" $line {\1} line
4329  if {![string equal $line ""]} {
4330  append lines $line " "
4331  }
4332  }
4333 
4334  # remove block comments also /* */
4335  regsub -all {/\*.*\*/} $lines "" lines
4336 
4337  # create a list of characters to split for tokenizing
4338  set punctuation [list]
4339  foreach char [list "(" ")" ";" "," " " "!" "<=" ":=" "=" "\[" "\]"] {
4340  lappend punctuation $char "\000$char\000"
4341  }
4342 
4343  # split the file into tokens
4344  set tokens [split [string map $punctuation $lines] \000]
4345 
4346  set parameters [dict create]
4347 
4348  set PARAM_NAME 1
4349  set PARAM_VALUE 2
4350  set LEXING 3
4351  set PARAM_WIDTH 4
4352  set state $LEXING
4353 
4354  # loop over the generic lines
4355  foreach token $tokens {
4356  set token [string trim $token]
4357  if {![string equal "" $token]} {
4358  if {[string equal [string tolower $token] "parameter"]} {
4359  set state $PARAM_NAME
4360  } elseif {[string equal $token ")"] || [string equal $token ";"]} {
4361  set state $LEXING
4362  } elseif {$state == $PARAM_WIDTH} {
4363  if {[string equal $token "\]"]} {
4364  set state $PARAM_NAME
4365  }
4366  } elseif {$state == $PARAM_VALUE} {
4367  if {[string equal $token ","]} {
4368  set state $PARAM_NAME
4369  } elseif {[string equal $token ";"]} {
4370  set state $LEXING
4371  } else {
4372 
4373  }
4374  } elseif {$state == $PARAM_NAME} {
4375  if {[string equal $token "="]} {
4376  set state $PARAM_VALUE
4377  } elseif {[string equal $token "\["]} {
4378  set state $PARAM_WIDTH
4379  } elseif {[string equal $token ","]} {
4380  set state $PARAM_NAME
4381  } elseif {[string equal $token ";"]} {
4382  set state $LEXING
4383  } elseif {[string equal $token ")"]} {
4384  set state $LEXING
4385  } else {
4386  dict set parameters $token "integer"
4387  }
4388  }
4389  }
4390  }
4391 
4392  return $parameters
4393 }
4394 
4395 ## Get a dictionary of VHDL generics with their types for a given file
4396 #
4397 # @param[in] file File to read Generics from
4398 # @param[in] entity The entity from which extracting the generics
4399 proc GetVhdlGenerics {file {entity ""}} {
4400  set fp [open $file r]
4401  set data [read $fp]
4402  close $fp
4403  set lines []
4404 
4405  # read in the vhdl file and remove comments
4406  foreach line [split $data "\n"] {
4407  regsub "^\\s*--.*" $line "" line
4408  regsub "(.*)--.*" $line {\1} line
4409  if {![string equal $line ""]} {
4410  append lines $line " "
4411  }
4412  }
4413 
4414  # extract the generic block
4415  set generic_block ""
4416  set generics [dict create]
4417 
4418  if {1 == [string equal $entity ""]} {
4419  regexp {(?i).*entity\s+([^\s]+)\s+is} $lines _ entity
4420  }
4421 
4422  set generics_regexp "(?i).*entity\\s+$entity\\s+is\\s+generic\\s*\\((.*)\\)\\s*;\\s*port.*end.*$entity"
4423 
4424  if {[regexp $generics_regexp $lines _ generic_block]} {
4425  # loop over the generic lines
4426  foreach line [split $generic_block ";"] {
4427  # split the line into the generic + the type
4428  regexp {(.*):\s*([A-Za-z0-9_]+).*} $line _ generic type
4429 
4430  # one line can have multiple generics of the same type, so loop over them
4431  set splits [split $generic ","]
4432  foreach split $splits {
4433  dict set generics [string trim $split] [string trim $type]
4434  }
4435  }
4436  }
4437  return $generics
4438 }
4439 
4440 ## @brief Runs a GHDL command and returns its output and exit state
4441 proc GHDL {command logfile} {
4442  set ret [catch {exec -ignorestderr ghdl {*}$command >>& $logfile} result options]
4443  # puts "ret: $ret"
4444  # puts "result: $result\n"
4445  # puts "options: $options"
4446  # puts "*********"
4447  return [list $ret $result]
4448 }
4449 
4450 ## @brief Handle git commands without causing an error if ret is not 0
4451 #
4452 # It can be used with lassign like this: lassign [GitRet <git command> <possibly files> ] ret result
4453 #
4454 # @param[in] command: the git command to be run including refs (branch, tags, sha, etc.), except files.
4455 # @param[in] files: files given to git as argument. They will always be separated with -- to avoid weird accidents
4456 # Sometimes you need to remove the --. To do that just set files to " "
4457 #
4458 # @returns a list of 2 elements: the return value (0 if no error occurred) and the output of the git command
4459 proc GitRet {command {files ""}} {
4460  global env
4461  if {$files eq ""} {
4462  set ret [catch {exec -ignorestderr git {*}$command} result]
4463  } else {
4464  set ret [catch {exec -ignorestderr git {*}$command -- {*}$files} result]
4465  }
4466  return [list $ret $result]
4467 }
4468 
4469 ## @brief Check git version installed in this machine
4470 #
4471 # @param[in] target_version the version required by the current project
4472 #
4473 # @return Returns 1, if the system git version is greater or equal to the target
4474 proc GitVersion {target_version} {
4475  set ver [split $target_version "."]
4476  set v [Git --version]
4477  #Msg Info "Found Git version: $v"
4478  set current_ver [split [lindex $v 2] "."]
4479  set target [expr {[lindex $ver 0] * 100000 + [lindex $ver 1] * 100 + [lindex $ver 2]}]
4480  set current [expr {[lindex $current_ver 0] * 100000 + [lindex $current_ver 1] * 100 + [lindex $current_ver 2]}]
4481  return [expr {$target <= $current}]
4482 }
4483 
4484 ## @brief Check if a file is present in an Rclone remote repository
4485 #
4486 # The output of rclone is captured rather than printed, so that the error messages that rclone
4487 # emits when the file (or its parent directory) is not there do not pollute the Hog log.
4488 # Any other rclone failure (e.g. wrong credentials, unreachable remote) is reported instead.
4489 #
4490 # @param[in] remote_file: the full rclone path of the file to look for
4491 # @param[in] config_path: the rclone configuration file to be used
4492 #
4493 # @return 1 if the file exists, 0 if it does not exist or if rclone could not be run
4494 proc RcloneFileExists {remote_file config_path} {
4495  if {[catch {exec rclone ls $remote_file --config $config_path 2>@1} result opt] == 0} {
4496  return 1
4497  }
4498  # rclone exits with 3 (directory not found) or 4 (file not found) when the file is simply not there
4499  set rclone_ret [lindex [dict get $opt -errorcode] end]
4500  if {$rclone_ret ne "3" && $rclone_ret ne "4"} {
4501  Msg CriticalWarning "Could not check if $remote_file is in the Rclone repository: $result"
4502  } else {
4503  Msg Debug "$remote_file not found in the Rclone repository (rclone exit code $rclone_ret)."
4504  }
4505  return 0
4506 }
4507 
4508 ## @brief Copy IP generated files from/to a remote o local directory (possibly EOS)
4509 #
4510 # @param[in] what_to_do: the action you want to perform, either
4511 # "push", if you want to copy the local IP synth result to the remote directory
4512 # "pull" if you want to copy the files from thre remote directory to your local repository
4513 # @param[in] xci_file: the .xci file of the IP you want to handle
4514 # @param[in] ip_path: the path of the directory you want the IP to be saved (possibly EOS)
4515 # @param[in] repo_path: the main path of your repository
4516 # @param[in] gen_dir: the directory where generated files are placed, \
4517 # by default the files are placed in the same folder as the .xci
4518 # @param[in] force: if not set to 0, will copy the IP to the remote directory even if it is already present
4519 #
4520 proc HandleIP {what_to_do xci_file ip_path repo_path on_eos on_rclone rclone_config_path {gen_dir "."} {force 0}} {
4521  global env
4522  if {!($what_to_do eq "push") && !($what_to_do eq "pull")} {
4523  Msg Error "You must specify push or pull as first argument."
4524  }
4525 
4526  if {[catch {package require tar} TARPACKAGE]} {
4527  Msg CriticalWarning "Cannot find package tar. You can fix this by installing package \"tcllib\""
4528  return -1
4529  }
4530 
4531  set old_path [pwd]
4532  cd $repo_path
4533 
4534  if {!([file exists $xci_file])} {
4535  Msg CriticalWarning "Could not find $xci_file."
4536  cd $old_path
4537  return -1
4538  }
4539 
4540 
4541  set xci_path [file dirname $xci_file]
4542  set xci_name [file tail $xci_file]
4543  set xci_ip_name [file rootname [file tail $xci_file]]
4544  set xci_dir_name [file tail $xci_path]
4545  set gen_path $gen_dir
4546 
4547  set hash [Md5Sum $xci_file]
4548  set file_name $xci_name\_$hash
4549 
4550  # Check if the IP is a subcore of a BD file
4551 
4552  set scope [get_property -quiet SCOPE [get_ips $xci_ip_name]]
4553  if {$scope != ""} {
4554  Msg Debug "IP $xci_name is a subcore of a BD file, will not pull/push it from/to the remote directory."
4555  return 0
4556  }
4557 
4558  Msg Info "Preparing to $what_to_do IP: $xci_name ($file_name)."
4559 
4560  if {$what_to_do eq "push"} {
4561  set will_copy 0
4562  set will_remove 0
4563  if {$on_rclone == 1} {
4564  # lassign [ExecuteRet rclone ls $ip_path/$file_name.tar --config $rclone_config_path] ret result
4565  if {[RcloneFileExists $ip_path/$file_name.tar $rclone_config_path] == 0} {
4566  set will_copy 1
4567  } else {
4568  if {$force == 0} {
4569  Msg Info "IP already in the Rclone repository, will not copy..."
4570  } else {
4571  Msg Info "IP already in the Rclone repository, will forcefully replace..."
4572  set will_copy 1
4573  set will_remove 1
4574  }
4575  }
4576  } elseif {$on_eos == 1} {
4577  lassign [eos "ls $ip_path/$file_name.tar"] ret result
4578  if {$ret != 0} {
4579  set will_copy 1
4580  } else {
4581  if {$force == 0} {
4582  Msg Info "IP already in the EOS repository, will not copy..."
4583  } else {
4584  Msg Info "IP already in the EOS repository, will forcefully replace..."
4585  set will_copy 1
4586  set will_remove 1
4587  }
4588  }
4589  } else {
4590  if {[file exists "$ip_path/$file_name.tar"]} {
4591  if {$force == 0} {
4592  Msg Info "IP already in the local repository, will not copy..."
4593  } else {
4594  Msg Info "IP already in the local repository, will forcefully replace..."
4595  set will_copy 1
4596  set will_remove 1
4597  }
4598  } else {
4599  set will_copy 1
4600  }
4601  }
4602 
4603  if {$will_copy == 1} {
4604  # Check if there are files in the .gen directory first and copy them into the right place
4605  Msg Info "Looking for generated files in $gen_path..."
4606  set ip_gen_files [glob -nocomplain $gen_path/*]
4607 
4608  #here we should remove the .xci file from the list if it's there
4609 
4610  if {[llength $ip_gen_files] > 0} {
4611  Msg Info "Found some IP synthesised files matching $xci_ip_name"
4612  if {$will_remove == 1} {
4613  Msg Info "Removing old synthesised directory $ip_path/$file_name.tar..."
4614  if {$on_rclone == 1} {
4615  lassign [ExecuteRet rclone delete $ip_path/$file_name.tar --config $rclone_config_path] ret result
4616  if {$ret != 0} {
4617  Msg CriticalWarning "Could not delete file from Rclone: $result"
4618  }
4619  } elseif {$on_eos == 1} {
4620  eos "rm -rf $ip_path/$file_name.tar" 5
4621  } else {
4622  file delete -force "$ip_path/$file_name.tar"
4623  }
4624  }
4625 
4626  Msg Info "Creating local archive with IP generated files..."
4627  set tar_files []
4628 
4629  set at_least_one_long 0
4630  foreach f $ip_gen_files {
4631  set new_f "[Relative [file normalize $repo_path] $f]"
4632  set len [string length $new_f]
4633  if {$len > 254} {
4634  Msg Warning "One file in $xci_ip_name is too long ($len chars): $new_f"
4635  set at_least_one_long 1
4636  }
4637  lappend tar_files $new_f
4638  }
4639 
4640  Msg Debug "Tar files: $tar_files"
4641  if {$at_least_one_long == 1} {
4642  Msg Warning "Using regular tar, please cross your fingers..."
4643  #lassign [ExecuteRet tar --format=pax -cf $file_name.tar {*}$tar_files] ret_tar result_tar
4644  lassign [ExecuteRet tar -cf $file_name.tar {*}$tar_files] ret_tar result_tar
4645  if {$ret_tar != 0} {
4646  Msg CriticalWarning "Something went wrong when using regular tar. Error message: $result_tar"
4647  }
4648  } else {
4649  if {[catch { ::tar::create $file_name.tar $tar_files } err]} {
4650  Msg Warning "Tcl built-in tar failed: $err, will try regular tar..."
4651  #lassign [ExecuteRet tar --format=pax -cf $file_name.tar {*}$tar_files] ret_tar result_tar
4652  lassign [ExecuteRet tar -cf $file_name.tar {*}$tar_files] ret_tar result_tar
4653  if {$ret_tar != 0} {
4654  Msg CriticalWarning "Something went wrong when using regular tar. Error message: $result_tar"
4655  }
4656  }
4657  }
4658 
4659  Msg Info "Copying IP generated files for $xci_name..."
4660  if {$on_rclone == 1} {
4661  lassign [ExecuteRet rclone copyto $file_name.tar $ip_path/$file_name.tar --config $rclone_config_path] ret result
4662  if {$ret != 0} {
4663  Msg CriticalWarning "Something went wrong when copying the IP files to Rclone. Error message: $result"
4664  }
4665  } elseif {$on_eos == 1} {
4666  lassign [ExecuteRet xrdcp -f -s $file_name.tar $::env(EOS_MGM_URL)//$ip_path/] ret msg
4667  if {$ret != 0} {
4668  Msg CriticalWarning "Something went wrong when copying the IP files to EOS. Error message: $msg"
4669  }
4670  } else {
4671  Copy "$file_name.tar" "$ip_path/"
4672  }
4673  Msg Info "Removing local archive"
4674  file delete $file_name.tar
4675  } else {
4676  Msg Warning "Could not find synthesized files matching $gen_path/$file_name*"
4677  }
4678  }
4679  } elseif {$what_to_do eq "pull"} {
4680  if {$on_rclone == 1} {
4681  # lassign [ExecuteRet rclone ls $ip_path/$file_name.tar --config $rclone_config_path] ret result
4682  if {[RcloneFileExists $ip_path/$file_name.tar $rclone_config_path] == 0} {
4683  Msg Info "Nothing for $xci_name was found in the Rclone repository, cannot pull."
4684  cd $old_path
4685  return -1
4686  } else {
4687  Msg Info "IP $xci_name found in the Rclone repository $ip_path, copying it locally to $repo_path..."
4688  lassign [ExecuteRet rclone copyto $ip_path/$file_name.tar $file_name.tar --config $rclone_config_path] ret_copy result_copy
4689  if {$ret_copy != 0} {
4690  Msg CriticalWarning "Something went wrong when copying the IP files from Rclone. Error message: $result_copy"
4691  }
4692  }
4693  } elseif {$on_eos == 1} {
4694  lassign [eos "ls $ip_path/$file_name.tar"] ret result
4695  if {$ret != 0} {
4696  Msg Info "Nothing for $xci_name was found in the EOS repository, cannot pull."
4697  cd $old_path
4698  return -1
4699  } else {
4700  set remote_tar "$::env(EOS_MGM_URL)//$ip_path/$file_name.tar"
4701  Msg Info "IP $xci_name found in the repository $remote_tar, copying it locally to $repo_path..."
4702 
4703  lassign [ExecuteRet xrdcp -f -r -s $remote_tar $repo_path] ret msg
4704  if {$ret != 0} {
4705  Msg CriticalWarning "Something went wrong when copying the IP files to EOS. Error message: $msg"
4706  }
4707  }
4708  } else {
4709  if {[file exists "$ip_path/$file_name.tar"]} {
4710  Msg Info "IP $xci_name found in local repository $ip_path/$file_name.tar, copying it locally to $repo_path..."
4711  Copy $ip_path/$file_name.tar $repo_path
4712  } else {
4713  Msg Info "Nothing for $xci_name was found in the local IP repository, cannot pull."
4714  cd $old_path
4715  return -1
4716  }
4717  }
4718 
4719  if {[file exists $file_name.tar]} {
4720  remove_files $xci_file
4721  Msg Info "Extracting IP files from archive to $repo_path..."
4722  ::tar::untar $file_name.tar -dir $repo_path -noperms
4723  Msg Info "Removing local archive"
4724  file delete $file_name.tar
4725  add_files -norecurse -fileset sources_1 $xci_file
4726  }
4727  }
4728  cd $old_path
4729  return 0
4730 }
4731 
4732 ## Convert hex version to M.m.p string
4733 #
4734 # @param[in] version the version (in 32-bit hexadecimal format 0xMMmmpppp) to be converted
4735 #
4736 # @return a string containing the version in M.m.p format
4737 #
4738 proc HexVersionToString {version} {
4739  scan [string range $version 0 1] %x M
4740  scan [string range $version 2 3] %x m
4741  scan [string range $version 4 7] %x c
4742  return "$M.$m.$c"
4743 }
4744 
4745 # @brief Import TCL Lib from an external installation for Libero, Synplify and Diamond
4746 proc ImportTclLib {} {
4747  global env
4748  if {[IsLibero] || [IsDiamond] || [IsSynplify]} {
4749  if {[info exists env(HOG_TCLLIB_PATH)]} {
4750  lappend auto_path $env(HOG_TCLLIB_PATH)
4751  return 1
4752  } else {
4753  puts "ERROR: To run Hog with Microsemi Libero SoC or Lattice Diamond, you need to define the HOG_TCLLIB_PATH variable."
4754  return 0
4755  }
4756  }
4757 }
4758 
4759 # @brief Initialise the Launcher and returns a list of project parameters: directive project project_name group_name repo_path old_path bin_dir top_path cmd ide
4760 #
4761 # @param[in] script The launch.tcl script
4762 # @param[in] tcl_path The launch.tcl script path
4763 # @param[in] parameters The allowed parameters for launch.tcl
4764 # @param[in] commands The allowed directives for launch.tcl
4765 # @param[in] argv The input arguments passed to launch.tcl
4766 # @param[in] custom_commands Custom commands to be added to the list of commands
4767 
4768 proc InitLauncher {script tcl_path parameters commands argv {custom_commands ""}} {
4769  set repo_path [file normalize "$tcl_path/../.."]
4770  set old_path [pwd]
4771  set bin_path [file normalize "$tcl_path/../../bin"]
4772  set top_path [file normalize "$tcl_path/../../Top"]
4773 
4774  set cmd_lines [split $commands "\n"]
4775 
4776  set command_options [dict create]
4777  set directive_descriptions [dict create]
4778  set directive_names [dict create]
4779  set common_directive_names [dict create]
4780  set directives_with_optional_projects ""
4781  set custom_command ""
4782  set custom_command_options ""
4783 
4784  foreach l $cmd_lines {
4785  #excludes direcitve with a # just after the \{
4786  if {[regexp {\\(.*) \{\#} $l minc d]} {
4787  lappend directives_with_projects $d
4788  set current_directive $d
4789  }
4790 
4791  if {[regexp {^[^#]* allow_empty_proj} $l minc dd]} {
4792  lappend directives_with_optional_projects $current_directive
4793  }
4794 
4795  #gets all the regexes
4796  if {[regexp {\\(.*) \{} $l minc regular_expression]} {
4797  lappend directive_regex $regular_expression
4798  }
4799 
4800  #gets all common directives
4801  if {[regexp {\#\s*NAME(\*)?:\s*(.*)\s*} $l minc star name]} {
4802  dict set directive_names $name $regular_expression
4803  if {$star eq "*"} {
4804  dict set common_directive_names $name $regular_expression
4805  }
4806  }
4807  set directive_names [DictSort $directive_names]
4808  set common_directive_names [DictSort $common_directive_names]
4809 
4810  #gets all the descriptions
4811  if {[regexp {\#\s*DESCRIPTION:\s*(.*)\s*} $l minc x]} {
4812  dict set directive_descriptions $regular_expression $x
4813  }
4814 
4815  #gets all the list of options
4816  if {[regexp {\#\s*OPTIONS:\s*(.*)\s*} $l minc x]} {
4817  dict set command_options $regular_expression [split [regsub -all {[ \t\n]+} $x {}] ","]
4818  }
4819  }
4820 
4821  set short_usage "usage: ./Hog/Do \[OPTIONS\] <directive> \[project\]\n\nMost common directives (case insensitive):"
4822 
4823  dict for {key value} $common_directive_names {
4824  set short_usage "$short_usage\n - $key: [dict get $directive_descriptions $value]"
4825  }
4826 
4827  if {[string length $custom_commands] > 0} {
4828  Msg Debug "Found custom commands to add to short short_usage."
4829  set short_usage "$short_usage\n\nCustom commands:"
4830  dict for {key command} $custom_commands {
4831  Msg Debug "Adding $key : [dict get $command DESCRIPTION]"
4832  set short_usage "$short_usage\n - $key: [dict get $command DESCRIPTION]"
4833  }
4834  }
4835 
4836 
4837  set short_usage "$short_usage\n\n\
4838  To see all the available directives, run:\n./Hog/Do HELP\n\n\
4839  To list available options for the chosen directive run:\n\
4840  ./Hog/Do <directive> HELP\n
4841  "
4842 
4843  set usage "usage: ./Hog/Do \[OPTIONS\] <directive> \[project\]\n\nDirectives (case insensitive):"
4844 
4845  dict for {key value} $directive_names {
4846  set usage "$usage\n - $key: [dict get $directive_descriptions $value]"
4847  }
4848 
4849  # if length of custom commands is greater than 0, add them to the short usage"
4850  if {[string length $custom_commands] > 0} {
4851  Msg Debug "Found custom commands to add to short usage."
4852  set usage "$usage\n\nCustom commands:"
4853  dict for {key command} $custom_commands {
4854  Msg Debug "Adding $key : [dict get $command DESCRIPTION]"
4855  set usage "$usage\n - $key: [dict get $command DESCRIPTION]"
4856  }
4857  }
4858 
4859 
4860  set usage "$usage\n\nTo list available options for the chosen directive run:\n./Hog/Do <directive> HELP"
4861 
4862  if {[IsTclsh]} {
4863  #Just display the logo the first time, not when the script is run in the IDE
4864  Logo $repo_path
4865  }
4866 
4867  # Check if HogEnv.conf exists and parse it
4868  if {[file exists [Hog::LoggerLib::GetUserFilePath "HogEnv.conf"]]} {
4869  Msg Debug "HogEnv.conf found"
4870  set loggerdict [Hog::LoggerLib::ParseTOML [Hog::LoggerLib::GetUserFilePath "HogEnv.conf"]]
4871  set HogEnvDict [Hog::LoggerLib::GetTOMLDict]
4872  Hog::LoggerLib::PrintTOMLDict $HogEnvDict
4873  }
4874 
4875 
4876  if {[catch {package require cmdline} ERROR]} {
4877  Msg Debug "The cmdline Tcl package was not found, sourcing it from Hog..."
4878  source $tcl_path/utils/cmdline.tcl
4879  }
4880 
4881  set argv [regsub -all {(?i) HELP\y} $argv " -help"]
4882 
4883 
4884  #Gather up all custom parameters
4885  #NOTE: right now user can accidentally redefine their own custom parameters, there is no check for that...
4886  set custom_parameters [list]
4887  dict for {key command} $custom_commands {
4888  set custom_parameters [concat $custom_parameters [dict get $command CUSTOM_OPTIONS]]
4889  }
4890 
4891  lassign [GetOptions $argv [concat $custom_parameters $parameters]] option_list arg_list
4892 
4893  if {[IsInList "-all" $option_list]} {
4894  set list_all 1
4895  } else {
4896  set list_all 2
4897  }
4898 
4899  #option_list will be emptied by the next instruction
4900 
4901  # Argv here is modified and the options are removed
4902  set directive [string toupper [lindex $arg_list 0]]
4903  set min_n_of_args 0
4904  set max_n_of_args 2
4905  set argument_is_no_project 1
4906 
4907  set NO_DIRECTIVE_FOUND 0
4908  switch -regexp -- $directive "$commands"
4909 
4910  if {$NO_DIRECTIVE_FOUND == 1} {
4911  if {[string length $custom_commands] > 0 && [dict exists $custom_commands $directive]} {
4912  set custom_command $directive
4913  set custom_command_dict [DictGet $custom_commands $directive]
4914  set custom_command_hog_parameters [DictGet $custom_command_dict OPTIONS]
4915  set custom_command_options [DictGet $custom_command_dict CUSTOM_OPTIONS]
4916  set custom_command_options [concat $custom_command_hog_parameters $custom_command_options]
4917  } else {
4918  Msg Status "ERROR: Unknown directive $directive.\n\n"
4919  puts $usage
4920  exit
4921  }
4922  }
4923 
4924  if {[IsInList $directive $directives_with_projects 1]} {
4925  set argument_is_no_project 0
4926  }
4927 
4928  if {[IsInList "-help" $option_list] || [IsInList "-?" $option_list] || [IsInList "-h" $option_list]} {
4929  if {$directive != ""} {
4930  if {[IsInList $directive $directives_with_projects 1]} {
4931  puts "usage: ./Hog/Do \[OPTIONS\] $directive <project>\n"
4932  } elseif {[regexp "^COMPSIM(LIB)?$" $directive]} {
4933  puts "usage: ./Hog/Do \[OPTIONS\] $directive <simulator>\n"
4934  } else {
4935  puts "usage: ./Hog/Do \[OPTIONS\] $directive \n"
4936  }
4937 
4938  dict for {dir desc} $directive_descriptions {
4939  if {[regexp $dir $directive]} {
4940  puts "$desc\n"
4941  break
4942  }
4943  }
4944 
4945  #if custom command, parse custom options instead
4946  if {$custom_command ne ""} {
4947  if {[llength $custom_command_options] > 0} {
4948  puts "Available options:"
4949  }
4950  foreach custom_option $custom_command_options {
4951  set n [llength $custom_option]
4952  if {$n == 2} {
4953  lassign $custom_option opt help
4954  puts " -$opt"
4955  puts " $help"
4956  } elseif {$n == 3} {
4957  lassign $custom_option opt def help
4958  puts " -$opt <argument>"
4959  if {$def ne ""} {
4960  puts " $help (default: $def)"
4961  } else {
4962  puts " $help"
4963  }
4964  } else {
4965  Msg Warning "Custom option spec has invalid arity (expected 2 or 3): $custom_option"
4966  }
4967  }
4968  }
4969 
4970  dict for {dir opts} $command_options {
4971  if {[regexp $dir $directive]} {
4972  puts "Available options:"
4973  foreach opt $opts {
4974  foreach par $parameters {
4975  if {$opt == [lindex $par 0]} {
4976  if {[regexp {\.arg$} $opt]} {
4977  set opt_name [regsub {\.arg$} $opt ""]
4978  puts " -$opt_name <argument>"
4979  } else {
4980  puts " -$opt"
4981  }
4982  puts " [lindex $par [llength $par]-1]"
4983  }
4984  }
4985  }
4986  puts ""
4987  }
4988  }
4989  } else {
4990  puts $usage
4991  }
4992  # Msg Info [cmdline::usage $parameters $usage]
4993  exit 0
4994  }
4995 
4996  # Gather the options supported by the chosen directive
4997  set directive_options ""
4998  set directive_found 0
4999  if {$custom_command ne ""} {
5000  set directive_options $custom_command_options
5001  set directive_found 1
5002  } else {
5003  dict for {dir opts} $command_options {
5004  if {[regexp $dir $directive]} {
5005  set directive_options $opts
5006  set directive_found 1
5007  break
5008  }
5009  }
5010  }
5011 
5012  # Get the directive name without `.arg` suffix
5013  set directive_option_names {}
5014  foreach opt $directive_options {
5015  lappend directive_option_names [regsub {\.arg$} [lindex $opt 0] ""]
5016  }
5017 
5018  if {$custom_command ne ""} {
5019  set parameters [concat $parameters $custom_command_options]
5020  }
5021 
5022  if {[catch {array set options [cmdline::getoptions option_list $parameters $usage]} err]} {
5023  Msg Status "\nERROR: Syntax error, probably unknown option.\n\n USAGE: $err"
5024  exit 1
5025  }
5026 
5027  # Ignore the options that are not supported by the chosen directive, restoring their default value
5028  if {$directive_found == 1} {
5029  set default_option_list {}
5030  array set default_options [cmdline::getoptions default_option_list $parameters $usage]
5031  foreach {key value} [array get options] {
5032  if {[IsInList $key $directive_option_names] || ![info exists default_options($key)]} {
5033  continue
5034  }
5035  if {$value ne $default_options($key)} {
5036  Msg Warning "Option -$key is not supported by directive $directive, ignoring it."
5037  set options($key) $default_options($key)
5038  }
5039  }
5040  }
5041 
5042  if {[llength $arg_list] <= $min_n_of_args || [llength $arg_list] > $max_n_of_args} {
5043  Msg Status "\nERROR: Wrong number of arguments: [llength $argv]"
5044  puts $short_usage
5045  exit 1
5046  }
5047 
5048  set project [lindex $arg_list 1]
5049  set optional_project [IsInList $directive $directives_with_optional_projects 1]
5050 
5051  if {$argument_is_no_project == 0} {
5052  # Remove leading Top/ or ./Top/ if in project_name
5053  regsub "^(\./)?Top/" $project "" project
5054  # Remove trailing / and spaces if in project_name
5055  regsub "/? *\$" $project "" project
5056 
5057  if {$project eq "" && $optional_project == 1} {
5058  set proj_conf 0
5059  } else {
5060  set proj_conf [ProjectExists $project $repo_path]
5061  }
5062  } else {
5063  set proj_conf 0
5064  }
5065 
5066  Msg Debug "Option list:"
5067 
5068  foreach {key value} [array get options] {
5069  Msg Debug "$key => $value"
5070  }
5071 
5072  set cmd ""
5073 
5074  if {[IsTclsh]} {
5075  # command is filled with the IDE exectuable when this function is called by Tcl scrpt
5076  if {$proj_conf != 0} {
5077  CheckLatestHogRelease $repo_path
5078 
5079  lassign [GetIDECommand $proj_conf] cmd before_tcl_script after_tcl_script end_marker
5080  Msg Info "Project $project uses $cmd IDE"
5081 
5082  ## The following is the IDE command to launch:
5083  set command "$cmd $before_tcl_script$script$after_tcl_script$argv$end_marker"
5084  } else {
5085  if {$custom_command ne ""} {
5086  if {[dict exists $custom_commands $directive IDE]} {
5087  lassign [GetIDECommand "" [dict get $custom_commands $directive IDE]] cmd before_tcl_script after_tcl_script end_marker
5088  Msg Info "Custom command: $custom_command uses $cmd IDE"
5089  set command "$cmd $before_tcl_script$script$after_tcl_script$argv$end_marker"
5090  } else {
5091  set command "custom_tcl"
5092  }
5093  } elseif {$argument_is_no_project == 1} {
5094  set command -4
5095  Msg Debug "$project will be used as first argument"
5096  } elseif {$project != ""} {
5097  #Project not given
5098  set command -1
5099  } elseif {$min_n_of_args < 0} {
5100  #Project not needed
5101  set command -3
5102  } else {
5103  #Project not found
5104  if {$optional_project == 0} {
5105  set command -2
5106  } else {
5107  set command -3
5108  }
5109  }
5110  }
5111  } else {
5112  # When the launcher is executed from within an IDE, command is set to 0
5113  set command 0
5114  }
5115 
5116  set project_group [file dirname $project]
5117  set project_name $project
5118  set project [file tail $project]
5119  Msg Debug "InitLauncher: project_group=$project_group, project_name=$project_name, project=$project"
5120 
5121  return [list $directive $project $project_name $project_group $repo_path $old_path\
5122  $bin_path $top_path $usage $short_usage $command $cmd [array get options]\
5123  $directive_options]
5124 }
5125 
5126 # @brief Returns 1 if a commit is an ancestor of another, otherwise 0
5127 #
5128 # @param[in] ancestor The potential ancestor commit
5129 # @param[in] commit The potential descendant commit
5130 proc IsCommitAncestor {ancestor commit} {
5131  lassign [GitRet "merge-base --is-ancestor $ancestor $commit"] status result
5132  if {$status == 0} {
5133  return 1
5134  } else {
5135  return 0
5136  }
5137 }
5138 
5139 proc IsDiamond {} {
5140  return [expr {[info commands sys_install] != ""}]
5141 }
5142 
5143 ## @brief Returns true if the IDE is MicroSemi Libero
5144 proc IsLibero {} {
5145  return [expr {[info commands get_libero_version] != ""}]
5146 }
5147 
5148 # @brief Returns 1 if an element is a list, 0 otherwise
5149 #
5150 # @param[in] element The element to search
5151 # @param[in] list The list to search into
5152 # @param[in] regex An optional regex to match. If 0, the element should match exactly an object in the list
5153 # @param[in] nocase If 1, perform case-insensitive comparison
5154 proc IsInList {element list {regex 0} {nocase 0}} {
5155  foreach x $list {
5156  if {$regex == 1} {
5157  if {$nocase == 1} {
5158  if {[regexp -nocase $x $element]} {
5159  return 1
5160  }
5161  } else {
5162  if {[regexp $x $element]} {
5163  return 1
5164  }
5165  }
5166  } elseif {$regex == 0} {
5167  if {$nocase == 1} {
5168  if {[string tolower $x] eq [string tolower $element]} {
5169  return 1
5170  }
5171  } else {
5172  if {$x eq $element} {
5173  return 1
5174  }
5175  }
5176  }
5177  }
5178  return 0
5179 }
5180 
5181 
5182 ## @brief Returns true, if the IDE is ISE/PlanAhead
5183 proc IsISE {} {
5184  if {[IsXilinx]} {
5185  return [expr {[string first PlanAhead [version]] == 0}]
5186  } else {
5187  return 0
5188  }
5189 }
5190 
5191 ## @brief Returns true, if IDE is Quartus
5192 proc IsQuartus {} {
5193  if {[catch {package require ::quartus::flow} result]} {
5194  # not available
5195  return 0
5196  } else {
5197  # available
5198  return 1
5199  }
5200 }
5201 
5202 ## Check if a path is absolute or relative
5203 #
5204 # @param[in] the path to check
5205 #
5206 proc IsRelativePath {path} {
5207  if {[string index $path 0] == "/" || [string index $path 0] == "~"} {
5208  return 0
5209  } else {
5210  return 1
5211  }
5212 }
5213 
5214 ## @brief Returns true if the Synthesis tool is Synplify
5215 proc IsSynplify {} {
5216  return [expr {[info commands program_version] != ""}]
5217 }
5218 
5219 ## @brief Returns true, if we are in tclsh
5220 proc IsTclsh {} {
5221  return [expr {![IsQuartus] && ![IsXilinx] && ![IsVitisClassic] && ![IsVitisUnified] && ![IsLibero] && ![IsSynplify] && ![IsDiamond]}]
5222 }
5223 
5224 # @brief Find out if the given file is a Verilog or SystemVerilog file
5225 #
5226 # @param[in] file The file to check
5227 # @param[out] 1 if it's Verilog/SystemVerilog 0 if it's not
5228 #
5229 proc IsVerilog {file} {
5230  if {[file extension $file] == ".v" || [file extension $file] == ".sv"} {
5231  return 1
5232  } else {
5233  return 0
5234  }
5235 }
5236 
5237 ## @brief Find out if the given Xilinx part is a Versal chip
5238 #
5239 # @param[out] 1 if it's Versal 0 if it's not
5240 # @param[in] part The FPGA part
5241 #
5242 proc IsVersal {part} {
5243  if {[get_property ARCHITECTURE [get_parts $part]] eq "versal"} {
5244  return 1
5245  } else {
5246  return 0
5247  }
5248 }
5249 
5250 ## @brief Returns true, if the IDE is Vivado
5251 proc IsVivado {} {
5252  if {[IsXilinx]} {
5253  return [expr {[string first Vivado [version]] == 0}]
5254  } else {
5255  return 0
5256  }
5257 }
5258 
5259 ## @brief Return true, if the IDE is Xilinx (Vivado or ISE)
5260 proc IsXilinx {} {
5261  if {[info commands version] != ""} {
5262  set current_version [version]
5263  if {[string first PlanAhead $current_version] == 0 || [string first Vivado $current_version] == 0} {
5264  return 1
5265  } elseif {[string first xsct $current_version] == 0} {
5266  return 0
5267  } else {
5268  Msg Warning "This IDE has the version command but it is not PlanAhead or Vivado: $current_version"
5269  return 0
5270  }
5271  } else {
5272  return 0
5273  }
5274 }
5275 
5276 ## @brief Returns true, if the IDE is vitis_classic
5277 proc IsVitisClassic {} {
5278  if {[info exists globalSettings::vitis_classic]} {
5279  return $globalSettings::vitis_classic
5280  }
5281  return [expr {[info commands platform] != ""}]
5282 }
5283 
5284 ## @brief Returns true, if the IDE is vitis_unified
5285 proc IsVitisUnified {} {
5286  if {[info exists globalSettings::vitis_unified]} {
5287  return $globalSettings::vitis_unified
5288  }
5289  return 0
5290 }
5291 
5292 ## @brief Execute a Python command via Vitis Unified command-line tool and display output in real-time
5293 #
5294 # @param[in] python_script Full path to the Python script (e.g., PlatformCommands.py or AppCommands.py)
5295 # @param[in] command The command to execute (e.g., "create_platform", "configure_app", "app_list")
5296 # @param[in] args List of arguments to pass to the command
5297 # @param[in] error_prefix Prefix for error messages (e.g., "Failed to create platform" or "Failed to configure app")
5298 # @param[in] output_var Optional variable name to store the output (if not provided, output is only printed)
5299 # @param[out] 1 on success, 0 on failure
5300 #
5301 proc ExecuteVitisUnifiedCommand {python_script command args {error_prefix "Failed to execute command"} {output_var ""}} {
5302  set cmdlist [list vitis -s $python_script $command]
5303  foreach arg $args {
5304  lappend cmdlist $arg
5305  }
5306  lappend cmdlist 2>@1
5307 
5308  Msg Debug "Executing: vitis -s $python_script $command $args"
5309 
5310  # Set PYTHONUNBUFFERED environment variable for real-time output
5311  set env(PYTHONUNBUFFERED) "1"
5312 
5313  # Open pipe and configure for line buffering
5314  if {[catch {set pipe [open "|$cmdlist" "r"]} err]} {
5315  Msg Error "$error_prefix: Failed to open pipe: $err"
5316  return 0
5317  }
5318 
5319  fconfigure $pipe -buffering line
5320  set script_output ""
5321  set vitis_version ""
5322 
5323  # Patterns to identify Vitis banner messages, these will be filtered out
5324  set vitis_banner_patterns {
5325  "*Vitis Development Environment*"
5326  "*Vitis v*"
5327  "*SW Build*"
5328  "*Copyright*Xilinx*"
5329  "*Copyright*Advanced Micro Devices*"
5330  "*All Rights Reserved*"
5331  }
5332 
5333  # Read and display output line by line
5334  while {[gets $pipe line] >= 0} {
5335  if {$line ne ""} {
5336  if {[string match "*Vitis v*" $line]} {
5337  if {[regexp {Vitis\s+v([0-9]+)\.([0-9]+)(?:\.[0-9]+)?} $line -> major minor]} {
5338  set year_last_two [string range $major end-1 end]
5339  set vitis_version "$year_last_two.$minor"
5340  }
5341  }
5342 
5343  # Filter out Vitis banner messages
5344  set is_banner 0
5345  foreach pattern $vitis_banner_patterns {
5346  if {[string match $pattern $line]} {
5347  set is_banner 1
5348  break
5349  }
5350  }
5351  if {!$is_banner} {
5352  if {![string match "INFO:*" $line] && ![string match "WARNING:*" $line] && ![string match "ERROR:*" $line] && ![string match "DEBUG:*" $line]} {
5353  if {$vitis_version ne ""} {
5354  set line "INFO: \[Vitis_v$vitis_version\] $line"
5355  } else {
5356  set line "INFO: $line"
5357  }
5358  }
5359  puts "$line"
5360  append script_output "$line\n"
5361  }
5362  }
5363  }
5364 
5365  # Close pipe and check exit code
5366  set exit_code 0
5367  if {[catch {close $pipe} err]} {
5368  if {[regexp {exit (\d+)} $err -> exit_code]} {
5369  if {$exit_code != 0} {
5370  Msg Error "$error_prefix (exit code: $exit_code)"
5371  if {$output_var ne ""} {
5372  upvar $output_var output
5373  set output $script_output
5374  }
5375  return 0
5376  }
5377  } else {
5378  Msg Error "$error_prefix: $err"
5379  if {$output_var ne ""} {
5380  upvar $output_var output
5381  set output $script_output
5382  }
5383  return 0
5384  }
5385  }
5386 
5387  # Return output if requested
5388  if {$output_var ne ""} {
5389  upvar $output_var output
5390  set output $script_output
5391  }
5392 
5393  return 1
5394 }
5395 
5396 ## @brief Find out if the given Xilinx part is a Versal chip
5397 #
5398 # @param[out] 1 if it's Zynq 0 if it's not
5399 # @param[in] part The FPGA part
5400 #
5401 proc IsZynq {part} {
5402  if {[regexp {^(xc7z|xczu).*} $part]} {
5403  return 1
5404  } else {
5405  return 0
5406  }
5407 }
5408 
5409 proc ImportGHDL {project_name repo_path simset_name simset_dict {ext_path ""}} {
5410  set list_path "$repo_path/Top/$project_name/list"
5411  lassign [GetHogFiles -list_files {.src,.ext,.sim} -ext_path $ext_path $list_path $repo_path] src_files properties filesets
5412  cd $repo_path
5413 
5414 
5415  # Get Properties
5416  set properties [DictGet $simset_dict "properties"]
5417  set options [DictGet $properties "options"]
5418 
5419  # Import GHDL files
5420  set workdir Projects/$project_name/ghdl
5421  file delete -force $workdir
5422  file mkdir $workdir
5423  set import_log "$workdir/ghdl-import-${simset_name}.log"
5424  dict for {lib sources} $src_files {
5425  set libname [file rootname $lib]
5426  foreach f $sources {
5427  if {[file extension $f] != ".vhd" && [file extension $f] != ".vhdl"} {
5428  Msg Info "File $f is not a VHDL file, copying it in workfolder..."
5429  file copy -force $f $workdir
5430  } else {
5431  set file_path [Relative $repo_path $f]
5432  set import_log_file [open $import_log "a"]
5433  puts "ghdl -i --work=$libname --workdir=$workdir -fsynopsys --ieee=standard $options $file_path"
5434  puts $import_log_file "ghdl -i --work=$libname --workdir=$workdir -fsynopsys --ieee=standard $options $file_path"
5435  close $import_log_file
5436  lassign [GHDL "-i --work=$libname --workdir=$workdir -fsynopsys --ieee=standard $options $file_path" $import_log] ret result
5437  if {$ret != 0} {
5438  Msg CriticalWarning "GHDL import failed for file $f: $result"
5439  }
5440  }
5441  }
5442  }
5443  PrintFileContent $import_log
5444 }
5445 
5446 proc LaunchGHDL {project_name repo_path simset_name simset_dict {ext_path ""}} {
5447  set top_sim ""
5448  # Setting Simulation Properties
5449  set sim_props [DictGet $simset_dict "properties"]
5450  set options [DictGet $sim_props "options"]
5451  set runopts [DictGet $sim_props "run_options"]
5452 
5453  dict for {prop_name prop_val} $sim_props {
5454  set prop_name [string toupper $prop_name]
5455  if {$prop_name == "TOP"} {
5456  set top_sim $prop_val
5457  }
5458  }
5459  set workdir $repo_path/Projects/$project_name/ghdl
5460  set make_log "$workdir/ghdl-make-${simset_name}.log"
5461  set run_log "$workdir/ghdl-run-${simset_name}.log"
5462  cd $workdir
5463  # Analyse and elaborate the design
5464  set make_log_file [open $make_log "w"]
5465 
5466  puts "ghdl -m --work=$simset_name -fsynopsys --ieee=standard $options $top_sim"
5467  puts $make_log_file "ghdl -m --work=$simset_name -fsynopsys --ieee=standard $options $top_sim"
5468  close $make_log_file
5469 
5470  lassign [GHDL "-m --work=$simset_name -fsynopsys --ieee=standard $options $top_sim" $make_log] ret result
5471  PrintFileContent $make_log
5472  if {$ret != 0} {
5473  Msg Error "GHDL make failed for $top_sim: $result"
5474  return
5475  }
5476 
5477  set run_log_file [open $run_log "w"]
5478  puts "ghdl -r --work=$simset_name -fsynopsys --ieee=standard $options $top_sim $runopts"
5479  puts $run_log_file "ghdl -r --work=$simset_name -fsynopsys --ieee=standard $options $top_sim $runopts"
5480  close $run_log_file
5481 
5482  lassign [GHDL "-r --work=$simset_name -fsynopsys --ieee=standard $options $top_sim $runopts" $run_log] ret result
5483  PrintFileContent $run_log
5484 
5485  if {$ret != 0} {
5486  Msg Error "GHDL run failed for $top_sim: $result"
5487  return
5488  }
5489 
5490  cd $repo_path
5491 }
5492 
5493 # @brief Launch the Implementation, for the current IDE and project
5494 #
5495 # @param[in] reset Reset the Implementation run
5496 # @param[in] do_create Recreate the project
5497 # @param[in] run_folder The folder where to store the run results
5498 # @param[in] project_name The name of the project
5499 # @param[in] repo_path The main path of the git repository (Default .)
5500 # @param[in] njobs The number of parallel CPU jobs for the Implementation (Default 4)
5501 proc LaunchImplementation {reset do_create run_folder project_name {repo_path .} {njobs 4} {do_bitstream 0}} {
5502  Msg Info "Starting implementation flow..."
5503  if {[IsXilinx]} {
5504  if {$reset == 1} {
5505  Msg Info "Resetting run before launching implementation..."
5506  reset_run impl_1
5507  }
5508 
5509  # check for and remove any previous timing results in the folder
5510  if {[file exist "$run_folder/timing_ok.txt"]} {
5511  file delete "$run_folder/timing_ok.txt"
5512  }
5513  if {[file exist "$run_folder/timing_error.txt"]} {
5514  file delete "$run_folder/timing_error.txt"
5515  }
5516 
5517  if {[IsISE]} {
5518  source $repo_path/Hog/Tcl/integrated/pre-implementation.tcl
5519  }
5520 
5521  if {$do_bitstream == 1} {
5522  launch_runs impl_1 -to_step [BinaryStepName [get_property PART [current_project]]] -jobs $njobs -dir $run_folder
5523  } else {
5524  launch_runs impl_1 -jobs $njobs -dir $run_folder
5525  }
5526  wait_on_run impl_1
5527 
5528  if {[IsISE]} {
5529  Msg Info "running post-implementation"
5530  source $repo_path/Hog/Tcl/integrated/post-implementation.tcl
5531  if {$do_bitstream == 1} {
5532  Msg Info "running pre-bitstream"
5533  source $repo_path/Hog/Tcl/integrated/pre-bitstream.tcl
5534  Msg Info "running post-bitstream"
5535  source $repo_path/Hog/Tcl/integrated/post-bitstream.tcl
5536  }
5537  }
5538 
5539  set prog [get_property PROGRESS [get_runs impl_1]]
5540  set status [get_property STATUS [get_runs impl_1]]
5541  Msg Info "Run: impl_1 progress: $prog, status : $status"
5542 
5543  # Check timing
5544  if {[IsISE]} {
5545  set status_file [open "$run_folder/timing.txt" "w"]
5546  puts $status_file "## $project_name Timing summary"
5547 
5548  set f [open [lindex [glob "$run_folder/impl_1/*.twr" 0]]]
5549  set errs -1
5550  while {[gets $f line] >= 0} {
5551  if {[string match "Timing summary:" $line]} {
5552  while {[gets $f line] >= 0} {
5553  if {[string match "Timing errors:*" $line]} {
5554  set errs [regexp -inline -- {[0-9]+} $line]
5555  }
5556  if {[string match "*Footnotes*" $line]} {
5557  break
5558  }
5559  puts $status_file "$line"
5560  }
5561  }
5562  }
5563 
5564  close $f
5565  close $status_file
5566 
5567  if {$errs == 0} {
5568  Msg Info "Time requirements are met"
5569  file rename -force "$run_folder/timing.txt" "$run_folder/timing_ok.txt"
5570  set timing_ok 1
5571  } else {
5572  Msg CriticalWarning "Time requirements are NOT met"
5573  file rename -force "$run_folder/timing.txt" "$run_folder/timing_error.txt"
5574  set timing_ok 0
5575  }
5576  }
5577 
5578  if {[IsVivado]} {
5579  set wns [get_property STATS.WNS [get_runs [current_run]]]
5580  set tns [get_property STATS.TNS [get_runs [current_run]]]
5581  set whs [get_property STATS.WHS [get_runs [current_run]]]
5582  set ths [get_property STATS.THS [get_runs [current_run]]]
5583  set tpws [get_property STATS.TPWS [get_runs [current_run]]]
5584 
5585  if {$wns >= 0 && $whs >= 0 && $tpws >= 0} {
5586  Msg Info "Time requirements are met"
5587  set status_file [open "$run_folder/timing_ok.txt" "w"]
5588  set timing_ok 1
5589  } else {
5590  Msg CriticalWarning "Time requirements are NOT met"
5591  set status_file [open "$run_folder/timing_error.txt" "w"]
5592  set timing_ok 0
5593  }
5594 
5595  Msg Status "*** Timing summary ***"
5596  Msg Status "WNS: $wns"
5597  Msg Status "TNS: $tns"
5598  Msg Status "WHS: $whs"
5599  Msg Status "THS: $ths"
5600  Msg Status "TPWS: $tpws"
5601 
5602  struct::matrix m
5603  m add columns 5
5604  m add row
5605 
5606  puts $status_file "## $project_name Timing summary"
5607 
5608  m add row "| **Parameter** | \"**value (ns)**\" |"
5609  m add row "| --- | --- |"
5610  m add row "| WNS: | $wns |"
5611  m add row "| TNS: | $tns |"
5612  m add row "| WHS: | $whs |"
5613  m add row "| THS: | $ths |"
5614  m add row "| TPWS: | $tpws |"
5615 
5616  puts $status_file [m format 2string]
5617  puts $status_file "\n"
5618  if {$timing_ok == 1} {
5619  puts $status_file " Time requirements are met."
5620  } else {
5621  puts $status_file "Time requirements are **NOT** met."
5622  }
5623  puts $status_file "\n\n"
5624  close $status_file
5625  }
5626 
5627  if {$prog ne "100%"} {
5628  Msg Error "Implementation error"
5629  }
5630 
5631  #Go to repository path
5632  cd $repo_path
5633  set describe [GetHogDescribe [file normalize ./Top/$project_name] $repo_path]
5634  Msg Info "Git describe set to $describe"
5635 
5636  set dst_dir [file normalize "$repo_path/bin/$project_name\-$describe"]
5637 
5638  file mkdir $dst_dir
5639 
5640  #Version table
5641  if {[file exists $run_folder/versions.txt]} {
5642  file copy -force $run_folder/versions.txt $dst_dir
5643  } else {
5644  Msg Warning "No versions file found in $run_folder/versions.txt"
5645  }
5646  #Timing file
5647  set timing_files [glob -nocomplain "$run_folder/timing_*.txt"]
5648  set timing_file [file normalize [lindex $timing_files 0]]
5649 
5650  if {[file exists $timing_file]} {
5651  file copy -force $timing_file $dst_dir/
5652  } else {
5653  Msg Warning "No timing file found, not a problem if running locally"
5654  }
5655 
5656  #### XSA here only for Versal Segmented Configuration
5657  if {[IsVersal [get_property part [current_project]]]} {
5658  if {[get_property segmented_configuration [current_project]] == 1} {
5659  Msg Info "Versal Segmented configuration detected: exporting XSA file..."
5660  set xsa_name "$dst_dir/[file tail $project_name]\-$describe.xsa"
5661  write_hw_platform -fixed -force -file $xsa_name
5662  }
5663  }
5664  } elseif {[IsQuartus]} {
5665  set revision [get_current_revision]
5666 
5667  set ic [get_global_assignment -name INCREMENTAL_COMPILATION]
5668  if {$ic ne "" && [string toupper $ic] ne "OFF"} {
5669  Msg Info "Incremental compilation enabled ($ic), running Partition Merge..."
5670  if {[catch {execute_module -tool cdb -args "--merge=on"} result]} {
5671  Msg Error "Result: $result\nPartition Merge failed. See the report file.\n"
5672  }
5673  }
5674 
5675  if {[catch {execute_module -tool fit} result]} {
5676  Msg Error "Result: $result\n"
5677  Msg Error "Place & Route failed. See the report file.\n"
5678  } else {
5679  Msg Info "\nINFO: Place & Route was successful for revision $revision.\n"
5680  }
5681 
5682  if {[catch {execute_module -tool sta -args "--do_report_timing"} result]} {
5683  Msg Error "Result: $result\n"
5684  Msg Error "Time Quest failed. See the report file.\n"
5685  } else {
5686  Msg Info "Time Quest was successfully run for revision $revision.\n"
5687  load_package report
5688  load_report
5689  set panel "Timing Analyzer||Timing Analyzer Summary"
5690  set device [get_report_panel_data -name $panel -col 1 -row_name "Device Name"]
5691  set timing_model [get_report_panel_data -name $panel -col 1 -row_name "Timing Models"]
5692  set delay_model [get_report_panel_data -name $panel -col 1 -row_name "Delay Model"]
5693  #set slack [ get_timing_analysis_summary_results -slack ]
5694  Msg Info "*******************************************************************"
5695  Msg Info "Device: $device"
5696  Msg Info "Timing Models: $timing_model"
5697  Msg Info "Delay Model: $delay_model"
5698  Msg Info "Slack:"
5699  #Msg Info $slack
5700  Msg Info "*******************************************************************"
5701  }
5702  } elseif {[IsLibero]} {
5703  Msg Info "Starting implementation flow..."
5704  if {[catch {run_tool -name {PLACEROUTE}}]} {
5705  Msg Error "PLACEROUTE FAILED!"
5706  } else {
5707  Msg Info "PLACEROUTE PASSED."
5708  }
5709 
5710  # Check timing
5711  Msg Info "Run VERIFYTIMING ..."
5712  if {[catch {run_tool -name {VERIFYTIMING} -script {Hog/Tcl/integrated/libero_timing.tcl}}]} {
5713  Msg CriticalWarning "VERIFYTIMING FAILED!"
5714  } else {
5715  Msg Info "VERIFYTIMING PASSED \n"
5716  }
5717 
5718  #Go to repository path
5719  cd $repo_path
5720 
5721  set describe [GetHogDescribe [file normalize ./Top/$project_name] $repo_path]
5722  Msg Info "Git describe set to $describe"
5723 
5724  set dst_dir [file normalize "$repo_path/bin/$project_name\-$describe"]
5725  file mkdir $dst_dir/reports
5726 
5727  #Version table
5728  if {[file exists $run_folder/versions.txt]} {
5729  file copy -force $run_folder/versions.txt $dst_dir
5730  } else {
5731  Msg Warning "No versions file found in $run_folder/versions.txt"
5732  }
5733  #Timing file
5734  set timing_file_path [file normalize "$repo_path/Projects/timing_libero.txt"]
5735  if {[file exists $timing_file_path]} {
5736  file copy -force $timing_file_path $dst_dir/reports/Timing.txt
5737  set timing_file [open $timing_file_path "r"]
5738  set status_file [open "$dst_dir/timing.txt" "w"]
5739  puts $status_file "## $project_name Timing summary\n\n"
5740  puts $status_file "| | |"
5741  puts $status_file "| --- | --- |"
5742  while {[gets $timing_file line] >= 0} {
5743  if {[string match "SUMMARY" $line]} {
5744  while {[gets $timing_file line] >= 0} {
5745  if {[string match "END SUMMARY" $line]} {
5746  break
5747  }
5748  if {[string first ":" $line] == -1} {
5749  continue
5750  }
5751  set out_string "| [string map {: | } $line] |"
5752  puts $status_file "$out_string"
5753  }
5754  }
5755  }
5756  } else {
5757  Msg Warning "No timing file found, not a problem if running locally"
5758  }
5759  } elseif {[IsDiamond]} {
5760  set force_rst ""
5761  if {$reset == 1} {
5762  set force_rst "-forceOne"
5763  }
5764  prj_run Map $force_rst
5765  prj_run PAR $force_rst
5766 
5767  # TODO: Check Timing for Diamond
5768  }
5769 }
5770 
5771 # @brief Re-generate the bitstream, for the current IDE and project (Vivado only for the moment). \
5772 # Useful for a Vivado-Vitis project to update the bitstream with a new ELF or to generate a new \
5773 # bootimage (ZYNQ) without running the full workflow.
5774 #
5775 # @param[in] project_name The name of the project
5776 # @param[in] repo_path The main path of the git repository (Default .)
5777 proc GenerateBitstreamOnly {project_name {repo_path .}} {
5778  cd $repo_path
5779 
5780  # Open the project first
5781  set project_file [file normalize "$repo_path/Projects/$project_name/$project_name.xpr"]
5782  if {![file exists $project_file]} {
5783  Msg Error "Project file not found: $project_file. Please create the project first."
5784  return
5785  }
5786 
5787 
5788  # Check if impl_1 run exists
5789  set impl_runs [get_runs -quiet impl_1]
5790  if {[llength $impl_runs] == 0} {
5791  Msg Error "Implementation run 'impl_1' does not exist. Please run implementation first."
5792  return
5793  }
5794 
5795  # The binary file is written in the standard implementation run directory,
5796  # the post-bitstream script will then copy it (and all the other artifacts) into the bin directory
5797  set run_dir [get_property DIRECTORY [get_runs impl_1]]
5798  if {![file exists $run_dir]} {
5799  Msg Error "Implementation run directory not found: $run_dir. Please check your implementation run."
5800  return
5801  }
5802  cd $run_dir
5803 
5804  Msg Info "Running pre-bitstream..."
5805  source $repo_path/Hog/Tcl/integrated/pre-bitstream.tcl
5806 
5807  Msg Info "Writing bitstream for $project_name..."
5808  open_run impl_1
5809  set top_name [GetTopModule]
5810  write_bitstream -force [file normalize "$run_dir/$top_name.bit"]
5811 
5812  Msg Info "Running post-bitstream..."
5813  source $repo_path/Hog/Tcl/integrated/post-bitstream.tcl
5814 }
5815 
5816 # @brief Launch the simulation (Vivado only for the moment)
5817 #
5818 # @param[in] project_name The name of the project
5819 # @param[in] lib_path The path to the simulation libraries
5820 # @param[in] simsets The simulation sets to simulate
5821 # @param[in] repo_path The main path of the git repository
5822 proc LaunchSimulation {project_name lib_path simsets {repo_path .} {scripts_only 0} {compile_only 0}} {
5823  if {[IsVivado]} {
5824  ##################### SIMULATION #######################
5825  set project [file tail $project_name]
5826  set main_sim_folder [file normalize "$repo_path/Projects/$project_name/$project.sim/"]
5827  set simsets_todo ""
5828  if {$simsets != ""} {
5829  dict for {simset sim_dict} $simsets {
5830  lappend simsets_todo $simset
5831  }
5832  Msg Info "Will run only the following simulation's sets (if they exist): $simsets_todo"
5833  }
5834 
5835  if {$scripts_only == 1} {
5836  Msg Info "Only generating simulation scripts, not running simulations..."
5837  }
5838 
5839  if {$compile_only == 1} {
5840  Msg Info "Only compiling simulation libraries, not running simulations..."
5841  }
5842 
5843  set failed []
5844  set success []
5845  set sim_dic [dict create]
5846 
5847  Msg Info "Retrieving list of simulation sets..."
5848  foreach s [get_filesets] {
5849  # Default behaviour, dont use simpass string and simulation is not quiet
5850  set use_simpass_str 0
5851  set quiet_sim ""
5852 
5853  set type [get_property FILESET_TYPE $s]
5854  if {$type eq "SimulationSrcs"} {
5855  if {$simsets_todo != "" && $s ni $simsets_todo} {
5856  Msg Info "Skipping $s as it was not specified with the -simset option..."
5857  continue
5858  }
5859  set sim_dict [DictGet $simsets $s]
5860  set simulator [DictGet $sim_dict "simulator"]
5861  set_property "target_simulator" $simulator [current_project]
5862  set hog_sim_props [DictGet $sim_dict "hog"]
5863  dict for {prop_name prop_val} $hog_sim_props {
5864  # If HOG_SIMPASS_STR is set, use the HOG_SIMPASS_STR string to search for in logs, after simulation is done
5865  if {[string toupper $prop_name] == "HOG_SIMPASS_STR" && $prop_val != ""} {
5866  Msg Info "Setting simulation pass string as '$prop_val'"
5867  set use_simpass_str 1
5868  set simpass_str $prop_val
5869  }
5870  if {[string toupper $prop_name] == "HOG_SILENT_SIM" && $prop_val == 1} {
5871  set quiet_sim " -quiet"
5872  } else {
5873  set quiet_sim ""
5874  }
5875  }
5876 
5877  Msg Info "Creating simulation scripts for $s..."
5878  if {[file exists $repo_path/Top/$project_name/pre-simulation.tcl]} {
5879  Msg Info "Running $repo_path/Top/$project_name/pre-simulation.tcl"
5880  source $repo_path/Top/$project_name/pre-simulation.tcl
5881  }
5882  if {[file exists $repo_path/Top/$project_name/pre-$s-simulation.tcl]} {
5883  Msg Info "Running $repo_path/Top/$project_name/pre-$s-simulation.tcl"
5884  source Running $repo_path/Top/$project_name/pre-$s-simulation.tcl
5885  }
5886  current_fileset -simset $s
5887  set sim_dir $main_sim_folder/$s/behav
5888  set sim_output_logfile $sim_dir/xsim/simulate.log
5889  if {([string tolower $simulator] eq "xsim")} {
5890  set sim_name "xsim:$s"
5891 
5892  set simulation_command "launch_simulation $quiet_sim -simset [get_filesets $s]"
5893  if {[catch $simulation_command log]} {
5894  # Explicitly close xsim simulation, without closing Vivado
5895  close_sim
5896  Msg CriticalWarning "Simulation failed for $s, error info: $::errorInfo"
5897  lappend failed $sim_name
5898  } else {
5899  # Explicitly close xsim simulation, without closing Vivado
5900  close_sim
5901  # If we use simpass_str, search for the string and update return code from simulation if the string is not found in simulation log
5902  if {$use_simpass_str == 1} {
5903  # Get the simulation output log
5904  # Note, xsim should always output simulation.log, hence no check for existence
5905  set file_desc [open $sim_output_logfile r]
5906  set log [read $file_desc]
5907  close $file_desc
5908 
5909  Msg Info "Searching for simulation pass string: '$simpass_str'"
5910  if {[string first $simpass_str $log] == -1} {
5911  Msg CriticalWarning "Simulation failed for $s, error info: '$simpass_str' NOT found!"
5912  lappend failed $sim_name
5913  } else {
5914  # HOG_SIMPASS_STR found, success
5915  lappend success $sim_name
5916  }
5917  } else {
5918  #Rely on simulator exit code
5919  lappend success $sim_name
5920  }
5921  }
5922  } else {
5923  Msg Info "Simulation library path is set to $lib_path."
5924  set simlib_ok 1
5925  if {!([file exists $lib_path])} {
5926  Msg Warning "Could not find simulation library path: $lib_path, $simulator simulation will not work."
5927  set simlib_ok 0
5928  }
5929 
5930  if {$simlib_ok == 1} {
5931  set_property "compxlib.${simulator}_compiled_library_dir" [file normalize $lib_path] [current_project]
5932  launch_simulation -scripts_only -simset [get_filesets $s]
5933  set top_name [get_property TOP $s]
5934  set sim_script [file normalize $sim_dir/$simulator/]
5935  Msg Info "Adding simulation script location $sim_script for $s..."
5936  lappend sim_scripts $sim_script
5937  dict append sim_dic $sim_script $s
5938  } else {
5939  Msg Error "Cannot run $simulator simulations without a valid library path"
5940  exit -1
5941  }
5942  }
5943  }
5944  }
5945 
5946  if {[info exists sim_scripts] && $scripts_only == 0} {
5947  # Only for modelsim/questasim
5948  Msg Info "Generating IP simulation targets, if any..."
5949 
5950  foreach ip [get_ips] {
5951  generate_target simulation -quiet $ip
5952  }
5953 
5954 
5955  Msg Status "\n\n"
5956  Msg Info "====== Starting simulations runs ======"
5957  Msg Status "\n\n"
5958 
5959  foreach s $sim_scripts {
5960  cd $s
5961  set cmd ./compile.sh
5962  Msg Info " ************* Compiling: $s ************* "
5963  lassign [ExecuteRet $cmd] ret log
5964  set sim_name "comp:[dict get $sim_dic $s]"
5965  if {$ret != 0} {
5966  Msg CriticalWarning "Compilation failed for $s, error info: $::errorInfo"
5967  lappend failed $sim_name
5968  } else {
5969  lappend success $sim_name
5970  }
5971  Msg Info "###################### Compilation log starts ######################"
5972  Msg Info "\n\n$log\n\n"
5973  Msg Info "###################### Compilation log ends ######################"
5974 
5975  if {$compile_only == 1} {
5976  continue
5977  }
5978  if {[file exists "./elaborate.sh"]} {
5979  set cmd ./elaborate.sh
5980  Msg Info " ************* Elaborating: $s ************* "
5981  lassign [ExecuteRet $cmd] ret log
5982  set sim_name "elab:[dict get $sim_dic $s]"
5983  if {$ret != 0} {
5984  Msg CriticalWarning "Elaboration failed for $s, error info: $::errorInfo"
5985  lappend failed $sim_name
5986  } else {
5987  lappend success $sim_name
5988  }
5989  Msg Info "###################### Elaboration log starts ######################"
5990  Msg Info "\n\n$log\n\n"
5991  Msg Info "###################### Elaboration log ends ######################"
5992  }
5993  set cmd ./simulate.sh
5994  Msg Info " ************* Simulating: $s ************* "
5995  lassign [ExecuteRet $cmd] ret log
5996 
5997 
5998  # If SIMPASS_STR is set, search log for the string
5999  if {$use_simpass_str == 1} {
6000  if {[string first $simpass_str $log] == -1} {
6001  set ret 1
6002  }
6003  } else {
6004  Msg Debug "Simulation pass string not set, relying on simulator exit code."
6005  }
6006 
6007 
6008  set sim_name "sim:[dict get $sim_dic $s]"
6009  if {$ret != 0} {
6010  Msg CriticalWarning "Simulation failed for $s, error info: $::errorInfo"
6011  lappend failed $sim_name
6012  } else {
6013  lappend success $sim_name
6014  }
6015  Msg Info "###################### Simulation log starts ######################"
6016  Msg Info "\n\n$log\n\n"
6017  Msg Info "###################### Simulation log ends ######################"
6018  }
6019  }
6020 
6021 
6022  if {[llength $success] > 0} {
6023  set successes [join $success "\n"]
6024  Msg Info "The following simulation sets were successful:\n\n$successes\n\n"
6025  }
6026 
6027  if {[llength $failed] > 0} {
6028  set failures [join $failed "\n"]
6029  Msg Error "The following simulation sets have failed:\n\n$failures\n\n"
6030  exit -1
6031  } elseif {[llength $success] > 0} {
6032  Msg Info "All the [llength $success] compilations, elaborations and simulations were successful."
6033  }
6034 
6035  Msg Info "Simulation done."
6036  } else {
6037  Msg Warning "Simulation is not yet supported for [GetIDEName]."
6038  }
6039 }
6040 
6041 # @brief Launch the RTL Analysis, for the current IDE and project
6042 #
6043 # @param[in] repo_path The main path of the git repository (Default .)
6044 proc LaunchRTLAnalysis {repo_path {pre_rtl_file ""} {post_rtl_file ""}} {
6045  if {[IsVivado]} {
6046  if {[file exists $pre_rtl_file]} {
6047  Msg Info "Found pre-rtl Tcl script $pre_rtl_file, executing it..."
6048  source $pre_rtl_file
6049  }
6050  Msg Info "Starting RTL Analysis..."
6051  cd $repo_path
6052  synth_design -rtl -name rtl_1
6053  if {[file exists $post_rtl_file]} {
6054  Msg Info "Found post-rtl Tcl script $post_rtl_file, executing it..."
6055  source $post_rtl_file
6056  }
6057  } else {
6058  Msg Warning "RTL Analysis is not yet supported for [GetIDEName]."
6059  }
6060 }
6061 
6062 # @brief Launch the synthesis, for the current IDE and project
6063 #
6064 # @param[in] reset Reset the Synthesis run
6065 # @param[in] do_create Recreate the project
6066 # @param[in] run_folder The folder where to store the run results
6067 # @param[in] project_name The name of the project
6068 # @param[in] repo_path The main path of the git repository (Default .)
6069 # @param[in] ext_path The path of source files external to the git repo (Default "")
6070 # @param[in] njobs The number of parallel CPU jobs for the synthesis (Default 4)
6071 proc LaunchSynthesis {reset do_create run_folder project_name {repo_path .} {ext_path ""} {njobs 4}} {
6072  if {[IsXilinx]} {
6073  if {$reset == 1} {
6074  Msg Info "Resetting run before launching synthesis..."
6075  reset_run synth_1
6076  }
6077  if {[IsISE]} {
6078  source $repo_path/Hog/Tcl/integrated/pre-synthesis.tcl
6079  }
6080  launch_runs synth_1 -jobs $njobs -dir $run_folder
6081  wait_on_run synth_1
6082  set prog [get_property PROGRESS [get_runs synth_1]]
6083  set status [get_property STATUS [get_runs synth_1]]
6084  Msg Info "Run: synth_1 progress: $prog, status : $status"
6085  # Copy IP reports in bin/
6086  set ips [get_ips *]
6087 
6088  #go to repository path
6089  cd $repo_path
6090 
6091  set describe [GetHogDescribe [file normalize ./Top/$project_name] $repo_path]
6092  Msg Info "Git describe set to $describe"
6093 
6094  foreach ip $ips {
6095  set xci_file [get_property IP_FILE $ip]
6096 
6097  set xci_path [file dirname $xci_file]
6098  set xci_ip_name [file rootname [file tail $xci_file]]
6099  foreach rptfile [glob -nocomplain -directory $xci_path *.rpt] {
6100  file copy $rptfile $repo_path/bin/$project_name-$describe/reports
6101  }
6102  }
6103 
6104  if {$prog ne "100%"} {
6105  Msg Error "Synthesis error, status is: $status"
6106  }
6107  } elseif {[IsQuartus]} {
6108  # TODO: Missing reset
6109  set project [file tail [file rootname $project_name]]
6110 
6111  Msg Info "Number of jobs set to $njobs."
6112  set_global_assignment -name NUM_PARALLEL_PROCESSORS $njobs
6113 
6114 
6115  # keep track of the current revision and of the top level entity name
6116  set describe [GetHogDescribe [file normalize $repo_path/Top/$project_name] $repo_path]
6117  #set top_level_name [ get_global_assignment -name TOP_LEVEL_ENTITY ]
6118  set revision [get_current_revision]
6119 
6120  #run PRE_FLOW_SCRIPT by hand
6121  set tool_and_command [split [get_global_assignment -name PRE_FLOW_SCRIPT_FILE] ":"]
6122  set tool [lindex $tool_and_command 0]
6123  set pre_flow_script [lindex $tool_and_command 1]
6124  set cmd "$tool -t $pre_flow_script quartus_map $project $revision"
6125  #Close project to avoid conflict with pre synthesis script
6126  project_close
6127 
6128  lassign [ExecuteRet {*}$cmd] ret log
6129  if {$ret != 0} {
6130  Msg Warning "Can not execute command $cmd"
6131  Msg Warning "LOG: $log"
6132  } else {
6133  Msg Info "Pre flow script executed!"
6134  Msg Info "Pre flow script log: \n$log"
6135  }
6136 
6137  # Re-open project
6138  if {![is_project_open]} {
6139  Msg Info "Re-opening project file $project_name..."
6140  project_open $project -current_revision
6141  }
6142 
6143  # Generate IP Files
6144  if {[catch {execute_module -tool ipg -args "--clean"} result]} {
6145  Msg Error "Result: $result\n"
6146  Msg Error "IP Generation failed. See the report file.\n"
6147  } else {
6148  Msg Info "IP Generation was successful for revision $revision.\n"
6149  }
6150 
6151  # Execute synthesis
6152  if {[catch {execute_module -tool map -args "--parallel"} result]} {
6153  Msg Error "Result: $result\n"
6154  Msg Error "Analysis & Synthesis failed. See the report file.\n"
6155  } else {
6156  Msg Info "Analysis & Synthesis was successful for revision $revision.\n"
6157  }
6158  } elseif {[IsLibero]} {
6159  # TODO: Missing Reset
6160  defvar_set -name RWNETLIST_32_64_MIXED_FLOW -value 0
6161 
6162  Msg Info "Run SYNTHESIS..."
6163  if {[catch {run_tool -name {SYNTHESIZE}}]} {
6164  Msg Error "SYNTHESIZE FAILED!"
6165  } else {
6166  Msg Info "SYNTHESIZE PASSED!"
6167  }
6168  } elseif {[IsDiamond]} {
6169  # TODO: Missing Reset
6170  set force_rst ""
6171  if {$reset == 1} {
6172  set force_rst "-forceOne"
6173  }
6174  prj_run Synthesis $force_rst
6175  if {[prj_syn] == "synplify"} {
6176  prj_run Translate $force_rst
6177  }
6178  } else {
6179  Msg Error "Impossible condition. You need to run this in an IDE."
6180  exit 1
6181  }
6182 }
6183 
6184 
6185 # @brief Launch the Vitis build
6186 #
6187 # @param[in] project_name The name of the project
6188 # @param[in] repo_path The main path of the git repository (Default ".")
6189 # @param[in] stage The stage of the build (Default "presynth")
6190 proc LaunchVitisBuild {project_name {repo_path .} {stage "presynth"}} {
6191  set proj_name $project_name
6192  set bin_dir [file normalize "$repo_path/bin"]
6193 
6194  cd $repo_path
6195 
6196  # Get app list
6197  if {[IsVitisUnified]} {
6198  set vitis_workspace [file normalize "$repo_path/Projects/$project_name/vitis_unified"]
6199  set python_script [file normalize "$repo_path/Hog/Other/Python/VitisUnified/AppCommands.py"]
6200  set json_output ""
6201  if {![ExecuteVitisUnifiedCommand $python_script "app_list" [list $vitis_workspace] "Failed to get app list from Vitis Unified" json_output]} {
6202  Msg Error "Failed to get app list from Vitis Unified"
6203  set ws_apps ""
6204  } else {
6205  if {[catch {package require json}]} {
6206  Msg Error "JSON package not available for parsing Vitis Unified app list"
6207  set ws_apps ""
6208  } else {
6209  set json_output_filtered ""
6210  if {[regexp -lineanchor {\{.*\}} $json_output json_output_filtered]} {
6211  set ws_apps [json::json2dict $json_output_filtered]
6212  } else {
6213  set ws_apps [json::json2dict $json_output]
6214  }
6215  }
6216  }
6217  } elseif {[IsVitisClassic]} {
6218  if {[catch {set ws_apps [app list -dict]}]} {set ws_apps ""}
6219  } else {
6220  Msg Error "Impossible condition. You need to run this in a Vitis Unified or Vitis Classic IDE."
6221  exit 1
6222  }
6223 
6224  # Get repository versions
6225  lassign [GetRepoVersions [file normalize $repo_path/Top/$proj_name] $repo_path] commit version hog_hash hog_ver top_hash top_ver \
6226  libs hashes vers cons_ver cons_hash ext_names ext_hashes xml_hash xml_ver user_ip_repos user_ip_hashes user_ip_vers
6227  set this_commit [GetSHA]
6228  if {$commit == 0} {set commit $this_commit}
6229  set flavour [GetProjectFlavour $project_name]
6230  lassign [GetDateAndTime $commit] date timee
6231 
6232  # Configure apps only for Vitis Classic, build-config is not supported in Vitis Unified
6233  # build directory seems to be the default
6234  if {[IsVitisClassic]} {
6235  foreach app_name [dict keys $ws_apps] {
6236  app config -name $app_name -set build-config Release
6237  }
6238  }
6239 
6240  WriteGenerics "vitisbuild" $repo_path $proj_name $date $timee $commit $version $top_hash $top_ver $hog_hash $hog_ver $cons_ver $cons_hash $libs \
6241  $vers $hashes $ext_names $ext_hashes $user_ip_repos $user_ip_vers $user_ip_hashes $flavour $xml_ver $xml_hash
6242 
6243  # Build apps
6244  foreach app_name [dict keys $ws_apps] {
6245  if {[IsVitisUnified]} {
6246  # Build vitis unified app
6247  if {![ExecuteVitisUnifiedCommand $python_script "build_app" [list $app_name $vitis_workspace] "Failed to build app $app_name"]} {
6248  Msg Error "Failed to build app $app_name"
6249  continue
6250  }
6251  } elseif {[IsVitisClassic]} {
6252  app build -name $app_name
6253  }
6254  }
6255 
6256  if {$stage == "presynth"} {
6257  Msg Info "Done building apps for $project_name..."
6258  }
6259 
6260  if {[info exists ::globalSettings::vitis_only_pass] && $::globalSettings::vitis_only_pass == 1} {
6261  Msg Info "Skipping bin directory creation in vitis_only mode (post-bitstream.tcl handles artifacts)."
6262  return
6263  }
6264 
6265  Msg Info "Evaluating Hog describe for $project_name..."
6266  set describe [GetHogDescribe [file normalize ./Top/$project_name] $repo_path]
6267  Msg Info "Hog describe set to: $describe"
6268  set dst_dir [file normalize "$bin_dir/$proj_name\-$describe"]
6269  if {![file exists $dst_dir]} {
6270  Msg Info "Creating $dst_dir..."
6271  file mkdir $dst_dir
6272  }
6273 
6274  foreach app_name [dict keys $ws_apps] {
6275  if {[IsVitisUnified]} {
6276  set main_file "$repo_path/Projects/$project_name/vitis_unified/$app_name/build/$app_name.elf"
6277  } elseif {[IsVitisClassic]} {
6278  set main_file "$repo_path/Projects/$project_name/vitis_classic/$app_name/Release/$app_name.elf"
6279  }
6280  set dst_main [file normalize "$dst_dir/[file tail $proj_name]\-$app_name\-$describe.elf"]
6281 
6282  if {![file exists $main_file]} {
6283  Msg Error "No Vitis .elf file found. Perhaps there was an issue building it?"
6284  continue
6285  }
6286 
6287  Msg Info "Copying main binary file $main_file into $dst_main..."
6288  file copy -force $main_file $dst_main
6289  }
6290 }
6291 
6292 # @brief Launch the HLS build for all [hls:*] components in the project
6293 #
6294 # For each HLS component defined in hog.conf, this proc runs:
6295 # - C synthesis
6296 # - Implementation
6297 # - Collects reports (.rpt, .xml, .log) into bin/ for CI
6298 # Packaging is controlled by hls_config.cfg (package.output.format, etc.)
6299 # Simulations (CSIM/COSIM) are handled by LaunchHlsSimulation via the S command.
6300 #
6301 # @param[in] project_name The name of the project
6302 # @param[in] repo_path The main path of the git repository (Default ".")
6303 proc LaunchHlsBuild {project_name {repo_path .}} {
6304  set proj_name $project_name
6305  set bin_dir [file normalize "$repo_path/bin"]
6306 
6307  cd $repo_path
6308 
6309  set conf_file [file normalize "$repo_path/Top/$project_name/hog.conf"]
6310  if {![file exists $conf_file]} {
6311  Msg Error "Configuration file not found: $conf_file"
6312  return
6313  }
6314  set properties [ReadConf $conf_file]
6315  set hls_components [dict filter $properties key {hls:*}]
6316 
6317  if {[dict size $hls_components] == 0} {
6318  Msg Info "No HLS components found for project $project_name"
6319  return
6320  }
6321 
6322  set python_script [file normalize "$repo_path/Hog/Other/Python/VitisUnified/HlsCommands.py"]
6323 
6324  # Determine whether this is a mixed Vivado+Vitis project. In that case HLS
6325  # component outputs are grouped under bin/<proj>/vitis_hls/ to keep them
6326  # visually separate from Vivado's top-level utilization.txt / timing_*.txt.
6327  # For pure vitis_unified projects (no Vivado) HLS outputs sit directly under
6328  # bin/<proj>/<component>/ since there's nothing else to collide with.
6329  set ide_info [GetIDEFromConf $conf_file]
6330  set ide_name [string tolower [lindex $ide_info 0]]
6331  if {$ide_name eq "vivado_vitis_unified"} {
6332  set is_mixed_project 1
6333  } else {
6334  set is_mixed_project 0
6335  }
6336 
6337  dict for {hls_key hls_props} $hls_components {
6338  if {![regexp {^hls:(.+)$} $hls_key -> component_name]} {
6339  continue
6340  }
6341  set component_name [string trim $component_name]
6342  Msg Info "Building HLS component: $component_name"
6343 
6344  # Read HLS_CONFIG path from hog.conf (mandatory)
6345  set hls_cfg_rel ""
6346  if {[dict exists $hls_props hls_config]} {
6347  set hls_cfg_rel [dict get $hls_props hls_config]
6348  } elseif {[dict exists $hls_props HLS_CONFIG]} {
6349  set hls_cfg_rel [dict get $hls_props HLS_CONFIG]
6350  }
6351  if {$hls_cfg_rel eq ""} {
6352  Msg Error "HLS component '$component_name' missing HLS_CONFIG in hog.conf"
6353  continue
6354  }
6355 
6356  set cfg_file [file normalize "$repo_path/$hls_cfg_rel"]
6357  if {![file exists $cfg_file]} {
6358  Msg Error "HLS config file not found: $cfg_file (from HLS_CONFIG=$hls_cfg_rel)"
6359  continue
6360  }
6361 
6362  set hls_work_dir [file normalize "$repo_path/Projects/$project_name/vitis_unified/$component_name"]
6363  file mkdir $hls_work_dir
6364 
6365  # C synthesis
6366  Msg Info "Running C synthesis for HLS component '$component_name'..."
6367  if {
6368  ![ExecuteVitisUnifiedCommand $python_script "synthesis" \
6369  [list $component_name $cfg_file $hls_work_dir] \
6370  "Failed to run C synthesis for $component_name"]
6371  } {
6372  Msg Error "C synthesis failed for HLS component '$component_name'"
6373  continue
6374  }
6375 
6376  # Implementation
6377  Msg Info "Running implementation for HLS component '$component_name'..."
6378  if {
6379  ![ExecuteVitisUnifiedCommand $python_script "impl" \
6380  [list $component_name $cfg_file $hls_work_dir] \
6381  "Failed to run implementation for $component_name"]
6382  } {
6383  Msg Error "Implementation failed for HLS component '$component_name'"
6384  continue
6385  }
6386 
6387  # Export VHDL to source tree if VHDL_OUTPUT is set
6388  set vhdl_output ""
6389  if {[dict exists $hls_props vhdl_output]} {
6390  set vhdl_output [dict get $hls_props vhdl_output]
6391  } elseif {[dict exists $hls_props VHDL_OUTPUT]} {
6392  set vhdl_output [dict get $hls_props VHDL_OUTPUT]
6393  }
6394  if {$vhdl_output ne ""} {
6395  set vhdl_output_dir [file normalize "$repo_path/$vhdl_output"]
6396  Msg Info "Exporting VHDL for '$component_name' to $vhdl_output_dir..."
6397  if {
6398  ![ExecuteVitisUnifiedCommand $python_script "export_rtl" \
6399  [list $component_name $hls_work_dir $vhdl_output_dir vhdl] \
6400  "Failed to export VHDL for $component_name"]
6401  } {
6402  Msg Warning "Could not export VHDL for HLS component '$component_name'"
6403  }
6404  }
6405 
6406  # Export Verilog to source tree if VERILOG_OUTPUT is set
6407  set verilog_output ""
6408  if {[dict exists $hls_props verilog_output]} {
6409  set verilog_output [dict get $hls_props verilog_output]
6410  } elseif {[dict exists $hls_props VERILOG_OUTPUT]} {
6411  set verilog_output [dict get $hls_props VERILOG_OUTPUT]
6412  }
6413  if {$verilog_output ne ""} {
6414  set verilog_output_dir [file normalize "$repo_path/$verilog_output"]
6415  Msg Info "Exporting Verilog for '$component_name' to $verilog_output_dir..."
6416  if {
6417  ![ExecuteVitisUnifiedCommand $python_script "export_rtl" \
6418  [list $component_name $hls_work_dir $verilog_output_dir verilog] \
6419  "Failed to export Verilog for $component_name"]
6420  } {
6421  Msg Warning "Could not export Verilog for HLS component '$component_name'"
6422  }
6423  }
6424 
6425  # Export IP catalog ZIP to source tree if IP_OUTPUT is set
6426  set ip_output ""
6427  if {[dict exists $hls_props ip_output]} {
6428  set ip_output [dict get $hls_props ip_output]
6429  } elseif {[dict exists $hls_props IP_OUTPUT]} {
6430  set ip_output [dict get $hls_props IP_OUTPUT]
6431  }
6432  if {$ip_output ne ""} {
6433  set ip_output_dir [file normalize "$repo_path/$ip_output"]
6434  Msg Info "Exporting IP catalog for '$component_name' to $ip_output_dir..."
6435  if {
6436  ![ExecuteVitisUnifiedCommand $python_script "export_ip" \
6437  [list $component_name $hls_work_dir $ip_output_dir] \
6438  "Failed to export IP for $component_name"]
6439  } {
6440  Msg Error "Could not export IP for HLS component '$component_name'. Make sure package.output.format=ip_catalog is set in hls_config.cfg."
6441  }
6442  }
6443 
6444  # Collect reports into bin/ for CI and release notes
6445  Msg Info "Evaluating Hog describe for $project_name..."
6446  set describe [GetHogDescribe [file normalize ./Top/$project_name] $repo_path]
6447  Msg Info "Hog describe set to: $describe"
6448  set dst_dir [file normalize "$bin_dir/$proj_name\-$describe"]
6449  if {![file exists $dst_dir]} {
6450  Msg Info "Creating $dst_dir..."
6451  file mkdir $dst_dir
6452  }
6453 
6454  # Mixed projects group HLS components under bin/<proj>/vitis_hls/; pure
6455  # vitis_unified projects place them directly under bin/<proj>/
6456  if {$is_mixed_project} {
6457  set hls_out_dir [file normalize "$dst_dir/vitis_hls"]
6458  } else {
6459  set hls_out_dir $dst_dir
6460  }
6461 
6462  Msg Info "Collecting HLS reports for component '$component_name'..."
6463  if {
6464  ![ExecuteVitisUnifiedCommand $python_script "collect_reports" \
6465  [list $component_name $hls_work_dir $hls_out_dir] \
6466  "Failed to collect HLS reports for $component_name"]
6467  } {
6468  Msg Warning "No reports found for HLS component '$component_name'"
6469  }
6470 
6471  # Generate markdown summary files (utilization.txt, timing_ok.txt /
6472  # timing_error.txt) under <hls_out_dir>/<component>/. File names mirror the
6473  # Vivado convention so the existing CI logic (release notes assembly and
6474  # timing-failure detection) works for HLS components too
6475  Msg Info "Generating HLS release-notes summary for component '$component_name'..."
6476  if {
6477  ![ExecuteVitisUnifiedCommand $python_script "release_notes" \
6478  [list $component_name $hls_work_dir $hls_out_dir] \
6479  "Failed to generate HLS summary for $component_name"]
6480  } {
6481  Msg Warning "Could not generate HLS summary for component '$component_name'"
6482  }
6483 
6484  Msg Info "HLS component '$component_name' built successfully"
6485  }
6486 
6487  # For pure vitis_unified (HLS-only) projects, emit a top-level versions.txt
6488  # at the bin project root
6489  if {!$is_mixed_project && [info exists dst_dir] && [file isdirectory $dst_dir]} {
6490  set versions_file [file normalize "$dst_dir/versions.txt"]
6491  Msg Info "Generating top-level versions.txt for pure-HLS project at $versions_file"
6492  if {
6493  [catch {
6494  lassign [GetRepoVersions [file normalize $repo_path/Top/$project_name] $repo_path] \
6495  commit version hog_hash hog_ver top_hash top_ver \
6496  libs hashes vers cons_ver cons_hash \
6497  ext_names ext_hashes xml_hash xml_ver \
6498  user_ip_repos user_ip_hashes user_ip_vers
6499  if {$commit == 0} {set commit [GetSHA]}
6500  set version_str [HexVersionToString $version]
6501  set hog_ver_str [HexVersionToString $hog_ver]
6502  set top_ver_str [HexVersionToString $top_ver]
6503  set fh [open $versions_file "w"]
6504  puts $fh "## $proj_name Version Table\n"
6505  puts $fh "| **File set** | **Commit SHA** | **Version** |"
6506  puts $fh "| --- | --- | --- |"
6507  puts $fh "| Global | $commit | $version_str |"
6508  puts $fh "| Top Directory | $top_hash | $top_ver_str |"
6509  puts $fh "| Hog | $hog_hash | $hog_ver_str |"
6510  foreach l $libs v $vers h $hashes {
6511  set v_str [HexVersionToString $v]
6512  puts $fh "| **Lib:** $l | $h | $v_str |"
6513  }
6514  puts $fh "\n"
6515  close $fh
6516  } err]
6517  } {
6518  Msg Warning "Could not generate top-level versions.txt for pure-HLS project: $err"
6519  }
6520  }
6521 
6522  Msg Info "Done building HLS components for $project_name"
6523 }
6524 
6525 
6526 # @brief Launch HLS simulations for [hls:*] components in the project
6527 #
6528 # When called without hls_simsets (or with an empty list), runs all enabled
6529 # HLS simulations (CSIM if CSIM=true, COSIM if COSIM=true in hog.conf).
6530 # When called with specific targets (e.g. "csim:iir_lp_filter cosim:iir_lp_filter"),
6531 # runs only the requested simulations.
6532 # COSIM automatically triggers C synthesis first if not already done.
6533 # Triggered by the S (SIMULATE) command.
6534 #
6535 # @param[in] project_name The name of the project
6536 # @param[in] repo_path The main path of the git repository (Default ".")
6537 # @param[in] hls_simsets Optional list of specific HLS simsets to run (e.g. "csim:name" "cosim:name")
6538 proc LaunchHlsSimulation {project_name {repo_path .} {hls_simsets {}}} {
6539  cd $repo_path
6540 
6541  set conf_file [file normalize "$repo_path/Top/$project_name/hog.conf"]
6542  if {![file exists $conf_file]} {
6543  Msg Error "Configuration file not found: $conf_file"
6544  return
6545  }
6546  set properties [ReadConf $conf_file]
6547  set hls_components [dict filter $properties key {hls:*}]
6548 
6549  if {[dict size $hls_components] == 0} {
6550  Msg Info "No HLS components found for project $project_name"
6551  return
6552  }
6553 
6554  # Build a lookup of requested targets: component_name -> list of sim types
6555  # If hls_simsets is empty, we'll use hog.conf flags for all components
6556  set targeted_mode [expr {[llength $hls_simsets] > 0}]
6557  set targets [dict create]
6558  foreach entry $hls_simsets {
6559  if {[regexp {^(csim|cosim):(.+)$} $entry -> sim_type comp_name]} {
6560  dict lappend targets $comp_name $sim_type
6561  } else {
6562  Msg Error "Invalid HLS simset format '$entry'. Expected 'csim:<component>' or 'cosim:<component>'."
6563  Msg Info "Available HLS components in hog.conf:"
6564  dict for {hls_key hls_props} $hls_components {
6565  if {[regexp {^hls:(.+)$} $hls_key -> cname]} {
6566  Msg Info " - csim:[string trim $cname]"
6567  Msg Info " - cosim:[string trim $cname]"
6568  }
6569  }
6570  return
6571  }
6572  }
6573 
6574  set python_script [file normalize "$repo_path/Hog/Other/Python/VitisUnified/HlsCommands.py"]
6575 
6576  dict for {hls_key hls_props} $hls_components {
6577  if {![regexp {^hls:(.+)$} $hls_key -> component_name]} {
6578  continue
6579  }
6580  set component_name [string trim $component_name]
6581 
6582  # In targeted mode, skip components not in the request
6583  if {$targeted_mode && ![dict exists $targets $component_name]} {
6584  continue
6585  }
6586 
6587  # Read HLS_CONFIG path from hog.conf (mandatory)
6588  set hls_cfg_rel ""
6589  if {[dict exists $hls_props hls_config]} {
6590  set hls_cfg_rel [dict get $hls_props hls_config]
6591  } elseif {[dict exists $hls_props HLS_CONFIG]} {
6592  set hls_cfg_rel [dict get $hls_props HLS_CONFIG]
6593  }
6594  if {$hls_cfg_rel eq ""} {
6595  Msg Error "HLS component '$component_name' missing HLS_CONFIG in hog.conf"
6596  continue
6597  }
6598 
6599  set cfg_file [file normalize "$repo_path/$hls_cfg_rel"]
6600  if {![file exists $cfg_file]} {
6601  Msg Error "HLS config file not found: $cfg_file (from HLS_CONFIG=$hls_cfg_rel)"
6602  continue
6603  }
6604 
6605  set hls_work_dir [file normalize "$repo_path/Projects/$project_name/vitis_unified/$component_name"]
6606  file mkdir $hls_work_dir
6607 
6608  # Determine which simulations to run
6609  if {$targeted_mode} {
6610  # User explicitly requested these sim types via -simset
6611  set sim_types [dict get $targets $component_name]
6612  set run_csim [expr {"csim" in $sim_types}]
6613  set run_cosim [expr {"cosim" in $sim_types}]
6614 
6615  # Validate: check if the requested sim is enabled in hog.conf
6616  foreach st $sim_types {
6617  set flag_val ""
6618  if {[dict exists $hls_props $st]} {
6619  set flag_val [dict get $hls_props $st]
6620  } elseif {[dict exists $hls_props [string toupper $st]]} {
6621  set flag_val [dict get $hls_props [string toupper $st]]
6622  }
6623  set is_enabled [expr {[string tolower $flag_val] eq "true" || $flag_val eq "1"}]
6624  if {!$is_enabled} {
6625  set upper_st [string toupper $st]
6626  Msg Warning "HLS simulation '$st' requested for '$component_name' but\
6627  $upper_st is not enabled in \[hls:$component_name\] section of\
6628  hog.conf. Running it anyway."
6629  Msg Info "To enable it permanently, add '$upper_st=true' under\
6630  \[hls:$component_name\] in Top/$project_name/hog.conf"
6631  }
6632  }
6633  } else {
6634  # Default mode: read flags from hog.conf
6635  set run_csim 0
6636  set run_cosim 0
6637  foreach {key_lower key_upper} {csim CSIM cosim COSIM} {
6638  set val ""
6639  if {[dict exists $hls_props $key_lower]} {
6640  set val [dict get $hls_props $key_lower]
6641  } elseif {[dict exists $hls_props $key_upper]} {
6642  set val [dict get $hls_props $key_upper]
6643  }
6644  if {[string tolower $val] eq "true" || $val eq "1"} {
6645  set run_$key_lower 1
6646  }
6647  }
6648 
6649  if {!$run_csim && !$run_cosim} {
6650  Msg Info "No HLS simulations enabled for '$component_name' (set CSIM=true and/or COSIM=true in \[hls:$component_name\] in hog.conf)"
6651  continue
6652  }
6653  }
6654 
6655  Msg Info "Running HLS simulations for component: $component_name"
6656 
6657  # CSIM
6658  if {$run_csim} {
6659  Msg Info "Running C simulation for HLS component '$component_name'..."
6660  if {
6661  ![ExecuteVitisUnifiedCommand $python_script "csim" \
6662  [list $component_name $cfg_file $hls_work_dir] \
6663  "Failed to run C simulation for $component_name"]
6664  } {
6665  Msg Error "C simulation failed for HLS component '$component_name'"
6666  continue
6667  }
6668  }
6669 
6670  # COSIM (auto-run synthesis if needed)
6671  if {$run_cosim} {
6672  set syn_done_marker [file normalize "$hls_work_dir/hls/syn"]
6673  if {![file exists $syn_done_marker]} {
6674  Msg Info "C synthesis output not found for '$component_name', running synthesis automatically before co-simulation..."
6675  if {
6676  ![ExecuteVitisUnifiedCommand $python_script "synthesis" \
6677  [list $component_name $cfg_file $hls_work_dir] \
6678  "Failed to run C synthesis for $component_name"]
6679  } {
6680  Msg Error "C synthesis failed for HLS component '$component_name', cannot proceed with co-simulation"
6681  continue
6682  }
6683  }
6684 
6685  Msg Info "Running C/RTL co-simulation for HLS component '$component_name'..."
6686  if {
6687  ![ExecuteVitisUnifiedCommand $python_script "cosim" \
6688  [list $component_name $cfg_file $hls_work_dir] \
6689  "Failed to run co-simulation for $component_name"]
6690  } {
6691  Msg Error "C/RTL co-simulation failed for HLS component '$component_name'"
6692  continue
6693  }
6694  }
6695 
6696  Msg Info "HLS simulations completed for '$component_name'"
6697  }
6698 
6699  Msg Info "Done with HLS simulations for $project_name"
6700 }
6701 
6702 # @brief Returns the BIF file path from the properties
6703 #
6704 # @param[in] props A dictionary with the properties defined in Hog.conf
6705 # @param[in] app The application name
6706 # @return The path of the BIF file or empty string if not found
6707 proc GetProcFromProps {repo_path props platform} {
6708  if {[dict exists $props "platform:$platform" "BIF"]} {
6709  set bif_file [dict get $props "platform:$platform" "BIF"]
6710  if {[IsRelativePath $bif_file] == 1} {
6711  set bif_file "$repo_path/$bif_file"
6712  }
6713  return $bif_file
6714  } else {
6715  Msg CriticalWarning "BIF file not found in platform ($platform) properties, skipping bootable image (.bin) generation"
6716  return ""
6717  }
6718 }
6719 
6720 # @brief Returns the BIF file path from the properties
6721 #
6722 # @param[in] props A dictionary with the properties defined in Hog.conf
6723 # @param[in] platform The platform name
6724 # @return The path of the BIF file or empty string if not found
6725 proc GetBifFromProps {repo_path props platform} {
6726  if {[dict exists $props "platform:$platform" "BIF"]} {
6727  set bif_file [dict get $props "platform:$platform" "BIF"]
6728  if {[IsRelativePath $bif_file] == 1} {
6729  set bif_file "$repo_path/$bif_file"
6730  }
6731  return $bif_file
6732  } else {
6733  Msg CriticalWarning "BIF file not found in platform ($platform) properties, skipping bootable image (.bin) generation"
6734  return ""
6735  }
6736 }
6737 
6738 # @brief Returns the part number from the properties
6739 #
6740 # @param[in] props A dictionary with the properties defined in Hog.conf
6741 # @return The part number
6742 proc GetPartFromProps {props} {
6743  if {[dict exists $props "main" "PART"]} {
6744  return [string tolower [dict get $props "main" "PART"]]
6745  } else {
6746  Msg Error "Part number not found in properties"
6747  return ""
6748  }
6749 }
6750 
6751 # @brief Determines the architecture from the part number
6752 #
6753 # @param[in] part The FPGA part number (e.g., xczu4cg-fbvb900-1-e)
6754 # @return String with the architecture (zynqmp, zynq, versal, fpga, or unknown)
6755 proc GetArchFromPart {part} {
6756  set part [string tolower $part]
6757  # bootgen -arch values: zynq, zynqmp, versal, fpga
6758  if {[string match "xczu*" $part] || [string match "xqzu*" $part]} {
6759  return "zynqmp"
6760  } elseif {[string match "xc7z*" $part] || [string match "xq7z*" $part]} {
6761  return "zynq"
6762  } elseif {
6763  [string match "xcve*" $part] || [string match "xcvc*" $part] ||
6764  [string match "xcvp*" $part] || [string match "xcvm*" $part] ||
6765  [string match "xqve*" $part] || [string match "xck26*" $part]
6766  } {
6767  return "versal"
6768  } elseif {[string match "xc*" $part] || [string match "xq*" $part]} {
6769  # Non-SoC FPGA (e.g. Kintex-7 / Artix-7 / UltraScale) for MicroBlaze/RISC-V
6770  return "fpga"
6771  } else {
6772  Msg CriticalWarning "Unknown part number: $part"
6773  return "unknown"
6774  }
6775 }
6776 
6777 # @brief Returns a list of application names from the properties
6778 #
6779 # @param[in] props A dictionary with the applications properties defined in Hog.conf
6780 # @param[in] list_names If 1, returns a list of application names rather than a dictionary of applications
6781 proc GetAppsFromProps {props {list_names 0}} {
6782  set prop_apps [dict filter $props key {app:*}]
6783  set apps [dict create]
6784  set app_names [list]
6785 
6786  dict for {app_key app_value} $prop_apps {
6787  if {[regexp {^app:(.+)$} $app_key -> app_name]} {
6788  set app_name [string trim [string tolower $app_name]]
6789  # Convert only the keys of the inner dictionary to lowercase
6790  set app_value_lower [dict create]
6791  dict for {key value} $app_value {
6792  dict set app_value_lower [string tolower $key] $value
6793  }
6794  dict set apps $app_name $app_value_lower
6795  lappend app_names $app_name
6796  }
6797  }
6798  if {$list_names eq 1} {
6799  return $app_names
6800  }
6801  return $apps
6802 }
6803 
6804 # @brief Returns a list of platform names from the properties
6805 #
6806 # @param[in] props A dictionary with the platforms properties
6807 # @param[in] list_names If 1, returns a list of platform names rather than a dictionary of platforms
6808 # @param[in] lower_case If 1, returns the platform names in lowercase
6809 proc GetPlatformsFromProps {props {list_names 0} {lower_case 0}} {
6810  set platforms [dict create]
6811  set platform_names [list]
6812  set prop_platforms [dict filter $props key {platform:*}]
6813 
6814  dict for {platform_key platform_value} $prop_platforms {
6815  if {[regexp {^platform:(.+)$} $platform_key -> platform_name]} {
6816  if {$lower_case == 1} {
6817  set platform_name [string trim [string tolower $platform_name]]
6818  } else {
6819  set platform_name [string trim $platform_name]
6820  }
6821  dict set platforms $platform_name $platform_value
6822  lappend platform_names $platform_name
6823  }
6824  }
6825  if {$list_names eq 1} {
6826  return $platform_names
6827  }
6828  return $platforms
6829 }
6830 
6831 # @brief Generates boot artifacts for the application. If the application targets a soft \
6832 # processor (e.g. microblaze, riscv), the bitstream (.bit) memory is updated to include the ELF file. Otherwise, for \
6833 # applications targeting a hard processor (e.g. zynq, versal), a bootable binary image (.bin) is generated.
6834 #
6835 # @param[in] properties A dictionary with the properties defined in Hog.conf
6836 # @param[in] repo_path The main path of the git repository
6837 # @param[in] proj_dir The directory of the project
6838 # @param[in] bin_dir The directory of the generated binary files
6839 # @param[in] bitfile The bitfile to update
6840 # @param[in] mmi_file The MMI file to update
6841 proc GenerateBootArtifacts {properties repo_path proj_dir bin_dir proj_name describe bitfile mmi_file} {
6842  set elf_list [glob -nocomplain "$bin_dir/*.elf"]
6843  set apps [GetAppsFromProps $properties 0]
6844  set platforms [GetPlatformsFromProps $properties 1]
6845 
6846  if {[llength $elf_list] == 0} {
6847  Msg Warning "No ELF files found in $bin_dir, skipping generation of boot artifacts"
6848  return
6849  }
6850 
6851  if {![file exists $bitfile]} {
6852  Msg Warning "Bitfile $bitfile does not exist, skipping generation of boot artifacts"
6853  return
6854  }
6855 
6856  Msg Info "Generating boot artifacts for $proj_name..."
6857  Msg Info "Found apps: $apps"
6858  Msg Info "Found platforms: $platforms"
6859 
6860 
6861  # Update bitstream with ELF files for the applications targeting a soft processor (e.g. microblaze, riscv)
6862  foreach elf_file $elf_list {
6863  set elf_name [file rootname [file tail $elf_file]]
6864  Msg Info "Found elf name: $elf_name"
6865  Msg Info "Removing $describe from elf"
6866 
6867  # Extract application name from ELF file name
6868  if {[regexp "^(.+)-(.+)-$describe\$" $elf_name -> project_name elf_app]} {
6869  set elf_app [string trim [string tolower $elf_app]]
6870  Msg Info "Found elf_app: $elf_app"
6871  } else {
6872  Msg Error "Could not extract app name from elf file: $elf_name"
6873  continue
6874  }
6875  Msg Info "Removed project name ($project_name) and $describe from elf"
6876 
6877  set app_conf [dict get $apps $elf_app]
6878  set plat [dict get $app_conf "platform"]
6879  set app_proc [dict get $app_conf "proc"]
6880 
6881  # If the application targets a soft processor, update bitstream memory with ELF file
6882  if {[regexp -nocase {microblaze|risc} $app_proc]} {
6883  Msg Info "Detected soft processor ($app_proc) for $elf_app, updating bitstream memory with ELF file..."
6884 
6885  # updatemem needs the processor instance path as written in the MMI file, which includes
6886  # the block design wrapper hierarchy. The platform processor map is only used as a
6887  # fallback, since it contains names relative to the block design
6888  set proc_cell [GetProcInstPathFromMmi $mmi_file $app_proc]
6889  if {$proc_cell eq ""} {
6890  set proc_map_file [file normalize "$proj_dir/vitis_classic/${plat}.PROC_MAP"]
6891  set proc_map [ReadProcMap $proc_map_file]
6892  if {[dict exists $proc_map $app_proc]} {
6893  set proc_cell [dict get $proc_map $app_proc]
6894  Msg Info "Processor $app_proc not found in $mmi_file, using $proc_map_file instead."
6895  } else {
6896  Msg Error "Could not determine the instance path of processor '$app_proc' for $elf_app, \
6897  neither from $mmi_file nor from $proc_map_file. \
6898  Check that proc= in the \[app:$elf_app\] section of hog.conf matches the processor instance name."
6899  continue
6900  }
6901  }
6902  Msg Info "Updating memory at processor cell: $proc_cell"
6903 
6904  set update_mem_cmd "updatemem -force -meminfo $mmi_file -data $elf_file -bit $bitfile -proc $proc_cell -out $bitfile"
6905  set ret [catch {exec -ignorestderr {*}$update_mem_cmd >@ stdout} result]
6906  if {$ret != 0} {
6907  Msg Error "Error updating memory for $elf_app: $result"
6908  }
6909  Msg Info "Done updating memory for $elf_app"
6910  } else {
6911  Msg Info "Detected hard processor ($app_proc) for $elf_app. Make sure the .elf file is defined in the platform ($plat)\
6912  .bif file to be included in the bootable binary image (.bin) generation."
6913  }
6914  }
6915 
6916 
6917  # Generate a bootable binary image for platforms that have a .bif file defined
6918  foreach plat $platforms {
6919  set bif_file [GetBifFromProps $repo_path $properties $plat]
6920  if {$bif_file != ""} {
6921  Msg Info "Generating bootable binary image (.bin) for $plat"
6922  set arch [GetArchFromPart [GetPartFromProps $properties]]
6923  Msg Info "Architecture: $arch"
6924  Msg Info "BIF file: $bif_file"
6925  if {$arch eq "unknown"} {
6926  Msg CriticalWarning "Skipping bootable image (.bin) generation for $plat: \
6927  could not determine bootgen architecture from the FPGA part."
6928  continue
6929  }
6930  set bootgen_cmd "bootgen -arch $arch -image $bif_file -o i $bin_dir/$proj_name-$plat-$describe.bin -w on"
6931  set ret [catch {exec -ignorestderr {*}$bootgen_cmd >@ stdout} result]
6932  if {$ret != 0} {
6933  Msg Error "Error generating bootable binary image (.bin) for $plat: $result"
6934  }
6935  Msg Info "Done generating bootable binary image (.bin) for $plat"
6936  }
6937  }
6938 }
6939 
6940 ## @brief Generate output products for standalone XCI IPs
6941 #
6942 # IPs nested inside a block design, or inside another IP, must be generated by their
6943 # parent sub-design: generating them directly makes Vivado fail with
6944 # "[Vivado 12-3563] The Nested sub-design ... can only be generated by its parent sub-design".
6945 # Vivado flags every such file by setting its PARENT_COMPOSITE_FILE property, so that is what
6946 # we use to tell nested IPs from standalone ones.
6947 proc GenerateStandaloneXciTargets {} {
6948  if {![IsVivado]} {
6949  return
6950  }
6951 
6952  foreach xci [get_files -quiet *.xci] {
6953  set xci_file [get_files -quiet $xci]
6954  if {$xci_file eq ""} {
6955  continue
6956  }
6957 
6958  set parent ""
6959  catch {set parent [get_property -quiet PARENT_COMPOSITE_FILE $xci_file]}
6960  if {$parent ne ""} {
6961  Msg Debug "Skipping [file tail $xci], it is generated by its parent [file tail $parent]"
6962  continue
6963  }
6964 
6965  Msg Info "Generating targets for standalone IP [file tail $xci]..."
6966  if {[catch {generate_target all $xci_file} err]} {
6967  Msg CriticalWarning "Failed to generate targets for $xci: $err"
6968  }
6969  }
6970 }
6971 
6972 # @brief Returns the processor instance path to be used with updatemem -proc
6973 #
6974 # The updatemem -proc option must match the InstPath attribute of one of the Processor entries
6975 # of the MMI file, which Vivado writes together with the bitstream and which contains the full
6976 # hierarchical path of the processor.
6977 #
6978 # @param[in] mmi_file The path to the MMI file
6979 # @param[in] app_proc The processor name, as defined with proc= in hog.conf
6980 # @return The matching instance path, or an empty string if it could not be determined
6981 proc GetProcInstPathFromMmi {mmi_file app_proc} {
6982  if {![file exists $mmi_file]} {
6983  Msg Debug "MMI file not found: $mmi_file"
6984  return ""
6985  }
6986 
6987  set f [open $mmi_file "r"]
6988  set mmi_content [read $f]
6989  close $f
6990 
6991  set inst_paths [list]
6992  foreach {match inst_path} [regexp -all -inline {<Processor[^>]*InstPath\s*=\s*"([^"]+)"} $mmi_content] {
6993  if {[lsearch -exact $inst_paths $inst_path] < 0} {
6994  lappend inst_paths $inst_path
6995  }
6996  }
6997  Msg Debug "Processor instance paths found in $mmi_file: $inst_paths"
6998 
6999  foreach inst_path $inst_paths {
7000  if {[string equal -nocase [file tail $inst_path] $app_proc]} {
7001  return $inst_path
7002  }
7003  }
7004 
7005  # A design with a single processor is unambiguous, even if the names do not match
7006  if {[llength $inst_paths] == 1} {
7007  set inst_path [lindex $inst_paths 0]
7008  Msg Info "Processor $app_proc does not match [file tail $inst_path], \
7009  using the only processor found in the MMI file: $inst_path"
7010  return $inst_path
7011  }
7012 
7013  return ""
7014 }
7015 
7016 # @brief Reads the processor map file
7017 #
7018 # @param[in] proc_map_file The path to the processor map file
7019 # @return A dictionary with the processor map
7020 proc ReadProcMap {proc_map_file} {
7021  set proc_map [dict create]
7022  if {[file exists $proc_map_file]} {
7023  set f [open $proc_map_file "r"]
7024  while {[gets $f line] >= 0} {
7025  Msg Debug "Line: $line"
7026  if {[regexp {^(\S+)\s+(.+)$} $line -> key value]} {
7027  Msg Debug "Found key: $key, value: $value in proc map file"
7028  dict set proc_map $key $value
7029  }
7030  }
7031  close $f
7032  }
7033  return $proc_map
7034 }
7035 
7036 
7037 # Returns the list of all the Hog Projects in the repository
7038 #
7039 # @param[in] repo_path The main path of the git repository
7040 # @param[in] print if 1 print the list of projects in the repository, if 2 does not print test projects
7041 # @param[in] ret_conf if 1 returns conf file rather than list of project names
7042 proc ListProjects {{repo_path .} {print 1} {ret_conf 0} {silent 0}} {
7043  set top_path [file normalize $repo_path/Top]
7044  set confs [findFiles [file normalize $top_path] hog.conf]
7045  set projects ""
7046  set confs [lsort $confs]
7047  set g ""
7048 
7049  foreach c $confs {
7050  set p [Relative $top_path [file dirname $c]]
7051  if {$print >= 1} {
7052  set description [DescriptionFromConf $c]
7053  if {$description eq "test"} {
7054  set description " - Test project"
7055  } elseif {$description ne ""} {
7056  set description " - $description"
7057  }
7058 
7059  if {$print == 1 || $description ne " - Test project"} {
7060  set old_g $g
7061  set g [file dirname $p]
7062  # Print a list of the projects with relative IDE and description (second line comment in hog.conf)
7063  if {$g ne $old_g} {
7064  if {$silent == 0} {Msg Status ""}
7065  }
7066  if {$silent == 0} {Msg Status "$p \([GetIDEFromConf $c]\)$description"}
7067  }
7068  }
7069  lappend projects $p
7070  }
7071 
7072  if {$ret_conf == 0} {
7073  # Returns a list of project names
7074  return $projects
7075  } else {
7076  # Return the list of hog.conf with full path
7077  return $confs
7078  }
7079 }
7080 
7081 ## @brief Evaluates the md5 sum of a file
7082 #
7083 # @param[in] file_name: the name of the file of which you want to evaluate the md5 checksum
7084 proc Md5Sum {file_name} {
7085  if {!([file exists $file_name])} {
7086  Msg Warning "Could not find $file_name."
7087  set file_hash -1
7088  }
7089  if {[catch {package require md5 2.0.7} result]} {
7090  Msg Warning "Tcl package md5 version 2.0.7 not found ($result), will use command line..."
7091  set hash [lindex [Execute md5sum $file_name] 0]
7092  } else {
7093  set file_hash [string tolower [md5::md5 -hex -file $file_name]]
7094  }
7095 }
7096 
7097 ## @brief Merges two tcl dictionaries of lists
7098 #
7099 # If the dictionaries contain same keys, the list at the common key is a merging of the two
7100 #
7101 # @param[in] dict0 the name of the first dictionary
7102 # @param[in] dict1 the name of the second dictionary
7103 # @param[in] remove_duplicates if 1, removes duplicates from the merged dictionary (default 1)
7104 #
7105 # @return the merged dictionary
7106 #
7107 proc MergeDict {dict0 dict1 {remove_duplicates 1}} {
7108  set outdict [dict merge $dict1 $dict0]
7109  foreach key [dict keys $dict1] {
7110  if {[dict exists $dict0 $key]} {
7111  set temp_list [dict get $dict1 $key]
7112  foreach item $temp_list {
7113  # Avoid duplication
7114  if {[IsInList $item [DictGet $outdict $key]] == 0 || $remove_duplicates == 0} {
7115  # If the key exists in both dictionaries, append the item to the list
7116  dict lappend outdict $key $item
7117  }
7118  }
7119  }
7120  }
7121  return $outdict
7122 }
7123 
7124 # @brief Move an element in the list to the end
7125 #
7126 # @param[in] inputList the list
7127 # @param[in] element the element to move to the end of the list
7128 proc MoveElementToEnd {inputList element} {
7129  set index [lsearch $inputList $element]
7130  if {$index != -1} {
7131  set inputList [lreplace $inputList $index $index]
7132  lappend inputList $element
7133  }
7134  return $inputList
7135 }
7136 
7137 # @brief Open the project with the corresponding IDE
7138 #
7139 # @param[in] project_file The project_file
7140 # @param[in] repo_path The main path of the git repository
7141 proc OpenProject {project_file repo_path} {
7142  if {[IsXilinx]} {
7143  open_project $project_file
7144  } elseif {[IsQuartus]} {
7145  set project_folder [file dirname $project_file]
7146  set project [file tail [file rootname $project_file]]
7147  if {[file exists $project_folder]} {
7148  cd $project_folder
7149  if {![is_project_open]} {
7150  Msg Info "Opening existing project file $project_file..."
7151  project_open $project -current_revision
7152  }
7153  } else {
7154  Msg Error "Project directory not found for $project_file."
7155  return 1
7156  }
7157  } elseif {[IsLibero]} {
7158  Msg Info "Opening existing project file $project_file..."
7159  cd $repo_path
7160  open_project -file $project_file -do_backup_on_convert 1 -backup_file {./Projects/$project_file.zip}
7161  } elseif {[IsDiamond]} {
7162  Msg Info "Opening existing project file $project_file..."
7163  prj_project open $project_file
7164  } else {
7165  Msg Error "This IDE is currently not supported by Hog. Exiting!"
7166  }
7167 }
7168 
7169 ## @brief Return the operative system name
7170 proc OS {} {
7171  global tcl_platform
7172  return $tcl_platform(platform)
7173 }
7174 
7175 ## @brief Parse JSON file
7176 #
7177 # @param[in] JSON_FILE The JSON File to parse
7178 # @param[in] JSON_KEY The key to extract from the JSON file
7179 #
7180 # @returns -1 in case of failure, JSON KEY VALUE in case of success
7181 #
7182 proc ParseJSON {JSON_FILE JSON_KEY} {
7183  set result [catch {package require Tcl 8.4} TclFound]
7184  if {"$result" != "0"} {
7185  Msg CriticalWarning "Cannot find Tcl package version equal or higher than 8.4.\n $TclFound\n Exiting"
7186  return -1
7187  }
7188 
7189  set result [catch {package require json} JsonFound]
7190  if {"$result" != "0"} {
7191  Msg CriticalWarning "Cannot find JSON package equal or higher than 1.0.\n $JsonFound\n Exiting"
7192  return -1
7193  }
7194  set JsonDict [json::json2dict $JSON_FILE]
7195  set result [catch {dict get $JsonDict $JSON_KEY} RETURNVALUE]
7196  if {"$result" != "0"} {
7197  Msg CriticalWarning "Cannot find $JSON_KEY in $JSON_FILE\n Exiting"
7198  return -1
7199  } else {
7200  return $RETURNVALUE
7201  }
7202 }
7203 
7204 
7205 # @brief Check if a Hog project exists, and if it exists returns the conf file
7206 # if it doesnt returns 0
7207 #
7208 # @brief project The project name
7209 # @brief repo_path The main path of the git repository
7210 proc ProjectExists {project {repo_path .}} {
7211  set index [lsearch -exact [ListProjects $repo_path 0] $project]
7212 
7213  if {$index >= 0} {
7214  # if project exists we return the relative hog.conf file
7215  return [lindex [ListProjects $repo_path 0 1] $index]
7216  } else {
7217  Msg Warning "Project not found. Available projects in $repo_path are"
7218  ListProjects $repo_path
7219  puts ""
7220  Msg Error "Project $project not found in repository $repo_path"
7221  return 1
7222  }
7223 }
7224 
7225 ## Read a property configuration file and returns a dictionary
7226 #
7227 # @param[in] file_name the configuration file
7228 #
7229 # @return The dictionary
7230 #
7231 proc ReadConf {file_name} {
7232  if {[catch {package require inifile 0.2.3} ERROR]} {
7233  Msg Error "Could not find inifile package version 0.2.3 or higher.\n
7234  To use ghdl, libero or diamond with Hog, you need to install the tcllib package\n
7235  You can install it with 'sudo apt install tcllib' on Debian/Ubuntu or 'sudo dnf install tcllib' on Fedora/RedHat/CentOs."
7236  }
7237 
7238 
7239  ::ini::commentchar "#"
7240  set f [::ini::open $file_name]
7241  set properties [dict create]
7242  foreach sec [::ini::sections $f] {
7243  set new_sec $sec
7244  if {$new_sec == "files"} {
7245  continue
7246  }
7247  set key_pairs [::ini::get $f $sec]
7248  #manipulate strings here:
7249  regsub -all {\{\"} $key_pairs "\{" key_pairs
7250  regsub -all {\"\}} $key_pairs "\}" key_pairs
7251 
7252  dict set properties $new_sec [dict create {*}$key_pairs]
7253  }
7254 
7255  ::ini::close $f
7256 
7257  return $properties
7258 }
7259 
7260 ## @brief Function used to read the list of files generated at creation time by tcl scripts in Project/proj/.hog/extra.files
7261 #
7262 # @param[in] extra_file_name the path to the extra.files file
7263 # @returns a dictionary with the full name of the files as key and a SHA as value
7264 #
7265 proc ReadExtraFileList {extra_file_name} {
7266  set extra_file_dict [dict create]
7267  if {[file exists $extra_file_name]} {
7268  set file [open $extra_file_name "r"]
7269  set file_data [read $file]
7270  close $file
7271 
7272  set data [split $file_data "\n"]
7273  foreach line $data {
7274  if {![regexp {^ *$} $line] & ![regexp {^ *\#} $line]} {
7275  set ip_and_md5 [regexp -all -inline {\S+} $line]
7276  dict lappend extra_file_dict "[lindex $ip_and_md5 0]" "[lindex $ip_and_md5 1]"
7277  }
7278  }
7279  }
7280  return $extra_file_dict
7281 }
7282 
7283 
7284 # @brief Expand a Vitis HLS configuration file (hls_config.cfg) into the list of
7285 # repository files it references, so Hog can hash them for dirty/SHA
7286 # detection without forcing the user to duplicate the list in a .src
7287 #
7288 # @param[in] cfg_file Absolute or relative path to the hls_config.cfg file.
7289 # @return A de-duplicated list of normalized absolute file paths.
7290 #
7291 proc ExpandHlsConfigFiles {cfg_file} {
7292  set out [list]
7293  if {![file exists $cfg_file] || ![file isfile $cfg_file]} {
7294  return $out
7295  }
7296  set cfg_dir [file normalize [file dirname $cfg_file]]
7297 
7298  if {[catch {set fp [open $cfg_file r]} err]} {
7299  Msg Warning "ExpandHlsConfigFiles: cannot open $cfg_file: $err"
7300  return $out
7301  }
7302  set lines [split [read $fp] "\n"]
7303  close $fp
7304 
7305  foreach line $lines {
7306  if {[regexp {^[\t\s]*$} $line]} {continue}
7307  if {[regexp {^[\t\s]*\#} $line]} {continue}
7308  if {[regexp {^\s*\[.*\]\s*$} $line]} {continue}
7309  if {![regexp {^\s*[^=\s]+\s*=\s*(.+?)\s*$} $line -> value]} {continue}
7310 
7311  foreach tok [regexp -all -inline {\S+} $value] {
7312  if {[file pathtype $tok] eq "absolute"} {
7313  set candidate [file normalize $tok]
7314  } else {
7315  set candidate [file normalize [file join $cfg_dir $tok]]
7316  }
7317  if {[file isfile $candidate]} {
7318  lappend out $candidate
7319  } elseif {[file isdirectory $candidate]} {
7320  set stack [list $candidate]
7321  while {[llength $stack] > 0} {
7322  set d [lindex $stack 0]
7323  set stack [lrange $stack 1 end]
7324  foreach f [glob -nocomplain -directory $d -types f *] {
7325  lappend out [file normalize $f]
7326  }
7327  foreach sub [glob -nocomplain -directory $d -types d *] {
7328  lappend stack $sub
7329  }
7330  }
7331  }
7332  }
7333  }
7334  return [lsort -unique $out]
7335 }
7336 
7337 
7338 # @brief Discover HLS components declared in a project's hog.conf and return
7339 # the absolute path of each component's hls_config.cfg
7340 #
7341 # @param[in] conf_file Absolute path of the project's hog.conf
7342 # @param[in] repo_path Repository root (paths in HLS_CONFIG are relative to it)
7343 # @return A dict { component_name -> absolute_cfg_path }. Empty if no HLS
7344 proc GetHlsConfigsFromProjConf {conf_file repo_path} {
7345  set out [dict create]
7346  if {![file exists $conf_file]} {return $out}
7347  if {[catch {set properties [ReadConf $conf_file]} err]} {
7348  Msg Debug "GetHlsConfigsFromProjConf: cannot parse $conf_file: $err"
7349  return $out
7350  }
7351  set hls_components [dict filter $properties key {hls:*}]
7352  dict for {hls_key hls_props} $hls_components {
7353  if {![regexp {^hls:(.+)$} $hls_key -> component_name]} {continue}
7354  set component_name [string trim $component_name]
7355  set cfg_rel ""
7356  if {[dict exists $hls_props hls_config]} {
7357  set cfg_rel [dict get $hls_props hls_config]
7358  } elseif {[dict exists $hls_props HLS_CONFIG]} {
7359  set cfg_rel [dict get $hls_props HLS_CONFIG]
7360  }
7361  if {$cfg_rel eq ""} {continue}
7362  set cfg_abs [file normalize "$repo_path/$cfg_rel"]
7363  if {![file exists $cfg_abs]} {
7364  Msg CriticalWarning "HLS component '$component_name': HLS_CONFIG=$cfg_rel does not exist on disk."
7365  continue
7366  }
7367  dict set out $component_name $cfg_abs
7368  }
7369  return $out
7370 }
7371 
7372 
7373 # @brief Read a list file and return a list of three dictionaries
7374 #
7375 # Additional information is provided with text separated from the file name with one or more spaces
7376 #
7377 # @param[in] args The arguments are <list_file> <path> [options]
7378 # * list_file file containing vhdl list with optional properties
7379 # * path path the vhdl file are referred to in the list file
7380 # Options:
7381 # * -lib <library> name of the library files will be added to, if not given will be extracted from the file name
7382 # * -sha_mode if 1, the list files will be added as well and the IPs will be added to the file rather than to the special ip library.
7383 # The SHA mode should be used when you use the lists to calculate the git SHA, rather than to add the files to the project.
7384 #
7385 # @return a list of 3 dictionaries:
7386 # "libraries" has library name as keys and a list of filenames as values,
7387 # "properties" has as file names as keys and a list of properties as values
7388 # "filesets" has the fileset' names as keys and the list of associated libraries as values.
7389 proc ReadListFile {args} {
7390  if {[IsQuartus]} {
7391  load_package report
7392  if {[catch {package require cmdline} ERROR]} {
7393  puts "$ERROR\n If you are running this script on tclsh, you can fix this by installing 'tcllib'"
7394  return 1
7395  }
7396  }
7397  # tclint-disable line-length
7398  set parameters {
7399  {lib.arg "" "The name of the library files will be added to, if not given will be extracted from the file name."}
7400  {fileset.arg "" "The name of the library, from the main list file"}
7401  {sha_mode "If set, the list files will be added as well and the IPs will be added to the file rather than to the special IP library. The SHA mode should be used when you use the lists to calculate the git SHA, rather than to add the files to the project."}
7402  {print_log "If set, will use PrintFileTree for the VIEW directive"}
7403  {indent.arg "" "Used to indent files with the VIEW directive"}
7404  }
7405  # tclint-enable line-length
7406  set usage "USAGE: ReadListFile \[options\] <list file> <path>"
7407  if {[catch {array set options [cmdline::getoptions args $parameters $usage]}] || [llength $args] != 2} {
7408  Msg CriticalWarning "[cmdline::usage $parameters $usage]"
7409  return
7410  }
7411 
7412 
7413  set list_file [lindex $args 0]
7414  set path [lindex $args 1]
7415  set sha_mode $options(sha_mode)
7416  set lib $options(lib)
7417  set fileset $options(fileset)
7418  set print_log $options(print_log)
7419  set indent $options(indent)
7420 
7421  if {$sha_mode == 1} {
7422  set sha_mode_opt "-sha_mode"
7423  } else {
7424  set sha_mode_opt ""
7425  }
7426 
7427  if {$print_log == 1} {
7428  set print_log_opt "-print_log"
7429  } else {
7430  set print_log_opt ""
7431  }
7432 
7433  # if no library is given, work it out from the file name
7434  if {$lib eq ""} {
7435  set lib [file rootname [file tail $list_file]]
7436  }
7437  set fp [open $list_file r]
7438  set file_data [read $fp]
7439  close $fp
7440  set list_file_ext [file extension $list_file]
7441  switch $list_file_ext {
7442  .sim {
7443  if {$fileset eq ""} {
7444  # If fileset is empty, use the library name for .sim file
7445  set fileset "$lib"
7446  }
7447  }
7448  .con {
7449  set fileset "constrs_1"
7450  }
7451  default {
7452  set fileset "sources_1"
7453  }
7454  }
7455 
7456  set libraries [dict create]
7457  set filesets [dict create]
7458  set properties [dict create]
7459  # Process data file
7460  set data [split $file_data "\n"]
7461  set data [ExtractFilesSection $data]
7462  set n [llength $data]
7463  set last_printed ""
7464  if {$print_log == 1} {
7465  if {$indent eq ""} {
7466  set list_file_rel [file tail $list_file]
7467  Msg Status "\n$list_file_rel"
7468  }
7469  set last_printed [PrintFileTree $data $path "$indent"]
7470  }
7471  Msg Debug "$n lines read from $list_file."
7472  set cnt 0
7473 
7474  foreach line $data {
7475  # Exclude empty lines and comments
7476  if {![regexp {^[\t\s]*$} $line] & ![regexp {^[\t\s]*\#} $line]} {
7477  set file_and_prop [regexp -all -inline {\S+} $line]
7478  set srcfile [lindex $file_and_prop 0]
7479  set srcfile "$path/$srcfile"
7480 
7481  set srcfiles [glob -nocomplain $srcfile]
7482 
7483  # glob the file list for wildcards
7484  if {$srcfiles != $srcfile && ![string equal $srcfiles ""]} {
7485  Msg Debug "Wildcard source expanded from $srcfile to $srcfiles"
7486  } else {
7487  if {![file exists $srcfile]} {
7488  if {$print_log == 0} {
7489  Msg CriticalWarning "File: $srcfile (from list file: $list_file) does not exist."
7490  }
7491  continue
7492  }
7493  }
7494 
7495  foreach vhdlfile $srcfiles {
7496  if {[file exists $vhdlfile]} {
7497  set vhdlfile [file normalize $vhdlfile]
7498  set extension [file extension $vhdlfile]
7499  ### Set file properties
7500  set prop [lrange $file_and_prop 1 end]
7501 
7502  # The next lines should be inside the case for recursive list files, also we should check the allowed properties for the .src as well
7503  set library [lindex [regexp -inline {\ylib\s*=\s*(.+?)\y.*} $prop] 1]
7504  if {$library == ""} {
7505  set library $lib
7506  }
7507 
7508  if {$extension == $list_file_ext} {
7509  # Deal with recursive list files
7510  # In the next regex we use \S+ instead of .+? because we want to include forward slashes
7511  set ref_path [lindex [regexp -inline {\ypath\s*=\s*(\S+).*} $prop] 1]
7512  if {$ref_path eq ""} {
7513  set ref_path $path
7514  } else {
7515  set ref_path [file normalize $path/$ref_path]
7516  }
7517  Msg Debug "List file $vhdlfile found in list file, recursively opening it using path \"$ref_path\"..."
7518  if {$print_log == 1} {
7519  if {[file normalize $last_printed] ne [file normalize $vhdlfile]} {
7520  Msg Status "$indent Inside [file tail $vhdlfile]:"
7521  set last_printed ""
7522  }
7523  }
7524  lassign [ReadListFile {*}"-indent \" $indent\" -lib $library -fileset $fileset $sha_mode_opt $print_log_opt $vhdlfile $ref_path"] l p fs
7525  set libraries [MergeDict $l $libraries]
7526  set properties [MergeDict $p $properties]
7527  set filesets [MergeDict $fs $filesets]
7528  } elseif {[lsearch {.src .sim .con ReadExtraFileList} $extension] >= 0} {
7529  # Not supported extensions
7530  Msg Error "$vhdlfile cannot be included into $list_file, $extension files must be included into $extension files."
7531  } else {
7532  # Deal with single files
7533  regsub -all " *= *" $prop "=" prop
7534  # Fill property dictionary
7535  foreach p $prop {
7536  # No need to append the lib= property
7537  if {[string first "lib=" $p] == -1} {
7538  # Get property name up to the = (for QSYS properties at the moment)
7539  set pos [string first "=" $p]
7540  if {$pos == -1} {
7541  set prop_name $p
7542  } else {
7543  set prop_name [string range $p 0 [expr {$pos - 1}]]
7544  }
7545  if {[IsInList $prop_name [DictGet [ALLOWED_PROPS] $extension]] || [string first "top" $p] == 0 || $list_file_ext eq ".ipb"} {
7546  if {$list_file_ext eq ".ipb"} {
7547  dict lappend properties $vhdlfile $path/$p
7548  } else {
7549  dict lappend properties $vhdlfile $p
7550  }
7551  Msg Debug "Adding property $p to $vhdlfile..."
7552  } elseif {$list_file_ext != ".ipb"} {
7553  Msg Warning "Setting Property $p is not supported for file $vhdlfile or it is already its default. \
7554  The allowed properties for this file type are \[ [DictGet [ALLOWED_PROPS] $extension]\]"
7555  }
7556  }
7557  }
7558  if {[lsearch {.xci .ip .bd .xcix} $extension] >= 0} {
7559  # Adding IP library
7560  set lib_name "ips.src"
7561  } elseif {[IsInList $extension {.vhd .vhdl}] || $list_file_ext == ".sim"} {
7562  # VHDL files and simulation
7563  if {![IsInList $extension {.vhd .vhdl}]} {
7564  set lib_name "others.sim"
7565  } else {
7566  set lib_name "$library$list_file_ext"
7567  }
7568  } elseif {$list_file_ext == ".con"} {
7569  set lib_name "sources.con"
7570  } elseif {$list_file_ext == ".ipb"} {
7571  set lib_name "xml.ipb"
7572  } elseif {[IsInList $list_file_ext {.src}] && [IsInList $extension {.c .cpp .h .hpp}]} {
7573  # Adding Vitis library
7574  set lib_name "$library$list_file_ext"
7575  } else {
7576  # Other files are stored in the OTHER dictionary from vivado (no library assignment)
7577  set lib_name "others.src"
7578  }
7579 
7580  Msg Debug "Appending $vhdlfile to $lib_name list..."
7581  dict lappend libraries $lib_name $vhdlfile
7582  if {$sha_mode != 0 && [file type $vhdlfile] eq "link"} {
7583  #if the file is a link, also add the linked file in sha mode
7584  set real_file [GetLinkedFile $vhdlfile]
7585  dict lappend libraries $lib_name $real_file
7586  Msg Debug "File $vhdlfile is a soft link, also adding the real file: $real_file"
7587  }
7588 
7589  # Auto-expand HLS config files: when a .cfg is referenced from a .src
7590  # we treat it as a Vitis HLS hls_config.cfg and add every existing
7591  # file/dir it references (syn.file, tb.file, -I include dirs, ...)
7592  # to the same library. This avoids forcing the user to duplicate the
7593  # HLS file list in both hls_config.cfg and the .src.
7594  if {$list_file_ext eq ".src" && $extension eq ".cfg"} {
7595  set hls_extras [ExpandHlsConfigFiles $vhdlfile]
7596  if {$print_log == 1 && [llength $hls_extras] > 0} {
7597  Msg Status "$indent Inside [file tail $vhdlfile] (HLS auto-expand):"
7598  }
7599  set n_extras [llength $hls_extras]
7600  set i 0
7601  foreach hls_extra $hls_extras {
7602  incr i
7603  if {$hls_extra eq $vhdlfile} {continue}
7604  Msg Debug "HLS cfg expansion: adding $hls_extra (from $vhdlfile) to $lib_name"
7605  if {$print_log == 1} {
7606  if {$i == $n_extras} {set pad "└──"} else {set pad "├──"}
7607  set rel [Relative [file dirname $vhdlfile] $hls_extra]
7608  Msg Status "$indent $pad $rel"
7609  }
7610  dict lappend libraries $lib_name $hls_extra
7611  if {$sha_mode != 0 && [file type $hls_extra] eq "link"} {
7612  set real_extra [GetLinkedFile $hls_extra]
7613  dict lappend libraries $lib_name $real_extra
7614  Msg Debug "HLS expanded file $hls_extra is a soft link, also adding $real_extra"
7615  }
7616  }
7617  }
7618 
7619 
7620  # Create the fileset (if not already) and append the library
7621  if {[dict exists $filesets $fileset] == 0} {
7622  # Fileset has not been defined yet, adding to dictionary...
7623  Msg Debug "Adding $fileset to the fileset dictionary..."
7624  Msg Debug "Adding library $lib_name to fileset $fileset..."
7625  dict set filesets $fileset $lib_name
7626  } else {
7627  # Fileset already exist in dictionary, append library to list, if not already there
7628  if {[IsInList $lib_name [DictGet $filesets $fileset]] == 0} {
7629  Msg Debug "Adding library $lib_name to fileset $fileset..."
7630  dict lappend filesets $fileset $lib_name
7631  }
7632  }
7633  }
7634  incr cnt
7635  } else {
7636  Msg CriticalWarning "File $vhdlfile not found."
7637  }
7638  }
7639  }
7640  }
7641 
7642  if {$sha_mode != 0} {
7643  #In SHA mode we also need to add the list file to the list
7644  if {$list_file_ext eq ".ipb"} {
7645  set sha_lib "xml.ipb"
7646  } else {
7647  set sha_lib $lib$list_file_ext
7648  }
7649  dict lappend libraries $sha_lib [file normalize $list_file]
7650  if {[file type $list_file] eq "link"} {
7651  #if the file is a link, also add the linked file
7652  set real_file [GetLinkedFile $list_file]
7653  dict lappend libraries $lib$list_file_ext $real_file
7654  Msg Debug "List file $list_file is a soft link, also adding the real file: $real_file"
7655  }
7656  }
7657  return [list $libraries $properties $filesets]
7658 }
7659 
7660 ## @brief Returns the destination path relative to base
7661 #
7662 # @param[in] base the path with respect to witch the dst path is calculated
7663 # @param[in] dst the path to be calculated with respect to base
7664 # @param[in] quiet if 1, does not print warnings when paths are of different types
7665 #
7666 proc Relative {base dst {quiet 0}} {
7667  if {![string equal [file pathtype $base] [file pathtype $dst]]} {
7668  if {$quiet == 0} {
7669  Msg CriticalWarning "Unable to compute relation for paths of different path types: [file pathtype $base] vs. [file pathtype $dst], ($base vs. $dst)"
7670  }
7671  return ""
7672  }
7673 
7674  set base [file normalize [file join [pwd] $base]]
7675  set dst [file normalize [file join [pwd] $dst]]
7676 
7677  set save $dst
7678  set base [file split $base]
7679  set dst [file split $dst]
7680 
7681  while {[string equal [lindex $dst 0] [lindex $base 0]]} {
7682  set dst [lrange $dst 1 end]
7683  set base [lrange $base 1 end]
7684  if {![llength $dst]} {break}
7685  }
7686 
7687  set dstlen [llength $dst]
7688  set baselen [llength $base]
7689 
7690  if {($dstlen == 0) && ($baselen == 0)} {
7691  set dst .
7692  } else {
7693  while {$baselen > 0} {
7694  set dst [linsert $dst 0 ..]
7695  incr baselen -1
7696  }
7697  set dst [eval [linsert $dst 0 file join]]
7698  }
7699 
7700  return $dst
7701 }
7702 
7703 ## @brief Returns the path of filePath relative to pathName
7704 #
7705 # @param[in] pathName the path with respect to which the returned path is calculated
7706 # @param[in] filePath the path of filePath
7707 #
7708 proc RelativeLocal {pathName filePath} {
7709  if {[string first [file normalize $pathName] [file normalize $filePath]] != -1} {
7710  return [Relative $pathName $filePath]
7711  } else {
7712  return ""
7713  }
7714 }
7715 
7716 ## @brief Remove duplicates in a dictionary
7717 #
7718 # @param[in] mydict the input dictionary
7719 #
7720 # @return the dictionary stripped of duplicates
7721 proc RemoveDuplicates {mydict} {
7722  set new_dict [dict create]
7723  foreach key [dict keys $mydict] {
7724  set values [DictGet $mydict $key]
7725  foreach value $values {
7726  set idxs [lreverse [lreplace [lsearch -exact -all $values $value] 0 0]]
7727  foreach idx $idxs {
7728  set values [lreplace $values $idx $idx]
7729  }
7730  }
7731  dict set new_dict $key $values
7732  }
7733  return $new_dict
7734 }
7735 
7736 ## Reset files in the repository
7737 #
7738 # @param[in] reset_file a file containing a list of files separated by new lines or spaces (Hog-CI creates such a file in Projects/hog_reset_files)
7739 #
7740 # @return Nothing
7741 #
7742 proc ResetRepoFiles {reset_file} {
7743  if {[file exists $reset_file]} {
7744  Msg Info "Found $reset_file, opening it..."
7745  set fp [open $reset_file r]
7746  set wild_cards [lsearch -all -inline -not -regexp [split [read $fp] "\n"] "^ *$"]
7747  close $fp
7748  Msg Info "Found the following files/wild cards to restore if modified: $wild_cards..."
7749  foreach w $wild_cards {
7750  set mod_files [GetModifiedFiles "." $w]
7751  if {[llength $mod_files] > 0} {
7752  Msg Info "Found modified $w files: $mod_files, will restore them..."
7753  RestoreModifiedFiles "." $w
7754  } else {
7755  Msg Info "No modified $w files found."
7756  }
7757  }
7758  }
7759 }
7760 
7761 ## @brief Restore with checkout -- the files specified in pattern
7762 #
7763 # @param[in] repo_path the path of the git repository
7764 # @param[in] pattern the pattern with wildcards that files should match
7765 #
7766 proc RestoreModifiedFiles {{repo_path "."} {pattern "."}} {
7767  set old_path [pwd]
7768  cd $repo_path
7769  set ret [Git checkout $pattern]
7770  cd $old_path
7771  return
7772 }
7773 
7774 ## Search the Hog projects inside a directory
7775 #
7776 # @param[in] dir The directory to search
7777 #
7778 # @return The list of projects
7779 #
7780 proc SearchHogProjects {dir} {
7781  set projects_list {}
7782  if {[file exists $dir]} {
7783  if {[file isdirectory $dir]} {
7784  foreach proj_dir [glob -nocomplain -types d $dir/*] {
7785  if {![regexp {^.*Top/+(.*)$} $proj_dir dummy proj_name]} {
7786  Msg Warning "Could not parse Top directory $dir"
7787  break
7788  }
7789  if {[file exists "$proj_dir/hog.conf"]} {
7790  lappend projects_list $proj_name
7791  } else {
7792  foreach p [SearchHogProjects $proj_dir] {
7793  lappend projects_list $p
7794  }
7795  }
7796  }
7797  } else {
7798  Msg Error "Input $dir is not a directory!"
7799  }
7800  } else {
7801  Msg Error "Directory $dir doesn't exist!"
7802  }
7803  return $projects_list
7804 }
7805 
7806 ## @brief Sets the generics in all the sim.conf simulation file sets
7807 #
7808 # @param[in] repo_path: the top folder of the projectThe path to the main git repository
7809 # @param[in] proj_dir: the top folder of the project
7810 # @param[in] target: software target(vivado, questa)
7811 #
7812 proc SetGenericsSimulation {repo_path proj_dir target} {
7813  set top_dir "$repo_path/Top/$proj_dir"
7814  set simsets [get_filesets]
7815  if {$simsets != ""} {
7816  foreach simset $simsets {
7817  # Only for simulation filesets
7818  if {[get_property FILESET_TYPE $simset] != "SimulationSrcs"} {
7819  continue
7820  }
7821 
7822  set merged_generics_dict [dict create]
7823  # Get generics from sim.conf file
7824  set simset_dict [DictGet [GetSimSets $proj_dir $repo_path $simset] $simset]
7825  set hog_generics [GetGenericsFromConf $proj_dir]
7826  set simset_generics [DictGet $simset_dict "generics"]
7827  set merged_generics_dict [MergeDict $merged_generics_dict $simset_generics 0]
7828  set generic_str [GenericToSimulatorString $merged_generics_dict $target]
7829 
7830  Msg Debug "TOP = [get_property top [get_filesets sources_1]]"
7831  Msg Debug "GENERICS = [get_property generic [get_filesets sources_1]]"
7832 
7833  set_property generic $generic_str [get_filesets $simset]
7834  Msg Info "Setting generics $generic_str for simulator $target\
7835  and simulation file-set $simset..."
7836  }
7837  }
7838 }
7839 
7840 ## @brief set the top module as top module in the chosen fileset
7841 #
7842 # It automatically recognises the IDE
7843 #
7844 # @param[out] top_module Name of the top module
7845 # @param[in] fileset The name of the fileset
7846 #
7847 proc SetTopProperty {top_module fileset} {
7848  Msg Info "Setting TOP property to $top_module module"
7849  if {[IsXilinx]} {
7850  #VIVADO_ONLY
7851  set_property "top" $top_module [get_filesets $fileset]
7852  } elseif {[IsQuartus]} {
7853  #QUARTUS ONLY
7854  set_global_assignment -name TOP_LEVEL_ENTITY $top_module
7855  } elseif {[IsLibero]} {
7856  set_root -module $top_module
7857  } elseif {[IsDiamond]} {
7858  prj_impl option top $top_module
7859  }
7860 }
7861 
7862 ## @brief Returns a list of Vivado properties that expect a PATH for value
7863 proc VIVADO_PATH_PROPERTIES {} {
7864  return {"\.*\.TCL\.PRE$" "^.*\.TCL\.POST$" "^RQS_FILES$" "^INCREMENTAL\_CHECKPOINT$" "NOC\_SOLUTION\_FILE"}
7865 }
7866 
7867 ## @brief Returns a list of Vitis properties that expect a PATH for value
7868 proc VITIS_PATH_PROPERTIES {} {
7869  return {"^HW$" "^XPFM$" "^LINKER-SCRIPT$" "^LIBRARIES$" "^LIBRARY-SEARCH-PATH$"}
7870 }
7871 
7872 ## @brief Write a property configuration file from a dictionary
7873 #
7874 # @param[in] file_name the configuration file
7875 # @param[in] config the configuration dictionary
7876 # @param[in] comment comment to add at the beginning of configuration file
7877 #
7878 #
7879 proc WriteConf {file_name config {comment ""}} {
7880  if {[catch {package require inifile 0.2.3} ERROR]} {
7881  puts "$ERROR\n If you are running this script on tclsh, you can fix this by installing 'tcllib'"
7882  return 1
7883  }
7884 
7885  ::ini::commentchar "#"
7886  set f [::ini::open $file_name w]
7887 
7888  foreach sec [dict keys $config] {
7889  set section [dict get $config $sec]
7890  dict for {p v} $section {
7891  if {[string trim $v] == ""} {
7892  Msg Warning "Property $p has empty value. Skipping..."
7893  continue
7894  }
7895  ::ini::set $f $sec $p $v
7896  }
7897  }
7898 
7899  #write comment before the first section (first line of file)
7900  if {![string equal "$comment" ""]} {
7901  ::ini::comment $f [lindex [::ini::sections $f] 0] "" $comment
7902  set hog_header "Generated by Hog on [clock format [clock seconds] -format "%Y-%m-%d %H:%M:%S"]"
7903  ::ini::comment $f [lindex [::ini::sections $f] 0] "" $hog_header
7904  }
7905  ::ini::commit $f
7906 
7907  ::ini::close $f
7908 }
7909 
7910 ## Set the generics property
7911 #
7912 # @param[in] mode if it's "create", the function will assume the project is being created
7913 # @param[in] repo_path The path to the main git repository
7914 # @param[in] design The name of the design
7915 
7916 # @param[in] list of variables to be written in the generics in the usual order
7917 
7918 proc WriteGenerics {
7919  mode repo_path design date timee
7920  commit version top_hash top_ver hog_hash hog_ver
7921  cons_ver cons_hash libs vers hashes ext_names ext_hashes
7922  user_ip_repos user_ip_vers user_ip_hashes flavour {xml_ver ""} {xml_hash ""}
7923 } {
7924  Msg Info "Passing parameters/generics to project's top module..."
7925  ##### Passing Hog generic to top file
7926  # set global generic variables
7927  set generic_string [concat \
7928  "GLOBAL_DATE=[FormatGeneric $date]" \
7929  "GLOBAL_TIME=[FormatGeneric $timee]" \
7930  "GLOBAL_VER=[FormatGeneric $version]" \
7931  "GLOBAL_SHA=[FormatGeneric $commit]" \
7932  "TOP_SHA=[FormatGeneric $top_hash]" \
7933  "TOP_VER=[FormatGeneric $top_ver]" \
7934  "HOG_SHA=[FormatGeneric $hog_hash]" \
7935  "HOG_VER=[FormatGeneric $hog_ver]" \
7936  "CON_VER=[FormatGeneric $cons_ver]" \
7937  "CON_SHA=[FormatGeneric $cons_hash]"]
7938  # xml hash
7939  if {$xml_hash != "" && $xml_ver != ""} {
7940  lappend generic_string \
7941  "XML_VER=[FormatGeneric $xml_ver]" \
7942  "XML_SHA=[FormatGeneric $xml_hash]"
7943  }
7944  #set project specific lists
7945  foreach l $libs v $vers h $hashes {
7946  set ver "[string toupper $l]_VER=[FormatGeneric $v]"
7947  set hash "[string toupper $l]_SHA=[FormatGeneric $h]"
7948  # Replaces all occurrences of dots (.) and hyphens (-) in the generic name
7949  # with underscores (_) to make it compatible with VHDL/Verilog syntax
7950  # Uses regsub with -all flag to replace all matches of the regex pattern [\.-]
7951  set ver [regsub -all {[\.-]} $ver {_}]
7952  set hash [regsub -all {[\.-]} $hash {_}]
7953  lappend generic_string "$ver" "$hash"
7954  }
7955 
7956  foreach e $ext_names h $ext_hashes {
7957  set hash "[string toupper $e]_SHA=[FormatGeneric $h]"
7958  lappend generic_string "$hash"
7959  }
7960 
7961  foreach repo $user_ip_repos v $user_ip_vers h $user_ip_hashes {
7962  set repo_name [file tail $repo]
7963  set ver "[string toupper $repo_name]_VER=[FormatGeneric $v]"
7964  set hash "[string toupper $repo_name]_SHA=[FormatGeneric $h]"
7965  set ver [regsub -all {[\.-]} $ver {_}]
7966  set hash [regsub -all {[\.-]} $hash {_}]
7967  lappend generic_string "$ver" "$hash"
7968  }
7969 
7970  if {$flavour != -1} {
7971  lappend generic_string "FLAVOUR=$flavour"
7972  }
7973 
7974  # Dealing with project generics in Vivado
7975  if {[IsVivado] || [IsVitisClassic] || [IsVitisUnified]} {
7976  set prj_generics [GenericToSimulatorString [GetGenericsFromConf $design] "Vivado"]
7977  set generic_string "$prj_generics $generic_string"
7978  }
7979 
7980  # Extract the generics from the top level source file
7981  if {[IsXilinx]} {
7982  # Top File can be retrieved only at creation time or in ISE
7983  if {$mode == "create" || [IsISE]} {
7984  set top_file [GetTopFile]
7985  set top_name [GetTopModule]
7986  if {[file exists $top_file]} {
7987  set generics [GetFileGenerics $top_file $top_name]
7988 
7989  Msg Debug "Found top level generics $generics in $top_file"
7990 
7991  set filtered_generic_string ""
7992 
7993  foreach generic_to_set [split [string trim $generic_string]] {
7994  set key [lindex [split $generic_to_set "="] 0]
7995  if {[dict exists $generics $key]} {
7996  Msg Debug "Hog generic $key found in $top_name"
7997  lappend filtered_generic_string "$generic_to_set"
7998  } else {
7999  Msg Warning "Generic $key is passed by Hog but is NOT present in $top_name."
8000  }
8001  }
8002 
8003  # only filter in ISE
8004  if {[IsISE]} {
8005  set generic_string $filtered_generic_string
8006  }
8007  }
8008  }
8009 
8010  set_property generic $generic_string [current_fileset]
8011  Msg Info "Setting parameters/generics..."
8012  Msg Debug "Detailed parameters/generics: $generic_string"
8013 
8014 
8015  if {[IsVivado]} {
8016  # Dealing with project generics in Simulators
8017  set simulator [get_property target_simulator [current_project]]
8018  if {$mode == "create"} {
8019  SetGenericsSimulation $repo_path $design $simulator
8020  }
8021 
8022  WriteGenericsToBdIPs $mode $repo_path $design $generic_string
8023  }
8024  } elseif {[IsSynplify]} {
8025  Msg Info "Setting Synplify parameters/generics one by one..."
8026  foreach generic $generic_string {
8027  Msg Debug "Setting Synplify generic: $generic"
8028  set_option -hdl_param -set "$generic"
8029  }
8030  } elseif {[IsDiamond]} {
8031  Msg Info "Setting Diamond parameters/generics one by one..."
8032  prj_impl option -impl Implementation0 HDL_PARAM "$generic_string"
8033  } elseif {[IsVitisClassic] || [IsVitisUnified]} {
8034  if {[catch {set ws_apps [app list -dict]}]} {set ws_apps ""}
8035 
8036  foreach app_name [dict keys $ws_apps] {
8037  set defined_symbols [app config -name $app_name -get define-compiler-symbols]
8038  foreach generic_to_set [split [string trim $generic_string]] {
8039  set key [lindex [split $generic_to_set "="] 0]
8040  set value [lindex [split $generic_to_set "="] 1]
8041  if {[string match "32'h*" $value]} {
8042  set value [string map {"32'h" "0x"} $value]
8043  }
8044 
8045  foreach symbol [split $defined_symbols ";"] {
8046  if {[string match "$key=*" $symbol]} {
8047  Msg Debug "Generic $key found in $app_name, removing it..."
8048  app config -name $app_name -remove define-compiler-symbols "$symbol"
8049  }
8050  }
8051 
8052  Msg Info "Setting Vitis parameters/generics for app $app_name: $key=$value"
8053  app config -name $app_name define-compiler-symbols "$key=$value"
8054  }
8055  }
8056  }
8057 }
8058 
8059 ## @brief Applies generic values to IPs within block designs
8060 #
8061 # @param[in] mode create: to write the generics at creation time. synth to write at synthesis time
8062 # @param[in] repo_path The main path of the git repository
8063 # @param[in] proj The project name
8064 # @param[in] generic_string the string containing the generics to be applied
8065 proc WriteGenericsToBdIPs {mode repo_path proj generic_string} {
8066  Msg Debug "Parameters/generics passed to WriteGenericsToIP: $generic_string"
8067 
8068  set bd_ip_generics false
8069  set properties [ReadConf [lindex [GetConfFiles $repo_path/Top/$proj] 0]]
8070  if {[dict exists $properties "hog"]} {
8071  set propDict [dict get $properties "hog"]
8072  if {[dict exists $propDict "PASS_GENERICS_TO_BD_IPS"]} {
8073  set bd_ip_generics [dict get $propDict "PASS_GENERICS_TO_BD_IPS"]
8074  }
8075  }
8076 
8077  if {[string compare [string tolower $bd_ip_generics] "false"] == 0} {
8078  return
8079  }
8080 
8081  if {$mode == "synth"} {
8082  Msg Info "Attempting to apply generics pre-synthesis..."
8083  set PARENT_PRJ [get_property "PARENT.PROJECT_PATH" [current_project]]
8084  set workaround [open "$repo_path/Projects/$proj/.hog/presynth_workaround.tcl" "w"]
8085  puts $workaround "source \[lindex \$argv 0\];"
8086  puts $workaround "open_project \[lindex \$argv 1\];"
8087  puts $workaround "WriteGenericsToBdIPs \[lindex \$argv 2\] \[lindex \$argv 3\] \[lindex \$argv 4\] \[lindex \$argv 5\];"
8088  puts $workaround "close_project"
8089  close $workaround
8090  if {
8091  [catch {
8092  exec vivado -mode batch -source $repo_path/Projects/$proj/.hog/presynth_workaround.tcl \
8093  -tclargs $repo_path/Hog/Tcl/hog.tcl $PARENT_PRJ \
8094  "childprocess" $repo_path $proj $generic_string
8095  } errMsg] != 0
8096  } {
8097  Msg Error "Encountered an error while attempting workaround: $errMsg"
8098  }
8099  file delete $repo_path/Projects/$proj/.hog/presynth_workaround.tcl
8100  ResetRepoFiles "$repo_path/Projects/hog_reset_files"
8101  Msg Info "Done applying generics pre-synthesis."
8102  return
8103  }
8104 
8105  Msg Info "Looking for IPs to add generics to..."
8106  set ips_generic_string ""
8107  foreach generic_to_set [split [string trim $generic_string]] {
8108  set key [lindex [split $generic_to_set "="] 0]
8109  set value [lindex [split $generic_to_set "="] 1]
8110  append ips_generic_string "CONFIG.$key $value "
8111  }
8112 
8113 
8114  if {[string compare [string tolower $bd_ip_generics] "true"] == 0} {
8115  set ip_regex ".*"
8116  } else {
8117  set ip_regex $bd_ip_generics
8118  }
8119 
8120  set ip_list [get_ips -regex $ip_regex]
8121  Msg Debug "IPs found with regex \{$ip_regex\}: $ip_list"
8122 
8123  set regen_targets {}
8124 
8125  foreach {ip} $ip_list {
8126  set WARN_ABOUT_IP false
8127  set ip_props [list_property [get_ips $ip]]
8128 
8129  #Not sure if this is needed, but it's here to prevent potential errors with get_property
8130  if {[lsearch -exact $ip_props "IS_BD_CONTEXT"] == -1} {
8131  continue
8132  }
8133 
8134  if {[get_property "IS_BD_CONTEXT" [get_ips $ip]] eq "1"} {
8135  foreach {ip_prop} $ip_props {
8136  if {[dict exists $ips_generic_string $ip_prop]} {
8137  if {$WARN_ABOUT_IP == false} {
8138  lappend regen_targets [get_property SCOPE [get_ips $ip]]
8139  Msg Warning "The ip \{$ip\} contains generics that are set by Hog.\
8140  If this is IP is apart of a block design, the .bd file may contain stale, unused, values.\
8141  Hog will always apply the most up-to-date values to the IP during synthesis,\
8142  however these values may or may not be reflected in the .bd file."
8143  set WARN_ABOUT_IP true
8144  }
8145 
8146  # vivado is annoying about the format when setting generics for ips
8147  # this tries to find and set the format to what vivado likes
8148  set xci_path [get_property IP_FILE [get_ips $ip]]
8149  set generic_format [GetGenericFormatFromXci $ip_prop $xci_path]
8150  if {[string equal $generic_format "ERROR"]} {
8151  Msg Warning "Could not find format for generic $ip_prop in IP $ip. Skipping..."
8152  continue
8153  }
8154 
8155  set value_to_set [dict get $ips_generic_string $ip_prop]
8156  switch -exact $generic_format {
8157  "long" {
8158  if {[string match "32'h*" $value_to_set]} {
8159  scan [string map {"32'h" ""} $value_to_set] "%x" value_to_set
8160  }
8161  }
8162  "bool" {
8163  set value_to_set [expr {$value_to_set ? "true" : "false"}]
8164  }
8165  "float" {
8166  if {[string match "32'h*" $value_to_set]} {
8167  binary scan [binary format H* [string map {"32'h" ""} $value_to_set]] d value_to_set
8168  }
8169  }
8170  "bitString" {
8171  if {[string match "32'h*" $value_to_set]} {
8172  set value_to_set [string map {"32'h" "0x"} $value_to_set]
8173  }
8174  }
8175  "string" {
8176  set value_to_set [format "%s" $value_to_set]
8177  }
8178  default {
8179  Msg Warning "Unknown generic format $generic_format for IP $ip. Will attempt to pass as string..."
8180  }
8181  }
8182 
8183 
8184  Msg Info "The IP \{$ip\} contains: $ip_prop ($generic_format), setting it to $value_to_set."
8185  if {[catch {set_property -name $ip_prop -value $value_to_set -objects [get_ips $ip]} prop_error]} {
8186  Msg CriticalWarning "Failed to set property $ip_prop to $value_to_set for IP \{$ip\}: $prop_error"
8187  }
8188  }
8189  }
8190  }
8191  }
8192 
8193  foreach {regen_target} [lsort -unique $regen_targets] {
8194  Msg Info "Regenerating target: $regen_target"
8195  if {[catch {generate_target -force all [get_files $regen_target]} prop_error]} {
8196  Msg CriticalWarning "Failed to regen targets: $prop_error"
8197  }
8198  }
8199 }
8200 
8201 ## @brief Returns the format of a generic from an XML file
8202 ## @param[in] generic_name: The name of the generic
8203 ## @param[in] xml_file: The path to the XML XCI file
8204 proc GetGenericFormatFromXciXML {generic_name xml_file} {
8205  if {![file exists $xml_file]} {
8206  Msg Error "Could not find XML file: $xml_file"
8207  return "ERROR"
8208  }
8209 
8210  set fp [open $xml_file r]
8211  set xci_data [read $fp]
8212  close $fp
8213 
8214  set paramType "string"
8215  set modelparam_regex [format {^.*\y%s\y.*$} [string map {"CONFIG." "MODELPARAM_VALUE."} $generic_name]]
8216  set format_regex {format="([^"]+)"}
8217 
8218  set line [lindex [regexp -inline -line $modelparam_regex $xci_data] 0]
8219  Msg Debug "line: $line"
8220 
8221  if {[regexp $format_regex $line match format_value]} {
8222  Msg Debug "Extracted: $format_value format from xml"
8223  set paramType $format_value
8224  } else {
8225  Msg Debug "No format found, using string"
8226  }
8227 
8228  return $paramType
8229 }
8230 
8231 ## @brief Returns the format of a generic from an XCI file
8232 ## @param[in] generic_name: The name of the generic
8233 ## @param[in] xci_file: The path to the XCI file
8234 proc GetGenericFormatFromXci {generic_name xci_file} {
8235  if {![file exists $xci_file]} {
8236  Msg Error "Could not find XCI file: $xci_file"
8237  return "ERROR"
8238  }
8239 
8240  set fp [open $xci_file r]
8241  set xci_data [read $fp]
8242  close $fp
8243 
8244  set paramType "string"
8245  if {[string first "xilinx.com:schema:json_instance:1.0" $xci_data] == -1} {
8246  Msg Debug "XCI format is not JSON, trying XML..."
8247  set xml_file "[file rootname $xci_file].xml"
8248  return [GetGenericFormatFromXciXML $generic_name $xml_file]
8249  }
8250 
8251  set generic_name [string map {"CONFIG." ""} $generic_name]
8252  set ip_inst [ParseJSON $xci_data "ip_inst"]
8253  set parameters [dict get $ip_inst parameters]
8254  set component_parameters [dict get $parameters component_parameters]
8255  if {[dict exists $component_parameters $generic_name]} {
8256  set generic_info [dict get $component_parameters $generic_name]
8257  if {[dict exists [lindex $generic_info 0] format]} {
8258  set paramType [dict get [lindex $generic_info 0] format]
8259  Msg Debug "Extracted: $paramType format from xci"
8260  return $paramType
8261  }
8262  Msg Debug "No format found, using string"
8263  return $paramType
8264  } else {
8265  return "ERROR"
8266  }
8267 }
8268 
8269 
8270 ## @brief Returns the gitlab-ci.yml snippet for a CI stage and a defined project
8271 #
8272 # @param[in] proj_name: The project name
8273 # @param[in] ci_confs: Dictionary with CI configurations
8274 #
8275 proc WriteGitLabCIYAML {proj_name {ci_conf ""}} {
8276  if {[catch {package require yaml 0.3.3} YAMLPACKAGE]} {
8277  Msg CriticalWarning "Cannot find package YAML.\n Error message: $YAMLPACKAGE. \
8278  If you are running on tclsh, you can fix this by installing package \"tcllib\""
8279  return -1
8280  }
8281 
8282  set job_list []
8283  if {$ci_conf != ""} {
8284  set ci_confs [ReadConf $ci_conf]
8285  foreach sec [dict keys $ci_confs] {
8286  if {[string first : $sec] == -1} {
8287  lappend job_list $sec
8288  }
8289  }
8290  } else {
8291  set job_list {"generate_project" "simulate_project"}
8292  set ci_confs ""
8293  }
8294 
8295  set out_yaml [huddle create]
8296  foreach job $job_list {
8297  # Check main project configurations
8298  set huddle_tags [huddle list]
8299  set tag_section ""
8300  set sec_dict [dict create]
8301 
8302  if {$ci_confs != ""} {
8303  foreach var [dict keys [dict get $ci_confs $job]] {
8304  if {$var == "tags"} {
8305  set tag_section "tags"
8306  set tags [dict get [dict get $ci_confs $job] $var]
8307  set tags [split $tags ","]
8308  foreach tag $tags {
8309  set tag_list [huddle list $tag]
8310  set huddle_tags [huddle combine $huddle_tags $tag_list]
8311  }
8312  } else {
8313  dict set sec_dict $var [dict get [dict get $ci_confs $job] $var]
8314  }
8315  }
8316  }
8317 
8318  # Check if there are extra variables in the conf file
8319  set huddle_variables [huddle create "PROJECT_NAME" $proj_name "extends" ".vars"]
8320  if {[dict exists $ci_confs "$job:variables"]} {
8321  set var_dict [dict get $ci_confs $job:variables]
8322  foreach var [dict keys $var_dict] {
8323  # puts [dict get $var_dict $var]
8324  set value [dict get $var_dict "$var"]
8325  set var_inner [huddle create "$var" "$value"]
8326  set huddle_variables [huddle combine $huddle_variables $var_inner]
8327  }
8328  }
8329 
8330 
8331  set middle [huddle create "extends" ".$job" "variables" $huddle_variables]
8332  foreach sec [dict keys $sec_dict] {
8333  set value [dict get $sec_dict $sec]
8334  set var_inner [huddle create "$sec" "$value"]
8335  set middle [huddle combine $middle $var_inner]
8336  }
8337  if {$tag_section != ""} {
8338  set middle2 [huddle create "$tag_section" $huddle_tags]
8339  set middle [huddle combine $middle $middle2]
8340  }
8341 
8342  set outer [huddle create "$job:$proj_name" $middle]
8343  set out_yaml [huddle combine $out_yaml $outer]
8344  }
8345 
8346  return [string trimleft [yaml::huddle2yaml $out_yaml] "-"]
8347 }
8348 
8349 # @brief Write the content of Hog-library-dictionary created from the project into a .src/.ext/.con list file
8350 #
8351 # @param[in] libs The Hog-Library dictionary with the list of files in the project to write
8352 # @param[in] props The Hog-library dictionary with the file sets
8353 # @param[in] list_path The path of the output list file
8354 # @param[in] repo_path The main repository path
8355 # @param[in] ext_path The external path
8356 proc WriteListFiles {libs props list_path repo_path {ext_path ""}} {
8357  # Writing simulation list files
8358  foreach lib [dict keys $libs] {
8359  if {[llength [DictGet $libs $lib]] > 0} {
8360  set list_file_name $list_path$lib
8361  set list_file [open $list_file_name w]
8362  Msg Info "Writing $list_file_name..."
8363  puts $list_file "#Generated by Hog on [clock format [clock seconds] -format "%Y-%m-%d %H:%M:%S"]"
8364  foreach file [DictGet $libs $lib] {
8365  # Retrieve file properties from prop list
8366  set prop [DictGet $props $file]
8367  # Check if file is local to the repository or external
8368  if {[RelativeLocal $repo_path $file] != ""} {
8369  set file_path [RelativeLocal $repo_path $file]
8370  puts $list_file "$file_path $prop"
8371  } elseif {[RelativeLocal $ext_path $file] != ""} {
8372  set file_path [RelativeLocal $ext_path $file]
8373  set ext_list_file [open "[file rootname $list_file].ext" a]
8374  puts $ext_list_file "$file_path $prop"
8375  close $ext_list_file
8376  } else {
8377  # File is not relative to repo or ext_path... Write a Warning and continue
8378  Msg Warning "The path of file $file is not relative to your repository. Please check!"
8379  }
8380  }
8381  close $list_file
8382  }
8383  }
8384 }
8385 
8386 # @brief Write the content of Hog-library-dictionary created from the project into a .sim list file
8387 #
8388 # @param[in] libs The Hog-Library dictionary with the list of files in the project to write
8389 # @param[in] props The Hog-library dictionary with the file sets
8390 # @param[in] simsets The Hog-library dictionary with the file sets (relevant only for simulation)
8391 # @param[in] list_path The path of the output list file
8392 # @param[in] repo_path The main repository path
8393 # @param[in] force If 1, it will overwrite the existing list files
8394 proc WriteSimListFile {simset libs props simsets list_path repo_path {force 0}} {
8395  # Writing simulation list file
8396  set list_file_name $list_path/${simset}.sim
8397  if {$force == 0 && [file exists $list_file_name]} {
8398  Msg Info "List file $list_file_name already exists, skipping..."
8399  continue
8400  }
8401 
8402  set list_file [open $list_file_name a+]
8403 
8404  # Write the header with the simulator
8405  puts $list_file "\[files\]"
8406  Msg Info "Writing $list_file_name..."
8407  foreach lib [DictGet $simsets $simset] {
8408  foreach file [DictGet $libs $lib] {
8409  # Retrieve file properties from prop list
8410  set prop [DictGet $props $file]
8411  # Check if file is local to the repository or external
8412  if {[RelativeLocal $repo_path $file] != ""} {
8413  set file_path [RelativeLocal $repo_path $file]
8414  set lib_name [file rootname $lib]
8415  if {$lib_name != $simset && [file extension $file] == ".vhd" && [file extension $file] == ""} {
8416  lappend prop "lib=$lib_name"
8417  }
8418  puts $list_file "$file_path $prop"
8419  } else {
8420  # File is not relative to repo or ext_path... Write a Warning and continue
8421  Msg Warning "The path of file $file is not relative to your repository. Please check!"
8422  }
8423  }
8424  }
8425  close $list_file
8426 }
8427 
8428 
8429 ## @brief Write into a file, and if the file exists, it will append the string
8430 #
8431 # @param[out] File The log file to write into the message
8432 # @param[in] msg The message text
8433 proc WriteToFile {File msg} {
8434  set f [open $File a+]
8435  puts $f $msg
8436  close $f
8437 }
8438 
8439 ## Write the resource utilization table into a a file (Vivado only)
8440 #
8441 # @param[in] input the input .rpt report file from Vivado
8442 # @param[in] output the output file
8443 # @param[in] project_name the name of the project
8444 # @param[in] run synthesis or implementation
8445 proc WriteUtilizationSummary {input output project_name run} {
8446  set f [open $input "r"]
8447  set o [open $output "a"]
8448  puts $o "## $project_name $run Utilization report\n\n"
8449  struct::matrix util_m
8450  util_m add columns 14
8451  util_m add row
8452  if {[GetIDEVersion] >= 2021.0} {
8453  util_m add row "| **Site Type** | **Used** | **Fixed** | **Prohibited** | **Available** | **Util%** |"
8454  util_m add row "| --- | --- | --- | --- | --- | --- |"
8455  } else {
8456  util_m add row "| **Site Type** | **Used** | **Fixed** | **Available** | **Util%** |"
8457  util_m add row "| --- | --- | --- | --- | --- |"
8458  }
8459 
8460  set luts 0
8461  set regs 0
8462  set uram 0
8463  set bram 0
8464  set dsps 0
8465  set ios 0
8466 
8467  while {[gets $f line] >= 0} {
8468  if {([string first "| CLB LUTs" $line] >= 0 || [string first "| Slice LUTs" $line] >= 0) && $luts == 0} {
8469  util_m add row $line
8470  set luts 1
8471  }
8472  if {([string first "| CLB Registers" $line] >= 0 || [string first "| Slice Registers" $line] >= 0) && $regs == 0} {
8473  util_m add row $line
8474  set regs 1
8475  }
8476  if {[string first "| Block RAM Tile" $line] >= 0 && $bram == 0} {
8477  util_m add row $line
8478  set bram 1
8479  }
8480  if {[string first "URAM " $line] >= 0 && $uram == 0} {
8481  util_m add row $line
8482  set uram 1
8483  }
8484  if {[string first "DSPs" $line] >= 0 && $dsps == 0} {
8485  util_m add row $line
8486  set dsps 1
8487  }
8488  if {[string first "Bonded IOB" $line] >= 0 && $ios == 0} {
8489  util_m add row $line
8490  set ios 1
8491  }
8492  }
8493  util_m add row
8494 
8495  close $f
8496  puts $o [util_m format 2string]
8497  close $o
8498 }
8499 
8500 # Check Git Version when sourcing hog.tcl
8501 if {[GitVersion 2.7.2] == 0} {
8502  Msg Error "Found Git version older than 2.7.2. Hog will not work as expected, exiting now."
8503 }
8504 
8505 ## @brief Tries to find the coorrect command to be launched for curl
8506 #
8507 # @details If running in vivado shell you may need to unsed LD_LIBRARY_PATH befor running curl to avoid conflicts with vivado libraries.
8508 # This procedure tests curl if execution is correct returns "curl"
8509 # If execution fails tries to run env -u LD_LIBRARY_PATH curl --silent --show-error, and returns "env -u LD_LIBRARY_PATH curl --silent --show-error" on success.
8510 # If both fail returns "curl", this will most probably generate failures later
8511 proc GetCurl {{gitlab_url "https://gitlab.com"}} {
8512  if {[auto_execok curl] == ""} {
8513  Msg Warning "Cannot find a working curl invocation"
8514  return 0
8515  }
8516 
8517  if {[IsTclsh]} {
8518  set cmd [list curl --silent --show-error]
8519  } else {
8520  set cmd [list env -u LD_LIBRARY_PATH curl --silent --show-error]
8521  }
8522 
8523  if {![catch {exec {*}$cmd -I $gitlab_url}]} {
8524  return $cmd
8525  } else {
8526  Msg Warning "Impossible to contact $gitlab_url"
8527  return 0
8528  }
8529 }