From f25f8fe7c445e19a11835ccea87a16947b201c3e Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Mon, 21 Jul 2025 05:32:31 +0000 Subject: [PATCH 01/24] In the Verilog backend, only sort modules that we're going to emit. If you have a large design with a lot of modules and you use the Verilog backend to emit modules one at a time to separate files, performance is very low. The problem is that the Verilog backend calls `design->sort()` every time, which sorts the contents of all modules, and this is slow even when everything is already sorted. We can easily fix this by only sorting the contents of modules that we're actually going to emit. --- backends/verilog/verilog_backend.cc | 3 ++- kernel/rtlil.cc | 6 ++++++ kernel/rtlil.h | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/backends/verilog/verilog_backend.cc b/backends/verilog/verilog_backend.cc index 1cef7be60..144dad90c 100644 --- a/backends/verilog/verilog_backend.cc +++ b/backends/verilog/verilog_backend.cc @@ -2612,7 +2612,7 @@ struct VerilogBackend : public Backend { Pass::call(design, "clean_zerowidth"); log_pop(); - design->sort(); + design->sort_modules(); *f << stringf("/* Generated by %s */\n", yosys_maybe_version()); @@ -2625,6 +2625,7 @@ struct VerilogBackend : public Backend { continue; } log("Dumping module `%s'.\n", module->name.c_str()); + module->sort(); dump_module(*f, "", module); } diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index 8a0080dbf..db954a7c3 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -1139,6 +1139,12 @@ void RTLIL::Design::sort() it.second->sort(); } +void RTLIL::Design::sort_modules() +{ + scratchpad.sort(); + modules_.sort(sort_by_id_str()); +} + void RTLIL::Design::check() { #ifndef NDEBUG diff --git a/kernel/rtlil.h b/kernel/rtlil.h index 504fa0062..cf3f24c81 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -1368,6 +1368,7 @@ struct RTLIL::Design std::string scratchpad_get_string(const std::string &varname, const std::string &default_value = std::string()) const; void sort(); + void sort_modules(); void check(); void optimize(); From 875e06833494c9386e701b8b1ee1b48c38890fe0 Mon Sep 17 00:00:00 2001 From: "N. Engelhardt" Date: Tue, 22 Jul 2025 10:15:37 +0200 Subject: [PATCH 02/24] sort and comment .gitignore --- .gitignore | 69 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 45 insertions(+), 24 deletions(-) diff --git a/.gitignore b/.gitignore index 7b4a1fb1e..3b77edf1f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,26 +1,19 @@ +## user config +/Makefile.conf + +## build artifacts +# compiler intermediate files *.o *.d *.dwo -.*.swp *.gch *.gcda *.gcno -*~ -__pycache__ -/.cache -/.cproject -/.project -/.settings -/qtcreator.files -/qtcreator.includes -/qtcreator.config -/qtcreator.creator -/qtcreator.creator.user -/compile_commands.json -/coverage.info -/coverage_html -/Makefile.conf -/viz.js +*.so.dSYM/ + +# compiler output files +/kernel/version_*.cc +/share /yosys /yosys.exe /yosys.js @@ -36,22 +29,50 @@ __pycache__ /yosys-witness-script.py /yosys-filterlib /yosys-filterlib.exe -/kernel/*.pyh -/kernel/python_wrappers.cc -/kernel/version_*.cc -/share /yosys-win32-mxebin-* /yosys-win32-vcxsrc-* /yosysjs-* /libyosys.so + +# build directories /tests/unit/bintest/ /tests/unit/objtest/ /tests/ystests +/build /result /dist -/*.egg-info -/build -/venv + +# pyosys +/kernel/*.pyh +/kernel/python_wrappers.cc /boost /ffi +/venv /*.whl +/*.egg-info + +# yosysjs dependency +/viz.js + +# other +/coverage.info +/coverage_html + + +# these really belong in global gitignore since they're not specific to this project but rather to user tool choice +# but too many people don't have a global gitignore configured: +# https://docs.github.com/en/get-started/git-basics/ignoring-files#configuring-ignored-files-for-all-repositories-on-your-computer +__pycache__ +*~ +.*.swp +/.cache +/.vscode +/.cproject +/.project +/.settings +/qtcreator.files +/qtcreator.includes +/qtcreator.config +/qtcreator.creator +/qtcreator.creator.user +/compile_commands.json From 8b75c061418f4b2efed215e49418e933796b8d02 Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Tue, 22 Jul 2025 10:38:19 +0000 Subject: [PATCH 03/24] Add a general tests/.gitignore and remove redundant entries in subdirectory .gitignore files. --- techlibs/xilinx/tests/.gitignore | 1 - tests/{opt => }/.gitignore | 2 ++ tests/aiger/.gitignore | 1 - tests/arch/anlogic/.gitignore | 2 -- tests/arch/ecp5/.gitignore | 2 -- tests/arch/efinix/.gitignore | 3 --- tests/arch/fabulous/.gitignore | 2 -- tests/arch/gatemate/.gitignore | 2 -- tests/arch/gowin/.gitignore | 3 --- tests/arch/ice40/.gitignore | 2 -- tests/arch/intel_alm/.gitignore | 2 -- tests/arch/machxo2/.gitignore | 2 -- tests/arch/microchip/.gitignore | 2 -- tests/arch/nanoxplore/.gitignore | 2 -- tests/arch/nexus/.gitignore | 2 -- tests/arch/quicklogic/.gitignore | 2 -- tests/arch/xilinx/.gitignore | 2 -- tests/asicworld/.gitignore | 2 -- tests/bind/.gitignore | 2 -- tests/blif/.gitignore | 1 - tests/fmt/.gitignore | 1 - tests/hana/.gitignore | 2 -- tests/liberty/.gitignore | 1 - tests/lut/.gitignore | 1 - tests/memlib/.gitignore | 3 --- tests/memories/.gitignore | 2 -- tests/peepopt/.gitignore | 1 - tests/proc/.gitignore | 1 - tests/rpc/.gitignore | 1 - tests/sat/.gitignore | 2 -- tests/select/.gitignore | 1 - tests/sim/.gitignore | 3 --- tests/simple/.gitignore | 3 --- tests/simple_abc9/.gitignore | 2 -- tests/svtypes/.gitignore | 3 --- tests/techmap/.gitignore | 3 --- tests/various/.gitignore | 3 --- tests/verific/.gitignore | 3 --- tests/verilog/.gitignore | 4 ---- 39 files changed, 2 insertions(+), 77 deletions(-) rename tests/{opt => }/.gitignore (60%) delete mode 100644 tests/arch/ecp5/.gitignore delete mode 100644 tests/arch/efinix/.gitignore delete mode 100644 tests/arch/gowin/.gitignore delete mode 100644 tests/arch/intel_alm/.gitignore delete mode 100644 tests/arch/machxo2/.gitignore delete mode 100644 tests/arch/nanoxplore/.gitignore delete mode 100644 tests/arch/nexus/.gitignore delete mode 100644 tests/asicworld/.gitignore delete mode 100644 tests/bind/.gitignore delete mode 100644 tests/blif/.gitignore delete mode 100644 tests/hana/.gitignore delete mode 100644 tests/lut/.gitignore delete mode 100644 tests/peepopt/.gitignore delete mode 100644 tests/proc/.gitignore delete mode 100644 tests/rpc/.gitignore delete mode 100644 tests/select/.gitignore delete mode 100644 tests/simple/.gitignore delete mode 100644 tests/svtypes/.gitignore delete mode 100644 tests/techmap/.gitignore delete mode 100644 tests/verific/.gitignore diff --git a/techlibs/xilinx/tests/.gitignore b/techlibs/xilinx/tests/.gitignore index 0d9c28fde..7cb771190 100644 --- a/techlibs/xilinx/tests/.gitignore +++ b/techlibs/xilinx/tests/.gitignore @@ -1,7 +1,6 @@ bram1_cmp bram1.mk bram1_[0-9]*/ -bram2.log bram2_syn.v bram2_tb dsp_work*/ diff --git a/tests/opt/.gitignore b/tests/.gitignore similarity index 60% rename from tests/opt/.gitignore rename to tests/.gitignore index 8355de9dc..d8e01e026 100644 --- a/tests/opt/.gitignore +++ b/tests/.gitignore @@ -1,2 +1,4 @@ *.log +*.out +*.err run-test.mk diff --git a/tests/aiger/.gitignore b/tests/aiger/.gitignore index 54b4a279b..4bb3e67f6 100644 --- a/tests/aiger/.gitignore +++ b/tests/aiger/.gitignore @@ -1,3 +1,2 @@ /*_ref.v -/*.log /neg.out/ diff --git a/tests/arch/anlogic/.gitignore b/tests/arch/anlogic/.gitignore index 9a71dca69..d9f7e276c 100644 --- a/tests/arch/anlogic/.gitignore +++ b/tests/arch/anlogic/.gitignore @@ -1,4 +1,2 @@ -*.log -/run-test.mk +*_synth.v +*_testbench diff --git a/tests/arch/ecp5/.gitignore b/tests/arch/ecp5/.gitignore deleted file mode 100644 index 1d329c933..000000000 --- a/tests/arch/ecp5/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.log -/run-test.mk diff --git a/tests/arch/efinix/.gitignore b/tests/arch/efinix/.gitignore deleted file mode 100644 index b48f808a1..000000000 --- a/tests/arch/efinix/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/*.log -/*.out -/run-test.mk diff --git a/tests/arch/fabulous/.gitignore b/tests/arch/fabulous/.gitignore index 9a71dca69..d9f7e276c 100644 --- a/tests/arch/fabulous/.gitignore +++ b/tests/arch/fabulous/.gitignore @@ -1,4 +1,2 @@ -*.log -/run-test.mk +*_synth.v +*_testbench diff --git a/tests/arch/gatemate/.gitignore b/tests/arch/gatemate/.gitignore index 9a71dca69..d9f7e276c 100644 --- a/tests/arch/gatemate/.gitignore +++ b/tests/arch/gatemate/.gitignore @@ -1,4 +1,2 @@ -*.log -/run-test.mk +*_synth.v +*_testbench diff --git a/tests/arch/gowin/.gitignore b/tests/arch/gowin/.gitignore deleted file mode 100644 index b48f808a1..000000000 --- a/tests/arch/gowin/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/*.log -/*.out -/run-test.mk diff --git a/tests/arch/ice40/.gitignore b/tests/arch/ice40/.gitignore index 54f908bdb..a86b36792 100644 --- a/tests/arch/ice40/.gitignore +++ b/tests/arch/ice40/.gitignore @@ -1,5 +1,3 @@ -*.log *.json -/run-test.mk +*_synth.v +*_testbench diff --git a/tests/arch/intel_alm/.gitignore b/tests/arch/intel_alm/.gitignore deleted file mode 100644 index ba42e1ee6..000000000 --- a/tests/arch/intel_alm/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/*.log -/run-test.mk diff --git a/tests/arch/machxo2/.gitignore b/tests/arch/machxo2/.gitignore deleted file mode 100644 index 1d329c933..000000000 --- a/tests/arch/machxo2/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.log -/run-test.mk diff --git a/tests/arch/microchip/.gitignore b/tests/arch/microchip/.gitignore index 9c0a77944..aae26dc02 100644 --- a/tests/arch/microchip/.gitignore +++ b/tests/arch/microchip/.gitignore @@ -1,4 +1,2 @@ -*.log -/run-test.mk *.vm diff --git a/tests/arch/nanoxplore/.gitignore b/tests/arch/nanoxplore/.gitignore deleted file mode 100644 index 1d329c933..000000000 --- a/tests/arch/nanoxplore/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.log -/run-test.mk diff --git a/tests/arch/nexus/.gitignore b/tests/arch/nexus/.gitignore deleted file mode 100644 index ba42e1ee6..000000000 --- a/tests/arch/nexus/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/*.log -/run-test.mk diff --git a/tests/arch/quicklogic/.gitignore b/tests/arch/quicklogic/.gitignore index ae20ed342..d9f7e276c 100644 --- a/tests/arch/quicklogic/.gitignore +++ b/tests/arch/quicklogic/.gitignore @@ -1,4 +1,2 @@ -*.log -run-test.mk +*_synth.v +*_testbench diff --git a/tests/arch/xilinx/.gitignore b/tests/arch/xilinx/.gitignore index c99b79371..15a12d049 100644 --- a/tests/arch/xilinx/.gitignore +++ b/tests/arch/xilinx/.gitignore @@ -1,5 +1,3 @@ -/*.log -/*.out /run-test.mk /*_uut.v /test_macc diff --git a/tests/asicworld/.gitignore b/tests/asicworld/.gitignore deleted file mode 100644 index 073f46157..000000000 --- a/tests/asicworld/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.log -*.out diff --git a/tests/bind/.gitignore b/tests/bind/.gitignore deleted file mode 100644 index 8355de9dc..000000000 --- a/tests/bind/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.log -run-test.mk diff --git a/tests/blif/.gitignore b/tests/blif/.gitignore deleted file mode 100644 index 397b4a762..000000000 --- a/tests/blif/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.log diff --git a/tests/fmt/.gitignore b/tests/fmt/.gitignore index a36a15ec4..e6ae0d879 100644 --- a/tests/fmt/.gitignore +++ b/tests/fmt/.gitignore @@ -1,3 +1,2 @@ -*.log iverilog-* yosys-* diff --git a/tests/hana/.gitignore b/tests/hana/.gitignore deleted file mode 100644 index 073f46157..000000000 --- a/tests/hana/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.log -*.out diff --git a/tests/liberty/.gitignore b/tests/liberty/.gitignore index 2ee56e9d1..b312f8c50 100644 --- a/tests/liberty/.gitignore +++ b/tests/liberty/.gitignore @@ -1,3 +1,2 @@ -*.log /*.filtered *.verilogsim diff --git a/tests/lut/.gitignore b/tests/lut/.gitignore deleted file mode 100644 index 397b4a762..000000000 --- a/tests/lut/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.log diff --git a/tests/memlib/.gitignore b/tests/memlib/.gitignore index 03dbe82ae..bfa1a801e 100644 --- a/tests/memlib/.gitignore +++ b/tests/memlib/.gitignore @@ -1,5 +1,2 @@ -t_*.log -t_*.out t_*.v t_*.ys -run-test.mk diff --git a/tests/memories/.gitignore b/tests/memories/.gitignore index 90a0983a6..14e98cead 100644 --- a/tests/memories/.gitignore +++ b/tests/memories/.gitignore @@ -1,3 +1 @@ -*.log -*.out *.dmp diff --git a/tests/peepopt/.gitignore b/tests/peepopt/.gitignore deleted file mode 100644 index 50e13221d..000000000 --- a/tests/peepopt/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/*.log diff --git a/tests/proc/.gitignore b/tests/proc/.gitignore deleted file mode 100644 index 397b4a762..000000000 --- a/tests/proc/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.log diff --git a/tests/rpc/.gitignore b/tests/rpc/.gitignore deleted file mode 100644 index 397b4a762..000000000 --- a/tests/rpc/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.log diff --git a/tests/sat/.gitignore b/tests/sat/.gitignore index 664425d73..3fa128fcc 100644 --- a/tests/sat/.gitignore +++ b/tests/sat/.gitignore @@ -1,4 +1,2 @@ -*.log -run-test.mk *.vcd *.fst diff --git a/tests/select/.gitignore b/tests/select/.gitignore deleted file mode 100644 index 50e13221d..000000000 --- a/tests/select/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/*.log diff --git a/tests/sim/.gitignore b/tests/sim/.gitignore index 2c96b65f8..769b314cd 100644 --- a/tests/sim/.gitignore +++ b/tests/sim/.gitignore @@ -1,6 +1,3 @@ -*.log -/run-test.mk +*_synth.v +*_testbench -*.out *.fst diff --git a/tests/simple/.gitignore b/tests/simple/.gitignore deleted file mode 100644 index 5daaadbd7..000000000 --- a/tests/simple/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.log -*.out -*.err diff --git a/tests/simple_abc9/.gitignore b/tests/simple_abc9/.gitignore index fda60e577..e31b7dc7f 100644 --- a/tests/simple_abc9/.gitignore +++ b/tests/simple_abc9/.gitignore @@ -1,5 +1,3 @@ *.v *.sv -*.log -*.out *.bak diff --git a/tests/svtypes/.gitignore b/tests/svtypes/.gitignore deleted file mode 100644 index b48f808a1..000000000 --- a/tests/svtypes/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/*.log -/*.out -/run-test.mk diff --git a/tests/techmap/.gitignore b/tests/techmap/.gitignore deleted file mode 100644 index 56c9ba8f9..000000000 --- a/tests/techmap/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.log -*.out -/*.mk diff --git a/tests/various/.gitignore b/tests/various/.gitignore index 3dbd50843..879645f66 100644 --- a/tests/various/.gitignore +++ b/tests/various/.gitignore @@ -1,9 +1,6 @@ -/*.log -/*.out /*.sel /write_gzip.v /write_gzip.v.gz -/run-test.mk /plugin.so /plugin.so.dSYM /temp diff --git a/tests/verific/.gitignore b/tests/verific/.gitignore deleted file mode 100644 index b48f808a1..000000000 --- a/tests/verific/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/*.log -/*.out -/run-test.mk diff --git a/tests/verilog/.gitignore b/tests/verilog/.gitignore index cfd72076e..3702f10cf 100644 --- a/tests/verilog/.gitignore +++ b/tests/verilog/.gitignore @@ -1,7 +1,3 @@ -/*.log -/*.out -/*.err -/run-test.mk /const_arst.v /const_sr.v /doubleslash.v From 0314db80eafcb81be0d04c5b92986bf748b020bf Mon Sep 17 00:00:00 2001 From: Mike Inouye Date: Fri, 25 Jul 2025 19:15:01 +0000 Subject: [PATCH 04/24] Correctly reset Verific flags to Yosys defaults after -import and warn this has occurred. Co-authored-by: Chris Pearce Signed-off-by: Mike Inouye --- frontends/verific/verific.cc | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/frontends/verific/verific.cc b/frontends/verific/verific.cc index 241b2db30..cf8fad8b0 100644 --- a/frontends/verific/verific.cc +++ b/frontends/verific/verific.cc @@ -2978,6 +2978,9 @@ std::set import_tops(const char* work, std::map args, RTLIL::Design *design) override { - static bool set_verific_global_flags = true; if (check_noverific_env()) log_cmd_error("This version of Yosys is built without Verific support.\n" @@ -4127,6 +4130,9 @@ struct VerificPass : public Pass { if ((unsigned long)verific_sva_fsm_limit >= sizeof(1ull)*8) log_cmd_error("-L %d: limit too large; maximum allowed value is %zu.\n", verific_sva_fsm_limit, sizeof(1ull)*8-1); + if (already_imported) + log_warning("Note that all Verific flags were reset to defaults after last -import.\n"); + std::set top_mod_names; if (mode_all) @@ -4204,6 +4210,7 @@ struct VerificPass : public Pass { } verific_cleanup(); + already_imported = true; goto check_error; } From 902cbda4f92f3bfae19bd32b647a9cee6c6bf991 Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Tue, 29 Jul 2025 11:39:49 +1200 Subject: [PATCH 05/24] bugpoint: Document -wires flag --- passes/cmds/bugpoint.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/passes/cmds/bugpoint.cc b/passes/cmds/bugpoint.cc index da06acedf..e1f3544b4 100644 --- a/passes/cmds/bugpoint.cc +++ b/passes/cmds/bugpoint.cc @@ -89,6 +89,10 @@ struct BugpointPass : public Pass { log(" -updates\n"); log(" try to remove process updates from syncs.\n"); log("\n"); + log(" -wires\n"); + log(" try to remove wires. wires with a (* bugpoint_keep *) attribute will be\n"); + log(" skipped.\n"); + log("\n"); log(" -runner \"\"\n"); log(" child process wrapping command, e.g., \"timeout 30\", or valgrind.\n"); log("\n"); From 8a732080e02e765f97c1a1dd8f4f3eff619f3a77 Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Tue, 29 Jul 2025 11:39:50 +1200 Subject: [PATCH 06/24] bugpoint: Add -expect-return Allows checking return value from crashing design. Makes it possible to only accept designs that crash with e.g. SEGFAULT. Based on `exec -expect-return`. --- passes/cmds/bugpoint.cc | 62 +++++++++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/passes/cmds/bugpoint.cc b/passes/cmds/bugpoint.cc index e1f3544b4..24c778fdb 100644 --- a/passes/cmds/bugpoint.cc +++ b/passes/cmds/bugpoint.cc @@ -50,6 +50,10 @@ struct BugpointPass : public Pass { log(" -grep \"\"\n"); log(" only consider crashes that place this string in the log file.\n"); log("\n"); + log(" -expect-return \n"); + log(" only consider crashes that return the specified value. e.g. SEGFAULT\n"); + log(" returns a value of 139.\n"); + log("\n"); log(" -fast\n"); log(" run `proc_clean; clean -purge` after each minimization step. converges\n"); log(" faster, but produces larger testcases, and may fail to produce any\n"); @@ -98,7 +102,7 @@ struct BugpointPass : public Pass { log("\n"); } - bool run_yosys(RTLIL::Design *design, string runner, string yosys_cmd, string yosys_arg) + int run_yosys(RTLIL::Design *design, string runner, string yosys_cmd, string yosys_arg) { design->sort(); @@ -107,7 +111,16 @@ struct BugpointPass : public Pass { f.close(); string yosys_cmdline = stringf("%s %s -qq -L bugpoint-case.log %s bugpoint-case.il", runner.c_str(), yosys_cmd.c_str(), yosys_arg.c_str()); - return run_command(yosys_cmdline) == 0; + auto status = run_command(yosys_cmdline); + // we're not processing lines, which means we're getting raw system() returns + if(WIFEXITED(status)) + return WEXITSTATUS(status); + else if(WIFSIGNALED(status)) + return WTERMSIG(status); + else if(WIFSTOPPED(status)) + return WSTOPSIG(status); + else + return 0; } bool check_logfile(string grep) @@ -404,6 +417,8 @@ struct BugpointPass : public Pass { void execute(std::vector args, RTLIL::Design *design) override { string yosys_cmd = "yosys", yosys_arg, grep, runner; + bool flag_expect_return = false, has_check = false; + int expect_return_value = 0; bool fast = false, clean = false; bool modules = false, ports = false, cells = false, connections = false, processes = false, assigns = false, updates = false, wires = false, has_part = false; @@ -430,9 +445,19 @@ struct BugpointPass : public Pass { continue; } if (args[argidx] == "-grep" && argidx + 1 < args.size()) { + has_check = true; grep = args[++argidx]; continue; } + if (args[argidx] == "-expect-return") { + flag_expect_return = true; + ++argidx; + if (argidx >= args.size()) + log_cmd_error("No expected return value specified.\n"); + + expect_return_value = atoi(args[argidx].c_str()); + continue; + } if (args[argidx] == "-fast") { fast = true; continue; @@ -496,6 +521,9 @@ struct BugpointPass : public Pass { if (yosys_arg.empty()) log_cmd_error("Missing -script or -command option.\n"); + if (flag_expect_return && expect_return_value == 0 && !has_check) + log_cmd_error("Nothing to match on for -expect-return 0; change value or use -grep.\n"); + if (!has_part) { modules = true; @@ -512,7 +540,10 @@ struct BugpointPass : public Pass { log_cmd_error("This command only operates on fully selected designs!\n"); RTLIL::Design *crashing_design = clean_design(design, clean); - if (run_yosys(crashing_design, runner, yosys_cmd, yosys_arg)) + int retval = run_yosys(crashing_design, runner, yosys_cmd, yosys_arg); + if (flag_expect_return && retval != expect_return_value) + log_cmd_error("The provided script file or command and Yosys binary returned value %d instead of expected %d on this design!\n", retval, expect_return_value); + if (!flag_expect_return && retval == 0) log_cmd_error("The provided script file or command and Yosys binary do not crash on this design!\n"); if (!check_logfile(grep)) log_cmd_error("The provided grep string is not found in the log file!\n"); @@ -525,21 +556,37 @@ struct BugpointPass : public Pass { { simplified = clean_design(simplified, fast, /*do_delete=*/true); - bool crashes; if (clean) { RTLIL::Design *testcase = clean_design(simplified); - crashes = !run_yosys(testcase, runner, yosys_cmd, yosys_arg); + retval = run_yosys(testcase, runner, yosys_cmd, yosys_arg); delete testcase; } else { - crashes = !run_yosys(simplified, runner, yosys_cmd, yosys_arg); + retval = run_yosys(simplified, runner, yosys_cmd, yosys_arg); } - if (crashes && check_logfile(grep)) + bool crashes = false; + if (flag_expect_return && retval == expect_return_value && check_logfile(grep)) + { + log("Testcase matches expected crash.\n"); + crashes = true; + } + else if (!flag_expect_return && retval == 0) + log("Testcase does not crash.\n"); + else if (!flag_expect_return && check_logfile(grep)) { log("Testcase crashes.\n"); + crashes = true; + } + else + // flag_expect_return && !(retval == expect_return_value && check_logfile(grep)) + // !flag_expect_return && !(retval == 0 && check_logfile(grep)) + log("Testcase does not match expected crash.\n"); + + if (crashes) + { if (crashing_design != design) delete crashing_design; crashing_design = simplified; @@ -547,7 +594,6 @@ struct BugpointPass : public Pass { } else { - log("Testcase does not crash.\n"); delete simplified; seed++; } From a7a926b24701faeb62e1e7e0b35bff5679def157 Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Tue, 29 Jul 2025 11:39:50 +1200 Subject: [PATCH 07/24] bugpoint.cc: WIN32 exit signals --- passes/cmds/bugpoint.cc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/passes/cmds/bugpoint.cc b/passes/cmds/bugpoint.cc index 24c778fdb..06aae1885 100644 --- a/passes/cmds/bugpoint.cc +++ b/passes/cmds/bugpoint.cc @@ -20,6 +20,15 @@ #include "kernel/yosys.h" #include "backends/rtlil/rtlil_backend.h" +#if defined(_WIN32) +# define WIFEXITED(x) 1 +# define WIFSIGNALED(x) 0 +# define WIFSTOPPED(x) 0 +# define WEXITSTATUS(x) ((x) & 0xff) +# define WTERMSIG(x) SIGTERM +# define WSTOPSIG(x) 0 +#endif + USING_YOSYS_NAMESPACE using namespace RTLIL_BACKEND; PRIVATE_NAMESPACE_BEGIN From 134da811f7f7c7688c3871ad93f7b480cc8c1274 Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Tue, 29 Jul 2025 11:39:50 +1200 Subject: [PATCH 08/24] Add raise_error pass Raise errors from attributes for testing. I want it for bugpoint tests but it could be useful elsewhere. --- kernel/constids.inc | 1 + passes/tests/Makefile.inc | 1 + passes/tests/raise_error.cc | 61 +++++++++++++++++++++++++++++++++++++ tests/bugpoint/.gitignore | 2 ++ tests/bugpoint/err.ys | 27 ++++++++++++++++ 5 files changed, 92 insertions(+) create mode 100644 passes/tests/raise_error.cc create mode 100644 tests/bugpoint/.gitignore create mode 100644 tests/bugpoint/err.ys diff --git a/kernel/constids.inc b/kernel/constids.inc index c77a25c01..29872d45e 100644 --- a/kernel/constids.inc +++ b/kernel/constids.inc @@ -286,3 +286,4 @@ X(A_WIDTHS) X(B_WIDTHS) X(C_WIDTHS) X(C_SIGNED) +X(raise_error) diff --git a/passes/tests/Makefile.inc b/passes/tests/Makefile.inc index 531943d78..25e11967c 100644 --- a/passes/tests/Makefile.inc +++ b/passes/tests/Makefile.inc @@ -2,4 +2,5 @@ OBJS += passes/tests/test_autotb.o OBJS += passes/tests/test_cell.o OBJS += passes/tests/test_abcloop.o +OBJS += passes/tests/raise_error.o diff --git a/passes/tests/raise_error.cc b/passes/tests/raise_error.cc new file mode 100644 index 000000000..edc42de65 --- /dev/null +++ b/passes/tests/raise_error.cc @@ -0,0 +1,61 @@ +#include "kernel/yosys.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct RaiseErrorPass : public Pass { + RaiseErrorPass() : Pass("raise_error", "raise errors from attributes") { } + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" raise_error [selection]\n"); + log("\n"); + log("Test error handling by raising arbitrary errors. This pass iterates over the\n"); + log("design (or selection of it) checking for objects with the 'raise_error'\n"); + log("attribute set.\n"); + log("\n"); + } + void execute(vector args, RTLIL::Design *design) override + { + log_header(design, "Executing RAISE_ERROR pass.\n"); + + extra_args(args, 1, design, true); + + RTLIL::NamedObject *err_obj = nullptr; + + for (auto mod : design->all_selected_modules()) { + if (mod->has_attribute(ID::raise_error)) { + err_obj = mod->clone(); + break; + } + for (auto memb : mod->selected_members()) { + if (memb->has_attribute(ID::raise_error)) { + err_obj = memb; + break; + } + } + if (err_obj != nullptr) break; + } + + if (err_obj != nullptr) { + log("Raising error from '%s'.\n", log_id(err_obj)); + auto err_no = err_obj->attributes[ID::raise_error].as_int(); + if (err_no < 256) { + log_flush(); + #if defined(_MSC_VER) + _exit(err_no); + #else + _Exit(err_no); + #endif + } else { + auto err_msg = err_obj->get_string_attribute(ID::raise_error); + log_error("%s\n", err_msg.c_str()); + } + } else { + log("'raise_error' attribute not found\n"); + } + } +} RaiseErrorPass; + +PRIVATE_NAMESPACE_END diff --git a/tests/bugpoint/.gitignore b/tests/bugpoint/.gitignore new file mode 100644 index 000000000..072ed1097 --- /dev/null +++ b/tests/bugpoint/.gitignore @@ -0,0 +1,2 @@ +*.il +*.log \ No newline at end of file diff --git a/tests/bugpoint/err.ys b/tests/bugpoint/err.ys new file mode 100644 index 000000000..d6a152144 --- /dev/null +++ b/tests/bugpoint/err.ys @@ -0,0 +1,27 @@ +read_verilog -noblackbox << EOF +(* raise_error=7 *) +module top(); +endmodule + +(* raise_error="help me" *) +module other(); +endmodule + +module zzy(); +endmodule +EOF +select -assert-mod-count 3 =* +design -stash read + +# raise_error with int exits with status +design -load read +bugpoint -yosys ../../yosys -command raise_error -expect-return 7 +select -assert-mod-count 1 =* +select -assert-mod-count 1 top + +# raise_error with string prints message +design -load read +rename top abc +bugpoint -yosys ../../yosys -command raise_error -grep "help me" +select -assert-mod-count 1 =* +select -assert-mod-count 1 other From 8d5dbae06e33fede98fd3041f2fdf3b04137bddc Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Tue, 29 Jul 2025 11:39:50 +1200 Subject: [PATCH 09/24] raise_error.cc: Option for direct to stderr Add more to help text to describe usage. Add test for no value (should `exit(1)`). --- passes/tests/raise_error.cc | 40 ++++++++++++++++++++++++++++--------- tests/bugpoint/err.ys | 27 ++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 10 deletions(-) diff --git a/passes/tests/raise_error.cc b/passes/tests/raise_error.cc index edc42de65..f9055e6d2 100644 --- a/passes/tests/raise_error.cc +++ b/passes/tests/raise_error.cc @@ -9,18 +9,35 @@ struct RaiseErrorPass : public Pass { { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); - log(" raise_error [selection]\n"); + log(" raise_error [options] [selection]\n"); log("\n"); log("Test error handling by raising arbitrary errors. This pass iterates over the\n"); log("design (or selection of it) checking for objects with the 'raise_error'\n"); - log("attribute set.\n"); + log("attribute set. Assigning 'raise_error' to a string more than one character long\n"); + log("will log that string as an error message before exiting. Assigning 'raise_error'\n"); + log("to an integer (less than 256) will exit with that value as the exit code.\n"); + log("\n"); + log(" -stderr\n"); + log(" Log error messages directly to stderr instead of using 'log_error'.\n"); log("\n"); } void execute(vector args, RTLIL::Design *design) override { log_header(design, "Executing RAISE_ERROR pass.\n"); - extra_args(args, 1, design, true); + bool use_stderr = false; + + int argidx; + for (argidx = 1; argidx < GetSize(args); argidx++) + { + if (args[argidx] == "-stderr") { + use_stderr = true; + continue; + } + break; + } + + extra_args(args, argidx, design, true); RTLIL::NamedObject *err_obj = nullptr; @@ -43,15 +60,20 @@ struct RaiseErrorPass : public Pass { auto err_no = err_obj->attributes[ID::raise_error].as_int(); if (err_no < 256) { log_flush(); - #if defined(_MSC_VER) - _exit(err_no); - #else - _Exit(err_no); - #endif } else { auto err_msg = err_obj->get_string_attribute(ID::raise_error); - log_error("%s\n", err_msg.c_str()); + if (use_stderr) { + std::cerr << err_msg << std::endl; + err_no = 1; + } else { + log_error("%s\n", err_msg.c_str()); + } } + #if defined(_MSC_VER) + _exit(err_no); + #else + _Exit(err_no); + #endif } else { log("'raise_error' attribute not found\n"); } diff --git a/tests/bugpoint/err.ys b/tests/bugpoint/err.ys index d6a152144..7b2fbcc81 100644 --- a/tests/bugpoint/err.ys +++ b/tests/bugpoint/err.ys @@ -7,7 +7,8 @@ endmodule module other(); endmodule -module zzy(); +(* raise_error *) +module def(); endmodule EOF select -assert-mod-count 3 =* @@ -25,3 +26,27 @@ rename top abc bugpoint -yosys ../../yosys -command raise_error -grep "help me" select -assert-mod-count 1 =* select -assert-mod-count 1 other + +# raise_error with no value exits with 1 +design -load read +rename def zzy +bugpoint -yosys ../../yosys -command raise_error -expect-return 1 +select -assert-mod-count 1 =* +select -assert-mod-count 1 zzy + +# raise_error -stderr exits with 1 +design -load read +rename top abc +delete def +bugpoint -yosys ../../yosys -command "raise_error -stderr" -expect-return 1 +select -assert-mod-count 1 =* +select -assert-mod-count 1 other + +#TODO +# raise_error -stderr prints to stderr +design -load read +rename top abc +delete def +# bugpoint -yosys ../../yosys -command "raise_error -stderr" -grep "help me" +# select -assert-mod-count 1 =* +# select -assert-mod-count 1 other From fb92eabdcd7d55ed371f49ffdf53a88bc05e69fe Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Tue, 29 Jul 2025 11:39:51 +1200 Subject: [PATCH 10/24] bugpoint: Add -greperr option `-greperr ` redirects stderr to 'bugpoint-case.err', and then searches that file for ``. Move `-runner` option up with the other options to reduce ambiguity (i.e. so it doesn't look like it's another design parts constraint). Also some shuffling of `err.ys`. --- passes/cmds/bugpoint.cc | 53 ++++++++++++++++++++++++++++----------- tests/bugpoint/.gitignore | 3 ++- tests/bugpoint/err.ys | 19 ++++---------- 3 files changed, 45 insertions(+), 30 deletions(-) diff --git a/passes/cmds/bugpoint.cc b/passes/cmds/bugpoint.cc index 06aae1885..11f6de35b 100644 --- a/passes/cmds/bugpoint.cc +++ b/passes/cmds/bugpoint.cc @@ -73,6 +73,13 @@ struct BugpointPass : public Pass { log(" finishing. produces smaller and more useful testcases, but may fail to\n"); log(" produce any testcase at all if the crash is related to dangling wires.\n"); log("\n"); + log(" -runner \"\"\n"); + log(" child process wrapping command, e.g., \"timeout 30\", or valgrind.\n"); + log("\n"); + log(" -greperr \"\"\n"); + log(" only consider crashes that print this string on stderr. useful for\n"); + log(" errors outside of yosys.\n"); + log("\n"); log("It is possible to constrain which parts of the design will be considered for\n"); log("removal. Unless one or more of the following options are specified, all parts\n"); log("will be considered.\n"); @@ -106,12 +113,9 @@ struct BugpointPass : public Pass { log(" try to remove wires. wires with a (* bugpoint_keep *) attribute will be\n"); log(" skipped.\n"); log("\n"); - log(" -runner \"\"\n"); - log(" child process wrapping command, e.g., \"timeout 30\", or valgrind.\n"); - log("\n"); } - int run_yosys(RTLIL::Design *design, string runner, string yosys_cmd, string yosys_arg) + int run_yosys(RTLIL::Design *design, string runner, string yosys_cmd, string yosys_arg, bool catch_err) { design->sort(); @@ -120,6 +124,7 @@ struct BugpointPass : public Pass { f.close(); string yosys_cmdline = stringf("%s %s -qq -L bugpoint-case.log %s bugpoint-case.il", runner.c_str(), yosys_cmd.c_str(), yosys_arg.c_str()); + if (catch_err) yosys_cmdline += " 2>bugpoint-case.err"; auto status = run_command(yosys_cmdline); // we're not processing lines, which means we're getting raw system() returns if(WIFEXITED(status)) @@ -132,7 +137,7 @@ struct BugpointPass : public Pass { return 0; } - bool check_logfile(string grep) + bool check_logfile(string grep, bool err=false) { if (grep.empty()) return true; @@ -140,7 +145,12 @@ struct BugpointPass : public Pass { if (grep.size() > 2 && grep.front() == '"' && grep.back() == '"') grep = grep.substr(1, grep.size() - 2); - std::ifstream f("bugpoint-case.log"); + std::ifstream f; + if (err) + f = std::ifstream("bugpoint-case.err"); + else + f = std::ifstream("bugpoint-case.log"); + while (!f.eof()) { string line; @@ -151,6 +161,11 @@ struct BugpointPass : public Pass { return false; } + bool check_logfiles(string grep, string greperr) + { + return check_logfile(grep) && check_logfile(greperr, true); + } + RTLIL::Design *clean_design(RTLIL::Design *design, bool do_clean = true, bool do_delete = false) { if (!do_clean) @@ -425,8 +440,8 @@ struct BugpointPass : public Pass { void execute(std::vector args, RTLIL::Design *design) override { - string yosys_cmd = "yosys", yosys_arg, grep, runner; - bool flag_expect_return = false, has_check = false; + string yosys_cmd = "yosys", yosys_arg, grep, greperr, runner; + bool flag_expect_return = false, has_check = false, check_err = false; int expect_return_value = 0; bool fast = false, clean = false; bool modules = false, ports = false, cells = false, connections = false, processes = false, assigns = false, updates = false, wires = false, has_part = false; @@ -458,6 +473,12 @@ struct BugpointPass : public Pass { grep = args[++argidx]; continue; } + if (args[argidx] == "-greperr" && argidx + 1 < args.size()) { + has_check = true; + check_err = true; + greperr = args[++argidx]; + continue; + } if (args[argidx] == "-expect-return") { flag_expect_return = true; ++argidx; @@ -549,13 +570,15 @@ struct BugpointPass : public Pass { log_cmd_error("This command only operates on fully selected designs!\n"); RTLIL::Design *crashing_design = clean_design(design, clean); - int retval = run_yosys(crashing_design, runner, yosys_cmd, yosys_arg); + int retval = run_yosys(crashing_design, runner, yosys_cmd, yosys_arg, check_err); if (flag_expect_return && retval != expect_return_value) log_cmd_error("The provided script file or command and Yosys binary returned value %d instead of expected %d on this design!\n", retval, expect_return_value); if (!flag_expect_return && retval == 0) log_cmd_error("The provided script file or command and Yosys binary do not crash on this design!\n"); if (!check_logfile(grep)) log_cmd_error("The provided grep string is not found in the log file!\n"); + if (!check_logfile(greperr, true)) + log_cmd_error("The provided grep string is not found in stderr log!\n"); int seed = 0; bool found_something = false, stage2 = false; @@ -568,30 +591,30 @@ struct BugpointPass : public Pass { if (clean) { RTLIL::Design *testcase = clean_design(simplified); - retval = run_yosys(testcase, runner, yosys_cmd, yosys_arg); + retval = run_yosys(testcase, runner, yosys_cmd, yosys_arg, check_err); delete testcase; } else { - retval = run_yosys(simplified, runner, yosys_cmd, yosys_arg); + retval = run_yosys(simplified, runner, yosys_cmd, yosys_arg, check_err); } bool crashes = false; - if (flag_expect_return && retval == expect_return_value && check_logfile(grep)) + if (flag_expect_return && retval == expect_return_value && check_logfiles(grep, greperr)) { log("Testcase matches expected crash.\n"); crashes = true; } else if (!flag_expect_return && retval == 0) log("Testcase does not crash.\n"); - else if (!flag_expect_return && check_logfile(grep)) + else if (!flag_expect_return && check_logfiles(grep, greperr)) { log("Testcase crashes.\n"); crashes = true; } else - // flag_expect_return && !(retval == expect_return_value && check_logfile(grep)) - // !flag_expect_return && !(retval == 0 && check_logfile(grep)) + // flag_expect_return && !(retval == expect_return_value && check_logfiles(grep, greperr)) + // !flag_expect_return && !(retval == 0 && check_logfiles(grep, greperr)) log("Testcase does not match expected crash.\n"); if (crashes) diff --git a/tests/bugpoint/.gitignore b/tests/bugpoint/.gitignore index 072ed1097..818575593 100644 --- a/tests/bugpoint/.gitignore +++ b/tests/bugpoint/.gitignore @@ -1,2 +1,3 @@ *.il -*.log \ No newline at end of file +*.log +*.err diff --git a/tests/bugpoint/err.ys b/tests/bugpoint/err.ys index 7b2fbcc81..c74e34b19 100644 --- a/tests/bugpoint/err.ys +++ b/tests/bugpoint/err.ys @@ -20,33 +20,24 @@ bugpoint -yosys ../../yosys -command raise_error -expect-return 7 select -assert-mod-count 1 =* select -assert-mod-count 1 top -# raise_error with string prints message +# raise_error with string prints message and exits with 1 design -load read rename top abc -bugpoint -yosys ../../yosys -command raise_error -grep "help me" +bugpoint -yosys ../../yosys -command raise_error -grep "help me" -expect-return 1 select -assert-mod-count 1 =* select -assert-mod-count 1 other # raise_error with no value exits with 1 design -load read rename def zzy +delete other bugpoint -yosys ../../yosys -command raise_error -expect-return 1 select -assert-mod-count 1 =* select -assert-mod-count 1 zzy -# raise_error -stderr exits with 1 +# raise_error -stderr prints to stderr and exits with 1 design -load read rename top abc -delete def -bugpoint -yosys ../../yosys -command "raise_error -stderr" -expect-return 1 +bugpoint -yosys ../../yosys -command "raise_error -stderr" -greperr "help me" -expect-return 1 select -assert-mod-count 1 =* select -assert-mod-count 1 other - -#TODO -# raise_error -stderr prints to stderr -design -load read -rename top abc -delete def -# bugpoint -yosys ../../yosys -command "raise_error -stderr" -grep "help me" -# select -assert-mod-count 1 =* -# select -assert-mod-count 1 other From 65147670a67e759d7e08c5bc4fb14520b5221398 Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Tue, 29 Jul 2025 11:39:51 +1200 Subject: [PATCH 11/24] bugpoint.cc: Include csignal for windows --- passes/cmds/bugpoint.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/passes/cmds/bugpoint.cc b/passes/cmds/bugpoint.cc index 11f6de35b..101d82bd6 100644 --- a/passes/cmds/bugpoint.cc +++ b/passes/cmds/bugpoint.cc @@ -21,6 +21,7 @@ #include "backends/rtlil/rtlil_backend.h" #if defined(_WIN32) +# include # define WIFEXITED(x) 1 # define WIFSIGNALED(x) 0 # define WIFSTOPPED(x) 0 From b5a13ae95b9b9350d726e1ec2ad5eec9cc544b0c Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Tue, 29 Jul 2025 11:39:51 +1200 Subject: [PATCH 12/24] bugpoint.cc: Rename to -err_grep --- passes/cmds/bugpoint.cc | 22 +++++++++++----------- tests/bugpoint/err.ys | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/passes/cmds/bugpoint.cc b/passes/cmds/bugpoint.cc index 101d82bd6..f740c2bcd 100644 --- a/passes/cmds/bugpoint.cc +++ b/passes/cmds/bugpoint.cc @@ -77,7 +77,7 @@ struct BugpointPass : public Pass { log(" -runner \"\"\n"); log(" child process wrapping command, e.g., \"timeout 30\", or valgrind.\n"); log("\n"); - log(" -greperr \"\"\n"); + log(" -err_grep \"\"\n"); log(" only consider crashes that print this string on stderr. useful for\n"); log(" errors outside of yosys.\n"); log("\n"); @@ -162,9 +162,9 @@ struct BugpointPass : public Pass { return false; } - bool check_logfiles(string grep, string greperr) + bool check_logfiles(string grep, string err_grep) { - return check_logfile(grep) && check_logfile(greperr, true); + return check_logfile(grep) && check_logfile(err_grep, true); } RTLIL::Design *clean_design(RTLIL::Design *design, bool do_clean = true, bool do_delete = false) @@ -441,7 +441,7 @@ struct BugpointPass : public Pass { void execute(std::vector args, RTLIL::Design *design) override { - string yosys_cmd = "yosys", yosys_arg, grep, greperr, runner; + string yosys_cmd = "yosys", yosys_arg, grep, err_grep, runner; bool flag_expect_return = false, has_check = false, check_err = false; int expect_return_value = 0; bool fast = false, clean = false; @@ -474,10 +474,10 @@ struct BugpointPass : public Pass { grep = args[++argidx]; continue; } - if (args[argidx] == "-greperr" && argidx + 1 < args.size()) { + if (args[argidx] == "-err_grep" && argidx + 1 < args.size()) { has_check = true; check_err = true; - greperr = args[++argidx]; + err_grep = args[++argidx]; continue; } if (args[argidx] == "-expect-return") { @@ -578,7 +578,7 @@ struct BugpointPass : public Pass { log_cmd_error("The provided script file or command and Yosys binary do not crash on this design!\n"); if (!check_logfile(grep)) log_cmd_error("The provided grep string is not found in the log file!\n"); - if (!check_logfile(greperr, true)) + if (!check_logfile(err_grep, true)) log_cmd_error("The provided grep string is not found in stderr log!\n"); int seed = 0; @@ -601,21 +601,21 @@ struct BugpointPass : public Pass { } bool crashes = false; - if (flag_expect_return && retval == expect_return_value && check_logfiles(grep, greperr)) + if (flag_expect_return && retval == expect_return_value && check_logfiles(grep, err_grep)) { log("Testcase matches expected crash.\n"); crashes = true; } else if (!flag_expect_return && retval == 0) log("Testcase does not crash.\n"); - else if (!flag_expect_return && check_logfiles(grep, greperr)) + else if (!flag_expect_return && check_logfiles(grep, err_grep)) { log("Testcase crashes.\n"); crashes = true; } else - // flag_expect_return && !(retval == expect_return_value && check_logfiles(grep, greperr)) - // !flag_expect_return && !(retval == 0 && check_logfiles(grep, greperr)) + // flag_expect_return && !(retval == expect_return_value && check_logfiles(grep, err_grep)) + // !flag_expect_return && !(retval == 0 && check_logfiles(grep, err_grep)) log("Testcase does not match expected crash.\n"); if (crashes) diff --git a/tests/bugpoint/err.ys b/tests/bugpoint/err.ys index c74e34b19..08bf0a9c7 100644 --- a/tests/bugpoint/err.ys +++ b/tests/bugpoint/err.ys @@ -38,6 +38,6 @@ select -assert-mod-count 1 zzy # raise_error -stderr prints to stderr and exits with 1 design -load read rename top abc -bugpoint -yosys ../../yosys -command "raise_error -stderr" -greperr "help me" -expect-return 1 +bugpoint -yosys ../../yosys -command "raise_error -stderr" -err_grep "help me" -expect-return 1 select -assert-mod-count 1 =* select -assert-mod-count 1 other From 93f7429f4fd9feaef054e61ddc4179a8756e5719 Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Tue, 29 Jul 2025 11:39:51 +1200 Subject: [PATCH 13/24] tests: Add bugpoint to MK_TEST_DIRS Also change `-err_grep` to `-err-grep` for consistency with `-expect-return`. --- Makefile | 1 + passes/cmds/bugpoint.cc | 4 ++-- tests/bugpoint/err.ys | 2 +- tests/bugpoint/run-test.sh | 4 ++++ 4 files changed, 8 insertions(+), 3 deletions(-) create mode 100755 tests/bugpoint/run-test.sh diff --git a/Makefile b/Makefile index 083805fbc..3f17d471c 100644 --- a/Makefile +++ b/Makefile @@ -868,6 +868,7 @@ MK_TEST_DIRS += tests/arch/nexus MK_TEST_DIRS += tests/arch/quicklogic/pp3 MK_TEST_DIRS += tests/arch/quicklogic/qlf_k6n10f MK_TEST_DIRS += tests/arch/xilinx +MK_TEST_DIRS += tests/bugpoint MK_TEST_DIRS += tests/opt MK_TEST_DIRS += tests/sat MK_TEST_DIRS += tests/sim diff --git a/passes/cmds/bugpoint.cc b/passes/cmds/bugpoint.cc index f740c2bcd..683430372 100644 --- a/passes/cmds/bugpoint.cc +++ b/passes/cmds/bugpoint.cc @@ -77,7 +77,7 @@ struct BugpointPass : public Pass { log(" -runner \"\"\n"); log(" child process wrapping command, e.g., \"timeout 30\", or valgrind.\n"); log("\n"); - log(" -err_grep \"\"\n"); + log(" -err-grep \"\"\n"); log(" only consider crashes that print this string on stderr. useful for\n"); log(" errors outside of yosys.\n"); log("\n"); @@ -474,7 +474,7 @@ struct BugpointPass : public Pass { grep = args[++argidx]; continue; } - if (args[argidx] == "-err_grep" && argidx + 1 < args.size()) { + if (args[argidx] == "-err-grep" && argidx + 1 < args.size()) { has_check = true; check_err = true; err_grep = args[++argidx]; diff --git a/tests/bugpoint/err.ys b/tests/bugpoint/err.ys index 08bf0a9c7..42e84241c 100644 --- a/tests/bugpoint/err.ys +++ b/tests/bugpoint/err.ys @@ -38,6 +38,6 @@ select -assert-mod-count 1 zzy # raise_error -stderr prints to stderr and exits with 1 design -load read rename top abc -bugpoint -yosys ../../yosys -command "raise_error -stderr" -err_grep "help me" -expect-return 1 +bugpoint -yosys ../../yosys -command "raise_error -stderr" -err-grep "help me" -expect-return 1 select -assert-mod-count 1 =* select -assert-mod-count 1 other diff --git a/tests/bugpoint/run-test.sh b/tests/bugpoint/run-test.sh new file mode 100755 index 000000000..006c731e3 --- /dev/null +++ b/tests/bugpoint/run-test.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -eu +source ../gen-tests-makefile.sh +generate_mk --yosys-scripts From d3ff803b8176594be47ca2b83ab138b16c5a11fc Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Tue, 29 Jul 2025 11:39:52 +1200 Subject: [PATCH 14/24] bugpoint: Add -suffix option Allows for adding a suffix to the `bugpoint-case` file name so that multiple `bugpoint`s can run in the same directory, e.g. during a `make test -j4`. --- passes/cmds/bugpoint.cc | 61 ++++++++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/passes/cmds/bugpoint.cc b/passes/cmds/bugpoint.cc index 683430372..08dc76dda 100644 --- a/passes/cmds/bugpoint.cc +++ b/passes/cmds/bugpoint.cc @@ -81,6 +81,10 @@ struct BugpointPass : public Pass { log(" only consider crashes that print this string on stderr. useful for\n"); log(" errors outside of yosys.\n"); log("\n"); + log(" -suffix \"\"\n"); + log(" add suffix to generated file names. useful when running more than one\n"); + log(" instance of bugpoint in the same directory. limited to 8 characters.\n"); + log("\n"); log("It is possible to constrain which parts of the design will be considered for\n"); log("removal. Unless one or more of the following options are specified, all parts\n"); log("will be considered.\n"); @@ -116,16 +120,20 @@ struct BugpointPass : public Pass { log("\n"); } - int run_yosys(RTLIL::Design *design, string runner, string yosys_cmd, string yosys_arg, bool catch_err) + int run_yosys(RTLIL::Design *design, string runner, string yosys_cmd, string yosys_arg, string suffix, bool catch_err) { design->sort(); - std::ofstream f("bugpoint-case.il"); + string bugpoint_file = "bugpoint-case"; + if (suffix.size()) + bugpoint_file += stringf(".%.8s", suffix.c_str()); + + std::ofstream f(bugpoint_file + ".il"); RTLIL_BACKEND::dump_design(f, design, /*only_selected=*/false, /*flag_m=*/true, /*flag_n=*/false); f.close(); - string yosys_cmdline = stringf("%s %s -qq -L bugpoint-case.log %s bugpoint-case.il", runner.c_str(), yosys_cmd.c_str(), yosys_arg.c_str()); - if (catch_err) yosys_cmdline += " 2>bugpoint-case.err"; + string yosys_cmdline = stringf("%s %s -qq -L %s.log %s %s.il", runner.c_str(), yosys_cmd.c_str(), bugpoint_file.c_str(), yosys_arg.c_str(), bugpoint_file.c_str()); + if (catch_err) yosys_cmdline += stringf(" 2>%s.err", bugpoint_file.c_str()); auto status = run_command(yosys_cmdline); // we're not processing lines, which means we're getting raw system() returns if(WIFEXITED(status)) @@ -138,7 +146,7 @@ struct BugpointPass : public Pass { return 0; } - bool check_logfile(string grep, bool err=false) + bool check_logfile(string grep, string suffix, bool err=false) { if (grep.empty()) return true; @@ -146,11 +154,12 @@ struct BugpointPass : public Pass { if (grep.size() > 2 && grep.front() == '"' && grep.back() == '"') grep = grep.substr(1, grep.size() - 2); - std::ifstream f; - if (err) - f = std::ifstream("bugpoint-case.err"); - else - f = std::ifstream("bugpoint-case.log"); + string bugpoint_file = "bugpoint-case"; + if (suffix.size()) + bugpoint_file += stringf(".%.8s", suffix.c_str()); + bugpoint_file += err ? ".err" : ".log"; + + std::ifstream f(bugpoint_file); while (!f.eof()) { @@ -162,9 +171,9 @@ struct BugpointPass : public Pass { return false; } - bool check_logfiles(string grep, string err_grep) + bool check_logfiles(string grep, string err_grep, string suffix) { - return check_logfile(grep) && check_logfile(err_grep, true); + return check_logfile(grep, suffix) && check_logfile(err_grep, suffix, true); } RTLIL::Design *clean_design(RTLIL::Design *design, bool do_clean = true, bool do_delete = false) @@ -441,7 +450,7 @@ struct BugpointPass : public Pass { void execute(std::vector args, RTLIL::Design *design) override { - string yosys_cmd = "yosys", yosys_arg, grep, err_grep, runner; + string yosys_cmd = "yosys", yosys_arg, grep, err_grep, runner, suffix; bool flag_expect_return = false, has_check = false, check_err = false; int expect_return_value = 0; bool fast = false, clean = false; @@ -545,6 +554,14 @@ struct BugpointPass : public Pass { } continue; } + if (args[argidx] == "-suffix" && argidx + 1 < args.size()) { + suffix = args[++argidx]; + if (suffix.size() && suffix.at(0) == '"') { + log_assert(suffix.back() == '"'); + suffix = suffix.substr(1, suffix.size() - 2); + } + continue; + } break; } extra_args(args, argidx, design); @@ -571,14 +588,14 @@ struct BugpointPass : public Pass { log_cmd_error("This command only operates on fully selected designs!\n"); RTLIL::Design *crashing_design = clean_design(design, clean); - int retval = run_yosys(crashing_design, runner, yosys_cmd, yosys_arg, check_err); + int retval = run_yosys(crashing_design, runner, yosys_cmd, yosys_arg, suffix, check_err); if (flag_expect_return && retval != expect_return_value) log_cmd_error("The provided script file or command and Yosys binary returned value %d instead of expected %d on this design!\n", retval, expect_return_value); if (!flag_expect_return && retval == 0) log_cmd_error("The provided script file or command and Yosys binary do not crash on this design!\n"); - if (!check_logfile(grep)) + if (!check_logfile(grep, suffix)) log_cmd_error("The provided grep string is not found in the log file!\n"); - if (!check_logfile(err_grep, true)) + if (!check_logfile(err_grep, suffix, true)) log_cmd_error("The provided grep string is not found in stderr log!\n"); int seed = 0; @@ -592,30 +609,30 @@ struct BugpointPass : public Pass { if (clean) { RTLIL::Design *testcase = clean_design(simplified); - retval = run_yosys(testcase, runner, yosys_cmd, yosys_arg, check_err); + retval = run_yosys(testcase, runner, yosys_cmd, yosys_arg, suffix, check_err); delete testcase; } else { - retval = run_yosys(simplified, runner, yosys_cmd, yosys_arg, check_err); + retval = run_yosys(simplified, runner, yosys_cmd, yosys_arg, suffix, check_err); } bool crashes = false; - if (flag_expect_return && retval == expect_return_value && check_logfiles(grep, err_grep)) + if (flag_expect_return && retval == expect_return_value && check_logfiles(grep, err_grep, suffix)) { log("Testcase matches expected crash.\n"); crashes = true; } else if (!flag_expect_return && retval == 0) log("Testcase does not crash.\n"); - else if (!flag_expect_return && check_logfiles(grep, err_grep)) + else if (!flag_expect_return && check_logfiles(grep, err_grep, suffix)) { log("Testcase crashes.\n"); crashes = true; } else - // flag_expect_return && !(retval == expect_return_value && check_logfiles(grep, err_grep)) - // !flag_expect_return && !(retval == 0 && check_logfiles(grep, err_grep)) + // flag_expect_return && !(retval == expect_return_value && check_logfiles(grep, err_grep, suffix)) + // !flag_expect_return && !(retval == 0 && check_logfiles(grep, err_grep, suffix)) log("Testcase does not match expected crash.\n"); if (crashes) From fe07d390f1b5b80aaeeb295bd74978d617e4bbc0 Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Tue, 29 Jul 2025 11:39:52 +1200 Subject: [PATCH 15/24] tests/bugpoint: More tests More coverage. --- tests/bugpoint/.gitignore | 4 +- tests/bugpoint/failures.ys | 29 ++++++++ tests/bugpoint/mod_constraints.ys | 83 +++++++++++++++++++++++ tests/bugpoint/mods.il | 38 +++++++++++ tests/bugpoint/proc_constraints.ys | 49 +++++++++++++ tests/bugpoint/procs.il | 42 ++++++++++++ tests/bugpoint/{err.ys => raise_error.ys} | 8 +-- 7 files changed, 248 insertions(+), 5 deletions(-) create mode 100644 tests/bugpoint/failures.ys create mode 100644 tests/bugpoint/mod_constraints.ys create mode 100644 tests/bugpoint/mods.il create mode 100644 tests/bugpoint/proc_constraints.ys create mode 100644 tests/bugpoint/procs.il rename tests/bugpoint/{err.ys => raise_error.ys} (71%) diff --git a/tests/bugpoint/.gitignore b/tests/bugpoint/.gitignore index 818575593..1012c66cc 100644 --- a/tests/bugpoint/.gitignore +++ b/tests/bugpoint/.gitignore @@ -1,3 +1,5 @@ -*.il +bugpoint-case.* *.log *.err +*.temp +run-test.mk diff --git a/tests/bugpoint/failures.ys b/tests/bugpoint/failures.ys new file mode 100644 index 000000000..ce8daa8cc --- /dev/null +++ b/tests/bugpoint/failures.ys @@ -0,0 +1,29 @@ +write_file fail.temp << EOF +logger -expect error "Missing -script or -command option." 1 +bugpoint -suffix fail -yosys ../../yosys +EOF +exec -expect-return 0 -- ../../yosys -qq mods.il -s fail.temp + +write_file fail.temp << EOF +logger -expect error "do not crash on this design" 1 +bugpoint -suffix fail -yosys ../../yosys -command "dump" +EOF +exec -expect-return 0 -- ../../yosys -qq mods.il -s fail.temp + +write_file fail.temp << EOF +logger -expect error "returned value 3 instead of expected 7" 1 +bugpoint -suffix fail -yosys ../../yosys -command raise_error -expect-return 7 +EOF +exec -expect-return 0 -- ../../yosys -qq mods.il -s fail.temp + +write_file fail.temp << EOF +logger -expect error "not found in the log file!" 1 +bugpoint -suffix fail -yosys ../../yosys -command raise_error -grep "nope" +EOF +exec -expect-return 0 -- ../../yosys -qq mods.il -s fail.temp + +write_file fail.temp << EOF +logger -expect error "not found in stderr log!" 1 +bugpoint -suffix fail -yosys ../../yosys -command raise_error -err-grep "nope" +EOF +exec -expect-return 0 -- ../../yosys -qq mods.il -s fail.temp diff --git a/tests/bugpoint/mod_constraints.ys b/tests/bugpoint/mod_constraints.ys new file mode 100644 index 000000000..f35095510 --- /dev/null +++ b/tests/bugpoint/mod_constraints.ys @@ -0,0 +1,83 @@ +read_rtlil mods.il +select -assert-count 7 w:* +select -assert-mod-count 3 =* +select -assert-count 4 c:* +design -stash base + +# everything is removed by default +design -load base +bugpoint -suffix mods -yosys ../../yosys -command raise_error -expect-return 3 +select -assert-count 1 w:* +select -assert-mod-count 1 =* +select -assert-none c:* + +# don't remove wires +design -load base +bugpoint -suffix mods -yosys ../../yosys -command raise_error -expect-return 3 -modules -cells +select -assert-count 3 w:* +select -assert-mod-count 1 =* +select -assert-none c:* + +# don't remove cells or their connections +design -load base +bugpoint -suffix mods -yosys ../../yosys -command raise_error -expect-return 3 -wires -modules +select -assert-count 5 w:* +select -assert-mod-count 1 =* +select -assert-count 4 c:* + +# don't remove cells but do remove their connections +design -load base +bugpoint -suffix mods -yosys ../../yosys -command raise_error -expect-return 3 -wires -modules -connections +select -assert-count 1 w:* +select -assert-mod-count 1 =* +select -assert-count 4 c:* + +# don't remove modules +design -load base +bugpoint -suffix mods -yosys ../../yosys -command raise_error -expect-return 3 -wires -cells +select -assert-count 1 w:* +select -assert-mod-count 3 =* +select -assert-none c:* + +# can keep wires +design -load base +setattr -set bugpoint_keep 1 w:w_b +bugpoint -suffix mods -yosys ../../yosys -command raise_error -expect-return 3 +select -assert-count 2 w:* +select -assert-mod-count 1 =* +select -assert-none c:* + +# a wire with keep won't keep the cell/module containing it +design -load base +setattr -set bugpoint_keep 1 w:w_o +bugpoint -suffix mods -yosys ../../yosys -command raise_error -expect-return 3 +select -assert-count 1 w:* +select -assert-mod-count 1 =* +select -assert-none c:* + +# can keep cells (and do it without the associated module) +design -load base +setattr -set bugpoint_keep 1 c:c_a +bugpoint -suffix mods -yosys ../../yosys -command raise_error -expect-return 3 +select -assert-count 1 w:* +select -assert-mod-count 1 =* +select -assert-count 1 c:* + +# can keep modules +design -load base +setattr -mod -set bugpoint_keep 1 m_a +bugpoint -suffix mods -yosys ../../yosys -command raise_error -expect-return 3 +select -assert-count 1 w:* +select -assert-mod-count 2 =* +select -assert-none c:* + +# minimize to just the path connecting w_a and w_c +# which happens via w_b, w_i, w_o, m_a, c_a and c_b +write_file script.temp << EOF +select -assert-none w:w_a %co* w:w_c %ci* %i +EOF +design -load base +bugpoint -suffix mods -yosys ../../yosys -script script.temp -grep "Assertion failed" +select -assert-count 5 w:* +select -assert-mod-count 2 =* +select -assert-count 2 c:* diff --git a/tests/bugpoint/mods.il b/tests/bugpoint/mods.il new file mode 100644 index 000000000..fe3ce6522 --- /dev/null +++ b/tests/bugpoint/mods.il @@ -0,0 +1,38 @@ +module \m_a + wire input 1 \w_i + wire output 2 \w_o + connect \w_o \w_i +end + +module \m_b + wire input 1 \w_i + wire output 2 \w_o +end + +attribute \top 1 +module \top + attribute \raise_error 3 + wire \w_a + wire \w_b + wire \w_c + + cell \m_a \c_a + connect \w_i \w_a + connect \w_o \w_b + end + + cell \m_a \c_b + connect \w_i \w_b + connect \w_o \w_c + end + + cell \m_b \c_c + connect \w_i \w_c + connect \w_o \w_a + end + + cell \m_b \c_d + connect \w_i 1'0 + connect \w_o 1'1 + end +end diff --git a/tests/bugpoint/proc_constraints.ys b/tests/bugpoint/proc_constraints.ys new file mode 100644 index 000000000..22b8b3c60 --- /dev/null +++ b/tests/bugpoint/proc_constraints.ys @@ -0,0 +1,49 @@ +read_rtlil procs.il +select -assert-count 2 p:* +design -stash err_q + +# processes get removed by default +design -load err_q +bugpoint -suffix procs -yosys ../../yosys -command raise_error -expect-return 4 +select -assert-none p:* + +# individual processes can be kept +design -load err_q +setattr -set bugpoint_keep 1 p:proc_a +bugpoint -suffix procs -yosys ../../yosys -command raise_error -expect-return 4 +select -assert-count 1 p:* + +# all processes can be kept +design -load err_q +bugpoint -suffix procs -yosys ../../yosys -command raise_error -expect-return 4 -wires +select -assert-count 2 p:* + +# d and clock are connected after proc +design -load err_q +proc +select -assert-count 3 w:d %co +select -assert-count 3 w:clock %co + +# no assigns means no d +design -load err_q +bugpoint -suffix procs -yosys ../../yosys -command raise_error -expect-return 4 -assigns +proc +select -assert-count 1 w:d %co + +# no updates means no clock +design -load err_q +bugpoint -suffix procs -yosys ../../yosys -command raise_error -expect-return 4 -updates +proc +select -assert-count 1 w:clock %co + +# can remove ports +design -load err_q +select -assert-count 5 x:* +bugpoint -suffix procs -yosys ../../yosys -command raise_error -expect-return 4 -ports +select -assert-none x:* + +# can keep ports +design -load err_q +setattr -set bugpoint_keep 1 i:d o:q +bugpoint -suffix procs -yosys ../../yosys -command raise_error -expect-return 4 -ports +select -assert-count 2 x:* diff --git a/tests/bugpoint/procs.il b/tests/bugpoint/procs.il new file mode 100644 index 000000000..cb9f7c8dd --- /dev/null +++ b/tests/bugpoint/procs.il @@ -0,0 +1,42 @@ +module \ff_with_en_and_sync_reset + wire $0\q[1:1] + wire $0\q[0:0] + attribute \raise_error 4 + wire width 2 output 5 \q + wire width 2 input 4 \d + wire input 3 \enable + wire input 2 \reset + wire input 1 \clock + + process \proc_a + assign $0\q[0:0] \q [0] + switch \reset + case 1'1 + assign $0\q[0:0] 1'0 + case + switch \enable + case 1'1 + assign $0\q[0:0] \d [0] + case + end + end + sync posedge \clock + update \q [0] $0\q[0:0] + end + + process \proc_b + assign $0\q[1:1] \q [1] + switch \reset + case 1'1 + assign $0\q[1:1] 1'0 + case + switch \enable + case 1'1 + assign $0\q[1:1] \d [1] + case + end + end + sync posedge \clock + update \q [1] $0\q[1:1] + end +end diff --git a/tests/bugpoint/err.ys b/tests/bugpoint/raise_error.ys similarity index 71% rename from tests/bugpoint/err.ys rename to tests/bugpoint/raise_error.ys index 42e84241c..a0a03f447 100644 --- a/tests/bugpoint/err.ys +++ b/tests/bugpoint/raise_error.ys @@ -16,14 +16,14 @@ design -stash read # raise_error with int exits with status design -load read -bugpoint -yosys ../../yosys -command raise_error -expect-return 7 +bugpoint -suffix error -yosys ../../yosys -command raise_error -expect-return 7 select -assert-mod-count 1 =* select -assert-mod-count 1 top # raise_error with string prints message and exits with 1 design -load read rename top abc -bugpoint -yosys ../../yosys -command raise_error -grep "help me" -expect-return 1 +bugpoint -suffix error -yosys ../../yosys -command raise_error -grep "help me" -expect-return 1 select -assert-mod-count 1 =* select -assert-mod-count 1 other @@ -31,13 +31,13 @@ select -assert-mod-count 1 other design -load read rename def zzy delete other -bugpoint -yosys ../../yosys -command raise_error -expect-return 1 +bugpoint -suffix error -yosys ../../yosys -command raise_error -expect-return 1 select -assert-mod-count 1 =* select -assert-mod-count 1 zzy # raise_error -stderr prints to stderr and exits with 1 design -load read rename top abc -bugpoint -yosys ../../yosys -command "raise_error -stderr" -err-grep "help me" -expect-return 1 +bugpoint -suffix error -yosys ../../yosys -command "raise_error -stderr" -err-grep "help me" -expect-return 1 select -assert-mod-count 1 =* select -assert-mod-count 1 other From 8f6d7a3043d4ca89b1fab25d2b4f45a56b9e5efa Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 29 Jul 2025 00:29:04 +0000 Subject: [PATCH 16/24] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 083805fbc..36b4d0491 100644 --- a/Makefile +++ b/Makefile @@ -160,7 +160,7 @@ ifeq ($(OS), Haiku) CXXFLAGS += -D_DEFAULT_SOURCE endif -YOSYS_VER := 0.55+112 +YOSYS_VER := 0.55+115 YOSYS_MAJOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f1) YOSYS_MINOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f2 | cut -d'+' -f1) YOSYS_COMMIT := $(shell echo $(YOSYS_VER) | cut -d'+' -f2) From 6ee3cd8ffdb636301b5600513f549e3e319f1ac6 Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Wed, 9 Jul 2025 02:31:33 +0000 Subject: [PATCH 17/24] Replace `stringf()` with a templated function which does compile-time format string checking. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Checking only happens at compile time if -std=c++20 (or greater) is enabled. Otherwise the checking happens at run time. This requires the format string to be a compile-time constant (when compiling with C++20), so fix a few places where that isn't true. The format string behavior is a bit more lenient than C printf. For %d/%u you can pass any integer type and it will be converted and output without truncating bits, i.e. any length specifier is ignored and the conversion is always treated as 'll'. Any truncation needs to be done by casting the argument itself. For %f/%g you can pass anything that converts to double, including integers. Performance results with clang 19 -O3 on Linux: ``` hyperfine './yosys -dp "read_rtlil /usr/local/google/home/rocallahan/Downloads/jpeg.synth.il; dump"' ``` C++17 before: Time (mean ± σ): 101.3 ms ± 0.8 ms [User: 85.6 ms, System: 15.6 ms] C++17 after: Time (mean ± σ): 98.4 ms ± 1.2 ms [User: 82.1 ms, System: 16.1 ms] C++20 before: Time (mean ± σ): 100.9 ms ± 1.1 ms [User: 87.0 ms, System: 13.8 ms] C++20 after: Time (mean ± σ): 97.8 ms ± 1.4 ms [User: 83.1 ms, System: 14.7 ms] The generated code is reasonably efficient. E.g. with clang 19, `stringf()` with a format with no %% escapes and no other parameters (a weirdly common case) often compiles to a fully inlined `std::string` construction. In general the format string parsing is often (not always) compiled away. --- backends/verilog/verilog_backend.cc | 20 +- kernel/io.cc | 149 +++++++++++ kernel/io.h | 392 +++++++++++++++++++++++++++- kernel/yosys_common.h | 9 + 4 files changed, 551 insertions(+), 19 deletions(-) diff --git a/backends/verilog/verilog_backend.cc b/backends/verilog/verilog_backend.cc index 1cef7be60..44f8f05ea 100644 --- a/backends/verilog/verilog_backend.cc +++ b/backends/verilog/verilog_backend.cc @@ -1163,9 +1163,9 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) dump_sigspec(f, cell->getPort(ID::Y)); f << stringf(" = ~(("); dump_cell_expr_port(f, cell, "A", false); - f << stringf(cell->type == ID($_AOI3_) ? " & " : " | "); + f << (cell->type == ID($_AOI3_) ? " & " : " | "); dump_cell_expr_port(f, cell, "B", false); - f << stringf(cell->type == ID($_AOI3_) ? ") |" : ") &"); + f << (cell->type == ID($_AOI3_) ? ") |" : ") &"); dump_attributes(f, "", cell->attributes, " "); f << stringf(" "); dump_cell_expr_port(f, cell, "C", false); @@ -1178,13 +1178,13 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) dump_sigspec(f, cell->getPort(ID::Y)); f << stringf(" = ~(("); dump_cell_expr_port(f, cell, "A", false); - f << stringf(cell->type == ID($_AOI4_) ? " & " : " | "); + f << (cell->type == ID($_AOI4_) ? " & " : " | "); dump_cell_expr_port(f, cell, "B", false); - f << stringf(cell->type == ID($_AOI4_) ? ") |" : ") &"); + f << (cell->type == ID($_AOI4_) ? ") |" : ") &"); dump_attributes(f, "", cell->attributes, " "); f << stringf(" ("); dump_cell_expr_port(f, cell, "C", false); - f << stringf(cell->type == ID($_AOI4_) ? " & " : " | "); + f << (cell->type == ID($_AOI4_) ? " & " : " | "); dump_cell_expr_port(f, cell, "D", false); f << stringf("));\n"); return true; @@ -1395,10 +1395,10 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) int s_width = cell->getPort(ID::S).size(); std::string func_name = cellname(cell); - f << stringf("%s" "function [%d:0] %s;\n", indent.c_str(), width-1, func_name.c_str()); - f << stringf("%s" " input [%d:0] a;\n", indent.c_str(), width-1); - f << stringf("%s" " input [%d:0] b;\n", indent.c_str(), s_width*width-1); - f << stringf("%s" " input [%d:0] s;\n", indent.c_str(), s_width-1); + f << stringf("%s" "function [%d:0] %s;\n", indent, width-1, func_name); + f << stringf("%s" " input [%d:0] a;\n", indent, width-1); + f << stringf("%s" " input [%d:0] b;\n", indent, s_width*width-1); + f << stringf("%s" " input [%d:0] s;\n", indent, s_width-1); dump_attributes(f, indent + " ", cell->attributes); if (noparallelcase) @@ -1407,7 +1407,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) if (!noattr) f << stringf("%s" " (* parallel_case *)\n", indent.c_str()); f << stringf("%s" " casez (s)", indent.c_str()); - f << stringf(noattr ? " // synopsys parallel_case\n" : "\n"); + f << (noattr ? " // synopsys parallel_case\n" : "\n"); } for (int i = 0; i < s_width; i++) diff --git a/kernel/io.cc b/kernel/io.cc index d7d126c4c..3deb54d86 100644 --- a/kernel/io.cc +++ b/kernel/io.cc @@ -384,4 +384,153 @@ std::string escape_filename_spaces(const std::string& filename) return out; } +void format_emit_unescaped(std::string &result, std::string_view fmt) +{ + result.reserve(result.size() + fmt.size()); + for (size_t i = 0; i < fmt.size(); ++i) { + char ch = fmt[i]; + result.push_back(ch); + if (ch == '%' && i + 1 < fmt.size() && fmt[i + 1] == '%') { + ++i; + } + } +} + +std::string unescape_format_string(std::string_view fmt) +{ + std::string result; + format_emit_unescaped(result, fmt); + return result; +} + +static std::string string_view_stringf(std::string_view spec, ...) +{ + std::string fmt(spec); + char format_specifier = fmt[fmt.size() - 1]; + switch (format_specifier) { + case 'd': + case 'i': + case 'o': + case 'u': + case 'x': + case 'X': { + // Strip any length modifier off `fmt` + std::string long_fmt; + for (size_t i = 0; i + 1 < fmt.size(); ++i) { + char ch = fmt[i]; + if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { + break; + } + long_fmt.push_back(ch); + } + // Add `lld` or whatever + long_fmt += "ll"; + long_fmt.push_back(format_specifier); + fmt = long_fmt; + break; + } + default: + break; + } + + va_list ap; + va_start(ap, spec); + std::string result = vstringf(fmt.c_str(), ap); + va_end(ap); + return result; +} + +template +static void format_emit_stringf(std::string &result, std::string_view spec, int *dynamic_ints, + DynamicIntCount num_dynamic_ints, Arg arg) +{ + // Delegate nontrivial formats to the C library. + switch (num_dynamic_ints) { + case DynamicIntCount::NONE: + result += string_view_stringf(spec, arg); + return; + case DynamicIntCount::ONE: + result += string_view_stringf(spec, dynamic_ints[0], arg); + return; + case DynamicIntCount::TWO: + result += string_view_stringf(spec, dynamic_ints[0], dynamic_ints[1], arg); + return; + } + YOSYS_ABORT("Internal error"); +} + +void format_emit_long_long(std::string &result, std::string_view spec, int *dynamic_ints, + DynamicIntCount num_dynamic_ints, long long arg) +{ + if (spec == "%d") { + // Format checking will have guaranteed num_dynamic_ints == 0. + result += std::to_string(arg); + return; + } + format_emit_stringf(result, spec, dynamic_ints, num_dynamic_ints, arg); +} + +void format_emit_unsigned_long_long(std::string &result, std::string_view spec, int *dynamic_ints, + DynamicIntCount num_dynamic_ints, unsigned long long arg) +{ + if (spec == "%u") { + // Format checking will have guaranteed num_dynamic_ints == 0. + result += std::to_string(arg); + return; + } + if (spec == "%c") { + result += static_cast(arg); + return; + } + format_emit_stringf(result, spec, dynamic_ints, num_dynamic_ints, arg); +} + +void format_emit_double(std::string &result, std::string_view spec, int *dynamic_ints, + DynamicIntCount num_dynamic_ints, double arg) +{ + format_emit_stringf(result, spec, dynamic_ints, num_dynamic_ints, arg); +} + +void format_emit_char_ptr(std::string &result, std::string_view spec, int *dynamic_ints, + DynamicIntCount num_dynamic_ints, const char *arg) +{ + if (spec == "%s") { + // Format checking will have guaranteed num_dynamic_ints == 0. + result += arg; + return; + } + format_emit_stringf(result, spec, dynamic_ints, num_dynamic_ints, arg); +} + +void format_emit_string(std::string &result, std::string_view spec, int *dynamic_ints, + DynamicIntCount num_dynamic_ints, const std::string &arg) +{ + if (spec == "%s") { + // Format checking will have guaranteed num_dynamic_ints == 0. + result += arg; + return; + } + format_emit_stringf(result, spec, dynamic_ints, num_dynamic_ints, arg.c_str()); +} + +void format_emit_string_view(std::string &result, std::string_view spec, int *dynamic_ints, + DynamicIntCount num_dynamic_ints, std::string_view arg) +{ + if (spec == "%s") { + // Format checking will have guaranteed num_dynamic_ints == 0. + // We can output the string without creating a temporary copy. + result += arg; + return; + } + // Delegate nontrivial formats to the C library. We need to construct + // a temporary string to ensure null termination. + format_emit_stringf(result, spec, dynamic_ints, num_dynamic_ints, std::string(arg).c_str()); +} + +void format_emit_void_ptr(std::string &result, std::string_view spec, int *dynamic_ints, + DynamicIntCount num_dynamic_ints, const void *arg) +{ + format_emit_stringf(result, spec, dynamic_ints, num_dynamic_ints, arg); +} + YOSYS_NAMESPACE_END diff --git a/kernel/io.h b/kernel/io.h index 91699d775..ea2499e43 100644 --- a/kernel/io.h +++ b/kernel/io.h @@ -1,5 +1,6 @@ #include #include +#include #include "kernel/yosys_common.h" #ifndef YOSYS_IO_H @@ -50,18 +51,391 @@ inline std::string vstringf(const char *fmt, va_list ap) #endif } -std::string stringf(const char *fmt, ...) YS_ATTRIBUTE(format(printf, 1, 2)); - -inline std::string stringf(const char *fmt, ...) +enum ConversionSpecifier : uint8_t { - std::string string; - va_list ap; + CONVSPEC_NONE, + // Specifier not understood/supported + CONVSPEC_ERROR, + // Consumes a "long long" + CONVSPEC_SIGNED_INT, + // Consumes a "unsigned long long" + CONVSPEC_UNSIGNED_INT, + // Consumes a "double" + CONVSPEC_DOUBLE, + // Consumes a "const char*" + CONVSPEC_CHAR_PTR, + // Consumes a "void*" + CONVSPEC_VOID_PTR, +}; - va_start(ap, fmt); - string = vstringf(fmt, ap); - va_end(ap); +constexpr ConversionSpecifier parse_conversion_specifier(char ch, char prev_ch) +{ + switch (ch) { + case 'd': + case 'i': + return CONVSPEC_SIGNED_INT; + case 'o': + case 'u': + case 'x': + case 'X': + case 'm': + return CONVSPEC_UNSIGNED_INT; + case 'c': + if (prev_ch == 'l' || prev_ch == 'q' || prev_ch == 'L') { + // wchar not supported + return CONVSPEC_ERROR; + } + return CONVSPEC_UNSIGNED_INT; + case 'e': + case 'E': + case 'f': + case 'F': + case 'g': + case 'G': + case 'a': + case 'A': + return CONVSPEC_DOUBLE; + case 's': + if (prev_ch == 'l' || prev_ch == 'q' || prev_ch == 'L') { + // wchar not supported + return CONVSPEC_ERROR; + } + return CONVSPEC_CHAR_PTR; + case 'p': + return CONVSPEC_VOID_PTR; + case '$': // positional parameters + case 'n': + case 'S': + return CONVSPEC_ERROR; + default: + return CONVSPEC_NONE; + } +} - return string; +enum class DynamicIntCount : uint8_t { + NONE = 0, + ONE = 1, + TWO = 2, +}; + +// Describes a printf-style format conversion specifier found in a format string. +struct FoundFormatSpec +{ + // The start offset of the conversion spec in the format string. + int start; + // The end offset of the conversion spec in the format string. + int end; + ConversionSpecifier spec; + // Number of int args consumed by '*' dynamic width/precision args. + DynamicIntCount num_dynamic_ints; +}; + +// Ensure there is no format spec. +constexpr void ensure_no_format_spec(std::string_view fmt, int index, bool *has_escapes) +{ + int fmt_size = static_cast(fmt.size()); + // A trailing '%' is not a format spec. + while (index + 1 < fmt_size) { + if (fmt[index] != '%') { + ++index; + continue; + } + if (fmt[index + 1] != '%') { + YOSYS_ABORT("More format conversion specifiers than arguments"); + } + *has_escapes = true; + index += 2; + } +} + +// Returns the next format conversion specifier (starting with '%'). +// Returns CONVSPEC_NONE if there isn't a format conversion specifier. +constexpr FoundFormatSpec find_next_format_spec(std::string_view fmt, int fmt_start, bool *has_escapes) +{ + int index = fmt_start; + int fmt_size = static_cast(fmt.size()); + while (index < fmt_size) { + if (fmt[index] != '%') { + ++index; + continue; + } + int p = index + 1; + uint8_t num_dynamic_ints = 0; + while (true) { + if (p == fmt_size) { + return {0, 0, CONVSPEC_NONE, DynamicIntCount::NONE}; + } + if (fmt[p] == '%') { + *has_escapes = true; + index = p + 1; + break; + } + if (fmt[p] == '*') { + if (num_dynamic_ints >= 2) { + return {0, 0, CONVSPEC_ERROR, DynamicIntCount::NONE}; + } + ++num_dynamic_ints; + } + ConversionSpecifier spec = parse_conversion_specifier(fmt[p], fmt[p - 1]); + if (spec != CONVSPEC_NONE) { + return {index, p + 1, spec, static_cast(num_dynamic_ints)}; + } + ++p; + } + } + return {0, 0, CONVSPEC_NONE, DynamicIntCount::NONE}; +} + +template +constexpr typename std::enable_if::type +check_format(std::string_view fmt, int fmt_start, bool *has_escapes, FoundFormatSpec*, DynamicIntCount) +{ + ensure_no_format_spec(fmt, fmt_start, has_escapes); +} + +// Check that the format string `fmt.substr(fmt_start)` is valid for the given type arguments. +// Fills `specs` with the FoundFormatSpecs found in the format string. +// `int_args_consumed` is the number of int arguments already consumed to satisfy the +// dynamic width/precision args for the next format conversion specifier. +template +constexpr void check_format(std::string_view fmt, int fmt_start, bool *has_escapes, FoundFormatSpec* specs, + DynamicIntCount int_args_consumed) +{ + FoundFormatSpec found = find_next_format_spec(fmt, fmt_start, has_escapes); + if (found.num_dynamic_ints > int_args_consumed) { + // We need to consume at least one more int for the dynamic + // width/precision of this format conversion specifier. + if constexpr (!std::is_convertible_v) { + YOSYS_ABORT("Expected dynamic int argument"); + } + check_format(fmt, fmt_start, has_escapes, specs, + static_cast(static_cast(int_args_consumed) + 1)); + return; + } + switch (found.spec) { + case CONVSPEC_NONE: + YOSYS_ABORT("Expected format conversion specifier for argument"); + break; + case CONVSPEC_ERROR: + YOSYS_ABORT("Found unsupported format conversion specifier"); + break; + case CONVSPEC_SIGNED_INT: + if constexpr (!std::is_convertible_v) { + YOSYS_ABORT("Expected type convertible to signed integer"); + } + *specs = found; + break; + case CONVSPEC_UNSIGNED_INT: + if constexpr (!std::is_convertible_v) { + YOSYS_ABORT("Expected type convertible to unsigned integer"); + } + *specs = found; + break; + case CONVSPEC_DOUBLE: + if constexpr (!std::is_convertible_v) { + YOSYS_ABORT("Expected type convertible to double"); + } + *specs = found; + break; + case CONVSPEC_CHAR_PTR: + if constexpr (!std::is_convertible_v && + !std::is_convertible_v && + !std::is_convertible_v) { + YOSYS_ABORT("Expected type convertible to char *"); + } + *specs = found; + break; + case CONVSPEC_VOID_PTR: + if constexpr (!std::is_convertible_v) { + YOSYS_ABORT("Expected pointer type"); + } + *specs = found; + break; + } + check_format(fmt, found.end, has_escapes, specs + 1, DynamicIntCount::NONE); +} + +// Emit the string representation of `arg` that has been converted to a `long long'. +void format_emit_long_long(std::string &result, std::string_view spec, int *dynamic_ints, + DynamicIntCount num_dynamic_ints, long long arg); + +// Emit the string representation of `arg` that has been converted to a `unsigned long long'. +void format_emit_unsigned_long_long(std::string &result, std::string_view spec, int *dynamic_ints, + DynamicIntCount num_dynamic_ints, unsigned long long arg); + +// Emit the string representation of `arg` that has been converted to a `double'. +void format_emit_double(std::string &result, std::string_view spec, int *dynamic_ints, + DynamicIntCount num_dynamic_ints, double arg); + +// Emit the string representation of `arg` that has been converted to a `const char*'. +void format_emit_char_ptr(std::string &result, std::string_view spec, int *dynamic_ints, + DynamicIntCount num_dynamic_ints, const char *arg); + +// Emit the string representation of `arg` that has been converted to a `std::string'. +void format_emit_string(std::string &result, std::string_view spec, int *dynamic_ints, + DynamicIntCount num_dynamic_ints, const std::string &arg); + +// Emit the string representation of `arg` that has been converted to a `std::string_view'. +void format_emit_string_view(std::string &result, std::string_view spec, int *dynamic_ints, + DynamicIntCount num_dynamic_ints, std::string_view arg); + +// Emit the string representation of `arg` that has been converted to a `double'. +void format_emit_void_ptr(std::string &result, std::string_view spec, int *dynamic_ints, + DynamicIntCount num_dynamic_ints, const void *arg); + +// Emit the string representation of `arg` according to the given `FoundFormatSpec`, +// appending it to `result`. +template +inline void format_emit_one(std::string &result, std::string_view fmt, const FoundFormatSpec &ffspec, + int *dynamic_ints, const Arg& arg) +{ + std::string_view spec = fmt.substr(ffspec.start, ffspec.end - ffspec.start); + DynamicIntCount num_dynamic_ints = ffspec.num_dynamic_ints; + switch (ffspec.spec) { + case CONVSPEC_SIGNED_INT: + if constexpr (std::is_convertible_v) { + long long s = arg; + format_emit_long_long(result, spec, dynamic_ints, num_dynamic_ints, s); + return; + } + break; + case CONVSPEC_UNSIGNED_INT: + if constexpr (std::is_convertible_v) { + unsigned long long s = arg; + format_emit_unsigned_long_long(result, spec, dynamic_ints, num_dynamic_ints, s); + return; + } + break; + case CONVSPEC_DOUBLE: + if constexpr (std::is_convertible_v) { + double s = arg; + format_emit_double(result, spec, dynamic_ints, num_dynamic_ints, s); + return; + } + break; + case CONVSPEC_CHAR_PTR: + if constexpr (std::is_convertible_v) { + const char *s = arg; + format_emit_char_ptr(result, spec, dynamic_ints, num_dynamic_ints, s); + return; + } + if constexpr (std::is_convertible_v) { + const std::string &s = arg; + format_emit_string(result, spec, dynamic_ints, num_dynamic_ints, s); + return; + } + if constexpr (std::is_convertible_v) { + const std::string_view &s = arg; + format_emit_string_view(result, spec, dynamic_ints, num_dynamic_ints, s); + return; + } + break; + case CONVSPEC_VOID_PTR: + if constexpr (std::is_convertible_v) { + const void *s = arg; + format_emit_void_ptr(result, spec, dynamic_ints, num_dynamic_ints, s); + return; + } + break; + default: + break; + } + YOSYS_ABORT("Internal error"); +} + +// Append the format string `fmt` to `result`, assuming there are no format conversion +// specifiers other than "%%" and therefore no arguments. Unescape "%%". +void format_emit_unescaped(std::string &result, std::string_view fmt); +std::string unescape_format_string(std::string_view fmt); + +inline void format_emit(std::string &result, std::string_view fmt, int fmt_start, + bool has_escapes, const FoundFormatSpec*, int*, DynamicIntCount) +{ + fmt = fmt.substr(fmt_start); + if (has_escapes) { + format_emit_unescaped(result, fmt); + } else { + result += fmt; + } +} +// Format `args` according to `fmt` (starting at `fmt_start`) and `specs` and append to `result`. +// `num_dynamic_ints` in `dynamic_ints[]` have already been collected to provide as +// dynamic width/precision args for the next format conversion specifier. +template +inline void format_emit(std::string &result, std::string_view fmt, int fmt_start, bool has_escapes, + const FoundFormatSpec* specs, int *dynamic_ints, DynamicIntCount num_dynamic_ints, + const Arg &arg, const Args &... args) +{ + if (specs->num_dynamic_ints > num_dynamic_ints) { + // Collect another int for the dynamic width precision/args + // for the next format conversion specifier. + if constexpr (std::is_convertible_v) { + dynamic_ints[static_cast(num_dynamic_ints)] = arg; + } else { + YOSYS_ABORT("Internal error"); + } + format_emit(result, fmt, fmt_start, has_escapes, specs, dynamic_ints, + static_cast(static_cast(num_dynamic_ints) + 1), args...); + return; + } + std::string_view str = fmt.substr(fmt_start, specs->start - fmt_start); + if (has_escapes) { + format_emit_unescaped(result, str); + } else { + result += str; + } + format_emit_one(result, fmt, *specs, dynamic_ints, arg); + format_emit(result, fmt, specs->end, has_escapes, specs + 1, dynamic_ints, DynamicIntCount::NONE, args...); +} + +template +inline std::string format_emit_toplevel(std::string_view fmt, bool has_escapes, const FoundFormatSpec* specs, const Args &... args) +{ + std::string result; + int dynamic_ints[2] = { 0, 0 }; + format_emit(result, fmt, 0, has_escapes, specs, dynamic_ints, DynamicIntCount::NONE, args...); + return result; +} +template <> +inline std::string format_emit_toplevel(std::string_view fmt, bool has_escapes, const FoundFormatSpec*) +{ + if (!has_escapes) { + return std::string(fmt); + } + return unescape_format_string(fmt); +} + +// This class parses format strings to build a list of `FoundFormatSpecs` in `specs`. +// When the compiler supports `consteval` (C++20), this parsing happens at compile time and +// type errors will be reported at compile time. Otherwise the parsing happens at +// runtime and type errors will trigger an `abort()` at runtime. +template +class FmtString +{ +public: + // Implicit conversion from const char * means that users can pass + // C string constants which are automatically parsed. + YOSYS_CONSTEVAL FmtString(const char *p) : fmt(p) + { + check_format(fmt, 0, &has_escapes, specs, DynamicIntCount::NONE); + } + std::string format(const Args &... args) + { + return format_emit_toplevel(fmt, has_escapes, specs, args...); + } +private: + std::string_view fmt; + bool has_escapes = false; + FoundFormatSpec specs[sizeof...(Args)] = {}; +}; + +template struct WrapType { using type = T; }; +template using TypeIdentity = typename WrapType::type; + +template +inline std::string stringf(FmtString...> fmt, Args... args) +{ + return fmt.format(args...); } int readsome(std::istream &f, char *s, int n); diff --git a/kernel/yosys_common.h b/kernel/yosys_common.h index e84676bc0..ecc8ce623 100644 --- a/kernel/yosys_common.h +++ b/kernel/yosys_common.h @@ -134,6 +134,15 @@ # define YS_COLD #endif +#ifdef __cpp_consteval +#define YOSYS_CONSTEVAL consteval +#else +// If we can't use consteval we can at least make it constexpr. +#define YOSYS_CONSTEVAL constexpr +#endif + +#define YOSYS_ABORT(s) abort() + #include "kernel/io.h" YOSYS_NAMESPACE_BEGIN From 2c8b4d7ad13e4817a36b3c20f20a47b658dd5b9e Mon Sep 17 00:00:00 2001 From: Drew Lewis Date: Tue, 22 Jul 2025 19:49:10 +0000 Subject: [PATCH 18/24] Use unordered_map instead of dict for IdString char* to index storage. dict is pretty slow when you don't ever need to iterate the container in order. And the hashfunction for char* in dict hashes for every single byte in the string, likely doing significantly more work than std::hash. --- kernel/rtlil.cc | 2 +- kernel/rtlil.h | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index 8a0080dbf..d24ab4d1e 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -35,7 +35,7 @@ YOSYS_NAMESPACE_BEGIN bool RTLIL::IdString::destruct_guard_ok = false; RTLIL::IdString::destruct_guard_t RTLIL::IdString::destruct_guard; std::vector RTLIL::IdString::global_id_storage_; -dict RTLIL::IdString::global_id_index_; +std::unordered_map RTLIL::IdString::global_id_index_; #ifndef YOSYS_NO_IDS_REFCNT std::vector RTLIL::IdString::global_refcount_storage_; std::vector RTLIL::IdString::global_free_idx_list_; diff --git a/kernel/rtlil.h b/kernel/rtlil.h index 504fa0062..745b8fcaf 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -23,6 +23,9 @@ #include "kernel/yosys_common.h" #include "kernel/yosys.h" +#include +#include + YOSYS_NAMESPACE_BEGIN namespace RTLIL @@ -122,7 +125,7 @@ struct RTLIL::IdString } destruct_guard; static std::vector global_id_storage_; - static dict global_id_index_; + static std::unordered_map global_id_index_; #ifndef YOSYS_NO_IDS_REFCNT static std::vector global_refcount_storage_; static std::vector global_free_idx_list_; From d9b3a3f7bb6f054181d587298b9ca3f19fa816b1 Mon Sep 17 00:00:00 2001 From: Drew Lewis Date: Tue, 22 Jul 2025 15:38:02 +0000 Subject: [PATCH 19/24] Improve the performance of `concat_name` in the flattening pass - Make IdString parameter pass by const ref to avoid IdString ref counting in the constructor - Remove extra std::string allocation to remove prefix - Avoid using `stringf` for concatenation --- passes/techmap/flatten.cc | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/passes/techmap/flatten.cc b/passes/techmap/flatten.cc index 6363b3432..299b18006 100644 --- a/passes/techmap/flatten.cc +++ b/passes/techmap/flatten.cc @@ -24,20 +24,36 @@ #include #include #include +#include USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN -IdString concat_name(RTLIL::Cell *cell, IdString object_name, const std::string &separator = ".") +template +[[nodiscard]] std::string concat_views(const Args&... views) { + static_assert((std::is_convertible_v && ...), + "All arguments must be convertible to std::string_view."); + const std::size_t total_size = (std::string_view(views).size() + ... + 0); + + std::string result; + result.reserve(total_size); + + (result.append(views), ...); + return result; +} + +IdString concat_name(RTLIL::Cell *cell, IdString const &object_name, const std::string &separator = ".") { - if (object_name[0] == '\\') - return stringf("%s%s%s", cell->name.c_str(), separator.c_str(), object_name.c_str() + 1); - else { - std::string object_name_str = object_name.str(); - if (object_name_str.substr(0, 8) == "$flatten") - object_name_str.erase(0, 8); - return stringf("$flatten%s%s%s", cell->name.c_str(), separator.c_str(), object_name_str.c_str()); + std::string_view object_name_view(object_name.c_str()); + if (object_name_view[0] == '\\'){ + return concat_views(cell->name.c_str(), separator, object_name_view.substr(1)); } + + constexpr std::string_view prefix = "$flatten"; + if (object_name_view.substr(0, prefix.size()) == prefix){ + object_name_view.remove_prefix(prefix.size()); + } + return concat_views(prefix, cell->name.c_str(), separator, object_name_view); } template From 5392a42a9772c049a21efc0751bf3a65fd8841f3 Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Tue, 29 Jul 2025 16:11:13 +0200 Subject: [PATCH 20/24] Update ABC to latest --- abc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/abc b/abc index e55d316cc..fcd8ac34d 160000 --- a/abc +++ b/abc @@ -1 +1 @@ -Subproject commit e55d316cc9a7f72a84a76eda555aa6ec083c9d0d +Subproject commit fcd8ac34d6105b48f32dfaf31983b071c46fb7b9 From b64484795d0f0c304494e28c0b3b40344cb9f21d Mon Sep 17 00:00:00 2001 From: Fred Tombs Date: Tue, 29 Jul 2025 15:27:59 -0400 Subject: [PATCH 21/24] Fix typos in memlib --- passes/memory/memlib.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/passes/memory/memlib.md b/passes/memory/memlib.md index 855aa1345..f3c0dd937 100644 --- a/passes/memory/memlib.md +++ b/passes/memory/memlib.md @@ -148,7 +148,7 @@ The rules for this property are as follows: - for every available width, the width needs to be a multiple of the byte size, or the byte size needs to be larger than the width -- if the byte size is larger than the width, the byte enable signel is assumed +- if the byte size is larger than the width, the byte enable signal is assumed to be one bit wide and cover the whole port - otherwise, the byte enable signal has one bit for every `byte` bits of the data port @@ -176,7 +176,7 @@ Eg. for the following properties: The cost of a given cell will be assumed to be `(8 - 7) + 7 * (used_bits / 14)`. If `widthscale` is used, The pass will attach a `BITS_USED` parameter to mapped -calls, with a bitmask of which data bits of the memory are actually in use. +cells, with a bitmask of which data bits of the memory are actually in use. The parameter width will be the widest width in the `widths` property, and the bit correspondence is defined accordingly. @@ -193,7 +193,7 @@ one of the following values: - `zero`: the memory contents are zero, memories can be mapped to this cell iff their initialization value is entirely zero or undef - `any`: the memory contents can be arbitrarily selected, and the initialization - will be passes as the `INIT` parameter to the mapped cell + will be passed as the `INIT` parameter to the mapped cell - `no_undef`: like `any`, but only 0 and 1 bit values are supported (the pass will convert any x bits to 0) @@ -234,7 +234,7 @@ Ports come in 5 kinds: - `ar`: asynchronous read port - `sr`: synchronous read port - `sw`: synchronous write port -- `arsw`: simultanous synchronous write + asynchronous read with common address (commonly found in LUT RAMs) +- `arsw`: simultaneous synchronous write + asynchronous read with common address (commonly found in LUT RAMs) - `srsw`: synchronous write + synchronous read with common address The port properties available are: @@ -419,7 +419,7 @@ If not provided, `none` is assumed for all three properties. The `wrprio` property is only allowed on write ports and defines a priority relationship between port — when `wrprio "B";` is used in definition of port `"A"`, and both ports -simultanously write to the same memory cell, the value written by port `"A"` will have +simultaneously write to the same memory cell, the value written by port `"A"` will have precedence. This property is optional, and can be used multiple times as necessary. If no relationship @@ -493,7 +493,7 @@ will disallow combining the RAM option `ABC = 2` with port option `DEF = "GHI"`. ## Ifdefs -To allow reusing a library for multiple FPGA families with slighly differing +To allow reusing a library for multiple FPGA families with slightly differing capabilities, `ifdef` (and `ifndef`) blocks are provided: ifdef IS_FANCY_FPGA_WITH_CONFIGURABLE_ASYNC_RESET { From 6440499e572d4d75ca5f857023c77b27ee1a69f8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 30 Jul 2025 00:26:56 +0000 Subject: [PATCH 22/24] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index bcb855940..2dcc7ed72 100644 --- a/Makefile +++ b/Makefile @@ -160,7 +160,7 @@ ifeq ($(OS), Haiku) CXXFLAGS += -D_DEFAULT_SOURCE endif -YOSYS_VER := 0.55+115 +YOSYS_VER := 0.55+144 YOSYS_MAJOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f1) YOSYS_MINOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f2 | cut -d'+' -f1) YOSYS_COMMIT := $(shell echo $(YOSYS_VER) | cut -d'+' -f2) From f92a53ec31edb44e985546dcb4c8d6d8c275959c Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Wed, 30 Jul 2025 10:51:54 +0200 Subject: [PATCH 23/24] verific: handle nullptr for message_id --- frontends/verific/verific.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontends/verific/verific.cc b/frontends/verific/verific.cc index cf8fad8b0..7284c7cd9 100644 --- a/frontends/verific/verific.cc +++ b/frontends/verific/verific.cc @@ -123,7 +123,7 @@ void msg_func(msg_type_t msg_type, const char *message_id, linefile_type linefil msg_type == VERIFIC_IGNORE ? "IGNORE" : msg_type == VERIFIC_INFO ? "INFO" : msg_type == VERIFIC_COMMENT ? "COMMENT" : - msg_type == VERIFIC_PROGRAM_ERROR ? "PROGRAM_ERROR" : "UNKNOWN", message_id); + msg_type == VERIFIC_PROGRAM_ERROR ? "PROGRAM_ERROR" : "UNKNOWN", message_id ? message_id : ""); string message = linefile ? stringf("%s:%d: ", LineFile::GetFileName(linefile), LineFile::GetLineNo(linefile)) : ""; message += vstringf(msg, args); From 262b00d5e5fe6a1c60c047dcbabd522309e4d1ef Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 31 Jul 2025 00:26:33 +0000 Subject: [PATCH 24/24] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 2dcc7ed72..d509b9234 100644 --- a/Makefile +++ b/Makefile @@ -160,7 +160,7 @@ ifeq ($(OS), Haiku) CXXFLAGS += -D_DEFAULT_SOURCE endif -YOSYS_VER := 0.55+144 +YOSYS_VER := 0.55+146 YOSYS_MAJOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f1) YOSYS_MINOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f2 | cut -d'+' -f1) YOSYS_COMMIT := $(shell echo $(YOSYS_VER) | cut -d'+' -f2)