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