/* * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2012 Clifford Wolf * * 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/register.h" #include "kernel/sigtools.h" #include "kernel/consteval.h" #include "kernel/log.h" #include #include #include USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN struct proc_dlatch_db_t { Module *module; SigMap sigmap; pool generated_dlatches; dict> mux_srcbits; dict> mux_drivers; dict sigusers; dict> initbits; proc_dlatch_db_t(Module *module) : module(module), sigmap(module) { for (auto cell : module->cells()) { if (cell->type.in(ID($mux), ID($pmux))) { auto sig_y = sigmap(cell->getPort(ID::Y)); for (int i = 0; i < GetSize(sig_y); i++) mux_drivers[sig_y[i]] = pair(cell, i); pool mux_srcbits_pool; for (auto bit : sigmap(cell->getPort(ID::A))) mux_srcbits_pool.insert(bit); for (auto bit : sigmap(cell->getPort(ID::B))) mux_srcbits_pool.insert(bit); vector mux_srcbits_vec; for (auto bit : mux_srcbits_pool) if (bit.wire != nullptr) mux_srcbits_vec.push_back(bit); mux_srcbits[cell].swap(mux_srcbits_vec); } for (auto &conn : cell->connections()) if (!cell->known() || cell->input(conn.first)) for (auto bit : sigmap(conn.second)) sigusers[bit]++; } for (auto wire : module->wires()) { if (wire->port_input) for (auto bit : sigmap(wire)) sigusers[bit]++; if (wire->attributes.count(ID::init)) { SigSpec wirebits = sigmap(wire); Const initval = wire->attributes.at(ID::init); for (int i = 0; i < GetSize(wirebits) && i < GetSize(initval); i++) { SigBit bit = wirebits[i]; State val = initval[i]; if (val != State::S0 && val != State::S1 && bit.wire != nullptr) continue; if (initbits.count(bit)) { if (initbits.at(bit).first != val) log_error("Conflicting init values for signal %s (%s = %s != %s).\n", log_signal(bit), log_signal(SigBit(wire, i)), log_signal(val), log_signal(initbits.at(bit).first)); continue; } initbits[bit] = std::make_pair(val,SigBit(wire,i)); } } } } bool quickcheck(const SigSpec &haystack, const SigSpec &needle) { pool haystack_bits = sigmap(haystack).to_sigbit_pool(); pool needle_bits = sigmap(needle).to_sigbit_pool(); pool cells_queue, cells_visited; pool bits_queue, bits_visited; bits_queue = haystack_bits; while (!bits_queue.empty()) { for (auto &bit : bits_queue) { auto it = mux_drivers.find(bit); if (it != mux_drivers.end()) if (!cells_visited.count(it->second.first)) cells_queue.insert(it->second.first); bits_visited.insert(bit); } bits_queue.clear(); for (auto c : cells_queue) { for (auto bit : mux_srcbits[c]) { if (needle_bits.count(bit)) return true; if (!bits_visited.count(bit)) bits_queue.insert(bit); } } cells_queue.clear(); } return false; } struct rule_node_t { // a node is true if "signal" equals "match" and [any // of the child nodes is true or "children" is empty] SigBit signal, match; vector children; bool operator==(const rule_node_t &other) const { return signal == other.signal && match == other.match && children == other.children; } unsigned int hash() const { unsigned int h = mkhash_init; mkhash(h, signal.hash()); mkhash(h, match.hash()); for (auto i : children) mkhash(h, i); return h; } }; enum tf_node_types_t : int { true_node = 1, false_node = 2 }; idict rules_db; dict rules_sig; int make_leaf(SigBit signal, SigBit match) { rule_node_t node; node.signal = signal; node.match = match; return rules_db(node); } int make_inner(SigBit signal, SigBit match, int child) { rule_node_t node; node.signal = signal; node.match = match; node.children.push_back(child); return rules_db(node); } int make_inner(const pool &children) { rule_node_t node; node.signal = State::S0; node.match = State::S0; node.children = vector(children.begin(), children.end()); std::sort(node.children.begin(), node.children.end()); return rules_db(node); } int find_mux_feedback(SigBit haystack, SigBit needle, bool set_undef) { if (sigusers[haystack] > 1) set_undef = false; if (haystack == needle) return true_node; auto it = mux_drivers.find(haystack); if (it == mux_drivers.end()) return false_node; Cell *cell = it->second.first; int index = it->second.second; SigSpec sig_a = sigmap(cell->getPort(ID::A)); SigSpec sig_b = sigmap(cell->getPort(ID::B)); SigSpec sig_s = sigmap(cell->getPort(ID::S)); int width = GetSize(sig_a); pool children; int n = find_mux_feedback(sig_a[index], needle, set_undef); if (n != false_node) { if (set_undef && sig_a[index] == needle) { SigSpec sig = cell->getPort(ID::A); sig[index] = State::Sx; cell->setPort(ID::A, sig); } for (int i = 0; i < GetSize(sig_s); i++) n = make_inner(sig_s[i], State::S0, n); children.insert(n); } for (int i = 0; i < GetSize(sig_s); i++) { n = find_mux_feedback(sig_b[i*width + index], needle, set_undef); if (n != false_node) { if (set_undef && sig_b[i*width + index] == needle) { SigSpec sig = cell->getPort(ID::B); sig[i*width + index] = State::Sx; cell->setPort(ID::B, sig); } children.insert(make_inner(sig_s[i], State::S1, n)); } } if (children.empty()) return false_node; return make_inner(children); } SigBit make_hold(int n, string &src) { if (n == true_node) return State::S1; if (n == false_node) return State::S0; if (rules_sig.count(n)) return rules_sig.at(n); const rule_node_t &rule = rules_db[n]; SigSpec and_bits; if (rule.signal != rule.match) { if (rule.match == State::S1) and_bits.append(rule.signal); else if (rule.match == State::S0) and_bits.append(module->Not(NEW_ID, rule.signal, false, src)); else and_bits.append(module->Eq(NEW_ID, rule.signal, rule.match, false, src)); } if (!rule.children.empty()) { SigSpec or_bits; for (int k : rule.children) or_bits.append(make_hold(k, src)); and_bits.append(module->ReduceOr(NEW_ID, or_bits, false, src)); } if (GetSize(and_bits) == 2) and_bits = module->And(NEW_ID, and_bits[0], and_bits[1], false, src); log_assert(GetSize(and_bits) == 1); rules_sig[n] = and_bits[0]; return and_bits[0]; } void fixup_mux(Cell *cell) { SigSpec sig_a = cell->getPort(ID::A); SigSpec sig_b = cell->getPort(ID::B); SigSpec sig_s = cell->getPort(ID::S); SigSpec sig_any_valid_b; SigSpec sig_new_b, sig_new_s; for (int i = 0; i < GetSize(sig_s); i++) { SigSpec b = sig_b.extract(i*GetSize(sig_a), GetSize(sig_a)); if (!b.is_fully_undef()) { sig_any_valid_b = b; sig_new_b.append(b); sig_new_s.append(sig_s[i]); } } if (sig_new_s.empty()) { sig_new_b = sig_a; sig_new_s = State::S0; } if (sig_a.is_fully_undef() && !sig_any_valid_b.empty()) cell->setPort(ID::A, sig_any_valid_b); if (GetSize(sig_new_s) == 1) { cell->type = ID($mux); cell->unsetParam(ID::S_WIDTH); } else { cell->type = ID($pmux); cell->setParam(ID::S_WIDTH, GetSize(sig_new_s)); } cell->setPort(ID::B, sig_new_b); cell->setPort(ID::S, sig_new_s); } void fixup_muxes() { pool visited, queue; dict> upstream_cell2net; dict> upstream_net2cell; CellTypes ct; ct.setup_internals(); for (auto cell : module->cells()) for (auto conn : cell->connections()) { if (cell->input(conn.first)) for (auto bit : sigmap(conn.second)) upstream_cell2net[cell].insert(bit); if (cell->output(conn.first)) for (auto bit : sigmap(conn.second)) upstream_net2cell[bit].insert(cell); } queue = generated_dlatches; while (!queue.empty()) { pool next_queue; for (auto cell : queue) { if (cell->type.in(ID($mux), ID($pmux))) fixup_mux(cell); for (auto bit : upstream_cell2net[cell]) for (auto cell : upstream_net2cell[bit]) next_queue.insert(cell); visited.insert(cell); } queue.clear(); for (auto cell : next_queue) { if (!visited.count(cell) && ct.cell_known(cell->type)) queue.insert(cell); } } } }; void proc_dlatch(proc_dlatch_db_t &db, RTLIL::Process *proc) { std::vector new_syncs; RTLIL::SigSig latches_bits, nolatches_bits; dict latches_out_in; dict latches_hold; std::string src = proc->get_src_attribute(); for (auto sr : proc->syncs) { if (sr->type != RTLIL::SyncType::STa) { new_syncs.push_back(sr); continue; } if (proc->get_bool_attribute(ID::always_ff)) log_error("Found non edge/level sensitive event in always_ff process `%s.%s'.\n", db.module->name.c_str(), proc->name.c_str()); for (auto ss : sr->actions) { db.sigmap.apply(ss.first); db.sigmap.apply(ss.second); if (!db.quickcheck(ss.second, ss.first)) { nolatches_bits.first.append(ss.first); nolatches_bits.second.append(ss.second); continue; } for (int i = 0; i < GetSize(ss.first); i++) latches_out_in[ss.first[i]] = ss.second[i]; } delete sr; } latches_out_in.sort(); for (auto &it : latches_out_in) { int n = db.find_mux_feedback(it.second, it.first, true); if (n == db.false_node) { nolatches_bits.first.append(it.first); nolatches_bits.second.append(it.second); } else { latches_bits.first.append(it.first); latches_bits.second.append(it.second); latches_hold[it.first] = n; } } int offset = 0; for (auto chunk : nolatches_bits.first.chunks()) { SigSpec lhs = chunk, rhs = nolatches_bits.second.extract(offset, chunk.width); if (proc->get_bool_attribute(ID::always_latch)) log_error("No latch inferred for signal `%s.%s' from always_latch process `%s.%s'.\n", db.module->name.c_str(), log_signal(lhs), db.module->name.c_str(), proc->name.c_str()); else log("No latch inferred for signal `%s.%s' from process `%s.%s'.\n", db.module->name.c_str(), log_signal(lhs), db.module->name.c_str(), proc->name.c_str()); for (auto &bit : lhs) { auto it = db.initbits.find(bit); if (it != db.initbits.end()) { log("Removing init bit %s for non-memory siginal `%s.%s` in process `%s.%s`.\n", log_signal(it->second.first), db.module->name.c_str(), log_signal(bit), db.module->name.c_str(), proc->name.c_str()); it->second.second.wire->attributes.at(ID::init)[it->second.second.offset] = State::Sx; } } db.module->connect(lhs, rhs); offset += chunk.width; } offset = 0; while (offset < GetSize(latches_bits.first)) { int width = 1; int n = latches_hold[latches_bits.first[offset]]; Wire *w = latches_bits.first[offset].wire; if (w != nullptr) { while (offset+width < GetSize(latches_bits.first) && n == latches_hold[latches_bits.first[offset+width]] && w == latches_bits.first[offset+width].wire) width++; SigSpec lhs = latches_bits.first.extract(offset, width); SigSpec rhs = latches_bits.second.extract(offset, width); Cell *cell = db.module->addDlatch(NEW_ID, db.module->Not(NEW_ID, db.make_hold(n, src)), rhs, lhs); cell->set_src_attribute(src); db.generated_dlatches.insert(cell); if (proc->get_bool_attribute(ID::always_comb)) log_error("Latch inferred for signal `%s.%s' from always_comb process `%s.%s'.\n", db.module->name.c_str(), log_signal(lhs), db.module->name.c_str(), proc->name.c_str()); else log("Latch inferred for signal `%s.%s' from process `%s.%s': %s\n", db.module->name.c_str(), log_signal(lhs), db.module->name.c_str(), proc->name.c_str(), log_id(cell)); } offset += width; } new_syncs.swap(proc->syncs); } struct ProcDlatchPass : public Pass { ProcDlatchPass() : Pass("proc_dlatch", "extract latches from processes") { } void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); log(" proc_dlatch [selection]\n"); log("\n"); log("This pass identifies latches in the processes and converts them to\n"); log("d-type latches.\n"); log("\n"); } void execute(std::vector args, RTLIL::Design *design) override { log_header(design, "Executing PROC_DLATCH pass (convert process syncs to latches).\n"); extra_args(args, 1, design); for (auto module : design->selected_modules()) { proc_dlatch_db_t db(module); for (auto &proc_it : module->processes) if (design->selected(module, proc_it.second)) proc_dlatch(db, proc_it.second); db.fixup_muxes(); } } } ProcDlatchPass; PRIVATE_NAMESPACE_END