Hog v10.14.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 application 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 # Find repo root by walking up from a start dir until we see Top/ and Projects/.
2021 # For absolute paths we do not normalize, so the path form is preserved and [file exists]
2022 # sees the same view as the script was loaded from.
2023 proc FindRepoRoot {start_dir} {
2024  if {[file pathtype $start_dir] eq "relative"} {
2025  set dir [file normalize [file join [pwd] $start_dir]]
2026  } else {
2027  set dir $start_dir
2028  }
2029  while {1} {
2030  if {[file exists [file join $dir Top]] && [file exists [file join $dir Projects]]} {
2031  return $dir
2032  }
2033  set parent [file dirname $dir]
2034  if {$parent eq $dir} {
2035  return ""
2036  }
2037  set dir $parent
2038  }
2039 }
2040 
2041 ## @brief Set VHDL version to 2008 for *.vhd files
2042 #
2043 # @param[in] file_name the name of the HDL file
2044 #
2045 # @return "-hdl_version VHDL_2008" if the file is a *.vhd files else ""
2046 proc FindVhdlVersion {file_name} {
2047  set extension [file extension $file_name]
2048  switch $extension {
2049  .vhd {
2050  set vhdl_version "-hdl_version VHDL_2008"
2051  }
2052  .vhdl {
2053  set vhdl_version "-hdl_version VHDL_2008"
2054  }
2055  default {
2056  set vhdl_version ""
2057  }
2058  }
2059 
2060  return $vhdl_version
2061 }
2062 
2063 ## @brief Format a generic to a 32 bit verilog style hex number, e.g.
2064 # take in ea8394c and return 32'h0ea8394c
2065 #
2066 # @param[in] unformatted generic
2067 proc FormatGeneric {generic} {
2068  if {[string is integer "0x$generic"]} {
2069  return [format "32'h%08X" "0x$generic"]
2070  } else {
2071  # for non integers (e.g. blanks) just return 0
2072  return [format "32'h%08X" 0]
2073  }
2074 }
2075 
2076 # @brief Generate the bitstream
2077 #
2078 # @param[in] project_name The name of the project
2079 # @param[in] run_folder The path where to run the implementation
2080 # @param[in] repo_path The main path of the git repository
2081 # @param[in] njobs The number of CPU jobs to run in parallel
2082 proc GenerateBitstream {{run_folder ""} {repo_path .} {njobs 1}} {
2083  Msg Info "Starting write bitstream flow..."
2084  if {[IsQuartus]} {
2085  set revision [get_current_revision]
2086  if {[catch {execute_module -tool asm} result]} {
2087  Msg Error "Result: $result\n"
2088  Msg Error "Generate bitstream failed. See the report file.\n"
2089  } else {
2090  Msg Info "Generate bitstream was successful for revision $revision.\n"
2091  }
2092  } elseif {[IsLibero]} {
2093  Msg Info "Run GENERATEPROGRAMMINGDATA ..."
2094  if {[catch {run_tool -name {GENERATEPROGRAMMINGDATA}}]} {
2095  Msg Error "GENERATEPROGRAMMINGDATA FAILED!"
2096  } else {
2097  Msg Info "GENERATEPROGRAMMINGDATA PASSED."
2098  }
2099  Msg Info "Sourcing Hog/Tcl/integrated/post-bitstream.tcl"
2100  source $repo_path/Hog/Tcl/integrated/post-bitstream.tcl
2101  } elseif {[IsDiamond]} {
2102  prj_run Export -impl Implementation0 -task Bitgen
2103  }
2104 }
2105 
2106 ## @brief Function used to generate a qsys system from a .qsys file.
2107 # The procedure adds the generated IPs to the project.
2108 #
2109 # @param[in] qsysFile the Intel Platform Designed file (.qsys), containing the system to be generated
2110 # @param[in] commandOpts the command options to be used during system generation as they are in qsys-generate options
2111 #
2112 proc GenerateQsysSystem {qsysFile commandOpts} {
2113  global env
2114  if {[file exists $qsysFile] != 0} {
2115  set qsysPath [file dirname $qsysFile]
2116  set qsysName [file rootname [file tail $qsysFile]]
2117  set qsysIPDir "$qsysPath/$qsysName"
2118  set qsysLogFile "$qsysPath/$qsysName.qsys-generate.log"
2119 
2120  set qsys_rootdir ""
2121  if {![info exists ::env(QSYS_ROOTDIR)]} {
2122  if {[info exists ::env(QUARTUS_ROOTDIR)]} {
2123  set qsys_rootdir "$::env(QUARTUS_ROOTDIR)/sopc_builder/bin"
2124  Msg Warning "The QSYS_ROOTDIR environment variable is not set! I will use $qsys_rootdir"
2125  } else {
2126  Msg CriticalWarning "The QUARTUS_ROOTDIR environment variable is not set! Assuming all quartus executables are contained in your PATH!"
2127  }
2128  } else {
2129  set qsys_rootdir $::env(QSYS_ROOTDIR)
2130  }
2131 
2132  set cmd "$qsys_rootdir/qsys-generate"
2133  set cmd_options "$qsysFile --output-directory=$qsysIPDir $commandOpts"
2134  if {![catch {"exec $cmd -version"}] || [lindex $::errorCode 0] eq "NONE"} {
2135  Msg Info "Executing: $cmd $cmd_options"
2136  Msg Info "Saving logfile in: $qsysLogFile"
2137  if {[catch {eval exec -ignorestderr "$cmd $cmd_options >>& $qsysLogFile"} ret opt]} {
2138  set makeRet [lindex [dict get $opt -errorcode] end]
2139  Msg CriticalWarning "$cmd returned with $makeRet"
2140  }
2141  } else {
2142  Msg Error " Could not execute command $cmd"
2143  exit 1
2144  }
2145  #Add generated IPs to project
2146  set qsysIPFileList [concat \
2147  [glob -nocomplain -directory $qsysIPDir -types f *.ip *.qip] \
2148  [glob -nocomplain -directory "$qsysIPDir/synthesis" -types f *.ip *.qip *.vhd *.vhdl] \
2149  ]
2150  foreach qsysIPFile $qsysIPFileList {
2151  if {[file exists $qsysIPFile] != 0} {
2152  set qsysIPFileType [FindFileType $qsysIPFile]
2153  set_global_assignment -name $qsysIPFileType $qsysIPFile
2154  # Write checksum to file
2155  set IpMd5Sum [Md5Sum $qsysIPFile]
2156  # open file for writing
2157  set fileDir [file normalize "./hogTmp"]
2158  set fileName "$fileDir/.hogQsys.md5"
2159  if {![file exists $fileDir]} {
2160  file mkdir $fileDir
2161  }
2162  set hogQsysFile [open $fileName "a"]
2163  set fileEntry "$qsysIPFile\t$IpMd5Sum"
2164  puts $hogQsysFile $fileEntry
2165  close $hogQsysFile
2166  }
2167  }
2168  } else {
2169  Msg ERROR "Error while generating ip variations from qsys: $qsysFile not found!"
2170  }
2171 }
2172 
2173 
2174 ## Format generics from conf file to string that simulators accepts
2175 #
2176 # @param[in] dict containing generics from conf file
2177 # @param[in] target: software target(vivado, questa)
2178 # defines the output format of the string
2179 proc GenericToSimulatorString {prop_dict target} {
2180  set prj_generics ""
2181  dict for {theKey theValue} $prop_dict {
2182  set valueHexFull ""
2183  set valueNumBits ""
2184  set valueHexFlag ""
2185  set valueHex ""
2186  set valueIntFull ""
2187  set ValueInt ""
2188  set valueStrFull ""
2189  set ValueStr ""
2190  regexp {([0-9]*)('h)([0-9a-fA-F]*)} $theValue valueHexFull valueNumBits valueHexFlag valueHex
2191  regexp {^([0-9]*)$} $theValue valueIntFull ValueInt
2192  regexp {(?!^\d+$)^.+$} $theValue valueStrFull ValueStr
2193  if {[string tolower $target] == "vivado" || [string tolower $target] == "xsim"} {
2194  if {[string tolower $theValue] == "true" || [string tolower $theValue] == "false"} {
2195  set prj_generics "$prj_generics $theKey=$theValue"
2196  } elseif {$valueNumBits != "" && $valueHexFlag != "" && $valueHex != ""} {
2197  set prj_generics "$prj_generics $theKey=$valueHexFull"
2198  } elseif {$valueIntFull != "" && $ValueInt != ""} {
2199  set prj_generics "$prj_generics $theKey=$ValueInt"
2200  } elseif {$valueStrFull != "" && $ValueStr != ""} {
2201  set prj_generics "$prj_generics $theKey=\"$ValueStr\""
2202  } else {
2203  set prj_generics "$prj_generics $theKey=\"$theValue\""
2204  }
2205  } elseif {[lsearch -exact [GetSimulators] [string tolower $target]] >= 0} {
2206  if {$valueNumBits != "" && $valueHexFlag != "" && $valueHex != ""} {
2207  set numBits 0
2208  scan $valueNumBits %d numBits
2209  set numHex 0
2210  scan $valueHex %x numHex
2211  binary scan [binary format "I" $numHex] "B*" binval
2212  set numBits [expr {$numBits - 1}]
2213  set numBin [string range $binval end-$numBits end]
2214  set prj_generics "$prj_generics $theKey=\"$numBin\""
2215  } elseif {$valueIntFull != "" && $ValueInt != ""} {
2216  set prj_generics "$prj_generics $theKey=$ValueInt"
2217  } elseif {$valueStrFull != "" && $ValueStr != ""} {
2218  set prj_generics "$prj_generics {$theKey=\"$ValueStr\"}"
2219  } else {
2220  set prj_generics "$prj_generics {$theKey=\"$theValue\"}"
2221  }
2222  } else {
2223  Msg Warning "Target : $target not implemented"
2224  }
2225  }
2226  return $prj_generics
2227 }
2228 
2229 ## Get the configuration files to create a Hog project
2230 #
2231 # @param[in] proj_dir: The project directory containing the conf file or the the tcl file
2232 #
2233 # @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
2234 proc GetConfFiles {proj_dir} {
2235  Msg Debug "GetConfFiles called with proj_dir=$proj_dir"
2236  if {![file isdirectory $proj_dir]} {
2237  Msg Error "$proj_dir is supposed to be the top project directory"
2238  return -1
2239  }
2240  set conf_file [file normalize $proj_dir/hog.conf]
2241  set sim_file [file normalize $proj_dir/sim.conf]
2242  set pre_tcl [file normalize $proj_dir/pre-creation.tcl]
2243  set post_tcl [file normalize $proj_dir/post-creation.tcl]
2244  set pre_rtl [file normalize $proj_dir/pre-rtl.tcl]
2245  set post_rtl [file normalize $proj_dir/post-rtl.tcl]
2246 
2247  return [list $conf_file $sim_file $pre_tcl $post_tcl $pre_rtl $post_rtl]
2248 }
2249 
2250 
2251 # Searches directory for tcl scripts to add as custom commands to launch.tcl
2252 # Returns string of tcl scripts formatted as usage or switch statement
2253 #
2254 # @param[in] directory The directory where to look for custom tcl scripts (Default .)
2255 # @param[in] ret_commands if 1 returns commands as switch statement string instead of usage (Default 0)
2256 proc GetCustomCommands {parameters {directory .}} {
2257  set commands_dict [dict create]
2258  set commands_files [glob -nocomplain $directory/*.tcl]
2259 
2260  if {[llength $commands_files] == 0} {
2261  return ""
2262  }
2263 
2264  foreach file $commands_files {
2265 
2266  #Msg Info "do compile libe? $do_compile_lib"
2267  set custom_cmd [LoadCustomCommandFile $file $parameters]
2268 
2269  if {$custom_cmd eq ""} {
2270  continue
2271  }
2272 
2273  #Msg Info "Validating custom command $custom_cmd"
2274  set custom_cmd_name [dict get $custom_cmd NAME]
2275 
2276  Msg Debug "Loaded custom command '$custom_cmd_name' from $file"
2277 
2278  #Ensure command is not already defined
2279  if {[dict exists $commands_dict $custom_cmd_name]} {
2280  Msg Error "Custom command '$custom_cmd_name' in $file already defined as: \[dict get $commands_dict $custom_cmd_name\]. Skipping."
2281  continue
2282  }
2283 
2284 
2285 
2286  set custom_cmd_name [ string toupper $custom_cmd_name ]
2287  dict set commands_dict $custom_cmd_name $custom_cmd
2288  }
2289 
2290  return $commands_dict
2291 }
2292 
2293 proc SanitizeCustomCommand {cmdDict file parameters} {
2294  # Normalize all user-provided keys to uppercase so NAME/DESCRIPTION/etc are case-insensitive.
2295  set normalized {}
2296  foreach k [dict keys $cmdDict] {
2297  set K [string toupper $k]
2298  dict set normalized $K [dict get $cmdDict $k]
2299  }
2300 
2301  set cmdDict $normalized
2302  if {![dict exists $cmdDict NAME]} {
2303  Msg Error "Custom command in $file missing required key NAME. Skipping."
2304  return ""
2305  }
2306  if {![dict exists $cmdDict SCRIPT]} {
2307  Msg Error "Custom command '$[dict get $cmdDict NAME]' in $file missing SCRIPT. Skipping."
2308  return ""
2309  }
2310 
2311  # Allowed keys (uppercased). IDE is optional and will be validated if present.
2312  set allowed {NAME DESCRIPTION OPTIONS CUSTOM_OPTIONS SCRIPT IDE NO_EXIT}
2313  foreach k [dict keys $cmdDict] {
2314  if {[lsearch -exact $allowed $k] < 0} {
2315  Msg Warning "Custom command '[dict get $cmdDict NAME]' in $file: unknown key '$k'. Allowed: $allowed. Skipping."
2316  }
2317  }
2318 
2319  # NAME
2320  set name [string trim [dict get $cmdDict NAME]]
2321  if {$name eq ""} {
2322  Msg Error "Custom command in $file has empty NAME. Skipping."
2323  return ""
2324  }
2325 
2326  if {![regexp {^[a-zA-Z][a-zA-Z0-9_]+$} $name]} {
2327  Msg Error "Custom command NAME '$name' (file $file) contains invalid characters."
2328  }
2329 
2330  # DESCRIPTION
2331  if {![dict exists $cmdDict DESCRIPTION]} {
2332  dict set cmdDict DESCRIPTION "No description provided."
2333  }
2334 
2335 
2336  set hog_parameters {}
2337  foreach p $parameters {
2338  lappend hog_parameters [lindex $p 0]
2339  }
2340 
2341  # OPTIONS
2342  set hog_options {}
2343  if {[dict exists $cmdDict OPTIONS]} {
2344  set raw_opts [dict get $cmdDict OPTIONS]
2345  if {![llength $raw_opts]} {
2346  set raw_opts {}
2347  }
2348 
2349  foreach item $raw_opts {
2350  set found 0
2351  foreach p $parameters {
2352  set hog_parameter [lindex $p 0]
2353  if { $item eq $hog_parameter } {
2354  lappend hog_options $p
2355  set found 1
2356  break
2357  }
2358  }
2359  if {!$found} {
2360  Msg Warning "Custom command '$name' in $file: option '$item' not found in Hog parameters. Skipping."
2361  }
2362  }
2363  dict set cmdDict OPTIONS $hog_options
2364  } else {
2365  dict set cmdDict CUSTOM_OPTIONS {}
2366  }
2367 
2368 
2369 
2370  # CUSTOM_OPTIONS
2371  set opt_defs {}
2372  if {[dict exists $cmdDict CUSTOM_OPTIONS]} {
2373  set raw_opts [dict get $cmdDict CUSTOM_OPTIONS]
2374  if {![llength $raw_opts]} {
2375  set raw_opts {}
2376  }
2377  foreach item $raw_opts {
2378 
2379  if {[llength $item] != 2 && [llength $item] != 3} {
2380  Msg Error "Bad custom option: \[$item\]. Custom command '$name' in $file: \
2381  each CUSTOM_OPTIONS entry must be {option \"help\"} for flags \
2382  and {option \"default_value\" \"help\"} for options with arguments. Skipping command."
2383  return ""
2384  }
2385 
2386  if {[llength $item] == 2} {
2387  lassign $item opt help
2388  set def ""
2389  } else {
2390  lassign $item opt def help
2391  }
2392 
2393  if { [IsInList $opt $hog_parameters] == 1 } {
2394  Msg Warning "Custom command '$name' in $file: option '$opt' already defined in Hog parameters. Skipping."
2395  continue
2396  }
2397 
2398 
2399  #optional .arg in option regex
2400  if {![regexp {^[a-zA-Z][a-zA-Z0-9_]*(\.arg)?$} $opt]} {
2401  Msg Error "Custom command '$name' in $file: invalid option name '$opt'."
2402  return ""
2403  }
2404 
2405  if {$help eq ""} {
2406  Msg Warning "Custom command '$name' option '$opt' has empty help text."
2407  }
2408  }
2409  } else {
2410  dict set cmdDict CUSTOM_OPTIONS {}
2411  }
2412 
2413  # NO EXIT
2414  if {[dict exists $cmdDict NO_EXIT]} {
2415  set no_exit [dict get $cmdDict NO_EXIT]
2416  set no_exit [string tolower [string trim $no_exit]]
2417 
2418  if {$no_exit eq "1" || $no_exit eq "true"} {
2419  set no_exit 1
2420  } else {
2421  set no_exit 0
2422  }
2423 
2424  dict set cmdDict NO_EXIT $no_exit
2425  } else {
2426  dict set cmdDict NO_EXIT 0
2427  }
2428 
2429  return $cmdDict
2430 }
2431 
2432 proc LoadCustomCommandFile {file parameters} {
2433  set saved_pwd [pwd]
2434  set dir [file dirname $file]
2435  cd $dir
2436  unset -nocomplain ::hog_command
2437  set rc [catch {source $file} err]
2438  cd $saved_pwd
2439  if {$rc} {
2440  Msg Error "Error sourcing custom command file $file: $err"
2441  return ""
2442  }
2443  if {![info exists ::hog_command]} {
2444  Msg Warning "File $file did not define ::hog_command. Skipping."
2445  return ""
2446  }
2447  set cmdDict $::hog_command
2448  # Ensure it's a dict
2449  if {[catch {dict size $cmdDict}]} {
2450  Msg Error "In $file ::hog_command is not a valid dict. Skipping."
2451  return ""
2452  }
2453  return [SanitizeCustomCommand $cmdDict $file $parameters]
2454 }
2455 
2456 
2457 ## Get the Date and time of a commit (or current time if Git < 2.9.3)
2458 #
2459 # @param[in] commit The commit
2460 proc GetDateAndTime {commit} {
2461  set clock_seconds [clock seconds]
2462 
2463  if {[GitVersion 2.9.3]} {
2464  set date [Git "log -1 --format=%cd --date=format:%d%m%Y $commit"]
2465  set timee [Git "log -1 --format=%cd --date=format:00%H%M%S $commit"]
2466  } else {
2467  Msg Warning "Found Git version older than 2.9.3. Using current date and time instead of commit time."
2468  set date [clock format $clock_seconds -format {%d%m%Y}]
2469  set timee [clock format $clock_seconds -format {00%H%M%S}]
2470  }
2471  return [list $date $timee]
2472 }
2473 
2474 ## @brief Gets a list of files contained in the current fileset that match a file name (passed as parameter)
2475 #
2476 # The file name is matched against the input parameter.
2477 #
2478 # @param[in] file name (or part of it)
2479 # @param[in] fileset name
2480 #
2481 # @return a list of files matching the parameter in the chosen fileset
2482 #
2483 proc GetFile {file fileset} {
2484  if {[IsXilinx]} {
2485  # Vivado
2486  set Files [get_files -all $file -of_object [get_filesets $fileset]]
2487  set f [lindex $Files 0]
2488 
2489  return $f
2490  } elseif {[IsQuartus]} {
2491  # Quartus
2492  return ""
2493  } else {
2494  # Tcl Shell
2495  puts "***DEBUG Hog:GetFile $file"
2496  return "DEBUG_file"
2497  }
2498 }
2499 
2500 ## @brief Extract the generics from a file
2501 #
2502 # @param[in] filename The file from which to extract the generics
2503 # @param[in] entity The entity in the file from which to extract the generics (default "")
2504 proc GetFileGenerics {filename {entity ""}} {
2505  set file_type [FindFileType $filename]
2506  if {[string equal $file_type "VERILOG_FILE"] || [string equal $file_type "SYSTEMVERILOG_FILE"]} {
2507  return [GetVerilogGenerics $filename]
2508  } elseif {[string equal $file_type "VHDL_FILE"]} {
2509  return [GetVhdlGenerics $filename $entity]
2510  } else {
2511  Msg CriticalWarning "Could not determine extension of top level file."
2512  }
2513 }
2514 
2515 ## @brief Gets custom generics from hog
2516 #
2517 # @param[in] proj_dir: the top folder of the project
2518 # @return dict with generics
2519 #
2520 proc GetGenericsFromConf {proj_dir} {
2521  set generics_dict [dict create]
2522  set top_dir "Top/$proj_dir"
2523  set conf_file "$top_dir/hog.conf"
2524  set conf_index 0
2525  Msg Debug "GetGenericsFromConf called with proj_dir=$proj_dir, top_dir=$top_dir"
2526 
2527  if {[file exists $conf_file]} {
2528  set properties [ReadConf [lindex [GetConfFiles $top_dir] $conf_index]]
2529  if {[dict exists $properties generics]} {
2530  set generics_dict [dict get $properties generics]
2531  }
2532  } else {
2533  Msg Warning "File $conf_file not found."
2534  }
2535  return $generics_dict
2536 }
2537 
2538 ## @brief Gets the simulation sets from the project
2539 #
2540 # @param[in] project_name: the name of the project
2541 # @param[in] repo_path: the path to the repository
2542 # @param[in] simsets: a list of simulation sets to retrieve (default: all)
2543 # @param[in] ghdl: if 1, only GHDL simulation sets are returned (default: 0),
2544 # otherwise only non-GHDL simulation sets are returned
2545 # @param[in] no_conf: if 1, the simulation sets are returned without reading the sim.conf file (default: 0)
2546 # @return a dictionary with the simulation sets, where the keys are the simulation set names
2547 # and the values are dictionaries with the properties of each simulation set
2548 proc GetSimSets {project_name repo_path {simsets ""} {ghdl 0} {no_conf 0}} {
2549  set simsets_dict [dict create]
2550  set list_dir "$repo_path/Top/$project_name/list"
2551  set list_files []
2552  if {$simsets != ""} {
2553  foreach s $simsets {
2554  set list_file "$list_dir/$s.sim"
2555  if {[file exists $list_file]} {
2556  lappend list_files $list_file
2557  } elseif {$s != "sim_1"} {
2558  Msg CriticalWarning "Simulation set list file $list_file not found."
2559  return ""
2560  }
2561  }
2562  } else {
2563  set list_files [glob -nocomplain -directory $list_dir "*.sim"]
2564  }
2565 
2566  # Get simulation properties from conf file
2567  set proj_dir [file normalize $repo_path/Top/$project_name]
2568  set sim_file [file normalize $proj_dir/sim.conf]
2569 
2570  foreach list_file $list_files {
2571  set file_name [file tail $list_file]
2572  set simset_name [file rootname $file_name]
2573  set fp [open $list_file r]
2574  set file_data [read $fp]
2575  close $fp
2576  set data [split $file_data "\n"]
2577 
2578  set firstline [lindex $data 0]
2579  # Find simulator
2580  if {[regexp {^ *\# *Simulator} $firstline]} {
2581  set simulator_prop [regexp -all -inline {\S+} $firstline]
2582  set simulator [string tolower [lindex $simulator_prop end]]
2583  } else {
2584  Msg Warning "Simulator not set in $simset_name.sim. \
2585  The first line of $simset_name.sim should be #Simulator <SIMULATOR_NAME>,\
2586  where <SIMULATOR_NAME> can be xsim, questa, modelsim, ghdl, riviera, activehdl,\
2587  ies, or vcs, e.g. #Simulator questa.\
2588  Setting simulator by default to xsim."
2589  set simulator "xsim"
2590  }
2591  if {$simulator eq "skip_simulation"} {
2592  Msg Info "Skipping simulation for $simset_name"
2593  continue
2594  }
2595  if {($ghdl == 1 && $simulator != "ghdl") || ($ghdl == 0 && $simulator == "ghdl")} {
2596  continue
2597  }
2598 
2599  set SIM_PROPERTIES ""
2600  if {[file exists $sim_file] && $no_conf == 0} {
2601  set SIM_PROPERTIES [ReadConf $sim_file]
2602  }
2603 
2604  set global_sim_props [dict create]
2605  dict set global_sim_props "properties" [DictGet $SIM_PROPERTIES "sim"]
2606  dict set global_sim_props "generics" [DictGet $SIM_PROPERTIES "generics"]
2607  dict set global_sim_props "hog" [DictGet $SIM_PROPERTIES "hog"]
2608 
2609 
2610  set sim_dict [dict create]
2611  dict set sim_dict "simulator" $simulator
2612  if {[dict exists $SIM_PROPERTIES $simset_name]} {
2613  dict set sim_dict "properties" [DictGet $SIM_PROPERTIES $simset_name]
2614  dict set sim_dict "generics" [DictGet $SIM_PROPERTIES "$simset_name:generics"]
2615  dict set sim_dict "hog" [DictGet $SIM_PROPERTIES "$simset_name:hog"]
2616  } elseif {$no_conf == 0} {
2617  # Retrieve properties from .sim file
2618  set conf_dict [ReadConf $list_file]
2619  set sim_dict [MergeDict $sim_dict $conf_dict 0]
2620  }
2621  set sim_dict [MergeDict $sim_dict $global_sim_props 0]
2622  dict set simsets_dict $simset_name $sim_dict
2623  }
2624  return $simsets_dict
2625 }
2626 
2627 ## @brief Gets all custom <simset>:generics from sim.conf
2628 #
2629 # @param[in] proj_dir: the top folder of the project
2630 # @return nested dict with all <simset>:generics
2631 #
2632 proc GetSimsetGenericsFromConf {proj_dir} {
2633  set simsets_generics_dict [dict create]
2634  set top_dir "Top/$proj_dir"
2635  set conf_file "$top_dir/sim.conf"
2636  set conf_index 1
2637 
2638  if {[file exists $conf_file]} {
2639  set properties [ReadConf [lindex [GetConfFiles $top_dir] $conf_index]]
2640  # Filter the dictionary for keys ending with ":generics"
2641  set simsets_generics_dict [dict filter $properties key *:generics]
2642  } else {
2643  Msg Warning "File $conf_file not found."
2644  }
2645  return $simsets_generics_dict
2646 }
2647 
2648 
2649 ## Returns the group name from the project directory
2650 #
2651 # @param[in] proj_dir project directory
2652 # @param[in] repo_dir repository directory
2653 #
2654 # @return the group name without initial and final slashes
2655 #
2656 proc GetGroupName {proj_dir repo_dir} {
2657  if {[regexp {^(.*)/(Top|Projects)/+(.*?)/*$} $proj_dir dummy possible_repo_dir proj_or_top dir]} {
2658  # The Top or Project folder is in the root of a the git repository
2659  if {[file normalize $repo_dir] eq [file normalize $possible_repo_dir]} {
2660  set group [file dir $dir]
2661  if {$group == "."} {
2662  set group ""
2663  }
2664  } else {
2665  # The Top or Project folder is NOT in the root of a git repository
2666  Msg Warning "Project directory $proj_dir seems to be in $possible_repo_dir which is not a the main Git repository $repo_dir."
2667  }
2668  } else {
2669  Msg Warning "Could not parse project directory $proj_dir"
2670  set group ""
2671  }
2672  return $group
2673 }
2674 
2675 ## Get custom Hog describe for the project under investigation
2676 #
2677 # @param[in] proj_dir the top directory of the project (e.g. repo_path/Top/group/project)
2678 # @param[in] repo_path the main path of the repository
2679 #
2680 # @return the Hog describe of the project, with a "-dirty" suffix if the project files are not clean
2681 #
2682 proc GetHogDescribe {proj_dir {repo_path .}} {
2683  lassign [GetRepoVersions $proj_dir $repo_path] global_commit global_version
2684  if {$global_commit == 0} {
2685  # in case the repo is dirty, we use the last committed sha and add a -dirty suffix
2686  set new_sha "[string toupper [GetSHA]]"
2687  set suffix "-dirty"
2688  } else {
2689  set new_sha [string toupper $global_commit]
2690  set suffix ""
2691  }
2692  set describe "v[HexVersionToString $global_version]-$new_sha$suffix"
2693  return $describe
2694 }
2695 
2696 ## @brief Extract files, libraries and properties from the project's list files
2697 #
2698 # @param[in] args The arguments are <list_path> <repository path>[options]
2699 # * list_path path to the list file directory
2700 # Options:
2701 # * -list_files <List files> the file wildcard, if not specified all Hog list files will be looked for
2702 # * -sha_mode forwarded to ReadListFile, see there for info
2703 # * -ext_path <external path> path for external libraries forwarded to ReadListFile
2704 #
2705 # @return a list of 3 dictionaries: libraries and properties
2706 # - libraries has library name as keys and a list of filenames as values
2707 # - properties has file names as keys and a list of properties as values
2708 # - filesets has the fileset name as keys and the correspondent list of libraries as values (significant only for simulations)
2709 proc GetHogFiles {args} {
2710  if {[IsQuartus]} {
2711  load_package report
2712  if {[catch {package require cmdline} ERROR]} {
2713  puts "$ERROR\n If you are running this script on tclsh, you can fix this by installing 'tcllib'"
2714  return 0
2715  }
2716  }
2717 
2718 
2719  set parameters {
2720  {list_files.arg "" "The file wildcard, if not specified all Hog list files will be looked for."}
2721  {sha_mode "Forwarded to ReadListFile, see there for info."}
2722  {ext_path.arg "" "Path for the external libraries forwarded to ReadListFile."}
2723  {print_log "Forwarded to ReadListFile, see there for info."}
2724  }
2725  set usage "USAGE: GetHogFiles \[options\] <list path> <repository path>"
2726  if {[catch {array set options [cmdline::getoptions args $parameters $usage]}] || [llength $args] != 2} {
2727  Msg CriticalWarning [cmdline::usage $parameters $usage]
2728  return
2729  }
2730  set list_path [lindex $args 0]
2731  set repo_path [lindex $args 1]
2732 
2733  set list_files $options(list_files)
2734  set sha_mode $options(sha_mode)
2735  set ext_path $options(ext_path)
2736  set print_log $options(print_log)
2737 
2738  if {$sha_mode == 1} {
2739  set sha_mode_opt "-sha_mode"
2740  } else {
2741  set sha_mode_opt ""
2742  }
2743 
2744  if {$print_log == 1} {
2745  set print_log_opt "-print_log"
2746  } else {
2747  set print_log_opt ""
2748  }
2749 
2750 
2751  if {$list_files == ""} {
2752  set list_files {.src,.con,.sim,.ext}
2753  }
2754  set libraries [dict create]
2755  set properties [dict create]
2756  set list_files [glob -nocomplain -directory $list_path "*{$list_files}"]
2757  set filesets [dict create]
2758 
2759  foreach f $list_files {
2760  set ext [file extension $f]
2761  if {$ext == ".ext"} {
2762  lassign [ReadListFile {*}"$sha_mode_opt $print_log_opt $f $ext_path"] l p fs
2763  } else {
2764  lassign [ReadListFile {*}"$sha_mode_opt $print_log_opt $f $repo_path"] l p fs
2765  }
2766  set libraries [MergeDict $l $libraries]
2767  set properties [MergeDict $p $properties]
2768  Msg Debug "list file $f, filesets: $fs"
2769  set filesets [MergeDict $fs $filesets]
2770  Msg Debug "Merged filesets $filesets"
2771  }
2772 
2773  # Auto-discover HLS components from the project's hog.conf (no .src needed).
2774  # The list_path argument is conventionally <proj_dir>/list/, so hog.conf sits
2775  # one level up. For each [hls:<comp>] section with HLS_CONFIG=<path>, add the
2776  # cfg + everything it references to a synthesized "<comp>" library, and -- if
2777  # print_log is on -- show the file tree exactly like a .src would
2778  set proj_conf [file normalize [file join [file dirname $list_path] hog.conf]]
2779  if {[file exists $proj_conf]} {
2780  set hls_configs [GetHlsConfigsFromProjConf $proj_conf $repo_path]
2781  set already_tracked [dict create]
2782  dict for {libname libfiles} $libraries {
2783  foreach lf $libfiles { dict set already_tracked [file normalize $lf] 1 }
2784  }
2785  dict for {comp_name cfg_abs} $hls_configs {
2786  if {[dict exists $already_tracked $cfg_abs]} {
2787  Msg Debug "HLS component '$comp_name': cfg $cfg_abs already tracked via a .src, skipping conf-based GetHogFiles discovery."
2788  continue
2789  }
2790  set lib_name "${comp_name}.src"
2791  dict lappend libraries $lib_name $cfg_abs
2792  dict set already_tracked $cfg_abs 1
2793  set hls_extras [ExpandHlsConfigFiles $cfg_abs]
2794  Msg Info "HLS component '$comp_name' (from hog.conf): tracking [expr {1 + [llength $hls_extras]}] file(s) via $cfg_abs"
2795  if {$print_log == 1} {
2796  Msg Status "\nhog.conf \[hls:$comp_name\] (auto-discovered)"
2797  set rel_cfg [Relative $repo_path $cfg_abs 1]
2798  if {$rel_cfg eq ""} { set rel_cfg $cfg_abs }
2799  if {[llength $hls_extras] == 0} {
2800  Msg Status "└── $rel_cfg"
2801  } else {
2802  Msg Status "├── $rel_cfg"
2803  Msg Status " Inside [file tail $cfg_abs] (HLS auto-expand):"
2804  }
2805  }
2806  set n_extras [llength $hls_extras]
2807  set i 0
2808  foreach hls_extra $hls_extras {
2809  incr i
2810  if {[dict exists $already_tracked $hls_extra]} { continue }
2811  dict lappend libraries $lib_name $hls_extra
2812  dict set already_tracked $hls_extra 1
2813  if {$print_log == 1} {
2814  if {$i == $n_extras} { set pad "└──" } else { set pad "├──" }
2815  set rel_extra [Relative [file dirname $cfg_abs] $hls_extra 1]
2816  if {$rel_extra eq ""} { set rel_extra $hls_extra }
2817  Msg Status " $pad $rel_extra"
2818  }
2819  }
2820  }
2821  }
2822 
2823  return [list $libraries $properties $filesets]
2824 }
2825 
2826 # @brief Get the IDE of a Hog project and returns the correct argument for the IDE cli command
2827 #
2828 # @param[in] proj_conf The project hog.conf file
2829 # @param[in] custom_ver If set, use this version instead of the one in the hog.conf
2830 proc GetIDECommand {proj_conf {custom_ver ""}} {
2831  if {$custom_ver ne ""} {
2832  set ide_name_and_ver [string tolower "$custom_ver"]
2833  } elseif {[file exists $proj_conf]} {
2834  set ide_name_and_ver [string tolower [GetIDEFromConf $proj_conf]]
2835  } else {
2836  Msg Error "Configuration file $proj_conf not found."
2837  }
2838 
2839  set ide_name [lindex [regexp -all -inline {\S+} $ide_name_and_ver] 0]
2840 
2841  if {$ide_name eq "vivado" || $ide_name eq "vivado_vitis_classic" || $ide_name eq "vivado_vitis_unified" || $ide_name eq "vitis_unified"} {
2842  set command "vivado"
2843  # A space after the before_tcl_script is important
2844  set before_tcl_script " -nojournal -nolog -mode batch -notrace -source "
2845  set after_tcl_script " -tclargs "
2846  set end_marker ""
2847  } elseif {$ide_name eq "planahead"} {
2848  set command "planAhead"
2849  # A space ater the before_tcl_script is important
2850  set before_tcl_script " -nojournal -nolog -mode batch -notrace -source "
2851  set after_tcl_script " -tclargs "
2852  set end_marker ""
2853  } elseif {$ide_name eq "quartus"} {
2854  set command "quartus_sh"
2855  # A space after the before_tcl_script is important
2856  set before_tcl_script " -t "
2857  set after_tcl_script " "
2858  set end_marker ""
2859  } elseif {$ide_name eq "libero"} {
2860  #I think we need quotes for libero, not sure...
2861 
2862  set command "libero"
2863  set before_tcl_script "SCRIPT:"
2864  set after_tcl_script " SCRIPT_ARGS:\""
2865  set end_marker "\""
2866  } elseif {$ide_name eq "diamond"} {
2867  set command "diamondc"
2868  set before_tcl_script " "
2869  set after_tcl_script " "
2870  set end_marker ""
2871  } elseif {$ide_name eq "vitis_classic"} {
2872  set command "xsct"
2873  # A space after the before_tcl_script is important
2874  set before_tcl_script ""
2875  set after_tcl_script " "
2876  set end_marker ""
2877  } elseif {$ide_name eq "ghdl"} {
2878  set command "ghdl"
2879  set before_tcl_script " "
2880  set after_tcl_script " "
2881  set end_marker ""
2882  } else {
2883  Msg Error "IDE: $ide_name not known."
2884  }
2885 
2886  return [list $command $before_tcl_script $after_tcl_script $end_marker]
2887 }
2888 
2889 ## Get the IDE (Vivado,Quartus,PlanAhead,Libero) version from the conf file she-bang
2890 #
2891 # @param[in] conf_file The hog.conf file
2892 proc GetIDEFromConf {conf_file} {
2893  set f [open $conf_file "r"]
2894  set line [gets $f]
2895  close $f
2896  if {[regexp -all {^\# *(\w*) *(vitis_(?:classic|unified))? *(\d+\.\d+(?:\.\d+)?(?:\.\d+)?)?(_.*)? *$} $line dummy ide vitisflag version patch]} {
2897  if {[info exists vitisflag] && $vitisflag != ""} {
2898  set ide "${ide}_${vitisflag}"
2899  }
2900 
2901  if {[info exists version] && $version != ""} {
2902  set ver $version
2903  } else {
2904  set ver 0.0.0
2905  }
2906  # what shall we do with $patch? ignored for the time being
2907  set ret [list $ide $ver]
2908  } else {
2909  Msg CriticalWarning "The first line of hog.conf should be \#<IDE name> <version>, \
2910  where <IDE name>. is quartus, vivado, planahead, libero, diamond or ghdl, \
2911  and <version> the tool version, e.g. \#vivado 2020.2. Will assume vivado."
2912  set ret [list "vivado" "0.0.0"]
2913  }
2914 
2915  return $ret
2916 }
2917 
2918 # @brief Returns the name of the running IDE
2919 proc GetIDEName {} {
2920  if {[IsISE]} {
2921  return "ISE/PlanAhead"
2922  } elseif {[IsVivado]} {
2923  return "Vivado"
2924  } elseif {[IsQuartus]} {
2925  return "Quartus"
2926  } elseif {[IsLibero]} {
2927  return "Libero"
2928  } elseif {[IsDiamond]} {
2929  return "Diamond"
2930  } else {
2931  return ""
2932  }
2933 }
2934 
2935 ## Returns the version of the IDE (Vivado,Quartus,PlanAhead,Libero) in use
2936 #
2937 # @return the version in string format, e.g. 2020.2
2938 #
2939 proc GetIDEVersion {} {
2940  if {[IsXilinx]} {
2941  # Vivado or planAhead
2942  regexp {\d+\.\d+(\.\d+)?} [version -short] ver
2943  # This regex will cut away anything after the numbers, useful for patched version 2020.1_AR75210
2944  } elseif {[IsQuartus]} {
2945  # Quartus
2946  global quartus
2947  regexp {[\.0-9]+} $quartus(version) ver
2948  } elseif {[IsLibero]} {
2949  # Libero
2950  set ver [get_libero_version]
2951  } elseif {[IsDiamond]} {
2952  # Diamond
2953  regexp {\d+\.\d+(\.\d+)?} [sys_install version] ver
2954  } elseif {[IsVitisClassic]} {
2955  # Vitis Classic
2956  regexp {\d+\.\d+(\.\d+)?} [version] ver
2957  } elseif {[IsVitisUnified]} {
2958  # Vitis Unified
2959  set vitis_output [exec vitis --version 2>@1]
2960  regexp {[Vv]itis\s+v?(\d+\.\d+(?:\.\d+)?)} $vitis_output -> ver
2961  } else {
2962  set ver "0.0.0"
2963  }
2964  return $ver
2965 }
2966 
2967 
2968 ## @brief Returns the real file linked by a soft link
2969 #
2970 # If the provided file is not a soft link, it will give a Warning and return an empty string.
2971 # If the link is broken, will give a warning but still return the linked file
2972 #
2973 # @param[in] link_file The soft link file
2974 proc GetLinkedFile {link_file} {
2975  if {[file type $link_file] eq "link"} {
2976  if {[OS] == "windows"} {
2977  #on windows we need to use readlink because Tcl is broken
2978  lassign [ExecuteRet realpath $link_file] ret msg
2979  lassign [ExecuteRet cygpath -m $msg] ret2 msg2
2980  if {$ret == 0 && $ret2 == 0} {
2981  set real_file $msg2
2982  Msg Debug "Found link file $link_file on Windows, the linked file is: $real_file"
2983  } else {
2984  Msg CriticalWarning "[file normalize $link_file] is a soft link. \
2985  Soft link are not supported on Windows and readlink.exe or cygpath.exe did not work: readlink=$ret: $msg, cygpath=$ret2: $msg2."
2986  set real_file $link_file
2987  }
2988  } else {
2989  #on linux Tcl just works
2990  set linked_file [file link $link_file]
2991  set real_file [file normalize [file dirname $link_file]/$linked_file]
2992  }
2993 
2994  if {![file exists $real_file]} {
2995  Msg Warning "$link_file is a broken link, because the linked file: $real_file does not exist."
2996  }
2997  } else {
2998  Msg Warning "$link file is not a soft link"
2999  set real_file $link_file
3000  }
3001  return $real_file
3002 }
3003 
3004 ## @brief Gets MAX number of Threads property from property.conf file in Top/$proj_name directory.
3005 #
3006 # If property is not set returns default = 1
3007 #
3008 # @param[in] proj_dir: the top folder of the project
3009 #
3010 # @return 1 if property is not set else the value of MaxThreads
3011 #
3012 proc GetMaxThreads {proj_dir} {
3013  set maxThreads 1
3014  if {[file exists $proj_dir/hog.conf]} {
3015  set properties [ReadConf [lindex [GetConfFiles $proj_dir] 0]]
3016  if {[dict exists $properties parameters]} {
3017  set propDict [dict get $properties parameters]
3018  if {[dict exists $propDict MAX_THREADS]} {
3019  set maxThreads [dict get $propDict MAX_THREADS]
3020  }
3021  }
3022  } else {
3023  Msg Warning "File $proj_dir/hog.conf not found. Max threads will be set to default value 1"
3024  }
3025  return $maxThreads
3026 }
3027 
3028 
3029 ## @brief Get a list of all modified the files matching then pattern
3030 #
3031 # @param[in] repo_path the path of the git repository
3032 # @param[in] pattern the pattern with wildcards that files should match
3033 #
3034 # @return a list of all modified files matching the pattern
3035 #
3036 proc GetModifiedFiles {{repo_path "."} {pattern "."}} {
3037  set old_path [pwd]
3038  cd $repo_path
3039  set ret [Git "ls-files --modified $pattern"]
3040  cd $old_path
3041  return $ret
3042 }
3043 
3044 # @brief Gets the command argv list and returns a list of
3045 # options and arguments
3046 # @param[in] argv The command input arguments
3047 # @param[in] parameters The command input parameters
3048 proc GetOptions {argv parameters} {
3049  # Get Options from argv
3050  set arg_list [list]
3051  set param_list [list]
3052  set option_list [list]
3053 
3054  foreach p $parameters {
3055  lappend param_list [lindex $p 0]
3056  }
3057 
3058  set index 0
3059  while {$index < [llength $argv]} {
3060  set arg [lindex $argv $index]
3061  if {[string first - $arg] == 0} {
3062  set option [string trimleft $arg "-"]
3063  incr index
3064  lappend option_list $arg
3065  if {[lsearch -regex $param_list "$option\[.arg]?"] >= 0 } {
3066  if {[lsearch -regex $param_list "$option\[.arg]"] >= 0 } {
3067  lappend option_list [lindex $argv $index]
3068  incr index
3069  }
3070  }
3071  } else {
3072  lappend arg_list $arg
3073  incr index
3074  }
3075  }
3076  Msg Debug "Argv: $argv"
3077  Msg Debug "Options: $option_list"
3078  Msg Debug "Arguments: $arg_list"
3079  return [list $option_list $arg_list]
3080 }
3081 
3082 # return [list $libraries $properties $simlibraries $constraints $srcsets $simsets $consets]
3083 ## @ brief Returns a list of 7 dictionaries: libraries, properties, constraints, and filesets for sources and simulations
3084 #
3085 # The returned dictionaries are libraries, properties, simlibraries, constraints, srcsets, simsets, consets
3086 # - libraries and simlibraries have the library name as keys and a list of filenames as values
3087 # - properties has as file names as keys and a list of properties as values
3088 # - constraints is a dictionary with a single key (sources.con) and a list of constraint files as value
3089 # - srcsets is a dictionary with a fileset name as a key (e.g. sources_1) and a list of libraries as value
3090 # - simsets is a dictionary with a simset name as a key (e.g. sim_1) and a list of libraries as value
3091 # - 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)
3092 #
3093 # Files, libraries and properties are extracted from the current project
3094 #
3095 # @param[in] project_file The project file (for Libero and Diamond)
3096 # @return A list of 7 dictionaries: libraries, properties, constraints, and filesets for sources and simulations
3097 proc GetProjectFiles {{project_file ""}} {
3098  set libraries [dict create]
3099  set simlibraries [dict create]
3100  set constraints [dict create]
3101  set properties [dict create]
3102  set consets [dict create]
3103  set srcsets [dict create]
3104  set simsets [dict create]
3105 
3106  if {[IsVivado]} {
3107  set all_filesets [get_filesets]
3108  set simulator [get_property target_simulator [current_project]]
3109  set top [get_property "top" [current_fileset]]
3110  set topfile [GetTopFile]
3111  dict lappend properties $topfile "top=$top"
3112 
3113  foreach fs $all_filesets {
3114  if {$fs == "utils_1"} {
3115  # Skipping utility fileset
3116  continue
3117  }
3118 
3119  set all_files [get_files -quiet -of_objects [get_filesets $fs]]
3120  set fs_type [get_property FILESET_TYPE [get_filesets $fs]]
3121 
3122  if {$fs_type == "BlockSrcs"} {
3123  # Vivado creates for each ip a blockset... Let's redirect to sources_1
3124  set dict_fs "sources_1"
3125  } else {
3126  set dict_fs $fs
3127  }
3128  foreach f $all_files {
3129  # Ignore files that are part of the vivado/planahead project but would not be reflected
3130  # in list files (e.g. generated products from ip cores)
3131  set ignore 0
3132  # Generated files point to a parent composite file;
3133  # planahead does not have an IS_GENERATED property
3134  if {[IsInList "IS_GENERATED" [list_property [GetFile $f $fs]]]} {
3135  if {[lindex [get_property IS_GENERATED [GetFile $f $fs]] 0] != 0} {
3136  set ignore 1
3137  }
3138  }
3139 
3140  if {[get_property FILE_TYPE [GetFile $f $fs]] == "Configuration Files"} {
3141  set ignore 1
3142  }
3143 
3144 
3145  if {[IsInList "CORE_CONTAINER" [list_property [GetFile $f $fs]]]} {
3146  if {[get_property CORE_CONTAINER [GetFile $f $fs]] != ""} {
3147  if {[file extension $f] == ".xcix"} {
3148  set f [get_property CORE_CONTAINER [GetFile $f $fs]]
3149  } else {
3150  set ignore 1
3151  }
3152  }
3153  }
3154 
3155  if {[IsInList "SCOPED_TO_REF" [list_property [GetFile $f $fs]]]} {
3156  if {[get_property SCOPED_TO_REF [GetFile $f $fs]] != ""} {
3157  dict lappend properties $f "scoped_to_ref=[get_property SCOPED_TO_REF [GetFile $f $fs]]"
3158  }
3159  }
3160 
3161  if {[IsInList "SCOPED_TO_CELLS" [list_property [GetFile $f $fs]]]} {
3162  if {[get_property SCOPED_TO_CELLS [GetFile $f $fs]] != ""} {
3163  dict lappend properties $f "scoped_to_cells=[regsub " " [get_property SCOPED_TO_CELLS [GetFile $f $fs]] ","]"
3164  }
3165  }
3166 
3167  if {[IsInList "PARENT_COMPOSITE_FILE" [list_property [GetFile $f $fs]]]} {
3168  set ignore 1
3169  }
3170 
3171  # Ignore nocattrs.dat for Versal
3172  if {[file tail $f] == "nocattrs.dat"} {
3173  set ignore 1
3174  }
3175 
3176  if {!$ignore} {
3177  if {[file extension $f] != ".coe"} {
3178  set f [file normalize $f]
3179  }
3180  lappend files $f
3181  set type [get_property FILE_TYPE [GetFile $f $fs]]
3182  # Added a -quiet because some files (.v, .sv) don't have a library
3183  set lib [get_property -quiet LIBRARY [GetFile $f $fs]]
3184 
3185  # Type can be complex like VHDL 2008, in that case we want the second part to be a property
3186  Msg Debug "File $f Extension [file extension $f] Type [lindex $type 0]"
3187 
3188  if {[string equal [lindex $type 0] "VHDL"] && [llength $type] == 1} {
3189  set prop "93"
3190  } elseif {[string equal [lindex $type 0] "Block"] && [string equal [lindex $type 1] "Designs"]} {
3191  set type "IP"
3192  set prop ""
3193  } elseif {[string equal $type "SystemVerilog"] && [file extension $f] != ".sv" && [file extension $f] != ".svp"} {
3194  set prop "SystemVerilog"
3195  } elseif {[string equal [lindex $type 0] "XDC"] && [file extension $f] != ".xdc"} {
3196  set prop "XDC"
3197  } elseif {[string equal $type "Verilog Header"] && [file extension $f] != ".vh" && [file extension $f] != ".svh"} {
3198  set prop "verilog_header"
3199  } elseif {[string equal $type "Verilog Template"] && [file extension $f] == ".v" && [file extension $f] != ".sv"} {
3200  set prop "verilog_template"
3201  } else {
3202  set type [lindex $type 0]
3203  set prop ""
3204  }
3205  #If type is "VHDL 2008" we will keep only VHDL
3206  if {![string equal $prop ""]} {
3207  dict lappend properties $f $prop
3208  }
3209  # check where the file is used and add it to prop
3210  if {[string equal $fs_type "SimulationSrcs"]} {
3211  # Simulation sources
3212  if {[string equal $type "VHDL"]} {
3213  set library "${lib}.sim"
3214  } else {
3215  set library "others.sim"
3216  }
3217 
3218  if {[IsInList $library [DictGet $simsets $dict_fs]] == 0} {
3219  dict lappend simsets $dict_fs $library
3220  }
3221 
3222  dict lappend simlibraries $library $f
3223  } elseif {[string equal $type "VHDL"]} {
3224  # VHDL files (both 2008 and 93)
3225  if {[IsInList "${lib}.src" [DictGet $srcsets $dict_fs]] == 0} {
3226  dict lappend srcsets $dict_fs "${lib}.src"
3227  }
3228  dict lappend libraries "${lib}.src" $f
3229  } elseif {[string first "IP" $type] != -1} {
3230  # IPs
3231  if {[IsInList "ips.src" [DictGet $srcsets $dict_fs]] == 0} {
3232  dict lappend srcsets $dict_fs "ips.src"
3233  }
3234  dict lappend libraries "ips.src" $f
3235  Msg Debug "Appending $f to ips.src"
3236  } elseif {[string equal $fs_type "Constrs"]} {
3237  # Constraints
3238  if {[IsInList "sources.con" [DictGet $consets $dict_fs]] == 0} {
3239  dict lappend consets $dict_fs "sources.con"
3240  }
3241  dict lappend constraints "sources.con" $f
3242  } else {
3243  # Verilog and other files
3244  if {[IsInList "others.src" [DictGet $srcsets $dict_fs]] == 0} {
3245  dict lappend srcsets $dict_fs "others.src"
3246  }
3247  dict lappend libraries "others.src" $f
3248  Msg Debug "Appending $f to others.src"
3249  }
3250 
3251  if {[lindex [get_property -quiet used_in_synthesis [GetFile $f $fs]] 0] == 0} {
3252  dict lappend properties $f "nosynth"
3253  }
3254  if {[lindex [get_property -quiet used_in_implementation [GetFile $f $fs]] 0] == 0} {
3255  dict lappend properties $f "noimpl"
3256  }
3257  if {[lindex [get_property -quiet used_in_simulation [GetFile $f $fs]] 0] == 0} {
3258  dict lappend properties $f "nosim"
3259  }
3260  if {[lindex [get_property -quiet IS_MANAGED [GetFile $f $fs]] 0] == 0 && [file extension $f] != ".xcix"} {
3261  dict lappend properties $f "locked"
3262  }
3263  }
3264  }
3265  }
3266 
3267  dict lappend properties "Simulator" [get_property target_simulator [current_project]]
3268  } elseif {[IsLibero] || [IsSynplify]} {
3269  # Open the project file
3270  set file [open $project_file r]
3271  set in_file_manager 0
3272  set top ""
3273  while {[gets $file line] >= 0} {
3274  # Detect the ActiveRoot (Top) module
3275  if {[regexp {^KEY ActiveRoot \"([^\"]+)\"} $line -> value]} {
3276  set top [string range $value 0 [expr {[string first "::" $value] - 1}]]
3277  }
3278 
3279  # Detect the start of the FileManager section
3280  if {[regexp {^LIST FileManager} $line]} {
3281  set in_file_manager 1
3282  continue
3283  }
3284 
3285  # Detect the end of the FileManager section
3286  if {$in_file_manager && [regexp {^ENDLIST} $line]} {
3287  break
3288  }
3289 
3290  # Extract file paths from the VALUE entries
3291  if {$in_file_manager && [regexp {^VALUE \"([^\"]+)} $line -> value]} {
3292  # lappend source_files [remove_after_comma $filepath]
3293  # set file_path ""
3294  lassign [split $value ,] file_path file_type
3295  # Extract file properties
3296  set parent_file ""
3297  set library "others"
3298  while {[gets $file line] >= 0} {
3299  if {$line == "ENDFILE"} {
3300  break
3301  }
3302  regexp {^LIBRARY=\"([^\"]+)} $line -> library
3303  regexp {^PARENT=\"([^\"]+)} $line -> parent_file
3304  }
3305  Msg Debug "Found file ${file_path} in project.."
3306  if {$parent_file == ""} {
3307  if {$file_type == "hdl"} {
3308  # VHDL files (both 2008 and 93)
3309  if {[IsInList "${library}.src" [DictGet $srcsets "sources_1"]] == 0} {
3310  dict lappend srcsets "sources_1" "${library}.src"
3311  }
3312  dict lappend libraries "${library}.src" $file_path
3313  # Check if file is top_module in project
3314  Msg Debug "File $file_path module [GetModuleName $file_path]"
3315 
3316  if {[GetModuleName $file_path] == [string tolower $top] && $top != ""} {
3317  Msg Debug "Found top module $top in $file_path"
3318  dict lappend properties $file_path "top=$top"
3319  }
3320  } elseif {$file_type == "tb_hdl"} {
3321  if {[IsInList "${library}.sim" [DictGet $simsets "sim_1"]] == 0} {
3322  dict lappend simsets "sim_1" "${library}.sim"
3323  }
3324  dict lappend simlibraries "${library}.sim" $file_path
3325  } elseif {$file_type == "io_pdc" || $file_type == "sdc"} {
3326  if {[IsInList "sources.con" [DictGet $consets "constrs_1"]] == 0} {
3327  dict lappend consets "constrs_1" "sources.con"
3328  }
3329  dict lappend constraints "sources.con" $file_path
3330  }
3331  }
3332  }
3333  }
3334  } elseif {[IsDiamond]} {
3335  # Open the Diamond XML project file content
3336  set fileData [read [open $project_file]]
3337 
3338  set project_path [file dirname $project_file]
3339 
3340  # Remove XML declaration
3341  regsub {<\?xml.*\?>} $fileData "" fileData
3342 
3343  # Extract the Implementation block
3344  regexp {<Implementation.*?>(.*)</Implementation>} $fileData -> implementationContent
3345 
3346  # Extract each Source block one by one
3347  set sources {}
3348  set sourceRegex {<Source name="([^"]*?)" type="([^"]*?)" type_short="([^"]*?)".*?>(.*?)</Source>}
3349 
3350  set optionsRegex {<Options(.*?)\/>}
3351  regexp $optionsRegex $implementationContent -> prj_options
3352  foreach option $prj_options {
3353  if {[regexp {^top=\"([^\"]+)\"} $option match result]} {
3354  set top $result
3355  }
3356  }
3357 
3358  while {[regexp $sourceRegex $implementationContent match name type type_short optionsContent]} {
3359  Msg Debug "Found file ${name} in project..."
3360  set file_path [file normalize $project_path/$name]
3361  # Extract the Options attributes
3362  set optionsRegex {<Options(.*?)\/>}
3363  regexp $optionsRegex $optionsContent -> options
3364  set library "others"
3365  set isSV 0
3366  foreach option $options {
3367  if {[string first "System Verilog" $option]} {
3368  set isSV 1
3369  }
3370  if {[regexp {^lib=\"([^\"]+)\"} $option match1 result]} {
3371  set library $result
3372  }
3373  }
3374  set ext ".src"
3375  if {[regexp {syn_sim="([^"]*?)"} $match match_sim simonly]} {
3376  set ext ".sim"
3377  }
3378 
3379  # Append VHDL files
3380  if {$type_short == "VHDL" || $type_short == "Verilog" || $type_short == "IPX"} {
3381  if {$ext == ".src"} {
3382  if {[IsInList "${library}${ext}" [DictGet $srcsets "sources_1"]] == 0} {
3383  dict lappend srcsets "sources_1" "${library}${ext}"
3384  }
3385  dict lappend libraries "${library}${ext}" $file_path
3386  } elseif {$ext == ".sim"} {
3387  if {[IsInList "${library}.sim" [DictGet $simsets "sim_1"]] == 0} {
3388  dict lappend simsets "sim_1" "${library}.sim"
3389  }
3390  dict lappend simlibraries "${library}.sim" $file_path
3391  }
3392  # Check if file is top_module in project
3393  Msg Debug "File $file_path module [GetModuleName $file_path]"
3394 
3395  if {[GetModuleName $file_path] == $top && $top != ""} {
3396  Msg Debug "Found top module $top in $file_path"
3397  dict lappend properties $file_path "top=$top"
3398  }
3399  } elseif {$type_short == "SDC"} {
3400  if {[IsInList "sources.con" [DictGet $consets "constrs_1"]] == 0} {
3401  dict lappend consets "constrs_1" "sources.con"
3402  }
3403  dict lappend constraints "sources.con" $file_path
3404  }
3405 
3406  # Remove the processed Source block from the implementation content
3407  regsub -- $match $implementationContent "" implementationContent
3408  }
3409  }
3410  return [list $libraries $properties $simlibraries $constraints $srcsets $simsets $consets]
3411 }
3412 
3413 #"
3414 ## Get the Project flavour
3415 #
3416 # @param[in] proj_name The project name
3417 proc GetProjectFlavour {proj_name} {
3418  # Calculating flavour if any
3419  set flavour [string map {. ""} [file extension $proj_name]]
3420  if {$flavour != ""} {
3421  if {[string is integer $flavour]} {
3422  Msg Info "Project $proj_name has flavour = $flavour, the generic variable FLAVOUR will be set to $flavour"
3423  } else {
3424  Msg Warning "Project name has a unexpected non numeric extension, flavour will be set to -1"
3425  set flavour -1
3426  }
3427  } else {
3428  set flavour -1
3429  }
3430  return $flavour
3431 }
3432 
3433 ## Get the project version
3434 #
3435 # @param[in] proj_dir: The top folder of the project of which all the version must be calculated
3436 # @param[in] repo_path: The top folder of the repository
3437 # @param[in] ext_path: path for external libraries
3438 # @param[in] sim: if enabled, check the version also for the simulation files
3439 #
3440 # @return returns the project version
3441 proc GetProjectVersion {proj_dir repo_path {ext_path ""} {sim 0}} {
3442  if {![file exists $proj_dir]} {
3443  Msg CriticalWarning "$proj_dir not found"
3444  return -1
3445  }
3446  set old_dir [pwd]
3447  cd $proj_dir
3448 
3449  #The latest version the repository
3450  set v_last [ExtractVersionFromTag [Git {describe --abbrev=0 --match "v*"}]]
3451  lassign [GetRepoVersions $proj_dir $repo_path $ext_path $sim] sha ver
3452  if {$sha == 0} {
3453  Msg Warning "Repository is not clean"
3454  cd $old_dir
3455  return -1
3456  }
3457 
3458  #The project version
3459  set v_proj [ExtractVersionFromTag v[HexVersionToString $ver]]
3460  set comp [CompareVersions $v_proj $v_last]
3461  Msg Debug "Project version $v_proj, latest tag $v_last"
3462  if {$comp == 1} {
3463  Msg Info "The specified project was modified since official version."
3464  set ret 0
3465  } else {
3466  set ret v[HexVersionToString $ver]
3467  }
3468 
3469  if {$comp == 0} {
3470  Msg Info "The specified project was modified in the latest official version $ret"
3471  } elseif {$comp == -1} {
3472  Msg Info "The specified project was modified in a past official version $ret"
3473  }
3474 
3475  cd $old_dir
3476  return $ret
3477 }
3478 
3479 ## Get the versions for all libraries, submodules, etc. for a given project
3480 #
3481 # @param[in] proj_dir: The project directory containing the conf file or the the tcl file
3482 # @param[in] repo_path: top path of the repository
3483 # @param[in] ext_path: path for external libraries
3484 # @param[in] sim: if enabled, check the version also for the simulation files
3485 #
3486 # @return a list containing all the versions: global, top (hog.conf, pre and post tcl scripts, etc.), constraints,
3487 # libraries, submodules, external, ipbus xml, user ip repos
3488 proc GetRepoVersions {proj_dir repo_path {ext_path ""} {sim 0}} {
3489  if {[catch {package require cmdline} ERROR]} {
3490  puts "$ERROR\n If you are running this script on tclsh, you can fix this by installing 'tcllib'"
3491  return 1
3492  }
3493 
3494  set old_path [pwd]
3495  set conf_files [GetConfFiles $proj_dir]
3496 
3497  # This will be the list of all the SHAs of this project, the most recent will be picked up as GLOBAL SHA
3498  set SHAs ""
3499  set versions ""
3500 
3501  # Hog submodule
3502  cd $repo_path
3503 
3504  # Append the SHA in which Hog submodule was changed, not the submodule SHA
3505  lappend SHAs [GetSHA {Hog}]
3506  lappend versions [GetVerFromSHA $SHAs $repo_path]
3507 
3508  cd "$repo_path/Hog"
3509  if {[Git {status --untracked-files=no --porcelain}] eq ""} {
3510  Msg Info "Hog submodule [pwd] clean."
3511  lassign [GetVer ./] hog_ver hog_hash
3512  } else {
3513  Msg CriticalWarning "Hog submodule [pwd] not clean, commit hash will be set to 0."
3514  set hog_hash "0000000"
3515  set hog_ver "00000000"
3516  }
3517 
3518  cd $proj_dir
3519 
3520  # Collect all project-relevant files; the clean check will be deferred until all files are known
3521  set project_files $conf_files
3522  lappend project_files $repo_path/Hog
3523 
3524  # Top project directory
3525  lassign [GetVer [join $conf_files]] top_ver top_hash
3526  lappend SHAs $top_hash
3527  lappend versions $top_ver
3528 
3529  # Read list files
3530  set libs ""
3531  set vers ""
3532  set hashes ""
3533  # Specify sha_mode 1 for GetHogFiles to get all the files, including the list-files themselves
3534  lassign [GetHogFiles -list_files "*.src" -sha_mode "./list/" $repo_path] src_files dummy
3535  dict for {f files} $src_files {
3536  # library names have a .src extension in values returned by GetHogFiles
3537  set name [file rootname [file tail $f]]
3538  if {[file ext $f] == ".oth"} {
3539  set name "OTHERS"
3540  }
3541  lassign [GetVer $files] ver hash
3542  # Msg Info "Found source list file $f, version: $ver commit SHA: $hash"
3543  lappend libs $name
3544  lappend versions $ver
3545  lappend vers $ver
3546  lappend hashes $hash
3547  lappend SHAs $hash
3548  lappend project_files $f {*}$files
3549  }
3550 
3551  # Read constraint list files
3552  set cons_hashes ""
3553  # Specify sha_mode 1 for GetHogFiles to get all the files, including the list-files themselves
3554  lassign [GetHogFiles -list_files "*.con" -sha_mode "./list/" $repo_path] cons_files dummy
3555  dict for {f files} $cons_files {
3556  #library names have a .con extension in values returned by GetHogFiles
3557  set name [file rootname [file tail $f]]
3558  lassign [GetVer $files] ver hash
3559  #Msg Info "Found constraint list file $f, version: $ver commit SHA: $hash"
3560  if {$hash eq ""} {
3561  Msg CriticalWarning "Constraints file $f not found in Git."
3562  }
3563  lappend cons_hashes $hash
3564  lappend SHAs $hash
3565  lappend versions $ver
3566  lappend project_files $f {*}$files
3567  }
3568 
3569  # Read simulation list files
3570  if {$sim == 1} {
3571  set sim_hashes ""
3572  # Specify sha_mode 1 for GetHogFiles to get all the files, including the list-files themselves
3573  lassign [GetHogFiles -list_files "*.sim" -sha_mode "./list/" $repo_path] sim_files dummy
3574  dict for {f files} $sim_files {
3575  #library names have a .sim extension in values returned by GetHogFiles
3576  set name [file rootname [file tail $f]]
3577  lassign [GetVer $files] ver hash
3578  #Msg Info "Found simulation list file $f, version: $ver commit SHA: $hash"
3579  lappend sim_hashes $hash
3580  lappend SHAs $hash
3581  lappend versions $ver
3582  lappend project_files $f {*}$files
3583  }
3584  }
3585 
3586 
3587  #Of all the constraints we get the most recent
3588  if {[IsInList {} $cons_hashes]} {
3589  #" Fake comment for Visual Code Studio
3590  Msg CriticalWarning "No hashes found for constraints files (not in git)"
3591  set cons_hash ""
3592  } else {
3593  set cons_hash [string tolower [Git "log --format=%h -1 $cons_hashes"]]
3594  }
3595  set cons_ver [GetVerFromSHA $cons_hash $repo_path]
3596  #Msg Info "Among all the constraint list files, if more than one, the most recent version was chosen: $cons_ver commit SHA: $cons_hash"
3597 
3598  # Read external library files
3599  set ext_hashes ""
3600  set ext_files [glob -nocomplain "./list/*.ext"]
3601  set ext_names ""
3602 
3603  foreach f $ext_files {
3604  set name [file rootname [file tail $f]]
3605  set hash [GetSHA $f]
3606  #Msg Info "Found source file $f, commit SHA: $hash"
3607  lappend ext_names $name
3608  lappend ext_hashes $hash
3609  lappend SHAs $hash
3610  set ext_ver [GetVerFromSHA $hash $repo_path]
3611  lappend versions $ext_ver
3612  lappend project_files $f
3613 
3614  set fp [open $f r]
3615  set file_data [read $fp]
3616  close $fp
3617  set data [split $file_data "\n"]
3618  #Msg Info "Checking checksums of external library files in $f"
3619  foreach line $data {
3620  if {![regexp {^ *$} $line] & ![regexp {^ *\#} $line]} {
3621  #Exclude empty lines and comments
3622  set file_and_prop [regexp -all -inline {\S+} $line]
3623  set hdlfile [lindex $file_and_prop 0]
3624  set hdlfile $ext_path/$hdlfile
3625  if {[file exists $hdlfile]} {
3626  set hash [lindex $file_and_prop 1]
3627  set current_hash [Md5Sum $hdlfile]
3628  if {[string first $hash $current_hash] == -1} {
3629  Msg CriticalWarning "File $hdlfile has a wrong hash. Current checksum: $current_hash, expected: $hash"
3630  }
3631  }
3632  }
3633  }
3634  }
3635 
3636  # Ipbus XML
3637  if {[llength [glob -nocomplain ./list/*.ipb]] > 0} {
3638  #Msg Info "Found IPbus XML list file, evaluating version and SHA of listed files..."
3639  lassign [GetHogFiles -list_files "*.ipb" -sha_mode "./list/" $repo_path] xml_files dummy
3640  set xml_source_files [dict get $xml_files "xml.ipb"]
3641  lassign [GetVer $xml_source_files] xml_ver xml_hash
3642  lappend SHAs $xml_hash
3643  lappend versions $xml_ver
3644  lappend project_files {*}[glob ./list/*.ipb] {*}$xml_source_files
3645 
3646  #Msg Info "Found IPbus XML SHA: $xml_hash and version: $xml_ver."
3647  } else {
3648  Msg Info "This project does not use IPbus XMLs"
3649  set xml_ver ""
3650  set xml_hash ""
3651  }
3652 
3653  set user_ip_repos ""
3654  set user_ip_repo_hashes ""
3655  set user_ip_repo_vers ""
3656  # User IP Repository (Vivado only, hog.conf only)
3657  if {[file exists [lindex $conf_files 0]]} {
3658  set PROPERTIES [ReadConf [lindex $conf_files 0]]
3659  if {[dict exists $PROPERTIES main]} {
3660  set main [dict get $PROPERTIES main]
3661  dict for {p v} $main {
3662  if {[string tolower $p] == "ip_repo_paths"} {
3663  foreach repo $v {
3664  if {[file isdirectory "$repo_path/$repo"]} {
3665  set repo_file_list [glob -nocomplain "$repo_path/$repo/*"]
3666  if {[llength $repo_file_list] == 0} {
3667  Msg Warning "IP_REPO_PATHS property set to $repo in hog.conf but directory is empty."
3668  } else {
3669  lappend user_ip_repos "$repo_path/$repo"
3670  }
3671  }
3672  }
3673  }
3674  }
3675  }
3676 
3677  # For each defined IP repository get hash and version if directory exists and not empty
3678  foreach repo $user_ip_repos {
3679  if {[file isdirectory $repo]} {
3680  set repo_file_list [glob -nocomplain "$repo/*"]
3681  if {[llength $repo_file_list] != 0} {
3682  lassign [GetVer $repo] ver sha
3683  lappend user_ip_repo_hashes $sha
3684  lappend user_ip_repo_vers $ver
3685  lappend versions $ver
3686  lappend project_files $repo
3687  } else {
3688  Msg Warning "IP_REPO_PATHS property set to $repo in hog.conf but directory is empty."
3689  }
3690  } else {
3691  Msg Warning "IP_REPO_PATHS property set to $repo in hog.conf but directory does not exist."
3692  }
3693  }
3694  }
3695 
3696 
3697  # Check cleanliness only for the files that belong to this project
3698  if {[Git "status --untracked-files=no --porcelain" $project_files] eq ""} {
3699  Msg Info "Project-relevant files are clean."
3700  set clean 1
3701  } else {
3702  Msg CriticalWarning "Project-relevant files not clean, commit hash and version will be set to 0."
3703  set clean 0
3704  }
3705 
3706  #The global SHA and ver is the most recent among everything
3707  if {$clean == 1} {
3708  set found 0
3709  while {$found == 0} {
3710  set global_commit [Git "log --format=%h -1 --abbrev=7 $SHAs"]
3711  foreach sha $SHAs {
3712  set found 1
3713  if {![IsCommitAncestor $sha $global_commit]} {
3714  set common_child [FindCommonGitChild $global_commit $sha]
3715  if {$common_child == 0} {
3716  Msg CriticalWarning "The commit $sha is not an ancestor of the global commit $global_commit, which is OK. \
3717  But $sha and $global_commit do not have any common child, which is NOT OK. \
3718  This is probably do to a REBASE that is forbidden in Hog methodology as it changes git history. \
3719  Hog cannot guarantee the accuracy of the SHAs. \
3720  A way to fix this is to make a commit that touches all the projects in the repositories (e.g. change the Hog version), \
3721  but please do not rebase in the official branches in the future."
3722  } else {
3723  Msg Info "The commit $sha is not an ancestor of the global commit $global_commit, adding the first common child $common_child instead..."
3724  lappend SHAs $common_child
3725  }
3726  set found 0
3727 
3728  break
3729  }
3730  }
3731  }
3732  set global_version [FindNewestVersion $versions]
3733  } else {
3734  set global_commit "0000000"
3735  set global_version "00000000"
3736  }
3737 
3738  cd $old_path
3739 
3740  set top_hash [format %+07s $top_hash]
3741  set cons_hash [format %+07s $cons_hash]
3742  return [list $global_commit $global_version \
3743  $hog_hash $hog_ver $top_hash $top_ver \
3744  $libs $hashes $vers $cons_ver $cons_hash \
3745  $ext_names $ext_hashes $xml_hash $xml_ver \
3746  $user_ip_repos $user_ip_repo_hashes $user_ip_repo_vers]
3747 }
3748 
3749 ## @brief Get git SHA of a subset of list file
3750 #
3751 # @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
3752 #
3753 # @return the value of the desired SHA
3754 #
3755 proc GetSHA {{path ""}} {
3756  if {$path == ""} {
3757  lassign [GitRet {log --format=%h --abbrev=7 -1}] status result
3758  if {$status == 0} {
3759  return [string tolower $result]
3760  } else {
3761  Msg Error "Something went wrong while finding the latest SHA. Does the repository have a commit?"
3762  exit 1
3763  }
3764  }
3765 
3766  # Get repository top level
3767  set repo_path [lindex [Git {rev-parse --show-toplevel}] 0]
3768  set paths {}
3769  # Retrieve the list of submodules in the repository
3770  foreach f $path {
3771  set file_in_module 0
3772  if {[file exists $repo_path/.gitmodules]} {
3773  lassign [GitRet "config --file $repo_path/.gitmodules --get-regexp path"] status result
3774  if {$status == 0} {
3775  set submodules [split $result "\n"]
3776  } else {
3777  set submodules ""
3778  Msg Warning "Something went wrong while trying to find submodules: $result"
3779  }
3780 
3781  foreach mod $submodules {
3782  set module [lindex $mod 1]
3783  if {[string first "$repo_path/$module" $f] == 0} {
3784  # File is in a submodule. Append
3785  set file_in_module 1
3786  lappend paths "$repo_path/$module"
3787  break
3788  }
3789  }
3790  }
3791  if {$file_in_module == 0} {
3792  #File is not in a submodule
3793  lappend paths $f
3794  }
3795  }
3796 
3797  lassign [GitRet {log --format=%h --abbrev=7 -1} $paths] status result
3798  if {$status == 0} {
3799  return [string tolower $result]
3800  } else {
3801  Msg Error "Something went wrong while finding the latest SHA. Does the repository have a commit?"
3802  exit 1
3803  }
3804  return [string tolower $result]
3805 }
3806 
3807 ## @brief Returns the list of Simulators supported by Vivado
3808 proc GetSimulators {} {
3809  set SIMULATORS [list "modelsim" "questa" "riviera" "activehdl" "ies" "vcs"]
3810  return $SIMULATORS
3811 }
3812 
3813 ## @brief Return the path to the active top file
3814 proc GetTopFile {} {
3815  if {[IsVivado]} {
3816  set compile_order_prop [get_property source_mgmt_mode [current_project]]
3817  if {$compile_order_prop ne "All"} {
3818  Msg CriticalWarning "Compile order is not set to automatic, setting it now..."
3819  set_property source_mgmt_mode All [current_project]
3820  update_compile_order -fileset sources_1
3821  }
3822  return [lindex [get_files -quiet -compile_order sources -used_in synthesis -filter {FILE_TYPE =~ "VHDL*" || FILE_TYPE =~ "*Verilog*" }] end]
3823  } elseif {[IsISE]} {
3824  debug::design_graph_mgr -create [current_fileset]
3825  debug::design_graph -add_fileset [current_fileset]
3826  debug::design_graph -update_all
3827  return [lindex [debug::design_graph -get_compile_order] end]
3828  } else {
3829  Msg Error "GetTopFile not yet implemented for this IDE"
3830  }
3831 }
3832 
3833 ## @brief Return the name of the active top module
3834 proc GetTopModule {} {
3835  if {[IsXilinx]} {
3836  return [get_property top [current_fileset]]
3837  } else {
3838  Msg Error "GetTopModule not yet implemented for this IDE"
3839  }
3840 }
3841 
3842 ## @brief Get git version and commit hash of a subset of files
3843 #
3844 # @param[in] path list file or path containing the subset of files whose latest commit hash will be returned
3845 #
3846 # @return a list: the git SHA, the version in hex format
3847 #
3848 proc GetVer {path {force_develop 0} {verbose 1}} {
3849  set SHA [GetSHA $path]
3850  #oldest tag containing SHA
3851  if {$SHA eq ""} {
3852  Msg CriticalWarning "Empty SHA found for ${path}. Commit to Git to resolve this warning."
3853  }
3854  set old_path [pwd]
3855  set p [lindex $path 0]
3856  if {[file isdirectory $p]} {
3857  cd $p
3858  } else {
3859  cd [file dirname $p]
3860  }
3861  set repo_path [Git {rev-parse --show-toplevel}]
3862  cd $old_path
3863 
3864  return [list [GetVerFromSHA $SHA $repo_path $force_develop $verbose] $SHA]
3865 }
3866 
3867 ## @brief Get git version and commit hash of a specific commit give the SHA
3868 #
3869 # @param[in] SHA the git SHA of the commit
3870 # @param[in] repo_path the path of the repository, this is used to open the Top/repo.conf file
3871 # @param[in] force_develop Force a tag for the develop branch (increase m)
3872 # @param[in] verbose Print extra information
3873 #
3874 # @return a list: the git SHA, the version in hex format
3875 #
3876 proc GetVerFromSHA {SHA repo_path {force_develop 0} {verbose 1}} {
3877  if {$SHA eq ""} {
3878  Msg CriticalWarning "Empty SHA found"
3879  set ver "v0.0.0"
3880  } else {
3881  lassign [GitRet "tag --sort=creatordate --contain $SHA -l v*.*.* -l b*v*.*.*"] status result
3882 
3883  if {$status == 0} {
3884  if {[regexp {^ *$} $result]} {
3885  # We do not want the most recent tag, we want the biggest value
3886  lassign [GitRet "log --oneline --pretty=\"%d\""] status2 tag_list
3887  #Msg Status "List of all tags including $SHA: $tag_list."
3888  #cleanup the list and get only the tags
3889  set pattern {tag: v\d+\.\d+\.\d+}
3890  set real_tag_list {}
3891  foreach x $tag_list {
3892  set x_untrimmed [regexp -all -inline $pattern $x]
3893  regsub "tag: " $x_untrimmed "" x_trimmed
3894  set tt [lindex $x_trimmed 0]
3895  if {![string equal $tt ""]} {
3896  lappend real_tag_list $tt
3897  #puts "<$tt>"
3898  }
3899  }
3900  Msg Debug "Cleaned up list: $real_tag_list."
3901  # Sort the tags in version order
3902  set sorted_tags [lsort -decreasing -command CompareVersions $real_tag_list]
3903 
3904  Msg Debug "Sorted Tag list: $sorted_tags"
3905  # Select the newest tag in terms of number, not time
3906  set tag [lindex $sorted_tags 0]
3907 
3908  # Msg Debug "Chosen Tag $tag"
3909  set pattern {v\d+\.\d+\.\d+}
3910  if {![regexp $pattern $tag]} {
3911  Msg CriticalWarning "No Hog version tags found in this repository."
3912  set ver v0.0.0
3913  } else {
3914  lassign [ExtractVersionFromTag $tag] M m p mr
3915  # Open repo.conf and check prefixes
3916  set repo_conf $repo_path/Top/repo.conf
3917 
3918  # Check if the develop/master scheme is used and where is the merge directed to
3919  # Default values
3920  set hotfix_prefix "hotfix/"
3921  set minor_prefix "minor_version/"
3922  set major_prefix "major_version/"
3923  set is_hotfix 0
3924  set enable_develop_branch $force_develop
3925 
3926  set branch_name [Git {rev-parse --abbrev-ref HEAD}]
3927 
3928  if {[file exists $repo_conf]} {
3929  set PROPERTIES [ReadConf $repo_conf]
3930  # [main] section
3931  if {[dict exists $PROPERTIES main]} {
3932  set mainDict [dict get $PROPERTIES main]
3933 
3934  # ENABLE_DEVELOP_ BRANCH property
3935  if {[dict exists $mainDict ENABLE_DEVELOP_BRANCH]} {
3936  set enable_develop_branch [dict get $mainDict ENABLE_DEVELOP_BRANCH]
3937  }
3938  # More properties in [main] here ...
3939  }
3940 
3941  # [prefixes] section
3942  if {[dict exists $PROPERTIES prefixes]} {
3943  set prefixDict [dict get $PROPERTIES prefixes]
3944 
3945  if {[dict exists $prefixDict HOTFIX]} {
3946  set hotfix_prefix [dict get $prefixDict HOTFIX]
3947  }
3948  if {[dict exists $prefixDict MINOR_VERSION]} {
3949  set minor_prefix [dict get $prefixDict MINOR_VERSION]
3950  }
3951  if {[dict exists $prefixDict MAJOR_VERSION]} {
3952  set major_prefix [dict get $prefixDict MAJOR_VERSION]
3953  }
3954  # More properties in [prefixes] here ...
3955  }
3956  }
3957 
3958  if {[string match "HEAD" $branch_name]} {
3959  if {$verbose == 1} {
3960  Msg Warning "Detached HEAD detected - attempting to find branch name"
3961  }
3962  # if the branch_name is HEAD (not a legal branch name btw)
3963  # then the branch has been checked out in a detached head state
3964  # this is a fallback condition to enable finding the branch name that the commit is linked too
3965  set log_refs [Git {show -s --pretty=%D HEAD}]
3966  set branch_list [split $log_refs ","]
3967  Msg Debug "list of possible branch refs $log_refs"
3968 
3969  # iterate over all possible refs and match against all prefix types
3970  # set branch name as matched prefix if and only if one match is found
3971 
3972  set match_count 0
3973  set match_prefixes [list $hotfix_prefix $minor_prefix $major_prefix]
3974  set prev_branch_name $branch_name
3975 
3976  foreach br $branch_list {
3977  foreach pr $match_prefixes {
3978  if {[string match "$pr*" [string trim $br]]} {
3979  set branch_name [string trim $br]
3980  incr match_count 1
3981  }
3982  }
3983  }
3984 
3985  if {!$match_count == 1} {
3986  set branch_name $prev_branch_name
3987  if {$verbose == 1} {
3988  Msg Warning "Branch name not found. Using $branch_name"
3989  }
3990  } else {
3991  if {$verbose == 1} {
3992  Msg Info "Branch name found: $branch_name"
3993  }
3994  }
3995  }
3996 
3997  if {$enable_develop_branch == 1} {
3998  if {[string match "$hotfix_prefix*" $branch_name]} {
3999  set is_hotfix 1
4000  }
4001  }
4002 
4003  if {[string match "$major_prefix*" $branch_name]} {
4004  # If major prefix is used, we increase M regardless of anything else
4005  set version_level major
4006  } elseif {[string match "$minor_prefix*" $branch_name] || ($enable_develop_branch == 1 && $is_hotfix == 0)} {
4007  # 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
4008  set version_level minor
4009  } else {
4010  # 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
4011  set version_level patch
4012  }
4013 
4014  if {$M == -1} {
4015  Msg CriticalWarning "Tag $tag does not contain a Hog compatible version in this repository."
4016  exit
4017  #set ver v0.0.0
4018  } elseif {$mr == 0} {
4019  switch $version_level {
4020  minor {
4021  incr m
4022  set p 0
4023  }
4024  major {
4025  incr M
4026  set m 0
4027  set p 0
4028  }
4029  default {
4030  incr p
4031  }
4032  }
4033  } else {
4034  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."
4035  }
4036  set ver v$M.$m.$p
4037  }
4038  } else {
4039  #The tag in $result contains the current SHA
4040  set vers [split $result "\n"]
4041  set ver [lindex $vers 0]
4042  foreach v $vers {
4043  if {[regexp {^v.*$} $v]} {
4044  set un_ver $ver
4045  set ver $v
4046  break
4047  }
4048  }
4049  }
4050  } else {
4051  Msg CriticalWarning "Error while trying to find tag for $SHA"
4052  set ver "v0.0.0"
4053  }
4054  }
4055  lassign [ExtractVersionFromTag $ver] M m c mr
4056 
4057  if {$mr > -1} {
4058  # Candidate tab
4059  set M [format %02X $M]
4060  set m [format %02X $m]
4061  set c [format %04X $c]
4062  } elseif {$M > -1} {
4063  # official tag
4064  set M [format %02X $M]
4065  set m [format %02X $m]
4066  set c [format %04X $c]
4067  } else {
4068  Msg Warning "Tag does not contain a properly formatted version: $ver"
4069  set M [format %02X 0]
4070  set m [format %02X 0]
4071  set c [format %04X 0]
4072  }
4073 
4074  return $M$m$c
4075 }
4076 
4077 ## @brief Handle git commands
4078 #
4079 #
4080 # @param[in] command: the git command to be run including refs (branch, tags, sha, etc.), except files.
4081 # @param[in] files: files given to git as argument. They will always be separated with -- to avoid weird accidents
4082 #
4083 # @returns the output of the git command
4084 proc Git {command {files ""}} {
4085  lassign [GitRet $command $files] ret result
4086  if {$ret != 0} {
4087  Msg Error "Code $ret returned by git running: $command -- $files"
4088  }
4089 
4090  return $result
4091 }
4092 
4093 
4094 # @brief Get the name of the module in a HDL file. If module is not found, it returns an empty string
4095 #
4096 # @param[in] filename The name of the hdl file
4097 
4098 proc GetModuleName {filename} {
4099  # Check if the file exists
4100  if {![file exists $filename]} {
4101  Msg CriticalWarning "Error: File $filename does not exist."
4102  return ""
4103  }
4104 
4105  # Open the file for reading
4106  set fileId [open $filename r]
4107 
4108  # Read the content of the file
4109  set file_content [read $fileId]
4110 
4111  # Close the file
4112  close $fileId
4113 
4114 
4115  if {[file extension $filename] == ".vhd" || [file extension $filename] == ".vhdl"} {
4116  # Convert the file content to lowercase for case-insensitive matching
4117  set file_content [string tolower $file_content]
4118  # Regular expression to match the entity name after the 'entity' keyword
4119  set pattern {(?m)^\s*entity\s+(\S+)\s+is}
4120  } elseif {[file extension $filename] == ".v" || [file extension $filename] == ".sv"} {
4121  # Regular expression to match the module name after the 'module' keyword
4122  set pattern {\n\s*module\s*(\w+)(\s*|\(|\n)}
4123  } else {
4124  Msg Debug "File is neither VHDL nor Verilog... Returning empty string..."
4125  return "'"
4126  }
4127 
4128  # Search for the module name using the regular expression
4129  if {[regexp $pattern $file_content match module_name]} {
4130  return $module_name
4131  } else {
4132  Msg Debug "No module was found in $filename. Returning an empty string..."
4133  return ""
4134  }
4135 }
4136 
4137 ## Get a dictionary of verilog generics with their types for a given file
4138 #
4139 # @param[in] file File to read Generics from
4140 proc GetVerilogGenerics {file} {
4141  set fp [open $file r]
4142  set data [read $fp]
4143  close $fp
4144  set lines []
4145 
4146  # read in the verilog file and remove comments
4147  foreach line [split $data "\n"] {
4148  regsub "^\\s*\/\/.*" $line "" line
4149  regsub "(.*)\/\/.*" $line {\1} line
4150  if {![string equal $line ""]} {
4151  append lines $line " "
4152  }
4153  }
4154 
4155  # remove block comments also /* */
4156  regsub -all {/\*.*\*/} $lines "" lines
4157 
4158  # create a list of characters to split for tokenizing
4159  set punctuation [list]
4160  foreach char [list "(" ")" ";" "," " " "!" "<=" ":=" "=" "\[" "\]"] {
4161  lappend punctuation $char "\000$char\000"
4162  }
4163 
4164  # split the file into tokens
4165  set tokens [split [string map $punctuation $lines] \000]
4166 
4167  set parameters [dict create]
4168 
4169  set PARAM_NAME 1
4170  set PARAM_VALUE 2
4171  set LEXING 3
4172  set PARAM_WIDTH 4
4173  set state $LEXING
4174 
4175  # loop over the generic lines
4176  foreach token $tokens {
4177  set token [string trim $token]
4178  if {![string equal "" $token]} {
4179  if {[string equal [string tolower $token] "parameter"]} {
4180  set state $PARAM_NAME
4181  } elseif {[string equal $token ")"] || [string equal $token ";"]} {
4182  set state $LEXING
4183  } elseif {$state == $PARAM_WIDTH} {
4184  if {[string equal $token "\]"]} {
4185  set state $PARAM_NAME
4186  }
4187  } elseif {$state == $PARAM_VALUE} {
4188  if {[string equal $token ","]} {
4189  set state $PARAM_NAME
4190  } elseif {[string equal $token ";"]} {
4191  set state $LEXING
4192  } else {
4193 
4194  }
4195  } elseif {$state == $PARAM_NAME} {
4196  if {[string equal $token "="]} {
4197  set state $PARAM_VALUE
4198  } elseif {[string equal $token "\["]} {
4199  set state $PARAM_WIDTH
4200  } elseif {[string equal $token ","]} {
4201  set state $PARAM_NAME
4202  } elseif {[string equal $token ";"]} {
4203  set state $LEXING
4204  } elseif {[string equal $token ")"]} {
4205  set state $LEXING
4206  } else {
4207  dict set parameters $token "integer"
4208  }
4209  }
4210  }
4211  }
4212 
4213  return $parameters
4214 }
4215 
4216 ## Get a dictionary of VHDL generics with their types for a given file
4217 #
4218 # @param[in] file File to read Generics from
4219 # @param[in] entity The entity from which extracting the generics
4220 proc GetVhdlGenerics {file {entity ""}} {
4221  set fp [open $file r]
4222  set data [read $fp]
4223  close $fp
4224  set lines []
4225 
4226  # read in the vhdl file and remove comments
4227  foreach line [split $data "\n"] {
4228  regsub "^\\s*--.*" $line "" line
4229  regsub "(.*)--.*" $line {\1} line
4230  if {![string equal $line ""]} {
4231  append lines $line " "
4232  }
4233  }
4234 
4235  # extract the generic block
4236  set generic_block ""
4237  set generics [dict create]
4238 
4239  if {1 == [string equal $entity ""]} {
4240  regexp {(?i).*entity\s+([^\s]+)\s+is} $lines _ entity
4241  }
4242 
4243  set generics_regexp "(?i).*entity\\s+$entity\\s+is\\s+generic\\s*\\((.*)\\)\\s*;\\s*port.*end.*$entity"
4244 
4245  if {[regexp $generics_regexp $lines _ generic_block]} {
4246  # loop over the generic lines
4247  foreach line [split $generic_block ";"] {
4248  # split the line into the generic + the type
4249  regexp {(.*):\s*([A-Za-z0-9_]+).*} $line _ generic type
4250 
4251  # one line can have multiple generics of the same type, so loop over them
4252  set splits [split $generic ","]
4253  foreach split $splits {
4254  dict set generics [string trim $split] [string trim $type]
4255  }
4256  }
4257  }
4258  return $generics
4259 }
4260 
4261 ## @brief Runs a GHDL command and returns its output and exit state
4262 proc GHDL {command logfile} {
4263  set ret [catch {exec -ignorestderr ghdl {*}$command >>& $logfile} result options]
4264  # puts "ret: $ret"
4265  # puts "result: $result\n"
4266  # puts "options: $options"
4267  # puts "*********"
4268  return [list $ret $result]
4269 }
4270 
4271 ## @brief Handle git commands without causing an error if ret is not 0
4272 #
4273 # It can be used with lassign like this: lassign [GitRet <git command> <possibly files> ] ret result
4274 #
4275 # @param[in] command: the git command to be run including refs (branch, tags, sha, etc.), except files.
4276 # @param[in] files: files given to git as argument. They will always be separated with -- to avoid weird accidents
4277 # Sometimes you need to remove the --. To do that just set files to " "
4278 #
4279 # @returns a list of 2 elements: the return value (0 if no error occurred) and the output of the git command
4280 proc GitRet {command {files ""}} {
4281  global env
4282  if {$files eq ""} {
4283  set ret [catch {exec -ignorestderr git {*}$command} result]
4284  } else {
4285  set ret [catch {exec -ignorestderr git {*}$command -- {*}$files} result]
4286  }
4287  return [list $ret $result]
4288 }
4289 
4290 ## @brief Check git version installed in this machine
4291 #
4292 # @param[in] target_version the version required by the current project
4293 #
4294 # @return Returns 1, if the system git version is greater or equal to the target
4295 proc GitVersion {target_version} {
4296  set ver [split $target_version "."]
4297  set v [Git --version]
4298  #Msg Info "Found Git version: $v"
4299  set current_ver [split [lindex $v 2] "."]
4300  set target [expr {[lindex $ver 0] * 100000 + [lindex $ver 1] * 100 + [lindex $ver 2]}]
4301  set current [expr {[lindex $current_ver 0] * 100000 + [lindex $current_ver 1] * 100 + [lindex $current_ver 2]}]
4302  return [expr {$target <= $current}]
4303 }
4304 
4305 ## @brief Copy IP generated files from/to a remote o local directory (possibly EOS)
4306 #
4307 # @param[in] what_to_do: the action you want to perform, either
4308  # "push", if you want to copy the local IP synth result to the remote directory
4309  # "pull" if you want to copy the files from thre remote directory to your local repository
4310 # @param[in] xci_file: the .xci file of the IP you want to handle
4311 # @param[in] ip_path: the path of the directory you want the IP to be saved (possibly EOS)
4312 # @param[in] repo_path: the main path of your repository
4313 # @param[in] gen_dir: the directory where generated files are placed, by default the files are placed in the same folder as the .xci
4314 # @param[in] force: if not set to 0, will copy the IP to the remote directory even if it is already present
4315 #
4316 proc HandleIP {what_to_do xci_file ip_path repo_path {gen_dir "."} {force 0}} {
4317  global env
4318  if {!($what_to_do eq "push") && !($what_to_do eq "pull")} {
4319  Msg Error "You must specify push or pull as first argument."
4320  }
4321 
4322  if {[catch {package require tar} TARPACKAGE]} {
4323  Msg CriticalWarning "Cannot find package tar. You can fix this by installing package \"tcllib\""
4324  return -1
4325  }
4326 
4327  set old_path [pwd]
4328 
4329  cd $repo_path
4330 
4331  set on_eos 0
4332  set on_rclone 0
4333 
4334  if {[regexp {^[^/]+:} $ip_path]} {
4335  # Rclone path (e.g., dropbox:Project/IPs or eos:user/d/dcieri/...)
4336  set on_rclone 1
4337  # Check if rclone is available
4338  lassign [ExecuteRet rclone --version] rclone_ret rclone_ver
4339  if {$rclone_ret != 0} {
4340  Msg CriticalWarning "Rclone path specified but rclone not found or failed: $rclone_ver"
4341  cd $old_path
4342  return -1
4343  } else {
4344  Msg Info "IP remote directory path, on Rclone, is set to: $ip_path"
4345  # Check if RCLONE_CONFIG environment variable is set, if not set it to the default path
4346  if {[info exists env(HOG_RCLONE_CONFIG)]} {
4347  Msg Info "Using rclone config from environment variable HOG_RCLONE_CONFIG: $env(HOG_RCLONE_CONFIG)"
4348  set config_path $env(HOG_RCLONE_CONFIG)
4349  } else {
4350  set config_path "/dev/null"
4351  Msg Info "Environment variable HOG_RCLONE_CONFIG not set, using rclone environmental variables..."
4352  }
4353 
4354  set remote_name "[lindex [split $ip_path ":"] 0]:"
4355  lassign [ExecuteRet rclone listremotes --config $config_path] rclone_list_ret remotes
4356  if {$rclone_list_ret != 0} {
4357  Msg CriticalWarning "Could not list rclone remotes: $remotes"
4358  cd $old_path
4359  return -1
4360  } else {
4361  if {![IsInList $remote_name $remotes]} {
4362  Msg CriticalWarning "Rclone remote $remote_name not found among available remotes: $remotes"
4363  cd $old_path
4364  return -1
4365  }
4366  }
4367  }
4368  } elseif {[string first "/eos/" $ip_path] == 0} {
4369  # IP Path is on EOS
4370  set on_eos 1
4371  lassign [eos "ls $ip_path"] ret result
4372  if {$ret != 0} {
4373  Msg CriticalWarning "Could not run ls for for EOS path: $ip_path (error: $result). \
4374  Either the drectory does not exist or there are (temporary) problem with EOS."
4375  cd $old_path
4376  return -1
4377  } else {
4378  Msg Info "IP remote directory path, on EOS, is set to: $ip_path"
4379  }
4380  } else {
4381  file mkdir $ip_path
4382  }
4383 
4384  if {!([file exists $xci_file])} {
4385  Msg CriticalWarning "Could not find $xci_file."
4386  cd $old_path
4387  return -1
4388  }
4389 
4390 
4391  set xci_path [file dirname $xci_file]
4392  set xci_name [file tail $xci_file]
4393  set xci_ip_name [file rootname [file tail $xci_file]]
4394  set xci_dir_name [file tail $xci_path]
4395  set gen_path $gen_dir
4396 
4397  set hash [Md5Sum $xci_file]
4398  set file_name $xci_name\_$hash
4399 
4400  Msg Info "Preparing to $what_to_do IP: $xci_name..."
4401 
4402  if {$what_to_do eq "push"} {
4403  set will_copy 0
4404  set will_remove 0
4405  if {$on_rclone == 1} {
4406  lassign [ExecuteRet rclone ls $ip_path/$file_name.tar --config $config_path] ret result
4407  if {$ret != 0} {
4408  set will_copy 1
4409  } else {
4410  if {$force == 0} {
4411  Msg Info "IP already in the Rclone repository, will not copy..."
4412  } else {
4413  Msg Info "IP already in the Rclone repository, will forcefully replace..."
4414  set will_copy 1
4415  set will_remove 1
4416  }
4417  }
4418  } elseif {$on_eos == 1} {
4419  lassign [eos "ls $ip_path/$file_name.tar"] ret result
4420  if {$ret != 0} {
4421  set will_copy 1
4422  } else {
4423  if {$force == 0} {
4424  Msg Info "IP already in the EOS repository, will not copy..."
4425  } else {
4426  Msg Info "IP already in the EOS repository, will forcefully replace..."
4427  set will_copy 1
4428  set will_remove 1
4429  }
4430  }
4431  } else {
4432  if {[file exists "$ip_path/$file_name.tar"]} {
4433  if {$force == 0} {
4434  Msg Info "IP already in the local repository, will not copy..."
4435  } else {
4436  Msg Info "IP already in the local repository, will forcefully replace..."
4437  set will_copy 1
4438  set will_remove 1
4439  }
4440  } else {
4441  set will_copy 1
4442  }
4443  }
4444 
4445  if {$will_copy == 1} {
4446  # Check if there are files in the .gen directory first and copy them into the right place
4447  Msg Info "Looking for generated files in $gen_path..."
4448  set ip_gen_files [glob -nocomplain $gen_path/*]
4449 
4450  #here we should remove the .xci file from the list if it's there
4451 
4452  if {[llength $ip_gen_files] > 0} {
4453  Msg Info "Found some IP synthesised files matching $xci_ip_name"
4454  if {$will_remove == 1} {
4455  Msg Info "Removing old synthesised directory $ip_path/$file_name.tar..."
4456  if {$on_rclone == 1} {
4457  lassign [ExecuteRet rclone delete $ip_path/$file_name.tar --config $config_path] ret result
4458  if {$ret != 0} {
4459  Msg CriticalWarning "Could not delete file from Rclone: $result"
4460  }
4461  } elseif {$on_eos == 1} {
4462  eos "rm -rf $ip_path/$file_name.tar" 5
4463  } else {
4464  file delete -force "$ip_path/$file_name.tar"
4465  }
4466  }
4467 
4468  Msg Info "Creating local archive with IP generated files..."
4469  set tar_files []
4470 
4471  foreach f $ip_gen_files {
4472  lappend tar_files "[Relative [file normalize $repo_path] $f]"
4473  }
4474 
4475  ::tar::create $file_name.tar $tar_files
4476 
4477  Msg Info "Copying IP generated files for $xci_name..."
4478  if {$on_rclone == 1} {
4479  lassign [ExecuteRet rclone copyto $file_name.tar $ip_path/$file_name.tar --config $config_path] ret result
4480  if {$ret != 0} {
4481  Msg CriticalWarning "Something went wrong when copying the IP files to Rclone. Error message: $result"
4482  }
4483  } elseif {$on_eos == 1} {
4484  lassign [ExecuteRet xrdcp -f -s $file_name.tar $::env(EOS_MGM_URL)//$ip_path/] ret msg
4485  if {$ret != 0} {
4486  Msg CriticalWarning "Something went wrong when copying the IP files to EOS. Error message: $msg"
4487  }
4488  } else {
4489  Copy "$file_name.tar" "$ip_path/"
4490  }
4491  Msg Info "Removing local archive"
4492  file delete $file_name.tar
4493  } else {
4494  Msg Warning "Could not find synthesized files matching $gen_path/$file_name*"
4495  }
4496  }
4497  } elseif {$what_to_do eq "pull"} {
4498  if {$on_rclone == 1} {
4499  lassign [ExecuteRet rclone ls $ip_path/$file_name.tar --config $config_path] ret result
4500  if {$ret != 0} {
4501  Msg Info "Nothing for $xci_name was found in the Rclone repository, cannot pull."
4502  cd $old_path
4503  return -1
4504  } else {
4505  Msg Info "IP $xci_name found in the Rclone repository $ip_path, copying it locally to $repo_path..."
4506  lassign [ExecuteRet rclone copyto $ip_path/$file_name.tar $file_name.tar --config $config_path] ret_copy result_copy
4507  if {$ret_copy != 0} {
4508  Msg CriticalWarning "Something went wrong when copying the IP files from Rclone. Error message: $result_copy"
4509  }
4510  }
4511  } elseif {$on_eos == 1} {
4512  lassign [eos "ls $ip_path/$file_name.tar"] ret result
4513  if {$ret != 0} {
4514  Msg Info "Nothing for $xci_name was found in the EOS repository, cannot pull."
4515  cd $old_path
4516  return -1
4517  } else {
4518  set remote_tar "$::env(EOS_MGM_URL)//$ip_path/$file_name.tar"
4519  Msg Info "IP $xci_name found in the repository $remote_tar, copying it locally to $repo_path..."
4520 
4521  lassign [ExecuteRet xrdcp -f -r -s $remote_tar $repo_path] ret msg
4522  if {$ret != 0} {
4523  Msg CriticalWarning "Something went wrong when copying the IP files to EOS. Error message: $msg"
4524  }
4525  }
4526  } else {
4527  if {[file exists "$ip_path/$file_name.tar"]} {
4528  Msg Info "IP $xci_name found in local repository $ip_path/$file_name.tar, copying it locally to $repo_path..."
4529  Copy $ip_path/$file_name.tar $repo_path
4530  } else {
4531  Msg Info "Nothing for $xci_name was found in the local IP repository, cannot pull."
4532  cd $old_path
4533  return -1
4534  }
4535  }
4536 
4537  if {[file exists $file_name.tar]} {
4538  remove_files $xci_file
4539  Msg Info "Extracting IP files from archive to $repo_path..."
4540  ::tar::untar $file_name.tar -dir $repo_path -noperms
4541  Msg Info "Removing local archive"
4542  file delete $file_name.tar
4543  add_files -norecurse -fileset sources_1 $xci_file
4544  }
4545  }
4546  cd $old_path
4547  return 0
4548 }
4549 
4550 ## Convert hex version to M.m.p string
4551 #
4552 # @param[in] version the version (in 32-bit hexadecimal format 0xMMmmpppp) to be converted
4553 #
4554 # @return a string containing the version in M.m.p format
4555 #
4556 proc HexVersionToString {version} {
4557  scan [string range $version 0 1] %x M
4558  scan [string range $version 2 3] %x m
4559  scan [string range $version 4 7] %x c
4560  return "$M.$m.$c"
4561 }
4562 
4563 # @brief Import TCL Lib from an external installation for Libero, Synplify and Diamond
4564 proc ImportTclLib {} {
4565  global env
4566  if {[IsLibero] || [IsDiamond] || [IsSynplify]} {
4567  if {[info exists env(HOG_TCLLIB_PATH)]} {
4568  lappend auto_path $env(HOG_TCLLIB_PATH)
4569  return 1
4570  } else {
4571  puts "ERROR: To run Hog with Microsemi Libero SoC or Lattice Diamond, you need to define the HOG_TCLLIB_PATH variable."
4572  return 0
4573  }
4574  }
4575 }
4576 
4577 # @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
4578 #
4579 # @param[in] script The launch.tcl script
4580 # @param[in] tcl_path The launch.tcl script path
4581 # @param[in] parameters The allowed parameters for launch.tcl
4582 # @param[in] commands The allowed directives for launch.tcl
4583 # @param[in] argv The input arguments passed to launch.tcl
4584 # @param[in] custom_commands Custom commands to be added to the list of commands
4585 
4586 proc InitLauncher {script tcl_path parameters commands argv {custom_commands ""}} {
4587  set repo_path [file normalize "$tcl_path/../.."]
4588  set old_path [pwd]
4589  set bin_path [file normalize "$tcl_path/../../bin"]
4590  set top_path [file normalize "$tcl_path/../../Top"]
4591 
4592  set cmd_lines [split $commands "\n"]
4593 
4594  set command_options [dict create]
4595  set directive_descriptions [dict create]
4596  set directive_names [dict create]
4597  set common_directive_names [dict create]
4598  set custom_command ""
4599  set custom_command_options ""
4600 
4601  foreach l $cmd_lines {
4602  #excludes direcitve with a # just after the \{
4603  if {[regexp {\\(.*) \{\#} $l minc d]} {
4604  lappend directives_with_projects $d
4605  }
4606 
4607  #gets all the regexes
4608  if {[regexp {\\(.*) \{} $l minc regular_expression]} {
4609  lappend directive_regex $regular_expression
4610  }
4611 
4612  #gets all common directives
4613  if {[regexp {\#\s*NAME(\*)?:\s*(.*)\s*} $l minc star name]} {
4614  dict set directive_names $name $regular_expression
4615  if {$star eq "*"} {
4616  dict set common_directive_names $name $regular_expression
4617  }
4618  }
4619  set directive_names [DictSort $directive_names]
4620  set common_directive_names [DictSort $common_directive_names]
4621 
4622  #gets all the descriptions
4623  if {[regexp {\#\s*DESCRIPTION:\s*(.*)\s*} $l minc x]} {
4624  dict set directive_descriptions $regular_expression $x
4625  }
4626 
4627  #gets all the list of options
4628  if {[regexp {\#\s*OPTIONS:\s*(.*)\s*} $l minc x]} {
4629  dict set command_options $regular_expression [split [regsub -all {[ \t\n]+} $x {}] ","]
4630  }
4631  }
4632 
4633  set short_usage "usage: ./Hog/Do \[OPTIONS\] <directive> \[project\]\n\nMost common directives (case insensitive):"
4634 
4635  dict for {key value} $common_directive_names {
4636  set short_usage "$short_usage\n - $key: [dict get $directive_descriptions $value]"
4637  }
4638 
4639  if {[string length $custom_commands] > 0} {
4640  Msg Debug "Found custom commands to add to short short_usage."
4641  set short_usage "$short_usage\n\nCustom commands:"
4642  dict for {key command} $custom_commands {
4643  Msg Debug "Adding $key : [dict get $command DESCRIPTION]"
4644  set short_usage "$short_usage\n - $key: [dict get $command DESCRIPTION]"
4645  }
4646  }
4647 
4648 
4649 
4650  set short_usage "$short_usage\n\n\
4651  To see all the available directives, run:\n./Hog/Do HELP\n\n\
4652  To list available options for the chosen directive run:\n\
4653  ./Hog/Do <directive> HELP\n
4654  "
4655 
4656  set usage "usage: ./Hog/Do \[OPTIONS\] <directive> \[project\]\n\nDirectives (case insensitive):"
4657 
4658  dict for {key value} $directive_names {
4659  set usage "$usage\n - $key: [dict get $directive_descriptions $value]"
4660  }
4661 
4662  # if length of custom commands is greater than 0, add them to the short usage"
4663  if {[string length $custom_commands] > 0} {
4664  Msg Debug "Found custom commands to add to short usage."
4665  set usage "$usage\n\nCustom commands:"
4666  dict for {key command} $custom_commands {
4667  Msg Debug "Adding $key : [dict get $command DESCRIPTION]"
4668  set usage "$usage\n - $key: [dict get $command DESCRIPTION]"
4669  }
4670  }
4671 
4672 
4673  set usage "$usage\n\nTo list available options for the chosen directive run:\n./Hog/Do <directive> HELP"
4674 
4675  if {[IsTclsh]} {
4676  #Just display the logo the first time, not when the script is run in the IDE
4677  Logo $repo_path
4678  }
4679 
4680  # Check if HogEnv.conf exists and parse it
4681  if {[file exists [Hog::LoggerLib::GetUserFilePath "HogEnv.conf"]] } {
4682  Msg Debug "HogEnv.conf found"
4683  set loggerdict [Hog::LoggerLib::ParseTOML [Hog::LoggerLib::GetUserFilePath "HogEnv.conf" ]]
4684  set HogEnvDict [Hog::LoggerLib::GetTOMLDict]
4685  Hog::LoggerLib::PrintTOMLDict $HogEnvDict
4686  }
4687 
4688 
4689 
4690  if {[catch {package require cmdline} ERROR]} {
4691  Msg Debug "The cmdline Tcl package was not found, sourcing it from Hog..."
4692  source $tcl_path/utils/cmdline.tcl
4693  }
4694 
4695  set argv [regsub -all {(?i) HELP\y} $argv " -help"]
4696 
4697 
4698  #Gather up all custom parameters
4699  #NOTE: right now user can accidentally redefine their own custom parameters, there is no check for that...
4700  set custom_parameters [list]
4701  dict for {key command} $custom_commands {
4702  set custom_parameters [concat $custom_parameters [dict get $command CUSTOM_OPTIONS]]
4703  }
4704 
4705  lassign [GetOptions $argv [concat $custom_parameters $parameters]] option_list arg_list
4706 
4707  if {[IsInList "-all" $option_list]} {
4708  set list_all 1
4709  } else {
4710  set list_all 2
4711  }
4712 
4713  #option_list will be emptied by the next instruction
4714 
4715  # Argv here is modified and the options are removed
4716  set directive [string toupper [lindex $arg_list 0]]
4717  set min_n_of_args 0
4718  set max_n_of_args 2
4719  set argument_is_no_project 1
4720 
4721  set NO_DIRECTIVE_FOUND 0
4722  switch -regexp -- $directive "$commands"
4723  if {$NO_DIRECTIVE_FOUND == 1} {
4724  if {[string length $custom_commands] > 0 && [dict exists $custom_commands $directive]} {
4725  set custom_command $directive
4726  set custom_command_hog_parameters [dict get $custom_commands $directive OPTIONS]
4727  set custom_command_options [dict get $custom_commands $directive CUSTOM_OPTIONS]
4728  set custom_command_options [concat $custom_command_hog_parameters $custom_command_options]
4729  } else {
4730  Msg Status "ERROR: Unknown directive $directive.\n\n"
4731  puts $usage
4732  exit
4733  }
4734  }
4735 
4736  if {[IsInList $directive $directives_with_projects 1]} {
4737  set argument_is_no_project 0
4738  }
4739 
4740  if {[IsInList "-help" $option_list] || [IsInList "-?" $option_list] || [IsInList "-h" $option_list]} {
4741  if {$directive != ""} {
4742  if {[IsInList $directive $directives_with_projects 1]} {
4743  puts "usage: ./Hog/Do \[OPTIONS\] $directive <project>\n"
4744  } elseif {[regexp "^COMPSIM(LIB)?$" $directive]} {
4745  puts "usage: ./Hog/Do \[OPTIONS\] $directive <simulator>\n"
4746  } else {
4747  puts "usage: ./Hog/Do \[OPTIONS\] $directive \n"
4748  }
4749 
4750  dict for {dir desc} $directive_descriptions {
4751  if {[regexp $dir $directive]} {
4752  puts "$desc\n"
4753  break
4754  }
4755  }
4756 
4757 
4758  #if custom command, parse custom options instead
4759  if {$custom_command ne ""} {
4760  if {[llength $custom_command_options] > 0} {
4761  puts "Available options:"
4762  }
4763  foreach custom_option $custom_command_options {
4764  set n [llength $custom_option]
4765  if {$n == 2} {
4766  lassign $custom_option opt help
4767  puts " -$opt"
4768  puts " $help"
4769  } elseif {$n == 3} {
4770  lassign $custom_option opt def help
4771  puts " -$opt <argument>"
4772  if {$def ne ""} {
4773  puts " $help (default: $def)"
4774  } else {
4775  puts " $help"
4776  }
4777  } else {
4778  Msg Warning "Custom option spec has invalid arity (expected 2 or 3): $custom_option"
4779  }
4780  }
4781  }
4782 
4783  dict for {dir opts} $command_options {
4784  if {[regexp $dir $directive]} {
4785  puts "Available options:"
4786  foreach opt $opts {
4787  foreach par $parameters {
4788  if {$opt == [lindex $par 0]} {
4789  if {[regexp {\.arg$} $opt]} {
4790  set opt_name [regsub {\.arg$} $opt ""]
4791  puts " -$opt_name <argument>"
4792  } else {
4793  puts " -$opt"
4794  }
4795  puts " [lindex $par [llength $par]-1]"
4796  }
4797  }
4798  }
4799  puts ""
4800  }
4801  }
4802  } else {
4803  puts $usage
4804  }
4805  # Msg Info [cmdline::usage $parameters $usage]
4806  exit 0
4807  }
4808 
4809  if {$custom_command ne ""} {
4810  set parameters [concat $parameters $custom_command_options]
4811  }
4812 
4813  if {[catch {array set options [cmdline::getoptions option_list $parameters $usage]} err]} {
4814  Msg Status "\nERROR: Syntax error, probably unknown option.\n\n USAGE: $err"
4815  exit 1
4816  }
4817 
4818  if {[llength $arg_list] <= $min_n_of_args || [llength $arg_list] > $max_n_of_args} {
4819  Msg Status "\nERROR: Wrong number of arguments: [llength $argv]"
4820  puts $short_usage
4821  exit 1
4822  }
4823 
4824  set project [lindex $arg_list 1]
4825 
4826  if {$argument_is_no_project == 0} {
4827  # Remove leading Top/ or ./Top/ if in project_name
4828  regsub "^(\./)?Top/" $project "" project
4829  # Remove trailing / and spaces if in project_name
4830  regsub "/? *\$" $project "" project
4831  set proj_conf [ProjectExists $project $repo_path]
4832  } else {
4833  set proj_conf 0
4834  }
4835 
4836  Msg Debug "Option list:"
4837  foreach {key value} [array get options] {
4838  Msg Debug "$key => $value"
4839  }
4840 
4841  set cmd ""
4842 
4843  if {[IsTclsh]} {
4844  # command is filled with the IDE exectuable when this function is called by Tcl scrpt
4845  if {$proj_conf != 0} {
4846  CheckLatestHogRelease $repo_path
4847 
4848  lassign [GetIDECommand $proj_conf] cmd before_tcl_script after_tcl_script end_marker
4849  Msg Info "Project $project uses $cmd IDE"
4850 
4851  ## The following is the IDE command to launch:
4852  set command "$cmd $before_tcl_script$script$after_tcl_script$argv$end_marker"
4853  } else {
4854  if {$custom_command ne ""} {
4855  if { [dict exists $custom_commands $directive IDE] } {
4856  lassign [GetIDECommand "" [dict get $custom_commands $directive IDE]] cmd before_tcl_script after_tcl_script end_marker
4857  Msg Info "Custom command: $custom_command uses $cmd IDE"
4858  set command "$cmd $before_tcl_script$script$after_tcl_script$argv$end_marker"
4859  } else {
4860  set command "custom_tcl"
4861  }
4862  } elseif {$argument_is_no_project == 1} {
4863  set command -4
4864  Msg Debug "$project will be used as first argument"
4865  } elseif {$project != ""} {
4866  #Project not given
4867  set command -1
4868  } elseif {$min_n_of_args < 0} {
4869  #Project not needed
4870  set command -3
4871  } else {
4872  #Project not found
4873  set command -2
4874  }
4875  }
4876  } else {
4877  # When the launcher is executed from within an IDE, command is set to 0
4878  set command 0
4879  }
4880 
4881  set project_group [file dirname $project]
4882  set project_name $project
4883  set project [file tail $project]
4884  Msg Debug "InitLauncher: project_group=$project_group, project_name=$project_name, project=$project"
4885 
4886  return [list $directive $project $project_name $project_group $repo_path $old_path $bin_path $top_path $usage $short_usage $command $cmd [array get options]]
4887 }
4888 
4889 # @brief Returns 1 if a commit is an ancestor of another, otherwise 0
4890 #
4891 # @param[in] ancestor The potential ancestor commit
4892 # @param[in] commit The potential descendant commit
4893 proc IsCommitAncestor {ancestor commit} {
4894  lassign [GitRet "merge-base --is-ancestor $ancestor $commit"] status result
4895  if {$status == 0} {
4896  return 1
4897  } else {
4898  return 0
4899  }
4900 }
4901 
4902 proc IsDiamond {} {
4903  return [expr {[info commands sys_install] != ""}]
4904 }
4905 
4906 ## @brief Returns true if the IDE is MicroSemi Libero
4907 proc IsLibero {} {
4908  return [expr {[info commands get_libero_version] != ""}]
4909 }
4910 
4911 # @brief Returns 1 if an element is a list, 0 otherwise
4912 #
4913 # @param[in] element The element to search
4914 # @param[in] list The list to search into
4915 # @param[in] regex An optional regex to match. If 0, the element should match exactly an object in the list
4916 # @param[in] nocase If 1, perform case-insensitive comparison
4917 proc IsInList {element list {regex 0} {nocase 0}} {
4918  foreach x $list {
4919  if {$regex == 1} {
4920  if {$nocase == 1} {
4921  if {[regexp -nocase $x $element]} {
4922  return 1
4923  }
4924  } else {
4925  if {[regexp $x $element]} {
4926  return 1
4927  }
4928  }
4929  } elseif {$regex == 0} {
4930  if {$nocase == 1} {
4931  if {[string tolower $x] eq [string tolower $element]} {
4932  return 1
4933  }
4934  } else {
4935  if {$x eq $element} {
4936  return 1
4937  }
4938  }
4939  }
4940  }
4941  return 0
4942 }
4943 
4944 
4945 ## @brief Returns true, if the IDE is ISE/PlanAhead
4946 proc IsISE {} {
4947  if {[IsXilinx]} {
4948  return [expr {[string first PlanAhead [version]] == 0}]
4949  } else {
4950  return 0
4951  }
4952 }
4953 
4954 ## @brief Returns true, if IDE is Quartus
4955 proc IsQuartus {} {
4956  if {[catch {package require ::quartus::flow} result]} {
4957  # not available
4958  return 0
4959  } else {
4960  # available
4961  return 1
4962  }
4963 }
4964 
4965 ## Check if a path is absolute or relative
4966 #
4967 # @param[in] the path to check
4968 #
4969 proc IsRelativePath {path} {
4970  if {[string index $path 0] == "/" || [string index $path 0] == "~"} {
4971  return 0
4972  } else {
4973  return 1
4974  }
4975 }
4976 
4977 ## @brief Returns true if the Synthesis tool is Synplify
4978 proc IsSynplify {} {
4979  return [expr {[info commands program_version] != ""}]
4980 }
4981 
4982 ## @brief Returns true, if we are in tclsh
4983 proc IsTclsh {} {
4984  return [expr {![IsQuartus] && ![IsXilinx] && ![IsVitisClassic] && ![IsVitisUnified] && ![IsLibero] && ![IsSynplify] && ![IsDiamond]}]
4985 }
4986 
4987 # @brief Find out if the given file is a Verilog or SystemVerilog file
4988 #
4989 # @param[in] file The file to check
4990 # @param[out] 1 if it's Verilog/SystemVerilog 0 if it's not
4991 #
4992 proc IsVerilog {file} {
4993  if {[file extension $file] == ".v" || [file extension $file] == ".sv"} {
4994  return 1
4995  } else {
4996  return 0
4997  }
4998 }
4999 
5000 ## @brief Find out if the given Xilinx part is a Versal chip
5001 #
5002 # @param[out] 1 if it's Versal 0 if it's not
5003 # @param[in] part The FPGA part
5004 #
5005 proc IsVersal {part} {
5006  if {[get_property ARCHITECTURE [get_parts $part]] eq "versal"} {
5007  return 1
5008  } else {
5009  return 0
5010  }
5011 }
5012 
5013 ## @brief Returns true, if the IDE is Vivado
5014 proc IsVivado {} {
5015  if {[IsXilinx]} {
5016  return [expr {[string first Vivado [version]] == 0}]
5017  } else {
5018  return 0
5019  }
5020 }
5021 
5022 ## @brief Return true, if the IDE is Xilinx (Vivado or ISE)
5023 proc IsXilinx {} {
5024  if {[info commands version] != ""} {
5025  set current_version [version]
5026  if {[string first PlanAhead $current_version] == 0 || [string first Vivado $current_version] == 0} {
5027  return 1
5028  } elseif {[string first xsct $current_version] == 0} {
5029  return 0
5030  } else {
5031  Msg Warning "This IDE has the version command but it is not PlanAhead or Vivado: $current_version"
5032  return 0
5033  }
5034  } else {
5035  return 0
5036  }
5037 }
5038 
5039 ## @brief Returns true, if the IDE is vitis_classic
5040 proc IsVitisClassic {} {
5041  if {[info exists globalSettings::vitis_classic]} {
5042  return $globalSettings::vitis_classic
5043  }
5044  return [expr {[info commands platform] != ""}]
5045 }
5046 
5047 ## @brief Returns true, if the IDE is vitis_unified
5048 proc IsVitisUnified {} {
5049  if {[info exists globalSettings::vitis_unified]} {
5050  return $globalSettings::vitis_unified
5051  }
5052  return 0
5053 }
5054 
5055 ## @brief Execute a Python command via Vitis Unified command-line tool and display output in real-time
5056 #
5057 # @param[in] python_script Full path to the Python script (e.g., PlatformCommands.py or AppCommands.py)
5058 # @param[in] command The command to execute (e.g., "create_platform", "configure_app", "app_list")
5059 # @param[in] args List of arguments to pass to the command
5060 # @param[in] error_prefix Prefix for error messages (e.g., "Failed to create platform" or "Failed to configure app")
5061 # @param[in] output_var Optional variable name to store the output (if not provided, output is only printed)
5062 # @param[out] 1 on success, 0 on failure
5063 #
5064 proc ExecuteVitisUnifiedCommand {python_script command args {error_prefix "Failed to execute command"} {output_var ""}} {
5065  set cmdlist [list vitis -s $python_script $command]
5066  foreach arg $args {
5067  lappend cmdlist $arg
5068  }
5069  lappend cmdlist 2>@1
5070 
5071  Msg Debug "Executing: vitis -s $python_script $command $args"
5072 
5073  # Set PYTHONUNBUFFERED environment variable for real-time output
5074  set env(PYTHONUNBUFFERED) "1"
5075 
5076  # Open pipe and configure for line buffering
5077  if {[catch {set pipe [open "|$cmdlist" "r"]} err]} {
5078  Msg Error "$error_prefix: Failed to open pipe: $err"
5079  return 0
5080  }
5081 
5082  fconfigure $pipe -buffering line
5083  set script_output ""
5084  set vitis_version ""
5085 
5086  # Patterns to identify Vitis banner messages, these will be filtered out
5087  set vitis_banner_patterns {
5088  "*Vitis Development Environment*"
5089  "*Vitis v*"
5090  "*SW Build*"
5091  "*Copyright*Xilinx*"
5092  "*Copyright*Advanced Micro Devices*"
5093  "*All Rights Reserved*"
5094  }
5095 
5096  # Read and display output line by line
5097  while {[gets $pipe line] >= 0} {
5098  if {$line ne ""} {
5099  if {[string match "*Vitis v*" $line]} {
5100  if {[regexp {Vitis\s+v([0-9]+)\.([0-9]+)(?:\.[0-9]+)?} $line -> major minor]} {
5101  set year_last_two [string range $major end-1 end]
5102  set vitis_version "$year_last_two.$minor"
5103  }
5104  }
5105 
5106  # Filter out Vitis banner messages
5107  set is_banner 0
5108  foreach pattern $vitis_banner_patterns {
5109  if {[string match $pattern $line]} {
5110  set is_banner 1
5111  break
5112  }
5113  }
5114  if {!$is_banner} {
5115  if {![string match "INFO:*" $line] && ![string match "WARNING:*" $line] && ![string match "ERROR:*" $line] && ![string match "DEBUG:*" $line]} {
5116  if {$vitis_version ne ""} {
5117  set line "INFO: \[Vitis_v$vitis_version\] $line"
5118  } else {
5119  set line "INFO: $line"
5120  }
5121  }
5122  puts "$line"
5123  append script_output "$line\n"
5124  }
5125  }
5126  }
5127 
5128  # Close pipe and check exit code
5129  set exit_code 0
5130  if {[catch {close $pipe} err]} {
5131  if {[regexp {exit (\d+)} $err -> exit_code]} {
5132  if {$exit_code != 0} {
5133  Msg Error "$error_prefix (exit code: $exit_code)"
5134  if {$output_var ne ""} {
5135  upvar $output_var output
5136  set output $script_output
5137  }
5138  return 0
5139  }
5140  } else {
5141  Msg Error "$error_prefix: $err"
5142  if {$output_var ne ""} {
5143  upvar $output_var output
5144  set output $script_output
5145  }
5146  return 0
5147  }
5148  }
5149 
5150  # Return output if requested
5151  if {$output_var ne ""} {
5152  upvar $output_var output
5153  set output $script_output
5154  }
5155 
5156  return 1
5157 }
5158 
5159 ## @brief Find out if the given Xilinx part is a Versal chip
5160 #
5161 # @param[out] 1 if it's Zynq 0 if it's not
5162 # @param[in] part The FPGA part
5163 #
5164 proc IsZynq {part} {
5165  if {[regexp {^(xc7z|xczu).*} $part]} {
5166  return 1
5167  } else {
5168  return 0
5169  }
5170 }
5171 
5172 proc ImportGHDL {project_name repo_path simset_name simset_dict {ext_path ""}} {
5173  set list_path "$repo_path/Top/$project_name/list"
5174  lassign [GetHogFiles -list_files {.src,.ext,.sim} -ext_path $ext_path $list_path $repo_path] src_files properties filesets
5175  cd $repo_path
5176 
5177 
5178  # Get Properties
5179  set properties [DictGet $simset_dict "properties"]
5180  set options [DictGet $properties "options"]
5181 
5182  # Import GHDL files
5183  set workdir Projects/$project_name/ghdl
5184  file delete -force $workdir
5185  file mkdir $workdir
5186  set import_log "$workdir/ghdl-import-${simset_name}.log"
5187  dict for {lib sources} $src_files {
5188  set libname [file rootname $lib]
5189  foreach f $sources {
5190  if {[file extension $f] != ".vhd" && [file extension $f] != ".vhdl"} {
5191  Msg Info "File $f is not a VHDL file, copying it in workfolder..."
5192  file copy -force $f $workdir
5193  } else {
5194  set file_path [Relative $repo_path $f]
5195  set import_log_file [open $import_log "a"]
5196  puts "ghdl -i --work=$libname --workdir=$workdir -fsynopsys --ieee=standard $options $file_path"
5197  puts $import_log_file "ghdl -i --work=$libname --workdir=$workdir -fsynopsys --ieee=standard $options $file_path"
5198  close $import_log_file
5199  lassign [GHDL "-i --work=$libname --workdir=$workdir -fsynopsys --ieee=standard $options $file_path" $import_log] ret result
5200  if {$ret != 0} {
5201  Msg CriticalWarning "GHDL import failed for file $f: $result"
5202  }
5203  }
5204  }
5205  }
5206  PrintFileContent $import_log
5207 
5208 }
5209 
5210 proc LaunchGHDL {project_name repo_path simset_name simset_dict {ext_path ""}} {
5211  set top_sim ""
5212  # Setting Simulation Properties
5213  set sim_props [DictGet $simset_dict "properties"]
5214  set options [DictGet $sim_props "options"]
5215  set runopts [DictGet $sim_props "run_options"]
5216 
5217  dict for {prop_name prop_val} $sim_props {
5218  set prop_name [string toupper $prop_name]
5219  if {$prop_name == "TOP"} {
5220  set top_sim $prop_val
5221  }
5222  }
5223  set workdir $repo_path/Projects/$project_name/ghdl
5224  set make_log "$workdir/ghdl-make-${simset_name}.log"
5225  set run_log "$workdir/ghdl-run-${simset_name}.log"
5226  cd $workdir
5227  # Analyse and elaborate the design
5228  set make_log_file [open $make_log "w"]
5229 
5230  puts "ghdl -m --work=$simset_name -fsynopsys --ieee=standard $options $top_sim"
5231  puts $make_log_file "ghdl -m --work=$simset_name -fsynopsys --ieee=standard $options $top_sim"
5232  close $make_log_file
5233 
5234  lassign [GHDL "-m --work=$simset_name -fsynopsys --ieee=standard $options $top_sim" $make_log] ret result
5235  PrintFileContent $make_log
5236  if {$ret != 0} {
5237  Msg Error "GHDL make failed for $top_sim: $result"
5238  return
5239  }
5240 
5241  set run_log_file [open $run_log "w"]
5242  puts "ghdl -r --work=$simset_name -fsynopsys --ieee=standard $options $top_sim $runopts"
5243  puts $run_log_file "ghdl -r --work=$simset_name -fsynopsys --ieee=standard $options $top_sim $runopts"
5244  close $run_log_file
5245 
5246  lassign [GHDL "-r --work=$simset_name -fsynopsys --ieee=standard $options $top_sim $runopts" $run_log] ret result
5247  PrintFileContent $run_log
5248 
5249  if {$ret != 0} {
5250  Msg Error "GHDL run failed for $top_sim: $result"
5251  return
5252  }
5253 
5254  cd $repo_path
5255 }
5256 
5257 # @brief Launch the Implementation, for the current IDE and project
5258 #
5259 # @param[in] reset Reset the Implementation run
5260 # @param[in] do_create Recreate the project
5261 # @param[in] run_folder The folder where to store the run results
5262 # @param[in] project_name The name of the project
5263 # @param[in] repo_path The main path of the git repository (Default .)
5264 # @param[in] njobs The number of parallel CPU jobs for the Implementation (Default 4)
5265 proc LaunchImplementation {reset do_create run_folder project_name {repo_path .} {njobs 4} {do_bitstream 0}} {
5266  Msg Info "Starting implementation flow..."
5267  if {[IsXilinx]} {
5268  if {$reset == 1} {
5269  Msg Info "Resetting run before launching implementation..."
5270  reset_run impl_1
5271  }
5272 
5273  if {[IsISE]} {
5274  source $repo_path/Hog/Tcl/integrated/pre-implementation.tcl
5275  }
5276 
5277  if {$do_bitstream == 1} {
5278  launch_runs impl_1 -to_step [BinaryStepName [get_property PART [current_project]]] -jobs $njobs -dir $run_folder
5279  } else {
5280  launch_runs impl_1 -jobs $njobs -dir $run_folder
5281  }
5282  wait_on_run impl_1
5283 
5284  if {[IsISE]} {
5285  Msg Info "running post-implementation"
5286  source $repo_path/Hog/Tcl/integrated/post-implementation.tcl
5287  if {$do_bitstream == 1} {
5288  Msg Info "running pre-bitstream"
5289  source $repo_path/Hog/Tcl/integrated/pre-bitstream.tcl
5290  Msg Info "running post-bitstream"
5291  source $repo_path/Hog/Tcl/integrated/post-bitstream.tcl
5292  }
5293  }
5294 
5295  set prog [get_property PROGRESS [get_runs impl_1]]
5296  set status [get_property STATUS [get_runs impl_1]]
5297  Msg Info "Run: impl_1 progress: $prog, status : $status"
5298 
5299  # Check timing
5300  if {[IsISE]} {
5301  set status_file [open "$run_folder/timing.txt" "w"]
5302  puts $status_file "## $project_name Timing summary"
5303 
5304  set f [open [lindex [glob "$run_folder/impl_1/*.twr" 0]]]
5305  set errs -1
5306  while {[gets $f line] >= 0} {
5307  if {[string match "Timing summary:" $line]} {
5308  while {[gets $f line] >= 0} {
5309  if {[string match "Timing errors:*" $line]} {
5310  set errs [regexp -inline -- {[0-9]+} $line]
5311  }
5312  if {[string match "*Footnotes*" $line]} {
5313  break
5314  }
5315  puts $status_file "$line"
5316  }
5317  }
5318  }
5319 
5320  close $f
5321  close $status_file
5322 
5323  if {$errs == 0} {
5324  Msg Info "Time requirements are met"
5325  file rename -force "$run_folder/timing.txt" "$run_folder/timing_ok.txt"
5326  set timing_ok 1
5327  } else {
5328  Msg CriticalWarning "Time requirements are NOT met"
5329  file rename -force "$run_folder/timing.txt" "$run_folder/timing_error.txt"
5330  set timing_ok 0
5331  }
5332  }
5333 
5334  if {[IsVivado]} {
5335  set wns [get_property STATS.WNS [get_runs [current_run]]]
5336  set tns [get_property STATS.TNS [get_runs [current_run]]]
5337  set whs [get_property STATS.WHS [get_runs [current_run]]]
5338  set ths [get_property STATS.THS [get_runs [current_run]]]
5339  set tpws [get_property STATS.TPWS [get_runs [current_run]]]
5340 
5341  if {$wns >= 0 && $whs >= 0 && $tpws >= 0} {
5342  Msg Info "Time requirements are met"
5343  set status_file [open "$run_folder/timing_ok.txt" "w"]
5344  set timing_ok 1
5345  } else {
5346  Msg CriticalWarning "Time requirements are NOT met"
5347  set status_file [open "$run_folder/timing_error.txt" "w"]
5348  set timing_ok 0
5349  }
5350 
5351  Msg Status "*** Timing summary ***"
5352  Msg Status "WNS: $wns"
5353  Msg Status "TNS: $tns"
5354  Msg Status "WHS: $whs"
5355  Msg Status "THS: $ths"
5356  Msg Status "TPWS: $tpws"
5357 
5358  struct::matrix m
5359  m add columns 5
5360  m add row
5361 
5362  puts $status_file "## $project_name Timing summary"
5363 
5364  m add row "| **Parameter** | \"**value (ns)**\" |"
5365  m add row "| --- | --- |"
5366  m add row "| WNS: | $wns |"
5367  m add row "| TNS: | $tns |"
5368  m add row "| WHS: | $whs |"
5369  m add row "| THS: | $ths |"
5370  m add row "| TPWS: | $tpws |"
5371 
5372  puts $status_file [m format 2string]
5373  puts $status_file "\n"
5374  if {$timing_ok == 1} {
5375  puts $status_file " Time requirements are met."
5376  } else {
5377  puts $status_file "Time requirements are **NOT** met."
5378  }
5379  puts $status_file "\n\n"
5380  close $status_file
5381  }
5382 
5383  if {$prog ne "100%"} {
5384  Msg Error "Implementation error"
5385  }
5386 
5387  #Go to repository path
5388  cd $repo_path
5389  set describe [GetHogDescribe [file normalize ./Top/$project_name] $repo_path]
5390  Msg Info "Git describe set to $describe"
5391 
5392  set dst_dir [file normalize "$repo_path/bin/$project_name\-$describe"]
5393 
5394  file mkdir $dst_dir
5395 
5396  #Version table
5397  if {[file exists $run_folder/versions.txt]} {
5398  file copy -force $run_folder/versions.txt $dst_dir
5399  } else {
5400  Msg Warning "No versions file found in $run_folder/versions.txt"
5401  }
5402  #Timing file
5403  set timing_files [glob -nocomplain "$run_folder/timing_*.txt"]
5404  set timing_file [file normalize [lindex $timing_files 0]]
5405 
5406  if {[file exists $timing_file]} {
5407  file copy -force $timing_file $dst_dir/
5408  } else {
5409  Msg Warning "No timing file found, not a problem if running locally"
5410  }
5411 
5412  #### XSA here only for Versal Segmented Configuration
5413  if {[IsVersal [get_property part [current_project]]]} {
5414  if {[get_property segmented_configuration [current_project]] == 1} {
5415  Msg Info "Versal Segmented configuration detected: exporting XSA file..."
5416  set xsa_name "$dst_dir/[file tail $project_name]\-$describe.xsa"
5417  write_hw_platform -fixed -force -file $xsa_name
5418  }
5419  }
5420  } elseif {[IsQuartus]} {
5421  set revision [get_current_revision]
5422 
5423  if {[catch {execute_module -tool fit} result]} {
5424  Msg Error "Result: $result\n"
5425  Msg Error "Place & Route failed. See the report file.\n"
5426  } else {
5427  Msg Info "\nINFO: Place & Route was successful for revision $revision.\n"
5428  }
5429 
5430  if {[catch {execute_module -tool sta -args "--do_report_timing"} result]} {
5431  Msg Error "Result: $result\n"
5432  Msg Error "Time Quest failed. See the report file.\n"
5433  } else {
5434  Msg Info "Time Quest was successfully run for revision $revision.\n"
5435  load_package report
5436  load_report
5437  set panel "Timing Analyzer||Timing Analyzer Summary"
5438  set device [get_report_panel_data -name $panel -col 1 -row_name "Device Name"]
5439  set timing_model [get_report_panel_data -name $panel -col 1 -row_name "Timing Models"]
5440  set delay_model [get_report_panel_data -name $panel -col 1 -row_name "Delay Model"]
5441  #set slack [ get_timing_analysis_summary_results -slack ]
5442  Msg Info "*******************************************************************"
5443  Msg Info "Device: $device"
5444  Msg Info "Timing Models: $timing_model"
5445  Msg Info "Delay Model: $delay_model"
5446  Msg Info "Slack:"
5447  #Msg Info $slack
5448  Msg Info "*******************************************************************"
5449  }
5450  } elseif {[IsLibero]} {
5451  Msg Info "Starting implementation flow..."
5452  if {[catch {run_tool -name {PLACEROUTE}}]} {
5453  Msg Error "PLACEROUTE FAILED!"
5454  } else {
5455  Msg Info "PLACEROUTE PASSED."
5456  }
5457 
5458  # Check timing
5459  Msg Info "Run VERIFYTIMING ..."
5460  if {[catch {run_tool -name {VERIFYTIMING} -script {Hog/Tcl/integrated/libero_timing.tcl}}]} {
5461  Msg CriticalWarning "VERIFYTIMING FAILED!"
5462  } else {
5463  Msg Info "VERIFYTIMING PASSED \n"
5464  }
5465 
5466  #Go to repository path
5467  cd $repo_path
5468 
5469  set describe [GetHogDescribe [file normalize ./Top/$project_name] $repo_path]
5470  Msg Info "Git describe set to $describe"
5471 
5472  set dst_dir [file normalize "$repo_path/bin/$project_name\-$describe"]
5473  file mkdir $dst_dir/reports
5474 
5475  #Version table
5476  if {[file exists $run_folder/versions.txt]} {
5477  file copy -force $run_folder/versions.txt $dst_dir
5478  } else {
5479  Msg Warning "No versions file found in $run_folder/versions.txt"
5480  }
5481  #Timing file
5482  set timing_file_path [file normalize "$repo_path/Projects/timing_libero.txt"]
5483  if {[file exists $timing_file_path]} {
5484  file copy -force $timing_file_path $dst_dir/reports/Timing.txt
5485  set timing_file [open $timing_file_path "r"]
5486  set status_file [open "$dst_dir/timing.txt" "w"]
5487  puts $status_file "## $project_name Timing summary\n\n"
5488  puts $status_file "| | |"
5489  puts $status_file "| --- | --- |"
5490  while {[gets $timing_file line] >= 0} {
5491  if {[string match "SUMMARY" $line]} {
5492  while {[gets $timing_file line] >= 0} {
5493  if {[string match "END SUMMARY" $line]} {
5494  break
5495  }
5496  if {[string first ":" $line] == -1} {
5497  continue
5498  }
5499  set out_string "| [string map {: | } $line] |"
5500  puts $status_file "$out_string"
5501  }
5502  }
5503  }
5504  } else {
5505  Msg Warning "No timing file found, not a problem if running locally"
5506  }
5507  } elseif {[IsDiamond]} {
5508  set force_rst ""
5509  if {$reset == 1} {
5510  set force_rst "-forceOne"
5511  }
5512  prj_run Map $force_rst
5513  prj_run PAR $force_rst
5514 
5515  # TODO: Check Timing for Diamond
5516  }
5517 }
5518 
5519 # @brief Re-generate the bitstream, for the current IDE and project (Vivado only for the moment). \
5520 # Useful for a Vivado-Vitis project to update the bitstream with a new ELF or to generate a new \
5521 # bootimage (ZYNQ) without running the full workflow.
5522 #
5523 # @param[in] project_name The name of the project
5524 # @param[in] repo_path The main path of the git repository (Default .)
5525 proc GenerateBitstreamOnly {project_name {repo_path .}} {
5526  cd $repo_path
5527 
5528  # Open the project first
5529  set project_file [file normalize "$repo_path/Projects/$project_name/$project_name.xpr"]
5530  if {![file exists $project_file]} {
5531  Msg Error "Project file not found: $project_file. Please create the project first."
5532  return
5533  }
5534 
5535  OpenProject $project_file $repo_path
5536 
5537  # Check if impl_1 run exists
5538  set impl_runs [get_runs -quiet impl_1]
5539  if {[llength $impl_runs] == 0} {
5540  Msg Error "Implementation run 'impl_1' does not exist. Please run implementation first."
5541  return
5542  }
5543 
5544  set describe [GetHogDescribe [file normalize ./Top/$project_name] $repo_path]
5545  set dst_dir [file normalize "$repo_path/bin/$project_name\-$describe"]
5546 
5547  cd Projects/$project_name/$project_name.runs/impl_1
5548  Msg Info "Running pre-bitstream..."
5549  source $repo_path/Hog/Tcl/integrated/pre-bitstream.tcl
5550 
5551  Msg Info "Writing bitstream for $project_name..."
5552  open_run impl_1
5553  write_bitstream -force $dst_dir/$project_name-$describe.bit
5554 
5555  Msg Info "Running post-bitstream..."
5556  source $repo_path/Hog/Tcl/integrated/post-bitstream.tcl
5557 }
5558 
5559 # @brief Launch the simulation (Vivado only for the moment)
5560 #
5561 # @param[in] project_name The name of the project
5562 # @param[in] lib_path The path to the simulation libraries
5563 # @param[in] simsets The simulation sets to simulate
5564 # @param[in] repo_path The main path of the git repository
5565 proc LaunchSimulation {project_name lib_path simsets {repo_path .} {scripts_only 0} {compile_only 0}} {
5566  if {[IsVivado]} {
5567  ##################### SIMULATION #######################
5568  set project [file tail $project_name]
5569  set main_sim_folder [file normalize "$repo_path/Projects/$project_name/$project.sim/"]
5570  set simsets_todo ""
5571  if {$simsets != ""} {
5572  dict for {simset sim_dict} $simsets {
5573  lappend simsets_todo $simset
5574  }
5575  Msg Info "Will run only the following simulation's sets (if they exist): $simsets_todo"
5576  }
5577 
5578  if {$scripts_only == 1} {
5579  Msg Info "Only generating simulation scripts, not running simulations..."
5580  }
5581 
5582  if {$compile_only == 1} {
5583  Msg Info "Only compiling simulation libraries, not running simulations..."
5584  }
5585 
5586  set failed []
5587  set success []
5588  set sim_dic [dict create]
5589 
5590  Msg Info "Retrieving list of simulation sets..."
5591  foreach s [get_filesets] {
5592  # Default behaviour, dont use simpass string and simulation is not quiet
5593  set use_simpass_str 0
5594  set quiet_sim ""
5595 
5596  set type [get_property FILESET_TYPE $s]
5597  if {$type eq "SimulationSrcs"} {
5598  if {$simsets_todo != "" && $s ni $simsets_todo} {
5599  Msg Info "Skipping $s as it was not specified with the -simset option..."
5600  continue
5601  }
5602  set sim_dict [DictGet $simsets $s]
5603  set simulator [DictGet $sim_dict "simulator"]
5604  set_property "target_simulator" $simulator [current_project]
5605  set hog_sim_props [DictGet $sim_dict "hog"]
5606  dict for {prop_name prop_val} $hog_sim_props {
5607  # If HOG_SIMPASS_STR is set, use the HOG_SIMPASS_STR string to search for in logs, after simulation is done
5608  if {[string toupper $prop_name] == "HOG_SIMPASS_STR" && $prop_val != ""} {
5609  Msg Info "Setting simulation pass string as '$prop_val'"
5610  set use_simpass_str 1
5611  set simpass_str $prop_val
5612  }
5613  if {[string toupper $prop_name] == "HOG_SILENT_SIM" && $prop_val == 1} {
5614  set quiet_sim " -quiet"
5615  } else {
5616  set quiet_sim ""
5617  }
5618  }
5619 
5620  Msg Info "Creating simulation scripts for $s..."
5621  if {[file exists $repo_path/Top/$project_name/pre-simulation.tcl]} {
5622  Msg Info "Running $repo_path/Top/$project_name/pre-simulation.tcl"
5623  source $repo_path/Top/$project_name/pre-simulation.tcl
5624  }
5625  if {[file exists $repo_path/Top/$project_name/pre-$s-simulation.tcl]} {
5626  Msg Info "Running $repo_path/Top/$project_name/pre-$s-simulation.tcl"
5627  source Running $repo_path/Top/$project_name/pre-$s-simulation.tcl
5628  }
5629  current_fileset -simset $s
5630  set sim_dir $main_sim_folder/$s/behav
5631  set sim_output_logfile $sim_dir/xsim/simulate.log
5632  if {([string tolower $simulator] eq "xsim")} {
5633  set sim_name "xsim:$s"
5634 
5635  set simulation_command "launch_simulation $quiet_sim -simset [get_filesets $s]"
5636  if {[catch $simulation_command log]} {
5637  # Explicitly close xsim simulation, without closing Vivado
5638  close_sim
5639  Msg CriticalWarning "Simulation failed for $s, error info: $::errorInfo"
5640  lappend failed $sim_name
5641  } else {
5642  # Explicitly close xsim simulation, without closing Vivado
5643  close_sim
5644  # If we use simpass_str, search for the string and update return code from simulation if the string is not found in simulation log
5645  if {$use_simpass_str == 1} {
5646  # Get the simulation output log
5647  # Note, xsim should always output simulation.log, hence no check for existence
5648  set file_desc [open $sim_output_logfile r]
5649  set log [read $file_desc]
5650  close $file_desc
5651 
5652  Msg Info "Searching for simulation pass string: '$simpass_str'"
5653  if {[string first $simpass_str $log] == -1} {
5654  Msg CriticalWarning "Simulation failed for $s, error info: '$simpass_str' NOT found!"
5655  lappend failed $sim_name
5656  } else {
5657  # HOG_SIMPASS_STR found, success
5658  lappend success $sim_name
5659  }
5660  } else {
5661  #Rely on simulator exit code
5662  lappend success $sim_name
5663  }
5664  }
5665  } else {
5666  Msg Info "Simulation library path is set to $lib_path."
5667  set simlib_ok 1
5668  if {!([file exists $lib_path])} {
5669  Msg Warning "Could not find simulation library path: $lib_path, $simulator simulation will not work."
5670  set simlib_ok 0
5671  }
5672 
5673  if {$simlib_ok == 1} {
5674  set_property "compxlib.${simulator}_compiled_library_dir" [file normalize $lib_path] [current_project]
5675  launch_simulation -scripts_only -simset [get_filesets $s]
5676  set top_name [get_property TOP $s]
5677  set sim_script [file normalize $sim_dir/$simulator/]
5678  Msg Info "Adding simulation script location $sim_script for $s..."
5679  lappend sim_scripts $sim_script
5680  dict append sim_dic $sim_script $s
5681  } else {
5682  Msg Error "Cannot run $simulator simulations without a valid library path"
5683  exit -1
5684  }
5685  }
5686  }
5687  }
5688 
5689  if {[info exists sim_scripts] && $scripts_only == 0} {
5690  # Only for modelsim/questasim
5691  Msg Info "Generating IP simulation targets, if any..."
5692 
5693  foreach ip [get_ips] {
5694  generate_target simulation -quiet $ip
5695  }
5696 
5697 
5698  Msg Status "\n\n"
5699  Msg Info "====== Starting simulations runs ======"
5700  Msg Status "\n\n"
5701 
5702  foreach s $sim_scripts {
5703  cd $s
5704  set cmd ./compile.sh
5705  Msg Info " ************* Compiling: $s ************* "
5706  lassign [ExecuteRet $cmd] ret log
5707  set sim_name "comp:[dict get $sim_dic $s]"
5708  if {$ret != 0} {
5709  Msg CriticalWarning "Compilation failed for $s, error info: $::errorInfo"
5710  lappend failed $sim_name
5711  } else {
5712  lappend success $sim_name
5713  }
5714  Msg Info "###################### Compilation log starts ######################"
5715  Msg Info "\n\n$log\n\n"
5716  Msg Info "###################### Compilation log ends ######################"
5717 
5718  if {$compile_only == 1} {
5719  continue
5720  }
5721  if {[file exists "./elaborate.sh"] } {
5722  set cmd ./elaborate.sh
5723  Msg Info " ************* Elaborating: $s ************* "
5724  lassign [ExecuteRet $cmd] ret log
5725  set sim_name "elab:[dict get $sim_dic $s]"
5726  if {$ret != 0} {
5727  Msg CriticalWarning "Elaboration failed for $s, error info: $::errorInfo"
5728  lappend failed $sim_name
5729  } else {
5730  lappend success $sim_name
5731  }
5732  Msg Info "###################### Elaboration log starts ######################"
5733  Msg Info "\n\n$log\n\n"
5734  Msg Info "###################### Elaboration log ends ######################"
5735  }
5736  set cmd ./simulate.sh
5737  Msg Info " ************* Simulating: $s ************* "
5738  lassign [ExecuteRet $cmd] ret log
5739 
5740 
5741  # If SIMPASS_STR is set, search log for the string
5742  if {$use_simpass_str == 1} {
5743  if {[string first $simpass_str $log] == -1} {
5744  set ret 1
5745  }
5746  } else {
5747  Msg Debug "Simulation pass string not set, relying on simulator exit code."
5748  }
5749 
5750 
5751  set sim_name "sim:[dict get $sim_dic $s]"
5752  if {$ret != 0} {
5753  Msg CriticalWarning "Simulation failed for $s, error info: $::errorInfo"
5754  lappend failed $sim_name
5755  } else {
5756  lappend success $sim_name
5757  }
5758  Msg Info "###################### Simulation log starts ######################"
5759  Msg Info "\n\n$log\n\n"
5760  Msg Info "###################### Simulation log ends ######################"
5761  }
5762  }
5763 
5764 
5765  if {[llength $success] > 0} {
5766  set successes [join $success "\n"]
5767  Msg Info "The following simulation sets were successful:\n\n$successes\n\n"
5768  }
5769 
5770  if {[llength $failed] > 0} {
5771  set failures [join $failed "\n"]
5772  Msg Error "The following simulation sets have failed:\n\n$failures\n\n"
5773  exit -1
5774  } elseif {[llength $success] > 0} {
5775  Msg Info "All the [llength $success] compilations, elaborations and simulations were successful."
5776  }
5777 
5778  Msg Info "Simulation done."
5779  } else {
5780  Msg Warning "Simulation is not yet supported for [GetIDEName]."
5781  }
5782 }
5783 
5784 # @brief Launch the RTL Analysis, for the current IDE and project
5785 #
5786 # @param[in] repo_path The main path of the git repository (Default .)
5787 proc LaunchRTLAnalysis {repo_path {pre_rtl_file ""} {post_rtl_file ""}} {
5788  if {[IsVivado]} {
5789  if {[file exists $pre_rtl_file]} {
5790  Msg Info "Found pre-rtl Tcl script $pre_rtl_file, executing it..."
5791  source $pre_rtl_file
5792  }
5793  Msg Info "Starting RTL Analysis..."
5794  cd $repo_path
5795  synth_design -rtl -name rtl_1
5796  if {[file exists $post_rtl_file]} {
5797  Msg Info "Found post-rtl Tcl script $post_rtl_file, executing it..."
5798  source $post_rtl_file
5799  }
5800  } else {
5801  Msg Warning "RTL Analysis is not yet supported for [GetIDEName]."
5802  }
5803 }
5804 
5805 # @brief Launch the synthesis, for the current IDE and project
5806 #
5807 # @param[in] reset Reset the Synthesis run
5808 # @param[in] do_create Recreate the project
5809 # @param[in] run_folder The folder where to store the run results
5810 # @param[in] project_name The name of the project
5811 # @param[in] repo_path The main path of the git repository (Default .)
5812 # @param[in] ext_path The path of source files external to the git repo (Default "")
5813 # @param[in] njobs The number of parallel CPU jobs for the synthesis (Default 4)
5814 proc LaunchSynthesis {reset do_create run_folder project_name {repo_path .} {ext_path ""} {njobs 4}} {
5815  if {[IsXilinx]} {
5816  if {$reset == 1} {
5817  Msg Info "Resetting run before launching synthesis..."
5818  reset_run synth_1
5819  }
5820  if {[IsISE]} {
5821  source $repo_path/Hog/Tcl/integrated/pre-synthesis.tcl
5822  }
5823  launch_runs synth_1 -jobs $njobs -dir $run_folder
5824  wait_on_run synth_1
5825  set prog [get_property PROGRESS [get_runs synth_1]]
5826  set status [get_property STATUS [get_runs synth_1]]
5827  Msg Info "Run: synth_1 progress: $prog, status : $status"
5828  # Copy IP reports in bin/
5829  set ips [get_ips *]
5830 
5831  #go to repository path
5832  cd $repo_path
5833 
5834  set describe [GetHogDescribe [file normalize ./Top/$project_name] $repo_path]
5835  Msg Info "Git describe set to $describe"
5836 
5837  foreach ip $ips {
5838  set xci_file [get_property IP_FILE $ip]
5839 
5840  set xci_path [file dirname $xci_file]
5841  set xci_ip_name [file rootname [file tail $xci_file]]
5842  foreach rptfile [glob -nocomplain -directory $xci_path *.rpt] {
5843  file copy $rptfile $repo_path/bin/$project_name-$describe/reports
5844  }
5845  }
5846 
5847  if {$prog ne "100%"} {
5848  Msg Error "Synthesis error, status is: $status"
5849  }
5850  } elseif {[IsQuartus]} {
5851  # TODO: Missing reset
5852  set project [file tail [file rootname $project_name]]
5853 
5854  Msg Info "Number of jobs set to $njobs."
5855  set_global_assignment -name NUM_PARALLEL_PROCESSORS $njobs
5856 
5857 
5858  # keep track of the current revision and of the top level entity name
5859  set describe [GetHogDescribe [file normalize $repo_path/Top/$project_name] $repo_path]
5860  #set top_level_name [ get_global_assignment -name TOP_LEVEL_ENTITY ]
5861  set revision [get_current_revision]
5862 
5863  #run PRE_FLOW_SCRIPT by hand
5864  set tool_and_command [split [get_global_assignment -name PRE_FLOW_SCRIPT_FILE] ":"]
5865  set tool [lindex $tool_and_command 0]
5866  set pre_flow_script [lindex $tool_and_command 1]
5867  set cmd "$tool -t $pre_flow_script quartus_map $project $revision"
5868  #Close project to avoid conflict with pre synthesis script
5869  project_close
5870 
5871  lassign [ExecuteRet {*}$cmd] ret log
5872  if {$ret != 0} {
5873  Msg Warning "Can not execute command $cmd"
5874  Msg Warning "LOG: $log"
5875  } else {
5876  Msg Info "Pre flow script executed!"
5877  }
5878 
5879  # Re-open project
5880  if {![is_project_open]} {
5881  Msg Info "Re-opening project file $project_name..."
5882  project_open $project -current_revision
5883  }
5884 
5885  # Generate IP Files
5886  if {[catch {execute_module -tool ipg -args "--clean"} result]} {
5887  Msg Error "Result: $result\n"
5888  Msg Error "IP Generation failed. See the report file.\n"
5889  } else {
5890  Msg Info "IP Generation was successful for revision $revision.\n"
5891  }
5892 
5893  # Execute synthesis
5894  if {[catch {execute_module -tool map -args "--parallel"} result]} {
5895  Msg Error "Result: $result\n"
5896  Msg Error "Analysis & Synthesis failed. See the report file.\n"
5897  } else {
5898  Msg Info "Analysis & Synthesis was successful for revision $revision.\n"
5899  }
5900  } elseif {[IsLibero]} {
5901  # TODO: Missing Reset
5902  defvar_set -name RWNETLIST_32_64_MIXED_FLOW -value 0
5903 
5904  Msg Info "Run SYNTHESIS..."
5905  if {[catch {run_tool -name {SYNTHESIZE}}]} {
5906  Msg Error "SYNTHESIZE FAILED!"
5907  } else {
5908  Msg Info "SYNTHESIZE PASSED!"
5909  }
5910  } elseif {[IsDiamond]} {
5911  # TODO: Missing Reset
5912  set force_rst ""
5913  if {$reset == 1} {
5914  set force_rst "-forceOne"
5915  }
5916  prj_run Synthesis $force_rst
5917  if {[prj_syn] == "synplify"} {
5918  prj_run Translate $force_rst
5919  }
5920  } else {
5921  Msg Error "Impossible condition. You need to run this in an IDE."
5922  exit 1
5923  }
5924 }
5925 
5926 
5927 # @brief Launch the Vitis build
5928 #
5929 # @param[in] project_name The name of the project
5930 # @param[in] repo_path The main path of the git repository (Default ".")
5931 # @param[in] stage The stage of the build (Default "presynth")
5932 proc LaunchVitisBuild {project_name {repo_path .} {stage "presynth"}} {
5933  set proj_name $project_name
5934  set bin_dir [file normalize "$repo_path/bin"]
5935 
5936  cd $repo_path
5937 
5938  # Get app list
5939  if {[IsVitisUnified]} {
5940  set vitis_workspace [file normalize "$repo_path/Projects/$project_name/vitis_unified"]
5941  set python_script [file normalize "$repo_path/Hog/Other/Python/VitisUnified/AppCommands.py"]
5942  set json_output ""
5943  if {![ExecuteVitisUnifiedCommand $python_script "app_list" [list $vitis_workspace] "Failed to get app list from Vitis Unified" json_output]} {
5944  Msg Error "Failed to get app list from Vitis Unified"
5945  set ws_apps ""
5946  } else {
5947  if {[catch {package require json}]} {
5948  Msg Error "JSON package not available for parsing Vitis Unified app list"
5949  set ws_apps ""
5950  } else {
5951  set json_output_filtered ""
5952  if {[regexp -lineanchor {\{.*\}} $json_output json_output_filtered]} {
5953  set ws_apps [json::json2dict $json_output_filtered]
5954  } else {
5955  set ws_apps [json::json2dict $json_output]
5956  }
5957  }
5958  }
5959  } elseif {[IsVitisClassic]} {
5960  if {[catch {set ws_apps [app list -dict]}]} { set ws_apps "" }
5961  } else {
5962  Msg Error "Impossible condition. You need to run this in a Vitis Unified or Vitis Classic IDE."
5963  exit 1
5964  }
5965 
5966  # Get repository versions
5967  lassign [GetRepoVersions [file normalize $repo_path/Top/$proj_name] $repo_path ] commit version hog_hash hog_ver top_hash top_ver \
5968  libs hashes vers cons_ver cons_hash ext_names ext_hashes xml_hash xml_ver user_ip_repos user_ip_hashes user_ip_vers
5969  set this_commit [GetSHA]
5970  if {$commit == 0 } { set commit $this_commit }
5971  set flavour [GetProjectFlavour $project_name]
5972  lassign [GetDateAndTime $commit] date timee
5973 
5974  # Configure apps only for Vitis Classic, build-config is not supported in Vitis Unified
5975  # build directory seems to be the default
5976  if {[IsVitisClassic]} {
5977  foreach app_name [dict keys $ws_apps] {
5978  app config -name $app_name -set build-config Release
5979  }
5980  }
5981 
5982  WriteGenerics "vitisbuild" $repo_path $proj_name $date $timee $commit $version $top_hash $top_ver $hog_hash $hog_ver $cons_ver $cons_hash $libs \
5983  $vers $hashes $ext_names $ext_hashes $user_ip_repos $user_ip_vers $user_ip_hashes $flavour $xml_ver $xml_hash
5984 
5985  # Build apps
5986  foreach app_name [dict keys $ws_apps] {
5987  if {[IsVitisUnified]} {
5988  # Build vitis unified app
5989  if {![ExecuteVitisUnifiedCommand $python_script "build_app" [list $app_name $vitis_workspace] "Failed to build app $app_name"]} {
5990  Msg Error "Failed to build app $app_name"
5991  continue
5992  }
5993  } elseif {[IsVitisClassic]} {
5994  app build -name $app_name
5995  }
5996  }
5997 
5998  if {$stage == "presynth"} {
5999  Msg Info "Done building apps for $project_name..."
6000  }
6001 
6002  if {[info exists ::globalSettings::vitis_only_pass] && $::globalSettings::vitis_only_pass == 1} {
6003  Msg Info "Skipping bin directory creation in vitis_only mode (post-bitstream.tcl handles artifacts)."
6004  return
6005  }
6006 
6007  Msg Info "Evaluating Hog describe for $project_name..."
6008  set describe [GetHogDescribe [file normalize ./Top/$project_name] $repo_path]
6009  Msg Info "Hog describe set to: $describe"
6010  set dst_dir [file normalize "$bin_dir/$proj_name\-$describe"]
6011  if {![file exists $dst_dir]} {
6012  Msg Info "Creating $dst_dir..."
6013  file mkdir $dst_dir
6014  }
6015 
6016  foreach app_name [dict keys $ws_apps] {
6017  if {[IsVitisUnified]} {
6018  set main_file "$repo_path/Projects/$project_name/vitis_unified/$app_name/build/$app_name.elf"
6019  } elseif {[IsVitisClassic]} {
6020  set main_file "$repo_path/Projects/$project_name/vitis_classic/$app_name/Release/$app_name.elf"
6021  }
6022  set dst_main [file normalize "$dst_dir/[file tail $proj_name]\-$app_name\-$describe.elf"]
6023 
6024  if {![file exists $main_file]} {
6025  Msg Error "No Vitis .elf file found. Perhaps there was an issue building it?"
6026  continue
6027  }
6028 
6029  Msg Info "Copying main binary file $main_file into $dst_main..."
6030  file copy -force $main_file $dst_main
6031  }
6032 }
6033 
6034 # @brief Launch the HLS build for all [hls:*] components in the project
6035 #
6036 # For each HLS component defined in hog.conf, this proc runs:
6037 # - C synthesis
6038 # - Implementation
6039 # - Collects reports (.rpt, .xml, .log) into bin/ for CI
6040 # Packaging is controlled by hls_config.cfg (package.output.format, etc.)
6041 # Simulations (CSIM/COSIM) are handled by LaunchHlsSimulation via the S command.
6042 #
6043 # @param[in] project_name The name of the project
6044 # @param[in] repo_path The main path of the git repository (Default ".")
6045 proc LaunchHlsBuild {project_name {repo_path .}} {
6046  set proj_name $project_name
6047  set bin_dir [file normalize "$repo_path/bin"]
6048 
6049  cd $repo_path
6050 
6051  set conf_file [file normalize "$repo_path/Top/$project_name/hog.conf"]
6052  if {![file exists $conf_file]} {
6053  Msg Error "Configuration file not found: $conf_file"
6054  return
6055  }
6056  set properties [ReadConf $conf_file]
6057  set hls_components [dict filter $properties key {hls:*}]
6058 
6059  if {[dict size $hls_components] == 0} {
6060  Msg Info "No HLS components found for project $project_name"
6061  return
6062  }
6063 
6064  set python_script [file normalize "$repo_path/Hog/Other/Python/VitisUnified/HlsCommands.py"]
6065 
6066  # Determine whether this is a mixed Vivado+Vitis project. In that case HLS
6067  # component outputs are grouped under bin/<proj>/vitis_hls/ to keep them
6068  # visually separate from Vivado's top-level utilization.txt / timing_*.txt.
6069  # For pure vitis_unified projects (no Vivado) HLS outputs sit directly under
6070  # bin/<proj>/<component>/ since there's nothing else to collide with.
6071  set ide_info [GetIDEFromConf $conf_file]
6072  set ide_name [string tolower [lindex $ide_info 0]]
6073  if {$ide_name eq "vivado_vitis_unified"} {
6074  set is_mixed_project 1
6075  } else {
6076  set is_mixed_project 0
6077  }
6078 
6079  dict for {hls_key hls_props} $hls_components {
6080  if {![regexp {^hls:(.+)$} $hls_key -> component_name]} {
6081  continue
6082  }
6083  set component_name [string trim $component_name]
6084  Msg Info "Building HLS component: $component_name"
6085 
6086  # Read HLS_CONFIG path from hog.conf (mandatory)
6087  set hls_cfg_rel ""
6088  if {[dict exists $hls_props hls_config]} {
6089  set hls_cfg_rel [dict get $hls_props hls_config]
6090  } elseif {[dict exists $hls_props HLS_CONFIG]} {
6091  set hls_cfg_rel [dict get $hls_props HLS_CONFIG]
6092  }
6093  if {$hls_cfg_rel eq ""} {
6094  Msg Error "HLS component '$component_name' missing HLS_CONFIG in hog.conf"
6095  continue
6096  }
6097 
6098  set cfg_file [file normalize "$repo_path/$hls_cfg_rel"]
6099  if {![file exists $cfg_file]} {
6100  Msg Error "HLS config file not found: $cfg_file (from HLS_CONFIG=$hls_cfg_rel)"
6101  continue
6102  }
6103 
6104  set hls_work_dir [file normalize "$repo_path/Projects/$project_name/vitis_unified/$component_name"]
6105  file mkdir $hls_work_dir
6106 
6107  # C synthesis
6108  Msg Info "Running C synthesis for HLS component '$component_name'..."
6109  if {![ExecuteVitisUnifiedCommand $python_script "synthesis" \
6110  [list $component_name $cfg_file $hls_work_dir] \
6111  "Failed to run C synthesis for $component_name"]} {
6112  Msg Error "C synthesis failed for HLS component '$component_name'"
6113  continue
6114  }
6115 
6116  # Implementation
6117  Msg Info "Running implementation for HLS component '$component_name'..."
6118  if {![ExecuteVitisUnifiedCommand $python_script "impl" \
6119  [list $component_name $cfg_file $hls_work_dir] \
6120  "Failed to run implementation for $component_name"]} {
6121  Msg Error "Implementation failed for HLS component '$component_name'"
6122  continue
6123  }
6124 
6125  # Export VHDL to source tree if VHDL_OUTPUT is set
6126  set vhdl_output ""
6127  if {[dict exists $hls_props vhdl_output]} {
6128  set vhdl_output [dict get $hls_props vhdl_output]
6129  } elseif {[dict exists $hls_props VHDL_OUTPUT]} {
6130  set vhdl_output [dict get $hls_props VHDL_OUTPUT]
6131  }
6132  if {$vhdl_output ne ""} {
6133  set vhdl_output_dir [file normalize "$repo_path/$vhdl_output"]
6134  Msg Info "Exporting VHDL for '$component_name' to $vhdl_output_dir..."
6135  if {![ExecuteVitisUnifiedCommand $python_script "export_rtl" \
6136  [list $component_name $hls_work_dir $vhdl_output_dir vhdl] \
6137  "Failed to export VHDL for $component_name"]} {
6138  Msg Warning "Could not export VHDL for HLS component '$component_name'"
6139  }
6140  }
6141 
6142  # Export Verilog to source tree if VERILOG_OUTPUT is set
6143  set verilog_output ""
6144  if {[dict exists $hls_props verilog_output]} {
6145  set verilog_output [dict get $hls_props verilog_output]
6146  } elseif {[dict exists $hls_props VERILOG_OUTPUT]} {
6147  set verilog_output [dict get $hls_props VERILOG_OUTPUT]
6148  }
6149  if {$verilog_output ne ""} {
6150  set verilog_output_dir [file normalize "$repo_path/$verilog_output"]
6151  Msg Info "Exporting Verilog for '$component_name' to $verilog_output_dir..."
6152  if {![ExecuteVitisUnifiedCommand $python_script "export_rtl" \
6153  [list $component_name $hls_work_dir $verilog_output_dir verilog] \
6154  "Failed to export Verilog for $component_name"]} {
6155  Msg Warning "Could not export Verilog for HLS component '$component_name'"
6156  }
6157  }
6158 
6159  # Export IP catalog ZIP to source tree if IP_OUTPUT is set
6160  set ip_output ""
6161  if {[dict exists $hls_props ip_output]} {
6162  set ip_output [dict get $hls_props ip_output]
6163  } elseif {[dict exists $hls_props IP_OUTPUT]} {
6164  set ip_output [dict get $hls_props IP_OUTPUT]
6165  }
6166  if {$ip_output ne ""} {
6167  set ip_output_dir [file normalize "$repo_path/$ip_output"]
6168  Msg Info "Exporting IP catalog for '$component_name' to $ip_output_dir..."
6169  if {![ExecuteVitisUnifiedCommand $python_script "export_ip" \
6170  [list $component_name $hls_work_dir $ip_output_dir] \
6171  "Failed to export IP for $component_name"]} {
6172  Msg Error "Could not export IP for HLS component '$component_name'. Make sure package.output.format=ip_catalog is set in hls_config.cfg."
6173  }
6174  }
6175 
6176  # Collect reports into bin/ for CI and release notes
6177  Msg Info "Evaluating Hog describe for $project_name..."
6178  set describe [GetHogDescribe [file normalize ./Top/$project_name] $repo_path]
6179  Msg Info "Hog describe set to: $describe"
6180  set dst_dir [file normalize "$bin_dir/$proj_name\-$describe"]
6181  if {![file exists $dst_dir]} {
6182  Msg Info "Creating $dst_dir..."
6183  file mkdir $dst_dir
6184  }
6185 
6186  # Mixed projects group HLS components under bin/<proj>/vitis_hls/; pure
6187  # vitis_unified projects place them directly under bin/<proj>/
6188  if {$is_mixed_project} {
6189  set hls_out_dir [file normalize "$dst_dir/vitis_hls"]
6190  } else {
6191  set hls_out_dir $dst_dir
6192  }
6193 
6194  Msg Info "Collecting HLS reports for component '$component_name'..."
6195  if {![ExecuteVitisUnifiedCommand $python_script "collect_reports" \
6196  [list $component_name $hls_work_dir $hls_out_dir] \
6197  "Failed to collect HLS reports for $component_name"]} {
6198  Msg Warning "No reports found for HLS component '$component_name'"
6199  }
6200 
6201  # Generate markdown summary files (utilization.txt, timing_ok.txt /
6202  # timing_error.txt) under <hls_out_dir>/<component>/. File names mirror the
6203  # Vivado convention so the existing CI logic (release notes assembly and
6204  # timing-failure detection) works for HLS components too
6205  Msg Info "Generating HLS release-notes summary for component '$component_name'..."
6206  if {![ExecuteVitisUnifiedCommand $python_script "release_notes" \
6207  [list $component_name $hls_work_dir $hls_out_dir] \
6208  "Failed to generate HLS summary for $component_name"]} {
6209  Msg Warning "Could not generate HLS summary for component '$component_name'"
6210  }
6211 
6212  Msg Info "HLS component '$component_name' built successfully"
6213  }
6214 
6215  # For pure vitis_unified (HLS-only) projects, emit a top-level versions.txt
6216  # at the bin project root
6217  if {!$is_mixed_project && [info exists dst_dir] && [file isdirectory $dst_dir]} {
6218  set versions_file [file normalize "$dst_dir/versions.txt"]
6219  Msg Info "Generating top-level versions.txt for pure-HLS project at $versions_file"
6220  if {[catch {
6221  lassign [GetRepoVersions [file normalize $repo_path/Top/$project_name] $repo_path] \
6222  commit version hog_hash hog_ver top_hash top_ver \
6223  libs hashes vers cons_ver cons_hash \
6224  ext_names ext_hashes xml_hash xml_ver \
6225  user_ip_repos user_ip_hashes user_ip_vers
6226  if {$commit == 0} { set commit [GetSHA] }
6227  set version_str [HexVersionToString $version]
6228  set hog_ver_str [HexVersionToString $hog_ver]
6229  set top_ver_str [HexVersionToString $top_ver]
6230  set fh [open $versions_file "w"]
6231  puts $fh "## $proj_name Version Table\n"
6232  puts $fh "| **File set** | **Commit SHA** | **Version** |"
6233  puts $fh "| --- | --- | --- |"
6234  puts $fh "| Global | $commit | $version_str |"
6235  puts $fh "| Top Directory | $top_hash | $top_ver_str |"
6236  puts $fh "| Hog | $hog_hash | $hog_ver_str |"
6237  foreach l $libs v $vers h $hashes {
6238  set v_str [HexVersionToString $v]
6239  puts $fh "| **Lib:** $l | $h | $v_str |"
6240  }
6241  puts $fh "\n"
6242  close $fh
6243  } err]} {
6244  Msg Warning "Could not generate top-level versions.txt for pure-HLS project: $err"
6245  }
6246  }
6247 
6248  Msg Info "Done building HLS components for $project_name"
6249 }
6250 
6251 
6252 # @brief Launch HLS simulations for [hls:*] components in the project
6253 #
6254 # When called without hls_simsets (or with an empty list), runs all enabled
6255 # HLS simulations (CSIM if CSIM=true, COSIM if COSIM=true in hog.conf).
6256 # When called with specific targets (e.g. "csim:iir_lp_filter cosim:iir_lp_filter"),
6257 # runs only the requested simulations.
6258 # COSIM automatically triggers C synthesis first if not already done.
6259 # Triggered by the S (SIMULATE) command.
6260 #
6261 # @param[in] project_name The name of the project
6262 # @param[in] repo_path The main path of the git repository (Default ".")
6263 # @param[in] hls_simsets Optional list of specific HLS simsets to run (e.g. "csim:name" "cosim:name")
6264 proc LaunchHlsSimulation {project_name {repo_path .} {hls_simsets {}}} {
6265  cd $repo_path
6266 
6267  set conf_file [file normalize "$repo_path/Top/$project_name/hog.conf"]
6268  if {![file exists $conf_file]} {
6269  Msg Error "Configuration file not found: $conf_file"
6270  return
6271  }
6272  set properties [ReadConf $conf_file]
6273  set hls_components [dict filter $properties key {hls:*}]
6274 
6275  if {[dict size $hls_components] == 0} {
6276  Msg Info "No HLS components found for project $project_name"
6277  return
6278  }
6279 
6280  # Build a lookup of requested targets: component_name -> list of sim types
6281  # If hls_simsets is empty, we'll use hog.conf flags for all components
6282  set targeted_mode [expr {[llength $hls_simsets] > 0}]
6283  set targets [dict create]
6284  foreach entry $hls_simsets {
6285  if {[regexp {^(csim|cosim):(.+)$} $entry -> sim_type comp_name]} {
6286  dict lappend targets $comp_name $sim_type
6287  } else {
6288  Msg Error "Invalid HLS simset format '$entry'. Expected 'csim:<component>' or 'cosim:<component>'."
6289  Msg Info "Available HLS components in hog.conf:"
6290  dict for {hls_key hls_props} $hls_components {
6291  if {[regexp {^hls:(.+)$} $hls_key -> cname]} {
6292  Msg Info " - csim:[string trim $cname]"
6293  Msg Info " - cosim:[string trim $cname]"
6294  }
6295  }
6296  return
6297  }
6298  }
6299 
6300  set python_script [file normalize "$repo_path/Hog/Other/Python/VitisUnified/HlsCommands.py"]
6301 
6302  dict for {hls_key hls_props} $hls_components {
6303  if {![regexp {^hls:(.+)$} $hls_key -> component_name]} {
6304  continue
6305  }
6306  set component_name [string trim $component_name]
6307 
6308  # In targeted mode, skip components not in the request
6309  if {$targeted_mode && ![dict exists $targets $component_name]} {
6310  continue
6311  }
6312 
6313  # Read HLS_CONFIG path from hog.conf (mandatory)
6314  set hls_cfg_rel ""
6315  if {[dict exists $hls_props hls_config]} {
6316  set hls_cfg_rel [dict get $hls_props hls_config]
6317  } elseif {[dict exists $hls_props HLS_CONFIG]} {
6318  set hls_cfg_rel [dict get $hls_props HLS_CONFIG]
6319  }
6320  if {$hls_cfg_rel eq ""} {
6321  Msg Error "HLS component '$component_name' missing HLS_CONFIG in hog.conf"
6322  continue
6323  }
6324 
6325  set cfg_file [file normalize "$repo_path/$hls_cfg_rel"]
6326  if {![file exists $cfg_file]} {
6327  Msg Error "HLS config file not found: $cfg_file (from HLS_CONFIG=$hls_cfg_rel)"
6328  continue
6329  }
6330 
6331  set hls_work_dir [file normalize "$repo_path/Projects/$project_name/vitis_unified/$component_name"]
6332  file mkdir $hls_work_dir
6333 
6334  # Determine which simulations to run
6335  if {$targeted_mode} {
6336  # User explicitly requested these sim types via -simset
6337  set sim_types [dict get $targets $component_name]
6338  set run_csim [expr {"csim" in $sim_types}]
6339  set run_cosim [expr {"cosim" in $sim_types}]
6340 
6341  # Validate: check if the requested sim is enabled in hog.conf
6342  foreach st $sim_types {
6343  set flag_val ""
6344  if {[dict exists $hls_props $st]} {
6345  set flag_val [dict get $hls_props $st]
6346  } elseif {[dict exists $hls_props [string toupper $st]]} {
6347  set flag_val [dict get $hls_props [string toupper $st]]
6348  }
6349  set is_enabled [expr {[string tolower $flag_val] eq "true" || $flag_val eq "1"}]
6350  if {!$is_enabled} {
6351  set upper_st [string toupper $st]
6352  Msg Warning "HLS simulation '$st' requested for '$component_name' but\
6353  $upper_st is not enabled in \[hls:$component_name\] section of\
6354  hog.conf. Running it anyway."
6355  Msg Info "To enable it permanently, add '$upper_st=true' under\
6356  \[hls:$component_name\] in Top/$project_name/hog.conf"
6357  }
6358  }
6359  } else {
6360  # Default mode: read flags from hog.conf
6361  set run_csim 0
6362  set run_cosim 0
6363  foreach {key_lower key_upper} {csim CSIM cosim COSIM} {
6364  set val ""
6365  if {[dict exists $hls_props $key_lower]} {
6366  set val [dict get $hls_props $key_lower]
6367  } elseif {[dict exists $hls_props $key_upper]} {
6368  set val [dict get $hls_props $key_upper]
6369  }
6370  if {[string tolower $val] eq "true" || $val eq "1"} {
6371  set run_$key_lower 1
6372  }
6373  }
6374 
6375  if {!$run_csim && !$run_cosim} {
6376  Msg Info "No HLS simulations enabled for '$component_name' (set CSIM=true and/or COSIM=true in \[hls:$component_name\] in hog.conf)"
6377  continue
6378  }
6379  }
6380 
6381  Msg Info "Running HLS simulations for component: $component_name"
6382 
6383  # CSIM
6384  if {$run_csim} {
6385  Msg Info "Running C simulation for HLS component '$component_name'..."
6386  if {![ExecuteVitisUnifiedCommand $python_script "csim" \
6387  [list $component_name $cfg_file $hls_work_dir] \
6388  "Failed to run C simulation for $component_name"]} {
6389  Msg Error "C simulation failed for HLS component '$component_name'"
6390  continue
6391  }
6392  }
6393 
6394  # COSIM (auto-run synthesis if needed)
6395  if {$run_cosim} {
6396  set syn_done_marker [file normalize "$hls_work_dir/hls/syn"]
6397  if {![file exists $syn_done_marker]} {
6398  Msg Info "C synthesis output not found for '$component_name', running synthesis automatically before co-simulation..."
6399  if {![ExecuteVitisUnifiedCommand $python_script "synthesis" \
6400  [list $component_name $cfg_file $hls_work_dir] \
6401  "Failed to run C synthesis for $component_name"]} {
6402  Msg Error "C synthesis failed for HLS component '$component_name', cannot proceed with co-simulation"
6403  continue
6404  }
6405  }
6406 
6407  Msg Info "Running C/RTL co-simulation for HLS component '$component_name'..."
6408  if {![ExecuteVitisUnifiedCommand $python_script "cosim" \
6409  [list $component_name $cfg_file $hls_work_dir] \
6410  "Failed to run co-simulation for $component_name"]} {
6411  Msg Error "C/RTL co-simulation failed for HLS component '$component_name'"
6412  continue
6413  }
6414  }
6415 
6416  Msg Info "HLS simulations completed for '$component_name'"
6417  }
6418 
6419  Msg Info "Done with HLS simulations for $project_name"
6420 }
6421 
6422 # @brief Returns the BIF file path from the properties
6423 #
6424 # @param[in] props A dictionary with the properties defined in Hog.conf
6425 # @param[in] app The application name
6426 # @return The path of the BIF file or empty string if not found
6427 proc GetProcFromProps {repo_path props platform} {
6428  if {[dict exists $props "platform:$platform" "BIF"]} {
6429  set bif_file [dict get $props "platform:$platform" "BIF"]
6430  if {[IsRelativePath $bif_file] == 1} {
6431  set bif_file "$repo_path/$bif_file"
6432  }
6433  return $bif_file
6434  } else {
6435  Msg CriticalWarning "BIF file not found in platform ($platform) properties, skipping bootable image (.bin) generation"
6436  return ""
6437  }
6438 }
6439 
6440 # @brief Returns the BIF file path from the properties
6441 #
6442 # @param[in] props A dictionary with the properties defined in Hog.conf
6443 # @param[in] platform The platform name
6444 # @return The path of the BIF file or empty string if not found
6445 proc GetBifFromProps {repo_path props platform} {
6446  if {[dict exists $props "platform:$platform" "BIF"]} {
6447  set bif_file [dict get $props "platform:$platform" "BIF"]
6448  if {[IsRelativePath $bif_file] == 1} {
6449  set bif_file "$repo_path/$bif_file"
6450  }
6451  return $bif_file
6452  } else {
6453  Msg CriticalWarning "BIF file not found in platform ($platform) properties, skipping bootable image (.bin) generation"
6454  return ""
6455  }
6456 }
6457 
6458 # @brief Returns the part number from the properties
6459 #
6460 # @param[in] props A dictionary with the properties defined in Hog.conf
6461 # @return The part number
6462 proc GetPartFromProps {props} {
6463  if {[dict exists $props "main" "PART"]} {
6464  return [string tolower [dict get $props "main" "PART"]]
6465  } else {
6466  Msg Error "Part number not found in properties"
6467  return ""
6468  }
6469 }
6470 
6471 # @brief Determines the architecture from the part number
6472 #
6473 # @param[in] part The FPGA part number (e.g., xczu4cg-fbvb900-1-e)
6474 # @return String with the architecture (zynqmp, zynq, versal, or unknown)
6475 proc GetArchFromPart {part} {
6476  # Determine architecture based on part prefix
6477  if {[string match "xczu*" $part]} {
6478  return "zynqmp"
6479  } elseif {[string match "xc7z*" $part]} {
6480  return "zynq"
6481  } elseif {[string match "xck26*" $part]} {
6482  return "versal"
6483  } else {
6484  Msg CriticalWarning "Unknown part number: $part"
6485  return "unknown"
6486  }
6487 }
6488 
6489 # @brief Returns a list of application names from the properties
6490 #
6491 # @param[in] props A dictionary with the applications properties defined in Hog.conf
6492 # @param[in] list_names If 1, returns a list of application names rather than a dictionary of applications
6493 proc GetAppsFromProps {props {list_names 0}} {
6494  set prop_apps [dict filter $props key {app:*}]
6495  set apps [dict create]
6496  set app_names [list]
6497 
6498  dict for {app_key app_value} $prop_apps {
6499  if {[regexp {^app:(.+)$} $app_key -> app_name]} {
6500  set app_name [string trim [string tolower $app_name]]
6501  # Convert only the keys of the inner dictionary to lowercase
6502  set app_value_lower [dict create]
6503  dict for {key value} $app_value {
6504  dict set app_value_lower [string tolower $key] $value
6505  }
6506  dict set apps $app_name $app_value_lower
6507  lappend app_names $app_name
6508  }
6509  }
6510  if {$list_names eq 1} {
6511  return $app_names
6512  }
6513  return $apps
6514 }
6515 
6516 # @brief Returns a list of platform names from the properties
6517 #
6518 # @param[in] props A dictionary with the platforms properties
6519 # @param[in] list_names If 1, returns a list of platform names rather than a dictionary of platforms
6520 # @param[in] lower_case If 1, returns the platform names in lowercase
6521 proc GetPlatformsFromProps {props {list_names 0} {lower_case 0}} {
6522  set platforms [dict create]
6523  set platform_names [list]
6524  set prop_platforms [dict filter $props key {platform:*}]
6525 
6526  dict for {platform_key platform_value} $prop_platforms {
6527  if {[regexp {^platform:(.+)$} $platform_key -> platform_name]} {
6528  if {$lower_case == 1} {
6529  set platform_name [string trim [string tolower $platform_name]]
6530  } else {
6531  set platform_name [string trim $platform_name]
6532  }
6533  dict set platforms $platform_name $platform_value
6534  lappend platform_names $platform_name
6535  }
6536  }
6537  if {$list_names eq 1} {
6538  return $platform_names
6539  }
6540  return $platforms
6541 }
6542 
6543 # @brief Generates boot artifacts for the application. If the application targets a soft \
6544 # processor (e.g. microblaze, riscv), the bitstream (.bit) memory is updated to include the ELF file. Otherwise, for \
6545 # applications targeting a hard processor (e.g. zynq, versal), a bootable binary image (.bin) is generated.
6546 #
6547 # @param[in] properties A dictionary with the properties defined in Hog.conf
6548 # @param[in] repo_path The main path of the git repository
6549 # @param[in] proj_dir The directory of the project
6550 # @param[in] bin_dir The directory of the generated binary files
6551 # @param[in] bitfile The bitfile to update
6552 # @param[in] mmi_file The MMI file to update
6553 proc GenerateBootArtifacts {properties repo_path proj_dir bin_dir proj_name describe bitfile mmi_file} {
6554  set elf_list [glob -nocomplain "$bin_dir/*.elf"]
6555  set apps [GetAppsFromProps $properties 0]
6556  set platforms [GetPlatformsFromProps $properties 1]
6557 
6558  if {[llength $elf_list] == 0} {
6559  Msg Warning "No ELF files found in $bin_dir, skipping generation of boot artifacts"
6560  return
6561  }
6562 
6563  if {![file exists $bitfile]} {
6564  Msg Warning "Bitfile $bitfile does not exist, skipping generation of boot artifacts"
6565  return
6566  }
6567 
6568  Msg Info "Generating boot artifacts for $proj_name..."
6569  Msg Info "Found apps: $apps"
6570  Msg Info "Found platforms: $platforms"
6571 
6572 
6573  # Update bitstream with ELF files for the applications targeting a soft processor (e.g. microblaze, riscv)
6574  foreach elf_file $elf_list {
6575  set elf_name [file rootname [file tail $elf_file]]
6576  Msg Info "Found elf name: $elf_name"
6577  Msg Info "Removing $describe from elf"
6578 
6579  # Extract application name from ELF file name
6580  if {[regexp "^(.+)-(.+)-$describe\$" $elf_name -> project_name elf_app]} {
6581  set elf_app [string trim [string tolower $elf_app]]
6582  Msg Info "Found elf_app: $elf_app"
6583  } else {
6584  Msg Error "Could not extract app name from elf file: $elf_name"
6585  continue
6586  }
6587  Msg Info "Removed project name ($project_name) and $describe from elf"
6588 
6589  set app_conf [dict get $apps $elf_app]
6590  set plat [dict get $app_conf "platform"]
6591  set app_proc [dict get $app_conf "proc"]
6592 
6593  # If the application targets a soft processor, update bitstream memory with ELF file
6594  if {[regexp -nocase {microblaze|risc} $app_proc]} {
6595  Msg Info "Detected soft processor ($app_proc) for $elf_app, updating bitstream memory with ELF file..."
6596 
6597  set proc_map [ReadProcMap $proc_map_file]
6598  if {[dict size $proc_map] == 0} {
6599  Msg Error "Failed to read map from $proc_map_file"
6600  continue
6601  }
6602  Msg Info "Found processor map: $proc_map"
6603 
6604  set proc_cell [lindex [split [dict get $proc_map $app_proc] ":"] 1]
6605  Msg Info "Updating memory at processor cell: $proc_cell"
6606 
6607  set update_mem_cmd "updatemem -force -meminfo $mmi_file -data $elf_file -bit $bitfile -proc $proc_cell -out $bitfile"
6608  set ret [catch {exec -ignorestderr {*}$update_mem_cmd >@ stdout} result]
6609  if {$ret != 0} {
6610  Msg Error "Error updating memory for $elf_app: $result"
6611  }
6612  Msg Info "Done updating memory for $elf_app"
6613 
6614  } else {
6615  Msg Info "Detected hard processor ($app_proc) for $elf_app. Make sure the .elf file is defined in the platform ($plat)\
6616  .bif file to be included in the bootable binary image (.bin) generation."
6617  }
6618  }
6619 
6620 
6621  # Generate a bootable binary image for platforms that have a .bif file defined
6622  foreach plat $platforms {
6623  set bif_file [GetBifFromProps $repo_path $properties $plat]
6624  if {$bif_file != ""} {
6625  Msg Info "Generating bootable binary image (.bin) for $plat"
6626  set arch [GetArchFromPart [GetPartFromProps $properties]]
6627  Msg Info "Architecture: $arch"
6628  Msg Info "BIF file: $bif_file"
6629  set bootgen_cmd "bootgen -arch $arch -image $bif_file -o i $bin_dir/$proj_name-$plat-$describe.bin -w on"
6630  set ret [catch {exec -ignorestderr {*}$bootgen_cmd >@ stdout} result]
6631  if {$ret != 0} {
6632  Msg Error "Error generating bootable binary image (.bin) for $elf_app: $result"
6633  }
6634  Msg Info "Done generating bootable binary image (.bin) for $plat"
6635  }
6636  }
6637 }
6638 
6639 # @brief Reads the processor map file
6640 #
6641 # @param[in] proc_map_file The path to the processor map file
6642 # @return A dictionary with the processor map
6643 proc ReadProcMap {proc_map_file} {
6644  set proc_map [dict create]
6645  if {[file exists $proc_map_file]} {
6646  set f [open $proc_map_file "r"]
6647  while {[gets $f line] >= 0} {
6648  Msg Debug "Line: $line"
6649  if {[regexp {^(\S+)\s+(.+)$} $line -> key value]} {
6650  Msg Debug "Found key: $key, value: $value in proc map file"
6651  dict set proc_map $key $value
6652  }
6653  }
6654  close $f
6655  }
6656  return $proc_map
6657 }
6658 
6659 
6660 # Returns the list of all the Hog Projects in the repository
6661 #
6662 # @param[in] repo_path The main path of the git repository
6663 # @param[in] print if 1 print the list of projects in the repository, if 2 does not print test projects
6664 # @param[in] ret_conf if 1 returns conf file rather than list of project names
6665 proc ListProjects {{repo_path .} {print 1} {ret_conf 0}} {
6666  set top_path [file normalize $repo_path/Top]
6667  set confs [findFiles [file normalize $top_path] hog.conf]
6668  set projects ""
6669  set confs [lsort $confs]
6670  set g ""
6671 
6672  foreach c $confs {
6673  set p [Relative $top_path [file dirname $c]]
6674  if {$print >= 1} {
6675  set description [DescriptionFromConf $c]
6676  if {$description eq "test"} {
6677  set description " - Test project"
6678  } elseif {$description ne ""} {
6679  set description " - $description"
6680  }
6681 
6682  if {$print == 1 || $description ne " - Test project"} {
6683  set old_g $g
6684  set g [file dirname $p]
6685  # Print a list of the projects with relative IDE and description (second line comment in hog.conf)
6686  if {$g ne $old_g} {
6687  Msg Status ""
6688  }
6689  Msg Status "$p \([GetIDEFromConf $c]\)$description"
6690  }
6691  }
6692  lappend projects $p
6693  }
6694 
6695  if {$ret_conf == 0} {
6696  # Returns a list of project names
6697  return $projects
6698  } else {
6699  # Return the list of hog.conf with full path
6700  return $confs
6701  }
6702 }
6703 
6704 ## @brief Evaluates the md5 sum of a file
6705 #
6706 # @param[in] file_name: the name of the file of which you want to evaluate the md5 checksum
6707 proc Md5Sum {file_name} {
6708  if {!([file exists $file_name])} {
6709  Msg Warning "Could not find $file_name."
6710  set file_hash -1
6711  }
6712  if {[catch {package require md5 2.0.7} result]} {
6713  Msg Warning "Tcl package md5 version 2.0.7 not found ($result), will use command line..."
6714  set hash [lindex [Execute md5sum $file_name] 0]
6715  } else {
6716  set file_hash [string tolower [md5::md5 -hex -file $file_name]]
6717  }
6718 }
6719 
6720 ## @brief Merges two tcl dictionaries of lists
6721 #
6722 # If the dictionaries contain same keys, the list at the common key is a merging of the two
6723 #
6724 # @param[in] dict0 the name of the first dictionary
6725 # @param[in] dict1 the name of the second dictionary
6726 # @param[in] remove_duplicates if 1, removes duplicates from the merged dictionary (default 1)
6727 #
6728 # @return the merged dictionary
6729 #
6730 proc MergeDict {dict0 dict1 {remove_duplicates 1}} {
6731  set outdict [dict merge $dict1 $dict0]
6732  foreach key [dict keys $dict1] {
6733  if {[dict exists $dict0 $key]} {
6734  set temp_list [dict get $dict1 $key]
6735  foreach item $temp_list {
6736  # Avoid duplication
6737  if {[IsInList $item [DictGet $outdict $key]] == 0 || $remove_duplicates == 0} {
6738  # If the key exists in both dictionaries, append the item to the list
6739  dict lappend outdict $key $item
6740  }
6741  }
6742  }
6743  }
6744  return $outdict
6745 }
6746 
6747 # @brief Move an element in the list to the end
6748 #
6749 # @param[in] inputList the list
6750 # @param[in] element the element to move to the end of the list
6751 proc MoveElementToEnd {inputList element} {
6752  set index [lsearch $inputList $element]
6753  if {$index != -1} {
6754  set inputList [lreplace $inputList $index $index]
6755  lappend inputList $element
6756  }
6757  return $inputList
6758 }
6759 
6760 # @brief Open the project with the corresponding IDE
6761 #
6762 # @param[in] project_file The project_file
6763 # @param[in] repo_path The main path of the git repository
6764 proc OpenProject {project_file repo_path} {
6765  if {[IsXilinx]} {
6766  open_project $project_file
6767  } elseif {[IsQuartus]} {
6768  set project_folder [file dirname $project_file]
6769  set project [file tail [file rootname $project_file]]
6770  if {[file exists $project_folder]} {
6771  cd $project_folder
6772  if {![is_project_open]} {
6773  Msg Info "Opening existing project file $project_file..."
6774  project_open $project -current_revision
6775  }
6776  } else {
6777  Msg Error "Project directory not found for $project_file."
6778  return 1
6779  }
6780  } elseif {[IsLibero]} {
6781  Msg Info "Opening existing project file $project_file..."
6782  cd $repo_path
6783  open_project -file $project_file -do_backup_on_convert 1 -backup_file {./Projects/$project_file.zip}
6784  } elseif {[IsDiamond]} {
6785  Msg Info "Opening existing project file $project_file..."
6786  prj_project open $project_file
6787  } else {
6788  Msg Error "This IDE is currently not supported by Hog. Exiting!"
6789  }
6790 }
6791 
6792 ## @brief Return the operative system name
6793 proc OS {} {
6794  global tcl_platform
6795  return $tcl_platform(platform)
6796 }
6797 
6798 ## @brief Parse JSON file
6799 #
6800 # @param[in] JSON_FILE The JSON File to parse
6801 # @param[in] JSON_KEY The key to extract from the JSON file
6802 #
6803 # @returns -1 in case of failure, JSON KEY VALUE in case of success
6804 #
6805 proc ParseJSON {JSON_FILE JSON_KEY} {
6806  set result [catch {package require Tcl 8.4} TclFound]
6807  if {"$result" != "0"} {
6808  Msg CriticalWarning "Cannot find Tcl package version equal or higher than 8.4.\n $TclFound\n Exiting"
6809  return -1
6810  }
6811 
6812  set result [catch {package require json} JsonFound]
6813  if {"$result" != "0"} {
6814  Msg CriticalWarning "Cannot find JSON package equal or higher than 1.0.\n $JsonFound\n Exiting"
6815  return -1
6816  }
6817  set JsonDict [json::json2dict $JSON_FILE]
6818  set result [catch {dict get $JsonDict $JSON_KEY} RETURNVALUE]
6819  if {"$result" != "0"} {
6820  Msg CriticalWarning "Cannot find $JSON_KEY in $JSON_FILE\n Exiting"
6821  return -1
6822  } else {
6823  return $RETURNVALUE
6824  }
6825 }
6826 
6827 
6828 # @brief Check if a Hog project exists, and if it exists returns the conf file
6829 # if it doesnt returns 0
6830 #
6831 # @brief project The project name
6832 # @brief repo_path The main path of the git repository
6833 proc ProjectExists {project {repo_path .}} {
6834  set index [lsearch -exact [ListProjects $repo_path 0] $project]
6835 
6836  if {$index >= 0} {
6837  # if project exists we return the relative hog.conf file
6838  return [lindex [ListProjects $repo_path 0 1] $index]
6839  } else {
6840  Msg Error "Project $project not found in repository $repo_path"
6841  return 1
6842  }
6843 }
6844 
6845 ## Read a property configuration file and returns a dictionary
6846 #
6847 # @param[in] file_name the configuration file
6848 #
6849 # @return The dictionary
6850 #
6851 proc ReadConf {file_name} {
6852  if {[catch {package require inifile 0.2.3} ERROR]} {
6853  Msg Error "Could not find inifile package version 0.2.3 or higher.\n
6854  To use ghdl, libero or diamond with Hog, you need to install the tcllib package\n
6855  You can install it with 'sudo apt install tcllib' on Debian/Ubuntu or 'sudo dnf install tcllib' on Fedora/RedHat/CentOs."
6856  }
6857 
6858 
6859  ::ini::commentchar "#"
6860  set f [::ini::open $file_name]
6861  set properties [dict create]
6862  foreach sec [::ini::sections $f] {
6863  set new_sec $sec
6864  if {$new_sec == "files"} {
6865  continue
6866  }
6867  set key_pairs [::ini::get $f $sec]
6868  #manipulate strings here:
6869  regsub -all {\{\"} $key_pairs "\{" key_pairs
6870  regsub -all {\"\}} $key_pairs "\}" key_pairs
6871 
6872  dict set properties $new_sec [dict create {*}$key_pairs]
6873  }
6874 
6875  ::ini::close $f
6876 
6877  return $properties
6878 }
6879 
6880 ## @brief Function used to read the list of files generated at creation time by tcl scripts in Project/proj/.hog/extra.files
6881 #
6882 # @param[in] extra_file_name the path to the extra.files file
6883 # @returns a dictionary with the full name of the files as key and a SHA as value
6884 #
6885 proc ReadExtraFileList {extra_file_name} {
6886  set extra_file_dict [dict create]
6887  if {[file exists $extra_file_name]} {
6888  set file [open $extra_file_name "r"]
6889  set file_data [read $file]
6890  close $file
6891 
6892  set data [split $file_data "\n"]
6893  foreach line $data {
6894  if {![regexp {^ *$} $line] & ![regexp {^ *\#} $line]} {
6895  set ip_and_md5 [regexp -all -inline {\S+} $line]
6896  dict lappend extra_file_dict "[lindex $ip_and_md5 0]" "[lindex $ip_and_md5 1]"
6897  }
6898  }
6899  }
6900  return $extra_file_dict
6901 }
6902 
6903 
6904 # @brief Expand a Vitis HLS configuration file (hls_config.cfg) into the list of
6905 # repository files it references, so Hog can hash them for dirty/SHA
6906 # detection without forcing the user to duplicate the list in a .src
6907 #
6908 # @param[in] cfg_file Absolute or relative path to the hls_config.cfg file.
6909 # @return A de-duplicated list of normalized absolute file paths.
6910 #
6911 proc ExpandHlsConfigFiles {cfg_file} {
6912  set out [list]
6913  if {![file exists $cfg_file] || ![file isfile $cfg_file]} {
6914  return $out
6915  }
6916  set cfg_dir [file normalize [file dirname $cfg_file]]
6917 
6918  if {[catch {set fp [open $cfg_file r]} err]} {
6919  Msg Warning "ExpandHlsConfigFiles: cannot open $cfg_file: $err"
6920  return $out
6921  }
6922  set lines [split [read $fp] "\n"]
6923  close $fp
6924 
6925  foreach line $lines {
6926  if {[regexp {^[\t\s]*$} $line]} { continue }
6927  if {[regexp {^[\t\s]*\#} $line]} { continue }
6928  if {[regexp {^\s*\[.*\]\s*$} $line]} { continue }
6929  if {![regexp {^\s*[^=\s]+\s*=\s*(.+?)\s*$} $line -> value]} { continue }
6930 
6931  foreach tok [regexp -all -inline {\S+} $value] {
6932  if {[file pathtype $tok] eq "absolute"} {
6933  set candidate [file normalize $tok]
6934  } else {
6935  set candidate [file normalize [file join $cfg_dir $tok]]
6936  }
6937  if {[file isfile $candidate]} {
6938  lappend out $candidate
6939  } elseif {[file isdirectory $candidate]} {
6940  set stack [list $candidate]
6941  while {[llength $stack] > 0} {
6942  set d [lindex $stack 0]
6943  set stack [lrange $stack 1 end]
6944  foreach f [glob -nocomplain -directory $d -types f *] {
6945  lappend out [file normalize $f]
6946  }
6947  foreach sub [glob -nocomplain -directory $d -types d *] {
6948  lappend stack $sub
6949  }
6950  }
6951  }
6952  }
6953  }
6954  return [lsort -unique $out]
6955 }
6956 
6957 
6958 # @brief Discover HLS components declared in a project's hog.conf and return
6959 # the absolute path of each component's hls_config.cfg
6960 #
6961 # @param[in] conf_file Absolute path of the project's hog.conf
6962 # @param[in] repo_path Repository root (paths in HLS_CONFIG are relative to it)
6963 # @return A dict { component_name -> absolute_cfg_path }. Empty if no HLS
6964 proc GetHlsConfigsFromProjConf {conf_file repo_path} {
6965  set out [dict create]
6966  if {![file exists $conf_file]} { return $out }
6967  if {[catch {set properties [ReadConf $conf_file]} err]} {
6968  Msg Debug "GetHlsConfigsFromProjConf: cannot parse $conf_file: $err"
6969  return $out
6970  }
6971  set hls_components [dict filter $properties key {hls:*}]
6972  dict for {hls_key hls_props} $hls_components {
6973  if {![regexp {^hls:(.+)$} $hls_key -> component_name]} { continue }
6974  set component_name [string trim $component_name]
6975  set cfg_rel ""
6976  if {[dict exists $hls_props hls_config]} {
6977  set cfg_rel [dict get $hls_props hls_config]
6978  } elseif {[dict exists $hls_props HLS_CONFIG]} {
6979  set cfg_rel [dict get $hls_props HLS_CONFIG]
6980  }
6981  if {$cfg_rel eq ""} { continue }
6982  set cfg_abs [file normalize "$repo_path/$cfg_rel"]
6983  if {![file exists $cfg_abs]} {
6984  Msg CriticalWarning "HLS component '$component_name': HLS_CONFIG=$cfg_rel does not exist on disk."
6985  continue
6986  }
6987  dict set out $component_name $cfg_abs
6988  }
6989  return $out
6990 }
6991 
6992 
6993 # @brief Read a list file and return a list of three dictionaries
6994 #
6995 # Additional information is provided with text separated from the file name with one or more spaces
6996 #
6997 # @param[in] args The arguments are <list_file> <path> [options]
6998 # * list_file file containing vhdl list with optional properties
6999 # * path path the vhdl file are referred to in the list file
7000 # Options:
7001 # * -lib <library> name of the library files will be added to, if not given will be extracted from the file name
7002 # * -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.
7003 # 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.
7004 #
7005 # @return a list of 3 dictionaries:
7006 # "libraries" has library name as keys and a list of filenames as values,
7007 # "properties" has as file names as keys and a list of properties as values
7008 # "filesets" has the fileset' names as keys and the list of associated libraries as values.
7009 proc ReadListFile {args} {
7010  if {[IsQuartus]} {
7011  load_package report
7012  if {[catch {package require cmdline} ERROR]} {
7013  puts "$ERROR\n If you are running this script on tclsh, you can fix this by installing 'tcllib'"
7014  return 1
7015  }
7016  }
7017  # tclint-disable line-length
7018  set parameters {
7019  {lib.arg "" "The name of the library files will be added to, if not given will be extracted from the file name."}
7020  {fileset.arg "" "The name of the library, from the main list file"}
7021  {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."}
7022  {print_log "If set, will use PrintFileTree for the VIEW directive"}
7023  {indent.arg "" "Used to indent files with the VIEW directive"}
7024  }
7025  # tclint-enable line-length
7026  set usage "USAGE: ReadListFile \[options\] <list file> <path>"
7027  if {[catch {array set options [cmdline::getoptions args $parameters $usage]}] || [llength $args] != 2} {
7028  Msg CriticalWarning "[cmdline::usage $parameters $usage]"
7029  return
7030  }
7031 
7032 
7033  set list_file [lindex $args 0]
7034  set path [lindex $args 1]
7035  set sha_mode $options(sha_mode)
7036  set lib $options(lib)
7037  set fileset $options(fileset)
7038  set print_log $options(print_log)
7039  set indent $options(indent)
7040 
7041  if {$sha_mode == 1} {
7042  set sha_mode_opt "-sha_mode"
7043  } else {
7044  set sha_mode_opt ""
7045  }
7046 
7047  if {$print_log == 1} {
7048  set print_log_opt "-print_log"
7049  } else {
7050  set print_log_opt ""
7051  }
7052 
7053  # if no library is given, work it out from the file name
7054  if {$lib eq ""} {
7055  set lib [file rootname [file tail $list_file]]
7056  }
7057  set fp [open $list_file r]
7058  set file_data [read $fp]
7059  close $fp
7060  set list_file_ext [file extension $list_file]
7061  switch $list_file_ext {
7062  .sim {
7063  if {$fileset eq ""} {
7064  # If fileset is empty, use the library name for .sim file
7065  set fileset "$lib"
7066  }
7067  }
7068  .con {
7069  set fileset "constrs_1"
7070  }
7071  default {
7072  set fileset "sources_1"
7073  }
7074  }
7075 
7076  set libraries [dict create]
7077  set filesets [dict create]
7078  set properties [dict create]
7079  # Process data file
7080  set data [split $file_data "\n"]
7081  set data [ExtractFilesSection $data]
7082  set n [llength $data]
7083  set last_printed ""
7084  if {$print_log == 1} {
7085  if {$indent eq ""} {
7086  set list_file_rel [file tail $list_file]
7087  Msg Status "\n$list_file_rel"
7088  }
7089  set last_printed [PrintFileTree $data $path "$indent"]
7090  }
7091  Msg Debug "$n lines read from $list_file."
7092  set cnt 0
7093 
7094  foreach line $data {
7095  # Exclude empty lines and comments
7096  if {![regexp {^[\t\s]*$} $line] & ![regexp {^[\t\s]*\#} $line]} {
7097  set file_and_prop [regexp -all -inline {\S+} $line]
7098  set srcfile [lindex $file_and_prop 0]
7099  set srcfile "$path/$srcfile"
7100 
7101  set srcfiles [glob -nocomplain $srcfile]
7102 
7103  # glob the file list for wildcards
7104  if {$srcfiles != $srcfile && ![string equal $srcfiles ""]} {
7105  Msg Debug "Wildcard source expanded from $srcfile to $srcfiles"
7106  } else {
7107  if {![file exists $srcfile]} {
7108  if {$print_log == 0} {
7109  Msg CriticalWarning "File: $srcfile (from list file: $list_file) does not exist."
7110  }
7111  continue
7112  }
7113  }
7114 
7115  foreach vhdlfile $srcfiles {
7116  if {[file exists $vhdlfile]} {
7117  set vhdlfile [file normalize $vhdlfile]
7118  set extension [file extension $vhdlfile]
7119  ### Set file properties
7120  set prop [lrange $file_and_prop 1 end]
7121 
7122  # The next lines should be inside the case for recursive list files, also we should check the allowed properties for the .src as well
7123  set library [lindex [regexp -inline {\ylib\s*=\s*(.+?)\y.*} $prop] 1]
7124  if {$library == ""} {
7125  set library $lib
7126  }
7127 
7128  if {$extension == $list_file_ext} {
7129  # Deal with recursive list files
7130  # In the next regex we use \S+ instead of .+? because we want to include forward slashes
7131  set ref_path [lindex [regexp -inline {\ypath\s*=\s*(\S+).*} $prop] 1]
7132  if {$ref_path eq ""} {
7133  set ref_path $path
7134  } else {
7135  set ref_path [file normalize $path/$ref_path]
7136  }
7137  Msg Debug "List file $vhdlfile found in list file, recursively opening it using path \"$ref_path\"..."
7138  if {$print_log == 1} {
7139  if {[file normalize $last_printed] ne [file normalize $vhdlfile]} {
7140  Msg Status "$indent Inside [file tail $vhdlfile]:"
7141  set last_printed ""
7142  }
7143  }
7144  lassign [ReadListFile {*}"-indent \" $indent\" -lib $library -fileset $fileset $sha_mode_opt $print_log_opt $vhdlfile $ref_path"] l p fs
7145  set libraries [MergeDict $l $libraries]
7146  set properties [MergeDict $p $properties]
7147  set filesets [MergeDict $fs $filesets]
7148  } elseif {[lsearch {.src .sim .con ReadExtraFileList} $extension] >= 0} {
7149  # Not supported extensions
7150  Msg Error "$vhdlfile cannot be included into $list_file, $extension files must be included into $extension files."
7151  } else {
7152  # Deal with single files
7153  regsub -all " *= *" $prop "=" prop
7154  # Fill property dictionary
7155  foreach p $prop {
7156  # No need to append the lib= property
7157  if {[string first "lib=" $p] == -1} {
7158  # Get property name up to the = (for QSYS properties at the moment)
7159  set pos [string first "=" $p]
7160  if {$pos == -1} {
7161  set prop_name $p
7162  } else {
7163  set prop_name [string range $p 0 [expr {$pos - 1}]]
7164  }
7165  if {[IsInList $prop_name [DictGet [ALLOWED_PROPS] $extension]] || [string first "top" $p] == 0 || $list_file_ext eq ".ipb"} {
7166  dict lappend properties $vhdlfile $p
7167  Msg Debug "Adding property $p to $vhdlfile..."
7168  } elseif {$list_file_ext != ".ipb"} {
7169  Msg Warning "Setting Property $p is not supported for file $vhdlfile or it is already its default. \
7170  The allowed properties for this file type are \[ [DictGet [ALLOWED_PROPS] $extension]\]"
7171  }
7172  }
7173  }
7174  if {[lsearch {.xci .ip .bd .xcix} $extension] >= 0} {
7175  # Adding IP library
7176  set lib_name "ips.src"
7177  } elseif {[IsInList $extension {.vhd .vhdl}] || $list_file_ext == ".sim"} {
7178  # VHDL files and simulation
7179  if {![IsInList $extension {.vhd .vhdl}]} {
7180  set lib_name "others.sim"
7181  } else {
7182  set lib_name "$library$list_file_ext"
7183  }
7184  } elseif {$list_file_ext == ".con"} {
7185  set lib_name "sources.con"
7186  } elseif {$list_file_ext == ".ipb"} {
7187  set lib_name "xml.ipb"
7188  } elseif { [IsInList $list_file_ext {.src}] && [IsInList $extension {.c .cpp .h .hpp}] } {
7189  # Adding Vitis library
7190  set lib_name "$library$list_file_ext"
7191  } else {
7192  # Other files are stored in the OTHER dictionary from vivado (no library assignment)
7193  set lib_name "others.src"
7194  }
7195 
7196  Msg Debug "Appending $vhdlfile to $lib_name list..."
7197  dict lappend libraries $lib_name $vhdlfile
7198  if {$sha_mode != 0 && [file type $vhdlfile] eq "link"} {
7199  #if the file is a link, also add the linked file in sha mode
7200  set real_file [GetLinkedFile $vhdlfile]
7201  dict lappend libraries $lib_name $real_file
7202  Msg Debug "File $vhdlfile is a soft link, also adding the real file: $real_file"
7203  }
7204 
7205  # Auto-expand HLS config files: when a .cfg is referenced from a .src
7206  # we treat it as a Vitis HLS hls_config.cfg and add every existing
7207  # file/dir it references (syn.file, tb.file, -I include dirs, ...)
7208  # to the same library. This avoids forcing the user to duplicate the
7209  # HLS file list in both hls_config.cfg and the .src.
7210  if {$list_file_ext eq ".src" && $extension eq ".cfg"} {
7211  set hls_extras [ExpandHlsConfigFiles $vhdlfile]
7212  if {$print_log == 1 && [llength $hls_extras] > 0} {
7213  Msg Status "$indent Inside [file tail $vhdlfile] (HLS auto-expand):"
7214  }
7215  set n_extras [llength $hls_extras]
7216  set i 0
7217  foreach hls_extra $hls_extras {
7218  incr i
7219  if {$hls_extra eq $vhdlfile} { continue }
7220  Msg Debug "HLS cfg expansion: adding $hls_extra (from $vhdlfile) to $lib_name"
7221  if {$print_log == 1} {
7222  if {$i == $n_extras} { set pad "└──" } else { set pad "├──" }
7223  set rel [Relative [file dirname $vhdlfile] $hls_extra]
7224  Msg Status "$indent $pad $rel"
7225  }
7226  dict lappend libraries $lib_name $hls_extra
7227  if {$sha_mode != 0 && [file type $hls_extra] eq "link"} {
7228  set real_extra [GetLinkedFile $hls_extra]
7229  dict lappend libraries $lib_name $real_extra
7230  Msg Debug "HLS expanded file $hls_extra is a soft link, also adding $real_extra"
7231  }
7232  }
7233  }
7234 
7235 
7236  # Create the fileset (if not already) and append the library
7237  if {[dict exists $filesets $fileset] == 0} {
7238  # Fileset has not been defined yet, adding to dictionary...
7239  Msg Debug "Adding $fileset to the fileset dictionary..."
7240  Msg Debug "Adding library $lib_name to fileset $fileset..."
7241  dict set filesets $fileset $lib_name
7242  } else {
7243  # Fileset already exist in dictionary, append library to list, if not already there
7244  if {[IsInList $lib_name [DictGet $filesets $fileset]] == 0} {
7245  Msg Debug "Adding library $lib_name to fileset $fileset..."
7246  dict lappend filesets $fileset $lib_name
7247  }
7248  }
7249  }
7250  incr cnt
7251  } else {
7252  Msg CriticalWarning "File $vhdlfile not found."
7253  }
7254  }
7255  }
7256  }
7257 
7258  if {$sha_mode != 0} {
7259  #In SHA mode we also need to add the list file to the list
7260  if {$list_file_ext eq ".ipb"} {
7261  set sha_lib "xml.ipb"
7262  } else {
7263  set sha_lib $lib$list_file_ext
7264  }
7265  dict lappend libraries $sha_lib [file normalize $list_file]
7266  if {[file type $list_file] eq "link"} {
7267  #if the file is a link, also add the linked file
7268  set real_file [GetLinkedFile $list_file]
7269  dict lappend libraries $lib$list_file_ext $real_file
7270  Msg Debug "List file $list_file is a soft link, also adding the real file: $real_file"
7271  }
7272  }
7273  return [list $libraries $properties $filesets]
7274 }
7275 
7276 ## @brief Returns the destination path relative to base
7277 #
7278 # @param[in] base the path with respect to witch the dst path is calculated
7279 # @param[in] dst the path to be calculated with respect to base
7280 # @param[in] quiet if 1, does not print warnings when paths are of different types
7281 #
7282 proc Relative {base dst {quiet 0}} {
7283  if {![string equal [file pathtype $base] [file pathtype $dst]]} {
7284  if {$quiet == 0} {
7285  Msg CriticalWarning "Unable to compute relation for paths of different path types: [file pathtype $base] vs. [file pathtype $dst], ($base vs. $dst)"
7286  }
7287  return ""
7288  }
7289 
7290  set base [file normalize [file join [pwd] $base]]
7291  set dst [file normalize [file join [pwd] $dst]]
7292 
7293  set save $dst
7294  set base [file split $base]
7295  set dst [file split $dst]
7296 
7297  while {[string equal [lindex $dst 0] [lindex $base 0]]} {
7298  set dst [lrange $dst 1 end]
7299  set base [lrange $base 1 end]
7300  if {![llength $dst]} {break}
7301  }
7302 
7303  set dstlen [llength $dst]
7304  set baselen [llength $base]
7305 
7306  if {($dstlen == 0) && ($baselen == 0)} {
7307  set dst .
7308  } else {
7309  while {$baselen > 0} {
7310  set dst [linsert $dst 0 ..]
7311  incr baselen -1
7312  }
7313  set dst [eval [linsert $dst 0 file join]]
7314  }
7315 
7316  return $dst
7317 }
7318 
7319 ## @brief Returns the path of filePath relative to pathName
7320 #
7321 # @param[in] pathName the path with respect to which the returned path is calculated
7322 # @param[in] filePath the path of filePath
7323 #
7324 proc RelativeLocal {pathName filePath} {
7325  if {[string first [file normalize $pathName] [file normalize $filePath]] != -1} {
7326  return [Relative $pathName $filePath]
7327  } else {
7328  return ""
7329  }
7330 }
7331 
7332 ## @brief Remove duplicates in a dictionary
7333 #
7334 # @param[in] mydict the input dictionary
7335 #
7336 # @return the dictionary stripped of duplicates
7337 proc RemoveDuplicates {mydict} {
7338  set new_dict [dict create]
7339  foreach key [dict keys $mydict] {
7340  set values [DictGet $mydict $key]
7341  foreach value $values {
7342  set idxs [lreverse [lreplace [lsearch -exact -all $values $value] 0 0]]
7343  foreach idx $idxs {
7344  set values [lreplace $values $idx $idx]
7345  }
7346  }
7347  dict set new_dict $key $values
7348  }
7349  return $new_dict
7350 }
7351 
7352 ## Reset files in the repository
7353 #
7354 # @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)
7355 #
7356 # @return Nothing
7357 #
7358 proc ResetRepoFiles {reset_file} {
7359  if {[file exists $reset_file]} {
7360  Msg Info "Found $reset_file, opening it..."
7361  set fp [open $reset_file r]
7362  set wild_cards [lsearch -all -inline -not -regexp [split [read $fp] "\n"] "^ *$"]
7363  close $fp
7364  Msg Info "Found the following files/wild cards to restore if modified: $wild_cards..."
7365  foreach w $wild_cards {
7366  set mod_files [GetModifiedFiles "." $w]
7367  if {[llength $mod_files] > 0} {
7368  Msg Info "Found modified $w files: $mod_files, will restore them..."
7369  RestoreModifiedFiles "." $w
7370  } else {
7371  Msg Info "No modified $w files found."
7372  }
7373  }
7374  }
7375 }
7376 
7377 ## @brief Restore with checkout -- the files specified in pattern
7378 #
7379 # @param[in] repo_path the path of the git repository
7380 # @param[in] pattern the pattern with wildcards that files should match
7381 #
7382 proc RestoreModifiedFiles {{repo_path "."} {pattern "."}} {
7383  set old_path [pwd]
7384  cd $repo_path
7385  set ret [Git checkout $pattern]
7386  cd $old_path
7387  return
7388 }
7389 
7390 ## Search the Hog projects inside a directory
7391 #
7392 # @param[in] dir The directory to search
7393 #
7394 # @return The list of projects
7395 #
7396 proc SearchHogProjects {dir} {
7397  set projects_list {}
7398  if {[file exists $dir]} {
7399  if {[file isdirectory $dir]} {
7400  foreach proj_dir [glob -nocomplain -types d $dir/*] {
7401  if {![regexp {^.*Top/+(.*)$} $proj_dir dummy proj_name]} {
7402  Msg Warning "Could not parse Top directory $dir"
7403  break
7404  }
7405  if {[file exists "$proj_dir/hog.conf"]} {
7406  lappend projects_list $proj_name
7407  } else {
7408  foreach p [SearchHogProjects $proj_dir] {
7409  lappend projects_list $p
7410  }
7411  }
7412  }
7413  } else {
7414  Msg Error "Input $dir is not a directory!"
7415  }
7416  } else {
7417  Msg Error "Directory $dir doesn't exist!"
7418  }
7419  return $projects_list
7420 }
7421 
7422 ## @brief Sets the generics in all the sim.conf simulation file sets
7423 #
7424 # @param[in] repo_path: the top folder of the projectThe path to the main git repository
7425 # @param[in] proj_dir: the top folder of the project
7426 # @param[in] target: software target(vivado, questa)
7427 #
7428 proc SetGenericsSimulation {repo_path proj_dir target} {
7429  set top_dir "$repo_path/Top/$proj_dir"
7430  set simsets [get_filesets]
7431  if {$simsets != ""} {
7432  foreach simset $simsets {
7433  # Only for simulation filesets
7434  if {[get_property FILESET_TYPE $simset] != "SimulationSrcs"} {
7435  continue
7436  }
7437 
7438  set merged_generics_dict [dict create]
7439  # Get generics from sim.conf file
7440  set simset_dict [DictGet [GetSimSets $proj_dir $repo_path $simset] $simset]
7441  set hog_generics [GetGenericsFromConf $proj_dir]
7442  set simset_generics [DictGet $simset_dict "generics"]
7443  set merged_generics_dict [MergeDict $merged_generics_dict $simset_generics 0]
7444  set generic_str [GenericToSimulatorString $merged_generics_dict $target]
7445 
7446  Msg Debug "TOP = [get_property top [get_filesets sources_1]]"
7447  Msg Debug "GENERICS = [get_property generic [get_filesets sources_1]]"
7448 
7449  set_property generic $generic_str [get_filesets $simset]
7450  Msg Info "Setting generics $generic_str for simulator $target\
7451  and simulation file-set $simset..."
7452  }
7453  }
7454 }
7455 
7456 ## @brief set the top module as top module in the chosen fileset
7457 #
7458 # It automatically recognises the IDE
7459 #
7460 # @param[out] top_module Name of the top module
7461 # @param[in] fileset The name of the fileset
7462 #
7463 proc SetTopProperty {top_module fileset} {
7464  Msg Info "Setting TOP property to $top_module module"
7465  if {[IsXilinx]} {
7466  #VIVADO_ONLY
7467  set_property "top" $top_module [get_filesets $fileset]
7468  } elseif {[IsQuartus]} {
7469  #QUARTUS ONLY
7470  set_global_assignment -name TOP_LEVEL_ENTITY $top_module
7471  } elseif {[IsLibero]} {
7472  set_root -module $top_module
7473  } elseif {[IsDiamond]} {
7474  prj_impl option top $top_module
7475  }
7476 }
7477 
7478 ## @brief Returns a list of Vivado properties that expect a PATH for value
7479 proc VIVADO_PATH_PROPERTIES {} {
7480  return {"\.*\.TCL\.PRE$" "^.*\.TCL\.POST$" "^RQS_FILES$" "^INCREMENTAL\_CHECKPOINT$" "NOC\_SOLUTION\_FILE"}
7481 }
7482 
7483 ## @brief Returns a list of Vitis properties that expect a PATH for value
7484 proc VITIS_PATH_PROPERTIES {} {
7485  return {"^HW$" "^XPFM$" "^LINKER-SCRIPT$" "^LIBRARIES$" "^LIBRARY-SEARCH-PATH$"}
7486 }
7487 
7488 ## @brief Write a property configuration file from a dictionary
7489 #
7490 # @param[in] file_name the configuration file
7491 # @param[in] config the configuration dictionary
7492 # @param[in] comment comment to add at the beginning of configuration file
7493 #
7494 #
7495 proc WriteConf {file_name config {comment ""}} {
7496  if {[catch {package require inifile 0.2.3} ERROR]} {
7497  puts "$ERROR\n If you are running this script on tclsh, you can fix this by installing 'tcllib'"
7498  return 1
7499  }
7500 
7501  ::ini::commentchar "#"
7502  set f [::ini::open $file_name w]
7503 
7504  foreach sec [dict keys $config] {
7505  set section [dict get $config $sec]
7506  dict for {p v} $section {
7507  if {[string trim $v] == ""} {
7508  Msg Warning "Property $p has empty value. Skipping..."
7509  continue
7510  }
7511  ::ini::set $f $sec $p $v
7512  }
7513  }
7514 
7515  #write comment before the first section (first line of file)
7516  if {![string equal "$comment" ""]} {
7517  ::ini::comment $f [lindex [::ini::sections $f] 0] "" $comment
7518  set hog_header "Generated by Hog on [clock format [clock seconds] -format "%Y-%m-%d %H:%M:%S"]"
7519  ::ini::comment $f [lindex [::ini::sections $f] 0] "" $hog_header
7520  }
7521  ::ini::commit $f
7522 
7523  ::ini::close $f
7524 }
7525 
7526 ## Set the generics property
7527 #
7528 # @param[in] mode if it's "create", the function will assume the project is being created
7529 # @param[in] repo_path The path to the main git repository
7530 # @param[in] design The name of the design
7531 
7532 # @param[in] list of variables to be written in the generics in the usual order
7533 
7534 proc WriteGenerics {mode repo_path design date timee\
7535  commit version top_hash top_ver hog_hash hog_ver \
7536  cons_ver cons_hash libs vers hashes ext_names ext_hashes \
7537  user_ip_repos user_ip_vers user_ip_hashes flavour {xml_ver ""} {xml_hash ""}} {
7538  Msg Info "Passing parameters/generics to project's top module..."
7539  ##### Passing Hog generic to top file
7540  # set global generic variables
7541  set generic_string [concat \
7542  "GLOBAL_DATE=[FormatGeneric $date]" \
7543  "GLOBAL_TIME=[FormatGeneric $timee]" \
7544  "GLOBAL_VER=[FormatGeneric $version]" \
7545  "GLOBAL_SHA=[FormatGeneric $commit]" \
7546  "TOP_SHA=[FormatGeneric $top_hash]" \
7547  "TOP_VER=[FormatGeneric $top_ver]" \
7548  "HOG_SHA=[FormatGeneric $hog_hash]" \
7549  "HOG_VER=[FormatGeneric $hog_ver]" \
7550  "CON_VER=[FormatGeneric $cons_ver]" \
7551  "CON_SHA=[FormatGeneric $cons_hash]"
7552  ]
7553  # xml hash
7554  if {$xml_hash != "" && $xml_ver != ""} {
7555  lappend generic_string \
7556  "XML_VER=[FormatGeneric $xml_ver]" \
7557  "XML_SHA=[FormatGeneric $xml_hash]"
7558  }
7559  #set project specific lists
7560  foreach l $libs v $vers h $hashes {
7561  set ver "[string toupper $l]_VER=[FormatGeneric $v]"
7562  set hash "[string toupper $l]_SHA=[FormatGeneric $h]"
7563  # Replaces all occurrences of dots (.) and hyphens (-) in the generic name
7564  # with underscores (_) to make it compatible with VHDL/Verilog syntax
7565  # Uses regsub with -all flag to replace all matches of the regex pattern [\.-]
7566  set ver [regsub -all {[\.-]} $ver {_}]
7567  set hash [regsub -all {[\.-]} $hash {_}]
7568  lappend generic_string "$ver" "$hash"
7569  }
7570 
7571  foreach e $ext_names h $ext_hashes {
7572  set hash "[string toupper $e]_SHA=[FormatGeneric $h]"
7573  lappend generic_string "$hash"
7574  }
7575 
7576  foreach repo $user_ip_repos v $user_ip_vers h $user_ip_hashes {
7577  set repo_name [file tail $repo]
7578  set ver "[string toupper $repo_name]_VER=[FormatGeneric $v]"
7579  set hash "[string toupper $repo_name]_SHA=[FormatGeneric $h]"
7580  set ver [regsub -all {[\.-]} $ver {_}]
7581  set hash [regsub -all {[\.-]} $hash {_}]
7582  lappend generic_string "$ver" "$hash"
7583  }
7584 
7585  if {$flavour != -1} {
7586  lappend generic_string "FLAVOUR=$flavour"
7587  }
7588 
7589  # Dealing with project generics in Vivado
7590  if {[IsVivado] || [IsVitisClassic] || [IsVitisUnified]} {
7591  set prj_generics [GenericToSimulatorString [GetGenericsFromConf $design] "Vivado"]
7592  set generic_string "$prj_generics $generic_string"
7593  }
7594 
7595  # Extract the generics from the top level source file
7596  if {[IsXilinx]} {
7597  # Top File can be retrieved only at creation time or in ISE
7598  if {$mode == "create" || [IsISE]} {
7599  set top_file [GetTopFile]
7600  set top_name [GetTopModule]
7601  if {[file exists $top_file]} {
7602  set generics [GetFileGenerics $top_file $top_name]
7603 
7604  Msg Debug "Found top level generics $generics in $top_file"
7605 
7606  set filtered_generic_string ""
7607 
7608  foreach generic_to_set [split [string trim $generic_string]] {
7609  set key [lindex [split $generic_to_set "="] 0]
7610  if {[dict exists $generics $key]} {
7611  Msg Debug "Hog generic $key found in $top_name"
7612  lappend filtered_generic_string "$generic_to_set"
7613  } else {
7614  Msg Warning "Generic $key is passed by Hog but is NOT present in $top_name."
7615  }
7616  }
7617 
7618  # only filter in ISE
7619  if {[IsISE]} {
7620  set generic_string $filtered_generic_string
7621  }
7622  }
7623  }
7624 
7625  set_property generic $generic_string [current_fileset]
7626  Msg Info "Setting parameters/generics..."
7627  Msg Debug "Detailed parameters/generics: $generic_string"
7628 
7629 
7630  if {[IsVivado]} {
7631  # Dealing with project generics in Simulators
7632  set simulator [get_property target_simulator [current_project]]
7633  if {$mode == "create"} {
7634  SetGenericsSimulation $repo_path $design $simulator
7635  }
7636 
7637  WriteGenericsToBdIPs $mode $repo_path $design $generic_string
7638  }
7639  } elseif {[IsSynplify]} {
7640  Msg Info "Setting Synplify parameters/generics one by one..."
7641  foreach generic $generic_string {
7642  Msg Debug "Setting Synplify generic: $generic"
7643  set_option -hdl_param -set "$generic"
7644  }
7645  } elseif {[IsDiamond]} {
7646  Msg Info "Setting Diamond parameters/generics one by one..."
7647  prj_impl option -impl Implementation0 HDL_PARAM "$generic_string"
7648  } elseif {[IsVitisClassic] || [IsVitisUnified]} {
7649  if {[catch {set ws_apps [app list -dict]}]} { set ws_apps "" }
7650 
7651  foreach app_name [dict keys $ws_apps] {
7652  set defined_symbols [app config -name $app_name -get define-compiler-symbols]
7653  foreach generic_to_set [split [string trim $generic_string]] {
7654  set key [lindex [split $generic_to_set "="] 0]
7655  set value [lindex [split $generic_to_set "="] 1]
7656  if {[string match "32'h*" $value]} {
7657  set value [string map {"32'h" "0x"} $value]
7658  }
7659 
7660  foreach symbol [split $defined_symbols ";"] {
7661  if {[string match "$key=*" $symbol]} {
7662  Msg Debug "Generic $key found in $app_name, removing it..."
7663  app config -name $app_name -remove define-compiler-symbols "$symbol"
7664  }
7665  }
7666 
7667  Msg Info "Setting Vitis parameters/generics for app $app_name: $key=$value"
7668  app config -name $app_name define-compiler-symbols "$key=$value"
7669  }
7670  }
7671  }
7672 }
7673 
7674 ## @brief Applies generic values to IPs within block designs
7675 #
7676 # @param[in] mode create: to write the generics at creation time. synth to write at synthesis time
7677 # @param[in] repo_path The main path of the git repository
7678 # @param[in] proj The project name
7679 # @param[in] generic_string the string containing the generics to be applied
7680 proc WriteGenericsToBdIPs {mode repo_path proj generic_string} {
7681  Msg Debug "Parameters/generics passed to WriteGenericsToIP: $generic_string"
7682 
7683  set bd_ip_generics false
7684  set properties [ReadConf [lindex [GetConfFiles $repo_path/Top/$proj] 0]]
7685  if {[dict exists $properties "hog"]} {
7686  set propDict [dict get $properties "hog"]
7687  if {[dict exists $propDict "PASS_GENERICS_TO_BD_IPS"]} {
7688  set bd_ip_generics [dict get $propDict "PASS_GENERICS_TO_BD_IPS"]
7689  }
7690  }
7691 
7692  if {[string compare [string tolower $bd_ip_generics] "false"] == 0} {
7693  return
7694  }
7695 
7696  if {$mode == "synth"} {
7697  Msg Info "Attempting to apply generics pre-synthesis..."
7698  set PARENT_PRJ [get_property "PARENT.PROJECT_PATH" [current_project]]
7699  set workaround [open "$repo_path/Projects/$proj/.hog/presynth_workaround.tcl" "w"]
7700  puts $workaround "source \[lindex \$argv 0\];"
7701  puts $workaround "open_project \[lindex \$argv 1\];"
7702  puts $workaround "WriteGenericsToBdIPs \[lindex \$argv 2\] \[lindex \$argv 3\] \[lindex \$argv 4\] \[lindex \$argv 5\];"
7703  puts $workaround "close_project"
7704  close $workaround
7705  if {
7706  [catch {
7707  exec vivado -mode batch -source $repo_path/Projects/$proj/.hog/presynth_workaround.tcl \
7708  -tclargs $repo_path/Hog/Tcl/hog.tcl $PARENT_PRJ \
7709  "childprocess" $repo_path $proj $generic_string
7710  } errMsg] != 0
7711  } {
7712  Msg Error "Encountered an error while attempting workaround: $errMsg"
7713  }
7714  file delete $repo_path/Projects/$proj/.hog/presynth_workaround.tcl
7715  ResetRepoFiles "$repo_path/Projects/hog_reset_files"
7716  Msg Info "Done applying generics pre-synthesis."
7717  return
7718  }
7719 
7720  Msg Info "Looking for IPs to add generics to..."
7721  set ips_generic_string ""
7722  foreach generic_to_set [split [string trim $generic_string]] {
7723  set key [lindex [split $generic_to_set "="] 0]
7724  set value [lindex [split $generic_to_set "="] 1]
7725  append ips_generic_string "CONFIG.$key $value "
7726  }
7727 
7728 
7729  if {[string compare [string tolower $bd_ip_generics] "true"] == 0} {
7730  set ip_regex ".*"
7731  } else {
7732  set ip_regex $bd_ip_generics
7733  }
7734 
7735  set ip_list [get_ips -regex $ip_regex]
7736  Msg Debug "IPs found with regex \{$ip_regex\}: $ip_list"
7737 
7738  set regen_targets {}
7739 
7740  foreach {ip} $ip_list {
7741  set WARN_ABOUT_IP false
7742  set ip_props [list_property [get_ips $ip]]
7743 
7744  #Not sure if this is needed, but it's here to prevent potential errors with get_property
7745  if {[lsearch -exact $ip_props "IS_BD_CONTEXT"] == -1} {
7746  continue
7747  }
7748 
7749  if {[get_property "IS_BD_CONTEXT" [get_ips $ip]] eq "1"} {
7750  foreach {ip_prop} $ip_props {
7751  if {[dict exists $ips_generic_string $ip_prop]} {
7752  if {$WARN_ABOUT_IP == false} {
7753  lappend regen_targets [get_property SCOPE [get_ips $ip]]
7754  Msg Warning "The ip \{$ip\} contains generics that are set by Hog.\
7755  If this is IP is apart of a block design, the .bd file may contain stale, unused, values.\
7756  Hog will always apply the most up-to-date values to the IP during synthesis,\
7757  however these values may or may not be reflected in the .bd file."
7758  set WARN_ABOUT_IP true
7759  }
7760 
7761  # vivado is annoying about the format when setting generics for ips
7762  # this tries to find and set the format to what vivado likes
7763  set xci_path [get_property IP_FILE [get_ips $ip]]
7764  set generic_format [GetGenericFormatFromXci $ip_prop $xci_path]
7765  if {[string equal $generic_format "ERROR"]} {
7766  Msg Warning "Could not find format for generic $ip_prop in IP $ip. Skipping..."
7767  continue
7768  }
7769 
7770  set value_to_set [dict get $ips_generic_string $ip_prop]
7771  switch -exact $generic_format {
7772  "long" {
7773  if {[string match "32'h*" $value_to_set]} {
7774  scan [string map {"32'h" ""} $value_to_set] "%x" value_to_set
7775  }
7776  }
7777  "bool" {
7778  set value_to_set [expr {$value_to_set ? "true" : "false"}]
7779  }
7780  "float" {
7781  if {[string match "32'h*" $value_to_set]} {
7782  binary scan [binary format H* [string map {"32'h" ""} $value_to_set]] d value_to_set
7783  }
7784  }
7785  "bitString" {
7786  if {[string match "32'h*" $value_to_set]} {
7787  set value_to_set [string map {"32'h" "0x"} $value_to_set]
7788  }
7789  }
7790  "string" {
7791  set value_to_set [format "%s" $value_to_set]
7792  }
7793  default {
7794  Msg Warning "Unknown generic format $generic_format for IP $ip. Will attempt to pass as string..."
7795  }
7796  }
7797 
7798 
7799  Msg Info "The IP \{$ip\} contains: $ip_prop ($generic_format), setting it to $value_to_set."
7800  if {[catch {set_property -name $ip_prop -value $value_to_set -objects [get_ips $ip]} prop_error]} {
7801  Msg CriticalWarning "Failed to set property $ip_prop to $value_to_set for IP \{$ip\}: $prop_error"
7802  }
7803  }
7804  }
7805  }
7806  }
7807 
7808  foreach {regen_target} [lsort -unique $regen_targets] {
7809  Msg Info "Regenerating target: $regen_target"
7810  if {[catch {generate_target -force all [get_files $regen_target]} prop_error]} {
7811  Msg CriticalWarning "Failed to regen targets: $prop_error"
7812  }
7813  }
7814 }
7815 
7816 ## @brief Returns the format of a generic from an XML file
7817 ## @param[in] generic_name: The name of the generic
7818 ## @param[in] xml_file: The path to the XML XCI file
7819 proc GetGenericFormatFromXciXML {generic_name xml_file} {
7820  if {![file exists $xml_file]} {
7821  Msg Error "Could not find XML file: $xml_file"
7822  return "ERROR"
7823  }
7824 
7825  set fp [open $xml_file r]
7826  set xci_data [read $fp]
7827  close $fp
7828 
7829  set paramType "string"
7830  set modelparam_regex [format {^.*\y%s\y.*$} [string map {"CONFIG." "MODELPARAM_VALUE."} $generic_name]]
7831  set format_regex {format="([^"]+)"}
7832 
7833  set line [lindex [regexp -inline -line $modelparam_regex $xci_data] 0]
7834  Msg Debug "line: $line"
7835 
7836  if {[regexp $format_regex $line match format_value]} {
7837  Msg Debug "Extracted: $format_value format from xml"
7838  set paramType $format_value
7839  } else {
7840  Msg Debug "No format found, using string"
7841  }
7842 
7843  return $paramType
7844 }
7845 
7846 ## @brief Returns the format of a generic from an XCI file
7847 ## @param[in] generic_name: The name of the generic
7848 ## @param[in] xci_file: The path to the XCI file
7849 proc GetGenericFormatFromXci {generic_name xci_file} {
7850  if {![file exists $xci_file]} {
7851  Msg Error "Could not find XCI file: $xci_file"
7852  return "ERROR"
7853  }
7854 
7855  set fp [open $xci_file r]
7856  set xci_data [read $fp]
7857  close $fp
7858 
7859  set paramType "string"
7860  if {[string first "xilinx.com:schema:json_instance:1.0" $xci_data] == -1} {
7861  Msg Debug "XCI format is not JSON, trying XML..."
7862  set xml_file "[file rootname $xci_file].xml"
7863  return [GetGenericFormatFromXciXML $generic_name $xml_file]
7864  }
7865 
7866  set generic_name [string map {"CONFIG." ""} $generic_name]
7867  set ip_inst [ParseJSON $xci_data "ip_inst"]
7868  set parameters [dict get $ip_inst parameters]
7869  set component_parameters [dict get $parameters component_parameters]
7870  if {[dict exists $component_parameters $generic_name]} {
7871  set generic_info [dict get $component_parameters $generic_name]
7872  if {[dict exists [lindex $generic_info 0] format]} {
7873  set paramType [dict get [lindex $generic_info 0] format]
7874  Msg Debug "Extracted: $paramType format from xci"
7875  return $paramType
7876  }
7877  Msg Debug "No format found, using string"
7878  return $paramType
7879  } else {
7880  return "ERROR"
7881  }
7882 }
7883 
7884 
7885 ## @brief Returns the gitlab-ci.yml snippet for a CI stage and a defined project
7886 #
7887 # @param[in] proj_name: The project name
7888 # @param[in] ci_confs: Dictionary with CI configurations
7889 #
7890 proc WriteGitLabCIYAML {proj_name {ci_conf ""}} {
7891  if {[catch {package require yaml 0.3.3} YAMLPACKAGE]} {
7892  Msg CriticalWarning "Cannot find package YAML.\n Error message: $YAMLPACKAGE. \
7893  If you are running on tclsh, you can fix this by installing package \"tcllib\""
7894  return -1
7895  }
7896 
7897  set job_list []
7898  if {$ci_conf != ""} {
7899  set ci_confs [ReadConf $ci_conf]
7900  foreach sec [dict keys $ci_confs] {
7901  if {[string first : $sec] == -1} {
7902  lappend job_list $sec
7903  }
7904  }
7905  } else {
7906  set job_list {"generate_project" "simulate_project"}
7907  set ci_confs ""
7908  }
7909 
7910  set out_yaml [huddle create]
7911  foreach job $job_list {
7912  # Check main project configurations
7913  set huddle_tags [huddle list]
7914  set tag_section ""
7915  set sec_dict [dict create]
7916 
7917  if {$ci_confs != ""} {
7918  foreach var [dict keys [dict get $ci_confs $job]] {
7919  if {$var == "tags"} {
7920  set tag_section "tags"
7921  set tags [dict get [dict get $ci_confs $job] $var]
7922  set tags [split $tags ","]
7923  foreach tag $tags {
7924  set tag_list [huddle list $tag]
7925  set huddle_tags [huddle combine $huddle_tags $tag_list]
7926  }
7927  } else {
7928  dict set sec_dict $var [dict get [dict get $ci_confs $job] $var]
7929  }
7930  }
7931  }
7932 
7933  # Check if there are extra variables in the conf file
7934  set huddle_variables [huddle create "PROJECT_NAME" $proj_name "extends" ".vars"]
7935  if {[dict exists $ci_confs "$job:variables"]} {
7936  set var_dict [dict get $ci_confs $job:variables]
7937  foreach var [dict keys $var_dict] {
7938  # puts [dict get $var_dict $var]
7939  set value [dict get $var_dict "$var"]
7940  set var_inner [huddle create "$var" "$value"]
7941  set huddle_variables [huddle combine $huddle_variables $var_inner]
7942  }
7943  }
7944 
7945 
7946  set middle [huddle create "extends" ".$job" "variables" $huddle_variables]
7947  foreach sec [dict keys $sec_dict] {
7948  set value [dict get $sec_dict $sec]
7949  set var_inner [huddle create "$sec" "$value"]
7950  set middle [huddle combine $middle $var_inner]
7951  }
7952  if {$tag_section != ""} {
7953  set middle2 [huddle create "$tag_section" $huddle_tags]
7954  set middle [huddle combine $middle $middle2]
7955  }
7956 
7957  set outer [huddle create "$job:$proj_name" $middle]
7958  set out_yaml [huddle combine $out_yaml $outer]
7959  }
7960 
7961  return [string trimleft [yaml::huddle2yaml $out_yaml] "-"]
7962 }
7963 
7964 # @brief Write the content of Hog-library-dictionary created from the project into a .src/.ext/.con list file
7965 #
7966 # @param[in] libs The Hog-Library dictionary with the list of files in the project to write
7967 # @param[in] props The Hog-library dictionary with the file sets
7968 # @param[in] list_path The path of the output list file
7969 # @param[in] repo_path The main repository path
7970 # @param[in] ext_path The external path
7971 proc WriteListFiles {libs props list_path repo_path {ext_path ""}} {
7972  # Writing simulation list files
7973  foreach lib [dict keys $libs] {
7974  if {[llength [DictGet $libs $lib]] > 0} {
7975  set list_file_name $list_path$lib
7976  set list_file [open $list_file_name w]
7977  Msg Info "Writing $list_file_name..."
7978  puts $list_file "#Generated by Hog on [clock format [clock seconds] -format "%Y-%m-%d %H:%M:%S"]"
7979  foreach file [DictGet $libs $lib] {
7980  # Retrieve file properties from prop list
7981  set prop [DictGet $props $file]
7982  # Check if file is local to the repository or external
7983  if {[RelativeLocal $repo_path $file] != ""} {
7984  set file_path [RelativeLocal $repo_path $file]
7985  puts $list_file "$file_path $prop"
7986  } elseif {[RelativeLocal $ext_path $file] != ""} {
7987  set file_path [RelativeLocal $ext_path $file]
7988  set ext_list_file [open "[file rootname $list_file].ext" a]
7989  puts $ext_list_file "$file_path $prop"
7990  close $ext_list_file
7991  } else {
7992  # File is not relative to repo or ext_path... Write a Warning and continue
7993  Msg Warning "The path of file $file is not relative to your repository. Please check!"
7994  }
7995  }
7996  close $list_file
7997  }
7998  }
7999 }
8000 
8001 # @brief Write the content of Hog-library-dictionary created from the project into a .sim list file
8002 #
8003 # @param[in] libs The Hog-Library dictionary with the list of files in the project to write
8004 # @param[in] props The Hog-library dictionary with the file sets
8005 # @param[in] simsets The Hog-library dictionary with the file sets (relevant only for simulation)
8006 # @param[in] list_path The path of the output list file
8007 # @param[in] repo_path The main repository path
8008 # @param[in] force If 1, it will overwrite the existing list files
8009 proc WriteSimListFile {simset libs props simsets list_path repo_path {force 0}} {
8010  # Writing simulation list file
8011  set list_file_name $list_path/${simset}.sim
8012  if {$force == 0 && [file exists $list_file_name]} {
8013  Msg Info "List file $list_file_name already exists, skipping..."
8014  continue
8015  }
8016 
8017  set list_file [open $list_file_name a+]
8018 
8019  # Write the header with the simulator
8020  puts $list_file "\[files\]"
8021  Msg Info "Writing $list_file_name..."
8022  foreach lib [DictGet $simsets $simset] {
8023  foreach file [DictGet $libs $lib] {
8024  # Retrieve file properties from prop list
8025  set prop [DictGet $props $file]
8026  # Check if file is local to the repository or external
8027  if {[RelativeLocal $repo_path $file] != ""} {
8028  set file_path [RelativeLocal $repo_path $file]
8029  set lib_name [file rootname $lib]
8030  if {$lib_name != $simset && [file extension $file] == ".vhd" && [file extension $file] == ""} {
8031  lappend prop "lib=$lib_name"
8032  }
8033  puts $list_file "$file_path $prop"
8034  } else {
8035  # File is not relative to repo or ext_path... Write a Warning and continue
8036  Msg Warning "The path of file $file is not relative to your repository. Please check!"
8037  }
8038  }
8039  }
8040  close $list_file
8041 }
8042 
8043 
8044 ## @brief Write into a file, and if the file exists, it will append the string
8045 #
8046 # @param[out] File The log file to write into the message
8047 # @param[in] msg The message text
8048 proc WriteToFile {File msg} {
8049  set f [open $File a+]
8050  puts $f $msg
8051  close $f
8052 }
8053 
8054 ## Write the resource utilization table into a a file (Vivado only)
8055 #
8056 # @param[in] input the input .rpt report file from Vivado
8057 # @param[in] output the output file
8058 # @param[in] project_name the name of the project
8059 # @param[in] run synthesis or implementation
8060 proc WriteUtilizationSummary {input output project_name run} {
8061  set f [open $input "r"]
8062  set o [open $output "a"]
8063  puts $o "## $project_name $run Utilization report\n\n"
8064  struct::matrix util_m
8065  util_m add columns 14
8066  util_m add row
8067  if {[GetIDEVersion] >= 2021.0} {
8068  util_m add row "| **Site Type** | **Used** | **Fixed** | **Prohibited** | **Available** | **Util%** |"
8069  util_m add row "| --- | --- | --- | --- | --- | --- |"
8070  } else {
8071  util_m add row "| **Site Type** | **Used** | **Fixed** | **Available** | **Util%** |"
8072  util_m add row "| --- | --- | --- | --- | --- |"
8073  }
8074 
8075  set luts 0
8076  set regs 0
8077  set uram 0
8078  set bram 0
8079  set dsps 0
8080  set ios 0
8081 
8082  while {[gets $f line] >= 0} {
8083  if {([string first "| CLB LUTs" $line] >= 0 || [string first "| Slice LUTs" $line] >= 0) && $luts == 0} {
8084  util_m add row $line
8085  set luts 1
8086  }
8087  if {([string first "| CLB Registers" $line] >= 0 || [string first "| Slice Registers" $line] >= 0) && $regs == 0} {
8088  util_m add row $line
8089  set regs 1
8090  }
8091  if {[string first "| Block RAM Tile" $line] >= 0 && $bram == 0} {
8092  util_m add row $line
8093  set bram 1
8094  }
8095  if {[string first "URAM " $line] >= 0 && $uram == 0} {
8096  util_m add row $line
8097  set uram 1
8098  }
8099  if {[string first "DSPs" $line] >= 0 && $dsps == 0} {
8100  util_m add row $line
8101  set dsps 1
8102  }
8103  if {[string first "Bonded IOB" $line] >= 0 && $ios == 0} {
8104  util_m add row $line
8105  set ios 1
8106  }
8107  }
8108  util_m add row
8109 
8110  close $f
8111  puts $o [util_m format 2string]
8112  close $o
8113 }
8114 
8115 # Check Git Version when sourcing hog.tcl
8116 if {[GitVersion 2.7.2] == 0} {
8117  Msg Error "Found Git version older than 2.7.2. Hog will not work as expected, exiting now."
8118 }
8119 
8120 ## @brief Tries to find the coorrect command to be launched for curl
8121 #
8122 # @details If running in vivado shell you may need to unsed LD_LIBRARY_PATH befor running curl to avoid conflicts with vivado libraries.
8123 # This procedure tests curl if execution is correct returns "curl"
8124 # 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.
8125 # If both fail returns "curl", this will most probably generate failures later
8126 proc GetCurl {} {
8127  if {![catch {exec curl --silent --show-error --version}]} {
8128  if {![catch {exec curl --silent --show-error -I https://gitlab.com}]} {
8129  return [list curl --silent --show-error]
8130  }
8131  }
8132 
8133  if {![catch {exec env -u LD_LIBRARY_PATH curl --silent --show-error --version}]} {
8134  if {![catch {exec env -u LD_LIBRARY_PATH curl --silent --show-error -I https://gitlab.com}]} {
8135  return [list env -u LD_LIBRARY_PATH curl --silent --show-error]
8136  }
8137  }
8138 
8139  error "Cannot find a working curl invocation"
8140 }