735 lines
17 KiB
C++
735 lines
17 KiB
C++
// OpenSTA, Static Timing Analyzer
|
|
// Copyright (c) 2023, Parallax Software, Inc.
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
#include "SpefReader.hh"
|
|
|
|
#include "Zlib.hh"
|
|
#include "Report.hh"
|
|
#include "Debug.hh"
|
|
#include "StringUtil.hh"
|
|
#include "Map.hh"
|
|
#include "Transition.hh"
|
|
#include "Liberty.hh"
|
|
#include "Network.hh"
|
|
#include "PortDirection.hh"
|
|
#include "Sdc.hh"
|
|
#include "Parasitics.hh"
|
|
#include "SpefReaderPvt.hh"
|
|
#include "SpefNamespace.hh"
|
|
|
|
int
|
|
SpefParse_parse();
|
|
void
|
|
spefResetScanner();
|
|
|
|
namespace sta {
|
|
|
|
SpefReader *spef_reader;
|
|
|
|
bool
|
|
readSpefFile(const char *filename,
|
|
Instance *instance,
|
|
ParasiticAnalysisPt *ap,
|
|
bool pin_cap_included,
|
|
bool keep_coupling_caps,
|
|
float coupling_cap_factor,
|
|
ReducedParasiticType reduce_to,
|
|
bool delete_after_reduce,
|
|
const OperatingConditions *op_cond,
|
|
const Corner *corner,
|
|
const MinMax *cnst_min_max,
|
|
bool quiet,
|
|
Report *report,
|
|
Network *network,
|
|
Parasitics *parasitics)
|
|
{
|
|
bool success = false;
|
|
// Use zlib to uncompress gzip'd files automagically.
|
|
gzFile stream = gzopen(filename, "rb");
|
|
if (stream) {
|
|
SpefReader reader(filename, stream, instance, ap,
|
|
pin_cap_included, keep_coupling_caps, coupling_cap_factor,
|
|
reduce_to, delete_after_reduce, op_cond, corner,
|
|
cnst_min_max, quiet, report, network, parasitics);
|
|
spef_reader = &reader;
|
|
::spefResetScanner();
|
|
// yyparse returns 0 on success.
|
|
success = (::SpefParse_parse() == 0);
|
|
gzclose(stream);
|
|
spef_reader = nullptr;
|
|
}
|
|
else
|
|
throw FileNotReadable(filename);
|
|
return success;
|
|
}
|
|
|
|
SpefReader::SpefReader(const char *filename,
|
|
gzFile stream,
|
|
Instance *instance,
|
|
ParasiticAnalysisPt *ap,
|
|
bool pin_cap_included,
|
|
bool keep_coupling_caps,
|
|
float coupling_cap_factor,
|
|
ReducedParasiticType reduce_to,
|
|
bool delete_after_reduce,
|
|
const OperatingConditions *op_cond,
|
|
const Corner *corner,
|
|
const MinMax *cnst_min_max,
|
|
bool quiet,
|
|
Report *report,
|
|
Network *network,
|
|
Parasitics *parasitics) :
|
|
filename_(filename),
|
|
instance_(instance),
|
|
ap_(ap),
|
|
pin_cap_included_(pin_cap_included),
|
|
keep_coupling_caps_(keep_coupling_caps),
|
|
reduce_to_(reduce_to),
|
|
delete_after_reduce_(delete_after_reduce),
|
|
op_cond_(op_cond),
|
|
corner_(corner),
|
|
cnst_min_max_(cnst_min_max),
|
|
keep_device_names_(false),
|
|
quiet_(quiet),
|
|
stream_(stream),
|
|
line_(1),
|
|
// defaults
|
|
divider_('\0'),
|
|
delimiter_('\0'),
|
|
bus_brkt_left_('\0'),
|
|
bus_brkt_right_('\0'),
|
|
net_(nullptr),
|
|
report_(report),
|
|
network_(network),
|
|
parasitics_(parasitics),
|
|
triple_index_(0),
|
|
time_scale_(1.0),
|
|
cap_scale_(1.0),
|
|
res_scale_(1.0),
|
|
induct_scale_(1.0),
|
|
design_flow_(nullptr),
|
|
parasitic_(nullptr)
|
|
{
|
|
ap->setCouplingCapFactor(coupling_cap_factor);
|
|
}
|
|
|
|
SpefReader::~SpefReader()
|
|
{
|
|
if (design_flow_) {
|
|
deleteContents(design_flow_);
|
|
delete design_flow_;
|
|
design_flow_ = nullptr;
|
|
}
|
|
|
|
SpefNameMap::Iterator map_iter(name_map_);
|
|
while (map_iter.hasNext()) {
|
|
int index;
|
|
char *name;
|
|
map_iter.next(index, name);
|
|
stringDelete(name);
|
|
}
|
|
}
|
|
|
|
void
|
|
SpefReader::setDivider(char divider)
|
|
{
|
|
divider_ = divider;
|
|
}
|
|
|
|
void
|
|
SpefReader::setDelimiter(char delimiter)
|
|
{
|
|
delimiter_ = delimiter;
|
|
}
|
|
|
|
void
|
|
SpefReader::setBusBrackets(char left, char right)
|
|
{
|
|
if (!((left == '[' && right == ']')
|
|
|| (left == '{' && right == '}')
|
|
|| (left == '(' && right == ')')
|
|
|| (left == '<' && right == '>')
|
|
|| (left == ':' && right == '\0')
|
|
|| (left == '.' && right == '\0')))
|
|
warn(167, "illegal bus delimiters.");
|
|
bus_brkt_left_ = left;
|
|
bus_brkt_right_ = right;
|
|
}
|
|
|
|
Instance *
|
|
SpefReader::findInstanceRelative(const char *name)
|
|
{
|
|
return network_->findInstanceRelative(instance_, name);
|
|
}
|
|
|
|
Net *
|
|
SpefReader::findNetRelative(const char *name)
|
|
{
|
|
return network_->findNetRelative(instance_, name);
|
|
}
|
|
|
|
Pin *
|
|
SpefReader::findPinRelative(const char *name)
|
|
{
|
|
return network_->findPinRelative(instance_, name);
|
|
}
|
|
|
|
Pin *
|
|
SpefReader::findPortPinRelative(const char *name)
|
|
{
|
|
return network_->findPin(instance_, name);
|
|
}
|
|
|
|
void
|
|
SpefReader::getChars(char *buf,
|
|
int &result,
|
|
size_t max_size)
|
|
{
|
|
char *status = gzgets(stream_, buf, max_size);
|
|
if (status == Z_NULL)
|
|
result = 0; // YY_nullptr
|
|
else
|
|
result = static_cast<int>(strlen(buf));
|
|
}
|
|
|
|
void
|
|
SpefReader::getChars(char *buf,
|
|
size_t &result,
|
|
size_t max_size)
|
|
{
|
|
char *status = gzgets(stream_, buf, max_size);
|
|
if (status == Z_NULL)
|
|
result = 0; // YY_nullptr
|
|
else
|
|
result = strlen(buf);
|
|
}
|
|
|
|
char *
|
|
SpefReader::translated(const char *token)
|
|
{
|
|
return spefToSta(token, divider_, network_->pathDivider(),
|
|
network_->pathEscape());
|
|
}
|
|
|
|
void
|
|
SpefReader::incrLine()
|
|
{
|
|
line_++;
|
|
}
|
|
|
|
void
|
|
SpefReader::warn(int id, const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
report_->vfileWarn(id, filename_, line_, fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
void
|
|
SpefReader::setTimeScale(float scale,
|
|
const char *units)
|
|
{
|
|
if (stringEq(units, "NS"))
|
|
time_scale_ = scale * 1E-9F;
|
|
else if (stringEq(units, "PS"))
|
|
time_scale_ = scale * 1E-12F;
|
|
else
|
|
warn(168, "unknown units %s.", units);
|
|
stringDelete(units);
|
|
}
|
|
|
|
void
|
|
SpefReader::setCapScale(float scale,
|
|
const char *units)
|
|
{
|
|
if (stringEq(units, "PF"))
|
|
cap_scale_ = scale * 1E-12F;
|
|
else if (stringEq(units, "FF"))
|
|
cap_scale_ = scale * 1E-15F;
|
|
else
|
|
warn(168, "unknown units %s.", units);
|
|
stringDelete(units);
|
|
}
|
|
|
|
void
|
|
SpefReader::setResScale(float scale,
|
|
const char *units)
|
|
{
|
|
if (stringEq(units, "OHM"))
|
|
res_scale_ = scale;
|
|
else if (stringEq(units, "KOHM"))
|
|
res_scale_ = scale * 1E+3F;
|
|
else
|
|
warn(170, "unknown units %s.", units);
|
|
stringDelete(units);
|
|
}
|
|
|
|
void
|
|
SpefReader::setInductScale(float scale,
|
|
const char *units)
|
|
{
|
|
if (stringEq(units, "HENRY"))
|
|
induct_scale_ = scale;
|
|
else if (stringEq(units, "MH"))
|
|
induct_scale_ = scale * 1E-3F;
|
|
else if (stringEq(units, "UH"))
|
|
induct_scale_ = scale * 1E-6F;
|
|
else
|
|
warn(168, "unknown units %s.", units);
|
|
stringDelete(units);
|
|
}
|
|
|
|
void
|
|
SpefReader::makeNameMapEntry(char *index,
|
|
char *name)
|
|
{
|
|
int i = atoi(index + 1);
|
|
name_map_[i] = name;
|
|
}
|
|
|
|
char *
|
|
SpefReader::nameMapLookup(char *name)
|
|
{
|
|
if (name && name[0] == '*') {
|
|
char *mapped_name;
|
|
bool exists;
|
|
int index = atoi(name + 1);
|
|
name_map_.findKey(index, mapped_name, exists);
|
|
if (exists)
|
|
return mapped_name;
|
|
else {
|
|
warn(169, "no name map entry for %d.", index);
|
|
return nullptr;
|
|
}
|
|
}
|
|
else
|
|
return name;
|
|
}
|
|
|
|
PortDirection *
|
|
SpefReader::portDirection(char *spef_dir)
|
|
{
|
|
PortDirection *direction = PortDirection::unknown();
|
|
if (stringEq(spef_dir, "I"))
|
|
direction = PortDirection::input();
|
|
else if (stringEq(spef_dir, "O"))
|
|
direction = PortDirection::output();
|
|
else if (stringEq(spef_dir, "B"))
|
|
direction = PortDirection::bidirect();
|
|
else
|
|
warn(170, "unknown port direction %s.", spef_dir);
|
|
return direction;
|
|
}
|
|
|
|
void
|
|
SpefReader::setDesignFlow(StringSeq *flow)
|
|
{
|
|
design_flow_ = flow;
|
|
}
|
|
|
|
Pin *
|
|
SpefReader::findPin(char *name)
|
|
{
|
|
Pin *pin = nullptr;
|
|
if (name) {
|
|
char *delim = strrchr(name, delimiter_);
|
|
if (delim) {
|
|
*delim = '\0';
|
|
name = nameMapLookup(name);
|
|
if (name) {
|
|
Instance *inst = findInstanceRelative(name);
|
|
// Replace delimiter for error messages.
|
|
*delim = delimiter_;
|
|
const char *port_name = delim + 1;
|
|
if (inst) {
|
|
pin = network_->findPin(inst, port_name);
|
|
if (pin == nullptr)
|
|
warn(171, "pin %s not found.", name);
|
|
}
|
|
else
|
|
warn(172, "instance %s not found.", name);
|
|
}
|
|
}
|
|
else {
|
|
pin = findPortPinRelative(name);
|
|
if (pin == nullptr)
|
|
warn(173, "pin %s not found.", name);
|
|
}
|
|
}
|
|
return pin;
|
|
}
|
|
|
|
Net *
|
|
SpefReader::findNet(char *name)
|
|
{
|
|
Net *net = nullptr;
|
|
name = nameMapLookup(name);
|
|
if (name) {
|
|
net = findNetRelative(name);
|
|
if (net == nullptr)
|
|
warn(174, "net %s not found.", name);
|
|
}
|
|
return net;
|
|
}
|
|
|
|
void
|
|
SpefReader::rspfBegin(Net *net,
|
|
SpefTriple *total_cap)
|
|
{
|
|
if (net)
|
|
parasitics_->deleteParasitics(net, ap_);
|
|
// Net total capacitance is ignored.
|
|
delete total_cap;
|
|
}
|
|
|
|
void
|
|
SpefReader::rspfFinish()
|
|
{
|
|
}
|
|
|
|
void
|
|
SpefReader::rspfDrvrBegin(Pin *drvr_pin,
|
|
SpefRspfPi *pi)
|
|
{
|
|
if (drvr_pin) {
|
|
float c2 = pi->c2()->value(triple_index_) * cap_scale_;
|
|
float rpi = pi->r1()->value(triple_index_) * res_scale_;
|
|
float c1 = pi->c1()->value(triple_index_) * cap_scale_;
|
|
// Delete pi model and elmore delays.
|
|
parasitics_->deleteParasitics(drvr_pin, ap_);
|
|
// Only one parasitic, save it under rise transition.
|
|
parasitic_ = parasitics_->makePiElmore(drvr_pin,
|
|
RiseFall::rise(),
|
|
ap_, c2, rpi, c1);
|
|
}
|
|
delete pi;
|
|
}
|
|
|
|
void
|
|
SpefReader::rspfLoad(Pin *load_pin,
|
|
SpefTriple *rc)
|
|
{
|
|
if (parasitic_ && load_pin) {
|
|
float elmore = rc->value(triple_index_) * time_scale_;
|
|
parasitics_->setElmore(parasitic_, load_pin, elmore);
|
|
}
|
|
delete rc;
|
|
}
|
|
|
|
void
|
|
SpefReader::rspfDrvrFinish()
|
|
{
|
|
parasitic_ = nullptr;
|
|
}
|
|
|
|
// Net cap (total_cap) is ignored.
|
|
void
|
|
SpefReader::dspfBegin(Net *net,
|
|
SpefTriple *total_cap)
|
|
{
|
|
if (net) {
|
|
if (network_->isTopInstance(instance_)) {
|
|
parasitics_->deleteReducedParasitics(net, ap_);
|
|
parasitic_ = parasitics_->makeParasiticNetwork(net, pin_cap_included_, ap_);
|
|
}
|
|
else {
|
|
Net *parasitic_owner = net;
|
|
NetTermIterator *term_iter = network_->termIterator(net);
|
|
if (term_iter->hasNext()) {
|
|
Term *term = term_iter->next();
|
|
Pin *hpin = network_->pin(term);
|
|
parasitic_owner = network_->net(hpin);
|
|
}
|
|
delete term_iter;
|
|
parasitic_ = parasitics_->findParasiticNetwork(parasitic_owner, ap_);
|
|
if (parasitic_ == nullptr)
|
|
parasitic_ = parasitics_->makeParasiticNetwork(parasitic_owner,
|
|
pin_cap_included_, ap_);
|
|
}
|
|
net_ = net;
|
|
}
|
|
else {
|
|
parasitic_ = nullptr;
|
|
net_ = nullptr;
|
|
}
|
|
delete total_cap;
|
|
}
|
|
|
|
void
|
|
SpefReader::dspfFinish()
|
|
{
|
|
if (parasitic_) {
|
|
if (!quiet_)
|
|
parasitics_->check(parasitic_);
|
|
if (reduce_to_ != ReducedParasiticType::none) {
|
|
parasitics_->reduceTo(parasitic_, net_, reduce_to_, op_cond_,
|
|
corner_, cnst_min_max_, ap_);
|
|
if (delete_after_reduce_)
|
|
parasitics_->deleteParasiticNetwork(net_, ap_);
|
|
}
|
|
}
|
|
parasitic_ = nullptr;
|
|
net_ = nullptr;
|
|
}
|
|
|
|
// Caller is only interested in nodes on net_.
|
|
ParasiticNode *
|
|
SpefReader::findParasiticNode(char *name)
|
|
{
|
|
ParasiticNode *node;
|
|
Net *ext_net;
|
|
int ext_node_id;
|
|
Pin *ext_pin;
|
|
findParasiticNode(name, node, ext_net, ext_node_id, ext_pin);
|
|
if (node == nullptr
|
|
&& (ext_net || ext_pin))
|
|
warn(175, "%s not connected to net %s.", name, network_->pathName(net_));
|
|
return node;
|
|
}
|
|
|
|
void
|
|
SpefReader::findParasiticNode(char *name,
|
|
ParasiticNode *&node,
|
|
Net *&ext_net,
|
|
int &ext_node_id,
|
|
Pin *&ext_pin)
|
|
{
|
|
node = nullptr;
|
|
ext_net = nullptr;
|
|
ext_node_id = 0;
|
|
ext_pin = nullptr;
|
|
if (name) {
|
|
if (parasitic_) {
|
|
char *delim = strrchr(name, delimiter_);
|
|
if (delim) {
|
|
*delim = '\0';
|
|
char *name2 = delim + 1;
|
|
name = nameMapLookup(name);
|
|
Instance *inst = findInstanceRelative(name);
|
|
if (inst) {
|
|
// <instance>:<port>
|
|
Pin *pin = network_->findPin(inst, name2);
|
|
if (pin) {
|
|
if (network_->isConnected(net_, pin))
|
|
node = parasitics_->ensureParasiticNode(parasitic_, pin);
|
|
else
|
|
ext_pin = pin;
|
|
}
|
|
else {
|
|
// Replace delimiter for error message.
|
|
*delim = delimiter_;
|
|
warn(176, "pin %s not found.", name);
|
|
}
|
|
}
|
|
else {
|
|
Net *net = findNet(name);
|
|
// Replace delimiter for error messages.
|
|
*delim = delimiter_;
|
|
if (net) {
|
|
// <net>:<subnode_id>
|
|
const char *id_str = delim + 1;
|
|
if (isDigits(id_str)) {
|
|
int id = atoi(id_str);
|
|
if (network_->isConnected(net, net_))
|
|
node = parasitics_->ensureParasiticNode(parasitic_, net, id);
|
|
else {
|
|
ext_net = net;
|
|
ext_node_id = id;
|
|
}
|
|
}
|
|
else
|
|
warn(177, "node %s not a pin or net:number", name);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// <top_level_port>
|
|
name = nameMapLookup(name);
|
|
Pin *pin = findPortPinRelative(name);
|
|
if (pin) {
|
|
if (network_->isConnected(net_, pin))
|
|
node = parasitics_->ensureParasiticNode(parasitic_, pin);
|
|
else
|
|
ext_pin = pin;
|
|
}
|
|
else
|
|
warn(178, "pin %s not found.", name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
SpefReader::makeCapacitor(int, char *node_name,
|
|
SpefTriple *cap)
|
|
{
|
|
ParasiticNode *node = findParasiticNode(node_name);
|
|
if (node) {
|
|
float cap1 = cap->value(triple_index_) * cap_scale_;
|
|
parasitics_->incrCap(node, cap1, ap_);
|
|
}
|
|
delete cap;
|
|
stringDelete(node_name);
|
|
}
|
|
|
|
void
|
|
SpefReader::makeCapacitor(int id,
|
|
char *node_name1,
|
|
char *node_name2,
|
|
SpefTriple *cap)
|
|
{
|
|
float cap1 = cap->value(triple_index_) * cap_scale_;
|
|
if (keep_coupling_caps_)
|
|
makeCouplingCap(id, node_name1, node_name2, cap1);
|
|
else {
|
|
ParasiticNode *node1, *node2;
|
|
Net *ext_net1, *ext_net2;
|
|
int ext_node_id1, ext_node_id2;
|
|
Pin *ext_pin1, *ext_pin2;
|
|
findParasiticNode(node_name1, node1, ext_net1, ext_node_id1, ext_pin1);
|
|
findParasiticNode(node_name2, node2, ext_net2, ext_node_id2, ext_pin2);
|
|
float scaled_cap = cap1 * ap_->couplingCapFactor();
|
|
if (node1)
|
|
parasitics_->incrCap(node1, scaled_cap, ap_);
|
|
if (node2)
|
|
parasitics_->incrCap(node2, scaled_cap, ap_);
|
|
}
|
|
delete cap;
|
|
stringDelete(node_name1);
|
|
stringDelete(node_name2);
|
|
}
|
|
|
|
void
|
|
SpefReader::makeCouplingCap(int id,
|
|
char *node_name1,
|
|
char *node_name2,
|
|
float cap)
|
|
{
|
|
const char *name = nullptr;
|
|
const char *name_tmp = nullptr;
|
|
if (keep_device_names_)
|
|
// Prepend device type because OA uses one namespace for all devices.
|
|
name = name_tmp = stringPrint("C%d", id);
|
|
|
|
ParasiticNode *node1, *node2;
|
|
Net *ext_net1, *ext_net2;
|
|
int ext_node_id1, ext_node_id2;
|
|
Pin *ext_pin1, *ext_pin2;
|
|
findParasiticNode(node_name1, node1, ext_net1, ext_node_id1, ext_pin1);
|
|
findParasiticNode(node_name2, node2, ext_net2, ext_node_id2, ext_pin2);
|
|
if (node1 && node2)
|
|
parasitics_->makeCouplingCap(name, node1, node2, cap, ap_);
|
|
if (node1 && node2 == nullptr) {
|
|
if (ext_net2)
|
|
parasitics_->makeCouplingCap(name, node1, ext_net2, ext_node_id2,
|
|
cap, ap_);
|
|
else if (ext_pin2)
|
|
parasitics_->makeCouplingCap(name, node1, ext_pin2, cap, ap_);
|
|
}
|
|
else if (node1 == nullptr && node2) {
|
|
if (ext_net1)
|
|
parasitics_->makeCouplingCap(name, node2, ext_net1, ext_node_id1,
|
|
cap, ap_);
|
|
else if (ext_pin1)
|
|
parasitics_->makeCouplingCap(name, node2, ext_pin1, cap, ap_);
|
|
}
|
|
stringDelete(name_tmp);
|
|
}
|
|
|
|
void
|
|
SpefReader::makeResistor(int id,
|
|
char *node_name1,
|
|
char *node_name2,
|
|
SpefTriple *res)
|
|
{
|
|
ParasiticNode *node1 = findParasiticNode(node_name1);
|
|
ParasiticNode *node2 = findParasiticNode(node_name2);
|
|
if (node1 && node2) {
|
|
float res1 = res->value(triple_index_) * res_scale_;
|
|
const char *name = nullptr;
|
|
const char *name_tmp = nullptr;
|
|
if (keep_device_names_)
|
|
// Prepend device type because OA uses one namespace for all devices.
|
|
name = name_tmp = stringPrint("R%d", id);
|
|
parasitics_->makeResistor(name, node1, node2, res1, ap_);
|
|
stringDelete(name_tmp);
|
|
}
|
|
delete res;
|
|
stringDelete(node_name1);
|
|
stringDelete(node_name2);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
SpefRspfPi::SpefRspfPi(SpefTriple *c2,
|
|
SpefTriple *r1,
|
|
SpefTriple *c1) :
|
|
c2_(c2),
|
|
r1_(r1),
|
|
c1_(c1)
|
|
{
|
|
}
|
|
|
|
SpefRspfPi::~SpefRspfPi()
|
|
{
|
|
delete c2_;
|
|
delete r1_;
|
|
delete c1_;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
SpefTriple::SpefTriple(float value) :
|
|
is_triple_(false)
|
|
{
|
|
values_[0] = value;
|
|
}
|
|
|
|
SpefTriple::SpefTriple(float value1,
|
|
float value2,
|
|
float value3) :
|
|
is_triple_(true)
|
|
{
|
|
values_[0] = value1;
|
|
values_[1] = value2;
|
|
values_[2] = value3;
|
|
}
|
|
|
|
float
|
|
SpefTriple::value(int index) const
|
|
{
|
|
if (is_triple_)
|
|
return values_[index];
|
|
else
|
|
return values_[0];
|
|
}
|
|
|
|
} // namespace
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// Global namespace
|
|
|
|
void spefFlushBuffer();
|
|
|
|
int
|
|
SpefParse_error(const char *msg)
|
|
{
|
|
spefFlushBuffer();
|
|
sta::spef_reader->warn(179, "%s.", msg);
|
|
return 0;
|
|
}
|