Settings.pm 26.7 KB
Newer Older
Christian Fibich's avatar
Christian Fibich committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#-------------------------------------------------------------------------------
#  University of Applied Sciences Technikum Wien
#
#  Department of Embedded Systems
#  http://embsys.technikum-wien.at
#
#  Josef Ressel Center for Verification of Embedded Computing Systems
#  http://vecs.technikum-wien.at
#
#-------------------------------------------------------------------------------
#  File:              Settings.pm
#  Created on:        16.02.2015
#  $LastChangedBy$
#  $LastChangedDate$
#
#  Description:
#
# Contains helper functions to deal with FIJI Settings files.
#-------------------------------------------------------------------------------


22
## @file
23

24
## @class FIJI::Settings
25
#
26
# Contains helper functions to deal with FIJI Settings files.
27
28
package FIJI::Settings;

29
30
31
use strict;
use warnings;

32
use Scalar::Util 'blessed';
33
34
35
use Log::Log4perl qw(get_logger);
use Scalar::Util "looks_like_number";
use Config::Simple;
36
37
38
use Data::Dumper;
use POSIX qw(ceil);
use List::Util qw(max);
39

40
41
use FIJI qw(:all);

Stefan Tauner's avatar
Stefan Tauner committed
42
43
44
45
46
## @function new ($phase, $fiji_ini_file, $existing_settings)
# Create a new settings instance.
#
# \param $phase Tool flow phase the settings need to be compatible with.
# \param $fiji_ini_file (optional) Path to the configuration file to read.
47
48
# \param $existing_settings (optional) A reference to reuse and return
#        with values read from file. Any contained data will be cleared.
49
50
# \returns The new settings instance or a string describing the reason
#          why it could not be created.
51
52
sub new ($;$$) {
  my $logger = get_logger();
Stefan Tauner's avatar
Stefan Tauner committed
53
54
55
  my ($class, $phase, $fiji_ini_file, $existing_settings) = @_;

  my $fiji_settings_ref;
56
  # if there is no existing settings instance yet, create one
Stefan Tauner's avatar
Stefan Tauner committed
57
58
59
60
  if (!defined($existing_settings)) {
    $fiji_settings_ref = {};
    $fiji_settings_ref = bless($fiji_settings_ref, $class);
  } else {
61
62
63
64
65
66
67
68
69
70
71
    $fiji_settings_ref = $existing_settings;
  }
  if (!blessed($fiji_settings_ref) || !$fiji_settings_ref->isa("FIJI::Settings")) {
    my $msg;
    if (!defined($existing_settings)) {
      $msg = "Could not create FIJI::Settings instance.";
    } else {
      $msg = "Given settings are not of type FIJI::Settings.";
    }
    $logger->error($msg);
    return $msg;
Stefan Tauner's avatar
Stefan Tauner committed
72
  }
73

74
  # If there is a file given, try to read it. Else just create a default instance.
75
  if (defined($fiji_ini_file)) {
Stefan Tauner's avatar
Stefan Tauner committed
76
    $fiji_settings_ref = read_settingsfile($phase, $fiji_ini_file, $fiji_settings_ref);
77
78
    if (!ref($fiji_settings_ref)) {
      return $fiji_settings_ref; # actually an error message
79
80
    }
  } else {
81
82
    $fiji_settings_ref->{'design'} = {};
    _set_defaults(DESIGNMAP, $fiji_settings_ref->{'design'}, $phase);
83
    $fiji_settings_ref->{'fius'} = [];
84
  }
85

Stefan Tauner's avatar
Stefan Tauner committed
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
  return $fiji_settings_ref;
}


sub _export_value {
  my $logger = get_logger();
  my ($map_ref, $k, $v_ref) = @_;

  if (defined($map_ref->{$k}->{'type'})) {
    my $orig = ${$v_ref};
    if ($map_ref->{$k}->{'type'} eq 'hexadecimal') {
      ${$v_ref} = sprintf("0x%x", $orig);
    # } elsif ($map_ref->{$k}->{'type'} eq 'natural') {
    # } elsif ($map_ref->{$k}->{'type'} eq 'boolean') {
      $logger->trace("Converted value of $k (\"$orig\") to \"${$v_ref}\".") if ($orig ne ${$v_ref});
    }
  # } elsif (defined($map_ref->{$k}->{'values'})) {
103
104
105
106
107
108
109
110
111
  }
}

## @method save ($fiji_ini_file)
# @brief Store contained FIJI Settings to file.
#
# @ATTENTION Will happily overwrite existing files!
#
# \param fiji_ini_file The file name to write the FIJI Settings to.
Stefan Tauner's avatar
Stefan Tauner committed
112
sub save ($) {
113
114
115
116
117
  my $logger = get_logger();
  my ($self, $fiji_ini_file) = @_;
  return "No file name given" if !defined($fiji_ini_file);

  my $fiji_ini = new Config::Simple(syntax=>'ini');
118
119
  my $design_ref;
  my $fiu_cnt = 0;
120
121
122
123
124
  foreach my $key (keys %{$self}) {
    my $val = $self->{$key};
    if (ref(\$val) eq "REF") {
      if (ref($val) eq "HASH") {
        if ($key eq "design") {
125
          $design_ref = $val;
126
127
128
          next;
        }
      } elsif (ref($val) eq "ARRAY") {
129
        if ($key eq "fius") {
130
131
          foreach my $fiu (@{$val}) {
            my $ini_fiu;
Stefan Tauner's avatar
Stefan Tauner committed
132
            
133
            foreach my $k (keys(%{$fiu})) {
Stefan Tauner's avatar
Stefan Tauner committed
134
              my $ini_name = FIUMAP->{$k}->{'ini_name'};
135
136
137
138
139
              if (!defined($fiu->{$k})) {
                $logger->debug("Skip saving undefined value of FIU constant with key $ini_name.");
                next;
              }
              # Copy value to new hash with external naming.
Stefan Tauner's avatar
Stefan Tauner committed
140
              $ini_fiu->{$ini_name} = $fiu->{$k};
141
              # Convert value to external representation
142
143
144
145
146
147
148
149
              _export_value(FIUMAP, $k, \$ini_fiu->{$ini_name});
              $logger->trace(sprintf("Exporting FIU%d setting %s -> %s (%s -> %s).",
                                     $fiu_cnt,
                                     $k,
                                     $ini_name,
                                     defined($fiu->{$k}) ? $fiu->{$k} : "undef",
                                     defined($ini_fiu->{$ini_name}) ? $ini_fiu->{$ini_name} : "undef"),
                            );
150
151
152
153
154
155
156
157
158
159
160
            }
            $fiji_ini->set_block("FIU" . $fiu_cnt++, $ini_fiu);
          }
          next;
        }
      }
    }
    my $err = "Unknown element found in FIJI Settings: \"$val\"";
    $logger->error($err);
    return $err;
  }
161
  $design_ref->{'FIU_NUM'} = $fiu_cnt;
162
  my $ini_design;
163
  foreach my $k (keys(%{$design_ref})) {
164
165
166
167
168
169
170
171
    my $ini_name = DESIGNMAP->{$k}->{'ini_name'};
    if (!defined($design_ref->{$k})) {
      $logger->debug("Skip saving undefined value of design constant with key $ini_name.");
      next;
    }
    # Copy value to new hash with external naming.
    $ini_design->{$ini_name} = $design_ref->{$k};
    # Convert value to external representation
172
173
174
175
176
177
178
    _export_value(DESIGNMAP, $k, \$ini_design->{$ini_name});
    $logger->trace(sprintf("Exporting Design setting %s -> %s (%s -> %s).",
                           $k,
                           $ini_name,
                           defined($design_ref->{$k}) ? $design_ref->{$k} : "undef",
                           defined($ini_design->{$ini_name}) ? $ini_design->{$ini_name} : "undef"),
                  );
Stefan Tauner's avatar
Stefan Tauner committed
179
  }
180
  $fiji_ini->set_block("CONSTS", $ini_design);
181

182
183
184
185
186
187
188
189
190
  if (!defined($fiji_ini->write($fiji_ini_file))) {
    my $err = Config::Simple->error();
    $logger->error($err);
    return $err;
  }
  return undef;
}


191
## @function read_settingsfile ($phase, $fiji_ini_file)
192
# @brief Load the FIJI Settings file containing design and FIU constants.
193
#
194
# \param phase  Tool flow phase the settings stored in the given file
Stefan Tauner's avatar
Stefan Tauner committed
195
#               need to be compatible with.
196
197
# \param fiji_ini_file The name of an .ini file with FIJI Settings:
#         - a 'consts' block containing the constants specified by
Stefan Tauner's avatar
Stefan Tauner committed
198
#           \ref _sanitize_design.
199
200
201
#         - at least one FIU block named "FIU<number>" where "<number>"
#           is a strictly increasing integer starting with 0 containing
#           the constants for the respective FIU, see \ref _sanitize_fiu
202
203
#
# \returns a reference to the hash containing the read constants.
204
sub read_settingsfile ($$$) {
205
  my $logger = get_logger();
Stefan Tauner's avatar
Stefan Tauner committed
206
  my ($phase, $fiji_ini_file, $existing_settings) = @_;
207
208
209
  my $fiji_ini;
  eval { $fiji_ini = new Config::Simple($fiji_ini_file) }; # pesky library tries to die on syntax errors
  if (!defined($fiji_ini)) {
210
211
212
213
214
    my $submsg = defined($@) ? $@ : Config::Simple->error();
    if (length($submsg) == 0) {
      $submsg = "Empty file?";
    }
    my $msg = "Could not read config file \"$fiji_ini_file\": $submsg";
215
216
    $logger->error($msg);
    return $msg;
217
218
  }

219
220
  my $design_ref = $fiji_ini->get_block("CONSTS");
  if (!(%$design_ref)) {
221
222
223
    my $msg = "Could not fetch CONSTS block from config file \"$fiji_ini_file\"";
    $logger->error($msg);
    return $msg;
224
  }
225
  _set_defaults(DESIGNMAP, $design_ref, $phase);
Stefan Tauner's avatar
Stefan Tauner committed
226

227
228
  $design_ref = _rename_import(DESIGNMAP, $design_ref);
  if (!defined($design_ref)) {
229
230
231
    my $msg = "Design constants do not match the FIJI Settings naming scheme.";
    $logger->error($msg);
    return $msg;
232
  }
Stefan Tauner's avatar
Stefan Tauner committed
233

234
  # Create a new instance or reuse the shallow hull of the existing one
235
  my $fiji_settings_ref;
236
237
238
239
240
241
242
243
  if (!defined($existing_settings)) {
    $fiji_settings_ref = {};
    bless($fiji_settings_ref, "FIJI::Settings");
  } else {
    $fiji_settings_ref = $existing_settings;
    # Clear the hash
    for (keys %$fiji_settings_ref) {
        delete $fiji_settings_ref->{$_};
Stefan Tauner's avatar
Stefan Tauner committed
244
    }
245
246
247
248
249
   }
  if (!blessed($fiji_settings_ref) || !$fiji_settings_ref->isa("FIJI::Settings")) {
    my $msg;
    if (!defined($existing_settings)) {
      $msg = "Could not create FIJI::Settings instance.";
Stefan Tauner's avatar
Stefan Tauner committed
250
    } else {
251
      $msg = "Given settings are not of type FIJI::Settings.";
Stefan Tauner's avatar
Stefan Tauner committed
252
    }
253
254
    $logger->error($msg);
    return $msg;
255
  }
256
  $fiji_settings_ref->{'design'} = $design_ref;
257
  $fiji_settings_ref->{'fius'} = [];
Stefan Tauner's avatar
Stefan Tauner committed
258
259

  # sanitize and validate read design constants
260
261
262
263
  $design_ref = _sanitize_design($design_ref, $phase);
  if (!ref($design_ref)) {
    $logger->error($design_ref);
    return $design_ref;
Stefan Tauner's avatar
Stefan Tauner committed
264
  }
265
266

  my $fiu_num = 0;
267
  # loop over all read fius
268
269
  while (1) {
    my $fiu_name = "FIU" . $fiu_num;
270
271
    my $fiu_ref = $fiji_ini->get_block($fiu_name);
    if (!(%$fiu_ref)) {
272
273
      last;
    }
274
275
    $fiu_ref = _rename_import(FIUMAP, $fiu_ref);
    if (!defined($design_ref)) {
276
277
278
      my $msg = "FIU constants of $fiu_name do not match the FIJI Settings naming scheme.";
      $logger->error($msg);
      return $msg;
Stefan Tauner's avatar
Stefan Tauner committed
279
    }
280

Stefan Tauner's avatar
Stefan Tauner committed
281
282
    my $tmp_fiu = {};
    _set_defaults(FIUMAP, $tmp_fiu, $phase);
283
284
285
    # overwrite default entries
    foreach my $k (keys(%{$fiu_ref})) {
      $tmp_fiu->{$k} = $fiu_ref->{$k};
Stefan Tauner's avatar
Stefan Tauner committed
286
    }
287
288
289
290
291
    $fiu_ref = $tmp_fiu;

    $fiu_ref = _sanitize_fiu($fiu_ref, $phase);
    if (!ref($fiu_ref)) {
      my $msg = "(Some) constants for $fiu_name in FIJI Settings are invalid.";
292
293
      $logger->error($msg);
      return $msg;
294
    }
295
    $fiji_settings_ref->{'fius'}->[$fiu_num] = $fiu_ref;
296
297
298
299
300
    $fiu_num++;
    $logger->trace("Read in $fiu_name from FIJI Settings file successfully.");
  }

  if ($fiu_num == 0) {
301
    $logger->debug("Could not fetch any FIU blocks from config file \"$fiji_ini_file\"");
302
  }
303
304
305

  # FIU_NUM is optional in the Settings file... if it was set check that
  # it corresponds to the number of FIU<number> blocks.
306
  if (defined($design_ref->{'FIU_NUM'}) && $design_ref->{'FIU_NUM'} != $fiu_num) {
307
308
309
      my $msg = FIU_NUM->{'ini_name'} . " does not match the numbers of FIU blocks found.";
      $logger->error($msg);
      return $msg;
310
  } else {
311
    $design_ref->{'FIU_NUM'} = $fiu_num; # assume the best if FIU_NUM constant is not given
312
313
  }

314
  splice(@{$fiji_settings_ref->{'fius'}}, $fiu_num);
315
  $logger->info("Successfully read in design constants and $fiu_num FIU definitions from FIJI Settings file \"$fiji_ini_file\".");
316
317
318
  return $fiji_settings_ref;
}

Stefan Tauner's avatar
Stefan Tauner committed
319
## @method set_fiu_defaults (%$fiu_ref)
320
321
322
323
# @brief Overwrite existing fields (if defined) with defaults defined in FIJI.pm.
#
sub set_fiu_defaults ($) {
  my ($self, $consts_ref) = @_;
Stefan Tauner's avatar
Stefan Tauner committed
324
325
326
327
328
329
330
331
  return _set_defaults(FIUMAP, $consts_ref);
}

## @function _set_defaults (%$map_ref, %$consts_ref)
# @brief Set defaults according to FIJI.pm.
sub _set_defaults {
  my $logger = get_logger();
  my ($map_ref, $consts_ref, $phase) = @_;
332
  foreach my $k (keys(%{$map_ref})) {
333
    if (exists($map_ref->{$k}->{'default'})) {
Stefan Tauner's avatar
Stefan Tauner committed
334
      # Set default only if it is not mandatory in the given phase.
335
336
      if (defined($phase) && scalar(grep {$_ eq $phase} @{$map_ref->{$k}->{'phases_opt'}}) == 0) {
        next;
337
      }
338

339
340
341
342
      # Also, do not overwrite existing values
      if (defined($consts_ref->{$k})) {
        next;
      }
343
344
      $consts_ref->{$k} = $map_ref->{$k}->{default};
      $logger->trace(sprintf("Adding default constant: %s (%s) = %s.", $k, $map_ref->{$k}->{'ini_name'}, $map_ref->{$k}->{default}));
345
346
    }
  }
347
348
}

349

Stefan Tauner's avatar
Stefan Tauner committed
350
351
352
353
354
355
356
357
358
359
sub validate_design_value {
  my ($k, $v_ref) = @_;
  return validate_value(DESIGNMAP, $k, $v_ref);
}

sub validate_fiu_value {
  my ($k, $v_ref) = @_;
  return validate_value(FIUMAP, $k, $v_ref);
}

360
361
362
363
364
365
366
367
368
369
## @function validate_value (%$map_ref, $k, $$v_ref, $old, $log_func)
#   Do validation (and conversation from external->internal representation)
#
#   \param map_ref  reference to FIJI Settings mappings
#   \param k        key identifying the respective setting
#   \param v_ref    scalar reference to the proposed value (that may be modified)
#   \param old      (optional) previously valid value
#   \param log_func (optional) the (log4perl) log function to use
#                   (defaul is \&Log::Log4perl::Logger::trace)
sub validate_value ($$$;$$) {
Stefan Tauner's avatar
Stefan Tauner committed
370
  my $logger = get_logger();
371
  my ($map_ref, $k, $v_ref, $old, $log_func) = @_;
Stefan Tauner's avatar
Stefan Tauner committed
372
373
374
  $log_func = \&Log::Log4perl::Logger::trace if !defined($log_func);
  if (defined($map_ref->{$k}->{'type'})) {
    my $orig = ${$v_ref};
375
376
377
378
379
    # FIXME: check if the constant is depending on another constant, that needs to be enabled.
    # my $dependency = @{$map_ref->{$k}->{'depends_on'}};
    # if (defined($dependency) && defined(@{$map_ref->{$dependency}->{'value'}}
    # }

380
381
382
383
    if ($map_ref->{$k}->{'type'} eq 'net') {
      # TODO: check syntax? there is nothing more to do in here unless we supply allowed nets...
      # which is actually possible because the input netlist is stored in the settings as well
    } elsif ($map_ref->{$k}->{'type'} eq 'hexadecimal') {
Stefan Tauner's avatar
Stefan Tauner committed
384
385
386
      if ($orig !~ /^(0|(0[xX][[:xdigit:]]+))$/) {
        $log_func->($logger, "$k: $orig does not look like a natural hexadecimal number.");
        return 0;
387
      }
Stefan Tauner's avatar
Stefan Tauner committed
388
389
390
391
392
393
394
      ${$v_ref} = hex($orig);
      # Check for natural value range. Should never trigger due to the regex above.
      if (${$v_ref} < 0) {
        $log_func->($logger, "$k: $orig is negative.");
        return 0;
      }
    } elsif ($map_ref->{$k}->{'type'} eq 'natural') {
395
      # Match hexadecimal, binary, octal and decimal numbers (the latter also in scientific notation)
Stefan Tauner's avatar
Stefan Tauner committed
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
      if ($orig !~ /^(0|(0(x[0-9a-fA-F]+|[0-7]+))|(0b[01]+)|([1-9][0-9]*(e(-[0-9])?[0-9]+)?))$/) {
        $log_func->($logger, "$k: $orig does not look like a number.");
        return 0;
      }
      
      # convert non-decimal (hexadecimal, binary, octal) values to decimal
      ${$v_ref} = oct($orig) if $orig =~ /^0/;
      if (!looks_like_number(${$v_ref})) {
        $log_func->($logger, "$k: $orig does not look like a number.");
        return 0;
      }
      # Check for natural value range. Should never trigger due to the regex above.
      if (${$v_ref} < 0) {
        $log_func->($logger, "$k: $orig is negative.");
        return 0;
      }
    } elsif ($map_ref->{$k}->{'type'} eq 'boolean') {
      # convert strings to binary if need be
      if (!defined($orig)) {
        $log_func->($logger, "$k: \"undef\" is not a boolean value.");
        return 0;
      } elsif (lc($orig) eq 'true') {
        $orig = 1;
      } elsif (lc($orig) eq 'false') {
        $orig = 0;
421
      }
Stefan Tauner's avatar
Stefan Tauner committed
422
423
424
425
426
427
      if (($orig ne '0') && ($orig ne '1')) {
        $log_func->($logger, "$k: \"$orig\" does not look like a boolean value.");
        return 0;
      }
      # ensure proper boolean value, i.e. 0 or 1
      ${$v_ref} = (!!$orig) ? 1 : 0;
428
    }
429
    $logger->trace("Converted value of $k (\"$orig\") to \"${$v_ref}\".") if (defined($orig) && $orig ne ${$v_ref});
Stefan Tauner's avatar
Stefan Tauner committed
430
  }
431

Stefan Tauner's avatar
Stefan Tauner committed
432
433
434
435
436
437
438
  if (defined($map_ref->{$k}->{'values'})) {
    my $values_ref = $map_ref->{$k}->{'values'};
    if (ref($values_ref) eq 'ARRAY') {
      # Look for given value in allowed values
      if (scalar(grep {$_ eq ${$v_ref}} @{$values_ref}) == 0) {
        $log_func->($logger, "$k: ${$v_ref} is not allowed. Allowed values are: " . join(", ", @{$map_ref->{$k}->{'values'}}));
        return 0;
439
      }
Stefan Tauner's avatar
Stefan Tauner committed
440
    } elsif (ref($values_ref) eq 'CODE') {
441
      if (!$values_ref->(${$v_ref}, $old)) {
Stefan Tauner's avatar
Stefan Tauner committed
442
443
        $log_func->($logger, "$k: ${$v_ref} is not allowed.");
        return 0;
444
      }
445
446
    }
  }
Stefan Tauner's avatar
Stefan Tauner committed
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
  return 1;
}


# @function _rename_import (%$map_ref, %$consts_ref)
# Rename and convert entries in consts_ref according to map_ref.
#
# This function takes a hash of FIJI Settings and converts its contents
# to the respective internal representation. This allows to use different
# names and value representations in the external file than within the
# implementation.
#
# \returns $consts_ref, or undef on errors
sub _rename_import {
  my $logger = get_logger();
  my ($map_ref, $consts_ref) = @_;
  if (ref($consts_ref) ne 'HASH') {
    $logger->error("Parameter is not a reference to a hash (containing design constants).");
    return undef;
  }

  # Iterating over respective hash from FIJI.pm and rename the entries
  # to match our internal naming scheme.
470
  foreach my $k (keys(%{$map_ref})) {
Stefan Tauner's avatar
Stefan Tauner committed
471
472
473
    my $ini_name = $map_ref->{$k}->{'ini_name'};
    if (exists($consts_ref->{$ini_name}) && $ini_name ne $k) {
      $consts_ref->{$k} = $consts_ref->{$ini_name};
474
475
476
477
478
      $logger->trace(sprintf("Renaming Design setting %s -> %s (=%s).",
                             $ini_name,
                             $k,
                             defined($consts_ref->{$ini_name}) ? $consts_ref->{$ini_name} : "undef"),
                    );
Stefan Tauner's avatar
Stefan Tauner committed
479
480
481
      delete $consts_ref->{$ini_name};
    }
  }
482
  foreach my $entry_key (keys(%{$consts_ref})) {
Stefan Tauner's avatar
Stefan Tauner committed
483
484
485
486
487
488
    if (!exists($map_ref->{$entry_key})) {
      $logger->debug("Deleting unknown setting %s = %s.", $entry_key, $consts_ref->{$entry_key});
      delete $consts_ref->{$entry_key};
    }
  }

489
  return $consts_ref;
490
491
}

Stefan Tauner's avatar
Stefan Tauner committed
492

493
## @function _sanitize_fiu (%$fiu_ref, $phase)
494
495
# @brief Convert and sanity check FIJI Settings.
#
496
497
498
499
500
# \param fiu_ref a reference to a hash containing FIJI Settings for a
#                single FIU.
#
# \returns A new hash with all constants required in the FIU settings
#          in sanitized form, or undef on errors.
Stefan Tauner's avatar
Stefan Tauner committed
501
sub _sanitize_fiu ($;$) {
502
  my $logger = get_logger();
Stefan Tauner's avatar
Stefan Tauner committed
503
  my ($fiu_ref, $phase) = @_;
504
  if (ref($fiu_ref) ne 'HASH') {
505
506
507
    my $msg = "Parameter is not a reference to a hash (containing FIU constants).";
    $logger->error($msg);
    return $msg;
508
509
  }

Stefan Tauner's avatar
Stefan Tauner committed
510
  $fiu_ref = _validate_hashmap(FIUMAP, $fiu_ref, $phase);
511
  if (!ref($fiu_ref)) {
Stefan Tauner's avatar
Stefan Tauner committed
512
      $logger->error("Could not validate Design Constants.");
513
      return $fiu_ref;
514
515
516
517
518
  }

  return $fiu_ref;
}

Stefan Tauner's avatar
Stefan Tauner committed
519

520
521
522
523
524
525
526
sub _disabled_via_dependency ($$$) {
  my ($map_ref, $consts_ref, $k) = @_;
  my $dependency = $map_ref->{$k}->{'depends_on'};
  return defined($dependency) && !$consts_ref->{$dependency};
}


Stefan Tauner's avatar
Stefan Tauner committed
527
528
529
sub _validate_hashmap ($$;$) {
  my $logger = get_logger();
  my ($map_ref, $consts_ref, $phase) = @_;
530
531
  my @map_keys = keys(%{$map_ref});
  foreach my $entry_key (keys(%{$consts_ref})) {
Stefan Tauner's avatar
Stefan Tauner committed
532
533
534
535
536
537
538
    my $ini_name = $map_ref->{$entry_key}->{'ini_name'};
    
    if (!exists($map_ref->{$entry_key})) {
      $logger->debug("Deleting unknown setting %s = %s.", $entry_key, $consts_ref->{$entry_key});
      next;
    }

539
540
541
542
    @map_keys = grep {$_ ne $entry_key} @map_keys; # mark constant key as done
    if (_disabled_via_dependency($map_ref, $consts_ref, $entry_key)) {
      $logger->debug(sprintf("Key %s is disabled via %s. Skipping validation.", $entry_key, $map_ref->{$entry_key}->{'depends_on'}));
    } elsif (!validate_value($map_ref, $entry_key, \$consts_ref->{$entry_key})) {
543
544
545
      my $msg = sprintf("%s = %s is invalid.", $entry_key, !defined($consts_ref->{$entry_key}) ? "<undef>" : $consts_ref->{$entry_key});
      $logger->error($msg);
      return $msg;
Stefan Tauner's avatar
Stefan Tauner committed
546
547
548
549
550
551
552
553
554
555
556
557
558
    }
  }

  if (!defined($phase)) {
    # If there is no phase we are creating an "empty" hash that has
    # all possible defaults set earlier already and the there is
    # nothing we can or should do here.
    return $consts_ref;
  }

  # Iterate over the constants defined in FIJI.pm that apparently are
  # not contained in $consts_ref.
  foreach my $k (@map_keys) {
559
560
561
562
563
    my $dependency = $map_ref->{$k}->{'depends_on'};
    if (
        scalar(grep {$_ eq $phase} @{$map_ref->{$k}->{'phases_opt'}}) == 0 && # mandatory in current phase
        !_disabled_via_dependency($map_ref, $consts_ref, $k) # no dependency or dependency is enabled
       ) {
564
565
566
      my $msg = "$k is mandatory in phase $phase.";
      $logger->error($msg);
      return $msg;
Stefan Tauner's avatar
Stefan Tauner committed
567
568
569
570
571
572
    }
  }
  return $consts_ref
}


573
## @function _sanitize_design (%$consts_ref, $phase)
Stefan Tauner's avatar
Stefan Tauner committed
574
# @brief Sanity check FIJI Design Settings.
575
#
Stefan Tauner's avatar
Stefan Tauner committed
576
# The function deals with sanity checks for the values
577
# themselves. It checks for the following conditions:
578
579
580
#
#   - FIU_NUM: > 0
#   - FIU_CFG_BITS: > 0
581
#   - TIMER_WIDTH: > 0, <= 16
582
583
584
#   - ID: > 0, < 2^15-1
#   - BAUDRATE: > 0
#
585
# \param consts_ref a reference to a hash containing some design settings
Stefan Tauner's avatar
Stefan Tauner committed
586
# \param phase the tool flow phase that defines the rules to check against
587
#
Stefan Tauner's avatar
Stefan Tauner committed
588
# \returns The given hash with all constants required in the design
589
#          settings in sanitized form, or an error message.
Stefan Tauner's avatar
Stefan Tauner committed
590
sub _sanitize_design {
591
  my $logger = get_logger();
Stefan Tauner's avatar
Stefan Tauner committed
592
  my ($consts_ref, $phase) = @_;
593
  if (ref($consts_ref) ne 'HASH') {
594
595
596
    my $msg = "Parameter is not a reference to a hash (containing design constants).";
    $logger->error($msg);
    return $msg;
597
  }
Stefan Tauner's avatar
Stefan Tauner committed
598
  
599
  # check for sane values
Stefan Tauner's avatar
Stefan Tauner committed
600
  $consts_ref = _validate_hashmap(DESIGNMAP, $consts_ref, $phase);
601
602
603
604
  if (!ref($consts_ref)) {
      my $msg = "Could not validate Design Constants.";
      $logger->error($msg);
      return "$msg $consts_ref";
605
  }
Stefan Tauner's avatar
Stefan Tauner committed
606
607

  if (($consts_ref->{'FIU_CFG_BITS'} <= 0)) {
608
609
610
      my $msg = "FIU_CFG_BITS is <= 0.";
      $logger->error($msg);
      return $msg;
611
  }
612
  if (($consts_ref->{'TIMER_WIDTH'} <= 0) || ($consts_ref->{'TIMER_WIDTH'} > 16)) {
613
614
615
      my $msg = "TIMER_WIDTH is invalid ($consts_ref->{'TIMER_WIDTH'}).";
      $logger->error($msg);
      return $msg;
616
  }
617
  if (defined($consts_ref->{'ID'}) && ($consts_ref->{ID} < 0 || $consts_ref->{ID} > (2**16 - 1))) {
618
619
620
      my $msg = "ID is invalid ($consts_ref->{ID}).";
      $logger->error($msg);
      return $msg;
621
  }
Stefan Tauner's avatar
Stefan Tauner committed
622
  if (($consts_ref->{'BAUDRATE'} <= 0)) {
623
624
625
      my $msg = "BAUDRATE missing is <= 0.";
      $logger->error($msg);
      return $msg;
626
  }
Stefan Tauner's avatar
Stefan Tauner committed
627

628
  return $consts_ref;
629
630
}

631
632
633
634
635
sub _log2 {
    my $val = shift;
    return ($val > 0) ? (log($val)/log(2)) : 0;
}

636
sub estimate_resources {
637
638
639
    my $logger = get_logger();
    my ($settings_ref) = @_;

640
641
    # FIXME where do we put these values? they are likely to change if the VHDL
    # source is changed...
642
643
644
645
646
647
648
649
650
651
652
653
654
655

    my $CTRL_COMB_FACTOR_MIN   = 0.85; # c5 ft1
    my $CTRL_COMB_FACTOR_MAX   = 1.2;  # c3de0
    my $FIU_COMB_FACTOR_RT_MIN = 0.36; # v5max
    my $FIU_COMB_FACTOR_RT_MAX = 1.22; # c3 de0
    my $FIU_COMB_FACTOR_SI_MIN = 0.42; # v5mid
    my $FIU_COMB_FACTOR_SI_MAX = 0.75; # c3 de0
    my $BUFFER_COMB_FACTOR_MIN = 0.85; # v5max
    my $BUFFER_COMB_FACTOR_MAX = 1.1;  # c5 ft1
    my $UART_COMB_FACTOR_MIN   = 0.9;  # v5mid
    my $UART_COMB_FACTOR_MAX   = 1.83; # c3 de0

    my $consts_ref = $settings_ref->{'design'};
    my $fiu_ref = $settings_ref->{'fius'};
656
657
    my $fiubytes = ceil((@$fiu_ref*$consts_ref->{'CFGS_PER_MSG'}*$consts_ref->{'FIU_CFG_BITS'})/8.0);
    my $cfgbytes = ($consts_ref->{'TIMER_WIDTH'} * $consts_ref->{'CFGS_PER_MSG'} + 4);
658
659
660
661
662
663
664
665
666
667
    my $comb_min = 0;
    my $comb_max = 0;
    my $reg  = 0;
    my $uart_regs   = 0;
    my $ctrl_regs   = 0;
    my $fiu_regs    = 0;
    my $buffer_regs = 0;
    my $rv;


668
    # resources for FIC
669

670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
    my $ctrl_reg_hash = {
            TRIGGER_SYNC => 3,
            FSM_STATE    => 9,
            CRC          => 8,
            ERROR_BITS   => 3,
            INJ_START    => 1,
            ID           => 16,
            CONFIG       => 8,
            RST_CNTR     => ($consts_ref->{'RESET_DUT_IN_EN'}) ? ceil(_log2($consts_ref->{'RESET_DUT_IN_DURATION'})) : 0,
            BYTE_CNTR    => ceil(_log2(max($fiubytes,$cfgbytes))),
            PAT_CNTR     => ceil(_log2($consts_ref->{'CFGS_PER_MSG'})) * 2, # we need to store an older value, too
            TIMER_STOR   => $consts_ref->{'TIMER_WIDTH'} * 8 * $consts_ref->{'CFGS_PER_MSG'}, # value storage
            TIMER_VAL    => $consts_ref->{'TIMER_WIDTH'} * 8,
            TIMER_IDLE   => 1
        };

    my $uart_reg_hash = {
            RX_SYNC         => 4,
            RX_BIT_COUNT    => 3,
            RX_BAUD_GEN     => ceil(_log2($consts_ref->{'FREQUENCY'}/$consts_ref->{'BAUDRATE'})),
            RX_STATE        => 4,
            TX_BIT_COUNT    => 3,
            TX_BAUD_GEN     => ceil(_log2($consts_ref->{'FREQUENCY'}/$consts_ref->{'BAUDRATE'})),
            TX_DATA_REG     => 8,
            TX_OUT_REG      => 1,
            TX_STATE        => 4,
            OUT_REGS_TO_CTRL=> 4
        };

    my $buffer_reg_hash = {
            BUFFER_STORAGE  => 10, # Buffer width * (Depth+1)
            BUFFER_INDEX_RD => 3, # (ceil(log2(depth)) + 1)
            BUFFER_INDEX_WR => 3, # (ceil(log2(depth)) + 1)
            READER_STATE    => 2
        };
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732

    $logger->debug("Num FIUs: ".@$fiu_ref);

    my $stuck_open_flag = 0;

    # resources for FIUs
    foreach my $fiu (@$fiu_ref) {
        my $this_fiu_reg;
        $this_fiu_reg += 12; # we don't assume regs get optimized away when using a single model
        $this_fiu_reg += 1  if ($fiu->{'FIU_MODEL'} eq "RUNTIME" or $fiu->{'FIU_MODEL'} eq "DELAY");
        if (($fiu->{'FIU_MODEL'} eq "RUNTIME" or $fiu->{'FIU_MODEL'} eq "STUCK_OPEN")) {
            # actually, STUCK_OPEN will only be implemented if additionally $fiu->{'FIU_LFSR_MASK'} != 0 
            # but to give the user a conservative estimate immediately, we omit this condition

            $this_fiu_reg += 1;
            $stuck_open_flag = 1;
        }
        if ($fiu->{'FIU_MODEL'} eq "RUNTIME") {
            $comb_min += $this_fiu_reg * $FIU_COMB_FACTOR_RT_MIN;
            $comb_max += $this_fiu_reg * $FIU_COMB_FACTOR_RT_MAX;
        } else {
            $comb_min += $this_fiu_reg * $FIU_COMB_FACTOR_SI_MIN;
            $comb_max += $this_fiu_reg * $FIU_COMB_FACTOR_RT_MAX;
        }
        $fiu_regs += $this_fiu_reg
    }

    # LFSR will get optimized away if it is not used
733
734
735
    $ctrl_reg_hash->{'LFSR'} = $consts_ref->{'LFSR_WIDTH'} if $stuck_open_flag != 0;

    # calculate total registers
736

737
738
739
    for my $k (keys(%$ctrl_reg_hash)) {
        $ctrl_regs += $ctrl_reg_hash->{$k};
    }
740
741

    $logger->debug("ESTIMATE: CTRL will need ~$ctrl_regs registers");
742
743
744
745
746

    for my $k (keys(%$uart_reg_hash)) {
        $uart_regs += $uart_reg_hash->{$k};
    }

747
    $logger->debug("ESTIMATE: UART will need ~$uart_regs registers");
748
749
750
751
752
753

    for my $k (keys(%$buffer_reg_hash)) {
        $buffer_regs += $buffer_reg_hash->{$k};
    }

    $logger->debug("ESTIMATE: BUFFER will need ~$buffer_regs registers");
754
755
756
757
758
759
760
761
762
763
764
765
766
767
    $logger->debug("ESTIMATE: FIUS will need ~$fiu_regs registers");

    $reg  = $ctrl_regs + $buffer_regs + $uart_regs + $fiu_regs;

    $comb_min += $ctrl_regs   * $CTRL_COMB_FACTOR_MIN;
    $comb_max += $ctrl_regs   * $CTRL_COMB_FACTOR_MAX;
    $comb_min += $buffer_regs * $BUFFER_COMB_FACTOR_MIN;
    $comb_max += $buffer_regs * $BUFFER_COMB_FACTOR_MAX;
    $comb_min += $uart_regs   * $UART_COMB_FACTOR_MIN;
    $comb_max += $uart_regs   * $UART_COMB_FACTOR_MAX;

    $rv->{'regs'} = ceil($reg/10)*10;
    $rv->{'comb_min'} = ceil($comb_min/10)*10;
    $rv->{'comb_max'} = ceil($comb_max/10)*10;
768
769
    $logger->debug("ESTIMATE: current config will need ~$reg registers and ~$comb_min to $comb_max LUTs (tech-dependent)");
    $logger->debug("ESTIMATE: current config will need ~$rv->{'regs'} registers and ~$rv->{'comb_min'} to $rv->{'comb_max'} LUTs (tech-dependent)");
770
771
772
    return $rv;
}

773
1;