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