Cerata
A library to generate structural hardware designs
edge.cc
1 // Copyright 2018-2019 Delft University of Technology
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "cerata/edge.h"
16 
17 #include <memory>
18 #include <utility>
19 #include <vector>
20 #include <string>
21 #include <optional>
22 
23 #include "cerata/graph.h"
24 #include "cerata/node.h"
25 #include "cerata/logging.h"
26 
27 namespace cerata {
28 
29 Edge::Edge(std::string name, Node *dst, Node *src)
30  : Named(std::move(name)), dst_(dst), src_(src) {
31  if ((dst == nullptr) || (src == nullptr)) {
32  CERATA_LOG(FATAL, "Cannot construct edge with nullptr nodes.");
33  }
34 }
35 
36 std::shared_ptr<Edge> Edge::Make(const std::string &name,
37  Node *dst,
38  Node *src) {
39  auto e = new Edge(name, dst, src);
40  return std::shared_ptr<Edge>(e);
41 }
42 
43 static void CheckDomains(Node *src, Node *dst) {
44  if ((src->IsPort() || src->IsSignal()) && (dst->IsPort() || dst->IsSignal())) {
45  auto src_dom = dynamic_cast<Synchronous *>(src)->domain();
46  auto dst_dom = dynamic_cast<Synchronous *>(dst)->domain();
47  if (src_dom != dst_dom) {
48  std::stringstream warning;
49  warning << "Attempting to connect Synchronous nodes, but clock domains differ.\n";
50 
51  warning << "Src: [" + src->ToString() + "] in domain: [" + dst_dom->name() + "]";
52  if (src->parent()) {
53  warning << " on parent: [" + src->parent().value()->name() + "]";
54  }
55 
56  warning << "\nDst: [" + dst->ToString() + "] in domain: [" + src_dom->name() + "]";
57  if (dst->parent()) {
58  warning << " on parent: [" + dst->parent().value()->name() + "]";
59  }
60 
61  warning << "\nAutomated CDC crossings are not yet implemented or instantiated.";
62  warning << "This behavior may cause incorrect designs.";
63  CERATA_LOG(WARNING, warning.str());
64  }
65  }
66 }
67 
68 std::shared_ptr<Edge> Connect(Node *dst, Node *src) {
69  // Check for potential errors
70  if (src == nullptr) {
71  CERATA_LOG(FATAL, "Source node is null");
72  return nullptr;
73  } else if (dst == nullptr) {
74  CERATA_LOG(FATAL, "Destination node is null");
75  return nullptr;
76  }
77 
78  // Check if the clock domains correspond. Currently, this doesn't result in an error as automated CDC support is not
79  // in place yet. Just generate a warning for now:
80  CheckDomains(src, dst);
81 
82  // Check if either source or destination is a signal or port.
83  if (src->IsPort() || src->IsSignal()) {
84  // Check if the types can be mapped onto each other.
85  if (!src->type()->GetMapper(dst->type())) {
86  CERATA_LOG(ERROR, "No known type mapping available for connection between node ["
87  + dst->ToString() + "] and ["
88  + src->ToString() + "]");
89  }
90  // TODO(johanpel): do something similar for parameters, literals, etc...
91  }
92 
93  // Deal with specific of nodes that are on a graph.
94  if (src->parent() && dst->parent()) {
95  auto sp = src->parent().value();
96  auto dp = dst->parent().value();
97  if (dp->IsComponent()) {
98  if (sp->IsComponent() && (sp != dp)) {
99  // Check if we're not making a component to component connection on different components.
100  CERATA_LOG(ERROR, "Edge between component " + dp->name() + " node " + dst->name() +
101  " and component " + sp->name() + " node " + src->name() + " not allowed.");
102  }
103  auto si = dynamic_cast<Instance *>(sp);
104  auto dc = dynamic_cast<Component *>(dp);
105  // Check if we're not sourcing from an instance parameter into a node on the instance parent:
106  if (dc->HasChild(*si) && src->IsParameter()) {
107  CERATA_LOG(ERROR, "Instance parameters can not source component nodes.");
108  }
109  }
110  }
111  if (dst->parent()) {
112  auto dp = dst->parent().value();
113  if (dp->IsInstance()) {
114  auto ip = dynamic_cast<Instance *>(dp);
115  // When we're connecting a parameter node of an instance, add it to the instance-to-component rebind map.
116  // We must do this because otherwise, if we e.g. attach signals to instance ports, the signal type generics
117  // could be bound to the instance parameter. They should be rebound to the source of the parameter.
118  auto map = dynamic_cast<Component *>(ip->parent())->inst_to_comp_map();
119  (*map)[dst] = src;
120  }
121  }
122 
123  // If the destination is a terminator
124  if (dst->IsPort()) {
125  auto port = dynamic_cast<Port *>(dst);
126  // Check if it has a parent
127  if (dst->parent()) {
128  auto parent = *dst->parent();
129  if (parent->IsInstance() && port->IsOutput()) {
130  // If the parent is an instance, and the terminator node is an output, then we may not drive it.
131  CERATA_LOG(FATAL,
132  "Cannot drive instance " + dst->parent().value()->ToString() + " port " + dst->ToString()
133  + " of mode output with " + src->ToString());
134  } else if (parent->IsComponent() && port->IsInput()) {
135  // If the parent is a component, and the terminator node is an input, then we may not drive it.
136  CERATA_LOG(FATAL,
137  "Cannot drive component " + dst->parent().value()->ToString() + " port " + dst->ToString()
138  + " of mode input with " + src->ToString());
139  }
140  }
141  }
142 
143  // If the source is a terminator
144  if (src->IsPort()) {
145  auto port = dynamic_cast<Port *>(src);
146  // Check if it has a parent
147  if (src->parent()) {
148  auto parent = *src->parent();
149  if (parent->IsInstance() && port->IsInput()) {
150  // If the parent is an instance, and the terminator node is an input, then we may not source from it.
151  CERATA_LOG(FATAL,
152  "Cannot source from instance port " + src->ToString() + " of mode input on " + parent->ToString());
153  } else if (parent->IsComponent() && port->IsOutput()) {
154  // If the parent is a component, and the terminator node is an output, then we may not source from it.
155  CERATA_LOG(FATAL,
156  "Cannot source from component port " + src->ToString() + " of mode output on " + parent->ToString());
157  }
158  }
159  }
160 
161  std::string edge_name = src->name() + "_to_" + dst->name();
162  auto edge = Edge::Make(edge_name, dst, src);
163  src->AddEdge(edge);
164  dst->AddEdge(edge);
165  return edge;
166 }
167 
168 std::shared_ptr<Edge> operator<<=(Node *dst, const std::shared_ptr<Node> &src) {
169  return Connect(dst, src.get());
170 }
171 
172 std::shared_ptr<Edge> operator<<=(const std::shared_ptr<Node> &dst, const std::shared_ptr<Node> &src) {
173  return Connect(dst.get(), src.get());
174 }
175 
176 std::shared_ptr<Edge> operator<<=(const std::shared_ptr<Node> &dst, Node *src) {
177  return Connect(dst.get(), src);
178 }
179 
180 std::vector<Edge *> GetAllEdges(const Graph &graph) {
181  std::vector<Edge *> all_edges;
182 
183  // Get all normal nodes
184  for (const auto &node : graph.GetAll<Node>()) {
185  auto out_edges = node->sinks();
186  for (const auto &e : out_edges) {
187  all_edges.push_back(e);
188  }
189  auto in_edges = node->sources();
190  for (const auto &e : in_edges) {
191  all_edges.push_back(e);
192  }
193  }
194 
195  for (const auto &array : graph.GetAll<NodeArray>()) {
196  for (const auto &node : array->nodes()) {
197  auto out_edges = node->sinks();
198  for (const auto &e : out_edges) {
199  all_edges.push_back(e);
200  }
201  auto in_edges = node->sources();
202  for (const auto &e : in_edges) {
203  all_edges.push_back(e);
204  }
205  }
206  }
207 
208  if (graph.IsComponent()) {
209  auto &comp = dynamic_cast<const Component &>(graph);
210  for (const auto &g : comp.children()) {
211  auto child_edges = GetAllEdges(*g);
212  all_edges.insert(all_edges.end(), child_edges.begin(), child_edges.end());
213  }
214  }
215 
216  return all_edges;
217 }
218 
219 std::shared_ptr<Edge> Connect(Node *dst, const std::shared_ptr<Node> &src) {
220  return Connect(dst, src.get());
221 }
222 
223 std::shared_ptr<Edge> Connect(const std::shared_ptr<Node> &dst, Node *src) {
224  return Connect(dst.get(), src);
225 }
226 
227 std::shared_ptr<Edge> Connect(const std::shared_ptr<Node> &dst, const std::shared_ptr<Node> &src) {
228  return Connect(dst.get(), src.get());
229 }
230 
231 std::optional<Node *> Edge::GetOtherNode(const Node &node) const {
232  if (src_ == &node) {
233  return dst_;
234  } else if (dst_ == &node) {
235  return src_;
236  } else {
237  return std::nullopt;
238  }
239 }
240 
241 static std::shared_ptr<ClockDomain> DomainOf(NodeArray *node_array) {
242  std::shared_ptr<ClockDomain> result;
243  auto base = node_array->base();
244  if (base->IsSignal()) {
245  result = std::dynamic_pointer_cast<Signal>(base)->domain();
246  } else if (base->IsPort()) {
247  result = std::dynamic_pointer_cast<Port>(base)->domain();
248  } else {
249  throw std::runtime_error("Base node is not a signal or port.");
250  }
251  return result;
252 }
253 
254 Signal *AttachSignalToNode(Component *comp, NormalNode *node, NodeMap *rebinding, std::string name) {
255  std::shared_ptr<Type> type = node->type()->shared_from_this();
256  // Figure out if the type is a generic type.
257  if (type->IsGeneric()) {
258  // In that case, obtain the type generic nodes.
259  auto generics = type->GetGenerics();
260  // We need to copy over any type generic nodes that are not on the component yet.
261  // Potentially produce new generics in the rebind map.
262  ImplicitlyRebindNodes(comp, generics, rebinding);
263  // Now we must rebind the type generics to the nodes on the component graph.
264  // Copy the type and provide the rebind map.
265  type = type->Copy(*rebinding);
266  }
267 
268  // Get the clock domain of the node.
269  auto domain = default_domain();
270  if (node->IsSignal()) domain = node->AsSignal()->domain();
271  if (node->IsPort()) domain = node->AsPort()->domain();
272 
273  // Get the name of the node if no name was supplied.
274  if (name.empty()) {
275  name = node->name();
276  // Check if the node is on an instance, and prepend that.
277  if (node->parent()) {
278  if (node->parent().value()->IsInstance()) {
279  name = node->parent().value()->name() + "_" + name;
280  }
281  }
282  auto new_name = name;
283  // Check if a node with this name already exists, generate a new name suffixed with a number if it does.
284  int i = 0;
285  while (comp->Has(new_name)) {
286  i++;
287  new_name = name + "_" + std::to_string(i);
288  }
289  name = new_name;
290  }
291 
292  // Create the new signal.
293  auto new_signal = signal(name, type, domain);
294 
295  // Copy metadata
296  new_signal->meta = node->meta;
297 
298  comp->Add(new_signal);
299 
300  // Iterate over any existing edges that are sinks of the original node.
301  for (auto &e : node->sinks()) {
302  // Figure out the original destination of this edge.
303  auto dst = e->dst();
304  // Remove the edge from the original node and the destination node.
305  node->RemoveEdge(e);
306  dst->RemoveEdge(e);
307  // Create a new edge, driving the destination with the new signal.
308  Connect(dst, new_signal);
309  Connect(new_signal, node);
310  }
311 
312  // Iterate over any existing edges that source the original node.
313  for (auto &e : node->sources()) {
314  // Get the destination node.
315  auto src = e->src();
316  // Remove the original edge from the port array child node and source node.
317  node->RemoveEdge(e);
318  src->RemoveEdge(e);
319  // Create a new edge, driving the new signal with the original source.
320  Connect(new_signal, src);
321  Connect(node, new_signal);
322  }
323 
324  return new_signal.get();
325 }
326 
328  // The size parameter must potentially be "rebound".
329  ImplicitlyRebindNodes(comp, {array->size()}, rebinding);
330  auto size = rebinding->at(array->size())->shared_from_this();
331 
332  // Get the base node.
333  auto base = array->base();
334 
335  std::shared_ptr<Type> type = base->type()->shared_from_this();
336  // Figure out if the type is a generic type.
337  if (type->IsGeneric()) {
338  // In that case, obtain the type generic nodes.
339  auto generics = base->type()->GetGenerics();
340  // We need to copy over any type generic nodes that are not on the component yet.
341  // Potentially produce new generics in the rebind map.
342  ImplicitlyRebindNodes(comp, generics, rebinding);
343  // Now we must rebind the type generics to the nodes on the component graph.
344  // Copy the type and provide the rebind map.
345  type = array->type()->Copy(*rebinding);
346  }
347 
348  // Get the clock domain of the array node
349  auto domain = DomainOf(array);
350  auto name = array->name();
351  // Check if the array is on an instance, and prepend that.
352  if (array->parent()) {
353  if (array->parent().value()->IsInstance()) {
354  name = array->parent().value()->name() + "_" + name;
355  }
356  }
357  auto new_name = name;
358  // Check if a node with this name already exists, generate a new name suffixed with a number if it does.
359  int i = 0;
360  while (comp->Has(new_name)) {
361  i++;
362  new_name = name + "_" + std::to_string(i);
363  }
364 
365  // Create the new array.
366  auto new_array = signal_array(new_name, type, size, domain);
367  comp->Add(new_array);
368 
369  // Now iterate over all original nodes on the NodeArray and reconnect them.
370  for (size_t n = 0; n < array->num_nodes(); n++) {
371  // Create a new child node inside the array, but don't increment the size.
372  // We've already (potentially) rebound the size from some other source and it should be set properly already.
373  auto new_sig = new_array->Append(false);
374 
375  bool has_sinks = false;
376  bool has_sources = false;
377 
378  // Iterate over any existing edges that are sinked by the original array node.
379  for (auto &e : array->node(n)->sinks()) {
380  // Figure out the original destination.
381  auto dst = e->dst();
382  // Source the destination with the new signal.
383  Connect(dst, new_sig);
384  // Remove the original edge from the port array child node and destination node.
385  array->node(n)->RemoveEdge(e);
386  dst->RemoveEdge(e);
387  has_sinks = true;
388  }
389 
390  // Iterate over any existing edges that source the original array node.
391  for (auto &e : array->node(n)->sources()) {
392  // Get the destination node.
393  auto src = e->src();
394  Connect(new_sig, src);
395  // Remove the original edge from the port array child node and source node.
396  array->node(n)->RemoveEdge(e);
397  src->RemoveEdge(e);
398  has_sources = true;
399  }
400 
401  // Source the new signal node with the original node, depending on whether it had any sinks or sources.
402  if (has_sinks) {
403  Connect(new_sig, array->node(n));
404  }
405  if (has_sources) {
406  Connect(array->node(n), new_sig);
407  }
408  }
409  return new_array.get();
410 }
411 
412 std::shared_ptr<Edge> Connect(Node *dst, std::string str) {
413  return Connect(dst, strl(std::move(str)));
414 }
415 
416 } // namespace cerata
cerata::Node::sinks
virtual std::vector< Edge * > sinks() const
Get the output edges of this Node.
Definition: node.h:88
cerata::NodeArray::type
Type * type() const
Return the type of the nodes in the NodeArray.
Definition: array.h:50
cerata::Object::meta
std::unordered_map< std::string, std::string > meta
KV storage for metadata of tools or specific backend implementations.
Definition: object.h:67
cerata::Component
A Component graph.
Definition: graph.h:158
cerata::Node::sources
virtual std::vector< Edge * > sources() const
Get the input edges of this Node.
Definition: node.h:86
cerata::GetAllEdges
std::vector< Edge * > GetAllEdges(const Graph &graph)
Obtain all edges in a graph.
Definition: edge.cc:180
cerata::Graph::GetAll
std::vector< T * > GetAll() const
Get all objects of a specific type.
Definition: graph.h:63
cerata::operator<<=
std::shared_ptr< Edge > operator<<=(Node *dst, const std::shared_ptr< Node > &src)
Create an edge, connecting the src node to the dst node.
Definition: edge.cc:168
cerata::SignalArray
An array of signal nodes.
Definition: array.h:100
cerata::Edge::dst
Node * dst() const
Return the destination node.
Definition: edge.h:44
cerata::Component::Add
Graph & Add(const std::shared_ptr< Object > &object) override
Add an object to the component.
Definition: graph.cc:361
cerata::Edge::src
Node * src() const
Return the source node.
Definition: edge.h:46
cerata::NormalNode::sources
std::vector< Edge * > sources() const override
Return the incoming edges (in this case just the single input edge).
Definition: node.cc:197
cerata::NodeArray::num_nodes
size_t num_nodes() const
Return the number of element nodes.
Definition: array.h:67
cerata::Graph
A graph representing a hardware structure.
Definition: graph.h:37
cerata::NodeArray
An array of nodes.
Definition: array.h:31
cerata::Signal
A Signal Node.
Definition: signal.h:30
cerata
Contains every Cerata class, function, etc...
Definition: api.h:41
cerata::Graph::IsComponent
bool IsComponent() const
Return true if this graph is a component, false otherwise.
Definition: graph.h:51
cerata::AttachSignalToNode
Signal * AttachSignalToNode(Component *comp, NormalNode *node, NodeMap *rebinding, std::string name)
Attach a Signal to a Node, redirecting all edges through the new Signal.
Definition: edge.cc:254
cerata::Type::GetMapper
std::optional< std::shared_ptr< TypeMapper > > GetMapper(Type *other, bool generate_implicit=true)
Get a mapper to another type, if it exists. Generates one, if possible, when generate_implicit = true...
Definition: type.cc:112
cerata::Node
A node.
Definition: node.h:42
cerata::NodeMap
std::unordered_map< const Node *, Node * > NodeMap
A mapping from one object to another object, used in e.g. type generic rebinding.
Definition: node.h:135
cerata::strl
std::shared_ptr< Literal > strl(std::string str)
Obtain a shared pointer to a string literal from the default node pool.
Definition: pool.h:151
cerata::Synchronous
Class to mark nodes with information for synchronous designs, e.g. clock domain.
Definition: domain.h:46
cerata::Edge::Make
static std::shared_ptr< Edge > Make(const std::string &name, Node *dst, Node *src)
Shorthand to get a smart pointer to an edge.
Definition: edge.cc:36
cerata::Named::name
std::string name() const
Return the name of the object.
Definition: utils.h:45
cerata::Node::RemoveEdge
virtual bool RemoveEdge(Edge *edge)=0
Remove an edge of this node.
cerata::NormalNode
A single-input, multiple-outputs node.
Definition: node.h:167
cerata::Edge::src_
Node * src_
Source node.
Definition: edge.h:60
cerata::Node::AddEdge
virtual bool AddEdge(const std::shared_ptr< Edge > &edge)=0
Add an edge to this node.
cerata::NodeArray::base
std::shared_ptr< Node > base() const
Return the base node of this NodeArray.
Definition: array.h:75
cerata::Named
Convenience structure for anything that is named. Names are case-sensitive.
Definition: utils.h:40
cerata::Edge::dst_
Node * dst_
Destination node.
Definition: edge.h:58
cerata::AttachSignalArrayToNodeArray
SignalArray * AttachSignalArrayToNodeArray(Component *comp, NodeArray *array, NodeMap *rebinding)
Attach a SignalArray to a Node, redirecting all edges through the new SignalArray.
Definition: edge.cc:327
cerata::Instance
An instance.
Definition: graph.h:231
cerata::MultiOutputNode::sinks
std::vector< Edge * > sinks() const override
The outgoing Edges that this Node sinks.
Definition: node.h:151
cerata::default_domain
std::shared_ptr< ClockDomain > default_domain()
Return a static default clock domain.
Definition: domain.cc:28
cerata::Graph::Has
bool Has(const std::string &name)
Return true if object with name already exists on graph.
Definition: graph.cc:220
cerata::Node::ToString
virtual std::string ToString() const
Return a human-readable string of this node.
Definition: node.cc:35
cerata::Edge::Edge
Edge(std::string name, Node *dst, Node *src)
Construct a new edge.
Definition: edge.cc:29
cerata::signal_array
std::shared_ptr< SignalArray > signal_array(const std::string &name, const std::shared_ptr< Type > &type, const std::shared_ptr< Node > &size, const std::shared_ptr< ClockDomain > &domain)
Construct a new node array and return a shared pointer to it.
Definition: array.cc:198
cerata::Type::GetGenerics
virtual std::vector< Node * > GetGenerics() const
Obtain any nodes that this type uses as generics.
Definition: type.h:126
cerata::signal
std::shared_ptr< Signal > signal(const std::string &name, const std::shared_ptr< Type > &type, const std::shared_ptr< ClockDomain > &domain)
Create a new Signal and return a smart pointer to it.
Definition: signal.cc:36
cerata::Edge::GetOtherNode
std::optional< Node * > GetOtherNode(const Node &node) const
Get the node opposite to the other edge node.
Definition: edge.cc:231
cerata::Type::Copy
virtual std::shared_ptr< Type > Copy(const NodeMap &rebinding) const =0
Make a copy of the type, and rebind any type generic nodes that are keys in the rebinding to their va...
cerata::NodeArray::size
Node * size() const
Return the size node.
Definition: array.h:43
cerata::NodeArray::node
Node * node(size_t i) const
Return element node i.
Definition: array.cc:96
cerata::Object::parent
virtual std::optional< Graph * > parent() const
Return the parent graph of this object, if any. Returns empty option otherwise.
Definition: object.cc:33
cerata::Port
A port is a terminator node on a graph.
Definition: port.h:57
cerata::port
std::shared_ptr< Port > port(const std::string &name, const std::shared_ptr< Type > &type, Term::Dir dir, const std::shared_ptr< ClockDomain > &domain)
Make a new port with some name, type and direction.
Definition: port.cc:22
cerata::Node::type
Type * type() const
Return the node Type.
Definition: node.h:57
cerata::NormalNode::RemoveEdge
bool RemoveEdge(Edge *edge) override
Remove an edge from this node.
Definition: node.cc:223
cerata::ImplicitlyRebindNodes
void ImplicitlyRebindNodes(Graph *dst, const std::vector< Node * > &nodes, NodeMap *rebinding)
Make sure that the NodeMap contains all nodes to be rebound onto the destination graph.
Definition: node.cc:39
cerata::Connect
std::shared_ptr< Edge > Connect(Node *dst, Node *src)
Connect two nodes, returns the corresponding edge.
Definition: edge.cc:68