Hog v9.71.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" "scoped_to_ref" "scoped_to_cells"] \
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 {parameters {directory .}} {
1858  set commands_dict [dict create]
1859  set commands_files [glob -nocomplain $directory/*.tcl]
1860 
1861  if {[llength $commands_files] == 0} {
1862  return ""
1863  }
1864 
1865  foreach file $commands_files {
1866 
1867  #Msg Info "do compile libe? $do_compile_lib"
1868  set custom_cmd [LoadCustomCommandFile $file $parameters]
1869 
1870  if {$custom_cmd eq ""} {
1871  continue
1872  }
1873 
1874  #Msg Info "Validating custom command $custom_cmd"
1875  set custom_cmd_name [dict get $custom_cmd NAME]
1876 
1877  Msg Debug "Loaded custom command '$custom_cmd_name' from $file"
1878 
1879  #Ensure command is not already defined
1880  if {[dict exists $commands_dict $custom_cmd_name]} {
1881  Msg Error "Custom command '$custom_cmd_name' in $file already defined as: \[dict get $commands_dict $custom_cmd_name\]. Skipping."
1882  continue
1883  }
1884 
1885 
1886 
1887  set custom_cmd_name [ string toupper $custom_cmd_name ]
1888  dict set commands_dict $custom_cmd_name $custom_cmd
1889  }
1890 
1891  return $commands_dict
1892 }
1893 
1894 proc SanitizeCustomCommand {cmdDict file parameters} {
1895  # Normalize all user-provided keys to uppercase so NAME/DESCRIPTION/etc are case-insensitive.
1896  set normalized {}
1897  foreach k [dict keys $cmdDict] {
1898  set K [string toupper $k]
1899  dict set normalized $K [dict get $cmdDict $k]
1900  }
1901 
1902  set cmdDict $normalized
1903  if {![dict exists $cmdDict NAME]} {
1904  Msg Error "Custom command in $file missing required key NAME. Skipping."
1905  return ""
1906  }
1907  if {![dict exists $cmdDict SCRIPT]} {
1908  Msg Error "Custom command '$[dict get $cmdDict NAME]' in $file missing SCRIPT. Skipping."
1909  return ""
1910  }
1911 
1912  # Allowed keys (uppercased). IDE is optional and will be validated if present.
1913  set allowed {NAME DESCRIPTION OPTIONS CUSTOM_OPTIONS SCRIPT IDE NO_EXIT}
1914  foreach k [dict keys $cmdDict] {
1915  if {[lsearch -exact $allowed $k] < 0} {
1916  Msg Warning "Custom command '[dict get $cmdDict NAME]' in $file: unknown key '$k'. Allowed: $allowed. Skipping."
1917  }
1918  }
1919 
1920  # NAME
1921  set name [string trim [dict get $cmdDict NAME]]
1922  if {$name eq ""} {
1923  Msg Error "Custom command in $file has empty NAME. Skipping."
1924  return ""
1925  }
1926 
1927  if {![regexp {^[a-zA-Z][a-zA-Z0-9_]+$} $name]} {
1928  Msg Error "Custom command NAME '$name' (file $file) contains invalid characters."
1929  }
1930 
1931  # DESCRIPTION
1932  if {![dict exists $cmdDict DESCRIPTION]} {
1933  dict set cmdDict DESCRIPTION "No description provided."
1934  }
1935 
1936 
1937  set hog_parameters {}
1938  foreach p $parameters {
1939  lappend hog_parameters [lindex $p 0]
1940  }
1941 
1942  # OPTIONS
1943  set hog_options {}
1944  if {[dict exists $cmdDict OPTIONS]} {
1945  set raw_opts [dict get $cmdDict OPTIONS]
1946  if {![llength $raw_opts]} {
1947  set raw_opts {}
1948  }
1949 
1950  foreach item $raw_opts {
1951  set found 0
1952  foreach p $parameters {
1953  set hog_parameter [lindex $p 0]
1954  if { $item eq $hog_parameter } {
1955  lappend hog_options $p
1956  set found 1
1957  break
1958  }
1959  }
1960  if {!$found} {
1961  Msg Warning "Custom command '$name' in $file: option '$item' not found in Hog parameters. Skipping."
1962  }
1963  }
1964  dict set cmdDict OPTIONS $hog_options
1965  } else {
1966  dict set cmdDict CUSTOM_OPTIONS {}
1967  }
1968 
1969 
1970 
1971  # CUSTOM_OPTIONS
1972  set opt_defs {}
1973  if {[dict exists $cmdDict CUSTOM_OPTIONS]} {
1974  set raw_opts [dict get $cmdDict CUSTOM_OPTIONS]
1975  if {![llength $raw_opts]} {
1976  set raw_opts {}
1977  }
1978  foreach item $raw_opts {
1979 
1980  if {[llength $item] != 2 && [llength $item] != 3} {
1981  Msg Error "Bad custom option: \[$item\]. Custom command '$name' in $file: \
1982  each CUSTOM_OPTIONS entry must be {option \"help\"} for flags \
1983  and {option \"default_value\" \"help\"} for options with arguments. Skipping command."
1984  return ""
1985  }
1986 
1987  if {[llength $item] == 2} {
1988  lassign $item opt help
1989  set def ""
1990  } else {
1991  lassign $item opt def help
1992  }
1993 
1994  if { [IsInList $opt $hog_parameters] == 1 } {
1995  Msg Warning "Custom command '$name' in $file: option '$opt' already defined in Hog parameters. Skipping."
1996  continue
1997  }
1998 
1999 
2000  #optional .arg in option regex
2001  if {![regexp {^[a-zA-Z][a-zA-Z0-9_]*(\.arg)?$} $opt]} {
2002  Msg Error "Custom command '$name' in $file: invalid option name '$opt'."
2003  return ""
2004  }
2005 
2006  if {$help eq ""} {
2007  Msg Warning "Custom command '$name' option '$opt' has empty help text."
2008  }
2009  }
2010  } else {
2011  dict set cmdDict CUSTOM_OPTIONS {}
2012  }
2013 
2014  # NO EXIT
2015  if {[dict exists $cmdDict NO_EXIT]} {
2016  set no_exit [dict get $cmdDict NO_EXIT]
2017  set no_exit [string tolower [string trim $no_exit]]
2018 
2019  if {$no_exit eq "1" || $no_exit eq "true"} {
2020  set no_exit 1
2021  } else {
2022  set no_exit 0
2023  }
2024 
2025  dict set cmdDict NO_EXIT $no_exit
2026  } else {
2027  dict set cmdDict NO_EXIT 0
2028  }
2029 
2030  return $cmdDict
2031 }
2032 
2033 proc LoadCustomCommandFile {file parameters} {
2034  set saved_pwd [pwd]
2035  set dir [file dirname $file]
2036  cd $dir
2037  unset -nocomplain ::hog_command
2038  set rc [catch {source $file} err]
2039  cd $saved_pwd
2040  if {$rc} {
2041  Msg Error "Error sourcing custom command file $file: $err"
2042  return ""
2043  }
2044  if {![info exists ::hog_command]} {
2045  Msg Warning "File $file did not define ::hog_command. Skipping."
2046  return ""
2047  }
2048  set cmdDict $::hog_command
2049  # Ensure it's a dict
2050  if {[catch {dict size $cmdDict}]} {
2051  Msg Error "In $file ::hog_command is not a valid dict. Skipping."
2052  return ""
2053  }
2054  return [SanitizeCustomCommand $cmdDict $file $parameters]
2055 }
2056 
2057 
2058 ## Get the Date and time of a commit (or current time if Git < 2.9.3)
2059 #
2060 # @param[in] commit The commit
2061 proc GetDateAndTime {commit} {
2062  set clock_seconds [clock seconds]
2063 
2064  if {[GitVersion 2.9.3]} {
2065  set date [Git "log -1 --format=%cd --date=format:%d%m%Y $commit"]
2066  set timee [Git "log -1 --format=%cd --date=format:00%H%M%S $commit"]
2067  } else {
2068  Msg Warning "Found Git version older than 2.9.3. Using current date and time instead of commit time."
2069  set date [clock format $clock_seconds -format {%d%m%Y}]
2070  set timee [clock format $clock_seconds -format {00%H%M%S}]
2071  }
2072  return [list $date $timee]
2073 }
2074 
2075 ## @brief Gets a list of files contained in the current fileset that match a file name (passed as parameter)
2076 #
2077 # The file name is matched against the input parameter.
2078 #
2079 # @param[in] file name (or part of it)
2080 # @param[in] fileset name
2081 #
2082 # @return a list of files matching the parameter in the chosen fileset
2083 #
2084 proc GetFile {file fileset} {
2085  if {[IsXilinx]} {
2086  # Vivado
2087  set Files [get_files -all $file -of_object [get_filesets $fileset]]
2088  set f [lindex $Files 0]
2089 
2090  return $f
2091  } elseif {[IsQuartus]} {
2092  # Quartus
2093  return ""
2094  } else {
2095  # Tcl Shell
2096  puts "***DEBUG Hog:GetFile $file"
2097  return "DEBUG_file"
2098  }
2099 }
2100 
2101 ## @brief Extract the generics from a file
2102 #
2103 # @param[in] filename The file from which to extract the generics
2104 # @param[in] entity The entity in the file from which to extract the generics (default "")
2105 proc GetFileGenerics {filename {entity ""}} {
2106  set file_type [FindFileType $filename]
2107  if {[string equal $file_type "VERILOG_FILE"] || [string equal $file_type "SYSTEMVERILOG_FILE"]} {
2108  return [GetVerilogGenerics $filename]
2109  } elseif {[string equal $file_type "VHDL_FILE"]} {
2110  return [GetVhdlGenerics $filename $entity]
2111  } else {
2112  Msg CriticalWarning "Could not determine extension of top level file."
2113  }
2114 }
2115 
2116 ## @brief Gets custom generics from hog
2117 #
2118 # @param[in] proj_dir: the top folder of the project
2119 # @return dict with generics
2120 #
2121 proc GetGenericsFromConf {proj_dir} {
2122  set generics_dict [dict create]
2123  set top_dir "Top/$proj_dir"
2124  set conf_file "$top_dir/hog.conf"
2125  set conf_index 0
2126  Msg Debug "GetGenericsFromConf called with proj_dir=$proj_dir, top_dir=$top_dir"
2127 
2128  if {[file exists $conf_file]} {
2129  set properties [ReadConf [lindex [GetConfFiles $top_dir] $conf_index]]
2130  if {[dict exists $properties generics]} {
2131  set generics_dict [dict get $properties generics]
2132  }
2133  } else {
2134  Msg Warning "File $conf_file not found."
2135  }
2136  return $generics_dict
2137 }
2138 
2139 ## @brief Gets the simulation sets from the project
2140 #
2141 # @param[in] project_name: the name of the project
2142 # @param[in] repo_path: the path to the repository
2143 # @param[in] simsets: a list of simulation sets to retrieve (default: all)
2144 # @param[in] ghdl: if 1, only GHDL simulation sets are returned (default: 0),
2145 # otherwise only non-GHDL simulation sets are returned
2146 # @param[in] no_conf: if 1, the simulation sets are returned without reading the sim.conf file (default: 0)
2147 # @return a dictionary with the simulation sets, where the keys are the simulation set names
2148 # and the values are dictionaries with the properties of each simulation set
2149 proc GetSimSets {project_name repo_path {simsets ""} {ghdl 0} {no_conf 0}} {
2150  set simsets_dict [dict create]
2151  set list_dir "$repo_path/Top/$project_name/list"
2152  set list_files []
2153  if {$simsets != ""} {
2154  foreach s $simsets {
2155  set list_file "$list_dir/$s.sim"
2156  if {[file exists $list_file]} {
2157  lappend list_files $list_file
2158  } elseif {$s != "sim_1"} {
2159  Msg CriticalWarning "Simulation set list file $list_file not found."
2160  return ""
2161  }
2162  }
2163  } else {
2164  set list_files [glob -nocomplain -directory $list_dir "*.sim"]
2165  }
2166 
2167  # Get simulation properties from conf file
2168  set proj_dir [file normalize $repo_path/Top/$project_name]
2169  set sim_file [file normalize $proj_dir/sim.conf]
2170 
2171  foreach list_file $list_files {
2172  set file_name [file tail $list_file]
2173  set simset_name [file rootname $file_name]
2174  set fp [open $list_file r]
2175  set file_data [read $fp]
2176  close $fp
2177  set data [split $file_data "\n"]
2178 
2179  set firstline [lindex $data 0]
2180  # Find simulator
2181  if {[regexp {^ *\# *Simulator} $firstline]} {
2182  set simulator_prop [regexp -all -inline {\S+} $firstline]
2183  set simulator [string tolower [lindex $simulator_prop end]]
2184  } else {
2185  Msg Warning "Simulator not set in $simset_name.sim. \
2186  The first line of $simset_name.sim should be #Simulator <SIMULATOR_NAME>,\
2187  where <SIMULATOR_NAME> can be xsim, questa, modelsim, ghdl, riviera, activehdl,\
2188  ies, or vcs, e.g. #Simulator questa.\
2189  Setting simulator by default to xsim."
2190  set simulator "xsim"
2191  }
2192  if {$simulator eq "skip_simulation"} {
2193  Msg Info "Skipping simulation for $simset_name"
2194  continue
2195  }
2196  if {($ghdl == 1 && $simulator != "ghdl") || ($ghdl == 0 && $simulator == "ghdl")} {
2197  continue
2198  }
2199 
2200  set SIM_PROPERTIES ""
2201  if {[file exists $sim_file] && $no_conf == 0} {
2202  set SIM_PROPERTIES [ReadConf $sim_file]
2203  }
2204 
2205  set global_sim_props [dict create]
2206  dict set global_sim_props "properties" [DictGet $SIM_PROPERTIES "sim"]
2207  dict set global_sim_props "generics" [DictGet $SIM_PROPERTIES "generics"]
2208  dict set global_sim_props "hog" [DictGet $SIM_PROPERTIES "hog"]
2209 
2210 
2211  set sim_dict [dict create]
2212  dict set sim_dict "simulator" $simulator
2213  if {[dict exists $SIM_PROPERTIES $simset_name]} {
2214  dict set sim_dict "properties" [DictGet $SIM_PROPERTIES $simset_name]
2215  dict set sim_dict "generics" [DictGet $SIM_PROPERTIES "$simset_name:generics"]
2216  dict set sim_dict "hog" [DictGet $SIM_PROPERTIES "$simset_name:hog"]
2217  } elseif {$no_conf == 0} {
2218  # Retrieve properties from .sim file
2219  set conf_dict [ReadConf $list_file]
2220  set sim_dict [MergeDict $sim_dict $conf_dict 0]
2221  }
2222  set sim_dict [MergeDict $sim_dict $global_sim_props 0]
2223  dict set simsets_dict $simset_name $sim_dict
2224  }
2225  return $simsets_dict
2226 }
2227 
2228 ## @brief Gets all custom <simset>:generics from sim.conf
2229 #
2230 # @param[in] proj_dir: the top folder of the project
2231 # @return nested dict with all <simset>:generics
2232 #
2233 proc GetSimsetGenericsFromConf {proj_dir} {
2234  set simsets_generics_dict [dict create]
2235  set top_dir "Top/$proj_dir"
2236  set conf_file "$top_dir/sim.conf"
2237  set conf_index 1
2238 
2239  if {[file exists $conf_file]} {
2240  set properties [ReadConf [lindex [GetConfFiles $top_dir] $conf_index]]
2241  # Filter the dictionary for keys ending with ":generics"
2242  set simsets_generics_dict [dict filter $properties key *:generics]
2243  } else {
2244  Msg Warning "File $conf_file not found."
2245  }
2246  return $simsets_generics_dict
2247 }
2248 
2249 
2250 ## Returns the group name from the project directory
2251 #
2252 # @param[in] proj_dir project directory
2253 # @param[in] repo_dir repository directory
2254 #
2255 # @return the group name without initial and final slashes
2256 #
2257 proc GetGroupName {proj_dir repo_dir} {
2258  if {[regexp {^(.*)/(Top|Projects)/+(.*?)/*$} $proj_dir dummy possible_repo_dir proj_or_top dir]} {
2259  # The Top or Project folder is in the root of a the git repository
2260  if {[file normalize $repo_dir] eq [file normalize $possible_repo_dir]} {
2261  set group [file dir $dir]
2262  if {$group == "."} {
2263  set group ""
2264  }
2265  } else {
2266  # The Top or Project folder is NOT in the root of a git repository
2267  Msg Warning "Project directory $proj_dir seems to be in $possible_repo_dir which is not a the main Git repository $repo_dir."
2268  }
2269  } else {
2270  Msg Warning "Could not parse project directory $proj_dir"
2271  set group ""
2272  }
2273  return $group
2274 }
2275 
2276 ## Get custom Hog describe of a specific SHA
2277 #
2278 # @param[in] sha the git sha of the commit you want to calculate the describe of
2279 # @param[in] repo_path the main path of the repository
2280 #
2281 # @return the Hog describe of the sha or the current one if the sha is 0
2282 #
2283 proc GetHogDescribe {sha {repo_path .}} {
2284  if {$sha == 0} {
2285  # in case the repo is dirty, we use the last committed sha and add a -dirty suffix
2286  set new_sha "[string toupper [GetSHA]]"
2287  set suffix "-dirty"
2288  } else {
2289  set new_sha [string toupper $sha]
2290  set suffix ""
2291  }
2292  set describe "v[HexVersionToString [GetVerFromSHA $new_sha $repo_path]]-$new_sha$suffix"
2293  return $describe
2294 }
2295 
2296 ## @brief Extract files, libraries and properties from the project's list files
2297 #
2298 # @param[in] args The arguments are <list_path> <repository path>[options]
2299 # * list_path path to the list file directory
2300 # Options:
2301 # * -list_files <List files> the file wildcard, if not specified all Hog list files will be looked for
2302 # * -sha_mode forwarded to ReadListFile, see there for info
2303 # * -ext_path <external path> path for external libraries forwarded to ReadListFile
2304 #
2305 # @return a list of 3 dictionaries: libraries and properties
2306 # - libraries has library name as keys and a list of filenames as values
2307 # - properties has file names as keys and a list of properties as values
2308 # - filesets has the fileset name as keys and the correspondent list of libraries as values (significant only for simulations)
2309 proc GetHogFiles {args} {
2310  if {[IsQuartus]} {
2311  load_package report
2312  if {[catch {package require cmdline} ERROR]} {
2313  puts "$ERROR\n If you are running this script on tclsh, you can fix this by installing 'tcllib'"
2314  return 0
2315  }
2316  }
2317 
2318 
2319  set parameters {
2320  {list_files.arg "" "The file wildcard, if not specified all Hog list files will be looked for."}
2321  {sha_mode "Forwarded to ReadListFile, see there for info."}
2322  {ext_path.arg "" "Path for the external libraries forwarded to ReadListFile."}
2323  {print_log "Forwarded to ReadListFile, see there for info."}
2324  }
2325  set usage "USAGE: GetHogFiles \[options\] <list path> <repository path>"
2326  if {[catch {array set options [cmdline::getoptions args $parameters $usage]}] || [llength $args] != 2} {
2327  Msg CriticalWarning [cmdline::usage $parameters $usage]
2328  return
2329  }
2330  set list_path [lindex $args 0]
2331  set repo_path [lindex $args 1]
2332 
2333  set list_files $options(list_files)
2334  set sha_mode $options(sha_mode)
2335  set ext_path $options(ext_path)
2336  set print_log $options(print_log)
2337 
2338  if {$sha_mode == 1} {
2339  set sha_mode_opt "-sha_mode"
2340  } else {
2341  set sha_mode_opt ""
2342  }
2343 
2344  if {$print_log == 1} {
2345  set print_log_opt "-print_log"
2346  } else {
2347  set print_log_opt ""
2348  }
2349 
2350 
2351  if {$list_files == ""} {
2352  set list_files {.src,.con,.sim,.ext}
2353  }
2354  set libraries [dict create]
2355  set properties [dict create]
2356  set list_files [glob -nocomplain -directory $list_path "*{$list_files}"]
2357  set filesets [dict create]
2358 
2359  foreach f $list_files {
2360  set ext [file extension $f]
2361  if {$ext == ".ext"} {
2362  lassign [ReadListFile {*}"$sha_mode_opt $print_log_opt $f $ext_path"] l p fs
2363  } else {
2364  lassign [ReadListFile {*}"$sha_mode_opt $print_log_opt $f $repo_path"] l p fs
2365  }
2366  set libraries [MergeDict $l $libraries]
2367  set properties [MergeDict $p $properties]
2368  Msg Debug "list file $f, filesets: $fs"
2369  set filesets [MergeDict $fs $filesets]
2370  Msg Debug "Merged filesets $filesets"
2371  }
2372  return [list $libraries $properties $filesets]
2373 }
2374 
2375 # @brief Get the IDE of a Hog project and returns the correct argument for the IDE cli command
2376 #
2377 # @param[in] proj_conf The project hog.conf file
2378 # @param[in] custom_ver If set, use this version instead of the one in the hog.conf
2379 proc GetIDECommand {proj_conf {custom_ver ""}} {
2380  if {$custom_ver ne ""} {
2381  set ide_name_and_ver [string tolower "$custom_ver"]
2382  } elseif {[file exists $proj_conf]} {
2383  set ide_name_and_ver [string tolower [GetIDEFromConf $proj_conf]]
2384  } else {
2385  Msg Error "Configuration file $proj_conf not found."
2386  }
2387 
2388  set ide_name [lindex [regexp -all -inline {\S+} $ide_name_and_ver] 0]
2389 
2390  if {$ide_name eq "vivado" || $ide_name eq "vivado_vitis_classic"} {
2391  set command "vivado"
2392  # A space ater the before_tcl_script is important
2393  set before_tcl_script " -nojournal -nolog -mode batch -notrace -source "
2394  set after_tcl_script " -tclargs "
2395  set end_marker ""
2396  } elseif {$ide_name eq "planahead"} {
2397  set command "planAhead"
2398  # A space ater the before_tcl_script is important
2399  set before_tcl_script " -nojournal -nolog -mode batch -notrace -source "
2400  set after_tcl_script " -tclargs "
2401  set end_marker ""
2402  } elseif {$ide_name eq "quartus"} {
2403  set command "quartus_sh"
2404  # A space after the before_tcl_script is important
2405  set before_tcl_script " -t "
2406  set after_tcl_script " "
2407  set end_marker ""
2408  } elseif {$ide_name eq "libero"} {
2409  #I think we need quotes for libero, not sure...
2410 
2411  set command "libero"
2412  set before_tcl_script "SCRIPT:"
2413  set after_tcl_script " SCRIPT_ARGS:\""
2414  set end_marker "\""
2415  } elseif {$ide_name eq "diamond"} {
2416  set command "diamondc"
2417  set before_tcl_script " "
2418  set after_tcl_script " "
2419  set end_marker ""
2420  } elseif {$ide_name eq "vitis_classic"} {
2421  set command "xsct"
2422  # A space after the before_tcl_script is important
2423  set before_tcl_script ""
2424  set after_tcl_script " "
2425  set end_marker ""
2426  } elseif {$ide_name eq "ghdl"} {
2427  set command "ghdl"
2428  set before_tcl_script " "
2429  set after_tcl_script " "
2430  set end_marker ""
2431  } else {
2432  Msg Error "IDE: $ide_name not known."
2433  }
2434 
2435  return [list $command $before_tcl_script $after_tcl_script $end_marker]
2436 }
2437 
2438 ## Get the IDE (Vivado,Quartus,PlanAhead,Libero) version from the conf file she-bang
2439 #
2440 # @param[in] conf_file The hog.conf file
2441 proc GetIDEFromConf {conf_file} {
2442  set f [open $conf_file "r"]
2443  set line [gets $f]
2444  close $f
2445  if {[regexp -all {^\# *(\w*) *(vitis_classic)? *(\d+\.\d+(?:\.\d+)?(?:\.\d+)?)?(_.*)? *$} $line dummy ide vitisflag version patch]} {
2446  if {[info exists vitisflag] && $vitisflag != ""} {
2447  set ide "${ide}_${vitisflag}"
2448  }
2449 
2450  if {[info exists version] && $version != ""} {
2451  set ver $version
2452  } else {
2453  set ver 0.0.0
2454  }
2455  # what shall we do with $patch? ignored for the time being
2456  set ret [list $ide $ver]
2457  } else {
2458  Msg CriticalWarning "The first line of hog.conf should be \#<IDE name> <version>, \
2459  where <IDE name>. is quartus, vivado, planahead, libero, diamond or ghdl, \
2460  and <version> the tool version, e.g. \#vivado 2020.2. Will assume vivado."
2461  set ret [list "vivado" "0.0.0"]
2462  }
2463 
2464  return $ret
2465 }
2466 
2467 # @brief Returns the name of the running IDE
2468 proc GetIDEName {} {
2469  if {[IsISE]} {
2470  return "ISE/PlanAhead"
2471  } elseif {[IsVivado]} {
2472  return "Vivado"
2473  } elseif {[IsQuartus]} {
2474  return "Quartus"
2475  } elseif {[IsLibero]} {
2476  return "Libero"
2477  } elseif {[IsDiamond]} {
2478  return "Diamond"
2479  } else {
2480  return ""
2481  }
2482 }
2483 
2484 ## Returns the version of the IDE (Vivado,Quartus,PlanAhead,Libero) in use
2485 #
2486 # @return the version in string format, e.g. 2020.2
2487 #
2488 proc GetIDEVersion {} {
2489  if {[IsXilinx]} {
2490  #Vivado or planAhead
2491  regexp {\d+\.\d+(\.\d+)?} [version -short] ver
2492  # This regex will cut away anything after the numbers, useful for patched version 2020.1_AR75210
2493  } elseif {[IsQuartus]} {
2494  # Quartus
2495  global quartus
2496  regexp {[\.0-9]+} $quartus(version) ver
2497  } elseif {[IsLibero]} {
2498  # Libero
2499  set ver [get_libero_version]
2500  } elseif {[IsDiamond]} {
2501  regexp {\d+\.\d+(\.\d+)?} [sys_install version] ver
2502  } elseif {[IsVitisClassic]} {
2503  regexp {\d+\.\d+(\.\d+)?} [version] ver
2504  }
2505  return $ver
2506 }
2507 
2508 
2509 ## @brief Returns the real file linked by a soft link
2510 #
2511 # If the provided file is not a soft link, it will give a Warning and return an empty string.
2512 # If the link is broken, will give a warning but still return the linked file
2513 #
2514 # @param[in] link_file The soft link file
2515 proc GetLinkedFile {link_file} {
2516  if {[file type $link_file] eq "link"} {
2517  if {[OS] == "windows"} {
2518  #on windows we need to use readlink because Tcl is broken
2519  lassign [ExecuteRet realpath $link_file] ret msg
2520  lassign [ExecuteRet cygpath -m $msg] ret2 msg2
2521  if {$ret == 0 && $ret2 == 0} {
2522  set real_file $msg2
2523  Msg Debug "Found link file $link_file on Windows, the linked file is: $real_file"
2524  } else {
2525  Msg CriticalWarning "[file normalize $link_file] is a soft link. \
2526  Soft link are not supported on Windows and readlink.exe or cygpath.exe did not work: readlink=$ret: $msg, cygpath=$ret2: $msg2."
2527  set real_file $link_file
2528  }
2529  } else {
2530  #on linux Tcl just works
2531  set linked_file [file link $link_file]
2532  set real_file [file normalize [file dirname $link_file]/$linked_file]
2533  }
2534 
2535  if {![file exists $real_file]} {
2536  Msg Warning "$link_file is a broken link, because the linked file: $real_file does not exist."
2537  }
2538  } else {
2539  Msg Warning "$link file is not a soft link"
2540  set real_file $link_file
2541  }
2542  return $real_file
2543 }
2544 
2545 ## @brief Gets MAX number of Threads property from property.conf file in Top/$proj_name directory.
2546 #
2547 # If property is not set returns default = 1
2548 #
2549 # @param[in] proj_dir: the top folder of the project
2550 #
2551 # @return 1 if property is not set else the value of MaxThreads
2552 #
2553 proc GetMaxThreads {proj_dir} {
2554  set maxThreads 1
2555  if {[file exists $proj_dir/hog.conf]} {
2556  set properties [ReadConf [lindex [GetConfFiles $proj_dir] 0]]
2557  if {[dict exists $properties parameters]} {
2558  set propDict [dict get $properties parameters]
2559  if {[dict exists $propDict MAX_THREADS]} {
2560  set maxThreads [dict get $propDict MAX_THREADS]
2561  }
2562  }
2563  } else {
2564  Msg Warning "File $proj_dir/hog.conf not found. Max threads will be set to default value 1"
2565  }
2566  return $maxThreads
2567 }
2568 
2569 
2570 ## @brief Get a list of all modified the files matching then pattern
2571 #
2572 # @param[in] repo_path the path of the git repository
2573 # @param[in] pattern the pattern with wildcards that files should match
2574 #
2575 # @return a list of all modified files matching the pattern
2576 #
2577 proc GetModifiedFiles {{repo_path "."} {pattern "."}} {
2578  set old_path [pwd]
2579  cd $repo_path
2580  set ret [Git "ls-files --modified $pattern"]
2581  cd $old_path
2582  return $ret
2583 }
2584 
2585 # @brief Gets the command argv list and returns a list of
2586 # options and arguments
2587 # @param[in] argv The command input arguments
2588 # @param[in] parameters The command input parameters
2589 proc GetOptions {argv parameters} {
2590  # Get Options from argv
2591  set arg_list [list]
2592  set param_list [list]
2593  set option_list [list]
2594 
2595  foreach p $parameters {
2596  lappend param_list [lindex $p 0]
2597  }
2598 
2599  set index 0
2600  while {$index < [llength $argv]} {
2601  set arg [lindex $argv $index]
2602  if {[string first - $arg] == 0} {
2603  set option [string trimleft $arg "-"]
2604  incr index
2605  lappend option_list $arg
2606  if {[lsearch -regex $param_list "$option\[.arg]?"] >= 0 } {
2607  if {[lsearch -regex $param_list "$option\[.arg]"] >= 0 } {
2608  lappend option_list [lindex $argv $index]
2609  incr index
2610  }
2611  }
2612  } else {
2613  lappend arg_list $arg
2614  incr index
2615  }
2616  }
2617  Msg Debug "Argv: $argv"
2618  Msg Debug "Options: $option_list"
2619  Msg Debug "Arguments: $arg_list"
2620  return [list $option_list $arg_list]
2621 }
2622 
2623 # return [list $libraries $properties $simlibraries $constraints $srcsets $simsets $consets]
2624 ## @ brief Returns a list of 7 dictionaries: libraries, properties, constraints, and filesets for sources and simulations
2625 #
2626 # The returned dictionaries are libraries, properties, simlibraries, constraints, srcsets, simsets, consets
2627 # - libraries and simlibraries have the library name as keys and a list of filenames as values
2628 # - properties has as file names as keys and a list of properties as values
2629 # - constraints is a dictionary with a single key (sources.con) and a list of constraint files as value
2630 # - srcsets is a dictionary with a fileset name as a key (e.g. sources_1) and a list of libraries as value
2631 # - simsets is a dictionary with a simset name as a key (e.g. sim_1) and a list of libraries as value
2632 # - 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)
2633 #
2634 # Files, libraries and properties are extracted from the current project
2635 #
2636 # @param[in] project_file The project file (for Libero and Diamond)
2637 # @return A list of 7 dictionaries: libraries, properties, constraints, and filesets for sources and simulations
2638 proc GetProjectFiles {{project_file ""}} {
2639  set libraries [dict create]
2640  set simlibraries [dict create]
2641  set constraints [dict create]
2642  set properties [dict create]
2643  set consets [dict create]
2644  set srcsets [dict create]
2645  set simsets [dict create]
2646 
2647  if {[IsVivado]} {
2648  set all_filesets [get_filesets]
2649  set simulator [get_property target_simulator [current_project]]
2650  set top [get_property "top" [current_fileset]]
2651  set topfile [GetTopFile]
2652  dict lappend properties $topfile "top=$top"
2653 
2654  foreach fs $all_filesets {
2655  if {$fs == "utils_1"} {
2656  # Skipping utility fileset
2657  continue
2658  }
2659 
2660  set all_files [get_files -quiet -of_objects [get_filesets $fs]]
2661  set fs_type [get_property FILESET_TYPE [get_filesets $fs]]
2662 
2663  if {$fs_type == "BlockSrcs"} {
2664  # Vivado creates for each ip a blockset... Let's redirect to sources_1
2665  set dict_fs "sources_1"
2666  } else {
2667  set dict_fs $fs
2668  }
2669  foreach f $all_files {
2670  # Ignore files that are part of the vivado/planahead project but would not be reflected
2671  # in list files (e.g. generated products from ip cores)
2672  set ignore 0
2673  # Generated files point to a parent composite file;
2674  # planahead does not have an IS_GENERATED property
2675  if {[IsInList "IS_GENERATED" [list_property [GetFile $f $fs]]]} {
2676  if {[lindex [get_property IS_GENERATED [GetFile $f $fs]] 0] != 0} {
2677  set ignore 1
2678  }
2679  }
2680 
2681  if {[get_property FILE_TYPE [GetFile $f $fs]] == "Configuration Files"} {
2682  set ignore 1
2683  }
2684 
2685 
2686  if {[IsInList "CORE_CONTAINER" [list_property [GetFile $f $fs]]]} {
2687  if {[get_property CORE_CONTAINER [GetFile $f $fs]] != ""} {
2688  if {[file extension $f] == ".xcix"} {
2689  set f [get_property CORE_CONTAINER [GetFile $f $fs]]
2690  } else {
2691  set ignore 1
2692  }
2693  }
2694  }
2695 
2696  if {[IsInList "SCOPED_TO_REF" [list_property [GetFile $f $fs]]]} {
2697  if {[get_property SCOPED_TO_REF [GetFile $f $fs]] != ""} {
2698  dict lappend properties $f "scoped_to_ref=[get_property SCOPED_TO_REF [GetFile $f $fs]]"
2699  }
2700  }
2701 
2702  if {[IsInList "SCOPED_TO_CELLS" [list_property [GetFile $f $fs]]]} {
2703  if {[get_property SCOPED_TO_CELLS [GetFile $f $fs]] != ""} {
2704  dict lappend properties $f "scoped_to_cells=[get_property SCOPED_TO_CELLS [GetFile $f $fs]]"
2705  }
2706  }
2707 
2708  if {[IsInList "PARENT_COMPOSITE_FILE" [list_property [GetFile $f $fs]]]} {
2709  set ignore 1
2710  }
2711 
2712  # Ignore nocattrs.dat for Versal
2713  if {[file tail $f] == "nocattrs.dat"} {
2714  set ignore 1
2715  }
2716 
2717  if {!$ignore} {
2718  if {[file extension $f] != ".coe"} {
2719  set f [file normalize $f]
2720  }
2721  lappend files $f
2722  set type [get_property FILE_TYPE [GetFile $f $fs]]
2723  # Added a -quiet because some files (.v, .sv) don't have a library
2724  set lib [get_property -quiet LIBRARY [GetFile $f $fs]]
2725 
2726  # Type can be complex like VHDL 2008, in that case we want the second part to be a property
2727  Msg Debug "File $f Extension [file extension $f] Type [lindex $type 0]"
2728 
2729  if {[string equal [lindex $type 0] "VHDL"] && [llength $type] == 1} {
2730  set prop "93"
2731  } elseif {[string equal [lindex $type 0] "Block"] && [string equal [lindex $type 1] "Designs"]} {
2732  set type "IP"
2733  set prop ""
2734  } elseif {[string equal $type "SystemVerilog"] && [file extension $f] != ".sv"} {
2735  set prop "SystemVerilog"
2736  } elseif {[string equal [lindex $type 0] "XDC"] && [file extension $f] != ".xdc"} {
2737  set prop "XDC"
2738  } elseif {[string equal $type "Verilog Header"] && [file extension $f] != ".vh" && [file extension $f] != ".svh"} {
2739  set prop "verilog_header"
2740  } elseif {[string equal $type "Verilog Template"] && [file extension $f] == ".v" && [file extension $f] != ".sv"} {
2741  set prop "verilog_template"
2742  } else {
2743  set type [lindex $type 0]
2744  set prop ""
2745  }
2746  #If type is "VHDL 2008" we will keep only VHDL
2747  if {![string equal $prop ""]} {
2748  dict lappend properties $f $prop
2749  }
2750  # check where the file is used and add it to prop
2751  if {[string equal $fs_type "SimulationSrcs"]} {
2752  # Simulation sources
2753  if {[string equal $type "VHDL"]} {
2754  set library "${lib}.sim"
2755  } else {
2756  set library "others.sim"
2757  }
2758 
2759  if {[IsInList $library [DictGet $simsets $dict_fs]] == 0} {
2760  dict lappend simsets $dict_fs $library
2761  }
2762 
2763  dict lappend simlibraries $library $f
2764  } elseif {[string equal $type "VHDL"]} {
2765  # VHDL files (both 2008 and 93)
2766  if {[IsInList "${lib}.src" [DictGet $srcsets $dict_fs]] == 0} {
2767  dict lappend srcsets $dict_fs "${lib}.src"
2768  }
2769  dict lappend libraries "${lib}.src" $f
2770  } elseif {[string first "IP" $type] != -1} {
2771  # IPs
2772  if {[IsInList "ips.src" [DictGet $srcsets $dict_fs]] == 0} {
2773  dict lappend srcsets $dict_fs "ips.src"
2774  }
2775  dict lappend libraries "ips.src" $f
2776  Msg Debug "Appending $f to ips.src"
2777  } elseif {[string equal $fs_type "Constrs"]} {
2778  # Constraints
2779  if {[IsInList "sources.con" [DictGet $consets $dict_fs]] == 0} {
2780  dict lappend consets $dict_fs "sources.con"
2781  }
2782  dict lappend constraints "sources.con" $f
2783  } else {
2784  # Verilog and other files
2785  if {[IsInList "others.src" [DictGet $srcsets $dict_fs]] == 0} {
2786  dict lappend srcsets $dict_fs "others.src"
2787  }
2788  dict lappend libraries "others.src" $f
2789  Msg Debug "Appending $f to others.src"
2790  }
2791 
2792  if {[lindex [get_property -quiet used_in_synthesis [GetFile $f $fs]] 0] == 0} {
2793  dict lappend properties $f "nosynth"
2794  }
2795  if {[lindex [get_property -quiet used_in_implementation [GetFile $f $fs]] 0] == 0} {
2796  dict lappend properties $f "noimpl"
2797  }
2798  if {[lindex [get_property -quiet used_in_simulation [GetFile $f $fs]] 0] == 0} {
2799  dict lappend properties $f "nosim"
2800  }
2801  if {[lindex [get_property -quiet IS_MANAGED [GetFile $f $fs]] 0] == 0 && [file extension $f] != ".xcix"} {
2802  dict lappend properties $f "locked"
2803  }
2804  }
2805  }
2806  }
2807 
2808  dict lappend properties "Simulator" [get_property target_simulator [current_project]]
2809  } elseif {[IsLibero] || [IsSynplify]} {
2810  # Open the project file
2811  set file [open $project_file r]
2812  set in_file_manager 0
2813  set top ""
2814  while {[gets $file line] >= 0} {
2815  # Detect the ActiveRoot (Top) module
2816  if {[regexp {^KEY ActiveRoot \"([^\"]+)\"} $line -> value]} {
2817  set top [string range $value 0 [expr {[string first "::" $value] - 1}]]
2818  }
2819 
2820  # Detect the start of the FileManager section
2821  if {[regexp {^LIST FileManager} $line]} {
2822  set in_file_manager 1
2823  continue
2824  }
2825 
2826  # Detect the end of the FileManager section
2827  if {$in_file_manager && [regexp {^ENDLIST} $line]} {
2828  break
2829  }
2830 
2831  # Extract file paths from the VALUE entries
2832  if {$in_file_manager && [regexp {^VALUE \"([^\"]+)} $line -> value]} {
2833  # lappend source_files [remove_after_comma $filepath]
2834  # set file_path ""
2835  lassign [split $value ,] file_path file_type
2836  # Extract file properties
2837  set parent_file ""
2838  set library "others"
2839  while {[gets $file line] >= 0} {
2840  if {$line == "ENDFILE"} {
2841  break
2842  }
2843  regexp {^LIBRARY=\"([^\"]+)} $line -> library
2844  regexp {^PARENT=\"([^\"]+)} $line -> parent_file
2845  }
2846  Msg Debug "Found file ${file_path} in project.."
2847  if {$parent_file == ""} {
2848  if {$file_type == "hdl"} {
2849  # VHDL files (both 2008 and 93)
2850  if {[IsInList "${library}.src" [DictGet $srcsets "sources_1"]] == 0} {
2851  dict lappend srcsets "sources_1" "${library}.src"
2852  }
2853  dict lappend libraries "${library}.src" $file_path
2854  # Check if file is top_module in project
2855  Msg Debug "File $file_path module [GetModuleName $file_path]"
2856 
2857  if {[GetModuleName $file_path] == [string tolower $top] && $top != ""} {
2858  Msg Debug "Found top module $top in $file_path"
2859  dict lappend properties $file_path "top=$top"
2860  }
2861  } elseif {$file_type == "tb_hdl"} {
2862  if {[IsInList "${library}.sim" [DictGet $simsets "sim_1"]] == 0} {
2863  dict lappend simsets "sim_1" "${library}.sim"
2864  }
2865  dict lappend simlibraries "${library}.sim" $file_path
2866  } elseif {$file_type == "io_pdc" || $file_type == "sdc"} {
2867  if {[IsInList "sources.con" [DictGet $consets "constrs_1"]] == 0} {
2868  dict lappend consets "constrs_1" "sources.con"
2869  }
2870  dict lappend constraints "sources.con" $file_path
2871  }
2872  }
2873  }
2874  }
2875  } elseif {[IsDiamond]} {
2876  # Open the Diamond XML project file content
2877  set fileData [read [open $project_file]]
2878 
2879  set project_path [file dirname $project_file]
2880 
2881  # Remove XML declaration
2882  regsub {<\?xml.*\?>} $fileData "" fileData
2883 
2884  # Extract the Implementation block
2885  regexp {<Implementation.*?>(.*)</Implementation>} $fileData -> implementationContent
2886 
2887  # Extract each Source block one by one
2888  set sources {}
2889  set sourceRegex {<Source name="([^"]*?)" type="([^"]*?)" type_short="([^"]*?)".*?>(.*?)</Source>}
2890 
2891  set optionsRegex {<Options(.*?)\/>}
2892  regexp $optionsRegex $implementationContent -> prj_options
2893  foreach option $prj_options {
2894  if {[regexp {^top=\"([^\"]+)\"} $option match result]} {
2895  set top $result
2896  }
2897  }
2898 
2899  while {[regexp $sourceRegex $implementationContent match name type type_short optionsContent]} {
2900  Msg Debug "Found file ${name} in project..."
2901  set file_path [file normalize $project_path/$name]
2902  # Extract the Options attributes
2903  set optionsRegex {<Options(.*?)\/>}
2904  regexp $optionsRegex $optionsContent -> options
2905  set library "others"
2906  set isSV 0
2907  foreach option $options {
2908  if {[string first "System Verilog" $option]} {
2909  set isSV 1
2910  }
2911  if {[regexp {^lib=\"([^\"]+)\"} $option match1 result]} {
2912  set library $result
2913  }
2914  }
2915  set ext ".src"
2916  if {[regexp {syn_sim="([^"]*?)"} $match match_sim simonly]} {
2917  set ext ".sim"
2918  }
2919 
2920  # Append VHDL files
2921  if {$type_short == "VHDL" || $type_short == "Verilog" || $type_short == "IPX"} {
2922  if {$ext == ".src"} {
2923  if {[IsInList "${library}${ext}" [DictGet $srcsets "sources_1"]] == 0} {
2924  dict lappend srcsets "sources_1" "${library}${ext}"
2925  }
2926  dict lappend libraries "${library}${ext}" $file_path
2927  } elseif {$ext == ".sim"} {
2928  if {[IsInList "${library}.sim" [DictGet $simsets "sim_1"]] == 0} {
2929  dict lappend simsets "sim_1" "${library}.sim"
2930  }
2931  dict lappend simlibraries "${library}.sim" $file_path
2932  }
2933  # Check if file is top_module in project
2934  Msg Debug "File $file_path module [GetModuleName $file_path]"
2935 
2936  if {[GetModuleName $file_path] == $top && $top != ""} {
2937  Msg Debug "Found top module $top in $file_path"
2938  dict lappend properties $file_path "top=$top"
2939  }
2940  } elseif {$type_short == "SDC"} {
2941  if {[IsInList "sources.con" [DictGet $consets "constrs_1"]] == 0} {
2942  dict lappend consets "constrs_1" "sources.con"
2943  }
2944  dict lappend constraints "sources.con" $file_path
2945  }
2946 
2947  # Remove the processed Source block from the implementation content
2948  regsub -- $match $implementationContent "" implementationContent
2949  }
2950  }
2951  return [list $libraries $properties $simlibraries $constraints $srcsets $simsets $consets]
2952 }
2953 
2954 #"
2955 ## Get the Project flavour
2956 #
2957 # @param[in] proj_name The project name
2958 proc GetProjectFlavour {proj_name} {
2959  # Calculating flavour if any
2960  set flavour [string map {. ""} [file extension $proj_name]]
2961  if {$flavour != ""} {
2962  if {[string is integer $flavour]} {
2963  Msg Info "Project $proj_name has flavour = $flavour, the generic variable FLAVOUR will be set to $flavour"
2964  } else {
2965  Msg Warning "Project name has a unexpected non numeric extension, flavour will be set to -1"
2966  set flavour -1
2967  }
2968  } else {
2969  set flavour -1
2970  }
2971  return $flavour
2972 }
2973 
2974 ## Get the project version
2975 #
2976 # @param[in] proj_dir: The top folder of the project of which all the version must be calculated
2977 # @param[in] repo_path: The top folder of the repository
2978 # @param[in] ext_path: path for external libraries
2979 # @param[in] sim: if enabled, check the version also for the simulation files
2980 #
2981 # @return returns the project version
2982 proc GetProjectVersion {proj_dir repo_path {ext_path ""} {sim 0}} {
2983  if {![file exists $proj_dir]} {
2984  Msg CriticalWarning "$proj_dir not found"
2985  return -1
2986  }
2987  set old_dir [pwd]
2988  cd $proj_dir
2989 
2990  #The latest version the repository
2991  set v_last [ExtractVersionFromTag [Git {describe --abbrev=0 --match "v*"}]]
2992  lassign [GetRepoVersions $proj_dir $repo_path $ext_path $sim] sha ver
2993  if {$sha == 0} {
2994  Msg Warning "Repository is not clean"
2995  cd $old_dir
2996  return -1
2997  }
2998 
2999  #The project version
3000  set v_proj [ExtractVersionFromTag v[HexVersionToString $ver]]
3001  set comp [CompareVersions $v_proj $v_last]
3002  Msg Debug "Project version $v_proj, latest tag $v_last"
3003  if {$comp == 1} {
3004  Msg Info "The specified project was modified since official version."
3005  set ret 0
3006  } else {
3007  set ret v[HexVersionToString $ver]
3008  }
3009 
3010  if {$comp == 0} {
3011  Msg Info "The specified project was modified in the latest official version $ret"
3012  } elseif {$comp == -1} {
3013  Msg Info "The specified project was modified in a past official version $ret"
3014  }
3015 
3016  cd $old_dir
3017  return $ret
3018 }
3019 
3020 ## Get the versions for all libraries, submodules, etc. for a given project
3021 #
3022 # @param[in] proj_dir: The project directory containing the conf file or the the tcl file
3023 # @param[in] repo_path: top path of the repository
3024 # @param[in] ext_path: path for external libraries
3025 # @param[in] sim: if enabled, check the version also for the simulation files
3026 #
3027 # @return a list containing all the versions: global, top (hog.conf, pre and post tcl scripts, etc.), constraints,
3028 # libraries, submodules, external, ipbus xml, user ip repos
3029 proc GetRepoVersions {proj_dir repo_path {ext_path ""} {sim 0}} {
3030  if {[catch {package require cmdline} ERROR]} {
3031  puts "$ERROR\n If you are running this script on tclsh, you can fix this by installing 'tcllib'"
3032  return 1
3033  }
3034 
3035  set old_path [pwd]
3036  set conf_files [GetConfFiles $proj_dir]
3037 
3038  # This will be the list of all the SHAs of this project, the most recent will be picked up as GLOBAL SHA
3039  set SHAs ""
3040  set versions ""
3041 
3042  # Hog submodule
3043  cd $repo_path
3044 
3045  # Append the SHA in which Hog submodule was changed, not the submodule SHA
3046  lappend SHAs [GetSHA {Hog}]
3047  lappend versions [GetVerFromSHA $SHAs $repo_path]
3048 
3049  cd "$repo_path/Hog"
3050  if {[Git {status --untracked-files=no --porcelain}] eq ""} {
3051  Msg Info "Hog submodule [pwd] clean."
3052  lassign [GetVer ./] hog_ver hog_hash
3053  } else {
3054  Msg CriticalWarning "Hog submodule [pwd] not clean, commit hash will be set to 0."
3055  set hog_hash "0000000"
3056  set hog_ver "00000000"
3057  }
3058 
3059  cd $proj_dir
3060 
3061  if {[Git {status --untracked-files=no --porcelain}] eq ""} {
3062  Msg Info "Git working directory [pwd] clean."
3063  set clean 1
3064  } else {
3065  Msg CriticalWarning "Git working directory [pwd] not clean, commit hash, and version will be set to 0."
3066  set clean 0
3067  }
3068 
3069  # Top project directory
3070  lassign [GetVer [join $conf_files]] top_ver top_hash
3071  lappend SHAs $top_hash
3072  lappend versions $top_ver
3073 
3074  # Read list files
3075  set libs ""
3076  set vers ""
3077  set hashes ""
3078  # Specify sha_mode 1 for GetHogFiles to get all the files, including the list-files themselves
3079  lassign [GetHogFiles -list_files "*.src" -sha_mode "./list/" $repo_path] src_files dummy
3080  dict for {f files} $src_files {
3081  # library names have a .src extension in values returned by GetHogFiles
3082  set name [file rootname [file tail $f]]
3083  if {[file ext $f] == ".oth"} {
3084  set name "OTHERS"
3085  }
3086  lassign [GetVer $files] ver hash
3087  # Msg Info "Found source list file $f, version: $ver commit SHA: $hash"
3088  lappend libs $name
3089  lappend versions $ver
3090  lappend vers $ver
3091  lappend hashes $hash
3092  lappend SHAs $hash
3093  }
3094 
3095  # Read constraint list files
3096  set cons_hashes ""
3097  # Specify sha_mode 1 for GetHogFiles to get all the files, including the list-files themselves
3098  lassign [GetHogFiles -list_files "*.con" -sha_mode "./list/" $repo_path] cons_files dummy
3099  dict for {f files} $cons_files {
3100  #library names have a .con extension in values returned by GetHogFiles
3101  set name [file rootname [file tail $f]]
3102  lassign [GetVer $files] ver hash
3103  #Msg Info "Found constraint list file $f, version: $ver commit SHA: $hash"
3104  if {$hash eq ""} {
3105  Msg CriticalWarning "Constraints file $f not found in Git."
3106  }
3107  lappend cons_hashes $hash
3108  lappend SHAs $hash
3109  lappend versions $ver
3110  }
3111 
3112  # Read simulation list files
3113  if {$sim == 1} {
3114  set sim_hashes ""
3115  # Specify sha_mode 1 for GetHogFiles to get all the files, including the list-files themselves
3116  lassign [GetHogFiles -list_files "*.sim" -sha_mode "./list/" $repo_path] sim_files dummy
3117  dict for {f files} $sim_files {
3118  #library names have a .sim extension in values returned by GetHogFiles
3119  set name [file rootname [file tail $f]]
3120  lassign [GetVer $files] ver hash
3121  #Msg Info "Found simulation list file $f, version: $ver commit SHA: $hash"
3122  lappend sim_hashes $hash
3123  lappend SHAs $hash
3124  lappend versions $ver
3125  }
3126  }
3127 
3128 
3129  #Of all the constraints we get the most recent
3130  if {"{}" eq $cons_hashes} {
3131  #" Fake comment for Visual Code Studio
3132  Msg CriticalWarning "No hashes found for constraints files (not in git)"
3133  set cons_hash ""
3134  } else {
3135  set cons_hash [string tolower [Git "log --format=%h -1 $cons_hashes"]]
3136  }
3137  set cons_ver [GetVerFromSHA $cons_hash $repo_path]
3138  #Msg Info "Among all the constraint list files, if more than one, the most recent version was chosen: $cons_ver commit SHA: $cons_hash"
3139 
3140  # Read external library files
3141  set ext_hashes ""
3142  set ext_files [glob -nocomplain "./list/*.ext"]
3143  set ext_names ""
3144 
3145  foreach f $ext_files {
3146  set name [file rootname [file tail $f]]
3147  set hash [GetSHA $f]
3148  #Msg Info "Found source file $f, commit SHA: $hash"
3149  lappend ext_names $name
3150  lappend ext_hashes $hash
3151  lappend SHAs $hash
3152  set ext_ver [GetVerFromSHA $hash $repo_path]
3153  lappend versions $ext_ver
3154 
3155  set fp [open $f r]
3156  set file_data [read $fp]
3157  close $fp
3158  set data [split $file_data "\n"]
3159  #Msg Info "Checking checksums of external library files in $f"
3160  foreach line $data {
3161  if {![regexp {^ *$} $line] & ![regexp {^ *\#} $line]} {
3162  #Exclude empty lines and comments
3163  set file_and_prop [regexp -all -inline {\S+} $line]
3164  set hdlfile [lindex $file_and_prop 0]
3165  set hdlfile $ext_path/$hdlfile
3166  if {[file exists $hdlfile]} {
3167  set hash [lindex $file_and_prop 1]
3168  set current_hash [Md5Sum $hdlfile]
3169  if {[string first $hash $current_hash] == -1} {
3170  Msg CriticalWarning "File $hdlfile has a wrong hash. Current checksum: $current_hash, expected: $hash"
3171  }
3172  }
3173  }
3174  }
3175  }
3176 
3177  # Ipbus XML
3178  if {[llength [glob -nocomplain ./list/*.ipb]] > 0} {
3179  #Msg Info "Found IPbus XML list file, evaluating version and SHA of listed files..."
3180  lassign [GetHogFiles -list_files "*.ipb" -sha_mode "./list/" $repo_path] xml_files dummy
3181  lassign [GetVer [dict get $xml_files "xml.ipb"]] xml_ver xml_hash
3182  lappend SHAs $xml_hash
3183  lappend versions $xml_ver
3184 
3185  #Msg Info "Found IPbus XML SHA: $xml_hash and version: $xml_ver."
3186  } else {
3187  Msg Info "This project does not use IPbus XMLs"
3188  set xml_ver ""
3189  set xml_hash ""
3190  }
3191 
3192  set user_ip_repos ""
3193  set user_ip_repo_hashes ""
3194  set user_ip_repo_vers ""
3195  # User IP Repository (Vivado only, hog.conf only)
3196  if {[file exists [lindex $conf_files 0]]} {
3197  set PROPERTIES [ReadConf [lindex $conf_files 0]]
3198  if {[dict exists $PROPERTIES main]} {
3199  set main [dict get $PROPERTIES main]
3200  dict for {p v} $main {
3201  if {[string tolower $p] == "ip_repo_paths"} {
3202  foreach repo $v {
3203  if {[file isdirectory "$repo_path/$repo"]} {
3204  set repo_file_list [glob -nocomplain "$repo_path/$repo/*"]
3205  if {[llength $repo_file_list] == 0} {
3206  Msg Warning "IP_REPO_PATHS property set to $repo in hog.conf but directory is empty."
3207  } else {
3208  lappend user_ip_repos "$repo_path/$repo"
3209  }
3210  }
3211  }
3212  }
3213  }
3214  }
3215 
3216  # For each defined IP repository get hash and version if directory exists and not empty
3217  foreach repo $user_ip_repos {
3218  if {[file isdirectory $repo]} {
3219  set repo_file_list [glob -nocomplain "$repo/*"]
3220  if {[llength $repo_file_list] != 0} {
3221  lassign [GetVer $repo] ver sha
3222  lappend user_ip_repo_hashes $sha
3223  lappend user_ip_repo_vers $ver
3224  lappend versions $ver
3225  } else {
3226  Msg Warning "IP_REPO_PATHS property set to $repo in hog.conf but directory is empty."
3227  }
3228  } else {
3229  Msg Warning "IP_REPO_PATHS property set to $repo in hog.conf but directory does not exist."
3230  }
3231  }
3232  }
3233 
3234 
3235  #The global SHA and ver is the most recent among everything
3236  if {$clean == 1} {
3237  set found 0
3238  while {$found == 0} {
3239  set global_commit [Git "log --format=%h -1 --abbrev=7 $SHAs"]
3240  foreach sha $SHAs {
3241  set found 1
3242  if {![IsCommitAncestor $sha $global_commit]} {
3243  set common_child [FindCommonGitChild $global_commit $sha]
3244  if {$common_child == 0} {
3245  Msg CriticalWarning "The commit $sha is not an ancestor of the global commit $global_commit, which is OK. \
3246  But $sha and $global_commit do not have any common child, which is NOT OK. \
3247  This is probably do to a REBASE that is forbidden in Hog methodology as it changes git history. \
3248  Hog cannot guarantee the accuracy of the SHAs. \
3249  A way to fix this is to make a commit that touches all the projects in the repositories (e.g. change the Hog version), \
3250  but please do not rebase in the official branches in the future."
3251  } else {
3252  Msg Info "The commit $sha is not an ancestor of the global commit $global_commit, adding the first common child $common_child instead..."
3253  lappend SHAs $common_child
3254  }
3255  set found 0
3256 
3257  break
3258  }
3259  }
3260  }
3261  set global_version [FindNewestVersion $versions]
3262  } else {
3263  set global_commit "0000000"
3264  set global_version "00000000"
3265  }
3266 
3267  cd $old_path
3268 
3269  set top_hash [format %+07s $top_hash]
3270  set cons_hash [format %+07s $cons_hash]
3271  return [list $global_commit $global_version \
3272  $hog_hash $hog_ver $top_hash $top_ver \
3273  $libs $hashes $vers $cons_ver $cons_hash \
3274  $ext_names $ext_hashes $xml_hash $xml_ver \
3275  $user_ip_repos $user_ip_repo_hashes $user_ip_repo_vers]
3276 }
3277 
3278 ## @brief Get git SHA of a subset of list file
3279 #
3280 # @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
3281 #
3282 # @return the value of the desired SHA
3283 #
3284 proc GetSHA {{path ""}} {
3285  if {$path == ""} {
3286  lassign [GitRet {log --format=%h --abbrev=7 -1}] status result
3287  if {$status == 0} {
3288  return [string tolower $result]
3289  } else {
3290  Msg Error "Something went wrong while finding the latest SHA. Does the repository have a commit?"
3291  exit 1
3292  }
3293  }
3294 
3295  # Get repository top level
3296  set repo_path [lindex [Git {rev-parse --show-toplevel}] 0]
3297  set paths {}
3298  # Retrieve the list of submodules in the repository
3299  foreach f $path {
3300  set file_in_module 0
3301  if {[file exists $repo_path/.gitmodules]} {
3302  lassign [GitRet "config --file $repo_path/.gitmodules --get-regexp path"] status result
3303  if {$status == 0} {
3304  set submodules [split $result "\n"]
3305  } else {
3306  set submodules ""
3307  Msg Warning "Something went wrong while trying to find submodules: $result"
3308  }
3309 
3310  foreach mod $submodules {
3311  set module [lindex $mod 1]
3312  if {[string first "$repo_path/$module" $f] == 0} {
3313  # File is in a submodule. Append
3314  set file_in_module 1
3315  lappend paths "$repo_path/$module"
3316  break
3317  }
3318  }
3319  }
3320  if {$file_in_module == 0} {
3321  #File is not in a submodule
3322  lappend paths $f
3323  }
3324  }
3325 
3326  lassign [GitRet {log --format=%h --abbrev=7 -1} $paths] status result
3327  if {$status == 0} {
3328  return [string tolower $result]
3329  } else {
3330  Msg Error "Something went wrong while finding the latest SHA. Does the repository have a commit?"
3331  exit 1
3332  }
3333  return [string tolower $result]
3334 }
3335 
3336 ## @brief Returns the list of Simulators supported by Vivado
3337 proc GetSimulators {} {
3338  set SIMULATORS [list "modelsim" "questa" "riviera" "activehdl" "ies" "vcs"]
3339  return $SIMULATORS
3340 }
3341 
3342 ## @brief Return the path to the active top file
3343 proc GetTopFile {} {
3344  if {[IsVivado]} {
3345  set compile_order_prop [get_property source_mgmt_mode [current_project]]
3346  if {$compile_order_prop ne "All"} {
3347  Msg CriticalWarning "Compile order is not set to automatic, setting it now..."
3348  set_property source_mgmt_mode All [current_project]
3349  update_compile_order -fileset sources_1
3350  }
3351  return [lindex [get_files -quiet -compile_order sources -used_in synthesis -filter {FILE_TYPE =~ "VHDL*" || FILE_TYPE =~ "*Verilog*" }] end]
3352  } elseif {[IsISE]} {
3353  debug::design_graph_mgr -create [current_fileset]
3354  debug::design_graph -add_fileset [current_fileset]
3355  debug::design_graph -update_all
3356  return [lindex [debug::design_graph -get_compile_order] end]
3357  } else {
3358  Msg Error "GetTopFile not yet implemented for this IDE"
3359  }
3360 }
3361 
3362 ## @brief Return the name of the active top module
3363 proc GetTopModule {} {
3364  if {[IsXilinx]} {
3365  return [get_property top [current_fileset]]
3366  } else {
3367  Msg Error "GetTopModule not yet implemented for this IDE"
3368  }
3369 }
3370 
3371 ## @brief Get git version and commit hash of a subset of files
3372 #
3373 # @param[in] path list file or path containing the subset of files whose latest commit hash will be returned
3374 #
3375 # @return a list: the git SHA, the version in hex format
3376 #
3377 proc GetVer {path {force_develop 0}} {
3378  set SHA [GetSHA $path]
3379  #oldest tag containing SHA
3380  if {$SHA eq ""} {
3381  Msg CriticalWarning "Empty SHA found for ${path}. Commit to Git to resolve this warning."
3382  }
3383  set old_path [pwd]
3384  set p [lindex $path 0]
3385  if {[file isdirectory $p]} {
3386  cd $p
3387  } else {
3388  cd [file dirname $p]
3389  }
3390  set repo_path [Git {rev-parse --show-toplevel}]
3391  cd $old_path
3392 
3393  return [list [GetVerFromSHA $SHA $repo_path $force_develop] $SHA]
3394 }
3395 
3396 ## @brief Get git version and commit hash of a specific commit give the SHA
3397 #
3398 # @param[in] SHA the git SHA of the commit
3399 # @param[in] repo_path the path of the repository, this is used to open the Top/repo.conf file
3400 # @param[in] force_develop Force a tag for the develop branch (increase m)
3401 #
3402 # @return a list: the git SHA, the version in hex format
3403 #
3404 proc GetVerFromSHA {SHA repo_path {force_develop 0}} {
3405  if {$SHA eq ""} {
3406  Msg CriticalWarning "Empty SHA found"
3407  set ver "v0.0.0"
3408  } else {
3409  lassign [GitRet "tag --sort=creatordate --contain $SHA -l v*.*.* -l b*v*.*.*"] status result
3410 
3411  if {$status == 0} {
3412  if {[regexp {^ *$} $result]} {
3413  # We do not want the most recent tag, we want the biggest value
3414  lassign [GitRet "log --oneline --pretty=\"%d\""] status2 tag_list
3415  #Msg Status "List of all tags including $SHA: $tag_list."
3416  #cleanup the list and get only the tags
3417  set pattern {tag: v\d+\.\d+\.\d+}
3418  set real_tag_list {}
3419  foreach x $tag_list {
3420  set x_untrimmed [regexp -all -inline $pattern $x]
3421  regsub "tag: " $x_untrimmed "" x_trimmed
3422  set tt [lindex $x_trimmed 0]
3423  if {![string equal $tt ""]} {
3424  lappend real_tag_list $tt
3425  #puts "<$tt>"
3426  }
3427  }
3428  Msg Debug "Cleaned up list: $real_tag_list."
3429  # Sort the tags in version order
3430  set sorted_tags [lsort -decreasing -command CompareVersions $real_tag_list]
3431 
3432  Msg Debug "Sorted Tag list: $sorted_tags"
3433  # Select the newest tag in terms of number, not time
3434  set tag [lindex $sorted_tags 0]
3435 
3436  # Msg Debug "Chosen Tag $tag"
3437  set pattern {v\d+\.\d+\.\d+}
3438  if {![regexp $pattern $tag]} {
3439  Msg CriticalWarning "No Hog version tags found in this repository."
3440  set ver v0.0.0
3441  } else {
3442  lassign [ExtractVersionFromTag $tag] M m p mr
3443  # Open repo.conf and check prefixes
3444  set repo_conf $repo_path/Top/repo.conf
3445 
3446  # Check if the develop/master scheme is used and where is the merge directed to
3447  # Default values
3448  set hotfix_prefix "hotfix/"
3449  set minor_prefix "minor_version/"
3450  set major_prefix "major_version/"
3451  set is_hotfix 0
3452  set enable_develop_branch $force_develop
3453 
3454  set branch_name [Git {rev-parse --abbrev-ref HEAD}]
3455 
3456  if {[file exists $repo_conf]} {
3457  set PROPERTIES [ReadConf $repo_conf]
3458  # [main] section
3459  if {[dict exists $PROPERTIES main]} {
3460  set mainDict [dict get $PROPERTIES main]
3461 
3462  # ENABLE_DEVELOP_ BRANCH property
3463  if {[dict exists $mainDict ENABLE_DEVELOP_BRANCH]} {
3464  set enable_develop_branch [dict get $mainDict ENABLE_DEVELOP_BRANCH]
3465  }
3466  # More properties in [main] here ...
3467  }
3468 
3469  # [prefixes] section
3470  if {[dict exists $PROPERTIES prefixes]} {
3471  set prefixDict [dict get $PROPERTIES prefixes]
3472 
3473  if {[dict exists $prefixDict HOTFIX]} {
3474  set hotfix_prefix [dict get $prefixDict HOTFIX]
3475  }
3476  if {[dict exists $prefixDict MINOR_VERSION]} {
3477  set minor_prefix [dict get $prefixDict MINOR_VERSION]
3478  }
3479  if {[dict exists $prefixDict MAJOR_VERSION]} {
3480  set major_prefix [dict get $prefixDict MAJOR_VERSION]
3481  }
3482  # More properties in [prefixes] here ...
3483  }
3484  }
3485 
3486  if {$enable_develop_branch == 1} {
3487  if {[string match "$hotfix_prefix*" $branch_name]} {
3488  set is_hotfix 1
3489  }
3490  }
3491 
3492  if {[string match "$major_prefix*" $branch_name]} {
3493  # If major prefix is used, we increase M regardless of anything else
3494  set version_level major
3495  } elseif {[string match "$minor_prefix*" $branch_name] || ($enable_develop_branch == 1 && $is_hotfix == 0)} {
3496  # 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
3497  set version_level minor
3498  } else {
3499  # 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
3500  set version_level patch
3501  }
3502 
3503  if {$M == -1} {
3504  Msg CriticalWarning "Tag $tag does not contain a Hog compatible version in this repository."
3505  exit
3506  #set ver v0.0.0
3507  } elseif {$mr == 0} {
3508  switch $version_level {
3509  minor {
3510  incr m
3511  set p 0
3512  }
3513  major {
3514  incr M
3515  set m 0
3516  set p 0
3517  }
3518  default {
3519  incr p
3520  }
3521  }
3522  } else {
3523  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."
3524  }
3525  set ver v$M.$m.$p
3526  }
3527  } else {
3528  #The tag in $result contains the current SHA
3529  set vers [split $result "\n"]
3530  set ver [lindex $vers 0]
3531  foreach v $vers {
3532  if {[regexp {^v.*$} $v]} {
3533  set un_ver $ver
3534  set ver $v
3535  break
3536  }
3537  }
3538  }
3539  } else {
3540  Msg CriticalWarning "Error while trying to find tag for $SHA"
3541  set ver "v0.0.0"
3542  }
3543  }
3544  lassign [ExtractVersionFromTag $ver] M m c mr
3545 
3546  if {$mr > -1} {
3547  # Candidate tab
3548  set M [format %02X $M]
3549  set m [format %02X $m]
3550  set c [format %04X $c]
3551  } elseif {$M > -1} {
3552  # official tag
3553  set M [format %02X $M]
3554  set m [format %02X $m]
3555  set c [format %04X $c]
3556  } else {
3557  Msg Warning "Tag does not contain a properly formatted version: $ver"
3558  set M [format %02X 0]
3559  set m [format %02X 0]
3560  set c [format %04X 0]
3561  }
3562 
3563  return $M$m$c
3564 }
3565 
3566 ## @brief Handle git commands
3567 #
3568 #
3569 # @param[in] command: the git command to be run including refs (branch, tags, sha, etc.), except files.
3570 # @param[in] files: files given to git as argument. They will always be separated with -- to avoid weird accidents
3571 #
3572 # @returns the output of the git command
3573 proc Git {command {files ""}} {
3574  lassign [GitRet $command $files] ret result
3575  if {$ret != 0} {
3576  Msg Error "Code $ret returned by git running: $command -- $files"
3577  }
3578 
3579  return $result
3580 }
3581 
3582 
3583 # @brief Get the name of the module in a HDL file. If module is not found, it returns an empty string
3584 #
3585 # @param[in] filename The name of the hdl file
3586 
3587 proc GetModuleName {filename} {
3588  # Check if the file exists
3589  if {![file exists $filename]} {
3590  Msg CriticalWarning "Error: File $filename does not exist."
3591  return ""
3592  }
3593 
3594  # Open the file for reading
3595  set fileId [open $filename r]
3596 
3597  # Read the content of the file
3598  set file_content [read $fileId]
3599 
3600  # Close the file
3601  close $fileId
3602 
3603 
3604  if {[file extension $filename] == ".vhd" || [file extension $filename] == ".vhdl"} {
3605  # Convert the file content to lowercase for case-insensitive matching
3606  set file_content [string tolower $file_content]
3607  # Regular expression to match the entity name after the 'entity' keyword
3608  set pattern {(?m)^\s*entity\s+(\S+)\s+is}
3609  } elseif {[file extension $filename] == ".v" || [file extension $filename] == ".sv"} {
3610  # Regular expression to match the module name after the 'module' keyword
3611  set pattern {\n\s*module\s*(\w+)(\s*|\(|\n)}
3612  } else {
3613  Msg Debug "File is neither VHDL nor Verilog... Returning empty string..."
3614  return "'"
3615  }
3616 
3617  # Search for the module name using the regular expression
3618  if {[regexp $pattern $file_content match module_name]} {
3619  return $module_name
3620  } else {
3621  Msg Debug "No module was found in $filename. Returning an empty string..."
3622  return ""
3623  }
3624 }
3625 
3626 ## Get a dictionary of verilog generics with their types for a given file
3627 #
3628 # @param[in] file File to read Generics from
3629 proc GetVerilogGenerics {file} {
3630  set fp [open $file r]
3631  set data [read $fp]
3632  close $fp
3633  set lines []
3634 
3635  # read in the verilog file and remove comments
3636  foreach line [split $data "\n"] {
3637  regsub "^\\s*\/\/.*" $line "" line
3638  regsub "(.*)\/\/.*" $line {\1} line
3639  if {![string equal $line ""]} {
3640  append lines $line " "
3641  }
3642  }
3643 
3644  # remove block comments also /* */
3645  regsub -all {/\*.*\*/} $lines "" lines
3646 
3647  # create a list of characters to split for tokenizing
3648  set punctuation [list]
3649  foreach char [list "(" ")" ";" "," " " "!" "<=" ":=" "=" "\[" "\]"] {
3650  lappend punctuation $char "\000$char\000"
3651  }
3652 
3653  # split the file into tokens
3654  set tokens [split [string map $punctuation $lines] \000]
3655 
3656  set parameters [dict create]
3657 
3658  set PARAM_NAME 1
3659  set PARAM_VALUE 2
3660  set LEXING 3
3661  set PARAM_WIDTH 4
3662  set state $LEXING
3663 
3664  # loop over the generic lines
3665  foreach token $tokens {
3666  set token [string trim $token]
3667  if {![string equal "" $token]} {
3668  if {[string equal [string tolower $token] "parameter"]} {
3669  set state $PARAM_NAME
3670  } elseif {[string equal $token ")"] || [string equal $token ";"]} {
3671  set state $LEXING
3672  } elseif {$state == $PARAM_WIDTH} {
3673  if {[string equal $token "\]"]} {
3674  set state $PARAM_NAME
3675  }
3676  } elseif {$state == $PARAM_VALUE} {
3677  if {[string equal $token ","]} {
3678  set state $PARAM_NAME
3679  } elseif {[string equal $token ";"]} {
3680  set state $LEXING
3681  } else {
3682 
3683  }
3684  } elseif {$state == $PARAM_NAME} {
3685  if {[string equal $token "="]} {
3686  set state $PARAM_VALUE
3687  } elseif {[string equal $token "\["]} {
3688  set state $PARAM_WIDTH
3689  } elseif {[string equal $token ","]} {
3690  set state $PARAM_NAME
3691  } elseif {[string equal $token ";"]} {
3692  set state $LEXING
3693  } elseif {[string equal $token ")"]} {
3694  set state $LEXING
3695  } else {
3696  dict set parameters $token "integer"
3697  }
3698  }
3699  }
3700  }
3701 
3702  return $parameters
3703 }
3704 
3705 ## Get a dictionary of VHDL generics with their types for a given file
3706 #
3707 # @param[in] file File to read Generics from
3708 # @param[in] entity The entity from which extracting the generics
3709 proc GetVhdlGenerics {file {entity ""}} {
3710  set fp [open $file r]
3711  set data [read $fp]
3712  close $fp
3713  set lines []
3714 
3715  # read in the vhdl file and remove comments
3716  foreach line [split $data "\n"] {
3717  regsub "^\\s*--.*" $line "" line
3718  regsub "(.*)--.*" $line {\1} line
3719  if {![string equal $line ""]} {
3720  append lines $line " "
3721  }
3722  }
3723 
3724  # extract the generic block
3725  set generic_block ""
3726  set generics [dict create]
3727 
3728  if {1 == [string equal $entity ""]} {
3729  regexp {(?i).*entity\s+([^\s]+)\s+is} $lines _ entity
3730  }
3731 
3732  set generics_regexp "(?i).*entity\\s+$entity\\s+is\\s+generic\\s*\\((.*)\\)\\s*;\\s*port.*end.*$entity"
3733 
3734  if {[regexp $generics_regexp $lines _ generic_block]} {
3735  # loop over the generic lines
3736  foreach line [split $generic_block ";"] {
3737  # split the line into the generic + the type
3738  regexp {(.*):\s*([A-Za-z0-9_]+).*} $line _ generic type
3739 
3740  # one line can have multiple generics of the same type, so loop over them
3741  set splits [split $generic ","]
3742  foreach split $splits {
3743  dict set generics [string trim $split] [string trim $type]
3744  }
3745  }
3746  }
3747  return $generics
3748 }
3749 
3750 ## @brief Runs a GHDL command and returns its output and exit state
3751 proc GHDL {command logfile} {
3752  set ret [catch {exec -ignorestderr ghdl {*}$command >>& $logfile} result options]
3753  # puts "ret: $ret"
3754  # puts "result: $result\n"
3755  # puts "options: $options"
3756  # puts "*********"
3757  return [list $ret $result]
3758 }
3759 
3760 ## @brief Handle git commands without causing an error if ret is not 0
3761 #
3762 # It can be used with lassign like this: lassign [GitRet <git command> <possibly files> ] ret result
3763 #
3764 # @param[in] command: the git command to be run including refs (branch, tags, sha, etc.), except files.
3765 # @param[in] files: files given to git as argument. They will always be separated with -- to avoid weird accidents
3766 # Sometimes you need to remove the --. To do that just set files to " "
3767 #
3768 # @returns a list of 2 elements: the return value (0 if no error occurred) and the output of the git command
3769 proc GitRet {command {files ""}} {
3770  global env
3771  if {$files eq ""} {
3772  set ret [catch {exec -ignorestderr git {*}$command} result]
3773  } else {
3774  set ret [catch {exec -ignorestderr git {*}$command -- {*}$files} result]
3775  }
3776  return [list $ret $result]
3777 }
3778 
3779 ## @brief Check git version installed in this machine
3780 #
3781 # @param[in] target_version the version required by the current project
3782 #
3783 # @return Returns 1, if the system git version is greater or equal to the target
3784 proc GitVersion {target_version} {
3785  set ver [split $target_version "."]
3786  set v [Git --version]
3787  #Msg Info "Found Git version: $v"
3788  set current_ver [split [lindex $v 2] "."]
3789  set target [expr {[lindex $ver 0] * 100000 + [lindex $ver 1] * 100 + [lindex $ver 2]}]
3790  set current [expr {[lindex $current_ver 0] * 100000 + [lindex $current_ver 1] * 100 + [lindex $current_ver 2]}]
3791  return [expr {$target <= $current}]
3792 }
3793 
3794 ## @brief Copy IP generated files from/to a remote o local directory (possibly EOS)
3795 #
3796 # @param[in] what_to_do: the action you want to perform, either
3797  # "push", if you want to copy the local IP synth result to the remote directory
3798  # "pull" if you want to copy the files from thre remote directory to your local repository
3799 # @param[in] xci_file: the .xci file of the IP you want to handle
3800 # @param[in] ip_path: the path of the directory you want the IP to be saved (possibly EOS)
3801 # @param[in] repo_path: the main path of your repository
3802 # @param[in] gen_dir: the directory where generated files are placed, by default the files are placed in the same folder as the .xci
3803 # @param[in] force: if not set to 0, will copy the IP to the remote directory even if it is already present
3804 #
3805 proc HandleIP {what_to_do xci_file ip_path repo_path {gen_dir "."} {force 0}} {
3806  if {!($what_to_do eq "push") && !($what_to_do eq "pull")} {
3807  Msg Error "You must specify push or pull as first argument."
3808  }
3809 
3810  if {[catch {package require tar} TARPACKAGE]} {
3811  Msg CriticalWarning "Cannot find package tar. You can fix this by installing package \"tcllib\""
3812  return -1
3813  }
3814 
3815  set old_path [pwd]
3816 
3817  cd $repo_path
3818 
3819 
3820  if {[string first "/eos/" $ip_path] == 0} {
3821  # IP Path is on EOS
3822  set on_eos 1
3823  } else {
3824  set on_eos 0
3825  }
3826 
3827  if {$on_eos == 1} {
3828  lassign [eos "ls $ip_path"] ret result
3829  if {$ret != 0} {
3830  Msg CriticalWarning "Could not run ls for for EOS path: $ip_path (error: $result). \
3831  Either the drectory does not exist or there are (temporary) problem with EOS."
3832  cd $old_path
3833  return -1
3834  } else {
3835  Msg Info "IP remote directory path, on EOS, is set to: $ip_path"
3836  }
3837  } else {
3838  file mkdir $ip_path
3839  }
3840 
3841  if {!([file exists $xci_file])} {
3842  Msg CriticalWarning "Could not find $xci_file."
3843  cd $old_path
3844  return -1
3845  }
3846 
3847 
3848  set xci_path [file dirname $xci_file]
3849  set xci_name [file tail $xci_file]
3850  set xci_ip_name [file rootname [file tail $xci_file]]
3851  set xci_dir_name [file tail $xci_path]
3852  set gen_path $gen_dir
3853 
3854  set hash [Md5Sum $xci_file]
3855  set file_name $xci_name\_$hash
3856 
3857  Msg Info "Preparing to $what_to_do IP: $xci_name..."
3858 
3859  if {$what_to_do eq "push"} {
3860  set will_copy 0
3861  set will_remove 0
3862  if {$on_eos == 1} {
3863  lassign [eos "ls $ip_path/$file_name.tar"] ret result
3864  if {$ret != 0} {
3865  set will_copy 1
3866  } else {
3867  if {$force == 0} {
3868  Msg Info "IP already in the EOS repository, will not copy..."
3869  } else {
3870  Msg Info "IP already in the EOS repository, will forcefully replace..."
3871  set will_copy 1
3872  set will_remove 1
3873  }
3874  }
3875  } else {
3876  if {[file exists "$ip_path/$file_name.tar"]} {
3877  if {$force == 0} {
3878  Msg Info "IP already in the local repository, will not copy..."
3879  } else {
3880  Msg Info "IP already in the local repository, will forcefully replace..."
3881  set will_copy 1
3882  set will_remove 1
3883  }
3884  } else {
3885  set will_copy 1
3886  }
3887  }
3888 
3889  if {$will_copy == 1} {
3890  # Check if there are files in the .gen directory first and copy them into the right place
3891  Msg Info "Looking for generated files in $gen_path..."
3892  set ip_gen_files [glob -nocomplain $gen_path/*]
3893 
3894  #here we should remove the .xci file from the list if it's there
3895 
3896  if {[llength $ip_gen_files] > 0} {
3897  Msg Info "Found some IP synthesised files matching $xci_ip_name"
3898  if {$will_remove == 1} {
3899  Msg Info "Removing old synthesised directory $ip_path/$file_name.tar..."
3900  if {$on_eos == 1} {
3901  eos "rm -rf $ip_path/$file_name.tar" 5
3902  } else {
3903  file delete -force "$ip_path/$file_name.tar"
3904  }
3905  }
3906 
3907  Msg Info "Creating local archive with IP generated files..."
3908  set first_file 0
3909  foreach f $ip_gen_files {
3910  if {$first_file == 0} {
3911  ::tar::create $file_name.tar "[Relative [file normalize $repo_path] $f]"
3912  set first_file 1
3913  } else {
3914  ::tar::add $file_name.tar "[Relative [file normalize $repo_path] $f]"
3915  }
3916  }
3917 
3918  Msg Info "Copying IP generated files for $xci_name..."
3919  if {$on_eos == 1} {
3920  lassign [ExecuteRet xrdcp -f -s $file_name.tar $::env(EOS_MGM_URL)//$ip_path/] ret msg
3921  if {$ret != 0} {
3922  Msg CriticalWarning "Something went wrong when copying the IP files to EOS. Error message: $msg"
3923  }
3924  } else {
3925  Copy "$file_name.tar" "$ip_path/"
3926  }
3927  Msg Info "Removing local archive"
3928  file delete $file_name.tar
3929  } else {
3930  Msg Warning "Could not find synthesized files matching $gen_path/$file_name*"
3931  }
3932  }
3933  } elseif {$what_to_do eq "pull"} {
3934  if {$on_eos == 1} {
3935  lassign [eos "ls $ip_path/$file_name.tar"] ret result
3936  if {$ret != 0} {
3937  Msg Info "Nothing for $xci_name was found in the EOS repository, cannot pull."
3938  cd $old_path
3939  return -1
3940  } else {
3941  set remote_tar "$::env(EOS_MGM_URL)//$ip_path/$file_name.tar"
3942  Msg Info "IP $xci_name found in the repository $remote_tar, copying it locally to $repo_path..."
3943 
3944  lassign [ExecuteRet xrdcp -f -r -s $remote_tar $repo_path] ret msg
3945  if {$ret != 0} {
3946  Msg CriticalWarning "Something went wrong when copying the IP files to EOS. Error message: $msg"
3947  }
3948  }
3949  } else {
3950  if {[file exists "$ip_path/$file_name.tar"]} {
3951  Msg Info "IP $xci_name found in local repository $ip_path/$file_name.tar, copying it locally to $repo_path..."
3952  Copy $ip_path/$file_name.tar $repo_path
3953  } else {
3954  Msg Info "Nothing for $xci_name was found in the local IP repository, cannot pull."
3955  cd $old_path
3956  return -1
3957  }
3958  }
3959 
3960  if {[file exists $file_name.tar]} {
3961  remove_files $xci_file
3962  Msg Info "Extracting IP files from archive to $repo_path..."
3963  ::tar::untar $file_name.tar -dir $repo_path -noperms
3964  Msg Info "Removing local archive"
3965  file delete $file_name.tar
3966  add_files -norecurse -fileset sources_1 $xci_file
3967  }
3968  }
3969  cd $old_path
3970  return 0
3971 }
3972 
3973 ## Convert hex version to M.m.p string
3974 #
3975 # @param[in] version the version (in 32-bit hexadecimal format 0xMMmmpppp) to be converted
3976 #
3977 # @return a string containing the version in M.m.p format
3978 #
3979 proc HexVersionToString {version} {
3980  scan [string range $version 0 1] %x M
3981  scan [string range $version 2 3] %x m
3982  scan [string range $version 4 7] %x c
3983  return "$M.$m.$c"
3984 }
3985 
3986 # @brief Import TCL Lib from an external installation for Libero, Synplify and Diamond
3987 proc ImportTclLib {} {
3988  if {[IsLibero] || [IsDiamond] || [IsSynplify]} {
3989  if {[info exists env(HOG_TCLLIB_PATH)]} {
3990  lappend auto_path $env(HOG_TCLLIB_PATH)
3991  return 1
3992  } else {
3993  puts "ERROR: To run Hog with Microsemi Libero SoC or Lattice Diamond, you need to define the HOG_TCLLIB_PATH variable."
3994  return 0
3995  }
3996  }
3997 }
3998 
3999 # @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
4000 #
4001 # @param[in] script The launch.tcl script
4002 # @param[in] tcl_path The launch.tcl script path
4003 # @param[in] parameters The allowed parameters for launch.tcl
4004 # @param[in] commands The allowed directives for launch.tcl
4005 # @param[in] argv The input arguments passed to launch.tcl
4006 # @param[in] custom_commands Custom commands to be added to the list of commands
4007 
4008 proc InitLauncher {script tcl_path parameters commands argv {custom_commands ""}} {
4009  set repo_path [file normalize "$tcl_path/../.."]
4010  set old_path [pwd]
4011  set bin_path [file normalize "$tcl_path/../../bin"]
4012  set top_path [file normalize "$tcl_path/../../Top"]
4013 
4014  set cmd_lines [split $commands "\n"]
4015 
4016  set command_options [dict create]
4017  set directive_descriptions [dict create]
4018  set directive_names [dict create]
4019  set common_directive_names [dict create]
4020  set custom_command ""
4021  set custom_command_options ""
4022 
4023  foreach l $cmd_lines {
4024  #excludes direcitve with a # just after the \{
4025  if {[regexp {\\(.*) \{\#} $l minc d]} {
4026  lappend directives_with_projects $d
4027  }
4028 
4029  #gets all the regexes
4030  if {[regexp {\\(.*) \{} $l minc regular_expression]} {
4031  lappend directive_regex $regular_expression
4032  }
4033 
4034  #gets all common directives
4035  if {[regexp {\#\s*NAME(\*)?:\s*(.*)\s*} $l minc star name]} {
4036  dict set directive_names $name $regular_expression
4037  if {$star eq "*"} {
4038  dict set common_directive_names $name $regular_expression
4039  }
4040  }
4041  set directive_names [DictSort $directive_names]
4042  set common_directive_names [DictSort $common_directive_names]
4043 
4044  #gets all the descriptions
4045  if {[regexp {\#\s*DESCRIPTION:\s*(.*)\s*} $l minc x]} {
4046  dict set directive_descriptions $regular_expression $x
4047  }
4048 
4049  #gets all the list of options
4050  if {[regexp {\#\s*OPTIONS:\s*(.*)\s*} $l minc x]} {
4051  dict set command_options $regular_expression [split [regsub -all {[ \t\n]+} $x {}] ","]
4052  }
4053  }
4054 
4055  set short_usage "usage: ./Hog/Do \[OPTIONS\] <directive> \[project\]\n\nMost common directives (case insensitive):"
4056 
4057  dict for {key value} $common_directive_names {
4058  set short_usage "$short_usage\n - $key: [dict get $directive_descriptions $value]"
4059  }
4060 
4061  if {[string length $custom_commands] > 0} {
4062  Msg Debug "Found custom commands to add to short short_usage."
4063  set short_usage "$short_usage\n\nCustom commands:"
4064  dict for {key command} $custom_commands {
4065  Msg Debug "Adding $key : [dict get $command DESCRIPTION]"
4066  set short_usage "$short_usage\n - $key: [dict get $command DESCRIPTION]"
4067  }
4068  }
4069 
4070 
4071 
4072  set short_usage "$short_usage\n\n\
4073  To see all the available directives, run:\n./Hog/Do HELP\n\n\
4074  To list available options for the chosen directive run:\n\
4075  ./Hog/Do <directive> HELP\n
4076  "
4077 
4078  set usage "usage: ./Hog/Do \[OPTIONS\] <directive> \[project\]\n\nDirectives (case insensitive):"
4079 
4080  dict for {key value} $directive_names {
4081  set usage "$usage\n - $key: [dict get $directive_descriptions $value]"
4082  }
4083 
4084  #if length of custom commands is greater than 0, add them to the short usage
4085  if {[string length $custom_commands] > 0} {
4086  Msg Debug "Found custom commands to add to short usage."
4087  set usage "$usage\n\nCustom commands:"
4088  dict for {key command} $custom_commands {
4089  Msg Debug "Adding $key : [dict get $command DESCRIPTION]"
4090  set usage "$usage\n - $key: [dict get $command DESCRIPTION]"
4091  }
4092  }
4093 
4094 
4095  set usage "$usage\n\nTo list available options for the chosen directive run:\n./Hog/Do <directive> HELP"
4096 
4097  if {[IsTclsh]} {
4098  #Just display the logo the first time, not when the script is run in the IDE
4099  Logo $repo_path
4100  }
4101 
4102  # Check if HogEnv.conf exists and parse it
4103  if {[file exists [Hog::LoggerLib::GetUserFilePath "HogEnv.conf"]] } {
4104  Msg Debug "HogEnv.conf found"
4105  set loggerdict [Hog::LoggerLib::ParseTOML [Hog::LoggerLib::GetUserFilePath "HogEnv.conf" ]]
4106  set HogEnvDict [Hog::LoggerLib::GetTOMLDict]
4107  Hog::LoggerLib::PrintTOMLDict $HogEnvDict
4108  }
4109 
4110 
4111 
4112  if {[catch {package require cmdline} ERROR]} {
4113  Msg Debug "The cmdline Tcl package was not found, sourcing it from Hog..."
4114  source $tcl_path/utils/cmdline.tcl
4115  }
4116 
4117  set argv [regsub -all {(?i) HELP\y} $argv " -help"]
4118 
4119 
4120  #Gather up all custom parameters
4121  #NOTE: right now user can accidentally redefine their own custom parameters, there is no check for that...
4122  set custom_parameters [list]
4123  dict for {key command} $custom_commands {
4124  set custom_parameters [concat $custom_parameters [dict get $command CUSTOM_OPTIONS]]
4125  }
4126 
4127  lassign [GetOptions $argv [concat $custom_parameters $parameters]] option_list arg_list
4128 
4129  if {[IsInList "-all" $option_list]} {
4130  set list_all 1
4131  } else {
4132  set list_all 2
4133  }
4134 
4135  #option_list will be emptied by the next instruction
4136 
4137  # Argv here is modified and the options are removed
4138  set directive [string toupper [lindex $arg_list 0]]
4139  set min_n_of_args 0
4140  set max_n_of_args 2
4141  set argument_is_no_project 1
4142 
4143  set NO_DIRECTIVE_FOUND 0
4144  switch -regexp -- $directive "$commands"
4145  if {$NO_DIRECTIVE_FOUND == 1} {
4146  if {[string length $custom_commands] > 0 && [dict exists $custom_commands $directive]} {
4147  set custom_command $directive
4148  set custom_command_hog_parameters [dict get $custom_commands $directive OPTIONS]
4149  set custom_command_options [dict get $custom_commands $directive CUSTOM_OPTIONS]
4150  set custom_command_options [concat $custom_command_hog_parameters $custom_command_options]
4151  } else {
4152  Msg Status "ERROR: Unknown directive $directive.\n\n"
4153  puts $usage
4154  exit
4155  }
4156  }
4157 
4158  if {[IsInList $directive $directives_with_projects 1]} {
4159  set argument_is_no_project 0
4160  }
4161 
4162  if {[IsInList "-help" $option_list] || [IsInList "-?" $option_list] || [IsInList "-h" $option_list]} {
4163  if {$directive != ""} {
4164  if {[IsInList $directive $directives_with_projects 1]} {
4165  puts "usage: ./Hog/Do \[OPTIONS\] $directive <project>\n"
4166  } elseif {[regexp "^COMPSIM(LIB)?$" $directive]} {
4167  puts "usage: ./Hog/Do \[OPTIONS\] $directive <simulator>\n"
4168  } else {
4169  puts "usage: ./Hog/Do \[OPTIONS\] $directive \n"
4170  }
4171 
4172  dict for {dir desc} $directive_descriptions {
4173  if {[regexp $dir $directive]} {
4174  puts "$desc\n"
4175  break
4176  }
4177  }
4178 
4179 
4180  #if custom command, parse custom options instead
4181  if {$custom_command ne ""} {
4182  if {[llength $custom_command_options] > 0} {
4183  puts "Available options:"
4184  }
4185  foreach custom_option $custom_command_options {
4186  set n [llength $custom_option]
4187  if {$n == 2} {
4188  lassign $custom_option opt help
4189  puts " -$opt"
4190  puts " $help"
4191  } elseif {$n == 3} {
4192  lassign $custom_option opt def help
4193  puts " -$opt <argument>"
4194  if {$def ne ""} {
4195  puts " $help (default: $def)"
4196  } else {
4197  puts " $help"
4198  }
4199  } else {
4200  Msg Warning "Custom option spec has invalid arity (expected 2 or 3): $custom_option"
4201  }
4202  }
4203  }
4204 
4205  dict for {dir opts} $command_options {
4206  if {[regexp $dir $directive]} {
4207  puts "Available options:"
4208  foreach opt $opts {
4209  foreach par $parameters {
4210  if {$opt == [lindex $par 0]} {
4211  if {[regexp {\.arg$} $opt]} {
4212  set opt_name [regsub {\.arg$} $opt ""]
4213  puts " -$opt_name <argument>"
4214  } else {
4215  puts " -$opt"
4216  }
4217  puts " [lindex $par [llength $par]-1]"
4218  }
4219  }
4220  }
4221  puts ""
4222  }
4223  }
4224  } else {
4225  puts $usage
4226  }
4227  # Msg Info [cmdline::usage $parameters $usage]
4228  exit 0
4229  }
4230 
4231  if {$custom_command ne ""} {
4232  set parameters [concat $parameters $custom_command_options]
4233  }
4234 
4235  if {[catch {array set options [cmdline::getoptions option_list $parameters $usage]} err]} {
4236  Msg Status "\nERROR: Syntax error, probably unknown option.\n\n USAGE: $err"
4237  exit 1
4238  }
4239 
4240  if {[llength $arg_list] <= $min_n_of_args || [llength $arg_list] > $max_n_of_args} {
4241  Msg Status "\nERROR: Wrong number of arguments: [llength $argv]"
4242  puts $short_usage
4243  exit 1
4244  }
4245 
4246  set project [lindex $arg_list 1]
4247 
4248  if {$argument_is_no_project == 0} {
4249  # Remove leading Top/ or ./Top/ if in project_name
4250  regsub "^(\./)?Top/" $project "" project
4251  # Remove trailing / and spaces if in project_name
4252  regsub "/? *\$" $project "" project
4253  set proj_conf [ProjectExists $project $repo_path]
4254  } else {
4255  set proj_conf 0
4256  }
4257 
4258  Msg Debug "Option list:"
4259  foreach {key value} [array get options] {
4260  Msg Debug "$key => $value"
4261  }
4262 
4263  set cmd ""
4264 
4265  if {[IsTclsh]} {
4266  # command is filled with the IDE exectuable when this function is called by Tcl scrpt
4267  if {$proj_conf != 0} {
4268  CheckLatestHogRelease $repo_path
4269 
4270  lassign [GetIDECommand $proj_conf] cmd before_tcl_script after_tcl_script end_marker
4271  Msg Info "Project $project uses $cmd IDE"
4272 
4273  ## The following is the IDE command to launch:
4274  set command "$cmd $before_tcl_script$script$after_tcl_script$argv$end_marker"
4275  } else {
4276  if {$custom_command ne ""} {
4277  if { [dict exists $custom_commands $directive IDE] } {
4278  lassign [GetIDECommand "" [dict get $custom_commands $directive IDE]] cmd before_tcl_script after_tcl_script end_marker
4279  Msg Info "Custom command: $custom_command uses $cmd IDE"
4280  set command "$cmd $before_tcl_script$script$after_tcl_script$argv$end_marker"
4281  } else {
4282  set command "custom_tcl"
4283  }
4284  } elseif {$argument_is_no_project == 1} {
4285  set command -4
4286  Msg Debug "$project will be used as first argument"
4287  } elseif {$project != ""} {
4288  #Project not given
4289  set command -1
4290  } elseif {$min_n_of_args < 0} {
4291  #Project not needed
4292  set command -3
4293  } else {
4294  #Project not found
4295  set command -2
4296  }
4297  }
4298  } else {
4299  # When the launcher is executed from within an IDE, command is set to 0
4300  set command 0
4301  }
4302 
4303  set project_group [file dirname $project]
4304  set project_name $project
4305  set project [file tail $project]
4306  Msg Debug "InitLauncher: project_group=$project_group, project_name=$project_name, project=$project"
4307 
4308  return [list $directive $project $project_name $project_group $repo_path $old_path $bin_path $top_path $usage $short_usage $command $cmd [array get options]]
4309 }
4310 
4311 # @brief Returns 1 if a commit is an ancestor of another, otherwise 0
4312 #
4313 # @param[in] ancestor The potential ancestor commit
4314 # @param[in] commit The potential descendant commit
4315 proc IsCommitAncestor {ancestor commit} {
4316  lassign [GitRet "merge-base --is-ancestor $ancestor $commit"] status result
4317  if {$status == 0} {
4318  return 1
4319  } else {
4320  return 0
4321  }
4322 }
4323 
4324 proc IsDiamond {} {
4325  return [expr {[info commands sys_install] != ""}]
4326 }
4327 
4328 ## @brief Returns true if the IDE is MicroSemi Libero
4329 proc IsLibero {} {
4330  return [expr {[info commands get_libero_version] != ""}]
4331 }
4332 
4333 # @brief Returns 1 if an element is a list, 0 otherwise
4334 #
4335 # @param[in] element The element to search
4336 # @param[in] list The list to search into
4337 # @param[in] regex An optional regex to match. If 0, the element should match exactly an object in the list
4338 # @param[in] nocase If 1, perform case-insensitive comparison
4339 proc IsInList {element list {regex 0} {nocase 0}} {
4340  foreach x $list {
4341  if {$regex == 1} {
4342  if {$nocase == 1} {
4343  if {[regexp -nocase $x $element]} {
4344  return 1
4345  }
4346  } else {
4347  if {[regexp $x $element]} {
4348  return 1
4349  }
4350  }
4351  } elseif {$regex == 0} {
4352  if {$nocase == 1} {
4353  if {[string tolower $x] eq [string tolower $element]} {
4354  return 1
4355  }
4356  } else {
4357  if {$x eq $element} {
4358  return 1
4359  }
4360  }
4361  }
4362  }
4363  return 0
4364 }
4365 
4366 
4367 ## @brief Returns true, if the IDE is ISE/PlanAhead
4368 proc IsISE {} {
4369  if {[IsXilinx]} {
4370  return [expr {[string first PlanAhead [version]] == 0}]
4371  } else {
4372  return 0
4373  }
4374 }
4375 
4376 ## @brief Returns true, if IDE is Quartus
4377 proc IsQuartus {} {
4378  if {[catch {package require ::quartus::flow} result]} {
4379  # not available
4380  return 0
4381  } else {
4382  # available
4383  return 1
4384  }
4385 }
4386 
4387 ## Check if a path is absolute or relative
4388 #
4389 # @param[in] the path to check
4390 #
4391 proc IsRelativePath {path} {
4392  if {[string index $path 0] == "/" || [string index $path 0] == "~"} {
4393  return 0
4394  } else {
4395  return 1
4396  }
4397 }
4398 
4399 ## @brief Returns true if the Synthesis tool is Synplify
4400 proc IsSynplify {} {
4401  return [expr {[info commands program_version] != ""}]
4402 }
4403 
4404 ## @brief Returns true, if we are in tclsh
4405 proc IsTclsh {} {
4406  return [expr {![IsQuartus] && ![IsXilinx] && ![IsVitisClassic] && ![IsLibero] && ![IsSynplify] && ![IsDiamond]}]
4407 }
4408 
4409 ## @brief Find out if the given Xilinx part is a Vesal chip
4410 #
4411 # @param[out] 1 if it's Versal 0 if it's not
4412 # @param[in] part The FPGA part
4413 #
4414 proc IsVersal {part} {
4415  if {[get_property ARCHITECTURE [get_parts $part]] eq "versal"} {
4416  return 1
4417  } else {
4418  return 0
4419  }
4420 }
4421 
4422 ## @brief Returns true, if the IDE is Vivado
4423 proc IsVivado {} {
4424  if {[IsXilinx]} {
4425  return [expr {[string first Vivado [version]] == 0}]
4426  } else {
4427  return 0
4428  }
4429 }
4430 
4431 ## @brief Return true, if the IDE is Xilinx (Vivado or ISE)
4432 proc IsXilinx {} {
4433  if {[info commands version] != ""} {
4434  set current_version [version]
4435  if {[string first PlanAhead $current_version] == 0 || [string first Vivado $current_version] == 0} {
4436  return 1
4437  } elseif {[string first xsct $current_version] == 0} {
4438  return 0
4439  } else {
4440  Msg Warning "This IDE has the version command but it is not PlanAhead or Vivado: $current_version"
4441  return 0
4442  }
4443  } else {
4444  return 0
4445  }
4446 }
4447 
4448 ## @brief Returns true, if the IDE is vitis_classic
4449 proc IsVitisClassic {} {
4450  return [expr {[info commands platform] != ""}]
4451 }
4452 
4453 ## @brief Find out if the given Xilinx part is a Vesal chip
4454 #
4455 # @param[out] 1 if it's Zynq 0 if it's not
4456 # @param[in] part The FPGA part
4457 #
4458 proc IsZynq {part} {
4459  if {[regexp {^(xc7z|xczu).*} $part]} {
4460  return 1
4461  } else {
4462  return 0
4463  }
4464 }
4465 
4466 proc ImportGHDL {project_name repo_path simset_name simset_dict {ext_path ""}} {
4467  set list_path "$repo_path/Top/$project_name/list"
4468  lassign [GetHogFiles -list_files {.src,.ext,.sim} -ext_path $ext_path $list_path $repo_path] src_files properties filesets
4469  cd $repo_path
4470 
4471 
4472  # Get Properties
4473  set properties [DictGet $simset_dict "properties"]
4474  set options [DictGet $properties "options"]
4475 
4476  # Import GHDL files
4477  set workdir Projects/$project_name/ghdl
4478  file delete -force $workdir
4479  file mkdir $workdir
4480  set import_log "$workdir/ghdl-import-${simset_name}.log"
4481  dict for {lib sources} $src_files {
4482  set libname [file rootname $lib]
4483  foreach f $sources {
4484  if {[file extension $f] != ".vhd" && [file extension $f] != ".vhdl"} {
4485  Msg Info "File $f is not a VHDL file, copying it in workfolder..."
4486  file copy -force $f $workdir
4487  } else {
4488  set file_path [Relative $repo_path $f]
4489  set import_log_file [open $import_log "a"]
4490  puts "ghdl -i --work=$libname --workdir=$workdir -fsynopsys --ieee=standard $options $file_path"
4491  puts $import_log_file "ghdl -i --work=$libname --workdir=$workdir -fsynopsys --ieee=standard $options $file_path"
4492  close $import_log_file
4493  lassign [GHDL "-i --work=$libname --workdir=$workdir -fsynopsys --ieee=standard $options $file_path" $import_log] ret result
4494  if {$ret != 0} {
4495  Msg CriticalWarning "GHDL import failed for file $f: $result"
4496  }
4497  }
4498  }
4499  }
4500  PrintFileContent $import_log
4501 
4502 }
4503 
4504 proc LaunchGHDL {project_name repo_path simset_name simset_dict {ext_path ""}} {
4505  set top_sim ""
4506  # Setting Simulation Properties
4507  set sim_props [DictGet $simset_dict "properties"]
4508  set options [DictGet $sim_props "options"]
4509  set runopts [DictGet $sim_props "run_options"]
4510 
4511  dict for {prop_name prop_val} $sim_props {
4512  set prop_name [string toupper $prop_name]
4513  if {$prop_name == "TOP"} {
4514  set top_sim $prop_val
4515  }
4516  }
4517  set workdir $repo_path/Projects/$project_name/ghdl
4518  set make_log "$workdir/ghdl-make-${simset_name}.log"
4519  set run_log "$workdir/ghdl-run-${simset_name}.log"
4520  cd $workdir
4521  # Analyse and elaborate the design
4522  set make_log_file [open $make_log "w"]
4523 
4524  puts "ghdl -m --work=$simset_name -fsynopsys --ieee=standard $options $top_sim"
4525  puts $make_log_file "ghdl -m --work=$simset_name -fsynopsys --ieee=standard $options $top_sim"
4526  close $make_log_file
4527 
4528  lassign [GHDL "-m --work=$simset_name -fsynopsys --ieee=standard $options $top_sim" $make_log] ret result
4529  PrintFileContent $make_log
4530  if {$ret != 0} {
4531  Msg Error "GHDL make failed for $top_sim: $result"
4532  return
4533  }
4534 
4535  set run_log_file [open $run_log "w"]
4536  puts "ghdl -r --work=$simset_name -fsynopsys --ieee=standard $options $top_sim $runopts"
4537  puts $run_log_file "ghdl -r --work=$simset_name -fsynopsys --ieee=standard $options $top_sim $runopts"
4538  close $run_log_file
4539 
4540  lassign [GHDL "-r --work=$simset_name -fsynopsys --ieee=standard $options $top_sim $runopts" $run_log] ret result
4541  PrintFileContent $run_log
4542 
4543  if {$ret != 0} {
4544  Msg Error "GHDL run failed for $top_sim: $result"
4545  return
4546  }
4547 
4548  cd $repo_path
4549 }
4550 
4551 # @brief Launch the Implementation, for the current IDE and project
4552 #
4553 # @param[in] reset Reset the Implementation run
4554 # @param[in] do_create Recreate the project
4555 # @param[in] run_folder The folder where to store the run results
4556 # @param[in] project_name The name of the project
4557 # @param[in] repo_path The main path of the git repository (Default .)
4558 # @param[in] njobs The number of parallel CPU jobs for the Implementation (Default 4)
4559 proc LaunchImplementation {reset do_create run_folder project_name {repo_path .} {njobs 4} {do_bitstream 0}} {
4560  Msg Info "Starting implementation flow..."
4561  if {[IsXilinx]} {
4562  if {$reset == 1 && $do_create == 0} {
4563  Msg Info "Resetting run before launching implementation..."
4564  reset_run impl_1
4565  }
4566 
4567  if {[IsISE]} {
4568  source $repo_path/Hog/Tcl/integrated/pre-implementation.tcl
4569  }
4570 
4571  if {$do_bitstream == 1} {
4572  launch_runs impl_1 -to_step [BinaryStepName [get_property PART [current_project]]] -jobs $njobs -dir $run_folder
4573  } else {
4574  launch_runs impl_1 -jobs $njobs -dir $run_folder
4575  }
4576  wait_on_run impl_1
4577 
4578  if {[IsISE]} {
4579  Msg Info "running post-implementation"
4580  source $repo_path/Hog/Tcl/integrated/post-implementation.tcl
4581  if {$do_bitstream == 1} {
4582  Msg Info "running pre-bitstream"
4583  source $repo_path/Hog/Tcl/integrated/pre-bitstream.tcl
4584  Msg Info "running post-bitstream"
4585  source $repo_path/Hog/Tcl/integrated/post-bitstream.tcl
4586  }
4587  }
4588 
4589  set prog [get_property PROGRESS [get_runs impl_1]]
4590  set status [get_property STATUS [get_runs impl_1]]
4591  Msg Info "Run: impl_1 progress: $prog, status : $status"
4592 
4593  # Check timing
4594  if {[IsISE]} {
4595  set status_file [open "$run_folder/timing.txt" "w"]
4596  puts $status_file "## $project_name Timing summary"
4597 
4598  set f [open [lindex [glob "$run_folder/impl_1/*.twr" 0]]]
4599  set errs -1
4600  while {[gets $f line] >= 0} {
4601  if {[string match "Timing summary:" $line]} {
4602  while {[gets $f line] >= 0} {
4603  if {[string match "Timing errors:*" $line]} {
4604  set errs [regexp -inline -- {[0-9]+} $line]
4605  }
4606  if {[string match "*Footnotes*" $line]} {
4607  break
4608  }
4609  puts $status_file "$line"
4610  }
4611  }
4612  }
4613 
4614  close $f
4615  close $status_file
4616 
4617  if {$errs == 0} {
4618  Msg Info "Time requirements are met"
4619  file rename -force "$run_folder/timing.txt" "$run_folder/timing_ok.txt"
4620  set timing_ok 1
4621  } else {
4622  Msg CriticalWarning "Time requirements are NOT met"
4623  file rename -force "$run_folder/timing.txt" "$run_folder/timing_error.txt"
4624  set timing_ok 0
4625  }
4626  }
4627 
4628  if {[IsVivado]} {
4629  set wns [get_property STATS.WNS [get_runs [current_run]]]
4630  set tns [get_property STATS.TNS [get_runs [current_run]]]
4631  set whs [get_property STATS.WHS [get_runs [current_run]]]
4632  set ths [get_property STATS.THS [get_runs [current_run]]]
4633  set tpws [get_property STATS.TPWS [get_runs [current_run]]]
4634 
4635  if {$wns >= 0 && $whs >= 0 && $tpws >= 0} {
4636  Msg Info "Time requirements are met"
4637  set status_file [open "$run_folder/timing_ok.txt" "w"]
4638  set timing_ok 1
4639  } else {
4640  Msg CriticalWarning "Time requirements are NOT met"
4641  set status_file [open "$run_folder/timing_error.txt" "w"]
4642  set timing_ok 0
4643  }
4644 
4645  Msg Status "*** Timing summary ***"
4646  Msg Status "WNS: $wns"
4647  Msg Status "TNS: $tns"
4648  Msg Status "WHS: $whs"
4649  Msg Status "THS: $ths"
4650  Msg Status "TPWS: $tpws"
4651 
4652  struct::matrix m
4653  m add columns 5
4654  m add row
4655 
4656  puts $status_file "## $project_name Timing summary"
4657 
4658  m add row "| **Parameter** | \"**value (ns)**\" |"
4659  m add row "| --- | --- |"
4660  m add row "| WNS: | $wns |"
4661  m add row "| TNS: | $tns |"
4662  m add row "| WHS: | $whs |"
4663  m add row "| THS: | $ths |"
4664  m add row "| TPWS: | $tpws |"
4665 
4666  puts $status_file [m format 2string]
4667  puts $status_file "\n"
4668  if {$timing_ok == 1} {
4669  puts $status_file " Time requirements are met."
4670  } else {
4671  puts $status_file "Time requirements are **NOT** met."
4672  }
4673  puts $status_file "\n\n"
4674  close $status_file
4675  }
4676 
4677  if {$prog ne "100%"} {
4678  Msg Error "Implementation error"
4679  }
4680 
4681  #Go to repository path
4682  cd $repo_path
4683  lassign [GetRepoVersions [file normalize ./Top/$project_name] $repo_path] sha
4684  set describe [GetHogDescribe $sha $repo_path]
4685  Msg Info "Git describe set to $describe"
4686 
4687  set dst_dir [file normalize "$repo_path/bin/$project_name\-$describe"]
4688 
4689  file mkdir $dst_dir
4690 
4691  #Version table
4692  if {[file exists $run_folder/versions.txt]} {
4693  file copy -force $run_folder/versions.txt $dst_dir
4694  } else {
4695  Msg Warning "No versions file found in $run_folder/versions.txt"
4696  }
4697  #Timing file
4698  set timing_files [glob -nocomplain "$run_folder/timing_*.txt"]
4699  set timing_file [file normalize [lindex $timing_files 0]]
4700 
4701  if {[file exists $timing_file]} {
4702  file copy -force $timing_file $dst_dir/
4703  } else {
4704  Msg Warning "No timing file found, not a problem if running locally"
4705  }
4706 
4707  #### XSA here only for Versal Segmented Configuration
4708  if {[IsVersal [get_property part [current_project]]]} {
4709  if {[get_property segmented_configuration [current_project]] == 1} {
4710  Msg Info "Versal Segmented configuration detected: exporting XSA file..."
4711  set xsa_name "$dst_dir/[file tail $project_name]\-$describe.xsa"
4712  write_hw_platform -fixed -force -file $xsa_name
4713  }
4714  }
4715  } elseif {[IsQuartus]} {
4716  set revision [get_current_revision]
4717 
4718  if {[catch {execute_module -tool fit} result]} {
4719  Msg Error "Result: $result\n"
4720  Msg Error "Place & Route failed. See the report file.\n"
4721  } else {
4722  Msg Info "\nINFO: Place & Route was successful for revision $revision.\n"
4723  }
4724 
4725  if {[catch {execute_module -tool sta -args "--do_report_timing"} result]} {
4726  Msg Error "Result: $result\n"
4727  Msg Error "Time Quest failed. See the report file.\n"
4728  } else {
4729  Msg Info "Time Quest was successfully run for revision $revision.\n"
4730  load_package report
4731  load_report
4732  set panel "Timing Analyzer||Timing Analyzer Summary"
4733  set device [get_report_panel_data -name $panel -col 1 -row_name "Device Name"]
4734  set timing_model [get_report_panel_data -name $panel -col 1 -row_name "Timing Models"]
4735  set delay_model [get_report_panel_data -name $panel -col 1 -row_name "Delay Model"]
4736  #set slack [ get_timing_analysis_summary_results -slack ]
4737  Msg Info "*******************************************************************"
4738  Msg Info "Device: $device"
4739  Msg Info "Timing Models: $timing_model"
4740  Msg Info "Delay Model: $delay_model"
4741  Msg Info "Slack:"
4742  #Msg Info $slack
4743  Msg Info "*******************************************************************"
4744  }
4745  } elseif {[IsLibero]} {
4746  Msg Info "Starting implementation flow..."
4747  if {[catch {run_tool -name {PLACEROUTE}}]} {
4748  Msg Error "PLACEROUTE FAILED!"
4749  } else {
4750  Msg Info "PLACEROUTE PASSED."
4751  }
4752 
4753  # Check timing
4754  Msg Info "Run VERIFYTIMING ..."
4755  if {[catch {run_tool -name {VERIFYTIMING} -script {Hog/Tcl/integrated/libero_timing.tcl}}]} {
4756  Msg CriticalWarning "VERIFYTIMING FAILED!"
4757  } else {
4758  Msg Info "VERIFYTIMING PASSED \n"
4759  }
4760 
4761  #Go to repository path
4762  cd $repo_path
4763 
4764  lassign [GetRepoVersions [file normalize ./Top/$project_name] $repo_path] sha
4765  set describe [GetHogDescribe $sha $repo_path]
4766  Msg Info "Git describe set to $describe"
4767 
4768  set dst_dir [file normalize "$repo_path/bin/$project_name\-$describe"]
4769  file mkdir $dst_dir/reports
4770 
4771  #Version table
4772  if {[file exists $run_folder/versions.txt]} {
4773  file copy -force $run_folder/versions.txt $dst_dir
4774  } else {
4775  Msg Warning "No versions file found in $run_folder/versions.txt"
4776  }
4777  #Timing file
4778  set timing_file_path [file normalize "$repo_path/Projects/timing_libero.txt"]
4779  if {[file exists $timing_file_path]} {
4780  file copy -force $timing_file_path $dst_dir/reports/Timing.txt
4781  set timing_file [open $timing_file_path "r"]
4782  set status_file [open "$dst_dir/timing.txt" "w"]
4783  puts $status_file "## $project_name Timing summary\n\n"
4784  puts $status_file "| | |"
4785  puts $status_file "| --- | --- |"
4786  while {[gets $timing_file line] >= 0} {
4787  if {[string match "SUMMARY" $line]} {
4788  while {[gets $timing_file line] >= 0} {
4789  if {[string match "END SUMMARY" $line]} {
4790  break
4791  }
4792  if {[string first ":" $line] == -1} {
4793  continue
4794  }
4795  set out_string "| [string map {: | } $line] |"
4796  puts $status_file "$out_string"
4797  }
4798  }
4799  }
4800  } else {
4801  Msg Warning "No timing file found, not a problem if running locally"
4802  }
4803  } elseif {[IsDiamond]} {
4804  set force_rst ""
4805  if {$reset == 1} {
4806  set force_rst "-forceOne"
4807  }
4808  prj_run Map $force_rst
4809  prj_run PAR $force_rst
4810 
4811  # TODO: Check Timing for Diamond
4812  }
4813 }
4814 
4815 # @brief Re-generate the bitstream, for the current IDE and project (Vivado only for the moment). \
4816 # Useful for a Vivado-Vitis project to update the bitstream with a new ELF or to generate a new \
4817 # bootimage (ZYNQ) without running the full workflow.
4818 #
4819 # @param[in] project_name The name of the project
4820 # @param[in] repo_path The main path of the git repository (Default .)
4821 proc GenerateBitstreamOnly {project_name {repo_path .}} {
4822  cd $repo_path
4823  lassign [GetRepoVersions [file normalize ./Top/$project_name] $repo_path] sha
4824  set describe [GetHogDescribe $sha $repo_path]
4825  set dst_dir [file normalize "$repo_path/bin/$project_name\-$describe"]
4826 
4827  cd Projects/$project_name/$project_name.runs/impl_1
4828  Msg Info "Running pre-bitstream..."
4829  source $repo_path/Hog/Tcl/integrated/pre-bitstream.tcl
4830 
4831  Msg Info "Writing bitstream for $project_name..."
4832  open_run impl_1
4833  write_bitstream -force $dst_dir/$project_name-$describe.bit
4834 
4835  Msg Info "Running post-bitstream..."
4836  source $repo_path/Hog/Tcl/integrated/post-bitstream.tcl
4837 }
4838 
4839 # @brief Launch the simulation (Vivado only for the moment)
4840 #
4841 # @param[in] project_name The name of the project
4842 # @param[in] lib_path The path to the simulation libraries
4843 # @param[in] simsets The simulation sets to simulate
4844 # @param[in] repo_path The main path of the git repository
4845 proc LaunchSimulation {project_name lib_path simsets {repo_path .} {scripts_only 0} {compile_only 0}} {
4846  if {[IsVivado]} {
4847  ##################### SIMULATION #######################
4848  set project [file tail $project_name]
4849  set main_sim_folder [file normalize "$repo_path/Projects/$project_name/$project.sim/"]
4850  set simsets_todo ""
4851  if {$simsets != ""} {
4852  dict for {simset sim_dict} $simsets {
4853  lappend simsets_todo $simset
4854  }
4855  Msg Info "Will run only the following simulation's sets (if they exist): $simsets_todo"
4856  }
4857 
4858  if {$scripts_only == 1} {
4859  Msg Info "Only generating simulation scripts, not running simulations..."
4860  }
4861 
4862  if {$compile_only == 1} {
4863  Msg Info "Only compiling simulation libraries, not running simulations..."
4864  }
4865 
4866  set failed []
4867  set success []
4868  set sim_dic [dict create]
4869 
4870  Msg Info "Retrieving list of simulation sets..."
4871  foreach s [get_filesets] {
4872  # Default behaviour, dont use simpass string and simulation is not quiet
4873  set use_simpass_str 0
4874  set quiet_sim ""
4875 
4876  set type [get_property FILESET_TYPE $s]
4877  if {$type eq "SimulationSrcs"} {
4878  if {$simsets_todo != "" && $s ni $simsets_todo} {
4879  Msg Info "Skipping $s as it was not specified with the -simset option..."
4880  continue
4881  }
4882  set sim_dict [DictGet $simsets $s]
4883  set simulator [DictGet $sim_dict "simulator"]
4884  set_property "target_simulator" $simulator [current_project]
4885  set hog_sim_props [DictGet $sim_dict "hog"]
4886  dict for {prop_name prop_val} $hog_sim_props {
4887  # If HOG_SIMPASS_STR is set, use the HOG_SIMPASS_STR string to search for in logs, after simulation is done
4888  if {[string toupper $prop_name] == "HOG_SIMPASS_STR" && $prop_val != ""} {
4889  Msg Info "Setting simulation pass string as '$prop_val'"
4890  set use_simpass_str 1
4891  set simpass_str $prop_val
4892  }
4893  if {[string toupper $prop_name] == "HOG_SILENT_SIM" && $prop_val == 1} {
4894  set quiet_sim " -quiet"
4895  } else {
4896  set quiet_sim ""
4897  }
4898  }
4899 
4900  Msg Info "Creating simulation scripts for $s..."
4901  if {[file exists $repo_path/Top/$project_name/pre-simulation.tcl]} {
4902  Msg Info "Running $repo_path/Top/$project_name/pre-simulation.tcl"
4903  source $repo_path/Top/$project_name/pre-simulation.tcl
4904  }
4905  if {[file exists $repo_path/Top/$project_name/pre-$s-simulation.tcl]} {
4906  Msg Info "Running $repo_path/Top/$project_name/pre-$s-simulation.tcl"
4907  source Running $repo_path/Top/$project_name/pre-$s-simulation.tcl
4908  }
4909  current_fileset -simset $s
4910  set sim_dir $main_sim_folder/$s/behav
4911  set sim_output_logfile $sim_dir/xsim/simulate.log
4912  if {([string tolower $simulator] eq "xsim")} {
4913  set sim_name "xsim:$s"
4914 
4915  set simulation_command "launch_simulation $quiet_sim -simset [get_filesets $s]"
4916  if {[catch $simulation_command log]} {
4917  # Explicitly close xsim simulation, without closing Vivado
4918  close_sim
4919  Msg CriticalWarning "Simulation failed for $s, error info: $::errorInfo"
4920  lappend failed $sim_name
4921  } else {
4922  # Explicitly close xsim simulation, without closing Vivado
4923  close_sim
4924  # If we use simpass_str, search for the string and update return code from simulation if the string is not found in simulation log
4925  if {$use_simpass_str == 1} {
4926  # Get the simulation output log
4927  # Note, xsim should always output simulation.log, hence no check for existence
4928  set file_desc [open $sim_output_logfile r]
4929  set log [read $file_desc]
4930  close $file_desc
4931 
4932  Msg Info "Searching for simulation pass string: '$simpass_str'"
4933  if {[string first $simpass_str $log] == -1} {
4934  Msg CriticalWarning "Simulation failed for $s, error info: '$simpass_str' NOT found!"
4935  lappend failed $sim_name
4936  } else {
4937  # HOG_SIMPASS_STR found, success
4938  lappend success $sim_name
4939  }
4940  } else {
4941  #Rely on simulator exit code
4942  lappend success $sim_name
4943  }
4944  }
4945  } else {
4946  Msg Info "Simulation library path is set to $lib_path."
4947  set simlib_ok 1
4948  if {!([file exists $lib_path])} {
4949  Msg Warning "Could not find simulation library path: $lib_path, $simulator simulation will not work."
4950  set simlib_ok 0
4951  }
4952 
4953  if {$simlib_ok == 1} {
4954  set_property "compxlib.${simulator}_compiled_library_dir" [file normalize $lib_path] [current_project]
4955  launch_simulation -scripts_only -simset [get_filesets $s]
4956  set top_name [get_property TOP $s]
4957  set sim_script [file normalize $sim_dir/$simulator/]
4958  Msg Info "Adding simulation script location $sim_script for $s..."
4959  lappend sim_scripts $sim_script
4960  dict append sim_dic $sim_script $s
4961  } else {
4962  Msg Error "Cannot run $simulator simulations without a valid library path"
4963  exit -1
4964  }
4965  }
4966  }
4967  }
4968 
4969  if {[info exists sim_scripts] && $scripts_only == 0} {
4970  # Only for modelsim/questasim
4971  Msg Info "Generating IP simulation targets, if any..."
4972 
4973  foreach ip [get_ips] {
4974  generate_target simulation -quiet $ip
4975  }
4976 
4977 
4978  Msg Status "\n\n"
4979  Msg Info "====== Starting simulations runs ======"
4980  Msg Status "\n\n"
4981 
4982  foreach s $sim_scripts {
4983  cd $s
4984  set cmd ./compile.sh
4985  Msg Info " ************* Compiling: $s ************* "
4986  lassign [ExecuteRet $cmd] ret log
4987  set sim_name "comp:[dict get $sim_dic $s]"
4988  if {$ret != 0} {
4989  Msg CriticalWarning "Compilation failed for $s, error info: $::errorInfo"
4990  lappend failed $sim_name
4991  } else {
4992  lappend success $sim_name
4993  }
4994  Msg Info "###################### Compilation log starts ######################"
4995  Msg Info "\n\n$log\n\n"
4996  Msg Info "###################### Compilation log ends ######################"
4997 
4998  if {$compile_only == 1} {
4999  continue
5000  }
5001  if {[file exists "./elaborate.sh"] } {
5002  set cmd ./elaborate.sh
5003  Msg Info " ************* Elaborating: $s ************* "
5004  lassign [ExecuteRet $cmd] ret log
5005  set sim_name "elab:[dict get $sim_dic $s]"
5006  if {$ret != 0} {
5007  Msg CriticalWarning "Elaboration failed for $s, error info: $::errorInfo"
5008  lappend failed $sim_name
5009  } else {
5010  lappend success $sim_name
5011  }
5012  Msg Info "###################### Elaboration log starts ######################"
5013  Msg Info "\n\n$log\n\n"
5014  Msg Info "###################### Elaboration log ends ######################"
5015  }
5016  set cmd ./simulate.sh
5017  Msg Info " ************* Simulating: $s ************* "
5018  lassign [ExecuteRet $cmd] ret log
5019 
5020 
5021  # If SIMPASS_STR is set, search log for the string
5022  if {$use_simpass_str == 1} {
5023  if {[string first $simpass_str $log] == -1} {
5024  set ret 1
5025  }
5026  } else {
5027  Msg Debug "Simulation pass string not set, relying on simulator exit code."
5028  }
5029 
5030 
5031  set sim_name "sim:[dict get $sim_dic $s]"
5032  if {$ret != 0} {
5033  Msg CriticalWarning "Simulation failed for $s, error info: $::errorInfo"
5034  lappend failed $sim_name
5035  } else {
5036  lappend success $sim_name
5037  }
5038  Msg Info "###################### Simulation log starts ######################"
5039  Msg Info "\n\n$log\n\n"
5040  Msg Info "###################### Simulation log ends ######################"
5041  }
5042  }
5043 
5044 
5045  if {[llength $success] > 0} {
5046  set successes [join $success "\n"]
5047  Msg Info "The following simulation sets were successful:\n\n$successes\n\n"
5048  }
5049 
5050  if {[llength $failed] > 0} {
5051  set failures [join $failed "\n"]
5052  Msg Error "The following simulation sets have failed:\n\n$failures\n\n"
5053  exit -1
5054  } elseif {[llength $success] > 0} {
5055  Msg Info "All the [llength $success] compilations, elaborations and simulations were successful."
5056  }
5057 
5058  Msg Info "Simulation done."
5059  } else {
5060  Msg Warning "Simulation is not yet supported for [GetIDEName]."
5061  }
5062 }
5063 
5064 # @brief Launch the RTL Analysis, for the current IDE and project
5065 #
5066 # @param[in] run_folder The folder where to store the run results
5067 # @param[in] project_name The name of the project
5068 # @param[in] repo_path The main path of the git repository (Default .)
5069 # @param[in] ext_path The path of source files external to the git repo (Default "")
5070 # @param[in] njobs The number of parallel CPU jobs for the synthesis (Default 4)
5071 proc LaunchRTLAnalysis {} {
5072  if {[IsVivado]} {
5073  Msg Info "Starting RTL Analysis..."
5074  synth_design -rtl -name rtl_1
5075  }
5076 }
5077 
5078 # @brief Launch the synthesis, for the current IDE and project
5079 #
5080 # @param[in] reset Reset the Synthesis run
5081 # @param[in] do_create Recreate the project
5082 # @param[in] run_folder The folder where to store the run results
5083 # @param[in] project_name The name of the project
5084 # @param[in] repo_path The main path of the git repository (Default .)
5085 # @param[in] ext_path The path of source files external to the git repo (Default "")
5086 # @param[in] njobs The number of parallel CPU jobs for the synthesis (Default 4)
5087 proc LaunchSynthesis {reset do_create run_folder project_name {repo_path .} {ext_path ""} {njobs 4}} {
5088  if {[IsXilinx]} {
5089  if {$reset == 1 && $do_create == 0} {
5090  Msg Info "Resetting run before launching synthesis..."
5091  reset_run synth_1
5092  }
5093  if {[IsISE]} {
5094  source $repo_path/Hog/Tcl/integrated/pre-synthesis.tcl
5095  }
5096  launch_runs synth_1 -jobs $njobs -dir $run_folder
5097  wait_on_run synth_1
5098  set prog [get_property PROGRESS [get_runs synth_1]]
5099  set status [get_property STATUS [get_runs synth_1]]
5100  Msg Info "Run: synth_1 progress: $prog, status : $status"
5101  # Copy IP reports in bin/
5102  set ips [get_ips *]
5103 
5104  #go to repository path
5105  cd $repo_path
5106 
5107  lassign [GetRepoVersions [file normalize ./Top/$project_name] $repo_path $ext_path] sha
5108  set describe [GetHogDescribe $sha $repo_path]
5109  Msg Info "Git describe set to $describe"
5110 
5111  foreach ip $ips {
5112  set xci_file [get_property IP_FILE $ip]
5113 
5114  set xci_path [file dirname $xci_file]
5115  set xci_ip_name [file rootname [file tail $xci_file]]
5116  foreach rptfile [glob -nocomplain -directory $xci_path *.rpt] {
5117  file copy $rptfile $repo_path/bin/$project_name-$describe/reports
5118  }
5119  }
5120 
5121  if {$prog ne "100%"} {
5122  Msg Error "Synthesis error, status is: $status"
5123  }
5124  } elseif {[IsQuartus]} {
5125  # TODO: Missing reset
5126  set project [file tail [file rootname $project_name]]
5127 
5128  Msg Info "Number of jobs set to $njobs."
5129  set_global_assignment -name NUM_PARALLEL_PROCESSORS $njobs
5130 
5131 
5132  # keep track of the current revision and of the top level entity name
5133  lassign [GetRepoVersions [file normalize $repo_path/Top/$project_name] $repo_path] sha
5134  set describe [GetHogDescribe $sha $repo_path]
5135  #set top_level_name [ get_global_assignment -name TOP_LEVEL_ENTITY ]
5136  set revision [get_current_revision]
5137 
5138  #run PRE_FLOW_SCRIPT by hand
5139  set tool_and_command [split [get_global_assignment -name PRE_FLOW_SCRIPT_FILE] ":"]
5140  set tool [lindex $tool_and_command 0]
5141  set pre_flow_script [lindex $tool_and_command 1]
5142  set cmd "$tool -t $pre_flow_script quartus_map $project $revision"
5143  #Close project to avoid conflict with pre synthesis script
5144  project_close
5145 
5146  lassign [ExecuteRet {*}$cmd] ret log
5147  if {$ret != 0} {
5148  Msg Warning "Can not execute command $cmd"
5149  Msg Warning "LOG: $log"
5150  } else {
5151  Msg Info "Pre flow script executed!"
5152  }
5153 
5154  # Re-open project
5155  if {![is_project_open]} {
5156  Msg Info "Re-opening project file $project_name..."
5157  project_open $project -current_revision
5158  }
5159 
5160  # Generate IP Files
5161  if {[catch {execute_module -tool ipg -args "--clean"} result]} {
5162  Msg Error "Result: $result\n"
5163  Msg Error "IP Generation failed. See the report file.\n"
5164  } else {
5165  Msg Info "IP Generation was successful for revision $revision.\n"
5166  }
5167 
5168  # Execute synthesis
5169  if {[catch {execute_module -tool map -args "--parallel"} result]} {
5170  Msg Error "Result: $result\n"
5171  Msg Error "Analysis & Synthesis failed. See the report file.\n"
5172  } else {
5173  Msg Info "Analysis & Synthesis was successful for revision $revision.\n"
5174  }
5175  } elseif {[IsLibero]} {
5176  # TODO: Missing Reset
5177  defvar_set -name RWNETLIST_32_64_MIXED_FLOW -value 0
5178 
5179  Msg Info "Run SYNTHESIS..."
5180  if {[catch {run_tool -name {SYNTHESIZE}}]} {
5181  Msg Error "SYNTHESIZE FAILED!"
5182  } else {
5183  Msg Info "SYNTHESIZE PASSED!"
5184  }
5185  } elseif {[IsDiamond]} {
5186  # TODO: Missing Reset
5187  set force_rst ""
5188  if {$reset == 1} {
5189  set force_rst "-forceOne"
5190  }
5191  prj_run Synthesis $force_rst
5192  if {[prj_syn] == "synplify"} {
5193  prj_run Translate $force_rst
5194  }
5195  } else {
5196  Msg Error "Impossible condition. You need to run this in an IDE."
5197  exit 1
5198  }
5199 }
5200 
5201 
5202 # @brief Launch the Vitis build
5203 #
5204 # @param[in] project_name The name of the project
5205 # @param[in] repo_path The main path of the git repository (Default ".")
5206 # @param[in] stage The stage of the build (Default "presynth")
5207 proc LaunchVitisBuild {project_name {repo_path .} {stage "presynth"}} {
5208  set proj_name $project_name
5209  set bin_dir [file normalize "$repo_path/bin"]
5210 
5211  cd $repo_path
5212 
5213  if {[catch {set ws_apps [app list -dict]}]} { set ws_apps "" }
5214  lassign [GetRepoVersions [file normalize $repo_path/Top/$proj_name] $repo_path ] commit version hog_hash hog_ver top_hash top_ver \
5215  libs hashes vers cons_ver cons_hash ext_names ext_hashes xml_hash xml_ver user_ip_repos user_ip_hashes user_ip_vers
5216  set this_commit [GetSHA]
5217  if {$commit == 0 } { set commit $this_commit }
5218  set flavour [GetProjectFlavour $project_name]
5219  lassign [GetDateAndTime $commit] date timee
5220 
5221  foreach app_name [dict keys $ws_apps] {
5222  app config -name $app_name -set build-config Release
5223  }
5224 
5225  WriteGenerics "vitisbuild" $repo_path $proj_name $date $timee $commit $version $top_hash $top_ver $hog_hash $hog_ver $cons_ver $cons_hash $libs \
5226  $vers $hashes $ext_names $ext_hashes $user_ip_repos $user_ip_vers $user_ip_hashes $flavour $xml_ver $xml_hash
5227  foreach app_name [dict keys $ws_apps] { app build -name $app_name }
5228 
5229  if {$stage == "presynth"} {
5230  Msg Info "Done building apps for $project_name..."
5231  # return
5232  }
5233 
5234  Msg Info "Evaluating Git sha for $project_name..."
5235  lassign [GetRepoVersions [file normalize ./Top/$project_name] $repo_path] sha
5236 
5237  set describe [GetHogDescribe $sha $repo_path]
5238  Msg Info "Hog describe set to: $describe"
5239  set dst_dir [file normalize "$bin_dir/$proj_name\-$describe"]
5240  if {![file exists $dst_dir]} {
5241  Msg Info "Creating $dst_dir..."
5242  file mkdir $dst_dir
5243  }
5244 
5245  foreach app_name [dict keys $ws_apps] {
5246  set main_file "$repo_path/Projects/$project_name/vitis_classic/$app_name/Release/$app_name.elf"
5247  set dst_main [file normalize "$dst_dir/[file tail $proj_name]\-$app_name\-$describe.elf"]
5248 
5249  if {![file exists $main_file]} {
5250  Msg Error "No Vitis .elf file found. Perhaps there was an issue building it?"
5251  continue
5252  }
5253 
5254  Msg Info "Copying main binary file $main_file into $dst_main..."
5255  file copy -force $main_file $dst_main
5256  }
5257 }
5258 
5259 # @brief Returns the BIF file path from the properties
5260 #
5261 # @param[in] props A dictionary with the properties defined in Hog.conf
5262 # @param[in] app The application name
5263 # @return The path of the BIF file or empty string if not found
5264 proc GetProcFromProps {repo_path props platform} {
5265  if {[dict exists $props "platform:$platform" "BIF"]} {
5266  set bif_file [dict get $props "platform:$platform" "BIF"]
5267  if {[IsRelativePath $bif_file] == 1} {
5268  set bif_file "$repo_path/$bif_file"
5269  }
5270  return $bif_file
5271  } else {
5272  Msg CriticalWarning "BIF file not found in platform ($platform) properties, skipping bootable image (.bin) generation"
5273  return ""
5274  }
5275 }
5276 
5277 # @brief Returns the BIF file path from the properties
5278 #
5279 # @param[in] props A dictionary with the properties defined in Hog.conf
5280 # @param[in] platform The platform name
5281 # @return The path of the BIF file or empty string if not found
5282 proc GetBifFromProps {repo_path props platform} {
5283  if {[dict exists $props "platform:$platform" "BIF"]} {
5284  set bif_file [dict get $props "platform:$platform" "BIF"]
5285  if {[IsRelativePath $bif_file] == 1} {
5286  set bif_file "$repo_path/$bif_file"
5287  }
5288  return $bif_file
5289  } else {
5290  Msg CriticalWarning "BIF file not found in platform ($platform) properties, skipping bootable image (.bin) generation"
5291  return ""
5292  }
5293 }
5294 
5295 # @brief Returns the part number from the properties
5296 #
5297 # @param[in] props A dictionary with the properties defined in Hog.conf
5298 # @return The part number
5299 proc GetPartFromProps {props} {
5300  if {[dict exists $props "main" "PART"]} {
5301  return [string tolower [dict get $props "main" "PART"]]
5302  } else {
5303  Msg Error "Part number not found in properties"
5304  return ""
5305  }
5306 }
5307 
5308 # @brief Determines the architecture from the part number
5309 #
5310 # @param[in] part The FPGA part number (e.g., xczu4cg-fbvb900-1-e)
5311 # @return String with the architecture (zynqmp, zynq, versal, or unknown)
5312 proc GetArchFromPart {part} {
5313  # Determine architecture based on part prefix
5314  if {[string match "xczu*" $part]} {
5315  return "zynqmp"
5316  } elseif {[string match "xc7z*" $part]} {
5317  return "zynq"
5318  } elseif {[string match "xck26*" $part]} {
5319  return "versal"
5320  } else {
5321  Msg CriticalWarning "Unknown part number: $part"
5322  return "unknown"
5323  }
5324 }
5325 
5326 # @brief Returns a list of application names from the properties
5327 #
5328 # @param[in] props A dictionary with the applications properties defined in Hog.conf
5329 # @param[in] list_names If 1, returns a list of application names rather than a dictionary of applications
5330 proc GetAppsFromProps {props {list_names 0}} {
5331  set prop_apps [dict filter $props key {app:*}]
5332  set apps [dict create]
5333  set app_names [list]
5334 
5335  dict for {app_key app_value} $prop_apps {
5336  if {[regexp {^app:(.+)$} $app_key -> app_name]} {
5337  set app_name [string trim [string tolower $app_name]]
5338  # Convert only the keys of the inner dictionary to lowercase
5339  set app_value_lower [dict create]
5340  dict for {key value} $app_value {
5341  dict set app_value_lower [string tolower $key] $value
5342  }
5343  dict set apps $app_name $app_value_lower
5344  lappend app_names $app_name
5345  }
5346  }
5347  if {$list_names eq 1} {
5348  return $app_names
5349  }
5350  return $apps
5351 }
5352 
5353 # @brief Returns a list of platform names from the properties
5354 #
5355 # @param[in] props A dictionary with the platforms properties
5356 # @param[in] list_names If 1, returns a list of platform names rather than a dictionary of platforms
5357 # @param[in] lower_case If 1, returns the platform names in lowercase
5358 proc GetPlatformsFromProps {props {list_names 0} {lower_case 0}} {
5359  set platforms [dict create]
5360  set platform_names [list]
5361  set prop_platforms [dict filter $props key {platform:*}]
5362 
5363  dict for {platform_key platform_value} $prop_platforms {
5364  if {[regexp {^platform:(.+)$} $platform_key -> platform_name]} {
5365  if {$lower_case == 1} {
5366  set platform_name [string trim [string tolower $platform_name]]
5367  } else {
5368  set platform_name [string trim $platform_name]
5369  }
5370  dict set platforms $platform_name $platform_value
5371  lappend platform_names $platform_name
5372  }
5373  }
5374  if {$list_names eq 1} {
5375  return $platform_names
5376  }
5377  return $platforms
5378 }
5379 
5380 # @brief Generates boot artifacts for the application. If the application targets a soft \
5381 # processor (e.g. microblaze, riscv), the bitstream (.bit) memory is updated to include the ELF file. Otherwise, for \
5382 # applications targeting a hard processor (e.g. zynq, versal), a bootable binary image (.bin) is generated.
5383 #
5384 # @param[in] properties A dictionary with the properties defined in Hog.conf
5385 # @param[in] repo_path The main path of the git repository
5386 # @param[in] proj_dir The directory of the project
5387 # @param[in] bin_dir The directory of the generated binary files
5388 # @param[in] bitfile The bitfile to update
5389 # @param[in] mmi_file The MMI file to update
5390 proc GenerateBootArtifacts {properties repo_path proj_dir bin_dir proj_name describe bitfile mmi_file} {
5391  set elf_list [glob -nocomplain "$bin_dir/*.elf"]
5392  set apps [GetAppsFromProps $properties 0]
5393  set platforms [GetPlatformsFromProps $properties 1]
5394 
5395  if {[llength $elf_list] == 0} {
5396  Msg Warning "No ELF files found in $bin_dir, skipping generation of boot artifacts"
5397  return
5398  }
5399 
5400  if {![file exists $bitfile]} {
5401  Msg Warning "Bitfile $bitfile does not exist, skipping generation of boot artifacts"
5402  return
5403  }
5404 
5405  Msg Info "Generating boot artifacts for $proj_name..."
5406  Msg Info "Found apps: $apps"
5407  Msg Info "Found platforms: $platforms"
5408 
5409 
5410  # Update bitstream with ELF files for the applications targeting a soft processor (e.g. microblaze, riscv)
5411  foreach elf_file $elf_list {
5412  set elf_name [file rootname [file tail $elf_file]]
5413  Msg Info "Found elf name: $elf_name"
5414  Msg Info "Removing $describe from elf"
5415 
5416  # Extract application name from ELF file name
5417  if {[regexp "^(.+)-(.+)-$describe\$" $elf_name -> project_name elf_app]} {
5418  set elf_app [string trim [string tolower $elf_app]]
5419  Msg Info "Found elf_app: $elf_app"
5420  } else {
5421  Msg Error "Could not extract app name from elf file: $elf_name"
5422  continue
5423  }
5424  Msg Info "Removed project name ($project_name) and $describe from elf"
5425 
5426  set app_conf [dict get $apps $elf_app]
5427  set plat [dict get $app_conf "platform"]
5428  set app_proc [dict get $app_conf "proc"]
5429 
5430  # If the application targets a soft processor, update bitstream memory with ELF file
5431  if {[regexp -nocase {microblaze|risc} $app_proc]} {
5432  Msg Info "Detected soft processor ($app_proc) for $elf_app, updating bitstream memory with ELF file..."
5433 
5434  set proc_map [ReadProcMap $proc_map_file]
5435  if {[dict size $proc_map] == 0} {
5436  Msg Error "Failed to read map from $proc_map_file"
5437  continue
5438  }
5439  Msg Info "Found processor map: $proc_map"
5440 
5441  set proc_cell [lindex [split [dict get $proc_map $app_proc] ":"] 1]
5442  Msg Info "Updating memory at processor cell: $proc_cell"
5443 
5444  set update_mem_cmd "updatemem -force -meminfo $mmi_file -data $elf_file -bit $bitfile -proc $proc_cell -out $bitfile"
5445  set ret [catch {exec -ignorestderr {*}$update_mem_cmd >@ stdout} result]
5446  if {$ret != 0} {
5447  Msg Error "Error updating memory for $elf_app: $result"
5448  }
5449  Msg Info "Done updating memory for $elf_app"
5450 
5451  } else {
5452  Msg Info "Detected hard processor ($app_proc) for $elf_app. Make sure the .elf file is defined in the platform ($plat)\
5453  .bif file to be included in the bootable binary image (.bin) generation."
5454  }
5455  }
5456 
5457 
5458  # Generate a bootable binary image for platforms that have a .bif file defined
5459  foreach plat $platforms {
5460  set bif_file [GetBifFromProps $repo_path $properties $plat]
5461  if {$bif_file != ""} {
5462  Msg Info "Generating bootable binary image (.bin) for $plat"
5463  set arch [GetArchFromPart [GetPartFromProps $properties]]
5464  Msg Info "Architecture: $arch"
5465  Msg Info "BIF file: $bif_file"
5466  set bootgen_cmd "bootgen -arch $arch -image $bif_file -o i $bin_dir/$proj_name-$plat-$describe.bin -w on"
5467  set ret [catch {exec -ignorestderr {*}$bootgen_cmd >@ stdout} result]
5468  if {$ret != 0} {
5469  Msg Error "Error generating bootable binary image (.bin) for $elf_app: $result"
5470  }
5471  Msg Info "Done generating bootable binary image (.bin) for $plat"
5472  }
5473  }
5474 }
5475 
5476 # @brief Reads the processor map file
5477 #
5478 # @param[in] proc_map_file The path to the processor map file
5479 # @return A dictionary with the processor map
5480 proc ReadProcMap {proc_map_file} {
5481  set proc_map [dict create]
5482  if {[file exists $proc_map_file]} {
5483  set f [open $proc_map_file "r"]
5484  while {[gets $f line] >= 0} {
5485  Msg Debug "Line: $line"
5486  if {[regexp {^(\S+)\s+(.+)$} $line -> key value]} {
5487  Msg Debug "Found key: $key, value: $value in proc map file"
5488  dict set proc_map $key $value
5489  }
5490  }
5491  close $f
5492  }
5493  return $proc_map
5494 }
5495 
5496 
5497 # Returns the list of all the Hog Projects in the repository
5498 #
5499 # @param[in] repo_path The main path of the git repository
5500 # @param[in] print if 1 print the list of projects in the repository, if 2 does not print test projects
5501 # @param[in] ret_conf if 1 returns conf file rather than list of project names
5502 proc ListProjects {{repo_path .} {print 1} {ret_conf 0}} {
5503  set top_path [file normalize $repo_path/Top]
5504  set confs [findFiles [file normalize $top_path] hog.conf]
5505  set projects ""
5506  set confs [lsort $confs]
5507  set g ""
5508 
5509  foreach c $confs {
5510  set p [Relative $top_path [file dirname $c]]
5511  if {$print >= 1} {
5512  set description [DescriptionFromConf $c]
5513  if {$description eq "test"} {
5514  set description " - Test project"
5515  } elseif {$description ne ""} {
5516  set description " - $description"
5517  }
5518 
5519  if {$print == 1 || $description ne " - Test project"} {
5520  set old_g $g
5521  set g [file dirname $p]
5522  # Print a list of the projects with relative IDE and description (second line comment in hog.conf)
5523  if {$g ne $old_g} {
5524  Msg Status ""
5525  }
5526  Msg Status "$p \([GetIDEFromConf $c]\)$description"
5527  }
5528  }
5529  lappend projects $p
5530  }
5531 
5532  if {$ret_conf == 0} {
5533  # Returns a list of project names
5534  return $projects
5535  } else {
5536  # Return the list of hog.conf with full path
5537  return $confs
5538  }
5539 }
5540 
5541 ## @brief Evaluates the md5 sum of a file
5542 #
5543 # @param[in] file_name: the name of the file of which you want to evaluate the md5 checksum
5544 proc Md5Sum {file_name} {
5545  if {!([file exists $file_name])} {
5546  Msg Warning "Could not find $file_name."
5547  set file_hash -1
5548  }
5549  if {[catch {package require md5 2.0.7} result]} {
5550  Msg Warning "Tcl package md5 version 2.0.7 not found ($result), will use command line..."
5551  set hash [lindex [Execute md5sum $file_name] 0]
5552  } else {
5553  set file_hash [string tolower [md5::md5 -hex -file $file_name]]
5554  }
5555 }
5556 
5557 ## @brief Merges two tcl dictionaries of lists
5558 #
5559 # If the dictionaries contain same keys, the list at the common key is a merging of the two
5560 #
5561 # @param[in] dict0 the name of the first dictionary
5562 # @param[in] dict1 the name of the second dictionary
5563 # @param[in] remove_duplicates if 1, removes duplicates from the merged dictionary (default 1)
5564 #
5565 # @return the merged dictionary
5566 #
5567 proc MergeDict {dict0 dict1 {remove_duplicates 1}} {
5568  set outdict [dict merge $dict1 $dict0]
5569  foreach key [dict keys $dict1] {
5570  if {[dict exists $dict0 $key]} {
5571  set temp_list [dict get $dict1 $key]
5572  foreach item $temp_list {
5573  # Avoid duplication
5574  if {[IsInList $item [DictGet $outdict $key]] == 0 || $remove_duplicates == 0} {
5575  # If the key exists in both dictionaries, append the item to the list
5576  dict lappend outdict $key $item
5577  }
5578  }
5579  }
5580  }
5581  return $outdict
5582 }
5583 
5584 # @brief Move an element in the list to the end
5585 #
5586 # @param[in] inputList the list
5587 # @param[in] element the element to move to the end of the list
5588 proc MoveElementToEnd {inputList element} {
5589  set index [lsearch $inputList $element]
5590  if {$index != -1} {
5591  set inputList [lreplace $inputList $index $index]
5592  lappend inputList $element
5593  }
5594  return $inputList
5595 }
5596 
5597 # @brief Open the project with the corresponding IDE
5598 #
5599 # @param[in] project_file The project_file
5600 # @param[in] repo_path The main path of the git repository
5601 proc OpenProject {project_file repo_path} {
5602  if {[IsXilinx]} {
5603  open_project $project_file
5604  } elseif {[IsQuartus]} {
5605  set project_folder [file dirname $project_file]
5606  set project [file tail [file rootname $project_file]]
5607  if {[file exists $project_folder]} {
5608  cd $project_folder
5609  if {![is_project_open]} {
5610  Msg Info "Opening existing project file $project_file..."
5611  project_open $project -current_revision
5612  }
5613  } else {
5614  Msg Error "Project directory not found for $project_file."
5615  return 1
5616  }
5617  } elseif {[IsLibero]} {
5618  Msg Info "Opening existing project file $project_file..."
5619  cd $repo_path
5620  open_project -file $project_file -do_backup_on_convert 1 -backup_file {./Projects/$project_file.zip}
5621  } elseif {[IsDiamond]} {
5622  Msg Info "Opening existing project file $project_file..."
5623  prj_project open $project_file
5624  } else {
5625  Msg Error "This IDE is currently not supported by Hog. Exiting!"
5626  }
5627 }
5628 
5629 ## @brief Return the operative system name
5630 proc OS {} {
5631  global tcl_platform
5632  return $tcl_platform(platform)
5633 }
5634 
5635 ## @brief Parse JSON file
5636 #
5637 # @param[in] JSON_FILE The JSON File to parse
5638 # @param[in] JSON_KEY The key to extract from the JSON file
5639 #
5640 # @returns -1 in case of failure, JSON KEY VALUE in case of success
5641 #
5642 proc ParseJSON {JSON_FILE JSON_KEY} {
5643  set result [catch {package require Tcl 8.4} TclFound]
5644  if {"$result" != "0"} {
5645  Msg CriticalWarning "Cannot find Tcl package version equal or higher than 8.4.\n $TclFound\n Exiting"
5646  return -1
5647  }
5648 
5649  set result [catch {package require json} JsonFound]
5650  if {"$result" != "0"} {
5651  Msg CriticalWarning "Cannot find JSON package equal or higher than 1.0.\n $JsonFound\n Exiting"
5652  return -1
5653  }
5654  set JsonDict [json::json2dict $JSON_FILE]
5655  set result [catch {dict get $JsonDict $JSON_KEY} RETURNVALUE]
5656  if {"$result" != "0"} {
5657  Msg CriticalWarning "Cannot find $JSON_KEY in $JSON_FILE\n Exiting"
5658  return -1
5659  } else {
5660  return $RETURNVALUE
5661  }
5662 }
5663 
5664 
5665 # @brief Check if a Hog project exists, and if it exists returns the conf file
5666 # if it doesnt returns 0
5667 #
5668 # @brief project The project name
5669 # @brief repo_path The main path of the git repository
5670 proc ProjectExists {project {repo_path .}} {
5671  set index [lsearch -exact [ListProjects $repo_path 0] $project]
5672 
5673  if {$index >= 0} {
5674  # if project exists we return the relative hog.conf file
5675  return [lindex [ListProjects $repo_path 0 1] $index]
5676  } else {
5677  Msg Error "Project $project not found in repository $repo_path"
5678  return 1
5679  }
5680 }
5681 
5682 ## Read a property configuration file and returns a dictionary
5683 #
5684 # @param[in] file_name the configuration file
5685 #
5686 # @return The dictionary
5687 #
5688 proc ReadConf {file_name} {
5689  if {[catch {package require inifile 0.2.3} ERROR]} {
5690  Msg Error "Could not find inifile package version 0.2.3 or higher.\n
5691  To use ghdl, libero or diamond with Hog, you need to install the tcllib package\n
5692  You can install it with 'sudo apt install tcllib' on Debian/Ubuntu or 'sudo dnf install tcllib' on Fedora/RedHat/CentOs."
5693  }
5694 
5695 
5696  ::ini::commentchar "#"
5697  set f [::ini::open $file_name]
5698  set properties [dict create]
5699  foreach sec [::ini::sections $f] {
5700  set new_sec $sec
5701  if {$new_sec == "files"} {
5702  continue
5703  }
5704  set key_pairs [::ini::get $f $sec]
5705  #manipulate strings here:
5706  regsub -all {\{\"} $key_pairs "\{" key_pairs
5707  regsub -all {\"\}} $key_pairs "\}" key_pairs
5708 
5709  dict set properties $new_sec [dict create {*}$key_pairs]
5710  }
5711 
5712  ::ini::close $f
5713 
5714  return $properties
5715 }
5716 
5717 ## @brief Function used to read the list of files generated at creation time by tcl scripts in Project/proj/.hog/extra.files
5718 #
5719 # @param[in] extra_file_name the path to the extra.files file
5720 # @returns a dictionary with the full name of the files as key and a SHA as value
5721 #
5722 proc ReadExtraFileList {extra_file_name} {
5723  set extra_file_dict [dict create]
5724  if {[file exists $extra_file_name]} {
5725  set file [open $extra_file_name "r"]
5726  set file_data [read $file]
5727  close $file
5728 
5729  set data [split $file_data "\n"]
5730  foreach line $data {
5731  if {![regexp {^ *$} $line] & ![regexp {^ *\#} $line]} {
5732  set ip_and_md5 [regexp -all -inline {\S+} $line]
5733  dict lappend extra_file_dict "[lindex $ip_and_md5 0]" "[lindex $ip_and_md5 1]"
5734  }
5735  }
5736  }
5737  return $extra_file_dict
5738 }
5739 
5740 
5741 # @brief Read a list file and return a list of three dictionaries
5742 #
5743 # Additional information is provided with text separated from the file name with one or more spaces
5744 #
5745 # @param[in] args The arguments are <list_file> <path> [options]
5746 # * list_file file containing vhdl list with optional properties
5747 # * path path the vhdl file are referred to in the list file
5748 # Options:
5749 # * -lib <library> name of the library files will be added to, if not given will be extracted from the file name
5750 # * -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.
5751 # 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.
5752 #
5753 # @return a list of 3 dictionaries:
5754 # "libraries" has library name as keys and a list of filenames as values,
5755 # "properties" has as file names as keys and a list of properties as values
5756 # "filesets" has the fileset' names as keys and the list of associated libraries as values.
5757 proc ReadListFile {args} {
5758  if {[IsQuartus]} {
5759  load_package report
5760  if {[catch {package require cmdline} ERROR]} {
5761  puts "$ERROR\n If you are running this script on tclsh, you can fix this by installing 'tcllib'"
5762  return 1
5763  }
5764  }
5765  # tclint-disable line-length
5766  set parameters {
5767  {lib.arg "" "The name of the library files will be added to, if not given will be extracted from the file name."}
5768  {fileset.arg "" "The name of the library, from the main list file"}
5769  {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."}
5770  {print_log "If set, will use PrintFileTree for the VIEW directive"}
5771  {indent.arg "" "Used to indent files with the VIEW directive"}
5772  }
5773  # tclint-enable line-length
5774  set usage "USAGE: ReadListFile \[options\] <list file> <path>"
5775  if {[catch {array set options [cmdline::getoptions args $parameters $usage]}] || [llength $args] != 2} {
5776  Msg CriticalWarning "[cmdline::usage $parameters $usage]"
5777  return
5778  }
5779 
5780 
5781  set list_file [lindex $args 0]
5782  set path [lindex $args 1]
5783  set sha_mode $options(sha_mode)
5784  set lib $options(lib)
5785  set fileset $options(fileset)
5786  set print_log $options(print_log)
5787  set indent $options(indent)
5788 
5789  if {$sha_mode == 1} {
5790  set sha_mode_opt "-sha_mode"
5791  } else {
5792  set sha_mode_opt ""
5793  }
5794 
5795  if {$print_log == 1} {
5796  set print_log_opt "-print_log"
5797  } else {
5798  set print_log_opt ""
5799  }
5800 
5801  # if no library is given, work it out from the file name
5802  if {$lib eq ""} {
5803  set lib [file rootname [file tail $list_file]]
5804  }
5805  set fp [open $list_file r]
5806  set file_data [read $fp]
5807  close $fp
5808  set list_file_ext [file extension $list_file]
5809  switch $list_file_ext {
5810  .sim {
5811  if {$fileset eq ""} {
5812  # If fileset is empty, use the library name for .sim file
5813  set fileset "$lib"
5814  }
5815  }
5816  .con {
5817  set fileset "constrs_1"
5818  }
5819  default {
5820  set fileset "sources_1"
5821  }
5822  }
5823 
5824  set libraries [dict create]
5825  set filesets [dict create]
5826  set properties [dict create]
5827  # Process data file
5828  set data [split $file_data "\n"]
5829  set data [ExtractFilesSection $data]
5830  set n [llength $data]
5831  set last_printed ""
5832  if {$print_log == 1} {
5833  if {$indent eq ""} {
5834  set list_file_rel [file tail $list_file]
5835  Msg Status "\n$list_file_rel"
5836  }
5837  set last_printed [PrintFileTree $data $path "$indent"]
5838  }
5839  Msg Debug "$n lines read from $list_file."
5840  set cnt 0
5841 
5842  foreach line $data {
5843  # Exclude empty lines and comments
5844  if {![regexp {^[\t\s]*$} $line] & ![regexp {^[\t\s]*\#} $line]} {
5845  set file_and_prop [regexp -all -inline {\S+} $line]
5846  set srcfile [lindex $file_and_prop 0]
5847  set srcfile "$path/$srcfile"
5848 
5849  set srcfiles [glob -nocomplain $srcfile]
5850 
5851  # glob the file list for wildcards
5852  if {$srcfiles != $srcfile && ![string equal $srcfiles ""]} {
5853  Msg Debug "Wildcard source expanded from $srcfile to $srcfiles"
5854  } else {
5855  if {![file exists $srcfile]} {
5856  if {$print_log == 0} {
5857  Msg CriticalWarning "File: $srcfile (from list file: $list_file) does not exist."
5858  }
5859  continue
5860  }
5861  }
5862 
5863  foreach vhdlfile $srcfiles {
5864  if {[file exists $vhdlfile]} {
5865  set vhdlfile [file normalize $vhdlfile]
5866  set extension [file extension $vhdlfile]
5867  ### Set file properties
5868  set prop [lrange $file_and_prop 1 end]
5869 
5870  # The next lines should be inside the case for recursive list files, also we should check the allowed properties for the .src as well
5871  set library [lindex [regexp -inline {\ylib\s*=\s*(.+?)\y.*} $prop] 1]
5872  if {$library == ""} {
5873  set library $lib
5874  }
5875 
5876  if {$extension == $list_file_ext} {
5877  # Deal with recursive list files
5878  # In the next regex we use \S+ instead of .+? because we want to include forward slashes
5879  set ref_path [lindex [regexp -inline {\ypath\s*=\s*(\S+).*} $prop] 1]
5880  if {$ref_path eq ""} {
5881  set ref_path $path
5882  } else {
5883  set ref_path [file normalize $path/$ref_path]
5884  }
5885  Msg Debug "List file $vhdlfile found in list file, recursively opening it using path \"$ref_path\"..."
5886  if {$print_log == 1} {
5887  if {[file normalize $last_printed] ne [file normalize $vhdlfile]} {
5888  Msg Status "$indent Inside [file tail $vhdlfile]:"
5889  set last_printed ""
5890  }
5891  }
5892  lassign [ReadListFile {*}"-indent \" $indent\" -lib $library -fileset $fileset $sha_mode_opt $print_log_opt $vhdlfile $ref_path"] l p fs
5893  set libraries [MergeDict $l $libraries]
5894  set properties [MergeDict $p $properties]
5895  set filesets [MergeDict $fs $filesets]
5896  } elseif {[lsearch {.src .sim .con ReadExtraFileList} $extension] >= 0} {
5897  # Not supported extensions
5898  Msg Error "$vhdlfile cannot be included into $list_file, $extension files must be included into $extension files."
5899  } else {
5900  # Deal with single files
5901  regsub -all " *= *" $prop "=" prop
5902  # Fill property dictionary
5903  foreach p $prop {
5904  # No need to append the lib= property
5905  if {[string first "lib=" $p] == -1} {
5906  # Get property name up to the = (for QSYS properties at the moment)
5907  set pos [string first "=" $p]
5908  if {$pos == -1} {
5909  set prop_name $p
5910  } else {
5911  set prop_name [string range $p 0 [expr {$pos - 1}]]
5912  }
5913  if {[IsInList $prop_name [DictGet [ALLOWED_PROPS] $extension]] || [string first "top" $p] == 0 || $list_file_ext eq ".ipb"} {
5914  dict lappend properties $vhdlfile $p
5915  Msg Debug "Adding property $p to $vhdlfile..."
5916  } elseif {$list_file_ext != ".ipb"} {
5917  Msg Warning "Setting Property $p is not supported for file $vhdlfile or it is already its default. \
5918  The allowed properties for this file type are \[ [DictGet [ALLOWED_PROPS] $extension]\]"
5919  }
5920  }
5921  }
5922  if {[lsearch {.xci .ip .bd .xcix} $extension] >= 0} {
5923  # Adding IP library
5924  set lib_name "ips.src"
5925  } elseif {[IsInList $extension {.vhd .vhdl}] || $list_file_ext == ".sim"} {
5926  # VHDL files and simulation
5927  if {![IsInList $extension {.vhd .vhdl}]} {
5928  set lib_name "others.sim"
5929  } else {
5930  set lib_name "$library$list_file_ext"
5931  }
5932  } elseif {$list_file_ext == ".con"} {
5933  set lib_name "sources.con"
5934  } elseif {$list_file_ext == ".ipb"} {
5935  set lib_name "xml.ipb"
5936  } elseif { [IsInList $list_file_ext {.src}] && [IsInList $extension {.c .cpp .h .hpp}] } {
5937  # Adding Vitis library
5938  set lib_name "$library$list_file_ext"
5939  } else {
5940  # Other files are stored in the OTHER dictionary from vivado (no library assignment)
5941  set lib_name "others.src"
5942  }
5943 
5944  Msg Debug "Appending $vhdlfile to $lib_name list..."
5945  dict lappend libraries $lib_name $vhdlfile
5946  if {$sha_mode != 0 && [file type $vhdlfile] eq "link"} {
5947  #if the file is a link, also add the linked file in sha mode
5948  set real_file [GetLinkedFile $vhdlfile]
5949  dict lappend libraries $lib_name $real_file
5950  Msg Debug "File $vhdlfile is a soft link, also adding the real file: $real_file"
5951  }
5952 
5953 
5954  # Create the fileset (if not already) and append the library
5955  if {[dict exists $filesets $fileset] == 0} {
5956  # Fileset has not been defined yet, adding to dictionary...
5957  Msg Debug "Adding $fileset to the fileset dictionary..."
5958  Msg Debug "Adding library $lib_name to fileset $fileset..."
5959  dict set filesets $fileset $lib_name
5960  } else {
5961  # Fileset already exist in dictionary, append library to list, if not already there
5962  if {[IsInList $lib_name [DictGet $filesets $fileset]] == 0} {
5963  Msg Debug "Adding library $lib_name to fileset $fileset..."
5964  dict lappend filesets $fileset $lib_name
5965  }
5966  }
5967  }
5968  incr cnt
5969  } else {
5970  Msg CriticalWarning "File $vhdlfile not found."
5971  }
5972  }
5973  }
5974  }
5975 
5976  if {$sha_mode != 0} {
5977  #In SHA mode we also need to add the list file to the list
5978  if {$list_file_ext eq ".ipb"} {
5979  set sha_lib "xml.ipb"
5980  } else {
5981  set sha_lib $lib$list_file_ext
5982  }
5983  dict lappend libraries $sha_lib [file normalize $list_file]
5984  if {[file type $list_file] eq "link"} {
5985  #if the file is a link, also add the linked file
5986  set real_file [GetLinkedFile $list_file]
5987  dict lappend libraries $lib$list_file_ext $real_file
5988  Msg Debug "List file $list_file is a soft link, also adding the real file: $real_file"
5989  }
5990  }
5991  return [list $libraries $properties $filesets]
5992 }
5993 
5994 ## @brief Returns the destination path relative to base
5995 #
5996 # @param[in] base the path with respect to witch the dst path is calculated
5997 # @param[in] dst the path to be calculated with respect to base
5998 #
5999 proc Relative {base dst} {
6000  if {![string equal [file pathtype $base] [file pathtype $dst]]} {
6001  Msg CriticalWarning "Unable to compute relation for paths of different path types: [file pathtype $base] vs. [file pathtype $dst], ($base vs. $dst)"
6002  return ""
6003  }
6004 
6005  set base [file normalize [file join [pwd] $base]]
6006  set dst [file normalize [file join [pwd] $dst]]
6007 
6008  set save $dst
6009  set base [file split $base]
6010  set dst [file split $dst]
6011 
6012  while {[string equal [lindex $dst 0] [lindex $base 0]]} {
6013  set dst [lrange $dst 1 end]
6014  set base [lrange $base 1 end]
6015  if {![llength $dst]} {break}
6016  }
6017 
6018  set dstlen [llength $dst]
6019  set baselen [llength $base]
6020 
6021  if {($dstlen == 0) && ($baselen == 0)} {
6022  set dst .
6023  } else {
6024  while {$baselen > 0} {
6025  set dst [linsert $dst 0 ..]
6026  incr baselen -1
6027  }
6028  set dst [eval [linsert $dst 0 file join]]
6029  }
6030 
6031  return $dst
6032 }
6033 
6034 ## @brief Returns the path of filePath relative to pathName
6035 #
6036 # @param[in] pathName the path with respect to which the returned path is calculated
6037 # @param[in] filePath the path of filePath
6038 #
6039 proc RelativeLocal {pathName filePath} {
6040  if {[string first [file normalize $pathName] [file normalize $filePath]] != -1} {
6041  return [Relative $pathName $filePath]
6042  } else {
6043  return ""
6044  }
6045 }
6046 
6047 ## @brief Remove duplicates in a dictionary
6048 #
6049 # @param[in] mydict the input dictionary
6050 #
6051 # @return the dictionary stripped of duplicates
6052 proc RemoveDuplicates {mydict} {
6053  set new_dict [dict create]
6054  foreach key [dict keys $mydict] {
6055  set values [DictGet $mydict $key]
6056  foreach value $values {
6057  set idxs [lreverse [lreplace [lsearch -exact -all $values $value] 0 0]]
6058  foreach idx $idxs {
6059  set values [lreplace $values $idx $idx]
6060  }
6061  }
6062  dict set new_dict $key $values
6063  }
6064  return $new_dict
6065 }
6066 
6067 ## Reset files in the repository
6068 #
6069 # @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)
6070 #
6071 # @return Nothing
6072 #
6073 proc ResetRepoFiles {reset_file} {
6074  if {[file exists $reset_file]} {
6075  Msg Info "Found $reset_file, opening it..."
6076  set fp [open $reset_file r]
6077  set wild_cards [lsearch -all -inline -not -regexp [split [read $fp] "\n"] "^ *$"]
6078  close $fp
6079  Msg Info "Found the following files/wild cards to restore if modified: $wild_cards..."
6080  foreach w $wild_cards {
6081  set mod_files [GetModifiedFiles "." $w]
6082  if {[llength $mod_files] > 0} {
6083  Msg Info "Found modified $w files: $mod_files, will restore them..."
6084  RestoreModifiedFiles "." $w
6085  } else {
6086  Msg Info "No modified $w files found."
6087  }
6088  }
6089  }
6090 }
6091 
6092 ## @brief Restore with checkout -- the files specified in pattern
6093 #
6094 # @param[in] repo_path the path of the git repository
6095 # @param[in] pattern the pattern with wildcards that files should match
6096 #
6097 proc RestoreModifiedFiles {{repo_path "."} {pattern "."}} {
6098  set old_path [pwd]
6099  cd $repo_path
6100  set ret [Git checkout $pattern]
6101  cd $old_path
6102  return
6103 }
6104 
6105 ## Search the Hog projects inside a directory
6106 #
6107 # @param[in] dir The directory to search
6108 #
6109 # @return The list of projects
6110 #
6111 proc SearchHogProjects {dir} {
6112  set projects_list {}
6113  if {[file exists $dir]} {
6114  if {[file isdirectory $dir]} {
6115  foreach proj_dir [glob -nocomplain -types d $dir/*] {
6116  if {![regexp {^.*Top/+(.*)$} $proj_dir dummy proj_name]} {
6117  Msg Warning "Could not parse Top directory $dir"
6118  break
6119  }
6120  if {[file exists "$proj_dir/hog.conf"]} {
6121  lappend projects_list $proj_name
6122  } else {
6123  foreach p [SearchHogProjects $proj_dir] {
6124  lappend projects_list $p
6125  }
6126  }
6127  }
6128  } else {
6129  Msg Error "Input $dir is not a directory!"
6130  }
6131  } else {
6132  Msg Error "Directory $dir doesn't exist!"
6133  }
6134  return $projects_list
6135 }
6136 
6137 ## @brief Sets the generics in all the sim.conf simulation file sets
6138 #
6139 # @param[in] repo_path: the top folder of the projectThe path to the main git repository
6140 # @param[in] proj_dir: the top folder of the project
6141 # @param[in] target: software target(vivado, questa)
6142 #
6143 proc SetGenericsSimulation {repo_path proj_dir target} {
6144  set top_dir "$repo_path/Top/$proj_dir"
6145  set simsets [get_filesets]
6146  if {$simsets != ""} {
6147  foreach simset $simsets {
6148  # Only for simulation filesets
6149  if {[get_property FILESET_TYPE $simset] != "SimulationSrcs"} {
6150  continue
6151  }
6152 
6153  set merged_generics_dict [dict create]
6154  # Get generics from sim.conf file
6155  set simset_dict [DictGet [GetSimSets $proj_dir $repo_path $simset] $simset]
6156  set hog_generics [GetGenericsFromConf $proj_dir]
6157  set simset_generics [DictGet $simset_dict "generics"]
6158  set merged_generics_dict [MergeDict $merged_generics_dict $simset_generics 0]
6159  set generic_str [GenericToSimulatorString $merged_generics_dict $target]
6160  set_property generic $generic_str [get_filesets $simset]
6161  Msg Debug "Setting generics $generic_str for simulator $target\
6162  and simulation file-set $simset..."
6163  }
6164  }
6165 }
6166 
6167 ## @brief set the top module as top module in the chosen fileset
6168 #
6169 # It automatically recognises the IDE
6170 #
6171 # @param[out] top_module Name of the top module
6172 # @param[in] fileset The name of the fileset
6173 #
6174 proc SetTopProperty {top_module fileset} {
6175  Msg Info "Setting TOP property to $top_module module"
6176  if {[IsXilinx]} {
6177  #VIVADO_ONLY
6178  set_property "top" $top_module [get_filesets $fileset]
6179  } elseif {[IsQuartus]} {
6180  #QUARTUS ONLY
6181  set_global_assignment -name TOP_LEVEL_ENTITY $top_module
6182  } elseif {[IsLibero]} {
6183  set_root -module $top_module
6184  } elseif {[IsDiamond]} {
6185  prj_impl option top $top_module
6186  }
6187 }
6188 
6189 ## @brief Returns a list of Vivado properties that expect a PATH for value
6190 proc VIVADO_PATH_PROPERTIES {} {
6191  return {"\.*\.TCL\.PRE$" "^.*\.TCL\.POST$" "^RQS_FILES$" "^INCREMENTAL\_CHECKPOINT$" "NOC\_SOLUTION\_FILE"}
6192 }
6193 
6194 ## @brief Returns a list of Vitis properties that expect a PATH for value
6195 proc VITIS_PATH_PROPERTIES {} {
6196  return {"^HW$" "^XPFM$" "^LINKER-SCRIPT$" "^LIBRARIES$" "^LIBRARY-SEARCH-PATH$"}
6197 }
6198 
6199 ## @brief Write a property configuration file from a dictionary
6200 #
6201 # @param[in] file_name the configuration file
6202 # @param[in] config the configuration dictionary
6203 # @param[in] comment comment to add at the beginning of configuration file
6204 #
6205 #
6206 proc WriteConf {file_name config {comment ""}} {
6207  if {[catch {package require inifile 0.2.3} ERROR]} {
6208  puts "$ERROR\n If you are running this script on tclsh, you can fix this by installing 'tcllib'"
6209  return 1
6210  }
6211 
6212  ::ini::commentchar "#"
6213  set f [::ini::open $file_name w]
6214 
6215  foreach sec [dict keys $config] {
6216  set section [dict get $config $sec]
6217  dict for {p v} $section {
6218  if {[string trim $v] == ""} {
6219  Msg Warning "Property $p has empty value. Skipping..."
6220  continue
6221  }
6222  ::ini::set $f $sec $p $v
6223  }
6224  }
6225 
6226  #write comment before the first section (first line of file)
6227  if {![string equal "$comment" ""]} {
6228  ::ini::comment $f [lindex [::ini::sections $f] 0] "" $comment
6229  set hog_header "Generated by Hog on [clock format [clock seconds] -format "%Y-%m-%d %H:%M:%S"]"
6230  ::ini::comment $f [lindex [::ini::sections $f] 0] "" $hog_header
6231  }
6232  ::ini::commit $f
6233 
6234  ::ini::close $f
6235 }
6236 
6237 ## Set the generics property
6238 #
6239 # @param[in] mode if it's "create", the function will assume the project is being created
6240 # @param[in] repo_path The path to the main git repository
6241 # @param[in] design The name of the design
6242 
6243 # @param[in] list of variables to be written in the generics in the usual order
6244 
6245 proc WriteGenerics {mode repo_path design date timee\
6246  commit version top_hash top_ver hog_hash hog_ver \
6247  cons_ver cons_hash libs vers hashes ext_names ext_hashes \
6248  user_ip_repos user_ip_vers user_ip_hashes flavour {xml_ver ""} {xml_hash ""}} {
6249  Msg Info "Passing parameters/generics to project's top module..."
6250  ##### Passing Hog generic to top file
6251  # set global generic variables
6252  set generic_string [concat \
6253  "GLOBAL_DATE=[FormatGeneric $date]" \
6254  "GLOBAL_TIME=[FormatGeneric $timee]" \
6255  "GLOBAL_VER=[FormatGeneric $version]" \
6256  "GLOBAL_SHA=[FormatGeneric $commit]" \
6257  "TOP_SHA=[FormatGeneric $top_hash]" \
6258  "TOP_VER=[FormatGeneric $top_ver]" \
6259  "HOG_SHA=[FormatGeneric $hog_hash]" \
6260  "HOG_VER=[FormatGeneric $hog_ver]" \
6261  "CON_VER=[FormatGeneric $cons_ver]" \
6262  "CON_SHA=[FormatGeneric $cons_hash]"]
6263  # xml hash
6264  if {$xml_hash != "" && $xml_ver != ""} {
6265  lappend generic_string \
6266  "XML_VER=[FormatGeneric $xml_ver]" \
6267  "XML_SHA=[FormatGeneric $xml_hash]"
6268  }
6269  #set project specific lists
6270  foreach l $libs v $vers h $hashes {
6271  set ver "[string toupper $l]_VER=[FormatGeneric $v]"
6272  set hash "[string toupper $l]_SHA=[FormatGeneric $h]"
6273  lappend generic_string "$ver" "$hash"
6274  }
6275 
6276  foreach e $ext_names h $ext_hashes {
6277  set hash "[string toupper $e]_SHA=[FormatGeneric $h]"
6278  lappend generic_string "$hash"
6279  }
6280 
6281  foreach repo $user_ip_repos v $user_ip_vers h $user_ip_hashes {
6282  set repo_name [file tail $repo]
6283  set ver "[string toupper $repo_name]_VER=[FormatGeneric $v]"
6284  set hash "[string toupper $repo_name]_SHA=[FormatGeneric $h]"
6285  lappend generic_string "$ver" "$hash"
6286  }
6287 
6288  if {$flavour != -1} {
6289  lappend generic_string "FLAVOUR=$flavour"
6290  }
6291 
6292  # Dealing with project generics in Vivado
6293  if {[IsVivado] || [IsVitisClassic]} {
6294  set prj_generics [GenericToSimulatorString [GetGenericsFromConf $design] "Vivado"]
6295  set generic_string "$prj_generics $generic_string"
6296  }
6297 
6298  # Extract the generics from the top level source file
6299  if {[IsXilinx]} {
6300  # Top File can be retrieved only at creation time or in ISE
6301  if {$mode == "create" || [IsISE]} {
6302  set top_file [GetTopFile]
6303  set top_name [GetTopModule]
6304  if {[file exists $top_file]} {
6305  set generics [GetFileGenerics $top_file $top_name]
6306 
6307  Msg Debug "Found top level generics $generics in $top_file"
6308 
6309  set filtered_generic_string ""
6310 
6311  foreach generic_to_set [split [string trim $generic_string]] {
6312  set key [lindex [split $generic_to_set "="] 0]
6313  if {[dict exists $generics $key]} {
6314  Msg Debug "Hog generic $key found in $top_name"
6315  lappend filtered_generic_string "$generic_to_set"
6316  } else {
6317  Msg Warning "Generic $key is passed by Hog but is NOT present in $top_name."
6318  }
6319  }
6320 
6321  # only filter in ISE
6322  if {[IsISE]} {
6323  set generic_string $filtered_generic_string
6324  }
6325  }
6326  }
6327 
6328  set_property generic $generic_string [current_fileset]
6329  Msg Info "Setting parameters/generics..."
6330  Msg Debug "Detailed parameters/generics: $generic_string"
6331 
6332 
6333  if {[IsVivado]} {
6334  # Dealing with project generics in Simulators
6335  set simulator [get_property target_simulator [current_project]]
6336  if {$mode == "create"} {
6337  SetGenericsSimulation $repo_path $design $simulator
6338  }
6339 
6340  WriteGenericsToBdIPs $mode $repo_path $design $generic_string
6341  }
6342  } elseif {[IsSynplify]} {
6343  Msg Info "Setting Synplify parameters/generics one by one..."
6344  foreach generic $generic_string {
6345  Msg Debug "Setting Synplify generic: $generic"
6346  set_option -hdl_param -set "$generic"
6347  }
6348  } elseif {[IsDiamond]} {
6349  Msg Info "Setting Diamond parameters/generics one by one..."
6350  prj_impl option -impl Implementation0 HDL_PARAM "$generic_string"
6351  } elseif {[IsVitisClassic]} {
6352  if {[catch {set ws_apps [app list -dict]}]} { set ws_apps "" }
6353 
6354  foreach app_name [dict keys $ws_apps] {
6355  set defined_symbols [app config -name $app_name -get define-compiler-symbols]
6356  foreach generic_to_set [split [string trim $generic_string]] {
6357  set key [lindex [split $generic_to_set "="] 0]
6358  set value [lindex [split $generic_to_set "="] 1]
6359  if {[string match "32'h*" $value]} {
6360  set value [string map {"32'h" "0x"} $value]
6361  }
6362 
6363  foreach symbol [split $defined_symbols ";"] {
6364  if {[string match "$key=*" $symbol]} {
6365  Msg Debug "Generic $key found in $app_name, removing it..."
6366  app config -name $app_name -remove define-compiler-symbols "$symbol"
6367  }
6368  }
6369 
6370  Msg Info "Setting Vitis parameters/generics for app $app_name: $key=$value"
6371  app config -name $app_name define-compiler-symbols "$key=$value"
6372  }
6373  }
6374  }
6375 }
6376 
6377 ## @brief Applies generic values to IPs within block designs
6378 #
6379 # @param[in] mode create: to write the generics at creation time. synth to write at synthesis time
6380 # @param[in] repo_path The main path of the git repository
6381 # @param[in] proj The project name
6382 # @param[in] generic_string the string containing the generics to be applied
6383 proc WriteGenericsToBdIPs {mode repo_path proj generic_string} {
6384  Msg Debug "Parameters/generics passed to WriteGenericsToIP: $generic_string"
6385 
6386  set bd_ip_generics false
6387  set properties [ReadConf [lindex [GetConfFiles $repo_path/Top/$proj] 0]]
6388  if {[dict exists $properties "hog"]} {
6389  set propDict [dict get $properties "hog"]
6390  if {[dict exists $propDict "PASS_GENERICS_TO_BD_IPS"]} {
6391  set bd_ip_generics [dict get $propDict "PASS_GENERICS_TO_BD_IPS"]
6392  }
6393  }
6394 
6395  if {[string compare [string tolower $bd_ip_generics] "false"] == 0} {
6396  return
6397  }
6398 
6399  if {$mode == "synth"} {
6400  Msg Info "Attempting to apply generics pre-synthesis..."
6401  set PARENT_PRJ [get_property "PARENT.PROJECT_PATH" [current_project]]
6402  set workaround [open "$repo_path/Projects/$proj/.hog/presynth_workaround.tcl" "w"]
6403  puts $workaround "source \[lindex \$argv 0\];"
6404  puts $workaround "open_project \[lindex \$argv 1\];"
6405  puts $workaround "WriteGenericsToBdIPs \[lindex \$argv 2\] \[lindex \$argv 3\] \[lindex \$argv 4\] \[lindex \$argv 5\];"
6406  puts $workaround "close_project"
6407  close $workaround
6408  if {
6409  [catch {
6410  exec vivado -mode batch -source $repo_path/Projects/$proj/.hog/presynth_workaround.tcl \
6411  -tclargs $repo_path/Hog/Tcl/hog.tcl $PARENT_PRJ \
6412  "childprocess" $repo_path $proj $generic_string
6413  } errMsg] != 0
6414  } {
6415  Msg Error "Encountered an error while attempting workaround: $errMsg"
6416  }
6417  file delete $repo_path/Projects/$proj/.hog/presynth_workaround.tcl
6418  ResetRepoFiles "$repo_path/Projects/hog_reset_files"
6419  Msg Info "Done applying generics pre-synthesis."
6420  return
6421  }
6422 
6423  Msg Info "Looking for IPs to add generics to..."
6424  set ips_generic_string ""
6425  foreach generic_to_set [split [string trim $generic_string]] {
6426  set key [lindex [split $generic_to_set "="] 0]
6427  set value [lindex [split $generic_to_set "="] 1]
6428  append ips_generic_string "CONFIG.$key $value "
6429  }
6430 
6431 
6432  if {[string compare [string tolower $bd_ip_generics] "true"] == 0} {
6433  set ip_regex ".*"
6434  } else {
6435  set ip_regex $bd_ip_generics
6436  }
6437 
6438  set ip_list [get_ips -regex $ip_regex]
6439  Msg Debug "IPs found with regex \{$ip_regex\}: $ip_list"
6440 
6441  set regen_targets {}
6442 
6443  foreach {ip} $ip_list {
6444  set WARN_ABOUT_IP false
6445  set ip_props [list_property [get_ips $ip]]
6446 
6447  #Not sure if this is needed, but it's here to prevent potential errors with get_property
6448  if {[lsearch -exact $ip_props "IS_BD_CONTEXT"] == -1} {
6449  continue
6450  }
6451 
6452  if {[get_property "IS_BD_CONTEXT" [get_ips $ip]] eq "1"} {
6453  foreach {ip_prop} $ip_props {
6454  if {[dict exists $ips_generic_string $ip_prop]} {
6455  if {$WARN_ABOUT_IP == false} {
6456  lappend regen_targets [get_property SCOPE [get_ips $ip]]
6457  Msg Warning "The ip \{$ip\} contains generics that are set by Hog.\
6458  If this is IP is apart of a block design, the .bd file may contain stale, unused, values.\
6459  Hog will always apply the most up-to-date values to the IP during synthesis,\
6460  however these values may or may not be reflected in the .bd file."
6461  set WARN_ABOUT_IP true
6462  }
6463 
6464  # vivado is annoying about the format when setting generics for ips
6465  # this tries to find and set the format to what vivado likes
6466  set xci_path [get_property IP_FILE [get_ips $ip]]
6467  set generic_format [GetGenericFormatFromXci $ip_prop $xci_path]
6468  if {[string equal $generic_format "ERROR"]} {
6469  Msg Warning "Could not find format for generic $ip_prop in IP $ip. Skipping..."
6470  continue
6471  }
6472 
6473  set value_to_set [dict get $ips_generic_string $ip_prop]
6474  switch -exact $generic_format {
6475  "long" {
6476  if {[string match "32'h*" $value_to_set]} {
6477  scan [string map {"32'h" ""} $value_to_set] "%x" value_to_set
6478  }
6479  }
6480  "bool" {
6481  set value_to_set [expr {$value_to_set ? "true" : "false"}]
6482  }
6483  "float" {
6484  if {[string match "32'h*" $value_to_set]} {
6485  binary scan [binary format H* [string map {"32'h" ""} $value_to_set]] d value_to_set
6486  }
6487  }
6488  "bitString" {
6489  if {[string match "32'h*" $value_to_set]} {
6490  set value_to_set [string map {"32'h" "0x"} $value_to_set]
6491  }
6492  }
6493  "string" {
6494  set value_to_set [format "%s" $value_to_set]
6495  }
6496  default {
6497  Msg Warning "Unknown generic format $generic_format for IP $ip. Will attempt to pass as string..."
6498  }
6499  }
6500 
6501 
6502  Msg Info "The IP \{$ip\} contains: $ip_prop ($generic_format), setting it to $value_to_set."
6503  if {[catch {set_property -name $ip_prop -value $value_to_set -objects [get_ips $ip]} prop_error]} {
6504  Msg CriticalWarning "Failed to set property $ip_prop to $value_to_set for IP \{$ip\}: $prop_error"
6505  }
6506  }
6507  }
6508  }
6509  }
6510 
6511  foreach {regen_target} [lsort -unique $regen_targets] {
6512  Msg Info "Regenerating target: $regen_target"
6513  if {[catch {generate_target -force all [get_files $regen_target]} prop_error]} {
6514  Msg CriticalWarning "Failed to regen targets: $prop_error"
6515  }
6516  }
6517 }
6518 
6519 ## @brief Returns the format of a generic from an XML file
6520 ## @param[in] generic_name: The name of the generic
6521 ## @param[in] xml_file: The path to the XML XCI file
6522 proc GetGenericFormatFromXciXML {generic_name xml_file} {
6523  if {![file exists $xml_file]} {
6524  Msg Error "Could not find XML file: $xml_file"
6525  return "ERROR"
6526  }
6527 
6528  set fp [open $xml_file r]
6529  set xci_data [read $fp]
6530  close $fp
6531 
6532  set paramType "string"
6533  set modelparam_regex [format {^.*\y%s\y.*$} [string map {"CONFIG." "MODELPARAM_VALUE."} $generic_name]]
6534  set format_regex {format="([^"]+)"}
6535 
6536  set line [lindex [regexp -inline -line $modelparam_regex $xci_data] 0]
6537  Msg Debug "line: $line"
6538 
6539  if {[regexp $format_regex $line match format_value]} {
6540  Msg Debug "Extracted: $format_value format from xml"
6541  set paramType $format_value
6542  } else {
6543  Msg Debug "No format found, using string"
6544  }
6545 
6546  return $paramType
6547 }
6548 
6549 ## @brief Returns the format of a generic from an XCI file
6550 ## @param[in] generic_name: The name of the generic
6551 ## @param[in] xci_file: The path to the XCI file
6552 proc GetGenericFormatFromXci {generic_name xci_file} {
6553  if {![file exists $xci_file]} {
6554  Msg Error "Could not find XCI file: $xci_file"
6555  return "ERROR"
6556  }
6557 
6558  set fp [open $xci_file r]
6559  set xci_data [read $fp]
6560  close $fp
6561 
6562  set paramType "string"
6563  if {[string first "xilinx.com:schema:json_instance:1.0" $xci_data] == -1} {
6564  Msg Debug "XCI format is not JSON, trying XML..."
6565  set xml_file "[file rootname $xci_file].xml"
6566  return [GetGenericFormatFromXciXML $generic_name $xml_file]
6567  }
6568 
6569  set generic_name [string map {"CONFIG." ""} $generic_name]
6570  set ip_inst [ParseJSON $xci_data "ip_inst"]
6571  set parameters [dict get $ip_inst parameters]
6572  set component_parameters [dict get $parameters component_parameters]
6573  if {[dict exists $component_parameters $generic_name]} {
6574  set generic_info [dict get $component_parameters $generic_name]
6575  if {[dict exists [lindex $generic_info 0] format]} {
6576  set paramType [dict get [lindex $generic_info 0] format]
6577  Msg Debug "Extracted: $paramType format from xci"
6578  return $paramType
6579  }
6580  Msg Debug "No format found, using string"
6581  return $paramType
6582  } else {
6583  return "ERROR"
6584  }
6585 }
6586 
6587 
6588 ## @brief Returns the gitlab-ci.yml snippet for a CI stage and a defined project
6589 #
6590 # @param[in] proj_name: The project name
6591 # @param[in] ci_confs: Dictionary with CI configurations
6592 #
6593 proc WriteGitLabCIYAML {proj_name {ci_conf ""}} {
6594  if {[catch {package require yaml 0.3.3} YAMLPACKAGE]} {
6595  Msg CriticalWarning "Cannot find package YAML.\n Error message: $YAMLPACKAGE. \
6596  If you are running on tclsh, you can fix this by installing package \"tcllib\""
6597  return -1
6598  }
6599 
6600  set job_list []
6601  if {$ci_conf != ""} {
6602  set ci_confs [ReadConf $ci_conf]
6603  foreach sec [dict keys $ci_confs] {
6604  if {[string first : $sec] == -1} {
6605  lappend job_list $sec
6606  }
6607  }
6608  } else {
6609  set job_list {"generate_project" "simulate_project"}
6610  set ci_confs ""
6611  }
6612 
6613  set out_yaml [huddle create]
6614  foreach job $job_list {
6615  # Check main project configurations
6616  set huddle_tags [huddle list]
6617  set tag_section ""
6618  set sec_dict [dict create]
6619 
6620  if {$ci_confs != ""} {
6621  foreach var [dict keys [dict get $ci_confs $job]] {
6622  if {$var == "tags"} {
6623  set tag_section "tags"
6624  set tags [dict get [dict get $ci_confs $job] $var]
6625  set tags [split $tags ","]
6626  foreach tag $tags {
6627  set tag_list [huddle list $tag]
6628  set huddle_tags [huddle combine $huddle_tags $tag_list]
6629  }
6630  } else {
6631  dict set sec_dict $var [dict get [dict get $ci_confs $job] $var]
6632  }
6633  }
6634  }
6635 
6636  # Check if there are extra variables in the conf file
6637  set huddle_variables [huddle create "PROJECT_NAME" $proj_name "extends" ".vars"]
6638  if {[dict exists $ci_confs "$job:variables"]} {
6639  set var_dict [dict get $ci_confs $job:variables]
6640  foreach var [dict keys $var_dict] {
6641  # puts [dict get $var_dict $var]
6642  set value [dict get $var_dict "$var"]
6643  set var_inner [huddle create "$var" "$value"]
6644  set huddle_variables [huddle combine $huddle_variables $var_inner]
6645  }
6646  }
6647 
6648 
6649  set middle [huddle create "extends" ".$job" "variables" $huddle_variables]
6650  foreach sec [dict keys $sec_dict] {
6651  set value [dict get $sec_dict $sec]
6652  set var_inner [huddle create "$sec" "$value"]
6653  set middle [huddle combine $middle $var_inner]
6654  }
6655  if {$tag_section != ""} {
6656  set middle2 [huddle create "$tag_section" $huddle_tags]
6657  set middle [huddle combine $middle $middle2]
6658  }
6659 
6660  set outer [huddle create "$job:$proj_name" $middle]
6661  set out_yaml [huddle combine $out_yaml $outer]
6662  }
6663 
6664  return [string trimleft [yaml::huddle2yaml $out_yaml] "-"]
6665 }
6666 
6667 # @brief Write the content of Hog-library-dictionary created from the project into a .src/.ext/.con list file
6668 #
6669 # @param[in] libs The Hog-Library dictionary with the list of files in the project to write
6670 # @param[in] props The Hog-library dictionary with the file sets
6671 # @param[in] list_path The path of the output list file
6672 # @param[in] repo_path The main repository path
6673 # @param[in] ext_path The external path
6674 proc WriteListFiles {libs props list_path repo_path {ext_path ""}} {
6675  # Writing simulation list files
6676  foreach lib [dict keys $libs] {
6677  if {[llength [DictGet $libs $lib]] > 0} {
6678  set list_file_name $list_path$lib
6679  set list_file [open $list_file_name w]
6680  Msg Info "Writing $list_file_name..."
6681  puts $list_file "#Generated by Hog on [clock format [clock seconds] -format "%Y-%m-%d %H:%M:%S"]"
6682  foreach file [DictGet $libs $lib] {
6683  # Retrieve file properties from prop list
6684  set prop [DictGet $props $file]
6685  # Check if file is local to the repository or external
6686  if {[RelativeLocal $repo_path $file] != ""} {
6687  set file_path [RelativeLocal $repo_path $file]
6688  puts $list_file "$file_path $prop"
6689  } elseif {[RelativeLocal $ext_path $file] != ""} {
6690  set file_path [RelativeLocal $ext_path $file]
6691  set ext_list_file [open "[file rootname $list_file].ext" a]
6692  puts $ext_list_file "$file_path $prop"
6693  close $ext_list_file
6694  } else {
6695  # File is not relative to repo or ext_path... Write a Warning and continue
6696  Msg Warning "The path of file $file is not relative to your repository. Please check!"
6697  }
6698  }
6699  close $list_file
6700  }
6701  }
6702 }
6703 
6704 # @brief Write the content of Hog-library-dictionary created from the project into a .sim list file
6705 #
6706 # @param[in] libs The Hog-Library dictionary with the list of files in the project to write
6707 # @param[in] props The Hog-library dictionary with the file sets
6708 # @param[in] simsets The Hog-library dictionary with the file sets (relevant only for simulation)
6709 # @param[in] list_path The path of the output list file
6710 # @param[in] repo_path The main repository path
6711 # @param[in] force If 1, it will overwrite the existing list files
6712 proc WriteSimListFile {simset libs props simsets list_path repo_path {force 0}} {
6713  # Writing simulation list file
6714  set list_file_name $list_path/${simset}.sim
6715  if {$force == 0 && [file exists $list_file_name]} {
6716  Msg Info "List file $list_file_name already exists, skipping..."
6717  continue
6718  }
6719 
6720  set list_file [open $list_file_name a+]
6721 
6722  # Write the header with the simulator
6723  puts $list_file "\[files\]"
6724  Msg Info "Writing $list_file_name..."
6725  foreach lib [DictGet $simsets $simset] {
6726  foreach file [DictGet $libs $lib] {
6727  # Retrieve file properties from prop list
6728  set prop [DictGet $props $file]
6729  # Check if file is local to the repository or external
6730  if {[RelativeLocal $repo_path $file] != ""} {
6731  set file_path [RelativeLocal $repo_path $file]
6732  set lib_name [file rootname $lib]
6733  if {$lib_name != $simset && [file extension $file] == ".vhd" && [file extension $file] == ""} {
6734  lappend prop "lib=$lib_name"
6735  }
6736  puts $list_file "$file_path $prop"
6737  } else {
6738  # File is not relative to repo or ext_path... Write a Warning and continue
6739  Msg Warning "The path of file $file is not relative to your repository. Please check!"
6740  }
6741  }
6742  }
6743  close $list_file
6744 }
6745 
6746 
6747 ## @brief Write into a file, and if the file exists, it will append the string
6748 #
6749 # @param[out] File The log file to write into the message
6750 # @param[in] msg The message text
6751 proc WriteToFile {File msg} {
6752  set f [open $File a+]
6753  puts $f $msg
6754  close $f
6755 }
6756 
6757 ## Write the resource utilization table into a a file (Vivado only)
6758 #
6759 # @param[in] input the input .rpt report file from Vivado
6760 # @param[in] output the output file
6761 # @param[in] project_name the name of the project
6762 # @param[in] run synthesis or implementation
6763 proc WriteUtilizationSummary {input output project_name run} {
6764  set f [open $input "r"]
6765  set o [open $output "a"]
6766  puts $o "## $project_name $run Utilization report\n\n"
6767  struct::matrix util_m
6768  util_m add columns 14
6769  util_m add row
6770  if {[GetIDEVersion] >= 2021.0} {
6771  util_m add row "| **Site Type** | **Used** | **Fixed** | **Prohibited** | **Available** | **Util%** |"
6772  util_m add row "| --- | --- | --- | --- | --- | --- |"
6773  } else {
6774  util_m add row "| **Site Type** | **Used** | **Fixed** | **Available** | **Util%** |"
6775  util_m add row "| --- | --- | --- | --- | --- |"
6776  }
6777 
6778  set luts 0
6779  set regs 0
6780  set uram 0
6781  set bram 0
6782  set dsps 0
6783  set ios 0
6784 
6785  while {[gets $f line] >= 0} {
6786  if {([string first "| CLB LUTs" $line] >= 0 || [string first "| Slice LUTs" $line] >= 0) && $luts == 0} {
6787  util_m add row $line
6788  set luts 1
6789  }
6790  if {([string first "| CLB Registers" $line] >= 0 || [string first "| Slice Registers" $line] >= 0) && $regs == 0} {
6791  util_m add row $line
6792  set regs 1
6793  }
6794  if {[string first "| Block RAM Tile" $line] >= 0 && $bram == 0} {
6795  util_m add row $line
6796  set bram 1
6797  }
6798  if {[string first "URAM " $line] >= 0 && $uram == 0} {
6799  util_m add row $line
6800  set uram 1
6801  }
6802  if {[string first "DSPs" $line] >= 0 && $dsps == 0} {
6803  util_m add row $line
6804  set dsps 1
6805  }
6806  if {[string first "Bonded IOB" $line] >= 0 && $ios == 0} {
6807  util_m add row $line
6808  set ios 1
6809  }
6810  }
6811  util_m add row
6812 
6813  close $f
6814  puts $o [util_m format 2string]
6815  close $o
6816 }
6817 
6818 # Check Git Version when sourcing hog.tcl
6819 if {[GitVersion 2.7.2] == 0} {
6820  Msg Error "Found Git version older than 2.7.2. Hog will not work as expected, exiting now."
6821 }