mirror of https://github.com/KLayout/klayout.git
Spice reader: Support for resistance, capacitance and inductance values within parameters, basic support for 3-terminal resistors, more flexibility in SpiceReaderDelegate.
This commit is contained in:
parent
ea3bfabd90
commit
4eb8f69a22
|
|
@ -40,6 +40,62 @@ namespace db
|
|||
|
||||
// ------------------------------------------------------------------------------------------------------
|
||||
|
||||
static const char *allowed_name_chars = "_.:,!+$/&\\#[]|<>";
|
||||
|
||||
inline static int hex_num (char c)
|
||||
{
|
||||
if (c >= '0' && c <= '9') {
|
||||
return (int (c - '0'));
|
||||
} else if (c >= 'a' && c <= 'f') {
|
||||
return (int (c - 'f') + 10);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static std::string unescape_name (const std::string &n)
|
||||
{
|
||||
std::string nn;
|
||||
nn.reserve (n.size ());
|
||||
|
||||
const char *cp = n.c_str ();
|
||||
while (*cp) {
|
||||
|
||||
if (*cp == '\\' && cp[1]) {
|
||||
|
||||
if (tolower (cp[1]) == 'x') {
|
||||
|
||||
cp += 2;
|
||||
|
||||
char c = 0;
|
||||
for (int i = 0; i < 2 && *cp; ++i) {
|
||||
int n = hex_num (*cp);
|
||||
if (n >= 0) {
|
||||
++cp;
|
||||
c = c * 16 + char (n);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
nn += c;
|
||||
|
||||
} else {
|
||||
++cp;
|
||||
nn += *cp++;
|
||||
}
|
||||
|
||||
} else {
|
||||
nn += *cp++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nn;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------------
|
||||
|
||||
NetlistSpiceReaderDelegate::NetlistSpiceReaderDelegate ()
|
||||
{
|
||||
// .. nothing yet ..
|
||||
|
|
@ -60,11 +116,21 @@ void NetlistSpiceReaderDelegate::finish (db::Netlist * /*netlist*/)
|
|||
// .. nothing yet ..
|
||||
}
|
||||
|
||||
bool NetlistSpiceReaderDelegate::control_statement(const std::string & /*line*/)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NetlistSpiceReaderDelegate::wants_subcircuit (const std::string & /*circuit_name*/)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string NetlistSpiceReaderDelegate::translate_net_name (const std::string &nn)
|
||||
{
|
||||
return unescape_name (nn);
|
||||
}
|
||||
|
||||
void NetlistSpiceReaderDelegate::error (const std::string &msg)
|
||||
{
|
||||
throw tl::Exception (msg);
|
||||
|
|
@ -87,6 +153,270 @@ static db::DeviceClass *make_device_class (db::Circuit *circuit, const std::stri
|
|||
return cls;
|
||||
}
|
||||
|
||||
static std::string parse_component (tl::Extractor &ex)
|
||||
{
|
||||
const char *cp = ex.skip ();
|
||||
const char *cp0 = cp;
|
||||
|
||||
char quote = 0;
|
||||
unsigned int brackets = 0;
|
||||
|
||||
while (*cp) {
|
||||
if (quote) {
|
||||
if (*cp == quote) {
|
||||
quote = 0;
|
||||
} else if (*cp == '\\' && cp[1]) {
|
||||
++cp;
|
||||
}
|
||||
} else if ((isspace (*cp) || *cp == '=') && ! brackets) {
|
||||
break;
|
||||
} else if (*cp == '"' || *cp == '\'') {
|
||||
quote = *cp;
|
||||
} else if (*cp == '(') {
|
||||
++brackets;
|
||||
} else if (*cp == ')') {
|
||||
if (brackets > 0) {
|
||||
--brackets;
|
||||
}
|
||||
}
|
||||
++cp;
|
||||
}
|
||||
|
||||
ex = tl::Extractor (cp);
|
||||
return std::string (cp0, cp - cp0);
|
||||
}
|
||||
|
||||
void NetlistSpiceReaderDelegate::parse_element_components (const std::string &s, std::vector<std::string> &strings, std::map<std::string, double> &pv)
|
||||
{
|
||||
tl::Extractor ex (s.c_str ());
|
||||
bool in_params = false;
|
||||
|
||||
while (! ex.at_end ()) {
|
||||
|
||||
if (ex.test_without_case ("params:")) {
|
||||
|
||||
in_params = true;
|
||||
|
||||
} else {
|
||||
|
||||
tl::Extractor ex0 = ex;
|
||||
std::string n;
|
||||
|
||||
if (ex.try_read_word (n) && ex.test ("=")) {
|
||||
// a parameter. Note that parameter names are always made upper case.
|
||||
pv.insert (std::make_pair (tl::to_upper_case (n), read_value (ex)));
|
||||
} else {
|
||||
ex = ex0;
|
||||
if (in_params) {
|
||||
ex.error (tl::to_string (tr ("Invalid syntax for parameter assignment - needs keyword followed by '='")));
|
||||
}
|
||||
strings.push_back (parse_component (ex));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
double NetlistSpiceReaderDelegate::read_atomic_value (tl::Extractor &ex)
|
||||
{
|
||||
if (ex.test ("(")) {
|
||||
|
||||
double v = read_dot_expr (ex);
|
||||
ex.expect (")");
|
||||
return v;
|
||||
|
||||
} else {
|
||||
|
||||
double v = 0.0;
|
||||
ex.read (v);
|
||||
|
||||
double f = 1.0;
|
||||
if (*ex == 't' || *ex == 'T') {
|
||||
f = 1e12;
|
||||
} else if (*ex == 'g' || *ex == 'G') {
|
||||
f = 1e9;
|
||||
} else if (*ex == 'k' || *ex == 'K') {
|
||||
f = 1e3;
|
||||
} else if (*ex == 'm' || *ex == 'M') {
|
||||
f = 1e-3;
|
||||
if (ex.test_without_case ("meg")) {
|
||||
f = 1e6;
|
||||
}
|
||||
} else if (*ex == 'u' || *ex == 'U') {
|
||||
f = 1e-6;
|
||||
} else if (*ex == 'n' || *ex == 'N') {
|
||||
f = 1e-9;
|
||||
} else if (*ex == 'p' || *ex == 'P') {
|
||||
f = 1e-12;
|
||||
} else if (*ex == 'f' || *ex == 'F') {
|
||||
f = 1e-15;
|
||||
} else if (*ex == 'a' || *ex == 'A') {
|
||||
f = 1e-18;
|
||||
}
|
||||
while (*ex && isalpha (*ex)) {
|
||||
++ex;
|
||||
}
|
||||
|
||||
v *= f;
|
||||
return v;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
double NetlistSpiceReaderDelegate::read_bar_expr (tl::Extractor &ex)
|
||||
{
|
||||
double v = read_atomic_value (ex);
|
||||
while (true) {
|
||||
if (ex.test ("+")) {
|
||||
double vv = read_atomic_value (ex);
|
||||
v += vv;
|
||||
} else if (ex.test ("+")) {
|
||||
double vv = read_atomic_value (ex);
|
||||
v -= vv;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
double NetlistSpiceReaderDelegate::read_dot_expr (tl::Extractor &ex)
|
||||
{
|
||||
double v = read_bar_expr (ex);
|
||||
while (true) {
|
||||
if (ex.test ("*")) {
|
||||
double vv = read_bar_expr (ex);
|
||||
v *= vv;
|
||||
} else if (ex.test ("/")) {
|
||||
double vv = read_bar_expr (ex);
|
||||
v /= vv;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
double NetlistSpiceReaderDelegate::read_value (tl::Extractor &ex)
|
||||
{
|
||||
return read_dot_expr (ex);
|
||||
}
|
||||
|
||||
bool NetlistSpiceReaderDelegate::try_read_value (const std::string &s, double &value)
|
||||
{
|
||||
tl::Extractor ve (s.c_str ());
|
||||
double vv = 0;
|
||||
if (ve.try_read (vv) || ve.test ("(")) {
|
||||
ve = tl::Extractor (s.c_str ());
|
||||
value = read_value (ve);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void NetlistSpiceReaderDelegate::parse_element (const std::string &s, const std::string &element, std::string &model, double &value, std::vector<std::string> &nn, std::map<std::string, double> &pv)
|
||||
{
|
||||
parse_element_components (s, nn, pv);
|
||||
|
||||
// interpret the parameters according to the code
|
||||
if (element == "X") {
|
||||
|
||||
// subcircuit call:
|
||||
// Xname n1 n2 ... nn circuit [params]
|
||||
|
||||
if (nn.empty ()) {
|
||||
error (tl::to_string (tr ("No circuit name given for subcircuit call")));
|
||||
}
|
||||
|
||||
model = nn.back ();
|
||||
nn.pop_back ();
|
||||
|
||||
} else if (element == "R" || element == "C" || element == "L") {
|
||||
|
||||
// resistor, cap, inductor: two-terminal devices with a value
|
||||
// Rname n1 n2 value
|
||||
// Rname n1 n2 n3 value
|
||||
// Rname n1 n2 value model [params]
|
||||
// Rname n1 n2 n3 value model [params]
|
||||
// Rname n1 n2 [params]
|
||||
// Rname n1 n2 model [params]
|
||||
// Rname n1 n2 n3 model [params]
|
||||
// NOTE: there is no "Rname n1 n2 n3 [params]"!
|
||||
// (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")));
|
||||
}
|
||||
|
||||
std::map<std::string, double>::const_iterator rv = pv.find (element);
|
||||
if (rv != pv.end ()) {
|
||||
|
||||
// value given by parameter
|
||||
value = rv->second;
|
||||
|
||||
if (nn.size () >= 3) {
|
||||
// Rname n1 n2 model [params]
|
||||
// Rname n1 n2 n3 model [params]
|
||||
model = nn.back ();
|
||||
nn.pop_back ();
|
||||
}
|
||||
|
||||
} else if (nn.size () >= 3) {
|
||||
|
||||
if (try_read_value (nn.back (), value)) {
|
||||
|
||||
// Rname n1 n2 value
|
||||
// Rname n1 n2 n3 value
|
||||
nn.pop_back ();
|
||||
|
||||
} else {
|
||||
|
||||
// Rname n1 n2 value model [params]
|
||||
// Rname n1 n2 n3 value model [params]
|
||||
model = nn.back ();
|
||||
nn.pop_back ();
|
||||
if (! try_read_value (nn.back (), value)) {
|
||||
error (tl::to_string (tr ("Can't find a value for a R, C or L device")));
|
||||
} else {
|
||||
nn.pop_back ();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// others: n-terminal devices with a model (last node)
|
||||
|
||||
if (nn.empty ()) {
|
||||
error (tl::sprintf (tl::to_string (tr ("No model name given for element '%s'")), element));
|
||||
}
|
||||
|
||||
model = nn.back ();
|
||||
nn.pop_back ();
|
||||
|
||||
if (element == "M") {
|
||||
if (nn.size () != 4) {
|
||||
error (tl::to_string (tr ("'M' element must have four nodes")));
|
||||
}
|
||||
} else if (element == "Q") {
|
||||
if (nn.size () != 3 && nn.size () != 4) {
|
||||
error (tl::to_string (tr ("'Q' element must have three or four nodes")));
|
||||
}
|
||||
} else if (element == "D") {
|
||||
if (nn.size () != 2) {
|
||||
error (tl::to_string (tr ("'D' element must have two nodes")));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: other devices?
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::string &element, const std::string &name, const std::string &model, double value, const std::vector<db::Net *> &nets, const std::map<std::string, double> &pv)
|
||||
{
|
||||
std::map<std::string, double> params = pv;
|
||||
|
|
@ -106,15 +436,30 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin
|
|||
|
||||
if (element == "R") {
|
||||
|
||||
if (cls) {
|
||||
if (! dynamic_cast<db::DeviceClassResistor *>(cls)) {
|
||||
error (tl::sprintf (tl::to_string (tr ("Class %s not a resistor device class as required by 'R' element")), cn));
|
||||
if (nets.size () == 2) {
|
||||
if (cls) {
|
||||
if (! dynamic_cast<db::DeviceClassResistor *>(cls)) {
|
||||
error (tl::sprintf (tl::to_string (tr ("Class %s is not a resistor device class as required by 'R' element")), cn));
|
||||
}
|
||||
} else {
|
||||
if (cn.empty ()) {
|
||||
cn = "RES";
|
||||
}
|
||||
cls = make_device_class<db::DeviceClassResistor> (circuit, cn);
|
||||
}
|
||||
} else if (nets.size () == 3) {
|
||||
if (cls) {
|
||||
if (! dynamic_cast<db::DeviceClassResistorWithBulk *>(cls)) {
|
||||
error (tl::sprintf (tl::to_string (tr ("Class %s is not a three-terminal resistor device class as required by 'R' element")), cn));
|
||||
}
|
||||
} else {
|
||||
if (cn.empty ()) {
|
||||
cn = "RES";
|
||||
}
|
||||
cls = make_device_class<db::DeviceClassResistorWithBulk> (circuit, cn);
|
||||
}
|
||||
} else {
|
||||
if (cn.empty ()) {
|
||||
cn = "RES";
|
||||
}
|
||||
cls = make_device_class<db::DeviceClassResistor> (circuit, cn);
|
||||
error (tl::to_string (tr ("A 'R' element requires two or three nets")));
|
||||
}
|
||||
|
||||
// Apply multiplier
|
||||
|
|
@ -122,15 +467,19 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin
|
|||
|
||||
} else if (element == "L") {
|
||||
|
||||
if (cls) {
|
||||
if (! dynamic_cast<db::DeviceClassInductor *>(cls)) {
|
||||
error (tl::sprintf (tl::to_string (tr ("Class %s not a inductor device class as required by 'L' element")), cn));
|
||||
if (nets.size () == 2) {
|
||||
if (cls) {
|
||||
if (! dynamic_cast<db::DeviceClassInductor *>(cls)) {
|
||||
error (tl::sprintf (tl::to_string (tr ("Class %s is not a inductor device class as required by 'L' element")), cn));
|
||||
}
|
||||
} else {
|
||||
if (cn.empty ()) {
|
||||
cn = "IND";
|
||||
}
|
||||
cls = make_device_class<db::DeviceClassInductor> (circuit, cn);
|
||||
}
|
||||
} else {
|
||||
if (cn.empty ()) {
|
||||
cn = "IND";
|
||||
}
|
||||
cls = make_device_class<db::DeviceClassInductor> (circuit, cn);
|
||||
error (tl::to_string (tr ("A 'L' element requires two nets")));
|
||||
}
|
||||
|
||||
// Apply multiplier
|
||||
|
|
@ -138,15 +487,30 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin
|
|||
|
||||
} else if (element == "C") {
|
||||
|
||||
if (cls) {
|
||||
if (! dynamic_cast<db::DeviceClassCapacitor *>(cls)) {
|
||||
error (tl::sprintf (tl::to_string (tr ("Class %s not a capacitor device class as required by 'C' element")), cn));
|
||||
if (nets.size () == 2) {
|
||||
if (cls) {
|
||||
if (! dynamic_cast<db::DeviceClassCapacitor *>(cls)) {
|
||||
error (tl::sprintf (tl::to_string (tr ("Class %s is not a capacitor device class as required by 'C' element")), cn));
|
||||
}
|
||||
} else {
|
||||
if (cn.empty ()) {
|
||||
cn = "CAP";
|
||||
}
|
||||
cls = make_device_class<db::DeviceClassCapacitor> (circuit, cn);
|
||||
}
|
||||
} else if (nets.size () == 3) {
|
||||
if (cls) {
|
||||
if (! dynamic_cast<db::DeviceClassCapacitorWithBulk *>(cls)) {
|
||||
error (tl::sprintf (tl::to_string (tr ("Class %s is not a three-terminal capacitor device class as required by 'C' element")), cn));
|
||||
}
|
||||
} else {
|
||||
if (cn.empty ()) {
|
||||
cn = "CAP";
|
||||
}
|
||||
cls = make_device_class<db::DeviceClassCapacitorWithBulk> (circuit, cn);
|
||||
}
|
||||
} else {
|
||||
if (cn.empty ()) {
|
||||
cn = "CAP";
|
||||
}
|
||||
cls = make_device_class<db::DeviceClassCapacitor> (circuit, cn);
|
||||
error (tl::to_string (tr ("A 'C' element requires two or three nets")));
|
||||
}
|
||||
|
||||
// Apply multiplier
|
||||
|
|
@ -156,7 +520,7 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin
|
|||
|
||||
if (cls) {
|
||||
if (! dynamic_cast<db::DeviceClassDiode *>(cls)) {
|
||||
error (tl::sprintf (tl::to_string (tr ("Class %s not a diode device class as required by 'D' element")), cn));
|
||||
error (tl::sprintf (tl::to_string (tr ("Class %s is not a diode device class as required by 'D' element")), cn));
|
||||
}
|
||||
} else {
|
||||
if (cn.empty ()) {
|
||||
|
|
@ -179,11 +543,11 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin
|
|||
} else if (cls) {
|
||||
if (nets.size () == 3) {
|
||||
if (! dynamic_cast<db::DeviceClassBJT3Transistor *>(cls)) {
|
||||
error (tl::sprintf (tl::to_string (tr ("Class %s not a 3-terminal BJT device class as required by 'Q' element")), cn));
|
||||
error (tl::sprintf (tl::to_string (tr ("Class %s is not a 3-terminal BJT device class as required by 'Q' element")), cn));
|
||||
}
|
||||
} else {
|
||||
if (! dynamic_cast<db::DeviceClassBJT4Transistor *>(cls)) {
|
||||
error (tl::sprintf (tl::to_string (tr ("Class %s not a 4-terminal BJT device class as required by 'Q' element")), cn));
|
||||
error (tl::sprintf (tl::to_string (tr ("Class %s is not a 4-terminal BJT device class as required by 'Q' element")), cn));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -211,7 +575,7 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin
|
|||
|
||||
if (cls) {
|
||||
if (! dynamic_cast<db::DeviceClassMOS4Transistor *>(cls)) {
|
||||
error (tl::sprintf (tl::to_string (tr ("Class %s not a 4-terminal MOS device class as required by 'M' element")), cn));
|
||||
error (tl::sprintf (tl::to_string (tr ("Class %s is not a 4-terminal MOS device class as required by 'M' element")), cn));
|
||||
}
|
||||
} else {
|
||||
if (nets.size () == 4) {
|
||||
|
|
@ -271,8 +635,6 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin
|
|||
|
||||
// ------------------------------------------------------------------------------------------------------
|
||||
|
||||
static const char *allowed_name_chars = "_.:,!+$/&\\#[]|<>";
|
||||
|
||||
NetlistSpiceReader::NetlistSpiceReader (NetlistSpiceReaderDelegate *delegate)
|
||||
: mp_netlist (0), mp_stream (), mp_delegate (delegate)
|
||||
{
|
||||
|
|
@ -450,7 +812,8 @@ std::string NetlistSpiceReader::get_line ()
|
|||
tl::Extractor ex (l.c_str ());
|
||||
if (ex.test_without_case (".include") || ex.test_without_case (".inc")) {
|
||||
|
||||
std::string path = read_name_with_case (ex);
|
||||
std::string path;
|
||||
ex.read_word_or_quoted (path, allowed_name_chars);
|
||||
|
||||
push_stream (path);
|
||||
|
||||
|
|
@ -504,7 +867,7 @@ bool NetlistSpiceReader::read_card ()
|
|||
} else if (ex.test_without_case ("global")) {
|
||||
|
||||
while (! ex.at_end ()) {
|
||||
std::string n = read_name (ex);
|
||||
std::string n = mp_delegate->translate_net_name (read_name (ex));
|
||||
if (m_global_net_names.find (n) == m_global_net_names.end ()) {
|
||||
m_global_nets.push_back (n);
|
||||
m_global_net_names.insert (n);
|
||||
|
|
@ -528,7 +891,7 @@ bool NetlistSpiceReader::read_card ()
|
|||
|
||||
// ignore end statements
|
||||
|
||||
} else {
|
||||
} else if (! mp_delegate->control_statement (l)) {
|
||||
|
||||
std::string s;
|
||||
ex.read_word (s);
|
||||
|
|
@ -551,8 +914,6 @@ bool NetlistSpiceReader::read_card ()
|
|||
warn (tl::sprintf (tl::to_string (tr ("Element type '%c' ignored")), next_char));
|
||||
}
|
||||
|
||||
ex.expect_end ();
|
||||
|
||||
} else {
|
||||
warn (tl::to_string (tr ("Line ignored")));
|
||||
}
|
||||
|
|
@ -571,91 +932,6 @@ void NetlistSpiceReader::warn (const std::string &msg)
|
|||
tl::warn << fmt_msg;
|
||||
}
|
||||
|
||||
double NetlistSpiceReader::read_atomic_value (tl::Extractor &ex)
|
||||
{
|
||||
if (ex.test ("(")) {
|
||||
|
||||
double v = read_dot_expr (ex);
|
||||
ex.expect (")");
|
||||
return v;
|
||||
|
||||
} else {
|
||||
|
||||
double v = 0.0;
|
||||
ex.read (v);
|
||||
|
||||
double f = 1.0;
|
||||
if (*ex == 't' || *ex == 'T') {
|
||||
f = 1e12;
|
||||
} else if (*ex == 'g' || *ex == 'G') {
|
||||
f = 1e9;
|
||||
} else if (*ex == 'k' || *ex == 'K') {
|
||||
f = 1e3;
|
||||
} else if (*ex == 'm' || *ex == 'M') {
|
||||
f = 1e-3;
|
||||
if (ex.test_without_case ("meg")) {
|
||||
f = 1e6;
|
||||
}
|
||||
} else if (*ex == 'u' || *ex == 'U') {
|
||||
f = 1e-6;
|
||||
} else if (*ex == 'n' || *ex == 'N') {
|
||||
f = 1e-9;
|
||||
} else if (*ex == 'p' || *ex == 'P') {
|
||||
f = 1e-12;
|
||||
} else if (*ex == 'f' || *ex == 'F') {
|
||||
f = 1e-15;
|
||||
} else if (*ex == 'a' || *ex == 'A') {
|
||||
f = 1e-18;
|
||||
}
|
||||
while (*ex && isalpha (*ex)) {
|
||||
++ex;
|
||||
}
|
||||
|
||||
v *= f;
|
||||
return v;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
double NetlistSpiceReader::read_bar_expr (tl::Extractor &ex)
|
||||
{
|
||||
double v = read_atomic_value (ex);
|
||||
while (true) {
|
||||
if (ex.test ("+")) {
|
||||
double vv = read_atomic_value (ex);
|
||||
v += vv;
|
||||
} else if (ex.test ("+")) {
|
||||
double vv = read_atomic_value (ex);
|
||||
v -= vv;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
double NetlistSpiceReader::read_dot_expr (tl::Extractor &ex)
|
||||
{
|
||||
double v = read_atomic_value (ex);
|
||||
while (true) {
|
||||
if (ex.test ("*")) {
|
||||
double vv = read_atomic_value (ex);
|
||||
v *= vv;
|
||||
} else if (ex.test ("/")) {
|
||||
double vv = read_atomic_value (ex);
|
||||
v /= vv;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
double NetlistSpiceReader::read_value (tl::Extractor &ex)
|
||||
{
|
||||
return read_dot_expr (ex);
|
||||
}
|
||||
|
||||
void NetlistSpiceReader::ensure_circuit ()
|
||||
{
|
||||
if (! mp_circuit) {
|
||||
|
|
@ -693,92 +969,11 @@ db::Net *NetlistSpiceReader::make_net (const std::string &name)
|
|||
return net;
|
||||
}
|
||||
|
||||
void NetlistSpiceReader::read_pin_and_parameters (tl::Extractor &ex, std::vector<std::string> &nn, std::map<std::string, double> &pv)
|
||||
{
|
||||
bool in_params = false;
|
||||
|
||||
while (! ex.at_end ()) {
|
||||
|
||||
if (ex.test_without_case ("params:")) {
|
||||
|
||||
in_params = true;
|
||||
|
||||
} else {
|
||||
|
||||
std::string n = read_name (ex);
|
||||
|
||||
if (ex.test ("=")) {
|
||||
// a parameter
|
||||
pv.insert (std::make_pair (n, read_value (ex)));
|
||||
} else {
|
||||
if (in_params) {
|
||||
error (tl::to_string (tr ("Missing '=' in parameter assignment")));
|
||||
}
|
||||
nn.push_back (n);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
inline static int hex_num (char c)
|
||||
{
|
||||
if (c >= '0' && c <= '9') {
|
||||
return (int (c - '0'));
|
||||
} else if (c >= 'a' && c <= 'f') {
|
||||
return (int (c - 'f') + 10);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
std::string NetlistSpiceReader::read_name_with_case (tl::Extractor &ex)
|
||||
std::string NetlistSpiceReader::read_name (tl::Extractor &ex)
|
||||
{
|
||||
std::string n;
|
||||
ex.read_word_or_quoted (n, allowed_name_chars);
|
||||
|
||||
std::string nn;
|
||||
nn.reserve (n.size ());
|
||||
const char *cp = n.c_str ();
|
||||
while (*cp) {
|
||||
|
||||
if (*cp == '\\' && cp[1]) {
|
||||
|
||||
if (tolower (cp[1]) == 'x') {
|
||||
|
||||
cp += 2;
|
||||
|
||||
char c = 0;
|
||||
for (int i = 0; i < 2 && *cp; ++i) {
|
||||
int n = hex_num (*cp);
|
||||
if (n >= 0) {
|
||||
++cp;
|
||||
c = c * 16 + char (n);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
nn += c;
|
||||
|
||||
} else {
|
||||
++cp;
|
||||
nn += *cp++;
|
||||
}
|
||||
|
||||
} else {
|
||||
nn += *cp++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nn;
|
||||
}
|
||||
|
||||
std::string NetlistSpiceReader::read_name (tl::Extractor &ex)
|
||||
{
|
||||
return mp_netlist->normalize_name (read_name_with_case (ex));
|
||||
return mp_netlist->normalize_name (n);
|
||||
}
|
||||
|
||||
bool NetlistSpiceReader::read_element (tl::Extractor &ex, const std::string &element, const std::string &name)
|
||||
|
|
@ -786,99 +981,16 @@ bool NetlistSpiceReader::read_element (tl::Extractor &ex, const std::string &ele
|
|||
// generic parse
|
||||
std::vector<std::string> nn;
|
||||
std::map<std::string, double> pv;
|
||||
|
||||
std::string model;
|
||||
double value = 0.0;
|
||||
|
||||
// interpret the parameters according to the code
|
||||
if (element == "X") {
|
||||
mp_delegate->parse_element (ex.skip (), element, model, value, nn, pv);
|
||||
|
||||
// subcircuit call:
|
||||
// Xname n1 n2 ... nn circuit [params]
|
||||
|
||||
read_pin_and_parameters (ex, nn, pv);
|
||||
|
||||
if (nn.empty ()) {
|
||||
error (tl::to_string (tr ("No circuit name given for subcircuit call")));
|
||||
}
|
||||
|
||||
model = nn.back ();
|
||||
nn.pop_back ();
|
||||
|
||||
} else if (element == "R" || element == "C" || element == "L") {
|
||||
|
||||
// resistor, cap, inductor: two-terminal devices with a value
|
||||
// Rname n1 n2 value
|
||||
// Rname n1 n2 value model [params]
|
||||
// Rname n1 n2 model [params]
|
||||
// (same for C, L instead of R)
|
||||
|
||||
while (! ex.at_end () && nn.size () < 2) {
|
||||
nn.push_back (read_name (ex));
|
||||
}
|
||||
|
||||
if (nn.size () != 2) {
|
||||
error (tl::to_string (tr ("Two-terminal device needs two nets")));
|
||||
}
|
||||
|
||||
tl::Extractor ve (ex);
|
||||
double vv = 0.0;
|
||||
if (ve.try_read (vv) || ve.test ("(")) {
|
||||
value = read_value (ex);
|
||||
}
|
||||
|
||||
while (! ex.at_end ()) {
|
||||
std::string n = read_name (ex);
|
||||
if (ex.test ("=")) {
|
||||
pv [n] = read_value (ex);
|
||||
} else if (! model.empty ()) {
|
||||
error (tl::sprintf (tl::to_string (tr ("Too many arguments for two-terminal device (additional argumen is '%s')")), n));
|
||||
} else {
|
||||
model = n;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// others: n-terminal devices with a model (last node)
|
||||
|
||||
while (! ex.at_end ()) {
|
||||
std::string n = read_name (ex);
|
||||
if (ex.test ("=")) {
|
||||
pv [n] = read_value (ex);
|
||||
} else {
|
||||
nn.push_back (n);
|
||||
}
|
||||
}
|
||||
|
||||
if (nn.empty ()) {
|
||||
error (tl::sprintf (tl::to_string (tr ("No model name given for element '%s'")), element));
|
||||
}
|
||||
|
||||
model = nn.back ();
|
||||
nn.pop_back ();
|
||||
|
||||
if (element == "M") {
|
||||
if (nn.size () != 4) {
|
||||
error (tl::to_string (tr ("'M' element must have four nodes")));
|
||||
}
|
||||
} else if (element == "Q") {
|
||||
if (nn.size () != 3 && nn.size () != 4) {
|
||||
error (tl::to_string (tr ("'Q' element must have three or four nodes")));
|
||||
}
|
||||
} else if (element == "D") {
|
||||
if (nn.size () != 2) {
|
||||
error (tl::to_string (tr ("'D' element must have two nodes")));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: other devices?
|
||||
|
||||
}
|
||||
model = mp_netlist->normalize_name (model);
|
||||
|
||||
std::vector<db::Net *> nets;
|
||||
for (std::vector<std::string>::const_iterator i = nn.begin (); i != nn.end (); ++i) {
|
||||
nets.push_back (make_net (*i));
|
||||
nets.push_back (make_net (mp_delegate->translate_net_name (mp_netlist->normalize_name (*i))));
|
||||
}
|
||||
|
||||
if (element == "X" && ! subcircuit_captured (model)) {
|
||||
|
|
@ -946,7 +1058,11 @@ void NetlistSpiceReader::read_circuit (tl::Extractor &ex, const std::string &nc)
|
|||
{
|
||||
std::vector<std::string> nn;
|
||||
std::map<std::string, double> pv;
|
||||
read_pin_and_parameters (ex, nn, pv);
|
||||
mp_delegate->parse_element_components (ex.skip (), nn, pv);
|
||||
|
||||
for (std::vector<std::string>::iterator i = nn.begin (); i != nn.end (); ++i) {
|
||||
*i = mp_delegate->translate_net_name (mp_netlist->normalize_name (*i));
|
||||
}
|
||||
|
||||
if (! pv.empty ()) {
|
||||
warn (tl::to_string (tr ("Circuit parameters are not allowed currently")));
|
||||
|
|
@ -999,8 +1115,6 @@ void NetlistSpiceReader::read_circuit (tl::Extractor &ex, const std::string &nc)
|
|||
|
||||
mp_nets_by_name.reset (n2n.release ());
|
||||
std::swap (cc, mp_circuit);
|
||||
|
||||
ex.expect_end ();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,6 +78,13 @@ public:
|
|||
*/
|
||||
virtual void finish (db::Netlist *netlist);
|
||||
|
||||
/**
|
||||
* @brief Called when an unknown control statement is encountered
|
||||
*
|
||||
* Returns true if the statement is understood.
|
||||
*/
|
||||
virtual bool control_statement (const std::string &line);
|
||||
|
||||
/**
|
||||
* @brief Returns true, if the delegate wants subcircuit elements with this name
|
||||
*
|
||||
|
|
@ -85,6 +92,13 @@ public:
|
|||
*/
|
||||
virtual bool wants_subcircuit (const std::string &circuit_name);
|
||||
|
||||
/**
|
||||
* @brief This method translates a raw net name to a valid net name
|
||||
*
|
||||
* The default implementation will unescape backslash sequences into plain characters.
|
||||
*/
|
||||
virtual std::string translate_net_name (const std::string &nn);
|
||||
|
||||
/**
|
||||
* @brief Makes a device from an element line
|
||||
*
|
||||
|
|
@ -103,10 +117,42 @@ public:
|
|||
*/
|
||||
virtual bool element (db::Circuit *circuit, const std::string &element, const std::string &name, const std::string &model, double value, const std::vector<db::Net *> &nets, const std::map<std::string, double> ¶ms);
|
||||
|
||||
/**
|
||||
* @brief Parses an element from a line
|
||||
*
|
||||
* @param s The line to parse (the part after the element and name)
|
||||
* @param model Out parameter: the model name if given
|
||||
* @param value Out parameter: the value if given (for R, L, C)
|
||||
* @param nn Out parameter: the net names
|
||||
* @param pv Out parameter: the parameter values (key/value pairs)
|
||||
*/
|
||||
virtual void parse_element (const std::string &s, const std::string &element, std::string &model, double &value, std::vector<std::string> &nn, std::map<std::string, double> &pv);
|
||||
|
||||
/**
|
||||
* @brief Produces an error with the given message
|
||||
*/
|
||||
virtual void error (const std::string &msg);
|
||||
|
||||
/**
|
||||
* @brief Reads a set of string components and parameters from the string
|
||||
* A special key "param:" is recognized for starting a parameter list.
|
||||
*/
|
||||
void parse_element_components (const std::string &s, std::vector<std::string> &strings, std::map<std::string, double> &pv);
|
||||
|
||||
/**
|
||||
* @brief Reads a value from the extractor (with formula evaluation)
|
||||
*/
|
||||
double read_value (tl::Extractor &ex);
|
||||
|
||||
/**
|
||||
* @brief Tries to read a value from the extractor (with formula evaluation)
|
||||
*/
|
||||
bool try_read_value (const std::string &s, double &v);
|
||||
|
||||
private:
|
||||
double read_atomic_value (tl::Extractor &ex);
|
||||
double read_dot_expr (tl::Extractor &ex);
|
||||
double read_bar_expr (tl::Extractor &ex);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -138,18 +184,12 @@ private:
|
|||
void push_stream (const std::string &path);
|
||||
void pop_stream ();
|
||||
bool at_end ();
|
||||
void read_pin_and_parameters (tl::Extractor &ex, std::vector<std::string> &nn, std::map<std::string, double> &pv);
|
||||
bool read_element (tl::Extractor &ex, const std::string &element, const std::string &name);
|
||||
void read_subcircuit (const std::string &sc_name, const std::string &nc_name, const std::vector<db::Net *> &nets);
|
||||
void read_circuit (tl::Extractor &ex, const std::string &name);
|
||||
void skip_circuit (tl::Extractor &ex);
|
||||
bool read_card ();
|
||||
double read_value (tl::Extractor &ex);
|
||||
std::string read_name_with_case (tl::Extractor &ex);
|
||||
std::string read_name (tl::Extractor &ex);
|
||||
double read_atomic_value (tl::Extractor &ex);
|
||||
double read_dot_expr (tl::Extractor &ex);
|
||||
double read_bar_expr (tl::Extractor &ex);
|
||||
std::string get_line ();
|
||||
void unget_line (const std::string &l);
|
||||
void error (const std::string &msg);
|
||||
|
|
|
|||
|
|
@ -2122,6 +2122,54 @@ Class<db::NetlistReader> db_NetlistReader ("db", "NetlistReader",
|
|||
"@hide\n"
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief A helper class wrapping the return values for NetlistSpiceReaderDelegateImpl::parse_element
|
||||
*/
|
||||
class ParseElementData
|
||||
{
|
||||
public:
|
||||
ParseElementData () : m_value (0.0) { }
|
||||
|
||||
const std::string &model_name () const { return m_model; }
|
||||
std::string &model_name_nc () { return m_model; }
|
||||
void set_model_name (const std::string &model) { m_model = model; }
|
||||
double value () const { return m_value; }
|
||||
double &value_nc () { return m_value; }
|
||||
void set_value (double value) { m_value = value; }
|
||||
const std::vector<std::string> &net_names () const { return m_net_names; }
|
||||
std::vector<std::string> &net_names_nc () { return m_net_names; }
|
||||
void set_net_names (const std::vector<std::string> &nn) { m_net_names = nn; }
|
||||
const std::map<std::string, double> ¶meters () const { return m_parameters; }
|
||||
std::map<std::string, double> ¶meters_nc () { return m_parameters; }
|
||||
void set_parameters (const std::map<std::string, double> ¶meters) { m_parameters = parameters; }
|
||||
|
||||
private:
|
||||
std::string m_model;
|
||||
double m_value;
|
||||
std::vector<std::string> m_net_names;
|
||||
std::map<std::string, double> m_parameters;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A helper class for the return values of NetlistSpiceReaderDelegateImpl::parse_element_components
|
||||
*/
|
||||
class ParseElementComponentsData
|
||||
{
|
||||
public:
|
||||
ParseElementComponentsData () { }
|
||||
|
||||
const std::vector<std::string> &strings () const { return m_strings; }
|
||||
std::vector<std::string> &strings_nc () { return m_strings; }
|
||||
void set_strings (const std::vector<std::string> &nn) { m_strings = nn; }
|
||||
const std::map<std::string, double> ¶meters () const { return m_parameters; }
|
||||
std::map<std::string, double> ¶meters_nc () { return m_parameters; }
|
||||
void set_parameters (const std::map<std::string, double> ¶meters) { m_parameters = parameters; }
|
||||
|
||||
private:
|
||||
std::vector<std::string> m_strings;
|
||||
std::map<std::string, double> m_parameters;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A SPICE reader delegate base class for reimplementation
|
||||
*/
|
||||
|
|
@ -2180,6 +2228,25 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
virtual bool control_statement (const std::string &line)
|
||||
{
|
||||
try {
|
||||
m_error.clear ();
|
||||
if (cb_control_statement.can_issue ()) {
|
||||
return cb_control_statement.issue<db::NetlistSpiceReaderDelegate, bool, const std::string &> (&db::NetlistSpiceReaderDelegate::control_statement, line);
|
||||
} else {
|
||||
return db::NetlistSpiceReaderDelegate::control_statement (line);
|
||||
}
|
||||
} catch (tl::Exception &) {
|
||||
if (! m_error.empty ()) {
|
||||
db::NetlistSpiceReaderDelegate::error (m_error);
|
||||
} else {
|
||||
throw;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool wants_subcircuit (const std::string &circuit_name)
|
||||
{
|
||||
try {
|
||||
|
|
@ -2199,6 +2266,59 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
virtual std::string translate_net_name (const std::string &nn)
|
||||
{
|
||||
try {
|
||||
m_error.clear ();
|
||||
if (cb_translate_net_name.can_issue ()) {
|
||||
return cb_translate_net_name.issue<db::NetlistSpiceReaderDelegate, std::string, const std::string &> (&db::NetlistSpiceReaderDelegate::translate_net_name, nn);
|
||||
} else {
|
||||
return db::NetlistSpiceReaderDelegate::translate_net_name (nn);
|
||||
}
|
||||
} catch (tl::Exception &) {
|
||||
if (! m_error.empty ()) {
|
||||
db::NetlistSpiceReaderDelegate::error (m_error);
|
||||
} else {
|
||||
throw;
|
||||
}
|
||||
return std::string ();
|
||||
}
|
||||
}
|
||||
|
||||
ParseElementData parse_element_helper (const std::string &s, const std::string &element)
|
||||
{
|
||||
ParseElementData data;
|
||||
db::NetlistSpiceReaderDelegate::parse_element (s, element, data.model_name_nc (), data.value_nc (), data.net_names_nc (), data.parameters_nc ());
|
||||
return data;
|
||||
}
|
||||
|
||||
virtual void parse_element (const std::string &s, const std::string &element, std::string &model, double &value, std::vector<std::string> &nn, std::map<std::string, double> &pv)
|
||||
{
|
||||
try {
|
||||
|
||||
m_error.clear ();
|
||||
|
||||
ParseElementData data;
|
||||
if (cb_parse_element.can_issue ()) {
|
||||
data = cb_parse_element.issue<NetlistSpiceReaderDelegateImpl, ParseElementData, const std::string &, const std::string &> (&NetlistSpiceReaderDelegateImpl::parse_element_helper, s, element);
|
||||
} else {
|
||||
data = parse_element_helper (s, element);
|
||||
}
|
||||
|
||||
model = data.model_name ();
|
||||
value = data.value ();
|
||||
nn = data.net_names ();
|
||||
pv = data.parameters ();
|
||||
|
||||
} catch (tl::Exception &) {
|
||||
if (! m_error.empty ()) {
|
||||
db::NetlistSpiceReaderDelegate::error (m_error);
|
||||
} else {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool element (db::Circuit *circuit, const std::string &element, const std::string &name, const std::string &model, double value, const std::vector<db::Net *> &nets, const std::map<std::string, double> ¶ms)
|
||||
{
|
||||
try {
|
||||
|
|
@ -2220,38 +2340,128 @@ public:
|
|||
|
||||
gsi::Callback cb_start;
|
||||
gsi::Callback cb_finish;
|
||||
gsi::Callback cb_control_statement;
|
||||
gsi::Callback cb_wants_subcircuit;
|
||||
gsi::Callback cb_translate_net_name;
|
||||
gsi::Callback cb_element;
|
||||
gsi::Callback cb_parse_element;
|
||||
|
||||
private:
|
||||
std::string m_error;
|
||||
};
|
||||
|
||||
static void start_fb (db::NetlistSpiceReaderDelegate *delegate, db::Netlist *netlist)
|
||||
static void start_fb (NetlistSpiceReaderDelegateImpl *delegate, db::Netlist *netlist)
|
||||
{
|
||||
delegate->db::NetlistSpiceReaderDelegate::start (netlist);
|
||||
}
|
||||
|
||||
static void finish_fb (db::NetlistSpiceReaderDelegate *delegate, db::Netlist *netlist)
|
||||
static void finish_fb (NetlistSpiceReaderDelegateImpl *delegate, db::Netlist *netlist)
|
||||
{
|
||||
delegate->db::NetlistSpiceReaderDelegate::finish (netlist);
|
||||
}
|
||||
|
||||
static bool wants_subcircuit_fb (db::NetlistSpiceReaderDelegate *delegate, const std::string &model)
|
||||
static bool wants_subcircuit_fb (NetlistSpiceReaderDelegateImpl *delegate, const std::string &model)
|
||||
{
|
||||
return delegate->db::NetlistSpiceReaderDelegate::wants_subcircuit (model);
|
||||
}
|
||||
|
||||
static bool element_fb (db::NetlistSpiceReaderDelegate *delegate, db::Circuit *circuit, const std::string &element, const std::string &name, const std::string &model, double value, const std::vector<db::Net *> &nets, const std::map<std::string, double> ¶ms)
|
||||
static bool control_statement_fb (NetlistSpiceReaderDelegateImpl *delegate, const std::string &line)
|
||||
{
|
||||
return delegate->db::NetlistSpiceReaderDelegate::control_statement (line);
|
||||
}
|
||||
|
||||
static std::string translate_net_name_fb (NetlistSpiceReaderDelegateImpl *delegate, const std::string &name)
|
||||
{
|
||||
return delegate->db::NetlistSpiceReaderDelegate::translate_net_name (name);
|
||||
}
|
||||
|
||||
static bool element_fb (NetlistSpiceReaderDelegateImpl *delegate, db::Circuit *circuit, const std::string &element, const std::string &name, const std::string &model, double value, const std::vector<db::Net *> &nets, const std::map<std::string, double> ¶ms)
|
||||
{
|
||||
return delegate->db::NetlistSpiceReaderDelegate::element (circuit, element, name, model, value, nets, params);
|
||||
}
|
||||
|
||||
static ParseElementData parse_element_fb (NetlistSpiceReaderDelegateImpl *delegate, const std::string &s, const std::string &element)
|
||||
{
|
||||
return delegate->parse_element_helper (s, element);
|
||||
}
|
||||
|
||||
static tl::Variant value_from_string (NetlistSpiceReaderDelegateImpl *delegate, const std::string &s)
|
||||
{
|
||||
tl::Variant res;
|
||||
double v = 0.0;
|
||||
if (delegate->try_read_value (s, v)) {
|
||||
res = v;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static ParseElementComponentsData parse_element_components (NetlistSpiceReaderDelegateImpl *delegate, const std::string &s)
|
||||
{
|
||||
ParseElementComponentsData data;
|
||||
delegate->parse_element_components (s, data.strings_nc (), data.parameters_nc ());
|
||||
return data;
|
||||
}
|
||||
|
||||
Class<ParseElementComponentsData> db_ParseElementComponentsData ("db", "ParseElementComponentsData",
|
||||
gsi::method ("strings", &ParseElementComponentsData::strings,
|
||||
"@brief Gets the string parameters\n"
|
||||
) +
|
||||
gsi::method ("strings=", &ParseElementComponentsData::set_strings, gsi::arg ("list"),
|
||||
"@brief Sets the string parameters\n"
|
||||
) +
|
||||
gsi::method ("parameters", &ParseElementComponentsData::parameters,
|
||||
"@brief Gets the (named) numerical parameters\n"
|
||||
) +
|
||||
gsi::method ("parameters=", &ParseElementComponentsData::set_parameters, gsi::arg ("dict"),
|
||||
"@brief Sets the (named) numerical parameters\n"
|
||||
),
|
||||
"@brief Supplies the return value for \\NetlistSpiceReaderDelegate#parse_element_components.\n"
|
||||
"This is a structure with two members: 'strings' for the string arguments and 'parameters' for the "
|
||||
"named numerical arguments.\n"
|
||||
"\n"
|
||||
"This helper class has been introduced in version 0.27.1.\n"
|
||||
);
|
||||
|
||||
Class<ParseElementData> db_ParseElementData ("db", "ParseElementData",
|
||||
gsi::method ("value", &ParseElementData::value,
|
||||
"@brief Gets the value\n"
|
||||
) +
|
||||
gsi::method ("value=", &ParseElementData::set_value, gsi::arg ("v"),
|
||||
"@brief Sets the value\n"
|
||||
) +
|
||||
gsi::method ("model_name", &ParseElementData::model_name,
|
||||
"@brief Gets the model name\n"
|
||||
) +
|
||||
gsi::method ("model_name=", &ParseElementData::set_model_name, gsi::arg ("m"),
|
||||
"@brief Sets the model name\n"
|
||||
) +
|
||||
gsi::method ("net_names", &ParseElementData::net_names,
|
||||
"@brief Gets the net names\n"
|
||||
) +
|
||||
gsi::method ("net_names=", &ParseElementData::set_net_names, gsi::arg ("list"),
|
||||
"@brief Sets the net names\n"
|
||||
) +
|
||||
gsi::method ("parameters", &ParseElementData::parameters,
|
||||
"@brief Gets the (named) numerical parameters\n"
|
||||
) +
|
||||
gsi::method ("parameters=", &ParseElementData::set_parameters, gsi::arg ("dict"),
|
||||
"@brief Sets the (named) numerical parameters\n"
|
||||
),
|
||||
"@brief Supplies the return value for \\NetlistSpiceReaderDelegate#parse_element.\n"
|
||||
"This is a structure with four members: 'model_name' for the model name, 'value' for the default numerical value, 'net_names' for the net names and 'parameters' for the "
|
||||
"named numerical parameters.\n"
|
||||
"\n"
|
||||
"This helper class has been introduced in version 0.27.1.\n"
|
||||
);
|
||||
|
||||
Class<NetlistSpiceReaderDelegateImpl> db_NetlistSpiceReaderDelegate ("db", "NetlistSpiceReaderDelegate",
|
||||
gsi::method_ext ("start", &start_fb, "@hide") +
|
||||
gsi::method_ext ("finish", &finish_fb, "@hide") +
|
||||
gsi::method_ext ("wants_subcircuit", &wants_subcircuit_fb, "@hide") +
|
||||
gsi::method_ext ("element", &element_fb, "@hide") +
|
||||
gsi::method_ext ("parse_element", &parse_element_fb, "@hide") +
|
||||
gsi::method_ext ("control_statement", &control_statement_fb, "@hide") +
|
||||
gsi::method_ext ("translate_net_name", &translate_net_name_fb, "@hide") +
|
||||
gsi::callback ("start", &NetlistSpiceReaderDelegateImpl::start, &NetlistSpiceReaderDelegateImpl::cb_start, gsi::arg ("netlist"),
|
||||
"@brief This method is called when the reader starts reading a netlist\n"
|
||||
) +
|
||||
|
|
@ -2262,6 +2472,36 @@ Class<NetlistSpiceReaderDelegateImpl> db_NetlistSpiceReaderDelegate ("db", "Netl
|
|||
"@brief Returns true, if the delegate wants subcircuit elements with this name\n"
|
||||
"The name is always upper case.\n"
|
||||
) +
|
||||
gsi::callback ("control_statement", &NetlistSpiceReaderDelegateImpl::control_statement, &NetlistSpiceReaderDelegateImpl::cb_control_statement, gsi::arg ("line"),
|
||||
"@brief Receives control statements not understood by the standard reader\n"
|
||||
"When the reader encounters a control statement not understood by the parser, it will pass the line to the delegate using this method.\n"
|
||||
"The delegate can decide if it wants to read this statement. It should return true in this case.\n"
|
||||
"\n"
|
||||
"This method has been introduced in version 0.27.1\n"
|
||||
) +
|
||||
gsi::callback ("translate_net_name", &NetlistSpiceReaderDelegateImpl::translate_net_name, &NetlistSpiceReaderDelegateImpl::cb_translate_net_name, gsi::arg ("net_name"),
|
||||
"@brief Translates a net name from the raw net name to the true net name\n"
|
||||
"The default implementation will replace backslash sequences by the corresponding character.\n"
|
||||
"'translate_net_name' is called before a net name is turned into a net object.\n"
|
||||
"The method can be reimplemented to supply a different translation scheme for net names. For example, to translate special characters.\n"
|
||||
"\n"
|
||||
"This method has been introduced in version 0.27.1\n"
|
||||
) +
|
||||
gsi::callback ("parse_element", &NetlistSpiceReaderDelegateImpl::parse_element_helper, &NetlistSpiceReaderDelegateImpl::cb_parse_element,
|
||||
gsi::arg ("s"), gsi::arg ("element"),
|
||||
"@brief Parses an element card\n"
|
||||
"@param s The specification part of the element line (the part after element code and name).\n"
|
||||
"@param element The upper-case element code (\"M\", \"R\", ...).\n"
|
||||
"@return A \\ParseElementData object with the parts of the element.\n"
|
||||
"\n"
|
||||
"This method receives a string with the element specification and the element code. It is supposed to "
|
||||
"parse the element line and return a model name, a value, a list of net names and a parameter value dictionary.\n"
|
||||
"\n"
|
||||
"'parse_element' is called one every element card. The results of this call go into the \\element method "
|
||||
"to actually create the device. This method can be reimplemented to support other flavors of SPICE.\n"
|
||||
"\n"
|
||||
"This method has been introduced in version 0.27.1\n"
|
||||
) +
|
||||
gsi::callback ("element", &NetlistSpiceReaderDelegateImpl::element, &NetlistSpiceReaderDelegateImpl::cb_element,
|
||||
gsi::arg ("circuit"), gsi::arg ("element"), gsi::arg ("name"), gsi::arg ("model"), gsi::arg ("value"), gsi::arg ("nets"), gsi::arg ("parameters"),
|
||||
"@brief Makes a device from an element line\n"
|
||||
|
|
@ -2281,6 +2521,23 @@ Class<NetlistSpiceReaderDelegateImpl> db_NetlistSpiceReaderDelegate ("db", "Netl
|
|||
gsi::method ("error", &NetlistSpiceReaderDelegateImpl::error, gsi::arg ("msg"),
|
||||
"@brief Issues an error with the given message.\n"
|
||||
"Use this method to generate an error."
|
||||
) +
|
||||
gsi::method_ext ("value_from_string", &value_from_string, gsi::arg ("s"),
|
||||
"@brief Translates a string into a value\n"
|
||||
"This function simplifies the implementation of SPICE readers by providing a translation of a unit-annotated string "
|
||||
"into double values. For example, '1k' is translated to 1000.0. In addition, simple formula evaluation is supported, e.g "
|
||||
"'(1+3)*2' is translated into 8.0.\n"
|
||||
"\n"
|
||||
"This method has been introduced in version 0.27.1\n"
|
||||
) +
|
||||
gsi::method_ext ("parse_element_components", &parse_element_components, gsi::arg ("s"),
|
||||
"@brief Parses a string into string and parameter components.\n"
|
||||
"This method is provided for simplifying the implementation of 'parse_element'. It takes a string and splits it into "
|
||||
"string arguments and parameter values. For example, 'a b c=6' renders two string arguments in 'nn' and one parameter in pv ('C'->6.0). "
|
||||
"It returns data \\ParseElementComponentsData object with the strings and parameters.\n"
|
||||
"The parameter names are already translated to upper case.\n"
|
||||
"\n"
|
||||
"This method has been introduced in version 0.27.1\n"
|
||||
),
|
||||
"@brief Provides a delegate for the SPICE reader for translating device statements\n"
|
||||
"Supply a customized class to provide a specialized reading scheme for devices. "
|
||||
|
|
@ -2396,7 +2653,21 @@ Class<db::NetlistSpiceReader> db_NetlistSpiceReader (db_NetlistReader, "db", "Ne
|
|||
"nl.read(input_file, reader)\n"
|
||||
"@/code\n"
|
||||
"\n"
|
||||
"This class has been introduced in version 0.26."
|
||||
"A somewhat contrived example for using the delegate to translate net names is this:\n"
|
||||
"\n"
|
||||
"@code\n"
|
||||
"class MyDelegate < RBA::NetlistSpiceReaderDelegate\n"
|
||||
"\n"
|
||||
" # translates 'VDD' to 'VXX' and leave all other net names as is:\n"
|
||||
" alias translate_net_name_org translate_net_name\n"
|
||||
" def translate_net_name(n)\n"
|
||||
" return n == \"VDD\" ? \"VXX\" : translate_net_name_org(n)}\n"
|
||||
" end\n"
|
||||
"\n"
|
||||
"end\n"
|
||||
"@/code\n"
|
||||
"\n"
|
||||
"This class has been introduced in version 0.26. It has been extended in version 0.27.1."
|
||||
);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,6 +74,34 @@ class MyNetlistSpiceReaderDelegate < RBA::NetlistSpiceReaderDelegate
|
|||
|
||||
end
|
||||
|
||||
class MyNetlistSpiceReaderDelegate2 < MyNetlistSpiceReaderDelegate
|
||||
|
||||
def start(netlist)
|
||||
netlist.description = "Read by MyDelegate2"
|
||||
end
|
||||
|
||||
def finish(netlist)
|
||||
netlist.description = "Read by MyDelegate2 (sucessfully)"
|
||||
end
|
||||
|
||||
alias translate_net_name_org translate_net_name
|
||||
|
||||
def translate_net_name(n)
|
||||
return n == "VDD" ? "VXX" : translate_net_name_org(n)
|
||||
end
|
||||
|
||||
alias parse_element_org parse_element
|
||||
|
||||
def parse_element(s, element)
|
||||
data = parse_element_org(s, element)
|
||||
if element == "R"
|
||||
data.model_name = "WIDERSTAND"
|
||||
end
|
||||
data
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class DBNetlistReaderTests_TestClass < TestBase
|
||||
|
||||
def test_1_Basic
|
||||
|
|
@ -107,6 +135,37 @@ END
|
|||
|
||||
end
|
||||
|
||||
def test_1b_Basic
|
||||
|
||||
nl = RBA::Netlist::new
|
||||
|
||||
input = File.join($ut_testsrc, "testdata", "algo", "nreader6.cir")
|
||||
|
||||
mydelegate = MyNetlistSpiceReaderDelegate2::new
|
||||
reader = RBA::NetlistSpiceReader::new(mydelegate)
|
||||
# the delegate is kept by the SPICE writer ..
|
||||
mydelegate = nil
|
||||
GC.start
|
||||
nl.read(input, reader)
|
||||
|
||||
assert_equal(nl.description, "Read by MyDelegate2 (sucessfully)")
|
||||
|
||||
assert_equal(nl.to_s, <<"END")
|
||||
circuit SUBCKT ($1=$1,A=A,VXX=VXX,Z=Z,GND=GND,GND$1=GND$1);
|
||||
device HVPMOS $1 (S=VXX,G=$3,D=Z,B=$1) (L=0.3,W=1.5,AS=0.27,AD=0.27,PS=3.24,PD=3.24);
|
||||
device HVPMOS $2 (S=VXX,G=A,D=$3,B=$1) (L=0.3,W=1.5,AS=0.27,AD=0.27,PS=3.24,PD=3.24);
|
||||
device HVNMOS $3 (S=GND,G=$3,D=GND,B=GND$1) (L=1.695,W=3.18,AS=0,AD=0,PS=9,PD=9);
|
||||
device HVNMOS $4 (S=GND,G=$3,D=Z,B=GND$1) (L=0.6,W=0.6,AS=0.285,AD=0.285,PS=1.74,PD=1.74);
|
||||
device HVNMOS $5 (S=GND,G=A,D=$3,B=GND$1) (L=0.6,W=0.6,AS=0.285,AD=0.285,PS=2.64,PD=2.64);
|
||||
device WIDERSTAND $1 (A=A,B=Z) (R=100000,L=0,W=0,A=0,P=0);
|
||||
end;
|
||||
circuit .TOP ();
|
||||
subcircuit SUBCKT SUBCKT ($1=IN,A=OUT,VXX=VXX,Z=Z,GND=VSS,GND$1=VSS);
|
||||
end;
|
||||
END
|
||||
|
||||
end
|
||||
|
||||
def test_2_WithError
|
||||
|
||||
nl = RBA::Netlist::new
|
||||
|
|
@ -136,6 +195,50 @@ END
|
|||
|
||||
end
|
||||
|
||||
def test_3_delegateHelpers
|
||||
|
||||
dg = RBA::NetlistSpiceReaderDelegate::new
|
||||
assert_equal(dg.value_from_string("xy").inspect, "nil")
|
||||
assert_equal(dg.value_from_string("17.5").inspect, "17.5")
|
||||
assert_equal(dg.value_from_string("1k").inspect, "1000.0")
|
||||
assert_equal(dg.value_from_string("1pF*2.5").inspect, "2.5e-12")
|
||||
assert_equal(dg.value_from_string("(1+3)*2").inspect, "8.0")
|
||||
|
||||
end
|
||||
|
||||
def test_4_ParseElementData
|
||||
|
||||
pd = RBA::ParseElementData::new
|
||||
pd.model_name = "a"
|
||||
assert_equal(pd.model_name, "a")
|
||||
pd.value = 42
|
||||
assert_equal(pd.value, 42)
|
||||
pd.net_names = [ "x", "y", "z" ]
|
||||
assert_equal(pd.net_names.join(","), "x,y,z")
|
||||
pd.parameters = { "A" => 17.5, "B" => 1 }
|
||||
assert_equal(pd.parameters.inspect, "{\"A\"=>17.5, \"B\"=>1.0}")
|
||||
|
||||
end
|
||||
|
||||
def test_5_ParseElementComponentsData
|
||||
|
||||
pd = RBA::ParseElementComponentsData::new
|
||||
pd.strings = [ "x", "y", "z" ]
|
||||
assert_equal(pd.strings.join(","), "x,y,z")
|
||||
pd.parameters = { "A" => 17.5, "B" => 1 }
|
||||
assert_equal(pd.parameters.inspect, "{\"A\"=>17.5, \"B\"=>1.0}")
|
||||
|
||||
end
|
||||
|
||||
def test_6_delegateHelpers2
|
||||
|
||||
dg = RBA::NetlistSpiceReaderDelegate::new
|
||||
pd = dg.parse_element_components("17 5 1e-9 a=17 b=1k")
|
||||
assert_equal(pd.strings.join(","), "17,5,1e-9")
|
||||
assert_equal(pd.parameters.inspect, "{\"A\"=>17.0, \"B\"=>1000.0}")
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
load("test_epilogue.rb")
|
||||
|
|
|
|||
Loading…
Reference in New Issue