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

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

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

24
25
26
use strict;
use warnings;

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

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

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

40
## @function public new ($phase, $fiji_ini_file, $existing_settings, $nl_ref)
Stefan Tauner's avatar
Stefan Tauner committed
41
42
# Create a new settings instance.
#
43
44
45
46
47
48
49
50
51
# @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 {
52
    my $logger = get_logger("");
53
    my ($class, $phase, $fiji_ini_file, $existing_settings, $nl_ref) = @_;
54
55
56
57

    my $fiji_settings_ref;

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

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

90
    @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");
91

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    # sanitize and validate read design constants
272
273
274

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

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

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

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

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

306
307
        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
308
        if (!ref($fiu_ref)) {
309
310
311
            my $msg = "";
            $msg .= "$errors\n" if defined($errors);
            $msg .= "Fatal problems sanitizing $fiu_id:\n$fiu_errors";
312
            $logger->error($msg);
313
314
315
316
317
318
319
            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";
320
        }
321

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

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

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

    # 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
346
    if (defined($design_ref->{'FIU_NUM'}) && $design_ref->{'FIU_NUM'} != $fiu_num) {
347
348
349
350
        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";
351
352
353
354
    } else {
        $design_ref->{'FIU_NUM'} = $fiu_num;    # assume the best if FIU_NUM constant is not given
    }

355
356
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
    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;
416
417
}

418
## @method public set_fiu_defaults (%$fiu_ref)
419
420
421
# @brief Overwrite existing fields (if defined) with defaults defined in FIJI.pm.
#
sub set_fiu_defaults ($) {
422
423
    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
424
425
}

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

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

            # Also, do not overwrite existing values
Christian Fibich's avatar
Christian Fibich committed
448
            if (defined($consts_ref->{$k})) {
449
450
                next;
            }
451
452
453
454
455
456
            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
457
            $logger->trace(sprintf("Adding default constant: %s (%s) = %s.", $k, $map_ref->{$k}->{'ini_name'}, $map_ref->{$k}->{default}));
458
459
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
# @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;
}


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

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

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

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

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

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

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

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

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

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

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

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

Christian Fibich's avatar
Christian Fibich committed
637
    if (defined($map_ref->{$k}->{'values'})) {
638
        my $values_ref = $map_ref->{$k}->{'values'};
639
        my $dep_val;
640
641
        if (defined($map_ref->{$k}->{'depends_on'})) {
            $dep_val = $consts_ref->{$map_ref->{$k}->{'depends_on'}};
642
643
        }

Christian Fibich's avatar
Christian Fibich committed
644
        if (ref($values_ref) eq 'ARRAY') {
645
646

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

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

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

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

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

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

724
    return ($fiu_ref, $errors, $warnings);
725
726
}

727
sub _disabled_via_dependency {
Christian Fibich's avatar
Christian Fibich committed
728
    my ($map_ref, $consts_ref, $k) = @_;
729
730
731
    my $dependency = $map_ref->{$k}->{'depends_on'};
    return defined($dependency) && !$consts_ref->{$dependency};
}
732

Christian Fibich's avatar
Christian Fibich committed
733
sub _validate_hashmap {
734
    my $logger = get_logger("");
735
    my ($map_ref, $nl_ref, $consts_ref, $phase) = @_;
Christian Fibich's avatar
Christian Fibich committed
736
    my @map_keys = keys(%{$map_ref});
737
738
    my $errors;
    my $warnings;
Christian Fibich's avatar
Christian Fibich committed
739
    foreach my $entry_key (keys(%{$consts_ref})) {
740
741
742
743
        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
744
745
        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'}));
746
747
748
        } else {
            my $rv = validate_value($map_ref, $nl_ref, $entry_key, \$consts_ref->{$entry_key}, undef, $consts_ref);
            if (defined($rv)) {
749
                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" : "");
750
751
752
753
754
755
756
757
758
759
                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";
                }
            }
760
761
        }

Christian Fibich's avatar
Christian Fibich committed
762
        if (defined($forbidden_by) && $consts_ref->{$entry_key} && $consts_ref->{$forbidden_by}) {
763
764
            my $msg = "$entry_key is forbidden when $forbidden_by is enabled";
            $logger->error($msg);
765
766
            $errors = "" if !defined($errors);
            $errors .= "$msg\n";
767
        }
Stefan Tauner's avatar
Stefan Tauner committed
768
    }
769

Christian Fibich's avatar
Christian Fibich committed
770
    if (!defined($phase)) {
771

772
773
774
        # 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.
775
        return ($consts_ref, $errors, $warnings);
776
    }
777

778
779
780
781
782
    # 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
783
784
785
            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
           )
786
787
788
        {
            my $msg = "$k is mandatory in phase $phase.";
            $logger->error($msg);
789
790
            $errors = "" if !defined($errors);
            $errors .= "$msg\n";
791
792
793
        }

    }
Stefan Tauner's avatar
Stefan Tauner committed
794

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

797
    return ($consts_ref, $errors, $warnings);
798
}
Stefan Tauner's avatar
Stefan Tauner committed
799

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

    # check for sane values
826
827
    my ($warnings, $errors);
    ($consts_ref, $errors, $warnings) = _validate_hashmap(DESIGNMAP, $nl_ref, $consts_ref, $phase);
Christian Fibich's avatar
Christian Fibich committed
828
    if (!ref($consts_ref)) {
829
830
        my $msg = "Could not validate Design Constants:\n$errors";
        return (undef, chomp $msg, $warnings);
831
832
    }

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

850
    return ($consts_ref, $errors, $warnings);
851
852
}

853
854
sub _log2 {
    my $val = shift;
Christian Fibich's avatar
Christian Fibich committed
855
    return ($val > 0) ? (log($val) / log(2)) : 0;
856
857
}

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

Christian Fibich's avatar
Christian Fibich committed
872
sub _est_resources {
873
    my $logger = get_logger("");
874
    my ($FREQUENCY, $BAUD, $TIMER_WIDTH, $RST_CYCLES, $LFSR_WIDTH, $FIU_NUM) = @_;
Christian Fibich's avatar
Christian Fibich committed
875

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

Christian Fibich's avatar
Christian Fibich committed
879
880
    my $registers;
    my $lut6;
881
    my $out_of_range = [];
Christian Fibich's avatar
Christian Fibich committed
882

883
884
885
886
    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
887
    if ($BAUD < 9600 || $BAUD > 3000000) {
888
889
890
        $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
891
    if ($TIMER_WIDTH < 1 || $TIMER_WIDTH > 8) {
892
893
894
        $logger->debug("TIMER_WIDTH $TIMER_WIDTH out of range for correct resource estimation (1 - 8)");
        push @{$out_of_range}, "TIMER_WIDTH";
    }
895
896
897
    if ($RST_CYCLES < 1 || $RST_CYCLES > 16) {
        $logger->debug("RST_CYCLES $RST_CYCLES out of range for correct resource estimation (1 - 16)");
        push @{$out_of_range}, "RST_CYCLES";
898
    }
Christian Fibich's avatar
Christian Fibich committed
899
    if ($LFSR_WIDTH < 16 || $LFSR_WIDTH > 64) {
900
        $logger->debug("LFSR_WIDTH $LFSR_WIDTH out of range for correct resource estimation (16 - 64)");
901
        push @{$out_of_range}, "RST_CYCLES";