mirror of
				https://github.com/YosysHQ/yosys
				synced 2025-10-31 19:52:31 +00:00 
			
		
		
		
	- 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 $@))`.
		
			
				
	
	
		
			409 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
			
		
		
	
	
			409 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
| // This file describes the third of three pattern matcher setups that
 | |
| //   forms the `xilinx_dsp` pass described in xilinx_dsp.cc
 | |
| // At a high level, it works as follows:
 | |
| //   (1) Starting from a DSP48E1 cell that (a) has the Z multiplexer
 | |
| //       (controlled by OPMODE[6:4]) set to zero and (b) doesn't already
 | |
| //       use the 'PCOUT' port
 | |
| //   (2.1) Match another DSP48E1 cell that (a) does not have the CREG enabled,
 | |
| //         (b) has its Z multiplexer output set to the 'C' port, which is
 | |
| //         driven by the 'P' output of the previous DSP cell, and (c) has its
 | |
| //         'PCIN' port unused
 | |
| //   (2.2) Same as (2.1) but with the 'C' port driven by the 'P' output of the
 | |
| //         previous DSP cell right-shifted by 17 bits
 | |
| //   (3) For this subequent DSP48E1 match (i.e. PCOUT -> PCIN cascade exists)
 | |
| //       if (a) the previous DSP48E1 uses either the A2REG or A1REG, (b) this
 | |
| //       DSP48 does not use A2REG nor A1REG, (c) this DSP48E1 does not already
 | |
| //       have an ACOUT -> ACIN cascade, (d) the previous DSP does not already
 | |
| //       use its ACOUT port, then examine if an ACOUT -> ACIN cascade
 | |
| //       opportunity exists by matching for a $dff-with-optional-clock-enable-
 | |
| //       or-reset and checking that the 'D' input of this register is the same
 | |
| //       as the 'A' input of the previous DSP
 | |
| //   (4) Same as (3) but for BCOUT -> BCIN cascade
 | |
| //   (5) Recursively go to (2.1) until no more matches possible, keeping track
 | |
| //       of the longest possible chain found
 | |
| //   (6) The longest chain is then divided into chunks of no more than
 | |
| //       MAX_DSP_CASCADE in length (to prevent long cascades that exceed the
 | |
| //       height of a DSP column) with each DSP in each chunk being rewritten
 | |
| //       to use [ABP]COUT -> [ABP]CIN cascading as appropriate
 | |
| // Notes:
 | |
| //   - Currently, [AB]COUT -> [AB]COUT cascades (3 or 4) are only considered
 | |
| //     if a PCOUT -> PCIN cascade is (2.1 or 2.2) first identified; this need
 | |
| //     not be the case --- [AB] cascades can exist independently of a P cascade
 | |
| //     (though all three cascades must come from the same DSP). This situation
 | |
| //     is not handled currently.
 | |
| //   - In addition, [AB]COUT -> [AB]COUT cascades (3 or 4) are currently
 | |
| //     conservative in that they examine the situation where (a) the previous
 | |
| //     DSP has [AB]2REG or [AB]1REG enabled, (b) that the downstream DSP has no
 | |
| //     registers enabled, and (c) that there exists only one additional register
 | |
| //     between the upstream and downstream DSPs. This can certainly be relaxed
 | |
| //     to identify situations ranging from (i) neither DSP uses any registers,
 | |
| //     to (ii) upstream DSP has 2 registers, downstream DSP has 2 registers, and
 | |
| //     there exists a further 2 registers between them. This remains a TODO
 | |
| //     item.
 | |
| 
 | |
| pattern xilinx_dsp_cascade
 | |
| 
 | |
| udata <std::function<SigSpec(const SigSpec&)>> unextend
 | |
| udata <vector<std::tuple<Cell*,int,int,int>>> chain longest_chain
 | |
| state <Cell*> next
 | |
| state <SigBit> clock
 | |
| state <int> AREG BREG
 | |
| 
 | |
| // Variables used for subpatterns
 | |
| state <SigSpec> argQ argD
 | |
| state <int> ffoffset
 | |
| udata <SigSpec> dffD dffQ
 | |
| udata <SigBit> dffclock
 | |
| udata <Cell*> dff
 | |
| 
 | |
| code
 | |
| #define MAX_DSP_CASCADE 20
 | |
| endcode
 | |
| 
 | |
| // (1) Starting from a DSP48* cell that (a) has the Z multiplexer
 | |
| //     (controlled by OPMODE[3:2] for DSP48A*, by OPMODE[6:4] for DSP48E1)
 | |
| //     set to zero and (b) doesn't already use the 'PCOUT' port
 | |
| match first
 | |
| 	select (first->type.in(\DSP48A, \DSP48A1) && port(first, \OPMODE, Const(0, 8)).extract(2,2) == Const::from_string("00")) || (first->type.in(\DSP48E1) && port(first, \OPMODE, Const(0, 7)).extract(4,3) == Const::from_string("000"))
 | |
| 	select nusers(port(first, \PCOUT, SigSpec())) <= 1
 | |
| endmatch
 | |
| 
 | |
| // (6) The longest chain is then divided into chunks of no more than
 | |
| //     MAX_DSP_CASCADE in length (to prevent long cascades that exceed the
 | |
| //     height of a DSP column) with each DSP in each chunk being rewritten
 | |
| //     to use [ABP]COUT -> [ABP]CIN cascading as appropriate
 | |
| code
 | |
| 	longest_chain.clear();
 | |
| 	chain.emplace_back(first, -1, -1, -1);
 | |
| 	subpattern(tail);
 | |
| finally
 | |
| 	chain.pop_back();
 | |
| 	log_assert(chain.empty());
 | |
| 	if (GetSize(longest_chain) > 1) {
 | |
| 		Cell *dsp = std::get<0>(longest_chain.front());
 | |
| 
 | |
| 		Cell *dsp_pcin;
 | |
| 		int P, AREG, BREG;
 | |
| 		for (int i = 1; i < GetSize(longest_chain); i++) {
 | |
| 			std::tie(dsp_pcin,P,AREG,BREG) = longest_chain[i];
 | |
| 
 | |
| 			if (i % MAX_DSP_CASCADE > 0) {
 | |
| 				if (P >= 0) {
 | |
| 					Wire *cascade = module->addWire(NEW_ID, 48);
 | |
| 					dsp_pcin->setPort(ID(C), Const(0, 48));
 | |
| 					dsp_pcin->setPort(ID(PCIN), cascade);
 | |
| 					dsp->setPort(ID(PCOUT), cascade);
 | |
| 					add_siguser(cascade, dsp_pcin);
 | |
| 					add_siguser(cascade, dsp);
 | |
| 
 | |
| 					SigSpec opmode = port(dsp_pcin, \OPMODE, Const(0, 7));
 | |
| 					if (dsp->type.in(\DSP48A, \DSP48A1)) {
 | |
| 						log_assert(P == 0);
 | |
| 						opmode[3] = State::S0;
 | |
| 						opmode[2] = State::S1;
 | |
| 					}
 | |
| 					else if (dsp->type.in(\DSP48E1)) {
 | |
| 						if (P == 17)
 | |
| 							opmode[6] = State::S1;
 | |
| 						else if (P == 0)
 | |
| 							opmode[6] = State::S0;
 | |
| 						else log_abort();
 | |
| 
 | |
| 						opmode[5] = State::S0;
 | |
| 						opmode[4] = State::S1;
 | |
| 					}
 | |
| 					dsp_pcin->setPort(\OPMODE, opmode);
 | |
| 
 | |
| 					log_debug("PCOUT -> PCIN cascade for %s -> %s\n", log_id(dsp), log_id(dsp_pcin));
 | |
| 				}
 | |
| 				if (AREG >= 0) {
 | |
| 					Wire *cascade = module->addWire(NEW_ID, 30);
 | |
| 					dsp_pcin->setPort(ID(A), Const(0, 30));
 | |
| 					dsp_pcin->setPort(ID(ACIN), cascade);
 | |
| 					dsp->setPort(ID(ACOUT), cascade);
 | |
| 					add_siguser(cascade, dsp_pcin);
 | |
| 					add_siguser(cascade, dsp);
 | |
| 
 | |
| 					if (dsp->type.in(\DSP48E1))
 | |
| 						dsp->setParam(ID(ACASCREG), AREG);
 | |
| 					dsp_pcin->setParam(ID(A_INPUT), Const("CASCADE"));
 | |
| 
 | |
| 					log_debug("ACOUT -> ACIN cascade for %s -> %s\n", log_id(dsp), log_id(dsp_pcin));
 | |
| 				}
 | |
| 				if (BREG >= 0) {
 | |
| 					Wire *cascade = module->addWire(NEW_ID, 18);
 | |
| 					if (dsp->type.in(\DSP48A, \DSP48A1)) {
 | |
| 						// According to UG389 p9 [https://www.xilinx.com/support/documentation/user_guides/ug389.pdf]
 | |
| 						// "The DSP48A1 component uses this input when cascading
 | |
| 						//   BCOUT from an adjacent DSP48A1 slice. The tools then
 | |
| 						//   translate BCOUT cascading to the dedicated BCIN input
 | |
| 						//   and set the B_INPUT attribute for implementation."
 | |
| 						dsp_pcin->setPort(ID(B), cascade);
 | |
| 					}
 | |
| 					else {
 | |
| 						dsp_pcin->setPort(ID(B), Const(0, 18));
 | |
| 						dsp_pcin->setPort(ID(BCIN), cascade);
 | |
| 					}
 | |
| 					dsp->setPort(ID(BCOUT), cascade);
 | |
| 					add_siguser(cascade, dsp_pcin);
 | |
| 					add_siguser(cascade, dsp);
 | |
| 
 | |
| 					if (dsp->type.in(\DSP48E1)) {
 | |
| 						dsp->setParam(ID(BCASCREG), BREG);
 | |
| 						// According to UG389 p13 [https://www.xilinx.com/support/documentation/user_guides/ug389.pdf]
 | |
| 						// "The attribute is only used by place and route tools and
 | |
| 						//   is not necessary for the users to set for synthesis. The
 | |
| 						//   attribute is determined by the connection to the B port
 | |
| 						//   of the DSP48A1 slice. If the B port is connected to the
 | |
| 						//   BCOUT of another DSP48A1 slice, then the tools automatically
 | |
| 						//   set the attribute to 'CASCADE', otherwise it is set to
 | |
| 						//   'DIRECT'".
 | |
| 						dsp_pcin->setParam(ID(B_INPUT), Const("CASCADE"));
 | |
| 					}
 | |
| 
 | |
| 					log_debug("BCOUT -> BCIN cascade for %s -> %s\n", log_id(dsp), log_id(dsp_pcin));
 | |
| 				}
 | |
| 			}
 | |
| 			else {
 | |
| 				log_debug("  Blocking %s -> %s cascade (exceeds max: %d)\n", log_id(dsp), log_id(dsp_pcin), MAX_DSP_CASCADE);
 | |
| 			}
 | |
| 
 | |
| 			dsp = dsp_pcin;
 | |
| 		}
 | |
| 
 | |
| 		accept;
 | |
| 	}
 | |
| endcode
 | |
| 
 | |
| // ------------------------------------------------------------------
 | |
| 
 | |
| subpattern tail
 | |
| arg first
 | |
| arg next
 | |
| 
 | |
| // (2.1) Match another DSP48* cell that (a) does not have the CREG enabled,
 | |
| //       (b) has its Z multiplexer output set to the 'C' port, which is
 | |
| //       driven by the 'P' output of the previous DSP cell, and (c) has its
 | |
| //       'PCIN' port unused
 | |
| match nextP
 | |
| 	select !nextP->type.in(\DSP48E1) || !param(nextP, \CREG).as_bool()
 | |
| 	select (nextP->type.in(\DSP48A, \DSP48A1) && port(nextP, \OPMODE, Const(0, 8)).extract(2,2) == Const::from_string("11")) || (nextP->type.in(\DSP48E1) && port(nextP, \OPMODE, Const(0, 7)).extract(4,3) == Const::from_string("011"))
 | |
| 	select nusers(port(nextP, \C, SigSpec())) > 1
 | |
| 	select nusers(port(nextP, \PCIN, SigSpec())) == 0
 | |
| 	index <SigBit> port(nextP, \C)[0] === port(std::get<0>(chain.back()), \P)[0]
 | |
| 	semioptional
 | |
| endmatch
 | |
| 
 | |
| // (2.2) For DSP48E1 only, same as (2.1) but with the 'C' port driven
 | |
| //       by the 'P' output of the previous DSP cell right-shifted by 17 bits
 | |
| match nextP_shift17
 | |
| 	if !nextP
 | |
| 	select nextP_shift17->type.in(\DSP48E1)
 | |
| 	select !param(nextP_shift17, \CREG).as_bool()
 | |
| 	select port(nextP_shift17, \OPMODE, Const(0, 7)).extract(4,3) == Const::from_string("011")
 | |
| 	select nusers(port(nextP_shift17, \C, SigSpec())) > 1
 | |
| 	select nusers(port(nextP_shift17, \PCIN, SigSpec())) == 0
 | |
| 	index <SigBit> port(nextP_shift17, \C)[0] === port(std::get<0>(chain.back()), \P)[17]
 | |
| 	semioptional
 | |
| endmatch
 | |
| 
 | |
| code next
 | |
| 	next = nextP;
 | |
| 	if (!nextP)
 | |
| 		next = nextP_shift17;
 | |
| 	if (next) {
 | |
| 		if (next->type != first->type)
 | |
| 			reject;
 | |
| 		unextend = [](const SigSpec &sig) {
 | |
| 			int i;
 | |
| 			for (i = GetSize(sig)-1; i > 0; i--)
 | |
| 				if (sig[i] != sig[i-1])
 | |
| 					break;
 | |
| 			// Do not remove non-const sign bit
 | |
| 			if (sig[i].wire)
 | |
| 				++i;
 | |
| 			return sig.extract(0, i);
 | |
| 		};
 | |
| 	}
 | |
| endcode
 | |
| 
 | |
| // (3) For this subequent DSP48E1 match (i.e. PCOUT -> PCIN cascade exists)
 | |
| //     if (a) this DSP48E1 does not already have an ACOUT -> ACIN cascade,
 | |
| //     (b) the previous DSP does  not already use its ACOUT port, then
 | |
| //     examine if an ACOUT -> ACIN cascade  opportunity exists if
 | |
| //     (i) A ports are identical, or (ii) separated by a
 | |
| //     $dff-with-optional-clock-enable-or-reset and checking that the 'D' input
 | |
| //     of this register is the same as the 'A' input of the previous DSP
 | |
| //     TODO: Check for two levels of flops, instead of just one
 | |
| code argQ clock AREG
 | |
| 	AREG = -1;
 | |
| 	if (next && next->type.in(\DSP48E1)) {
 | |
| 		Cell *prev = std::get<0>(chain.back());
 | |
| 
 | |
| 		if (param(next, \A_INPUT).decode_string() == "DIRECT" &&
 | |
| 				port(next, \ACIN, SigSpec()).is_fully_zero() &&
 | |
| 				nusers(port(prev, \ACOUT, SigSpec())) <= 1) {
 | |
| 			if (param(prev, \AREG) == 0) {
 | |
| 				if (port(prev, \A) == port(next, \A))
 | |
| 					AREG = 0;
 | |
| 			}
 | |
| 			else {
 | |
| 				argQ = unextend(port(next, \A));
 | |
| 				clock = port(prev, \CLK);
 | |
| 				subpattern(in_dffe);
 | |
| 				if (dff) {
 | |
| 					if (!dff->type.in($sdff, $sdffe) && port(prev, \RSTA, State::S0) != State::S0)
 | |
| 						goto reject_AREG;
 | |
| 					if (dff->type.in($sdff, $sdffe) && (port(dff, \SRST) != port(prev, \RSTA, State::S0) || !param(dff, \SRST_POLARITY).as_bool()))
 | |
| 						goto reject_AREG;
 | |
| 					IdString CEA;
 | |
| 					if (param(prev, \AREG) == 1)
 | |
| 						CEA = \CEA2;
 | |
| 					else if (param(prev, \AREG) == 2)
 | |
| 						CEA = \CEA1;
 | |
| 					else log_abort();
 | |
| 					if (!dff->type.in($dffe, $sdffe) && port(prev, CEA, State::S0) != State::S1)
 | |
| 						goto reject_AREG;
 | |
| 					if (dff->type.in($dffe, $sdffe) && (port(dff, \EN) != port(prev, CEA, State::S0) || !param(dff, \EN_POLARITY).as_bool()))
 | |
| 						goto reject_AREG;
 | |
| 					if (dffD == unextend(port(prev, \A)))
 | |
| 						AREG = 1;
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| reject_AREG:	;
 | |
| 	}
 | |
| endcode
 | |
| 
 | |
| // (4) Same as (3) but for BCOUT -> BCIN cascade
 | |
| code argQ clock BREG
 | |
| 	BREG = -1;
 | |
| 	if (next) {
 | |
| 		Cell *prev = std::get<0>(chain.back());
 | |
| 		if ((next->type != \DSP48E1 || param(next, \B_INPUT).decode_string() == "DIRECT") &&
 | |
| 				port(next, \BCIN, SigSpec()).is_fully_zero() &&
 | |
| 				nusers(port(prev, \BCOUT, SigSpec())) <= 1) {
 | |
| 			if ((next->type.in(\DSP48A, \DSP48A1) && param(prev, \B0REG) == 0 && param(prev, \B1REG) == 0) ||
 | |
| 				(next->type.in(\DSP48E1) && param(prev, \BREG) == 0)) {
 | |
| 				if (port(prev, \B) == port(next, \B))
 | |
| 					BREG = 0;
 | |
| 			}
 | |
| 			else {
 | |
| 				argQ = unextend(port(next, \B));
 | |
| 				clock = port(prev, \CLK);
 | |
| 				subpattern(in_dffe);
 | |
| 				if (dff) {
 | |
| 					if (!dff->type.in($sdff, $sdffe) && port(prev, \RSTB, State::S0) != State::S0)
 | |
| 						goto reject_BREG;
 | |
| 					if (dff->type.in($sdff, $sdffe) && (port(dff, \SRST) != port(prev, \RSTB, State::S0) || !param(dff, \SRST_POLARITY).as_bool()))
 | |
| 						goto reject_BREG;
 | |
| 					IdString CEB;
 | |
| 					if (next->type.in(\DSP48A, \DSP48A1))
 | |
| 						CEB = \CEB;
 | |
| 					else if (next->type.in(\DSP48E1)) {
 | |
| 						if (param(prev, \BREG) == 1)
 | |
| 							CEB = \CEB2;
 | |
| 						else if (param(prev, \BREG) == 2)
 | |
| 							CEB = \CEB1;
 | |
| 						else log_abort();
 | |
| 					}
 | |
| 					else log_abort();
 | |
| 					if (!dff->type.in($dffe, $sdffe) && port(prev, CEB, State::S0) != State::S1)
 | |
| 						goto reject_BREG;
 | |
| 					if (dff->type.in($dffe, $sdffe) && (port(dff, \EN) != port(prev, CEB, State::S0) || !param(dff, \EN_POLARITY).as_bool()))
 | |
| 						goto reject_BREG;
 | |
| 					if (dffD == unextend(port(prev, \B))) {
 | |
| 						if (next->type.in(\DSP48A, \DSP48A1) && param(prev, \B0REG) != 0)
 | |
| 							goto reject_BREG;
 | |
| 						BREG = 1;
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| reject_BREG:	;
 | |
| 	}
 | |
| endcode
 | |
| 
 | |
| // (5) Recursively go to (2.1) until no more matches possible, recording the
 | |
| //     longest possible chain
 | |
| code
 | |
| 	if (next) {
 | |
| 		chain.emplace_back(next, nextP_shift17 ? 17 : nextP ? 0 : -1, AREG, BREG);
 | |
| 
 | |
| 		SigSpec sigC = unextend(port(next, \C));
 | |
| 
 | |
| 		if (nextP_shift17) {
 | |
| 			if (GetSize(sigC)+17 <= GetSize(port(std::get<0>(chain.back()), \P)) &&
 | |
| 					port(std::get<0>(chain.back()), \P).extract(17, GetSize(sigC)) != sigC)
 | |
| 				subpattern(tail);
 | |
| 		}
 | |
| 		else {
 | |
| 			if (GetSize(sigC) <= GetSize(port(std::get<0>(chain.back()), \P)) &&
 | |
| 					port(std::get<0>(chain.back()), \P).extract(0, GetSize(sigC)) != sigC)
 | |
| 				subpattern(tail);
 | |
| 
 | |
| 		}
 | |
| 	} else {
 | |
| 		if (GetSize(chain) > GetSize(longest_chain))
 | |
| 			longest_chain = chain;
 | |
| 	}
 | |
| finally
 | |
| 	if (next)
 | |
| 		chain.pop_back();
 | |
| endcode
 | |
| 
 | |
| // #######################
 | |
| 
 | |
| // Subpattern for matching against input registers, based on knowledge of the
 | |
| //   'Q' input.
 | |
| subpattern in_dffe
 | |
| arg argQ clock
 | |
| 
 | |
| code
 | |
| 	dff = nullptr;
 | |
| 	if (argQ.empty())
 | |
| 		reject;
 | |
| 	for (const auto &c : argQ.chunks()) {
 | |
| 		// Abandon matches when 'Q' is a constant
 | |
| 		if (!c.wire)
 | |
| 			reject;
 | |
| 		// Abandon matches when 'Q' has the keep attribute set
 | |
| 		if (c.wire->get_bool_attribute(\keep))
 | |
| 			reject;
 | |
| 		// Abandon matches when 'Q' has a non-zero init attribute set
 | |
| 		// (not supported by DSP48E1)
 | |
| 		Const init = c.wire->attributes.at(\init, Const());
 | |
| 		if (!init.empty())
 | |
| 			for (auto b : init.extract(c.offset, c.width))
 | |
| 				if (b != State::Sx && b != State::S0)
 | |
| 					reject;
 | |
| 	}
 | |
| endcode
 | |
| 
 | |
| match ff
 | |
| 	select ff->type.in($dff, $dffe, $sdff, $sdffe)
 | |
| 	// DSP48E1 does not support clock inversion
 | |
| 	select param(ff, \CLK_POLARITY).as_bool()
 | |
| 
 | |
| 	// Check that reset value, if present, is fully 0.
 | |
| 	filter ff->type.in($dff, $dffe) || param(ff, \SRST_VALUE).is_fully_zero()
 | |
| 
 | |
| 	slice offset GetSize(port(ff, \D))
 | |
| 	index <SigBit> port(ff, \Q)[offset] === argQ[0]
 | |
| 
 | |
| 	// Check that the rest of argQ is present
 | |
| 	filter GetSize(port(ff, \Q)) >= offset + GetSize(argQ)
 | |
| 	filter port(ff, \Q).extract(offset, GetSize(argQ)) == argQ
 | |
| 
 | |
| 	filter clock == SigBit() || port(ff, \CLK)[0] == clock
 | |
| endmatch
 | |
| 
 | |
| code argQ
 | |
| 	SigSpec Q = port(ff, \Q);
 | |
| 	dff = ff;
 | |
| 	dffclock = port(ff, \CLK);
 | |
| 	dffD = argQ;
 | |
| 	SigSpec D = port(ff, \D);
 | |
| 	argQ = Q;
 | |
| 	dffD.replace(argQ, D);
 | |
| endcode
 |