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