Hog v10.48.0
hog.tcl
Go to the documentation of this file.
1 # Copyright 2018-2026 The University of Birmingham
2 # Copyright 2018-2026 Max-Planck-Institute for Physics
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 
16 ## @file hog.tcl
17 
18 if {![info exists tcl_path]} {
19  set tcl_path [file normalize "[file dirname [info script]]"]
20 }
21 
22 set hog_path [file normalize "[file dirname [info script]]"]
23 
24 source "$hog_path/utils/Logger.tcl"
25 
26 
27 #### GLOBAL CONSTANTS
28 set CI_STAGES {"generate_project" "simulate_project"}
29 set CI_PROPS {"-synth_only"}
30 
31 #### FUNCTIONS
32 
33 ## @brief Add a new file to a fileset in Vivado
34 #
35 # @param[in] file The name of the file to add. NOTE: directories are not supported.
36 # @param[in] fileset The fileset name
37 #
38 proc AddFile {file fileset} {
39  if {[IsXilinx]} {
40  add_files -norecurse -fileset $fileset $file
41  }
42 }
43 
44 ## @brief Add libraries, properties and filesets to the project
45 #
46 # @param[in] libraries has library name as keys and a list of filenames as values
47 # @param[in] properties has as file names as keys and a list of properties as values
48 # @param[in] filesets has fileset name as keys and a list of libraries as values
49 #
50 proc AddHogFiles {libraries properties filesets} {
51  Msg Info "Adding source files to project..."
52  Msg Debug "Filesets: $filesets"
53  Msg Debug "Libraries: $libraries"
54  Msg Debug "Properties: $properties"
55 
56  if {[IsLibero]} {
57  set synth_conf_command "organize_tool_files -tool {SYNTHESIZE} -input_type {constraint}"
58  set synth_conf 0
59  set timing_conf_command "organize_tool_files -tool {VERIFYTIMING} -input_type {constraint}"
60  set timing_conf 0
61  set place_conf_command "organize_tool_files -tool {PLACEROUTE} -input_type {constraint}"
62  set place_conf 0
63  }
64 
65  # 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 Map a Cheby generator alias from the .chb list file to the
1792 # corresponding cheby command-line flag.
1793 #
1794 # Each line in a .chb list file may set a `gen=` property whose value is a
1795 # comma-separated list of generators (e.g. `gen=hdl,consts`). The aliases used
1796 # in the list file are intentionally short and stable; this proc translates
1797 # them to the actual cheby executable flags (e.g. `--gen-hdl`).
1798 #
1799 # @param[in] gen The short generator alias (e.g. "hdl", "consts", "c", "doc")
1800 #
1801 # @return The cheby command-line flag for that generator,
1802 # or an empty string if the alias is unknown.
1803 proc ChebyGenFlag {gen} {
1804  switch -- $gen {
1805  "hdl" { return "--gen-hdl" }
1806  "consts" { return "--gen-consts" }
1807  "c" { return "--gen-c" }
1808  "c_check" { return "--gen-c-check-layout" }
1809  "doc" { return "--gen-doc" }
1810  "memmap" { return "--print-memmap" }
1811  "memmap_v" { return "--print-memmap-verbose" }
1812  "pretty" { return "--print-pretty" }
1813  "devicetree" { return "--gen-devicetree" }
1814  "edge" { return "--gen-edge" }
1815  "silecs" { return "--gen-silecs" }
1816  "wbgen" { return "--gen-wbgen-hdl" }
1817  "custom" { return "--gen-custom" }
1818  "install" { return "--gen-install-script" }
1819  "gena_memmap" { return "--gen-gena-memmap" }
1820  "gena_regctrl" { return "--gen-gena-regctrl" }
1821  default { return "" }
1822  }
1823 }
1824 
1825 ## @brief Parse a Cheby `.chb` list file into a list of per-line dictionaries.
1826 #
1827 # Each non-empty, non-comment line is expected to have the form:
1828 # <input.cheby> [<output1> <output2> ...] [key=value ...]
1829 # Tokens containing `=` are treated as properties (mapped to cheby flags),
1830 # while tokens without `=` are treated as expected output files.
1831 #
1832 # @param[in] chb_file Path to a .chb list file.
1833 #
1834 # @return A list of dictionaries, one per line. Each dictionary
1835 # contains keys: input, outputs, props, line, file.
1836 proc ParseChebyListFile {chb_file} {
1837  set fp [open $chb_file r]
1838  set data [read $fp]
1839  close $fp
1840  set lines [split $data "\n"]
1841  set result [list]
1842  set line_num 0
1843  foreach line $lines {
1844  incr line_num
1845  set trimmed [string trim $line]
1846  if {$trimmed eq "" || [string index $trimmed 0] eq "#"} {
1847  continue
1848  }
1849  set tokens [regexp -all -inline {\S+} $line]
1850  if {[llength $tokens] < 1} {
1851  continue
1852  }
1853  set input [lindex $tokens 0]
1854  set rest [lrange $tokens 1 end]
1855  set outputs [list]
1856  set props [dict create]
1857  foreach tok $rest {
1858  set eq_pos [string first "=" $tok]
1859  if {$eq_pos == -1} {
1860  lappend outputs $tok
1861  } else {
1862  set key [string range $tok 0 [expr {$eq_pos - 1}]]
1863  set val [string range $tok [expr {$eq_pos + 1}] end]
1864  dict set props $key $val
1865  }
1866  }
1867  lappend result [dict create input $input outputs $outputs props $props \
1868  line $line_num file $chb_file]
1869  }
1870  return $result
1871 }
1872 
1873 ## @brief Translate Cheby list-file properties into cheby command-line flags
1874 # and pair each declared output file with its generator.
1875 #
1876 # The properties dictionary may contain a special `gen=` entry whose value is
1877 # a comma-separated list of generator aliases. The number of comma-separated
1878 # generators must match the number of expected output files declared on the
1879 # same .chb line. When `gen=` is absent, this proc tries to infer a single
1880 # generator from the file extension of the (single) declared output file.
1881 #
1882 # All other recognised properties are mapped to cheby flags, e.g.:
1883 # hdl=vhdl -> --hdl vhdl
1884 # header=commit -> --header commit
1885 # c_style=arm -> --c-style arm
1886 # doc=html -> --doc html
1887 # Boolean-style properties (e.g. hdl_preload=1) are emitted as bare flags.
1888 # Properties whose key already starts with `--` are passed through verbatim.
1889 #
1890 # @param[in] props A dict of key=value properties parsed from the .chb line.
1891 # @param[in] outputs A list of expected output file paths declared on the
1892 # same .chb line.
1893 #
1894 # @return A 3-element list { ok gen_map opt_flags } where:
1895 # ok is 0 on success or non-zero on error.
1896 # gen_map on success: a list { gen out gen out ... }
1897 # pairing each generator with its output;
1898 # on error: a human-readable error message.
1899 # opt_flags on success: a flat list of cheby flag/value
1900 # elements to add to the command line.
1901 proc BuildChebyCmdArgs {props outputs} {
1902  set gens [list]
1903  if {[dict exists $props "gen"]} {
1904  set gen_csv [dict get $props "gen"]
1905  set gens [split $gen_csv ","]
1906  }
1907  set ngens [llength $gens]
1908  if {$ngens == 0} {
1909  if {[llength $outputs] == 1} {
1910  set ext [string tolower [file extension [lindex $outputs 0]]]
1911  switch -- $ext {
1912  ".vhd" -
1913  ".vhdl" -
1914  ".v" -
1915  ".sv" { set gens [list "hdl"] }
1916  ".h" -
1917  ".hpp" { set gens [list "c"] }
1918  ".html" -
1919  ".md" -
1920  ".rst" -
1921  ".tex" { set gens [list "doc"] }
1922  default {
1923  return [list 1 "Cannot infer generator from output extension '$ext'.\
1924  Please add a `gen=...` property to the line." [list]]
1925  }
1926  }
1927  set ngens 1
1928  } else {
1929  return [list 1 "Cannot map [llength $outputs] outputs without a `gen=...`\
1930  property listing the generators." [list]]
1931  }
1932  }
1933  if {[llength $outputs] != $ngens} {
1934  return [list 1 "Number of declared outputs ([llength $outputs]) does not match\
1935  number of generators ($ngens, gen=[join $gens ,])." [list]]
1936  }
1937  set gen_map [list]
1938  foreach g $gens o $outputs {
1939  lappend gen_map $g $o
1940  }
1941 
1942  set option_map [dict create \
1943  "hdl" "--hdl" \
1944  "header" "--header" \
1945  "c_style" "--c-style" \
1946  "consts_style" "--consts-style" \
1947  "doc" "--doc" \
1948  "address_space" "--address-space" \
1949  "out_prefix" "--out-prefix" \
1950  "ff_reset" "--ff-reset" \
1951  "word_endian" "--word-endian" \
1952  "wb_lib_name" "--wb-lib-name" \
1953  "axil_lib_name" "--axil-lib-name" \
1954  "rest_headers" "--rest-headers" \
1955  "custom" "--custom" \
1956  ]
1957  set bool_map [dict create \
1958  "hdl_preload" "--hdl-preload" \
1959  "doc_hide_comments" "--doc-hide-comments" \
1960  "doc_no_reg_drawing" "--doc-no-reg-drawing" \
1961  "doc_include_js_dep" "--doc-include-js-dep" \
1962  "gena_common_visual" "--gena-common-visual" \
1963  "gen_c_bit_struct" "--gen-c-bit-struct" \
1964  "gen_gena_dsp" "--gen-gena-dsp" \
1965  ]
1966 
1967  set opt_flags [list]
1968  dict for {k v} $props {
1969  if {$k eq "gen" || $k eq "profile"} {
1970  continue
1971  }
1972  if {[dict exists $option_map $k]} {
1973  lappend opt_flags [dict get $option_map $k] $v
1974  } elseif {[dict exists $bool_map $k]} {
1975  if {$v eq "1" || [string tolower $v] eq "true" || [string tolower $v] eq "yes"} {
1976  lappend opt_flags [dict get $bool_map $k]
1977  }
1978  } elseif {[string range $k 0 1] eq "--"} {
1979  lappend opt_flags $k $v
1980  } else {
1981  Msg Warning "Unknown cheby property '$k=$v', ignoring."
1982  }
1983  }
1984  return [list 0 $gen_map $opt_flags]
1985 }
1986 
1987 ## @brief Return @a abs as a path relative to @a base for cheby argv/header display.
1988 #
1989 # Cheby records sys.argv in generated file headers. Running cheby from the
1990 # YAML directory with relative paths produces headers comparable to manual
1991 # gen_regs.sh invocations (e.g. gasp_regs.yaml instead of an absolute path).
1992 #
1993 # @param[in] base Reference directory (typically the .yaml/.cheby parent dir).
1994 # @param[in] abs Absolute path to express relatively.
1995 #
1996 # @return A relative path, or @a abs unchanged when no relative form exists.
1997 proc ChebyRelPath {base abs} {
1998  set base [file normalize $base]
1999  set abs [file normalize $abs]
2000  if {$base eq [file dirname $abs]} {
2001  return [file tail $abs]
2002  }
2003  set rel [Relative $base $abs 1]
2004  if {$rel eq ""} {
2005  return $abs
2006  }
2007  return $rel
2008 }
2009 
2010 ## @brief Drop the leading auto-generated cheby comment header from a line list.
2011 #
2012 # Cheby headers (see cheby/gen_header.py) are metadata only and always sit at
2013 # the top of the file in a fixed shape: a banner line, the argv line, and an
2014 # optional "Generated on <date> by <user>" line (only with header=full). None
2015 # of these should cause check-mode mismatches when the register body matches.
2016 # Blank lines are assumed already removed by the caller.
2017 #
2018 # @param[in] lines Non-blank, trimmed lines read from a generated file.
2019 #
2020 # @return Lines with the cheby banner/argv/timestamp lines removed.
2021 proc StripChebyGeneratedHeader {lines} {
2022  set idx [lsearch -regexp $lines {Do not edit\.\s+Generated by cheby}]
2023  if {$idx < 0} {
2024  return $lines
2025  }
2026  # Always drop the banner and the following argv line.
2027  set last $idx
2028  incr last
2029  # Drop the optional timestamp line when present (header=full).
2030  # Match anywhere in the line: VHDL uses "-- Generated on ...", C/Markdown
2031  # may use other comment prefixes.
2032  set ts_line [lindex $lines [expr {$idx + 2}]]
2033  if {$ts_line ne "" && [string first "Generated on " $ts_line] >= 0} {
2034  incr last
2035  }
2036  return [lreplace $lines $idx $last]
2037 }
2038 
2039 ## @brief Compare two text files ignoring trailing whitespace and blank lines.
2040 #
2041 # Used by CopyChebyFiles to detect if a regenerated cheby output is
2042 # semantically different from what is currently committed on disk.
2043 #
2044 # @param[in] file1 Path to the first file.
2045 # @param[in] file2 Path to the second file.
2046 # @param[in] skip_cheby_header If 1, ignore cheby auto-generated comment headers.
2047 #
2048 # @return 1 if the files are equivalent, 0 otherwise.
2049 proc FilesAreSemanticallyEqual {file1 file2 {skip_cheby_header 0}} {
2050  if {![file exists $file1] || ![file exists $file2]} {
2051  return 0
2052  }
2053  set fa [open $file1 r]
2054  set fb [open $file2 r]
2055  set la [list]
2056  set lb [list]
2057  while {[gets $fa line] != -1} {
2058  set line [string trimright $line]
2059  if {[string trim $line] eq ""} { continue }
2060  lappend la $line
2061  }
2062  while {[gets $fb line] != -1} {
2063  set line [string trimright $line]
2064  if {[string trim $line] eq ""} { continue }
2065  lappend lb $line
2066  }
2067  close $fa
2068  close $fb
2069  if {$skip_cheby_header} {
2070  set la [StripChebyGeneratedHeader $la]
2071  set lb [StripChebyGeneratedHeader $lb]
2072  }
2073  if {[llength $la] != [llength $lb]} {
2074  return 0
2075  }
2076  foreach x $la y $lb {
2077  if {$x ne $y} { return 0 }
2078  }
2079  return 1
2080 }
2081 
2082 ## @brief Read .chb (Cheby) list files and either regenerate or check the
2083 # declared cheby outputs (HDL, C headers, documentation, etc.).
2084 #
2085 # This is the Cheby equivalent of CopyIPbusXMLs. For each .chb file in
2086 # `<proj_dir>/list/`, every non-comment line is parsed (see ParseChebyListFile)
2087 # and the cheby executable is invoked with the appropriate flags. In `check`
2088 # mode (the default), generated outputs are written to a temporary directory
2089 # and compared against the on-disk targets, reporting any mismatches; in
2090 # `generate` mode the on-disk targets are overwritten with the freshly
2091 # generated content.
2092 #
2093 # A copy of the resolved .cheby inputs and the (existing) generated outputs
2094 # is also placed under `<dst>` for traceability.
2095 #
2096 # @param[in] proj_dir Project directory (must contain a `list/` folder).
2097 # @param[in] path Base path against which paths inside the .chb
2098 # list files are resolved (typically the repo root).
2099 # @param[in] dst Destination directory where the resolved cheby
2100 # dependency set and selected outputs are copied.
2101 # @param[in] cheby_version Project-wide cheby version (recorded in logs).
2102 # @param[in] cheby_sha Project-wide cheby SHA (recorded in logs).
2103 # @param[in] generate If 1, overwrite on-disk outputs with newly
2104 # generated content; if 0, only check.
2105 # @param[in] profile If not "all", only process .chb lines whose
2106 # `profile=` property matches this value.
2107 # @param[in] tool Cheby executable to invoke (default: "cheby").
2108 # @param[in] strict If 1, any mismatch/missing output is reported as
2109 # an Error (otherwise as a CriticalWarning).
2110 # @param[in] verbose If 1, print expanded cheby command lines.
2111 #
2112 # @return The number of errors encountered (0 on success).
2113 proc CopyChebyFiles {proj_dir path dst {cheby_version "0.0.0"} {cheby_sha "00000000"} \
2114  {generate 0} {profile "all"} {tool "cheby"} {strict 0} {verbose 0}} {
2115  set chb_files [glob -nocomplain $proj_dir/list/*.chb]
2116  if {[llength $chb_files] == 0} {
2117  Msg CriticalWarning "No files with .chb extension found in $proj_dir/list."
2118  return 1
2119  }
2120 
2121  lassign [ExecuteRetUserEnv $tool --version] ret tool_msg
2122  if {$ret != 0} {
2123  if {$generate == 1} {
2124  Msg Error "Cannot run cheby: '$tool' not found or not working: $tool_msg"
2125  return 1
2126  } else {
2127  Msg Warning "Cheby executable '$tool' not found or not working ($tool_msg).\
2128  Will not verify cheby outputs."
2129  return 0
2130  }
2131  }
2132  Msg Info "Using cheby tool: $tool ([string trim $tool_msg])"
2133  if {$cheby_version ne "" && $cheby_sha ne ""} {
2134  Msg Info "Cheby list set version: $cheby_version, SHA: $cheby_sha"
2135  }
2136 
2137  set dst [file normalize $dst]
2138  file mkdir $dst
2139  set tmp_dir [file join $dst _check]
2140  file mkdir $tmp_dir
2141  set inputs_dir [file join $dst inputs]
2142  file mkdir $inputs_dir
2143 
2144  set n_lines 0
2145  set n_processed 0
2146  set n_changed 0
2147  set n_errors 0
2148  set severity [expr {$strict ? "Error" : "CriticalWarning"}]
2149 
2150  foreach chb_file $chb_files {
2151  Msg Info "Processing cheby list file: $chb_file"
2152  set entries [ParseChebyListFile $chb_file]
2153  foreach entry $entries {
2154  incr n_lines
2155  set input_rel [dict get $entry input]
2156  set outputs [dict get $entry outputs]
2157  set props [dict get $entry props]
2158  set line_num [dict get $entry line]
2159  set chb_tail [file tail $chb_file]
2160 
2161  set line_profile [DictGet $props "profile" "all"]
2162  if {$profile ne "all" && $line_profile ne "all" && $line_profile ne $profile} {
2163  if {$verbose} {
2164  Msg Info " Skipping $chb_tail:$line_num (profile='$line_profile')"
2165  }
2166  continue
2167  }
2168 
2169  set abs_input [file normalize "$path/$input_rel"]
2170  if {![file exists $abs_input]} {
2171  Msg $severity "Cheby input not found ($chb_tail:$line_num): $abs_input"
2172  incr n_errors
2173  continue
2174  }
2175 
2176  lassign [BuildChebyCmdArgs $props $outputs] ok gen_map opt_flags
2177  if {$ok != 0} {
2178  Msg $severity "$chb_tail:$line_num: $gen_map"
2179  incr n_errors
2180  continue
2181  }
2182 
2183  # Run cheby from the input directory with relative argv paths so generated
2184  # headers match manual invocations (cheby records sys.argv verbatim).
2185  set cheby_cwd [file dirname $abs_input]
2186  set cmd [list $tool -i [file tail $abs_input]]
2187  foreach f $opt_flags { lappend cmd $f }
2188 
2189  set out_targets [list]
2190  set bad_gen 0
2191  foreach {gen out_rel} $gen_map {
2192  set gen_flag [ChebyGenFlag $gen]
2193  if {$gen_flag eq ""} {
2194  Msg $severity "$chb_tail:$line_num: unknown generator '$gen'"
2195  incr n_errors
2196  set bad_gen 1
2197  break
2198  }
2199  set abs_target [file normalize "$path/$out_rel"]
2200  if {$generate == 1} {
2201  set out_path $abs_target
2202  } else {
2203  set out_path [file join $tmp_dir \
2204  [file rootname [file tail $abs_target]]_${n_lines}_${gen}[file extension $abs_target]]
2205  }
2206  lappend cmd $gen_flag [ChebyRelPath $cheby_cwd $out_path]
2207  lappend out_targets [list $out_rel $abs_target $out_path $gen]
2208  }
2209  if {$bad_gen} { continue }
2210 
2211  foreach t $out_targets {
2212  file mkdir [file dirname [lindex $t 1]]
2213  file mkdir [file dirname [lindex $t 2]]
2214  }
2215 
2216  if {$verbose} {
2217  Msg Info " Running (cwd=$cheby_cwd): [join $cmd { }]"
2218  }
2219 
2220  set saved_pwd [pwd]
2221  cd $cheby_cwd
2222  lassign [ExecuteRetUserEnv {*}$cmd] cret cmsg
2223  cd $saved_pwd
2224  if {$cret != 0} {
2225  Msg $severity "Cheby failed for $input_rel ($chb_tail:$line_num): $cmsg"
2226  incr n_errors
2227  continue
2228  }
2229  incr n_processed
2230 
2231  if {$generate == 1} {
2232  foreach t $out_targets {
2233  Msg Info " Generated: [lindex $t 0]"
2234  incr n_changed
2235  }
2236  } else {
2237  foreach t $out_targets {
2238  set out_rel [lindex $t 0]
2239  set abs_target [lindex $t 1]
2240  set out_path [lindex $t 2]
2241  if {![file exists $abs_target]} {
2242  Msg $severity "Expected cheby output missing: $out_rel ($chb_tail:$line_num)"
2243  incr n_errors
2244  continue
2245  }
2246  if {[FilesAreSemanticallyEqual $out_path $abs_target 1] == 0} {
2247  Msg $severity "Cheby output mismatch: $out_rel does not match what cheby\
2248  would generate from $input_rel ($chb_tail:$line_num).\
2249  Run `Hog/Do cheby <project> -generate` to refresh."
2250  catch { file copy -force $out_path "$dst/diff_[file tail $abs_target]" }
2251  incr n_changed
2252  incr n_errors
2253  } else {
2254  Msg Info " OK: $out_rel matches generator output."
2255  }
2256  }
2257  }
2258 
2259  catch { file copy -force $abs_input [file join $inputs_dir [file tail $abs_input]] }
2260  foreach t $out_targets {
2261  set abs_target [lindex $t 1]
2262  if {[file exists $abs_target]} {
2263  catch { file copy -force $abs_target [file join $dst [file tail $abs_target]] }
2264  }
2265  }
2266  }
2267  }
2268 
2269  catch { file delete -force $tmp_dir }
2270 
2271  set summary "Cheby summary: parsed $n_lines line(s), ran $n_processed cheby invocation(s),\
2272  $n_changed change(s), $n_errors error(s)."
2273  if {$n_errors > 0} {
2274  Msg CriticalWarning $summary
2275  } else {
2276  Msg Info $summary
2277  }
2278  return $n_errors
2279 }
2280 
2281 ## @brief Returns the description from the hog.conf file.
2282 # The description is the comment in the second line stripped of the hashes
2283 # If the description contains the word test, Test or TEST, then "test" is simply returned.
2284 # This is used to avoid printing them in ListProjects unless -all is specified
2285 #
2286 # @param[in] conf_file the path to the hog.conf file
2287 #
2288 proc DescriptionFromConf {conf_file} {
2289  set f [open $conf_file "r"]
2290  set lines [split [read $f] "\n"]
2291  close $f
2292  set second_line [lindex $lines 1]
2293 
2294 
2295  if {![regexp {\#+ *(.+)} $second_line - description]} {
2296  set description ""
2297  }
2298 
2299  if {[regexp -all {test|Test|TEST} $description]} {
2300  set description "test"
2301  }
2302 
2303  return $description
2304 }
2305 
2306 ## @brief Returns the value in a Tcl dictionary corresponding to the chosen key
2307 #
2308 # @param[in] dictName the name of the dictionary
2309 # @param[in] keyName the name of the key
2310 # @param[in] default the default value to be returned if the key is not found (default "")
2311 #
2312 # @return The value in the dictionary corresponding to the provided key
2313 proc DictGet {dictName keyName {default ""}} {
2314  if {[dict exists $dictName $keyName]} {
2315  return [dict get $dictName $keyName]
2316  } else {
2317  return $default
2318  }
2319 }
2320 
2321 ## Sorts a dictionary
2322 #
2323 # @param[in] dict the dictionary
2324 # @param[in] args the arguments to pass to lsort, e.g. -ascii, -dictionary, -decreasing
2325 # @returns a new dictionary with the keys sorted according to the arguments
2326 proc DictSort {dict args} {
2327  set res {}
2328  foreach key [lsort {*}$args [dict keys $dict]] {
2329  dict set res $key [dict get $dict $key]
2330  }
2331  set res
2332 }
2333 
2334 ## @brief Checks the Doxygen version installed in this machine
2335 #
2336 # @param[in] target_version the version required by the current project
2337 #
2338 # @return Returns 1, if the system Doxygen version is greater or equal to the target
2339 proc DoxygenVersion {target_version} {
2340  set ver [split $target_version "."]
2341  set v [Execute doxygen --version]
2342  Msg Info "Found Doxygen version: $v"
2343  set current_ver [split $v ". "]
2344  set target [expr {[lindex $ver 0] * 100000 + [lindex $ver 1] * 100 + [lindex $ver 2]}]
2345  set current [expr {[lindex $current_ver 0] * 100000 + [lindex $current_ver 1] * 100 + [lindex $current_ver 2]}]
2346 
2347  return [expr {$target <= $current}]
2348 }
2349 
2350 ## @brief Handle eos commands
2351 #
2352 # It can be used with lassign like this: lassign [eos <eos command> ] ret result
2353 #
2354 # @param[in] command: the EOS command to be run, e.g. ls, cp, mv, rm
2355 # @param[in] attempt: (default 0) how many times the command should be attempted in case of failure
2356 #
2357 # @returns a list of 2 elements: the return value (0 if no error occurred) and the output of the EOS command
2358 proc eos {command {attempt 1}} {
2359  global env
2360  if {![info exists env(EOS_MGM_URL)]} {
2361  Msg Warning "Environment variable EOS_MGM_URL not set, setting it to default value root://eosuser.cern.ch"
2362  set ::env(EOS_MGM_URL) "root://eosuser.cern.ch"
2363  }
2364  if {$attempt < 1} {
2365  Msg Warning "The value of attempt should be 1 or more, not $attempt, setting it to 1 as default"
2366  set attempt 1
2367  }
2368  for {set i 0} {$i < $attempt} {incr i} {
2369  set ret [catch {exec -ignorestderr eos {*}$command} result]
2370  if {$ret == 0} {
2371  break
2372  } else {
2373  if {$attempt > 1} {
2374  set wait [expr {1 + int(rand() * 29)}]
2375  Msg Warning "Command $command failed ($i/$attempt): $result, trying again in $wait seconds..."
2376  after [expr {$wait * 1000}]
2377  }
2378  }
2379  }
2380  return [list $ret $result]
2381 }
2382 
2383 ## @brief Quote a string for a POSIX shell single-quoted literal.
2384 proc ShellSingleQuote {s} {
2385  return "'[string map {' '\\''} $s]'"
2386 }
2387 
2388 ## @brief Run an external command using the user's shell environment.
2389 #
2390 # Inside Vivado, Tcl's exec can route Python scripts through Vivado's bundled
2391 # Python (missing deps such as PyYAML). Re-run via bash with Vivado Python env
2392 # cleared, and for .py tools call /usr/bin/python3 explicitly.
2393 #
2394 # Optional env HOG_PYTHON overrides the python command (full path or name).
2395 # Important: the bash -c script must be passed as ONE Tcl list element, otherwise
2396 # Tcl word-splits it and bash only runs the first token (e.g. bare `env`).
2397 proc ExecuteRetUserEnv {args} {
2398  global env
2399  if {![IsVivado]} {
2400  return [ExecuteRet {*}$args]
2401  }
2402 
2403  set run_args $args
2404  set tool [lindex $args 0]
2405  if {[regexp {\.py$} $tool]} {
2406  if {[info exists env(HOG_PYTHON)] && $env(HOG_PYTHON) ne ""} {
2407  set py $env(HOG_PYTHON)
2408  } else {
2409  set py /usr/bin/python3
2410  }
2411  set run_args [linsert $args 0 $py]
2412  }
2413 
2414  set quoted [list]
2415  foreach a $run_args {
2416  lappend quoted [ShellSingleQuote $a]
2417  }
2418  set inner [join $quoted { }]
2419 
2420  if {[OS] eq "windows"} {
2421  return [ExecuteRet {*}[list cmd /c $inner]]
2422  }
2423 
2424  # Vivado sets PYTHON/PYTHONPATH/PYTHONHOME to its bundled 3.8.3. Clear them
2425  # and put system bins first so cheby sees system packages (e.g. PyYAML).
2426  set cmd "env -u PYTHONHOME -u PYTHONPATH -u PYTHON \
2427 PATH=/usr/local/bin:/usr/bin:/bin:\$PATH $inner"
2428  return [ExecuteRet {*}[list /bin/bash -c $cmd]]
2429 }
2430 
2431 ## @brief Handle shell commands
2432 #
2433 # It can be used with lassign like this: lassign [Execute <command> ] ret result
2434 #
2435 # @param[in] args: the shell command
2436 #
2437 # @returns the output of the command
2438 proc Execute {args} {
2439  global env
2440  lassign [ExecuteRet {*}$args] ret result
2441  if {$ret != 0} {
2442  Msg Error "Command [join $args] returned error code: $ret"
2443  }
2444 
2445  return $result
2446 }
2447 
2448 
2449 ## @brief Handle shell commands
2450 #
2451 # It can be used with lassign like this: lassign [ExecuteRet <command> ] ret result
2452 #
2453 # @param[in] args: the shell command
2454 #
2455 # @returns a list of 2 elements: the return value (0 if no error occurred) and the output of the command
2456 proc ExecuteRet {args} {
2457  global env
2458  if {[llength $args] == 0} {
2459  Msg CriticalWarning "No argument given"
2460  set ret -1
2461  set result ""
2462  } else {
2463  set ret [catch {exec -ignorestderr {*}$args} result]
2464  }
2465 
2466  return [list $ret $result]
2467 }
2468 
2469 ## @brief Extract the [files] section from a sim list file
2470 #
2471 # @param[in] the content of the simulation list file to extract the [files] section from
2472 # @returns a list of files in the [files] section, or all lines if no [files] section is found
2473 proc ExtractFilesSection {file_data} {
2474  set in_files_section 0
2475  set result {}
2476 
2477  foreach line $file_data {
2478  if {[regexp {^ *\[ *files *\]} $line]} {
2479  set in_files_section 1
2480  continue
2481  }
2482  if {$in_files_section} {
2483  if {[regexp {^ *\[.*\]} $line]} {
2484  break
2485  }
2486  lappend result $line
2487  }
2488  }
2489 
2490  # If [files] was not found, return all file_data
2491  if {!$in_files_section} {
2492  return $file_data
2493  } else {
2494  return $result
2495  }
2496 }
2497 
2498 
2499 ## @brief Tags the repository with a new version calculated on the basis of the previous tags
2500 #
2501 # @param[in] tag a tag in the Hog format: v$M.$m.$p or b$(mr)v$M.$m.$p-$n
2502 #
2503 # @return a list containing: Major minor patch v.
2504 #
2505 proc ExtractVersionFromTag {tag} {
2506  if {[regexp {^(?:b(\d+))?v0*(\d+)\.0*(\d+).0*(\d+)(?:-\d+)?$} $tag -> mr M m p]} {
2507  if {$mr eq ""} {
2508  set mr 0
2509  }
2510  } else {
2511  Msg Warning "Repository tag $tag is not in a Hog-compatible format."
2512  set mr -1
2513  set M -1
2514  set m -1
2515  set p -1
2516  }
2517  return [list $M $m $p $mr]
2518 }
2519 
2520 
2521 ## @brief Checks if file was committed into the repository
2522 #
2523 #
2524 # @param[in] File: file name
2525 #
2526 # @returns 1 if file was committed and 0 if file was not committed
2527 proc FileCommitted {File} {
2528  set Ret 1
2529  set currentDir [pwd]
2530  cd [file dirname [file normalize $File]]
2531  set GitLog [Git ls-files [file tail $File]]
2532  if {$GitLog == ""} {
2533  Msg CriticalWarning "File [file normalize $File] is not in the git repository. Please add it with:\n git add [file normalize $File]\n"
2534  set Ret 0
2535  }
2536  cd $currentDir
2537  return $Ret
2538 }
2539 
2540 # @brief Returns the common child of two git commits
2541 #
2542 # @param[in] SHA1 The first commit
2543 # @param[in] SHA2 The second commit
2544 proc FindCommonGitChild {SHA1 SHA2} {
2545  # Get the list of all commits in the repository
2546  set commits [Git {log --oneline --merges}]
2547  set ancestor 0
2548  # Iterate over each commit
2549  foreach line [split $commits "\n"] {
2550  set commit [lindex [split $line] 0]
2551 
2552  # Check if both SHA1 and SHA2 are ancestors of the commit
2553  if {[IsCommitAncestor $SHA1 $commit] && [IsCommitAncestor $SHA2 $commit]} {
2554  set ancestor $commit
2555  break
2556  }
2557  }
2558  return $ancestor
2559 }
2560 
2561 
2562 # @brief Returns a list of files in a directory matching a pattern
2563 #
2564 # @param[in] basedir The directory to start looking in
2565 # @param[in pattern A pattern, as defined by the glob command, that the files must match
2566 # Credit: https://stackexchange.com/users/14219/jackson
2567 proc findFiles {basedir pattern} {
2568  # Fix the directory name, this ensures the directory name is in the
2569  # native format for the platform and contains a final directory seperator
2570  set basedir [string trimright [file join [file normalize $basedir] { }]]
2571  set fileList {}
2572 
2573  # Look in the current directory for matching files, -type {f r}
2574  # means ony readable normal files are looked at, -nocomplain stops
2575  # an error being thrown if the returned list is empty
2576  foreach fileName [glob -nocomplain -type {f r} -path $basedir $pattern] {
2577  lappend fileList $fileName
2578  }
2579 
2580  # Now look for any sub direcories in the current directory
2581  foreach dirName [glob -nocomplain -type {d r} -path $basedir *] {
2582  # Recusively call the routine on the sub directory and append any
2583  # new files to the results
2584  set subDirList [findFiles $dirName $pattern]
2585  if {[llength $subDirList] > 0} {
2586  foreach subDirFile $subDirList {
2587  lappend fileList $subDirFile
2588  }
2589  }
2590  }
2591  return $fileList
2592 }
2593 
2594 ## @brief determine file type from extension
2595 # Used only for Quartus
2596 #
2597 ## @return FILE_TYPE the file Type
2598 proc FindFileType {file_name} {
2599  set extension [file extension $file_name]
2600  switch $extension {
2601  .stp {
2602  set file_extension "USE_SIGNALTAP_FILE"
2603  }
2604  .vhd {
2605  set file_extension "VHDL_FILE"
2606  }
2607  .vhdl {
2608  set file_extension "VHDL_FILE"
2609  }
2610  .v {
2611  set file_extension "VERILOG_FILE"
2612  }
2613  .sv {
2614  set file_extension "SYSTEMVERILOG_FILE"
2615  }
2616  .sdc {
2617  set file_extension "SDC_FILE"
2618  }
2619  .pdc {
2620  set file_extension "PDC_FILE"
2621  }
2622  .ndc {
2623  set file_extension "NDC_FILE"
2624  }
2625  .fdc {
2626  set file_extension "FDC_FILE"
2627  }
2628  .qsf {
2629  set file_extension "SOURCE_FILE"
2630  }
2631  .ip {
2632  set file_extension "IP_FILE"
2633  }
2634  .qsys {
2635  set file_extension "QSYS_FILE"
2636  }
2637  .qip {
2638  set file_extension "QIP_FILE"
2639  }
2640  .sip {
2641  set file_extension "SIP_FILE"
2642  }
2643  .bsf {
2644  set file_extension "BSF_FILE"
2645  }
2646  .bdf {
2647  set file_extension "BDF_FILE"
2648  }
2649  .tcl {
2650  set file_extension "COMMAND_MACRO_FILE"
2651  }
2652  .vdm {
2653  set file_extension "VQM_FILE"
2654  }
2655  default {
2656  set file_extension "ERROR"
2657  Msg Error "Unknown file extension $extension"
2658  }
2659  }
2660  return $file_extension
2661 }
2662 
2663 
2664 # @brief Returns the newest version in a list of versions
2665 #
2666 # @param[in] versions The list of versions
2667 proc FindNewestVersion {versions} {
2668  set new_ver 00000000
2669  foreach ver $versions {
2670  # tclint-disable-next-line redundant-expr
2671  if {[expr 0x$ver > 0x$new_ver]} {
2672  set new_ver $ver
2673  }
2674  }
2675  return $new_ver
2676 }
2677 
2678 # Find repo root by walking up from a start dir until we see Top/ and Projects/.
2679 # For absolute paths we do not normalize, so the path form is preserved and [file exists]
2680 # sees the same view as the script was loaded from.
2681 proc FindRepoRoot {start_dir} {
2682  if {[file pathtype $start_dir] eq "relative"} {
2683  set dir [file normalize [file join [pwd] $start_dir]]
2684  } else {
2685  set dir $start_dir
2686  }
2687  while {1} {
2688  if {[file exists [file join $dir Top]] && [file exists [file join $dir Projects]]} {
2689  return $dir
2690  }
2691  set parent [file dirname $dir]
2692  if {$parent eq $dir} {
2693  return ""
2694  }
2695  set dir $parent
2696  }
2697 }
2698 
2699 ## @brief Set VHDL version to 2008 for *.vhd files
2700 #
2701 # @param[in] file_name the name of the HDL file
2702 #
2703 # @return "-hdl_version VHDL_2008" if the file is a *.vhd files else ""
2704 proc FindVhdlVersion {file_name} {
2705  set extension [file extension $file_name]
2706  switch $extension {
2707  .vhd {
2708  set vhdl_version "-hdl_version VHDL_2008"
2709  }
2710  .vhdl {
2711  set vhdl_version "-hdl_version VHDL_2008"
2712  }
2713  default {
2714  set vhdl_version ""
2715  }
2716  }
2717 
2718  return $vhdl_version
2719 }
2720 
2721 ## @brief Format a generic to a 32 bit verilog style hex number, e.g.
2722 # take in ea8394c and return 32'h0ea8394c
2723 #
2724 # @param[in] unformatted generic
2725 proc FormatGeneric {generic} {
2726  if {[string is integer "0x$generic"]} {
2727  return [format "32'h%08X" "0x$generic"]
2728  } else {
2729  # for non integers (e.g. blanks) just return 0
2730  return [format "32'h%08X" 0]
2731  }
2732 }
2733 
2734 # @brief Generate the bitstream
2735 #
2736 # @param[in] project_name The name of the project
2737 # @param[in] run_folder The path where to run the implementation
2738 # @param[in] repo_path The main path of the git repository
2739 # @param[in] njobs The number of CPU jobs to run in parallel
2740 proc GenerateBitstream {{run_folder ""} {repo_path .} {njobs 1}} {
2741  Msg Info "Starting write bitstream flow..."
2742  if {[IsQuartus]} {
2743  set revision [get_current_revision]
2744  if {[catch {execute_module -tool asm} result]} {
2745  Msg Error "Result: $result\n"
2746  Msg Error "Generate bitstream failed. See the report file.\n"
2747  } else {
2748  Msg Info "Generate bitstream was successful for revision $revision.\n"
2749  }
2750  } elseif {[IsLibero]} {
2751  Msg Info "Run GENERATEPROGRAMMINGDATA ..."
2752  if {[catch {run_tool -name {GENERATEPROGRAMMINGDATA}}]} {
2753  Msg Error "GENERATEPROGRAMMINGDATA FAILED!"
2754  } else {
2755  Msg Info "GENERATEPROGRAMMINGDATA PASSED."
2756  }
2757  Msg Info "Sourcing Hog/Tcl/integrated/post-bitstream.tcl"
2758  source $repo_path/Hog/Tcl/integrated/post-bitstream.tcl
2759  } elseif {[IsDiamond]} {
2760  prj_run Export -impl Implementation0 -task Bitgen
2761  }
2762 }
2763 
2764 ## @brief Function used to generate a qsys system from a .qsys file.
2765 # The procedure adds the generated IPs to the project.
2766 #
2767 # @param[in] qsysFile the Intel Platform Designed file (.qsys), containing the system to be generated
2768 # @param[in] commandOpts the command options to be used during system generation as they are in qsys-generate options
2769 #
2770 proc GenerateQsysSystem {qsysFile commandOpts} {
2771  global env
2772  if {[file exists $qsysFile] != 0} {
2773  set qsysPath [file dirname $qsysFile]
2774  set qsysName [file rootname [file tail $qsysFile]]
2775  set qsysIPDir "$qsysPath/$qsysName"
2776  set qsysLogFile "$qsysPath/$qsysName.qsys-generate.log"
2777 
2778  set qsys_rootdir ""
2779  if {![info exists ::env(QSYS_ROOTDIR)]} {
2780  if {[info exists ::env(QUARTUS_ROOTDIR)]} {
2781  set qsys_rootdir "$::env(QUARTUS_ROOTDIR)/sopc_builder/bin"
2782  Msg Warning "The QSYS_ROOTDIR environment variable is not set! I will use $qsys_rootdir"
2783  } else {
2784  Msg CriticalWarning "The QUARTUS_ROOTDIR environment variable is not set! Assuming all quartus executables are contained in your PATH!"
2785  }
2786  } else {
2787  set qsys_rootdir $::env(QSYS_ROOTDIR)
2788  }
2789 
2790  set cmd "$qsys_rootdir/qsys-generate"
2791  set cmd_options "$qsysFile --output-directory=$qsysIPDir $commandOpts"
2792  if {![catch {"exec $cmd -version"}] || [lindex $::errorCode 0] eq "NONE"} {
2793  Msg Info "Executing: $cmd $cmd_options"
2794  Msg Info "Saving logfile in: $qsysLogFile"
2795  if {[catch {eval exec -ignorestderr "$cmd $cmd_options >>& $qsysLogFile"} ret opt]} {
2796  set makeRet [lindex [dict get $opt -errorcode] end]
2797  Msg CriticalWarning "$cmd returned with $makeRet"
2798  }
2799  } else {
2800  Msg Error " Could not execute command $cmd"
2801  exit 1
2802  }
2803  #Add generated IPs to project
2804  set qsysIPFileList [concat \
2805  [glob -nocomplain -directory $qsysIPDir -types f *.ip *.qip] \
2806  [glob -nocomplain -directory "$qsysIPDir/synthesis" -types f *.ip *.qip *.vhd *.vhdl]]
2807  foreach qsysIPFile $qsysIPFileList {
2808  if {[file exists $qsysIPFile] != 0} {
2809  set qsysIPFileType [FindFileType $qsysIPFile]
2810  set_global_assignment -name $qsysIPFileType $qsysIPFile
2811  # Write checksum to file
2812  set IpMd5Sum [Md5Sum $qsysIPFile]
2813  # open file for writing
2814  set fileDir [file normalize "./hogTmp"]
2815  set fileName "$fileDir/.hogQsys.md5"
2816  if {![file exists $fileDir]} {
2817  file mkdir $fileDir
2818  }
2819  set hogQsysFile [open $fileName "a"]
2820  set fileEntry "$qsysIPFile\t$IpMd5Sum"
2821  puts $hogQsysFile $fileEntry
2822  close $hogQsysFile
2823  }
2824  }
2825  } else {
2826  Msg ERROR "Error while generating ip variations from qsys: $qsysFile not found!"
2827  }
2828 }
2829 
2830 
2831 ## Format generics from conf file to string that simulators accepts
2832 #
2833 # @param[in] dict containing generics from conf file
2834 # @param[in] target: software target(vivado, questa)
2835 # defines the output format of the string
2836 proc GenericToSimulatorString {prop_dict target} {
2837  set prj_generics ""
2838  dict for {theKey theValue} $prop_dict {
2839  set theValue [string trim $theValue]
2840  set valueHexFull ""
2841  set valueNumBits ""
2842  set valueHexFlag ""
2843  set valueHex ""
2844  set valueIntFull ""
2845  set ValueInt ""
2846  set valueStrFull ""
2847  set ValueStr ""
2848  regexp {([0-9]*)('h)([0-9a-fA-F]*)} $theValue valueHexFull valueNumBits valueHexFlag valueHex
2849  regexp {^([0-9]*)$} $theValue valueIntFull ValueInt
2850  regexp {(?!^\d+$)^.+$} $theValue valueStrFull ValueStr
2851  if {[string tolower $target] == "vivado" || [string tolower $target] == "xsim"} {
2852  if {[string tolower $theValue] == "true" || [string tolower $theValue] == "false"} {
2853  set prj_generics "$prj_generics $theKey=[string tolower $theValue]"
2854  } elseif {$valueNumBits != "" && $valueHexFlag != "" && $valueHex != ""} {
2855  set prj_generics "$prj_generics $theKey=$valueHexFull"
2856  } elseif {$valueIntFull != "" && $ValueInt != ""} {
2857  set prj_generics "$prj_generics $theKey=$ValueInt"
2858  } elseif {$valueStrFull != "" && $ValueStr != ""} {
2859  set prj_generics "$prj_generics $theKey=\"$ValueStr\""
2860  } else {
2861  set prj_generics "$prj_generics $theKey=\"$theValue\""
2862  }
2863  } elseif {[lsearch -exact [GetSimulators] [string tolower $target]] >= 0} {
2864  if {$valueNumBits != "" && $valueHexFlag != "" && $valueHex != ""} {
2865  set numBits 0
2866  scan $valueNumBits %d numBits
2867  set numHex 0
2868  scan $valueHex %x numHex
2869  binary scan [binary format "I" $numHex] "B*" binval
2870  set numBits [expr {$numBits - 1}]
2871  set numBin [string range $binval end-$numBits end]
2872  set prj_generics "$prj_generics $theKey=\"$numBin\""
2873  } elseif {$valueIntFull != "" && $ValueInt != ""} {
2874  set prj_generics "$prj_generics $theKey=$ValueInt"
2875  } elseif {$valueStrFull != "" && $ValueStr != ""} {
2876  set prj_generics "$prj_generics {$theKey=\"$ValueStr\"}"
2877  } else {
2878  set prj_generics "$prj_generics {$theKey=\"$theValue\"}"
2879  }
2880  } else {
2881  Msg Warning "Target : $target not implemented"
2882  }
2883  }
2884  return $prj_generics
2885 }
2886 
2887 ## Get the configuration files to create a Hog project
2888 #
2889 # @param[in] proj_dir: The project directory containing the conf file or the the tcl file
2890 #
2891 # @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
2892 proc GetConfFiles {proj_dir} {
2893  Msg Debug "GetConfFiles called with proj_dir=$proj_dir"
2894  if {![file isdirectory $proj_dir]} {
2895  Msg Error "$proj_dir is supposed to be the top project directory"
2896  return -1
2897  }
2898  set conf_file [file normalize $proj_dir/hog.conf]
2899  set sim_file [file normalize $proj_dir/sim.conf]
2900  set pre_tcl [file normalize $proj_dir/pre-creation.tcl]
2901  set post_tcl [file normalize $proj_dir/post-creation.tcl]
2902  set pre_rtl [file normalize $proj_dir/pre-rtl.tcl]
2903  set post_rtl [file normalize $proj_dir/post-rtl.tcl]
2904 
2905  return [list $conf_file $sim_file $pre_tcl $post_tcl $pre_rtl $post_rtl]
2906 }
2907 
2908 
2909 # Searches directory for tcl scripts to add as custom commands to launch.tcl
2910 # Returns string of tcl scripts formatted as usage or switch statement
2911 #
2912 # @param[in] directory The directory where to look for custom tcl scripts (Default .)
2913 # @param[in] ret_commands if 1 returns commands as switch statement string instead of usage (Default 0)
2914 proc GetCustomCommands {parameters {directory .}} {
2915  set commands_dict [dict create]
2916  set commands_files [glob -nocomplain $directory/*.tcl]
2917 
2918  if {[llength $commands_files] == 0} {
2919  return ""
2920  }
2921 
2922  foreach file $commands_files {
2923  #Msg Info "do compile libe? $do_compile_lib"
2924  set custom_cmd [LoadCustomCommandFile $file $parameters]
2925 
2926  if {$custom_cmd eq ""} {
2927  continue
2928  }
2929 
2930  #Msg Info "Validating custom command $custom_cmd"
2931  set custom_cmd_name [dict get $custom_cmd NAME]
2932 
2933  Msg Debug "Loaded custom command '$custom_cmd_name' from $file"
2934 
2935  #Ensure command is not already defined
2936  if {[dict exists $commands_dict $custom_cmd_name]} {
2937  Msg Error "Custom command '$custom_cmd_name' in $file already defined as: \[dict get $commands_dict $custom_cmd_name\]. Skipping."
2938  continue
2939  }
2940 
2941 
2942  set custom_cmd_name [string toupper $custom_cmd_name]
2943  dict set commands_dict $custom_cmd_name $custom_cmd
2944  }
2945 
2946  return $commands_dict
2947 }
2948 
2949 proc SanitizeCustomCommand {cmdDict file parameters} {
2950  # Normalize all user-provided keys to uppercase so NAME/DESCRIPTION/etc are case-insensitive.
2951 
2952 
2953  set normalized {}
2954  foreach k [dict keys $cmdDict] {
2955  set K [string toupper $k]
2956  dict set normalized $K [dict get $cmdDict $k]
2957  }
2958 
2959  set cmdDict $normalized
2960  if {![dict exists $cmdDict NAME]} {
2961  Msg Error "Custom command in $file missing required key NAME. Skipping."
2962  return ""
2963  }
2964  if {![dict exists $cmdDict SCRIPT]} {
2965  Msg Error "Custom command '$[dict get $cmdDict NAME]' in $file missing SCRIPT. Skipping."
2966  return ""
2967  }
2968 
2969  # Allowed keys (uppercased). IDE is optional and will be validated if present.
2970  set allowed {NAME DESCRIPTION OPTIONS CUSTOM_OPTIONS SCRIPT IDE NO_EXIT}
2971  foreach k [dict keys $cmdDict] {
2972  if {[lsearch -exact $allowed $k] < 0} {
2973  Msg Warning "Custom command '[dict get $cmdDict NAME]' in $file: unknown key '$k'. Allowed: $allowed. Skipping."
2974  }
2975  }
2976 
2977  # NAME
2978  set name [string trim [dict get $cmdDict NAME]]
2979  if {$name eq ""} {
2980  Msg Error "Custom command in $file has empty NAME. Skipping."
2981  return ""
2982  }
2983 
2984  if {![regexp {^[a-zA-Z][a-zA-Z0-9_]+$} $name]} {
2985  Msg Error "Custom command NAME '$name' (file $file) contains invalid characters."
2986  }
2987 
2988  # DESCRIPTION
2989  if {![dict exists $cmdDict DESCRIPTION]} {
2990  dict set cmdDict DESCRIPTION "No description provided."
2991  }
2992 
2993 
2994  set hog_parameters {}
2995  foreach p $parameters {
2996  lappend hog_parameters [lindex $p 0]
2997  }
2998 
2999  # OPTIONS
3000  set hog_options {}
3001  if {[dict exists $cmdDict OPTIONS]} {
3002  set raw_opts [dict get $cmdDict OPTIONS]
3003  if {![llength $raw_opts]} {
3004  set raw_opts {}
3005  }
3006 
3007  foreach item $raw_opts {
3008  set found 0
3009  foreach p $parameters {
3010  set hog_parameter [lindex $p 0]
3011  if {$item eq $hog_parameter} {
3012  lappend hog_options $p
3013  set found 1
3014  break
3015  }
3016  }
3017  if {!$found} {
3018  Msg Warning "Custom command '$name' in $file: option '$item' not found in Hog parameters. Skipping."
3019  }
3020  }
3021  dict set cmdDict OPTIONS $hog_options
3022  } else {
3023  dict set cmdDict OPTIONS {}
3024  }
3025 
3026 
3027  # CUSTOM_OPTIONS
3028  set opt_defs {}
3029  if {[dict exists $cmdDict CUSTOM_OPTIONS]} {
3030  set raw_opts [dict get $cmdDict CUSTOM_OPTIONS]
3031  if {![llength $raw_opts]} {
3032  set raw_opts {}
3033  }
3034  foreach item $raw_opts {
3035  if {[llength $item] != 2 && [llength $item] != 3} {
3036  Msg Error "Bad custom option: \[$item\]. Custom command '$name' in $file: \
3037  each CUSTOM_OPTIONS entry must be {option \"help\"} for flags \
3038  and {option \"default_value\" \"help\"} for options with arguments. Skipping command."
3039  return ""
3040  }
3041 
3042  if {[llength $item] == 2} {
3043  lassign $item opt help
3044  set def ""
3045  } else {
3046  lassign $item opt def help
3047  }
3048 
3049  if {[IsInList $opt $hog_parameters] == 1} {
3050  Msg Warning "Custom command '$name' in $file: option '$opt' already defined in Hog parameters. Skipping."
3051  continue
3052  }
3053 
3054 
3055  #optional .arg in option regex
3056  if {![regexp {^[a-zA-Z][a-zA-Z0-9_]*(\.arg)?$} $opt]} {
3057  Msg Error "Custom command '$name' in $file: invalid option name '$opt'."
3058  return ""
3059  }
3060 
3061  if {$help eq ""} {
3062  Msg Warning "Custom command '$name' option '$opt' has empty help text."
3063  }
3064  }
3065  } else {
3066  dict set cmdDict CUSTOM_OPTIONS {}
3067  }
3068 
3069  # NO EXIT
3070  if {[dict exists $cmdDict NO_EXIT]} {
3071  set no_exit [dict get $cmdDict NO_EXIT]
3072  set no_exit [string tolower [string trim $no_exit]]
3073 
3074  if {$no_exit eq "1" || $no_exit eq "true"} {
3075  set no_exit 1
3076  } else {
3077  set no_exit 0
3078  }
3079 
3080  dict set cmdDict NO_EXIT $no_exit
3081  } else {
3082  dict set cmdDict NO_EXIT 0
3083  }
3084 
3085  return $cmdDict
3086 }
3087 
3088 proc LoadCustomCommandFile {file parameters} {
3089  set saved_pwd [pwd]
3090  set dir [file dirname $file]
3091  cd $dir
3092  unset -nocomplain ::hog_command
3093  set rc [catch {source $file} err]
3094  cd $saved_pwd
3095  if {$rc} {
3096  Msg Error "Error sourcing custom command file $file: $err"
3097  return ""
3098  }
3099  if {![info exists ::hog_command]} {
3100  Msg Warning "File $file did not define ::hog_command. Skipping."
3101  return ""
3102  }
3103  set cmdDict $::hog_command
3104  # Ensure it's a dict
3105  if {[catch {dict size $cmdDict}]} {
3106  Msg Error "In $file ::hog_command is not a valid dict. Skipping."
3107  return ""
3108  }
3109  return [SanitizeCustomCommand $cmdDict $file $parameters]
3110 }
3111 
3112 
3113 ## Get the Date and time of a commit (or current time if Git < 2.9.3)
3114 #
3115 # @param[in] commit The commit
3116 proc GetDateAndTime {commit} {
3117  set clock_seconds [clock seconds]
3118 
3119  if {[GitVersion 2.9.3]} {
3120  set date [Git "log -1 --format=%cd --date=format:%d%m%Y $commit"]
3121  set timee [Git "log -1 --format=%cd --date=format:00%H%M%S $commit"]
3122  } else {
3123  Msg Warning "Found Git version older than 2.9.3. Using current date and time instead of commit time."
3124  set date [clock format $clock_seconds -format {%d%m%Y}]
3125  set timee [clock format $clock_seconds -format {00%H%M%S}]
3126  }
3127  return [list $date $timee]
3128 }
3129 
3130 ## @brief Gets a list of files contained in the current fileset that match a file name (passed as parameter)
3131 #
3132 # The file name is matched against the input parameter.
3133 #
3134 # @param[in] file name (or part of it)
3135 # @param[in] fileset name
3136 #
3137 # @return a list of files matching the parameter in the chosen fileset
3138 #
3139 proc GetFile {file fileset} {
3140  if {[IsXilinx]} {
3141  # Vivado
3142  set Files [get_files -all $file -of_object [get_filesets $fileset]]
3143  set f [lindex $Files 0]
3144 
3145  return $f
3146  } elseif {[IsQuartus]} {
3147  # Quartus
3148  return ""
3149  } else {
3150  # Tcl Shell
3151  puts "***DEBUG Hog:GetFile $file"
3152  return "DEBUG_file"
3153  }
3154 }
3155 
3156 ## @brief Extract the generics from a file
3157 #
3158 # @param[in] filename The file from which to extract the generics
3159 # @param[in] entity The entity in the file from which to extract the generics (default "")
3160 proc GetFileGenerics {filename {entity ""}} {
3161  set file_type [FindFileType $filename]
3162  if {[string equal $file_type "VERILOG_FILE"] || [string equal $file_type "SYSTEMVERILOG_FILE"]} {
3163  return [GetVerilogGenerics $filename]
3164  } elseif {[string equal $file_type "VHDL_FILE"]} {
3165  return [GetVhdlGenerics $filename $entity]
3166  } else {
3167  Msg CriticalWarning "Could not determine extension of top level file."
3168  }
3169 }
3170 
3171 ## @brief Gets custom generics from hog
3172 #
3173 # @param[in] proj_dir: the top folder of the project
3174 # @return dict with generics
3175 #
3176 proc GetGenericsFromConf {proj_dir} {
3177  set generics_dict [dict create]
3178  set top_dir "Top/$proj_dir"
3179  set conf_file "$top_dir/hog.conf"
3180  set conf_index 0
3181  Msg Debug "GetGenericsFromConf called with proj_dir=$proj_dir, top_dir=$top_dir"
3182 
3183  if {[file exists $conf_file]} {
3184  set properties [ReadConf [lindex [GetConfFiles $top_dir] $conf_index]]
3185  if {[dict exists $properties generics]} {
3186  set generics_dict [dict get $properties generics]
3187  }
3188  } else {
3189  Msg Warning "File $conf_file not found."
3190  }
3191  return $generics_dict
3192 }
3193 
3194 ## @brief Gets custom Verilog `define macros from hog.conf
3195 #
3196 # @param[in] proj_dir: the top folder of the project
3197 # @return dict with defines
3198 #
3199 proc GetDefinesFromConf {proj_dir} {
3200  set defines_dict [dict create]
3201  set top_dir "Top/$proj_dir"
3202  set conf_file "$top_dir/hog.conf"
3203  set conf_index 0
3204  Msg Debug "GetDefinesFromConf called with proj_dir=$proj_dir, top_dir=$top_dir"
3205 
3206  if {[file exists $conf_file]} {
3207  set properties [ReadConf [lindex [GetConfFiles $top_dir] $conf_index]]
3208  if {[dict exists $properties defines]} {
3209  set defines_dict [dict get $properties defines]
3210  }
3211  } else {
3212  Msg Warning "File $conf_file not found."
3213  }
3214  return $defines_dict
3215 }
3216 
3217 ## @brief Gets the simulation sets from the project
3218 #
3219 # @param[in] project_name: the name of the project
3220 # @param[in] repo_path: the path to the repository
3221 # @param[in] simsets: a list of simulation sets to retrieve (default: all)
3222 # @param[in] ghdl: if 1, only GHDL simulation sets are returned (default: 0),
3223 # otherwise only non-GHDL simulation sets are returned
3224 # @param[in] no_conf: if 1, the simulation sets are returned without reading the sim.conf file (default: 0)
3225 # @return a dictionary with the simulation sets, where the keys are the simulation set names
3226 # and the values are dictionaries with the properties of each simulation set
3227 proc GetSimSets {project_name repo_path {simsets ""} {ghdl 0} {no_conf 0}} {
3228  set simsets_dict [dict create]
3229  set list_dir "$repo_path/Top/$project_name/list"
3230  set list_files []
3231  if {$simsets != ""} {
3232  foreach s $simsets {
3233  set list_file "$list_dir/$s.sim"
3234  if {[file exists $list_file]} {
3235  lappend list_files $list_file
3236  } elseif {$s != "sim_1"} {
3237  Msg CriticalWarning "Simulation set list file $list_file not found."
3238  return ""
3239  }
3240  }
3241  } else {
3242  set list_files [glob -nocomplain -directory $list_dir "*.sim"]
3243  }
3244 
3245  # Get simulation properties from conf file
3246  set proj_dir [file normalize $repo_path/Top/$project_name]
3247  set sim_file [file normalize $proj_dir/sim.conf]
3248 
3249  foreach list_file $list_files {
3250  set file_name [file tail $list_file]
3251  set simset_name [file rootname $file_name]
3252  set fp [open $list_file r]
3253  set file_data [read $fp]
3254  close $fp
3255  set data [split $file_data "\n"]
3256 
3257  set firstline [lindex $data 0]
3258  # Find simulator
3259  if {[regexp {^ *\# *Simulator} $firstline]} {
3260  set simulator_prop [regexp -all -inline {\S+} $firstline]
3261  set simulator [string tolower [lindex $simulator_prop end]]
3262  } else {
3263  Msg Warning "Simulator not set in $simset_name.sim. \
3264  The first line of $simset_name.sim should be #Simulator <SIMULATOR_NAME>,\
3265  where <SIMULATOR_NAME> can be xsim, questa, modelsim, ghdl, riviera, activehdl,\
3266  ies, or vcs, e.g. #Simulator questa.\
3267  Setting simulator by default to xsim."
3268  set simulator "xsim"
3269  }
3270  if {$simulator eq "skip_simulation"} {
3271  Msg Info "Skipping simulation for $simset_name"
3272  continue
3273  }
3274  if {($ghdl == 1 && $simulator != "ghdl") || ($ghdl == 0 && $simulator == "ghdl")} {
3275  continue
3276  }
3277 
3278  set SIM_PROPERTIES ""
3279  if {[file exists $sim_file] && $no_conf == 0} {
3280  set SIM_PROPERTIES [ReadConf $sim_file]
3281  }
3282 
3283  set global_sim_props [dict create]
3284  dict set global_sim_props "properties" [DictGet $SIM_PROPERTIES "sim"]
3285  dict set global_sim_props "generics" [DictGet $SIM_PROPERTIES "generics"]
3286  dict set global_sim_props "hog" [DictGet $SIM_PROPERTIES "hog"]
3287 
3288 
3289  set sim_dict [dict create]
3290  dict set sim_dict "simulator" $simulator
3291  if {[dict exists $SIM_PROPERTIES $simset_name]} {
3292  dict set sim_dict "properties" [DictGet $SIM_PROPERTIES $simset_name]
3293  dict set sim_dict "generics" [DictGet $SIM_PROPERTIES "$simset_name:generics"]
3294  dict set sim_dict "hog" [DictGet $SIM_PROPERTIES "$simset_name:hog"]
3295  } elseif {$no_conf == 0} {
3296  # Retrieve properties from .sim file
3297  set conf_dict [ReadConf $list_file]
3298  set sim_dict [MergeDict $sim_dict $conf_dict 0]
3299  }
3300  set sim_dict [MergeDict $sim_dict $global_sim_props 0]
3301  dict set simsets_dict $simset_name $sim_dict
3302  }
3303  return $simsets_dict
3304 }
3305 
3306 ## @brief Gets all custom <simset>:generics from sim.conf
3307 #
3308 # @param[in] proj_dir: the top folder of the project
3309 # @return nested dict with all <simset>:generics
3310 #
3311 proc GetSimsetGenericsFromConf {proj_dir} {
3312  set simsets_generics_dict [dict create]
3313  set top_dir "Top/$proj_dir"
3314  set conf_file "$top_dir/sim.conf"
3315  set conf_index 1
3316 
3317  if {[file exists $conf_file]} {
3318  set properties [ReadConf [lindex [GetConfFiles $top_dir] $conf_index]]
3319  # Filter the dictionary for keys ending with ":generics"
3320  set simsets_generics_dict [dict filter $properties key *:generics]
3321  } else {
3322  Msg Warning "File $conf_file not found."
3323  }
3324  return $simsets_generics_dict
3325 }
3326 
3327 
3328 ## Returns the group name from the project directory
3329 #
3330 # @param[in] proj_dir project directory
3331 # @param[in] repo_dir repository directory
3332 #
3333 # @return the group name without initial and final slashes
3334 #
3335 proc GetGroupName {proj_dir repo_dir} {
3336  if {[regexp {^(.*)/(Top|Projects)/+(.*?)/*$} $proj_dir dummy possible_repo_dir proj_or_top dir]} {
3337  # The Top or Project folder is in the root of a the git repository
3338  if {[file normalize $repo_dir] eq [file normalize $possible_repo_dir]} {
3339  set group [file dir $dir]
3340  if {$group == "."} {
3341  set group ""
3342  }
3343  } else {
3344  # The Top or Project folder is NOT in the root of a git repository
3345  Msg Warning "Project directory $proj_dir seems to be in $possible_repo_dir which is not a the main Git repository $repo_dir."
3346  }
3347  } else {
3348  Msg Warning "Could not parse project directory $proj_dir"
3349  set group ""
3350  }
3351  return $group
3352 }
3353 
3354 ## Get custom Hog describe for the project under investigation
3355 #
3356 # @param[in] proj_dir the top directory of the project (e.g. repo_path/Top/group/project)
3357 # @param[in] repo_path the main path of the repository
3358 #
3359 # @return the Hog describe of the project, with a "-dirty" suffix if the project files are not clean
3360 #
3361 proc GetHogDescribe {proj_dir {repo_path .}} {
3362  lassign [GetRepoVersions $proj_dir $repo_path] global_commit global_version
3363  if {$global_commit == 0} {
3364  # in case the repo is dirty, we use the last committed sha and add a -dirty suffix
3365  set new_sha "[string toupper [GetSHA]]"
3366  set suffix "-dirty"
3367  } else {
3368  set new_sha [string toupper $global_commit]
3369  set suffix ""
3370  }
3371  set describe "v[HexVersionToString $global_version]-$new_sha$suffix"
3372  return $describe
3373 }
3374 
3375 ## @brief Extract files, libraries and properties from the project's list files
3376 #
3377 # @param[in] args The arguments are <list_path> <repository path>[options]
3378 # * list_path path to the list file directory
3379 # Options:
3380 # * -list_files <List files> the file wildcard, if not specified all Hog list files will be looked for
3381 # * -sha_mode forwarded to ReadListFile, see there for info
3382 # * -ext_path <external path> path for external libraries forwarded to ReadListFile
3383 #
3384 # @return a list of 3 dictionaries: libraries and properties
3385 # - libraries has library name as keys and a list of filenames as values
3386 # - properties has file names as keys and a list of properties as values
3387 # - filesets has the fileset name as keys and the correspondent list of libraries as values (significant only for simulations)
3388 proc GetHogFiles {args} {
3389  if {[IsQuartus]} {
3390  load_package report
3391  if {[catch {package require cmdline} ERROR]} {
3392  puts "$ERROR\n If you are running this script on tclsh, you can fix this by installing 'tcllib'"
3393  return 0
3394  }
3395  }
3396 
3397 
3398  set parameters {
3399  {list_files.arg "" "The file wildcard, if not specified all Hog list files will be looked for."}
3400  {sha_mode "Forwarded to ReadListFile, see there for info."}
3401  {ext_path.arg "" "Path for the external libraries forwarded to ReadListFile."}
3402  {print_log "Forwarded to ReadListFile, see there for info."}
3403  }
3404  set usage "USAGE: GetHogFiles \[options\] <list path> <repository path>"
3405  if {[catch {array set options [cmdline::getoptions args $parameters $usage]}] || [llength $args] != 2} {
3406  Msg CriticalWarning [cmdline::usage $parameters $usage]
3407  return
3408  }
3409  set list_path [lindex $args 0]
3410  set repo_path [lindex $args 1]
3411 
3412  set list_files $options(list_files)
3413  set sha_mode $options(sha_mode)
3414  set ext_path $options(ext_path)
3415  set print_log $options(print_log)
3416 
3417  if {$sha_mode == 1} {
3418  set sha_mode_opt "-sha_mode"
3419  } else {
3420  set sha_mode_opt ""
3421  }
3422 
3423  if {$print_log == 1} {
3424  set print_log_opt "-print_log"
3425  } else {
3426  set print_log_opt ""
3427  }
3428 
3429 
3430  if {$list_files == ""} {
3431  set list_files {.src,.con,.sim,.ext}
3432  }
3433  set libraries [dict create]
3434  set properties [dict create]
3435  set list_files [glob -nocomplain -directory $list_path "*{$list_files}"]
3436  set filesets [dict create]
3437 
3438  foreach f $list_files {
3439  set ext [file extension $f]
3440  if {$ext == ".ext"} {
3441  lassign [ReadListFile {*}"$sha_mode_opt $print_log_opt $f $ext_path"] l p fs
3442  } else {
3443  lassign [ReadListFile {*}"$sha_mode_opt $print_log_opt $f $repo_path"] l p fs
3444  }
3445  set libraries [MergeDict $l $libraries]
3446  set properties [MergeDict $p $properties]
3447  Msg Debug "list file $f, filesets: $fs"
3448  set filesets [MergeDict $fs $filesets]
3449  Msg Debug "Merged filesets $filesets"
3450  }
3451 
3452  # Auto-discover HLS components from the project's hog.conf (no .src needed).
3453  # The list_path argument is conventionally <proj_dir>/list/, so hog.conf sits
3454  # one level up. For each [hls:<comp>] section with HLS_CONFIG=<path>, add the
3455  # cfg + everything it references to a synthesized "<comp>" library, and -- if
3456  # print_log is on -- show the file tree exactly like a .src would
3457  set proj_conf [file normalize [file join [file dirname $list_path] hog.conf]]
3458  if {[file exists $proj_conf]} {
3459  set hls_configs [GetHlsConfigsFromProjConf $proj_conf $repo_path]
3460  set already_tracked [dict create]
3461  dict for {libname libfiles} $libraries {
3462  foreach lf $libfiles {dict set already_tracked [file normalize $lf] 1}
3463  }
3464  dict for {comp_name cfg_abs} $hls_configs {
3465  if {[dict exists $already_tracked $cfg_abs]} {
3466  Msg Debug "HLS component '$comp_name': cfg $cfg_abs already tracked via a .src, skipping conf-based GetHogFiles discovery."
3467  continue
3468  }
3469  set lib_name "${comp_name}.src"
3470  dict lappend libraries $lib_name $cfg_abs
3471  dict set already_tracked $cfg_abs 1
3472  set hls_extras [ExpandHlsConfigFiles $cfg_abs]
3473  Msg Info "HLS component '$comp_name' (from hog.conf): tracking [expr {1 + [llength $hls_extras]}] file(s) via $cfg_abs"
3474  if {$print_log == 1} {
3475  Msg Status "\nhog.conf \[hls:$comp_name\] (auto-discovered)"
3476  set rel_cfg [Relative $repo_path $cfg_abs 1]
3477  if {$rel_cfg eq ""} {set rel_cfg $cfg_abs}
3478  if {[llength $hls_extras] == 0} {
3479  Msg Status "└── $rel_cfg"
3480  } else {
3481  Msg Status "├── $rel_cfg"
3482  Msg Status " Inside [file tail $cfg_abs] (HLS auto-expand):"
3483  }
3484  }
3485  set n_extras [llength $hls_extras]
3486  set i 0
3487  foreach hls_extra $hls_extras {
3488  incr i
3489  if {[dict exists $already_tracked $hls_extra]} {continue}
3490  dict lappend libraries $lib_name $hls_extra
3491  dict set already_tracked $hls_extra 1
3492  if {$print_log == 1} {
3493  if {$i == $n_extras} {set pad "└──"} else {set pad "├──"}
3494  set rel_extra [Relative [file dirname $cfg_abs] $hls_extra 1]
3495  if {$rel_extra eq ""} {set rel_extra $hls_extra}
3496  Msg Status " $pad $rel_extra"
3497  }
3498  }
3499  }
3500  }
3501 
3502  return [list $libraries $properties $filesets]
3503 }
3504 
3505 # @brief Get the IDE of a Hog project and returns the correct argument for the IDE cli command
3506 #
3507 # @param[in] proj_conf The project hog.conf file
3508 # @param[in] custom_ver If set, use this version instead of the one in the hog.conf
3509 proc GetIDECommand {proj_conf {custom_ver ""}} {
3510  if {$custom_ver ne ""} {
3511  set ide_name_and_ver [string tolower "$custom_ver"]
3512  } elseif {[file exists $proj_conf]} {
3513  set ide_name_and_ver [string tolower [GetIDEFromConf $proj_conf]]
3514  } else {
3515  Msg Error "Configuration file $proj_conf not found."
3516  }
3517 
3518  set ide_name [lindex [regexp -all -inline {\S+} $ide_name_and_ver] 0]
3519 
3520  if {$ide_name eq "vivado" || $ide_name eq "vivado_vitis_classic" || $ide_name eq "vivado_vitis_unified" || $ide_name eq "vitis_unified"} {
3521  set command "vivado"
3522  # A space after the before_tcl_script is important
3523  set before_tcl_script " -nojournal -nolog -mode batch -notrace -source "
3524  set after_tcl_script " -tclargs "
3525  set end_marker ""
3526  } elseif {$ide_name eq "planahead"} {
3527  set command "planAhead"
3528  # A space ater the before_tcl_script is important
3529  set before_tcl_script " -nojournal -nolog -mode batch -notrace -source "
3530  set after_tcl_script " -tclargs "
3531  set end_marker ""
3532  } elseif {$ide_name eq "quartus"} {
3533  set command "quartus_sh"
3534  # A space after the before_tcl_script is important
3535  set before_tcl_script " -t "
3536  set after_tcl_script " "
3537  set end_marker ""
3538  } elseif {$ide_name eq "libero"} {
3539  #I think we need quotes for libero, not sure...
3540 
3541  set command "libero"
3542  set before_tcl_script "SCRIPT:"
3543  set after_tcl_script " SCRIPT_ARGS:\""
3544  set end_marker "\""
3545  } elseif {$ide_name eq "diamond"} {
3546  set command "diamondc"
3547  set before_tcl_script " "
3548  set after_tcl_script " "
3549  set end_marker ""
3550  } elseif {$ide_name eq "vitis_classic"} {
3551  set command "xsct"
3552  # A space after the before_tcl_script is important
3553  set before_tcl_script ""
3554  set after_tcl_script " "
3555  set end_marker ""
3556  } elseif {$ide_name eq "ghdl"} {
3557  set command "ghdl"
3558  set before_tcl_script " "
3559  set after_tcl_script " "
3560  set end_marker ""
3561  } else {
3562  Msg Error "IDE: $ide_name not known."
3563  }
3564 
3565  return [list $command $before_tcl_script $after_tcl_script $end_marker]
3566 }
3567 
3568 ## Get the IDE (Vivado,Quartus,PlanAhead,Libero) version from the conf file she-bang
3569 #
3570 # @param[in] conf_file The hog.conf file
3571 proc GetIDEFromConf {conf_file} {
3572  set f [open $conf_file "r"]
3573  set line [gets $f]
3574  close $f
3575  if {[regexp -all {^\# *(\w*) *(vitis_(?:classic|unified))? *(\d+\.\d+(?:\.\d+)?(?:\.\d+)?)?(_.*)? *$} $line dummy ide vitisflag version patch]} {
3576  if {[info exists vitisflag] && $vitisflag != ""} {
3577  set ide "${ide}_${vitisflag}"
3578  }
3579 
3580  if {[info exists version] && $version != ""} {
3581  set ver $version
3582  } else {
3583  set ver 0.0.0
3584  }
3585  # what shall we do with $patch? ignored for the time being
3586  set ret [list $ide $ver]
3587  } else {
3588  Msg CriticalWarning "The first line of hog.conf should be \#<IDE name> <version>, \
3589  where <IDE name>. is quartus, vivado, planahead, libero, diamond or ghdl, \
3590  and <version> the tool version, e.g. \#vivado 2020.2. Will assume vivado."
3591  set ret [list "vivado" "0.0.0"]
3592  }
3593 
3594  return $ret
3595 }
3596 
3597 # @brief Returns the name of the running IDE
3598 proc GetIDEName {} {
3599  if {[IsISE]} {
3600  return "ISE/PlanAhead"
3601  } elseif {[IsVivado]} {
3602  return "Vivado"
3603  } elseif {[IsQuartus]} {
3604  return "Quartus"
3605  } elseif {[IsLibero]} {
3606  return "Libero"
3607  } elseif {[IsDiamond]} {
3608  return "Diamond"
3609  } else {
3610  return ""
3611  }
3612 }
3613 
3614 ## Returns the version of the IDE (Vivado,Quartus,PlanAhead,Libero) in use
3615 #
3616 # @return the version in string format, e.g. 2020.2
3617 #
3618 proc GetIDEVersion {} {
3619  if {[IsXilinx]} {
3620  # Vivado or planAhead
3621  regexp {\d+\.\d+(\.\d+)?} [version -short] ver
3622  # This regex will cut away anything after the numbers, useful for patched version 2020.1_AR75210
3623  } elseif {[IsQuartus]} {
3624  # Quartus
3625  global quartus
3626  regexp {[\.0-9]+} $quartus(version) ver
3627  } elseif {[IsLibero]} {
3628  # Libero
3629  set ver [get_libero_version]
3630  } elseif {[IsDiamond]} {
3631  # Diamond
3632  regexp {\d+\.\d+(\.\d+)?} [sys_install version] ver
3633  } elseif {[IsVitisClassic]} {
3634  # Vitis Classic
3635  regexp {\d+\.\d+(\.\d+)?} [version] ver
3636  } elseif {[IsVitisUnified]} {
3637  # Vitis Unified
3638  set vitis_output [exec vitis --version 2>@1]
3639  regexp {[Vv]itis\s+v?(\d+\.\d+(?:\.\d+)?)} $vitis_output -> ver
3640  } else {
3641  set ver "0.0.0"
3642  }
3643  return $ver
3644 }
3645 
3646 
3647 ## @brief Returns the real file linked by a soft link
3648 #
3649 # If the provided file is not a soft link, it will give a Warning and return an empty string.
3650 # If the link is broken, will give a warning but still return the linked file
3651 #
3652 # @param[in] link_file The soft link file
3653 proc GetLinkedFile {link_file} {
3654  if {[file type $link_file] eq "link"} {
3655  if {[OS] == "windows"} {
3656  #on windows we need to use readlink because Tcl is broken
3657  lassign [ExecuteRet realpath $link_file] ret msg
3658  lassign [ExecuteRet cygpath -m $msg] ret2 msg2
3659  if {$ret == 0 && $ret2 == 0} {
3660  set real_file $msg2
3661  Msg Debug "Found link file $link_file on Windows, the linked file is: $real_file"
3662  } else {
3663  Msg CriticalWarning "[file normalize $link_file] is a soft link. \
3664  Soft link are not supported on Windows and readlink.exe or cygpath.exe did not work: readlink=$ret: $msg, cygpath=$ret2: $msg2."
3665  set real_file $link_file
3666  }
3667  } else {
3668  #on linux Tcl just works
3669  set linked_file [file link $link_file]
3670  set real_file [file normalize [file dirname $link_file]/$linked_file]
3671  }
3672 
3673  if {![file exists $real_file]} {
3674  Msg Warning "$link_file is a broken link, because the linked file: $real_file does not exist."
3675  }
3676  } else {
3677  Msg Warning "$link file is not a soft link"
3678  set real_file $link_file
3679  }
3680  return $real_file
3681 }
3682 
3683 ## @brief Gets MAX number of Threads property from property.conf file in Top/$proj_name directory.
3684 #
3685 # If property is not set returns default = 1
3686 #
3687 # @param[in] proj_dir: the top folder of the project
3688 #
3689 # @return 1 if property is not set else the value of MaxThreads
3690 #
3691 proc GetMaxThreads {proj_dir} {
3692  set maxThreads 1
3693  if {[file exists $proj_dir/hog.conf]} {
3694  set properties [ReadConf [lindex [GetConfFiles $proj_dir] 0]]
3695  if {[dict exists $properties parameters]} {
3696  set propDict [dict get $properties parameters]
3697  if {[dict exists $propDict MAX_THREADS]} {
3698  set maxThreads [dict get $propDict MAX_THREADS]
3699  }
3700  }
3701  } else {
3702  Msg Warning "File $proj_dir/hog.conf not found. Max threads will be set to default value 1"
3703  }
3704  return $maxThreads
3705 }
3706 
3707 
3708 ## @brief Get a list of all modified the files matching then pattern
3709 #
3710 # @param[in] repo_path the path of the git repository
3711 # @param[in] pattern the pattern with wildcards that files should match
3712 #
3713 # @return a list of all modified files matching the pattern
3714 #
3715 proc GetModifiedFiles {{repo_path "."} {pattern "."}} {
3716  set old_path [pwd]
3717  cd $repo_path
3718  set ret [Git "ls-files --modified $pattern"]
3719  cd $old_path
3720  return $ret
3721 }
3722 
3723 # @brief Gets the command argv list and returns a list of
3724 # options and arguments
3725 # @param[in] argv The command input arguments
3726 # @param[in] parameters The command input parameters
3727 proc GetOptions {argv parameters} {
3728  # Get Options from argv
3729  set arg_list [list]
3730  set param_list [list]
3731  set option_list [list]
3732 
3733  foreach p $parameters {
3734  lappend param_list [lindex $p 0]
3735  }
3736 
3737  set index 0
3738  while {$index < [llength $argv]} {
3739  set arg [lindex $argv $index]
3740  if {[string first - $arg] == 0} {
3741  set option [string trimleft $arg "-"]
3742  incr index
3743  lappend option_list $arg
3744  if {[lsearch -regex $param_list "$option\[.arg]?"] >= 0} {
3745  if {[lsearch -regex $param_list "$option\[.arg]"] >= 0} {
3746  lappend option_list [lindex $argv $index]
3747  incr index
3748  }
3749  }
3750  } else {
3751  lappend arg_list $arg
3752  incr index
3753  }
3754  }
3755  Msg Debug "Argv: $argv"
3756  Msg Debug "Options: $option_list"
3757  Msg Debug "Arguments: $arg_list"
3758  return [list $option_list $arg_list]
3759 }
3760 
3761 # return [list $libraries $properties $simlibraries $constraints $srcsets $simsets $consets]
3762 ## @ brief Returns a list of 7 dictionaries: libraries, properties, constraints, and filesets for sources and simulations
3763 #
3764 # The returned dictionaries are libraries, properties, simlibraries, constraints, srcsets, simsets, consets
3765 # - libraries and simlibraries have the library name as keys and a list of filenames as values
3766 # - properties has as file names as keys and a list of properties as values
3767 # - constraints is a dictionary with a single key (sources.con) and a list of constraint files as value
3768 # - srcsets is a dictionary with a fileset name as a key (e.g. sources_1) and a list of libraries as value
3769 # - simsets is a dictionary with a simset name as a key (e.g. sim_1) and a list of libraries as value
3770 # - 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)
3771 #
3772 # Files, libraries and properties are extracted from the current project
3773 #
3774 # @param[in] project_file The project file (for Libero and Diamond)
3775 # @return A list of 7 dictionaries: libraries, properties, constraints, and filesets for sources and simulations
3776 proc GetProjectFiles {{project_file ""}} {
3777  set libraries [dict create]
3778  set simlibraries [dict create]
3779  set constraints [dict create]
3780  set properties [dict create]
3781  set consets [dict create]
3782  set srcsets [dict create]
3783  set simsets [dict create]
3784 
3785  if {[IsVivado]} {
3786  set all_filesets [get_filesets]
3787  set simulator [get_property target_simulator [current_project]]
3788  set top [get_property "top" [current_fileset]]
3789  set topfile [GetTopFile]
3790  dict lappend properties $topfile "top=$top"
3791 
3792  foreach fs $all_filesets {
3793  if {$fs == "utils_1"} {
3794  # Skipping utility fileset
3795  continue
3796  }
3797 
3798  set all_files [get_files -quiet -of_objects [get_filesets $fs]]
3799  set fs_type [get_property FILESET_TYPE [get_filesets $fs]]
3800 
3801  if {$fs_type == "BlockSrcs"} {
3802  # Vivado creates for each ip a blockset... Let's redirect to sources_1
3803  set dict_fs "sources_1"
3804  } else {
3805  set dict_fs $fs
3806  }
3807  foreach f $all_files {
3808  # Ignore files that are part of the vivado/planahead project but would not be reflected
3809  # in list files (e.g. generated products from ip cores)
3810  set ignore 0
3811  # Generated files point to a parent composite file;
3812  # planahead does not have an IS_GENERATED property
3813  if {[IsInList "IS_GENERATED" [list_property [GetFile $f $fs]]]} {
3814  if {[lindex [get_property IS_GENERATED [GetFile $f $fs]] 0] != 0} {
3815  set ignore 1
3816  }
3817  }
3818 
3819  if {[get_property FILE_TYPE [GetFile $f $fs]] == "Configuration Files"} {
3820  set ignore 1
3821  }
3822 
3823 
3824  if {[IsInList "CORE_CONTAINER" [list_property [GetFile $f $fs]]]} {
3825  if {[get_property CORE_CONTAINER [GetFile $f $fs]] != ""} {
3826  if {[file extension $f] == ".xcix"} {
3827  set f [get_property CORE_CONTAINER [GetFile $f $fs]]
3828  } else {
3829  set ignore 1
3830  }
3831  }
3832  }
3833 
3834  if {[IsInList "SCOPED_TO_REF" [list_property [GetFile $f $fs]]]} {
3835  if {[get_property SCOPED_TO_REF [GetFile $f $fs]] != ""} {
3836  dict lappend properties $f "scoped_to_ref=[get_property SCOPED_TO_REF [GetFile $f $fs]]"
3837  }
3838  }
3839 
3840  if {[IsInList "SCOPED_TO_CELLS" [list_property [GetFile $f $fs]]]} {
3841  if {[get_property SCOPED_TO_CELLS [GetFile $f $fs]] != ""} {
3842  dict lappend properties $f "scoped_to_cells=[regsub " " [get_property SCOPED_TO_CELLS [GetFile $f $fs]] ","]"
3843  }
3844  }
3845 
3846  if {[IsInList "PARENT_COMPOSITE_FILE" [list_property [GetFile $f $fs]]]} {
3847  set ignore 1
3848  }
3849 
3850  # Ignore nocattrs.dat for Versal
3851  if {[file tail $f] == "nocattrs.dat"} {
3852  set ignore 1
3853  }
3854 
3855  if {!$ignore} {
3856  if {[file extension $f] != ".coe"} {
3857  set f [file normalize $f]
3858  }
3859  lappend files $f
3860  set type [get_property FILE_TYPE [GetFile $f $fs]]
3861  # Added a -quiet because some files (.v, .sv) don't have a library
3862  set lib [get_property -quiet LIBRARY [GetFile $f $fs]]
3863 
3864  # Type can be complex like VHDL 2008, in that case we want the second part to be a property
3865  Msg Debug "File $f Extension [file extension $f] Type [lindex $type 0]"
3866 
3867  if {[string equal [lindex $type 0] "VHDL"] && [llength $type] == 1} {
3868  set prop "93"
3869  } elseif {[string equal [lindex $type 0] "VHDL"] && [string equal [lindex $type 1] "2019"]} {
3870  # VHDL 2019 must be reported as an explicit property, unlike VHDL 2008
3871  # (the default, kept propertyless below) so it matches the "2019" tag
3872  # used in list files.
3873  set type "VHDL"
3874  set prop "2019"
3875  } elseif {[string equal [lindex $type 0] "Block"] && [string equal [lindex $type 1] "Designs"]} {
3876  set type "IP"
3877  set prop ""
3878  } elseif {[string equal $type "SystemVerilog"] && [file extension $f] != ".sv" && [file extension $f] != ".svp"} {
3879  set prop "SystemVerilog"
3880  } elseif {[string equal [lindex $type 0] "XDC"] && [file extension $f] != ".xdc"} {
3881  set prop "XDC"
3882  } elseif {[string equal $type "Verilog Header"] && [file extension $f] != ".vh" && [file extension $f] != ".svh"} {
3883  set prop "verilog_header"
3884  } elseif {[string equal $type "Verilog Template"] && [file extension $f] == ".v" && [file extension $f] != ".sv"} {
3885  set prop "verilog_template"
3886  } else {
3887  set type [lindex $type 0]
3888  set prop ""
3889  }
3890  #If type is "VHDL 2008" we will keep only VHDL
3891  if {![string equal $prop ""]} {
3892  dict lappend properties $f $prop
3893  }
3894  # check where the file is used and add it to prop
3895  if {[string equal $fs_type "SimulationSrcs"]} {
3896  # Simulation sources
3897  if {[string equal $type "VHDL"]} {
3898  set library "${lib}.sim"
3899  } else {
3900  set library "others.sim"
3901  }
3902 
3903  if {[IsInList $library [DictGet $simsets $dict_fs]] == 0} {
3904  dict lappend simsets $dict_fs $library
3905  }
3906 
3907  dict lappend simlibraries $library $f
3908  } elseif {[string equal $type "VHDL"]} {
3909  # VHDL files (both 2008 and 93)
3910  if {[IsInList "${lib}.src" [DictGet $srcsets $dict_fs]] == 0} {
3911  dict lappend srcsets $dict_fs "${lib}.src"
3912  }
3913  dict lappend libraries "${lib}.src" $f
3914  } elseif {[string first "IP" $type] != -1} {
3915  # IPs
3916  if {[IsInList "ips.src" [DictGet $srcsets $dict_fs]] == 0} {
3917  dict lappend srcsets $dict_fs "ips.src"
3918  }
3919  dict lappend libraries "ips.src" $f
3920  Msg Debug "Appending $f to ips.src"
3921  } elseif {[string equal $fs_type "Constrs"]} {
3922  # Constraints
3923  if {[IsInList "sources.con" [DictGet $consets $dict_fs]] == 0} {
3924  dict lappend consets $dict_fs "sources.con"
3925  }
3926  dict lappend constraints "sources.con" $f
3927  } else {
3928  # Verilog and other files
3929  if {[IsInList "others.src" [DictGet $srcsets $dict_fs]] == 0} {
3930  dict lappend srcsets $dict_fs "others.src"
3931  }
3932  dict lappend libraries "others.src" $f
3933  Msg Debug "Appending $f to others.src"
3934  }
3935 
3936  if {[lindex [get_property -quiet used_in_synthesis [GetFile $f $fs]] 0] == 0} {
3937  dict lappend properties $f "nosynth"
3938  }
3939  if {[lindex [get_property -quiet used_in_implementation [GetFile $f $fs]] 0] == 0} {
3940  dict lappend properties $f "noimpl"
3941  }
3942  if {[lindex [get_property -quiet used_in_simulation [GetFile $f $fs]] 0] == 0} {
3943  dict lappend properties $f "nosim"
3944  }
3945  if {[lindex [get_property -quiet IS_MANAGED [GetFile $f $fs]] 0] == 0 && [file extension $f] != ".xcix"} {
3946  dict lappend properties $f "locked"
3947  }
3948  }
3949  }
3950  }
3951 
3952  dict lappend properties "Simulator" [get_property target_simulator [current_project]]
3953  } elseif {[IsLibero] || [IsSynplify]} {
3954  # Open the project file
3955  set file [open $project_file r]
3956  set in_file_manager 0
3957  set top ""
3958  while {[gets $file line] >= 0} {
3959  # Detect the ActiveRoot (Top) module
3960  if {[regexp {^KEY ActiveRoot \"([^\"]+)\"} $line -> value]} {
3961  set top [string range $value 0 [expr {[string first "::" $value] - 1}]]
3962  }
3963 
3964  # Detect the start of the FileManager section
3965  if {[regexp {^LIST FileManager} $line]} {
3966  set in_file_manager 1
3967  continue
3968  }
3969 
3970  # Detect the end of the FileManager section
3971  if {$in_file_manager && [regexp {^ENDLIST} $line]} {
3972  break
3973  }
3974 
3975  # Extract file paths from the VALUE entries
3976  if {$in_file_manager && [regexp {^VALUE \"([^\"]+)} $line -> value]} {
3977  # lappend source_files [remove_after_comma $filepath]
3978  # set file_path ""
3979  lassign [split $value ,] file_path file_type
3980  # Extract file properties
3981  set parent_file ""
3982  set library "others"
3983  while {[gets $file line] >= 0} {
3984  if {$line == "ENDFILE"} {
3985  break
3986  }
3987  regexp {^LIBRARY=\"([^\"]+)} $line -> library
3988  regexp {^PARENT=\"([^\"]+)} $line -> parent_file
3989  }
3990  Msg Debug "Found file ${file_path} in project.."
3991  if {$parent_file == ""} {
3992  if {$file_type == "hdl"} {
3993  # VHDL files (both 2008 and 93)
3994  if {[IsInList "${library}.src" [DictGet $srcsets "sources_1"]] == 0} {
3995  dict lappend srcsets "sources_1" "${library}.src"
3996  }
3997  dict lappend libraries "${library}.src" $file_path
3998  # Check if file is top_module in project
3999  Msg Debug "File $file_path module [GetModuleName $file_path]"
4000 
4001  if {[GetModuleName $file_path] == [string tolower $top] && $top != ""} {
4002  Msg Debug "Found top module $top in $file_path"
4003  dict lappend properties $file_path "top=$top"
4004  }
4005  } elseif {$file_type == "tb_hdl"} {
4006  if {[IsInList "${library}.sim" [DictGet $simsets "sim_1"]] == 0} {
4007  dict lappend simsets "sim_1" "${library}.sim"
4008  }
4009  dict lappend simlibraries "${library}.sim" $file_path
4010  } elseif {$file_type == "io_pdc" || $file_type == "sdc"} {
4011  if {[IsInList "sources.con" [DictGet $consets "constrs_1"]] == 0} {
4012  dict lappend consets "constrs_1" "sources.con"
4013  }
4014  dict lappend constraints "sources.con" $file_path
4015  }
4016  }
4017  }
4018  }
4019  } elseif {[IsDiamond]} {
4020  # Open the Diamond XML project file content
4021  set fileData [read [open $project_file]]
4022 
4023  set project_path [file dirname $project_file]
4024 
4025  # Remove XML declaration
4026  regsub {<\?xml.*\?>} $fileData "" fileData
4027 
4028  # Extract the Implementation block
4029  regexp {<Implementation.*?>(.*)</Implementation>} $fileData -> implementationContent
4030 
4031  # Extract each Source block one by one
4032  set sources {}
4033  set sourceRegex {<Source name="([^"]*?)" type="([^"]*?)" type_short="([^"]*?)".*?>(.*?)</Source>}
4034 
4035  set optionsRegex {<Options(.*?)\/>}
4036  regexp $optionsRegex $implementationContent -> prj_options
4037  foreach option $prj_options {
4038  if {[regexp {^top=\"([^\"]+)\"} $option match result]} {
4039  set top $result
4040  }
4041  }
4042 
4043  while {[regexp $sourceRegex $implementationContent match name type type_short optionsContent]} {
4044  Msg Debug "Found file ${name} in project..."
4045  set file_path [file normalize $project_path/$name]
4046  # Extract the Options attributes
4047  set optionsRegex {<Options(.*?)\/>}
4048  regexp $optionsRegex $optionsContent -> options
4049  set library "others"
4050  set isSV 0
4051  foreach option $options {
4052  if {[string first "System Verilog" $option]} {
4053  set isSV 1
4054  }
4055  if {[regexp {^lib=\"([^\"]+)\"} $option match1 result]} {
4056  set library $result
4057  }
4058  }
4059  set ext ".src"
4060  if {[regexp {syn_sim="([^"]*?)"} $match match_sim simonly]} {
4061  set ext ".sim"
4062  }
4063 
4064  # Append VHDL files
4065  if {$type_short == "VHDL" || $type_short == "Verilog" || $type_short == "IPX"} {
4066  if {$ext == ".src"} {
4067  if {[IsInList "${library}${ext}" [DictGet $srcsets "sources_1"]] == 0} {
4068  dict lappend srcsets "sources_1" "${library}${ext}"
4069  }
4070  dict lappend libraries "${library}${ext}" $file_path
4071  } elseif {$ext == ".sim"} {
4072  if {[IsInList "${library}.sim" [DictGet $simsets "sim_1"]] == 0} {
4073  dict lappend simsets "sim_1" "${library}.sim"
4074  }
4075  dict lappend simlibraries "${library}.sim" $file_path
4076  }
4077  # Check if file is top_module in project
4078  Msg Debug "File $file_path module [GetModuleName $file_path]"
4079 
4080  if {[GetModuleName $file_path] == $top && $top != ""} {
4081  Msg Debug "Found top module $top in $file_path"
4082  dict lappend properties $file_path "top=$top"
4083  }
4084  } elseif {$type_short == "SDC"} {
4085  if {[IsInList "sources.con" [DictGet $consets "constrs_1"]] == 0} {
4086  dict lappend consets "constrs_1" "sources.con"
4087  }
4088  dict lappend constraints "sources.con" $file_path
4089  }
4090 
4091  # Remove the processed Source block from the implementation content
4092  regsub -- $match $implementationContent "" implementationContent
4093  }
4094  }
4095  return [list $libraries $properties $simlibraries $constraints $srcsets $simsets $consets]
4096 }
4097 
4098 #"
4099 ## Get the Project flavour
4100 #
4101 # @param[in] proj_name The project name
4102 proc GetProjectFlavour {proj_name} {
4103  # Calculating flavour if any
4104  set flavour [string map {. ""} [file extension $proj_name]]
4105  if {$flavour != ""} {
4106  if {[string is integer $flavour]} {
4107  Msg Info "Project $proj_name has flavour = $flavour, the generic variable FLAVOUR will be set to $flavour"
4108  } else {
4109  Msg Warning "Project name has a unexpected non numeric extension, flavour will be set to -1"
4110  set flavour -1
4111  }
4112  } else {
4113  set flavour -1
4114  }
4115  return $flavour
4116 }
4117 
4118 ## Get the project version
4119 #
4120 # @param[in] proj_dir: The top folder of the project of which all the version must be calculated
4121 # @param[in] repo_path: The top folder of the repository
4122 # @param[in] ext_path: path for external libraries
4123 # @param[in] sim: if enabled, check the version also for the simulation files
4124 #
4125 # @return returns the project version
4126 proc GetProjectVersion {proj_dir repo_path {ext_path ""} {sim 0}} {
4127  if {![file exists $proj_dir]} {
4128  Msg CriticalWarning "$proj_dir not found"
4129  return -1
4130  }
4131  set old_dir [pwd]
4132  cd $proj_dir
4133 
4134  #The latest version the repository
4135  lassign [GitRet {describe --tags --abbrev=0 --match "v*"}] ret result
4136  if {$ret != 0} {
4137  Msg CriticalWarning "No Hog versioning tags (v*) in repo"
4138  return -1
4139  }
4140 
4141  set v_last [ExtractVersionFromTag $result]
4142  lassign [GetRepoVersions $proj_dir $repo_path $ext_path $sim] sha ver
4143  if {$sha == 0} {
4144  Msg Warning "Repository is not clean"
4145  cd $old_dir
4146  return -1
4147  }
4148 
4149  #The project version
4150  set v_proj [ExtractVersionFromTag v[HexVersionToString $ver]]
4151  set comp [CompareVersions $v_proj $v_last]
4152  Msg Debug "Project version $v_proj, latest tag $v_last"
4153  if {$comp == 1} {
4154  Msg Debug "The specified project was modified since official version."
4155  set ret 0
4156  } else {
4157  set ret v[HexVersionToString $ver]
4158  }
4159 
4160  if {$comp == 0} {
4161  Msg Info "The specified project was modified in the latest official version $ret"
4162  } elseif {$comp == -1} {
4163  Msg Info "The specified project was modified in a past official version $ret"
4164  }
4165 
4166  cd $old_dir
4167  return $ret
4168 }
4169 
4170 ## Get the versions for all libraries, submodules, etc. for a given project
4171 #
4172 # @param[in] proj_dir: The project directory containing the conf file or the the tcl file
4173 # @param[in] repo_path: top path of the repository
4174 # @param[in] ext_path: path for external libraries
4175 # @param[in] sim: if enabled, check the version also for the simulation files
4176 #
4177 # @return a list containing all the versions: global, top (hog.conf, pre and post tcl scripts, etc.), constraints,
4178 # libraries, submodules, external, ipbus xml, user ip repos, cheby (hash and ver)
4179 proc GetRepoVersions {proj_dir repo_path {ext_path ""} {sim 0}} {
4180  global NOT_CLEAN_WARNED
4181  if {[catch {package require cmdline} ERROR]} {
4182  puts "$ERROR\n If you are running this script on tclsh, you can fix this by installing 'tcllib'"
4183  return 1
4184  }
4185 
4186  set old_path [pwd]
4187  set conf_files [GetConfFiles $proj_dir]
4188 
4189  # This will be the list of all the SHAs of this project, the most recent will be picked up as GLOBAL SHA
4190  set SHAs ""
4191  set versions ""
4192 
4193  # Hog submodule
4194  cd $repo_path
4195 
4196  # Append the SHA in which Hog submodule was changed, not the submodule SHA
4197  lappend SHAs [GetSHA {Hog}]
4198  lappend versions [GetVerFromSHA $SHAs $repo_path]
4199 
4200  cd "$repo_path/Hog"
4201  if {[Git {status --untracked-files=no --porcelain}] eq ""} {
4202  Msg Debug "Hog submodule [pwd] clean."
4203  lassign [GetVer ./] hog_ver hog_hash
4204  } else {
4205  Msg CriticalWarning "Hog submodule [pwd] not clean, commit hash will be set to 0."
4206  set hog_hash "0000000"
4207  set hog_ver "00000000"
4208  }
4209 
4210  cd $repo_path
4211  # Collect all project-relevant files; the clean check will be deferred until all files are known
4212  # set project_files $conf_files
4213  foreach conf_file $conf_files {
4214  if {[string match "$repo_path/*" $conf_file]} {
4215  set conf_file [string replace $conf_file 0 [string length $repo_path]]
4216  }
4217  lappend project_files $conf_file
4218  }
4219 
4220  lappend project_files Hog
4221 
4222  # Top project directory
4223  lassign [GetVer [join $conf_files]] top_ver top_hash
4224  lappend SHAs $top_hash
4225  lappend versions $top_ver
4226 
4227  # Read list files
4228  set libs ""
4229  set vers ""
4230  set hashes ""
4231  # Specify sha_mode 1 for GetHogFiles to get all the files, including the list-files themselves
4232  lassign [GetHogFiles -list_files "*.src" -sha_mode "$proj_dir/list/" $repo_path] src_files dummy
4233  dict for {f files} $src_files {
4234  # library names have a .src extension in values returned by GetHogFiles
4235  set name [file rootname [file tail $f]]
4236  if {[file ext $f] == ".oth"} {
4237  set name "OTHERS"
4238  }
4239  lassign [GetVer $files] ver hash
4240  # Msg Info "Found source list file $f, version: $ver commit SHA: $hash"
4241  lappend libs $name
4242  lappend versions $ver
4243  lappend vers $ver
4244  lappend hashes $hash
4245  lappend SHAs $hash
4246  set relative_files ""
4247  foreach fil $files {
4248  if {[string match "$repo_path/*" $fil]} {
4249  set fil [string replace $fil 0 [string length $repo_path]]
4250  }
4251  lappend relative_files $fil
4252  }
4253 
4254  lappend project_files $f {*}$relative_files
4255  }
4256 
4257  # Read constraint list files
4258  set cons_hashes ""
4259  # Specify sha_mode 1 for GetHogFiles to get all the files, including the list-files themselves
4260  lassign [GetHogFiles -list_files "*.con" -sha_mode "$proj_dir/list/" $repo_path] cons_files dummy
4261  dict for {f files} $cons_files {
4262  #library names have a .con extension in values returned by GetHogFiles
4263  set name [file rootname [file tail $f]]
4264  lassign [GetVer $files] ver hash
4265  #Msg Info "Found constraint list file $f, version: $ver commit SHA: $hash"
4266  if {$hash eq ""} {
4267  Msg CriticalWarning "Constraints file $f not found in Git."
4268  }
4269  lappend cons_hashes $hash
4270  lappend SHAs $hash
4271  lappend versions $ver
4272  set relative_files ""
4273  foreach fil $files {
4274  if {[string match "$repo_path/*" $fil]} {
4275  set fil [string replace $fil 0 [string length $repo_path]]
4276  }
4277  lappend relative_files $fil
4278  }
4279  lappend project_files $f {*}$relative_files
4280  }
4281 
4282  # Read simulation list files
4283  if {$sim == 1} {
4284  set sim_hashes ""
4285  # Specify sha_mode 1 for GetHogFiles to get all the files, including the list-files themselves
4286  lassign [GetHogFiles -list_files "*.sim" -sha_mode "$proj_dir/list/" $repo_path] sim_files dummy
4287  dict for {f files} $sim_files {
4288  #library names have a .sim extension in values returned by GetHogFiles
4289  set name [file rootname [file tail $f]]
4290  lassign [GetVer $files] ver hash
4291  #Msg Info "Found simulation list file $f, version: $ver commit SHA: $hash"
4292  lappend sim_hashes $hash
4293  lappend SHAs $hash
4294  lappend versions $ver
4295  set relative_files ""
4296  foreach fil $files {
4297  if {[string match "$repo_path/*" $fil]} {
4298  set fil [string replace $fil 0 [string length $repo_path]]
4299  }
4300  lappend relative_files $fil
4301  }
4302  lappend project_files $f {*}$relative_files
4303  }
4304  }
4305 
4306 
4307  #Of all the constraints we get the most recent
4308  if {[IsInList {} $cons_hashes]} {
4309  #" Fake comment for Visual Code Studio
4310  Msg CriticalWarning "No hashes found for constraints files (not in git)"
4311  set cons_hash ""
4312  } else {
4313  set cons_hash [string tolower [Git "log --format=%h -1 $cons_hashes"]]
4314  }
4315  set cons_ver [GetVerFromSHA $cons_hash $repo_path]
4316  #Msg Info "Among all the constraint list files, if more than one, the most recent version was chosen: $cons_ver commit SHA: $cons_hash"
4317 
4318  # Read external library files
4319  set ext_hashes ""
4320  set ext_files [glob -nocomplain "$proj_dir/list/*.ext"]
4321  set ext_names ""
4322 
4323  foreach f $ext_files {
4324  set name [file rootname [file tail $f]]
4325  set hash [GetSHA $f]
4326  #Msg Info "Found source file $f, commit SHA: $hash"
4327  lappend ext_names $name
4328  lappend ext_hashes $hash
4329  lappend SHAs $hash
4330  set ext_ver [GetVerFromSHA $hash $repo_path]
4331  lappend versions $ext_ver
4332  lappend project_files $f
4333 
4334  set fp [open $f r]
4335  set file_data [read $fp]
4336  close $fp
4337  set data [split $file_data "\n"]
4338  #Msg Info "Checking checksums of external library files in $f"
4339  foreach line $data {
4340  if {![regexp {^ *$} $line] & ![regexp {^ *\#} $line]} {
4341  #Exclude empty lines and comments
4342  set file_and_prop [regexp -all -inline {\S+} $line]
4343  set hdlfile [lindex $file_and_prop 0]
4344  set hdlfile $ext_path/$hdlfile
4345  if {[file exists $hdlfile]} {
4346  set hash [lindex $file_and_prop 1]
4347  set current_hash [Md5Sum $hdlfile]
4348  if {[string first $hash $current_hash] == -1} {
4349  Msg CriticalWarning "File $hdlfile has a wrong hash. Current checksum: $current_hash, expected: $hash"
4350  }
4351  }
4352  }
4353  }
4354  }
4355 
4356  # Ipbus XML
4357  if {[llength [glob -nocomplain $proj_dir/list/*.ipb]] > 0} {
4358  #Msg Info "Found IPbus XML list file, evaluating version and SHA of listed files..."
4359  lassign [GetHogFiles -list_files "*.ipb" -sha_mode "$proj_dir/list/" $repo_path] xml_files dummy
4360  set xml_source_files [dict get $xml_files "xml.ipb"]
4361  lassign [GetVer $xml_source_files] xml_ver xml_hash
4362  lappend SHAs $xml_hash
4363  lappend versions $xml_ver
4364  set relative_files ""
4365  foreach fil $xml_source_files {
4366  if {[string match "$repo_path/*" $fil]} {
4367  set fil [string replace $fil 0 [string length $repo_path]]
4368  }
4369  lappend relative_files $fil
4370  }
4371  lappend project_files {*}[glob $proj_dir/list/*.ipb] {*}$relative_files
4372  #Msg Info "Found IPbus XML SHA: $xml_hash and version: $xml_ver."
4373  } else {
4374  Msg Debug "This project does not use IPbus XMLs"
4375  set xml_ver ""
4376  set xml_hash ""
4377  }
4378 
4379  # Cheby register-map sources
4380  if {[llength [glob -nocomplain $proj_dir/list/*.chb]] > 0} {
4381  lassign [GetHogFiles -list_files "*.chb" -sha_mode "$proj_dir/list/" $repo_path] cheby_files dummy
4382  if {[dict exists $cheby_files "cheby.chb"]} {
4383  set cheby_source_files [dict get $cheby_files "cheby.chb"]
4384  lassign [GetVer $cheby_source_files] cheby_ver cheby_hash
4385  lappend SHAs $cheby_hash
4386  lappend versions $cheby_ver
4387  lappend project_files {*}[glob $proj_dir/list/*.chb] {*}$cheby_source_files
4388  } else {
4389  set cheby_ver ""
4390  set cheby_hash ""
4391  }
4392  } else {
4393  Msg Info "This project does not use Cheby register maps"
4394  set cheby_ver ""
4395  set cheby_hash ""
4396  }
4397 
4398  set user_ip_repos ""
4399  set user_ip_repo_hashes ""
4400  set user_ip_repo_vers ""
4401  # User IP Repository (Vivado only, hog.conf only)
4402  if {[file exists [lindex $conf_files 0]]} {
4403  set PROPERTIES [ReadConf [lindex $conf_files 0]]
4404  if {[dict exists $PROPERTIES main]} {
4405  set main [dict get $PROPERTIES main]
4406  dict for {p v} $main {
4407  if {[string tolower $p] == "ip_repo_paths"} {
4408  foreach repo $v {
4409  if {[file isdirectory "$repo_path/$repo"]} {
4410  set repo_file_list [glob -nocomplain "$repo_path/$repo/*"]
4411  if {[llength $repo_file_list] == 0} {
4412  Msg Warning "IP_REPO_PATHS property set to $repo in hog.conf but directory is empty."
4413  } else {
4414  lappend user_ip_repos "$repo_path/$repo"
4415  }
4416  }
4417  }
4418  }
4419  }
4420  }
4421 
4422  # For each defined IP repository get hash and version if directory exists and not empty
4423  foreach repo $user_ip_repos {
4424  if {[file isdirectory $repo]} {
4425  set repo_file_list [glob -nocomplain "$repo/*"]
4426  if {[llength $repo_file_list] != 0} {
4427  lassign [GetVer $repo] ver sha
4428  lappend user_ip_repo_hashes $sha
4429  lappend user_ip_repo_vers $ver
4430  lappend versions $ver
4431  lappend project_files $repo
4432  } else {
4433  Msg Warning "IP_REPO_PATHS property set to $repo in hog.conf but directory is empty."
4434  }
4435  } else {
4436  Msg Warning "IP_REPO_PATHS property set to $repo in hog.conf but directory does not exist."
4437  }
4438  }
4439  }
4440 
4441  cd $repo_path
4442  # Check cleanliness only for the files that belong to this project
4443  set dirty ""
4444  # if {[OS] == "windows"} {
4445  # set chunk_size 10
4446  # for {set i 0} {$i < [llength $project_files]} {incr i $chunk_size} {
4447  # set chunk [lrange $project_files $i [expr {$i + $chunk_size - 1}]]
4448  # append dirty [Git "status --untracked-files=no --porcelain" $chunk]
4449  # }
4450  # } else {
4451  append dirty [Git "status --untracked-files=no --porcelain" $project_files]
4452  # }
4453 
4454  if {$dirty eq ""} {
4455  Msg Debug "Project-relevant files are clean."
4456  set clean 1
4457  } else {
4458  if {![info exists NOT_CLEAN_WARNED]} {
4459  Msg CriticalWarning "Project-relevant files not clean, commit hash and version will be set to 0."
4460  set NOT_CLEAN_WARNED 1
4461  }
4462  set clean 0
4463  }
4464 
4465  #The global SHA and ver is the most recent among everything
4466  if {$clean == 1} {
4467  set found 0
4468  while {$found == 0} {
4469  set global_commit [Git "log --topo-order --format=%h -1 --abbrev=7 $SHAs"]
4470  foreach sha $SHAs {
4471  set found 1
4472  if {![IsCommitAncestor $sha $global_commit]} {
4473  set common_child [FindCommonGitChild $global_commit $sha]
4474  if {$common_child == 0} {
4475  Msg CriticalWarning "The commit $sha is not an ancestor of the global commit $global_commit, which is OK. \
4476  But $sha and $global_commit do not have any common child, which is NOT OK. \
4477  This is probably do to a REBASE that is forbidden in Hog methodology as it changes git history. \
4478  Hog cannot guarantee the accuracy of the SHAs. \
4479  A way to fix this is to make a commit that touches all the projects in the repositories (e.g. change the Hog version), \
4480  but please do not rebase in the official branches in the future."
4481  } else {
4482  Msg Info "The commit $sha is not an ancestor of the global commit $global_commit, adding the first common child $common_child instead..."
4483  lappend SHAs $common_child
4484  }
4485  set found 0
4486 
4487  break
4488  }
4489  }
4490  }
4491  set global_version [FindNewestVersion $versions]
4492  } else {
4493  set global_commit "0000000"
4494  set global_version "00000000"
4495  }
4496 
4497  cd $old_path
4498 
4499  set top_hash [format %+07s $top_hash]
4500  set cons_hash [format %+07s $cons_hash]
4501  return [list $global_commit $global_version \
4502  $hog_hash $hog_ver $top_hash $top_ver \
4503  $libs $hashes $vers $cons_ver $cons_hash \
4504  $ext_names $ext_hashes $xml_hash $xml_ver \
4505  $user_ip_repos $user_ip_repo_hashes $user_ip_repo_vers \
4506  $cheby_hash $cheby_ver]
4507 }
4508 
4509 ## @brief Get git SHA of a subset of list file
4510 #
4511 # @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
4512 #
4513 # @return the value of the desired SHA
4514 #
4515 proc GetSHA {{path ""}} {
4516  set old_path [pwd]
4517  if {$path == ""} {
4518  lassign [GitRet {log --format=%h --abbrev=7 -1}] status result
4519  if {$status == 0} {
4520  return [string tolower $result]
4521  } else {
4522  Msg Error "Something went wrong while finding the latest SHA. Does the repository have a commit?"
4523  exit 1
4524  }
4525  }
4526 
4527  # Get repository top level
4528  set repo_path [lindex [Git {rev-parse --show-toplevel}] 0]
4529  cd $repo_path
4530  set paths {}
4531  # Retrieve the list of submodules in the repository
4532  foreach f $path {
4533  set file_in_module 0
4534  if {[file exists .gitmodules]} {
4535  lassign [GitRet "config --file .gitmodules --get-regexp path"] status result
4536  if {$status == 0} {
4537  set submodules [split $result "\n"]
4538  } else {
4539  set submodules ""
4540  Msg Warning "Something went wrong while trying to find submodules: $result"
4541  }
4542 
4543  foreach mod $submodules {
4544  set module [lindex $mod 1]
4545  if {[string first "$repo_path/$module" $f] == 0} {
4546  # File is in a submodule. Append
4547  set file_in_module 1
4548  lappend paths "$module"
4549  break
4550  }
4551  }
4552  }
4553  if {$file_in_module == 0} {
4554  #File is not in a submodule
4555  lappend paths $f
4556  }
4557  }
4558 
4559  lassign [GitRet {log --format=%h --abbrev=7 -1} $paths] status result
4560  if {$status == 0} {
4561  return [string tolower $result]
4562  } else {
4563  Msg Error "Something went wrong while finding the latest SHA. Does the repository have a commit?"
4564  exit 1
4565  }
4566  cd $old_path
4567  return [string tolower $result]
4568 }
4569 
4570 ## @brief Returns the list of Simulators supported by Vivado
4571 proc GetSimulators {} {
4572  set SIMULATORS [list "modelsim" "questa" "riviera" "activehdl" "ies" "vcs"]
4573  return $SIMULATORS
4574 }
4575 
4576 ## @brief Return the path to the active top file
4577 proc GetTopFile {} {
4578  if {[IsVivado]} {
4579  set compile_order_prop [get_property source_mgmt_mode [current_project]]
4580  if {$compile_order_prop ne "All"} {
4581  Msg CriticalWarning "Compile order is not set to automatic, setting it now..."
4582  set_property source_mgmt_mode All [current_project]
4583  update_compile_order -fileset sources_1
4584  }
4585  return [lindex [get_files -quiet -compile_order sources -used_in synthesis -filter {FILE_TYPE =~ "VHDL*" || FILE_TYPE =~ "*Verilog*" }] end]
4586  } elseif {[IsISE]} {
4587  debug::design_graph_mgr -create [current_fileset]
4588  debug::design_graph -add_fileset [current_fileset]
4589  debug::design_graph -update_all
4590  return [lindex [debug::design_graph -get_compile_order] end]
4591  } else {
4592  Msg Error "GetTopFile not yet implemented for this IDE"
4593  }
4594 }
4595 
4596 ## @brief Return the name of the active top module
4597 proc GetTopModule {} {
4598  if {[IsXilinx]} {
4599  return [get_property top [current_fileset]]
4600  } else {
4601  Msg Error "GetTopModule not yet implemented for this IDE"
4602  }
4603 }
4604 
4605 ## @brief Get git version and commit hash of a subset of files
4606 #
4607 # @param[in] path list file or path containing the subset of files whose latest commit hash will be returned
4608 #
4609 # @return a list: the git SHA, the version in hex format
4610 #
4611 proc GetVer {path {force_develop 0}} {
4612  set SHA [GetSHA $path]
4613  #oldest tag containing SHA
4614  if {$SHA eq ""} {
4615  Msg CriticalWarning "Empty SHA found for ${path}. Commit to Git to resolve this warning."
4616  }
4617  set old_path [pwd]
4618  set p [lindex $path 0]
4619  if {[file isdirectory $p]} {
4620  cd $p
4621  } else {
4622  cd [file dirname $p]
4623  }
4624  set repo_path [Git {rev-parse --show-toplevel}]
4625  cd $old_path
4626 
4627  return [list [GetVerFromSHA $SHA $repo_path $force_develop] $SHA]
4628 }
4629 
4630 ## @brief Get git version and commit hash of a specific commit give the SHA
4631 #
4632 # @param[in] SHA the git SHA of the commit
4633 # @param[in] repo_path the path of the repository, this is used to open the Top/repo.conf file
4634 # @param[in] force_develop Force a tag for the develop branch (increase m)
4635 #
4636 # @return a list: the git SHA, the version in hex format
4637 #
4638 proc GetVerFromSHA {SHA repo_path {force_develop 0}} {
4639  global NO_HOG_VER_TAG_WARNED
4640  if {$SHA eq ""} {
4641  Msg CriticalWarning "Empty SHA found"
4642  set ver "v0.0.0"
4643  } else {
4644  lassign [GitRet "tag --sort=creatordate --contain $SHA -l v*.*.* -l b*v*.*.*"] status result
4645 
4646  if {$status == 0} {
4647  if {[regexp {^ *$} $result]} {
4648  # We do not want the most recent tag, we want the biggest value
4649  lassign [GitRet "log --oneline --pretty=\"%d\""] status2 tag_list
4650  #Msg Status "List of all tags including $SHA: $tag_list."
4651  #cleanup the list and get only the tags
4652  set pattern {tag: v\d+\.\d+\.\d+}
4653  set real_tag_list {}
4654  foreach x $tag_list {
4655  set x_untrimmed [regexp -all -inline $pattern $x]
4656  regsub "tag: " $x_untrimmed "" x_trimmed
4657  set tt [lindex $x_trimmed 0]
4658  if {![string equal $tt ""]} {
4659  lappend real_tag_list $tt
4660  #puts "<$tt>"
4661  }
4662  }
4663  Msg Debug "Cleaned up list: $real_tag_list."
4664  # Sort the tags in version order
4665  set sorted_tags [lsort -decreasing -command CompareVersions $real_tag_list]
4666 
4667  Msg Debug "Sorted Tag list: $sorted_tags"
4668  # Select the newest tag in terms of number, not time
4669  set tag [lindex $sorted_tags 0]
4670 
4671  # Msg Debug "Chosen Tag $tag"
4672  set pattern {v\d+\.\d+\.\d+}
4673  if {![regexp $pattern $tag]} {
4674  if {![info exists NO_HOG_VER_TAG_WARNED]} {
4675  Msg CriticalWarning "No Hog version tags found in this repository."
4676  set NO_HOG_VER_TAG_WARNED 1
4677  }
4678  set ver v0.0.0
4679  } else {
4680  lassign [ExtractVersionFromTag $tag] M m p mr
4681  # Open repo.conf and check prefixes
4682  set repo_conf $repo_path/Top/repo.conf
4683 
4684  # Check if the develop/master scheme is used and where is the merge directed to
4685  # Default values
4686  set hotfix_prefix "hotfix/"
4687  set minor_prefix "minor_version/"
4688  set major_prefix "major_version/"
4689  set is_hotfix 0
4690  set enable_develop_branch $force_develop
4691 
4692  set branch_name [Git {rev-parse --abbrev-ref HEAD}]
4693 
4694  if {[file exists $repo_conf]} {
4695  set PROPERTIES [ReadConf $repo_conf]
4696  # [main] section
4697  if {[dict exists $PROPERTIES main]} {
4698  set mainDict [dict get $PROPERTIES main]
4699 
4700  # ENABLE_DEVELOP_ BRANCH property
4701  if {[dict exists $mainDict ENABLE_DEVELOP_BRANCH]} {
4702  set enable_develop_branch [dict get $mainDict ENABLE_DEVELOP_BRANCH]
4703  }
4704  # More properties in [main] here ...
4705  }
4706 
4707  # [prefixes] section
4708  if {[dict exists $PROPERTIES prefixes]} {
4709  set prefixDict [dict get $PROPERTIES prefixes]
4710 
4711  if {[dict exists $prefixDict HOTFIX]} {
4712  set hotfix_prefix [dict get $prefixDict HOTFIX]
4713  }
4714  if {[dict exists $prefixDict MINOR_VERSION]} {
4715  set minor_prefix [dict get $prefixDict MINOR_VERSION]
4716  }
4717  if {[dict exists $prefixDict MAJOR_VERSION]} {
4718  set major_prefix [dict get $prefixDict MAJOR_VERSION]
4719  }
4720  # More properties in [prefixes] here ...
4721  }
4722  }
4723 
4724  if {[string match "HEAD" $branch_name]} {
4725  Msg Debug "Detached HEAD detected - attempting to find branch name"
4726 
4727  # if the branch_name is HEAD (not a legal branch name btw)
4728  # then the branch has been checked out in a detached head state
4729  # this is a fallback condition to enable finding the branch name that the commit is linked too
4730  set log_refs [Git {show -s --pretty=%D HEAD}]
4731  set branch_list [split $log_refs ","]
4732  Msg Debug "list of possible branch refs $log_refs"
4733 
4734  # iterate over all possible refs and match against all prefix types
4735  # set branch name as matched prefix if and only if one match is found
4736 
4737  set match_count 0
4738  set match_prefixes [list $hotfix_prefix $minor_prefix $major_prefix]
4739  set prev_branch_name $branch_name
4740 
4741  foreach br $branch_list {
4742  foreach pr $match_prefixes {
4743  if {[string match "$pr*" [string trim $br]]} {
4744  set branch_name [string trim $br]
4745  incr match_count 1
4746  }
4747  }
4748  }
4749 
4750  if {!$match_count == 1} {
4751  set branch_name $prev_branch_name
4752  Msg Debug "Branch name not found. Using $branch_name"
4753  } else {
4754  Msg Debug "Branch name found: $branch_name"
4755  }
4756  }
4757 
4758  if {$enable_develop_branch == 1} {
4759  if {[string match "$hotfix_prefix*" $branch_name]} {
4760  set is_hotfix 1
4761  }
4762  }
4763 
4764  if {[string match "$major_prefix*" $branch_name]} {
4765  # If major prefix is used, we increase M regardless of anything else
4766  set version_level major
4767  } elseif {[string match "$minor_prefix*" $branch_name] || ($enable_develop_branch == 1 && $is_hotfix == 0)} {
4768  # 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
4769  set version_level minor
4770  } else {
4771  # 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
4772  set version_level patch
4773  }
4774 
4775  if {$M == -1} {
4776  Msg CriticalWarning "Tag $tag does not contain a Hog compatible version in this repository."
4777  exit
4778  #set ver v0.0.0
4779  } elseif {$mr == 0} {
4780  switch $version_level {
4781  minor {
4782  incr m
4783  set p 0
4784  }
4785  major {
4786  incr M
4787  set m 0
4788  set p 0
4789  }
4790  default {
4791  incr p
4792  }
4793  }
4794  } else {
4795  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."
4796  }
4797  set ver v$M.$m.$p
4798  }
4799  } else {
4800  #The tag in $result contains the current SHA
4801  set vers [split $result "\n"]
4802  set ver [lindex $vers 0]
4803  foreach v $vers {
4804  if {[regexp {^v.*$} $v]} {
4805  set un_ver $ver
4806  set ver $v
4807  break
4808  }
4809  }
4810  }
4811  } else {
4812  Msg CriticalWarning "Error while trying to find tag for $SHA"
4813  set ver "v0.0.0"
4814  }
4815  }
4816  lassign [ExtractVersionFromTag $ver] M m c mr
4817 
4818  if {$mr > -1} {
4819  # Candidate tab
4820  set M [format %02X $M]
4821  set m [format %02X $m]
4822  set c [format %04X $c]
4823  } elseif {$M > -1} {
4824  # official tag
4825  set M [format %02X $M]
4826  set m [format %02X $m]
4827  set c [format %04X $c]
4828  } else {
4829  Msg Warning "Tag does not contain a properly formatted version: $ver"
4830  set M [format %02X 0]
4831  set m [format %02X 0]
4832  set c [format %04X 0]
4833  }
4834 
4835  return $M$m$c
4836 }
4837 
4838 ## @brief Handle git commands
4839 #
4840 #
4841 # @param[in] command: the git command to be run including refs (branch, tags, sha, etc.), except files.
4842 # @param[in] files: files given to git as argument. They will always be separated with -- to avoid weird accidents
4843 #
4844 # @returns the output of the git command
4845 proc Git {command {files ""}} {
4846  lassign [GitRet $command $files] ret result
4847  if {$ret != 0} {
4848  Msg Error "Code $ret returned by git running: $command -- $files"
4849  }
4850 
4851  return $result
4852 }
4853 
4854 
4855 # @brief Get the name of the module in a HDL file. If module is not found, it returns an empty string
4856 #
4857 # @param[in] filename The name of the hdl file
4858 
4859 proc GetModuleName {filename} {
4860  # Check if the file exists
4861  if {![file exists $filename]} {
4862  Msg CriticalWarning "Error: File $filename does not exist."
4863  return ""
4864  }
4865 
4866  # Open the file for reading
4867  set fileId [open $filename r]
4868 
4869  # Read the content of the file
4870  set file_content [read $fileId]
4871 
4872  # Close the file
4873  close $fileId
4874 
4875 
4876  if {[file extension $filename] == ".vhd" || [file extension $filename] == ".vhdl"} {
4877  # Convert the file content to lowercase for case-insensitive matching
4878  set file_content [string tolower $file_content]
4879  # Regular expression to match the entity name after the 'entity' keyword
4880  set pattern {(?m)^\s*entity\s+(\S+)\s+is}
4881  } elseif {[file extension $filename] == ".v" || [file extension $filename] == ".sv"} {
4882  # Regular expression to match the module name after the 'module' keyword
4883  set pattern {\n\s*module\s*(\w+)(\s*|\(|\n)}
4884  } else {
4885  Msg Debug "File is neither VHDL nor Verilog... Returning empty string..."
4886  return "'"
4887  }
4888 
4889  # Search for the module name using the regular expression
4890  if {[regexp $pattern $file_content match module_name]} {
4891  return $module_name
4892  } else {
4893  Msg Debug "No module was found in $filename. Returning an empty string..."
4894  return ""
4895  }
4896 }
4897 
4898 ## Get a dictionary of verilog generics with their types for a given file
4899 #
4900 # @param[in] file File to read Generics from
4901 proc GetVerilogGenerics {file} {
4902  set fp [open $file r]
4903  set data [read $fp]
4904  close $fp
4905  set lines []
4906 
4907  # read in the verilog file and remove comments
4908  foreach line [split $data "\n"] {
4909  regsub "^\\s*\/\/.*" $line "" line
4910  regsub "(.*)\/\/.*" $line {\1} line
4911  if {![string equal $line ""]} {
4912  append lines $line " "
4913  }
4914  }
4915 
4916  # remove block comments also /* */
4917  regsub -all {/\*.*\*/} $lines "" lines
4918 
4919  # create a list of characters to split for tokenizing
4920  set punctuation [list]
4921  foreach char [list "(" ")" ";" "," " " "!" "<=" ":=" "=" "\[" "\]"] {
4922  lappend punctuation $char "\000$char\000"
4923  }
4924 
4925  # split the file into tokens
4926  set tokens [split [string map $punctuation $lines] \000]
4927 
4928  set parameters [dict create]
4929 
4930  set PARAM_NAME 1
4931  set PARAM_VALUE 2
4932  set LEXING 3
4933  set PARAM_WIDTH 4
4934  set state $LEXING
4935 
4936  # loop over the generic lines
4937  foreach token $tokens {
4938  set token [string trim $token]
4939  if {![string equal "" $token]} {
4940  if {[string equal [string tolower $token] "parameter"]} {
4941  set state $PARAM_NAME
4942  } elseif {[string equal $token ")"] || [string equal $token ";"]} {
4943  set state $LEXING
4944  } elseif {$state == $PARAM_WIDTH} {
4945  if {[string equal $token "\]"]} {
4946  set state $PARAM_NAME
4947  }
4948  } elseif {$state == $PARAM_VALUE} {
4949  if {[string equal $token ","]} {
4950  set state $PARAM_NAME
4951  } elseif {[string equal $token ";"]} {
4952  set state $LEXING
4953  } else {
4954 
4955  }
4956  } elseif {$state == $PARAM_NAME} {
4957  if {[string equal $token "="]} {
4958  set state $PARAM_VALUE
4959  } elseif {[string equal $token "\["]} {
4960  set state $PARAM_WIDTH
4961  } elseif {[string equal $token ","]} {
4962  set state $PARAM_NAME
4963  } elseif {[string equal $token ";"]} {
4964  set state $LEXING
4965  } elseif {[string equal $token ")"]} {
4966  set state $LEXING
4967  } else {
4968  dict set parameters $token "integer"
4969  }
4970  }
4971  }
4972  }
4973 
4974  return $parameters
4975 }
4976 
4977 ## Get a dictionary of VHDL generics with their types for a given file
4978 #
4979 # @param[in] file File to read Generics from
4980 # @param[in] entity The entity from which extracting the generics
4981 proc GetVhdlGenerics {file {entity ""}} {
4982  set fp [open $file r]
4983  set data [read $fp]
4984  close $fp
4985  set lines []
4986 
4987  # read in the vhdl file and remove comments
4988  foreach line [split $data "\n"] {
4989  regsub "^\\s*--.*" $line "" line
4990  regsub "(.*)--.*" $line {\1} line
4991  if {![string equal $line ""]} {
4992  append lines $line " "
4993  }
4994  }
4995 
4996  # extract the generic block
4997  set generic_block ""
4998  set generics [dict create]
4999 
5000  if {1 == [string equal $entity ""]} {
5001  regexp {(?i).*entity\s+([^\s]+)\s+is} $lines _ entity
5002  }
5003 
5004  set generics_regexp "(?i).*entity\\s+$entity\\s+is\\s+generic\\s*\\((.*)\\)\\s*;\\s*port.*end.*$entity"
5005 
5006  if {[regexp $generics_regexp $lines _ generic_block]} {
5007  # loop over the generic lines
5008  foreach line [split $generic_block ";"] {
5009  # split the line into the generic + the type
5010  regexp {(.*):\s*([A-Za-z0-9_]+).*} $line _ generic type
5011 
5012  # one line can have multiple generics of the same type, so loop over them
5013  set splits [split $generic ","]
5014  foreach split $splits {
5015  dict set generics [string trim $split] [string trim $type]
5016  }
5017  }
5018  }
5019  return $generics
5020 }
5021 
5022 ## @brief Runs a GHDL command and returns its output and exit state
5023 proc GHDL {command logfile} {
5024  set ret [catch {exec -ignorestderr ghdl {*}$command >>& $logfile} result options]
5025  # puts "ret: $ret"
5026  # puts "result: $result\n"
5027  # puts "options: $options"
5028  # puts "*********"
5029  return [list $ret $result]
5030 }
5031 
5032 ## @brief Handle git commands without causing an error if ret is not 0
5033 #
5034 # It can be used with lassign like this: lassign [GitRet <git command> <possibly files> ] ret result
5035 #
5036 # @param[in] command: the git command to be run including refs (branch, tags, sha, etc.), except files.
5037 # @param[in] files: files given to git as argument. They will always be separated with -- to avoid weird accidents
5038 # Sometimes you need to remove the --. To do that just set files to " "
5039 #
5040 # @returns a list of 2 elements: the return value (0 if no error occurred) and the output of the git command
5041 proc GitRet {command {files ""}} {
5042  global env
5043  if {$files eq ""} {
5044  set ret [catch {exec -ignorestderr git {*}$command} result]
5045  } else {
5046  set ret [catch {exec -ignorestderr git {*}$command -- {*}$files} result]
5047  }
5048  return [list $ret $result]
5049 }
5050 
5051 ## @brief Check git version installed in this machine
5052 #
5053 # @param[in] target_version the version required by the current project
5054 #
5055 # @return Returns 1, if the system git version is greater or equal to the target
5056 proc GitVersion {target_version} {
5057  set ver [split $target_version "."]
5058  set v [Git --version]
5059  #Msg Info "Found Git version: $v"
5060  set current_ver [split [lindex $v 2] "."]
5061  set target [expr {[lindex $ver 0] * 100000 + [lindex $ver 1] * 100 + [lindex $ver 2]}]
5062  set current [expr {[lindex $current_ver 0] * 100000 + [lindex $current_ver 1] * 100 + [lindex $current_ver 2]}]
5063  return [expr {$target <= $current}]
5064 }
5065 
5066 ## @brief Check if a file is present in an Rclone remote repository
5067 #
5068 # The output of rclone is captured rather than printed, so that the error messages that rclone
5069 # emits when the file (or its parent directory) is not there do not pollute the Hog log.
5070 # Any other rclone failure (e.g. wrong credentials, unreachable remote) is reported instead.
5071 #
5072 # @param[in] remote_file: the full rclone path of the file to look for
5073 # @param[in] config_path: the rclone configuration file to be used
5074 #
5075 # @return 1 if the file exists, 0 if it does not exist or if rclone could not be run
5076 proc RcloneFileExists {remote_file config_path} {
5077  if {[catch {exec rclone ls $remote_file --config $config_path 2>@1} result opt] == 0} {
5078  return 1
5079  }
5080  # rclone exits with 3 (directory not found) or 4 (file not found) when the file is simply not there
5081  set rclone_ret [lindex [dict get $opt -errorcode] end]
5082  if {$rclone_ret ne "3" && $rclone_ret ne "4"} {
5083  Msg CriticalWarning "Could not check if $remote_file is in the Rclone repository: $result"
5084  } else {
5085  Msg Debug "$remote_file not found in the Rclone repository (rclone exit code $rclone_ret)."
5086  }
5087  return 0
5088 }
5089 
5090 ## @brief Copy IP generated files from/to a remote o local directory (possibly EOS)
5091 #
5092 # @param[in] what_to_do: the action you want to perform, either
5093 # "push", if you want to copy the local IP synth result to the remote directory
5094 # "pull" if you want to copy the files from thre remote directory to your local repository
5095 # @param[in] xci_file: the .xci file of the IP you want to handle
5096 # @param[in] ip_path: the path of the directory you want the IP to be saved (possibly EOS)
5097 # @param[in] repo_path: the main path of your repository
5098 # @param[in] gen_dir: the directory where generated files are placed, \
5099 # by default the files are placed in the same folder as the .xci
5100 # @param[in] force: if not set to 0, will copy the IP to the remote directory even if it is already present
5101 #
5102 proc HandleIP {what_to_do xci_file ip_path repo_path on_eos on_rclone rclone_config_path {gen_dir "."} {force 0}} {
5103  global env
5104  if {!($what_to_do eq "push") && !($what_to_do eq "pull")} {
5105  Msg Error "You must specify push or pull as first argument."
5106  }
5107 
5108  if {[catch {package require tar} TARPACKAGE]} {
5109  Msg CriticalWarning "Cannot find package tar. You can fix this by installing package \"tcllib\""
5110  return -1
5111  }
5112 
5113  set old_path [pwd]
5114  cd $repo_path
5115 
5116  if {!([file exists $xci_file])} {
5117  Msg CriticalWarning "Could not find $xci_file."
5118  cd $old_path
5119  return -1
5120  }
5121 
5122 
5123  set xci_path [file dirname $xci_file]
5124  set xci_name [file tail $xci_file]
5125  set xci_ip_name [file rootname [file tail $xci_file]]
5126  set xci_dir_name [file tail $xci_path]
5127  set gen_path $gen_dir
5128 
5129  set hash [Md5Sum $xci_file]
5130  set file_name $xci_name\_$hash
5131 
5132  # Check if the IP is a subcore of a BD file
5133 
5134  set scope [get_property -quiet SCOPE [get_ips $xci_ip_name]]
5135  if {$scope != ""} {
5136  Msg Debug "IP $xci_name is a subcore of a BD file, will not pull/push it from/to the remote directory."
5137  return 0
5138  }
5139 
5140  Msg Info "Preparing to $what_to_do IP: $xci_name ($file_name)."
5141 
5142  if {$what_to_do eq "push"} {
5143  set will_copy 0
5144  set will_remove 0
5145  if {$on_rclone == 1} {
5146  # lassign [ExecuteRet rclone ls $ip_path/$file_name.tar --config $rclone_config_path] ret result
5147  if {[RcloneFileExists $ip_path/$file_name.tar $rclone_config_path] == 0} {
5148  set will_copy 1
5149  } else {
5150  if {$force == 0} {
5151  Msg Info "IP already in the Rclone repository, will not copy..."
5152  } else {
5153  Msg Info "IP already in the Rclone repository, will forcefully replace..."
5154  set will_copy 1
5155  set will_remove 1
5156  }
5157  }
5158  } elseif {$on_eos == 1} {
5159  lassign [eos "ls $ip_path/$file_name.tar"] ret result
5160  if {$ret != 0} {
5161  set will_copy 1
5162  } else {
5163  if {$force == 0} {
5164  Msg Info "IP already in the EOS repository, will not copy..."
5165  } else {
5166  Msg Info "IP already in the EOS repository, will forcefully replace..."
5167  set will_copy 1
5168  set will_remove 1
5169  }
5170  }
5171  } else {
5172  if {[file exists "$ip_path/$file_name.tar"]} {
5173  if {$force == 0} {
5174  Msg Info "IP already in the local repository, will not copy..."
5175  } else {
5176  Msg Info "IP already in the local repository, will forcefully replace..."
5177  set will_copy 1
5178  set will_remove 1
5179  }
5180  } else {
5181  set will_copy 1
5182  }
5183  }
5184 
5185  if {$will_copy == 1} {
5186  # Check if there are files in the .gen directory first and copy them into the right place
5187  Msg Info "Looking for generated files in $gen_path..."
5188  set ip_gen_files [glob -nocomplain $gen_path/*]
5189 
5190  #here we should remove the .xci file from the list if it's there
5191 
5192  if {[llength $ip_gen_files] > 0} {
5193  Msg Info "Found some IP synthesised files matching $xci_ip_name"
5194  if {$will_remove == 1} {
5195  Msg Info "Removing old synthesised directory $ip_path/$file_name.tar..."
5196  if {$on_rclone == 1} {
5197  lassign [ExecuteRet rclone delete $ip_path/$file_name.tar --config $rclone_config_path] ret result
5198  if {$ret != 0} {
5199  Msg CriticalWarning "Could not delete file from Rclone: $result"
5200  }
5201  } elseif {$on_eos == 1} {
5202  eos "rm -rf $ip_path/$file_name.tar" 5
5203  } else {
5204  file delete -force "$ip_path/$file_name.tar"
5205  }
5206  }
5207 
5208  Msg Info "Creating local archive with IP generated files..."
5209  set tar_files []
5210 
5211  set at_least_one_long 0
5212  foreach f $ip_gen_files {
5213  set new_f "[Relative [file normalize $repo_path] $f]"
5214  set len [string length $new_f]
5215  if {$len > 254} {
5216  Msg Warning "One file in $xci_ip_name is too long ($len chars): $new_f"
5217  set at_least_one_long 1
5218  }
5219  lappend tar_files $new_f
5220  }
5221 
5222  Msg Debug "Tar files: $tar_files"
5223  if {$at_least_one_long == 1} {
5224  Msg Warning "Using regular tar, please cross your fingers..."
5225  #lassign [ExecuteRet tar --format=pax -cf $file_name.tar {*}$tar_files] ret_tar result_tar
5226  lassign [ExecuteRet tar -cf $file_name.tar {*}$tar_files] ret_tar result_tar
5227  if {$ret_tar != 0} {
5228  Msg CriticalWarning "Something went wrong when using regular tar. Error message: $result_tar"
5229  }
5230  } else {
5231  if {[catch { ::tar::create $file_name.tar $tar_files } err]} {
5232  Msg Warning "Tcl built-in tar failed: $err, will try regular tar..."
5233  #lassign [ExecuteRet tar --format=pax -cf $file_name.tar {*}$tar_files] ret_tar result_tar
5234  lassign [ExecuteRet tar -cf $file_name.tar {*}$tar_files] ret_tar result_tar
5235  if {$ret_tar != 0} {
5236  Msg CriticalWarning "Something went wrong when using regular tar. Error message: $result_tar"
5237  }
5238  }
5239  }
5240 
5241  Msg Info "Copying IP generated files for $xci_name..."
5242  if {$on_rclone == 1} {
5243  lassign [ExecuteRet rclone copyto $file_name.tar $ip_path/$file_name.tar --config $rclone_config_path] ret result
5244  if {$ret != 0} {
5245  Msg CriticalWarning "Something went wrong when copying the IP files to Rclone. Error message: $result"
5246  }
5247  } elseif {$on_eos == 1} {
5248  lassign [ExecuteRet xrdcp -f -s $file_name.tar $::env(EOS_MGM_URL)//$ip_path/] ret msg
5249  if {$ret != 0} {
5250  Msg CriticalWarning "Something went wrong when copying the IP files to EOS. Error message: $msg"
5251  }
5252  } else {
5253  Copy "$file_name.tar" "$ip_path/"
5254  }
5255  Msg Info "Removing local archive"
5256  file delete $file_name.tar
5257  } else {
5258  Msg Warning "Could not find synthesized files matching $gen_path/$file_name*"
5259  }
5260  }
5261  } elseif {$what_to_do eq "pull"} {
5262  if {$on_rclone == 1} {
5263  # lassign [ExecuteRet rclone ls $ip_path/$file_name.tar --config $rclone_config_path] ret result
5264  if {[RcloneFileExists $ip_path/$file_name.tar $rclone_config_path] == 0} {
5265  Msg Info "Nothing for $xci_name was found in the Rclone repository, cannot pull."
5266  cd $old_path
5267  return -1
5268  } else {
5269  Msg Info "IP $xci_name found in the Rclone repository $ip_path, copying it locally to $repo_path..."
5270  lassign [ExecuteRet rclone copyto $ip_path/$file_name.tar $file_name.tar --config $rclone_config_path] ret_copy result_copy
5271  if {$ret_copy != 0} {
5272  Msg CriticalWarning "Something went wrong when copying the IP files from Rclone. Error message: $result_copy"
5273  }
5274  }
5275  } elseif {$on_eos == 1} {
5276  lassign [eos "ls $ip_path/$file_name.tar"] ret result
5277  if {$ret != 0} {
5278  Msg Info "Nothing for $xci_name was found in the EOS repository, cannot pull."
5279  cd $old_path
5280  return -1
5281  } else {
5282  set remote_tar "$::env(EOS_MGM_URL)//$ip_path/$file_name.tar"
5283  Msg Info "IP $xci_name found in the repository $remote_tar, copying it locally to $repo_path..."
5284 
5285  lassign [ExecuteRet xrdcp -f -r -s $remote_tar $repo_path] ret msg
5286  if {$ret != 0} {
5287  Msg CriticalWarning "Something went wrong when copying the IP files to EOS. Error message: $msg"
5288  }
5289  }
5290  } else {
5291  if {[file exists "$ip_path/$file_name.tar"]} {
5292  Msg Info "IP $xci_name found in local repository $ip_path/$file_name.tar, copying it locally to $repo_path..."
5293  Copy $ip_path/$file_name.tar $repo_path
5294  } else {
5295  Msg Info "Nothing for $xci_name was found in the local IP repository, cannot pull."
5296  cd $old_path
5297  return -1
5298  }
5299  }
5300 
5301  if {[file exists $file_name.tar]} {
5302  remove_files $xci_file
5303  Msg Info "Extracting IP files from archive to $repo_path..."
5304  ::tar::untar $file_name.tar -dir $repo_path -noperms
5305  Msg Info "Removing local archive"
5306  file delete $file_name.tar
5307  add_files -norecurse -fileset sources_1 $xci_file
5308  }
5309  }
5310  cd $old_path
5311  return 0
5312 }
5313 
5314 ## Convert hex version to M.m.p string
5315 #
5316 # @param[in] version the version (in 32-bit hexadecimal format 0xMMmmpppp) to be converted
5317 #
5318 # @return a string containing the version in M.m.p format
5319 #
5320 proc HexVersionToString {version} {
5321  scan [string range $version 0 1] %x M
5322  scan [string range $version 2 3] %x m
5323  scan [string range $version 4 7] %x c
5324  return "$M.$m.$c"
5325 }
5326 
5327 # @brief Import TCL Lib from an external installation for Libero, Synplify and Diamond
5328 proc ImportTclLib {} {
5329  global env
5330  if {[IsLibero] || [IsDiamond] || [IsSynplify]} {
5331  if {[info exists env(HOG_TCLLIB_PATH)]} {
5332  lappend auto_path $env(HOG_TCLLIB_PATH)
5333  return 1
5334  } else {
5335  puts "ERROR: To run Hog with Microsemi Libero SoC or Lattice Diamond, you need to define the HOG_TCLLIB_PATH variable."
5336  return 0
5337  }
5338  }
5339 }
5340 
5341 # @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
5342 #
5343 # @param[in] script The launch.tcl script
5344 # @param[in] tcl_path The launch.tcl script path
5345 # @param[in] parameters The allowed parameters for launch.tcl
5346 # @param[in] commands The allowed directives for launch.tcl
5347 # @param[in] argv The input arguments passed to launch.tcl
5348 # @param[in] custom_commands Custom commands to be added to the list of commands
5349 
5350 proc InitLauncher {script tcl_path parameters commands argv {custom_commands ""}} {
5351  set repo_path [file normalize "$tcl_path/../.."]
5352  set old_path [pwd]
5353  set bin_path [file normalize "$tcl_path/../../bin"]
5354  set top_path [file normalize "$tcl_path/../../Top"]
5355 
5356  set cmd_lines [split $commands "\n"]
5357 
5358  set command_options [dict create]
5359  set directive_descriptions [dict create]
5360  set directive_names [dict create]
5361  set common_directive_names [dict create]
5362  set directives_with_optional_projects ""
5363  set custom_command ""
5364  set custom_command_options ""
5365 
5366  foreach l $cmd_lines {
5367  #excludes direcitve with a # just after the \{
5368  if {[regexp {\\(.*) \{\#} $l minc d]} {
5369  lappend directives_with_projects $d
5370  set current_directive $d
5371  }
5372 
5373  if {[regexp {^[^#]* allow_empty_proj} $l minc dd]} {
5374  lappend directives_with_optional_projects $current_directive
5375  }
5376 
5377  #gets all the regexes
5378  if {[regexp {\\(.*) \{} $l minc regular_expression]} {
5379  lappend directive_regex $regular_expression
5380  }
5381 
5382  #gets all common directives
5383  if {[regexp {\#\s*NAME(\*)?:\s*(.*)\s*} $l minc star name]} {
5384  dict set directive_names $name $regular_expression
5385  if {$star eq "*"} {
5386  dict set common_directive_names $name $regular_expression
5387  }
5388  }
5389  set directive_names [DictSort $directive_names]
5390  set common_directive_names [DictSort $common_directive_names]
5391 
5392  #gets all the descriptions
5393  if {[regexp {\#\s*DESCRIPTION:\s*(.*)\s*} $l minc x]} {
5394  dict set directive_descriptions $regular_expression $x
5395  }
5396 
5397  #gets all the list of options
5398  if {[regexp {\#\s*OPTIONS:\s*(.*)\s*} $l minc x]} {
5399  dict set command_options $regular_expression [split [regsub -all {[ \t\n]+} $x {}] ","]
5400  }
5401  }
5402 
5403  set short_usage "usage: ./Hog/Do \[OPTIONS\] <directive> \[project\]\n\nMost common directives (case insensitive):"
5404 
5405  dict for {key value} $common_directive_names {
5406  set short_usage "$short_usage\n - $key: [dict get $directive_descriptions $value]"
5407  }
5408 
5409  if {[string length $custom_commands] > 0} {
5410  Msg Debug "Found custom commands to add to short short_usage."
5411  set short_usage "$short_usage\n\nCustom commands:"
5412  dict for {key command} $custom_commands {
5413  Msg Debug "Adding $key : [dict get $command DESCRIPTION]"
5414  set short_usage "$short_usage\n - $key: [dict get $command DESCRIPTION]"
5415  }
5416  }
5417 
5418 
5419  set short_usage "$short_usage\n\n\
5420  To see all the available directives, run:\n./Hog/Do HELP\n\n\
5421  To list available options for the chosen directive run:\n\
5422  ./Hog/Do <directive> HELP\n
5423  "
5424 
5425  set usage "usage: ./Hog/Do \[OPTIONS\] <directive> \[project\]\n\nDirectives (case insensitive):"
5426 
5427  dict for {key value} $directive_names {
5428  set usage "$usage\n - $key: [dict get $directive_descriptions $value]"
5429  }
5430 
5431  # if length of custom commands is greater than 0, add them to the short usage"
5432  if {[string length $custom_commands] > 0} {
5433  Msg Debug "Found custom commands to add to short usage."
5434  set usage "$usage\n\nCustom commands:"
5435  dict for {key command} $custom_commands {
5436  Msg Debug "Adding $key : [dict get $command DESCRIPTION]"
5437  set usage "$usage\n - $key: [dict get $command DESCRIPTION]"
5438  }
5439  }
5440 
5441 
5442  set usage "$usage\n\nTo list available options for the chosen directive run:\n./Hog/Do <directive> HELP"
5443 
5444  if {[IsTclsh]} {
5445  #Just display the logo the first time, not when the script is run in the IDE
5446  Logo $repo_path
5447  }
5448 
5449  # Check if HogEnv.conf exists and parse it
5450  if {[file exists [Hog::LoggerLib::GetUserFilePath "HogEnv.conf"]]} {
5451  Msg Debug "HogEnv.conf found"
5452  set loggerdict [Hog::LoggerLib::ParseTOML [Hog::LoggerLib::GetUserFilePath "HogEnv.conf"]]
5453  set HogEnvDict [Hog::LoggerLib::GetTOMLDict]
5454  Hog::LoggerLib::PrintTOMLDict $HogEnvDict
5455  }
5456 
5457 
5458  if {[catch {package require cmdline} ERROR]} {
5459  Msg Debug "The cmdline Tcl package was not found, sourcing it from Hog..."
5460  source $tcl_path/utils/cmdline.tcl
5461  }
5462 
5463  set argv [regsub -all {(?i) HELP\y} $argv " -help"]
5464 
5465 
5466  #Gather up all custom parameters
5467  #NOTE: right now user can accidentally redefine their own custom parameters, there is no check for that...
5468  set custom_parameters [list]
5469  dict for {key command} $custom_commands {
5470  set custom_parameters [concat $custom_parameters [dict get $command CUSTOM_OPTIONS]]
5471  }
5472 
5473  lassign [GetOptions $argv [concat $custom_parameters $parameters]] option_list arg_list
5474 
5475  if {[IsInList "-all" $option_list]} {
5476  set list_all 1
5477  } else {
5478  set list_all 2
5479  }
5480 
5481  #option_list will be emptied by the next instruction
5482 
5483  # Argv here is modified and the options are removed
5484  set directive [string toupper [lindex $arg_list 0]]
5485  set min_n_of_args 0
5486  set max_n_of_args 2
5487  set argument_is_no_project 1
5488 
5489  set NO_DIRECTIVE_FOUND 0
5490  switch -regexp -- $directive "$commands"
5491 
5492  if {$NO_DIRECTIVE_FOUND == 1} {
5493  if {[string length $custom_commands] > 0 && [dict exists $custom_commands $directive]} {
5494  set custom_command $directive
5495  set custom_command_dict [DictGet $custom_commands $directive]
5496  set custom_command_hog_parameters [DictGet $custom_command_dict OPTIONS]
5497  set custom_command_options [DictGet $custom_command_dict CUSTOM_OPTIONS]
5498  set custom_command_options [concat $custom_command_hog_parameters $custom_command_options]
5499  } else {
5500  Msg Status "ERROR: Unknown directive $directive.\n\n"
5501  puts $usage
5502  exit
5503  }
5504  }
5505 
5506  if {[IsInList $directive $directives_with_projects 1]} {
5507  set argument_is_no_project 0
5508  }
5509 
5510  if {[IsInList "-help" $option_list] || [IsInList "-?" $option_list] || [IsInList "-h" $option_list]} {
5511  if {$directive != ""} {
5512  if {[IsInList $directive $directives_with_projects 1]} {
5513  puts "usage: ./Hog/Do \[OPTIONS\] $directive <project>\n"
5514  } elseif {[regexp "^COMPSIM(LIB)?$" $directive]} {
5515  puts "usage: ./Hog/Do \[OPTIONS\] $directive <simulator>\n"
5516  } else {
5517  puts "usage: ./Hog/Do \[OPTIONS\] $directive \n"
5518  }
5519 
5520  dict for {dir desc} $directive_descriptions {
5521  if {[regexp $dir $directive]} {
5522  puts "$desc\n"
5523  break
5524  }
5525  }
5526 
5527  #if custom command, parse custom options instead
5528  if {$custom_command ne ""} {
5529  if {[llength $custom_command_options] > 0} {
5530  puts "Available options:"
5531  }
5532  foreach custom_option $custom_command_options {
5533  set n [llength $custom_option]
5534  if {$n == 2} {
5535  lassign $custom_option opt help
5536  puts " -$opt"
5537  puts " $help"
5538  } elseif {$n == 3} {
5539  lassign $custom_option opt def help
5540  puts " -$opt <argument>"
5541  if {$def ne ""} {
5542  puts " $help (default: $def)"
5543  } else {
5544  puts " $help"
5545  }
5546  } else {
5547  Msg Warning "Custom option spec has invalid arity (expected 2 or 3): $custom_option"
5548  }
5549  }
5550  }
5551 
5552  dict for {dir opts} $command_options {
5553  if {[regexp $dir $directive]} {
5554  puts "Available options:"
5555  foreach opt $opts {
5556  foreach par $parameters {
5557  if {$opt == [lindex $par 0]} {
5558  if {[regexp {\.arg$} $opt]} {
5559  set opt_name [regsub {\.arg$} $opt ""]
5560  puts " -$opt_name <argument>"
5561  } else {
5562  puts " -$opt"
5563  }
5564  puts " [lindex $par [llength $par]-1]"
5565  }
5566  }
5567  }
5568  puts ""
5569  }
5570  }
5571  } else {
5572  puts $usage
5573  }
5574  # Msg Info [cmdline::usage $parameters $usage]
5575  exit 0
5576  }
5577 
5578  # Gather the options supported by the chosen directive
5579  set directive_options ""
5580  set directive_found 0
5581  if {$custom_command ne ""} {
5582  set directive_options $custom_command_options
5583  set directive_found 1
5584  } else {
5585  dict for {dir opts} $command_options {
5586  if {[regexp $dir $directive]} {
5587  set directive_options $opts
5588  set directive_found 1
5589  break
5590  }
5591  }
5592  }
5593 
5594  # Get the directive name without `.arg` suffix
5595  set directive_option_names {}
5596  foreach opt $directive_options {
5597  lappend directive_option_names [regsub {\.arg$} [lindex $opt 0] ""]
5598  }
5599 
5600  if {$custom_command ne ""} {
5601  set parameters [concat $parameters $custom_command_options]
5602  }
5603 
5604  if {[catch {array set options [cmdline::getoptions option_list $parameters $usage]} err]} {
5605  Msg Status "\nERROR: Syntax error, probably unknown option.\n\n USAGE: $err"
5606  exit 1
5607  }
5608 
5609  # Ignore the options that are not supported by the chosen directive, restoring their default value
5610  if {$directive_found == 1} {
5611  set default_option_list {}
5612  array set default_options [cmdline::getoptions default_option_list $parameters $usage]
5613  foreach {key value} [array get options] {
5614  if {[IsInList $key $directive_option_names] || ![info exists default_options($key)]} {
5615  continue
5616  }
5617  if {$value ne $default_options($key)} {
5618  Msg Warning "Option -$key is not supported by directive $directive, ignoring it."
5619  set options($key) $default_options($key)
5620  }
5621  }
5622  }
5623 
5624  if {[llength $arg_list] <= $min_n_of_args || [llength $arg_list] > $max_n_of_args} {
5625  Msg Status "\nERROR: Wrong number of arguments: [llength $argv]"
5626  puts $short_usage
5627  exit 1
5628  }
5629 
5630  set project [lindex $arg_list 1]
5631  set optional_project [IsInList $directive $directives_with_optional_projects 1]
5632 
5633  if {$argument_is_no_project == 0} {
5634  # Remove leading Top/ or ./Top/ if in project_name
5635  regsub "^(\./)?Top/" $project "" project
5636  # Remove trailing / and spaces if in project_name
5637  regsub "/? *\$" $project "" project
5638 
5639  if {$project eq "" && $optional_project == 1} {
5640  set proj_conf 0
5641  } else {
5642  set proj_conf [ProjectExists $project $repo_path]
5643  }
5644  } else {
5645  set proj_conf 0
5646  }
5647 
5648  Msg Debug "Option list:"
5649 
5650  foreach {key value} [array get options] {
5651  Msg Debug "$key => $value"
5652  }
5653 
5654  set cmd ""
5655 
5656  if {[IsTclsh]} {
5657  # command is filled with the IDE exectuable when this function is called by Tcl scrpt
5658  if {$proj_conf != 0} {
5659  CheckLatestHogRelease $repo_path
5660 
5661  lassign [GetIDECommand $proj_conf] cmd before_tcl_script after_tcl_script end_marker
5662  Msg Info "Project $project uses $cmd IDE"
5663 
5664  ## The following is the IDE command to launch:
5665  set command "$cmd $before_tcl_script$script$after_tcl_script$argv$end_marker"
5666  } else {
5667  if {$custom_command ne ""} {
5668  if {[dict exists $custom_commands $directive IDE]} {
5669  lassign [GetIDECommand "" [dict get $custom_commands $directive IDE]] cmd before_tcl_script after_tcl_script end_marker
5670  Msg Info "Custom command: $custom_command uses $cmd IDE"
5671  set command "$cmd $before_tcl_script$script$after_tcl_script$argv$end_marker"
5672  } else {
5673  set command "custom_tcl"
5674  }
5675  } elseif {$argument_is_no_project == 1} {
5676  set command -4
5677  Msg Debug "$project will be used as first argument"
5678  } elseif {$project != ""} {
5679  #Project not given
5680  set command -1
5681  } elseif {$min_n_of_args < 0} {
5682  #Project not needed
5683  set command -3
5684  } else {
5685  #Project not found
5686  if {$optional_project == 0} {
5687  set command -2
5688  } else {
5689  set command -3
5690  }
5691  }
5692  }
5693  } else {
5694  # When the launcher is executed from within an IDE, command is set to 0
5695  set command 0
5696  }
5697 
5698  set project_group [file dirname $project]
5699  set project_name $project
5700  set project [file tail $project]
5701  Msg Debug "InitLauncher: project_group=$project_group, project_name=$project_name, project=$project"
5702 
5703  return [list $directive $project $project_name $project_group $repo_path $old_path\
5704  $bin_path $top_path $usage $short_usage $command $cmd [array get options]\
5705  $directive_options]
5706 }
5707 
5708 # @brief Returns 1 if a commit is an ancestor of another, otherwise 0
5709 #
5710 # @param[in] ancestor The potential ancestor commit
5711 # @param[in] commit The potential descendant commit
5712 proc IsCommitAncestor {ancestor commit} {
5713  lassign [GitRet "merge-base --is-ancestor $ancestor $commit"] status result
5714  if {$status == 0} {
5715  return 1
5716  } else {
5717  return 0
5718  }
5719 }
5720 
5721 proc IsDiamond {} {
5722  return [expr {[info commands sys_install] != ""}]
5723 }
5724 
5725 ## @brief Returns true if the IDE is MicroSemi Libero
5726 proc IsLibero {} {
5727  return [expr {[info commands get_libero_version] != ""}]
5728 }
5729 
5730 # @brief Returns 1 if an element is a list, 0 otherwise
5731 #
5732 # @param[in] element The element to search
5733 # @param[in] list The list to search into
5734 # @param[in] regex An optional regex to match. If 0, the element should match exactly an object in the list
5735 # @param[in] nocase If 1, perform case-insensitive comparison
5736 proc IsInList {element list {regex 0} {nocase 0}} {
5737  foreach x $list {
5738  if {$regex == 1} {
5739  if {$nocase == 1} {
5740  if {[regexp -nocase $x $element]} {
5741  return 1
5742  }
5743  } else {
5744  if {[regexp $x $element]} {
5745  return 1
5746  }
5747  }
5748  } elseif {$regex == 0} {
5749  if {$nocase == 1} {
5750  if {[string tolower $x] eq [string tolower $element]} {
5751  return 1
5752  }
5753  } else {
5754  if {$x eq $element} {
5755  return 1
5756  }
5757  }
5758  }
5759  }
5760  return 0
5761 }
5762 
5763 
5764 ## @brief Returns true, if the IDE is ISE/PlanAhead
5765 proc IsISE {} {
5766  if {[IsXilinx]} {
5767  return [expr {[string first PlanAhead [version]] == 0}]
5768  } else {
5769  return 0
5770  }
5771 }
5772 
5773 ## @brief Returns true, if IDE is Quartus
5774 proc IsQuartus {} {
5775  if {[catch {package require ::quartus::flow} result]} {
5776  # not available
5777  return 0
5778  } else {
5779  # available
5780  return 1
5781  }
5782 }
5783 
5784 ## Check if a path is absolute or relative
5785 #
5786 # @param[in] the path to check
5787 #
5788 proc IsRelativePath {path} {
5789  if {[string index $path 0] == "~"} {
5790  return 0
5791  }
5792  return [expr {[file pathtype $path] eq "relative"}]
5793 }
5794 
5795 ## @brief Returns true if the Synthesis tool is Synplify
5796 proc IsSynplify {} {
5797  return [expr {[info commands program_version] != ""}]
5798 }
5799 
5800 ## @brief Returns true, if we are in tclsh
5801 proc IsTclsh {} {
5802  return [expr {![IsQuartus] && ![IsXilinx] && ![IsVitisClassic] && ![IsVitisUnified] && ![IsLibero] && ![IsSynplify] && ![IsDiamond]}]
5803 }
5804 
5805 # @brief Find out if the given file is a Verilog or SystemVerilog file
5806 #
5807 # @param[in] file The file to check
5808 # @param[out] 1 if it's Verilog/SystemVerilog 0 if it's not
5809 #
5810 proc IsVerilog {file} {
5811  if {[file extension $file] == ".v" || [file extension $file] == ".sv"} {
5812  return 1
5813  } else {
5814  return 0
5815  }
5816 }
5817 
5818 ## @brief Find out if the given Xilinx part is a Versal chip
5819 #
5820 # @param[out] 1 if it's Versal 0 if it's not
5821 # @param[in] part The FPGA part
5822 #
5823 proc IsVersal {part} {
5824  if {[get_property ARCHITECTURE [get_parts $part]] eq "versal"} {
5825  return 1
5826  } else {
5827  return 0
5828  }
5829 }
5830 
5831 ## @brief Returns true, if the IDE is Vivado
5832 proc IsVivado {} {
5833  if {[IsXilinx]} {
5834  return [expr {[string first Vivado [version]] == 0}]
5835  } else {
5836  return 0
5837  }
5838 }
5839 
5840 ## @brief Return true, if the IDE is Xilinx (Vivado or ISE)
5841 proc IsXilinx {} {
5842  if {[info commands version] != ""} {
5843  set current_version [version]
5844  if {[string first PlanAhead $current_version] == 0 || [string first Vivado $current_version] == 0} {
5845  return 1
5846  } elseif {[string first xsct $current_version] == 0} {
5847  return 0
5848  } else {
5849  Msg Warning "This IDE has the version command but it is not PlanAhead or Vivado: $current_version"
5850  return 0
5851  }
5852  } else {
5853  return 0
5854  }
5855 }
5856 
5857 ## @brief Returns true, if the IDE is vitis_classic
5858 proc IsVitisClassic {} {
5859  if {[info exists globalSettings::vitis_classic]} {
5860  return $globalSettings::vitis_classic
5861  }
5862  return [expr {[info commands platform] != ""}]
5863 }
5864 
5865 ## @brief Returns true, if the IDE is vitis_unified
5866 proc IsVitisUnified {} {
5867  if {[info exists globalSettings::vitis_unified]} {
5868  return $globalSettings::vitis_unified
5869  }
5870  return 0
5871 }
5872 
5873 ## @brief Execute a Python command via Vitis Unified command-line tool and display output in real-time
5874 #
5875 # @param[in] python_script Full path to the Python script (e.g., PlatformCommands.py or AppCommands.py)
5876 # @param[in] command The command to execute (e.g., "create_platform", "configure_app", "app_list")
5877 # @param[in] args List of arguments to pass to the command
5878 # @param[in] error_prefix Prefix for error messages (e.g., "Failed to create platform" or "Failed to configure app")
5879 # @param[in] output_var Optional variable name to store the output (if not provided, output is only printed)
5880 # @param[out] 1 on success, 0 on failure
5881 #
5882 proc ExecuteVitisUnifiedCommand {python_script command args {error_prefix "Failed to execute command"} {output_var ""}} {
5883  set cmdlist [list vitis -s $python_script $command]
5884  foreach arg $args {
5885  lappend cmdlist $arg
5886  }
5887  lappend cmdlist 2>@1
5888 
5889  Msg Debug "Executing: vitis -s $python_script $command $args"
5890 
5891  # Set PYTHONUNBUFFERED environment variable for real-time output
5892  set ::env(PYTHONUNBUFFERED) "1"
5893 
5894  # Open pipe and configure for line buffering
5895  if {[catch {set pipe [open "|$cmdlist" "r"]} err]} {
5896  Msg Error "$error_prefix: Failed to open pipe: $err"
5897  return 0
5898  }
5899 
5900  fconfigure $pipe -buffering line
5901  set script_output ""
5902  set vitis_version ""
5903 
5904  # Patterns to identify Vitis banner messages, these will be filtered out
5905  set vitis_banner_patterns {
5906  "*Vitis Development Environment*"
5907  "*Vitis v*"
5908  "*SW Build*"
5909  "*Copyright*Xilinx*"
5910  "*Copyright*Advanced Micro Devices*"
5911  "*All Rights Reserved*"
5912  }
5913 
5914  # Read and display output line by line
5915  while {[gets $pipe line] >= 0} {
5916  if {$line ne ""} {
5917  if {[string match "*Vitis v*" $line]} {
5918  if {[regexp {Vitis\s+v([0-9]+)\.([0-9]+)(?:\.[0-9]+)?} $line -> major minor]} {
5919  set year_last_two [string range $major end-1 end]
5920  set vitis_version "$year_last_two.$minor"
5921  }
5922  }
5923 
5924  # Filter out Vitis banner messages
5925  set is_banner 0
5926  foreach pattern $vitis_banner_patterns {
5927  if {[string match $pattern $line]} {
5928  set is_banner 1
5929  break
5930  }
5931  }
5932  if {!$is_banner} {
5933  if {![string match "INFO:*" $line] && ![string match "WARNING:*" $line] && ![string match "ERROR:*" $line] && ![string match "DEBUG:*" $line]} {
5934  if {$vitis_version ne ""} {
5935  set line "INFO: \[Vitis_v$vitis_version\] $line"
5936  } else {
5937  set line "INFO: $line"
5938  }
5939  }
5940  puts "$line"
5941  append script_output "$line\n"
5942  }
5943  }
5944  }
5945 
5946  # Close pipe and check exit code
5947  set exit_code 0
5948  if {[catch {close $pipe} err]} {
5949  if {[regexp {exit (\d+)} $err -> exit_code]} {
5950  if {$exit_code != 0} {
5951  Msg Error "$error_prefix (exit code: $exit_code)"
5952  if {$output_var ne ""} {
5953  upvar $output_var output
5954  set output $script_output
5955  }
5956  return 0
5957  }
5958  } else {
5959  Msg Error "$error_prefix: $err"
5960  if {$output_var ne ""} {
5961  upvar $output_var output
5962  set output $script_output
5963  }
5964  return 0
5965  }
5966  }
5967 
5968  # Return output if requested
5969  if {$output_var ne ""} {
5970  upvar $output_var output
5971  set output $script_output
5972  }
5973 
5974  return 1
5975 }
5976 
5977 ## @brief Get the applications defined in the Vitis workspace
5978 #
5979 # In Vitis Unified this spawns an external "vitis -s" process which opens and
5980 # locks the workspace, so the result must be cached by the caller rather than
5981 # queried repeatedly.
5982 #
5983 # @param[in] vitis_workspace Path of the Vitis Unified workspace (not needed in Vitis Classic)
5984 # @param[in] python_script Full path to AppCommands.py (not needed in Vitis Classic)
5985 # @param[out] A dict with the app names as keys, or an empty string if the list could not be retrieved
5986 #
5987 proc GetVitisApps {{vitis_workspace ""} {python_script ""}} {
5988  if {[IsVitisClassic]} {
5989  # TODO: "app list -dict" return wrong configuration parameters for Vitis Classic versions older than 2022.1
5990  if {[catch {set ws_apps [app list -dict]}]} {
5991  set ws_apps ""
5992  }
5993  return $ws_apps
5994  }
5995 
5996  if {![IsVitisUnified]} {
5997  return ""
5998  }
5999 
6000  set json_output ""
6001  if {
6002  ![ExecuteVitisUnifiedCommand $python_script "app_list" [list $vitis_workspace] \
6003  "Failed to get app list from Vitis Unified workspace $vitis_workspace" json_output]
6004  } {
6005  return ""
6006  }
6007 
6008  if {[catch {package require json}]} {
6009  Msg Warning "JSON package not available for parsing Vitis Unified app list"
6010  return ""
6011  }
6012 
6013  set json_output_filtered ""
6014  if {[regexp -lineanchor {\{.*\}} $json_output json_output_filtered]} {
6015  return [json::json2dict $json_output_filtered]
6016  }
6017  return [json::json2dict $json_output]
6018 }
6019 
6020 ## @brief Find out if the given Xilinx part is a Versal chip
6021 #
6022 # @param[out] 1 if it's Zynq 0 if it's not
6023 # @param[in] part The FPGA part
6024 #
6025 proc IsZynq {part} {
6026  if {[regexp {^(xc7z|xczu).*} $part]} {
6027  return 1
6028  } else {
6029  return 0
6030  }
6031 }
6032 
6033 proc ImportGHDL {project_name repo_path simset_name simset_dict {ext_path ""}} {
6034  set list_path "$repo_path/Top/$project_name/list"
6035  lassign [GetHogFiles -list_files {.src,.ext,.sim} -ext_path $ext_path $list_path $repo_path] src_files properties filesets
6036  cd $repo_path
6037 
6038 
6039  # Get Properties
6040  set properties [DictGet $simset_dict "properties"]
6041  set options [DictGet $properties "options"]
6042 
6043  # Import GHDL files
6044  set workdir Projects/$project_name/ghdl
6045  file delete -force $workdir
6046  file mkdir $workdir
6047  set import_log "$workdir/ghdl-import-${simset_name}.log"
6048  dict for {lib sources} $src_files {
6049  set libname [file rootname $lib]
6050  foreach f $sources {
6051  if {[file extension $f] != ".vhd" && [file extension $f] != ".vhdl"} {
6052  Msg Info "File $f is not a VHDL file, copying it in workfolder..."
6053  file copy -force $f $workdir
6054  } else {
6055  set file_path [Relative $repo_path $f]
6056  set import_log_file [open $import_log "a"]
6057  puts "ghdl -i --work=$libname --workdir=$workdir -fsynopsys --ieee=standard $options $file_path"
6058  puts $import_log_file "ghdl -i --work=$libname --workdir=$workdir -fsynopsys --ieee=standard $options $file_path"
6059  close $import_log_file
6060  lassign [GHDL "-i --work=$libname --workdir=$workdir -fsynopsys --ieee=standard $options $file_path" $import_log] ret result
6061  if {$ret != 0} {
6062  Msg CriticalWarning "GHDL import failed for file $f: $result"
6063  }
6064  }
6065  }
6066  }
6067  PrintFileContent $import_log
6068 }
6069 
6070 proc LaunchGHDL {project_name repo_path simset_name simset_dict {ext_path ""}} {
6071  set top_sim ""
6072  # Setting Simulation Properties
6073  set sim_props [DictGet $simset_dict "properties"]
6074  set options [DictGet $sim_props "options"]
6075  set runopts [DictGet $sim_props "run_options"]
6076 
6077  dict for {prop_name prop_val} $sim_props {
6078  set prop_name [string toupper $prop_name]
6079  if {$prop_name == "TOP"} {
6080  set top_sim $prop_val
6081  }
6082  }
6083  set workdir $repo_path/Projects/$project_name/ghdl
6084  set make_log "$workdir/ghdl-make-${simset_name}.log"
6085  set run_log "$workdir/ghdl-run-${simset_name}.log"
6086  cd $workdir
6087  # Analyse and elaborate the design
6088  set make_log_file [open $make_log "w"]
6089 
6090  puts "ghdl -m --work=$simset_name -fsynopsys --ieee=standard $options $top_sim"
6091  puts $make_log_file "ghdl -m --work=$simset_name -fsynopsys --ieee=standard $options $top_sim"
6092  close $make_log_file
6093 
6094  lassign [GHDL "-m --work=$simset_name -fsynopsys --ieee=standard $options $top_sim" $make_log] ret result
6095  PrintFileContent $make_log
6096  if {$ret != 0} {
6097  Msg Error "GHDL make failed for $top_sim: $result"
6098  return
6099  }
6100 
6101  set run_log_file [open $run_log "w"]
6102  puts "ghdl -r --work=$simset_name -fsynopsys --ieee=standard $options $top_sim $runopts"
6103  puts $run_log_file "ghdl -r --work=$simset_name -fsynopsys --ieee=standard $options $top_sim $runopts"
6104  close $run_log_file
6105 
6106  lassign [GHDL "-r --work=$simset_name -fsynopsys --ieee=standard $options $top_sim $runopts" $run_log] ret result
6107  PrintFileContent $run_log
6108 
6109  if {$ret != 0} {
6110  Msg Error "GHDL run failed for $top_sim: $result"
6111  return
6112  }
6113 
6114  cd $repo_path
6115 }
6116 
6117 # @brief Launch the Implementation, for the current IDE and project
6118 #
6119 # @param[in] reset Reset the Implementation run
6120 # @param[in] do_create Recreate the project
6121 # @param[in] run_folder The folder where to store the run results
6122 # @param[in] project_name The name of the project
6123 # @param[in] repo_path The main path of the git repository (Default .)
6124 # @param[in] njobs The number of parallel CPU jobs for the Implementation (Default 4)
6125 proc LaunchImplementation {reset do_create run_folder project_name {repo_path .} {njobs 4} {do_bitstream 0}} {
6126  Msg Info "Starting implementation flow..."
6127  if {[IsXilinx]} {
6128  if {$reset == 1} {
6129  Msg Info "Resetting run before launching implementation..."
6130  reset_run impl_1
6131  }
6132 
6133  # check for and remove any previous timing results in the folder
6134  if {[file exist "$run_folder/timing_ok.txt"]} {
6135  file delete "$run_folder/timing_ok.txt"
6136  }
6137  if {[file exist "$run_folder/timing_error.txt"]} {
6138  file delete "$run_folder/timing_error.txt"
6139  }
6140 
6141  if {[IsISE]} {
6142  source $repo_path/Hog/Tcl/integrated/pre-implementation.tcl
6143  }
6144 
6145  if {$do_bitstream == 1} {
6146  launch_runs impl_1 -to_step [BinaryStepName [get_property PART [current_project]]] -jobs $njobs -dir $run_folder
6147  } else {
6148  launch_runs impl_1 -jobs $njobs -dir $run_folder
6149  }
6150  wait_on_run impl_1
6151 
6152  if {[IsISE]} {
6153  Msg Info "running post-implementation"
6154  source $repo_path/Hog/Tcl/integrated/post-implementation.tcl
6155  if {$do_bitstream == 1} {
6156  Msg Info "running pre-bitstream"
6157  source $repo_path/Hog/Tcl/integrated/pre-bitstream.tcl
6158  Msg Info "running post-bitstream"
6159  source $repo_path/Hog/Tcl/integrated/post-bitstream.tcl
6160  }
6161  }
6162 
6163  set prog [get_property PROGRESS [get_runs impl_1]]
6164  set status [get_property STATUS [get_runs impl_1]]
6165  Msg Info "Run: impl_1 progress: $prog, status : $status"
6166 
6167  # Check timing
6168  if {[IsISE]} {
6169  set status_file [open "$run_folder/timing.txt" "w"]
6170  puts $status_file "## $project_name Timing summary"
6171 
6172  set f [open [lindex [glob "$run_folder/impl_1/*.twr" 0]]]
6173  set errs -1
6174  while {[gets $f line] >= 0} {
6175  if {[string match "Timing summary:" $line]} {
6176  while {[gets $f line] >= 0} {
6177  if {[string match "Timing errors:*" $line]} {
6178  set errs [regexp -inline -- {[0-9]+} $line]
6179  }
6180  if {[string match "*Footnotes*" $line]} {
6181  break
6182  }
6183  puts $status_file "$line"
6184  }
6185  }
6186  }
6187 
6188  close $f
6189  close $status_file
6190 
6191  if {$errs == 0} {
6192  Msg Info "Time requirements are met"
6193  file rename -force "$run_folder/timing.txt" "$run_folder/timing_ok.txt"
6194  set timing_ok 1
6195  } else {
6196  Msg CriticalWarning "Time requirements are NOT met"
6197  file rename -force "$run_folder/timing.txt" "$run_folder/timing_error.txt"
6198  set timing_ok 0
6199  }
6200  }
6201 
6202  if {[IsVivado]} {
6203  set wns [get_property STATS.WNS [get_runs [current_run]]]
6204  set tns [get_property STATS.TNS [get_runs [current_run]]]
6205  set whs [get_property STATS.WHS [get_runs [current_run]]]
6206  set ths [get_property STATS.THS [get_runs [current_run]]]
6207  set tpws [get_property STATS.TPWS [get_runs [current_run]]]
6208 
6209  if {$wns >= 0 && $whs >= 0 && $tpws >= 0} {
6210  Msg Info "Time requirements are met"
6211  set status_file [open "$run_folder/timing_ok.txt" "w"]
6212  set timing_ok 1
6213  } else {
6214  Msg CriticalWarning "Time requirements are NOT met"
6215  set status_file [open "$run_folder/timing_error.txt" "w"]
6216  set timing_ok 0
6217  }
6218 
6219  Msg Status "*** Timing summary ***"
6220  Msg Status "WNS: $wns"
6221  Msg Status "TNS: $tns"
6222  Msg Status "WHS: $whs"
6223  Msg Status "THS: $ths"
6224  Msg Status "TPWS: $tpws"
6225 
6226  struct::matrix m
6227  m add columns 5
6228  m add row
6229 
6230  puts $status_file "## $project_name Timing summary"
6231 
6232  m add row "| **Parameter** | \"**value (ns)**\" |"
6233  m add row "| --- | --- |"
6234  m add row "| WNS: | $wns |"
6235  m add row "| TNS: | $tns |"
6236  m add row "| WHS: | $whs |"
6237  m add row "| THS: | $ths |"
6238  m add row "| TPWS: | $tpws |"
6239 
6240  puts $status_file [m format 2string]
6241  puts $status_file "\n"
6242  if {$timing_ok == 1} {
6243  puts $status_file " Time requirements are met."
6244  } else {
6245  puts $status_file "Time requirements are **NOT** met."
6246  }
6247  puts $status_file "\n\n"
6248  close $status_file
6249  }
6250 
6251  if {$prog ne "100%"} {
6252  Msg Error "Implementation error"
6253  }
6254 
6255  #Go to repository path
6256  cd $repo_path
6257  set describe [GetHogDescribe [file normalize ./Top/$project_name] $repo_path]
6258  Msg Info "Git describe set to $describe"
6259 
6260  set dst_dir [file normalize "$repo_path/bin/$project_name\-$describe"]
6261 
6262  file mkdir $dst_dir
6263 
6264  #Version table
6265  if {[file exists $run_folder/versions.txt]} {
6266  file copy -force $run_folder/versions.txt $dst_dir
6267  } else {
6268  Msg Warning "No versions file found in $run_folder/versions.txt"
6269  }
6270  #Timing file
6271  set timing_files [glob -nocomplain "$run_folder/timing_*.txt"]
6272  set timing_file [file normalize [lindex $timing_files 0]]
6273 
6274  if {[file exists $timing_file]} {
6275  file copy -force $timing_file $dst_dir/
6276  } else {
6277  Msg Warning "No timing file found, not a problem if running locally"
6278  }
6279 
6280  #### XSA here only for Versal Segmented Configuration
6281  if {[IsVersal [get_property part [current_project]]]} {
6282  if {[get_property segmented_configuration [current_project]] == 1} {
6283  Msg Info "Versal Segmented configuration detected: exporting XSA file..."
6284  set xsa_name "$dst_dir/[file tail $project_name]\-$describe.xsa"
6285  write_hw_platform -fixed -force -file $xsa_name
6286  }
6287  }
6288  } elseif {[IsQuartus]} {
6289  set revision [get_current_revision]
6290 
6291  set ic [get_global_assignment -name INCREMENTAL_COMPILATION]
6292  if {$ic ne "" && [string toupper $ic] ne "OFF"} {
6293  Msg Info "Incremental compilation enabled ($ic), running Partition Merge..."
6294  if {[catch {execute_module -tool cdb -args "--merge=on"} result]} {
6295  Msg Error "Result: $result\nPartition Merge failed. See the report file.\n"
6296  }
6297  }
6298 
6299  if {[catch {execute_module -tool fit} result]} {
6300  Msg Error "Result: $result\n"
6301  Msg Error "Place & Route failed. See the report file.\n"
6302  } else {
6303  Msg Info "\nINFO: Place & Route was successful for revision $revision.\n"
6304  }
6305 
6306  if {[catch {execute_module -tool sta -args "--do_report_timing"} result]} {
6307  Msg Error "Result: $result\n"
6308  Msg Error "Time Quest failed. See the report file.\n"
6309  } else {
6310  Msg Info "Time Quest was successfully run for revision $revision.\n"
6311  load_package report
6312  load_report
6313  set panel "Timing Analyzer||Timing Analyzer Summary"
6314  set device [get_report_panel_data -name $panel -col 1 -row_name "Device Name"]
6315  set timing_model [get_report_panel_data -name $panel -col 1 -row_name "Timing Models"]
6316  set delay_model [get_report_panel_data -name $panel -col 1 -row_name "Delay Model"]
6317  #set slack [ get_timing_analysis_summary_results -slack ]
6318  Msg Info "*******************************************************************"
6319  Msg Info "Device: $device"
6320  Msg Info "Timing Models: $timing_model"
6321  Msg Info "Delay Model: $delay_model"
6322  Msg Info "Slack:"
6323  #Msg Info $slack
6324  Msg Info "*******************************************************************"
6325  }
6326  } elseif {[IsLibero]} {
6327  Msg Info "Starting implementation flow..."
6328  if {[catch {run_tool -name {PLACEROUTE}}]} {
6329  Msg Error "PLACEROUTE FAILED!"
6330  } else {
6331  Msg Info "PLACEROUTE PASSED."
6332  }
6333 
6334  # Check timing
6335  Msg Info "Run VERIFYTIMING ..."
6336  if {[catch {run_tool -name {VERIFYTIMING} -script {Hog/Tcl/integrated/libero_timing.tcl}}]} {
6337  Msg CriticalWarning "VERIFYTIMING FAILED!"
6338  } else {
6339  Msg Info "VERIFYTIMING PASSED \n"
6340  }
6341 
6342  #Go to repository path
6343  cd $repo_path
6344 
6345  set describe [GetHogDescribe [file normalize ./Top/$project_name] $repo_path]
6346  Msg Info "Git describe set to $describe"
6347 
6348  set dst_dir [file normalize "$repo_path/bin/$project_name\-$describe"]
6349  file mkdir $dst_dir/reports
6350 
6351  #Version table
6352  if {[file exists $run_folder/versions.txt]} {
6353  file copy -force $run_folder/versions.txt $dst_dir
6354  } else {
6355  Msg Warning "No versions file found in $run_folder/versions.txt"
6356  }
6357  #Timing file
6358  set timing_file_path [file normalize "$repo_path/Projects/timing_libero.txt"]
6359  if {[file exists $timing_file_path]} {
6360  file copy -force $timing_file_path $dst_dir/reports/Timing.txt
6361  set timing_file [open $timing_file_path "r"]
6362  set status_file [open "$dst_dir/timing.txt" "w"]
6363  puts $status_file "## $project_name Timing summary\n\n"
6364  puts $status_file "| | |"
6365  puts $status_file "| --- | --- |"
6366  while {[gets $timing_file line] >= 0} {
6367  if {[string match "SUMMARY" $line]} {
6368  while {[gets $timing_file line] >= 0} {
6369  if {[string match "END SUMMARY" $line]} {
6370  break
6371  }
6372  if {[string first ":" $line] == -1} {
6373  continue
6374  }
6375  set out_string "| [string map {: | } $line] |"
6376  puts $status_file "$out_string"
6377  }
6378  }
6379  }
6380  } else {
6381  Msg Warning "No timing file found, not a problem if running locally"
6382  }
6383  } elseif {[IsDiamond]} {
6384  set force_rst ""
6385  if {$reset == 1} {
6386  set force_rst "-forceOne"
6387  }
6388  prj_run Map $force_rst
6389  prj_run PAR $force_rst
6390 
6391  # TODO: Check Timing for Diamond
6392  }
6393 }
6394 
6395 # @brief Re-generate the bitstream, for the current IDE and project (Vivado only for the moment). \
6396 # Useful for a Vivado-Vitis project to update the bitstream with a new ELF or to generate a new \
6397 # bootimage (ZYNQ) without running the full workflow.
6398 #
6399 # @param[in] project_name The name of the project
6400 # @param[in] repo_path The main path of the git repository (Default .)
6401 proc GenerateBitstreamOnly {project_name {repo_path .}} {
6402  cd $repo_path
6403 
6404  # Open the project first
6405  set project_file [file normalize "$repo_path/Projects/$project_name/$project_name.xpr"]
6406  if {![file exists $project_file]} {
6407  Msg Error "Project file not found: $project_file. Please create the project first."
6408  return
6409  }
6410 
6411 
6412  # Check if impl_1 run exists
6413  set impl_runs [get_runs -quiet impl_1]
6414  if {[llength $impl_runs] == 0} {
6415  Msg Error "Implementation run 'impl_1' does not exist. Please run implementation first."
6416  return
6417  }
6418 
6419  # The binary file is written in the standard implementation run directory,
6420  # the post-bitstream script will then copy it (and all the other artifacts) into the bin directory
6421  set run_dir [get_property DIRECTORY [get_runs impl_1]]
6422  if {![file exists $run_dir]} {
6423  Msg Error "Implementation run directory not found: $run_dir. Please check your implementation run."
6424  return
6425  }
6426  cd $run_dir
6427 
6428  Msg Info "Running pre-bitstream..."
6429  source $repo_path/Hog/Tcl/integrated/pre-bitstream.tcl
6430 
6431  Msg Info "Writing bitstream for $project_name..."
6432  open_run impl_1
6433  set top_name [GetTopModule]
6434  write_bitstream -force [file normalize "$run_dir/$top_name.bit"]
6435 
6436  Msg Info "Running post-bitstream..."
6437  source $repo_path/Hog/Tcl/integrated/post-bitstream.tcl
6438 }
6439 
6440 # @brief Launch the simulation (Vivado only for the moment)
6441 #
6442 # @param[in] project_name The name of the project
6443 # @param[in] lib_path The path to the simulation libraries
6444 # @param[in] simsets The simulation sets to simulate
6445 # @param[in] repo_path The main path of the git repository
6446 proc LaunchSimulation {project_name lib_path simsets {repo_path .} {scripts_only 0} {compile_only 0}} {
6447  if {[IsVivado]} {
6448  ##################### SIMULATION #######################
6449  set project [file tail $project_name]
6450  set main_sim_folder [file normalize "$repo_path/Projects/$project_name/$project.sim/"]
6451  set simsets_todo ""
6452  if {$simsets != ""} {
6453  dict for {simset sim_dict} $simsets {
6454  lappend simsets_todo $simset
6455  }
6456  Msg Info "Will run only the following simulation's sets (if they exist): $simsets_todo"
6457  }
6458 
6459  if {$scripts_only == 1} {
6460  Msg Info "Only generating simulation scripts, not running simulations..."
6461  }
6462 
6463  if {$compile_only == 1} {
6464  Msg Info "Only compiling simulation libraries, not running simulations..."
6465  }
6466 
6467  set failed []
6468  set success []
6469  set sim_dic [dict create]
6470 
6471  Msg Info "Retrieving list of simulation sets..."
6472  foreach s [get_filesets] {
6473  # Default behaviour, dont use simpass string and simulation is not quiet
6474  set use_simpass_str 0
6475  set quiet_sim ""
6476 
6477  set type [get_property FILESET_TYPE $s]
6478  if {$type eq "SimulationSrcs"} {
6479  if {$simsets_todo != "" && $s ni $simsets_todo} {
6480  Msg Info "Skipping $s as it was not specified with the -simset option..."
6481  continue
6482  }
6483  set sim_dict [DictGet $simsets $s]
6484  set simulator [DictGet $sim_dict "simulator"]
6485  set_property "target_simulator" $simulator [current_project]
6486  set hog_sim_props [DictGet $sim_dict "hog"]
6487  dict for {prop_name prop_val} $hog_sim_props {
6488  # If HOG_SIMPASS_STR is set, use the HOG_SIMPASS_STR string to search for in logs, after simulation is done
6489  if {[string toupper $prop_name] == "HOG_SIMPASS_STR" && $prop_val != ""} {
6490  Msg Info "Setting simulation pass string as '$prop_val'"
6491  set use_simpass_str 1
6492  set simpass_str $prop_val
6493  }
6494  if {[string toupper $prop_name] == "HOG_SILENT_SIM" && $prop_val == 1} {
6495  set quiet_sim " -quiet"
6496  } else {
6497  set quiet_sim ""
6498  }
6499  }
6500 
6501  Msg Info "Creating simulation scripts for $s..."
6502  if {[file exists $repo_path/Top/$project_name/pre-simulation.tcl]} {
6503  Msg Info "Running $repo_path/Top/$project_name/pre-simulation.tcl"
6504  source $repo_path/Top/$project_name/pre-simulation.tcl
6505  }
6506  if {[file exists $repo_path/Top/$project_name/pre-$s-simulation.tcl]} {
6507  Msg Info "Running $repo_path/Top/$project_name/pre-$s-simulation.tcl"
6508  source $repo_path/Top/$project_name/pre-$s-simulation.tcl
6509  }
6510  current_fileset -simset $s
6511  set sim_dir $main_sim_folder/$s/behav
6512  set sim_output_logfile $sim_dir/xsim/simulate.log
6513  if {([string tolower $simulator] eq "xsim")} {
6514  set sim_name "xsim:$s"
6515 
6516  set simulation_command "launch_simulation $quiet_sim -simset [get_filesets $s]"
6517  if {[catch $simulation_command log]} {
6518  # Explicitly close xsim simulation, without closing Vivado
6519  close_sim
6520  Msg CriticalWarning "Simulation failed for $s, error info: $::errorInfo"
6521  lappend failed $sim_name
6522  } else {
6523  # Explicitly close xsim simulation, without closing Vivado
6524  close_sim
6525  # If we use simpass_str, search for the string and update return code from simulation if the string is not found in simulation log
6526  if {$use_simpass_str == 1} {
6527  # Get the simulation output log
6528  # Note, xsim should always output simulation.log, hence no check for existence
6529  set file_desc [open $sim_output_logfile r]
6530  set log [read $file_desc]
6531  close $file_desc
6532 
6533  Msg Info "Searching for simulation pass string: '$simpass_str'"
6534  if {[string first $simpass_str $log] == -1} {
6535  Msg CriticalWarning "Simulation failed for $s, error info: '$simpass_str' NOT found!"
6536  lappend failed $sim_name
6537  } else {
6538  # HOG_SIMPASS_STR found, success
6539  lappend success $sim_name
6540  }
6541  } else {
6542  #Rely on simulator exit code
6543  lappend success $sim_name
6544  }
6545  }
6546  } else {
6547  Msg Info "Simulation library path is set to $lib_path."
6548  set simlib_ok 1
6549  if {!([file exists $lib_path])} {
6550  Msg Warning "Could not find simulation library path: $lib_path, $simulator simulation will not work."
6551  set simlib_ok 0
6552  }
6553 
6554  if {$simlib_ok == 1} {
6555  set_property "compxlib.${simulator}_compiled_library_dir" [file normalize $lib_path] [current_project]
6556  launch_simulation -scripts_only -simset [get_filesets $s]
6557  set top_name [get_property TOP $s]
6558  set sim_script [file normalize $sim_dir/$simulator/]
6559  Msg Info "Adding simulation script location $sim_script for $s..."
6560  lappend sim_scripts $sim_script
6561  dict append sim_dic $sim_script $s
6562  } else {
6563  Msg Error "Cannot run $simulator simulations without a valid library path"
6564  exit -1
6565  }
6566  }
6567  }
6568  }
6569 
6570  if {[info exists sim_scripts] && $scripts_only == 0} {
6571  # Only for modelsim/questasim
6572  Msg Info "Generating IP simulation targets, if any..."
6573 
6574  foreach ip [get_ips] {
6575  generate_target simulation -quiet $ip
6576  }
6577 
6578 
6579  Msg Status "\n\n"
6580  Msg Info "====== Starting simulations runs ======"
6581  Msg Status "\n\n"
6582 
6583  foreach s $sim_scripts {
6584  cd $s
6585  set cmd ./compile.sh
6586  Msg Info " ************* Compiling: $s ************* "
6587  lassign [ExecuteRet $cmd] ret log
6588  set sim_name "comp:[dict get $sim_dic $s]"
6589  if {$ret != 0} {
6590  Msg CriticalWarning "Compilation failed for $s, error info: $::errorInfo"
6591  lappend failed $sim_name
6592  } else {
6593  lappend success $sim_name
6594  }
6595  Msg Info "###################### Compilation log starts ######################"
6596  Msg Info "\n\n$log\n\n"
6597  Msg Info "###################### Compilation log ends ######################"
6598 
6599  if {$compile_only == 1} {
6600  continue
6601  }
6602  if {[file exists "./elaborate.sh"]} {
6603  set cmd ./elaborate.sh
6604  Msg Info " ************* Elaborating: $s ************* "
6605  lassign [ExecuteRet $cmd] ret log
6606  set sim_name "elab:[dict get $sim_dic $s]"
6607  if {$ret != 0} {
6608  Msg CriticalWarning "Elaboration failed for $s, error info: $::errorInfo"
6609  lappend failed $sim_name
6610  } else {
6611  lappend success $sim_name
6612  }
6613  Msg Info "###################### Elaboration log starts ######################"
6614  Msg Info "\n\n$log\n\n"
6615  Msg Info "###################### Elaboration log ends ######################"
6616  }
6617  set cmd ./simulate.sh
6618  Msg Info " ************* Simulating: $s ************* "
6619  lassign [ExecuteRet $cmd] ret log
6620 
6621 
6622  # If SIMPASS_STR is set, search log for the string
6623  if {$use_simpass_str == 1} {
6624  if {[string first $simpass_str $log] == -1} {
6625  set ret 1
6626  }
6627  } else {
6628  Msg Debug "Simulation pass string not set, relying on simulator exit code."
6629  }
6630 
6631 
6632  set sim_name "sim:[dict get $sim_dic $s]"
6633  if {$ret != 0} {
6634  Msg CriticalWarning "Simulation failed for $s, error info: $::errorInfo"
6635  lappend failed $sim_name
6636  } else {
6637  lappend success $sim_name
6638  }
6639  Msg Info "###################### Simulation log starts ######################"
6640  Msg Info "\n\n$log\n\n"
6641  Msg Info "###################### Simulation log ends ######################"
6642  }
6643  }
6644 
6645 
6646  if {[llength $success] > 0} {
6647  set successes [join $success "\n"]
6648  Msg Info "The following simulation sets were successful:\n\n$successes\n\n"
6649  }
6650 
6651  if {[llength $failed] > 0} {
6652  set failures [join $failed "\n"]
6653  Msg Error "The following simulation sets have failed:\n\n$failures\n\n"
6654  exit -1
6655  } elseif {[llength $success] > 0} {
6656  Msg Info "All the [llength $success] compilations, elaborations and simulations were successful."
6657  }
6658 
6659  Msg Info "Simulation done."
6660  } else {
6661  Msg Warning "Simulation is not yet supported for [GetIDEName]."
6662  }
6663 }
6664 
6665 # @brief Launch the RTL Analysis, for the current IDE and project
6666 #
6667 # @param[in] repo_path The main path of the git repository (Default .)
6668 proc LaunchRTLAnalysis {repo_path {pre_rtl_file ""} {post_rtl_file ""}} {
6669  if {[IsVivado]} {
6670  if {[file exists $pre_rtl_file]} {
6671  Msg Info "Found pre-rtl Tcl script $pre_rtl_file, executing it..."
6672  source $pre_rtl_file
6673  }
6674  Msg Info "Starting RTL Analysis..."
6675  cd $repo_path
6676  synth_design -rtl -name rtl_1
6677  if {[file exists $post_rtl_file]} {
6678  Msg Info "Found post-rtl Tcl script $post_rtl_file, executing it..."
6679  source $post_rtl_file
6680  }
6681  } else {
6682  Msg Warning "RTL Analysis is not yet supported for [GetIDEName]."
6683  }
6684 }
6685 
6686 # @brief Launch the synthesis, for the current IDE and project
6687 #
6688 # @param[in] reset Reset the Synthesis run
6689 # @param[in] do_create Recreate the project
6690 # @param[in] run_folder The folder where to store the run results
6691 # @param[in] project_name The name of the project
6692 # @param[in] repo_path The main path of the git repository (Default .)
6693 # @param[in] ext_path The path of source files external to the git repo (Default "")
6694 # @param[in] njobs The number of parallel CPU jobs for the synthesis (Default 4)
6695 proc LaunchSynthesis {reset do_create run_folder project_name {repo_path .} {ext_path ""} {njobs 4}} {
6696  if {[IsXilinx]} {
6697  if {$reset == 1} {
6698  Msg Info "Resetting run before launching synthesis..."
6699  reset_run synth_1
6700  }
6701  if {[IsISE]} {
6702  source $repo_path/Hog/Tcl/integrated/pre-synthesis.tcl
6703  }
6704  launch_runs synth_1 -jobs $njobs -dir $run_folder
6705  wait_on_run synth_1
6706  set prog [get_property PROGRESS [get_runs synth_1]]
6707  set status [get_property STATUS [get_runs synth_1]]
6708  Msg Info "Run: synth_1 progress: $prog, status : $status"
6709  # Copy IP reports in bin/
6710  set ips [get_ips *]
6711 
6712  #go to repository path
6713  cd $repo_path
6714 
6715  set describe [GetHogDescribe [file normalize ./Top/$project_name] $repo_path]
6716  Msg Info "Git describe set to $describe"
6717 
6718  foreach ip $ips {
6719  set xci_file [get_property IP_FILE $ip]
6720 
6721  set xci_path [file dirname $xci_file]
6722  set xci_ip_name [file rootname [file tail $xci_file]]
6723  foreach rptfile [glob -nocomplain -directory $xci_path *.rpt] {
6724  file copy $rptfile $repo_path/bin/$project_name-$describe/reports
6725  }
6726  }
6727 
6728  if {$prog ne "100%"} {
6729  Msg Error "Synthesis error, status is: $status"
6730  }
6731  } elseif {[IsQuartus]} {
6732  # TODO: Missing reset
6733  set project [file tail [file rootname $project_name]]
6734 
6735  Msg Info "Number of jobs set to $njobs."
6736  set_global_assignment -name NUM_PARALLEL_PROCESSORS $njobs
6737 
6738 
6739  # keep track of the current revision and of the top level entity name
6740  set describe [GetHogDescribe [file normalize $repo_path/Top/$project_name] $repo_path]
6741  #set top_level_name [ get_global_assignment -name TOP_LEVEL_ENTITY ]
6742  set revision [get_current_revision]
6743 
6744  #run PRE_FLOW_SCRIPT by hand
6745  set tool_and_command [split [get_global_assignment -name PRE_FLOW_SCRIPT_FILE] ":"]
6746  set tool [lindex $tool_and_command 0]
6747  set pre_flow_script [lindex $tool_and_command 1]
6748  set cmd "$tool -t $pre_flow_script quartus_map $project $revision"
6749  #Close project to avoid conflict with pre synthesis script
6750  project_close
6751 
6752  lassign [ExecuteRet {*}$cmd] ret log
6753  if {$ret != 0} {
6754  Msg Warning "Can not execute command $cmd"
6755  Msg Warning "LOG: $log"
6756  } else {
6757  Msg Info "Pre flow script executed!"
6758  Msg Info "Pre flow script log: \n$log"
6759  }
6760 
6761  # Re-open project
6762  if {![is_project_open]} {
6763  Msg Info "Re-opening project file $project_name..."
6764  project_open $project -current_revision
6765  }
6766 
6767  # Generate IP Files
6768  if {[catch {execute_module -tool ipg -args "--clean"} result]} {
6769  Msg Error "Result: $result\n"
6770  Msg Error "IP Generation failed. See the report file.\n"
6771  } else {
6772  Msg Info "IP Generation was successful for revision $revision.\n"
6773  }
6774 
6775  # Execute synthesis
6776  if {[catch {execute_module -tool map -args "--parallel"} result]} {
6777  Msg Error "Result: $result\n"
6778  Msg Error "Analysis & Synthesis failed. See the report file.\n"
6779  } else {
6780  Msg Info "Analysis & Synthesis was successful for revision $revision.\n"
6781  }
6782  } elseif {[IsLibero]} {
6783  # TODO: Missing Reset
6784  defvar_set -name RWNETLIST_32_64_MIXED_FLOW -value 0
6785 
6786  Msg Info "Run SYNTHESIS..."
6787  if {[catch {run_tool -name {SYNTHESIZE}}]} {
6788  Msg Error "SYNTHESIZE FAILED!"
6789  } else {
6790  Msg Info "SYNTHESIZE PASSED!"
6791  }
6792  } elseif {[IsDiamond]} {
6793  # TODO: Missing Reset
6794  set force_rst ""
6795  if {$reset == 1} {
6796  set force_rst "-forceOne"
6797  }
6798  prj_run Synthesis $force_rst
6799  if {[prj_syn] == "synplify"} {
6800  prj_run Translate $force_rst
6801  }
6802  } else {
6803  Msg Error "Impossible condition. You need to run this in an IDE."
6804  exit 1
6805  }
6806 }
6807 
6808 
6809 # @brief Launch the Vitis build
6810 #
6811 # @param[in] project_name The name of the project
6812 # @param[in] repo_path The main path of the git repository (Default ".")
6813 # @param[in] stage The stage of the build (Default "presynth")
6814 proc LaunchVitisBuild {project_name {repo_path .} {stage "presynth"}} {
6815  set proj_name $project_name
6816  set bin_dir [file normalize "$repo_path/bin"]
6817 
6818  cd $repo_path
6819 
6820  # Get app list
6821  if {[IsVitisUnified]} {
6822  set vitis_workspace [file normalize "$repo_path/Projects/$project_name/vitis_unified"]
6823  set python_script [file normalize "$repo_path/Hog/Other/Python/VitisUnified/AppCommands.py"]
6824  set ws_apps [GetVitisApps $vitis_workspace $python_script]
6825  } elseif {[IsVitisClassic]} {
6826  set ws_apps [GetVitisApps]
6827  } else {
6828  Msg Error "Impossible condition. You need to run this in a Vitis Unified or Vitis Classic IDE."
6829  exit 1
6830  }
6831 
6832  # Get repository versions
6833  lassign [GetRepoVersions [file normalize $repo_path/Top/$proj_name] $repo_path] commit version hog_hash hog_ver top_hash top_ver \
6834  libs hashes vers cons_ver cons_hash ext_names ext_hashes xml_hash xml_ver user_ip_repos user_ip_hashes user_ip_vers \
6835  cheby_hash cheby_ver
6836  set this_commit [GetSHA]
6837  if {$commit == 0} {set commit $this_commit}
6838  set flavour [GetProjectFlavour $project_name]
6839  lassign [GetDateAndTime $commit] date timee
6840 
6841  # Configure apps only for Vitis Classic, build-config is not supported in Vitis Unified
6842  # build directory seems to be the default
6843  if {[IsVitisClassic]} {
6844  foreach app_name [dict keys $ws_apps] {
6845  app config -name $app_name -set build-config Release
6846  }
6847  }
6848 
6849  WriteGenerics "vitisbuild" $repo_path $proj_name $date $timee $commit $version $top_hash $top_ver $hog_hash $hog_ver $cons_ver $cons_hash $libs \
6850  $vers $hashes $ext_names $ext_hashes $user_ip_repos $user_ip_vers $user_ip_hashes $flavour $xml_ver $xml_hash \
6851  $cheby_ver $cheby_hash
6852 
6853  # Build apps
6854  foreach app_name [dict keys $ws_apps] {
6855  if {[IsVitisUnified]} {
6856  # Build vitis unified app
6857  if {![ExecuteVitisUnifiedCommand $python_script "build_app" [list $app_name $vitis_workspace] "Failed to build app $app_name"]} {
6858  Msg Error "Failed to build app $app_name"
6859  continue
6860  }
6861  } elseif {[IsVitisClassic]} {
6862  app build -name $app_name
6863  }
6864  }
6865 
6866  if {$stage == "presynth"} {
6867  Msg Info "Done building apps for $project_name..."
6868  }
6869 
6870  if {[info exists ::globalSettings::vitis_only_pass] && $::globalSettings::vitis_only_pass == 1} {
6871  Msg Info "Skipping bin directory creation in vitis_only mode (post-bitstream.tcl handles artifacts)."
6872  return
6873  }
6874 
6875  Msg Info "Evaluating Hog describe for $project_name..."
6876  set describe [GetHogDescribe [file normalize ./Top/$project_name] $repo_path]
6877  Msg Info "Hog describe set to: $describe"
6878  set dst_dir [file normalize "$bin_dir/$proj_name\-$describe"]
6879  if {![file exists $dst_dir]} {
6880  Msg Info "Creating $dst_dir..."
6881  file mkdir $dst_dir
6882  }
6883 
6884  foreach app_name [dict keys $ws_apps] {
6885  if {[IsVitisUnified]} {
6886  set main_file "$repo_path/Projects/$project_name/vitis_unified/$app_name/build/$app_name.elf"
6887  } elseif {[IsVitisClassic]} {
6888  set main_file "$repo_path/Projects/$project_name/vitis_classic/$app_name/Release/$app_name.elf"
6889  }
6890  set dst_main [file normalize "$dst_dir/[file tail $proj_name]\-$app_name\-$describe.elf"]
6891 
6892  if {![file exists $main_file]} {
6893  Msg Error "No Vitis .elf file found. Perhaps there was an issue building it?"
6894  continue
6895  }
6896 
6897  Msg Info "Copying main binary file $main_file into $dst_main..."
6898  file copy -force $main_file $dst_main
6899  }
6900 }
6901 
6902 # @brief Launch the HLS build for all [hls:*] components in the project
6903 #
6904 # For each HLS component defined in hog.conf, this proc runs:
6905 # - C synthesis
6906 # - Implementation
6907 # - Collects reports (.rpt, .xml, .log) into bin/ for CI
6908 # Packaging is controlled by hls_config.cfg (package.output.format, etc.)
6909 # Simulations (CSIM/COSIM) are handled by LaunchHlsSimulation via the S command.
6910 #
6911 # @param[in] project_name The name of the project
6912 # @param[in] repo_path The main path of the git repository (Default ".")
6913 proc LaunchHlsBuild {project_name {repo_path .}} {
6914  set proj_name $project_name
6915  set bin_dir [file normalize "$repo_path/bin"]
6916 
6917  cd $repo_path
6918 
6919  set conf_file [file normalize "$repo_path/Top/$project_name/hog.conf"]
6920  if {![file exists $conf_file]} {
6921  Msg Error "Configuration file not found: $conf_file"
6922  return
6923  }
6924  set properties [ReadConf $conf_file]
6925  set hls_components [dict filter $properties key {hls:*}]
6926 
6927  if {[dict size $hls_components] == 0} {
6928  Msg Info "No HLS components found for project $project_name"
6929  return
6930  }
6931 
6932  set python_script [file normalize "$repo_path/Hog/Other/Python/VitisUnified/HlsCommands.py"]
6933 
6934  # Determine whether this is a mixed Vivado+Vitis project. In that case HLS
6935  # component outputs are grouped under bin/<proj>/vitis_hls/ to keep them
6936  # visually separate from Vivado's top-level utilization.txt / timing_*.txt.
6937  # For pure vitis_unified projects (no Vivado) HLS outputs sit directly under
6938  # bin/<proj>/<component>/ since there's nothing else to collide with.
6939  set ide_info [GetIDEFromConf $conf_file]
6940  set ide_name [string tolower [lindex $ide_info 0]]
6941  if {$ide_name eq "vivado_vitis_unified"} {
6942  set is_mixed_project 1
6943  } else {
6944  set is_mixed_project 0
6945  }
6946 
6947  dict for {hls_key hls_props} $hls_components {
6948  if {![regexp {^hls:(.+)$} $hls_key -> component_name]} {
6949  continue
6950  }
6951  set component_name [string trim $component_name]
6952  Msg Info "Building HLS component: $component_name"
6953 
6954  # Read HLS_CONFIG path from hog.conf (mandatory)
6955  set hls_cfg_rel ""
6956  if {[dict exists $hls_props hls_config]} {
6957  set hls_cfg_rel [dict get $hls_props hls_config]
6958  } elseif {[dict exists $hls_props HLS_CONFIG]} {
6959  set hls_cfg_rel [dict get $hls_props HLS_CONFIG]
6960  }
6961  if {$hls_cfg_rel eq ""} {
6962  Msg Error "HLS component '$component_name' missing HLS_CONFIG in hog.conf"
6963  continue
6964  }
6965 
6966  set cfg_file [file normalize "$repo_path/$hls_cfg_rel"]
6967  if {![file exists $cfg_file]} {
6968  Msg Error "HLS config file not found: $cfg_file (from HLS_CONFIG=$hls_cfg_rel)"
6969  continue
6970  }
6971 
6972  set hls_work_dir [file normalize "$repo_path/Projects/$project_name/vitis_unified/$component_name"]
6973  file mkdir $hls_work_dir
6974 
6975  # C synthesis
6976  Msg Info "Running C synthesis for HLS component '$component_name'..."
6977  if {
6978  ![ExecuteVitisUnifiedCommand $python_script "synthesis" \
6979  [list $component_name $cfg_file $hls_work_dir] \
6980  "Failed to run C synthesis for $component_name"]
6981  } {
6982  Msg Error "C synthesis failed for HLS component '$component_name'"
6983  continue
6984  }
6985 
6986  # Implementation
6987  Msg Info "Running implementation for HLS component '$component_name'..."
6988  if {
6989  ![ExecuteVitisUnifiedCommand $python_script "impl" \
6990  [list $component_name $cfg_file $hls_work_dir] \
6991  "Failed to run implementation for $component_name"]
6992  } {
6993  Msg Error "Implementation failed for HLS component '$component_name'"
6994  continue
6995  }
6996 
6997  # Export VHDL to source tree if VHDL_OUTPUT is set
6998  set vhdl_output ""
6999  if {[dict exists $hls_props vhdl_output]} {
7000  set vhdl_output [dict get $hls_props vhdl_output]
7001  } elseif {[dict exists $hls_props VHDL_OUTPUT]} {
7002  set vhdl_output [dict get $hls_props VHDL_OUTPUT]
7003  }
7004  if {$vhdl_output ne ""} {
7005  set vhdl_output_dir [file normalize "$repo_path/$vhdl_output"]
7006  Msg Info "Exporting VHDL for '$component_name' to $vhdl_output_dir..."
7007  if {
7008  ![ExecuteVitisUnifiedCommand $python_script "export_rtl" \
7009  [list $component_name $hls_work_dir $vhdl_output_dir vhdl] \
7010  "Failed to export VHDL for $component_name"]
7011  } {
7012  Msg Warning "Could not export VHDL for HLS component '$component_name'"
7013  }
7014  }
7015 
7016  # Export Verilog to source tree if VERILOG_OUTPUT is set
7017  set verilog_output ""
7018  if {[dict exists $hls_props verilog_output]} {
7019  set verilog_output [dict get $hls_props verilog_output]
7020  } elseif {[dict exists $hls_props VERILOG_OUTPUT]} {
7021  set verilog_output [dict get $hls_props VERILOG_OUTPUT]
7022  }
7023  if {$verilog_output ne ""} {
7024  set verilog_output_dir [file normalize "$repo_path/$verilog_output"]
7025  Msg Info "Exporting Verilog for '$component_name' to $verilog_output_dir..."
7026  if {
7027  ![ExecuteVitisUnifiedCommand $python_script "export_rtl" \
7028  [list $component_name $hls_work_dir $verilog_output_dir verilog] \
7029  "Failed to export Verilog for $component_name"]
7030  } {
7031  Msg Warning "Could not export Verilog for HLS component '$component_name'"
7032  }
7033  }
7034 
7035  # Export IP catalog ZIP to source tree if IP_OUTPUT is set
7036  set ip_output ""
7037  if {[dict exists $hls_props ip_output]} {
7038  set ip_output [dict get $hls_props ip_output]
7039  } elseif {[dict exists $hls_props IP_OUTPUT]} {
7040  set ip_output [dict get $hls_props IP_OUTPUT]
7041  }
7042  if {$ip_output ne ""} {
7043  set ip_output_dir [file normalize "$repo_path/$ip_output"]
7044  Msg Info "Exporting IP catalog for '$component_name' to $ip_output_dir..."
7045  if {
7046  ![ExecuteVitisUnifiedCommand $python_script "export_ip" \
7047  [list $component_name $hls_work_dir $ip_output_dir] \
7048  "Failed to export IP for $component_name"]
7049  } {
7050  Msg Error "Could not export IP for HLS component '$component_name'. Make sure package.output.format=ip_catalog is set in hls_config.cfg."
7051  }
7052  }
7053 
7054  # Collect reports into bin/ for CI and release notes
7055  Msg Info "Evaluating Hog describe for $project_name..."
7056  set describe [GetHogDescribe [file normalize ./Top/$project_name] $repo_path]
7057  Msg Info "Hog describe set to: $describe"
7058  set dst_dir [file normalize "$bin_dir/$proj_name\-$describe"]
7059  if {![file exists $dst_dir]} {
7060  Msg Info "Creating $dst_dir..."
7061  file mkdir $dst_dir
7062  }
7063 
7064  # Mixed projects group HLS components under bin/<proj>/vitis_hls/; pure
7065  # vitis_unified projects place them directly under bin/<proj>/
7066  if {$is_mixed_project} {
7067  set hls_out_dir [file normalize "$dst_dir/vitis_hls"]
7068  } else {
7069  set hls_out_dir $dst_dir
7070  }
7071 
7072  Msg Info "Collecting HLS reports for component '$component_name'..."
7073  if {
7074  ![ExecuteVitisUnifiedCommand $python_script "collect_reports" \
7075  [list $component_name $hls_work_dir $hls_out_dir] \
7076  "Failed to collect HLS reports for $component_name"]
7077  } {
7078  Msg Warning "No reports found for HLS component '$component_name'"
7079  }
7080 
7081  # Generate markdown summary files (utilization.txt, timing_ok.txt /
7082  # timing_error.txt) under <hls_out_dir>/<component>/. File names mirror the
7083  # Vivado convention so the existing CI logic (release notes assembly and
7084  # timing-failure detection) works for HLS components too
7085  Msg Info "Generating HLS release-notes summary for component '$component_name'..."
7086  if {
7087  ![ExecuteVitisUnifiedCommand $python_script "release_notes" \
7088  [list $component_name $hls_work_dir $hls_out_dir] \
7089  "Failed to generate HLS summary for $component_name"]
7090  } {
7091  Msg Warning "Could not generate HLS summary for component '$component_name'"
7092  }
7093 
7094  Msg Info "HLS component '$component_name' built successfully"
7095  }
7096 
7097  # For pure vitis_unified (HLS-only) projects, emit a top-level versions.txt
7098  # at the bin project root
7099  if {!$is_mixed_project && [info exists dst_dir] && [file isdirectory $dst_dir]} {
7100  set versions_file [file normalize "$dst_dir/versions.txt"]
7101  Msg Info "Generating top-level versions.txt for pure-HLS project at $versions_file"
7102  if {
7103  [catch {
7104  lassign [GetRepoVersions [file normalize $repo_path/Top/$project_name] $repo_path] \
7105  commit version hog_hash hog_ver top_hash top_ver \
7106  libs hashes vers cons_ver cons_hash \
7107  ext_names ext_hashes xml_hash xml_ver \
7108  user_ip_repos user_ip_hashes user_ip_vers \
7109  cheby_hash cheby_ver
7110  if {$commit == 0} {set commit [GetSHA]}
7111  set version_str [HexVersionToString $version]
7112  set hog_ver_str [HexVersionToString $hog_ver]
7113  set top_ver_str [HexVersionToString $top_ver]
7114  set fh [open $versions_file "w"]
7115  puts $fh "## $proj_name Version Table\n"
7116  puts $fh "| **File set** | **Commit SHA** | **Version** |"
7117  puts $fh "| --- | --- | --- |"
7118  puts $fh "| Global | $commit | $version_str |"
7119  puts $fh "| Top Directory | $top_hash | $top_ver_str |"
7120  puts $fh "| Hog | $hog_hash | $hog_ver_str |"
7121  foreach l $libs v $vers h $hashes {
7122  set v_str [HexVersionToString $v]
7123  puts $fh "| **Lib:** $l | $h | $v_str |"
7124  }
7125  puts $fh "\n"
7126  close $fh
7127  } err]
7128  } {
7129  Msg Warning "Could not generate top-level versions.txt for pure-HLS project: $err"
7130  }
7131  }
7132 
7133  Msg Info "Done building HLS components for $project_name"
7134 }
7135 
7136 
7137 # @brief Launch HLS simulations for [hls:*] components in the project
7138 #
7139 # When called without hls_simsets (or with an empty list), runs all enabled
7140 # HLS simulations (CSIM if CSIM=true, COSIM if COSIM=true in hog.conf).
7141 # When called with specific targets (e.g. "csim:iir_lp_filter cosim:iir_lp_filter"),
7142 # runs only the requested simulations.
7143 # COSIM automatically triggers C synthesis first if not already done.
7144 # Triggered by the S (SIMULATE) command.
7145 #
7146 # @param[in] project_name The name of the project
7147 # @param[in] repo_path The main path of the git repository (Default ".")
7148 # @param[in] hls_simsets Optional list of specific HLS simsets to run (e.g. "csim:name" "cosim:name")
7149 proc LaunchHlsSimulation {project_name {repo_path .} {hls_simsets {}}} {
7150  cd $repo_path
7151 
7152  set conf_file [file normalize "$repo_path/Top/$project_name/hog.conf"]
7153  if {![file exists $conf_file]} {
7154  Msg Error "Configuration file not found: $conf_file"
7155  return
7156  }
7157  set properties [ReadConf $conf_file]
7158  set hls_components [dict filter $properties key {hls:*}]
7159 
7160  if {[dict size $hls_components] == 0} {
7161  Msg Info "No HLS components found for project $project_name"
7162  return
7163  }
7164 
7165  # Build a lookup of requested targets: component_name -> list of sim types
7166  # If hls_simsets is empty, we'll use hog.conf flags for all components
7167  set targeted_mode [expr {[llength $hls_simsets] > 0}]
7168  set targets [dict create]
7169  foreach entry $hls_simsets {
7170  if {[regexp {^(csim|cosim):(.+)$} $entry -> sim_type comp_name]} {
7171  dict lappend targets $comp_name $sim_type
7172  } else {
7173  Msg Error "Invalid HLS simset format '$entry'. Expected 'csim:<component>' or 'cosim:<component>'."
7174  Msg Info "Available HLS components in hog.conf:"
7175  dict for {hls_key hls_props} $hls_components {
7176  if {[regexp {^hls:(.+)$} $hls_key -> cname]} {
7177  Msg Info " - csim:[string trim $cname]"
7178  Msg Info " - cosim:[string trim $cname]"
7179  }
7180  }
7181  return
7182  }
7183  }
7184 
7185  set python_script [file normalize "$repo_path/Hog/Other/Python/VitisUnified/HlsCommands.py"]
7186 
7187  dict for {hls_key hls_props} $hls_components {
7188  if {![regexp {^hls:(.+)$} $hls_key -> component_name]} {
7189  continue
7190  }
7191  set component_name [string trim $component_name]
7192 
7193  # In targeted mode, skip components not in the request
7194  if {$targeted_mode && ![dict exists $targets $component_name]} {
7195  continue
7196  }
7197 
7198  # Read HLS_CONFIG path from hog.conf (mandatory)
7199  set hls_cfg_rel ""
7200  if {[dict exists $hls_props hls_config]} {
7201  set hls_cfg_rel [dict get $hls_props hls_config]
7202  } elseif {[dict exists $hls_props HLS_CONFIG]} {
7203  set hls_cfg_rel [dict get $hls_props HLS_CONFIG]
7204  }
7205  if {$hls_cfg_rel eq ""} {
7206  Msg Error "HLS component '$component_name' missing HLS_CONFIG in hog.conf"
7207  continue
7208  }
7209 
7210  set cfg_file [file normalize "$repo_path/$hls_cfg_rel"]
7211  if {![file exists $cfg_file]} {
7212  Msg Error "HLS config file not found: $cfg_file (from HLS_CONFIG=$hls_cfg_rel)"
7213  continue
7214  }
7215 
7216  set hls_work_dir [file normalize "$repo_path/Projects/$project_name/vitis_unified/$component_name"]
7217  file mkdir $hls_work_dir
7218 
7219  # Determine which simulations to run
7220  if {$targeted_mode} {
7221  # User explicitly requested these sim types via -simset
7222  set sim_types [dict get $targets $component_name]
7223  set run_csim [expr {"csim" in $sim_types}]
7224  set run_cosim [expr {"cosim" in $sim_types}]
7225 
7226  # Validate: check if the requested sim is enabled in hog.conf
7227  foreach st $sim_types {
7228  set flag_val ""
7229  if {[dict exists $hls_props $st]} {
7230  set flag_val [dict get $hls_props $st]
7231  } elseif {[dict exists $hls_props [string toupper $st]]} {
7232  set flag_val [dict get $hls_props [string toupper $st]]
7233  }
7234  set is_enabled [expr {[string tolower $flag_val] eq "true" || $flag_val eq "1"}]
7235  if {!$is_enabled} {
7236  set upper_st [string toupper $st]
7237  Msg Warning "HLS simulation '$st' requested for '$component_name' but\
7238  $upper_st is not enabled in \[hls:$component_name\] section of\
7239  hog.conf. Running it anyway."
7240  Msg Info "To enable it permanently, add '$upper_st=true' under\
7241  \[hls:$component_name\] in Top/$project_name/hog.conf"
7242  }
7243  }
7244  } else {
7245  # Default mode: read flags from hog.conf
7246  set run_csim 0
7247  set run_cosim 0
7248  foreach {key_lower key_upper} {csim CSIM cosim COSIM} {
7249  set val ""
7250  if {[dict exists $hls_props $key_lower]} {
7251  set val [dict get $hls_props $key_lower]
7252  } elseif {[dict exists $hls_props $key_upper]} {
7253  set val [dict get $hls_props $key_upper]
7254  }
7255  if {[string tolower $val] eq "true" || $val eq "1"} {
7256  set run_$key_lower 1
7257  }
7258  }
7259 
7260  if {!$run_csim && !$run_cosim} {
7261  Msg Info "No HLS simulations enabled for '$component_name' (set CSIM=true and/or COSIM=true in \[hls:$component_name\] in hog.conf)"
7262  continue
7263  }
7264  }
7265 
7266  Msg Info "Running HLS simulations for component: $component_name"
7267 
7268  # CSIM
7269  if {$run_csim} {
7270  Msg Info "Running C simulation for HLS component '$component_name'..."
7271  if {
7272  ![ExecuteVitisUnifiedCommand $python_script "csim" \
7273  [list $component_name $cfg_file $hls_work_dir] \
7274  "Failed to run C simulation for $component_name"]
7275  } {
7276  Msg Error "C simulation failed for HLS component '$component_name'"
7277  continue
7278  }
7279  }
7280 
7281  # COSIM (auto-run synthesis if needed)
7282  if {$run_cosim} {
7283  set syn_done_marker [file normalize "$hls_work_dir/hls/syn"]
7284  if {![file exists $syn_done_marker]} {
7285  Msg Info "C synthesis output not found for '$component_name', running synthesis automatically before co-simulation..."
7286  if {
7287  ![ExecuteVitisUnifiedCommand $python_script "synthesis" \
7288  [list $component_name $cfg_file $hls_work_dir] \
7289  "Failed to run C synthesis for $component_name"]
7290  } {
7291  Msg Error "C synthesis failed for HLS component '$component_name', cannot proceed with co-simulation"
7292  continue
7293  }
7294  }
7295 
7296  Msg Info "Running C/RTL co-simulation for HLS component '$component_name'..."
7297  if {
7298  ![ExecuteVitisUnifiedCommand $python_script "cosim" \
7299  [list $component_name $cfg_file $hls_work_dir] \
7300  "Failed to run co-simulation for $component_name"]
7301  } {
7302  Msg Error "C/RTL co-simulation failed for HLS component '$component_name'"
7303  continue
7304  }
7305  }
7306 
7307  Msg Info "HLS simulations completed for '$component_name'"
7308  }
7309 
7310  Msg Info "Done with HLS simulations for $project_name"
7311 }
7312 
7313 # @brief Returns the BIF file path from the properties
7314 #
7315 # @param[in] props A dictionary with the properties defined in Hog.conf
7316 # @param[in] app The application name
7317 # @return The path of the BIF file or empty string if not found
7318 proc GetProcFromProps {repo_path props platform} {
7319  if {[dict exists $props "platform:$platform" "BIF"]} {
7320  set bif_file [dict get $props "platform:$platform" "BIF"]
7321  if {[IsRelativePath $bif_file] == 1} {
7322  set bif_file "$repo_path/$bif_file"
7323  }
7324  return $bif_file
7325  } else {
7326  Msg CriticalWarning "BIF file not found in platform ($platform) properties, skipping bootable image (.bin) generation"
7327  return ""
7328  }
7329 }
7330 
7331 # @brief Returns the BIF file path from the properties
7332 #
7333 # @param[in] props A dictionary with the properties defined in Hog.conf
7334 # @param[in] platform The platform name
7335 # @return The path of the BIF file or empty string if not found
7336 proc GetBifFromProps {repo_path props platform} {
7337  if {[dict exists $props "platform:$platform" "BIF"]} {
7338  set bif_file [dict get $props "platform:$platform" "BIF"]
7339  if {[IsRelativePath $bif_file] == 1} {
7340  set bif_file "$repo_path/$bif_file"
7341  }
7342  return $bif_file
7343  } else {
7344  Msg CriticalWarning "BIF file not found in platform ($platform) properties, skipping bootable image (.bin) generation"
7345  return ""
7346  }
7347 }
7348 
7349 # @brief Returns the part number from the properties
7350 #
7351 # @param[in] props A dictionary with the properties defined in Hog.conf
7352 # @return The part number
7353 proc GetPartFromProps {props} {
7354  if {[dict exists $props "main" "PART"]} {
7355  return [string tolower [dict get $props "main" "PART"]]
7356  } else {
7357  Msg Error "Part number not found in properties"
7358  return ""
7359  }
7360 }
7361 
7362 # @brief Determines the architecture from the part number
7363 #
7364 # @param[in] part The FPGA part number (e.g., xczu4cg-fbvb900-1-e)
7365 # @return String with the architecture (zynqmp, zynq, versal, fpga, or unknown)
7366 proc GetArchFromPart {part} {
7367  set part [string tolower $part]
7368  # bootgen -arch values: zynq, zynqmp, versal, fpga
7369  if {[string match "xczu*" $part] || [string match "xqzu*" $part]} {
7370  return "zynqmp"
7371  } elseif {[string match "xc7z*" $part] || [string match "xq7z*" $part]} {
7372  return "zynq"
7373  } elseif {
7374  [string match "xcve*" $part] || [string match "xcvc*" $part] ||
7375  [string match "xcvp*" $part] || [string match "xcvm*" $part] ||
7376  [string match "xqve*" $part] || [string match "xck26*" $part]
7377  } {
7378  return "versal"
7379  } elseif {[string match "xc*" $part] || [string match "xq*" $part]} {
7380  # Non-SoC FPGA (e.g. Kintex-7 / Artix-7 / UltraScale) for MicroBlaze/RISC-V
7381  return "fpga"
7382  } else {
7383  Msg CriticalWarning "Unknown part number: $part"
7384  return "unknown"
7385  }
7386 }
7387 
7388 # @brief Returns a list of application names from the properties
7389 #
7390 # @param[in] props A dictionary with the applications properties defined in Hog.conf
7391 # @param[in] list_names If 1, returns a list of application names rather than a dictionary of applications
7392 proc GetAppsFromProps {props {list_names 0}} {
7393  set prop_apps [dict filter $props key {app:*}]
7394  set apps [dict create]
7395  set app_names [list]
7396 
7397  dict for {app_key app_value} $prop_apps {
7398  if {[regexp {^app:(.+)$} $app_key -> app_name]} {
7399  set app_name [string trim [string tolower $app_name]]
7400  # Convert only the keys of the inner dictionary to lowercase
7401  set app_value_lower [dict create]
7402  dict for {key value} $app_value {
7403  dict set app_value_lower [string tolower $key] $value
7404  }
7405  dict set apps $app_name $app_value_lower
7406  lappend app_names $app_name
7407  }
7408  }
7409  if {$list_names eq 1} {
7410  return $app_names
7411  }
7412  return $apps
7413 }
7414 
7415 # @brief Returns a list of platform names from the properties
7416 #
7417 # @param[in] props A dictionary with the platforms properties
7418 # @param[in] list_names If 1, returns a list of platform names rather than a dictionary of platforms
7419 # @param[in] lower_case If 1, returns the platform names in lowercase
7420 proc GetPlatformsFromProps {props {list_names 0} {lower_case 0}} {
7421  set platforms [dict create]
7422  set platform_names [list]
7423  set prop_platforms [dict filter $props key {platform:*}]
7424 
7425  dict for {platform_key platform_value} $prop_platforms {
7426  if {[regexp {^platform:(.+)$} $platform_key -> platform_name]} {
7427  if {$lower_case == 1} {
7428  set platform_name [string trim [string tolower $platform_name]]
7429  } else {
7430  set platform_name [string trim $platform_name]
7431  }
7432  dict set platforms $platform_name $platform_value
7433  lappend platform_names $platform_name
7434  }
7435  }
7436  if {$list_names eq 1} {
7437  return $platform_names
7438  }
7439  return $platforms
7440 }
7441 
7442 # @brief Generates boot artifacts for the application. If the application targets a soft \
7443 # processor (e.g. microblaze, riscv), the bitstream (.bit) memory is updated to include the ELF file. Otherwise, for \
7444 # applications targeting a hard processor (e.g. zynq, versal), a bootable binary image (.bin) is generated.
7445 #
7446 # @param[in] properties A dictionary with the properties defined in Hog.conf
7447 # @param[in] repo_path The main path of the git repository
7448 # @param[in] proj_dir The directory of the project
7449 # @param[in] bin_dir The directory of the generated binary files
7450 # @param[in] bitfile The bitfile to update
7451 # @param[in] mmi_file The MMI file to update
7452 proc GenerateBootArtifacts {properties repo_path proj_dir bin_dir proj_name describe bitfile mmi_file} {
7453  set elf_list [glob -nocomplain "$bin_dir/*.elf"]
7454  set apps [GetAppsFromProps $properties 0]
7455  set platforms [GetPlatformsFromProps $properties 1]
7456 
7457  if {[llength $elf_list] == 0} {
7458  Msg Warning "No ELF files found in $bin_dir, skipping generation of boot artifacts"
7459  return
7460  }
7461 
7462  if {![file exists $bitfile]} {
7463  Msg Warning "Bitfile $bitfile does not exist, skipping generation of boot artifacts"
7464  return
7465  }
7466 
7467  Msg Info "Generating boot artifacts for $proj_name..."
7468  Msg Info "Found apps: $apps"
7469  Msg Info "Found platforms: $platforms"
7470 
7471 
7472  # Update bitstream with ELF files for the applications targeting a soft processor (e.g. microblaze, riscv)
7473  foreach elf_file $elf_list {
7474  set elf_name [file rootname [file tail $elf_file]]
7475  Msg Info "Found elf name: $elf_name"
7476  Msg Info "Removing $describe from elf"
7477 
7478  # Extract application name from ELF file name
7479  if {[regexp "^(.+)-(.+)-$describe\$" $elf_name -> project_name elf_app]} {
7480  set elf_app [string trim [string tolower $elf_app]]
7481  Msg Info "Found elf_app: $elf_app"
7482  } else {
7483  Msg Error "Could not extract app name from elf file: $elf_name"
7484  continue
7485  }
7486  Msg Info "Removed project name ($project_name) and $describe from elf"
7487 
7488  set app_conf [dict get $apps $elf_app]
7489  set plat [dict get $app_conf "platform"]
7490  set app_proc [dict get $app_conf "proc"]
7491 
7492  # If the application targets a soft processor, update bitstream memory with ELF file
7493  if {[regexp -nocase {microblaze|risc} $app_proc]} {
7494  Msg Info "Detected soft processor ($app_proc) for $elf_app, updating bitstream memory with ELF file..."
7495 
7496  # updatemem needs the processor instance path as written in the MMI file, which includes
7497  # the block design wrapper hierarchy. The platform processor map is only used as a
7498  # fallback, since it contains names relative to the block design
7499  set proc_cell [GetProcInstPathFromMmi $mmi_file $app_proc]
7500  if {$proc_cell eq ""} {
7501  set proc_map_file [file normalize "$proj_dir/vitis_classic/${plat}.PROC_MAP"]
7502  set proc_map [ReadProcMap $proc_map_file]
7503  if {[dict exists $proc_map $app_proc]} {
7504  set proc_cell [dict get $proc_map $app_proc]
7505  Msg Info "Processor $app_proc not found in $mmi_file, using $proc_map_file instead."
7506  } else {
7507  Msg Error "Could not determine the instance path of processor '$app_proc' for $elf_app, \
7508  neither from $mmi_file nor from $proc_map_file. \
7509  Check that proc= in the \[app:$elf_app\] section of hog.conf matches the processor instance name."
7510  continue
7511  }
7512  }
7513  Msg Info "Updating memory at processor cell: $proc_cell"
7514 
7515  set update_mem_cmd "updatemem -force -meminfo $mmi_file -data $elf_file -bit $bitfile -proc $proc_cell -out $bitfile"
7516  set ret [catch {exec -ignorestderr {*}$update_mem_cmd >@ stdout} result]
7517  if {$ret != 0} {
7518  Msg Error "Error updating memory for $elf_app: $result"
7519  }
7520  Msg Info "Done updating memory for $elf_app"
7521  } else {
7522  Msg Info "Detected hard processor ($app_proc) for $elf_app. Make sure the .elf file is defined in the platform ($plat)\
7523  .bif file to be included in the bootable binary image (.bin) generation."
7524  }
7525  }
7526 
7527 
7528  # Generate a bootable binary image for platforms that have a .bif file defined
7529  foreach plat $platforms {
7530  set bif_file [GetBifFromProps $repo_path $properties $plat]
7531  if {$bif_file != ""} {
7532  Msg Info "Generating bootable binary image (.bin) for $plat"
7533  set arch [GetArchFromPart [GetPartFromProps $properties]]
7534  Msg Info "Architecture: $arch"
7535  Msg Info "BIF file: $bif_file"
7536  if {$arch eq "unknown"} {
7537  Msg CriticalWarning "Skipping bootable image (.bin) generation for $plat: \
7538  could not determine bootgen architecture from the FPGA part."
7539  continue
7540  }
7541  set bootgen_cmd "bootgen -arch $arch -image $bif_file -o i $bin_dir/$proj_name-$plat-$describe.bin -w on"
7542  set ret [catch {exec -ignorestderr {*}$bootgen_cmd >@ stdout} result]
7543  if {$ret != 0} {
7544  Msg Error "Error generating bootable binary image (.bin) for $plat: $result"
7545  }
7546  Msg Info "Done generating bootable binary image (.bin) for $plat"
7547  }
7548  }
7549 }
7550 
7551 ## @brief Generate output products for standalone XCI IPs
7552 #
7553 # IPs nested inside a block design, or inside another IP, must be generated by their
7554 # parent sub-design: generating them directly makes Vivado fail with
7555 # "[Vivado 12-3563] The Nested sub-design ... can only be generated by its parent sub-design".
7556 # Vivado flags every such file by setting its PARENT_COMPOSITE_FILE property, so that is what
7557 # we use to tell nested IPs from standalone ones.
7558 proc GenerateStandaloneXciTargets {} {
7559  if {![IsVivado]} {
7560  return
7561  }
7562 
7563  foreach xci [get_files -quiet *.xci] {
7564  set xci_file [get_files -quiet $xci]
7565  if {$xci_file eq ""} {
7566  continue
7567  }
7568 
7569  set parent ""
7570  catch {set parent [get_property -quiet PARENT_COMPOSITE_FILE $xci_file]}
7571  if {$parent ne ""} {
7572  Msg Debug "Skipping [file tail $xci], it is generated by its parent [file tail $parent]"
7573  continue
7574  }
7575 
7576  Msg Info "Generating targets for standalone IP [file tail $xci]..."
7577  if {[catch {generate_target all $xci_file} err]} {
7578  Msg CriticalWarning "Failed to generate targets for $xci: $err"
7579  }
7580  }
7581 }
7582 
7583 # @brief Returns the processor instance path to be used with updatemem -proc
7584 #
7585 # The updatemem -proc option must match the InstPath attribute of one of the Processor entries
7586 # of the MMI file, which Vivado writes together with the bitstream and which contains the full
7587 # hierarchical path of the processor.
7588 #
7589 # @param[in] mmi_file The path to the MMI file
7590 # @param[in] app_proc The processor name, as defined with proc= in hog.conf
7591 # @return The matching instance path, or an empty string if it could not be determined
7592 proc GetProcInstPathFromMmi {mmi_file app_proc} {
7593  if {![file exists $mmi_file]} {
7594  Msg Debug "MMI file not found: $mmi_file"
7595  return ""
7596  }
7597 
7598  set f [open $mmi_file "r"]
7599  set mmi_content [read $f]
7600  close $f
7601 
7602  set inst_paths [list]
7603  foreach {match inst_path} [regexp -all -inline {<Processor[^>]*InstPath\s*=\s*"([^"]+)"} $mmi_content] {
7604  if {[lsearch -exact $inst_paths $inst_path] < 0} {
7605  lappend inst_paths $inst_path
7606  }
7607  }
7608  Msg Debug "Processor instance paths found in $mmi_file: $inst_paths"
7609 
7610  foreach inst_path $inst_paths {
7611  if {[string equal -nocase [file tail $inst_path] $app_proc]} {
7612  return $inst_path
7613  }
7614  }
7615 
7616  # A design with a single processor is unambiguous, even if the names do not match
7617  if {[llength $inst_paths] == 1} {
7618  set inst_path [lindex $inst_paths 0]
7619  Msg Info "Processor $app_proc does not match [file tail $inst_path], \
7620  using the only processor found in the MMI file: $inst_path"
7621  return $inst_path
7622  }
7623 
7624  return ""
7625 }
7626 
7627 # @brief Reads the processor map file
7628 #
7629 # @param[in] proc_map_file The path to the processor map file
7630 # @return A dictionary with the processor map
7631 proc ReadProcMap {proc_map_file} {
7632  set proc_map [dict create]
7633  if {[file exists $proc_map_file]} {
7634  set f [open $proc_map_file "r"]
7635  while {[gets $f line] >= 0} {
7636  Msg Debug "Line: $line"
7637  if {[regexp {^(\S+)\s+(.+)$} $line -> key value]} {
7638  Msg Debug "Found key: $key, value: $value in proc map file"
7639  dict set proc_map $key $value
7640  }
7641  }
7642  close $f
7643  }
7644  return $proc_map
7645 }
7646 
7647 
7648 # Returns the list of all the Hog Projects in the repository
7649 #
7650 # @param[in] repo_path The main path of the git repository
7651 # @param[in] print if 1 print the list of projects in the repository, if 2 does not print test projects
7652 # @param[in] ret_conf if 1 returns conf file rather than list of project names
7653 proc ListProjects {{repo_path .} {print 1} {ret_conf 0} {silent 0}} {
7654  set top_path [file normalize $repo_path/Top]
7655  set confs [findFiles [file normalize $top_path] hog.conf]
7656  set projects ""
7657  set confs [lsort $confs]
7658  set g ""
7659 
7660  foreach c $confs {
7661  set p [Relative $top_path [file dirname $c]]
7662  if {$print >= 1} {
7663  set description [DescriptionFromConf $c]
7664  if {$description eq "test"} {
7665  set description " - Test project"
7666  } elseif {$description ne ""} {
7667  set description " - $description"
7668  }
7669 
7670  if {$print == 1 || $description ne " - Test project"} {
7671  set old_g $g
7672  set g [file dirname $p]
7673  # Print a list of the projects with relative IDE and description (second line comment in hog.conf)
7674  if {$g ne $old_g} {
7675  if {$silent == 0} {Msg Status ""}
7676  }
7677  if {$silent == 0} {Msg Status "$p \([GetIDEFromConf $c]\)$description"}
7678  }
7679  }
7680  lappend projects $p
7681  }
7682 
7683  if {$ret_conf == 0} {
7684  # Returns a list of project names
7685  return $projects
7686  } else {
7687  # Return the list of hog.conf with full path
7688  return $confs
7689  }
7690 }
7691 
7692 ## @brief Evaluates the md5 sum of a file
7693 #
7694 # @param[in] file_name: the name of the file of which you want to evaluate the md5 checksum
7695 proc Md5Sum {file_name} {
7696  if {!([file exists $file_name])} {
7697  Msg Warning "Could not find $file_name."
7698  set file_hash -1
7699  }
7700  if {[catch {package require md5 2.0.7} result]} {
7701  Msg Warning "Tcl package md5 version 2.0.7 not found ($result), will use command line..."
7702  set hash [lindex [Execute md5sum $file_name] 0]
7703  } else {
7704  set file_hash [string tolower [md5::md5 -hex -file $file_name]]
7705  }
7706 }
7707 
7708 ## @brief Merges two tcl dictionaries of lists
7709 #
7710 # If the dictionaries contain same keys, the list at the common key is a merging of the two
7711 #
7712 # @param[in] dict0 the name of the first dictionary
7713 # @param[in] dict1 the name of the second dictionary
7714 # @param[in] remove_duplicates if 1, removes duplicates from the merged dictionary (default 1)
7715 #
7716 # @return the merged dictionary
7717 #
7718 proc MergeDict {dict0 dict1 {remove_duplicates 1}} {
7719  set outdict [dict merge $dict1 $dict0]
7720  foreach key [dict keys $dict1] {
7721  if {[dict exists $dict0 $key]} {
7722  set temp_list [dict get $dict1 $key]
7723  foreach item $temp_list {
7724  # Avoid duplication
7725  if {[IsInList $item [DictGet $outdict $key]] == 0 || $remove_duplicates == 0} {
7726  # If the key exists in both dictionaries, append the item to the list
7727  dict lappend outdict $key $item
7728  }
7729  }
7730  }
7731  }
7732  return $outdict
7733 }
7734 
7735 # @brief Move an element in the list to the end
7736 #
7737 # @param[in] inputList the list
7738 # @param[in] element the element to move to the end of the list
7739 proc MoveElementToEnd {inputList element} {
7740  set index [lsearch $inputList $element]
7741  if {$index != -1} {
7742  set inputList [lreplace $inputList $index $index]
7743  lappend inputList $element
7744  }
7745  return $inputList
7746 }
7747 
7748 # @brief Open the project with the corresponding IDE
7749 #
7750 # @param[in] project_file The project_file
7751 # @param[in] repo_path The main path of the git repository
7752 proc OpenProject {project_file repo_path} {
7753  if {[IsXilinx]} {
7754  open_project $project_file
7755  } elseif {[IsQuartus]} {
7756  set project_folder [file dirname $project_file]
7757  set project [file tail [file rootname $project_file]]
7758  if {[file exists $project_folder]} {
7759  cd $project_folder
7760  if {![is_project_open]} {
7761  Msg Info "Opening existing project file $project_file..."
7762  project_open $project -current_revision
7763  }
7764  } else {
7765  Msg Error "Project directory not found for $project_file."
7766  return 1
7767  }
7768  } elseif {[IsLibero]} {
7769  Msg Info "Opening existing project file $project_file..."
7770  cd $repo_path
7771  open_project -file $project_file -do_backup_on_convert 1 -backup_file {./Projects/$project_file.zip}
7772  } elseif {[IsDiamond]} {
7773  Msg Info "Opening existing project file $project_file..."
7774  prj_project open $project_file
7775  } else {
7776  Msg Error "This IDE is currently not supported by Hog. Exiting!"
7777  }
7778 }
7779 
7780 ## @brief Return the operative system name
7781 proc OS {} {
7782  global tcl_platform
7783  return $tcl_platform(platform)
7784 }
7785 
7786 ## @brief Parse JSON file
7787 #
7788 # @param[in] JSON_FILE The JSON File to parse
7789 # @param[in] JSON_KEY The key to extract from the JSON file
7790 #
7791 # @returns -1 in case of failure, JSON KEY VALUE in case of success
7792 #
7793 proc ParseJSON {JSON_FILE JSON_KEY} {
7794  set result [catch {package require Tcl 8.4} TclFound]
7795  if {"$result" != "0"} {
7796  Msg CriticalWarning "Cannot find Tcl package version equal or higher than 8.4.\n $TclFound\n Exiting"
7797  return -1
7798  }
7799 
7800  set result [catch {package require json} JsonFound]
7801  if {"$result" != "0"} {
7802  Msg CriticalWarning "Cannot find JSON package equal or higher than 1.0.\n $JsonFound\n Exiting"
7803  return -1
7804  }
7805  set JsonDict [json::json2dict $JSON_FILE]
7806  set result [catch {dict get $JsonDict $JSON_KEY} RETURNVALUE]
7807  if {"$result" != "0"} {
7808  Msg CriticalWarning "Cannot find $JSON_KEY in $JSON_FILE\n Exiting"
7809  return -1
7810  } else {
7811  return $RETURNVALUE
7812  }
7813 }
7814 
7815 
7816 # @brief Check if a Hog project exists, and if it exists returns the conf file
7817 # if it doesnt returns 0
7818 #
7819 # @brief project The project name
7820 # @brief repo_path The main path of the git repository
7821 proc ProjectExists {project {repo_path .}} {
7822  set index [lsearch -exact [ListProjects $repo_path 0] $project]
7823 
7824  if {$index >= 0} {
7825  # if project exists we return the relative hog.conf file
7826  return [lindex [ListProjects $repo_path 0 1] $index]
7827  } else {
7828  Msg Warning "Project not found. Available projects in $repo_path are"
7829  ListProjects $repo_path
7830  puts ""
7831  Msg Error "Project $project not found in repository $repo_path"
7832  return 1
7833  }
7834 }
7835 
7836 ## Read a property configuration file and returns a dictionary
7837 #
7838 # @param[in] file_name the configuration file
7839 #
7840 # @return The dictionary
7841 #
7842 proc ReadConf {file_name} {
7843  if {[catch {package require inifile 0.2.3} ERROR]} {
7844  Msg Error "Could not find inifile package version 0.2.3 or higher.\n
7845  To use ghdl, libero or diamond with Hog, you need to install the tcllib package\n
7846  You can install it with 'sudo apt install tcllib' on Debian/Ubuntu or 'sudo dnf install tcllib' on Fedora/RedHat/CentOs."
7847  }
7848 
7849 
7850  ::ini::commentchar "#"
7851  set f [::ini::open $file_name]
7852  set properties [dict create]
7853  foreach sec [::ini::sections $f] {
7854  set new_sec $sec
7855  if {$new_sec == "files"} {
7856  continue
7857  }
7858  set key_pairs [::ini::get $f $sec]
7859  #manipulate strings here:
7860  regsub -all {\{\"} $key_pairs "\{" key_pairs
7861  regsub -all {\"\}} $key_pairs "\}" key_pairs
7862 
7863  dict set properties $new_sec [dict create {*}$key_pairs]
7864  }
7865 
7866  ::ini::close $f
7867 
7868  return $properties
7869 }
7870 
7871 ## @brief Function used to read the list of files generated at creation time by tcl scripts in Project/proj/.hog/extra.files
7872 #
7873 # @param[in] extra_file_name the path to the extra.files file
7874 # @returns a dictionary with the full name of the files as key and a SHA as value
7875 #
7876 proc ReadExtraFileList {extra_file_name} {
7877  set extra_file_dict [dict create]
7878  if {[file exists $extra_file_name]} {
7879  set file [open $extra_file_name "r"]
7880  set file_data [read $file]
7881  close $file
7882 
7883  set data [split $file_data "\n"]
7884  foreach line $data {
7885  if {![regexp {^ *$} $line] & ![regexp {^ *\#} $line]} {
7886  set ip_and_md5 [regexp -all -inline {\S+} $line]
7887  dict lappend extra_file_dict "[lindex $ip_and_md5 0]" "[lindex $ip_and_md5 1]"
7888  }
7889  }
7890  }
7891  return $extra_file_dict
7892 }
7893 
7894 
7895 # @brief Expand a Vitis HLS configuration file (hls_config.cfg) into the list of
7896 # repository files it references, so Hog can hash them for dirty/SHA
7897 # detection without forcing the user to duplicate the list in a .src
7898 #
7899 # @param[in] cfg_file Absolute or relative path to the hls_config.cfg file.
7900 # @return A de-duplicated list of normalized absolute file paths.
7901 #
7902 proc ExpandHlsConfigFiles {cfg_file} {
7903  set out [list]
7904  if {![file exists $cfg_file] || ![file isfile $cfg_file]} {
7905  return $out
7906  }
7907  set cfg_dir [file normalize [file dirname $cfg_file]]
7908 
7909  if {[catch {set fp [open $cfg_file r]} err]} {
7910  Msg Warning "ExpandHlsConfigFiles: cannot open $cfg_file: $err"
7911  return $out
7912  }
7913  set lines [split [read $fp] "\n"]
7914  close $fp
7915 
7916  foreach line $lines {
7917  if {[regexp {^[\t\s]*$} $line]} {continue}
7918  if {[regexp {^[\t\s]*\#} $line]} {continue}
7919  if {[regexp {^\s*\[.*\]\s*$} $line]} {continue}
7920  if {![regexp {^\s*[^=\s]+\s*=\s*(.+?)\s*$} $line -> value]} {continue}
7921 
7922  foreach tok [regexp -all -inline {\S+} $value] {
7923  if {[file pathtype $tok] eq "absolute"} {
7924  set candidate [file normalize $tok]
7925  } else {
7926  set candidate [file normalize [file join $cfg_dir $tok]]
7927  }
7928  if {[file isfile $candidate]} {
7929  lappend out $candidate
7930  } elseif {[file isdirectory $candidate]} {
7931  set stack [list $candidate]
7932  while {[llength $stack] > 0} {
7933  set d [lindex $stack 0]
7934  set stack [lrange $stack 1 end]
7935  foreach f [glob -nocomplain -directory $d -types f *] {
7936  lappend out [file normalize $f]
7937  }
7938  foreach sub [glob -nocomplain -directory $d -types d *] {
7939  lappend stack $sub
7940  }
7941  }
7942  }
7943  }
7944  }
7945  return [lsort -unique $out]
7946 }
7947 
7948 
7949 # @brief Discover HLS components declared in a project's hog.conf and return
7950 # the absolute path of each component's hls_config.cfg
7951 #
7952 # @param[in] conf_file Absolute path of the project's hog.conf
7953 # @param[in] repo_path Repository root (paths in HLS_CONFIG are relative to it)
7954 # @return A dict { component_name -> absolute_cfg_path }. Empty if no HLS
7955 proc GetHlsConfigsFromProjConf {conf_file repo_path} {
7956  set out [dict create]
7957  if {![file exists $conf_file]} {return $out}
7958  if {[catch {set properties [ReadConf $conf_file]} err]} {
7959  Msg Debug "GetHlsConfigsFromProjConf: cannot parse $conf_file: $err"
7960  return $out
7961  }
7962  set hls_components [dict filter $properties key {hls:*}]
7963  dict for {hls_key hls_props} $hls_components {
7964  if {![regexp {^hls:(.+)$} $hls_key -> component_name]} {continue}
7965  set component_name [string trim $component_name]
7966  set cfg_rel ""
7967  if {[dict exists $hls_props hls_config]} {
7968  set cfg_rel [dict get $hls_props hls_config]
7969  } elseif {[dict exists $hls_props HLS_CONFIG]} {
7970  set cfg_rel [dict get $hls_props HLS_CONFIG]
7971  }
7972  if {$cfg_rel eq ""} {continue}
7973  set cfg_abs [file normalize "$repo_path/$cfg_rel"]
7974  if {![file exists $cfg_abs]} {
7975  Msg CriticalWarning "HLS component '$component_name': HLS_CONFIG=$cfg_rel does not exist on disk."
7976  continue
7977  }
7978  dict set out $component_name $cfg_abs
7979  }
7980  return $out
7981 }
7982 
7983 
7984 # @brief Read a list file and return a list of three dictionaries
7985 #
7986 # Additional information is provided with text separated from the file name with one or more spaces
7987 #
7988 # @param[in] args The arguments are <list_file> <path> [options]
7989 # * list_file file containing vhdl list with optional properties
7990 # * path path the vhdl file are referred to in the list file
7991 # Options:
7992 # * -lib <library> name of the library files will be added to, if not given will be extracted from the file name
7993 # * -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.
7994 # 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.
7995 #
7996 # @return a list of 3 dictionaries:
7997 # "libraries" has library name as keys and a list of filenames as values,
7998 # "properties" has as file names as keys and a list of properties as values
7999 # "filesets" has the fileset' names as keys and the list of associated libraries as values.
8000 proc ReadListFile {args} {
8001  if {[IsQuartus]} {
8002  load_package report
8003  if {[catch {package require cmdline} ERROR]} {
8004  puts "$ERROR\n If you are running this script on tclsh, you can fix this by installing 'tcllib'"
8005  return 1
8006  }
8007  }
8008  # tclint-disable line-length
8009  set parameters {
8010  {lib.arg "" "The name of the library files will be added to, if not given will be extracted from the file name."}
8011  {fileset.arg "" "The name of the library, from the main list file"}
8012  {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."}
8013  {print_log "If set, will use PrintFileTree for the VIEW directive"}
8014  {indent.arg "" "Used to indent files with the VIEW directive"}
8015  }
8016  # tclint-enable line-length
8017  set usage "USAGE: ReadListFile \[options\] <list file> <path>"
8018  if {[catch {array set options [cmdline::getoptions args $parameters $usage]}] || [llength $args] != 2} {
8019  Msg CriticalWarning "[cmdline::usage $parameters $usage]"
8020  return
8021  }
8022 
8023 
8024  set list_file [lindex $args 0]
8025  set path [lindex $args 1]
8026  set sha_mode $options(sha_mode)
8027  set lib $options(lib)
8028  set fileset $options(fileset)
8029  set print_log $options(print_log)
8030  set indent $options(indent)
8031 
8032  if {$sha_mode == 1} {
8033  set sha_mode_opt "-sha_mode"
8034  } else {
8035  set sha_mode_opt ""
8036  }
8037 
8038  if {$print_log == 1} {
8039  set print_log_opt "-print_log"
8040  } else {
8041  set print_log_opt ""
8042  }
8043 
8044  # if no library is given, work it out from the file name
8045  if {$lib eq ""} {
8046  set lib [file rootname [file tail $list_file]]
8047  }
8048  set fp [open $list_file r]
8049  set file_data [read $fp]
8050  close $fp
8051  set list_file_ext [file extension $list_file]
8052  switch $list_file_ext {
8053  .sim {
8054  if {$fileset eq ""} {
8055  # If fileset is empty, use the library name for .sim file
8056  set fileset "$lib"
8057  }
8058  }
8059  .con {
8060  set fileset "constrs_1"
8061  }
8062  default {
8063  set fileset "sources_1"
8064  }
8065  }
8066 
8067  set libraries [dict create]
8068  set filesets [dict create]
8069  set properties [dict create]
8070  # Process data file
8071  set data [split $file_data "\n"]
8072  set data [ExtractFilesSection $data]
8073  set n [llength $data]
8074  set last_printed ""
8075  if {$print_log == 1} {
8076  if {$indent eq ""} {
8077  set list_file_rel [file tail $list_file]
8078  Msg Status "\n$list_file_rel"
8079  }
8080  set last_printed [PrintFileTree $data $path "$indent"]
8081  }
8082  Msg Debug "$n lines read from $list_file."
8083  set cnt 0
8084 
8085  foreach line $data {
8086  # Exclude empty lines and comments
8087  if {![regexp {^[\t\s]*$} $line] & ![regexp {^[\t\s]*\#} $line]} {
8088  set file_and_prop [regexp -all -inline {\S+} $line]
8089  set srcfile [lindex $file_and_prop 0]
8090  set srcfile "$path/$srcfile"
8091 
8092  set srcfiles [glob -nocomplain $srcfile]
8093 
8094  # glob the file list for wildcards
8095  if {$srcfiles != $srcfile && ![string equal $srcfiles ""]} {
8096  Msg Debug "Wildcard source expanded from $srcfile to $srcfiles"
8097  } else {
8098  if {![file exists $srcfile]} {
8099  if {$print_log == 0} {
8100  Msg CriticalWarning "File: $srcfile (from list file: $list_file) does not exist."
8101  }
8102  continue
8103  }
8104  }
8105 
8106  foreach vhdlfile $srcfiles {
8107  if {[file exists $vhdlfile]} {
8108  set vhdlfile [file normalize $vhdlfile]
8109  set extension [file extension $vhdlfile]
8110  ### Set file properties
8111  set prop [lrange $file_and_prop 1 end]
8112 
8113  # The next lines should be inside the case for recursive list files, also we should check the allowed properties for the .src as well
8114  set library [lindex [regexp -inline {\ylib\s*=\s*(.+?)\y.*} $prop] 1]
8115  if {$library == ""} {
8116  set library $lib
8117  }
8118 
8119  if {$extension == $list_file_ext} {
8120  # Deal with recursive list files
8121  # In the next regex we use \S+ instead of .+? because we want to include forward slashes
8122  set ref_path [lindex [regexp -inline {\ypath\s*=\s*(\S+).*} $prop] 1]
8123  if {$ref_path eq ""} {
8124  set ref_path $path
8125  } else {
8126  set ref_path [file normalize $path/$ref_path]
8127  }
8128  Msg Debug "List file $vhdlfile found in list file, recursively opening it using path \"$ref_path\"..."
8129  if {$print_log == 1} {
8130  if {[file normalize $last_printed] ne [file normalize $vhdlfile]} {
8131  Msg Status "$indent Inside [file tail $vhdlfile]:"
8132  set last_printed ""
8133  }
8134  }
8135  lassign [ReadListFile {*}"-indent \" $indent\" -lib $library -fileset $fileset $sha_mode_opt $print_log_opt $vhdlfile $ref_path"] l p fs
8136  set libraries [MergeDict $l $libraries]
8137  set properties [MergeDict $p $properties]
8138  set filesets [MergeDict $fs $filesets]
8139  } elseif {[lsearch {.src .sim .con ReadExtraFileList} $extension] >= 0} {
8140  # Not supported extensions
8141  Msg Error "$vhdlfile cannot be included into $list_file, $extension files must be included into $extension files."
8142  } else {
8143  # Deal with single files
8144  regsub -all " *= *" $prop "=" prop
8145  # Fill property dictionary
8146  foreach p $prop {
8147  # No need to append the lib= property
8148  if {[string first "lib=" $p] == -1} {
8149  # Get property name up to the = (for QSYS properties at the moment)
8150  set pos [string first "=" $p]
8151  if {$pos == -1} {
8152  set prop_name $p
8153  } else {
8154  set prop_name [string range $p 0 [expr {$pos - 1}]]
8155  }
8156  if {[IsInList $prop_name [DictGet [ALLOWED_PROPS] $extension]]
8157  || [string first "top" $p] == 0
8158  || $list_file_ext eq ".ipb"
8159  || $list_file_ext eq ".chb"} {
8160  if {$list_file_ext eq ".ipb"} {
8161  dict lappend properties $vhdlfile $path/$p
8162  } else {
8163  dict lappend properties $vhdlfile $p
8164  }
8165  Msg Debug "Adding property $p to $vhdlfile..."
8166  } elseif {$list_file_ext != ".ipb" && $list_file_ext != ".chb"} {
8167  Msg Warning "Setting Property $p is not supported for file $vhdlfile or it is already its default. \
8168  The allowed properties for this file type are \[ [DictGet [ALLOWED_PROPS] $extension]\]"
8169  }
8170  }
8171  }
8172  if {[lsearch {.xci .ip .bd .xcix} $extension] >= 0} {
8173  # Adding IP library
8174  set lib_name "ips.src"
8175  } elseif {[IsInList $extension {.vhd .vhdl}] || $list_file_ext == ".sim"} {
8176  # VHDL files and simulation
8177  if {![IsInList $extension {.vhd .vhdl}]} {
8178  set lib_name "others.sim"
8179  } else {
8180  set lib_name "$library$list_file_ext"
8181  }
8182  } elseif {$list_file_ext == ".con"} {
8183  set lib_name "sources.con"
8184  } elseif {$list_file_ext == ".ipb"} {
8185  set lib_name "xml.ipb"
8186  } elseif {$list_file_ext == ".chb"} {
8187  set lib_name "cheby.chb"
8188  } elseif {[IsInList $list_file_ext {.src}] && [IsInList $extension {.c .cpp .h .hpp}]} {
8189  # Adding Vitis library
8190  set lib_name "$library$list_file_ext"
8191  } else {
8192  # Other files are stored in the OTHER dictionary from vivado (no library assignment)
8193  set lib_name "others.src"
8194  }
8195 
8196  Msg Debug "Appending $vhdlfile to $lib_name list..."
8197  dict lappend libraries $lib_name $vhdlfile
8198  if {$sha_mode != 0 && [file type $vhdlfile] eq "link"} {
8199  #if the file is a link, also add the linked file in sha mode
8200  set real_file [GetLinkedFile $vhdlfile]
8201  dict lappend libraries $lib_name $real_file
8202  Msg Debug "File $vhdlfile is a soft link, also adding the real file: $real_file"
8203  }
8204 
8205  # Auto-expand HLS config files: when a .cfg is referenced from a .src
8206  # we treat it as a Vitis HLS hls_config.cfg and add every existing
8207  # file/dir it references (syn.file, tb.file, -I include dirs, ...)
8208  # to the same library. This avoids forcing the user to duplicate the
8209  # HLS file list in both hls_config.cfg and the .src.
8210  if {$list_file_ext eq ".src" && $extension eq ".cfg"} {
8211  set hls_extras [ExpandHlsConfigFiles $vhdlfile]
8212  if {$print_log == 1 && [llength $hls_extras] > 0} {
8213  Msg Status "$indent Inside [file tail $vhdlfile] (HLS auto-expand):"
8214  }
8215  set n_extras [llength $hls_extras]
8216  set i 0
8217  foreach hls_extra $hls_extras {
8218  incr i
8219  if {$hls_extra eq $vhdlfile} {continue}
8220  Msg Debug "HLS cfg expansion: adding $hls_extra (from $vhdlfile) to $lib_name"
8221  if {$print_log == 1} {
8222  if {$i == $n_extras} {set pad "└──"} else {set pad "├──"}
8223  set rel [Relative [file dirname $vhdlfile] $hls_extra]
8224  Msg Status "$indent $pad $rel"
8225  }
8226  dict lappend libraries $lib_name $hls_extra
8227  if {$sha_mode != 0 && [file type $hls_extra] eq "link"} {
8228  set real_extra [GetLinkedFile $hls_extra]
8229  dict lappend libraries $lib_name $real_extra
8230  Msg Debug "HLS expanded file $hls_extra is a soft link, also adding $real_extra"
8231  }
8232  }
8233  }
8234 
8235 
8236  # Create the fileset (if not already) and append the library
8237  if {[dict exists $filesets $fileset] == 0} {
8238  # Fileset has not been defined yet, adding to dictionary...
8239  Msg Debug "Adding $fileset to the fileset dictionary..."
8240  Msg Debug "Adding library $lib_name to fileset $fileset..."
8241  dict set filesets $fileset $lib_name
8242  } else {
8243  # Fileset already exist in dictionary, append library to list, if not already there
8244  if {[IsInList $lib_name [DictGet $filesets $fileset]] == 0} {
8245  Msg Debug "Adding library $lib_name to fileset $fileset..."
8246  dict lappend filesets $fileset $lib_name
8247  }
8248  }
8249  }
8250  incr cnt
8251  } else {
8252  Msg CriticalWarning "File $vhdlfile not found."
8253  }
8254  }
8255  }
8256  }
8257 
8258  if {$sha_mode != 0} {
8259  #In SHA mode we also need to add the list file to the list
8260  if {$list_file_ext eq ".ipb"} {
8261  set sha_lib "xml.ipb"
8262  } elseif {$list_file_ext eq ".chb"} {
8263  set sha_lib "cheby.chb"
8264  } else {
8265  set sha_lib $lib$list_file_ext
8266  }
8267  dict lappend libraries $sha_lib [file normalize $list_file]
8268  if {[file type $list_file] eq "link"} {
8269  #if the file is a link, also add the linked file
8270  set real_file [GetLinkedFile $list_file]
8271  dict lappend libraries $lib$list_file_ext $real_file
8272  Msg Debug "List file $list_file is a soft link, also adding the real file: $real_file"
8273  }
8274  }
8275  return [list $libraries $properties $filesets]
8276 }
8277 
8278 ## @brief Returns the destination path relative to base
8279 #
8280 # @param[in] base the path with respect to witch the dst path is calculated
8281 # @param[in] dst the path to be calculated with respect to base
8282 # @param[in] quiet if 1, does not print warnings when paths are of different types
8283 #
8284 proc Relative {base dst {quiet 0}} {
8285  if {![string equal [file pathtype $base] [file pathtype $dst]]} {
8286  if {$quiet == 0} {
8287  Msg CriticalWarning "Unable to compute relation for paths of different path types: [file pathtype $base] vs. [file pathtype $dst], ($base vs. $dst)"
8288  }
8289  return ""
8290  }
8291 
8292  set base [file normalize [file join [pwd] $base]]
8293  set dst [file normalize [file join [pwd] $dst]]
8294 
8295  set save $dst
8296  set base [file split $base]
8297  set dst [file split $dst]
8298 
8299  while {[string equal [lindex $dst 0] [lindex $base 0]]} {
8300  set dst [lrange $dst 1 end]
8301  set base [lrange $base 1 end]
8302  if {![llength $dst]} {break}
8303  }
8304 
8305  set dstlen [llength $dst]
8306  set baselen [llength $base]
8307 
8308  if {($dstlen == 0) && ($baselen == 0)} {
8309  set dst .
8310  } else {
8311  while {$baselen > 0} {
8312  set dst [linsert $dst 0 ..]
8313  incr baselen -1
8314  }
8315  set dst [eval [linsert $dst 0 file join]]
8316  }
8317 
8318  return $dst
8319 }
8320 
8321 ## @brief Returns the path of filePath relative to pathName
8322 #
8323 # @param[in] pathName the path with respect to which the returned path is calculated
8324 # @param[in] filePath the path of filePath
8325 #
8326 proc RelativeLocal {pathName filePath} {
8327  if {[string first [file normalize $pathName] [file normalize $filePath]] != -1} {
8328  return [Relative $pathName $filePath]
8329  } else {
8330  return ""
8331  }
8332 }
8333 
8334 ## @brief Remove duplicates in a dictionary
8335 #
8336 # @param[in] mydict the input dictionary
8337 #
8338 # @return the dictionary stripped of duplicates
8339 proc RemoveDuplicates {mydict} {
8340  set new_dict [dict create]
8341  foreach key [dict keys $mydict] {
8342  set values [DictGet $mydict $key]
8343  foreach value $values {
8344  set idxs [lreverse [lreplace [lsearch -exact -all $values $value] 0 0]]
8345  foreach idx $idxs {
8346  set values [lreplace $values $idx $idx]
8347  }
8348  }
8349  dict set new_dict $key $values
8350  }
8351  return $new_dict
8352 }
8353 
8354 ## Reset files in the repository
8355 #
8356 # @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)
8357 #
8358 # @return Nothing
8359 #
8360 proc ResetRepoFiles {reset_file} {
8361  if {[file exists $reset_file]} {
8362  Msg Info "Found $reset_file, opening it..."
8363  set fp [open $reset_file r]
8364  set wild_cards [lsearch -all -inline -not -regexp [split [read $fp] "\n"] "^ *$"]
8365  close $fp
8366  Msg Info "Found the following files/wild cards to restore if modified: $wild_cards..."
8367  foreach w $wild_cards {
8368  set mod_files [GetModifiedFiles "." $w]
8369  if {[llength $mod_files] > 0} {
8370  Msg Info "Found modified $w files: $mod_files, will restore them..."
8371  RestoreModifiedFiles "." $w
8372  } else {
8373  Msg Info "No modified $w files found."
8374  }
8375  }
8376  }
8377 }
8378 
8379 ## @brief Restore with checkout -- the files specified in pattern
8380 #
8381 # @param[in] repo_path the path of the git repository
8382 # @param[in] pattern the pattern with wildcards that files should match
8383 #
8384 proc RestoreModifiedFiles {{repo_path "."} {pattern "."}} {
8385  set old_path [pwd]
8386  cd $repo_path
8387  set ret [Git checkout $pattern]
8388  cd $old_path
8389  return
8390 }
8391 
8392 ## Search the Hog projects inside a directory
8393 #
8394 # @param[in] dir The directory to search
8395 #
8396 # @return The list of projects
8397 #
8398 proc SearchHogProjects {dir} {
8399  set projects_list {}
8400  if {[file exists $dir]} {
8401  if {[file isdirectory $dir]} {
8402  foreach proj_dir [glob -nocomplain -types d $dir/*] {
8403  if {![regexp {^.*Top/+(.*)$} $proj_dir dummy proj_name]} {
8404  Msg Warning "Could not parse Top directory $dir"
8405  break
8406  }
8407  if {[file exists "$proj_dir/hog.conf"]} {
8408  lappend projects_list $proj_name
8409  } else {
8410  foreach p [SearchHogProjects $proj_dir] {
8411  lappend projects_list $p
8412  }
8413  }
8414  }
8415  } else {
8416  Msg Error "Input $dir is not a directory!"
8417  }
8418  } else {
8419  Msg Error "Directory $dir doesn't exist!"
8420  }
8421  return $projects_list
8422 }
8423 
8424 ## @brief Sets the generics in all the sim.conf simulation file sets
8425 #
8426 # @param[in] repo_path: the top folder of the projectThe path to the main git repository
8427 # @param[in] proj_dir: the top folder of the project
8428 # @param[in] target: software target(vivado, questa)
8429 #
8430 proc SetGenericsSimulation {repo_path proj_dir target} {
8431  set top_dir "$repo_path/Top/$proj_dir"
8432  set simsets [get_filesets]
8433  if {$simsets != ""} {
8434  foreach simset $simsets {
8435  # Only for simulation filesets
8436  if {[get_property FILESET_TYPE $simset] != "SimulationSrcs"} {
8437  continue
8438  }
8439 
8440  set merged_generics_dict [dict create]
8441  # Get generics from sim.conf file
8442  set simset_dict [DictGet [GetSimSets $proj_dir $repo_path $simset] $simset]
8443  set hog_generics [GetGenericsFromConf $proj_dir]
8444  set simset_generics [DictGet $simset_dict "generics"]
8445  set merged_generics_dict [MergeDict $merged_generics_dict $simset_generics 0]
8446  set generic_str [GenericToSimulatorString $merged_generics_dict $target]
8447 
8448  Msg Debug "TOP = [get_property top [get_filesets sources_1]]"
8449  Msg Debug "GENERICS = [get_property generic [get_filesets sources_1]]"
8450 
8451  set_property generic $generic_str [get_filesets $simset]
8452  Msg Info "Setting generics $generic_str for simulator $target\
8453  and simulation file-set $simset..."
8454  }
8455  }
8456 }
8457 
8458 ## @brief set the top module as top module in the chosen fileset
8459 #
8460 # It automatically recognises the IDE
8461 #
8462 # @param[out] top_module Name of the top module
8463 # @param[in] fileset The name of the fileset
8464 #
8465 proc SetTopProperty {top_module fileset} {
8466  Msg Info "Setting TOP property to $top_module module"
8467  if {[IsXilinx]} {
8468  #VIVADO_ONLY
8469  set_property "top" $top_module [get_filesets $fileset]
8470  } elseif {[IsQuartus]} {
8471  #QUARTUS ONLY
8472  set_global_assignment -name TOP_LEVEL_ENTITY $top_module
8473  } elseif {[IsLibero]} {
8474  set_root -module $top_module
8475  } elseif {[IsDiamond]} {
8476  prj_impl option top $top_module
8477  }
8478 }
8479 
8480 ## @brief Returns a list of Vivado properties that expect a PATH for value
8481 proc VIVADO_PATH_PROPERTIES {} {
8482  return {"\.*\.TCL\.PRE$" "^.*\.TCL\.POST$" "^RQS_FILES$" "^INCREMENTAL\_CHECKPOINT$" "NOC\_SOLUTION\_FILE"}
8483 }
8484 
8485 ## @brief Returns a list of Vitis properties that expect a PATH for value
8486 proc VITIS_PATH_PROPERTIES {} {
8487  return {"^HW$" "^XPFM$" "^LINKER-SCRIPT$" "^LIBRARIES$" "^LIBRARY-SEARCH-PATH$"}
8488 }
8489 
8490 ## @brief Write a property configuration file from a dictionary
8491 #
8492 # @param[in] file_name the configuration file
8493 # @param[in] config the configuration dictionary
8494 # @param[in] comment comment to add at the beginning of configuration file
8495 #
8496 #
8497 proc WriteConf {file_name config {comment ""}} {
8498  if {[catch {package require inifile 0.2.3} ERROR]} {
8499  puts "$ERROR\n If you are running this script on tclsh, you can fix this by installing 'tcllib'"
8500  return 1
8501  }
8502 
8503  ::ini::commentchar "#"
8504  set f [::ini::open $file_name w]
8505 
8506  foreach sec [dict keys $config] {
8507  set section [dict get $config $sec]
8508  dict for {p v} $section {
8509  if {[string trim $v] == ""} {
8510  Msg Warning "Property $p has empty value. Skipping..."
8511  continue
8512  }
8513  ::ini::set $f $sec $p $v
8514  }
8515  }
8516 
8517  #write comment before the first section (first line of file)
8518  if {![string equal "$comment" ""]} {
8519  ::ini::comment $f [lindex [::ini::sections $f] 0] "" $comment
8520  set hog_header "Generated by Hog on [clock format [clock seconds] -format "%Y-%m-%d %H:%M:%S"]"
8521  ::ini::comment $f [lindex [::ini::sections $f] 0] "" $hog_header
8522  }
8523  ::ini::commit $f
8524 
8525  ::ini::close $f
8526 }
8527 
8528 ## @brief Set custom Verilog `define macros from the [defines] section of hog.conf
8529 #
8530 # @param[in] design The name of the design
8531 #
8532 proc WriteDefines {design} {
8533  set prj_defines [GetDefinesFromConf $design]
8534  if {[dict size $prj_defines] > 0} {
8535  if {[IsVivado]} {
8536  set define_string [GenericToSimulatorString $prj_defines "Vivado"]
8537  set_property verilog_define $define_string [current_fileset]
8538  Msg Info "Setting Verilog defines from the \[defines\] section of hog.conf..."
8539  Msg Debug "Detailed defines: $define_string"
8540  } else {
8541  set ide_name [GetIDEName]
8542  Msg Error "Project $design has a \[defines\] section in hog.conf, but Verilog defines are not supported for $ide_name projects."
8543  }
8544  }
8545 }
8546 
8547 ## Set the generics property
8548 #
8549 # @param[in] mode if it's "create", the function will assume the project is being created
8550 # @param[in] repo_path The path to the main git repository
8551 # @param[in] design The name of the design
8552 
8553 # @param[in] list of variables to be written in the generics in the usual order
8554 
8555 proc WriteGenerics {
8556  mode repo_path design date timee
8557  commit version top_hash top_ver hog_hash hog_ver
8558  cons_ver cons_hash libs vers hashes ext_names ext_hashes
8559  user_ip_repos user_ip_vers user_ip_hashes flavour {xml_ver ""} {xml_hash ""}
8560  {cheby_ver ""} {cheby_hash ""}
8561 } {
8562  Msg Info "Passing parameters/generics to project's top module..."
8563  ##### Passing Hog generic to top file
8564  # set global generic variables
8565  set generic_string [concat \
8566  "GLOBAL_DATE=[FormatGeneric $date]" \
8567  "GLOBAL_TIME=[FormatGeneric $timee]" \
8568  "GLOBAL_VER=[FormatGeneric $version]" \
8569  "GLOBAL_SHA=[FormatGeneric $commit]" \
8570  "TOP_SHA=[FormatGeneric $top_hash]" \
8571  "TOP_VER=[FormatGeneric $top_ver]" \
8572  "HOG_SHA=[FormatGeneric $hog_hash]" \
8573  "HOG_VER=[FormatGeneric $hog_ver]" \
8574  "CON_VER=[FormatGeneric $cons_ver]" \
8575  "CON_SHA=[FormatGeneric $cons_hash]"]
8576  # xml hash
8577  if {$xml_hash != "" && $xml_ver != ""} {
8578  lappend generic_string \
8579  "XML_VER=[FormatGeneric $xml_ver]" \
8580  "XML_SHA=[FormatGeneric $xml_hash]"
8581  }
8582  # cheby hash
8583  if {$cheby_hash != "" && $cheby_ver != ""} {
8584  lappend generic_string \
8585  "CHEBY_VER=[FormatGeneric $cheby_ver]" \
8586  "CHEBY_SHA=[FormatGeneric $cheby_hash]"
8587  }
8588  #set project specific lists
8589  foreach l $libs v $vers h $hashes {
8590  set ver "[string toupper $l]_VER=[FormatGeneric $v]"
8591  set hash "[string toupper $l]_SHA=[FormatGeneric $h]"
8592  # Replaces all occurrences of dots (.) and hyphens (-) in the generic name
8593  # with underscores (_) to make it compatible with VHDL/Verilog syntax
8594  # Uses regsub with -all flag to replace all matches of the regex pattern [\.-]
8595  set ver [regsub -all {[\.-]} $ver {_}]
8596  set hash [regsub -all {[\.-]} $hash {_}]
8597  lappend generic_string "$ver" "$hash"
8598  }
8599 
8600  foreach e $ext_names h $ext_hashes {
8601  set hash "[string toupper $e]_SHA=[FormatGeneric $h]"
8602  lappend generic_string "$hash"
8603  }
8604 
8605  foreach repo $user_ip_repos v $user_ip_vers h $user_ip_hashes {
8606  set repo_name [file tail $repo]
8607  set ver "[string toupper $repo_name]_VER=[FormatGeneric $v]"
8608  set hash "[string toupper $repo_name]_SHA=[FormatGeneric $h]"
8609  set ver [regsub -all {[\.-]} $ver {_}]
8610  set hash [regsub -all {[\.-]} $hash {_}]
8611  lappend generic_string "$ver" "$hash"
8612  }
8613 
8614  if {$flavour != -1} {
8615  lappend generic_string "FLAVOUR=$flavour"
8616  }
8617 
8618  # Dealing with project generics in Vivado
8619  if {[IsVivado] || [IsVitisClassic] || [IsVitisUnified]} {
8620  set prj_generics [GenericToSimulatorString [GetGenericsFromConf $design] "Vivado"]
8621  set generic_string "$prj_generics $generic_string"
8622  }
8623 
8624  # Dealing with custom Verilog `define macros from the [defines] section of hog.conf
8625  WriteDefines $design
8626 
8627  # Extract the generics from the top level source file
8628  if {[IsXilinx]} {
8629  # Top File can be retrieved only at creation time or in ISE
8630  if {$mode == "create" || [IsISE]} {
8631  set top_file [GetTopFile]
8632  set top_name [GetTopModule]
8633  if {[file exists $top_file]} {
8634  set generics [GetFileGenerics $top_file $top_name]
8635 
8636  Msg Debug "Found top level generics $generics in $top_file"
8637 
8638  set filtered_generic_string ""
8639 
8640  foreach generic_to_set [split [string trim $generic_string]] {
8641  set key [lindex [split $generic_to_set "="] 0]
8642  if {[dict exists $generics $key]} {
8643  Msg Debug "Hog generic $key found in $top_name"
8644  lappend filtered_generic_string "$generic_to_set"
8645  } else {
8646  Msg Warning "Generic $key is passed by Hog but is NOT present in $top_name."
8647  }
8648  }
8649 
8650  # only filter in ISE
8651  if {[IsISE]} {
8652  set generic_string $filtered_generic_string
8653  }
8654  }
8655  }
8656 
8657  set_property generic $generic_string [current_fileset]
8658  Msg Info "Setting parameters/generics..."
8659  Msg Debug "Detailed parameters/generics: $generic_string"
8660 
8661 
8662  if {[IsVivado]} {
8663  # Dealing with project generics in Simulators
8664  set simulator [get_property target_simulator [current_project]]
8665  if {$mode == "create"} {
8666  SetGenericsSimulation $repo_path $design $simulator
8667  }
8668 
8669  WriteGenericsToBdIPs $mode $repo_path $design $generic_string
8670  }
8671  } elseif {[IsSynplify]} {
8672  Msg Info "Setting Synplify parameters/generics one by one..."
8673  foreach generic $generic_string {
8674  Msg Debug "Setting Synplify generic: $generic"
8675  set_option -hdl_param -set "$generic"
8676  }
8677  } elseif {[IsDiamond]} {
8678  Msg Info "Setting Diamond parameters/generics one by one..."
8679  prj_impl option -impl Implementation0 HDL_PARAM "$generic_string"
8680  } elseif {[IsVitisClassic] || [IsVitisUnified]} {
8681  if {[catch {set ws_apps [app list -dict]}]} {set ws_apps ""}
8682 
8683  foreach app_name [dict keys $ws_apps] {
8684  set defined_symbols [app config -name $app_name -get define-compiler-symbols]
8685  foreach generic_to_set [split [string trim $generic_string]] {
8686  set key [lindex [split $generic_to_set "="] 0]
8687  set value [lindex [split $generic_to_set "="] 1]
8688  if {[string match "32'h*" $value]} {
8689  set value [string map {"32'h" "0x"} $value]
8690  }
8691 
8692  foreach symbol [split $defined_symbols ";"] {
8693  if {[string match "$key=*" $symbol]} {
8694  Msg Debug "Generic $key found in $app_name, removing it..."
8695  app config -name $app_name -remove define-compiler-symbols "$symbol"
8696  }
8697  }
8698 
8699  Msg Info "Setting Vitis parameters/generics for app $app_name: $key=$value"
8700  app config -name $app_name define-compiler-symbols "$key=$value"
8701  }
8702  }
8703  }
8704 }
8705 
8706 ## @brief Applies generic values to IPs within block designs
8707 #
8708 # @param[in] mode create: to write the generics at creation time. synth to write at synthesis time
8709 # @param[in] repo_path The main path of the git repository
8710 # @param[in] proj The project name
8711 # @param[in] generic_string the string containing the generics to be applied
8712 proc WriteGenericsToBdIPs {mode repo_path proj generic_string} {
8713  Msg Debug "Parameters/generics passed to WriteGenericsToIP: $generic_string"
8714 
8715  set bd_ip_generics false
8716  set properties [ReadConf [lindex [GetConfFiles $repo_path/Top/$proj] 0]]
8717  if {[dict exists $properties "hog"]} {
8718  set propDict [dict get $properties "hog"]
8719  if {[dict exists $propDict "PASS_GENERICS_TO_BD_IPS"]} {
8720  set bd_ip_generics [dict get $propDict "PASS_GENERICS_TO_BD_IPS"]
8721  }
8722  }
8723 
8724  if {[string compare [string tolower $bd_ip_generics] "false"] == 0} {
8725  return
8726  }
8727 
8728  if {$mode == "synth"} {
8729  Msg Info "Attempting to apply generics pre-synthesis..."
8730  set PARENT_PRJ [get_property "PARENT.PROJECT_PATH" [current_project]]
8731  set workaround [open "$repo_path/Projects/$proj/.hog/presynth_workaround.tcl" "w"]
8732  puts $workaround "source \[lindex \$argv 0\];"
8733  puts $workaround "open_project \[lindex \$argv 1\];"
8734  puts $workaround "WriteGenericsToBdIPs \[lindex \$argv 2\] \[lindex \$argv 3\] \[lindex \$argv 4\] \[lindex \$argv 5\];"
8735  puts $workaround "close_project"
8736  close $workaround
8737  if {
8738  [catch {
8739  exec vivado -mode batch -source $repo_path/Projects/$proj/.hog/presynth_workaround.tcl \
8740  -tclargs $repo_path/Hog/Tcl/hog.tcl $PARENT_PRJ \
8741  "childprocess" $repo_path $proj $generic_string
8742  } errMsg] != 0
8743  } {
8744  Msg Error "Encountered an error while attempting workaround: $errMsg"
8745  }
8746  file delete $repo_path/Projects/$proj/.hog/presynth_workaround.tcl
8747  ResetRepoFiles "$repo_path/Projects/hog_reset_files"
8748  Msg Info "Done applying generics pre-synthesis."
8749  return
8750  }
8751 
8752  Msg Info "Looking for IPs to add generics to..."
8753  set ips_generic_string ""
8754  foreach generic_to_set [split [string trim $generic_string]] {
8755  set key [lindex [split $generic_to_set "="] 0]
8756  set value [lindex [split $generic_to_set "="] 1]
8757  append ips_generic_string "CONFIG.$key $value "
8758  }
8759 
8760 
8761  if {[string compare [string tolower $bd_ip_generics] "true"] == 0} {
8762  set ip_regex ".*"
8763  } else {
8764  set ip_regex $bd_ip_generics
8765  }
8766 
8767  set ip_list [get_ips -regex $ip_regex]
8768  Msg Debug "IPs found with regex \{$ip_regex\}: $ip_list"
8769 
8770  set regen_targets {}
8771 
8772  foreach {ip} $ip_list {
8773  set WARN_ABOUT_IP false
8774  set ip_props [list_property [get_ips $ip]]
8775 
8776  #Not sure if this is needed, but it's here to prevent potential errors with get_property
8777  if {[lsearch -exact $ip_props "IS_BD_CONTEXT"] == -1} {
8778  continue
8779  }
8780 
8781  if {[get_property "IS_BD_CONTEXT" [get_ips $ip]] eq "1"} {
8782  foreach {ip_prop} $ip_props {
8783  if {[dict exists $ips_generic_string $ip_prop]} {
8784  if {$WARN_ABOUT_IP == false} {
8785  lappend regen_targets [get_property SCOPE [get_ips $ip]]
8786  Msg Warning "The ip \{$ip\} contains generics that are set by Hog.\
8787  If this is IP is apart of a block design, the .bd file may contain stale, unused, values.\
8788  Hog will always apply the most up-to-date values to the IP during synthesis,\
8789  however these values may or may not be reflected in the .bd file."
8790  set WARN_ABOUT_IP true
8791  }
8792 
8793  # vivado is annoying about the format when setting generics for ips
8794  # this tries to find and set the format to what vivado likes
8795  set xci_path [get_property IP_FILE [get_ips $ip]]
8796  set generic_format [GetGenericFormatFromXci $ip_prop $xci_path]
8797  if {[string equal $generic_format "ERROR"]} {
8798  Msg Warning "Could not find format for generic $ip_prop in IP $ip. Skipping..."
8799  continue
8800  }
8801 
8802  set value_to_set [dict get $ips_generic_string $ip_prop]
8803  switch -exact $generic_format {
8804  "long" {
8805  if {[string match "32'h*" $value_to_set]} {
8806  scan [string map {"32'h" ""} $value_to_set] "%x" value_to_set
8807  }
8808  }
8809  "bool" {
8810  set value_to_set [expr {$value_to_set ? "true" : "false"}]
8811  }
8812  "float" {
8813  if {[string match "32'h*" $value_to_set]} {
8814  binary scan [binary format H* [string map {"32'h" ""} $value_to_set]] d value_to_set
8815  }
8816  }
8817  "bitString" {
8818  if {[string match "32'h*" $value_to_set]} {
8819  set value_to_set [string map {"32'h" "0x"} $value_to_set]
8820  }
8821  }
8822  "string" {
8823  set value_to_set [format "%s" $value_to_set]
8824  }
8825  default {
8826  Msg Warning "Unknown generic format $generic_format for IP $ip. Will attempt to pass as string..."
8827  }
8828  }
8829 
8830 
8831  Msg Info "The IP \{$ip\} contains: $ip_prop ($generic_format), setting it to $value_to_set."
8832  if {[catch {set_property -name $ip_prop -value $value_to_set -objects [get_ips $ip]} prop_error]} {
8833  Msg CriticalWarning "Failed to set property $ip_prop to $value_to_set for IP \{$ip\}: $prop_error"
8834  }
8835  }
8836  }
8837  }
8838  }
8839 
8840  foreach {regen_target} [lsort -unique $regen_targets] {
8841  Msg Info "Regenerating target: $regen_target"
8842  if {[catch {generate_target -force all [get_files $regen_target]} prop_error]} {
8843  Msg CriticalWarning "Failed to regen targets: $prop_error"
8844  }
8845  }
8846 }
8847 
8848 ## @brief Returns the format of a generic from an XML file
8849 ## @param[in] generic_name: The name of the generic
8850 ## @param[in] xml_file: The path to the XML XCI file
8851 proc GetGenericFormatFromXciXML {generic_name xml_file} {
8852  if {![file exists $xml_file]} {
8853  Msg Error "Could not find XML file: $xml_file"
8854  return "ERROR"
8855  }
8856 
8857  set fp [open $xml_file r]
8858  set xci_data [read $fp]
8859  close $fp
8860 
8861  set paramType "string"
8862  set modelparam_regex [format {^.*\y%s\y.*$} [string map {"CONFIG." "MODELPARAM_VALUE."} $generic_name]]
8863  set format_regex {format="([^"]+)"}
8864 
8865  set line [lindex [regexp -inline -line $modelparam_regex $xci_data] 0]
8866  Msg Debug "line: $line"
8867 
8868  if {[regexp $format_regex $line match format_value]} {
8869  Msg Debug "Extracted: $format_value format from xml"
8870  set paramType $format_value
8871  } else {
8872  Msg Debug "No format found, using string"
8873  }
8874 
8875  return $paramType
8876 }
8877 
8878 ## @brief Returns the format of a generic from an XCI file
8879 ## @param[in] generic_name: The name of the generic
8880 ## @param[in] xci_file: The path to the XCI file
8881 proc GetGenericFormatFromXci {generic_name xci_file} {
8882  if {![file exists $xci_file]} {
8883  Msg Error "Could not find XCI file: $xci_file"
8884  return "ERROR"
8885  }
8886 
8887  set fp [open $xci_file r]
8888  set xci_data [read $fp]
8889  close $fp
8890 
8891  set paramType "string"
8892  if {[string first "xilinx.com:schema:json_instance:1.0" $xci_data] == -1} {
8893  Msg Debug "XCI format is not JSON, trying XML..."
8894  set xml_file "[file rootname $xci_file].xml"
8895  return [GetGenericFormatFromXciXML $generic_name $xml_file]
8896  }
8897 
8898  set generic_name [string map {"CONFIG." ""} $generic_name]
8899  set ip_inst [ParseJSON $xci_data "ip_inst"]
8900  set parameters [dict get $ip_inst parameters]
8901  set component_parameters [dict get $parameters component_parameters]
8902  if {[dict exists $component_parameters $generic_name]} {
8903  set generic_info [dict get $component_parameters $generic_name]
8904  if {[dict exists [lindex $generic_info 0] format]} {
8905  set paramType [dict get [lindex $generic_info 0] format]
8906  Msg Debug "Extracted: $paramType format from xci"
8907  return $paramType
8908  }
8909  Msg Debug "No format found, using string"
8910  return $paramType
8911  } else {
8912  return "ERROR"
8913  }
8914 }
8915 
8916 
8917 ## @brief Returns the gitlab-ci.yml snippet for a CI stage and a defined project
8918 #
8919 # @param[in] proj_name: The project name
8920 # @param[in] ci_confs: Dictionary with CI configurations
8921 #
8922 proc WriteGitLabCIYAML {proj_name {ci_conf ""}} {
8923  if {[catch {package require yaml 0.3.3} YAMLPACKAGE]} {
8924  Msg CriticalWarning "Cannot find package YAML.\n Error message: $YAMLPACKAGE. \
8925  If you are running on tclsh, you can fix this by installing package \"tcllib\""
8926  return -1
8927  }
8928 
8929  set job_list []
8930  if {$ci_conf != ""} {
8931  set ci_confs [ReadConf $ci_conf]
8932  foreach sec [dict keys $ci_confs] {
8933  if {[string first : $sec] == -1} {
8934  lappend job_list $sec
8935  }
8936  }
8937  } else {
8938  set job_list {"generate_project" "simulate_project"}
8939  set ci_confs ""
8940  }
8941 
8942  set out_yaml [huddle create]
8943  foreach job $job_list {
8944  # Check main project configurations
8945  set huddle_tags [huddle list]
8946  set tag_section ""
8947  set sec_dict [dict create]
8948 
8949  if {$ci_confs != ""} {
8950  foreach var [dict keys [dict get $ci_confs $job]] {
8951  if {$var == "tags"} {
8952  set tag_section "tags"
8953  set tags [dict get [dict get $ci_confs $job] $var]
8954  set tags [split $tags ","]
8955  foreach tag $tags {
8956  set tag_list [huddle list $tag]
8957  set huddle_tags [huddle combine $huddle_tags $tag_list]
8958  }
8959  } else {
8960  dict set sec_dict $var [dict get [dict get $ci_confs $job] $var]
8961  }
8962  }
8963  }
8964 
8965  # Check if there are extra variables in the conf file
8966  set huddle_variables [huddle create "PROJECT_NAME" $proj_name "extends" ".vars"]
8967  if {[dict exists $ci_confs "$job:variables"]} {
8968  set var_dict [dict get $ci_confs $job:variables]
8969  foreach var [dict keys $var_dict] {
8970  # puts [dict get $var_dict $var]
8971  set value [dict get $var_dict "$var"]
8972  set var_inner [huddle create "$var" "$value"]
8973  set huddle_variables [huddle combine $huddle_variables $var_inner]
8974  }
8975  }
8976 
8977 
8978  set middle [huddle create "extends" ".$job" "variables" $huddle_variables]
8979  foreach sec [dict keys $sec_dict] {
8980  set value [dict get $sec_dict $sec]
8981  set var_inner [huddle create "$sec" "$value"]
8982  set middle [huddle combine $middle $var_inner]
8983  }
8984  if {$tag_section != ""} {
8985  set middle2 [huddle create "$tag_section" $huddle_tags]
8986  set middle [huddle combine $middle $middle2]
8987  }
8988 
8989  set outer [huddle create "$job:$proj_name" $middle]
8990  set out_yaml [huddle combine $out_yaml $outer]
8991  }
8992 
8993  return [string trimleft [yaml::huddle2yaml $out_yaml] "-"]
8994 }
8995 
8996 # @brief Write the content of Hog-library-dictionary created from the project into a .src/.ext/.con list file
8997 #
8998 # @param[in] libs The Hog-Library dictionary with the list of files in the project to write
8999 # @param[in] props The Hog-library dictionary with the file sets
9000 # @param[in] list_path The path of the output list file
9001 # @param[in] repo_path The main repository path
9002 # @param[in] ext_path The external path
9003 proc WriteListFiles {libs props list_path repo_path {ext_path ""}} {
9004  # Writing simulation list files
9005  foreach lib [dict keys $libs] {
9006  if {[llength [DictGet $libs $lib]] > 0} {
9007  set list_file_name $list_path$lib
9008  set list_file [open $list_file_name w]
9009  Msg Info "Writing $list_file_name..."
9010  puts $list_file "#Generated by Hog on [clock format [clock seconds] -format "%Y-%m-%d %H:%M:%S"]"
9011  foreach file [DictGet $libs $lib] {
9012  # Retrieve file properties from prop list
9013  set prop [DictGet $props $file]
9014  # Check if file is local to the repository or external
9015  if {[RelativeLocal $repo_path $file] != ""} {
9016  set file_path [RelativeLocal $repo_path $file]
9017  puts $list_file "$file_path $prop"
9018  } elseif {[RelativeLocal $ext_path $file] != ""} {
9019  set file_path [RelativeLocal $ext_path $file]
9020  set ext_list_file [open "[file rootname $list_file].ext" a]
9021  puts $ext_list_file "$file_path $prop"
9022  close $ext_list_file
9023  } else {
9024  # File is not relative to repo or ext_path... Write a Warning and continue
9025  Msg Warning "The path of file $file is not relative to your repository. Please check!"
9026  }
9027  }
9028  close $list_file
9029  }
9030  }
9031 }
9032 
9033 # @brief Write the content of Hog-library-dictionary created from the project into a .sim list file
9034 #
9035 # @param[in] libs The Hog-Library dictionary with the list of files in the project to write
9036 # @param[in] props The Hog-library dictionary with the file sets
9037 # @param[in] simsets The Hog-library dictionary with the file sets (relevant only for simulation)
9038 # @param[in] list_path The path of the output list file
9039 # @param[in] repo_path The main repository path
9040 # @param[in] force If 1, it will overwrite the existing list files
9041 proc WriteSimListFile {simset libs props simsets list_path repo_path {force 0}} {
9042  # Writing simulation list file
9043  set list_file_name $list_path/${simset}.sim
9044  if {$force == 0 && [file exists $list_file_name]} {
9045  Msg Info "List file $list_file_name already exists, skipping..."
9046  continue
9047  }
9048 
9049  set list_file [open $list_file_name a+]
9050 
9051  # Write the header with the simulator
9052  puts $list_file "\[files\]"
9053  Msg Info "Writing $list_file_name..."
9054  foreach lib [DictGet $simsets $simset] {
9055  foreach file [DictGet $libs $lib] {
9056  # Retrieve file properties from prop list
9057  set prop [DictGet $props $file]
9058  # Check if file is local to the repository or external
9059  if {[RelativeLocal $repo_path $file] != ""} {
9060  set file_path [RelativeLocal $repo_path $file]
9061  set lib_name [file rootname $lib]
9062  if {$lib_name != $simset && [file extension $file] == ".vhd" && [file extension $file] == ""} {
9063  lappend prop "lib=$lib_name"
9064  }
9065  puts $list_file "$file_path $prop"
9066  } else {
9067  # File is not relative to repo or ext_path... Write a Warning and continue
9068  Msg Warning "The path of file $file is not relative to your repository. Please check!"
9069  }
9070  }
9071  }
9072  close $list_file
9073 }
9074 
9075 
9076 ## @brief Write into a file, and if the file exists, it will append the string
9077 #
9078 # @param[out] File The log file to write into the message
9079 # @param[in] msg The message text
9080 proc WriteToFile {File msg} {
9081  set f [open $File a+]
9082  puts $f $msg
9083  close $f
9084 }
9085 
9086 ## Write the resource utilization table into a a file (Vivado only)
9087 #
9088 # @param[in] input the input .rpt report file from Vivado
9089 # @param[in] output the output file
9090 # @param[in] project_name the name of the project
9091 # @param[in] run synthesis or implementation
9092 proc WriteUtilizationSummary {input output project_name run} {
9093  set f [open $input "r"]
9094  set o [open $output "a"]
9095  puts $o "## $project_name $run Utilization report\n\n"
9096  struct::matrix util_m
9097  util_m add columns 14
9098  util_m add row
9099  if {[GetIDEVersion] >= 2021.0} {
9100  util_m add row "| **Site Type** | **Used** | **Fixed** | **Prohibited** | **Available** | **Util%** |"
9101  util_m add row "| --- | --- | --- | --- | --- | --- |"
9102  } else {
9103  util_m add row "| **Site Type** | **Used** | **Fixed** | **Available** | **Util%** |"
9104  util_m add row "| --- | --- | --- | --- | --- |"
9105  }
9106 
9107  set luts 0
9108  set regs 0
9109  set uram 0
9110  set bram 0
9111  set dsps 0
9112  set ios 0
9113 
9114  while {[gets $f line] >= 0} {
9115  if {([string first "| CLB LUTs" $line] >= 0 || [string first "| Slice LUTs" $line] >= 0) && $luts == 0} {
9116  util_m add row $line
9117  set luts 1
9118  }
9119  if {([string first "| CLB Registers" $line] >= 0 || [string first "| Slice Registers" $line] >= 0) && $regs == 0} {
9120  util_m add row $line
9121  set regs 1
9122  }
9123  if {[string first "| Block RAM Tile" $line] >= 0 && $bram == 0} {
9124  util_m add row $line
9125  set bram 1
9126  }
9127  if {[string first "URAM " $line] >= 0 && $uram == 0} {
9128  util_m add row $line
9129  set uram 1
9130  }
9131  if {[string first "DSPs" $line] >= 0 && $dsps == 0} {
9132  util_m add row $line
9133  set dsps 1
9134  }
9135  if {[string first "Bonded IOB" $line] >= 0 && $ios == 0} {
9136  util_m add row $line
9137  set ios 1
9138  }
9139  }
9140  util_m add row
9141 
9142  close $f
9143  puts $o [util_m format 2string]
9144  close $o
9145 }
9146 
9147 # Check Git Version when sourcing hog.tcl
9148 if {[GitVersion 2.7.2] == 0} {
9149  Msg Error "Found Git version older than 2.7.2. Hog will not work as expected, exiting now."
9150 }
9151 
9152 ## @brief Tries to find the coorrect command to be launched for curl
9153 #
9154 # @details If running in vivado shell you may need to unsed LD_LIBRARY_PATH befor running curl to avoid conflicts with vivado libraries.
9155 # This procedure tests curl if execution is correct returns "curl"
9156 # 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.
9157 # If both fail returns "curl", this will most probably generate failures later
9158 proc GetCurl {{gitlab_url "https://gitlab.com"}} {
9159  if {[auto_execok curl] == ""} {
9160  Msg Warning "Cannot find a working curl invocation"
9161  return 0
9162  }
9163 
9164  if {[IsTclsh]} {
9165  set cmd [list curl --silent --show-error]
9166  } else {
9167  set cmd [list env -u LD_LIBRARY_PATH curl --silent --show-error]
9168  }
9169 
9170  if {![catch {exec {*}$cmd -I $gitlab_url}]} {
9171  return $cmd
9172  } else {
9173  Msg Warning "Impossible to contact $gitlab_url"
9174  return 0
9175  }
9176 }