Merge pull request #1986 from YosysHQ/eddie/verific_enum
[yosys.git] / passes / cmds / bugpoint.cc
1 /*
2 * yosys -- Yosys Open SYnthesis Suite
3 *
4 * Copyright (C) 2018 whitequark <whitequark@whitequark.org>
5 *
6 * Permission to use, copy, modify, and/or distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 *
18 */
19
20 #include "kernel/yosys.h"
21 #include "backends/ilang/ilang_backend.h"
22
23 USING_YOSYS_NAMESPACE
24 using namespace ILANG_BACKEND;
25 PRIVATE_NAMESPACE_BEGIN
26
27 struct BugpointPass : public Pass {
28 BugpointPass() : Pass("bugpoint", "minimize testcases") { }
29 void help() YS_OVERRIDE
30 {
31 // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
32 log("\n");
33 log(" bugpoint [options]\n");
34 log("\n");
35 log("This command minimizes testcases that crash Yosys. It removes an arbitrary part\n");
36 log("of the design and recursively invokes Yosys with a given script, repeating these\n");
37 log("steps while it can find a smaller design that still causes a crash. Once this\n");
38 log("command finishes, it replaces the current design with the smallest testcase it\n");
39 log("was able to produce.\n");
40 log("\n");
41 log("It is possible to specify the kinds of design part that will be removed. If none\n");
42 log("are specified, all parts of design will be removed.\n");
43 log("\n");
44 log(" -yosys <filename>\n");
45 log(" use this Yosys binary. if not specified, `yosys` is used.\n");
46 log("\n");
47 log(" -script <filename>\n");
48 log(" use this script to crash Yosys. required.\n");
49 log("\n");
50 log(" -grep <string>\n");
51 log(" only consider crashes that place this string in the log file.\n");
52 log("\n");
53 log(" -fast\n");
54 log(" run `proc_clean; clean -purge` after each minimization step. converges\n");
55 log(" faster, but produces larger testcases, and may fail to produce any\n");
56 log(" testcase at all if the crash is related to dangling wires.\n");
57 log("\n");
58 log(" -clean\n");
59 log(" run `proc_clean; clean -purge` before checking testcase and after\n");
60 log(" finishing. produces smaller and more useful testcases, but may fail to\n");
61 log(" produce any testcase at all if the crash is related to dangling wires.\n");
62 log("\n");
63 log(" -modules\n");
64 log(" try to remove modules.\n");
65 log("\n");
66 log(" -ports\n");
67 log(" try to remove module ports.\n");
68 log("\n");
69 log(" -cells\n");
70 log(" try to remove cells.\n");
71 log("\n");
72 log(" -connections\n");
73 log(" try to reconnect ports to 'x.\n");
74 log("\n");
75 log(" -assigns\n");
76 log(" try to remove process assigns from cases.\n");
77 log("\n");
78 log(" -updates\n");
79 log(" try to remove process updates from syncs.\n");
80 log("\n");
81 }
82
83 bool run_yosys(RTLIL::Design *design, string yosys_cmd, string script)
84 {
85 design->sort();
86
87 std::ofstream f("bugpoint-case.il");
88 ILANG_BACKEND::dump_design(f, design, /*only_selected=*/false, /*flag_m=*/true, /*flag_n=*/false);
89 f.close();
90
91 string yosys_cmdline = stringf("%s -qq -L bugpoint-case.log -s %s bugpoint-case.il", yosys_cmd.c_str(), script.c_str());
92 return run_command(yosys_cmdline) == 0;
93 }
94
95 bool check_logfile(string grep)
96 {
97 if (grep.empty())
98 return true;
99
100 std::ifstream f("bugpoint-case.log");
101 while (!f.eof())
102 {
103 string line;
104 getline(f, line);
105 if (line.find(grep) != std::string::npos)
106 return true;
107 }
108 return false;
109 }
110
111 RTLIL::Design *clean_design(RTLIL::Design *design, bool do_clean = true, bool do_delete = false)
112 {
113 if (!do_clean)
114 return design;
115
116 RTLIL::Design *design_copy = new RTLIL::Design;
117 for (auto module : design->modules())
118 design_copy->add(module->clone());
119 Pass::call(design_copy, "proc_clean -quiet");
120 Pass::call(design_copy, "clean -purge");
121
122 if (do_delete)
123 delete design;
124 return design_copy;
125 }
126
127 RTLIL::Design *simplify_something(RTLIL::Design *design, int &seed, bool stage2, bool modules, bool ports, bool cells, bool connections, bool assigns, bool updates)
128 {
129 RTLIL::Design *design_copy = new RTLIL::Design;
130 for (auto module : design->modules())
131 design_copy->add(module->clone());
132
133 int index = 0;
134 if (modules)
135 {
136 Module *removed_module = nullptr;
137 for (auto module : design_copy->modules())
138 {
139 if (module->get_blackbox_attribute())
140 continue;
141
142 if (index++ == seed)
143 {
144 log("Trying to remove module %s.\n", module->name.c_str());
145 removed_module = module;
146 break;
147 }
148 }
149 if (removed_module) {
150 design_copy->remove(removed_module);
151 return design_copy;
152 }
153 }
154 if (ports)
155 {
156 for (auto mod : design_copy->modules())
157 {
158 if (mod->get_blackbox_attribute())
159 continue;
160
161 for (auto wire : mod->wires())
162 {
163 if (!stage2 && wire->get_bool_attribute(ID($bugpoint)))
164 continue;
165
166 if (wire->port_input || wire->port_output)
167 {
168 if (index++ == seed)
169 {
170 log("Trying to remove module port %s.\n", log_signal(wire));
171 wire->port_input = wire->port_output = false;
172 mod->fixup_ports();
173 return design_copy;
174 }
175 }
176 }
177 }
178 }
179 if (cells)
180 {
181 for (auto mod : design_copy->modules())
182 {
183 if (mod->get_blackbox_attribute())
184 continue;
185
186 Cell *removed_cell = nullptr;
187 for (auto cell : mod->cells())
188 {
189 if (index++ == seed)
190 {
191 log("Trying to remove cell %s.%s.\n", mod->name.c_str(), cell->name.c_str());
192 removed_cell = cell;
193 break;
194 }
195 }
196 if (removed_cell) {
197 mod->remove(removed_cell);
198 return design_copy;
199 }
200 }
201 }
202 if (connections)
203 {
204 for (auto mod : design_copy->modules())
205 {
206 if (mod->get_blackbox_attribute())
207 continue;
208
209 for (auto cell : mod->cells())
210 {
211 for (auto it : cell->connections_)
212 {
213 RTLIL::SigSpec port = cell->getPort(it.first);
214 bool is_undef = port.is_fully_undef();
215 bool is_port = port.is_wire() && (port.as_wire()->port_input || port.as_wire()->port_output);
216
217 if(is_undef || (!stage2 && is_port))
218 continue;
219
220 if (index++ == seed)
221 {
222 log("Trying to remove cell port %s.%s.%s.\n", mod->name.c_str(), cell->name.c_str(), it.first.c_str());
223 RTLIL::SigSpec port_x(State::Sx, port.size());
224 cell->unsetPort(it.first);
225 cell->setPort(it.first, port_x);
226 return design_copy;
227 }
228
229 if (!stage2 && (cell->input(it.first) || cell->output(it.first)) && index++ == seed)
230 {
231 log("Trying to expose cell port %s.%s.%s as module port.\n", mod->name.c_str(), cell->name.c_str(), it.first.c_str());
232 RTLIL::Wire *wire = mod->addWire(NEW_ID, port.size());
233 wire->set_bool_attribute(ID($bugpoint));
234 wire->port_input = cell->input(it.first);
235 wire->port_output = cell->output(it.first);
236 cell->unsetPort(it.first);
237 cell->setPort(it.first, wire);
238 mod->fixup_ports();
239 return design_copy;
240 }
241 }
242 }
243 }
244 }
245 if (assigns)
246 {
247 for (auto mod : design_copy->modules())
248 {
249 if (mod->get_blackbox_attribute())
250 continue;
251
252 for (auto &pr : mod->processes)
253 {
254 vector<RTLIL::CaseRule*> cases = {&pr.second->root_case};
255 while (!cases.empty())
256 {
257 RTLIL::CaseRule *cs = cases[0];
258 cases.erase(cases.begin());
259 for (auto it = cs->actions.begin(); it != cs->actions.end(); ++it)
260 {
261 if (index++ == seed)
262 {
263 log("Trying to remove assign %s %s in %s.%s.\n", log_signal((*it).first), log_signal((*it).second), mod->name.c_str(), pr.first.c_str());
264 cs->actions.erase(it);
265 return design_copy;
266 }
267 }
268 for (auto &sw : cs->switches)
269 cases.insert(cases.end(), sw->cases.begin(), sw->cases.end());
270 }
271 }
272 }
273 }
274 if (updates)
275 {
276 for (auto mod : design_copy->modules())
277 {
278 if (mod->get_blackbox_attribute())
279 continue;
280
281 for (auto &pr : mod->processes)
282 {
283 for (auto &sy : pr.second->syncs)
284 {
285 for (auto it = sy->actions.begin(); it != sy->actions.end(); ++it)
286 {
287 if (index++ == seed)
288 {
289 log("Trying to remove sync %s update %s %s in %s.%s.\n", log_signal(sy->signal), log_signal((*it).first), log_signal((*it).second), mod->name.c_str(), pr.first.c_str());
290 sy->actions.erase(it);
291 return design_copy;
292 }
293 }
294 }
295 }
296 }
297 }
298 return nullptr;
299 }
300
301 void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
302 {
303 string yosys_cmd = "yosys", script, grep;
304 bool fast = false, clean = false;
305 bool modules = false, ports = false, cells = false, connections = false, assigns = false, updates = false, has_part = false;
306
307 size_t argidx;
308 for (argidx = 1; argidx < args.size(); argidx++)
309 {
310 if (args[argidx] == "-yosys" && argidx + 1 < args.size()) {
311 yosys_cmd = args[++argidx];
312 continue;
313 }
314 if (args[argidx] == "-script" && argidx + 1 < args.size()) {
315 script = args[++argidx];
316 continue;
317 }
318 if (args[argidx] == "-grep" && argidx + 1 < args.size()) {
319 grep = args[++argidx];
320 continue;
321 }
322 if (args[argidx] == "-fast") {
323 fast = true;
324 continue;
325 }
326 if (args[argidx] == "-clean") {
327 clean = true;
328 continue;
329 }
330 if (args[argidx] == "-modules") {
331 modules = true;
332 has_part = true;
333 continue;
334 }
335 if (args[argidx] == "-ports") {
336 ports = true;
337 has_part = true;
338 continue;
339 }
340 if (args[argidx] == "-cells") {
341 cells = true;
342 has_part = true;
343 continue;
344 }
345 if (args[argidx] == "-connections") {
346 connections = true;
347 has_part = true;
348 continue;
349 }
350 if (args[argidx] == "-assigns") {
351 assigns = true;
352 has_part = true;
353 continue;
354 }
355 if (args[argidx] == "-updates") {
356 updates = true;
357 has_part = true;
358 continue;
359 }
360 break;
361 }
362 extra_args(args, argidx, design);
363
364 if (script.empty())
365 log_cmd_error("Missing -script option.\n");
366
367 if (!has_part)
368 {
369 modules = true;
370 ports = true;
371 cells = true;
372 connections = true;
373 assigns = true;
374 updates = true;
375 }
376
377 if (!design->full_selection())
378 log_cmd_error("This command only operates on fully selected designs!\n");
379
380 RTLIL::Design *crashing_design = clean_design(design, clean);
381 if (run_yosys(crashing_design, yosys_cmd, script))
382 log_cmd_error("The provided script file and Yosys binary do not crash on this design!\n");
383 if (!check_logfile(grep))
384 log_cmd_error("The provided grep string is not found in the log file!\n");
385
386 int seed = 0;
387 bool found_something = false, stage2 = false;
388 while (true)
389 {
390 if (RTLIL::Design *simplified = simplify_something(crashing_design, seed, stage2, modules, ports, cells, connections, assigns, updates))
391 {
392 simplified = clean_design(simplified, fast, /*do_delete=*/true);
393
394 bool crashes;
395 if (clean)
396 {
397 RTLIL::Design *testcase = clean_design(simplified);
398 crashes = !run_yosys(testcase, yosys_cmd, script);
399 delete testcase;
400 }
401 else
402 {
403 crashes = !run_yosys(simplified, yosys_cmd, script);
404 }
405
406 if (crashes && check_logfile(grep))
407 {
408 log("Testcase crashes.\n");
409 if (crashing_design != design)
410 delete crashing_design;
411 crashing_design = simplified;
412 found_something = true;
413 }
414 else
415 {
416 log("Testcase does not crash.\n");
417 delete simplified;
418 seed++;
419 }
420 }
421 else
422 {
423 seed = 0;
424 if (found_something)
425 found_something = false;
426 else
427 {
428 if (!stage2)
429 {
430 log("Demoting introduced module ports.\n");
431 stage2 = true;
432 }
433 else
434 {
435 log("Simplifications exhausted.\n");
436 break;
437 }
438 }
439 }
440 }
441
442 if (crashing_design != design)
443 {
444 Pass::call(design, "design -reset");
445 crashing_design = clean_design(crashing_design, clean, /*do_delete=*/true);
446 for (auto module : crashing_design->modules())
447 design->add(module->clone());
448 delete crashing_design;
449 }
450 }
451 } BugpointPass;
452
453 PRIVATE_NAMESPACE_END