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
# Copyright (C) 2017 Christian Fibich <fibich@technikum-wien.at>
# Copyright (C) 2017 Stefan Tauner <tauner@technikum-wien.at>
Christian Fibich's avatar
Christian Fibich committed
11
#
12
13
# 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
14
#
15
16
17
# 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
18
#
19
20
# See the LICENSE file for more details.
#-----------------------------------------------------------------------
Christian Fibich's avatar
Christian Fibich committed
21

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

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

29
30
31
32
33
34
35
36
37
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
38
39
40
use File::Spec;

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

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

48
## @function public new ($phase, %$global_settings_ref, $fiji_ini_file, %$existing_settings)
49
50
# Create a new settings instance.
#
Christian Fibich's avatar
Christian Fibich committed
51
52
53
# \param phase Tool flow phase the settings need to be compatible with.
# \param fiji_ini_file (optional) Path to the configuration file to read.
# \param existing_settings (optional) A reference to reuse and return
54
#        with values read from file. Any contained data will be cleared.
55
56
# \returns The new settings instance or undef in case of errors, and
#        a string describing the reason if it could not be created.
57
sub new {
58
    my $logger = get_logger("");
59
    my ($class, $phase, $global_settings_ref, $fiji_ini_file, $existing_settings, $num_tests) = @_;
60

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

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

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

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

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

92
93
        # create a default instance

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

98
        my $tkm_ref = TESTCONSTMAP;
Christian Fibich's avatar
Christian Fibich committed
99
        for my $k (keys(%{$tkm_ref})) {
100
101
102
103
104
105
106
107
            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;
108
        }
109

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

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

Christian Fibich's avatar
Christian Fibich committed
114
115
        if (defined($num_tests)) {
            for (my $i = 0 ; $i < $num_tests ; $i++) {
116
                my $thistest = {};
117
118
119
120
121
122
123
124
125
126
                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;
127
                }
128
                push @{$self->{'tests'}}, $thistest;
Christian Fibich's avatar
Christian Fibich committed
129
            }
130
131
        }
    }
132
    return ($self, $warn);
133
134
}

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

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

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

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

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

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

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

Christian Fibich's avatar
Christian Fibich committed
183
                        foreach my $k (keys(%{$test})) {
184
                            my $ini_name = $self->{'TESTPATMAP'}->{$k}->{'ini_name'};
Christian Fibich's avatar
Christian Fibich committed
185
                            if (!defined($test->{$k})) {
186
187
188
189
190
191
192
193
                                $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
194
                            _export_value($self->{'TESTPATMAP'}, $k, \$ini_test->{$ini_name});
Christian Fibich's avatar
Christian Fibich committed
195
                            $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"),);
196
                        }
Christian Fibich's avatar
Christian Fibich committed
197
                        $fiji_ini->set_block("TEST" . $test_cnt++, $ini_test);
198
199
200
                    }
                    next;
                }
201
            }
202
203
        } elsif ($key eq "filename") {
            next;
204
        }
205

206
207
208
209
210
211
212
        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
213
    foreach my $k (keys(%{$design_ref})) {
214
        my $ini_name = TESTCONSTMAP->{$k}->{'ini_name'};
Christian Fibich's avatar
Christian Fibich committed
215
        if (!defined($design_ref->{$k})) {
216
217
218
219
220
221
            $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};
222

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

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

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

239
## @method public read_settingsfile ($phase, $fiji_ini_file)
240
241
242
243
244
245
246
247
248
249
250
# @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
#
251
252
# \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.
253
sub _read_settingsfile {
254
    my $logger = get_logger("");
255
    my ($self, $phase, $global_settings_ref, $fiji_ini_file) = @_;
256
    my $fiji_ini;
257
    my $msg_ref;
258

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

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

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

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

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

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

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

    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
312
        if (!(%$test_ref)) {
313
314
315
            last;
        }

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

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

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

332
333
334
        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);
335
        $self->{'tests'}->[$test_num] = $test_ref;
336
        $test_num++;
337
        $logger->trace(sprintf("Read in $test_name from FIJI Tests file%s.", !defined($msg) ? "successfully" : ""));
338
339
    }

Christian Fibich's avatar
Christian Fibich committed
340
    if ($test_num == 0) {
341
342
343
344
345
        $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
346
    if (defined($design_ref->{'NUM_TESTS'}) && $design_ref->{'NUM_TESTS'} != $test_num) {
347
348
        my $msg = NUM_TESTS->{'ini_name'} . " does not match the numbers of TEST blocks found.";
        $logger->error($msg);
349
        push(@$msg_ref, $msg);
350
351
352
353
    } else {
        $design_ref->{'NUM_TESTS'} = $test_num;    # assume the best if TEST_NUM constant is not given
    }

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

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

368
    my $testpatmap = clone(TESTPATMAP);
369

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

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

380
        for (my $fiu_idx = 0 ; $fiu_idx < $global_settings_ref->{'design'}->{'FIU_NUM'} ; $fiu_idx++) {
381
382
383
384
385
386
387
            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)]
            };
388
389
390
        }
    }

391
    return $testpatmap;
392
393
}

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

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

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

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

            my $def;
            if (ref($map_ref->{$k}->{default}) eq 'CODE') {
422
                $def = $map_ref->{$k}->{'default'}->($self->{'settings'});
423
424
425
426
427
            } 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));
428
429
430
        }
    }
}
431

432
## @function public validate_value (%$map_ref, $k, $$v_ref, $old, $log_func)
433
434
#   Do validation (and conversation from external->internal representation)
#
435
#   \param global_settings_ref  reference to FIJI Settings mappings
436
#   \param map_ref  reference to FIJI Test mappings
437
438
439
440
#   \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
441
#                   (default is \&Log::Log4perl::Logger::trace)
442
sub validate_value {
443
    my $logger = get_logger("");
444
    my ($global_settings_ref, $map_ref, $k, $v_ref, $old, $log_func) = @_;
445
    $log_func = \&Log::Log4perl::Logger::trace if !defined($log_func);
Christian Fibich's avatar
Christian Fibich committed
446
    if (defined($map_ref->{$k}->{'type'})) {
447
448
        my $orig = ${$v_ref};

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

Christian Fibich's avatar
Christian Fibich committed
454
455
456
        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.");
457
458
459
460
461
                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
462
463
            if (${$v_ref} < 0) {
                $log_func->($logger, "$k: $orig is negative.");
464
465
                return 0;
            }
Christian Fibich's avatar
Christian Fibich committed
466
        } elsif ($map_ref->{$k}->{'type'} eq 'natural'
467
            || $map_ref->{$k}->{'type'} eq 'min_duration'
Christian Fibich's avatar
Christian Fibich committed
468
            || $map_ref->{$k}->{'type'} eq 'max_duration')
469
470
        {
            # Match hexadecimal, binary, octal and decimal numbers (the latter also in scientific notation)
Christian Fibich's avatar
Christian Fibich committed
471
472
            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.");
473
474
475
476
477
                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
478
479
            if (!looks_like_number(${$v_ref})) {
                $log_func->($logger, "$k: $orig does not look like a number.");
480
481
482
483
                return 0;
            }

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

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

            # ensure proper boolean value, i.e. 0 or 1
Christian Fibich's avatar
Christian Fibich committed
505
506
507
508
            ${$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.");
509
510
                return 0;
            }
Christian Fibich's avatar
Christian Fibich committed
511
        }
512

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

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

520
            # Look for given value in allowed values
Christian Fibich's avatar
Christian Fibich committed
521
522
            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'}}));
523
524
                return 0;
            }
Christian Fibich's avatar
Christian Fibich committed
525
        } elsif (ref($values_ref) eq 'CODE') {
526
            my $new_vals = $values_ref->($old, $global_settings_ref);
527
            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
528
                $log_func->($logger, "$k: ${$v_ref} is not allowed.");
529
530
531
                return 0;
            }
        }
532
    }
533

534
    return 1;
535
536
}

537
# @function public _rename_import (%$map_ref, %$consts_ref)
538
539
540
541
542
543
544
545
546
# 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 {
547
    my $logger = get_logger("");
Christian Fibich's avatar
Christian Fibich committed
548
549
    my ($map_ref, $consts_ref) = @_;
    if (ref($consts_ref) ne 'HASH') {
550
551
552
        $logger->error("Parameter is not a reference to a hash (containing design constants).");
        return undef;
    }
553

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

    return $consts_ref;
}
573

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

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

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

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

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

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

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

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

632
633
#     Iterate over the constants defined in FIJI.pm that apparently are
#     not contained in $consts_ref.
634
    foreach my $k (@map_keys) {
635
636
637
638
639
        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
           ) {
640
641
642
            my $msg = "$k is mandatory in phase $phase.";
            $logger->error($msg);
            push(@$msg_ref, $msg);
643
        }
644
    }
645
    return $msg_ref;
646
647
}

648
## @function public _sanitize_design (%$consts_ref, $phase)
649
650
651
652
653
654
655
656
657
658
659
660
661
662
# @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 {
663
    my $logger = get_logger("");
664
    my ($global_settings_ref, $consts_ref, $phase) = @_;
Christian Fibich's avatar
Christian Fibich committed
665
    if (ref($consts_ref) ne 'HASH') {
666
667
        my $msg = "Parameter is not a reference to a hash (containing design constants).";
        $logger->error($msg);
668
        return $msg;
669
670
    }

671
    my $errors;
672
    # check for sane values
673
674
    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));
675

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

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

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

692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
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;
}

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

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

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

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

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

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

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

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

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

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

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

Christian Fibich's avatar
Christian Fibich committed
766
## @method export_as_sim_script ($sim_file_name, $last_test, $num_repetitions, %$global_settings_ref)
767
768
# @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
769
# @TODO: validate generated sim script files in Questa
770
#       makes use of random_force functionality or implement a different way of exporting sim scripts
Christian Fibich's avatar
Christian Fibich committed
771
772
773
774
775
#
# @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
776
sub export_as_sim_script {
777
    my $logger = get_logger("");
Christian Fibich's avatar
Christian Fibich committed
778
    my ($self, $sim_file_name, $last_test, $num_repetitions, $global_settings_ref) = @_;
779
    my $script_text = "# Sim script generated " . localtime . "\n#source random_force.tcl\n";
Christian Fibich's avatar
Christian Fibich committed
780

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

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

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

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

Christian Fibich's avatar
Christian Fibich committed
797
    # @FIXME translate in actual Questasim tcl syntax
798
    if ($global_settings_ref->{'design'}->{'RST_DUT_OUT_EN'} == 1) {
Christian Fibich's avatar
Christian Fibich committed
799
        $script_text .= <<"END_RST";
800
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
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};
}

826
when -label rst {\"\$testbench_name|$global_settings_ref->{'design'}->{'RST_DUT_OUT_NAME'}\" == not $global_settings_ref->{'design'}->{'RST_DUT_IN_ACT'} &&
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
                 \"\$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
843
        $script_text .= "set start 1";
Christian Fibich's avatar
Christian Fibich committed
844
845
    }

846
847
    my $absolute_test_index = 0;