Hog Hog2025.1-6
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 {\ytop\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 {\ytopsim\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 {\yruntime\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 {\yscoped_to_ref\s*=\s*(.+?)\y.*} $props] 1]
249  set cell [lindex [regexp -inline {\yscoped_to_cells\s*=\s*(.+?)\y.*} $props] 1]
250  if {([file extension $f] == ".tcl" || [file extension $f] == ".xdc") && $ext == ".con"} {
251  if {$ref != ""} {
252  set_property SCOPED_TO_REF $ref $file_obj
253  }
254  if {$cell != ""} {
255  set_property SCOPED_TO_CELLS $cell $file_obj
256  }
257  }
258 
259  }
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 {\ytop\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 {\ytop\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 {\ytop\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  if {[catch {package require ::quartus::flow} result]} {
3789  # not available
3790  return 0
3791  } else {
3792  # available
3793  return 1
3794  }
3795 }
3796 
3797 ## Check if a path is absolute or relative
3798 #
3799 # @param[in] the path to check
3800 #
3801 proc IsRelativePath {path} {
3802  if {[string index $path 0] == "/" || [string index $path 0] == "~"} {
3803  return 0
3804  } else {
3805  return 1
3806  }
3807 }
3808 
3809 ## @brief Returns true if the Synthesis tool is Synplify
3810 proc IsSynplify {} {
3811  return [expr {[info commands program_version] != ""}]
3812 }
3813 
3814 ## @brief Returns true, if we are in tclsh
3815 proc IsTclsh {} {
3816  return [expr {![IsQuartus] && ![IsXilinx] && ![IsLibero] && ![IsSynplify] && ![IsDiamond]}]
3817 }
3818 
3819 ## @brief Find out if the given Xilinx part is a Vesal chip
3820 #
3821 # @param[out] 1 if it's Versal 0 if it's not
3822 # @param[in] part The FPGA part
3823 #
3824 proc IsVersal {part} {
3825  if {[get_property ARCHITECTURE [get_parts $part]] eq "versal"} {
3826  return 1
3827  } else {
3828  return 0
3829  }
3830 }
3831 
3832 ## @brief Returns true, if the IDE is Vivado
3833 proc IsVivado {} {
3834  if {[IsXilinx]} {
3835  return [expr {[string first Vivado [version]] == 0}]
3836  } else {
3837  return 0
3838  }
3839 }
3840 
3841 ## @brief Return true, if the IDE is Xilinx (Vivado or ISE)
3842 proc IsXilinx {} {
3843  if {[info commands version] != ""} {
3844  set current_version [version]
3845  if {[string first PlanAhead $current_version] == 0 || [string first Vivado $current_version] == 0} {
3846  return 1
3847  } else {
3848  Msg Warning "This IDE has the version command but it is not PlanAhead or Vivado: $current_version"
3849  return 0
3850  }
3851  } else {
3852  return 0
3853  }
3854 }
3855 
3856 ## @brief Find out if the given Xilinx part is a Vesal chip
3857 #
3858 # @param[out] 1 if it's Zynq 0 if it's not
3859 # @param[in] part The FPGA part
3860 #
3861 proc IsZynq {part} {
3862  if { [regexp {^(xc7z|xczu).*} $part] } {
3863  return 1
3864  } else {
3865  return 0
3866  }
3867 }
3868 
3869 # @brief Launch the Implementation, for the current IDE and project
3870 #
3871 # @param[in] reset Reset the Implementation run
3872 # @param[in] do_create Recreate the project
3873 # @param[in] run_folder The folder where to store the run results
3874 # @param[in] project_name The name of the project
3875 # @param[in] repo_path The main path of the git repository (Default .)
3876 # @param[in] njobs The number of parallel CPU jobs for the Implementation (Default 4)
3877 proc LaunchImplementation {reset do_create run_folder project_name {repo_path .} {njobs 4} {do_bitstream 0}} {
3878  Msg Info "Starting implementation flow..."
3879  if {[IsXilinx]} {
3880  if { $reset == 1 && $do_create == 0} {
3881  Msg Info "Resetting run before launching implementation..."
3882  reset_run impl_1
3883  }
3884 
3885  if {[IsISE]} {
3886  source $repo_path/Hog/Tcl/integrated/pre-implementation.tcl
3887  }
3888 
3889  if {$do_bitstream == 1} {
3890  launch_runs impl_1 -to_step [BinaryStepName [get_property PART [current_project]]] $njobs -dir $run_folder
3891  } else {
3892  launch_runs impl_1 -jobs $njobs -dir $run_folder
3893  }
3894  wait_on_run impl_1
3895 
3896  if {[IsISE]} {
3897  Msg Info "running post-implementation"
3898  source $repo_path/Hog/Tcl/integrated/post-implementation.tcl
3899  if {$do_bitstream == 1} {
3900  Msg Info "running pre-bitstream"
3901  source $repo_path/Hog/Tcl/integrated/pre-bitstream.tcl
3902  Msg Info "running post-bitstream"
3903  source $repo_path/Hog/Tcl/integrated/post-bitstream.tcl
3904  }
3905  }
3906 
3907  set prog [get_property PROGRESS [get_runs impl_1]]
3908  set status [get_property STATUS [get_runs impl_1]]
3909  Msg Info "Run: impl_1 progress: $prog, status : $status"
3910 
3911  # Check timing
3912  if {[IsISE]} {
3913  set status_file [open "$run_folder/timing.txt" "w"]
3914  puts $status_file "## $project_name Timing summary"
3915 
3916  set f [open [lindex [glob "$run_folder/impl_1/*.twr" 0]]]
3917  set errs -1
3918  while {[gets $f line] >= 0} {
3919  if { [string match "Timing summary:" $line] } {
3920  while {[gets $f line] >= 0} {
3921  if { [string match "Timing errors:*" $line] } {
3922  set errs [regexp -inline -- {[0-9]+} $line]
3923  }
3924  if { [string match "*Footnotes*" $line ] } {
3925  break
3926  }
3927  puts $status_file "$line"
3928  }
3929  }
3930  }
3931 
3932  close $f
3933  close $status_file
3934 
3935  if {$errs == 0} {
3936  Msg Info "Time requirements are met"
3937  file rename -force "$run_folder/timing.txt" "$run_folder/timing_ok.txt"
3938  set timing_ok 1
3939  } else {
3940  Msg CriticalWarning "Time requirements are NOT met"
3941  file rename -force "$run_folder/timing.txt" "$run_folder/timing_error.txt"
3942  set timing_ok 0
3943  }
3944  }
3945 
3946  if {[IsVivado]} {
3947  set wns [get_property STATS.WNS [get_runs [current_run]]]
3948  set tns [get_property STATS.TNS [get_runs [current_run]]]
3949  set whs [get_property STATS.WHS [get_runs [current_run]]]
3950  set ths [get_property STATS.THS [get_runs [current_run]]]
3951  set tpws [get_property STATS.TPWS [get_runs [current_run]]]
3952 
3953  if {$wns >= 0 && $whs >= 0 && $tpws >= 0} {
3954  Msg Info "Time requirements are met"
3955  set status_file [open "$run_folder/timing_ok.txt" "w"]
3956  set timing_ok 1
3957  } else {
3958  Msg CriticalWarning "Time requirements are NOT met"
3959  set status_file [open "$run_folder/timing_error.txt" "w"]
3960  set timing_ok 0
3961  }
3962 
3963  Msg Status "*** Timing summary ***"
3964  Msg Status "WNS: $wns"
3965  Msg Status "TNS: $tns"
3966  Msg Status "WHS: $whs"
3967  Msg Status "THS: $ths"
3968  Msg Status "TPWS: $tpws"
3969 
3970  struct::matrix m
3971  m add columns 5
3972  m add row
3973 
3974  puts $status_file "## $project_name Timing summary"
3975 
3976  m add row "| **Parameter** | \"**value (ns)**\" |"
3977  m add row "| --- | --- |"
3978  m add row "| WNS: | $wns |"
3979  m add row "| TNS: | $tns |"
3980  m add row "| WHS: | $whs |"
3981  m add row "| THS: | $ths |"
3982  m add row "| TPWS: | $tpws |"
3983 
3984  puts $status_file [m format 2string]
3985  puts $status_file "\n"
3986  if {$timing_ok == 1} {
3987  puts $status_file " Time requirements are met."
3988  } else {
3989  puts $status_file "Time requirements are **NOT** met."
3990  }
3991  puts $status_file "\n\n"
3992  close $status_file
3993  }
3994 
3995  if {$prog ne "100%"} {
3996  Msg Error "Implementation error"
3997  }
3998 
3999  #Go to repository path
4000  cd $repo_path
4001  lassign [GetRepoVersions [file normalize ./Top/$project_name] $repo_path] sha
4002  set describe [GetHogDescribe $sha $repo_path]
4003  Msg Info "Git describe set to $describe"
4004 
4005  set dst_dir [file normalize "$repo_path/bin/$project_name\-$describe"]
4006 
4007  file mkdir $dst_dir
4008 
4009  #Version table
4010  if {[file exists $run_folder/versions.txt]} {
4011  file copy -force $run_folder/versions.txt $dst_dir
4012  } else {
4013  Msg Warning "No versions file found in $run_folder/versions.txt"
4014  }
4015  #Timing file
4016  set timing_files [ glob -nocomplain "$run_folder/timing_*.txt" ]
4017  set timing_file [file normalize [lindex $timing_files 0]]
4018 
4019  if {[file exists $timing_file]} {
4020  file copy -force $timing_file $dst_dir/
4021  } else {
4022  Msg Warning "No timing file found, not a problem if running locally"
4023  }
4024 
4025  } elseif {[IsQuartus]} {
4026  set revision [get_current_revision]
4027 
4028  if {[catch {execute_module -tool fit} result]} {
4029  Msg Error "Result: $result\n"
4030  Msg Error "Place & Route failed. See the report file.\n"
4031  } else {
4032  Msg Info "\nINFO: Place & Route was successful for revision $revision.\n"
4033  }
4034 
4035  if {[catch {execute_module -tool sta -args "--do_report_timing"} result]} {
4036  Msg Error "Result: $result\n"
4037  Msg Error "Time Quest failed. See the report file.\n"
4038  } else {
4039  Msg Info "Time Quest was successfully run for revision $revision.\n"
4040  load_package report
4041  load_report
4042  set panel "Timing Analyzer||Timing Analyzer Summary"
4043  set device [ get_report_panel_data -name $panel -col 1 -row_name "Device Name" ]
4044  set timing_model [ get_report_panel_data -name $panel -col 1 -row_name "Timing Models" ]
4045  set delay_model [ get_report_panel_data -name $panel -col 1 -row_name "Delay Model" ]
4046  #set slack [ get_timing_analysis_summary_results -slack ]
4047  Msg Info "*******************************************************************"
4048  Msg Info "Device: $device"
4049  Msg Info "Timing Models: $timing_model"
4050  Msg Info "Delay Model: $delay_model"
4051  Msg Info "Slack:"
4052  #Msg Info $slack
4053  Msg Info "*******************************************************************"
4054  }
4055  } elseif {[IsLibero]} {
4056  Msg Info "Starting implementation flow..."
4057  if {[catch {run_tool -name {PLACEROUTE} }] } {
4058  Msg Error "PLACEROUTE FAILED!"
4059  } else {
4060  Msg Info "PLACEROUTE PASSED."
4061  }
4062 
4063  # Check timing
4064  Msg Info "Run VERIFYTIMING ..."
4065  if {[catch {run_tool -name {VERIFYTIMING} -script {Hog/Tcl/integrated/libero_timing.tcl} }] } {
4066  Msg CriticalWarning "VERIFYTIMING FAILED!"
4067  } else {
4068  Msg Info "VERIFYTIMING PASSED \n"
4069  }
4070 
4071  #Go to repository path
4072  cd $repo_path
4073 
4074  lassign [GetRepoVersions [file normalize ./Top/$project_name] $repo_path] sha
4075  set describe [GetHogDescribe $sha $repo_path]
4076  Msg Info "Git describe set to $describe"
4077 
4078  set dst_dir [file normalize "$repo_path/bin/$project_name\-$describe"]
4079  file mkdir $dst_dir/reports
4080 
4081  #Version table
4082  if {[file exists $run_folder/versions.txt]} {
4083  file copy -force $run_folder/versions.txt $dst_dir
4084  } else {
4085  Msg Warning "No versions file found in $run_folder/versions.txt"
4086  }
4087  #Timing file
4088  set timing_file_path [file normalize "$repo_path/Projects/timing_libero.txt"]
4089  if {[file exists $timing_file_path]} {
4090  file copy -force $timing_file_path $dst_dir/reports/Timing.txt
4091  set timing_file [open $timing_file_path "r"]
4092  set status_file [open "$dst_dir/timing.txt" "w"]
4093  puts $status_file "## $project_name Timing summary\n\n"
4094  puts $status_file "| | |"
4095  puts $status_file "| --- | --- |"
4096  while {[gets $timing_file line] >= 0} {
4097  if { [string match "SUMMARY" $line] } {
4098  while {[gets $timing_file line] >= 0} {
4099  if { [string match "END SUMMARY" $line ] } {
4100  break
4101  }
4102  if {[string first ":" $line] == -1} {
4103  continue
4104  }
4105  set out_string "| [string map {: | } $line] |"
4106  puts $status_file "$out_string"
4107  }
4108  }
4109  }
4110  } else {
4111  Msg Warning "No timing file found, not a problem if running locally"
4112  }
4113  } elseif {[IsDiamond]} {
4114  set force_rst ""
4115  if {$reset == 1} {
4116  set force_rst "-forceOne"
4117  }
4118  prj_run Map $force_rst
4119  prj_run PAR $force_rst
4120 
4121  # TODO: Check Timing for Diamond
4122  }
4123 }
4124 
4125 
4126 # @brief Launch the simulation (Vivado only for the moment)
4127 #
4128 # @param[in] project_name The name of the project
4129 # @param[in] lib_path The path to the simulation libraries
4130 # @param[in] simsets The simulation sets to simulate
4131 # @param[in] repo_path The main path of the git repository
4132 proc LaunchSimulation {project_name lib_path {simsets ""} {repo_path .}} {
4133  if {[IsVivado]} {
4134  ##################### SIMULATION #######################
4135  set project [file tail $project_name]
4136  set main_sim_folder [file normalize "$repo_path/Projects/$project_name/$project.sim/"]
4137  set simsets_todo ""
4138  if {$simsets != ""} {
4139  set simsets_todo [split $simsets ","]
4140  Msg Info "Will run only the following simsets (if they exist): $simsets_todo"
4141  }
4142 
4143  set failed []
4144  set success []
4145  set sim_dic [dict create]
4146  # Default behaviour, dont use simpass string
4147  set use_simpass_str 0
4148 
4149  # Get simulation properties from conf file
4150  set proj_dir [file normalize $repo_path/Top/$project_name]
4151  set sim_file [file normalize $proj_dir/sim.conf]
4152  if {[file exists $sim_file]} {
4153  Msg Info "Parsing simulation configuration file $sim_file..."
4154  SetGlobalVar SIM_PROPERTIES [ReadConf $sim_file]
4155  } else {
4156  SetGlobalVar SIM_PROPERTIES ""
4157  }
4158 
4159  # Get Hog specific simulation properties
4160  if {[dict exists $globalSettings::SIM_PROPERTIES hog]} {
4161  set hog_sim_props [dict get $globalSettings::SIM_PROPERTIES hog]
4162  dict for {prop_name prop_val} $hog_sim_props {
4163  # If HOG_SIMPASS_STR is set, use the HOG_SIMPASS_STR string to search for in logs, after simulation is done
4164  if { $prop_name == "HOG_SIMPASS_STR" && $prop_val != "" } {
4165  Msg Info "Setting simulation pass string as '$prop_val'"
4166  set use_simpass_str 1
4167  set simpass_str $prop_val
4168  }
4169  }
4170  }
4171 
4172  Msg Info "Retrieving list of simulation sets..."
4173  foreach s [get_filesets] {
4174  set type [get_property FILESET_TYPE $s]
4175  if {$type eq "SimulationSrcs"} {
4176  if {$simsets_todo != "" && $s ni $simsets_todo} {
4177  Msg Info "Skipping $s as it was not specified with the -simset option..."
4178  continue
4179  }
4180  if {[file exists "$repo_path/Top/$project_name/list/$s.sim"]} {
4181  set fp [open "$repo_path/Top/$project_name/list/$s.sim" r]
4182  set file_data [read $fp]
4183  close $fp
4184  set data [split $file_data "\n"]
4185  set n [llength $data]
4186  Msg Info "$n lines read from $s.sim"
4187 
4188  set firstline [lindex $data 0]
4189  # Find simulator
4190  if { [regexp {^ *\#Simulator} $firstline] } {
4191  set simulator_prop [regexp -all -inline {\S+} $firstline]
4192  set simulator [string tolower [lindex $simulator_prop 1]]
4193  } else {
4194  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."
4195  set simulator "xsim"
4196  }
4197  if {$simulator eq "skip_simulation"} {
4198  Msg Info "Skipping simulation for $s"
4199  continue
4200  }
4201  set_property "target_simulator" $simulator [current_project]
4202  Msg Info "Creating simulation scripts for $s..."
4203  if { [file exists $repo_path/Top/$project_name/pre-simulation.tcl] } {
4204  Msg Info "Running $repo_path/Top/$project_name/pre-simulation.tcl"
4205  source $repo_path/Top/$project_name/pre-simulation.tcl
4206  }
4207  if { [file exists $repo_path/Top/$project_name/pre-$s-simulation.tcl] } {
4208  Msg Info "Running $repo_path/Top/$project_name/pre-$s-simulation.tcl"
4209  source Running $repo_path/Top/$project_name/pre-$s-simulation.tcl
4210  }
4211  current_fileset -simset $s
4212  set sim_dir $main_sim_folder/$s/behav
4213  set sim_output_logfile $sim_dir/xsim/simulate.log
4214  if { ([string tolower $simulator] eq "xsim") } {
4215  set sim_name "xsim:$s"
4216  if { [catch { launch_simulation -simset [get_filesets $s] } log] } {
4217  # Explicitly close xsim simulation, without closing Vivado
4218  close_sim
4219  Msg CriticalWarning "Simulation failed for $s, error info: $::errorInfo"
4220  lappend failed $sim_name
4221  } else {
4222  # Explicitly close xsim simulation, without closing Vivado
4223  close_sim
4224  # If we use simpass_str, search for the string and update return code from simulation if the string is not found in simulation log
4225  if {$use_simpass_str == 1} {
4226  # Get the simulation output log
4227  # Note, xsim should always output simulation.log, hence no check for existence
4228  set file_desc [open $sim_output_logfile r]
4229  set log [read $file_desc]
4230  close $file_desc
4231 
4232  Msg Info "Searching for simulation pass string: '$simpass_str'"
4233  if {[string first $simpass_str $log] == -1} {
4234  Msg CriticalWarning "Simulation failed for $s, error info: '$simpass_str' NOT found!"
4235  lappend failed $sim_name
4236  } else {
4237  # HOG_SIMPASS_STR found, success
4238  lappend success $sim_name
4239  }
4240  } else { #Rely on simulator exit code
4241  lappend success $sim_name
4242  }
4243  }
4244  } else {
4245  Msg Info "Simulation library path is set to $lib_path."
4246  set simlib_ok 1
4247  if {!([file exists $lib_path])} {
4248  Msg Warning "Could not find simulation library path: $lib_path, $simulator simulation will not work."
4249  set simlib_ok 0
4250  }
4251 
4252  if {$simlib_ok == 1} {
4253  set_property "compxlib.${simulator}_compiled_library_dir" [file normalize $lib_path] [current_project]
4254  launch_simulation -scripts_only -simset [get_filesets $s]
4255  set top_name [get_property TOP $s]
4256  set sim_script [file normalize $sim_dir/$simulator/]
4257  Msg Info "Adding simulation script location $sim_script for $s..."
4258  lappend sim_scripts $sim_script
4259  dict append sim_dic $sim_script $s
4260  } else {
4261  Msg Error "Cannot run $simulator simulations without a valid library path"
4262  exit -1
4263  }
4264  }
4265  }
4266  }
4267  }
4268 
4269  if {[info exists sim_scripts]} {
4270  # Only for modelsim/questasim
4271  Msg Info "Generating IP simulation targets, if any..."
4272 
4273  foreach ip [get_ips] {
4274  generate_target simulation -quiet $ip
4275  }
4276 
4277 
4278  Msg Status "\n\n"
4279  Msg Info "====== Starting simulations runs ======"
4280  Msg Status "\n\n"
4281 
4282  foreach s $sim_scripts {
4283  cd $s
4284  set cmd ./compile.sh
4285  Msg Info " ************* Compiling: $s ************* "
4286  lassign [ExecuteRet $cmd] ret log
4287  set sim_name "comp:[dict get $sim_dic $s]"
4288  if {$ret != 0} {
4289  Msg CriticalWarning "Compilation failed for $s, error info: $::errorInfo"
4290  lappend failed $sim_name
4291  } else {
4292  lappend success $sim_name
4293  }
4294  Msg Info "###################### Compilation log starts ######################"
4295  Msg Info "\n\n$log\n\n"
4296  Msg Info "###################### Compilation log ends ######################"
4297 
4298 
4299  if { [file exists "./elaborate.sh"] } {
4300  set cmd ./elaborate.sh
4301  Msg Info " ************* Elaborating: $s ************* "
4302  lassign [ExecuteRet $cmd] ret log
4303  set sim_name "elab:[dict get $sim_dic $s]"
4304  if {$ret != 0} {
4305  Msg CriticalWarning "Elaboration failed for $s, error info: $::errorInfo"
4306  lappend failed $sim_name
4307  } else {
4308  lappend success $sim_name
4309  }
4310  Msg Info "###################### Elaboration log starts ######################"
4311  Msg Info "\n\n$log\n\n"
4312  Msg Info "###################### Elaboration log ends ######################"
4313  }
4314  set cmd ./simulate.sh
4315  Msg Info " ************* Simulating: $s ************* "
4316  lassign [ExecuteRet $cmd] ret log
4317 
4318 
4319  # If SIMPASS_STR is set, search log for the string
4320  if {$use_simpass_str == 1} {
4321  if {[string first $simpass_str $log] == -1} {
4322  set ret 1
4323  }
4324  } else {
4325  Msg Debug "Simulation pass string not set, relying on simulator exit code."
4326  }
4327 
4328 
4329 
4330  set sim_name "sim:[dict get $sim_dic $s]"
4331  if {$ret != 0} {
4332  Msg CriticalWarning "Simulation failed for $s, error info: $::errorInfo"
4333  lappend failed $sim_name
4334  } else {
4335  lappend success $sim_name
4336  }
4337  Msg Info "###################### Simulation log starts ######################"
4338  Msg Info "\n\n$log\n\n"
4339  Msg Info "###################### Simulation log ends ######################"
4340 
4341  }
4342  }
4343 
4344 
4345  if {[llength $success] > 0} {
4346  set successes [join $success "\n"]
4347  Msg Info "The following simulation sets were successful:\n\n$successes\n\n"
4348  }
4349 
4350  if {[llength $failed] > 0} {
4351  set failures [join $failed "\n"]
4352  Msg Error "The following simulation sets have failed:\n\n$failures\n\n"
4353  exit -1
4354  } elseif {[llength $success] > 0} {
4355  Msg Info "All the [llength $success] compilations, elaborations and simulations were successful."
4356  }
4357 
4358  Msg Info "Simulation done."
4359  } else {
4360  Msg Warning "Simulation is not yet supported for [GetIDEName]."
4361  }
4362 }
4363 
4364 # @brief Launch the synthesis, for the current IDE and project
4365 #
4366 # @param[in] reset Reset the Synthesis run
4367 # @param[in] do_create Recreate the project
4368 # @param[in] run_folder The folder where to store the run results
4369 # @param[in] project_name The name of the project
4370 # @param[in] repo_path The main path of the git repository (Default .)
4371 # @param[in] ext_path The path of source files external to the git repo (Default "")
4372 # @param[in] njobs The number of parallel CPU jobs for the synthesis (Default 4)
4373 proc LaunchSynthesis {reset do_create run_folder project_name {repo_path .} {ext_path ""} {njobs 4}} {
4374  if {[IsXilinx]} {
4375  if {$reset == 1 && $do_create == 0} {
4376  Msg Info "Resetting run before launching synthesis..."
4377  reset_run synth_1
4378  }
4379  if {[IsISE]} {
4380  source $repo_path/Hog/Tcl/integrated/pre-synthesis.tcl
4381  }
4382  launch_runs synth_1 -jobs $njobs -dir $run_folder
4383  wait_on_run synth_1
4384  set prog [get_property PROGRESS [get_runs synth_1]]
4385  set status [get_property STATUS [get_runs synth_1]]
4386  Msg Info "Run: synth_1 progress: $prog, status : $status"
4387  # Copy IP reports in bin/
4388  set ips [get_ips *]
4389 
4390  #go to repository path
4391  cd $repo_path
4392 
4393  lassign [GetRepoVersions [file normalize ./Top/$project_name] $repo_path $ext_path ] sha
4394  set describe [GetHogDescribe $sha $repo_path]
4395  Msg Info "Git describe set to $describe"
4396 
4397  foreach ip $ips {
4398  set xci_file [get_property IP_FILE $ip]
4399 
4400  set xci_path [file dirname $xci_file]
4401  set xci_ip_name [file rootname [file tail $xci_file]]
4402  foreach rptfile [glob -nocomplain -directory $xci_path *.rpt] {
4403  file copy $rptfile $repo_path/bin/$project_name-$describe/reports
4404  }
4405 
4406  # Let's leave the following commented part
4407  # 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
4408  #
4409  # ######### Copy IP to IP repository
4410  # if {[IsVivado]} {
4411  # set gen_path [get_property IP_OUTPUT_DIR $ip]
4412  # if {($ip_path != "")} {
4413  # # IP is not in the gitlab repo
4414  # set force 0
4415  # if [info exist runs] {
4416  # if {[lsearch $runs $ip\_synth_1] != -1} {
4417  # Msg Info "$ip was synthesized, will force the copy to the IP repository..."
4418  # set force 1
4419  # }
4420  # }
4421  # Msg Info "Copying synthesised IP $xci_ip_name ($xci_file) to $ip_path..."
4422  # HandleIP push $xci_file $ip_path $repo_path $gen_path $force
4423  # }
4424  # }
4425  }
4426 
4427  if {$prog ne "100%"} {
4428  Msg Error "Synthesis error, status is: $status"
4429  }
4430  } elseif {[IsQuartus]} {
4431  # TODO: Missing reset
4432  set project [file tail [file rootname $project_name]]
4433 
4434  Msg Info "Number of jobs set to $njobs."
4435  set_global_assignment -name NUM_PARALLEL_PROCESSORS $njobs
4436 
4437 
4438  # keep track of the current revision and of the top level entity name
4439  lassign [GetRepoVersions [file normalize $repo_path/Top/$project_name] $repo_path ] sha
4440  set describe [GetHogDescribe $sha $repo_path]
4441  #set top_level_name [ get_global_assignment -name TOP_LEVEL_ENTITY ]
4442  set revision [get_current_revision]
4443 
4444  #run PRE_FLOW_SCRIPT by hand
4445  set tool_and_command [ split [get_global_assignment -name PRE_FLOW_SCRIPT_FILE] ":"]
4446  set tool [lindex $tool_and_command 0]
4447  set pre_flow_script [lindex $tool_and_command 1]
4448  set cmd "$tool -t $pre_flow_script quartus_map $project $revision"
4449  #Close project to avoid conflict with pre synthesis script
4450  project_close
4451 
4452  lassign [ExecuteRet {*}$cmd ] ret log
4453  if {$ret != 0} {
4454  Msg Warning "Can not execute command $cmd"
4455  Msg Warning "LOG: $log"
4456  } else {
4457  Msg Info "Pre flow script executed!"
4458  }
4459 
4460  # Re-open project
4461  if { ![is_project_open ] } {
4462  Msg Info "Re-opening project file $project_name..."
4463  project_open $project -current_revision
4464  }
4465 
4466  # Generate IP Files
4467  if {[catch {execute_module -tool ipg -args "--clean"} result]} {
4468  Msg Error "Result: $result\n"
4469  Msg Error "IP Generation failed. See the report file.\n"
4470  } else {
4471  Msg Info "IP Generation was successful for revision $revision.\n"
4472  }
4473 
4474  # Execute synthesis
4475  if {[catch {execute_module -tool map -args "--parallel"} result]} {
4476  Msg Error "Result: $result\n"
4477  Msg Error "Analysis & Synthesis failed. See the report file.\n"
4478  } else {
4479  Msg Info "Analysis & Synthesis was successful for revision $revision.\n"
4480  }
4481  } elseif {[IsLibero]} {
4482  # TODO: Missing Reset
4483  defvar_set -name RWNETLIST_32_64_MIXED_FLOW -value 0
4484 
4485  Msg Info "Run SYNTHESIS..."
4486  if {[catch {run_tool -name {SYNTHESIZE} }] } {
4487  Msg Error "SYNTHESIZE FAILED!"
4488  } else {
4489  Msg Info "SYNTHESIZE PASSED!"
4490  }
4491  } elseif {[IsDiamond]} {
4492  # TODO: Missing Reset
4493  set force_rst ""
4494  if {$reset == 1} {
4495  set force_rst "-forceOne"
4496  }
4497  prj_run Synthesis $force_rst
4498  if {[prj_syn] == "synplify"} {
4499  prj_run Translate $force_rst
4500  }
4501  } else {
4502  Msg Error "Impossible condition. You need to run this in an IDE."
4503  exit 1
4504  }
4505 }
4506 
4507 # Returns the list of all the Hog Projects in the repository
4508 #
4509 # @param[in] repo_path The main path of the git repository
4510 # @param[in] print if 1 print the list of projects in the repository, if 2 does not print test projects
4511 # @param[in] ret_conf if 1 returns conf file rather than list of project names
4512 
4513 proc ListProjects {{repo_path .} {print 1} {ret_conf 0}} {
4514  set top_path [file normalize $repo_path/Top]
4515  set confs [findFiles [file normalize $top_path] hog.conf]
4516  set projects ""
4517  set confs [lsort $confs]
4518  set g ""
4519 
4520  foreach c $confs {
4521  set p [Relative $top_path [file dirname $c]]
4522  if {$print >= 1} {
4523  set description [DescriptionFromConf $c]
4524  if { $description eq "test"} {
4525  set description " - Test project"
4526  } elseif { $description ne ""} {
4527  set description " - $description"
4528  }
4529 
4530  if {$print == 1 || $description ne " - Test project"} {
4531  set old_g $g
4532  set g [file dirname $p]
4533  # Print a list of the projects with relative IDE and description (second line comment in hog.conf)
4534  if {$g ne $old_g} {
4535  Msg Status ""
4536  }
4537  Msg Status "$p \([GetIDEFromConf $c]\)$description"
4538  }
4539  }
4540  lappend projects $p
4541  }
4542 
4543  if {$ret_conf == 0} {
4544 
4545  # Returns a list of project names
4546  return $projects
4547  } else {
4548 
4549  # Return the list of hog.conf with full path
4550  return $confs
4551  }
4552 }
4553 
4554 
4555 # @brief Print the Hog Logo
4556 #
4557 # @param[in] repo_path The main path of the git repository (default .)
4558 proc Logo { {repo_path .} } {
4559  if {[info exists ::env(HOG_COLORED)] && [string match "ENABLED" $::env(HOG_COLORED)]} {
4560  set logo_file "$repo_path/Hog/images/hog_logo_color.txt"
4561  } else {
4562  set logo_file "$repo_path/Hog/images/hog_logo.txt"
4563  }
4564 
4565  set old_path [pwd]
4566  cd $repo_path/Hog
4567  set ver [Git {describe --always}]
4568  cd $old_path
4569 
4570  if {[file exists $logo_file]} {
4571  set f [open $logo_file "r"]
4572  set data [read $f]
4573  close $f
4574  set lines [split $data "\n"]
4575  foreach l $lines {
4576  Msg Status $l
4577  }
4578 
4579  } {
4580  Msg CriticalWarning "Logo file: $logo_file not found"
4581  }
4582 
4583  Msg Status "Version: $ver"
4584 }
4585 
4586 ## @brief Evaluates the md5 sum of a file
4587 #
4588 # @param[in] file_name: the name of the file of which you want to evaluate the md5 checksum
4589 proc Md5Sum {file_name} {
4590  if {!([file exists $file_name])} {
4591  Msg Warning "Could not find $file_name."
4592  set file_hash -1
4593  }
4594  if {[catch {package require md5 2.0.7} result]} {
4595  Msg Warning "Tcl package md5 version 2.0.7 not found ($result), will use command line..."
4596  set hash [lindex [Execute md5sum $file_name] 0]
4597  } else {
4598  set file_hash [string tolower [md5::md5 -hex -file $file_name]]
4599  }
4600 }
4601 
4602 ## @brief Merges two tcl dictionaries of lists
4603 #
4604 # If the dictionaries contain same keys, the list at the common key is a merging of the two
4605 #
4606 # @param[in] dict0 the name of the first dictionary
4607 # @param[in] dict1 the name of the second dictionary
4608 #
4609 # @return the merged dictionary
4610 #
4611 proc MergeDict {dict0 dict1} {
4612  set outdict [dict merge $dict1 $dict0]
4613  foreach key [dict keys $dict1 ] {
4614  if {[dict exists $dict0 $key]} {
4615  set temp_list [dict get $dict1 $key]
4616  foreach vhdfile $temp_list {
4617  # Avoid duplication
4618  if {[IsInList $vhdfile [DictGet $outdict $key]] == 0} {
4619  dict lappend outdict $key $vhdfile
4620  }
4621  }
4622  }
4623  }
4624  return $outdict
4625 }
4626 
4627 # @brief Move an element in the list to the end
4628 #
4629 # @param[in] inputList the list
4630 # @param[in] element the element to move to the end of the list
4631 proc MoveElementToEnd {inputList element} {
4632  set index [lsearch $inputList $element]
4633  if {$index != -1} {
4634  set inputList [lreplace $inputList $index $index]
4635  lappend inputList $element
4636  }
4637  return $inputList
4638 }
4639 
4640 ## @brief The Hog Printout Msg function
4641 #
4642 # @param[in] level The severity level (status, info, warning, critical, error, debug)
4643 # @param[in] msg The message to print
4644 # @param[in] title The title string to be included in the header of the message [Hog:$title] (default "")
4645 proc Msg {level msg {title ""}} {
4646  set level [string tolower $level]
4647  if {$title == ""} {set title [lindex [info level [expr {[info level]-1}]] 0]}
4648  if {$level == 0 || $level == "status" || $level == "extra_info"} {
4649  set vlevel {STATUS}
4650  set qlevel info
4651  } elseif {$level == 1 || $level == "info"} {
4652  set vlevel {INFO}
4653  set qlevel info
4654  } elseif {$level == 2 || $level == "warning"} {
4655  set vlevel {WARNING}
4656  set qlevel warning
4657  } elseif {$level == 3 || [string first "critical" $level] !=-1} {
4658  set vlevel {CRITICAL WARNING}
4659  set qlevel critical_warning
4660  } elseif {$level == 4 || $level == "error"} {
4661  set vlevel {ERROR}
4662  set qlevel error
4663  } elseif {$level == 5 || $level == "debug"} {
4664  if {([info exists ::DEBUG_MODE] && $::DEBUG_MODE == 1) || ([info exists ::env(HOG_DEBUG_MODE)] && $::env(HOG_DEBUG_MODE) == 1)} {
4665  set vlevel {STATUS}
4666  set qlevel extra_info
4667  set msg "DEBUG: \[Hog:$title\] $msg"
4668  } else {
4669  return
4670  }
4671  } else {
4672  puts "Hog Error: level $level not defined"
4673  exit -1
4674  }
4675 
4676 
4677  if {[IsXilinx]} {
4678  # Vivado
4679  set status [catch {send_msg_id Hog:$title-0 $vlevel $msg}]
4680  if {$status != 0} {
4681  exit $status
4682  }
4683  } elseif {[IsQuartus]} {
4684  # Quartus
4685  post_message -type $qlevel "Hog:$title $msg"
4686  if { $qlevel == "error"} {
4687  exit 1
4688  }
4689  } else {
4690  # Tcl Shell / Libero
4691  if {$vlevel != "STATUS"} {
4692  puts "$vlevel: \[Hog:$title\] $msg"
4693  } else {
4694  puts $msg
4695  }
4696 
4697  if {$qlevel == "error"} {
4698  exit 1
4699  }
4700  }
4701 }
4702 
4703 ## @brief Prints a message with selected severity and optionally write into a log file
4704 #
4705 # @param[in] msg The message to print
4706 # @param[in] severity The severity of the message
4707 # @param[in] outFile The path of the output logfile
4708 #
4709 proc MsgAndLog {msg {severity "CriticalWarning"} {outFile ""}} {
4710  Msg $severity $msg
4711  if {$outFile != ""} {
4712  set oF [open "$outFile" a+]
4713  puts $oF $msg
4714  close $oF
4715  }
4716 }
4717 
4718 # @brief Open the project with the corresponding IDE
4719 #
4720 # @param[in] project_file The project_file
4721 # @param[in] repo_path The main path of the git repository
4722 proc OpenProject {project_file repo_path} {
4723  if {[IsXilinx]} {
4724  open_project $project_file
4725  } elseif {[IsQuartus]} {
4726  set project_folder [file dirname $project_file]
4727  set project [file tail [file rootname $project_file]]
4728  if {[file exists $project_folder]} {
4729  cd $project_folder
4730  if { ![is_project_open ] } {
4731  Msg Info "Opening existing project file $project_file..."
4732  project_open $project -current_revision
4733  }
4734  } else {
4735  Msg Error "Project directory not found for $project_file."
4736  return 1
4737  }
4738  } elseif {[IsLibero]} {
4739  Msg Info "Opening existing project file $project_file..."
4740  cd $repo_path
4741  open_project -file $project_file -do_backup_on_convert 1 -backup_file {./Projects/$project_file.zip}
4742  } elseif {[IsDiamond]} {
4743  Msg Info "Opening existing project file $project_file..."
4744  prj_project open $project_file
4745  } else {
4746  Msg Error "This IDE is currently not supported by Hog. Exiting!"
4747  }
4748 }
4749 
4750 ## @brief Return the operative system name
4751 proc OS {} {
4752  global tcl_platform
4753  return $tcl_platform(platform)
4754 }
4755 
4756 ## @brief Parse JSON file
4757 #
4758 # @param[in] JSON_FILE The JSON File to parse
4759 # @param[in] JSON_KEY The key to extract from the JSON file
4760 #
4761 # @returns -1 in case of failure, JSON KEY VALUE in case of success
4762 #
4763 proc ParseJSON {JSON_FILE JSON_KEY} {
4764  set result [catch {package require Tcl 8.4} TclFound]
4765  if {"$result" != "0"} {
4766  Msg CriticalWarning "Cannot find Tcl package version equal or higher than 8.4.\n $TclFound\n Exiting"
4767  return -1
4768  }
4769 
4770  set result [catch {package require json} JsonFound]
4771  if {"$result" != "0"} {
4772  Msg CriticalWarning "Cannot find JSON package equal or higher than 1.0.\n $JsonFound\n Exiting"
4773  return -1
4774  }
4775  set JsonDict [json::json2dict $JSON_FILE]
4776  set result [catch {dict get $JsonDict $JSON_KEY} RETURNVALUE]
4777  if {"$result" != "0"} {
4778  Msg CriticalWarning "Cannot find $JSON_KEY in $JSON_FILE\n Exiting"
4779  return -1
4780  } else {
4781  return $RETURNVALUE
4782  }
4783 }
4784 
4785 # @brief Check if a Hog project exists, and if it exists returns the conf file
4786 # if it doesnt returns 0
4787 #
4788 # @brief project The project name
4789 # @brief repo_path The main path of the git repository
4790 proc ProjectExists {project {repo_path .}} {
4791  set index [lsearch -exact [ListProjects $repo_path 0] $project]
4792 
4793  if {$index >= 0} {
4794  # if project exists we return the relative hog.conf file
4795  return [lindex [ListProjects $repo_path 0 1] $index]
4796  } else {
4797  return 0
4798  }
4799 }
4800 
4801 ## Read a property configuration file and returns a dictionary
4802 #
4803 # @param[in] file_name the configuration file
4804 #
4805 # @return The dictionary
4806 #
4807 proc ReadConf {file_name} {
4808 
4809  if { [catch {package require inifile 0.2.3} ERROR] } {
4810  puts "$ERROR\n If you are running this script on tclsh, you can fix this by installing 'tcllib'"
4811  return 1
4812  }
4813 
4814 
4815  ::ini::commentchar "#"
4816  set f [::ini::open $file_name]
4817  set properties [dict create]
4818  foreach sec [::ini::sections $f] {
4819  set new_sec $sec
4820  set key_pairs [::ini::get $f $sec]
4821 
4822  #manipulate strings here:
4823  regsub -all {\{\"} $key_pairs "\{" key_pairs
4824  regsub -all {\"\}} $key_pairs "\}" key_pairs
4825 
4826  dict set properties $new_sec [dict create {*}$key_pairs]
4827  }
4828 
4829  ::ini::close $f
4830 
4831  return $properties
4832 }
4833 
4834 ## @brief Function used to read the list of files generated at creation time by tcl scripts in Project/proj/.hog/extra.files
4835 #
4836 # @param[in] extra_file_name the path to the extra.files file
4837 # @returns a dictionary with the full name of the files as key and a SHA as value
4838 #
4839 proc ReadExtraFileList { extra_file_name } {
4840  set extra_file_dict [dict create]
4841  if {[file exists $extra_file_name]} {
4842  set file [open $extra_file_name "r"]
4843  set file_data [read $file]
4844  close $file
4845 
4846  set data [split $file_data "\n"]
4847  foreach line $data {
4848  if {![regexp {^ *$} $line] & ![regexp {^ *\#} $line] } {
4849  set ip_and_md5 [regexp -all -inline {\S+} $line]
4850  dict lappend extra_file_dict "[lindex $ip_and_md5 0]" "[lindex $ip_and_md5 1]"
4851  }
4852  }
4853  }
4854  return $extra_file_dict
4855 }
4856 
4857 ## @brief Read a list file and return a list of three dictionaries
4858 #
4859 # Additional information is provided with text separated from the file name with one or more spaces
4860 #
4861 # @param[in] args The arguments are <list_file> <path> [options]
4862 # * list_file file containing vhdl list with optional properties
4863 # * path path the vhdl file are referred to in the list file
4864 # Options:
4865 # * -lib <library> name of the library files will be added to, if not given will be extracted from the file name
4866 # * -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.
4867 #
4868 # @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.
4869 proc ReadListFile args {
4870 
4871  if {[IsQuartus]} {
4872  load_package report
4873  if { [catch {package require cmdline} ERROR] } {
4874  puts "$ERROR\n If you are running this script on tclsh, you can fix this by installing 'tcllib'"
4875  return 1
4876  }
4877  }
4878 
4879  set parameters {
4880  {lib.arg "" "The name of the library files will be added to, if not given will be extracted from the file name."}
4881  {fileset.arg "" "The name of the library, from the main list file"}
4882  {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."}
4883  }
4884  set usage "USAGE: ReadListFile \[options\] <list file> <path>"
4885  if {[catch {array set options [cmdline::getoptions args $parameters $usage]}] || [llength $args] != 2 } {
4886  Msg CriticalWarning "[cmdline::usage $parameters $usage]"
4887  return
4888  }
4889  set list_file [lindex $args 0]
4890  set path [lindex $args 1]
4891  set sha_mode $options(sha_mode)
4892  set lib $options(lib)
4893  set fileset $options(fileset)
4894 
4895  if { $sha_mode == 1} {
4896  set sha_mode_opt "-sha_mode"
4897  } else {
4898  set sha_mode_opt ""
4899  }
4900 
4901  # if no library is given, work it out from the file name
4902  if {$lib eq ""} {
4903  set lib [file rootname [file tail $list_file]]
4904  }
4905  set fp [open $list_file r]
4906  set file_data [read $fp]
4907  close $fp
4908  set list_file_ext [file extension $list_file]
4909  switch $list_file_ext {
4910  .sim {
4911  if {$fileset eq ""} {
4912  # If fileset is empty, use the library name for .sim file
4913  set fileset "$lib"
4914  }
4915  }
4916  .con {
4917  set fileset "constrs_1"
4918  }
4919  default {
4920  set fileset "sources_1"
4921  }
4922  }
4923 
4924  set libraries [dict create]
4925  set filesets [dict create]
4926  set properties [dict create]
4927  # Process data file
4928  set data [split $file_data "\n"]
4929  set n [llength $data]
4930  Msg Debug "$n lines read from $list_file."
4931  set cnt 0
4932 
4933  foreach line $data {
4934  # Exclude empty lines and comments
4935  if {![regexp {^ *$} $line] & ![regexp {^ *\#} $line] } {
4936  set file_and_prop [regexp -all -inline {\S+} $line]
4937  set srcfile [lindex $file_and_prop 0]
4938  set srcfile "$path/$srcfile"
4939 
4940  set srcfiles [glob -nocomplain $srcfile]
4941 
4942  # glob the file list for wildcards
4943  if {$srcfiles != $srcfile && ! [string equal $srcfiles "" ]} {
4944  Msg Debug "Wildcard source expanded from $srcfile to $srcfiles"
4945  } else {
4946  if {![file exists $srcfile]} {
4947  Msg CriticalWarning "File: $srcfile (from list file: $list_file) does not exist."
4948  continue
4949  }
4950  }
4951 
4952  foreach vhdlfile $srcfiles {
4953  if {[file exists $vhdlfile]} {
4954  set vhdlfile [file normalize $vhdlfile]
4955  set extension [file extension $vhdlfile]
4956  ### Set file properties
4957  set prop [lrange $file_and_prop 1 end]
4958 
4959  # The next lines should be inside the case for recursive list files, also we should check the allowed properties for the .src as well
4960  set library [lindex [regexp -inline {\ylib\s*=\s*(.+?)\y.*} $prop] 1]
4961  if { $library == "" } {
4962  set library $lib
4963  }
4964 
4965  if { $extension == $list_file_ext } {
4966  # Deal with recursive list files
4967  # In the next regex we use \S+ instead of .+? because we want to include forward slashes
4968  set ref_path [lindex [regexp -inline {\ypath\s*=\s*(\S+).*} $prop] 1]
4969  if { $ref_path eq "" } {
4970  set ref_path $path
4971  } else {
4972  set ref_path [file normalize $path/$ref_path]
4973  }
4974  Msg Debug "List file $vhdlfile found in list file, recursively opening it using path \"$ref_path\"..."
4975  lassign [ReadListFile {*}"-lib $library -fileset $fileset $sha_mode_opt $vhdlfile $ref_path"] l p fs
4976  set libraries [MergeDict $l $libraries]
4977  set properties [MergeDict $p $properties]
4978  set filesets [MergeDict $fs $filesets]
4979  } elseif {[lsearch {.src .sim .con ReadExtraFileList} $extension] >= 0 } {
4980  # Not supported extensions
4981  Msg Error "$vhdlfile cannot be included into $list_file, $extension files must be included into $extension files."
4982  } else {
4983  # Deal with single files
4984  regsub -all " *= *" $prop "=" prop
4985  # Fill property dictionary
4986  foreach p $prop {
4987  # No need to append the lib= property
4988  if { [string first "lib=" $p ] == -1} {
4989  # Get property name up to the = (for QSYS properties at the moment)
4990  set pos [string first "=" $p]
4991  if { $pos == -1 } {
4992  set prop_name $p
4993  } else {
4994  set prop_name [string range $p 0 [expr {$pos - 1}]]
4995  }
4996  if { [IsInList $prop_name [DictGet [ALLOWED_PROPS] $extension ] ] || [string first "top" $p] == 0 || $list_file_ext eq ".ipb"} {
4997  dict lappend properties $vhdlfile $p
4998  Msg Debug "Adding property $p to $vhdlfile..."
4999  } elseif { $list_file_ext != ".ipb" } {
5000  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 ]\]"
5001  }
5002  }
5003  }
5004  if { [lsearch {.xci .ip .bd .xcix} $extension] >= 0} {
5005  # Adding IP library
5006  set lib_name "ips.src"
5007  } elseif { [IsInList $extension {.vhd .vhdl}] || $list_file_ext == ".sim"} {
5008  # VHDL files and simulation
5009  if { ![IsInList $extension {.vhd .vhdl}]} {
5010  set lib_name "others.sim"
5011  } else {
5012  set lib_name "$library$list_file_ext"
5013  }
5014  } elseif { $list_file_ext == ".con" } {
5015  set lib_name "sources.con"
5016  } elseif { $list_file_ext == ".ipb" } {
5017  set lib_name "xml.ipb"
5018  } else {
5019  # Other files are stored in the OTHER dictionary from vivado (no library assignment)
5020  set lib_name "others.src"
5021  }
5022 
5023  Msg Debug "Appending $vhdlfile to $lib_name list..."
5024  dict lappend libraries $lib_name $vhdlfile
5025  if { $sha_mode != 0 && [file type $vhdlfile] eq "link"} {
5026  #if the file is a link, also add the linked file in sha mode
5027  set real_file [GetLinkedFile $vhdlfile]
5028  dict lappend libraries $lib_name $real_file
5029  Msg Debug "File $vhdlfile is a soft link, also adding the real file: $real_file"
5030  }
5031 
5032 
5033  # Create the fileset (if not already) and append the library
5034  if {[dict exists $filesets $fileset] == 0} {
5035  # Fileset has not been defined yet, adding to dictionary...
5036  Msg Debug "Adding $fileset to the fileset dictionary..."
5037  Msg Debug "Adding library $lib_name to fileset $fileset..."
5038  dict set filesets $fileset $lib_name
5039  } else {
5040  # Fileset already exist in dictionary, append library to list, if not already there
5041  if {[IsInList $lib_name [DictGet $filesets $fileset]] == 0} {
5042  Msg Debug "Adding library $lib_name to fileset $fileset..."
5043  dict lappend filesets $fileset $lib_name
5044  }
5045  }
5046  }
5047  incr cnt
5048  } else {
5049  Msg CriticalWarning "File $vhdlfile not found."
5050  }
5051  }
5052  }
5053  }
5054 
5055  if {$sha_mode != 0} {
5056  #In SHA mode we also need to add the list file to the list
5057  if {$list_file_ext eq ".ipb"} {
5058  set sha_lib "xml.ipb"
5059  } else {
5060  set sha_lib $lib$list_file_ext
5061  }
5062  dict lappend libraries $sha_lib [file normalize $list_file]
5063  if {[file type $list_file] eq "link"} {
5064  #if the file is a link, also add the linked file
5065  set real_file [GetLinkedFile $list_file]
5066  dict lappend libraries $lib$list_file_ext $real_file
5067  Msg Debug "List file $list_file is a soft link, also adding the real file: $real_file"
5068  }
5069  }
5070  return [list $libraries $properties $filesets]
5071 }
5072 
5073 ## @brief Returns the destination path relative to base
5074 #
5075 # @param[in] base the path with respect to witch the dst path is calculated
5076 # @param[in] dst the path to be calculated with respect to base
5077 #
5078 proc Relative {base dst} {
5079  if {![string equal [file pathtype $base] [file pathtype $dst]]} {
5080  Msg CriticalWarning "Unable to compute relation for paths of different path types: [file pathtype $base] vs. [file pathtype $dst], ($base vs. $dst)"
5081  return ""
5082  }
5083 
5084  set base [file normalize [file join [pwd] $base]]
5085  set dst [file normalize [file join [pwd] $dst]]
5086 
5087  set save $dst
5088  set base [file split $base]
5089  set dst [file split $dst]
5090 
5091  while {[string equal [lindex $dst 0] [lindex $base 0]]} {
5092  set dst [lrange $dst 1 end]
5093  set base [lrange $base 1 end]
5094  if {![llength $dst]} {break}
5095  }
5096 
5097  set dstlen [llength $dst]
5098  set baselen [llength $base]
5099 
5100  if {($dstlen == 0) && ($baselen == 0)} {
5101  set dst .
5102  } else {
5103  while {$baselen > 0} {
5104  set dst [linsert $dst 0 ..]
5105  incr baselen -1
5106  }
5107  set dst [eval [linsert $dst 0 file join]]
5108  }
5109 
5110  return $dst
5111 }
5112 
5113 ## @brief Returns the path of filePath relative to pathName
5114 #
5115 # @param[in] pathName the path with respect to which the returned path is calculated
5116 # @param[in] filePath the path of filePath
5117 #
5118 proc RelativeLocal {pathName filePath} {
5119  if {[string first [file normalize $pathName] [file normalize $filePath]] != -1} {
5120  return [Relative $pathName $filePath]
5121  } else {
5122  return ""
5123  }
5124 }
5125 
5126 ## @brief Remove duplicates in a dictionary
5127 #
5128 # @param[in] mydict the input dictionary
5129 #
5130 # @return the dictionary stripped of duplicates
5131 proc RemoveDuplicates {mydict} {
5132  set new_dict [dict create]
5133  foreach key [dict keys $mydict] {
5134  set values [DictGet $mydict $key]
5135  foreach value $values {
5136  set idxs [lreverse [lreplace [lsearch -exact -all $values $value] 0 0]]
5137  foreach idx $idxs {
5138  set values [lreplace $values $idx $idx]
5139  }
5140  }
5141  dict set new_dict $key $values
5142  }
5143  return $new_dict
5144 }
5145 
5146 ## Reset files in the repository
5147 #
5148 # @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)
5149 #
5150 # @return Nothing
5151 #
5152 proc ResetRepoFiles {reset_file} {
5153  if {[file exists $reset_file]} {
5154  Msg Info "Found $reset_file, opening it..."
5155  set fp [open $reset_file r]
5156  set wild_cards [lsearch -all -inline -not -regexp [split [read $fp] "\n"] "^ *$"]
5157  close $fp
5158  Msg Info "Found the following files/wild cards to restore if modified: $wild_cards..."
5159  foreach w $wild_cards {
5160  set mod_files [GetModifiedFiles "." $w]
5161  if {[llength $mod_files] > 0} {
5162  Msg Info "Found modified $w files: $mod_files, will restore them..."
5163  RestoreModifiedFiles "." $w
5164  } else {
5165  Msg Info "No modified $w files found."
5166  }
5167  }
5168  }
5169 }
5170 
5171 ## @brief Restore with checkout -- the files specified in pattern
5172 #
5173 # @param[in] repo_path the path of the git repository
5174 # @param[in] pattern the pattern with wildcards that files should match
5175 #
5176 proc RestoreModifiedFiles {{repo_path "."} {pattern "."}} {
5177  set old_path [pwd]
5178  cd $repo_path
5179  set ret [Git checkout $pattern]
5180  cd $old_path
5181  return
5182 }
5183 
5184 ## Search the Hog projects inside a directory
5185 #
5186 # @param[in] dir The directory to search
5187 #
5188 # @return The list of projects
5189 #
5190 proc SearchHogProjects {dir} {
5191  set projects_list {}
5192  if {[file exists $dir]} {
5193  if {[file isdirectory $dir]} {
5194  foreach proj_dir [glob -nocomplain -types d $dir/* ] {
5195  if {![regexp {^.*Top/+(.*)$} $proj_dir dummy proj_name]} {
5196  Msg Warning "Could not parse Top directory $dir"
5197  break
5198  }
5199  if { [file exists "$proj_dir/hog.conf" ] } {
5200  lappend projects_list $proj_name
5201  } else {
5202  foreach p [SearchHogProjects $proj_dir] {
5203  lappend projects_list $p
5204  }
5205  }
5206  }
5207 
5208  } else {
5209  Msg Error "Input $dir is not a directory!"
5210  }
5211  } else {
5212  Msg Error "Directory $dir doesn't exist!"
5213  }
5214  return $projects_list
5215 }
5216 
5217 ## @brief Sets the generics in all the sim.conf simulation file sets
5218 #
5219 # @param[in] repo_path: the top folder of the projectThe path to the main git repository
5220 # @param[in] proj_dir: the top folder of the project
5221 # @param[in] target: software target(vivado, questa)
5222 #
5223 proc SetGenericsSimulation {repo_path proj_dir target} {
5224  set top_dir "$repo_path/Top/$proj_dir"
5225  set read_aux [GetConfFiles $top_dir]
5226  set sim_cfg_index [lsearch -regexp -index 0 $read_aux ".*sim.conf"]
5227  set sim_cfg_index [lsearch -regexp -index 0 [GetConfFiles $top_dir] ".*sim.conf"]
5228  set simsets [get_filesets]
5229  if { $simsets != "" } {
5230  if {[file exists $top_dir/sim.conf]} {
5231  set sim_generics_dict [GetGenericsFromConf $proj_dir 1]
5232  set simsets_generics_dict [GetSimsetGenericsFromConf $proj_dir]
5233 
5234  if {[dict size $sim_generics_dict] > 0 || [dict size $simsets_generics_dict] > 0} {
5235  foreach simset $simsets {
5236  # Only for simulation filesets
5237  if {[get_property FILESET_TYPE $simset] != "SimulationSrcs" } {
5238  continue
5239  }
5240  # Check if any specific generics for this simset
5241  if {[dict exists $simsets_generics_dict $simset:generics]} {
5242  set simset_generics_dict [dict get $simsets_generics_dict $simset:generics]
5243  # Merge with global sim generics (if any), specific simset generics also have priority
5244  set merged_generics_dict [dict merge $sim_generics_dict $simset_generics_dict]
5245  set generic_str [GenericToSimulatorString $merged_generics_dict $target]
5246  set_property generic $generic_str [get_filesets $simset]
5247  Msg Debug "Setting generics $generic_str for simulator $target and simulation file-set $simset..."
5248  } elseif {[dict size $sim_generics_dict] > 0} {
5249  set generic_str [GenericToSimulatorString $sim_generics_dict $target]
5250  set_property generic $generic_str [get_filesets $simset]
5251  Msg Debug "Setting generics $generic_str for simulator $target and simulation file-set $simset..."
5252  }
5253  }
5254  }
5255  } else {
5256  if {[glob -nocomplain "$top_dir/list/*.sim"] ne ""} {
5257  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."
5258  }
5259  }
5260  }
5261 }
5262 
5263 ## @brief set the top module as top module in the chosen fileset
5264 #
5265 # It automatically recognises the IDE
5266 #
5267 # @param[out] top_module Name of the top module
5268 # @param[in] fileset The name of the fileset
5269 #
5270 proc SetTopProperty {top_module fileset} {
5271  Msg Info "Setting TOP property to $top_module module"
5272  if {[IsXilinx]} {
5273  #VIVADO_ONLY
5274  set_property "top" $top_module [get_filesets $fileset]
5275  } elseif {[IsQuartus]} {
5276  #QUARTUS ONLY
5277  set_global_assignment -name TOP_LEVEL_ENTITY $top_module
5278  } elseif {[IsLibero]} {
5279  set_root -module $top_module
5280  } elseif {[IsDiamond]} {
5281  prj_impl option top $top_module
5282  }
5283 }
5284 
5285 ## @brief Returns a list of Vivado properties that expect a PATH for value
5286 proc VIVADO_PATH_PROPERTIES {} {
5287  return {"\.*\.TCL\.PRE$" "^.*\.TCL\.POST$" "^RQS_FILES$" "^INCREMENTAL\_CHECKPOINT$"}
5288 }
5289 
5290 ## @brief Write a property configuration file from a dictionary
5291 #
5292 # @param[in] file_name the configuration file
5293 # @param[in] config the configuration dictionary
5294 # @param[in] comment comment to add at the beginning of configuration file
5295 #
5296 #
5297 proc WriteConf {file_name config {comment ""}} {
5298  if { [catch {package require inifile 0.2.3} ERROR] } {
5299  puts "$ERROR\n If you are running this script on tclsh, you can fix this by installing 'tcllib'"
5300  return 1
5301  }
5302 
5303  ::ini::commentchar "#"
5304  set f [::ini::open $file_name w]
5305 
5306  foreach sec [dict keys $config] {
5307  set section [dict get $config $sec]
5308  dict for {p v} $section {
5309  if {[string trim $v] == ""} {
5310  Msg Warning "Property $p has empty value. Skipping..."
5311  continue;
5312  }
5313  ::ini::set $f $sec $p $v
5314  }
5315  }
5316 
5317  #write comment before the first section (first line of file)
5318  if {![string equal "$comment" ""]} {
5319  ::ini::comment $f [lindex [::ini::sections $f] 0] "" $comment
5320  }
5321  ::ini::commit $f
5322 
5323  ::ini::close $f
5324 
5325 }
5326 
5327 ## Set the generics property
5328 #
5329 # @param[in] mode if it's "create", the function will assume the project is being created
5330 # @param[in] repo_path The path to the main git repository
5331 # @param[in] design The name of the design
5332 
5333 # @param[in] list of variables to be written in the generics in the usual order
5334 
5335 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 ""}} {
5336  Msg Info "Passing parameters/generics to project's top module..."
5337  ##### Passing Hog generic to top file
5338  # set global generic variables
5339  set generic_string [concat \
5340  "GLOBAL_DATE=[FormatGeneric $date]" \
5341  "GLOBAL_TIME=[FormatGeneric $timee]" \
5342  "GLOBAL_VER=[FormatGeneric $version]" \
5343  "GLOBAL_SHA=[FormatGeneric $commit]" \
5344  "TOP_SHA=[FormatGeneric $top_hash]" \
5345  "TOP_VER=[FormatGeneric $top_ver]" \
5346  "HOG_SHA=[FormatGeneric $hog_hash]" \
5347  "HOG_VER=[FormatGeneric $hog_ver]" \
5348  "CON_VER=[FormatGeneric $cons_ver]" \
5349  "CON_SHA=[FormatGeneric $cons_hash]"]
5350  # xml hash
5351  if {$xml_hash != "" && $xml_ver != ""} {
5352  lappend generic_string \
5353  "XML_VER=[FormatGeneric $xml_ver]" \
5354  "XML_SHA=[FormatGeneric $xml_hash]"
5355  }
5356  #set project specific lists
5357  foreach l $libs v $vers h $hashes {
5358  set ver "[string toupper $l]_VER=[FormatGeneric $v]"
5359  set hash "[string toupper $l]_SHA=[FormatGeneric $h]"
5360  lappend generic_string "$ver" "$hash"
5361  }
5362 
5363  foreach e $ext_names h $ext_hashes {
5364  set hash "[string toupper $e]_SHA=[FormatGeneric $h]"
5365  lappend generic_string "$hash"
5366  }
5367 
5368  foreach repo $user_ip_repos v $user_ip_vers h $user_ip_hashes {
5369  set repo_name [file tail $repo]
5370  set ver "[string toupper $repo_name]_VER=[FormatGeneric $v]"
5371  set hash "[string toupper $repo_name]_SHA=[FormatGeneric $h]"
5372  lappend generic_string "$ver" "$hash"
5373  }
5374 
5375  if {$flavour != -1} {
5376  lappend generic_string "FLAVOUR=$flavour"
5377  }
5378 
5379  # Dealing with project generics in Vivado
5380  if {[IsVivado]} {
5381  set prj_generics [GenericToSimulatorString [GetGenericsFromConf $design] "Vivado"]
5382  set generic_string "$prj_generics $generic_string"
5383  }
5384 
5385  # Extract the generics from the top level source file
5386  if {[IsXilinx]} {
5387  # Top File can be retrieved only at creation time or in ISE
5388  if {$mode == "create" || [IsISE]} {
5389 
5390  set top_file [GetTopFile]
5391  set top_name [GetTopModule]
5392  if {[file exists $top_file]} {
5393  set generics [GetFileGenerics $top_file $top_name]
5394 
5395  Msg Debug "Found top level generics $generics in $top_file"
5396 
5397  set filtered_generic_string ""
5398 
5399  foreach generic_to_set [split [string trim $generic_string]] {
5400  set key [lindex [split $generic_to_set "="] 0]
5401  if {[dict exists $generics $key]} {
5402  Msg Debug "Hog generic $key found in $top_name"
5403  lappend filtered_generic_string "$generic_to_set"
5404  } else {
5405  Msg Warning "Generic $key is passed by Hog but is NOT present in $top_name."
5406  }
5407  }
5408 
5409  # only filter in ISE
5410  if {[IsISE]} {
5411  set generic_string $filtered_generic_string
5412  }
5413  }
5414  }
5415 
5416  set_property generic $generic_string [current_fileset]
5417  Msg Info "Setting parameters/generics..."
5418  Msg Debug "Detailed parameters/generics: $generic_string"
5419 
5420 
5421  if {[IsVivado]} {
5422  # Dealing with project generics in Simulators
5423  set simulator [get_property target_simulator [current_project]]
5424  if {$mode == "create"} {
5425  SetGenericsSimulation $repo_path $design $simulator
5426  }
5427 
5428  WriteGenericsToBdIPs $mode $repo_path $design $generic_string
5429  }
5430  } elseif {[IsSynplify]} {
5431  Msg Info "Setting Synplify parameters/generics one by one..."
5432  foreach generic $generic_string {
5433  Msg Debug "Setting Synplify generic: $generic"
5434  set_option -hdl_param -set "$generic"
5435  }
5436  } elseif {[IsDiamond]} {
5437  Msg Info "Setting Diamond parameters/generics one by one..."
5438  prj_impl option -impl Implementation0 HDL_PARAM "$generic_string"
5439  }
5440 }
5441 
5442 ## @brief Applies generic values to IPs within block designs
5443 #
5444 # @param[in] mode create: to write the generics at creation time. synth to write at synthesis time
5445 # @param[in] repo_path The main path of the git repository
5446 # @param[in] proj The project name
5447 # @param[in] generic_string the string containing the generics to be applied
5448 proc WriteGenericsToBdIPs {mode repo_path proj generic_string} {
5449  Msg Debug "Parameters/generics passed to WriteGenericsToIP: $generic_string"
5450 
5451  set bd_ip_generics false
5452  set properties [ReadConf [lindex [GetConfFiles $repo_path/Top/$proj] 0]]
5453  if {[dict exists $properties "hog"]} {
5454  set propDict [dict get $properties "hog"]
5455  if {[dict exists $propDict "PASS_GENERICS_TO_BD_IPS"]} {
5456  set bd_ip_generics [dict get $propDict "PASS_GENERICS_TO_BD_IPS"]
5457  }
5458  }
5459 
5460  if {[string compare [string tolower $bd_ip_generics] "false"]==0} {
5461  return
5462  }
5463 
5464  if {$mode == "synth"} {
5465  Msg Info "Attempting to apply generics pre-synthesis..."
5466  set PARENT_PRJ [get_property "PARENT.PROJECT_PATH" [current_project]]
5467  set workaround [open "$repo_path/Projects/$proj/.hog/presynth_workaround.tcl" "w"]
5468  puts $workaround "source \[lindex \$argv 0\];"
5469  puts $workaround "open_project \[lindex \$argv 1\];"
5470  puts $workaround "WriteGenericsToBdIPs \[lindex \$argv 2\] \[lindex \$argv 3\] \[lindex \$argv 4\] \[lindex \$argv 5\];"
5471  puts $workaround "close_project"
5472  close $workaround
5473  if {[catch {
5474  exec vivado -mode batch -source $repo_path/Projects/$proj/.hog/presynth_workaround.tcl \
5475  -tclargs $repo_path/Hog/Tcl/hog.tcl $PARENT_PRJ \
5476  "childprocess" $repo_path $proj $generic_string
5477  } errMsg] != 0} {
5478  Msg Error "Encountered an error while attempting workaround: $errMsg"
5479  }
5480  file delete $repo_path/Projects/$proj/.hog/presynth_workaround.tcl
5481  ResetRepoFiles "$repo_path/Projects/hog_reset_files"
5482  Msg Info "Done applying generics pre-synthesis."
5483  return
5484  }
5485 
5486  Msg Info "Looking for IPs to add generics to..."
5487  set ips_generic_string ""
5488  foreach generic_to_set [split [string trim $generic_string]] {
5489  set key [lindex [split $generic_to_set "="] 0]
5490  set value [lindex [split $generic_to_set "="] 1]
5491  append ips_generic_string "CONFIG.$key $value "
5492  }
5493 
5494 
5495  if {[string compare [string tolower $bd_ip_generics] "true"]==0} {
5496  set ip_regex ".*"
5497  } else {
5498  set ip_regex $bd_ip_generics
5499  }
5500 
5501  set ip_list [get_ips -regex $ip_regex]
5502  Msg Debug "IPs found with regex \{$ip_regex\}: $ip_list"
5503 
5504  set regen_targets {}
5505 
5506  foreach {ip} $ip_list {
5507  set WARN_ABOUT_IP false
5508  set ip_props [list_property [get_ips $ip]]
5509 
5510  #Not sure if this is needed, but it's here to prevent potential errors with get_property
5511  if {[lsearch -exact $ip_props "IS_BD_CONTEXT"] == -1} {
5512  continue
5513  }
5514 
5515  if {[get_property "IS_BD_CONTEXT" [get_ips $ip]] eq "1"} {
5516  foreach {ip_prop} $ip_props {
5517  if {[dict exists $ips_generic_string $ip_prop ]} {
5518  if {$WARN_ABOUT_IP == false} {
5519  lappend regen_targets [get_property SCOPE [get_ips $ip]]
5520  Msg Warning "The ip \{$ip\} contains generics that are set by Hog.\
5521  If this is IP is apart of a block design, the .bd file may contain stale, unused, values.\
5522  Hog will always apply the most up-to-date values to the IP during synthesis,\
5523  however these values may or may not be reflected in the .bd file."
5524  set WARN_ABOUT_IP true
5525  }
5526 
5527  # vivado is annoying about the format when setting generics for ips
5528  # this tries to find and set the format to what vivado likes
5529  set xci_path [get_property IP_FILE [get_ips $ip]]
5530  set generic_format [GetGenericFormatFromXci $ip_prop $xci_path]
5531  if {[string equal $generic_format "ERROR"]} {
5532  Msg Warning "Could not find format for generic $ip_prop in IP $ip. Skipping..."
5533  continue
5534  }
5535 
5536  set value_to_set [dict get $ips_generic_string $ip_prop]
5537  switch -exact $generic_format {
5538  "long" {
5539  if {[string match "32'h*" $value_to_set]} {
5540  scan [string map {"32'h" ""} $value_to_set] "%x" value_to_set
5541  }
5542  }
5543  "bool" {
5544  set value_to_set [expr {$value_to_set ? "true" : "false"}]
5545  }
5546  "float" {
5547  if {[string match "32'h*" $value_to_set]} {
5548  binary scan [binary format H* [string map {"32'h" ""} $value_to_set]] d value_to_set
5549  }
5550  }
5551  "bitString" {
5552  if {[string match "32'h*" $value_to_set]} {
5553  set value_to_set [string map {"32'h" "0x"} $value_to_set]
5554  }
5555  }
5556  "string" {
5557  set value_to_set [format "%s" $value_to_set]
5558  }
5559  default {
5560  Msg Warning "Unknown generic format $generic_format for IP $ip. Will attempt to pass as string..."
5561  }
5562  }
5563 
5564 
5565  Msg Info "The IP \{$ip\} contains: $ip_prop ($generic_format), setting it to $value_to_set."
5566  if {[catch {set_property -name $ip_prop -value $value_to_set -objects [ get_ips $ip ]} prop_error]} {
5567  Msg CriticalWarning "Failed to set property $ip_prop to $value_to_set for IP \{$ip\}: $prop_error"
5568  }
5569  }
5570  }
5571  }
5572  }
5573 
5574  foreach {regen_target} [lsort -unique $regen_targets] {
5575  Msg Info "Regenerating target: $regen_target"
5576  if {[catch {generate_target -force all [get_files $regen_target]} prop_error]} {
5577  Msg CriticalWarning "Failed to regen targets: $prop_error"
5578  }
5579  }
5580 
5581 }
5582 
5583 ## @brief Returns the format of a generic from an XML file
5584 ## @param[in] generic_name: The name of the generic
5585 ## @param[in] xml_file: The path to the XML XCI file
5586 proc GetGenericFormatFromXciXML {generic_name xml_file} {
5587 
5588  if {![file exists $xml_file]} {
5589  Msg Error "Could not find XML file: $xml_file"
5590  return "ERROR"
5591  }
5592 
5593  set fp [open $xml_file r]
5594  set xci_data [read $fp]
5595  close $fp
5596 
5597  set paramType "string"
5598  set modelparam_regex [format {^.*\y%s\y.*$} [string map {"CONFIG." "MODELPARAM_VALUE."} $generic_name]]
5599  set format_regex {format="([^"]+)"}
5600 
5601  set line [lindex [regexp -inline -line $modelparam_regex $xci_data] 0]
5602  Msg Debug "line: $line"
5603 
5604  if {[regexp $format_regex $line match format_value]} {
5605  Msg Debug "Extracted: $format_value format from xml"
5606  set paramType $format_value
5607  } else {
5608  Msg Debug "No format found, using string"
5609  }
5610 
5611  return $paramType
5612 }
5613 
5614 ## @brief Returns the format of a generic from an XCI file
5615 ## @param[in] generic_name: The name of the generic
5616 ## @param[in] xci_file: The path to the XCI file
5617 proc GetGenericFormatFromXci {generic_name xci_file} {
5618 
5619  if {! [file exists $xci_file]} {
5620  Msg Error "Could not find XCI file: $xci_file"
5621  return "ERROR"
5622  }
5623 
5624  set fp [open $xci_file r]
5625  set xci_data [read $fp]
5626  close $fp
5627 
5628  set paramType "string"
5629  if {[string first "xilinx.com:schema:json_instance:1.0" $xci_data] == -1} {
5630  Msg Debug "XCI format is not JSON, trying XML..."
5631  set xml_file "[file rootname $xci_file].xml"
5632  return [GetGenericFormatFromXciXML $generic_name $xml_file]
5633 
5634  }
5635 
5636  set generic_name [string map {"CONFIG." ""} $generic_name]
5637  set ip_inst [ParseJSON $xci_data "ip_inst"]
5638  set parameters [dict get $ip_inst parameters]
5639  set component_parameters [dict get $parameters component_parameters]
5640  if {[dict exists $component_parameters $generic_name]} {
5641  set generic_info [dict get $component_parameters $generic_name]
5642  if {[dict exists [lindex $generic_info 0] format]} {
5643  set paramType [dict get [lindex $generic_info 0] format]
5644  Msg Debug "Extracted: $paramType format from xci"
5645  return $paramType
5646  }
5647  Msg Debug "No format found, using string"
5648  return $paramType
5649  } else {
5650  return "ERROR"
5651  }
5652 }
5653 
5654 
5655 ## @brief Returns the gitlab-ci.yml snippet for a CI stage and a defined project
5656 #
5657 # @param[in] proj_name: The project name
5658 # @param[in] ci_confs: Dictionary with CI configurations
5659 #
5660 proc WriteGitLabCIYAML {proj_name {ci_conf ""}} {
5661  if { [catch {package require yaml 0.3.3} YAMLPACKAGE]} {
5662  Msg CriticalWarning "Cannot find package YAML.\n Error message: $YAMLPACKAGE. If you are running on tclsh, you can fix this by installing package \"tcllib\""
5663  return -1
5664  }
5665 
5666  set job_list []
5667  if {$ci_conf != ""} {
5668  set ci_confs [ReadConf $ci_conf]
5669  foreach sec [dict keys $ci_confs] {
5670  if {[string first : $sec] == -1} {
5671  lappend job_list $sec
5672  }
5673  }
5674  } else {
5675  set job_list {"generate_project" "simulate_project"}
5676  set ci_confs ""
5677  }
5678 
5679  set out_yaml [huddle create]
5680  foreach job $job_list {
5681  # Check main project configurations
5682  set huddle_tags [huddle list]
5683  set tag_section ""
5684  set sec_dict [dict create]
5685 
5686  if {$ci_confs != ""} {
5687  foreach var [dict keys [dict get $ci_confs $job]] {
5688  if {$var == "tags"} {
5689  set tag_section "tags"
5690  set tags [dict get [dict get $ci_confs $job] $var]
5691  set tags [split $tags ","]
5692  foreach tag $tags {
5693  set tag_list [huddle list $tag]
5694  set huddle_tags [huddle combine $huddle_tags $tag_list]
5695  }
5696  } else {
5697  dict set sec_dict $var [dict get [dict get $ci_confs $job] $var]
5698  }
5699  }
5700  }
5701 
5702  # Check if there are extra variables in the conf file
5703  set huddle_variables [huddle create "PROJECT_NAME" $proj_name "extends" ".vars"]
5704  if {[dict exists $ci_confs "$job:variables"]} {
5705  set var_dict [dict get $ci_confs $job:variables]
5706  foreach var [dict keys $var_dict] {
5707  # puts [dict get $var_dict $var]
5708  set value [dict get $var_dict "$var"]
5709  set var_inner [huddle create "$var" "$value"]
5710  set huddle_variables [huddle combine $huddle_variables $var_inner]
5711  }
5712  }
5713 
5714 
5715  set middle [huddle create "extends" ".$job" "variables" $huddle_variables]
5716  foreach sec [dict keys $sec_dict] {
5717  set value [dict get $sec_dict $sec]
5718  set var_inner [huddle create "$sec" "$value"]
5719  set middle [huddle combine $middle $var_inner]
5720  }
5721  if {$tag_section != ""} {
5722  set middle2 [huddle create "$tag_section" $huddle_tags]
5723  set middle [huddle combine $middle $middle2]
5724  }
5725 
5726  set outer [huddle create "$job:$proj_name" $middle ]
5727  set out_yaml [huddle combine $out_yaml $outer]
5728  }
5729 
5730  return [ string trimleft [ yaml::huddle2yaml $out_yaml ] "-" ]
5731 }
5732 
5733 # @brief Write the content of Hog-library-dictionary created from the project into a .src/.ext/.con list file
5734 #
5735 # @param[in] libs The Hog-Library dictionary with the list of files in the project to write
5736 # @param[in] props The Hog-library dictionary with the file sets
5737 # @param[in] list_path The path of the output list file
5738 # @param[in] repo_path The main repository path
5739 # @param[in] ext_path The external path
5740 proc WriteListFiles {libs props list_path repo_path {ext_path ""} } {
5741  # Writing simulation list files
5742  foreach lib [dict keys $libs] {
5743  if {[llength [DictGet $libs $lib]] > 0} {
5744  set list_file_name $list_path$lib
5745  set list_file [open $list_file_name w]
5746  Msg Info "Writing $list_file_name..."
5747  foreach file [DictGet $libs $lib] {
5748  # Retrieve file properties from prop list
5749  set prop [DictGet $props $file]
5750  # Check if file is local to the repository or external
5751  if {[RelativeLocal $repo_path $file] != ""} {
5752  set file_path [RelativeLocal $repo_path $file]
5753  puts $list_file "$file_path $prop"
5754  } elseif {[RelativeLocal $ext_path $file] != ""} {
5755  set file_path [RelativeLocal $ext_path $file]
5756  set ext_list_file [open "[file rootname $list_file].ext" a]
5757  puts $ext_list_file "$file_path $prop"
5758  close $ext_list_file
5759  } else {
5760  # File is not relative to repo or ext_path... Write a Warning and continue
5761  Msg Warning "The path of file $file is not relative to your repository. Please check!"
5762  }
5763  }
5764  close $list_file
5765  }
5766  }
5767 }
5768 
5769 # @brief Write the content of Hog-library-dictionary created from the project into a .sim list file
5770 #
5771 # @param[in] libs The Hog-Library dictionary with the list of files in the project to write
5772 # @param[in] props The Hog-library dictionary with the file sets
5773 # @param[in] simsets The Hog-library dictionary with the file sets (relevant only for simulation)
5774 # @param[in] list_path The path of the output list file
5775 # @param[in] repo_path The main repository path
5776 proc WriteSimListFiles {libs props simsets list_path repo_path } {
5777  # Writing simulation list files
5778  foreach simset [dict keys $simsets] {
5779  set list_file_name $list_path/${simset}.sim
5780  set list_file [open $list_file_name w]
5781  Msg Info "Writing $list_file_name..."
5782  foreach lib [DictGet $simsets $simset] {
5783  foreach file [DictGet $libs $lib] {
5784  # Retrieve file properties from prop list
5785  set prop [DictGet $props $file]
5786  # Check if file is local to the repository or external
5787  if {[RelativeLocal $repo_path $file] != ""} {
5788  set file_path [RelativeLocal $repo_path $file]
5789  set lib_name [file rootname $lib]
5790  if {$lib_name != $simset} {
5791  lappend prop "lib=$lib_name"
5792  }
5793  puts $list_file "$file_path $prop"
5794  } else {
5795  # File is not relative to repo or ext_path... Write a Warning and continue
5796  Msg Warning "The path of file $file is not relative to your repository. Please check!"
5797  }
5798  }
5799  }
5800  }
5801 }
5802 
5803 
5804 ## @brief Write into a file, and if the file exists, it will append the string
5805 #
5806 # @param[out] File The log file to write into the message
5807 # @param[in] msg The message text
5808 proc WriteToFile {File msg} {
5809  set f [open $File a+]
5810  puts $f $msg
5811  close $f
5812 }
5813 
5814 ## Write the resource utilization table into a a file (Vivado only)
5815 #
5816 # @param[in] input the input .rpt report file from Vivado
5817 # @param[in] output the output file
5818 # @param[in] project_name the name of the project
5819 # @param[in] run synthesis or implementation
5820 proc WriteUtilizationSummary {input output project_name run} {
5821  set f [open $input "r"]
5822  set o [open $output "a"]
5823  puts $o "## $project_name $run Utilization report\n\n"
5824  struct::matrix util_m
5825  util_m add columns 14
5826  util_m add row
5827  if { [GetIDEVersion] >= 2021.0 } {
5828  util_m add row "| **Site Type** | **Used** | **Fixed** | **Prohibited** | **Available** | **Util%** |"
5829  util_m add row "| --- | --- | --- | --- | --- | --- |"
5830  } else {
5831  util_m add row "| **Site Type** | **Used** | **Fixed** | **Available** | **Util%** |"
5832  util_m add row "| --- | --- | --- | --- | --- |"
5833  }
5834 
5835  set luts 0
5836  set regs 0
5837  set uram 0
5838  set bram 0
5839  set dsps 0
5840  set ios 0
5841 
5842  while {[gets $f line] >= 0} {
5843  if { ( [string first "| CLB LUTs" $line] >= 0 || [string first "| Slice LUTs" $line] >= 0 ) && $luts == 0 } {
5844  util_m add row $line
5845  set luts 1
5846  }
5847  if { ( [string first "| CLB Registers" $line] >= 0 || [string first "| Slice Registers" $line] >= 0 ) && $regs == 0} {
5848  util_m add row $line
5849  set regs 1
5850  }
5851  if { [string first "| Block RAM Tile" $line] >= 0 && $bram == 0 } {
5852  util_m add row $line
5853  set bram 1
5854  }
5855  if { [string first "URAM " $line] >= 0 && $uram == 0} {
5856  util_m add row $line
5857  set uram 1
5858  }
5859  if { [string first "DSPs" $line] >= 0 && $dsps == 0 } {
5860  util_m add row $line
5861  set dsps 1
5862  }
5863  if { [string first "Bonded IOB" $line] >= 0 && $ios == 0 } {
5864  util_m add row $line
5865  set ios 1
5866  }
5867  }
5868  util_m add row
5869 
5870  close $f
5871  puts $o [util_m format 2string]
5872  close $o
5873 }
5874 
5875 # Check Git Version when sourcing hog.tcl
5876 if {[GitVersion 2.7.2] == 0 } {
5877  Msg Error "Found Git version older than 2.7.2. Hog will not work as expected, exiting now."
5878 }
5879 
5880 ### Source the Create project file TODO: Do we need to source in hog.tcl?
5881 #source [file dirname [info script]]/create_project.tcl