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