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