2010-03-09 09:24:55 -06:00
|
|
|
|
|
|
|
// -*- C++ -*-
|
|
|
|
//
|
|
|
|
// This file is part of the Coriolis Software.
|
2010-04-23 08:14:17 -05:00
|
|
|
// Copyright (c) UPMC/LIP6 2008-2010, All Rights Reserved
|
2010-03-09 09:24:55 -06:00
|
|
|
//
|
|
|
|
// ===================================================================
|
|
|
|
//
|
|
|
|
// $Id$
|
|
|
|
//
|
|
|
|
// x-----------------------------------------------------------------x
|
|
|
|
// | |
|
|
|
|
// | C O R I O L I S |
|
|
|
|
// | K i t e - D e t a i l e d R o u t e r |
|
|
|
|
// | |
|
|
|
|
// | Author : Jean-Paul CHAPUT |
|
|
|
|
// | E-mail : Jean-Paul.Chaput@asim.lip6.fr |
|
|
|
|
// | =============================================================== |
|
2010-04-23 08:14:17 -05:00
|
|
|
// | C++ Module : "./Configuration.cpp" |
|
2010-03-09 09:24:55 -06:00
|
|
|
// | *************************************************************** |
|
|
|
|
// | U p d a t e s |
|
|
|
|
// | |
|
|
|
|
// x-----------------------------------------------------------------x
|
|
|
|
|
|
|
|
|
2010-04-28 10:44:07 -05:00
|
|
|
#include <string>
|
|
|
|
|
2010-06-18 09:03:38 -05:00
|
|
|
#include "vlsisapd/configuration/Configuration.h"
|
2010-04-28 10:44:07 -05:00
|
|
|
#include "hurricane/Cell.h"
|
|
|
|
#include "crlcore/Utilities.h"
|
2010-03-09 09:24:55 -06:00
|
|
|
#include "kite/Configuration.h"
|
|
|
|
#include "kite/KiteEngine.h"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace Kite {
|
|
|
|
|
|
|
|
|
2010-04-28 10:44:07 -05:00
|
|
|
using std::cout;
|
|
|
|
using std::cerr;
|
|
|
|
using std::endl;
|
|
|
|
using std::ostringstream;
|
|
|
|
using Hurricane::tab;
|
|
|
|
using Hurricane::inltrace;
|
|
|
|
using Hurricane::Error;
|
|
|
|
using Hurricane::Technology;
|
2010-03-09 09:24:55 -06:00
|
|
|
|
|
|
|
|
|
|
|
// -------------------------------------------------------------------
|
|
|
|
// Class : "Kite::Configuration".
|
|
|
|
|
|
|
|
|
|
|
|
Configuration::Configuration ( Katabatic::Configuration* base )
|
|
|
|
: Katabatic::Configuration()
|
|
|
|
, _base (base)
|
|
|
|
, _postEventCb ()
|
2010-06-18 09:03:38 -05:00
|
|
|
, _edgeCapacityPercent(Cfg::getParamPercentage("kite.edgeCapacity", 80.0)->asDouble())
|
|
|
|
, _expandStep (Cfg::getParamPercentage("kite.expandStep" ,100.0)->asDouble())
|
2010-03-09 09:24:55 -06:00
|
|
|
, _ripupLimits ()
|
2010-06-22 08:59:22 -05:00
|
|
|
, _ripupCost (Cfg::getParamInt("kite.ripupCost" , 3)->asInt())
|
|
|
|
, _eventsLimit (Cfg::getParamInt("kite.eventsLimit" ,4000000)->asInt())
|
2010-03-09 09:24:55 -06:00
|
|
|
{
|
2010-06-22 08:59:22 -05:00
|
|
|
_ripupLimits[BorderRipupLimit] = Cfg::getParamInt("kite.borderRipupLimit" ,26)->asInt();
|
|
|
|
_ripupLimits[StrapRipupLimit] = Cfg::getParamInt("kite.strapRipupLimit" ,16)->asInt();
|
|
|
|
_ripupLimits[LocalRipupLimit] = Cfg::getParamInt("kite.localRipupLimit" , 7)->asInt();
|
|
|
|
_ripupLimits[GlobalRipupLimit] = Cfg::getParamInt("kite.globalRipupLimit" , 5)->asInt();
|
|
|
|
_ripupLimits[LongGlobalRipupLimit] = Cfg::getParamInt("kite.longGlobalRipupLimit" , 5)->asInt();
|
2010-08-22 07:38:27 -05:00
|
|
|
|
|
|
|
for ( size_t i=0 ; i<MaxMetalDepth ; ++i ) {
|
|
|
|
ostringstream paramName;
|
|
|
|
paramName << "kite.metal" << (i+1) << "MinBreak";
|
|
|
|
|
|
|
|
int threshold = 29*50;
|
|
|
|
switch ( i ) {
|
|
|
|
case 0:
|
|
|
|
case 1:
|
|
|
|
threshold = 2*50;
|
|
|
|
break;
|
|
|
|
default:
|
* ./Kite:
- New: In BuildPowerRails, special processing for the power ring segments.
The "diagonal" of vias at each corner is causing a misbehavior of the
routing algorithm (due to fully saturated GCells in one direction).
As a temporary fix, extend the segments so they form a "square corner".
(problem arise on "d_in_i(22)").
- New: In RoutingEvent::_processNegociate, disable the "isForcedToHint()"
feature. No noticeable loss of quality or speed.
- New: In TrackElement/TrackSegment, wraps the AutoSegment parent's mechanism.
Allows to gets the DataNegociate of either the segment or it's parent.
- New: State::solveFullBlockages(), dedicated method to solves the case when
all the allowed tracks of a segment are blocked, tries to moves up
local segments and to break-up global ones.
- New: RoutingEventLoop, a more sophisticated way to detect looping.
Maintain a dynamic histogram of the last N (default 10) segments routeds,
with the count of how many times they have occurred. If that count
exeed 40, we *may* be facing a loop.
- Change: In State::conflictSolve1, implement new policy. The global segments
no more can be broken by local ones. The idea behind is that breaking
a global on the request of a local will only produce more cluttering
in the GCell. Globals must be keep straigth pass through, especially
inside near-saturated GCells. Globals breaking however can occurs at
another global's request.
- Change: In TrackCost, implement the new policy about locals segments that
cannot break globals segments. The sorting class now accept flags to
modulate the sorting function. Two options avalaibles: IgnoreAxisWeigth
(to uses for strap segments) and DiscardGlobals (to uses with locals).
- Change: In TrackCost, the "distance to fixed" have now an upper bound of
50 lambdas (no need to be greater because it means it's outside the
begin & en GCells). Take account not only of fixed segment, but also
of placed segments which makes bound.
- Bug: In Track::_check(), while calling each individual TrackSegment check,
uses it as the *first* argument of the "or", otherwise it may not be
called.
- Bug: In ProtectRoutingPad, loop over segment Collections while modificating
it was producing non-deterministic results. The fact that a collection
must be not modificated while beeing iterated is becoming a more and more
painful problem.
2010-12-30 12:42:17 -06:00
|
|
|
threshold = 30*50;
|
2010-08-22 07:38:27 -05:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
* ./Kite:
- New: In BuildPowerRails, special processing for the power ring segments.
The "diagonal" of vias at each corner is causing a misbehavior of the
routing algorithm (due to fully saturated GCells in one direction).
As a temporary fix, extend the segments so they form a "square corner".
(problem arise on "d_in_i(22)").
- New: In RoutingEvent::_processNegociate, disable the "isForcedToHint()"
feature. No noticeable loss of quality or speed.
- New: In TrackElement/TrackSegment, wraps the AutoSegment parent's mechanism.
Allows to gets the DataNegociate of either the segment or it's parent.
- New: State::solveFullBlockages(), dedicated method to solves the case when
all the allowed tracks of a segment are blocked, tries to moves up
local segments and to break-up global ones.
- New: RoutingEventLoop, a more sophisticated way to detect looping.
Maintain a dynamic histogram of the last N (default 10) segments routeds,
with the count of how many times they have occurred. If that count
exeed 40, we *may* be facing a loop.
- Change: In State::conflictSolve1, implement new policy. The global segments
no more can be broken by local ones. The idea behind is that breaking
a global on the request of a local will only produce more cluttering
in the GCell. Globals must be keep straigth pass through, especially
inside near-saturated GCells. Globals breaking however can occurs at
another global's request.
- Change: In TrackCost, implement the new policy about locals segments that
cannot break globals segments. The sorting class now accept flags to
modulate the sorting function. Two options avalaibles: IgnoreAxisWeigth
(to uses for strap segments) and DiscardGlobals (to uses with locals).
- Change: In TrackCost, the "distance to fixed" have now an upper bound of
50 lambdas (no need to be greater because it means it's outside the
begin & en GCells). Take account not only of fixed segment, but also
of placed segments which makes bound.
- Bug: In Track::_check(), while calling each individual TrackSegment check,
uses it as the *first* argument of the "or", otherwise it may not be
called.
- Bug: In ProtectRoutingPad, loop over segment Collections while modificating
it was producing non-deterministic results. The fact that a collection
must be not modificated while beeing iterated is becoming a more and more
painful problem.
2010-12-30 12:42:17 -06:00
|
|
|
Cfg::getParamDouble(paramName.str())->setDouble(threshold);
|
|
|
|
_globalMinBreaks[i] = DbU::lambda (Cfg::getParamDouble(paramName.str())->asDouble());
|
2010-08-22 07:38:27 -05:00
|
|
|
}
|
2010-03-09 09:24:55 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-04-28 10:44:07 -05:00
|
|
|
Configuration::Configuration ( const Configuration& other, Katabatic::Configuration* base )
|
|
|
|
: Katabatic::Configuration()
|
|
|
|
, _base (base)
|
|
|
|
, _postEventCb (other._postEventCb)
|
|
|
|
, _edgeCapacityPercent(other._edgeCapacityPercent)
|
|
|
|
, _expandStep (other._expandStep)
|
|
|
|
, _ripupLimits ()
|
|
|
|
, _ripupCost (other._ripupCost)
|
|
|
|
, _eventsLimit (other._eventsLimit)
|
|
|
|
{
|
|
|
|
if ( _base == NULL ) _base = other._base->clone();
|
|
|
|
|
|
|
|
_ripupLimits[BorderRipupLimit] = other._ripupLimits[BorderRipupLimit];
|
|
|
|
_ripupLimits[StrapRipupLimit] = other._ripupLimits[StrapRipupLimit];
|
|
|
|
_ripupLimits[LocalRipupLimit] = other._ripupLimits[LocalRipupLimit];
|
|
|
|
_ripupLimits[GlobalRipupLimit] = other._ripupLimits[GlobalRipupLimit];
|
|
|
|
_ripupLimits[LongGlobalRipupLimit] = other._ripupLimits[LongGlobalRipupLimit];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-09 09:24:55 -06:00
|
|
|
Configuration::~Configuration ()
|
|
|
|
{ }
|
|
|
|
|
|
|
|
|
2010-04-28 10:44:07 -05:00
|
|
|
Configuration* Configuration::clone () const
|
|
|
|
{ return new Configuration(*this); }
|
|
|
|
|
|
|
|
|
|
|
|
Configuration* Configuration::clone ( KiteEngine* kite ) const
|
|
|
|
{ return new Configuration(*this,kite->base()->getKatabaticConfiguration()); }
|
|
|
|
|
|
|
|
|
2010-03-09 09:24:55 -06:00
|
|
|
bool Configuration::isGMetal ( const Layer* layer ) const
|
|
|
|
{ return _base->isGMetal(layer); }
|
|
|
|
|
|
|
|
|
|
|
|
size_t Configuration::getDepth () const
|
|
|
|
{ return _base->getDepth(); }
|
|
|
|
|
|
|
|
|
2010-05-27 11:12:44 -05:00
|
|
|
size_t Configuration::getAllowedDepth () const
|
|
|
|
{ return _base->getAllowedDepth(); }
|
|
|
|
|
|
|
|
|
2010-03-09 09:24:55 -06:00
|
|
|
size_t Configuration::getLayerDepth ( const Layer* layer ) const
|
|
|
|
{ return _base->getLayerDepth(layer); }
|
|
|
|
|
|
|
|
|
|
|
|
RoutingGauge* Configuration::getRoutingGauge () const
|
|
|
|
{ return _base->getRoutingGauge(); }
|
|
|
|
|
|
|
|
|
|
|
|
RoutingLayerGauge* Configuration::getLayerGauge ( size_t depth ) const
|
|
|
|
{ return _base->getLayerGauge(depth); }
|
|
|
|
|
|
|
|
|
|
|
|
const Layer* Configuration::getRoutingLayer ( size_t depth ) const
|
|
|
|
{ return _base->getRoutingLayer(depth); }
|
|
|
|
|
|
|
|
|
|
|
|
Layer* Configuration::getContactLayer ( size_t depth ) const
|
|
|
|
{ return _base->getContactLayer(depth); }
|
|
|
|
|
|
|
|
|
|
|
|
DbU::Unit Configuration::getExtensionCap () const
|
|
|
|
{ return _base->getExtensionCap(); }
|
|
|
|
|
|
|
|
|
|
|
|
float Configuration::getSaturateRatio () const
|
|
|
|
{ return _base->getSaturateRatio(); }
|
|
|
|
|
|
|
|
|
2010-08-18 15:25:02 -05:00
|
|
|
size_t Configuration::getSaturateRp () const
|
|
|
|
{ return _base->getSaturateRp(); }
|
|
|
|
|
|
|
|
|
2010-03-09 09:24:55 -06:00
|
|
|
DbU::Unit Configuration::getGlobalThreshold () const
|
|
|
|
{ return _base->getGlobalThreshold(); }
|
|
|
|
|
2010-08-22 07:38:27 -05:00
|
|
|
size_t Configuration::getHEdgeCapacity () const
|
|
|
|
{ return _base->getHEdgeCapacity(); }
|
|
|
|
|
|
|
|
|
|
|
|
size_t Configuration::getVEdgeCapacity () const
|
|
|
|
{ return _base->getVEdgeCapacity(); }
|
|
|
|
|
2010-03-09 09:24:55 -06:00
|
|
|
|
2010-05-27 11:12:44 -05:00
|
|
|
void Configuration::setAllowedDepth ( size_t allowedDepth )
|
|
|
|
{ _base->setAllowedDepth(allowedDepth); }
|
|
|
|
|
|
|
|
|
2010-03-09 09:24:55 -06:00
|
|
|
void Configuration::setSaturateRatio ( float ratio )
|
|
|
|
{ _base->setSaturateRatio(ratio); }
|
|
|
|
|
|
|
|
|
2010-08-18 15:25:02 -05:00
|
|
|
void Configuration::setSaturateRp ( size_t threshold )
|
|
|
|
{ _base->setSaturateRp(threshold); }
|
|
|
|
|
|
|
|
|
2010-03-09 09:24:55 -06:00
|
|
|
void Configuration::setGlobalThreshold ( DbU::Unit threshold )
|
|
|
|
{ _base->setGlobalThreshold(threshold); }
|
|
|
|
|
|
|
|
|
2010-06-08 07:03:24 -05:00
|
|
|
void Configuration::setRipupLimit ( unsigned int type, unsigned int limit )
|
2010-03-09 09:24:55 -06:00
|
|
|
{
|
|
|
|
if ( type >= RipupLimitsTableSize ) {
|
|
|
|
cerr << Error("setRipupLimit(): Bad ripup limit index: %ud (> %ud)."
|
|
|
|
,type,RipupLimitsTableSize) << endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
_ripupLimits [ type ] = limit;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-04-28 10:44:07 -05:00
|
|
|
void Configuration::setEdgeCapacityPercent ( float percent )
|
|
|
|
{
|
|
|
|
if ( percent > 1.0 )
|
|
|
|
throw Error("Configuration::setEdgeCapacityPercent(): edge capacity ratio greater than 1.0 (%.1f)."
|
|
|
|
,percent);
|
|
|
|
|
|
|
|
_edgeCapacityPercent = percent;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-09 09:24:55 -06:00
|
|
|
unsigned int Configuration::getRipupLimit ( unsigned int type ) const
|
|
|
|
{
|
|
|
|
if ( type >= RipupLimitsTableSize ) {
|
2010-06-08 07:03:24 -05:00
|
|
|
cerr << Error("getRipupLimit(): Bad ripup limit index: %u (> %u)."
|
2010-03-09 09:24:55 -06:00
|
|
|
,type,RipupLimitsTableSize) << endl;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return _ripupLimits[type];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-04-28 10:44:07 -05:00
|
|
|
void Configuration::print ( Cell* cell ) const
|
|
|
|
{
|
|
|
|
cout << " o Configuration of ToolEngine<Kite> for Cell <" << cell->getName() << ">" << endl;
|
|
|
|
cout << Dots::asPercentage(" - Global router edge capacity" ,_edgeCapacityPercent) << endl;
|
|
|
|
cout << Dots::asPercentage(" - GCell aggregation threshold (delta)",_expandStep) << endl;
|
|
|
|
cout << Dots::asULong (" - Events limit (iterations)" ,_eventsLimit) << endl;
|
|
|
|
cout << Dots::asUInt (" - Ripup limit, borders" ,_ripupLimits[BorderRipupLimit]) << endl;
|
|
|
|
cout << Dots::asUInt (" - Ripup limit, straps" ,_ripupLimits[StrapRipupLimit]) << endl;
|
|
|
|
cout << Dots::asUInt (" - Ripup limit, locals" ,_ripupLimits[LocalRipupLimit]) << endl;
|
|
|
|
cout << Dots::asUInt (" - Ripup limit, globals" ,_ripupLimits[GlobalRipupLimit]) << endl;
|
|
|
|
cout << Dots::asUInt (" - Ripup limit, long globals" ,_ripupLimits[LongGlobalRipupLimit]) << endl;
|
|
|
|
|
|
|
|
_base->print ( cell );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-09 09:24:55 -06:00
|
|
|
string Configuration::_getTypeName () const
|
* ./Kite:
- New: In BuildPowerRails, special processing for the power ring segments.
The "diagonal" of vias at each corner is causing a misbehavior of the
routing algorithm (due to fully saturated GCells in one direction).
As a temporary fix, extend the segments so they form a "square corner".
(problem arise on "d_in_i(22)").
- New: In RoutingEvent::_processNegociate, disable the "isForcedToHint()"
feature. No noticeable loss of quality or speed.
- New: In TrackElement/TrackSegment, wraps the AutoSegment parent's mechanism.
Allows to gets the DataNegociate of either the segment or it's parent.
- New: State::solveFullBlockages(), dedicated method to solves the case when
all the allowed tracks of a segment are blocked, tries to moves up
local segments and to break-up global ones.
- New: RoutingEventLoop, a more sophisticated way to detect looping.
Maintain a dynamic histogram of the last N (default 10) segments routeds,
with the count of how many times they have occurred. If that count
exeed 40, we *may* be facing a loop.
- Change: In State::conflictSolve1, implement new policy. The global segments
no more can be broken by local ones. The idea behind is that breaking
a global on the request of a local will only produce more cluttering
in the GCell. Globals must be keep straigth pass through, especially
inside near-saturated GCells. Globals breaking however can occurs at
another global's request.
- Change: In TrackCost, implement the new policy about locals segments that
cannot break globals segments. The sorting class now accept flags to
modulate the sorting function. Two options avalaibles: IgnoreAxisWeigth
(to uses for strap segments) and DiscardGlobals (to uses with locals).
- Change: In TrackCost, the "distance to fixed" have now an upper bound of
50 lambdas (no need to be greater because it means it's outside the
begin & en GCells). Take account not only of fixed segment, but also
of placed segments which makes bound.
- Bug: In Track::_check(), while calling each individual TrackSegment check,
uses it as the *first* argument of the "or", otherwise it may not be
called.
- Bug: In ProtectRoutingPad, loop over segment Collections while modificating
it was producing non-deterministic results. The fact that a collection
must be not modificated while beeing iterated is becoming a more and more
painful problem.
2010-12-30 12:42:17 -06:00
|
|
|
{ return "Configuration"; }
|
2010-03-09 09:24:55 -06:00
|
|
|
|
|
|
|
|
|
|
|
string Configuration::_getString () const
|
|
|
|
{
|
|
|
|
ostringstream os;
|
|
|
|
|
|
|
|
os << "<" << _getTypeName() << " " << getRoutingGauge()->getName() << ">";
|
|
|
|
|
|
|
|
return os.str();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Record* Configuration::_getRecord () const
|
|
|
|
{
|
|
|
|
Record* record = _base->_getRecord();
|
|
|
|
//record->add ( getSlot ( "_rg" , _rg ) );
|
* ./Kite:
- New: In BuildPowerRails, special processing for the power ring segments.
The "diagonal" of vias at each corner is causing a misbehavior of the
routing algorithm (due to fully saturated GCells in one direction).
As a temporary fix, extend the segments so they form a "square corner".
(problem arise on "d_in_i(22)").
- New: In RoutingEvent::_processNegociate, disable the "isForcedToHint()"
feature. No noticeable loss of quality or speed.
- New: In TrackElement/TrackSegment, wraps the AutoSegment parent's mechanism.
Allows to gets the DataNegociate of either the segment or it's parent.
- New: State::solveFullBlockages(), dedicated method to solves the case when
all the allowed tracks of a segment are blocked, tries to moves up
local segments and to break-up global ones.
- New: RoutingEventLoop, a more sophisticated way to detect looping.
Maintain a dynamic histogram of the last N (default 10) segments routeds,
with the count of how many times they have occurred. If that count
exeed 40, we *may* be facing a loop.
- Change: In State::conflictSolve1, implement new policy. The global segments
no more can be broken by local ones. The idea behind is that breaking
a global on the request of a local will only produce more cluttering
in the GCell. Globals must be keep straigth pass through, especially
inside near-saturated GCells. Globals breaking however can occurs at
another global's request.
- Change: In TrackCost, implement the new policy about locals segments that
cannot break globals segments. The sorting class now accept flags to
modulate the sorting function. Two options avalaibles: IgnoreAxisWeigth
(to uses for strap segments) and DiscardGlobals (to uses with locals).
- Change: In TrackCost, the "distance to fixed" have now an upper bound of
50 lambdas (no need to be greater because it means it's outside the
begin & en GCells). Take account not only of fixed segment, but also
of placed segments which makes bound.
- Bug: In Track::_check(), while calling each individual TrackSegment check,
uses it as the *first* argument of the "or", otherwise it may not be
called.
- Bug: In ProtectRoutingPad, loop over segment Collections while modificating
it was producing non-deterministic results. The fact that a collection
must be not modificated while beeing iterated is becoming a more and more
painful problem.
2010-12-30 12:42:17 -06:00
|
|
|
if ( record ) {
|
|
|
|
record->add ( getSlot("_edgeCapacityPercent",_edgeCapacityPercent) );
|
|
|
|
record->add ( getSlot("_ripupCost" ,_ripupCost ) );
|
|
|
|
record->add ( getSlot("_eventsLimit" ,_eventsLimit ) );
|
|
|
|
record->add ( getSlot("_edgeCapacityPercent",_edgeCapacityPercent) );
|
|
|
|
|
|
|
|
record->add ( getSlot("_ripupLimits[StrapRipupLimit]" ,_ripupLimits[StrapRipupLimit] ) );
|
|
|
|
record->add ( getSlot("_ripupLimits[LocalRipupLimit]" ,_ripupLimits[LocalRipupLimit] ) );
|
|
|
|
record->add ( getSlot("_ripupLimits[GlobalRipupLimit]" ,_ripupLimits[GlobalRipupLimit] ) );
|
|
|
|
record->add ( getSlot("_ripupLimits[LongGlobalRipupLimit]",_ripupLimits[LongGlobalRipupLimit]) );
|
|
|
|
|
|
|
|
for ( size_t i=0 ; i<MaxMetalDepth ; ++i ) {
|
|
|
|
ostringstream paramName;
|
|
|
|
paramName << "metal" << (i+1) << "MinBreak";
|
|
|
|
|
|
|
|
record->add ( DbU::getValueSlot(paramName.str(),&_globalMinBreaks[i]) );
|
|
|
|
}
|
|
|
|
}
|
2010-03-09 09:24:55 -06:00
|
|
|
|
|
|
|
return record;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} // End of Kite namespace.
|