Tests.pm 42.5 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
# 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
7
#
8
9
# 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
10
#
11
12
13
# 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
14
#
15
16
# See the LICENSE file for more details.
#-----------------------------------------------------------------------
Christian Fibich's avatar
Christian Fibich committed
17

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

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

25
26
27
28
29
30
31
32
33
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
34
35
36
use File::Spec;

use FIJI::ConfigSorted;
37
use FIJI::Settings;
38
39
use FIJI::Tests::VHDL;
use FIJI::Tests::SystemVerilog;
40
use FIJI qw(:all);
Christian Fibich's avatar
Christian Fibich committed
41

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

44
## @function public new ($phase, %$global_settings_ref, $fiji_ini_file, %$existing_settings)
45
46
# Create a new settings instance.
#
Christian Fibich's avatar
Christian Fibich committed
47
48
49
# \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
50
51
52
#        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.
53
sub new {
54
    my $logger = get_logger("");
55
    my ($class, $phase, $global_settings_ref, $fiji_ini_file, $existing_settings, $num_tests) = @_;
56

57
    my $self = {};
Christian Fibich's avatar
Christian Fibich committed
58

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

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

81
82
        if (!defined($self)) {
            return (undef, $warn);
83
        }
84
        $self->{'filename'} = $fiji_ini_file;
85

86
    } else {
Christian Fibich's avatar
Christian Fibich committed
87

88
89
        # create a default instance

90
        $self->{'design'} = {};
91
        $self->_set_defaults(TESTCONSTMAP, $self->{'design'}, $phase, $global_settings_ref);
92
93
        $self->{'design'}->{'NUM_TESTS'} = $num_tests;

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

106
        $self->{'TESTPATMAP'}   = $self->_generate_testpatmap($global_settings_ref);
107

108
        $self->{'tests'} = [];
109

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

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

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

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

        # } elsif (defined($map_ref->{$k}->{'values'})) {
    }
147
148
}

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

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

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

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

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

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

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

    $self->{'filename'} = $fiji_ini_file;
232
233
    return undef;
}
234

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

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

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

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

280
    # Reuse the shallow hull of the existing Tests instance
281

282
283
284
    # Clear the hash
    for (keys %$self) {
        delete $self->{$_};
285
286
    }

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

293
    my $warn;
294
    # sanitize and validate read design constants
295
296
297
298
    ($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
299
    if (!ref($design_ref)) {
300
        return (undef, $msg_ref);
301
302
303
304
305
306
307
308
    }

    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
309
        if (!(%$test_ref)) {
310
311
312
            last;
        }

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

        my $tmp_test = {};
321
        $self->_set_defaults($self->{'TESTPATMAP'}, $tmp_test, $phase, $global_settings_ref);
322
323

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

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

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
356
357
358
359
360
    my $i = $test_num;
    for (; $i < $global_settings_ref->{'design'}->{'FIU_NUM'}; $i++) {
        my $tmp_test = {};
        $self->_set_defaults($self->{'TESTPATMAP'}, $tmp_test, $phase, $global_settings_ref);
        $self->{'tests'}->[$i] = $tmp_test;
    }
361
    $logger->info("Successfully read in design constants and $test_num TEST definitions from FIJI Tests file \"$fiji_ini_file\".");
362
    splice(@{$self->{'tests'}}, $i);
363
    return ($self, $msg_ref);
364
365
}

366
367
368
## @method private _generate_testpatmap ()
# @brief generates tailored TESTPATMAP for validating test patterns
#
369
# clones the original TESTPATMAP and adds FIU_x_FAULT_y keys
370
371
#
# @returns the tailored TESTPATMAP
372
sub _generate_testpatmap {
373
    my ($self, $global_settings_ref) = @_;
374

375
    my $testpatmap = clone(TESTPATMAP);
376

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

379
        my $prop_name = "TIMER_VALUE_${pattern_idx}";
380
        $testpatmap->{$prop_name} = {
381
            ini_name   => $prop_name,
382
            default    => $global_settings_ref->default_timer_value(),
383
            type       => "natural",
384
            phases_opt => [qw(manual)]
385
386
        };

387
        for (my $fiu_idx = 0 ; $fiu_idx < $global_settings_ref->{'design'}->{'FIU_NUM'} ; $fiu_idx++) {
388
389
390
391
392
393
394
            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)]
            };
395
396
397
        }
    }

398
    return $testpatmap;
399
400
}

401
## @method public set_test_defaults (%$test_ref)
402
403
# @brief Overwrite existing fields (if defined) with defaults defined in FIJI.pm.
#
404
sub set_test_defaults {
Christian Fibich's avatar
Christian Fibich committed
405
    my ($self, $consts_ref) = @_;
406
    return $self->_set_defaults($self->{'TESTPATMAP'}, $consts_ref);
407
408
}

409
## @method public _set_defaults (%$map_ref, %$consts_ref)
410
411
# @brief Set defaults according to FIJI.pm.
sub _set_defaults {
412
    my $logger = get_logger("");
413
    my ($self, $map_ref, $consts_ref, $phase, $global_settings_ref) = @_;
Christian Fibich's avatar
Christian Fibich committed
414
415
    foreach my $k (keys(%{$map_ref})) {
        if (exists($map_ref->{$k}->{'default'})) {
416
417

            # Set default only if it is not mandatory in the given phase.
418
419
            # In GUI mode everything is optional... the user can modify it before starting any test.
            if (defined($phase) && (scalar(grep { $_ eq $phase } @{$map_ref->{$k}->{'phases_opt'}}) == 0 && $phase ne "GUI")) {
420
421
                next;
            }
422

423
            # Also, do not overwrite existing values
Christian Fibich's avatar
Christian Fibich committed
424
            if (defined($consts_ref->{$k})) {
425
426
                next;
            }
427
428
429

            my $def;
            if (ref($map_ref->{$k}->{default}) eq 'CODE') {
430
                $def = $map_ref->{$k}->{'default'}->($global_settings_ref);
431
432
433
434
435
            } 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));
436
437
438
        }
    }
}
439

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

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

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

            # Check for natural value range. Should never trigger due to the regex above.
Christian Fibich's avatar
Christian Fibich committed
492
493
            if (${$v_ref} < 0) {
                $log_func->($logger, "$k: $orig is negative.");
494
495
                return 0;
            }
Christian Fibich's avatar
Christian Fibich committed
496
        } elsif ($map_ref->{$k}->{'type'} eq 'boolean') {
497
498

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

            # ensure proper boolean value, i.e. 0 or 1
Christian Fibich's avatar
Christian Fibich committed
513
514
515
516
            ${$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.");
517
518
                return 0;
            }
Christian Fibich's avatar
Christian Fibich committed
519
        }
520

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

Christian Fibich's avatar
Christian Fibich committed
524
    if (defined($map_ref->{$k}->{'values'})) {
525
        my $values_ref = $map_ref->{$k}->{'values'};
Christian Fibich's avatar
Christian Fibich committed
526
        if (ref($values_ref) eq 'ARRAY') {
527

528
            # Look for given value in allowed values
Christian Fibich's avatar
Christian Fibich committed
529
530
            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'}}));
531
532
                return 0;
            }
Christian Fibich's avatar
Christian Fibich committed
533
        } elsif (ref($values_ref) eq 'CODE') {
534
            my $new_vals = $values_ref->($old, $global_settings_ref);
535
            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
536
                $log_func->($logger, "$k: ${$v_ref} is not allowed.");
537
538
539
                return 0;
            }
        }
540
    }
541

542
    return 1;
543
544
}

545
# @function public _rename_import (%$map_ref, %$consts_ref)
546
547
548
549
550
551
552
553
554
# 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 {
555
    my $logger = get_logger("");
Christian Fibich's avatar
Christian Fibich committed
556
557
    my ($map_ref, $consts_ref) = @_;
    if (ref($consts_ref) ne 'HASH') {
558
559
560
        $logger->error("Parameter is not a reference to a hash (containing design constants).");
        return undef;
    }
561

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

    return $consts_ref;
}
581

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

600
    return _validate_hashmap($global_settings_ref, $testpatmap, $test_ref, $phase);
601
602
603
}

sub _disabled_via_dependency ($$$) {
Christian Fibich's avatar
Christian Fibich committed
604
    my ($map_ref, $consts_ref, $k) = @_;
605
606
    my $dependency = $map_ref->{$k}->{'depends_on'};
    return defined($dependency) && !$consts_ref->{$dependency};
607
608
}

609
sub _validate_hashmap ($$$;$) {
610
    my $logger = get_logger("");
611
    my ($global_settings_ref, $map_ref, $consts_ref, $phase) = @_;
Christian Fibich's avatar
Christian Fibich committed
612
    my @map_keys = keys(%{$map_ref});
613
    my $msg_ref;
Christian Fibich's avatar
Christian Fibich committed
614
    foreach my $entry_key (keys(%{$consts_ref})) {
615
616
        my $ini_name = $map_ref->{$entry_key}->{'ini_name'};

Christian Fibich's avatar
Christian Fibich committed
617
        if (!exists($map_ref->{$entry_key})) {
618
            $logger->debug(sprintf("Deleting unknown setting %s = %s.", $entry_key, $consts_ref->{$entry_key}));
619
620
621
622
            next;
        }

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

Christian Fibich's avatar
Christian Fibich committed
632
    if (!defined($phase)) {
633
634
635
636

        # 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.
637
        return ($consts_ref, $msg_ref);
638
639
    }

640
641
#     Iterate over the constants defined in FIJI.pm that apparently are
#     not contained in $consts_ref.
642
    foreach my $k (@map_keys) {
643
644
645
646
647
        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
           ) {
648
649
650
            my $msg = "$k is mandatory in phase $phase.";
            $logger->error($msg);
            push(@$msg_ref, $msg);
651
        }
652
653
    }
    return ($consts_ref, $msg_ref);
654
655
}

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

679
    my $warn;
680
    # check for sane values
681
    ($consts_ref, $warn) = _validate_hashmap($global_settings_ref, TESTCONSTMAP, $consts_ref, $phase);
Christian Fibich's avatar
Christian Fibich committed
682
    if (!ref($consts_ref)) {
683
        my $msg = "Could not validate Design Constants.";
684
685
        $logger->error("$msg $consts_ref");
        return (undef, "$msg $consts_ref");
686
687
    }

Christian Fibich's avatar
Christian Fibich committed
688
    if (($consts_ref->{'NUM_TESTS'} <= 0)) {
689
690
        my $msg = "NUM_TESTS is <= 0.";
        $logger->error($msg);
691
        push(@$warn, $msg);
692
693
    }

Christian Fibich's avatar
Christian Fibich committed
694
    if (($consts_ref->{'REPEAT_OFFSET'} < 0) || ($consts_ref->{'REPEAT_OFFSET'} >= $consts_ref->{'NUM_TESTS'})) {
695
        my $msg = "REPEAT_OFFSET (" . $consts_ref->{'REPEAT_OFFSET'} . ") is out of range [0 - NUM_TESTS].";
696
        $logger->error($msg);
697
        push(@$warn, $msg);
698
699
    }

700
    return ($consts_ref, $warn);
701
702
}

703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
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;
}

725
sub export_tb {
726
    my $logger = get_logger("");
727
    my ($self, $tb_file_name, $global_settings_ref, $num_repetitions, $last_test) = @_;
728

729
730
731
    $num_repetitions = 1 if (!defined $num_repetitions);
    $last_test       = @{$self->{'tests'}} if (!defined $last_test);
    
732
733
734
735
736
    my $export_sim_model_map = {
        ".vhd" => \&FIJI::Tests::VHDL::export_as_vhd_fiji_architecture,
        ".sv"  => \&FIJI::Tests::VHDL::export_as_vhd_fiji_architecture,
    };

737
738
739
740
    my $export_controller_map = {
        ".vhd" => \&FIJI::Tests::VHDL::export_controller,
        ".sv"   => \&FIJI::Tests::SystemVerilog::export_controller,
    };
741

742
743
744
745
    my $export_fiu_map = {
        ".vhd" => \&FIJI::Tests::VHDL::export_fiu,
        ".sv"   => \&FIJI::Tests::SystemVerilog::export_fiu,
    };
746

747
748
749
    for my $ext (".vhd", ".sv") {
        my $tbfd;
        my $fn = $tb_file_name.$ext;
750

751
752
753
        if (!defined open($tbfd, ">", $fn)) {
            return "Could not open file $fn for writing: $!";
        }
754

755
756
757
        unless (defined $export_controller_map->{$ext}) {
            return "Controller export: Unknown HDL filetype \"$ext\"";
        }
758

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

761
762
763
        unless (defined $export_fiu_map->{$ext}) {
            return "FIU export: Unknown HDL filetype \"$ext\"";
        }
Christian Fibich's avatar
Christian Fibich committed
764

765
766
        for (my $fi = 0; $fi < $global_settings_ref->{'design'}->{'FIU_NUM'}; $fi++) {
            print $tbfd $export_fiu_map->{$ext}->($fi,$global_settings_ref);
767
768
        }

769
770
        unless (close($tbfd)) {
             return "Could not close file $fn: $!";
771
772
        }
    }
773
    $logger->info("Exporting as templates for RTL simulation successful.");
774
    return undef;
775
776
}

Christian Fibich's avatar
Christian Fibich committed
777
## @method export_as_sim_script ($sim_file_name, $last_test, $num_repetitions, %$global_settings_ref)
778
779
# @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
780
# @TODO: validate generated sim script files in Questa
781
#       makes use of random_force functionality or implement a different way of exporting sim scripts
Christian Fibich's avatar
Christian Fibich committed
782
783
784
785
786
#
# @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
787
sub export_as_sim_script {
788
    my $logger = get_logger("");
Christian Fibich's avatar
Christian Fibich committed
789
    my ($self, $sim_file_name, $last_test, $num_repetitions, $global_settings_ref) = @_;
790
    my $script_text = "# Sim script generated " . localtime . "\n#source random_force.tcl\n";
Christian Fibich's avatar
Christian Fibich committed
791

Christian Fibich's avatar
Christian Fibich committed
792
    if (ref($global_settings_ref) ne "FIJI::Settings") {
Christian Fibich's avatar
Christian Fibich committed
793
794
795
796
        $logger->error("Given settings are not of type FIJI::Settings");
        return 0;
    }

Christian Fibich's avatar
Christian Fibich committed
797
    my $msg;
798
    my $time   = 0;
Christian Fibich's avatar
Christian Fibich committed
799
800
    my $indent = "    ";

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

Christian Fibich's avatar
Christian Fibich committed
803
    if (!defined $sfd) {
Christian Fibich's avatar
Christian Fibich committed
804
805
806
807
        $msg = "Could not open file $sim_file_name for writing: $!";
        return $msg;
    }

Christian Fibich's avatar
Christian Fibich committed
808
    # @FIXME translate in actual Questasim tcl syntax
809
    if ($global_settings_ref->{'design'}->{'RST_DUT_OUT_EN'} == 1) {
Christian Fibich's avatar
Christian Fibich committed
810
        $script_text .= <<"END_RST";
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
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};
}

837
when -label rst {\"\$testbench_name|$global_settings_ref->{'design'}->{'RST_DUT_OUT_NAME'}\" == not $global_settings_ref->{'design'}->{'RST_DUT_IN_ACT'} &&
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
                 \"\$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 {