Add bidirectional digital/analog bridge.
This commit is contained in:
parent
e80b724460
commit
372d9b2c87
|
|
@ -123,15 +123,23 @@ for device libraries whose devices are defined by subcircuits.
|
||||||
digital nodes where defaults are supplied. For digital the defaults
|
digital nodes where defaults are supplied. For digital the defaults
|
||||||
are:
|
are:
|
||||||
|
|
||||||
( ".model auto_adc adc_bridge(in_low = {%g/2} in_high = {%g/2})"
|
( ".model auto_adc adc_bridge(in_low = '%g/2' in_high = '%g/2')"
|
||||||
"auto_adc%d [ %s ] [ %s ] auto_adc" )
|
"auto_adc%d [ %s ] [ %s ] auto_adc" )
|
||||||
|
|
||||||
for a digital input and
|
for a digital input with
|
||||||
|
|
||||||
( ".model auto_dac dac_bridge(out_low = 0 out_high = %g)"
|
( ".model auto_dac dac_bridge(out_low = 0 out_high = %g)"
|
||||||
"auto_dac%d [ %s ] [ %s ] auto_dac" )
|
"auto_dac%d [ %s ] [ %s ] auto_dac" )
|
||||||
|
|
||||||
for digital output.
|
for digital output and
|
||||||
|
|
||||||
|
( ".model auto_bidi bidi_bridge(out_high=%g in_low='%g/2' in_high='%g/2')"
|
||||||
|
"auto_bidi%d [ %s ] [ %s ] null auto_bidi" )
|
||||||
|
|
||||||
|
for a node with a digital inout port or with both inputs and outputs.
|
||||||
|
Note that single quotes surround expressions to be evaluated during
|
||||||
|
netlist parsing. They are preferred to braces because braces are stripped
|
||||||
|
by the "set" command.
|
||||||
|
|
||||||
A non-digital example (real to analogue) is:
|
A non-digital example (real to analogue) is:
|
||||||
|
|
||||||
|
|
@ -583,22 +591,22 @@ static struct bridge *find_bridge(Evt_Node_Info_t *event_node,
|
||||||
/* Last and probably most common case, default digital bridges. */
|
/* Last and probably most common case, default digital bridges. */
|
||||||
|
|
||||||
if (!format && event_node->udn_index == 0) {
|
if (!format && event_node->udn_index == 0) {
|
||||||
if (direction == MIF_INOUT) {
|
|
||||||
return NULL; // Abandon hope, for now.
|
|
||||||
} else {
|
|
||||||
if (direction == MIF_IN) {
|
if (direction == MIF_IN) {
|
||||||
setup = ".model auto_adc adc_bridge("
|
setup = ".model auto_adc adc_bridge("
|
||||||
"in_low = {%g/2} in_high = {%g/2})",
|
"in_low = '%g/2' in_high = '%g/2')";
|
||||||
format = copy("auto_adc%d [ %s ] [ %s ] auto_adc");
|
format = copy("auto_adc%d [ %s ] [ %s ] auto_adc");
|
||||||
} else { // MIF_OUT
|
} else if (direction == MIF_OUT) { // MIF_OUT
|
||||||
setup = ".model auto_dac dac_bridge("
|
setup = ".model auto_dac dac_bridge("
|
||||||
"out_low = 0 out_high = %g)";
|
"out_low = 0 out_high = %g)";
|
||||||
format = "auto_dac%d [ %s ] [ %s ] auto_dac";
|
format = "auto_dac%d [ %s ] [ %s ] auto_dac";
|
||||||
|
} else {
|
||||||
|
setup = ".model auto_bidi bidi_bridge("
|
||||||
|
"out_high = %g in_low = '%g/2' in_high = '%g/2')";
|
||||||
|
format = "auto_bidi%d [ %s ] [ %s ] null auto_bidi";
|
||||||
}
|
}
|
||||||
setup = copy(setup);
|
setup = copy(setup);
|
||||||
format = copy(format);
|
format = copy(format);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (!format)
|
if (!format)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,446 @@
|
||||||
|
/* -*- mode: C;-*- (emacs magic)
|
||||||
|
*
|
||||||
|
* Simple bidirectional digital/analog bridge.
|
||||||
|
* Giles Atkinson 2022
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* These define driver cut-off: use PARAMs? */
|
||||||
|
|
||||||
|
#define OFF_LOW 0.7
|
||||||
|
#define OFF_HIGH 0.3
|
||||||
|
|
||||||
|
/* Structure for analogue state: input and output. */
|
||||||
|
|
||||||
|
struct a_data {
|
||||||
|
Digital_t i; // Previous output drive
|
||||||
|
double svoc; // Previous scaled open-circuit estimate
|
||||||
|
double o; // Previous output
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Structure for digital state: input and output. */
|
||||||
|
|
||||||
|
struct d_data {
|
||||||
|
Digital_t i, n, o; // Input, new output, previous output
|
||||||
|
double i_changed; // Time of first input change.
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Called at end to free memory. */
|
||||||
|
|
||||||
|
void cm_bidi_bridge(ARGS)
|
||||||
|
{
|
||||||
|
Digital_State_t atod;
|
||||||
|
struct d_data *d;
|
||||||
|
struct a_data *a, *olda;
|
||||||
|
int i, size, dsize, strength, dir_param;
|
||||||
|
double delta, rise_delay, fall_delay, t_rise, t_fall;
|
||||||
|
|
||||||
|
|
||||||
|
size = PORT_SIZE(a);
|
||||||
|
dsize = PORT_NULL(dir) ? 0 : PORT_SIZE(dir);
|
||||||
|
|
||||||
|
if (INIT) {
|
||||||
|
/* Allocate storage for previous values on both sides */
|
||||||
|
|
||||||
|
cm_analog_alloc(0, size * (int) sizeof (struct a_data));
|
||||||
|
a = (struct a_data *)cm_analog_get_ptr(0, 1);
|
||||||
|
cm_event_alloc(0, size * (int) sizeof (struct d_data));
|
||||||
|
d = (struct d_data *)cm_event_get_ptr(0, 0);
|
||||||
|
|
||||||
|
for (i = 0; i < size; ++i) {
|
||||||
|
LOAD(d[i]) = PARAM(input_load);
|
||||||
|
a[i].o = 0.0;
|
||||||
|
a[i].i.state = UNKNOWN;
|
||||||
|
a[i].i.strength = HI_IMPEDANCE;
|
||||||
|
|
||||||
|
/* Force port initialisation on first event call. */
|
||||||
|
|
||||||
|
d[i].i.state = UNKNOWN;
|
||||||
|
d[i].i.strength = UNDETERMINED;
|
||||||
|
d[i].o.state = (Digital_State_t)(UNKNOWN + 1); // Force update
|
||||||
|
d[i].n.state = UNKNOWN;
|
||||||
|
d[i].i_changed = -1.0;
|
||||||
|
OUTPUT_STATE(d[i]) = UNKNOWN;
|
||||||
|
OUTPUT_STRENGTH(d[i]) = d[i].o.strength = HI_IMPEDANCE;
|
||||||
|
}
|
||||||
|
for (i = 0; i < dsize; ++i)
|
||||||
|
LOAD(dir[i]) = PARAM(input_load);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dsize > size)
|
||||||
|
dsize = size;
|
||||||
|
strength = PARAM(strength);
|
||||||
|
dir_param = PARAM(direction);
|
||||||
|
t_rise = PARAM(t_rise);
|
||||||
|
t_fall = PARAM(t_fall);
|
||||||
|
d = (struct d_data *)cm_event_get_ptr(0,0);
|
||||||
|
|
||||||
|
if (CALL_TYPE == ANALOG) {
|
||||||
|
double in_high, in_low;
|
||||||
|
double out_high, out_low, drive_high, drive_low, r_high, r_low;
|
||||||
|
double r_stl, r_sth;
|
||||||
|
int smooth; // Smoothing level 0-2 (default 0).
|
||||||
|
|
||||||
|
a = (struct a_data *)cm_analog_get_ptr(0,0);
|
||||||
|
olda = (struct a_data *)cm_analog_get_ptr(0, 1);
|
||||||
|
in_high = PARAM(in_high);
|
||||||
|
in_low = PARAM(in_low);
|
||||||
|
out_high = PARAM(out_high);
|
||||||
|
out_low = PARAM(out_low);
|
||||||
|
drive_low = PARAM(drive_low);
|
||||||
|
r_stl = PARAM(r_stl);
|
||||||
|
drive_high = PARAM(drive_high);
|
||||||
|
r_sth = PARAM(r_sth);
|
||||||
|
r_low = PARAM(r_low);
|
||||||
|
r_high = PARAM(r_high);
|
||||||
|
smooth = PARAM(smooth);
|
||||||
|
|
||||||
|
for (i = 0; i < size; ++i) {
|
||||||
|
double svoc, voc; // Notional open-circuit voltage scaled/actual.
|
||||||
|
double in, out;
|
||||||
|
|
||||||
|
/* Determine direction. */
|
||||||
|
|
||||||
|
in = INPUT(a[i]);
|
||||||
|
atod = (Digital_State_t)dir_param;
|
||||||
|
if (atod == UNKNOWN)
|
||||||
|
atod = (i < dsize) ? INPUT_STATE(dir[i]) : UNKNOWN;
|
||||||
|
|
||||||
|
/* Analog to digital. */
|
||||||
|
|
||||||
|
if (atod != ZERO) {
|
||||||
|
Digital_State_t new;
|
||||||
|
|
||||||
|
/* Determine digital output state. */
|
||||||
|
|
||||||
|
if (in_high < in_low) { // Hysteresis
|
||||||
|
if (in > in_low)
|
||||||
|
new = ONE;
|
||||||
|
else if (in < in_high)
|
||||||
|
new = ZERO;
|
||||||
|
else
|
||||||
|
new = d[i].o.state; // Same as before
|
||||||
|
} else {
|
||||||
|
if (in < in_low)
|
||||||
|
new = ZERO;
|
||||||
|
else if (in > in_high)
|
||||||
|
new = ONE;
|
||||||
|
else
|
||||||
|
new = UNKNOWN;
|
||||||
|
}
|
||||||
|
if (new != d[i].n.state) {
|
||||||
|
/* Output change, schedule EVENT call. */
|
||||||
|
|
||||||
|
d[i].n.state = new;
|
||||||
|
cm_event_queue(TIME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Digital to analog. */
|
||||||
|
|
||||||
|
if (atod == ONE) {
|
||||||
|
out = 0.0; /* AtoD, no analogue output. */
|
||||||
|
svoc = 0.5;
|
||||||
|
} else {
|
||||||
|
double target, iota, interval[2], range, partial;
|
||||||
|
int step, step_count;
|
||||||
|
Digital_t drive, *dp;
|
||||||
|
|
||||||
|
/* Find analogue output current. */
|
||||||
|
|
||||||
|
if (atod == UNKNOWN) {
|
||||||
|
/* What is the external drive on the digital node? */
|
||||||
|
|
||||||
|
drive.state = UNKNOWN; drive.strength = HI_IMPEDANCE;
|
||||||
|
if (!cm_probe_node(1, (unsigned int)i, &drive))
|
||||||
|
cm_message_printf("Can not probe port d[%d].", i);
|
||||||
|
} else {
|
||||||
|
drive = *(Digital_t *)INPUT(d[i]);
|
||||||
|
}
|
||||||
|
a[i].i = drive;
|
||||||
|
|
||||||
|
/* Has the input changed during this timestep? */
|
||||||
|
|
||||||
|
iota = (T(0) - T(1)) * 1e-6; // Ignorable
|
||||||
|
if (T(0) - d[i].i_changed < iota) {
|
||||||
|
/* Previous input value in force for whole step. */
|
||||||
|
|
||||||
|
step_count = 1;
|
||||||
|
step = 0;
|
||||||
|
interval[0] = T(0) - T(1);
|
||||||
|
} else if (d[i].i_changed - T(1) < iota) {
|
||||||
|
/* New input value in force for whole step.
|
||||||
|
* Includes common no-change case where new == old.
|
||||||
|
*/
|
||||||
|
|
||||||
|
step_count = 2;
|
||||||
|
step = 1;
|
||||||
|
interval[1] = T(0) - T(1);
|
||||||
|
} else {
|
||||||
|
/* Calculate both sides of change. */
|
||||||
|
|
||||||
|
step_count = 2;
|
||||||
|
step = 0;
|
||||||
|
interval[0] = d[i].i_changed - T(1);
|
||||||
|
interval[1] = T(0) - d[i].i_changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
out = olda[i].o;
|
||||||
|
svoc = olda[i].svoc;
|
||||||
|
for (; step < step_count; ++step) {
|
||||||
|
double change, tv;
|
||||||
|
double max_high, max_low;
|
||||||
|
|
||||||
|
if (step == 0) {
|
||||||
|
dp = &olda[i].i;
|
||||||
|
} else {
|
||||||
|
dp = &drive;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Calculate new value for open-circuit output voltage. */
|
||||||
|
|
||||||
|
change = T(0) - T(1);
|
||||||
|
switch (dp->state) {
|
||||||
|
case ZERO:
|
||||||
|
svoc -= change / t_fall;
|
||||||
|
if (svoc < 0.0)
|
||||||
|
svoc = 0.0;
|
||||||
|
break;
|
||||||
|
case ONE:
|
||||||
|
svoc += change / t_rise;
|
||||||
|
if (svoc > 1.0)
|
||||||
|
svoc = 1.0;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (dp->strength == HI_IMPEDANCE) {
|
||||||
|
svoc = 0.5; // Keeps compiler happy
|
||||||
|
} else {
|
||||||
|
/* Assume both drivers are on. */
|
||||||
|
|
||||||
|
if (dp->strength == STRONG) {
|
||||||
|
if (drive_high > drive_low)
|
||||||
|
tv = 1.0;
|
||||||
|
else if (drive_high > drive_low)
|
||||||
|
tv = 0.0;
|
||||||
|
else
|
||||||
|
tv = 0.5;
|
||||||
|
} else {
|
||||||
|
tv = r_low / (r_low + r_high);
|
||||||
|
}
|
||||||
|
if (svoc < tv) {
|
||||||
|
svoc += change / t_rise;
|
||||||
|
if (svoc > tv)
|
||||||
|
svoc = tv;
|
||||||
|
} else {
|
||||||
|
svoc -= change / t_fall;
|
||||||
|
if (svoc < tv)
|
||||||
|
svoc = tv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (smooth > 0) {
|
||||||
|
cm_smooth_discontinuity(svoc, 0.0, 0.0, 1.0, 1.0,
|
||||||
|
&voc, &tv); // tv is dummy.
|
||||||
|
} else {
|
||||||
|
voc = svoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Available current depends on svoc (driver cut-off). */
|
||||||
|
|
||||||
|
max_high = drive_high;
|
||||||
|
max_high *= (voc - OFF_HIGH) / (1 - OFF_HIGH);
|
||||||
|
if (max_high < 0.0)
|
||||||
|
max_high = 0.0;
|
||||||
|
if (smooth > 1) {
|
||||||
|
cm_smooth_discontinuity(max_high, 0.0, 0.0,
|
||||||
|
drive_high, drive_high,
|
||||||
|
&max_high, &tv);
|
||||||
|
}
|
||||||
|
max_low = drive_low * (OFF_LOW - voc) / OFF_LOW;
|
||||||
|
if (max_low < 0.0)
|
||||||
|
max_low = 0.0;
|
||||||
|
if (smooth > 1) {
|
||||||
|
cm_smooth_discontinuity(max_low, 0.0, 0.0,
|
||||||
|
drive_low, drive_low,
|
||||||
|
&max_low, &tv);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Convert to voltage. */
|
||||||
|
|
||||||
|
voc = out_low + (out_high - out_low) * voc;
|
||||||
|
target = 0.0;
|
||||||
|
partial = 0.0;
|
||||||
|
|
||||||
|
/* Calculate new value for output current. */
|
||||||
|
|
||||||
|
switch (dp->strength) {
|
||||||
|
case STRONG:
|
||||||
|
range = drive_high + drive_low;
|
||||||
|
switch (dp->state) {
|
||||||
|
case ZERO:
|
||||||
|
target = (in - voc) / r_stl;
|
||||||
|
partial = 1.0 / r_stl;
|
||||||
|
break;
|
||||||
|
case ONE:
|
||||||
|
target = (in - voc) / r_sth;
|
||||||
|
partial = 1.0 / r_sth;
|
||||||
|
break;
|
||||||
|
case UNKNOWN:
|
||||||
|
/* Assume both drivers are on. */
|
||||||
|
|
||||||
|
target = (in - voc) *
|
||||||
|
((r_stl + r_sth) / (r_stl * r_sth));
|
||||||
|
partial = (r_stl + r_sth) / (r_stl * r_sth);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (target > max_low) {
|
||||||
|
target = max_low;
|
||||||
|
partial = 0.0;
|
||||||
|
} else if (target < -max_high) {
|
||||||
|
target = -max_high;
|
||||||
|
partial = 0.0;
|
||||||
|
}
|
||||||
|
if (smooth > 2) {
|
||||||
|
cm_smooth_discontinuity(target,
|
||||||
|
-max_high, -max_high,
|
||||||
|
max_low, max_low,
|
||||||
|
&target, &tv);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RESISTIVE:
|
||||||
|
case UNDETERMINED: // Who knows?
|
||||||
|
range = out_high / r_high + out_low / r_low;
|
||||||
|
switch (dp->state) {
|
||||||
|
case ZERO:
|
||||||
|
if (in < voc) {
|
||||||
|
target = 0.0;
|
||||||
|
} else {
|
||||||
|
target = (in - voc) / r_low;
|
||||||
|
partial = 1.0 / r_low;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ONE:
|
||||||
|
if (in > voc) {
|
||||||
|
target = 0.0;
|
||||||
|
} else {
|
||||||
|
target = (in - voc) / r_high;
|
||||||
|
partial = 1.0 / r_high;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case UNKNOWN:
|
||||||
|
if (in < out_low) {
|
||||||
|
target = (in - out_high) / r_high;
|
||||||
|
partial = 1.0 / r_high;
|
||||||
|
} else if (in >= out_high) {
|
||||||
|
target = (in - out_low) / r_low;
|
||||||
|
partial = 1.0 / r_low;
|
||||||
|
} else {
|
||||||
|
/* Both drivers on. */
|
||||||
|
|
||||||
|
partial = 1.0 / r_low + 1.0 / r_high;
|
||||||
|
target = (in - voc ) * partial;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case HI_IMPEDANCE:
|
||||||
|
default:
|
||||||
|
range = fmax(drive_high, drive_low);
|
||||||
|
target = 0.0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Can the transition complete in available time? */
|
||||||
|
|
||||||
|
delta = target - out; // Current
|
||||||
|
delta *=
|
||||||
|
((delta > 0) ? t_rise : t_fall) / range; // Time
|
||||||
|
if (delta < 0)
|
||||||
|
delta = -delta;
|
||||||
|
if (delta > interval[step]) {
|
||||||
|
out += (target - out) * interval[step] / delta;
|
||||||
|
} else {
|
||||||
|
/* Transition complete. */
|
||||||
|
|
||||||
|
out = target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (partial != 0.0)
|
||||||
|
PARTIAL(a[i], a[i]) = partial;
|
||||||
|
}
|
||||||
|
OUTPUT(a[i]) = a[i].o = out;
|
||||||
|
a[i].svoc = svoc;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Digital. */
|
||||||
|
|
||||||
|
rise_delay = PARAM(rise_delay);
|
||||||
|
fall_delay = PARAM(fall_delay);
|
||||||
|
|
||||||
|
for (i = 0; i < size; ++i) {
|
||||||
|
/* Determine direction. */
|
||||||
|
|
||||||
|
atod = (Digital_State_t)dir_param;
|
||||||
|
if (atod == UNKNOWN)
|
||||||
|
atod = (i < dsize) ? INPUT_STATE(dir[i]) : UNKNOWN;
|
||||||
|
|
||||||
|
if (atod != ONE && (INPUT_STATE(d[i]) != d[i].i.state ||
|
||||||
|
INPUT_STRENGTH(d[i]) != d[i].i.strength)) {
|
||||||
|
double transition;
|
||||||
|
|
||||||
|
/* Digital input changed, request break. */
|
||||||
|
|
||||||
|
d[i].i = *(Digital_t *)INPUT(d[i]);
|
||||||
|
cm_analog_set_temp_bkpt(TIME);
|
||||||
|
d[i].i_changed = TIME;
|
||||||
|
|
||||||
|
/* Do not let big timesteps smear the transition (it could be
|
||||||
|
* much faster with current output into high impedance.
|
||||||
|
*/
|
||||||
|
|
||||||
|
transition = (d[i].i.state == ZERO) ? t_fall :
|
||||||
|
(d[i].i.state == ONE) ? t_rise : fmin(t_rise, t_fall);
|
||||||
|
cm_analog_set_perm_bkpt(TIME + transition * 1.0001);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check for output change from analogue. */
|
||||||
|
|
||||||
|
if (atod == ZERO) {
|
||||||
|
if (d[i].o.strength != HI_IMPEDANCE) {
|
||||||
|
OUTPUT_STATE(d[i]) = d[i].o.state = UNKNOWN;
|
||||||
|
OUTPUT_STRENGTH(d[i]) = d[i].o.strength = HI_IMPEDANCE;
|
||||||
|
OUTPUT_DELAY(d[i]) = fmax(rise_delay, fall_delay);
|
||||||
|
} else {
|
||||||
|
OUTPUT_CHANGED(d[i]) = FALSE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (d[i].o.state != d[i].n.state) {
|
||||||
|
double delay;
|
||||||
|
|
||||||
|
OUTPUT_STATE(d[i]) = d[i].o.state = d[i].n.state;
|
||||||
|
OUTPUT_STRENGTH(d[i]) = d[i].o.strength = strength;
|
||||||
|
switch (d[i].o.state) {
|
||||||
|
case ZERO:
|
||||||
|
delay = fall_delay;
|
||||||
|
break;
|
||||||
|
case ONE:
|
||||||
|
delay = rise_delay;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
case UNKNOWN:
|
||||||
|
delay = fmin(rise_delay, fall_delay);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
OUTPUT_DELAY(d[i]) = delay;
|
||||||
|
} else {
|
||||||
|
OUTPUT_CHANGED(d[i]) = FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,196 @@
|
||||||
|
/*
|
||||||
|
AUTHORS
|
||||||
|
|
||||||
|
24 Apr 2022 Giles Atkinson
|
||||||
|
|
||||||
|
SUMMARY
|
||||||
|
|
||||||
|
This file contains the interface specification file for the
|
||||||
|
hybrid bidi_bridge code model, a (potentially) bidirectional
|
||||||
|
analogue/digital bridge. The device has direction inputs
|
||||||
|
but if they are not driven it tries to behave like a piece of wire.
|
||||||
|
A parameter may override any direction inputs.
|
||||||
|
The analogue ports are type "g" as that is bidirectional.
|
||||||
|
*/
|
||||||
|
|
||||||
|
NAME_TABLE:
|
||||||
|
|
||||||
|
|
||||||
|
C_Function_Name: cm_bidi_bridge
|
||||||
|
Spice_Model_Name: bidi_bridge
|
||||||
|
Description: "bidirectional digital/analog node bridge"
|
||||||
|
|
||||||
|
|
||||||
|
PORT_TABLE:
|
||||||
|
|
||||||
|
Port_Name: a d
|
||||||
|
Description: "analog" "digital in/out"
|
||||||
|
Direction: inout inout
|
||||||
|
Default_Type: g d
|
||||||
|
Allowed_Types: [g, gd] [d]
|
||||||
|
Vector: yes yes
|
||||||
|
Vector_Bounds: [1 -] [1 -]
|
||||||
|
Null_Allowed: no no
|
||||||
|
|
||||||
|
/* The direction of the bridge ports may be controlled by digital inputs.
|
||||||
|
* If null, or the value is UNKNOWN the bridge will be truly bi-directional.
|
||||||
|
*/
|
||||||
|
|
||||||
|
PORT_TABLE:
|
||||||
|
|
||||||
|
Port_Name: dir
|
||||||
|
Description: "direction"
|
||||||
|
Direction: in
|
||||||
|
Default_Type: d
|
||||||
|
Allowed_Types: [d]
|
||||||
|
Vector: yes
|
||||||
|
Vector_Bounds: -
|
||||||
|
Null_Allowed: yes
|
||||||
|
|
||||||
|
/* Alternatively, this parameter sets direction: 0-2 for DAC, ADC, ignore.
|
||||||
|
* Values 0/1 override the direction port.
|
||||||
|
*/
|
||||||
|
|
||||||
|
PARAMETER_TABLE:
|
||||||
|
|
||||||
|
Parameter_Name: direction input_load
|
||||||
|
Description: "force direction" "capacitive input load (F)"
|
||||||
|
Data_Type: int real
|
||||||
|
Default_Value: 2 1.0e-12
|
||||||
|
Limits: [0 2] -
|
||||||
|
Vector: no no
|
||||||
|
Vector_Bounds: - -
|
||||||
|
Null_Allowed: yes yes
|
||||||
|
|
||||||
|
/* Output strength is 0 (strong, default) or 1 (resistive).
|
||||||
|
* Smooth controls use of smoothing functions, default is 0 (no smoothing).
|
||||||
|
*/
|
||||||
|
|
||||||
|
PARAMETER_TABLE:
|
||||||
|
|
||||||
|
Parameter_Name: strength smooth
|
||||||
|
Description: "output strength" "smoothing level"
|
||||||
|
Data_Type: int int
|
||||||
|
Default_Value: 0 0
|
||||||
|
Limits: [0 2] [0 2]
|
||||||
|
Vector: no no
|
||||||
|
Vector_Bounds: - -
|
||||||
|
Null_Allowed: yes yes
|
||||||
|
|
||||||
|
/* Analog thresholds, in_low may be greater than in-high, enabling hysteresis.
|
||||||
|
*/
|
||||||
|
|
||||||
|
PARAMETER_TABLE:
|
||||||
|
|
||||||
|
Parameter_Name: in_low
|
||||||
|
Description: "maximum 0-valued analog input"
|
||||||
|
Data_Type: real
|
||||||
|
Default_Value: 0.1
|
||||||
|
Limits: -
|
||||||
|
Vector: no
|
||||||
|
Vector_Bounds: -
|
||||||
|
Null_Allowed: yes
|
||||||
|
|
||||||
|
PARAMETER_TABLE:
|
||||||
|
|
||||||
|
Parameter_Name: in_high
|
||||||
|
Description: "minimum 1-valued analog input"
|
||||||
|
Data_Type: real
|
||||||
|
Default_Value: 0.9
|
||||||
|
Limits: -
|
||||||
|
Vector: no
|
||||||
|
Vector_Bounds: -
|
||||||
|
Null_Allowed: yes
|
||||||
|
|
||||||
|
PARAMETER_TABLE:
|
||||||
|
|
||||||
|
/* Analog maximum and minimum output voltages. */
|
||||||
|
|
||||||
|
Parameter_Name: out_low
|
||||||
|
Description: "minimum analog output voltage for 'ZERO' digital input"
|
||||||
|
Data_Type: real
|
||||||
|
Default_Value: 0.0
|
||||||
|
Limits: -
|
||||||
|
Vector: no
|
||||||
|
Vector_Bounds: -
|
||||||
|
Null_Allowed: yes
|
||||||
|
|
||||||
|
PARAMETER_TABLE:
|
||||||
|
|
||||||
|
Parameter_Name: out_high
|
||||||
|
Description: "maximum analog output voltage for 'ONE' digital input"
|
||||||
|
Data_Type: real
|
||||||
|
Default_Value: 3.3
|
||||||
|
Limits: -
|
||||||
|
Vector: no
|
||||||
|
Vector_Bounds: -
|
||||||
|
Null_Allowed: yes
|
||||||
|
|
||||||
|
/* Analog maximum current. */
|
||||||
|
|
||||||
|
PARAMETER_TABLE:
|
||||||
|
|
||||||
|
Parameter_Name: drive_low drive_high
|
||||||
|
Description: "max current to ground" "max current to ground"
|
||||||
|
Data_Type: real real
|
||||||
|
Default_Value: 0.02 0.02
|
||||||
|
Limits: - -
|
||||||
|
Vector: no no
|
||||||
|
Vector_Bounds: - -
|
||||||
|
Null_Allowed: yes yes
|
||||||
|
|
||||||
|
/* Strong analog output cuts off smoothly at the voltage limits.
|
||||||
|
* Let vth = out_high - r_sth * drive_high.
|
||||||
|
* Then for input voltage v, with drive_high > v > vth,
|
||||||
|
* the maximum output current is (drive_high - v) / r_sth
|
||||||
|
*/
|
||||||
|
|
||||||
|
PARAMETER_TABLE:
|
||||||
|
|
||||||
|
Parameter_Name: r_stl r_sth
|
||||||
|
Description: "low taper resistance" "high taper resistance"
|
||||||
|
Data_Type: real real
|
||||||
|
Default_Value: 20 20
|
||||||
|
Limits: [1e-6 -] [1e-6 -]
|
||||||
|
Vector: no no
|
||||||
|
Vector_Bounds: - -
|
||||||
|
Null_Allowed: yes yes
|
||||||
|
|
||||||
|
/* Resistive analog drive. */
|
||||||
|
|
||||||
|
PARAMETER_TABLE:
|
||||||
|
|
||||||
|
Parameter_Name: r_low r_high
|
||||||
|
Description: "drive resistor to ground" "drive resistor to out_high"
|
||||||
|
Data_Type: real real
|
||||||
|
Default_Value: 10000 10000
|
||||||
|
Limits: [1e-6 -] [1e-6 -]
|
||||||
|
Vector: no no
|
||||||
|
Vector_Bounds: - -
|
||||||
|
Null_Allowed: yes yes
|
||||||
|
|
||||||
|
/* Analog rise and fall times. */
|
||||||
|
|
||||||
|
PARAMETER_TABLE:
|
||||||
|
|
||||||
|
Parameter_Name: t_rise t_fall
|
||||||
|
Description: "rise time 0 -> 1" "fall time 1 -> 0"
|
||||||
|
Data_Type: real real
|
||||||
|
Default_Value: 1.0e-9 1.0e-9
|
||||||
|
Limits: [1e-12 -] [1e-12 -]
|
||||||
|
Vector: no no
|
||||||
|
Vector_Bounds: - -
|
||||||
|
Null_Allowed: yes yes
|
||||||
|
|
||||||
|
/* Digital rise and fall delays. */
|
||||||
|
|
||||||
|
PARAMETER_TABLE:
|
||||||
|
|
||||||
|
Parameter_Name: rise_delay fall_delay
|
||||||
|
Description: "rise delay 0 -> 1" "fall delay 1 -> 0"
|
||||||
|
Data_Type: real real
|
||||||
|
Default_Value: 1.0e-9 1.0e-9
|
||||||
|
Limits: [1e-12 -] [1e-12 -]
|
||||||
|
Vector: no no
|
||||||
|
Vector_Bounds: - -
|
||||||
|
Null_Allowed: yes yes
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
adc_bridge
|
adc_bridge
|
||||||
dac_bridge
|
dac_bridge
|
||||||
|
bidi_bridge
|
||||||
d_and
|
d_and
|
||||||
d_buffer
|
d_buffer
|
||||||
d_dff
|
d_dff
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue