From 7c3cb8169cc44b15c7b900e43e06be29d9c9bdfd Mon Sep 17 00:00:00 2001 From: Holger Vogt Date: Sat, 7 Oct 2023 17:13:55 +0200 Subject: [PATCH] This is a re-write of the PWM generator. It is modelled according to the new d_OSC by Giles Atkinson. Here frequency is fixed and duty cycle is controlled by an analog input. --- src/xspice/icm/digital/d_pwm/cfunc.mod | 655 ++++++++----------------- src/xspice/icm/digital/d_pwm/d_pwm.h | 93 ---- 2 files changed, 196 insertions(+), 552 deletions(-) delete mode 100644 src/xspice/icm/digital/d_pwm/d_pwm.h diff --git a/src/xspice/icm/digital/d_pwm/cfunc.mod b/src/xspice/icm/digital/d_pwm/cfunc.mod index 8ed3930b1..908a2b5b3 100644 --- a/src/xspice/icm/digital/d_pwm/cfunc.mod +++ b/src/xspice/icm/digital/d_pwm/cfunc.mod @@ -1,477 +1,214 @@ -/*.......1.........2.........3.........4.........5.........6.........7.........8 -================================================================================ +/* XSPICE code model for the Controlled PWM Oscillator. + * This is a complete redesign of the original version, + * according to the d_osc model provided by G. Atkinson + */ -FILE d_pwm/cfunc.mod - -Public Domain - -Georgia Tech Research Corporation -Atlanta, Georgia 30332 -PROJECT A-8503-405 -The ngspice team - -AUTHORS - - 24 Jul 1991 Jeffrey P. Murray - 02 Mar 2022 Holger Vogt - -MODIFICATIONS - - 23 Aug 1991 Jeffrey P. Murray - 30 Sep 1991 Jeffrey P. Murray - 06 Oct 2022 Holger Vogt - 05 Jan 2023 Robert Turnbull - -SUMMARY - - This file contains the model-specific routines used to - functionally describe the d_pwm code model. - - -INTERFACES - - FILE ROUTINE CALLED - - CMmacros.h cm_message_send(); - - CM.c void *cm_analog_alloc() - void *cm_analog_get_ptr() - - CMevt.c void cm_event_queue() - - -REFERENCED FILES - - Inputs from and outputs to ARGS structure. - - -NON-STANDARD FEATURES - - NONE - -===============================================================================*/ - -/*=== INCLUDE FILES ====================*/ - -#include "d_pwm.h" /* ...contains macros & type defns. - for this model. 7/24/91 - JPM */ #include +#define FACTOR 0.75 // Controls timing of next scheduled call. */ +/* PWL table entry. */ -/*=== CONSTANTS ========================*/ +struct pwl { + double ctl, dc; +}; +/* Called at end to free memory. */ - - -/*=== MACROS ===========================*/ - - - - -/*=== LOCAL VARIABLES & TYPEDEFS =======*/ - -typedef struct { - double *x; - double *y; -} Local_Data_t; - - -/*=== FUNCTION PROTOTYPE DEFINITIONS ===*/ - - - - - -/*============================================================================== - -FUNCTION cm_d_pwm() - -AUTHORS - - 24 Jul 1991 Jeffrey P. Murray - 02 Mar 2022 Holger Vogt - -MODIFICATIONS - - 30 Sep 1991 Jeffrey P. Murray - -SUMMARY - - This function implements the d_pwm code model. - -INTERFACES - - FILE ROUTINE CALLED - - CMmacros.h cm_message_send(); - - CM.c void *cm_analog_alloc() - void *cm_analog_get_ptr() - - CMevt.c void cm_event_queue() - -RETURNED VALUE - - Returns inputs and outputs via ARGS structure. - -GLOBAL VARIABLES - - NONE - -NON-STANDARD FEATURES - - NONE - -==============================================================================*/ - -static void cm_d_pwm_callback(ARGS, - Mif_Callback_Reason_t reason) +static void cm_d_pwm_callback(ARGS, Mif_Callback_Reason_t reason) { - switch (reason) { - case MIF_CB_DESTROY: { - Local_Data_t *loc = STATIC_VAR(locdata); - if (loc) { - if (loc->x) - free(loc->x); - if(loc->y) - free(loc->y); - free(loc); - STATIC_VAR(locdata) = loc = NULL; - } - break; - } /* end of case MIF_CB_DESTROY */ - } /* end of switch over reason being called */ -} /* end of function cm_d_pwm_callback */ + if (reason == MIF_CB_DESTROY) { + struct pwl *table = STATIC_VAR(locdata); - - -/*=== CM_D_PWM ROUTINE ===*/ - -/************************************************************* -* The following is the model for a duty cycle controlled * -* digital oscillator, derived from the controlled digital * -* oscillator d_osc. * -* * -* Created 3/02/2022 H. Vogt * -*************************************************************/ - -/************************************************************* -* * -* * -* <-----duty_cycle-----> * -* I * -* I t2 t3 * -* I \______________/_____ * -* I | | * -* I | | | | * -* I | | * -* I | | | | * -* I | | * -* I | | | | * -* I-----------------*-----* - - - - - - - - - -*--------- * -* t1 t4 * -* * -* * -* t2 = t1 + rise_delay * -* t4 = t3 + fall_delay * -* * -* Note that for the digital model, unlike for the * -* analog "square" model, t1 and t3 are stored and * -* adjusted values, but t2 & t4 are implied by the * -* rise and fall delays of the model, but are otherwise * -* not stored values. JPM * -* * -*************************************************************/ - - -void cm_d_pwm(ARGS) -{ - - double *x, /* analog input value control array */ - *y, /* frequency array */ - cntl_input, /* control input value */ - *phase, /* instantaneous phase of the model */ - *phase_old, /* previous phase of the model */ - *t1, /* pointer to t1 value */ - *t3, /* pointer to t3 value */ - /*time1,*/ /* variable for calculating new time1 value */ - /*time3,*/ /* variable for calculating new time3 value */ - dc = 0.5, /* instantaneous duty cycle value */ - dphase, /* fractional part into cycle */ - frequency, /* frequency value */ - test_double, /* testing variable */ - slope; /* slope value...used to extrapolate - freq values past endpoints. */ - - int i, /* generic loop counter index */ - cntl_size, /* control array size */ - dc_size; /* duty cycle array size */ - - Local_Data_t *loc; /* Pointer to local static data, not to be included - in the state vector (save memory!) */ - - /**** Retrieve frequently used parameters... ****/ - - cntl_size = PARAM_SIZE(cntl_array); - dc_size = PARAM_SIZE(dc_array); - frequency = PARAM(frequency); - - /* check and make sure that the control array is the - same size as the frequency array */ - - if(cntl_size != dc_size){ - cm_message_send(d_pwm_array_error); - return; - } - - if (INIT) { /*** Test for INIT == TRUE. If so, allocate storage, etc. ***/ - - /* Allocate storage for internal variables */ - cm_analog_alloc(0, sizeof(double)); - cm_analog_alloc(1, sizeof(double)); - cm_analog_alloc(2, sizeof(double)); - - /* assign internal variables */ - phase = phase_old = (double *) cm_analog_get_ptr(0,0); - - t1 = (double *) cm_analog_get_ptr(1,0); - - t3 = (double *) cm_analog_get_ptr(2,0); - - /*** allocate static storage for *loc ***/ - STATIC_VAR (locdata) = calloc (1 , sizeof ( Local_Data_t )); - loc = STATIC_VAR (locdata); - CALLBACK = cm_d_pwm_callback; - - x = loc->x = (double *) calloc((size_t) cntl_size, sizeof(double)); - if (!x) { - cm_message_send(d_pwm_allocation_error); - return; - } - y = loc->y = (double *) calloc((size_t) cntl_size, sizeof(double)); - if (!y) { - cm_message_send(d_pwm_allocation_error); - if(x) - free(x); - return; - } - /* Retrieve x and y values. */ - for (i=0; i *phase ) { - *phase = *phase + 360.0; - } - *phase = *phase / 360.0; - - /* set phase value to init_phase */ - *phase_old = *phase; - - /* preset time values to harmless values... */ - *t1 = -1; - *t3 = -1; - } - - loc = STATIC_VAR (locdata); - x = loc->x; - y = loc->y; - - /* Retrieve cntl_input value. */ - cntl_input = INPUT(cntl_in); - - /* Determine segment boundaries within which cntl_input resides */ - /*** cntl_input below lowest cntl_voltage ***/ - if (cntl_input <= x[0]) { - - slope = (y[1] - y[0])/(x[1] - x[0]); - dc = y[0] + (cntl_input - x[0]) * slope; - - } - else - /*** cntl_input above highest cntl_voltage ***/ - - if (cntl_input >= x[cntl_size-1]) { - - slope = (y[cntl_size-1] - y[cntl_size-2]) / - (x[cntl_size-1] - x[cntl_size-2]); - dc = y[cntl_size-1] + (cntl_input - x[cntl_size-1]) * slope; - - } - else { /*** cntl_input within bounds of end midpoints... - must determine position progressively & then - calculate required output. ***/ - - for (i=0; i= x[i]) ) { - - /* Interpolate to the correct duty cycle value */ - - dc = ( (cntl_input - x[i]) / (x[i+1] - x[i]) ) * - ( y[i+1]-y[i] ) + y[i]; - } - } - } - - /*** If dc < 0.0, clamp to 0 & issue a warning ***/ - if ( 0.0 > dc ) { - dc = 0; -// cm_message_send(d_pwm_negative_dc_error); - } - /*** If dc > 1.0, clamp to 1 & issue a warning ***/ - if ( 1.0 < dc ) { - dc = 1; -// cm_message_send(d_pwm_positive_dc_error); - } - - /* calculate the instantaneous phase */ - *phase = *phase_old + frequency * (TIME - T(1)); - - /* dphase is the percent into the cycle for - the period */ - dphase = *phase_old - floor(*phase_old); - - /* Calculate the time variables and the output value - for this iteration */ - - if((*t1 <= TIME) && (TIME <= *t3)) { /* output high */ - - *t3 = T(1) + (1 - dphase)/frequency; - - if(TIME < *t3) { - cm_event_queue(*t3); - } - } - else - - if((*t3 <= TIME) && (TIME <= *t1)) { /* output low */ - - if(dphase > (1.0 - dc) ) { - dphase = dphase - 1.0; - } - *t1 = T(1) + ( (1.0 - dc) - dphase)/frequency; - - if(TIME < *t1) { - - cm_event_queue(*t1); - } - } - else { - - if(dphase > (1.0 - dc) ) { - dphase = dphase - 1.0; - } - *t1 = T(1) + ( (1.0 - dc) - dphase )/frequency; - - if((TIME < *t1) || (T(1) == 0)) { - cm_event_queue(*t1); - } - - *t3 = T(1) + (1 - dphase)/frequency; - } - cm_analog_set_temp_bkpt(*t1); - cm_analog_set_temp_bkpt(*t3); - } - break; - - case EVENT: /** discrete call...lots to do **/ - - test_double = TIME; - - if ( 0.0 == TIME ) { /* DC analysis...preset values, - as appropriate.... */ - - /* retrieve & normalize phase value */ - *phase = PARAM(init_phase); - if ( 0 > *phase ) { - *phase = *phase + 360.0; - } - *phase = *phase / 360.0; - - /* set phase value to init_phase */ - *phase_old = *phase; - - /* preset time values to harmless values... */ - *t1 = -1; - *t3 = -1; - } - - /* Calculate the time variables and the output value - for this iteration */ - - /* Output is always set to STRONG */ - OUTPUT_STRENGTH(out) = STRONG; - - if( *t1 == TIME ) { /* rising edge */ - - OUTPUT_STATE(out) = ONE; - OUTPUT_DELAY(out) = PARAM(rise_delay); - - } - else { - - if ( *t3 == TIME ) { /* falling edge */ - - OUTPUT_STATE(out) = ZERO; - OUTPUT_DELAY(out) = PARAM(fall_delay); - } - - else { /* no change in output */ - - if ( TIME != 0.0 ) { - OUTPUT_CHANGED(out) = FALSE; - } - - if ( (*t1 < TIME) && (TIME < *t3) ) { - OUTPUT_STATE(out) = ONE; - } - else { - OUTPUT_STATE(out) = ZERO; - } - } - } - break; + if (table) + free(table); + STATIC_VAR(locdata) = NULL; } } +/* Get the current duty cycle. */ +static double get_dc(double ctl, struct pwl *table, int csize) +{ + double d; + int i; + for (i = 0; i < csize; ++i) { + if (table[i].ctl > ctl) + break; + } + /* Interpolation outside input range continues slope. */ + if (i > 0) { + if (i == csize) + i -= 2; + else + i--; + } + d = table[i].dc + + (ctl - table[i].ctl) * ((table[i + 1].dc - table[i].dc) / + (table[i + 1].ctl - table[i].ctl)); + /* limit duty cycle d to 0.01 <= d <= 0.99 */ + if (d > 0.99) + d = 0.99; + else if (d < 0.01) + d = 0.01; + return d; +} + +/* The state data. */ + +struct state { + double last_time; // Time of last output change. + Digital_State_t last; // Last value output. +}; + +/* The code-model function. */ + +void cm_d_pwm(ARGS) +{ + struct pwl *table; + struct state *state; + double ctl, delta, when, ddc; + int csize, i; + + CALLBACK = cm_d_pwm_callback; + + csize = PARAM_SIZE(cntl_array); + delta = 1.0 / PARAM(frequency); + + if (INIT) { + + /* Validate PWL table. */ + + for (i = 0; i < csize - 1; ++i) { + if (PARAM(cntl_array[i]) >= PARAM(cntl_array[i + 1])) + break; + } + + if (i < csize - 1 || csize != PARAM_SIZE(dc_array)) { + cm_message_send("Badly-formed control table"); + STATIC_VAR(locdata) = NULL; + return; + } + + /* Allocate PWL table. */ + + table = malloc(csize * sizeof (struct pwl)); + STATIC_VAR(locdata) = table; + if (!table) + return; + + for (i = 0; i < csize; ++i) { + table[i].ctl = PARAM(cntl_array[i]); + table[i].dc = PARAM(dc_array[i]); + if (table[i].dc <= 0) { + cm_message_printf("Error: duty cycle %g is not positve, " + "value replaced by 0.01.", + table[i].dc); + table[i].dc = 0.01; + } + else if (table[i].dc >= 1) { + cm_message_printf("Error: duty cycle %g is 1 or larger, " + "value replaced by 0.99.", + table[i].dc); + table[i].dc = 0.99; + } + } + + /* Allocate state data. */ + + cm_event_alloc(0, sizeof (struct state)); + return; + } + + table = STATIC_VAR(locdata); + if (!table) + return; + state = (struct state *)cm_event_get_ptr(0, 0); + + if (CALL_TYPE != EVENT) { + if (TIME == 0.0) { + double phase; + + /* Set initial output and state data. */ + + ctl = INPUT(cntl_in); + ddc = get_dc(ctl, table, csize); + + phase = PARAM(init_phase); + phase /= 360.0; + if (phase < 0.0) + phase += 1.0; + + /* When would a hypothetical previous transition have been? */ + + state->last_time = delta * (1.0 - ddc - phase); + if (state->last_time < 0.0) { + state->last = ONE; + } else { + state->last = ZERO; + state->last_time = -delta * phase; + } + } + return; + } + + /* Event call; either one requested previously or just before + * a time-step is accepted. + */ + + if (TIME == 0.0) { + OUTPUT_STATE(out) = state->last; + OUTPUT_STRENGTH(out) = STRONG; + return; + } + + /* When is the next transition due? */ + + ctl = INPUT(cntl_in); + ddc = get_dc(ctl, table, csize); + if (state->last) + delta *= ddc; + else + delta *= (1.0 - ddc); + when = state->last_time + delta; + + if (TIME >= when) { + // If the frequency rose rapidly, the transition has been missed. + // Force a shorter time-step and schedule then. + + cm_analog_set_temp_bkpt(state->last_time + FACTOR * delta); + OUTPUT_CHANGED(out) = FALSE; + return; + } + + if (TIME >= state->last_time + FACTOR * delta) { + /* TIME is reasonably close to transition time. Request output. */ + + state->last_time = when; + state->last ^= ONE; + OUTPUT_STATE(out) = state->last; + OUTPUT_STRENGTH(out) = STRONG; + OUTPUT_DELAY(out) = when - TIME; + + /* Request a call in the next half-cycle. */ + + cm_event_queue(when + FACTOR * delta); + } else { + OUTPUT_CHANGED(out) = FALSE; + + if (TIME < state->last_time) { + /* Output transition pending, nothing to do. */ + + return; + } else { + /* Request a call nearer to transition time. */ + + cm_event_queue(state->last_time + FACTOR * delta); + } + } +} diff --git a/src/xspice/icm/digital/d_pwm/d_pwm.h b/src/xspice/icm/digital/d_pwm/d_pwm.h deleted file mode 100644 index 8dc88c20d..000000000 --- a/src/xspice/icm/digital/d_pwm/d_pwm.h +++ /dev/null @@ -1,93 +0,0 @@ -/*.......1.........2.........3.........4.........5.........6.........7.........8 -================================================================================ - -FILE d_pwm/d_pwm.h - -Public Domain - -Georgia Tech Research Corporation -Atlanta, Georgia 30332 -PROJECT A-8503-405 -The ngspice team - -AUTHORS - - 25 Jul 1991 Jeffrey P. Murray - 02 Mar 2022 Holger Vogt - - -MODIFICATIONS - - 30 Sept 1991 Jeffrey P. Murray - - -SUMMARY - - This file contains the header information for the d_pwm - code model. - - -INTERFACES - - FILE ROUTINE CALLED - - N/A N/A - - - -REFERENCED FILES - - N/A - - -NON-STANDARD FEATURES - - NONE - -===============================================================================*/ -/* - Structures, etc. for d_pwm oscillator model. - 7/25/90 - Last Modified 7/25/91 J.P.Murray - 3/02/22 H. Vogt */ - -/*=======================================================================*/ - -/*=== INCLUDE FILES =====================================================*/ - -#include -#include -#include -#include - - - - -/*=== CONSTANTS =========================================================*/ - - -/**** Error Messages ****/ -char *d_pwm_allocation_error = "\n**** Error ****\nD_PWM: Error allocating VCO block storage \n"; -char *d_pwm_array_error = "\n**** Error ****\nD_PWM: Size of control array different than duty cycle array \n"; -char *d_pwm_negative_dc_error = "\n**** Error ****\nD_PWM: The extrapolated value for duty cycle\nhas been found to be negative... \n Lower duty cycle level has been clamped to 0.0 \n"; -char *d_pwm_positive_dc_error = "\n**** Error ****\nD_PWM: The extrapolated value for duty cycle\nhas been found to be > 1... \n Upper duty cycle level has been clamped to 1.0 \n"; - - - - - - -/*=== MACROS ============================================================*/ - - - -/*=== LOCAL VARIABLES & TYPEDEFS ========================================*/ - - - -/*=== FUNCTION PROTOTYPE DEFINITIONS ====================================*/ - - -/*=======================================================================*/ - -