Add support for including analog node changes in VCD file output,
and an option to explicitly set the VCD timestep. Correct the output value for high-impedance nodes.
This commit is contained in:
parent
68f1015075
commit
84821a4cf5
|
|
@ -259,7 +259,7 @@ struct comm spcp_coms[] = {
|
||||||
{ "eprvcd", EVTprintvcd, FALSE, TRUE,
|
{ "eprvcd", EVTprintvcd, FALSE, TRUE,
|
||||||
{ 040000, 040000, 040000, 040000 }, E_BEGINNING, 1, LOTS,
|
{ 040000, 040000, 040000, 040000 }, E_BEGINNING, 1, LOTS,
|
||||||
arg_enodes,
|
arg_enodes,
|
||||||
"node node ... : Print event values into vcd file." },
|
"[-a] [-t timescale] node node ... : Print event values into VCD file." },
|
||||||
{ "edisplay", EVTdisplay, FALSE, TRUE,
|
{ "edisplay", EVTdisplay, FALSE, TRUE,
|
||||||
{ 040000, 040000, 040000, 040000 }, E_BEGINNING, 0, 0,
|
{ 040000, 040000, 040000, 040000 }, E_BEGINNING, 0, 0,
|
||||||
NULL,
|
NULL,
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,8 @@ NON-STANDARD FEATURES
|
||||||
|
|
||||||
#include "ngspice/evtproto.h"
|
#include "ngspice/evtproto.h"
|
||||||
|
|
||||||
|
#include "ngspice/fteext.h"
|
||||||
|
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <locale.h>
|
#include <locale.h>
|
||||||
|
|
||||||
|
|
@ -466,10 +468,11 @@ get_vcdval(char *xspiceval, char **newval)
|
||||||
"0z", "1z", "Uz",
|
"0z", "1z", "Uz",
|
||||||
"0u", "1u", "Uu"
|
"0u", "1u", "Uu"
|
||||||
};
|
};
|
||||||
|
|
||||||
static char *returnmap[] = {
|
static char *returnmap[] = {
|
||||||
"0", "1", "x",
|
"0", "1", "x",
|
||||||
"0", "1", "x",
|
"0", "1", "x",
|
||||||
"0", "1", "z",
|
"z", "z", "z",
|
||||||
"0", "1", "x"
|
"0", "1", "x"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -482,7 +485,7 @@ get_vcdval(char *xspiceval, char **newval)
|
||||||
/* is it a real number ? */
|
/* is it a real number ? */
|
||||||
retval = INPevaluate(&xspiceval, &err, 1);
|
retval = INPevaluate(&xspiceval, &err, 1);
|
||||||
if (err) {
|
if (err) {
|
||||||
*newval = copy("unknown");
|
*newval = copy(xspiceval); // Assume the node type is coded for this.
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
*newval = tprintf("%.16g", retval);
|
*newval = tprintf("%.16g", retval);
|
||||||
|
|
@ -495,6 +498,73 @@ get_vcdval(char *xspiceval, char **newval)
|
||||||
#define localtime _localtime64
|
#define localtime _localtime64
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* Function to return a real value to be written to a VCD file. */
|
||||||
|
|
||||||
|
struct reals {
|
||||||
|
struct dvec *time; // Scale vector
|
||||||
|
int v_index, last_i;
|
||||||
|
double factor;
|
||||||
|
struct dvec *node_vector[EPRINT_MAXARGS]; // For analog nodes
|
||||||
|
};
|
||||||
|
|
||||||
|
static double get_real(int index, double when, struct reals *ctx)
|
||||||
|
{
|
||||||
|
struct dvec *dv;
|
||||||
|
|
||||||
|
if (index < ctx->last_i) {
|
||||||
|
/* Starting a new pass. */
|
||||||
|
|
||||||
|
if (!ctx->time) {
|
||||||
|
ctx->v_index = 0;
|
||||||
|
ctx->time = vec_get("time");
|
||||||
|
if (!ctx->time) {
|
||||||
|
if (ctx->last_i == EPRINT_MAXARGS) { // First time
|
||||||
|
fprintf(cp_err,
|
||||||
|
"ERROR - No vector 'time' in current plot\n");
|
||||||
|
}
|
||||||
|
ctx->node_vector[index] = NULL; // No more calls
|
||||||
|
return NAN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Advance the vector index. */
|
||||||
|
|
||||||
|
while (ctx->v_index < ctx->time->v_length &&
|
||||||
|
ctx->time->v_realdata[ctx->v_index++] < when) ;
|
||||||
|
ctx->v_index--;
|
||||||
|
|
||||||
|
/* Calculate interpolation factor. */
|
||||||
|
|
||||||
|
if (ctx->v_index + 1 < ctx->time->v_length) {
|
||||||
|
ctx->factor = (when - ctx->time->v_realdata[ctx->v_index]);
|
||||||
|
ctx->factor /= (ctx->time->v_realdata[ctx->v_index + 1] -
|
||||||
|
ctx->time->v_realdata[ctx->v_index]);
|
||||||
|
if (ctx->factor < 0.0 || ctx->factor >= 1.0)
|
||||||
|
ctx->factor = 0.0; // Rounding
|
||||||
|
} else {
|
||||||
|
ctx->factor = 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return interpolated value. */
|
||||||
|
|
||||||
|
ctx->last_i = index;
|
||||||
|
dv = ctx->node_vector[index];
|
||||||
|
if (ctx->v_index < dv->v_length) {
|
||||||
|
if (ctx->factor == 0.0) {
|
||||||
|
return dv->v_realdata[ctx->v_index];
|
||||||
|
} else {
|
||||||
|
return dv->v_realdata[ctx->v_index] +
|
||||||
|
ctx->factor *
|
||||||
|
(dv->v_realdata[ctx->v_index + 1] -
|
||||||
|
dv->v_realdata[ctx->v_index]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx->node_vector[index] = NULL; // No more calls
|
||||||
|
return dv->v_realdata[dv->v_length - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A simple vcd file printer.
|
* A simple vcd file printer.
|
||||||
* command 'eprvcd a0 a1 a2 b0 b1 b2 clk > myvcd.vcd'
|
* command 'eprvcd a0 a1 a2 b0 b1 b2 clk > myvcd.vcd'
|
||||||
|
|
@ -510,16 +580,23 @@ EVTprintvcd(wordlist *wl)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
int nargs;
|
int nargs;
|
||||||
|
int timesteps = 0, tspower = -1;
|
||||||
|
|
||||||
wordlist *w;
|
wordlist *w;
|
||||||
|
|
||||||
|
struct reals ctx;
|
||||||
|
|
||||||
|
double out_time, last_out_time;
|
||||||
|
|
||||||
|
|
||||||
char *node_name[EPRINT_MAXARGS];
|
char *node_name[EPRINT_MAXARGS];
|
||||||
int node_index[EPRINT_MAXARGS];
|
int node_index[EPRINT_MAXARGS];
|
||||||
int udn_index[EPRINT_MAXARGS];
|
int udn_index[EPRINT_MAXARGS];
|
||||||
Evt_Node_t *node_data[EPRINT_MAXARGS];
|
Evt_Node_t *node_data[EPRINT_MAXARGS];
|
||||||
char *node_value[EPRINT_MAXARGS];
|
char *node_value[EPRINT_MAXARGS];
|
||||||
char *old_node_value[EPRINT_MAXARGS];
|
char *old_node_value[EPRINT_MAXARGS];
|
||||||
char node_ident[EPRINT_MAXARGS + 1];
|
char node_ident[EPRINT_MAXARGS + 1];
|
||||||
|
char vbuf[24][2][EPRINT_MAXARGS]; // Analog value strings
|
||||||
|
|
||||||
CKTcircuit *ckt;
|
CKTcircuit *ckt;
|
||||||
|
|
||||||
|
|
@ -527,19 +604,37 @@ EVTprintvcd(wordlist *wl)
|
||||||
|
|
||||||
Mif_Boolean_t more;
|
Mif_Boolean_t more;
|
||||||
|
|
||||||
double step = 0.0;
|
|
||||||
double next_step;
|
double next_step;
|
||||||
double this_step;
|
double this_step;
|
||||||
|
|
||||||
char *value;
|
char *value;
|
||||||
|
|
||||||
|
/* Check for the "-a" option (output analog values at timesteps)
|
||||||
|
* and "-t nn": specifies the VCD timestep as a power of ten.
|
||||||
|
*/
|
||||||
|
|
||||||
|
while (wl && wl->wl_word[0] == '-') {
|
||||||
|
if (wl->wl_word[1] == 'a' && !wl->wl_word[2]) {
|
||||||
|
timesteps = 1;
|
||||||
|
} else if (wl->wl_word[1] == 't' && !wl->wl_word[2]) {
|
||||||
|
wl = wl->wl_next;
|
||||||
|
if (wl)
|
||||||
|
tspower = atoi(wl->wl_word);
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
wl = wl->wl_next;
|
||||||
|
}
|
||||||
|
|
||||||
/* Count the number of arguments to the command */
|
/* Count the number of arguments to the command */
|
||||||
nargs = 0;
|
nargs = 0;
|
||||||
for (w = wl; w; w = w->wl_next)
|
for (w = wl; w; w = w->wl_next)
|
||||||
nargs++;
|
nargs++;
|
||||||
|
|
||||||
if (nargs < 1) {
|
if (nargs < 1) {
|
||||||
printf("Usage: eprvcd <node1> <node2> ...\n");
|
printf("Usage: eprvcd [-a] <node1> <node2> ...\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (nargs > EPRINT_MAXARGS) {
|
if (nargs > EPRINT_MAXARGS) {
|
||||||
|
|
@ -560,17 +655,41 @@ EVTprintvcd(wordlist *wl)
|
||||||
node_table = ckt->evt->info.node_table;
|
node_table = ckt->evt->info.node_table;
|
||||||
|
|
||||||
/* Get data for each argument */
|
/* Get data for each argument */
|
||||||
|
|
||||||
w = wl;
|
w = wl;
|
||||||
for (i = 0; i < nargs; i++) {
|
for (i = 0; i < nargs; i++) {
|
||||||
node_name[i] = w->wl_word;
|
node_name[i] = w->wl_word;
|
||||||
node_index[i] = get_index(node_name[i]);
|
node_index[i] = get_index(node_name[i]);
|
||||||
if (node_index[i] < 0) {
|
|
||||||
fprintf(cp_err, "ERROR - Node %s is not an event node.\n", node_name[i]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
udn_index[i] = node_table[node_index[i]]->udn_index;
|
|
||||||
|
|
||||||
node_data[i] = ckt->evt->data.node->head[node_index[i]];
|
if (node_index[i] >= 0) {
|
||||||
|
udn_index[i] = node_table[node_index[i]]->udn_index;
|
||||||
|
node_data[i] = ckt->evt->data.node->head[node_index[i]];
|
||||||
|
ctx.node_vector[i] = NULL;
|
||||||
|
} else {
|
||||||
|
struct pnode *pn;
|
||||||
|
struct dvec *dv;
|
||||||
|
wordlist *save;
|
||||||
|
|
||||||
|
/* Is it an analog parameter/node expression?
|
||||||
|
* The whole expression must be a single word (no spaces).
|
||||||
|
*/
|
||||||
|
|
||||||
|
save = w->wl_next;
|
||||||
|
w->wl_next = NULL;
|
||||||
|
pn = ft_getpnames_quotes(w, TRUE);
|
||||||
|
w->wl_next = save;
|
||||||
|
if (pn) {
|
||||||
|
dv = ft_evaluate(pn);
|
||||||
|
free_pnode(pn);
|
||||||
|
} else {
|
||||||
|
dv = NULL;
|
||||||
|
}
|
||||||
|
if (!dv) {
|
||||||
|
fprintf(cp_err, "ERROR - Node %s not parsed.\n", node_name[i]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.node_vector[i] = dv;
|
||||||
|
}
|
||||||
node_value[i] = "";
|
node_value[i] = "";
|
||||||
w = w->wl_next;
|
w = w->wl_next;
|
||||||
}
|
}
|
||||||
|
|
@ -606,43 +725,95 @@ EVTprintvcd(wordlist *wl)
|
||||||
|
|
||||||
/* get the sim time resolution based on tstep */
|
/* get the sim time resolution based on tstep */
|
||||||
char *unit;
|
char *unit;
|
||||||
double scale;
|
double scale, tick;
|
||||||
double tstep = ckt->CKTstep;
|
|
||||||
|
|
||||||
/* if selected time step is down to [ms] then report time at [us] etc., always with one level higher resolution */
|
if (tspower >= 0) {
|
||||||
if (tstep >= 1e-3) {
|
/* VCD timestep set by "-t" option. */
|
||||||
unit = "us";
|
|
||||||
scale = 1e6;
|
if (tspower == 0) {
|
||||||
|
unit = "s";
|
||||||
|
scale = 1.0;
|
||||||
|
} else if (tspower < 4) {
|
||||||
|
unit = "ms";
|
||||||
|
tspower = 3 - tspower;
|
||||||
|
scale = 1e3 * exp10((double)-tspower);
|
||||||
|
} else if (tspower < 7) {
|
||||||
|
unit = "us";
|
||||||
|
tspower = 6 - tspower;
|
||||||
|
scale = 1e6 * exp10((double)-tspower);
|
||||||
|
} else if (tspower < 10) {
|
||||||
|
unit = "ns";
|
||||||
|
tspower = 9 - tspower;
|
||||||
|
scale = 1e9 * exp10((double)-tspower);
|
||||||
|
} else if (tspower < 13) {
|
||||||
|
unit = "ps";
|
||||||
|
tspower = 12 - tspower;
|
||||||
|
scale = 1e12 * exp10((double)-tspower);
|
||||||
|
} else if (tspower < 16) {
|
||||||
|
unit = "fs";
|
||||||
|
tspower = 15 - tspower;
|
||||||
|
scale = 1e15 * exp10((double)-tspower);
|
||||||
|
} else { // 1 fS is the bottom.
|
||||||
|
unit = "fs";
|
||||||
|
tspower = 0;
|
||||||
|
scale = 1e15;
|
||||||
|
}
|
||||||
|
out_printf("$timescale %g %s $end\n", exp10((double)tspower), unit);
|
||||||
|
} else {
|
||||||
|
double tstep = ckt->CKTstep;
|
||||||
|
|
||||||
|
/* Use the simulation time step. If the selected time step
|
||||||
|
* is down to [ms] then report time at [us] etc.,
|
||||||
|
* always with one level higher resolution.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (tstep >= 1e-3) {
|
||||||
|
unit = "us";
|
||||||
|
scale = 1e6;
|
||||||
|
}
|
||||||
|
else if (tstep >= 1e-6) {
|
||||||
|
unit = "ns";
|
||||||
|
scale = 1e9;
|
||||||
|
}
|
||||||
|
else if (tstep >= 1e-9) {
|
||||||
|
unit = "ps";
|
||||||
|
scale = 1e12;
|
||||||
|
} else {
|
||||||
|
unit = "fs";
|
||||||
|
scale = 1e15;
|
||||||
|
}
|
||||||
|
out_printf("$timescale 1 %s $end\n", unit);
|
||||||
}
|
}
|
||||||
else if (tstep >= 1e-6) {
|
tick = 1.0 / scale;
|
||||||
unit = "ns";
|
|
||||||
scale = 1e9;
|
|
||||||
}
|
|
||||||
else if (tstep >= 1e-9) {
|
|
||||||
unit = "ps";
|
|
||||||
scale = 1e12;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
unit = "fs";
|
|
||||||
scale = 1e15;
|
|
||||||
}
|
|
||||||
out_printf("$timescale 1 %s $end\n", unit);
|
|
||||||
|
|
||||||
/* Scan the node data. Go for printing using $dumpvars
|
/* Scan the node data. Go for printing using $dumpvars
|
||||||
for the initial values. Also, determine if there is
|
for the initial values. Also, determine if there is
|
||||||
more data following it and if so, what the next step is. */
|
more data following it and if so, what the next step is. */
|
||||||
|
|
||||||
|
ctx.time = NULL;
|
||||||
|
ctx.last_i = EPRINT_MAXARGS; // Indicate restart
|
||||||
more = MIF_FALSE;
|
more = MIF_FALSE;
|
||||||
next_step = 1e30;
|
next_step = 1e30;
|
||||||
for (i = 0; i < nargs; i++) {
|
for (i = 0; i < nargs; i++) {
|
||||||
step = node_data[i]->step;
|
if (ctx.node_vector[i]) {
|
||||||
g_evt_udn_info[udn_index[i]]->print_val
|
/* Analog node or expression. */
|
||||||
(node_data[i]->node_value, "all", &value);
|
|
||||||
old_node_value[i] = node_value[i] = value;
|
sprintf(vbuf[0][i], "%.16g", get_real(i, 0.0, &ctx));
|
||||||
node_data[i] = node_data[i]->next;
|
node_value[i] = vbuf[0][i];
|
||||||
if (node_data[i]) {
|
old_node_value[i] = vbuf[1][i];
|
||||||
more = MIF_TRUE;
|
strcpy(vbuf[1][i], vbuf[0][i]);
|
||||||
if (next_step > node_data[i]->step)
|
} else {
|
||||||
next_step = node_data[i]->step;
|
/* This must return a pointer to a statically-allocated string. */
|
||||||
|
|
||||||
|
g_evt_udn_info[udn_index[i]]->print_val
|
||||||
|
(node_data[i]->node_value, "all", &value);
|
||||||
|
node_data[i] = node_data[i]->next;
|
||||||
|
old_node_value[i] = node_value[i] = value;
|
||||||
|
if (node_data[i]) {
|
||||||
|
more = MIF_TRUE;
|
||||||
|
if (next_step > node_data[i]->step)
|
||||||
|
next_step = node_data[i]->step;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -658,7 +829,6 @@ EVTprintvcd(wordlist *wl)
|
||||||
}
|
}
|
||||||
|
|
||||||
out_printf("$enddefinitions $end\n");
|
out_printf("$enddefinitions $end\n");
|
||||||
out_printf("#%lld\n", (unsigned long long)(step * scale));
|
|
||||||
|
|
||||||
/* first set of data for initialization
|
/* first set of data for initialization
|
||||||
or if only op has been calculated */
|
or if only op has been calculated */
|
||||||
|
|
@ -676,38 +846,92 @@ EVTprintvcd(wordlist *wl)
|
||||||
out_printf("$end\n");
|
out_printf("$end\n");
|
||||||
|
|
||||||
/* While there is more data, get the next values and print */
|
/* While there is more data, get the next values and print */
|
||||||
while (more) {
|
|
||||||
|
|
||||||
more = MIF_FALSE;
|
last_out_time = 0.0;
|
||||||
|
while (more ||
|
||||||
|
(timesteps && ctx.time && ctx.v_index + 1 < ctx.time->v_length)) {
|
||||||
|
int got_one;
|
||||||
|
|
||||||
this_step = next_step;
|
this_step = next_step;
|
||||||
next_step = 1e30;
|
|
||||||
|
|
||||||
for (i = 0; i < nargs; i++)
|
if (timesteps && ctx.time && ctx.v_index + 1 < ctx.time->v_length &&
|
||||||
if (node_data[i]) {
|
(ctx.time->v_realdata[ctx.v_index + 1] < this_step ||
|
||||||
if (node_data[i]->step == this_step) {
|
(timesteps && !more))) {
|
||||||
g_evt_udn_info[udn_index[i]]->print_val
|
|
||||||
(node_data[i]->node_value, "all", &value);
|
/* Analogue output at each time step, skipping if they would
|
||||||
node_value[i] = value;
|
* appear simulataneous in the output.
|
||||||
node_data[i] = node_data[i]->next;
|
*/
|
||||||
}
|
|
||||||
if (node_data[i]) {
|
out_time = ctx.time->v_realdata[ctx.v_index + 1];
|
||||||
more = MIF_TRUE;
|
if (out_time - last_out_time < tick) {
|
||||||
if (next_step > node_data[i]->step)
|
++ctx.v_index;
|
||||||
next_step = node_data[i]->step;
|
continue;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* timestamp */
|
for (i = 0; i < nargs; i++) {
|
||||||
out_printf("#%lld\n", (unsigned long long)(this_step * scale));
|
if (ctx.node_vector[i])
|
||||||
|
sprintf(node_value[i], "%.16g",
|
||||||
|
get_real(i, out_time, &ctx));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Process next event. */
|
||||||
|
|
||||||
|
out_time = this_step;
|
||||||
|
more = MIF_FALSE;
|
||||||
|
next_step = 1e30;
|
||||||
|
for (i = 0; i < nargs; i++) {
|
||||||
|
if (ctx.node_vector[i]) {
|
||||||
|
/* Analog node or expression. */
|
||||||
|
|
||||||
|
sprintf(node_value[i], "%.16g",
|
||||||
|
get_real(i, this_step, &ctx));
|
||||||
|
} else if (node_data[i]) {
|
||||||
|
if (node_data[i]->step == this_step) {
|
||||||
|
g_evt_udn_info[udn_index[i]]->print_val
|
||||||
|
(node_data[i]->node_value, "all", &value);
|
||||||
|
node_value[i] = value;
|
||||||
|
node_data[i] = node_data[i]->next;
|
||||||
|
}
|
||||||
|
if (node_data[i]) {
|
||||||
|
more = MIF_TRUE;
|
||||||
|
if (next_step > node_data[i]->step)
|
||||||
|
next_step = node_data[i]->step;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* print only values that have changed */
|
/* print only values that have changed */
|
||||||
for (i = 0; i < nargs; i++) {
|
|
||||||
|
for (i = 0, got_one = 0; i < nargs; i++) {
|
||||||
if (!eq(old_node_value[i], node_value[i])) {
|
if (!eq(old_node_value[i], node_value[i])) {
|
||||||
char *buf;
|
char *buf;
|
||||||
|
|
||||||
|
if (!got_one) {
|
||||||
|
/* timestamp */
|
||||||
|
|
||||||
|
out_printf("#%lld\n",
|
||||||
|
(unsigned long long)(out_time * scale));
|
||||||
|
last_out_time = out_time;;
|
||||||
|
got_one = 1;
|
||||||
|
}
|
||||||
|
|
||||||
if (get_vcdval(node_value[i], &buf) == 1)
|
if (get_vcdval(node_value[i], &buf) == 1)
|
||||||
out_printf("r%s %c\n", buf, node_ident[i]);
|
out_printf("r%s %c\n", buf, node_ident[i]);
|
||||||
else
|
else
|
||||||
out_printf("%s%c\n", buf, node_ident[i]);
|
out_printf("%s%c\n", buf, node_ident[i]);
|
||||||
old_node_value[i] = node_value[i];
|
|
||||||
|
if (ctx.node_vector[i]) {
|
||||||
|
char *t;
|
||||||
|
|
||||||
|
/* Swap buffers. */
|
||||||
|
|
||||||
|
t = old_node_value[i];
|
||||||
|
old_node_value[i] = node_value[i];
|
||||||
|
node_value[i] = t;
|
||||||
|
} else {;
|
||||||
|
old_node_value[i] = node_value[i];
|
||||||
|
}
|
||||||
tfree(buf);
|
tfree(buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue