CGRA-ME
ILPMapper.cpp
Go to the documentation of this file.
1 /*******************************************************************************
2  * The software programs comprising "CGRA-ME" and the documentation provided
3  * with them are copyright by its authors and the University of Toronto. Only
4  * non-commercial, not-for-profit use of this software is permitted without ex-
5  * plicit permission. This software is provided "as is" with no warranties or
6  * guarantees of support. See the LICENCE for more details. You should have re-
7  * ceived a copy of the full licence along with this software. If not, see
8  * <http://cgra-me.ece.utoronto.ca/license/>.
9  ******************************************************************************/
10 
11 #include <CGRA/ILPMapper.h>
12 
13 #include <algorithm>
14 
15 #include <CGRA/CGRA.h>
16 #include <CGRA/ConstraintSet.h>
17 #include <CGRA/Exception.h>
18 #include <CGRA/MRRGProcedures.h>
19 #include <CGRA/OpGraphProcedures.h>
21 
22 #include <gurobi_c++.h>
23 
24 enum class ILPMapper::ConstraintGroup : int {
34  EdgeUsage,
38 };
39 
40 static const std::map<ILPMapper::ConstraintGroup, std::string> lazy_constraint_names {
41  std::make_pair(ILPMapper::ConstraintGroup::RouteExclusivity, "route_exclusivity"),
42  std::make_pair(ILPMapper::ConstraintGroup::FunctionUnitExclusivity, "function_unit_exclusivity"),
43  std::make_pair(ILPMapper::ConstraintGroup::OpsMustBeMapped, "ensure_all_ops_mapped"),
44  std::make_pair(ILPMapper::ConstraintGroup::FanoutRouting, "fanout_routing"),
45  std::make_pair(ILPMapper::ConstraintGroup::MuxExclusivity, "mux_exclusivity"),
46  std::make_pair(ILPMapper::ConstraintGroup::FunctionUnitFanout, "function_unit_fanout"),
47  std::make_pair(ILPMapper::ConstraintGroup::OpSupported, "op_support"),
48  std::make_pair(ILPMapper::ConstraintGroup::LinePathBalance, "line_path_balance"),
49  std::make_pair(ILPMapper::ConstraintGroup::CyclePathBalance, "cycle_path_balance"),
50  std::make_pair(ILPMapper::ConstraintGroup::EdgeUsage, "fake_edge_used"), // "fake" while still using general constraints
51  std::make_pair(ILPMapper::ConstraintGroup::TopologicalOrdering, "fake_ordering"), // "fake" while still using general constraints
52  std::make_pair(ILPMapper::ConstraintGroup::OperandRouting, "ensure that operands are routed correctly"),
53  std::make_pair(ILPMapper::ConstraintGroup::FunctionUnitFanin, "function_unit_fanin"),
54 };
55 
56 std::ostream& operator<<(std::ostream& os, const ILPMapper::ConstraintGroup& lct) {
57  os << lazy_constraint_names.at(lct);
58  return os;
59 }
60 
61 namespace {
62 
63 std::vector<GRBVar> makeGRBVars(GRBModel& model, int count, char type) {
64  std::unique_ptr<GRBVar[]> storage(model.addVars(count, type));
65  return {storage.get(), storage.get() + count};
66 }
67 
68 struct MRRGInfo {
69  auto& nodeClasses() const { return node_classes; }
70  const auto& mrrg() const { return *m_mrrg; }
71 
72  const MRRG* m_mrrg;
73  MRRGNodeClassLists node_classes = computeNodeClassLists(*m_mrrg);
74 };
75 
76 }
77 
79  [](std::shared_ptr<CGRA> cgra, int timelimit, const ConfigStore& args) {
80  return std::make_unique<ILPMapper>(cgra, timelimit, args);
81  },
82  false, // not a composite
83  "Fully encode the mapping problem in an ILP.\n"
84  "Initially presented in 'An Architecture-Agnostic Integer Linear Programming Approach to CGRA Mapping' (DAC 2018)",
85  { // required args
86  {"enable_topological_order_costing_by_max", 0, ""},
87  {"enable_topological_order_costing_by_sum", 0, ""},
88  {"enable_topological_order_mode", 0, ""},
89  {"lazy_constraint_add_mode", 0, ""},
90  {"max_threads", 1, ""},
91  {"mip_gap", 0.2, "Relative threshold on the difference between the primal and dual cost functions to consider a solution optimal"},
92  {"per_value_topological_order_variables", 0, ""},
93  {"seed", 0, ""},
94  {"solution_limit", 1, "Maximum number of solutions that will be considered before giving up and taking the lowest cost one found. Larger values generally lead to more optimal solutions."},
95  {"model_dump_filename", "", ""},
96  {"model_IIS_dump_filename", "", ""},
97  {"verbosity", 0, ""},
98  },
99  { // optional arg regexes
100  {"enable_lazy_constraint_.*", "boolean, controls whether the constraint group that follows is added lazily"},
101  }
102 );
103 
104 ILPMapper::ILPMapper(std::shared_ptr<CGRA> cgra, int timelimit, const ConfigStore& args)
105  : Mapper(cgra, timelimit)
106  , flags(args)
107  , lazy_constraints()
108  , mipgap(args.getReal("mip_gap"))
109  , solnlimit(args.getInt("solution_limit"))
110 {
111  for (auto& type_and_name : lazy_constraint_names) {
112  if (args.getBoolOr("enable_lazy_constraint_" + type_and_name.second, false)) {
113  lazy_constraints.insert(type_and_name.first);
114  }
115  }
116 }
117 
118 // This is the main mapping function
119 // true on success, false on failure
120 Mapping ILPMapper::mapOpGraph(std::shared_ptr<OpGraph> opgraph, int II, const MRRG& mrrg, std::unordered_map<std::string, std::string> fix_port)
121 {
122  if (flags.getInt("verbosity") > 0) {
123  std::cout << "[INFO] Mapping DFG Onto CGRA Architecture..." << std::endl;
124  std::cout << "[INFO] ILP Mapper Config: " << flags << '\n';
125  std::cout << "[INFO] ILP Mapper lazy constraints: "; print_container(std::cout, lazy_constraints); std::cout << '\n';
126  }
127 
128  auto muxExInsertionResult = muxExNodeInsertion(mrrg);
129 
130  verifyAndPrintReport(muxExInsertionResult.transformed_mrrg, std::cout, true, true, {
131  { "check_mux_exclusivity", "error" },
132  });
133 
134  if (flags.getInt("verbosity") > 0) {
135  std::cout << "[INFO] Inserted " << (muxExInsertionResult.transformed_mrrg.size() - mrrg.size())
136  << " nodes for mux exclusivity invariant\n";
137  }
138 
139  // Create result obj
140  Mapping mapping_result(cgra, II, opgraph);
141 
143  try {
144  mapper_status = GurobiMap(*opgraph, II, &mapping_result, muxExInsertionResult.transformed_mrrg, fix_port);
145  } catch (...) {
146  throwExceptionButWrapIfGurobiException(std::current_exception());
147  }
148 
149  switch (mapper_status) {
150  case ILPMapperStatus::OPTIMAL_FOUND: [[fallthrough]];
152  mapping_result.setStatus(MappingStatus::success);
153  break;
155  mapping_result.setStatus(MappingStatus::timeout);
156  break;
158  std::cout << "[ERROR] CGRA Mapping Results in Unlisted Status" << std::endl;
159  std::cout << "[INFO] Please Report this Bug to Xander Chin(xan@ece.utoronto.ca)" << std::endl;
160  [[fallthrough]];
161  case ILPMapperStatus::INTERRUPTED: [[fallthrough]];
162  case ILPMapperStatus::INFEASIBLE: [[fallthrough]];
163  default:
164  mapping_result.setStatus(MappingStatus::failure);
165  break;
166  }
167 
168  return transformToOriginalMRRG(mapping_result, muxExInsertionResult);
169 }
170 
171 ILPMapperStatus ILPMapper::GurobiMap(const OpGraph& opgraph, int II, Mapping* mapping_result, const MRRG& mrrg, std::unordered_map<std::string, std::string> fix_port)
172 {
173  const auto& trails_to_balance = computeTrailsToBalance(opgraph);
174 
175  // Create gurobi instance
176  GRBEnv env = GRBEnv(true);
177  env.set(GRB_IntParam_OutputFlag, flags.getInt("verbosity") > 0);
178  env.start();
179  env.set(GRB_DoubleParam_MIPGap, mipgap);
180  if (timelimit != 0) { env.set(GRB_DoubleParam_TimeLimit, timelimit); }
181  if (solnlimit != 0) { env.set(GRB_IntParam_SolutionLimit, solnlimit); }
182 
183  GRBModel model = GRBModel(env);
184  model.set(GRB_IntParam_Seed, flags.getInt("seed"));
185  model.set(GRB_IntParam_Threads, flags.getInt("max_threads"));
186 
187  MRRGInfo mrrg_info {
188  &mrrg,
189  };
190 
191  const int num_dfg_vals = opgraph.valNodes().size();
192  const int num_dfg_ops = opgraph.opNodes().size();
193  const int num_mrrg_r = mrrg_info.nodeClasses().routing_nodes.size();
194  const int num_mrrg_f = mrrg_info.nodeClasses().function_nodes.size();
195 
196  const auto R = makeGRBVars(model, num_dfg_vals * num_mrrg_r, GRB_BINARY);
197  const auto F = makeGRBVars(model, num_dfg_ops * num_mrrg_f, GRB_BINARY);
198 
199  std::map<OpGraph::ValDescriptor, std::map<MRRG::NodeDescriptor, GRBVar>> R_var_map; // routing node in use
200  std::map<OpGraph::ValDescriptor, std::map<MRRG::NodeDescriptor, std::vector<GRBVar>>> S_var_map; // routing node in use by signal
201  std::map<OpGraph::ValDescriptor, std::map<MRRG::NodeDescriptor, GRBVar>> T_var_map; // ordering values
202  std::map<OpGraph::OpDescriptor, std::map<MRRG::NodeDescriptor, GRBVar>> F_var_map; // function node in use
203 
204  // secondary variables
205  std::map<OpGraph::ValDescriptor, std::map<MRRG::NodeDescriptor, std::map<MRRG::NodeDescriptor, GRBVar>>> edge_used_var_map;
206 
207  // Populate maps
208  int constr_count = 0;
209  int j = 0;
210  for (auto& val : opgraph.valNodes()) {
211  const auto num_val_fanouts = (std::ptrdiff_t)opgraph.outputOps(val).size();
212  int i = 0;
213  for (auto& r : mrrg_info.nodeClasses().routing_nodes) {
214  const auto& node_ref = mrrg.getNodeRef(r);
215  const auto base_name = "R_" + node_ref.getFullName() + "_" + std::to_string(i);
216  R_var_map[val][r] = R[j * num_mrrg_r + i];
217  R_var_map.at(val).at(r).set(GRB_StringAttr_VarName, makeNameSolverSafe(base_name));
218  auto& s_vars_for_val_and_node = S_var_map[val][r];
219  if (num_val_fanouts > 1) {
220  s_vars_for_val_and_node = makeGRBVars(model, num_val_fanouts, GRB_BINARY);
221  for (int k = 0; k < num_val_fanouts; k++) {
222  auto& fanout_s_var = s_vars_for_val_and_node.at(k);
223  fanout_s_var.set(GRB_StringAttr_VarName, makeNameSolverSafe(base_name + "_" + std::to_string(k)));
224  model.addConstr((R_var_map.at(val).at(r)) >= fanout_s_var, "sub_val" + std::to_string(constr_count++));
225  }
226  } else {
227  s_vars_for_val_and_node = {R_var_map.at(val).at(r)};
228  }
229  i++;
230  }
231  j++;
232  }
233 
234  int p = 0;
235  for (auto& op : opgraph.opNodes()) {
236  int q = 0;
237  for (auto& f : mrrg_info.nodeClasses().function_nodes) {
238  F_var_map[op][f] = F[p * num_mrrg_f + q];
239  F_var_map.at(op).at(f).set(GRB_StringAttr_VarName, "F_" + std::to_string(p) + "_" + std::to_string(q));
240  q++;
241  }
242  p++;
243  }
244 
245  // needed for the GRBVar::get calls below
246  model.update();
247 
248  std::vector<GRBVar> all_T_vars;
249  std::map<MRRG::NodeDescriptor, GRBVar> T_var_by_node;
250  for (const auto& val_and_node_to_var_map : R_var_map) {
251  for (const auto& node_and_var : val_and_node_to_var_map.second) {
252  auto& val = val_and_node_to_var_map.first;
253  auto& r = node_and_var.first;
254  const auto& tvar = [&]() {
255  if (not flags.getBool("per_value_topological_order_variables")) {
256  const auto search_result = T_var_by_node.find(r);
257  if (search_result == end(T_var_by_node)) {
258  auto res = std::unique_ptr<GRBVar[]>(model.addVars(1, GRB_INTEGER))[0]; // free the returned pointer immediately (this is the easiest way to make one var?)
259  T_var_by_node.emplace(r, res);
260  res.set(GRB_StringAttr_VarName, "T_" + R_var_map.at(val).at(r).get(GRB_StringAttr_VarName));
261  all_T_vars.emplace_back(res);
262  return res;
263  } else {
264  return search_result->second;
265  }
266  } else {
267  auto res = std::unique_ptr<GRBVar[]>(model.addVars(1, GRB_INTEGER))[0]; // free the returned pointer immediately (this is the easiest way to make one var?)
268  res.set(GRB_StringAttr_VarName, "T_" + R_var_map.at(val).at(r).get(GRB_StringAttr_VarName));
269  all_T_vars.emplace_back(res);
270  return res;
271  }
272  }();
273  T_var_map[val][r] = tvar;
274  }
275  }
276 
277  for (auto& val : opgraph.valNodes()) {
278  for (auto &r: mrrg_info.nodeClasses().routing_nodes) {
279  for (auto &mrrg_fanout : r->fanout) {
280  if (mrrg_fanout->type != MRRG_NODE_ROUTING) {
281  continue;
282  }
283  const auto& rvar = R_var_map.at(val).at(r);
284  const auto& rfanoutvar = R_var_map.at(val).at(mrrg_fanout);
285  const auto RvalString = rvar.get(GRB_StringAttr_VarName) + "_to_" + rfanoutvar.get(GRB_StringAttr_VarName);
286  auto evar = std::unique_ptr<GRBVar[]>(model.addVars(1, GRB_BINARY))[0]; // free the returned pointer immediately (this is the easiest way to make one var?)
287  evar.set(GRB_StringAttr_VarName, "Edge_used_" + RvalString);
288  edge_used_var_map[val][r][mrrg_fanout] = evar;
289  }
290  }
291  }
292 
293  const auto dot_product_of_S_and_F_vars_with_latency_for_opgraph_path = [&](const auto& path) {
294  GRBLinExpr sum;
295  for (const auto& edge : path) {
296  for (const auto& r : mrrg_info.nodeClasses().routing_nodes) {
297  sum += mrrg.getNodeRef(r).getLatency() * S_var_map.at(edge.val).at(r).at(edge.output_index);
298  }
299  for (const auto& f : mrrg_info.nodeClasses().function_nodes) {
300  sum += mrrg.getNodeRef(f).getLatency() * F_var_map.at(opgraph.inputOp(edge.val)).at(f);
301  }
302  }
303  return sum;
304  };
305 
306  // Create and Set objective
307  GRBLinExpr objective;
308  for (const auto& r : R) {
309  objective += r;
310  }
311 
312  if (flags.getBool("enable_topological_order_costing_by_max") && flags.getBool("enable_topological_order_costing_by_sum")) {
313  throw cgrame_mapper_error("can't set both enable_topological_order_costing_by_max and enable_topological_order_costing_by_sum");
314  }
315 
316  if (flags.getBool("enable_topological_order_costing_by_max")) {
317  auto max_tvar = std::unique_ptr<GRBVar[]>(model.addVars(1, GRB_INTEGER))[0];
318  model.addGenConstrMax(max_tvar, all_T_vars.data(), all_T_vars.size(), 0, "max_order_var");
319  objective += max_tvar;
320  }
321 
322  if (flags.getBool("enable_topological_order_costing_by_sum")) {
323  for (const auto& t : all_T_vars) {
324  objective += t;
325  }
326  }
327 
328  model.setObjective(objective, GRB_MINIMIZE);
329 
330  ConstraintSet<ConstraintGroup> constraint_set;
331  constraint_set.setVerbosity(flags.getInt("verbosity"));
332 
333  // Constraint Group - Route Exclusivity
334  constraint_set.registerGenerator(ConstraintGroup::RouteExclusivity, [&](ConstraintGeneratorParams params) {
335  auto result = params.makeReturnValue();
336 
337  for (int i = 0; i < num_mrrg_r; i++) {
338  GRBLinExpr constraint;
339  long num_node_usages = 0;
340 
341  for (int j = 0; j < num_dfg_vals; j++) {
342  auto& r_val = R[j * num_mrrg_r + i];
343  constraint += r_val;
344  num_node_usages += params.inIncrementalMode() ? std::lround(params.callback->getSolution(r_val)) : 0;
345  }
346 
347  if (not params.inIncrementalMode() or not ( num_node_usages <= 1 )) {
348  result.constraints.emplace_back(makeNewStandardConstraintID(i), constraint <= 1);
349  }
350  }
351 
352  return result;
353  });
354 
355  // Constraint Group - Function Unit Exclusivity
356  constraint_set.registerGenerator(ConstraintGroup::FunctionUnitExclusivity, [&](ConstraintGeneratorParams params) {
357  auto result = params.makeReturnValue();
358 
359  for (int p = 0; p < num_mrrg_f; p++) {
360  GRBLinExpr constraint;
361  auto coeffs = std::make_unique<double[]>(num_dfg_ops);
362  auto vars = std::make_unique<GRBVar[]>(num_dfg_ops);
363 
364  for (int q = 0; q < num_dfg_ops; q++) {
365  coeffs[q] = 1.0;
366  vars[q] = F[q * num_mrrg_f + p];
367  }
368 
369  constraint.addTerms(coeffs.get(), vars.get(), num_dfg_ops);
370 
371  result.constraints.emplace_back(makeNewStandardConstraintID(p), constraint <= 1);
372  }
373 
374  return result;
375  });
376 
377  // Constraint Group - All Ops must be mapped
378  constraint_set.registerGenerator(ConstraintGroup::OpsMustBeMapped, [&](ConstraintGeneratorParams params) {
379  auto result = params.makeReturnValue();
380 
381  for (int q = 0; q < num_dfg_ops; q++) {
382  GRBLinExpr constraint;
383  auto coeffs = std::make_unique<double[]>(num_mrrg_f);
384  auto vars = std::make_unique<GRBVar[]>(num_mrrg_f);
385 
386  for (int p = 0; p < num_mrrg_f; p++) {
387  coeffs[p] = 1.0;
388  vars[p] = F[q * num_mrrg_f + p];
389  }
390 
391  constraint.addTerms(coeffs.get(), vars.get(), num_mrrg_f);
392 
393  result.constraints.emplace_back(makeNewStandardConstraintID(q), constraint == 1);
394  }
395 
396  return result;
397  });
398 
399  // Constraint Group - Fanout Routing
400  constraint_set.registerGenerator(ConstraintGroup::FanoutRouting, [&](ConstraintGeneratorParams params) {
401  auto result = params.makeReturnValue();
402 
403  for (auto &val: opgraph.valNodes()) {
404  for (auto &r: mrrg_info.nodeClasses().routing_nodes) {
405  int val_fanouts = val->output.size();
406  for (int i = 0; i < val_fanouts; i++) {
407  GRBLinExpr sum_of_fanouts;
408  long num_fanouts_used = 0.0;
409  for (auto &mrrg_fanout : r->fanout) {
410  if (mrrg_fanout->type == MRRG_NODE_ROUTING) {
411  const auto& s_val = S_var_map.at(val).at(mrrg_fanout).at(i);
412  sum_of_fanouts += s_val;
413  num_fanouts_used += params.inIncrementalMode() ? std::lround(params.callback->getSolution(s_val)) : 0;
414  } else if (mrrg_fanout->type == MRRG_NODE_FUNCTION) {
415  auto &op = val->output[i];
416  for (auto& fanin : mrrg_fanout->fanin) {
417  if (fanin == r) {
418  const auto& f_val = F_var_map.at(op).at(mrrg_fanout);
419  sum_of_fanouts += f_val;
420  num_fanouts_used += params.inIncrementalMode() ? std::lround(params.callback->getSolution(f_val)) : 0;
421  break;
422  }
423  }
424  } else {
425  make_and_throw<cgrame_mapper_error>([&](auto&& s) {
426  s << "unsupported MRRG node type for " << ConstraintGroup::FanoutRouting << " constraints: " << mrrg_fanout->type;
427  });
428  }
429  }
430 
431  const auto& src_s_val = S_var_map.at(val).at(r).at(i);
432  const auto constraint_is_obeyed = [&]() {
433  return num_fanouts_used >= std::lround(params.callback->getSolution(src_s_val));
434  };
435 
436  if (not params.inIncrementalMode() || not constraint_is_obeyed()) {
437  result.constraints.emplace_back(makeNewTupleConstraintID(val, r), sum_of_fanouts >= src_s_val);
438  }
439  }
440  }
441  }
442 
443  return result;
444  });
445 
446  // Constraint Group - Mux Exclusivity
447  constraint_set.registerGenerator(ConstraintGroup::MuxExclusivity, [&](ConstraintGeneratorParams params) {
448  auto result = params.makeReturnValue();
449 
450  for (auto &val: opgraph.valNodes()) {
451  for (auto &r: mrrg_info.nodeClasses().routing_nodes) {
452  GRBLinExpr sum_of_fanins;
453  long num_fanin_used = 0;
454  int fanin_count = r->fanin.size();
455  if (fanin_count > 1) {
456  for (auto &fanin : r->fanin) {
457  if (fanin->type != MRRG_NODE_ROUTING) {
458  throw make_from_stream<cgrame_mapper_error>([&](auto&& s) {
459  s << "node has more than one fanin, and one of these fanin is not a routing node";
460  });
461  }
462 
463  if (fanin->fanout.size() != 1) {
464  throw make_from_stream<cgrame_mapper_error>([&](auto&& s) {
465  s << "Mux Exclusivity Constraint Invariant is violated\n";
466  s << "Candidate MUX node is: " << mrrg.getNodeRef(r) << '\n';
467  for (const auto& r_fanin : mrrg.fanin(r)) {
468  s << mrrg.getNodeRef(r_fanin) << "->" << mrrg.getNodeRef(r) << '\n';
469  }
470 
471  s << "Problem fanin is: " << mrrg.getNodeRef(fanin) << '\n';
472  for (const auto& fanin_fanout : mrrg.fanout(fanin)) {
473  s << mrrg.getNodeRef(fanin) << "->" << mrrg.getNodeRef(fanin_fanout) << '\n';
474  }
475  });
476  }
477 
478  auto& fanin_r_val = R_var_map.at(val).at(fanin);
479  sum_of_fanins += fanin_r_val;
480  num_fanin_used += params.inIncrementalMode() ? std::lround(params.callback->getSolution(fanin_r_val)) : 0.0;
481  }
482 
483  auto& src_r_val = R_var_map.at(val).at(r);
484  const auto& constraint_is_obeyed = [&]() {
485  return num_fanin_used == std::lround(params.callback->getSolution(src_r_val));
486  };
487 
488  if (not params.inIncrementalMode() or not constraint_is_obeyed()) {
489  auto cid = makeNewTupleConstraintID(val, r);
490  result.constraints.emplace_back(std::move(cid), sum_of_fanins == src_r_val);
491  }
492  }
493  }
494  }
495 
496  return result;
497  });
498 
499  // Constraint Group - FU Fanout
500  // XXX: this assumes single output nodes in both OpGraph and MRRG
501  constraint_set.registerGenerator(ConstraintGroup::FunctionUnitFanout, [&](ConstraintGeneratorParams params) {
502  auto result = params.makeReturnValue();
503 
504  for (auto &op: opgraph.opNodes()) {
505  for (auto &f: mrrg_info.nodeClasses().function_nodes) {
506  if (op->output) {
507  OpGraphVal* val = op->output;
508  if (f->fanout.size() != 1) {
509  throw make_from_stream<cgrame_mapper_error>([&](auto&& s) {
510  s << "FU does not have exactly one fanout. " << mrrg.getNodeRef(f).name << " has " << mrrg.fanout(f).size();
511  });
512  }
513  for (auto& r : f->fanout) {
514  int val_fanouts = val->output.size();
515  for (int i = 0; i < val_fanouts; i++) {
516  result.constraints.emplace_back(makeNewTupleConstraintID(op, f), (F_var_map.at(op).at(f)) == S_var_map.at(val).at(r).at(i));
517  }
518  }
519  }
520  }
521  }
522 
523  return result;
524  });
525 
526  // For multi-edges in the opgraph, make sure FU input nodes are used by at most one val output
527  // Only needed for multi-edges.
528  // Electrically, it's okay for S vars to use the same node *if they are the same val*.
529  // However, we need to make sure that a FU receives the right number of inputs!
530  constraint_set.registerGenerator(ConstraintGroup::FunctionUnitFanin, [&](ConstraintGeneratorParams params) {
531  auto result = params.makeReturnValue();
532 
533  for (const auto& val : opgraph.valNodes()) {
534  const auto& val_fanout = opgraph.outputOps(val);
535 
536  // Collect the output indexes that go to each op. Indexes are needed for getting S vars
537  std::map<OpGraph::OpDescriptor, std::vector<int>> outputs_to_op;
538  for (int i = 0; i < val_fanout.size(); ++i) {
539  const auto op = val_fanout.at(i);
540  outputs_to_op[op].push_back(i);
541  }
542 
543  // Make a constraint for each multi-edge for each input of each compatible FU.
544  for (const auto& op_and_output_list : outputs_to_op) {
545  const auto& op = op_and_output_list.first;
546  const auto& output_list = op_and_output_list.second;
547  if (output_list.size() == 1) continue; // the multi-edge check
548 
549  for (const auto& f : mrrg_info.nodeClasses().function_nodes) {
550  if (not mrrg.getNodeRef(f).canMapOp(op)) continue; // the compatible check
551  for (const auto& fanin_mrrg : mrrg.fanin(f)) {
552  // make a constraint from the collected indices
553  GRBLinExpr sum_of_s_at_input;
554  for (const auto i : output_list) {
555  sum_of_s_at_input += S_var_map.at(val).at(fanin_mrrg).at(i);
556  }
557  result.constraints.emplace_back(
558  makeNewTupleConstraintID(op, val, f, fanin_mrrg),
559  sum_of_s_at_input <= 1
560  );
561  }
562  }
563  }
564  }
565 
566  return result;
567  });
568 
569  // Constraint Group - FU supported Op legality
570  constraint_set.registerGenerator(ConstraintGroup::OpSupported, [&](ConstraintGeneratorParams params) {
571  auto result = params.makeReturnValue();
572  bool fix = false;
573  std::string fixNode;
574  //std::cout << "fix_port size:" << fix_port.size() << std::endl;
575  for (auto &op: opgraph.opNodes()) {
576  fix = false;
577  for (auto& node : fix_port) {
578  if (op->getName() == node.first) {
579  fix = true;
580  fixNode = node.second;
581  }
582  }
583  for (auto &f: mrrg_info.nodeClasses().function_nodes) {
584  if (fix) {
585  if(f->getHierarchyQualifiedName().find(fixNode) == fixNode.npos || !f->canMapOp(op)) {
586  // std::cout << op->getName() << " " << fixNode << std::endl;
587  // std::cout << f->getHierarchyQualifiedName() << std::endl;
588  result.constraints.emplace_back(makeNewTupleConstraintID(op, f), (F_var_map.at(op).at(f)) == 0);
589  }
590  } else if (!f->canMapOp(op)) {
591  result.constraints.emplace_back(makeNewTupleConstraintID(op, f), (F_var_map.at(op).at(f)) == 0);
592  }
593  }
594  }
595 
596  /*for (auto &op: opgraph.opNodes()) {
597  std::cout << op->getName() << " ";
598  }
599  std::cout << std::endl;
600  for (auto &f: mrrg_info.nodeClasses().function_nodes) {
601  std::cout << f->getFullName() << " ";
602  }
603  std::cout << std::endl;*/
604  return result;
605  });
606 
607  // Constraint Group - Line Path Balance
608  constraint_set.registerGenerator(ConstraintGroup::LinePathBalance, [&](ConstraintGeneratorParams params) {
609  auto result = params.makeReturnValue();
610 
611  for (const auto& path_pair : trails_to_balance.noncyclic_trail_pairs) {
612  auto sum1 = dot_product_of_S_and_F_vars_with_latency_for_opgraph_path(path_pair.first);
613  auto sum2 = dot_product_of_S_and_F_vars_with_latency_for_opgraph_path(path_pair.second);
614  result.constraints.emplace_back(makeNewTupleConstraintID(&path_pair), sum1 == sum2);
615  }
616 
617  return result;
618  });
619 
620  // Constraint - Cycle Path Balance
621  constraint_set.registerGenerator(ConstraintGroup::CyclePathBalance, [&](ConstraintGeneratorParams params) {
622  auto result = params.makeReturnValue();
623 
624  for (const auto& path : trails_to_balance.cyclic_trails) {
625  auto sum = dot_product_of_S_and_F_vars_with_latency_for_opgraph_path(path);
626  result.constraints.emplace_back(makeNewTupleConstraintID(&path), sum == mrrg.initiationInterval());
627  }
628 
629  return result;
630  });
631 
632  // Constraint Group - keep track of edge usage
633  constraint_set.registerGenerator(ConstraintGroup::EdgeUsage, [&](ConstraintGeneratorParams params) {
634  auto result = params.makeReturnValue();
635 
636  if (params.inIncrementalMode()) {
637  std::cerr << "Cannot add edge use constraints lazily, as they are general constraints" << std::endl;
638  params.callback->abort();
639  }
640 
641  int edge_used_constr_count = 0;
642  for (auto& val : opgraph.valNodes()) {
643  for (auto &r: mrrg_info.nodeClasses().routing_nodes) {
644  for (auto &mrrg_fanout : r->fanout) {
645  if (mrrg_fanout->type != MRRG_NODE_ROUTING) {
646  continue;
647  }
648  const auto& rvar = R_var_map.at(val).at(r);
649  const auto& rfanoutvar = R_var_map.at(val).at(mrrg_fanout);
650  const auto& evar = edge_used_var_map.at(val).at(r).at(mrrg_fanout);
651 
652  // The edge is used if the R values on both sides are used
653  std::array<GRBVar, 2> r_values { rvar, rfanoutvar };
654  model.addGenConstrAnd(
655  evar,
656  r_values.data(), r_values.size(),
657  "edge_used_" + std::to_string(edge_used_constr_count++)
658  );
659  }
660  }
661  }
662 
663  if (flags.getInt("verbosity") > 0) {
664  std::cout << "actually added " << edge_used_constr_count << " edge_used constraints\n";
665  }
666  return result;
667  });
668 
669  // Constraint Group - to enforce the existence of a topological ordering
670  constraint_set.registerGenerator(ConstraintGroup::TopologicalOrdering, [&](ConstraintGeneratorParams params) {
671  auto result = params.makeReturnValue();
672 
673  if (params.inIncrementalMode()) {
674  std::cerr << "Cannot add topological constraints lazily, as they are general constraints" << std::endl;
675  params.callback->abort();
676  }
677 
678  int ordering_constr_count = 0;
679  for (const auto& val_and_node_to_var_map : R_var_map) {
680  for (const auto& node_and_var : val_and_node_to_var_map.second) {
681  auto& r = node_and_var.first;
682  auto& val = val_and_node_to_var_map.first;
683  for (auto& mrrg_fanout : r->fanout) {
684  if (mrrg_fanout->type != MRRG_NODE_ROUTING) {
685  continue;
686  }
687  model.addGenConstrIndicator(
688  edge_used_var_map.at(val).at(r).at(mrrg_fanout), true,
689  T_var_map.at(val).at(mrrg_fanout) - T_var_map.at(val).at(r), GRB_GREATER_EQUAL, 1,
690  "ordering_" + std::to_string(ordering_constr_count++)
691  );
692  }
693  }
694  }
695 
696  if (flags.getInt("verbosity") > 0) {
697  std::cout << "actually added " << ordering_constr_count << " ordering constraints\n";
698  }
699  return result;
700  });
701 
702  // Constraint Group - Operands must be mapped to viable mrrg nodes
703  constraint_set.registerGenerator(ConstraintGroup::OperandRouting, [&](ConstraintGeneratorParams params) {
704  auto result = params.makeReturnValue();
705 
706  for (auto &val: opgraph.valNodes()) {
707  for (auto &r: mrrg_info.nodeClasses().routing_nodes) {
708  if (r->supported_operand_tags.empty()) continue;
709  for (int k = 0; k < (ptrdiff_t)val->output.size(); k++) {
710  if (not nodeIsCompatibleWithOperand(mrrg, r, opgraph.getOperandTag(OpGraph::EdgeDescriptor{val,k}))) {
711  result.constraints.emplace_back(makeNewTupleConstraintID(val, r, k), S_var_map.at(val).at(r).at(k) == 0);
712  }
713  }
714  }
715  }
716  return result;
717  });
718 
719  const auto& all_cgroups = constraint_set.getAllConstraintGroupsRegistered();
720  const auto filtered_cgroups = filter_collection(all_cgroups, [&](auto&& e) {
721  if (flags.getBool("enable_topological_order_mode")) {
722  if (e == ConstraintGroup::MuxExclusivity) {
723  return false;
724  }
725  } else {
727  return false;
728  }
729  }
730  return true;
731  });
732 
733  const auto non_lazy_cgroups = filter_collection(filtered_cgroups, [&](auto&& e) {
734  return lazy_constraints.find(e) == end(lazy_constraints);
735  });
736 
737  const auto lazy_add_mode = [&]() { switch (flags.getInt("lazy_constraint_add_mode")) {
738  default: return ConstraintAddMode::LazyViaCallback;
739  case 1: return ConstraintAddMode::LazyToModel1;
740  case 2: return ConstraintAddMode::LazyToModel2;
741  case 3: return ConstraintAddMode::LazyToModel3;
742  }}();
743 
744  auto constraint_add_conf = ConstraintAddConf::make<ConstraintGroup>({
745  { lazy_add_mode, lazy_constraints },
746  { ConstraintAddMode::NotLazyToModel, non_lazy_cgroups },
747  });
748 
749  if (flags.getInt("verbosity") > 0) {
750  std::cout << "Adding constraints to model...\n";
751  }
752  auto constraint_cache = constraint_set.addConstraintsToModel(ConstraintCache{}, constraint_add_conf, model);
753 
754  auto callback = makeOnMIPSolutionFunctorGRBCallback([&](auto& cb_obj) {
755  if (flags.getInt("verbosity") > 0) {
756  std::cout << "CGRAME_main_ILP_callback is adding constraints...\n";
757  }
758  constraint_cache = constraint_set.addConstraintsViaCallback(std::move(constraint_cache), constraint_add_conf, cb_obj);
759  });
760 
761  if (constraint_add_conf.willRequireLazyModelAttribute()) {
762  model.set(GRB_IntParam_LazyConstraints, 1);
763  }
764  model.update();
765 
766  model.setCallback(&callback);
767 
768  {const auto model_dump_filename = flags.getString("model_dump_filename");
769  if (not model_dump_filename.empty()) {
770  const auto model_dump_filename_with_extension = model_dump_filename
771  + (model_dump_filename.find('.') == model_dump_filename.npos ? ".lp" : "");
772  std::cout << "[INFO] Writing model to " << model_dump_filename_with_extension << std::endl;
773  model.write(model_dump_filename_with_extension);
774  std::cout << "[INFO] Done writing model to " << model_dump_filename_with_extension << std::endl;
775  }}
776 
777  // Optimize model
778  model.optimize();
779 
780  mapping_result->solve_time_in_seconds = model.get(GRB_DoubleAttr_Runtime);
781 
782  int status = model.get(GRB_IntAttr_Status);
783  switch (status) {
784  case GRB_INFEASIBLE:
785  {const auto model_IIS_dump_filename = flags.getString("model_IIS_dump_filename");
786  if (not model_IIS_dump_filename.empty()) {
787  const auto iis_filename_with_extension = model_IIS_dump_filename
788  + (model_IIS_dump_filename.find('.') == model_IIS_dump_filename.npos ? ".ilp" : "");
789  std::cout << "[INFO] Computing IIS, will dump to " << iis_filename_with_extension << std::endl;
790  model.computeIIS();
791  std::cout << "[INFO] Writing IIS to " << iis_filename_with_extension << std::endl;
792  model.write(iis_filename_with_extension);
793  std::cout << "[INFO] Done writing IIS to " << iis_filename_with_extension << std::endl;
794  }}
796  case GRB_TIME_LIMIT: return ILPMapperStatus::TIMEOUT;
797  case GRB_INTERRUPTED: return ILPMapperStatus::INTERRUPTED;
798  case GRB_OPTIMAL: [[fallthrough]];
799  case GRB_SUBOPTIMAL: [[fallthrough]];
800  case GRB_SOLUTION_LIMIT:
801  if (flags.getInt("verbosity") > 0) {
802  std::cout << "FinalILPObjective: " << model.get(GRB_DoubleAttr_ObjVal) << '\n';
803  }
804 
805  for (auto& val : opgraph.valNodes()) {
806  for (auto& r : mrrg_info.nodeClasses().routing_nodes) {
807  if (std::llround(R_var_map.at(val).at(r).get(GRB_DoubleAttr_X))) {
808  mapping_result->mapMRRGNode(val, r);
809  }
810  }
811  }
812 
813  for (auto& op : opgraph.opNodes()) {
814  for (auto& f : mrrg_info.nodeClasses().function_nodes) {
815  if (std::llround(F_var_map.at(op).at(f).get(GRB_DoubleAttr_X))) {
816  mapping_result->mapMRRGNode(op, f);
817  }
818  }
819  }
820 
821  return (status == GRB_OPTIMAL) ? ILPMapperStatus::OPTIMAL_FOUND : ILPMapperStatus::SUBOPTIMAL_FOUND;
822  default:
824  }
825 }
ILPMapper::ConstraintGroup::CyclePathBalance
@ CyclePathBalance
MRRGNode::getFullName
std::string getFullName() const
Definition: MRRG.cpp:569
ILPMapper::ConstraintGroup::FunctionUnitFanin
@ FunctionUnitFanin
OpGraphVal::output
std::vector< OpGraphOp * > output
Definition: OpGraph.h:196
computeTrailsToBalance
TrailsToBalance computeTrailsToBalance(const OpGraph &opgraph)
Definition: OpGraphProcedures.cpp:40
ILPMapper::ConstraintGroup::FunctionUnitFanout
@ FunctionUnitFanout
lazy_constraint_names
static const std::map< ILPMapper::ConstraintGroup, std::string > lazy_constraint_names
Definition: ILPMapper.cpp:40
ILPMapper::ConstraintGroup::FanoutRouting
@ FanoutRouting
verifyAndPrintReport
bool verifyAndPrintReport(const MRRG &mrrg, std::ostream &os, bool silent_on_no_errors, bool throw_if_errors, const ConfigStore &extra_opts)
Definition: MRRG.cpp:473
ILPMapper_arm
AutoRegisterMapper ILPMapper_arm("ILPMapper", [](std::shared_ptr< CGRA > cgra, int timelimit, const ConfigStore &args) { return std::make_unique< ILPMapper >(cgra, timelimit, args);}, false, "Fully encode the mapping problem in an ILP.\n" "Initially presented in 'An Architecture-Agnostic Integer Linear Programming Approach to CGRA Mapping' (DAC 2018)", { {"enable_topological_order_costing_by_max", 0, ""}, {"enable_topological_order_costing_by_sum", 0, ""}, {"enable_topological_order_mode", 0, ""}, {"lazy_constraint_add_mode", 0, ""}, {"max_threads", 1, ""}, {"mip_gap", 0.2, "Relative threshold on the difference between the primal and dual cost functions to consider a solution optimal"}, {"per_value_topological_order_variables", 0, ""}, {"seed", 0, ""}, {"solution_limit", 1, "Maximum number of solutions that will be considered before giving up and taking the lowest cost one found. Larger values generally lead to more optimal solutions."}, {"model_dump_filename", "", ""}, {"model_IIS_dump_filename", "", ""}, {"verbosity", 0, ""}, }, { {"enable_lazy_constraint_.*", "boolean, controls whether the constraint group that follows is added lazily"}, })
ILPMapperStatus::INFEASIBLE
@ INFEASIBLE
MRRG
Definition: MRRG.h:216
ConfigStore::getBoolOr
bool getBoolOr(const std::string &key, bool otherwise) const
Definition: ConfigStore.h:201
OpGraphProcedures.h
ConstraintSet.h
nodeIsCompatibleWithOperand
bool nodeIsCompatibleWithOperand(const MRRG &mrrg, MRRG::NodeDescriptor node, const OperandTag &operand_tag)
Definition: MRRGProcedures.cpp:391
ILPMapper::solnlimit
int solnlimit
Definition: ILPMapper.h:44
OpGraph::valNodes
auto & valNodes() const
Definition: OpGraph.h:314
ILPMapperStatus
ILPMapperStatus
Definition: ILPMapper.h:18
ILPMapper::ConstraintGroup::LinePathBalance
@ LinePathBalance
AutoRegisterMapper
Special helper for registering mappers to the default mapper registry.
Definition: Mapper.h:200
ConfigStore
Definition: ConfigStore.h:76
OpGraph::getOperandTag
const OperandTag & getOperandTag(EdgeDescriptor edge) const
Definition: OpGraph.cpp:509
ILPMapperStatus::INTERRUPTED
@ INTERRUPTED
Mapper
Common interface for mappers.
Definition: Mapper.h:28
OpGraph::inputOp
OpDescriptor inputOp(ValDescriptor val) const
Definition: OpGraph.cpp:504
to_string
const std::string & to_string(const OpGraphOpCode &opcode)
Definition: OpGraph.cpp:111
ILPMapper::GurobiMap
ILPMapperStatus GurobiMap(const OpGraph &opgraph, int II, Mapping *mapping, const MRRG &mrrg, std::unordered_map< std::string, std::string > fix_port)
Definition: ILPMapper.cpp:171
ILPMapperStatus::OPTIMAL_FOUND
@ OPTIMAL_FOUND
makeNameSolverSafe
std::string makeNameSolverSafe(std::string s)
Definition: ConstraintSet.cpp:126
ILPMapper::ConstraintGroup
ConstraintGroup
Definition: ILPMapper.cpp:24
ILPMapper::ILPMapper
ILPMapper(std::shared_ptr< CGRA > cgra, int timelimit, const ConfigStore &args)
Definition: ILPMapper.cpp:104
Mapping
Definition: Mapping.h:31
Exception.h
ILPMapper::mipgap
double mipgap
Definition: ILPMapper.h:43
muxExNodeInsertion
MRRGTransformationResult muxExNodeInsertion(const MRRG &src_mrrg)
Definition: MRRGProcedures.cpp:458
MRRG::fanout
auto & fanout(NodeDescriptor ndesc) const
Definition: MRRG.h:288
MappingStatus::timeout
@ timeout
MRRGNode::canMapOp
bool canMapOp(OpGraphOp const *op) const
Definition: MRRG.cpp:581
OpGraph::outputOps
const std::vector< OpGraphOp * > & outputOps(ValDescriptor op) const
Definition: OpGraph.cpp:488
MRRG::getNodeRef
MRRGNode & getNodeRef(NodeDescriptor ndesc)
Definition: MRRG.h:273
MappingStatus::failure
@ failure
OpGraph::opNodes
auto & opNodes() const
Definition: OpGraph.h:313
MRRG_NODE_FUNCTION
@ MRRG_NODE_FUNCTION
Definition: MRRG.h:46
ILPMapperStatus::UNLISTED_STATUS
@ UNLISTED_STATUS
MRRG::fanin
auto & fanin(NodeDescriptor ndesc) const
Definition: MRRG.h:293
MRRG_NODE_ROUTING
@ MRRG_NODE_ROUTING
Definition: MRRG.h:45
CGRA.h
OpGraphVal
Definition: OpGraph.h:178
transformToOriginalMRRG
Mapping transformToOriginalMRRG(const Mapping &map, const MRRGTransformationResult &transformation)
Definition: MRRGProcedures.cpp:607
Collections.h
end
auto end(const SingleItemImmutableSet< VertexID > &siis)
Definition: Collections.h:138
ILPMapper::flags
ConfigStore flags
Definition: ILPMapper.h:38
ILPMapper::ConstraintGroup::EdgeUsage
@ EdgeUsage
print_container
void print_container(OSTREAM &&os, const CONTAINER &c, const T1 &element_separator, const T2 &container_prefix, const T3 &container_suffix, PRINTER &&element_printer=PRINTER{})
Definition: Collections.h:435
ILPMapper::ConstraintGroup::MuxExclusivity
@ MuxExclusivity
ILPMapperStatus::TIMEOUT
@ TIMEOUT
ILPMapper.h
OpGraph::EdgeDescriptor
Definition: OpGraph.h:224
ILPMapper::ConstraintGroup::OperandRouting
@ OperandRouting
ILPMapper::ConstraintGroup::OpSupported
@ OpSupported
ILPMapper::ConstraintGroup::TopologicalOrdering
@ TopologicalOrdering
MappingStatus::success
@ success
MRRG::initiationInterval
int initiationInterval() const
Definition: MRRG.h:346
ILPMapperStatus::SUBOPTIMAL_FOUND
@ SUBOPTIMAL_FOUND
MRRGProcedures.h
operator<<
std::ostream & operator<<(std::ostream &os, const ILPMapper::ConstraintGroup &lct)
Definition: ILPMapper.cpp:56
MRRGNode::getLatency
auto getLatency() const
Definition: MRRG.h:125
MRRGNodeClassLists
Definition: MRRGProcedures.h:38
ILPMapper::ConstraintGroup::FunctionUnitExclusivity
@ FunctionUnitExclusivity
filter_collection
auto filter_collection(const COLLECTION &base_container, UNARY_PREDICATE &&filter)
Definition: Collections.h:317
ConfigStore::getInt
long long getInt(const std::string &key) const
Definition: ConfigStore.h:146
cgrame_mapper_error
Definition: Exception.h:27
OpGraph
Definition: OpGraph.h:215
ILPMapper::ConstraintGroup::RouteExclusivity
@ RouteExclusivity
ILPMapper::mapOpGraph
Mapping mapOpGraph(std::shared_ptr< OpGraph > opgraph, int II, const MRRG &mrrg, std::unordered_map< std::string, std::string > fix_port) override
Definition: ILPMapper.cpp:120
computeNodeClassLists
MRRGNodeClassLists computeNodeClassLists(const MRRG &mrrg)
Definition: MRRGProcedures.cpp:169
ConfigStore::getBool
bool getBool(const std::string &key) const
Definition: ConfigStore.h:166
Mapper::timelimit
int timelimit
Definition: Mapper.h:46
ILPMapper::lazy_constraints
ConstraintGroupSet lazy_constraints
Definition: ILPMapper.h:39
ILPMapper::ConstraintGroup::OpsMustBeMapped
@ OpsMustBeMapped