Hog Hog2026.1-2
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 
4084  if {[string first "/eos/" $ip_path] == 0} {
4085  # IP Path is on EOS
4086  set on_eos 1
4087  } else {
4088  set on_eos 0
4089  }
4090 
4091  if {$on_eos == 1} {
4092  lassign [eos "ls $ip_path"] ret result
4093  if {$ret != 0} {
4094  Msg CriticalWarning "Could not run ls for for EOS path: $ip_path (error: $result). \
4095  Either the drectory does not exist or there are (temporary) problem with EOS."
4096  cd $old_path
4097  return -1
4098  } else {
4099  Msg Info "IP remote directory path, on EOS, is set to: $ip_path"
4100  }
4101  } else {
4102  file mkdir $ip_path
4103  }
4104 
4105  if {!([file exists $xci_file])} {
4106  Msg CriticalWarning "Could not find $xci_file."
4107  cd $old_path
4108  return -1
4109  }
4110 
4111 
4112  set xci_path [file dirname $xci_file]
4113  set xci_name [file tail $xci_file]
4114  set xci_ip_name [file rootname [file tail $xci_file]]
4115  set xci_dir_name [file tail $xci_path]
4116  set gen_path $gen_dir
4117 
4118  set hash [Md5Sum $xci_file]
4119  set file_name $xci_name\_$hash
4120 
4121  Msg Info "Preparing to $what_to_do IP: $xci_name..."
4122 
4123  if {$what_to_do eq "push"} {
4124  set will_copy 0
4125  set will_remove 0
4126  if {$on_eos == 1} {
4127  lassign [eos "ls $ip_path/$file_name.tar"] ret result
4128  if {$ret != 0} {
4129  set will_copy 1
4130  } else {
4131  if {$force == 0} {
4132  Msg Info "IP already in the EOS repository, will not copy..."
4133  } else {
4134  Msg Info "IP already in the EOS repository, will forcefully replace..."
4135  set will_copy 1
4136  set will_remove 1
4137  }
4138  }
4139  } else {
4140  if {[file exists "$ip_path/$file_name.tar"]} {
4141  if {$force == 0} {
4142  Msg Info "IP already in the local repository, will not copy..."
4143  } else {
4144  Msg Info "IP already in the local repository, will forcefully replace..."
4145  set will_copy 1
4146  set will_remove 1
4147  }
4148  } else {
4149  set will_copy 1
4150  }
4151  }
4152 
4153  if {$will_copy == 1} {
4154  # Check if there are files in the .gen directory first and copy them into the right place
4155  Msg Info "Looking for generated files in $gen_path..."
4156  set ip_gen_files [glob -nocomplain $gen_path/*]
4157 
4158  #here we should remove the .xci file from the list if it's there
4159 
4160  if {[llength $ip_gen_files] > 0} {
4161  Msg Info "Found some IP synthesised files matching $xci_ip_name"
4162  if {$will_remove == 1} {
4163  Msg Info "Removing old synthesised directory $ip_path/$file_name.tar..."
4164  if {$on_eos == 1} {
4165  eos "rm -rf $ip_path/$file_name.tar" 5
4166  } else {
4167  file delete -force "$ip_path/$file_name.tar"
4168  }
4169  }
4170 
4171  Msg Info "Creating local archive with IP generated files..."
4172  set tar_files []
4173 
4174  foreach f $ip_gen_files {
4175  lappend tar_files "[Relative [file normalize $repo_path] $f]"
4176  }
4177 
4178  ::tar::create $file_name.tar $tar_files
4179 
4180  Msg Info "Copying IP generated files for $xci_name..."
4181  if {$on_eos == 1} {
4182  lassign [ExecuteRet xrdcp -f -s $file_name.tar $::env(EOS_MGM_URL)//$ip_path/] ret msg
4183  if {$ret != 0} {
4184  Msg CriticalWarning "Something went wrong when copying the IP files to EOS. Error message: $msg"
4185  }
4186  } else {
4187  Copy "$file_name.tar" "$ip_path/"
4188  }
4189  Msg Info "Removing local archive"
4190  file delete $file_name.tar
4191  } else {
4192  Msg Warning "Could not find synthesized files matching $gen_path/$file_name*"
4193  }
4194  }
4195  } elseif {$what_to_do eq "pull"} {
4196  if {$on_eos == 1} {
4197  lassign [eos "ls $ip_path/$file_name.tar"] ret result
4198  if {$ret != 0} {
4199  Msg Info "Nothing for $xci_name was found in the EOS repository, cannot pull."
4200  cd $old_path
4201  return -1
4202  } else {
4203  set remote_tar "$::env(EOS_MGM_URL)//$ip_path/$file_name.tar"
4204  Msg Info "IP $xci_name found in the repository $remote_tar, copying it locally to $repo_path..."
4205 
4206  lassign [ExecuteRet xrdcp -f -r -s $remote_tar $repo_path] ret msg
4207  if {$ret != 0} {
4208  Msg CriticalWarning "Something went wrong when copying the IP files to EOS. Error message: $msg"
4209  }
4210  }
4211  } else {
4212  if {[file exists "$ip_path/$file_name.tar"]} {
4213  Msg Info "IP $xci_name found in local repository $ip_path/$file_name.tar, copying it locally to $repo_path..."
4214  Copy $ip_path/$file_name.tar $repo_path
4215  } else {
4216  Msg Info "Nothing for $xci_name was found in the local IP repository, cannot pull."
4217  cd $old_path
4218  return -1
4219  }
4220  }
4221 
4222  if {[file exists $file_name.tar]} {
4223  remove_files $xci_file
4224  Msg Info "Extracting IP files from archive to $repo_path..."
4225  ::tar::untar $file_name.tar -dir $repo_path -noperms
4226  Msg Info "Removing local archive"
4227  file delete $file_name.tar
4228  add_files -norecurse -fileset sources_1 $xci_file
4229  }
4230  }
4231  cd $old_path
4232  return 0
4233 }
4234 
4235 ## Convert hex version to M.m.p string
4236 #
4237 # @param[in] version the version (in 32-bit hexadecimal format 0xMMmmpppp) to be converted
4238 #
4239 # @return a string containing the version in M.m.p format
4240 #
4241 proc HexVersionToString {version} {
4242  scan [string range $version 0 1] %x M
4243  scan [string range $version 2 3] %x m
4244  scan [string range $version 4 7] %x c
4245  return "$M.$m.$c"
4246 }
4247 
4248 # @brief Import TCL Lib from an external installation for Libero, Synplify and Diamond
4249 proc ImportTclLib {} {
4250  global env
4251  if {[IsLibero] || [IsDiamond] || [IsSynplify]} {
4252  if {[info exists env(HOG_TCLLIB_PATH)]} {
4253  lappend auto_path $env(HOG_TCLLIB_PATH)
4254  return 1
4255  } else {
4256  puts "ERROR: To run Hog with Microsemi Libero SoC or Lattice Diamond, you need to define the HOG_TCLLIB_PATH variable."
4257  return 0
4258  }
4259  }
4260 }
4261 
4262 # @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
4263 #
4264 # @param[in] script The launch.tcl script
4265 # @param[in] tcl_path The launch.tcl script path
4266 # @param[in] parameters The allowed parameters for launch.tcl
4267 # @param[in] commands The allowed directives for launch.tcl
4268 # @param[in] argv The input arguments passed to launch.tcl
4269 # @param[in] custom_commands Custom commands to be added to the list of commands
4270 
4271 proc InitLauncher {script tcl_path parameters commands argv {custom_commands ""}} {
4272  set repo_path [file normalize "$tcl_path/../.."]
4273  set old_path [pwd]
4274  set bin_path [file normalize "$tcl_path/../../bin"]
4275  set top_path [file normalize "$tcl_path/../../Top"]
4276 
4277  set cmd_lines [split $commands "\n"]
4278 
4279  set command_options [dict create]
4280  set directive_descriptions [dict create]
4281  set directive_names [dict create]
4282  set common_directive_names [dict create]
4283  set custom_command ""
4284  set custom_command_options ""
4285 
4286  foreach l $cmd_lines {
4287  #excludes direcitve with a # just after the \{
4288  if {[regexp {\\(.*) \{\#} $l minc d]} {
4289  lappend directives_with_projects $d
4290  }
4291 
4292  #gets all the regexes
4293  if {[regexp {\\(.*) \{} $l minc regular_expression]} {
4294  lappend directive_regex $regular_expression
4295  }
4296 
4297  #gets all common directives
4298  if {[regexp {\#\s*NAME(\*)?:\s*(.*)\s*} $l minc star name]} {
4299  dict set directive_names $name $regular_expression
4300  if {$star eq "*"} {
4301  dict set common_directive_names $name $regular_expression
4302  }
4303  }
4304  set directive_names [DictSort $directive_names]
4305  set common_directive_names [DictSort $common_directive_names]
4306 
4307  #gets all the descriptions
4308  if {[regexp {\#\s*DESCRIPTION:\s*(.*)\s*} $l minc x]} {
4309  dict set directive_descriptions $regular_expression $x
4310  }
4311 
4312  #gets all the list of options
4313  if {[regexp {\#\s*OPTIONS:\s*(.*)\s*} $l minc x]} {
4314  dict set command_options $regular_expression [split [regsub -all {[ \t\n]+} $x {}] ","]
4315  }
4316  }
4317 
4318  set short_usage "usage: ./Hog/Do \[OPTIONS\] <directive> \[project\]\n\nMost common directives (case insensitive):"
4319 
4320  dict for {key value} $common_directive_names {
4321  set short_usage "$short_usage\n - $key: [dict get $directive_descriptions $value]"
4322  }
4323  # VSCODE COMMENT"
4324 
4325  if {[string length $custom_commands] > 0} {
4326  Msg Debug "Found custom commands to add to short short_usage."
4327  set short_usage "$short_usage\n\nCustom commands:"
4328  dict for {key command} $custom_commands {
4329  Msg Debug "Adding $key : [dict get $command DESCRIPTION]"
4330  set short_usage "$short_usage\n - $key: [dict get $command DESCRIPTION]"
4331  }
4332  }
4333 
4334 
4335 
4336  set short_usage "$short_usage\n\n\
4337  To see all the available directives, run:\n./Hog/Do HELP\n\n\
4338  To list available options for the chosen directive run:\n\
4339  ./Hog/Do <directive> HELP\n
4340  "
4341 
4342  set usage "usage: ./Hog/Do \[OPTIONS\] <directive> \[project\]\n\nDirectives (case insensitive):"
4343 
4344  dict for {key value} $directive_names {
4345  set usage "$usage\n - $key: [dict get $directive_descriptions $value]"
4346  }
4347 
4348  # if length of custom commands is greater than 0, add them to the short usage"
4349  if {[string length $custom_commands] > 0} {
4350  Msg Debug "Found custom commands to add to short usage."
4351  set usage "$usage\n\nCustom commands:"
4352  dict for {key command} $custom_commands {
4353  Msg Debug "Adding $key : [dict get $command DESCRIPTION]"
4354  set usage "$usage\n - $key: [dict get $command DESCRIPTION]"
4355  }
4356  }
4357 
4358 
4359  set usage "$usage\n\nTo list available options for the chosen directive run:\n./Hog/Do <directive> HELP"
4360 
4361  if {[IsTclsh]} {
4362  #Just display the logo the first time, not when the script is run in the IDE
4363  Logo $repo_path
4364  }
4365 
4366  # Check if HogEnv.conf exists and parse it
4367  if {[file exists [Hog::LoggerLib::GetUserFilePath "HogEnv.conf"]] } {
4368  Msg Debug "HogEnv.conf found"
4369  set loggerdict [Hog::LoggerLib::ParseTOML [Hog::LoggerLib::GetUserFilePath "HogEnv.conf" ]]
4370  set HogEnvDict [Hog::LoggerLib::GetTOMLDict]
4371  Hog::LoggerLib::PrintTOMLDict $HogEnvDict
4372  }
4373 
4374 
4375 
4376  if {[catch {package require cmdline} ERROR]} {
4377  Msg Debug "The cmdline Tcl package was not found, sourcing it from Hog..."
4378  source $tcl_path/utils/cmdline.tcl
4379  }
4380 
4381  set argv [regsub -all {(?i) HELP\y} $argv " -help"]
4382 
4383 
4384  #Gather up all custom parameters
4385  #NOTE: right now user can accidentally redefine their own custom parameters, there is no check for that...
4386  set custom_parameters [list]
4387  dict for {key command} $custom_commands {
4388  set custom_parameters [concat $custom_parameters [dict get $command CUSTOM_OPTIONS]]
4389  }
4390 
4391  lassign [GetOptions $argv [concat $custom_parameters $parameters]] option_list arg_list
4392 
4393  if {[IsInList "-all" $option_list]} {
4394  set list_all 1
4395  } else {
4396  set list_all 2
4397  }
4398 
4399  #option_list will be emptied by the next instruction
4400 
4401  # Argv here is modified and the options are removed
4402  set directive [string toupper [lindex $arg_list 0]]
4403  set min_n_of_args 0
4404  set max_n_of_args 2
4405  set argument_is_no_project 1
4406 
4407  set NO_DIRECTIVE_FOUND 0
4408  switch -regexp -- $directive "$commands"
4409  if {$NO_DIRECTIVE_FOUND == 1} {
4410  if {[string length $custom_commands] > 0 && [dict exists $custom_commands $directive]} {
4411  set custom_command $directive
4412  set custom_command_hog_parameters [dict get $custom_commands $directive OPTIONS]
4413  set custom_command_options [dict get $custom_commands $directive CUSTOM_OPTIONS]
4414  set custom_command_options [concat $custom_command_hog_parameters $custom_command_options]
4415  } else {
4416  Msg Status "ERROR: Unknown directive $directive.\n\n"
4417  puts $usage
4418  exit
4419  }
4420  }
4421 
4422  if {[IsInList $directive $directives_with_projects 1]} {
4423  set argument_is_no_project 0
4424  }
4425 
4426  if {[IsInList "-help" $option_list] || [IsInList "-?" $option_list] || [IsInList "-h" $option_list]} {
4427  if {$directive != ""} {
4428  if {[IsInList $directive $directives_with_projects 1]} {
4429  puts "usage: ./Hog/Do \[OPTIONS\] $directive <project>\n"
4430  } elseif {[regexp "^COMPSIM(LIB)?$" $directive]} {
4431  puts "usage: ./Hog/Do \[OPTIONS\] $directive <simulator>\n"
4432  } else {
4433  puts "usage: ./Hog/Do \[OPTIONS\] $directive \n"
4434  }
4435 
4436  dict for {dir desc} $directive_descriptions {
4437  if {[regexp $dir $directive]} {
4438  puts "$desc\n"
4439  break
4440  }
4441  }
4442 
4443 
4444  #if custom command, parse custom options instead
4445  if {$custom_command ne ""} {
4446  if {[llength $custom_command_options] > 0} {
4447  puts "Available options:"
4448  }
4449  foreach custom_option $custom_command_options {
4450  set n [llength $custom_option]
4451  if {$n == 2} {
4452  lassign $custom_option opt help
4453  puts " -$opt"
4454  puts " $help"
4455  } elseif {$n == 3} {
4456  lassign $custom_option opt def help
4457  puts " -$opt <argument>"
4458  if {$def ne ""} {
4459  puts " $help (default: $def)"
4460  } else {
4461  puts " $help"
4462  }
4463  } else {
4464  Msg Warning "Custom option spec has invalid arity (expected 2 or 3): $custom_option"
4465  }
4466  }
4467  }
4468 
4469  dict for {dir opts} $command_options {
4470  if {[regexp $dir $directive]} {
4471  puts "Available options:"
4472  foreach opt $opts {
4473  foreach par $parameters {
4474  if {$opt == [lindex $par 0]} {
4475  if {[regexp {\.arg$} $opt]} {
4476  set opt_name [regsub {\.arg$} $opt ""]
4477  puts " -$opt_name <argument>"
4478  } else {
4479  puts " -$opt"
4480  }
4481  puts " [lindex $par [llength $par]-1]"
4482  }
4483  }
4484  }
4485  puts ""
4486  }
4487  }
4488  } else {
4489  puts $usage
4490  }
4491  # Msg Info [cmdline::usage $parameters $usage]
4492  exit 0
4493  }
4494 
4495  if {$custom_command ne ""} {
4496  set parameters [concat $parameters $custom_command_options]
4497  }
4498 
4499  if {[catch {array set options [cmdline::getoptions option_list $parameters $usage]} err]} {
4500  Msg Status "\nERROR: Syntax error, probably unknown option.\n\n USAGE: $err"
4501  exit 1
4502  }
4503 
4504  if {[llength $arg_list] <= $min_n_of_args || [llength $arg_list] > $max_n_of_args} {
4505  Msg Status "\nERROR: Wrong number of arguments: [llength $argv]"
4506  puts $short_usage
4507  exit 1
4508  }
4509 
4510  set project [lindex $arg_list 1]
4511 
4512  if {$argument_is_no_project == 0} {
4513  # Remove leading Top/ or ./Top/ if in project_name
4514  regsub "^(\./)?Top/" $project "" project
4515  # Remove trailing / and spaces if in project_name
4516  regsub "/? *\$" $project "" project
4517  set proj_conf [ProjectExists $project $repo_path]
4518  } else {
4519  set proj_conf 0
4520  }
4521 
4522  Msg Debug "Option list:"
4523  foreach {key value} [array get options] {
4524  Msg Debug "$key => $value"
4525  }
4526 
4527  set cmd ""
4528 
4529  if {[IsTclsh]} {
4530  # command is filled with the IDE exectuable when this function is called by Tcl scrpt
4531  if {$proj_conf != 0} {
4532  CheckLatestHogRelease $repo_path
4533 
4534  lassign [GetIDECommand $proj_conf] cmd before_tcl_script after_tcl_script end_marker
4535  Msg Info "Project $project uses $cmd IDE"
4536 
4537  ## The following is the IDE command to launch:
4538  set command "$cmd $before_tcl_script$script$after_tcl_script$argv$end_marker"
4539  } else {
4540  if {$custom_command ne ""} {
4541  if { [dict exists $custom_commands $directive IDE] } {
4542  lassign [GetIDECommand "" [dict get $custom_commands $directive IDE]] cmd before_tcl_script after_tcl_script end_marker
4543  Msg Info "Custom command: $custom_command uses $cmd IDE"
4544  set command "$cmd $before_tcl_script$script$after_tcl_script$argv$end_marker"
4545  } else {
4546  set command "custom_tcl"
4547  }
4548  } elseif {$argument_is_no_project == 1} {
4549  set command -4
4550  Msg Debug "$project will be used as first argument"
4551  } elseif {$project != ""} {
4552  #Project not given
4553  set command -1
4554  } elseif {$min_n_of_args < 0} {
4555  #Project not needed
4556  set command -3
4557  } else {
4558  #Project not found
4559  set command -2
4560  }
4561  }
4562  } else {
4563  # When the launcher is executed from within an IDE, command is set to 0
4564  set command 0
4565  }
4566 
4567  set project_group [file dirname $project]
4568  set project_name $project
4569  set project [file tail $project]
4570  Msg Debug "InitLauncher: project_group=$project_group, project_name=$project_name, project=$project"
4571 
4572  return [list $directive $project $project_name $project_group $repo_path $old_path $bin_path $top_path $usage $short_usage $command $cmd [array get options]]
4573 }
4574 
4575 # @brief Returns 1 if a commit is an ancestor of another, otherwise 0
4576 #
4577 # @param[in] ancestor The potential ancestor commit
4578 # @param[in] commit The potential descendant commit
4579 proc IsCommitAncestor {ancestor commit} {
4580  lassign [GitRet "merge-base --is-ancestor $ancestor $commit"] status result
4581  if {$status == 0} {
4582  return 1
4583  } else {
4584  return 0
4585  }
4586 }
4587 
4588 proc IsDiamond {} {
4589  return [expr {[info commands sys_install] != ""}]
4590 }
4591 
4592 ## @brief Returns true if the IDE is MicroSemi Libero
4593 proc IsLibero {} {
4594  return [expr {[info commands get_libero_version] != ""}]
4595 }
4596 
4597 # @brief Returns 1 if an element is a list, 0 otherwise
4598 #
4599 # @param[in] element The element to search
4600 # @param[in] list The list to search into
4601 # @param[in] regex An optional regex to match. If 0, the element should match exactly an object in the list
4602 # @param[in] nocase If 1, perform case-insensitive comparison
4603 proc IsInList {element list {regex 0} {nocase 0}} {
4604  foreach x $list {
4605  if {$regex == 1} {
4606  if {$nocase == 1} {
4607  if {[regexp -nocase $x $element]} {
4608  return 1
4609  }
4610  } else {
4611  if {[regexp $x $element]} {
4612  return 1
4613  }
4614  }
4615  } elseif {$regex == 0} {
4616  if {$nocase == 1} {
4617  if {[string tolower $x] eq [string tolower $element]} {
4618  return 1
4619  }
4620  } else {
4621  if {$x eq $element} {
4622  return 1
4623  }
4624  }
4625  }
4626  }
4627  return 0
4628 }
4629 
4630 
4631 ## @brief Returns true, if the IDE is ISE/PlanAhead
4632 proc IsISE {} {
4633  if {[IsXilinx]} {
4634  return [expr {[string first PlanAhead [version]] == 0}]
4635  } else {
4636  return 0
4637  }
4638 }
4639 
4640 ## @brief Returns true, if IDE is Quartus
4641 proc IsQuartus {} {
4642  if {[catch {package require ::quartus::flow} result]} {
4643  # not available
4644  return 0
4645  } else {
4646  # available
4647  return 1
4648  }
4649 }
4650 
4651 ## Check if a path is absolute or relative
4652 #
4653 # @param[in] the path to check
4654 #
4655 proc IsRelativePath {path} {
4656  if {[string index $path 0] == "/" || [string index $path 0] == "~"} {
4657  return 0
4658  } else {
4659  return 1
4660  }
4661 }
4662 
4663 ## @brief Returns true if the Synthesis tool is Synplify
4664 proc IsSynplify {} {
4665  return [expr {[info commands program_version] != ""}]
4666 }
4667 
4668 ## @brief Returns true, if we are in tclsh
4669 proc IsTclsh {} {
4670  return [expr {![IsQuartus] && ![IsXilinx] && ![IsVitisClassic] && ![IsLibero] && ![IsSynplify] && ![IsDiamond]}]
4671 }
4672 
4673 # @brief Find out if the given file is a Verilog or SystemVerilog file
4674 #
4675 # @param[in] file The file to check
4676 # @param[out] 1 if it's Verilog/SystemVerilog 0 if it's not
4677 #
4678 proc IsVerilog {file} {
4679  if {[file extension $file] == ".v" || [file extension $file] == ".sv"} {
4680  return 1
4681  } else {
4682  return 0
4683  }
4684 }
4685 
4686 ## @brief Find out if the given Xilinx part is a Vesal chip
4687 #
4688 # @param[out] 1 if it's Versal 0 if it's not
4689 # @param[in] part The FPGA part
4690 #
4691 proc IsVersal {part} {
4692  if {[get_property ARCHITECTURE [get_parts $part]] eq "versal"} {
4693  return 1
4694  } else {
4695  return 0
4696  }
4697 }
4698 
4699 ## @brief Returns true, if the IDE is Vivado
4700 proc IsVivado {} {
4701  if {[IsXilinx]} {
4702  return [expr {[string first Vivado [version]] == 0}]
4703  } else {
4704  return 0
4705  }
4706 }
4707 
4708 ## @brief Return true, if the IDE is Xilinx (Vivado or ISE)
4709 proc IsXilinx {} {
4710  if {[info commands version] != ""} {
4711  set current_version [version]
4712  if {[string first PlanAhead $current_version] == 0 || [string first Vivado $current_version] == 0} {
4713  return 1
4714  } elseif {[string first xsct $current_version] == 0} {
4715  return 0
4716  } else {
4717  Msg Warning "This IDE has the version command but it is not PlanAhead or Vivado: $current_version"
4718  return 0
4719  }
4720  } else {
4721  return 0
4722  }
4723 }
4724 
4725 ## @brief Returns true, if the IDE is vitis_classic
4726 proc IsVitisClassic {} {
4727  return [expr {[info commands platform] != ""}]
4728 }
4729 
4730 ## @brief Find out if the given Xilinx part is a Vesal chip
4731 #
4732 # @param[out] 1 if it's Zynq 0 if it's not
4733 # @param[in] part The FPGA part
4734 #
4735 proc IsZynq {part} {
4736  if {[regexp {^(xc7z|xczu).*} $part]} {
4737  return 1
4738  } else {
4739  return 0
4740  }
4741 }
4742 
4743 proc ImportGHDL {project_name repo_path simset_name simset_dict {ext_path ""}} {
4744  set list_path "$repo_path/Top/$project_name/list"
4745  lassign [GetHogFiles -list_files {.src,.ext,.sim} -ext_path $ext_path $list_path $repo_path] src_files properties filesets
4746  cd $repo_path
4747 
4748 
4749  # Get Properties
4750  set properties [DictGet $simset_dict "properties"]
4751  set options [DictGet $properties "options"]
4752 
4753  # Import GHDL files
4754  set workdir Projects/$project_name/ghdl
4755  file delete -force $workdir
4756  file mkdir $workdir
4757  set import_log "$workdir/ghdl-import-${simset_name}.log"
4758  dict for {lib sources} $src_files {
4759  set libname [file rootname $lib]
4760  foreach f $sources {
4761  if {[file extension $f] != ".vhd" && [file extension $f] != ".vhdl"} {
4762  Msg Info "File $f is not a VHDL file, copying it in workfolder..."
4763  file copy -force $f $workdir
4764  } else {
4765  set file_path [Relative $repo_path $f]
4766  set import_log_file [open $import_log "a"]
4767  puts "ghdl -i --work=$libname --workdir=$workdir -fsynopsys --ieee=standard $options $file_path"
4768  puts $import_log_file "ghdl -i --work=$libname --workdir=$workdir -fsynopsys --ieee=standard $options $file_path"
4769  close $import_log_file
4770  lassign [GHDL "-i --work=$libname --workdir=$workdir -fsynopsys --ieee=standard $options $file_path" $import_log] ret result
4771  if {$ret != 0} {
4772  Msg CriticalWarning "GHDL import failed for file $f: $result"
4773  }
4774  }
4775  }
4776  }
4777  PrintFileContent $import_log
4778 
4779 }
4780 
4781 proc LaunchGHDL {project_name repo_path simset_name simset_dict {ext_path ""}} {
4782  set top_sim ""
4783  # Setting Simulation Properties
4784  set sim_props [DictGet $simset_dict "properties"]
4785  set options [DictGet $sim_props "options"]
4786  set runopts [DictGet $sim_props "run_options"]
4787 
4788  dict for {prop_name prop_val} $sim_props {
4789  set prop_name [string toupper $prop_name]
4790  if {$prop_name == "TOP"} {
4791  set top_sim $prop_val
4792  }
4793  }
4794  set workdir $repo_path/Projects/$project_name/ghdl
4795  set make_log "$workdir/ghdl-make-${simset_name}.log"
4796  set run_log "$workdir/ghdl-run-${simset_name}.log"
4797  cd $workdir
4798  # Analyse and elaborate the design
4799  set make_log_file [open $make_log "w"]
4800 
4801  puts "ghdl -m --work=$simset_name -fsynopsys --ieee=standard $options $top_sim"
4802  puts $make_log_file "ghdl -m --work=$simset_name -fsynopsys --ieee=standard $options $top_sim"
4803  close $make_log_file
4804 
4805  lassign [GHDL "-m --work=$simset_name -fsynopsys --ieee=standard $options $top_sim" $make_log] ret result
4806  PrintFileContent $make_log
4807  if {$ret != 0} {
4808  Msg Error "GHDL make failed for $top_sim: $result"
4809  return
4810  }
4811 
4812  set run_log_file [open $run_log "w"]
4813  puts "ghdl -r --work=$simset_name -fsynopsys --ieee=standard $options $top_sim $runopts"
4814  puts $run_log_file "ghdl -r --work=$simset_name -fsynopsys --ieee=standard $options $top_sim $runopts"
4815  close $run_log_file
4816 
4817  lassign [GHDL "-r --work=$simset_name -fsynopsys --ieee=standard $options $top_sim $runopts" $run_log] ret result
4818  PrintFileContent $run_log
4819 
4820  if {$ret != 0} {
4821  Msg Error "GHDL run failed for $top_sim: $result"
4822  return
4823  }
4824 
4825  cd $repo_path
4826 }
4827 
4828 # @brief Launch the Implementation, for the current IDE and project
4829 #
4830 # @param[in] reset Reset the Implementation run
4831 # @param[in] do_create Recreate the project
4832 # @param[in] run_folder The folder where to store the run results
4833 # @param[in] project_name The name of the project
4834 # @param[in] repo_path The main path of the git repository (Default .)
4835 # @param[in] njobs The number of parallel CPU jobs for the Implementation (Default 4)
4836 proc LaunchImplementation {reset do_create run_folder project_name {repo_path .} {njobs 4} {do_bitstream 0}} {
4837  Msg Info "Starting implementation flow..."
4838  if {[IsXilinx]} {
4839  if {$reset == 1 && $do_create == 0} {
4840  Msg Info "Resetting run before launching implementation..."
4841  reset_run impl_1
4842  }
4843 
4844  if {[IsISE]} {
4845  source $repo_path/Hog/Tcl/integrated/pre-implementation.tcl
4846  }
4847 
4848  if {$do_bitstream == 1} {
4849  launch_runs impl_1 -to_step [BinaryStepName [get_property PART [current_project]]] -jobs $njobs -dir $run_folder
4850  } else {
4851  launch_runs impl_1 -jobs $njobs -dir $run_folder
4852  }
4853  wait_on_run impl_1
4854 
4855  if {[IsISE]} {
4856  Msg Info "running post-implementation"
4857  source $repo_path/Hog/Tcl/integrated/post-implementation.tcl
4858  if {$do_bitstream == 1} {
4859  Msg Info "running pre-bitstream"
4860  source $repo_path/Hog/Tcl/integrated/pre-bitstream.tcl
4861  Msg Info "running post-bitstream"
4862  source $repo_path/Hog/Tcl/integrated/post-bitstream.tcl
4863  }
4864  }
4865 
4866  set prog [get_property PROGRESS [get_runs impl_1]]
4867  set status [get_property STATUS [get_runs impl_1]]
4868  Msg Info "Run: impl_1 progress: $prog, status : $status"
4869 
4870  # Check timing
4871  if {[IsISE]} {
4872  set status_file [open "$run_folder/timing.txt" "w"]
4873  puts $status_file "## $project_name Timing summary"
4874 
4875  set f [open [lindex [glob "$run_folder/impl_1/*.twr" 0]]]
4876  set errs -1
4877  while {[gets $f line] >= 0} {
4878  if {[string match "Timing summary:" $line]} {
4879  while {[gets $f line] >= 0} {
4880  if {[string match "Timing errors:*" $line]} {
4881  set errs [regexp -inline -- {[0-9]+} $line]
4882  }
4883  if {[string match "*Footnotes*" $line]} {
4884  break
4885  }
4886  puts $status_file "$line"
4887  }
4888  }
4889  }
4890 
4891  close $f
4892  close $status_file
4893 
4894  if {$errs == 0} {
4895  Msg Info "Time requirements are met"
4896  file rename -force "$run_folder/timing.txt" "$run_folder/timing_ok.txt"
4897  set timing_ok 1
4898  } else {
4899  Msg CriticalWarning "Time requirements are NOT met"
4900  file rename -force "$run_folder/timing.txt" "$run_folder/timing_error.txt"
4901  set timing_ok 0
4902  }
4903  }
4904 
4905  if {[IsVivado]} {
4906  set wns [get_property STATS.WNS [get_runs [current_run]]]
4907  set tns [get_property STATS.TNS [get_runs [current_run]]]
4908  set whs [get_property STATS.WHS [get_runs [current_run]]]
4909  set ths [get_property STATS.THS [get_runs [current_run]]]
4910  set tpws [get_property STATS.TPWS [get_runs [current_run]]]
4911 
4912  if {$wns >= 0 && $whs >= 0 && $tpws >= 0} {
4913  Msg Info "Time requirements are met"
4914  set status_file [open "$run_folder/timing_ok.txt" "w"]
4915  set timing_ok 1
4916  } else {
4917  Msg CriticalWarning "Time requirements are NOT met"
4918  set status_file [open "$run_folder/timing_error.txt" "w"]
4919  set timing_ok 0
4920  }
4921 
4922  Msg Status "*** Timing summary ***"
4923  Msg Status "WNS: $wns"
4924  Msg Status "TNS: $tns"
4925  Msg Status "WHS: $whs"
4926  Msg Status "THS: $ths"
4927  Msg Status "TPWS: $tpws"
4928 
4929  struct::matrix m
4930  m add columns 5
4931  m add row
4932 
4933  puts $status_file "## $project_name Timing summary"
4934 
4935  m add row "| **Parameter** | \"**value (ns)**\" |"
4936  m add row "| --- | --- |"
4937  m add row "| WNS: | $wns |"
4938  m add row "| TNS: | $tns |"
4939  m add row "| WHS: | $whs |"
4940  m add row "| THS: | $ths |"
4941  m add row "| TPWS: | $tpws |"
4942 
4943  puts $status_file [m format 2string]
4944  puts $status_file "\n"
4945  if {$timing_ok == 1} {
4946  puts $status_file " Time requirements are met."
4947  } else {
4948  puts $status_file "Time requirements are **NOT** met."
4949  }
4950  puts $status_file "\n\n"
4951  close $status_file
4952  }
4953 
4954  if {$prog ne "100%"} {
4955  Msg Error "Implementation error"
4956  }
4957 
4958  #Go to repository path
4959  cd $repo_path
4960  lassign [GetRepoVersions [file normalize ./Top/$project_name] $repo_path] sha
4961  set describe [GetHogDescribe $sha $repo_path]
4962  Msg Info "Git describe set to $describe"
4963 
4964  set dst_dir [file normalize "$repo_path/bin/$project_name\-$describe"]
4965 
4966  file mkdir $dst_dir
4967 
4968  #Version table
4969  if {[file exists $run_folder/versions.txt]} {
4970  file copy -force $run_folder/versions.txt $dst_dir
4971  } else {
4972  Msg Warning "No versions file found in $run_folder/versions.txt"
4973  }
4974  #Timing file
4975  set timing_files [glob -nocomplain "$run_folder/timing_*.txt"]
4976  set timing_file [file normalize [lindex $timing_files 0]]
4977 
4978  if {[file exists $timing_file]} {
4979  file copy -force $timing_file $dst_dir/
4980  } else {
4981  Msg Warning "No timing file found, not a problem if running locally"
4982  }
4983 
4984  #### XSA here only for Versal Segmented Configuration
4985  if {[IsVersal [get_property part [current_project]]]} {
4986  if {[get_property segmented_configuration [current_project]] == 1} {
4987  Msg Info "Versal Segmented configuration detected: exporting XSA file..."
4988  set xsa_name "$dst_dir/[file tail $project_name]\-$describe.xsa"
4989  write_hw_platform -fixed -force -file $xsa_name
4990  }
4991  }
4992  } elseif {[IsQuartus]} {
4993  set revision [get_current_revision]
4994 
4995  if {[catch {execute_module -tool fit} result]} {
4996  Msg Error "Result: $result\n"
4997  Msg Error "Place & Route failed. See the report file.\n"
4998  } else {
4999  Msg Info "\nINFO: Place & Route was successful for revision $revision.\n"
5000  }
5001 
5002  if {[catch {execute_module -tool sta -args "--do_report_timing"} result]} {
5003  Msg Error "Result: $result\n"
5004  Msg Error "Time Quest failed. See the report file.\n"
5005  } else {
5006  Msg Info "Time Quest was successfully run for revision $revision.\n"
5007  load_package report
5008  load_report
5009  set panel "Timing Analyzer||Timing Analyzer Summary"
5010  set device [get_report_panel_data -name $panel -col 1 -row_name "Device Name"]
5011  set timing_model [get_report_panel_data -name $panel -col 1 -row_name "Timing Models"]
5012  set delay_model [get_report_panel_data -name $panel -col 1 -row_name "Delay Model"]
5013  #set slack [ get_timing_analysis_summary_results -slack ]
5014  Msg Info "*******************************************************************"
5015  Msg Info "Device: $device"
5016  Msg Info "Timing Models: $timing_model"
5017  Msg Info "Delay Model: $delay_model"
5018  Msg Info "Slack:"
5019  #Msg Info $slack
5020  Msg Info "*******************************************************************"
5021  }
5022  } elseif {[IsLibero]} {
5023  Msg Info "Starting implementation flow..."
5024  if {[catch {run_tool -name {PLACEROUTE}}]} {
5025  Msg Error "PLACEROUTE FAILED!"
5026  } else {
5027  Msg Info "PLACEROUTE PASSED."
5028  }
5029 
5030  # Check timing
5031  Msg Info "Run VERIFYTIMING ..."
5032  if {[catch {run_tool -name {VERIFYTIMING} -script {Hog/Tcl/integrated/libero_timing.tcl}}]} {
5033  Msg CriticalWarning "VERIFYTIMING FAILED!"
5034  } else {
5035  Msg Info "VERIFYTIMING PASSED \n"
5036  }
5037 
5038  #Go to repository path
5039  cd $repo_path
5040 
5041  lassign [GetRepoVersions [file normalize ./Top/$project_name] $repo_path] sha
5042  set describe [GetHogDescribe $sha $repo_path]
5043  Msg Info "Git describe set to $describe"
5044 
5045  set dst_dir [file normalize "$repo_path/bin/$project_name\-$describe"]
5046  file mkdir $dst_dir/reports
5047 
5048  #Version table
5049  if {[file exists $run_folder/versions.txt]} {
5050  file copy -force $run_folder/versions.txt $dst_dir
5051  } else {
5052  Msg Warning "No versions file found in $run_folder/versions.txt"
5053  }
5054  #Timing file
5055  set timing_file_path [file normalize "$repo_path/Projects/timing_libero.txt"]
5056  if {[file exists $timing_file_path]} {
5057  file copy -force $timing_file_path $dst_dir/reports/Timing.txt
5058  set timing_file [open $timing_file_path "r"]
5059  set status_file [open "$dst_dir/timing.txt" "w"]
5060  puts $status_file "## $project_name Timing summary\n\n"
5061  puts $status_file "| | |"
5062  puts $status_file "| --- | --- |"
5063  while {[gets $timing_file line] >= 0} {
5064  if {[string match "SUMMARY" $line]} {
5065  while {[gets $timing_file line] >= 0} {
5066  if {[string match "END SUMMARY" $line]} {
5067  break
5068  }
5069  if {[string first ":" $line] == -1} {
5070  continue
5071  }
5072  set out_string "| [string map {: | } $line] |"
5073  puts $status_file "$out_string"
5074  }
5075  }
5076  }
5077  } else {
5078  Msg Warning "No timing file found, not a problem if running locally"
5079  }
5080  } elseif {[IsDiamond]} {
5081  set force_rst ""
5082  if {$reset == 1} {
5083  set force_rst "-forceOne"
5084  }
5085  prj_run Map $force_rst
5086  prj_run PAR $force_rst
5087 
5088  # TODO: Check Timing for Diamond
5089  }
5090 }
5091 
5092 # @brief Re-generate the bitstream, for the current IDE and project (Vivado only for the moment). \
5093 # Useful for a Vivado-Vitis project to update the bitstream with a new ELF or to generate a new \
5094 # bootimage (ZYNQ) without running the full workflow.
5095 #
5096 # @param[in] project_name The name of the project
5097 # @param[in] repo_path The main path of the git repository (Default .)
5098 proc GenerateBitstreamOnly {project_name {repo_path .}} {
5099  cd $repo_path
5100  lassign [GetRepoVersions [file normalize ./Top/$project_name] $repo_path] sha
5101  set describe [GetHogDescribe $sha $repo_path]
5102  set dst_dir [file normalize "$repo_path/bin/$project_name\-$describe"]
5103 
5104  cd Projects/$project_name/$project_name.runs/impl_1
5105  Msg Info "Running pre-bitstream..."
5106  source $repo_path/Hog/Tcl/integrated/pre-bitstream.tcl
5107 
5108  Msg Info "Writing bitstream for $project_name..."
5109  open_run impl_1
5110  write_bitstream -force $dst_dir/$project_name-$describe.bit
5111 
5112  Msg Info "Running post-bitstream..."
5113  source $repo_path/Hog/Tcl/integrated/post-bitstream.tcl
5114 }
5115 
5116 # @brief Launch the simulation (Vivado only for the moment)
5117 #
5118 # @param[in] project_name The name of the project
5119 # @param[in] lib_path The path to the simulation libraries
5120 # @param[in] simsets The simulation sets to simulate
5121 # @param[in] repo_path The main path of the git repository
5122 proc LaunchSimulation {project_name lib_path simsets {repo_path .} {scripts_only 0} {compile_only 0}} {
5123  if {[IsVivado]} {
5124  ##################### SIMULATION #######################
5125  set project [file tail $project_name]
5126  set main_sim_folder [file normalize "$repo_path/Projects/$project_name/$project.sim/"]
5127  set simsets_todo ""
5128  if {$simsets != ""} {
5129  dict for {simset sim_dict} $simsets {
5130  lappend simsets_todo $simset
5131  }
5132  Msg Info "Will run only the following simulation's sets (if they exist): $simsets_todo"
5133  }
5134 
5135  if {$scripts_only == 1} {
5136  Msg Info "Only generating simulation scripts, not running simulations..."
5137  }
5138 
5139  if {$compile_only == 1} {
5140  Msg Info "Only compiling simulation libraries, not running simulations..."
5141  }
5142 
5143  set failed []
5144  set success []
5145  set sim_dic [dict create]
5146 
5147  Msg Info "Retrieving list of simulation sets..."
5148  foreach s [get_filesets] {
5149  # Default behaviour, dont use simpass string and simulation is not quiet
5150  set use_simpass_str 0
5151  set quiet_sim ""
5152 
5153  set type [get_property FILESET_TYPE $s]
5154  if {$type eq "SimulationSrcs"} {
5155  if {$simsets_todo != "" && $s ni $simsets_todo} {
5156  Msg Info "Skipping $s as it was not specified with the -simset option..."
5157  continue
5158  }
5159  set sim_dict [DictGet $simsets $s]
5160  set simulator [DictGet $sim_dict "simulator"]
5161  set_property "target_simulator" $simulator [current_project]
5162  set hog_sim_props [DictGet $sim_dict "hog"]
5163  dict for {prop_name prop_val} $hog_sim_props {
5164  # If HOG_SIMPASS_STR is set, use the HOG_SIMPASS_STR string to search for in logs, after simulation is done
5165  if {[string toupper $prop_name] == "HOG_SIMPASS_STR" && $prop_val != ""} {
5166  Msg Info "Setting simulation pass string as '$prop_val'"
5167  set use_simpass_str 1
5168  set simpass_str $prop_val
5169  }
5170  if {[string toupper $prop_name] == "HOG_SILENT_SIM" && $prop_val == 1} {
5171  set quiet_sim " -quiet"
5172  } else {
5173  set quiet_sim ""
5174  }
5175  }
5176 
5177  Msg Info "Creating simulation scripts for $s..."
5178  if {[file exists $repo_path/Top/$project_name/pre-simulation.tcl]} {
5179  Msg Info "Running $repo_path/Top/$project_name/pre-simulation.tcl"
5180  source $repo_path/Top/$project_name/pre-simulation.tcl
5181  }
5182  if {[file exists $repo_path/Top/$project_name/pre-$s-simulation.tcl]} {
5183  Msg Info "Running $repo_path/Top/$project_name/pre-$s-simulation.tcl"
5184  source Running $repo_path/Top/$project_name/pre-$s-simulation.tcl
5185  }
5186  current_fileset -simset $s
5187  set sim_dir $main_sim_folder/$s/behav
5188  set sim_output_logfile $sim_dir/xsim/simulate.log
5189  if {([string tolower $simulator] eq "xsim")} {
5190  set sim_name "xsim:$s"
5191 
5192  set simulation_command "launch_simulation $quiet_sim -simset [get_filesets $s]"
5193  if {[catch $simulation_command log]} {
5194  # Explicitly close xsim simulation, without closing Vivado
5195  close_sim
5196  Msg CriticalWarning "Simulation failed for $s, error info: $::errorInfo"
5197  lappend failed $sim_name
5198  } else {
5199  # Explicitly close xsim simulation, without closing Vivado
5200  close_sim
5201  # If we use simpass_str, search for the string and update return code from simulation if the string is not found in simulation log
5202  if {$use_simpass_str == 1} {
5203  # Get the simulation output log
5204  # Note, xsim should always output simulation.log, hence no check for existence
5205  set file_desc [open $sim_output_logfile r]
5206  set log [read $file_desc]
5207  close $file_desc
5208 
5209  Msg Info "Searching for simulation pass string: '$simpass_str'"
5210  if {[string first $simpass_str $log] == -1} {
5211  Msg CriticalWarning "Simulation failed for $s, error info: '$simpass_str' NOT found!"
5212  lappend failed $sim_name
5213  } else {
5214  # HOG_SIMPASS_STR found, success
5215  lappend success $sim_name
5216  }
5217  } else {
5218  #Rely on simulator exit code
5219  lappend success $sim_name
5220  }
5221  }
5222  } else {
5223  Msg Info "Simulation library path is set to $lib_path."
5224  set simlib_ok 1
5225  if {!([file exists $lib_path])} {
5226  Msg Warning "Could not find simulation library path: $lib_path, $simulator simulation will not work."
5227  set simlib_ok 0
5228  }
5229 
5230  if {$simlib_ok == 1} {
5231  set_property "compxlib.${simulator}_compiled_library_dir" [file normalize $lib_path] [current_project]
5232  launch_simulation -scripts_only -simset [get_filesets $s]
5233  set top_name [get_property TOP $s]
5234  set sim_script [file normalize $sim_dir/$simulator/]
5235  Msg Info "Adding simulation script location $sim_script for $s..."
5236  lappend sim_scripts $sim_script
5237  dict append sim_dic $sim_script $s
5238  } else {
5239  Msg Error "Cannot run $simulator simulations without a valid library path"
5240  exit -1
5241  }
5242  }
5243  }
5244  }
5245 
5246  if {[info exists sim_scripts] && $scripts_only == 0} {
5247  # Only for modelsim/questasim
5248  Msg Info "Generating IP simulation targets, if any..."
5249 
5250  foreach ip [get_ips] {
5251  generate_target simulation -quiet $ip
5252  }
5253 
5254 
5255  Msg Status "\n\n"
5256  Msg Info "====== Starting simulations runs ======"
5257  Msg Status "\n\n"
5258 
5259  foreach s $sim_scripts {
5260  cd $s
5261  set cmd ./compile.sh
5262  Msg Info " ************* Compiling: $s ************* "
5263  lassign [ExecuteRet $cmd] ret log
5264  set sim_name "comp:[dict get $sim_dic $s]"
5265  if {$ret != 0} {
5266  Msg CriticalWarning "Compilation failed for $s, error info: $::errorInfo"
5267  lappend failed $sim_name
5268  } else {
5269  lappend success $sim_name
5270  }
5271  Msg Info "###################### Compilation log starts ######################"
5272  Msg Info "\n\n$log\n\n"
5273  Msg Info "###################### Compilation log ends ######################"
5274 
5275  if {$compile_only == 1} {
5276  continue
5277  }
5278  if {[file exists "./elaborate.sh"] } {
5279  set cmd ./elaborate.sh
5280  Msg Info " ************* Elaborating: $s ************* "
5281  lassign [ExecuteRet $cmd] ret log
5282  set sim_name "elab:[dict get $sim_dic $s]"
5283  if {$ret != 0} {
5284  Msg CriticalWarning "Elaboration failed for $s, error info: $::errorInfo"
5285  lappend failed $sim_name
5286  } else {
5287  lappend success $sim_name
5288  }
5289  Msg Info "###################### Elaboration log starts ######################"
5290  Msg Info "\n\n$log\n\n"
5291  Msg Info "###################### Elaboration log ends ######################"
5292  }
5293  set cmd ./simulate.sh
5294  Msg Info " ************* Simulating: $s ************* "
5295  lassign [ExecuteRet $cmd] ret log
5296 
5297 
5298  # If SIMPASS_STR is set, search log for the string
5299  if {$use_simpass_str == 1} {
5300  if {[string first $simpass_str $log] == -1} {
5301  set ret 1
5302  }
5303  } else {
5304  Msg Debug "Simulation pass string not set, relying on simulator exit code."
5305  }
5306 
5307 
5308  set sim_name "sim:[dict get $sim_dic $s]"
5309  if {$ret != 0} {
5310  Msg CriticalWarning "Simulation failed for $s, error info: $::errorInfo"
5311  lappend failed $sim_name
5312  } else {
5313  lappend success $sim_name
5314  }
5315  Msg Info "###################### Simulation log starts ######################"
5316  Msg Info "\n\n$log\n\n"
5317  Msg Info "###################### Simulation log ends ######################"
5318  }
5319  }
5320 
5321 
5322  if {[llength $success] > 0} {
5323  set successes [join $success "\n"]
5324  Msg Info "The following simulation sets were successful:\n\n$successes\n\n"
5325  }
5326 
5327  if {[llength $failed] > 0} {
5328  set failures [join $failed "\n"]
5329  Msg Error "The following simulation sets have failed:\n\n$failures\n\n"
5330  exit -1
5331  } elseif {[llength $success] > 0} {
5332  Msg Info "All the [llength $success] compilations, elaborations and simulations were successful."
5333  }
5334 
5335  Msg Info "Simulation done."
5336  } else {
5337  Msg Warning "Simulation is not yet supported for [GetIDEName]."
5338  }
5339 }
5340 
5341 #'"
5342 # @brief Launch the RTL Analysis, for the current IDE and project
5343 #
5344 # @param[in] repo_path The main path of the git repository (Default .)
5345 proc LaunchRTLAnalysis {repo_path} {
5346  if {[IsVivado]} {
5347  Msg Info "Starting RTL Analysis..."
5348  cd $repo_path
5349  synth_design -rtl -name rtl_1
5350  } else {
5351  Msg Warning "RTL Analysis is not yet supported for [GetIDEName]."
5352  }
5353 }
5354 
5355 # @brief Launch the synthesis, for the current IDE and project
5356 #
5357 # @param[in] reset Reset the Synthesis run
5358 # @param[in] do_create Recreate the project
5359 # @param[in] run_folder The folder where to store the run results
5360 # @param[in] project_name The name of the project
5361 # @param[in] repo_path The main path of the git repository (Default .)
5362 # @param[in] ext_path The path of source files external to the git repo (Default "")
5363 # @param[in] njobs The number of parallel CPU jobs for the synthesis (Default 4)
5364 proc LaunchSynthesis {reset do_create run_folder project_name {repo_path .} {ext_path ""} {njobs 4}} {
5365  if {[IsXilinx]} {
5366  if {$reset == 1 && $do_create == 0} {
5367  Msg Info "Resetting run before launching synthesis..."
5368  reset_run synth_1
5369  }
5370  if {[IsISE]} {
5371  source $repo_path/Hog/Tcl/integrated/pre-synthesis.tcl
5372  }
5373  launch_runs synth_1 -jobs $njobs -dir $run_folder
5374  wait_on_run synth_1
5375  set prog [get_property PROGRESS [get_runs synth_1]]
5376  set status [get_property STATUS [get_runs synth_1]]
5377  Msg Info "Run: synth_1 progress: $prog, status : $status"
5378  # Copy IP reports in bin/
5379  set ips [get_ips *]
5380 
5381  #go to repository path
5382  cd $repo_path
5383 
5384  lassign [GetRepoVersions [file normalize ./Top/$project_name] $repo_path $ext_path] sha
5385  set describe [GetHogDescribe $sha $repo_path]
5386  Msg Info "Git describe set to $describe"
5387 
5388  foreach ip $ips {
5389  set xci_file [get_property IP_FILE $ip]
5390 
5391  set xci_path [file dirname $xci_file]
5392  set xci_ip_name [file rootname [file tail $xci_file]]
5393  foreach rptfile [glob -nocomplain -directory $xci_path *.rpt] {
5394  file copy $rptfile $repo_path/bin/$project_name-$describe/reports
5395  }
5396  }
5397 
5398  if {$prog ne "100%"} {
5399  Msg Error "Synthesis error, status is: $status"
5400  }
5401  } elseif {[IsQuartus]} {
5402  # TODO: Missing reset
5403  set project [file tail [file rootname $project_name]]
5404 
5405  Msg Info "Number of jobs set to $njobs."
5406  set_global_assignment -name NUM_PARALLEL_PROCESSORS $njobs
5407 
5408 
5409  # keep track of the current revision and of the top level entity name
5410  lassign [GetRepoVersions [file normalize $repo_path/Top/$project_name] $repo_path] sha
5411  set describe [GetHogDescribe $sha $repo_path]
5412  #set top_level_name [ get_global_assignment -name TOP_LEVEL_ENTITY ]
5413  set revision [get_current_revision]
5414 
5415  #run PRE_FLOW_SCRIPT by hand
5416  set tool_and_command [split [get_global_assignment -name PRE_FLOW_SCRIPT_FILE] ":"]
5417  set tool [lindex $tool_and_command 0]
5418  set pre_flow_script [lindex $tool_and_command 1]
5419  set cmd "$tool -t $pre_flow_script quartus_map $project $revision"
5420  #Close project to avoid conflict with pre synthesis script
5421  project_close
5422 
5423  lassign [ExecuteRet {*}$cmd] ret log
5424  if {$ret != 0} {
5425  Msg Warning "Can not execute command $cmd"
5426  Msg Warning "LOG: $log"
5427  } else {
5428  Msg Info "Pre flow script executed!"
5429  }
5430 
5431  # Re-open project
5432  if {![is_project_open]} {
5433  Msg Info "Re-opening project file $project_name..."
5434  project_open $project -current_revision
5435  }
5436 
5437  # Generate IP Files
5438  if {[catch {execute_module -tool ipg -args "--clean"} result]} {
5439  Msg Error "Result: $result\n"
5440  Msg Error "IP Generation failed. See the report file.\n"
5441  } else {
5442  Msg Info "IP Generation was successful for revision $revision.\n"
5443  }
5444 
5445  # Execute synthesis
5446  if {[catch {execute_module -tool map -args "--parallel"} result]} {
5447  Msg Error "Result: $result\n"
5448  Msg Error "Analysis & Synthesis failed. See the report file.\n"
5449  } else {
5450  Msg Info "Analysis & Synthesis was successful for revision $revision.\n"
5451  }
5452  } elseif {[IsLibero]} {
5453  # TODO: Missing Reset
5454  defvar_set -name RWNETLIST_32_64_MIXED_FLOW -value 0
5455 
5456  Msg Info "Run SYNTHESIS..."
5457  if {[catch {run_tool -name {SYNTHESIZE}}]} {
5458  Msg Error "SYNTHESIZE FAILED!"
5459  } else {
5460  Msg Info "SYNTHESIZE PASSED!"
5461  }
5462  } elseif {[IsDiamond]} {
5463  # TODO: Missing Reset
5464  set force_rst ""
5465  if {$reset == 1} {
5466  set force_rst "-forceOne"
5467  }
5468  prj_run Synthesis $force_rst
5469  if {[prj_syn] == "synplify"} {
5470  prj_run Translate $force_rst
5471  }
5472  } else {
5473  Msg Error "Impossible condition. You need to run this in an IDE."
5474  exit 1
5475  }
5476 }
5477 
5478 
5479 # @brief Launch the Vitis build
5480 #
5481 # @param[in] project_name The name of the project
5482 # @param[in] repo_path The main path of the git repository (Default ".")
5483 # @param[in] stage The stage of the build (Default "presynth")
5484 proc LaunchVitisBuild {project_name {repo_path .} {stage "presynth"}} {
5485  set proj_name $project_name
5486  set bin_dir [file normalize "$repo_path/bin"]
5487 
5488  cd $repo_path
5489 
5490  if {[catch {set ws_apps [app list -dict]}]} { set ws_apps "" }
5491  lassign [GetRepoVersions [file normalize $repo_path/Top/$proj_name] $repo_path ] commit version hog_hash hog_ver top_hash top_ver \
5492  libs hashes vers cons_ver cons_hash ext_names ext_hashes xml_hash xml_ver user_ip_repos user_ip_hashes user_ip_vers
5493  set this_commit [GetSHA]
5494  if {$commit == 0 } { set commit $this_commit }
5495  set flavour [GetProjectFlavour $project_name]
5496  lassign [GetDateAndTime $commit] date timee
5497 
5498  foreach app_name [dict keys $ws_apps] {
5499  app config -name $app_name -set build-config Release
5500  }
5501 
5502  WriteGenerics "vitisbuild" $repo_path $proj_name $date $timee $commit $version $top_hash $top_ver $hog_hash $hog_ver $cons_ver $cons_hash $libs \
5503  $vers $hashes $ext_names $ext_hashes $user_ip_repos $user_ip_vers $user_ip_hashes $flavour $xml_ver $xml_hash
5504  foreach app_name [dict keys $ws_apps] { app build -name $app_name }
5505 
5506  if {$stage == "presynth"} {
5507  Msg Info "Done building apps for $project_name..."
5508  # return
5509  }
5510 
5511  Msg Info "Evaluating Git sha for $project_name..."
5512  lassign [GetRepoVersions [file normalize ./Top/$project_name] $repo_path] sha
5513 
5514  set describe [GetHogDescribe $sha $repo_path]
5515  Msg Info "Hog describe set to: $describe"
5516  set dst_dir [file normalize "$bin_dir/$proj_name\-$describe"]
5517  if {![file exists $dst_dir]} {
5518  Msg Info "Creating $dst_dir..."
5519  file mkdir $dst_dir
5520  }
5521 
5522  foreach app_name [dict keys $ws_apps] {
5523  set main_file "$repo_path/Projects/$project_name/vitis_classic/$app_name/Release/$app_name.elf"
5524  set dst_main [file normalize "$dst_dir/[file tail $proj_name]\-$app_name\-$describe.elf"]
5525 
5526  if {![file exists $main_file]} {
5527  Msg Error "No Vitis .elf file found. Perhaps there was an issue building it?"
5528  continue
5529  }
5530 
5531  Msg Info "Copying main binary file $main_file into $dst_main..."
5532  file copy -force $main_file $dst_main
5533  }
5534 }
5535 
5536 # @brief Returns the BIF file path from the properties
5537 #
5538 # @param[in] props A dictionary with the properties defined in Hog.conf
5539 # @param[in] app The application name
5540 # @return The path of the BIF file or empty string if not found
5541 proc GetProcFromProps {repo_path props platform} {
5542  if {[dict exists $props "platform:$platform" "BIF"]} {
5543  set bif_file [dict get $props "platform:$platform" "BIF"]
5544  if {[IsRelativePath $bif_file] == 1} {
5545  set bif_file "$repo_path/$bif_file"
5546  }
5547  return $bif_file
5548  } else {
5549  Msg CriticalWarning "BIF file not found in platform ($platform) properties, skipping bootable image (.bin) generation"
5550  return ""
5551  }
5552 }
5553 
5554 # @brief Returns the BIF file path from the properties
5555 #
5556 # @param[in] props A dictionary with the properties defined in Hog.conf
5557 # @param[in] platform The platform name
5558 # @return The path of the BIF file or empty string if not found
5559 proc GetBifFromProps {repo_path props platform} {
5560  if {[dict exists $props "platform:$platform" "BIF"]} {
5561  set bif_file [dict get $props "platform:$platform" "BIF"]
5562  if {[IsRelativePath $bif_file] == 1} {
5563  set bif_file "$repo_path/$bif_file"
5564  }
5565  return $bif_file
5566  } else {
5567  Msg CriticalWarning "BIF file not found in platform ($platform) properties, skipping bootable image (.bin) generation"
5568  return ""
5569  }
5570 }
5571 
5572 # @brief Returns the part number from the properties
5573 #
5574 # @param[in] props A dictionary with the properties defined in Hog.conf
5575 # @return The part number
5576 proc GetPartFromProps {props} {
5577  if {[dict exists $props "main" "PART"]} {
5578  return [string tolower [dict get $props "main" "PART"]]
5579  } else {
5580  Msg Error "Part number not found in properties"
5581  return ""
5582  }
5583 }
5584 
5585 # @brief Determines the architecture from the part number
5586 #
5587 # @param[in] part The FPGA part number (e.g., xczu4cg-fbvb900-1-e)
5588 # @return String with the architecture (zynqmp, zynq, versal, or unknown)
5589 proc GetArchFromPart {part} {
5590  # Determine architecture based on part prefix
5591  if {[string match "xczu*" $part]} {
5592  return "zynqmp"
5593  } elseif {[string match "xc7z*" $part]} {
5594  return "zynq"
5595  } elseif {[string match "xck26*" $part]} {
5596  return "versal"
5597  } else {
5598  Msg CriticalWarning "Unknown part number: $part"
5599  return "unknown"
5600  }
5601 }
5602 
5603 # @brief Returns a list of application names from the properties
5604 #
5605 # @param[in] props A dictionary with the applications properties defined in Hog.conf
5606 # @param[in] list_names If 1, returns a list of application names rather than a dictionary of applications
5607 proc GetAppsFromProps {props {list_names 0}} {
5608  set prop_apps [dict filter $props key {app:*}]
5609  set apps [dict create]
5610  set app_names [list]
5611 
5612  dict for {app_key app_value} $prop_apps {
5613  if {[regexp {^app:(.+)$} $app_key -> app_name]} {
5614  set app_name [string trim [string tolower $app_name]]
5615  # Convert only the keys of the inner dictionary to lowercase
5616  set app_value_lower [dict create]
5617  dict for {key value} $app_value {
5618  dict set app_value_lower [string tolower $key] $value
5619  }
5620  dict set apps $app_name $app_value_lower
5621  lappend app_names $app_name
5622  }
5623  }
5624  if {$list_names eq 1} {
5625  return $app_names
5626  }
5627  return $apps
5628 }
5629 
5630 # @brief Returns a list of platform names from the properties
5631 #
5632 # @param[in] props A dictionary with the platforms properties
5633 # @param[in] list_names If 1, returns a list of platform names rather than a dictionary of platforms
5634 # @param[in] lower_case If 1, returns the platform names in lowercase
5635 proc GetPlatformsFromProps {props {list_names 0} {lower_case 0}} {
5636  set platforms [dict create]
5637  set platform_names [list]
5638  set prop_platforms [dict filter $props key {platform:*}]
5639 
5640  dict for {platform_key platform_value} $prop_platforms {
5641  if {[regexp {^platform:(.+)$} $platform_key -> platform_name]} {
5642  if {$lower_case == 1} {
5643  set platform_name [string trim [string tolower $platform_name]]
5644  } else {
5645  set platform_name [string trim $platform_name]
5646  }
5647  dict set platforms $platform_name $platform_value
5648  lappend platform_names $platform_name
5649  }
5650  }
5651  if {$list_names eq 1} {
5652  return $platform_names
5653  }
5654  return $platforms
5655 }
5656 
5657 # @brief Generates boot artifacts for the application. If the application targets a soft \
5658 # processor (e.g. microblaze, riscv), the bitstream (.bit) memory is updated to include the ELF file. Otherwise, for \
5659 # applications targeting a hard processor (e.g. zynq, versal), a bootable binary image (.bin) is generated.
5660 #
5661 # @param[in] properties A dictionary with the properties defined in Hog.conf
5662 # @param[in] repo_path The main path of the git repository
5663 # @param[in] proj_dir The directory of the project
5664 # @param[in] bin_dir The directory of the generated binary files
5665 # @param[in] bitfile The bitfile to update
5666 # @param[in] mmi_file The MMI file to update
5667 proc GenerateBootArtifacts {properties repo_path proj_dir bin_dir proj_name describe bitfile mmi_file} {
5668  set elf_list [glob -nocomplain "$bin_dir/*.elf"]
5669  set apps [GetAppsFromProps $properties 0]
5670  set platforms [GetPlatformsFromProps $properties 1]
5671 
5672  if {[llength $elf_list] == 0} {
5673  Msg Warning "No ELF files found in $bin_dir, skipping generation of boot artifacts"
5674  return
5675  }
5676 
5677  if {![file exists $bitfile]} {
5678  Msg Warning "Bitfile $bitfile does not exist, skipping generation of boot artifacts"
5679  return
5680  }
5681 
5682  Msg Info "Generating boot artifacts for $proj_name..."
5683  Msg Info "Found apps: $apps"
5684  Msg Info "Found platforms: $platforms"
5685 
5686 
5687  # Update bitstream with ELF files for the applications targeting a soft processor (e.g. microblaze, riscv)
5688  foreach elf_file $elf_list {
5689  set elf_name [file rootname [file tail $elf_file]]
5690  Msg Info "Found elf name: $elf_name"
5691  Msg Info "Removing $describe from elf"
5692 
5693  # Extract application name from ELF file name
5694  if {[regexp "^(.+)-(.+)-$describe\$" $elf_name -> project_name elf_app]} {
5695  set elf_app [string trim [string tolower $elf_app]]
5696  Msg Info "Found elf_app: $elf_app"
5697  } else {
5698  Msg Error "Could not extract app name from elf file: $elf_name"
5699  continue
5700  }
5701  Msg Info "Removed project name ($project_name) and $describe from elf"
5702 
5703  set app_conf [dict get $apps $elf_app]
5704  set plat [dict get $app_conf "platform"]
5705  set app_proc [dict get $app_conf "proc"]
5706 
5707  # If the application targets a soft processor, update bitstream memory with ELF file
5708  if {[regexp -nocase {microblaze|risc} $app_proc]} {
5709  Msg Info "Detected soft processor ($app_proc) for $elf_app, updating bitstream memory with ELF file..."
5710 
5711  set proc_map [ReadProcMap $proc_map_file]
5712  if {[dict size $proc_map] == 0} {
5713  Msg Error "Failed to read map from $proc_map_file"
5714  continue
5715  }
5716  Msg Info "Found processor map: $proc_map"
5717 
5718  set proc_cell [lindex [split [dict get $proc_map $app_proc] ":"] 1]
5719  Msg Info "Updating memory at processor cell: $proc_cell"
5720 
5721  set update_mem_cmd "updatemem -force -meminfo $mmi_file -data $elf_file -bit $bitfile -proc $proc_cell -out $bitfile"
5722  set ret [catch {exec -ignorestderr {*}$update_mem_cmd >@ stdout} result]
5723  if {$ret != 0} {
5724  Msg Error "Error updating memory for $elf_app: $result"
5725  }
5726  Msg Info "Done updating memory for $elf_app"
5727 
5728  } else {
5729  Msg Info "Detected hard processor ($app_proc) for $elf_app. Make sure the .elf file is defined in the platform ($plat)\
5730  .bif file to be included in the bootable binary image (.bin) generation."
5731  }
5732  }
5733 
5734 
5735  # Generate a bootable binary image for platforms that have a .bif file defined
5736  foreach plat $platforms {
5737  set bif_file [GetBifFromProps $repo_path $properties $plat]
5738  if {$bif_file != ""} {
5739  Msg Info "Generating bootable binary image (.bin) for $plat"
5740  set arch [GetArchFromPart [GetPartFromProps $properties]]
5741  Msg Info "Architecture: $arch"
5742  Msg Info "BIF file: $bif_file"
5743  set bootgen_cmd "bootgen -arch $arch -image $bif_file -o i $bin_dir/$proj_name-$plat-$describe.bin -w on"
5744  set ret [catch {exec -ignorestderr {*}$bootgen_cmd >@ stdout} result]
5745  if {$ret != 0} {
5746  Msg Error "Error generating bootable binary image (.bin) for $elf_app: $result"
5747  }
5748  Msg Info "Done generating bootable binary image (.bin) for $plat"
5749  }
5750  }
5751 }
5752 
5753 # @brief Reads the processor map file
5754 #
5755 # @param[in] proc_map_file The path to the processor map file
5756 # @return A dictionary with the processor map
5757 proc ReadProcMap {proc_map_file} {
5758  set proc_map [dict create]
5759  if {[file exists $proc_map_file]} {
5760  set f [open $proc_map_file "r"]
5761  while {[gets $f line] >= 0} {
5762  Msg Debug "Line: $line"
5763  if {[regexp {^(\S+)\s+(.+)$} $line -> key value]} {
5764  Msg Debug "Found key: $key, value: $value in proc map file"
5765  dict set proc_map $key $value
5766  }
5767  }
5768  close $f
5769  }
5770  return $proc_map
5771 }
5772 
5773 
5774 # Returns the list of all the Hog Projects in the repository
5775 #
5776 # @param[in] repo_path The main path of the git repository
5777 # @param[in] print if 1 print the list of projects in the repository, if 2 does not print test projects
5778 # @param[in] ret_conf if 1 returns conf file rather than list of project names
5779 proc ListProjects {{repo_path .} {print 1} {ret_conf 0}} {
5780  set top_path [file normalize $repo_path/Top]
5781  set confs [findFiles [file normalize $top_path] hog.conf]
5782  set projects ""
5783  set confs [lsort $confs]
5784  set g ""
5785 
5786  foreach c $confs {
5787  set p [Relative $top_path [file dirname $c]]
5788  if {$print >= 1} {
5789  set description [DescriptionFromConf $c]
5790  if {$description eq "test"} {
5791  set description " - Test project"
5792  } elseif {$description ne ""} {
5793  set description " - $description"
5794  }
5795 
5796  if {$print == 1 || $description ne " - Test project"} {
5797  set old_g $g
5798  set g [file dirname $p]
5799  # Print a list of the projects with relative IDE and description (second line comment in hog.conf)
5800  if {$g ne $old_g} {
5801  Msg Status ""
5802  }
5803  Msg Status "$p \([GetIDEFromConf $c]\)$description"
5804  }
5805  }
5806  lappend projects $p
5807  }
5808 
5809  if {$ret_conf == 0} {
5810  # Returns a list of project names
5811  return $projects
5812  } else {
5813  # Return the list of hog.conf with full path
5814  return $confs
5815  }
5816 }
5817 
5818 ## @brief Evaluates the md5 sum of a file
5819 #
5820 # @param[in] file_name: the name of the file of which you want to evaluate the md5 checksum
5821 proc Md5Sum {file_name} {
5822  if {!([file exists $file_name])} {
5823  Msg Warning "Could not find $file_name."
5824  set file_hash -1
5825  }
5826  if {[catch {package require md5 2.0.7} result]} {
5827  Msg Warning "Tcl package md5 version 2.0.7 not found ($result), will use command line..."
5828  set hash [lindex [Execute md5sum $file_name] 0]
5829  } else {
5830  set file_hash [string tolower [md5::md5 -hex -file $file_name]]
5831  }
5832 }
5833 
5834 ## @brief Merges two tcl dictionaries of lists
5835 #
5836 # If the dictionaries contain same keys, the list at the common key is a merging of the two
5837 #
5838 # @param[in] dict0 the name of the first dictionary
5839 # @param[in] dict1 the name of the second dictionary
5840 # @param[in] remove_duplicates if 1, removes duplicates from the merged dictionary (default 1)
5841 #
5842 # @return the merged dictionary
5843 #
5844 proc MergeDict {dict0 dict1 {remove_duplicates 1}} {
5845  set outdict [dict merge $dict1 $dict0]
5846  foreach key [dict keys $dict1] {
5847  if {[dict exists $dict0 $key]} {
5848  set temp_list [dict get $dict1 $key]
5849  foreach item $temp_list {
5850  # Avoid duplication
5851  if {[IsInList $item [DictGet $outdict $key]] == 0 || $remove_duplicates == 0} {
5852  # If the key exists in both dictionaries, append the item to the list
5853  dict lappend outdict $key $item
5854  }
5855  }
5856  }
5857  }
5858  return $outdict
5859 }
5860 
5861 # @brief Move an element in the list to the end
5862 #
5863 # @param[in] inputList the list
5864 # @param[in] element the element to move to the end of the list
5865 proc MoveElementToEnd {inputList element} {
5866  set index [lsearch $inputList $element]
5867  if {$index != -1} {
5868  set inputList [lreplace $inputList $index $index]
5869  lappend inputList $element
5870  }
5871  return $inputList
5872 }
5873 
5874 # @brief Open the project with the corresponding IDE
5875 #
5876 # @param[in] project_file The project_file
5877 # @param[in] repo_path The main path of the git repository
5878 proc OpenProject {project_file repo_path} {
5879  if {[IsXilinx]} {
5880  open_project $project_file
5881  } elseif {[IsQuartus]} {
5882  set project_folder [file dirname $project_file]
5883  set project [file tail [file rootname $project_file]]
5884  if {[file exists $project_folder]} {
5885  cd $project_folder
5886  if {![is_project_open]} {
5887  Msg Info "Opening existing project file $project_file..."
5888  project_open $project -current_revision
5889  }
5890  } else {
5891  Msg Error "Project directory not found for $project_file."
5892  return 1
5893  }
5894  } elseif {[IsLibero]} {
5895  Msg Info "Opening existing project file $project_file..."
5896  cd $repo_path
5897  open_project -file $project_file -do_backup_on_convert 1 -backup_file {./Projects/$project_file.zip}
5898  } elseif {[IsDiamond]} {
5899  Msg Info "Opening existing project file $project_file..."
5900  prj_project open $project_file
5901  } else {
5902  Msg Error "This IDE is currently not supported by Hog. Exiting!"
5903  }
5904 }
5905 
5906 ## @brief Return the operative system name
5907 proc OS {} {
5908  global tcl_platform
5909  return $tcl_platform(platform)
5910 }
5911 
5912 ## @brief Parse JSON file
5913 #
5914 # @param[in] JSON_FILE The JSON File to parse
5915 # @param[in] JSON_KEY The key to extract from the JSON file
5916 #
5917 # @returns -1 in case of failure, JSON KEY VALUE in case of success
5918 #
5919 proc ParseJSON {JSON_FILE JSON_KEY} {
5920  set result [catch {package require Tcl 8.4} TclFound]
5921  if {"$result" != "0"} {
5922  Msg CriticalWarning "Cannot find Tcl package version equal or higher than 8.4.\n $TclFound\n Exiting"
5923  return -1
5924  }
5925 
5926  set result [catch {package require json} JsonFound]
5927  if {"$result" != "0"} {
5928  Msg CriticalWarning "Cannot find JSON package equal or higher than 1.0.\n $JsonFound\n Exiting"
5929  return -1
5930  }
5931  set JsonDict [json::json2dict $JSON_FILE]
5932  set result [catch {dict get $JsonDict $JSON_KEY} RETURNVALUE]
5933  if {"$result" != "0"} {
5934  Msg CriticalWarning "Cannot find $JSON_KEY in $JSON_FILE\n Exiting"
5935  return -1
5936  } else {
5937  return $RETURNVALUE
5938  }
5939 }
5940 
5941 
5942 # @brief Check if a Hog project exists, and if it exists returns the conf file
5943 # if it doesnt returns 0
5944 #
5945 # @brief project The project name
5946 # @brief repo_path The main path of the git repository
5947 proc ProjectExists {project {repo_path .}} {
5948  set index [lsearch -exact [ListProjects $repo_path 0] $project]
5949 
5950  if {$index >= 0} {
5951  # if project exists we return the relative hog.conf file
5952  return [lindex [ListProjects $repo_path 0 1] $index]
5953  } else {
5954  Msg Error "Project $project not found in repository $repo_path"
5955  return 1
5956  }
5957 }
5958 
5959 ## Read a property configuration file and returns a dictionary
5960 #
5961 # @param[in] file_name the configuration file
5962 #
5963 # @return The dictionary
5964 #
5965 proc ReadConf {file_name} {
5966  if {[catch {package require inifile 0.2.3} ERROR]} {
5967  Msg Error "Could not find inifile package version 0.2.3 or higher.\n
5968  To use ghdl, libero or diamond with Hog, you need to install the tcllib package\n
5969  You can install it with 'sudo apt install tcllib' on Debian/Ubuntu or 'sudo dnf install tcllib' on Fedora/RedHat/CentOs."
5970  }
5971 
5972 
5973  ::ini::commentchar "#"
5974  set f [::ini::open $file_name]
5975  set properties [dict create]
5976  foreach sec [::ini::sections $f] {
5977  set new_sec $sec
5978  if {$new_sec == "files"} {
5979  continue
5980  }
5981  set key_pairs [::ini::get $f $sec]
5982  #manipulate strings here:
5983  regsub -all {\{\"} $key_pairs "\{" key_pairs
5984  regsub -all {\"\}} $key_pairs "\}" key_pairs
5985 
5986  dict set properties $new_sec [dict create {*}$key_pairs]
5987  }
5988 
5989  ::ini::close $f
5990 
5991  return $properties
5992 }
5993 
5994 ## @brief Function used to read the list of files generated at creation time by tcl scripts in Project/proj/.hog/extra.files
5995 #
5996 # @param[in] extra_file_name the path to the extra.files file
5997 # @returns a dictionary with the full name of the files as key and a SHA as value
5998 #
5999 proc ReadExtraFileList {extra_file_name} {
6000  set extra_file_dict [dict create]
6001  if {[file exists $extra_file_name]} {
6002  set file [open $extra_file_name "r"]
6003  set file_data [read $file]
6004  close $file
6005 
6006  set data [split $file_data "\n"]
6007  foreach line $data {
6008  if {![regexp {^ *$} $line] & ![regexp {^ *\#} $line]} {
6009  set ip_and_md5 [regexp -all -inline {\S+} $line]
6010  dict lappend extra_file_dict "[lindex $ip_and_md5 0]" "[lindex $ip_and_md5 1]"
6011  }
6012  }
6013  }
6014  return $extra_file_dict
6015 }
6016 
6017 
6018 # @brief Read a list file and return a list of three dictionaries
6019 #
6020 # Additional information is provided with text separated from the file name with one or more spaces
6021 #
6022 # @param[in] args The arguments are <list_file> <path> [options]
6023 # * list_file file containing vhdl list with optional properties
6024 # * path path the vhdl file are referred to in the list file
6025 # Options:
6026 # * -lib <library> name of the library files will be added to, if not given will be extracted from the file name
6027 # * -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.
6028 # 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.
6029 #
6030 # @return a list of 3 dictionaries:
6031 # "libraries" has library name as keys and a list of filenames as values,
6032 # "properties" has as file names as keys and a list of properties as values
6033 # "filesets" has the fileset' names as keys and the list of associated libraries as values.
6034 proc ReadListFile {args} {
6035  if {[IsQuartus]} {
6036  load_package report
6037  if {[catch {package require cmdline} ERROR]} {
6038  puts "$ERROR\n If you are running this script on tclsh, you can fix this by installing 'tcllib'"
6039  return 1
6040  }
6041  }
6042  # tclint-disable line-length
6043  set parameters {
6044  {lib.arg "" "The name of the library files will be added to, if not given will be extracted from the file name."}
6045  {fileset.arg "" "The name of the library, from the main list file"}
6046  {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."}
6047  {print_log "If set, will use PrintFileTree for the VIEW directive"}
6048  {indent.arg "" "Used to indent files with the VIEW directive"}
6049  }
6050  # tclint-enable line-length
6051  set usage "USAGE: ReadListFile \[options\] <list file> <path>"
6052  if {[catch {array set options [cmdline::getoptions args $parameters $usage]}] || [llength $args] != 2} {
6053  Msg CriticalWarning "[cmdline::usage $parameters $usage]"
6054  return
6055  }
6056 
6057 
6058  set list_file [lindex $args 0]
6059  set path [lindex $args 1]
6060  set sha_mode $options(sha_mode)
6061  set lib $options(lib)
6062  set fileset $options(fileset)
6063  set print_log $options(print_log)
6064  set indent $options(indent)
6065 
6066  if {$sha_mode == 1} {
6067  set sha_mode_opt "-sha_mode"
6068  } else {
6069  set sha_mode_opt ""
6070  }
6071 
6072  if {$print_log == 1} {
6073  set print_log_opt "-print_log"
6074  } else {
6075  set print_log_opt ""
6076  }
6077 
6078  # if no library is given, work it out from the file name
6079  if {$lib eq ""} {
6080  set lib [file rootname [file tail $list_file]]
6081  }
6082  set fp [open $list_file r]
6083  set file_data [read $fp]
6084  close $fp
6085  set list_file_ext [file extension $list_file]
6086  switch $list_file_ext {
6087  .sim {
6088  if {$fileset eq ""} {
6089  # If fileset is empty, use the library name for .sim file
6090  set fileset "$lib"
6091  }
6092  }
6093  .con {
6094  set fileset "constrs_1"
6095  }
6096  default {
6097  set fileset "sources_1"
6098  }
6099  }
6100 
6101  set libraries [dict create]
6102  set filesets [dict create]
6103  set properties [dict create]
6104  # Process data file
6105  set data [split $file_data "\n"]
6106  set data [ExtractFilesSection $data]
6107  set n [llength $data]
6108  set last_printed ""
6109  if {$print_log == 1} {
6110  if {$indent eq ""} {
6111  set list_file_rel [file tail $list_file]
6112  Msg Status "\n$list_file_rel"
6113  }
6114  set last_printed [PrintFileTree $data $path "$indent"]
6115  }
6116  Msg Debug "$n lines read from $list_file."
6117  set cnt 0
6118 
6119  foreach line $data {
6120  # Exclude empty lines and comments
6121  if {![regexp {^[\t\s]*$} $line] & ![regexp {^[\t\s]*\#} $line]} {
6122  set file_and_prop [regexp -all -inline {\S+} $line]
6123  set srcfile [lindex $file_and_prop 0]
6124  set srcfile "$path/$srcfile"
6125 
6126  set srcfiles [glob -nocomplain $srcfile]
6127 
6128  # glob the file list for wildcards
6129  if {$srcfiles != $srcfile && ![string equal $srcfiles ""]} {
6130  Msg Debug "Wildcard source expanded from $srcfile to $srcfiles"
6131  } else {
6132  if {![file exists $srcfile]} {
6133  if {$print_log == 0} {
6134  Msg CriticalWarning "File: $srcfile (from list file: $list_file) does not exist."
6135  }
6136  continue
6137  }
6138  }
6139 
6140  foreach vhdlfile $srcfiles {
6141  if {[file exists $vhdlfile]} {
6142  set vhdlfile [file normalize $vhdlfile]
6143  set extension [file extension $vhdlfile]
6144  ### Set file properties
6145  set prop [lrange $file_and_prop 1 end]
6146 
6147  # The next lines should be inside the case for recursive list files, also we should check the allowed properties for the .src as well
6148  set library [lindex [regexp -inline {\ylib\s*=\s*(.+?)\y.*} $prop] 1]
6149  if {$library == ""} {
6150  set library $lib
6151  }
6152 
6153  if {$extension == $list_file_ext} {
6154  # Deal with recursive list files
6155  # In the next regex we use \S+ instead of .+? because we want to include forward slashes
6156  set ref_path [lindex [regexp -inline {\ypath\s*=\s*(\S+).*} $prop] 1]
6157  if {$ref_path eq ""} {
6158  set ref_path $path
6159  } else {
6160  set ref_path [file normalize $path/$ref_path]
6161  }
6162  Msg Debug "List file $vhdlfile found in list file, recursively opening it using path \"$ref_path\"..."
6163  if {$print_log == 1} {
6164  if {[file normalize $last_printed] ne [file normalize $vhdlfile]} {
6165  Msg Status "$indent Inside [file tail $vhdlfile]:"
6166  set last_printed ""
6167  }
6168  }
6169  lassign [ReadListFile {*}"-indent \" $indent\" -lib $library -fileset $fileset $sha_mode_opt $print_log_opt $vhdlfile $ref_path"] l p fs
6170  set libraries [MergeDict $l $libraries]
6171  set properties [MergeDict $p $properties]
6172  set filesets [MergeDict $fs $filesets]
6173  } elseif {[lsearch {.src .sim .con ReadExtraFileList} $extension] >= 0} {
6174  # Not supported extensions
6175  Msg Error "$vhdlfile cannot be included into $list_file, $extension files must be included into $extension files."
6176  } else {
6177  # Deal with single files
6178  regsub -all " *= *" $prop "=" prop
6179  # Fill property dictionary
6180  foreach p $prop {
6181  # No need to append the lib= property
6182  if {[string first "lib=" $p] == -1} {
6183  # Get property name up to the = (for QSYS properties at the moment)
6184  set pos [string first "=" $p]
6185  if {$pos == -1} {
6186  set prop_name $p
6187  } else {
6188  set prop_name [string range $p 0 [expr {$pos - 1}]]
6189  }
6190  if {[IsInList $prop_name [DictGet [ALLOWED_PROPS] $extension]] || [string first "top" $p] == 0 || $list_file_ext eq ".ipb"} {
6191  dict lappend properties $vhdlfile $p
6192  Msg Debug "Adding property $p to $vhdlfile..."
6193  } elseif {$list_file_ext != ".ipb"} {
6194  Msg Warning "Setting Property $p is not supported for file $vhdlfile or it is already its default. \
6195  The allowed properties for this file type are \[ [DictGet [ALLOWED_PROPS] $extension]\]"
6196  }
6197  }
6198  }
6199  if {[lsearch {.xci .ip .bd .xcix} $extension] >= 0} {
6200  # Adding IP library
6201  set lib_name "ips.src"
6202  } elseif {[IsInList $extension {.vhd .vhdl}] || $list_file_ext == ".sim"} {
6203  # VHDL files and simulation
6204  if {![IsInList $extension {.vhd .vhdl}]} {
6205  set lib_name "others.sim"
6206  } else {
6207  set lib_name "$library$list_file_ext"
6208  }
6209  } elseif {$list_file_ext == ".con"} {
6210  set lib_name "sources.con"
6211  } elseif {$list_file_ext == ".ipb"} {
6212  set lib_name "xml.ipb"
6213  } elseif { [IsInList $list_file_ext {.src}] && [IsInList $extension {.c .cpp .h .hpp}] } {
6214  # Adding Vitis library
6215  set lib_name "$library$list_file_ext"
6216  } else {
6217  # Other files are stored in the OTHER dictionary from vivado (no library assignment)
6218  set lib_name "others.src"
6219  }
6220 
6221  Msg Debug "Appending $vhdlfile to $lib_name list..."
6222  dict lappend libraries $lib_name $vhdlfile
6223  if {$sha_mode != 0 && [file type $vhdlfile] eq "link"} {
6224  #if the file is a link, also add the linked file in sha mode
6225  set real_file [GetLinkedFile $vhdlfile]
6226  dict lappend libraries $lib_name $real_file
6227  Msg Debug "File $vhdlfile is a soft link, also adding the real file: $real_file"
6228  }
6229 
6230 
6231  # Create the fileset (if not already) and append the library
6232  if {[dict exists $filesets $fileset] == 0} {
6233  # Fileset has not been defined yet, adding to dictionary...
6234  Msg Debug "Adding $fileset to the fileset dictionary..."
6235  Msg Debug "Adding library $lib_name to fileset $fileset..."
6236  dict set filesets $fileset $lib_name
6237  } else {
6238  # Fileset already exist in dictionary, append library to list, if not already there
6239  if {[IsInList $lib_name [DictGet $filesets $fileset]] == 0} {
6240  Msg Debug "Adding library $lib_name to fileset $fileset..."
6241  dict lappend filesets $fileset $lib_name
6242  }
6243  }
6244  }
6245  incr cnt
6246  } else {
6247  Msg CriticalWarning "File $vhdlfile not found."
6248  }
6249  }
6250  }
6251  }
6252 
6253  if {$sha_mode != 0} {
6254  #In SHA mode we also need to add the list file to the list
6255  if {$list_file_ext eq ".ipb"} {
6256  set sha_lib "xml.ipb"
6257  } else {
6258  set sha_lib $lib$list_file_ext
6259  }
6260  dict lappend libraries $sha_lib [file normalize $list_file]
6261  if {[file type $list_file] eq "link"} {
6262  #if the file is a link, also add the linked file
6263  set real_file [GetLinkedFile $list_file]
6264  dict lappend libraries $lib$list_file_ext $real_file
6265  Msg Debug "List file $list_file is a soft link, also adding the real file: $real_file"
6266  }
6267  }
6268  return [list $libraries $properties $filesets]
6269 }
6270 
6271 ## @brief Returns the destination path relative to base
6272 #
6273 # @param[in] base the path with respect to witch the dst path is calculated
6274 # @param[in] dst the path to be calculated with respect to base
6275 # @param[in] quiet if 1, does not print warnings when paths are of different types
6276 #
6277 proc Relative {base dst {quiet 0}} {
6278  if {![string equal [file pathtype $base] [file pathtype $dst]]} {
6279  if {$quiet == 0} {
6280  Msg CriticalWarning "Unable to compute relation for paths of different path types: [file pathtype $base] vs. [file pathtype $dst], ($base vs. $dst)"
6281  }
6282  return ""
6283  }
6284 
6285  set base [file normalize [file join [pwd] $base]]
6286  set dst [file normalize [file join [pwd] $dst]]
6287 
6288  set save $dst
6289  set base [file split $base]
6290  set dst [file split $dst]
6291 
6292  while {[string equal [lindex $dst 0] [lindex $base 0]]} {
6293  set dst [lrange $dst 1 end]
6294  set base [lrange $base 1 end]
6295  if {![llength $dst]} {break}
6296  }
6297 
6298  set dstlen [llength $dst]
6299  set baselen [llength $base]
6300 
6301  if {($dstlen == 0) && ($baselen == 0)} {
6302  set dst .
6303  } else {
6304  while {$baselen > 0} {
6305  set dst [linsert $dst 0 ..]
6306  incr baselen -1
6307  }
6308  set dst [eval [linsert $dst 0 file join]]
6309  }
6310 
6311  return $dst
6312 }
6313 
6314 ## @brief Returns the path of filePath relative to pathName
6315 #
6316 # @param[in] pathName the path with respect to which the returned path is calculated
6317 # @param[in] filePath the path of filePath
6318 #
6319 proc RelativeLocal {pathName filePath} {
6320  if {[string first [file normalize $pathName] [file normalize $filePath]] != -1} {
6321  return [Relative $pathName $filePath]
6322  } else {
6323  return ""
6324  }
6325 }
6326 
6327 ## @brief Remove duplicates in a dictionary
6328 #
6329 # @param[in] mydict the input dictionary
6330 #
6331 # @return the dictionary stripped of duplicates
6332 proc RemoveDuplicates {mydict} {
6333  set new_dict [dict create]
6334  foreach key [dict keys $mydict] {
6335  set values [DictGet $mydict $key]
6336  foreach value $values {
6337  set idxs [lreverse [lreplace [lsearch -exact -all $values $value] 0 0]]
6338  foreach idx $idxs {
6339  set values [lreplace $values $idx $idx]
6340  }
6341  }
6342  dict set new_dict $key $values
6343  }
6344  return $new_dict
6345 }
6346 
6347 ## Reset files in the repository
6348 #
6349 # @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)
6350 #
6351 # @return Nothing
6352 #
6353 proc ResetRepoFiles {reset_file} {
6354  if {[file exists $reset_file]} {
6355  Msg Info "Found $reset_file, opening it..."
6356  set fp [open $reset_file r]
6357  set wild_cards [lsearch -all -inline -not -regexp [split [read $fp] "\n"] "^ *$"]
6358  close $fp
6359  Msg Info "Found the following files/wild cards to restore if modified: $wild_cards..."
6360  foreach w $wild_cards {
6361  set mod_files [GetModifiedFiles "." $w]
6362  if {[llength $mod_files] > 0} {
6363  Msg Info "Found modified $w files: $mod_files, will restore them..."
6364  RestoreModifiedFiles "." $w
6365  } else {
6366  Msg Info "No modified $w files found."
6367  }
6368  }
6369  }
6370 }
6371 
6372 ## @brief Restore with checkout -- the files specified in pattern
6373 #
6374 # @param[in] repo_path the path of the git repository
6375 # @param[in] pattern the pattern with wildcards that files should match
6376 #
6377 proc RestoreModifiedFiles {{repo_path "."} {pattern "."}} {
6378  set old_path [pwd]
6379  cd $repo_path
6380  set ret [Git checkout $pattern]
6381  cd $old_path
6382  return
6383 }
6384 
6385 ## Search the Hog projects inside a directory
6386 #
6387 # @param[in] dir The directory to search
6388 #
6389 # @return The list of projects
6390 #
6391 proc SearchHogProjects {dir} {
6392  set projects_list {}
6393  if {[file exists $dir]} {
6394  if {[file isdirectory $dir]} {
6395  foreach proj_dir [glob -nocomplain -types d $dir/*] {
6396  if {![regexp {^.*Top/+(.*)$} $proj_dir dummy proj_name]} {
6397  Msg Warning "Could not parse Top directory $dir"
6398  break
6399  }
6400  if {[file exists "$proj_dir/hog.conf"]} {
6401  lappend projects_list $proj_name
6402  } else {
6403  foreach p [SearchHogProjects $proj_dir] {
6404  lappend projects_list $p
6405  }
6406  }
6407  }
6408  } else {
6409  Msg Error "Input $dir is not a directory!"
6410  }
6411  } else {
6412  Msg Error "Directory $dir doesn't exist!"
6413  }
6414  return $projects_list
6415 }
6416 
6417 ## @brief Sets the generics in all the sim.conf simulation file sets
6418 #
6419 # @param[in] repo_path: the top folder of the projectThe path to the main git repository
6420 # @param[in] proj_dir: the top folder of the project
6421 # @param[in] target: software target(vivado, questa)
6422 #
6423 proc SetGenericsSimulation {repo_path proj_dir target} {
6424  set top_dir "$repo_path/Top/$proj_dir"
6425  set simsets [get_filesets]
6426  if {$simsets != ""} {
6427  foreach simset $simsets {
6428  # Only for simulation filesets
6429  if {[get_property FILESET_TYPE $simset] != "SimulationSrcs"} {
6430  continue
6431  }
6432 
6433  set merged_generics_dict [dict create]
6434  # Get generics from sim.conf file
6435  set simset_dict [DictGet [GetSimSets $proj_dir $repo_path $simset] $simset]
6436  set hog_generics [GetGenericsFromConf $proj_dir]
6437  set simset_generics [DictGet $simset_dict "generics"]
6438  set merged_generics_dict [MergeDict $merged_generics_dict $simset_generics 0]
6439  set generic_str [GenericToSimulatorString $merged_generics_dict $target]
6440  set_property generic $generic_str [get_filesets $simset]
6441  Msg Debug "Setting generics $generic_str for simulator $target\
6442  and simulation file-set $simset..."
6443  }
6444  }
6445 }
6446 
6447 ## @brief set the top module as top module in the chosen fileset
6448 #
6449 # It automatically recognises the IDE
6450 #
6451 # @param[out] top_module Name of the top module
6452 # @param[in] fileset The name of the fileset
6453 #
6454 proc SetTopProperty {top_module fileset} {
6455  Msg Info "Setting TOP property to $top_module module"
6456  if {[IsXilinx]} {
6457  #VIVADO_ONLY
6458  set_property "top" $top_module [get_filesets $fileset]
6459  } elseif {[IsQuartus]} {
6460  #QUARTUS ONLY
6461  set_global_assignment -name TOP_LEVEL_ENTITY $top_module
6462  } elseif {[IsLibero]} {
6463  set_root -module $top_module
6464  } elseif {[IsDiamond]} {
6465  prj_impl option top $top_module
6466  }
6467 }
6468 
6469 ## @brief Returns a list of Vivado properties that expect a PATH for value
6470 proc VIVADO_PATH_PROPERTIES {} {
6471  return {"\.*\.TCL\.PRE$" "^.*\.TCL\.POST$" "^RQS_FILES$" "^INCREMENTAL\_CHECKPOINT$" "NOC\_SOLUTION\_FILE"}
6472 }
6473 
6474 ## @brief Returns a list of Vitis properties that expect a PATH for value
6475 proc VITIS_PATH_PROPERTIES {} {
6476  return {"^HW$" "^XPFM$" "^LINKER-SCRIPT$" "^LIBRARIES$" "^LIBRARY-SEARCH-PATH$"}
6477 }
6478 
6479 ## @brief Write a property configuration file from a dictionary
6480 #
6481 # @param[in] file_name the configuration file
6482 # @param[in] config the configuration dictionary
6483 # @param[in] comment comment to add at the beginning of configuration file
6484 #
6485 #
6486 proc WriteConf {file_name config {comment ""}} {
6487  if {[catch {package require inifile 0.2.3} ERROR]} {
6488  puts "$ERROR\n If you are running this script on tclsh, you can fix this by installing 'tcllib'"
6489  return 1
6490  }
6491 
6492  ::ini::commentchar "#"
6493  set f [::ini::open $file_name w]
6494 
6495  foreach sec [dict keys $config] {
6496  set section [dict get $config $sec]
6497  dict for {p v} $section {
6498  if {[string trim $v] == ""} {
6499  Msg Warning "Property $p has empty value. Skipping..."
6500  continue
6501  }
6502  ::ini::set $f $sec $p $v
6503  }
6504  }
6505 
6506  #write comment before the first section (first line of file)
6507  if {![string equal "$comment" ""]} {
6508  ::ini::comment $f [lindex [::ini::sections $f] 0] "" $comment
6509  set hog_header "Generated by Hog on [clock format [clock seconds] -format "%Y-%m-%d %H:%M:%S"]"
6510  ::ini::comment $f [lindex [::ini::sections $f] 0] "" $hog_header
6511  }
6512  ::ini::commit $f
6513 
6514  ::ini::close $f
6515 }
6516 
6517 ## Set the generics property
6518 #
6519 # @param[in] mode if it's "create", the function will assume the project is being created
6520 # @param[in] repo_path The path to the main git repository
6521 # @param[in] design The name of the design
6522 
6523 # @param[in] list of variables to be written in the generics in the usual order
6524 
6525 proc WriteGenerics {mode repo_path design date timee\
6526  commit version top_hash top_ver hog_hash hog_ver \
6527  cons_ver cons_hash libs vers hashes ext_names ext_hashes \
6528  user_ip_repos user_ip_vers user_ip_hashes flavour {xml_ver ""} {xml_hash ""}} {
6529  Msg Info "Passing parameters/generics to project's top module..."
6530  ##### Passing Hog generic to top file
6531  # set global generic variables
6532  set generic_string [concat \
6533  "GLOBAL_DATE=[FormatGeneric $date]" \
6534  "GLOBAL_TIME=[FormatGeneric $timee]" \
6535  "GLOBAL_VER=[FormatGeneric $version]" \
6536  "GLOBAL_SHA=[FormatGeneric $commit]" \
6537  "TOP_SHA=[FormatGeneric $top_hash]" \
6538  "TOP_VER=[FormatGeneric $top_ver]" \
6539  "HOG_SHA=[FormatGeneric $hog_hash]" \
6540  "HOG_VER=[FormatGeneric $hog_ver]" \
6541  "CON_VER=[FormatGeneric $cons_ver]" \
6542  "CON_SHA=[FormatGeneric $cons_hash]"]
6543  # xml hash
6544  if {$xml_hash != "" && $xml_ver != ""} {
6545  lappend generic_string \
6546  "XML_VER=[FormatGeneric $xml_ver]" \
6547  "XML_SHA=[FormatGeneric $xml_hash]"
6548  }
6549  #set project specific lists
6550  foreach l $libs v $vers h $hashes {
6551  set ver "[string toupper $l]_VER=[FormatGeneric $v]"
6552  set hash "[string toupper $l]_SHA=[FormatGeneric $h]"
6553  # Replaces all occurrences of dots (.) and hyphens (-) in the generic name
6554  # with underscores (_) to make it compatible with VHDL/Verilog syntax
6555  # Uses regsub with -all flag to replace all matches of the regex pattern [\.-]
6556  set ver [regsub -all {[\.-]} $ver {_}]
6557  set hash [regsub -all {[\.-]} $hash {_}]
6558  lappend generic_string "$ver" "$hash"
6559  }
6560 
6561  foreach e $ext_names h $ext_hashes {
6562  set hash "[string toupper $e]_SHA=[FormatGeneric $h]"
6563  lappend generic_string "$hash"
6564  }
6565 
6566  foreach repo $user_ip_repos v $user_ip_vers h $user_ip_hashes {
6567  set repo_name [file tail $repo]
6568  set ver "[string toupper $repo_name]_VER=[FormatGeneric $v]"
6569  set hash "[string toupper $repo_name]_SHA=[FormatGeneric $h]"
6570  set ver [regsub -all {[\.-]} $ver {_}]
6571  set hash [regsub -all {[\.-]} $hash {_}]
6572  lappend generic_string "$ver" "$hash"
6573  }
6574 
6575  if {$flavour != -1} {
6576  lappend generic_string "FLAVOUR=$flavour"
6577  }
6578 
6579  # Dealing with project generics in Vivado
6580  if {[IsVivado] || [IsVitisClassic]} {
6581  set prj_generics [GenericToSimulatorString [GetGenericsFromConf $design] "Vivado"]
6582  set generic_string "$prj_generics $generic_string"
6583  }
6584 
6585  # Extract the generics from the top level source file
6586  if {[IsXilinx]} {
6587  # Top File can be retrieved only at creation time or in ISE
6588  if {$mode == "create" || [IsISE]} {
6589  set top_file [GetTopFile]
6590  set top_name [GetTopModule]
6591  if {[file exists $top_file]} {
6592  set generics [GetFileGenerics $top_file $top_name]
6593 
6594  Msg Debug "Found top level generics $generics in $top_file"
6595 
6596  set filtered_generic_string ""
6597 
6598  foreach generic_to_set [split [string trim $generic_string]] {
6599  set key [lindex [split $generic_to_set "="] 0]
6600  if {[dict exists $generics $key]} {
6601  Msg Debug "Hog generic $key found in $top_name"
6602  lappend filtered_generic_string "$generic_to_set"
6603  } else {
6604  Msg Warning "Generic $key is passed by Hog but is NOT present in $top_name."
6605  }
6606  }
6607 
6608  # only filter in ISE
6609  if {[IsISE]} {
6610  set generic_string $filtered_generic_string
6611  }
6612  }
6613  }
6614 
6615  set_property generic $generic_string [current_fileset]
6616  Msg Info "Setting parameters/generics..."
6617  Msg Debug "Detailed parameters/generics: $generic_string"
6618 
6619 
6620  if {[IsVivado]} {
6621  # Dealing with project generics in Simulators
6622  set simulator [get_property target_simulator [current_project]]
6623  if {$mode == "create"} {
6624  SetGenericsSimulation $repo_path $design $simulator
6625  }
6626 
6627  WriteGenericsToBdIPs $mode $repo_path $design $generic_string
6628  }
6629  } elseif {[IsSynplify]} {
6630  Msg Info "Setting Synplify parameters/generics one by one..."
6631  foreach generic $generic_string {
6632  Msg Debug "Setting Synplify generic: $generic"
6633  set_option -hdl_param -set "$generic"
6634  }
6635  } elseif {[IsDiamond]} {
6636  Msg Info "Setting Diamond parameters/generics one by one..."
6637  prj_impl option -impl Implementation0 HDL_PARAM "$generic_string"
6638  } elseif {[IsVitisClassic]} {
6639  if {[catch {set ws_apps [app list -dict]}]} { set ws_apps "" }
6640 
6641  foreach app_name [dict keys $ws_apps] {
6642  set defined_symbols [app config -name $app_name -get define-compiler-symbols]
6643  foreach generic_to_set [split [string trim $generic_string]] {
6644  set key [lindex [split $generic_to_set "="] 0]
6645  set value [lindex [split $generic_to_set "="] 1]
6646  if {[string match "32'h*" $value]} {
6647  set value [string map {"32'h" "0x"} $value]
6648  }
6649 
6650  foreach symbol [split $defined_symbols ";"] {
6651  if {[string match "$key=*" $symbol]} {
6652  Msg Debug "Generic $key found in $app_name, removing it..."
6653  app config -name $app_name -remove define-compiler-symbols "$symbol"
6654  }
6655  }
6656 
6657  Msg Info "Setting Vitis parameters/generics for app $app_name: $key=$value"
6658  app config -name $app_name define-compiler-symbols "$key=$value"
6659  }
6660  }
6661  }
6662 }
6663 
6664 ## @brief Applies generic values to IPs within block designs
6665 #
6666 # @param[in] mode create: to write the generics at creation time. synth to write at synthesis time
6667 # @param[in] repo_path The main path of the git repository
6668 # @param[in] proj The project name
6669 # @param[in] generic_string the string containing the generics to be applied
6670 proc WriteGenericsToBdIPs {mode repo_path proj generic_string} {
6671  Msg Debug "Parameters/generics passed to WriteGenericsToIP: $generic_string"
6672 
6673  set bd_ip_generics false
6674  set properties [ReadConf [lindex [GetConfFiles $repo_path/Top/$proj] 0]]
6675  if {[dict exists $properties "hog"]} {
6676  set propDict [dict get $properties "hog"]
6677  if {[dict exists $propDict "PASS_GENERICS_TO_BD_IPS"]} {
6678  set bd_ip_generics [dict get $propDict "PASS_GENERICS_TO_BD_IPS"]
6679  }
6680  }
6681 
6682  if {[string compare [string tolower $bd_ip_generics] "false"] == 0} {
6683  return
6684  }
6685 
6686  if {$mode == "synth"} {
6687  Msg Info "Attempting to apply generics pre-synthesis..."
6688  set PARENT_PRJ [get_property "PARENT.PROJECT_PATH" [current_project]]
6689  set workaround [open "$repo_path/Projects/$proj/.hog/presynth_workaround.tcl" "w"]
6690  puts $workaround "source \[lindex \$argv 0\];"
6691  puts $workaround "open_project \[lindex \$argv 1\];"
6692  puts $workaround "WriteGenericsToBdIPs \[lindex \$argv 2\] \[lindex \$argv 3\] \[lindex \$argv 4\] \[lindex \$argv 5\];"
6693  puts $workaround "close_project"
6694  close $workaround
6695  if {
6696  [catch {
6697  exec vivado -mode batch -source $repo_path/Projects/$proj/.hog/presynth_workaround.tcl \
6698  -tclargs $repo_path/Hog/Tcl/hog.tcl $PARENT_PRJ \
6699  "childprocess" $repo_path $proj $generic_string
6700  } errMsg] != 0
6701  } {
6702  Msg Error "Encountered an error while attempting workaround: $errMsg"
6703  }
6704  file delete $repo_path/Projects/$proj/.hog/presynth_workaround.tcl
6705  ResetRepoFiles "$repo_path/Projects/hog_reset_files"
6706  Msg Info "Done applying generics pre-synthesis."
6707  return
6708  }
6709 
6710  Msg Info "Looking for IPs to add generics to..."
6711  set ips_generic_string ""
6712  foreach generic_to_set [split [string trim $generic_string]] {
6713  set key [lindex [split $generic_to_set "="] 0]
6714  set value [lindex [split $generic_to_set "="] 1]
6715  append ips_generic_string "CONFIG.$key $value "
6716  }
6717 
6718 
6719  if {[string compare [string tolower $bd_ip_generics] "true"] == 0} {
6720  set ip_regex ".*"
6721  } else {
6722  set ip_regex $bd_ip_generics
6723  }
6724 
6725  set ip_list [get_ips -regex $ip_regex]
6726  Msg Debug "IPs found with regex \{$ip_regex\}: $ip_list"
6727 
6728  set regen_targets {}
6729 
6730  foreach {ip} $ip_list {
6731  set WARN_ABOUT_IP false
6732  set ip_props [list_property [get_ips $ip]]
6733 
6734  #Not sure if this is needed, but it's here to prevent potential errors with get_property
6735  if {[lsearch -exact $ip_props "IS_BD_CONTEXT"] == -1} {
6736  continue
6737  }
6738 
6739  if {[get_property "IS_BD_CONTEXT" [get_ips $ip]] eq "1"} {
6740  foreach {ip_prop} $ip_props {
6741  if {[dict exists $ips_generic_string $ip_prop]} {
6742  if {$WARN_ABOUT_IP == false} {
6743  lappend regen_targets [get_property SCOPE [get_ips $ip]]
6744  Msg Warning "The ip \{$ip\} contains generics that are set by Hog.\
6745  If this is IP is apart of a block design, the .bd file may contain stale, unused, values.\
6746  Hog will always apply the most up-to-date values to the IP during synthesis,\
6747  however these values may or may not be reflected in the .bd file."
6748  set WARN_ABOUT_IP true
6749  }
6750 
6751  # vivado is annoying about the format when setting generics for ips
6752  # this tries to find and set the format to what vivado likes
6753  set xci_path [get_property IP_FILE [get_ips $ip]]
6754  set generic_format [GetGenericFormatFromXci $ip_prop $xci_path]
6755  if {[string equal $generic_format "ERROR"]} {
6756  Msg Warning "Could not find format for generic $ip_prop in IP $ip. Skipping..."
6757  continue
6758  }
6759 
6760  set value_to_set [dict get $ips_generic_string $ip_prop]
6761  switch -exact $generic_format {
6762  "long" {
6763  if {[string match "32'h*" $value_to_set]} {
6764  scan [string map {"32'h" ""} $value_to_set] "%x" value_to_set
6765  }
6766  }
6767  "bool" {
6768  set value_to_set [expr {$value_to_set ? "true" : "false"}]
6769  }
6770  "float" {
6771  if {[string match "32'h*" $value_to_set]} {
6772  binary scan [binary format H* [string map {"32'h" ""} $value_to_set]] d value_to_set
6773  }
6774  }
6775  "bitString" {
6776  if {[string match "32'h*" $value_to_set]} {
6777  set value_to_set [string map {"32'h" "0x"} $value_to_set]
6778  }
6779  }
6780  "string" {
6781  set value_to_set [format "%s" $value_to_set]
6782  }
6783  default {
6784  Msg Warning "Unknown generic format $generic_format for IP $ip. Will attempt to pass as string..."
6785  }
6786  }
6787 
6788 
6789  Msg Info "The IP \{$ip\} contains: $ip_prop ($generic_format), setting it to $value_to_set."
6790  if {[catch {set_property -name $ip_prop -value $value_to_set -objects [get_ips $ip]} prop_error]} {
6791  Msg CriticalWarning "Failed to set property $ip_prop to $value_to_set for IP \{$ip\}: $prop_error"
6792  }
6793  }
6794  }
6795  }
6796  }
6797 
6798  foreach {regen_target} [lsort -unique $regen_targets] {
6799  Msg Info "Regenerating target: $regen_target"
6800  if {[catch {generate_target -force all [get_files $regen_target]} prop_error]} {
6801  Msg CriticalWarning "Failed to regen targets: $prop_error"
6802  }
6803  }
6804 }
6805 
6806 ## @brief Returns the format of a generic from an XML file
6807 ## @param[in] generic_name: The name of the generic
6808 ## @param[in] xml_file: The path to the XML XCI file
6809 proc GetGenericFormatFromXciXML {generic_name xml_file} {
6810  if {![file exists $xml_file]} {
6811  Msg Error "Could not find XML file: $xml_file"
6812  return "ERROR"
6813  }
6814 
6815  set fp [open $xml_file r]
6816  set xci_data [read $fp]
6817  close $fp
6818 
6819  set paramType "string"
6820  set modelparam_regex [format {^.*\y%s\y.*$} [string map {"CONFIG." "MODELPARAM_VALUE."} $generic_name]]
6821  set format_regex {format="([^"]+)"}
6822 
6823  set line [lindex [regexp -inline -line $modelparam_regex $xci_data] 0]
6824  Msg Debug "line: $line"
6825 
6826  if {[regexp $format_regex $line match format_value]} {
6827  Msg Debug "Extracted: $format_value format from xml"
6828  set paramType $format_value
6829  } else {
6830  Msg Debug "No format found, using string"
6831  }
6832 
6833  return $paramType
6834 }
6835 
6836 ## @brief Returns the format of a generic from an XCI file
6837 ## @param[in] generic_name: The name of the generic
6838 ## @param[in] xci_file: The path to the XCI file
6839 proc GetGenericFormatFromXci {generic_name xci_file} {
6840  if {![file exists $xci_file]} {
6841  Msg Error "Could not find XCI file: $xci_file"
6842  return "ERROR"
6843  }
6844 
6845  set fp [open $xci_file r]
6846  set xci_data [read $fp]
6847  close $fp
6848 
6849  set paramType "string"
6850  if {[string first "xilinx.com:schema:json_instance:1.0" $xci_data] == -1} {
6851  Msg Debug "XCI format is not JSON, trying XML..."
6852  set xml_file "[file rootname $xci_file].xml"
6853  return [GetGenericFormatFromXciXML $generic_name $xml_file]
6854  }
6855 
6856  set generic_name [string map {"CONFIG." ""} $generic_name]
6857  set ip_inst [ParseJSON $xci_data "ip_inst"]
6858  set parameters [dict get $ip_inst parameters]
6859  set component_parameters [dict get $parameters component_parameters]
6860  if {[dict exists $component_parameters $generic_name]} {
6861  set generic_info [dict get $component_parameters $generic_name]
6862  if {[dict exists [lindex $generic_info 0] format]} {
6863  set paramType [dict get [lindex $generic_info 0] format]
6864  Msg Debug "Extracted: $paramType format from xci"
6865  return $paramType
6866  }
6867  Msg Debug "No format found, using string"
6868  return $paramType
6869  } else {
6870  return "ERROR"
6871  }
6872 }
6873 
6874 
6875 ## @brief Returns the gitlab-ci.yml snippet for a CI stage and a defined project
6876 #
6877 # @param[in] proj_name: The project name
6878 # @param[in] ci_confs: Dictionary with CI configurations
6879 #
6880 proc WriteGitLabCIYAML {proj_name {ci_conf ""}} {
6881  if {[catch {package require yaml 0.3.3} YAMLPACKAGE]} {
6882  Msg CriticalWarning "Cannot find package YAML.\n Error message: $YAMLPACKAGE. \
6883  If you are running on tclsh, you can fix this by installing package \"tcllib\""
6884  return -1
6885  }
6886 
6887  set job_list []
6888  if {$ci_conf != ""} {
6889  set ci_confs [ReadConf $ci_conf]
6890  foreach sec [dict keys $ci_confs] {
6891  if {[string first : $sec] == -1} {
6892  lappend job_list $sec
6893  }
6894  }
6895  } else {
6896  set job_list {"generate_project" "simulate_project"}
6897  set ci_confs ""
6898  }
6899 
6900  set out_yaml [huddle create]
6901  foreach job $job_list {
6902  # Check main project configurations
6903  set huddle_tags [huddle list]
6904  set tag_section ""
6905  set sec_dict [dict create]
6906 
6907  if {$ci_confs != ""} {
6908  foreach var [dict keys [dict get $ci_confs $job]] {
6909  if {$var == "tags"} {
6910  set tag_section "tags"
6911  set tags [dict get [dict get $ci_confs $job] $var]
6912  set tags [split $tags ","]
6913  foreach tag $tags {
6914  set tag_list [huddle list $tag]
6915  set huddle_tags [huddle combine $huddle_tags $tag_list]
6916  }
6917  } else {
6918  dict set sec_dict $var [dict get [dict get $ci_confs $job] $var]
6919  }
6920  }
6921  }
6922 
6923  # Check if there are extra variables in the conf file
6924  set huddle_variables [huddle create "PROJECT_NAME" $proj_name "extends" ".vars"]
6925  if {[dict exists $ci_confs "$job:variables"]} {
6926  set var_dict [dict get $ci_confs $job:variables]
6927  foreach var [dict keys $var_dict] {
6928  # puts [dict get $var_dict $var]
6929  set value [dict get $var_dict "$var"]
6930  set var_inner [huddle create "$var" "$value"]
6931  set huddle_variables [huddle combine $huddle_variables $var_inner]
6932  }
6933  }
6934 
6935 
6936  set middle [huddle create "extends" ".$job" "variables" $huddle_variables]
6937  foreach sec [dict keys $sec_dict] {
6938  set value [dict get $sec_dict $sec]
6939  set var_inner [huddle create "$sec" "$value"]
6940  set middle [huddle combine $middle $var_inner]
6941  }
6942  if {$tag_section != ""} {
6943  set middle2 [huddle create "$tag_section" $huddle_tags]
6944  set middle [huddle combine $middle $middle2]
6945  }
6946 
6947  set outer [huddle create "$job:$proj_name" $middle]
6948  set out_yaml [huddle combine $out_yaml $outer]
6949  }
6950 
6951  return [string trimleft [yaml::huddle2yaml $out_yaml] "-"]
6952 }
6953 
6954 # @brief Write the content of Hog-library-dictionary created from the project into a .src/.ext/.con list file
6955 #
6956 # @param[in] libs The Hog-Library dictionary with the list of files in the project to write
6957 # @param[in] props The Hog-library dictionary with the file sets
6958 # @param[in] list_path The path of the output list file
6959 # @param[in] repo_path The main repository path
6960 # @param[in] ext_path The external path
6961 proc WriteListFiles {libs props list_path repo_path {ext_path ""}} {
6962  # Writing simulation list files
6963  foreach lib [dict keys $libs] {
6964  if {[llength [DictGet $libs $lib]] > 0} {
6965  set list_file_name $list_path$lib
6966  set list_file [open $list_file_name w]
6967  Msg Info "Writing $list_file_name..."
6968  puts $list_file "#Generated by Hog on [clock format [clock seconds] -format "%Y-%m-%d %H:%M:%S"]"
6969  foreach file [DictGet $libs $lib] {
6970  # Retrieve file properties from prop list
6971  set prop [DictGet $props $file]
6972  # Check if file is local to the repository or external
6973  if {[RelativeLocal $repo_path $file] != ""} {
6974  set file_path [RelativeLocal $repo_path $file]
6975  puts $list_file "$file_path $prop"
6976  } elseif {[RelativeLocal $ext_path $file] != ""} {
6977  set file_path [RelativeLocal $ext_path $file]
6978  set ext_list_file [open "[file rootname $list_file].ext" a]
6979  puts $ext_list_file "$file_path $prop"
6980  close $ext_list_file
6981  } else {
6982  # File is not relative to repo or ext_path... Write a Warning and continue
6983  Msg Warning "The path of file $file is not relative to your repository. Please check!"
6984  }
6985  }
6986  close $list_file
6987  }
6988  }
6989 }
6990 
6991 # @brief Write the content of Hog-library-dictionary created from the project into a .sim list file
6992 #
6993 # @param[in] libs The Hog-Library dictionary with the list of files in the project to write
6994 # @param[in] props The Hog-library dictionary with the file sets
6995 # @param[in] simsets The Hog-library dictionary with the file sets (relevant only for simulation)
6996 # @param[in] list_path The path of the output list file
6997 # @param[in] repo_path The main repository path
6998 # @param[in] force If 1, it will overwrite the existing list files
6999 proc WriteSimListFile {simset libs props simsets list_path repo_path {force 0}} {
7000  # Writing simulation list file
7001  set list_file_name $list_path/${simset}.sim
7002  if {$force == 0 && [file exists $list_file_name]} {
7003  Msg Info "List file $list_file_name already exists, skipping..."
7004  continue
7005  }
7006 
7007  set list_file [open $list_file_name a+]
7008 
7009  # Write the header with the simulator
7010  puts $list_file "\[files\]"
7011  Msg Info "Writing $list_file_name..."
7012  foreach lib [DictGet $simsets $simset] {
7013  foreach file [DictGet $libs $lib] {
7014  # Retrieve file properties from prop list
7015  set prop [DictGet $props $file]
7016  # Check if file is local to the repository or external
7017  if {[RelativeLocal $repo_path $file] != ""} {
7018  set file_path [RelativeLocal $repo_path $file]
7019  set lib_name [file rootname $lib]
7020  if {$lib_name != $simset && [file extension $file] == ".vhd" && [file extension $file] == ""} {
7021  lappend prop "lib=$lib_name"
7022  }
7023  puts $list_file "$file_path $prop"
7024  } else {
7025  # File is not relative to repo or ext_path... Write a Warning and continue
7026  Msg Warning "The path of file $file is not relative to your repository. Please check!"
7027  }
7028  }
7029  }
7030  close $list_file
7031 }
7032 
7033 
7034 ## @brief Write into a file, and if the file exists, it will append the string
7035 #
7036 # @param[out] File The log file to write into the message
7037 # @param[in] msg The message text
7038 proc WriteToFile {File msg} {
7039  set f [open $File a+]
7040  puts $f $msg
7041  close $f
7042 }
7043 
7044 ## Write the resource utilization table into a a file (Vivado only)
7045 #
7046 # @param[in] input the input .rpt report file from Vivado
7047 # @param[in] output the output file
7048 # @param[in] project_name the name of the project
7049 # @param[in] run synthesis or implementation
7050 proc WriteUtilizationSummary {input output project_name run} {
7051  set f [open $input "r"]
7052  set o [open $output "a"]
7053  puts $o "## $project_name $run Utilization report\n\n"
7054  struct::matrix util_m
7055  util_m add columns 14
7056  util_m add row
7057  if {[GetIDEVersion] >= 2021.0} {
7058  util_m add row "| **Site Type** | **Used** | **Fixed** | **Prohibited** | **Available** | **Util%** |"
7059  util_m add row "| --- | --- | --- | --- | --- | --- |"
7060  } else {
7061  util_m add row "| **Site Type** | **Used** | **Fixed** | **Available** | **Util%** |"
7062  util_m add row "| --- | --- | --- | --- | --- |"
7063  }
7064 
7065  set luts 0
7066  set regs 0
7067  set uram 0
7068  set bram 0
7069  set dsps 0
7070  set ios 0
7071 
7072  while {[gets $f line] >= 0} {
7073  if {([string first "| CLB LUTs" $line] >= 0 || [string first "| Slice LUTs" $line] >= 0) && $luts == 0} {
7074  util_m add row $line
7075  set luts 1
7076  }
7077  if {([string first "| CLB Registers" $line] >= 0 || [string first "| Slice Registers" $line] >= 0) && $regs == 0} {
7078  util_m add row $line
7079  set regs 1
7080  }
7081  if {[string first "| Block RAM Tile" $line] >= 0 && $bram == 0} {
7082  util_m add row $line
7083  set bram 1
7084  }
7085  if {[string first "URAM " $line] >= 0 && $uram == 0} {
7086  util_m add row $line
7087  set uram 1
7088  }
7089  if {[string first "DSPs" $line] >= 0 && $dsps == 0} {
7090  util_m add row $line
7091  set dsps 1
7092  }
7093  if {[string first "Bonded IOB" $line] >= 0 && $ios == 0} {
7094  util_m add row $line
7095  set ios 1
7096  }
7097  }
7098  util_m add row
7099 
7100  close $f
7101  puts $o [util_m format 2string]
7102  close $o
7103 }
7104 
7105 # Check Git Version when sourcing hog.tcl
7106 if {[GitVersion 2.7.2] == 0} {
7107  Msg Error "Found Git version older than 2.7.2. Hog will not work as expected, exiting now."
7108 }