| /* | |
| * yosys -- Yosys Open SYnthesis Suite | |
| * | |
| * Copyright (C) 2017 Robert Ou <rqou@robertou.com> | |
| * | |
| * 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 <deque> | |
| USING_YOSYS_NAMESPACE | |
| PRIVATE_NAMESPACE_BEGIN | |
| struct ExtractReducePass : public Pass | |
| { | |
| enum GateType { | |
| And, | |
| Or, | |
| Xor | |
| }; | |
| ExtractReducePass() : Pass("extract_reduce", "converts gate chains into $reduce_* cells") { } | |
| void help() YS_OVERRIDE | |
| { | |
| // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| | |
| log("\n"); | |
| log(" extract_reduce [options] [selection]\n"); | |
| log("\n"); | |
| log("converts gate chains into $reduce_* cells\n"); | |
| log("\n"); | |
| log("This command finds chains of $_AND_, $_OR_, and $_XOR_ cells and replaces them\n"); | |
| log("with their corresponding $reduce_* cells. Because this command only operates on\n"); | |
| log("these cell types, it is recommended to map the design to only these cell types\n"); | |
| log("using the `abc -g` command. Note that, in some cases, it may be more effective\n"); | |
| log("to map the design to only $_AND_ cells, run extract_reduce, map the remaining\n"); | |
| log("parts of the design to AND/OR/XOR cells, and run extract_reduce a second time.\n"); | |
| log("\n"); | |
| log(" -allow-off-chain\n"); | |
| log(" Allows matching of cells that have loads outside the chain. These cells\n"); | |
| log(" will be replicated and folded into the $reduce_* cell, but the original\n"); | |
| log(" cell will remain, driving its original loads.\n"); | |
| log("\n"); | |
| } | |
| inline bool IsRightType(Cell* cell, GateType gt) | |
| { | |
| return (cell->type == ID($_AND_) && gt == GateType::And) || | |
| (cell->type == ID($_OR_) && gt == GateType::Or) || | |
| (cell->type == ID($_XOR_) && gt == GateType::Xor); | |
| } | |
| void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE | |
| { | |
| log_header(design, "Executing EXTRACT_REDUCE pass.\n"); | |
| log_push(); | |
| size_t argidx; | |
| bool allow_off_chain = false; | |
| for (argidx = 1; argidx < args.size(); argidx++) | |
| { | |
| if (args[argidx] == "-allow-off-chain") | |
| { | |
| allow_off_chain = true; | |
| continue; | |
| } | |
| break; | |
| } | |
| extra_args(args, argidx, design); | |
| for (auto module : design->selected_modules()) | |
| { | |
| SigMap sigmap(module); | |
| // Index all of the nets in the module | |
| dict<SigBit, Cell*> sig_to_driver; | |
| dict<SigBit, pool<Cell*>> sig_to_sink; | |
| for (auto cell : module->selected_cells()) | |
| { | |
| for (auto &conn : cell->connections()) | |
| { | |
| if (cell->output(conn.first)) | |
| for (auto bit : sigmap(conn.second)) | |
| sig_to_driver[bit] = cell; | |
| if (cell->input(conn.first)) | |
| { | |
| for (auto bit : sigmap(conn.second)) | |
| { | |
| if (sig_to_sink.count(bit) == 0) | |
| sig_to_sink[bit] = pool<Cell*>(); | |
| sig_to_sink[bit].insert(cell); | |
| } | |
| } | |
| } | |
| } | |
| // Need to check if any wires connect to module ports | |
| pool<SigBit> port_sigs; | |
| for (auto wire : module->selected_wires()) | |
| if (wire->port_input || wire->port_output) | |
| for (auto bit : sigmap(wire)) | |
| port_sigs.insert(bit); | |
| // Actual logic starts here | |
| pool<Cell*> consumed_cells; | |
| for (auto cell : module->selected_cells()) | |
| { | |
| if (consumed_cells.count(cell)) | |
| continue; | |
| GateType gt; | |
| if (cell->type == ID($_AND_)) | |
| gt = GateType::And; | |
| else if (cell->type == ID($_OR_)) | |
| gt = GateType::Or; | |
| else if (cell->type == ID($_XOR_)) | |
| gt = GateType::Xor; | |
| else | |
| continue; | |
| log("Working on cell %s...\n", cell->name.c_str()); | |
| // If looking for a single chain, follow linearly to the sink | |
| pool<Cell*> sinks; | |
| if(!allow_off_chain) | |
| { | |
| Cell* head_cell = cell; | |
| Cell* x = cell; | |
| while (true) | |
| { | |
| if(!IsRightType(x, gt)) | |
| break; | |
| head_cell = x; | |
| auto y = sigmap(x->getPort(ID::Y)); | |
| log_assert(y.size() == 1); | |
| // Should only continue if there is one fanout back into a cell (not to a port) | |
| if (sig_to_sink[y[0]].size() != 1) | |
| break; | |
| x = *sig_to_sink[y[0]].begin(); | |
| } | |
| sinks.insert(head_cell); | |
| } | |
| //If off-chain loads are allowed, we have to do a wider traversal to see what the longest chain is | |
| else | |
| { | |
| //BFS, following all chains until they hit a cell of a different type | |
| //Pick the longest one | |
| auto y = sigmap(cell->getPort(ID::Y)); | |
| pool<Cell*> current_loads = sig_to_sink[y]; | |
| pool<Cell*> next_loads; | |
| while(!current_loads.empty()) | |
| { | |
| //Find each sink and see what they are | |
| for(auto x : current_loads) | |
| { | |
| //Not one of our gates? Don't follow any further | |
| //(but add the originating cell to the list of sinks) | |
| if(!IsRightType(x, gt)) | |
| { | |
| sinks.insert(cell); | |
| continue; | |
| } | |
| //If this signal drives a port, add it to the sinks | |
| //(even though it may not be the end of a chain) | |
| if(port_sigs.count(x) && !consumed_cells.count(x)) | |
| sinks.insert(x); | |
| //It's a match, search everything out from it | |
| auto& next = sig_to_sink[x]; | |
| for(auto z : next) | |
| next_loads.insert(z); | |
| } | |
| //If we couldn't find any downstream loads, stop. | |
| //Create a reduction for each of the max-length chains we found | |
| if(next_loads.empty()) | |
| { | |
| for(auto s : current_loads) | |
| { | |
| //Not one of our gates? Don't follow any further | |
| if(!IsRightType(s, gt)) | |
| continue; | |
| sinks.insert(s); | |
| } | |
| break; | |
| } | |
| //Otherwise, continue down the chain | |
| current_loads = next_loads; | |
| next_loads.clear(); | |
| } | |
| } | |
| //We have our list, go act on it | |
| for(auto head_cell : sinks) | |
| { | |
| log(" Head cell is %s\n", head_cell->name.c_str()); | |
| //Avoid duplication if we already were covered | |
| if(consumed_cells.count(head_cell)) | |
| continue; | |
| pool<Cell*> cur_supercell; | |
| std::deque<Cell*> bfs_queue = {head_cell}; | |
| while (bfs_queue.size()) | |
| { | |
| Cell* x = bfs_queue.front(); | |
| bfs_queue.pop_front(); | |
| cur_supercell.insert(x); | |
| auto a = sigmap(x->getPort(ID::A)); | |
| log_assert(a.size() == 1); | |
| // Must have only one sink unless we're going off chain | |
| // XXX: Check that it is indeed this node? | |
| if( allow_off_chain || (sig_to_sink[a[0]].size() + port_sigs.count(a[0]) == 1) ) | |
| { | |
| Cell* cell_a = sig_to_driver[a[0]]; | |
| if(cell_a && IsRightType(cell_a, gt)) | |
| { | |
| // The cell here is the correct type, and it's definitely driving | |
| // this current cell. | |
| bfs_queue.push_back(cell_a); | |
| } | |
| } | |
| auto b = sigmap(x->getPort(ID::B)); | |
| log_assert(b.size() == 1); | |
| // Must have only one sink | |
| // XXX: Check that it is indeed this node? | |
| if( allow_off_chain || (sig_to_sink[b[0]].size() + port_sigs.count(b[0]) == 1) ) | |
| { | |
| Cell* cell_b = sig_to_driver[b[0]]; | |
| if(cell_b && IsRightType(cell_b, gt)) | |
| { | |
| // The cell here is the correct type, and it's definitely driving only | |
| // this current cell. | |
| bfs_queue.push_back(cell_b); | |
| } | |
| } | |
| } | |
| log(" Cells:\n"); | |
| for (auto x : cur_supercell) | |
| log(" %s\n", x->name.c_str()); | |
| if (cur_supercell.size() > 1) | |
| { | |
| // Worth it to create reduce cell | |
| log(" Creating $reduce_* cell!\n"); | |
| pool<SigBit> input_pool; | |
| pool<SigBit> input_pool_intermed; | |
| for (auto x : cur_supercell) | |
| { | |
| input_pool.insert(sigmap(x->getPort(ID::A))[0]); | |
| input_pool.insert(sigmap(x->getPort(ID::B))[0]); | |
| input_pool_intermed.insert(sigmap(x->getPort(ID::Y))[0]); | |
| } | |
| SigSpec input; | |
| for (auto b : input_pool) | |
| if (input_pool_intermed.count(b) == 0) | |
| input.append_bit(b); | |
| SigBit output = sigmap(head_cell->getPort(ID::Y)[0]); | |
| auto new_reduce_cell = module->addCell(NEW_ID, | |
| gt == GateType::And ? ID($reduce_and) : | |
| gt == GateType::Or ? ID($reduce_or) : | |
| gt == GateType::Xor ? ID($reduce_xor) : ""); | |
| new_reduce_cell->setParam(ID(A_SIGNED), 0); | |
| new_reduce_cell->setParam(ID(A_WIDTH), input.size()); | |
| new_reduce_cell->setParam(ID(Y_WIDTH), 1); | |
| new_reduce_cell->setPort(ID::A, input); | |
| new_reduce_cell->setPort(ID::Y, output); | |
| if(allow_off_chain) | |
| consumed_cells.insert(head_cell); | |
| else | |
| { | |
| for (auto x : cur_supercell) | |
| consumed_cells.insert(x); | |
| } | |
| } | |
| } | |
| } | |
| // Remove all of the head cells, since we supplant them. | |
| // Do not remove the upstream cells since some might still be in use ("clean" will get rid of unused ones) | |
| for (auto cell : consumed_cells) | |
| module->remove(cell); | |
| } | |
| log_pop(); | |
| } | |
| } ExtractReducePass; | |
| PRIVATE_NAMESPACE_END |