-------------------------------------------------------------------------------
-- This is the Thevenin package.  It was written by Jim Lear at Cirrus Logic in
-- 2010 and is released directly to the public domain without copyright
-- restrictions. 
-------------------------------------------------------------------------------

package thevenin_pkg is

  -- unresolved type
  type uthevenin is record
                      Vth, Rth : real;
                    end record;
  type uthevenin_vector is array (natural range <>) of uthevenin;

  function resolve_thevenin (
    drivers : uthevenin_vector)
    return uthevenin;
  
  -- resolved type
  subtype thevenin is resolve_thevenin uthevenin;

  -- purpose: From the point of view of one of the drivers of a signal, the resolved_
  -- value is the combination of the "my" driven value and "everyone else's"
  -- driven values.  This function calculates "everyone else's" driven value
  -- given the total resolved_value and "my" driven_value  
  function external_drive (
    resolved_value : uthevenin;
    driven_value   : uthevenin)
    return uthevenin;

  constant short : uthevenin := (Vth => 0.0, Rth => 0.0);
  constant opn   : uthevenin := (Vth => 0.0, Rth => real'right);

end thevenin_pkg;

package body thevenin_pkg is

  -- purpose: resolves multiple thevenin drivers
  function resolve_thevenin (
    drivers : uthevenin_vector)
    return uthevenin is
    variable resolved_value : uthevenin;
    variable geq : real := 0.0;
  begin  -- resolve_thevenin
--    report lf&"Thevenin resolution function" severity note;
    for i in drivers'range loop
--      report "driver("&integer'image(i)&") = (Vth=>"&real'image(drivers(i).vth)&", Rth=>"&real'image(drivers(i).rth)&")" severity note;
      if drivers(i).rth = 0.0 then
        if geq=real'right then
          -- already had a zero impedance driver
          report "Shorted driver" severity error;
        end if;
        geq := real'right;
      else
        geq := geq + 1.0/drivers(i).rth;
      end if;
    end loop;  -- i
    if geq=real'right then
      -- make sure the resistance is zero, not epsilon
      resolved_value.rth := 0.0;
    else
      resolved_value.rth := 1.0/geq;
    end if;
    resolved_value.vth := 0.0;
    for i in drivers'range loop
      if drivers(i).rth = 0.0 then
        -- take first driven value
        resolved_value.vth := drivers(i).vth;
        exit;
      else
        resolved_value.vth := resolved_value.vth + (drivers(i).vth/drivers(i).rth/geq);
      end if;
    end loop;  -- i
    
--    report "resolved_value = (Vth=>"&real'image(resolved_value.vth)&", Rth=>"&real'image(resolved_value.rth)&")" severity note;
    return resolved_value;
  end resolve_thevenin;

  -- purpose: From the point of view of one of the drivers of a signal, the resolved_
  -- value is the combination of the "my" driven value and "everyone else's"
  -- driven values.  This function calculates "everyone else's" driven value
  -- given the total resolved_value and "my" driven_value  
  function external_drive (
    resolved_value : uthevenin;
    driven_value   : uthevenin)
    return uthevenin is
    variable external_dr : uthevenin;
  begin  -- external_drive
    if driven_value.rth=0.0 then
      -- cannot discerne external driver
      external_dr := (Vth => 0.0, Rth => real'right);
      return external_dr;
    end if;
    if resolved_value.rth=0.0 then
      -- resolved value equals external driver
      external_dr := resolved_value;
      return external_dr;
    end if;
    if resolved_value.rth = driven_value.rth then
      -- resolved value equals driven value
      external_dr.rth := real'right;
      external_dr.vth := resolved_value.vth;
      return external_dr;
    end if;
    external_dr.rth := 1.0/(1.0/resolved_value.rth - 1.0/driven_value.rth);
    external_dr.vth := resolved_value.vth*(1.0/external_dr.rth + 1.0/driven_value.rth)/(1.0/external_dr.rth)
                       - driven_value.vth*(external_dr.rth/driven_value.rth);
    return external_dr;
  end external_drive;

end thevenin_pkg;
