Hog v9.3.0
hog.tcl
Go to the documentation of this file.
1 # Copyright 2018-2024 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 # @brief Collection of Tcl functions used in Vivado/Quartus scripts
17 
18 ### @brief Display a Vivado/Quartus/Tcl-shell info message
19 #
20 # @param[in] level the severity level of the message given as string or integer: status/extra_info 0, info 1, warning 2, critical warning 3, error 4.
21 # @param[in] msg the message text.
22 # @param[in] title the name of the script displaying the message, if not given, the calling script name will be used by default.
23 #
24 
25 #### GLOBAL CONSTANTS
26 set CI_STAGES {"generate_project" "simulate_project"}
27 set CI_PROPS {"-synth_only"}
28 
29 proc ALLOWED_PROPS {} {
30  return [dict create ".vhd" [list "93" "nosynth" "noimpl" "nosim" "1987" "1993" "2008" ]\
31  ".vhdl" [list "93" "nosynth" "noimpl" "nosim" "1987" "1993" "2008" ]\
32  ".v" [list "SystemVerilog" "verilog_header" "nosynth" "noimpl" "nosim" "1995" "2001"]\
33  ".sv" [list "verilog" "verilog_header" "nosynth" "noimpl" "nosim" "2005" "2009"]\
34  ".do" [list "nosim"]\
35  ".udo" [list "nosim"]\
36  ".xci" [list "nosynth" "noimpl" "nosim" "locked"]\
37  ".xdc" [list "nosynth" "noimpl" ]\
38  ".tcl" [list "nosynth" "noimpl" "nosim" "source" "qsys" "noadd" "--block-symbol-file" "--clear-output-directory" "--example-design" "--export-qsys-script" "--family" "--greybox" "--ipxact" "--jvm-max-heap-size" "--parallel" "--part" "--search-path" "--simulation" "--synthesis" "--testbench" "--testbench-simulation" "--upgrade-ip-cores" "--upgrade-variation-file"]\
39  ".qsys" [list "nogenerate" "noadd" "--block-symbol-file" "--clear-output-directory" "--example-design" "--export-qsys-script" "--family" "--greybox" "--ipxact" "--jvm-max-heap-size" "--parallel" "--part" "--search-path" "--simulation" "--synthesis" "--testbench" "--testbench-simulation" "--upgrade-ip-cores" "--upgrade-variation-file"]\
40  ".sdc" [list "notiming" "nosynth" "noplace"]\
41  ".pdc" [list "nosynth" "noplace"]]\
42 }
43 
44 
45 #### FUNCTIONS
46 
47 proc GetSimulators {} {
48  set SIMULATORS [list "modelsim" "questa" "riviera" "activehdl" "ies" "vcs"]
49  return $SIMULATORS
50 }
51 
52 ## Get whether the IDE is MicroSemi Libero
53 proc IsLibero {} {
54  return [expr {[info commands get_libero_version] != ""}]
55 }
56 
57 ### Get whether the Synthesis tools is Synplify
58 proc IsSynplify {} {
59  return [expr {[info commands program_version] != ""}]
60 }
61 
62 
63 ## Get whether the IDE is Xilinx (Vivado or ISE)
64 proc IsXilinx {} {
65  return [expr {[info commands get_property] != ""}]
66 }
67 
68 ## Get whether the IDE is vivado
69 proc IsVivado {} {
70  if {[IsXilinx]} {
71  return [expr {[string first Vivado [version]] == 0}]
72  } else {
73  return 0
74  }
75 }
76 
77 ## Get whether the IDE is ISE (planAhead)
78 proc IsISE {} {
79  if {[IsXilinx]} {
80  return [expr {[string first PlanAhead [version]] == 0}]
81  } else {
82  return 0
83  }
84 }
85 
86 ## Get whether the IDE is Quartus
87 proc IsQuartus {} {
88  return [expr {[info commands project_new] != ""}]
89 }
90 
91 ## Get whether we are in tclsh
92 proc IsTclsh {} {
93  return [expr {![IsQuartus] && ![IsXilinx] && ![IsLibero] && ![IsSynplify]}]
94 }
95 
96 ## @brief Find out if the given Xilinx part is a Vesal chip
97 #
98 # @param[out] 1 if it's Versal 0 if it's not
99 # @param[in] part The FPGA part
100 #
101 proc IsVersal {part} {
102  if { [regexp {^(xcvp|xcvm|xcve|xcvc|xqvc|xqvm).*} $part] } {
103  return 1
104  } else {
105  return 0
106  }
107 }
108 
109 ## @brief Find out if the given Xilinx part is a Vesal chip
110 #
111 # @param[out] 1 if it's Zynq 0 if it's not
112 # @param[in] part The FPGA part
113 #
114 proc IsZynq {part} {
115  if { [regexp {^(xc7z|xczu).*} $part] } {
116  return 1
117  } else {
118  return 0
119  }
120 }
121 
122 
123 
124 ## @brief # Returns the step name for the stage that produces the binary file
125 #
126 # Projects using Versal chips have a different step for producing the
127 # binary file, we use this function to take that into account
128 #
129 # @param[out] 1 if it's Versal 0 if it's not
130 # @param[in] part The FPGA part
131 #
132 proc BinaryStepName {part} {
133  if {[IsVersal $part]} {
134  return "WRITE_DEVICE_IMAGE"
135  } else {
136  return "WRITE_BITSTREAM"
137  }
138 }
139 
140 ## Hog message printout function
141 proc Msg {level msg {title ""}} {
142 
143  set level [string tolower $level]
144 
145  if {$title == ""} {set title [lindex [info level [expr {[info level]-1}]] 0]}
146 
147  if {$level == 0 || $level == "status" || $level == "extra_info"} {
148  set vlevel {STATUS}
149  set qlevel info
150  } elseif {$level == 1 || $level == "info"} {
151  set vlevel {INFO}
152  set qlevel info
153  } elseif {$level == 2 || $level == "warning"} {
154  set vlevel {WARNING}
155  set qlevel warning
156  } elseif {$level == 3 || [string first "critical" $level] !=-1} {
157  set vlevel {CRITICAL WARNING}
158  set qlevel critical_warning
159  } elseif {$level == 4 || $level == "error"} {
160  set vlevel {ERROR}
161  set qlevel error
162  } elseif {$level == 5 || $level == "debug"} {
163  if {([info exists ::DEBUG_MODE] && $::DEBUG_MODE == 1) || ([info exists ::env(HOG_DEBUG_MODE)] && $::env(HOG_DEBUG_MODE) == 1)} {
164  set vlevel {STATUS}
165  set qlevel extra_info
166  set msg "DEBUG: \[Hog:$title\] $msg"
167  } else {
168  return
169  }
170 
171  } else {
172  puts "Hog Error: level $level not defined"
173  exit -1
174  }
175 
176 
177  if {[IsXilinx]} {
178  # Vivado
179  set status [catch {send_msg_id Hog:$title-0 $vlevel $msg}]
180  if {$status != 0} {
181  exit $status
182  }
183  } elseif {[IsQuartus]} {
184  # Quartus
185  post_message -type $qlevel "Hog:$title $msg"
186  if { $qlevel == "error"} {
187  exit 1
188  }
189  } else {
190  # Tcl Shell / Libero
191  if {$vlevel != "STATUS"} {
192  puts "$vlevel: \[Hog:$title\] $msg"
193  } else {
194  puts $msg
195  }
196 
197  if {$qlevel == "error"} {
198  exit 1
199  }
200  }
201 }
202 
203 ## @brief Write a into file, if the file exists, it will append the string
204 #
205 # @param[out] File The log file onto which write the message
206 # @param[in] msg The message text
207 #
208 proc WriteToFile {File msg} {
209  set f [open $File a+]
210  puts $f $msg
211  close $f
212 }
213 
214 ## @brief Sets a property of an object to a given value.
215 #
216 # It automatically recognises whether it is in Vivado or Quartus mode
217 #
218 # @param[out] property:
219 # @param[in] value:
220 # @param[out] object
221 #
222 proc SetProperty {property value object} {
223  if {[IsXilinx]} {
224  # Vivado
225  set_property $property $value $object
226 
227  } elseif {[IsQuartus]} {
228  # Quartus
229 
230  } else {
231  # Tcl Shell
232  puts "***DEBUG Hog:SetProperty $property to $value of $object"
233  }
234 
235 
236 }
237 
238 ## @brief Retrieves the value of a property of an object
239 #
240 # It automatically recognises whether it is in Vivado or Quartus mode
241 #
242 # @param[in] property the name of the property to be retrieved
243 # @param[in] object the object from which to retrieve the property
244 #
245 # @returns the value of object.property
246 #
247 proc GetProperty {property object} {
248  if {[IsXilinx]} {
249  # Vivado
250  return [get_property -quiet $property $object]
251 
252  } elseif {[IsQuartus]} {
253  # Quartus
254  return ""
255  } else {
256  # Tcl Shell
257  puts "***DEBUG Hog:GetProperty $property of $object"
258  return "DEBUG_property_value"
259  }
260 }
261 
262 ## @brief Sets the value of a parameter to a given value.
263 #
264 # This function is a wrapper for set_param $parameter $value
265 #
266 # @param[out] parameter the parameter whose value must be set
267 # @param[in] value the value of the parameter
268 
269 proc SetParameter {parameter value } {
270  set_param $parameter $value
271 }
272 
273 ## @brief Adds the file containing the top module to the project
274 #
275 # It automatically recognises whether it is in Vivado or Quartus mode
276 #
277 # @param[in] top_module name of the top module, expected @c top_<project_name>
278 # @param[in] top_file name of the file containing the top module
279 # @param[in] sources list of source files
280 proc AddTopFile {top_module top_file sources} {
281  if {[IsXilinx]} {
282  #VIVADO_ONLY
283  add_files -norecurse -fileset $sources $top_file
284  } elseif {[IsQuartus]} {
285  #QUARTUS ONLY
286  set file_type [FindFileType $top_file]
287  set hdl_version [FindVhdlVersion $top_file]
288  set_global_assignment -name $file_type $top_file
289  } else {
290  puts "Adding project top module $top_module"
291  }
292 }
293 
294 ## @brief set the top module as top module.
295 #
296 # It automatically recognises whether it is in Vivado or Quartus mode
297 #
298 # @param[out] top_module name of the top module
299 # @param[in] sources list of all source files in the project
300 #
301 proc SetTopProperty {top_module sources} {
302  Msg Info "Setting TOP property to $top_module module"
303  if {[IsXilinx]} {
304  #VIVADO_ONLY
305  set_property "top" $top_module $sources
306  } elseif {[IsQuartus]} {
307  #QUARTUS ONLY
308  set_global_assignment -name TOP_LEVEL_ENTITY $top_module
309  } elseif {[IsLibero]} {
310  set_root -module $top_module
311  }
312 
313 }
314 
315 ## @brief Retrieves the project named proj
316 #
317 # It automatically recognises whether it is in Vivado or Quartus mode
318 #
319 # @param[in] proj the project name
320 #
321 # @return the project $proj
322 #
323 proc GetProject {proj} {
324  if {[IsXilinx]} {
325  # Vivado
326  return [get_projects $proj]
327 
328  } elseif {[IsQuartus]} {
329  # Quartus
330  return ""
331  } else {
332  # Tcl Shell
333  puts "***DEBUG Hog:GetProject $proj"
334  return "DEBUG_project"
335  }
336 
337 }
338 
339 ## @brief Gets a list of synthesis and implementation runs in the current project that match a run (passed as parameter)
340 #
341 # The run name is matched against the input parameter
342 #
343 # @param[in] run the run identifier
344 #
345 # @return a list of synthesis and implementation runs matching the parameter
346 #
347 proc GetRun {run} {
348  if {[IsXilinx]} {
349  # Vivado
350  return [get_runs -quiet $run]
351 
352  } elseif {[IsQuartus]} {
353  # Quartus
354  return ""
355  } else {
356  # Tcl Shell
357  puts "***DEBUG Hog:GetRun $run"
358  return "DEBUG_run"
359  }
360 }
361 
362 ## @brief Gets a list of files contained in the current project that match a file name (passed as parameter)
363 #
364 # The file name is matched against the input parameter.
365 # IF no parameter if passed returns a list of all files in the project
366 #
367 # @param[in] file name (or part of it)
368 #
369 # @return a list of files matching the parameter
370 #
371 proc GetFile {file} {
372  if {[IsXilinx]} {
373  # Vivado
374  set Files [get_files -all $file]
375  set f [lindex $Files 0]
376 
377  return $f
378 
379  } elseif {[IsQuartus]} {
380  # Quartus
381  return ""
382  } else {
383  # Tcl Shell
384  puts "***DEBUG Hog:GetFile $file"
385  return "DEBUG_file"
386  }
387 }
388 
389 ## @brief Creates a new fileset
390 #
391 # A file set is a list of files with a specific function within the project.
392 #
393 # @param[in] fileset
394 #
395 # @returns The create_fileset command returns the name of the newly created fileset
396 #
397 proc CreateFileSet {fileset} {
398  set a [create_fileset -srcset $fileset]
399  return $a
400 }
401 
402 ## @brief Retrieves a fileset
403 #
404 # Gets a list of filesets in the current project that match a specified search pattern.
405 # The default command gets a list of all filesets in the project.
406 #
407 # @param[in] fileset the name to be checked
408 #
409 # @return a list of filesets in the current project that match the specified search pattern.
410 #
411 proc GetFileSet {fileset} {
412  set a [get_filesets $fileset]
413  return $a
414 }
415 
416 ## @brief Add a new file to a fileset
417 #
418 # @param[in] file name of the files to add. NOTE: directories are not supported.
419 # @param[in] fileset fileset name
420 #
421 proc AddFile {file fileset} {
422  add_files -norecurse -fileset $fileset $file
423 }
424 
425 ## @brief gets the full path to the /../../ folder
426 #
427 # @return "[file normalize [file dirname [info script]]]/../../"
428 #
429 proc GetRepoPath {} {
430  return "[file normalize [file dirname [info script]]]/../../"
431 }
432 
433 ## @brief Compare two semantic versions
434 #
435 # @param[in] ver1 a list of 3 numbers M m p
436 # @param[in] ver2 a list of 3 numbers M m p
437 #
438 # In case the ver1 or ver2 are in the vormat vX.Y.Z rather than a list, they will be converted.
439 # If one of the tags is an empty string it will be considered as 0.0.0
440 #
441 # @return Return 1 ver1 is greather than ver2, 0 if they are equal, and -1 if ver2 is greater than ver1
442 #
443 proc CompareVersions {ver1 ver2} {
444  if {$ver1 eq ""} {
445  set ver1 v0.0.0
446  }
447 
448  if {$ver2 eq ""} {
449  set ver2 v0.0.0
450  }
451 
452  if {[regexp {v(\d+)\.(\d+)\.(\d+)} $ver1 - x y z]} {
453  set ver1 [list $x $y $z]
454  }
455  if {[regexp {v(\d+)\.(\d+)\.(\d+)} $ver2 - x y z]} {
456  set ver2 [list $x $y $z]
457  }
458 
459  # Add 1 in front to avoid crazy Tcl behaviour with leading 0 being octal...
460  set v1 [join $ver1 ""]
461  set v1 "1$v1"
462  set v2 [join $ver2 ""]
463  set v2 "1$v2"
464 
465  if {[string is integer $v1] && [string is integer $v2]} {
466 
467  set ver1 [expr {[scan [lindex $ver1 0] %d]*100000 + [scan [lindex $ver1 1] %d]*1000 + [scan [lindex $ver1 2] %d]}]
468  set ver2 [expr {[scan [lindex $ver2 0] %d]*100000 + [scan [lindex $ver2 1] %d]*1000 + [scan [lindex $ver2 2] %d]}]
469 
470  if {$ver1 > $ver2 } {
471  set ret 1
472  } elseif {$ver1 == $ver2} {
473  set ret 0
474  } else {
475  set ret -1
476  }
477 
478  } else {
479  Msg Warning "Version is not numeric: $ver1, $ver2"
480  set ret 0
481  }
482  return [expr {$ret}]
483 }
484 
485 ## @brief Check git version installed in this machine
486 #
487 # @param[in] target_version the version required by the current project
488 #
489 # @return Return 1 if the system git version is greater or equal to the target
490 #
491 proc GitVersion {target_version} {
492  set ver [split $target_version "."]
493  set v [Git --version]
494  #Msg Info "Found Git version: $v"
495  set current_ver [split [lindex $v 2] "."]
496  set target [expr {[lindex $ver 0]*100000 + [lindex $ver 1]*100 + [lindex $ver 2]}]
497  set current [expr {[lindex $current_ver 0]*100000 + [lindex $current_ver 1]*100 + [lindex $current_ver 2]}]
498  return [expr {$target <= $current}]
499 }
500 
501 ## @brief Checks doxygen version installed in this machine
502 #
503 # @param[in] target_version the version required by the current project
504 #
505 # @return Return 1 if the system Doxygen version is greater or equal to the target
506 #
507 proc DoxygenVersion {target_version} {
508  set ver [split $target_version "."]
509  set v [Execute doxygen --version]
510  Msg Info "Found doxygen version: $v"
511  set current_ver [split $v ". "]
512  set target [expr {[lindex $ver 0]*100000 + [lindex $ver 1]*100 + [lindex $ver 2]}]
513  set current [expr {[lindex $current_ver 0]*100000 + [lindex $current_ver 1]*100 + [lindex $current_ver 2]}]
514 
515  return [expr {$target <= $current}]
516 }
517 
518 ## @brief determine file type from extension
519 # Used only for Quartus
520 #
521 ## @return FILE_TYPE the file Type
522 proc FindFileType {file_name} {
523  set extension [file extension $file_name]
524  switch $extension {
525  .stp {
526  set file_extension "USE_SIGNALTAP_FILE"
527  }
528  .vhd {
529  set file_extension "VHDL_FILE"
530  }
531  .vhdl {
532  set file_extension "VHDL_FILE"
533  }
534  .v {
535  set file_extension "VERILOG_FILE"
536  }
537  .sv {
538  set file_extension "SYSTEMVERILOG_FILE"
539  }
540  .sdc {
541  set file_extension "SDC_FILE"
542  }
543  .pdc {
544  set file_extension "PDC_FILE"
545  }
546  .ndc {
547  set file_extension "NDC_FILE"
548  }
549  .fdc {
550  set file_extension "FDC_FILE"
551  }
552  .qsf {
553  set file_extension "SOURCE_FILE"
554  }
555  .ip {
556  set file_extension "IP_FILE"
557  }
558  .qsys {
559  set file_extension "QSYS_FILE"
560  }
561  .qip {
562  set file_extension "QIP_FILE"
563  }
564  .sip {
565  set file_extension "SIP_FILE"
566  }
567  .bsf {
568  set file_extension "BSF_FILE"
569  }
570  .bdf {
571  set file_extension "BDF_FILE"
572  }
573  .tcl {
574  set file_extension "COMMAND_MACRO_FILE"
575  }
576  .vdm {
577  set file_extension "VQM_FILE"
578  }
579  default {
580  set file_extension "ERROR"
581  Msg Error "Unknown file extension $extension"
582  }
583  }
584  return $file_extension
585 }
586 
587 ## @brief Set VHDL version to 2008 for *.vhd files
588 #
589 # @param[in] file_name the name of the HDL file
590 #
591 # @return "-hdl_version VHDL_2008" if the file is a *.vhd files else ""
592 proc FindVhdlVersion {file_name} {
593  set extension [file extension $file_name]
594  switch $extension {
595  .vhd {
596  set vhdl_version "-hdl_version VHDL_2008"
597  }
598  .vhdl {
599  set vhdl_version "-hdl_version VHDL_2008"
600  }
601  default {
602  set vhdl_version ""
603  }
604  }
605 
606  return $vhdl_version
607 }
608 
609 ## @brief Read a list file and adds the files to Vivado/Quartus, adding the additional information as file type.
610 #
611 # Additional information is provided with text separated from the file name with one or more spaces
612 #
613 # @param[in] args The arguments are <list_file> <path> [options]
614 # * list_file file containing vhdl list with optional properties
615 # * path path the vhdl file are referred to in the list file
616 # Options:
617 # * -lib <library> name of the library files will be added to, if not given will be extracted from the file name
618 # * -sha_mode if not set to 0, 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.
619 #
620 # @return a list of 3 dictionaries: "libraries" has library name as keys and a list of filenames as values, "properties" has as file names as keys and a list of properties as values, "filesets" has fileset name as keys and the list of associated libraries as values.
621 
622 proc ReadListFile args {
623 
624  if {[IsQuartus]} {
625  load_package report
626  if { [catch {package require cmdline} ERROR] } {
627  puts "$ERROR\n If you are running this script on tclsh, you can fix this by installing 'tcllib'"
628  return 1
629  }
630  }
631 
632  set parameters {
633  {lib.arg "" "The name of the library files will be added to, if not given will be extracted from the file name."}
634  {fileset.arg "" "The name of the library, from the main list file"}
635  {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."}
636  }
637  set usage "USAGE: ReadListFile \[options\] <list file> <path>"
638  if {[catch {array set options [cmdline::getoptions args $parameters $usage]}] || [llength $args] != 2 } {
639  Msg CriticalWarning "[cmdline::usage $parameters $usage]"
640  return
641  }
642  set list_file [lindex $args 0]
643  set path [lindex $args 1]
644  set sha_mode $options(sha_mode)
645  set lib $options(lib)
646  set fileset $options(fileset)
647 
648  if { $sha_mode == 1} {
649  set sha_mode_opt "-sha_mode"
650  } else {
651  set sha_mode_opt ""
652  }
653 
654  # if no library is given, work it out from the file name
655  if {$lib eq ""} {
656  set lib [file rootname [file tail $list_file]]
657  }
658  set fp [open $list_file r]
659  set file_data [read $fp]
660  close $fp
661  set list_file_ext [file extension $list_file]
662  switch $list_file_ext {
663  .sim {
664  if {$fileset eq ""} {
665  # If fileset is empty, use the library name for .sim file
666  set fileset "$lib"
667  }
668  }
669  .con {
670  set fileset "constrs_1"
671  }
672  default {
673  set fileset "sources_1"
674  }
675  }
676 
677  set libraries [dict create]
678  set filesets [dict create]
679  set properties [dict create]
680  # Process data file
681  set data [split $file_data "\n"]
682  set n [llength $data]
683  Msg Debug "$n lines read from $list_file."
684  set cnt 0
685 
686  foreach line $data {
687  # Exclude empty lines and comments
688  if {![regexp {^ *$} $line] & ![regexp {^ *\#} $line] } {
689  set file_and_prop [regexp -all -inline {\S+} $line]
690  set srcfile [lindex $file_and_prop 0]
691  set srcfile "$path/$srcfile"
692 
693  set srcfiles [glob -nocomplain $srcfile]
694 
695  # glob the file list for wildcards
696  if {$srcfiles != $srcfile && ! [string equal $srcfiles "" ]} {
697  Msg Debug "Wildcard source expanded from $srcfile to $srcfiles"
698  } else {
699  if {![file exists $srcfile]} {
700  Msg CriticalWarning "File: $srcfile (from list file: $list_file) does not exist."
701  continue
702  }
703  }
704 
705  foreach vhdlfile $srcfiles {
706  if {[file exists $vhdlfile]} {
707  set vhdlfile [file normalize $vhdlfile]
708  set extension [file extension $vhdlfile]
709  ### Set file properties
710  set prop [lrange $file_and_prop 1 end]
711 
712  # The next lines should be inside the case for recursive list files, also we should check the allowed properties for the .src as well
713  set library [lindex [regexp -inline {lib\s*=\s*(.+?)\y.*} $prop] 1]
714  if { $library == "" } {
715  set library $lib
716  }
717 
718  if { $extension == $list_file_ext } {
719  # Deal with recusive list files
720  set ref_path [lindex [regexp -inline {path\s*=\s*(.+?)\y.*} $prop] 1]
721  if { $ref_path eq "" } {
722  set ref_path $path
723  } else {
724  set ref_path [file normalize $path/$ref_path]
725  }
726  Msg Debug "List file $vhdlfile found in list file, recursively opening it using path \"$ref_path\"..."
727  lassign [ReadListFile {*}"-lib $library -fileset $fileset $sha_mode_opt $vhdlfile $ref_path"] l p fs
728  set libraries [MergeDict $l $libraries]
729  set properties [MergeDict $p $properties]
730  set filesets [MergeDict $fs $filesets]
731  } elseif {[lsearch {.src .sim .con ReadExtraFileList} $extension] >= 0 } {
732  # Not supported extensions
733  Msg Error "$vhdlfile cannot be included into $list_file, $extension files must be included into $extension files."
734  } else {
735  # Deal with single files
736  regsub -all " *= *" $prop "=" prop
737  # Fill property dictionary
738  foreach p $prop {
739  # No need to append the lib= property
740  if { [string first "lib=" $p ] == -1} {
741  # Get property name up to the = (for QSYS properties at the moment)
742  set pos [string first "=" $p]
743  if { $pos == -1 } {
744  set prop_name $p
745  } else {
746  set prop_name [string range $p 0 [expr {$pos - 1}]]
747  }
748  if { [IsInList $prop_name [DictGet [ALLOWED_PROPS] $extension ] ] || [string first "top" $p] == 0 || $list_file_ext eq ".ipb"} {
749  dict lappend properties $vhdlfile $p
750  Msg Debug "Adding property $p to $vhdlfile..."
751  } elseif { $list_file_ext != ".ipb" } {
752  Msg Warning "Setting Property $p is not supported for file $vhdlfile or it is already its default. The allowed properties for this file type are \[ [DictGet [ALLOWED_PROPS] $extension ]\]"
753  }
754  }
755  }
756  if { [lsearch {.xci .ip .bd .xcix} $extension] >= 0} {
757  # Adding IP library
758  set lib_name "ips.src"
759  } elseif { [IsInList $extension {.vhd .vhdl}] || $list_file_ext == ".sim"} {
760  # VHDL files and simulation
761  if { ![IsInList $extension {.vhd .vhdl}]} {
762  set lib_name "others.sim"
763  } else {
764  set lib_name "$library$list_file_ext"
765  }
766  } elseif { $list_file_ext == ".con" } {
767  set lib_name "sources.con"
768  } elseif { $list_file_ext == ".ipb" } {
769  set lib_name "xml.ipb"
770  } else {
771  # Other files are stored in the OTHER dictionary from vivado (no library assignment)
772  set lib_name "others.src"
773  }
774 
775  Msg Debug "Appending $vhdlfile to $lib_name list..."
776  dict lappend libraries $lib_name $vhdlfile
777  if { $sha_mode != 0 && [file type $vhdlfile] eq "link"} {
778  #if the file is a link, also add the linked file in sha mode
779  set real_file [GetLinkedFile $vhdlfile]
780  dict lappend libraries $lib_name $real_file
781  Msg Debug "File $vhdlfile is a soft link, also adding the real file: $real_file"
782  }
783 
784 
785  # Create the fileset (if not already) and append the library
786  if {[dict exists $filesets $fileset] == 0} {
787  # Fileset has not been defined yet, adding to dictionary...
788  Msg Debug "Adding $fileset to the fileset dictionary..."
789  Msg Debug "Adding library $lib_name to fileset $fileset..."
790  dict set filesets $fileset $lib_name
791  } else {
792  # Fileset already exist in dictionary, append library to list, if not already there
793  if {[IsInList $lib_name [DictGet $filesets $fileset]] == 0} {
794  Msg Debug "Adding library $lib_name to fileset $fileset..."
795  dict lappend filesets $fileset $lib_name
796  }
797  }
798  }
799  incr cnt
800  } else {
801  Msg CriticalWarning "File $vhdlfile not found."
802  }
803  }
804  }
805  }
806 
807  if {$sha_mode != 0} {
808  #In SHA mode we also need to add the list file to the list
809  if {$list_file_ext eq ".ipb"} {
810  set sha_lib "xml.ipb"
811  } else {
812  set sha_lib $lib$list_file_ext
813  }
814  dict lappend libraries $sha_lib [file normalize $list_file]
815  if {[file type $list_file] eq "link"} {
816  #if the file is a link, also add the linked file
817  set real_file [GetLinkedFile $list_file]
818  dict lappend libraries $lib$list_file_ext $real_file
819  Msg Debug "List file $list_file is a soft link, also adding the real file: $real_file"
820  }
821  }
822  return [list $libraries $properties $filesets]
823 }
824 
825 ## @brief Return operative sistem
826 proc OS {} {
827  global tcl_platform
828  return $tcl_platform(platform)
829 }
830 
831 ## @brief Return the real file linked by a soft link
832 #
833 # If the provided file is not a soft link, will give a Warning and return an empty string.
834 # If the link is broken, will give a warning but still return the linked file
835 #
836 # @param[in] link_file the soft link file
837 proc GetLinkedFile {link_file} {
838  if {[file type $link_file] eq "link"} {
839  if {[OS] == "windows" } {
840  #on windows we need to use readlink because Tcl is broken
841  lassign [ExecuteRet realpath $link_file] ret msg
842  lassign [ExecuteRet cygpath -m $msg] ret2 msg2
843  if {$ret == 0 && $ret2 == 0} {
844  set real_file $msg2
845  Msg Debug "Found link file $link_file on Windows, the linked file is: $real_file"
846  } else {
847  Msg CriticalWarning "[file normalize $link_file] is a soft link. Soft link are not supported on Windows and readlink.exe or cygpath.exe did not work: readlink=$ret: $msg, cygpath=$ret2: $msg2."
848  set real_file $link_file
849  }
850  } else {
851  #on linux Tcl just works
852  set linked_file [file link $link_file]
853  set real_file [file normalize [file dirname $link_file]/$linked_file]
854  }
855 
856  if {![file exists $real_file]} {
857  Msg Warning "$link_file is a broken link, because the linked file: $real_file does not exist."
858  }
859  } else {
860  Msg Warning "$link file is not a soft link"
861  set real_file $link_file
862  }
863  return $real_file
864 }
865 
866 ## @brief Merge two tcl dictionaries of lists
867 #
868 # If the dictionaries contain same keys, the list at the common key is a merging of the two
869 #
870 #
871 # @param[in] dict0 the name of the first dictionary
872 # @param[in] dict1 the name of the second dictionary
873 #
874 # @return the merged dictionary
875 #
876 proc MergeDict {dict0 dict1} {
877  set outdict [dict merge $dict1 $dict0]
878  foreach key [dict keys $dict1 ] {
879  if {[dict exists $dict0 $key]} {
880  set temp_list [dict get $dict1 $key]
881  foreach vhdfile $temp_list {
882  # Avoid duplication
883  if {[IsInList $vhdfile [DictGet $outdict $key]] == 0} {
884  dict lappend outdict $key $vhdfile
885  }
886  }
887  }
888  }
889  return $outdict
890 }
891 
892 ## @brief Gets key from dict and returns default if key not found
893 #
894 # @param[in] dictName the name of the dictionary
895 # @param[in] keyName the name of the key
896 # @param[in] default the default value to be returned if the key is not found
897 #
898 # @return the dictionary key value
899 
900 proc DictGet {dictName keyName {default ""}} {
901  if {[dict exists $dictName $keyName]} {
902  return [dict get $dictName $keyName]
903  } else {
904  return $default
905  }
906 }
907 
908 ## @brief Get git SHA of a vivado library
909 #
910 # If the special string "ALL" is used, returns the global hash
911 #
912 # @param[in] lib the name of the library whose latest commit hash will be returned
913 #
914 # @return the git SHA of the specified library
915 #
916 proc GetHashLib {lib} {
917  if {$lib eq "ALL"} {
918  set ret [GetSHA]
919  } else {
920  set ff [get_files -filter LIBRARY==$lib]
921  set ret [GetSHA $ff]
922  }
923 
924  return $ret
925 }
926 
927 
928 ## @brief Get a list of all modified the files matching then pattern
929 #
930 # @param[in] repo_path the path of the git repository
931 # @param[in] pattern the pattern with wildcards that files should match
932 #
933 # @return a list of all modified files matchin the pattern
934 #
935 proc GetModifiedFiles {{repo_path "."} {pattern "."}} {
936  set old_path [pwd]
937  cd $repo_path
938  set ret [Git "ls-files --modified $pattern"]
939  cd $old_path
940  return $ret
941 }
942 
943 ## @brief Restore with checkout -- the files specified in pattern
944 #
945 # @param[in] repo_path the path of the git repository
946 # @param[in] pattern the pattern with wildcards that files should match
947 #
948 proc RestoreModifiedFiles {{repo_path "."} {pattern "."}} {
949  set old_path [pwd]
950  cd $repo_path
951  set ret [Git checkout $pattern]
952  cd $old_path
953  return
954 }
955 
956 ## @brief Recursively gets file names from list file
957 #
958 # If the list file contains files with extension .src .sim .con, it will recursively open them
959 #
960 # @param[in] FILE list file to open
961 # @param[in] path the path the files are referred to in the list file
962 #
963 # @returns a list of the files contained in the list file
964 #
965 proc GetFileList {FILE path} {
966  set fp [open $FILE r]
967  set file_data [read $fp]
968  set file_list {}
969  close $fp
970  # Process data file
971  set data [split $file_data "\n"]
972  foreach line $data {
973  if {![regexp {^ *$} $line] & ![regexp {^ *\#} $line] } {
974  #Exclude empty lines and comments
975  set file_and_prop [regexp -all -inline {\S+} $line]
976  set vhdlfile [lindex $file_and_prop 0]
977  set vhdlfile "$path/$vhdlfile"
978  if {[file exists $vhdlfile]} {
979  set extension [file extension $vhdlfile]
980  if { [IsInList $extension {.src .sim .con} ] } {
981  lappend file_list {*}[GetFileList $vhdlfile $path]
982  } else {
983  lappend file_list $vhdlfile
984  }
985  } else {
986  Msg Warning "File $vhdlfile not found"
987  }
988  }
989  }
990 
991  return $file_list
992 }
993 
994 ## @brief Get git SHA of a subset of list file
995 #
996 # @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
997 #
998 # @return the value of the desired SHA
999 #
1000 proc GetSHA {{path ""}} {
1001  if {$path == ""} {
1002  lassign [GitRet {log --format=%h --abbrev=7 -1}] status result
1003  if {$status == 0} {
1004  return [string tolower $result]
1005  } else {
1006  Msg Error "Something went wrong while finding the latest SHA. Does the repository have a commit?"
1007  exit 1
1008  }
1009  }
1010 
1011  # Get repository top level
1012  set repo_path [lindex [Git {rev-parse --show-toplevel}] 0]
1013  set paths {}
1014  # Retrieve the list of submodules in the repository
1015  foreach f $path {
1016  set file_in_module 0
1017  if {[file exists $repo_path/.gitmodules]} {
1018  lassign [GitRet "config --file $repo_path/.gitmodules --get-regexp path"] status result
1019  if {$status == 0} {
1020  set submodules [split $result "\n"]
1021  } else {
1022  set submodules ""
1023  Msg Warning "Something went wrong while trying to find submodules: $result"
1024  }
1025 
1026  foreach mod $submodules {
1027  set module [lindex $mod 1]
1028  if {[string first "$repo_path/$module" $f] == 0} {
1029  # File is in a submodule. Append
1030  set file_in_module 1
1031  lappend paths "$repo_path/$module"
1032  break
1033  }
1034  }
1035 
1036  }
1037  if {$file_in_module == 0} {
1038  #File is not in a submodule
1039  lappend paths $f
1040  }
1041  }
1042 
1043  lassign [GitRet {log --format=%h --abbrev=7 -1} $paths] status result
1044  if {$status == 0} {
1045  return [string tolower $result]
1046  } else {
1047  Msg Error "Something went wrong while finding the latest SHA. Does the repository have a commit?"
1048  exit 1
1049  }
1050  return [string tolower $result]
1051 }
1052 
1053 ## @brief Get git version and commit hash of a subset of files
1054 #
1055 # @param[in] path list file or path containing the subset of files whose latest commit hash will be returned
1056 #
1057 # @return a list: the git SHA, the version in hex format
1058 #
1059 proc GetVer {path {force_develop 0}} {
1060  set SHA [GetSHA $path]
1061  #oldest tag containing SHA
1062  if {$SHA eq ""} {
1063  Msg CriticalWarning "Empty SHA found for ${path}. Commit to Git to resolve this warning."
1064  }
1065  set old_path [pwd]
1066  set p [lindex $path 0]
1067  if {[file isdirectory $p]} {
1068  cd $p
1069  } else {
1070  cd [file dirname $p]
1071  }
1072  set repo_path [Git {rev-parse --show-toplevel}]
1073  cd $old_path
1074 
1075  return [list [GetVerFromSHA $SHA $repo_path $force_develop] $SHA]
1076 }
1077 
1078 ## @brief Get git version and commit hash of a specific commit give the SHA
1079 #
1080 # @param[in] SHA the git SHA of the commit
1081 # @param[in] repo_path the path of the repository, this is used to open the Top/repo.conf file
1082 # @param[in] force_develop Force a tag for the develop branch (increase m)
1083 #
1084 # @return a list: the git SHA, the version in hex format
1085 #
1086 proc GetVerFromSHA {SHA repo_path {force_develop 0}} {
1087  if { $SHA eq ""} {
1088  Msg CriticalWarning "Empty SHA found"
1089  set ver "v0.0.0"
1090  } else {
1091  lassign [GitRet "tag --sort=creatordate --contain $SHA -l v*.*.* -l b*v*.*.*" ] status result
1092 
1093  if {$status == 0} {
1094  if {[regexp {^ *$} $result]} {
1095  # We do not want the most recent tag, we want the biggest value
1096  lassign [GitRet "log --oneline --pretty=\"%d\""] status2 tag_list
1097  #Msg Status "List of all tags including $SHA: $tag_list."
1098  #cleanup the list and get only the tags
1099  set pattern {tag: v\d+\.\d+\.\d+}
1100  set real_tag_list {}
1101  foreach x $tag_list {
1102  set x_untrimmed [regexp -all -inline $pattern $x]
1103  regsub "tag: " $x_untrimmed "" x_trimmed
1104  set tt [lindex $x_trimmed 0]
1105  if {![string equal $tt ""]} {
1106  lappend real_tag_list $tt
1107  #puts "<$tt>"
1108  }
1109  }
1110  #Msg Status "Cleaned up list: $real_tag_list."
1111  # Sort the tags in version order
1112  set sorted_tags [lsort -decreasing -command CompareVersions $real_tag_list]
1113 
1114  #Msg Status "Sorted Tag list: $sorted_tags"
1115  # Select the newest tag in terms of number, not time
1116  set tag [lindex $sorted_tags 0]
1117 
1118  # Msg Debug "Chosen Tag $tag"
1119  set pattern {v\d+\.\d+\.\d+}
1120  if {![regexp $pattern $tag]} {
1121  Msg CriticalWarning "No Hog version tags found in this repository."
1122  set ver v0.0.0
1123  } else {
1124 
1125  lassign [ExtractVersionFromTag $tag] M m p mr
1126  # Open repo.conf and check prefixes
1127  set repo_conf $repo_path/Top/repo.conf
1128 
1129  # Check if the develop/master scheme is used and where is the merge directed to
1130  # Default values
1131  set hotfix_prefix "hotfix/"
1132  set minor_prefix "minor_version/"
1133  set major_prefix "major_version/"
1134  set is_hotfix 0
1135  set enable_develop_branch $force_develop
1136 
1137  set branch_name [Git {rev-parse --abbrev-ref HEAD}]
1138 
1139  if {[file exists $repo_conf]} {
1140  set PROPERTIES [ReadConf $repo_conf]
1141  # [main] section
1142  if {[dict exists $PROPERTIES main]} {
1143  set mainDict [dict get $PROPERTIES main]
1144 
1145  # ENABLE_DEVELOP_ BRANCH property
1146  if {[dict exists $mainDict ENABLE_DEVELOP_BRANCH]} {
1147  set enable_develop_branch [dict get $mainDict ENABLE_DEVELOP_BRANCH]
1148  }
1149  # More properties in [main] here ...
1150 
1151  }
1152 
1153  # [prefixes] section
1154  if {[dict exists $PROPERTIES prefixes]} {
1155  set prefixDict [dict get $PROPERTIES prefixes]
1156 
1157  if {[dict exists $prefixDict HOTFIX]} {
1158  set hotfix_prefix [dict get $prefixDict HOTFIX]
1159  }
1160  if {[dict exists $prefixDict MINOR_VERSION]} {
1161  set minor_prefix [dict get $prefixDict MINOR_VERSION]
1162  }
1163  if {[dict exists $prefixDict MAJOR_VERSION]} {
1164  set major_prefix [dict get $prefixDict MAJOR_VERSION]
1165  }
1166  # More properties in [prefixes] here ...
1167  }
1168  }
1169 
1170  if {$enable_develop_branch == 1 } {
1171  if {[string match "$hotfix_prefix*" $branch_name]} {
1172  set is_hotfix 1
1173  }
1174  }
1175 
1176  if {[string match "$major_prefix*" $branch_name]} {
1177  # If major prefix is used, we increase M regardless of anything else
1178  set version_level major
1179  } elseif {[string match "$minor_prefix*" $branch_name] || ($enable_develop_branch == 1 && $is_hotfix == 0)} {
1180  # 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
1181  set version_level minor
1182  } else {
1183  # 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
1184  set version_level patch
1185  }
1186 
1187  #Let's keep this for a while, more bugs may come soon
1188  #Msg Info "******** $repo_path HF: $hotfix_prefix, M: $major_prefix, m: $minor_prefix, is_hotfix: $is_hotfix: VL: $version_level, BRANCH: $branch_name"
1189 
1190 
1191  if {$M == -1} {
1192  Msg CriticalWarning "Tag $tag does not contain a Hog compatible version in this repository."
1193  exit
1194  #set ver v0.0.0
1195  } elseif {$mr == 0} {
1196  #Msg Info "No tag contains $SHA, will use most recent tag $tag. As this is an official tag, patch will be incremented to $p."
1197  switch $version_level {
1198  minor {
1199  incr m
1200  set p 0
1201  }
1202  major {
1203  incr M
1204  set m 0
1205  set p 0
1206  }
1207  default {
1208  incr p
1209  }
1210  }
1211 
1212  } else {
1213  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."
1214  }
1215  set ver v$M.$m.$p
1216  }
1217  } else {
1218  #The tag in $result contains the current SHA
1219  set vers [split $result "\n"]
1220  set ver [lindex $vers 0]
1221  foreach v $vers {
1222  if {[regexp {^v.*$} $v]} {
1223  set un_ver $ver
1224  set ver $v
1225  break
1226  }
1227  }
1228  }
1229  } else {
1230  Msg CriticalWarning "Error while trying to find tag for $SHA"
1231  set ver "v0.0.0"
1232  }
1233  }
1234  lassign [ExtractVersionFromTag $ver] M m c mr
1235 
1236  if {$mr > -1} {
1237  # Candidate tab
1238  set M [format %02X $M]
1239  set m [format %02X $m]
1240  set c [format %04X $c]
1241 
1242  } elseif { $M > -1 } {
1243  # official tag
1244  set M [format %02X $M]
1245  set m [format %02X $m]
1246  set c [format %04X $c]
1247 
1248  } else {
1249  Msg Warning "Tag does not contain a properly formatted version: $ver"
1250  set M [format %02X 0]
1251  set m [format %02X 0]
1252  set c [format %04X 0]
1253  }
1254 
1255  return $M$m$c
1256 }
1257 
1258 ## Get the project version
1259 #
1260 # @param[in] proj_dir: The top folder of the project of which all the version must be calculated
1261 # @param[in] repo_path: The top folder of the repository
1262 # @param[in] ext_path: path for external libraries
1263 # @param[in] sim: if enabled, check the version also for the simulation files
1264 #
1265 # @return returns the project version
1266 #
1267 proc GetProjectVersion {proj_dir repo_path {ext_path ""} {sim 0}} {
1268  if { ![file exists $proj_dir] } {
1269  Msg CriticalWarning "$proj_dir not found"
1270  return -1
1271  }
1272  set old_dir [pwd]
1273  cd $proj_dir
1274 
1275  #The latest version the repository
1276  set v_last [ExtractVersionFromTag [Git {describe --abbrev=0 --match "v*"}]]
1277  lassign [GetRepoVersions $proj_dir $repo_path $ext_path $sim] sha ver
1278  if {$sha == 0} {
1279  Msg Warning "Repository is not clean"
1280  cd $old_dir
1281  return -1
1282  }
1283 
1284  #The project version
1285  set v_proj [ExtractVersionFromTag v[HexVersionToString $ver]]
1286  set comp [CompareVersions $v_proj $v_last]
1287  if {$comp == 1} {
1288  Msg Info "The specified project was modified since official version."
1289  set ret 0
1290  } else {
1291  set ret v[HexVersionToString $ver]
1292  }
1293 
1294  if {$comp == 0} {
1295  Msg Info "The specified project was modified in the latest official version $ret"
1296  } elseif {$comp == -1} {
1297  Msg Info "The specified project was modified in a past official version $ret"
1298  }
1299 
1300  cd $old_dir
1301  return $ret
1302 }
1303 
1304 
1305 ## Get custom Hog describe of a specific SHA
1306 #
1307 # @param[in] sha the git sha of the commit you want to calculate the describe of
1308 # @param[in] repo_path the main path of the repository
1309 #
1310 # @return the Hog describe of the sha or the current one if the sha is 0
1311 #
1312 proc GetHogDescribe {sha {repo_path .}} {
1313  if {$sha == 0 } {
1314  # in case the repo is dirty, we use the last committed sha and add a -dirty suffix
1315  set new_sha "[string toupper [GetSHA]]"
1316  set suffix "-dirty"
1317  } else {
1318  set new_sha [string toupper $sha]
1319  set suffix ""
1320  }
1321  set describe "v[HexVersionToString [GetVerFromSHA $new_sha $repo_path]]-$new_sha$suffix"
1322  return $describe
1323 }
1324 
1325 
1326 ## Get submodule of a specific file. Returns an empty string if the file is not in a submodule
1327 #
1328 # @param[in] path_file path of the file that whose paternity must be checked
1329 #
1330 # @return The path of the submodule. Returns an empty string if not in a submodule.
1331 #
1332 proc GetSubmodule {path_file} {
1333  set old_dir [pwd]
1334  set directory [file normalize [file dirname $path_file]]
1335  cd $directory
1336  lassign [GitRet {rev-parse --show-superproject-working-tree}] ret base
1337  if {$ret != 0} {
1338  Msg CriticalWarning "Git repository error: $base"
1339  cd $old_dir
1340  return ""
1341  }
1342  if {$base eq "" } {
1343  set submodule ""
1344  } else {
1345  lassign [GitRet {rev-parse --show-toplevel}] ret sub
1346  if {$ret != 0} {
1347  Msg CriticalWarning "Git submodule error: $sub"
1348  cd $old_dir
1349  return ""
1350  }
1351  set submodule [Relative $base $sub]
1352  }
1353 
1354  cd $old_dir
1355  return $submodule
1356 }
1357 
1358 
1359 ## Get the configuration files to create a vivado/quartus project
1360 #
1361 # @param[in] proj_dir: The project directory containing the conf file or the the tcl file
1362 #
1363 # @return[in] a list containing the full path of the hog.conf, sim.conf, pre-creation.tcl, post-creation.tcl and proj.tcl files
1364 
1365 proc GetConfFiles {proj_dir} {
1366  if {![file isdirectory $proj_dir]} {
1367  Msg Error "$proj_dir is supposed to be the top project directory"
1368  return -1
1369  }
1370  set conf_file [file normalize $proj_dir/hog.conf]
1371  set sim_file [file normalize $proj_dir/sim.conf]
1372  set pre_tcl [file normalize $proj_dir/pre-creation.tcl]
1373  set post_tcl [file normalize $proj_dir/post-creation.tcl]
1374 
1375  return [list $conf_file $sim_file $pre_tcl $post_tcl]
1376 }
1377 
1378 ## Get the versions for all libraries, submodules, etc. for a given project
1379 #
1380 # @param[in] proj_dir: The project directory containing the conf file or the the tcl file
1381 # @param[in] repo_path: top path of the repository
1382 # @param[in] ext_path: path for external libraries
1383 # @param[in] sim: if enabled, check the version also for the simulation files
1384 #
1385 # @return a list containing all the versions: global, top (hog.conf, pre and post tcl scrpts, etc.), constraints, libraries, submodules, external, ipbus xml, user ip repos
1386 #
1387 proc GetRepoVersions {proj_dir repo_path {ext_path ""} {sim 0}} {
1388  if { [catch {package require cmdline} ERROR] } {
1389  puts "$ERROR\n If you are running this script on tclsh, you can fix this by installing 'tcllib'"
1390  return 1
1391  }
1392 
1393  set old_path [pwd]
1394  set conf_files [GetConfFiles $proj_dir]
1395 
1396  # This will be the list of all the SHAs of this project, the most recent will be picked up as GLOBAL SHA
1397  set SHAs ""
1398  set versions ""
1399 
1400  # Hog submodule
1401  cd $repo_path
1402 
1403  # Append the SHA in which Hog submodule was changed, not the submodule SHA
1404  lappend SHAs [GetSHA {Hog}]
1405  lappend versions [GetVerFromSHA $SHAs $repo_path]
1406 
1407  cd "$repo_path/Hog"
1408  if {[Git {status --untracked-files=no --porcelain}] eq ""} {
1409  Msg Info "Hog submodule [pwd] clean."
1410  lassign [GetVer ./] hog_ver hog_hash
1411  } else {
1412  Msg CriticalWarning "Hog submodule [pwd] not clean, commit hash will be set to 0."
1413  set hog_hash "0000000"
1414  set hog_ver "00000000"
1415  }
1416 
1417  cd $proj_dir
1418 
1419  if {[Git {status --untracked-files=no --porcelain}] eq ""} {
1420  Msg Info "Git working directory [pwd] clean."
1421  set clean 1
1422  } else {
1423  Msg CriticalWarning "Git working directory [pwd] not clean, commit hash, and version will be set to 0."
1424  set clean 0
1425  }
1426 
1427  # Top project directory
1428  lassign [GetVer [join $conf_files]] top_ver top_hash
1429  lappend SHAs $top_hash
1430  lappend versions $top_ver
1431 
1432  # Read list files
1433  set libs ""
1434  set vers ""
1435  set hashes ""
1436  # Specify sha_mode 1 for GetHogFiles to get all the files, including the list-files themselves
1437  lassign [GetHogFiles -list_files "*.src" -sha_mode "./list/" $repo_path] src_files dummy
1438  dict for {f files} $src_files {
1439  # library names have a .src extension in values returned by GetHogFiles
1440  set name [file rootname [file tail $f]]
1441  if {[file ext $f] == ".oth"} {
1442  set name "OTHERS"
1443  }
1444  lassign [GetVer $files] ver hash
1445  # Msg Info "Found source list file $f, version: $ver commit SHA: $hash"
1446  lappend libs $name
1447  lappend versions $ver
1448  lappend vers $ver
1449  lappend hashes $hash
1450  lappend SHAs $hash
1451  }
1452 
1453  # Read constraint list files
1454  set cons_hashes ""
1455  # Specify sha_mode 1 for GetHogFiles to get all the files, including the list-files themselves
1456  lassign [GetHogFiles -list_files "*.con" -sha_mode "./list/" $repo_path] cons_files dummy
1457  dict for {f files} $cons_files {
1458  #library names have a .con extension in values returned by GetHogFiles
1459  set name [file rootname [file tail $f]]
1460  lassign [GetVer $files] ver hash
1461  #Msg Info "Found constraint list file $f, version: $ver commit SHA: $hash"
1462  if {$hash eq ""} {
1463  Msg CriticalWarning "Constraints file $f not found in Git."
1464  }
1465  lappend cons_hashes $hash
1466  lappend SHAs $hash
1467  lappend versions $ver
1468  }
1469 
1470  # Read simulation list files
1471  if {$sim == 1} {
1472  set sim_hashes ""
1473  # Specify sha_mode 1 for GetHogFiles to get all the files, including the list-files themselves
1474  lassign [GetHogFiles -list_files "*.sim" -sha_mode "./list/" $repo_path] sim_files dummy
1475  dict for {f files} $sim_files {
1476  #library names have a .sim extension in values returned by GetHogFiles
1477  set name [file rootname [file tail $f]]
1478  lassign [GetVer $files] ver hash
1479  #Msg Info "Found simulation list file $f, version: $ver commit SHA: $hash"
1480  lappend sim_hashes $hash
1481  lappend SHAs $hash
1482  lappend versions $ver
1483  }
1484  }
1485 
1486 
1487  #Of all the constraints we get the most recent
1488  if {"{}" eq $cons_hashes} {
1489  #" Fake comment for Visual Code Studio
1490  Msg CriticalWarning "No hashes found for constraints files (not in git)"
1491  set cons_hash ""
1492  } else {
1493  set cons_hash [string tolower [Git "log --format=%h -1 $cons_hashes"]]
1494  }
1495  set cons_ver [GetVerFromSHA $cons_hash $repo_path]
1496  #Msg Info "Among all the constraint list files, if more than one, the most recent version was chosen: $cons_ver commit SHA: $cons_hash"
1497 
1498  # Read external library files
1499  set ext_hashes ""
1500  set ext_files [glob -nocomplain "./list/*.ext"]
1501  set ext_names ""
1502 
1503  foreach f $ext_files {
1504  set name [file rootname [file tail $f]]
1505  set hash [GetSHA $f]
1506  #Msg Info "Found source file $f, commit SHA: $hash"
1507  lappend ext_names $name
1508  lappend ext_hashes $hash
1509  lappend SHAs $hash
1510  set ext_ver [GetVerFromSHA $hash $repo_path]
1511  lappend versions $ext_ver
1512 
1513  set fp [open $f r]
1514  set file_data [read $fp]
1515  close $fp
1516  set data [split $file_data "\n"]
1517  #Msg Info "Checking checksums of external library files in $f"
1518  foreach line $data {
1519  if {![regexp {^ *$} $line] & ![regexp {^ *\#} $line] } {
1520  #Exclude empty lines and comments
1521  set file_and_prop [regexp -all -inline {\S+} $line]
1522  set hdlfile [lindex $file_and_prop 0]
1523  set hdlfile $ext_path/$hdlfile
1524  if { [file exists $hdlfile] } {
1525  set hash [lindex $file_and_prop 1]
1526  set current_hash [Md5Sum $hdlfile]
1527  if {[string first $hash $current_hash] == -1} {
1528  Msg CriticalWarning "File $hdlfile has a wrong hash. Current checksum: $current_hash, expected: $hash"
1529  }
1530  }
1531  }
1532  }
1533  }
1534 
1535  # Ipbus XML
1536  if {[llength [glob -nocomplain ./list/*.ipb]] > 0 } {
1537  #Msg Info "Found IPbus XML list file, evaluating version and SHA of listed files..."
1538  lassign [GetHogFiles -list_files "*.ipb" -sha_mode "./list/" $repo_path] xml_files dummy
1539  lassign [GetVer [dict get $xml_files "xml.ipb"] ] xml_ver xml_hash
1540  lappend SHAs $xml_hash
1541  lappend versions $xml_ver
1542 
1543  #Msg Info "Found IPbus XML SHA: $xml_hash and version: $xml_ver."
1544 
1545  } else {
1546  Msg Info "This project does not use IPbus XMLs"
1547  set xml_ver ""
1548  set xml_hash ""
1549  }
1550 
1551  set user_ip_repos ""
1552  set user_ip_repo_hashes ""
1553  set user_ip_repo_vers ""
1554  # User IP Repository (Vivado only, hog.conf only)
1555  if {[file exists [lindex $conf_files 0]]} {
1556  set PROPERTIES [ReadConf [lindex $conf_files 0]]
1557  if {[dict exists $PROPERTIES main]} {
1558  set main [dict get $PROPERTIES main]
1559  dict for {p v} $main {
1560  if { [ string tolower $p ] == "ip_repo_paths" } {
1561  foreach repo $v {
1562  lappend user_ip_repos "$repo_path/$repo"
1563  }
1564  }
1565  }
1566  }
1567 
1568  # For each defined IP repository get hash and version if directory exists and not empty
1569  foreach repo $user_ip_repos {
1570  if {[file isdirectory $repo]} {
1571  set repo_file_list [glob -nocomplain "$repo/*"]
1572  if {[llength $repo_file_list] != 0} {
1573  lassign [GetVer $repo] ver sha
1574  lappend user_ip_repo_hashes $sha
1575  lappend user_ip_repo_vers $ver
1576  lappend versions $ver
1577  } else {
1578  Msg Warning "IP_REPO_PATHS property set to $repo in hog.conf but directory is empty."
1579  }
1580  } else {
1581  Msg Warning "IP_REPO_PATHS property set to $repo in hog.conf but directory does not exist."
1582  }
1583  }
1584  }
1585 
1586 
1587  #The global SHA and ver is the most recent among everything
1588  if {$clean == 1} {
1589  set found 0
1590  while {$found == 0} {
1591  set global_commit [Git "log --format=%h -1 --abbrev=7 $SHAs"]
1592  foreach sha $SHAs {
1593  set found 1
1594  if {![IsCommitAncestor $sha $global_commit]} {
1595  set common_child [FindCommonGitChild $global_commit $sha]
1596  if {$common_child == 0} {
1597  Msg CriticalWarning "The commit $sha is not an ancestor of the global commit $global_commit, which is OK. But $sha and $global_commit do not have any common child, which is NOT OK. This is probably do to a REBASE that is forbidden in Hog methodology as it changes git history. Hog cannot gaurantee the accuracy of the SHAs. A way to fix this is to make a commit that touches all the projects in the repositories (e.g. change the Hog version) but please do not rebase in the official branches in the future."
1598  } else {
1599  Msg Info "The commit $sha is not an ancestor of the global commit $global_commit, adding the first common child $common_child instead..."
1600  lappend SHAs $common_child
1601  }
1602  set found 0
1603 
1604  break
1605  }
1606  }
1607  }
1608  set global_version [FindNewestVersion $versions]
1609  } else {
1610  set global_commit "0000000"
1611  set global_version "00000000"
1612  }
1613 
1614  cd $old_path
1615 
1616  set top_hash [format %+07s $top_hash]
1617  set cons_hash [format %+07s $cons_hash]
1618  return [list $global_commit $global_version $hog_hash $hog_ver $top_hash $top_ver $libs $hashes $vers $cons_ver $cons_hash $ext_names $ext_hashes $xml_hash $xml_ver $user_ip_repos $user_ip_repo_hashes $user_ip_repo_vers ]
1619 }
1620 
1621 
1622 
1623 ## Convert hex version to M.m.p string
1624 #
1625 # @param[in] version the version (in 32-bit hexadecimal format 0xMMmmpppp) to be converted
1626 #
1627 # @return a string containing the version in M.m.p format
1628 #
1629 proc HexVersionToString {version} {
1630  scan [string range $version 0 1] %x M
1631  scan [string range $version 2 3] %x m
1632  scan [string range $version 4 7] %x c
1633  return "$M.$m.$c"
1634 }
1635 
1636 ## @brief Tags the repository with a new version calculated on the basis of the previous tags
1637 #
1638 # @param[in] tag a tag in the Hog format: v$M.$m.$p or b$(mr)v$M.$m.$p-$n
1639 #
1640 # @return a list containing: Major minor patch v.
1641 #
1642 proc ExtractVersionFromTag {tag} {
1643  if {[regexp {^(?:b(\d+))?v(\d+)\.(\d+).(\d+)(?:-\d+)?$} $tag -> mr M m p]} {
1644  if {$mr eq ""} {
1645  set mr 0
1646  }
1647  } else {
1648  Msg Warning "Repository tag $tag is not in a Hog-compatible format."
1649  set mr -1
1650  set M -1
1651  set m -1
1652  set p -1
1653  }
1654  return [list $M $m $p $mr]
1655 }
1656 
1657 ## @brief Read a XML list file and copy files to destination
1658 #
1659 # Additional information is provided with text separated from the file name with one or more spaces
1660 #
1661 # @param[in] proj_dir project path, path containing the ./list directory containing at least a list file with .ipb extention
1662 # @param[in] path the path the XML files are referred to in the list file
1663 # @param[in] dst the path the XML files must be copied to
1664 # @param[in] xml_version the M.m.p version to be used to replace the __VERSION__ placeholder in any of the xml files
1665 # @param[in] xml_sha the Git-SHA to be used to replace the __GIT_SHA__ placeholder in any of the xml files
1666 # @param[in] generate if set to 1, tells the function to generate the VHDL decode address files rather than check them
1667 #
1668 proc CopyIPbusXMLs {proj_dir path dst {xml_version "0.0.0"} {xml_sha "00000000"} {generate 0} } {
1669  # set ::env(PYTHONHOME) "/usr"
1670  lassign [ExecuteRet python -c "from __future__ import print_function; from sys import path;print(':'.join(path\[1:\]))"] ret msg
1671  if {$ret == 0} {
1672  set ::env(PYTHONPATH) $msg
1673  lassign [ExecuteRet gen_ipbus_addr_decode -h] ret msg
1674  if {$ret != 0} {
1675  set can_generate 0
1676  } else {
1677  set can_generate 1
1678  }
1679  } else {
1680  Msg Warning "Error while trying to run python: $msg"
1681  set can_generate 0
1682  }
1683  set dst [file normalize $dst]
1684  if {$can_generate == 0} {
1685  if {$generate == 1} {
1686  Msg Error "Cannot generate IPbus address files, IPbus executable gen_ipbus_addr_decode not found or not working: $msg"
1687  return -1
1688 
1689  } else {
1690  Msg Warning "IPbus executable gen_ipbus_addr_decode not found or not working, will not verify IPbus address tables."
1691  }
1692  }
1693 
1694  set ipb_files [glob -nocomplain $proj_dir/list/*.ipb]
1695  set n_ipb_files [llength $ipb_files]
1696  if {$n_ipb_files == 0} {
1697  Msg CriticalWarning "No files with .ipb extension found in $proj_dir/list."
1698  return
1699  }
1700  set libraries [dict create]
1701  set vhdl_dict [dict create]
1702 
1703  foreach ipb_file $ipb_files {
1704  lassign [ReadListFile {*}"$ipb_file $path"] l p fs
1705  set libraries [MergeDict $l $libraries]
1706  set vhdl_dict [MergeDict $p $vhdl_dict]
1707  }
1708 
1709  set xmlfiles [dict get $libraries "xml.ipb"]
1710 
1711 
1712  set xml_list_error 0
1713  foreach xmlfile $xmlfiles {
1714 
1715  if {[file isdirectory $xmlfile]} {
1716  Msg CriticalWarning "Directory $xmlfile listed in xml list file $list_file. Directories are not supported!"
1717  set xml_list_error 1
1718  }
1719 
1720  if {[file exists $xmlfile]} {
1721  lappend vhdls [file normalize [dict get $vhdl_dict $xmlfile] ]
1722  set xmlfile [file normalize $xmlfile]
1723  Msg Info "Copying $xmlfile to $dst and replacing place holders..."
1724  set in [open $xmlfile r]
1725  set out [open $dst/[file tail $xmlfile] w]
1726 
1727  while {[gets $in line] != -1} {
1728  set new_line [regsub {(.*)__VERSION__(.*)} $line "\\1$xml_version\\2"]
1729  set new_line2 [regsub {(.*)__GIT_SHA__(.*)} $new_line "\\1$xml_sha\\2"]
1730  puts $out $new_line2
1731  }
1732  close $in
1733  close $out
1734  lappend xmls [file tail $xmlfile]
1735  } else {
1736  Msg Warning "XML file $xmlfile not found"
1737  }
1738  }
1739  if {${xml_list_error}} {
1740  Msg Error "Invalid files added to $list_file!"
1741  }
1742 
1743  set cnt [llength $xmls]
1744  Msg Info "$cnt xml file/s copied"
1745 
1746 
1747  if {$can_generate == 1} {
1748  set old_dir [pwd]
1749  cd $dst
1750  file mkdir "address_decode"
1751  cd "address_decode"
1752 
1753  foreach x $xmls v $vhdls{
1754  if {$v ! eq ""} {
1755  set x [file normalize ../$x]
1756  if {[file exists $x]} {
1757  lassign [ExecuteRet gen_ipbus_addr_decode $x 2>&1] status log
1758  if {$status == 0} {
1759  set generated_vhdl ./ipbus_decode_[file rootname [file tail $x]].vhd
1760  if {$generate == 1} {
1761  Msg Info "Copying generated VHDL file $generated_vhdl into $v (replacing if necessary)"
1762  file copy -force -- $generated_vhdl $v
1763  } else {
1764  if {[file exists $v]} {
1765  set diff [CompareVHDL $generated_vhdl $v]
1766  if {[llength $diff] > 0} {
1767  Msg CriticalWarning "$v does not correspond to its XML $x, [expr {$n/3}] line/s differ:"
1768  Msg Status [join $diff "\n"]
1769  set diff_file [open ../diff_[file rootname [file tail $x]].txt w]
1770  puts $diff_file $diff
1771  close $diff_file
1772  } else {
1773  Msg Info "[file tail $x] and $v match."
1774  }
1775  } else {
1776  Msg Warning "VHDL address map file $v not found."
1777  }
1778  }
1779  } else {
1780  Msg Warning "Address map generation failed for [file tail $x]: $log"
1781  }
1782  } else {
1783  Msg Warning "Copied XML file $x not found."
1784  }
1785  } else {
1786  Msg Info "Skipped verification of [file tail $x] as no VHDL file was specified."
1787  }
1788  }
1789  cd ..
1790  file delete -force address_decode
1791  cd $old_dir
1792  }
1793 }
1794 
1795 ## @brief Compare two VHDL files ignoring spaces and comments
1796 #
1797 # @param[in] file1 the first file
1798 # @param[in] file2 the second file
1799 #
1800 # @ return A string with the diff of the files
1801 #
1802 proc CompareVHDL {file1 file2} {
1803  set a [open $file1 r]
1804  set b [open $file2 r]
1805 
1806  while {[gets $a line] != -1} {
1807  set line [regsub {^[\t\s]*(.*)?\s*} $line "\\1"]
1808  if {![regexp {^$} $line] & ![regexp {^--} $line] } {
1809  #Exclude empty lines and comments
1810  lappend f1 $line
1811  }
1812  }
1813 
1814  while {[gets $b line] != -1} {
1815  set line [regsub {^[\t\s]*(.*)?\s*} $line "\\1"]
1816  if {![regexp {^$} $line] & ![regexp {^--} $line] } {
1817  #Exclude empty lines and comments
1818  lappend f2 $line
1819  }
1820  }
1821 
1822  close $a
1823  close $b
1824  set diff {}
1825  foreach x $f1 y $f2 {
1826  if {$x != $y} {
1827  lappend diff "> $x\n< $y\n\n"
1828  }
1829  }
1830 
1831  return $diff
1832 }
1833 
1834 ## @brief Returns the dst path relative to base
1835 #
1836 # @param[in] base the path with respect to witch the dst path is calculated
1837 # @param[in] dst the path to be calculated with respect to base
1838 #
1839 proc Relative {base dst} {
1840  if {![string equal [file pathtype $base] [file pathtype $dst]]} {
1841  Msg CriticalWarning "Unable to compute relation for paths of different pathtypes: [file pathtype $base] vs. [file pathtype $dst], ($base vs. $dst)"
1842  return ""
1843  }
1844 
1845  set base [file normalize [file join [pwd] $base]]
1846  set dst [file normalize [file join [pwd] $dst]]
1847 
1848  set save $dst
1849  set base [file split $base]
1850  set dst [file split $dst]
1851 
1852  while {[string equal [lindex $dst 0] [lindex $base 0]]} {
1853  set dst [lrange $dst 1 end]
1854  set base [lrange $base 1 end]
1855  if {![llength $dst]} {break}
1856  }
1857 
1858  set dstlen [llength $dst]
1859  set baselen [llength $base]
1860 
1861  if {($dstlen == 0) && ($baselen == 0)} {
1862  set dst .
1863  } else {
1864  while {$baselen > 0} {
1865  set dst [linsert $dst 0 ..]
1866  incr baselen -1
1867  }
1868  set dst [eval [linsert $dst 0 file join]]
1869  }
1870 
1871  return $dst
1872 }
1873 
1874 ## @brief Returns the path of filePath relative to pathName
1875 #
1876 # @param[in] pathName the path with respect to which the returned path is calculated
1877 # @param[in] filePath the path of filePath
1878 #
1879 proc RelativeLocal {pathName filePath} {
1880  if {[string first [file normalize $pathName] [file normalize $filePath]] != -1} {
1881  return [Relative $pathName $filePath]
1882  } else {
1883  return ""
1884  }
1885 }
1886 
1887 ## @brief Prints a message with selected severity and optionally write into a log file
1888 #
1889 # @param[in] msg The message to print
1890 # @param[in] severity The severity of the message
1891 # @param[in] outFile The path of the output logfile
1892 #
1893 proc MsgAndLog {msg {severity "CriticalWarning"} {outFile ""}} {
1894  Msg $severity $msg
1895  if {$outFile != ""} {
1896  set oF [open "$outFile" a+]
1897  puts $oF $msg
1898  close $oF
1899  }
1900 }
1901 
1902 ## @ brief Returns a list of 2 dictionaries: libraries and properties
1903 # - libraries has library name as keys and a list of filenames as values
1904 # - properties has as file names as keys and a list of properties as values
1905 #
1906 # Files, libraries and properties are extracted from the current Vivado project
1907 #
1908 # @return a list of two elements. The first element is a dictionary containing all libraries. The second elements is a discretionary containing all properties
1909 proc GetProjectFiles {} {
1910 
1911  set all_filesets [get_filesets]
1912  set libraries [dict create]
1913  set simlibraries [dict create]
1914  set constraints [dict create]
1915  set properties [dict create]
1916  set consets [dict create]
1917  set srcsets [dict create]
1918  set simsets [dict create]
1919  set simulator [get_property target_simulator [current_project]]
1920  set top [get_property "top" [current_fileset]]
1921  set topfile [GetTopFile]
1922  dict lappend properties $topfile "top=$top"
1923 
1924  foreach fs $all_filesets {
1925  if {$fs == "utils_1"} {
1926  # Skipping utility fileset
1927  continue
1928  }
1929 
1930  set all_files [get_files -quiet -of_objects [get_filesets $fs]]
1931  set fs_type [get_property FILESET_TYPE [get_filesets $fs]]
1932 
1933  if {$fs_type == "BlockSrcs"} {
1934  # Vivado creates for each ip a blockset... Let's redirect to sources_1
1935  set fs "sources_1"
1936  }
1937  foreach f $all_files {
1938  # Ignore files that are part of the vivado/planahead project but would not be reflected
1939  # in list files (e.g. generated products from ip cores)
1940  set ignore 0
1941  # Generated files point to a parent composite file;
1942  # planahead does not have an IS_GENERATED property
1943  if { [IsInList "IS_GENERATED" [list_property [GetFile $f]]]} {
1944  if { [lindex [get_property IS_GENERATED [GetFile $f]] 0] != 0} {
1945  set ignore 1
1946  }
1947  }
1948  if { [IsInList "CORE_CONTAINER" [list_property [GetFile $f]]]} {
1949  if {[get_property CORE_CONTAINER [GetFile $f]] != ""} {
1950  if { [file extension $f] == ".xcix"} {
1951  set f [get_property CORE_CONTAINER [GetFile $f]]
1952  } else {
1953  set ignore 1
1954  }
1955  }
1956 
1957  }
1958  if {[IsInList "PARENT_COMPOSITE_FILE" [list_property [GetFile $f]]]} {
1959  set ignore 1
1960  }
1961 
1962  # Ignore nocattrs.dat for Versal
1963  if {[file tail $f] == "nocattrs.dat"} {
1964  set ignore 1
1965  }
1966 
1967  if {!$ignore} {
1968  if {[file extension $f] != ".coe"} {
1969  set f [file normalize $f]
1970  }
1971  lappend files $f
1972  set type [get_property FILE_TYPE [GetFile $f]]
1973  # Added a -quiet because some files (.v, .sv) don't have a library
1974  set lib [get_property -quiet LIBRARY [GetFile $f]]
1975 
1976  # Type can be complex like VHDL 2008, in that case we want the second part to be a property
1977  Msg Debug "File $f Extension [file extension $f] Type [lindex $type 0]"
1978 
1979  if {[string equal [lindex $type 0] "VHDL"] && [llength $type] == 1} {
1980  set prop "93"
1981  } elseif {[string equal [lindex $type 0] "Block"] && [string equal [lindex $type 1] "Designs"]} {
1982  set type "IP"
1983  set prop ""
1984  } elseif {[string equal $type "SystemVerilog"] && [file extension $f] != ".sv"} {
1985  set prop "SystemVerilog"
1986  } elseif {[string equal [lindex $type 0] "XDC"] && [file extension $f] != ".xdc"} {
1987  set prop "XDC"
1988  } elseif {[string equal $type "Verilog Header"] && [file extension $f] != ".vh" && [file extension $f] != ".svh"} {
1989  set prop "verilog_header"
1990  } elseif {[string equal $type "Verilog Template"] && [file extension $f] == ".v" && [file extension $f] != ".sv"} {
1991  set prop "verilog_template"
1992  } else {
1993  set type [lindex $type 0]
1994  set prop ""
1995  }
1996  #If type is "VHDL 2008" we will keep only VHDL
1997 
1998  if {![string equal $prop ""]} {
1999  dict lappend properties $f $prop
2000  }
2001  # check where the file is used and add it to prop
2002  if {[string equal $fs_type "SimulationSrcs"]} {
2003  # Simulation sources
2004  if {[string equal $type "VHDL"] } {
2005  set library "${lib}.sim"
2006  } else {
2007  set library "others.sim"
2008  }
2009 
2010  if {[IsInList $library [DictGet $simsets $fs]]==0} {
2011  dict lappend simsets $fs $library
2012  }
2013 
2014  dict lappend simlibraries $library $f
2015 
2016  } elseif {[string equal $type "VHDL"] } {
2017  # VHDL files (both 2008 and 93)
2018  if {[IsInList "${lib}.src" [DictGet $srcsets $fs]]==0} {
2019  dict lappend srcsets $fs "${lib}.src"
2020  }
2021  dict lappend libraries "${lib}.src" $f
2022  } elseif {[string first "IP" $type] != -1} {
2023  # IPs
2024  if {[IsInList "ips.src" [DictGet $srcsets $fs]]==0} {
2025  dict lappend srcsets $fs "ips.src"
2026  }
2027  dict lappend libraries "ips.src" $f
2028  Msg Debug "Appending $f to ips.src"
2029  } elseif {[string equal $fs_type "Constrs"]} {
2030  # Constraints
2031  if {[IsInList "sources.con" [DictGet $consets $fs]]==0} {
2032  dict lappend consets $fs "sources.con"
2033  }
2034  dict lappend constraints "sources.con" $f
2035  } else {
2036  # Verilog and other files
2037  if {[IsInList "others.src" [DictGet $srcsets $fs]]==0} {
2038  dict lappend srcsets $fs "others.src"
2039  }
2040  dict lappend libraries "others.src" $f
2041  Msg Debug "Appending $f to others.src"
2042  }
2043 
2044  if {[lindex [get_property -quiet used_in_synthesis [GetFile $f]] 0] == 0} {
2045  dict lappend properties $f "nosynth"
2046  }
2047  if {[lindex [get_property -quiet used_in_implementation [GetFile $f]] 0] == 0} {
2048  dict lappend properties $f "noimpl"
2049  }
2050  if {[lindex [get_property -quiet used_in_simulation [GetFile $f]] 0] == 0} {
2051  dict lappend properties $f "nosim"
2052  }
2053  if {[lindex [get_property -quiet IS_MANAGED [GetFile $f]] 0] == 0 && [file extension $f] != ".xcix" } {
2054  dict lappend properties $f "locked"
2055  }
2056  }
2057  }
2058  }
2059 
2060  dict lappend properties "Simulator" [get_property target_simulator [current_project]]
2061 
2062  return [list $libraries $properties $simlibraries $constraints $srcsets $simsets $consets]
2063 }
2064 
2065 
2066 
2067 ## @brief Extract files, libraries and properties from the project's list files
2068 #
2069 # @param[in] args The arguments are <list_path> <repository path>[options]
2070 # * list_path path to the list file directory
2071 # Options:
2072 # * -list_files <List files> the file wildcard, if not specified all Hog list files will be looked for
2073 # * -sha_mode forwarded to ReadListFile, see there for info
2074 # * -ext_path <external path> path for external libraries forwarded to ReadListFile
2075 #
2076 # @return a list of 3 dictionaries: libraries and properties
2077 # - libraries has library name as keys and a list of filenames as values
2078 # - properties has as file names as keys and a list of properties as values
2079 # - filesets has filset name as keys and the correspondent list of libraries as values (significant only for simulations)
2080 
2081 proc GetHogFiles args {
2082 
2083  if {[IsQuartus]} {
2084  load_package report
2085  if { [catch {package require cmdline} ERROR] } {
2086  puts "$ERROR\n If you are running this script on tclsh, you can fix this by installing 'tcllib'"
2087  return 1
2088  }
2089  }
2090 
2091 
2092  set parameters {
2093  {list_files.arg "" "The file wildcard, if not specified all Hog list files will be looked for."}
2094  {sha_mode "Forwarded to ReadListFile, see there for info."}
2095  {ext_path.arg "" "Path for the external libraries forwarded to ReadListFile."}
2096  }
2097  set usage "USAGE: GetHogFiles \[options\] <list path> <repository path>"
2098  if {[catch {array set options [cmdline::getoptions args $parameters $usage]}] || [llength $args] != 2 } {
2099  Msg CriticalWarning [cmdline::usage $parameters $usage]
2100  return
2101  }
2102  set list_path [lindex $args 0]
2103  set repo_path [lindex $args 1]
2104 
2105  set list_files $options(list_files)
2106  set sha_mode $options(sha_mode)
2107  set ext_path $options(ext_path)
2108 
2109 
2110  if { $sha_mode == 1 } {
2111  set sha_mode_opt "-sha_mode"
2112  } else {
2113  set sha_mode_opt ""
2114  }
2115 
2116  if { $list_files == "" } {
2117  set list_files {.src,.con,.sim,.ext}
2118  }
2119  set libraries [dict create]
2120  set properties [dict create]
2121  set list_files [glob -nocomplain -directory $list_path "*{$list_files}"]
2122  set filesets [dict create]
2123 
2124  foreach f $list_files {
2125  set ext [file extension $f]
2126  if {$ext == ".ext"} {
2127  lassign [ReadListFile {*}"$sha_mode_opt $f $ext_path"] l p fs
2128  } else {
2129  lassign [ReadListFile {*}"$sha_mode_opt $f $repo_path"] l p fs
2130  }
2131  set libraries [MergeDict $l $libraries]
2132  set properties [MergeDict $p $properties]
2133  Msg Debug "list file $f, filesets: $fs"
2134  set filesets [MergeDict $fs $filesets]
2135  Msg Debug "Merged filesets $filesets"
2136  }
2137  return [list $libraries $properties $filesets]
2138 }
2139 
2140 
2141 ## @brief Parse possible commands in the first line of Hog files (e.g. \#Vivado, \#Simulator, etc)
2142 #
2143 # @param[in] list_path path to the list file directory
2144 # @param[in] list_file the list file name
2145 #
2146 # @return a string with the first-line command
2147 # - libraries has library name as keys and a list of filenames as values
2148 # - properties has as file names as keys and a list of properties as values
2149 #
2150 proc ParseFirstLineHogFiles {list_path list_file} {
2151  set repo_path [file normalize $list_path/../../..]
2152  if {![file exists $list_path/$list_file]} {
2153  Msg Error "list file $list_path/$list_file does not exist!"
2154  return ""
2155  }
2156  set fp [open $list_path/$list_file r]
2157  set line [lindex [split [read $fp] "\n"] 0]
2158  close $fp
2159 
2160  if {[string match "#*" $line]} {
2161  return [string trim [string range $line 1 end]]
2162  } else {
2163  return ""
2164  }
2165 }
2166 
2167 
2168 ## @brief Add libraries and properties to Vivado/Quartus project
2169 #
2170 # @param[in] libraries has library name as keys and a list of filenames as values
2171 # @param[in] properties has as file names as keys and a list of properties as values
2172 #
2173 proc AddHogFiles { libraries properties filesets } {
2174  Msg Info "Adding source files to project..."
2175  Msg Debug "Filesets: $filesets"
2176  Msg Debug "Libraries: $libraries"
2177  Msg Debug "Properties: $properties"
2178 
2179  if {[IsLibero]} {
2180  set synth_conf_command "organize_tool_files -tool {SYNTHESIZE} -input_type {constraint}"
2181  set synth_conf 0
2182  set timing_conf_command "organize_tool_files -tool {VERIFYTIMING} -input_type {constraint}"
2183  set timing_conf 0
2184  set place_conf_command "organize_tool_files -tool {PLACEROUTE} -input_type {constraint}"
2185  set place_conf 0
2186  }
2187 
2188  foreach fileset [dict keys $filesets] {
2189  Msg Debug "Fileset: $fileset"
2190  # Create fileset if it doesn't exist yet
2191  if {[IsVivado]} {
2192  if {[string equal [get_filesets -quiet $fileset] ""]} {
2193  # Simulation list files supported only by Vivado
2194  create_fileset -simset $fileset
2195  # Set active when creating, by default it will be the latest simset to be created, unless is specified in the sim.conf
2196  current_fileset -simset [ get_filesets $fileset ]
2197  set simulation [get_filesets $fileset]
2198  foreach simulator [GetSimulators] {
2199  set_property -name {$simulator.compile.vhdl_syntax} -value {2008} -objects $simulation
2200  }
2201  set_property SOURCE_SET sources_1 $simulation
2202  }
2203  }
2204  # Check if ips.src is in $fileset
2205  set libs_in_fileset [DictGet $filesets $fileset]
2206  if { [IsInList "ips.src" $libs_in_fileset] } {
2207  set libs_in_fileset [moveElementToEnd $libs_in_fileset "ips.src"]
2208  }
2209 
2210  # Loop over libraries in fileset
2211  foreach lib $libs_in_fileset {
2212  Msg Debug "lib: $lib \n"
2213  set lib_files [DictGet $libraries $lib]
2214  Msg Debug "Files in $lib: $lib_files"
2215  set rootlib [file rootname [file tail $lib]]
2216  set ext [file extension $lib]
2217  Msg Debug "lib: $lib ext: $ext fileset: $fileset"
2218  # ADD NOW LISTS TO VIVADO PROJECT
2219  if {[IsXilinx]} {
2220  Msg Debug "Adding $lib to $fileset"
2221  add_files -norecurse -fileset $fileset $lib_files
2222  # Add Properties
2223  foreach f $lib_files {
2224  set file_obj [get_files -of_objects [get_filesets $fileset] [list "*$f"]]
2225  #ADDING LIBRARY
2226  if {[file extension $f] == ".vhd" || [file extension $f] == ".vhdl"} {
2227  set_property -name "library" -value $rootlib -objects $file_obj
2228  }
2229 
2230  # ADDING FILE PROPERTIES
2231  set props [DictGet $properties $f]
2232  if {[file extension $f] == ".vhd" || [file extension $f] == ".vhdl"} {
2233  # VHDL 93 property
2234  if {[lsearch -inline -regexp $props "93"] < 0} {
2235  # ISE does not support vhdl2008
2236  if {[IsVivado]} {
2237  set_property -name "file_type" -value "VHDL 2008" -objects $file_obj
2238  }
2239  } else {
2240  Msg Debug "Filetype is VHDL 93 for $f"
2241  }
2242  }
2243 
2244  # SystemVerilog property
2245  if {[lsearch -inline -regexp $props "SystemVerilog"] > 0 } {
2246  # ISE does not support SystemVerilog
2247  if {[IsVivado]} {
2248  set_property -name "file_type" -value "SystemVerilog" -objects $file_obj
2249  Msg Debug "Filetype is SystemVerilog for $f"
2250  } else {
2251  Msg Warning "Xilinx PlanAhead/ISE does not support SystemVerilog. Property not set for $f"
2252  }
2253  }
2254 
2255  # Top synthesis module
2256  set top [lindex [regexp -inline {top\s*=\s*(.+?)\y.*} $props] 1]
2257  if { $top != "" } {
2258  Msg Info "Setting $top as top module for file set $fileset..."
2259  set globalSettings::synth_top_module $top
2260  }
2261 
2262 
2263  # Verilog headers
2264  if {[lsearch -inline -regexp $props "verilog_header"] >= 0} {
2265  Msg Debug "Setting verilog header type for $f..."
2266  set_property file_type {Verilog Header} [get_files $f]
2267  } elseif {[lsearch -inline -regexp $props "verilog_template"] >= 0} {
2268  # Verilog Template
2269  Msg Debug "Setting verilog template type for $f..."
2270  set_property file_type {Verilog Template} [get_files $f]
2271  } elseif {[lsearch -inline -regexp $props "verilog"] >= 0} {
2272  # Normal Verilog
2273  Msg Debug "Setting verilog type for $f..."
2274  set_property file_type {Verilog} [get_files $f]
2275  }
2276 
2277  # Not used in synthesis
2278  if {[lsearch -inline -regexp $props "nosynth"] >= 0} {
2279  Msg Debug "Setting not used in synthesis for $f..."
2280  set_property -name "used_in_synthesis" -value "false" -objects $file_obj
2281  }
2282 
2283  # Not used in implementation
2284  if {[lsearch -inline -regexp $props "noimpl"] >= 0} {
2285  Msg Debug "Setting not used in implementation for $f..."
2286  set_property -name "used_in_implementation" -value "false" -objects $file_obj
2287  }
2288 
2289  # Not used in simulation
2290  if {[lsearch -inline -regexp $props "nosim"] >= 0} {
2291  Msg Debug "Setting not used in simulation for $f..."
2292  set_property -name "used_in_simulation" -value "false" -objects $file_obj
2293  }
2294 
2295  ## Simulation properties
2296  # Top simulation module
2297  set top_sim [lindex [regexp -inline {topsim\s*=\s*(.+?)\y.*} $props] 1]
2298  if { $top_sim != "" } {
2299  Msg Warning "Setting the simulation top module from simulation list files is now deprecated. Please set this property in the sim.conf file, by adding the following line under the \[$fileset\] section.\ntop=$top_sim"
2300  }
2301 
2302  # Simulation runtime
2303  set sim_runtime [lindex [regexp -inline {runtime\s*=\s*(.+?)\y.*} $props] 1]
2304  if { $sim_runtime != "" } {
2305  Msg Warning "Setting the simulation runtime from simulation list files is now deprecated. Please set this property in the sim.conf file, by adding the following line under the \[$fileset\] section.\n<simulator_name>.simulate.runtime=$sim_runtime"
2306  # set_property -name {xsim.simulate.runtime} -value $sim_runtime -objects [get_filesets $fileset]
2307  # foreach simulator [GetSimulators] {
2308  # set_property $simulator.simulate.runtime $sim_runtime [get_filesets $fileset]
2309  # }
2310  }
2311 
2312  # Wave do file
2313  if {[lsearch -inline -regexp $props "wavefile"] >= 0} {
2314  Msg Warning "Setting a wave do file from simulation list files is now deprecated. Set this property in the sim.conf file, by adding the following line under the \[$fileset\] section.\n<simulator_name>.simulate.custom_wave_do=[file tail $f]"
2315 
2316  # Msg Debug "Setting $f as wave do file for simulation file set $fileset..."
2317 
2318  # # check if file exists...
2319  # if {[file exists $f]} {
2320  # foreach simulator [GetSimulators] {
2321  # set_property "$simulator.simulate.custom_wave_do" [file tail $f] [get_filesets $fileset]
2322  # }
2323  # } else {
2324  # Msg Warning "File $f was not found."
2325  # }
2326  }
2327 
2328  #Do file
2329  if {[lsearch -inline -regexp $props "dofile"] >= 0} {
2330  Msg Warning "Setting a custom do file from simulation list files is now deprecated. Set this property in the sim.conf file, by adding the following line under the \[$fileset\] section.\n<simulator_name>.simulate.custom_do=[file tail $f]"
2331  # Msg Debug "Setting $f as do file for simulation file set $fileset..."
2332 
2333  # if {[file exists $f]} {
2334  # foreach simulator [GetSimulators] {
2335  # set_property "$simulator.simulate.custom_udo" [file tail $f] [get_filesets $fileset]
2336  # }
2337  # } else {
2338  # Msg Warning "File $f was not found."
2339  # }
2340  }
2341 
2342  # Lock the IP
2343  if {[lsearch -inline -regexp $props "locked"] >= 0 && $ext == ".ip"} {
2344  Msg Info "Locking IP $f..."
2345  set_property IS_MANAGED 0 [get_files $f]
2346  }
2347 
2348  # Generating Target for BD File
2349  if {[file extension $f] == ".bd"} {
2350  Msg Info "Generating Target for [file tail $f], please remember to commit the (possible) changed file."
2351  generate_target all [get_files $f]
2352  }
2353 
2354 
2355  # Tcl
2356  if {[file extension $f] == ".tcl" && $ext != ".con"} {
2357  if { [lsearch -inline -regexp $props "source"] >= 0} {
2358  Msg Info "Sourcing Tcl script $f, and setting it not used in synthesis, implementation and simulation..."
2359  source $f
2360  set_property -name "used_in_synthesis" -value "false" -objects $file_obj
2361  set_property -name "used_in_implementation" -value "false" -objects $file_obj
2362  set_property -name "used_in_simulation" -value "false" -objects $file_obj
2363  }
2364  }
2365  }
2366  Msg Info "[llength $lib_files] file/s added to library $rootlib..."
2367  } elseif {[IsQuartus] } {
2368  #QUARTUS ONLY
2369  if { $ext == ".sim"} {
2370  Msg Warning "Simulation files not supported in Quartus Prime mode... Skipping $lib"
2371  } else {
2372  if {! [is_project_open] } {
2373  Msg Error "Project is closed"
2374  }
2375  foreach cur_file $lib_files {
2376  set file_type [FindFileType $cur_file]
2377 
2378  #ADDING FILE PROPERTIES
2379  set props [DictGet $properties $cur_file]
2380 
2381  # Top synthesis module
2382  set top [lindex [regexp -inline {top\s*=\s*(.+?)\y.*} $props] 1]
2383  if { $top != "" } {
2384  Msg Info "Setting $top as top module for file set $fileset..."
2385  set globalSettings::synth_top_module $top
2386  }
2387  # VHDL file properties
2388  if {[string first "VHDL" $file_type] != -1 } {
2389  if {[string first "1987" $props] != -1 } {
2390  set hdl_version "VHDL_1987"
2391  } elseif {[string first "1993" $props] != -1 } {
2392  set hdl_version "VHDL_1993"
2393  } elseif {[string first "2008" $props] != -1 } {
2394  set hdl_version "VHDL_2008"
2395  } else {
2396  set hdl_version "default"
2397  }
2398  if { $hdl_version == "default" } {
2399  set_global_assignment -name $file_type $cur_file -library $rootlib
2400  } else {
2401  set_global_assignment -name $file_type $cur_file -hdl_version $hdl_version -library $rootlib
2402  }
2403  } elseif {[string first "SYSTEMVERILOG" $file_type] != -1 } {
2404  # SystemVerilog file properties
2405  if {[string first "2005" $props] != -1 } {
2406  set hdl_version "systemverilog_2005"
2407  } elseif {[string first "2009" $props] != -1 } {
2408  set hdl_version "systemverilog_2009"
2409  } else {
2410  set hdl_version "default"
2411  }
2412  if { $hdl_version == "default" } {
2413  set_global_assignment -name $file_type $cur_file
2414  } else {
2415  set_global_assignment -name $file_type $cur_file -hdl_version $hdl_version
2416  }
2417  } elseif {[string first "VERILOG" $file_type] != -1 } {
2418  # Verilog file properties
2419  if {[string first "1995" $props] != -1 } {
2420  set hdl_version "verilog_1995"
2421  } elseif {[string first "2001" $props] != -1 } {
2422  set hdl_version "verilog_2001"
2423  } else {
2424  set hdl_version "default"
2425  }
2426  if { $hdl_version == "default" } {
2427  set_global_assignment -name $file_type $cur_file
2428  } else {
2429  set_global_assignment -name $file_type $cur_file -hdl_version $hdl_version
2430  }
2431  } elseif {[string first "SOURCE" $file_type] != -1 || [string first "COMMAND_MACRO" $file_type] != -1 } {
2432  set_global_assignment -name $file_type $cur_file
2433  if { $ext == ".con"} {
2434  source $cur_file
2435  } elseif { $ext == ".src"} {
2436  # If this is a Platform Designer file then generate the system
2437  if {[string first "qsys" $props] != -1 } {
2438  # remove qsys from options since we used it
2439  set emptyString ""
2440  regsub -all {\{||qsys||\}} $props $emptyString props
2441 
2442  set qsysPath [file dirname $cur_file]
2443  set qsysName "[file rootname [file tail $cur_file]].qsys"
2444  set qsysFile "$qsysPath/$qsysName"
2445  set qsysLogFile "$qsysPath/[file rootname [file tail $cur_file]].qsys-script.log"
2446 
2447  set qsys_rootdir ""
2448  if {! [info exists ::env(QSYS_ROOTDIR)] } {
2449  if {[info exists ::env(QUARTUS_ROOTDIR)] } {
2450  set qsys_rootdir "$::env(QUARTUS_ROOTDIR)/sopc_builder/bin"
2451  Msg Warning "The QSYS_ROOTDIR environment variable is not set! I will use $qsys_rootdir"
2452  } else {
2453  Msg CriticalWarning "The QUARTUS_ROOTDIR environment variable is not set! Assuming all quartus executables are contained in your PATH!"
2454  }
2455  } else {
2456  set qsys_rootdir $::env(QSYS_ROOTDIR)
2457  }
2458 
2459  set cmd "$qsys_rootdir/qsys-script"
2460  set cmd_options " --script=$cur_file"
2461  if {![catch {"exec $cmd -version"}] || [lindex $::errorCode 0] eq "NONE"} {
2462  Msg Info "Executing: $cmd $cmd_options"
2463  Msg Info "Saving logfile in: $qsysLogFile"
2464  if { [ catch {eval exec -ignorestderr "$cmd $cmd_options >>& $qsysLogFile"} ret opt ]} {
2465  set makeRet [lindex [dict get $opt -errorcode] end]
2466  Msg CriticalWarning "$cmd returned with $makeRet"
2467  }
2468  } else {
2469  Msg Error " Could not execute command $cmd"
2470  exit 1
2471  }
2472  # Check the system is generated correctly and move file to correct directory
2473  if { [file exists $qsysName] != 0} {
2474  file rename -force $qsysName $qsysFile
2475  # Write checksum to file
2476  set qsysMd5Sum [Md5Sum $qsysFile]
2477  # open file for writing
2478  set fileDir [file normalize "./hogTmp"]
2479  set fileName "$fileDir/.hogQsys.md5"
2480  if {![file exists $fileDir]} {
2481  file mkdir $fileDir
2482  }
2483  set hogQsysFile [open $fileName "a"]
2484  set fileEntry "$qsysFile\t$qsysMd5Sum"
2485  puts $hogQsysFile $fileEntry
2486  close $hogQsysFile
2487  } else {
2488  Msg ERROR "Error while moving the generated qsys file to final location: $qsysName not found!";
2489  }
2490  if { [file exists $qsysFile] != 0} {
2491  if {[string first "noadd" $props] == -1} {
2492  set qsysFileType [FindFileType $qsysFile]
2493  set_global_assignment -name $qsysFileType $qsysFile
2494  } else {
2495  regsub -all {noadd} $props $emptyString props
2496  }
2497  if {[string first "nogenerate" $props] == -1} {
2498  GenerateQsysSystem $qsysFile $props
2499  }
2500 
2501  } else {
2502  Msg ERROR "Error while generating ip variations from qsys: $qsysFile not found!";
2503  }
2504  }
2505  }
2506  } elseif { [string first "QSYS" $file_type] != -1 } {
2507  set emptyString ""
2508  regsub -all {\{||\}} $props $emptyString props
2509  if {[string first "noadd" $props] == -1} {
2510  set_global_assignment -name $file_type $cur_file
2511  } else {
2512  regsub -all {noadd} $props $emptyString props
2513  }
2514 
2515  #Generate IPs
2516  if {[string first "nogenerate" $props] == -1} {
2517  GenerateQsysSystem $cur_file $props
2518  }
2519  } else {
2520  set_global_assignment -name $file_type $cur_file -library $rootlib
2521  }
2522  }
2523  }
2524  } elseif {[IsLibero] } {
2525  if {$ext == ".con"} {
2526  set vld_exts {.sdc .pin .dcf .gcf .pdc .ndc .fdc .crt .vcd }
2527  foreach con_file $lib_files {
2528  # Check for valid constrain files
2529  set con_ext [file extension $con_file]
2530  if {[IsInList [file extension $con_file] $vld_exts ]} {
2531  set option [string map {. -} $con_ext]
2532  set option [string map {fdc net_fdc} $option]
2533  set option [string map {pdc io_pdc} $option]
2534  create_links -convert_EDN_to_HDL 0 -library {work} $option $con_file
2535 
2536  set props [DictGet $properties $con_file]
2537 
2538  if {$con_ext == ".sdc"} {
2539  if { [lsearch $props "notiming"] >= 0 } {
2540  Msg Info "Excluding $con_file from timing verification..."
2541  } else {
2542  Msg Info "Adding $con_file to time verification"
2543  append timing_conf_command " -file $con_file"
2544  set timing_conf 1
2545  }
2546 
2547  if { [lsearch $props "nosynth"] >= 0 } {
2548  Msg Info "Excluding $con_file from synthesis..."
2549  } else {
2550  Msg Info "Adding $con_file to synthesis"
2551  append synth_conf_command " -file $con_file"
2552  set synth_conf 1
2553  }
2554  }
2555 
2556  if {$con_ext == ".pdc" || $con_ext == ".sdc"} {
2557  if { [lsearch $props "noplace"] >= 0 } {
2558  Msg Info "Excluding $con_file from place and route..."
2559  } else {
2560  Msg Info "Adding $con_file to place and route"
2561  append place_conf_command " -file $con_file"
2562  set place_conf 1
2563  }
2564  }
2565 
2566  } else {
2567  Msg CriticalWarning "Constraint file $con_file does not have a valid extension. Allowed extensions are: \n $vld_exts"
2568  }
2569  }
2570  } elseif {$ext == ".src"} {
2571  foreach f $lib_files {
2572  Msg Debug "Adding source $f to library $rootlib..."
2573  create_links -library $rootlib -hdl_source $f
2574  }
2575  }
2576  build_design_hierarchy
2577  foreach cur_file $lib_files {
2578  set file_type [FindFileType $cur_file]
2579 
2580  #ADDING FILE PROPERTIES
2581  set props [DictGet $properties $cur_file]
2582 
2583  # Top synthesis module
2584  set top [lindex [regexp -inline {top\s*=\s*(.+?)\y.*} $props] 1]
2585  if { $top != "" } {
2586  Msg Info "Setting $top as top module for file set $rootlib..."
2587  set globalSettings::synth_top_module "${top}::$rootlib"
2588  }
2589  }
2590  # Closing IDE if cascade
2591  }
2592  # Closing library loop
2593  }
2594  # Closing fileset loop
2595  }
2596 
2597  if {[IsVivado]} {
2598  if {[DictGet $filesets "sim_1"] == ""} {
2599  delete_fileset -quiet [get_filesets -quiet "sim_1" ]
2600  }
2601  }
2602 
2603  # Add constraints to workflow in Libero
2604  if {[IsLibero]} {
2605  if {$synth_conf == 1} {
2606  Msg Info $synth_conf_command
2607  eval $synth_conf_command
2608  }
2609  if {$timing_conf == 1} {
2610  Msg Info $timing_conf_command
2611  eval $timing_conf_command
2612  }
2613  if {$place_conf == 1} {
2614  Msg Info $place_conf_command
2615  eval $place_conf_command
2616  }
2617  }
2618 
2619 
2620 }
2621 
2622 # @brief Function searching for extra IP/BD files added at creation time using user scripts, and writing the list in
2623 # Project/proj/.hog/extra.files, with the correspondent md5sum
2624 #
2625 # @param[in] libraries The Hog libraries
2626 proc CheckExtraFiles {libraries} {
2627  ### CHECK NOW FOR IP OUTSIDE OF LIST FILE (Vivado only!)
2628  if {[IsVivado]} {
2629  lassign [GetProjectFiles] prjLibraries prjProperties prjSimLibraries prjConstraints
2630  set prjLibraries [MergeDict $prjLibraries $prjSimLibraries]
2631  set prjLibraries [MergeDict $prjLibraries $prjConstraints]
2632  set prj_dir [get_property DIRECTORY [current_project]]
2633  file mkdir "$prj_dir/.hog"
2634  set extra_file_name "$prj_dir/.hog/extra.files"
2635  set new_extra_file [open $extra_file_name "w"]
2636 
2637  dict for {prjLib prjFiles} $prjLibraries {
2638  foreach prjFile $prjFiles {
2639  if {[file extension $prjFile] == ".xcix"} {
2640  Msg Warning "IP $prjFile is packed in a .xcix core container. This files are not suitable for version control systems. We recommend to use .xci files instead."
2641  continue
2642  }
2643  if {[file extension $prjFile] == ".xci" && [get_property CORE_CONTAINER [get_files $prjFile]] != ""} {
2644  Msg Info "$prjFile is a virtual IP file in a core container. Ignoring it..."
2645  continue
2646  }
2647 
2648  if {[IsInList $prjFile [DictGet $libraries $prjLib]]==0} {
2649  if { [file extension $prjFile] == ".bd"} {
2650  # Generating BD products to save md5sum of already modified BD
2651  Msg Info "Generating targets of $prjFile..."
2652  generate_target all [get_files $prjFile]
2653  }
2654  puts $new_extra_file "$prjFile [Md5Sum $prjFile]"
2655  Msg Info "$prjFile (lib: $prjLib) has been generated by an external script. Adding to $extra_file_name..."
2656  }
2657  }
2658  }
2659  close $new_extra_file
2660  }
2661 }
2662 
2663 ## @brief Function used to read the list of files generated at creation time by tcl scripts in Project/proj/.hog/extra.files
2664 #
2665 # @param[in] extra_file_name the path to the extra.files file
2666 # @returns a dictionary with the full name of the files as key and a SHA as value
2667 #
2668 proc ReadExtraFileList { extra_file_name } {
2669  set extra_file_dict [dict create]
2670  if {[file exists $extra_file_name]} {
2671  set file [open $extra_file_name "r"]
2672  set file_data [read $file]
2673  close $file
2674 
2675  set data [split $file_data "\n"]
2676  foreach line $data {
2677  if {![regexp {^ *$} $line] & ![regexp {^ *\#} $line] } {
2678  set ip_and_md5 [regexp -all -inline {\S+} $line]
2679  dict lappend extra_file_dict "[lindex $ip_and_md5 0]" "[lindex $ip_and_md5 1]"
2680  }
2681  }
2682  }
2683  return $extra_file_dict
2684 }
2685 
2686 ## @brief Function used to generate a qsys system from a .qsys file.
2687 # The procedure adds the generated IPs to the project.
2688 #
2689 # @param[in] qsysFile the Intel Platform Designed file (.qsys), containing the system to be generated
2690 # @param[in] commandOpts the command options to be used during system generation as they are in qsys-generate options
2691 #
2692 proc GenerateQsysSystem {qsysFile commandOpts} {
2693  if { [file exists $qsysFile] != 0} {
2694  set qsysPath [file dirname $qsysFile]
2695  set qsysName [file rootname [file tail $qsysFile] ]
2696  set qsysIPDir "$qsysPath/$qsysName"
2697  set qsysLogFile "$qsysPath/$qsysName.qsys-generate.log"
2698 
2699  set qsys_rootdir ""
2700  if {! [info exists ::env(QSYS_ROOTDIR)] } {
2701  if {[info exists ::env(QUARTUS_ROOTDIR)] } {
2702  set qsys_rootdir "$::env(QUARTUS_ROOTDIR)/sopc_builder/bin"
2703  Msg Warning "The QSYS_ROOTDIR environment variable is not set! I will use $qsys_rootdir"
2704  } else {
2705  Msg CriticalWarning "The QUARTUS_ROOTDIR environment variable is not set! Assuming all quartus executables are contained in your PATH!"
2706  }
2707  } else {
2708  set qsys_rootdir $::env(QSYS_ROOTDIR)
2709  }
2710 
2711  set cmd "$qsys_rootdir/qsys-generate"
2712  set cmd_options "$qsysFile --output-directory=$qsysIPDir $commandOpts"
2713  if {![catch {"exec $cmd -version"}] || [lindex $::errorCode 0] eq "NONE"} {
2714  Msg Info "Executing: $cmd $cmd_options"
2715  Msg Info "Saving logfile in: $qsysLogFile"
2716  if {[ catch {eval exec -ignorestderr "$cmd $cmd_options >>& $qsysLogFile"} ret opt]} {
2717  set makeRet [lindex [dict get $opt -errorcode] end]
2718  Msg CriticalWarning "$cmd returned with $makeRet"
2719  }
2720  } else {
2721  Msg Error " Could not execute command $cmd"
2722  exit 1
2723  }
2724  #Add generated IPs to project
2725  set qsysIPFileList [concat [glob -nocomplain -directory $qsysIPDir -types f *.ip *.qip ] [glob -nocomplain -directory "$qsysIPDir/synthesis" -types f *.ip *.qip *.vhd *.vhdl ] ]
2726  foreach qsysIPFile $qsysIPFileList {
2727  if { [file exists $qsysIPFile] != 0} {
2728  set qsysIPFileType [FindFileType $qsysIPFile]
2729  set_global_assignment -name $qsysIPFileType $qsysIPFile
2730  # Write checksum to file
2731  set IpMd5Sum [Md5Sum $qsysIPFile]
2732  # open file for writing
2733  set fileDir [file normalize "./hogTmp"]
2734  set fileName "$fileDir/.hogQsys.md5"
2735  if {![file exists $fileDir]} {
2736  file mkdir $fileDir
2737  }
2738  set hogQsysFile [open $fileName "a"]
2739  set fileEntry "$qsysIPFile\t$IpMd5Sum"
2740  puts $hogQsysFile $fileEntry
2741  close $hogQsysFile
2742  }
2743  }
2744  } else {
2745  Msg ERROR "Error while generating ip variations from qsys: $qsysFile not found!"
2746  }
2747 }
2748 
2749 ## @brief Forces all the Vivado runs to look up to date, useful before write bitstream
2750 #
2751 proc ForceUpToDate {} {
2752  Msg Info "Forcing all the runs to look up to date..."
2753  set runs [get_runs]
2754  foreach r $runs {
2755  Msg Info "Forcing $r..."
2756  set_property needs_refresh false [get_runs $r]
2757  }
2758 }
2759 
2760 
2761 ## @brief Copy IP generated files from/to a remote o local direcotry (possibly EOS)
2762 #
2763 # @param[in] what_to_do: can be "push", if you want to copy the local IP synth result to the remote direcyory or "pull" if you want to copy the files from thre remote directory to your local repository
2764 # @param[in] xci_file: the .xci file of the IP you want to handle
2765 # @param[in] ip_path: the path of the directory you want the IP to be saved (possibly EOS)
2766 # @param[in] repo_path: the main path of your repository
2767 # @param[in] gen_dir: the directory where generated files are placed, by default the files are placed in the same folder as the .xci
2768 # @param[in] force: if not set to 0, will copy the IP to the remote directory even if it is already present
2769 #
2770 proc HandleIP {what_to_do xci_file ip_path repo_path {gen_dir "."} {force 0}} {
2771  if {!($what_to_do eq "push") && !($what_to_do eq "pull")} {
2772  Msg Error "You must specify push or pull as first argument."
2773  }
2774 
2775  if { [catch {package require tar} TARPACKAGE]} {
2776  Msg CriticalWarning "Cannot find package tar. You can fix this by installing package \"tcllib\""
2777  return -1
2778  }
2779 
2780  set old_path [pwd]
2781 
2782  cd $repo_path
2783 
2784 
2785  if {[string first "/eos/" $ip_path] == 0} {
2786  # IP Path is on EOS
2787  set on_eos 1
2788  } else {
2789  set on_eos 0
2790  }
2791 
2792  if {$on_eos == 1} {
2793  lassign [eos "ls $ip_path"] ret result
2794  if {$ret != 0} {
2795  Msg CriticalWarning "Could not run ls for for EOS path: $ip_path (error: $result). Either the drectory does not exist or there are (temporary) problem with EOS."
2796  cd $old_path
2797  return -1
2798  } else {
2799  Msg Info "IP remote directory path, on EOS, is set to: $ip_path"
2800  }
2801 
2802  } else {
2803  file mkdir $ip_path
2804  }
2805 
2806  if {!([file exists $xci_file])} {
2807  Msg CriticalWarning "Could not find $xci_file."
2808  cd $old_path
2809  return -1
2810  }
2811 
2812 
2813  set xci_path [file dirname $xci_file]
2814  set xci_name [file tail $xci_file]
2815  set xci_ip_name [file rootname [file tail $xci_file]]
2816  set xci_dir_name [file tail $xci_path]
2817  set gen_path $gen_dir
2818 
2819  set hash [Md5Sum $xci_file]
2820  set file_name $xci_name\_$hash
2821 
2822  Msg Info "Preparing to $what_to_do IP: $xci_name..."
2823 
2824  if {$what_to_do eq "push"} {
2825  set will_copy 0
2826  set will_remove 0
2827  if {$on_eos == 1} {
2828  lassign [eos "ls $ip_path/$file_name.tar"] ret result
2829  if {$ret != 0} {
2830  set will_copy 1
2831  } else {
2832  if {$force == 0 } {
2833  Msg Info "IP already in the EOS repository, will not copy..."
2834  } else {
2835  Msg Info "IP already in the EOS repository, will forcefully replace..."
2836  set will_copy 1
2837  set will_remove 1
2838  }
2839  }
2840  } else {
2841  if {[file exists "$ip_path/$file_name.tar"]} {
2842  if {$force == 0 } {
2843  Msg Info "IP already in the local repository, will not copy..."
2844  } else {
2845  Msg Info "IP already in the local repository, will forcefully replace..."
2846  set will_copy 1
2847  set will_remove 1
2848  }
2849  } else {
2850  set will_copy 1
2851  }
2852  }
2853 
2854  if {$will_copy == 1} {
2855  # Check if there are files in the .gen directory first and copy them into the right place
2856  Msg Info "Looking for generated files in $gen_path..."
2857  set ip_gen_files [glob -nocomplain $gen_path/*]
2858 
2859  #here we should remove the .xci file from the list if it's there
2860 
2861  if {[llength $ip_gen_files] > 0} {
2862  Msg Info "Found some IP synthesised files matching $xci_ip_name"
2863  if {$will_remove == 1} {
2864  Msg Info "Removing old synthesised directory $ip_path/$file_name.tar..."
2865  if {$on_eos == 1} {
2866  eos "rm -rf $ip_path/$file_name.tar" 5
2867  } else {
2868  file delete -force "$ip_path/$file_name.tar"
2869  }
2870  }
2871 
2872  Msg Info "Creating local archive with IP generated files..."
2873  set first_file 0
2874  foreach f $ip_gen_files {
2875  if {$first_file == 0} {
2876  ::tar::create $file_name.tar "[Relative [file normalize $repo_path] $f]"
2877  set first_file 1
2878  } else {
2879  ::tar::add $file_name.tar "[Relative [file normalize $repo_path] $f]"
2880  }
2881  }
2882 
2883  Msg Info "Copying IP generated files for $xci_name..."
2884  if {$on_eos == 1} {
2885  lassign [ExecuteRet xrdcp -f -s $file_name.tar $::env(EOS_MGM_URL)//$ip_path/] ret msg
2886  if {$ret != 0} {
2887  Msg CriticalWarning "Something went wrong when copying the IP files to EOS. Error message: $msg"
2888  }
2889  } else {
2890  Copy "$file_name.tar" "$ip_path/"
2891  }
2892  Msg Info "Removing local archive"
2893  file delete $file_name.tar
2894 
2895  } else {
2896  Msg Warning "Could not find synthesized files matching $gen_path/$file_name*"
2897  }
2898  }
2899  } elseif {$what_to_do eq "pull"} {
2900  if {$on_eos == 1} {
2901  lassign [eos "ls $ip_path/$file_name.tar"] ret result
2902  if {$ret != 0} {
2903  Msg Info "Nothing for $xci_name was found in the EOS repository, cannot pull."
2904  cd $old_path
2905  return -1
2906 
2907  } else {
2908  set remote_tar "$::env(EOS_MGM_URL)//$ip_path/$file_name.tar"
2909  Msg Info "IP $xci_name found in the repository $remote_tar, copying it locally to $repo_path..."
2910 
2911  lassign [ExecuteRet xrdcp -f -r -s $remote_tar $repo_path] ret msg
2912  if {$ret != 0} {
2913  Msg CriticalWarning "Something went wrong when copying the IP files to EOS. Error message: $msg"
2914  }
2915  }
2916  } else {
2917  if {[file exists "$ip_path/$file_name.tar"]} {
2918  Msg Info "IP $xci_name found in local repository $ip_path/$file_name.tar, copying it locally to $repo_path..."
2919  Copy $ip_path/$file_name.tar $repo_path
2920 
2921  } else {
2922  Msg Info "Nothing for $xci_name was found in the local IP repository, cannot pull."
2923  cd $old_path
2924  return -1
2925  }
2926 
2927  }
2928 
2929  if {[file exists $file_name.tar]} {
2930  remove_files $xci_file
2931  Msg Info "Extracting IP files from archive to $repo_path..."
2932  ::tar::untar $file_name.tar -dir $repo_path -noperms
2933  Msg Info "Removing local archive"
2934  file delete $file_name.tar
2935  add_files -norecurse -fileset sources_1 $xci_file
2936  }
2937  }
2938  cd $old_path
2939  return 0
2940 }
2941 
2942 ## @brief Evaluates the md5 sum of a file
2943 #
2944 # @param[in] file_name: the name of the file of which you want to evaluate the md5 checksum
2945 proc Md5Sum {file_name} {
2946  if {!([file exists $file_name])} {
2947  Msg Warning "Could not find $file_name."
2948  set file_hash -1
2949  }
2950  if {[catch {package require md5 2.0.7} result]} {
2951  Msg Warning "Tcl package md5 version 2.0.7 not found ($result), will use command line..."
2952  set hash [lindex [Execute md5sum $file_name] 0]
2953  } else {
2954  set file_hash [string tolower [md5::md5 -hex -file $file_name]]
2955  }
2956 }
2957 
2958 
2959 ## @brief Checks that "ref" in .gitlab-ci.yml actually matches the hog.yml file in the
2960 #
2961 # @param[in] repo_path path to the repository root
2962 # @param[in] allow_failure if true throws CriticalWarnings instead of Errors
2963 #
2964 proc CheckYmlRef {repo_path allow_failure} {
2965 
2966  if {$allow_failure} {
2967  set MSG_TYPE CriticalWarning
2968  } else {
2969  set MSG_TYPE Error
2970  }
2971 
2972  if { [catch {package require yaml 0.3.3} YAMLPACKAGE]} {
2973  Msg CriticalWarning "Cannot find package YAML, skipping consistency check of \"ref\" in gilab-ci.yaml file.\n Error message: $YAMLPACKAGE
2974  You can fix this by installing package \"tcllib\""
2975  return
2976  }
2977 
2978  set thisPath [pwd]
2979 
2980  # Go to repository path
2981  cd "$repo_path"
2982  if {[file exists .gitlab-ci.yml]} {
2983  #get .gitlab-ci ref
2984  set YML_REF ""
2985  set YML_NAME ""
2986  if { [file exists .gitlab-ci.yml] } {
2987  set fp [open ".gitlab-ci.yml" r]
2988  set file_data [read $fp]
2989  close $fp
2990  } else {
2991  Msg $MSG_TYPE "Cannot open file .gitlab-ci.yml"
2992  cd $thisPath
2993  return
2994  }
2995  set file_data "\n$file_data\n\n"
2996 
2997  if { [catch {::yaml::yaml2dict -stream $file_data} yamlDict]} {
2998  Msg $MSG_TYPE "Parsing $repo_path/.gitlab-ci.yml failed. To fix this, check that yaml syntax is respected, remember not to use tabs."
2999  cd $thisPath
3000  return
3001  } else {
3002  dict for {dictKey dictValue} $yamlDict {
3003  #looking for Hog include in .gitlab-ci.yml
3004  if {"$dictKey" == "include" && ([lsearch [split $dictValue " {}"] "/hog.yml" ] != "-1" || [lsearch [split $dictValue " {}"] "/hog-dynamic.yml" ] != "-1")} {
3005  set YML_REF [lindex [split $dictValue " {}"] [expr {[lsearch -dictionary [split $dictValue " {}"] "ref"]+1} ] ]
3006  set YML_NAME [lindex [split $dictValue " {}"] [expr {[lsearch -dictionary [split $dictValue " {}"] "file"]+1} ] ]
3007  }
3008  }
3009  }
3010  if {$YML_REF == ""} {
3011  Msg Warning "Hog version not specified in the .gitlab-ci.yml. Assuming that master branch is used."
3012  cd Hog
3013  set YML_REF_F [Git {name-rev --tags --name-only origin/master}]
3014  cd ..
3015  } else {
3016  set YML_REF_F [regsub -all "'" $YML_REF ""]
3017  }
3018 
3019  if {$YML_NAME == ""} {
3020  Msg $MSG_TYPE "Hog included yml file not specified, assuming hog.yml"
3021  set YML_NAME_F hog.yml
3022  } else {
3023  set YML_NAME_F [regsub -all "^/" $YML_NAME ""]
3024  }
3025 
3026  lappend YML_FILES $YML_NAME_F
3027 
3028  #getting Hog repository tag and commit
3029  cd "Hog"
3030 
3031  #check if the yml file includes other files
3032  if { [catch {::yaml::yaml2dict -file $YML_NAME_F} yamlDict]} {
3033  Msg $MSG_TYPE "Parsing $YML_NAME_F failed."
3034  cd $thisPath
3035  return
3036  } else {
3037  dict for {dictKey dictValue} $yamlDict {
3038  #looking for included files
3039  if {"$dictKey" == "include"} {
3040  foreach v $dictValue {
3041  lappend YML_FILES [lindex [split $v " "] [expr {[lsearch -dictionary [split $v " "] "local"]+1} ] ]
3042  }
3043  }
3044  }
3045  }
3046 
3047  Msg Info "Found the following yml files: $YML_FILES"
3048 
3049  set HOGYML_SHA [GetSHA $YML_FILES]
3050  lassign [GitRet "log --format=%h -1 --abbrev=7 $YML_REF_F" $YML_FILES] ret EXPECTEDYML_SHA
3051  if {$ret != 0} {
3052  lassign [GitRet "log --format=%h -1 --abbrev=7 origin/$YML_REF_F" $YML_FILES] ret EXPECTEDYML_SHA
3053  if {$ret != 0} {
3054  Msg $MSG_TYPE "Error in project .gitlab-ci.yml. ref: $YML_REF not found"
3055  set EXPECTEDYML_SHA ""
3056  }
3057  }
3058  if {!($EXPECTEDYML_SHA eq "")} {
3059  if {$HOGYML_SHA == $EXPECTEDYML_SHA} {
3060  Msg Info "Hog included file $YML_FILES matches with $YML_REF in .gitlab-ci.yml."
3061 
3062  } else {
3063  Msg $MSG_TYPE "HOG $YML_FILES SHA mismatch.
3064  From Hog submodule: $HOGYML_SHA
3065  From ref in .gitlab-ci.yml: $EXPECTEDYML_SHA
3066  You can fix this in 2 ways: by changing the ref in your repository or by changing the Hog submodule commit"
3067  }
3068  } else {
3069  Msg $MSG_TYPE "One or more of the following files could not be found $YML_FILES in Hog at $YML_REF"
3070  }
3071  } else {
3072  Msg Info ".gitlab-ci.yml not found in $repo_path. Skipping this step"
3073  }
3074 
3075  cd "$thisPath"
3076 }
3077 
3078 ## @brief Parse JSON file
3079 #
3080 # @returns -1 in case of failure, JSON KEY VALUE in case of success
3081 #
3082 proc ParseJSON {JSON_FILE JSON_KEY} {
3083  set result [catch {package require Tcl 8.4} TclFound]
3084  if {"$result" != "0"} {
3085  Msg CriticalWarning "Cannot find Tcl package version equal or higher than 8.4.\n $TclFound\n Exiting"
3086  return -1
3087  }
3088 
3089  set result [catch {package require json} JsonFound]
3090  if {"$result" != "0"} {
3091  Msg CriticalWarning "Cannot find JSON package equal or higher than 1.0.\n $JsonFound\n Exiting"
3092  return -1
3093  }
3094  set JsonDict [json::json2dict $JSON_FILE]
3095  set result [catch {dict get $JsonDict $JSON_KEY} RETURNVALUE]
3096  if {"$result" != "0"} {
3097  Msg CriticalWarning "Cannot find $JSON_KEY in $JSON_FILE\n Exiting"
3098  return -1
3099  } else {
3100  #Msg Info "$JSON_KEY --> $RETURNVALUE"
3101  return $RETURNVALUE
3102  }
3103 }
3104 
3105 ## @brief Handle eos commands
3106 #
3107 # It can be used with lassign like this: lassign [eos <eos command> ] ret result
3108 #
3109 # @param[in] command: the EOS command to be run, e.g. ls, cp, mv, rm
3110 # @param[in] attempt: (default 0) how many times the command should be attempted in case of failure
3111 #
3112 # @returns a list of 2 elements: the return value (0 if no error occurred) and the output of the EOS command
3113 proc eos {command {attempt 1}} {
3114  global env
3115  if {![info exists env(EOS_MGM_URL)]} {
3116  Msg Warning "Environment variable EOS_MGM_URL not set, setting it to default value root://eosuser.cern.ch"
3117  set ::env(EOS_MGM_URL) "root://eosuser.cern.ch"
3118  }
3119  if {$attempt < 1} {
3120  Msg Warning "The value of attempt should be 1 or more, not $attempt, setting it to 1 as default"
3121  set attempt 1
3122  }
3123  for {set i 0} {$i < $attempt} {incr i } {
3124  set ret [catch {exec -ignorestderr eos {*}$command} result]
3125  if {$ret == 0} {
3126  break
3127  } else {
3128  if {$attempt > 1} {
3129  set wait [expr {1+int(rand()*29)}]
3130  Msg Warning "Command $command failed ($i/$attempt): $result, trying again in $wait seconds..."
3131  after [expr {$wait*1000}]
3132  }
3133  }
3134  }
3135  return [list $ret $result]
3136 }
3137 
3138 ## @brief Handle git commands
3139 #
3140 #
3141 # @param[in] command: the git command to be run including refs (branch, tags, sha, etc.), except files.
3142 # @param[in] files: files given to git as argument. They will always be separated with -- to avoid weird accidents
3143 #
3144 # @returns the output of the git command
3145 proc Git {command {files ""}} {
3146  lassign [GitRet $command $files] ret result
3147  if {$ret != 0} {
3148  Msg Error "Code $ret returned by git running: $command -- $files"
3149  }
3150 
3151  return $result
3152 }
3153 
3154 ## @brief Handle git commands without causing an error if ret is not 0
3155 #
3156 # It can be used with lassign like this: lassign [GitRet <git command> <possibly files> ] ret result
3157 #
3158 # @param[in] command: the git command to be run including refs (branch, tags, sha, etc.), except files.
3159 # @param[in] files: files given to git as argument. They will always be separated with -- to avoid weird accidents
3160 # Sometimes you need to remove the --. To do that just set files to " "
3161 #
3162 # @returns a list of 2 elements: the return value (0 if no error occurred) and the output of the git command
3163 proc GitRet {command {files ""}} {
3164  global env
3165  if {$files eq ""} {
3166  set ret [catch {exec -ignorestderr git {*}$command} result]
3167  } else {
3168  set ret [catch {exec -ignorestderr git {*}$command -- {*}$files} result]
3169  }
3170 
3171 
3172  return [list $ret $result]
3173 }
3174 
3175 ## @brief Checks if file was committed into the repository
3176 #
3177 #
3178 # @param[in] File: file name
3179 #
3180 # @returns 1 if file was committed and 0 if file was not committed
3181 proc FileCommitted {File } {
3182  set Ret 1
3183  set currentDir [pwd]
3184  cd [file dirname [file normalize $File]]
3185  set GitLog [Git ls-files [file tail $File]]
3186  if {$GitLog == ""} {
3187  Msg CriticalWarning "File [file normalize $File] is not in the git repository. Please add it with:\n git add [file normalize $File]\n"
3188  set Ret 0
3189  }
3190  cd $currentDir
3191  return $Ret
3192 }
3193 
3194 
3195 ## @brief Handle shell commands
3196 #
3197 # It can be used with lassign like this: lassign [ExecuteRet <command> ] ret result
3198 #
3199 # @param[in] args: the shell command
3200 #
3201 # @returns a list of 2 elements: the return value (0 if no error occurred) and the output of the command
3202 proc ExecuteRet {args} {
3203  global env
3204  if {[llength $args] == 0} {
3205  Msg CriticalWarning "No argument given"
3206  set ret -1
3207  set result ""
3208  } else {
3209  set ret [catch {exec -ignorestderr {*}$args} result]
3210  }
3211 
3212  return [list $ret $result]
3213 }
3214 
3215 ## @brief Handle shell commands
3216 #
3217 # It can be used with lassign like this: lassign [Execute <command> ] ret result
3218 #
3219 # @param[in] args: the shell command
3220 #
3221 # @returns the output of the command
3222 proc Execute {args} {
3223  global env
3224  lassign [ExecuteRet {*}$args] ret result
3225  if {$ret != 0} {
3226  Msg Error "Command [join $args] returned error code: $ret"
3227  }
3228 
3229  return $result
3230 }
3231 
3232 
3233 ## @brief Gets MAX number of Threads property from property.conf file in Top/$proj_name directory.
3234 #
3235 # If property is not set returns default = 1
3236 #
3237 # @param[in] proj_dir: the top folder of the project
3238 #
3239 # @return 1 if property is not set else the value of MaxThreads
3240 #
3241 proc GetMaxThreads {proj_dir} {
3242  set maxThreads 1
3243  if {[file exists $proj_dir/hog.conf]} {
3244  set properties [ReadConf [lindex [GetConfFiles $proj_dir] 0]]
3245  if {[dict exists $properties parameters]} {
3246  set propDict [dict get $properties parameters]
3247  if {[dict exists $propDict MAX_THREADS]} {
3248  set maxThreads [dict get $propDict MAX_THREADS]
3249  }
3250  }
3251  } else {
3252  Msg Warning "File $proj_dir/hog.conf not found. Max threads will be set to default value 1"
3253  }
3254  return $maxThreads
3255 }
3256 
3257 
3258 
3259 ## @brief Returns the gitlab-ci.yml snippet for a CI stage and a defined project
3260 #
3261 # @param[in] proj_name: The project name
3262 # @param[in] ci_confs: Dictionary with CI configurations
3263 #
3264 proc WriteGitLabCIYAML {proj_name {ci_conf ""}} {
3265  if { [catch {package require yaml 0.3.3} YAMLPACKAGE]} {
3266  Msg CriticalWarning "Cannot find package YAML.\n Error message: $YAMLPACKAGE. If you are tunning on tclsh, you can fix this by installing package \"tcllib\""
3267  return -1
3268  }
3269 
3270  set job_list []
3271  if {$ci_conf != ""} {
3272  set ci_confs [ReadConf $ci_conf]
3273  foreach sec [dict keys $ci_confs] {
3274  if {[string first : $sec] == -1} {
3275  lappend job_list $sec
3276  }
3277  }
3278  } else {
3279  set job_list {"generate_project" "simulate_project"}
3280  set ci_confs ""
3281  }
3282 
3283  set out_yaml [huddle create]
3284  foreach job $job_list {
3285  # Check main project configurations
3286  set huddle_tags [huddle list]
3287  set tag_section ""
3288  set sec_dict [dict create]
3289 
3290  if {$ci_confs != ""} {
3291  foreach var [dict keys [dict get $ci_confs $job]] {
3292  if {$var == "tags"} {
3293  set tag_section "tags"
3294  set tags [dict get [dict get $ci_confs $job] $var]
3295  set tags [split $tags ","]
3296  foreach tag $tags {
3297  set tag_list [huddle list $tag]
3298  set huddle_tags [huddle combine $huddle_tags $tag_list]
3299  }
3300  } else {
3301  dict set sec_dict $var [dict get [dict get $ci_confs $job] $var]
3302  }
3303  }
3304  }
3305 
3306  # Check if there are extra variables in the conf file
3307  set huddle_variables [huddle create "PROJECT_NAME" $proj_name "extends" ".vars"]
3308  if {[dict exists $ci_confs "$job:variables"]} {
3309  set var_dict [dict get $ci_confs $job:variables]
3310  foreach var [dict keys $var_dict] {
3311  # puts [dict get $var_dict $var]
3312  set value [dict get $var_dict "$var"]
3313  set var_inner [huddle create "$var" "$value"]
3314  set huddle_variables [huddle combine $huddle_variables $var_inner]
3315  }
3316  }
3317 
3318 
3319  set middle [huddle create "extends" ".$job" "variables" $huddle_variables]
3320  foreach sec [dict keys $sec_dict] {
3321  set value [dict get $sec_dict $sec]
3322  set var_inner [huddle create "$sec" "$value"]
3323  set middle [huddle combine $middle $var_inner]
3324  }
3325  if {$tag_section != ""} {
3326  set middle2 [huddle create "$tag_section" $huddle_tags]
3327  set middle [huddle combine $middle $middle2]
3328  }
3329 
3330  set outer [huddle create "$job:$proj_name" $middle ]
3331  set out_yaml [huddle combine $out_yaml $outer]
3332  }
3333 
3334  return [ string trimleft [ yaml::huddle2yaml $out_yaml ] "-" ]
3335 }
3336 
3337 
3338 proc FindNewestVersion { versions } {
3339  set new_ver 00000000
3340  foreach ver $versions {
3341  ##nagelfar ignore
3342  if {[ expr 0x$ver > 0x$new_ver ] } {
3343  set new_ver $ver
3344  }
3345  }
3346  return $new_ver
3347 }
3348 
3349 ## Reset files in the repository
3350 #
3351 # @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)
3352 #
3353 # @return Nothing
3354 #
3355 proc ResetRepoFiles {reset_file} {
3356  if {[file exists $reset_file]} {
3357  Msg Info "Found $reset_file, opening it..."
3358  set fp [open $reset_file r]
3359  set wild_cards [lsearch -all -inline -not -regexp [split [read $fp] "\n"] "^ *$"]
3360  close $fp
3361  Msg Info "Found the following files/wild cards to restore if modified: $wild_cards..."
3362  foreach w $wild_cards {
3363  set mod_files [GetModifiedFiles "." $w]
3364  if {[llength $mod_files] > 0} {
3365  Msg Info "Found modified $w files: $mod_files, will restore them..."
3366  RestoreModifiedFiles "." $w
3367  } else {
3368  Msg Info "No modified $w files found."
3369  }
3370  }
3371  }
3372 }
3373 
3374 ## Search the Hog projects inside a directory
3375 #
3376 # @param[in] dir The directory to search
3377 #
3378 # @return The list of projects
3379 #
3380 proc SearchHogProjects {dir} {
3381  set projects_list {}
3382  if {[file exists $dir]} {
3383  if {[file isdirectory $dir]} {
3384  foreach proj_dir [glob -nocomplain -types d $dir/* ] {
3385  if {![regexp {^.*Top/+(.*)$} $proj_dir dummy proj_name]} {
3386  Msg Warning "Could not parse Top directory $dir"
3387  break
3388  }
3389  if { [file exists "$proj_dir/hog.conf" ] } {
3390  lappend projects_list $proj_name
3391  } else {
3392  foreach p [SearchHogProjects $proj_dir] {
3393  lappend projects_list $p
3394  }
3395  }
3396  }
3397 
3398  } else {
3399  Msg Error "Input $dir is not a directory!"
3400  }
3401  } else {
3402  Msg Error "Directory $dir doesn't exist!"
3403  }
3404  return $projects_list
3405 }
3406 
3407 ## Returns the group name from the project directory
3408 #
3409 # @param[in] proj_dir project directory
3410 # @param[in] repo_dir repository directory
3411 #
3412 # @return the group name without initial and final slashes
3413 #
3414 proc GetGroupName {proj_dir repo_dir} {
3415  if {[regexp {^(.*)/(Top|Projects)/+(.*?)/*$} $proj_dir dummy possible_repo_dir proj_or_top dir]} {
3416  # The Top or Project folder is in the root of a the git repository
3417  if {[file normalize $repo_dir] eq [file normalize $possible_repo_dir]} {
3418  set group [file dir $dir]
3419  if { $group == "." } {
3420  set group ""
3421  }
3422  } else {
3423  # The Top or Project folder is NOT in the root of a git repository
3424  Msg Warning "Project directory $proj_dir seems to be in $possible_repo_dir which is not a the main Git repository $repo_dir."
3425  }
3426  } else {
3427  Msg Warning "Could not parse project directory $proj_dir"
3428  set group ""
3429  }
3430  return $group
3431 }
3432 
3433 ## Read a property configuration file and returns a dictionary
3434 #
3435 # @param[in] file_name the configuration file
3436 #
3437 # @return The dictionary
3438 #
3439 proc ReadConf {file_name} {
3440 
3441  if { [catch {package require inifile 0.2.3} ERROR] } {
3442  puts "$ERROR\n If you are running this script on tclsh, you can fix this by installing 'tcllib'"
3443  return 1
3444  }
3445 
3446 
3447  ::ini::commentchar "#"
3448  set f [::ini::open $file_name]
3449  set properties [dict create]
3450  foreach sec [::ini::sections $f] {
3451  set new_sec $sec
3452  set key_pairs [::ini::get $f $sec]
3453 
3454  #manipulate strings here:
3455  regsub -all {\{\"} $key_pairs "\{" key_pairs
3456  regsub -all {\"\}} $key_pairs "\}" key_pairs
3457 
3458  dict set properties $new_sec [dict create {*}$key_pairs]
3459  }
3460 
3461  ::ini::close $f
3462 
3463  return $properties
3464 }
3465 
3466 ## Write a property configuration file from a dictionary
3467 #
3468 # @param[in] file_name the configuration file
3469 # @param[in] config the configuration dictionary
3470 # @param[in] comment comment to add at the beginning of configuration file
3471 #
3472 #
3473 proc WriteConf {file_name config {comment ""}} {
3474  if { [catch {package require inifile 0.2.3} ERROR] } {
3475  puts "$ERROR\n If you are running this script on tclsh, you can fix this by installing 'tcllib'"
3476  return 1
3477  }
3478 
3479  ::ini::commentchar "#"
3480  set f [::ini::open $file_name w]
3481 
3482  foreach sec [dict keys $config] {
3483  set section [dict get $config $sec]
3484  dict for {p v} $section {
3485  if {[string trim $v] == ""} {
3486  Msg Warning "Property $p has empty value. Skipping..."
3487  continue;
3488  }
3489  ::ini::set $f $sec $p $v
3490  }
3491  }
3492 
3493  #write comment before the first section (first line of file)
3494  if {![string equal "$comment" ""]} {
3495  ::ini::comment $f [lindex [::ini::sections $f] 0] "" $comment
3496  }
3497  ::ini::commit $f
3498 
3499  ::ini::close $f
3500 
3501 }
3502 
3503 
3504 ## Check if a path is absolute or relative
3505 #
3506 # @param[in] the path to check
3507 #
3508 proc IsRelativePath {path} {
3509  if {[string index $path 0] == "/" || [string index $path 0] == "~"} {
3510  return 0
3511  } else {
3512  return 1
3513  }
3514 }
3515 
3516 
3517 # Check Git Version when sourcing hog.tcl
3518 if {[GitVersion 2.7.2] == 0 } {
3519  Msg Error "Found Git version older than 2.7.2. Hog will not work as expected, exiting now."
3520 }
3521 
3522 
3523 ## Write the resource utilization table into a a file (Vivado only)
3524 #
3525 # @param[in] input the input .rpt report file from Vivado
3526 # @param[in] output the output file
3527 # @param[in] project_name the name of the project
3528 # @param[in] run synthesis or implementation
3529 proc WriteUtilizationSummary {input output project_name run} {
3530  set f [open $input "r"]
3531  set o [open $output "a"]
3532  puts $o "## $project_name $run Utilization report\n\n"
3533  struct::matrix util_m
3534  util_m add columns 14
3535  util_m add row
3536  if { [GetIDEVersion] >= 2021.0 } {
3537  util_m add row "| **Site Type** | **Used** | **Fixed** | **Prohibited** | **Available** | **Util%** |"
3538  util_m add row "| --- | --- | --- | --- | --- | --- |"
3539  } else {
3540  util_m add row "| **Site Type** | **Used** | **Fixed** | **Available** | **Util%** |"
3541  util_m add row "| --- | --- | --- | --- | --- |"
3542  }
3543 
3544  set luts 0
3545  set regs 0
3546  set uram 0
3547  set bram 0
3548  set dsps 0
3549  set ios 0
3550 
3551  while {[gets $f line] >= 0} {
3552  if { ( [string first "| CLB LUTs" $line] >= 0 || [string first "| Slice LUTs" $line] >= 0 ) && $luts == 0 } {
3553  util_m add row $line
3554  set luts 1
3555  }
3556  if { ( [string first "| CLB Registers" $line] >= 0 || [string first "| Slice Registers" $line] >= 0 ) && $regs == 0} {
3557  util_m add row $line
3558  set regs 1
3559  }
3560  if { [string first "| Block RAM Tile" $line] >= 0 && $bram == 0 } {
3561  util_m add row $line
3562  set bram 1
3563  }
3564  if { [string first "URAM " $line] >= 0 && $uram == 0} {
3565  util_m add row $line
3566  set uram 1
3567  }
3568  if { [string first "DSPs" $line] >= 0 && $dsps == 0 } {
3569  util_m add row $line
3570  set dsps 1
3571  }
3572  if { [string first "Bonded IOB" $line] >= 0 && $ios == 0 } {
3573  util_m add row $line
3574  set ios 1
3575  }
3576  }
3577  util_m add row
3578 
3579  close $f
3580  puts $o [util_m format 2string]
3581  close $o
3582 }
3583 
3584 ## Get the Date and time of a commit (or current time if Git < 2.9.3)
3585 #
3586 # @param[in] commit The commit
3587 proc GetDateAndTime {commit} {
3588  set clock_seconds [clock seconds]
3589 
3590  if {[GitVersion 2.9.3]} {
3591  set date [Git "log -1 --format=%cd --date=format:%d%m%Y $commit"]
3592  set timee [Git "log -1 --format=%cd --date=format:00%H%M%S $commit"]
3593  } else {
3594  Msg Warning "Found Git version older than 2.9.3. Using current date and time instead of commit time."
3595  set date [clock format $clock_seconds -format {%d%m%Y}]
3596  set timee [clock format $clock_seconds -format {00%H%M%S}]
3597  }
3598  return [list $date $timee]
3599 }
3600 
3601 ## Get the Project flavour
3602 #
3603 # @param[in] proj_name The project name
3604 proc GetProjectFlavour {proj_name} {
3605  # Calculating flavour if any
3606  set flavour [string map {. ""} [file extension $proj_name]]
3607  if {$flavour != ""} {
3608  if {[string is integer $flavour]} {
3609  Msg Info "Project $proj_name has flavour = $flavour, the generic variable FLAVOUR will be set to $flavour"
3610  } else {
3611  Msg Warning "Project name has a unexpected non numeric extension, flavour will be set to -1"
3612  set flavour -1
3613  }
3614 
3615  } else {
3616  set flavour -1
3617  }
3618  return $flavour
3619 }
3620 
3621 ## Format a generic to a 32 bit verilog style hex number, e.g.
3622 # take in ea8394c and return 32'h0ea8394c
3623 #
3624 # @param[in] unformatted generic
3625 proc FormatGeneric {generic} {
3626  if {[string is integer "0x$generic"]} {
3627  return [format "32'h%08X" "0x$generic"]
3628  } else {
3629  # for non integers (e.g. blanks) just return 0
3630  return [format "32'h%08X" 0]
3631  }
3632 }
3633 
3634 ## @brief Gets custom generics from hog.conf
3635 #
3636 # @param[in] proj_dir: the top folder of the project
3637 # @param[in] target: software target(vivado, questa)
3638 # defines the output format of the string
3639 # @return string with generics
3640 #
3641 proc GetGenericFromConf {proj_dir target {sim 0}} {
3642  set prj_generics ""
3643  set top_dir "Top/$proj_dir"
3644  set conf_file "$top_dir/hog.conf"
3645  set conf_index 0
3646  if {$sim == 1} {
3647  set conf_file "$top_dir/sim.conf"
3648  set conf_index 1
3649  }
3650 
3651 
3652  if {[file exists $conf_file]} {
3653  set properties [ReadConf [lindex [GetConfFiles $top_dir] $conf_index]]
3654  if {[dict exists $properties generics]} {
3655  set propDict [dict get $properties generics]
3656  dict for {theKey theValue} $propDict {
3657  set valueHexFull ""
3658  set valueNumBits ""
3659  set valueHexFlag ""
3660  set valueHex ""
3661  set valueIntFull ""
3662  set ValueInt ""
3663  set valueStrFull ""
3664  set ValueStr ""
3665  regexp {([0-9]*)('h)([0-9a-fA-F]*)} $theValue valueHexFull valueNumBits valueHexFlag valueHex
3666  regexp {^([0-9]*)$} $theValue valueIntFull ValueInt
3667  regexp {(?!^\d+$)^.+$} $theValue valueStrFull ValueStr
3668  if { [string tolower $target] == "vivado" || [string tolower $target] == "xsim" } {
3669  if {$valueNumBits != "" && $valueHexFlag != "" && $valueHex != ""} {
3670  set prj_generics "$prj_generics $theKey=$valueHexFull"
3671  } elseif { $valueIntFull != "" && $ValueInt != "" } {
3672  set prj_generics "$prj_generics $theKey=$ValueInt"
3673  } elseif { $valueStrFull != "" && $ValueStr != "" } {
3674  set prj_generics "$prj_generics $theKey=\"$ValueStr\""
3675  } else {
3676  set prj_generics "$prj_generics $theKey=\"$theValue\""
3677  }
3678  } elseif { [lsearch -exact [GetSimulators] [string tolower $target] ] >= 0 } {
3679  if {$valueNumBits != "" && $valueHexFlag != "" && $valueHex != ""} {
3680  set numBits 0
3681  scan $valueNumBits %d numBits
3682  set numHex 0
3683  scan $valueHex %x numHex
3684  binary scan [binary format "I" $numHex] "B*" binval
3685  set numBits [expr {$numBits-1}]
3686  set numBin [string range $binval end-$numBits end]
3687  set prj_generics "$prj_generics $theKey=\"$numBin\""
3688 
3689  } elseif { $valueIntFull != "" && $ValueInt != "" } {
3690  set prj_generics "$prj_generics $theKey=$ValueInt"
3691  } elseif { $valueStrFull != "" && $ValueStr != "" } {
3692  set prj_generics "$prj_generics {$theKey=\"$ValueStr\"}"
3693 
3694  } else {
3695  set prj_generics "$prj_generics {$theKey=\"$theValue\"}"
3696  }
3697  } else {
3698  Msg Warning "Target : $target not implemented"
3699  }
3700  }
3701  }
3702  } else {
3703  Msg Warning "File $top_dir/hog.conf not found."
3704  }
3705  return $prj_generics
3706 }
3707 
3708 ## @brief Sets the generics in all the sim.conf simulation file sets
3709 #
3710 # @param[in] repo_path: the top folder of the projectThe path to the main git repository
3711 # @param[in] proj_dir: the top folder of the project
3712 # @param[in] target: software target(vivado, questa)
3713 # defines the output format of the string
3714 #
3715 proc SetGenericsSimulation {repo_path proj_dir target} {
3716  set sim_generics ""
3717  set top_dir "$repo_path/Top/$proj_dir"
3718  set read_aux [GetConfFiles $top_dir]
3719  set sim_cfg_index [lsearch -regexp -index 0 $read_aux ".*sim.conf"]
3720  set sim_cfg_index [lsearch -regexp -index 0 [GetConfFiles $top_dir] ".*sim.conf"]
3721  set simsets [get_filesets]
3722  if { $simsets != "" } {
3723  if {[file exists $top_dir/sim.conf]} {
3724  set sim_generics [GetGenericFromConf $proj_dir $target 1]
3725  if {$sim_generics != ""} {
3726  foreach simset $simsets {
3727  if {[get_property FILESET_TYPE $simset] != "SimulationSrcs" } {
3728  continue
3729  }
3730  set_property generic $sim_generics [get_filesets $simset]
3731  Msg Debug "Setting generics $sim_generics for simulator $target and simulation file-set $simset..."
3732  }
3733  }
3734  } else {
3735  if {[glob -nocomplain "$top_dir/list/*.sim"] ne "" } {
3736  Msg CriticalWarning "Simulation sets and .sim files are present in the project but no sim.conf found in $top_dir. Please refer to Hog's manual to create one."
3737  }
3738  }
3739  }
3740 }
3741 
3742 ## @brief Return the path to the active top file
3743 proc GetTopFile {} {
3744  if {[IsVivado]} {
3745  set compile_order_prop [get_property source_mgmt_mode [current_project]]
3746  if {$compile_order_prop ne "All"} {
3747  Msg CriticalWarning "Compile order is not set to automatic, setting it now..."
3748  set_property source_mgmt_mode All [current_project]
3749  update_compile_order -fileset sources_1
3750  }
3751  return [lindex [get_files -quiet -compile_order sources -used_in synthesis -filter {FILE_TYPE =~ "VHDL*" || FILE_TYPE =~ "*Verilog*" } ] end]
3752  } elseif {[IsISE]} {
3753  debug::design_graph_mgr -create [current_fileset]
3754  debug::design_graph -add_fileset [current_fileset]
3755  debug::design_graph -update_all
3756  return [lindex [debug::design_graph -get_compile_order] end]
3757  } else {
3758  Msg Error "GetTopFile not yet implemented for this IDE"
3759  }
3760 }
3761 
3762 ## @brief Return the name of the active top module
3763 proc GetTopModule {} {
3764  if {[IsXilinx]} {
3765  return [get_property top [current_fileset]]
3766  } else {
3767  Msg Error "GetTopModule not yet implemented for this IDE"
3768  }
3769 }
3770 
3771 ## Get a dictionary of verilog generics with their types for a given file
3772 #
3773 # @param[in] file File to read Generics from
3774 proc GetVerilogGenerics {file} {
3775  set fp [open $file r]
3776  set data [read $fp]
3777  close $fp
3778  set lines []
3779 
3780  # read in the verilog file and remove comments
3781  foreach line [split $data "\n"] {
3782  regsub "^\\s*\/\/.*" $line "" line
3783  regsub "(.*)\/\/.*" $line {\1} line
3784  if {![string equal $line ""]} {
3785  append lines $line " "
3786  }
3787  }
3788 
3789  # remove block comments also /* */
3790  regsub -all {/\*.*\*/} $lines "" lines
3791 
3792  # create a list of characters to split for tokenizing
3793  set punctuation [list]
3794  foreach char [list "(" ")" ";" "," " " "!" "<=" ":=" "=" "\[" "\]"] {
3795  lappend punctuation $char "\000$char\000"
3796  }
3797 
3798  # split the file into tokens
3799  set tokens [split [string map $punctuation $lines] \000]
3800 
3801  set parameters [dict create]
3802 
3803  set PARAM_NAME 1
3804  set PARAM_VALUE 2
3805  set LEXING 3
3806  set PARAM_WIDTH 4
3807  set state $LEXING
3808 
3809  # # loop over the generic lines
3810  foreach token $tokens {
3811  set token [string trim $token]
3812  if {![string equal "" $token]} {
3813  if {[string equal [string tolower $token] "parameter"]} {
3814  set state $PARAM_NAME
3815  } elseif {[string equal $token ")"] || [string equal $token ";"]} {
3816  set state $LEXING
3817  } elseif {$state == $PARAM_WIDTH} {
3818  if {[string equal $token "\]"]} {
3819  set state $PARAM_NAME
3820  }
3821  } elseif {$state == $PARAM_VALUE} {
3822  if {[string equal $token ","]} {
3823  set state $PARAM_NAME
3824  } elseif {[string equal $token ";"]} {
3825  set state $LEXING
3826  } else {
3827  }
3828  } elseif {$state == $PARAM_NAME} {
3829 
3830  if {[string equal $token "="]} {
3831  set state $PARAM_VALUE
3832  } elseif {[string equal $token "\["]} {
3833  set state $PARAM_WIDTH
3834  } elseif {[string equal $token ","]} {
3835  set state $PARAM_NAME
3836  } elseif {[string equal $token ";"]} {
3837  set state $LEXING
3838  } elseif {[string equal $token ")"]} {
3839  set state $LEXING
3840  } else {
3841  dict set parameters $token "integer"
3842  }}}}
3843 
3844  return $parameters
3845 }
3846 
3847 ## Get a dictionary of VHDL generics with their types for a given file
3848 #
3849 # @param[in] file File to read Generics from
3850 
3851 proc GetVhdlGenerics {file {entity ""} } {
3852  set fp [open $file r]
3853  set data [read $fp]
3854  close $fp
3855  set lines []
3856 
3857  # read in the vhdl file and remove comments
3858  foreach line [split $data "\n"] {
3859  regsub "^\\s*--.*" $line "" line
3860  regsub "(.*)--.*" $line {\1} line
3861  if {![string equal $line ""]} {
3862  append lines $line " "
3863  }
3864  }
3865 
3866  # extract the generic block
3867  set generic_block ""
3868  set generics [dict create]
3869 
3870  if {1==[string equal $entity ""]} {
3871  regexp {(?i).*entity\s+([^\s]+)\s+is} $lines _ entity
3872  }
3873 
3874  set generics_regexp "(?i).*entity\\s+$entity\\s+is\\s+generic\\s*\\((.*)\\)\\s*;\\s*port.*end.*$entity"
3875 
3876  if {[regexp $generics_regexp $lines _ generic_block]} {
3877 
3878  # loop over the generic lines
3879  foreach line [split $generic_block ";"] {
3880 
3881  # split the line into the generic + the type
3882  regexp {(.*):\s*([A-Za-z0-9_]+).*} $line _ generic type
3883 
3884  # one line can have multiple generics of the same type, so loop over them
3885  set splits [split $generic ","]
3886  foreach split $splits {
3887  dict set generics [string trim $split] [string trim $type]
3888  }
3889  }
3890  }
3891  return $generics
3892 }
3893 
3894 proc GetFileGenerics {filename {entity ""}} {
3895  set file_type [FindFileType $filename]
3896  if {[string equal $file_type "VERILOG_FILE"] || [string equal $file_type "SYSTEMVERILOG_FILE"]} {
3897  return [GetVerilogGenerics $filename]
3898  } elseif {[string equal $file_type "VHDL_FILE"]} {
3899  return [GetVhdlGenerics $filename $entity]
3900  } else {
3901  Msg CriticalWarning "Could not determine extension of top level file."
3902  }
3903 }
3904 
3905 ## Set the generics property
3906 #
3907 # @param[in] mode if it's "create", the function will assume the project is being created
3908 # @param[in] repo_path The path to the main git repository
3909 # @param[in] design The name of the design
3910 
3911 # @param[in] list of variables to be written in the generics in the usual order
3912 
3913 proc WriteGenerics {mode repo_path design date timee commit version top_hash top_ver hog_hash hog_ver cons_ver cons_hash libs vers hashes ext_names ext_hashes user_ip_repos user_ip_vers user_ip_hashes flavour {xml_ver ""} {xml_hash ""}} {
3914  Msg Info "Passing parameters/generics to project's top module..."
3915  ##### Passing Hog generic to top file
3916  # set global generic variables
3917  set generic_string [concat \
3918  "GLOBAL_DATE=[FormatGeneric $date]" \
3919  "GLOBAL_TIME=[FormatGeneric $timee]" \
3920  "GLOBAL_VER=[FormatGeneric $version]" \
3921  "GLOBAL_SHA=[FormatGeneric $commit]" \
3922  "TOP_SHA=[FormatGeneric $top_hash]" \
3923  "TOP_VER=[FormatGeneric $top_ver]" \
3924  "HOG_SHA=[FormatGeneric $hog_hash]" \
3925  "HOG_VER=[FormatGeneric $hog_ver]" \
3926  "CON_VER=[FormatGeneric $cons_ver]" \
3927  "CON_SHA=[FormatGeneric $cons_hash]"]
3928  # xml hash
3929  if {$xml_hash != "" && $xml_ver != ""} {
3930  lappend generic_string \
3931  "XML_VER=[FormatGeneric $xml_ver]" \
3932  "XML_SHA=[FormatGeneric $xml_hash]"
3933  }
3934  #set project specific lists
3935  foreach l $libs v $vers h $hashes {
3936  set ver "[string toupper $l]_VER=[FormatGeneric $v]"
3937  set hash "[string toupper $l]_SHA=[FormatGeneric $h]"
3938  lappend generic_string "$ver" "$hash"
3939  }
3940 
3941  foreach e $ext_names h $ext_hashes {
3942  set hash "[string toupper $e]_SHA=[FormatGeneric $h]"
3943  lappend generic_string "$hash"
3944  }
3945 
3946  foreach repo $user_ip_repos v $user_ip_vers h $user_ip_hashes {
3947  set repo_name [file tail $repo]
3948  set ver "[string toupper $repo_name]_VER=[FormatGeneric $v]"
3949  set hash "[string toupper $repo_name]_SHA=[FormatGeneric $h]"
3950  lappend generic_string "$ver" "$hash"
3951  }
3952 
3953  if {$flavour != -1} {
3954  lappend generic_string "FLAVOUR=$flavour"
3955  }
3956 
3957  # Dealing with project generics in Vivado
3958  set prj_generics [GetGenericFromConf $design "Vivado"]
3959  set generic_string "$prj_generics $generic_string"
3960 
3961  # Extract the generics from the top level source file
3962  if {[IsXilinx]} {
3963  # Top File can be retrieved only at creation time or in ISE
3964  if {$mode == "create" || [IsISE]} {
3965 
3966  set top_file [GetTopFile]
3967  set top_name [GetTopModule]
3968  if {[file exists $top_file]} {
3969  set generics [GetFileGenerics $top_file $top_name]
3970 
3971  Msg Debug "Found top level generics $generics in $top_file"
3972 
3973  set filtered_generic_string ""
3974 
3975  foreach generic_to_set [split [string trim $generic_string]] {
3976  set key [lindex [split $generic_to_set "="] 0]
3977  if {[dict exists $generics $key]} {
3978  Msg Debug "Hog generic $key found in $top_name"
3979  lappend filtered_generic_string "$generic_to_set"
3980  } else {
3981  Msg Warning "Generic $key is passed by Hog but is NOT present in $top_name."
3982  }
3983  }
3984 
3985  # only filter in ISE
3986  if {[IsISE]} {
3987  set generic_string $filtered_generic_string
3988  }
3989  }
3990  }
3991 
3992  set_property generic $generic_string [current_fileset]
3993  Msg Info "Setting parameters/generics..."
3994  Msg Debug "Detailed parameters/generics: $generic_string"
3995 
3996  if {[IsVivado]} {
3997  # Dealing with project generics in Simulators
3998  set simulator [get_property target_simulator [current_project]]
3999  if {$mode == "create"} {
4000  SetGenericsSimulation $repo_path $design $simulator
4001  }
4002  }
4003  } elseif {[IsSynplify]} {
4004  Msg Info "Setting Synplify parameters/generics one by one..."
4005  foreach generic $generic_string {
4006  Msg Debug "Setting Synplify generic: $generic"
4007  set_option -hdl_param -set "$generic"
4008  }
4009  }
4010 }
4011 
4012 ## Returns the version of the IDE (Vivado,Quartus,PlanAhead,Libero) in use
4013 #
4014 # @return the version in string format, e.g. 2020.2
4015 #
4016 proc GetIDEVersion {} {
4017  if {[IsXilinx]} {
4018  #Vivado or planAhead
4019  regexp {\d+\.\d+(\.\d+)?} [version -short] ver
4020  # This regex will cut away anything after the numbers, useful for patched version 2020.1_AR75210
4021 
4022  } elseif {[IsQuartus]} {
4023  # Quartus
4024  global quartus
4025  regexp {[\.0-9]+} $quartus(version) ver
4026  } elseif {[IsLibero]} {
4027  # Libero
4028  set ver [get_libero_version]
4029  }
4030  return $ver
4031 }
4032 
4033 ## Get the IDE (Vivado,Quartus,PlanAhead,Libero) version from the conf file she-bang
4034 #
4035 # @param[in] conf_file The hog.conf file
4036 proc GetIDEFromConf {conf_file} {
4037  set f [open $conf_file "r"]
4038  set line [gets $f]
4039  close $f
4040  if {[regexp -all {^\# *(\w*) *(\d+\.\d+(?:\.\d+)?(?:\.\d+)?)?(_.*)? *$} $line dummy ide version patch]} {
4041  if {[info exists version] && $version != ""} {
4042  set ver $version
4043  } else {
4044  set ver 0.0.0
4045  }
4046  # what shall we do with $patch? ignored for the time being
4047  set ret [list $ide $ver]
4048  } else {
4049  Msg CriticalWarning "The first line of hog.conf should be \#<IDE name> <version>, where <IDE name>. is quartus, vivado, planahead and <version> the tool version, e.g. \#vivado 2020.2. Will assume vivado."
4050  set ret [list "vivado" "0.0.0"]
4051  }
4052 
4053  return $ret
4054 }
4055 
4056 ##
4057 ## Create a new directory, not throwing an error if it already exists
4058 ##
4059 ## @param dir The dir
4060 ##
4061 proc Mkdir {dir} {
4062  if {[file exists $dir] && [file isdirectory $dir]} {
4063  return
4064  } else {
4065  file mkdir $dir
4066  return
4067  }
4068 }
4069 
4070 ##
4071 ## Copy a file or folder into a new path, not throwing an error if the final path is not empty
4072 ##
4073 ## @param i_dirs The directory or file to copy
4074 ## @param o_dir The final destination
4075 ##
4076 proc Copy {i_dirs o_dir} {
4077  foreach i_dir $i_dirs {
4078  if {[file isdirectory $i_dir] && [file isdirectory $o_dir]} {
4079  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]])} {
4080  file delete -force $o_dir/[file tail $i_dir]
4081  }
4082  }
4083 
4084  file copy -force $i_dir $o_dir
4085  }
4086 }
4087 
4088 ## @brief Remove duplicates in a dictionary
4089 #
4090 # @param[in] mydict the input dictionary
4091 #
4092 # @return the dictionary stripped of duplicates
4093 proc RemoveDuplicates {mydict} {
4094  set new_dict [dict create]
4095  foreach key [dict keys $mydict] {
4096  set values [DictGet $mydict $key]
4097  foreach value $values {
4098  set idxs [lreverse [lreplace [lsearch -exact -all $values $value] 0 0]]
4099  foreach idx $idxs {
4100  set values [lreplace $values $idx $idx]
4101  }
4102  }
4103  dict set new_dict $key $values
4104  }
4105  return $new_dict
4106 }
4107 
4108 ## @brief Compare the contents of two dictionaries
4109 #
4110 # @param[in] proj_libs The dictionary of libraries in the project
4111 # @param[in] list_libs The dictionary of libraries in list files
4112 # @param[in] proj_sets The dictionary of filesets in the project
4113 # @param[in] list_sets The dictionary of filesets in list files
4114 # @param[in] proj_props The dictionary of file properties in the project
4115 # @param[in] list_props The dictionary of file pproperties in list files
4116 # @param[in] severity The severity of the message in case a file is not found (Default: CriticalWarning)
4117 # @param[in] outFile The output log file, to write the messages (Default "")
4118 # @param[in] extraFiles The dictionary of extra files generated a creation time (Default "")
4119 #
4120 # @return n_diffs The number of differences
4121 # @return extra_files Remaining list of extra files
4122 
4123 proc CompareLibDicts {proj_libs list_libs proj_sets list_sets proj_props list_props {severity "CriticalWarning"} {outFile ""} {extraFiles ""} } {
4124  set extra_files $extraFiles
4125  set n_diffs 0
4126  set out_prjlibs $proj_libs
4127  set out_prjprops $proj_props
4128  # Loop over filesets in project
4129  dict for {prjSet prjLibraries} $proj_sets {
4130  # Check if sets is also in list files
4131  if {[IsInList $prjSet $list_sets]} {
4132  set listLibraries [DictGet $list_sets $prjSet]
4133  # Loop over libraries in fileset
4134  foreach prjLib $prjLibraries {
4135  set prjFiles [DictGet $proj_libs $prjLib]
4136  # Check if library exists in list files
4137  if {[IsInList $prjLib $listLibraries]} {
4138  # Loop over files in library
4139  set listFiles [DictGet $list_libs $prjLib]
4140  foreach prjFile $prjFiles {
4141  set idx [lsearch -exact $listFiles $prjFile]
4142  set listFiles [lreplace $listFiles $idx $idx]
4143  if {$idx < 0} {
4144  # File is in project but not in list libraries, check if it was generated at creation time...
4145  if { [dict exists $extra_files $prjFile] } {
4146  # File was generated at creation time, checking the md5sum
4147  # Removing the file from the prjFiles list
4148  set idx2 [lsearch -exact $prjFiles $prjFile]
4149  set prjFiles [lreplace $prjFiles $idx2 $idx2]
4150  set new_md5sum [Md5Sum $prjFile]
4151  set old_md5sum [DictGet $extra_files $prjFile]
4152  if {$new_md5sum != $old_md5sum} {
4153  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
4154  incr n_diffs
4155  }
4156  set extra_files [dict remove $extra_files $prjFile]
4157  } else {
4158  # File is neither in list files nor in extra_files
4159  MsgAndLog "$prjFile was found in project but not in list files or .hog/extra.files" $severity $outFile
4160  incr n_diffs
4161  }
4162  } else {
4163  # File is both in list files and project, checking properties...
4164  set prjProps [DictGet $proj_props $prjFile]
4165  set listProps [DictGet $list_props $prjFile]
4166  # Check if it is a potential sourced file
4167  if {[IsInList "nosynth" $prjProps] && [IsInList "noimpl" $prjProps] && [IsInList "nosim" $prjProps]} {
4168  # Check if it is sourced
4169  set idx_source [lsearch -exact $listProps "source"]
4170  if {$idx_source >= 0 } {
4171  # It is sourced, let's replace the individual properties with source
4172  set idx [lsearch -exact $prjProps "noimpl"]
4173  set prjProps [lreplace $prjProps $idx $idx]
4174  set idx [lsearch -exact $prjProps "nosynth"]
4175  set prjProps [lreplace $prjProps $idx $idx]
4176  set idx [lsearch -exact $prjProps "nosim"]
4177  set prjProps [lreplace $prjProps $idx $idx]
4178  lappend prjProps "source"
4179  }
4180  }
4181 
4182  foreach prjProp $prjProps {
4183  set idx [lsearch -exact $listProps $prjProp]
4184  set listProps [lreplace $listProps $idx $idx]
4185  if {$idx < 0} {
4186  MsgAndLog "Property $prjProp of $prjFile was set in project but not in list files" $severity $outFile
4187  incr n_diffs
4188  }
4189  }
4190 
4191  foreach listProp $listProps {
4192  if {[string first $listProp "topsim="] == -1} {
4193  MsgAndLog "Property $listProp of $prjFile was found in list files but not set in project." $severity $outFile
4194  incr n_diffs
4195  }
4196  }
4197 
4198  # Update project prjProps
4199  dict set out_prjprops $prjFile $prjProps
4200  }
4201  }
4202  # Loop over remaining files in list libraries
4203  foreach listFile $listFiles {
4204  MsgAndLog "$listFile was found in list files but not in project." $severity $outFile
4205  incr n_diffs
4206  }
4207  } else {
4208  # Check extra files again...
4209  foreach prjFile $prjFiles {
4210  if { [dict exists $extra_files $prjFile] } {
4211  # File was generated at creation time, checking the md5sum
4212  # Removing the file from the prjFiles list
4213  set idx2 [lsearch -exact $prjFiles $prjFile]
4214  set prjFiles [lreplace $prjFiles $idx2 $idx2]
4215  set new_md5sum [Md5Sum $prjFile]
4216  set old_md5sum [DictGet $extra_files $prjFile]
4217  if {$new_md5sum != $old_md5sum} {
4218  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
4219  incr n_diffs
4220  }
4221  set extra_files [dict remove $extra_files $prjFile]
4222  } else {
4223  # File is neither in list files nor in extra_files
4224  MsgAndLog "$prjFile was found in project but not in list files or .hog/extra.files" $severity $outFile
4225  incr n_diffs
4226  }
4227  }
4228  }
4229  # Update prjLibraries
4230  dict set out_prjlibs $prjLib $prjFiles
4231  }
4232  } else {
4233  MsgAndLog "Fileset $prjSet found in project but not in list files" $severity $outFile
4234  incr n_diffs
4235  }
4236  }
4237 
4238  return [list $n_diffs $extra_files $out_prjlibs $out_prjprops]
4239 }
4240 
4241 # @brief Write the content of Hog-library-dictionary created from the project into a .sim list file
4242 #
4243 # @param[in] libs The Hog-Library dictionary with the list of files in the project to write
4244 # @param[in] props The Hog-library dictionary with the file sets
4245 # @param[in] simsets The Hog-library dictionary with the file sets (relevant only for simulation)
4246 # @param[in] list_path The path of the output list file
4247 # @param[in] repo_path The main repository path
4248 proc WriteSimListFiles {libs props simsets list_path repo_path } {
4249  # Writing simulation list files
4250  foreach simset [dict keys $simsets] {
4251  set list_file_name $list_path/${simset}.sim
4252  set list_file [open $list_file_name w]
4253  Msg Info "Writing $list_file_name..."
4254  foreach lib [DictGet $simsets $simset] {
4255  foreach file [DictGet $libs $lib] {
4256  # Retrieve file properties from prop list
4257  set prop [DictGet $props $file]
4258  # Check if file is local to the repository or external
4259  if {[RelativeLocal $repo_path $file] != ""} {
4260  set file_path [RelativeLocal $repo_path $file]
4261  set lib_name [file rootname $lib]
4262  if {$lib_name != $simset} {
4263  lappend prop "lib=$lib_name"
4264  }
4265  puts $list_file "$file_path $prop"
4266  } else {
4267  # File is not relative to repo or ext_path... Write a Warning and continue
4268  Msg Warning "The path of file $file is not relative to your repository. Please check!"
4269  }
4270  }
4271  }
4272  }
4273 }
4274 
4275 
4276 # @brief Write the content of Hog-library-dictionary created from the project into a .src/.ext/.con list file
4277 #
4278 # @param[in] libs The Hog-Library dictionary with the list of files in the project to write
4279 # @param[in] props The Hog-library dictionary with the file sets
4280 # @param[in] list_path The path of the output list file
4281 # @param[in] repo_path The main repository path
4282 # @param[in] ext_path The external path
4283 proc WriteListFiles {libs props list_path repo_path {$ext_path ""} } {
4284  # Writing simulation list files
4285  foreach lib [dict keys $libs] {
4286  if {[llength [DictGet $libs $lib]] > 0} {
4287  set list_file_name $list_path$lib
4288  set list_file [open $list_file_name w]
4289  Msg Info "Writing $list_file_name..."
4290  foreach file [DictGet $libs $lib] {
4291  # Retrieve file properties from prop list
4292  set prop [DictGet $props $file]
4293  # Check if file is local to the repository or external
4294  if {[RelativeLocal $repo_path $file] != ""} {
4295  set file_path [RelativeLocal $repo_path $file]
4296  puts $list_file "$file_path $prop"
4297  } elseif {[RelativeLocal $ext_path $file] != ""} {
4298  set file_path [RelativeLocal $ext_path $file]
4299  set ext_list_file [open "[file rootname $list_file].ext" a]
4300  puts $ext_list_file "$file_path $prop"
4301  close $ext_list_file
4302  } else {
4303  # File is not relative to repo or ext_path... Write a Warning and continue
4304  Msg Warning "The path of file $file is not relative to your repository. Please check!"
4305  }
4306  }
4307  close $list_file
4308  }
4309  }
4310 }
4311 
4312 # @brief Remove empty keys from dictionary
4313 proc RemoveEmptyKeys {d} {
4314  set newDict $d
4315  foreach {k v} $newDict {
4316  if {$v == {{}} || $v == "" } {
4317  set newDict [dict remove $newDict $k]
4318  }
4319  }
4320  return $newDict
4321 }
4322 
4323 #print logo in images/hog_logo.txt
4324 proc Logo { {repo_path .} } {
4325  if {[info exists ::env(HOG_COLORED)] && [string match "ENABLED" $::env(HOG_COLORED)]} {
4326  set logo_file "$repo_path/Hog/images/hog_logo_color.txt"
4327  } else {
4328  set logo_file "$repo_path/Hog/images/hog_logo.txt"
4329  }
4330  # set logo_file "$repo_path/Hog/images/hog_logo.txt"
4331 
4332  set old_path [pwd]
4333  cd $repo_path/Hog
4334  set ver [Git {describe --always}]
4335  cd $old_path
4336 
4337  if {[file exists $logo_file]} {
4338  set f [open $logo_file "r"]
4339  set data [read $f]
4340  close $f
4341  set lines [split $data "\n"]
4342  foreach l $lines {
4343  Msg Status $l
4344  }
4345 
4346  } {
4347  Msg CriticalWarning "Logo file: $logo_file not found"
4348  }
4349 
4350  Msg Status "Version: $ver"
4351 }
4352 
4353 # Check last version online
4354 proc CheckLatestHogRelease {{repo_path .}} {
4355  set old_path [pwd]
4356  cd $repo_path/Hog
4357  set current_ver [Git {describe --always}]
4358  Msg Debug "Current version: $current_ver"
4359  set current_sha [Git "log $current_ver -1 --format=format:%H"]
4360  Msg Debug "Current SHA: $current_sha"
4361 
4362  #We should find a proper way of checking for timeout using vwait, this'll do for now
4363  if {[OS] == "windows" } {
4364  Msg Info "On windows we cannot set a timeout on 'git fetch', hopefully nothing will go wrong..."
4365  Git fetch
4366  } else {
4367  Msg Info "Checking for latest Hog release, can take up to 5 seconds..."
4368  ExecuteRet timeout 5s git fetch
4369  }
4370  set master_ver [Git "describe origin/master"]
4371  Msg Debug "Master version: $master_ver"
4372  set master_sha [Git "log $master_ver -1 --format=format:%H"]
4373  Msg Debug "Master SHA: $master_sha"
4374  set merge_base [Git "merge-base $current_sha $master_sha"]
4375  Msg Debug "merge base: $merge_base"
4376 
4377 
4378  if {$merge_base != $master_sha} {
4379  # If master_sha is NOT an ancestor of current_sha
4380  Msg Info "Version $master_ver has been released (https://gitlab.com/hog-cern/Hog/-/releases/$master_ver)"
4381  Msg Status "You should consider updating Hog submodule with the following instructions:"
4382  Msg Status ""
4383  Msg Status "cd Hog && git checkout master && git pull"
4384  Msg Status ""
4385  Msg Status "Also update the ref: in your .gitlab-ci.yml to $master_ver"
4386  Msg Status ""
4387  } else {
4388 
4389  # If it is
4390  Msg Info "Latest official version is $master_ver, nothing to do."
4391  }
4392 
4393  cd $old_path
4394 
4395 }
4396 
4397 # @brief Gets the command argv list and returns a list of
4398 # options and arguments
4399 proc GetOptions {argv parameters usage} {
4400  # Get Options from argv
4401  set arg_list [list]
4402  set param_list [list]
4403  set option_list [list]
4404 
4405  foreach p $parameters {
4406  lappend param_list [lindex $p 0]
4407  }
4408 
4409  set index 0
4410  while {$index < [llength $argv]} {
4411  set arg [lindex $argv $index]
4412  if {[string first - $arg] == 0} {
4413  set option [string trimleft $arg "-"]
4414  incr index
4415  lappend option_list $arg
4416  if {[lsearch $param_list ${option}*] >= 0 && [string first ".arg" [lsearch -inline $param_list ${option}*]] >= 0} {
4417  lappend option_list [lindex $argv $index]
4418  incr index
4419  }
4420  } else {
4421  lappend arg_list $arg
4422  incr index
4423  }
4424  }
4425  Msg Debug "Argv: $argv"
4426  Msg Debug "Options: $option_list"
4427  Msg Debug "Arguments: $arg_list"
4428  return [list $option_list $arg_list]
4429 }
4430 
4431 proc InitLauncher {script tcl_path parameters usage argv} {
4432  set repo_path [file normalize "$tcl_path/../.."]
4433  set old_path [pwd]
4434  set bin_path [file normalize "$tcl_path/../../bin"]
4435  set top_path [file normalize "$tcl_path/../../Top"]
4436  set commands_path [file normalize "$tcl_path/../../hog-commands/"]
4437 
4438  if {[IsTclsh]} {
4439  #Just display the logo the first time, not when the scripot is run in the IDE
4440  Logo $repo_path
4441  }
4442 
4443  if {[catch {package require cmdline} ERROR]} {
4444  Msg Debug "The cmdline Tcl package was not found, sourcing it from Hog..."
4445  source $tcl_path/utils/cmdline.tcl
4446  }
4447 
4448  append usage [GetCustomCommands $commands_path]
4449  append usage "\n** Options:"
4450 
4451  lassign [ GetOptions $argv $parameters $usage] option_list arg_list
4452 
4453  if { [IsInList "-help" $option_list] || [IsInList "-?" $option_list] || [IsInList "-h" $option_list] } {
4454  Msg Info [cmdline::usage $parameters $usage]
4455  exit 0
4456  }
4457 
4458  if {[catch {array set options [cmdline::getoptions option_list $parameters $usage]} err] } {
4459  Msg Status "\nERROR: Syntax error, probably unknown option.\n\n USAGE: $err"
4460  exit 1
4461  }
4462  # Argv here is modified and the options are removed
4463  set directive [string toupper [lindex $arg_list 0]]
4464 
4465  if { [llength $arg_list] == 1 && ($directive == "L" || $directive == "LIST")} {
4466  Msg Status "\n** The projects in this repository are:"
4467  ListProjects $repo_path
4468  Msg Status "\n"
4469  exit 0
4470 
4471  } elseif { [llength $arg_list] == 0 || [llength $arg_list] > 2} {
4472  Msg Status "\nERROR: Wrong number of arguments: [llength $argv].\n\n"
4473  Msg Status "USAGE: [cmdline::usage $parameters $usage]"
4474  exit 1
4475  }
4476 
4477  set project [lindex $arg_list 1]
4478  # Remove leading Top/ or ./Top/ if in project_name
4479  regsub "^(\./)?Top/" $project "" project
4480  # Remove trailing / and spaces if in project_name
4481  regsub "/? *\$" $project "" project
4482  set proj_conf [ProjectExists $project $repo_path]
4483 
4484  Msg Debug "Option list:"
4485  foreach {key value} [array get options] {
4486  Msg Debug "$key => $value"
4487  }
4488 
4489  set cmd ""
4490 
4491  if {[IsTclsh]} {
4492  # command is filled with the IDE exectuable when this function is called by Tcl scrpt
4493  if {$proj_conf != 0} {
4494  CheckLatestHogRelease $repo_path
4495 
4496  lassign [GetIDECommand $proj_conf] cmd before_tcl_script after_tcl_script end_marker
4497  Msg Info "Project $project uses $cmd IDE"
4498 
4499  ## The following is the IDE command to launch:
4500  set command "$cmd $before_tcl_script$script$after_tcl_script$argv$end_marker"
4501 
4502  } else {
4503  if {$project != ""} {
4504  #Project not given
4505  set command -1
4506  } else {
4507  #Project not found
4508  set command -2
4509  }
4510  }
4511  } else {
4512  # When the launcher is executed from within an IDE, command is set to 0
4513  set command 0
4514  }
4515 
4516  set project_group [file dirname $project]
4517  set project [file tail $project]
4518  if { $project_group != "." } {
4519  set project_name "$project_group/$project"
4520  } else {
4521  set project_name "$project"
4522  }
4523 
4524 
4525 
4526  return [list $directive $project $project_name $project_group $repo_path $old_path $bin_path $top_path $commands_path $command $cmd]
4527 }
4528 
4529 # Searches directory for tcl scripts to add as custom commands
4530 # Returns string of tcl scripts formatted as usage or switch statement
4531 # ret_commands if 1 returns commands as switch statement string instead of usage
4532 proc GetCustomCommands {{directory .} {ret_commands 0}} {
4533  set commands_dict [dict create]
4534  set commands_files [glob -nocomplain $directory/*.tcl ]
4535  set commands_string ""
4536 
4537  if {[llength $commands_files] == 0} {
4538  return ""
4539  }
4540 
4541  if {$ret_commands == 0} {
4542  append commands_string "\n** Custom Commands:\n"
4543  }
4544 
4545  foreach file $commands_files {
4546  set base_name [string toupper [file rootname [file tail $file]]]
4547  if {$ret_commands == 1} {
4548  append commands_string "
4549  \\^$base_name\$ {
4550  Msg Info \"Running custom script: $file\"
4551  source \"$file\"
4552  Msg Info \"Done running custom script...\"
4553  exit
4554  }
4555  "
4556  } else {
4557  append commands_string "- $base_name: runs $file \n"
4558  }
4559  }
4560  return $commands_string
4561 }
4562 
4563 # List projects all projects in the repository
4564 # print if 1 print a list
4565 # ret_conf if 1 returns conf file rather than list of project names
4566 proc ListProjects {{repo_path .} {print 1} {ret_conf 0}} {
4567  set top_path [file normalize $repo_path/Top]
4568  set confs [findFiles [file normalize $top_path] hog.conf]
4569  set projects ""
4570 
4571  foreach c $confs {
4572  set p [Relative $top_path [file dirname $c]]
4573  if {$print == 1} {
4574  # Print a list of the projects with relative IDE
4575  Msg Status "$p \([GetIDEFromConf $c]\)"
4576  }
4577  lappend projects $p
4578  }
4579 
4580  if {$ret_conf == 0} {
4581 
4582  # Returns a list of project names
4583  return $projects
4584  } else {
4585 
4586  # Return the list of hog.conf with full path
4587  return $confs
4588  }
4589 }
4590 
4591 # if it exists returns the conf file
4592 # if it doesnt returns 0
4593 proc ProjectExists {project {repo_path .}} {
4594  set index [lsearch -exact [ListProjects $repo_path 0] $project]
4595 
4596  if {$index >= 0} {
4597  # if project exists we return the relative hog.conf file
4598  return [lindex [ListProjects $repo_path 0 1] $index]
4599  } else {
4600  return 0
4601  }
4602 }
4603 
4604 # Find IDE for a project
4605 proc GetIDECommand {proj_conf} {
4606  # GetConfFiles returns a list, the first element is hog.conf
4607  if {[file exists $proj_conf]} {
4608  set ide_name_and_ver [string tolower [GetIDEFromConf $proj_conf]]
4609  set ide_name [lindex [regexp -all -inline {\S+} $ide_name_and_ver ] 0]
4610 
4611  if {$ide_name eq "vivado"} {
4612  set command "vivado"
4613  # A space ater the before_tcl_script is important
4614  set before_tcl_script " -nojournal -nolog -mode batch -notrace -source "
4615  set after_tcl_script " -tclargs "
4616  set end_marker ""
4617 
4618  } elseif {$ide_name eq "planahead"} {
4619  set command "planAhead"
4620  # A space ater the before_tcl_script is important
4621  set before_tcl_script " -nojournal -nolog -mode batch -notrace -source "
4622  set after_tcl_script " -tclargs "
4623  set end_marker ""
4624 
4625  } elseif {$ide_name eq "quartus"} {
4626  set command "quartus_sh"
4627  # A space ater the before_tcl_script is important
4628  set before_tcl_script " -t "
4629  set after_tcl_script " "
4630  set end_marker ""
4631 
4632  } elseif {$ide_name eq "libero"} {
4633  #I think we need quotes for libero, not sure...
4634 
4635  set command "libero"
4636  set before_tcl_script "SCRIPT:"
4637  set after_tcl_script " SCRIPT_ARGS:\""
4638  set end_marker "\""
4639  } else {
4640  Msg Error "IDE: $ide_name not known."
4641  }
4642 
4643  } else {
4644  Msg Error "Configuration file $proj_conf not found."
4645  }
4646 
4647  return [list $command $before_tcl_script $after_tcl_script $end_marker]
4648 }
4649 
4650 
4651 # findFiles
4652 # basedir - the directory to start looking in
4653 # pattern - A pattern, as defined by the glob command, that the files must match
4654 # Credit: https://stackexchange.com/users/14219/jackson
4655 proc findFiles { basedir pattern } {
4656 
4657  # Fix the directory name, this ensures the directory name is in the
4658  # native format for the platform and contains a final directory seperator
4659  set basedir [string trimright [file join [file normalize $basedir] { }]]
4660  set fileList {}
4661 
4662  # Look in the current directory for matching files, -type {f r}
4663  # means ony readable normal files are looked at, -nocomplain stops
4664  # an error being thrown if the returned list is empty
4665  foreach fileName [glob -nocomplain -type {f r} -path $basedir $pattern] {
4666  lappend fileList $fileName
4667  }
4668 
4669  # Now look for any sub direcories in the current directory
4670  foreach dirName [glob -nocomplain -type {d r} -path $basedir *] {
4671  # Recusively call the routine on the sub directory and append any
4672  # new files to the results
4673  set subDirList [findFiles $dirName $pattern]
4674  if { [llength $subDirList] > 0 } {
4675  foreach subDirFile $subDirList {
4676  lappend fileList $subDirFile
4677  }
4678  }
4679  }
4680  return $fileList
4681 }
4682 
4683 # Check if element is in list
4684 proc IsInList {element list} {
4685  if {[lsearch -exact $list $element] >= 0} {
4686  return 1
4687  } else {
4688  return 0
4689  }
4690 }
4691 
4692 # Function to check if a commit is an ancestor of another
4693 proc IsCommitAncestor {ancestor commit} {
4694  lassign [GitRet "merge-base --is-ancestor $ancestor $commit"] status result
4695  if {$status == 0 } {
4696  return 1
4697  } else {
4698  return 0
4699  }
4700 }
4701 
4702 proc FindCommonGitChild {SHA1 SHA2} {
4703  # Get the list of all commits in the repository
4704  set commits [Git {log --oneline --merges}]
4705  set ancestor 0
4706  # Iterate over each commit
4707  foreach line [split $commits "\n"] {
4708  set commit [lindex [split $line] 0]
4709 
4710  # Check if both SHA1 and SHA2 are ancestors of the commit
4711  if {[IsCommitAncestor $SHA1 $commit] && [IsCommitAncestor $SHA2 $commit] } {
4712  set ancestor $commit
4713  break
4714  }
4715  }
4716  return $ancestor
4717 }
4718 
4719 
4720 # Move an element in the list to the end
4721 proc moveElementToEnd {inputList element} {
4722  set index [lsearch $inputList $element]
4723  if {$index != -1} {
4724  set inputList [lreplace $inputList $index $index]
4725  lappend inputList $element
4726  }
4727  return $inputList
4728 }
4729 
4730 ### Source the Create project file
4731 source [file dirname [info script]]/create_project.tcl