coriolis/anabatic/src/AntennaProtect.cpp

643 lines
23 KiB
C++
Raw Normal View History

Second version of the antenna effect protection. * Change: In EtesianEngine::globalPlace(), disable the call to antennaProtect(). First reason is that, after all, Coloquinte do not handle so well the resizing of the cells "on the fly", it overspill the boundaries sometimes. Second reason is that as we cannot know the routing tree at this stage, we will not be able to choose the correct points for diode insertions. We only have a Steiner tree wich may not be the same as a density driven Dijkstra. * Change: In Etesian::Area, the Occurrence to the Instances where not stored in a uniform way. Some where starting from the placed sub-block, some where starting from the top level (corona), making their processing (and remembering it) tricky. Now, they are all expressed from the top cell (corona). The coordinate system is now systematically the one of the top block (*not* the block). Create various overloaded functions EtesianEngine::toCell() and EtesianEngine::toBlock() to ease Occurrence & coordinate translations. * New: In Etesian::Slice::createDiodeUnder(), add a X position hint. Search is done by going through the whole slice range and minimizing the distance to the hint. If it starts to be too slow, we may optimize. * Bug: In EtesianEngine::toColoquinte(), the placement of the top level external pins was not taken into account (this at last explain their weird positioning). * New: AnabaticEngine::antennaProtect(), new algorithm to avoid antenna effect. This step must be done *after* global routing and *before* detailed routing. This way we have access to the real routing and can mend it (along with the netlist) to insert diodes at the rigth points. From the global routing we build clusters (DiodeCluster) of RoutingPads connected through a set of wire whose total length is below the antenna effect threshold. Long wires connecting the clusters are also tagged because we need to put a diode between them and the first RoutingPad of the cluster. This is to avoid a long METAL2 wire connecting to the RoutingPad before the diode is connected through METAL3 (in case of misalignment). This protection is not even enough. For *very long* wires, we needs to put *more* than one diode (this is to be implemented).
2021-01-27 04:38:00 -06:00
// -*- C++ -*-
//
// This file is part of the Coriolis Software.
// Copyright (c) UPMC 2021-2021, All Rights Reserved
//
// +-----------------------------------------------------------------+
// | C O R I O L I S |
// | A n a b a t i c - Routing Toolbox |
// | |
// | Author : Jean-Paul CHAPUT |
// | E-mail : Jean-Paul.Chaput@lip6.fr |
// | =============================================================== |
// | C++ Module : "./AntennaProtect.cpp" |
// +-----------------------------------------------------------------+
#include <cstdlib>
#include <sstream>
#include <tuple>
#include "hurricane/Bug.h"
#include "hurricane/Warning.h"
#include "hurricane/DebugSession.h"
#include "hurricane/Breakpoint.h"
#include "hurricane/Net.h"
#include "hurricane/NetExternalComponents.h"
#include "hurricane/NetRoutingProperty.h"
#include "hurricane/Layer.h"
#include "hurricane/RoutingPad.h"
#include "hurricane/Pad.h"
#include "hurricane/Plug.h"
#include "hurricane/Instance.h"
#include "hurricane/Vertical.h"
#include "hurricane/Horizontal.h"
#include "hurricane/Cell.h"
#include "crlcore/RoutingGauge.h"
#include "etesian/EtesianEngine.h"
#include "anabatic/AutoContactTerminal.h"
#include "anabatic/AutoSegment.h"
#include "anabatic/AnabaticEngine.h"
namespace {
using namespace std;
using namespace CRL;
using namespace Hurricane;
using namespace Anabatic;
using Etesian::EtesianEngine;
class CompareGCellByXMin {
public:
inline bool operator () ( const GCell* lhs, const GCell* rhs ) const;
};
inline bool CompareGCellByXMin::operator () ( const GCell* lhs, const GCell* rhs ) const
{ return lhs->getXMin() < rhs->getXMin(); }
// -----------------------------------------------------------------
// Class : "::DiodeCluster".
class DiodeCluster {
public:
static const uint32_t IsDriver;
static const uint32_t IsSink;
static const uint32_t HasDiode;
static const uint32_t IsSegSource;
static string toStr ( uint32_t );
public:
typedef tuple<uint32_t,Segment*> RPInfos;
typedef map<RoutingPad*,RPInfos,DBo::CompareById> RoutingPadInfos;
typedef set<Horizontal*,DBo::CompareById> HorizontalSet;
typedef set<Vertical* ,DBo::CompareById> VerticalSet;
typedef map< DbU::Unit, vector<GCell*> > GCellArea;
public:
DiodeCluster ( RoutingPad*, AnabaticEngine* );
inline bool hasRp ( RoutingPad* ) const;
bool hasGCell ( GCell* ) const;
inline Net* getTopNet () const;
inline RoutingPad* getRefRp () const;
inline const RoutingPadInfos& getRoutingPads () const;
inline const vector<Instance*>& getDiodes () const;
inline DbU::Unit getWL () const;
bool needsDiode () const;
Box getBoundingBox () const;
void merge ( GCell* );
void merge ( RoutingPad* );
void merge ( Segment* );
void mergeHalo ( Segment* );
void forceDiodeOn ( RoutingPad*, Segment*, Flags );
Instance* createDiode ( Etesian::Area*, GCell*, Flags side, uint32_t distance=1 );
const vector<Instance*>& createDiodes ( Etesian::Area* );
private:
void _consolidate ();
private:
AnabaticEngine* _anabatic;
bool _sortArea;
DbU::Unit _WL;
RoutingPadInfos _routingPads;
HorizontalSet _horizontals;
VerticalSet _verticals;
HorizontalSet _horizontalHalo;
VerticalSet _verticalHalo;
GCellArea _area;
vector<Instance*> _diodes;
};
const uint32_t DiodeCluster::IsDriver = (1 << 0);
const uint32_t DiodeCluster::IsSink = (1 << 1);
const uint32_t DiodeCluster::HasDiode = (1 << 2);
const uint32_t DiodeCluster::IsSegSource = (1 << 3);
string DiodeCluster::toStr ( uint32_t flags )
{
string s;
s += (flags & IsDriver ) ? 'd' : '-';
s += (flags & IsSink ) ? 's' : '-';
s += (flags & HasDiode ) ? 'D' : '-';
s += (flags & IsSegSource) ? 'S' : '-';
return s;
}
DiodeCluster::DiodeCluster ( RoutingPad* rp, AnabaticEngine* anabatic )
: _anabatic(anabatic)
, _sortArea(true)
, _WL(0)
, _routingPads()
, _horizontals()
, _verticals()
, _horizontalHalo()
, _verticalHalo()
, _area()
, _diodes()
{
merge( rp );
}
inline Net* DiodeCluster::getTopNet () const { return getRefRp()->getNet(); }
inline DbU::Unit DiodeCluster::getWL () const { return _WL; }
inline const DiodeCluster::RoutingPadInfos& DiodeCluster::getRoutingPads () const { return _routingPads; }
inline const vector<Instance*>& DiodeCluster::getDiodes () const { return _diodes; }
bool DiodeCluster::needsDiode () const
{
for ( auto& item : _routingPads ) {
if (std::get<0>(item.second) & IsSink) return true;
}
return false;
}
inline bool DiodeCluster::hasRp ( RoutingPad* rp ) const
{ return (_routingPads.find(rp) != _routingPads.end()); }
inline bool DiodeCluster::hasGCell ( GCell* gcell ) const
{
if (not gcell) return false;
auto islice = _area.find( gcell->getYMin());
if (islice == _area.end()) return false;
for ( const GCell* igcell : (*islice).second )
if (igcell == gcell) return true;
return false;
}
inline RoutingPad* DiodeCluster::getRefRp () const
{
if (not _routingPads.empty()) return (*_routingPads.begin()).first;
return NULL;
}
void DiodeCluster::merge ( RoutingPad* rp )
{
Plug* rpPlug = dynamic_cast<Plug*>( rp->getPlugOccurrence().getEntity() );
if (rpPlug) {
merge( _anabatic->getGCellUnder( rp->getPosition() ));
if (rpPlug->getMasterNet()->getDirection() & Net::Direction::DirIn) {
_routingPads.insert( make_pair(rp,make_tuple(IsSink,(Segment*)NULL)) );
} else {
_routingPads.insert( make_pair(rp,make_tuple(IsDriver,(Segment*)NULL)) );
cdebug_log(147,0) << "| Driver " << rp << endl;
}
} else {
Pin* rpPin = dynamic_cast<Pin*>( rp->getPlugOccurrence().getEntity() );
if (rpPin) {
_routingPads.insert( make_pair(rp,make_tuple(IsDriver,(Segment*)NULL)) );
cdebug_log(147,0) << "| Pin (considered driver) " << rp << endl;
}
}
}
void DiodeCluster::merge ( Segment* segment )
{
_WL += segment->getLength();
GCellsUnder gcells = _anabatic->getGCellsUnder( segment );
if (not gcells->empty()) {
_sortArea = true;
for ( size_t i=0 ; i<gcells->size() ; ++i ) {
merge( gcells->gcellAt(i) );
}
}
}
void DiodeCluster::merge ( GCell* gcell )
{
if (not gcell) return;
auto islice = _area.find( gcell->getYMin());
if (islice == _area.end())
islice = _area.insert( make_pair(gcell->getYMin(),vector<GCell*>()) ).first;
for ( const GCell* igcell : (*islice).second ) {
if (igcell == gcell) return;
}
(*islice).second.push_back( gcell );
}
Box DiodeCluster::getBoundingBox () const
{
Box bb;
for ( auto& item : _routingPads ) bb.merge( item.first->getPosition() );
return bb;
}
void DiodeCluster::forceDiodeOn ( RoutingPad* rp, Segment* segment, Flags flags )
{
cdebug_log(147,0) << "DiodeCluster::forceDiodeOn(): " << endl;
cdebug_log(147,0) << " rp=" << rp << endl;
cdebug_log(147,0) << " seg=" << segment << endl;
if (not rp or not segment) return;
for ( auto& item : _routingPads ) {
if (item.first == rp) {
std::get<0>(item.second) |= HasDiode;
if (flags & Flags::Source) std::get<0>(item.second) |= IsSegSource;
std::get<1>(item.second) = segment;
return;
}
}
cdebug_log(147,0) << "DiodeCluster::forceDiodeOn(): No RP registered." << endl;
}
Instance* DiodeCluster::createDiode ( Etesian::Area* area, GCell* gcell, Flags side, uint32_t distance )
{
cdebug_log(147,0) << "DiodeCluster::createDiode(): from=" << gcell
<< " distance=" << distance << endl;
GCell* neighbor = gcell;
for ( uint32_t i=0 ; i<distance and neighbor ; ++i ) {
if (side.contains(Flags::WestSide )) neighbor = neighbor->getWest();
else if (side.contains(Flags::EastSide )) neighbor = neighbor->getEast();
else if (side.contains(Flags::NorthSide)) neighbor = neighbor->getNorth();
else if (side.contains(Flags::SouthSide)) neighbor = neighbor->getSouth();
}
if (not neighbor) return NULL;
DbU::Unit xHint = 0;
if (side.contains(Flags::WestSide )) xHint = neighbor->getBoundingBox().getXMax();
else if (side.contains(Flags::EastSide )) xHint = neighbor->getBoundingBox().getXMin();
else if (side.contains(Flags::NorthSide)) xHint = neighbor->getBoundingBox().getXCenter();
else if (side.contains(Flags::SouthSide)) xHint = neighbor->getBoundingBox().getXCenter();
Box bb = neighbor->getBoundingBox();
Instance* diode = area->createDiodeUnder( getRefRp(), bb, xHint );
if (diode) {
_diodes.push_back( diode );
cdebug_log(147,0) << "> GCell area (neighbor): " << bb << endl;
Contact* sourceContact = gcell->breakGoThrough( getTopNet() );
Contact* targetContact = neighbor->hasGContact( getTopNet() );
if (not targetContact) {
bool hasGoThrough = (neighbor->hasGoThrough(getTopNet()) != NULL);
targetContact = neighbor->breakGoThrough( getTopNet() );
if (not hasGoThrough) {
if (side & Flags::Horizontal) {
if (sourceContact->getX() > targetContact->getX())
std::swap( sourceContact, targetContact );
Horizontal::create( sourceContact
, targetContact
, _anabatic->getConfiguration()->getGHorizontalLayer()
, bb.getCenter().getY()
, _anabatic->getConfiguration()->getGHorizontalPitch()
);
} else {
if (sourceContact->getY() > targetContact->getY())
std::swap( sourceContact, targetContact );
Vertical::create( sourceContact
, targetContact
, _anabatic->getConfiguration()->getGVerticalLayer()
, bb.getCenter().getX()
, _anabatic->getConfiguration()->getGVerticalPitch()
);
}
}
}
}
return diode;
}
const vector<Instance*>& DiodeCluster::createDiodes ( Etesian::Area* area )
{
if (not needsDiode()) return _diodes;
_consolidate();
Instance* diode = NULL;
for ( auto& item : _routingPads ) {
if (not (std::get<0>(item.second) & HasDiode)) continue;
diode = NULL;
GCell* gcell = _anabatic->getGCellUnder( item.first->getPosition() );
Horizontal* h = dynamic_cast<Horizontal*>(std::get<1>(item.second));
if (h) {
cdebug_log(147,0) << "> Long segment (forced diode) h=" << h << endl;
diode = area->createDiodeUnder( getRefRp()
, h->getBoundingBox()
, item.first->getPosition().getX() );
if (diode) {
_diodes.push_back( diode );
continue;
}
}
auto islice = _area.find( gcell->getYMin());
if (islice != _area.end()) {
Box gcellsBb;
bool containsRp = false;
vector<GCell*>& slice = (*islice).second;
for ( size_t i=0 ; i < slice.size() ; ++i ) {
if (slice[i] == gcell) containsRp = true;
if ((i > 0) and (slice[i-1]->getXMax() < slice[i]->getXMin())) {
cdebug_log(147,0) << "> GCell area (forced diode): " << gcellsBb << endl;
if (containsRp) {
diode = area->createDiodeUnder( getRefRp(), gcellsBb, item.first->getPosition().getX() );
if (diode) {
_diodes.push_back( diode );
containsRp = false;
break;
}
}
gcellsBb.makeEmpty();
}
gcellsBb.merge( slice[i]->getBoundingBox() );
}
if (diode) continue;
if (containsRp and not gcellsBb.isEmpty()) {
cdebug_log(147,0) << "> GCell area (forced diode, end): " << gcellsBb << endl;
diode = area->createDiodeUnder( getRefRp(), gcellsBb, item.first->getPosition().getX() );
if (diode) {
_diodes.push_back( diode );
continue;
}
}
cdebug_log(147,0) << "> Try along connecting segment" << endl;
Horizontal* h = dynamic_cast<Horizontal*>( std::get<1>(item.second) );
if (h) {
if (std::get<0>(item.second) & IsSegSource) {
for ( uint32_t i=0 ; i<3 ; ++i ) {
if ((diode = createDiode(area,gcell,Flags::EastSide,i))) break;
}
} else {
for ( uint32_t i=0 ; i<3 ; ++i ) {
if ((diode = createDiode(area,gcell,Flags::WestSide,i))) break;
}
}
} else {
if (std::get<0>(item.second) & IsSegSource) {
for ( uint32_t i=0 ; i<3 ; ++i ) {
if ((diode = createDiode(area,gcell,Flags::NorthSide,i))) break;
}
} else {
for ( uint32_t i=0 ; i<3 ; ++i ) {
if ((diode = createDiode(area,gcell,Flags::SouthSide,i))) break;
}
}
}
}
}
if (diode) return _diodes;
for ( auto& item : _area ) {
cdebug_log(147,0) << "+ Slice @" << DbU::getValueString(item.first) << endl;
Box gcellsBb;
vector<GCell*>& slice = item.second;
for ( size_t i=0 ; (i < slice.size()) and not diode ; ++i ) {
if ((i > 0) and (slice[i-1]->getXMax() < slice[i]->getXMin())) {
cdebug_log(147,0) << "> GCell area: " << gcellsBb << endl;
diode = area->createDiodeUnder( getRefRp(), gcellsBb, gcellsBb.getXCenter() );
if (diode) {
_diodes.push_back( diode );
return _diodes;
}
gcellsBb.makeEmpty();
}
cdebug_log(147,0) << "| Agglomerate [" << i << "]: " << slice[i] << endl;
gcellsBb.merge( slice[i]->getBoundingBox() );
}
if (not gcellsBb.isEmpty()) {
cdebug_log(147,0) << "> GCell area (end): " << gcellsBb << endl;
diode = area->createDiodeUnder( getRefRp(), gcellsBb, gcellsBb.getXCenter() );
if (diode) {
_diodes.push_back( diode );
return _diodes;
}
}
}
for ( auto& item : _area ) {
vector<GCell*>& slice = item.second;
if (createDiode(area,slice[0 ],Flags::WestSide)) return _diodes;
if (createDiode(area,slice[slice.size()-1],Flags::EastSide)) return _diodes;
}
for ( auto& item : _routingPads ) {
GCell* gcell = _anabatic->getGCellUnder( item.first->getPosition() );
if (createDiode(area,gcell,Flags::NorthSide)) return _diodes;
if (createDiode(area,gcell,Flags::SouthSide)) return _diodes;
}
return _diodes;
}
void DiodeCluster::_consolidate ()
{
if (not _sortArea) return;
for ( auto& item : _area ) {
std::sort( item.second.begin(), item.second.end(), CompareGCellByXMin() );
}
_sortArea = false;
}
} // Anonymous namespace.
namespace Anabatic {
using namespace Hurricane;
using CRL::ToolEngine;
using Etesian::EtesianEngine;
void AnabaticEngine::antennaProtect ( Net* net, uint32_t& failed, uint32_t& total )
{
DebugSession::open( net, 145, 150 );
cdebug_log(147,1) << "Net \"" << net->getName() << endl;
EtesianEngine* etesian = static_cast<EtesianEngine*>
( ToolEngine::get( getCell(), EtesianEngine::staticGetName() ));
DbU::Unit antennaMaxWL = etesian->getAntennaMaxWL();
vector<DiodeCluster*> clusters;
set<RoutingPad*,DBo::CompareById> rpsDone;
set<Segment* ,DBo::CompareById> segmentsDone;
for ( RoutingPad* rp : net->getRoutingPads() ) {
if (rpsDone.find(rp) != rpsDone.end()) continue;
DiodeCluster* cluster = new DiodeCluster ( rp, this );
clusters.push_back( cluster );
vector<Hook*> hooksStack;
hooksStack.push_back( rp->getBodyHook() );
while ( not hooksStack.empty() ) {
Hook* topHook = hooksStack.back();
RoutingPad* topRp = dynamic_cast<RoutingPad*>( topHook->getComponent() );
RoutingPad* connexRp = NULL;
hooksStack.pop_back();
for ( Hook* hook : topHook->getHooks() ) {
RoutingPad* gcellRp = dynamic_cast<RoutingPad*>( hook->getComponent() );
if (gcellRp) {
if (not connexRp) connexRp = gcellRp;
if (rpsDone.find(gcellRp) == rpsDone.end())
cluster->merge( gcellRp );
rpsDone.insert( gcellRp );
}
}
for ( Hook* hook : topHook->getHooks() ) {
Segment* segment = dynamic_cast<Segment*>( hook->getComponent() );
if (segment) {
cdebug_log(147,0) << "| " << DbU::getValueString(segment->getLength())
<< " " << segment << endl;
if (segmentsDone.find(segment) != segmentsDone.end()) continue;
segmentsDone.insert( segment );
if (segment->getLength() > antennaMaxWL/2) {
Flags flags = (segment->getSourceHook() == hook) ? Flags::Source : Flags::Target;
if (not connexRp) connexRp = topRp;
cluster->forceDiodeOn( connexRp, segment, flags );
continue;
}
cluster->merge( segment );
if (dynamic_cast<Segment::SourceHook*>(hook)) {
hooksStack.push_back( segment->getTargetHook() );
} else {
hooksStack.push_back( segment->getSourceHook() );
}
}
}
}
}
Cell* diodeCell = etesian->getDiodeCell();
Net* diodeOutput = NULL;
for ( Net* net : diodeCell->getNets() ) {
if (net->isSupply() or not net->isExternal()) continue;
diodeOutput = net;
break;
}
if (clusters.size() > 1) {
total += clusters.size();
cdebug_log(147,1) << "Net \"" << net->getName() << " has " << clusters.size() << " diode clusters." << endl;
for ( size_t i=0 ; i<clusters.size() ; ++i ) {
cdebug_log(147,0) << "Cluster [" << i << "] needsDiode=" << clusters[i]->needsDiode()
<< " bb=" << clusters[i]->getBoundingBox() << endl;
for ( auto& item : clusters[i]->getRoutingPads() ) {
cdebug_log(147,0) << "| flags=" << DiodeCluster::toStr(std::get<0>(item.second))
<< " " << item.first << endl;
}
if (not clusters[i]->needsDiode()) continue;
const vector<Instance*> diodes = clusters[i]->createDiodes( etesian->getArea() );
RoutingPad* rp = clusters[i]->getRefRp();
if (not diodes.empty()) {
Net* topNet = rp->getNet();
Plug* sinkPlug = dynamic_cast<Plug*>( rp->getPlugOccurrence().getEntity() );
Path path = rp->getOccurrence().getPath().getHeadPath();
if (sinkPlug) {
for ( Instance* diode : diodes ) {
cdebug_log(147,0) << " Bind diode input:" << endl;
cdebug_log(147,0) << " " << diode << " @" << diode ->getTransformation() << endl;
cdebug_log(147,0) << " topNet->getCell():" << topNet->getCell() << endl;
cdebug_log(147,0) << " " << rp->getOccurrence().getPath() << endl;
//Path path = rp->getOccurrence().getPath().getHeadPath();
Plug* diodePlug = diode->getPlug( diodeOutput );
diodePlug->setNet( sinkPlug->getNet() );
RoutingPad* diodeRp = RoutingPad::create( topNet, Occurrence(diodePlug,path), RoutingPad::BiggestArea );
GCell* gcell = getGCellUnder( diodeRp->getPosition() );
if (gcell) {
Contact* contact = gcell->breakGoThrough( topNet );
contact->getBodyHook()->merge( diodeRp->getBodyHook() );
}
}
} else {
cerr << Error( "EtesianEngine::antennaProtect(): For %s (rps:%u, clusters:%u)\n"
" Cannot get a Plug from %s (?)."
, getString(net).c_str()
, rpsDone.size()
, clusters.size()
, getString(clusters[i]->getRefRp()).c_str()
) << endl;
}
} else {
cerr << Error( "EtesianEngine::antennaProtect(): For %s (rps:%u, clusters:%u)\n"
" Cannot find a diode nearby %s."
, getString(net).c_str()
, rpsDone.size()
, clusters.size()
, getString(clusters[i]->getRefRp()).c_str()
) << endl;
failed += 1;
}
}
cdebug_tabw(147,-1);
}
for ( DiodeCluster* cluster : clusters ) delete cluster;
cdebug_tabw(147,-1);
DebugSession::close();
}
void AnabaticEngine::antennaProtect ()
{
//DebugSession::open( 145, 150 );
if (not ToolEngine::get( getCell(), EtesianEngine::staticGetName() )) {
cerr << Warning( "AnabaticEngine::antennaProtect(): No EtesianEngine found, skipped." ) << endl;
return;
}
EtesianEngine* etesian = static_cast<EtesianEngine*>
( ToolEngine::get( getCell(), EtesianEngine::staticGetName() ));
DbU::Unit segmentMaxWL = etesian->getAntennaMaxWL() / 2;
if (not etesian->getDiodeCell()) {
cerr << Warning( "AnabaticEngine::antennaProtect(): No diode cell found, skipped." ) << endl;
return;
}
startMeasures();
openSession();
uint32_t failed = 0;
uint32_t total = 0;
for ( Net* net : getCell()->getNets() ) {
if (net->isSupply()) continue;
if (net->isClock ()) continue;
antennaProtect( net, failed, total );
}
cmess2 << Dots::asString ( " - Antenna maximum WL" , DbU::getValueString(etesian->getAntennaMaxWL()) ) << endl;
cmess2 << Dots::asString ( " - Segment maximum WL" , DbU::getValueString(segmentMaxWL) ) << endl;
cmess2 << Dots::asInt ( " - Total needed diodes", total ) << endl;
cmess2 << Dots::asInt ( " - Failed to allocate" , failed ) << endl;
cmess2 << Dots::asPercentage( " - Success ratio" , (float)(total-failed)/(float)total ) << endl;
stopMeasures();
printMeasures( "antennas" );
// cmess2 << " - Total segments : " << total << endl;
// cmess2 << " - Global segments : " << global << endl;
// cmess2 << " - Ratio : "
// << ((float)global/(float)total)*100.0 << "%." << endl;
Session::close();
//DebugSession::close();
Breakpoint::stop( 99, "After diodes insertions." );
}
} // Anabatic namespace.