#!/usr/bin/env perl
#
# Script to handle regression for normal Verilog files.
#
# This script is based on code with the following Copyright.
#
# Copyright (c) 1999-2020 Guy Hutchison (ghutchis@pacbell.net)
#
#    This source code is free software; you can redistribute it
#    and/or modify it in source code form under the terms of the GNU
#    General Public License as published by the Free Software
#    Foundation; either version 2 of the License, or (at your option)
#    any later version.
#
#    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.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA

use lib './perl-lib';

use RegressionList;
use Diff;
use Reporting;
use Environment;


#
#  Main script
#
&open_report_file;
my ($suffix, $strict, $with_valg, $force_sv) = &get_args;
my $ver = &get_ivl_version($suffix);
my $opt = $force_sv ? " (force SV)" : "";
my $msg = $with_valg ? " (with valgrind)" : "";
&print_rpt("Running vlog95 compiler/VVP tests for Icarus Verilog " .
           "version: $ver$opt$msg.\n");
&print_rpt("-" x 76 . "\n");
if ($#ARGV != -1) {
    my $regress_fn = &get_regress_fn;
    &read_regression_list($regress_fn, $ver, $force_sv, "");

} else {
    if ($force_sv) {
        &read_regression_list("regress-fsv.list", $ver, $force_sv, "");
    }
    &read_regression_list("regress-vlog95.list", $ver, $force_sv, "");
    &read_regression_list("regress-v$ver.list", $ver, $force_sv, "");
    &read_regression_list("regress-ivl2.list", $ver, $force_sv, "");
    &read_regression_list("regress-ivl1.list", $ver, $force_sv, "");
    &read_regression_list("regress-vlg.list",  $ver, $force_sv, "");
    &read_regression_list("regress-vams.list", $ver, $force_sv, "");
    if ($ver >= 10) {
        &read_regression_list("regress-sv.list",   $ver, $force_sv, "");
        &read_regression_list("regress-vhdl.list", $ver, $force_sv, "");
    }
    if ($ver == 0.9) {
        &read_regression_list("regress-synth.list", $ver, $force_sv, "");
    } else {
        &read_regression_list("regress-synth.list", $ver, $force_sv, "-S");
    }
}
&execute_regression($suffix, $with_valg);
&close_report_file;


#
#  execute_regression sequentially compiles and executes each test in
#  the regression. It then checks that the output matches the gold file.
#
sub execute_regression {
    my $sfx = shift(@_);
    my $with_valg = shift(@_);
    my ($tname, $total, $passed, $failed, $expected_fail, $not_impl,
        $len, $cmd, $diff_file);

    $total = 0;
    $passed = 0;
    $failed = 0;
    $expected_fail = 0;
    $not_impl = 0;
    $len = 0;

    foreach $tname (@testlist) {
        $len = length($tname) if (length($tname) > $len);
    }

    # Make sure we have a log and work directory.
    if (! -d 'log') {
        mkdir 'log' or die "Error: unable to create log directory.\n";
    }
    if (! -d 'work') {
        mkdir 'work' or die "Error: unable to create work directory.\n";
    }

    foreach $tname (@testlist) {
        my $pass_type;
        next if ($tname eq "");  # Skip test that have been replaced.

        $total++;
        &print_rpt(sprintf("%${len}s: ", $tname));
        if ($diff{$tname} ne "" and -e $diff{$tname}) {
            unlink $diff{$tname} or
                die "Error: unable to remove old diff file $diff{$tname}.\n";
        }
        if (-e "log/$tname.log") {
            unlink "log/$tname.log" or
                die "Error: unable to remove old log file log/$tname.log.\n";
        }

        if ($testtype{$tname} eq "NI") {
            &print_rpt("Not Implemented.\n");
            $not_impl++;
            next;
        }

        if (! -e "./$srcpath{$tname}/$tname.v") {
            &print_rpt("Failed - missing source file.\n");
            $failed++;
            next;
        }

        #
        # Build up the iverilog command line and run it.
        #
        $pass_type = 0;
        $cmd = $with_valg ? "valgrind --trace-children=yes " : "";
        $cmd .= "iverilog$sfx -o vlog95.v";
        $cmd .= " -s $testmod{$tname}" if ($testmod{$tname} ne "");
        $cmd .= $testtype{$tname} eq "CN" ? " -t null" : " -t vlog95";
        $cmd .= " -pfileline=1 -pspacing=4" if ($testtype{$tname} ne "CN");
        $cmd .= " -D__ICARUS_UNSIZED__ $args{$tname}";
        $cmd .= " ./$srcpath{$tname}/$tname.v > log/$tname.log 2>&1";
#        print "$cmd\n";
        if (system("$cmd")) {
            if ($testtype{$tname} eq "CE") {
                # Check if the system command core dumped!
                if ($? >> 8 & 128) {
                    &print_rpt("==> Failed - CE (core dump).\n");
                    $failed++;
                    next;
                } else {
                    $pass_type = 1;
                }
            } else {
                &print_rpt("==> Failed - running iverilog.\n");
                $failed++;
                next;
            }
        } else {
            if ($testtype{$tname} eq "CE") {
                &print_rpt("==> Failed - CE (no error reported).\n");
                $failed++;
                next;
            }
        }

        if ($testtype{$tname} eq "CO") {
            &print_rpt("Passed - CO.\n");
            $passed++;
            next;
        }
        if ($testtype{$tname} eq "CN") {
            &print_rpt("Passed - CN.\n");
            $passed++;
            next;
        }

        # Run the translated Verilog code. All compile errors should
        # already be handled. Remove the -S flag if it exists along
        # with any included source file(s) and any -f arguments. The
	# -pallowsigned flag and the various generation flags should
	# also be removed. If we had -pallowsigned=1 then use the
	# -g2001-noconfig to get signed/unsigned otherwise use -g1995.
        my $gen_flag;
	if($args{$tname} =~ m/-pallowsigned=1/) {
	    $gen_flag = "-g2001-noconfig";
	} else {
	    $gen_flag = "-g1995";
	}
        $args{$tname} =~ s/-S//;
        $args{$tname} =~ s/\S+\.vhd//g;
        $args{$tname} =~ s/\S+\.v//g;
        $args{$tname} =~ s/-f\S+//g;
        $args{$tname} =~ s/-pallowsigned=1//g;
        $args{$tname} =~ s/-g2001(-noconfig)?//g;
        $args{$tname} =~ s/-g2005(-sv)?//g;
        $args{$tname} =~ s/-g2009//g;
        $args{$tname} =~ s/-g2012//g;
        $args{$tname} =~ s/-gverilog-ams//g;
        $cmd = "iverilog$sfx -o vsim $gen_flag $args{$tname}";
        $cmd .= " -s $testmod{$tname}" if ($testmod{$tname} ne "");
        $cmd .= " vlog95.v >> log/$tname.log 2>&1";
#        print "$cmd\n";
        if ($pass_type == 0 and system("$cmd")) {
            if ($testtype{$tname} eq "TE") {
                # Check if the system command core dumped!
                if ($? >> 8 & 128) {
                    &print_rpt("==> Failed - TE (core dump).\n");
                    $failed++;
                    next;
                } else {
                    $pass_type = 3;
                }
            } else {
                &print_rpt("==> Failed - running iverilog (translated).\n");
                $failed++;
                next;
            }
        }

        $cmd = "vvp$sfx vsim $plargs{$tname} >> log/$tname.log 2>&1";
#        print "$cmd\n";
        if ($pass_type == 0 and system("$cmd")) {
            if ($testtype{$tname} eq "RE") {
                # Check if the system command core dumped!
                if ($? >> 8 & 128) {
                    &print_rpt("==> Failed - RE (core dump).\n");
                    $failed++;
                    next;
                } else {
                    $pass_type = 2;
                }
            } else {
                &print_rpt("==> Failed - running vvp.\n");
                $failed++;
                next;
            }
        }

        if ($diff{$tname} ne "") {
            $diff_file = $diff{$tname}
        } else {
            if ($pass_type == 1) {
                &print_rpt("Passed - CE.\n");
                $passed++;
                next;
            } elsif ($pass_type == 2) {
                &print_rpt("Passed - RE.\n");
                $passed++;
                next;
            } elsif ($pass_type == 3) {
                &print_rpt("Passed - TE.\n");
                $passed++;
                next;
            }
            $diff_file = "log/$tname.log";
        }
#        print "diff $gold{$tname}, $diff_file, $offset{$tname}, $unordered{$tname}\n";
        if (diff($gold{$tname}, $diff_file, $offset{$tname}, $unordered{$tname})) {
            if ($testtype{$tname} eq "EF") {
                &print_rpt("Passed - expected fail.\n");
                $expected_fail++;
                next;
            }
            &print_rpt("==> Failed -");
            if ($pass_type == 1) {
                &print_rpt(" CE -");
            } elsif ($pass_type == 2) {
                &print_rpt(" RE -");
            } elsif ($pass_type == 3) {
                &print_rpt(" TE -");
            }
            &print_rpt(" output does not match gold file.\n");
            $failed++;
            next;
        }

        if ($pass_type == 1) {
            &print_rpt("Passed - CE.\n");
        } elsif ($pass_type == 2) {
            &print_rpt("Passed - RE.\n");
        } elsif ($pass_type == 3) {
            &print_rpt("Passed - TE.\n");
        } else {
            &print_rpt("Passed.\n");
        }
        $passed++;

    } continue {
        if ($tname ne "") {
            system("rm -f ./vlog95.v ./vsim") and
                die "Error: failed to remove temporary file.\n";
        }
    }

    &print_rpt("=" x 76 . "\n");
    &print_rpt("Test results:\n  Total=$total, Passed=$passed, Failed=$failed,".
               " Not Implemented=$not_impl, Expected Fail=$expected_fail\n");
}
