2022-07-20 08:14:38 -05:00
|
|
|
// -*- C++ -*-
|
|
|
|
//
|
|
|
|
// This file is part of the Coriolis Software.
|
|
|
|
// Copyright (c) SU 2022-2022, All Rights Reserved
|
|
|
|
//
|
|
|
|
// +-----------------------------------------------------------------+
|
|
|
|
// | C O R I O L I S |
|
|
|
|
// | S e a b r e e z e - Timing Analysis |
|
|
|
|
// | |
|
|
|
|
// | Author : Vu Hoang Anh PHAM |
|
|
|
|
// | E-mail : Jean-Paul.Chaput@lip6.fr |
|
|
|
|
// | =============================================================== |
|
|
|
|
// | C++ Module : "./Elmore.cpp" |
|
|
|
|
// +-----------------------------------------------------------------+
|
|
|
|
|
|
|
|
#include "hurricane/DebugSession.h"
|
|
|
|
#include "hurricane/Error.h"
|
2022-07-21 18:01:21 -05:00
|
|
|
#include "hurricane/Segment.h"
|
|
|
|
#include "hurricane/Plug.h"
|
|
|
|
#include "hurricane/Net.h"
|
2022-07-20 08:14:38 -05:00
|
|
|
#include "seabreeze/Elmore.h"
|
2022-07-21 12:24:42 -05:00
|
|
|
#include "seabreeze/Node.h"
|
2022-07-20 08:14:38 -05:00
|
|
|
#include "seabreeze/SeabreezeEngine.h"
|
|
|
|
|
|
|
|
namespace Seabreeze {
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
using Hurricane::Error;
|
|
|
|
using Hurricane::DBo;
|
|
|
|
using Hurricane::DbU;
|
2022-07-21 18:01:21 -05:00
|
|
|
using Hurricane::Plug;
|
2022-07-20 08:14:38 -05:00
|
|
|
using Hurricane::Net;
|
|
|
|
using Hurricane::Cell;
|
|
|
|
using Hurricane::Instance;
|
|
|
|
using Hurricane::Property;
|
|
|
|
using Hurricane::PrivateProperty;
|
|
|
|
using Hurricane::Component;
|
|
|
|
using Hurricane::Segment;
|
|
|
|
using Hurricane::DebugSession;
|
|
|
|
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// Class : "Elmore"
|
|
|
|
|
|
|
|
Elmore::Elmore ( Net* net )
|
|
|
|
: _seabreeze(nullptr)
|
2022-07-21 18:01:21 -05:00
|
|
|
, _net (net)
|
|
|
|
, _driver (nullptr)
|
|
|
|
, _tree (new Tree(this))
|
|
|
|
, _delays ()
|
2022-07-20 08:14:38 -05:00
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
|
|
Elmore::~Elmore ()
|
|
|
|
{
|
2022-07-21 12:24:42 -05:00
|
|
|
cdebug_log(199,0) << "Elmore::~Elmore() " << endl;
|
2022-07-20 08:14:38 -05:00
|
|
|
delete _tree;
|
2022-07-21 18:01:21 -05:00
|
|
|
for ( Delay* delay : _delays ) delete delay;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Elmore::setup ()
|
|
|
|
{
|
|
|
|
cdebug_log(199,1) << "Elmore::findDriver()" << endl;
|
|
|
|
|
|
|
|
for ( RoutingPad* rp : _net->getRoutingPads() ) {
|
|
|
|
Plug* plug = static_cast<Plug*>( rp->getPlugOccurrence().getEntity() );
|
|
|
|
if (plug->getMasterNet()->getDirection() & Net::Direction::DirOut) {
|
|
|
|
if (_driver) {
|
|
|
|
cerr << Error( "Elmore::setup(): %s has more than one driver:\n"
|
|
|
|
" * Using: %s\n"
|
|
|
|
" * Ignoring: %s"
|
|
|
|
, getString(_net).c_str()
|
|
|
|
, getString(_driver).c_str()
|
|
|
|
, getString(rp).c_str()
|
|
|
|
) << endl;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
_driver = rp;
|
|
|
|
} else {
|
|
|
|
_delays.push_back( new Delay(this,rp) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
cdebug_log(199,0) << "Found " << _delays.size() << " sink points:" << endl;
|
|
|
|
for ( Delay* delay : _delays ) {
|
|
|
|
cdebug_log(199,0) << "| " << delay << endl;
|
|
|
|
}
|
|
|
|
if (not _driver) {
|
|
|
|
cerr << Error( "Elmore::_postCreate(): No driver found on %s, aborting."
|
|
|
|
, getString(_net).c_str() ) << endl;
|
|
|
|
cdebug_tabw(199,-1);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
cdebug_tabw(199,-1);
|
2022-07-20 08:14:38 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const Configuration* Elmore::getConfiguration () const
|
|
|
|
{ return _seabreeze->getConfiguration(); }
|
|
|
|
|
|
|
|
|
2022-07-21 18:01:21 -05:00
|
|
|
Delay* Elmore::getDelay ( RoutingPad* rp ) const
|
2022-07-20 08:14:38 -05:00
|
|
|
{
|
2022-07-21 18:01:21 -05:00
|
|
|
for ( Delay* delay : _delays ) {
|
|
|
|
if (delay->getSink() == rp) return delay;
|
2022-07-20 08:14:38 -05:00
|
|
|
}
|
2022-07-21 18:01:21 -05:00
|
|
|
return nullptr;
|
2022-07-20 08:14:38 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-07-21 18:01:21 -05:00
|
|
|
void Elmore::buildTree ()
|
2022-07-20 08:14:38 -05:00
|
|
|
{
|
2022-07-21 18:01:21 -05:00
|
|
|
if (not _driver) {
|
|
|
|
cerr << Error( "Elmore::buildTree(): Net has no driver, aborting." ) << endl;
|
2022-07-20 08:14:38 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Contact* rootContact = nullptr;
|
2022-07-21 18:01:21 -05:00
|
|
|
for ( Component* component : _driver->getSlaveComponents() ) {
|
2022-07-20 08:14:38 -05:00
|
|
|
Contact* contact = dynamic_cast<Contact*>(component);
|
|
|
|
if (contact) {
|
|
|
|
rootContact = contact;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (not rootContact) {
|
|
|
|
cerr << Error( "Elmore::buildTree(): No Contact anchored on %s."
|
2022-07-21 18:01:21 -05:00
|
|
|
, getString(_driver).c_str() ) << endl;
|
2022-07-20 08:14:38 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
cdebug_log(199,1) << "Elmore::buildTree()" << endl;
|
|
|
|
cdebug_log(199,0) << "Root contact " << rootContact << endl;
|
|
|
|
|
|
|
|
Node* rootNode = new Node( nullptr, rootContact );
|
|
|
|
double R = 0;
|
|
|
|
double C = 0;
|
|
|
|
setRC( &R, &C, rootContact, nullptr );
|
|
|
|
rootNode->setR( R );
|
|
|
|
rootNode->setC( (C == 0.0) ? 0.0 : 1/C );
|
|
|
|
|
|
|
|
Segment* segment = nullptr;
|
|
|
|
size_t count = 0;
|
|
|
|
for ( Component* component : rootContact->getSlaveComponents() ) {
|
|
|
|
segment = dynamic_cast<Segment*>( component );
|
|
|
|
if (not segment) continue;
|
|
|
|
++count;
|
|
|
|
}
|
|
|
|
if (count != 1) {
|
|
|
|
cerr << Error( "Elmore::buildTree(): Terminal contact has more than one segment (%d), aborting.\n"
|
|
|
|
" (on %s)"
|
|
|
|
, count
|
|
|
|
, getString(rootContact).c_str()
|
|
|
|
) << endl;
|
|
|
|
cdebug_tabw(199,-1);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
buildFromNode( rootNode, segment );
|
|
|
|
cdebug_log(199,0) << "Elmore::buildTree() - Finished" << endl;
|
|
|
|
cdebug_tabw(199,-1);
|
|
|
|
|
|
|
|
_tree->print( cerr );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Elmore::buildFromNode ( Node* rootNode, Segment* toSegment )
|
|
|
|
{
|
|
|
|
if (not rootNode->contact()) {
|
|
|
|
cerr << Error( "Elmore::buildFromNode(): rootNode has no contact, aborting." ) << endl;
|
|
|
|
return;
|
|
|
|
}
|
2022-07-20 10:49:43 -05:00
|
|
|
_tree->addNode( rootNode );
|
2022-07-20 08:14:38 -05:00
|
|
|
|
|
|
|
cdebug_log(199,1) << "Elmore::buildFromNode()" << endl;
|
|
|
|
cdebug_log(199,0) << "rootNode->_contact=" << rootNode->contact() << endl;
|
|
|
|
cdebug_log(199,0) << "toSegment=" << toSegment << endl;
|
|
|
|
|
|
|
|
Contact* opposite = dynamic_cast<Contact*>( toSegment->getOppositeAnchor( rootNode->contact()) );
|
|
|
|
if (not opposite or (rootNode->parent() and (opposite == rootNode->parent()->contact())) ) {
|
|
|
|
cdebug_tabw(199,-1);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
cdebug_log(199,0) << "opposite=" << opposite << endl;
|
|
|
|
|
|
|
|
double Rb = 0;
|
|
|
|
double Cb = 0;
|
|
|
|
opposite = buildBranch( &Rb, &Cb, opposite );
|
|
|
|
if (not opposite) {
|
|
|
|
cerr << Error( "Elmore::buildFromNode(): Branch end up on NULL Contact, pruned." ) << endl;
|
|
|
|
cdebug_tabw(199,-1);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
cdebug_log(199,0) << "Reached fork " << opposite << endl;
|
|
|
|
|
|
|
|
Node* node = new Node( rootNode, opposite);
|
|
|
|
node->setR( Rb );
|
|
|
|
node->setC( (Cb == 0.0) ? 0.0 : 1/Cb );
|
|
|
|
cdebug_log(199,0) << "R=" << Rb << " C=" << Cb << endl;
|
|
|
|
|
|
|
|
int count = 0;
|
|
|
|
for ( Component* component : opposite->getSlaveComponents() ) {
|
|
|
|
count += (dynamic_cast<Segment*>(component)) ? 1 : 0;
|
|
|
|
}
|
|
|
|
cdebug_log(199,0) << "Node's contact has : " << count << " segments" << endl;
|
|
|
|
|
|
|
|
if (count == 1) {
|
2022-07-20 10:49:43 -05:00
|
|
|
_tree->addNode( node );
|
2022-07-20 08:14:38 -05:00
|
|
|
} else if (count > 2) {
|
|
|
|
for ( Component* component : opposite->getSlaveComponents() ) {
|
|
|
|
Segment* segment = dynamic_cast<Segment*>( component );
|
|
|
|
if (not segment) continue;
|
|
|
|
cdebug_log(199,0) << "| " << segment << endl;
|
|
|
|
|
|
|
|
Contact* target = dynamic_cast<Contact*>( segment->getOppositeAnchor(opposite) );
|
|
|
|
if (not target) {
|
|
|
|
cerr << Error( "Elmore::buildFromNode(): Segment missing opposite anchor. pruned." ) << endl;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
cdebug_log(199,0) << "target=" << target << endl;
|
|
|
|
|
2022-07-21 18:01:21 -05:00
|
|
|
if (not _tree->isReached(target)) {
|
2022-07-20 08:14:38 -05:00
|
|
|
buildFromNode( node, segment);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
cdebug_tabw(199,-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Contact* Elmore::buildBranch ( double* R, double* C, Contact* current )
|
|
|
|
{
|
|
|
|
cdebug_log(199,1) << "Elmore::buildBranch()" << endl;
|
|
|
|
cdebug_log(199,0) << "current=" << current << endl;
|
|
|
|
|
|
|
|
while ( true ) {
|
2022-07-21 18:01:21 -05:00
|
|
|
_tree->setReached( current );
|
2022-07-20 08:14:38 -05:00
|
|
|
int count = 0;
|
|
|
|
Segment* segment = nullptr;
|
|
|
|
for ( Component* component : current->getSlaveComponents() ) {
|
|
|
|
segment = dynamic_cast<Segment*>( component );
|
|
|
|
if (not segment) continue;
|
|
|
|
|
|
|
|
Contact* opposite = dynamic_cast<Contact*>( segment->getOppositeAnchor(current) );
|
2022-07-21 18:01:21 -05:00
|
|
|
if (opposite and _tree->isReached(opposite)) {
|
2022-07-20 08:14:38 -05:00
|
|
|
setRC( R, C, current, segment );
|
|
|
|
cdebug_log(199,0) << "current=" << current << endl;
|
|
|
|
cdebug_log(199,0) << "segment=" << segment << endl;
|
|
|
|
cdebug_log(199,0) << "R=" << *R << endl;
|
|
|
|
cdebug_log(199,0) << "C=" << *C << endl;
|
|
|
|
}
|
|
|
|
++count;
|
|
|
|
}
|
|
|
|
if (not count) {
|
|
|
|
cerr << Error( "Elmore::buildBranch(): Contact seems to be a dead end, pruning.\n"
|
|
|
|
" (on %s)"
|
|
|
|
, getString(current).c_str() ) << endl;
|
|
|
|
cdebug_tabw(199,-1);
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
else if (count != 2) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
Contact* next = nullptr;
|
|
|
|
for ( Component* component : current->getSlaveComponents() ) {
|
|
|
|
segment = dynamic_cast<Segment*>( component );
|
|
|
|
if (not segment) continue;
|
|
|
|
|
|
|
|
cdebug_log(199,0) << "| " << segment << endl;
|
|
|
|
|
|
|
|
Contact* opposite = dynamic_cast<Contact*>( segment->getOppositeAnchor(current) );
|
|
|
|
cdebug_log(199,0) << "opposite=" << opposite << endl;
|
2022-07-21 18:01:21 -05:00
|
|
|
if (opposite and not _tree->isReached(opposite))
|
2022-07-20 08:14:38 -05:00
|
|
|
next = opposite;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (not next) {
|
|
|
|
cerr << Error( "Elmore::buildBranch(): Wire loop detected.\n"
|
|
|
|
" (on %s)"
|
|
|
|
, getString(current).c_str() ) << endl;
|
|
|
|
cdebug_tabw(199,-1);
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
current = next;
|
|
|
|
}
|
|
|
|
|
|
|
|
cdebug_tabw(199,-1);
|
|
|
|
return current;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Elmore::setRC ( double* R, double* C, Contact* contact, Segment* segment )
|
|
|
|
{
|
|
|
|
double Rcont = getConfiguration()->getRct();
|
|
|
|
//double Hcont = DbU::toPhysical( contact->getHeight(), DbU::UnitPower::Nano );
|
|
|
|
//double Wcont = DbU::toPhysical( contact->getWidth (), DbU::UnitPower::Nano );
|
|
|
|
double Wcont = DbU::toLambda( contact->getWidth () );
|
|
|
|
double Acont = Wcont * Wcont;
|
|
|
|
*R += Rcont * Acont;
|
|
|
|
|
|
|
|
if (not segment) {
|
|
|
|
*C = 0;
|
|
|
|
} else {
|
|
|
|
double Rseg = getConfiguration()->getRsm();
|
|
|
|
double Cseg = getConfiguration()->getCsm();
|
|
|
|
//double Lseg = DbU::toPhysical( segment->getLength(), DbU::UnitPower::Nano );
|
|
|
|
//double Wseg = DbU::toPhysical( segment->getWidth (), DbU::UnitPower::Nano );
|
|
|
|
double Lseg = DbU::toLambda( segment->getLength() );
|
|
|
|
double Wseg = DbU::toLambda( segment->getWidth () );
|
|
|
|
double Aseg = Lseg * Wseg;
|
|
|
|
cdebug_log(199,0) << "Elmore::setRC() on " << segment << endl;
|
|
|
|
cdebug_log(199,0) << " Lseg=" << Lseg << " Wseg=" << Wseg << " Aseg=" << Aseg << endl;
|
|
|
|
*R += Rseg * Aseg;
|
|
|
|
*C += (Aseg) ? 1/(Cseg*Aseg) : 0.0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-07-21 18:01:21 -05:00
|
|
|
Delay* Elmore::delayElmore ( RoutingPad* rp )
|
2022-07-20 10:49:43 -05:00
|
|
|
{ return _tree->computeElmoreDelay( rp ); }
|
2022-07-20 08:14:38 -05:00
|
|
|
|
|
|
|
|
2022-07-21 18:01:21 -05:00
|
|
|
void Elmore::toTree ( ostream& os ) const
|
2022-07-20 08:14:38 -05:00
|
|
|
{ _tree->print( os ); }
|
|
|
|
|
|
|
|
|
2022-07-21 18:01:21 -05:00
|
|
|
string Elmore::_getTypeName () const
|
2022-07-21 12:24:42 -05:00
|
|
|
{ return "Seabreeze::Elmore"; }
|
|
|
|
|
|
|
|
|
|
|
|
string Elmore::_getString () const
|
|
|
|
{
|
|
|
|
string s = "<" + _getTypeName() + ">";
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Record* Elmore::_getRecord () const
|
|
|
|
{
|
|
|
|
Record* record = new Record ( _getString() );
|
|
|
|
if (record != nullptr) {
|
|
|
|
record->add( getSlot("_seabreeze", _seabreeze) );
|
2022-07-21 18:01:21 -05:00
|
|
|
record->add( getSlot("_net" , _net ) );
|
|
|
|
record->add( getSlot("_driver" , _driver ) );
|
2022-07-21 12:24:42 -05:00
|
|
|
record->add( getSlot("_tree" , _tree ) );
|
2022-07-21 18:01:21 -05:00
|
|
|
record->add( getSlot("_delays" , &_delays ) );
|
2022-07-21 12:24:42 -05:00
|
|
|
}
|
|
|
|
return record;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-07-20 08:14:38 -05:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// Class : "ElmoreProperty"
|
|
|
|
|
|
|
|
Name ElmoreProperty::_name = "Seabreeze::Elmore";
|
|
|
|
|
|
|
|
|
|
|
|
ElmoreProperty::ElmoreProperty ( Net* net )
|
|
|
|
: PrivateProperty()
|
|
|
|
, _elmore (net)
|
|
|
|
{
|
|
|
|
if (net) {
|
|
|
|
SeabreezeEngine* seabreeze
|
|
|
|
= dynamic_cast<SeabreezeEngine*>( ToolEngine::get( net->getCell(), "Seabreeze" ));
|
|
|
|
if (not seabreeze) {
|
|
|
|
cerr << Error( "ElmoreProperty::ElmoreProperty(): Cannot find SeabreezeEngine on %s."
|
|
|
|
, getString(net->getCell()).c_str()) << endl;
|
|
|
|
}
|
|
|
|
_elmore.setSeabreeze( seabreeze );
|
2022-07-21 18:01:21 -05:00
|
|
|
_elmore.setup();
|
2022-07-20 08:14:38 -05:00
|
|
|
}
|
2022-07-21 12:24:42 -05:00
|
|
|
cdebug_log(199,0) << "ElmoreProperty::ElmoreProperty() on " << net << endl;
|
2022-07-20 08:14:38 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ElmoreProperty* ElmoreProperty::create ( Net* net )
|
|
|
|
{
|
|
|
|
ElmoreProperty* property = new ElmoreProperty( net );
|
|
|
|
property->_postCreate();
|
|
|
|
return property;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Name ElmoreProperty::staticGetName ()
|
|
|
|
{ return _name; }
|
|
|
|
|
|
|
|
|
|
|
|
Name ElmoreProperty::getName () const
|
|
|
|
{ return _name; }
|
|
|
|
|
|
|
|
|
|
|
|
string ElmoreProperty::_getTypeName () const
|
|
|
|
{ return "ElmoreProperty"; }
|
|
|
|
|
|
|
|
|
2022-07-21 12:24:42 -05:00
|
|
|
string ElmoreProperty::_getString () const
|
|
|
|
{
|
|
|
|
string s = PrivateProperty::_getString ();
|
|
|
|
s.insert ( s.length() - 1 , " " + getString(&_elmore) );
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Record* ElmoreProperty::_getRecord () const
|
|
|
|
{
|
|
|
|
Record* record = PrivateProperty::_getRecord();
|
|
|
|
if ( record ) {
|
|
|
|
record->add( getSlot( "_name" , _name ) );
|
|
|
|
record->add( getSlot( "_elmore", &_elmore ) );
|
|
|
|
}
|
|
|
|
return record;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-07-20 08:14:38 -05:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// Class : "ElmoreExtension"
|
|
|
|
|
|
|
|
Elmore* ElmoreExtension::create ( Net* net )
|
|
|
|
{
|
2022-07-21 12:24:42 -05:00
|
|
|
cdebug_log(199,1) << "ElmoreExtension::create() " << net << endl;
|
2022-07-20 08:14:38 -05:00
|
|
|
Elmore* elmore = get( net );
|
|
|
|
if (not elmore) {
|
|
|
|
ElmoreProperty* property = new ElmoreProperty( net );
|
|
|
|
net->put( property );
|
|
|
|
elmore = property->getElmore();
|
|
|
|
}
|
2022-07-21 12:24:42 -05:00
|
|
|
cdebug_tabw(199,-1);
|
2022-07-20 08:14:38 -05:00
|
|
|
return elmore;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ElmoreExtension::destroy ( Net* net )
|
|
|
|
{
|
2022-07-21 12:24:42 -05:00
|
|
|
cdebug_log(199,1) << "ElmoreExtension::destroy() " << net << endl;
|
2022-07-20 08:14:38 -05:00
|
|
|
Property* property = net->getProperty( ElmoreProperty::staticGetName() );
|
|
|
|
if (property) net->remove( property );
|
2022-07-21 12:24:42 -05:00
|
|
|
cdebug_tabw(199,-1);
|
2022-07-20 08:14:38 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Elmore* ElmoreExtension::get ( Net* net )
|
|
|
|
{
|
|
|
|
Elmore* elmore = nullptr;
|
|
|
|
Property* property = net->getProperty( ElmoreProperty::staticGetName() );
|
|
|
|
if (property) elmore = static_cast<ElmoreProperty*>( property )->getElmore();
|
|
|
|
return elmore;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} // Seabreeze namespace.
|