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