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