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