1############################################################################
2##
3## Copyright (c) 2019, The Regents of the University of California
4## All rights reserved.
5##
6## BSD 3-Clause License
7##
8## Redistribution and use in source and binary forms, with or without
9## modification, are permitted provided that the following conditions are met:
10##
11## * Redistributions of source code must retain the above copyright notice, this
12##   list of conditions and the following disclaimer.
13##
14## * Redistributions in binary form must reproduce the above copyright notice,
15##   this list of conditions and the following disclaimer in the documentation
16##   and/or other materials provided with the distribution.
17##
18## * Neither the name of the copyright holder nor the names of its
19##   contributors may be used to endorse or promote products derived from
20##   this software without specific prior written permission.
21##
22## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23## AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25## ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
26## LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27## CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28## SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29## INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30## CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31## ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32## POSSIBILITY OF SUCH DAMAGE.
33##
34############################################################################
35
36# Functions/variables common to metrics scripts.
37
38proc define_metric { name short_name fmt cmp_op margin_expr } {
39  variable metrics
40  dict set metrics $name [list $short_name $fmt $cmp_op $margin_expr]
41}
42
43proc metric_names {} {
44  variable metrics
45  return [dict keys $metrics]
46}
47
48proc metric_short_name { name } {
49  variable metrics
50  lassign [dict get $metrics $name] short_name fmt cmp_op margin_expr
51  return $short_name
52}
53
54proc metric_format { name } {
55  variable metrics
56  lassign [dict get $metrics $name] short_name fmt cmp_op margin_expr
57  return $fmt
58}
59
60proc metric_json_key { name } {
61  return $name
62}
63
64proc metric_cmp_op { name } {
65  variable metrics
66  lassign [dict get $metrics $name] short_name fmt cmp_op margin_expr
67  return $cmp_op
68}
69
70proc metric_margin_expr { name } {
71  variable metrics
72  lassign [dict get $metrics $name] short_name fmt cmp_op margin_expr
73  return $margin_expr
74}
75
76proc cmp_op_negated { cmp_op } {
77  if { $cmp_op == "<" } {
78    return ">="
79  } elseif { $cmp_op == "<=" } {
80    return ">"
81  } elseif { $cmp_op == ">" } {
82    return "<="
83  } elseif { $cmp_op == ">=" } {
84    return "<"
85  } elseif { $cmp_op == "==" } {
86    return "!="
87  } else {
88    error "unknown comparison operator"
89  }
90}
91
92# make format field width to match short name width
93define_metric "instance_count" "  insts" "%7d" "<" {$value * .2}
94define_metric "design_area" "   area" "%7.0f" "<" {$value * .2}
95define_metric "utilization" "util" "%4.1f" "<" {$value * .2}
96define_metric "worst_slack_min" "slack_min" "%9.3f" ">" {-$clock_period * .1}
97define_metric "worst_slack_max" "slack_max" "%9.3f" ">" {-$clock_period * .1}
98define_metric "tns_max" " tns_max" "%8.1f" ">" {-$clock_period * .1 * $instance_count * .1}
99define_metric "clock_skew" "clk_skew" "%8.3f" "<=" {$value * .2}
100define_metric "max_slew_violations" "max_slew" "%8d" "<=" {0}
101define_metric "max_capacitance_violations" "max_cap" "%7d" "<=" {0}
102define_metric "max_fanout_violations" "max_fanout" "%10d" "<=" {0}
103define_metric "DPL::errors" "DPL" "%3d" "<=" {0}
104define_metric "ANT::errors" "ANT" "%3d" "<=" {0}
105define_metric "DRT::drv" "drv" "%3d" "<=" {0}
106define_metric "clock_period" "" "%d" "<=" {0}
107
108################################################################
109
110# Used by regression.tcl to check pass/fail metrics test.
111# Returns "pass" or failing metric comparison string.
112proc check_test_metrics { test } {
113  # Don't require json until it is really needed.
114  package require json
115
116  set metrics_file [test_metrics_result_file $test]
117  set metrics_limits_file [test_metrics_limits_file $test]
118  if { ![file exists $metrics_file] } {
119    return "missing metrics file"
120  }
121  set stream [open $metrics_file r]
122  set json_string [read $stream]
123  close $stream
124  if { [catch {json::json2dict $json_string} metrics_dict] } {
125    return "error parsing metrics json"
126  }
127
128  if { ![file exists $metrics_limits_file] } {
129    return "missing metrics limits file"
130  }
131  set stream [open $metrics_limits_file r]
132  set json_string [read $stream]
133  close $stream
134  if { [catch {json::json2dict $json_string} metrics_limits_dict] } {
135    return "error parsing metrics limits json"
136  }
137
138  set failures ""
139  foreach name [metric_names] {
140    set json_key [metric_json_key $name]
141    set cmp_op [metric_cmp_op $name]
142    if { [dict exists $metrics_dict $json_key] } {
143      set value [dict get $metrics_dict $json_key]
144      if { [dict exists $metrics_limits_dict $json_key] } {
145        set limit [dict get $metrics_limits_dict $json_key]
146        if { ![expr $value $cmp_op $limit] } {
147          fail "$name [format [metric_format $name] $value] [cmp_op_negated $cmp_op] [format [metric_format $name] $limit]"
148        }
149      } else {
150        fail "missing $name in metric limits"
151      }
152    } else {
153      fail "missing $name in metrics"
154    }
155  }
156  if { $failures == "" } {
157    return "pass"
158  } else {
159    return $failures
160  }
161}
162
163proc fail { msg } {
164  upvar 1 failures failures
165
166  if { $failures != "" } {
167    set failures [concat $failures "; $msg"]
168  } else {
169    set failures $msg
170  }
171}
172
173################################################################
174
175proc report_flow_metrics_main {} {
176  global argc argv test_groups
177  if { $argc == 0 } {
178    set tests $test_groups(flow)
179  } else {
180    set tests [expand_tests $argv]
181  }
182
183  report_metrics_header
184  foreach test $tests {
185    report_test_metrics $test
186  }
187}
188
189proc report_metrics_header {} {
190  puts -nonewline [format "%-20s" ""]
191  foreach name [metric_names] {
192    set short_name [metric_short_name $name]
193    if { $short_name != "" } {
194      puts -nonewline " $short_name"
195    }
196  }
197  puts ""
198}
199
200proc report_test_metrics { test } {
201  # Don't require json until it is really needed.
202  package require json
203
204  set metrics_result_file [test_metrics_result_file $test]
205  if { [file exists $metrics_result_file] } {
206    set stream [open $metrics_result_file r]
207    set json_string [read $stream]
208    close $stream
209    puts -nonewline [format "%-20s" $test]
210    if { ![catch {json::json2dict $json_string} metrics_dict] } {
211      foreach name [metric_names] {
212        set short_name [metric_short_name $name]
213        if { $short_name != "" } {
214          set key [metric_json_key $name]
215          if { [dict exists $metrics_dict $key] } {
216            set value [dict get $metrics_dict $key]
217            set field [format [metric_format $name] $value]
218          } else {
219            set field [format "%[string length $short_name]s" "N/A"]
220          }
221          puts -nonewline " $field"
222        }
223      }
224    }
225    puts ""
226  }
227}
228
229################################################################
230
231proc compare_flow_metrics_main {} {
232  global argc argv test_groups
233  if { $argv == "help" || $argv == "-help" } {
234    puts {Usage: save_flow_metrics [test1]...}
235  } else {
236    set relative 0
237    if { $argc >= 1 && [lindex $argv 0] == "-relative" } {
238      set relative 1
239      set argc [expr $argc - 1]
240      set argv [lrange $argv 1 end]
241    }
242    if { $argc == 0 } {
243      set tests $test_groups(flow)
244    } else {
245      set tests [expand_tests $argv]
246    }
247
248    report_metrics_header
249    foreach test $tests {
250      compare_test_metrics $test $relative
251    }
252  }
253}
254
255proc compare_test_metrics { test relative } {
256  # Don't require json until it is really needed.
257  package require json
258
259  set metrics_file [test_metrics_file $test]
260  set result_file [test_metrics_result_file $test]
261  if { [file exists $metrics_file] \
262         && [file exists $result_file] } {
263    set metrics_stream [open $metrics_file r]
264    set results_stream [open $result_file r]
265    set metrics_json [read $metrics_stream]
266    set results_json [read $results_stream]
267    close $metrics_stream
268    close $results_stream
269    puts -nonewline [format "%-20s" $test]
270    if { ![catch {json::json2dict $metrics_json} metrics_dict] \
271         && ![catch {json::json2dict $results_json} results_dict]} {
272      foreach name [metric_names] {
273        set short_name [metric_short_name $name]
274        if { $short_name != "" } {
275          set key [metric_json_key $name]
276          if { [dict exists $metrics_dict $key] \
277               && [dict exists $results_dict $key]} {
278            set metrics_value [dict get $metrics_dict $key]
279            set result_value [dict get $results_dict $key]
280            if { $metrics_value != 0 && $relative } {
281              set delta [expr ($result_value - $metrics_value) * 100.0 / abs($metrics_value)]
282              set field [format "%+.1f%%" $delta]
283              set field [format "%[string length $short_name]s" $field]
284            } else {
285              set delta [expr $result_value - $metrics_value]
286              set field [format [metric_format $name] $delta]
287            }
288          } else {
289            set field [format "%[string length $short_name]s" "N/A"]
290          }
291          puts -nonewline " $field"
292        }
293      }
294    }
295    puts ""
296  }
297}
298
299################################################################
300
301# Copy result metrics to test results saved in the repository.
302proc save_flow_metrics_main {} {
303  global argc argv
304
305  if { $argv == "help" || $argv == "-help" } {
306    puts {Usage: save_flow_metrics [test1]...}
307  } else {
308    if { $argc == 0 } {
309      set tests [expand_tests "flow"]
310    } else {
311      set tests [expand_tests $argv]
312    }
313    foreach test $tests {
314      save_metrics $test
315    }
316  }
317}
318
319proc save_metrics { test } {
320  if { [lsearch [group_tests "all"] $test] == -1 } {
321    puts "Error: test $test not found."
322  } else {
323    set metrics_result_file [test_metrics_result_file $test]
324    set metrics_file [test_metrics_file $test]
325    file copy -force $metrics_result_file $metrics_file
326  }
327}
328
329################################################################
330
331# Save result metrics + margins as metric limits.
332proc save_flow_metric_limits_main {} {
333  global argc argv
334  if { $argv == "help" || $argv == "-help" } {
335    puts {Usage: save_flow_metric_limits [failures] test1 [test2]...}
336  } else {
337    if { $argc == 0 } {
338      set tests [expand_tests "flow"]
339    } else {
340      set tests [expand_tests $argv]
341    }
342    foreach test $tests {
343      save_metric_limits $test
344    }
345  }
346}
347
348proc save_metric_limits { test } {
349  # Don't require json until it is really needed.
350  package require json
351
352  set metrics_file [test_metrics_result_file $test]
353  set metrics_limits [test_metrics_limits_file $test]
354  if { ! [file exists $metrics_file] } {
355    puts "Error: metrics file $metrics_file not found."
356  } else {
357    set stream [open $metrics_file r]
358    set json_string [read $stream]
359    close $stream
360    if { ![catch {json::json2dict $json_string} metrics_dict] } {
361      set limits_stream [open $metrics_limits w]
362      puts $limits_stream "{"
363      set first 1
364      # Find value of variables used in margin expr's.
365      foreach var {"clock_period" "instance_count"} {
366        set key [metric_json_key $var]
367        if { [dict exists $metrics_dict $key] } {
368          set value [dict get $metrics_dict $key]
369          set $var $value
370        } else {
371          puts "Error: $test missing $var metric."
372          return
373        }
374      }
375      foreach name [metric_names] {
376        set key [metric_json_key $name]
377        if { [dict exists $metrics_dict $key] } {
378          set value [dict get $metrics_dict $key]
379          set margin_expr [metric_margin_expr $name]
380          set margin [expr $margin_expr]
381          set value_limit [expr $value + $margin]
382          if { $first } {
383            puts -nonewline $limits_stream "  "
384          } else {
385            puts -nonewline $limits_stream " ,"
386          }
387          puts $limits_stream "\"$key\" : \"$value_limit\""
388          set first 0
389        }
390      }
391
392      puts $limits_stream "}"
393      close $limits_stream
394    } else {
395      puts "Error: json parse error $metrics_dict"
396    }
397  }
398}
399
400################################################################
401
402proc test_metrics_file { test } {
403  global test_dir
404  return [file join $test_dir "$test.metrics"]
405}
406
407proc test_metrics_result_file { test } {
408  global test_dir
409  return [file join $test_dir "results" "$test.metrics"]
410}
411
412proc test_metrics_limits_file { test } {
413  global test_dir
414  return [file join $test_dir "$test.metrics_limits"]
415}
416