18#include "mlir/IR/Builders.h"
19#include "mlir/IR/BuiltinTypes.h"
20#include "mlir/IR/PatternMatch.h"
21#include "mlir/IR/SymbolTable.h"
30circt::esi::ChannelType ChannelBufferOp::channelType() {
31 return getOutput().getType();
34LogicalResult ChannelBufferOp::verify() {
35 if (getInput().getType().getInner() != getOutput().getType().getInner())
36 return emitOpError(
"input and output data types must match");
44LogicalResult SnoopValidReadyOp::verify() {
45 ChannelType type = getInput().getType();
46 if (type.getSignaling() != ChannelSignaling::ValidReady)
47 return emitOpError(
"only supports valid-ready signaling");
48 if (type.getInner() != getData().getType())
49 return emitOpError(
"input and output types must match");
53LogicalResult SnoopValidReadyOp::inferReturnTypes(
54 MLIRContext *
context, std::optional<Location> loc, ValueRange operands,
55 DictionaryAttr
attrs, mlir::PropertyRef properties,
56 mlir::RegionRange regions, SmallVectorImpl<Type> &results) {
57 auto i1 = IntegerType::get(
context, 1);
58 results.push_back(i1);
59 results.push_back(i1);
60 results.push_back(cast<ChannelType>(operands[0].getType()).getInner());
64LogicalResult SnoopTransactionOp::verify() {
65 ChannelType type = getInput().getType();
66 if (type.getInner() != getData().getType())
67 return emitOpError(
"input and output types must match");
71LogicalResult SnoopTransactionOp::inferReturnTypes(
72 MLIRContext *
context, std::optional<Location> loc, ValueRange operands,
73 DictionaryAttr
attrs, mlir::PropertyRef properties,
74 mlir::RegionRange regions, SmallVectorImpl<Type> &results) {
75 auto i1 = IntegerType::get(
context, 1);
76 results.push_back(i1);
77 results.push_back(cast<ChannelType>(operands[0].getType()).getInner());
85LogicalResult FIFOOp::verify() {
86 if (getOutput().getType().getInner() != getInput().getType().getInner())
87 return emitOpError(
"input and output types must match");
90 ChannelType inputType = getInput().getType();
91 if (inputType.getSignaling() != ChannelSignaling::ValidOnly &&
92 getDepth() <= inputType.getDataDelay())
93 return emitOpError(
"depth must be greater than the input data delay");
101circt::esi::ChannelType PipelineStageOp::channelType() {
102 return cast<circt::esi::ChannelType>(getInput().getType());
105LogicalResult PipelineStageOp::verify() {
106 if (getInput().getType().getSignaling() != ChannelSignaling::ValidReady)
107 return emitOpError(
"only supports valid-ready signaling");
117 Type &chanOutputType) {
118 ChannelType chanType;
119 if (p.parseType(chanType))
121 dataType = chanType.getInner();
122 chanOutputType = chanType;
127 Type chanOutputType) {
131LogicalResult WrapValidReadyOp::verify() {
132 mlir::TypedValue<ChannelType> chanOut = getChanOutput();
133 if (chanOut.getType().getSignaling() != ChannelSignaling::ValidReady)
134 return emitOpError(
"only supports valid-ready signaling");
135 if (chanOut.getType().getInner() != getRawInput().getType())
136 return emitOpError(
"input and output types must match");
137 if (failed(ChannelType::verifyChannel(chanOut)))
142LogicalResult UnwrapValidReadyOp::verify() {
143 if (getChanInput().getType().getSignaling() != ChannelSignaling::ValidReady)
144 return emitOpError(
"only supports valid-ready signaling");
148LogicalResult UnwrapValidReadyOp::inferReturnTypes(
149 MLIRContext *
context, std::optional<Location>, ValueRange operands,
150 DictionaryAttr, mlir::PropertyRef, mlir::RegionRange,
151 SmallVectorImpl<Type> &inferredReturnTypes) {
152 inferredReturnTypes.push_back(
153 cast<ChannelType>(operands[0].getType()).getInner());
154 inferredReturnTypes.push_back(IntegerType::get(
context, 1));
158circt::esi::ChannelType WrapValidReadyOp::channelType() {
159 return cast<circt::esi::ChannelType>(getChanOutput().getType());
162circt::esi::ChannelType UnwrapValidReadyOp::channelType() {
163 return cast<circt::esi::ChannelType>(getChanInput().getType());
166circt::esi::ChannelType WrapFIFOOp::channelType() {
167 return cast<circt::esi::ChannelType>(getChanOutput().getType());
170LogicalResult WrapFIFOOp::verify() {
171 if (getChanOutput().getType().getSignaling() != ChannelSignaling::FIFO)
172 return emitOpError(
"only supports FIFO signaling");
173 if (getChanOutput().getType().getInner() != getData().getType())
174 return emitOpError(
"input and output types must match");
178circt::esi::ChannelType UnwrapFIFOOp::channelType() {
179 return cast<circt::esi::ChannelType>(getChanInput().getType());
182LogicalResult UnwrapFIFOOp::verify() {
183 if (getChanInput().getType().getSignaling() != ChannelSignaling::FIFO)
184 return emitOpError(
"only supports FIFO signaling");
189UnwrapFIFOOp::inferReturnTypes(MLIRContext *
context, std::optional<Location>,
190 ValueRange operands, DictionaryAttr,
191 mlir::PropertyRef, mlir::RegionRange,
192 SmallVectorImpl<Type> &inferredResulTypes) {
193 inferredResulTypes.push_back(
194 cast<ChannelType>(operands[0].getType()).getInner());
195 inferredResulTypes.push_back(
196 IntegerType::get(
context, 1, IntegerType::Signless));
204circt::esi::ChannelType WrapValidOnlyOp::channelType() {
205 return cast<circt::esi::ChannelType>(getChanOutput().getType());
208LogicalResult WrapValidOnlyOp::verify() {
209 ChannelType chanType = getChanOutput().getType();
210 if (chanType.getSignaling() != ChannelSignaling::ValidOnly)
211 return emitOpError(
"only supports valid-only signaling");
212 if (chanType.getInner() != getRawInput().getType())
213 return emitOpError(
"input and output types must match");
217circt::esi::ChannelType UnwrapValidOnlyOp::channelType() {
218 return cast<circt::esi::ChannelType>(getChanInput().getType());
221LogicalResult UnwrapValidOnlyOp::verify() {
222 if (getChanInput().getType().getSignaling() != ChannelSignaling::ValidOnly)
223 return emitOpError(
"only supports ValidOnly signaling");
227LogicalResult UnwrapValidOnlyOp::inferReturnTypes(
228 MLIRContext *
context, std::optional<Location>, ValueRange operands,
229 DictionaryAttr, mlir::PropertyRef, mlir::RegionRange,
230 SmallVectorImpl<Type> &inferredResulTypes) {
231 inferredResulTypes.push_back(
232 cast<ChannelType>(operands[0].getType()).getInner());
233 inferredResulTypes.push_back(
234 IntegerType::get(
context, 1, IntegerType::Signless));
238LogicalResult WrapWindow::verify() {
239 Type expectedInput = getWindow().getType().getLoweredType();
240 if (expectedInput == getFrame().getType())
242 return emitOpError(
"Expected input type is ") << expectedInput;
246UnwrapWindow::inferReturnTypes(MLIRContext *, std::optional<Location>,
247 ValueRange operands, DictionaryAttr,
248 mlir::PropertyRef, mlir::RegionRange,
249 SmallVectorImpl<Type> &inferredReturnTypes) {
250 auto windowType = cast<WindowType>(operands.front().getType());
251 inferredReturnTypes.push_back(windowType.getLoweredType());
258 if (p.parseType(window))
261 frame = window.getLoweredType();
274LogicalResult CosimFromHostEndpointOp::verify() {
275 ChannelType chanType = getFromHost().getType();
276 if (chanType.getSignaling() != ChannelSignaling::ValidReady)
277 return emitOpError(
"only supports valid-ready signaling");
281LogicalResult CosimToHostEndpointOp::verify() {
282 ChannelType chanType = getToHost().getType();
283 if (chanType.getSignaling() != ChannelSignaling::ValidReady)
284 return emitOpError(
"only supports valid-ready signaling");
293static FailureOr<ServiceDeclOpInterface>
295 hw::InnerRefAttr servicePort) {
296 ModuleOp top = op->getParentOfType<mlir::ModuleOp>();
297 SymbolTable &topSyms = symbolTable.getSymbolTable(top);
299 StringAttr modName = servicePort.getModule();
300 auto serviceDecl = topSyms.lookup<ServiceDeclOpInterface>(modName);
302 return op->emitOpError(
"Could not find service declaration ")
303 << servicePort.getModuleRef();
308static FailureOr<ServicePortInfo>
310 hw::InnerRefAttr servicePort) {
312 if (failed(serviceDecl))
314 auto portInfo = serviceDecl->getPortInfo(servicePort.getName());
315 if (failed(portInfo))
316 return op->emitOpError(
"Could not locate port ") << servicePort.getName();
325 if (expected == actual)
329 return TypeSwitch<Type, LogicalResult>(expected)
331 .Case<AnyType>([&](Type) {
return success(); })
333 .Case<ChannelType>([&](ChannelType expectedChannel) {
334 auto actualChannel = dyn_cast<ChannelType>(actual);
338 actualChannel.getInner());
341 .Case<hw::StructType>([&](hw::StructType expectedStruct) {
342 auto actualStruct = dyn_cast<hw::StructType>(actual);
345 auto expectedFields = expectedStruct.getElements();
346 auto actualFields = actualStruct.getElements();
347 if (expectedFields.size() != actualFields.size())
349 for (
auto [efield, afield] : llvm::zip(expectedFields, actualFields)) {
350 if (efield.name != afield.name)
358 .Case<hw::ArrayType>([&](hw::ArrayType expectedArray) {
359 auto actualArray = dyn_cast<hw::ArrayType>(actual);
362 if (expectedArray.getNumElements() != actualArray.getNumElements())
365 actualArray.getElementType());
368 .Case<hw::UnionType>([&](hw::UnionType expectedUnion) {
369 auto actualUnion = dyn_cast<hw::UnionType>(actual);
372 auto expectedElements = expectedUnion.getElements();
373 auto actualElements = actualUnion.getElements();
374 if (expectedElements.size() != actualElements.size())
376 for (
auto [efield, afield] :
377 llvm::zip(expectedElements, actualElements)) {
378 if (efield.name != afield.name)
380 if (efield.offset != afield.offset)
388 .Case<ListType>([&](ListType expectedList) {
389 auto actualList = dyn_cast<ListType>(actual);
393 actualList.getElementType());
396 .Case<WindowType>([&](WindowType expectedWindow) {
397 auto actualWindow = dyn_cast<WindowType>(actual);
401 actualWindow.getInto());
404 .Case<hw::TypeAliasType>([&](hw::TypeAliasType expectedAlias) {
405 auto actualAlias = dyn_cast<hw::TypeAliasType>(actual);
409 actualAlias.getCanonicalType());
412 .Default([&](Type) {
return failure(); });
418 ChannelBundleType svcBundleType,
419 ChannelBundleType reqBundleType,
420 bool skipDirectionCheck) {
421 if (svcBundleType.getChannels().size() != reqBundleType.getChannels().size())
422 return req->emitOpError(
423 "Request port bundle channel count does not match service "
424 "port bundle channel count");
427 DenseMap<StringAttr, BundledChannel> declBundleChannels;
429 declBundleChannels[bc.name] = bc;
433 auto f = declBundleChannels.find(bc.name);
434 if (f == declBundleChannels.end())
435 return req->emitOpError(
436 "Request channel name not found in service port bundle");
437 if (!skipDirectionCheck && f->second.direction != bc.direction)
438 return req->emitOpError(
439 "Request channel direction does not match service "
440 "port bundle channel direction");
443 return req->emitOpError(
444 "Request channel type does not match service port "
445 "bundle channel type")
447 <<
"Service port '" << bc.name.getValue()
448 <<
"' type: " << f->second.type;
457RequestConnectionOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
464 auto svcDecl =
getServiceDecl(*
this, symbolTable, getServicePortAttr());
467 return svcDecl->verifyRequest(*svcPort, getToClient().getType(), *
this);
470LogicalResult ServiceImplementConnReqOp::verifySymbolUses(
471 SymbolTableCollection &symbolTable) {
478 auto svcDecl =
getServiceDecl(*
this, symbolTable, getServicePortAttr());
481 return svcDecl->verifyRequest(*svcPort, getToClient().getType(), *
this);
484void CustomServiceDeclOp::getPortList(SmallVectorImpl<ServicePortInfo> &ports) {
485 for (
auto toClient : getOps<ServiceDeclPortOp>())
487 hw::InnerRefAttr::get(getSymNameAttr(), toClient.getInnerSymAttr()),
488 toClient.getToClientType()});
497 SmallVectorImpl<Type> &toChannelTypes,
498 SmallVectorImpl<Type> &fromChannelTypes, Type &type) {
500 ChannelBundleType bundleType;
501 if (parser.parseType(bundleType))
506 if (ch.direction == ChannelDirection::to)
507 toChannelTypes.push_back(ch.type);
508 else if (ch.direction == ChannelDirection::from)
509 fromChannelTypes.push_back(ch.type);
511 assert(
false &&
"Channel direction invalid");
514template <
typename T3,
typename T4>
517 p.printType(bundleType);
519void UnpackBundleOp::build(::mlir::OpBuilder &odsBuilder,
520 ::mlir::OperationState &odsState, Value bundle,
521 mlir::ValueRange fromChannels) {
523 cast<ChannelBundleType>(bundle.getType()).getChannels())
524 if (ch.direction == ChannelDirection::to)
525 odsState.addTypes(ch.type);
526 odsState.addOperands(bundle);
527 odsState.addOperands(fromChannels);
530LogicalResult PackBundleOp::verify() {
531 if (!getBundle().hasOneUse()) {
532 if (getBundle().getUsers().
empty())
533 return emitOpError(
"bundle must be used");
534 return emitOpError(
"bundle must only be used once");
538void PackBundleOp::build(::mlir::OpBuilder &odsBuilder,
539 ::mlir::OperationState &odsState,
540 ChannelBundleType bundleType,
541 mlir::ValueRange toChannels) {
542 odsState.addTypes(bundleType);
543 for (
BundledChannel ch : cast<ChannelBundleType>(bundleType).getChannels())
544 if (ch.direction == ChannelDirection::from)
545 odsState.addTypes(ch.type);
546 odsState.addOperands(toChannels);
549LogicalResult UnpackBundleOp::verify() {
550 if (!getBundle().hasOneUse()) {
551 if (getBundle().getUsers().
empty())
552 return emitOpError(
"bundle must be used");
553 return emitOpError(
"bundle must only be used once");
559 if (getNumResults() == 0)
561 setNameFn(getResult(0),
"bundle");
562 for (
auto [idx, from] : llvm::enumerate(llvm::make_filter_range(
564 return ch.
direction == ChannelDirection::from;
566 if (idx + 1 < getNumResults())
567 setNameFn(getResult(idx + 1), from.name.getValue());
571 for (
auto [idx, to] : llvm::enumerate(llvm::make_filter_range(
573 return ch.
direction == ChannelDirection::to;
575 if (idx < getNumResults())
576 setNameFn(getResult(idx), to.name.getValue());
582LogicalResult ESIPureModuleOp::verify() {
583 ESIDialect *esiDialect = getContext()->getLoadedDialect<ESIDialect>();
584 Block &body = getBody().front();
585 auto channelOrOutput = [](Value v) {
586 if (isa<ChannelType, ChannelBundleType>(v.getType()))
588 if (v.getUsers().empty())
590 return llvm::all_of(v.getUsers(), [](Operation *op) {
591 return isa<ESIPureModuleOutputOp>(op);
595 DenseMap<StringAttr, std::tuple<hw::ModulePort::Direction, Type, Operation *>>
597 for (Operation &op : body.getOperations()) {
598 if (igraph::InstanceOpInterface inst =
599 dyn_cast<igraph::InstanceOpInterface>(op)) {
600 if (llvm::any_of(op.getOperands(), [](Value v) {
601 return !(isa<ChannelType, ChannelBundleType>(v.getType()) ||
602 isa<ESIPureModuleInputOp>(v.getDefiningOp()));
604 return inst.emitOpError(
605 "instances in ESI pure modules can only contain channel ports or "
606 "ports driven by 'input' ops");
607 if (!llvm::all_of(op.getResults(), channelOrOutput))
608 return inst.emitOpError(
609 "instances in ESI pure modules can only contain channel ports or "
610 "drive only 'outputs'");
613 if (op.getDialect() != esiDialect)
614 return op.emitOpError(
"operation not allowed in ESI pure modules");
618 if (
auto port = dyn_cast<ESIPureModuleInputOp>(op)) {
619 auto existing = ports.find(port.getNameAttr());
620 Type portType = port.getResult().getType();
621 if (existing != ports.end()) {
622 auto [dir, type, op] = existing->getSecond();
624 return (port.emitOpError(
"port '")
625 << port.getName() <<
"' previously declared as type " << type)
626 .attachNote(op->getLoc());
628 ports[port.getNameAttr()] = std::make_tuple(
630 }
else if (
auto port = dyn_cast<ESIPureModuleOutputOp>(op)) {
631 auto existing = ports.find(port.getNameAttr());
632 if (existing != ports.end())
633 return (port.emitOpError(
"port '")
634 << port.getName() <<
"' previously declared")
635 .attachNote(std::get<2>(existing->getSecond())->getLoc());
636 ports[port.getNameAttr()] =
638 port.getValue().getType(), port.getOperation());
644hw::ModuleType ESIPureModuleOp::getHWModuleType() {
645 return hw::ModuleType::get(getContext(), {});
648SmallVector<::circt::hw::PortInfo> ESIPureModuleOp::getPortList() {
return {}; }
650 ::llvm::report_fatal_error(
"not supported");
653size_t ESIPureModuleOp::getNumPorts() {
return 0; }
654size_t ESIPureModuleOp::getNumInputPorts() {
return 0; }
655size_t ESIPureModuleOp::getNumOutputPorts() {
return 0; }
656size_t ESIPureModuleOp::getPortIdForInputId(
size_t) {
657 assert(0 &&
"Out of bounds input port id");
660size_t ESIPureModuleOp::getPortIdForOutputId(
size_t) {
661 assert(0 &&
"Out of bounds output port id");
665SmallVector<Location> ESIPureModuleOp::getAllPortLocs() {
666 SmallVector<Location> retval;
670void ESIPureModuleOp::setAllPortLocsAttrs(ArrayRef<Attribute> locs) {
671 emitError(
"No ports for port locations");
674void ESIPureModuleOp::setAllPortNames(ArrayRef<Attribute> names) {
675 emitError(
"No ports for port naming");
678void ESIPureModuleOp::setAllPortAttrs(ArrayRef<Attribute>
attrs) {
679 emitError(
"No ports for port attributes");
682void ESIPureModuleOp::removeAllPortAttrs() {
683 emitError(
"No ports for port attributes)");
686ArrayRef<Attribute> ESIPureModuleOp::getAllPortAttrs() {
return {}; }
688void ESIPureModuleOp::setHWModuleType(hw::ModuleType type) {
689 emitError(
"No ports for port types");
696StringRef ServiceImplRecordOp::getManifestClass() {
697 return getIsEngine() ?
"engine" :
"service";
700void ServiceImplRecordOp::getDetails(SmallVectorImpl<NamedAttribute> &results) {
701 auto *ctxt = getContext();
703 results.emplace_back(getAppIDAttrName(), getAppIDAttr());
705 results.emplace_back(getServiceAttrName(), getServiceAttr());
706 results.emplace_back(getServiceImplNameAttrName(), getServiceImplNameAttr());
708 for (
auto implDetail : getImplDetailsAttr().getValue())
709 results.push_back(implDetail);
712 SmallVector<Attribute, 8> reqDetails;
713 for (
auto reqDetail : getReqDetails().front().getOps<IsManifestData>())
714 reqDetails.push_back(reqDetail.getDetailsAsDict());
715 results.emplace_back(StringAttr::get(ctxt,
"clientDetails"),
716 ArrayAttr::get(ctxt, reqDetails));
720 Region &reqDetailsRegion) {
721 parser.parseOptionalRegion(reqDetailsRegion);
722 if (reqDetailsRegion.empty())
723 reqDetailsRegion.emplaceBlock();
728 Region &reqDetailsRegion) {
729 if (!reqDetailsRegion.empty() && !reqDetailsRegion.front().empty())
730 p.printRegion(reqDetailsRegion,
false,
734StringRef ServiceImplClientRecordOp::getManifestClass() {
735 return "serviceClient";
737void ServiceImplClientRecordOp::getDetails(
738 SmallVectorImpl<NamedAttribute> &results) {
742 results.emplace_back(getRelAppIDPathAttrName(), getRelAppIDPathAttr());
743 auto servicePort = getServicePortAttr();
744 results.emplace_back(
745 getServicePortAttrName(),
749 NamedAttribute(StringAttr::get(getContext(),
"serviceName"),
750 FlatSymbolRefAttr::get(servicePort.getModule())),
751 NamedAttribute(StringAttr::get(getContext(),
"port"),
752 servicePort.getName()),
754 if (
const auto &channelAssignments = getChannelAssignments())
756 NamedAttribute(getChannelAssignmentsAttrName(), *channelAssignments));
758 if (
const auto &implDetails = getImplDetails())
759 for (
const auto &implDetail : *implDetails)
760 results.push_back(implDetail);
763StringRef ServiceRequestRecordOp::getManifestClass() {
return "clientPort"; }
765void ServiceRequestRecordOp::getDetails(
766 SmallVectorImpl<NamedAttribute> &results) {
767 auto *ctxt = getContext();
768 results.emplace_back(StringAttr::get(ctxt,
"appID"), getRequestorAttr());
769 results.emplace_back(getTypeIDAttrName(), getTypeIDAttr());
770 auto servicePort = getServicePortAttr();
771 results.emplace_back(
772 getServicePortAttrName(),
776 NamedAttribute(StringAttr::get(getContext(),
"serviceName"),
777 FlatSymbolRefAttr::get(servicePort.getModule())),
778 NamedAttribute(StringAttr::get(getContext(),
"port"),
779 servicePort.getName()),
783StringRef SymbolMetadataOp::getManifestClass() {
return "symInfo"; }
785StringRef SymbolConstantsOp::getManifestClass() {
return "symConsts"; }
786void SymbolConstantsOp::getDetails(SmallVectorImpl<NamedAttribute> &results) {
787 for (
auto &attr : getConstantsAttr())
788 results.push_back(attr);
791#define GET_OP_CLASSES
792#include "circt/Dialect/ESI/ESI.cpp.inc"
794#include "circt/Dialect/ESI/ESIInterfaces.cpp.inc"
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
static FailureOr< ServiceDeclOpInterface > getServiceDecl(Operation *op, SymbolTableCollection &symbolTable, hw::InnerRefAttr servicePort)
Get the port declaration op for the specified service decl, port name.
static bool parseInferWindowRet(OpAsmParser &p, Type &frame, Type &windowOut)
Determine the input type ('frame') from the return type ('window').
static void printWrapChannelType(OpAsmPrinter &p, Operation *, Type, Type chanOutputType)
void printServiceImplRecordReqDetails(OpAsmPrinter &p, ServiceImplRecordOp, Region &reqDetailsRegion)
static ParseResult parseWrapChannelType(OpAsmParser &p, Type &dataType, Type &chanOutputType)
Wrap ops print only their channel type, from which the data type is derived.
static FailureOr< ServicePortInfo > getServicePortInfo(Operation *op, SymbolTableCollection &symbolTable, hw::InnerRefAttr servicePort)
Get the port info for the specified service decl and port name.
static void printUnPackBundleType(OpAsmPrinter &p, Operation *, T3, T4, Type bundleType)
static ParseResult parseUnPackBundleType(OpAsmParser &parser, SmallVectorImpl< Type > &toChannelTypes, SmallVectorImpl< Type > &fromChannelTypes, Type &type)
static void printInferWindowRet(OpAsmPrinter &p, Operation *, Type, Type window)
bool parseServiceImplRecordReqDetails(OpAsmParser &parser, Region &reqDetailsRegion)
false return failure()
Parse an optional keyword or string and set instance into 'result'.
static InstancePath empty
LogicalResult checkBundleTypeMatch(Operation *req, ChannelBundleType svcBundleType, ChannelBundleType reqBundleType, bool skipDirectionCheck)
Check that the channels on two bundles match allowing for AnyType in the 'svc' bundle.
LogicalResult checkInnerTypeMatch(Type expected, Type actual)
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
function_ref< void(Value, StringRef)> OpAsmSetValueNameFn
ChannelDirection direction
Describes a service port.
This holds the name, type, direction of a module's ports.