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