diff options
Diffstat (limited to 'passes')
-rw-r--r-- | passes/opt/Makefile.inc | 3 | ||||
-rw-r--r-- | passes/opt/opt_demorgan.cc | 202 | ||||
-rw-r--r-- | passes/techmap/extract_counter.cc | 163 |
3 files changed, 320 insertions, 48 deletions
diff --git a/passes/opt/Makefile.inc b/passes/opt/Makefile.inc index a145cf946..0d01e9d35 100644 --- a/passes/opt/Makefile.inc +++ b/passes/opt/Makefile.inc @@ -6,10 +6,11 @@ OBJS += passes/opt/opt_reduce.o OBJS += passes/opt/opt_rmdff.o OBJS += passes/opt/opt_clean.o OBJS += passes/opt/opt_expr.o -OBJS += passes/opt/rmports.o ifneq ($(SMALL),1) OBJS += passes/opt/share.o OBJS += passes/opt/wreduce.o +OBJS += passes/opt/opt_demorgan.o +OBJS += passes/opt/rmports.o endif diff --git a/passes/opt/opt_demorgan.cc b/passes/opt/opt_demorgan.cc new file mode 100644 index 000000000..f2af1cb93 --- /dev/null +++ b/passes/opt/opt_demorgan.cc @@ -0,0 +1,202 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2017 Clifford Wolf <clifford@clifford.at> + * + * 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/modtools.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +void demorgan_worker( + ModIndex& index, + Cell *cell, + unsigned int& cells_changed) +{ + SigMap& sigmap = index.sigmap; + auto m = cell->module; + + //TODO: Add support for reduce_xor + //DeMorgan of XOR is either XOR (if even number of inputs) or XNOR (if odd number) + + if( (cell->type != "$reduce_and") && (cell->type != "$reduce_or") ) + return; + + auto insig = sigmap(cell->getPort("\\A")); + log("Inspecting %s cell %s (%d inputs)\n", log_id(cell->type), log_id(cell->name), GetSize(insig)); + int num_inverted = 0; + for(int i=0; i<GetSize(insig); i++) + { + auto b = insig[i]; + + //See if this bit is driven by a $not cell + //TODO: do other stuff like nor/nand? + pool<ModIndex::PortInfo> ports = index.query_ports(b); + bool inverted = false; + for(auto x : ports) + { + if(x.port == "\\Y" && x.cell->type == "$_NOT_") + { + inverted = true; + break; + } + } + + if(inverted) + num_inverted ++; + } + + //Stop if less than half of the inputs are inverted + if(num_inverted*2 < GetSize(insig)) + { + log(" %d / %d inputs are inverted, not pushing\n", num_inverted, GetSize(insig)); + return; + } + + //More than half of the inputs are inverted! Push through + cells_changed ++; + log(" %d / %d inputs are inverted, pushing inverter through reduction\n", num_inverted, GetSize(insig)); + + //For each input, either add or remove the inverter as needed + //TODO: this duplicates the loop up above, can we refactor it? + for(int i=0; i<GetSize(insig); i++) + { + auto b = insig[i]; + + //See if this bit is driven by a $not cell + //TODO: do other stuff like nor/nand? + pool<ModIndex::PortInfo> ports = index.query_ports(b); + RTLIL::Cell* srcinv = NULL; + for(auto x : ports) + { + if(x.port == "\\Y" && x.cell->type == "$_NOT_") + { + srcinv = x.cell; + break; + } + } + + //We are NOT inverted! Add an inverter + if(!srcinv) + { + auto inverted_b = m->addWire(NEW_ID); + m->addNot(NEW_ID, RTLIL::SigSpec(b), RTLIL::SigSpec(inverted_b)); + insig[i] = inverted_b; + } + + //We ARE inverted - bypass it + //Don't automatically delete the inverter since other stuff might still use it + else + insig[i] = srcinv->getPort("\\A"); + } + + //Cosmetic fixup: If our input is just a scrambled version of one bus, rearrange it + //Reductions are all commutative, so there's no point in having them in a weird order + bool same_signal = true; + RTLIL::Wire* srcwire = insig[0].wire; + dict<int, int> seen_bits; + for(int i=0; i<GetSize(insig); i++) + seen_bits[i] = 0; + for(int i=0; i<GetSize(insig); i++) + { + seen_bits[insig[i].offset] ++; + if(insig[i].wire != srcwire) + { + same_signal = false; + break; + } + } + if(same_signal) + { + //Make sure we've seen every bit exactly once + bool every_bit_once = true; + for(int i=0; i<GetSize(insig); i++) + { + if(seen_bits[i] != 1) + { + every_bit_once = false; + break; + } + } + + //All good? Just use the whole wire as-is without any reordering + //We do have to swap MSB to LSB b/c that's the way the reduction cells seem to work? + //Unclear on why this isn't sorting properly + //TODO: can we do SigChunks instead of single bits if we have subsets of a bus? + if(every_bit_once && (GetSize(insig) == srcwire->width) ) + { + log("Rearranging bits\n"); + RTLIL::SigSpec newsig; + for(int i=0; i<GetSize(insig); i++) + newsig.append(RTLIL::SigBit(srcwire, GetSize(insig) - i - 1)); + insig = newsig; + insig.sort(); + } + } + + //Push the new input signal back to the reduction (after bypassing/adding inverters) + cell->setPort("\\A", insig); + + //Change the cell type + if(cell->type == "$reduce_and") + cell->type = "$reduce_or"; + else if(cell->type == "$reduce_or") + cell->type = "$reduce_and"; + //don't change XOR + + //Add an inverter to the output + auto inverted_output = cell->getPort("\\Y"); + auto uninverted_output = m->addWire(NEW_ID); + m->addNot(NEW_ID, RTLIL::SigSpec(uninverted_output), inverted_output); + cell->setPort("\\Y", uninverted_output); +} + +struct OptDemorganPass : public Pass { + OptDemorganPass() : Pass("opt_demorgan", "Optimize reductions with DeMorgan equivalents") { } + virtual void help() + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" opt_demorgan [selection]\n"); + log("\n"); + log("This pass pushes inverters through $reduce_* cells if this will reduce the\n"); + log("overall gate count of the circuit\n"); + log("\n"); + } + virtual void execute(std::vector<std::string> args, RTLIL::Design *design) + { + log_header(design, "Executing OPT_DEMORGAN pass (push inverters through $reduce_* cells).\n"); + + int argidx = 0; + extra_args(args, argidx, design); + + unsigned int cells_changed = 0; + for (auto module : design->selected_modules()) + { + ModIndex index(module); + for (auto cell : module->selected_cells()) + demorgan_worker(index, cell, cells_changed); + } + + if(cells_changed) + log("Pushed inverters through %u reductions\n", cells_changed); + } +} OptDemorganPass; + +PRIVATE_NAMESPACE_END diff --git a/passes/techmap/extract_counter.cc b/passes/techmap/extract_counter.cc index 540b1593d..af0eb852a 100644 --- a/passes/techmap/extract_counter.cc +++ b/passes/techmap/extract_counter.cc @@ -92,9 +92,13 @@ struct CounterExtraction int width; //counter width RTLIL::Wire* rwire; //the register output bool has_reset; //true if we have a reset + bool has_ce; //true if we have a clock enable RTLIL::SigSpec rst; //reset pin + bool rst_inverted; //true if reset is active low + bool rst_to_max; //true if we reset to max instead of 0 int count_value; //value we count from - RTLIL::SigSpec clk; //clock signal + RTLIL::SigSpec ce; //clock signal + RTLIL::SigSpec clk; //clock enable, if any RTLIL::SigSpec outsig; //counter output signal RTLIL::Cell* count_mux; //counter mux RTLIL::Cell* count_reg; //counter register @@ -190,12 +194,43 @@ int counter_tryextract( return 13; extract.underflow_inv = underflow_inv; - //Y connection of the mux must have exactly one load, the counter's internal register + //Y connection of the mux must have exactly one load, the counter's internal register, if there's no clock enable + //If we have a clock enable, Y drives the B input of a mux. A of that mux must come from our register const RTLIL::SigSpec muxy = sigmap(count_mux->getPort("\\Y")); pool<Cell*> muxy_loads = get_other_cells(muxy, index, count_mux); if(muxy_loads.size() != 1) return 14; - Cell* count_reg = *muxy_loads.begin(); + Cell* muxload = *muxy_loads.begin(); + Cell* count_reg = muxload; + Cell* cemux = NULL; + RTLIL::SigSpec cey; + if(muxload->type == "$mux") + { + //This mux is probably a clock enable mux. + //Find our count register (should be our only load) + cemux = muxload; + cey = sigmap(cemux->getPort("\\Y")); + pool<Cell*> cey_loads = get_other_cells(cey, index, cemux); + if(cey_loads.size() != 1) + return 24; + count_reg = *cey_loads.begin(); + + //Mux should have A driven by count Q, and B by muxy + //TODO: if A and B are swapped, CE polarity is inverted + if(sigmap(cemux->getPort("\\B")) != muxy) + return 24; + if(sigmap(cemux->getPort("\\A")) != sigmap(count_reg->getPort("\\Q"))) + return 24; + if(sigmap(cemux->getPort("\\Y")) != sigmap(count_reg->getPort("\\D"))) + return 24; + + //Select of the mux is our clock enable + extract.has_ce = true; + extract.ce = sigmap(cemux->getPort("\\S")); + } + else + extract.has_ce = false; + extract.count_reg = count_reg; if(count_reg->type == "$dff") extract.has_reset = false; @@ -203,11 +238,16 @@ int counter_tryextract( { extract.has_reset = true; - //Verify ARST_VALUE is zero and ARST_POLARITY is 1 - //TODO: infer an inverter to make it 1 if necessary, so we can support negative level resets? - if(count_reg->getParam("\\ARST_POLARITY").as_int() != 1) - return 22; - if(count_reg->getParam("\\ARST_VALUE").as_int() != 0) + //Check polarity of reset - we may have to add an inverter later on! + extract.rst_inverted = (count_reg->getParam("\\ARST_POLARITY").as_int() != 1); + + //Verify ARST_VALUE is zero or full scale + int rst_value = count_reg->getParam("\\ARST_VALUE").as_int(); + if(rst_value == 0) + extract.rst_to_max = false; + else if(rst_value == extract.count_value) + extract.rst_to_max = true; + else return 23; //Save the reset @@ -216,54 +256,59 @@ int counter_tryextract( //TODO: support synchronous reset else return 15; - if(!is_full_bus(muxy, index, count_mux, "\\Y", count_reg, "\\D")) + + //Sanity check that we use the ALU output properly + if(extract.has_ce) + { + if(!is_full_bus(muxy, index, count_mux, "\\Y", cemux, "\\B")) + return 16; + if(!is_full_bus(cey, index, cemux, "\\Y", count_reg, "\\D")) + return 16; + } + else if(!is_full_bus(muxy, index, count_mux, "\\Y", count_reg, "\\D")) return 16; //TODO: Verify count_reg CLK_POLARITY is 1 //Register output must have exactly two loads, the inverter and ALU //(unless we have a parallel output!) + //If we have a clock enable, 3 is OK const RTLIL::SigSpec qport = count_reg->getPort("\\Q"); const RTLIL::SigSpec cnout = sigmap(qport); pool<Cell*> cnout_loads = get_other_cells(cnout, index, count_reg); - if(cnout_loads.size() > 2) + unsigned int max_loads = 2; + if(extract.has_ce) + max_loads = 3; + if(cnout_loads.size() > max_loads) { - //If we specified a limited set of cells for parallel output, check that we only drive them - if(!parallel_cells.empty()) + for(auto c : cnout_loads) { - for(auto c : cnout_loads) - { - if(c == underflow_inv) - continue; - if(c == cell) - continue; + if(c == underflow_inv) + continue; + if(c == cell) + continue; + if(c == muxload) + continue; + //If we specified a limited set of cells for parallel output, check that we only drive them + if(!parallel_cells.empty()) + { //Make sure we're in the whitelist if( parallel_cells.find(c->type) == parallel_cells.end()) return 17; + } - //Figure out what port(s) are driven by it - //TODO: this can probably be done more efficiently w/o multiple iterations over our whole net? - RTLIL::IdString portname; - for(auto b : qport) + //Figure out what port(s) are driven by it + //TODO: this can probably be done more efficiently w/o multiple iterations over our whole net? + for(auto b : qport) + { + pool<ModIndex::PortInfo> ports = index.query_ports(b); + for(auto x : ports) { - pool<ModIndex::PortInfo> ports = index.query_ports(b); - for(auto x : ports) - { - if(x.cell != c) - continue; - if(portname == "") - portname = x.port; - - //somehow our counter output is going to multiple ports - //this makes no sense, don't allow inference - else if(portname != x.port) - return 17; - } + if(x.cell != c) + continue; + extract.pouts.insert(ModIndex::PortInfo(c, x.port, 0)); } - - //Save the other loads - extract.pouts.insert(ModIndex::PortInfo(c, portname, 0)); } } } @@ -346,7 +391,7 @@ void counter_worker( //Do nothing, unless extraction was forced in which case give an error if(reason != 0) { - static const char* reasons[24]= + static const char* reasons[25]= { "no problem", //0 "counter is too large/small", //1 @@ -370,8 +415,9 @@ void counter_worker( "Register output is not full bus", //19 "No init value found", //20 "Underflow value is not equal to init value", //21 - "Reset polarity is not positive", //22 - "Reset is not to zero" //23 + "RESERVED, not implemented", //22, kept for compatibility but not used anymore + "Reset is not to zero or COUNT_TO", //23 + "Clock enable configuration is unsupported" //24 }; if(force_extract) @@ -409,7 +455,16 @@ void counter_worker( { //TODO: support other kinds of reset cell->setParam("\\RESET_MODE", RTLIL::Const("LEVEL")); - cell->setPort("\\RST", extract.rst); + + //If the reset is active low, infer an inverter ($__COUNT_ cells always have active high reset) + if(extract.rst_inverted) + { + auto realreset = cell->module->addWire(NEW_ID); + cell->module->addNot(NEW_ID, extract.rst, RTLIL::SigSpec(realreset)); + cell->setPort("\\RST", realreset); + } + else + cell->setPort("\\RST", extract.rst); } else { @@ -424,12 +479,21 @@ void counter_worker( cell->setPort("\\CLK", extract.clk); cell->setPort("\\OUT", extract.outsig); - //Hook up hard-wired ports (for now CE and up/=down are not supported), default to no parallel output + //Hook up clock enable + if(extract.has_ce) + { + cell->setParam("\\HAS_CE", RTLIL::Const(1)); + cell->setPort("\\CE", extract.ce); + } + else + cell->setParam("\\HAS_CE", RTLIL::Const(0)); + + //Hook up hard-wired ports (for now up/down are not supported), default to no parallel output cell->setParam("\\HAS_POUT", RTLIL::Const(0)); - cell->setParam("\\HAS_CE", RTLIL::Const(0)); + cell->setParam("\\RESET_TO_MAX", RTLIL::Const(0)); cell->setParam("\\DIRECTION", RTLIL::Const("DOWN")); cell->setPort("\\CE", RTLIL::Const(1)); - cell->setPort("\\UP", RTLIL::Const(1)); + cell->setPort("\\UP", RTLIL::Const(0)); //Hook up any parallel outputs for(auto load : extract.pouts) @@ -455,10 +519,15 @@ void counter_worker( string reset_type = "non-resettable"; if(extract.has_reset) { + if(extract.rst_inverted) + reset_type = "negative"; + else + reset_type = "positive"; + //TODO: support other kind of reset - reset_type = "async resettable"; + reset_type += " async resettable"; } - log(" Found %d-bit %s down counter %s (counting from %d) for register %s declared at %s\n", + log(" Found %d-bit (%s) down counter %s (counting from %d) for register %s, declared at %s\n", extract.width, reset_type.c_str(), countname.c_str(), |