From 9e072ec21f9985126e1cfeb04fa7f2bc963790b3 Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 5 Dec 2018 00:23:22 +0000 Subject: [PATCH 1/6] opt_lut: new pass, to combine LUTs for tighter packing. --- Makefile | 3 +- passes/opt/Makefile.inc | 1 + passes/opt/opt_lut.cpp | 274 ++++++++++++++++++++++++++++++++++++++++ tests/opt/.gitignore | 1 + tests/opt/ice40_carry.v | 3 + tests/opt/opt_lut.v | 18 +++ tests/opt/opt_lut.ys | 15 +++ tests/opt/run-test.sh | 6 + 8 files changed, 320 insertions(+), 1 deletion(-) create mode 100644 passes/opt/opt_lut.cpp create mode 100644 tests/opt/.gitignore create mode 100644 tests/opt/ice40_carry.v create mode 100644 tests/opt/opt_lut.v create mode 100644 tests/opt/opt_lut.ys create mode 100755 tests/opt/run-test.sh diff --git a/Makefile b/Makefile index b33166059..053796e9d 100644 --- a/Makefile +++ b/Makefile @@ -160,7 +160,7 @@ ABCMKARGS += ARCHFLAGS="-DABC_USE_STDINT_H" else ifeq ($(CONFIG),gcc-static) LD = $(CXX) LDFLAGS := $(filter-out -rdynamic,$(LDFLAGS)) -static -LDLIBS := $(filter-out -lrt,$(LDLIBS)) +LDLIBS := $(filter-out -lrt,$(LDLIBS)) CXXFLAGS := $(filter-out -fPIC,$(CXXFLAGS)) CXXFLAGS += -std=c++11 -Os ABCMKARGS = CC="$(CC)" CXX="$(CXX)" LD="$(LD)" ABC_USE_LIBSTDCXX=1 LIBS="-lm -lpthread -static" OPTFLAGS="-O" \ @@ -578,6 +578,7 @@ test: $(TARGETS) $(EXTRA_TARGETS) +cd tests/various && bash run-test.sh +cd tests/sat && bash run-test.sh +cd tests/svinterfaces && bash run-test.sh $(SEEDOPT) + +cd tests/opt && bash run-test.sh @echo "" @echo " Passed \"make test\"." @echo "" diff --git a/passes/opt/Makefile.inc b/passes/opt/Makefile.inc index 0d01e9d35..0f596b1f4 100644 --- a/passes/opt/Makefile.inc +++ b/passes/opt/Makefile.inc @@ -6,6 +6,7 @@ OBJS += passes/opt/opt_reduce.o OBJS += passes/opt/opt_rmdff.o OBJS += passes/opt/opt_clean.o OBJS += passes/opt/opt_expr.o +OBJS += passes/opt/opt_lut.o ifneq ($(SMALL),1) OBJS += passes/opt/share.o diff --git a/passes/opt/opt_lut.cpp b/passes/opt/opt_lut.cpp new file mode 100644 index 000000000..4d6d491f7 --- /dev/null +++ b/passes/opt/opt_lut.cpp @@ -0,0 +1,274 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2018 whitequark + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "kernel/yosys.h" +#include "kernel/sigtools.h" +#include "kernel/modtools.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +static bool evaluate_lut(SigMap &sigmap, RTLIL::Cell *lut, dict inputs) +{ + SigSpec lut_input = sigmap(lut->getPort("\\A")); + int lut_width = lut->getParam("\\WIDTH").as_int(); + Const lut_table = lut->getParam("\\LUT"); + int lut_index = 0; + + for (int i = 0; i < lut_width; i++) + { + SigBit input = sigmap(lut_input[i]); + if (inputs.count(input)) + { + lut_index |= inputs[input] << i; + } + else + { + lut_index |= SigSpec(lut_input[i]).as_bool() << i; + } + } + + return lut_table.extract(lut_index).as_int(); +} + +static void run_lut_opts(Module *module) +{ + ModIndex index(module); + SigMap sigmap(module); + + log("Discovering LUTs.\n"); + + pool luts; + dict luts_arity; + for (auto cell : module->selected_cells()) + { + if (cell->type == "$lut") + { + int lut_width = cell->getParam("\\WIDTH").as_int(); + SigSpec lut_input = cell->getPort("\\A"); + int lut_arity = 0; + + for (auto &bit : lut_input) + { + if (bit.wire) + lut_arity++; + } + + log("Found $lut cell %s.%s with WIDTH=%d implementing %d-LUT.\n", log_id(module), log_id(cell), lut_width, lut_arity); + luts.insert(cell); + luts_arity[cell] = lut_arity; + } + } + + log("\n"); + log("Combining LUTs.\n"); + + pool worklist = luts; + while (worklist.size()) + { + auto lutA = worklist.pop(); + SigSpec lutA_input = sigmap(lutA->getPort("\\A")); + SigSpec lutA_output = sigmap(lutA->getPort("\\Y")[0]); + int lutA_width = lutA->getParam("\\WIDTH").as_int(); + int lutA_arity = luts_arity[lutA]; + + auto lutA_output_ports = index.query_ports(lutA->getPort("\\Y")); + if (lutA_output_ports.size() != 2) + continue; + + for (auto port : lutA_output_ports) + { + if (port.cell == lutA) + continue; + + if (luts.count(port.cell)) + { + auto lutB = port.cell; + SigSpec lutB_input = sigmap(lutB->getPort("\\A")); + SigSpec lutB_output = sigmap(lutB->getPort("\\Y")[0]); + int lutB_width = lutB->getParam("\\WIDTH").as_int(); + int lutB_arity = luts_arity[lutB]; + + log("Found %s.%s (cell A) feeding %s.%s (cell B).\n", log_id(module), log_id(lutA), log_id(module), log_id(lutB)); + + pool lutA_inputs; + pool lutB_inputs; + for (auto &bit : lutA_input) + { + if (bit.wire) + lutA_inputs.insert(sigmap(bit)); + } + for (auto &bit : lutB_input) + { + if(bit.wire) + lutB_inputs.insert(sigmap(bit)); + } + + pool common_inputs; + for (auto &bit : lutA_inputs) + { + if (lutB_inputs.count(bit)) + common_inputs.insert(bit); + } + + int lutM_arity = lutA_arity + lutB_arity - 1 - common_inputs.size(); + log(" Cell A is a %d-LUT. Cell B is a %d-LUT. Cells share %zu input(s) and can be merged into one %d-LUT.\n", lutA_arity, lutB_arity, common_inputs.size(), lutM_arity); + + int combine = -1; + if (combine == -1) + { + if (lutM_arity > lutA_width) + { + log(" Not combining LUTs into cell A (combined LUT too wide).\n"); + } + else if (lutB->get_bool_attribute("\\lut_keep")) + { + log(" Not combining LUTs into cell A (cell B has attribute \\lut_keep).\n"); + } + else combine = 0; + } + if (combine == -1) + { + if (lutM_arity > lutB_width) + { + log(" Not combining LUTs into cell B (combined LUT too wide).\n"); + } + else if (lutA->get_bool_attribute("\\lut_keep")) + { + log(" Not combining LUTs into cell B (cell A has attribute \\lut_keep).\n"); + } + else combine = 1; + } + + RTLIL::Cell *lutM, *lutR; + pool lutM_inputs, lutR_inputs; + if (combine == 0) + { + log(" Combining LUTs into cell A.\n"); + lutM = lutA; + lutM_inputs = lutA_inputs; + lutR = lutB; + lutR_inputs = lutB_inputs; + } + else if (combine == 1) + { + log(" Combining LUTs into cell B.\n"); + lutM = lutB; + lutM_inputs = lutB_inputs; + lutR = lutA; + lutR_inputs = lutA_inputs; + } + else + { + log(" Cannot combine LUTs.\n"); + continue; + } + + pool lutR_unique; + for (auto &bit : lutR_inputs) + { + if (!common_inputs.count(bit) && bit != lutA_output) + lutR_unique.insert(bit); + } + + int lutM_width = lutM->getParam("\\WIDTH").as_int(); + SigSpec lutM_input = sigmap(lutM->getPort("\\A")); + std::vector lutM_new_inputs; + for (int i = 0; i < lutM_width; i++) + { + if ((!lutM_input[i].wire || sigmap(lutM_input[i]) == lutA_output) && lutR_unique.size()) + { + SigBit new_input = lutR_unique.pop(); + log(" Connecting input %d as %s.\n", i, log_signal(new_input)); + lutM_new_inputs.push_back(new_input); + } + else if (sigmap(lutM_input[i]) == lutA_output) + { + log(" Disconnecting input %d.\n", i); + lutM_new_inputs.push_back(SigBit()); + } + else + { + log(" Leaving input %d as %s.\n", i, log_signal(lutM_input[i])); + lutM_new_inputs.push_back(lutM_input[i]); + } + } + log_assert(lutR_unique.size() == 0); + + RTLIL::Const lutM_new_table(State::Sx, 1 << lutM_width); + for (int eval = 0; eval < 1 << lutM_width; eval++) + { + dict eval_inputs; + for (size_t i = 0; i < lutM_new_inputs.size(); i++) + { + eval_inputs[lutM_new_inputs[i]] = (eval >> i) & 1; + } + eval_inputs[lutA_output] = evaluate_lut(sigmap, lutA, eval_inputs); + lutM_new_table[eval] = (RTLIL::State) evaluate_lut(sigmap, lutB, eval_inputs); + } + + log(" Old truth table: %s.\n", lutM->getParam("\\LUT").as_string().c_str()); + log(" New truth table: %s.\n", lutM_new_table.as_string().c_str()); + + lutM->setParam("\\LUT", lutM_new_table); + lutM->setPort("\\A", lutM_new_inputs); + lutM->setPort("\\Y", lutB_output); + + luts_arity[lutM] = lutM_arity; + luts.erase(lutR); + luts_arity.erase(lutR); + lutR->module->remove(lutR); + + worklist.insert(lutM); + worklist.erase(lutR); + } + } + } +} + +struct OptLutPass : public Pass { + OptLutPass() : Pass("opt_lut", "optimize LUT cells") { } + void help() YS_OVERRIDE + { + log("\n"); + log(" opt_lut [options] [selection]\n"); + log("\n"); + log("This pass combines cascaded $lut cells with unused inputs.\n"); + log("\n"); + } + void execute(std::vector args, RTLIL::Design *design) YS_OVERRIDE + { + log_header(design, "Executing OPT_LUT pass (optimize LUTs).\n"); + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) + { + // if (args[argidx] == "-???") { + // continue; + // } + break; + } + extra_args(args, argidx, design); + + for (auto module : design->selected_modules()) + run_lut_opts(module); + } +} OptLutPass; + +PRIVATE_NAMESPACE_END diff --git a/tests/opt/.gitignore b/tests/opt/.gitignore new file mode 100644 index 000000000..397b4a762 --- /dev/null +++ b/tests/opt/.gitignore @@ -0,0 +1 @@ +*.log diff --git a/tests/opt/ice40_carry.v b/tests/opt/ice40_carry.v new file mode 100644 index 000000000..ed938932a --- /dev/null +++ b/tests/opt/ice40_carry.v @@ -0,0 +1,3 @@ +module SB_CARRY (output CO, input I0, I1, CI); + assign CO = (I0 && I1) || ((I0 || I1) && CI); +endmodule diff --git a/tests/opt/opt_lut.v b/tests/opt/opt_lut.v new file mode 100644 index 000000000..b13db367d --- /dev/null +++ b/tests/opt/opt_lut.v @@ -0,0 +1,18 @@ +module top( + input [8:0] a, + input [8:0] b, + output [8:0] o1, + output [2:0] o2, + input [2:0] c, + input [2:0] d, + output [2:0] o3, + output [2:0] o4, + input s +); + +assign o1 = (s ? 0 : a + b); +assign o2 = (s ? a : a - b); +assign o3 = (s ? 4'b1111 : d + c); +assign o4 = (s ? d : c - d); + +endmodule diff --git a/tests/opt/opt_lut.ys b/tests/opt/opt_lut.ys new file mode 100644 index 000000000..1c7dc1473 --- /dev/null +++ b/tests/opt/opt_lut.ys @@ -0,0 +1,15 @@ +read_verilog opt_lut.v +synth_ice40 +ice40_unlut +design -save preopt + +opt_lut +design -stash postopt + +design -copy-from preopt -as preopt top +design -copy-from postopt -as postopt top +equiv_make preopt postopt equiv +techmap -map ice40_carry.v +prep -flatten -top equiv +equiv_induct +equiv_status -assert diff --git a/tests/opt/run-test.sh b/tests/opt/run-test.sh new file mode 100755 index 000000000..44ce7e674 --- /dev/null +++ b/tests/opt/run-test.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -e +for x in *.ys; do + echo "Running $x.." + ../../yosys -ql ${x%.ys}.log $x +done From ea4870b12690bd313852c952f4fd17efaf63abb6 Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 5 Dec 2018 00:28:03 +0000 Subject: [PATCH 2/6] synth_ice40: add -relut option, to run ice40_unlut and opt_lut. --- techlibs/ice40/synth_ice40.cc | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/techlibs/ice40/synth_ice40.cc b/techlibs/ice40/synth_ice40.cc index 93965a55d..7094db50d 100644 --- a/techlibs/ice40/synth_ice40.cc +++ b/techlibs/ice40/synth_ice40.cc @@ -63,6 +63,9 @@ struct SynthIce40Pass : public ScriptPass log(" -retime\n"); log(" run 'abc' with -dff option\n"); log("\n"); + log(" -relut\n"); + log(" combine LUTs after synthesis\n"); + log("\n"); log(" -nocarry\n"); log(" do not use SB_CARRY cells in output netlist\n"); log("\n"); @@ -90,7 +93,7 @@ struct SynthIce40Pass : public ScriptPass } string top_opt, blif_file, edif_file, json_file; - bool nocarry, nodffe, nobram, flatten, retime, abc2, vpr; + bool nocarry, nodffe, nobram, flatten, retime, relut, abc2, vpr; int min_ce_use; void clear_flags() YS_OVERRIDE @@ -105,6 +108,7 @@ struct SynthIce40Pass : public ScriptPass nobram = false; flatten = true; retime = false; + relut = false; abc2 = false; vpr = false; } @@ -153,6 +157,10 @@ struct SynthIce40Pass : public ScriptPass retime = true; continue; } + if (args[argidx] == "-relut") { + relut = true; + continue; + } if (args[argidx] == "-nocarry") { nocarry = true; continue; @@ -259,6 +267,10 @@ struct SynthIce40Pass : public ScriptPass run("techmap -map +/ice40/latches_map.v"); run("abc -lut 4"); run("clean"); + if (relut || help_mode) { + run("ice40_unlut", "(only if -relut)"); + run("opt_lut", " (only if -relut)"); + } } if (check_label("map_cells")) From e54c7e951c5659ae6ebb4dfe9c42adfe0498dc4b Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 5 Dec 2018 12:26:41 +0000 Subject: [PATCH 3/6] opt_lut: refactor to use a worker. NFC. --- passes/opt/opt_lut.cpp | 365 +++++++++++++++++++++-------------------- 1 file changed, 186 insertions(+), 179 deletions(-) diff --git a/passes/opt/opt_lut.cpp b/passes/opt/opt_lut.cpp index 4d6d491f7..f33b23cf4 100644 --- a/passes/opt/opt_lut.cpp +++ b/passes/opt/opt_lut.cpp @@ -24,223 +24,228 @@ USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN -static bool evaluate_lut(SigMap &sigmap, RTLIL::Cell *lut, dict inputs) +struct OptLutWorker { - SigSpec lut_input = sigmap(lut->getPort("\\A")); - int lut_width = lut->getParam("\\WIDTH").as_int(); - Const lut_table = lut->getParam("\\LUT"); - int lut_index = 0; + RTLIL::Module *module; + ModIndex index; + SigMap sigmap; - for (int i = 0; i < lut_width; i++) + bool evaluate_lut(RTLIL::Cell *lut, dict inputs) { - SigBit input = sigmap(lut_input[i]); - if (inputs.count(input)) + SigSpec lut_input = sigmap(lut->getPort("\\A")); + int lut_width = lut->getParam("\\WIDTH").as_int(); + Const lut_table = lut->getParam("\\LUT"); + int lut_index = 0; + + for (int i = 0; i < lut_width; i++) { - lut_index |= inputs[input] << i; - } - else - { - lut_index |= SigSpec(lut_input[i]).as_bool() << i; - } - } - - return lut_table.extract(lut_index).as_int(); -} - -static void run_lut_opts(Module *module) -{ - ModIndex index(module); - SigMap sigmap(module); - - log("Discovering LUTs.\n"); - - pool luts; - dict luts_arity; - for (auto cell : module->selected_cells()) - { - if (cell->type == "$lut") - { - int lut_width = cell->getParam("\\WIDTH").as_int(); - SigSpec lut_input = cell->getPort("\\A"); - int lut_arity = 0; - - for (auto &bit : lut_input) + SigBit input = sigmap(lut_input[i]); + if (inputs.count(input)) { - if (bit.wire) - lut_arity++; + lut_index |= inputs[input] << i; + } + else + { + lut_index |= SigSpec(lut_input[i]).as_bool() << i; } - - log("Found $lut cell %s.%s with WIDTH=%d implementing %d-LUT.\n", log_id(module), log_id(cell), lut_width, lut_arity); - luts.insert(cell); - luts_arity[cell] = lut_arity; } + + return lut_table.extract(lut_index).as_int(); } - log("\n"); - log("Combining LUTs.\n"); - - pool worklist = luts; - while (worklist.size()) + OptLutWorker(RTLIL::Module *module) : + module(module), index(module), sigmap(module) { - auto lutA = worklist.pop(); - SigSpec lutA_input = sigmap(lutA->getPort("\\A")); - SigSpec lutA_output = sigmap(lutA->getPort("\\Y")[0]); - int lutA_width = lutA->getParam("\\WIDTH").as_int(); - int lutA_arity = luts_arity[lutA]; + pool luts; + dict luts_arity; - auto lutA_output_ports = index.query_ports(lutA->getPort("\\Y")); - if (lutA_output_ports.size() != 2) - continue; - - for (auto port : lutA_output_ports) + log("Discovering LUTs.\n"); + for (auto cell : module->selected_cells()) { - if (port.cell == lutA) - continue; - - if (luts.count(port.cell)) + if (cell->type == "$lut") { - auto lutB = port.cell; - SigSpec lutB_input = sigmap(lutB->getPort("\\A")); - SigSpec lutB_output = sigmap(lutB->getPort("\\Y")[0]); - int lutB_width = lutB->getParam("\\WIDTH").as_int(); - int lutB_arity = luts_arity[lutB]; + int lut_width = cell->getParam("\\WIDTH").as_int(); + SigSpec lut_input = cell->getPort("\\A"); + int lut_arity = 0; - log("Found %s.%s (cell A) feeding %s.%s (cell B).\n", log_id(module), log_id(lutA), log_id(module), log_id(lutB)); - - pool lutA_inputs; - pool lutB_inputs; - for (auto &bit : lutA_input) + for (auto &bit : lut_input) { if (bit.wire) - lutA_inputs.insert(sigmap(bit)); - } - for (auto &bit : lutB_input) - { - if(bit.wire) - lutB_inputs.insert(sigmap(bit)); + lut_arity++; } - pool common_inputs; - for (auto &bit : lutA_inputs) - { - if (lutB_inputs.count(bit)) - common_inputs.insert(bit); - } + log("Found $lut cell %s.%s with WIDTH=%d implementing %d-LUT.\n", log_id(module), log_id(cell), lut_width, lut_arity); + luts.insert(cell); + luts_arity[cell] = lut_arity; + } + } - int lutM_arity = lutA_arity + lutB_arity - 1 - common_inputs.size(); - log(" Cell A is a %d-LUT. Cell B is a %d-LUT. Cells share %zu input(s) and can be merged into one %d-LUT.\n", lutA_arity, lutB_arity, common_inputs.size(), lutM_arity); + log("\n"); + log("Combining LUTs.\n"); - int combine = -1; - if (combine == -1) - { - if (lutM_arity > lutA_width) - { - log(" Not combining LUTs into cell A (combined LUT too wide).\n"); - } - else if (lutB->get_bool_attribute("\\lut_keep")) - { - log(" Not combining LUTs into cell A (cell B has attribute \\lut_keep).\n"); - } - else combine = 0; - } - if (combine == -1) - { - if (lutM_arity > lutB_width) - { - log(" Not combining LUTs into cell B (combined LUT too wide).\n"); - } - else if (lutA->get_bool_attribute("\\lut_keep")) - { - log(" Not combining LUTs into cell B (cell A has attribute \\lut_keep).\n"); - } - else combine = 1; - } + pool worklist = luts; + while (worklist.size()) + { + auto lutA = worklist.pop(); + SigSpec lutA_input = sigmap(lutA->getPort("\\A")); + SigSpec lutA_output = sigmap(lutA->getPort("\\Y")[0]); + int lutA_width = lutA->getParam("\\WIDTH").as_int(); + int lutA_arity = luts_arity[lutA]; - RTLIL::Cell *lutM, *lutR; - pool lutM_inputs, lutR_inputs; - if (combine == 0) - { - log(" Combining LUTs into cell A.\n"); - lutM = lutA; - lutM_inputs = lutA_inputs; - lutR = lutB; - lutR_inputs = lutB_inputs; - } - else if (combine == 1) - { - log(" Combining LUTs into cell B.\n"); - lutM = lutB; - lutM_inputs = lutB_inputs; - lutR = lutA; - lutR_inputs = lutA_inputs; - } - else - { - log(" Cannot combine LUTs.\n"); + auto lutA_output_ports = index.query_ports(lutA->getPort("\\Y")); + if (lutA_output_ports.size() != 2) + continue; + + for (auto port : lutA_output_ports) + { + if (port.cell == lutA) continue; - } - pool lutR_unique; - for (auto &bit : lutR_inputs) + if (luts.count(port.cell)) { - if (!common_inputs.count(bit) && bit != lutA_output) - lutR_unique.insert(bit); - } + auto lutB = port.cell; + SigSpec lutB_input = sigmap(lutB->getPort("\\A")); + SigSpec lutB_output = sigmap(lutB->getPort("\\Y")[0]); + int lutB_width = lutB->getParam("\\WIDTH").as_int(); + int lutB_arity = luts_arity[lutB]; - int lutM_width = lutM->getParam("\\WIDTH").as_int(); - SigSpec lutM_input = sigmap(lutM->getPort("\\A")); - std::vector lutM_new_inputs; - for (int i = 0; i < lutM_width; i++) - { - if ((!lutM_input[i].wire || sigmap(lutM_input[i]) == lutA_output) && lutR_unique.size()) + log("Found %s.%s (cell A) feeding %s.%s (cell B).\n", log_id(module), log_id(lutA), log_id(module), log_id(lutB)); + + pool lutA_inputs; + pool lutB_inputs; + for (auto &bit : lutA_input) { - SigBit new_input = lutR_unique.pop(); - log(" Connecting input %d as %s.\n", i, log_signal(new_input)); - lutM_new_inputs.push_back(new_input); + if (bit.wire) + lutA_inputs.insert(sigmap(bit)); } - else if (sigmap(lutM_input[i]) == lutA_output) + for (auto &bit : lutB_input) { - log(" Disconnecting input %d.\n", i); - lutM_new_inputs.push_back(SigBit()); + if(bit.wire) + lutB_inputs.insert(sigmap(bit)); + } + + pool common_inputs; + for (auto &bit : lutA_inputs) + { + if (lutB_inputs.count(bit)) + common_inputs.insert(bit); + } + + int lutM_arity = lutA_arity + lutB_arity - 1 - common_inputs.size(); + log(" Cell A is a %d-LUT. Cell B is a %d-LUT. Cells share %zu input(s) and can be merged into one %d-LUT.\n", lutA_arity, lutB_arity, common_inputs.size(), lutM_arity); + + int combine = -1; + if (combine == -1) + { + if (lutM_arity > lutA_width) + { + log(" Not combining LUTs into cell A (combined LUT too wide).\n"); + } + else if (lutB->get_bool_attribute("\\lut_keep")) + { + log(" Not combining LUTs into cell A (cell B has attribute \\lut_keep).\n"); + } + else combine = 0; + } + if (combine == -1) + { + if (lutM_arity > lutB_width) + { + log(" Not combining LUTs into cell B (combined LUT too wide).\n"); + } + else if (lutA->get_bool_attribute("\\lut_keep")) + { + log(" Not combining LUTs into cell B (cell A has attribute \\lut_keep).\n"); + } + else combine = 1; + } + + RTLIL::Cell *lutM, *lutR; + pool lutM_inputs, lutR_inputs; + if (combine == 0) + { + log(" Combining LUTs into cell A.\n"); + lutM = lutA; + lutM_inputs = lutA_inputs; + lutR = lutB; + lutR_inputs = lutB_inputs; + } + else if (combine == 1) + { + log(" Combining LUTs into cell B.\n"); + lutM = lutB; + lutM_inputs = lutB_inputs; + lutR = lutA; + lutR_inputs = lutA_inputs; } else { - log(" Leaving input %d as %s.\n", i, log_signal(lutM_input[i])); - lutM_new_inputs.push_back(lutM_input[i]); + log(" Cannot combine LUTs.\n"); + continue; } - } - log_assert(lutR_unique.size() == 0); - RTLIL::Const lutM_new_table(State::Sx, 1 << lutM_width); - for (int eval = 0; eval < 1 << lutM_width; eval++) - { - dict eval_inputs; - for (size_t i = 0; i < lutM_new_inputs.size(); i++) + pool lutR_unique; + for (auto &bit : lutR_inputs) { - eval_inputs[lutM_new_inputs[i]] = (eval >> i) & 1; + if (!common_inputs.count(bit) && bit != lutA_output) + lutR_unique.insert(bit); } - eval_inputs[lutA_output] = evaluate_lut(sigmap, lutA, eval_inputs); - lutM_new_table[eval] = (RTLIL::State) evaluate_lut(sigmap, lutB, eval_inputs); + + int lutM_width = lutM->getParam("\\WIDTH").as_int(); + SigSpec lutM_input = sigmap(lutM->getPort("\\A")); + std::vector lutM_new_inputs; + for (int i = 0; i < lutM_width; i++) + { + if ((!lutM_input[i].wire || sigmap(lutM_input[i]) == lutA_output) && lutR_unique.size()) + { + SigBit new_input = lutR_unique.pop(); + log(" Connecting input %d as %s.\n", i, log_signal(new_input)); + lutM_new_inputs.push_back(new_input); + } + else if (sigmap(lutM_input[i]) == lutA_output) + { + log(" Disconnecting input %d.\n", i); + lutM_new_inputs.push_back(SigBit()); + } + else + { + log(" Leaving input %d as %s.\n", i, log_signal(lutM_input[i])); + lutM_new_inputs.push_back(lutM_input[i]); + } + } + log_assert(lutR_unique.size() == 0); + + RTLIL::Const lutM_new_table(State::Sx, 1 << lutM_width); + for (int eval = 0; eval < 1 << lutM_width; eval++) + { + dict eval_inputs; + for (size_t i = 0; i < lutM_new_inputs.size(); i++) + { + eval_inputs[lutM_new_inputs[i]] = (eval >> i) & 1; + } + eval_inputs[lutA_output] = evaluate_lut(lutA, eval_inputs); + lutM_new_table[eval] = (RTLIL::State) evaluate_lut(lutB, eval_inputs); + } + + log(" Old truth table: %s.\n", lutM->getParam("\\LUT").as_string().c_str()); + log(" New truth table: %s.\n", lutM_new_table.as_string().c_str()); + + lutM->setParam("\\LUT", lutM_new_table); + lutM->setPort("\\A", lutM_new_inputs); + lutM->setPort("\\Y", lutB_output); + + luts_arity[lutM] = lutM_arity; + luts.erase(lutR); + luts_arity.erase(lutR); + lutR->module->remove(lutR); + + worklist.insert(lutM); + worklist.erase(lutR); } - - log(" Old truth table: %s.\n", lutM->getParam("\\LUT").as_string().c_str()); - log(" New truth table: %s.\n", lutM_new_table.as_string().c_str()); - - lutM->setParam("\\LUT", lutM_new_table); - lutM->setPort("\\A", lutM_new_inputs); - lutM->setPort("\\Y", lutB_output); - - luts_arity[lutM] = lutM_arity; - luts.erase(lutR); - luts_arity.erase(lutR); - lutR->module->remove(lutR); - - worklist.insert(lutM); - worklist.erase(lutR); } } } -} +}; struct OptLutPass : public Pass { OptLutPass() : Pass("opt_lut", "optimize LUT cells") { } @@ -267,7 +272,9 @@ struct OptLutPass : public Pass { extra_args(args, argidx, design); for (auto module : design->selected_modules()) - run_lut_opts(module); + { + OptLutWorker worker(module); + } } } OptLutPass; From 59eea0183ffaa453f77c6d439a9df8c1087c0fe0 Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 5 Dec 2018 12:35:27 +0000 Subject: [PATCH 4/6] opt_lut: collect and display statistics. --- passes/opt/opt_lut.cpp | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/passes/opt/opt_lut.cpp b/passes/opt/opt_lut.cpp index f33b23cf4..b043f6c10 100644 --- a/passes/opt/opt_lut.cpp +++ b/passes/opt/opt_lut.cpp @@ -30,6 +30,11 @@ struct OptLutWorker ModIndex index; SigMap sigmap; + pool luts; + dict luts_arity; + + int combined_count = 0; + bool evaluate_lut(RTLIL::Cell *lut, dict inputs) { SigSpec lut_input = sigmap(lut->getPort("\\A")); @@ -53,12 +58,27 @@ struct OptLutWorker return lut_table.extract(lut_index).as_int(); } + void show_stats_by_arity() + { + dict arity_counts; + int max_arity = 0; + for (auto lut_arity : luts_arity) + { + max_arity = max(max_arity, lut_arity.second); + arity_counts[lut_arity.second]++; + } + + log("Number of LUTs: %6zu\n", luts.size()); + for (int arity = 1; arity <= max_arity; arity++) + { + if (arity_counts[arity]) + log(" %d-LUT: %13d\n", arity, arity_counts[arity]); + } + } + OptLutWorker(RTLIL::Module *module) : module(module), index(module), sigmap(module) { - pool luts; - dict luts_arity; - log("Discovering LUTs.\n"); for (auto cell : module->selected_cells()) { @@ -79,10 +99,10 @@ struct OptLutWorker luts_arity[cell] = lut_arity; } } + show_stats_by_arity(); log("\n"); log("Combining LUTs.\n"); - pool worklist = luts; while (worklist.size()) { @@ -241,9 +261,12 @@ struct OptLutWorker worklist.insert(lutM); worklist.erase(lutR); + + combined_count++; } } } + show_stats_by_arity(); } }; @@ -271,10 +294,16 @@ struct OptLutPass : public Pass { } extra_args(args, argidx, design); + int total_count = 0; for (auto module : design->selected_modules()) { OptLutWorker worker(module); + total_count += worker.combined_count; } + if (total_count) + design->scratchpad_set_bool("opt.did_something", true); + log("\n"); + log("Combined %d LUTs.\n", total_count); } } OptLutPass; From e6034840702effa2ff0c2eee0cca046b5c097e86 Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 5 Dec 2018 13:14:44 +0000 Subject: [PATCH 5/6] opt_lut: always prefer to eliminate 1-LUTs. These are always either buffers or inverters, and keeping the larger LUT preserves more source-level information about the design. --- passes/opt/opt_lut.cpp | 60 +++++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/passes/opt/opt_lut.cpp b/passes/opt/opt_lut.cpp index b043f6c10..0eb6b13e5 100644 --- a/passes/opt/opt_lut.cpp +++ b/passes/opt/opt_lut.cpp @@ -154,35 +154,57 @@ struct OptLutWorker int lutM_arity = lutA_arity + lutB_arity - 1 - common_inputs.size(); log(" Cell A is a %d-LUT. Cell B is a %d-LUT. Cells share %zu input(s) and can be merged into one %d-LUT.\n", lutA_arity, lutB_arity, common_inputs.size(), lutM_arity); - int combine = -1; - if (combine == -1) + const int COMBINE_A = 1, COMBINE_B = 2, COMBINE_EITHER = COMBINE_A | COMBINE_B; + int combine_mask = 0; + if (lutM_arity > lutA_width) { - if (lutM_arity > lutA_width) - { - log(" Not combining LUTs into cell A (combined LUT too wide).\n"); - } - else if (lutB->get_bool_attribute("\\lut_keep")) - { - log(" Not combining LUTs into cell A (cell B has attribute \\lut_keep).\n"); - } - else combine = 0; + log(" Not combining LUTs into cell A (combined LUT wider than cell A).\n"); } - if (combine == -1) + else if (lutB->get_bool_attribute("\\lut_keep")) { - if (lutM_arity > lutB_width) + log(" Not combining LUTs into cell A (cell B has attribute \\lut_keep).\n"); + } + else + { + combine_mask |= COMBINE_A; + } + if (lutM_arity > lutB_width) + { + log(" Not combining LUTs into cell B (combined LUT wider than cell B).\n"); + } + else if (lutA->get_bool_attribute("\\lut_keep")) + { + log(" Not combining LUTs into cell B (cell A has attribute \\lut_keep).\n"); + } + else + { + combine_mask |= COMBINE_B; + } + + int combine = combine_mask; + if (combine == COMBINE_EITHER) + { + log(" Can combine into either cell.\n"); + if (lutA_arity == 1) { - log(" Not combining LUTs into cell B (combined LUT too wide).\n"); + log(" Cell A is a buffer or inverter, combining into cell B.\n"); + combine = COMBINE_B; } - else if (lutA->get_bool_attribute("\\lut_keep")) + else if (lutB_arity == 1) { - log(" Not combining LUTs into cell B (cell A has attribute \\lut_keep).\n"); + log(" Cell B is a buffer or inverter, combining into cell A.\n"); + combine = COMBINE_A; + } + else + { + log(" Arbitrarily combining into cell A.\n"); + combine = COMBINE_A; } - else combine = 1; } RTLIL::Cell *lutM, *lutR; pool lutM_inputs, lutR_inputs; - if (combine == 0) + if (combine == COMBINE_A) { log(" Combining LUTs into cell A.\n"); lutM = lutA; @@ -190,7 +212,7 @@ struct OptLutWorker lutR = lutB; lutR_inputs = lutB_inputs; } - else if (combine == 1) + else if (combine == COMBINE_B) { log(" Combining LUTs into cell B.\n"); lutM = lutB; From 45cb6200af13469724de42656f2c1f0f61c8766a Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 5 Dec 2018 15:26:40 +0000 Subject: [PATCH 6/6] opt_lut: add -dlogic, to avoid disturbing logic such as carry chains. --- passes/opt/opt_lut.cpp | 180 ++++++++++++++++++++++++++++++---- techlibs/ice40/synth_ice40.cc | 4 +- tests/opt/opt_lut.ys | 2 +- 3 files changed, 166 insertions(+), 20 deletions(-) diff --git a/passes/opt/opt_lut.cpp b/passes/opt/opt_lut.cpp index 0eb6b13e5..befe346a3 100644 --- a/passes/opt/opt_lut.cpp +++ b/passes/opt/opt_lut.cpp @@ -26,12 +26,15 @@ PRIVATE_NAMESPACE_BEGIN struct OptLutWorker { + dict> &dlogic; RTLIL::Module *module; ModIndex index; SigMap sigmap; pool luts; dict luts_arity; + dict> luts_dlogics; + dict> luts_dlogic_inputs; int combined_count = 0; @@ -61,23 +64,37 @@ struct OptLutWorker void show_stats_by_arity() { dict arity_counts; + dict dlogic_counts; int max_arity = 0; + for (auto lut_arity : luts_arity) { max_arity = max(max_arity, lut_arity.second); arity_counts[lut_arity.second]++; } - log("Number of LUTs: %6zu\n", luts.size()); + for (auto &lut_dlogics : luts_dlogics) + { + for (auto &lut_dlogic : lut_dlogics.second) + { + dlogic_counts[lut_dlogic->type]++; + } + } + + log("Number of LUTs: %8zu\n", luts.size()); for (int arity = 1; arity <= max_arity; arity++) { if (arity_counts[arity]) - log(" %d-LUT: %13d\n", arity, arity_counts[arity]); + log(" %d-LUT %16d\n", arity, arity_counts[arity]); + } + for (auto &dlogic_count : dlogic_counts) + { + log(" with %-12s %4d\n", dlogic_count.first.c_str(), dlogic_count.second); } } - OptLutWorker(RTLIL::Module *module) : - module(module), index(module), sigmap(module) + OptLutWorker(dict> &dlogic, RTLIL::Module *module) : + dlogic(dlogic), module(module), index(module), sigmap(module) { log("Discovering LUTs.\n"); for (auto cell : module->selected_cells()) @@ -88,15 +105,84 @@ struct OptLutWorker SigSpec lut_input = cell->getPort("\\A"); int lut_arity = 0; - for (auto &bit : lut_input) + log("Found $lut\\WIDTH=%d cell %s.%s.\n", lut_width, log_id(module), log_id(cell)); + luts.insert(cell); + + // First, find all dedicated logic we're connected to. This results in an overapproximation + // of such connections. + pool lut_all_dlogics; + for (int i = 0; i < lut_width; i++) { - if (bit.wire) + SigBit bit = lut_input[i]; + for (auto &port : index.query_ports(bit)) + { + if (dlogic.count(port.cell->type)) + { + auto &dlogic_map = dlogic[port.cell->type]; + if (dlogic_map.count(i)) + { + if (port.port == dlogic_map[i]) + { + lut_all_dlogics.insert(port.cell); + } + } + } + } + } + + // Second, make sure that the connection to dedicated logic is legal. If it is not legal, + // it means one of the two things: + // * The connection is spurious. I.e. this is dedicated logic that will be packed + // with some other LUT, and it just happens to be conected to this LUT as well. + // * The connection is illegal. + // In either of these cases, we don't need to concern ourselves with preserving the connection + // between this LUT and this dedicated logic cell. + pool lut_legal_dlogics; + pool lut_dlogic_inputs; + for (auto lut_dlogic : lut_all_dlogics) + { + auto &dlogic_map = dlogic[lut_dlogic->type]; + bool legal = true; + for (auto &dlogic_conn : dlogic_map) + { + if (lut_width <= dlogic_conn.first) + { + log(" LUT has illegal connection to %s cell %s.%s.\n", lut_dlogic->type.c_str(), log_id(module), log_id(lut_dlogic)); + log(" LUT input A[%d] not present.\n", dlogic_conn.first); + legal = false; + break; + } + if (sigmap(lut_input[dlogic_conn.first]) != sigmap(lut_dlogic->getPort(dlogic_conn.second))) + { + log(" LUT has illegal connection to %s cell %s.%s.\n", lut_dlogic->type.c_str(), log_id(module), log_id(lut_dlogic)); + log(" LUT input A[%d] (wire %s) not connected to %s port %s (wire %s).\n", dlogic_conn.first, log_signal(lut_input[dlogic_conn.first]), lut_dlogic->type.c_str(), dlogic_conn.second.c_str(), log_signal(lut_dlogic->getPort(dlogic_conn.second))); + legal = false; + break; + } + } + + if (legal) + { + log(" LUT has legal connection to %s cell %s.%s.\n", lut_dlogic->type.c_str(), log_id(module), log_id(lut_dlogic)); + lut_legal_dlogics.insert(lut_dlogic); + for (auto &dlogic_conn : dlogic_map) + lut_dlogic_inputs.insert(dlogic_conn.first); + } + } + + // Third, determine LUT arity. An n-wide LUT that has k constant inputs and m inputs shared with dedicated + // logic implements an (n-k-m)-ary function. + for (int i = 0; i < lut_width; i++) + { + SigBit bit = lut_input[i]; + if (bit.wire || lut_dlogic_inputs.count(i)) lut_arity++; } - log("Found $lut cell %s.%s with WIDTH=%d implementing %d-LUT.\n", log_id(module), log_id(cell), lut_width, lut_arity); - luts.insert(cell); + log(" Cell implements a %d-LUT.\n", lut_arity); luts_arity[cell] = lut_arity; + luts_dlogics[cell] = lut_legal_dlogics; + luts_dlogic_inputs[cell] = lut_dlogic_inputs; } } show_stats_by_arity(); @@ -111,12 +197,13 @@ struct OptLutWorker SigSpec lutA_output = sigmap(lutA->getPort("\\Y")[0]); int lutA_width = lutA->getParam("\\WIDTH").as_int(); int lutA_arity = luts_arity[lutA]; + pool &lutA_dlogic_inputs = luts_dlogic_inputs[lutA]; auto lutA_output_ports = index.query_ports(lutA->getPort("\\Y")); if (lutA_output_ports.size() != 2) continue; - for (auto port : lutA_output_ports) + for (auto &port : lutA_output_ports) { if (port.cell == lutA) continue; @@ -128,6 +215,7 @@ struct OptLutWorker SigSpec lutB_output = sigmap(lutB->getPort("\\Y")[0]); int lutB_width = lutB->getParam("\\WIDTH").as_int(); int lutB_arity = luts_arity[lutB]; + pool &lutB_dlogic_inputs = luts_dlogic_inputs[lutB]; log("Found %s.%s (cell A) feeding %s.%s (cell B).\n", log_id(module), log_id(lutA), log_id(module), log_id(lutB)); @@ -140,7 +228,7 @@ struct OptLutWorker } for (auto &bit : lutB_input) { - if(bit.wire) + if (bit.wire) lutB_inputs.insert(sigmap(bit)); } @@ -152,7 +240,15 @@ struct OptLutWorker } int lutM_arity = lutA_arity + lutB_arity - 1 - common_inputs.size(); - log(" Cell A is a %d-LUT. Cell B is a %d-LUT. Cells share %zu input(s) and can be merged into one %d-LUT.\n", lutA_arity, lutB_arity, common_inputs.size(), lutM_arity); + if (lutA_dlogic_inputs.size()) + log(" Cell A is a %d-LUT with %zu dedicated connections. ", lutA_arity, lutA_dlogic_inputs.size()); + else + log(" Cell A is a %d-LUT. ", lutA_arity); + if (lutB_dlogic_inputs.size()) + log("Cell B is a %d-LUT with %zu dedicated connections.\n", lutB_arity, lutB_dlogic_inputs.size()); + else + log("Cell B is a %d-LUT.\n", lutB_arity); + log(" Cells share %zu input(s) and can be merged into one %d-LUT.\n", common_inputs.size(), lutM_arity); const int COMBINE_A = 1, COMBINE_B = 2, COMBINE_EITHER = COMBINE_A | COMBINE_B; int combine_mask = 0; @@ -160,6 +256,10 @@ struct OptLutWorker { log(" Not combining LUTs into cell A (combined LUT wider than cell A).\n"); } + else if (lutB_dlogic_inputs.size() > 0) + { + log(" Not combining LUTs into cell A (cell B is connected to dedicated logic).\n"); + } else if (lutB->get_bool_attribute("\\lut_keep")) { log(" Not combining LUTs into cell A (cell B has attribute \\lut_keep).\n"); @@ -172,6 +272,10 @@ struct OptLutWorker { log(" Not combining LUTs into cell B (combined LUT wider than cell B).\n"); } + else if (lutA_dlogic_inputs.size() > 0) + { + log(" Not combining LUTs into cell B (cell A is connected to dedicated logic).\n"); + } else if (lutA->get_bool_attribute("\\lut_keep")) { log(" Not combining LUTs into cell B (cell A has attribute \\lut_keep).\n"); @@ -204,11 +308,13 @@ struct OptLutWorker RTLIL::Cell *lutM, *lutR; pool lutM_inputs, lutR_inputs; + pool lutM_dlogic_inputs; if (combine == COMBINE_A) { log(" Combining LUTs into cell A.\n"); lutM = lutA; lutM_inputs = lutA_inputs; + lutM_dlogic_inputs = lutA_dlogic_inputs; lutR = lutB; lutR_inputs = lutB_inputs; } @@ -217,6 +323,7 @@ struct OptLutWorker log(" Combining LUTs into cell B.\n"); lutM = lutB; lutM_inputs = lutB_inputs; + lutM_dlogic_inputs = lutB_dlogic_inputs; lutR = lutA; lutR_inputs = lutA_inputs; } @@ -238,7 +345,13 @@ struct OptLutWorker std::vector lutM_new_inputs; for (int i = 0; i < lutM_width; i++) { - if ((!lutM_input[i].wire || sigmap(lutM_input[i]) == lutA_output) && lutR_unique.size()) + bool input_unused = false; + if (sigmap(lutM_input[i]) == lutA_output) + input_unused = true; + if (!lutM_input[i].wire && !lutM_dlogic_inputs.count(i)) + input_unused = true; + + if (input_unused && lutR_unique.size()) { SigBit new_input = lutR_unique.pop(); log(" Connecting input %d as %s.\n", i, log_signal(new_input)); @@ -246,7 +359,7 @@ struct OptLutWorker } else if (sigmap(lutM_input[i]) == lutA_output) { - log(" Disconnecting input %d.\n", i); + log(" Disconnecting cascade input %d.\n", i); lutM_new_inputs.push_back(SigBit()); } else @@ -292,26 +405,59 @@ struct OptLutWorker } }; +static void split(std::vector &tokens, const std::string &text, char sep) +{ + size_t start = 0, end = 0; + while ((end = text.find(sep, start)) != std::string::npos) { + tokens.push_back(text.substr(start, end - start)); + start = end + 1; + } + tokens.push_back(text.substr(start)); +} + struct OptLutPass : public Pass { OptLutPass() : Pass("opt_lut", "optimize LUT cells") { } void help() YS_OVERRIDE { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); log(" opt_lut [options] [selection]\n"); log("\n"); log("This pass combines cascaded $lut cells with unused inputs.\n"); log("\n"); + log(" -dlogic :=[:=...]\n"); + log(" preserve connections to dedicated logic cell that has ports\n"); + log(" connected to LUT inputs . this includes\n"); + log(" the case where both LUT and dedicated logic input are connected to\n"); + log(" the same constant.\n"); + log("\n"); } void execute(std::vector args, RTLIL::Design *design) YS_OVERRIDE { log_header(design, "Executing OPT_LUT pass (optimize LUTs).\n"); + dict> dlogic; + size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { - // if (args[argidx] == "-???") { - // continue; - // } + if (args[argidx] == "-dlogic" && argidx+1 < args.size()) { + std::vector tokens; + split(tokens, args[++argidx], ':'); + if (tokens.size() < 2) + log_cmd_error("The -dlogic option requires at least one connection.\n"); + IdString type = "\\" + tokens[0]; + for (auto it = tokens.begin() + 1; it != tokens.end(); ++it) { + std::vector conn_tokens; + split(conn_tokens, *it, '='); + if (conn_tokens.size() != 2) + log_cmd_error("Invalid format of -dlogic signal mapping.\n"); + IdString logic_port = "\\" + conn_tokens[0]; + int lut_input = atoi(conn_tokens[1].c_str()); + dlogic[type][lut_input] = logic_port; + } + continue; + } break; } extra_args(args, argidx, design); @@ -319,7 +465,7 @@ struct OptLutPass : public Pass { int total_count = 0; for (auto module : design->selected_modules()) { - OptLutWorker worker(module); + OptLutWorker worker(dlogic, module); total_count += worker.combined_count; } if (total_count) diff --git a/techlibs/ice40/synth_ice40.cc b/techlibs/ice40/synth_ice40.cc index 7094db50d..cc4627cd3 100644 --- a/techlibs/ice40/synth_ice40.cc +++ b/techlibs/ice40/synth_ice40.cc @@ -268,8 +268,8 @@ struct SynthIce40Pass : public ScriptPass run("abc -lut 4"); run("clean"); if (relut || help_mode) { - run("ice40_unlut", "(only if -relut)"); - run("opt_lut", " (only if -relut)"); + run("ice40_unlut", " (only if -relut)"); + run("opt_lut -dlogic SB_CARRY:I0=1:I1=2:CI=3", "(only if -relut)"); } } diff --git a/tests/opt/opt_lut.ys b/tests/opt/opt_lut.ys index 1c7dc1473..86ad93bb3 100644 --- a/tests/opt/opt_lut.ys +++ b/tests/opt/opt_lut.ys @@ -3,7 +3,7 @@ synth_ice40 ice40_unlut design -save preopt -opt_lut +opt_lut -dlogic SB_CARRY:I0=1:I1=2:CI=3 design -stash postopt design -copy-from preopt -as preopt top