mirror of
				https://github.com/YosysHQ/yosys
				synced 2025-11-04 05:19:11 +00:00 
			
		
		
		
	pmgen: Move passes out of pmgen folder
- Techlib pmgens are now in relevant techlibs/*. - `peepopt` pmgens are now in passes/opt. - `test_pmgen` is still in passes/pmgen. - Update `Makefile.inc` and `.gitignore` file(s) to match new `*_pm.h` location, as well as the `#include`s. - Change default `%_pm.h` make target to `techlibs/%_pm.h` and move it to the top level Makefile. - Update pmgen target to use `$(notdir $*)` (where `$*` is the part of the file name that matched the '%' in the target) instead of `$(subst _pm.h,,$(notdir $@))`.
This commit is contained in:
		
							parent
							
								
									18a7c00382
								
							
						
					
					
						commit
						0ec5f1b756
					
				
					 31 changed files with 71 additions and 77 deletions
				
			
		
							
								
								
									
										1
									
								
								passes/opt/.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								passes/opt/.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
/peepopt*_pm.h
 | 
			
		||||
| 
						 | 
				
			
			@ -22,4 +22,18 @@ OBJS += passes/opt/opt_lut_ins.o
 | 
			
		|||
OBJS += passes/opt/opt_ffinv.o
 | 
			
		||||
OBJS += passes/opt/pmux2shiftx.o
 | 
			
		||||
OBJS += passes/opt/muxpack.o
 | 
			
		||||
 | 
			
		||||
OBJS += passes/opt/peepopt.o
 | 
			
		||||
GENFILES += passes/opt/peepopt_pm.h
 | 
			
		||||
passes/opt/peepopt.o: passes/opt/peepopt_pm.h
 | 
			
		||||
$(eval $(call add_extra_objs,passes/opt/peepopt_pm.h))
 | 
			
		||||
 | 
			
		||||
PEEPOPT_PATTERN  = passes/opt/peepopt_shiftmul_right.pmg
 | 
			
		||||
PEEPOPT_PATTERN += passes/opt/peepopt_shiftmul_left.pmg
 | 
			
		||||
PEEPOPT_PATTERN += passes/opt/peepopt_shiftadd.pmg
 | 
			
		||||
PEEPOPT_PATTERN += passes/opt/peepopt_muldiv.pmg
 | 
			
		||||
PEEPOPT_PATTERN += passes/opt/peepopt_formal_clockgateff.pmg
 | 
			
		||||
 | 
			
		||||
passes/opt/peepopt_pm.h: passes/pmgen/pmgen.py $(PEEPOPT_PATTERN)
 | 
			
		||||
	$(P) mkdir -p $(dir $@) && $(PYTHON_EXECUTABLE) $< -o $@ -p peepopt $(filter-out $<,$^)
 | 
			
		||||
endif
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										114
									
								
								passes/opt/peepopt.cc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								passes/opt/peepopt.cc
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,114 @@
 | 
			
		|||
/*
 | 
			
		||||
 *  yosys -- Yosys Open SYnthesis Suite
 | 
			
		||||
 *
 | 
			
		||||
 *  Copyright (C) 2012  Claire Xenia Wolf <claire@yosyshq.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"
 | 
			
		||||
 | 
			
		||||
USING_YOSYS_NAMESPACE
 | 
			
		||||
PRIVATE_NAMESPACE_BEGIN
 | 
			
		||||
 | 
			
		||||
bool did_something;
 | 
			
		||||
 | 
			
		||||
// scratchpad configurations for pmgen
 | 
			
		||||
int shiftadd_max_ratio;
 | 
			
		||||
 | 
			
		||||
#include "passes/opt/peepopt_pm.h"
 | 
			
		||||
 | 
			
		||||
struct PeepoptPass : public Pass {
 | 
			
		||||
	PeepoptPass() : Pass("peepopt", "collection of peephole optimizers") { }
 | 
			
		||||
	void help() override
 | 
			
		||||
	{
 | 
			
		||||
		//   |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
 | 
			
		||||
		log("\n");
 | 
			
		||||
		log("    peepopt [options] [selection]\n");
 | 
			
		||||
		log("\n");
 | 
			
		||||
		log("This pass applies a collection of peephole optimizers to the current design.\n");
 | 
			
		||||
		log("\n");
 | 
			
		||||
		log("This pass employs the following rules by default:\n");
 | 
			
		||||
		log("\n");
 | 
			
		||||
		log("   * muldiv - Replace (A*B)/B with A\n");
 | 
			
		||||
		log("\n");
 | 
			
		||||
		log("   * shiftmul - Replace A>>(B*C) with A'>>(B<<K) where C and K are constants\n");
 | 
			
		||||
		log("                and A' is derived from A by appropriately inserting padding\n");
 | 
			
		||||
		log("                into the signal. (right variant)\n");
 | 
			
		||||
		log("\n");
 | 
			
		||||
		log("                Analogously, replace A<<(B*C) with appropriate selection of\n");
 | 
			
		||||
		log("                output bits from A<<(B<<K). (left variant)\n");
 | 
			
		||||
		log("\n");
 | 
			
		||||
		log("   * shiftadd - Replace A>>(B+D) with (A'>>D)>>(B) where D is constant and\n");
 | 
			
		||||
		log("                A' is derived from A by padding or cutting inaccessible bits.\n");
 | 
			
		||||
		log("                Scratchpad: 'peepopt.shiftadd.max_data_multiple' (default: 2)\n");
 | 
			
		||||
		log("                limits the amount of padding to a multiple of the data, \n");
 | 
			
		||||
		log("                to avoid high resource usage from large temporary MUX trees.\n");
 | 
			
		||||
		log("\n");
 | 
			
		||||
		log("If -formalclk is specified it instead employs the following rules:\n");
 | 
			
		||||
		log("\n");
 | 
			
		||||
		log("   * clockgateff - Replace latch based clock gating patterns with a flip-flop\n");
 | 
			
		||||
		log("                   based pattern to prevent combinational paths from the\n");
 | 
			
		||||
		log("                   output to the enable input after running clk2fflogic.\n");
 | 
			
		||||
		log("\n");
 | 
			
		||||
	}
 | 
			
		||||
	void execute(std::vector<std::string> args, RTLIL::Design *design) override
 | 
			
		||||
	{
 | 
			
		||||
		log_header(design, "Executing PEEPOPT pass (run peephole optimizers).\n");
 | 
			
		||||
 | 
			
		||||
		bool formalclk = false;
 | 
			
		||||
 | 
			
		||||
		size_t argidx;
 | 
			
		||||
		for (argidx = 1; argidx < args.size(); argidx++)
 | 
			
		||||
		{
 | 
			
		||||
			if (args[argidx] == "-formalclk") {
 | 
			
		||||
				formalclk = true;
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
		extra_args(args, argidx, design);
 | 
			
		||||
 | 
			
		||||
		// limit the padding from shiftadd to a multiple of the input data
 | 
			
		||||
		// during techmap it creates (#data + #padding) * log(shift) $_MUX_ cells
 | 
			
		||||
		// 2x implies there is a constant shift larger than the input-data which should be extremely rare
 | 
			
		||||
		shiftadd_max_ratio = design->scratchpad_get_int("peepopt.shiftadd.max_data_multiple", 2);
 | 
			
		||||
 | 
			
		||||
		for (auto module : design->selected_modules())
 | 
			
		||||
		{
 | 
			
		||||
			did_something = true;
 | 
			
		||||
 | 
			
		||||
			while (did_something)
 | 
			
		||||
			{
 | 
			
		||||
				did_something = false;
 | 
			
		||||
 | 
			
		||||
				peepopt_pm pm(module);
 | 
			
		||||
 | 
			
		||||
				pm.setup(module->selected_cells());
 | 
			
		||||
 | 
			
		||||
				if (formalclk) {
 | 
			
		||||
					pm.run_formal_clockgateff();
 | 
			
		||||
				} else {
 | 
			
		||||
					pm.run_shiftadd();
 | 
			
		||||
					pm.run_shiftmul_right();
 | 
			
		||||
					pm.run_shiftmul_left();
 | 
			
		||||
					pm.run_muldiv();
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
} PeepoptPass;
 | 
			
		||||
 | 
			
		||||
PRIVATE_NAMESPACE_END
 | 
			
		||||
							
								
								
									
										59
									
								
								passes/opt/peepopt_formal_clockgateff.pmg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								passes/opt/peepopt_formal_clockgateff.pmg
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,59 @@
 | 
			
		|||
pattern formal_clockgateff
 | 
			
		||||
 | 
			
		||||
// Detects the most common clock gating pattern using a latch and replaces it
 | 
			
		||||
// with a functionally equivalent pattern based on a flip-flop. The latch
 | 
			
		||||
// based pattern has a combinational path from the enable input to output after
 | 
			
		||||
// clk2fflogic, but this is a stable loop and the flip-flop based pattern does
 | 
			
		||||
// not exhibit this.
 | 
			
		||||
//
 | 
			
		||||
// This optimization is suitable for formal to prevent false comb loops, but
 | 
			
		||||
// should not be used for synthesis where the latch is an intentional choice
 | 
			
		||||
//
 | 
			
		||||
// Latch style:
 | 
			
		||||
// always @* if (!clk_i) latched_en = en;
 | 
			
		||||
// assign gated_clk_o = latched_en & clk_i;
 | 
			
		||||
//
 | 
			
		||||
// Flip-flop style:
 | 
			
		||||
// always @(posedge clk) flopped_en <= en;
 | 
			
		||||
// assign gated_clk_o = flopped_en & clk_i;
 | 
			
		||||
 | 
			
		||||
state <SigSpec> clk en latched_en gated_clk
 | 
			
		||||
state <IdString> latched_en_port_name
 | 
			
		||||
 | 
			
		||||
match latch
 | 
			
		||||
	select latch->type == $dlatch
 | 
			
		||||
	select param(latch, \WIDTH) == 1
 | 
			
		||||
	select param(latch, \EN_POLARITY).as_bool() == false
 | 
			
		||||
	set clk port(latch, \EN)
 | 
			
		||||
	set en port(latch, \D)
 | 
			
		||||
	set latched_en port(latch, \Q)
 | 
			
		||||
endmatch
 | 
			
		||||
 | 
			
		||||
match and_gate
 | 
			
		||||
	select and_gate->type.in($and, $logic_and)
 | 
			
		||||
	select param(and_gate, \A_WIDTH) == 1
 | 
			
		||||
	select param(and_gate, \B_WIDTH) == 1
 | 
			
		||||
	select param(and_gate, \Y_WIDTH) == 1
 | 
			
		||||
	choice <IdString> clk_port {\A, \B}
 | 
			
		||||
	define <IdString> latch_port {clk_port == \A ? \B : \A}
 | 
			
		||||
	index <SigSpec> port(and_gate, clk_port) === clk
 | 
			
		||||
	index <SigSpec> port(and_gate, latch_port) === latched_en
 | 
			
		||||
	set gated_clk port(and_gate, \Y)
 | 
			
		||||
	set latched_en_port_name latch_port
 | 
			
		||||
endmatch
 | 
			
		||||
 | 
			
		||||
code
 | 
			
		||||
	log("replacing clock gate pattern in %s with ff: latch=%s, and=%s\n",
 | 
			
		||||
		log_id(module), log_id(latch), log_id(and_gate));
 | 
			
		||||
 | 
			
		||||
	// Add a flip-flop and rewire the AND gate to use the output of this flop
 | 
			
		||||
	// instead of the latch. We don't delete the latch in case its output is
 | 
			
		||||
	// used to drive other nodes. If it isn't, it will be trivially removed by
 | 
			
		||||
	// clean
 | 
			
		||||
	SigSpec flopped_en = module->addWire(NEW_ID);
 | 
			
		||||
	module->addDff(NEW_ID, clk, en, flopped_en, true, latch->get_src_attribute());
 | 
			
		||||
	and_gate->setPort(latched_en_port_name, flopped_en);
 | 
			
		||||
	did_something = true;
 | 
			
		||||
 | 
			
		||||
	accept;
 | 
			
		||||
endcode
 | 
			
		||||
							
								
								
									
										39
									
								
								passes/opt/peepopt_muldiv.pmg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								passes/opt/peepopt_muldiv.pmg
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,39 @@
 | 
			
		|||
pattern muldiv
 | 
			
		||||
 | 
			
		||||
state <SigSpec> t x y
 | 
			
		||||
state <bool> is_signed
 | 
			
		||||
 | 
			
		||||
match mul
 | 
			
		||||
	select mul->type == $mul
 | 
			
		||||
	select GetSize(port(mul, \A)) + GetSize(port(mul, \B)) <= GetSize(port(mul, \Y))
 | 
			
		||||
endmatch
 | 
			
		||||
 | 
			
		||||
code t x y is_signed
 | 
			
		||||
	t = port(mul, \Y);
 | 
			
		||||
	x = port(mul, \A);
 | 
			
		||||
	y = port(mul, \B);
 | 
			
		||||
	is_signed = param(mul, \A_SIGNED).as_bool();
 | 
			
		||||
	branch;
 | 
			
		||||
	std::swap(x, y);
 | 
			
		||||
endcode
 | 
			
		||||
 | 
			
		||||
match div
 | 
			
		||||
	select div->type.in($div)
 | 
			
		||||
	index <SigSpec> port(div, \A) === t
 | 
			
		||||
	index <SigSpec> port(div, \B) === x
 | 
			
		||||
	filter param(div, \A_SIGNED).as_bool() == is_signed
 | 
			
		||||
endmatch
 | 
			
		||||
 | 
			
		||||
code
 | 
			
		||||
	SigSpec div_y = port(div, \Y);
 | 
			
		||||
	SigSpec val_y = y;
 | 
			
		||||
 | 
			
		||||
	if (GetSize(div_y) != GetSize(val_y))
 | 
			
		||||
		val_y.extend_u0(GetSize(div_y), param(div, \A_SIGNED).as_bool());
 | 
			
		||||
 | 
			
		||||
	did_something = true;
 | 
			
		||||
	log("muldiv pattern in %s: mul=%s, div=%s\n", log_id(module), log_id(mul), log_id(div));
 | 
			
		||||
	module->connect(div_y, val_y);
 | 
			
		||||
	autoremove(div);
 | 
			
		||||
	accept;
 | 
			
		||||
endcode
 | 
			
		||||
							
								
								
									
										151
									
								
								passes/opt/peepopt_shiftadd.pmg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								passes/opt/peepopt_shiftadd.pmg
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,151 @@
 | 
			
		|||
pattern shiftadd
 | 
			
		||||
//
 | 
			
		||||
// Transforms add/sub+shift pairs that result from expressions such as data[s*W +C +:W2]
 | 
			
		||||
// specifically something like: out[W2-1:0] = data >> (s*W +C)
 | 
			
		||||
// will be transformed into: out[W2-1:0] = (data >> C) >> (s*W)
 | 
			
		||||
// this can then be optimized using peepopt_shiftmul_right.pmg
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
match shift
 | 
			
		||||
	select shift->type.in($shift, $shiftx, $shr)
 | 
			
		||||
	filter !port(shift, \B).empty()
 | 
			
		||||
endmatch
 | 
			
		||||
 | 
			
		||||
// the right shift amount
 | 
			
		||||
state <SigSpec> shift_amount
 | 
			
		||||
// log2 scale factor in interpreting of shift_amount
 | 
			
		||||
// due to zero padding on the shift cell's B port
 | 
			
		||||
state <int> log2scale
 | 
			
		||||
// zeros at the MSB position make it unsigned
 | 
			
		||||
state <bool> msb_zeros
 | 
			
		||||
 | 
			
		||||
code shift_amount log2scale msb_zeros
 | 
			
		||||
	shift_amount = port(shift, \B);
 | 
			
		||||
 | 
			
		||||
	log2scale = 0;
 | 
			
		||||
	while (shift_amount[0] == State::S0) {
 | 
			
		||||
		shift_amount.remove(0);
 | 
			
		||||
		if (shift_amount.empty()) reject;
 | 
			
		||||
		log2scale++;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	msb_zeros = 0;
 | 
			
		||||
	while (shift_amount.bits().back() == State::S0) {
 | 
			
		||||
		msb_zeros = true;
 | 
			
		||||
		shift_amount.remove(GetSize(shift_amount) - 1);
 | 
			
		||||
		if (shift_amount.empty()) reject;
 | 
			
		||||
	}
 | 
			
		||||
endcode
 | 
			
		||||
 | 
			
		||||
state <bool>    var_signed
 | 
			
		||||
state <SigSpec> var_signal
 | 
			
		||||
// offset: signed constant value c in data[var+c +:W1] (constant shift-right amount)
 | 
			
		||||
state <int>     offset
 | 
			
		||||
 | 
			
		||||
match add
 | 
			
		||||
	// either data[var+c +:W1] or data[var-c +:W1]
 | 
			
		||||
	select add->type.in($add, $sub)
 | 
			
		||||
	index <SigSpec> port(add, \Y) === shift_amount
 | 
			
		||||
 | 
			
		||||
	// one must be constant, the other is variable
 | 
			
		||||
	choice <IdString> constport {\A, \B}
 | 
			
		||||
	select !port(add, constport).empty()
 | 
			
		||||
	select port(add, constport).is_fully_const()
 | 
			
		||||
	define <IdString> varport (constport == \A ? \B : \A)
 | 
			
		||||
 | 
			
		||||
	// only optimize for constants up to a fixed width. this prevents cases
 | 
			
		||||
	// with a blowup in internal term size and prevents larger constants being
 | 
			
		||||
	// casted to int incorrectly
 | 
			
		||||
	select (GetSize(port(add, constport)) <= 24)
 | 
			
		||||
 | 
			
		||||
	// if a value of var is able to wrap the output, the transformation might give wrong results
 | 
			
		||||
	// an addition/substraction can at most flip one more bit than the largest operand (the carry bit)
 | 
			
		||||
	// as long as the output can show this bit, no wrap should occur (assuming all signed-ness make sense)
 | 
			
		||||
	select ( GetSize(port(add, \Y)) > max(GetSize(port(add, \A)), GetSize(port(add, \B))) )
 | 
			
		||||
 | 
			
		||||
	define <bool> varport_A (varport == \A)
 | 
			
		||||
	define <bool> is_sub add->type.in($sub)
 | 
			
		||||
 | 
			
		||||
	define <bool> constport_signed param(add, !varport_A ? \A_SIGNED : \B_SIGNED).as_bool()
 | 
			
		||||
	define <bool> varport_signed param(add, varport_A ? \A_SIGNED : \B_SIGNED).as_bool();
 | 
			
		||||
	define <bool> const_negative (constport_signed && (port(add, constport).bits().back() == State::S1))
 | 
			
		||||
	define <bool> offset_negative ((is_sub && varport_A) ^ const_negative)
 | 
			
		||||
 | 
			
		||||
	// checking some value boundaries as well:
 | 
			
		||||
	// data[...-c +:W1] is fine for any signed var (pad at LSB, all data still accessible)
 | 
			
		||||
	// unsigned shift may underflow (eg var-3 with var<3) -> cannot be converted
 | 
			
		||||
	// data[...+c +:W1] is only fine for +var(add) and var unsigned
 | 
			
		||||
	// (+c cuts lower C bits, making them inaccessible, a signed var could try to access them)
 | 
			
		||||
	// either its an add or the variable port is A (it must be positive)
 | 
			
		||||
	select (add->type.in($add) || varport == \A)
 | 
			
		||||
 | 
			
		||||
	// -> data[var+c +:W1] (with var signed) is illegal
 | 
			
		||||
	filter !(!offset_negative && varport_signed)
 | 
			
		||||
	// -> data >> (var-c) (with var unsigned) is illegal
 | 
			
		||||
	filter !(offset_negative && !varport_signed)
 | 
			
		||||
 | 
			
		||||
	// state-variables are assigned at the end only:
 | 
			
		||||
	// shift the log2scale offset in-front of add to get true value: (var+c)<<N -> (var<<N)+(c<<N)
 | 
			
		||||
	set offset ( (port(add, constport).as_int(constport_signed) << log2scale) * ( (is_sub && varport_A) ? -1 : 1 ) )
 | 
			
		||||
 | 
			
		||||
	set var_signed varport_signed
 | 
			
		||||
	set var_signal add->getPort(varport)
 | 
			
		||||
endmatch
 | 
			
		||||
 | 
			
		||||
code
 | 
			
		||||
{
 | 
			
		||||
	// positive constant offset with a signed variable (index) cannot be handled
 | 
			
		||||
	// the above filter should get rid of this case but 'offset' is calculated differently
 | 
			
		||||
	// due to limitations of state-variables in pmgen
 | 
			
		||||
	// it should only differ if previous passes create invalid data
 | 
			
		||||
	log_assert(!(offset>0 && var_signed));
 | 
			
		||||
 | 
			
		||||
	SigSpec old_a = port(shift, \A); // data
 | 
			
		||||
	std::string location = shift->get_src_attribute();
 | 
			
		||||
 | 
			
		||||
	if(shiftadd_max_ratio>0 && offset<0 && -offset*shiftadd_max_ratio > old_a.size()) {
 | 
			
		||||
		log_warning("at %s: candiate for shiftadd optimization (shifting '%s' by '%s - %d' bits) " 
 | 
			
		||||
					"was ignored to avoid high resource usage, see help peepopt\n", 
 | 
			
		||||
					location.c_str(), log_signal(old_a), log_signal(var_signal), -offset);
 | 
			
		||||
		reject;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	did_something = true;
 | 
			
		||||
	log("shiftadd pattern in %s: shift=%s, add/sub=%s, offset: %d\n", \
 | 
			
		||||
			log_id(module), log_id(shift), log_id(add), offset);
 | 
			
		||||
 | 
			
		||||
	SigSpec new_a;
 | 
			
		||||
	if(offset<0) {
 | 
			
		||||
		// data >> (...-c) transformed to {data, c'X} >> (...)
 | 
			
		||||
		SigSpec padding( (shift->type.in($shiftx) ? State::Sx : State::S0), -offset );
 | 
			
		||||
		new_a.append(padding);
 | 
			
		||||
		new_a.append(old_a);
 | 
			
		||||
	} else {
 | 
			
		||||
		// data >> (...+c) transformed to data[MAX:c] >> (...)
 | 
			
		||||
		if(offset < GetSize(old_a)) { // some signal bits left?
 | 
			
		||||
			new_a.append(old_a.extract_end(offset));
 | 
			
		||||
		} else {
 | 
			
		||||
			// warn user in case data is empty (no bits left)
 | 
			
		||||
			if (location.empty())
 | 
			
		||||
				location = shift->name.str();
 | 
			
		||||
			if(shift->type.in($shiftx))
 | 
			
		||||
				log_warning("at %s: result of indexed part-selection is always constant (selecting from '%s' with index '%s + %d')\n", \
 | 
			
		||||
							location.c_str(), log_signal(old_a), log_signal(var_signal), offset);
 | 
			
		||||
			else
 | 
			
		||||
				log_warning("at %s: result of shift operation is always constant (shifting '%s' by '%s + %d' bits)\n", \
 | 
			
		||||
							location.c_str(), log_signal(old_a), log_signal(var_signal), offset);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	SigSpec new_b = {var_signal, SigSpec(State::S0, log2scale)};
 | 
			
		||||
	if (msb_zeros || !var_signed)
 | 
			
		||||
		new_b.append(State::S0);
 | 
			
		||||
 | 
			
		||||
	shift->setPort(\A, new_a);
 | 
			
		||||
	shift->setParam(\A_WIDTH, GetSize(new_a));
 | 
			
		||||
	shift->setPort(\B, new_b);
 | 
			
		||||
	shift->setParam(\B_WIDTH, GetSize(new_b));
 | 
			
		||||
	blacklist(add);
 | 
			
		||||
	accept;
 | 
			
		||||
}
 | 
			
		||||
endcode
 | 
			
		||||
							
								
								
									
										160
									
								
								passes/opt/peepopt_shiftmul_left.pmg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								passes/opt/peepopt_shiftmul_left.pmg
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,160 @@
 | 
			
		|||
pattern shiftmul_left
 | 
			
		||||
//
 | 
			
		||||
// Optimize mul+shift pairs that result from expressions such as foo[s*W+:W]
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
match shift
 | 
			
		||||
	select shift->type.in($shift, $shiftx, $shl)
 | 
			
		||||
	select shift->type.in($shl) || param(shift, \B_SIGNED).as_bool()
 | 
			
		||||
	filter !port(shift, \B).empty()
 | 
			
		||||
endmatch
 | 
			
		||||
 | 
			
		||||
match neg
 | 
			
		||||
	if shift->type.in($shift, $shiftx)
 | 
			
		||||
	select neg->type == $neg
 | 
			
		||||
	index <SigSpec> port(neg, \Y) === port(shift, \B)
 | 
			
		||||
	filter !port(shift, \A).empty()
 | 
			
		||||
endmatch
 | 
			
		||||
 | 
			
		||||
// the left shift amount
 | 
			
		||||
state <SigSpec> shift_amount
 | 
			
		||||
// log2 scale factor in interpreting of shift_amount
 | 
			
		||||
// due to zero padding on the shift cell's B port
 | 
			
		||||
state <int> log2scale
 | 
			
		||||
 | 
			
		||||
code shift_amount log2scale
 | 
			
		||||
	if (neg) {
 | 
			
		||||
		// case of `$shift`, `$shiftx`
 | 
			
		||||
		shift_amount = port(neg, \A);
 | 
			
		||||
		if (!param(neg, \A_SIGNED).as_bool())
 | 
			
		||||
			shift_amount.append(State::S0);
 | 
			
		||||
	} else {
 | 
			
		||||
		// case of `$shl`
 | 
			
		||||
		shift_amount = port(shift, \B);
 | 
			
		||||
		if (!param(shift, \B_SIGNED).as_bool())
 | 
			
		||||
			shift_amount.append(State::S0);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// at this point shift_amount is signed, make
 | 
			
		||||
	// sure we can never go negative
 | 
			
		||||
	if (shift_amount.bits().back() != State::S0)
 | 
			
		||||
		reject;
 | 
			
		||||
 | 
			
		||||
	while (shift_amount.bits().back() == State::S0) {
 | 
			
		||||
		shift_amount.remove(GetSize(shift_amount) - 1);
 | 
			
		||||
		if (shift_amount.empty()) reject;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log2scale = 0;
 | 
			
		||||
	while (shift_amount[0] == State::S0) {
 | 
			
		||||
		shift_amount.remove(0);
 | 
			
		||||
		if (shift_amount.empty()) reject;
 | 
			
		||||
		log2scale++;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (GetSize(shift_amount) > 20)
 | 
			
		||||
		reject;
 | 
			
		||||
endcode
 | 
			
		||||
 | 
			
		||||
state <SigSpec> mul_din
 | 
			
		||||
state <Const> mul_const
 | 
			
		||||
 | 
			
		||||
match mul
 | 
			
		||||
	select mul->type.in($mul)
 | 
			
		||||
	index <SigSpec> port(mul, \Y) === shift_amount
 | 
			
		||||
	filter !param(mul, \A_SIGNED).as_bool()
 | 
			
		||||
 | 
			
		||||
	choice <IdString> constport {\A, \B}
 | 
			
		||||
	filter port(mul, constport).is_fully_const()
 | 
			
		||||
 | 
			
		||||
	define <IdString> varport (constport == \A ? \B : \A)
 | 
			
		||||
	set mul_const SigSpec({port(mul, constport), SigSpec(State::S0, log2scale)}).as_const()
 | 
			
		||||
	// get mul_din unmapped (so no `port()` shorthand)
 | 
			
		||||
	// because we will be using it to set the \A port
 | 
			
		||||
	// on the shift cell, and we want to stay close
 | 
			
		||||
	// to the original design
 | 
			
		||||
	set mul_din mul->getPort(varport)
 | 
			
		||||
endmatch
 | 
			
		||||
 | 
			
		||||
code
 | 
			
		||||
{
 | 
			
		||||
	if (mul_const.empty() || GetSize(mul_const) > 20)
 | 
			
		||||
		reject;
 | 
			
		||||
 | 
			
		||||
	// make sure there's no overlap in the signal
 | 
			
		||||
	// selections by the shiftmul pattern
 | 
			
		||||
	if (GetSize(port(shift, \A)) > mul_const.as_int())
 | 
			
		||||
		reject;
 | 
			
		||||
 | 
			
		||||
	int factor_bits = ceil_log2(mul_const.as_int());
 | 
			
		||||
	// make sure the multiplication never wraps around
 | 
			
		||||
	if (GetSize(shift_amount) < factor_bits + GetSize(mul_din))
 | 
			
		||||
		reject;
 | 
			
		||||
 | 
			
		||||
	if (neg) {
 | 
			
		||||
		// make sure the negation never wraps around
 | 
			
		||||
		if (GetSize(port(shift, \B)) < factor_bits + GetSize(mul_din)
 | 
			
		||||
										+ log2scale + 1)
 | 
			
		||||
			reject;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	did_something = true;
 | 
			
		||||
	log("left shiftmul pattern in %s: shift=%s, mul=%s\n", log_id(module), log_id(shift), log_id(mul));
 | 
			
		||||
 | 
			
		||||
	int const_factor = mul_const.as_int();
 | 
			
		||||
	int new_const_factor = 1 << factor_bits;
 | 
			
		||||
	SigSpec padding(State::Sm, new_const_factor-const_factor);
 | 
			
		||||
	SigSpec old_y = port(shift, \Y), new_y;
 | 
			
		||||
	int trunc = 0;
 | 
			
		||||
 | 
			
		||||
	if (GetSize(old_y) % const_factor != 0) {
 | 
			
		||||
		trunc = const_factor - GetSize(old_y) % const_factor;
 | 
			
		||||
		old_y.append(SigSpec(State::Sm, trunc));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for (int i = 0; i*const_factor < GetSize(old_y); i++) {
 | 
			
		||||
		SigSpec slice = old_y.extract(i*const_factor, const_factor);
 | 
			
		||||
		new_y.append(slice);
 | 
			
		||||
		new_y.append(padding);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (trunc > 0)
 | 
			
		||||
		new_y.remove(GetSize(new_y)-trunc, trunc);
 | 
			
		||||
 | 
			
		||||
	{
 | 
			
		||||
		// Now replace occurences of Sm in new_y with bits
 | 
			
		||||
		// of a dummy wire
 | 
			
		||||
		int padbits = 0;
 | 
			
		||||
		for (auto bit : new_y)
 | 
			
		||||
		if (bit == SigBit(State::Sm))
 | 
			
		||||
			padbits++;
 | 
			
		||||
 | 
			
		||||
		SigSpec padwire = module->addWire(NEW_ID, padbits);
 | 
			
		||||
 | 
			
		||||
		for (int i = new_y.size() - 1; i >= 0; i--)
 | 
			
		||||
		if (new_y[i] == SigBit(State::Sm)) {
 | 
			
		||||
			new_y[i] = padwire.bits().back();
 | 
			
		||||
			padwire.remove(padwire.size() - 1);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	SigSpec new_b = {mul_din, SigSpec(State::S0, factor_bits)};
 | 
			
		||||
 | 
			
		||||
	shift->setPort(\Y, new_y);
 | 
			
		||||
	shift->setParam(\Y_WIDTH, GetSize(new_y));
 | 
			
		||||
	if (shift->type == $shl) {
 | 
			
		||||
		if (param(shift, \B_SIGNED).as_bool())
 | 
			
		||||
			new_b.append(State::S0);
 | 
			
		||||
		shift->setPort(\B, new_b);
 | 
			
		||||
		shift->setParam(\B_WIDTH, GetSize(new_b));
 | 
			
		||||
	} else {
 | 
			
		||||
		SigSpec b_neg = module->addWire(NEW_ID, GetSize(new_b) + 1);
 | 
			
		||||
		module->addNeg(NEW_ID, new_b, b_neg);
 | 
			
		||||
		shift->setPort(\B, b_neg);
 | 
			
		||||
		shift->setParam(\B_WIDTH, GetSize(b_neg));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	blacklist(shift);
 | 
			
		||||
	accept;
 | 
			
		||||
}
 | 
			
		||||
endcode
 | 
			
		||||
							
								
								
									
										108
									
								
								passes/opt/peepopt_shiftmul_right.pmg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								passes/opt/peepopt_shiftmul_right.pmg
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,108 @@
 | 
			
		|||
pattern shiftmul_right
 | 
			
		||||
//
 | 
			
		||||
// Optimize mul+shift pairs that result from expressions such as foo[s*W+:W]
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
match shift
 | 
			
		||||
	select shift->type.in($shift, $shiftx, $shr)
 | 
			
		||||
	filter !port(shift, \B).empty()
 | 
			
		||||
endmatch
 | 
			
		||||
 | 
			
		||||
// the right shift amount
 | 
			
		||||
state <SigSpec> shift_amount
 | 
			
		||||
// log2 scale factor in interpreting of shift_amount
 | 
			
		||||
// due to zero padding on the shift cell's B port
 | 
			
		||||
state <int> log2scale
 | 
			
		||||
 | 
			
		||||
code shift_amount log2scale
 | 
			
		||||
	shift_amount = port(shift, \B);
 | 
			
		||||
	if (shift->type.in($shr) || !param(shift, \B_SIGNED).as_bool())
 | 
			
		||||
		shift_amount.append(State::S0);
 | 
			
		||||
 | 
			
		||||
	// at this point shift_amount is signed, make
 | 
			
		||||
	// sure we can never go negative
 | 
			
		||||
	if (shift_amount.bits().back() != State::S0)
 | 
			
		||||
		reject;
 | 
			
		||||
 | 
			
		||||
	while (shift_amount.bits().back() == State::S0) {
 | 
			
		||||
		shift_amount.remove(GetSize(shift_amount) - 1);
 | 
			
		||||
		if (shift_amount.empty()) reject;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log2scale = 0;
 | 
			
		||||
	while (shift_amount[0] == State::S0) {
 | 
			
		||||
		shift_amount.remove(0);
 | 
			
		||||
		if (shift_amount.empty()) reject;
 | 
			
		||||
		log2scale++;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (GetSize(shift_amount) > 20)
 | 
			
		||||
		reject;
 | 
			
		||||
endcode
 | 
			
		||||
 | 
			
		||||
state <SigSpec> mul_din
 | 
			
		||||
state <Const> mul_const
 | 
			
		||||
 | 
			
		||||
match mul
 | 
			
		||||
	select mul->type.in($mul)
 | 
			
		||||
	index <SigSpec> port(mul, \Y) === shift_amount
 | 
			
		||||
	filter !param(mul, \A_SIGNED).as_bool()
 | 
			
		||||
 | 
			
		||||
	choice <IdString> constport {\A, \B}
 | 
			
		||||
	filter port(mul, constport).is_fully_const()
 | 
			
		||||
 | 
			
		||||
	define <IdString> varport (constport == \A ? \B : \A)
 | 
			
		||||
	set mul_const SigSpec({port(mul, constport), SigSpec(State::S0, log2scale)}).as_const()
 | 
			
		||||
	// get mul_din unmapped (so no `port()` shorthand)
 | 
			
		||||
	// because we will be using it to set the \A port
 | 
			
		||||
	// on the shift cell, and we want to stay close
 | 
			
		||||
	// to the original design
 | 
			
		||||
	set mul_din mul->getPort(varport)
 | 
			
		||||
endmatch
 | 
			
		||||
 | 
			
		||||
code
 | 
			
		||||
{
 | 
			
		||||
	if (mul_const.empty() || GetSize(mul_const) > 20)
 | 
			
		||||
		reject;
 | 
			
		||||
 | 
			
		||||
	// make sure there's no overlap in the signal
 | 
			
		||||
	// selections by the shiftmul pattern
 | 
			
		||||
	if (GetSize(port(shift, \Y)) > mul_const.as_int())
 | 
			
		||||
		reject;
 | 
			
		||||
 | 
			
		||||
	int factor_bits = ceil_log2(mul_const.as_int());
 | 
			
		||||
	// make sure the multiplication never wraps around
 | 
			
		||||
	if (GetSize(shift_amount) + log2scale < factor_bits + GetSize(mul_din))
 | 
			
		||||
		reject;
 | 
			
		||||
 | 
			
		||||
	did_something = true;
 | 
			
		||||
	log("right shiftmul pattern in %s: shift=%s, mul=%s\n", log_id(module), log_id(shift), log_id(mul));
 | 
			
		||||
 | 
			
		||||
	int const_factor = mul_const.as_int();
 | 
			
		||||
	int new_const_factor = 1 << factor_bits;
 | 
			
		||||
	SigSpec padding(State::Sx, new_const_factor-const_factor);
 | 
			
		||||
	SigSpec old_a = port(shift, \A), new_a;
 | 
			
		||||
 | 
			
		||||
	for (int i = 0; i*const_factor < GetSize(old_a); i++) {
 | 
			
		||||
		if ((i+1)*const_factor < GetSize(old_a)) {
 | 
			
		||||
			SigSpec slice = old_a.extract(i*const_factor, const_factor);
 | 
			
		||||
			new_a.append(slice);
 | 
			
		||||
			new_a.append(padding);
 | 
			
		||||
		} else {
 | 
			
		||||
			new_a.append(old_a.extract_end(i*const_factor));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	SigSpec new_b = {mul_din, SigSpec(State::S0, factor_bits)};
 | 
			
		||||
	if (param(shift, \B_SIGNED).as_bool())
 | 
			
		||||
		new_b.append(State::S0);
 | 
			
		||||
 | 
			
		||||
	shift->setPort(\A, new_a);
 | 
			
		||||
	shift->setParam(\A_WIDTH, GetSize(new_a));
 | 
			
		||||
	shift->setPort(\B, new_b);
 | 
			
		||||
	shift->setParam(\B_WIDTH, GetSize(new_b));
 | 
			
		||||
 | 
			
		||||
	blacklist(shift);
 | 
			
		||||
	accept;
 | 
			
		||||
}
 | 
			
		||||
endcode
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue