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