/* * ********************************************************************* * * Copyright (C) 1988, 1990 Stanford University. * * * Permission to use, copy, modify, and distribute this * * * software and its documentation for any purpose and without * * * fee is hereby granted, provided that the above copyright * * * notice appear in all copies. Stanford University * * * makes no representations about the suitability of this * * * software for any purpose. It is provided "as is" without * * * express or implied warranty. Export of this software outside * * * of the United States of America may require an export license. * * ********************************************************************* */ /* * Event-driven timing simulation step for irsim. * * Use diamond shape region in R-V plane for dc voltage computation. * Use 2-pole 1-zero model for pure charge sharing delay calculations. * Use 2-pole zero-at-origin model for driven spike analysis. * Details of the models can be found in Chorng-Yeong Chu's thesis: * "Improved Models for Switch-Level Simulation" also available as a * Stanford Technical Report CSL-TR-88-368. */ #include #include #include #include #include "defs.h" #include "net.h" #include "globals.h" #ifdef DO_CACHE #include "cache.h" #endif #define SMALL 1E-15 /* A small number */ #define LARGE 1E15 /* A large number */ #define LIMIT 1E8 /* R > LIMIT are considered infinite */ #define NP_RATIO 0.7 /* nmos-pmos ratio for spike analysis */ #define MIN( a, b ) ((a < b) ? a : b) #define MAX( a, b ) ((a > b) ? a : b) /* combine 2 resistors in parallel */ #define COMBINE( R1, R2 ) ( (R1) * (R2) / ( (R1) + (R2) ) ) /* combine 2 resistors in parallel, watch out for zero resistance */ #define COMBINE_R( A, B ) ( ((A) + (B) <= SMALL) ? 0 : COMBINE( A, B ) ) #define IsWatched( N ) ( (N)->nflags & WATCHED ) #define SetDebug( FL, N ) \ ( ((debug & (FL)) == (FL) and IsWatched( N )) ? 1 : 0 ) /* flags used in thevenin structure */ #define T_DEFINITE 0x01 /* set for a definite rooted path */ #define T_UDELAY 0x02 /* set if user specified delay encountered */ #define T_SPIKE 0x04 /* set if charge-sharing spike possible */ #define T_DRIVEN 0x08 /* set if this branch is driven */ #define T_REFNODE 0x10 /* set for the reference node in pure c-s */ #define T_XTRAN 0x20 /* set if connecting through an X trans. */ #define T_INT 0x40 /* set if we should consider input slope */ #define T_DOMDRIVEN 0x80 /* set if branch driven to dominant voltage */ public int tunitdelay = 0; /* if <> 0, all transitions take * 'tunitdelay' DELAY-units */ public int tdecay = 0; /* if <> 0, undriven nodes decay to * X after 'tdecay' DELAY-units */ public int settle = 0; /* temporary hack. If driven by * two nodes at opposite values, * driven node settles to X after * 'settle' DELAY-units. Added by * Tim, 10/6/06 */ public char withdriven; /* TRUE if stage is driven by * some input */ typedef struct { double min; double max; } Range; /* Parameters gathered during a tree walk */ /* resists are in ohms, caps in pf */ typedef struct thevenin { union { Thev t; nptr n; } link; /* links these structures together */ int flags; /* flags defined above */ Range Clow; /* capacitance charged low */ Range Chigh; /* capacitance charged high */ Range Rup; /* resistance pulling up to Vdd */ Range Rdown; /* resistance pulling down to GND */ Range Req; /* resist. of present (parallel) xtor(s) */ Range V; /* normalized voltage range (0-1) */ double Rmin; /* minimum resistance to any driver */ double Rdom; /* minimum resistance to dominant driver */ double Rmax; /* maximum resistance to dominant driver */ double Ca; /* Adjusted non-switching capacitance */ double Cd; /* Adjusted total capacitance */ double tauD; /* Elmore delay (psec) */ double tauA; /* 1st order time-constant (psec) */ double tauP; /* 2nd order time-constant (psec) */ double Tin; /* input transition = (input_tau) * Rin */ short tplh; /* user specified low->high delay (DELTA) */ short tphl; /* user specified high->low delay (DELTA) */ char final; /* steady-state value calculated (H, L, X) */ char tau_done; /* if tau calculated, == dominant voltage */ char taup_done; /* if tauP calculated, == dominant voltage */ } thevenin; typedef struct /* one for each possible dominant voltage */ { nptr nd; /* list of nodes driven to this potential */ int spike; /* TRUE if this pot needs spike analysis */ } Dominant; typedef struct { double ch_delay; /* charging delay */ double dr_delay; /* driven delay */ float peak; /* spike peak */ int charge; /* spike charge */ } SpikeRec, *pspk; private Thev thev_free = NULL; /* Free list of thev structures */ private int inc_level; /* 1 if debug and node is watched */ private Dominant dom_pot[ N_POTS ]; /* dominant voltage structure */ private thevenin init_thev; /* pre-initialized thevenin structs */ private thevenin input_thev[ N_POTS ]; /* forward references */ private void scheduleDriven(), schedulePureCS(), UndoConnList(); private void parallel_op(), CleanEvents(); private void EnqueDecay( nptr, int); private int ComputeDC( nptr, char *); private Thev get_dc_val(), series_op(), get_tau(); private double get_tauP(); private pspk ComputeSpike(); private void print_dc(), print_fval(), print_tau(), print_taup(); private void print_final(), print_spike(), print_spk(); public void linear_model( n ) nptr n; { int i, changes; char conflict = FALSE; nevals++; for( i = LOW; i <= HIGH; i++ ) dom_pot[i].nd = NULL, dom_pot[i].spike = FALSE; if( n->nflags & VISITED ) BuildConnList( n ); changes = ComputeDC( n, &conflict ); if( not changes ) CleanEvents( n ); else if( withdriven ) { if (settle != 0 and conflict ) EnqueDecay( n, settle ); else scheduleDriven(); } else { if( tdecay != 0) EnqueDecay( n, tdecay ); else schedulePureCS( n ); } UndoConnList( n ); } private void CleanEvents( n ) register nptr n; { register evptr ev; do { while( (ev = n->events) != NULL ) PuntEvent( n, ev ); } while( (n = n->nlink) != NULL ); } private void EnqueDecay( n, decayval ) register nptr n; register int decayval; { register evptr ev; do { ev = n->events; if( (ev == NULL) ? (n->npot != X) : (ev->eval != X) ) { if( (debug & DEBUG_EV) and IsWatched( n ) ) lprintf( stdout, " decay transition for %s @ %.2fns\n", pnode( n ), d2ns( cur_delta + decayval ) ); enqueue_event( n, DECAY, (long) decayval, (long) decayval ); } n = n->nlink; } while( n != NULL ); } private void UndoConnList( n ) register nptr n; { register nptr next; register lptr l; register tptr t; #ifdef CL_STATS register int num_trans = 0; for( next = n; next != NULL; next = next->nlink ) { for( l = next->nterm; l != NULL; l = l->next ) { t = l->xtor; if( t->state != OFF ) t->tflags |= CROSSED; } } #endif /* CL_STATS */ do { next = n->nlink; n->nlink = NULL; n->n.thev->link.t = thev_free; thev_free = n->n.thev; for( l = n->nterm; l != NULL; l = l->next ) { t = l->xtor; if( t->state == OFF ) continue; #ifdef CL_STATS if( t->tflags & CROSSED ) num_trans ++; #endif /* CL_STATS */ if( not( t->tflags & (PBROKEN | BROKEN) ) ) { register Thev r; if( (r = t->scache.r) != NULL ) { r->link.t = thev_free; thev_free = r; } if( (r = t->dcache.r) != NULL ) { r->link.t = thev_free; thev_free = r; } } t->scache.r = t->dcache.r = NULL; t->tflags &= ~(CROSSED | BROKEN | PBROKEN | PARALLEL); } } while( (n = next) != NULL ); #ifdef CL_STATS RecordConnList( num_trans ); #endif /* CL_STATS */ } /* * Schedule the final value for node 'nd'. Check to see if this final value * invalidates other events. Since this event has more recent information * regarding the state of the network, delete transitions scheduled to come * after it. Zero-delay transitions are avoided by turning them into unit * delays (1 delta). Events scheduled to occur at the same time as this event * and driving the node to the same value are not punted. * Finally, before scheduling the final value, check that it is different * from the previously calculated value for the node. */ public void QueueFVal( nd, fval, tau, delay ) nptr nd; int fval; double tau, delay; { register evptr ev; register Ulong delta; int queued = 0; delta = cur_delta + (Ulong) ps2d( delay ); if( delta == cur_delta ) /* avoid zero delay */ delta++; while( (ev = nd->events) != NULL and ev->ntime >= delta ) { if( ev->ntime == delta and ev->eval == fval ) break; PuntEvent( nd, ev ); } delta -= cur_delta; if( fval != ((ev == NULL) ? nd->npot : ev->eval) ) { enqueue_event( nd, fval, (long) delta, (long) ps2d( tau ) ); queued = 1; } if( (debug & DEBUG_EV) and IsWatched( nd ) ) print_final( nd, queued, tau, delta ); } private void QueueSpike( nd, spk ) nptr nd; pspk spk; { register evptr ev; register Ulong ch_delta, dr_delta; while( (ev = nd->events) != NULL ) PuntEvent( nd, ev ); if( spk == NULL ) /* no spike, just punt events */ { return; } ch_delta = (Ulong) ps2d( spk->ch_delay ); dr_delta = (Ulong) ps2d( spk->dr_delay ); if( ch_delta == 0 ) ch_delta = 1; if( dr_delta == 0 ) dr_delta = 1; if( (debug & DEBUG_EV) and IsWatched( nd ) ) print_spike( nd, spk, ch_delta, dr_delta ); if( dr_delta <= ch_delta ) /* no zero delay spikes, done */ { return; } /* enqueue spike and final value events */ enqueue_event( nd, (int) spk->charge, (long) ch_delta, (long) ch_delta ); enqueue_event( nd, (int) nd->npot, (long) dr_delta, (long) ch_delta ); } private void scheduleDriven() { register nptr nd; register Thev r; int dom; double tau, delay; for( dom = 0; dom < N_POTS; dom++ ) { for( nd = dom_pot[ dom ].nd; nd != NULL; nd = r->link.n ) { inc_level = SetDebug( DEBUG_TAU | DEBUG_TW, nd ); r = get_tau( nd, (tptr) NULL, dom, inc_level ); if( inc_level == 0 and SetDebug( DEBUG_TAU, nd ) ) print_tau( nd, r, -1 ); r->tauA = r->Rdom * r->Ca; r->tauD = r->Rdom * r->Cd; if( r->flags & T_SPIKE ) /* deal with these later */ continue; if( nd->npot == r->final ) /* no change, just punt */ { evptr ev; while( (ev = nd->events) != NULL ) PuntEvent( nd, ev ); continue; } else if( tunitdelay ) { delay = tunitdelay; tau = 0.0; } else if( r->flags & T_UDELAY ) { switch( r->final ) { case LOW : tau = d2ps( r->tphl ); break; case HIGH : tau = d2ps( r->tplh ); break; case X : tau = d2ps( MIN( r->tphl, r->tplh ) ); break; } delay = tau; } else { if( r->final == X ) tau = r->Rmin * r->Ca; else if( r->flags & T_DEFINITE ) tau = r->Rmax * r->Ca; else tau = r->Rdom * r->Ca; if( (r->flags & T_INT) and r->Tin > 0.5 ) delay = sqrt( tau * tau + d2ps( r->Tin ) * r->Ca ); else delay = tau; } QueueFVal( nd, (int) r->final, tau, delay ); } if( dom_pot[ dom ].spike ) { for( nd = dom_pot[ dom ].nd; nd != NULL; nd = nd->n.thev->link.n ) { r = nd->n.thev; if( not (r->flags & T_SPIKE) ) continue; inc_level = SetDebug( DEBUG_TAUP | DEBUG_TW, nd ); r->tauP = get_tauP( nd, (tptr) NULL, dom, inc_level ); r->tauP *= r->Rdom / r->tauA; QueueSpike( nd, ComputeSpike( nd, r, dom ) ); } } } } private void schedulePureCS( nlist ) nptr nlist; { register nptr nd; register Thev r; int dom; double taup, tau, delay; r = nlist->n.thev; dom = r->final; r->flags |= T_REFNODE; taup = 0.0; for( nd = nlist; nd != NULL; nd = nd->nlink ) { inc_level = SetDebug( DEBUG_TAU | DEBUG_TW, nd ); r = get_tau( nd, (tptr) NULL, dom, inc_level ); r->tauD = r->Rdom * r->Ca; switch( dom ) { case LOW : r->tauA = r->Rdom * ( r->Ca - r->Cd * r->V.max ); break; case HIGH : r->tauA = r->Rdom * ( r->Cd * ( 1 - r->V.min ) - r->Ca ); break; case X : /* approximate Vf = 0.5 */ r->tauA = r->Rdom * ( r->Ca - r->Cd * 0.5 ); break; } taup += r->tauA * nd->ncap; } r = nlist->n.thev; taup = taup / (r->Clow.min + r->Chigh.max); /* tauP = tauP / CT */ for( nd = nlist; nd != NULL; nd = nd->nlink ) { r = nd->n.thev; if( r->final == nd->npot ) /* no change, no delay */ delay = tau = 0.0; else { switch( r->final ) { case LOW : tau = (r->tauA - taup) / (1.0 - r->V.max); break; case HIGH : tau = (taup - r->tauA) / r->V.min; break; case X : tau = (r->tauA - taup) * 2.0; break; } if( tau < 0.0 ) tau = 0.0; if( tunitdelay ) delay = tunitdelay, tau = 0.0; else delay = tau; } QueueFVal( nd, (int) r->final, tau, delay ); } } /* * Compute the final value for each node on the connection list. * This routine will update V.min and V.max and add the node to the * corresponding dom_driver entry. Return TRUE if any node changes value. */ private int ComputeDC( nlist, conflict ) nptr nlist; char *conflict; { register nptr this; register Thev r; int anyChange = FALSE; for( this = nlist; this != NULL; this = this->nlink ) { inc_level = SetDebug( DEBUG_DC | DEBUG_TW, this ); this->n.thev = r = get_dc_val( this, (tptr) NULL, inc_level ); if( withdriven ) { if( r->Rdown.min >= LIMIT ) r->V.min = 1; else r->V.min = r->Rdown.min / ( r->Rdown.min + r->Rup.max ); if( r->Rup.min >= LIMIT ) r->V.max = 0; else r->V.max = r->Rdown.max / ( r->Rdown.max + r->Rup.min ); } else /* use charge/total charge if undriven */ { r->V.min = r->Chigh.min / ( r->Chigh.min + r->Clow.max ); r->V.max = r->Chigh.max / ( r->Chigh.max + r->Clow.min ); } if( r->V.min >= this->vhigh ) r->final = HIGH; else if( r->V.max <= this->vlow ) r->final = LOW; else { r->final = X; if ( withdriven ) *conflict = TRUE; } if( withdriven ) { /* * if driven and indefinite, driven value must equal * charging value otherwise the final value is X */ if( r->final != X and not (r->flags & T_DEFINITE) ) { char cs_val; if( r->Chigh.min >= this->vhigh * (r->Chigh.min + r->Clow.max) ) cs_val = HIGH; else if( r->Chigh.max <= this->vlow * (r->Chigh.max + r->Clow.min) ) cs_val = LOW; else cs_val = X; /* always X */ if( cs_val != r->final ) r->final = X; } r->link.n = dom_pot[ (int)r->final ].nd; /* add it to list */ dom_pot[ (int)r->final ].nd = this; /* possible spike if no transition and opposite charge exists */ if( r->final == this->npot and ( (r->final == LOW and r->Chigh.min > SMALL) or (r->final == HIGH and r->Clow.min > SMALL) ) ) { r->flags |= T_SPIKE; dom_pot[ (int)r->final ].spike = TRUE; anyChange = TRUE; } } if( r->final != this->npot ) anyChange = TRUE; if( SetDebug( DEBUG_DC, this ) ) print_fval( this, r ); } return( anyChange ); } #define NEW_THEV( T ) \ { \ if( ((T) = thev_free) == NULL ) \ (T) = (Thev) MallocList( sizeof( thevenin ), 1 ); \ thev_free = T->link.t; \ } /* * Compute the parametes used to calculate the final value (Chigh, Clow, * Rup, Rdown) by doing a depth-first traversal of the tree rooted at node * 'n'. The traversal is done by a recursive walk through the tree. Note that * the stage is already a simple tree; loops are broken by BuildConnList. As * a side effect also compute Req of the present transistor and all other * transistors in parallel with it, and any specified user delays. * The parameters are: * * n : the node whose dc parameters we want. * tran : the transistor that leads to 'n' (NULL if none). * level : level of recursion if we are debugging this node, else 0. */ private Thev get_dc_val( n, tran, level ) nptr n; tptr tran; int level; { register lptr l; register tptr t; register nptr other; register Thev r; Thev cache, *pcache; NEW_THEV( r ); if( n->nflags & INPUT ) { *r = input_thev[ n->npot ]; return( r ); } *r = init_thev; switch( n->npot ) { case LOW : r->Clow.min = r->Clow.max = n->ncap; break; case X : r->Clow.max = r->Chigh.max = n->ncap; break; case HIGH : r->Chigh.min = r->Chigh.max = n->ncap; break; } for( l = n->nterm; l != NULL; l = l->next ) { t = l->xtor; /* ignore path going back or through a broken loop */ if( t == tran or t->state == OFF or (t->tflags & (BROKEN | PBROKEN)) ) continue; if( n == t->source ) { other = t->drain; pcache = &(t->dcache.r); } else { other = t->source; pcache = &(t->scache.r); } /* * if cache is not empty use the value found there, otherwise * compute what is on the other side of the transistor and * transmit the result through a series operation. */ if( (cache = *pcache) == NULL ) { cache = series_op( get_dc_val( other, t, level+inc_level ), t ); *pcache = cache; } parallel_op( r, cache ); } if( n->nflags & USERDELAY ) /* record user delays, if any */ { r->tplh = n->tplh; r->tphl = n->tphl; r->flags |= T_UDELAY; } if( level != 0 ) print_dc( n, r, level ); return( r ); } /* * The following macros set Req to the appropriate dynamic resistance. * If the transistor state is UNKNOWN then also set the T_XTRAN flag. */ #define GetReq( R, T, TYPE ) \ { \ if( ( (T)->tflags & PARALLEL ) ) \ get_parallel( R, T, TYPE ); \ else \ { \ (R)->Req.min = (T)->r->dynres[TYPE]; \ if( (T)->state == UNKNOWN ) \ (R)->flags |= T_XTRAN; \ else \ (R)->Req.max = (T)->r->dynres[TYPE];\ } \ } \ #define GetMinR( R, T ) \ { \ if( ( (T)->tflags & PARALLEL ) ) \ get_min_parallel( R, T ); \ else \ { \ (R)->Req.min = MIN( (T)->r->dynhigh, (T)->r->dynlow ); \ if( (T)->state == UNKNOWN ) \ (R)->flags |= T_XTRAN; \ else \ (R)->Req.max = (R)->Req.min; \ } \ } \ /* * Do the same as GetReq but deal with parallel transistors. */ private void get_parallel( r, t, restype ) Thev r; register tptr t; int restype; { register Resists *rp = t->r; double gmin, gmax; gmin = 1.0 / rp->dynres[ restype ]; gmax = (t->state == UNKNOWN ) ? 0.0 : gmin; for( t = par_list( t ); t != NULL; t = t->dcache.t ) { rp = t->r; gmin += 1.0 / rp->dynres[ restype ]; if( t->state != UNKNOWN ) gmax += 1.0 / rp->dynres[ restype ]; } r->Req.min = 1.0 / gmin; if( gmax == 0.0 ) r->flags |= T_XTRAN; else r->Req.max = 1.0 / gmax; } /* * Do the same as get_parallel but use the minimum dynamic resistance. */ void get_min_parallel( r, t ) Thev r; register tptr t; { register Resists *rp = t->r; double gmin, gmax, tmp; gmin = 1.0 / MIN( rp->dynlow, rp->dynhigh ); gmax = (t->state == UNKNOWN ) ? 0.0 : gmin; for( t = par_list( t ); t != NULL; t = t->dcache.t ) { rp = t->r; tmp = 1.0 / MIN( rp->dynlow, rp->dynhigh ); gmin += tmp; if( t->state != UNKNOWN ) gmax += tmp; } r->Req.min = 1.0 / gmin; if( gmax == 0.0 ) r->flags |= T_XTRAN; else r->Req.max = 1.0 / gmax; } /* * Add transistor 't' in series with thevenin struct 'r'. As a side effect * set Req for 't'. The midpoint voltage is used to determine whether to * use the dynamic-high or dynamic-low resistance. If the branch connecting * to 't' is not driven by an input then use the charge information. The * current estimates of both resistance or capacitance is used. */ private Thev series_op( r, t ) register Thev r; register tptr t; { double up_min, down_min; if( not (r->flags & T_DRIVEN) ) { if( r->Chigh.min > r->Clow.max ) GetReq( r, t, R_HIGH ) else if( r->Chigh.max < r->Clow.min ) GetReq( r, t, R_LOW ) else GetMinR( r, t ) return( r ); /* no driver, so just set Req */ } if( r->Rdown.min > r->Rup.max ) GetReq( r, t, R_HIGH ) else if( r->Rdown.max < r->Rup.min ) GetReq( r, t, R_LOW ) else GetMinR( r, t ) up_min = r->Rup.min; down_min = r->Rdown.min; if( up_min < LIMIT ) r->Rup.min += r->Req.min * ( 1.0 + up_min / r->Rdown.max ); if( down_min < LIMIT ) r->Rdown.min += r->Req.min * ( 1.0 + down_min / r->Rup.max ); if( r->flags & T_XTRAN ) { r->flags &= ~T_DEFINITE; r->Rup.max = r->Rdown.max = LARGE; } else { if( r->Rup.max < LIMIT ) r->Rup.max += r->Req.max * ( 1.0 + r->Rup.max / down_min ); if( r->Rdown.max < LIMIT ) r->Rdown.max += r->Req.max * ( 1.0 + r->Rdown.max / up_min ); } return( r ); } /* make oldr = (oldr || newr), but watch out for infinte resistances. */ #define DoParallel( oldr, newr ) \ { \ if( oldr > LIMIT ) \ oldr = newr; \ else if( newr < LIMIT ) \ oldr = COMBINE( oldr, newr ); \ } \ /* * Combine the new resistance block of the tree walk with the current one. * Accumulate the low and high capacitances, the user-specified * delay (if any), and the driven flag of the resulting structure. */ private void parallel_op( r, new ) Thev r, new; { r->Clow.max += new->Clow.max; r->Chigh.max += new->Chigh.max; if( not (new->flags & T_XTRAN) ) { r->Clow.min += new->Clow.min; r->Chigh.min += new->Chigh.min; } /* * Accumulate the minimum user-specified delay only if the new block * has some drive associated with it. */ if( (new->flags & (T_DEFINITE | T_UDELAY)) == (T_DEFINITE | T_UDELAY) ) { if( r->flags & T_UDELAY ) { r->tplh = MIN( r->tplh, new->tplh ); r->tphl = MIN( r->tphl, new->tphl ); } else { r->tplh = new->tplh; r->tphl = new->tphl; r->flags |= T_UDELAY; } } if( not (new->flags & T_DRIVEN) ) return; /* undriven, just update caps */ r->flags |= T_DRIVEN; /* combined result is driven */ DoParallel( r->Rup.min, new->Rup.min ); DoParallel( r->Rdown.min, new->Rdown.min ); if( r->flags & new->flags & T_DEFINITE ) /* both definite blocks */ { DoParallel( r->Rup.max, new->Rup.max ); DoParallel( r->Rdown.max, new->Rdown.max ); } else if( new->flags & T_DEFINITE ) /* only new is definite */ { r->Rup.max = new->Rup.max; r->Rdown.max = new->Rdown.max; r->flags |= T_DEFINITE; /* result is definite */ } else /* new (perhaps r) is indefinite */ { if( new->Rup.max < r->Rup.max ) r->Rup.max = new->Rup.max; if( new->Rdown.max < r->Rdown.max ) r->Rdown.max = new->Rdown.max; } } /* * Determine the input time-constant (input-slope * rstatic). We are only * interseted in transistors that just turned on (its gate has a transition * at time == cur_delta). We must be careful not to report as a transition * nodes that stop being inputs (hist->delay == 0 and hist->inp == 0). */ #define IsCurrTransition( H ) \ ( (H)->time == cur_delta and ((H)->inp == 1 or (H)->t.r.delay != 0) ) /* * Return TRUE if we should consider the input slope of this transistor. As * a side-effect, return the input time constant in 'ptin'. */ private int GetTin( t, ptin ) register tptr t; double *ptin; { register hptr h; int is_int = FALSE; if( t->state != ON ) return( FALSE ); if( (t->ttype & GATELIST) == 0 ) { h = t->gate->curr; if( IsCurrTransition( h ) ) { *ptin = h->t.r.rtime * t->r->rstatic; is_int = TRUE; } } else { double tmp = 0.0; for( t = (tptr) t->gate; t != NULL; t = t->scache.t ) { h = t->gate->curr; if( IsCurrTransition( h ) ) { is_int = TRUE; tmp += h->t.r.rtime * t->r->rstatic; } } *ptin = tmp; } return( is_int ); } #define InputTau( T, PR ) \ ( ((T)->tflags & PARALLEL) ? parallel_GetTin( T, PR ) : GetTin( T, PR ) ) private int parallel_GetTin( t, itau ) register tptr t; double *itau; { double tin, tmp = 0.0; int is_int; is_int = GetTin( t, &tin ); for( t = par_list( t ); t != NULL; t = t->dcache.t ) { if( GetTin( t, &tmp ) ) { tin = (is_int) ? COMBINE_R( tin, tmp ) : tmp; is_int = TRUE; } *itau = tin; } return( is_int ); } /* * Compute the parameters needed to calculate the 1st order time-constants * (Rmin, Rdom, Rmax, Ca, Cd, Tin) by doing a depth-first traversal of the * tree rooted at node 'n'. The parameters are gathered by performing a * recursive tree walk similar to ComputeDC. As a side effect, the tauP * field will contain the multiplication factor to move a capacitor across * a transistor using 'current distribution', this field may be required * later when computing tauP. The parameters are: * * n : the node whose time-constant parameters we want. * dom : the value of the dominant driver for this stage. * tran : the transistor that leads to 'n' (NULL if none). * level : level of recursion if we are debugging this node, else 0. * * This routine can be called more than once if the stage is dominated by * more than 1 potential, hence the tau_done flag keeps track of the potential * for which the parameters stored in the cache were computed. If the flag * value and the current dominant potential do not match, we go ahead and * recompute the values. */ private Thev get_tau( n, tran, dom, level ) nptr n; tptr tran; int dom; int level; { register Thev r, cache; register lptr l; register tptr t; register nptr other; if( tran == NULL ) r = n->n.thev; else r = (tran->source == n) ? tran->scache.r : tran->dcache.r; r->tau_done = dom; if( n->nflags & INPUT ) { r->Tin = r->Rmin = r->Ca = r->Cd = 0.0; if( n->npot == dom ) { r->Rdom = r->Rmax = 0.0; r->flags |= T_DOMDRIVEN; } else { r->flags &= ~(T_DOMDRIVEN | T_INT); if( dom == X ) r->Rdom = r->Rmax = 0.0; else r->Rdom = r->Rmax = LARGE; } return( r ); } if( n->n.thev->flags & T_REFNODE ) /* reference node in pure CS */ { r->Rmin = r->Rdom = r->Rmax = 0.0; r->Ca = r->Cd = 0.0; return( r ); } r->Rmin = r->Rdom = r->Rmax = LARGE; r->Cd = n->ncap; if( dom == X ) /* assume X nodes are charged high */ r->Ca = (n->npot == LOW) ? 0.0 : n->ncap; else r->Ca = (n->npot == dom) ? 0.0 : n->ncap; r->Tin = 0.0; r->flags &= ~(T_DOMDRIVEN | T_INT); for( l = n->nterm; l != NULL; l = l->next ) { t = l->xtor; if( t->state == OFF or t == tran or (t->tflags & (BROKEN | PBROKEN)) ) continue; if( n == t->source ) { other = t->drain; cache = t->dcache.r; } else { other = t->source; cache = t->scache.r; } if( cache->tau_done != dom ) { double oldr; cache = get_tau( other, t, dom, level + inc_level ); /* Only use input slope for xtors on the dominant (driven) path */ if( (cache->flags & T_DOMDRIVEN) and InputTau( t, &oldr ) ) { cache->flags |= T_INT; cache->Tin += oldr; } oldr = cache->Rdom; cache->Rmin += cache->Req.min; cache->Rdom += cache->Req.min; if( cache->flags & T_XTRAN ) cache->Rmax = LARGE; else cache->Rmax += cache->Req.max; /* Exclude capacitors if the other side of X transistor == dom */ if( (cache->flags & T_XTRAN) and other->npot == dom ) cache->tauP = cache->Ca = cache->Cd = 0.0; else if( oldr > LIMIT ) cache->tauP = 1.0; else { cache->tauP = oldr / cache->Rdom; cache->Ca *= cache->tauP; cache->Cd *= cache->tauP; } } r->Ca += cache->Ca; r->Cd += cache->Cd; r->Rmin = COMBINE( r->Rmin, cache->Rmin ); if( r->Rdom > LIMIT ) { r->Rdom = cache->Rdom; r->Rmax = cache->Rmax; } else if( cache->Rdom < LIMIT ) { r->Rdom = COMBINE( r->Rdom, cache->Rdom ); r->Rmax = COMBINE( r->Rmax, cache->Rmax ); } if( cache->flags & T_DOMDRIVEN ) r->flags |= T_DOMDRIVEN; /* at least 1 dominant driven path */ if( cache->flags & T_INT ) { if( r->flags & T_INT ) r->Tin = COMBINE_R( r->Tin, cache->Tin ); else { r->Tin = cache->Tin; r->flags |= T_INT; } } } if( level > 0 ) print_tau( n, r, level ); return( r ); } /* * Calculate the 2nd order time constant (tauP) for the net configuration * as seen through node 'n'. The net traversal and the parameters are * similar to 'get_tau'. Note that at this point we have not have calculated * tauA for nodes not driven to the dominant potential, hence we need to * compute those by first calling get_tau. This routine will update the tauP * entry as well as the taup_done flag. */ private double get_tauP( n, tran, dom, level ) nptr n; tptr tran; int dom; int level; { register lptr l; register tptr t; register Thev r; nptr other; double taup; if( n->nflags & INPUT ) return( 0.0 ); r = n->n.thev; if( r->tau_done != dom ) /* compute tauA for the node */ { r = get_tau( n, (tptr) NULL, dom, 0 ); r->tauA = r->Rdom * r->Ca; r->tauD = r->Rdom * r->Cd; } taup = r->tauA * n->ncap; for( l = n->nterm; l != NULL; l = l->next ) { t = l->xtor; if( t->state == OFF or t == tran or (t->tflags & (BROKEN | PBROKEN)) ) continue; if( t->source == n ) other = t->drain, r = t->dcache.r; else other = t->source, r = t->scache.r; if( r->taup_done != dom ) { r->tauP *= get_tauP( other, t, dom, level + inc_level ); r->taup_done = dom; } taup += r->tauP; } if( level > 0 ) print_taup( n, level, taup ); return( taup ); } #include "spiketbl.c" /* * Compute the size of spike. If the spike is too small return NULL, else * fill in the appropriate structure and return a pointer to it. In order * to determine in which table to lookup the spike peak we look at the * conductivity of all ON transistors connected to node 'nd'; the type with * the largest conductivity determines whether it is mostly an nmos or pmos * network. This simple scheme should work for most simple nets. */ private pspk ComputeSpike( nd, r, dom ) nptr nd; register Thev r; int dom; { int rtype, tab_indx, alpha, beta; float nmos, pmos; static SpikeRec spk; register lptr l; if( r->tauP <= SMALL ) /* no capacitance, no spike */ { if( (debug & DEBUG_SPK) and IsWatched( nd ) ) lprintf( stdout, " spike( %s ) ignored (taup=0)\n" ); return( NULL ); } rtype = (dom == LOW) ? R_LOW : R_HIGH; nmos = pmos = 0; for( l = nd->nterm; l != NULL; l = l->next ) { register tptr t; t = l->xtor; if( t->state == OFF or (t->tflags & BROKEN) ) continue; if( BASETYPE( t->ttype ) == PCHAN ) pmos += 1.0 / t->r->dynres[ rtype ]; else nmos += 1.0 / t->r->dynres[ rtype ]; } if( nmos > NP_RATIO * (pmos + nmos) ) /* mostly nmos */ tab_indx = (rtype == R_LOW) ? NLSPKMIN : NLSPKMAX; else if( pmos > NP_RATIO * (pmos + nmos) ) /* mostly pmos */ tab_indx = (rtype == R_LOW) ? NLSPKMAX : NLSPKMIN; else tab_indx = LINEARSPK; alpha = (int) ( SPIKETBLSIZE * r->tauA / (r->tauA + r->tauP - r->tauD) ); if( alpha < 0 ) alpha = 0; else if( alpha > SPIKETBLSIZE ) alpha = SPIKETBLSIZE; beta = (int) ( SPIKETBLSIZE * (r->tauD - r->tauA) / r->tauD ); if( beta < 0 ) beta = 0; else if( beta > SPIKETBLSIZE ) beta = SPIKETBLSIZE; spk.peak = spikeTable[ tab_indx ][ beta ][ alpha ]; spk.ch_delay = delayTable[ beta ][ alpha ]; if( dom == LOW ) { if( spk.peak <= nd->vlow ) /* spike is too small */ goto no_spike; else spk.charge = (spk.peak >= nd->vhigh) ? HIGH : X; } else /* dom == HIGH */ { if( spk.peak <= 1.0 - nd->vhigh ) goto no_spike; else spk.charge = (spk.peak >= 1.0 - nd->vlow) ? LOW : X; } spk.ch_delay *= r->tauA * r->tauD / r->tauP; if( r->Rmax < LARGE ) spk.dr_delay = r->Rmax * r->Ca; else spk.dr_delay = r->Rdom * r->Ca; if( (debug & DEBUG_SPK) and IsWatched( nd ) ) print_spk( nd, r, tab_indx, dom, alpha, beta, &spk, TRUE ); return( &spk ); no_spike : if( (debug & DEBUG_SPK) and IsWatched( nd ) ) print_spk( nd, r, tab_indx, dom, alpha, beta, &spk, FALSE ); return( NULL ); } /* * Initialize pre-initialized thevenin structs. I want to get it right * and this is much safer than letting the compiler initialize it. */ public void InitThevs() { register Thev t; init_thev.link.n = NULL; init_thev.flags = 0; init_thev.Clow.min = 0.0; init_thev.Clow.max = 0.0; init_thev.Chigh.min = 0.0; init_thev.Chigh.max = 0.0; init_thev.Rup.min = LARGE; init_thev.Rup.max = LARGE; init_thev.Rdown.min = LARGE; init_thev.Rdown.max = LARGE; init_thev.Req.min = LARGE; init_thev.Req.max = LARGE; init_thev.V.min = 1.0; init_thev.V.max = 0.0; init_thev.Rmin = LARGE; init_thev.Rdom = LARGE; init_thev.Rmax = LARGE; init_thev.Ca = 0.0; init_thev.Cd = 0.0; init_thev.tauD = 0.0; init_thev.tauA = 0.0; init_thev.tauP = 0.0; init_thev.Tin = SMALL; init_thev.tplh = 0; init_thev.tphl = 0; init_thev.final = X; init_thev.tau_done = N_POTS; init_thev.taup_done = N_POTS; t = &input_thev[ LOW ]; t->link.n = NULL; t->flags = T_DEFINITE | T_DRIVEN; t->Clow.min = 0.0; t->Clow.max = 0.0; t->Chigh.min = 0.0; t->Chigh.max = 0.0; t->Rup.min = LARGE; t->Rup.max = LARGE; t->Rdown.min = SMALL; t->Rdown.max = SMALL; t->Req.min = LARGE; t->Req.max = LARGE; t->V.min = 0.0; t->V.max = 0.0; t->Rmin = SMALL; t->Rdom = LARGE; t->Rmax = LARGE; t->Ca = 0.0; t->Cd = 0.0; t->tauD = 0.0; t->tauA = 0.0; t->tauP = 0.0; t->Tin = SMALL; t->tplh = 0; t->tphl = 0; t->final = LOW; t->tau_done = N_POTS; t->taup_done = N_POTS; t = &input_thev[ HIGH ]; t->link.n = NULL; t->flags = T_DEFINITE | T_DRIVEN; t->Clow.min = 0.0; t->Clow.max = 0.0; t->Chigh.min = 0.0; t->Chigh.max = 0.0; t->Rup.min = SMALL; t->Rup.max = SMALL; t->Rdown.min = LARGE; t->Rdown.max = LARGE; t->Req.min = LARGE; t->Req.max = LARGE; t->V.min = 1.0; t->V.max = 1.0; t->Rmin = SMALL; t->Rdom = LARGE; t->Rmax = LARGE; t->Ca = 0.0; t->Cd = 0.0; t->tauD = 0.0; t->tauA = 0.0; t->tauP = 0.0; t->Tin = SMALL; t->tplh = 0; t->tphl = 0; t->final = HIGH; t->tau_done = N_POTS; t->taup_done = N_POTS; t = &input_thev[ X ]; t->link.n = NULL; t->flags = T_DEFINITE | T_DRIVEN; t->Clow.min = 0.0; t->Clow.max = 0.0; t->Chigh.min = 0.0; t->Chigh.max = 0.0; t->Rup.min = SMALL; t->Rup.max = LARGE; t->Rdown.min = SMALL; t->Rdown.max = LARGE; t->Req.min = LARGE; t->Req.max = LARGE; t->V.min = 1.0; t->V.max = 0.0; t->Rmin = SMALL; t->Rdom = LARGE; t->Rmax = LARGE; t->Ca = 0.0; t->Cd = 0.0; t->tauD = 0.0; t->tauA = 0.0; t->tauP = 0.0; t->Tin = SMALL; t->tplh = 0; t->tphl = 0; t->final = X; t->tau_done = N_POTS; t->taup_done = N_POTS; input_thev[ X+1 ] = input_thev[ X ]; } /* * printing routines for debug mode */ private char *get_indent( i ) int i; { static char indent[] = "........................."; static char spaces[] = " "; static int last_c = 0; if( i >= sizeof( indent ) ) i = sizeof( indent ) - 1; else i++; indent[ i ] = '\0'; lprintf( stdout, " %s", indent ); indent[ i ] = ' '; spaces[ last_c ] = ' '; last_c = i + 1; spaces[ last_c ] = '\0'; return( spaces ); } private char *r2ascii( s, r ) char *s; double r; { if( r >= LIMIT ) (void) strcpy( s, " - " ); else if( r > 1.0 ) { int exp; for( exp = 0; r >= 1000.0; exp++, r *= 0.001 ); (void) sprintf( s, "%.1f%c", r, " KMG"[ exp ] ); } else (void) sprintf( s, "%g", r ); return( s ); } private void print_dc( n, r, level ) nptr n; Thev r; int level; { char cbuf[4][20]; char *indent; indent = get_indent( level ); lprintf( stdout, "compute_dc( %s )\n%s", pnode( n ), indent ); if( not withdriven ) lprintf( stdout, "pure cs:" ); else lprintf( stdout, "%sefinite", (r->flags & T_DEFINITE) ? "D" : "Ind"); lprintf( stdout, " Rup=[%s, %s] Rdown=[%s, %s]\n", r2ascii( cbuf[0], r->Rup.min ), r2ascii( cbuf[1], r->Rup.max ), r2ascii( cbuf[2], r->Rdown.min ), r2ascii( cbuf[3], r->Rdown.max ) ); lprintf( stdout, "%sClow=[%.2f, %.2f] Chigh=[%.2f, %.2f]\n", indent, r->Clow.min, r->Clow.max, r->Chigh.min, r->Chigh.max ); } private void print_fval( n, r ) nptr n; Thev r; { lprintf( stdout, " final_value( %s ) V=[%.2f, %.2f] => %c", pnode( n ), r->V.min, r->V.max, vchars[ (int)r->final ] ); lprintf( stdout, (r->flags & T_SPIKE) ? " (spk)\n" : "\n" ); } private void print_tau( n, r, level ) nptr n; Thev r; int level; { char cbuf[3][20]; char *indent; indent = get_indent( level ); lprintf( stdout, "compute_tau( %s )\n%s", pnode( n ), indent ); lprintf( stdout, "{Rmin=%s Rdom=%s Rmax=%s}", r2ascii( cbuf[0], r->Rmin ), r2ascii( cbuf[1], r->Rdom ), r2ascii( cbuf[2], r->Rmax ) ); lprintf( stdout, " {Ca=%.2f Cd=%.2f}\n", r->Ca, r->Cd ); lprintf( stdout, "%stauA=%.2f tauD=%.2f ns, RTin=", indent, ps2ns( r->Rdom * r->Ca ), ps2ns( r->Rdom * r->Cd ) ); if( r->flags & T_INT ) lprintf( stdout, "%.2f ohm*ns\n", d2ns( r->Tin ) ); else lprintf( stdout, "-\n" ); } private void print_taup( n, level, taup ) nptr n; int level; double taup; { (void) get_indent( level ); lprintf( stdout, "tauP( %s ) = %.2f ns\n", pnode( n ), ps2ns( taup ) ); } private void print_final( nd, queued, tau, delay ) nptr nd; int queued; double tau; Ulong delay; { Thev r; Ulong dtau; /* tau in deltas */ r = nd->n.thev; dtau = ps2d( tau ); lprintf( stdout, " [event %s->%c @ %.2f] ", pnode( cur_node ), vchars[ cur_node->npot ], d2ns( cur_delta ) ); lprintf( stdout, (queued ? "causes %stransition for" : "%sevaluates"), (withdriven ? "" : "CS ") ); lprintf( stdout, " %s: %c -> %c", pnode( nd ), vchars[ nd->npot ], vchars[ (int)r->final ] ); lprintf( stdout, " (tau=%.2fns, delay=%.2fns)\n", d2ns( dtau ), d2ns( delay ) ); } private void print_spike( nd, spk, ch_delay, dr_delay ) nptr nd; pspk spk; Ulong ch_delay, dr_delay; { lprintf( stdout, " [event %s->%c @ %.2f] causes ", pnode( cur_node ), vchars[ cur_node->npot ], d2ns( cur_delta ) ); if( dr_delay <= ch_delay ) lprintf( stdout, "suppressed " ); lprintf( stdout, "spike for %s: %c -> %c -> %c", pnode( nd ), vchars[ nd->npot ], vchars[ spk->charge ], vchars[ nd->npot ] ); lprintf( stdout, " (peak=%.2f delay: ch=%.2fns, dr=%.2fns)\n", spk->peak, d2ns( ch_delay ), d2ns( dr_delay ) ); } private void print_spk( nd, r, tab, dom, alpha, beta, spk, is_spk ) nptr nd; Thev r; int tab, dom, alpha, beta; pspk spk; int is_spk; { char *net_type; lprintf( stdout, " spike_analysis( %s ):", pnode( nd ) ); if( tab == LINEARSPK ) net_type = "n-p mix"; else if( tab == NLSPKMIN ) net_type = (dom == LOW) ? "nmos" : "pmos"; else net_type = (dom == LOW) ? "pmos" : "nmos"; lprintf( stdout, " %s driven %s\n", net_type, (dom == LOW) ? "low" : "high" ); lprintf( stdout, "{tauA=%.2f tauD=%.2f tauP=%.2f} ns ", ps2ns( r->tauA ), ps2ns( r->tauD ), ps2ns( r->tauP ) ); lprintf( stdout, "alpha=%d beta=%d => peak=%.2f", alpha, beta, spk->peak ); if( is_spk ) lprintf( stdout, " v=%c\n", vchars[ spk->charge ] ); else lprintf( stdout, " (too small)\n" ); }