- New support for <hbtree> section in <layout> section used to describe relative placement with constraints

- Examples (c++/python parse/drive) have been updated

    TODO: test under linux / write corresponding documentation
This commit is contained in:
Damien Dupuis 2011-09-14 08:13:46 +00:00
parent 8bb398b88f
commit c6ab2d5ed8
13 changed files with 466 additions and 16 deletions

View File

@ -11,6 +11,7 @@ using namespace std;
#include "vlsisapd/openChams/Sizing.h"
#include "vlsisapd/openChams/Operator.h"
#include "vlsisapd/openChams/Layout.h"
#include "vlsisapd/openChams/Node.h"
#include "vlsisapd/openChams/Port.h"
#include "vlsisapd/openChams/Wire.h"
@ -109,6 +110,14 @@ int main(int argc, char * argv[]) {
OpenChams::Layout* layout = circuit->createLayout();
layout->addInstance(OpenChams::Name("pmos1"), OpenChams::Name("Common transistor"));
layout->addInstance(OpenChams::Name("nmos1"), OpenChams::Name("Rotate transistor"));
// create hbtree
OpenChams::Group* g1 = new OpenChams::Group("g1"); // default position is NONE and default parent is NULL
g1->setAlign(OpenChams::Group::VERTICAL);
OpenChams::Bloc* b1 = new OpenChams::Bloc("nmos1", OpenChams::Node::NONE, g1);
g1->setRootNode(b1); // b1 is root node of group g1
OpenChams::Bloc* b2 = new OpenChams::Bloc("pmos1", OpenChams::Node::TOP, b1);
b1->setTop(b2); // b2 is on top of b1
layout->setHBTreeRoot(g1); // g1 is the root of the tree
circuit->writeToFile("./myInverter.xml");
return 0;

View File

@ -16,10 +16,57 @@ using namespace std;
#include "vlsisapd/openChams/Sizing.h"
#include "vlsisapd/openChams/Operator.h"
#include "vlsisapd/openChams/Layout.h"
#include "vlsisapd/openChams/Node.h"
#include "vlsisapd/openChams/Port.h"
#include "vlsisapd/openChams/Wire.h"
#include "vlsisapd/openChams/OpenChamsException.h"
void printHBTree(OpenChams::Node* node, unsigned indent) {
if (!node) return; // since we pass nnode->getRight and node-getTop without checking for NULL
for (unsigned i = 0 ; i < indent ; i++) {
cerr << " |";
}
string pos = "";
switch(node->getPosition()) {
case OpenChams::Node::TOP:
pos = "top";
break;
case OpenChams::Node::RIGHT:
pos = "right";
break;
default:
break;
}
OpenChams::Bloc* bloc = dynamic_cast<OpenChams::Bloc*>(node);
if (bloc) {
cerr << " bloc: " << bloc->getName().getString() << " - " << pos << endl;
printHBTree(bloc->getTop() , indent+1);
printHBTree(bloc->getRight(), indent+1);
return;
}
OpenChams::Group* group = dynamic_cast<OpenChams::Group*>(node);
if (group) {
string align = "none";
switch(group->getAlign()) {
case OpenChams::Group::VERTICAL:
align = "vertical";
break;
case OpenChams::Group::HORIZONTAL:
align = "horizontal";
break;
default:
break;
}
cerr << " group: " << group->getName().getString() << " - " << pos << " - align: " << align << " - isolated: " << group->isIsolated() << " - paired: " << group->isPaired() << endl;
printHBTree(group->getRootNode(), indent+1);
printHBTree(group->getTop() , indent+1);
printHBTree(group->getRight() , indent+1);
return;
}
cerr << "[ERROR] printHBTree: node is nor a bloc nor a group !" << endl;
return;
}
int main(int argc, char * argv[]) {
string file = "";
if (argc == 1)
@ -166,11 +213,18 @@ int main(int argc, char * argv[]) {
}
}
OpenChams::Layout* layout = circuit->getLayout();
if (layout && !layout->hasNoInstance()) {
cerr << " + layout" << endl;
for (map<OpenChams::Name, OpenChams::Name>::const_iterator lit = layout->getInstances().begin() ; lit != layout->getInstances().end() ; ++lit) {
cerr << " | | instance name: " << ((*lit).first).getString() << " - style: " << ((*lit).second).getString() << endl;
}
if (layout) {
if (!layout->hasNoInstance()) {
cerr << " + layout" << endl;
for (map<OpenChams::Name, OpenChams::Name>::const_iterator lit = layout->getInstances().begin() ; lit != layout->getInstances().end() ; ++lit) {
cerr << " | | instance name: " << ((*lit).first).getString() << " - style: " << ((*lit).second).getString() << endl;
}
}
OpenChams::Node* root = layout->getHBTreeRoot();
if (root) {
cerr << " | + hbtree" << endl;
printHBTree(root, 2);
}
}

View File

@ -119,5 +119,12 @@
<layout>
<instance name="pmos1" style="Common transistor"/>
<instance name="nmos1" style="Rotate transistor"/>
<hbtree>
<group name="g1" align="vertical">
<bloc name="nmos1">
<bloc name="pmos1" position="top"/>
</bloc>
</group>
</hbtree>
</layout>
</circuit>

View File

@ -92,5 +92,13 @@ op_nmos1.addConstraint("another", "myEq", -2.5 )
layout = circuit.createLayout()
layout.addInstance("pmos1", "Common transistor")
layout.addInstance("nmos1", "Rotate transistor")
# create hbtree
g1 = Group("g1")
g1.align = Group.Align.VERTICAL
b1 = Bloc("nmos1", Node.Position.NONE, g1)
g1.rootNode = b1
b2 = Bloc("pmos1", Node.Position.TOP, b1)
b1.top = b2
layout.hbTreeRoot = g1
circuit.writeToFile("./myInverter.xml")

View File

@ -2,6 +2,23 @@ import sys
from OPENCHAMS import *
def printHBTree(node, indent):
if node == None:
return
for i in range(indent):
print " |",
if isinstance(node, Bloc):
print " bloc:", node.getName(), "-", node.getPosition()
printHBTree(node.top , indent+1)
printHBTree(node.right, indent+1)
return
if isinstance(node, Group):
print " group:", node.getName(), "-", node.getPosition(), "-", node.align, "-", node.isolated, "-", node.paired
printHBTree(node.rootNode, indent+1)
printHBTree(node.top , indent+1)
printHBTree(node.right , indent+1)
return
def printContents(circuit):
print circuit.name
# circuit parameters
@ -78,6 +95,9 @@ def printContents(circuit):
print " + layout"
for inst in circuit.layout.getInstances():
print " | | instance name:", inst.key, "- style:", inst.value
if circuit.layout.hbTreeRoot != None:
print " | + hbtree"
printHBTree(circuit.layout.hbTreeRoot, 2)
def usage():
print "usage:", sys.argv[0], "[filename]"

View File

@ -12,6 +12,7 @@ SET ( hpps vlsisapd/openChams/Circuit.h
vlsisapd/openChams/SimulModel.h
vlsisapd/openChams/Sizing.h
vlsisapd/openChams/Layout.h
vlsisapd/openChams/Node.h
vlsisapd/openChams/Transistor.h
vlsisapd/openChams/Port.h
vlsisapd/openChams/Wire.h
@ -29,6 +30,7 @@ SET ( cpps Circuit.cpp
SimulModel.cpp
Sizing.cpp
Layout.cpp
Node.cpp
Transistor.cpp
Wire.cpp
)

View File

@ -23,6 +23,7 @@ using namespace std;
#include "vlsisapd/openChams/SimulModel.h"
#include "vlsisapd/openChams/Sizing.h"
#include "vlsisapd/openChams/Layout.h"
#include "vlsisapd/openChams/Node.h"
#include "vlsisapd/openChams/Transistor.h"
#include "vlsisapd/openChams/Operator.h"
#include "vlsisapd/openChams/Port.h"
@ -688,6 +689,7 @@ void Circuit::readSizing(xmlNode* node) {
}
Sizing* sizing = new Sizing(this);
cerr << "** S ** " << node->name << ": " << node->type << endl;
xmlNode* child = node->children;
for (xmlNode* node = child; node; node = node->next) {
if (node->type == XML_ELEMENT_NODE) {
@ -796,12 +798,15 @@ void Circuit::readLayout(xmlNode* node) {
Layout* layout = new Layout(this);
xmlNode* child = node->children;
cerr << "** L ** " << node->name << ": " << node->type << endl;
for (xmlNode* node = child; node; node = node->next) {
if (node->type == XML_ELEMENT_NODE) {
if (xmlStrEqual(node->name, (xmlChar*)"instance")) {
readInstanceLayout(node, layout);
} else if (xmlStrEqual(node->name, (xmlChar*)"hbtree")) {
readHBTree(node, layout);
} else {
cerr << "[WARNING] Only 'instance' nodes are allowed in 'sizing', others will be ignored." << endl;
cerr << "[WARNING] Only 'instance' and 'hbtree' nodes are allowed in 'layout' section, others will be ignored." << endl;
}
}
}
@ -821,6 +826,90 @@ void Circuit::readInstanceLayout(xmlNode* node, Layout* layout) {
}
}
void Circuit::readHBTree(xmlNode* node, Layout* layout) {
// HBTree node can have only one child (group or bloc)
xmlNode* child = node->children;
if (child->type == XML_ELEMENT_NODE) {
// create root node
// thanks to readNodeOrBloc
Node* root = readNodeOrBloc(child);
// save root node in layout
layout->setHBTreeRoot(root);
}
}
Node* Circuit::readNodeOrBloc(xmlNode* node, Node* parent) {
// 1 - create Node based on xmlNode* passed as argument
if (node->type == XML_ELEMENT_NODE) {
bool isAGroup = xmlStrEqual(node->name, (xmlChar*)"group");
xmlChar* nameC = xmlGetProp(node, (xmlChar*)"name");
xmlChar* posiC = xmlGetProp(node, (xmlChar*)"position");
if (!nameC)
throw OpenChamsException("[ERROR] 'bloc' and 'group' nodes in 'hbtree' must have at least a 'name' property.");
Node* nodeOC = NULL;
Name name ((const char*)nameC);
Node::Position pos = Node::NONE;
if (posiC) {
string posStr ((const char*)posiC);
if (posStr == "right") pos = Node::RIGHT;
else if (posStr == "top") pos = Node::TOP;
else throw OpenChamsException("[ERROR] 'position' property of 'bloc' and 'group' nodes must be 'right' or 'top'.");
}
if (isAGroup) {
Group* groupOC = new Group(name, pos, parent);
xmlChar* isolatC = xmlGetProp(node, (xmlChar*)"isolation");
xmlChar* alignC = xmlGetProp(node, (xmlChar*)"align");
xmlChar* pairedC = xmlGetProp(node, (xmlChar*)"paired");
if (isolatC) {
string isolation ((const char*)isolatC);
if (isolation == "true") groupOC->setIsolated(true);
else if (isolation == "false") groupOC->setIsolated(false);
else throw OpenChamsException("[ERROR] 'isolation' property of 'group' node must be 'true' or 'false'.");
}
if (alignC) {
string align ((const char*)alignC);
Group::Align galign = Group::NONE;
if (align == "vertical") galign = Group::VERTICAL;
else if (align == "horizontal") galign = Group::HORIZONTAL;
else throw OpenChamsException("[ERROR] 'align' property of 'group' node must be 'vertical' or 'horizontal'.");
groupOC->setAlign(galign);
}
if (pairedC) {
string paired ((const char*)pairedC);
if (paired == "true") groupOC->setPaired(true);
else if (paired == "false") groupOC->setPaired(false);
else throw OpenChamsException("[ERROR] 'paired' property of 'group' node must be 'true' or 'false'.");
}
nodeOC = groupOC;
} else {
nodeOC = new Bloc(name, pos, parent);
}
// 2 - for each children (up to 2) readNodeOrBloc
for (xmlNode* child = node->children; child; child = child->next) {
if (child->type == XML_ELEMENT_NODE) {
Node* childOC = readNodeOrBloc(child, nodeOC);
// 3 - add to returned Node* to current Node* as right or top children (based on its position)
switch(childOC->getPosition()) {
case Node::RIGHT:
nodeOC->setRight(childOC);
break;
case Node::TOP:
nodeOC->setTop(childOC);
break;
case Node::NONE:
if (!isAGroup)
throw OpenChamsException("[ERROR] a 'bloc' or 'group' without position is only allowed directly under a 'group'.");
Group* groupOC = dynamic_cast<Group*>(nodeOC);
groupOC->setRootNode(childOC);
}
}
}
// 4 - return current Node
return nodeOC;
}
return NULL;
}
void Circuit::setAbsolutePath(const string filePath) {
if (filePath[0] == '/')
_absolutePath = filePath;
@ -835,7 +924,7 @@ Circuit* Circuit::readFromFile(const string filePath) {
LIBXML_TEST_VERSION;
Circuit* cir = NULL;
xmlDoc* doc = xmlReadFile(filePath.c_str(), NULL, 0);
xmlDoc* doc = xmlReadFile(filePath.c_str(), NULL, XML_PARSE_NOBLANKS);
if (doc == NULL) {
string error ("[ERROR] Failed to parse: ");
error += filePath;
@ -941,13 +1030,75 @@ Layout* Circuit::createLayout() {
return _layout;
}
void Circuit::driveHBTree(ofstream& file, Node* node, unsigned indent) {
if (!node) return;
for (unsigned i = 0 ; i < indent ; i++)
file << " ";
string pos = "";
switch(node->getPosition()) {
case OpenChams::Node::TOP:
pos = "top";
break;
case OpenChams::Node::RIGHT:
pos = "right";
break;
default:
break;
}
Bloc* bloc = dynamic_cast<Bloc*>(node);
if (bloc) {
file << "<bloc name=\"" << bloc->getName().getString() << "\"";
if (pos != "")
file << " position=\"" << pos << "\"";
if (bloc->getTop() == NULL && bloc->getRight() == NULL)
file << "/>" << endl;
else {
file << ">" << endl;
driveHBTree(file, bloc->getTop() , indent+1);
driveHBTree(file, bloc->getRight(), indent+1);
for (unsigned i = 0 ; i < indent ; i++)
file << " ";
file << "</bloc>" << endl;
}
return;
}
Group* group = dynamic_cast<Group*>(node);
if (group) {
string align = "";
switch(group->getAlign()) {
case OpenChams::Group::VERTICAL:
align = "vertical";
break;
case OpenChams::Group::HORIZONTAL:
align = "horizontal";
break;
default:
break;
}
file << "<group name=\"" << group->getName().getString() << "\"";
if (pos != "") file << " position=\"" << pos << "\"";
if (align != "") file << " align=\"" << align << "\"";
if (group->isIsolated()) file << " isolated=\"true\"";
if (group->isPaired()) file << " paired=\"true\"";
file << ">" << endl;
driveHBTree(file, group->getRootNode(), indent+1);
driveHBTree(file, group->getTop() , indent+1);
driveHBTree(file, group->getRight() , indent+1);
for (unsigned i = 0 ; i < indent ; i++)
file << " ";
file << "</group>" << endl;
return;
}
}
bool Circuit::writeToFile(string filePath) {
ofstream file;
file.open(filePath.c_str());
if (!file.is_open()) {
string error("[ERROR] Cannot open file ");
error += filePath;
error += " for writting.";
error += " for writing.";
throw OpenChamsException(error);
}
// checks before do anything
@ -1148,10 +1299,17 @@ bool Circuit::writeToFile(string filePath) {
}
file << " </sizing>" << endl;
}
if (_layout && !_layout->hasNoInstance()) {
if (_layout) {
file << " <layout>" << endl;
for (map<Name, Name>::const_iterator it = _layout->getInstances().begin() ; it != _layout->getInstances().end() ; ++it) {
file << " <instance name=\"" << ((*it).first).getString() << "\" style=\"" << ((*it).second).getString() << "\"/>" << endl;
if (!_layout->hasNoInstance()) {
for (map<Name, Name>::const_iterator it = _layout->getInstances().begin() ; it != _layout->getInstances().end() ; ++it) {
file << " <instance name=\"" << ((*it).first).getString() << "\" style=\"" << ((*it).second).getString() << "\"/>" << endl;
}
}
if (Node* root = _layout->getHBTreeRoot()) {
file << " <hbtree>" << endl;
driveHBTree(file, root, 3);
file << " </hbtree>" << endl;
}
file << " </layout>" << endl;
}

View File

@ -3,7 +3,7 @@
* openChams
*
* Created by damien dupuis on 31/08/10.
* Copyright 2008-2010 UPMC / LIP6. All rights reserved.
* Copyright 2008-2011 UPMC / LIP6. All rights reserved.
*
*/
@ -15,7 +15,7 @@ using namespace std;
#include "vlsisapd/openChams/OpenChamsException.h"
namespace OpenChams {
Layout::Layout(Circuit* circuit): _circuit(circuit) {}
Layout::Layout(Circuit* circuit): _circuit(circuit), _hbTreeRoot(NULL), _instances() {}
void Layout::addInstance(Name name, Name style) {
map<Name, Name>::iterator it = _instances.find(name);

View File

@ -0,0 +1,34 @@
/*
* Node.cpp
* openChams
*
* Created by damien dupuis on 23/08/11.
* Copyright 2010-2011 UPMC / LIP6. All rights reserved.
*
*/
using namespace std;
#include "vlsisapd/openChams/Node.h"
namespace OpenChams {
Node::Node(Name nodeName, Position pos, Node* parent)
: _name(nodeName)
, _position(pos)
, _parent(parent)
, _right(NULL)
, _top(NULL)
{}
Bloc::Bloc(Name blocName, Position pos, Node* parent)
: Node(blocName, pos, parent)
{}
Group::Group(Name groupName, Position pos, Node* parent)
: Node(groupName, pos, parent)
, _isolated(false)
, _paired(false)
, _align(Group::NONE)
{}
} // namespace

View File

@ -16,6 +16,7 @@ using namespace boost::python;
#include "vlsisapd/openChams/SimulModel.h"
#include "vlsisapd/openChams/Sizing.h"
#include "vlsisapd/openChams/Layout.h"
#include "vlsisapd/openChams/Node.h"
#include "vlsisapd/openChams/Circuit.h"
#include "vlsisapd/openChams/Port.h"
#include "vlsisapd/openChams/Wire.h"
@ -317,6 +318,8 @@ BOOST_PYTHON_MODULE(OPENCHAMS) {
STL_MAP_WRAPPING(Name, Name, "LayoutInstancesMap")
// class OpenChams::Layout
class_<Layout, Layout*>("Layout", init<Circuit*>())
// properties
.add_property("hbTreeRoot", make_function(&Layout::getHBTreeRoot, return_value_policy<reference_existing_object>()), &Layout::setHBTreeRoot)
// accessors
.def("hasNoInstance", &Layout::hasNoInstance)
// modifiers
@ -349,6 +352,47 @@ BOOST_PYTHON_MODULE(OPENCHAMS) {
.def("writeToFile" , &Circuit::writeToFile)
;
{ //this scope is used to define Position as a subenum of Node
// class OpenChams::Node
scope nod = class_<Node, Node*, boost::noncopyable>("Node", no_init)
// properties
.add_property("top" , make_function(&Node::getTop , return_value_policy<reference_existing_object>()), &Node::setTop )
.add_property("right", make_function(&Node::getRight, return_value_policy<reference_existing_object>()), &Node::setRight)
// accessors
.def("getName" , &Node::getName )
.def("getPosition", &Node::getPosition)
.def("getParent" , &Node::getParent , return_value_policy<reference_existing_object>())
.def("isRoot" , &Node::isRoot )
;
enum_<Node::Position>("Position")
.value("NONE" , Node::NONE )
.value("RIGHT", Node::RIGHT)
.value("TOP" , Node::TOP )
.export_values()
;
} // end of node scope
// class OpenChams::Bloc
class_<Bloc, bases<Node> >("Bloc", init<Name, optional<Node::Position, Node*> >())
;
{ // this scope is used to define Align as a subenum of Group
// class OpenChams::Group
scope grou = class_<Group, bases<Node> >("Group", init<Name, optional<Node::Position, Node*> >())
.add_property("rootNode", make_function(&Group::getRootNode, return_value_policy<reference_existing_object>()), &Group::setRootNode)
.add_property("isolated", &Group::isIsolated, &Group::setIsolated)
.add_property("paired" , &Group::isPaired , &Group::setPaired )
.add_property("align" , &Group::getAlign , &Group::setAlign )
;
enum_<Group::Align>("Align")
.value("NONE" , Group::NONE )
.value("VERTICAL" , Group::VERTICAL )
.value("HORIZONTAL", Group::HORIZONTAL)
;
} // end of group scope
// OpenChamsException translator
register_exception_translator<OpenChamsException>(translator)
;

View File

@ -30,6 +30,7 @@ class Sizing;
class Transistor;
class Operator;
class Layout;
class Node;
class Circuit {
public:
@ -57,6 +58,8 @@ class Circuit {
Schematic* createSchematic();
Sizing* createSizing();
Layout* createLayout();
void driveHBTree(ofstream&, Node*, unsigned);
bool writeToFile(std::string filePath);
static Circuit* readFromFile(const std::string filePath);
@ -91,6 +94,8 @@ class Circuit {
void readEquation(xmlNode*, Sizing*);
void readLayout(xmlNode*);
void readInstanceLayout(xmlNode*, Layout*);
void readHBTree(xmlNode*, Layout*);
Node* readNodeOrBloc(xmlNode*, Node* parent = NULL);
void setAbsolutePath(const std::string filePath);
void check_uppercase(std::string& str, std::vector<std::string>& compares, std::string message);

View File

@ -15,6 +15,7 @@
namespace OpenChams {
class Name;
class Circuit;
class Node;
class Layout {
public:
@ -24,14 +25,22 @@ class Layout {
inline bool hasNoInstance();
inline const std::map<Name, Name>& getInstances();
inline Node* getHBTreeRoot();
inline void setHBTreeRoot(Node*);
private:
Circuit* _circuit;
Node* _hbTreeRoot;
std::map<Name, Name> _instances; // device name <-> style (name)
};
};
inline bool Layout::hasNoInstance() { return (_instances.size() == 0) ? true : false; };
inline const std::map<Name, Name>& Layout::getInstances() { return _instances; };
inline bool Layout::hasNoInstance() { return (_instances.size() == 0) ? true : false; };
inline const std::map<Name, Name>& Layout::getInstances() { return _instances; };
inline Node* Layout::getHBTreeRoot() { return _hbTreeRoot; }
inline void Layout::setHBTreeRoot(Node* root) { _hbTreeRoot = root; }
} // namespace
#endif

View File

@ -0,0 +1,100 @@
/*
* Node.h
* openChams
*
* Created by damien dupuis on 23/08/11.
* Copyright 2010-2011 UPMC / LIP6. All rights reserved.
*
*/
#ifndef __OPENCHAMS_NODE_H__
#define __OPENCHAMS_NODE_H__
#include "vlsisapd/openChams/Name.h"
namespace OpenChams {
class Node {
public:
enum Position { NONE = 0,
RIGHT = 1,
TOP = 2
};
protected:
Node(Name nodeName, Position pos, Node* parent);
virtual ~Node() {};
public:
inline Name getName() const;
inline Position getPosition() const;
inline Node* getParent();
inline Node* getRight();
inline Node* getTop();
inline bool isRoot();
inline void setRight(Node*);
inline void setTop(Node*);
private:
Name _name;
Position _position;
Node* _parent;
Node* _right;
Node* _top;
};
inline Name Node::getName() const { return _name; }
inline Node::Position Node::getPosition() const { return _position; }
inline Node* Node::getParent() { return _parent; }
inline Node* Node::getRight() { return _right; }
inline Node* Node::getTop() { return _top; }
inline bool Node::isRoot() { return _parent == NULL; }
inline void Node::setRight(Node* right) { _right = right; }
inline void Node::setTop(Node* top) { _top = top; }
class Bloc : public Node {
public:
Bloc(Name blocName, Position pos=Node::NONE, Node* parent=NULL);
};
class Group : public Node {
public:
enum Align { NONE = 0
, VERTICAL = 1
, HORIZONTAL = 2
};
Group(Name groupName, Position pos=Node::NONE, Node* parent=NULL);
inline void setRootNode(Node*);
inline void setIsolated(bool);
inline void setPaired(bool);
inline void setAlign(Align);
inline Node* getRootNode();
inline bool isIsolated();
inline bool isPaired();
inline Align getAlign();
private:
Node* _root;
bool _isolated;
bool _paired;
Align _align;
};
inline void Group::setRootNode(Node* root) { _root = root; }
inline void Group::setIsolated(bool isolated) { _isolated = isolated; }
inline void Group::setPaired(bool paired) { _paired = paired; }
inline void Group::setAlign(Group::Align align) { _align = align; }
inline Node* Group::getRootNode() { return _root; }
inline bool Group::isIsolated() { return _isolated; }
inline bool Group::isPaired() { return _paired; }
inline Group::Align Group::getAlign() { return _align; }
} // namespace
#endif