Commit d5813503 authored by Stefan Tauner's avatar Stefan Tauner
Browse files

fiji_tests_viewer+Tests: revamp widget activation settings and optional widgets

parent 8605fe62
......@@ -133,11 +133,12 @@ use constant LFSR_POLY_CHOICES => {
# Fields:
# - ini_name key name in FIJI Settings file
# - unit (optional) physical unit
# - type (optional) enables type-specific conversions and tests:
# - type enables type-specific conversions and tests:
# - natural: values must be oct, hex, binary strings looking like a real number.
# - hexadecimal: values must be hexadecimal numbers.
# - boolean: will be convert to a truth value by Perl semantics
# - net: name of a net from the input netlist
# FIXME: complete docs
# - values (optional) used to restrict valid values. Can be...
# - an array reference listing all valid values (emulates an enum)
# - a code reference to a function that gets the new and old values and
......@@ -764,20 +765,17 @@ use constant FIUMAP => \%fiumap;
# - net: name of a net from the input netlist
# - values (optional) used to restrict valid values. Can be...
# - an array reference listing all valid values (emulates an enum)
# - a code reference to a function that gets the new and old values and
# returns true if the new value is allowed, or false otherwise.
# - a code reference to a function that gets the old value and current settings and
# returns the allowed value(s).
# - phases_opt (optional) list of phases (subset of "setup", "instrument", "download")
# where no value must be present in input (e.g. ID is only necessary while downloading).
# - gui_modes (optional) specifies in which mode of {manual,auto,random} this key will be displayed in the GUI
# - depends_on (optional) specifies the key of another constant.
# The respective constant is only relevant (and thuse required as input)
# if the value of the referenced constant is true.
# - group (optional) specifies how to group this value in output. Value will not be displayed if undef
# - order (optional) specifies how to sort this value in output group
# - noedit (optional) display value, but don't allow to alter
# - default (optional) default value if not given in file and not determinable otherwise
# - help (optional) short help text, e.g. to be displayed via Tk:Balloon
# - forbidden_by (optional) specifies the key of another constant
# The respective constant may not be enabled if the value
# of the referenced constant is true
my %testconstmap;
......@@ -812,7 +810,7 @@ BEGIN {
activated_by_settings => sub {
my $settings = shift;
return ($settings->{'design'}->{'FAULT_DETECT_1_EN'} == 1 || $settings->{'design'}->{'FAULT_DETECT_2_EN'} == 1);
}
},
},
NUM_TESTS => {
ini_name => "NUM_TESTS",
......@@ -979,24 +977,26 @@ BEGIN {
activated_by_settings => sub {
my $settings = shift;
return ($settings->{'design'}->{'RESET_DUT_IN_EN'} == 1);
}
},
},
INITIAL_TRIGGER => {
description => "Initial test: Wait for trigger?",
ini_name => "INITIAL_TRIGGER",
default => 'NONE',
values => [qw(DUT EXT NONE)],
type => 'dropdown',
phases_opt => [qw(manual auto)],
gui_modes => [qw (random)],
order => 16,
activated_by_settings => sub {
my $settings = shift;
my $opt = ();
return ($settings->{'design'}->{'TRIGGER_DUT_EN'} == 1 || $settings->{'design'}->{'TRIGGER_EXT_EN'} == 1);
},
values => sub {
my ($old, $settings) = @_;
my $opt = ();
push @{$opt}, "NONE";
push @{$opt}, "INT" if ($settings->{'design'}->{'TRIGGER_DUT_EN'} == 1);
push @{$opt}, "EXT" if ($settings->{'design'}->{'TRIGGER_DUT_EN'} == 1);
push @{$opt}, "EXT" if ($settings->{'design'}->{'TRIGGER_EXT_EN'} == 1);
return $opt;
}
},
......
......@@ -59,7 +59,7 @@ sub new(;$$) {
if (defined $existing_tests) {
$rvs = $self->existing_tests($existing_tests);
} elsif (defined $testsname) {
$rvs = $self->tests_from_file($mode, $self->{'fiji_settings'}->{'design'}->{'CFGS_PER_MSG'}, $self->{'fiji_settings'}->{'design'}->{'FIU_NUM'}, $testsname);
$rvs = $self->tests_from_file($mode, $self->{'fiji_settings'}, $testsname);
}
$logger->error($rvt) if (!ref $rvt);
......@@ -97,19 +97,21 @@ sub existing_settings {
}
}
## @method public tests_from_file ($msgs_per_cfg, $fiu_num, $cfgname)
## @method public tests_from_file ($mode, $set_ref, $cfgname)
# @brief read tests from a given file
#
# @param mode currently active FIJI settings
# @param set_ref currently active FIJI settings
# @param cfgname the file to read from
# @param msgs_per_cfg the number of faults per FIU
# @param fiu_num the number of FIUs
#
# @returns STRING if an error ocurred
# @returns FIJI::Tests object if sucessful
sub tests_from_file {
my $rv;
my $logger = get_logger("");
my ($self, $mode, $msgs_per_cfg, $fiu_num, $cfgname) = @_;
my ($self, $mode, $set_ref, $cfgname) = @_;
$logger->info("New Tests for mode '$mode'");
my $fiji_tests = FIJI::Tests->new($mode, $msgs_per_cfg, $fiu_num, $cfgname);
my $fiji_tests = FIJI::Tests->new($mode, $set_ref, $cfgname);
if (!ref($fiji_tests)) {
$rv = $fiji_tests . " Aborting.\n";
} else {
......@@ -562,7 +564,7 @@ sub _download_test {
# download test via serial
my $recv_msg = _test_fi_uart($port, \@payload, $t1_duration, $t2_duration, $trigger_en, $trigger_ext, $reset, $fiji_design_consts, $dryrun);
if (ref($recv_msg) eq "HASH") {
if (ref($recv_msg) eq "HASH" && defined($recv_msg->{'msg_type'})) {
my $log_msg = "Received " . $recv_msg->{'msg_type'} . " message";
$log_msg .= sprintf(": U=%d, I=%d, C=%d, F1=%d, F2=%d,", $recv_msg->{'error'}->{'U'}, $recv_msg->{'error'}->{'I'}, $recv_msg->{'error'}->{'C'}, $recv_msg->{'fault_detect'}->{'1'}, $recv_msg->{'fault_detect'}->{'2'}) if (defined($recv_msg->{'error'}));
$logger->info($log_msg);
......
......@@ -37,10 +37,9 @@ use FIJI::Tests::VHDL;
use FIJI::Tests::SystemVerilog;
use FIJI qw(:all);
# @FIXME rather similar to Settings.pm
# Can we generalize this?
# @FIXME this is almost a 1:1 copy of Settings.pm. refactoring or cleanup is required
## @function public new ($phase, $fiji_ini_file, $existing_settings)
## @function public new ($phase, $set_ref, $fiji_ini_file, $existing_settings)
# Create a new settings instance.
#
# \param phase Tool flow phase the settings need to be compatible with.
......@@ -51,7 +50,10 @@ use FIJI qw(:all);
# why it could not be created.
sub new {
my $logger = get_logger("");
my ($class, $phase, $cfgs_per_msg, $fiu_num, $fiji_ini_file, $existing_settings, $num_tests) = @_;
my ($class, $phase, $set_ref, $fiji_ini_file, $existing_settings, $num_tests) = @_;
my $cfgs_per_msg = $set_ref->{'design'}->{'CFGS_PER_MSG'};
my $fiu_num = $set_ref->{'design'}->{'FIU_NUM'};
my $fiji_settings_ref = {};
......@@ -77,7 +79,7 @@ sub new {
# If there is a file given, try to read it. Else just create a default instance.
if (defined($fiji_ini_file)) {
$fiji_settings_ref = read_settingsfile($phase, $fiji_ini_file, $fiji_settings_ref, $cfgs_per_msg, $fiu_num);
$fiji_settings_ref = read_settingsfile($phase, $set_ref, $fiji_ini_file, $fiji_settings_ref, $cfgs_per_msg, $fiu_num);
if (!ref($fiji_settings_ref)) {
return $fiji_settings_ref; # actually an error message
......@@ -233,7 +235,7 @@ sub save {
# \returns a reference to the hash containing the read constants.
sub read_settingsfile {
my $logger = get_logger("");
my ($phase, $fiji_ini_file, $existing_settings, $cfgs_per_msg, $fiu_num) = @_;
my ($phase, $set_ref, $fiji_ini_file, $existing_settings, $cfgs_per_msg, $fiu_num) = @_;
my $fiji_ini;
my $global_settings_filename;
......@@ -295,7 +297,7 @@ sub read_settingsfile {
$fiji_settings_ref->{'tests'} = [];
# sanitize and validate read design constants
$design_ref = _sanitize_design($design_ref, $phase);
$design_ref = _sanitize_design($set_ref, $design_ref, $phase);
if (!ref($design_ref)) {
$logger->error($design_ref);
return $design_ref;
......@@ -327,7 +329,7 @@ sub read_settingsfile {
}
$test_ref = $tmp_test;
$test_ref = _sanitize_test($fiji_settings_ref->{'ext'}->{'TESTPATMAP'}, $test_ref, $phase);
$test_ref = _sanitize_test($set_ref, $fiji_settings_ref->{'ext'}->{'TESTPATMAP'}, $test_ref, $phase);
if (!ref($test_ref)) {
my $msg = "(Some) constants for $test_name in Tests Settings are invalid.";
$logger->error($msg);
......@@ -423,20 +425,11 @@ sub _set_defaults {
}
}
sub validate_design_value {
my ($k, $v_ref) = @_;
return validate_value(TESTCONSTMAP, $k, $v_ref);
}
sub validate_test_value {
my ($m, $k, $v_ref) = @_;
return validate_value($m, $k, $v_ref);
}
## @function public validate_value (%$map_ref, $k, $$v_ref, $old, $log_func)
# Do validation (and conversation from external->internal representation)
#
# \param map_ref reference to FIJI Settings mappings
# \param set_ref reference to FIJI Settings mappings
# \param map_ref reference to FIJI Test mappings
# \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
......@@ -444,7 +437,7 @@ sub validate_test_value {
# (defaul is \&Log::Log4perl::Logger::trace)
sub validate_value {
my $logger = get_logger("");
my ($map_ref, $k, $v_ref, $old, $log_func) = @_;
my ($set_ref, $map_ref, $k, $v_ref, $old, $log_func) = @_;
$log_func = \&Log::Log4perl::Logger::trace if !defined($log_func);
if (defined($map_ref->{$k}->{'type'})) {
my $orig = ${$v_ref};
......@@ -526,7 +519,8 @@ sub validate_value {
return 0;
}
} elsif (ref($values_ref) eq 'CODE') {
if (!$values_ref->(${$v_ref}, $old)) {
my $new_vals = $values_ref->($old, $set_ref);
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)) {
$log_func->($logger, "$k: ${$v_ref} is not allowed.");
return 0;
}
......@@ -584,14 +578,14 @@ sub _rename_import {
# in sanitized form, or undef on errors.
sub _sanitize_test {
my $logger = get_logger("");
my ($testpatmap, $test_ref, $phase) = @_;
my ($set_ref, $testpatmap, $test_ref, $phase) = @_;
if (ref($test_ref) ne 'HASH') {
my $msg = "Parameter is not a reference to a hash (containing TEST constants).";
$logger->error($msg);
return $msg;
}
$test_ref = _validate_hashmap($testpatmap, $test_ref, $phase);
$test_ref = _validate_hashmap($set_ref, $testpatmap, $test_ref, $phase);
if (!ref($test_ref)) {
$logger->error("Could not validate Design Constants.");
return $test_ref;
......@@ -606,9 +600,9 @@ sub _disabled_via_dependency ($$$) {
return defined($dependency) && !$consts_ref->{$dependency};
}
sub _validate_hashmap ($$;$) {
sub _validate_hashmap ($$$;$) {
my $logger = get_logger("");
my ($map_ref, $consts_ref, $phase) = @_;
my ($set_ref, $map_ref, $consts_ref, $phase) = @_;
my @map_keys = keys(%{$map_ref});
foreach my $entry_key (keys(%{$consts_ref})) {
my $ini_name = $map_ref->{$entry_key}->{'ini_name'};
......@@ -621,7 +615,7 @@ sub _validate_hashmap ($$;$) {
@map_keys = grep { $_ ne $entry_key } @map_keys; # mark constant key as done
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'}));
} elsif (!validate_value($map_ref, $entry_key, \$consts_ref->{$entry_key})) {
} elsif (!validate_value($set_ref, $map_ref, $entry_key, \$consts_ref->{$entry_key})) {
my $msg = sprintf("%s = %s is invalid.", $entry_key, !defined($consts_ref->{$entry_key}) ? "<undef>" : $consts_ref->{$entry_key});
$logger->error($msg);
return $msg;
......@@ -668,7 +662,7 @@ sub _validate_hashmap ($$;$) {
# settings in sanitized form, or an error message.
sub _sanitize_design {
my $logger = get_logger("");
my ($consts_ref, $phase) = @_;
my ($set_ref, $consts_ref, $phase) = @_;
if (ref($consts_ref) ne 'HASH') {
my $msg = "Parameter is not a reference to a hash (containing design constants).";
$logger->error($msg);
......@@ -676,7 +670,7 @@ sub _sanitize_design {
}
# check for sane values
$consts_ref = _validate_hashmap(TESTCONSTMAP, $consts_ref, $phase);
$consts_ref = _validate_hashmap($set_ref, TESTCONSTMAP, $consts_ref, $phase);
if (!ref($consts_ref)) {
my $msg = "Could not validate Design Constants.";
$logger->error($msg);
......
......@@ -515,8 +515,6 @@ sub _populate_widget {
-expand => 1,
);
#Tk::FIJIUtils::bind_mousewheel($self->{'mw'},$text_scrolled);
#---
# Configure logging window
#
......@@ -571,16 +569,16 @@ sub _populate_widget {
'-justify' => 'left'
);
$label->grid(
-row => $row,
-column => 0,
'-row' => $row,
'-column' => 0,
'-sticky' => 'w'
);
# unit
my $unit = $page->Label('-text' => TESTCONSTMAP->{$k}->{'unit'},);
$unit->grid(
-row => $row,
-column => 1,
'-row' => $row,
'-column' => 1,
'-ipadx' => ".5c",
'-sticky' => 'w',
);
......@@ -588,130 +586,99 @@ sub _populate_widget {
# entry
my $type = TESTCONSTMAP->{$k}->{'type'};
my $state = "normal";
my $en;
my $activation_text = "";
my $activation_values = "";
my $activation_func = TESTCONSTMAP->{$k}->{'activated_by_settings'};
if (defined($activation_func) && ref($activation_func) eq "CODE")
{
$activation_values = $activation_func->($self->{'settings'});
if (ref($activation_values) eq "ARRAY") {
$state = (scalar(@{$activation_values}) > 1) ? "normal" : "disabled";
} elsif (ref(\$activation_values) eq "SCALAR") {
$state = ($activation_values ne "") ? "normal" : "disabled";
}
$activation_text = "(disabled due to current FIJI configuration)" if ($state eq "disabled");
$logger->trace("$k: activation_values=$activation_values");
$en = $activation_func->($self->{'settings'});
$activation_text = "(disabled due to current FIJI configuration)" if (!$en);
} else {
$en = 1;
}
if (defined($type) && $type eq 'boolean') {
$state = "disabled" if (TESTCONSTMAP->{$k}->{'noedit'});
$entry = $page->Checkbutton(
'-state' => $state,
'-state' => $en ? "active" : "disabled",
'-variable' => \$self->{'tests'}->{'design'}->{$k},
'-justify' => 'left',
'-text' => $activation_text,
);
$entry->grid(
-row => $row,
-column => 2,
'-row' => $row,
'-column' => 2,
'-sticky' => 'w'
);
} elsif (defined($type) && $type eq 'autocomplete') {
my $options = TESTCONSTMAP->{$k}->{'values'};
my $base;
if (defined $options && ref($options) eq "CODE") {
$options = $options->();
# If we can call some code to fetch the values we want to
# add an update bottom next to the entry field.
# To that end we create an intermediate frame here if need be.
$base = $page->Frame();
} else {
$base = $page;
}
my $complete = $base->CompleteEntry(
'-choices' => $options,
$entry = $page->Frame();
my $complete = $entry->CompleteEntry(
'-state' => $en ? "normal" : "disabled",
);
if ($base != $page) {
$complete->grid(
-row => 0,
-column => 0,
-sticky => "ew",
);
$base->gridColumnconfigure(0, -weight => 1);
my $b = $base->Button(
$complete->grid(
'-row' => 0,
'-column' => 0,
'-sticky' => "ew",
);
$entry->gridColumnconfigure(0, -weight => 1);
if (defined $options && ref($options) eq "CODE") {
my $b = $entry->Button(
-text => 'Update Choices',
-command => sub {
$complete->configure('-choices', TESTCONSTMAP->{$k}->{'values'}->());
$complete->configure('-choices' => TESTCONSTMAP->{$k}->{'values'}->($self->{'settings'}->{$k}, $self->{'settings'}));
},
)->grid(
-row => 0,
-column => 1,
-sticky => "w",
'-row' => 0,
'-column' => 1,
'-sticky' => "w",
);
$entry = $base;
} else {
$entry = $complete;
$options = $options->();
}
$complete->configure('-choices' => $options);
# Whatever our entry is, we want it in the 2nd column on the left
$entry->grid(
-row => $row,
-column => 2,
'-row' => $row,
'-column' => 2,
'-sticky' => 'ew'
);
} elsif (defined($type) && $type eq 'dropdown') {
my $options = TESTCONSTMAP->{$k}->{'values'};
$options = $activation_values if defined($activation_func);
if (defined $options && ref($options) eq "CODE") {
$options = $options->();
$options = $options->($self->{'settings'}->{$k}, $self->{'settings'});
}
# If we are disabled we want to add a label next to the dropdown.
# To that end we create an intermediate frame here if need be.
my $base;
if ($state eq "disabled") {
$base = $page->Frame();
} else {
$base = $page;
}
my $opts = $base->Optionmenu(
'-state' => $state,
$entry = $page->Frame();
my $opts = $entry->Optionmenu(
'-state' => $en ? "normal" : "disabled",
'-options' => $options,
'-textvariable' => \$self->{'tests'}->{'design'}->{$k},
'-anchor' => 'w',
'-justify' => 'left',
);
$opts->grid(
'-row' => 0,
'-column' => 0,
'-sticky' => "w"
);
if ($state eq "disabled") {
$opts->grid(
-row => 0,
-column => 0,
-sticky => "w"
);
$base->gridColumnconfigure(1, -weight => 1);
$base->Label(
-text => $activation_text,
-state => "disabled",
)->pack(
-side => "left",
-padx => 5
)->grid(
-row => 0,
-column => 1,
'-sticky' => 'w'
);
$entry = $base;
} else {
$entry = $opts;
}
$entry->Label(
'-text' => $activation_text,
'-state' => "disabled",
)->pack(
'-side' => "left",
'-padx' => 5
)->grid(
'-row' => 0,
'-column' => 1,
'-sticky' => 'w'
);
$entry->gridColumnconfigure(1, -weight => 1);
# Whatever our entry is, we want it in the 2nd column on the left
$entry->grid(
-row => $row,
-column => 2,
'-row' => $row,
'-column' => 2,
'-sticky' => 'w'
);
} elsif (defined($type) && $type eq 'file') {
......@@ -722,6 +689,7 @@ sub _populate_widget {
my $b = $entry->Button(
-text => 'Open',
'-state' => $en ? "normal" : "disabled",
-command => sub {
my $fb = $page->FBox(
-type => 'open',
......@@ -753,7 +721,7 @@ sub _populate_widget {
'-sticky' => 'ew'
);
} else {
$entry = $page->Entry('-state' => (TESTCONSTMAP->{$k}->{'noedit'}) ? "disabled" : "normal",);
$entry = $page->Entry('-state' => $en ? "normal" : "readonly");
$entry->configure(
'-validate' => 'key',
'-validatecommand' => [\&_validate_design_entry, $self, $entry, $k],
......@@ -845,46 +813,68 @@ sub update {
# loop through all widgets and set their values and states according
# to the settings
for my $widget (@{$self->{'widgets'}}) {
my $k = $widget->{'key'};
my $k = $widget->{'key'};
next if (!defined $k || !defined TESTCONSTMAP->{$k}->{'type'});
my $en;
my $activation_func = TESTCONSTMAP->{$k}->{'activated_by_settings'};
if (defined($activation_func) && ref($activation_func) eq "CODE")
{
$en = $activation_func->($self->{'settings'});
} else {
$en = 1;
}
my $val = \$self->{'tests'}->{'design'}->{$k};
if (ref($widget) eq "Tk::Entry") {
$widget->configure('-state' => $en ? "normal" : "readonly");
if (defined($val)) {
$widget->configure(-text => $val);
} else {
$widget->delete('0', 'end');
}
} elsif (ref($widget) eq "Tk::Checkbutton") {
$widget->configure(-variable => \$self->{'tests'}->{'design'}->{$k});
$widget->configure(
'-state' => $en ? "normal" : "disabled",
'-variable' => \$self->{'tests'}->{'design'}->{$k},
);
if (defined($self->{'depends'}->{$k})) {
$widget->configure('-command' => [\&_set_fields_by_button, $self, $widget, $self->{'depends'}->{$k}, []]);
}
_set_fields_by_button($self, $widget, $self->{'depends'}->{$k}, []);
} elsif (ref($widget) eq "Tk::Optionmenu") {
my $options = TESTCONSTMAP->{$k}->{'values'};
if (defined(TESTCONSTMAP->{$k}->{'activated_by_settings'})
&& ref(TESTCONSTMAP->{$k}->{'activated_by_settings'}) eq "CODE")
{
$options = TESTCONSTMAP->{$k}->{'activated_by_settings'}->($self->{'settings'});
}
if (defined $options && ref($options) eq "CODE") {
$options = $options->();
}
$widget->configure(-options => $options, -textvariable => $val);
} elsif (ref($widget) eq "Tk::CompleteEntry") {
my $options = TESTCONSTMAP->{$k}->{'values'};
if (defined(TESTCONSTMAP->{$k}->{'activated_by_settings'})
&& ref(TESTCONSTMAP->{$k}->{'activated_by_settings'}) eq "CODE")
{
$options = TESTCONSTMAP->{$k}->{'activated_by_settings'}->($self->{'settings'});
}
if (defined $options && ref($options) eq "CODE") {
$options = $options->();
}
$widget->configure(-choices => $options, -textvariable => $val);
} elsif (ref($widget) eq "Tk::Frame") {
if (defined TESTCONSTMAP->{$k}->{'type'} && TESTCONSTMAP->{$k}->{'type'} eq 'file') {
(($widget->children)[1])->configure('-text' => (defined $$val) ? $$val : "");
if (TESTCONSTMAP->{$k}->{'type'} eq 'file') {
$widget = ($widget->children)[1];
$widget->configure(
'-state' => $en ? "normal" : "disabled",
'-text' => (defined $$val) ? $$val : "",
);
} elsif (TESTCONSTMAP->{$k}->{'type'} eq 'dropdown') {
my $options = TESTCONSTMAP->{$k}->{'values'};
if (defined $options && ref($options) eq "CODE") {
$options = $options->($self->{'settings'}->{$k}, $self->{'settings'});
}
$widget = ($widget->children)[0];
$widget->configure(
'-state' => $en ? "active" : "disabled",
'-options' => $options,
'-textvariable' => $val,
);
} elsif (TESTCONSTMAP->{$k}->{'type'} eq 'autocomplete') {
my $options = TESTCONSTMAP->{$k}->{'values'};
if (defined $options && ref($options) eq "CODE") {
$options = $options->($self->{'settings'}->{$k}, $self->{'settings'});
}
$widget = ($widget->children)[0];
$widget->configure(
'-state' => $en ? "normal" : "disabled",
'-choices' => $options,
'-textvariable' => $val,
);
}
} else {
my $logger = get_logger("");
$logger->error("Updating unknown widget: " . ref($widget) . " of type: " . TESTCONSTMAP->{$k}->{'type'});
}
}
...