/* * 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. * */ #include "kernel/functional.h" #include "kernel/topo_scc.h" #include "ff.h" #include "ffinit.h" #include YOSYS_NAMESPACE_BEGIN namespace Functional { const char *fn_to_string(Fn fn) { switch(fn) { case Fn::invalid: return "invalid"; case Fn::buf: return "buf"; case Fn::slice: return "slice"; case Fn::zero_extend: return "zero_extend"; case Fn::sign_extend: return "sign_extend"; case Fn::concat: return "concat"; case Fn::add: return "add"; case Fn::sub: return "sub"; case Fn::mul: return "mul"; case Fn::unsigned_div: return "unsigned_div"; case Fn::unsigned_mod: return "unsigned_mod"; case Fn::bitwise_and: return "bitwise_and"; case Fn::bitwise_or: return "bitwise_or"; case Fn::bitwise_xor: return "bitwise_xor"; case Fn::bitwise_not: return "bitwise_not"; case Fn::reduce_and: return "reduce_and"; case Fn::reduce_or: return "reduce_or"; case Fn::reduce_xor: return "reduce_xor"; case Fn::unary_minus: return "unary_minus"; case Fn::equal: return "equal"; case Fn::not_equal: return "not_equal"; case Fn::signed_greater_than: return "signed_greater_than"; case Fn::signed_greater_equal: return "signed_greater_equal"; case Fn::unsigned_greater_than: return "unsigned_greater_than"; case Fn::unsigned_greater_equal: return "unsigned_greater_equal"; case Fn::logical_shift_left: return "logical_shift_left"; case Fn::logical_shift_right: return "logical_shift_right"; case Fn::arithmetic_shift_right: return "arithmetic_shift_right"; case Fn::mux: return "mux"; case Fn::constant: return "constant"; case Fn::input: return "input"; case Fn::state: return "state"; case Fn::memory_read: return "memory_read"; case Fn::memory_write: return "memory_write"; } log_error("fn_to_string: unknown Functional::Fn value %d", (int)fn); } vector IR::inputs(IdString kind) const { vector ret; for (const auto &[name, input] : _inputs) if(input.kind == kind) ret.push_back(&input); return ret; } vector IR::outputs(IdString kind) const { vector ret; for (const auto &[name, output] : _outputs) if(output.kind == kind) ret.push_back(&output); return ret; } vector IR::states(IdString kind) const { vector ret; for (const auto &[name, state] : _states) if(state.kind == kind) ret.push_back(&state); return ret; } vector IR::all_inputs() const { vector ret; for (const auto &[name, input] : _inputs) ret.push_back(&input); return ret; } vector IR::all_outputs() const { vector ret; for (const auto &[name, output] : _outputs) ret.push_back(&output); return ret; } vector IR::all_states() const { vector ret; for (const auto &[name, state] : _states) ret.push_back(&state); return ret; } struct PrintVisitor : DefaultVisitor { std::function np; PrintVisitor(std::function np) : np(np) { } // as a general rule the default handler is good enough iff the only arguments are of type Node std::string slice(Node, Node a, int offset, int out_width) override { return "slice(" + np(a) + ", " + std::to_string(offset) + ", " + std::to_string(out_width) + ")"; } 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 default_handler(Node self) override { std::string ret = fn_to_string(self.fn()); ret += "("; for(size_t i = 0; i < self.arg_count(); i++) { if(i > 0) ret += ", "; ret += np(self.arg(i)); } ret += ")"; return ret; } }; std::string Node::to_string() { return to_string([](Node n) { return RTLIL::unescape_id(n.name()); }); } std::string Node::to_string(std::function np) { return visit(PrintVisitor(np)); } class CellSimplifier { Factory &factory; Node sign(Node a) { return factory.slice(a, a.width() - 1, 1); } Node neg_if(Node a, Node s) { return factory.mux(a, factory.unary_minus(a), s); } Node abs(Node a) { return neg_if(a, sign(a)); } Node handle_shift(Node a, Node b, bool is_right, bool is_signed) { // to prevent new_width == 0, we handle this case separately if(a.width() == 1) { if(!is_signed) return factory.bitwise_and(a, factory.bitwise_not(factory.reduce_or(b))); else return a; } int new_width = ceil_log2(a.width()); Node b_truncated = factory.extend(b, new_width, false); Node y = !is_right ? factory.logical_shift_left(a, b_truncated) : !is_signed ? factory.logical_shift_right(a, b_truncated) : factory.arithmetic_shift_right(a, b_truncated); if(b.width() <= new_width) return y; Node overflow = factory.unsigned_greater_equal(b, factory.constant(RTLIL::Const(a.width(), b.width()))); Node y_if_overflow = is_signed ? factory.extend(sign(a), a.width(), true) : factory.constant(RTLIL::Const(State::S0, a.width())); return factory.mux(y, y_if_overflow, overflow); } public: Node logical_shift_left(Node a, Node b) { return handle_shift(a, b, false, false); } Node logical_shift_right(Node a, Node b) { return handle_shift(a, b, true, false); } Node arithmetic_shift_right(Node a, Node b) { return handle_shift(a, b, true, true); } Node bitwise_mux(Node a, Node b, Node s) { Node aa = factory.bitwise_and(a, factory.bitwise_not(s)); Node bb = factory.bitwise_and(b, s); return factory.bitwise_or(aa, bb); } CellSimplifier(Factory &f) : factory(f) {} private: Node handle_pow(Node a0, Node b, int y_width, bool is_signed) { Node a = factory.extend(a0, y_width, is_signed); Node r = factory.constant(Const(1, y_width)); for(int i = 0; i < b.width(); i++) { Node b_bit = factory.slice(b, i, 1); r = factory.mux(r, factory.mul(r, a), b_bit); a = factory.mul(a, a); } if (is_signed) { Node a_ge_1 = factory.unsigned_greater_than(abs(a0), factory.constant(Const(1, a0.width()))); Node zero_result = factory.bitwise_and(a_ge_1, sign(b)); r = factory.mux(r, factory.constant(Const(0, y_width)), zero_result); } return r; } Node handle_bmux(Node a, Node s, int a_offset, int width, int sn) { if(sn < 1) return factory.slice(a, a_offset, width); else { Node y0 = handle_bmux(a, s, a_offset, width, sn - 1); Node y1 = handle_bmux(a, s, a_offset + (width << (sn - 1)), width, sn - 1); return factory.mux(y0, y1, factory.slice(s, sn - 1, 1)); } } Node handle_pmux(Node a, Node b, Node s) { // TODO : what to do about multiple b bits set ? log_assert(b.width() == a.width() * s.width()); Node y = a; for(int i = 0; i < s.width(); i++) y = factory.mux(y, factory.slice(b, a.width() * i, a.width()), factory.slice(s, i, 1)); return y; } dict handle_fa(Node a, Node b, Node c) { Node t1 = factory.bitwise_xor(a, b); Node t2 = factory.bitwise_and(a, b); Node t3 = factory.bitwise_and(c, t1); Node y = factory.bitwise_xor(c, t1); Node x = factory.bitwise_or(t2, t3); return {{ID(X), x}, {ID(Y), y}}; } dict handle_alu(Node a_in, Node b_in, int y_width, bool is_signed, Node ci, Node bi) { Node a = factory.extend(a_in, y_width, is_signed); Node b_uninverted = factory.extend(b_in, y_width, is_signed); Node b = factory.mux(b_uninverted, factory.bitwise_not(b_uninverted), bi); Node x = factory.bitwise_xor(a, b); // we can compute the carry into each bit using (a+b+c)^a^b. since we want the carry out, // i.e. the carry into the next bit, we have to add an extra bit to a and b, and // then slice off the bottom bit of the result. Node a_extra = factory.extend(a, y_width + 1, false); Node b_extra = factory.extend(b, y_width + 1, false); Node y_extra = factory.add(factory.add(a_extra, b_extra), factory.extend(ci, a.width() + 1, false)); Node y = factory.slice(y_extra, 0, y_width); Node carries = factory.bitwise_xor(y_extra, factory.bitwise_xor(a_extra, b_extra)); Node co = factory.slice(carries, 1, y_width); return {{ID(X), x}, {ID(Y), y}, {ID(CO), co}}; } Node handle_lcu(Node p, Node g, Node ci) { return handle_alu(g, factory.bitwise_or(p, g), g.width(), false, ci, factory.constant(Const(State::S0, 1))).at(ID(CO)); } public: std::variant, Node> handle(IdString cellName, IdString 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(); 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)})){ 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); if(cellType == ID($add)) return factory.add(a, b); else if(cellType == ID($sub)) return factory.sub(a, b); else if(cellType == ID($mul)) return factory.mul(a, b); else if(cellType == ID($and)) return factory.bitwise_and(a, b); else if(cellType == ID($or)) return factory.bitwise_or(a, b); else if(cellType == ID($xor)) return factory.bitwise_xor(a, b); else if(cellType == ID($xnor)) 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)})){ 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)})) return factory.extend(factory.equal(a, b), y_width, false); 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); else if(cellType == ID($le)) return factory.extend(is_signed ? factory.signed_greater_equal(b, a) : factory.unsigned_greater_equal(b, a), y_width, false); else if(cellType == ID($gt)) return factory.extend(is_signed ? factory.signed_greater_than(a, b) : factory.unsigned_greater_than(a, b), y_width, false); else if(cellType == ID($ge)) 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)})){ 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); return factory.extend(y, y_width, false); }else if(cellType == ID($not)){ Node a = factory.extend(inputs.at(ID(A)), y_width, a_signed); return factory.bitwise_not(a); }else if(cellType == ID($pos)){ return factory.extend(inputs.at(ID(A)), y_width, a_signed); }else if(cellType == ID($neg)){ Node a = factory.extend(inputs.at(ID(A)), y_width, a_signed); return factory.unary_minus(a); }else if(cellType == ID($logic_not)){ 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)})){ 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)})){ 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); }else if(cellType == ID($shl) || cellType == ID($sshl)){ Node a = factory.extend(inputs.at(ID(A)), y_width, a_signed); Node b = inputs.at(ID(B)); return logical_shift_left(a, b); }else if(cellType == ID($shr) || cellType == ID($sshr)){ int width = max(a_width, y_width); Node a = factory.extend(inputs.at(ID(A)), width, a_signed); Node b = inputs.at(ID(B)); Node y = a_signed && cellType == ID($sshr) ? arithmetic_shift_right(a, b) : logical_shift_right(a, b); return factory.extend(y, y_width, a_signed); }else if(cellType == ID($shiftx) || cellType == ID($shift)){ int width = max(a_width, y_width); Node a = factory.extend(inputs.at(ID(A)), width, cellType == ID($shift) && a_signed); Node b = inputs.at(ID(B)); Node shr = logical_shift_right(a, b); if(b_signed) { Node shl = logical_shift_left(a, factory.unary_minus(b)); Node y = factory.mux(shr, shl, sign(b)); return factory.extend(y, y_width, false); } else { return factory.extend(shr, y_width, false); } }else if(cellType == ID($mux)){ return factory.mux(inputs.at(ID(A)), inputs.at(ID(B)), inputs.at(ID(S))); }else if(cellType == ID($pmux)){ return handle_pmux(inputs.at(ID(A)), inputs.at(ID(B)), inputs.at(ID(S))); }else if(cellType == ID($concat)){ Node a = inputs.at(ID(A)); Node b = inputs.at(ID(B)); return factory.concat(a, b); }else if(cellType == ID($slice)){ 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)})) { 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); Node b = factory.extend(inputs.at(ID(B)), width, is_signed); if(is_signed) { if(cellType == ID($div)) { // divide absolute values, then flip the sign if input signs differ // but extend the width first, to handle the case (most negative value) / (-1) Node abs_y = factory.unsigned_div(abs(a), abs(b)); Node out_sign = factory.not_equal(sign(a), sign(b)); return neg_if(factory.extend(abs_y, y_width, false), out_sign); } else if(cellType == ID($mod)) { // similar to division but output sign == divisor sign Node abs_y = factory.unsigned_mod(abs(a), abs(b)); return neg_if(factory.extend(abs_y, y_width, false), sign(a)); } else if(cellType == ID($divfloor)) { // if b is negative, flip both signs so that b is positive Node b_sign = sign(b); Node a1 = neg_if(a, b_sign); Node b1 = neg_if(b, b_sign); // if a is now negative, calculate ~((~a) / b) = -((-a - 1) / b + 1) // which equals the negative of (-a) / b with rounding up rather than down // note that to handle the case where a = most negative value properly, // we have to calculate a1_sign from the original values rather than using sign(a1) Node a1_sign = factory.bitwise_and(factory.not_equal(sign(a), sign(b)), factory.reduce_or(a)); Node a2 = factory.mux(a1, factory.bitwise_not(a1), a1_sign); Node y1 = factory.unsigned_div(a2, b1); Node y2 = factory.extend(y1, y_width, false); return factory.mux(y2, factory.bitwise_not(y2), a1_sign); } else if(cellType == ID($modfloor)) { // calculate |a| % |b| and then subtract from |b| if input signs differ and the remainder is non-zero Node abs_b = abs(b); Node abs_y = factory.unsigned_mod(abs(a), abs_b); Node flip_y = factory.bitwise_and(factory.bitwise_xor(sign(a), sign(b)), factory.reduce_or(abs_y)); Node y_flipped = factory.mux(abs_y, factory.sub(abs_b, abs_y), flip_y); // since y_flipped is strictly less than |b|, the top bit is always 0 and we can just sign extend the flipped result Node y = neg_if(y_flipped, sign(b)); return factory.extend(y, y_width, true); } else log_error("unhandled cell in CellSimplifier %s\n", cellType.c_str()); } else { 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); } } else if(cellType == ID($pow)) { return handle_pow(inputs.at(ID(A)), inputs.at(ID(B)), y_width, a_signed && b_signed); } else if (cellType == ID($lut)) { int width = parameters.at(ID(WIDTH)).as_int(); Const lut_table = parameters.at(ID(LUT)); lut_table.extu(1 << width); return handle_bmux(factory.constant(lut_table), inputs.at(ID(A)), 0, 1, width); } else if (cellType == ID($bwmux)) { Node a = inputs.at(ID(A)); Node b = inputs.at(ID(B)); Node s = inputs.at(ID(S)); return factory.bitwise_or( factory.bitwise_and(a, factory.bitwise_not(s)), factory.bitwise_and(b, s)); } else if (cellType == ID($bweqx)) { Node a = inputs.at(ID(A)); Node b = inputs.at(ID(B)); return factory.bitwise_not(factory.bitwise_xor(a, b)); } else if(cellType == ID($bmux)) { int width = parameters.at(ID(WIDTH)).as_int(); int s_width = parameters.at(ID(S_WIDTH)).as_int(); return handle_bmux(inputs.at(ID(A)), inputs.at(ID(S)), 0, width, s_width); } else if(cellType == ID($demux)) { int width = parameters.at(ID(WIDTH)).as_int(); int s_width = parameters.at(ID(S_WIDTH)).as_int(); int y_width = width << s_width; int b_width = ceil_log2(y_width); Node a = factory.extend(inputs.at(ID(A)), y_width, false); Node s = factory.extend(inputs.at(ID(S)), b_width, false); Node b = factory.mul(s, factory.constant(Const(width, b_width))); return factory.logical_shift_left(a, b); } else if(cellType == ID($fa)) { return handle_fa(inputs.at(ID(A)), inputs.at(ID(B)), inputs.at(ID(C))); } else if(cellType == ID($lcu)) { 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)})) { 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)})) { int width = parameters.at(ID(WIDTH)).as_int(); auto &input = factory.add_input(cellName, cellType, Sort(width)); return factory.value(input); } else if(cellType == ID($initstate)) { if(factory.ir().has_state(ID($initstate), ID($state))) return factory.value(factory.ir().state(ID($initstate))); else { auto &state = factory.add_state(ID($initstate), ID($state), Sort(1)); state.set_initial_value(RTLIL::Const(State::S1, 1)); state.set_next_value(factory.constant(RTLIL::Const(State::S0, 1))); return factory.value(state); } } else if(cellType == ID($check)) { log_error("The design contains a $check cell `%s'. This is not supported by the functional backend. Call `chformal -lower' to avoid this error.\n", cellName.c_str()); } else { log_error("`%s' cells are not supported by the functional backend\n", cellType.c_str()); } } }; class FunctionalIRConstruction { std::deque> queue; dict graph_nodes; dict, Node> cell_outputs; DriverMap driver_map; Factory& factory; CellSimplifier simplifier; vector memories_vector; dict memories; SigMap sig_map; // TODO: this is only for FfInitVals, remove this once FfInitVals supports DriverMap FfInitVals ff_initvals; Node enqueue(DriveSpec const &spec) { auto it = graph_nodes.find(spec); if(it == graph_nodes.end()){ auto node = factory.create_pending(spec.size()); graph_nodes.insert({spec, node}); queue.emplace_back(spec); return node; }else return it->second; } Node enqueue_cell(Cell *cell, IdString port_name) { auto it = cell_outputs.find({cell, port_name}); if(it == cell_outputs.end()) { queue.emplace_back(cell); std::optional rv; for(auto const &[name, sigspec] : cell->connections()) if(driver_map.celltypes.cell_output(cell->type, name)) { auto node = factory.create_pending(sigspec.size()); factory.suggest_name(node, cell->name.str() + "$" + name.str()); cell_outputs.emplace({cell, name}, node); if(name == port_name) rv = node; } return *rv; } else return it->second; } public: FunctionalIRConstruction(Module *module, Factory &f) : factory(f) , simplifier(f) , sig_map(module) , ff_initvals(&sig_map, module) { driver_map.add(module); for (auto cell : module->cells()) { if (cell->type.in(ID($assert), ID($assume), ID($live), ID($fair), ID($cover), ID($check))) queue.emplace_back(cell); } for (auto wire : module->wires()) { if (wire->port_input) factory.add_input(wire->name, ID($input), Sort(wire->width)); if (wire->port_output) { auto &output = factory.add_output(wire->name, ID($output), Sort(wire->width)); output.set_value(enqueue(DriveChunk(DriveChunkWire(wire, 0, wire->width)))); } } memories_vector = Mem::get_all_memories(module); for (auto &mem : memories_vector) { if (mem.cell != nullptr) memories[mem.cell] = &mem; } } private: Node concatenate_read_results(Mem *mem, vector results) { // sanity check: all read ports concatenated should equal to the RD_DATA port const SigSpec &rd_data = mem->cell->connections().at(ID(RD_DATA)); int current = 0; for(size_t i = 0; i < mem->rd_ports.size(); i++) { int width = mem->width << mem->rd_ports[i].wide_log2; log_assert (results[i].width() == width); log_assert (mem->rd_ports[i].data == rd_data.extract(current, width)); current += width; } log_assert (current == rd_data.size()); log_assert (!results.empty()); Node node = results[0]; for(size_t i = 1; i < results.size(); i++) node = factory.concat(node, results[i]); return node; } Node handle_memory(Mem *mem) { // To simplify memory handling, the functional backend makes the following assumptions: // - Since async2sync or clk2fflogic must be run to use the functional backend, // we can assume that all ports are asynchronous. // - Async rd/wr are always transparent and so we must do reads after writes, // but we can ignore transparency_mask. // - We ignore collision_x_mask because x is a dont care value for us anyway. // - 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, ID($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, log_id(mem->module), log_id(mem->memid)); 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))); Node old_data = factory.memory_read(node, addr); Node wr_data = simplifier.bitwise_mux(old_data, new_data, en); node = factory.memory_write(node, addr, wr_data); } 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.", log_id(mem->module), log_id(mem->memid)); 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, log_id(mem->module), log_id(mem->memid)); Node addr = enqueue(driver_map(DriveSpec(rd.addr))); read_results.push_back(factory.memory_read(node, addr)); } state.set_next_value(node); return concatenate_read_results(mem, read_results); } void process_cell(Cell *cell) { if (cell->is_mem_cell()) { Mem *mem = memories.at(cell, nullptr); if (mem == nullptr) { log_assert(cell->has_memid()); log_error("The design contains an unpacked memory at %s. This is not supported by the functional backend. " "Call memory_collect to avoid this error.\n", log_const(cell->parameters.at(ID(MEMID)))); } Node node = handle_memory(mem); factory.update_pending(cell_outputs.at({cell, ID(RD_DATA)}), node); } else if (RTLIL::builtin_ff_cell_types().count(cell->type)) { FfData ff(&ff_initvals, cell); 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", log_id(cell->type), log_id(cell)); auto &state = factory.add_state(ff.name, ID($state), Sort(ff.width)); Node q_value = factory.value(state); factory.suggest_name(q_value, ff.name); factory.update_pending(cell_outputs.at({cell, ID(Q)}), q_value); state.set_next_value(enqueue(ff.sig_d)); state.set_initial_value(ff.val_init); } else { dict connections; IdString output_name; // for the single output case int n_outputs = 0; for(auto const &[name, sigspec] : cell->connections()) { if(driver_map.celltypes.cell_input(cell->type, name) && sigspec.size() > 0) connections.insert({ name, enqueue(DriveChunkPort(cell, {name, sigspec})) }); if(driver_map.celltypes.cell_output(cell->type, name)) { output_name = name; n_outputs++; } } std::variant, Node> outputs = simplifier.handle(cell->name, cell->type, 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); } else { for(auto [name, node] : std::get>(outputs)) factory.update_pending(cell_outputs.at({cell, name}), node); } } } void undriven(const char *name) { log_error("The design contains an undriven signal %s. This is not supported by the functional backend. " "Call setundef with appropriate options to avoid this error.\n", name); } // we perform this check separately to give better error messages that include the wire or port name void check_undriven(DriveSpec const& spec, std::string const& name) { for(auto const &chunk : spec.chunks()) if(chunk.is_none()) undriven(name.c_str()); } public: void process_queue() { for (; !queue.empty(); queue.pop_front()) { if(auto p = std::get_if(&queue.front()); p != nullptr) { process_cell(*p); continue; } DriveSpec spec = std::get(queue.front()); Node pending = graph_nodes.at(spec); if (spec.chunks().size() > 1) { auto chunks = spec.chunks(); Node node = enqueue(chunks[0]); for(size_t i = 1; i < chunks.size(); i++) node = factory.concat(node, enqueue(chunks[i])); factory.update_pending(pending, node); } else if (spec.chunks().size() == 1) { DriveChunk chunk = spec.chunks()[0]; if (chunk.is_wire()) { 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); factory.update_pending(pending, node); } else { DriveSpec driver = driver_map(DriveSpec(wire_chunk)); check_undriven(driver, RTLIL::unescape_id(wire_chunk.wire->name)); Node node = enqueue(driver); factory.suggest_name(node, wire_chunk.wire->name); factory.update_pending(pending, node); } } else { DriveChunkWire whole_wire(wire_chunk.wire, 0, wire_chunk.wire->width); Node node = factory.slice(enqueue(whole_wire), wire_chunk.offset, wire_chunk.width); factory.update_pending(pending, node); } } else if (chunk.is_port()) { DriveChunkPort port_chunk = chunk.port(); if (port_chunk.is_whole()) { if (driver_map.celltypes.cell_output(port_chunk.cell->type, port_chunk.port)) { Node node = enqueue_cell(port_chunk.cell, port_chunk.port); factory.update_pending(pending, node); } else { DriveSpec driver = driver_map(DriveSpec(port_chunk)); check_undriven(driver, RTLIL::unescape_id(port_chunk.cell->name) + " port " + RTLIL::unescape_id(port_chunk.port)); factory.update_pending(pending, enqueue(driver)); } } else { DriveChunkPort whole_port(port_chunk.cell, port_chunk.port, 0, GetSize(port_chunk.cell->connections().at(port_chunk.port))); Node node = factory.slice(enqueue(whole_port), port_chunk.offset, port_chunk.width); factory.update_pending(pending, node); } } 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.update_pending(pending, node); } else if (chunk.is_multiple()) { log_error("Signal %s has multiple drivers. This is not supported by the functional backend. " "If tristate drivers are used, call tristate -formal to avoid this error.\n", log_signal(chunk)); } else if (chunk.is_none()) { undriven(log_signal(chunk)); } else { log_error("unhandled drivespec: %s\n", log_signal(chunk)); log_abort(); } } else { log_abort(); } } } }; IR IR::from_module(Module *module) { IR ir; auto factory = ir.factory(); FunctionalIRConstruction ctor(module, factory); ctor.process_queue(); ir.topological_sort(); ir.forward_buf(); return ir; } void IR::topological_sort() { Graph::SccAdaptor compute_graph_scc(_graph); bool scc = false; std::vector perm; TopoSortedSccs toposort(compute_graph_scc, [&](int *begin, int *end) { perm.insert(perm.end(), begin, end); if (end > begin + 1) { log_warning("Combinational loop:\n"); for (int *i = begin; i != end; ++i) { Node node(_graph[*i]); log("- %s = %s\n", RTLIL::unescape_id(node.name()).c_str(), node.to_string().c_str()); } log("\n"); scc = true; } }); for(const auto &[name, state]: _states) if(state.has_next_value()) toposort.process(state.next_value().id()); for(const auto &[name, output]: _outputs) if(output.has_value()) toposort.process(output.value().id()); // any nodes untouched by this point are dead code and will be removed by permute _graph.permute(perm); if(scc) log_error("The design contains combinational loops. This is not supported by the functional backend. " "Try `scc -select; simplemap; select -clear` to avoid this error.\n"); } static IdString merge_name(IdString a, IdString b) { if(a[0] == '$' && b[0] == '\\') return b; else return a; } void IR::forward_buf() { std::vector perm, alias; perm.clear(); for (int i = 0; i < _graph.size(); ++i) { auto node = _graph[i]; if (node.function().fn() == Fn::buf && node.arg(0).index() < i) { int target_index = alias[node.arg(0).index()]; 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()); target_node.sparse_attr() = id; } else { IdString id = node.sparse_attr(); target_node.sparse_attr() = id; } } alias.push_back(target_index); } else { alias.push_back(GetSize(perm)); perm.push_back(i); } } _graph.permute(perm, alias); } // Quoting routine to make error messages nicer static std::string quote_fmt(const char *fmt) { std::string r; for(const char *p = fmt; *p != 0; p++) { switch(*p) { case '\n': r += "\\n"; break; case '\t': r += "\\t"; break; case '"': r += "\\\""; break; case '\\': r += "\\\\"; break; default: r += *p; break; } } return r; } void Writer::print_impl(const char *fmt, vector> &fns) { size_t next_index = 0; for(const char *p = fmt; *p != 0; p++) switch(*p) { case '{': if(*++p == '{') { *os << '{'; } else { char *pe; size_t index = strtoul(p, &pe, 10); if(*pe != '}') log_error("invalid format string: expected {}, {} or {{, got \"%s\": \"%s\"\n", quote_fmt(std::string(p - 1, pe - p + 2).c_str()).c_str(), quote_fmt(fmt).c_str()); if(p == pe) index = next_index; else p = pe; if(index >= fns.size()) log_error("invalid format string: index %zu out of bounds (%zu): \"%s\"\n", index, fns.size(), quote_fmt(fmt).c_str()); fns[index](); next_index = index + 1; } break; case '}': p++; if(*p != '}') log_error("invalid format string: unescaped }: \"%s\"\n", quote_fmt(fmt).c_str()); *os << '}'; break; default: *os << *p; } } } YOSYS_NAMESPACE_END