// This is -*- Verilog-A -*-

// ============================================================================
//
// (c) Copyright 2005, All Rights Reserved, Philips Electronics N.V.
//
//
// Version: August 22, 2005
//
// ============================================================================

// Spice primitives
// Verilog-AMS LRM 2.0 Annex E "SPICE compatibility"

`include "disciplines.vams"
`include "constants.vams"

`ifdef IPWL_VA
`else
`define IPWL_VA 1

/**
 * @brief exponential waveform current source.
 *
 * A piece-wise linear waveform with constant-value extrapolation outside
 * table points and linear interpolation in between table points.
 *
 * @param dc		DC current level in [A].
 * @param mag		AC small-signal source magnitude in [A].
 * @param phase		AC small-signal source phase in [rad].
 * @param npoints	number of points in the wave table.
 * @param wave		array of length 2*npoints of time-value pairs in [s,V].
 */

module Ipwl (p, n);
inout p, n;
electrical p, n;
parameter dc = 0.0;
parameter mag = 1.0m;
parameter phase = 0.0;
parameter integer npoints = 2 from [2:inf);
parameter real wave[0:2*npoints - 1] = { 0.0, 0.0, 1.0, 1.0m };

real t;
real dc_val;
integer pwl_points;

genvar i;

`define TIME(x)		(2*(x))
`define VALUE(x)	(2*(x)+1)

analog begin

  @(initial_step) begin

    pwl_points = npoints * 2;
  
    for (i = 0; i <= npoints; i += 1)
      if (wave[`TIME(i)] <= 0.0) begin
        $strobe("ERROR: %m has a negative time value at index %d.", i);
        $finish;
      end
      else if ((i > 0) && (wave[`TIME(i-1)] >= wave[`TIME(i)])) begin
        $strobe("ERROR: %m has a non-monotonous wave change at index %d.", i);
        $finish;
      end
  
    // If the DC value is not given, and the first time point is at t == 0,
    // i.e. wave[0] == 0, we make it equal to the wave[1] value,
    // so the DC solution is continuous with any subsequent transient.
    // We assume the DC value not given if it is 0, wave[0] is 0 and and
    // wave[1] is not 0.
    if ((dc == 0.0) && (wave[`TIME(0)] == 0.0) && (wave[`VALUE(0)] != 0.0))
      dc_val = wave[`VALUE(0)];
    else
      dc_val = dc;

  end

  I(p, n) <+ ac_stim("ac", mag, phase);
  
  if (analysis("dc","ic","static")) begin

    I(p, n) <+ dc_val;

  end
  else if (analysis("tran")) begin : pwl_tr_block

    integer i, j;
    real dv2, dv1, dt2, dt1;

    if ((i == 0) && (wave[`TIME(0)] <= 0.0)) begin
      i = 1;
      j = i;
    end

    @(timer(wave[`TIME(j)])) begin
      i = i + 1;
      j = (i >= npoints) ? npoints - 1 : i;
      $discontinuity;
    end

    t = $abstime;
    if (i == 0) begin
      dt1 = wave[`TIME(i)];
      dv1 = wave[`VALUE(i)] - dc_val;
      dv2 = dv1 * t / dt1;
    end
    
    if ((i > 0) && (i < npoints)) begin
      dt1 = wave[`TIME(i)] - wave[`TIME(i-1)];
      dt2 = t - wave[`TIME(i-1)];
      dv1 = wave[`VALUE(i)] - wave[`VALUE(i-1)];
      dv2 = dv1 * dt2 / dt1;
    end

    if (i >= npoints) begin
      dv2 = wave[`VALUE(npoints - 1)];
    end

    I(p, n) <+ dv2;

  end

end

endmodule // Ipwl

`endif
