From 5b5e7ce771bb6d38ecfec68fe8f8d1ccae5f9d8a Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Wed, 24 Jun 2026 11:53:09 +0200 Subject: [PATCH] functinoal: twines --- kernel/functional.cc | 56 +- kernel/functional.h | 1279 +++++++++++++++++++++--------------------- 2 files changed, 671 insertions(+), 664 deletions(-) diff --git a/kernel/functional.cc b/kernel/functional.cc index 590b1d6ee..7f9a9c7a9 100644 --- a/kernel/functional.cc +++ b/kernel/functional.cc @@ -120,8 +120,8 @@ struct PrintVisitor : DefaultVisitor { std::string zero_extend(Node, Node a, int out_width) override { return "zero_extend(" + np(a) + ", " + std::to_string(out_width) + ")"; } std::string sign_extend(Node, Node a, int out_width) override { return "sign_extend(" + np(a) + ", " + std::to_string(out_width) + ")"; } std::string constant(Node, RTLIL::Const const& value) override { return "constant(" + value.as_string() + ")"; } - std::string input(Node, IdString name, IdString kind) override { return "input(" + name.str() + ", " + kind.str() + ")"; } - std::string state(Node, IdString name, IdString kind) override { return "state(" + name.str() + ", " + kind.str() + ")"; } + std::string input(Node self, TwineRef name, TwineRef kind) override { return "input(" + self.design->twines.unescaped_str(name) + ", " + self.design->twines.unescaped_str(kind) + ")"; } + std::string state(Node self, TwineRef name, TwineRef kind) override { return "state(" + self.design->twines.unescaped_str(name) + ", " + self.design->twines.unescaped_str(kind) + ")"; } std::string default_handler(Node self) override { std::string ret = fn_to_string(self.fn()); ret += "("; @@ -136,7 +136,7 @@ struct PrintVisitor : DefaultVisitor { std::string Node::to_string() { - return to_string([](Node n) { return design->twines.unescaped_str(n.name()); }); + return to_string([](Node n) { return n.design->twines.unescaped_str(n.name()); }); } std::string Node::to_string(std::function np) @@ -246,7 +246,7 @@ private: return handle_alu(g, factory.bitwise_or(p, g), g.width(), false, ci, factory.constant(Const(State::S0, 1))).at(TW::CO); } public: - std::variant, Node> handle(IdString cellName, IdString cellType, dict parameters, dict inputs) + std::variant, Node> handle(TwineRef cellName, TwineRef cellType, dict parameters, dict inputs) { int a_width = parameters.at(ID(A_WIDTH), Const(-1)).as_int(); int b_width = parameters.at(ID(B_WIDTH), Const(-1)).as_int(); @@ -470,6 +470,7 @@ class FunctionalIRConstruction { dict graph_nodes; dict, Node> cell_outputs; DriverMap driver_map; + Design *design; Factory& factory; CellSimplifier simplifier; vector memories_vector; @@ -497,7 +498,7 @@ class FunctionalIRConstruction { for(auto const &[name, sigspec] : cell->connections()) if(driver_map.celltypes.cell_output(cell->type.ref(), name)) { auto node = factory.create_pending(sigspec.size()); - factory.suggest_name(node, cell->name.str() + "$" + cell->module->design->twines.str(name)); + factory.suggest_name(node, design->twines.add(cell->name.str() + "$" + design->twines.str(name))); cell_outputs.emplace({cell, name}, node); if(name == port_name) rv = node; @@ -508,7 +509,8 @@ class FunctionalIRConstruction { } public: FunctionalIRConstruction(Module *module, Factory &f) - : factory(f) + : design(module->design) + , factory(f) , simplifier(f) , sig_map(module) , ff_initvals(&sig_map, module) @@ -522,10 +524,10 @@ public: for (auto riter = module->ports.rbegin(); riter != module->ports.rend(); ++riter) { auto *wire = module->wire(*riter); if (wire && wire->port_input) { - factory.add_input(wire->name, TW($input), Sort(wire->width)); + factory.add_input(wire->name.ref(), TW($input), Sort(wire->width)); } if (wire && wire->port_output) { - auto &output = factory.add_output(wire->name, TW($output), Sort(wire->width)); + auto &output = factory.add_output(wire->name.ref(), TW($output), Sort(wire->width)); output.set_value(enqueue(DriveChunk(DriveChunkWire(wire, 0, wire->width)))); } } @@ -565,14 +567,14 @@ private: // - Since wr port j can only have priority over wr port i if j > i, if we do writes in // ascending index order the result will obey the priorty relation. vector read_results; - auto &state = factory.add_state(mem->cell->name, TW($state), Sort(ceil_log2(mem->size), mem->width)); + auto &state = factory.add_state(mem->cell->name.ref(), TW($state), Sort(ceil_log2(mem->size), mem->width)); state.set_initial_value(MemContents(mem)); Node node = factory.value(state); for (size_t i = 0; i < mem->wr_ports.size(); i++) { const auto &wr = mem->wr_ports[i]; if (wr.clk_enable) log_error("Write port %zd of memory %s.%s is clocked. This is not supported by the functional backend. " - "Call async2sync or clk2fflogic to avoid this error.\n", i, mem->module, design->twines.unescaped_str(mem->memid)); + "Call async2sync or clk2fflogic to avoid this error.\n", i, mem->module, RTLIL::unescape_id(mem->memid).c_str()); Node en = enqueue(driver_map(DriveSpec(wr.en))); Node addr = enqueue(driver_map(DriveSpec(wr.addr))); Node new_data = enqueue(driver_map(DriveSpec(wr.data))); @@ -582,12 +584,12 @@ private: } if (mem->rd_ports.empty()) log_error("Memory %s.%s has no read ports. This is not supported by the functional backend. " - "Call opt_clean to remove it.", mem->module, design->twines.unescaped_str(mem->memid)); + "Call opt_clean to remove it.", mem->module, RTLIL::unescape_id(mem->memid).c_str()); for (size_t i = 0; i < mem->rd_ports.size(); i++) { const auto &rd = mem->rd_ports[i]; if (rd.clk_enable) log_error("Read port %zd of memory %s.%s is clocked. This is not supported by the functional backend. " - "Call memory_nordff to avoid this error.\n", i, mem->module, design->twines.unescaped_str(mem->memid)); + "Call memory_nordff to avoid this error.\n", i, mem->module, RTLIL::unescape_id(mem->memid).c_str()); Node addr = enqueue(driver_map(DriveSpec(rd.addr))); read_results.push_back(factory.memory_read(node, addr)); } @@ -610,9 +612,10 @@ private: if (!ff.has_gclk) log_error("The design contains a %s flip-flop at %s. This is not supported by the functional backend. " "Call async2sync or clk2fflogic to avoid this error.\n", cell->type.unescaped(), cell); - auto &state = factory.add_state(ff.name, TW($state), Sort(ff.width)); + TwineRef ff_name = design->twines.add(ff.name.str()); + auto &state = factory.add_state(ff_name, TW($state), Sort(ff.width)); Node q_value = factory.value(state); - factory.suggest_name(q_value, ff.name); + factory.suggest_name(q_value, ff_name); factory.update_pending(cell_outputs.at({cell, TW::Q}), q_value); state.set_next_value(enqueue(ff.sig_d)); state.set_initial_value(ff.val_init); @@ -628,7 +631,7 @@ private: n_outputs++; } } - std::variant, Node> outputs = simplifier.handle(cell->name, cell->type, cell->parameters, connections); + std::variant, Node> outputs = simplifier.handle(cell->name.ref(), cell->type.ref(), cell->parameters, connections); if(auto *nodep = std::get_if(&outputs); nodep != nullptr) { log_assert(n_outputs == 1); factory.update_pending(cell_outputs.at({cell, output_name}), *nodep); @@ -672,14 +675,14 @@ public: DriveChunkWire wire_chunk = chunk.wire(); if (wire_chunk.is_whole()) { if (wire_chunk.wire->port_input) { - Node node = factory.value(factory.ir().input(wire_chunk.wire->name)); - factory.suggest_name(node, wire_chunk.wire->name); + Node node = factory.value(factory.ir().input(wire_chunk.wire->name.ref())); + factory.suggest_name(node, wire_chunk.wire->name.ref()); factory.update_pending(pending, node); } else { DriveSpec driver = driver_map(DriveSpec(wire_chunk)); - check_undriven(driver, design->twines.unescaped_str(wire_chunk.wire->name)); + check_undriven(driver, design->twines.unescaped_str(wire_chunk.wire->name.ref())); Node node = enqueue(driver); - factory.suggest_name(node, wire_chunk.wire->name); + factory.suggest_name(node, wire_chunk.wire->name.ref()); factory.update_pending(pending, node); } } else { @@ -706,7 +709,7 @@ public: } } else if (chunk.is_constant()) { Node node = factory.constant(chunk.constant()); - factory.suggest_name(node, "$const" + std::to_string(chunk.size()) + "b" + chunk.constant().as_string()); + factory.suggest_name(node, design->twines.add("$const" + std::to_string(chunk.size()) + "b" + chunk.constant().as_string())); factory.update_pending(pending, node); } else if (chunk.is_multiple()) { log_error("Signal %s has multiple drivers. This is not supported by the functional backend. " @@ -726,6 +729,7 @@ public: IR IR::from_module(Module *module) { IR ir; + ir.design = module->design; auto factory = ir.factory(); FunctionalIRConstruction ctor(module, factory); ctor.process_queue(); @@ -744,8 +748,8 @@ void IR::topological_sort() { { log_warning("Combinational loop:\n"); for (int *i = begin; i != end; ++i) { - Node node(_graph[*i]); - log("- %s = %s\n", design->twines.unescaped_str(node.name()), node.to_string()); + Node node(_graph[*i], design); + log("- %s = %s\n", design->twines.unescaped_str(node.name()).c_str(), node.to_string().c_str()); } log("\n"); scc = true; @@ -763,8 +767,8 @@ void IR::topological_sort() { "Try `scc -select; simplemap; select -clear` to avoid this error.\n"); } -static IdString merge_name(IdString a, IdString b) { - if(a[0] == '$' && b[0] == '\\') +static TwineRef merge_name(TwineRef a, TwineRef b) { + if(!twine_is_public(a) && twine_is_public(b)) return b; else return a; @@ -783,10 +787,10 @@ void IR::forward_buf() { auto target_node = _graph[perm[target_index]]; if(node.has_sparse_attr()) { if(target_node.has_sparse_attr()) { - IdString id = merge_name(node.sparse_attr(), target_node.sparse_attr()); + TwineRef id = merge_name(node.sparse_attr(), target_node.sparse_attr()); target_node.sparse_attr() = id; } else { - IdString id = node.sparse_attr(); + TwineRef id = node.sparse_attr(); target_node.sparse_attr() = id; } } diff --git a/kernel/functional.h b/kernel/functional.h index 776b29205..9feb93c66 100644 --- a/kernel/functional.h +++ b/kernel/functional.h @@ -1,645 +1,648 @@ -// /* -// * yosys -- Yosys Open SYnthesis Suite -// * -// * Copyright (C) 2024 Emily Schmidt -// * Copyright (C) 2024 National Technology and Engineering Solutions of Sandia, LLC -// * -// * 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. -// * -// */ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Emily Schmidt + * Copyright (C) 2024 National Technology and Engineering Solutions of Sandia, LLC + * + * 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. + * + */ -// #ifndef FUNCTIONAL_H -// #define FUNCTIONAL_H +#ifndef FUNCTIONAL_H +#define FUNCTIONAL_H -// #include "kernel/yosys.h" -// #include "kernel/compute_graph.h" -// #include "kernel/drivertools.h" -// #include "kernel/mem.h" -// #include "kernel/utils.h" +#include "kernel/yosys.h" +#include "kernel/compute_graph.h" +#include "kernel/drivertools.h" +#include "kernel/mem.h" +#include "kernel/utils.h" -// USING_YOSYS_NAMESPACE -// YOSYS_NAMESPACE_BEGIN +USING_YOSYS_NAMESPACE +YOSYS_NAMESPACE_BEGIN -// namespace Functional { -// // each function is documented with a short pseudocode declaration or definition -// // standard C/Verilog operators are used to describe the result -// // -// // the sorts used in this are: -// // - bit[N]: a bitvector of N bits -// // bit[N] can be indicated as signed or unsigned. this is not tracked by the functional backend -// // but is meant to indicate how the value is interpreted -// // if a bit[N] is marked as neither signed nor unsigned, this means the result should be valid with *either* interpretation -// // - memory[N, M]: a memory with N address and M data bits -// // - int: C++ int -// // - Const[N]: yosys RTLIL::Const (with size() == N) -// // - TwineRef: yosys TwineRef -// // - any: used in documentation to indicate that the sort is unconstrained -// // -// // nodes in the functional backend are either of sort bit[N] or memory[N,M] (for some N, M: int) -// // additionally, they can carry a constant of sort int, Const[N] or TwineRef -// // each node has a 'sort' field that stores the sort of the node -// // slice, zero_extend, sign_extend use the sort field to store out_width -// enum class Fn { -// // invalid() = known-invalid/shouldn't happen value -// // TODO: maybe remove this and use e.g. std::optional instead? -// invalid, -// // buf(a: any): any = a -// // no-op operation -// // when constructing the compute graph we generate invalid buf() nodes as a placeholder -// // and later insert the argument -// buf, -// // slice(a: bit[in_width], offset: int, out_width: int): bit[out_width] = a[offset +: out_width] -// // required: offset + out_width <= in_width -// slice, -// // zero_extend(a: unsigned bit[in_width], out_width: int): unsigned bit[out_width] = a (zero extended) -// // required: out_width > in_width -// zero_extend, -// // sign_extend(a: signed bit[in_width], out_width: int): signed bit[out_width] = a (sign extended) -// // required: out_width > in_width -// sign_extend, -// // concat(a: bit[N], b: bit[M]): bit[N+M] = {b, a} (verilog syntax) -// // concatenates two bitvectors, with a in the least significant position and b in the more significant position -// concat, -// // add(a: bit[N], b: bit[N]): bit[N] = a + b -// add, -// // sub(a: bit[N], b: bit[N]): bit[N] = a - b -// sub, -// // mul(a: bit[N], b: bit[N]): bit[N] = a * b -// mul, -// // unsigned_div(a: unsigned bit[N], b: unsigned bit[N]): bit[N] = a / b -// unsigned_div, -// // unsigned_mod(a: signed bit[N], b: signed bit[N]): bit[N] = a % b -// unsigned_mod, -// // bitwise_and(a: bit[N], b: bit[N]): bit[N] = a & b -// bitwise_and, -// // bitwise_or(a: bit[N], b: bit[N]): bit[N] = a | b -// bitwise_or, -// // bitwise_xor(a: bit[N], b: bit[N]): bit[N] = a ^ b -// bitwise_xor, -// // bitwise_not(a: bit[N]): bit[N] = ~a -// bitwise_not, -// // reduce_and(a: bit[N]): bit[1] = &a -// reduce_and, -// // reduce_or(a: bit[N]): bit[1] = |a -// reduce_or, -// // reduce_xor(a: bit[N]): bit[1] = ^a -// reduce_xor, -// // unary_minus(a: bit[N]): bit[N] = -a -// unary_minus, -// // equal(a: bit[N], b: bit[N]): bit[1] = (a == b) -// equal, -// // not_equal(a: bit[N], b: bit[N]): bit[1] = (a != b) -// not_equal, -// // signed_greater_than(a: signed bit[N], b: signed bit[N]): bit[1] = (a > b) -// signed_greater_than, -// // signed_greater_equal(a: signed bit[N], b: signed bit[N]): bit[1] = (a >= b) -// signed_greater_equal, -// // unsigned_greater_than(a: unsigned bit[N], b: unsigned bit[N]): bit[1] = (a > b) -// unsigned_greater_than, -// // unsigned_greater_equal(a: unsigned bit[N], b: unsigned bit[N]): bit[1] = (a >= b) -// unsigned_greater_equal, -// // logical_shift_left(a: bit[N], b: unsigned bit[M]): bit[N] = a << b -// // required: M == clog2(N) -// logical_shift_left, -// // logical_shift_right(a: unsigned bit[N], b: unsigned bit[M]): unsigned bit[N] = a >> b -// // required: M == clog2(N) -// logical_shift_right, -// // arithmetic_shift_right(a: signed bit[N], b: unsigned bit[M]): signed bit[N] = a >> b -// // required: M == clog2(N) -// arithmetic_shift_right, -// // mux(a: bit[N], b: bit[N], s: bit[1]): bit[N] = s ? b : a -// mux, -// // constant(a: Const[N]): bit[N] = a -// constant, -// // input(a: IdString): any -// // returns the current value of the input with the specified name -// input, -// // state(a: IdString): any -// // returns the current value of the state variable with the specified name -// state, -// // memory_read(memory: memory[addr_width, data_width], addr: bit[addr_width]): bit[data_width] = memory[addr] -// memory_read, -// // memory_write(memory: memory[addr_width, data_width], addr: bit[addr_width], data: bit[data_width]): memory[addr_width, data_width] -// // returns a copy of `memory` but with the value at `addr` changed to `data` -// memory_write -// }; -// // returns the name of a Fn value, as a string literal -// const char *fn_to_string(Fn); -// // Sort represents the sort or type of a node -// // currently the only two sorts are signal/bit and memory -// class Sort { -// std::variant> _v; -// public: -// explicit Sort(int width) : _v(width) { } -// Sort(int addr_width, int data_width) : _v(std::make_pair(addr_width, data_width)) { } -// bool is_signal() const { return _v.index() == 0; } -// bool is_memory() const { return _v.index() == 1; } -// // returns the width of a bitvector sort, errors out for other sorts -// int width() const { return std::get<0>(_v); } -// // returns the address width of a bitvector sort, errors out for other sorts -// int addr_width() const { return std::get<1>(_v).first; } -// // returns the data width of a bitvector sort, errors out for other sorts -// int data_width() const { return std::get<1>(_v).second; } -// bool operator==(Sort const& other) const { return _v == other._v; } -// [[nodiscard]] Hasher hash_into(Hasher h) const { h.eat(_v); return h; } -// }; -// class IR; -// class Factory; -// class Node; -// class IRInput { -// friend class Factory; -// public: -// TwineRef name; -// TwineRef kind; -// Sort sort; -// private: -// IRInput(IR &, IdString name, IdString kind, Sort sort) -// : name(name), kind(kind), sort(std::move(sort)) {} -// }; -// class IROutput { -// friend class Factory; -// IR &_ir; -// public: -// TwineRef name; -// TwineRef kind; -// Sort sort; -// private: -// IROutput(IR &ir, TwineRef name, TwineRef kind, Sort sort) -// : _ir(ir), name(name), kind(kind), sort(std::move(sort)) {} -// public: -// Node value() const; -// bool has_value() const; -// void set_value(Node value); -// }; -// class IRState { -// friend class Factory; -// IR &_ir; -// public: -// TwineRef name; -// TwineRef kind; -// Sort sort; -// private: -// std::variant _initial; -// IRState(IR &ir, TwineRef name, TwineRef kind, Sort sort) -// : _ir(ir), name(name), kind(kind), sort(std::move(sort)) {} -// public: -// Node next_value() const; -// bool has_next_value() const; -// RTLIL::Const const& initial_value_signal() const { return std::get(_initial); } -// MemContents const& initial_value_memory() const { return std::get(_initial); } -// void set_next_value(Node value); -// void set_initial_value(RTLIL::Const value) { value.extu(sort.width()); _initial = std::move(value); } -// void set_initial_value(MemContents value) { log_assert(Sort(value.addr_width(), value.data_width()) == sort); _initial = std::move(value); } -// }; -// class IR { -// friend class Factory; -// friend class Node; -// friend class IRInput; -// friend class IROutput; -// friend class IRState; -// // one NodeData is stored per Node, containing the function and non-node arguments -// // note that NodeData is deduplicated by ComputeGraph -// class NodeData { -// Fn _fn; -// std::variant< -// std::monostate, -// RTLIL::Const, -// std::pair, -// int -// > _extra; -// public: -// NodeData() : _fn(Fn::invalid) {} -// NodeData(Fn fn) : _fn(fn) {} -// template NodeData(Fn fn, T &&extra) : _fn(fn), _extra(std::forward(extra)) {} -// Fn fn() const { return _fn; } -// const RTLIL::Const &as_const() const { return std::get(_extra); } -// std::pair as_idstring_pair() const { return std::get>(_extra); } -// int as_int() const { return std::get(_extra); } -// [[nodiscard]] Hasher hash_into(Hasher h) const { -// h.eat((unsigned int) _fn); -// h.eat(_extra); -// return h; -// } -// bool operator==(NodeData const &other) const { -// return _fn == other._fn && _extra == other._extra; -// } -// }; -// // Attr contains all the information about a note that should not be deduplicated -// struct Attr { -// Sort sort; -// }; -// // our specialised version of ComputeGraph -// // the sparse_attr TwineRef stores a naming suggestion, retrieved with name() -// // the key is currently used to identify the nodes that represent output and next state values -// // the bool is true for next state values -// using Graph = ComputeGraph>; -// Graph _graph; -// dict, IRInput> _inputs; -// dict, IROutput> _outputs; -// dict, IRState> _states; -// IR::Graph::Ref mutate(Node n); -// public: -// static IR from_module(Module *module); -// Factory factory(); -// int size() const { return _graph.size(); } -// Node operator[](int i); -// void topological_sort(); -// void forward_buf(); -// IRInput const& input(TwineRef name, TwineRef kind) const { return _inputs.at({name, kind}); } -// IRInput const& input(TwineRef name) const { return input(name, TW($input)); } -// IROutput const& output(TwineRef name, TwineRef kind) const { return _outputs.at({name, kind}); } -// IROutput const& output(TwineRef name) const { return output(name, TW($output)); } -// IRState const& state(TwineRef name, TwineRef kind) const { return _states.at({name, kind}); } -// IRState const& state(TwineRef name) const { return state(name, TW($state)); } -// bool has_input(TwineRef name, TwineRef kind) const { return _inputs.count({name, kind}); } -// bool has_output(TwineRef name, TwineRef kind) const { return _outputs.count({name, kind}); } -// bool has_state(TwineRef name, TwineRef kind) const { return _states.count({name, kind}); } -// vector inputs(TwineRef kind) const; -// vector inputs() const { return inputs(TW($input)); } -// vector outputs(TwineRef kind) const; -// vector outputs() const { return outputs(TW($output)); } -// vector states(TwineRef kind) const; -// vector states() const { return states(TW($state)); } -// vector all_inputs() const; -// vector all_outputs() const; -// vector all_states() const; -// class iterator { -// friend class IR; -// IR *_ir; -// int _index; -// iterator(IR *ir, int index) : _ir(ir), _index(index) {} -// public: -// using iterator_category = std::input_iterator_tag; -// using value_type = Node; -// using pointer = arrow_proxy; -// using reference = Node; -// using difference_type = ptrdiff_t; -// Node operator*(); -// iterator &operator++() { _index++; return *this; } -// bool operator!=(iterator const &other) const { return _ir != other._ir || _index != other._index; } -// bool operator==(iterator const &other) const { return !(*this != other); } -// pointer operator->(); -// }; -// iterator begin() { return iterator(this, 0); } -// iterator end() { return iterator(this, _graph.size()); } -// }; -// // Node is an immutable reference to a FunctionalIR node -// class Node { -// friend class Factory; -// friend class IR; -// friend class IRInput; -// friend class IROutput; -// friend class IRState; -// IR::Graph::ConstRef _ref; -// explicit Node(IR::Graph::ConstRef ref) : _ref(ref) { } -// explicit operator IR::Graph::ConstRef() { return _ref; } -// public: -// // the node's index. may change if nodes are added or removed -// int id() const { return _ref.index(); } -// // a name suggestion for the node, which need not be unique -// TwineRef name() const { -// if(_ref.has_sparse_attr()) -// return _ref.sparse_attr(); -// else -// return std::string("\\n") + std::to_string(id()); -// } -// Fn fn() const { return _ref.function().fn(); } -// Sort sort() const { return _ref.attr().sort; } -// // returns the width of a bitvector node, errors out for other nodes -// int width() const { return sort().width(); } -// size_t arg_count() const { return _ref.size(); } -// Node arg(int n) const { return Node(_ref.arg(n)); } -// // visit calls the appropriate visitor method depending on the type of the node -// template auto visit(Visitor v) const -// { -// // currently templated but could be switched to AbstractVisitor & -// switch(_ref.function().fn()) { -// case Fn::invalid: log_error("invalid node in visit"); break; -// case Fn::buf: return v.buf(*this, arg(0)); break; -// case Fn::slice: return v.slice(*this, arg(0), _ref.function().as_int(), sort().width()); break; -// case Fn::zero_extend: return v.zero_extend(*this, arg(0), width()); break; -// case Fn::sign_extend: return v.sign_extend(*this, arg(0), width()); break; -// case Fn::concat: return v.concat(*this, arg(0), arg(1)); break; -// case Fn::add: return v.add(*this, arg(0), arg(1)); break; -// case Fn::sub: return v.sub(*this, arg(0), arg(1)); break; -// case Fn::mul: return v.mul(*this, arg(0), arg(1)); break; -// case Fn::unsigned_div: return v.unsigned_div(*this, arg(0), arg(1)); break; -// case Fn::unsigned_mod: return v.unsigned_mod(*this, arg(0), arg(1)); break; -// case Fn::bitwise_and: return v.bitwise_and(*this, arg(0), arg(1)); break; -// case Fn::bitwise_or: return v.bitwise_or(*this, arg(0), arg(1)); break; -// case Fn::bitwise_xor: return v.bitwise_xor(*this, arg(0), arg(1)); break; -// case Fn::bitwise_not: return v.bitwise_not(*this, arg(0)); break; -// case Fn::unary_minus: return v.unary_minus(*this, arg(0)); break; -// case Fn::reduce_and: return v.reduce_and(*this, arg(0)); break; -// case Fn::reduce_or: return v.reduce_or(*this, arg(0)); break; -// case Fn::reduce_xor: return v.reduce_xor(*this, arg(0)); break; -// case Fn::equal: return v.equal(*this, arg(0), arg(1)); break; -// case Fn::not_equal: return v.not_equal(*this, arg(0), arg(1)); break; -// case Fn::signed_greater_than: return v.signed_greater_than(*this, arg(0), arg(1)); break; -// case Fn::signed_greater_equal: return v.signed_greater_equal(*this, arg(0), arg(1)); break; -// case Fn::unsigned_greater_than: return v.unsigned_greater_than(*this, arg(0), arg(1)); break; -// case Fn::unsigned_greater_equal: return v.unsigned_greater_equal(*this, arg(0), arg(1)); break; -// case Fn::logical_shift_left: return v.logical_shift_left(*this, arg(0), arg(1)); break; -// case Fn::logical_shift_right: return v.logical_shift_right(*this, arg(0), arg(1)); break; -// case Fn::arithmetic_shift_right: return v.arithmetic_shift_right(*this, arg(0), arg(1)); break; -// case Fn::mux: return v.mux(*this, arg(0), arg(1), arg(2)); break; -// case Fn::constant: return v.constant(*this, _ref.function().as_const()); break; -// case Fn::input: return v.input(*this, _ref.function().as_idstring_pair().first, _ref.function().as_idstring_pair().second); break; -// case Fn::state: return v.state(*this, _ref.function().as_idstring_pair().first, _ref.function().as_idstring_pair().second); break; -// case Fn::memory_read: return v.memory_read(*this, arg(0), arg(1)); break; -// case Fn::memory_write: return v.memory_write(*this, arg(0), arg(1), arg(2)); break; -// } -// log_abort(); -// } -// std::string to_string(); -// std::string to_string(std::function); -// }; -// inline IR::Graph::Ref IR::mutate(Node n) { return _graph[n._ref.index()]; } -// inline Node IR::operator[](int i) { return Node(_graph[i]); } -// inline Node IROutput::value() const { return Node(_ir._graph({name, kind, false})); } -// inline bool IROutput::has_value() const { return _ir._graph.has_key({name, kind, false}); } -// inline void IROutput::set_value(Node value) { log_assert(sort == value.sort()); _ir.mutate(value).assign_key({name, kind, false}); } -// inline Node IRState::next_value() const { return Node(_ir._graph({name, kind, true})); } -// inline bool IRState::has_next_value() const { return _ir._graph.has_key({name, kind, true}); } -// inline void IRState::set_next_value(Node value) { log_assert(sort == value.sort()); _ir.mutate(value).assign_key({name, kind, true}); } -// inline Node IR::iterator::operator*() { return Node(_ir->_graph[_index]); } -// inline arrow_proxy IR::iterator::operator->() { return arrow_proxy(**this); } -// // AbstractVisitor provides an abstract base class for visitors -// template struct AbstractVisitor { -// virtual T buf(Node self, Node n) = 0; -// virtual T slice(Node self, Node a, int offset, int out_width) = 0; -// virtual T zero_extend(Node self, Node a, int out_width) = 0; -// virtual T sign_extend(Node self, Node a, int out_width) = 0; -// virtual T concat(Node self, Node a, Node b) = 0; -// virtual T add(Node self, Node a, Node b) = 0; -// virtual T sub(Node self, Node a, Node b) = 0; -// virtual T mul(Node self, Node a, Node b) = 0; -// virtual T unsigned_div(Node self, Node a, Node b) = 0; -// virtual T unsigned_mod(Node self, Node a, Node b) = 0; -// virtual T bitwise_and(Node self, Node a, Node b) = 0; -// virtual T bitwise_or(Node self, Node a, Node b) = 0; -// virtual T bitwise_xor(Node self, Node a, Node b) = 0; -// virtual T bitwise_not(Node self, Node a) = 0; -// virtual T unary_minus(Node self, Node a) = 0; -// virtual T reduce_and(Node self, Node a) = 0; -// virtual T reduce_or(Node self, Node a) = 0; -// virtual T reduce_xor(Node self, Node a) = 0; -// virtual T equal(Node self, Node a, Node b) = 0; -// virtual T not_equal(Node self, Node a, Node b) = 0; -// virtual T signed_greater_than(Node self, Node a, Node b) = 0; -// virtual T signed_greater_equal(Node self, Node a, Node b) = 0; -// virtual T unsigned_greater_than(Node self, Node a, Node b) = 0; -// virtual T unsigned_greater_equal(Node self, Node a, Node b) = 0; -// virtual T logical_shift_left(Node self, Node a, Node b) = 0; -// virtual T logical_shift_right(Node self, Node a, Node b) = 0; -// virtual T arithmetic_shift_right(Node self, Node a, Node b) = 0; -// virtual T mux(Node self, Node a, Node b, Node s) = 0; -// virtual T constant(Node self, RTLIL::Const const & value) = 0; -// virtual T input(Node self, TwineRef name, TwineRef kind) = 0; -// virtual T state(Node self, TwineRef name, TwineRef kind) = 0; -// virtual T memory_read(Node self, Node mem, Node addr) = 0; -// virtual T memory_write(Node self, Node mem, Node addr, Node data) = 0; -// }; -// // DefaultVisitor provides defaults for all visitor methods which just calls default_handler -// template struct DefaultVisitor : public AbstractVisitor { -// virtual T default_handler(Node self) = 0; -// T buf(Node self, Node) override { return default_handler(self); } -// T slice(Node self, Node, int, int) override { return default_handler(self); } -// T zero_extend(Node self, Node, int) override { return default_handler(self); } -// T sign_extend(Node self, Node, int) override { return default_handler(self); } -// T concat(Node self, Node, Node) override { return default_handler(self); } -// T add(Node self, Node, Node) override { return default_handler(self); } -// T sub(Node self, Node, Node) override { return default_handler(self); } -// T mul(Node self, Node, Node) override { return default_handler(self); } -// T unsigned_div(Node self, Node, Node) override { return default_handler(self); } -// T unsigned_mod(Node self, Node, Node) override { return default_handler(self); } -// T bitwise_and(Node self, Node, Node) override { return default_handler(self); } -// T bitwise_or(Node self, Node, Node) override { return default_handler(self); } -// T bitwise_xor(Node self, Node, Node) override { return default_handler(self); } -// T bitwise_not(Node self, Node) override { return default_handler(self); } -// T unary_minus(Node self, Node) override { return default_handler(self); } -// T reduce_and(Node self, Node) override { return default_handler(self); } -// T reduce_or(Node self, Node) override { return default_handler(self); } -// T reduce_xor(Node self, Node) override { return default_handler(self); } -// T equal(Node self, Node, Node) override { return default_handler(self); } -// T not_equal(Node self, Node, Node) override { return default_handler(self); } -// T signed_greater_than(Node self, Node, Node) override { return default_handler(self); } -// T signed_greater_equal(Node self, Node, Node) override { return default_handler(self); } -// T unsigned_greater_than(Node self, Node, Node) override { return default_handler(self); } -// T unsigned_greater_equal(Node self, Node, Node) override { return default_handler(self); } -// T logical_shift_left(Node self, Node, Node) override { return default_handler(self); } -// T logical_shift_right(Node self, Node, Node) override { return default_handler(self); } -// T arithmetic_shift_right(Node self, Node, Node) override { return default_handler(self); } -// T mux(Node self, Node, Node, Node) override { return default_handler(self); } -// T constant(Node self, RTLIL::Const const &) override { return default_handler(self); } -// T input(Node self, TwineRef, TwineRef) override { return default_handler(self); } -// T state(Node self, TwineRef, TwineRef) override { return default_handler(self); } -// T memory_read(Node self, Node, Node) override { return default_handler(self); } -// T memory_write(Node self, Node, Node, Node) override { return default_handler(self); } -// }; -// // a factory is used to modify a FunctionalIR. it creates new nodes and allows for some modification of existing nodes. -// class Factory { -// friend class IR; -// IR &_ir; -// explicit Factory(IR &ir) : _ir(ir) {} -// Node add(IR::NodeData &&fn, Sort const &sort, std::initializer_list args) { -// log_assert(!sort.is_signal() || sort.width() > 0); -// log_assert(!sort.is_memory() || (sort.addr_width() > 0 && sort.data_width() > 0)); -// IR::Graph::Ref ref = _ir._graph.add(std::move(fn), {std::move(sort)}); -// for (auto arg : args) -// ref.append_arg(IR::Graph::ConstRef(arg)); -// return Node(ref); -// } -// void check_basic_binary(Node const &a, Node const &b) { log_assert(a.sort().is_signal() && a.sort() == b.sort()); } -// void check_shift(Node const &a, Node const &b) { log_assert(a.sort().is_signal() && b.sort().is_signal() && b.width() == ceil_log2(a.width())); } -// void check_unary(Node const &a) { log_assert(a.sort().is_signal()); } -// public: -// IR &ir() { return _ir; } -// Node slice(Node a, int offset, int out_width) { -// log_assert(a.sort().is_signal() && offset + out_width <= a.sort().width()); -// if(offset == 0 && out_width == a.width()) -// return a; -// return add(IR::NodeData(Fn::slice, offset), Sort(out_width), {a}); -// } -// // extend will either extend or truncate the provided value to reach the desired width -// Node extend(Node a, int out_width, bool is_signed) { -// int in_width = a.sort().width(); -// log_assert(a.sort().is_signal()); -// if(in_width == out_width) -// return a; -// if(in_width > out_width) -// return slice(a, 0, out_width); -// if(is_signed) -// return add(Fn::sign_extend, Sort(out_width), {a}); -// else -// return add(Fn::zero_extend, Sort(out_width), {a}); -// } -// Node concat(Node a, Node b) { -// log_assert(a.sort().is_signal() && b.sort().is_signal()); -// return add(Fn::concat, Sort(a.sort().width() + b.sort().width()), {a, b}); -// } -// Node add(Node a, Node b) { check_basic_binary(a, b); return add(Fn::add, a.sort(), {a, b}); } -// Node sub(Node a, Node b) { check_basic_binary(a, b); return add(Fn::sub, a.sort(), {a, b}); } -// Node mul(Node a, Node b) { check_basic_binary(a, b); return add(Fn::mul, a.sort(), {a, b}); } -// Node unsigned_div(Node a, Node b) { check_basic_binary(a, b); return add(Fn::unsigned_div, a.sort(), {a, b}); } -// Node unsigned_mod(Node a, Node b) { check_basic_binary(a, b); return add(Fn::unsigned_mod, a.sort(), {a, b}); } -// Node bitwise_and(Node a, Node b) { check_basic_binary(a, b); return add(Fn::bitwise_and, a.sort(), {a, b}); } -// Node bitwise_or(Node a, Node b) { check_basic_binary(a, b); return add(Fn::bitwise_or, a.sort(), {a, b}); } -// Node bitwise_xor(Node a, Node b) { check_basic_binary(a, b); return add(Fn::bitwise_xor, a.sort(), {a, b}); } -// Node bitwise_not(Node a) { check_unary(a); return add(Fn::bitwise_not, a.sort(), {a}); } -// Node unary_minus(Node a) { check_unary(a); return add(Fn::unary_minus, a.sort(), {a}); } -// Node reduce_and(Node a) { -// check_unary(a); -// if(a.width() == 1) -// return a; -// return add(Fn::reduce_and, Sort(1), {a}); -// } -// Node reduce_or(Node a) { -// check_unary(a); -// if(a.width() == 1) -// return a; -// return add(Fn::reduce_or, Sort(1), {a}); -// } -// Node reduce_xor(Node a) { -// check_unary(a); -// if(a.width() == 1) -// return a; -// return add(Fn::reduce_xor, Sort(1), {a}); -// } -// Node equal(Node a, Node b) { check_basic_binary(a, b); return add(Fn::equal, Sort(1), {a, b}); } -// Node not_equal(Node a, Node b) { check_basic_binary(a, b); return add(Fn::not_equal, Sort(1), {a, b}); } -// Node signed_greater_than(Node a, Node b) { check_basic_binary(a, b); return add(Fn::signed_greater_than, Sort(1), {a, b}); } -// Node signed_greater_equal(Node a, Node b) { check_basic_binary(a, b); return add(Fn::signed_greater_equal, Sort(1), {a, b}); } -// Node unsigned_greater_than(Node a, Node b) { check_basic_binary(a, b); return add(Fn::unsigned_greater_than, Sort(1), {a, b}); } -// Node unsigned_greater_equal(Node a, Node b) { check_basic_binary(a, b); return add(Fn::unsigned_greater_equal, Sort(1), {a, b}); } -// Node logical_shift_left(Node a, Node b) { check_shift(a, b); return add(Fn::logical_shift_left, a.sort(), {a, b}); } -// Node logical_shift_right(Node a, Node b) { check_shift(a, b); return add(Fn::logical_shift_right, a.sort(), {a, b}); } -// Node arithmetic_shift_right(Node a, Node b) { check_shift(a, b); return add(Fn::arithmetic_shift_right, a.sort(), {a, b}); } -// Node mux(Node a, Node b, Node s) { -// log_assert(a.sort().is_signal() && a.sort() == b.sort() && s.sort() == Sort(1)); -// return add(Fn::mux, a.sort(), {a, b, s}); -// } -// Node memory_read(Node mem, Node addr) { -// log_assert(mem.sort().is_memory() && addr.sort().is_signal() && mem.sort().addr_width() == addr.sort().width()); -// return add(Fn::memory_read, Sort(mem.sort().data_width()), {mem, addr}); -// } -// Node memory_write(Node mem, Node addr, Node data) { -// log_assert(mem.sort().is_memory() && addr.sort().is_signal() && data.sort().is_signal() && -// mem.sort().addr_width() == addr.sort().width() && mem.sort().data_width() == data.sort().width()); -// return add(Fn::memory_write, mem.sort(), {mem, addr, data}); -// } -// Node constant(RTLIL::Const value) { -// int s = value.size(); -// return add(IR::NodeData(Fn::constant, std::move(value)), Sort(s), {}); -// } -// Node create_pending(int width) { -// return add(Fn::buf, Sort(width), {}); -// } -// void update_pending(Node node, Node value) { -// log_assert(node._ref.function() == Fn::buf && node._ref.size() == 0); -// log_assert(node.sort() == value.sort()); -// _ir.mutate(node).append_arg(value._ref); -// } -// IRInput &add_input(TwineRef name, TwineRef kind, Sort sort) { -// auto [it, inserted] = _ir._inputs.emplace({name, kind}, IRInput(_ir, name, kind, std::move(sort))); -// if (!inserted) log_error("input `%s` was re-defined", name); -// return it->second; -// } -// IROutput &add_output(TwineRef name, TwineRef kind, Sort sort) { -// auto [it, inserted] = _ir._outputs.emplace({name, kind}, IROutput(_ir, name, kind, std::move(sort))); -// if (!inserted) log_error("output `%s` was re-defined", name); -// return it->second; -// } -// IRState &add_state(TwineRef name, TwineRef kind, Sort sort) { -// auto [it, inserted] = _ir._states.emplace({name, kind}, IRState(_ir, name, kind, std::move(sort))); -// if (!inserted) log_error("state `%s` was re-defined", name); -// return it->second; -// } -// Node value(IRInput const& input) { -// return add(IR::NodeData(Fn::input, std::pair(input.name, input.kind)), input.sort, {}); -// } -// Node value(IRState const& state) { -// return add(IR::NodeData(Fn::state, std::pair(state.name, state.kind)), state.sort, {}); -// } -// void suggest_name(Node node, TwineRef name) { -// _ir.mutate(node).sparse_attr() = name; -// } -// }; -// inline Factory IR::factory() { return Factory(*this); } -// template class Scope { -// protected: -// char substitution_character = '_'; -// virtual bool is_character_legal(char, int) = 0; -// private: -// pool _used_names; -// dict _by_id; -// public: -// void reserve(std::string name) { -// _used_names.insert(std::move(name)); -// } -// std::string unique_name(TwineRef suggestion) { -// std::string str = design->twines.unescaped_str(suggestion); -// for(size_t i = 0; i < str.size(); i++) -// if(!is_character_legal(str[i], i)) -// str[i] = substitution_character; -// if(_used_names.count(str) == 0) { -// _used_names.insert(str); -// return str; -// } -// for (int idx = 0 ; ; idx++){ -// std::string suffixed = str + "_" + std::to_string(idx); -// if(_used_names.count(suffixed) == 0) { -// _used_names.insert(suffixed); -// return suffixed; -// } -// } -// } -// std::string operator()(Id id, TwineRef suggestion) { -// auto it = _by_id.find(id); -// if(it != _by_id.end()) -// return it->second; -// std::string str = unique_name(suggestion); -// _by_id.insert({id, str}); -// return str; -// } -// }; -// class Writer { -// std::ostream *os; -// void print_impl(const char *fmt, vector>& fns); -// public: -// Writer(std::ostream &os) : os(&os) {} -// template Writer& operator <<(T&& arg) { *os << std::forward(arg); return *this; } -// template -// void print(const char *fmt, Args&&... args) -// { -// vector> fns { [&]() { *this << args; }... }; -// print_impl(fmt, fns); -// } -// template -// void print_with(Fn fn, const char *fmt, Args&&... args) -// { -// vector> fns { [&]() { -// if constexpr (std::is_invocable_v) -// *this << fn(args); -// else -// *this << args; }... -// }; -// print_impl(fmt, fns); -// } -// }; +namespace Functional { + // each function is documented with a short pseudocode declaration or definition + // standard C/Verilog operators are used to describe the result + // + // the sorts used in this are: + // - bit[N]: a bitvector of N bits + // bit[N] can be indicated as signed or unsigned. this is not tracked by the functional backend + // but is meant to indicate how the value is interpreted + // if a bit[N] is marked as neither signed nor unsigned, this means the result should be valid with *either* interpretation + // - memory[N, M]: a memory with N address and M data bits + // - int: C++ int + // - Const[N]: yosys RTLIL::Const (with size() == N) + // - TwineRef: yosys TwineRef + // - any: used in documentation to indicate that the sort is unconstrained + // + // nodes in the functional backend are either of sort bit[N] or memory[N,M] (for some N, M: int) + // additionally, they can carry a constant of sort int, Const[N] or TwineRef + // each node has a 'sort' field that stores the sort of the node + // slice, zero_extend, sign_extend use the sort field to store out_width + enum class Fn { + // invalid() = known-invalid/shouldn't happen value + // TODO: maybe remove this and use e.g. std::optional instead? + invalid, + // buf(a: any): any = a + // no-op operation + // when constructing the compute graph we generate invalid buf() nodes as a placeholder + // and later insert the argument + buf, + // slice(a: bit[in_width], offset: int, out_width: int): bit[out_width] = a[offset +: out_width] + // required: offset + out_width <= in_width + slice, + // zero_extend(a: unsigned bit[in_width], out_width: int): unsigned bit[out_width] = a (zero extended) + // required: out_width > in_width + zero_extend, + // sign_extend(a: signed bit[in_width], out_width: int): signed bit[out_width] = a (sign extended) + // required: out_width > in_width + sign_extend, + // concat(a: bit[N], b: bit[M]): bit[N+M] = {b, a} (verilog syntax) + // concatenates two bitvectors, with a in the least significant position and b in the more significant position + concat, + // add(a: bit[N], b: bit[N]): bit[N] = a + b + add, + // sub(a: bit[N], b: bit[N]): bit[N] = a - b + sub, + // mul(a: bit[N], b: bit[N]): bit[N] = a * b + mul, + // unsigned_div(a: unsigned bit[N], b: unsigned bit[N]): bit[N] = a / b + unsigned_div, + // unsigned_mod(a: signed bit[N], b: signed bit[N]): bit[N] = a % b + unsigned_mod, + // bitwise_and(a: bit[N], b: bit[N]): bit[N] = a & b + bitwise_and, + // bitwise_or(a: bit[N], b: bit[N]): bit[N] = a | b + bitwise_or, + // bitwise_xor(a: bit[N], b: bit[N]): bit[N] = a ^ b + bitwise_xor, + // bitwise_not(a: bit[N]): bit[N] = ~a + bitwise_not, + // reduce_and(a: bit[N]): bit[1] = &a + reduce_and, + // reduce_or(a: bit[N]): bit[1] = |a + reduce_or, + // reduce_xor(a: bit[N]): bit[1] = ^a + reduce_xor, + // unary_minus(a: bit[N]): bit[N] = -a + unary_minus, + // equal(a: bit[N], b: bit[N]): bit[1] = (a == b) + equal, + // not_equal(a: bit[N], b: bit[N]): bit[1] = (a != b) + not_equal, + // signed_greater_than(a: signed bit[N], b: signed bit[N]): bit[1] = (a > b) + signed_greater_than, + // signed_greater_equal(a: signed bit[N], b: signed bit[N]): bit[1] = (a >= b) + signed_greater_equal, + // unsigned_greater_than(a: unsigned bit[N], b: unsigned bit[N]): bit[1] = (a > b) + unsigned_greater_than, + // unsigned_greater_equal(a: unsigned bit[N], b: unsigned bit[N]): bit[1] = (a >= b) + unsigned_greater_equal, + // logical_shift_left(a: bit[N], b: unsigned bit[M]): bit[N] = a << b + // required: M == clog2(N) + logical_shift_left, + // logical_shift_right(a: unsigned bit[N], b: unsigned bit[M]): unsigned bit[N] = a >> b + // required: M == clog2(N) + logical_shift_right, + // arithmetic_shift_right(a: signed bit[N], b: unsigned bit[M]): signed bit[N] = a >> b + // required: M == clog2(N) + arithmetic_shift_right, + // mux(a: bit[N], b: bit[N], s: bit[1]): bit[N] = s ? b : a + mux, + // constant(a: Const[N]): bit[N] = a + constant, + // input(a: IdString): any + // returns the current value of the input with the specified name + input, + // state(a: IdString): any + // returns the current value of the state variable with the specified name + state, + // memory_read(memory: memory[addr_width, data_width], addr: bit[addr_width]): bit[data_width] = memory[addr] + memory_read, + // memory_write(memory: memory[addr_width, data_width], addr: bit[addr_width], data: bit[data_width]): memory[addr_width, data_width] + // returns a copy of `memory` but with the value at `addr` changed to `data` + memory_write + }; + // returns the name of a Fn value, as a string literal + const char *fn_to_string(Fn); + // Sort represents the sort or type of a node + // currently the only two sorts are signal/bit and memory + class Sort { + std::variant> _v; + public: + explicit Sort(int width) : _v(width) { } + Sort(int addr_width, int data_width) : _v(std::make_pair(addr_width, data_width)) { } + bool is_signal() const { return _v.index() == 0; } + bool is_memory() const { return _v.index() == 1; } + // returns the width of a bitvector sort, errors out for other sorts + int width() const { return std::get<0>(_v); } + // returns the address width of a bitvector sort, errors out for other sorts + int addr_width() const { return std::get<1>(_v).first; } + // returns the data width of a bitvector sort, errors out for other sorts + int data_width() const { return std::get<1>(_v).second; } + bool operator==(Sort const& other) const { return _v == other._v; } + [[nodiscard]] Hasher hash_into(Hasher h) const { h.eat(_v); return h; } + }; + class IR; + class Factory; + class Node; + class IRInput { + friend class Factory; + public: + TwineRef name; + TwineRef kind; + Sort sort; + private: + IRInput(IR &, TwineRef name, TwineRef kind, Sort sort) + : name(name), kind(kind), sort(std::move(sort)) {} + }; + class IROutput { + friend class Factory; + IR &_ir; + public: + TwineRef name; + TwineRef kind; + Sort sort; + private: + IROutput(IR &ir, TwineRef name, TwineRef kind, Sort sort) + : _ir(ir), name(name), kind(kind), sort(std::move(sort)) {} + public: + Node value() const; + bool has_value() const; + void set_value(Node value); + }; + class IRState { + friend class Factory; + IR &_ir; + public: + TwineRef name; + TwineRef kind; + Sort sort; + private: + std::variant _initial; + IRState(IR &ir, TwineRef name, TwineRef kind, Sort sort) + : _ir(ir), name(name), kind(kind), sort(std::move(sort)) {} + public: + Node next_value() const; + bool has_next_value() const; + RTLIL::Const const& initial_value_signal() const { return std::get(_initial); } + MemContents const& initial_value_memory() const { return std::get(_initial); } + void set_next_value(Node value); + void set_initial_value(RTLIL::Const value) { value.extu(sort.width()); _initial = std::move(value); } + void set_initial_value(MemContents value) { log_assert(Sort(value.addr_width(), value.data_width()) == sort); _initial = std::move(value); } + }; + class IR { + friend class Factory; + friend class Node; + friend class IRInput; + friend class IROutput; + friend class IRState; + // one NodeData is stored per Node, containing the function and non-node arguments + // note that NodeData is deduplicated by ComputeGraph + class NodeData { + Fn _fn; + std::variant< + std::monostate, + RTLIL::Const, + std::pair, + int + > _extra; + public: + NodeData() : _fn(Fn::invalid) {} + NodeData(Fn fn) : _fn(fn) {} + template NodeData(Fn fn, T &&extra) : _fn(fn), _extra(std::forward(extra)) {} + Fn fn() const { return _fn; } + const RTLIL::Const &as_const() const { return std::get(_extra); } + std::pair as_idstring_pair() const { return std::get>(_extra); } + int as_int() const { return std::get(_extra); } + [[nodiscard]] Hasher hash_into(Hasher h) const { + h.eat((unsigned int) _fn); + h.eat(_extra); + return h; + } + bool operator==(NodeData const &other) const { + return _fn == other._fn && _extra == other._extra; + } + }; + // Attr contains all the information about a note that should not be deduplicated + struct Attr { + Sort sort; + }; + // our specialised version of ComputeGraph + // the sparse_attr TwineRef stores a naming suggestion, retrieved with name() + // the key is currently used to identify the nodes that represent output and next state values + // the bool is true for next state values + using Graph = ComputeGraph>; + Graph _graph; + dict, IRInput> _inputs; + dict, IROutput> _outputs; + dict, IRState> _states; + IR::Graph::Ref mutate(Node n); + public: + Design *design = nullptr; + static IR from_module(Module *module); + Factory factory(); + int size() const { return _graph.size(); } + Node operator[](int i); + void topological_sort(); + void forward_buf(); + IRInput const& input(TwineRef name, TwineRef kind) const { return _inputs.at({name, kind}); } + IRInput const& input(TwineRef name) const { return input(name, TW($input)); } + IROutput const& output(TwineRef name, TwineRef kind) const { return _outputs.at({name, kind}); } + IROutput const& output(TwineRef name) const { return output(name, TW($output)); } + IRState const& state(TwineRef name, TwineRef kind) const { return _states.at({name, kind}); } + IRState const& state(TwineRef name) const { return state(name, TW($state)); } + bool has_input(TwineRef name, TwineRef kind) const { return _inputs.count({name, kind}); } + bool has_output(TwineRef name, TwineRef kind) const { return _outputs.count({name, kind}); } + bool has_state(TwineRef name, TwineRef kind) const { return _states.count({name, kind}); } + vector inputs(TwineRef kind) const; + vector inputs() const { return inputs(TW($input)); } + vector outputs(TwineRef kind) const; + vector outputs() const { return outputs(TW($output)); } + vector states(TwineRef kind) const; + vector states() const { return states(TW($state)); } + vector all_inputs() const; + vector all_outputs() const; + vector all_states() const; + class iterator { + friend class IR; + IR *_ir; + int _index; + iterator(IR *ir, int index) : _ir(ir), _index(index) {} + public: + using iterator_category = std::input_iterator_tag; + using value_type = Node; + using pointer = arrow_proxy; + using reference = Node; + using difference_type = ptrdiff_t; + Node operator*(); + iterator &operator++() { _index++; return *this; } + bool operator!=(iterator const &other) const { return _ir != other._ir || _index != other._index; } + bool operator==(iterator const &other) const { return !(*this != other); } + pointer operator->(); + }; + iterator begin() { return iterator(this, 0); } + iterator end() { return iterator(this, _graph.size()); } + }; + // Node is an immutable reference to a FunctionalIR node + class Node { + friend class Factory; + friend class IR; + friend class IRInput; + friend class IROutput; + friend class IRState; + IR::Graph::ConstRef _ref; + Node(IR::Graph::ConstRef ref, Design *design) : _ref(ref), design(design) { } + explicit operator IR::Graph::ConstRef() { return _ref; } + public: + Design *design = nullptr; + // the node's index. may change if nodes are added or removed + int id() const { return _ref.index(); } + // a name suggestion for the node, which need not be unique + TwineRef name() const { + if(_ref.has_sparse_attr()) + return _ref.sparse_attr(); + else + return design->twines.add(std::string("\\n") + std::to_string(id())); + } + Fn fn() const { return _ref.function().fn(); } + Sort sort() const { return _ref.attr().sort; } + // returns the width of a bitvector node, errors out for other nodes + int width() const { return sort().width(); } + size_t arg_count() const { return _ref.size(); } + Node arg(int n) const { return Node(_ref.arg(n), design); } + // visit calls the appropriate visitor method depending on the type of the node + template auto visit(Visitor v) const + { + // currently templated but could be switched to AbstractVisitor & + switch(_ref.function().fn()) { + case Fn::invalid: log_error("invalid node in visit"); break; + case Fn::buf: return v.buf(*this, arg(0)); break; + case Fn::slice: return v.slice(*this, arg(0), _ref.function().as_int(), sort().width()); break; + case Fn::zero_extend: return v.zero_extend(*this, arg(0), width()); break; + case Fn::sign_extend: return v.sign_extend(*this, arg(0), width()); break; + case Fn::concat: return v.concat(*this, arg(0), arg(1)); break; + case Fn::add: return v.add(*this, arg(0), arg(1)); break; + case Fn::sub: return v.sub(*this, arg(0), arg(1)); break; + case Fn::mul: return v.mul(*this, arg(0), arg(1)); break; + case Fn::unsigned_div: return v.unsigned_div(*this, arg(0), arg(1)); break; + case Fn::unsigned_mod: return v.unsigned_mod(*this, arg(0), arg(1)); break; + case Fn::bitwise_and: return v.bitwise_and(*this, arg(0), arg(1)); break; + case Fn::bitwise_or: return v.bitwise_or(*this, arg(0), arg(1)); break; + case Fn::bitwise_xor: return v.bitwise_xor(*this, arg(0), arg(1)); break; + case Fn::bitwise_not: return v.bitwise_not(*this, arg(0)); break; + case Fn::unary_minus: return v.unary_minus(*this, arg(0)); break; + case Fn::reduce_and: return v.reduce_and(*this, arg(0)); break; + case Fn::reduce_or: return v.reduce_or(*this, arg(0)); break; + case Fn::reduce_xor: return v.reduce_xor(*this, arg(0)); break; + case Fn::equal: return v.equal(*this, arg(0), arg(1)); break; + case Fn::not_equal: return v.not_equal(*this, arg(0), arg(1)); break; + case Fn::signed_greater_than: return v.signed_greater_than(*this, arg(0), arg(1)); break; + case Fn::signed_greater_equal: return v.signed_greater_equal(*this, arg(0), arg(1)); break; + case Fn::unsigned_greater_than: return v.unsigned_greater_than(*this, arg(0), arg(1)); break; + case Fn::unsigned_greater_equal: return v.unsigned_greater_equal(*this, arg(0), arg(1)); break; + case Fn::logical_shift_left: return v.logical_shift_left(*this, arg(0), arg(1)); break; + case Fn::logical_shift_right: return v.logical_shift_right(*this, arg(0), arg(1)); break; + case Fn::arithmetic_shift_right: return v.arithmetic_shift_right(*this, arg(0), arg(1)); break; + case Fn::mux: return v.mux(*this, arg(0), arg(1), arg(2)); break; + case Fn::constant: return v.constant(*this, _ref.function().as_const()); break; + case Fn::input: return v.input(*this, _ref.function().as_idstring_pair().first, _ref.function().as_idstring_pair().second); break; + case Fn::state: return v.state(*this, _ref.function().as_idstring_pair().first, _ref.function().as_idstring_pair().second); break; + case Fn::memory_read: return v.memory_read(*this, arg(0), arg(1)); break; + case Fn::memory_write: return v.memory_write(*this, arg(0), arg(1), arg(2)); break; + } + log_abort(); + } + std::string to_string(); + std::string to_string(std::function); + }; + inline IR::Graph::Ref IR::mutate(Node n) { return _graph[n._ref.index()]; } + inline Node IR::operator[](int i) { return Node(_graph[i], design); } + inline Node IROutput::value() const { return Node(_ir._graph({name, kind, false}), _ir.design); } + inline bool IROutput::has_value() const { return _ir._graph.has_key({name, kind, false}); } + inline void IROutput::set_value(Node value) { log_assert(sort == value.sort()); _ir.mutate(value).assign_key({name, kind, false}); } + inline Node IRState::next_value() const { return Node(_ir._graph({name, kind, true}), _ir.design); } + inline bool IRState::has_next_value() const { return _ir._graph.has_key({name, kind, true}); } + inline void IRState::set_next_value(Node value) { log_assert(sort == value.sort()); _ir.mutate(value).assign_key({name, kind, true}); } + inline Node IR::iterator::operator*() { return Node(_ir->_graph[_index], _ir->design); } + inline arrow_proxy IR::iterator::operator->() { return arrow_proxy(**this); } + // AbstractVisitor provides an abstract base class for visitors + template struct AbstractVisitor { + virtual T buf(Node self, Node n) = 0; + virtual T slice(Node self, Node a, int offset, int out_width) = 0; + virtual T zero_extend(Node self, Node a, int out_width) = 0; + virtual T sign_extend(Node self, Node a, int out_width) = 0; + virtual T concat(Node self, Node a, Node b) = 0; + virtual T add(Node self, Node a, Node b) = 0; + virtual T sub(Node self, Node a, Node b) = 0; + virtual T mul(Node self, Node a, Node b) = 0; + virtual T unsigned_div(Node self, Node a, Node b) = 0; + virtual T unsigned_mod(Node self, Node a, Node b) = 0; + virtual T bitwise_and(Node self, Node a, Node b) = 0; + virtual T bitwise_or(Node self, Node a, Node b) = 0; + virtual T bitwise_xor(Node self, Node a, Node b) = 0; + virtual T bitwise_not(Node self, Node a) = 0; + virtual T unary_minus(Node self, Node a) = 0; + virtual T reduce_and(Node self, Node a) = 0; + virtual T reduce_or(Node self, Node a) = 0; + virtual T reduce_xor(Node self, Node a) = 0; + virtual T equal(Node self, Node a, Node b) = 0; + virtual T not_equal(Node self, Node a, Node b) = 0; + virtual T signed_greater_than(Node self, Node a, Node b) = 0; + virtual T signed_greater_equal(Node self, Node a, Node b) = 0; + virtual T unsigned_greater_than(Node self, Node a, Node b) = 0; + virtual T unsigned_greater_equal(Node self, Node a, Node b) = 0; + virtual T logical_shift_left(Node self, Node a, Node b) = 0; + virtual T logical_shift_right(Node self, Node a, Node b) = 0; + virtual T arithmetic_shift_right(Node self, Node a, Node b) = 0; + virtual T mux(Node self, Node a, Node b, Node s) = 0; + virtual T constant(Node self, RTLIL::Const const & value) = 0; + virtual T input(Node self, TwineRef name, TwineRef kind) = 0; + virtual T state(Node self, TwineRef name, TwineRef kind) = 0; + virtual T memory_read(Node self, Node mem, Node addr) = 0; + virtual T memory_write(Node self, Node mem, Node addr, Node data) = 0; + }; + // DefaultVisitor provides defaults for all visitor methods which just calls default_handler + template struct DefaultVisitor : public AbstractVisitor { + virtual T default_handler(Node self) = 0; + T buf(Node self, Node) override { return default_handler(self); } + T slice(Node self, Node, int, int) override { return default_handler(self); } + T zero_extend(Node self, Node, int) override { return default_handler(self); } + T sign_extend(Node self, Node, int) override { return default_handler(self); } + T concat(Node self, Node, Node) override { return default_handler(self); } + T add(Node self, Node, Node) override { return default_handler(self); } + T sub(Node self, Node, Node) override { return default_handler(self); } + T mul(Node self, Node, Node) override { return default_handler(self); } + T unsigned_div(Node self, Node, Node) override { return default_handler(self); } + T unsigned_mod(Node self, Node, Node) override { return default_handler(self); } + T bitwise_and(Node self, Node, Node) override { return default_handler(self); } + T bitwise_or(Node self, Node, Node) override { return default_handler(self); } + T bitwise_xor(Node self, Node, Node) override { return default_handler(self); } + T bitwise_not(Node self, Node) override { return default_handler(self); } + T unary_minus(Node self, Node) override { return default_handler(self); } + T reduce_and(Node self, Node) override { return default_handler(self); } + T reduce_or(Node self, Node) override { return default_handler(self); } + T reduce_xor(Node self, Node) override { return default_handler(self); } + T equal(Node self, Node, Node) override { return default_handler(self); } + T not_equal(Node self, Node, Node) override { return default_handler(self); } + T signed_greater_than(Node self, Node, Node) override { return default_handler(self); } + T signed_greater_equal(Node self, Node, Node) override { return default_handler(self); } + T unsigned_greater_than(Node self, Node, Node) override { return default_handler(self); } + T unsigned_greater_equal(Node self, Node, Node) override { return default_handler(self); } + T logical_shift_left(Node self, Node, Node) override { return default_handler(self); } + T logical_shift_right(Node self, Node, Node) override { return default_handler(self); } + T arithmetic_shift_right(Node self, Node, Node) override { return default_handler(self); } + T mux(Node self, Node, Node, Node) override { return default_handler(self); } + T constant(Node self, RTLIL::Const const &) override { return default_handler(self); } + T input(Node self, TwineRef, TwineRef) override { return default_handler(self); } + T state(Node self, TwineRef, TwineRef) override { return default_handler(self); } + T memory_read(Node self, Node, Node) override { return default_handler(self); } + T memory_write(Node self, Node, Node, Node) override { return default_handler(self); } + }; + // a factory is used to modify a FunctionalIR. it creates new nodes and allows for some modification of existing nodes. + class Factory { + friend class IR; + IR &_ir; + explicit Factory(IR &ir) : _ir(ir) {} + Node add(IR::NodeData &&fn, Sort const &sort, std::initializer_list args) { + log_assert(!sort.is_signal() || sort.width() > 0); + log_assert(!sort.is_memory() || (sort.addr_width() > 0 && sort.data_width() > 0)); + IR::Graph::Ref ref = _ir._graph.add(std::move(fn), {std::move(sort)}); + for (auto arg : args) + ref.append_arg(IR::Graph::ConstRef(arg)); + return Node(ref, _ir.design); + } + void check_basic_binary(Node const &a, Node const &b) { log_assert(a.sort().is_signal() && a.sort() == b.sort()); } + void check_shift(Node const &a, Node const &b) { log_assert(a.sort().is_signal() && b.sort().is_signal() && b.width() == ceil_log2(a.width())); } + void check_unary(Node const &a) { log_assert(a.sort().is_signal()); } + public: + IR &ir() { return _ir; } + Node slice(Node a, int offset, int out_width) { + log_assert(a.sort().is_signal() && offset + out_width <= a.sort().width()); + if(offset == 0 && out_width == a.width()) + return a; + return add(IR::NodeData(Fn::slice, offset), Sort(out_width), {a}); + } + // extend will either extend or truncate the provided value to reach the desired width + Node extend(Node a, int out_width, bool is_signed) { + int in_width = a.sort().width(); + log_assert(a.sort().is_signal()); + if(in_width == out_width) + return a; + if(in_width > out_width) + return slice(a, 0, out_width); + if(is_signed) + return add(Fn::sign_extend, Sort(out_width), {a}); + else + return add(Fn::zero_extend, Sort(out_width), {a}); + } + Node concat(Node a, Node b) { + log_assert(a.sort().is_signal() && b.sort().is_signal()); + return add(Fn::concat, Sort(a.sort().width() + b.sort().width()), {a, b}); + } + Node add(Node a, Node b) { check_basic_binary(a, b); return add(Fn::add, a.sort(), {a, b}); } + Node sub(Node a, Node b) { check_basic_binary(a, b); return add(Fn::sub, a.sort(), {a, b}); } + Node mul(Node a, Node b) { check_basic_binary(a, b); return add(Fn::mul, a.sort(), {a, b}); } + Node unsigned_div(Node a, Node b) { check_basic_binary(a, b); return add(Fn::unsigned_div, a.sort(), {a, b}); } + Node unsigned_mod(Node a, Node b) { check_basic_binary(a, b); return add(Fn::unsigned_mod, a.sort(), {a, b}); } + Node bitwise_and(Node a, Node b) { check_basic_binary(a, b); return add(Fn::bitwise_and, a.sort(), {a, b}); } + Node bitwise_or(Node a, Node b) { check_basic_binary(a, b); return add(Fn::bitwise_or, a.sort(), {a, b}); } + Node bitwise_xor(Node a, Node b) { check_basic_binary(a, b); return add(Fn::bitwise_xor, a.sort(), {a, b}); } + Node bitwise_not(Node a) { check_unary(a); return add(Fn::bitwise_not, a.sort(), {a}); } + Node unary_minus(Node a) { check_unary(a); return add(Fn::unary_minus, a.sort(), {a}); } + Node reduce_and(Node a) { + check_unary(a); + if(a.width() == 1) + return a; + return add(Fn::reduce_and, Sort(1), {a}); + } + Node reduce_or(Node a) { + check_unary(a); + if(a.width() == 1) + return a; + return add(Fn::reduce_or, Sort(1), {a}); + } + Node reduce_xor(Node a) { + check_unary(a); + if(a.width() == 1) + return a; + return add(Fn::reduce_xor, Sort(1), {a}); + } + Node equal(Node a, Node b) { check_basic_binary(a, b); return add(Fn::equal, Sort(1), {a, b}); } + Node not_equal(Node a, Node b) { check_basic_binary(a, b); return add(Fn::not_equal, Sort(1), {a, b}); } + Node signed_greater_than(Node a, Node b) { check_basic_binary(a, b); return add(Fn::signed_greater_than, Sort(1), {a, b}); } + Node signed_greater_equal(Node a, Node b) { check_basic_binary(a, b); return add(Fn::signed_greater_equal, Sort(1), {a, b}); } + Node unsigned_greater_than(Node a, Node b) { check_basic_binary(a, b); return add(Fn::unsigned_greater_than, Sort(1), {a, b}); } + Node unsigned_greater_equal(Node a, Node b) { check_basic_binary(a, b); return add(Fn::unsigned_greater_equal, Sort(1), {a, b}); } + Node logical_shift_left(Node a, Node b) { check_shift(a, b); return add(Fn::logical_shift_left, a.sort(), {a, b}); } + Node logical_shift_right(Node a, Node b) { check_shift(a, b); return add(Fn::logical_shift_right, a.sort(), {a, b}); } + Node arithmetic_shift_right(Node a, Node b) { check_shift(a, b); return add(Fn::arithmetic_shift_right, a.sort(), {a, b}); } + Node mux(Node a, Node b, Node s) { + log_assert(a.sort().is_signal() && a.sort() == b.sort() && s.sort() == Sort(1)); + return add(Fn::mux, a.sort(), {a, b, s}); + } + Node memory_read(Node mem, Node addr) { + log_assert(mem.sort().is_memory() && addr.sort().is_signal() && mem.sort().addr_width() == addr.sort().width()); + return add(Fn::memory_read, Sort(mem.sort().data_width()), {mem, addr}); + } + Node memory_write(Node mem, Node addr, Node data) { + log_assert(mem.sort().is_memory() && addr.sort().is_signal() && data.sort().is_signal() && + mem.sort().addr_width() == addr.sort().width() && mem.sort().data_width() == data.sort().width()); + return add(Fn::memory_write, mem.sort(), {mem, addr, data}); + } + Node constant(RTLIL::Const value) { + int s = value.size(); + return add(IR::NodeData(Fn::constant, std::move(value)), Sort(s), {}); + } + Node create_pending(int width) { + return add(Fn::buf, Sort(width), {}); + } + void update_pending(Node node, Node value) { + log_assert(node._ref.function() == Fn::buf && node._ref.size() == 0); + log_assert(node.sort() == value.sort()); + _ir.mutate(node).append_arg(value._ref); + } + IRInput &add_input(TwineRef name, TwineRef kind, Sort sort) { + auto [it, inserted] = _ir._inputs.emplace({name, kind}, IRInput(_ir, name, kind, std::move(sort))); + if (!inserted) log_error("input `%s` was re-defined", name); + return it->second; + } + IROutput &add_output(TwineRef name, TwineRef kind, Sort sort) { + auto [it, inserted] = _ir._outputs.emplace({name, kind}, IROutput(_ir, name, kind, std::move(sort))); + if (!inserted) log_error("output `%s` was re-defined", name); + return it->second; + } + IRState &add_state(TwineRef name, TwineRef kind, Sort sort) { + auto [it, inserted] = _ir._states.emplace({name, kind}, IRState(_ir, name, kind, std::move(sort))); + if (!inserted) log_error("state `%s` was re-defined", name); + return it->second; + } + Node value(IRInput const& input) { + return add(IR::NodeData(Fn::input, std::pair(input.name, input.kind)), input.sort, {}); + } + Node value(IRState const& state) { + return add(IR::NodeData(Fn::state, std::pair(state.name, state.kind)), state.sort, {}); + } + void suggest_name(Node node, TwineRef name) { + _ir.mutate(node).sparse_attr() = name; + } + }; + inline Factory IR::factory() { return Factory(*this); } + template class Scope { + protected: + char substitution_character = '_'; + Design *design = nullptr; + virtual bool is_character_legal(char, int) = 0; + private: + pool _used_names; + dict _by_id; + public: + void reserve(std::string name) { + _used_names.insert(std::move(name)); + } + std::string unique_name(TwineRef suggestion) { + std::string str = design->twines.unescaped_str(suggestion); + for(size_t i = 0; i < str.size(); i++) + if(!is_character_legal(str[i], i)) + str[i] = substitution_character; + if(_used_names.count(str) == 0) { + _used_names.insert(str); + return str; + } + for (int idx = 0 ; ; idx++){ + std::string suffixed = str + "_" + std::to_string(idx); + if(_used_names.count(suffixed) == 0) { + _used_names.insert(suffixed); + return suffixed; + } + } + } + std::string operator()(Id id, TwineRef suggestion) { + auto it = _by_id.find(id); + if(it != _by_id.end()) + return it->second; + std::string str = unique_name(suggestion); + _by_id.insert({id, str}); + return str; + } + }; + class Writer { + std::ostream *os; + void print_impl(const char *fmt, vector>& fns); + public: + Writer(std::ostream &os) : os(&os) {} + template Writer& operator <<(T&& arg) { *os << std::forward(arg); return *this; } + template + void print(const char *fmt, Args&&... args) + { + vector> fns { [&]() { *this << args; }... }; + print_impl(fmt, fns); + } + template + void print_with(Fn fn, const char *fmt, Args&&... args) + { + vector> fns { [&]() { + if constexpr (std::is_invocable_v) + *this << fn(args); + else + *this << args; }... + }; + print_impl(fmt, fns); + } + }; -// } +} -// YOSYS_NAMESPACE_END +YOSYS_NAMESPACE_END -// #endif +#endif