Settings.pm 38.6 KB
Newer Older
1
2
3
#-----------------------------------------------------------------------
# Fault InJection Instrumenter (FIJI)
# https://embsys.technikum-wien.at/projects/vecs/fiji
Christian Fibich's avatar
Christian Fibich committed
4
#
5
6
# Copyright (C) 2017 Christian Fibich <fibich@technikum-wien.at>
# Copyright (C) 2017 Stefan Tauner <tauner@technikum-wien.at>
Christian Fibich's avatar
Christian Fibich committed
7
#
8
9
# This module is free software; you can redistribute it and/or modify
# it under the same terms as Perl itself.
Christian Fibich's avatar
Christian Fibich committed
10
#
11
12
13
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Christian Fibich's avatar
Christian Fibich committed
14
#
15
16
# See the LICENSE file for more details.
#-----------------------------------------------------------------------
Christian Fibich's avatar
Christian Fibich committed
17

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

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

24
25
package FIJI::Settings;

26
27
28
use strict;
use warnings;

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

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

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

42
## @function public new ($phase, $fiji_ini_file, $existing_settings, $nl_ref)
Stefan Tauner's avatar
Stefan Tauner committed
43
44
# Create a new settings instance.
#
45
46
47
48
49
50
51
52
53
# @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 with values read from file.
#                          Any contained data will be cleared.
# @param nl_ref            A reference to a FIJI::netlist (for validation of nets and drivers only)
# @returns                 A list comprising a) The new settings instance (or undef in the case of an error),
#                          b) a diagnostic string describing the reason why it could not be created (undef if successful),
#                          c) a diagnostic string describing any anomalies of \c $fiji_ini_file that did not hinder creation.
sub new {
54
    my $logger = get_logger("");
55
    my ($class, $phase, $fiji_ini_file, $existing_settings, $nl_ref) = @_;
56
57
58
59

    my $fiji_settings_ref;

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

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

92
    @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");
93

94
    return ($fiji_settings_ref, $errors, $warnings);
95
}
Stefan Tauner's avatar
Stefan Tauner committed
96
97

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

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

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

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

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

Christian Fibich's avatar
Christian Fibich committed
145
                        foreach my $k (keys(%{$fiu})) {
146
                            my $ini_name = FIUMAP->{$k}->{'ini_name'};
Christian Fibich's avatar
Christian Fibich committed
147
                            if (!defined($fiu->{$k})) {
148
149
150
151
152
153
154
155
                                $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
156
157
                            _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"),);
158
                        }
Christian Fibich's avatar
Christian Fibich committed
159
                        $fiji_ini->set_block("FIU" . $fiu_cnt++, $ini_fiu);
160
161
162
                    }
                    next;
                }
163
            }
164
165
        } elsif ($key eq "filename") {
            next;
166
        }
167
168

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

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

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

199
## @function public read_settingsfile ($phase, $fiji_ini_file, $existing_settings, $nl_ref)
200
# @brief Load the FIJI Settings file containing design and FIU constants.
201
#
202
# @param phase  Tool flow phase the settings stored in the given file
Stefan Tauner's avatar
Stefan Tauner committed
203
#               need to be compatible with.
204
# @param fiji_ini_file The name of an .ini file with FIJI Settings:
205
#         - a 'consts' block containing the constants specified by
Stefan Tauner's avatar
Stefan Tauner committed
206
#           \ref _sanitize_design.
207
208
209
#         - 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
210
211
212
# @param existing_settings (optional) A reference to reuse and return with values read from file.
#                          Any contained data will be cleared.
# @param nl_ref A reference to a FIJI::netlist (for validation of nets and drivers only)
213
#
214
215
216
# @returns A list comprising a) a reference to the hash containing the read constants (or undef in the case of an error),
#          b) a diagnostic string describing the reason why it could not be created (undef if successful),
#          c) a diagnostic string describing any anomalies of \c $fiji_ini_file that did not hinder creation.
217
sub read_settingsfile ($$$) {
218
    my $logger = get_logger("");
219
    my ($phase, $fiji_ini_file, $existing_settings, $nl_ref) = @_;
220
    my $fiji_ini;
Christian Fibich's avatar
Christian Fibich committed
221
    eval { $fiji_ini = new FIJI::ConfigSorted($fiji_ini_file) };    # pesky library tries to die on syntax errors
Christian Fibich's avatar
Christian Fibich committed
222
    if (!defined($fiji_ini)) {
Christian Fibich's avatar
Christian Fibich committed
223
        my $submsg = defined($@) ? $@ : FIJI::ConfigSorted->error();
Christian Fibich's avatar
Christian Fibich committed
224
        if (length($submsg) == 0) {
225
226
227
228
            $submsg = "Empty file?";
        }
        my $msg = "Could not read config file \"$fiji_ini_file\": $submsg";
        $logger->error($msg);
229
        return (undef, $msg);
230
231
232
    }

    my $design_ref = $fiji_ini->get_block("CONSTS");
Christian Fibich's avatar
Christian Fibich committed
233
    if (!(%$design_ref)) {
234
235
        my $msg = "Could not fetch CONSTS block from config file \"$fiji_ini_file\"";
        $logger->error($msg);
236
        return (undef, $msg);
237
    }
Christian Fibich's avatar
Christian Fibich committed
238
    _set_defaults(DESIGNMAP, $design_ref, $phase);
239

Christian Fibich's avatar
Christian Fibich committed
240
241
    $design_ref = _rename_import(DESIGNMAP, $design_ref);
    if (!defined($design_ref)) {
242
243
        my $msg = "Design constants do not match the FIJI Settings naming scheme.";
        $logger->error($msg);
244
        return (undef, $msg);
245
246
247
248
    }

    # Create a new instance or reuse the shallow hull of the existing one
    my $fiji_settings_ref;
Christian Fibich's avatar
Christian Fibich committed
249
    if (!defined($existing_settings)) {
250
        $fiji_settings_ref = {};
Christian Fibich's avatar
Christian Fibich committed
251
        bless($fiji_settings_ref, "FIJI::Settings");
Stefan Tauner's avatar
Stefan Tauner committed
252
    } else {
253
254
255
        $fiji_settings_ref = $existing_settings;

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

    # sanitize and validate read design constants
274
275
276

    my ($errors, $warnings);
    ($design_ref, $errors, $warnings) = _sanitize_design($design_ref, $phase, $nl_ref);
Christian Fibich's avatar
Christian Fibich committed
277
    if (!ref($design_ref)) {
278
279
        $logger->error($errors);
        return (undef, $errors, $warnings);
280
281
282
    }

    my $fiu_num = 0;
283
284
    my $error;
    # Loop over all read FIUs
285
    while (1) {
286

287
288
        my $fiu_id = "FIU" . $fiu_num;
        my $fiu_ref  = $fiji_ini->get_block($fiu_id);
Christian Fibich's avatar
Christian Fibich committed
289
        if (!(%$fiu_ref)) {
290
291
            last;
        }
Christian Fibich's avatar
Christian Fibich committed
292
        $fiu_ref = _rename_import(FIUMAP, $fiu_ref);
293
294
        if (!defined($fiu_ref)) {
            my $msg = "FIU constants of $fiu_id do not match the FIJI Settings naming scheme.";
295
            $logger->error($msg);
296
            return (undef, $msg);
297
298
299
        }

        my $tmp_fiu = {};
300
        _set_defaults(FIUMAP, $tmp_fiu, $phase, $design_ref, $fiu_num);
301
302

        # overwrite default entries
Christian Fibich's avatar
Christian Fibich committed
303
        foreach my $k (keys(%{$fiu_ref})) {
304
305
306
307
            $tmp_fiu->{$k} = $fiu_ref->{$k};
        }
        $fiu_ref = $tmp_fiu;

308
309
        my ($fiu_errors, $fiu_warnings);
        ($fiu_ref, $fiu_errors, $fiu_warnings) = _sanitize_fiu($fiu_ref, $phase, $nl_ref);
Christian Fibich's avatar
Christian Fibich committed
310
        if (!ref($fiu_ref)) {
311
312
313
            my $msg = "";
            $msg .= "$errors\n" if defined($errors);
            $msg .= "Fatal problems sanitizing $fiu_id:\n$fiu_errors";
314
            $logger->error($msg);
315
316
317
318
319
320
321
            return (undef, $msg, $warnings);
        }
        my $name_str = $fiu_id;
        $name_str .= " (".$fiu_ref->{'FIU_NAME'} . ")" if (defined($fiu_ref->{'FIU_NAME'}) && $fiu_ref->{'FIU_NAME'} ne "");
        if (defined($fiu_errors)) {
            $errors = "" if !defined($errors);
            $errors .= "$name_str:\n$fiu_errors";
322
        }
323

324
325
326
        if (defined($fiu_warnings)) {
            $warnings = "" if !defined($warnings);
            $warnings .= "$name_str:\n$fiu_warnings";
327
328
        }

329
330
        $fiji_settings_ref->{'fius'}->[$fiu_num] = $fiu_ref;
        $fiu_num++;
331
        $logger->trace("Read in $fiu_id from FIJI Settings file successfully.");
332
333
    }

Christian Fibich's avatar
Christian Fibich committed
334
    if ($fiu_num == 0) {
335
        my $msg = "No FIU blocks in config file. Synthesis would fail.";
336
        $logger->warn($msg);
337
338
339
340
341
342
343
        if ($phase eq 'instrument') {
            $errors = "" if !defined($errors);
            $errors .= "$msg\n";
        } else {
            $warnings = "" if !defined($warnings);
            $warnings .= "$msg\n";
        }
344
345
346
347
    }

    # 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
348
    if (defined($design_ref->{'FIU_NUM'}) && $design_ref->{'FIU_NUM'} != $fiu_num) {
349
350
351
352
        my $msg = FIU_NUM->{'ini_name'} . " does not match the numbers of FIU blocks found (and loaded).";
        $logger->warn($msg);
        $warnings = defined($warnings) ? "$warnings\n" : "";
        $warnings .= "$msg\n";
353
354
355
356
    } else {
        $design_ref->{'FIU_NUM'} = $fiu_num;    # assume the best if FIU_NUM constant is not given
    }

357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
    my $dups = $fiji_settings_ref->determine_duplicate_fiu_nets();
    my @dup_nets = keys(%{$dups});
    if (scalar(@dup_nets) > 0) {
        my $msg = "More than one FIU is attached to the following net(s):\n";
        for my $dup (@dup_nets) {
            $msg .= "$dup is attached to FIUs " . join(", ", @{$dups->{$dup}}) . "\n";
        }
        chomp($msg);
        if ($phase eq 'instrument') {
            $logger->error($msg);
            if (defined($errors)) {
                $errors .= "\n$msg\n";
            } else {
                $errors = "$msg\n";
            }
        } else {
            $logger->warn($msg);
            if (defined($warnings)) {
                $warnings .= "\n$msg\n";
            } else {
                $warnings = "$msg\n";
            }
        }
    }

    $logger->info("Successfully read in design constants and $fiu_num FIU definitions from FIJI Settings file \"$fiji_ini_file\".") if !defined($errors);
    return (!defined($errors) ? $fiji_settings_ref : undef, $errors, $warnings);
}

## @method determine_duplicate_fiu_nets (@$fius_ref)
# @brief Test for multiple instrumentations of a single net
#
# @param fius_ref   arrayref to a list of FIU hashes
# @param netname    (optional) Look only for a specific net.
#
# @returns          a (possible empty) hash keys by nets that are referred to multiple times.
#                   The values are arrays of FIU indices that refer to the key's net.
sub determine_duplicate_fiu_nets {
    my ($self, $target_net) = @_;

    my $fius_ref = $self->{'fius'};
    my $seen = {};
    my $dup_nets = {};
    my $i = 0;
    foreach my $fiu_ref (@{$fius_ref}) {
        my $netname = $fiu_ref->{'FIU_NET_NAME'};
        if (defined($netname) && length($netname) > 0) {
            if (!defined($target_net) || ($target_net eq $netname)) {
                if (defined $seen->{$netname}) {
                    if (!defined($dup_nets->{$netname})) {
                        $dup_nets->{$netname} = [$seen->{$netname}];
                    }
                    push(@{$dup_nets->{$netname}}, $i);
                } else {
                    $seen->{$netname} = $i;
                }
            }
        }
        $i += 1;
    }
    return $dup_nets;
418
419
}

420
## @method public set_fiu_defaults (%$fiu_ref)
421
422
423
# @brief Overwrite existing fields (if defined) with defaults defined in FIJI.pm.
#
sub set_fiu_defaults ($) {
424
425
    my ($self, $consts_ref, $phase, $fiu_num) = @_;
    return _set_defaults(FIUMAP, $consts_ref, $phase, $self->{'design'}, $fiu_num);
Stefan Tauner's avatar
Stefan Tauner committed
426
427
}

428
429
430
431
432
433
434
435
## @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
436
437
438
## @function _set_defaults (%$map_ref, %$consts_ref)
# @brief Set defaults according to FIJI.pm.
sub _set_defaults {
439
    my $logger = get_logger("");
440
    my ($map_ref, $consts_ref, $phase, $design_ref, $fiu_num) = @_;
Christian Fibich's avatar
Christian Fibich committed
441
442
    foreach my $k (keys(%{$map_ref})) {
        if (exists($map_ref->{$k}->{'default'})) {
443

444
            # Set default only if it is not mandatory in the given phase.
Christian Fibich's avatar
Christian Fibich committed
445
            if (defined($phase) && scalar(grep { $_ eq $phase } @{$map_ref->{$k}->{'phases_opt'}}) == 0) {
446
447
448
449
                next;
            }

            # Also, do not overwrite existing values
Christian Fibich's avatar
Christian Fibich committed
450
            if (defined($consts_ref->{$k})) {
451
452
                next;
            }
453
454
455
456
457
458
            my $default = $map_ref->{$k}->{default};
            if (ref($default) eq 'CODE') {
                $consts_ref->{$k} = $default->($consts_ref, $phase, $design_ref, $fiu_num);
            } else {
                $consts_ref->{$k} = $default;
            }
Christian Fibich's avatar
Christian Fibich committed
459
            $logger->trace(sprintf("Adding default constant: %s (%s) = %s.", $k, $map_ref->{$k}->{'ini_name'}, $map_ref->{$k}->{default}));
460
461
462
        }
    }
}
463

464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
# @method _validate_port_entry
# Checks if a port name is valid.
#
# @param map_ref    reference to FIJI Settings mappings
# @param nl_ref     reference to a FIJI::netlist (for validation of nets, drivers etc. only)
# @param k          key identifying the respective setting
# @param new        the proposed value
# @param consts_ref reference to related settings hash
sub _validate_port_entry {
    my $logger = get_logger("");
    my ($map_ref, $nl_ref, $k, $new, $consts_ref) = @_;
    my $ok = 0;

    return "Netlist not available" if !defined($nl_ref);
    return "Port name is empty" if ($new eq "");

    # We have to avoid names of other FIJI entries
    # (e.g. RST_EXT_IN_NAME could conflict with TRIG_EXT_IN_NAME)...
    my @other_fiji_ports;
    foreach my $map_key (keys %{$map_ref}) {
        my $type = $map_ref->{$map_key}->{'type'};
        # Gather entries of type dut_port and external_port only
        next if !($type eq 'dut_port' || $type eq 'external_port');
        # Ignore disabled entries
        next if _disabled_via_dependency($map_ref, $consts_ref, $map_key);
        # Push all remaining entries excluding the one under validation
        push(@other_fiji_ports, $consts_ref->{$map_key}) if $map_key ne $k;
    }
    return "Port name $new is already used for another FIJI port" if _is_in_list(\@other_fiji_ports, $new);

    # Additionally, we need to check against the top level ports of the DUT that will get passed on to FIJI's top.
    my $dut_ports = $nl_ref->get_toplevel_port_names;
    # For external ports originating in FIJI itself they need to be neither used by the DUT nor other FIJI elements, thus
    #   ok = not in ext_ports and not in dut_ports
    if ($map_ref->{$k}->{'type'} eq 'external_port') {
        return "Port name $new already exists as DUT port" if _is_in_list($dut_ports, $new);

    # In the case of elements connecting to existing DUT ports (type dut_port),
    # the latter need to exist in the DUT but are not allowed to be used for other purposes of FIJI. Thus:
    #   ok = not in ext_ports and in dut_ports
    } elsif ($map_ref->{$k}->{'type'} eq 'dut_port') {
        return "Port name $new does not refer to a DUT port" if not _is_in_list($dut_ports, $new);
    } else {
        my $msg = sprintf("Internal error: unknown type %s (used in %s)", $map_ref->{$k}->{'type'}, $k);
        $logger->fatal($msg);
        return $msg;
    }
    return undef;
}

sub _is_in_list {
    my ($valid_list_ref, $new) = @_;
    my $logger = get_logger("");
    if (!ref($valid_list_ref)) {
        return 1;
    }
    my @complete_matches = ();
    my @prefix_matches   = ();

    # this is not implemented using grep because of
    # https://rt.cpan.org/Public/Bug/Display.html?id=112427
    for my $entry (@{$valid_list_ref}) {
        push (@complete_matches, $entry) if ($entry =~ m/^\Q$new\E$/);
        push (@prefix_matches,$entry) if ($entry =~ m/^\Q$new\E$/);
    }

    $logger->trace("'$new' matches " . scalar(@complete_matches) . " nets completely and is a prefix of " . scalar(@prefix_matches));
    return @complete_matches == 1;
}


535
## @function validate_value (%$map_ref, $nl_ref, $k, $$v_ref, $dep_ref, $consts_ref, $log_func)
536
537
#   Do validation (and conversation from external->internal representation)
#
538
539
540
541
542
543
544
545
546
547
#   @param map_ref  reference to FIJI Settings mappings
#   @param nl_ref   reference to a FIJI::netlist (for validation of nets, drivers etc. only)
#   @param k        key identifying the respective setting
#   @param v_ref    scalar reference to the proposed value (that may be modified)
#   @param dep_ref  scalar reference to a value the proposed value depends on
#   @param consts_ref (optional) reference to related settings hash
#   @param log_func (optional) the (log4perl) log function to use
#                   (default is \&Log::Log4perl::Logger::trace)
#
#   @returns undef if value is valid, or a string to explain why it is invalid.
548
sub validate_value {
549
    my $logger = get_logger("");
550
    my ($map_ref, $nl_ref, $k, $v_ref, $dep_ref, $consts_ref, $log_func) = @_;
551
    $log_func = \&Log::Log4perl::Logger::trace if !defined($log_func);
552
553
554
555
556

    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
557
        $logger->trace("$k is undef, setting to empty string");
558
559
560
        ${$v_ref} = ""
    }

Christian Fibich's avatar
Christian Fibich committed
561
    if (defined($map_ref->{$k}->{'type'})) {
562
563
        my $orig = ${$v_ref};

Christian Fibich's avatar
Christian Fibich committed
564
        if ($map_ref->{$k}->{'type'} eq 'net') {
565
566
567
568
569
570
            return "Netlist not available" if !defined($nl_ref);

            my $msg = $nl_ref->validate_net($orig);
            if (defined($msg)) {
                return $msg;
            }
571
572
573
574
575
        } elsif (($map_ref->{$k}->{'type'} eq 'external_port') || ($map_ref->{$k}->{'type'} eq 'dut_port')) {
            my $msg = _validate_port_entry($map_ref, $nl_ref, $k, $orig, $consts_ref);
            if (defined($msg)) {
                return $msg;
            }
576
577
578
579
580
581
582
583
584
585
586
        } elsif ($map_ref->{$k}->{'type'} eq 'driver') {
            return "Netlist not available" if !defined($nl_ref);

            my $net_path = $consts_ref->{'FIU_NET_NAME'};
            my $driver_path = $orig;
            my $driver_type = $consts_ref->{'FIU_DRIVER_TYPE'};
            
            my $msg = $nl_ref->validate_driver($net_path, $driver_path, $driver_type);
            if (defined($msg)) {
                return "$msg";
            }
Christian Fibich's avatar
Christian Fibich committed
587
        } elsif ($map_ref->{$k}->{'type'} eq 'hexadecimal' || $map_ref->{$k}->{'type'} eq 'lfsrpoly') {
Christian Fibich's avatar
Christian Fibich committed
588
            if ($orig !~ /^(0|(0[xX][[:xdigit:]]+))$/) {
589
                return "$orig does not look like a natural hexadecimal number";
590
591
592
593
            }
            ${$v_ref} = hex($orig);

            # Check for natural value range. Should never trigger due to the regex above.
Christian Fibich's avatar
Christian Fibich committed
594
            if (${$v_ref} < 0) {
595
                return "$orig is negative";
596
            }
Christian Fibich's avatar
Christian Fibich committed
597
        } elsif ($map_ref->{$k}->{'type'} eq 'natural') {
598
599

            # Match hexadecimal, binary, octal and decimal numbers (the latter also in scientific notation)
Christian Fibich's avatar
Christian Fibich committed
600
            if ($orig !~ /^(0|(0(x[0-9a-fA-F]+|[0-7]+))|(0b[01]+)|([1-9][0-9]*(e(-[0-9])?[0-9]+)?))$/) {
601
                return "$orig does not look like a number";
602
603
604
605
            }

            # convert non-decimal (hexadecimal, binary, octal) values to decimal
            ${$v_ref} = oct($orig) if $orig =~ /^0/;
Christian Fibich's avatar
Christian Fibich committed
606
            if (!looks_like_number(${$v_ref})) {
607
                return "$orig does not look like a number";
608
            }
Stefan Tauner's avatar
Stefan Tauner committed
609

610
            # Check for natural value range. Should never trigger due to the regex above.
Christian Fibich's avatar
Christian Fibich committed
611
            if (${$v_ref} < 0) {
612
                return "$orig is negative";
613
            }
Christian Fibich's avatar
Christian Fibich committed
614
        } elsif ($map_ref->{$k}->{'type'} eq 'boolean') {
615
616

            # convert strings to binary if need be
Christian Fibich's avatar
Christian Fibich committed
617
            if (!defined($orig)) {
618
                return "\"undef\" is not a boolean value";
Christian Fibich's avatar
Christian Fibich committed
619
            } elsif (lc($orig) eq 'true') {
620
                $orig = 1;
Christian Fibich's avatar
Christian Fibich committed
621
            } elsif (lc($orig) eq 'false') {
622
623
                $orig = 0;
            }
Christian Fibich's avatar
Christian Fibich committed
624
            if (($orig ne '0') && ($orig ne '1')) {
625
                return "\"$orig\" does not look like a boolean value";
626
627
628
            }

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

Christian Fibich's avatar
Christian Fibich committed
634
    if (defined($map_ref->{$k}->{'values'})) {
635
        my $values_ref = $map_ref->{$k}->{'values'};
Christian Fibich's avatar
Christian Fibich committed
636
        if (ref($values_ref) eq 'ARRAY') {
637
638

            # Look for given value in allowed values
Christian Fibich's avatar
Christian Fibich committed
639
            if (scalar(grep { $_ eq ${$v_ref} } @{$values_ref}) == 0) {
640
                return "${$v_ref} is not allowed. Allowed values are: " . join(", ", @{$map_ref->{$k}->{'values'}});
641
            }
Christian Fibich's avatar
Christian Fibich committed
642
        } elsif (ref($values_ref) eq 'CODE') {
643
            if (!$values_ref->(${$v_ref}, $consts_ref->{$k}, $map_ref, $consts_ref, $$dep_ref)) {
644
                return "${$v_ref} is not allowed";
645
646
647
            }
        }
    }
648
    return undef;
649
}
Stefan Tauner's avatar
Stefan Tauner committed
650
651
652
653

# @function _rename_import (%$map_ref, %$consts_ref)
# Rename and convert entries in consts_ref according to map_ref.
#
654
655
656
657
# 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
658
659
660
#
# \returns $consts_ref, or undef on errors
sub _rename_import {
661
    my $logger = get_logger("");
Christian Fibich's avatar
Christian Fibich committed
662
663
    my ($map_ref, $consts_ref) = @_;
    if (ref($consts_ref) ne 'HASH') {
664
665
666
        $logger->error("Parameter is not a reference to a hash (containing design constants).");
        return undef;
    }
667

668
669
    # Iterating over respective hash from FIJI.pm and rename the entries
    # to match our internal naming scheme.
Christian Fibich's avatar
Christian Fibich committed
670
    foreach my $k (keys(%{$map_ref})) {
671
        my $ini_name = $map_ref->{$k}->{'ini_name'};
Christian Fibich's avatar
Christian Fibich committed
672
        if (exists($consts_ref->{$ini_name}) && $ini_name ne $k) {
673
            $consts_ref->{$k} = $consts_ref->{$ini_name};
674
            $logger->trace(sprintf("Renaming Design setting %s -> %s (=%s).", $ini_name, $k, defined($consts_ref->{$ini_name}) ? $consts_ref->{$ini_name} : "<undef>"),);
675
676
677
            delete $consts_ref->{$ini_name};
        }
    }
Christian Fibich's avatar
Christian Fibich committed
678
679
    foreach my $entry_key (keys(%{$consts_ref})) {
        if (!exists($map_ref->{$entry_key})) {
680
            $logger->warn(sprintf("Deleting unknown setting %s = %s.", $entry_key, defined($consts_ref->{$entry_key}) ? $consts_ref->{$entry_key} : "<undef>"));
681
682
683
684
685
686
            delete $consts_ref->{$entry_key};
        }
    }

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

688
## @function _sanitize_fiu (%$fiu_ref, $phase)
689
690
# @brief Convert and sanity check FIJI Settings.
#
691
692
# \param fiu_ref a reference to a hash containing FIJI Settings for a
#                single FIU.
Christian Fibich's avatar
Christian Fibich committed
693
# \param phase   the current design phase
694
695
696
#
# \returns A new hash with all constants required in the FIU settings
#          in sanitized form, or undef on errors.
697
698
699
# @returns A list comprising a) the given hash with all constants required in the FIU settings in sanitized form (or undef in the case of an error),
#          b) a diagnostic string describing the reason why sanitation failed (undef if successful),
#          c) a diagnostic string describing any anomalies that did not hinder sanitation.
700
sub _sanitize_fiu {
701
    my $logger = get_logger("");
702
    my ($fiu_ref, $phase, $nl_ref) = @_;
Christian Fibich's avatar
Christian Fibich committed
703
    if (ref($fiu_ref) ne 'HASH') {
704
705
        my $msg = "Parameter is not a reference to a hash (containing FIU constants).";
        $logger->error($msg);
706
        return (undef, $msg);
707
    }
708

709
710
    my ($warnings, $errors);
    ($fiu_ref, $errors, $warnings) = _validate_hashmap(FIUMAP, $nl_ref, $fiu_ref, $phase);
Christian Fibich's avatar
Christian Fibich committed
711
    if (!ref($fiu_ref)) {
712
713
        my $msg = "Could not validate FIU Constants. $errors";
        return chomp $msg;
714
    }
Stefan Tauner's avatar
Stefan Tauner committed
715

716
    return ($fiu_ref, $errors, $warnings);
717
718
}

719
sub _disabled_via_dependency {
Christian Fibich's avatar
Christian Fibich committed
720
    my ($map_ref, $consts_ref, $k) = @_;
721
722
723
    my $dependency = $map_ref->{$k}->{'depends_on'};
    return defined($dependency) && !$consts_ref->{$dependency};
}
724

Christian Fibich's avatar
Christian Fibich committed
725
sub _validate_hashmap {
726
    my $logger = get_logger("");
727
    my ($map_ref, $nl_ref, $consts_ref, $phase) = @_;
Christian Fibich's avatar
Christian Fibich committed
728
    my @map_keys = keys(%{$map_ref});
729
730
    my $errors;
    my $warnings;
Christian Fibich's avatar
Christian Fibich committed
731
    foreach my $entry_key (keys(%{$consts_ref})) {
732
733
734
735
        my $forbidden_by = $map_ref->{$entry_key}->{'forbidden_by'};
        my $ini_name     = $map_ref->{$entry_key}->{'ini_name'};

        @map_keys = grep { $_ ne $entry_key } @map_keys;    # mark constant key as done
Christian Fibich's avatar
Christian Fibich committed
736
737
        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'}));
738
739
        } elsif (scalar(grep { $_ eq $phase } @{$map_ref->{$entry_key}->{'phases_moot'}}) != 0) {
            $logger->debug(sprintf("Key %s is irrelevant in phase %s. Skipping validation.", $entry_key, $phase));
740
741
742
        } else {
            my $rv = validate_value($map_ref, $nl_ref, $entry_key, \$consts_ref->{$entry_key}, undef, $consts_ref);
            if (defined($rv)) {
743
                my $msg = sprintf("%s = \"%s\" is invalid (%s)%s.", $entry_key, !defined($consts_ref->{$entry_key}) ? "<undef>" : $consts_ref->{$entry_key}, $rv, defined($phase) ? " in phase $phase" : "");
744
745
746
747
748
749
750
751
752
753
                if (scalar(grep { $_ eq $phase } @{$map_ref->{$entry_key}->{'phases_opt'}}) == 0) {
                    $logger->error($msg);
                    $errors = "" if !defined($errors);
                    $errors .= "$msg\n";
                } else {
                    $logger->warn($msg);
                    $warnings = "" if !defined($warnings);
                    $warnings .= "$msg\n";
                }
            }
754
755
        }

Christian Fibich's avatar
Christian Fibich committed
756
        if (defined($forbidden_by) && $consts_ref->{$entry_key} && $consts_ref->{$forbidden_by}) {
757
758
            my $msg = "$entry_key is forbidden when $forbidden_by is enabled";
            $logger->error($msg);
759
760
            $errors = "" if !defined($errors);
            $errors .= "$msg\n";
761
        }
Stefan Tauner's avatar
Stefan Tauner committed
762
    }
763

Christian Fibich's avatar
Christian Fibich committed
764
    if (!defined($phase)) {
765

766
767
768
        # 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.
769
        return ($consts_ref, $errors, $warnings);
770
    }
771

772
773
774
775
776
    # 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
777
778
779
            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
           )
780
781
782
        {
            my $msg = "$k is mandatory in phase $phase.";
            $logger->error($msg);
783
784
            $errors = "" if !defined($errors);
            $errors .= "$msg\n";
785
786
787
        }

    }
Stefan Tauner's avatar
Stefan Tauner committed
788

Christian Fibich's avatar
Christian Fibich committed
789
    # @TODO: implement the same mechanism with forbidden_by
790

791
    return ($consts_ref, $errors, $warnings);
792
}
Stefan Tauner's avatar
Stefan Tauner committed
793

794
## @function _sanitize_design (%$consts_ref, $phase, $nl_ref)
Stefan Tauner's avatar
Stefan Tauner committed
795
# @brief Sanity check FIJI Design Settings.
796
#
797
798
# The function deals with sanity checks for the values themselves as
# specified by FIJI.pm. Additionally, it checks for the following conditions:
799
800
#
#   - FIU_CFG_BITS: > 0
801
#   - TIMER_WIDTH: > 0, <= 16
802
803
804
#   - ID: > 0, < 2^15-1
#   - BAUDRATE: > 0
#
805
806
807
# @param consts_ref A reference to a hash containing some design settings
# @param phase      The tool flow phase that defines the rules to check against
# @param nl_ref     A reference to a FIJI::netlist (for validation of nets and drivers only)
808
#
809
810
811
# @returns A list comprising a) the given hash ref with all constants required in the design settings in sanitized form (or undef in the case of an error),
#          b) a diagnostic string describing the reason why it could not be created (undef if successful),
#          c) a diagnostic string describing any anomalies that did not hinder sanitation.
Stefan Tauner's avatar
Stefan Tauner committed
812
sub _sanitize_design {
813
    my $logger = get_logger("");
814
    my ($consts_ref, $phase, $nl_ref) = @_;
Christian Fibich's avatar
Christian Fibich committed
815
    if (ref($consts_ref) ne 'HASH') {
816
        return (undef, "Parameter is not a reference to a hash (containing design constants).");
817
818
819
    }

    # check for sane values
820
821
    my ($warnings, $errors);
    ($consts_ref, $errors, $warnings) = _validate_hashmap(DESIGNMAP, $nl_ref, $consts_ref, $phase);
Christian Fibich's avatar
Christian Fibich committed
822
    if (!ref($consts_ref)) {
823
824
        my $msg = "Could not validate Design Constants:\n$errors";
        return (undef, chomp $msg, $warnings);
825
826
    }

Christian Fibich's avatar
Christian Fibich committed
827
    if (($consts_ref->{'FIU_CFG_BITS'} <= 0)) {
828
829
        $errors = "" if !defined($errors);
        $errors .= "FIU_CFG_BITS is <= 0.\n";
830
    }
Christian Fibich's avatar
Christian Fibich committed
831
    if (($consts_ref->{'TIMER_WIDTH'} <= 0) || ($consts_ref->{'TIMER_WIDTH'} > 16)) {
832
833
        $errors = "" if !defined($errors);
        $errors .= "TIMER_WIDTH is invalid ($consts_ref->{'TIMER_WIDTH'}).\n";
834
    }
Christian Fibich's avatar
Christian Fibich committed
835
    if (defined($consts_ref->{'ID'}) && ($consts_ref->{ID} < 0 || $consts_ref->{ID} > (2**16 - 1))) {
836
837
        $errors = "" if !defined($errors);
        $errors .= sprintf("ID is invalid (0x%04X).\n", $consts_ref->{ID});
838
    }
Christian Fibich's avatar
Christian Fibich committed
839
    if (($consts_ref->{'BAUDRATE'} <= 0)) {
840
841
        $errors = "" if !defined($errors);
        $errors .= "BAUDRATE is <= 0.\n";
842
843
    }

844
    return ($consts_ref, $errors, $warnings);
845
846
}

847
848
sub _log2 {
    my $val = shift;
Christian Fibich's avatar
Christian Fibich committed
849
    return ($val > 0) ? (log($val) / log(2)) : 0;
850
851
}

852
## @function private _est_resources ($FREQUENCY, $BAUD, $TIMER_WIDTH, $RST_CYCLES, $LFSR_WIDTH, $FIU_NUM, $algo)
853
# @brief estimates the number of registers and LUTs to implement FIJI logic with the given settings
Christian Fibich's avatar
Christian Fibich committed
854
#
855
856
857
858
859
# 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
860
# @param RST_CYCLES   the number of cycles to apply a FIJI-to-DUT reset
861
# @param LFSR_WIDTH     width of the LFSR used for stuck-open emulation
Christian Fibich's avatar
Christian Fibich committed
862
# @param FIU_NUM        the number of FIUs in the configuration
863
864
865
#
# @returns ($registers, $lutsum)

Christian Fibich's avatar
Christian Fibich committed
866
sub _est_resources {
867
    my $logger = get_logger("");
868
    my ($FREQUENCY, $BAUD, $TIMER_WIDTH, $RST_CYCLES, $LFSR_WIDTH, $FIU_NUM) = @_;
Christian Fibich's avatar
Christian Fibich committed
869

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

Christian Fibich's avatar
Christian Fibich committed
873
874
    my $registers;
    my $lut6;
875
    my $out_of_range = [];
Christian Fibich's avatar
Christian Fibich committed
876

877
878
879
880
    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
881
    if ($BAUD < 9600 || $BAUD > 3000000) {
882
883
884
        $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
885
    if ($TIMER_WIDTH < 1 || $TIMER_WIDTH > 8) {
886
887
888
        $logger->debug("TIMER_WIDTH $TIMER_WIDTH out of range for correct resource estimation (1 - 8)");
        push @{$out_of_range}, "TIMER_WIDTH";
    }