mirror of https://github.com/KLayout/klayout.git
WIP: Debugging of LVS DB writer
This commit is contained in:
parent
b2fee5da3d
commit
81e512e1cd
|
|
@ -69,7 +69,7 @@ namespace db
|
|||
* A net declaration shall be there also if no geometry
|
||||
* is present. The ID is a numerical shortcut for the net.
|
||||
* pin(<name> <net-id>) - outgoing pin connection [short key: P]
|
||||
* device(<name> <abstract> [combined-device]* [terminal-route]* [device-def])
|
||||
* device(<name> <abstract-or-class> [combined-device]* [terminal-route]* [device-def])
|
||||
* - device with connections [short key: D]
|
||||
* circuit(<name> [subcircuit-def])
|
||||
* - subcircuit with connections [short key: X]
|
||||
|
|
|
|||
|
|
@ -335,10 +335,8 @@ void LayoutToNetlistStandardReader::read_netlist (db::Netlist *netlist, db::Layo
|
|||
dm->set_name (name);
|
||||
netlist->add_device_abstract (dm);
|
||||
|
||||
if (l2n) {
|
||||
db::cell_index_type ci = l2n->internal_layout ()->add_cell (name.c_str ());
|
||||
dm->set_cell_index (ci);
|
||||
}
|
||||
db::cell_index_type ci = l2n->internal_layout ()->add_cell (name.c_str ());
|
||||
dm->set_cell_index (ci);
|
||||
|
||||
std::string cls;
|
||||
read_word_or_quoted (cls);
|
||||
|
|
@ -522,16 +520,21 @@ LayoutToNetlistStandardReader::terminal_id (const db::DeviceClass *device_class,
|
|||
throw tl::Exception (tl::to_string (tr ("Not a valid terminal name: ")) + tname + tl::to_string (tr (" for device class: ")) + device_class->name ());
|
||||
}
|
||||
|
||||
db::DeviceAbstract *
|
||||
std::pair<db::DeviceAbstract *, const db::DeviceClass *>
|
||||
LayoutToNetlistStandardReader::device_model_by_name (db::Netlist *netlist, const std::string &dmname)
|
||||
{
|
||||
for (db::Netlist::device_abstract_iterator i = netlist->begin_device_abstracts (); i != netlist->end_device_abstracts (); ++i) {
|
||||
if (i->name () == dmname) {
|
||||
return i.operator-> ();
|
||||
return std::make_pair (i.operator-> (), i->device_class ());
|
||||
}
|
||||
}
|
||||
|
||||
throw tl::Exception (tl::to_string (tr ("Not a valid device abstract name: ")) + dmname);
|
||||
db::DeviceClass *cls = netlist->device_class_by_name (dmname);
|
||||
if (! cls) {
|
||||
throw tl::Exception (tl::to_string (tr ("Not a valid device abstract name: ")) + dmname);
|
||||
}
|
||||
|
||||
return std::make_pair ((db::DeviceAbstract *) 0, cls);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -545,11 +548,11 @@ LayoutToNetlistStandardReader::read_device (db::Netlist *netlist, db::LayoutToNe
|
|||
std::string dmname;
|
||||
read_word_or_quoted (dmname);
|
||||
|
||||
db::DeviceAbstract *dm = device_model_by_name (netlist, dmname);
|
||||
std::pair<db::DeviceAbstract *, const db::DeviceClass *> dm = device_model_by_name (netlist, dmname);
|
||||
|
||||
db::Device *device = new db::Device ();
|
||||
device->set_device_class (const_cast<db::DeviceClass *> (dm->device_class ()));
|
||||
device->set_device_abstract (dm);
|
||||
device->set_device_class (const_cast<db::DeviceClass *> (dm.second));
|
||||
device->set_device_abstract (dm.first);
|
||||
device->set_name (name);
|
||||
circuit->add_device (device);
|
||||
|
||||
|
|
@ -581,7 +584,7 @@ LayoutToNetlistStandardReader::read_device (db::Netlist *netlist, db::LayoutToNe
|
|||
|
||||
br2.done ();
|
||||
|
||||
db::DeviceAbstract *da = device_model_by_name (netlist, n);
|
||||
db::DeviceAbstract *da = device_model_by_name (netlist, n).first;
|
||||
|
||||
device->other_abstracts ().push_back (db::DeviceAbstractRef (da, db::DVector (dbu * dx, dbu * dy)));
|
||||
|
||||
|
|
@ -601,8 +604,8 @@ LayoutToNetlistStandardReader::read_device (db::Netlist *netlist, db::LayoutToNe
|
|||
throw tl::Exception (tl::to_string (tr ("Not a valid device component index: ")) + tl::to_string (device_comp_index));
|
||||
}
|
||||
|
||||
size_t touter_id = terminal_id (dm->device_class (), touter);
|
||||
size_t tinner_id = terminal_id (dm->device_class (), tinner);
|
||||
size_t touter_id = terminal_id (dm.second, touter);
|
||||
size_t tinner_id = terminal_id (dm.second, tinner);
|
||||
|
||||
device->reconnected_terminals () [touter_id].push_back (db::DeviceReconnectedTerminal (size_t (device_comp_index), tinner_id));
|
||||
|
||||
|
|
@ -614,7 +617,7 @@ LayoutToNetlistStandardReader::read_device (db::Netlist *netlist, db::LayoutToNe
|
|||
unsigned int netid = (unsigned int) read_int ();
|
||||
br2.done ();
|
||||
|
||||
size_t tid = terminal_id (dm->device_class (), tname);
|
||||
size_t tid = terminal_id (dm.second, tname);
|
||||
max_tid = std::max (max_tid, tid + 1);
|
||||
|
||||
db::Net *net = id2net [netid];
|
||||
|
|
@ -633,7 +636,7 @@ LayoutToNetlistStandardReader::read_device (db::Netlist *netlist, db::LayoutToNe
|
|||
br2.done ();
|
||||
|
||||
size_t pid = std::numeric_limits<size_t>::max ();
|
||||
const std::vector<db::DeviceParameterDefinition> &pd = dm->device_class ()->parameter_definitions ();
|
||||
const std::vector<db::DeviceParameterDefinition> &pd = dm.second->parameter_definitions ();
|
||||
for (std::vector<db::DeviceParameterDefinition>::const_iterator p = pd.begin (); p != pd.end (); ++p) {
|
||||
if (p->name () == pname) {
|
||||
pid = p->id ();
|
||||
|
|
@ -644,7 +647,7 @@ LayoutToNetlistStandardReader::read_device (db::Netlist *netlist, db::LayoutToNe
|
|||
// if no parameter with this name exists, create one
|
||||
if (pid == std::numeric_limits<size_t>::max ()) {
|
||||
// TODO: this should only happen for generic devices
|
||||
db::DeviceClass *dc = const_cast<db::DeviceClass *> (dm->device_class ());
|
||||
db::DeviceClass *dc = const_cast<db::DeviceClass *> (dm.second);
|
||||
pid = dc->add_parameter_definition (db::DeviceParameterDefinition (pname, std::string ())).id ();
|
||||
}
|
||||
|
||||
|
|
@ -660,14 +663,14 @@ LayoutToNetlistStandardReader::read_device (db::Netlist *netlist, db::LayoutToNe
|
|||
|
||||
br.done ();
|
||||
|
||||
if (l2n) {
|
||||
if (l2n && dm.first) {
|
||||
|
||||
db::Cell &ccell = l2n->internal_layout ()->cell (circuit->cell_index ());
|
||||
|
||||
// make device cell instances
|
||||
std::vector<db::CellInstArray> insts;
|
||||
|
||||
db::CellInstArray inst (db::CellInst (dm->cell_index ()), db::Trans (db::Vector (x, y)));
|
||||
db::CellInstArray inst (db::CellInst (dm.first->cell_index ()), db::Trans (db::Vector (x, y)));
|
||||
ccell.insert (inst);
|
||||
insts.push_back (inst);
|
||||
|
||||
|
|
@ -695,7 +698,7 @@ LayoutToNetlistStandardReader::read_device (db::Netlist *netlist, db::LayoutToNe
|
|||
if (tr) {
|
||||
|
||||
for (std::vector<db::DeviceReconnectedTerminal>::const_iterator i = tr->begin (); i != tr->end (); ++i) {
|
||||
const db::DeviceAbstract *da = dm;
|
||||
const db::DeviceAbstract *da = dm.first;
|
||||
if (i->device_index > 0) {
|
||||
da = device->other_abstracts () [i->device_index - 1].device_abstract;
|
||||
}
|
||||
|
|
@ -707,7 +710,7 @@ LayoutToNetlistStandardReader::read_device (db::Netlist *netlist, db::LayoutToNe
|
|||
|
||||
} else {
|
||||
|
||||
Connections ref (net->cluster_id (), dm->cluster_id_for_terminal (tid));
|
||||
Connections ref (net->cluster_id (), dm.first->cluster_id_for_terminal (tid));
|
||||
connections [insts [0]].push_back (ref);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ protected:
|
|||
|
||||
void read_netlist (Netlist *netlist, db::LayoutToNetlist *l2n, bool nested = false, std::map<const db::Circuit *, std::map<unsigned int, Net *> > *id2net_per_circuit = 0);
|
||||
static size_t terminal_id (const db::DeviceClass *device_class, const std::string &tname);
|
||||
static db::DeviceAbstract *device_model_by_name (db::Netlist *netlist, const std::string &dmname);
|
||||
static std::pair<db::DeviceAbstract *, const db::DeviceClass *> device_model_by_name (db::Netlist *netlist, const std::string &dmname);
|
||||
tl::TextInputStream &stream ();
|
||||
const std::string &path () const;
|
||||
|
||||
|
|
|
|||
|
|
@ -50,16 +50,13 @@ void LayoutToNetlistWriterBase::write (const db::LayoutToNetlist *l2n)
|
|||
namespace l2n_std_format
|
||||
{
|
||||
|
||||
template class std_writer_impl<l2n_std_format::keys<false> >;
|
||||
template class std_writer_impl<l2n_std_format::keys<true> >;
|
||||
|
||||
static const std::string endl ("\n");
|
||||
static const std::string indent1 (" ");
|
||||
static const std::string indent2 (" ");
|
||||
|
||||
template <class Keys>
|
||||
std_writer_impl<Keys>::std_writer_impl (tl::OutputStream &stream)
|
||||
: mp_stream (&stream)
|
||||
std_writer_impl<Keys>::std_writer_impl (tl::OutputStream &stream, double dbu)
|
||||
: mp_stream (&stream), m_dbu (dbu)
|
||||
{
|
||||
// .. nothing yet ..
|
||||
}
|
||||
|
|
@ -77,7 +74,6 @@ template <class Keys>
|
|||
void std_writer_impl<Keys>::write (const db::LayoutToNetlist *l2n)
|
||||
{
|
||||
write (l2n->netlist (), l2n, false, 0);
|
||||
|
||||
}
|
||||
|
||||
template <class Keys>
|
||||
|
|
@ -103,15 +99,16 @@ void std_writer_impl<Keys>::write (const db::Netlist *nl, const db::LayoutToNetl
|
|||
}
|
||||
if (ly) {
|
||||
*mp_stream << indent << Keys::top_key << "(" << tl::to_word_or_quoted_string (ly->cell_name (l2n->internal_top_cell ()->cell_index ())) << ")" << endl;
|
||||
*mp_stream << indent << Keys::unit_key << "(" << ly->dbu () << ")" << endl;
|
||||
*mp_stream << indent << Keys::unit_key << "(" << m_dbu << ")" << endl;
|
||||
}
|
||||
|
||||
if (! Keys::is_short ()) {
|
||||
*mp_stream << endl << indent << "# Layer section" << endl;
|
||||
*mp_stream << indent << "# This section lists the mask layers (drawing or derived) and their connections." << endl;
|
||||
}
|
||||
if (l2n) {
|
||||
|
||||
if (! Keys::is_short ()) {
|
||||
*mp_stream << endl << indent << "# Layer section" << endl;
|
||||
*mp_stream << indent << "# This section lists the mask layers (drawing or derived) and their connections." << endl;
|
||||
}
|
||||
|
||||
if (ly) {
|
||||
if (! Keys::is_short ()) {
|
||||
*mp_stream << endl << indent << "# Mask layers" << endl;
|
||||
}
|
||||
|
|
@ -123,9 +120,6 @@ void std_writer_impl<Keys>::write (const db::Netlist *nl, const db::LayoutToNetl
|
|||
}
|
||||
*mp_stream << indent << ")" << endl;
|
||||
}
|
||||
}
|
||||
|
||||
if (l2n) {
|
||||
|
||||
if (! Keys::is_short ()) {
|
||||
*mp_stream << endl << indent << "# Mask layer connectivity" << endl;
|
||||
|
|
@ -190,19 +184,19 @@ void std_writer_impl<Keys>::write (const db::Netlist *nl, const db::LayoutToNetl
|
|||
}
|
||||
|
||||
if (! Keys::is_short ()) {
|
||||
*mp_stream << endl << "# Circuit section" << endl;
|
||||
*mp_stream << "# Circuits are the hierarchical building blocks of the netlist." << endl;
|
||||
*mp_stream << endl << indent << "# Circuit section" << endl;
|
||||
*mp_stream << indent << "# Circuits are the hierarchical building blocks of the netlist." << endl;
|
||||
}
|
||||
for (db::Netlist::const_bottom_up_circuit_iterator i = nl->begin_bottom_up (); i != nl->end_bottom_up (); ++i) {
|
||||
const db::Circuit *x = *i;
|
||||
*mp_stream << indent << Keys::circuit_key << "(" << tl::to_word_or_quoted_string (x->name ()) << endl;
|
||||
write (l2n, *x, indent, net2id_per_circuit);
|
||||
write (nl, l2n, *x, indent, net2id_per_circuit);
|
||||
*mp_stream << indent << ")" << endl;
|
||||
}
|
||||
}
|
||||
|
||||
template <class Keys>
|
||||
void std_writer_impl<Keys>::write (const db::LayoutToNetlist *l2n, const db::Circuit &circuit, const std::string &indent, std::map<const db::Circuit *, std::map<const db::Net *, unsigned int> > *net2id_per_circuit)
|
||||
void std_writer_impl<Keys>::write (const db::Netlist *netlist, const db::LayoutToNetlist *l2n, const db::Circuit &circuit, const std::string &indent, std::map<const db::Circuit *, std::map<const db::Net *, unsigned int> > *net2id_per_circuit)
|
||||
{
|
||||
std::map<const db::Net *, unsigned int> net2id_local;
|
||||
std::map<const db::Net *, unsigned int> *net2id = &net2id_local;
|
||||
|
|
@ -211,14 +205,16 @@ void std_writer_impl<Keys>::write (const db::LayoutToNetlist *l2n, const db::Cir
|
|||
}
|
||||
|
||||
unsigned int id = 0;
|
||||
for (db::Circuit::const_net_iterator n = circuit.begin_nets (); n != circuit.end_nets (); ++n) {
|
||||
net2id->insert (std::make_pair (n.operator-> (), ++id));
|
||||
}
|
||||
|
||||
if (circuit.begin_nets () != circuit.end_nets ()) {
|
||||
if (l2n && circuit.begin_nets () != circuit.end_nets ()) {
|
||||
if (! Keys::is_short ()) {
|
||||
*mp_stream << endl << indent << indent1 << "# Nets with their geometries" << endl;
|
||||
}
|
||||
for (db::Circuit::const_net_iterator n = circuit.begin_nets (); n != circuit.end_nets (); ++n) {
|
||||
net2id->insert (std::make_pair (n.operator-> (), ++id));
|
||||
write (l2n, *n, id, indent);
|
||||
write (netlist, l2n, *n, (*net2id) [n.operator-> ()], indent);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -336,12 +332,8 @@ void std_writer_impl<Keys>::write (const db::PolygonRef *s, const db::ICplxTrans
|
|||
}
|
||||
|
||||
template <class Keys>
|
||||
void std_writer_impl<Keys>::write (const db::LayoutToNetlist *l2n, const db::Net &net, unsigned int id, const std::string &indent)
|
||||
void std_writer_impl<Keys>::write (const db::Netlist *netlist, const db::LayoutToNetlist *l2n, const db::Net &net, unsigned int id, const std::string &indent)
|
||||
{
|
||||
if (! l2n->netlist ()) {
|
||||
throw tl::Exception (tl::to_string (tr ("Can't write annotated netlist before extraction has been done")));
|
||||
}
|
||||
|
||||
const db::hier_clusters<db::PolygonRef> &clusters = l2n->net_clusters ();
|
||||
const db::Circuit *circuit = net.circuit ();
|
||||
const db::Connectivity &conn = l2n->connectivity ();
|
||||
|
|
@ -361,7 +353,7 @@ void std_writer_impl<Keys>::write (const db::LayoutToNetlist *l2n, const db::Net
|
|||
// vanish in "purge" but the clusters will still be there we need to recursive into clusters from
|
||||
// unknown cells.
|
||||
db::cell_index_type ci = si.cell_index ();
|
||||
if (ci != prev_ci && ci != cci && (l2n->netlist ()->circuit_by_cell_index (ci) || l2n->netlist ()->device_abstract_by_cell_index (ci))) {
|
||||
if (ci != prev_ci && ci != cci && (netlist->circuit_by_cell_index (ci) || netlist->device_abstract_by_cell_index (ci))) {
|
||||
|
||||
si.skip_cell ();
|
||||
|
||||
|
|
@ -376,7 +368,7 @@ void std_writer_impl<Keys>::write (const db::LayoutToNetlist *l2n, const db::Net
|
|||
any = true;
|
||||
}
|
||||
|
||||
*mp_stream << indent2;
|
||||
*mp_stream << indent << indent2;
|
||||
write (si.operator-> (), si.trans (), name_for_layer (l2n, *l), true);
|
||||
*mp_stream << endl;
|
||||
|
||||
|
|
@ -394,7 +386,7 @@ void std_writer_impl<Keys>::write (const db::LayoutToNetlist *l2n, const db::Net
|
|||
*mp_stream << indent << indent1 << ")" << endl;
|
||||
} else {
|
||||
|
||||
*mp_stream << indent1 << Keys::net_key << "(" << id;
|
||||
*mp_stream << indent << indent1 << Keys::net_key << "(" << id;
|
||||
if (! net.name ().empty ()) {
|
||||
*mp_stream << " " << Keys::name_key << "(" << tl::to_word_or_quoted_string (net.name ()) << ")";
|
||||
}
|
||||
|
|
@ -406,23 +398,24 @@ void std_writer_impl<Keys>::write (const db::LayoutToNetlist *l2n, const db::Net
|
|||
template <class Keys>
|
||||
void std_writer_impl<Keys>::write (const db::LayoutToNetlist *l2n, const db::SubCircuit &subcircuit, std::map<const Net *, unsigned int> &net2id, const std::string &indent)
|
||||
{
|
||||
const db::Layout *ly = l2n->internal_layout ();
|
||||
double dbu = ly->dbu ();
|
||||
|
||||
*mp_stream << indent << indent1 << Keys::circuit_key << "(" << tl::to_word_or_quoted_string (subcircuit.expanded_name ());
|
||||
*mp_stream << " " << tl::to_word_or_quoted_string (subcircuit.circuit_ref ()->name ());
|
||||
|
||||
const db::DCplxTrans &tr = subcircuit.trans ();
|
||||
if (tr.is_mag ()) {
|
||||
*mp_stream << " " << Keys::scale_key << "(" << tr.mag () << ")";
|
||||
if (l2n) {
|
||||
|
||||
const db::DCplxTrans &tr = subcircuit.trans ();
|
||||
if (tr.is_mag ()) {
|
||||
*mp_stream << " " << Keys::scale_key << "(" << tr.mag () << ")";
|
||||
}
|
||||
if (tr.is_mirror ()) {
|
||||
*mp_stream << " " << Keys::mirror_key;
|
||||
}
|
||||
if (fabs (tr.angle ()) > 1e-6) {
|
||||
*mp_stream << " " << Keys::rotation_key << "(" << tr.angle () << ")";
|
||||
}
|
||||
*mp_stream << " " << Keys::location_key << "(" << tr.disp ().x () / m_dbu << " " << tr.disp ().y () / m_dbu << ")";
|
||||
|
||||
}
|
||||
if (tr.is_mirror ()) {
|
||||
*mp_stream << " " << Keys::mirror_key;
|
||||
}
|
||||
if (fabs (tr.angle ()) > 1e-6) {
|
||||
*mp_stream << " " << Keys::rotation_key << "(" << tr.angle () << ")";
|
||||
}
|
||||
*mp_stream << " " << Keys::location_key << "(" << tr.disp ().x () / dbu << " " << tr.disp ().y () / dbu << ")";
|
||||
|
||||
// each pin in one line for more than a few pins
|
||||
bool separate_lines = (subcircuit.circuit_ref ()->pin_count () > 1);
|
||||
|
|
@ -486,11 +479,9 @@ void std_writer_impl<Keys>::write (const db::LayoutToNetlist *l2n, const db::Dev
|
|||
}
|
||||
|
||||
template <class Keys>
|
||||
void std_writer_impl<Keys>::write (const db::LayoutToNetlist *l2n, const db::Device &device, std::map<const Net *, unsigned int> &net2id, const std::string &indent)
|
||||
void std_writer_impl<Keys>::write (const db::LayoutToNetlist * /*l2n*/, const db::Device &device, std::map<const Net *, unsigned int> &net2id, const std::string &indent)
|
||||
{
|
||||
const db::Layout *ly = l2n->internal_layout ();
|
||||
double dbu = ly->dbu ();
|
||||
db::VCplxTrans dbu_inv (1.0 / dbu);
|
||||
db::VCplxTrans dbu_inv (1.0 / m_dbu);
|
||||
|
||||
tl_assert (device.device_class () != 0);
|
||||
const std::vector<DeviceTerminalDefinition> &td = device.device_class ()->terminal_definitions ();
|
||||
|
|
@ -498,31 +489,35 @@ void std_writer_impl<Keys>::write (const db::LayoutToNetlist *l2n, const db::Dev
|
|||
|
||||
*mp_stream << indent << indent1 << Keys::device_key << "(" << tl::to_word_or_quoted_string (device.expanded_name ());
|
||||
|
||||
tl_assert (device.device_abstract () != 0);
|
||||
*mp_stream << " " << tl::to_word_or_quoted_string (device.device_abstract ()->name ()) << endl;
|
||||
if (device.device_abstract ()) {
|
||||
|
||||
const std::vector<db::DeviceAbstractRef> &other_abstracts = device.other_abstracts ();
|
||||
for (std::vector<db::DeviceAbstractRef>::const_iterator a = other_abstracts.begin (); a != other_abstracts.end (); ++a) {
|
||||
*mp_stream << " " << tl::to_word_or_quoted_string (device.device_abstract ()->name ()) << endl;
|
||||
|
||||
db::Vector pos = dbu_inv * a->offset;
|
||||
const std::vector<db::DeviceAbstractRef> &other_abstracts = device.other_abstracts ();
|
||||
for (std::vector<db::DeviceAbstractRef>::const_iterator a = other_abstracts.begin (); a != other_abstracts.end (); ++a) {
|
||||
|
||||
*mp_stream << indent << indent2 << Keys::device_key << "(" << tl::to_word_or_quoted_string (a->device_abstract->name ()) << " " << pos.x () << " " << pos.y () << ")" << endl;
|
||||
db::Vector pos = dbu_inv * a->offset;
|
||||
|
||||
}
|
||||
*mp_stream << indent << indent2 << Keys::device_key << "(" << tl::to_word_or_quoted_string (a->device_abstract->name ()) << " " << pos.x () << " " << pos.y () << ")" << endl;
|
||||
|
||||
const std::map<unsigned int, std::vector<db::DeviceReconnectedTerminal> > &reconnected_terminals = device.reconnected_terminals ();
|
||||
for (std::map<unsigned int, std::vector<db::DeviceReconnectedTerminal> >::const_iterator t = reconnected_terminals.begin (); t != reconnected_terminals.end (); ++t) {
|
||||
|
||||
for (std::vector<db::DeviceReconnectedTerminal>::const_iterator c = t->second.begin (); c != t->second.end (); ++c) {
|
||||
*mp_stream << indent << indent2 << Keys::connect_key << "(" << c->device_index << " " << tl::to_word_or_quoted_string (td [t->first].name ()) << " " << tl::to_word_or_quoted_string (td [c->other_terminal_id].name ()) << ")" << endl;
|
||||
}
|
||||
|
||||
const std::map<unsigned int, std::vector<db::DeviceReconnectedTerminal> > &reconnected_terminals = device.reconnected_terminals ();
|
||||
for (std::map<unsigned int, std::vector<db::DeviceReconnectedTerminal> >::const_iterator t = reconnected_terminals.begin (); t != reconnected_terminals.end (); ++t) {
|
||||
|
||||
for (std::vector<db::DeviceReconnectedTerminal>::const_iterator c = t->second.begin (); c != t->second.end (); ++c) {
|
||||
*mp_stream << indent << indent2 << Keys::connect_key << "(" << c->device_index << " " << tl::to_word_or_quoted_string (td [t->first].name ()) << " " << tl::to_word_or_quoted_string (td [c->other_terminal_id].name ()) << ")" << endl;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
db::Point pos = dbu_inv * device.position ();
|
||||
*mp_stream << indent << indent2 << Keys::location_key << "(" << pos.x () << " " << pos.y () << ")" << endl;
|
||||
|
||||
} else {
|
||||
*mp_stream << " " << tl::to_word_or_quoted_string (device.device_class ()->name ()) << endl;
|
||||
}
|
||||
|
||||
db::Point pos = dbu_inv * device.position ();
|
||||
|
||||
*mp_stream << indent << indent2 << Keys::location_key << "(" << pos.x () << " " << pos.y () << ")" << endl;
|
||||
|
||||
for (std::vector<DeviceParameterDefinition>::const_iterator i = pd.begin (); i != pd.end (); ++i) {
|
||||
*mp_stream << indent << indent2 << Keys::param_key << "(" << tl::to_word_or_quoted_string (i->name ()) << " " << device.parameter_value (i->id ()) << ")" << endl;
|
||||
}
|
||||
|
|
@ -537,6 +532,10 @@ void std_writer_impl<Keys>::write (const db::LayoutToNetlist *l2n, const db::Dev
|
|||
*mp_stream << indent << indent1 << ")" << endl;
|
||||
}
|
||||
|
||||
// explicit instantiation
|
||||
template class std_writer_impl<l2n_std_format::keys<false> >;
|
||||
template class std_writer_impl<l2n_std_format::keys<true> >;
|
||||
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------
|
||||
|
|
@ -550,11 +549,20 @@ LayoutToNetlistStandardWriter::LayoutToNetlistStandardWriter (tl::OutputStream &
|
|||
|
||||
void LayoutToNetlistStandardWriter::do_write (const db::LayoutToNetlist *l2n)
|
||||
{
|
||||
if (! l2n->netlist ()) {
|
||||
throw tl::Exception (tl::to_string (tr ("Can't write annotated netlist before the netlist has been created")));
|
||||
}
|
||||
if (! l2n->internal_layout ()) {
|
||||
throw tl::Exception (tl::to_string (tr ("Can't write annotated netlist before the layout has been loaded")));
|
||||
}
|
||||
|
||||
double dbu = l2n->internal_layout ()->dbu ();
|
||||
|
||||
if (m_short_version) {
|
||||
l2n_std_format::std_writer_impl<l2n_std_format::keys<true> > writer (*mp_stream);
|
||||
l2n_std_format::std_writer_impl<l2n_std_format::keys<true> > writer (*mp_stream, dbu);
|
||||
writer.write (l2n);
|
||||
} else {
|
||||
l2n_std_format::std_writer_impl<l2n_std_format::keys<false> > writer (*mp_stream);
|
||||
l2n_std_format::std_writer_impl<l2n_std_format::keys<false> > writer (*mp_stream, dbu);
|
||||
writer.write (l2n);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,14 +47,14 @@ template <class Keys>
|
|||
class std_writer_impl
|
||||
{
|
||||
public:
|
||||
std_writer_impl (tl::OutputStream &stream);
|
||||
std_writer_impl (tl::OutputStream &stream, double dbu);
|
||||
|
||||
void write (const db::LayoutToNetlist *l2n);
|
||||
|
||||
protected:
|
||||
void write (const db::Netlist *netlist, const db::LayoutToNetlist *l2n, bool nested, std::map<const db::Circuit *, std::map<const db::Net *, unsigned int> > *net2id_per_circuit);
|
||||
void write (const db::LayoutToNetlist *l2n, const db::Circuit &circuit, const std::string &indent, std::map<const db::Circuit *, std::map<const db::Net *, unsigned int> > *net2id_per_circuit);
|
||||
void write (const db::LayoutToNetlist *l2n, const db::Net &net, unsigned int id, const std::string &indent);
|
||||
void write (const db::Netlist *netlist, const db::LayoutToNetlist *l2n, const db::Circuit &circuit, const std::string &indent, std::map<const db::Circuit *, std::map<const db::Net *, unsigned int> > *net2id_per_circuit);
|
||||
void write (const db::Netlist *netlist, const db::LayoutToNetlist *l2n, const db::Net &net, unsigned int id, const std::string &indent);
|
||||
void write (const db::LayoutToNetlist *l2n, const db::SubCircuit &subcircuit, std::map<const Net *, unsigned int> &net2id, const std::string &indent);
|
||||
void write (const db::LayoutToNetlist *l2n, const db::Device &device, std::map<const Net *, unsigned int> &net2id, const std::string &indent);
|
||||
void write (const db::LayoutToNetlist *l2n, const db::DeviceAbstract &device_abstract, const std::string &indent);
|
||||
|
|
@ -69,6 +69,7 @@ protected:
|
|||
private:
|
||||
tl::OutputStream *mp_stream;
|
||||
db::Point m_ref;
|
||||
double m_dbu;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -120,6 +120,8 @@ namespace lvs_std_format
|
|||
struct DB_PUBLIC keys
|
||||
: public l2n_std_format::keys<Short>
|
||||
{
|
||||
typedef l2n_std_format::keys<Short> l2n_keys;
|
||||
|
||||
static const std::string reference_key;
|
||||
static const std::string layout_key;
|
||||
static const std::string xref_key;
|
||||
|
|
|
|||
|
|
@ -56,17 +56,17 @@ namespace lvs_std_format
|
|||
|
||||
template <class Keys>
|
||||
class std_writer_impl
|
||||
: public l2n_std_format::std_writer_impl<Keys>
|
||||
: public l2n_std_format::std_writer_impl<typename Keys::l2n_keys>
|
||||
{
|
||||
public:
|
||||
std_writer_impl (tl::OutputStream &stream);
|
||||
std_writer_impl (tl::OutputStream &stream, double dbu);
|
||||
|
||||
void write (const db::LayoutVsSchematic *l2n);
|
||||
|
||||
private:
|
||||
tl::OutputStream &stream ()
|
||||
{
|
||||
return l2n_std_format::std_writer_impl<Keys>::stream ();
|
||||
return l2n_std_format::std_writer_impl<typename Keys::l2n_keys>::stream ();
|
||||
}
|
||||
|
||||
std::string status_to_s (const db::NetlistCrossReference::Status status);
|
||||
|
|
@ -80,8 +80,8 @@ static const std::string indent1 (" ");
|
|||
static const std::string indent2 (" ");
|
||||
|
||||
template <class Keys>
|
||||
std_writer_impl<Keys>::std_writer_impl (tl::OutputStream &stream)
|
||||
: l2n_std_format::std_writer_impl<Keys> (stream)
|
||||
std_writer_impl<Keys>::std_writer_impl (tl::OutputStream &stream, double dbu)
|
||||
: l2n_std_format::std_writer_impl<typename Keys::l2n_keys> (stream, dbu)
|
||||
{
|
||||
// .. nothing yet ..
|
||||
}
|
||||
|
|
@ -101,26 +101,32 @@ void std_writer_impl<Keys>::write (const db::LayoutVsSchematic *lvs)
|
|||
stream () << Keys::version_key << "(" << version << ")" << endl;
|
||||
}
|
||||
|
||||
if (! Keys::is_short ()) {
|
||||
stream () << endl << "# Layout" << endl;
|
||||
if (lvs->netlist ()) {
|
||||
if (! Keys::is_short ()) {
|
||||
stream () << endl << "# Layout" << endl;
|
||||
}
|
||||
stream () << Keys::layout_key << "(" << endl;
|
||||
l2n_std_format::std_writer_impl<typename Keys::l2n_keys>::write (lvs->netlist (), lvs, true, &m_net2id_per_circuit_a);
|
||||
stream () << ")" << endl;
|
||||
}
|
||||
stream () << Keys::layout_key << "(" << endl;
|
||||
l2n_std_format::std_writer_impl<Keys>::write (0, lvs, true, &m_net2id_per_circuit_a);
|
||||
stream () << ")" << endl;
|
||||
|
||||
if (! Keys::is_short ()) {
|
||||
stream () << endl << "# Reference netlist" << endl;
|
||||
if (lvs->reference_netlist ()) {
|
||||
if (! Keys::is_short ()) {
|
||||
stream () << endl << "# Reference netlist" << endl;
|
||||
}
|
||||
stream () << Keys::reference_key << "(" << endl;
|
||||
l2n_std_format::std_writer_impl<typename Keys::l2n_keys>::write (lvs->reference_netlist (), 0, true, &m_net2id_per_circuit_b);
|
||||
stream () << ")" << endl;
|
||||
}
|
||||
stream () << Keys::reference_key << "(" << endl;
|
||||
l2n_std_format::std_writer_impl<Keys>::write (lvs->reference_netlist (), 0, true, &m_net2id_per_circuit_b);
|
||||
stream () << ")" << endl;
|
||||
|
||||
if (! Keys::is_short ()) {
|
||||
stream () << endl << "# Cross reference" << endl;
|
||||
if (lvs->cross_ref ()) {
|
||||
if (! Keys::is_short ()) {
|
||||
stream () << endl << "# Cross reference" << endl;
|
||||
}
|
||||
stream () << Keys::xref_key << "(" << endl;
|
||||
write (lvs->cross_ref ());
|
||||
stream () << ")" << endl;
|
||||
}
|
||||
stream () << Keys::xref_key << "(" << endl;
|
||||
write (lvs->cross_ref ());
|
||||
stream () << ")" << endl;
|
||||
|
||||
if (! Keys::is_short ()) {
|
||||
stream () << endl;
|
||||
|
|
@ -222,11 +228,20 @@ LayoutVsSchematicStandardWriter::LayoutVsSchematicStandardWriter (tl::OutputStre
|
|||
|
||||
void LayoutVsSchematicStandardWriter::do_write_lvs (const db::LayoutVsSchematic *lvs)
|
||||
{
|
||||
if (! lvs->netlist ()) {
|
||||
throw tl::Exception (tl::to_string (tr ("Can't write LVS DB before the netlist has been created")));
|
||||
}
|
||||
if (! lvs->internal_layout ()) {
|
||||
throw tl::Exception (tl::to_string (tr ("Can't write LVS DB before the layout has been loaded")));
|
||||
}
|
||||
|
||||
double dbu = lvs->internal_layout ()->dbu ();
|
||||
|
||||
if (m_short_version) {
|
||||
lvs_std_format::std_writer_impl<lvs_std_format::keys<true> > writer (*mp_stream);
|
||||
lvs_std_format::std_writer_impl<lvs_std_format::keys<true> > writer (*mp_stream, dbu);
|
||||
writer.write (lvs);
|
||||
} else {
|
||||
lvs_std_format::std_writer_impl<lvs_std_format::keys<false> > writer (*mp_stream);
|
||||
lvs_std_format::std_writer_impl<lvs_std_format::keys<false> > writer (*mp_stream, dbu);
|
||||
writer.write (lvs);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,231 @@
|
|||
|
||||
/*
|
||||
|
||||
KLayout Layout Viewer
|
||||
Copyright (C) 2006-2019 Matthias Koefferlein
|
||||
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
*/
|
||||
|
||||
#include "dbNetlistDeviceExtractorClasses.h"
|
||||
#include "dbLayoutVsSchematic.h"
|
||||
#include "dbStream.h"
|
||||
#include "dbDeepRegion.h"
|
||||
#include "dbDeepShapeStore.h"
|
||||
#include "dbReader.h"
|
||||
#include "dbWriter.h"
|
||||
#include "dbCommonReader.h"
|
||||
#include "dbTestSupport.h"
|
||||
#include "dbNetlistSpiceWriter.h" // to create debug files
|
||||
#include "dbNetlistSpiceReader.h"
|
||||
#include "dbNetlistCompare.h"
|
||||
|
||||
#include "tlUnitTest.h"
|
||||
#include "tlString.h"
|
||||
#include "tlFileUtils.h"
|
||||
|
||||
#include <memory>
|
||||
#include <limits>
|
||||
|
||||
static unsigned int define_layer (db::Layout &ly, db::LayerMap &lmap, int gds_layer, int gds_datatype = 0)
|
||||
{
|
||||
unsigned int lid = ly.insert_layer (db::LayerProperties (gds_layer, gds_datatype));
|
||||
lmap.map (ly.get_properties (lid), lid);
|
||||
return lid;
|
||||
}
|
||||
|
||||
TEST(1_BasicFlow)
|
||||
{
|
||||
db::Layout ly;
|
||||
db::LayerMap lmap;
|
||||
|
||||
unsigned int nwell = define_layer (ly, lmap, 1);
|
||||
unsigned int active = define_layer (ly, lmap, 2);
|
||||
unsigned int pplus = define_layer (ly, lmap, 10);
|
||||
unsigned int nplus = define_layer (ly, lmap, 11);
|
||||
unsigned int poly = define_layer (ly, lmap, 3);
|
||||
unsigned int poly_lbl = define_layer (ly, lmap, 3, 1);
|
||||
unsigned int diff_cont = define_layer (ly, lmap, 4);
|
||||
unsigned int poly_cont = define_layer (ly, lmap, 5);
|
||||
unsigned int metal1 = define_layer (ly, lmap, 6);
|
||||
unsigned int metal1_lbl = define_layer (ly, lmap, 6, 1);
|
||||
unsigned int via1 = define_layer (ly, lmap, 7);
|
||||
unsigned int metal2 = define_layer (ly, lmap, 8);
|
||||
unsigned int metal2_lbl = define_layer (ly, lmap, 8, 1);
|
||||
|
||||
{
|
||||
db::LoadLayoutOptions options;
|
||||
options.get_options<db::CommonReaderOptions> ().layer_map = lmap;
|
||||
options.get_options<db::CommonReaderOptions> ().create_other_layers = false;
|
||||
|
||||
std::string fn (tl::testsrc ());
|
||||
fn = tl::combine_path (fn, "testdata");
|
||||
fn = tl::combine_path (fn, "algo");
|
||||
fn = tl::combine_path (fn, "lvs_test_1.gds");
|
||||
|
||||
tl::InputStream stream (fn);
|
||||
db::Reader reader (stream);
|
||||
reader.read (ly, options);
|
||||
}
|
||||
|
||||
db::Cell &tc = ly.cell (*ly.begin_top_down ());
|
||||
db::LayoutVsSchematic lvs (db::RecursiveShapeIterator (ly, tc, std::set<unsigned int> ()));
|
||||
|
||||
std::auto_ptr<db::Region> rbulk (lvs.make_layer ("bulk"));
|
||||
std::auto_ptr<db::Region> rnwell (lvs.make_layer (nwell, "nwell"));
|
||||
std::auto_ptr<db::Region> ractive (lvs.make_layer (active, "active"));
|
||||
std::auto_ptr<db::Region> rpplus (lvs.make_layer (pplus, "pplus"));
|
||||
std::auto_ptr<db::Region> rnplus (lvs.make_layer (nplus, "nplus"));
|
||||
std::auto_ptr<db::Region> rpoly (lvs.make_polygon_layer (poly, "poly"));
|
||||
std::auto_ptr<db::Region> rpoly_lbl (lvs.make_text_layer (poly_lbl, "poly_lbl"));
|
||||
std::auto_ptr<db::Region> rdiff_cont (lvs.make_polygon_layer (diff_cont, "diff_cont"));
|
||||
std::auto_ptr<db::Region> rpoly_cont (lvs.make_polygon_layer (poly_cont, "poly_cont"));
|
||||
std::auto_ptr<db::Region> rmetal1 (lvs.make_polygon_layer (metal1, "metal1"));
|
||||
std::auto_ptr<db::Region> rmetal1_lbl (lvs.make_text_layer (metal1_lbl, "metal1_lbl"));
|
||||
std::auto_ptr<db::Region> rvia1 (lvs.make_polygon_layer (via1, "via1"));
|
||||
std::auto_ptr<db::Region> rmetal2 (lvs.make_polygon_layer (metal2, "metal2"));
|
||||
std::auto_ptr<db::Region> rmetal2_lbl (lvs.make_text_layer (metal2_lbl, "metal2_lbl"));
|
||||
|
||||
// derived regions
|
||||
|
||||
db::Region ractive_in_nwell = *ractive & *rnwell;
|
||||
db::Region rpactive = ractive_in_nwell & *rpplus;
|
||||
db::Region rntie = ractive_in_nwell & *rnplus;
|
||||
db::Region rpgate = rpactive & *rpoly;
|
||||
db::Region rpsd = rpactive - rpgate;
|
||||
|
||||
db::Region ractive_outside_nwell = *ractive - *rnwell;
|
||||
db::Region rnactive = ractive_outside_nwell & *rnplus;
|
||||
db::Region rptie = ractive_outside_nwell & *rpplus;
|
||||
db::Region rngate = rnactive & *rpoly;
|
||||
db::Region rnsd = rnactive - rngate;
|
||||
|
||||
// return the computed layers into the original layout and write it for debugging purposes
|
||||
|
||||
unsigned int lgate = ly.insert_layer (db::LayerProperties (20, 0)); // 20/0 -> Gate
|
||||
unsigned int lsd = ly.insert_layer (db::LayerProperties (21, 0)); // 21/0 -> Source/Drain
|
||||
unsigned int lpdiff = ly.insert_layer (db::LayerProperties (22, 0)); // 22/0 -> P Diffusion
|
||||
unsigned int lndiff = ly.insert_layer (db::LayerProperties (23, 0)); // 23/0 -> N Diffusion
|
||||
unsigned int lptie = ly.insert_layer (db::LayerProperties (24, 0)); // 24/0 -> P Tie
|
||||
unsigned int lntie = ly.insert_layer (db::LayerProperties (25, 0)); // 25/0 -> N Tie
|
||||
|
||||
rpgate.insert_into (&ly, tc.cell_index (), lgate);
|
||||
rngate.insert_into (&ly, tc.cell_index (), lgate);
|
||||
rpsd.insert_into (&ly, tc.cell_index (), lsd);
|
||||
rnsd.insert_into (&ly, tc.cell_index (), lsd);
|
||||
rpsd.insert_into (&ly, tc.cell_index (), lpdiff);
|
||||
rnsd.insert_into (&ly, tc.cell_index (), lndiff);
|
||||
rpsd.insert_into (&ly, tc.cell_index (), lptie);
|
||||
rnsd.insert_into (&ly, tc.cell_index (), lntie);
|
||||
|
||||
db::NetlistDeviceExtractorMOS4Transistor pmos_ex ("PMOS");
|
||||
db::NetlistDeviceExtractorMOS4Transistor nmos_ex ("NMOS");
|
||||
|
||||
// device extraction
|
||||
|
||||
db::NetlistDeviceExtractor::input_layers dl;
|
||||
|
||||
dl["SD"] = &rpsd;
|
||||
dl["G"] = &rpgate;
|
||||
dl["P"] = rpoly.get (); // not needed for extraction but to return terminal shapes
|
||||
dl["W"] = rnwell.get ();
|
||||
lvs.extract_devices (pmos_ex, dl);
|
||||
|
||||
dl["SD"] = &rnsd;
|
||||
dl["G"] = &rngate;
|
||||
dl["P"] = rpoly.get (); // not needed for extraction but to return terminal shapes
|
||||
dl["W"] = rbulk.get ();
|
||||
lvs.extract_devices (nmos_ex, dl);
|
||||
|
||||
// net extraction
|
||||
|
||||
lvs.register_layer (rpsd, "psd");
|
||||
lvs.register_layer (rnsd, "nsd");
|
||||
lvs.register_layer (rptie, "ptie");
|
||||
lvs.register_layer (rntie, "ntie");
|
||||
|
||||
// Intra-layer
|
||||
lvs.connect (rpsd);
|
||||
lvs.connect (rnsd);
|
||||
lvs.connect (*rnwell);
|
||||
lvs.connect (*rpoly);
|
||||
lvs.connect (*rdiff_cont);
|
||||
lvs.connect (*rpoly_cont);
|
||||
lvs.connect (*rmetal1);
|
||||
lvs.connect (*rvia1);
|
||||
lvs.connect (*rmetal2);
|
||||
lvs.connect (rptie);
|
||||
lvs.connect (rntie);
|
||||
// Inter-layer
|
||||
lvs.connect (rpsd, *rdiff_cont);
|
||||
lvs.connect (rnsd, *rdiff_cont);
|
||||
lvs.connect (*rpoly, *rpoly_cont);
|
||||
lvs.connect (*rpoly_cont, *rmetal1);
|
||||
lvs.connect (*rdiff_cont, *rmetal1);
|
||||
lvs.connect (*rdiff_cont, rptie);
|
||||
lvs.connect (*rdiff_cont, rntie);
|
||||
lvs.connect (*rnwell, rntie);
|
||||
lvs.connect (*rmetal1, *rvia1);
|
||||
lvs.connect (*rvia1, *rmetal2);
|
||||
lvs.connect (*rpoly, *rpoly_lbl); // attaches labels
|
||||
lvs.connect (*rmetal1, *rmetal1_lbl); // attaches labels
|
||||
lvs.connect (*rmetal2, *rmetal2_lbl); // attaches labels
|
||||
// Global
|
||||
lvs.connect_global (rptie, "BULK");
|
||||
lvs.connect_global (*rbulk, "BULK");
|
||||
|
||||
// create some mess - we have to keep references to the layers to make them not disappear
|
||||
rmetal1_lbl.reset (0);
|
||||
rmetal2_lbl.reset (0);
|
||||
rpoly_lbl.reset (0);
|
||||
|
||||
lvs.extract_netlist ();
|
||||
|
||||
// doesn't do anything here, but we test that this does not destroy anything:
|
||||
lvs.netlist ()->combine_devices ();
|
||||
|
||||
// make pins for named nets of top-level circuits - this way they are not purged
|
||||
lvs.netlist ()->make_top_level_pins ();
|
||||
lvs.netlist ()->purge ();
|
||||
|
||||
// read the reference netlist
|
||||
{
|
||||
db::NetlistSpiceReader reader;
|
||||
|
||||
std::string fn (tl::testsrc ());
|
||||
fn = tl::combine_path (fn, "testdata");
|
||||
fn = tl::combine_path (fn, "algo");
|
||||
fn = tl::combine_path (fn, "lvs_test_1.spi");
|
||||
|
||||
std::auto_ptr<db::Netlist> netlist (new db::Netlist ());
|
||||
tl::InputStream stream (fn);
|
||||
reader.read (stream, *netlist);
|
||||
lvs.set_reference_netlist (netlist.release ());
|
||||
}
|
||||
|
||||
// perform the compare
|
||||
{
|
||||
db::NetlistComparer comparer;
|
||||
lvs.compare_netlists (&comparer);
|
||||
}
|
||||
|
||||
// produce the output
|
||||
{
|
||||
// @@@
|
||||
lvs.save ("lvs_test_1.lvsdb", false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -71,7 +71,8 @@ SOURCES = \
|
|||
dbDeepEdgesTests.cc \
|
||||
dbDeepEdgePairsTests.cc \
|
||||
dbNetlistCompareTests.cc \
|
||||
dbNetlistReaderTests.cc
|
||||
dbNetlistReaderTests.cc \
|
||||
dbLayoutVsSchematicTests.cc
|
||||
|
||||
INCLUDEPATH += $$TL_INC $$DB_INC $$GSI_INC
|
||||
DEPENDPATH += $$TL_INC $$DB_INC $$GSI_INC
|
||||
|
|
|
|||
Loading…
Reference in New Issue