Tests.pm 42.2 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

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

## @class FIJI::Tests
#
Christian Fibich's avatar
Christian Fibich committed
28
# @brief Contains helper functions to deal with FIJI Test files.
29

30
31
32
33
34
35
36
37
38
package FIJI::Tests;

use strict;
use warnings;

use Scalar::Util 'blessed';
use Log::Log4perl qw(get_logger);
use Scalar::Util "looks_like_number";
use Clone qw(clone);
Christian Fibich's avatar
Christian Fibich committed
39
40
41
use File::Spec;

use FIJI::ConfigSorted;
42
use FIJI::Settings;
43
44
use FIJI::Tests::VHDL;
use FIJI::Tests::SystemVerilog;
45
use FIJI qw(:all);
Christian Fibich's avatar
Christian Fibich committed
46

47
# @FIXME this was almost a 1:1 copy of Settings.pm. refactoring or cleanup is required
Christian Fibich's avatar
Christian Fibich committed
48

49
## @function public new ($phase, %$global_settings_ref, $fiji_ini_file, %$existing_settings)
50
51
# Create a new settings instance.
#
Christian Fibich's avatar
Christian Fibich committed
52
53
54
# \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
55
#        with values read from file. Any contained data will be cleared.
56
57
# \returns The new settings instance or undef in case of errors, and
#        a string describing the reason if it could not be created.
58
sub new {
59
    my $logger = get_logger("");
60
    my ($class, $phase, $global_settings_ref, $fiji_ini_file, $existing_settings, $num_tests) = @_;
61

62
    my $self = {};
Christian Fibich's avatar
Christian Fibich committed
63

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

81
    my $warn;
82
    # If there is a file given, try to read it. Else just create a default instance.
Christian Fibich's avatar
Christian Fibich committed
83
    if (defined($fiji_ini_file)) {
84
        ($self, $warn) = $self->_read_settingsfile($phase, $global_settings_ref, $fiji_ini_file);
Christian Fibich's avatar
Christian Fibich committed
85

86
87
        if (!defined($self)) {
            return (undef, $warn);
88
        }
89
        $self->{'filename'} = $fiji_ini_file;
90

91
    } else {
Christian Fibich's avatar
Christian Fibich committed
92

93
94
        # create a default instance

95
        $self->{'design'} = {};
96
        $self->_set_defaults(TESTCONSTMAP, $self->{'design'}, $phase);
97
98
        $self->{'design'}->{'NUM_TESTS'} = $num_tests;

99
        my $tkm_ref = TESTCONSTMAP;
Christian Fibich's avatar
Christian Fibich committed
100
        for my $k (keys(%{$tkm_ref})) {
101
102
103
104
105
106
107
108
            my $def;
            if (ref(TESTCONSTMAP->{$k}->{default}) eq 'CODE') {
                $def = TESTCONSTMAP->{$k}->{'default'}->($global_settings_ref);
                $def = @{$def}[0] if (ref($def) eq 'ARRAY');
            } else {
                $def = TESTCONSTMAP->{$k}->{'default'};
            }
            $self->{'design'}->{$k} = $def;
109
        }
110

111
        $self->{'TESTPATMAP'}   = $self->_generate_testpatmap($global_settings_ref);
112

113
        $self->{'tests'} = [];
114

Christian Fibich's avatar
Christian Fibich committed
115
116
        if (defined($num_tests)) {
            for (my $i = 0 ; $i < $num_tests ; $i++) {
117
                my $thistest = {};
118
119
120
121
122
123
124
125
126
127
                for my $k (keys(%{$self->{'TESTPATMAP'}})) {
                    my $def;
                    my $defsrc = $self->{'TESTPATMAP'}->{$k}->{'default'};
                    if (ref($defsrc) eq 'CODE') {
                        $def = $defsrc->($global_settings_ref);
                        $def = @{$def}[0] if (ref($def) eq 'ARRAY');
                    } else {
                        $def = $defsrc;
                    }
                    $thistest->{$k} = $def;
128
                }
129
                push @{$self->{'tests'}}, $thistest;
Christian Fibich's avatar
Christian Fibich committed
130
            }
131
132
        }
    }
133
    return ($self, $warn);
134
135
}

136
sub _export_value {
137
    my $logger = get_logger("");
Christian Fibich's avatar
Christian Fibich committed
138
    my ($map_ref, $k, $v_ref) = @_;
139

Christian Fibich's avatar
Christian Fibich committed
140
    if (defined($map_ref->{$k}->{'type'})) {
141
        my $orig = ${$v_ref};
Christian Fibich's avatar
Christian Fibich committed
142
143
        if ($map_ref->{$k}->{'type'} eq 'hexadecimal') {
            ${$v_ref} = sprintf("0x%x", $orig);
144

145
146
            # } elsif ($map_ref->{$k}->{'type'} eq 'natural') {
            # } elsif ($map_ref->{$k}->{'type'} eq 'boolean') {
Christian Fibich's avatar
Christian Fibich committed
147
            $logger->trace("Converted value of $k (\"$orig\") to \"${$v_ref}\".") if ($orig ne ${$v_ref});
148
149
150
151
        }

        # } elsif (defined($map_ref->{$k}->{'values'})) {
    }
152
153
}

154
## @method public save ($fiji_ini_file)
155
156
# @brief Store contained FIJI Settings to file.
#
Christian Fibich's avatar
Christian Fibich committed
157
# @attention Will happily overwrite existing files!
158
159
#
# \param fiji_ini_file The file name to write the FIJI Settings to.
160
sub save {
161
    my $logger = get_logger("");
Christian Fibich's avatar
Christian Fibich committed
162
    my ($self, $fiji_ini_file) = @_;
163
164
    return "No file name given" if !defined($fiji_ini_file);

Christian Fibich's avatar
Christian Fibich committed
165
    my $fiji_ini = new FIJI::ConfigSorted(syntax => 'ini');
166
167
168
    my $design_ref;
    my $test_cnt = 0;

Christian Fibich's avatar
Christian Fibich committed
169
    foreach my $key (keys %{$self}) {
170
        my $val = $self->{$key};
Christian Fibich's avatar
Christian Fibich committed
171
172
173
        if (ref(\$val) eq "REF") {
            if (ref($val) eq "HASH") {
                if ($key eq "design") {
174
175
                    $design_ref = $val;
                    next;
176
                } elsif ($key eq "TESTPATMAP") {
177
178
                    next;
                }
Christian Fibich's avatar
Christian Fibich committed
179
180
181
            } elsif (ref($val) eq "ARRAY") {
                if ($key eq "tests") {
                    foreach my $test (@{$val}) {
182
183
                        my $ini_test;

Christian Fibich's avatar
Christian Fibich committed
184
                        foreach my $k (keys(%{$test})) {
185
                            my $ini_name = $self->{'TESTPATMAP'}->{$k}->{'ini_name'};
Christian Fibich's avatar
Christian Fibich committed
186
                            if (!defined($test->{$k})) {
187
188
189
190
191
192
193
194
                                $logger->debug("Skip saving undefined value of TEST constant with key $ini_name.");
                                next;
                            }

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

                            # Convert value to external representation
195
                            _export_value($self->{'TESTPATMAP'}, $k, \$ini_test->{$ini_name});
Christian Fibich's avatar
Christian Fibich committed
196
                            $logger->trace(sprintf("Exporting TEST%d setting %s -> %s (%s -> %s).", $test_cnt, $k, $ini_name, defined($test->{$k}) ? $test->{$k} : "undef", defined($ini_test->{$ini_name}) ? $ini_test->{$ini_name} : "undef"),);
197
                        }
Christian Fibich's avatar
Christian Fibich committed
198
                        $fiji_ini->set_block("TEST" . $test_cnt++, $ini_test);
199
200
201
                    }
                    next;
                }
202
            }
203
204
        } elsif ($key eq "filename") {
            next;
205
        }
206

207
208
209
210
211
212
213
        my $err = "Unknown element found in FIJI Settings: \"$val\"";
        $logger->error($err);

        #return $err;
    }
    $design_ref->{'NUM_TESTS'} = $test_cnt;
    my $ini_design;
Christian Fibich's avatar
Christian Fibich committed
214
    foreach my $k (keys(%{$design_ref})) {
215
        my $ini_name = TESTCONSTMAP->{$k}->{'ini_name'};
Christian Fibich's avatar
Christian Fibich committed
216
        if (!defined($design_ref->{$k})) {
217
218
219
220
221
222
            $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};
223

224
        # Convert value to external representation
Christian Fibich's avatar
Christian Fibich committed
225
226
        _export_value(TESTCONSTMAP, $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"),);
227
    }
Christian Fibich's avatar
Christian Fibich committed
228
    $fiji_ini->set_block("CONSTS", $ini_design);
229

Christian Fibich's avatar
Christian Fibich committed
230
    if (!defined($fiji_ini->write($fiji_ini_file))) {
Christian Fibich's avatar
Christian Fibich committed
231
        my $err = FIJI::ConfigSorted->error();
232
233
234
        $logger->error($err);
        return $err;
    }
235
236

    $self->{'filename'} = $fiji_ini_file;
237
238
    return undef;
}
239

240
## @method public read_settingsfile ($phase, $fiji_ini_file)
241
242
243
244
245
246
247
248
249
250
251
# @brief Load the FIJI Settings file containing design and TEST constants.
#
# \param phase  Tool flow phase the settings stored in the given file
#               need to be compatible with.
# \param fiji_ini_file The name of an .ini file with FIJI Settings:
#         - a 'consts' block containing the constants specified by
#           \ref _sanitize_design.
#         - at least one TEST block named "TEST<number>" where "<number>"
#           is a strictly increasing integer starting with 0 containing
#           the constants for the respective TEST, see \ref _sanitize_test
#
252
253
# \returns a reference to the hash containing the read constants or undef in case of errors, and
#          a list of reasons if it could not be read successfully.
254
sub _read_settingsfile {
255
    my $logger = get_logger("");
256
    my ($self, $phase, $global_settings_ref, $fiji_ini_file) = @_;
257
    my $fiji_ini;
258
    my $msg_ref;
259

Christian Fibich's avatar
Christian Fibich committed
260
    eval { $fiji_ini = new FIJI::ConfigSorted($fiji_ini_file) };    # pesky library tries to die on syntax errors
Christian Fibich's avatar
Christian Fibich committed
261
    if (!defined($fiji_ini)) {
Christian Fibich's avatar
Christian Fibich committed
262
        my $submsg = defined($@) ? $@ : FIJI::ConfigSorted->error();
Christian Fibich's avatar
Christian Fibich committed
263
        if (length($submsg) == 0) {
264
265
266
267
            $submsg = "Empty file?";
        }
        my $msg = "Could not read config file \"$fiji_ini_file\": $submsg";
        $logger->error($msg);
268
        push(@$msg_ref, $msg);
269
    }
270

271
    my $design_ref = $fiji_ini->get_block("CONSTS");
Christian Fibich's avatar
Christian Fibich committed
272
    if (!(%$design_ref)) {
273
274
        my $msg = "Could not fetch CONSTS block from config file \"$fiji_ini_file\"";
        $logger->error($msg);
275
        push(@$msg_ref, $msg);
276
    }
277
    $self->_set_defaults(TESTCONSTMAP, $design_ref, $phase);
278

Christian Fibich's avatar
Christian Fibich committed
279
280
    $design_ref = _rename_import(TESTCONSTMAP, $design_ref);
    if (!defined($design_ref)) {
281
282
        my $msg = "Design constants do not match the FIJI Tests naming scheme.";
        $logger->error($msg);
283
        push(@$msg_ref, $msg);
284
    }
285

286
    # Reuse the shallow hull of the existing Tests instance
287

288
289
290
    # Clear the hash
    for (keys %$self) {
        delete $self->{$_};
291
292
    }

293
    # Generate data structure to verify any tests structure based on the given FIJI settings.
294
    # This allows, for example, to make sure we don't try to operate on an FIU that does not even exist.
295
296
297
    $self->{'TESTPATMAP'} = $self->_generate_testpatmap($global_settings_ref);
    $self->{'design'}     = $design_ref;
    $self->{'tests'}      = [];
298

299
    my $msg;
300
    # sanitize and validate read design constants
301
302
303
    $msg = _sanitize_design($global_settings_ref, $design_ref, $phase);
    if (defined($msg)) {
        push(@$msg_ref, $msg);
304
        return (undef, $msg_ref);
305
306
307
308
309
310
311
312
    }

    my $test_num = 0;

    # loop over all read TESTs
    while (1) {
        my $test_name = "TEST" . $test_num;
        my $test_ref  = $fiji_ini->get_block($test_name);
Christian Fibich's avatar
Christian Fibich committed
313
        if (!(%$test_ref)) {
314
315
316
            last;
        }

317
318
        $test_ref = _rename_import($self->{'TESTPATMAP'}, $test_ref);
        if (!defined($test_ref)) {
319
320
            my $msg = "Test constants of $test_name do not match the FIJI Tests naming scheme.";
            $logger->error($msg);
321
            push(@$msg_ref, $msg);
322
323
324
        }

        my $tmp_test = {};
325
        $self->_set_defaults($self->{'TESTPATMAP'}, $tmp_test, $phase);
326
327

        # overwrite default entries
Christian Fibich's avatar
Christian Fibich committed
328
        foreach my $k (keys(%{$test_ref})) {
329
330
331
332
            $tmp_test->{$k} = $test_ref->{$k};
        }
        $test_ref = $tmp_test;

333
334
335
        my $msg = _sanitize_test($global_settings_ref, $self->{'TESTPATMAP'}, $test_ref, $phase);
        # $msg = join("\n", @$msg) if defined($msg);
        push(@$msg_ref, @$msg) if defined($msg);
336
        $self->{'tests'}->[$test_num] = $test_ref;
337
        $test_num++;
338
        $logger->trace(sprintf("Read in $test_name from FIJI Tests file%s.", !defined($msg) ? "successfully" : ""));
339
340
    }

Christian Fibich's avatar
Christian Fibich committed
341
    if ($test_num == 0) {
342
343
344
345
346
        $logger->debug("Could not fetch any TEST blocks from config file \"$fiji_ini_file\"");
    }

    # TEST_NUM is optional in the Settings file... if it was set check that
    # it corresponds to the number of TEST<number> blocks.
Christian Fibich's avatar
Christian Fibich committed
347
    if (defined($design_ref->{'NUM_TESTS'}) && $design_ref->{'NUM_TESTS'} != $test_num) {
348
349
        my $msg = NUM_TESTS->{'ini_name'} . " does not match the numbers of TEST blocks found.";
        $logger->error($msg);
350
        push(@$msg_ref, $msg);
351
352
353
354
    } else {
        $design_ref->{'NUM_TESTS'} = $test_num;    # assume the best if TEST_NUM constant is not given
    }

355
    splice(@{$self->{'tests'}}, $test_num);
356
    $logger->info("Successfully read in design constants and $test_num TEST definitions from FIJI Tests file \"$fiji_ini_file\".");
357
    return ($self, $msg_ref);
358
359
}

360
361
362
## @method private _generate_testpatmap ()
# @brief generates tailored TESTPATMAP for validating test patterns
#
363
# clones the original TESTPATMAP and adds FIU_x_FAULT_y keys
364
365
#
# @returns the tailored TESTPATMAP
366
sub _generate_testpatmap {
367
    my ($self, $global_settings_ref) = @_;
368

369
    my $testpatmap = clone(TESTPATMAP);
370

371
    for (my $pattern_idx = 1 ; $pattern_idx <= $global_settings_ref->{'design'}->{'CFGS_PER_MSG'} ; $pattern_idx++) {
372

373
        my $prop_name = "TIMER_VALUE_${pattern_idx}";
374
        $testpatmap->{$prop_name} = {
375
            ini_name   => $prop_name,
376
            default    => $global_settings_ref->default_timer_value(),
377
            type       => "natural",
378
            phases_opt => [qw(manual)]
379
380
        };

381
        for (my $fiu_idx = 0 ; $fiu_idx < $global_settings_ref->{'design'}->{'FIU_NUM'} ; $fiu_idx++) {
382
383
384
385
386
387
388
            my $prop_name = "FIU_${fiu_idx}_FAULT_${pattern_idx}";
            $testpatmap->{$prop_name} = {
                ini_name   => $prop_name,
                default    => "NONE",
                values     => [qw(NONE STUCK_AT_0 STUCK_AT_1 DELAY SEU STUCK_OPEN)],
                phases_opt => [qw(manual)]
            };
389
390
391
        }
    }

392
    return $testpatmap;
393
394
}

395
## @method public set_test_defaults (%$test_ref)
396
397
# @brief Overwrite existing fields (if defined) with defaults defined in FIJI.pm.
#
398
sub set_test_defaults {
Christian Fibich's avatar
Christian Fibich committed
399
    my ($self, $consts_ref) = @_;
400
    return $self->_set_defaults($self->{'TESTPATMAP'}, $consts_ref);
401
402
}

403
## @method public _set_defaults (%$map_ref, %$consts_ref)
404
405
# @brief Set defaults according to FIJI.pm.
sub _set_defaults {
406
    my $logger = get_logger("");
407
    my ($self, $map_ref, $consts_ref, $phase) = @_;
Christian Fibich's avatar
Christian Fibich committed
408
409
    foreach my $k (keys(%{$map_ref})) {
        if (exists($map_ref->{$k}->{'default'})) {
410
411

            # Set default only if it is not mandatory in the given phase.
412
            if (defined($phase) && scalar(grep { $_ eq $phase } @{$map_ref->{$k}->{'phases_opt'}}) == 0) {
413
414
                next;
            }
415

416
            # Also, do not overwrite existing values
Christian Fibich's avatar
Christian Fibich committed
417
            if (defined($consts_ref->{$k})) {
418
419
                next;
            }
420
421
422

            my $def;
            if (ref($map_ref->{$k}->{default}) eq 'CODE') {
423
                $def = $map_ref->{$k}->{'default'}->($self->{'settings'});
424
425
426
427
428
            } else {
                $def = $map_ref->{$k}->{'default'};
            }
            $consts_ref->{$k} = $def;
            $logger->trace(sprintf("Adding default constant: %s (%s) = %s.", $k, $map_ref->{$k}->{'ini_name'}, $def));
429
430
431
        }
    }
}
432

433
## @function public validate_value (%$map_ref, $k, $$v_ref, $old, $log_func)
434
435
#   Do validation (and conversation from external->internal representation)
#
436
#   \param global_settings_ref  reference to FIJI Settings mappings
437
#   \param map_ref  reference to FIJI Test mappings
438
439
440
441
#   \param k        key identifying the respective setting
#   \param v_ref    scalar reference to the proposed value (that may be modified)
#   \param old      (optional) previously valid value
#   \param log_func (optional) the (log4perl) log function to use
442
#                   (default is \&Log::Log4perl::Logger::trace)
443
sub validate_value {
444
    my $logger = get_logger("");
445
    my ($global_settings_ref, $map_ref, $k, $v_ref, $old, $log_func) = @_;
446
    $log_func = \&Log::Log4perl::Logger::trace if !defined($log_func);
Christian Fibich's avatar
Christian Fibich committed
447
    if (defined($map_ref->{$k}->{'type'})) {
448
449
        my $orig = ${$v_ref};

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

Christian Fibich's avatar
Christian Fibich committed
455
456
457
        if ($map_ref->{$k}->{'type'} eq 'hexadecimal') {
            if ($orig !~ /^(0|(0[xX][[:xdigit:]]+))$/) {
                $log_func->($logger, "$k: $orig does not look like a natural hexadecimal number.");
458
459
460
461
462
                return 0;
            }
            ${$v_ref} = hex($orig);

            # Check for natural value range. Should never trigger due to the regex above.
Christian Fibich's avatar
Christian Fibich committed
463
464
            if (${$v_ref} < 0) {
                $log_func->($logger, "$k: $orig is negative.");
465
466
                return 0;
            }
Christian Fibich's avatar
Christian Fibich committed
467
        } elsif ($map_ref->{$k}->{'type'} eq 'natural'
468
            || $map_ref->{$k}->{'type'} eq 'min_duration'
Christian Fibich's avatar
Christian Fibich committed
469
            || $map_ref->{$k}->{'type'} eq 'max_duration')
470
471
        {
            # Match hexadecimal, binary, octal and decimal numbers (the latter also in scientific notation)
Christian Fibich's avatar
Christian Fibich committed
472
473
            if ($orig !~ /^(0|(0(x[0-9a-fA-F]+|[0-7]+))|(0b[01]+)|([1-9][0-9]*(e(-[0-9])?[0-9]+)?))$/) {
                $log_func->($logger, "$k: $orig does not look like a number.");
474
475
476
477
478
                return 0;
            }

            # convert non-decimal (hexadecimal, binary, octal) values to decimal
            ${$v_ref} = oct($orig) if $orig =~ /^0/;
Christian Fibich's avatar
Christian Fibich committed
479
480
            if (!looks_like_number(${$v_ref})) {
                $log_func->($logger, "$k: $orig does not look like a number.");
481
482
483
484
                return 0;
            }

            # Check for natural value range. Should never trigger due to the regex above.
Christian Fibich's avatar
Christian Fibich committed
485
486
            if (${$v_ref} < 0) {
                $log_func->($logger, "$k: $orig is negative.");
487
488
                return 0;
            }
Christian Fibich's avatar
Christian Fibich committed
489
        } elsif ($map_ref->{$k}->{'type'} eq 'boolean') {
490
491

            # convert strings to binary if need be
Christian Fibich's avatar
Christian Fibich committed
492
493
            if (!defined($orig)) {
                $log_func->($logger, "$k: \"undef\" is not a boolean value.");
494
                return 0;
Christian Fibich's avatar
Christian Fibich committed
495
            } elsif (lc($orig) eq 'true') {
496
                $orig = 1;
Christian Fibich's avatar
Christian Fibich committed
497
            } elsif (lc($orig) eq 'false') {
498
499
                $orig = 0;
            }
Christian Fibich's avatar
Christian Fibich committed
500
501
            if (($orig ne '0') && ($orig ne '1')) {
                $log_func->($logger, "$k: \"$orig\" does not look like a boolean value.");
502
503
504
505
                return 0;
            }

            # ensure proper boolean value, i.e. 0 or 1
Christian Fibich's avatar
Christian Fibich committed
506
507
508
509
            ${$v_ref} = (!!$orig) ? 1 : 0;
        } elsif ($map_ref->{$k}->{'type'} eq 'prob') {
            if ($orig !~ /^[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$/) {
                $log_func->($logger, "$k: $orig does not look like a positive real number.");
510
511
                return 0;
            }
Christian Fibich's avatar
Christian Fibich committed
512
        }
513

Christian Fibich's avatar
Christian Fibich committed
514
        $logger->trace("Converted value of $k (\"$orig\") to \"${$v_ref}\".") if (defined($orig) && $orig ne ${$v_ref});
515
    }
Christian Fibich's avatar
Christian Fibich committed
516

Christian Fibich's avatar
Christian Fibich committed
517
    if (defined($map_ref->{$k}->{'values'})) {
518
        my $values_ref = $map_ref->{$k}->{'values'};
Christian Fibich's avatar
Christian Fibich committed
519
        if (ref($values_ref) eq 'ARRAY') {
520

521
            # Look for given value in allowed values
Christian Fibich's avatar
Christian Fibich committed
522
523
            if (scalar(grep { $_ eq ${$v_ref} } @{$values_ref}) == 0) {
                $log_func->($logger, "$k: ${$v_ref} is not allowed. Allowed values are: " . join(", ", @{$map_ref->{$k}->{'values'}}));
524
525
                return 0;
            }
Christian Fibich's avatar
Christian Fibich committed
526
        } elsif (ref($values_ref) eq 'CODE') {
527
            my $new_vals = $values_ref->($old, $global_settings_ref);
528
            if (!defined($new_vals) || (ref($new_vals) eq "SCALAR" && ${$v_ref} ne $new_vals) || (ref($new_vals) eq "ARRAY" && scalar(grep { $_ eq ${$v_ref} } @{$new_vals}) == 0)) {
Christian Fibich's avatar
Christian Fibich committed
529
                $log_func->($logger, "$k: ${$v_ref} is not allowed.");
530
531
532
                return 0;
            }
        }
533
    }
534

535
    return 1;
536
537
}

538
# @function public _rename_import (%$map_ref, %$consts_ref)
539
540
541
542
543
544
545
546
547
# Rename and convert entries in consts_ref according to map_ref.
#
# This function takes a hash of FIJI Settings and converts its contents
# to the respective internal representation. This allows to use different
# names and value representations in the external file than within the
# implementation.
#
# \returns $consts_ref, or undef on errors
sub _rename_import {
548
    my $logger = get_logger("");
Christian Fibich's avatar
Christian Fibich committed
549
550
    my ($map_ref, $consts_ref) = @_;
    if (ref($consts_ref) ne 'HASH') {
551
552
553
        $logger->error("Parameter is not a reference to a hash (containing design constants).");
        return undef;
    }
554

555
556
    # Iterating over respective hash from FIJI.pm and rename the entries
    # to match our internal naming scheme.
Christian Fibich's avatar
Christian Fibich committed
557
    foreach my $k (keys(%{$map_ref})) {
558
        my $ini_name = $map_ref->{$k}->{'ini_name'};
Christian Fibich's avatar
Christian Fibich committed
559
        if (exists($consts_ref->{$ini_name}) && $ini_name ne $k) {
560
            $consts_ref->{$k} = $consts_ref->{$ini_name};
Christian Fibich's avatar
Christian Fibich committed
561
            $logger->trace(sprintf("Renaming Design setting %s -> %s (=%s).", $ini_name, $k, defined($consts_ref->{$ini_name}) ? $consts_ref->{$ini_name} : "undef"),);
562
563
564
            delete $consts_ref->{$ini_name};
        }
    }
Christian Fibich's avatar
Christian Fibich committed
565
566
    foreach my $entry_key (keys(%{$consts_ref})) {
        if (!exists($map_ref->{$entry_key})) {
567
            $logger->debug(sprintf("Deleting unknown setting %s = %s.", $entry_key, $consts_ref->{$entry_key}));
568
569
570
571
572
573
            delete $consts_ref->{$entry_key};
        }
    }

    return $consts_ref;
}
574

575
## @function public _sanitize_test (%$test_ref, $phase)
576
577
578
579
# @brief Convert and sanity check FIJI Settings.
#
# \param test_ref a reference to a hash containing FIJI Settings for a
#                single TEST.
Christian Fibich's avatar
Christian Fibich committed
580
# \param phase the current design phase
581
#
582
# \returns a reference to a list of diagnostic strings describing the reasons why sanitation failed (undef if successful)
583
sub _sanitize_test {
584
    my $logger = get_logger("");
585
    my ($global_settings_ref, $testpatmap, $test_ref, $phase) = @_;
Christian Fibich's avatar
Christian Fibich committed
586
    if (ref($test_ref) ne 'HASH') {
587
588
        my $msg = "Parameter is not a reference to a hash (containing TEST constants).";
        $logger->error($msg);
589
        return \[$msg];
590
    }
591

592
    return _validate_hashmap($global_settings_ref, $testpatmap, $test_ref, $phase);
593
594
595
}

sub _disabled_via_dependency ($$$) {
Christian Fibich's avatar
Christian Fibich committed
596
    my ($map_ref, $consts_ref, $k) = @_;
597
598
    my $dependency = $map_ref->{$k}->{'depends_on'};
    return defined($dependency) && !$consts_ref->{$dependency};
599
600
}

601
602
# \returns  a reference to a list of diagnostic strings describing the reasons why validation failed (undef if successful),
sub _validate_hashmap {
603
    my $logger = get_logger("");
604
    my ($global_settings_ref, $map_ref, $consts_ref, $phase) = @_;
Christian Fibich's avatar
Christian Fibich committed
605
    my @map_keys = keys(%{$map_ref});
606
    my $msg_ref;
Christian Fibich's avatar
Christian Fibich committed
607
    foreach my $entry_key (keys(%{$consts_ref})) {
608
609
        my $ini_name = $map_ref->{$entry_key}->{'ini_name'};

Christian Fibich's avatar
Christian Fibich committed
610
        if (!exists($map_ref->{$entry_key})) {
611
            $logger->debug(sprintf("Deleting unknown setting %s = %s.", $entry_key, $consts_ref->{$entry_key}));
612
613
614
615
            next;
        }

        @map_keys = grep { $_ ne $entry_key } @map_keys;    # mark constant key as done
Christian Fibich's avatar
Christian Fibich committed
616
617
        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'}));
618
        } elsif (!validate_value($global_settings_ref, $map_ref, $entry_key, \$consts_ref->{$entry_key})) {
Christian Fibich's avatar
Christian Fibich committed
619
            my $msg = sprintf("%s = %s is invalid.", $entry_key, !defined($consts_ref->{$entry_key}) ? "<undef>" : $consts_ref->{$entry_key});
620
            $logger->error($msg);
621
            push(@$msg_ref, $msg);
622
623
624
        }
    }

Christian Fibich's avatar
Christian Fibich committed
625
    if (!defined($phase)) {
626
627
628
629

        # 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.
630
        return $msg_ref;
631
632
    }

633
634
#     Iterate over the constants defined in FIJI.pm that apparently are
#     not contained in $consts_ref.
635
    foreach my $k (@map_keys) {
636
637
638
639
640
        my $dependency = $map_ref->{$k}->{'depends_on'};
        if (
            scalar(grep {$_ eq $phase} @{$map_ref->{$k}->{'phases_opt'}}) == 0 && # mandatory in current phase
            !_disabled_via_dependency($map_ref, $consts_ref, $k) # no dependency or dependency is enabled
           ) {
641
642
643
            my $msg = "$k is mandatory in phase $phase.";
            $logger->error($msg);
            push(@$msg_ref, $msg);
644
        }
645
    }
646
    return $msg_ref;
647
648
}

649
## @function public _sanitize_design (%$consts_ref, $phase)
650
651
652
653
654
655
656
657
658
659
660
661
662
663
# @brief Sanity check FIJI Design Settings.
#
# The function deals with sanity checks for the values
# themselves. It checks for the following conditions:
#
#   - NUM_TESTS: > 0
#   - REPEAT_OFFSET: 0 .. NUM_TESTS-1
#
# \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
#
# \returns The given hash with all constants required in the design
#          settings in sanitized form, or an error message.
sub _sanitize_design {
664
    my $logger = get_logger("");
665
    my ($global_settings_ref, $consts_ref, $phase) = @_;
Christian Fibich's avatar
Christian Fibich committed
666
    if (ref($consts_ref) ne 'HASH') {
667
668
        my $msg = "Parameter is not a reference to a hash (containing design constants).";
        $logger->error($msg);
669
        return $msg;
670
671
    }

672
    my $errors;
673
    # check for sane values
674
675
    my $msgs = _validate_hashmap($global_settings_ref, TESTCONSTMAP, $consts_ref, $phase);
    push(@$errors, "Could not validate Design Constants.\n".join("\n", @$msgs)) if (defined($msgs));
676

Christian Fibich's avatar
Christian Fibich committed
677
    if (($consts_ref->{'NUM_TESTS'} <= 0)) {
678
679
        my $msg = "NUM_TESTS is <= 0.";
        $logger->error($msg);
680
        push(@$errors, $msg);
681
682
    }

Christian Fibich's avatar
Christian Fibich committed
683
    if (($consts_ref->{'REPEAT_OFFSET'} < 0) || ($consts_ref->{'REPEAT_OFFSET'} >= $consts_ref->{'NUM_TESTS'})) {
684
        my $msg = "REPEAT_OFFSET (" . $consts_ref->{'REPEAT_OFFSET'} . ") is out of range [0 - NUM_TESTS].";
685
        $logger->error($msg);
686
        push(@$errors, $msg);
687
688
    }

689
690
    $errors = join("\n", @$errors) if defined($errors);
    return $errors;
691
692
}

693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
sub export_gate_level {
    my $logger = get_logger("");
    my ($self, $gl_file_name, $global_settings_ref, $num_repetitions, $last_test) = @_;

    my $glfd;

    $num_repetitions = 1 if (!defined $num_repetitions);
    $last_test       = @{$self->{'tests'}} if (!defined $last_test);
    
    if (!defined open($glfd, ">", $gl_file_name)) {
        return "Could not open file $gl_file_name for writing: $!";
    }

    print $glfd FIJI::Tests::VHDL::export_as_vhd_fiji_architecture($self,$last_test,$num_repetitions,$global_settings_ref);

    unless (close($glfd)) {
         return "Could not close file $gl_file_name: $!";
    }
    $logger->info("Exporting as VHDL architecture for gate-level simulation successful.");
    return undef;
}

715
sub export_tb {
716
    my $logger = get_logger("");
717
    my ($self, $tb_file_name, $global_settings_ref, $num_repetitions, $last_test) = @_;
718

719
720
721
    $num_repetitions = 1 if (!defined $num_repetitions);
    $last_test       = @{$self->{'tests'}} if (!defined $last_test);
    
722
723
724
725
726
    my $export_sim_model_map = {
        ".vhd" => \&FIJI::Tests::VHDL::export_as_vhd_fiji_architecture,
        ".sv"  => \&FIJI::Tests::VHDL::export_as_vhd_fiji_architecture,
    };

727
728
729
730
    my $export_controller_map = {
        ".vhd" => \&FIJI::Tests::VHDL::export_controller,
        ".sv"   => \&FIJI::Tests::SystemVerilog::export_controller,
    };
731

732
733
734
735
    my $export_fiu_map = {
        ".vhd" => \&FIJI::Tests::VHDL::export_fiu,
        ".sv"   => \&FIJI::Tests::SystemVerilog::export_fiu,
    };
736

737
738
739
    for my $ext (".vhd", ".sv") {
        my $tbfd;
        my $fn = $tb_file_name.$ext;
740

741
742
743
        if (!defined open($tbfd, ">", $fn)) {
            return "Could not open file $fn for writing: $!";
        }
744

745
746
747
        unless (defined $export_controller_map->{$ext}) {
            return "Controller export: Unknown HDL filetype \"$ext\"";
        }
748

749
        print $tbfd $export_controller_map->{$ext}->($self,$last_test,$num_repetitions,$global_settings_ref);
750

751
752
753
        unless (defined $export_fiu_map->{$ext}) {
            return "FIU export: Unknown HDL filetype \"$ext\"";
        }
Christian Fibich's avatar
Christian Fibich committed
754

755
756
        for (my $fi = 0; $fi < $global_settings_ref->{'design'}->{'FIU_NUM'}; $fi++) {
            print $tbfd $export_fiu_map->{$ext}->($fi,$global_settings_ref);
757
758
        }

759
760
        unless (close($tbfd)) {
             return "Could not close file $fn: $!";
761
762
        }
    }
763
    $logger->info("Exporting as templates for RTL simulation successful.");
764
    return undef;
765
766
}

Christian Fibich's avatar
Christian Fibich committed
767
## @method export_as_sim_script ($sim_file_name, $last_test, $num_repetitions, %$global_settings_ref)
768
769
# @brief generate a sim script from the current test file depending on repetitions and the last executed test
#
Christian Fibich's avatar
Christian Fibich committed
770
# @TODO: validate generated sim script files in Questa
771
#       makes use of random_force functionality or implement a different way of exporting sim scripts
Christian Fibich's avatar
Christian Fibich committed
772
773
774
775
776
#
# @param sim_file_name         the file to write the simulator script to
# @param last_test             index of the last test to be included in the script
# @param num_repetitions       the number of repetitions to be included in the script
# @param global_settings_ref   a reference to FIJI::Settings
777
sub export_as_sim_script {
778
    my $logger = get_logger("");
Christian Fibich's avatar
Christian Fibich committed
779
    my ($self, $sim_file_name, $last_test, $num_repetitions, $global_settings_ref) = @_;
780
    my $script_text = "# Sim script generated " . localtime . "\n#source random_force.tcl\n";
Christian Fibich's avatar
Christian Fibich committed
781

Christian Fibich's avatar
Christian Fibich committed
782
    if (ref($global_settings_ref) ne "FIJI::Settings") {
Christian Fibich's avatar
Christian Fibich committed
783
784
785
786
        $logger->error("Given settings are not of type FIJI::Settings");
        return 0;
    }

Christian Fibich's avatar
Christian Fibich committed
787
    my $msg;
788
    my $time   = 0;
Christian Fibich's avatar
Christian Fibich committed
789
790
    my $indent = "    ";

Christian Fibich's avatar
Christian Fibich committed
791
    open(my $sfd, ">", $sim_file_name);
792

Christian Fibich's avatar
Christian Fibich committed
793
    if (!defined $sfd) {
Christian Fibich's avatar
Christian Fibich committed
794
795
796
797
        $msg = "Could not open file $sim_file_name for writing: $!";
        return $msg;
    }

Christian Fibich's avatar
Christian Fibich committed
798
    # @FIXME translate in actual Questasim tcl syntax
799
    if ($global_settings_ref->{'design'}->{'RST_DUT_OUT_EN'} == 1) {
Christian Fibich's avatar
Christian Fibich committed
800
        $script_text .= <<"END_RST";
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
set testbench_name \$1;
set start    0;
set test     0;
set lfsrval  $global_settings_ref->{'design'}->{'LFSR_SEED'};

proc modbit signal {
     set curval [examine \$signal];

     # Always invert signal
     if {\$curval == "1"} {
        set retstring "0"
     } elseif {\$curval == "0"} {
        set retstring "1"
     } elseif {\$curval == "H"} {
        set retstring "L"
     } elseif {\$curval == "L"} {
        set retstring "H"
     }

    return \$retstring;
}

proc randbit mask {
    return expr {(\$lfsrval & \$mask) > 0};
}

827
when -label rst {\"\$testbench_name|$global_settings_ref->{'design'}->{'RST_DUT_OUT_NAME'}\" == not $global_settings_ref->{'design'}->{'RST_DUT_IN_ACT'} &&
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
                 \"\$testbench_name|$global_settings_ref->{'design'}->{'CLOCK_NET'}\" == 1 && \"\$start == 0\"} {
    set start 1;
    nowhen rst;
}

when -label lfsr {\"\$testbench_name|$global_settings_ref->{'design'}->{'CLOCK_NET'}'event && 
                    \$testbench_name|$global_settings_ref->{'design'}->{'CLOCK_NET'}' = '1'\" &&
                  \"\$start == 1\"} {
    if ( expr{ \$lfsrval & (1 << ($global_settings_ref->{'design'}->{'LFSR_WIDTH'}-1))} != 0 ) {
        set \$lfsrval [expr {((\$lfsrval ^ $global_settings_ref->{'design'}->{'LFSR_POLY'}) << 1) | 1}];
    } else {
        set \$lfsrval [expr {(\$lfsrval << 1)}];
    }
}
END_RST
    } else {
Christian Fibich's avatar
Christian Fibich committed
844
        $script_text .= "set start 1";
Christian Fibich's avatar
Christian Fibich committed
845
846
    }