From 83b095ab6ca294cb8ccd8504e5dcdae54841199a Mon Sep 17 00:00:00 2001 From: Anhijkt Date: Sun, 30 Mar 2025 15:43:41 +0300 Subject: [PATCH 1/7] opt_expr: optimize pow of 2 cells --- passes/opt/opt_expr.cc | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/passes/opt/opt_expr.cc b/passes/opt/opt_expr.cc index 62a0ffc48..5089959ae 100644 --- a/passes/opt/opt_expr.cc +++ b/passes/opt/opt_expr.cc @@ -1690,7 +1690,43 @@ skip_identity: else if (inA == inB) ACTION_DO(ID::Y, cell->getPort(ID::A)); } + if (cell->type == ID($pow) && cell->getPort(ID::A).is_fully_const() && !cell->parameters[ID::B_SIGNED].as_bool()) { + SigSpec sig_a = assign_map(cell->getPort(ID::A)); + SigSpec sig_y = assign_map(cell->getPort(ID::Y)); + int y_size = GetSize(sig_y); + unsigned int bits = unsigned(sig_a.as_int()); + int bit_count = 0; + for (; bits; bits >>= 1) + bit_count += (bits & 1); + + if (bit_count == 1) { + if (sig_a.as_int() == 2) { + log_debug("Replacing pow cell `%s' in module `%s' with left-shift\n", + cell->name.c_str(), module->name.c_str()); + cell->type = ID($shl); + cell->parameters[ID::A_WIDTH] = 1; + cell->setPort(ID::A, Const(1, 1)); + } + else { + log_debug("Replacing pow cell `%s' in module `%s' with multiply and left-shift\n", + cell->name.c_str(), module->name.c_str()); + cell->type = ID($mul); + cell->parameters[ID::A_SIGNED] = 0; + + int left_shift; + sig_a.is_onehot(&left_shift); + cell->setPort(ID::A, Const(left_shift, cell->parameters[ID::A_WIDTH].as_int())); + + SigSpec y_wire = module->addWire(NEW_ID, y_size); + cell->setPort(ID::Y, y_wire); + + module->addShl(NEW_ID, Const(1, 1), y_wire, sig_y); + } + did_something = true; + goto next_cell; + } + } if (!keepdc && cell->type == ID($mul)) { bool a_signed = cell->parameters[ID::A_SIGNED].as_bool(); From 58a515d57f137b0dab9708eebaffaff6d101c98b Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Mon, 31 Mar 2025 16:19:45 +0200 Subject: [PATCH 2/7] yosys-config: Propagate exit code for help command --- misc/yosys-config.in | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/misc/yosys-config.in b/misc/yosys-config.in index 2d9c35e4d..34cf3b53b 100755 --- a/misc/yosys-config.in +++ b/misc/yosys-config.in @@ -37,11 +37,11 @@ help() { echo " $0 --datdir/simlib.v" echo "" } >&2 - exit 1 + exit $1 } if [ $# -eq 0 ]; then - help + help 1 fi if [ "$1" = "--build" ]; then @@ -83,7 +83,7 @@ for opt; do tokens=( "${tokens[@]}" '@DATDIR@'"${opt#${prefix}datdir}" ) ;; --help|-\?|-h) if [ ${#tokens[@]} -eq 0 ]; then - help + help 0 else tokens=( "${tokens[@]}" "$opt" ) fi ;; From 66d7ffb2c55f8e1606b20ec13f7ea566438cf017 Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Tue, 1 Apr 2025 08:39:11 +0200 Subject: [PATCH 3/7] yosys-config: redirect to stderr/stdout depending of exit code --- Makefile | 4 ++-- misc/yosys-config.in | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 74128b41d..2efe23eea 100644 --- a/Makefile +++ b/Makefile @@ -1049,7 +1049,7 @@ define DOC_USAGE_STDERR docs/source/generated/$(1): $(TARGETS) docs/source/generated FORCE -$(Q) ./$(PROGRAM_PREFIX)$(1) --help 2> $$@ endef -DOCS_USAGE_STDERR := yosys-config yosys-filterlib +DOCS_USAGE_STDERR := yosys-filterlib # The in-tree ABC (yosys-abc) is only built when ABCEXTERNAL is not set. ifeq ($(ABCEXTERNAL),) @@ -1063,7 +1063,7 @@ define DOC_USAGE_STDOUT docs/source/generated/$(1): $(TARGETS) docs/source/generated $(Q) ./$(PROGRAM_PREFIX)$(1) --help > $$@ || rm $$@ endef -DOCS_USAGE_STDOUT := yosys yosys-smtbmc yosys-witness +DOCS_USAGE_STDOUT := yosys yosys-smtbmc yosys-witness yosys-config $(foreach usage,$(DOCS_USAGE_STDOUT),$(eval $(call DOC_USAGE_STDOUT,$(usage)))) docs/usage: $(addprefix docs/source/generated/,$(DOCS_USAGE_STDOUT) $(DOCS_USAGE_STDERR)) diff --git a/misc/yosys-config.in b/misc/yosys-config.in index 34cf3b53b..758ba79a9 100755 --- a/misc/yosys-config.in +++ b/misc/yosys-config.in @@ -36,7 +36,7 @@ help() { echo "" echo " $0 --datdir/simlib.v" echo "" - } >&2 + } >&$(( $1 + 1)) exit $1 } From 119e998f120832ca7e5d61f434cc5efd37fa5197 Mon Sep 17 00:00:00 2001 From: Jannis Harder Date: Tue, 1 Apr 2025 13:48:44 +0200 Subject: [PATCH 4/7] read_liberty: Faster input handling for the liberty lexer The lexer for liberty files was using istream's `get` and `unget` which are notorious for bad performance and that showed up during profiling. This replaces the direct `istream` use with a custom LibertyInputStream that does its own buffering to provide `get` and `unget` that behave the same way but are implemented with a fast path that is easy to inline and optimize. --- kernel/yosys_common.h | 6 +++++ passes/techmap/libparse.cc | 45 ++++++++++++++++++++++++++++++++++++++ passes/techmap/libparse.h | 33 +++++++++++++++++++++++++++- 3 files changed, 83 insertions(+), 1 deletion(-) diff --git a/kernel/yosys_common.h b/kernel/yosys_common.h index a68539ce1..6fadf788f 100644 --- a/kernel/yosys_common.h +++ b/kernel/yosys_common.h @@ -128,6 +128,12 @@ # error "C++17 or later compatible compiler is required" #endif +#if defined(__has_cpp_attribute) && __has_cpp_attribute(gnu::cold) +# define YS_COLD [[gnu::cold]] +#else +# define YS_COLD +#endif + #include "kernel/io.h" YOSYS_NAMESPACE_BEGIN diff --git a/passes/techmap/libparse.cc b/passes/techmap/libparse.cc index 06dd6288e..dbf191080 100644 --- a/passes/techmap/libparse.cc +++ b/passes/techmap/libparse.cc @@ -32,6 +32,51 @@ using namespace Yosys; +bool LibertyInputStream::extend_buffer_once() +{ + if (eof) + return false; + + // To support unget we leave the last already read character in the buffer + if (buf_pos > 1) { + size_t move_pos = buf_pos - 1; + memmove(buffer.data(), buffer.data() + move_pos, buf_end - move_pos); + buf_pos -= move_pos; + buf_end -= move_pos; + } + + const size_t chunk_size = 4096; + if (buffer.size() < buf_end + chunk_size) { + buffer.resize(buf_end + chunk_size); + } + + size_t read_size = f.rdbuf()->sgetn(buffer.data() + buf_end, chunk_size); + buf_end += read_size; + if (read_size < chunk_size) + eof = true; + return read_size != 0; +} + +bool LibertyInputStream::extend_buffer_at_least(size_t size) { + while (buffered_size() < size) { + if (!extend_buffer_once()) + return false; + } + return true; +} + +int LibertyInputStream::get_cold() +{ + if (buf_pos == buf_end) { + if (!extend_buffer_at_least()) + return EOF; + } + + int c = buffer[buf_pos]; + buf_pos += 1; + return c; +} + LibertyAst::~LibertyAst() { for (auto child : children) diff --git a/passes/techmap/libparse.h b/passes/techmap/libparse.h index 16808fc58..eb73e296d 100644 --- a/passes/techmap/libparse.h +++ b/passes/techmap/libparse.h @@ -90,12 +90,43 @@ namespace Yosys bool eval(dict& values); }; + class LibertyInputStream { + std::istream &f; + std::vector buffer; + size_t buf_pos = 0; + size_t buf_end = 0; + bool eof = false; + + bool extend_buffer_once(); + bool extend_buffer_at_least(size_t size = 1); + + YS_COLD int get_cold(); + + public: + LibertyInputStream(std::istream &f) : f(f) {} + + size_t buffered_size() { return buf_end - buf_pos; } + const char *buffered_data() { return buffer.data() + buf_pos; } + + int get() { + if (buf_pos == buf_end) + return get_cold(); + int c = buffer[buf_pos]; + buf_pos += 1; + return c; + } + + void unget() { + buf_pos -= 1; + } + }; + class LibertyMergedCells; class LibertyParser { friend class LibertyMergedCells; private: - std::istream &f; + LibertyInputStream f; int line; /* lexer return values: From bc01468c7545b2362a52b48f11fa0e5cccb4e75a Mon Sep 17 00:00:00 2001 From: Jannis Harder Date: Tue, 1 Apr 2025 13:50:29 +0200 Subject: [PATCH 5/7] read_liberty: Faster std::string construction in the liberty lexer This extends the `LibertyInputStream` added in the previous commit to allow arbitrary lookahead. Then this uses the lookahead to find the total length of the token within the input buffer, instead of consuming the token byte by byte while appending to a std::string. Constructing the std::string with the total length is known avoids any reallocations from growing std::string's buffer. --- passes/techmap/libparse.cc | 55 ++++++++++++++++++++++++-------------- passes/techmap/libparse.h | 11 ++++++++ 2 files changed, 46 insertions(+), 20 deletions(-) diff --git a/passes/techmap/libparse.cc b/passes/techmap/libparse.cc index dbf191080..d3d5b7d57 100644 --- a/passes/techmap/libparse.cc +++ b/passes/techmap/libparse.cc @@ -77,6 +77,16 @@ int LibertyInputStream::get_cold() return c; } +int LibertyInputStream::peek_cold(size_t offset) +{ + if (buf_pos + offset >= buf_end) { + if (!extend_buffer_at_least(offset + 1)) + return EOF; + } + + return buffer[buf_pos + offset]; +} + LibertyAst::~LibertyAst() { for (auto child : children) @@ -282,15 +292,19 @@ int LibertyParser::lexer(std::string &str) // search for identifiers, numbers, plus or minus. if (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || c == '_' || c == '-' || c == '+' || c == '.') { - str = static_cast(c); - while (1) { - c = f.get(); + f.unget(); + size_t i = 1; + while (true) { + c = f.peek(i); if (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || c == '_' || c == '-' || c == '+' || c == '.') - str += c; + i += 1; else break; } - f.unget(); + str.clear(); + str.append(f.buffered_data(), f.buffered_data() + i); + f.consume(i); + if (str == "+" || str == "-") { /* Single operator is not an identifier */ // fprintf(stderr, "LEX: char >>%s<<\n", str.c_str()); @@ -305,23 +319,24 @@ int LibertyParser::lexer(std::string &str) // if it wasn't an identifer, number of array range, // maybe it's a string? if (c == '"') { - str = ""; -#ifdef FILTERLIB - str += c; -#endif - while (1) { - c = f.get(); - if (c == '\n') - line++; - if (c == '"') { -#ifdef FILTERLIB - str += c; -#endif + size_t i = 0; + while (true) { + c = f.peek(i); + line += (c == '\n'); + if (c != '"') + i += 1; + else break; - } - str += c; } - // fprintf(stderr, "LEX: string >>%s<<\n", str.c_str()); + str.clear(); +#ifdef FILTERLIB + f.unget(); + str.append(f.buffered_data(), f.buffered_data() + i + 2); + f.consume(i + 2); +#else + str.append(f.buffered_data(), f.buffered_data() + i); + f.consume(i + 1); +#endif return 'v'; } diff --git a/passes/techmap/libparse.h b/passes/techmap/libparse.h index eb73e296d..1fcaaebee 100644 --- a/passes/techmap/libparse.h +++ b/passes/techmap/libparse.h @@ -101,6 +101,7 @@ namespace Yosys bool extend_buffer_at_least(size_t size = 1); YS_COLD int get_cold(); + YS_COLD int peek_cold(size_t offset); public: LibertyInputStream(std::istream &f) : f(f) {} @@ -116,6 +117,16 @@ namespace Yosys return c; } + int peek(size_t offset = 0) { + if (buf_pos + offset >= buf_end) + return peek_cold(offset); + return buffer[buf_pos + offset]; + } + + void consume(size_t n = 1) { + buf_pos += n; + } + void unget() { buf_pos -= 1; } From 6b5507139ecc8b0d5e71bf81ae077ff3f5258210 Mon Sep 17 00:00:00 2001 From: Anhijkt Date: Tue, 1 Apr 2025 20:37:22 +0300 Subject: [PATCH 6/7] opt_expr: requsted changes --- passes/opt/opt_expr.cc | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/passes/opt/opt_expr.cc b/passes/opt/opt_expr.cc index 5089959ae..9967c7753 100644 --- a/passes/opt/opt_expr.cc +++ b/passes/opt/opt_expr.cc @@ -1695,33 +1695,28 @@ skip_identity: SigSpec sig_y = assign_map(cell->getPort(ID::Y)); int y_size = GetSize(sig_y); - unsigned int bits = unsigned(sig_a.as_int()); - int bit_count = 0; - for (; bits; bits >>= 1) - bit_count += (bits & 1); + int bit_idx; + const auto onehot = sig_a.is_onehot(&bit_idx); - if (bit_count == 1) { - if (sig_a.as_int() == 2) { + if (onehot) { + if (bit_idx == 1) { log_debug("Replacing pow cell `%s' in module `%s' with left-shift\n", cell->name.c_str(), module->name.c_str()); cell->type = ID($shl); cell->parameters[ID::A_WIDTH] = 1; - cell->setPort(ID::A, Const(1, 1)); + cell->setPort(ID::A, Const(State::S1, 1)); } else { log_debug("Replacing pow cell `%s' in module `%s' with multiply and left-shift\n", cell->name.c_str(), module->name.c_str()); cell->type = ID($mul); cell->parameters[ID::A_SIGNED] = 0; - - int left_shift; - sig_a.is_onehot(&left_shift); - cell->setPort(ID::A, Const(left_shift, cell->parameters[ID::A_WIDTH].as_int())); + cell->setPort(ID::A, Const(bit_idx, cell->parameters[ID::A_WIDTH].as_int())); SigSpec y_wire = module->addWire(NEW_ID, y_size); cell->setPort(ID::Y, y_wire); - module->addShl(NEW_ID, Const(1, 1), y_wire, sig_y); + module->addShl(NEW_ID, Const(State::S1, 1), y_wire, sig_y); } did_something = true; goto next_cell; From c57cbfa8f9525ac230815ca9fffe751f0e63f2e7 Mon Sep 17 00:00:00 2001 From: Anhijkt Date: Tue, 1 Apr 2025 21:54:46 +0300 Subject: [PATCH 7/7] opt_expr: add test --- tests/opt/opt_pow.ys | 89 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 tests/opt/opt_pow.ys diff --git a/tests/opt/opt_pow.ys b/tests/opt/opt_pow.ys new file mode 100644 index 000000000..0fa2f88c7 --- /dev/null +++ b/tests/opt/opt_pow.ys @@ -0,0 +1,89 @@ +# Default power of two + +design -reset + +read_rtlil << EOT +autoidx 3 +attribute \cells_not_processed 1 +attribute \src ":1.1-3.10" +module \top + attribute \src ":2.17-2.20" + wire width 32 $add$:2$1_Y + attribute \src ":2.12-2.21" + wire width 32 signed $pow$:2$2_Y + attribute \src ":1.29-1.30" + wire width 15 input 1 \a + attribute \src ":1.51-1.52" + wire width 32 output 2 \b + attribute \src ":2.17-2.20" + cell $add $add$:2$1 + parameter \A_SIGNED 0 + parameter \A_WIDTH 15 + parameter \B_SIGNED 0 + parameter \B_WIDTH 32 + parameter \Y_WIDTH 32 + connect \A \a + connect \B 2 + connect \Y $add$:2$1_Y + end + attribute \src ":2.12-2.21" + cell $pow $pow$:2$2 + parameter \A_SIGNED 0 + parameter \A_WIDTH 32 + parameter \B_SIGNED 0 + parameter \B_WIDTH 32 + parameter \Y_WIDTH 32 + connect \A 2 + connect \B $add$:2$1_Y + connect \Y $pow$:2$2_Y + end + connect \b $pow$:2$2_Y +end +EOT + +select -assert-count 1 t:$pow +select -assert-none t:$shl +opt_expr +select -assert-none t:$pow +select -assert-count 1 t:$shl + +read_verilog << EOT +module ref(input wire [14:0] a, output wire [31:0] b); +assign b = 1 << (a+2); +endmodule +EOT + +equiv_make top ref equiv +select -assert-any -module equiv t:$equiv +equiv_induct +equiv_status -assert + +# Other power of 2 value + +design -reset + +read_verilog <