Cerata
A library to generate structural hardware designs
architecture.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/vhdl/architecture.h"
16 
17 #include <vector>
18 #include <string>
19 #include <algorithm>
20 #include <memory>
21 
22 #include "cerata/node.h"
23 #include "cerata/edge.h"
24 #include "cerata/expression.h"
25 #include "cerata/graph.h"
26 #include "cerata/vhdl/block.h"
27 #include "cerata/vhdl/declaration.h"
28 #include "cerata/vhdl/instantiation.h"
29 #include "cerata/vhdl/vhdl.h"
30 
31 namespace cerata::vhdl {
32 
34  MultiBlock result(indent);
35  // Component declarations
36  auto inst_comps = comp.GetAllInstanceComponents();
37  for (const auto &ic : inst_comps) {
38  // Check for metadata that this component is not marked primitive
39  // In this case, a library package has been added at the top of the design file.
40  if ((ic->meta().count(meta::PRIMITIVE) == 0) || (ic->meta().at(meta::PRIMITIVE) != "true")) {
41  // TODO(johanpel): generate packages with component declarations
42  auto decl = Decl::Generate(*ic);
43  result << decl;
44  result << Line();
45  }
46  }
47  return result;
48 }
49 
51  MultiBlock result(indent);
52  auto instances = comp.children();
53  for (const auto &i : instances) {
54  auto inst_decl = Inst::Generate(*i);
55  result << inst_decl;
56  result << Line();
57  }
58  return result;
59 }
60 
62  MultiBlock result;
63  result << Line("architecture Implementation of " + comp.name() + " is");
64  result << GenerateCompDeclarations(comp, 1);
65  result << GenerateNodeDeclarations<Signal>(comp, 1);
66  result << GenerateNodeDeclarations<SignalArray>(comp, 1);
67  result << Line("begin");
68  result << GenerateCompInstantiations(comp, 1);
69  result << GenerateAssignments<Port>(comp, 1);
70  result << GenerateAssignments<Signal>(comp, 1);
71  result << GenerateAssignments<SignalArray>(comp, 1);
72  result << Line("end architecture;");
73  return result;
74 }
75 
76 // TODO(johanpel): make this something less arcane
77 static Block GenerateMappingPair(const MappingPair &p,
78  size_t ia,
79  const std::shared_ptr<Node> &offset_a,
80  size_t ib,
81  const std::shared_ptr<Node> &offset_b,
82  const std::string &lh_prefix,
83  const std::string &rh_prefix,
84  bool a_is_array,
85  bool b_is_array) {
86  Block ret;
87 
88  std::shared_ptr<Node> next_offset_a;
89  std::shared_ptr<Node> next_offset_b;
90 
91  auto a_width = p.flat_type_a(ia).type_->width();
92  auto b_width = p.flat_type_b(ib).type_->width();
93 
94  next_offset_a = (offset_a + (b_width ? b_width.value() : rintl(0)));
95  next_offset_b = (offset_b + (a_width ? a_width.value() : rintl(0)));
96 
97  if (p.flat_type_a(0).type_->Is(Type::RECORD)) {
98  // Don't output anything for the nested record type.
99  } else {
100  auto a_ft = p.flat_type_a(ia);
101  auto b_ft = p.flat_type_b(ib);
102 
103  if (a_ft.type_->Is(Type::BIT) && b_ft.type_->Is(Type::VECTOR)) {
104  b_is_array = true;
105  }
106  if (b_ft.type_->Is(Type::BIT) && a_ft.type_->Is(Type::VECTOR)) {
107  a_is_array = true;
108  }
109  std::string a;
110  std::string b;
111 
112  a = a_ft.name(NamePart(lh_prefix, true));
113  // if right side is concatenated onto the left side
114  // or the left side is an array (right is also concatenated onto the left side)
115  if ((p.num_b() > 1) || a_is_array) {
116  if (a_ft.type_->Is(Type::BIT) || (b_ft.type_->Is(Type::BIT) && a_ft.type_->Is(Type::VECTOR))) {
117  a += "(" + offset_a->ToString() + ")";
118  } else {
119  a += "(" + (next_offset_a - 1ul)->ToString();
120  a += " downto " + offset_a->ToString() + ")";
121  }
122  }
123  b = b_ft.name(NamePart(rh_prefix, true));
124  if ((p.num_a() > 1) || b_is_array) {
125  if (b_ft.type_->Is(Type::BIT) || (a_ft.type_->Is(Type::BIT) && b_ft.type_->Is(Type::VECTOR))) {
126  b += "(" + offset_b->ToString() + ")";
127  } else {
128  b += "(" + (next_offset_b - 1ul)->ToString();
129  b += " downto " + offset_b->ToString() + ")";
130  }
131  }
132  Line l;
133  if (p.flat_type_a(ia).reverse_) {
134  l << b << " <= " << a;
135  } else {
136  l << a << " <= " << b;
137  }
138  ret << l;
139  }
140 
141  return ret;
142 }
143 
144 static Block GenerateAssignmentPair(std::vector<MappingPair> pairs, const Node &a, const Node &b) {
145  Block ret;
146  // Sort the pair in order of appearance on the flatmap
147  std::sort(pairs.begin(), pairs.end(), [](const MappingPair &x, const MappingPair &y) -> bool {
148  return x.index_a(0) < y.index_a(0);
149  });
150  bool a_array = false;
151  bool b_array = false;
152  size_t a_idx = 0;
153  size_t b_idx = 0;
154  // Figure out if these nodes are on NodeArrays and what their index is
155  if (a.array()) {
156  a_array = true;
157  a_idx = a.array().value()->IndexOf(a);
158  }
159  if (b.array()) {
160  b_array = true;
161  b_idx = b.array().value()->IndexOf(b);
162  }
163  // Loop over all pairs
164  for (const auto &pair : pairs) {
165  // Offset on the right side
166  std::shared_ptr<Node> b_offset = pair.width_a(intl(1)) * b_idx;
167  // Loop over everything on the left side
168  for (int64_t ia = 0; ia < pair.num_a(); ia++) {
169  // Get the width of the left side.
170  auto a_width = pair.flat_type_a(ia).type_->width();
171  // Offset on the left side.
172  std::shared_ptr<Node> a_offset = pair.width_b(intl(1)) * a_idx;
173  for (int64_t ib = 0; ib < pair.num_b(); ib++) {
174  // Get the width of the right side.
175  auto b_width = pair.flat_type_b(ib).type_->width();
176  // Generate the mapping pair with given offsets
177  auto mpblock = GenerateMappingPair(pair, ia, a_offset, ib, b_offset, a.name(), b.name(), a_array, b_array);
178  ret << mpblock;
179  // Increase the offset on the left side.
180  a_offset = a_offset + (b_width ? b_width.value() : rintl(1));
181  }
182  // Increase the offset on the right side.
183  b_offset = b_offset + (a_width ? a_width.value() : rintl(1));
184  }
185  }
186  return ret;
187 }
188 
189 static Block GenerateNodeAssignment(const Node &dst, const Node &src) {
190  Block result;
191  // Check if a type mapping exists
192  auto optional_type_mapper = dst.type()->GetMapper(src.type());
193  if (optional_type_mapper) {
194  auto type_mapper = optional_type_mapper.value();
195  // Obtain the unique mapping pairs for this mapping
196  auto pairs = type_mapper->GetUniqueMappingPairs();
197  // Generate the mapping for this port-node pair.
198  result << GenerateAssignmentPair(pairs, dst, src);
199  result << ";";
200  } else {
201  CERATA_LOG(FATAL, "No type mapping available for: Node[" + dst.name() + ": " + dst.type()->name()
202  + "] from Other[" + src.name() + " : " + src.type()->name() + "]");
203  }
204  return result;
205 }
206 
207 Block Arch::Generate(const Port &port, int indent) {
208  Block ret(indent);
209  // We need to assign this port. If we properly resolved VHDL issues, and didn't do weird stuff like loop a
210  // port back up, the only thing that could drive this port is a signal. We're going to sanity check this anyway.
211 
212  // Check if the port node is sourced at all. If it isn't, we don't have to assign it.
213  if (!port.input()) {
214  return ret;
215  }
216  // Generate the assignment.
217  auto edge = port.input().value();
218  if (!edge->src()->IsSignal()) {
219  CERATA_LOG(FATAL, "Component port is not sourced by signal.");
220  }
221  ret << GenerateNodeAssignment(*edge->dst(), *edge->src());
222 
223  return ret;
224 }
225 
226 Block Arch::Generate(const Signal &sig, int indent) {
227  Block ret(indent);
228  // We need to assign this signal when it is sourced by a node that is not an instance port.
229  // If the source is, then this is done in the associativity list of the port map of the component instantiation.
230 
231  // Check if the signal node is sourced at all.
232  if (!sig.input()) {
233  return ret;
234  }
235  auto edge = sig.input().value();
236  Block b;
237  Node *src = edge->src();
238  Node *dst = edge->dst();
239  // Check if the source is a port, has a parent and if its parent is an instance.
240  // In that case, the source is coming from an instance, and we don't have to make the assignment since that is done
241  // at the instance port map.
242  if (src->IsPort() && src->parent() && src->parent().value()->IsInstance()) {
243  return ret;
244  }
245  // Check if a type mapping exists
246  auto type_mapper = dst->type()->GetMapper(src->type());
247  if (type_mapper) {
248  // Obtain the unique mapping pairs for this mapping
249  auto pairs = type_mapper.value()->GetUniqueMappingPairs();
250  // Generate the mapping for this port-node pair.
251  b << GenerateAssignmentPair(pairs, *dst, *src);
252  b << ";";
253  ret << b;
254  } else {
255  CERATA_LOG(FATAL, "Assignment of signal " + src->ToString()
256  + " from " + dst->ToString()
257  + " failed. No type mapper available.");
258  }
259  return ret;
260 }
261 
262 Block Arch::Generate(const SignalArray &sig_array, int indent) {
263  Block ret(indent);
264  // Go over each node in the array
265  for (const auto &node : sig_array.nodes()) {
266  if (node->IsSignal()) {
267  const auto &sig = dynamic_cast<const Signal &>(*node);
268  ret << Generate(sig, indent);
269  } else {
270  CERATA_LOG(FATAL, "Signal Array contains non-signal node.");
271  }
272  }
273  return ret.Sort('(');
274 }
275 
276 } // namespace cerata::vhdl
cerata::Component::GetAllInstanceComponents
virtual std::vector< const Component * > GetAllInstanceComponents() const
Returns all unique Components that are referred to by child Instances of this graph.
Definition: graph.cc:268
cerata::Type::RECORD
@ RECORD
? | ? | Yes
Definition: type.h:77
cerata::Component
A Component graph.
Definition: graph.h:158
cerata::NodeArray::nodes
std::vector< Node * > nodes() const
Return all nodes of this NodeArray.
Definition: array.h:61
cerata::SignalArray
An array of signal nodes.
Definition: array.h:100
cerata::vhdl::MultiBlock
A structure to hold multiple blocks.
Definition: block.h:77
cerata::MappingPair::flat_type_a
FlatType flat_type_a(int64_t i) const
Return the i-th FlatType on the "a"-side in the mapping matrix.
Definition: flattype.h:299
cerata::FlatType::type_
Type * type_
A pointer to the original type.
Definition: flattype.h:56
cerata::Type::BIT
@ BIT
Yes | No | No.
Definition: type.h:70
cerata::Signal
A Signal Node.
Definition: signal.h:30
cerata::vhdl::Line
A line of code.
Definition: block.h:25
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::Type::Is
bool Is(ID type_id) const
Return true if the Type ID is type_id, false otherwise.
Definition: type.cc:28
cerata::Component::children
std::vector< Instance * > children() const
Returns all Instance graphs from this Component.
Definition: graph.h:187
cerata::vhdl::Decl::Generate
static Block Generate(const Parameter &par, int depth=0)
Generate a parameter declaration as VHDL generic.
Definition: declaration.cc:72
cerata::vhdl::Inst::Generate
static MultiBlock Generate(const Graph &graph)
Generate a VHDL instantiation of a graph.
Definition: instantiation.cc:246
cerata::Named::name
std::string name() const
Return the name of the object.
Definition: utils.h:45
cerata::intl
std::shared_ptr< Literal > intl(int64_t i)
Obtain a shared pointer to an integer literal from the default node pool.
Definition: pool.h:144
cerata::FlatType::reverse_
bool reverse_
Whether to invert this flattened type if it would be on a terminator node.
Definition: flattype.h:62
cerata::MappingPair::num_b
int64_t num_b() const
Return the number of FlatTypes on the "b"-side.
Definition: flattype.h:289
cerata::MappingPair::flat_type_b
FlatType flat_type_b(int64_t i) const
Return the i-th FlatType on the "b"-side in the mapping matrix.
Definition: flattype.h:301
cerata::Type::width
virtual std::optional< Node * > width() const
Return the width of the type, if it is synthesizable.
Definition: type.h:106
cerata::FlatType::name
std::string name(const NamePart &root=NamePart(), const std::string &sep="_") const
Return the name of this flattened type, constructed from the name parts.
Definition: flattype.cc:32
cerata::vhdl::ToString
std::string ToString(const std::vector< Block > &blocks)
Return a vector of blocks as a single string.
Definition: block.cc:186
cerata::rintl
Literal * rintl(int64_t i)
Obtain a raw pointer to an integer literal from the default node pool.
Definition: pool.h:140
cerata::vhdl::Block
A block of code.
Definition: block.h:50
cerata::vhdl::Block::Sort
Block & Sort(std::optional< char > c=std::nullopt)
Sort the lines in the block. Supply a character to stop sorting per line after encountering the chara...
Definition: block.cc:85
cerata::MappingPair
A structure representing a mapping pair for a type mapping.
Definition: flattype.h:276
cerata::vhdl::Arch::GenerateCompDeclarations
static MultiBlock GenerateCompDeclarations(const Component &comp, int indent=0)
Generate component declarations within VHDL architecture declarations block.
Definition: architecture.cc:33
cerata::Node::ToString
virtual std::string ToString() const
Return a human-readable string of this node.
Definition: node.cc:35
cerata::vhdl::Arch::Generate
static MultiBlock Generate(const Component &comp)
Generate the VHDL architecture of a component.
Definition: architecture.cc:61
cerata::vhdl::Arch::GenerateCompInstantiations
static MultiBlock GenerateCompInstantiations(const Component &comp, int indent=0)
Generate component instantiations within VHDL architecture concurrent statements block.
Definition: architecture.cc:50
cerata::NormalNode::input
std::optional< Edge * > input() const
Return the single incoming edge.
Definition: node.cc:190
cerata::Type::VECTOR
@ VECTOR
Yes | Yes | No.
Definition: type.h:71
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::vhdl
Contains everything related to the VHDL back-end.
Definition: architecture.cc:31
cerata::MappingPair::num_a
int64_t num_a() const
Return the number of FlatTypes on the "a"-side.
Definition: flattype.h:287