/* * 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/yosys.h" #include "kernel/sigtools.h" #include "kernel/ffinit.h" USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN struct OnehotDatabase { Module *module; const SigMap &sigmap; bool verbose = false; bool initialized = false; FfInitVals initvals; dict> sig_sources_db; dict sig_onehot_cache; pool recursion_guard; OnehotDatabase(Module *module, const SigMap &sigmap) : module(module), sigmap(sigmap) { } void initialize() { log_assert(!initialized); initialized = true; initvals.set(&sigmap, module); for (auto cell : module->cells()) { vector inputs; SigSpec output; if (cell->type.in(ID($adff), ID($adffe), ID($dff), ID($dffe), ID($sdff), ID($sdffe), ID($sdffce), ID($dlatch), ID($adlatch), ID($ff))) { output = cell->getPort(ID::Q); if (cell->type.in(ID($adff), ID($adffe), ID($adlatch))) inputs.push_back(cell->getParam(ID::ARST_VALUE)); if (cell->type.in(ID($sdff), ID($sdffe), ID($sdffce))) inputs.push_back(cell->getParam(ID::SRST_VALUE)); inputs.push_back(cell->getPort(ID::D)); } if (cell->type.in(ID($mux), ID($pmux))) { output = cell->getPort(ID::Y); inputs.push_back(cell->getPort(ID::A)); SigSpec B = cell->getPort(ID::B); for (int i = 0; i < GetSize(B); i += GetSize(output)) inputs.push_back(B.extract(i, GetSize(output))); } if (!output.empty()) { output = sigmap(output); auto &srcs = sig_sources_db[output]; for (auto src : inputs) { while (!src.empty() && src[GetSize(src)-1] == State::S0) src.remove(GetSize(src)-1); srcs.insert(sigmap(src)); } } } } void query_worker(const SigSpec &sig, bool &retval, bool &cache, int indent) { if (verbose) log("%*s %s\n", indent, "", log_signal(sig)); log_assert(retval); if (recursion_guard.count(sig)) { if (verbose) log("%*s - recursion\n", indent, ""); cache = false; return; } auto it = sig_onehot_cache.find(sig); if (it != sig_onehot_cache.end()) { if (verbose) log("%*s - cached (%s)\n", indent, "", it->second ? "true" : "false"); if (!it->second) retval = false; return; } bool found_init_ones = false; for (auto bit : sig) { if (initvals(bit) == State::S1) { if (found_init_ones) { if (verbose) log("%*s - non-onehot init value\n", indent, ""); retval = false; break; } found_init_ones = true; } } if (retval) { if (sig.is_fully_const()) { bool found_ones = false; for (auto bit : sig) { if (bit == State::S1) { if (found_ones) { if (verbose) log("%*s - non-onehot constant\n", indent, ""); retval = false; break; } found_ones = true; } } } else { auto srcs = sig_sources_db.find(sig); if (srcs == sig_sources_db.end()) { if (verbose) log("%*s - no sources for non-const signal\n", indent, ""); retval = false; } else { for (auto &src : srcs->second) { bool child_cache = true; recursion_guard.insert(sig); query_worker(src, retval, child_cache, indent+4); recursion_guard.erase(sig); if (!child_cache) cache = false; if (!retval) break; } } } } // it is always safe to cache a negative result if (cache || !retval) sig_onehot_cache[sig] = retval; } bool query(const SigSpec &sig) { bool retval = true; bool cache = true; if (verbose) log("** ONEHOT QUERY START (%s)\n", log_signal(sig)); if (!initialized) initialize(); query_worker(sig, retval, cache, 3); if (verbose) log("** ONEHOT QUERY RESULT = %s\n", retval ? "true" : "false"); // it is always safe to cache the root result of a query if (!cache) sig_onehot_cache[sig] = retval; return retval; } }; struct Pmux2ShiftxPass : public Pass { Pmux2ShiftxPass() : Pass("pmux2shiftx", "transform $pmux cells to $shiftx cells") { } void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); log(" pmux2shiftx [options] [selection]\n"); log("\n"); log("This pass transforms $pmux cells to $shiftx cells.\n"); log("\n"); log(" -v, -vv\n"); log(" verbose output\n"); log("\n"); log(" -min_density \n"); log(" specifies the minimum density for the shifter\n"); log(" default: 50\n"); log("\n"); log(" -min_choices \n"); log(" specified the minimum number of choices for a control signal\n"); log(" default: 3\n"); log("\n"); log(" -onehot ignore|pmux|shiftx\n"); log(" select strategy for one-hot encoded control signals\n"); log(" default: pmux\n"); log("\n"); log(" -norange\n"); log(" disable $sub inference for \"range decoders\"\n"); log("\n"); } void execute(std::vector args, RTLIL::Design *design) override { int min_density = 50; int min_choices = 3; bool allow_onehot = false; bool optimize_onehot = true; bool verbose = false; bool verbose_onehot = false; bool norange = false; log_header(design, "Executing PMUX2SHIFTX pass.\n"); size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { if (args[argidx] == "-min_density" && argidx+1 < args.size()) { min_density = atoi(args[++argidx].c_str()); continue; } if (args[argidx] == "-min_choices" && argidx+1 < args.size()) { min_choices = atoi(args[++argidx].c_str()); continue; } if (args[argidx] == "-onehot" && argidx+1 < args.size() && args[argidx+1] == "ignore") { argidx++; allow_onehot = false; optimize_onehot = false; continue; } if (args[argidx] == "-onehot" && argidx+1 < args.size() && args[argidx+1] == "pmux") { argidx++; allow_onehot = false; optimize_onehot = true; continue; } if (args[argidx] == "-onehot" && argidx+1 < args.size() && args[argidx+1] == "shiftx") { argidx++; allow_onehot = true; optimize_onehot = false; continue; } if (args[argidx] == "-v") { verbose = true; continue; } if (args[argidx] == "-vv") { verbose = true; verbose_onehot = true; continue; } if (args[argidx] == "-norange") { norange = true; continue; } break; } extra_args(args, argidx, design); for (auto module : design->selected_modules()) { SigMap sigmap(module); OnehotDatabase onehot_db(module, sigmap); onehot_db.verbose = verbose_onehot; dict> eqdb; for (auto cell : module->cells()) { if (cell->type == ID($eq)) { dict bits; SigSpec A = sigmap(cell->getPort(ID::A)); SigSpec B = sigmap(cell->getPort(ID::B)); int a_width = cell->getParam(ID::A_WIDTH).as_int(); int b_width = cell->getParam(ID::B_WIDTH).as_int(); if (a_width < b_width) { bool a_signed = cell->getParam(ID::A_SIGNED).as_int(); A.extend_u0(b_width, a_signed); } if (b_width < a_width) { bool b_signed = cell->getParam(ID::B_SIGNED).as_int(); B.extend_u0(a_width, b_signed); } for (int i = 0; i < GetSize(A); i++) { SigBit a_bit = A[i], b_bit = B[i]; if (b_bit.wire && !a_bit.wire) { std::swap(a_bit, b_bit); } if (!a_bit.wire || b_bit.wire) goto next_cell; if (bits.count(a_bit)) goto next_cell; bits[a_bit] = b_bit.data; } if (GetSize(bits) > 20) goto next_cell; bits.sort(); pair entry; for (auto it : bits) { entry.first.append(it.first); entry.second.bits.push_back(it.second); } eqdb[sigmap(cell->getPort(ID::Y)[0])] = entry; goto next_cell; } if (cell->type == ID($logic_not)) { dict bits; SigSpec A = sigmap(cell->getPort(ID::A)); for (int i = 0; i < GetSize(A); i++) bits[A[i]] = State::S0; bits.sort(); pair entry; for (auto it : bits) { entry.first.append(it.first); entry.second.bits.push_back(it.second); } eqdb[sigmap(cell->getPort(ID::Y)[0])] = entry; goto next_cell; } next_cell:; } for (auto cell : module->selected_cells()) { if (cell->type != ID($pmux)) continue; string src = cell->get_src_attribute(); int width = cell->getParam(ID::WIDTH).as_int(); int width_bits = ceil_log2(width); int extwidth = width; while (extwidth & (extwidth-1)) extwidth++; dict> seldb; SigSpec A = cell->getPort(ID::A); SigSpec B = cell->getPort(ID::B); SigSpec S = sigmap(cell->getPort(ID::S)); for (int i = 0; i < GetSize(S); i++) { if (!eqdb.count(S[i])) continue; auto &entry = eqdb.at(S[i]); seldb[entry.first].insert(i); } if (seldb.empty()) continue; bool printed_pmux_header = false; if (verbose) { printed_pmux_header = true; log("Inspecting $pmux cell %s/%s.\n", log_id(module), log_id(cell)); log(" data width: %d (next power-of-2 = %d, log2 = %d)\n", width, extwidth, width_bits); } SigSpec updated_S = cell->getPort(ID::S); SigSpec updated_B = cell->getPort(ID::B); while (!seldb.empty()) { // pick the largest entry in seldb SigSpec sig = seldb.begin()->first; for (auto &it : seldb) { if (GetSize(sig) < GetSize(it.first)) sig = it.first; else if (GetSize(seldb.at(sig)) < GetSize(it.second)) sig = it.first; } // find the relevant choices bool is_onehot = GetSize(sig) > 2; dict choices; for (int i : seldb.at(sig)) { Const val = eqdb.at(S[i]).second; int onebits = 0; for (auto b : val.bits) if (b == State::S1) onebits++; if (onebits > 1) is_onehot = false; choices[val] = i; } bool full_pmux = GetSize(choices) == GetSize(S); // TBD: also find choices that are using signals that are subsets of the bits in "sig" if (!verbose) { if (is_onehot && !allow_onehot && !optimize_onehot) { seldb.erase(sig); continue; } if (GetSize(choices) < min_choices) { seldb.erase(sig); continue; } } if (!printed_pmux_header) { printed_pmux_header = true; log("Inspecting $pmux cell %s/%s.\n", log_id(module), log_id(cell)); log(" data width: %d (next power-of-2 = %d, log2 = %d)\n", width, extwidth, width_bits); } log(" checking ctrl signal %s\n", log_signal(sig)); auto print_choices = [&]() { log(" table of choices:\n"); for (auto &it : choices) log(" %3d: %s: %s\n", it.second, log_signal(it.first), log_signal(B.extract(it.second*width, width))); }; if (verbose) { if (is_onehot && !allow_onehot && !optimize_onehot) { print_choices(); log(" ignoring one-hot encoding.\n"); seldb.erase(sig); continue; } if (GetSize(choices) < min_choices) { print_choices(); log(" insufficient choices.\n"); seldb.erase(sig); continue; } } if (is_onehot && optimize_onehot) { print_choices(); if (!onehot_db.query(sig)) { log(" failed to detect onehot driver. do not optimize.\n"); } else { log(" optimizing one-hot encoding.\n"); for (auto &it : choices) { const Const &val = it.first; int index = -1; for (int i = 0; i < GetSize(val); i++) if (val[i] == State::S1) { log_assert(index < 0); index = i; } if (index < 0) { log(" %3d: zero encoding.\n", it.second); continue; } SigBit new_ctrl = sig[index]; log(" %3d: new crtl signal is %s.\n", it.second, log_signal(new_ctrl)); updated_S[it.second] = new_ctrl; } } seldb.erase(sig); continue; } // find the best permutation vector perm_new_from_old(GetSize(sig)); Const perm_xormask(State::S0, GetSize(sig)); { vector values(GetSize(choices)); vector used_src_columns(GetSize(sig)); vector> columns(GetSize(sig), vector(GetSize(values))); for (int i = 0; i < GetSize(choices); i++) { Const val = choices.element(i)->first; for (int k = 0; k < GetSize(val); k++) if (val[k] == State::S1) columns[k][i] = true; } for (int dst_col = GetSize(sig)-1; dst_col >= 0; dst_col--) { int best_src_col = -1; bool best_inv = false; int best_maxval = 0; int best_delta = 0; // find best src column for this dst column for (int src_col = 0; src_col < GetSize(sig); src_col++) { if (used_src_columns[src_col]) continue; int this_maxval = 0; int this_minval = 1 << 30; int this_inv_maxval = 0; int this_inv_minval = 1 << 30; for (int i = 0; i < GetSize(values); i++) { int val = values[i]; int inv_val = val; if (columns[src_col][i]) val |= 1 << dst_col; else inv_val |= 1 << dst_col; this_maxval = std::max(this_maxval, val); this_minval = std::min(this_minval, val); this_inv_maxval = std::max(this_inv_maxval, inv_val); this_inv_minval = std::min(this_inv_minval, inv_val); } int this_delta = this_maxval - this_minval; int this_inv_delta = this_maxval - this_minval; bool this_inv = false; if (!norange && this_delta != this_inv_delta) this_inv = this_inv_delta < this_delta; else if (this_maxval != this_inv_maxval) this_inv = this_inv_maxval < this_maxval; if (this_inv) { this_delta = this_inv_delta; this_maxval = this_inv_maxval; this_minval = this_inv_minval; } bool this_is_better = false; if (best_src_col < 0) this_is_better = true; else if (!norange && this_delta != best_delta) this_is_better = this_delta < best_delta; else if (this_maxval != best_maxval) this_is_better = this_maxval < best_maxval; else this_is_better = sig[best_src_col] < sig[src_col]; if (this_is_better) { best_src_col = src_col; best_inv = this_inv; best_maxval = this_maxval; best_delta = this_delta; } } used_src_columns[best_src_col] = true; perm_new_from_old[dst_col] = best_src_col; perm_xormask[dst_col] = best_inv ? State::S1 : State::S0; } } // permutated sig SigSpec perm_sig(State::S0, GetSize(sig)); for (int i = 0; i < GetSize(sig); i++) perm_sig[i] = sig[perm_new_from_old[i]]; log(" best permutation: %s\n", log_signal(perm_sig)); log(" best xor mask: %s\n", log_signal(perm_xormask)); // permutated choices int min_choice = 1 << 30; int max_choice = -1; dict perm_choices; for (auto &it : choices) { Const &old_c = it.first; Const new_c(State::S0, GetSize(old_c)); for (int i = 0; i < GetSize(old_c); i++) new_c[i] = old_c[perm_new_from_old[i]]; Const new_c_before_xor = new_c; new_c = const_xor(new_c, perm_xormask, false, false, GetSize(new_c)); perm_choices[new_c] = it.second; min_choice = std::min(min_choice, new_c.as_int()); max_choice = std::max(max_choice, new_c.as_int()); log(" %3d: %s -> %s -> %s: %s\n", it.second, log_signal(old_c), log_signal(new_c_before_xor), log_signal(new_c), log_signal(B.extract(it.second*width, width))); } int range_density = 100*GetSize(choices) / (max_choice-min_choice+1); int absolute_density = 100*GetSize(choices) / (max_choice+1); log(" choices: %d\n", GetSize(choices)); log(" min choice: %d\n", min_choice); log(" max choice: %d\n", max_choice); log(" range density: %d%%\n", range_density); log(" absolute density: %d%%\n", absolute_density); if (full_pmux) { int full_density = 100*GetSize(choices) / (1 << GetSize(sig)); log(" full density: %d%%\n", full_density); if (full_density < min_density) { full_pmux = false; } else { min_choice = 0; max_choice = (1 << GetSize(sig))-1; log(" update to full case.\n"); log(" new min choice: %d\n", min_choice); log(" new max choice: %d\n", max_choice); } } bool full_case = (min_choice == 0) && (max_choice == (1 << GetSize(sig))-1) && (full_pmux || max_choice+1 == GetSize(choices)); log(" full case: %s\n", full_case ? "true" : "false"); // check density percentages Const offset(State::S0, GetSize(sig)); if (!norange && absolute_density < min_density && range_density >= min_density) { offset = Const(min_choice, GetSize(sig)); log(" offset: %s\n", log_signal(offset)); min_choice -= offset.as_int(); max_choice -= offset.as_int(); dict new_perm_choices; for (auto &it : perm_choices) new_perm_choices[const_sub(it.first, offset, false, false, GetSize(sig))] = it.second; perm_choices.swap(new_perm_choices); } else if (absolute_density < min_density) { log(" insufficient density.\n"); seldb.erase(sig); continue; } // creat cmp signal SigSpec cmp = perm_sig; if (perm_xormask.as_bool()) cmp = module->Xor(NEW_ID, cmp, perm_xormask, false, src); if (offset.as_bool()) cmp = module->Sub(NEW_ID, cmp, offset, false, src); // create enable signal SigBit en = State::S1; if (!full_case) { Const enable_mask(State::S0, max_choice+1); for (auto &it : perm_choices) enable_mask[it.first.as_int()] = State::S1; en = module->addWire(NEW_ID); module->addShift(NEW_ID, enable_mask, cmp, en, false, src); } // create data signal SigSpec data(State::Sx, (max_choice+1)*extwidth); if (full_pmux) { for (int i = 0; i <= max_choice; i++) data.replace(i*extwidth, A); } for (auto &it : perm_choices) { int position = it.first.as_int()*extwidth; int data_index = it.second; data.replace(position, B.extract(data_index*width, width)); updated_S[data_index] = State::S0; updated_B.replace(data_index*width, SigSpec(State::Sx, width)); } // create shiftx cell SigSpec shifted_cmp = {cmp, SigSpec(State::S0, width_bits)}; SigSpec outsig = module->addWire(NEW_ID, width); Cell *c = module->addShiftx(NEW_ID, data, shifted_cmp, outsig, false, src); updated_S.append(en); updated_B.append(outsig); log(" created $shiftx cell %s.\n", log_id(c)); // remove this sig and continue with the next block seldb.erase(sig); } // update $pmux cell cell->setPort(ID::S, updated_S); cell->setPort(ID::B, updated_B); cell->setParam(ID::S_WIDTH, GetSize(updated_S)); } } } } Pmux2ShiftxPass; struct OnehotPass : public Pass { OnehotPass() : Pass("onehot", "optimize $eq cells for onehot signals") { } void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); log(" onehot [options] [selection]\n"); log("\n"); log("This pass optimizes $eq cells that compare one-hot signals against constants\n"); log("\n"); log(" -v, -vv\n"); log(" verbose output\n"); log("\n"); } void execute(std::vector args, RTLIL::Design *design) override { bool verbose = false; bool verbose_onehot = false; log_header(design, "Executing ONEHOT pass.\n"); size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { if (args[argidx] == "-v") { verbose = true; continue; } if (args[argidx] == "-vv") { verbose = true; verbose_onehot = true; continue; } break; } extra_args(args, argidx, design); for (auto module : design->selected_modules()) { SigMap sigmap(module); OnehotDatabase onehot_db(module, sigmap); onehot_db.verbose = verbose_onehot; for (auto cell : module->selected_cells()) { if (cell->type != ID($eq)) continue; SigSpec A = sigmap(cell->getPort(ID::A)); SigSpec B = sigmap(cell->getPort(ID::B)); int a_width = cell->getParam(ID::A_WIDTH).as_int(); int b_width = cell->getParam(ID::B_WIDTH).as_int(); if (a_width < b_width) { bool a_signed = cell->getParam(ID::A_SIGNED).as_int(); A.extend_u0(b_width, a_signed); } if (b_width < a_width) { bool b_signed = cell->getParam(ID::B_SIGNED).as_int(); B.extend_u0(a_width, b_signed); } if (A.is_fully_const()) std::swap(A, B); if (!B.is_fully_const()) continue; if (verbose) log("Checking $eq(%s, %s) cell %s/%s.\n", log_signal(A), log_signal(B), log_id(module), log_id(cell)); if (!onehot_db.query(A)) { if (verbose) log(" onehot driver test on %s failed.\n", log_signal(A)); continue; } int index = -1; bool not_onehot = false; for (int i = 0; i < GetSize(B); i++) { if (B[i] != State::S1) continue; if (index >= 0) not_onehot = true; index = i; } if (index < 0) { if (verbose) log(" not optimizing the zero pattern.\n"); continue; } SigSpec Y = cell->getPort(ID::Y); if (not_onehot) { if (verbose) log(" replacing with constant 0 driver.\n"); else log("Replacing one-hot $eq(%s, %s) cell %s/%s with constant 0 driver.\n", log_signal(A), log_signal(B), log_id(module), log_id(cell)); module->connect(Y, SigSpec(1, GetSize(Y))); } else { SigSpec sig = A[index]; if (verbose) log(" replacing with signal %s.\n", log_signal(sig)); else log("Replacing one-hot $eq(%s, %s) cell %s/%s with signal %s.\n",log_signal(A), log_signal(B), log_id(module), log_id(cell), log_signal(sig)); sig.extend_u0(GetSize(Y)); module->connect(Y, sig); } module->remove(cell); } } } } OnehotPass; PRIVATE_NAMESPACE_END 65'>565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756