//
// IBIS_AMI_Tx.c
//
// Implementation of generic transmitter conforming to IBIS AMI API
//
// Copyright (c) 2007. Signal Integrity Software, Inc. (SiSoft)
// All Rights Reserved.
//
// History:
// 19 March 2008, Mike Steinberger: Removed channel impulse response convolution
//                                  from GetWave.
// 7 August 2007, Mike Steinberger: Fixed overflow array bounds bug in Init.
// 8 June 2007, Mike Steinberger: Added computation of channel output based
//    on step response of transmitter + channel.
// 31 May 2007, Mike Steinberger: Initial file creation
//
//
// Permission to use, copy, modify, and distribute this software and 
// associated documentation for educational, research and commercial 
// purposes, without fee and without a signed licensing agreement, is hereby
// granted, provided that the above copyright notice, this paragraph and
// the following three paragraphs appear in all copies, modifications, and
// distributions. 
//
// This is part of a sample IBIS Algorithmic Modeling Interface (AMI) model. 
// This code has been provided by SiSoft to help demonstrate and drive 
// adoption of the IBIS AMI modeling standard.  For more information
// on this and other IBIS AMI models, contact Signal Integrity Software, 
// 6 Clock Tower Place, Maynard, MA 01754, (978) 461-0449 or email
// ibis-ami@sisoft.com.
// 
// THIS MATERIAL IS PROVIDED FOR THE RECIPIENT'S USE AT THE RECIPIENT's OWN RISK.
// IN NO EVENT SHALL SISOFT BE LIABLE TO ANY PARTY FOR FOR ANY DIRECT,
// INDIRECT, INCIDENTAL,SPECIAL OR CONSEQUENTIAL DAMAGES, OR ANY OTHER DAMAGES 
// WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF BUSINESS PROFITS, 
// BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR OTHER PECUNIARY LOSS) 
// ARISING OUT OF THE USE OR INABILITY TO USE THIS LICENSED MATERIAL, WHETHER 
// OR NOT THE POSSIBILITY OR CAUSE OF SUCH DAMAGES WAS KNOWN TO SISOFT.
// 
// SISOFT SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
// PARTICULAR PURPOSE. THIS SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF
// ANY, PROVIDED HEREUNDER IS PROVIDED "AS IS". SISOFT HAS NO OBLIGATION
// TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
// MODIFICATIONS.

#include "IBIS_AMI_Tx.h"
#include "IBIS_AMI_tree.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>

IBIS_AMI_API long AMI_Init( double *impulse_matrix,
                            long row_size,
                            long aggressors,
                            double sample_interval,
                            double bit_time,
                            char *AMI_parameters_in,
                            char **AMI_parameters_out,
                            void **AMI_memory_handle,
                            char **msg ) {
   AtmTxModel *self;
   double samp_dbl,
          norm,
          *tmp_dbl;
   int indx, yndx, item_count;
   ParamListItem taps[4] = { { "-1", ami_double_type, 0 },
                             { "0",  ami_double_type, 0 },
                             { "1",  ami_double_type, 0 },
                             { "2",  ami_double_type, 0 } };
   ParamListItem param[2] = { { "tap_filter", ami_list_type, 0 },
                              { "tx_swing",   ami_double_type,  0 } };
   ParamList tap_list = { "tap_filter", 4, &taps[0] };

   ParamList param_list = { "IBIS_AMI_Tx", 2, &param[0] };

   //Default parameter values
   taps[0].p_val.dbl_val = 0;
   taps[1].p_val.dbl_val = 1;
   taps[2].p_val.dbl_val = 0;
   taps[3].p_val.dbl_val = 0;

   //Default parameter values
   param[0].p_val.ptr_val = (void*)&tap_list;
   param[1].p_val.dbl_val = 0.8;

   self = (AtmTxModel*)malloc( sizeof( AtmTxModel ) );
   *AMI_memory_handle = (void*)self;


   //Parse the parameter string
   item_count = 0;
   if( AMI_parameters_in ) {
      item_count = ParseTree( AMI_parameters_in, param_list );
   }

   self->msg = (char*)malloc( 68*sizeof( char ) );
   memset( self->msg, 0, 68*sizeof( char ) );
   sprintf( self->msg,
            "INFO: Configured %d items through parameter string.",
            item_count );
   *msg = self->msg;

   //Normalize the tap coefficients.
   norm = fabs( taps[0].p_val.dbl_val ) +
          fabs( taps[1].p_val.dbl_val ) +
          fabs( taps[2].p_val.dbl_val ) +
          fabs( taps[3].p_val.dbl_val ) ;
   taps[0].p_val.dbl_val /= norm;
   taps[1].p_val.dbl_val /= norm;
   taps[2].p_val.dbl_val /= norm;
   taps[3].p_val.dbl_val /= norm;

   //Echo the parameters back out
   *AMI_parameters_out = GrowTree( param_list );

   self->taps[0]         = taps[0].p_val.dbl_val;
   self->taps[1]         = taps[1].p_val.dbl_val;
   self->taps[2]         = taps[2].p_val.dbl_val;
   self->taps[3]         = taps[3].p_val.dbl_val;
   self->swing           = param[1].p_val.dbl_val;
   self->row_size        = row_size;
   self->out_buf         = 0;
   self->buf_size        = 0;
   self->wr_ptr          = 0;
   self->last_in         = 0;
   self->sample_interval = sample_interval;
   self->bit_time        = bit_time;
   self->params_out      = *AMI_parameters_out;

   samp_dbl = bit_time/sample_interval - 0.5;
   self->samples = 1;
   while( self->samples < samp_dbl ) {
      self->samples++;
   }

   tmp_dbl = (double*)malloc( row_size*(aggressors+1)*sizeof( double ) );
   for( yndx = 0; yndx < aggressors+1; yndx++ ) {
      for( indx = 0; indx < row_size; indx++ ) {
         tmp_dbl[ indx+row_size*yndx ] =
                            self->taps[0]*impulse_matrix[ indx+row_size*yndx ];
         if( indx >= self->samples ) {
            tmp_dbl[ indx+row_size*yndx ] +=
               self->taps[1]*impulse_matrix[ indx+row_size*yndx-self->samples ];
         }
         if( indx >= 2*self->samples ) {
            tmp_dbl[ indx+row_size*yndx ] +=
             self->taps[2]*impulse_matrix[ indx+row_size*yndx-2*self->samples ];
         }
         if( indx >= 3*self->samples ) {
            tmp_dbl[ indx+row_size*yndx ] +=
             self->taps[3]*impulse_matrix[ indx+row_size*yndx-3*self->samples ];
         }
         tmp_dbl[ indx+row_size*yndx ] *= self->swing;
      }
   }
   memcpy( impulse_matrix, tmp_dbl, row_size*(aggressors+1)*sizeof( double ) );
   free( tmp_dbl );

   //Allocate the input delay buffer for time domain processing.
   self->out_buf = (double*)malloc( 4*self->samples*sizeof( double ) );
   for( indx = 0; indx < 4*self->samples; indx++ ) self->out_buf[ indx ] = 0;

   return 1;
}

double gw_time = 0;

IBIS_AMI_API long AMI_GetWave( double *wave_in,
                               long wave_size,
                               double *clock_times,
                               char **AMI_parameters_out,
                               void *AMI_memory ) {
   int indx, yndx, clock_dx, samples, wr_ptr;
   AtmTxModel *self = (AtmTxModel*)AMI_memory;

   samples = self->samples;
   wr_ptr  = self->wr_ptr;

   //Compute the response.
   clock_dx = 0;
   for( indx = 0; indx < wave_size; indx++ ) {
      if( wave_in[ indx ] * self->last_in < 0 ){
         clock_times[ clock_dx++ ] = gw_time + (indx-0.5)*self->sample_interval;
      }
      self->last_in = wave_in[ indx ];
      self->out_buf[ wr_ptr ] = wave_in[ indx ];
      wave_in[ indx ]  = self->taps[0] * self->out_buf[ wr_ptr ];
      wave_in[ indx ] += self->taps[1] *
                         self->out_buf[ (wr_ptr+3*samples)%(4*samples) ];
      wave_in[ indx ] += self->taps[2] *
                         self->out_buf[ (wr_ptr+2*samples)%(4*samples) ];
      wave_in[ indx ] += self->taps[3] *
                         self->out_buf[ (wr_ptr+  samples)%(4*samples) ];
      wave_in[ indx ] *= self->swing;
      wr_ptr = (wr_ptr+1)%(4*samples);
   }

   self->wr_ptr = wr_ptr;

   //Terminate the list of clock ticks
   clock_times[ clock_dx ] = -1;

   gw_time += wave_size*self->sample_interval;

   return 1;
}

IBIS_AMI_API long AMI_Close( void *AMI_memory ) {
   AtmTxModel *self = (AtmTxModel*)AMI_memory;
   if( self ) {
      free( self->out_buf );
      free( self->params_out );
      free( self->msg );
      free( self ); //"The truth will set you free."
   }
   return 1;
}

