--===========================================================================
-- This file was written by Arpad Muranyi, Intel Corporation.  It does not
-- contain any confidential or proprietary information, and it is made
-- available to the public with the intent of promoting the usage of the
-- Multi-Lingual Extensions of the IBIS specification and to help pave the
-- way for more accurate behavioral I/O buffer models and simulations.  This
-- file may be used, modified, and distributed without any limitations;
-- however, this file is provided "as is" with no warranties whatsoever,
-- including any warranty of merchantability, noninfringement, fitness for
-- any particular purpose, or any warranty otherwise arising out of any
-- proposal, specification or sample.  Intel and the author disclaim all
-- liability, including liability or infringement of any proprietary rights,
-- including rights to use the information in this file.  No license,
-- express or implied, by estoppel or otherwise, to any intellectual
-- property rights is granted herein.
--===========================================================================
-- Base model v2.1
--
--   This version combines the analog equations in order to reduce the
--   number of quantities used.                             A.M. 4/5/2005
--
-- Base model v2.0
--
--   This version uses a different digital logic to find the state of the
--   buffer.  Process "Catch" was changed, processes "pu_on_event_time",
--   "pu_off_event_time", "pd_on_event_time", "pd_off_event_time" were
--   added, and the "simultaneous analog equations" section was also 
--   modified to accomodate the above changes.  This model is more compact
--   compared with the older version.                       A.M. 2/27/2004
--
-- Features:
--
--   1)  This model supports only basic (incomplete) IBIS 2.1 features
--
--   2)  This model uses 4 IV curves (I/O buffer model)
--
--   3)  This model uses 4 Vt curves (I/O buffer model)
--         Vt tables do not have to be the same length,
--         Vt tables do not have to start and/or end at the same time,
--         Vt tables do not have to have the same time points
--
--   4)  Model has common time axis generator for the Vt curves (executes
--       once during initialization of constants)
--
--   5)  Model has time axis interpolator for improved resolution with Vt
--       curves having a few points, or large dt between points (executes
--       once during initialization of constants)
--
--   6)  Includes Vt curve to scaling coefficient converter (executes once
--       during initialization of constants)
--
--   7)  This model has a 4-way split constant C_comp
--
--   8)  Includes complete logic for Input and Enable
--         High-to-Low / Low-to-High while enable is on or off
--         Enable / Disable while input is High or Low
--         Enable / Disable while input goes High-to-Low or Low-to-High
--
-- To be done:
--
--   1)  Add receiver model (threshold sensing, etc...)
--   2)  Add support for the Ramp keyword
--   3)  Provisions for switching into unfinished edges
--   4)  Test terminations using voltages other than V_fixture
--   5)  Provide on-die terminations as used with Submodel, etc.
--   6)  Data dependent switching and strength characteristics support
--   7)  Covering other buffer types (input, output, open_***, etc...)
--   8)  Receiver threshold modeling and logic
--   9)  File I/O to read IV, Vt, etc. data from external files
--  10)  Differencial I/O model version
--===========================================================================
library IEEE;
use IEEE.math_real.all;
use IEEE.std_logic_1164.all;
use IEEE.electrical_systems.all;
use IEEE.energy_systems.all;
--===========================================================================
entity IBIS_IO is

  generic (C_comp      : real := 4.55e-12;  -- Default C_comp value and
           k_C_comp_pc : real := 0.25;      -- splitting coefficients
           k_C_comp_pu : real := 0.25;
           k_C_comp_pd : real := 0.25;
           k_C_comp_gc : real := 0.25;
           ------------------------------------------------------------------
           -- [Pullup Reference] and [Pulldown Reference] values
           ------------------------------------------------------------------
           V_pu_ref : real := 5.0;
           V_pd_ref : real := 0.0;
           ------------------------------------------------------------------
           -- Vectors of the IV curve tables
           ------------------------------------------------------------------
           I_pc : real_vector := ( 0.08,  0.00,  0.00,  0.00);
           V_pc : real_vector := (-5.00, -1.00,  5.00, 10.00);
           I_pu : real_vector := ( 0.10,  0.00, -0.10, -0.20);
           V_pu : real_vector := (-5.00,  0.00,  5.00, 10.00);
           I_pd : real_vector := (-0.10,  0.00,  0.10,  0.20);
           V_pd : real_vector := (-5.00,  0.00,  5.00, 10.00);
           I_gc : real_vector := (-0.08,  0.00,  0.00,  0.00);
           V_gc : real_vector := (-5.00, -1.00,  5.00, 10.00);
           ------------------------------------------------------------------
           -- Vectors of the Vt curve tables
           ------------------------------------------------------------------
           V_pu_on  : real_vector := ( 0.00,  0.00,     2.50,     2.50);
           T_pu_on  : real_vector := ( 0.00,  1.00e-9,  2.00e-9,  3.00e-9);
           V_pu_off : real_vector := ( 2.50,  2.50,     0.00,     0.00);
           T_pu_off : real_vector := ( 0.00,  0.50e-9,  0.80e-9,  3.00e-9);
           V_pd_on  : real_vector := ( 5.00,  5.00,     2.50,     2.50);
           T_pd_on  : real_vector := ( 0.00,  1.00e-9,  2.00e-9,  3.00e-9);
           V_pd_off : real_vector := ( 2.50,  2.50,     5.00,     5.00);
           T_pd_off : real_vector := ( 0.00,  0.50e-9,  0.80e-9,  3.00e-9);
           ------------------------------------------------------------------
           -- V_fixture and R_fixture values
           ------------------------------------------------------------------
           Vfx_pu_on  : real := 0.0;
           Vfx_pu_off : real := 0.0;
           Vfx_pd_on  : real := 5.0;
           Vfx_pd_off : real := 5.0;

           Rfx_pu_on  : real := 50.0;
           Rfx_pu_off : real := 50.0;
           Rfx_pd_on  : real := 50.0;
           Rfx_pd_off : real := 50.0;
           ------------------------------------------------------------------
           Delta_t    : real := 10.0e-12);      -- This parameter
           -- determines what the maximum time delta will be between the
           -- points of the Vt curves and scaling coefficient curves
           -- after preprocessing the input data.
     ------------------------------------------------------------------------
     port (signal   In_D   : in   std_logic;
           signal   En_D   : in   std_logic;
           signal   Rcv_D  : out  std_logic;

           terminal IO     :      electrical;
           terminal PC_ref :      electrical;
           terminal PU_ref :      electrical;
           terminal PD_ref :      electrical;
           terminal GC_ref :      electrical);

end entity IBIS_IO;
--===========================================================================
architecture IO_1EQ of IBIS_IO is

  quantity  Vpc    across  Ipc    through  PC_ref  to  IO;
  quantity  Vpu    across  Ipu    through  PU_ref  to  IO;
  quantity  Vpd    across  Ipd    through  IO      to  PD_ref;
  quantity  Vgc    across  Igc    through  IO      to  GC_ref;

  signal    pu_on    : std_logic := '0';
  signal    pu_off   : std_logic := '0';
  signal    pd_on    : std_logic := '0';
  signal    pd_off   : std_logic := '0';

  signal    Tpu_on_event  : real := 0.0;
  signal    Tpd_off_event : real := 0.0;
  signal    Tpd_on_event  : real := 0.0;
  signal    Tpu_off_event : real := 0.0;

  quantity  k_pu          : real := 0.0;
  quantity  k_pd          : real := 0.0;
--===========================================================================
  function Lookup (Extrapolation : in string := "IV";
                   X             : in real;
                   Ydata         : in real_vector;
                   Xdata         : in real_vector) return real is 
  ---------------------------------------------------------------------------
  -- This function is basically the equivalent of a PWL function in SPICE.
  -- It returns "Y" that corresponds to "X" in the "Ydata" "Xdata" input pair
  -- using linear interpolation.
  --
  -- If the "X" input value lies outside the range of "Xdata", the returned
  -- "Y" value will either be equal to the first or last point in "Ydata",
  -- or it will be calculated using the slope between the first or last two
  -- points of "Ydata".  The extrapolation method is determined by the string
  -- in "Extrapolation".  "Vt" selects the repeated points method, "IV"
  -- selects the last slopes method.
  --
  -- (The original code of this function was received from Mentor Graphics,
  -- modifications written by Arpad Muranyi, Intel Corporation).
  ---------------------------------------------------------------------------
    variable xvalue, yvalue, m : real;
    variable start, fin, mid   : integer; 
  ---------------------------------------------------------------------------
  begin
    -------------------------------------------------------------------------
    -- Handle cases when "X" is outside the range of "Xdata"
    -------------------------------------------------------------------------
    if (Extrapolation = "IV") and (X <= Xdata(0)) then
      m := (Ydata(1) - Ydata(0)) / (Xdata(1) - Xdata(0));
      yvalue := Ydata(0) + m * (X - Xdata(0));
      return yvalue;

    elsif (Extrapolation = "Vt") and (X <= Xdata(0)) then
      yvalue := Ydata(0);
      return yvalue;

    elsif (Extrapolation = "IV") and (X >= Xdata(Xdata'right)) then
      m := (Ydata(Ydata'right) - Ydata(Ydata'right - 1)) / (Xdata(Xdata'right) - Xdata(Xdata'right - 1));
      yvalue := Ydata(Ydata'right) + m * (X - Xdata(Xdata'right));
      return yvalue;

    elsif (Extrapolation = "Vt") and (X >= Xdata(Xdata'right)) then
      yvalue := Ydata(Ydata'right);
      return yvalue;
    -------------------------------------------------------------------------
    -- Handle cases when "X" is in the range of "Xdata"
    -------------------------------------------------------------------------
    else
      start:= 0;
      fin := Xdata'right;

      while  start <= fin  loop
        mid := (start + fin) / 2; 

        if Xdata(mid) < X then
          start := mid + 1;
        else fin := mid - 1;
        end if;  

      end loop; 
                       
      if Xdata(mid) > X then
        mid := mid - 1; 
      end if;
      -----------------------------------------------------------------------
      -- Find "Y" by linear interpolation
      -----------------------------------------------------------------------
      yvalue := Ydata(mid) + (X - Xdata(mid)) * (Ydata(mid+1) - Ydata(mid)) / (Xdata(mid+1) - Xdata(mid));
      return yvalue;
    -------------------------------------------------------------------------
    end if;
    -------------------------------------------------------------------------
  end function Lookup;
--===========================================================================
  function Find_common_length (Max_dt : real := 1.0e-12;
                               Twfm_1 : in real_vector;
                               Twfm_2 : in real_vector;
                               Twfm_3 : in real_vector;
                               Twfm_4 : in real_vector) return integer is
  ---------------------------------------------------------------------------
  -- This function finds the total number of points needed for having a
  -- common time axis for all Vt curves, such that the maximum delta time
  -- between each time point doesn't exceed the value provided in "Max_dt".
  ---------------------------------------------------------------------------
  variable Common_length : integer := 0;
  variable Return_val    : integer := 0;

  variable index_1       : integer := Twfm_1'left;
  variable index_2       : integer := Twfm_2'left;
  variable index_3       : integer := Twfm_3'left;
  variable index_4       : integer := Twfm_4'left;

  variable new_index_1   : integer := Twfm_1'left;
  variable new_index_2   : integer := Twfm_2'left;
  variable new_index_3   : integer := Twfm_3'left;
  variable new_index_4   : integer := Twfm_4'left;

  variable old_t         : real    := 0.0;
  variable new_t         : real    := 0.0;
  variable remainder     : real    := 0.0;
  variable min_dt        : real    := 1.0e-3;  -- This sets the size of the
                                               -- roundoff error relative to
                                               -- "Max_dt"
  ---------------------------------------------------------------------------
  begin
  ---------------------------------------------------------------------------
  -- Put the earliest time value of all given time vectors into "old_t"
  ---------------------------------------------------------------------------
    old_t := Twfm_1(Twfm_1'left);
    if (Twfm_2(Twfm_2'left) < old_t) then old_t := Twfm_2(Twfm_2'left);
    end if;
    if (Twfm_3(Twfm_3'left) < old_t) then old_t := Twfm_3(Twfm_3'left);
    end if;
    if (Twfm_4(Twfm_4'left) < old_t) then old_t := Twfm_4(Twfm_4'left);
    end if;
  ---------------------------------------------------------------------------
  -- Put the latest time value of all given time vectors into "new_t"
  ---------------------------------------------------------------------------
    new_t := Twfm_1(Twfm_1'right);
    if (Twfm_2(Twfm_2'right) > new_t) then new_t := Twfm_2(Twfm_2'right);
    end if;
    if (Twfm_3(Twfm_3'right) > new_t) then new_t := Twfm_3(Twfm_3'right);
    end if;
    if (Twfm_4(Twfm_4'right) > new_t) then new_t := Twfm_4(Twfm_4'right);
    end if;
    new_t := new_t;
  ---------------------------------------------------------------------------
  -- Loop until latest time value is reached in each given time vector
  ---------------------------------------------------------------------------
  while (old_t < new_t) loop
    -------------------------------------------------------------------------
    -- Find which given time vector(s) have the lowest time value and
    -- advance their temporary indexes
    -------------------------------------------------------------------------
    if (Twfm_1(index_1) <= old_t) then new_index_1 := index_1 + 1;
    end if;
    if (Twfm_2(index_2) <= old_t) then new_index_2 := index_2 + 1;
    end if;
    if (Twfm_3(index_3) <= old_t) then new_index_3 := index_3 + 1;
    end if;
    if (Twfm_4(index_4) <= old_t) then new_index_4 := index_4 + 1;
    end if;
    -------------------------------------------------------------------------
    -- Find the lowest value at the new indexes in the given vector(s) and
    -- update indexes for next iteration
    -------------------------------------------------------------------------
    if (new_index_1 <= Twfm_1'right) then
      if (Twfm_1(new_index_1) < new_t) then new_t := Twfm_1(new_index_1);
      end if;
      index_1 := new_index_1;
    end if;
    if (new_index_2 <= Twfm_2'right) then
      if (Twfm_2(new_index_2) < new_t) then new_t := Twfm_2(new_index_2);
      end if;
      index_2 := new_index_2;
    end if;
    if (new_index_3 <= Twfm_3'right) then
      if (Twfm_3(new_index_3) < new_t) then new_t := Twfm_3(new_index_3);
      end if;
      index_3 := new_index_3;
    end if;
    if (new_index_4 <= Twfm_4'right) then
      if (Twfm_4(new_index_4) < new_t) then new_t := Twfm_4(new_index_4);
      end if;
      index_4 := new_index_4;
    end if;
    -------------------------------------------------------------------------
    -- Calculate how many additional points are needed between the given
    -- points to satisfy the "Max_dt" separation criteria.  Note: the extra
    -- logic is needed due to floating point inaccuracies.
    -------------------------------------------------------------------------
    Common_length := Common_length + integer(ceil((new_t-old_t)/Max_dt));

    remainder := "mod"((new_t-old_t),Max_dt);
    if (remainder < (min_dt * Max_dt)) and (remainder > 0.0) then
      Common_length := Common_length - 1;
    end if;
    -------------------------------------------------------------------------
    -- Update variables for next iteration
    -------------------------------------------------------------------------
    old_t   := new_t;

    new_t := Twfm_1(Twfm_1'right);
    if (Twfm_2(Twfm_2'right) > new_t) then new_t := Twfm_2(Twfm_2'right);
    end if;
    if (Twfm_3(Twfm_3'right) > new_t) then new_t := Twfm_3(Twfm_3'right);
    end if;
    if (Twfm_4(Twfm_4'right) > new_t) then new_t := Twfm_4(Twfm_4'right);
    end if;
    new_t := new_t;
  ---------------------------------------------------------------------------
  end loop; 
  ---------------------------------------------------------------------------
  return Common_length;
  end function Find_common_length;
--===========================================================================
  function Common_time (Max_dt : real := 1.0e-12;
                        Twfm_1 : in real_vector;
                        Twfm_2 : in real_vector;
                        Twfm_3 : in real_vector;
                        Twfm_4 : in real_vector) return real_vector is
  ---------------------------------------------------------------------------
  -- This function generates a vector that serves as a common time axis for
  -- all Vt curves, such that the maximum delta time between each time point
  -- doesn't exceed the value provided in "Max_dt".
  ---------------------------------------------------------------------------
  variable New_time     : real_vector(0 to Find_common_length(Max_dt, Twfm_1, Twfm_2, Twfm_3, Twfm_4)-1) := (others => 0.0);

  variable index        : integer := 0;
  variable extra_start  : integer := 0;
  variable extra_points : integer := 0;

  variable index_1      : integer := Twfm_1'left;
  variable index_2      : integer := Twfm_2'left;
  variable index_3      : integer := Twfm_3'left;
  variable index_4      : integer := Twfm_4'left;

  variable new_index_1  : integer := Twfm_1'left;
  variable new_index_2  : integer := Twfm_2'left;
  variable new_index_3  : integer := Twfm_3'left;
  variable new_index_4  : integer := Twfm_4'left;

  variable old_t        : real    := 0.0;
  variable new_t        : real    := 0.0;
  variable remainder    : real    := 0.0;
  variable min_dt       : real    := 1.0e-3;   -- This sets the size of the
                                               -- roundoff error relative to
                                               -- "Max_dt"
  ---------------------------------------------------------------------------
  begin
  ---------------------------------------------------------------------------
  -- Put the earliest time value of all given time vectors into "old_t"
  ---------------------------------------------------------------------------
    old_t := Twfm_1(Twfm_1'left);
    if (Twfm_2(Twfm_2'left) < old_t) then old_t := Twfm_2(Twfm_2'left);
    end if;
    if (Twfm_3(Twfm_3'left) < old_t) then old_t := Twfm_3(Twfm_3'left);
    end if;
    if (Twfm_4(Twfm_4'left) < old_t) then old_t := Twfm_4(Twfm_4'left);
    end if;
  ---------------------------------------------------------------------------
  -- Put the latest time value of all given time vectors into "new_t"
  ---------------------------------------------------------------------------
    new_t := Twfm_1(Twfm_1'right);
    if (Twfm_2(Twfm_2'right) > new_t) then new_t := Twfm_2(Twfm_2'right);
    end if;
    if (Twfm_3(Twfm_3'right) > new_t) then new_t := Twfm_3(Twfm_3'right);
    end if;
    if (Twfm_4(Twfm_4'right) > new_t) then new_t := Twfm_4(Twfm_4'right);
    end if;
    new_t := new_t;
  ---------------------------------------------------------------------------
  -- Loop until last index is reached in each given time vector
  ---------------------------------------------------------------------------
  index := New_time'left;
  while (old_t < new_t) loop
    -------------------------------------------------------------------------
    -- Find which given time vector(s) have the lowest time value and
    -- advance their temporary indexes
    -------------------------------------------------------------------------
    if (Twfm_1(index_1) <= old_t) then new_index_1 := index_1 + 1;
    end if;
    if (Twfm_2(index_2) <= old_t) then new_index_2 := index_2 + 1;
    end if;
    if (Twfm_3(index_3) <= old_t) then new_index_3 := index_3 + 1;
    end if;
    if (Twfm_4(index_4) <= old_t) then new_index_4 := index_4 + 1;
    end if;
    -------------------------------------------------------------------------
    -- Find the lowest value at the new indexes in the given vector(s) and
    -- update indexes for next iteration
    -------------------------------------------------------------------------
    if (new_index_1 <= Twfm_1'right) then
      if (Twfm_1(new_index_1) < new_t) then new_t := Twfm_1(new_index_1);
      end if;
      index_1 := new_index_1;
    end if;
    if (new_index_2 <= Twfm_2'right) then
      if (Twfm_2(new_index_2) < new_t) then new_t := Twfm_2(new_index_2);
      end if;
      index_2 := new_index_2;
    end if;
    if (new_index_3 <= Twfm_3'right) then
      if (Twfm_3(new_index_3) < new_t) then new_t := Twfm_3(new_index_3);
      end if;
      index_3 := new_index_3;
    end if;
    if (new_index_4 <= Twfm_4'right) then
      if (Twfm_4(new_index_4) < new_t) then new_t := Twfm_4(new_index_4);
      end if;
      index_4 := new_index_4;
    end if;
    -------------------------------------------------------------------------
    -- Calculate how many additional points are needed between the given
    -- points to satisfy the "Max_dt" separation criteria.  Note: the extra
    -- logic is needed due to floating point inaccuracies.
    -------------------------------------------------------------------------
    extra_points := integer(ceil((new_t-old_t)/Max_dt));

    remainder := "mod"((new_t-old_t),Max_dt);
    if remainder < (min_dt * Max_dt) and
       remainder > 0.0 then extra_points := extra_points - 1;
    end if;
    -------------------------------------------------------------------------
    -- Calculate and write the values of points into the "New_time" vector
    -------------------------------------------------------------------------
    for x_index in 0 to (extra_points - 1) loop
      New_time(index) := old_t + (real(x_index) * (new_t-old_t)) / real(extra_points);
      index := index + 1;
    end loop; 
    -------------------------------------------------------------------------
    -- Update variables for next iteration
    -------------------------------------------------------------------------
    old_t   := new_t;

    new_t := Twfm_1(Twfm_1'right);
    if (Twfm_2(Twfm_2'right) > new_t) then new_t := Twfm_2(Twfm_2'right);
    end if;
    if (Twfm_3(Twfm_3'right) > new_t) then new_t := Twfm_3(Twfm_3'right);
    end if;
    if (Twfm_4(Twfm_4'right) > new_t) then new_t := Twfm_4(Twfm_4'right);
    end if;
    new_t := new_t;
  ---------------------------------------------------------------------------
  end loop; 
  ---------------------------------------------------------------------------
  -- Insert the largest value at the end of the given vector(s) into the
  -- last position of "New_time".  (The loop above stops at the next to
  -- the last point).
  ---------------------------------------------------------------------------
  New_time(New_time'right) := new_t;
  ---------------------------------------------------------------------------
  return New_time;
  end function Common_time;
--===========================================================================
  function Common_wfm (New_t : in real_vector;
                       Vwfm  : in real_vector;
                       Twfm  : in real_vector) return real_vector is
  ---------------------------------------------------------------------------
  -- This function generates an interpolated vector based on the input vector
  -- "New_t" using the input vector pair "Vwfm" and "Twfm".
  ---------------------------------------------------------------------------
  variable New_v : real_vector(New_t'range) := (others => 1.0);
  ---------------------------------------------------------------------------
  begin
                                        -- This is the better notation
                                        -- but it doesn't work due to
--  for index in New_v'range loop       -- a bug in SystemVision
    for index in New_v'left to New_v'right loop
      New_v(index) := Lookup("Vt", New_t(index), Vwfm, Twfm);
    end loop; 

  return New_v;
  end function Common_wfm;
--===========================================================================
constant T_common        : real_vector := Common_time(Delta_t, T_pu_on,  T_pu_off, T_pd_on, T_pd_off);
constant V_pu_on_common  : real_vector := Common_wfm(T_common, V_pu_on,  T_pu_on);
constant V_pu_off_common : real_vector := Common_wfm(T_common, V_pu_off, T_pu_off);
constant V_pd_on_common  : real_vector := Common_wfm(T_common, V_pd_on,  T_pd_on);
constant V_pd_off_common : real_vector := Common_wfm(T_common, V_pd_off, T_pd_off);
--===========================================================================
  function Coeff (Edge : in string;
                  Vwfm : in real_vector;
                  Twfm : in real_vector;
                  Rfx  : in real;
                  Vfx  : in real;
                  Iiv  : in real_vector;
                  Viv  : in real_vector;
                  Vref : in real) return real_vector is
  ---------------------------------------------------------------------------
  -- This function converts the Vt curve to a corresponding scaling
  -- coefficient curve that is used to scale the IV curve with respect
  -- to time.  The effects of C_comp on the waveforms are included in the
  -- scaling coefficient, so that the presence of C_comp in the output
  -- stage will not "load" the driver, i.e. C_comp in the output stage 
  -- will not derate the original waveforms in the Vt curves.
  ---------------------------------------------------------------------------
  variable Kout  : real_vector(Vwfm'range);
  variable dVwfm : real_vector(Vwfm'range);
  
  variable I1    : real := 1.0;
  variable Ifx1  : real := 0.0;
  ---------------------------------------------------------------------------
  begin
    for index in Vwfm'range loop
    -------------------------------------------------------------------------
    -- Generate the derivative of the waveform
    -- Force the derivatives of the first and last points to zero
    -------------------------------------------------------------------------
      if (index = Vwfm'left) then
        dVwfm(index) := 0.0;
        dVwfm(index) := 0.0;
      elsif (index = Vwfm'right) then
        dVwfm(index) := 0.0;
        dVwfm(index) := 0.0;
      -----------------------------------------------------------------------
      -- Calculate the derivative of a point using both of its neighbors
      -----------------------------------------------------------------------
      else
        dVwfm(index) := ((Vwfm(index) - Vwfm(index-1))/(Twfm(index) - Twfm(index-1)) +
                         (Vwfm(index+1) - Vwfm(index))/(Twfm(index+1) - Twfm(index)))/2.0;
      end if;
      -----------------------------------------------------------------------
      -- Calculate intermediate currents
      -----------------------------------------------------------------------
      if (Edge = "K_pu_on") or (Edge = "K_pu_off") then
        I1   := Lookup("IV", Vref - Vwfm(index), Iiv, Viv);
        Ifx1 := realmin(((Vfx - Vwfm(index)) / Rfx), 0.0) - C_comp * dVwfm(index);
      elsif (Edge = "K_pd_on") or (Edge = "K_pd_off") then
        I1   := Lookup("IV", Vwfm(index) - Vref, Iiv, Viv);
        Ifx1 := realmax(((Vfx - Vwfm(index)) / Rfx), 0.0) - C_comp * dVwfm(index);
      else
        I1   := 1.0;
        Ifx1 := 0.0;
      end if;

      Kout(index) := Ifx1 / I1;
    -------------------------------------------------------------------------
    end loop;
    -------------------------------------------------------------------------
  return Kout;
  end function Coeff;
--===========================================================================
  constant  K_pu_on  : real_vector := Coeff("K_pu_on",  V_pu_on_common,  T_common, Rfx_pu_on,  Vfx_pu_on,  I_pu, V_pu, V_pu_ref);
  constant  K_pd_off : real_vector := Coeff("K_pd_off", V_pd_off_common, T_common, Rfx_pd_off, Vfx_pd_off, I_pd, V_pd, V_pd_ref);
  constant  K_pd_on  : real_vector := Coeff("K_pd_on",  V_pd_on_common,  T_common, Rfx_pd_on,  Vfx_pd_on,  I_pd, V_pd, V_pd_ref);
  constant  K_pu_off : real_vector := Coeff("K_pu_off", V_pu_off_common, T_common, Rfx_pu_off, Vfx_pu_off, I_pu, V_pu, V_pu_ref);
--===========================================================================
begin
--===========================================================================
  Catch: process (In_D, En_D) is
  ---------------------------------------------------------------------------
  begin
    Rcv_D <= In_D;                               -- Dummy receiver logic
-------------------------------------------------------------------------
    if (En_D = '1') and (In_D = '1') then        -- Find logic state
      pu_on  <= '1';
      pd_off <= '1';
      pd_on  <= '0';
      pu_off <= '0';
    elsif (En_D = '1') and (In_D = '0') then
      pu_on  <= '0';
      pd_off <= '0';
      pd_on  <= '1';
      pu_off <= '1';
    elsif (En_D = '0') then
      pu_on  <= '0';
      pd_off <= '1';
      pd_on  <= '0';
      pu_off <= '1';
    else
      pu_on  <= '1';
      pd_off <= '0';
      pd_on  <= '1';
      pu_off <= '0';
    end if;
  ---------------------------------------------------------------------------
  end process Catch;
--===========================================================================
  pu_on_event_time: process (pu_on) is      -- Update event time if changed
  ---------------------------------------------------------------------------
  begin
      Tpu_on_event <= now;
  ---------------------------------------------------------------------------
  end process pu_on_event_time;
--===========================================================================
  pu_off_event_time: process (pu_off) is    -- Update event time if changed
  ---------------------------------------------------------------------------
  begin
      Tpu_off_event <= now;
  ---------------------------------------------------------------------------
  end process pu_off_event_time;
--===========================================================================
  pd_on_event_time: process (pd_on) is      -- Update event time if changed
  ---------------------------------------------------------------------------
  begin
      Tpd_on_event <= now;
  ---------------------------------------------------------------------------
  end process pd_on_event_time;
--===========================================================================
  pd_off_event_time: process (pd_off) is    -- Update event time if changed
  ---------------------------------------------------------------------------
  begin
      Tpd_off_event <= now;
  ---------------------------------------------------------------------------
  end process pd_off_event_time;
--===========================================================================
  break on pu_on;
  break on pu_off;
  break on pd_on;
  break on pd_off;
--===========================================================================
  -- This section contains the simultaneous analog equations to find the
  -- appropriate scaling coefficients according to the state the buffer.
  ---------------------------------------------------------------------------
  if (Tpu_on_event = 0.0 and Tpu_off_event = 0.0 and
      Tpd_on_event = 0.0 and Tpd_off_event = 0.0) use
                                                 -- Initialization
    if (pu_on = '1') use                         -- Start with the end of the
      k_pu == K_pu_on(K_pu_on'right);            -- Vt curves for those which
    elsif (pu_off = '1') use                     -- are fully on initially
      k_pu == K_pu_off(K_pu_off'right);
    else
      k_pu == 0.0;
    end use;
    
    if (pd_on = '1') use
      k_pd == K_pd_on(K_pd_on'right);
    elsif (pd_off = '1') use
      k_pd == K_pd_off(K_pd_off'right);
    else
      k_pd == 0.0;
    end use;

  else                            -- Look up coefficients in normal operation

    if    (pu_on  = '1') use k_pu == Lookup("Vt", now - Tpu_on_event, K_pu_on,  T_common);
    elsif (pu_off = '1') use k_pu == Lookup("Vt", now - Tpu_off_event, K_pu_off, T_common);
    else                     k_pu == K_pu_on(K_pu_on'left);
    end use;

    if    (pd_on  = '1') use k_pd == Lookup("Vt", now - Tpd_on_event, K_pd_on,  T_common);
    elsif (pd_off = '1') use k_pd == Lookup("Vt", now - Tpd_off_event, K_pd_off, T_common);
    else                     k_pd == K_pd_on(K_pd_on'left);
    end use;

  end use;
--===========================================================================
  -- This section contains the simultaneous analog equations which calculate
  -- the output currents of the IV curves and C_comp capacitors.
  ---------------------------------------------------------------------------
  Ipc   == -1.0     *Lookup("IV", Vpc, I_pc, V_pc) + k_C_comp_pc*C_comp*Vpc'dot; 
  Ipu   == -1.0*k_pu*Lookup("IV", Vpu, I_pu, V_pu) + k_C_comp_pu*C_comp*Vpu'dot; 
  Ipd   ==      k_pd*Lookup("IV", Vpd, I_pd, V_pd) + k_C_comp_pd*C_comp*Vpd'dot;
  Igc   ==           Lookup("IV", Vgc, I_gc, V_gc) + k_C_comp_gc*C_comp*Vgc'dot;

--===========================================================================
end architecture IO_1EQ;
--===========================================================================
--***************************************************************************
--===========================================================================
architecture IO_2EQ of IBIS_IO is

  quantity  Vpc    across  Ipc    through  PC_ref  to  IO;
  quantity  Vpu    across  Ipu    through  PU_ref  to  IO;
  quantity  Vpd    across  Ipd    through  IO      to  PD_ref;
  quantity  Vgc    across  Igc    through  IO      to  GC_ref;

  signal    pu_on    : std_logic := '0';
  signal    pu_off   : std_logic := '0';
  signal    pd_on    : std_logic := '0';
  signal    pd_off   : std_logic := '0';

  signal    Tpu_on_event  : real := 0.0;
  signal    Tpd_off_event : real := 0.0;
  signal    Tpd_on_event  : real := 0.0;
  signal    Tpu_off_event : real := 0.0;

  quantity  k_pu          : real := 0.0;
  quantity  k_pd          : real := 0.0;
--===========================================================================
  function Lookup (Extrapolation : in string := "IV";
                   X             : in real;
                   Ydata         : in real_vector;
                   Xdata         : in real_vector) return real is 
  ---------------------------------------------------------------------------
  -- This function is basically the equivalent of a PWL function in SPICE.
  -- It returns "Y" that corresponds to "X" in the "Ydata" "Xdata" input pair
  -- using linear interpolation.
  --
  -- If the "X" input value lies outside the range of "Xdata", the returned
  -- "Y" value will either be equal to the first or last point in "Ydata",
  -- or it will be calculated using the slope between the first or last two
  -- points of "Ydata".  The extrapolation method is determined by the string
  -- in "Extrapolation".  "Vt" selects the repeated points method, "IV"
  -- selects the last slopes method.
  --
  -- (The original code of this function was received from Mentor Graphics,
  -- modifications written by Arpad Muranyi, Intel Corporation).
  ---------------------------------------------------------------------------
    variable xvalue, yvalue, m : real;
    variable start, fin, mid   : integer; 
  ---------------------------------------------------------------------------
  begin
    -------------------------------------------------------------------------
    -- Handle cases when "X" is outside the range of "Xdata"
    -------------------------------------------------------------------------
    if (Extrapolation = "IV") and (X <= Xdata(0)) then
      m := (Ydata(1) - Ydata(0)) / (Xdata(1) - Xdata(0));
      yvalue := Ydata(0) + m * (X - Xdata(0));
      return yvalue;

    elsif (Extrapolation = "Vt") and (X <= Xdata(0)) then
      yvalue := Ydata(0);
      return yvalue;

    elsif (Extrapolation = "IV") and (X >= Xdata(Xdata'right)) then
      m := (Ydata(Ydata'right) - Ydata(Ydata'right - 1)) / (Xdata(Xdata'right) - Xdata(Xdata'right - 1));
      yvalue := Ydata(Ydata'right) + m * (X - Xdata(Xdata'right));
      return yvalue;

    elsif (Extrapolation = "Vt") and (X >= Xdata(Xdata'right)) then
      yvalue := Ydata(Ydata'right);
      return yvalue;
    -------------------------------------------------------------------------
    -- Handle cases when "X" is in the range of "Xdata"
    -------------------------------------------------------------------------
    else
      start:= 0;
      fin := Xdata'right;

      while  start <= fin  loop
        mid := (start + fin) / 2; 

        if Xdata(mid) < X then
          start := mid + 1;
        else fin := mid - 1;
        end if;  

      end loop; 
                       
      if Xdata(mid) > X then
        mid := mid - 1; 
      end if;
      -----------------------------------------------------------------------
      -- Find "Y" by linear interpolation
      -----------------------------------------------------------------------
      yvalue := Ydata(mid) + (X - Xdata(mid)) * (Ydata(mid+1) - Ydata(mid)) / (Xdata(mid+1) - Xdata(mid));
      return yvalue;
    -------------------------------------------------------------------------
    end if;
    -------------------------------------------------------------------------
  end function Lookup;
--===========================================================================
  function Find_common_length (Max_dt : real := 1.0e-12;
                               Twfm_1 : in real_vector;
                               Twfm_2 : in real_vector;
                               Twfm_3 : in real_vector;
                               Twfm_4 : in real_vector) return integer is
  ---------------------------------------------------------------------------
  -- This function finds the total number of points needed for having a
  -- common time axis for all Vt curves, such that the maximum delta time
  -- between each time point doesn't exceed the value provided in "Max_dt".
  ---------------------------------------------------------------------------
  variable Common_length : integer := 0;
  variable Return_val    : integer := 0;

  variable index_1       : integer := Twfm_1'left;
  variable index_2       : integer := Twfm_2'left;
  variable index_3       : integer := Twfm_3'left;
  variable index_4       : integer := Twfm_4'left;

  variable new_index_1   : integer := Twfm_1'left;
  variable new_index_2   : integer := Twfm_2'left;
  variable new_index_3   : integer := Twfm_3'left;
  variable new_index_4   : integer := Twfm_4'left;

  variable old_t         : real    := 0.0;
  variable new_t         : real    := 0.0;
  variable remainder     : real    := 0.0;
  variable min_dt        : real    := 1.0e-3;  -- This sets the size of the
                                               -- roundoff error relative to
                                               -- "Max_dt"
  ---------------------------------------------------------------------------
  begin
  ---------------------------------------------------------------------------
  -- Put the earliest time value of all given time vectors into "old_t"
  ---------------------------------------------------------------------------
    old_t := Twfm_1(Twfm_1'left);
    if (Twfm_2(Twfm_2'left) < old_t) then old_t := Twfm_2(Twfm_2'left);
    end if;
    if (Twfm_3(Twfm_3'left) < old_t) then old_t := Twfm_3(Twfm_3'left);
    end if;
    if (Twfm_4(Twfm_4'left) < old_t) then old_t := Twfm_4(Twfm_4'left);
    end if;
  ---------------------------------------------------------------------------
  -- Put the latest time value of all given time vectors into "new_t"
  ---------------------------------------------------------------------------
    new_t := Twfm_1(Twfm_1'right);
    if (Twfm_2(Twfm_2'right) > new_t) then new_t := Twfm_2(Twfm_2'right);
    end if;
    if (Twfm_3(Twfm_3'right) > new_t) then new_t := Twfm_3(Twfm_3'right);
    end if;
    if (Twfm_4(Twfm_4'right) > new_t) then new_t := Twfm_4(Twfm_4'right);
    end if;
    new_t := new_t;
  ---------------------------------------------------------------------------
  -- Loop until latest time value is reached in each given time vector
  ---------------------------------------------------------------------------
  while (old_t < new_t) loop
    -------------------------------------------------------------------------
    -- Find which given time vector(s) have the lowest time value and
    -- advance their temporary indexes
    -------------------------------------------------------------------------
    if (Twfm_1(index_1) <= old_t) then new_index_1 := index_1 + 1;
    end if;
    if (Twfm_2(index_2) <= old_t) then new_index_2 := index_2 + 1;
    end if;
    if (Twfm_3(index_3) <= old_t) then new_index_3 := index_3 + 1;
    end if;
    if (Twfm_4(index_4) <= old_t) then new_index_4 := index_4 + 1;
    end if;
    -------------------------------------------------------------------------
    -- Find the lowest value at the new indexes in the given vector(s) and
    -- update indexes for next iteration
    -------------------------------------------------------------------------
    if (new_index_1 <= Twfm_1'right) then
      if (Twfm_1(new_index_1) < new_t) then new_t := Twfm_1(new_index_1);
      end if;
      index_1 := new_index_1;
    end if;
    if (new_index_2 <= Twfm_2'right) then
      if (Twfm_2(new_index_2) < new_t) then new_t := Twfm_2(new_index_2);
      end if;
      index_2 := new_index_2;
    end if;
    if (new_index_3 <= Twfm_3'right) then
      if (Twfm_3(new_index_3) < new_t) then new_t := Twfm_3(new_index_3);
      end if;
      index_3 := new_index_3;
    end if;
    if (new_index_4 <= Twfm_4'right) then
      if (Twfm_4(new_index_4) < new_t) then new_t := Twfm_4(new_index_4);
      end if;
      index_4 := new_index_4;
    end if;
    -------------------------------------------------------------------------
    -- Calculate how many additional points are needed between the given
    -- points to satisfy the "Max_dt" separation criteria.  Note: the extra
    -- logic is needed due to floating point inaccuracies.
    -------------------------------------------------------------------------
    Common_length := Common_length + integer(ceil((new_t-old_t)/Max_dt));

    remainder := "mod"((new_t-old_t),Max_dt);
    if (remainder < (min_dt * Max_dt)) and (remainder > 0.0) then
      Common_length := Common_length - 1;
    end if;
    -------------------------------------------------------------------------
    -- Update variables for next iteration
    -------------------------------------------------------------------------
    old_t   := new_t;

    new_t := Twfm_1(Twfm_1'right);
    if (Twfm_2(Twfm_2'right) > new_t) then new_t := Twfm_2(Twfm_2'right);
    end if;
    if (Twfm_3(Twfm_3'right) > new_t) then new_t := Twfm_3(Twfm_3'right);
    end if;
    if (Twfm_4(Twfm_4'right) > new_t) then new_t := Twfm_4(Twfm_4'right);
    end if;
    new_t := new_t;
  ---------------------------------------------------------------------------
  end loop; 
  ---------------------------------------------------------------------------
  return Common_length;
  end function Find_common_length;
--===========================================================================
  function Common_time (Max_dt : real := 1.0e-12;
                        Twfm_1 : in real_vector;
                        Twfm_2 : in real_vector;
                        Twfm_3 : in real_vector;
                        Twfm_4 : in real_vector) return real_vector is
  ---------------------------------------------------------------------------
  -- This function generates a vector that serves as a common time axis for
  -- all Vt curves, such that the maximum delta time between each time point
  -- doesn't exceed the value provided in "Max_dt".
  ---------------------------------------------------------------------------
  variable New_time     : real_vector(0 to Find_common_length(Max_dt, Twfm_1, Twfm_2, Twfm_3, Twfm_4)-1) := (others => 0.0);

  variable index        : integer := 0;
  variable extra_start  : integer := 0;
  variable extra_points : integer := 0;

  variable index_1      : integer := Twfm_1'left;
  variable index_2      : integer := Twfm_2'left;
  variable index_3      : integer := Twfm_3'left;
  variable index_4      : integer := Twfm_4'left;

  variable new_index_1  : integer := Twfm_1'left;
  variable new_index_2  : integer := Twfm_2'left;
  variable new_index_3  : integer := Twfm_3'left;
  variable new_index_4  : integer := Twfm_4'left;

  variable old_t        : real    := 0.0;
  variable new_t        : real    := 0.0;
  variable remainder    : real    := 0.0;
  variable min_dt       : real    := 1.0e-3;   -- This sets the size of the
                                               -- roundoff error relative to
                                               -- "Max_dt"
  ---------------------------------------------------------------------------
  begin
  ---------------------------------------------------------------------------
  -- Put the earliest time value of all given time vectors into "old_t"
  ---------------------------------------------------------------------------
    old_t := Twfm_1(Twfm_1'left);
    if (Twfm_2(Twfm_2'left) < old_t) then old_t := Twfm_2(Twfm_2'left);
    end if;
    if (Twfm_3(Twfm_3'left) < old_t) then old_t := Twfm_3(Twfm_3'left);
    end if;
    if (Twfm_4(Twfm_4'left) < old_t) then old_t := Twfm_4(Twfm_4'left);
    end if;
  ---------------------------------------------------------------------------
  -- Put the latest time value of all given time vectors into "new_t"
  ---------------------------------------------------------------------------
    new_t := Twfm_1(Twfm_1'right);
    if (Twfm_2(Twfm_2'right) > new_t) then new_t := Twfm_2(Twfm_2'right);
    end if;
    if (Twfm_3(Twfm_3'right) > new_t) then new_t := Twfm_3(Twfm_3'right);
    end if;
    if (Twfm_4(Twfm_4'right) > new_t) then new_t := Twfm_4(Twfm_4'right);
    end if;
    new_t := new_t;
  ---------------------------------------------------------------------------
  -- Loop until last index is reached in each given time vector
  ---------------------------------------------------------------------------
  index := New_time'left;
  while (old_t < new_t) loop
    -------------------------------------------------------------------------
    -- Find which given time vector(s) have the lowest time value and
    -- advance their temporary indexes
    -------------------------------------------------------------------------
    if (Twfm_1(index_1) <= old_t) then new_index_1 := index_1 + 1;
    end if;
    if (Twfm_2(index_2) <= old_t) then new_index_2 := index_2 + 1;
    end if;
    if (Twfm_3(index_3) <= old_t) then new_index_3 := index_3 + 1;
    end if;
    if (Twfm_4(index_4) <= old_t) then new_index_4 := index_4 + 1;
    end if;
    -------------------------------------------------------------------------
    -- Find the lowest value at the new indexes in the given vector(s) and
    -- update indexes for next iteration
    -------------------------------------------------------------------------
    if (new_index_1 <= Twfm_1'right) then
      if (Twfm_1(new_index_1) < new_t) then new_t := Twfm_1(new_index_1);
      end if;
      index_1 := new_index_1;
    end if;
    if (new_index_2 <= Twfm_2'right) then
      if (Twfm_2(new_index_2) < new_t) then new_t := Twfm_2(new_index_2);
      end if;
      index_2 := new_index_2;
    end if;
    if (new_index_3 <= Twfm_3'right) then
      if (Twfm_3(new_index_3) < new_t) then new_t := Twfm_3(new_index_3);
      end if;
      index_3 := new_index_3;
    end if;
    if (new_index_4 <= Twfm_4'right) then
      if (Twfm_4(new_index_4) < new_t) then new_t := Twfm_4(new_index_4);
      end if;
      index_4 := new_index_4;
    end if;
    -------------------------------------------------------------------------
    -- Calculate how many additional points are needed between the given
    -- points to satisfy the "Max_dt" separation criteria.  Note: the extra
    -- logic is needed due to floating point inaccuracies.
    -------------------------------------------------------------------------
    extra_points := integer(ceil((new_t-old_t)/Max_dt));

    remainder := "mod"((new_t-old_t),Max_dt);
    if remainder < (min_dt * Max_dt) and
       remainder > 0.0 then extra_points := extra_points - 1;
    end if;
    -------------------------------------------------------------------------
    -- Calculate and write the values of points into the "New_time" vector
    -------------------------------------------------------------------------
    for x_index in 0 to (extra_points - 1) loop
      New_time(index) := old_t + (real(x_index) * (new_t-old_t)) / real(extra_points);
      index := index + 1;
    end loop; 
    -------------------------------------------------------------------------
    -- Update variables for next iteration
    -------------------------------------------------------------------------
    old_t   := new_t;

    new_t := Twfm_1(Twfm_1'right);
    if (Twfm_2(Twfm_2'right) > new_t) then new_t := Twfm_2(Twfm_2'right);
    end if;
    if (Twfm_3(Twfm_3'right) > new_t) then new_t := Twfm_3(Twfm_3'right);
    end if;
    if (Twfm_4(Twfm_4'right) > new_t) then new_t := Twfm_4(Twfm_4'right);
    end if;
    new_t := new_t;
  ---------------------------------------------------------------------------
  end loop; 
  ---------------------------------------------------------------------------
  -- Insert the largest value at the end of the given vector(s) into the
  -- last position of "New_time".  (The loop above stops at the next to
  -- the last point).
  ---------------------------------------------------------------------------
  New_time(New_time'right) := new_t;
  ---------------------------------------------------------------------------
  return New_time;
  end function Common_time;
--===========================================================================
  function Common_wfm (New_t : in real_vector;
                       Vwfm  : in real_vector;
                       Twfm  : in real_vector) return real_vector is
  ---------------------------------------------------------------------------
  -- This function generates an interpolated vector based on the input vector
  -- "New_t" using the input vector pair "Vwfm" and "Twfm".
  ---------------------------------------------------------------------------
  variable New_v : real_vector(New_t'range) := (others => 1.0);
  ---------------------------------------------------------------------------
  begin
                                        -- This is the better notation
                                        -- but it doesn't work due to
--  for index in New_v'range loop       -- a bug in SystemVision
    for index in New_v'left to New_v'right loop
      New_v(index) := Lookup("Vt", New_t(index), Vwfm, Twfm);
    end loop; 

  return New_v;
  end function Common_wfm;
--===========================================================================
constant T_common        : real_vector := Common_time(Delta_t, T_pu_on,  T_pu_off, T_pd_on, T_pd_off);
constant V_pu_on_common  : real_vector := Common_wfm(T_common, V_pu_on,  T_pu_on);
constant V_pu_off_common : real_vector := Common_wfm(T_common, V_pu_off, T_pu_off);
constant V_pd_on_common  : real_vector := Common_wfm(T_common, V_pd_on,  T_pd_on);
constant V_pd_off_common : real_vector := Common_wfm(T_common, V_pd_off, T_pd_off);
--===========================================================================
  function Coeff (Edge     : in string;
                  Vwfm_pu  : in real_vector;
                  Vwfm_pd  : in real_vector;
                  Twfm     : in real_vector;
                  Rfx_pu   : in real;
                  Rfx_pd   : in real;
                  Vfx_pu   : in real;
                  Vfx_pd   : in real;
                  Iiv_pu   : in real_vector;
                  Viv_pu   : in real_vector;
                  Iiv_pd   : in real_vector;
                  Viv_pd   : in real_vector;
                  V_pu_ref : in real;
                  V_pd_ref : in real) return real_vector is
  ---------------------------------------------------------------------------
  -- This function converts each Vt curve to a corresponding scaling
  -- coefficient curve that is used to scale the IV curves with respect
  -- to time.  The effects of C_comp on the waveforms are included in the
  -- scaling coefficients, so that the presence of C_comp in the output
  -- stage will not "load" the driver, i.e. C_comp in the output stage 
  -- will not derate the original waveforms in the Vt curves.
  ---------------------------------------------------------------------------
  variable Kout     : real_vector(Vwfm_pu'range);
  variable dVwfm_pu : real_vector(Vwfm_pu'range);
  variable dVwfm_pd : real_vector(Vwfm_pd'range);

  variable I1   : real := 0.0;
  variable I2   : real := 0.0;
  variable I3   : real := 0.0;
  variable I4   : real := 0.0;
  variable Ifx1 : real := 0.0;
  variable Ifx2 : real := 0.0;
  variable num  : real := 0.0;
  variable den  : real := 1.0;
  ---------------------------------------------------------------------------
  begin
  ---------------------------------------------------------------------------
  -- Generate the derivative of each waveform
  ---------------------------------------------------------------------------
    for index in Vwfm_pu'range loop
    -------------------------------------------------------------------------
    -- Force the derivatives of the first and last points to zero
    -------------------------------------------------------------------------
      if (index = Vwfm_pu'left) then
        dVwfm_pu(index) := 0.0;
        dVwfm_pd(index) := 0.0;
      elsif (index = Vwfm_pu'right) then
        dVwfm_pu(index) := 0.0;
        dVwfm_pd(index) := 0.0;
      -----------------------------------------------------------------------
      -- Calculate the derivative of a point using both of its neighbors
      -----------------------------------------------------------------------
      else
        dVwfm_pu(index) := ((Vwfm_pu(index) - Vwfm_pu(index-1))/(Twfm(index) - Twfm(index-1)) +
                            (Vwfm_pu(index+1) - Vwfm_pu(index))/(Twfm(index+1) - Twfm(index)))/2.0;
        dVwfm_pd(index) := ((Vwfm_pd(index) - Vwfm_pd(index-1))/(Twfm(index) - Twfm(index-1)) +
                            (Vwfm_pd(index+1) - Vwfm_pd(index))/(Twfm(index+1) - Twfm(index)))/2.0;
      end if;
      -----------------------------------------------------------------------
      -- Calculate intermediate (current) variables
      -----------------------------------------------------------------------
      I1   :=        Lookup("IV", Vwfm_pd(index) - V_pd_ref, Iiv_pd, Viv_pd);
      I2   :=        Lookup("IV", Vwfm_pu(index) - V_pd_ref, Iiv_pd, Viv_pd);
      I3   := -1.0 * Lookup("IV", V_pu_ref - Vwfm_pu(index), Iiv_pu, Viv_pu);
      I4   := -1.0 * Lookup("IV", V_pu_ref - Vwfm_pd(index), Iiv_pu, Viv_pu);
      -----------------------------------------------------------------------
      -- Calculate intermediate (fixture) variables
      -----------------------------------------------------------------------
      Ifx1 := ((Vwfm_pu(index) - Vfx_pu) / Rfx_pu) + C_comp * dVwfm_pu(index);
      Ifx2 := ((Vfx_pd - Vwfm_pd(index)) / Rfx_pd) - C_comp * dVwfm_pd(index);
      -----------------------------------------------------------------------
      -- Set up the numerator of the equation depending on the direction of
      -- the transition, and set up denominator of the equation.
      -----------------------------------------------------------------------
      if (Edge = "K_pu_on") or (Edge = "K_pu_off") then
        num  := (Ifx1 * I1) + (Ifx2 * I2);
      elsif (Edge = "K_pd_on") or (Edge = "K_pd_off") then
        num  := (Ifx1 * I4) + (Ifx2 * I3);
      else
        num  := 0.0;
      end if;

      den  := (I1 * I3) - (I2 * I4);
      -----------------------------------------------------------------------
      -- This is the master equation that solves for the scaling coefficients
      -----------------------------------------------------------------------
      Kout(index) := num / den;
    -------------------------------------------------------------------------
    end loop;
    -------------------------------------------------------------------------
  return Kout;
  end function Coeff;
--===========================================================================
  constant  K_pu_on  : real_vector := Coeff("K_pu_on",  V_pu_on_common,  V_pd_off_common, T_common, Rfx_pu_on,  Rfx_pd_off, Vfx_pu_on,  Vfx_pd_off, I_pu, V_pu, I_pd, V_pd, V_pu_ref, V_pd_ref);
  constant  K_pd_off : real_vector := Coeff("K_pd_off", V_pu_on_common,  V_pd_off_common, T_common, Rfx_pu_on,  Rfx_pd_off, Vfx_pu_on,  Vfx_pd_off, I_pu, V_pu, I_pd, V_pd, V_pu_ref, V_pd_ref);
  constant  K_pd_on  : real_vector := Coeff("K_pd_on",  V_pu_off_common, V_pd_on_common,  T_common, Rfx_pu_off, Rfx_pd_on,  Vfx_pu_off, Vfx_pd_on,  I_pu, V_pu, I_pd, V_pd, V_pu_ref, V_pd_ref);
  constant  K_pu_off : real_vector := Coeff("K_pu_off", V_pu_off_common, V_pd_on_common,  T_common, Rfx_pu_off, Rfx_pd_on,  Vfx_pu_off, Vfx_pd_on,  I_pu, V_pu, I_pd, V_pd, V_pu_ref, V_pd_ref);
--===========================================================================
begin
--===========================================================================
  Catch: process (In_D, En_D) is
  ---------------------------------------------------------------------------
  begin
    Rcv_D <= In_D;                               -- Dummy receiver logic
-------------------------------------------------------------------------
    if (En_D = '1') and (In_D = '1') then        -- Find logic state
      pu_on  <= '1';
      pd_off <= '1';
      pd_on  <= '0';
      pu_off <= '0';
    elsif (En_D = '1') and (In_D = '0') then
      pu_on  <= '0';
      pd_off <= '0';
      pd_on  <= '1';
      pu_off <= '1';
    elsif (En_D = '0') then
      pu_on  <= '0';
      pd_off <= '1';
      pd_on  <= '0';
      pu_off <= '1';
    else
      pu_on  <= '1';
      pd_off <= '0';
      pd_on  <= '1';
      pu_off <= '0';
    end if;
  ---------------------------------------------------------------------------
  end process Catch;
--===========================================================================
  pu_on_event_time: process (pu_on) is      -- Update event time if changed
  ---------------------------------------------------------------------------
  begin
      Tpu_on_event <= now;
  ---------------------------------------------------------------------------
  end process pu_on_event_time;
--===========================================================================
  pu_off_event_time: process (pu_off) is    -- Update event time if changed
  ---------------------------------------------------------------------------
  begin
      Tpu_off_event <= now;
  ---------------------------------------------------------------------------
  end process pu_off_event_time;
--===========================================================================
  pd_on_event_time: process (pd_on) is      -- Update event time if changed
  ---------------------------------------------------------------------------
  begin
      Tpd_on_event <= now;
  ---------------------------------------------------------------------------
  end process pd_on_event_time;
--===========================================================================
  pd_off_event_time: process (pd_off) is    -- Update event time if changed
  ---------------------------------------------------------------------------
  begin
      Tpd_off_event <= now;
  ---------------------------------------------------------------------------
  end process pd_off_event_time;
--===========================================================================
  break on pu_on;
  break on pu_off;
  break on pd_on;
  break on pd_off;
--===========================================================================
  -- This section contains the simultaneous analog equations to find the
  -- appropriate scaling coefficients according to the state the buffer.
  ---------------------------------------------------------------------------
  if (Tpu_on_event = 0.0 and Tpu_off_event = 0.0 and
      Tpd_on_event = 0.0 and Tpd_off_event = 0.0) use
                                                 -- Initialization
    if (pu_on = '1') use                         -- Start with the end of the
      k_pu == K_pu_on(K_pu_on'right);            -- Vt curves for those which
    elsif (pu_off = '1') use                     -- are fully on initially
      k_pu == K_pu_off(K_pu_off'right);
    else
      k_pu == 0.0;
    end use;
    
    if (pd_on = '1') use
      k_pd == K_pd_on(K_pd_on'right);
    elsif (pd_off = '1') use
      k_pd == K_pd_off(K_pd_off'right);
    else
      k_pd == 0.0;
    end use;

  else                            -- Look up coefficients in normal operation

    if    (pu_on  = '1') use k_pu == Lookup("Vt", now - Tpu_on_event, K_pu_on,  T_common);
    elsif (pu_off = '1') use k_pu == Lookup("Vt", now - Tpu_off_event, K_pu_off, T_common);
    else                     k_pu == K_pu_on(K_pu_on'left);
    end use;

    if    (pd_on  = '1') use k_pd == Lookup("Vt", now - Tpd_on_event, K_pd_on,  T_common);
    elsif (pd_off = '1') use k_pd == Lookup("Vt", now - Tpd_off_event, K_pd_off, T_common);
    else                     k_pd == K_pd_on(K_pd_on'left);
    end use;

  end use;
--===========================================================================
  -- This section contains the simultaneous analog equations which calculate
  -- the output currents of the IV curves and C_comp capacitors.
  ---------------------------------------------------------------------------
  Ipc   == -1.0     *Lookup("IV", Vpc, I_pc, V_pc) + k_C_comp_pc*C_comp*Vpc'dot; 
  Ipu   == -1.0*k_pu*Lookup("IV", Vpu, I_pu, V_pu) + k_C_comp_pu*C_comp*Vpu'dot; 
  Ipd   ==      k_pd*Lookup("IV", Vpd, I_pd, V_pd) + k_C_comp_pd*C_comp*Vpd'dot;
  Igc   ==           Lookup("IV", Vgc, I_gc, V_gc) + k_C_comp_gc*C_comp*Vgc'dot;

--===========================================================================
end architecture IO_2EQ;
--===========================================================================
