Settings.pm 29.9 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
#-------------------------------------------------------------------------------
#  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
#
#-------------------------------------------------------------------------------
#  Description:
#
# Contains helper functions to deal with FIJI Settings files.
#-------------------------------------------------------------------------------

Christian Fibich's avatar
Christian Fibich committed
16
17
## @file Settings.pm
# @brief Contains class \ref FIJI::Settings
18

19
## @class FIJI::Settings
20
#
Christian Fibich's avatar
Christian Fibich committed
21
# @brief Contains helper functions to deal with FIJI Settings files.
22
23
package FIJI::Settings;

24
25
26
use strict;
use warnings;

27
use Scalar::Util 'blessed';
28
29
use Log::Log4perl qw(get_logger);
use Scalar::Util "looks_like_number";
30
31
use POSIX qw(ceil);
use List::Util qw(max);
32
use File::Spec;
33

Christian Fibich's avatar
Christian Fibich committed
34
use FIJI::ConfigSorted;
35
36
use FIJI qw(:all);

Christian Fibich's avatar
Christian Fibich committed
37
## @var @base_resources stores the resource count for default config
Christian Fibich's avatar
Christian Fibich committed
38
39
my @base_resources;

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

    my $fiji_settings_ref;

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

73
    my $warn;
74
    # If there is a file given, try to read it. Else just create a default instance.
Christian Fibich's avatar
Christian Fibich committed
75
    if (defined($fiji_ini_file)) {
76
77
        ($fiji_settings_ref, $warn) = read_settingsfile($phase, $fiji_ini_file, $fiji_settings_ref);
        if (!defined($fiji_settings_ref)) {
78
            return (undef, $warn);
79
        }
80
        $fiji_settings_ref->{'filename'} = $fiji_ini_file;
81
82
    } else {
        $fiji_settings_ref->{'design'} = {};
Christian Fibich's avatar
Christian Fibich committed
83
        _set_defaults(DESIGNMAP, $fiji_settings_ref->{'design'}, $phase);
84
        $fiji_settings_ref->{'fius'}     = [];
85
        $fiji_settings_ref->{'filename'} = File::Spec->curdir();
86
    }
Stefan Tauner's avatar
Stefan Tauner committed
87

88
    @base_resources = _est_resources(DESIGNMAP->{'FREQUENCY'}->{'default'}, DESIGNMAP->{'BAUDRATE'}->{'default'}, DESIGNMAP->{'TIMER_WIDTH'}->{'default'}, DESIGNMAP->{'RST_DUT_IN_DUR'}->{'default'}, DESIGNMAP->{'LFSR_WIDTH'}->{'default'}, 1, "logarithmic");
89

90
    return ($fiji_settings_ref, $warn);
91
}
Stefan Tauner's avatar
Stefan Tauner committed
92
93

sub _export_value {
94
    my $logger = get_logger("");
Christian Fibich's avatar
Christian Fibich committed
95
    my ($map_ref, $k, $v_ref) = @_;
96

Christian Fibich's avatar
Christian Fibich committed
97
    if (defined($map_ref->{$k}->{'type'})) {
98
        my $orig = ${$v_ref};
99
        if ($map_ref->{$k}->{'type'} eq 'hexadecimal' || $map_ref->{$k}->{'type'} eq 'lfsrpoly') {
Christian Fibich's avatar
Christian Fibich committed
100
            ${$v_ref} = sprintf("0x%x", $orig);
101
102
103

            # } elsif ($map_ref->{$k}->{'type'} eq 'natural') {
            # } elsif ($map_ref->{$k}->{'type'} eq 'boolean') {
Christian Fibich's avatar
Christian Fibich committed
104
            $logger->trace("Converted value of $k (\"$orig\") to \"${$v_ref}\".") if ($orig ne ${$v_ref});
105
106
107
        } elsif ($map_ref->{$k}->{'type'} eq 'net') {
            # Due to an annoying behavior of Config::Simple we have to enclose
            # escaped identifiers with quotes. These are necessary to preserve spaces.
108
109
            # This also helps with concatenations... so simply quote all nets unconditionally.
            ${$v_ref} = "\"$orig\"";
110
111
        }
    }
112
113
}

114
## @method public save ($fiji_ini_file)
115
116
# @brief Store contained FIJI Settings to file.
#
Christian Fibich's avatar
Christian Fibich committed
117
# @attention Will happily overwrite existing files!
118
#
119
# @param fiji_ini_file The file name to write the FIJI Settings to.
Stefan Tauner's avatar
Stefan Tauner committed
120
sub save ($) {
121
    my $logger = get_logger("");
Christian Fibich's avatar
Christian Fibich committed
122
    my ($self, $fiji_ini_file) = @_;
123
124
    return "No file name given" if !defined($fiji_ini_file);

Christian Fibich's avatar
Christian Fibich committed
125
    my $fiji_ini = new FIJI::ConfigSorted(syntax => 'ini');
126
127
    my $design_ref;
    my $fiu_cnt = 0;
Christian Fibich's avatar
Christian Fibich committed
128
    foreach my $key (keys %{$self}) {
129
        my $val = $self->{$key};
Christian Fibich's avatar
Christian Fibich committed
130
131
132
        if (ref(\$val) eq "REF") {
            if (ref($val) eq "HASH") {
                if ($key eq "design") {
133
134
135
                    $design_ref = $val;
                    next;
                }
Christian Fibich's avatar
Christian Fibich committed
136
137
138
            } elsif (ref($val) eq "ARRAY") {
                if ($key eq "fius") {
                    foreach my $fiu (@{$val}) {
139
140
                        my $ini_fiu;

Christian Fibich's avatar
Christian Fibich committed
141
                        foreach my $k (keys(%{$fiu})) {
142
                            my $ini_name = FIUMAP->{$k}->{'ini_name'};
Christian Fibich's avatar
Christian Fibich committed
143
                            if (!defined($fiu->{$k})) {
144
145
146
147
148
149
150
151
                                $logger->debug("Skip saving undefined value of FIU constant with key $ini_name.");
                                next;
                            }

                            # Copy value to new hash with external naming.
                            $ini_fiu->{$ini_name} = $fiu->{$k};

                            # Convert value to external representation
Christian Fibich's avatar
Christian Fibich committed
152
153
                            _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"),);
154
                        }
Christian Fibich's avatar
Christian Fibich committed
155
                        $fiji_ini->set_block("FIU" . $fiu_cnt++, $ini_fiu);
156
157
158
                    }
                    next;
                }
159
            }
160
161
        } elsif ($key eq "filename") {
            next;
162
        }
163
164

        my $err = "Unknown element found in FIJI Settings: key: \"$key\" val: \"$val\"";
165
166
167
168
169
        $logger->error($err);
        return $err;
    }
    $design_ref->{'FIU_NUM'} = $fiu_cnt;
    my $ini_design;
Christian Fibich's avatar
Christian Fibich committed
170
    foreach my $k (keys(%{$design_ref})) {
171
        my $ini_name = DESIGNMAP->{$k}->{'ini_name'};
Christian Fibich's avatar
Christian Fibich committed
172
        if (!defined($design_ref->{$k})) {
173
174
175
176
177
178
            $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};
179

180
        # Convert value to external representation
Christian Fibich's avatar
Christian Fibich committed
181
182
        _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"),);
183
    }
Christian Fibich's avatar
Christian Fibich committed
184
    $fiji_ini->set_block("CONSTS", $ini_design);
185

Christian Fibich's avatar
Christian Fibich committed
186
    if (!defined($fiji_ini->write($fiji_ini_file))) {
Christian Fibich's avatar
Christian Fibich committed
187
        my $err = FIJI::ConfigSorted->error();
188
189
190
        $logger->error($err);
        return $err;
    }
191
    $self->{'filename'} = $fiji_ini_file;
192
193
    return undef;
}
194

195
## @function public read_settingsfile ($phase, $fiji_ini_file)
196
# @brief Load the FIJI Settings file containing design and FIU constants.
197
#
198
# @param phase  Tool flow phase the settings stored in the given file
Stefan Tauner's avatar
Stefan Tauner committed
199
#               need to be compatible with.
200
# @param fiji_ini_file The name of an .ini file with FIJI Settings:
201
#         - a 'consts' block containing the constants specified by
Stefan Tauner's avatar
Stefan Tauner committed
202
#           \ref _sanitize_design.
203
204
205
#         - 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
206
#
207
208
# @returns a reference to the hash containing the read constants (or undef in the case of an error)
#          and a diagnostic string describing the reason why it could not be created (undef if successful)
209
sub read_settingsfile ($$$) {
210
    my $logger = get_logger("");
Christian Fibich's avatar
Christian Fibich committed
211
    my ($phase, $fiji_ini_file, $existing_settings) = @_;
212
    my $fiji_ini;
Christian Fibich's avatar
Christian Fibich committed
213
    eval { $fiji_ini = new FIJI::ConfigSorted($fiji_ini_file) };    # pesky library tries to die on syntax errors
Christian Fibich's avatar
Christian Fibich committed
214
    if (!defined($fiji_ini)) {
Christian Fibich's avatar
Christian Fibich committed
215
        my $submsg = defined($@) ? $@ : FIJI::ConfigSorted->error();
Christian Fibich's avatar
Christian Fibich committed
216
        if (length($submsg) == 0) {
217
218
219
220
            $submsg = "Empty file?";
        }
        my $msg = "Could not read config file \"$fiji_ini_file\": $submsg";
        $logger->error($msg);
221
        return (undef, $msg);
222
223
224
    }

    my $design_ref = $fiji_ini->get_block("CONSTS");
Christian Fibich's avatar
Christian Fibich committed
225
    if (!(%$design_ref)) {
226
227
        my $msg = "Could not fetch CONSTS block from config file \"$fiji_ini_file\"";
        $logger->error($msg);
228
        return (undef, $msg);
229
    }
Christian Fibich's avatar
Christian Fibich committed
230
    _set_defaults(DESIGNMAP, $design_ref, $phase);
231

Christian Fibich's avatar
Christian Fibich committed
232
233
    $design_ref = _rename_import(DESIGNMAP, $design_ref);
    if (!defined($design_ref)) {
234
235
        my $msg = "Design constants do not match the FIJI Settings naming scheme.";
        $logger->error($msg);
236
        return (undef, $msg);
237
238
239
240
    }

    # Create a new instance or reuse the shallow hull of the existing one
    my $fiji_settings_ref;
Christian Fibich's avatar
Christian Fibich committed
241
    if (!defined($existing_settings)) {
242
        $fiji_settings_ref = {};
Christian Fibich's avatar
Christian Fibich committed
243
        bless($fiji_settings_ref, "FIJI::Settings");
Stefan Tauner's avatar
Stefan Tauner committed
244
    } else {
245
246
247
        $fiji_settings_ref = $existing_settings;

        # Clear the hash
Christian Fibich's avatar
Christian Fibich committed
248
        for (keys %$fiji_settings_ref) {
249
250
251
            delete $fiji_settings_ref->{$_};
        }
    }
Christian Fibich's avatar
Christian Fibich committed
252
    if (!blessed($fiji_settings_ref) || !$fiji_settings_ref->isa("FIJI::Settings")) {
253
        my $msg;
Christian Fibich's avatar
Christian Fibich committed
254
        if (!defined($existing_settings)) {
255
256
257
258
259
            $msg = "Could not create FIJI::Settings instance.";
        } else {
            $msg = "Given settings are not of type FIJI::Settings.";
        }
        $logger->error($msg);
260
        return (undef, $msg);
261
262
263
264
265
    }
    $fiji_settings_ref->{'design'} = $design_ref;
    $fiji_settings_ref->{'fius'}   = [];

    # sanitize and validate read design constants
Christian Fibich's avatar
Christian Fibich committed
266
267
    $design_ref = _sanitize_design($design_ref, $phase);
    if (!ref($design_ref)) {
268
        $logger->error($design_ref);
269
        return (undef, $design_ref);
270
271
272
    }

    my $fiu_num = 0;
273
274
    my $dup = {};
    my $warn;
275
276
    # loop over all read fius
    while (1) {
277

278
279
        my $fiu_name = "FIU" . $fiu_num;
        my $fiu_ref  = $fiji_ini->get_block($fiu_name);
Christian Fibich's avatar
Christian Fibich committed
280
        if (!(%$fiu_ref)) {
281
282
            last;
        }
Christian Fibich's avatar
Christian Fibich committed
283
284
        $fiu_ref = _rename_import(FIUMAP, $fiu_ref);
        if (!defined($design_ref)) {
285
286
            my $msg = "FIU constants of $fiu_name do not match the FIJI Settings naming scheme.";
            $logger->error($msg);
287
            return (undef, $msg);
288
289
290
        }

        my $tmp_fiu = {};
Christian Fibich's avatar
Christian Fibich committed
291
        _set_defaults(FIUMAP, $tmp_fiu, $phase);
292
293

        # overwrite default entries
Christian Fibich's avatar
Christian Fibich committed
294
        foreach my $k (keys(%{$fiu_ref})) {
295
296
297
298
            $tmp_fiu->{$k} = $fiu_ref->{$k};
        }
        $fiu_ref = $tmp_fiu;

Christian Fibich's avatar
Christian Fibich committed
299
300
        $fiu_ref = _sanitize_fiu($fiu_ref, $phase);
        if (!ref($fiu_ref)) {
301
302
            my $msg = "(Some) constants for $fiu_name in FIJI Settings are invalid.";
            $logger->error($msg);
303
            return (undef, $msg);
304
        }
305
306
307
308
309
310
311
312
313

        if (defined $dup->{$fiu_ref->{'FIU_NET_NAME'}}) {
            my $msg = "More than one FIU attached to ".$fiu_ref->{'FIU_NET_NAME'};
            $logger->error($msg);
            $warn .= $msg;
        } else {
            $dup->{$fiu_ref->{'FIU_NET_NAME'}} = 1;
        }

314
315
316
317
318
        $fiji_settings_ref->{'fius'}->[$fiu_num] = $fiu_ref;
        $fiu_num++;
        $logger->trace("Read in $fiu_name from FIJI Settings file successfully.");
    }

Christian Fibich's avatar
Christian Fibich committed
319
    if ($fiu_num == 0) {
320
321
322
323
324
        $logger->debug("Could not fetch any FIU blocks from config file \"$fiji_ini_file\"");
    }

    # FIU_NUM is optional in the Settings file... if it was set check that
    # it corresponds to the number of FIU<number> blocks.
Christian Fibich's avatar
Christian Fibich committed
325
    if (defined($design_ref->{'FIU_NUM'}) && $design_ref->{'FIU_NUM'} != $fiu_num) {
326
327
        my $msg = FIU_NUM->{'ini_name'} . " does not match the numbers of FIU blocks found.";
        $logger->error($msg);
328
        return (undef, $msg);
329
330
331
332
    } else {
        $design_ref->{'FIU_NUM'} = $fiu_num;    # assume the best if FIU_NUM constant is not given
    }

Christian Fibich's avatar
Christian Fibich committed
333
    splice(@{$fiji_settings_ref->{'fius'}}, $fiu_num);
334
    $logger->info("Successfully read in design constants and $fiu_num FIU definitions from FIJI Settings file \"$fiji_ini_file\".");
335
    return ($fiji_settings_ref, $warn);
336
337
}

338
## @method public set_fiu_defaults (%$fiu_ref)
339
340
341
# @brief Overwrite existing fields (if defined) with defaults defined in FIJI.pm.
#
sub set_fiu_defaults ($) {
Christian Fibich's avatar
Christian Fibich committed
342
343
    my ($self, $consts_ref) = @_;
    return _set_defaults(FIUMAP, $consts_ref);
Stefan Tauner's avatar
Stefan Tauner committed
344
345
}

346
347
348
349
350
351
352
353
354
## @method public default_timer_value()
# @brief Return sensible default timer values for these settings
sub default_timer_value {
    my ($self) = @_;
    # default to 10 ms at default frequency
    return (10e-3 * $self->{'design'}->{'FREQUENCY'});
}


Stefan Tauner's avatar
Stefan Tauner committed
355
356
357
## @function _set_defaults (%$map_ref, %$consts_ref)
# @brief Set defaults according to FIJI.pm.
sub _set_defaults {
358
    my $logger = get_logger("");
Christian Fibich's avatar
Christian Fibich committed
359
360
361
    my ($map_ref, $consts_ref, $phase) = @_;
    foreach my $k (keys(%{$map_ref})) {
        if (exists($map_ref->{$k}->{'default'})) {
362

363
            # Set default only if it is not mandatory in the given phase.
Christian Fibich's avatar
Christian Fibich committed
364
            if (defined($phase) && scalar(grep { $_ eq $phase } @{$map_ref->{$k}->{'phases_opt'}}) == 0) {
365
366
367
368
                next;
            }

            # Also, do not overwrite existing values
Christian Fibich's avatar
Christian Fibich committed
369
            if (defined($consts_ref->{$k})) {
370
371
372
                next;
            }
            $consts_ref->{$k} = $map_ref->{$k}->{default};
Christian Fibich's avatar
Christian Fibich committed
373
            $logger->trace(sprintf("Adding default constant: %s (%s) = %s.", $k, $map_ref->{$k}->{'ini_name'}, $map_ref->{$k}->{default}));
374
375
376
        }
    }
}
377

Stefan Tauner's avatar
Stefan Tauner committed
378
sub validate_design_value {
Christian Fibich's avatar
Christian Fibich committed
379
380
    my ($k, $v_ref) = @_;
    return validate_value(DESIGNMAP, $k, $v_ref);
Stefan Tauner's avatar
Stefan Tauner committed
381
382
383
}

sub validate_fiu_value {
Christian Fibich's avatar
Christian Fibich committed
384
385
    my ($k, $v_ref) = @_;
    return validate_value(FIUMAP, $k, $v_ref);
Stefan Tauner's avatar
Stefan Tauner committed
386
387
}

388
389
390
391
392
393
## @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)
394
#   \param dep_ref    scalar reference to a value the proposed value depends on
395
396
397
#   \param old      (optional) previously valid value
#   \param log_func (optional) the (log4perl) log function to use
#                   (defaul is \&Log::Log4perl::Logger::trace)
398
sub validate_value {
399
    my $logger = get_logger("");
400
    my ($map_ref, $k, $v_ref, $dep_ref, $old, $log_func) = @_;
401
    $log_func = \&Log::Log4perl::Logger::trace if !defined($log_func);
402
403
404
405
406
407
408
409
410
411
412

    if (!defined (${$v_ref})) {
        # if a key does not contain a value, e.g.
        # DRIVER_TYPE=
        # set the corresponding value to an empty string instead of undef
        # FIXME: OK or do we need the undef value later on?
        my $orig = ${$v_ref};
        $log_func->($logger, "$k is undef, setting to empty string.");
        ${$v_ref} = ""
    }

Christian Fibich's avatar
Christian Fibich committed
413
    if (defined($map_ref->{$k}->{'type'})) {
414
415
        my $orig = ${$v_ref};

Christian Fibich's avatar
Christian Fibich committed
416
        # @FIXME: check if the constant is depending on another constant, that needs to be enabled.
417
418
419
420
        # my $dependency = @{$map_ref->{$k}->{'depends_on'}};
        # if (defined($dependency) && defined(@{$map_ref->{$dependency}->{'value'}}
        # }

Christian Fibich's avatar
Christian Fibich committed
421
422
        if ($map_ref->{$k}->{'type'} eq 'net') {
            # @TODO: check syntax? there is nothing more to do in here unless we supply allowed nets...
423
            # which is actually possible because the input netlist is stored in the settings as well
Christian Fibich's avatar
Christian Fibich committed
424
        } elsif ($map_ref->{$k}->{'type'} eq 'hexadecimal' || $map_ref->{$k}->{'type'} eq 'lfsrpoly') {
Christian Fibich's avatar
Christian Fibich committed
425
426
            if ($orig !~ /^(0|(0[xX][[:xdigit:]]+))$/) {
                $log_func->($logger, "$k: $orig does not look like a natural hexadecimal number.");
427
428
429
430
431
                return 0;
            }
            ${$v_ref} = hex($orig);

            # Check for natural value range. Should never trigger due to the regex above.
Christian Fibich's avatar
Christian Fibich committed
432
433
            if (${$v_ref} < 0) {
                $log_func->($logger, "$k: $orig is negative.");
434
435
                return 0;
            }
Christian Fibich's avatar
Christian Fibich committed
436
        } elsif ($map_ref->{$k}->{'type'} eq 'natural') {
437
438

            # Match hexadecimal, binary, octal and decimal numbers (the latter also in scientific notation)
Christian Fibich's avatar
Christian Fibich committed
439
440
            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.");
441
442
443
444
445
                return 0;
            }

            # convert non-decimal (hexadecimal, binary, octal) values to decimal
            ${$v_ref} = oct($orig) if $orig =~ /^0/;
Christian Fibich's avatar
Christian Fibich committed
446
447
            if (!looks_like_number(${$v_ref})) {
                $log_func->($logger, "$k: $orig does not look like a number.");
448
449
                return 0;
            }
Stefan Tauner's avatar
Stefan Tauner committed
450

451
            # Check for natural value range. Should never trigger due to the regex above.
Christian Fibich's avatar
Christian Fibich committed
452
453
            if (${$v_ref} < 0) {
                $log_func->($logger, "$k: $orig is negative.");
454
455
                return 0;
            }
Christian Fibich's avatar
Christian Fibich committed
456
        } elsif ($map_ref->{$k}->{'type'} eq 'boolean') {
457
458

            # convert strings to binary if need be
Christian Fibich's avatar
Christian Fibich committed
459
460
            if (!defined($orig)) {
                $log_func->($logger, "$k: \"undef\" is not a boolean value.");
461
                return 0;
Christian Fibich's avatar
Christian Fibich committed
462
            } elsif (lc($orig) eq 'true') {
463
                $orig = 1;
Christian Fibich's avatar
Christian Fibich committed
464
            } elsif (lc($orig) eq 'false') {
465
466
                $orig = 0;
            }
Christian Fibich's avatar
Christian Fibich committed
467
468
            if (($orig ne '0') && ($orig ne '1')) {
                $log_func->($logger, "$k: \"$orig\" does not look like a boolean value.");
469
470
471
472
                return 0;
            }

            # ensure proper boolean value, i.e. 0 or 1
Christian Fibich's avatar
Christian Fibich committed
473
            ${$v_ref} = (!!$orig) ? 1 : 0;
474
        }
Christian Fibich's avatar
Christian Fibich committed
475
        $logger->trace("Converted value of $k (\"$orig\") to \"${$v_ref}\".") if (defined($orig) && $orig ne ${$v_ref});
476
477
    }

Christian Fibich's avatar
Christian Fibich committed
478
    if (defined($map_ref->{$k}->{'values'})) {
479
        my $values_ref = $map_ref->{$k}->{'values'};
480
481
482
483
484
        my $dep_val;
        if (ref $dep_ref eq 'SCALAR') {
            $dep_val = $dep_ref;
        }

Christian Fibich's avatar
Christian Fibich committed
485
        if (ref($values_ref) eq 'ARRAY') {
486
487

            # Look for given value in allowed values
Christian Fibich's avatar
Christian Fibich committed
488
489
            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'}}));
490
491
                return 0;
            }
Christian Fibich's avatar
Christian Fibich committed
492
        } elsif (ref($values_ref) eq 'CODE') {
493
            if (!$values_ref->(${$v_ref}, $old, $$dep_val)) {
Christian Fibich's avatar
Christian Fibich committed
494
                $log_func->($logger, "$k: ${$v_ref} is not allowed.");
495
496
497
498
499
500
                return 0;
            }
        }
    }
    return 1;
}
Stefan Tauner's avatar
Stefan Tauner committed
501
502
503
504

# @function _rename_import (%$map_ref, %$consts_ref)
# Rename and convert entries in consts_ref according to map_ref.
#
505
506
507
508
# This function takes a hash of FIJI Settings and converts its keys
# to the respective internal representation. This allows us to use different
# name representations in the external file than within the implementation.
#
Stefan Tauner's avatar
Stefan Tauner committed
509
510
511
#
# \returns $consts_ref, or undef on errors
sub _rename_import {
512
    my $logger = get_logger("");
Christian Fibich's avatar
Christian Fibich committed
513
514
    my ($map_ref, $consts_ref) = @_;
    if (ref($consts_ref) ne 'HASH') {
515
516
517
        $logger->error("Parameter is not a reference to a hash (containing design constants).");
        return undef;
    }
518

519
520
    # Iterating over respective hash from FIJI.pm and rename the entries
    # to match our internal naming scheme.
Christian Fibich's avatar
Christian Fibich committed
521
    foreach my $k (keys(%{$map_ref})) {
522
        my $ini_name = $map_ref->{$k}->{'ini_name'};
Christian Fibich's avatar
Christian Fibich committed
523
        if (exists($consts_ref->{$ini_name}) && $ini_name ne $k) {
524
            $consts_ref->{$k} = $consts_ref->{$ini_name};
Christian Fibich's avatar
Christian Fibich committed
525
            $logger->trace(sprintf("Renaming Design setting %s -> %s (=%s).", $ini_name, $k, defined($consts_ref->{$ini_name}) ? $consts_ref->{$ini_name} : "undef"),);
526
527
528
            delete $consts_ref->{$ini_name};
        }
    }
Christian Fibich's avatar
Christian Fibich committed
529
530
    foreach my $entry_key (keys(%{$consts_ref})) {
        if (!exists($map_ref->{$entry_key})) {
531
            $logger->debug(sprintf("Deleting unknown setting %s = %s.", $entry_key, $consts_ref->{$entry_key}));
532
533
534
535
536
537
            delete $consts_ref->{$entry_key};
        }
    }

    return $consts_ref;
}
Stefan Tauner's avatar
Stefan Tauner committed
538

539
## @function _sanitize_fiu (%$fiu_ref, $phase)
540
541
# @brief Convert and sanity check FIJI Settings.
#
542
543
# \param fiu_ref a reference to a hash containing FIJI Settings for a
#                single FIU.
Christian Fibich's avatar
Christian Fibich committed
544
# \param phase   the current design phase
545
546
547
#
# \returns A new hash with all constants required in the FIU settings
#          in sanitized form, or undef on errors.
548
sub _sanitize_fiu {
549
    my $logger = get_logger("");
Christian Fibich's avatar
Christian Fibich committed
550
551
    my ($fiu_ref, $phase) = @_;
    if (ref($fiu_ref) ne 'HASH') {
552
553
554
555
        my $msg = "Parameter is not a reference to a hash (containing FIU constants).";
        $logger->error($msg);
        return $msg;
    }
556

Christian Fibich's avatar
Christian Fibich committed
557
558
    $fiu_ref = _validate_hashmap(FIUMAP, $fiu_ref, $phase);
    if (!ref($fiu_ref)) {
559
560
561
        $logger->error("Could not validate Design Constants.");
        return $fiu_ref;
    }
Stefan Tauner's avatar
Stefan Tauner committed
562

563
    return $fiu_ref;
564
565
}

566
sub _disabled_via_dependency {
Christian Fibich's avatar
Christian Fibich committed
567
    my ($map_ref, $consts_ref, $k) = @_;
568
569
570
    my $dependency = $map_ref->{$k}->{'depends_on'};
    return defined($dependency) && !$consts_ref->{$dependency};
}
571

Christian Fibich's avatar
Christian Fibich committed
572
sub _validate_hashmap {
573
    my $logger = get_logger("");
Christian Fibich's avatar
Christian Fibich committed
574
575
576
    my ($map_ref, $consts_ref, $phase) = @_;
    my @map_keys = keys(%{$map_ref});
    foreach my $entry_key (keys(%{$consts_ref})) {
577
578
579
        my $forbidden_by = $map_ref->{$entry_key}->{'forbidden_by'};
        my $ini_name     = $map_ref->{$entry_key}->{'ini_name'};

Christian Fibich's avatar
Christian Fibich committed
580
        if (!exists($map_ref->{$entry_key})) {
581
            $logger->debug(sprintf("Deleting unknown setting %s = %s.", $entry_key, $consts_ref->{$entry_key}));
582
583
            next;
        }
Stefan Tauner's avatar
Stefan Tauner committed
584

585
        @map_keys = grep { $_ ne $entry_key } @map_keys;    # mark constant key as done
Christian Fibich's avatar
Christian Fibich committed
586
587
588
589
        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})) {
            my $msg = sprintf("%s = %s is invalid.", $entry_key, !defined($consts_ref->{$entry_key}) ? "<undef>" : $consts_ref->{$entry_key});
590
591
592
593
            $logger->error($msg);
            return $msg;
        }

Christian Fibich's avatar
Christian Fibich committed
594
        if (defined($forbidden_by) && $consts_ref->{$entry_key} && $consts_ref->{$forbidden_by}) {
595
596
597
598
            my $msg = "$entry_key is forbidden when $forbidden_by is enabled";
            $logger->error($msg);
            return $msg;
        }
Stefan Tauner's avatar
Stefan Tauner committed
599
    }
600

Christian Fibich's avatar
Christian Fibich committed
601
    if (!defined($phase)) {
602

603
604
605
606
607
        # 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;
    }
608

609
610
611
612
613
    # Iterate over the constants defined in FIJI.pm that apparently are
    # not contained in $consts_ref.
    foreach my $k (@map_keys) {
        my $dependency = $map_ref->{$k}->{'depends_on'};
        if (
Christian Fibich's avatar
Christian Fibich committed
614
615
616
            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
           )
617
618
619
620
621
622
623
        {
            my $msg = "$k is mandatory in phase $phase.";
            $logger->error($msg);
            return $msg;
        }

    }
Stefan Tauner's avatar
Stefan Tauner committed
624

Christian Fibich's avatar
Christian Fibich committed
625
    # @TODO: implement the same mechanism with forbidden_by
626
627
628

    return $consts_ref;
}
Stefan Tauner's avatar
Stefan Tauner committed
629

630
## @function _sanitize_design (%$consts_ref, $phase)
Stefan Tauner's avatar
Stefan Tauner committed
631
# @brief Sanity check FIJI Design Settings.
632
#
Stefan Tauner's avatar
Stefan Tauner committed
633
# The function deals with sanity checks for the values
634
# themselves. It checks for the following conditions:
635
636
637
#
#   - FIU_NUM: > 0
#   - FIU_CFG_BITS: > 0
638
#   - TIMER_WIDTH: > 0, <= 16
639
640
641
#   - ID: > 0, < 2^15-1
#   - BAUDRATE: > 0
#
642
# \param consts_ref a reference to a hash containing some design settings
Stefan Tauner's avatar
Stefan Tauner committed
643
# \param phase the tool flow phase that defines the rules to check against
644
#
Stefan Tauner's avatar
Stefan Tauner committed
645
# \returns The given hash with all constants required in the design
646
#          settings in sanitized form, or an error message.
Stefan Tauner's avatar
Stefan Tauner committed
647
sub _sanitize_design {
648
    my $logger = get_logger("");
Christian Fibich's avatar
Christian Fibich committed
649
650
    my ($consts_ref, $phase) = @_;
    if (ref($consts_ref) ne 'HASH') {
651
        return "Parameter is not a reference to a hash (containing design constants).";
652
653
654
    }

    # check for sane values
Christian Fibich's avatar
Christian Fibich committed
655
656
    $consts_ref = _validate_hashmap(DESIGNMAP, $consts_ref, $phase);
    if (!ref($consts_ref)) {
657
        return "Could not validate Design Constants." . (defined($consts_ref) ? " $consts_ref" : "");
658
659
    }

Christian Fibich's avatar
Christian Fibich committed
660
    if (($consts_ref->{'FIU_CFG_BITS'} <= 0)) {
661
        return "FIU_CFG_BITS is <= 0.";
662
    }
Christian Fibich's avatar
Christian Fibich committed
663
    if (($consts_ref->{'TIMER_WIDTH'} <= 0) || ($consts_ref->{'TIMER_WIDTH'} > 16)) {
664
        return "TIMER_WIDTH is invalid ($consts_ref->{'TIMER_WIDTH'}).";
665
    }
Christian Fibich's avatar
Christian Fibich committed
666
    if (defined($consts_ref->{'ID'}) && ($consts_ref->{ID} < 0 || $consts_ref->{ID} > (2**16 - 1))) {
667
        return sprintf("ID is invalid (0x%04X).", $consts_ref->{ID});
668
    }
Christian Fibich's avatar
Christian Fibich committed
669
    if (($consts_ref->{'BAUDRATE'} <= 0)) {
670
        return "BAUDRATE missing is <= 0.";
671
672
673
    }

    return $consts_ref;
674
675
}

676
677
sub _log2 {
    my $val = shift;
Christian Fibich's avatar
Christian Fibich committed
678
    return ($val > 0) ? (log($val) / log(2)) : 0;
679
680
}

681
## @function private _est_resources ($FREQUENCY, $BAUD, $TIMER_WIDTH, $RST_CYCLES, $LFSR_WIDTH, $FIU_NUM, $algo)
682
# @brief estimates the number of Registers and LUTs to implement FIJI logic with the given settings
Christian Fibich's avatar
Christian Fibich committed
683
#
684
685
686
687
688
# The function ant its parameters were determined by experiment & fitted by scipy.optimize.curve_fit
#
# @param FREQUENCY      the clock frequency the FIJI logic will run at in Hz
# @param BAUD           the baud rate for DUT <-> FIJI communication
# @param TIMER_WIDTH    the width of the injection timer (durations) in Bytes
689
# @param RST_CYCLES   the number of cycles to apply a FIJI-to-DUT reset
690
# @param LFSR_WIDTH     width of the LFSR used for stuck-open emulation
Christian Fibich's avatar
Christian Fibich committed
691
# @param FIU_NUM        the number of FIUs in the configuration
692
693
694
#
# @returns ($registers, $lutsum)

Christian Fibich's avatar
Christian Fibich committed
695
sub _est_resources {
696
    my $logger = get_logger("");
697
    my ($FREQUENCY, $BAUD, $TIMER_WIDTH, $RST_CYCLES, $LFSR_WIDTH, $FIU_NUM) = @_;
Christian Fibich's avatar
Christian Fibich committed
698

Christian Fibich's avatar
Christian Fibich committed
699
    # @FIXME where do we put these values? they are likely to change if the VHDL
700
701
    # source is changed...

Christian Fibich's avatar
Christian Fibich committed
702
703
    my $registers;
    my $lut6;
704
    my $out_of_range = [];
Christian Fibich's avatar
Christian Fibich committed
705

706
707
708
709
    if ($FREQUENCY < 1000000 || $FREQUENCY > 500000000) {
        $logger->debug("FREQUENCY $FREQUENCY out of range for correct resource estimation (1000000 - 500000000)");
        push @{$out_of_range}, "FREQUENCY";
    }
Christian Fibich's avatar
Christian Fibich committed
710
    if ($BAUD < 9600 || $BAUD > 3000000) {
711
712
713
        $logger->debug("BAUD $BAUD out of range for correct resource estimation (9600 - 3000000)");
        push @{$out_of_range}, "BAUD";
    }
Christian Fibich's avatar
Christian Fibich committed
714
    if ($TIMER_WIDTH < 1 || $TIMER_WIDTH > 8) {
715
716
717
        $logger->debug("TIMER_WIDTH $TIMER_WIDTH out of range for correct resource estimation (1 - 8)");
        push @{$out_of_range}, "TIMER_WIDTH";
    }
718
719
720
    if ($RST_CYCLES < 1 || $RST_CYCLES > 16) {
        $logger->debug("RST_CYCLES $RST_CYCLES out of range for correct resource estimation (1 - 16)");
        push @{$out_of_range}, "RST_CYCLES";
721
    }
Christian Fibich's avatar
Christian Fibich committed
722
    if ($LFSR_WIDTH < 16 || $LFSR_WIDTH > 64) {
723
        $logger->debug("LFSR_WIDTH $LFSR_WIDTH out of range for correct resource estimation (16 - 64)");
724
        push @{$out_of_range}, "RST_CYCLES";
725
    }
Christian Fibich's avatar
Christian Fibich committed
726
    if ($FIU_NUM < 1 || $FIU_NUM > 64) {
727
728
729
        $logger->debug("FIU_NUM $FIU_NUM out of range for correct resource estimation (1 - 64)");
        push @{$out_of_range}, "FIU_NUM";
    }
730

731
    $registers = 8769408455.04;
Christian Fibich's avatar
Christian Fibich committed
732
733
734
    $registers += (3.16525787254e-08) * ($FREQUENCY) +    (-8063276676.79) * (2**(-1.60957863771e-17 * $FREQUENCY));
    $registers += (0.000329011902135) * ($BAUD) +         (-705457194.107) * (2**(7.38032560255e-13 * $BAUD));
    $registers += (86.8258719199) *     ($TIMER_WIDTH) +  (-424084.540754) * (2**(0.000218517717305 * $TIMER_WIDTH));
735
    $registers += (5.02154556118) *     ($RST_CYCLES) + (51167.4447102) *  (2**(-0.000135580209031 * $RST_CYCLES));
Christian Fibich's avatar
Christian Fibich committed
736
737
    $registers += (-5.30893252274) *    ($LFSR_WIDTH) +   (-301970.077109) * (2**(-2.99010866996e-05 * $LFSR_WIDTH));
    $registers += (13.746880792) *      ($FIU_NUM) +      (437.305954808) *  (2**(-0.00583539664462 * $FIU_NUM));
738
739

    $lut6 = -1591399138.51;
Christian Fibich's avatar
Christian Fibich committed
740
741
742
    $lut6 += (5.65349933334e-06) * ($FREQUENCY) +    (-245548261.879) * (2**(3.26830064347e-14 * $FREQUENCY));
    $lut6 += (-0.0602788840594) *  ($BAUD) +         (1868596655.23) *  (2**(4.65257746085e-11 * $BAUD));
    $lut6 += (9.04974779295) *     ($TIMER_WIDTH) +  (-123738.805411) * (2**(7.77721022021e-07 * $TIMER_WIDTH));
743
    $lut6 += (0.156780025973) *    ($RST_CYCLES) + (-176988.018256) * (2**(1e-08 * $RST_CYCLES));
Christian Fibich's avatar
Christian Fibich committed
744
745
    $lut6 += (-1.88257966999) *    ($LFSR_WIDTH) +   (-31351130.663) *  (2**(-8.63995764468e-08 * $LFSR_WIDTH));
    $lut6 += (3.95693187928) *     ($FIU_NUM) +      (2718.76465806) *  (2**(-0.000895867386597 * $FIU_NUM));
746

747
    return ($registers, $lut6, $out_of_range);
Christian Fibich's avatar
Christian Fibich committed
748
749
}

750
## @method public estimate_resources (%$settings_ref)
751
# @brief estimates the resources needed to implement the given settings
Christian Fibich's avatar
Christian Fibich committed
752
#
753
754
# @returns a hash with the members 'regs' and 'lut_calc'
#
755
sub estimate_resources {
756
    my $logger         = get_logger("");
757
    my ($settings_ref) = @_;
758
759
760
    my $consts_ref     = $settings_ref->{'design'};
    my $fiu_ref        = $settings_ref->{'fius'};
    my $fiu_num        = @{$fiu_ref};
Christian Fibich's avatar
Christian Fibich committed
761

762
    my @calcrv = _est_resources($consts_ref->{'FREQUENCY'}, $consts_ref->{'BAUDRATE'}, $consts_ref->{'TIMER_WIDTH'}, $consts_ref->{'RST_DUT_IN_DUR'}, $consts_ref->{'LFSR_WIDTH'}, $fiu_num, "logarithmic");
Christian Fibich's avatar
Christian Fibich committed
763

764
    my $rv = {
Christian Fibich's avatar
Christian Fibich committed
765
766
        regs         => sprintf("%.2f", $calcrv[0] / $base_resources[0]),
        lut_calc     => sprintf("%.2f", $calcrv[1] / $base_resources[1]),
767
        out_of_range => $calcrv[2],
768
    };
Christian Fibich's avatar
Christian Fibich committed
769

770
    $logger->debug("ESTIMATE: current config will need Base*$rv->{'regs'} registers and Base*$rv->{'lut_calc'} combinational resources");
771
772
773
    return $rv;
}

774
1;