Tests.pm 41.8 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 Test files.
#-------------------------------------------------------------------------------

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

## @class FIJI::Tests
#
Christian Fibich's avatar
Christian Fibich committed
21
# @brief Contains helper functions to deal with FIJI Test files.
22
23
24
25
26
27
28
29
30
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
31
32
33
use File::Spec;

use FIJI::ConfigSorted;
34
use FIJI::Settings;
35
36
use FIJI::Tests::VHDL;
use FIJI::Tests::SystemVerilog;
37
use FIJI qw(:all);
Christian Fibich's avatar
Christian Fibich committed
38

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

41
## @function public new ($phase, %$global_settings_ref, $fiji_ini_file, %$existing_settings)
42
43
# Create a new settings instance.
#
Christian Fibich's avatar
Christian Fibich committed
44
45
46
# \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
47
48
49
#        with values read from file. Any contained data will be cleared.
# \returns The new settings instance or a string describing the reason
#          why it could not be created.
50
sub new {
51
    my $logger = get_logger("");
52
    my ($class, $phase, $global_settings_ref, $fiji_ini_file, $existing_settings, $num_tests) = @_;
53

54
    my $self = {};
Christian Fibich's avatar
Christian Fibich committed
55

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

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

78
79
        if (!defined($self)) {
            return (undef, $warn);
80
        }
81
        $self->{'filename'} = $fiji_ini_file;
82

83
    } else {
Christian Fibich's avatar
Christian Fibich committed
84

85
86
        # create a default instance

87
88
89
90
        $self->{'design'} = {};
        $self->_set_defaults(TESTCONSTMAP, $self->{'design'}, $phase);
        $self->{'design'}->{'NUM_TESTS'} = $num_tests;

91
        my $tkm_ref = TESTCONSTMAP;
Christian Fibich's avatar
Christian Fibich committed
92
        for my $k (keys(%{$tkm_ref})) {
93
94
95
96
97
98
99
100
            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;
101
        }
102

103
        $self->{'TESTPATMAP'}   = $self->_generate_testpatmap($global_settings_ref);
104

105
        $self->{'tests'} = [];
106

Christian Fibich's avatar
Christian Fibich committed
107
108
        if (defined($num_tests)) {
            for (my $i = 0 ; $i < $num_tests ; $i++) {
109
                my $thistest = {};
110
111
112
113
114
115
116
117
118
119
                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;
120
                }
121
                push @{$self->{'tests'}}, $thistest;
Christian Fibich's avatar
Christian Fibich committed
122
            }
123
124
        }
    }
125
    return ($self, $warn);
126
127
}

128
sub _export_value {
129
    my $logger = get_logger("");
Christian Fibich's avatar
Christian Fibich committed
130
    my ($map_ref, $k, $v_ref) = @_;
131

Christian Fibich's avatar
Christian Fibich committed
132
    if (defined($map_ref->{$k}->{'type'})) {
133
        my $orig = ${$v_ref};
Christian Fibich's avatar
Christian Fibich committed
134
135
        if ($map_ref->{$k}->{'type'} eq 'hexadecimal') {
            ${$v_ref} = sprintf("0x%x", $orig);
136

137
138
            # } elsif ($map_ref->{$k}->{'type'} eq 'natural') {
            # } elsif ($map_ref->{$k}->{'type'} eq 'boolean') {
Christian Fibich's avatar
Christian Fibich committed
139
            $logger->trace("Converted value of $k (\"$orig\") to \"${$v_ref}\".") if ($orig ne ${$v_ref});
140
141
142
143
        }

        # } elsif (defined($map_ref->{$k}->{'values'})) {
    }
144
145
}

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

Christian Fibich's avatar
Christian Fibich committed
157
    my $fiji_ini = new FIJI::ConfigSorted(syntax => 'ini');
158
159
160
    my $design_ref;
    my $test_cnt = 0;

Christian Fibich's avatar
Christian Fibich committed
161
    foreach my $key (keys %{$self}) {
162
        my $val = $self->{$key};
Christian Fibich's avatar
Christian Fibich committed
163
164
165
        if (ref(\$val) eq "REF") {
            if (ref($val) eq "HASH") {
                if ($key eq "design") {
166
167
                    $design_ref = $val;
                    next;
168
                } elsif ($key eq "TESTPATMAP") {
169
170
                    next;
                }
Christian Fibich's avatar
Christian Fibich committed
171
172
173
            } elsif (ref($val) eq "ARRAY") {
                if ($key eq "tests") {
                    foreach my $test (@{$val}) {
174
175
                        my $ini_test;

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

199
200
201
202
203
204
205
        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
206
    foreach my $k (keys(%{$design_ref})) {
207
        my $ini_name = TESTCONSTMAP->{$k}->{'ini_name'};
Christian Fibich's avatar
Christian Fibich committed
208
        if (!defined($design_ref->{$k})) {
209
210
211
212
213
214
            $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};
215

216
        # Convert value to external representation
Christian Fibich's avatar
Christian Fibich committed
217
218
        _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"),);
219
    }
Christian Fibich's avatar
Christian Fibich committed
220
    $fiji_ini->set_block("CONSTS", $ini_design);
221

Christian Fibich's avatar
Christian Fibich committed
222
    if (!defined($fiji_ini->write($fiji_ini_file))) {
Christian Fibich's avatar
Christian Fibich committed
223
        my $err = FIJI::ConfigSorted->error();
224
225
226
        $logger->error($err);
        return $err;
    }
227
228

    $self->{'filename'} = $fiji_ini_file;
229
230
    return undef;
}
231

232
## @method public read_settingsfile ($phase, $fiji_ini_file)
233
234
235
236
237
238
239
240
241
242
243
244
# @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
#
# \returns a reference to the hash containing the read constants.
245
sub _read_settingsfile {
246
    my $logger = get_logger("");
247
    my ($self, $phase, $global_settings_ref, $fiji_ini_file) = @_;
248
    my $fiji_ini;
249
    my $msg_ref;
250

Christian Fibich's avatar
Christian Fibich committed
251
    eval { $fiji_ini = new FIJI::ConfigSorted($fiji_ini_file) };    # pesky library tries to die on syntax errors
Christian Fibich's avatar
Christian Fibich committed
252
    if (!defined($fiji_ini)) {
Christian Fibich's avatar
Christian Fibich committed
253
        my $submsg = defined($@) ? $@ : FIJI::ConfigSorted->error();
Christian Fibich's avatar
Christian Fibich committed
254
        if (length($submsg) == 0) {
255
256
257
258
            $submsg = "Empty file?";
        }
        my $msg = "Could not read config file \"$fiji_ini_file\": $submsg";
        $logger->error($msg);
259
        push(@$msg_ref, $msg);
260
    }
261

262
    my $design_ref = $fiji_ini->get_block("CONSTS");
Christian Fibich's avatar
Christian Fibich committed
263
    if (!(%$design_ref)) {
264
265
        my $msg = "Could not fetch CONSTS block from config file \"$fiji_ini_file\"";
        $logger->error($msg);
266
        push(@$msg_ref, $msg);
267
    }
268
    $self->_set_defaults(TESTCONSTMAP, $design_ref, $phase);
269

Christian Fibich's avatar
Christian Fibich committed
270
271
    $design_ref = _rename_import(TESTCONSTMAP, $design_ref);
    if (!defined($design_ref)) {
272
273
        my $msg = "Design constants do not match the FIJI Tests naming scheme.";
        $logger->error($msg);
274
        push(@$msg_ref, $msg);
275
    }
276

277
    # Reuse the shallow hull of the existing Tests instance
278

279
280
281
    # Clear the hash
    for (keys %$self) {
        delete $self->{$_};
282
283
    }

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

290
    my $warn;
291
    # sanitize and validate read design constants
292
293
294
295
    ($design_ref, $warn) = _sanitize_design($global_settings_ref, $design_ref, $phase);
    if (defined($warn)) {
        push(@$msg_ref, @$warn);
    }
Christian Fibich's avatar
Christian Fibich committed
296
    if (!ref($design_ref)) {
297
        return (undef, $msg_ref);
298
299
300
301
302
303
304
305
    }

    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
306
        if (!(%$test_ref)) {
307
308
309
            last;
        }

310
311
        $test_ref = _rename_import($self->{'TESTPATMAP'}, $test_ref);
        if (!defined($test_ref)) {
312
313
            my $msg = "Test constants of $test_name do not match the FIJI Tests naming scheme.";
            $logger->error($msg);
314
            push(@$msg_ref, $msg);
315
316
317
        }

        my $tmp_test = {};
318
        $self->_set_defaults($self->{'TESTPATMAP'}, $tmp_test, $phase);
319
320

        # overwrite default entries
Christian Fibich's avatar
Christian Fibich committed
321
        foreach my $k (keys(%{$test_ref})) {
322
323
324
325
            $tmp_test->{$k} = $test_ref->{$k};
        }
        $test_ref = $tmp_test;

326
327
        my $warn;
        ($test_ref, $warn) = _sanitize_test($global_settings_ref, $self->{'TESTPATMAP'}, $test_ref, $phase);
Christian Fibich's avatar
Christian Fibich committed
328
        if (!ref($test_ref)) {
329
330
            my $msg = "(Some) constants for $test_name in Tests Settings are invalid.";
            $logger->error($msg);
331
            push(@$msg_ref, unshift(@$warn, $msg));
332
        }
333
        $self->{'tests'}->[$test_num] = $test_ref;
334
335
336
337
        $test_num++;
        $logger->trace("Read in $test_name from FIJI Tests file successfully.");
    }

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

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

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

366
    my $testpatmap = clone(TESTPATMAP);
367

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

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

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

389
    return $testpatmap;
390
391
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

532
    return 1;
533
534
}

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

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

    return $consts_ref;
}
571

572
## @function public _sanitize_test (%$test_ref, $phase)
573
574
575
576
# @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
577
# \param phase the current design phase
578
579
580
#
# \returns A new hash with all constants required in the TEST settings
#          in sanitized form, or undef on errors.
581
sub _sanitize_test {
582
    my $logger = get_logger("");
583
    my ($global_settings_ref, $testpatmap, $test_ref, $phase) = @_;
Christian Fibich's avatar
Christian Fibich committed
584
    if (ref($test_ref) ne 'HASH') {
585
586
        my $msg = "Parameter is not a reference to a hash (containing TEST constants).";
        $logger->error($msg);
587
        return (undef, $msg);
588
    }
589

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

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

599
sub _validate_hashmap ($$$;$) {
600
    my $logger = get_logger("");
601
    my ($global_settings_ref, $map_ref, $consts_ref, $phase) = @_;
Christian Fibich's avatar
Christian Fibich committed
602
    my @map_keys = keys(%{$map_ref});
603
    my $msg_ref;
Christian Fibich's avatar
Christian Fibich committed
604
    foreach my $entry_key (keys(%{$consts_ref})) {
605
606
        my $ini_name = $map_ref->{$entry_key}->{'ini_name'};

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

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

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

        # 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.
627
        return ($consts_ref, $msg_ref);
628
629
    }

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

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

669
    my $warn;
670
    # check for sane values
671
    ($consts_ref, $warn) = _validate_hashmap($global_settings_ref, TESTCONSTMAP, $consts_ref, $phase);
Christian Fibich's avatar
Christian Fibich committed
672
    if (!ref($consts_ref)) {
673
        my $msg = "Could not validate Design Constants.";
674
675
        $logger->error("$msg $consts_ref");
        return (undef, "$msg $consts_ref");
676
677
    }

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

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

690
    return ($consts_ref, $warn);
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
    }

847
848
    my $absolute_test_index = 0;

Christian Fibich's avatar
Christian Fibich committed
849
    for (my $ri = 0 ; $ri <= $num_repetitions ; $ri++) {
Christian Fibich's avatar
Christian Fibich committed
850
        $script_text .= "#\n# Repetition $ri\n#\n";
Christian Fibich's avatar
Christian Fibich committed
851
852
853
        my $start = ($ri == 0) ? 0 : $self->{'design'}->{'REPEAT_OFFSET'};
        my $end = ($ri == $num_repetitions) ? $last_test : ($self->{'design'}->{'NUM_TESTS'} - 1);
        for (my $ti = $start ; $ti <= $end ; $ti++) {
854
855

            $script_text .= "\n#\n# Test $ri:$ti\n#\n";
Christian Fibich's avatar
Christian Fibich committed
856
            $script_text .= "when -label test_$absolute_test_index \"\$test = $absolute_test_index" . (($absolute_test_index == 0) ? " && \$start = 1" : "") . "\" {\n";
857
858

            if ($absolute_test_index > 0) {
Christian Fibich's avatar
Christian Fibich committed
859
860
861
862
863
864
                $script_text .= $indent . "eval {nowhen test_" .    ($absolute_test_index - 1) . "}\n";
                $script_text .= $indent . "eval {nowhen dutrst_" .  ($absolute_test_index - 1) . "}\n";
                $script_text .= $indent . "eval {nowhen trigger_" . ($absolute_test_index - 1) . "}\n";
                $script_text .= $indent . "eval {nowhen action_" .  ($absolute_test_index - 1) . "_1}\n";
                $script_text .= $indent . "eval {nowhen action_" .  ($absolute_test_index - 1) . "_2}\n";
                $script_text .= $indent . "eval {nowhen clk}\n";