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