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