From 387a235158beb2b3dc8c403c78465becfb8a5f81 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Wed, 6 Nov 2024 12:48:32 +0100 Subject: [PATCH 01/13] functional, glift: use fold overload of IdString::in instead of pool literals --- kernel/functional.cc | 22 +++++++++++----------- passes/cmds/glift.cc | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/kernel/functional.cc b/kernel/functional.cc index c270a2bb6..adf7bfb0c 100644 --- a/kernel/functional.cc +++ b/kernel/functional.cc @@ -253,7 +253,7 @@ public: int y_width = parameters.at(ID(Y_WIDTH), Const(-1)).as_int(); bool a_signed = parameters.at(ID(A_SIGNED), Const(0)).as_bool(); bool b_signed = parameters.at(ID(B_SIGNED), Const(0)).as_bool(); - if(cellType.in({ID($add), ID($sub), ID($and), ID($or), ID($xor), ID($xnor), ID($mul)})){ + if(cellType.in(ID($add), ID($sub), ID($and), ID($or), ID($xor), ID($xnor), ID($mul))){ bool is_signed = a_signed && b_signed; Node a = factory.extend(inputs.at(ID(A)), y_width, is_signed); Node b = factory.extend(inputs.at(ID(B)), y_width, is_signed); @@ -273,14 +273,14 @@ public: return factory.bitwise_not(factory.bitwise_xor(a, b)); else log_abort(); - }else if(cellType.in({ID($eq), ID($ne), ID($eqx), ID($nex), ID($le), ID($lt), ID($ge), ID($gt)})){ + }else if(cellType.in(ID($eq), ID($ne), ID($eqx), ID($nex), ID($le), ID($lt), ID($ge), ID($gt))){ bool is_signed = a_signed && b_signed; int width = max(a_width, b_width); Node a = factory.extend(inputs.at(ID(A)), width, is_signed); Node b = factory.extend(inputs.at(ID(B)), width, is_signed); - if(cellType.in({ID($eq), ID($eqx)})) + if(cellType.in(ID($eq), ID($eqx))) return factory.extend(factory.equal(a, b), y_width, false); - else if(cellType.in({ID($ne), ID($nex)})) + else if(cellType.in(ID($ne), ID($nex))) return factory.extend(factory.not_equal(a, b), y_width, false); else if(cellType == ID($lt)) return factory.extend(is_signed ? factory.signed_greater_than(b, a) : factory.unsigned_greater_than(b, a), y_width, false); @@ -292,7 +292,7 @@ public: return factory.extend(is_signed ? factory.signed_greater_equal(a, b) : factory.unsigned_greater_equal(a, b), y_width, false); else log_abort(); - }else if(cellType.in({ID($logic_or), ID($logic_and)})){ + }else if(cellType.in(ID($logic_or), ID($logic_and))){ Node a = factory.reduce_or(inputs.at(ID(A))); Node b = factory.reduce_or(inputs.at(ID(B))); Node y = cellType == ID($logic_and) ? factory.bitwise_and(a, b) : factory.bitwise_or(a, b); @@ -309,13 +309,13 @@ public: Node a = factory.reduce_or(inputs.at(ID(A))); Node y = factory.bitwise_not(a); return factory.extend(y, y_width, false); - }else if(cellType.in({ID($reduce_or), ID($reduce_bool)})){ + }else if(cellType.in(ID($reduce_or), ID($reduce_bool))){ Node a = factory.reduce_or(inputs.at(ID(A))); return factory.extend(a, y_width, false); }else if(cellType == ID($reduce_and)){ Node a = factory.reduce_and(inputs.at(ID(A))); return factory.extend(a, y_width, false); - }else if(cellType.in({ID($reduce_xor), ID($reduce_xnor)})){ + }else if(cellType.in(ID($reduce_xor), ID($reduce_xnor))){ Node a = factory.reduce_xor(inputs.at(ID(A))); Node y = cellType == ID($reduce_xnor) ? factory.bitwise_not(a) : a; return factory.extend(y, y_width, false); @@ -355,7 +355,7 @@ public: int offset = parameters.at(ID(OFFSET)).as_int(); Node a = inputs.at(ID(A)); return factory.slice(a, offset, y_width); - }else if(cellType.in({ID($div), ID($mod), ID($divfloor), ID($modfloor)})) { + }else if(cellType.in(ID($div), ID($mod), ID($divfloor), ID($modfloor))) { int width = max(a_width, b_width); bool is_signed = a_signed && b_signed; Node a = factory.extend(inputs.at(ID(A)), width, is_signed); @@ -397,7 +397,7 @@ public: } else log_error("unhandled cell in CellSimplifier %s\n", cellType.c_str()); } else { - if(cellType.in({ID($mod), ID($modfloor)})) + if(cellType.in(ID($mod), ID($modfloor))) return factory.extend(factory.unsigned_mod(a, b), y_width, false); else return factory.extend(factory.unsigned_div(a, b), y_width, false); @@ -439,12 +439,12 @@ public: return handle_lcu(inputs.at(ID(P)), inputs.at(ID(G)), inputs.at(ID(CI))); } else if(cellType == ID($alu)) { return handle_alu(inputs.at(ID(A)), inputs.at(ID(B)), y_width, a_signed && b_signed, inputs.at(ID(CI)), inputs.at(ID(BI))); - } else if(cellType.in({ID($assert), ID($assume), ID($live), ID($fair), ID($cover)})) { + } else if(cellType.in(ID($assert), ID($assume), ID($live), ID($fair), ID($cover))) { Node a = factory.mux(factory.constant(Const(State::S1, 1)), inputs.at(ID(A)), inputs.at(ID(EN))); auto &output = factory.add_output(cellName, cellType, Sort(1)); output.set_value(a); return {}; - } else if(cellType.in({ID($anyconst), ID($allconst), ID($anyseq), ID($allseq)})) { + } else if(cellType.in(ID($anyconst), ID($allconst), ID($anyseq), ID($allseq))) { int width = parameters.at(ID(WIDTH)).as_int(); auto &input = factory.add_input(cellName, cellType, Sort(width)); return factory.value(input); diff --git a/passes/cmds/glift.cc b/passes/cmds/glift.cc index 8553b02a5..6a7d070d7 100644 --- a/passes/cmds/glift.cc +++ b/passes/cmds/glift.cc @@ -184,7 +184,7 @@ private: std::vector connections(module->connections()); for(auto &cell : module->cells().to_vector()) { - if (!cell->type.in({ID($_AND_), ID($_NAND_), ID($_OR_), ID($_NOR_), ID($_XOR_), ID($_XNOR_), ID($_MUX_), ID($_NMUX_), ID($_NOT_), ID($anyconst), ID($allconst), ID($assume), ID($assert)}) && module->design->module(cell->type) == nullptr) { + if (!cell->type.in(ID($_AND_), ID($_NAND_), ID($_OR_), ID($_NOR_), ID($_XOR_), ID($_XNOR_), ID($_MUX_), ID($_NMUX_), ID($_NOT_), ID($anyconst), ID($allconst), ID($assume), ID($assert)) && module->design->module(cell->type) == nullptr) { log_cmd_error("Unsupported cell type \"%s\" found. Run `techmap` first.\n", cell->type.c_str()); } if (cell->type.in(ID($_AND_), ID($_NAND_), ID($_OR_), ID($_NOR_))) { From ff6c9446c038bc23baffedb81178a6af3935ef32 Mon Sep 17 00:00:00 2001 From: George Rennie Date: Tue, 12 Nov 2024 01:05:15 +0100 Subject: [PATCH 02/13] bufnorm: preserve constant bits when mapping back to connections --- passes/techmap/bufnorm.cc | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/passes/techmap/bufnorm.cc b/passes/techmap/bufnorm.cc index 163e42c0f..a4552c71b 100644 --- a/passes/techmap/bufnorm.cc +++ b/passes/techmap/bufnorm.cc @@ -404,6 +404,17 @@ struct BufnormPass : public Pass { pool added_buffers; + const auto lookup_mapping = [&mapped_bits](const SigBit bit, bool default_sx = false) + { + if (!bit.is_wire()) + return bit; + + if (default_sx) + return mapped_bits.at(bit, State::Sx); + + return mapped_bits.at(bit); + }; + auto make_buffer_f = [&](const IdString &type, const SigSpec &src, const SigSpec &dst) { auto it = old_buffers.find(pair(type, dst)); @@ -438,12 +449,8 @@ struct BufnormPass : public Pass { bool chain_this_wire = chain_this_wire_f(wire); SigSpec keysig = sigmap(wire), insig = wire, outsig = wire; - for (int i = 0; i < GetSize(insig); i++) { - if (keysig[i].is_wire()) - insig[i] = mapped_bits.at(keysig[i], State::Sx); - else - insig[i] = keysig[i]; - } + for (int i = 0; i < GetSize(insig); i++) + insig[i] = lookup_mapping(keysig[i], true); if (chain_this_wire) { for (int i = 0; i < GetSize(outsig); i++) @@ -491,7 +498,7 @@ struct BufnormPass : public Pass { SigSpec newsig = conn.second; for (auto &bit : newsig) - bit = mapped_bits[sigmap(bit)]; + bit = lookup_mapping(sigmap(bit)); if (conn.second != newsig) { log(" fixing input signal on cell %s port %s: %s\n", From 3afb7725f1030b0b79ecc288ac9d98268698b6ca Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 12 Nov 2024 00:20:17 +0000 Subject: [PATCH 03/13] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 063d3f068..2dda052f9 100644 --- a/Makefile +++ b/Makefile @@ -155,7 +155,7 @@ ifeq ($(OS), Haiku) CXXFLAGS += -D_DEFAULT_SOURCE endif -YOSYS_VER := 0.47+22 +YOSYS_VER := 0.47+42 # Note: We arrange for .gitcommit to contain the (short) commit hash in # tarballs generated with git-archive(1) using .gitattributes. The git repo From 49e1597ea4da316899cbc1aaa317f8088d297bb4 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Tue, 12 Nov 2024 01:21:07 +0100 Subject: [PATCH 04/13] filterlib: preserve value quotes --- passes/techmap/libparse.cc | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/passes/techmap/libparse.cc b/passes/techmap/libparse.cc index ebfb325fc..95f5a8fcb 100644 --- a/passes/techmap/libparse.cc +++ b/passes/techmap/libparse.cc @@ -115,12 +115,19 @@ int LibertyParser::lexer(std::string &str) // maybe it's a string? if (c == '"') { str = ""; +#ifdef FILTERLIB + str += c; +#endif while (1) { c = f.get(); if (c == '\n') line++; - if (c == '"') + if (c == '"') { +#ifdef FILTERLIB + str += c; +#endif break; + } str += c; } // fprintf(stderr, "LEX: string >>%s<<\n", str.c_str()); From 6d4f056a353faeaa69dc23bb30c1ea05f38aae80 Mon Sep 17 00:00:00 2001 From: Robin Ole Heinemann Date: Tue, 12 Nov 2024 01:38:57 +0100 Subject: [PATCH 05/13] cxxrtl: use debug attrs of alias not aliasee --- backends/cxxrtl/cxxrtl_backend.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backends/cxxrtl/cxxrtl_backend.cc b/backends/cxxrtl/cxxrtl_backend.cc index 1c22e7ae9..b72caf119 100644 --- a/backends/cxxrtl/cxxrtl_backend.cc +++ b/backends/cxxrtl/cxxrtl_backend.cc @@ -2497,7 +2497,7 @@ struct CxxrtlWorker { // Alias of a member wire const RTLIL::Wire *aliasee = debug_wire_type.sig_subst.as_wire(); f << indent << "items->add(path, " << escape_cxx_string(get_hdl_name(wire)) << ", "; - dump_debug_attrs(aliasee); + dump_debug_attrs(wire); f << ", "; // If the aliasee is an outline, then the alias must be an outline, too; otherwise downstream // tooling has no way to find out about the outline. From 06e3ac4415095aac00a485efcd32484027c613a8 Mon Sep 17 00:00:00 2001 From: Mike Inouye Date: Tue, 12 Nov 2024 18:46:26 +0000 Subject: [PATCH 06/13] Fix bug when setting Verific runtime string flags. Signed-off-by: Mike Inouye --- 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 0cdf772a8..a247bc2eb 100644 --- a/frontends/verific/verific.cc +++ b/frontends/verific/verific.cc @@ -4184,7 +4184,7 @@ struct VerificPass : public Pass { } if (v[0] == '"') { std::string s = v.substr(1, GetSize(v)-2); - RuntimeFlags::SetStringVar(k.c_str(), v.c_str()); + RuntimeFlags::SetStringVar(k.c_str(), s.c_str()); goto check_error; } char *endptr; From 8d0bf3f35e6e33d4814aca17b35612b396efe08e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 13 Nov 2024 00:20:36 +0000 Subject: [PATCH 07/13] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 2dda052f9..d1b6ff2c1 100644 --- a/Makefile +++ b/Makefile @@ -155,7 +155,7 @@ ifeq ($(OS), Haiku) CXXFLAGS += -D_DEFAULT_SOURCE endif -YOSYS_VER := 0.47+42 +YOSYS_VER := 0.47+46 # Note: We arrange for .gitcommit to contain the (short) commit hash in # tarballs generated with git-archive(1) using .gitattributes. The git repo From 2dba3450497627726bbd6bbd6b1ba5c286b573c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Wed, 13 Nov 2024 16:20:05 +0100 Subject: [PATCH 08/13] portarcs: New command to derive propagation arcs --- kernel/timinginfo.h | 8 + passes/cmds/Makefile.inc | 1 + passes/cmds/portarcs.cc | 310 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 319 insertions(+) create mode 100644 passes/cmds/portarcs.cc diff --git a/kernel/timinginfo.h b/kernel/timinginfo.h index e7e4eab6e..8eb7eb738 100644 --- a/kernel/timinginfo.h +++ b/kernel/timinginfo.h @@ -37,6 +37,14 @@ struct TimingInfo bool operator==(const NameBit& nb) const { return nb.name == name && nb.offset == offset; } bool operator!=(const NameBit& nb) const { return !operator==(nb); } unsigned int hash() const { return mkhash_add(name.hash(), offset); } + std::optional get_connection(RTLIL::Cell *cell) { + if (!cell->hasPort(name)) + return {}; + auto &port = cell->getPort(name); + if (offset >= port.size()) + return {}; + return port[offset]; + } }; struct BitBit { diff --git a/passes/cmds/Makefile.inc b/passes/cmds/Makefile.inc index af5451b89..95c799f8f 100644 --- a/passes/cmds/Makefile.inc +++ b/passes/cmds/Makefile.inc @@ -50,3 +50,4 @@ OBJS += passes/cmds/dft_tag.o OBJS += passes/cmds/future.o OBJS += passes/cmds/box_derive.o OBJS += passes/cmds/example_dt.o +OBJS += passes/cmds/portarcs.o diff --git a/passes/cmds/portarcs.cc b/passes/cmds/portarcs.cc new file mode 100644 index 000000000..7a1d5497c --- /dev/null +++ b/passes/cmds/portarcs.cc @@ -0,0 +1,310 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Martin PoviĊĦer + * + * 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/timinginfo.h" +#include "kernel/rtlil.h" +#include "kernel/utils.h" +#include "kernel/celltypes.h" + +PRIVATE_NAMESPACE_BEGIN +USING_YOSYS_NAMESPACE + +static RTLIL::SigBit canonical_bit(RTLIL::SigBit bit) +{ + RTLIL::Wire *w; + while ((w = bit.wire) != NULL && !w->port_input && + w->driverCell()->type.in(ID($buf), ID($_BUF_))) { + bit = w->driverCell()->getPort(ID::A)[bit.offset]; + } + return bit; +} + +struct PortarcsPass : Pass { + PortarcsPass() : Pass("portarcs", "derive port arcs for propagation delay") {} + + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" portarcs [options] [selection]\n"); + log("\n"); + log("This command characterizes the combinational content of selected modules and\n"); + log("derives timing arcs going from module inputs to module outputs representing the\n"); + log("propagation delay of the module.\n"); + log("\n"); + log(" -draw\n"); + log(" plot the computed delay table to the terminal\n"); + log("\n"); + log(" -icells\n"); + log(" assign unit delay to gates from the internal Yosys cell library\n"); + log("\n"); + log(" -write\n"); + log(" write the computed arcs back into the module as $specify2 instances\n"); + log("\n"); + } + + void execute(std::vector args, RTLIL::Design *d) override + { + log_header(d, "Executing PORTARCS pass. (derive propagation arcs)\n"); + + size_t argidx; + bool icells_mode = false, write_mode = false, draw_mode = false; + for (argidx = 1; argidx < args.size(); argidx++) { + if (args[argidx] == "-icells") + icells_mode = true; + else if (args[argidx] == "-write") + write_mode = true; + else if (args[argidx] == "-draw") + draw_mode = true; + else + break; + } + extra_args(args, argidx, d); + + d->bufNormalize(true); + TimingInfo tinfo(d); + + if (icells_mode) { + CellTypes ct; + ct.setup_stdcells_eval(); + for (auto [id, type] : ct.cell_types) { + auto &tdata = tinfo.data[id]; + tdata.has_inputs = true; + for (auto inp : type.inputs) + for (auto out : type.outputs) + tdata.comb[TimingInfo::BitBit({inp, 0}, {out, 0})] = 1000; + } + } + + for (auto m : d->selected_whole_modules_warn()) { + bool ambiguous_ports = false; + SigSpec inputs, outputs; + for (auto port : m->ports) { + Wire *w = m->wire(port); + log_assert(w->port_input || w->port_output); + if (w->port_input && w->port_output) { + log_warning("Module '%s' with ambiguous direction on port %s ignored.\n", + log_id(m), log_id(w)); + ambiguous_ports = true; + break; + } + if (w->port_input) + inputs.append(w); + else + outputs.append(w); + } + if (ambiguous_ports) + continue; + + SigSpec ordering; + { + TopoSort sort; + + for (auto cell : m->cells()) + if (cell->type != ID($buf)) { + auto tdata = tinfo.find(cell->type); + if (tdata == tinfo.end()) + log_cmd_error("Missing timing data for module '%s'.\n", log_id(cell->type)); + for (auto [edge, delay] : tdata->second.comb) { + auto from = edge.first.get_connection(cell); + auto to = edge.second.get_connection(cell); + if (from && to) { + auto from_c = canonical_bit(from.value()); + if (from_c.wire) + sort.edge(from_c, to.value()); + } + } + } + + if (!sort.sort()) + log_error("Failed to sort instances in module %s.\n", log_id(m)); + + ordering = sort.sorted; + } + + dict annotations; + std::vector> allocated; + std::vector recycling; + + auto alloc_for_bit = [&](SigBit bit) { + if (!recycling.empty()) { + annotations[bit] = recycling.back(); + recycling.pop_back(); + } else { + int *p = new int[std::max(1, inputs.size())]; + allocated.emplace_back(p); + annotations[bit] = p; + } + }; + + for (auto bit : outputs) { + SigBit bit_c = canonical_bit(bit); + alloc_for_bit(bit_c); + + // consistency check + annotations.at(bit_c)[0] = (intptr_t) bit_c.wire; + } + + for (int i = ordering.size() - 1; i >= 0; i--) { + SigBit bit = ordering[i]; + + if (!bit.wire->port_input) { + auto cell = bit.wire->driverCell(); + auto tdata = tinfo.find(cell->type); + log_assert(tdata != tinfo.end()); + for (auto [edge, delay] : tdata->second.comb) { + auto from = edge.first.get_connection(cell); + auto to = edge.second.get_connection(cell); + if (from && to && to.value() == bit) { + auto from_c = canonical_bit(from.value()); + if (from_c.wire) { + if (!annotations.count(from_c)) { + alloc_for_bit(from_c); + + // consistency check + annotations.at(from_c)[0] = (intptr_t) from_c.wire; + } else { + // consistency check + log_assert(annotations.at(from_c)[0] == ((int) (intptr_t) from_c.wire)); + } + } + } + } + } + + if (annotations.count(bit)) { + // consistency check + log_assert(annotations.at(bit)[0] == ((int) (intptr_t) bit.wire)); + + recycling.push_back(annotations.at(ordering[i])); + } + } + log_debug("Allocated %lux%d\n", allocated.size(), inputs.size()); + + for (auto bit : outputs) { + int *p = annotations.at(canonical_bit(bit)); + for (int i = 0; i < inputs.size(); i++) + p[i] = 0; + } + + for (int i = 0; i < ordering.size(); i++) { + SigBit bit = ordering[i]; + int *p = annotations.at(bit); + if (bit.wire->port_input) { + for (int j = 0; j < inputs.size(); j++) + p[j] = (j == i) ? 0 : -1; + } else { + for (int j = 0; j < inputs.size(); j++) + p[j] = -1; + + auto cell = ordering[i].wire->driverCell(); + auto tdata = tinfo.find(cell->type); + log_assert(tdata != tinfo.end()); + for (auto [edge, delay] : tdata->second.comb) { + auto from = edge.first.get_connection(cell); + auto to = edge.second.get_connection(cell); + if (from && to && to.value() == ordering[i]) { + auto from_c = canonical_bit(from.value()); + if (from_c.wire) { + int *q = annotations.at(from_c); + for (int j = 0; j < inputs.size(); j++) + if (q[j] >= 0) + p[j] = std::max(p[j], q[j] + delay); + } + } + } + } + } + + if (draw_mode) { + auto bit_str = [](SigBit bit) { + return stringf("%s%d", RTLIL::unescape_id(bit.wire->name.str()).c_str(), bit.offset); + }; + + std::vector headings; + int top_length = 0; + for (auto bit : inputs) { + headings.push_back(bit_str(bit)); + top_length = std::max(top_length, (int) headings.back().size()); + } + + int max_delay = 0; + for (auto bit : outputs) { + int *p = annotations.at(canonical_bit(bit)); + for (auto i = 0; i < inputs.size(); i++) + if (p[i] > max_delay) + max_delay = p[i]; + } + + log("Delay legend:\n\n"); + log(" "); + for (int i = 0; i < 24; i++) + log("\033[48;5;%dm ", 232+i); + log("\033[0m\n"); + log(" |%22s|\n", ""); + log(" 0%22s%d\n", "", max_delay); + log("\n"); + for (int k = top_length - 1; k >= 0; k--) { + log(" %10s ", ""); + for (auto &h : headings) + log("%c", (k < (int) h.size()) ? h[k] : ' '); + log("\n"); + } + log("\n"); + + for (auto bit : outputs) { + log(" %10s ", bit_str(bit).c_str()); + int *p = annotations.at(canonical_bit(bit)); + for (auto i = 0; i < inputs.size(); i++) + log("\033[48;5;%dm ", 232 + ((std::max(p[i], 0) * 24) - 1) / max_delay); + log("\033[0m\n"); + } + } + + if (write_mode) { + for (auto bit : outputs) { + int *p = annotations.at(canonical_bit(bit)); + for (auto i = 0; i < inputs.size(); i++) { + if (p[i] >= 0) { + Cell *spec = m->addCell(NEW_ID, ID($specify2)); + spec->setParam(ID::SRC_WIDTH, 1); + spec->setParam(ID::DST_WIDTH, 1); + spec->setParam(ID::T_FALL_MAX, p[i]); + spec->setParam(ID::T_FALL_TYP, p[i]); + spec->setParam(ID::T_FALL_MIN, p[i]); + spec->setParam(ID::T_RISE_MAX, p[i]); + spec->setParam(ID::T_RISE_TYP, p[i]); + spec->setParam(ID::T_RISE_MIN, p[i]); + spec->setParam(ID::SRC_DST_POL, false); + spec->setParam(ID::SRC_DST_PEN, false); + spec->setParam(ID::FULL, false); + spec->setPort(ID::EN, Const(1, 1)); + spec->setPort(ID::SRC, inputs[i]); + spec->setPort(ID::DST, bit); + } + } + } + } + } + d->bufNormalize(false); + } +} PortarcsPass; + +PRIVATE_NAMESPACE_END From 08ed2c765e5dd861ee00684558c04d3c4dd3a716 Mon Sep 17 00:00:00 2001 From: Lofty Date: Sun, 3 Nov 2024 16:24:40 +0000 Subject: [PATCH 09/13] dfflibmap: enable inference --- passes/techmap/dfflibmap.cc | 368 +++++++++++++++++++++++++++++++++--- 1 file changed, 342 insertions(+), 26 deletions(-) diff --git a/passes/techmap/dfflibmap.cc b/passes/techmap/dfflibmap.cc index 43eef1ac0..49d7f01f0 100644 --- a/passes/techmap/dfflibmap.cc +++ b/passes/techmap/dfflibmap.cc @@ -26,6 +26,248 @@ USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN +struct string_helper { + std::string s, expr; + + string_helper(std::string s) : s{s}, expr{s} {} + + bool empty() { + return s.empty(); + } + + char peek() { + return s[0]; + } + + char next() { + char c = s[0]; + s = s.substr(1, s.size()); + return c; + } + + std::string pin() { + auto length = s.find_first_of("\t()'!^*& +|"); + auto pin = s.substr(0, length); + s = s.substr(length, s.size()); + return pin; + } + + std::string full_expr() { + return expr; + } +}; + +enum expression_kind { + AND, + OR, + NOT, + XOR, + // the standard specifies constants, but they're probably rare in practice. + PIN, + EMPTY +}; + +struct expression_tree { + expression_kind kind; + std::string name; + std::vector children; + + expression_tree() : kind(expression_kind::EMPTY) {} + + void get_pin_names(pool& names) { + if (kind == expression_kind::PIN) { + names.insert(name); + } else { + for (auto& child : children) + child.get_pin_names(names); + } + } + + bool eval(dict& values) { + bool result = false; + switch (kind) { + case expression_kind::AND: + result = true; + for (auto& child : children) + result &= child.eval(values); + return result; + case expression_kind::OR: + result = false; + for (auto& child : children) + result |= child.eval(values); + return result; + case expression_kind::NOT: + log_assert(children.size() == 1); + return !children[0].eval(values); + case expression_kind::XOR: + result = false; + for (auto& child : children) + result ^= child.eval(values); + return result; + case expression_kind::PIN: + return values.at(name); + case expression_kind::EMPTY: + log_assert(false); + } + return false; + } + + std::string enable_pin(const std::string& ff_output, std::string &data_name, bool &data_not_inverted, bool &enable_not_inverted) { + auto pin_name_pool = pool{}; + get_pin_names(pin_name_pool); + if (pin_name_pool.size() != 3) + return ""; + if (!pin_name_pool.count(ff_output)) + return ""; + pin_name_pool.erase(ff_output); + auto pins = std::vector(pin_name_pool.begin(), pin_name_pool.end()); + int lut = 0; + for (int n = 0; n < 8; n++) { + auto values = dict{}; + values.insert(std::make_pair(pins[0], (n & 1) == 1)); + values.insert(std::make_pair(pins[1], (n & 2) == 2)); + values.insert(std::make_pair(ff_output, (n & 4) == 4)); + if (eval(values)) + lut |= 1 << n; + } + // the ff output Q is in a known bit location, so we now just have to compare the LUT mask to known values to find the enable pin and polarity. + if (lut == 0xD8) { + data_not_inverted = true; + data_name = pins[1]; + enable_not_inverted = true; + return pins[0]; + } + if (lut == 0xB8) { + data_not_inverted = true; + data_name = pins[0]; + enable_not_inverted = true; + return pins[1]; + } + if (lut == 0xE4) { + data_not_inverted = true; + data_name = pins[1]; + enable_not_inverted = false; + return pins[0]; + } + if (lut == 0xE2) { + data_not_inverted = true; + data_name = pins[0]; + enable_not_inverted = false; + return pins[1]; + } + // this does not match an enable flop. + data_not_inverted = false; + data_name = ""; + enable_not_inverted = false; + return ""; + } +}; + +// https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html +expression_tree parse_expression(string_helper &s, int min_prio = 0) { + if (s.empty()) + return expression_tree{}; + + char c = s.peek(); + auto lhs = expression_tree{}; + + while (isspace(c)) { + if (s.empty()) + return lhs; + s.next(); + c = s.peek(); + } + + if (isalpha(c)) { // pin + lhs.kind = expression_kind::PIN; + lhs.name = s.pin(); + } else if (c == '(') { // parens + s.next(); + lhs = parse_expression(s); + if (s.peek() != ')') { + log_warning("expected ')' instead of '%c' while parsing Liberty expression '%s'\n", s.peek(), s.full_expr().c_str()); + return lhs; + } + s.next(); + } else if (c == '!') { // prefix NOT + s.next(); + lhs.kind = expression_kind::NOT; + lhs.children.push_back(parse_expression(s, 7)); + } else { + log_warning("unrecognised character '%c' while parsing Liberty expression '%s'\n", c, s.full_expr().c_str()); + return lhs; + } + + do { + if (s.empty()) + break; + + c = s.peek(); + + while (isspace(c)) { + if (s.empty()) + return lhs; + s.next(); + c = s.peek(); + } + + if (c == '\'') { // postfix NOT + if (min_prio > 7) + break; + s.next(); + + auto n = expression_tree{}; + n.kind = expression_kind::NOT; + n.children.push_back(lhs); + lhs = std::move(n); + + continue; + } else if (c == '^') { // infix XOR + if (min_prio > 5) + break; + s.next(); + + auto rhs = parse_expression(s, 6); + auto n = expression_tree{}; + n.kind = expression_kind::XOR; + n.children.push_back(lhs); + n.children.push_back(rhs); + lhs = std::move(n); + + continue; + } else if (c == '&' || c == '*') { // infix AND + // technically space should be considered infix AND. it seems rare in practice. + if (min_prio > 3) + break; + s.next(); + + auto rhs = parse_expression(s, 4); + auto n = expression_tree{}; + n.kind = expression_kind::AND; + n.children.push_back(lhs); + n.children.push_back(rhs); + lhs = std::move(n); + + continue; + } else if (c == '+' || c == '|') { // infix OR + if (min_prio > 1) + break; + s.next(); + + auto rhs = parse_expression(s, 2); + auto n = expression_tree{}; + n.kind = expression_kind::OR; + n.children.push_back(lhs); + n.children.push_back(rhs); + lhs = std::move(n); + + continue; + } + } while (false); + + return lhs; +} + struct cell_mapping { IdString cell_name; std::map ports; @@ -66,6 +308,11 @@ static void logmap_all() logmap(ID($_DFF_PP0_)); logmap(ID($_DFF_PP1_)); + logmap(ID($_DFFE_NN_)); + logmap(ID($_DFFE_NP_)); + logmap(ID($_DFFE_PN_)); + logmap(ID($_DFFE_PP_)); + logmap(ID($_DFFSR_NNN_)); logmap(ID($_DFFSR_NNP_)); logmap(ID($_DFFSR_NPN_)); @@ -76,6 +323,66 @@ static void logmap_all() logmap(ID($_DFFSR_PPP_)); } +static bool parse_next_state(const LibertyAst *cell, const LibertyAst *attr, std::string &data_name, bool &data_not_inverted, std::string &enable_name, bool &enable_not_inverted) +{ + if (cell == nullptr || attr == nullptr || attr->value.empty()) + return false; + + std::string expr = attr->value; + + for (size_t pos = expr.find_first_of("\" \t"); pos != std::string::npos; pos = expr.find_first_of("\" \t")) + expr.erase(pos, 1); + + // if this isn't an enable flop, the next_state variable is usually just the input pin name. + if (expr[expr.size()-1] == '\'') { + data_name = expr.substr(0, expr.size()-1); + data_not_inverted = false; + } else if (expr[0] == '!') { + data_name = expr.substr(1, expr.size()-1); + data_not_inverted = false; + } else { + data_name = expr; + data_not_inverted = true; + } + + for (auto child : cell->children) + if (child->id == "pin" && child->args.size() == 1 && child->args[0] == data_name) + return true; + + // the next_state variable isn't just a pin name; perhaps this is an enable? + auto helper = string_helper(expr); + auto tree = parse_expression(helper); + + if (tree.kind == expression_kind::EMPTY) { + log_warning("Invalid expression '%s' in next_state attribute of cell '%s' - skipping.\n", expr.c_str(), cell->args[0].c_str()); + return false; + } + + auto pin_names = pool{}; + tree.get_pin_names(pin_names); + + // from the `ff` block, we know the flop output signal name for loopback. + auto ff = cell->find("ff"); + if (ff == nullptr || ff->args.size() != 2) + return false; + auto ff_output = ff->args.at(0); + + // This test is redundant with the one in enable_pin, but we're in a + // position that gives better diagnostics here. + if (!pin_names.count(ff_output)) { + log_warning("Inference failed on expression '%s' in next_state attribute of cell '%s' because it does not contain ff output '%s' - skipping.\n", expr.c_str(), cell->args[0].c_str(), ff_output.c_str()); + return false; + } + + enable_name = tree.enable_pin(ff_output, data_name, data_not_inverted, enable_not_inverted); + if (enable_name.empty()) { + log_warning("Inference failed on expression '%s' in next_state attribute of cell '%s' because it does not evaluate to an enable flop - skipping.\n", expr.c_str(), cell->args[0].c_str()); + return false; + } + + return true; +} + static bool parse_pin(const LibertyAst *cell, const LibertyAst *attr, std::string &pin_name, bool &pin_pol) { if (cell == nullptr || attr == nullptr || attr->value.empty()) @@ -115,7 +422,7 @@ static bool parse_pin(const LibertyAst *cell, const LibertyAst *attr, std::strin return false; } -static void find_cell(const LibertyAst *ast, IdString cell_type, bool clkpol, bool has_reset, bool rstpol, bool rstval, std::vector &dont_use_cells) +static void find_cell(const LibertyAst *ast, IdString cell_type, bool clkpol, bool has_reset, bool rstpol, bool rstval, bool has_enable, bool enapol, std::vector &dont_use_cells) { const LibertyAst *best_cell = nullptr; std::map best_cell_ports; @@ -151,12 +458,12 @@ static void find_cell(const LibertyAst *ast, IdString cell_type, bool clkpol, bo if (ff == nullptr) continue; - std::string cell_clk_pin, cell_rst_pin, cell_next_pin; - bool cell_clk_pol, cell_rst_pol, cell_next_pol; + std::string cell_clk_pin, cell_rst_pin, cell_next_pin, cell_enable_pin; + bool cell_clk_pol, cell_rst_pol, cell_next_pol, cell_enable_pol; if (!parse_pin(cell, ff->find("clocked_on"), cell_clk_pin, cell_clk_pol) || cell_clk_pol != clkpol) continue; - if (!parse_pin(cell, ff->find("next_state"), cell_next_pin, cell_next_pol)) + if (!parse_next_state(cell, ff->find("next_state"), cell_next_pin, cell_next_pol, cell_enable_pin, cell_enable_pol) || (has_enable && (cell_enable_pin.empty() || cell_enable_pol != enapol))) continue; if (has_reset && rstval == false) { if (!parse_pin(cell, ff->find("clear"), cell_rst_pin, cell_rst_pol) || cell_rst_pol != rstpol) @@ -171,6 +478,8 @@ static void find_cell(const LibertyAst *ast, IdString cell_type, bool clkpol, bo this_cell_ports[cell_clk_pin] = 'C'; if (has_reset) this_cell_ports[cell_rst_pin] = 'R'; + if (has_enable) + this_cell_ports[cell_enable_pin] = 'E'; this_cell_ports[cell_next_pin] = 'D'; double area = 0; @@ -239,7 +548,7 @@ static void find_cell(const LibertyAst *ast, IdString cell_type, bool clkpol, bo } } -static void find_cell_sr(const LibertyAst *ast, IdString cell_type, bool clkpol, bool setpol, bool clrpol, std::vector &dont_use_cells) +static void find_cell_sr(const LibertyAst *ast, IdString cell_type, bool clkpol, bool setpol, bool clrpol, bool has_enable, bool enapol, std::vector &dont_use_cells) { const LibertyAst *best_cell = nullptr; std::map best_cell_ports; @@ -275,12 +584,12 @@ static void find_cell_sr(const LibertyAst *ast, IdString cell_type, bool clkpol, if (ff == nullptr) continue; - std::string cell_clk_pin, cell_set_pin, cell_clr_pin, cell_next_pin; - bool cell_clk_pol, cell_set_pol, cell_clr_pol, cell_next_pol; + std::string cell_clk_pin, cell_set_pin, cell_clr_pin, cell_next_pin, cell_enable_pin; + bool cell_clk_pol, cell_set_pol, cell_clr_pol, cell_next_pol, cell_enable_pol; if (!parse_pin(cell, ff->find("clocked_on"), cell_clk_pin, cell_clk_pol) || cell_clk_pol != clkpol) continue; - if (!parse_pin(cell, ff->find("next_state"), cell_next_pin, cell_next_pol)) + if (!parse_next_state(cell, ff->find("next_state"), cell_next_pin, cell_next_pol, cell_enable_pin, cell_enable_pol)) continue; if (!parse_pin(cell, ff->find("preset"), cell_set_pin, cell_set_pol) || cell_set_pol != setpol) continue; @@ -291,6 +600,8 @@ static void find_cell_sr(const LibertyAst *ast, IdString cell_type, bool clkpol, this_cell_ports[cell_clk_pin] = 'C'; this_cell_ports[cell_set_pin] = 'S'; this_cell_ports[cell_clr_pin] = 'R'; + if (has_enable) + this_cell_ports[cell_enable_pin] = 'E'; this_cell_ports[cell_next_pin] = 'D'; double area = 0; @@ -526,26 +837,31 @@ struct DfflibmapPass : public Pass { LibertyParser libparser(f); f.close(); - find_cell(libparser.ast, ID($_DFF_N_), false, false, false, false, dont_use_cells); - find_cell(libparser.ast, ID($_DFF_P_), true, false, false, false, dont_use_cells); + find_cell(libparser.ast, ID($_DFF_N_), false, false, false, false, false, false, dont_use_cells); + find_cell(libparser.ast, ID($_DFF_P_), true, false, false, false, false, false, dont_use_cells); - find_cell(libparser.ast, ID($_DFF_NN0_), false, true, false, false, dont_use_cells); - find_cell(libparser.ast, ID($_DFF_NN1_), false, true, false, true, dont_use_cells); - find_cell(libparser.ast, ID($_DFF_NP0_), false, true, true, false, dont_use_cells); - find_cell(libparser.ast, ID($_DFF_NP1_), false, true, true, true, dont_use_cells); - find_cell(libparser.ast, ID($_DFF_PN0_), true, true, false, false, dont_use_cells); - find_cell(libparser.ast, ID($_DFF_PN1_), true, true, false, true, dont_use_cells); - find_cell(libparser.ast, ID($_DFF_PP0_), true, true, true, false, dont_use_cells); - find_cell(libparser.ast, ID($_DFF_PP1_), true, true, true, true, dont_use_cells); + find_cell(libparser.ast, ID($_DFF_NN0_), false, true, false, false, false, false, dont_use_cells); + find_cell(libparser.ast, ID($_DFF_NN1_), false, true, false, true, false, false, dont_use_cells); + find_cell(libparser.ast, ID($_DFF_NP0_), false, true, true, false, false, false, dont_use_cells); + find_cell(libparser.ast, ID($_DFF_NP1_), false, true, true, true, false, false, dont_use_cells); + find_cell(libparser.ast, ID($_DFF_PN0_), true, true, false, false, false, false, dont_use_cells); + find_cell(libparser.ast, ID($_DFF_PN1_), true, true, false, true, false, false, dont_use_cells); + find_cell(libparser.ast, ID($_DFF_PP0_), true, true, true, false, false, false, dont_use_cells); + find_cell(libparser.ast, ID($_DFF_PP1_), true, true, true, true, false, false, dont_use_cells); - find_cell_sr(libparser.ast, ID($_DFFSR_NNN_), false, false, false, dont_use_cells); - find_cell_sr(libparser.ast, ID($_DFFSR_NNP_), false, false, true, dont_use_cells); - find_cell_sr(libparser.ast, ID($_DFFSR_NPN_), false, true, false, dont_use_cells); - find_cell_sr(libparser.ast, ID($_DFFSR_NPP_), false, true, true, dont_use_cells); - find_cell_sr(libparser.ast, ID($_DFFSR_PNN_), true, false, false, dont_use_cells); - find_cell_sr(libparser.ast, ID($_DFFSR_PNP_), true, false, true, dont_use_cells); - find_cell_sr(libparser.ast, ID($_DFFSR_PPN_), true, true, false, dont_use_cells); - find_cell_sr(libparser.ast, ID($_DFFSR_PPP_), true, true, true, dont_use_cells); + find_cell(libparser.ast, ID($_DFFE_NN_), false, false, false, false, true, false, dont_use_cells); + find_cell(libparser.ast, ID($_DFFE_NP_), false, false, false, false, true, true, dont_use_cells); + find_cell(libparser.ast, ID($_DFFE_PN_), true, false, false, false, true, false, dont_use_cells); + find_cell(libparser.ast, ID($_DFFE_PP_), true, false, false, false, true, true, dont_use_cells); + + find_cell_sr(libparser.ast, ID($_DFFSR_NNN_), false, false, false, false, false, dont_use_cells); + find_cell_sr(libparser.ast, ID($_DFFSR_NNP_), false, false, true, false, false, dont_use_cells); + find_cell_sr(libparser.ast, ID($_DFFSR_NPN_), false, true, false, false, false, dont_use_cells); + find_cell_sr(libparser.ast, ID($_DFFSR_NPP_), false, true, true, false, false, dont_use_cells); + find_cell_sr(libparser.ast, ID($_DFFSR_PNN_), true, false, false, false, false, dont_use_cells); + find_cell_sr(libparser.ast, ID($_DFFSR_PNP_), true, false, true, false, false, dont_use_cells); + find_cell_sr(libparser.ast, ID($_DFFSR_PPN_), true, true, false, false, false, dont_use_cells); + find_cell_sr(libparser.ast, ID($_DFFSR_PPP_), true, true, true, false, false, dont_use_cells); log(" final dff cell mappings:\n"); logmap_all(); From 4f40187759d5d51d11578c62330db5e1286e5217 Mon Sep 17 00:00:00 2001 From: Lofty Date: Thu, 7 Nov 2024 00:51:23 +0000 Subject: [PATCH 10/13] dfflibmap: move expression code into libparse --- passes/techmap/dfflibmap.cc | 315 +++++++----------------------------- passes/techmap/libparse.cc | 146 +++++++++++++++++ passes/techmap/libparse.h | 47 ++++++ 3 files changed, 255 insertions(+), 253 deletions(-) diff --git a/passes/techmap/dfflibmap.cc b/passes/techmap/dfflibmap.cc index 49d7f01f0..8af4f6a04 100644 --- a/passes/techmap/dfflibmap.cc +++ b/passes/techmap/dfflibmap.cc @@ -26,248 +26,6 @@ USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN -struct string_helper { - std::string s, expr; - - string_helper(std::string s) : s{s}, expr{s} {} - - bool empty() { - return s.empty(); - } - - char peek() { - return s[0]; - } - - char next() { - char c = s[0]; - s = s.substr(1, s.size()); - return c; - } - - std::string pin() { - auto length = s.find_first_of("\t()'!^*& +|"); - auto pin = s.substr(0, length); - s = s.substr(length, s.size()); - return pin; - } - - std::string full_expr() { - return expr; - } -}; - -enum expression_kind { - AND, - OR, - NOT, - XOR, - // the standard specifies constants, but they're probably rare in practice. - PIN, - EMPTY -}; - -struct expression_tree { - expression_kind kind; - std::string name; - std::vector children; - - expression_tree() : kind(expression_kind::EMPTY) {} - - void get_pin_names(pool& names) { - if (kind == expression_kind::PIN) { - names.insert(name); - } else { - for (auto& child : children) - child.get_pin_names(names); - } - } - - bool eval(dict& values) { - bool result = false; - switch (kind) { - case expression_kind::AND: - result = true; - for (auto& child : children) - result &= child.eval(values); - return result; - case expression_kind::OR: - result = false; - for (auto& child : children) - result |= child.eval(values); - return result; - case expression_kind::NOT: - log_assert(children.size() == 1); - return !children[0].eval(values); - case expression_kind::XOR: - result = false; - for (auto& child : children) - result ^= child.eval(values); - return result; - case expression_kind::PIN: - return values.at(name); - case expression_kind::EMPTY: - log_assert(false); - } - return false; - } - - std::string enable_pin(const std::string& ff_output, std::string &data_name, bool &data_not_inverted, bool &enable_not_inverted) { - auto pin_name_pool = pool{}; - get_pin_names(pin_name_pool); - if (pin_name_pool.size() != 3) - return ""; - if (!pin_name_pool.count(ff_output)) - return ""; - pin_name_pool.erase(ff_output); - auto pins = std::vector(pin_name_pool.begin(), pin_name_pool.end()); - int lut = 0; - for (int n = 0; n < 8; n++) { - auto values = dict{}; - values.insert(std::make_pair(pins[0], (n & 1) == 1)); - values.insert(std::make_pair(pins[1], (n & 2) == 2)); - values.insert(std::make_pair(ff_output, (n & 4) == 4)); - if (eval(values)) - lut |= 1 << n; - } - // the ff output Q is in a known bit location, so we now just have to compare the LUT mask to known values to find the enable pin and polarity. - if (lut == 0xD8) { - data_not_inverted = true; - data_name = pins[1]; - enable_not_inverted = true; - return pins[0]; - } - if (lut == 0xB8) { - data_not_inverted = true; - data_name = pins[0]; - enable_not_inverted = true; - return pins[1]; - } - if (lut == 0xE4) { - data_not_inverted = true; - data_name = pins[1]; - enable_not_inverted = false; - return pins[0]; - } - if (lut == 0xE2) { - data_not_inverted = true; - data_name = pins[0]; - enable_not_inverted = false; - return pins[1]; - } - // this does not match an enable flop. - data_not_inverted = false; - data_name = ""; - enable_not_inverted = false; - return ""; - } -}; - -// https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html -expression_tree parse_expression(string_helper &s, int min_prio = 0) { - if (s.empty()) - return expression_tree{}; - - char c = s.peek(); - auto lhs = expression_tree{}; - - while (isspace(c)) { - if (s.empty()) - return lhs; - s.next(); - c = s.peek(); - } - - if (isalpha(c)) { // pin - lhs.kind = expression_kind::PIN; - lhs.name = s.pin(); - } else if (c == '(') { // parens - s.next(); - lhs = parse_expression(s); - if (s.peek() != ')') { - log_warning("expected ')' instead of '%c' while parsing Liberty expression '%s'\n", s.peek(), s.full_expr().c_str()); - return lhs; - } - s.next(); - } else if (c == '!') { // prefix NOT - s.next(); - lhs.kind = expression_kind::NOT; - lhs.children.push_back(parse_expression(s, 7)); - } else { - log_warning("unrecognised character '%c' while parsing Liberty expression '%s'\n", c, s.full_expr().c_str()); - return lhs; - } - - do { - if (s.empty()) - break; - - c = s.peek(); - - while (isspace(c)) { - if (s.empty()) - return lhs; - s.next(); - c = s.peek(); - } - - if (c == '\'') { // postfix NOT - if (min_prio > 7) - break; - s.next(); - - auto n = expression_tree{}; - n.kind = expression_kind::NOT; - n.children.push_back(lhs); - lhs = std::move(n); - - continue; - } else if (c == '^') { // infix XOR - if (min_prio > 5) - break; - s.next(); - - auto rhs = parse_expression(s, 6); - auto n = expression_tree{}; - n.kind = expression_kind::XOR; - n.children.push_back(lhs); - n.children.push_back(rhs); - lhs = std::move(n); - - continue; - } else if (c == '&' || c == '*') { // infix AND - // technically space should be considered infix AND. it seems rare in practice. - if (min_prio > 3) - break; - s.next(); - - auto rhs = parse_expression(s, 4); - auto n = expression_tree{}; - n.kind = expression_kind::AND; - n.children.push_back(lhs); - n.children.push_back(rhs); - lhs = std::move(n); - - continue; - } else if (c == '+' || c == '|') { // infix OR - if (min_prio > 1) - break; - s.next(); - - auto rhs = parse_expression(s, 2); - auto n = expression_tree{}; - n.kind = expression_kind::OR; - n.children.push_back(lhs); - n.children.push_back(rhs); - lhs = std::move(n); - - continue; - } - } while (false); - - return lhs; -} - struct cell_mapping { IdString cell_name; std::map ports; @@ -325,10 +83,13 @@ static void logmap_all() static bool parse_next_state(const LibertyAst *cell, const LibertyAst *attr, std::string &data_name, bool &data_not_inverted, std::string &enable_name, bool &enable_not_inverted) { + static pool warned_cells{}; + if (cell == nullptr || attr == nullptr || attr->value.empty()) return false; - std::string expr = attr->value; + auto expr = attr->value; + auto cell_name = cell->args[0]; for (size_t pos = expr.find_first_of("\" \t"); pos != std::string::npos; pos = expr.find_first_of("\" \t")) expr.erase(pos, 1); @@ -350,11 +111,14 @@ static bool parse_next_state(const LibertyAst *cell, const LibertyAst *attr, std return true; // the next_state variable isn't just a pin name; perhaps this is an enable? - auto helper = string_helper(expr); - auto tree = parse_expression(helper); + auto helper = LibertyExpression::Lexer(expr); + auto tree = LibertyExpression::parse(helper); - if (tree.kind == expression_kind::EMPTY) { - log_warning("Invalid expression '%s' in next_state attribute of cell '%s' - skipping.\n", expr.c_str(), cell->args[0].c_str()); + if (tree.kind == LibertyExpression::Kind::EMPTY) { + if (!warned_cells.count(cell_name)) { + log_warning("Invalid expression '%s' in next_state attribute of cell '%s' - skipping.\n", expr.c_str(), cell_name.c_str()); + warned_cells.insert(cell_name); + } return false; } @@ -370,17 +134,60 @@ static bool parse_next_state(const LibertyAst *cell, const LibertyAst *attr, std // This test is redundant with the one in enable_pin, but we're in a // position that gives better diagnostics here. if (!pin_names.count(ff_output)) { - log_warning("Inference failed on expression '%s' in next_state attribute of cell '%s' because it does not contain ff output '%s' - skipping.\n", expr.c_str(), cell->args[0].c_str(), ff_output.c_str()); + if (!warned_cells.count(cell_name)) { + log_warning("Inference failed on expression '%s' in next_state attribute of cell '%s' because it does not contain ff output '%s' - skipping.\n", expr.c_str(), cell_name.c_str(), ff_output.c_str()); + warned_cells.insert(cell_name); + } return false; } - enable_name = tree.enable_pin(ff_output, data_name, data_not_inverted, enable_not_inverted); - if (enable_name.empty()) { - log_warning("Inference failed on expression '%s' in next_state attribute of cell '%s' because it does not evaluate to an enable flop - skipping.\n", expr.c_str(), cell->args[0].c_str()); - return false; + data_not_inverted = true; + data_name = ""; + enable_not_inverted = true; + enable_name = ""; + + if (pin_names.size() == 3 && pin_names.count(ff_output)) { + pin_names.erase(ff_output); + auto pins = std::vector(pin_names.begin(), pin_names.end()); + int lut = 0; + for (int n = 0; n < 8; n++) { + auto values = dict{}; + values.insert(std::make_pair(pins[0], (n & 1) == 1)); + values.insert(std::make_pair(pins[1], (n & 2) == 2)); + values.insert(std::make_pair(ff_output, (n & 4) == 4)); + if (tree.eval(values)) + lut |= 1 << n; + } + // the ff output Q is in a known bit location, so we now just have to compare the LUT mask to known values to find the enable pin and polarity. + if (lut == 0xD8) { + data_name = pins[1]; + enable_name = pins[0]; + return true; + } + if (lut == 0xB8) { + data_name = pins[0]; + enable_name = pins[1]; + return true; + } + enable_not_inverted = false; + if (lut == 0xE4) { + data_name = pins[1]; + enable_name = pins[0]; + return true; + } + if (lut == 0xE2) { + data_name = pins[0]; + enable_name = pins[1]; + return true; + } + // this does not match an enable flop. } - return true; + if (!warned_cells.count(cell_name)) { + log_warning("Inference failed on expression '%s' in next_state attribute of cell '%s' because it does not evaluate to an enable flop - skipping.\n", expr.c_str(), cell_name.c_str()); + warned_cells.insert(cell_name); + } + return false; } static bool parse_pin(const LibertyAst *cell, const LibertyAst *attr, std::string &pin_name, bool &pin_pol) @@ -556,6 +363,8 @@ static void find_cell_sr(const LibertyAst *ast, IdString cell_type, bool clkpol, bool best_cell_noninv = false; double best_cell_area = 0; + log_assert(!enapol && "set/reset cell with enable is unimplemented due to lack of cells for testing"); + if (ast->id != "library") log_error("Format error in liberty file.\n"); diff --git a/passes/techmap/libparse.cc b/passes/techmap/libparse.cc index 95f5a8fcb..29174c10f 100644 --- a/passes/techmap/libparse.cc +++ b/passes/techmap/libparse.cc @@ -80,6 +80,152 @@ void LibertyAst::dump(FILE *f, sieve &blacklist, sieve &whitelist, std::string i fprintf(f, " ;\n"); } +#ifndef FILTERLIB +// https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html +LibertyExpression LibertyExpression::parse(Lexer &s, int min_prio) { + if (s.empty()) + return LibertyExpression{}; + + char c = s.peek(); + auto lhs = LibertyExpression{}; + + while (isspace(c)) { + if (s.empty()) + return lhs; + s.next(); + c = s.peek(); + } + + if (isalpha(c)) { // pin + lhs.kind = Kind::PIN; + lhs.name = s.pin(); + } else if (c == '(') { // parens + s.next(); + lhs = parse(s); + if (s.peek() != ')') { + log_warning("expected ')' instead of '%c' while parsing Liberty expression '%s'\n", s.peek(), s.full_expr().c_str()); + return lhs; + } + s.next(); + } else if (c == '!') { // prefix NOT + s.next(); + lhs.kind = Kind::NOT; + lhs.children.push_back(parse(s, 7)); + } else { + log_warning("unrecognised character '%c' while parsing Liberty expression '%s'\n", c, s.full_expr().c_str()); + return lhs; + } + + while (true) { + if (s.empty()) + break; + + c = s.peek(); + + while (isspace(c)) { + if (s.empty()) + return lhs; + s.next(); + c = s.peek(); + } + + if (c == '\'') { // postfix NOT + if (min_prio > 7) + break; + s.next(); + + auto n = LibertyExpression{}; + n.kind = Kind::NOT; + n.children.push_back(lhs); + lhs = std::move(n); + + continue; + } else if (c == '^') { // infix XOR + if (min_prio > 5) + break; + s.next(); + + auto rhs = parse(s, 6); + auto n = LibertyExpression{}; + n.kind = Kind::XOR; + n.children.push_back(lhs); + n.children.push_back(rhs); + lhs = std::move(n); + + continue; + } else if (c == '&' || c == '*') { // infix AND + // technically space should be considered infix AND. it seems rare in practice. + if (min_prio > 3) + break; + s.next(); + + auto rhs = parse(s, 4); + auto n = LibertyExpression{}; + n.kind = Kind::AND; + n.children.push_back(lhs); + n.children.push_back(rhs); + lhs = std::move(n); + + continue; + } else if (c == '+' || c == '|') { // infix OR + if (min_prio > 1) + break; + s.next(); + + auto rhs = parse(s, 2); + auto n = LibertyExpression{}; + n.kind = Kind::OR; + n.children.push_back(lhs); + n.children.push_back(rhs); + lhs = std::move(n); + + continue; + } + break; + } + + return lhs; +} + +void LibertyExpression::get_pin_names(pool& names) { + if (kind == Kind::PIN) { + names.insert(name); + } else { + for (auto& child : children) + child.get_pin_names(names); + } +} + +bool LibertyExpression::eval(dict& values) { + bool result = false; + switch (kind) { + case Kind::AND: + result = true; + for (auto& child : children) + result &= child.eval(values); + return result; + case Kind::OR: + result = false; + for (auto& child : children) + result |= child.eval(values); + return result; + case Kind::NOT: + log_assert(children.size() == 1); + return !children[0].eval(values); + case Kind::XOR: + result = false; + for (auto& child : children) + result ^= child.eval(values); + return result; + case Kind::PIN: + return values.at(name); + case Kind::EMPTY: + log_assert(false); + } + return false; +} +#endif + int LibertyParser::lexer(std::string &str) { int c; diff --git a/passes/techmap/libparse.h b/passes/techmap/libparse.h index ad38ae5f8..3c56dee60 100644 --- a/passes/techmap/libparse.h +++ b/passes/techmap/libparse.h @@ -20,6 +20,7 @@ #ifndef LIBPARSE_H #define LIBPARSE_H +#include "kernel/yosys.h" #include #include #include @@ -39,6 +40,52 @@ namespace Yosys void dump(FILE *f, sieve &blacklist, sieve &whitelist, std::string indent = "", std::string path = "", bool path_ok = false) const; }; + struct LibertyExpression + { + struct Lexer { + std::string s, expr; + + Lexer(std::string s) : s{s}, expr{s} {} + + bool empty() { return s.empty();} + char peek() { return s[0]; } + std::string full_expr() { return expr; } + + char next() { + char c = s[0]; + s = s.substr(1, s.size()); + return c; + } + + std::string pin() { + auto length = s.find_first_of("\t()'!^*& +|"); + auto pin = s.substr(0, length); + s = s.substr(length, s.size()); + return pin; + } + }; + + enum Kind { + AND, + OR, + NOT, + XOR, + // the standard specifies constants, but they're probably rare in practice. + PIN, + EMPTY + }; + + Kind kind; + std::string name; + std::vector children; + + LibertyExpression() : kind(Kind::EMPTY) {} + + static LibertyExpression parse(Lexer &s, int min_prio = 0); + void get_pin_names(pool& names); + bool eval(dict& values); + }; + class LibertyParser { private: From a22ff47d6290fca3518a54796ab3152a0776985f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 14 Nov 2024 00:20:42 +0000 Subject: [PATCH 11/13] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d1b6ff2c1..92b29b1f5 100644 --- a/Makefile +++ b/Makefile @@ -155,7 +155,7 @@ ifeq ($(OS), Haiku) CXXFLAGS += -D_DEFAULT_SOURCE endif -YOSYS_VER := 0.47+46 +YOSYS_VER := 0.47+56 # Note: We arrange for .gitcommit to contain the (short) commit hash in # tarballs generated with git-archive(1) using .gitattributes. The git repo From 77e1f748a5c559f343f80d461c32db07b49b971f Mon Sep 17 00:00:00 2001 From: Patrick Urban Date: Fri, 15 Nov 2024 09:49:49 +0100 Subject: [PATCH 12/13] gatemate: run `simplemap` after `muxcover` to prevent unmapped multiplexers --- techlibs/gatemate/synth_gatemate.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/techlibs/gatemate/synth_gatemate.cc b/techlibs/gatemate/synth_gatemate.cc index 1d46d7929..fa36252f5 100644 --- a/techlibs/gatemate/synth_gatemate.cc +++ b/techlibs/gatemate/synth_gatemate.cc @@ -301,6 +301,7 @@ struct SynthGateMatePass : public ScriptPass } run("muxcover " + muxcover_args); run("opt -full"); + run("simplemap"); run("techmap -map +/gatemate/mux_map.v"); } From 81011ad92620fdd4a11340b79f8eaeb5c2ca5f33 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 16 Nov 2024 00:21:32 +0000 Subject: [PATCH 13/13] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 92b29b1f5..06613c20a 100644 --- a/Makefile +++ b/Makefile @@ -155,7 +155,7 @@ ifeq ($(OS), Haiku) CXXFLAGS += -D_DEFAULT_SOURCE endif -YOSYS_VER := 0.47+56 +YOSYS_VER := 0.47+61 # Note: We arrange for .gitcommit to contain the (short) commit hash in # tarballs generated with git-archive(1) using .gitattributes. The git repo