-- This test suite verifies the check checker.
--
-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this file,
-- You can obtain one at http://mozilla.org/MPL/2.0/.
--
-- Copyright (c) 2016-2017, Lars Asplund lars.anders.asplund@gmail.com

library ieee;
use ieee.std_logic_1164.all;
library vunit_lib;
use vunit_lib.log_types_pkg.all;
use vunit_lib.check_types_pkg.all;
use vunit_lib.check_special_types_pkg.all;
use vunit_lib.check_pkg.all;
use vunit_lib.run_types_pkg.all;
use vunit_lib.run_base_pkg.all;
use vunit_lib.run_pkg.all;
use work.test_support.all;
use work.test_count.all;
use ieee.numeric_std.all;
entity tb_check is
  generic (
    use_check_not_check_true : boolean := true;
    runner_cfg : string);
end entity tb_check;

architecture test_fixture of tb_check is
  signal clk : std_logic := '0';
  signal check_in_1, check_in_2, check_in_3, check_in_4 : std_logic := '1';
  signal check_en_1, check_en_2, check_en_3, check_en_4 : std_logic := '1';

  shared variable check_checker, check_checker2, check_checker3, check_checker4 : checker_t;

begin
  clock: process is
  begin
    while runner.phase < test_runner_exit loop
      clk <= '1', '0' after 5 ns;
      wait for 10 ns;
    end loop;
    wait;
  end process clock;

  concurrent_checks: if use_check_not_check_true generate
    check_1 : check(clk, check_en_1, check_in_1);
    check_2 : check(check_checker2, clk, check_en_2, check_in_2, active_clock_edge => falling_edge);
    check_3 : check(check_checker3, clk, check_en_3, check_in_3);
    check_4 : check(check_checker4, clk, check_en_4, check_in_4);
  end generate concurrent_checks;

  concurrent_true_checks: if not use_check_not_check_true generate
    check_1 : check_true(clk, check_en_1, check_in_1);
    check_2 : check_true(check_checker2, clk, check_en_2, check_in_2, active_clock_edge => falling_edge);
    check_3 : check_true(check_checker3, clk, check_en_3, check_in_3);
    check_4 : check_true(check_checker4, clk, check_en_4, check_in_4);
  end generate concurrent_true_checks;

  check_runner : process
    variable pass : boolean;
    variable stat : checker_stat_t;
    constant pass_level : log_level_t := debug_low2;

    function prefix return string is
    begin
      if use_check_not_check_true then
        return "Check ";
      else
        return "True check ";
      end if;
    end function prefix;

    procedure test_concurrent_check (
      signal clk                        : in  std_logic;
      signal check_input                : out std_logic;
      variable checker : inout checker_t ;
      constant level                    : in  log_level_t := error;
      constant active_rising_clock_edge : in  boolean := true) is
    begin
      -- Verify that one log is generated on false and that that log is
      -- generated on the correct clock edge. No log on true.
      wait until clock_edge(clk, active_rising_clock_edge);
      wait for 1 ns;
      get_checker_stat(checker, stat);
      apply_sequence("0", clk, check_input, active_rising_clock_edge);
      wait until clock_edge(clk, not active_rising_clock_edge);
      wait for 1 ns;
      verify_passed_checks(checker, stat, 0);
      wait until clock_edge(clk, active_rising_clock_edge);
      wait for 1 ns;
      verify_log_call(inc_count, prefix & "failed.", expected_level => level);
      get_checker_stat(checker, stat);
      apply_sequence("1", clk, check_input, active_rising_clock_edge);
      wait until clock_edge(clk, active_rising_clock_edge);
      wait for 1 ns;
      verify_passed_checks(checker, stat, 1);
    end procedure test_concurrent_check;

    procedure internal_check(
      variable checker   : inout checker_t;
      variable pass      : out   boolean;
      constant expr      : in    boolean;
      constant msg       : in    string := result(".")) is
    begin
      if use_check_not_check_true then
        check(checker, pass, expr, msg);
      else
        check_true(checker, pass, expr, msg);
      end if;
    end;

    procedure internal_check(
      variable checker   : inout checker_t;
      constant expr      : in    boolean;
      constant msg       : in    string := result(".")) is
    begin
      if use_check_not_check_true then
        check(checker, expr, msg);
      else
        check_true(checker, expr, msg);
      end if;
    end;

    procedure internal_check(
      constant expr      : in boolean;
      constant msg       : in string := result(".")) is
    begin
      if use_check_not_check_true then
        check(expr, msg);
      else
        check_true(expr, msg);
      end if;
    end;

    procedure internal_check(
      variable pass      : out boolean;
      constant expr      : in  boolean;
      constant msg       : in  string := result(".")) is
    begin
      if use_check_not_check_true then
        check(pass, expr, msg);
      else
        check_true(pass, expr, msg);
      end if;
    end;

    impure function internal_check(
      constant expr      : in boolean;
      constant msg       : in string := result("."))
      return boolean is
    begin
      if use_check_not_check_true then
        return check(expr, msg);
      else
        return check_true(expr, msg);
      end if;
    end;

  begin
    custom_checker_init_from_scratch(check_checker3, default_level => info);
    test_runner_setup(runner, runner_cfg);

    while test_suite loop
      if run("Test should pass on true inputs to sequential checks") then
        get_checker_stat(stat);
        internal_check(true);
        internal_check(pass, true);
        counting_assert(pass, "Should return pass = true on passing check");
        pass := internal_check(true);
        counting_assert(pass, "Should return pass = true on passing check");
        verify_passed_checks(stat, 3);

        get_checker_stat(check_checker, stat);
        internal_check(check_checker, true);
        internal_check(check_checker, pass, true);
        counting_assert(pass, "Should return pass = true on passing check");
        verify_passed_checks(check_checker, stat, 2);
        verify_num_of_log_calls(get_count);
      elsif run("Test pass message") then
        enable_pass_msg;
        internal_check(true);
        verify_log_call(inc_count, prefix & "passed.", pass_level);
        internal_check(true, "");
        verify_log_call(inc_count, "", pass_level);
        internal_check(true, "Checking my data.");
        verify_log_call(inc_count, "Checking my data.", pass_level);
        internal_check(true, result("for my data."));
        verify_log_call(inc_count, prefix & "passed for my data.", pass_level);
        disable_pass_msg;
      elsif run("Test should fail on false inputs to sequential checks") then
        internal_check(false);
        verify_log_call(inc_count, prefix & "failed.");
        internal_check(false, "");
        verify_log_call(inc_count, "");
        internal_check(pass, false, "Checking my data.");
        counting_assert(not pass, "Should return pass = false on failing check");
        verify_log_call(inc_count, "Checking my data.");
        pass := internal_check(false, result("for my data."));
        counting_assert(not pass, "Should return pass = false on failing check");
        verify_log_call(inc_count, prefix & "failed for my data.");

        internal_check(check_checker, false);
        verify_log_call(inc_count, prefix & "failed.");
        internal_check(check_checker, pass, false, result("for my data."));
        counting_assert(not pass, "Should return pass = false on failing check");
        verify_log_call(inc_count, prefix & "failed for my data.");
      elsif run("Test should be possible to use concurrently") then
        test_concurrent_check(clk, check_in_1, default_checker);
      elsif run("Test should be possible to use concurrently with negative active clock edge") then
        test_concurrent_check(clk, check_in_2, check_checker2, error, false);
      elsif run("Test should be possible to use concurrently with custom checker") then
        test_concurrent_check(clk, check_in_3, check_checker3, info);
      elsif run("Test should pass on weak high but fail on other meta values") then
        wait until rising_edge(clk);
        wait for 1 ns;
        get_checker_stat(check_checker4, stat);
        apply_sequence("1H1", clk, check_in_4);
        wait until rising_edge(clk);
        wait for 1 ns;
        verify_passed_checks(check_checker4, stat, 3);
        apply_sequence("1UXZWL-1", clk, check_in_4);
        wait for 1 ns;
        verify_log_call(set_count(get_count + 6), prefix & "failed.");
      elsif run("Test should pass on logic low inputs when not enabled") then
        wait until rising_edge(clk);
        wait for 1 ns;
        get_checker_stat(stat);
        check_en_1 <= '0';
        apply_sequence("01", clk, check_in_1);
        check_en_1 <= '1';
        wait until rising_edge(clk);
        check_en_1 <= 'L';
        apply_sequence("01", clk, check_in_1);
        check_en_1 <= 'H';
        wait until rising_edge(clk);
        check_en_1 <= 'X';
        apply_sequence("01", clk, check_in_1);
        check_en_1 <= '1';
        wait until rising_edge(clk);
        wait for 1 ns;
        verify_passed_checks(stat, 3);
        verify_failed_checks(stat, 0);
      end if;
    end loop;

    get_and_print_test_result(stat);
    test_runner_cleanup(runner, stat);
    wait;
  end process;

  test_runner_watchdog(runner, 2 us);

end test_fixture;

-- vunit_pragma run_all_in_same_sim
