text: # 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) 2014-2019, Lars Asplund lars.anders.asplund@gmail.com
  
  from os.path import join, dirname
  from string import Template
  
  api_template = """  procedure check_equal(
      constant got       : in $got_type;
      constant expected  : in $expected_type;
      constant msg       : in string      := check_result_tag;
      constant level     : in log_level_t := null_log_level;
      constant line_num  : in natural     := 0;
      constant file_name : in string      := "");
  
    procedure check_equal(
      variable pass      : out boolean;
      constant got       : in $got_type;
      constant expected  : in $expected_type;
      constant msg       : in string      := check_result_tag;
      constant level     : in log_level_t := null_log_level;
      constant line_num  : in natural     := 0;
      constant file_name : in string      := "");
  
    procedure check_equal(
      constant checker   : in checker_t;
      variable pass      : out boolean;
      constant got       : in $got_type;
      constant expected  : in $expected_type;
      constant msg       : in string      := check_result_tag;
      constant level     : in log_level_t := null_log_level;
      constant line_num  : in natural     := 0;
      constant file_name : in string      := "");
  
    procedure check_equal(
      constant checker   : in checker_t;
      constant got       : in $got_type;
      constant expected  : in $expected_type;
      constant msg       : in string      := check_result_tag;
      constant level     : in log_level_t := null_log_level;
      constant line_num  : in natural     := 0;
      constant file_name : in string      := "");
  
    impure function check_equal(
      constant got       : in $got_type;
      constant expected  : in $expected_type;
      constant msg       : in string      := check_result_tag;
      constant level     : in log_level_t := null_log_level;
      constant line_num  : in natural     := 0;
      constant file_name : in string      := "")
      return boolean;
  
    impure function check_equal(
      constant checker   : in checker_t;
      constant got       : in $got_type;
      constant expected  : in $expected_type;
      constant msg       : in string      := check_result_tag;
      constant level     : in log_level_t := null_log_level;
      constant line_num  : in natural     := 0;
      constant file_name : in string      := "")
      return boolean;
  
  """
  
  impl_template = """  procedure check_equal(
      constant got       : in $got_type;
      constant expected  : in $expected_type;
      constant msg       : in string      := check_result_tag;
      constant level     : in log_level_t := null_log_level;
      constant line_num  : in natural     := 0;
      constant file_name : in string      := "") is
      variable pass : boolean;
    begin
      -- pragma translate_off
      check_equal(default_checker, pass, got, expected, msg, level, line_num, file_name);
      -- pragma translate_on
    end;
  
    procedure check_equal(
      variable pass      : out boolean;
      constant got       : in $got_type;
      constant expected  : in $expected_type;
      constant msg       : in string      := check_result_tag;
      constant level     : in log_level_t := null_log_level;
      constant line_num  : in natural     := 0;
      constant file_name : in string      := "") is
    begin
      -- pragma translate_off
      check_equal(default_checker, pass, got, expected, msg, level, line_num, file_name);
      -- pragma translate_on
    end;
  
    procedure check_equal(
      constant checker   : in checker_t;
      variable pass      : out boolean;
      constant got       : in $got_type;
      constant expected  : in $expected_type;
      constant msg       : in string      := check_result_tag;
      constant level     : in log_level_t := null_log_level;
      constant line_num  : in natural     := 0;
      constant file_name : in string      := "") is
    begin
      -- pragma translate_off
      if got = expected then
        pass := true;
        if is_pass_visible(checker) then
          passing_check(
            checker,
            std_msg(
              "Equality check passed", msg,
              "Got " & $got_str & "."),
            line_num, file_name);
        else
          passing_check(checker);
        end if;
      else
        pass := false;
        failing_check(
          checker,
          std_msg(
            "Equality check failed", msg,
            "Got " & $got_str & ". " &
            "Expected " & $expected_str & "."),
          level, line_num, file_name);
      end if;
      -- pragma translate_on
    end;
  
    procedure check_equal(
      constant checker   : in checker_t;
      constant got       : in $got_type;
      constant expected  : in $expected_type;
      constant msg       : in string      := check_result_tag;
      constant level     : in log_level_t := null_log_level;
      constant line_num  : in natural     := 0;
      constant file_name : in string      := "") is
      variable pass : boolean;
    begin
      -- pragma translate_off
      check_equal(checker, pass, got, expected, msg, level, line_num, file_name);
      -- pragma translate_on
    end;
  
    impure function check_equal(
      constant got       : in $got_type;
      constant expected  : in $expected_type;
      constant msg       : in string      := check_result_tag;
      constant level     : in log_level_t := null_log_level;
      constant line_num  : in natural     := 0;
      constant file_name : in string      := "")
      return boolean is
      variable pass : boolean;
    begin
      -- pragma translate_off
      check_equal(default_checker, pass, got, expected, msg, level, line_num, file_name);
      -- pragma translate_on
      return pass;
    end;
  
    impure function check_equal(
      constant checker   : in checker_t;
      constant got       : in $got_type;
      constant expected  : in $expected_type;
      constant msg       : in string      := check_result_tag;
      constant level     : in log_level_t := null_log_level;
      constant line_num  : in natural     := 0;
      constant file_name : in string      := "")
      return boolean is
      variable pass : boolean;
    begin
      -- pragma translate_off
      check_equal(checker, pass, got, expected, msg, level, line_num, file_name);
      -- pragma translate_on
      return pass;
    end;
  
  """
  
  test_template = """\
  
        elsif run("Test should pass on $left_type equal $right_type") then
          get_checker_stat(stat);
          check_equal($left_pass, $right_pass);
          check_equal(passed, $left_pass, $right_pass);
          assert_true(passed, "Should return pass = true on passing check");
          passed := check_equal($left_pass, $right_pass);
          assert_true(passed, "Should return pass = true on passing check");
          check_equal($left_min, $right_min);
          check_equal($left_max, $right_max);
          verify_passed_checks(stat, 5);
  
          get_checker_stat(my_checker, stat);
          check_equal(my_checker, $left_pass, $right_pass);
          check_equal(my_checker, passed, $left_pass, $right_pass);
          assert_true(passed, "Should return pass = true on passing check");
          passed := check_equal(my_checker, $left_pass, $right_pass);
          assert_true(passed, "Should return pass = true on passing check");
          verify_passed_checks(my_checker, stat, 3);
  
        elsif run("Test pass message on $left_type equal $right_type") then
          mock(check_logger);
          check_equal($left_pass, $right_pass);
          check_only_log(check_logger, "Equality check passed - Got $left_pass_str.", pass);
  
          check_equal($left_pass, $right_pass, "");
          check_only_log(check_logger, "Got $left_pass_str.", pass);
  
          check_equal($left_pass, $right_pass, "Checking my data");
          check_only_log(check_logger, "Checking my data - Got $left_pass_str.", pass);
  
          check_equal($left_pass, $right_pass, result("for my data"));
          check_only_log(check_logger, "Equality check passed for my data - Got $left_pass_str.", pass);
          unmock(check_logger);
  
        elsif run("Test should fail on $left_type not equal $right_type") then
          get_checker_stat(stat);
          mock(check_logger);
          check_equal($left_pass, $right_fail);
          check_only_log(check_logger, "Equality check failed - Got $left_pass_str. Expected $fail_str.",
                         default_level);
  
          check_equal($left_pass, $right_fail, "");
          check_only_log(check_logger, "Got $left_pass_str. Expected $fail_str.", default_level);
  
          check_equal($left_pass, $right_fail, "Checking my data");
          check_only_log(check_logger, "Checking my data - Got $left_pass_str. Expected $fail_str.", default_level);
  
          check_equal($left_pass, $right_fail, result("for my data"));
          check_only_log(check_logger, "Equality check failed for my data - Got $left_pass_str. Expected $fail_str.",
                         default_level);
  
          check_equal(passed, $left_pass, $right_fail);
          assert_true(not passed, "Should return pass = false on failing check");
          check_only_log(check_logger, "Equality check failed - Got $left_pass_str. Expected $fail_str.",
                         default_level);
  
          passed := check_equal($left_pass, $right_fail);
          assert_true(not passed, "Should return pass = false on failing check");
          check_only_log(check_logger, "Equality check failed - Got $left_pass_str. Expected $fail_str.",
                         default_level);
          unmock(check_logger);
          verify_passed_checks(stat, 0);
          verify_failed_checks(stat, 6);
          reset_checker_stat;
  
          get_checker_stat(my_checker, stat);
          mock(my_logger);
          check_equal(my_checker, $left_pass, $right_fail);
          check_only_log(my_logger, "Equality check failed - Got $left_pass_str. Expected $fail_str.", default_level);
  
          check_equal(my_checker, passed, $left_pass, $right_fail);
          assert_true(not passed, "Should return pass = false on failing check");
          check_only_log(my_logger, "Equality check failed - Got $left_pass_str. Expected $fail_str.", default_level);
  
          passed := check_equal(my_checker, $left_pass, $right_fail);
          assert_true(not passed, "Should return pass = false on failing check");
          check_only_log(my_logger, "Equality check failed - Got $left_pass_str. Expected $fail_str.", default_level);
  
          unmock(my_logger);
          verify_passed_checks(my_checker, stat, 0);
          verify_failed_checks(my_checker, stat, 3);
          reset_checker_stat(my_checker);
  """
  
  combinations = [
      ('unsigned', 'unsigned',
       """unsigned'(X"A5")""", """unsigned'(X"A5")""",
       "to_unsigned(natural'left,31)", "to_unsigned(natural'left,31)",
       "to_unsigned(natural'right,31)", "to_unsigned(natural'right,31)",
       """unsigned'(X"5A")""",
       "1010_0101 (165)", "1010_0101 (165)",
       "0101_1010 (90)"),
      ('unsigned', 'natural',
       """unsigned'(X"A5")""", "natural'(165)",
       "to_unsigned(natural'left,31)", "natural'left",
       "to_unsigned(natural'right,31)", "natural'right",
       "natural'(90)",
       "1010_0101 (165)", "165 (1010_0101)",
       "90 (0101_1010)"),
      ('natural', 'unsigned',
       "natural'(165)", """unsigned'(X"A5")""",
       "natural'left", "to_unsigned(natural'left,31)",
       "natural'right", "to_unsigned(natural'right,31)",
       """unsigned'(X"5A")""",
       "165 (1010_0101)", "1010_0101 (165)",
       "0101_1010 (90)"),
      ('unsigned', 'std_logic_vector',
       """unsigned'(X"A5")""", """std_logic_vector'(X"A5")""",
       "to_unsigned(natural'left,31)", "std_logic_vector(to_unsigned(natural'left,31))",
       "to_unsigned(natural'right,31)", "std_logic_vector(to_unsigned(natural'right,31))",
       """std_logic_vector'(X"5A")""",
       "1010_0101 (165)", "1010_0101 (165)",
       "0101_1010 (90)"),
      ('std_logic_vector', 'unsigned',
       """std_logic_vector'(X"A5")""", """unsigned'(X"A5")""",
       "std_logic_vector(to_unsigned(natural'left,31))", "to_unsigned(natural'left,31)",
       "std_logic_vector(to_unsigned(natural'right,31))", "to_unsigned(natural'right,31)",
       """unsigned'(X"5A")""",
       "1010_0101 (165)", "1010_0101 (165)",
       "0101_1010 (90)"),
      ('std_logic_vector', 'std_logic_vector',
       """std_logic_vector'(X"A5")""", """std_logic_vector'(X"A5")""",
       "std_logic_vector(to_unsigned(natural'left,31))", "std_logic_vector(to_unsigned(natural'left,31))",
       "std_logic_vector(to_unsigned(natural'right,31))", "std_logic_vector(to_unsigned(natural'right,31))",
       """std_logic_vector'(X"5A")""",
       "1010_0101 (165)", "1010_0101 (165)",
       "0101_1010 (90)"),
      ('std_logic_vector', 'natural',
       """std_logic_vector'(X"A5")""", "natural'(165)",
       "std_logic_vector(to_unsigned(natural'left,31))", "natural'left",
       "std_logic_vector(to_unsigned(natural'right,31))", "natural'right",
       "natural'(90)",
       "1010_0101 (165)", "165 (1010_0101)",
       "90 (0101_1010)"),
      ('natural', 'std_logic_vector',
       "natural'(165)", """std_logic_vector'(X"A5")""",
       "natural'left", "std_logic_vector(to_unsigned(natural'left,31))",
       "natural'right", "std_logic_vector(to_unsigned(natural'right,31))",
       """std_logic_vector'(X"5A")""",
       "165 (1010_0101)", "1010_0101 (165)",
       "0101_1010 (90)"),
      ('signed', 'signed',
       """signed'(X"A5")""", """signed'(X"A5")""",
       "to_signed(integer'left,32)", "to_signed(integer'left,32)",
       "to_signed(integer'right,32)", "to_signed(integer'right,32)",
       """signed'(X"5A")""",
       "1010_0101 (-91)", "1010_0101 (-91)",
       "0101_1010 (90)"),
      ('signed', 'integer',
       """signed'(X"A5")""", "integer'(-91)",
       "to_signed(integer'left,32)", "integer'left",
       "to_signed(integer'right,32)", "integer'right",
       "integer'(90)",
       "1010_0101 (-91)", "-91 (1010_0101)",
       "90 (0101_1010)"),
      ('integer', 'signed',
       "integer'(-91)", """signed'(X"A5")""",
       "integer'left", "to_signed(integer'left,32)",
       "integer'right", "to_signed(integer'right,32)",
       """signed'(X"5A")""",
       "-91 (1010_0101)", "1010_0101 (-91)",
       "0101_1010 (90)"),
      ('integer', 'integer',
       "integer'(-91)", "integer'(-91)",
       "integer'left", "integer'left",
       "integer'right", "integer'right",
       "integer'(90)",
       "-91", "-91",
       "90"),
      ('std_logic', 'std_logic',
       "'1'", "'1'",
       "'0'", "'0'",
       "'1'", "'1'",
       "'0'",
       "1", "1",
       "0"),
      ('std_logic', 'boolean',
       "'1'", "true",
       "'0'", "false",
       "'1'", "true",
       "false",
       "1", "true",
       "false"),
      ('boolean', 'std_logic',
       "true", "'1'",
       "false", "'0'",
       "true", "'1'",
       "'0'",
       "true", "1",
       "0"),
      ('boolean', 'boolean',
       "true", "true",
       "false", "false",
       "true", "true",
       "false",
       "true", "true",
       "false"),
      ('string', 'string',
       """string'("test")""", """string'("test")""",
       """string'("")""", """string'("")""",
       """string'("autogenerated test for type with no max value")""",
       """string'("autogenerated test for type with no max value")""",
       """string'("tests")""",
       "test", "test",
       "tests"),
      ('time', 'time',
       "time'(-91 ns)", "time'(-91 ns)",
       "time'left", "time'left",
       "time'right", "time'right",
       "time'(90 ns)",
       "\" & time'image(-91 ns) & \"", "\" & time'image(-91 ns) & \"",
       "\" & time'image(90 ns) & \"")]
  
  
  def generate_api():
      api = ''
      for c in combinations:
          t = Template(api_template)
          api += t.substitute(got_type=c[0], expected_type=c[1])
      return api
  
  
  def dual_format(base_type, got_or_expected):
      if got_or_expected == 'got':
          expected_or_got = 'expected'
      else:
          expected_or_got = 'got'
  
      if base_type in ['unsigned', 'signed', 'std_logic_vector']:
          return ('to_nibble_string(%s) & " (" & ' % got_or_expected
                  + "to_integer_string(%s) & " % got_or_expected + '")"')
      elif base_type == 'integer':
          return ('to_string(%s) & " (" & ' % got_or_expected
                  + "to_nibble_string(to_sufficient_signed(%s, %s'length)) & " % (got_or_expected, expected_or_got)
                  + '")"')
      else:
          return ('to_string(%s) & " (" & ' % got_or_expected
                  + "to_nibble_string(to_sufficient_unsigned(%s, %s'length)) & " % (got_or_expected, expected_or_got)
                  + '")"')
  
  
  def generate_impl():
      impl = ''
      for c in combinations:
          t = Template(impl_template)
          if (c[0] in ['unsigned', 'signed', 'std_logic_vector']) or (c[1] in ['unsigned', 'signed', 'std_logic_vector']):
              got_str = dual_format(c[0], 'got')
              expected_str = dual_format(c[1], 'expected')
          else:
              got_str = 'to_string(got)'
              expected_str = 'to_string(expected)'
          impl += t.substitute(got_type=c[0], expected_type=c[1], got_str=got_str, expected_str=expected_str)
      return impl
  
  
  def generate_test():
      test = """\
  -- This test suite verifies the check_equal 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) 2014-2019, Lars Asplund lars.anders.asplund@gmail.com
  
  library ieee;
  use ieee.std_logic_1164.all;
  use ieee.numeric_std.all;
  library vunit_lib;
  use vunit_lib.checker_pkg.all;
  use vunit_lib.check_pkg.all;
  use vunit_lib.run_types_pkg.all;
  use vunit_lib.run_pkg.all;
  use work.test_support.all;
  
  use vunit_lib.log_levels_pkg.all;
  use vunit_lib.logger_pkg.all;
  
  entity tb_check_equal is
    generic (
      runner_cfg : string);
  end entity tb_check_equal;
  
  architecture test_fixture of tb_check_equal is
  begin
    check_equal_runner : process
      variable stat : checker_stat_t;
      variable my_checker : checker_t := new_checker("my_checker");
      variable my_logger : logger_t := get_logger(my_checker);
      variable passed : boolean;
      constant default_level : log_level_t := error;
  
    begin
      test_runner_setup(runner, runner_cfg);
  
      while test_suite loop
        if run("Test should handle comparison of vectors longer than 32 bits") then
          get_checker_stat(stat);
          check_equal(unsigned'(X"A5A5A5A5A"), unsigned'(X"A5A5A5A5A"));
          check_equal(std_logic_vector'(X"A5A5A5A5A"), unsigned'(X"A5A5A5A5A"));
          check_equal(unsigned'(X"A5A5A5A5A"), std_logic_vector'(X"A5A5A5A5A"));
          check_equal(std_logic_vector'(X"A5A5A5A5A"), std_logic_vector'(X"A5A5A5A5A"));
          verify_passed_checks(stat, 4);
          verify_failed_checks(stat, 0);
  
          mock(check_logger);
          check_equal(unsigned'(X"A5A5A5A5A"), unsigned'(X"B5A5A5A5A"));
          check_only_log(check_logger, "\
  Equality check failed - Got 1010_0101_1010_0101_1010_0101_1010_0101_1010 (44465543770). \
  Expected 1011_0101_1010_0101_1010_0101_1010_0101_1010 (48760511066).", default_level);
  
          check_equal(std_logic_vector'(X"A5A5A5A5A"), unsigned'(X"B5A5A5A5A"));
          check_only_log(check_logger, "\
  Equality check failed - Got 1010_0101_1010_0101_1010_0101_1010_0101_1010 (44465543770). \
  Expected 1011_0101_1010_0101_1010_0101_1010_0101_1010 (48760511066).", default_level);
  
          check_equal(unsigned'(X"A5A5A5A5A"), std_logic_vector'(X"B5A5A5A5A"));
          check_only_log(check_logger, "\
  Equality check failed - Got 1010_0101_1010_0101_1010_0101_1010_0101_1010 (44465543770). \
  Expected 1011_0101_1010_0101_1010_0101_1010_0101_1010 (48760511066).", default_level);
  
          check_equal(std_logic_vector'(X"A5A5A5A5A"), std_logic_vector'(X"B5A5A5A5A"));
          check_only_log(check_logger, "\
  Equality check failed - Got 1010_0101_1010_0101_1010_0101_1010_0101_1010 (44465543770). \
  Expected 1011_0101_1010_0101_1010_0101_1010_0101_1010 (48760511066).", default_level);
          unmock(check_logger);
          verify_passed_checks(stat, 4);
          verify_failed_checks(stat, 4);
          reset_checker_stat;
  
        elsif run("Test print full integer vector when fail on comparison with to short vector") then
          get_checker_stat(stat);
          mock(check_logger);
          check_equal(unsigned'(X"A5"), natural'(256));
          check_only_log(check_logger, "Equality check failed - Got 1010_0101 (165). Expected 256 (1_0000_0000).",
                         default_level);
  
          check_equal(natural'(256), unsigned'(X"A5"));
          check_only_log(check_logger, "Equality check failed - Got 256 (1_0000_0000). Expected 1010_0101 (165).",
                         default_level);
  
          check_equal(unsigned'(X"A5"), natural'(2147483647));
          check_only_log(check_logger, "Equality check failed - Got 1010_0101 (165). \
  Expected 2147483647 (111_1111_1111_1111_1111_1111_1111_1111).", default_level);
  
          check_equal(signed'(X"A5"), integer'(-256));
          check_only_log(check_logger, "Equality check failed - Got 1010_0101 (-91). Expected -256 (1_0000_0000).",
                         default_level);
  
          check_equal(integer'(-256), signed'(X"A5"));
          check_only_log(check_logger, "Equality check failed - Got -256 (1_0000_0000). Expected 1010_0101 (-91).",
                         default_level);
  
          check_equal(signed'(X"05"), integer'(256));
          check_only_log(check_logger, "Equality check failed - Got 0000_0101 (5). Expected 256 (01_0000_0000).",
                         default_level);
  
          check_equal(signed'(X"A5"), integer'(-2147483648));
          check_only_log(check_logger, "Equality check failed - Got 1010_0101 (-91). \
  Expected -2147483648 (1000_0000_0000_0000_0000_0000_0000_0000).", default_level);
          unmock(check_logger);
          verify_passed_checks(stat, 0);
          verify_failed_checks(stat, 7);
          reset_checker_stat;
  """
  
      natural_equal_natural = [('natural', 'natural',
                                "natural'(165)", "natural'(165)",
                                "natural'left", "natural'left",
                                "natural'right", "natural'right",
                                "natural'(90)",
                                '165', '165',
                                '90')]
  
      for c in combinations + natural_equal_natural:
          t = Template(test_template)
          test += t.substitute(left_type=c[0], right_type=c[1],
                               left_pass=c[2], right_pass=c[3],
                               left_min=c[4], right_min=c[5],
                               left_max=c[6], right_max=c[7],
                               right_fail=c[8],
                               left_pass_str=c[9], right_pass_str=c[10],
                               fail_str=c[11])
  
      test += """\
        end if;
      end loop;
  
      test_runner_cleanup(runner);
      wait;
    end process;
  
    test_runner_watchdog(runner, 2 us);
  
  end test_fixture;
  """
      return test
  
  
  def replace_region(region_name, file_name, new_contents):
      result = ""
      inside_region = False
  
      with open(file_name, "rb") as fptr:
          contents = fptr.read().decode()
  
      previous_line = ""
      found_region = False
      for line in contents.splitlines():
          if inside_region:
              if not found_region:
                  result += new_contents
                  found_region = True
  
          if line.startswith("  -------------------"):
              inside_region = False
  
          if not inside_region:
              result += line + "\n"
  
          if previous_line.startswith("  -- %s" % region_name) and line.startswith("  ----------"):
              assert not found_region
              inside_region = True
  
          previous_line = line
  
      assert found_region
  
      with open(file_name, "wb") as fptr:
          fptr.write(result.encode())
  
  
  def main():
      check_api_file_name = join(dirname(__file__), "..", "src", "check_api.vhd")
      replace_region("check_equal", check_api_file_name, generate_api())
  
      check_file_name = join(dirname(__file__), "..", "src", "check.vhd")
      replace_region("check_equal", check_file_name, generate_impl())
  
      with open(join(dirname(__file__), "..", "test", "tb_check_equal.vhd"), "wb") as fptr:
          fptr.write(generate_test().encode())
  
  
  if __name__ == "__main__":
      main()
  