Fletchgen
The Fletcher Design Generator
profiler.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 "fletchgen/profiler.h"
16 
17 #include <cerata/api.h>
18 #include <cerata/vhdl/vhdl.h>
19 #include <cmath>
20 #include <memory>
21 #include <vector>
22 #include <tuple>
23 
24 #include "fletchgen/basic_types.h"
25 #include "fletchgen/nucleus.h"
26 
27 namespace fletchgen {
28 
29 using cerata::component;
30 using cerata::parameter;
31 using cerata::port;
32 using cerata::stream;
33 using cerata::vector;
34 using cerata::record;
35 using cerata::field;
36 using cerata::integer;
37 using cerata::bit;
38 
39 static constexpr uint32_t COUNT_WIDTH = 32;
40 
41 // Vhdmmio documentation strings for profiling:
42 namespace doc {
43 static constexpr char e[] = "Element count. Accumulates the number of elements transferred on the stream. "
44  "Writing to the register subtracts the written value.";
45 static constexpr char v[] = "Valid count. Increments each cycle that the stream is valid. "
46  "Writing to the register subtracts the written value.";
47 static constexpr char r[] = "Ready count. Increments each cycle that the stream is ready. "
48  "Writing to the register subtracts the written value.";
49 static constexpr char t[] = "Transfer count. "
50  "Increments for each transfer on the stream, i.e. when it is handshaked. "
51  "Writing to the register subtracts the written value.";
52 static constexpr char p[] = "Packet count. Increments each time the last signal is set during a handshake "
53  "Writing to the register subtracts the written value.";
54 static constexpr char c[] = "Cycle count. Increments each clock cycle while profiler is enabled.";
55 } // namespace doc
56 
57 namespace name {
58 static constexpr char e[] = "elements";
59 static constexpr char v[] = "valids";
60 static constexpr char r[] = "readies";
61 static constexpr char t[] = "transfers";
62 static constexpr char p[] = "packets";
63 static constexpr char c[] = "cycles";
64 } // namespace name
65 
66 std::vector<MmioReg> GetProfilingRegs(const std::vector<std::shared_ptr<RecordBatch>> &recordbatches) {
67  std::vector<MmioReg> profile_regs;
68  using MF = MmioFunction;
69  using MB = MmioBehavior;
70 
71  profile_regs.emplace_back(MF::PROFILE,
72  MB::CONTROL,
73  "Profile_enable",
74  "Activates profiler counting when this bit is high.",
75  1);
76 
77  profile_regs.emplace_back(MF::PROFILE,
78  MB::STROBE,
79  "Profile_clear",
80  "Resets profiler counters when this bit is asserted.",
81  1);
82 
83  for (const auto &rb : recordbatches) {
84  auto fps = rb->GetFieldPorts();
85  for (const auto &fp : fps) {
86  // Check if we should profile the field-derived port node.
87  if (fp->profile_) {
88  auto flattened = cerata::Flatten(fp->type());
89  int si = 0; // stream index
90  for (auto &fti : flattened) {
91  if (dynamic_cast<cerata::Stream *>(fti.type_) != nullptr) {
92  const auto pre = "Profile_" + fti.name(cerata::NamePart(fp->name())); // prefix
93  const auto sis = "_" + std::to_string(si) + "_"; // stream index string
94  MmioReg e(MF::PROFILE, MB::STATUS, pre + sis + name::e, doc::e, COUNT_WIDTH);
95  MmioReg v(MF::PROFILE, MB::STATUS, pre + sis + name::v, doc::v, COUNT_WIDTH);
96  MmioReg r(MF::PROFILE, MB::STATUS, pre + sis + name::r, doc::r, COUNT_WIDTH);
97  MmioReg t(MF::PROFILE, MB::STATUS, pre + sis + name::t, doc::t, COUNT_WIDTH);
98  MmioReg p(MF::PROFILE, MB::STATUS, pre + sis + name::p, doc::p, COUNT_WIDTH);
99  MmioReg c(MF::PROFILE, MB::STATUS, pre + sis + name::c, doc::c, COUNT_WIDTH);
100  profile_regs.insert(profile_regs.end(), {e, v, r, t, p, c});
101  si++;
102  }
103  }
104  }
105  }
106  }
107  return profile_regs;
108 }
109 
110 std::shared_ptr<cerata::Type> stream_probe(const std::shared_ptr<Node> &count_width) {
111  // We require a probe stream where the valid and ready are control fields that travel in the same direction.
112  // flat type indices:
113  auto result = stream("probe", // 0
114  "count", vector(count_width), // 4
115  {field(cerata::Stream::valid()), // 1
116  field(cerata::Stream::ready()), // 2
117  field(last())}); // 3
118  return result;
119 }
120 
121 static Component *profiler() {
122  // Check if the Array component was already created.
123  auto opt_comp = cerata::default_component_pool()->Get("Profiler");
124  if (opt_comp) {
125  return *opt_comp;
126  }
127 
128  // Parameters
129  auto icw = parameter("PROBE_COUNT_WIDTH", integer(), cerata::intl(1));
130  auto ocw = parameter("OUT_COUNT_WIDTH", integer(), cerata::intl(32));
131  auto oct = vector("out_count_type", ocw);
132 
133  auto pcr = port("pcd", cr(), Port::Dir::IN);
134  auto probe = port("probe", stream_probe(icw), Port::Dir::IN);
135  auto enable = port("enable", bit(), Port::Dir::IN);
136  auto clear = port("clear", bit(), Port::Dir::IN);
137  auto e = port(std::string("count_") + name::e, oct, Port::Dir::OUT);
138  auto v = port(std::string("count_") + name::v, oct, Port::Dir::OUT);
139  auto r = port(std::string("count_") + name::r, oct, Port::Dir::OUT);
140  auto t = port(std::string("count_") + name::t, oct, Port::Dir::OUT);
141  auto p = port(std::string("count_") + name::p, oct, Port::Dir::OUT);
142  auto c = port(std::string("count_") + name::c, oct, Port::Dir::OUT);
143 
144  // Component & ports
145  auto ret = component("Profiler", {icw, ocw, pcr, probe, enable, clear, e, v, r, t, p, c});
146 
147  // VHDL metadata
148  ret->SetMeta(cerata::vhdl::meta::PRIMITIVE, "true");
149  ret->SetMeta(cerata::vhdl::meta::LIBRARY, "work");
150  ret->SetMeta(cerata::vhdl::meta::PACKAGE, "Profile_pkg");
151 
152  return ret.get();
153 }
154 
155 NodeProfilerPorts EnableStreamProfiling(cerata::Component *comp,
156  const std::vector<cerata::Signal *> &profile_nodes) {
157  cerata::NodeMap rebinding;
158  NodeProfilerPorts result;
159  // Get all nodes and check if their type contains a stream, then check if they should be profiled.
160  for (auto node : profile_nodes) {
161  // Flatten the type
162  auto flat_types = Flatten(node->type());
163  int s = 0;
164  // Iterate over all flattened types. If we encounter a stream, we must profile it.
165  size_t fti = 0;
166  while (fti < flat_types.size()) {
167  if (flat_types[fti].type_->Is(Type::RECORD)) {
168  FLETCHER_LOG(DEBUG, "Inserting profiler for stream node " + node->name()
169  + ", sub-stream " + std::to_string(s)
170  + " of flattened type " + node->type()->name()
171  + " index " + std::to_string(fti) + ".");
172  // Signal must have domain, so we should be able to immediately get optional value.
173  auto domain = *GetDomain(*node);
174  auto cr_node = GetClockResetPort(comp, *domain);
175  if (!cr_node) {
176  throw std::runtime_error("No clock/reset port present on component [" + comp->name()
177  + "] for clock domain [" + domain->name()
178  + "] of stream node [" + node->name() + "].");
179  }
180 
181  // Instantiate a profiler.
182  std::string name = flat_types[fti].name(cerata::NamePart(node->name(), true));
183  auto profiler_inst = comp->Instantiate(profiler(), profiler()->name() + "_" + name + "_inst");
184  // Set the domain of all ports.
185  for (auto &p : profiler_inst->GetAll<Port>()) {
186  p->SetDomain(domain);
187  }
188 
189  // Obtain profiler ports.
190  auto p_probe = profiler_inst->prt("probe");
191  auto p_cr = profiler_inst->prt("pcd");
192  auto p_in_count_width = profiler_inst->par("PROBE_COUNT_WIDTH");
193 
194  // Set up a type mapper.
195  auto mapper = TypeMapper::Make(node->type(), p_probe->type());
196  auto matrix = mapper->map_matrix().Empty();
197  matrix(fti, 0) = 1; // Connect the stream record.
198  matrix(++fti, 1) = 1; // Connect the stream valid.
199  matrix(++fti, 2) = 1; // Connect the stream ready.
200 
201  // Now we need to find field marked with "count" metadata for EPC streams, if it exists.
202  // Increase the flat type field index, until we hit the next stream, or we see a field with metadata meant for
203  // this function.
204 
205  // Go the next flat type index.
206  // If there is any flat types left, figure out where the count and last fields are.
207  fti++;
208  while (fti < flat_types.size()) {
209  auto ft = flat_types[fti];
210  if (ft.type_->meta.count(meta::COUNT) > 0) {
211  auto width = std::strtol(flat_types[fti].type_->meta.at(meta::COUNT).c_str(), nullptr, 10);
212  p_in_count_width <<= intl(static_cast<int>(width));
213  // We've found the count field.
214  matrix(fti, 4) = 1; // Connect the count.
215  }
216  if (ft.type_->meta.count(meta::LAST) > 0) {
217  matrix(fti, 3) = 1; // Connect the last bit.
218  }
219  fti++;
220  }
221  // Set the mapping matrix of the new mapper, and add it to the probe.
222  mapper->SetMappingMatrix(matrix);
223  node->type()->AddMapper(mapper);
224 
225  // Connect the clock/reset, probe and profile output.
226  Connect(p_cr, *cr_node);
227  Connect(p_probe, node);
228 
229  // Create an entry in the map.
230  auto new_ports = std::vector<Port *>({profiler_inst->prt(std::string("count_") + name::e),
231  profiler_inst->prt(std::string("count_") + name::v),
232  profiler_inst->prt(std::string("count_") + name::r),
233  profiler_inst->prt(std::string("count_") + name::t),
234  profiler_inst->prt(std::string("count_") + name::p),
235  profiler_inst->prt(std::string("count_") + name::c)});
236 
237  if (result.count(node) == 0) {
238  // We need to create a new entry.
239  result[node] = {{profiler_inst}, new_ports};
240  } else {
241  // Insert the ports into the old entry.
242  result[node].first.push_back(profiler_inst);
243  auto vec = result[node].second;
244  vec.insert(vec.end(), new_ports.begin(), new_ports.end());
245  }
246  // Increase the s-th stream index in the flattened type.
247  s++;
248  } else {
249  // Not a stream, just continue.
250  fti++;
251  }
252  }
253  }
254  return result;
255 }
256 
257 } // namespace fletchgen
constexpr char LAST[]
Key to mark the last field in Arrow data streams.
Definition: basic_types.h:32
constexpr char COUNT[]
Key to mark the count field in Arrow data streams.
Definition: basic_types.h:30
Contains all classes and functions related to Fletchgen.
Definition: array.cc:29
std::shared_ptr< Type > cr()
Fletcher clock/reset;.
Definition: basic_types.cc:73
MmioBehavior
Register access behavior enumeration.
Definition: mmio.h:61
std::map< Node *, std::pair< std::vector< Instance * >, std::vector< Port * > >> NodeProfilerPorts
A mapping from nodes to profiler instances and ports.
Definition: profiler.h:30
MmioFunction
Register intended use enumeration.
Definition: mmio.h:52
std::shared_ptr< cerata::Type > stream_probe(const std::shared_ptr< Node > &count_width)
Returns a stream probe type based on a count width for multi-epc streams.
Definition: profiler.cc:110
std::shared_ptr< Type > last(int width, bool on_primitive)
Fletcher last.
Definition: basic_types.cc:129
NodeProfilerPorts EnableStreamProfiling(cerata::Component *comp, const std::vector< cerata::Signal * > &profile_nodes)
Transforms a Cerata component graph to include stream profilers for selected nodes.
Definition: profiler.cc:155
std::vector< MmioReg > GetProfilingRegs(const std::vector< std::shared_ptr< RecordBatch >> &recordbatches)
Obtain the registers that should be reserved in the mmio component for profiling.
Definition: profiler.cc:66
std::optional< cerata::Port * > GetClockResetPort(cerata::Graph *graph, const ClockDomain &domain)
Return the clock/reset port of a graph for a specific clock domain, if it exists.
Definition: basic_types.cc:181
Structure to represent an MMIO register.
Definition: mmio.h:68