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