library ieee;
use ieee.std_logic_1164.all;

library vunit_lib;
-- use vunit_lib.check_pkg.all;
context vunit_lib.vunit_context;

use work.tb_pkg.all;

package tb_i2c_pkg is
  signal sda : std_logic := 'H';
  signal scl : std_logic := 'H';

  signal tx_ready : std_logic;
  signal rx_valid : std_logic;
  signal rx_data : std_logic_vector(7 downto 0);

  procedure scl_fall (
    signal scl : inout std_logic);

  procedure scl_rise (
    signal scl : inout std_logic);

  procedure scl_pulse (
    signal scl : inout std_logic);

  procedure sda_fall (
    signal sda : inout std_logic;
    constant assert_no_condition : in std_logic := '1');

  procedure sda_rise (
    signal sda : inout std_logic;
    constant assert_no_condition : in std_logic := '1');

  procedure tx_write_data (
    constant data   : in    std_logic_vector(7 downto 0);
    signal tx_data : inout std_logic_vector(7 downto 0);
    signal tx_valid : inout std_logic);

  procedure rx_read_data (
    constant exp_data : in std_logic_vector(7 downto 0);
    signal rx_confirm_read : inout std_logic);

  procedure wait_for_start_condition (
    constant timeout : in time;
    signal scl       : in std_logic;
    signal sda       : in std_logic);

  procedure wait_for_stop_condition (
    constant timeout : in time;
    signal scl       : in std_logic;
    signal sda       : in std_logic);

  procedure wait_for_scl_rise (
    constant timeout : in time;
    signal scl       : in std_logic);

  procedure wait_for_scl_fall (
    constant timeout : in time;
    signal scl       : in std_logic);

end package tb_i2c_pkg;

package body tb_i2c_pkg is
  procedure scl_fall (
    signal scl : inout std_logic) is
  begin  -- procedure scl_rise
    scl <= '0';
    wait until falling_edge(clk);
    wait until falling_edge(clk);
    wait until falling_edge(clk);
    wait until falling_edge(clk);
  end procedure scl_fall;

  procedure scl_rise (
    signal scl : inout std_logic) is
  begin  -- procedure scl_rise
    wait until falling_edge(clk);
    wait until falling_edge(clk);
    wait until falling_edge(clk);
    scl <= 'Z';
    wait until falling_edge(clk);
    wait until falling_edge(clk);
    wait until falling_edge(clk);
    wait until falling_edge(clk);
    wait until falling_edge(clk);
    wait until falling_edge(clk);
    wait until falling_edge(clk);
  end procedure scl_rise;

  procedure scl_pulse (
    signal scl : inout std_logic) is
  begin  -- procedure scl_rise
    scl_rise(scl);
    wait until falling_edge(clk);
    wait until falling_edge(clk);
    scl_fall(scl);
  end procedure scl_pulse;

  procedure sda_fall (
    signal sda : inout std_logic;
    constant assert_no_condition : in std_logic := '1') is
  begin  -- procedure scl_rise
    if assert_no_condition = '1' and sda /= '0' then
      check_equal(scl, '0', "Cannot change sda as that would trigger start condition.", failure);
    end if;

    sda <= '0';
    wait until falling_edge(clk);
  end procedure sda_fall;

  procedure sda_rise (
    signal sda : inout std_logic;
    constant assert_no_condition : in std_logic := '1') is
  begin  -- procedure scl_rise
    if assert_no_condition = '1' and sda /= '0' then
      check_equal(scl, '0', "Cannot change sda as that would trigger stop condition.", failure);
    end if;

    sda <= 'Z';
    wait until falling_edge(clk);
  end procedure sda_rise;

  procedure tx_write_data (
    constant data   : in    std_logic_vector(7 downto 0);
    signal tx_data : inout std_logic_vector(7 downto 0);
    signal tx_valid : inout std_logic
    ) is
  begin
    check_equal(tx_ready, '1', "not ready when trying to write data!");
    tx_data <= data;
    tx_valid <= '1';
    wait until falling_edge(clk);
    tx_valid <= '0';
  end procedure tx_write_data;

  procedure rx_read_data (
    constant exp_data : in std_logic_vector(7 downto 0);
    signal rx_confirm_read : inout std_logic
    ) is
  begin
    check_equal(rx_valid, '1', "not valid when trying to read data!");
    check_equal(rx_data, exp_data, "Read rx data not equal to expected");
    rx_confirm_read <= '1';
    wait until falling_edge(clk);
    rx_confirm_read <= '0';
  end procedure rx_read_data;

  procedure wait_for_start_condition (
    constant timeout : in time;
    signal scl       : in std_logic;
    signal sda       : in std_logic) is
  begin
    wait until falling_edge(sda) and scl = 'H' for timeout;
    check(sda = '0' and scl = 'H', "Did not get start condition in time.");
  end procedure wait_for_start_condition;

  procedure wait_for_stop_condition (
    constant timeout : in time;
    signal scl       : in std_logic;
    signal sda       : in std_logic) is
  begin
    wait until rising_edge(sda) and scl = 'H' for timeout;
    check(sda = 'H' and scl = 'H', "Did not get stop condition in time.");
  end procedure wait_for_stop_condition;

  procedure wait_for_scl_rise (
    constant timeout : in time;
    signal scl       : in std_logic) is
  begin
    wait until rising_edge(scl) for timeout;
    check_equal(scl, 'H', "Did not get rising scl in time.");
    wait until falling_edge(clk);
  end procedure wait_for_scl_rise;


  procedure wait_for_scl_fall (
    constant timeout : in time;
    signal scl       : in std_logic) is
  begin
    wait until falling_edge(scl) for timeout;
    check_equal(scl, '0', "Did not get falling scl in time.");
    wait until falling_edge(clk);
  end procedure wait_for_scl_fall;


end package body tb_i2c_pkg;
