mirror of
https://github.com/ra3xdh/qucs_s
synced 2025-03-28 21:13:26 +00:00
224 lines
5.4 KiB
C++
224 lines
5.4 KiB
C++
#include <QPlainTextEdit>
|
|
#include <QPushButton>
|
|
#include <QCheckBox>
|
|
#include <QLabel>
|
|
#include <QVBoxLayout>
|
|
#include <QHBoxLayout>
|
|
#include <QMessageBox>
|
|
|
|
#include "misc.h"
|
|
#include "component.h"
|
|
|
|
#include "fillfromspicedialog.h"
|
|
|
|
fillFromSpiceDialog::fillFromSpiceDialog(Component *pc, QWidget *w)
|
|
: QDialog(w)
|
|
{
|
|
Comp = pc;
|
|
ModelLevel = 1;
|
|
|
|
edtModel = new QPlainTextEdit;
|
|
QLabel *lblModel = new QLabel(tr("Insert .MODEL text here"));
|
|
btnOK = new QPushButton(tr("OK"));
|
|
connect(btnOK,SIGNAL(clicked(bool)),this,SLOT(slotOK()));
|
|
btnCancel = new QPushButton(tr("Cancel"));
|
|
connect(btnCancel,SIGNAL(clicked(bool)),this,SLOT(reject()));
|
|
chbNumNotation = new QCheckBox(tr("Convert number notation"));
|
|
|
|
QVBoxLayout *top = new QVBoxLayout;
|
|
top->addWidget(lblModel);
|
|
top->addWidget(edtModel,4);
|
|
top->addWidget(chbNumNotation);
|
|
QHBoxLayout *l1 = new QHBoxLayout;
|
|
l1->addWidget(btnOK);
|
|
l1->addWidget(btnCancel);
|
|
l1->addStretch();
|
|
top->addLayout(l1);
|
|
|
|
this->setLayout(top);
|
|
this->setMinimumWidth(400);
|
|
this->setWindowTitle(tr("Import SPICE model"));
|
|
|
|
}
|
|
|
|
int fillFromSpiceDialog::parseModelcard()
|
|
{
|
|
QString ModelCard = edtModel->toPlainText();
|
|
QStringList lstModelCard = ModelCard.split("\n");
|
|
ModelCard.clear();
|
|
for (auto &line : lstModelCard) { // assemble model in one line
|
|
if (line.startsWith("*")) continue; // comment
|
|
if (line.trimmed().isEmpty()) continue;
|
|
if (line.at(0) == '+') line[0] = ' '; // line continuation
|
|
ModelCard += line;
|
|
}
|
|
ModelCard = ModelCard.trimmed();
|
|
ModelCard = ModelCard.toLower();
|
|
|
|
if (ModelCard.contains(".subckt")) {
|
|
return subcirFound;
|
|
}
|
|
|
|
if (!ModelCard.startsWith(".model")) {
|
|
return noModel;
|
|
}
|
|
|
|
// tokenize modelcard
|
|
QStringList tokens;
|
|
QString tok;
|
|
for (const auto &c: ModelCard) {
|
|
if (c.isLetterOrNumber() || c == '_' ||
|
|
c == '.' || c == '+' || c == '-') {
|
|
tok += c;
|
|
continue;
|
|
}
|
|
if (c == '(' || c == ')' || c == '=') {
|
|
if (!tok.isEmpty())
|
|
tokens.append(tok);
|
|
tok = c;
|
|
tokens.append(tok);
|
|
tok.clear();
|
|
continue;
|
|
}
|
|
if (c.isSpace()) {
|
|
if (!tok.isEmpty())
|
|
tokens.append(tok);
|
|
tok.clear();
|
|
continue;
|
|
}
|
|
}
|
|
if (!tok.isEmpty())
|
|
tokens.append(tok);
|
|
|
|
if (tokens.count() <= 3) {
|
|
return wrongModel;
|
|
}
|
|
|
|
ModelName = tokens.at(1);
|
|
ModelType = tokens.at(2);
|
|
|
|
if (!Comp->SpiceModelcards.contains(ModelType.toUpper())) {
|
|
return modelMismatch;
|
|
}
|
|
|
|
int cnt = tokens.count();
|
|
// fill propperties list
|
|
for (int i = 0; i < tokens.count(); i++) {
|
|
if (tokens.at(i) == "=") {
|
|
int idx_name = i - 1;
|
|
int idx_val = i + 1;
|
|
if (idx_name > cnt-1 || idx_name < 0
|
|
|| idx_val > cnt-1) {
|
|
return wrongModel;
|
|
}
|
|
QString name = tokens.at(idx_name);
|
|
QString value = tokens.at(idx_val);
|
|
|
|
if (ModelType == "d") { // Convert propperty names
|
|
if (name == "cjo") name = "cj0";
|
|
}
|
|
if (ModelType == "nmos" || ModelType == "pmos") {
|
|
if (name == "vto") name = "vt0";
|
|
}
|
|
if (ModelType == "njf" || ModelType == "pjf") {
|
|
if (name == "vto") name = "vt0";
|
|
}
|
|
if (value.endsWith("meg")) {
|
|
value.chop(3);
|
|
value += "M";
|
|
}
|
|
|
|
if (ModelType == "nmos" || ModelType == "pmos") { // check MOS level
|
|
if (name == "level") {
|
|
ModelLevel = value.toInt();
|
|
QList<int> allowed_levels;
|
|
allowed_levels<<1<<2<<3<<4<<5<<6<<7<<9;
|
|
if (!allowed_levels.contains(ModelLevel)) {
|
|
return wrongLevel;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (value.size() >=2) {
|
|
// Ngspice doesn't accept numbers without leading zero
|
|
if (value.at(0) == '.' && value.at(1).isDigit()) {
|
|
value.insert(0,'0');
|
|
}
|
|
}
|
|
|
|
if (chbNumNotation->isChecked()) {
|
|
QString vv = convertNumNotation(value);
|
|
if (!vv.isEmpty()) value = vv;
|
|
}
|
|
|
|
parsedProps[name] = value;
|
|
}
|
|
}
|
|
|
|
return noError;
|
|
}
|
|
|
|
QString fillFromSpiceDialog::convertNumNotation(const QString &value)
|
|
{
|
|
QString v;
|
|
bool ok = false;
|
|
double num = value.toDouble(&ok);
|
|
if (ok) {
|
|
v = misc::num2str(num);
|
|
}
|
|
return v;
|
|
}
|
|
|
|
void fillFromSpiceDialog::fillCompProps()
|
|
{
|
|
for(Property *p : Comp->Props) {
|
|
QString name = p->Name;
|
|
name = name.toLower();
|
|
if (parsedProps.contains(name)) {
|
|
p->Value = parsedProps[name];
|
|
}
|
|
}
|
|
}
|
|
|
|
void fillFromSpiceDialog::showErrorMsg(int code)
|
|
{
|
|
QString msg;
|
|
switch (code) {
|
|
case noModel:
|
|
msg = tr("No .MODEL directive found");
|
|
break;
|
|
case modelMismatch:
|
|
msg = tr("Device type doesn't match the model type. \n");
|
|
msg += tr("Model found: ") + ModelType.toUpper() + "\n";
|
|
msg += tr("Models expected: ") + Comp->SpiceModelcards.join(" ") + "\n";
|
|
break;
|
|
case wrongModel:
|
|
msg = tr("SPICE model parse error");
|
|
break;
|
|
case subcirFound:
|
|
msg = tr("Subcircuit model (.SUBCKT) found\n"
|
|
"Modelcard (.MODEL) expected");
|
|
break;
|
|
case wrongLevel:
|
|
msg = QString(tr("Model LEVEL=%1 is not allowed for unified MOS device\n"
|
|
"Use red SPICE device from Microelectronics group\n"
|
|
"Allowed LEVELS are: 1,2,3,4,5,6,9")).arg(ModelLevel);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
QMessageBox::critical(this,tr("Error"),msg);
|
|
}
|
|
|
|
void fillFromSpiceDialog::slotOK()
|
|
{
|
|
auto r = parseModelcard();
|
|
if ( r == noError ) {
|
|
fillCompProps();
|
|
accept();
|
|
} else {
|
|
showErrorMsg(r);
|
|
reject();
|
|
}
|
|
}
|