Merge pull request #1105 from YosysHQ/clifford/fixlogicinit
[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 `clean -purge` after each minimization step. converges faster, but\n");
55 log(" produces larger testcases, and may fail to produce any testcase at all if\n");
56 log(" the crash is related to dangling wires.\n");
57 log("\n");
58 log(" -clean\n");
59 log(" run `clean -purge` before checking testcase and after finishing. produces\n");
60 log(" smaller and more useful testcases, but may fail to produce any testcase\n");
61 log(" 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 }
76
77 bool run_yosys(RTLIL::Design *design, string yosys_cmd, string script)
78 {
79 design->sort();
80
81 std::ofstream f("bugpoint-case.il");
82 ILANG_BACKEND::dump_design(f, design, /*only_selected=*/false, /*flag_m=*/true, /*flag_n=*/false);
83 f.close();
84
85 string yosys_cmdline = stringf("%s -qq -L bugpoint-case.log -s %s bugpoint-case.il", yosys_cmd.c_str(), script.c_str());
86 return run_command(yosys_cmdline) == 0;
87 }
88
89 bool check_logfile(string grep)
90 {
91 if (grep.empty())
92 return true;
93
94 std::ifstream f("bugpoint-case.log");
95 while (!f.eof())
96 {
97 string line;
98 getline(f, line);
99 if (line.find(grep) != std::string::npos)
100 return true;
101 }
102 return false;
103 }
104
105 RTLIL::Design *clean_design(RTLIL::Design *design, bool do_clean = true, bool do_delete = false)
106 {
107 if (!do_clean)
108 return design;
109
110 RTLIL::Design *design_copy = new RTLIL::Design;
111 for (auto &it : design->modules_)
112 design_copy->add(it.second->clone());
113 Pass::call(design_copy, "clean -purge");
114
115 if (do_delete)
116 delete design;
117 return design_copy;
118 }
119
120 RTLIL::Design *simplify_something(RTLIL::Design *design, int &seed, bool stage2, bool modules, bool ports, bool cells, bool connections)
121 {
122 RTLIL::Design *design_copy = new RTLIL::Design;
123 for (auto &it : design->modules_)
124 design_copy->add(it.second->clone());
125
126 int index = 0;
127 if (modules)
128 {
129 for (auto &it : design_copy->modules_)
130 {
131 if (it.second->get_blackbox_attribute())
132 continue;
133
134 if (index++ == seed)
135 {
136 log("Trying to remove module %s.\n", it.first.c_str());
137 design_copy->remove(it.second);
138 return design_copy;
139 }
140 }
141 }
142 if (ports)
143 {
144 for (auto mod : design_copy->modules())
145 {
146 if (mod->get_blackbox_attribute())
147 continue;
148
149 for (auto wire : mod->wires())
150 {
151 if (!stage2 && wire->get_bool_attribute("$bugpoint"))
152 continue;
153
154 if (wire->port_input || wire->port_output)
155 {
156 if (index++ == seed)
157 {
158 log("Trying to remove module port %s.\n", log_signal(wire));
159 wire->port_input = wire->port_output = false;
160 mod->fixup_ports();
161 return design_copy;
162 }
163 }
164 }
165 }
166 }
167 if (cells)
168 {
169 for (auto mod : design_copy->modules())
170 {
171 if (mod->get_blackbox_attribute())
172 continue;
173
174 for (auto &it : mod->cells_)
175 {
176 if (index++ == seed)
177 {
178 log("Trying to remove cell %s.%s.\n", mod->name.c_str(), it.first.c_str());
179 mod->remove(it.second);
180 return design_copy;
181 }
182 }
183 }
184 }
185 if (connections)
186 {
187 for (auto mod : design_copy->modules())
188 {
189 if (mod->get_blackbox_attribute())
190 continue;
191
192 for (auto cell : mod->cells())
193 {
194 for (auto it : cell->connections_)
195 {
196 RTLIL::SigSpec port = cell->getPort(it.first);
197 bool is_undef = port.is_fully_undef();
198 bool is_port = port.is_wire() && (port.as_wire()->port_input || port.as_wire()->port_output);
199
200 if(is_undef || (!stage2 && is_port))
201 continue;
202
203 if (index++ == seed)
204 {
205 log("Trying to remove cell port %s.%s.%s.\n", mod->name.c_str(), cell->name.c_str(), it.first.c_str());
206 RTLIL::SigSpec port_x(State::Sx, port.size());
207 cell->unsetPort(it.first);
208 cell->setPort(it.first, port_x);
209 return design_copy;
210 }
211
212 if (!stage2 && (cell->input(it.first) || cell->output(it.first)) && index++ == seed)
213 {
214 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());
215 RTLIL::Wire *wire = mod->addWire(NEW_ID, port.size());
216 wire->set_bool_attribute("$bugpoint");
217 wire->port_input = cell->input(it.first);
218 wire->port_output = cell->output(it.first);
219 cell->unsetPort(it.first);
220 cell->setPort(it.first, wire);
221 mod->fixup_ports();
222 return design_copy;
223 }
224 }
225 }
226 }
227 }
228 return NULL;
229 }
230
231 void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
232 {
233 string yosys_cmd = "yosys", script, grep;
234 bool fast = false, clean = false;
235 bool modules = false, ports = false, cells = false, connections = false, has_part = false;
236
237 size_t argidx;
238 for (argidx = 1; argidx < args.size(); argidx++)
239 {
240 if (args[argidx] == "-yosys" && argidx + 1 < args.size()) {
241 yosys_cmd = args[++argidx];
242 continue;
243 }
244 if (args[argidx] == "-script" && argidx + 1 < args.size()) {
245 script = args[++argidx];
246 continue;
247 }
248 if (args[argidx] == "-grep" && argidx + 1 < args.size()) {
249 grep = args[++argidx];
250 continue;
251 }
252 if (args[argidx] == "-fast") {
253 fast = true;
254 continue;
255 }
256 if (args[argidx] == "-clean") {
257 clean = true;
258 continue;
259 }
260 if (args[argidx] == "-modules") {
261 modules = true;
262 has_part = true;
263 continue;
264 }
265 if (args[argidx] == "-ports") {
266 ports = true;
267 has_part = true;
268 continue;
269 }
270 if (args[argidx] == "-cells") {
271 cells = true;
272 has_part = true;
273 continue;
274 }
275 if (args[argidx] == "-connections") {
276 connections = true;
277 has_part = true;
278 continue;
279 }
280 break;
281 }
282 extra_args(args, argidx, design);
283
284 if (script.empty())
285 log_cmd_error("Missing -script option.\n");
286
287 if (!has_part)
288 {
289 modules = true;
290 ports = true;
291 cells = true;
292 connections = true;
293 }
294
295 if (!design->full_selection())
296 log_cmd_error("This command only operates on fully selected designs!\n");
297
298 RTLIL::Design *crashing_design = clean_design(design, clean);
299 if (run_yosys(crashing_design, yosys_cmd, script))
300 log_cmd_error("The provided script file and Yosys binary do not crash on this design!\n");
301 if (!check_logfile(grep))
302 log_cmd_error("The provided grep string is not found in the log file!\n");
303
304 int seed = 0;
305 bool found_something = false, stage2 = false;
306 while (true)
307 {
308 if (RTLIL::Design *simplified = simplify_something(crashing_design, seed, stage2, modules, ports, cells, connections))
309 {
310 simplified = clean_design(simplified, fast, /*do_delete=*/true);
311
312 bool crashes;
313 if (clean)
314 {
315 RTLIL::Design *testcase = clean_design(simplified);
316 crashes = !run_yosys(testcase, yosys_cmd, script);
317 delete testcase;
318 }
319 else
320 {
321 crashes = !run_yosys(simplified, yosys_cmd, script);
322 }
323
324 if (crashes && check_logfile(grep))
325 {
326 log("Testcase crashes.\n");
327 if (crashing_design != design)
328 delete crashing_design;
329 crashing_design = simplified;
330 found_something = true;
331 }
332 else
333 {
334 log("Testcase does not crash.\n");
335 delete simplified;
336 seed++;
337 }
338 }
339 else
340 {
341 seed = 0;
342 if (found_something)
343 found_something = false;
344 else
345 {
346 if (!stage2)
347 {
348 log("Demoting introduced module ports.\n");
349 stage2 = true;
350 }
351 else
352 {
353 log("Simplifications exhausted.\n");
354 break;
355 }
356 }
357 }
358 }
359
360 if (crashing_design != design)
361 {
362 Pass::call(design, "design -reset");
363 crashing_design = clean_design(crashing_design, clean, /*do_delete=*/true);
364 for (auto &it : crashing_design->modules_)
365 design->add(it.second->clone());
366 delete crashing_design;
367 }
368 }
369 } BugpointPass;
370
371 PRIVATE_NAMESPACE_END