Merge pull request #1329 from KLayout/spice

Spice
This commit is contained in:
Matthias Köfferlein 2023-04-13 22:53:55 +02:00 committed by GitHub
commit 3ba2e645f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 346 additions and 41 deletions

View File

@ -75,6 +75,7 @@ class SpiceReaderStream
{
public:
SpiceReaderStream ();
SpiceReaderStream (const std::string &lib);
~SpiceReaderStream ();
void set_stream (tl::InputStream &stream);
@ -85,9 +86,11 @@ public:
int line_number () const;
std::string source () const;
bool at_end () const;
const std::string &lib () const { return m_lib; }
void swap (SpiceReaderStream &other)
{
std::swap (m_lib, other.m_lib);
std::swap (mp_stream, other.mp_stream);
std::swap (m_owns_stream, other.m_owns_stream);
std::swap (mp_text_stream, other.mp_text_stream);
@ -103,6 +106,7 @@ private:
int m_line_number;
std::string m_stored_line;
bool m_has_stored_line;
std::string m_lib;
};
@ -112,6 +116,12 @@ SpiceReaderStream::SpiceReaderStream ()
// .. nothing yet ..
}
SpiceReaderStream::SpiceReaderStream (const std::string &lib)
: mp_stream (0), m_owns_stream (false), mp_text_stream (0), m_line_number (0), m_stored_line (), m_has_stored_line (false), m_lib (lib)
{
// .. nothing yet ..
}
SpiceReaderStream::~SpiceReaderStream ()
{
close ();
@ -372,6 +382,7 @@ private:
std::vector<std::string> m_paths;
std::map<std::string, int> m_file_id_per_path;
std::list<SpiceReaderStream> m_streams;
std::list<std::string> m_in_lib;
SpiceReaderStream m_stream;
int m_file_id;
std::map<std::string, SpiceCachedCircuit *> m_cached_circuits;
@ -382,7 +393,7 @@ private:
std::set<std::string> m_global_net_names;
std::vector<std::string> m_global_nets;
void push_stream (const std::string &path);
void push_stream (const std::string &path, const std::string &lib = std::string ());
void pop_stream ();
bool at_end ();
void read_subcircuit (const std::string &sc_name, const std::string &nc_name, const std::vector<db::Net *> &nets);
@ -489,7 +500,7 @@ SpiceCircuitDict::read (tl::InputStream &stream)
}
void
SpiceCircuitDict::push_stream (const std::string &path)
SpiceCircuitDict::push_stream (const std::string &path, const std::string &lib)
{
tl::URI current_uri (m_stream.source ());
tl::URI new_uri (path);
@ -505,7 +516,7 @@ SpiceCircuitDict::push_stream (const std::string &path)
istream = new tl::InputStream (current_uri.resolved (new_uri).to_abstract_path ());
}
m_streams.push_back (SpiceReaderStream ());
m_streams.push_back (SpiceReaderStream (lib));
m_streams.back ().swap (m_stream);
m_stream.set_stream (istream);
@ -557,24 +568,73 @@ SpiceCircuitDict::get_line ()
if (m_streams.empty ()) {
break;
} else {
if (! m_stream.lib ().empty ()) {
m_in_lib.pop_back ();
}
pop_stream ();
}
} else {
bool consider_line = m_in_lib.empty () || (! m_stream.lib ().empty () && m_stream.lib () == m_in_lib.back ());
tl::Extractor ex (lp.first.c_str ());
if (ex.test_without_case (".include") || ex.test_without_case (".inc")) {
std::string path;
ex.read_word_or_quoted (path, allowed_name_chars);
push_stream (path);
if (consider_line) {
std::string libname = m_stream.lib ();
push_stream (path, libname);
if (! libname.empty ()) {
m_in_lib.push_back (libname);
}
}
ex.expect_end ();
} else if (ex.test_without_case (".lib")) {
std::string path_or_libname;
ex.read_word_or_quoted (path_or_libname, allowed_name_chars);
if (! ex.at_end ()) {
std::string libname;
ex.read_word_or_quoted (libname, allowed_name_chars);
if (consider_line) {
libname = mp_netlist->normalize_name (libname);
push_stream (path_or_libname, libname);
if (! libname.empty ()) {
m_in_lib.push_back (std::string ());
}
}
} else {
std::string libname = mp_netlist->normalize_name (path_or_libname);
m_in_lib.push_back (libname);
ex.expect_end ();
}
} else if (ex.test_without_case (".endl")) {
if (! m_in_lib.empty ()) {
m_in_lib.pop_back ();
} else {
warn (tl::to_string (tr ("Ignoring .endl without .lib")));
}
ex.expect_end ();
} else if (ex.at_end () || ex.test ("*")) {
// skip empty and comment lines
} else {
} else if (consider_line) {
break;
}

View File

@ -225,7 +225,7 @@ void NetlistSpiceReaderDelegate::parse_element_components (const std::string &s,
// a parameter
std::string pn = mp_netlist ? mp_netlist->normalize_name (n) : tl::to_upper_case (n);
pv [pn] = read_value (ex, variables);
pv [pn] = read_value (ex, variables, pv);
} else {
@ -304,44 +304,81 @@ void NetlistSpiceReaderDelegate::parse_element (const std::string &s, const std:
// (same for C, L instead of R)
if (nn.size () < 2) {
error (tl::to_string (tr ("Not enough specs for a R, C or L device")));
error (tl::to_string (tr ("Not enough specs (nodes, value, model) for a R, C or L device")));
} else if (nn.size () > 5) {
error (tl::to_string (tr ("Too many specs (nodes, value, model) for a R, C or L device")));
}
// Variations are (here for "C" element):
// (1) Cname n1 n2 C=value [other params]
// (2) Cname n1 n2 value [params]
// (3) Cname n1 n2 model C=value [other params]
// Cname n1 n2 n3 C=value [other params] -> not supported, cannot tell from (3) without further analysis
// (4) Cname n1 n2 model value [params]
// Cname n1 n2 n3 value [params] -> not supported, cannot tell from (4) without further analysis
// (5) Cname n1 n2 n3 model C=value [other params]
// (6) Cname n1 n2 value model [params]
// (7) Cname n1 n2 n3 model value [params]
// (8) Cname n1 n2 n3 value model [params]
auto rv = pv.find (element);
if (rv != pv.end ()) {
// value given by parameter
value = rv->second.to_double ();
if (nn.size () >= 3) {
// Rname n1 n2 model [params]
// Rname n1 n2 n3 model [params]
model = nn.back ();
nn.pop_back ();
bool has_value = false;
if (nn.size () == 2) {
if (rv != pv.end ()) {
value = rv->second.to_double (); // (1)
has_value = true;
}
} else if (nn.size () >= 3) {
} else if (nn.size () == 3) {
if (try_read_value (nn.back (), value, variables)) {
// Rname n1 n2 value
// Rname n1 n2 n3 value
has_value = true; // (2)
nn.pop_back ();
} else {
// Rname n1 n2 value model [params]
// Rname n1 n2 n3 value model [params]
model = nn.back (); // (3)
nn.pop_back ();
if (rv != pv.end ()) {
value = rv->second.to_double ();
has_value = true;
}
}
} else if (nn.size () == 4) {
if (try_read_value (nn.back (), value, variables)) {
has_value = true; // (4)
nn.pop_back ();
} else if (rv != pv.end ()) {
value = rv->second.to_double (); // (5)
has_value = true;
model = nn.back ();
nn.pop_back ();
if (! try_read_value (nn.back (), value, variables)) {
error (tl::to_string (tr ("Can't find a value for a R, C or L device")));
} else {
nn.pop_back ();
}
} else if (try_read_value (nn[2], value, variables)) {
has_value = true; // (6)
model = nn.back ();
nn.pop_back ();
nn.pop_back ();
} else {
model = nn.back (); // fall back to (5)
nn.pop_back ();
}
} else {
if (try_read_value (nn.back (), value, variables)) {
has_value = true; // (7)
nn.pop_back ();
model = nn.back ();
nn.pop_back ();
} else if (try_read_value (nn[3], value, variables)) {
has_value = true; // (8)
model = nn.back ();
nn.pop_back ();
nn.pop_back ();
}
}
if (rv != pv.end ()) {
pv.erase (rv);
}
if (! has_value) {
error (tl::to_string (tr ("Can't find a value for a R, C or L device")));
}
} else {
@ -653,6 +690,13 @@ NetlistSpiceReaderDelegate::read_value (tl::Extractor &ex, const std::map<std::s
return parser.read (ex);
}
tl::Variant
NetlistSpiceReaderDelegate::read_value (tl::Extractor &ex, const std::map<std::string, tl::Variant> &variables1, const std::map<std::string, tl::Variant> &variables2)
{
NetlistSpiceReaderExpressionParser parser (&variables1, &variables2);
return parser.read (ex);
}
bool
NetlistSpiceReaderDelegate::try_read_value (const std::string &s, double &v, const std::map<std::string, tl::Variant> &variables)
{

View File

@ -154,6 +154,11 @@ public:
*/
static tl::Variant read_value(tl::Extractor &ex, const std::map<std::string, tl::Variant> &variables);
/**
* @brief Reads a value from the extractor (with formula evaluation and two levels of variables)
*/
static tl::Variant read_value(tl::Extractor &ex, const std::map<std::string, tl::Variant> &variables1, const std::map<std::string, tl::Variant> &variables2);
/**
* @brief Tries to read a value from the extractor (with formula evaluation)
*/

View File

@ -47,8 +47,15 @@ static bool to_bool (const tl::Variant &v)
NetlistSpiceReaderExpressionParser::NetlistSpiceReaderExpressionParser (const variables_type *vars, double def_scale)
: m_def_scale (def_scale)
{
static variables_type empty_variables;
mp_variables = vars ? vars : &empty_variables;
mp_variables1 = vars;
mp_variables2 = 0;
}
NetlistSpiceReaderExpressionParser::NetlistSpiceReaderExpressionParser (const variables_type *vars1, const variables_type *vars2, double def_scale)
: m_def_scale (def_scale)
{
mp_variables1 = vars1;
mp_variables2 = vars2;
}
// expression syntax taken from ngspice:
@ -264,13 +271,20 @@ NetlistSpiceReaderExpressionParser::read_atomic_value (tl::Extractor &ex, bool *
} else {
auto vi = mp_variables->find (var);
if (vi != mp_variables->end ()) {
return vi->second;
} else {
// keep word as string value
return tl::Variant (var);
if (mp_variables1) {
auto vi = mp_variables1->find (var);
if (vi != mp_variables1->end ()) {
return vi->second;
}
}
if (mp_variables2) {
auto vi = mp_variables2->find (var);
if (vi != mp_variables2->end ()) {
return vi->second;
}
}
// keep word as string value
return tl::Variant (var);
}

View File

@ -46,6 +46,7 @@ public:
typedef std::map<std::string, tl::Variant> variables_type;
NetlistSpiceReaderExpressionParser (const variables_type *vars, double def_scale = 1.0);
NetlistSpiceReaderExpressionParser (const variables_type *vars1, const variables_type *vars2, double def_scale = 1.0);
tl::Variant read (tl::Extractor &ex) const;
tl::Variant read (const std::string &s) const;
@ -53,7 +54,7 @@ public:
bool try_read (const std::string &s, tl::Variant &v) const;
private:
const variables_type *mp_variables;
const variables_type *mp_variables1, *mp_variables2;
double m_def_scale;
tl::Variant read_atomic_value (tl::Extractor &ex, bool *status) const;

View File

@ -752,6 +752,90 @@ TEST(19_ngspice_ref)
);
}
// using parameters evaluated before inside formulas
TEST(19b_ngspice_ref)
{
db::Netlist nl;
std::string path = tl::combine_path (tl::combine_path (tl::testdata (), "algo"), "nreader19b.cir");
db::NetlistSpiceReader reader;
tl::InputStream is (path);
reader.read (is, nl);
EXPECT_EQ (nl.to_string (),
"circuit .TOP ();\n"
" subcircuit 'PMOS4_STANDARD(L=0.15,NF=4,V=1.5)' XPMOS (D=Q,G=I,S=VDD,B=VDD);\n"
" subcircuit 'NMOS4_STANDARD(L=0.15,NF=4,V=1.5)' XNMOS (D=Q,G=I,S=VSS,B=VSS);\n"
" subcircuit 'NMOS4_STANDARD(L=0.15,NF=2,V=1.5)' XDUMMY0 (D=VSS,G=VSS,S=VSS,B=VSS);\n"
" subcircuit 'NMOS4_STANDARD(L=0.15,NF=2,V=1.5)' XDUMMY1 (D=VSS,G=VSS,S=VSS,B=VSS);\n"
" subcircuit 'PMOS4_STANDARD(L=0.15,NF=2,V=1.5)' XDUMMY2 (D=VDD,G=VDD,S=VDD,B=VDD);\n"
" subcircuit 'PMOS4_STANDARD(L=0.15,NF=2,V=1.5)' XDUMMY3 (D=VDD,G=VDD,S=VDD,B=VDD);\n"
"end;\n"
"circuit 'PMOS4_STANDARD(L=0.15,NF=4,V=1.5)' (D=D,G=G,S=S,B=B);\n"
" device SKY130_FD_PR__PFET_01V8 M1 (S=S,G=G,D=D,B=B) (L=0.15,W=6,AS=0.32625,AD=0.2175,PS=3.99,PD=2.66);\n"
"end;\n"
"circuit 'NMOS4_STANDARD(L=0.15,NF=4,V=1.5)' (D=D,G=G,S=S,B=B);\n"
" device SKY130_FD_PR__NFET_01V8 M1 (S=S,G=G,D=D,B=B) (L=0.15,W=6,AS=0.32625,AD=0.2175,PS=3.99,PD=2.66);\n"
"end;\n"
"circuit 'NMOS4_STANDARD(L=0.15,NF=2,V=1.5)' (D=D,G=G,S=S,B=B);\n"
" device SKY130_FD_PR__NFET_01V8 M1 (S=S,G=G,D=D,B=B) (L=0.15,W=3,AS=0.435,AD=0.2175,PS=4.16,PD=2.08);\n"
"end;\n"
"circuit 'PMOS4_STANDARD(L=0.15,NF=2,V=1.5)' (D=D,G=G,S=S,B=B);\n"
" device SKY130_FD_PR__PFET_01V8 M1 (S=S,G=G,D=D,B=B) (L=0.15,W=3,AS=0.435,AD=0.2175,PS=4.16,PD=2.08);\n"
"end;\n"
);
}
// issue #1319, clarification
TEST(20_precendence)
{
db::Netlist nl;
std::string path = tl::combine_path (tl::combine_path (tl::testdata (), "algo"), "nreader20.cir");
db::NetlistSpiceReader reader;
tl::InputStream is (path);
reader.read (is, nl);
EXPECT_EQ (nl.to_string (),
"circuit TEST ();\n"
" device CAP '1' (A=A,B=B) (C=5e-12,A=0,P=0);\n"
" device CAP '2A' (A=A,B=B) (C=2.5e-12,A=0,P=0);\n"
" device CAP '2B' (A=A,B=B) (C=2.5e-12,A=0,P=0);\n"
" device CAP_MODEL1 '3' (A=A,B=B) (C=5e-12,A=0,P=0);\n"
" device CAP3 '4' (A=A,B=B,W=CAP_MODEL1) (C=2.5e-12,A=0,P=0);\n"
" device CAP_MODEL2 '5' (A=A,B=B,W=C) (C=5e-12,A=0,P=0);\n"
" device CAP_MODEL1 '6' (A=A,B=B) (C=2.5e-12,A=0,P=0);\n"
" device CAP_MODEL2 '7A' (A=A,B=B,W=C) (C=2.5e-12,A=0,P=0);\n"
" device CAP_MODEL2 '7B' (A=A,B=B,W=C) (C=2.5e-12,A=0,P=0);\n"
" device CAP_MODEL2 '8' (A=A,B=B,W=C) (C=2.5e-12,A=0,P=0);\n"
"end;\n"
);
}
// issue #1320, .lib support
TEST(21_lib)
{
db::Netlist nl;
std::string path = tl::combine_path (tl::combine_path (tl::testdata (), "algo"), "nreader21.cir");
db::NetlistSpiceReader reader;
tl::InputStream is (path);
reader.read (is, nl);
EXPECT_EQ (nl.to_string (),
"circuit .TOP ();\n"
" device CAP '10' (A='1',B='2') (C=1e-12,A=0,P=0);\n"
" device CAP '1' (A='1',B='2') (C=1e-10,A=0,P=0);\n"
" device CAP '2A' (A='1',B='2') (C=1.01e-10,A=0,P=0);\n"
" device CAP '2B' (A='1',B='2') (C=1.02e-10,A=0,P=0);\n"
" device CAP '100' (A='1',B='2') (C=1.5e-11,A=0,P=0);\n"
"end;\n"
);
}
TEST(100_ExpressionParser)
{
std::map<std::string, tl::Variant> vars;

29
testdata/algo/nreader19b.cir vendored Normal file
View File

@ -0,0 +1,29 @@
* Test
.options scale=1e-6
.model sky130_fd_pr__pfet_01v8 NMOS level=8 version=3.3.0
.model sky130_fd_pr__nfet_01v8 NMOS level=8 version=3.3.0
XXpmos Q I VDD VDD pmos4_standard v=1.5 l=0.15 nf=4
XXnmos Q I VSS VSS nmos4_standard v=1.5 l=0.15 nf=4
XXDUMMY0 VSS VSS VSS VSS nmos4_standard v=1.5 l=0.15 nf=2
XXDUMMY1 VSS VSS VSS VSS nmos4_standard v=1.5 l=0.15 nf=2
XXDUMMY2 VDD VDD VDD VDD pmos4_standard v=1.5 l=0.15 nf=2
XXDUMMY3 VDD VDD VDD VDD pmos4_standard v=1.5 l=0.15 nf=2
* NOTE: "W" in the "ad" formula uses the previously computed parameter "W"
.subckt pmos4_standard D G S B v=0.1 l=0.018 nf=4
MM1 D G S B sky130_fd_pr__pfet_01v8 L=l W='v * nf ' ad='int((nf+1)/2) * W/nf**2 * 0.29' as='int((nf+2)/2) * W/nf**2 * 0.29'
+ pd='2*int((nf+1)/2) * (W/nf**2 + 0.29)' ps='2*int((nf+2)/2) * (W/nf**2 + 0.29)' nrd='0.29 / W' nrs='0.29 / W'
+ m=1
.ends
* NOTE: "W" in the "ad" formula uses the previously computed parameter "W"
.subckt nmos4_standard D G S B v=0.1 l=0.018 nf=4
MM1 D G S B sky130_fd_pr__nfet_01v8 L=l W='v * nf ' ad='int((nf+1)/2) * W/nf**2 * 0.29' as='int((nf+2)/2) * W/nf**2 * 0.29'
+ pd='2*int((nf+1)/2) * (W/nf**2 + 0.29)' ps='2*int((nf+2)/2) * (W/nf**2 + 0.29)' nrd='0.29 / W' nrs='0.29 / W'
+ m=1
.ends
.end

37
testdata/algo/nreader20.cir vendored Normal file
View File

@ -0,0 +1,37 @@
.subckt test
* Variations are:
* (1) Cname n1 n2 C=value [other params]
* (2) Cname n1 n2 value [params]
* (3) Cname n1 n2 model C=value [other params]
* Cname n1 n2 n3 C=value [other params] -> not supported, cannot tell from (3) without further analysis
* (4) Cname n1 n2 model value [params]
* Cname n1 n2 n3 value [params] -> not supported, cannot tell from (4) without further analysis
* (5) Cname n1 n2 n3 model C=value [other params]
* (6) Cname n1 n2 value model [params]
* (7) Cname n1 n2 n3 model value [params]
* (8) Cname n1 n2 n3 value model [params]
* ngspice takes second C parameter (5p)
c1 a b M=1 l=50.000u w=50.000u c=2.5p c=5p
* ngspice ignores C parameter and uses 2.5p
c2a a b M=1 l=50.000u w=50.000u 2.5p c=5p
* ngspice ignores C parameter and uses 2.5p
c2b a b 2.5p M=1 l=50.000u w=50.000u c=5p
* ngspice ignores first C parameter and uses 5p
c3 a b cap_model1 c=2.5p M=1 l=50.000u w=50.000u c=5p
* ngspice ignores C parameter and uses 2.5p
c4 a b cap_model1 2.5p M=1 l=50.000u w=50.000u c=5p
* ngspice ignores first C parameter and uses 5p
c5 a b c cap_model2 M=1 l=50.000u w=50.000u c=2.5p c=5p
* C parameter must not be present for this to be recognized as value
c6 a b 2.5p cap_model1 M=1 l=50.000u w=50.000u
* ngspice ignores C parameter and uses 2.5p
c7a a b c cap_model2 2.5p M=1 l=50.000u w=50.000u c=5p
* ngspice ignores C parameter and uses 2.5p
c7b a b c cap_model2 M=1 l=50.000u w=50.000u 2.5p c=5p
* ngspice ignores C parameter and uses 2.5p
c8 a b c 2.5p cap_model2 M=1 l=50.000u w=50.000u c=5p
.ends

6
testdata/algo/nreader21.cir vendored Normal file
View File

@ -0,0 +1,6 @@
.lib nreader21_lib1.cir lib1
.lib nreader21_lib1.cir lib2
C100 1 2 15p

4
testdata/algo/nreader21_inc.cir vendored Normal file
View File

@ -0,0 +1,4 @@
C2a 1 2 101p
C2b 1 2 102p

10
testdata/algo/nreader21_lib1.cir vendored Normal file
View File

@ -0,0 +1,10 @@
.lib lib1
.lib nreader21_lib2.cir lib3
C1 1 2 100p
.endl
.lib lib2
.include nreader21_inc.cir
.endl

11
testdata/algo/nreader21_lib2.cir vendored Normal file
View File

@ -0,0 +1,11 @@
.lib lib3
C10 1 2 1p
.endl
C11 1 2 1.5p
.lib lib4
C12 1 2 42p
.endl