Settings.pm 39.9 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
7
8
# The creation of this file has been supported by the publicly funded
# R&D project Josef Ressel Center for Verification of Embedded Computing
# Systems (VECS) managed by the Christian Doppler Gesellschaft (CDG).
#
9
10
11
# Authors:
# Christian Fibich <fibich@technikum-wien.at>
# Stefan Tauner <tauner@technikum-wien.at>
Christian Fibich's avatar
Christian Fibich committed
12
#
13
14
# 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
15
#
16
17
18
# 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
19
#
20
21
# See the LICENSE file for more details.
#-----------------------------------------------------------------------
Christian Fibich's avatar
Christian Fibich committed
22

Christian Fibich's avatar
Christian Fibich committed
23
24
## @file Settings.pm
# @brief Contains class \ref FIJI::Settings
25

26
## @class FIJI::Settings
Christian Fibich's avatar
Christian Fibich committed
27
# @brief Contains helper functions to deal with FIJI Settings files.
28

29
30
package FIJI::Settings;

31
32
33
use strict;
use warnings;

34
use Scalar::Util 'blessed';
35
36
use Log::Log4perl qw(get_logger);
use Scalar::Util "looks_like_number";
37
38
use POSIX qw(ceil);
use List::Util qw(max);
39
use File::Spec;
40

Christian Fibich's avatar
Christian Fibich committed
41
use FIJI::ConfigSorted;
42
43
use FIJI qw(:all);

Christian Fibich's avatar
Christian Fibich committed
44
## @var @base_resources stores the resource count for default config
Christian Fibich's avatar
Christian Fibich committed
45
46
my @base_resources;

47
## @function public new ($phase, $fiji_ini_file, $existing_settings, $nl_ref)
Stefan Tauner's avatar
Stefan Tauner committed
48
49
# Create a new settings instance.
#
50
51
52
53
54
55
56
57
58
# @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 {
59
    my $logger = get_logger("");
60
    my ($class, $phase, $fiji_ini_file, $existing_settings, $nl_ref) = @_;
61
62
63
64

    my $fiji_settings_ref;

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

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

97
    @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");
98

99
    return ($fiji_settings_ref, $errors, $warnings);
100
}
Stefan Tauner's avatar
Stefan Tauner committed
101
102

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

Christian Fibich's avatar
Christian Fibich committed
106
    if (defined($map_ref->{$k}->{'type'})) {
107
        my $orig = ${$v_ref};
108
        if ($map_ref->{$k}->{'type'} eq 'hexadecimal' || $map_ref->{$k}->{'type'} eq 'lfsrpoly') {
Christian Fibich's avatar
Christian Fibich committed
109
            ${$v_ref} = sprintf("0x%x", $orig);
110
111
112

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

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

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

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

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

189
        # Convert value to external representation
Christian Fibich's avatar
Christian Fibich committed
190
191
        _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"),);
192
    }
Christian Fibich's avatar
Christian Fibich committed
193
    $fiji_ini->set_block("CONSTS", $ini_design);
194

Christian Fibich's avatar
Christian Fibich committed
195
    if (!defined($fiji_ini->write($fiji_ini_file))) {
Christian Fibich's avatar
Christian Fibich committed
196
        my $err = FIJI::ConfigSorted->error();
197
198
199
        $logger->error($err);
        return $err;
    }
200
    $self->{'filename'} = $fiji_ini_file;
201
202
    return undef;
}
203

204
## @function public read_settingsfile ($phase, $fiji_ini_file, $existing_settings, $nl_ref)
205
# @brief Load the FIJI Settings file containing design and FIU constants.
206
#
207
# @param phase  Tool flow phase the settings stored in the given file
Stefan Tauner's avatar
Stefan Tauner committed
208
#               need to be compatible with.
209
# @param fiji_ini_file The name of an .ini file with FIJI Settings:
210
#         - a 'consts' block containing the constants specified by
Stefan Tauner's avatar
Stefan Tauner committed
211
#           \ref _sanitize_design.
212
213
214
#         - 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
215
216
217
# @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)
218
#
219
220
221
# @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.
222
sub read_settingsfile ($$$) {
223
    my $logger = get_logger("");
224
    my ($phase, $fiji_ini_file, $existing_settings, $nl_ref) = @_;
225
    my $fiji_ini;
Christian Fibich's avatar
Christian Fibich committed
226
    eval { $fiji_ini = new FIJI::ConfigSorted($fiji_ini_file) };    # pesky library tries to die on syntax errors
Christian Fibich's avatar
Christian Fibich committed
227
    if (!defined($fiji_ini)) {
Christian Fibich's avatar
Christian Fibich committed
228
        my $submsg = defined($@) ? $@ : FIJI::ConfigSorted->error();
Christian Fibich's avatar
Christian Fibich committed
229
        if (length($submsg) == 0) {
230
231
232
233
            $submsg = "Empty file?";
        }
        my $msg = "Could not read config file \"$fiji_ini_file\": $submsg";
        $logger->error($msg);
234
        return (undef, $msg);
235
236
237
    }

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

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

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

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

    # sanitize and validate read design constants
279
280
281

    my ($errors, $warnings);
    ($design_ref, $errors, $warnings) = _sanitize_design($design_ref, $phase, $nl_ref);
Christian Fibich's avatar
Christian Fibich committed
282
    if (!ref($design_ref)) {
283
284
        $logger->error($errors);
        return (undef, $errors, $warnings);
285
286
287
    }

    my $fiu_num = 0;
288
289
    my $error;
    # Loop over all read FIUs
290
    while (1) {
291

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

        my $tmp_fiu = {};
305
        _set_defaults(FIUMAP, $tmp_fiu, $phase, $design_ref, $fiu_num);
306
307

        # overwrite default entries
Christian Fibich's avatar
Christian Fibich committed
308
        foreach my $k (keys(%{$fiu_ref})) {
309
310
311
312
            $tmp_fiu->{$k} = $fiu_ref->{$k};
        }
        $fiu_ref = $tmp_fiu;

313
314
        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
315
        if (!ref($fiu_ref)) {
316
317
318
            my $msg = "";
            $msg .= "$errors\n" if defined($errors);
            $msg .= "Fatal problems sanitizing $fiu_id:\n$fiu_errors";
319
            $logger->error($msg);
320
321
322
323
324
325
326
            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";
327
        }
328

329
330
331
        if (defined($fiu_warnings)) {
            $warnings = "" if !defined($warnings);
            $warnings .= "$name_str:\n$fiu_warnings";
332
333
        }

334
335
        $fiji_settings_ref->{'fius'}->[$fiu_num] = $fiu_ref;
        $fiu_num++;
336
        $logger->trace("Read in $fiu_id from FIJI Settings file successfully.");
337
338
    }

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

    # 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
353
    if (defined($design_ref->{'FIU_NUM'}) && $design_ref->{'FIU_NUM'} != $fiu_num) {
354
355
356
357
        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";
358
    }
359
    $design_ref->{'FIU_NUM'} = $fiu_num; # assume the best if FIU_NUM constant is not given or misleading
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
418
419
420
421
    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;
422
423
}

424
## @method public set_fiu_defaults (%$fiu_ref)
425
426
427
# @brief Overwrite existing fields (if defined) with defaults defined in FIJI.pm.
#
sub set_fiu_defaults ($) {
428
429
    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
430
431
}

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

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

            # Also, do not overwrite existing values
Christian Fibich's avatar
Christian Fibich committed
454
            if (defined($consts_ref->{$k})) {
455
456
                next;
            }
457
458
459
460
461
462
            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
463
            $logger->trace(sprintf("Adding default constant: %s (%s) = %s.", $k, $map_ref->{$k}->{'ini_name'}, $map_ref->{$k}->{default}));
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
535
536
537
538
# @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;
}


539
## @function validate_value (%$map_ref, $nl_ref, $k, $$v_ref, $dep_ref, $consts_ref, $log_func)
540
541
#   Do validation (and conversation from external->internal representation)
#
542
543
544
545
546
547
548
549
550
551
#   @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.
552
sub validate_value {
553
    my $logger = get_logger("");
554
    my ($map_ref, $nl_ref, $k, $v_ref, $dep_ref, $consts_ref, $log_func) = @_;
555
    $log_func = \&Log::Log4perl::Logger::trace if !defined($log_func);
556
557
558
559
560

    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
561
        $logger->trace("$k is undef, setting to empty string");
562
563
564
        ${$v_ref} = ""
    }

Christian Fibich's avatar
Christian Fibich committed
565
    if (defined($map_ref->{$k}->{'type'})) {
566
567
        my $orig = ${$v_ref};

Christian Fibich's avatar
Christian Fibich committed
568
        if ($map_ref->{$k}->{'type'} eq 'net') {
569
570
571
572
573
574
            return "Netlist not available" if !defined($nl_ref);

            my $msg = $nl_ref->validate_net($orig);
            if (defined($msg)) {
                return $msg;
            }
575
576
577
578
579
        } 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;
            }
580
581
582
583
584
585
586
587
588
589
590
        } 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
591
        } elsif ($map_ref->{$k}->{'type'} eq 'hexadecimal' || $map_ref->{$k}->{'type'} eq 'lfsrpoly') {
Christian Fibich's avatar
Christian Fibich committed
592
            if ($orig !~ /^(0|(0[xX][[:xdigit:]]+))$/) {
593
                return "$orig does not look like a natural hexadecimal number";
594
            }
595
            return "$orig is too large" if (length($orig) > 2+8); # Hexadecimal numbers > 0xffffffff are non-portable
596
597
598
            ${$v_ref} = hex($orig);

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

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

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

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

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

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

Christian Fibich's avatar
Christian Fibich committed
639
    if (defined($map_ref->{$k}->{'values'})) {
640
        my $values_ref = $map_ref->{$k}->{'values'};
Christian Fibich's avatar
Christian Fibich committed
641
        if (ref($values_ref) eq 'ARRAY') {
642
643

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

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

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

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

693
## @function _sanitize_fiu (%$fiu_ref, $phase)
694
695
# @brief Convert and sanity check FIJI Settings.
#
696
697
# \param fiu_ref a reference to a hash containing FIJI Settings for a
#                single FIU.
Christian Fibich's avatar
Christian Fibich committed
698
# \param phase   the current design phase
699
700
701
#
# \returns A new hash with all constants required in the FIU settings
#          in sanitized form, or undef on errors.
702
703
704
# @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.
705
sub _sanitize_fiu {
706
    my $logger = get_logger("");
707
    my ($fiu_ref, $phase, $nl_ref) = @_;
Christian Fibich's avatar
Christian Fibich committed
708
    if (ref($fiu_ref) ne 'HASH') {
709
710
        my $msg = "Parameter is not a reference to a hash (containing FIU constants).";
        $logger->error($msg);
711
        return (undef, $msg);
712
    }
713

714
715
    my ($warnings, $errors);
    ($fiu_ref, $errors, $warnings) = _validate_hashmap(FIUMAP, $nl_ref, $fiu_ref, $phase);
Christian Fibich's avatar
Christian Fibich committed
716
    if (!ref($fiu_ref)) {
717
718
        my $msg = "Could not validate FIU Constants. $errors";
        return chomp $msg;
719
    }
Stefan Tauner's avatar
Stefan Tauner committed
720

721
    return ($fiu_ref, $errors, $warnings);
722
723
}

724
sub _disabled_via_dependency {
Christian Fibich's avatar
Christian Fibich committed
725
    my ($map_ref, $consts_ref, $k) = @_;
726
727
728
729
730
731
732
733
734
735
736
737
738
    my $depends_on = $map_ref->{$k}->{'depends_on'};

    # If we can't find the template for an entry in $map_ref, we assume
    # everything's OK. 
    # Rationale: We would need to pass >1 different maps to cross-check
    # entries defined in other maps (e.g., FIU_LFSR_MASK in the FIUMAP depends on LFSR_MASK in the DESIGNMAP)


    # For an entry to be disabled by a dependency, ...
    return defined($depends_on)                           && # ... it must depend on something
           defined($map_ref->{$depends_on})               && # ... that something must be found in the map
           $map_ref->{$depends_on}->{'type'} eq 'boolean' && # ... that something must be of type boolean
           !$consts_ref->{$depends_on};                      # ... that something must be set to false
739
}
740

Christian Fibich's avatar
Christian Fibich committed
741
sub _validate_hashmap {
742
    my $logger = get_logger("");
743
    my ($map_ref, $nl_ref, $consts_ref, $phase) = @_;
Christian Fibich's avatar
Christian Fibich committed
744
    my @map_keys = keys(%{$map_ref});
745
746
    my $errors;
    my $warnings;
Christian Fibich's avatar
Christian Fibich committed
747
    foreach my $entry_key (keys(%{$consts_ref})) {
748
749
750
751
        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
752
753
        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'}));
754
755
        } 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));
756
757
758
        } else {
            my $rv = validate_value($map_ref, $nl_ref, $entry_key, \$consts_ref->{$entry_key}, undef, $consts_ref);
            if (defined($rv)) {
759
                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" : "");
760
761
762
763
764
765
766
767
768
769
                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";
                }
            }
770
771
        }

Christian Fibich's avatar
Christian Fibich committed
772
        if (defined($forbidden_by) && $consts_ref->{$entry_key} && $consts_ref->{$forbidden_by}) {
773
774
            my $msg = "$entry_key is forbidden when $forbidden_by is enabled";
            $logger->error($msg);
775
776
            $errors = "" if !defined($errors);
            $errors .= "$msg\n";
777
        }
Stefan Tauner's avatar
Stefan Tauner committed
778
    }
779

Christian Fibich's avatar
Christian Fibich committed
780
    if (!defined($phase)) {
781

782
783
784
        # 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.
785
        return ($consts_ref, $errors, $warnings);
786
    }
787

788
789
790
791
792
    # 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
793
794
795
            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
           )
796
797
798
        {
            my $msg = "$k is mandatory in phase $phase.";
            $logger->error($msg);
799
800
            $errors = "" if !defined($errors);
            $errors .= "$msg\n";
801
802
803
        }

    }
Stefan Tauner's avatar
Stefan Tauner committed
804

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

807
    return ($consts_ref, $errors, $warnings);
808
}
Stefan Tauner's avatar
Stefan Tauner committed
809

810
## @function _sanitize_design (%$consts_ref, $phase, $nl_ref)
Stefan Tauner's avatar
Stefan Tauner committed
811
# @brief Sanity check FIJI Design Settings.
812
#
813
814
# The function deals with sanity checks for the values themselves as
# specified by FIJI.pm. Additionally, it checks for the following conditions:
815
816
#
#   - FIU_CFG_BITS: > 0
817
#   - TIMER_WIDTH: > 0, <= 16
818
819
820
#   - ID: > 0, < 2^15-1
#   - BAUDRATE: > 0
#
821
822
823
# @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)
824
#
825
826
827
# @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
828
sub _sanitize_design {
829
    my $logger = get_logger("");
830
    my ($consts_ref, $phase, $nl_ref) = @_;
Christian Fibich's avatar
Christian Fibich committed
831
    if (ref($consts_ref) ne 'HASH') {
832
        return (undef, "Parameter is not a reference to a hash (containing design constants).");
833
834
835
    }

    # check for sane values
836
837
    my ($warnings, $errors);
    ($consts_ref, $errors, $warnings) = _validate_hashmap(DESIGNMAP, $nl_ref, $consts_ref, $phase);
Christian Fibich's avatar
Christian Fibich committed
838
    if (!ref($consts_ref)) {
839
840
        my $msg = "Could not validate Design Constants:\n$errors";
        return (undef, chomp $msg, $warnings);
841
842
    }

Christian Fibich's avatar
Christian Fibich committed
843
    if (($consts_ref->{'FIU_CFG_BITS'} <= 0)) {
844
845
        $errors = "" if !defined($errors);
        $errors .= "FIU_CFG_BITS is <= 0.\n";
846
    }
Christian Fibich's avatar
Christian Fibich committed
847
    if (($consts_ref->{'TIMER_WIDTH'} <= 0) || ($consts_ref->{'TIMER_WIDTH'} > 16)) {
848
849
        $errors = "" if !defined($errors);
        $errors .= "TIMER_WIDTH is invalid ($consts_ref->{'TIMER_WIDTH'}).\n";
850
    }
Christian Fibich's avatar
Christian Fibich committed
851
    if (defined($consts_ref->{'ID'}) && ($consts_ref->{ID} < 0 || $consts_ref->{ID} > (2**16 - 1))) {
852
853
        $errors = "" if !defined($errors);
        $errors .= sprintf("ID is invalid (0x%04X).\n", $consts_ref->{ID});
854
    }
Christian Fibich's avatar
Christian Fibich committed
855
    if (($consts_ref->{'BAUDRATE'} <= 0)) {
856
857
        $errors = "" if !defined($errors);
        $errors .= "BAUDRATE is <= 0.\n";
858
859
    }

860
    return ($consts_ref, $errors, $warnings);
861
862
}

863
864
sub _log2 {
    my $val = shift;
Christian Fibich's avatar
Christian Fibich committed
865
    return ($val > 0) ? (log($val) / log(2)) : 0;
866
867
}

868
## @function private _est_resources ($FREQUENCY, $BAUD, $TIMER_WIDTH, $RST_CYCLES, $LFSR_WIDTH, $FIU_NUM, $algo)
869
# @brief estimates the number of registers and LUTs to implement FIJI logic with the given settings
Christian Fibich's avatar
Christian Fibich committed
870
#
871
872
873
874
875
# 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
876
# @param RST_CYCLES   the number of cycles to apply a FIJI-to-DUT reset
877
# @param LFSR_WIDTH     width of the LFSR used for stuck-open emulation
Christian Fibich's avatar
Christian Fibich committed
878
# @param FIU_NUM        the number of FIUs in the configuration
879
880
881
#
# @returns ($registers, $lutsum)

Christian Fibich's avatar
Christian Fibich committed
882
sub _est_resources {
883
884
885
886
    # We recalculate resources even if parameters are bogus. Disabling
    # warnings at least hides this stupidity from the user.
    no warnings 'numeric';

887
    my $logger = get_logger("");
888
    my ($FREQUENCY, $BAUD, $TIMER_WIDTH, $RST_CYCLES, $LFSR_WIDTH, $FIU_NUM) = @_;
Christian Fibich's avatar
Christian Fibich committed
889

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