CGRA-ME
MRRGProcedures_tests.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/Module.h>
12 #include <CGRA/MRRGProcedures.h>
13 
14 #include <catch2/catch.hpp>
15 
16 namespace {
17 
18 Module god_module("m", "", {0,0}, 32); // used for some MRRG nodes created in this file
19 
20 MRRG makeMRRG_4nodeLinear(int II, int latency_of_a, int latency_of_c, ConfigStore options = {}) {
21  const int cycle_after_a = (0 + latency_of_a) % II;
22  const int cycle_after_c = (cycle_after_a + latency_of_c) % II;
23 
24  // add operand tags given in the form of "operand-a1" etc.
25  std::map<std::string,MRRGNode::SupportedOpTags> operand_tags;
26  for (auto& n : {"a", "b", "c", "d"}) {
27  auto& tags_for_n = operand_tags[n]; // creates
28  for (auto& i : {"1", "2", "3"}) {
29  const auto& key = std::string("operand-") + n + i;
30  if (options.hasKey(key)) {
31  tags_for_n.insert(options.getString(key));
32  }
33  }
34  }
35 
36  MRRG mrrg(II);
37  const auto a = mrrg.insert( MRRGNode::make_operand_pin(&god_module, 32, 0, "a", operand_tags.at("a"), latency_of_a)).first;
38  const auto b = mrrg.insert(a, MRRGNode::make_operand_pin(&god_module, 32, cycle_after_a, "b", operand_tags.at("b"), 0 )).first;
39  const auto c = mrrg.insert(b, MRRGNode::make_operand_pin(&god_module, 32, cycle_after_a, "c", operand_tags.at("c"), latency_of_c)).first;
40  const auto d = mrrg.insert(c, MRRGNode::make_operand_pin(&god_module, 32, cycle_after_c, "d", operand_tags.at("d"), 0 )).first;
41  (void)d;
42 
43  verifyAndPrintReport(mrrg, std::cout, true, true);
44  return mrrg;
45 }
46 
47 MRRG makeMRRG_2nodeFUAndReg(int II, int latency_of_fu, int latency_of_reg) {
48  MRRG mrrg(II);
49  const int cycle_after_fu = (0 + latency_of_fu) % II;
50 
51  const auto fu = mrrg.insert( MRRGNode::make_function(nullptr, 32, 0, "fu", latency_of_fu, {OpCode::ADD})).first;
52  const auto reg = mrrg.insert(fu, MRRGNode::make_routing( nullptr, 32, cycle_after_fu, "reg", latency_of_reg)).first;
53 
54  mrrg.link(reg, fu);
55 
56  verifyAndPrintReport(mrrg, std::cout, true, true);
57  return mrrg;
58 }
59 
60 MRRG makeMRRG_2112DAG(int II, int latency_of_a, int latency_of_c) {
61  const int cycle_after_a = (0 + latency_of_a) % II;
62  const int cycle_after_c = (cycle_after_a + latency_of_c) % II;
63  MRRG mrrg(II);
64  const auto a1 = mrrg.insert( MRRGNode::make_routing(nullptr, 32, 0, "a1", latency_of_a)).first;
65  const auto a2 = mrrg.insert( MRRGNode::make_routing(nullptr, 32, 0, "a2", latency_of_a)).first;
66  const auto b = mrrg.insertMultiFanin({a1,a2}, MRRGNode::make_routing(nullptr, 32, cycle_after_a, "b", 0)).first;
67  const auto c = mrrg.insert(b, MRRGNode::make_routing(nullptr, 32, cycle_after_a, "c", latency_of_c)).first;
68  const auto d1 = mrrg.insert(c, MRRGNode::make_routing(nullptr, 32, cycle_after_c, "d1", 0 )).first;
69  const auto d2 = mrrg.insert(c, MRRGNode::make_routing(nullptr, 32, cycle_after_c, "d2", 0 )).first;
70  (void)d1;
71  (void)d2;
72 
73  verifyAndPrintReport(mrrg, std::cout, true, true);
74  return mrrg;
75 }
76 
77 MRRG makeMRRG_112221DAG(int II, int latency_of_a, int latency_of_c) {
78  const int cycle_after_a = (0 + latency_of_a) % II;
79  const int cycle_after_c = (cycle_after_a + latency_of_c) % II;
80  MRRG mrrg(II);
81  const auto a = mrrg.insert( MRRGNode::make_routing(nullptr, 32, 0, "a", latency_of_a)).first;
82  const auto b = mrrg.insert(a, MRRGNode::make_routing(nullptr, 32, cycle_after_a, "b", 0 )).first;
83  const auto c1 = mrrg.insert(b, MRRGNode::make_routing(nullptr, 32, cycle_after_a, "c1", latency_of_c)).first;
84  const auto c2 = mrrg.insert(b, MRRGNode::make_routing(nullptr, 32, cycle_after_a, "c2", latency_of_c)).first;
85  const auto d1 = mrrg.insert(c1, MRRGNode::make_routing(nullptr, 32, cycle_after_c, "d1", 0 )).first;
86  const auto d2 = mrrg.insert(c2, MRRGNode::make_routing(nullptr, 32, cycle_after_c, "d2", 0 )).first;
87  const auto e1 = mrrg.insert(d1, MRRGNode::make_routing(nullptr, 32, cycle_after_c, "e1", 0 )).first;
88  const auto e2 = mrrg.insert(d2, MRRGNode::make_routing(nullptr, 32, cycle_after_c, "e2", 0 )).first;
89  const auto f = mrrg.insertMultiFanin({e1,e2}, MRRGNode::make_routing(nullptr, 32, latency_of_c, "f", 0)).first;
90  (void)f;
91 
92  verifyAndPrintReport(mrrg, std::cout, true, true);
93  return mrrg;
94 }
95 
96 } // end anon namespace
97 
98 TEST_CASE("tripCountOfWalk Tests", "") {
99  const auto II = GENERATE(1,2,3);
100  const auto latency_of_a = GENERATE(0,1,2,3,5);
101  const auto latency_of_c = GENERATE(0,1,2,3,5);
102  GIVEN("a 4 node linear II == " << II << " MRRG with latency " << latency_of_a << " on node a and latency of " << latency_of_c << " for node c") {
103  const auto& mrrg = makeMRRG_4nodeLinear(II, latency_of_a, latency_of_c);
104  const auto node_a = mrrg.getNode(0, "a");
105  const auto node_b = *mrrg.fanout(node_a).begin();
106  const auto node_c = mrrg.getNode(mrrg.getNodeRef(node_b).getContextNum(), "c");
107  const auto walk = std::vector<MRRG::NodeDescriptor>{node_a, node_b, node_c, *mrrg.fanout(node_c).begin()};
108  const auto total_trip_count = (latency_of_c + latency_of_a)/II;
109  auto walk_len = GENERATE_REF(range(2, (int)walk.size()-1+1)); // can't have segments smaller than 2
110  GIVEN("a walk that we will spit after " << walk_len << " nodes") {
111  const auto expected_trip_count = (walk_len >= 4 ? total_trip_count : latency_of_a/II);
112  THEN("the first " << walk_len << "nodes should have the expected trip count") {
113  CHECK(expected_trip_count == tripCountOfWalk(mrrg, decltype(walk){walk.begin(), std::next(walk.begin(), walk_len)}));
114  }
115  THEN("the last " << walk.size() - walk_len << " nodes should have the expected trip count") {
116  CHECK(total_trip_count - expected_trip_count == tripCountOfWalk(mrrg, decltype(walk){std::next(walk.begin(), walk_len-1), walk.end()}));
117  }
118  }
119  }
120 }
121 
122 namespace {
123  struct OperandCompatibilityTest {
124 
125  };
126 } // end anon namespace
127 
128 TEST_CASE("Operand Compatibility Tests", "") {
129  const auto mrrg = makeMRRG_4nodeLinear(1, 0, 0, {
130  {"operand-c1","C1"},
131  {"operand-c2","C2"},
132  });
133 
134  // some node tests
135 
136  const auto& node_a = mrrg.getNode(0,"a");
137  const auto& node_b = mrrg.getNode(0,"b");
138  const auto& node_c = mrrg.getNode(0,"c");
139  const auto& node_d = mrrg.getNode(0,"d");
140 
141  CHECK (nodeIsCompatibleWithOperand(mrrg, node_a, Operands::UNTAGGED));
142  CHECK (nodeIsCompatibleWithOperand(mrrg, node_a, "aoeu"));
143  CHECK (nodeIsCompatibleWithOperand(mrrg, node_a, "C1"));
144  CHECK (nodeIsCompatibleWithOperand(mrrg, node_a, "C2"));
145 
146  CHECK (nodeIsCompatibleWithOperand(mrrg, node_b, Operands::UNTAGGED));
147  CHECK (nodeIsCompatibleWithOperand(mrrg, node_b, "aoeu"));
148  CHECK (nodeIsCompatibleWithOperand(mrrg, node_b, "C1"));
149  CHECK (nodeIsCompatibleWithOperand(mrrg, node_b, "C2"));
150 
151  CHECK (nodeIsCompatibleWithOperand(mrrg, node_c, Operands::UNTAGGED));
152  CHECK_FALSE(nodeIsCompatibleWithOperand(mrrg, node_c, "aoeu"));
153  CHECK (nodeIsCompatibleWithOperand(mrrg, node_c, "C1"));
154  CHECK (nodeIsCompatibleWithOperand(mrrg, node_c, "C2"));
155 
156  CHECK (nodeIsCompatibleWithOperand(mrrg, node_d, Operands::UNTAGGED));
157  CHECK (nodeIsCompatibleWithOperand(mrrg, node_d, "aoeu"));
158  CHECK (nodeIsCompatibleWithOperand(mrrg, node_d, "C1"));
159  CHECK (nodeIsCompatibleWithOperand(mrrg, node_d, "C2"));
160 
161  // some walk tests
162 
163  const auto nodes_a_to_a = std::vector<MRRG::NodeDescriptor>{node_a, node_b};
164  const auto nodes_a_to_b = std::vector<MRRG::NodeDescriptor>{node_a, node_b, node_c};
165  const auto nodes_b_to_c = std::vector<MRRG::NodeDescriptor>{ node_b, node_c, node_d};
166  const auto nodes_a_to_c = std::vector<MRRG::NodeDescriptor>{node_a, node_b, node_c, node_d};
167 
168  CHECK (walkIsCompatibleWithOperand(mrrg, nodes_a_to_a, Operands::UNTAGGED));
169  CHECK (walkIsCompatibleWithOperand(mrrg, nodes_a_to_a, "aoeu"));
170  CHECK (walkIsCompatibleWithOperand(mrrg, nodes_a_to_a, "C1"));
171  CHECK (walkIsCompatibleWithOperand(mrrg, nodes_a_to_a, "C2"));
172 
173  CHECK (walkIsCompatibleWithOperand(mrrg, nodes_a_to_b, Operands::UNTAGGED));
174  CHECK (walkIsCompatibleWithOperand(mrrg, nodes_a_to_b, "aoeu"));
175  CHECK (walkIsCompatibleWithOperand(mrrg, nodes_a_to_b, "C1"));
176  CHECK (walkIsCompatibleWithOperand(mrrg, nodes_a_to_b, "C2"));
177 
178  CHECK (walkIsCompatibleWithOperand(mrrg, nodes_a_to_c, Operands::UNTAGGED));
179  CHECK_FALSE(walkIsCompatibleWithOperand(mrrg, nodes_a_to_c, "aoeu"));
180  CHECK (walkIsCompatibleWithOperand(mrrg, nodes_a_to_c, "C1"));
181  CHECK (walkIsCompatibleWithOperand(mrrg, nodes_a_to_c, "C2"));
182 
183  CHECK (walkIsCompatibleWithOperand(mrrg, nodes_b_to_c, Operands::UNTAGGED));
184  CHECK_FALSE(walkIsCompatibleWithOperand(mrrg, nodes_b_to_c, "aoeu"));
185  CHECK (walkIsCompatibleWithOperand(mrrg, nodes_b_to_c, "C1"));
186  CHECK (walkIsCompatibleWithOperand(mrrg, nodes_b_to_c, "C2"));
187 
188  // some composability tests. Concatenation == logical AND
189 
190  CHECK(walkIsCompatibleWithOperand(mrrg, nodes_a_to_c, Operands::UNTAGGED) == (walkIsCompatibleWithOperand(mrrg, nodes_a_to_a, Operands::UNTAGGED) && walkIsCompatibleWithOperand(mrrg, nodes_b_to_c, Operands::UNTAGGED)));
191  CHECK(walkIsCompatibleWithOperand(mrrg, nodes_a_to_c, "C1" ) == (walkIsCompatibleWithOperand(mrrg, nodes_a_to_a, "C1" ) && walkIsCompatibleWithOperand(mrrg, nodes_b_to_c, "C1" )));
192  CHECK(walkIsCompatibleWithOperand(mrrg, nodes_a_to_c, "C2" ) == (walkIsCompatibleWithOperand(mrrg, nodes_a_to_a, "C2" ) && walkIsCompatibleWithOperand(mrrg, nodes_b_to_c, "C2" )));
193  CHECK(walkIsCompatibleWithOperand(mrrg, nodes_a_to_c, "aoeu") == (walkIsCompatibleWithOperand(mrrg, nodes_a_to_a, "aoeu") && walkIsCompatibleWithOperand(mrrg, nodes_b_to_c, "aoeu")));
194 }
195 
196 namespace {
197 
198 struct FindNRoutingPathsBetweenTest {
199  std::pair<int,int> num_paths_to_search_for_min_max;
200  int max_paths_expected;
201  std::pair<int,std::string> source;
202  std::pair<int,std::string> sink;
203  std::pair<int,int> cycle_path_latency_min_max;
204  std::string mrrg_description;
205  std::function<MRRG()> mrrg_generator;
206 };
207 
208 }
209 
210 TEST_CASE("findNRoutingPathsBetween Tests", "") {
211  const auto test = GENERATE(values<FindNRoutingPathsBetweenTest>({
212  { {1,3}, 1, {0,"fu"}, {0,"fu"}, {1,1}, "2 node loop with II=1, fu latency 0, reg latency 1", []{ return makeMRRG_2nodeFUAndReg(1, 0, 1); } },
213  }));
214 
215  const auto num_paths_to_search_for = GENERATE_REF(range(
216  test.num_paths_to_search_for_min_max.first, test.num_paths_to_search_for_min_max.second+1
217  ));
218  const auto mrrg = test.mrrg_generator();
219  const auto src = mrrg.getNode(test.source.first, test.source.second);
220  const auto sink = mrrg.getNode(test.sink.first, test.sink.second);
221  const auto actual = findNRoutingPathsBetween(
222  num_paths_to_search_for, mrrg, src, sink, test.cycle_path_latency_min_max, {}, nullptr);
223  CHECK(actual.size() <= test.max_paths_expected);
224  CHECK(actual.size() <= num_paths_to_search_for);
225 }
226 
227 namespace {
228 
229 template<typename VertexNameLists>
230 auto resolve_node_name_lists(bool create_if_not_exist, MRRG& mrrg, const VertexNameLists& vertex_lists) {
231  std::vector<std::vector<MRRG::NodeDescriptor>> result;
232  for (const auto& vlist : vertex_lists) {
233  std::vector<MRRG::NodeDescriptor> resolved_list;
234  for (const auto& v_name : vlist) {
235  const auto curr_ndesc_and_is_new = mrrg.insert(
236  MRRGNode::make_routing(&god_module, 32, 0, v_name));
237  if (curr_ndesc_and_is_new.second && not create_if_not_exist) {
238  throw make_from_stream<std::logic_error>([&](auto&& s) {
239  s << "used non-existant node " << v_name;
240  });
241  }
242  resolved_list.push_back(curr_ndesc_and_is_new.first);
243  if (resolved_list.size() > 1) {
244  const auto prev_ndesc = *std::next(resolved_list.rbegin());
245  const auto& prev_fo = mrrg.fanout(prev_ndesc);
246  // link if not already linked
247  if (std::find(prev_fo.begin(), prev_fo.end(), curr_ndesc_and_is_new.first) == prev_fo.end()) {
248  if (not create_if_not_exist) { throw make_from_stream<std::logic_error>([&](auto&& s) {
249  s << "used non-existant edge to " << v_name;
250  });}
251  mrrg.link(prev_ndesc, curr_ndesc_and_is_new.first);
252  }
253  }
254  }
255  result.push_back(std::move(resolved_list));
256  }
257  return result;
258 };
259 
260 struct MergeMRRGWalksTest {
261  std::string test_description;
262  // MRRG mrrg;
263  std::vector<std::vector<std::string>> input;
264  std::vector<std::vector<std::string>> expected;
265 };
266 
267 } // end anon namespace
268 
269 TEST_CASE("mergeMRRGWalks Tests", "") {
270  const auto test = GENERATE(values<MergeMRRGWalksTest>({
271  // NOTE: Many of these tests are tuned so that there is a unique solution.
272  // Nothing is assumed about the graph, but there is an assumption that
273  // mergeMRRGWalks uses breadth-first searches.
274  { "two isolated paths", {
275  { "a1", "z1" },
276  { "a2", "z2" },
277  }, {
278  { "a1", "z1" },
279  { "a2", "z2" },
280  },
281  },
282  { "two diverging paths", {
283  { "a", "z1" },
284  { "a", "z2" },
285  }, {
286  { "a", "z1" },
287  { "a", "z2" },
288  },
289  },
290  { "two re-converging paths -- same start and end", {
291  { "a", "b1", "c", "z" },
292  { "a", "b2", "z" },
293  }, {
294  { "a", "b2", "z" },
295  },
296  },
297  { "two re-converging paths -- same start, different end", {
298  { "a", "b1", "c", "d", "e", "z1" },
299  { "a", "b2", "d", "z2" },
300  }, {
301  { "a", "b2", "d", "z2" },
302  { "d", "e", "z1" },
303  },
304  },
305  { "two re-converging paths -- different start, different end", {
306  { "x1", "y", "a", "b1", "c", "d", "e", "z1" },
307  { "x2", "a", "b2", "d", "z2" },
308  }, {
309  { "x2", "a", "b2", "d", "z2" },
310  { "d", "e", "z1" },
311  { "x1", "y", "a", },
312  },
313  },
314  { "a loop", {
315  { "a", "b", "c", "a"},
316  }, {
317  { "a", "b", "c", "a"},
318  },
319  },
320  { "a loop, plus a line -- only sharing a source", {
321  { "a", "b", "c", "a"},
322  { "a", "z"},
323  }, {
324  { "a", "b", "c", "a"},
325  { "a", "z"},
326  },
327  },
328  { "a loop, plus a line -- sharing the source and a fanout", {
329  { "a", "b", "c", "a"},
330  { "a", "b", "d", "e", "z"},
331  }, {
332  { "a", "b", "c", "a"},
333  { "b", "d", "e", "z"},
334  },
335  },
336  }));
337 
338  GIVEN("A '" << test.test_description << "' test") {
339  MRRG mrrg(1);
340  const auto input = resolve_node_name_lists(true, mrrg, test.input);
341  auto expected = resolve_node_name_lists(false, mrrg, test.expected);
342  std::sort(expected.begin(), expected.end());
343 
344  auto actual = mergeMRRGWalks(mrrg, input);
345  std::sort(actual.begin(), actual.end());
346 
347  CHECK(actual == expected);
348  }
349 }
350 
351 // remove if used
352 TEST_CASE("unused functions", "[!hide]") {
353  (void)makeMRRG_2112DAG;
354  (void)makeMRRG_112221DAG;
355 }
MRRG::link
void link(MRRG::NodeDescriptor driver, MRRG::NodeDescriptor fanout)
Definition: MRRG.cpp:849
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
findNRoutingPathsBetween
std::vector< std::vector< MRRG::NodeDescriptor > > findNRoutingPathsBetween(int num_paths, const MRRG &mrrg, MRRG::NodeDescriptor source, MRRG::NodeDescriptor sink, std::pair< int, int > cycle_trip_count_min_max, const OperandTag &operand_tag, MRRGProcedureCacheHandle *cache_handle)
Definition: MRRGProcedures.cpp:248
MRRG
Definition: MRRG.h:216
TEST_CASE
TEST_CASE("tripCountOfWalk Tests", "")
Definition: MRRGProcedures_tests.cpp:98
nodeIsCompatibleWithOperand
bool nodeIsCompatibleWithOperand(const MRRG &mrrg, MRRG::NodeDescriptor node, const OperandTag &operand_tag)
Definition: MRRGProcedures.cpp:391
Module.h
ConfigStore
Definition: ConfigStore.h:76
OpCode::ADD
@ ADD
Operands::UNTAGGED
static constexpr auto & UNTAGGED
Definition: OpGraph.h:87
MRRG::insert
std::pair< NodeDescriptor, bool > insert(MRRGNode node)
Definition: MRRG.cpp:91
MRRG::fanout
auto & fanout(NodeDescriptor ndesc) const
Definition: MRRG.h:288
MRRGNode::make_operand_pin
static MRRGNode make_operand_pin(Module *parent, int bitwidth, int cycle, STR &&name, SupportedOpTags operand_tags, int latency=0, int max_cap=1)
Definition: MRRG.h:164
MRRGNode::make_function
static MRRGNode make_function(Module *parent, int bitwidth, int cycle, STR &&name, int latency, SupportedOps supported_ops, int max_cap=1, bool is_const_unit=false)
Definition: MRRG.h:191
Module
Definition: Module.h:163
MRRGProcedures.h
mergeMRRGWalks
std::vector< std::vector< MRRG::NodeDescriptor > > mergeMRRGWalks(const MRRG &mrrg, const std::vector< MrrgNodeSpan > &walks)
Definition: MRRGProcedures.cpp:296
MRRGNode::make_routing
static MRRGNode make_routing(Module *parent, int bitwidth, int cycle, STR &&name, int latency=0, int max_cap=1)
Definition: MRRG.h:151
tripCountOfWalk
int tripCountOfWalk(const MRRG &mrrg, const std::vector< MRRG::NodeDescriptor > &walk)
Definition: MRRGProcedures.cpp:379
walkIsCompatibleWithOperand
bool walkIsCompatibleWithOperand(const MRRG &mrrg, const std::vector< MRRG::NodeDescriptor > &walk, const OperandTag &operand_tag)
Definition: MRRGProcedures.cpp:396