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