From d31568bd83678df6663ca4345828a49e40987a47 Mon Sep 17 00:00:00 2001 From: Giles Atkinson <“gatk555@gmail.com”> Date: Tue, 23 May 2023 07:48:54 +0100 Subject: [PATCH] Add parsing and translation of the FREQ form of E-source devices, integrated with the existing parsing of AND/NAND/OR/NOR forms (inpcom.c). For the implementation, add a new analog XSPICE code model, xfer. Add an example to examples/sp. --- examples/sp/filter.lib | 185 ++++++++++ examples/sp/filter.sp | 91 +++++ src/frontend/inpcom.c | 470 ++++++++++++++++++-------- src/xspice/icm/analog/modpath.lst | 1 + src/xspice/icm/analog/xfer/cfunc.mod | 130 +++++++ src/xspice/icm/analog/xfer/ifspec.ifs | 91 +++++ 6 files changed, 834 insertions(+), 134 deletions(-) create mode 100644 examples/sp/filter.lib create mode 100644 examples/sp/filter.sp create mode 100644 src/xspice/icm/analog/xfer/cfunc.mod create mode 100644 src/xspice/icm/analog/xfer/ifspec.ifs diff --git a/examples/sp/filter.lib b/examples/sp/filter.lib new file mode 100644 index 000000000..173d853dc --- /dev/null +++ b/examples/sp/filter.lib @@ -0,0 +1,185 @@ +.SUBCKT filter 1 2 3 +*2-port S-parameter file +*Title: * simple test for xfer code model: comparison +*Generated by ngspice at Tue May 23 06:49:31 2023 +* Hz S RI R +* Z1=50.000000 Z2=50.000000 + +R1N 1 10 -5.000000e+01 +R1P 10 11 1.000000e+02 +R2N 2 20 -5.000000e+01 +R2P 20 21 1.000000e+02 + +*S11 FREQ DB PHASE +E11 11 12 FREQ {V(10,3)}= DB ++( 1.000000e+07Hz, 3.654967e-07, -7.619541e+01) ++( 1.487179e+07Hz, 1.964030e-07, -1.157703e+02) ++( 1.974359e+07Hz, -1.084219e-07, -1.569277e+02) ++( 2.461538e+07Hz, -5.321598e-06, -1.985756e+02) ++( 2.948718e+07Hz, -1.265047e-05, -2.395114e+02) ++( 3.435897e+07Hz, -2.727603e-05, -2.792878e+02) ++( 3.923077e+07Hz, -6.620604e-05, -3.184596e+02) ++( 4.410256e+07Hz, -1.624082e-04, -3.582070e+02) ++( 4.897436e+07Hz, -3.755273e-04, -3.996839e+02) ++( 5.384615e+07Hz, -7.725656e-04, -4.432805e+02) ++( 5.871795e+07Hz, -1.320643e-03, -4.881884e+02) ++( 6.358974e+07Hz, -1.599290e-03, -5.330311e+02) ++( 6.846154e+07Hz, -4.631092e-04, -5.776220e+02) ++( 7.333333e+07Hz, -1.388239e-02, -6.262936e+02) ++( 7.820513e+07Hz, -1.245578e+01, -8.015034e+02) ++( 8.307692e+07Hz, -1.142468e+00, -6.888087e+02) ++( 8.794872e+07Hz, -1.292324e+00, -7.542062e+02) ++( 9.282051e+07Hz, -2.163078e+00, -8.283885e+02) ++( 9.769231e+07Hz, -3.764829e+00, -9.213357e+02) ++( 1.025641e+08Hz, -4.165673e+00, -1.041098e+03) ++( 1.074359e+08Hz, -2.143343e+00, -1.139856e+03) ++( 1.123077e+08Hz, -1.084156e+00, -1.201868e+03) ++( 1.171795e+08Hz, -7.962690e-01, -1.249480e+03) ++( 1.220513e+08Hz, -1.204024e+00, -1.318959e+03) ++( 1.269231e+08Hz, -4.148462e-01, -1.169292e+03) ++( 1.317949e+08Hz, -1.026085e-02, -1.240330e+03) ++( 1.366667e+08Hz, -1.360661e-04, -1.265793e+03) ++( 1.415385e+08Hz, -2.410965e-03, -1.281511e+03) ++( 1.464103e+08Hz, -3.787891e-03, -1.293132e+03) ++( 1.512821e+08Hz, -3.602264e-03, -1.302433e+03) ++( 1.561538e+08Hz, -2.547410e-03, -1.310211e+03) ++( 1.610256e+08Hz, -1.530232e-03, -1.316893e+03) ++( 1.658974e+08Hz, -8.667728e-04, -1.322741e+03) ++( 1.707692e+08Hz, -4.905888e-04, -1.327928e+03) ++( 1.756410e+08Hz, -2.840626e-04, -1.332578e+03) ++( 1.805128e+08Hz, -1.688713e-04, -1.336783e+03) ++( 1.853846e+08Hz, -1.041703e-04, -1.340612e+03) ++( 1.902564e+08Hz, -6.546576e-05, -1.344121e+03) ++( 1.951282e+08Hz, -4.228835e-05, -1.347353e+03) ++( 2.000000e+08Hz, -2.867354e-05, -1.350343e+03) + +*S12 FREQ DB PHASE +E12 12 3 FREQ {V(20,3)}= DB ++( 1.000000e+07Hz, -1.248758e+02, -1.373971e+02) ++( 1.487179e+07Hz, -9.831399e+01, -1.647281e+02) ++( 1.974359e+07Hz, -7.673566e+01, -2.026127e+02) ++( 2.461538e+07Hz, -5.898226e+01, -2.906518e+02) ++( 2.948718e+07Hz, -5.542440e+01, -3.666485e+02) ++( 3.435897e+07Hz, -5.204910e+01, -4.009490e+02) ++( 3.923077e+07Hz, -4.814977e+01, -4.271950e+02) ++( 4.410256e+07Hz, -4.426072e+01, -4.514100e+02) ++( 4.897436e+07Hz, -4.063346e+01, -4.755494e+02) ++( 5.384615e+07Hz, -3.749934e+01, -5.003303e+02) ++( 5.871795e+07Hz, -3.516924e+01, -5.256223e+02) ++( 6.358974e+07Hz, -3.433887e+01, -5.509590e+02) ++( 6.846154e+07Hz, -3.971774e+01, -5.765977e+02) ++( 7.333333e+07Hz, -2.496027e+01, -4.260244e+02) ++( 7.820513e+07Hz, -2.540060e-01, -5.317362e+02) ++( 8.307692e+07Hz, -6.358120e+00, -6.335796e+02) ++( 8.794872e+07Hz, -5.894279e+00, -6.719727e+02) ++( 9.282051e+07Hz, -4.063864e+00, -7.129969e+02) ++( 9.769231e+07Hz, -2.367660e+00, -7.603597e+02) ++( 1.025641e+08Hz, -2.098602e+00, -8.143919e+02) ++( 1.074359e+08Hz, -4.094614e+00, -8.636479e+02) ++( 1.123077e+08Hz, -6.557734e+00, -8.998592e+02) ++( 1.171795e+08Hz, -7.759301e+00, -9.295624e+02) ++( 1.220513e+08Hz, -6.159604e+00, -9.703161e+02) ++( 1.269231e+08Hz, -1.040475e+01, -1.077742e+03) ++( 1.317949e+08Hz, -2.627115e+01, -1.123641e+03) ++( 1.366667e+08Hz, -4.503397e+01, -9.675219e+02) ++( 1.415385e+08Hz, -3.255698e+01, -9.893598e+02) ++( 1.464103e+08Hz, -3.059532e+01, -1.012566e+03) ++( 1.512821e+08Hz, -3.081422e+01, -1.036661e+03) ++( 1.561538e+08Hz, -3.231825e+01, -1.058931e+03) ++( 1.610256e+08Hz, -3.453111e+01, -1.077318e+03) ++( 1.658974e+08Hz, -3.699828e+01, -1.091740e+03) ++( 1.707692e+08Hz, -3.947129e+01, -1.103061e+03) ++( 1.756410e+08Hz, -4.184769e+01, -1.112161e+03) ++( 1.805128e+08Hz, -4.409577e+01, -1.119694e+03) ++( 1.853846e+08Hz, -4.621280e+01, -1.126103e+03) ++( 1.902564e+08Hz, -4.820666e+01, -1.131686e+03) ++( 1.951282e+08Hz, -5.008836e+01, -1.136645e+03) ++( 2.000000e+08Hz, -5.186916e+01, -1.141123e+03) + +*S21 FREQ DB PHASE +E21 21 22 FREQ {V(10,3)}= DB ++( 1.000000e+07Hz, -1.248758e+02, -1.373971e+02) ++( 1.487179e+07Hz, -9.831399e+01, -1.647281e+02) ++( 1.974359e+07Hz, -7.673566e+01, -2.026127e+02) ++( 2.461538e+07Hz, -5.898226e+01, -2.906518e+02) ++( 2.948718e+07Hz, -5.542440e+01, -3.666485e+02) ++( 3.435897e+07Hz, -5.204910e+01, -4.009490e+02) ++( 3.923077e+07Hz, -4.814977e+01, -4.271950e+02) ++( 4.410256e+07Hz, -4.426072e+01, -4.514100e+02) ++( 4.897436e+07Hz, -4.063346e+01, -4.755494e+02) ++( 5.384615e+07Hz, -3.749934e+01, -5.003303e+02) ++( 5.871795e+07Hz, -3.516924e+01, -5.256223e+02) ++( 6.358974e+07Hz, -3.433887e+01, -5.509590e+02) ++( 6.846154e+07Hz, -3.971774e+01, -5.765977e+02) ++( 7.333333e+07Hz, -2.496027e+01, -4.260244e+02) ++( 7.820513e+07Hz, -2.540060e-01, -5.317362e+02) ++( 8.307692e+07Hz, -6.358120e+00, -6.335796e+02) ++( 8.794872e+07Hz, -5.894279e+00, -6.719727e+02) ++( 9.282051e+07Hz, -4.063864e+00, -7.129969e+02) ++( 9.769231e+07Hz, -2.367660e+00, -7.603597e+02) ++( 1.025641e+08Hz, -2.098602e+00, -8.143919e+02) ++( 1.074359e+08Hz, -4.094614e+00, -8.636479e+02) ++( 1.123077e+08Hz, -6.557734e+00, -8.998592e+02) ++( 1.171795e+08Hz, -7.759301e+00, -9.295624e+02) ++( 1.220513e+08Hz, -6.159604e+00, -9.703161e+02) ++( 1.269231e+08Hz, -1.040475e+01, -1.077742e+03) ++( 1.317949e+08Hz, -2.627115e+01, -1.123641e+03) ++( 1.366667e+08Hz, -4.503397e+01, -9.675219e+02) ++( 1.415385e+08Hz, -3.255698e+01, -9.893598e+02) ++( 1.464103e+08Hz, -3.059532e+01, -1.012566e+03) ++( 1.512821e+08Hz, -3.081422e+01, -1.036661e+03) ++( 1.561538e+08Hz, -3.231825e+01, -1.058931e+03) ++( 1.610256e+08Hz, -3.453111e+01, -1.077318e+03) ++( 1.658974e+08Hz, -3.699828e+01, -1.091740e+03) ++( 1.707692e+08Hz, -3.947129e+01, -1.103061e+03) ++( 1.756410e+08Hz, -4.184769e+01, -1.112161e+03) ++( 1.805128e+08Hz, -4.409577e+01, -1.119694e+03) ++( 1.853846e+08Hz, -4.621280e+01, -1.126103e+03) ++( 1.902564e+08Hz, -4.820666e+01, -1.131686e+03) ++( 1.951282e+08Hz, -5.008836e+01, -1.136645e+03) ++( 2.000000e+08Hz, -5.186916e+01, -1.141123e+03) + +*S22 FREQ DB PHASE +E22 22 3 FREQ {V(20,3)}= DB ++( 1.000000e+07Hz, 1.633257e-07, -1.859873e+01) ++( 1.487179e+07Hz, -1.597472e-07, -3.368599e+01) ++( 1.974359e+07Hz, -9.840549e-08, -6.829764e+01) ++( 2.461538e+07Hz, -5.748843e-06, -2.027281e+02) ++( 2.948718e+07Hz, -1.209271e-05, -3.137855e+02) ++( 3.435897e+07Hz, -2.686288e-05, -3.426102e+02) ++( 3.923077e+07Hz, -6.619051e-05, -3.559304e+02) ++( 4.410256e+07Hz, -1.626503e-04, -3.646130e+02) ++( 4.897436e+07Hz, -3.750315e-04, -3.714149e+02) ++( 5.384615e+07Hz, -7.720713e-04, -3.773802e+02) ++( 5.871795e+07Hz, -1.321131e-03, -3.830562e+02) ++( 6.358974e+07Hz, -1.599127e-03, -3.888868e+02) ++( 6.846154e+07Hz, -4.639695e-04, -3.955734e+02) ++( 7.333333e+07Hz, -1.388161e-02, -4.057551e+02) ++( 7.820513e+07Hz, -1.245578e+01, -4.419690e+02) ++( 8.307692e+07Hz, -1.142468e+00, -3.983505e+02) ++( 8.794872e+07Hz, -1.292324e+00, -4.097393e+02) ++( 9.282051e+07Hz, -2.163078e+00, -4.176053e+02) ++( 9.769231e+07Hz, -3.764828e+00, -4.193838e+02) ++( 1.025641e+08Hz, -4.165672e+00, -4.076855e+02) ++( 1.074359e+08Hz, -2.143343e+00, -4.074397e+02) ++( 1.123077e+08Hz, -1.084156e+00, -4.178506e+02) ++( 1.171795e+08Hz, -7.962687e-01, -4.296447e+02) ++( 1.220513e+08Hz, -1.204024e+00, -4.416736e+02) ++( 1.269231e+08Hz, -4.148459e-01, -4.461929e+02) ++( 1.317949e+08Hz, -1.026073e-02, -4.669521e+02) ++( 1.366667e+08Hz, -1.361528e-04, -4.892508e+02) ++( 1.415385e+08Hz, -2.411138e-03, -5.172082e+02) ++( 1.464103e+08Hz, -3.788042e-03, -5.519999e+02) ++( 1.512821e+08Hz, -3.601989e-03, -5.908887e+02) ++( 1.561538e+08Hz, -2.547212e-03, -6.276516e+02) ++( 1.610256e+08Hz, -1.529976e-03, -6.577430e+02) ++( 1.658974e+08Hz, -8.674572e-04, -6.807399e+02) ++( 1.707692e+08Hz, -4.901623e-04, -6.981937e+02) ++( 1.756410e+08Hz, -2.836349e-04, -7.117446e+02) ++( 1.805128e+08Hz, -1.694495e-04, -7.226059e+02) ++( 1.853846e+08Hz, -1.034247e-04, -7.315943e+02) ++( 1.902564e+08Hz, -6.587183e-05, -7.392507e+02) ++( 1.951282e+08Hz, -4.251059e-05, -7.459375e+02) ++( 2.000000e+08Hz, -2.798303e-05, -7.519027e+02) + +.ENDS diff --git a/examples/sp/filter.sp b/examples/sp/filter.sp new file mode 100644 index 000000000..5851bcf6e --- /dev/null +++ b/examples/sp/filter.sp @@ -0,0 +1,91 @@ +* Simple test for xfer code model: comparison +* +* This circuit compares the results of an AC analysis of a filter (node out) +* with those from a behavioural model controlled by measured S-parameters +* of that circuit (node xout). The AC analysis has more data points than +* that used to measure the S-parameters, to prevent the results from matching +* exactly. + +* The use of S-parameters to create a behavioural simulation of a component +* was discussed here: +* https://sourceforge.net/p/ngspice/discussion/120973/thread/51228e0b01/ + +* Circuit from: +* Novarianti, Dini. (2019). +* Design and Implementation of Chebyshev Band Pass Filter with +* M-Derived Section in Frequency Band 88 - 108 MHz. +* Jurnal Jartel: Jurnal Jaringan Telekomunikasi. 8. 7-11. +* 10.33795/jartel.v8i1.147. +* +* https://www.researchgate.net/publication/352822864_Design_and_Implementation_of_Chebyshev_Band_Pass_Filter_with_M-Derived_Section_in_Frequency_Band_88_-_108_MHz + +* Set this parameter to 1 to generate a Touchstone file that can be used +* to generate the behavioural part of the circuit, filter.lib + +.param do_sp=0 +.csparam do_sp=do_sp + +.if (do_sp) + +.csparam Rbase=50 ; This is required by "wrs2p", below. +vgen 1 0 dc 0 ac 1 portnum 1 + +.else + +vgen in 0 dc 0 ac 1 +rs in 1 50 + +.endif + +l1 1 2 0.058u +c2 2 0 40.84p +l3 2 3 0.128u +c4 3 0 47.91p +l5 3 4 0.128u +c6 4 0 40.48p +l7 4 5 0.058u + +la 5 6 0.044u +lb 6 a 0.078u +cb a 0 17.61p +lc 6 b 0.151u +cc b 0 34.12p +c7 6 7 26.035p + +l8 7 0 0.0653u +c8 7 8 20.8p +l9 8 0 0.055u +c9 8 9 20.8p +l10 9 0 0.653u + +c10 9 out 45.64p + +.if (do_sp) + +vl out 0 dc 0 ac 0 portnum 2 + +.else + +rl out 0 50 + +* Behavioural circuit, for comparison. + +.inc filter.lib +R1 in port1 50 +xsp port1 xout 0 filter +R2 xout 0 50 + +.endif + +.control +if $&do_sp + sp lin 40 10meg 200meg + wrs2p filter.s2p + plot S_1_1 S_2_2 polar +else + ac lin 400 10meg 200meg + plot db(mag(out)) 5*unwrap(ph(out)) db(mag(xout)) 5*unwrap(ph(xout)) +end + +.endc +.end diff --git a/src/frontend/inpcom.c b/src/frontend/inpcom.c index dd83a62f4..d0314d41a 100644 --- a/src/frontend/inpcom.c +++ b/src/frontend/inpcom.c @@ -152,7 +152,7 @@ static char inp_get_elem_ident(char *type); static void rem_mfg_from_models(struct card *start_card); static void inp_fix_macro_param_func_paren_io(struct card *begin_card); static void inp_fix_gnd_name(struct card *deck); -static void inp_chk_for_multi_in_vcvs(struct card *deck, int *line_number); +static void inp_chk_for_e_source_to_xspice(struct card *deck, int *line_number); static void inp_add_control_section(struct card *deck, int *line_number); static char *get_quoted_token(char *string, char **token); static void replace_token(char *string, char *token, int where, int total); @@ -985,7 +985,7 @@ struct card *inp_readall(FILE *fp, const char *dir_name, subckt_w_params = NULL; if (!cp_getvar("no_auto_gnd", CP_BOOL, NULL, 0)) inp_fix_gnd_name(working); - inp_chk_for_multi_in_vcvs(working, &rv.line_number); + inp_chk_for_e_source_to_xspice(working, &rv.line_number); /* "addcontrol" variable is set if "ngspice -a file" was used. */ @@ -1899,7 +1899,6 @@ static void inp_fix_gnd_name(struct card *c) } } - /* * transform a VCVS "gate" instance into a XSPICE instance * @@ -1917,163 +1916,366 @@ static void inp_fix_gnd_name(struct card *c) * the x,y list is fixed to length 2 */ -static void inp_chk_for_multi_in_vcvs(struct card *c, int *line_number) +static int inp_chk_for_multi_in_vcvs(struct card *c, int *line_number) { - int skip_control = 0; + char *fcn_b, *line; - for (; c; c = c->nextcard) { + line = c->line; + if (((fcn_b = strstr(line, "nand(")) != NULL || + (fcn_b = strstr(line, "and(")) != NULL || + (fcn_b = strstr(line, "nor(")) != NULL || + (fcn_b = strstr(line, "or(")) != NULL) && + isspace_c(fcn_b[-1])) { +#ifndef XSPICE + fprintf(stderr, + "\n" + "Error: XSPICE is required to run the 'multi-input " + "pwl' option in line %d\n" + " %s\n" + "\n" + "See manual chapt. 31 for installation " + "instructions\n", + *line_number, line); + controlled_exit(EXIT_BAD); +#else + char keep, *comma_ptr, *xy_values1[5], *xy_values2[5]; + char *out_str, *ctrl_nodes_str, + *xy_values1_b = NULL, *ref_str, *fcn_name, + *fcn_e = NULL, *out_b, *out_e, *ref_e; + char *m_instance, *m_model; + char *xy_values2_b = NULL, *xy_values1_e = NULL, + *ctrl_nodes_b = NULL, *ctrl_nodes_e = NULL; + int xy_count1, xy_count2; + bool ok = FALSE; + + do { + ref_e = skip_non_ws(line); + + out_b = skip_ws(ref_e); + + out_e = skip_back_ws(fcn_b, out_b); + if (out_e <= out_b) + break; - char *line = c->line; + fcn_e = strchr(fcn_b, '('); - /* there is no e source inside .control ... .endc */ - if (ciprefix(".control", line)) { - skip_control++; - continue; - } - else if (ciprefix(".endc", line)) { - skip_control--; - continue; - } - else if (skip_control > 0) { - continue; + ctrl_nodes_b = strchr(fcn_e, ')'); + if (!ctrl_nodes_b) + break; + ctrl_nodes_b = skip_ws(ctrl_nodes_b + 1); + + comma_ptr = strchr(ctrl_nodes_b, ','); + if (!comma_ptr) + break; + + xy_values1_b = skip_back_ws(comma_ptr, ctrl_nodes_b); + if (xy_values1_b[-1] == '}') { + while (--xy_values1_b >= ctrl_nodes_b) + if (*xy_values1_b == '{') + break; + } else { + xy_values1_b = skip_back_non_ws(xy_values1_b, ctrl_nodes_b); + } + if (xy_values1_b <= ctrl_nodes_b) + break; + + ctrl_nodes_e = skip_back_ws(xy_values1_b, ctrl_nodes_b); + if (ctrl_nodes_e <= ctrl_nodes_b) + break; + + xy_values1_e = skip_ws(comma_ptr + 1); + if (*xy_values1_e == '{') { + xy_values1_e = inp_spawn_brace(xy_values1_e); + } else { + xy_values1_e = skip_non_ws(xy_values1_e); + } + if (!xy_values1_e) + break; + + xy_values2_b = skip_ws(xy_values1_e); + + ok = TRUE; + } while (0); + + if (!ok) { + fprintf(stderr, "ERROR: malformed line: %s\n", line); + controlled_exit(EXIT_FAILURE); } - if (*line == 'e') { + ref_str = copy_substring(line, ref_e); + out_str = copy_substring(out_b, out_e); + fcn_name = copy_substring(fcn_b, fcn_e); + ctrl_nodes_str = copy_substring(ctrl_nodes_b, ctrl_nodes_e); - char *fcn_b; + keep = *xy_values1_e; + *xy_values1_e = '\0'; + xy_count1 = + get_comma_separated_values(xy_values1, xy_values1_b); + *xy_values1_e = keep; - if (((fcn_b = strstr(line, "nand(")) != NULL || - (fcn_b = strstr(line, "and(")) != NULL || - (fcn_b = strstr(line, "nor(")) != NULL || - (fcn_b = strstr(line, "or(")) != NULL) && - isspace_c(fcn_b[-1])) { -#ifndef XSPICE - fprintf(stderr, - "\n" - "Error: XSPICE is required to run the 'multi-input " - "pwl' option in line %d\n" - " %s\n" - "\n" - "See manual chapt. 31 for installation " - "instructions\n", - *line_number, line); - controlled_exit(EXIT_BAD); -#else - char keep, *comma_ptr, *xy_values1[5], *xy_values2[5]; - char *out_str, *ctrl_nodes_str, - *xy_values1_b = NULL, *ref_str, *fcn_name, - *fcn_e = NULL, *out_b, *out_e, *ref_e; - char *m_instance, *m_model; - char *xy_values2_b = NULL, *xy_values1_e = NULL, - *ctrl_nodes_b = NULL, *ctrl_nodes_e = NULL; - int xy_count1, xy_count2; - bool ok = FALSE; - - do { - ref_e = skip_non_ws(line); - - out_b = skip_ws(ref_e); - - out_e = skip_back_ws(fcn_b, out_b); - if (out_e <= out_b) - break; + xy_count2 = get_comma_separated_values(xy_values2, xy_values2_b); - fcn_e = strchr(fcn_b, '('); + // place restrictions on only having 2 point values; this can + // change later + if (xy_count1 != 2 && xy_count2 != 2) + fprintf(stderr, + "ERROR: only expecting 2 pair values for " + "multi-input vcvs!\n"); + + m_instance = tprintf("%s %%vd[ %s ] %%vd( %s ) %s", ref_str, + ctrl_nodes_str, out_str, ref_str); + m_instance[0] = 'a'; + + m_model = tprintf(".model %s multi_input_pwl ( x = [%s %s] y " + "= [%s %s] model = \"%s\" )", + ref_str, xy_values1[0], xy_values2[0], xy_values1[1], + xy_values2[1], fcn_name); + + tfree(ref_str); + tfree(out_str); + tfree(fcn_name); + tfree(ctrl_nodes_str); + tfree(xy_values1[0]); + tfree(xy_values1[1]); + tfree(xy_values2[0]); + tfree(xy_values2[1]); + + *c->line = '*'; + c = insert_new_line(c, m_instance, (*line_number)++, c->linenum_orig); + c = insert_new_line(c, m_model, (*line_number)++, c->linenum_orig); +#endif + return 1; + } else { + return 0; // No keyword match. */ + } +} - ctrl_nodes_b = strchr(fcn_e, ')'); - if (!ctrl_nodes_b) - break; - ctrl_nodes_b = skip_ws(ctrl_nodes_b + 1); +/* replace the E and G source FREQ function by an XSPICE xfer instance + * (used by Touchstone to netlist converter programs). + * E1 n1 n2 FREQ {expression} = DB values ... + * will become + * B1_gen 1_gen 0 v = expression + * A1_gen 1_gen %d(n1 n2) 1_gen + * .model 1_gen xfer db=true table=[ values ] + */ - comma_ptr = strchr(ctrl_nodes_b, ','); - if (!comma_ptr) - break; +static void replace_freq(struct card *c, int *line_number) +{ +#ifdef XSPICE + char *line, *e, *e_e, *n1, *n1_e, *n2, *n2_e, *freq; + char *expr, *expr_e, *in, *in_e, *keywd, *cp, *list, *list_e; + int db, ri, rad, got_key, diff; + char pt, key[4]; - xy_values1_b = skip_back_ws(comma_ptr, ctrl_nodes_b); - if (xy_values1_b[-1] == '}') { - while (--xy_values1_b >= ctrl_nodes_b) - if (*xy_values1_b == '{') - break; - } - else { - xy_values1_b = - skip_back_non_ws(xy_values1_b, ctrl_nodes_b); - } - if (xy_values1_b <= ctrl_nodes_b) - break; + line = c->line; - ctrl_nodes_e = skip_back_ws(xy_values1_b, ctrl_nodes_b); - if (ctrl_nodes_e <= ctrl_nodes_b) - break; + /* First token is a node name. */ - xy_values1_e = skip_ws(comma_ptr + 1); - if (*xy_values1_e == '{') { - xy_values1_e = inp_spawn_brace(xy_values1_e); - } - else { - xy_values1_e = skip_non_ws(xy_values1_e); - } - if (!xy_values1_e) - break; + e = line + 1; + e_e = skip_non_ws(e); + n1 = skip_ws(e_e); + n1_e = skip_non_ws(n1); + freq = strstr(n1_e, "freq"); + if (!freq || !isspace_c(freq[-1]) || !isspace_c(freq[4])) + return; + n2 = skip_ws(n1_e); + if (n2 == freq) { + n2 = NULL; + } else { + n2_e = skip_non_ws(n2); + if (freq != skip_ws(n2_e)) // Three nodes or another keyword. + return; + } - xy_values2_b = skip_ws(xy_values1_e); + /* Isolate the input expression. */ - ok = TRUE; - } while (0); + expr = skip_ws(freq + 4); + if (*expr != '{') + return; + expr = skip_ws(expr + 1); + expr_e = strchr(expr, '}'); + if (!expr_e) + return; + skip_back_ws(expr_e, expr); - if (!ok) { - fprintf(stderr, "ERROR: malformed line: %s\n", line); - controlled_exit(EXIT_FAILURE); - } + /* Is the expression just a node name, or v(node) or v(node1, node2)? */ - ref_str = copy_substring(line, ref_e); - out_str = copy_substring(out_b, out_e); - fcn_name = copy_substring(fcn_b, fcn_e); - ctrl_nodes_str = copy_substring(ctrl_nodes_b, ctrl_nodes_e); + in = NULL; + diff = 0; + if (*expr < '0' || *expr > '9') { + for (in_e = expr; in_e < expr_e; ++in_e) { + if ((*in_e < '0' || *in_e > '9') && (*in_e < 'a' || *in_e > 'z') && + *in_e != '_') + break; + } + if (in_e == expr_e) { + /* A simple identifier. */ + + in = expr; + } + } + if (expr[0] == 'v' && expr[1] == '(' && expr_e[-1] == ')') { + in = expr + 2; + in_e = expr_e - 1; + cp = strchr(in, ','); + diff = (cp && cp < in_e); // Assume v(n1, n2) + } + + /* Look for a keyword. Previous processing may put braces around it. */ + + keywd = skip_ws(expr_e + 1); + if (*keywd == '=') + keywd = skip_ws(keywd + 1); + + db = 1; + rad = 0; + ri = 0; + do { + if (!keywd) + return; + list = keywd; // Perhaps not keyword + if (*keywd == '{') + ++keywd; + cp = key; + while (*keywd && !isspace_c(*keywd) && *keywd != '}' && + cp - key < sizeof key - 1) { + *cp++ = *keywd++; + } + *cp = 0; + if (*keywd == '}') + ++keywd; + if (!isspace_c(*keywd)) + return; + + /* Parse the format keyword, if any. */ + + got_key = 0; + if (!strcmp(key, "mag")) { + db = 0; + got_key = 1; + } else if (!strcmp(key, "db")) { + db = 1; + got_key = 1; + } else if (!strcmp(key, "rad")) { + rad = 1; + got_key = 1; + } else if (!strcmp(key, "deg")) { + rad = 0; + got_key = 1; + } else if (!strcmp(key, "r_i")) { + ri = 1; + got_key = 1; + } + + /* Get the list of values. */ + + if (got_key) + list = skip_ws(keywd); + if (!list) + return; + keywd = list; + } while(got_key); + + list_e = list + strlen(list) - 1; + skip_back_ws(list_e, list); + if (list >= list_e) + return; - keep = *xy_values1_e; - *xy_values1_e = '\0'; - xy_count1 = - get_comma_separated_values(xy_values1, xy_values1_b); - *xy_values1_e = keep; + /* All good, rewrite the line. + * Macro BSTR is used to pass counted string arguments to tprintf(). + */ - xy_count2 = - get_comma_separated_values(xy_values2, xy_values2_b); +#define BSTR(s) (int)(s##_e - s), s - // place restrictions on only having 2 point values; this can - // change later - if (xy_count1 != 2 && xy_count2 != 2) - fprintf(stderr, - "ERROR: only expecting 2 pair values for " - "multi-input vcvs!\n"); - - m_instance = tprintf("%s %%vd[ %s ] %%vd( %s ) %s", ref_str, - ctrl_nodes_str, out_str, ref_str); - m_instance[0] = 'a'; - - m_model = tprintf(".model %s multi_input_pwl ( x = [%s %s] y " - "= [%s %s] model = \"%s\" )", - ref_str, xy_values1[0], xy_values2[0], xy_values1[1], - xy_values2[1], fcn_name); - - tfree(ref_str); - tfree(out_str); - tfree(fcn_name); - tfree(ctrl_nodes_str); - tfree(xy_values1[0]); - tfree(xy_values1[1]); - tfree(xy_values2[0]); - tfree(xy_values2[1]); - - *c->line = '*'; - c = insert_new_line(c, m_instance, (*line_number)++, c->linenum_orig); - c = insert_new_line(c, m_model, (*line_number)++, c->linenum_orig); -#endif + pt = (*line == 'e') ? 'v' : 'i'; + *line = '*'; // Make a comment + if (in) { + /* Connect input nodes directly. */ + + if (diff) { + /* Differential input. */ + + if (n2) { + line = tprintf("a_gen_%.*s %%vd(%.*s) %%%cd(%.*s %.*s) " + "gen_model_%.*s", + BSTR(e), BSTR(in), pt, BSTR(n1), BSTR(n2), BSTR(e)); + } else { + line = tprintf("a_gen_%.*s %%vd(%.*s) %%%c(%.*s) " + "gen_model_%.*s", + BSTR(e), BSTR(in), pt, BSTR(n1), BSTR(e)); } + } else { + /* Single node input. */ + + if (n2) { + line = tprintf("a_gen_%.*s %.*s %%%cd(%.*s %.*s) " + "gen_model_%.*s", + BSTR(e), BSTR(in), pt, BSTR(n1), BSTR(n2), + BSTR(e)); + } else { + line = tprintf("a_gen_%.*s %.*s %%%c(%.*s) gen_model_%.*s", + BSTR(e), BSTR(in), pt, BSTR(n1), BSTR(e)); + } + } + } else { + /* Use a B-source for input. */ + + line = tprintf("b_gen_%.*s gen_node_%.*s 0 v=%.*s", + BSTR(e), BSTR(e), BSTR(expr)); + c = insert_new_line(c, line, (*line_number)++, c->linenum_orig); + if (n2) { + line = tprintf("a_gen_%.*s gen_node_%.*s %%%cd(%.*s %.*s) " + "gen_model_%.*s", + BSTR(e), BSTR(e), pt, BSTR(n1), BSTR(n2), BSTR(e)); + } else { + line = tprintf("a_gen_%.*s gen_node_%.*s %%%c(%.*s) " + "gen_model_%.*s", + BSTR(e), BSTR(e), pt, BSTR(n1), BSTR(e)); } } + c = insert_new_line(c, line, (*line_number)++, c->linenum_orig); + + line = tprintf(".model gen_model_%.*s xfer %s table = [%.*s]", + BSTR(e), + ri ? "r_i=true" : rad ? "rad=true" : !db ? "db=false" : "", + BSTR(list)); + c = insert_new_line(c, line, (*line_number)++, c->linenum_orig); +#endif } +/* Convert some E and G-source variants to XSPICE code models. */ + +static void inp_chk_for_e_source_to_xspice(struct card *c, int *line_number) +{ + int skip_control = 0; + + for (; c; c = c->nextcard) { + + char *line = c->line; + + /* there is no e source inside .control ... .endc */ + if (ciprefix(".control", line)) { + skip_control++; + continue; + } + else if (ciprefix(".endc", line)) { + skip_control--; + continue; + } + else if (skip_control > 0) { + continue; + } + + if (*line == 'e' && inp_chk_for_multi_in_vcvs(c, line_number)) + continue; + if (*line != 'e' && *line != 'g') + continue; + + /* Is it the FREQ form with S-parameter table? */ + + replace_freq(c, line_number); + } +} /* If ngspice is started with option -a, then variable 'autorun' * will be set and a control section is inserted to try and ensure diff --git a/src/xspice/icm/analog/modpath.lst b/src/xspice/icm/analog/modpath.lst index 481162718..acf24b33f 100644 --- a/src/xspice/icm/analog/modpath.lst +++ b/src/xspice/icm/analog/modpath.lst @@ -14,6 +14,7 @@ sine slew square summer +xfer s_xfer triangle file_source diff --git a/src/xspice/icm/analog/xfer/cfunc.mod b/src/xspice/icm/analog/xfer/cfunc.mod new file mode 100644 index 000000000..e57cbeaa8 --- /dev/null +++ b/src/xspice/icm/analog/xfer/cfunc.mod @@ -0,0 +1,130 @@ +/* Transfer function block for AC simulation, based on s_xfer code model. */ + +#include + +#define PI 3.141592653589793238462643383279502884197 + +/* How the table information is stored internally. */ + +struct data_pt { + double f; /* Frequency, radians/sec. */ + Mif_Complex_t s; /* The S-parameter. */ +}; + +static void cleanup(ARGS, Mif_Callback_Reason_t reason) +{ + struct data_pt *table; + + switch (reason) { + case MIF_CB_DESTROY: + table = (struct data_pt *)STATIC_VAR(table); + if (table) { + free(table); + STATIC_VAR(table) = NULL; + } + break; + } +} + +void cm_xfer(ARGS) /* structure holding parms, inputs, outputs, etc. */ +{ + struct data_pt *table; + Mif_Complex_t ac_gain; + double factor; + int span, size, i; + + span = PARAM(span); + if (INIT) { + Mif_Boolean_t ri, db, rad; + int offset, bad = 0, j; + + /* Validate table. */ + + offset = PARAM(offset); + size = PARAM_SIZE(table); + bad = size % span; + if (!bad) { + for (i = 0; i < size - span; i += span) { + if (PARAM(table[i]) < 0 || + PARAM(table[i + span]) < PARAM(table[i])) { + bad = 1; + break; + } + } + } + if (bad) { + cm_message_send("Warning: badly formed table."); + return; + } + + /* Allocate the internal table. */ + + size /= span; + table = (struct data_pt *)calloc(size, sizeof(struct data_pt)); + STATIC_VAR(table) = table; + CALLBACK = cleanup; + + /* Fill it. */ + + ri = PARAM(r_i); + db = PARAM(db); + rad = PARAM(rad); + for (i = 0, j = 0; i < size; i++, j += span) { + table[i].f = PARAM(table[j]) * 2.0 * PI; + if (ri) { + table[i].s.real = PARAM(table[j + offset]); + table[i].s.imag = PARAM(table[j + offset + 1]); + } else { + double phase, mag; + + mag = PARAM(table[j + offset]); + if (db) + mag = pow(10, mag / 20); + phase = PARAM(table[j + offset + 1]); + if (!rad) + phase *= 2 * PI / 360; + table[i].s.real = mag * cos(phase); + table[i].s.imag = mag * sin(phase); + } + } + } + + table = (struct data_pt *)STATIC_VAR(table); + if (!table) + return; + if (ANALYSIS == MIF_AC) { + double rv; + + size = PARAM_SIZE(table) / span; + rv = RAD_FREQ; + if (rv <= table[0].f) { + ac_gain = table[0].s; + } else if (rv >= table[size - 1].f) { + ac_gain = table[size - 1].s; + } else { + for (i = 0; i < size; i++) { + if (table[i].f > rv) + break; + } + + /* Linear interpolation. */ + + factor = (rv - table[i - 1].f) / (table[i].f - table[i - 1].f); + ac_gain.real = table[i - 1].s.real + + factor * (table[i].s.real - table[i - 1].s.real); + ac_gain.imag = table[i - 1].s.imag + + factor * (table[i].s.imag - table[i - 1].s.imag); + } + AC_GAIN(out, in) = ac_gain; + } else { /* DC, transient ... */ + if (ANALYSIS == MIF_TRAN) { + if (!STATIC_VAR(warned)) { + STATIC_VAR(warned) = 1; + cm_message_send("The xfer code model does not support " + "transient analysis."); + } + } + OUTPUT(out) = table[0].s.real * INPUT(in); + } +} + diff --git a/src/xspice/icm/analog/xfer/ifspec.ifs b/src/xspice/icm/analog/xfer/ifspec.ifs new file mode 100644 index 000000000..41f2ac177 --- /dev/null +++ b/src/xspice/icm/analog/xfer/ifspec.ifs @@ -0,0 +1,91 @@ +/* Interface specification for PWL transfer function code model. */ + +NAME_TABLE: + +Spice_Model_Name: xfer +C_Function_Name: cm_xfer +Description: "AC transfer function block" + + +PORT_TABLE: + +Port_Name: in out +Description: "input" "output" +Direction: in out +Default_Type: v v +Allowed_Types: [v,vd,i,id] [v,vd,i,id] +Vector: no no +Vector_Bounds: - - +Null_Allowed: no no + +PARAMETER_TABLE: + +Parameter_Name: table +Description: "PWL table: frequency/magnitude/phase" +Data_Type: real +Default_Value: - +Limits: - +Vector: yes +Vector_Bounds: [3 -] +Null_Allowed: no + +PARAMETER_TABLE: + +Parameter_Name: r_i +Description: "table is in real/imaginary format" +Data_Type: boolean +Default_Value: false +Limits: - +Vector: no +Vector_Bounds: - +Null_Allowed: yes + +PARAMETER_TABLE: + +Parameter_Name: db +Description: "table is in magnitude(dB)/phase format" +Data_Type: boolean +Default_Value: true +Limits: - +Vector: no +Vector_Bounds: - +Null_Allowed: yes + +PARAMETER_TABLE: + +Parameter_Name: rad +Description: "phase in radians, not degrees" +Data_Type: boolean +Default_Value: false +Limits: - +Vector: no +Vector_Bounds: - +Null_Allowed: yes + +PARAMETER_TABLE: + +Parameter_Name: span offset +Description: "Length of table rows" "Offset within row" +Data_Type: int int +Default_Value: 3 1 +Limits: [ 3 - ] [ 1 - ] +Vector: no no +Vector_Bounds: - - +Null_Allowed: yes yes + +/* This is used internally to store the table in compact complex form. */ + +STATIC_VAR_TABLE: + +Static_Var_Name: table +Description: "Internal copy of data" +Data_Type: pointer + +/* Only warn once about use in transient analysis. */ + +STATIC_VAR_TABLE: + +Static_Var_Name: warned +Description: "Warning indicator" +Data_Type: int +