//
// 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:
// 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->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 );

   //Calculate the step response
   self->step_response = (double*)malloc( row_size*sizeof( double ) );
   self->step_response[0] = sample_interval * impulse_matrix[0];
   for( indx = 1; indx < row_size; indx++ ) {
      self->step_response[indx] = self->step_response[indx-1] +
                                  sample_interval * impulse_matrix[indx];
   }

   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 tmp_size, indx, yndx, clock_dx;
   double *tmp_dbl, step_size;
   AtmTxModel *self = (AtmTxModel*)AMI_memory;

   //Create an extended array to compute from.
   tmp_size = self->row_size + wave_size;
   if( self->buf_size < tmp_size ) {
      tmp_dbl = (double*)malloc( tmp_size*sizeof( double ) );
      memset( tmp_dbl, 0, tmp_size*sizeof( double ) );
      //Resize if necessary
      if( self->out_buf != 0 ) {
         memcpy( tmp_dbl, self->out_buf, self->buf_size*sizeof( double ) );
         free( self->out_buf );
      }
      self->out_buf  = tmp_dbl;
      self->buf_size = tmp_size;
   }
   else {
      tmp_dbl = self->out_buf;
   }

   //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;
      }
      if( wave_in[ indx ] != self->last_in ) { //Add step response
         step_size = wave_in[ indx ] - self->last_in;
         for( yndx = 0; yndx < self->row_size; yndx++ ) {
            tmp_dbl[ indx+yndx ] += step_size * self->step_response[ yndx ];
         }
         for( yndx = indx+self->row_size; yndx < self->buf_size; yndx++ ) {
            tmp_dbl[ yndx ] += step_size *
                               self->step_response[ self->row_size-1 ];
         }
      }
      self->last_in = wave_in[ indx ];
      wave_in[ indx ] = tmp_dbl[ indx ]; //Save the output
   }

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

   //Save the remaining response for the next block of data
   for( indx = 0; indx < self->row_size; indx++ ) {
      tmp_dbl[ indx ] = tmp_dbl[ indx+wave_size ];
   }
   for( ; indx < self->buf_size; indx++ ) {
      tmp_dbl[ indx ] = tmp_dbl[ self->row_size-1 ];
   }

   gw_time += wave_size*self->sample_interval;

   return 1;
}

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

