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;
use work.tb_i2c_pkg.all;

package tb_i2c_slave_pkg is

  procedure i2c_slave_check_start (
    constant address : in std_logic_vector(6 downto 0);
    constant rw : in std_logic;
    constant timeout : in time;
    signal scl : inout std_logic;
    signal sda : inout std_logic;
    constant ack : in std_logic := '1');

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

  procedure i2c_slave_transmit (
    constant data        : in    std_logic_vector(7 downto 0);
    constant scl_timeout : in    time;
    signal scl           : inout std_logic;
    signal sda           : inout std_logic;
    constant exp_ack     : in    std_logic := '1');

  procedure i2c_slave_receive (
    constant exp_data       : in    std_logic_vector(7 downto 0);
    constant scl_timeout    : in    time;
    signal scl              : inout std_logic;
    signal sda              : inout std_logic;
    constant ack            : in    std_logic := '1');

end package tb_i2c_slave_pkg;

package body tb_i2c_slave_pkg is

  procedure i2c_slave_check_start (
    constant address : in std_logic_vector(6 downto 0);
    constant rw : in std_logic;
    constant timeout : in time;
    signal scl : inout std_logic;
    signal sda : inout std_logic;
    constant ack : in std_logic := '1') is
    begin
      wait_for_start_condition(timeout, scl, sda);
      i2c_slave_receive(address & rw, timeout, scl, sda, ack);
    end procedure i2c_slave_check_start;

  procedure i2c_slave_check_stop (
    constant timeout : in time;
    signal scl : inout std_logic;
    signal sda : inout std_logic) is
  begin
    wait_for_stop_condition(timeout, scl, sda);
  end procedure i2c_slave_check_stop;

  procedure i2c_slave_transmit (
    constant data        : in    std_logic_vector(7 downto 0);
    constant scl_timeout : in    time;
    signal scl           : inout std_logic;
    signal sda           : inout std_logic;
    constant exp_ack     : in    std_logic := '1') is

  begin  -- procedure transmit
    if scl = 'H' then
        wait_for_scl_fall(scl_timeout, scl);
    end if;

    -- data
    for i in 7 downto 0 loop
      wait until falling_edge(clk);
      sda <= '0' when data(i) = '0' else 'Z';
      wait_for_scl_rise(scl_timeout, scl);
      wait_for_scl_fall(scl_timeout, scl);
    end loop;  -- i

    wait until falling_edge(clk);
    sda <= 'Z';
    wait_for_scl_rise(scl_timeout, scl);

    if exp_ack = '1' then
      check_equal(sda, '0', "No acknowledge");
    elsif exp_ack = '0' then
      check_equal(sda, 'H', "There was acknowledge even though there shouldn't have been");
    end if;

    -- TODO consider removing this?
    wait_for_scl_fall(scl_timeout, scl);
    wait until falling_edge(clk);
    sda <= 'Z';
  end procedure i2c_slave_transmit;

  procedure i2c_slave_receive (
    constant exp_data       : in    std_logic_vector(7 downto 0);
    constant scl_timeout    : in    time;
    signal scl              : inout std_logic;
    signal sda              : inout std_logic;
    constant ack            : in    std_logic := '1') is

  begin  -- procedure transmit
    if scl = 'H' then
      wait_for_scl_fall(scl_timeout, scl);
    end if;

    sda <= 'Z';

    -- data
    for i in 7 downto 0 loop
      wait_for_scl_rise(scl_timeout, scl);
      if exp_data(i) = '1' then
        check(sda = '1' or sda = 'H', result("Received data (sda) not as expected."));
      elsif exp_data(i) = '0' then
        check(sda = '0' or sda = 'L', result("Received data (sda) not as expected."));
      end if;
      wait_for_scl_fall(scl_timeout, scl);
    end loop;  -- i

    if ack = '1' then
      sda <= '0';
    end if;

    wait_for_scl_rise(scl_timeout, scl);
    wait_for_scl_fall(scl_timeout, scl);

    sda <= 'Z';
  end procedure i2c_slave_receive;

end package body tb_i2c_slave_pkg;
