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