mirror of
				https://github.com/YosysHQ/yosys
				synced 2025-10-31 03:32:29 +00:00 
			
		
		
		
	Merge pull request #1359 from YosysHQ/xc7dsp
DSP inference for Xilinx (improved for ice40, initial support for ecp5)
This commit is contained in:
		
						commit
						8474c5b366
					
				
					 44 changed files with 6247 additions and 294 deletions
				
			
		|  | @ -43,6 +43,12 @@ Yosys 0.9 .. Yosys 0.9-dev | ||||||
|     - Added "-match-init" option to "dff2dffs" pass |     - Added "-match-init" option to "dff2dffs" pass | ||||||
|     - Added "techmap_autopurge" support to techmap |     - Added "techmap_autopurge" support to techmap | ||||||
|     - Added "add -mod <modname[s]>" |     - Added "add -mod <modname[s]>" | ||||||
|  |     - Added +/mul2dsp.v for decomposing wide multipliers to custom-sized ones | ||||||
|  |     - Added "ice40_dsp" for Lattice iCE40 DSP packing | ||||||
|  |     - Added "xilinx_dsp" for Xilinx DSP packing | ||||||
|  |     - "synth_xilinx" to now infer DSP blocks (-nodsp to disable) | ||||||
|  |     - "synth_ecp5" to now infer DSP blocks (-nodsp to disable, experimental) | ||||||
|  |     - "synth_ice40 -dsp" to infer DSP blocks | ||||||
| 
 | 
 | ||||||
| Yosys 0.8 .. Yosys 0.9 | Yosys 0.8 .. Yosys 0.9 | ||||||
| ---------------------- | ---------------------- | ||||||
|  |  | ||||||
|  | @ -350,6 +350,8 @@ struct XAigerWriter | ||||||
| 				if (!box_module || !box_module->attributes.count("\\abc_box_id")) | 				if (!box_module || !box_module->attributes.count("\\abc_box_id")) | ||||||
| 					continue; | 					continue; | ||||||
| 
 | 
 | ||||||
|  | 				bool blackbox = box_module->get_blackbox_attribute(true /* ignore_wb */); | ||||||
|  | 
 | ||||||
| 				// Fully pad all unused input connections of this box cell with S0
 | 				// Fully pad all unused input connections of this box cell with S0
 | ||||||
| 				// Fully pad all undriven output connections of this box cell with anonymous wires
 | 				// Fully pad all undriven output connections of this box cell with anonymous wires
 | ||||||
| 				// NB: Assume box_module->ports are sorted alphabetically
 | 				// NB: Assume box_module->ports are sorted alphabetically
 | ||||||
|  | @ -394,7 +396,10 @@ struct XAigerWriter | ||||||
| 							rhs = it->second; | 							rhs = it->second; | ||||||
| 						} | 						} | ||||||
| 						else { | 						else { | ||||||
| 							rhs = module->addWire(NEW_ID, GetSize(w)); | 							Wire *wire = module->addWire(NEW_ID, GetSize(w)); | ||||||
|  | 							if (blackbox) | ||||||
|  | 								wire->set_bool_attribute(ID(abc_padding)); | ||||||
|  | 							rhs = wire; | ||||||
| 							cell->setPort(port_name, rhs); | 							cell->setPort(port_name, rhs); | ||||||
| 						} | 						} | ||||||
| 
 | 
 | ||||||
|  | @ -405,15 +410,10 @@ struct XAigerWriter | ||||||
| 							if (O != b) | 							if (O != b) | ||||||
| 								alias_map[O] = b; | 								alias_map[O] = b; | ||||||
| 							undriven_bits.erase(O); | 							undriven_bits.erase(O); | ||||||
| 
 |  | ||||||
| 							auto jt = input_bits.find(b); |  | ||||||
| 							if (jt != input_bits.end()) { |  | ||||||
| 								log_assert(keep_bits.count(O)); |  | ||||||
| 							input_bits.erase(b); | 							input_bits.erase(b); | ||||||
| 						} | 						} | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 				} |  | ||||||
| 				box_list.emplace_back(cell); | 				box_list.emplace_back(cell); | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
|  | @ -429,7 +429,7 @@ struct XAigerWriter | ||||||
| 			// inherit existing inout's drivers
 | 			// inherit existing inout's drivers
 | ||||||
| 			if ((wire->port_input && wire->port_output && !undriven_bits.count(bit)) | 			if ((wire->port_input && wire->port_output && !undriven_bits.count(bit)) | ||||||
| 					|| keep_bits.count(bit)) { | 					|| keep_bits.count(bit)) { | ||||||
| 				RTLIL::IdString wire_name = wire->name.str() + "$inout.out"; | 				RTLIL::IdString wire_name = stringf("$%s$inout.out", wire->name.c_str()); | ||||||
| 				RTLIL::Wire *new_wire = module->wire(wire_name); | 				RTLIL::Wire *new_wire = module->wire(wire_name); | ||||||
| 				if (!new_wire) | 				if (!new_wire) | ||||||
| 					new_wire = module->addWire(wire_name, GetSize(wire)); | 					new_wire = module->addWire(wire_name, GetSize(wire)); | ||||||
|  |  | ||||||
|  | @ -868,7 +868,7 @@ void AigerReader::post_process() | ||||||
| 					if (!existing) { | 					if (!existing) { | ||||||
| 						if (escaped_s.ends_with("$inout.out")) { | 						if (escaped_s.ends_with("$inout.out")) { | ||||||
| 							wire->port_output = false; | 							wire->port_output = false; | ||||||
| 							RTLIL::Wire *in_wire = module->wire(escaped_s.substr(0, escaped_s.size()-10)); | 							RTLIL::Wire *in_wire = module->wire(escaped_s.substr(1, escaped_s.size()-11)); | ||||||
| 							log_assert(in_wire); | 							log_assert(in_wire); | ||||||
| 							log_assert(in_wire->port_input && !in_wire->port_output); | 							log_assert(in_wire->port_input && !in_wire->port_output); | ||||||
| 							in_wire->port_output = true; | 							in_wire->port_output = true; | ||||||
|  | @ -889,7 +889,7 @@ void AigerReader::post_process() | ||||||
| 					if (!existing) { | 					if (!existing) { | ||||||
| 						if (escaped_s.ends_with("$inout.out")) { | 						if (escaped_s.ends_with("$inout.out")) { | ||||||
| 							wire->port_output = false; | 							wire->port_output = false; | ||||||
| 							RTLIL::Wire *in_wire = module->wire(stringf("%s[%d]", escaped_s.substr(0, escaped_s.size()-10).c_str(), index)); | 							RTLIL::Wire *in_wire = module->wire(stringf("%s[%d]", escaped_s.substr(1, escaped_s.size()-11).c_str(), index)); | ||||||
| 							log_assert(in_wire); | 							log_assert(in_wire); | ||||||
| 							log_assert(in_wire->port_input && !in_wire->port_output); | 							log_assert(in_wire->port_input && !in_wire->port_output); | ||||||
| 							in_wire->port_output = true; | 							in_wire->port_output = true; | ||||||
|  |  | ||||||
|  | @ -21,6 +21,14 @@ $(eval $(call add_extra_objs,passes/pmgen/ice40_wrapcarry_pm.h)) | ||||||
| 
 | 
 | ||||||
| # --------------------------------------
 | # --------------------------------------
 | ||||||
| 
 | 
 | ||||||
|  | OBJS += passes/pmgen/xilinx_dsp.o | ||||||
|  | passes/pmgen/xilinx_dsp.o: passes/pmgen/xilinx_dsp_pm.h passes/pmgen/xilinx_dsp_CREG_pm.h passes/pmgen/xilinx_dsp_cascade_pm.h | ||||||
|  | $(eval $(call add_extra_objs,passes/pmgen/xilinx_dsp_pm.h)) | ||||||
|  | $(eval $(call add_extra_objs,passes/pmgen/xilinx_dsp_CREG_pm.h)) | ||||||
|  | $(eval $(call add_extra_objs,passes/pmgen/xilinx_dsp_cascade_pm.h)) | ||||||
|  | 
 | ||||||
|  | # --------------------------------------
 | ||||||
|  | 
 | ||||||
| OBJS += passes/pmgen/peepopt.o | OBJS += passes/pmgen/peepopt.o | ||||||
| passes/pmgen/peepopt.o: passes/pmgen/peepopt_pm.h | passes/pmgen/peepopt.o: passes/pmgen/peepopt_pm.h | ||||||
| $(eval $(call add_extra_objs,passes/pmgen/peepopt_pm.h)) | $(eval $(call add_extra_objs,passes/pmgen/peepopt_pm.h)) | ||||||
|  |  | ||||||
|  | @ -29,19 +29,19 @@ void create_ice40_dsp(ice40_dsp_pm &pm) | ||||||
| { | { | ||||||
| 	auto &st = pm.st_ice40_dsp; | 	auto &st = pm.st_ice40_dsp; | ||||||
| 
 | 
 | ||||||
| #if 0 |  | ||||||
| 	log("\n"); |  | ||||||
| 	log("ffA:   %s\n", log_id(st.ffA, "--")); |  | ||||||
| 	log("ffB:   %s\n", log_id(st.ffB, "--")); |  | ||||||
| 	log("mul:   %s\n", log_id(st.mul, "--")); |  | ||||||
| 	log("ffY:   %s\n", log_id(st.ffY, "--")); |  | ||||||
| 	log("addAB: %s\n", log_id(st.addAB, "--")); |  | ||||||
| 	log("muxAB: %s\n", log_id(st.muxAB, "--")); |  | ||||||
| 	log("ffS:   %s\n", log_id(st.ffS, "--")); |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
| 	log("Checking %s.%s for iCE40 DSP inference.\n", log_id(pm.module), log_id(st.mul)); | 	log("Checking %s.%s for iCE40 DSP inference.\n", log_id(pm.module), log_id(st.mul)); | ||||||
| 
 | 
 | ||||||
|  | 	log_debug("ffA:    %s %s %s\n", log_id(st.ffA, "--"), log_id(st.ffAholdmux, "--"), log_id(st.ffArstmux, "--")); | ||||||
|  | 	log_debug("ffB:    %s %s %s\n", log_id(st.ffB, "--"), log_id(st.ffBholdmux, "--"), log_id(st.ffBrstmux, "--")); | ||||||
|  | 	log_debug("ffCD:   %s %s\n", log_id(st.ffCD, "--"), log_id(st.ffCDholdmux, "--")); | ||||||
|  | 	log_debug("mul:    %s\n", log_id(st.mul, "--")); | ||||||
|  | 	log_debug("ffFJKG: %s\n", log_id(st.ffFJKG, "--")); | ||||||
|  | 	log_debug("ffH:    %s\n", log_id(st.ffH, "--")); | ||||||
|  | 	log_debug("add:    %s\n", log_id(st.add, "--")); | ||||||
|  | 	log_debug("mux:    %s\n", log_id(st.mux, "--")); | ||||||
|  | 	log_debug("ffO:    %s %s %s\n", log_id(st.ffO, "--"), log_id(st.ffOholdmux, "--"), log_id(st.ffOrstmux, "--")); | ||||||
|  | 	log_debug("\n"); | ||||||
|  | 
 | ||||||
| 	if (GetSize(st.sigA) > 16) { | 	if (GetSize(st.sigA) > 16) { | ||||||
| 		log("  input A (%s) is too large (%d > 16).\n", log_signal(st.sigA), GetSize(st.sigA)); | 		log("  input A (%s) is too large (%d > 16).\n", log_signal(st.sigA), GetSize(st.sigA)); | ||||||
| 		return; | 		return; | ||||||
|  | @ -52,59 +52,85 @@ void create_ice40_dsp(ice40_dsp_pm &pm) | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if (GetSize(st.sigS) > 32) { | 	if (GetSize(st.sigO) > 33) { | ||||||
| 		log("  accumulator (%s) is too large (%d > 32).\n", log_signal(st.sigS), GetSize(st.sigS)); | 		log("  adder/accumulator (%s) is too large (%d > 33).\n", log_signal(st.sigO), GetSize(st.sigO)); | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if (GetSize(st.sigY) > 32) { | 	if (GetSize(st.sigH) > 32) { | ||||||
| 		log("  output (%s) is too large (%d > 32).\n", log_signal(st.sigY), GetSize(st.sigY)); | 		log("  output (%s) is too large (%d > 32).\n", log_signal(st.sigH), GetSize(st.sigH)); | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	bool mul_signed = st.mul->getParam("\\A_SIGNED").as_bool(); | 	Cell *cell = st.mul; | ||||||
|  | 	if (cell->type == ID($mul)) { | ||||||
|  | 		log("  replacing %s with SB_MAC16 cell.\n", log_id(st.mul->type)); | ||||||
| 
 | 
 | ||||||
| 	log("  replacing $mul with SB_MAC16 cell.\n"); | 		cell = pm.module->addCell(NEW_ID, ID(SB_MAC16)); | ||||||
| 
 |  | ||||||
| 	Cell *cell = pm.module->addCell(NEW_ID, "\\SB_MAC16"); |  | ||||||
| 		pm.module->swap_names(cell, st.mul); | 		pm.module->swap_names(cell, st.mul); | ||||||
|  | 	} | ||||||
|  | 	else log_assert(cell->type == ID(SB_MAC16)); | ||||||
| 
 | 
 | ||||||
| 	// SB_MAC16 Input Interface
 | 	// SB_MAC16 Input Interface
 | ||||||
| 
 |  | ||||||
| 	SigSpec A = st.sigA; | 	SigSpec A = st.sigA; | ||||||
| 	A.extend_u0(16, mul_signed); | 	A.extend_u0(16, st.mul->getParam(ID(A_SIGNED)).as_bool()); | ||||||
|  | 	log_assert(GetSize(A) == 16); | ||||||
| 
 | 
 | ||||||
| 	SigSpec B = st.sigB; | 	SigSpec B = st.sigB; | ||||||
| 	B.extend_u0(16, mul_signed); | 	B.extend_u0(16, st.mul->getParam(ID(B_SIGNED)).as_bool()); | ||||||
|  | 	log_assert(GetSize(B) == 16); | ||||||
| 
 | 
 | ||||||
| 	SigSpec CD; | 	SigSpec CD = st.sigCD; | ||||||
| 	if (st.muxA) | 	if (CD.empty()) | ||||||
| 		CD = st.muxA->getPort("\\B"); | 		CD = RTLIL::Const(0, 32); | ||||||
| 	if (st.muxB) | 	else | ||||||
| 		CD = st.muxB->getPort("\\A"); | 		log_assert(GetSize(CD) == 32); | ||||||
| 	CD.extend_u0(32, mul_signed); |  | ||||||
| 
 | 
 | ||||||
| 	cell->setPort("\\A", A); | 	cell->setPort(ID::A, A); | ||||||
| 	cell->setPort("\\B", B); | 	cell->setPort(ID::B, B); | ||||||
| 	cell->setPort("\\C", CD.extract(0, 16)); | 	cell->setPort(ID(C), CD.extract(16, 16)); | ||||||
| 	cell->setPort("\\D", CD.extract(16, 16)); | 	cell->setPort(ID(D), CD.extract(0, 16)); | ||||||
| 
 | 
 | ||||||
| 	cell->setParam("\\A_REG", st.ffA ? State::S1 : State::S0); | 	cell->setParam(ID(A_REG), st.ffA ? State::S1 : State::S0); | ||||||
| 	cell->setParam("\\B_REG", st.ffB ? State::S1 : State::S0); | 	cell->setParam(ID(B_REG), st.ffB ? State::S1 : State::S0); | ||||||
|  | 	cell->setParam(ID(C_REG), st.ffCD ? State::S1 : State::S0); | ||||||
|  | 	cell->setParam(ID(D_REG), st.ffCD ? State::S1 : State::S0); | ||||||
| 
 | 
 | ||||||
| 	cell->setPort("\\AHOLD", State::S0); | 	SigSpec AHOLD, BHOLD, CDHOLD; | ||||||
| 	cell->setPort("\\BHOLD", State::S0); | 	if (st.ffAholdmux) | ||||||
| 	cell->setPort("\\CHOLD", State::S0); | 		AHOLD = st.ffAholdpol ? st.ffAholdmux->getPort(ID(S)) : pm.module->Not(NEW_ID, st.ffAholdmux->getPort(ID(S))); | ||||||
| 	cell->setPort("\\DHOLD", State::S0); | 	else | ||||||
|  | 		AHOLD = State::S0; | ||||||
|  | 	if (st.ffBholdmux) | ||||||
|  | 		BHOLD = st.ffBholdpol ? st.ffBholdmux->getPort(ID(S)) : pm.module->Not(NEW_ID, st.ffBholdmux->getPort(ID(S))); | ||||||
|  | 	else | ||||||
|  | 		BHOLD = State::S0; | ||||||
|  | 	if (st.ffCDholdmux) | ||||||
|  | 		CDHOLD = st.ffCDholdpol ? st.ffCDholdmux->getPort(ID(S)) : pm.module->Not(NEW_ID, st.ffCDholdmux->getPort(ID(S))); | ||||||
|  | 	else | ||||||
|  | 		CDHOLD = State::S0; | ||||||
|  | 	cell->setPort(ID(AHOLD), AHOLD); | ||||||
|  | 	cell->setPort(ID(BHOLD), BHOLD); | ||||||
|  | 	cell->setPort(ID(CHOLD), CDHOLD); | ||||||
|  | 	cell->setPort(ID(DHOLD), CDHOLD); | ||||||
| 
 | 
 | ||||||
| 	cell->setPort("\\IRSTTOP", State::S0); | 	SigSpec IRSTTOP, IRSTBOT; | ||||||
| 	cell->setPort("\\IRSTBOT", State::S0); | 	if (st.ffArstmux) | ||||||
|  | 		IRSTTOP = st.ffArstpol ? st.ffArstmux->getPort(ID(S)) : pm.module->Not(NEW_ID, st.ffArstmux->getPort(ID(S))); | ||||||
|  | 	else | ||||||
|  | 		IRSTTOP = State::S0; | ||||||
|  | 	if (st.ffBrstmux) | ||||||
|  | 		IRSTBOT = st.ffBrstpol ? st.ffBrstmux->getPort(ID(S)) : pm.module->Not(NEW_ID, st.ffBrstmux->getPort(ID(S))); | ||||||
|  | 	else | ||||||
|  | 		IRSTBOT = State::S0; | ||||||
|  | 	cell->setPort(ID(IRSTTOP), IRSTTOP); | ||||||
|  | 	cell->setPort(ID(IRSTBOT), IRSTBOT); | ||||||
| 
 | 
 | ||||||
| 	if (st.clock_vld) | 	if (st.clock != SigBit()) | ||||||
| 	{ | 	{ | ||||||
| 		cell->setPort("\\CLK", st.clock); | 		cell->setPort(ID(CLK), st.clock); | ||||||
| 		cell->setPort("\\CE", State::S1); | 		cell->setPort(ID(CE), State::S1); | ||||||
| 		cell->setParam("\\NEG_TRIGGER", st.clock_pol ? State::S0 : State::S1); | 		cell->setParam(ID(NEG_TRIGGER), st.clock_pol ? State::S0 : State::S1); | ||||||
| 
 | 
 | ||||||
| 		log("  clock: %s (%s)", log_signal(st.clock), st.clock_pol ? "posedge" : "negedge"); | 		log("  clock: %s (%s)", log_signal(st.clock), st.clock_pol ? "posedge" : "negedge"); | ||||||
| 
 | 
 | ||||||
|  | @ -114,91 +140,137 @@ void create_ice40_dsp(ice40_dsp_pm &pm) | ||||||
| 		if (st.ffB) | 		if (st.ffB) | ||||||
| 			log(" ffB:%s", log_id(st.ffB)); | 			log(" ffB:%s", log_id(st.ffB)); | ||||||
| 
 | 
 | ||||||
| 		if (st.ffY) | 		if (st.ffCD) | ||||||
| 			log(" ffY:%s", log_id(st.ffY)); | 			log(" ffCD:%s", log_id(st.ffCD)); | ||||||
| 
 | 
 | ||||||
| 		if (st.ffS) | 		if (st.ffFJKG) | ||||||
| 			log(" ffS:%s", log_id(st.ffS)); | 			log(" ffFJKG:%s", log_id(st.ffFJKG)); | ||||||
|  | 
 | ||||||
|  | 		if (st.ffH) | ||||||
|  | 			log(" ffH:%s", log_id(st.ffH)); | ||||||
|  | 
 | ||||||
|  | 		if (st.ffO) | ||||||
|  | 			log(" ffO:%s", log_id(st.ffO)); | ||||||
| 
 | 
 | ||||||
| 		log("\n"); | 		log("\n"); | ||||||
| 	} | 	} | ||||||
| 	else | 	else | ||||||
| 	{ | 	{ | ||||||
| 		cell->setPort("\\CLK", State::S0); | 		cell->setPort(ID(CLK), State::S0); | ||||||
| 		cell->setPort("\\CE", State::S0); | 		cell->setPort(ID(CE), State::S0); | ||||||
| 		cell->setParam("\\NEG_TRIGGER", State::S0); | 		cell->setParam(ID(NEG_TRIGGER), State::S0); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// SB_MAC16 Cascade Interface
 | 	// SB_MAC16 Cascade Interface
 | ||||||
| 
 | 
 | ||||||
| 	cell->setPort("\\SIGNEXTIN", State::Sx); | 	cell->setPort(ID(SIGNEXTIN), State::Sx); | ||||||
| 	cell->setPort("\\SIGNEXTOUT", pm.module->addWire(NEW_ID)); | 	cell->setPort(ID(SIGNEXTOUT), pm.module->addWire(NEW_ID)); | ||||||
| 
 | 
 | ||||||
| 	cell->setPort("\\CI", State::Sx); | 	cell->setPort(ID(CI), State::Sx); | ||||||
| 	cell->setPort("\\CO", pm.module->addWire(NEW_ID)); |  | ||||||
| 
 | 
 | ||||||
| 	cell->setPort("\\ACCUMCI", State::Sx); | 	cell->setPort(ID(ACCUMCI), State::Sx); | ||||||
| 	cell->setPort("\\ACCUMCO", pm.module->addWire(NEW_ID)); | 	cell->setPort(ID(ACCUMCO), pm.module->addWire(NEW_ID)); | ||||||
| 
 | 
 | ||||||
| 	// SB_MAC16 Output Interface
 | 	// SB_MAC16 Output Interface
 | ||||||
| 
 | 
 | ||||||
| 	SigSpec O = st.ffS ? st.sigS : st.sigY; | 	SigSpec O = st.sigO; | ||||||
|  | 	int O_width = GetSize(O); | ||||||
|  | 	if (O_width == 33) { | ||||||
|  | 		log_assert(st.add); | ||||||
|  | 		// If we have a signed multiply-add, then perform sign extension
 | ||||||
|  | 		if (st.add->getParam(ID(A_SIGNED)).as_bool() && st.add->getParam(ID(B_SIGNED)).as_bool()) | ||||||
|  | 			pm.module->connect(O[32], O[31]); | ||||||
|  | 		else | ||||||
|  | 			cell->setPort(ID(CO), O[32]); | ||||||
|  | 		O.remove(O_width-1); | ||||||
|  | 	} | ||||||
|  | 	else | ||||||
|  | 		cell->setPort(ID(CO), pm.module->addWire(NEW_ID)); | ||||||
|  | 	log_assert(GetSize(O) <= 32); | ||||||
| 	if (GetSize(O) < 32) | 	if (GetSize(O) < 32) | ||||||
| 		O.append(pm.module->addWire(NEW_ID, 32-GetSize(O))); | 		O.append(pm.module->addWire(NEW_ID, 32-GetSize(O))); | ||||||
| 
 | 
 | ||||||
| 	cell->setPort("\\O", O); | 	cell->setPort(ID(O), O); | ||||||
| 
 | 
 | ||||||
| 	if (st.addAB) { | 	bool accum = false; | ||||||
| 		log("  accumulator %s (%s)\n", log_id(st.addAB), log_id(st.addAB->type)); | 	if (st.add) { | ||||||
| 		cell->setPort("\\ADDSUBTOP", st.addAB->type == "$add" ? State::S0 : State::S1); | 		accum = (st.ffO && st.add->getPort(st.addAB == ID::A ? ID::B : ID::A) == st.sigO); | ||||||
| 		cell->setPort("\\ADDSUBBOT", st.addAB->type == "$add" ? State::S0 : State::S1); | 		if (accum) | ||||||
|  | 			log("  accumulator %s (%s)\n", log_id(st.add), log_id(st.add->type)); | ||||||
|  | 		else | ||||||
|  | 			log("  adder %s (%s)\n", log_id(st.add), log_id(st.add->type)); | ||||||
|  | 		cell->setPort(ID(ADDSUBTOP), st.add->type == ID($add) ? State::S0 : State::S1); | ||||||
|  | 		cell->setPort(ID(ADDSUBBOT), st.add->type == ID($add) ? State::S0 : State::S1); | ||||||
| 	} else { | 	} else { | ||||||
| 		cell->setPort("\\ADDSUBTOP", State::S0); | 		cell->setPort(ID(ADDSUBTOP), State::S0); | ||||||
| 		cell->setPort("\\ADDSUBBOT", State::S0); | 		cell->setPort(ID(ADDSUBBOT), State::S0); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	cell->setPort("\\ORSTTOP", State::S0); | 	SigSpec OHOLD; | ||||||
| 	cell->setPort("\\ORSTBOT", State::S0); | 	if (st.ffOholdmux) | ||||||
|  | 		OHOLD = st.ffOholdpol ? st.ffOholdmux->getPort(ID(S)) : pm.module->Not(NEW_ID, st.ffOholdmux->getPort(ID(S))); | ||||||
|  | 	else | ||||||
|  | 		OHOLD = State::S0; | ||||||
|  | 	cell->setPort(ID(OHOLDTOP), OHOLD); | ||||||
|  | 	cell->setPort(ID(OHOLDBOT), OHOLD); | ||||||
| 
 | 
 | ||||||
| 	cell->setPort("\\OHOLDTOP", State::S0); | 	SigSpec ORST; | ||||||
| 	cell->setPort("\\OHOLDBOT", State::S0); | 	if (st.ffOrstmux) | ||||||
|  | 		ORST = st.ffOrstpol ? st.ffOrstmux->getPort(ID(S)) : pm.module->Not(NEW_ID, st.ffOrstmux->getPort(ID(S))); | ||||||
|  | 	else | ||||||
|  | 		ORST = State::S0; | ||||||
|  | 	cell->setPort(ID(ORSTTOP), ORST); | ||||||
|  | 	cell->setPort(ID(ORSTBOT), ORST); | ||||||
| 
 | 
 | ||||||
| 	SigSpec acc_reset = State::S0; | 	SigSpec acc_reset = State::S0; | ||||||
| 	if (st.muxA) | 	if (st.mux) { | ||||||
| 		acc_reset = st.muxA->getPort("\\S"); | 		if (st.muxAB == ID::A) | ||||||
| 	if (st.muxB) | 			acc_reset = st.mux->getPort(ID(S)); | ||||||
| 		acc_reset = pm.module->Not(NEW_ID, st.muxB->getPort("\\S")); | 		else | ||||||
| 
 | 			acc_reset = pm.module->Not(NEW_ID, st.mux->getPort(ID(S))); | ||||||
| 	cell->setPort("\\OLOADTOP", acc_reset); | 	} | ||||||
| 	cell->setPort("\\OLOADBOT", acc_reset); | 	cell->setPort(ID(OLOADTOP), acc_reset); | ||||||
|  | 	cell->setPort(ID(OLOADBOT), acc_reset); | ||||||
| 
 | 
 | ||||||
| 	// SB_MAC16 Remaining Parameters
 | 	// SB_MAC16 Remaining Parameters
 | ||||||
| 
 | 
 | ||||||
| 	cell->setParam("\\C_REG", State::S0); | 	cell->setParam(ID(TOP_8x8_MULT_REG), st.ffFJKG ? State::S1 : State::S0); | ||||||
| 	cell->setParam("\\D_REG", State::S0); | 	cell->setParam(ID(BOT_8x8_MULT_REG), st.ffFJKG ? State::S1 : State::S0); | ||||||
|  | 	cell->setParam(ID(PIPELINE_16x16_MULT_REG1), st.ffFJKG ? State::S1 : State::S0); | ||||||
|  | 	cell->setParam(ID(PIPELINE_16x16_MULT_REG2), st.ffH ? State::S1 : State::S0); | ||||||
| 
 | 
 | ||||||
| 	cell->setParam("\\TOP_8x8_MULT_REG", st.ffY ? State::S1 : State::S0); | 	cell->setParam(ID(TOPADDSUB_LOWERINPUT), Const(2, 2)); | ||||||
| 	cell->setParam("\\BOT_8x8_MULT_REG", st.ffY ? State::S1 : State::S0); | 	cell->setParam(ID(TOPADDSUB_UPPERINPUT), accum ? State::S0 : State::S1); | ||||||
| 	cell->setParam("\\PIPELINE_16x16_MULT_REG1", st.ffY ? State::S1 : State::S0); | 	cell->setParam(ID(TOPADDSUB_CARRYSELECT), Const(3, 2)); | ||||||
| 	cell->setParam("\\PIPELINE_16x16_MULT_REG2", State::S0); |  | ||||||
| 
 | 
 | ||||||
| 	cell->setParam("\\TOPOUTPUT_SELECT", Const(st.ffS ? 1 : 3, 2)); | 	cell->setParam(ID(BOTADDSUB_LOWERINPUT), Const(2, 2)); | ||||||
| 	cell->setParam("\\TOPADDSUB_LOWERINPUT", Const(2, 2)); | 	cell->setParam(ID(BOTADDSUB_UPPERINPUT), accum ? State::S0 : State::S1); | ||||||
| 	cell->setParam("\\TOPADDSUB_UPPERINPUT", State::S0); | 	cell->setParam(ID(BOTADDSUB_CARRYSELECT), Const(0, 2)); | ||||||
| 	cell->setParam("\\TOPADDSUB_CARRYSELECT", Const(3, 2)); |  | ||||||
| 
 | 
 | ||||||
| 	cell->setParam("\\BOTOUTPUT_SELECT", Const(st.ffS ? 1 : 3, 2)); | 	cell->setParam(ID(MODE_8x8), State::S0); | ||||||
| 	cell->setParam("\\BOTADDSUB_LOWERINPUT", Const(2, 2)); | 	cell->setParam(ID(A_SIGNED), st.mul->getParam(ID(A_SIGNED)).as_bool()); | ||||||
| 	cell->setParam("\\BOTADDSUB_UPPERINPUT", State::S0); | 	cell->setParam(ID(B_SIGNED), st.mul->getParam(ID(B_SIGNED)).as_bool()); | ||||||
| 	cell->setParam("\\BOTADDSUB_CARRYSELECT", Const(0, 2)); |  | ||||||
| 
 | 
 | ||||||
| 	cell->setParam("\\MODE_8x8", State::S0); | 	if (st.ffO) { | ||||||
| 	cell->setParam("\\A_SIGNED", mul_signed ? State::S1 : State::S0); | 		if (st.o_lo) | ||||||
| 	cell->setParam("\\B_SIGNED", mul_signed ? State::S1 : State::S0); | 			cell->setParam(ID(TOPOUTPUT_SELECT), Const(st.add ? 0 : 3, 2)); | ||||||
|  | 		else | ||||||
|  | 			cell->setParam(ID(TOPOUTPUT_SELECT), Const(1, 2)); | ||||||
| 
 | 
 | ||||||
|  | 		st.ffO->connections_.at(ID(Q)).replace(O, pm.module->addWire(NEW_ID, GetSize(O))); | ||||||
|  | 		cell->setParam(ID(BOTOUTPUT_SELECT), Const(1, 2)); | ||||||
|  | 	} | ||||||
|  | 	else { | ||||||
|  | 		cell->setParam(ID(TOPOUTPUT_SELECT), Const(st.add ? 0 : 3, 2)); | ||||||
|  | 		cell->setParam(ID(BOTOUTPUT_SELECT), Const(st.add ? 0 : 3, 2)); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (cell != st.mul) | ||||||
| 		pm.autoremove(st.mul); | 		pm.autoremove(st.mul); | ||||||
| 	pm.autoremove(st.ffY); | 	else | ||||||
| 	pm.autoremove(st.ffS); | 		pm.blacklist(st.mul); | ||||||
|  | 	pm.autoremove(st.ffFJKG); | ||||||
|  | 	pm.autoremove(st.add); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| struct Ice40DspPass : public Pass { | struct Ice40DspPass : public Pass { | ||||||
|  | @ -209,7 +281,17 @@ struct Ice40DspPass : public Pass { | ||||||
| 		log("\n"); | 		log("\n"); | ||||||
| 		log("    ice40_dsp [options] [selection]\n"); | 		log("    ice40_dsp [options] [selection]\n"); | ||||||
| 		log("\n"); | 		log("\n"); | ||||||
| 		log("Map multipliers and multiply-accumulate blocks to iCE40 DSP resources.\n"); | 		log("Map multipliers ($mul/SB_MAC16) and multiply-accumulate ($mul/SB_MAC16 + $add)\n"); | ||||||
|  | 		log("cells into iCE40 DSP resources.\n"); | ||||||
|  | 		log("Currently, only the 16x16 multiply mode is supported and not the 2 x 8x8 mode.\n"); | ||||||
|  | 		log("\n"); | ||||||
|  | 		log("Pack input registers (A, B, {C,D}; with optional hold), pipeline registers\n"); | ||||||
|  | 		log("({F,J,K,G}, H), output registers (O -- full 32-bits or lower 16-bits only; with\n"); | ||||||
|  | 		log("optional hold), and post-adder into into the SB_MAC16 resource.\n"); | ||||||
|  | 		log("\n"); | ||||||
|  | 		log("Multiply-accumulate operations using the post-adder with feedback on the {C,D}\n"); | ||||||
|  | 		log("input will be folded into the DSP. In this scenario only, resetting the\n"); | ||||||
|  | 		log("the accumulator to an arbitrary value can be inferred to use the {C,D} input.\n"); | ||||||
| 		log("\n"); | 		log("\n"); | ||||||
| 	} | 	} | ||||||
| 	void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE | 	void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE | ||||||
|  |  | ||||||
|  | @ -1,163 +1,574 @@ | ||||||
| pattern ice40_dsp | pattern ice40_dsp | ||||||
| 
 | 
 | ||||||
| state <SigBit> clock | state <SigBit> clock | ||||||
| state <bool> clock_pol clock_vld | state <bool> clock_pol cd_signed o_lo | ||||||
| state <SigSpec> sigA sigB sigY sigS | state <SigSpec> sigA sigB sigCD sigH sigO | ||||||
| state <Cell*> addAB muxAB | state <Cell*> add mux | ||||||
|  | state <IdString> addAB muxAB | ||||||
|  | 
 | ||||||
|  | state <bool> ffAholdpol ffBholdpol ffCDholdpol ffOholdpol | ||||||
|  | state <bool> ffArstpol ffBrstpol ffCDrstpol ffOrstpol | ||||||
|  | 
 | ||||||
|  | state <Cell*> ffA ffAholdmux ffArstmux ffB ffBholdmux ffBrstmux ffCD ffCDholdmux | ||||||
|  | state <Cell*> ffFJKG ffH ffO ffOholdmux ffOrstmux | ||||||
|  | 
 | ||||||
|  | // subpattern | ||||||
|  | state <SigSpec> argQ argD | ||||||
|  | state <bool> ffholdpol ffrstpol | ||||||
|  | state <int> ffoffset | ||||||
|  | udata <SigSpec> dffD dffQ | ||||||
|  | udata <SigBit> dffclock | ||||||
|  | udata <Cell*> dff dffholdmux dffrstmux | ||||||
|  | udata <bool> dffholdpol dffrstpol dffclock_pol | ||||||
| 
 | 
 | ||||||
| match mul | match mul | ||||||
| 	select mul->type.in($mul) | 	select mul->type.in($mul, \SB_MAC16) | ||||||
| 	select GetSize(mul->getPort(\A)) + GetSize(mul->getPort(\B)) > 10 | 	select GetSize(mul->getPort(\A)) + GetSize(mul->getPort(\B)) > 10 | ||||||
| 	select GetSize(mul->getPort(\Y)) > 10 |  | ||||||
| endmatch | endmatch | ||||||
| 
 | 
 | ||||||
| match ffA | code sigA sigB sigH | ||||||
| 	select ffA->type.in($dff) | 	auto unextend = [](const SigSpec &sig) { | ||||||
| 	// select nusers(port(ffA, \Q)) == 2 | 		int i; | ||||||
| 	index <SigSpec> port(ffA, \Q) === port(mul, \A) | 		for (i = GetSize(sig)-1; i > 0; i--) | ||||||
| 	optional | 			if (sig[i] != sig[i-1]) | ||||||
| endmatch | 				break; | ||||||
|  | 		// Do not remove non-const sign bit | ||||||
|  | 		if (sig[i].wire) | ||||||
|  | 			++i; | ||||||
|  | 		return sig.extract(0, i); | ||||||
|  | 	}; | ||||||
|  | 	sigA = unextend(port(mul, \A)); | ||||||
|  | 	sigB = unextend(port(mul, \B)); | ||||||
| 
 | 
 | ||||||
| code sigA clock clock_pol clock_vld | 	SigSpec O; | ||||||
| 	sigA = port(mul, \A); | 	if (mul->type == $mul) | ||||||
| 
 | 		O = mul->getPort(\Y); | ||||||
| 	if (ffA) { | 	else if (mul->type == \SB_MAC16) | ||||||
| 		sigA = port(ffA, \D); | 		O = mul->getPort(\O); | ||||||
| 
 | 	else log_abort(); | ||||||
| 		clock = port(ffA, \CLK).as_bit(); | 	if (GetSize(O) <= 10) | ||||||
| 		clock_pol = param(ffA, \CLK_POLARITY).as_bool(); |  | ||||||
| 		clock_vld = true; |  | ||||||
| 	} |  | ||||||
| endcode |  | ||||||
| 
 |  | ||||||
| match ffB |  | ||||||
| 	select ffB->type.in($dff) |  | ||||||
| 	// select nusers(port(ffB, \Q)) == 2 |  | ||||||
| 	index <SigSpec> port(ffB, \Q) === port(mul, \B) |  | ||||||
| 	optional |  | ||||||
| endmatch |  | ||||||
| 
 |  | ||||||
| code sigB clock clock_pol clock_vld |  | ||||||
| 	sigB = port(mul, \B); |  | ||||||
| 
 |  | ||||||
| 	if (ffB) { |  | ||||||
| 		sigB = port(ffB, \D); |  | ||||||
| 		SigBit c = port(ffB, \CLK).as_bit(); |  | ||||||
| 		bool cp = param(ffB, \CLK_POLARITY).as_bool(); |  | ||||||
| 
 |  | ||||||
| 		if (clock_vld && (c != clock || cp != clock_pol)) |  | ||||||
| 		reject; | 		reject; | ||||||
| 
 | 
 | ||||||
| 		clock = c; | 	// Only care about those bits that are used | ||||||
| 		clock_pol = cp; | 	int i; | ||||||
| 		clock_vld = true; | 	for (i = 0; i < GetSize(O); i++) { | ||||||
|  | 		if (nusers(O[i]) <= 1) | ||||||
|  | 			break; | ||||||
|  | 		sigH.append(O[i]); | ||||||
|  | 	} | ||||||
|  | 	log_assert(nusers(O.extract_end(i)) <= 1); | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | code argQ ffA ffAholdmux ffArstmux ffAholdpol ffArstpol sigA clock clock_pol | ||||||
|  | 	if (mul->type != \SB_MAC16 || !param(mul, \A_REG).as_bool()) { | ||||||
|  | 		argQ = sigA; | ||||||
|  | 		subpattern(in_dffe); | ||||||
|  | 		if (dff) { | ||||||
|  | 			ffA = dff; | ||||||
|  | 			clock = dffclock; | ||||||
|  | 			clock_pol = dffclock_pol; | ||||||
|  | 			if (dffrstmux) { | ||||||
|  | 				ffArstmux = dffrstmux; | ||||||
|  | 				ffArstpol = dffrstpol; | ||||||
|  | 			} | ||||||
|  | 			if (dffholdmux) { | ||||||
|  | 				ffAholdmux = dffholdmux; | ||||||
|  | 				ffAholdpol = dffholdpol; | ||||||
|  | 			} | ||||||
|  | 			sigA = dffD; | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| endcode | endcode | ||||||
| 
 | 
 | ||||||
| match ffY | code argQ ffB ffBholdmux ffBrstmux ffBholdpol ffBrstpol sigB clock clock_pol | ||||||
| 	select ffY->type.in($dff) | 	if (mul->type != \SB_MAC16 || !param(mul, \B_REG).as_bool()) { | ||||||
| 	select nusers(port(ffY, \D)) == 2 | 		argQ = sigB; | ||||||
| 	index <SigSpec> port(ffY, \D) === port(mul, \Y) | 		subpattern(in_dffe); | ||||||
| 	optional | 		if (dff) { | ||||||
| endmatch | 			ffB = dff; | ||||||
| 
 | 			clock = dffclock; | ||||||
| code sigY clock clock_pol clock_vld | 			clock_pol = dffclock_pol; | ||||||
| 	sigY = port(mul, \Y); | 			if (dffrstmux) { | ||||||
| 
 | 				ffBrstmux = dffrstmux; | ||||||
| 	if (ffY) { | 				ffBrstpol = dffrstpol; | ||||||
| 		sigY = port(ffY, \Q); | 			} | ||||||
| 		SigBit c = port(ffY, \CLK).as_bit(); | 			if (dffholdmux) { | ||||||
| 		bool cp = param(ffY, \CLK_POLARITY).as_bool(); | 				ffBholdmux = dffholdmux; | ||||||
| 
 | 				ffBholdpol = dffholdpol; | ||||||
| 		if (clock_vld && (c != clock || cp != clock_pol)) | 			} | ||||||
| 			reject; | 			sigB = dffD; | ||||||
| 
 | 		} | ||||||
| 		clock = c; |  | ||||||
| 		clock_pol = cp; |  | ||||||
| 		clock_vld = true; |  | ||||||
| 	} | 	} | ||||||
| endcode | endcode | ||||||
| 
 | 
 | ||||||
| match addA | code argD ffFJKG sigH clock clock_pol | ||||||
| 	select addA->type.in($add) | 	if (nusers(sigH) == 2 && | ||||||
| 	select nusers(port(addA, \A)) == 2 | 			(mul->type != \SB_MAC16 || | ||||||
| 	index <SigSpec> port(addA, \A) === sigY | 			 (!param(mul, \TOP_8x8_MULT_REG).as_bool() && !param(mul, \BOT_8x8_MULT_REG).as_bool() && !param(mul, \PIPELINE_16x16_MULT_REG1).as_bool() && !param(mul, \PIPELINE_16x16_MULT_REG1).as_bool()))) { | ||||||
|  | 		argD = sigH; | ||||||
|  | 		subpattern(out_dffe); | ||||||
|  | 		if (dff) { | ||||||
|  | 			// F/J/K/G do not have a CE-like (hold) input | ||||||
|  | 			if (dffholdmux) | ||||||
|  | 				goto reject_ffFJKG; | ||||||
|  | 
 | ||||||
|  | 			// Reset signal of F/J (IRSTTOP) and K/G (IRSTBOT) | ||||||
|  | 			//   shared with A and B | ||||||
|  | 			if ((ffArstmux != NULL) != (dffrstmux != NULL)) | ||||||
|  | 				goto reject_ffFJKG; | ||||||
|  | 			if ((ffBrstmux != NULL) != (dffrstmux != NULL)) | ||||||
|  | 				goto reject_ffFJKG; | ||||||
|  | 			if (ffArstmux) { | ||||||
|  | 				if (port(ffArstmux, \S) != port(dffrstmux, \S)) | ||||||
|  | 					goto reject_ffFJKG; | ||||||
|  | 				if (ffArstpol != dffrstpol) | ||||||
|  | 					goto reject_ffFJKG; | ||||||
|  | 			} | ||||||
|  | 			if (ffBrstmux) { | ||||||
|  | 				if (port(ffBrstmux, \S) != port(dffrstmux, \S)) | ||||||
|  | 					goto reject_ffFJKG; | ||||||
|  | 				if (ffBrstpol != dffrstpol) | ||||||
|  | 					goto reject_ffFJKG; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			ffFJKG = dff; | ||||||
|  | 			clock = dffclock; | ||||||
|  | 			clock_pol = dffclock_pol; | ||||||
|  | 			sigH = dffQ; | ||||||
|  | 
 | ||||||
|  | reject_ffFJKG: 		; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | code argD ffH sigH sigO clock clock_pol | ||||||
|  | 	if (ffFJKG && nusers(sigH) == 2 && | ||||||
|  | 			(mul->type != \SB_MAC16 || !param(mul, \PIPELINE_16x16_MULT_REG2).as_bool())) { | ||||||
|  | 		argD = sigH; | ||||||
|  | 		subpattern(out_dffe); | ||||||
|  | 		if (dff) { | ||||||
|  | 			// H does not have a CE-like (hold) input | ||||||
|  | 			if (dffholdmux) | ||||||
|  | 				goto reject_ffH; | ||||||
|  | 
 | ||||||
|  | 			// Reset signal of H (IRSTBOT) shared with B | ||||||
|  | 			if ((ffBrstmux != NULL) != (dffrstmux != NULL)) | ||||||
|  | 				goto reject_ffH; | ||||||
|  | 			if (ffBrstmux) { | ||||||
|  | 				if (port(ffBrstmux, \S) != port(dffrstmux, \S)) | ||||||
|  | 					goto reject_ffH; | ||||||
|  | 				if (ffBrstpol != dffrstpol) | ||||||
|  | 					goto reject_ffH; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			ffH = dff; | ||||||
|  | 			clock = dffclock; | ||||||
|  | 			clock_pol = dffclock_pol; | ||||||
|  | 			sigH = dffQ; | ||||||
|  | 
 | ||||||
|  | reject_ffH:		; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	sigO = sigH; | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | match add | ||||||
|  | 	if mul->type != \SB_MAC16 || (param(mul, \TOPOUTPUT_SELECT).as_int() == 3 && param(mul, \BOTOUTPUT_SELECT).as_int() == 3) | ||||||
|  | 
 | ||||||
|  | 	select add->type.in($add) | ||||||
|  | 	choice <IdString> AB {\A, \B} | ||||||
|  | 	select nusers(port(add, AB)) == 2 | ||||||
|  | 
 | ||||||
|  | 	index <SigBit> port(add, AB)[0] === sigH[0] | ||||||
|  | 	filter GetSize(port(add, AB)) <= GetSize(sigH) | ||||||
|  | 	filter port(add, AB) == sigH.extract(0, GetSize(port(add, AB))) | ||||||
|  | 	filter nusers(sigH.extract_end(GetSize(port(add, AB)))) <= 1 | ||||||
|  | 	set addAB AB | ||||||
| 	optional | 	optional | ||||||
| endmatch | endmatch | ||||||
| 
 | 
 | ||||||
| match addB | code sigCD sigO cd_signed | ||||||
| 	if !addA | 	if (add) { | ||||||
| 	select addB->type.in($add, $sub) | 		sigCD = port(add, addAB == \A ? \B : \A); | ||||||
| 	select nusers(port(addB, \B)) == 2 | 		cd_signed = param(add, addAB == \A ? \B_SIGNED : \A_SIGNED).as_bool(); | ||||||
| 	index <SigSpec> port(addB, \B) === sigY |  | ||||||
| 	optional |  | ||||||
| endmatch |  | ||||||
| 
 | 
 | ||||||
| code addAB sigS |  | ||||||
| 	if (addA) { |  | ||||||
| 		addAB = addA; |  | ||||||
| 		sigS = port(addA, \B); |  | ||||||
| 	} |  | ||||||
| 	if (addB) { |  | ||||||
| 		addAB = addB; |  | ||||||
| 		sigS = port(addB, \A); |  | ||||||
| 	} |  | ||||||
| 	if (addAB) { |  | ||||||
| 		int natural_mul_width = GetSize(sigA) + GetSize(sigB); | 		int natural_mul_width = GetSize(sigA) + GetSize(sigB); | ||||||
| 		int actual_mul_width = GetSize(sigY); | 		int actual_mul_width = GetSize(sigH); | ||||||
| 		int actual_acc_width = GetSize(sigS); | 		int actual_acc_width = GetSize(sigCD); | ||||||
| 
 | 
 | ||||||
| 		if ((actual_acc_width > actual_mul_width) && (natural_mul_width > actual_mul_width)) | 		if ((actual_acc_width > actual_mul_width) && (natural_mul_width > actual_mul_width)) | ||||||
| 			reject; | 			reject; | ||||||
| 		if ((actual_acc_width != actual_mul_width) && (param(mul, \A_SIGNED).as_bool() != param(addAB, \A_SIGNED).as_bool())) | 		// If accumulator, check adder width and signedness | ||||||
|  | 		if (sigCD == sigH && (actual_acc_width != actual_mul_width) && (param(mul, \A_SIGNED).as_bool() != param(add, \A_SIGNED).as_bool())) | ||||||
| 			reject; | 			reject; | ||||||
|  | 
 | ||||||
|  | 		sigO = port(add, \Y); | ||||||
| 	} | 	} | ||||||
| endcode | endcode | ||||||
| 
 | 
 | ||||||
| match muxA | match mux | ||||||
| 	if addAB | 	select mux->type == $mux | ||||||
| 	select muxA->type.in($mux) | 	choice <IdString> AB {\A, \B} | ||||||
| 	select nusers(port(muxA, \A)) == 2 | 	select nusers(port(mux, AB)) == 2 | ||||||
| 	index <SigSpec> port(muxA, \A) === port(addAB, \Y) | 	index <SigSpec> port(mux, AB) === sigO | ||||||
|  | 	set muxAB AB | ||||||
| 	optional | 	optional | ||||||
| endmatch | endmatch | ||||||
| 
 | 
 | ||||||
| match muxB | code sigO | ||||||
| 	if addAB | 	if (mux) | ||||||
| 	if !muxA | 		sigO = port(mux, \Y); | ||||||
| 	select muxB->type.in($mux) |  | ||||||
| 	select nusers(port(muxB, \B)) == 2 |  | ||||||
| 	index <SigSpec> port(muxB, \B) === port(addAB, \Y) |  | ||||||
| 	optional |  | ||||||
| endmatch |  | ||||||
| 
 |  | ||||||
| code muxAB |  | ||||||
| 	muxAB = addAB; |  | ||||||
| 	if (muxA) |  | ||||||
| 		muxAB = muxA; |  | ||||||
| 	if (muxB) |  | ||||||
| 		muxAB = muxB; |  | ||||||
| endcode | endcode | ||||||
| 
 | 
 | ||||||
| match ffS | code argD ffO ffOholdmux ffOrstmux ffOholdpol ffOrstpol sigO sigCD clock clock_pol cd_signed o_lo | ||||||
| 	if muxAB | 	if (mul->type != \SB_MAC16 || | ||||||
| 	select ffS->type.in($dff) | 			// Ensure that register is not already used | ||||||
| 	select nusers(port(ffS, \D)) == 2 | 			((param(mul, \TOPOUTPUT_SELECT, 0).as_int() != 1 && param(mul, \BOTOUTPUT_SELECT, 0).as_int() != 1) && | ||||||
| 	index <SigSpec> port(ffS, \D) === port(muxAB, \Y) | 			 // Ensure that OLOADTOP/OLOADBOT is unused or zero | ||||||
| 	index <SigSpec> port(ffS, \Q) === sigS | 			 (port(mul, \OLOADTOP, State::S0).is_fully_zero() && port(mul, \OLOADBOT, State::S0).is_fully_zero()))) { | ||||||
| endmatch |  | ||||||
| 
 | 
 | ||||||
| code clock clock_pol clock_vld | 		dff = nullptr; | ||||||
| 	if (ffS) { |  | ||||||
| 		SigBit c = port(ffS, \CLK).as_bit(); |  | ||||||
| 		bool cp = param(ffS, \CLK_POLARITY).as_bool(); |  | ||||||
| 
 | 
 | ||||||
| 		if (clock_vld && (c != clock || cp != clock_pol)) | 		// First try entire sigO | ||||||
| 			reject; | 		if (nusers(sigO) == 2) { | ||||||
| 
 | 			argD = sigO; | ||||||
| 		clock = c; | 			subpattern(out_dffe); | ||||||
| 		clock_pol = cp; |  | ||||||
| 		clock_vld = true; |  | ||||||
| 		} | 		} | ||||||
|  | 
 | ||||||
|  | 		// Otherwise try just its least significant 16 bits | ||||||
|  | 		if (!dff && GetSize(sigO) > 16) { | ||||||
|  | 			argD = sigO.extract(0, 16); | ||||||
|  | 			if (nusers(argD) == 2) { | ||||||
|  | 				subpattern(out_dffe); | ||||||
|  | 				o_lo = dff; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (dff) { | ||||||
|  | 			ffO = dff; | ||||||
|  | 			clock = dffclock; | ||||||
|  | 			clock_pol = dffclock_pol; | ||||||
|  | 			if (dffrstmux) { | ||||||
|  | 				ffOrstmux = dffrstmux; | ||||||
|  | 				ffOrstpol = dffrstpol; | ||||||
|  | 			} | ||||||
|  | 			if (dffholdmux) { | ||||||
|  | 				ffOholdmux = dffholdmux; | ||||||
|  | 				ffOholdpol = dffholdpol; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			sigO.replace(sigO.extract(0, GetSize(dffQ)), dffQ); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// Loading value into output register is not | ||||||
|  | 		//   supported unless using accumulator | ||||||
|  | 		if (mux) { | ||||||
|  | 			if (sigCD != sigO) | ||||||
|  | 				reject; | ||||||
|  | 			sigCD = port(mux, muxAB == \B ? \A : \B); | ||||||
|  | 
 | ||||||
|  | 			cd_signed = add && param(add, \A_SIGNED).as_bool() && param(add, \B_SIGNED).as_bool(); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | code argQ ffCD ffCDholdmux ffCDholdpol ffCDrstpol sigCD clock clock_pol | ||||||
|  | 	if (!sigCD.empty() && sigCD != sigO && | ||||||
|  | 			(mul->type != \SB_MAC16 || (!param(mul, \C_REG).as_bool() && !param(mul, \D_REG).as_bool()))) { | ||||||
|  | 		argQ = sigCD; | ||||||
|  | 		subpattern(in_dffe); | ||||||
|  | 		if (dff) { | ||||||
|  | 			if (dffholdmux) { | ||||||
|  | 				ffCDholdmux = dffholdmux; | ||||||
|  | 				ffCDholdpol = dffholdpol; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// Reset signal of C (IRSTTOP) and D (IRSTBOT) | ||||||
|  | 			//   shared with A and B | ||||||
|  | 			if ((ffArstmux != NULL) != (dffrstmux != NULL)) | ||||||
|  | 				goto reject_ffCD; | ||||||
|  | 			if ((ffBrstmux != NULL) != (dffrstmux != NULL)) | ||||||
|  | 				goto reject_ffCD; | ||||||
|  | 			if (ffArstmux) { | ||||||
|  | 				if (port(ffArstmux, \S) != port(dffrstmux, \S)) | ||||||
|  | 					goto reject_ffCD; | ||||||
|  | 				if (ffArstpol != dffrstpol) | ||||||
|  | 					goto reject_ffCD; | ||||||
|  | 			} | ||||||
|  | 			if (ffBrstmux) { | ||||||
|  | 				if (port(ffBrstmux, \S) != port(dffrstmux, \S)) | ||||||
|  | 					goto reject_ffCD; | ||||||
|  | 				if (ffBrstpol != dffrstpol) | ||||||
|  | 					goto reject_ffCD; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			ffCD = dff; | ||||||
|  | 			clock = dffclock; | ||||||
|  | 			clock_pol = dffclock_pol; | ||||||
|  | 			sigCD = dffD; | ||||||
|  | 
 | ||||||
|  | reject_ffCD: 		; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | code sigCD | ||||||
|  | 	sigCD.extend_u0(32, cd_signed); | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | code | ||||||
| 	accept; | 	accept; | ||||||
| endcode | endcode | ||||||
|  | 
 | ||||||
|  | // ####################### | ||||||
|  | 
 | ||||||
|  | subpattern in_dffe | ||||||
|  | arg argD argQ clock clock_pol | ||||||
|  | 
 | ||||||
|  | code | ||||||
|  | 	dff = nullptr; | ||||||
|  | 	for (auto c : argQ.chunks()) { | ||||||
|  | 		if (!c.wire) | ||||||
|  | 			reject; | ||||||
|  | 		if (c.wire->get_bool_attribute(\keep)) | ||||||
|  | 			reject; | ||||||
|  | 		Const init = c.wire->attributes.at(\init, State::Sx); | ||||||
|  | 		if (!init.is_fully_undef() && !init.is_fully_zero()) | ||||||
|  | 			reject; | ||||||
|  | 	} | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | match ff | ||||||
|  | 	select ff->type.in($dff) | ||||||
|  | 	// DSP48E1 does not support clock inversion | ||||||
|  | 	select param(ff, \CLK_POLARITY).as_bool() | ||||||
|  | 
 | ||||||
|  | 	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 | ||||||
|  | 
 | ||||||
|  | 	set ffoffset offset | ||||||
|  | endmatch | ||||||
|  | 
 | ||||||
|  | code argQ argD | ||||||
|  | { | ||||||
|  | 	if (clock != SigBit()) { | ||||||
|  | 		if (port(ff, \CLK) != clock) | ||||||
|  | 			reject; | ||||||
|  | 		if (param(ff, \CLK_POLARITY).as_bool() != clock_pol) | ||||||
|  | 			reject; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	SigSpec Q = port(ff, \Q); | ||||||
|  | 	dff = ff; | ||||||
|  | 	dffclock = port(ff, \CLK); | ||||||
|  | 	dffclock_pol = param(ff, \CLK_POLARITY).as_bool(); | ||||||
|  | 	dffD = argQ; | ||||||
|  | 	argD = port(ff, \D); | ||||||
|  | 	argQ = Q; | ||||||
|  | 	dffD.replace(argQ, argD); | ||||||
|  | 	// Only search for ffrstmux if dffD only | ||||||
|  | 	//   has two (ff, ffrstmux) users | ||||||
|  | 	if (nusers(dffD) > 2) | ||||||
|  | 		argD = SigSpec(); | ||||||
|  | } | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | match ffrstmux | ||||||
|  | 	if false /* TODO: ice40 resets are actually async */ | ||||||
|  | 
 | ||||||
|  | 	if !argD.empty() | ||||||
|  | 	select ffrstmux->type.in($mux) | ||||||
|  | 	index <SigSpec> port(ffrstmux, \Y) === argD | ||||||
|  | 
 | ||||||
|  | 	choice <IdString> BA {\B, \A} | ||||||
|  | 	// DSP48E1 only supports reset to zero | ||||||
|  | 	select port(ffrstmux, BA).is_fully_zero() | ||||||
|  | 
 | ||||||
|  | 	define <bool> pol (BA == \B) | ||||||
|  | 	set ffrstpol pol | ||||||
|  | 	semioptional | ||||||
|  | endmatch | ||||||
|  | 
 | ||||||
|  | code argD | ||||||
|  | 	if (ffrstmux) { | ||||||
|  | 		dffrstmux = ffrstmux; | ||||||
|  | 		dffrstpol = ffrstpol; | ||||||
|  | 		argD = port(ffrstmux, ffrstpol ? \A : \B); | ||||||
|  | 		dffD.replace(port(ffrstmux, \Y), argD); | ||||||
|  | 
 | ||||||
|  | 		// Only search for ffholdmux if argQ has at | ||||||
|  | 		//   least 3 users (ff, <upstream>, ffrstmux) and | ||||||
|  | 		//   dffD only has two (ff, ffrstmux) | ||||||
|  | 		if (!(nusers(argQ) >= 3 && nusers(dffD) == 2)) | ||||||
|  | 			argD = SigSpec(); | ||||||
|  | 	} | ||||||
|  | 	else | ||||||
|  | 		dffrstmux = nullptr; | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | match ffholdmux | ||||||
|  | 	if !argD.empty() | ||||||
|  | 	select ffholdmux->type.in($mux) | ||||||
|  | 	index <SigSpec> port(ffholdmux, \Y) === argD | ||||||
|  | 	choice <IdString> BA {\B, \A} | ||||||
|  | 	index <SigSpec> port(ffholdmux, BA) === argQ | ||||||
|  | 	define <bool> pol (BA == \B) | ||||||
|  | 	set ffholdpol pol | ||||||
|  | 	semioptional | ||||||
|  | endmatch | ||||||
|  | 
 | ||||||
|  | code argD | ||||||
|  | 	if (ffholdmux) { | ||||||
|  | 		dffholdmux = ffholdmux; | ||||||
|  | 		dffholdpol = ffholdpol; | ||||||
|  | 		argD = port(ffholdmux, ffholdpol ? \A : \B); | ||||||
|  | 		dffD.replace(port(ffholdmux, \Y), argD); | ||||||
|  | 	} | ||||||
|  | 	else | ||||||
|  | 		dffholdmux = nullptr; | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | // ####################### | ||||||
|  | 
 | ||||||
|  | subpattern out_dffe | ||||||
|  | arg argD argQ clock clock_pol | ||||||
|  | 
 | ||||||
|  | code | ||||||
|  | 	dff = nullptr; | ||||||
|  | 	for (auto c : argD.chunks()) | ||||||
|  | 		if (c.wire->get_bool_attribute(\keep)) | ||||||
|  | 			reject; | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | match ffholdmux | ||||||
|  | 	select ffholdmux->type.in($mux) | ||||||
|  | 	// ffholdmux output must have two users: ffholdmux and ff.D | ||||||
|  | 	select nusers(port(ffholdmux, \Y)) == 2 | ||||||
|  | 
 | ||||||
|  | 	choice <IdString> BA {\B, \A} | ||||||
|  | 	// keep-last-value net must have at least three users: ffholdmux, ff, downstream sink(s) | ||||||
|  | 	select nusers(port(ffholdmux, BA)) >= 3 | ||||||
|  | 
 | ||||||
|  | 	slice offset GetSize(port(ffholdmux, \Y)) | ||||||
|  | 	define <IdString> AB (BA == \B ? \A : \B) | ||||||
|  | 	index <SigBit> port(ffholdmux, AB)[offset] === argD[0] | ||||||
|  | 
 | ||||||
|  | 	// Check that the rest of argD is present | ||||||
|  | 	filter GetSize(port(ffholdmux, AB)) >= offset + GetSize(argD) | ||||||
|  | 	filter port(ffholdmux, AB).extract(offset, GetSize(argD)) == argD | ||||||
|  | 
 | ||||||
|  | 	set ffoffset offset | ||||||
|  | 	define <bool> pol (BA == \B) | ||||||
|  | 	set ffholdpol pol | ||||||
|  | 
 | ||||||
|  | 	semioptional | ||||||
|  | endmatch | ||||||
|  | 
 | ||||||
|  | code argD argQ | ||||||
|  | 	dffholdmux = ffholdmux; | ||||||
|  | 	if (ffholdmux) { | ||||||
|  | 		SigSpec AB = port(ffholdmux, ffholdpol ? \A : \B); | ||||||
|  | 		SigSpec Y = port(ffholdmux, \Y); | ||||||
|  | 		argQ = argD; | ||||||
|  | 		argD.replace(AB, Y); | ||||||
|  | 		argQ.replace(AB, port(ffholdmux, ffholdpol ? \B : \A)); | ||||||
|  | 
 | ||||||
|  | 		dffholdmux = ffholdmux; | ||||||
|  | 		dffholdpol = ffholdpol; | ||||||
|  | 	} | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | match ffrstmux | ||||||
|  | 	if false /* TODO: ice40 resets are actually async */ | ||||||
|  | 
 | ||||||
|  | 	select ffrstmux->type.in($mux) | ||||||
|  | 	// ffrstmux output must have two users: ffrstmux and ff.D | ||||||
|  | 	select nusers(port(ffrstmux, \Y)) == 2 | ||||||
|  | 
 | ||||||
|  | 	choice <IdString> BA {\B, \A} | ||||||
|  | 	// DSP48E1 only supports reset to zero | ||||||
|  | 	select port(ffrstmux, BA).is_fully_zero() | ||||||
|  | 
 | ||||||
|  | 	slice offset GetSize(port(ffrstmux, \Y)) | ||||||
|  | 	define <IdString> AB (BA == \B ? \A : \B) | ||||||
|  | 	index <SigBit> port(ffrstmux, AB)[offset] === argD[0] | ||||||
|  | 
 | ||||||
|  | 	// Check that offset is consistent | ||||||
|  | 	filter !ffholdmux || ffoffset == offset | ||||||
|  | 	// Check that the rest of argD is present | ||||||
|  | 	filter GetSize(port(ffrstmux, AB)) >= offset + GetSize(argD) | ||||||
|  | 	filter port(ffrstmux, AB).extract(offset, GetSize(argD)) == argD | ||||||
|  | 
 | ||||||
|  | 	set ffoffset offset | ||||||
|  | 	define <bool> pol (AB == \A) | ||||||
|  | 	set ffrstpol pol | ||||||
|  | 
 | ||||||
|  | 	semioptional | ||||||
|  | endmatch | ||||||
|  | 
 | ||||||
|  | code argD argQ | ||||||
|  | 	dffrstmux = ffrstmux; | ||||||
|  | 	if (ffrstmux) { | ||||||
|  | 		SigSpec AB = port(ffrstmux, ffrstpol ? \A : \B); | ||||||
|  | 		SigSpec Y = port(ffrstmux, \Y); | ||||||
|  | 		argD.replace(AB, Y); | ||||||
|  | 
 | ||||||
|  | 		dffrstmux = ffrstmux; | ||||||
|  | 		dffrstpol = ffrstpol; | ||||||
|  | 	} | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | match ff | ||||||
|  | 	select ff->type.in($dff) | ||||||
|  | 	// DSP48E1 does not support clock inversion | ||||||
|  | 	select param(ff, \CLK_POLARITY).as_bool() | ||||||
|  | 
 | ||||||
|  | 	slice offset GetSize(port(ff, \D)) | ||||||
|  | 	index <SigBit> port(ff, \D)[offset] === argD[0] | ||||||
|  | 
 | ||||||
|  | 	// Check that offset is consistent | ||||||
|  | 	filter (!ffholdmux && !ffrstmux) || ffoffset == offset | ||||||
|  | 	// Check that the rest of argD is present | ||||||
|  | 	filter GetSize(port(ff, \D)) >= offset + GetSize(argD) | ||||||
|  | 	filter port(ff, \D).extract(offset, GetSize(argD)) == argD | ||||||
|  | 	// Check that FF.Q is connected to CE-mux | ||||||
|  | 	filter !ffholdmux || port(ff, \Q).extract(offset, GetSize(argQ)) == argQ | ||||||
|  | 
 | ||||||
|  | 	set ffoffset offset | ||||||
|  | endmatch | ||||||
|  | 
 | ||||||
|  | code argQ | ||||||
|  | 	if (ff) { | ||||||
|  | 		if (clock != SigBit()) { | ||||||
|  | 			if (port(ff, \CLK) != clock) | ||||||
|  | 				reject; | ||||||
|  | 			if (param(ff, \CLK_POLARITY).as_bool() != clock_pol) | ||||||
|  | 				reject; | ||||||
|  | 		} | ||||||
|  | 		SigSpec D = port(ff, \D); | ||||||
|  | 		SigSpec Q = port(ff, \Q); | ||||||
|  | 		if (!ffholdmux) { | ||||||
|  | 			argQ = argD; | ||||||
|  | 			argQ.replace(D, Q); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		for (auto c : argQ.chunks()) { | ||||||
|  | 			Const init = c.wire->attributes.at(\init, State::Sx); | ||||||
|  | 			if (!init.is_fully_undef() && !init.is_fully_zero()) | ||||||
|  | 				reject; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		dff = ff; | ||||||
|  | 		dffQ = argQ; | ||||||
|  | 		dffclock = port(ff, \CLK); | ||||||
|  | 		dffclock_pol = param(ff, \CLK_POLARITY).as_bool(); | ||||||
|  | 	} | ||||||
|  | 	// No enable/reset mux possible without flop | ||||||
|  | 	else if (dffholdmux || dffrstmux) | ||||||
|  | 		reject; | ||||||
|  | endcode | ||||||
|  |  | ||||||
|  | @ -286,7 +286,7 @@ def process_pmgfile(f, filename): | ||||||
|                         block["gencode"].append(rewrite_cpp(l.rstrip())) |                         block["gencode"].append(rewrite_cpp(l.rstrip())) | ||||||
|                     break |                     break | ||||||
| 
 | 
 | ||||||
|                 assert False |                 raise RuntimeError("'%s' statement not recognised on line %d" % (a[0], linenr)) | ||||||
| 
 | 
 | ||||||
|             if block["optional"]: |             if block["optional"]: | ||||||
|                 assert not block["semioptional"] |                 assert not block["semioptional"] | ||||||
|  | @ -305,7 +305,8 @@ def process_pmgfile(f, filename): | ||||||
|             block["states"] = set() |             block["states"] = set() | ||||||
| 
 | 
 | ||||||
|             for s in line.split()[1:]: |             for s in line.split()[1:]: | ||||||
|                 assert s in state_types[current_pattern] |                 if s not in state_types[current_pattern]: | ||||||
|  |                     raise RuntimeError("'%s' not in state_types" % s) | ||||||
|                 block["states"].add(s) |                 block["states"].add(s) | ||||||
| 
 | 
 | ||||||
|             codetype = "code" |             codetype = "code" | ||||||
|  | @ -327,7 +328,7 @@ def process_pmgfile(f, filename): | ||||||
|             blocks.append(block) |             blocks.append(block) | ||||||
|             continue |             continue | ||||||
| 
 | 
 | ||||||
|         assert False |         raise RuntimeError("'%s' command not recognised" % cmd) | ||||||
| 
 | 
 | ||||||
| for fn in pmgfiles: | for fn in pmgfiles: | ||||||
|     with open(fn, "r") as f: |     with open(fn, "r") as f: | ||||||
|  | @ -452,11 +453,19 @@ with open(outfile, "w") as f: | ||||||
|     print("    return sigmap(cell->getPort(portname));", file=f) |     print("    return sigmap(cell->getPort(portname));", file=f) | ||||||
|     print("  }", file=f) |     print("  }", file=f) | ||||||
|     print("", file=f) |     print("", file=f) | ||||||
|  |     print("  SigSpec port(Cell *cell, IdString portname, const SigSpec& defval) {", file=f) | ||||||
|  |     print("    return sigmap(cell->connections_.at(portname, defval));", file=f) | ||||||
|  |     print("  }", file=f) | ||||||
|  |     print("", file=f) | ||||||
| 
 | 
 | ||||||
|     print("  Const param(Cell *cell, IdString paramname) {", file=f) |     print("  Const param(Cell *cell, IdString paramname) {", file=f) | ||||||
|     print("    return cell->getParam(paramname);", file=f) |     print("    return cell->getParam(paramname);", file=f) | ||||||
|     print("  }", file=f) |     print("  }", file=f) | ||||||
|     print("", file=f) |     print("", file=f) | ||||||
|  |     print("  Const param(Cell *cell, IdString paramname, const Const& defval) {", file=f) | ||||||
|  |     print("    return cell->parameters.at(paramname, defval);", file=f) | ||||||
|  |     print("  }", file=f) | ||||||
|  |     print("", file=f) | ||||||
| 
 | 
 | ||||||
|     print("  int nusers(const SigSpec &sig) {", file=f) |     print("  int nusers(const SigSpec &sig) {", file=f) | ||||||
|     print("    pool<Cell*> users;", file=f) |     print("    pool<Cell*> users;", file=f) | ||||||
|  |  | ||||||
							
								
								
									
										637
									
								
								passes/pmgen/xilinx_dsp.cc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										637
									
								
								passes/pmgen/xilinx_dsp.cc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,637 @@ | ||||||
|  | /*
 | ||||||
|  |  *  yosys -- Yosys Open SYnthesis Suite | ||||||
|  |  * | ||||||
|  |  *  Copyright (C) 2012  Clifford Wolf <clifford@clifford.at> | ||||||
|  |  *                2019  Eddie Hung    <eddie@fpgeh.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 | ||||||
|  | 
 | ||||||
|  | #include "passes/pmgen/xilinx_dsp_pm.h" | ||||||
|  | #include "passes/pmgen/xilinx_dsp_CREG_pm.h" | ||||||
|  | #include "passes/pmgen/xilinx_dsp_cascade_pm.h" | ||||||
|  | 
 | ||||||
|  | static Cell* addDsp(Module *module) { | ||||||
|  | 	Cell *cell = module->addCell(NEW_ID, ID(DSP48E1)); | ||||||
|  | 	cell->setParam(ID(ACASCREG), 0); | ||||||
|  | 	cell->setParam(ID(ADREG), 0); | ||||||
|  | 	cell->setParam(ID(A_INPUT), Const("DIRECT")); | ||||||
|  | 	cell->setParam(ID(ALUMODEREG), 0); | ||||||
|  | 	cell->setParam(ID(AREG), 0); | ||||||
|  | 	cell->setParam(ID(BCASCREG), 0); | ||||||
|  | 	cell->setParam(ID(B_INPUT), Const("DIRECT")); | ||||||
|  | 	cell->setParam(ID(BREG), 0); | ||||||
|  | 	cell->setParam(ID(CARRYINREG), 0); | ||||||
|  | 	cell->setParam(ID(CARRYINSELREG), 0); | ||||||
|  | 	cell->setParam(ID(CREG), 0); | ||||||
|  | 	cell->setParam(ID(DREG), 0); | ||||||
|  | 	cell->setParam(ID(INMODEREG), 0); | ||||||
|  | 	cell->setParam(ID(MREG), 0); | ||||||
|  | 	cell->setParam(ID(OPMODEREG), 0); | ||||||
|  | 	cell->setParam(ID(PREG), 0); | ||||||
|  | 	cell->setParam(ID(USE_MULT), Const("NONE")); | ||||||
|  | 	cell->setParam(ID(USE_SIMD), Const("ONE48")); | ||||||
|  | 	cell->setParam(ID(USE_DPORT), Const("FALSE")); | ||||||
|  | 
 | ||||||
|  | 	cell->setPort(ID(D), Const(0, 25)); | ||||||
|  | 	cell->setPort(ID(INMODE), Const(0, 5)); | ||||||
|  | 	cell->setPort(ID(ALUMODE), Const(0, 4)); | ||||||
|  | 	cell->setPort(ID(OPMODE), Const(0, 7)); | ||||||
|  | 	cell->setPort(ID(CARRYINSEL), Const(0, 3)); | ||||||
|  | 	cell->setPort(ID(ACIN), Const(0, 30)); | ||||||
|  | 	cell->setPort(ID(BCIN), Const(0, 18)); | ||||||
|  | 	cell->setPort(ID(PCIN), Const(0, 48)); | ||||||
|  | 	cell->setPort(ID(CARRYIN), Const(0, 1)); | ||||||
|  | 	return cell; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void xilinx_simd_pack(Module *module, const std::vector<Cell*> &selected_cells) | ||||||
|  | { | ||||||
|  | 	std::deque<Cell*> simd12_add, simd12_sub; | ||||||
|  | 	std::deque<Cell*> simd24_add, simd24_sub; | ||||||
|  | 
 | ||||||
|  | 	for (auto cell : selected_cells) { | ||||||
|  | 		if (!cell->type.in(ID($add), ID($sub))) | ||||||
|  | 			continue; | ||||||
|  | 		SigSpec Y = cell->getPort(ID(Y)); | ||||||
|  | 		if (!Y.is_chunk()) | ||||||
|  | 			continue; | ||||||
|  | 		if (!Y.as_chunk().wire->get_strpool_attribute(ID(use_dsp)).count("simd")) | ||||||
|  | 			continue; | ||||||
|  | 		if (GetSize(Y) > 25) | ||||||
|  | 			continue; | ||||||
|  | 		SigSpec A = cell->getPort(ID(A)); | ||||||
|  | 		SigSpec B = cell->getPort(ID(B)); | ||||||
|  | 		if (GetSize(Y) <= 13) { | ||||||
|  | 			if (GetSize(A) > 12) | ||||||
|  | 				continue; | ||||||
|  | 			if (GetSize(B) > 12) | ||||||
|  | 				continue; | ||||||
|  | 			if (cell->type == ID($add)) | ||||||
|  | 				simd12_add.push_back(cell); | ||||||
|  | 			else if (cell->type == ID($sub)) | ||||||
|  | 				simd12_sub.push_back(cell); | ||||||
|  | 		} | ||||||
|  | 		else if (GetSize(Y) <= 25) { | ||||||
|  | 			if (GetSize(A) > 24) | ||||||
|  | 				continue; | ||||||
|  | 			if (GetSize(B) > 24) | ||||||
|  | 				continue; | ||||||
|  | 			if (cell->type == ID($add)) | ||||||
|  | 				simd24_add.push_back(cell); | ||||||
|  | 			else if (cell->type == ID($sub)) | ||||||
|  | 				simd24_sub.push_back(cell); | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 			log_abort(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	auto f12 = [module](SigSpec &AB, SigSpec &C, SigSpec &P, SigSpec &CARRYOUT, Cell *lane) { | ||||||
|  | 		SigSpec A = lane->getPort(ID(A)); | ||||||
|  | 		SigSpec B = lane->getPort(ID(B)); | ||||||
|  | 		SigSpec Y = lane->getPort(ID(Y)); | ||||||
|  | 		A.extend_u0(12, lane->getParam(ID(A_SIGNED)).as_bool()); | ||||||
|  | 		B.extend_u0(12, lane->getParam(ID(B_SIGNED)).as_bool()); | ||||||
|  | 		AB.append(A); | ||||||
|  | 		C.append(B); | ||||||
|  | 		if (GetSize(Y) < 13) | ||||||
|  | 			Y.append(module->addWire(NEW_ID, 13-GetSize(Y))); | ||||||
|  | 		else | ||||||
|  | 			log_assert(GetSize(Y) == 13); | ||||||
|  | 		P.append(Y.extract(0, 12)); | ||||||
|  | 		CARRYOUT.append(Y[12]); | ||||||
|  | 	}; | ||||||
|  | 	auto g12 = [&f12,module](std::deque<Cell*> &simd12) { | ||||||
|  | 		while (simd12.size() > 1) { | ||||||
|  | 			SigSpec AB, C, P, CARRYOUT; | ||||||
|  | 
 | ||||||
|  | 			Cell *lane1 = simd12.front(); | ||||||
|  | 			simd12.pop_front(); | ||||||
|  | 			Cell *lane2 = simd12.front(); | ||||||
|  | 			simd12.pop_front(); | ||||||
|  | 			Cell *lane3 = nullptr; | ||||||
|  | 			Cell *lane4 = nullptr; | ||||||
|  | 
 | ||||||
|  | 			if (!simd12.empty()) { | ||||||
|  | 				lane3 = simd12.front(); | ||||||
|  | 				simd12.pop_front(); | ||||||
|  | 				if (!simd12.empty()) { | ||||||
|  | 					lane4 = simd12.front(); | ||||||
|  | 					simd12.pop_front(); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			log("Analysing %s.%s for Xilinx DSP SIMD12 packing.\n", log_id(module), log_id(lane1)); | ||||||
|  | 
 | ||||||
|  | 			Cell *cell = addDsp(module); | ||||||
|  | 			cell->setParam(ID(USE_SIMD), Const("FOUR12")); | ||||||
|  | 			// X = A:B
 | ||||||
|  | 			// Y = 0
 | ||||||
|  | 			// Z = C
 | ||||||
|  | 			cell->setPort(ID(OPMODE), Const::from_string("0110011")); | ||||||
|  | 
 | ||||||
|  | 			log_assert(lane1); | ||||||
|  | 			log_assert(lane2); | ||||||
|  | 			f12(AB, C, P, CARRYOUT, lane1); | ||||||
|  | 			f12(AB, C, P, CARRYOUT, lane2); | ||||||
|  | 			if (lane3) { | ||||||
|  | 				f12(AB, C, P, CARRYOUT, lane3); | ||||||
|  | 				if (lane4) | ||||||
|  | 					f12(AB, C, P, CARRYOUT, lane4); | ||||||
|  | 				else { | ||||||
|  | 					AB.append(Const(0, 12)); | ||||||
|  | 					C.append(Const(0, 12)); | ||||||
|  | 					P.append(module->addWire(NEW_ID, 12)); | ||||||
|  | 					CARRYOUT.append(module->addWire(NEW_ID, 1)); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			else { | ||||||
|  | 				AB.append(Const(0, 24)); | ||||||
|  | 				C.append(Const(0, 24)); | ||||||
|  | 				P.append(module->addWire(NEW_ID, 24)); | ||||||
|  | 				CARRYOUT.append(module->addWire(NEW_ID, 2)); | ||||||
|  | 			} | ||||||
|  | 			log_assert(GetSize(AB) == 48); | ||||||
|  | 			log_assert(GetSize(C) == 48); | ||||||
|  | 			log_assert(GetSize(P) == 48); | ||||||
|  | 			log_assert(GetSize(CARRYOUT) == 4); | ||||||
|  | 			cell->setPort(ID(A), AB.extract(18, 30)); | ||||||
|  | 			cell->setPort(ID(B), AB.extract(0, 18)); | ||||||
|  | 			cell->setPort(ID(C), C); | ||||||
|  | 			cell->setPort(ID(P), P); | ||||||
|  | 			cell->setPort(ID(CARRYOUT), CARRYOUT); | ||||||
|  | 			if (lane1->type == ID($sub)) | ||||||
|  | 				cell->setPort(ID(ALUMODE), Const::from_string("0011")); | ||||||
|  | 
 | ||||||
|  | 			module->remove(lane1); | ||||||
|  | 			module->remove(lane2); | ||||||
|  | 			if (lane3) module->remove(lane3); | ||||||
|  | 			if (lane4) module->remove(lane4); | ||||||
|  | 
 | ||||||
|  | 			module->design->select(module, cell); | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | 	g12(simd12_add); | ||||||
|  | 	g12(simd12_sub); | ||||||
|  | 
 | ||||||
|  | 	auto f24 = [module](SigSpec &AB, SigSpec &C, SigSpec &P, SigSpec &CARRYOUT, Cell *lane) { | ||||||
|  | 		SigSpec A = lane->getPort(ID(A)); | ||||||
|  | 		SigSpec B = lane->getPort(ID(B)); | ||||||
|  | 		SigSpec Y = lane->getPort(ID(Y)); | ||||||
|  | 		A.extend_u0(24, lane->getParam(ID(A_SIGNED)).as_bool()); | ||||||
|  | 		B.extend_u0(24, lane->getParam(ID(B_SIGNED)).as_bool()); | ||||||
|  | 		C.append(A); | ||||||
|  | 		AB.append(B); | ||||||
|  | 		if (GetSize(Y) < 25) | ||||||
|  | 			Y.append(module->addWire(NEW_ID, 25-GetSize(Y))); | ||||||
|  | 		else | ||||||
|  | 			log_assert(GetSize(Y) == 25); | ||||||
|  | 		P.append(Y.extract(0, 24)); | ||||||
|  | 		CARRYOUT.append(module->addWire(NEW_ID)); // TWO24 uses every other bit
 | ||||||
|  | 		CARRYOUT.append(Y[24]); | ||||||
|  | 	}; | ||||||
|  | 	auto g24 = [&f24,module](std::deque<Cell*> &simd24) { | ||||||
|  | 		while (simd24.size() > 1) { | ||||||
|  | 			SigSpec AB; | ||||||
|  | 			SigSpec C; | ||||||
|  | 			SigSpec P; | ||||||
|  | 			SigSpec CARRYOUT; | ||||||
|  | 
 | ||||||
|  | 			Cell *lane1 = simd24.front(); | ||||||
|  | 			simd24.pop_front(); | ||||||
|  | 			Cell *lane2 = simd24.front(); | ||||||
|  | 			simd24.pop_front(); | ||||||
|  | 
 | ||||||
|  | 			log("Analysing %s.%s for Xilinx DSP SIMD24 packing.\n", log_id(module), log_id(lane1)); | ||||||
|  | 
 | ||||||
|  | 			Cell *cell = addDsp(module); | ||||||
|  | 			cell->setParam(ID(USE_SIMD), Const("TWO24")); | ||||||
|  | 			// X = A:B
 | ||||||
|  | 			// Y = 0
 | ||||||
|  | 			// Z = C
 | ||||||
|  | 			cell->setPort(ID(OPMODE), Const::from_string("0110011")); | ||||||
|  | 
 | ||||||
|  | 			log_assert(lane1); | ||||||
|  | 			log_assert(lane2); | ||||||
|  | 			f24(AB, C, P, CARRYOUT, lane1); | ||||||
|  | 			f24(AB, C, P, CARRYOUT, lane2); | ||||||
|  | 			log_assert(GetSize(AB) == 48); | ||||||
|  | 			log_assert(GetSize(C) == 48); | ||||||
|  | 			log_assert(GetSize(P) == 48); | ||||||
|  | 			log_assert(GetSize(CARRYOUT) == 4); | ||||||
|  | 			cell->setPort(ID(A), AB.extract(18, 30)); | ||||||
|  | 			cell->setPort(ID(B), AB.extract(0, 18)); | ||||||
|  | 			cell->setPort(ID(C), C); | ||||||
|  | 			cell->setPort(ID(P), P); | ||||||
|  | 			cell->setPort(ID(CARRYOUT), CARRYOUT); | ||||||
|  | 			if (lane1->type == ID($sub)) | ||||||
|  | 				cell->setPort(ID(ALUMODE), Const::from_string("0011")); | ||||||
|  | 
 | ||||||
|  | 			module->remove(lane1); | ||||||
|  | 			module->remove(lane2); | ||||||
|  | 
 | ||||||
|  | 			module->design->select(module, cell); | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | 	g24(simd24_add); | ||||||
|  | 	g24(simd24_sub); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void xilinx_dsp_pack(xilinx_dsp_pm &pm) | ||||||
|  | { | ||||||
|  | 	auto &st = pm.st_xilinx_dsp_pack; | ||||||
|  | 
 | ||||||
|  | 	log("Analysing %s.%s for Xilinx DSP packing.\n", log_id(pm.module), log_id(st.dsp)); | ||||||
|  | 
 | ||||||
|  | 	log_debug("preAdd:     %s\n", log_id(st.preAdd, "--")); | ||||||
|  | 	log_debug("ffAD:       %s %s %s\n", log_id(st.ffAD, "--"), log_id(st.ffADcemux, "--"), log_id(st.ffADrstmux, "--")); | ||||||
|  | 	log_debug("ffA2:       %s %s %s\n", log_id(st.ffA2, "--"), log_id(st.ffA2cemux, "--"), log_id(st.ffA2rstmux, "--")); | ||||||
|  | 	log_debug("ffA1:       %s %s %s\n", log_id(st.ffA1, "--"), log_id(st.ffA1cemux, "--"), log_id(st.ffA1rstmux, "--")); | ||||||
|  | 	log_debug("ffB2:       %s %s %s\n", log_id(st.ffB2, "--"), log_id(st.ffB2cemux, "--"), log_id(st.ffB2rstmux, "--")); | ||||||
|  | 	log_debug("ffB1:       %s %s %s\n", log_id(st.ffB1, "--"), log_id(st.ffB1cemux, "--"), log_id(st.ffB1rstmux, "--")); | ||||||
|  | 	log_debug("ffD:        %s %s %s\n", log_id(st.ffD, "--"), log_id(st.ffDcemux, "--"), log_id(st.ffDrstmux, "--")); | ||||||
|  | 	log_debug("dsp:        %s\n", log_id(st.dsp, "--")); | ||||||
|  | 	log_debug("ffM:        %s %s %s\n", log_id(st.ffM, "--"), log_id(st.ffMcemux, "--"), log_id(st.ffMrstmux, "--")); | ||||||
|  | 	log_debug("postAdd:    %s\n", log_id(st.postAdd, "--")); | ||||||
|  | 	log_debug("postAddMux: %s\n", log_id(st.postAddMux, "--")); | ||||||
|  | 	log_debug("ffP:        %s %s %s\n", log_id(st.ffP, "--"), log_id(st.ffPcemux, "--"), log_id(st.ffPrstmux, "--")); | ||||||
|  | 	log_debug("overflow:   %s\n", log_id(st.overflow, "--")); | ||||||
|  | 
 | ||||||
|  | 	Cell *cell = st.dsp; | ||||||
|  | 
 | ||||||
|  | 	if (st.preAdd) { | ||||||
|  | 		log("  preadder %s (%s)\n", log_id(st.preAdd), log_id(st.preAdd->type)); | ||||||
|  | 		bool A_SIGNED = st.preAdd->getParam(ID(A_SIGNED)).as_bool(); | ||||||
|  | 		bool D_SIGNED = st.preAdd->getParam(ID(B_SIGNED)).as_bool(); | ||||||
|  | 		if (st.sigA == st.preAdd->getPort(ID(B))) | ||||||
|  | 			std::swap(A_SIGNED, D_SIGNED); | ||||||
|  | 		st.sigA.extend_u0(30, A_SIGNED); | ||||||
|  | 		st.sigD.extend_u0(25, D_SIGNED); | ||||||
|  | 		cell->setPort(ID(A), st.sigA); | ||||||
|  | 		cell->setPort(ID(D), st.sigD); | ||||||
|  | 		cell->setPort(ID(INMODE), Const::from_string("00100")); | ||||||
|  | 
 | ||||||
|  | 		if (st.ffAD) { | ||||||
|  | 			if (st.ffADcemux) { | ||||||
|  | 				SigSpec S = st.ffADcemux->getPort(ID(S)); | ||||||
|  | 				cell->setPort(ID(CEAD), st.ffADcepol ? S : pm.module->Not(NEW_ID, S)); | ||||||
|  | 			} | ||||||
|  | 			else | ||||||
|  | 				cell->setPort(ID(CEAD), State::S1); | ||||||
|  | 			cell->setParam(ID(ADREG), 1); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		cell->setParam(ID(USE_DPORT), Const("TRUE")); | ||||||
|  | 
 | ||||||
|  | 		pm.autoremove(st.preAdd); | ||||||
|  | 	} | ||||||
|  | 	if (st.postAdd) { | ||||||
|  | 		log("  postadder %s (%s)\n", log_id(st.postAdd), log_id(st.postAdd->type)); | ||||||
|  | 
 | ||||||
|  | 		SigSpec &opmode = cell->connections_.at(ID(OPMODE)); | ||||||
|  | 		if (st.postAddMux) { | ||||||
|  | 			log_assert(st.ffP); | ||||||
|  | 			opmode[4] = st.postAddMux->getPort(ID(S)); | ||||||
|  | 			pm.autoremove(st.postAddMux); | ||||||
|  | 		} | ||||||
|  | 		else if (st.ffP && st.sigC == st.sigP) | ||||||
|  | 			opmode[4] = State::S0; | ||||||
|  | 		else | ||||||
|  | 			opmode[4] = State::S1; | ||||||
|  | 		opmode[6] = State::S0; | ||||||
|  | 		opmode[5] = State::S1; | ||||||
|  | 
 | ||||||
|  | 		if (opmode[4] != State::S0) { | ||||||
|  | 			if (st.postAddMuxAB == ID(A)) | ||||||
|  | 				st.sigC.extend_u0(48, st.postAdd->getParam(ID(B_SIGNED)).as_bool()); | ||||||
|  | 			else | ||||||
|  | 				st.sigC.extend_u0(48, st.postAdd->getParam(ID(A_SIGNED)).as_bool()); | ||||||
|  | 			cell->setPort(ID(C), st.sigC); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		pm.autoremove(st.postAdd); | ||||||
|  | 	} | ||||||
|  | 	if (st.overflow) { | ||||||
|  | 		log("  overflow %s (%s)\n", log_id(st.overflow), log_id(st.overflow->type)); | ||||||
|  | 		cell->setParam(ID(USE_PATTERN_DETECT), Const("PATDET")); | ||||||
|  | 		cell->setParam(ID(SEL_PATTERN), Const("PATTERN")); | ||||||
|  | 		cell->setParam(ID(SEL_MASK), Const("MASK")); | ||||||
|  | 
 | ||||||
|  | 		if (st.overflow->type == ID($ge)) { | ||||||
|  | 			Const B = st.overflow->getPort(ID(B)).as_const(); | ||||||
|  | 			log_assert(std::count(B.bits.begin(), B.bits.end(), State::S1) == 1); | ||||||
|  | 			// Since B is an exact power of 2, subtract 1
 | ||||||
|  | 			//   by inverting all bits up until hitting
 | ||||||
|  | 			//   that one hi bit
 | ||||||
|  | 			for (auto &b : B.bits) | ||||||
|  | 				if (b == State::S0) b = State::S1; | ||||||
|  | 				else if (b == State::S1) { | ||||||
|  | 					b = State::S0; | ||||||
|  | 					break; | ||||||
|  | 				} | ||||||
|  | 			B.extu(48); | ||||||
|  | 
 | ||||||
|  | 			cell->setParam(ID(MASK), B); | ||||||
|  | 			cell->setParam(ID(PATTERN), Const(0, 48)); | ||||||
|  | 			cell->setPort(ID(OVERFLOW), st.overflow->getPort(ID(Y))); | ||||||
|  | 		} | ||||||
|  | 		else log_abort(); | ||||||
|  | 
 | ||||||
|  | 		pm.autoremove(st.overflow); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (st.clock != SigBit()) | ||||||
|  | 	{ | ||||||
|  | 		cell->setPort(ID(CLK), st.clock); | ||||||
|  | 
 | ||||||
|  | 		auto f = [&pm,cell](SigSpec &A, Cell* ff, Cell* cemux, bool cepol, IdString ceport, Cell* rstmux, bool rstpol, IdString rstport) { | ||||||
|  | 			SigSpec D = ff->getPort(ID(D)); | ||||||
|  | 			SigSpec Q = pm.sigmap(ff->getPort(ID(Q))); | ||||||
|  | 			if (!A.empty()) | ||||||
|  | 				A.replace(Q, D); | ||||||
|  | 			if (rstmux) { | ||||||
|  | 				SigSpec Y = rstmux->getPort(ID(Y)); | ||||||
|  | 				SigSpec AB = rstmux->getPort(rstpol ? ID(A) : ID(B)); | ||||||
|  | 				if (!A.empty()) | ||||||
|  | 					A.replace(Y, AB); | ||||||
|  | 				if (rstport != IdString()) { | ||||||
|  | 					SigSpec S = rstmux->getPort(ID(S)); | ||||||
|  | 					cell->setPort(rstport, rstpol ? S : pm.module->Not(NEW_ID, S)); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			else if (rstport != IdString()) | ||||||
|  | 				cell->setPort(rstport, State::S0); | ||||||
|  | 			if (cemux) { | ||||||
|  | 				SigSpec Y = cemux->getPort(ID(Y)); | ||||||
|  | 				SigSpec BA = cemux->getPort(cepol ? ID(B) : ID(A)); | ||||||
|  | 				SigSpec S = cemux->getPort(ID(S)); | ||||||
|  | 				if (!A.empty()) | ||||||
|  | 					A.replace(Y, BA); | ||||||
|  | 				cell->setPort(ceport, cepol ? S : pm.module->Not(NEW_ID, S)); | ||||||
|  | 			} | ||||||
|  | 			else | ||||||
|  | 				cell->setPort(ceport, State::S1); | ||||||
|  | 
 | ||||||
|  | 			for (auto c : Q.chunks()) { | ||||||
|  | 				auto it = c.wire->attributes.find(ID(init)); | ||||||
|  | 				if (it == c.wire->attributes.end()) | ||||||
|  | 					continue; | ||||||
|  | 				for (int i = c.offset; i < c.offset+c.width; i++) { | ||||||
|  | 					log_assert(it->second[i] == State::S0 || it->second[i] == State::Sx); | ||||||
|  | 					it->second[i] = State::Sx; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  | 
 | ||||||
|  | 		if (st.ffA2) { | ||||||
|  | 			SigSpec A = cell->getPort(ID(A)); | ||||||
|  | 			f(A, st.ffA2, st.ffA2cemux, st.ffA2cepol, ID(CEA2), st.ffA2rstmux, st.ffArstpol, ID(RSTA)); | ||||||
|  | 			if (st.ffA1) { | ||||||
|  | 				f(A, st.ffA1, st.ffA1cemux, st.ffA1cepol, ID(CEA1), st.ffA1rstmux, st.ffArstpol, IdString()); | ||||||
|  | 				cell->setParam(ID(AREG), 2); | ||||||
|  | 				cell->setParam(ID(ACASCREG), 2); | ||||||
|  | 			} | ||||||
|  | 			else { | ||||||
|  | 				cell->setParam(ID(AREG), 1); | ||||||
|  | 				cell->setParam(ID(ACASCREG), 1); | ||||||
|  | 			} | ||||||
|  | 			pm.add_siguser(A, cell); | ||||||
|  | 			cell->setPort(ID(A), A); | ||||||
|  | 		} | ||||||
|  | 		if (st.ffB2) { | ||||||
|  | 			SigSpec B = cell->getPort(ID(B)); | ||||||
|  | 			f(B, st.ffB2, st.ffB2cemux, st.ffB2cepol, ID(CEB2), st.ffB2rstmux, st.ffBrstpol, ID(RSTB)); | ||||||
|  | 			if (st.ffB1) { | ||||||
|  | 				f(B, st.ffB1, st.ffB1cemux, st.ffB1cepol, ID(CEB1), st.ffB1rstmux, st.ffBrstpol, IdString()); | ||||||
|  | 				cell->setParam(ID(BREG), 2); | ||||||
|  | 				cell->setParam(ID(BCASCREG), 2); | ||||||
|  | 			} | ||||||
|  | 			else { | ||||||
|  | 				cell->setParam(ID(BREG), 1); | ||||||
|  | 				cell->setParam(ID(BCASCREG), 1); | ||||||
|  | 			} | ||||||
|  | 			pm.add_siguser(B, cell); | ||||||
|  | 			cell->setPort(ID(B), B); | ||||||
|  | 		} | ||||||
|  | 		if (st.ffD) { | ||||||
|  | 			SigSpec D = cell->getPort(ID(D)); | ||||||
|  | 			f(D, st.ffD, st.ffDcemux, st.ffDcepol, ID(CED), st.ffDrstmux, st.ffDrstpol, ID(RSTD)); | ||||||
|  | 			pm.add_siguser(D, cell); | ||||||
|  | 			cell->setPort(ID(D), D); | ||||||
|  | 			cell->setParam(ID(DREG), 1); | ||||||
|  | 		} | ||||||
|  | 		if (st.ffM) { | ||||||
|  | 			SigSpec M; // unused
 | ||||||
|  | 			f(M, st.ffM, st.ffMcemux, st.ffMcepol, ID(CEM), st.ffMrstmux, st.ffMrstpol, ID(RSTM)); | ||||||
|  | 			st.ffM->connections_.at(ID(Q)).replace(st.sigM, pm.module->addWire(NEW_ID, GetSize(st.sigM))); | ||||||
|  | 			cell->setParam(ID(MREG), State::S1); | ||||||
|  | 		} | ||||||
|  | 		if (st.ffP) { | ||||||
|  | 			SigSpec P; // unused
 | ||||||
|  | 			f(P, st.ffP, st.ffPcemux, st.ffPcepol, ID(CEP), st.ffPrstmux, st.ffPrstpol, ID(RSTP)); | ||||||
|  | 			st.ffP->connections_.at(ID(Q)).replace(st.sigP, pm.module->addWire(NEW_ID, GetSize(st.sigP))); | ||||||
|  | 			cell->setParam(ID(PREG), State::S1); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		log("  clock: %s (%s)", log_signal(st.clock), "posedge"); | ||||||
|  | 
 | ||||||
|  | 		if (st.ffA2) { | ||||||
|  | 			log(" ffA2:%s", log_id(st.ffA2)); | ||||||
|  | 			if (st.ffA1) | ||||||
|  | 				log(" ffA1:%s", log_id(st.ffA1)); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (st.ffAD) | ||||||
|  | 			log(" ffAD:%s", log_id(st.ffAD)); | ||||||
|  | 
 | ||||||
|  | 		if (st.ffB2) { | ||||||
|  | 			log(" ffB2:%s", log_id(st.ffB2)); | ||||||
|  | 			if (st.ffB1) | ||||||
|  | 				log(" ffB1:%s", log_id(st.ffB1)); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (st.ffD) | ||||||
|  | 			log(" ffD:%s", log_id(st.ffD)); | ||||||
|  | 
 | ||||||
|  | 		if (st.ffM) | ||||||
|  | 			log(" ffM:%s", log_id(st.ffM)); | ||||||
|  | 
 | ||||||
|  | 		if (st.ffP) | ||||||
|  | 			log(" ffP:%s", log_id(st.ffP)); | ||||||
|  | 	} | ||||||
|  | 	log("\n"); | ||||||
|  | 
 | ||||||
|  | 	SigSpec P = st.sigP; | ||||||
|  | 	if (GetSize(P) < 48) | ||||||
|  | 		P.append(pm.module->addWire(NEW_ID, 48-GetSize(P))); | ||||||
|  | 	cell->setPort(ID(P), P); | ||||||
|  | 
 | ||||||
|  | 	pm.blacklist(cell); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void xilinx_dsp_packC(xilinx_dsp_CREG_pm &pm) | ||||||
|  | { | ||||||
|  | 	auto &st = pm.st_xilinx_dsp_packC; | ||||||
|  | 
 | ||||||
|  | 	log_debug("Analysing %s.%s for Xilinx DSP packing (CREG).\n", log_id(pm.module), log_id(st.dsp)); | ||||||
|  | 	log_debug("ffC:        %s %s %s\n", log_id(st.ffC, "--"), log_id(st.ffCcemux, "--"), log_id(st.ffCrstmux, "--")); | ||||||
|  | 
 | ||||||
|  | 	Cell *cell = st.dsp; | ||||||
|  | 
 | ||||||
|  | 	if (st.clock != SigBit()) | ||||||
|  | 	{ | ||||||
|  | 		cell->setPort(ID(CLK), st.clock); | ||||||
|  | 
 | ||||||
|  | 		auto f = [&pm,cell](SigSpec &A, Cell* ff, Cell* cemux, bool cepol, IdString ceport, Cell* rstmux, bool rstpol, IdString rstport) { | ||||||
|  | 			SigSpec D = ff->getPort(ID(D)); | ||||||
|  | 			SigSpec Q = pm.sigmap(ff->getPort(ID(Q))); | ||||||
|  | 			if (!A.empty()) | ||||||
|  | 				A.replace(Q, D); | ||||||
|  | 			if (rstmux) { | ||||||
|  | 				SigSpec Y = rstmux->getPort(ID(Y)); | ||||||
|  | 				SigSpec AB = rstmux->getPort(rstpol ? ID(A) : ID(B)); | ||||||
|  | 				if (!A.empty()) | ||||||
|  | 					A.replace(Y, AB); | ||||||
|  | 				if (rstport != IdString()) { | ||||||
|  | 					SigSpec S = rstmux->getPort(ID(S)); | ||||||
|  | 					cell->setPort(rstport, rstpol ? S : pm.module->Not(NEW_ID, S)); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			else if (rstport != IdString()) | ||||||
|  | 				cell->setPort(rstport, State::S0); | ||||||
|  | 			if (cemux) { | ||||||
|  | 				SigSpec Y = cemux->getPort(ID(Y)); | ||||||
|  | 				SigSpec BA = cemux->getPort(cepol ? ID(B) : ID(A)); | ||||||
|  | 				SigSpec S = cemux->getPort(ID(S)); | ||||||
|  | 				if (!A.empty()) | ||||||
|  | 					A.replace(Y, BA); | ||||||
|  | 				cell->setPort(ceport, cepol ? S : pm.module->Not(NEW_ID, S)); | ||||||
|  | 			} | ||||||
|  | 			else | ||||||
|  | 				cell->setPort(ceport, State::S1); | ||||||
|  | 
 | ||||||
|  | 			for (auto c : Q.chunks()) { | ||||||
|  | 				auto it = c.wire->attributes.find(ID(init)); | ||||||
|  | 				if (it == c.wire->attributes.end()) | ||||||
|  | 					continue; | ||||||
|  | 				for (int i = c.offset; i < c.offset+c.width; i++) { | ||||||
|  | 					log_assert(it->second[i] == State::S0 || it->second[i] == State::Sx); | ||||||
|  | 					it->second[i] = State::Sx; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  | 
 | ||||||
|  | 		if (st.ffC) { | ||||||
|  | 			SigSpec C = cell->getPort(ID(C)); | ||||||
|  | 			f(C, st.ffC, st.ffCcemux, st.ffCcepol, ID(CEC), st.ffCrstmux, st.ffCrstpol, ID(RSTC)); | ||||||
|  | 			pm.add_siguser(C, cell); | ||||||
|  | 			cell->setPort(ID(C), C); | ||||||
|  | 			cell->setParam(ID(CREG), 1); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		log("  clock: %s (%s)", log_signal(st.clock), "posedge"); | ||||||
|  | 
 | ||||||
|  | 		if (st.ffC) | ||||||
|  | 			log(" ffC:%s", log_id(st.ffC)); | ||||||
|  | 		log("\n"); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	pm.blacklist(cell); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | struct XilinxDspPass : public Pass { | ||||||
|  | 	XilinxDspPass() : Pass("xilinx_dsp", "Xilinx: pack resources into DSPs") { } | ||||||
|  | 	void help() YS_OVERRIDE | ||||||
|  | 	{ | ||||||
|  | 		//   |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
 | ||||||
|  | 		log("\n"); | ||||||
|  | 		log("    xilinx_dsp [options] [selection]\n"); | ||||||
|  | 		log("\n"); | ||||||
|  | 		log("Pack input registers (A2, A1, B2, B1, C, D, AD; with optional enable/reset),\n"); | ||||||
|  | 		log("pipeline registers (M; with optional enable/reset), output registers (P; with\n"); | ||||||
|  | 		log("optional enable/reset), pre-adder and/or post-adder into Xilinx DSP resources.\n"); | ||||||
|  | 		log("\n"); | ||||||
|  | 		log("Multiply-accumulate operations using the post-adder with feedback on the 'C'\n"); | ||||||
|  | 		log("input will be folded into the DSP. In this scenario only, the 'C' input can be\n"); | ||||||
|  | 		log("used to override the current accumulation result with a new value, which will\n"); | ||||||
|  | 		log("be added to the multiplier result to form the next accumulation result.\n"); | ||||||
|  | 		log("\n"); | ||||||
|  | 		log("Use of the dedicated 'PCOUT' -> 'PCIN' cascade path is detected for 'P' -> 'C'\n"); | ||||||
|  | 		log("connections (optionally, where 'P' is right-shifted by 17-bits and used as an\n"); | ||||||
|  | 		log("input to the post-adder -- a pattern common for summing partial products to\n"); | ||||||
|  | 		log("implement wide multipliers). Limited support also exists for similar cascading\n"); | ||||||
|  | 		log("for A and B using '[AB]COUT' -> '[AB]CIN'. Currently, cascade chains are limited\n"); | ||||||
|  | 		log("to a maximum length of 20 cells, corresponding to the smallest Xilinx 7 Series\n"); | ||||||
|  | 		log("device.\n"); | ||||||
|  | 		log("\n"); | ||||||
|  | 		log("\n"); | ||||||
|  | 		log("Experimental feature: addition/subtractions less than 12 or 24 bits with the\n"); | ||||||
|  | 		log("'(* use_dsp=\"simd\" *)' attribute attached to the output wire or attached to\n"); | ||||||
|  | 		log("the add/subtract operator will cause those operations to be implemented using\n"); | ||||||
|  | 		log("the 'SIMD' feature of DSPs.\n"); | ||||||
|  | 		log("\n"); | ||||||
|  | 		log("Experimental feature: the presence of a `$ge' cell attached to the registered\n"); | ||||||
|  | 		log("P output implementing the operation \"(P >= <power-of-2>)\" will be transformed\n"); | ||||||
|  | 		log("into using the DSP48E1's pattern detector feature for overflow detection.\n"); | ||||||
|  | 		log("\n"); | ||||||
|  | 	} | ||||||
|  | 	void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE | ||||||
|  | 	{ | ||||||
|  | 		log_header(design, "Executing XILINX_DSP pass (pack resources into DSPs).\n"); | ||||||
|  | 
 | ||||||
|  | 		size_t argidx; | ||||||
|  | 		for (argidx = 1; argidx < args.size(); argidx++) | ||||||
|  | 		{ | ||||||
|  | 			// if (args[argidx] == "-singleton") {
 | ||||||
|  | 			// 	singleton_mode = true;
 | ||||||
|  | 			// 	continue;
 | ||||||
|  | 			// }
 | ||||||
|  | 			break; | ||||||
|  | 		} | ||||||
|  | 		extra_args(args, argidx, design); | ||||||
|  | 
 | ||||||
|  | 		for (auto module : design->selected_modules()) { | ||||||
|  | 			xilinx_simd_pack(module, module->selected_cells()); | ||||||
|  | 
 | ||||||
|  | 			{ | ||||||
|  | 				xilinx_dsp_pm pm(module, module->selected_cells()); | ||||||
|  | 				pm.run_xilinx_dsp_pack(xilinx_dsp_pack); | ||||||
|  | 			} | ||||||
|  | 			// Separating out CREG packing is necessary since there
 | ||||||
|  | 			//   is no guarantee that the cell ordering corresponds
 | ||||||
|  | 			//   to the "expected" case (i.e. the order in which
 | ||||||
|  | 			//   they appear in the source) thus the possiblity
 | ||||||
|  | 			//   existed that a register got packed as CREG into a
 | ||||||
|  | 			//   downstream DSP that should have otherwise been a
 | ||||||
|  | 			//   PREG of an upstream DSP that had not been pattern
 | ||||||
|  | 			//   matched yet
 | ||||||
|  | 			{ | ||||||
|  | 				xilinx_dsp_CREG_pm pm(module, module->selected_cells()); | ||||||
|  | 				pm.run_xilinx_dsp_packC(xilinx_dsp_packC); | ||||||
|  | 			} | ||||||
|  | 			{ | ||||||
|  | 				xilinx_dsp_cascade_pm pm(module, module->selected_cells()); | ||||||
|  | 				pm.run_xilinx_dsp_cascade(); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } XilinxDspPass; | ||||||
|  | 
 | ||||||
|  | PRIVATE_NAMESPACE_END | ||||||
							
								
								
									
										587
									
								
								passes/pmgen/xilinx_dsp.pmg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										587
									
								
								passes/pmgen/xilinx_dsp.pmg
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,587 @@ | ||||||
|  | pattern xilinx_dsp_pack | ||||||
|  | 
 | ||||||
|  | state <SigBit> clock | ||||||
|  | state <SigSpec> sigA sigB sigC sigD sigM sigP | ||||||
|  | state <IdString> postAddAB postAddMuxAB | ||||||
|  | state <bool> ffA1cepol ffA2cepol ffADcepol ffB1cepol ffB2cepol ffDcepol ffMcepol ffPcepol | ||||||
|  | state <bool> ffArstpol ffADrstpol ffBrstpol ffDrstpol ffMrstpol ffPrstpol | ||||||
|  | 
 | ||||||
|  | state <Cell*> ffAD ffADcemux ffADrstmux ffA1 ffA1cemux ffA1rstmux ffA2 ffA2cemux ffA2rstmux | ||||||
|  | state <Cell*> ffB1 ffB1cemux ffB1rstmux ffB2 ffB2cemux ffB2rstmux | ||||||
|  | state <Cell*> ffD ffDcemux ffDrstmux ffM ffMcemux ffMrstmux ffP ffPcemux ffPrstmux | ||||||
|  | 
 | ||||||
|  | // subpattern | ||||||
|  | state <SigSpec> argQ argD | ||||||
|  | state <bool> ffcepol ffrstpol | ||||||
|  | state <int> ffoffset | ||||||
|  | udata <SigSpec> dffD dffQ | ||||||
|  | udata <SigBit> dffclock | ||||||
|  | udata <Cell*> dff dffcemux dffrstmux | ||||||
|  | udata <bool> dffcepol dffrstpol | ||||||
|  | 
 | ||||||
|  | match dsp | ||||||
|  | 	select dsp->type.in(\DSP48E1) | ||||||
|  | endmatch | ||||||
|  | 
 | ||||||
|  | code sigA sigB sigC sigD sigM clock | ||||||
|  | 	auto 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); | ||||||
|  | 	}; | ||||||
|  | 	sigA = unextend(port(dsp, \A)); | ||||||
|  | 	sigB = unextend(port(dsp, \B)); | ||||||
|  | 
 | ||||||
|  | 	sigC = port(dsp, \C, SigSpec()); | ||||||
|  | 	sigD = port(dsp, \D, SigSpec()); | ||||||
|  | 
 | ||||||
|  | 	SigSpec P = port(dsp, \P); | ||||||
|  | 	if (param(dsp, \USE_MULT, Const("MULTIPLY")).decode_string() == "MULTIPLY") { | ||||||
|  | 		// Only care about those bits that are used | ||||||
|  | 		int i; | ||||||
|  | 		for (i = 0; i < GetSize(P); i++) { | ||||||
|  | 			if (nusers(P[i]) <= 1) | ||||||
|  | 				break; | ||||||
|  | 			sigM.append(P[i]); | ||||||
|  | 		} | ||||||
|  | 		log_assert(nusers(P.extract_end(i)) <= 1); | ||||||
|  | 	} | ||||||
|  | 	else | ||||||
|  | 		sigM = P; | ||||||
|  | 	// This sigM could have no users if downstream $add | ||||||
|  | 	//   is narrower than $mul result, for example | ||||||
|  | 	if (sigM.empty()) | ||||||
|  | 		reject; | ||||||
|  | 
 | ||||||
|  | 	clock = port(dsp, \CLK, SigBit()); | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | code argQ ffAD ffADcemux ffADrstmux ffADcepol ffADrstpol sigA clock | ||||||
|  | 	if (param(dsp, \ADREG).as_int() == 0) { | ||||||
|  | 		argQ = sigA; | ||||||
|  | 		subpattern(in_dffe); | ||||||
|  | 		if (dff) { | ||||||
|  | 			ffAD = dff; | ||||||
|  | 			clock = dffclock; | ||||||
|  | 			if (dffrstmux) { | ||||||
|  | 				ffADrstmux = dffrstmux; | ||||||
|  | 				ffADrstpol = dffrstpol; | ||||||
|  | 			} | ||||||
|  | 			if (dffcemux) { | ||||||
|  | 				ffADcemux = dffcemux; | ||||||
|  | 				ffADcepol = dffcepol; | ||||||
|  | 			} | ||||||
|  | 			sigA = dffD; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | match preAdd | ||||||
|  | 	if sigD.empty() || sigD.is_fully_zero() | ||||||
|  | 	// Ensure that preAdder not already used | ||||||
|  | 	if param(dsp, \USE_DPORT, Const("FALSE")).decode_string() == "FALSE" | ||||||
|  | 	if port(dsp, \INMODE, Const(0, 5)).is_fully_zero() | ||||||
|  | 
 | ||||||
|  | 	select preAdd->type.in($add) | ||||||
|  | 	// Output has to be 25 bits or less | ||||||
|  | 	select GetSize(port(preAdd, \Y)) <= 25 | ||||||
|  | 	select nusers(port(preAdd, \Y)) == 2 | ||||||
|  | 	choice <IdString> AB {\A, \B} | ||||||
|  | 	// A port has to be 30 bits or less | ||||||
|  | 	select GetSize(port(preAdd, AB)) <= 30 | ||||||
|  | 	define <IdString> BA (AB == \A ? \B : \A) | ||||||
|  | 	// D port has to be 25 bits or less | ||||||
|  | 	select GetSize(port(preAdd, BA)) <= 25 | ||||||
|  | 	index <SigSpec> port(preAdd, \Y) === sigA | ||||||
|  | 
 | ||||||
|  | 	optional | ||||||
|  | endmatch | ||||||
|  | 
 | ||||||
|  | code sigA sigD | ||||||
|  | 	if (preAdd) { | ||||||
|  | 		sigA = port(preAdd, \A); | ||||||
|  | 		sigD = port(preAdd, \B); | ||||||
|  | 		if (GetSize(sigA) < GetSize(sigD)) | ||||||
|  | 			std::swap(sigA, sigD); | ||||||
|  | 	} | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | code argQ ffAD ffADcemux ffADrstmux ffADcepol ffADrstpol sigA clock ffA2 ffA2cemux ffA2rstmux ffA2cepol ffArstpol ffA1 ffA1cemux ffA1rstmux ffA1cepol | ||||||
|  | 	// Only search for ffA2 if there was a pre-adder | ||||||
|  | 	//   (otherwise ffA2 would have been matched as ffAD) | ||||||
|  | 	if (preAdd) { | ||||||
|  | 		if (param(dsp, \AREG).as_int() == 0) { | ||||||
|  | 			argQ = sigA; | ||||||
|  | 			subpattern(in_dffe); | ||||||
|  | 			if (dff) { | ||||||
|  | 				ffA2 = dff; | ||||||
|  | 				clock = dffclock; | ||||||
|  | 				if (dffrstmux) { | ||||||
|  | 					ffA2rstmux = dffrstmux; | ||||||
|  | 					ffArstpol = dffrstpol; | ||||||
|  | 				} | ||||||
|  | 				if (dffcemux) { | ||||||
|  | 					ffA2cepol = dffcepol; | ||||||
|  | 					ffA2cemux = dffcemux; | ||||||
|  | 				} | ||||||
|  | 				sigA = dffD; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	// And if there wasn't a pre-adder, | ||||||
|  | 	//   move AD register to A | ||||||
|  | 	else if (ffAD) { | ||||||
|  | 		log_assert(!ffA2 && !ffA2cemux && !ffA2rstmux); | ||||||
|  | 		std::swap(ffA2, ffAD); | ||||||
|  | 		std::swap(ffA2cemux, ffADcemux); | ||||||
|  | 		std::swap(ffA2rstmux, ffADrstmux); | ||||||
|  | 		ffA2cepol = ffADcepol; | ||||||
|  | 		ffArstpol = ffADrstpol; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Now attempt to match A1 | ||||||
|  | 	if (ffA2) { | ||||||
|  | 		argQ = sigA; | ||||||
|  | 		subpattern(in_dffe); | ||||||
|  | 		if (dff) { | ||||||
|  | 			if ((ffA2rstmux != nullptr) ^ (dffrstmux != nullptr)) | ||||||
|  | 				goto ffA1_end; | ||||||
|  | 			if (dffrstmux) { | ||||||
|  | 				if (ffArstpol != dffrstpol) | ||||||
|  | 					goto ffA1_end; | ||||||
|  | 				if (port(ffA2rstmux, \S) != port(dffrstmux, \S)) | ||||||
|  | 					goto ffA1_end; | ||||||
|  | 				ffA1rstmux = dffrstmux; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			ffA1 = dff; | ||||||
|  | 			clock = dffclock; | ||||||
|  | 
 | ||||||
|  | 			if (dffcemux) { | ||||||
|  | 				ffA1cemux = dffcemux; | ||||||
|  | 				ffA1cepol = dffcepol; | ||||||
|  | 			} | ||||||
|  | 			sigA = dffD; | ||||||
|  | 
 | ||||||
|  | ffA1_end:		; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | code argQ ffB2 ffB2cemux ffB2rstmux ffB2cepol ffBrstpol sigB clock ffB1 ffB1cemux ffB1rstmux ffB1cepol | ||||||
|  | 	if (param(dsp, \BREG).as_int() == 0) { | ||||||
|  | 		argQ = sigB; | ||||||
|  | 		subpattern(in_dffe); | ||||||
|  | 		if (dff) { | ||||||
|  | 			ffB2 = dff; | ||||||
|  | 			clock = dffclock; | ||||||
|  | 			if (dffrstmux) { | ||||||
|  | 				ffB2rstmux = dffrstmux; | ||||||
|  | 				ffBrstpol = dffrstpol; | ||||||
|  | 			} | ||||||
|  | 			if (dffcemux) { | ||||||
|  | 				ffB2cemux = dffcemux; | ||||||
|  | 				ffB2cepol = dffcepol; | ||||||
|  | 			} | ||||||
|  | 			sigB = dffD; | ||||||
|  | 
 | ||||||
|  | 			// Now attempt to match B1 | ||||||
|  | 			if (ffB2) { | ||||||
|  | 				argQ = sigB; | ||||||
|  | 				subpattern(in_dffe); | ||||||
|  | 				if (dff) { | ||||||
|  | 					if ((ffB2rstmux != nullptr) ^ (dffrstmux != nullptr)) | ||||||
|  | 						goto ffB1_end; | ||||||
|  | 					if (dffrstmux) { | ||||||
|  | 						if (ffBrstpol != dffrstpol) | ||||||
|  | 							goto ffB1_end; | ||||||
|  | 						if (port(ffB2rstmux, \S) != port(dffrstmux, \S)) | ||||||
|  | 							goto ffB1_end; | ||||||
|  | 						ffB1rstmux = dffrstmux; | ||||||
|  | 					} | ||||||
|  | 
 | ||||||
|  | 					ffB1 = dff; | ||||||
|  | 					clock = dffclock; | ||||||
|  | 
 | ||||||
|  | 					if (dffcemux) { | ||||||
|  | 						ffB1cemux = dffcemux; | ||||||
|  | 						ffB1cepol = dffcepol; | ||||||
|  | 					} | ||||||
|  | 					sigB = dffD; | ||||||
|  | 
 | ||||||
|  | ffB1_end:				; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | code argQ ffD ffDcemux ffDrstmux ffDcepol ffDrstpol sigD clock | ||||||
|  | 	if (param(dsp, \DREG).as_int() == 0) { | ||||||
|  | 		argQ = sigD; | ||||||
|  | 		subpattern(in_dffe); | ||||||
|  | 		if (dff) { | ||||||
|  | 			ffD = dff; | ||||||
|  | 			clock = dffclock; | ||||||
|  | 			if (dffrstmux) { | ||||||
|  | 				ffDrstmux = dffrstmux; | ||||||
|  | 				ffDrstpol = dffrstpol; | ||||||
|  | 			} | ||||||
|  | 			if (dffcemux) { | ||||||
|  | 				ffDcemux = dffcemux; | ||||||
|  | 				ffDcepol = dffcepol; | ||||||
|  | 			} | ||||||
|  | 			sigD = dffD; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | code argD ffM ffMcemux ffMrstmux ffMcepol ffMrstpol sigM sigP clock | ||||||
|  | 	if (param(dsp, \MREG).as_int() == 0 && nusers(sigM) == 2) { | ||||||
|  | 		argD = sigM; | ||||||
|  | 		subpattern(out_dffe); | ||||||
|  | 		if (dff) { | ||||||
|  | 			ffM = dff; | ||||||
|  | 			clock = dffclock; | ||||||
|  | 			if (dffrstmux) { | ||||||
|  | 				ffMrstmux = dffrstmux; | ||||||
|  | 				ffMrstpol = dffrstpol; | ||||||
|  | 			} | ||||||
|  | 			if (dffcemux) { | ||||||
|  | 				ffMcemux = dffcemux; | ||||||
|  | 				ffMcepol = dffcepol; | ||||||
|  | 			} | ||||||
|  | 			sigM = dffQ; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	sigP = sigM; | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | match postAdd | ||||||
|  | 	// Ensure that Z mux is not already used | ||||||
|  | 	if port(dsp, \OPMODE, SigSpec()).extract(4,3).is_fully_zero() | ||||||
|  | 
 | ||||||
|  | 	select postAdd->type.in($add) | ||||||
|  | 	select GetSize(port(postAdd, \Y)) <= 48 | ||||||
|  | 	choice <IdString> AB {\A, \B} | ||||||
|  | 	select nusers(port(postAdd, AB)) <= 3 | ||||||
|  | 	filter ffMcemux || nusers(port(postAdd, AB)) == 2 | ||||||
|  | 	filter !ffMcemux || nusers(port(postAdd, AB)) == 3 | ||||||
|  | 
 | ||||||
|  | 	index <SigBit> port(postAdd, AB)[0] === sigP[0] | ||||||
|  | 	filter GetSize(port(postAdd, AB)) >= GetSize(sigP) | ||||||
|  | 	filter port(postAdd, AB).extract(0, GetSize(sigP)) == sigP | ||||||
|  | 	filter port(postAdd, AB).extract_end(GetSize(sigP)) == SigSpec(sigP[GetSize(sigP)-1], GetSize(port(postAdd, AB))-GetSize(sigP)) | ||||||
|  | 	set postAddAB AB | ||||||
|  | 	optional | ||||||
|  | endmatch | ||||||
|  | 
 | ||||||
|  | code sigC sigP | ||||||
|  | 	if (postAdd) { | ||||||
|  | 		sigC = port(postAdd, postAddAB == \A ? \B : \A); | ||||||
|  | 		sigP = port(postAdd, \Y); | ||||||
|  | 	} | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | code argD ffP ffPcemux ffPrstmux ffPcepol ffPrstpol sigP clock | ||||||
|  | 	if (param(dsp, \PREG).as_int() == 0) { | ||||||
|  | 		int users = 2; | ||||||
|  | 		// If ffMcemux and no postAdd new-value net must have three users: ffMcemux, ffM and ffPcemux | ||||||
|  | 		if (ffMcemux && !postAdd) users++; | ||||||
|  | 		if (nusers(sigP) == users) { | ||||||
|  | 			argD = sigP; | ||||||
|  | 			subpattern(out_dffe); | ||||||
|  | 			if (dff) { | ||||||
|  | 				ffP = dff; | ||||||
|  | 				clock = dffclock; | ||||||
|  | 				if (dffrstmux) { | ||||||
|  | 					ffPrstmux = dffrstmux; | ||||||
|  | 					ffPrstpol = dffrstpol; | ||||||
|  | 				} | ||||||
|  | 				if (dffcemux) { | ||||||
|  | 					ffPcemux = dffcemux; | ||||||
|  | 					ffPcepol = dffcepol; | ||||||
|  | 				} | ||||||
|  | 				sigP = dffQ; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | match postAddMux | ||||||
|  | 	if postAdd | ||||||
|  | 	if ffP | ||||||
|  | 	select postAddMux->type.in($mux) | ||||||
|  | 	select nusers(port(postAddMux, \Y)) == 2 | ||||||
|  | 	choice <IdString> AB {\A, \B} | ||||||
|  | 	index <SigSpec> port(postAddMux, AB) === sigP | ||||||
|  | 	index <SigSpec> port(postAddMux, \Y) === sigC | ||||||
|  | 	set postAddMuxAB AB | ||||||
|  | 	optional | ||||||
|  | endmatch | ||||||
|  | 
 | ||||||
|  | code sigC | ||||||
|  | 	if (postAddMux) | ||||||
|  | 		sigC = port(postAddMux, postAddMuxAB == \A ? \B : \A); | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | match overflow | ||||||
|  | 	if ffP | ||||||
|  | 	if param(dsp, \USE_PATTERN_DETECT, Const("NO_PATDET")).decode_string() == "NO_PATDET" | ||||||
|  | 	select overflow->type.in($ge) | ||||||
|  | 	select GetSize(port(overflow, \Y)) <= 48 | ||||||
|  | 	select port(overflow, \B).is_fully_const() | ||||||
|  | 	define <Const> B port(overflow, \B).as_const() | ||||||
|  | 	select std::count(B.bits.begin(), B.bits.end(), State::S1) == 1 | ||||||
|  | 	index <SigSpec> port(overflow, \A) === sigP | ||||||
|  | 	optional | ||||||
|  | endmatch | ||||||
|  | 
 | ||||||
|  | code | ||||||
|  | 	accept; | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | // ####################### | ||||||
|  | 
 | ||||||
|  | subpattern in_dffe | ||||||
|  | arg argD argQ clock | ||||||
|  | 
 | ||||||
|  | code | ||||||
|  | 	dff = nullptr; | ||||||
|  | 	for (auto c : argQ.chunks()) { | ||||||
|  | 		if (!c.wire) | ||||||
|  | 			reject; | ||||||
|  | 		if (c.wire->get_bool_attribute(\keep)) | ||||||
|  | 			reject; | ||||||
|  | 		Const init = c.wire->attributes.at(\init, State::Sx); | ||||||
|  | 		if (!init.is_fully_undef() && !init.is_fully_zero()) | ||||||
|  | 			reject; | ||||||
|  | 	} | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | match ff | ||||||
|  | 	select ff->type.in($dff) | ||||||
|  | 	// DSP48E1 does not support clock inversion | ||||||
|  | 	select param(ff, \CLK_POLARITY).as_bool() | ||||||
|  | 
 | ||||||
|  | 	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 | ||||||
|  | 
 | ||||||
|  | 	set ffoffset offset | ||||||
|  | endmatch | ||||||
|  | 
 | ||||||
|  | code argQ argD | ||||||
|  | { | ||||||
|  | 	if (clock != SigBit() && port(ff, \CLK) != clock) | ||||||
|  | 		reject; | ||||||
|  | 
 | ||||||
|  | 	SigSpec Q = port(ff, \Q); | ||||||
|  | 	dff = ff; | ||||||
|  | 	dffclock = port(ff, \CLK); | ||||||
|  | 	dffD = argQ; | ||||||
|  | 	argD = port(ff, \D); | ||||||
|  | 	argQ = Q; | ||||||
|  | 	dffD.replace(argQ, argD); | ||||||
|  | 	// Only search for ffrstmux if dffD only | ||||||
|  | 	//   has two (ff, ffrstmux) users | ||||||
|  | 	if (nusers(dffD) > 2) | ||||||
|  | 		argD = SigSpec(); | ||||||
|  | } | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | match ffrstmux | ||||||
|  | 	if !argD.empty() | ||||||
|  | 	select ffrstmux->type.in($mux) | ||||||
|  | 	index <SigSpec> port(ffrstmux, \Y) === argD | ||||||
|  | 
 | ||||||
|  | 	choice <IdString> BA {\B, \A} | ||||||
|  | 	// DSP48E1 only supports reset to zero | ||||||
|  | 	select port(ffrstmux, BA).is_fully_zero() | ||||||
|  | 
 | ||||||
|  | 	define <bool> pol (BA == \B) | ||||||
|  | 	set ffrstpol pol | ||||||
|  | 	semioptional | ||||||
|  | endmatch | ||||||
|  | 
 | ||||||
|  | code argD | ||||||
|  | 	if (ffrstmux) { | ||||||
|  | 		dffrstmux = ffrstmux; | ||||||
|  | 		dffrstpol = ffrstpol; | ||||||
|  | 		argD = port(ffrstmux, ffrstpol ? \A : \B); | ||||||
|  | 		dffD.replace(port(ffrstmux, \Y), argD); | ||||||
|  | 
 | ||||||
|  | 		// Only search for ffcemux if argQ has at | ||||||
|  | 		//   least 3 users (ff, <upstream>, ffrstmux) and | ||||||
|  | 		//   dffD only has two (ff, ffrstmux) | ||||||
|  | 		if (!(nusers(argQ) >= 3 && nusers(dffD) == 2)) | ||||||
|  | 			argD = SigSpec(); | ||||||
|  | 	} | ||||||
|  | 	else | ||||||
|  | 		dffrstmux = nullptr; | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | match ffcemux | ||||||
|  | 	if !argD.empty() | ||||||
|  | 	select ffcemux->type.in($mux) | ||||||
|  | 	index <SigSpec> port(ffcemux, \Y) === argD | ||||||
|  | 	choice <IdString> AB {\A, \B} | ||||||
|  | 	index <SigSpec> port(ffcemux, AB) === argQ | ||||||
|  | 	define <bool> pol (AB == \A) | ||||||
|  | 	set ffcepol pol | ||||||
|  | 	semioptional | ||||||
|  | endmatch | ||||||
|  | 
 | ||||||
|  | code argD | ||||||
|  | 	if (ffcemux) { | ||||||
|  | 		dffcemux = ffcemux; | ||||||
|  | 		dffcepol = ffcepol; | ||||||
|  | 		argD = port(ffcemux, ffcepol ? \B : \A); | ||||||
|  | 		dffD.replace(port(ffcemux, \Y), argD); | ||||||
|  | 	} | ||||||
|  | 	else | ||||||
|  | 		dffcemux = nullptr; | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | // ####################### | ||||||
|  | 
 | ||||||
|  | subpattern out_dffe | ||||||
|  | arg argD argQ clock | ||||||
|  | 
 | ||||||
|  | code | ||||||
|  | 	dff = nullptr; | ||||||
|  | 	for (auto c : argD.chunks()) | ||||||
|  | 		if (c.wire->get_bool_attribute(\keep)) | ||||||
|  | 			reject; | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | match ffcemux | ||||||
|  | 	select ffcemux->type.in($mux) | ||||||
|  | 	// ffcemux output must have two users: ffcemux and ff.D | ||||||
|  | 	select nusers(port(ffcemux, \Y)) == 2 | ||||||
|  | 
 | ||||||
|  | 	choice <IdString> AB {\A, \B} | ||||||
|  | 	// keep-last-value net must have at least three users: ffcemux, ff, downstream sink(s) | ||||||
|  | 	select nusers(port(ffcemux, AB)) >= 3 | ||||||
|  | 
 | ||||||
|  | 	slice offset GetSize(port(ffcemux, \Y)) | ||||||
|  | 	define <IdString> BA (AB == \A ? \B : \A) | ||||||
|  | 	index <SigBit> port(ffcemux, BA)[offset] === argD[0] | ||||||
|  | 
 | ||||||
|  | 	// Check that the rest of argD is present | ||||||
|  | 	filter GetSize(port(ffcemux, BA)) >= offset + GetSize(argD) | ||||||
|  | 	filter port(ffcemux, BA).extract(offset, GetSize(argD)) == argD | ||||||
|  | 
 | ||||||
|  | 	set ffoffset offset | ||||||
|  | 	define <bool> pol (AB == \A) | ||||||
|  | 	set ffcepol pol | ||||||
|  | 
 | ||||||
|  | 	semioptional | ||||||
|  | endmatch | ||||||
|  | 
 | ||||||
|  | code argD argQ | ||||||
|  | 	dffcemux = ffcemux; | ||||||
|  | 	if (ffcemux) { | ||||||
|  | 		SigSpec BA = port(ffcemux, ffcepol ? \B : \A); | ||||||
|  | 		SigSpec Y = port(ffcemux, \Y); | ||||||
|  | 		argQ = argD; | ||||||
|  | 		argD.replace(BA, Y); | ||||||
|  | 		argQ.replace(BA, port(ffcemux, ffcepol ? \A : \B)); | ||||||
|  | 
 | ||||||
|  | 		dffcemux = ffcemux; | ||||||
|  | 		dffcepol = ffcepol; | ||||||
|  | 	} | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | match ffrstmux | ||||||
|  | 	select ffrstmux->type.in($mux) | ||||||
|  | 	// ffrstmux output must have two users: ffrstmux and ff.D | ||||||
|  | 	select nusers(port(ffrstmux, \Y)) == 2 | ||||||
|  | 
 | ||||||
|  | 	choice <IdString> BA {\B, \A} | ||||||
|  | 	// DSP48E1 only supports reset to zero | ||||||
|  | 	select port(ffrstmux, BA).is_fully_zero() | ||||||
|  | 
 | ||||||
|  | 	slice offset GetSize(port(ffrstmux, \Y)) | ||||||
|  | 	define <IdString> AB (BA == \B ? \A : \B) | ||||||
|  | 	index <SigBit> port(ffrstmux, AB)[offset] === argD[0] | ||||||
|  | 
 | ||||||
|  | 	// Check that offset is consistent | ||||||
|  | 	filter !ffcemux || ffoffset == offset | ||||||
|  | 	// Check that the rest of argD is present | ||||||
|  | 	filter GetSize(port(ffrstmux, AB)) >= offset + GetSize(argD) | ||||||
|  | 	filter port(ffrstmux, AB).extract(offset, GetSize(argD)) == argD | ||||||
|  | 
 | ||||||
|  | 	set ffoffset offset | ||||||
|  | 	define <bool> pol (AB == \A) | ||||||
|  | 	set ffrstpol pol | ||||||
|  | 
 | ||||||
|  | 	semioptional | ||||||
|  | endmatch | ||||||
|  | 
 | ||||||
|  | code argD argQ | ||||||
|  | 	dffrstmux = ffrstmux; | ||||||
|  | 	if (ffrstmux) { | ||||||
|  | 		SigSpec AB = port(ffrstmux, ffrstpol ? \A : \B); | ||||||
|  | 		SigSpec Y = port(ffrstmux, \Y); | ||||||
|  | 		argD.replace(AB, Y); | ||||||
|  | 
 | ||||||
|  | 		dffrstmux = ffrstmux; | ||||||
|  | 		dffrstpol = ffrstpol; | ||||||
|  | 	} | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | match ff | ||||||
|  | 	select ff->type.in($dff) | ||||||
|  | 	// DSP48E1 does not support clock inversion | ||||||
|  | 	select param(ff, \CLK_POLARITY).as_bool() | ||||||
|  | 
 | ||||||
|  | 	slice offset GetSize(port(ff, \D)) | ||||||
|  | 	index <SigBit> port(ff, \D)[offset] === argD[0] | ||||||
|  | 
 | ||||||
|  | 	// Check that offset is consistent | ||||||
|  | 	filter (!ffcemux && !ffrstmux) || ffoffset == offset | ||||||
|  | 	// Check that the rest of argD is present | ||||||
|  | 	filter GetSize(port(ff, \D)) >= offset + GetSize(argD) | ||||||
|  | 	filter port(ff, \D).extract(offset, GetSize(argD)) == argD | ||||||
|  | 	// Check that FF.Q is connected to CE-mux | ||||||
|  | 	filter !ffcemux || port(ff, \Q).extract(offset, GetSize(argQ)) == argQ | ||||||
|  | 
 | ||||||
|  | 	set ffoffset offset | ||||||
|  | endmatch | ||||||
|  | 
 | ||||||
|  | code argQ | ||||||
|  | 	if (ff) { | ||||||
|  | 		if (clock != SigBit() && port(ff, \CLK) != clock) | ||||||
|  | 			reject; | ||||||
|  | 
 | ||||||
|  | 		SigSpec D = port(ff, \D); | ||||||
|  | 		SigSpec Q = port(ff, \Q); | ||||||
|  | 		if (!ffcemux) { | ||||||
|  | 			argQ = argD; | ||||||
|  | 			argQ.replace(D, Q); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		for (auto c : argQ.chunks()) { | ||||||
|  | 			Const init = c.wire->attributes.at(\init, State::Sx); | ||||||
|  | 			if (!init.is_fully_undef() && !init.is_fully_zero()) | ||||||
|  | 				reject; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		dff = ff; | ||||||
|  | 		dffQ = argQ; | ||||||
|  | 		dffclock = port(ff, \CLK); | ||||||
|  | 	} | ||||||
|  | 	// No enable/reset mux possible without flop | ||||||
|  | 	else if (dffcemux || dffrstmux) | ||||||
|  | 		reject; | ||||||
|  | endcode | ||||||
							
								
								
									
										181
									
								
								passes/pmgen/xilinx_dsp_CREG.pmg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								passes/pmgen/xilinx_dsp_CREG.pmg
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,181 @@ | ||||||
|  | pattern xilinx_dsp_packC | ||||||
|  | 
 | ||||||
|  | udata <std::function<SigSpec(const SigSpec&)>> unextend | ||||||
|  | state <SigBit> clock | ||||||
|  | state <SigSpec> sigC sigP | ||||||
|  | state <bool> ffCcepol ffCrstpol | ||||||
|  | state <Cell*> ffC ffCcemux ffCrstmux | ||||||
|  | 
 | ||||||
|  | // subpattern | ||||||
|  | state <SigSpec> argQ argD | ||||||
|  | state <bool> ffcepol ffrstpol | ||||||
|  | state <int> ffoffset | ||||||
|  | udata <SigSpec> dffD dffQ | ||||||
|  | udata <SigBit> dffclock | ||||||
|  | udata <Cell*> dff dffcemux dffrstmux | ||||||
|  | udata <bool> dffcepol dffrstpol | ||||||
|  | 
 | ||||||
|  | match dsp | ||||||
|  | 	select dsp->type.in(\DSP48E1) | ||||||
|  | 	select param(dsp, \CREG, 1).as_int() == 0 | ||||||
|  | 	select nusers(port(dsp, \C, SigSpec())) > 1 | ||||||
|  | endmatch | ||||||
|  | 
 | ||||||
|  | code argQ ffC ffCcemux ffCrstmux ffCcepol ffCrstpol sigC sigP clock | ||||||
|  | 	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); | ||||||
|  | 	}; | ||||||
|  | 	sigC = unextend(port(dsp, \C, SigSpec())); | ||||||
|  | 
 | ||||||
|  | 	SigSpec P = port(dsp, \P); | ||||||
|  | 	if (param(dsp, \USE_MULT, Const("MULTIPLY")).decode_string() == "MULTIPLY") { | ||||||
|  | 		// Only care about those bits that are used | ||||||
|  | 		int i; | ||||||
|  | 		for (i = 0; i < GetSize(P); i++) { | ||||||
|  | 			if (nusers(P[i]) <= 1) | ||||||
|  | 				break; | ||||||
|  | 			sigP.append(P[i]); | ||||||
|  | 		} | ||||||
|  | 		log_assert(nusers(P.extract_end(i)) <= 1); | ||||||
|  | 	} | ||||||
|  | 	else | ||||||
|  | 		sigP = P; | ||||||
|  | 
 | ||||||
|  | 	if (sigC == sigP) | ||||||
|  | 		reject; | ||||||
|  | 
 | ||||||
|  | 	clock = port(dsp, \CLK, SigBit()); | ||||||
|  | 
 | ||||||
|  | 	argQ = sigC; | ||||||
|  | 	subpattern(in_dffe); | ||||||
|  | 	if (dff) { | ||||||
|  | 		ffC = dff; | ||||||
|  | 		clock = dffclock; | ||||||
|  | 		if (dffrstmux) { | ||||||
|  | 			ffCrstmux = dffrstmux; | ||||||
|  | 			ffCrstpol = dffrstpol; | ||||||
|  | 		} | ||||||
|  | 		if (dffcemux) { | ||||||
|  | 			ffCcemux = dffcemux; | ||||||
|  | 			ffCcepol = dffcepol; | ||||||
|  | 		} | ||||||
|  | 		sigC = dffD; | ||||||
|  | 	} | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | code | ||||||
|  | 	if (ffC) | ||||||
|  | 		accept; | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | // ####################### | ||||||
|  | 
 | ||||||
|  | subpattern in_dffe | ||||||
|  | arg argD argQ clock | ||||||
|  | 
 | ||||||
|  | code | ||||||
|  | 	dff = nullptr; | ||||||
|  | 	for (auto c : argQ.chunks()) { | ||||||
|  | 		if (!c.wire) | ||||||
|  | 			reject; | ||||||
|  | 		if (c.wire->get_bool_attribute(\keep)) | ||||||
|  | 			reject; | ||||||
|  | 		Const init = c.wire->attributes.at(\init, State::Sx); | ||||||
|  | 		if (!init.is_fully_undef() && !init.is_fully_zero()) | ||||||
|  | 			reject; | ||||||
|  | 	} | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | match ff | ||||||
|  | 	select ff->type.in($dff) | ||||||
|  | 	// DSP48E1 does not support clock inversion | ||||||
|  | 	select param(ff, \CLK_POLARITY).as_bool() | ||||||
|  | 
 | ||||||
|  | 	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 | ||||||
|  | 
 | ||||||
|  | 	set ffoffset offset | ||||||
|  | endmatch | ||||||
|  | 
 | ||||||
|  | code argQ argD | ||||||
|  | { | ||||||
|  | 	if (clock != SigBit() && port(ff, \CLK) != clock) | ||||||
|  | 		reject; | ||||||
|  | 
 | ||||||
|  | 	SigSpec Q = port(ff, \Q); | ||||||
|  | 	dff = ff; | ||||||
|  | 	dffclock = port(ff, \CLK); | ||||||
|  | 	dffD = argQ; | ||||||
|  | 	argD = port(ff, \D); | ||||||
|  | 	argQ = Q; | ||||||
|  | 	dffD.replace(argQ, argD); | ||||||
|  | 	// Only search for ffrstmux if dffD only | ||||||
|  | 	//   has two (ff, ffrstmux) users | ||||||
|  | 	if (nusers(dffD) > 2) | ||||||
|  | 		argD = SigSpec(); | ||||||
|  | } | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | match ffrstmux | ||||||
|  | 	if !argD.empty() | ||||||
|  | 	select ffrstmux->type.in($mux) | ||||||
|  | 	index <SigSpec> port(ffrstmux, \Y) === argD | ||||||
|  | 
 | ||||||
|  | 	choice <IdString> BA {\B, \A} | ||||||
|  | 	// DSP48E1 only supports reset to zero | ||||||
|  | 	select port(ffrstmux, BA).is_fully_zero() | ||||||
|  | 
 | ||||||
|  | 	define <bool> pol (BA == \B) | ||||||
|  | 	set ffrstpol pol | ||||||
|  | 	semioptional | ||||||
|  | endmatch | ||||||
|  | 
 | ||||||
|  | code argD | ||||||
|  | 	if (ffrstmux) { | ||||||
|  | 		dffrstmux = ffrstmux; | ||||||
|  | 		dffrstpol = ffrstpol; | ||||||
|  | 		argD = port(ffrstmux, ffrstpol ? \A : \B); | ||||||
|  | 		dffD.replace(port(ffrstmux, \Y), argD); | ||||||
|  | 
 | ||||||
|  | 		// Only search for ffcemux if argQ has at | ||||||
|  | 		//   least 3 users (ff, <upstream>, ffrstmux) and | ||||||
|  | 		//   dffD only has two (ff, ffrstmux) | ||||||
|  | 		if (!(nusers(argQ) >= 3 && nusers(dffD) == 2)) | ||||||
|  | 			argD = SigSpec(); | ||||||
|  | 	} | ||||||
|  | 	else | ||||||
|  | 		dffrstmux = nullptr; | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | match ffcemux | ||||||
|  | 	if !argD.empty() | ||||||
|  | 	select ffcemux->type.in($mux) | ||||||
|  | 	index <SigSpec> port(ffcemux, \Y) === argD | ||||||
|  | 	choice <IdString> AB {\A, \B} | ||||||
|  | 	index <SigSpec> port(ffcemux, AB) === argQ | ||||||
|  | 	define <bool> pol (AB == \A) | ||||||
|  | 	set ffcepol pol | ||||||
|  | 	semioptional | ||||||
|  | endmatch | ||||||
|  | 
 | ||||||
|  | code argD | ||||||
|  | 	if (ffcemux) { | ||||||
|  | 		dffcemux = ffcemux; | ||||||
|  | 		dffcepol = ffcepol; | ||||||
|  | 		argD = port(ffcemux, ffcepol ? \B : \A); | ||||||
|  | 		dffD.replace(port(ffcemux, \Y), argD); | ||||||
|  | 	} | ||||||
|  | 	else | ||||||
|  | 		dffcemux = nullptr; | ||||||
|  | endcode | ||||||
							
								
								
									
										336
									
								
								passes/pmgen/xilinx_dsp_cascade.pmg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										336
									
								
								passes/pmgen/xilinx_dsp_cascade.pmg
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,336 @@ | ||||||
|  | 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 <SigSpec> clock | ||||||
|  | state <int> AREG BREG | ||||||
|  | 
 | ||||||
|  | // subpattern | ||||||
|  | state <SigSpec> argQ argD | ||||||
|  | state <bool> ffcepol ffrstpol | ||||||
|  | state <int> ffoffset | ||||||
|  | udata <SigSpec> dffD dffQ | ||||||
|  | udata <SigBit> dffclock | ||||||
|  | udata <Cell*> dff dffcemux dffrstmux | ||||||
|  | udata <bool> dffcepol dffrstpol | ||||||
|  | 
 | ||||||
|  | code | ||||||
|  | #define MAX_DSP_CASCADE 20 | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | match first | ||||||
|  | 	select first->type.in(\DSP48E1) | ||||||
|  | 	select port(first, \OPMODE, Const(0, 7)).extract(4,3) == Const::from_string("000") | ||||||
|  | 	select nusers(port(first, \PCOUT, SigSpec())) <= 1 | ||||||
|  | endmatch | ||||||
|  | 
 | ||||||
|  | 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 (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); | ||||||
|  | 
 | ||||||
|  | 					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); | ||||||
|  | 					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); | ||||||
|  | 
 | ||||||
|  | 					dsp->setParam(ID(BCASCREG), BREG); | ||||||
|  | 					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 | ||||||
|  | 
 | ||||||
|  | match nextP | ||||||
|  | 	select nextP->type.in(\DSP48E1) | ||||||
|  | 	select !param(nextP, \CREG, State::S1).as_bool() | ||||||
|  | 	select 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 | ||||||
|  | 
 | ||||||
|  | match nextP_shift17 | ||||||
|  | 	if !nextP | ||||||
|  | 	select nextP_shift17->type.in(\DSP48E1) | ||||||
|  | 	select !param(nextP_shift17, \CREG, State::S1).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) { | ||||||
|  | 		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 | ||||||
|  | 
 | ||||||
|  | code argQ clock AREG | ||||||
|  | 	AREG = -1; | ||||||
|  | 	if (next) { | ||||||
|  | 		Cell *prev = std::get<0>(chain.back()); | ||||||
|  | 		if (param(prev, \AREG, 2).as_int() > 0 && | ||||||
|  | 				param(next, \AREG, 2).as_int() > 0 && | ||||||
|  | 				param(next, \A_INPUT, Const("DIRECT")).decode_string() == "DIRECT" && | ||||||
|  | 				port(next, \ACIN, SigSpec()).is_fully_zero() && | ||||||
|  | 				nusers(port(prev, \ACOUT, SigSpec())) <= 1) { | ||||||
|  | 			argQ = unextend(port(next, \A)); | ||||||
|  | 			clock = port(prev, \CLK); | ||||||
|  | 			subpattern(in_dffe); | ||||||
|  | 			if (dff) { | ||||||
|  | 				if (!dffrstmux && port(prev, \RSTA, State::S0) != State::S0) | ||||||
|  | 					goto reject_AREG; | ||||||
|  | 				if (dffrstmux && port(dffrstmux, \S) != port(prev, \RSTA, State::S0)) | ||||||
|  | 					goto reject_AREG; | ||||||
|  | 				if (!dffcemux && port(prev, \CEA2, State::S0) != State::S0) | ||||||
|  | 					goto reject_AREG; | ||||||
|  | 				if (dffcemux && port(dffcemux, \S) != port(prev, \CEA2, State::S0)) | ||||||
|  | 					goto reject_AREG; | ||||||
|  | 				if (dffD == unextend(port(prev, \A))) | ||||||
|  | 					AREG = 1; | ||||||
|  | reject_AREG:			; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | code argQ clock BREG | ||||||
|  | 	BREG = -1; | ||||||
|  | 	if (next) { | ||||||
|  | 		Cell *prev = std::get<0>(chain.back()); | ||||||
|  | 		if (param(prev, \BREG, 2).as_int() > 0 && | ||||||
|  | 				param(next, \BREG, 2).as_int() > 0 && | ||||||
|  | 				param(next, \B_INPUT, Const("DIRECT")).decode_string() == "DIRECT" && | ||||||
|  | 				port(next, \BCIN, SigSpec()).is_fully_zero() && | ||||||
|  | 				nusers(port(prev, \BCOUT, SigSpec())) <= 1) { | ||||||
|  | 			argQ = unextend(port(next, \B)); | ||||||
|  | 			clock = port(prev, \CLK); | ||||||
|  | 			subpattern(in_dffe); | ||||||
|  | 			if (dff) { | ||||||
|  | 				if (!dffrstmux && port(prev, \RSTB, State::S0) != State::S0) | ||||||
|  | 					goto reject_BREG; | ||||||
|  | 				if (dffrstmux && port(dffrstmux, \S) != port(prev, \RSTB, State::S0)) | ||||||
|  | 					goto reject_BREG; | ||||||
|  | 				if (!dffcemux && port(prev, \CEB2, State::S0) != State::S0) | ||||||
|  | 					goto reject_BREG; | ||||||
|  | 				if (dffcemux && port(dffcemux, \S) != port(prev, \CEB2, State::S0)) | ||||||
|  | 					goto reject_BREG; | ||||||
|  | 				if (dffD == unextend(port(prev, \B))) | ||||||
|  | 					BREG = 1; | ||||||
|  | reject_BREG:			; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | code | ||||||
|  | 	if (next) { | ||||||
|  | 		chain.emplace_back(next, nextP_shift17 ? 17 : nextP ? 0 : -1, AREG, BREG); | ||||||
|  | 
 | ||||||
|  | 		SigSpec sigC = unextend(port(next, \C)); | ||||||
|  | 
 | ||||||
|  | 		// TODO: Cannot use 'reject' since semioptional | ||||||
|  | 		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 in_dffe | ||||||
|  | arg argD argQ clock | ||||||
|  | 
 | ||||||
|  | code | ||||||
|  | 	dff = nullptr; | ||||||
|  | 	for (auto c : argQ.chunks()) { | ||||||
|  | 		if (!c.wire) | ||||||
|  | 			reject; | ||||||
|  | 		if (c.wire->get_bool_attribute(\keep)) | ||||||
|  | 			reject; | ||||||
|  | 		Const init = c.wire->attributes.at(\init, State::Sx); | ||||||
|  | 		if (!init.is_fully_undef() && !init.is_fully_zero()) | ||||||
|  | 			reject; | ||||||
|  | 	} | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | match ff | ||||||
|  | 	select ff->type.in($dff) | ||||||
|  | 	// DSP48E1 does not support clock inversion | ||||||
|  | 	select param(ff, \CLK_POLARITY).as_bool() | ||||||
|  | 
 | ||||||
|  | 	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 | ||||||
|  | 
 | ||||||
|  | 	set ffoffset offset | ||||||
|  | endmatch | ||||||
|  | 
 | ||||||
|  | code argQ argD | ||||||
|  | { | ||||||
|  | 	if (clock != SigBit() && port(ff, \CLK) != clock) | ||||||
|  | 		reject; | ||||||
|  | 
 | ||||||
|  | 	SigSpec Q = port(ff, \Q); | ||||||
|  | 	dff = ff; | ||||||
|  | 	dffclock = port(ff, \CLK); | ||||||
|  | 	dffD = argQ; | ||||||
|  | 	argD = port(ff, \D); | ||||||
|  | 	argQ = Q; | ||||||
|  | 	dffD.replace(argQ, argD); | ||||||
|  | 	// Only search for ffrstmux if dffD only | ||||||
|  | 	//   has two (ff, ffrstmux) users | ||||||
|  | 	if (nusers(dffD) > 2) | ||||||
|  | 		argD = SigSpec(); | ||||||
|  | } | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | match ffrstmux | ||||||
|  | 	if !argD.empty() | ||||||
|  | 	select ffrstmux->type.in($mux) | ||||||
|  | 	index <SigSpec> port(ffrstmux, \Y) === argD | ||||||
|  | 
 | ||||||
|  | 	choice <IdString> BA {\B, \A} | ||||||
|  | 	// DSP48E1 only supports reset to zero | ||||||
|  | 	select port(ffrstmux, BA).is_fully_zero() | ||||||
|  | 
 | ||||||
|  | 	define <bool> pol (BA == \B) | ||||||
|  | 	set ffrstpol pol | ||||||
|  | 	semioptional | ||||||
|  | endmatch | ||||||
|  | 
 | ||||||
|  | code argD | ||||||
|  | 	if (ffrstmux) { | ||||||
|  | 		dffrstmux = ffrstmux; | ||||||
|  | 		dffrstpol = ffrstpol; | ||||||
|  | 		argD = port(ffrstmux, ffrstpol ? \A : \B); | ||||||
|  | 		dffD.replace(port(ffrstmux, \Y), argD); | ||||||
|  | 
 | ||||||
|  | 		// Only search for ffcemux if argQ has at | ||||||
|  | 		//   least 3 users (ff, <upstream>, ffrstmux) and | ||||||
|  | 		//   dffD only has two (ff, ffrstmux) | ||||||
|  | 		if (!(nusers(argQ) >= 3 && nusers(dffD) == 2)) | ||||||
|  | 			argD = SigSpec(); | ||||||
|  | 	} | ||||||
|  | 	else | ||||||
|  | 		dffrstmux = nullptr; | ||||||
|  | endcode | ||||||
|  | 
 | ||||||
|  | match ffcemux | ||||||
|  | 	if !argD.empty() | ||||||
|  | 	select ffcemux->type.in($mux) | ||||||
|  | 	index <SigSpec> port(ffcemux, \Y) === argD | ||||||
|  | 	choice <IdString> AB {\A, \B} | ||||||
|  | 	index <SigSpec> port(ffcemux, AB) === argQ | ||||||
|  | 	define <bool> pol (AB == \A) | ||||||
|  | 	set ffcepol pol | ||||||
|  | 	semioptional | ||||||
|  | endmatch | ||||||
|  | 
 | ||||||
|  | code argD | ||||||
|  | 	if (ffcemux) { | ||||||
|  | 		dffcemux = ffcemux; | ||||||
|  | 		dffcepol = ffcepol; | ||||||
|  | 		argD = port(ffcemux, ffcepol ? \B : \A); | ||||||
|  | 		dffD.replace(port(ffcemux, \Y), argD); | ||||||
|  | 	} | ||||||
|  | 	else | ||||||
|  | 		dffcemux = nullptr; | ||||||
|  | endcode | ||||||
|  | @ -13,9 +13,9 @@ endcode | ||||||
| match first | match first | ||||||
| 	select first->type.in($_DFF_N_, $_DFF_P_, $_DFFE_NN_, $_DFFE_NP_, $_DFFE_PN_, $_DFFE_PP_, \FDRE, \FDRE_1) | 	select first->type.in($_DFF_N_, $_DFF_P_, $_DFFE_NN_, $_DFFE_NP_, $_DFFE_PN_, $_DFFE_PP_, \FDRE, \FDRE_1) | ||||||
| 	select !first->has_keep_attr() | 	select !first->has_keep_attr() | ||||||
| 	select !first->type.in(\FDRE) || !first->parameters.at(\IS_R_INVERTED, State::S0).as_bool() | 	select !first->type.in(\FDRE) || !param(first, \IS_R_INVERTED, State::S0).as_bool() | ||||||
| 	select !first->type.in(\FDRE) || !first->parameters.at(\IS_D_INVERTED, State::S0).as_bool() | 	select !first->type.in(\FDRE) || !param(first, \IS_D_INVERTED, State::S0).as_bool() | ||||||
| 	select !first->type.in(\FDRE, \FDRE_1) || first->connections_.at(\R, State::S0).is_fully_zero() | 	select !first->type.in(\FDRE, \FDRE_1) || port(first, \R, State::S0).is_fully_zero() | ||||||
| 	filter !non_first_cells.count(first) | 	filter !non_first_cells.count(first) | ||||||
| generate | generate | ||||||
| 	SigSpec C = module->addWire(NEW_ID); | 	SigSpec C = module->addWire(NEW_ID); | ||||||
|  | @ -84,9 +84,9 @@ arg en_port | ||||||
| match first | match first | ||||||
| 	select first->type.in($_DFF_N_, $_DFF_P_, $_DFFE_NN_, $_DFFE_NP_, $_DFFE_PN_, $_DFFE_PP_, \FDRE, \FDRE_1) | 	select first->type.in($_DFF_N_, $_DFF_P_, $_DFFE_NN_, $_DFFE_NP_, $_DFFE_PN_, $_DFFE_PP_, \FDRE, \FDRE_1) | ||||||
| 	select !first->has_keep_attr() | 	select !first->has_keep_attr() | ||||||
| 	select !first->type.in(\FDRE) || !first->parameters.at(\IS_R_INVERTED, State::S0).as_bool() | 	select !first->type.in(\FDRE) || !param(first, \IS_R_INVERTED, State::S0).as_bool() | ||||||
| 	select !first->type.in(\FDRE) || !first->parameters.at(\IS_D_INVERTED, State::S0).as_bool() | 	select !first->type.in(\FDRE) || !param(first, \IS_D_INVERTED, State::S0).as_bool() | ||||||
| 	select !first->type.in(\FDRE, \FDRE_1) || first->connections_.at(\R, State::S0).is_fully_zero() | 	select !first->type.in(\FDRE, \FDRE_1) || port(first, \R, State::S0).is_fully_zero() | ||||||
| endmatch | endmatch | ||||||
| 
 | 
 | ||||||
| code clk_port en_port | code clk_port en_port | ||||||
|  | @ -111,10 +111,10 @@ match next | ||||||
| 	index <SigBit> port(next, \Q) === port(first, \D) | 	index <SigBit> port(next, \Q) === port(first, \D) | ||||||
| 	filter port(next, clk_port) == port(first, clk_port) | 	filter port(next, clk_port) == port(first, clk_port) | ||||||
| 	filter en_port == IdString() || port(next, en_port) == port(first, en_port) | 	filter en_port == IdString() || port(next, en_port) == port(first, en_port) | ||||||
| 	filter !first->type.in(\FDRE) || next->parameters.at(\IS_C_INVERTED, State::S0).as_bool() == first->parameters.at(\IS_C_INVERTED, State::S0).as_bool() | 	filter !first->type.in(\FDRE) || param(next, \IS_C_INVERTED, State::S0).as_bool() == param(first, \IS_C_INVERTED, State::S0).as_bool() | ||||||
| 	filter !first->type.in(\FDRE) || next->parameters.at(\IS_D_INVERTED, State::S0).as_bool() == first->parameters.at(\IS_D_INVERTED, State::S0).as_bool() | 	filter !first->type.in(\FDRE) || param(next, \IS_D_INVERTED, State::S0).as_bool() == param(first, \IS_D_INVERTED, State::S0).as_bool() | ||||||
| 	filter !first->type.in(\FDRE) || next->parameters.at(\IS_R_INVERTED, State::S0).as_bool() == first->parameters.at(\IS_R_INVERTED, State::S0).as_bool() | 	filter !first->type.in(\FDRE) || param(next, \IS_R_INVERTED, State::S0).as_bool() == param(first, \IS_R_INVERTED, State::S0).as_bool() | ||||||
| 	filter !first->type.in(\FDRE, \FDRE_1) || next->connections_.at(\R, State::S0).is_fully_zero() | 	filter !first->type.in(\FDRE, \FDRE_1) || port(next, \R, State::S0).is_fully_zero() | ||||||
| endmatch | endmatch | ||||||
| 
 | 
 | ||||||
| code | code | ||||||
|  | @ -138,10 +138,10 @@ match next | ||||||
| 	index <SigBit> port(next, \Q) === port(chain.back(), \D) | 	index <SigBit> port(next, \Q) === port(chain.back(), \D) | ||||||
| 	filter port(next, clk_port) == port(first, clk_port) | 	filter port(next, clk_port) == port(first, clk_port) | ||||||
| 	filter en_port == IdString() || port(next, en_port) == port(first, en_port) | 	filter en_port == IdString() || port(next, en_port) == port(first, en_port) | ||||||
| 	filter !first->type.in(\FDRE) || next->parameters.at(\IS_C_INVERTED, State::S0).as_bool() == first->parameters.at(\IS_C_INVERTED, State::S0).as_bool() | 	filter !first->type.in(\FDRE) || param(next, \IS_C_INVERTED, State::S0).as_bool() == param(first, \IS_C_INVERTED, State::S0).as_bool() | ||||||
| 	filter !first->type.in(\FDRE) || next->parameters.at(\IS_D_INVERTED, State::S0).as_bool() == first->parameters.at(\IS_D_INVERTED, State::S0).as_bool() | 	filter !first->type.in(\FDRE) || param(next, \IS_D_INVERTED, State::S0).as_bool() == param(first, \IS_D_INVERTED, State::S0).as_bool() | ||||||
| 	filter !first->type.in(\FDRE) || next->parameters.at(\IS_R_INVERTED, State::S0).as_bool() == first->parameters.at(\IS_R_INVERTED, State::S0).as_bool() | 	filter !first->type.in(\FDRE) || param(next, \IS_R_INVERTED, State::S0).as_bool() == param(first, \IS_R_INVERTED, State::S0).as_bool() | ||||||
| 	filter !first->type.in(\FDRE, \FDRE_1) || next->connections_.at(\R, State::S0).is_fully_zero() | 	filter !first->type.in(\FDRE, \FDRE_1) || port(next, \R, State::S0).is_fully_zero() | ||||||
| generate | generate | ||||||
| 	Cell *cell = module->addCell(NEW_ID, chain.back()->type); | 	Cell *cell = module->addCell(NEW_ID, chain.back()->type); | ||||||
| 	cell->setPort(\C, chain.back()->getPort(\C)); | 	cell->setPort(\C, chain.back()->getPort(\C)); | ||||||
|  | @ -149,7 +149,7 @@ generate | ||||||
| 	cell->setPort(\Q, chain.back()->getPort(\D)); | 	cell->setPort(\Q, chain.back()->getPort(\D)); | ||||||
| 	if (cell->type == \FDRE) { | 	if (cell->type == \FDRE) { | ||||||
| 		if (rng(2) == 0) | 		if (rng(2) == 0) | ||||||
| 			cell->setPort(\R, chain.back()->connections_.at(\R, State::S0)); | 			cell->setPort(\R, port(chain.back(), \R, State::S0)); | ||||||
| 		cell->setPort(\CE, chain.back()->getPort(\CE)); | 		cell->setPort(\CE, chain.back()->getPort(\CE)); | ||||||
| 	} | 	} | ||||||
| 	else if (cell->type.begins_with("$_DFFE_")) | 	else if (cell->type.begins_with("$_DFFE_")) | ||||||
|  |  | ||||||
|  | @ -606,7 +606,6 @@ void abc9_module(RTLIL::Design *design, RTLIL::Module *current_module, std::stri | ||||||
| 				existing_cell = module->cell(c->name); | 				existing_cell = module->cell(c->name); | ||||||
| 				log_assert(existing_cell); | 				log_assert(existing_cell); | ||||||
| 				cell = module->addCell(remap_name(c->name), c->type); | 				cell = module->addCell(remap_name(c->name), c->type); | ||||||
| 				module->swap_names(cell, existing_cell); |  | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			if (markgroups) cell->attributes[ID(abcgroup)] = map_autoidx; | 			if (markgroups) cell->attributes[ID(abcgroup)] = map_autoidx; | ||||||
|  | @ -642,8 +641,22 @@ void abc9_module(RTLIL::Design *design, RTLIL::Module *current_module, std::stri | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		for (auto cell : boxes) | 		for (auto existing_cell : boxes) { | ||||||
| 			module->remove(cell); | 			Cell *cell = module->cell(remap_name(existing_cell->name)); | ||||||
|  | 			if (cell) { | ||||||
|  | 				for (auto &conn : existing_cell->connections()) { | ||||||
|  | 					if (!conn.second.is_wire()) | ||||||
|  | 						continue; | ||||||
|  | 					Wire *wire = conn.second.as_wire(); | ||||||
|  | 					if (!wire->get_bool_attribute(ID(abc_padding))) | ||||||
|  | 						continue; | ||||||
|  | 					cell->unsetPort(conn.first); | ||||||
|  | 					log_debug("Dropping padded port connection for %s (%s) .%s (%s )\n", log_id(cell), cell->type.c_str(), log_id(conn.first), log_signal(conn.second)); | ||||||
|  | 				} | ||||||
|  | 				module->swap_names(cell, existing_cell); | ||||||
|  | 			} | ||||||
|  | 			module->remove(existing_cell); | ||||||
|  | 		} | ||||||
| 
 | 
 | ||||||
| 		// Copy connections (and rename) from mapped_mod to module
 | 		// Copy connections (and rename) from mapped_mod to module
 | ||||||
| 		for (auto conn : mapped_mod->connections()) { | 		for (auto conn : mapped_mod->connections()) { | ||||||
|  |  | ||||||
|  | @ -351,6 +351,11 @@ struct TestAutotbBackend : public Backend { | ||||||
| 		log("    -n <int>\n"); | 		log("    -n <int>\n"); | ||||||
| 		log("        number of iterations the test bench should run (default = 1000)\n"); | 		log("        number of iterations the test bench should run (default = 1000)\n"); | ||||||
| 		log("\n"); | 		log("\n"); | ||||||
|  | 		log("    -seed <int>\n"); | ||||||
|  | 		log("        seed used for pseudo-random number generation (default = 0).\n"); | ||||||
|  | 		log("        a value of 0 will cause an arbitrary seed to be chosen, based on\n"); | ||||||
|  | 		log("        the current system time.\n"); | ||||||
|  | 		log("\n"); | ||||||
| 	} | 	} | ||||||
| 	void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE | 	void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE | ||||||
| 	{ | 	{ | ||||||
|  |  | ||||||
|  | @ -28,4 +28,5 @@ $(eval $(call add_share_file,share,techlibs/common/dff2ff.v)) | ||||||
| $(eval $(call add_share_file,share,techlibs/common/gate2lut.v)) | $(eval $(call add_share_file,share,techlibs/common/gate2lut.v)) | ||||||
| $(eval $(call add_share_file,share,techlibs/common/cmp2lut.v)) | $(eval $(call add_share_file,share,techlibs/common/cmp2lut.v)) | ||||||
| $(eval $(call add_share_file,share,techlibs/common/cells.lib)) | $(eval $(call add_share_file,share,techlibs/common/cells.lib)) | ||||||
|  | $(eval $(call add_share_file,share,techlibs/common/mul2dsp.v)) | ||||||
| $(eval $(call add_share_file,share,techlibs/common/dummy.box)) | $(eval $(call add_share_file,share,techlibs/common/dummy.box)) | ||||||
|  |  | ||||||
							
								
								
									
										296
									
								
								techlibs/common/mul2dsp.v
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										296
									
								
								techlibs/common/mul2dsp.v
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,296 @@ | ||||||
|  | /* | ||||||
|  |  *  yosys -- Yosys Open SYnthesis Suite | ||||||
|  |  * | ||||||
|  |  *  Copyright (C) 2012  Clifford Wolf <clifford@clifford.at> | ||||||
|  |  *                2019  Eddie Hung    <eddie@fpgeh.com> | ||||||
|  |  *                2019  David Shah    <dave@ds0.me> | ||||||
|  |  * | ||||||
|  |  *  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. | ||||||
|  |  * | ||||||
|  |  *  --- | ||||||
|  |  * | ||||||
|  |  *  Tech-mapping rules for decomposing arbitrarily-sized $mul cells | ||||||
|  |  *  into an equivalent collection of smaller `DSP_NAME cells (with the  | ||||||
|  |  *  same interface as $mul) no larger than `DSP_[AB]_MAXWIDTH, attached  | ||||||
|  |  *  to $shl and $add cells. | ||||||
|  |  * | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | `ifndef DSP_A_MAXWIDTH | ||||||
|  | $fatal(1, "Macro DSP_A_MAXWIDTH must be defined"); | ||||||
|  | `endif | ||||||
|  | `ifndef DSP_B_MAXWIDTH | ||||||
|  | $fatal(1, "Macro DSP_B_MAXWIDTH must be defined"); | ||||||
|  | `endif | ||||||
|  | `ifndef DSP_B_MAXWIDTH | ||||||
|  | $fatal(1, "Macro DSP_B_MAXWIDTH must be defined"); | ||||||
|  | `endif | ||||||
|  | `ifndef DSP_A_MAXWIDTH_PARTIAL | ||||||
|  | `define DSP_A_MAXWIDTH_PARTIAL `DSP_A_MAXWIDTH | ||||||
|  | `endif | ||||||
|  | `ifndef DSP_B_MAXWIDTH_PARTIAL | ||||||
|  | `define DSP_B_MAXWIDTH_PARTIAL `DSP_B_MAXWIDTH | ||||||
|  | `endif | ||||||
|  | 
 | ||||||
|  | `ifndef DSP_NAME | ||||||
|  | $fatal(1, "Macro DSP_NAME must be defined"); | ||||||
|  | `endif | ||||||
|  | 
 | ||||||
|  | `define MAX(a,b) (a > b ? a : b) | ||||||
|  | `define MIN(a,b) (a < b ? a : b) | ||||||
|  | 
 | ||||||
|  | (* techmap_celltype = "$mul $__mul" *) | ||||||
|  | module _80_mul (A, B, Y); | ||||||
|  | 	parameter A_SIGNED = 0; | ||||||
|  | 	parameter B_SIGNED = 0; | ||||||
|  | 	parameter A_WIDTH = 1; | ||||||
|  | 	parameter B_WIDTH = 1; | ||||||
|  | 	parameter Y_WIDTH = 1; | ||||||
|  | 
 | ||||||
|  | 	input [A_WIDTH-1:0] A; | ||||||
|  | 	input [B_WIDTH-1:0] B; | ||||||
|  | 	output [Y_WIDTH-1:0] Y; | ||||||
|  | 
 | ||||||
|  | 	parameter _TECHMAP_CELLTYPE_ = ""; | ||||||
|  | 
 | ||||||
|  | 	generate | ||||||
|  | 	if (0) begin end | ||||||
|  | `ifdef DSP_A_MINWIDTH | ||||||
|  | 	else if (A_WIDTH < `DSP_A_MINWIDTH) | ||||||
|  | 		wire _TECHMAP_FAIL_ = 1; | ||||||
|  | `endif | ||||||
|  | `ifdef DSP_B_MINWIDTH | ||||||
|  | 	else if (B_WIDTH < `DSP_B_MINWIDTH) | ||||||
|  | 		wire _TECHMAP_FAIL_ = 1; | ||||||
|  | `endif | ||||||
|  | `ifdef DSP_Y_MINWIDTH | ||||||
|  | 	else if (Y_WIDTH < `DSP_Y_MINWIDTH) | ||||||
|  | 		wire _TECHMAP_FAIL_ = 1; | ||||||
|  | `endif | ||||||
|  | `ifdef DSP_SIGNEDONLY | ||||||
|  | 	else if (_TECHMAP_CELLTYPE_ == "$mul" && !A_SIGNED && !B_SIGNED) | ||||||
|  | 		\$mul #( | ||||||
|  | 			.A_SIGNED(1), | ||||||
|  | 			.B_SIGNED(1), | ||||||
|  | 			.A_WIDTH(A_WIDTH + 1), | ||||||
|  | 			.B_WIDTH(B_WIDTH + 1), | ||||||
|  | 			.Y_WIDTH(Y_WIDTH) | ||||||
|  | 		) _TECHMAP_REPLACE_ ( | ||||||
|  | 			.A({1'b0, A}), | ||||||
|  | 			.B({1'b0, B}), | ||||||
|  | 			.Y(Y) | ||||||
|  | 		); | ||||||
|  | `endif | ||||||
|  | 	else if (_TECHMAP_CELLTYPE_ == "$mul" && A_WIDTH < B_WIDTH) | ||||||
|  | 		\$mul #( | ||||||
|  | 			.A_SIGNED(B_SIGNED), | ||||||
|  | 			.B_SIGNED(A_SIGNED), | ||||||
|  | 			.A_WIDTH(B_WIDTH), | ||||||
|  | 			.B_WIDTH(A_WIDTH), | ||||||
|  | 			.Y_WIDTH(Y_WIDTH) | ||||||
|  | 		) _TECHMAP_REPLACE_ ( | ||||||
|  | 			.A(B), | ||||||
|  | 			.B(A), | ||||||
|  | 			.Y(Y) | ||||||
|  | 		); | ||||||
|  | 	else begin | ||||||
|  | 		wire [1023:0] _TECHMAP_DO_ = "proc; clean"; | ||||||
|  | 
 | ||||||
|  | `ifdef DSP_SIGNEDONLY | ||||||
|  | 		localparam sign_headroom = 1; | ||||||
|  | `else | ||||||
|  | 		localparam sign_headroom = 0; | ||||||
|  | `endif | ||||||
|  | 
 | ||||||
|  | 		genvar i; | ||||||
|  | 		if (A_WIDTH > `DSP_A_MAXWIDTH) begin | ||||||
|  | 			localparam n = (A_WIDTH-`DSP_A_MAXWIDTH+`DSP_A_MAXWIDTH_PARTIAL-sign_headroom-1) / (`DSP_A_MAXWIDTH_PARTIAL-sign_headroom); | ||||||
|  | 			localparam partial_Y_WIDTH = `MIN(Y_WIDTH, B_WIDTH+`DSP_A_MAXWIDTH_PARTIAL); | ||||||
|  | 			localparam last_A_WIDTH = A_WIDTH-n*(`DSP_A_MAXWIDTH_PARTIAL-sign_headroom); | ||||||
|  | 			localparam last_Y_WIDTH = B_WIDTH+last_A_WIDTH; | ||||||
|  | 			if (A_SIGNED && B_SIGNED) begin | ||||||
|  | 				wire signed [partial_Y_WIDTH-1:0] partial [n-1:0]; | ||||||
|  | 				wire signed [last_Y_WIDTH-1:0] last_partial; | ||||||
|  | 				wire signed [Y_WIDTH-1:0] partial_sum [n:0]; | ||||||
|  | 			end | ||||||
|  | 			else begin | ||||||
|  | 				wire [partial_Y_WIDTH-1:0] partial [n-1:0]; | ||||||
|  | 				wire [last_Y_WIDTH-1:0] last_partial; | ||||||
|  | 				wire [Y_WIDTH-1:0] partial_sum [n:0]; | ||||||
|  | 			end | ||||||
|  | 
 | ||||||
|  | 			for (i = 0; i < n; i=i+1) begin:sliceA | ||||||
|  | 				\$__mul #( | ||||||
|  | 					.A_SIGNED(sign_headroom), | ||||||
|  | 					.B_SIGNED(B_SIGNED), | ||||||
|  | 					.A_WIDTH(`DSP_A_MAXWIDTH_PARTIAL), | ||||||
|  | 					.B_WIDTH(B_WIDTH), | ||||||
|  | 					.Y_WIDTH(partial_Y_WIDTH) | ||||||
|  | 				) mul ( | ||||||
|  | 					.A({{sign_headroom{1'b0}}, A[i*(`DSP_A_MAXWIDTH_PARTIAL-sign_headroom) +: `DSP_A_MAXWIDTH_PARTIAL-sign_headroom]}), | ||||||
|  | 					.B(B), | ||||||
|  | 					.Y(partial[i]) | ||||||
|  | 				); | ||||||
|  | 				// TODO: Currently a 'cascade' approach to summing the partial | ||||||
|  | 				//       products is taken here, but a more efficient 'binary | ||||||
|  | 				//       reduction' approach also exists... | ||||||
|  | 				if (i == 0) | ||||||
|  | 					assign partial_sum[i] = partial[i]; | ||||||
|  | 				else | ||||||
|  | 					assign partial_sum[i] = (partial[i] << (* mul2dsp *) i*(`DSP_A_MAXWIDTH_PARTIAL-sign_headroom)) + (* mul2dsp *) partial_sum[i-1]; | ||||||
|  | 			end | ||||||
|  | 
 | ||||||
|  | 			\$__mul #( | ||||||
|  | 				.A_SIGNED(A_SIGNED), | ||||||
|  | 				.B_SIGNED(B_SIGNED), | ||||||
|  | 				.A_WIDTH(last_A_WIDTH), | ||||||
|  | 				.B_WIDTH(B_WIDTH), | ||||||
|  | 				.Y_WIDTH(last_Y_WIDTH) | ||||||
|  | 			) sliceA.last ( | ||||||
|  | 				.A(A[A_WIDTH-1 -: last_A_WIDTH]), | ||||||
|  | 				.B(B), | ||||||
|  | 				.Y(last_partial) | ||||||
|  | 			); | ||||||
|  | 			assign partial_sum[n] = (last_partial << (* mul2dsp *) n*(`DSP_A_MAXWIDTH_PARTIAL-sign_headroom)) + (* mul2dsp *) partial_sum[n-1]; | ||||||
|  | 			assign Y = partial_sum[n]; | ||||||
|  | 		end | ||||||
|  | 		else if (B_WIDTH > `DSP_B_MAXWIDTH) begin | ||||||
|  | 			localparam n = (B_WIDTH-`DSP_B_MAXWIDTH+`DSP_B_MAXWIDTH_PARTIAL-sign_headroom-1) / (`DSP_B_MAXWIDTH_PARTIAL-sign_headroom); | ||||||
|  | 			localparam partial_Y_WIDTH = `MIN(Y_WIDTH, A_WIDTH+`DSP_B_MAXWIDTH_PARTIAL); | ||||||
|  | 			localparam last_B_WIDTH = B_WIDTH-n*(`DSP_B_MAXWIDTH_PARTIAL-sign_headroom); | ||||||
|  | 			localparam last_Y_WIDTH = A_WIDTH+last_B_WIDTH; | ||||||
|  | 			if (A_SIGNED && B_SIGNED) begin | ||||||
|  | 				wire signed [partial_Y_WIDTH-1:0] partial [n-1:0]; | ||||||
|  | 				wire signed [last_Y_WIDTH-1:0] last_partial; | ||||||
|  | 				wire signed [Y_WIDTH-1:0] partial_sum [n:0]; | ||||||
|  | 			end | ||||||
|  | 			else begin | ||||||
|  | 				wire [partial_Y_WIDTH-1:0] partial [n-1:0]; | ||||||
|  | 				wire [last_Y_WIDTH-1:0] last_partial; | ||||||
|  | 				wire [Y_WIDTH-1:0] partial_sum [n:0]; | ||||||
|  | 			end | ||||||
|  | 
 | ||||||
|  | 			for (i = 0; i < n; i=i+1) begin:sliceB | ||||||
|  | 				\$__mul #( | ||||||
|  | 					.A_SIGNED(A_SIGNED), | ||||||
|  | 					.B_SIGNED(sign_headroom), | ||||||
|  | 					.A_WIDTH(A_WIDTH), | ||||||
|  | 					.B_WIDTH(`DSP_B_MAXWIDTH_PARTIAL), | ||||||
|  | 					.Y_WIDTH(partial_Y_WIDTH) | ||||||
|  | 				) mul ( | ||||||
|  | 					.A(A), | ||||||
|  | 					.B({{sign_headroom{1'b0}}, B[i*(`DSP_B_MAXWIDTH_PARTIAL-sign_headroom) +: `DSP_B_MAXWIDTH_PARTIAL-sign_headroom]}), | ||||||
|  | 					.Y(partial[i]) | ||||||
|  | 				); | ||||||
|  | 				// TODO: Currently a 'cascade' approach to summing the partial | ||||||
|  | 				//       products is taken here, but a more efficient 'binary | ||||||
|  | 				//       reduction' approach also exists... | ||||||
|  | 				if (i == 0) | ||||||
|  | 					assign partial_sum[i] = partial[i]; | ||||||
|  | 				else | ||||||
|  | 					assign partial_sum[i] = (partial[i] << (* mul2dsp *) i*(`DSP_B_MAXWIDTH_PARTIAL-sign_headroom)) + (* mul2dsp *) partial_sum[i-1]; | ||||||
|  | 			end | ||||||
|  | 
 | ||||||
|  | 			\$__mul #( | ||||||
|  | 				.A_SIGNED(A_SIGNED), | ||||||
|  | 				.B_SIGNED(B_SIGNED), | ||||||
|  | 				.A_WIDTH(A_WIDTH), | ||||||
|  | 				.B_WIDTH(last_B_WIDTH), | ||||||
|  | 				.Y_WIDTH(last_Y_WIDTH) | ||||||
|  | 			) mul_sliceB_last ( | ||||||
|  | 				.A(A), | ||||||
|  | 				.B(B[B_WIDTH-1 -: last_B_WIDTH]), | ||||||
|  | 				.Y(last_partial) | ||||||
|  | 			); | ||||||
|  | 			assign partial_sum[n] = (last_partial << (* mul2dsp *) n*(`DSP_B_MAXWIDTH_PARTIAL-sign_headroom)) + (* mul2dsp *) partial_sum[n-1]; | ||||||
|  | 			assign Y = partial_sum[n]; | ||||||
|  | 		end | ||||||
|  | 		else begin | ||||||
|  | 			if (A_SIGNED) | ||||||
|  | 				wire signed [`DSP_A_MAXWIDTH-1:0] Aext = $signed(A); | ||||||
|  | 			else | ||||||
|  | 				wire [`DSP_A_MAXWIDTH-1:0] Aext = A; | ||||||
|  | 			if (B_SIGNED) | ||||||
|  | 				wire signed [`DSP_B_MAXWIDTH-1:0] Bext = $signed(B); | ||||||
|  | 			else | ||||||
|  | 				wire [`DSP_B_MAXWIDTH-1:0] Bext = B; | ||||||
|  | 
 | ||||||
|  | 			`DSP_NAME #( | ||||||
|  | 				.A_SIGNED(A_SIGNED), | ||||||
|  | 				.B_SIGNED(B_SIGNED), | ||||||
|  | 				.A_WIDTH(`DSP_A_MAXWIDTH), | ||||||
|  | 				.B_WIDTH(`DSP_B_MAXWIDTH), | ||||||
|  | 				.Y_WIDTH(`MIN(Y_WIDTH,`DSP_A_MAXWIDTH+`DSP_B_MAXWIDTH)), | ||||||
|  | 			) _TECHMAP_REPLACE_ ( | ||||||
|  | 				.A(Aext), | ||||||
|  | 				.B(Bext), | ||||||
|  | 				.Y(Y) | ||||||
|  | 			); | ||||||
|  | 		end | ||||||
|  | 	end | ||||||
|  | 	endgenerate | ||||||
|  | endmodule | ||||||
|  | 
 | ||||||
|  | (* techmap_celltype = "$mul $__mul" *) | ||||||
|  | module _90_soft_mul (A, B, Y); | ||||||
|  | 	parameter A_SIGNED = 0; | ||||||
|  | 	parameter B_SIGNED = 0; | ||||||
|  | 	parameter A_WIDTH = 1; | ||||||
|  | 	parameter B_WIDTH = 1; | ||||||
|  | 	parameter Y_WIDTH = 1; | ||||||
|  | 
 | ||||||
|  | 	input [A_WIDTH-1:0] A; | ||||||
|  | 	input [B_WIDTH-1:0] B; | ||||||
|  | 	output [Y_WIDTH-1:0] Y; | ||||||
|  | 
 | ||||||
|  | 	// Indirection necessary since mapping | ||||||
|  | 	//   back to $mul will cause recursion | ||||||
|  | 	generate | ||||||
|  | 	if (A_SIGNED && !B_SIGNED) | ||||||
|  | 		\$__soft_mul #( | ||||||
|  | 			.A_SIGNED(A_SIGNED), | ||||||
|  | 			.B_SIGNED(1), | ||||||
|  | 			.A_WIDTH(A_WIDTH), | ||||||
|  | 			.B_WIDTH(B_WIDTH+1), | ||||||
|  | 			.Y_WIDTH(Y_WIDTH) | ||||||
|  | 		) _TECHMAP_REPLACE_ ( | ||||||
|  | 			.A(A), | ||||||
|  | 			.B({1'b0,B}), | ||||||
|  | 			.Y(Y) | ||||||
|  | 		); | ||||||
|  | 	else if (!A_SIGNED && B_SIGNED) | ||||||
|  | 		\$__soft_mul #( | ||||||
|  | 			.A_SIGNED(1), | ||||||
|  | 			.B_SIGNED(B_SIGNED), | ||||||
|  | 			.A_WIDTH(A_WIDTH+1), | ||||||
|  | 			.B_WIDTH(B_WIDTH), | ||||||
|  | 			.Y_WIDTH(Y_WIDTH) | ||||||
|  | 		) _TECHMAP_REPLACE_ ( | ||||||
|  | 			.A({1'b0,A}), | ||||||
|  | 			.B(B), | ||||||
|  | 			.Y(Y) | ||||||
|  | 		); | ||||||
|  | 	else | ||||||
|  | 		\$__soft_mul #( | ||||||
|  | 			.A_SIGNED(A_SIGNED), | ||||||
|  | 			.B_SIGNED(B_SIGNED), | ||||||
|  | 			.A_WIDTH(A_WIDTH), | ||||||
|  | 			.B_WIDTH(B_WIDTH), | ||||||
|  | 			.Y_WIDTH(Y_WIDTH) | ||||||
|  | 		) _TECHMAP_REPLACE_ ( | ||||||
|  | 			.A(A), | ||||||
|  | 			.B(B), | ||||||
|  | 			.Y(Y) | ||||||
|  | 		); | ||||||
|  | 	endgenerate | ||||||
|  | endmodule | ||||||
|  | @ -13,6 +13,7 @@ $(eval $(call add_share_file,share/ecp5,techlibs/ecp5/brams_map.v)) | ||||||
| $(eval $(call add_share_file,share/ecp5,techlibs/ecp5/bram.txt)) | $(eval $(call add_share_file,share/ecp5,techlibs/ecp5/bram.txt)) | ||||||
| $(eval $(call add_share_file,share/ecp5,techlibs/ecp5/arith_map.v)) | $(eval $(call add_share_file,share/ecp5,techlibs/ecp5/arith_map.v)) | ||||||
| $(eval $(call add_share_file,share/ecp5,techlibs/ecp5/latches_map.v)) | $(eval $(call add_share_file,share/ecp5,techlibs/ecp5/latches_map.v)) | ||||||
|  | $(eval $(call add_share_file,share/ecp5,techlibs/ecp5/dsp_map.v)) | ||||||
| 
 | 
 | ||||||
| $(eval $(call add_share_file,share/ecp5,techlibs/ecp5/abc_map.v)) | $(eval $(call add_share_file,share/ecp5,techlibs/ecp5/abc_map.v)) | ||||||
| $(eval $(call add_share_file,share/ecp5,techlibs/ecp5/abc_unmap.v)) | $(eval $(call add_share_file,share/ecp5,techlibs/ecp5/abc_unmap.v)) | ||||||
|  |  | ||||||
							
								
								
									
										17
									
								
								techlibs/ecp5/dsp_map.v
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								techlibs/ecp5/dsp_map.v
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | ||||||
|  | module \$__MUL18X18 (input [17:0] A, input [17:0] B, output [35:0] Y); | ||||||
|  | 
 | ||||||
|  | 	parameter A_WIDTH = 18; | ||||||
|  | 	parameter B_WIDTH = 18; | ||||||
|  | 	parameter Y_WIDTH = 36; | ||||||
|  | 	parameter A_SIGNED = 0; | ||||||
|  | 	parameter B_SIGNED = 0; | ||||||
|  | 
 | ||||||
|  | 	MULT18X18D _TECHMAP_REPLACE_ ( | ||||||
|  | 		.A0(A[0]), .A1(A[1]), .A2(A[2]), .A3(A[3]), .A4(A[4]), .A5(A[5]), .A6(A[6]), .A7(A[7]), .A8(A[8]), .A9(A[9]), .A10(A[10]), .A11(A[11]), .A12(A[12]), .A13(A[13]), .A14(A[14]), .A15(A[15]), .A16(A[16]), .A17(A[17]), | ||||||
|  | 		.B0(B[0]), .B1(B[1]), .B2(B[2]), .B3(B[3]), .B4(B[4]), .B5(B[5]), .B6(B[6]), .B7(B[7]), .B8(B[8]), .B9(B[9]), .B10(B[10]), .B11(B[11]), .B12(B[12]), .B13(B[13]), .B14(B[14]), .B15(B[15]), .B16(B[16]), .B17(B[17]), | ||||||
|  | 		.C17(1'b0), .C16(1'b0), .C15(1'b0), .C14(1'b0), .C13(1'b0), .C12(1'b0), .C11(1'b0), .C10(1'b0), .C9(1'b0), .C8(1'b0), .C7(1'b0), .C6(1'b0), .C5(1'b0), .C4(1'b0), .C3(1'b0), .C2(1'b0), .C1(1'b0), .C0(1'b0), | ||||||
|  | 		.SIGNEDA(A_SIGNED), .SIGNEDB(B_SIGNED), .SOURCEA(1'b0), .SOURCEB(1'b0), | ||||||
|  | 
 | ||||||
|  | 		.P0(Y[0]), .P1(Y[1]), .P2(Y[2]), .P3(Y[3]), .P4(Y[4]), .P5(Y[5]), .P6(Y[6]), .P7(Y[7]), .P8(Y[8]), .P9(Y[9]), .P10(Y[10]), .P11(Y[11]), .P12(Y[12]), .P13(Y[13]), .P14(Y[14]), .P15(Y[15]), .P16(Y[16]), .P17(Y[17]), .P18(Y[18]), .P19(Y[19]), .P20(Y[20]), .P21(Y[21]), .P22(Y[22]), .P23(Y[23]), .P24(Y[24]), .P25(Y[25]), .P26(Y[26]), .P27(Y[27]), .P28(Y[28]), .P29(Y[29]), .P30(Y[30]), .P31(Y[31]), .P32(Y[32]), .P33(Y[33]), .P34(Y[34]), .P35(Y[35]) | ||||||
|  | 	); | ||||||
|  | endmodule | ||||||
|  | @ -89,6 +89,9 @@ struct SynthEcp5Pass : public ScriptPass | ||||||
| 		log("        generate an output netlist (and BLIF file) suitable for VPR\n"); | 		log("        generate an output netlist (and BLIF file) suitable for VPR\n"); | ||||||
| 		log("        (this feature is experimental and incomplete)\n"); | 		log("        (this feature is experimental and incomplete)\n"); | ||||||
| 		log("\n"); | 		log("\n"); | ||||||
|  | 		log("    -nodsp\n"); | ||||||
|  | 		log("        do not map multipliers to MULT18X18D\n"); | ||||||
|  | 		log("\n"); | ||||||
| 		log("\n"); | 		log("\n"); | ||||||
| 		log("The following commands are executed by this synthesis command:\n"); | 		log("The following commands are executed by this synthesis command:\n"); | ||||||
| 		help_script(); | 		help_script(); | ||||||
|  | @ -96,7 +99,7 @@ struct SynthEcp5Pass : public ScriptPass | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	string top_opt, blif_file, edif_file, json_file; | 	string top_opt, blif_file, edif_file, json_file; | ||||||
| 	bool noccu2, nodffe, nobram, nolutram, nowidelut, flatten, retime, abc2, abc9, vpr; | 	bool noccu2, nodffe, nobram, nolutram, nowidelut, flatten, retime, abc2, abc9, nodsp, vpr; | ||||||
| 
 | 
 | ||||||
| 	void clear_flags() YS_OVERRIDE | 	void clear_flags() YS_OVERRIDE | ||||||
| 	{ | 	{ | ||||||
|  | @ -114,6 +117,7 @@ struct SynthEcp5Pass : public ScriptPass | ||||||
| 		abc2 = false; | 		abc2 = false; | ||||||
| 		vpr = false; | 		vpr = false; | ||||||
| 		abc9 = false; | 		abc9 = false; | ||||||
|  | 		nodsp = false; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE | 	void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE | ||||||
|  | @ -192,6 +196,10 @@ struct SynthEcp5Pass : public ScriptPass | ||||||
| 				abc9 = true; | 				abc9 = true; | ||||||
| 				continue; | 				continue; | ||||||
| 			} | 			} | ||||||
|  | 			if (args[argidx] == "-nodsp") { | ||||||
|  | 				nodsp = true; | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
| 			break; | 			break; | ||||||
| 		} | 		} | ||||||
| 		extra_args(args, argidx, design); | 		extra_args(args, argidx, design); | ||||||
|  | @ -218,17 +226,34 @@ struct SynthEcp5Pass : public ScriptPass | ||||||
| 			run(stringf("hierarchy -check %s", help_mode ? "-top <top>" : top_opt.c_str())); | 			run(stringf("hierarchy -check %s", help_mode ? "-top <top>" : top_opt.c_str())); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if (flatten && check_label("flatten", "(unless -noflatten)")) | 		if (check_label("coarse")) | ||||||
| 		{ | 		{ | ||||||
| 			run("proc"); | 			run("proc"); | ||||||
|  | 			if (flatten || help_mode) | ||||||
| 				run("flatten"); | 				run("flatten"); | ||||||
| 			run("tribuf -logic"); | 			run("tribuf -logic"); | ||||||
| 			run("deminout"); | 			run("deminout"); | ||||||
|  | 			run("opt_expr"); | ||||||
|  | 			run("opt_clean"); | ||||||
|  | 			run("check"); | ||||||
|  | 			run("opt"); | ||||||
|  | 			run("wreduce"); | ||||||
|  | 			run("peepopt"); | ||||||
|  | 			run("opt_clean"); | ||||||
|  | 			run("share"); | ||||||
|  | 			run("techmap -map +/cmp2lut.v -D LUT_WIDTH=4"); | ||||||
|  | 			run("opt_expr"); | ||||||
|  | 			run("opt_clean"); | ||||||
|  | 			if (!nodsp) { | ||||||
|  | 				run("techmap -map +/mul2dsp.v -map +/ecp5/dsp_map.v -D DSP_A_MAXWIDTH=18 -D DSP_B_MAXWIDTH=18  -D DSP_A_MINWIDTH=2 -D DSP_B_MINWIDTH=2  -D DSP_NAME=$__MUL18X18", "(unless -nodsp)"); | ||||||
|  | 				run("chtype -set $mul t:$__soft_mul", "(unless -nodsp)"); | ||||||
| 			} | 			} | ||||||
| 
 | 			run("alumacc"); | ||||||
| 		if (check_label("coarse")) | 			run("opt"); | ||||||
| 		{ | 			run("fsm"); | ||||||
| 			run("synth -run coarse"); | 			run("opt -fast"); | ||||||
|  | 			run("memory -nomap"); | ||||||
|  | 			run("opt_clean"); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if (!nobram && check_label("map_bram", "(skip if -nobram)")) | 		if (!nobram && check_label("map_bram", "(skip if -nobram)")) | ||||||
|  |  | ||||||
|  | @ -27,6 +27,7 @@ $(eval $(call add_share_file,share/ice40,techlibs/ice40/cells_sim.v)) | ||||||
| $(eval $(call add_share_file,share/ice40,techlibs/ice40/latches_map.v)) | $(eval $(call add_share_file,share/ice40,techlibs/ice40/latches_map.v)) | ||||||
| $(eval $(call add_share_file,share/ice40,techlibs/ice40/brams.txt)) | $(eval $(call add_share_file,share/ice40,techlibs/ice40/brams.txt)) | ||||||
| $(eval $(call add_share_file,share/ice40,techlibs/ice40/brams_map.v)) | $(eval $(call add_share_file,share/ice40,techlibs/ice40/brams_map.v)) | ||||||
|  | $(eval $(call add_share_file,share/ice40,techlibs/ice40/dsp_map.v)) | ||||||
| $(eval $(call add_share_file,share/ice40,techlibs/ice40/abc_hx.box)) | $(eval $(call add_share_file,share/ice40,techlibs/ice40/abc_hx.box)) | ||||||
| $(eval $(call add_share_file,share/ice40,techlibs/ice40/abc_hx.lut)) | $(eval $(call add_share_file,share/ice40,techlibs/ice40/abc_hx.lut)) | ||||||
| $(eval $(call add_share_file,share/ice40,techlibs/ice40/abc_lp.box)) | $(eval $(call add_share_file,share/ice40,techlibs/ice40/abc_lp.box)) | ||||||
|  |  | ||||||
							
								
								
									
										34
									
								
								techlibs/ice40/dsp_map.v
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								techlibs/ice40/dsp_map.v
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,34 @@ | ||||||
|  | module \$__MUL16X16 (input [15:0] A, input [15:0] B, output [31:0] Y); | ||||||
|  | 	parameter A_SIGNED = 0; | ||||||
|  | 	parameter B_SIGNED = 0; | ||||||
|  | 	parameter A_WIDTH = 0; | ||||||
|  | 	parameter B_WIDTH = 0; | ||||||
|  | 	parameter Y_WIDTH = 0; | ||||||
|  | 
 | ||||||
|  | 	SB_MAC16 #( | ||||||
|  | 		.NEG_TRIGGER(1'b0), | ||||||
|  | 		.C_REG(1'b0), | ||||||
|  | 		.A_REG(1'b0), | ||||||
|  | 		.B_REG(1'b0), | ||||||
|  | 		.D_REG(1'b0), | ||||||
|  | 		.TOP_8x8_MULT_REG(1'b0), | ||||||
|  | 		.BOT_8x8_MULT_REG(1'b0), | ||||||
|  | 		.PIPELINE_16x16_MULT_REG1(1'b0), | ||||||
|  | 		.PIPELINE_16x16_MULT_REG2(1'b0), | ||||||
|  | 		.TOPOUTPUT_SELECT(2'b11), | ||||||
|  | 		.TOPADDSUB_LOWERINPUT(2'b0), | ||||||
|  | 		.TOPADDSUB_UPPERINPUT(1'b0), | ||||||
|  | 		.TOPADDSUB_CARRYSELECT(2'b0), | ||||||
|  | 		.BOTOUTPUT_SELECT(2'b11), | ||||||
|  | 		.BOTADDSUB_LOWERINPUT(2'b0), | ||||||
|  | 		.BOTADDSUB_UPPERINPUT(1'b0), | ||||||
|  | 		.BOTADDSUB_CARRYSELECT(2'b0), | ||||||
|  | 		.MODE_8x8(1'b0), | ||||||
|  | 		.A_SIGNED(A_SIGNED), | ||||||
|  | 		.B_SIGNED(B_SIGNED) | ||||||
|  | 	) _TECHMAP_REPLACE_ ( | ||||||
|  | 		.A(A), | ||||||
|  | 		.B(B), | ||||||
|  | 		.O(Y), | ||||||
|  | 	); | ||||||
|  | endmodule | ||||||
|  | @ -272,8 +272,18 @@ struct SynthIce40Pass : public ScriptPass | ||||||
| 			run("techmap -map +/cmp2lut.v -D LUT_WIDTH=4"); | 			run("techmap -map +/cmp2lut.v -D LUT_WIDTH=4"); | ||||||
| 			run("opt_expr"); | 			run("opt_expr"); | ||||||
| 			run("opt_clean"); | 			run("opt_clean"); | ||||||
| 			if (help_mode || dsp) | 			if (help_mode || dsp) { | ||||||
| 				run("ice40_dsp", "(if -dsp)"); | 				run("techmap -map +/mul2dsp.v -map +/ice40/dsp_map.v -D DSP_A_MAXWIDTH=16 -D DSP_B_MAXWIDTH=16 " | ||||||
|  | 						"-D DSP_A_MINWIDTH=2 -D DSP_B_MINWIDTH=2 -D DSP_Y_MINWIDTH=11 " | ||||||
|  | 						"-D DSP_NAME=$__MUL16X16", "(if -dsp)"); | ||||||
|  | 				run("select a:mul2dsp", "              (if -dsp)"); | ||||||
|  | 				run("setattr -unset mul2dsp", "        (if -dsp)"); | ||||||
|  | 				run("opt_expr -fine", "                (if -dsp)"); | ||||||
|  | 				run("wreduce", "                       (if -dsp)"); | ||||||
|  | 				run("select -clear", "                 (if -dsp)"); | ||||||
|  | 				run("ice40_dsp", "                     (if -dsp)"); | ||||||
|  | 				run("chtype -set $mul t:$__soft_mul", "(if -dsp)"); | ||||||
|  | 			} | ||||||
| 			run("alumacc"); | 			run("alumacc"); | ||||||
| 			run("opt"); | 			run("opt"); | ||||||
| 			run("fsm"); | 			run("fsm"); | ||||||
|  |  | ||||||
|  | @ -42,6 +42,7 @@ $(eval $(call add_share_file,share/xilinx,techlibs/xilinx/xc6s_ff_map.v)) | ||||||
| $(eval $(call add_share_file,share/xilinx,techlibs/xilinx/xc7_ff_map.v)) | $(eval $(call add_share_file,share/xilinx,techlibs/xilinx/xc7_ff_map.v)) | ||||||
| $(eval $(call add_share_file,share/xilinx,techlibs/xilinx/lut_map.v)) | $(eval $(call add_share_file,share/xilinx,techlibs/xilinx/lut_map.v)) | ||||||
| $(eval $(call add_share_file,share/xilinx,techlibs/xilinx/mux_map.v)) | $(eval $(call add_share_file,share/xilinx,techlibs/xilinx/mux_map.v)) | ||||||
|  | $(eval $(call add_share_file,share/xilinx,techlibs/xilinx/dsp_map.v)) | ||||||
| 
 | 
 | ||||||
| $(eval $(call add_share_file,share/xilinx,techlibs/xilinx/abc_map.v)) | $(eval $(call add_share_file,share/xilinx,techlibs/xilinx/abc_map.v)) | ||||||
| $(eval $(call add_share_file,share/xilinx,techlibs/xilinx/abc_unmap.v)) | $(eval $(call add_share_file,share/xilinx,techlibs/xilinx/abc_unmap.v)) | ||||||
|  |  | ||||||
|  | @ -22,11 +22,11 @@ | ||||||
| 
 | 
 | ||||||
| module RAM32X1D ( | module RAM32X1D ( | ||||||
|   output DPO, SPO, |   output DPO, SPO, | ||||||
|   input  D, |   (* techmap_autopurge *) input  D, | ||||||
|   input  WCLK, |   (* techmap_autopurge *) input  WCLK, | ||||||
|   input  WE, |   (* techmap_autopurge *) input  WE, | ||||||
|   input  A0, A1, A2, A3, A4, |   (* techmap_autopurge *) input  A0, A1, A2, A3, A4, | ||||||
|   input  DPRA0, DPRA1, DPRA2, DPRA3, DPRA4 |   (* techmap_autopurge *) input  DPRA0, DPRA1, DPRA2, DPRA3, DPRA4 | ||||||
| ); | ); | ||||||
|   parameter INIT = 32'h0; |   parameter INIT = 32'h0; | ||||||
|   parameter IS_WCLK_INVERTED = 1'b0; |   parameter IS_WCLK_INVERTED = 1'b0; | ||||||
|  | @ -45,11 +45,11 @@ endmodule | ||||||
| 
 | 
 | ||||||
| module RAM64X1D ( | module RAM64X1D ( | ||||||
|   output DPO, SPO, |   output DPO, SPO, | ||||||
|   input  D, |   (* techmap_autopurge *) input  D, | ||||||
|   input  WCLK, |   (* techmap_autopurge *) input  WCLK, | ||||||
|   input  WE, |   (* techmap_autopurge *) input  WE, | ||||||
|   input  A0, A1, A2, A3, A4, A5, |   (* techmap_autopurge *) input  A0, A1, A2, A3, A4, A5, | ||||||
|   input  DPRA0, DPRA1, DPRA2, DPRA3, DPRA4, DPRA5 |   (* techmap_autopurge *) input  DPRA0, DPRA1, DPRA2, DPRA3, DPRA4, DPRA5 | ||||||
| ); | ); | ||||||
|   parameter INIT = 64'h0; |   parameter INIT = 64'h0; | ||||||
|   parameter IS_WCLK_INVERTED = 1'b0; |   parameter IS_WCLK_INVERTED = 1'b0; | ||||||
|  | @ -68,10 +68,10 @@ endmodule | ||||||
| 
 | 
 | ||||||
| module RAM128X1D ( | module RAM128X1D ( | ||||||
|   output       DPO, SPO, |   output       DPO, SPO, | ||||||
|   input        D, |   (* techmap_autopurge *) input        D, | ||||||
|   input        WCLK, |   (* techmap_autopurge *) input        WCLK, | ||||||
|   input        WE, |   (* techmap_autopurge *) input        WE, | ||||||
|   input  [6:0] A, DPRA |   (* techmap_autopurge *) input  [6:0] A, DPRA | ||||||
| ); | ); | ||||||
|   parameter INIT = 128'h0; |   parameter INIT = 128'h0; | ||||||
|   parameter IS_WCLK_INVERTED = 1'b0; |   parameter IS_WCLK_INVERTED = 1'b0; | ||||||
|  | @ -90,7 +90,7 @@ endmodule | ||||||
| 
 | 
 | ||||||
| module SRL16E ( | module SRL16E ( | ||||||
|   output Q, |   output Q, | ||||||
|   input A0, A1, A2, A3, CE, CLK, D |   (* techmap_autopurge *) input A0, A1, A2, A3, CE, CLK, D | ||||||
| ); | ); | ||||||
|   parameter [15:0] INIT = 16'h0000; |   parameter [15:0] INIT = 16'h0000; | ||||||
|   parameter [0:0] IS_CLK_INVERTED = 1'b0; |   parameter [0:0] IS_CLK_INVERTED = 1'b0; | ||||||
|  | @ -107,8 +107,8 @@ endmodule | ||||||
| module SRLC32E ( | module SRLC32E ( | ||||||
|   output Q, |   output Q, | ||||||
|   output Q31, |   output Q31, | ||||||
|   input [4:0] A, |   (* techmap_autopurge *) input [4:0] A, | ||||||
|   input CE, CLK, D |   (* techmap_autopurge *) input CE, CLK, D | ||||||
| ); | ); | ||||||
|   parameter [31:0] INIT = 32'h00000000; |   parameter [31:0] INIT = 32'h00000000; | ||||||
|   parameter [0:0] IS_CLK_INVERTED = 1'b0; |   parameter [0:0] IS_CLK_INVERTED = 1'b0; | ||||||
|  | @ -121,3 +121,327 @@ module SRLC32E ( | ||||||
|   ); |   ); | ||||||
|   \$__ABC_LUT6 q (.A(\$Q ), .S({1'b1, A}), .Y(Q)); |   \$__ABC_LUT6 q (.A(\$Q ), .S({1'b1, A}), .Y(Q)); | ||||||
| endmodule | endmodule | ||||||
|  | 
 | ||||||
|  | module DSP48E1 ( | ||||||
|  |     (* techmap_autopurge *) output [29:0] ACOUT, | ||||||
|  |     (* techmap_autopurge *) output [17:0] BCOUT, | ||||||
|  |     (* techmap_autopurge *) output reg CARRYCASCOUT, | ||||||
|  |     (* techmap_autopurge *) output reg [3:0] CARRYOUT, | ||||||
|  |     (* techmap_autopurge *) output reg MULTSIGNOUT, | ||||||
|  |     (* techmap_autopurge *) output OVERFLOW, | ||||||
|  |     (* techmap_autopurge *) output reg signed [47:0] P, | ||||||
|  |     (* techmap_autopurge *) output PATTERNBDETECT, | ||||||
|  |     (* techmap_autopurge *) output PATTERNDETECT, | ||||||
|  |     (* techmap_autopurge *) output [47:0] PCOUT, | ||||||
|  |     (* techmap_autopurge *) output UNDERFLOW, | ||||||
|  |     (* techmap_autopurge *) input signed [29:0] A, | ||||||
|  |     (* techmap_autopurge *) input [29:0] ACIN, | ||||||
|  |     (* techmap_autopurge *) input [3:0] ALUMODE, | ||||||
|  |     (* techmap_autopurge *) input signed [17:0] B, | ||||||
|  |     (* techmap_autopurge *) input [17:0] BCIN, | ||||||
|  |     (* techmap_autopurge *) input [47:0] C, | ||||||
|  |     (* techmap_autopurge *) input CARRYCASCIN, | ||||||
|  |     (* techmap_autopurge *) input CARRYIN, | ||||||
|  |     (* techmap_autopurge *) input [2:0] CARRYINSEL, | ||||||
|  |     (* techmap_autopurge *) input CEA1, | ||||||
|  |     (* techmap_autopurge *) input CEA2, | ||||||
|  |     (* techmap_autopurge *) input CEAD, | ||||||
|  |     (* techmap_autopurge *) input CEALUMODE, | ||||||
|  |     (* techmap_autopurge *) input CEB1, | ||||||
|  |     (* techmap_autopurge *) input CEB2, | ||||||
|  |     (* techmap_autopurge *) input CEC, | ||||||
|  |     (* techmap_autopurge *) input CECARRYIN, | ||||||
|  |     (* techmap_autopurge *) input CECTRL, | ||||||
|  |     (* techmap_autopurge *) input CED, | ||||||
|  |     (* techmap_autopurge *) input CEINMODE, | ||||||
|  |     (* techmap_autopurge *) input CEM, | ||||||
|  |     (* techmap_autopurge *) input CEP, | ||||||
|  |     (* techmap_autopurge *) input CLK, | ||||||
|  |     (* techmap_autopurge *) input [24:0] D, | ||||||
|  |     (* techmap_autopurge *) input [4:0] INMODE, | ||||||
|  |     (* techmap_autopurge *) input MULTSIGNIN, | ||||||
|  |     (* techmap_autopurge *) input [6:0] OPMODE, | ||||||
|  |     (* techmap_autopurge *) input [47:0] PCIN, | ||||||
|  |     (* techmap_autopurge *) input RSTA, | ||||||
|  |     (* techmap_autopurge *) input RSTALLCARRYIN, | ||||||
|  |     (* techmap_autopurge *) input RSTALUMODE, | ||||||
|  |     (* techmap_autopurge *) input RSTB, | ||||||
|  |     (* techmap_autopurge *) input RSTC, | ||||||
|  |     (* techmap_autopurge *) input RSTCTRL, | ||||||
|  |     (* techmap_autopurge *) input RSTD, | ||||||
|  |     (* techmap_autopurge *) input RSTINMODE, | ||||||
|  |     (* techmap_autopurge *) input RSTM, | ||||||
|  |     (* techmap_autopurge *) input RSTP | ||||||
|  | ); | ||||||
|  |     parameter integer ACASCREG = 1; | ||||||
|  |     parameter integer ADREG = 1; | ||||||
|  |     parameter integer ALUMODEREG = 1; | ||||||
|  |     parameter integer AREG = 1; | ||||||
|  |     parameter AUTORESET_PATDET = "NO_RESET"; | ||||||
|  |     parameter A_INPUT = "DIRECT"; | ||||||
|  |     parameter integer BCASCREG = 1; | ||||||
|  |     parameter integer BREG = 1; | ||||||
|  |     parameter B_INPUT = "DIRECT"; | ||||||
|  |     parameter integer CARRYINREG = 1; | ||||||
|  |     parameter integer CARRYINSELREG = 1; | ||||||
|  |     parameter integer CREG = 1; | ||||||
|  |     parameter integer DREG = 1; | ||||||
|  |     parameter integer INMODEREG = 1; | ||||||
|  |     parameter integer MREG = 1; | ||||||
|  |     parameter integer OPMODEREG = 1; | ||||||
|  |     parameter integer PREG = 1; | ||||||
|  |     parameter SEL_MASK = "MASK"; | ||||||
|  |     parameter SEL_PATTERN = "PATTERN"; | ||||||
|  |     parameter USE_DPORT = "FALSE"; | ||||||
|  |     parameter USE_MULT = "MULTIPLY"; | ||||||
|  |     parameter USE_PATTERN_DETECT = "NO_PATDET"; | ||||||
|  |     parameter USE_SIMD = "ONE48"; | ||||||
|  |     parameter [47:0] MASK = 48'h3FFFFFFFFFFF; | ||||||
|  |     parameter [47:0] PATTERN = 48'h000000000000; | ||||||
|  |     parameter [3:0] IS_ALUMODE_INVERTED = 4'b0; | ||||||
|  |     parameter [0:0] IS_CARRYIN_INVERTED = 1'b0; | ||||||
|  |     parameter [0:0] IS_CLK_INVERTED = 1'b0; | ||||||
|  |     parameter [4:0] IS_INMODE_INVERTED = 5'b0; | ||||||
|  |     parameter [6:0] IS_OPMODE_INVERTED = 7'b0; | ||||||
|  | 
 | ||||||
|  |     parameter _TECHMAP_CELLTYPE_ = ""; | ||||||
|  |     localparam techmap_guard = (_TECHMAP_CELLTYPE_ != ""); | ||||||
|  | 
 | ||||||
|  | `define DSP48E1_INST(__CELL__) """ | ||||||
|  | __CELL__ #( | ||||||
|  |             .ACASCREG(ACASCREG), | ||||||
|  |             .ADREG(ADREG), | ||||||
|  |             .ALUMODEREG(ALUMODEREG), | ||||||
|  |             .AREG(AREG), | ||||||
|  |             .AUTORESET_PATDET(AUTORESET_PATDET), | ||||||
|  |             .A_INPUT(A_INPUT), | ||||||
|  |             .BCASCREG(BCASCREG), | ||||||
|  |             .BREG(BREG), | ||||||
|  |             .B_INPUT(B_INPUT), | ||||||
|  |             .CARRYINREG(CARRYINREG), | ||||||
|  |             .CARRYINSELREG(CARRYINSELREG), | ||||||
|  |             .CREG(CREG), | ||||||
|  |             .DREG(DREG), | ||||||
|  |             .INMODEREG(INMODEREG), | ||||||
|  |             .MREG(MREG), | ||||||
|  |             .OPMODEREG(OPMODEREG), | ||||||
|  |             .PREG(PREG), | ||||||
|  |             .SEL_MASK(SEL_MASK), | ||||||
|  |             .SEL_PATTERN(SEL_PATTERN), | ||||||
|  |             .USE_DPORT(USE_DPORT), | ||||||
|  |             .USE_MULT(USE_MULT), | ||||||
|  |             .USE_PATTERN_DETECT(USE_PATTERN_DETECT), | ||||||
|  |             .USE_SIMD(USE_SIMD), | ||||||
|  |             .MASK(MASK), | ||||||
|  |             .PATTERN(PATTERN), | ||||||
|  |             .IS_ALUMODE_INVERTED(IS_ALUMODE_INVERTED), | ||||||
|  |             .IS_CARRYIN_INVERTED(IS_CARRYIN_INVERTED), | ||||||
|  |             .IS_CLK_INVERTED(IS_CLK_INVERTED), | ||||||
|  |             .IS_INMODE_INVERTED(IS_INMODE_INVERTED), | ||||||
|  |             .IS_OPMODE_INVERTED(IS_OPMODE_INVERTED) | ||||||
|  |         ) _TECHMAP_REPLACE_ ( | ||||||
|  |             .ACOUT(ACOUT), | ||||||
|  |             .BCOUT(BCOUT), | ||||||
|  |             .CARRYCASCOUT(CARRYCASCOUT), | ||||||
|  |             .CARRYOUT(CARRYOUT), | ||||||
|  |             .MULTSIGNOUT(MULTSIGNOUT), | ||||||
|  |             .OVERFLOW(OVERFLOW), | ||||||
|  |             .P(oP), | ||||||
|  |             .PATTERNBDETECT(PATTERNBDETECT), | ||||||
|  |             .PATTERNDETECT(PATTERNDETECT), | ||||||
|  |             .PCOUT(oPCOUT), | ||||||
|  |             .UNDERFLOW(UNDERFLOW), | ||||||
|  |             .A(iA), | ||||||
|  |             .ACIN(ACIN), | ||||||
|  |             .ALUMODE(ALUMODE), | ||||||
|  |             .B(iB), | ||||||
|  |             .BCIN(BCIN), | ||||||
|  |             .C(iC), | ||||||
|  |             .CARRYCASCIN(CARRYCASCIN), | ||||||
|  |             .CARRYIN(CARRYIN), | ||||||
|  |             .CARRYINSEL(CARRYINSEL), | ||||||
|  |             .CEA1(CEA1), | ||||||
|  |             .CEA2(CEA2), | ||||||
|  |             .CEAD(CEAD), | ||||||
|  |             .CEALUMODE(CEALUMODE), | ||||||
|  |             .CEB1(CEB1), | ||||||
|  |             .CEB2(CEB2), | ||||||
|  |             .CEC(CEC), | ||||||
|  |             .CECARRYIN(CECARRYIN), | ||||||
|  |             .CECTRL(CECTRL), | ||||||
|  |             .CED(CED), | ||||||
|  |             .CEINMODE(CEINMODE), | ||||||
|  |             .CEM(CEM), | ||||||
|  |             .CEP(CEP), | ||||||
|  |             .CLK(CLK), | ||||||
|  |             .D(iD), | ||||||
|  |             .INMODE(INMODE), | ||||||
|  |             .MULTSIGNIN(MULTSIGNIN), | ||||||
|  |             .OPMODE(OPMODE), | ||||||
|  |             .PCIN(PCIN), | ||||||
|  |             .RSTA(RSTA), | ||||||
|  |             .RSTALLCARRYIN(RSTALLCARRYIN), | ||||||
|  |             .RSTALUMODE(RSTALUMODE), | ||||||
|  |             .RSTB(RSTB), | ||||||
|  |             .RSTC(RSTC), | ||||||
|  |             .RSTCTRL(RSTCTRL), | ||||||
|  |             .RSTD(RSTD), | ||||||
|  |             .RSTINMODE(RSTINMODE), | ||||||
|  |             .RSTM(RSTM), | ||||||
|  |             .RSTP(RSTP) | ||||||
|  |         ); | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  |     wire [29:0] iA; | ||||||
|  |     wire [17:0] iB; | ||||||
|  |     wire [47:0] iC; | ||||||
|  |     wire [24:0] iD; | ||||||
|  | 
 | ||||||
|  |     wire pA, pB, pC, pD, pAD, pM, pP; | ||||||
|  |     wire [47:0] oP, mP; | ||||||
|  |     wire [47:0] oPCOUT, mPCOUT; | ||||||
|  | 
 | ||||||
|  |     generate | ||||||
|  |     if (USE_MULT == "MULTIPLY" && USE_DPORT == "FALSE") begin | ||||||
|  |         // Disconnect the A-input if MREG is enabled, since | ||||||
|  |         //   combinatorial path is broken | ||||||
|  |         if (AREG == 0 && MREG == 0 && PREG == 0) | ||||||
|  |             assign iA = A, pA = 1'bx; | ||||||
|  |         else | ||||||
|  |             \$__ABC_REG #(.WIDTH(30)) rA (.I(A), .O(iA), .Q(pA)); | ||||||
|  |         if (BREG == 0 && MREG == 0 && PREG == 0) | ||||||
|  |             assign iB = B, pB = 1'bx; | ||||||
|  |         else | ||||||
|  |             \$__ABC_REG #(.WIDTH(18)) rB (.I(B), .O(iB), .Q(pB)); | ||||||
|  |         if (CREG == 0 && PREG == 0) | ||||||
|  |             assign iC = C, pC = 1'bx; | ||||||
|  |         else | ||||||
|  |             \$__ABC_REG #(.WIDTH(48)) rC (.I(C), .O(iC), .Q(pC)); | ||||||
|  |         if (DREG == 0) | ||||||
|  |             assign iD = D; | ||||||
|  |         else if (techmap_guard) | ||||||
|  |         $error("Invalid DSP48E1 configuration: DREG enabled but USE_DPORT == \"FALSE\""); | ||||||
|  |         assign pD = 1'bx; | ||||||
|  |         if (ADREG == 1 && techmap_guard) | ||||||
|  |             $error("Invalid DSP48E1 configuration: ADREG enabled but USE_DPORT == \"FALSE\""); | ||||||
|  |         assign pAD = 1'bx; | ||||||
|  |     if (PREG == 0) begin | ||||||
|  |         if (MREG == 1) | ||||||
|  |         \$__ABC_REG rM (.Q(pM)); | ||||||
|  |         else | ||||||
|  |         assign pM = 1'bx; | ||||||
|  |         assign pP = 1'bx; | ||||||
|  |     end else begin | ||||||
|  |             assign pM = 1'bx; | ||||||
|  |             \$__ABC_REG rP (.Q(pP)); | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         if (MREG == 0 && PREG == 0) | ||||||
|  |             assign mP = oP, mPCOUT = oPCOUT; | ||||||
|  |         else | ||||||
|  |             assign mP = 1'bx, mPCOUT = 1'bx; | ||||||
|  |         \$__ABC_DSP48E1_MULT_P_MUX muxP ( | ||||||
|  |             .Aq(pA), .Bq(pB), .Cq(pC), .Dq(pD), .ADq(pAD), .I(oP), .Mq(pM), .P(mP), .Pq(pP), .O(P) | ||||||
|  |         ); | ||||||
|  |         \$__ABC_DSP48E1_MULT_PCOUT_MUX muxPCOUT ( | ||||||
|  |             .Aq(pA), .Bq(pB), .Cq(pC), .Dq(pD), .ADq(pAD), .I(oPCOUT), .Mq(pM), .P(mPCOUT), .Pq(pP), .O(PCOUT) | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         `DSP48E1_INST(\$__ABC_DSP48E1_MULT ) | ||||||
|  |     end | ||||||
|  |     else if (USE_MULT == "MULTIPLY" && USE_DPORT == "TRUE") begin | ||||||
|  |         // Disconnect the A-input if MREG is enabled, since | ||||||
|  |         //   combinatorial path is broken | ||||||
|  |         if (AREG == 0 && ADREG == 0 && MREG == 0 && PREG == 0) | ||||||
|  |             assign iA = A, pA = 1'bx; | ||||||
|  |         else | ||||||
|  |             \$__ABC_REG #(.WIDTH(30)) rA (.I(A), .O(iA), .Q(pA)); | ||||||
|  |         if (BREG == 0 && MREG == 0 && PREG == 0) | ||||||
|  |             assign iB = B, pB = 1'bx; | ||||||
|  |         else | ||||||
|  |             \$__ABC_REG #(.WIDTH(18)) rB (.I(B), .O(iB), .Q(pB)); | ||||||
|  |         if (CREG == 0 && PREG == 0) | ||||||
|  |             assign iC = C, pC = 1'bx; | ||||||
|  |         else | ||||||
|  |             \$__ABC_REG #(.WIDTH(48)) rC (.I(C), .O(iC), .Q(pC)); | ||||||
|  |         if (DREG == 0 && ADREG == 0) | ||||||
|  |             assign iD = D, pD = 1'bx; | ||||||
|  |         else | ||||||
|  |             \$__ABC_REG #(.WIDTH(25)) rD (.I(D), .O(iD), .Q(pD)); | ||||||
|  |         if (PREG == 0) begin | ||||||
|  |             if (MREG == 1) begin | ||||||
|  |                 assign pAD = 1'bx; | ||||||
|  |         \$__ABC_REG rM (.Q(pM)); | ||||||
|  |             end else begin | ||||||
|  |                 if (ADREG == 1) | ||||||
|  |                     \$__ABC_REG rAD (.Q(pAD)); | ||||||
|  |                 else | ||||||
|  |                     assign pAD = 1'bx; | ||||||
|  |         assign pM = 1'bx; | ||||||
|  |         end | ||||||
|  |         assign pP = 1'bx; | ||||||
|  |     end else begin | ||||||
|  |             assign pAD = 1'bx, pM = 1'bx; | ||||||
|  |             \$__ABC_REG rP (.Q(pP)); | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         if (MREG == 0 && PREG == 0) | ||||||
|  |             assign mP = oP, mPCOUT = oPCOUT; | ||||||
|  |         else | ||||||
|  |             assign mP = 1'bx, mPCOUT = 1'bx; | ||||||
|  |         \$__ABC_DSP48E1_MULT_DPORT_P_MUX muxP ( | ||||||
|  |             .Aq(pA), .Bq(pB), .Cq(pC), .Dq(pD), .ADq(pAD), .I(oP), .Mq(pM), .P(mP), .Pq(pP), .O(P) | ||||||
|  |         ); | ||||||
|  |         \$__ABC_DSP48E1_MULT_DPORT_PCOUT_MUX muxPCOUT ( | ||||||
|  |             .Aq(pA), .Bq(pB), .Cq(pC), .Dq(pD), .ADq(pAD), .I(oPCOUT), .Mq(pM), .P(mPCOUT), .Pq(pP), .O(PCOUT) | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         `DSP48E1_INST(\$__ABC_DSP48E1_MULT_DPORT ) | ||||||
|  |     end | ||||||
|  |     else if (USE_MULT == "NONE" && USE_DPORT == "FALSE") begin | ||||||
|  |         // Disconnect the A-input if MREG is enabled, since | ||||||
|  |         //   combinatorial path is broken | ||||||
|  |         if (AREG == 0 && PREG == 0) | ||||||
|  |             assign iA = A, pA = 1'bx; | ||||||
|  |         else | ||||||
|  |             \$__ABC_REG #(.WIDTH(30)) rA (.I(A), .O(iA), .Q(pA)); | ||||||
|  |         if (BREG == 0 && PREG == 0) | ||||||
|  |             assign iB = B, pB = 1'bx; | ||||||
|  |         else | ||||||
|  |             \$__ABC_REG #(.WIDTH(18)) rB (.I(B), .O(iB), .Q(pB)); | ||||||
|  |         if (CREG == 0 && PREG == 0) | ||||||
|  |             assign iC = C, pC = 1'bx; | ||||||
|  |         else | ||||||
|  |             \$__ABC_REG #(.WIDTH(48)) rC (.I(C), .O(iC), .Q(pC)); | ||||||
|  |         if (DREG == 1 && techmap_guard) | ||||||
|  |             $error("Invalid DSP48E1 configuration: DREG enabled but USE_DPORT == \"FALSE\""); | ||||||
|  |         assign pD = 1'bx; | ||||||
|  |         if (ADREG == 1 && techmap_guard) | ||||||
|  |             $error("Invalid DSP48E1 configuration: ADREG enabled but USE_DPORT == \"FALSE\""); | ||||||
|  |         assign pAD = 1'bx; | ||||||
|  |         if (MREG == 1 && techmap_guard) | ||||||
|  |             $error("Invalid DSP48E1 configuration: MREG enabled but USE_MULT == \"NONE\""); | ||||||
|  |         assign pM = 1'bx; | ||||||
|  |         if (PREG == 1) | ||||||
|  |             \$__ABC_REG rP (.Q(pP)); | ||||||
|  |         else | ||||||
|  |             assign pP = 1'bx; | ||||||
|  | 
 | ||||||
|  |         if (MREG == 0 && PREG == 0) | ||||||
|  |             assign mP = oP, mPCOUT = oPCOUT; | ||||||
|  |         else | ||||||
|  |             assign mP = 1'bx, mPCOUT = 1'bx; | ||||||
|  |         \$__ABC_DSP48E1_P_MUX muxP ( | ||||||
|  |             .Aq(pA), .Bq(pB), .Cq(pC), .Dq(pD), .ADq(pAD), .I(oP), .Mq(pM), .P(mP), .Pq(pP), .O(P) | ||||||
|  |         ); | ||||||
|  |         \$__ABC_DSP48E1_PCOUT_MUX muxPCOUT ( | ||||||
|  |             .Aq(pA), .Bq(pB), .Cq(pC), .Dq(pD), .ADq(pAD), .I(oPCOUT), .Mq(pM), .P(mPCOUT), .Pq(pP), .O(PCOUT) | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         `DSP48E1_INST(\$__ABC_DSP48E1 ) | ||||||
|  |     end | ||||||
|  |     else | ||||||
|  |         $error("Invalid DSP48E1 configuration"); | ||||||
|  |     endgenerate | ||||||
|  |     `undef DSP48E1_INST | ||||||
|  | endmodule | ||||||
|  |  | ||||||
|  | @ -20,15 +20,171 @@ | ||||||
| 
 | 
 | ||||||
| // ============================================================================ | // ============================================================================ | ||||||
| 
 | 
 | ||||||
|  | // Box containing MUXF7.[AB] + MUXF8, | ||||||
|  | //   Necessary to make these an atomic unit so that | ||||||
|  | //   ABC cannot optimise just one of the MUXF7 away | ||||||
|  | //   and expect to save on its delay | ||||||
| (* abc_box_id = 3, lib_whitebox *) | (* abc_box_id = 3, lib_whitebox *) | ||||||
| module \$__XILINX_MUXF78 (output O, input I0, I1, I2, I3, S0, S1); | module \$__XILINX_MUXF78 (output O, input I0, I1, I2, I3, S0, S1); | ||||||
|   assign O = S1 ? (S0 ? I3 : I2) |   assign O = S1 ? (S0 ? I3 : I2) | ||||||
|                 : (S0 ? I1 : I0); |                 : (S0 ? I1 : I0); | ||||||
| endmodule | endmodule | ||||||
| 
 | 
 | ||||||
|  | // Box to emulate comb/seq behaviour of RAMD{32,64} and SRL{16,32} | ||||||
|  | //   Necessary since RAMD* and SRL* have both combinatorial (i.e. | ||||||
|  | //   same-cycle read operation) and sequential (write operation | ||||||
|  | //   is only committed on the next clock edge). | ||||||
|  | //   To model the combinatorial path, such cells have to be split | ||||||
|  | //   into comb and seq parts, with this box modelling only the former. | ||||||
| (* abc_box_id=2000 *) | (* abc_box_id=2000 *) | ||||||
| module \$__ABC_LUT6 (input A, input [5:0] S, output Y); | module \$__ABC_LUT6 (input A, input [5:0] S, output Y); | ||||||
| endmodule | endmodule | ||||||
|  | // Box to emulate comb/seq behaviour of RAMD128 | ||||||
| (* abc_box_id=2001 *) | (* abc_box_id=2001 *) | ||||||
| module \$__ABC_LUT7 (input A, input [6:0] S, output Y); | module \$__ABC_LUT7 (input A, input [6:0] S, output Y); | ||||||
| endmodule | endmodule | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | // Modules used to model the comb/seq behaviour of DSP48E1 | ||||||
|  | //   With abc_map.v responsible for splicing the below modules | ||||||
|  | //   between the combinatorial DSP48E1 box (e.g. disconnecting | ||||||
|  | //   A when AREG, MREG or PREG is enabled and splicing in the | ||||||
|  | //   "$__ABC_DSP48E1_REG" blackbox as "REG" in the diagram below) | ||||||
|  | //   this acts to first disables the combinatorial path (as there | ||||||
|  | //   is no connectivity through REG), and secondly, since this is | ||||||
|  | //   blackbox a new PI will be introduced with an arrival time of | ||||||
|  | //   zero. | ||||||
|  | //   Note: Since these "$__ABC_DSP48E1_REG" modules are of a | ||||||
|  | //   sequential nature, they are not passed as a box to ABC and | ||||||
|  | //   (desirably) represented as PO/PIs. | ||||||
|  | // | ||||||
|  | //   At the DSP output, we place a blackbox mux ("M" in the diagram | ||||||
|  | //   below) to capture the fact that the critical-path could come | ||||||
|  | //   from any one of its inputs. | ||||||
|  | //   In contrast to "REG", the "$__ABC_DSP48E1_*_MUX" modules are | ||||||
|  | //   combinatorial blackboxes that do get passed to ABC. | ||||||
|  | //   The propagation delay through this box (specified in the box | ||||||
|  | //   file) captures the arrival time of the register (i.e. | ||||||
|  | //   propagation from AREG to P after clock edge), or zero delay | ||||||
|  | //   for the combinatorial path from the DSP. | ||||||
|  | // | ||||||
|  | //   Doing so should means that ABC is able to analyse the | ||||||
|  | //   worst-case delay through to P, regardless of if it was | ||||||
|  | //   through any combinatorial paths (e.g. B, below) or an | ||||||
|  | //   internal register (A2REG). | ||||||
|  | //   However, the true value of being as complete as this is | ||||||
|  | //   questionable since if AREG=1 and BREG=0 (as below) | ||||||
|  | //   then the worse-case path would very likely be through B | ||||||
|  | //   and very unlikely to be through AREG.Q...? | ||||||
|  | // | ||||||
|  | //   In graphical form: | ||||||
|  | // | ||||||
|  | //                 +-----+ | ||||||
|  | //         +------>> REG >>----+ | ||||||
|  | //         |       +-----+     | | ||||||
|  | //         |                   | | ||||||
|  | //         |    +---------+    |   __ | ||||||
|  | //    A >>-+X X-|         |    +--|  \ | ||||||
|  | //              | DSP48E1 |P      | M |--->> P | ||||||
|  | //              | AREG=1  |-------|__/ | ||||||
|  | //    B >>------|         | | ||||||
|  | //              +---------+ | ||||||
|  | // | ||||||
|  | `define ABC_DSP48E1_MUX(__NAME__) """ | ||||||
|  | module __NAME__ (input Aq, ADq, Bq, Cq, Dq, input [47:0] I, input Mq, input [47:0] P, input Pq, output [47:0] O); | ||||||
|  | endmodule | ||||||
|  | """ | ||||||
|  | (* abc_box_id=2100 *) `ABC_DSP48E1_MUX(\$__ABC_DSP48E1_MULT_P_MUX ) | ||||||
|  | (* abc_box_id=2101 *) `ABC_DSP48E1_MUX(\$__ABC_DSP48E1_MULT_PCOUT_MUX ) | ||||||
|  | (* abc_box_id=2102 *) `ABC_DSP48E1_MUX(\$__ABC_DSP48E1_MULT_DPORT_P_MUX ) | ||||||
|  | (* abc_box_id=2103 *) `ABC_DSP48E1_MUX(\$__ABC_DSP48E1_MULT_DPORT_PCOUT_MUX ) | ||||||
|  | (* abc_box_id=2104 *) `ABC_DSP48E1_MUX(\$__ABC_DSP48E1_P_MUX ) | ||||||
|  | (* abc_box_id=2105 *) `ABC_DSP48E1_MUX(\$__ABC_DSP48E1_PCOUT_MUX ) | ||||||
|  | 
 | ||||||
|  | `define ABC_DSP48E1(__NAME__) """ | ||||||
|  | module __NAME__ ( | ||||||
|  |     output [29:0] ACOUT, | ||||||
|  |     output [17:0] BCOUT, | ||||||
|  |     output reg CARRYCASCOUT, | ||||||
|  |     output reg [3:0] CARRYOUT, | ||||||
|  |     output reg MULTSIGNOUT, | ||||||
|  |     output OVERFLOW, | ||||||
|  |     output reg signed [47:0] P, | ||||||
|  |     output PATTERNBDETECT, | ||||||
|  |     output PATTERNDETECT, | ||||||
|  |     output [47:0] PCOUT, | ||||||
|  |     output UNDERFLOW, | ||||||
|  |     input signed [29:0] A, | ||||||
|  |     input [29:0] ACIN, | ||||||
|  |     input [3:0] ALUMODE, | ||||||
|  |     input signed [17:0] B, | ||||||
|  |     input [17:0] BCIN, | ||||||
|  |     input [47:0] C, | ||||||
|  |     input CARRYCASCIN, | ||||||
|  |     input CARRYIN, | ||||||
|  |     input [2:0] CARRYINSEL, | ||||||
|  |     input CEA1, | ||||||
|  |     input CEA2, | ||||||
|  |     input CEAD, | ||||||
|  |     input CEALUMODE, | ||||||
|  |     input CEB1, | ||||||
|  |     input CEB2, | ||||||
|  |     input CEC, | ||||||
|  |     input CECARRYIN, | ||||||
|  |     input CECTRL, | ||||||
|  |     input CED, | ||||||
|  |     input CEINMODE, | ||||||
|  |     input CEM, | ||||||
|  |     input CEP, | ||||||
|  |     input CLK, | ||||||
|  |     input [24:0] D, | ||||||
|  |     input [4:0] INMODE, | ||||||
|  |     input MULTSIGNIN, | ||||||
|  |     input [6:0] OPMODE, | ||||||
|  |     input [47:0] PCIN, | ||||||
|  |     input RSTA, | ||||||
|  |     input RSTALLCARRYIN, | ||||||
|  |     input RSTALUMODE, | ||||||
|  |     input RSTB, | ||||||
|  |     input RSTC, | ||||||
|  |     input RSTCTRL, | ||||||
|  |     input RSTD, | ||||||
|  |     input RSTINMODE, | ||||||
|  |     input RSTM, | ||||||
|  |     input RSTP | ||||||
|  | ); | ||||||
|  |     parameter integer ACASCREG = 1; | ||||||
|  |     parameter integer ADREG = 1; | ||||||
|  |     parameter integer ALUMODEREG = 1; | ||||||
|  |     parameter integer AREG = 1; | ||||||
|  |     parameter AUTORESET_PATDET = "NO_RESET"; | ||||||
|  |     parameter A_INPUT = "DIRECT"; | ||||||
|  |     parameter integer BCASCREG = 1; | ||||||
|  |     parameter integer BREG = 1; | ||||||
|  |     parameter B_INPUT = "DIRECT"; | ||||||
|  |     parameter integer CARRYINREG = 1; | ||||||
|  |     parameter integer CARRYINSELREG = 1; | ||||||
|  |     parameter integer CREG = 1; | ||||||
|  |     parameter integer DREG = 1; | ||||||
|  |     parameter integer INMODEREG = 1; | ||||||
|  |     parameter integer MREG = 1; | ||||||
|  |     parameter integer OPMODEREG = 1; | ||||||
|  |     parameter integer PREG = 1; | ||||||
|  |     parameter SEL_MASK = "MASK"; | ||||||
|  |     parameter SEL_PATTERN = "PATTERN"; | ||||||
|  |     parameter USE_DPORT = "FALSE"; | ||||||
|  |     parameter USE_MULT = "MULTIPLY"; | ||||||
|  |     parameter USE_PATTERN_DETECT = "NO_PATDET"; | ||||||
|  |     parameter USE_SIMD = "ONE48"; | ||||||
|  |     parameter [47:0] MASK = 48'h3FFFFFFFFFFF; | ||||||
|  |     parameter [47:0] PATTERN = 48'h000000000000; | ||||||
|  |     parameter [3:0] IS_ALUMODE_INVERTED = 4'b0; | ||||||
|  |     parameter [0:0] IS_CARRYIN_INVERTED = 1'b0; | ||||||
|  |     parameter [0:0] IS_CLK_INVERTED = 1'b0; | ||||||
|  |     parameter [4:0] IS_INMODE_INVERTED = 5'b0; | ||||||
|  |     parameter [6:0] IS_OPMODE_INVERTED = 7'b0; | ||||||
|  | endmodule | ||||||
|  | """ | ||||||
|  | (* abc_box_id=3000 *) `ABC_DSP48E1(\$__ABC_DSP48E1_MULT ) | ||||||
|  | (* abc_box_id=3001 *) `ABC_DSP48E1(\$__ABC_DSP48E1_MULT_DPORT ) | ||||||
|  | (* abc_box_id=3002 *) `ABC_DSP48E1(\$__ABC_DSP48E1 ) | ||||||
|  |  | ||||||
|  | @ -26,3 +26,186 @@ endmodule | ||||||
| module \$__ABC_LUT7 (input A, input [6:0] S, output Y); | module \$__ABC_LUT7 (input A, input [6:0] S, output Y); | ||||||
|   assign Y = A; |   assign Y = A; | ||||||
| endmodule | endmodule | ||||||
|  | 
 | ||||||
|  | module \$__ABC_REG (input [WIDTH-1:0] I, output [WIDTH-1:0] O, output Q); | ||||||
|  |   parameter WIDTH = 1; | ||||||
|  |   assign O = I; | ||||||
|  | endmodule | ||||||
|  | (* techmap_celltype = "$__ABC_DSP48E1_MULT_P_MUX $__ABC_DSP48E1_MULT_PCOUT_MUX $__ABC_DSP48E1_MULT_DPORT_P_MUX $__ABC_DSP48E1_MULT_DPORT_PCOUT_MUX $__ABC_DSP48E1_P_MUX $__ABC_DSP48E1_PCOUT_MUX" *) | ||||||
|  | module \$__ABC_DSP48E1_MUX ( | ||||||
|  |   input Aq, Bq, Cq, Dq, ADq, | ||||||
|  |   input [47:0] I, | ||||||
|  |   input Mq, | ||||||
|  |   input [47:0] P,  | ||||||
|  |   input Pq,  | ||||||
|  |   output [47:0] O | ||||||
|  | ); | ||||||
|  |   assign O = I; | ||||||
|  | endmodule | ||||||
|  | 
 | ||||||
|  | (* techmap_celltype = "$__ABC_DSP48E1_MULT $__ABC_DSP48E1_MULT_DPORT $__ABC_DSP48E1" *) | ||||||
|  | module \$__ABC_DSP48E1 ( | ||||||
|  |     (* techmap_autopurge *) output [29:0] ACOUT, | ||||||
|  |     (* techmap_autopurge *) output [17:0] BCOUT, | ||||||
|  |     (* techmap_autopurge *) output reg CARRYCASCOUT, | ||||||
|  |     (* techmap_autopurge *) output reg [3:0] CARRYOUT, | ||||||
|  |     (* techmap_autopurge *) output reg MULTSIGNOUT, | ||||||
|  |     (* techmap_autopurge *) output OVERFLOW, | ||||||
|  |     (* techmap_autopurge *) output reg signed [47:0] P, | ||||||
|  |     (* techmap_autopurge *) output PATTERNBDETECT, | ||||||
|  |     (* techmap_autopurge *) output PATTERNDETECT, | ||||||
|  |     (* techmap_autopurge *) output [47:0] PCOUT, | ||||||
|  |     (* techmap_autopurge *) output UNDERFLOW, | ||||||
|  |     (* techmap_autopurge *) input signed [29:0] A, | ||||||
|  |     (* techmap_autopurge *) input [29:0] ACIN, | ||||||
|  |     (* techmap_autopurge *) input [3:0] ALUMODE, | ||||||
|  |     (* techmap_autopurge *) input signed [17:0] B, | ||||||
|  |     (* techmap_autopurge *) input [17:0] BCIN, | ||||||
|  |     (* techmap_autopurge *) input [47:0] C, | ||||||
|  |     (* techmap_autopurge *) input CARRYCASCIN, | ||||||
|  |     (* techmap_autopurge *) input CARRYIN, | ||||||
|  |     (* techmap_autopurge *) input [2:0] CARRYINSEL, | ||||||
|  |     (* techmap_autopurge *) input CEA1, | ||||||
|  |     (* techmap_autopurge *) input CEA2, | ||||||
|  |     (* techmap_autopurge *) input CEAD, | ||||||
|  |     (* techmap_autopurge *) input CEALUMODE, | ||||||
|  |     (* techmap_autopurge *) input CEB1, | ||||||
|  |     (* techmap_autopurge *) input CEB2, | ||||||
|  |     (* techmap_autopurge *) input CEC, | ||||||
|  |     (* techmap_autopurge *) input CECARRYIN, | ||||||
|  |     (* techmap_autopurge *) input CECTRL, | ||||||
|  |     (* techmap_autopurge *) input CED, | ||||||
|  |     (* techmap_autopurge *) input CEINMODE, | ||||||
|  |     (* techmap_autopurge *) input CEM, | ||||||
|  |     (* techmap_autopurge *) input CEP, | ||||||
|  |     (* techmap_autopurge *) input CLK, | ||||||
|  |     (* techmap_autopurge *) input [24:0] D, | ||||||
|  |     (* techmap_autopurge *) input [4:0] INMODE, | ||||||
|  |     (* techmap_autopurge *) input MULTSIGNIN, | ||||||
|  |     (* techmap_autopurge *) input [6:0] OPMODE, | ||||||
|  |     (* techmap_autopurge *) input [47:0] PCIN, | ||||||
|  |     (* techmap_autopurge *) input RSTA, | ||||||
|  |     (* techmap_autopurge *) input RSTALLCARRYIN, | ||||||
|  |     (* techmap_autopurge *) input RSTALUMODE, | ||||||
|  |     (* techmap_autopurge *) input RSTB, | ||||||
|  |     (* techmap_autopurge *) input RSTC, | ||||||
|  |     (* techmap_autopurge *) input RSTCTRL, | ||||||
|  |     (* techmap_autopurge *) input RSTD, | ||||||
|  |     (* techmap_autopurge *) input RSTINMODE, | ||||||
|  |     (* techmap_autopurge *) input RSTM, | ||||||
|  |     (* techmap_autopurge *) input RSTP | ||||||
|  | ); | ||||||
|  |     parameter integer ACASCREG = 1; | ||||||
|  |     parameter integer ADREG = 1; | ||||||
|  |     parameter integer ALUMODEREG = 1; | ||||||
|  |     parameter integer AREG = 1; | ||||||
|  |     parameter AUTORESET_PATDET = "NO_RESET"; | ||||||
|  |     parameter A_INPUT = "DIRECT"; | ||||||
|  |     parameter integer BCASCREG = 1; | ||||||
|  |     parameter integer BREG = 1; | ||||||
|  |     parameter B_INPUT = "DIRECT"; | ||||||
|  |     parameter integer CARRYINREG = 1; | ||||||
|  |     parameter integer CARRYINSELREG = 1; | ||||||
|  |     parameter integer CREG = 1; | ||||||
|  |     parameter integer DREG = 1; | ||||||
|  |     parameter integer INMODEREG = 1; | ||||||
|  |     parameter integer MREG = 1; | ||||||
|  |     parameter integer OPMODEREG = 1; | ||||||
|  |     parameter integer PREG = 1; | ||||||
|  |     parameter SEL_MASK = "MASK"; | ||||||
|  |     parameter SEL_PATTERN = "PATTERN"; | ||||||
|  |     parameter USE_DPORT = "FALSE"; | ||||||
|  |     parameter USE_MULT = "MULTIPLY"; | ||||||
|  |     parameter USE_PATTERN_DETECT = "NO_PATDET"; | ||||||
|  |     parameter USE_SIMD = "ONE48"; | ||||||
|  |     parameter [47:0] MASK = 48'h3FFFFFFFFFFF; | ||||||
|  |     parameter [47:0] PATTERN = 48'h000000000000; | ||||||
|  |     parameter [3:0] IS_ALUMODE_INVERTED = 4'b0; | ||||||
|  |     parameter [0:0] IS_CARRYIN_INVERTED = 1'b0; | ||||||
|  |     parameter [0:0] IS_CLK_INVERTED = 1'b0; | ||||||
|  |     parameter [4:0] IS_INMODE_INVERTED = 5'b0; | ||||||
|  |     parameter [6:0] IS_OPMODE_INVERTED = 7'b0; | ||||||
|  | 
 | ||||||
|  |     DSP48E1 #( | ||||||
|  |         .ACASCREG(ACASCREG), | ||||||
|  |         .ADREG(ADREG), | ||||||
|  |         .ALUMODEREG(ALUMODEREG), | ||||||
|  |         .AREG(AREG), | ||||||
|  |         .AUTORESET_PATDET(AUTORESET_PATDET), | ||||||
|  |         .A_INPUT(A_INPUT), | ||||||
|  |         .BCASCREG(BCASCREG), | ||||||
|  |         .BREG(BREG), | ||||||
|  |         .B_INPUT(B_INPUT), | ||||||
|  |         .CARRYINREG(CARRYINREG), | ||||||
|  |         .CARRYINSELREG(CARRYINSELREG), | ||||||
|  |         .CREG(CREG), | ||||||
|  |         .DREG(DREG), | ||||||
|  |         .INMODEREG(INMODEREG), | ||||||
|  |         .MREG(MREG), | ||||||
|  |         .OPMODEREG(OPMODEREG), | ||||||
|  |         .PREG(PREG), | ||||||
|  |         .SEL_MASK(SEL_MASK), | ||||||
|  |         .SEL_PATTERN(SEL_PATTERN), | ||||||
|  |         .USE_DPORT(USE_DPORT), | ||||||
|  |         .USE_MULT(USE_MULT), | ||||||
|  |         .USE_PATTERN_DETECT(USE_PATTERN_DETECT), | ||||||
|  |         .USE_SIMD(USE_SIMD), | ||||||
|  |         .MASK(MASK), | ||||||
|  |         .PATTERN(PATTERN), | ||||||
|  |         .IS_ALUMODE_INVERTED(IS_ALUMODE_INVERTED), | ||||||
|  |         .IS_CARRYIN_INVERTED(IS_CARRYIN_INVERTED), | ||||||
|  |         .IS_CLK_INVERTED(IS_CLK_INVERTED), | ||||||
|  |         .IS_INMODE_INVERTED(IS_INMODE_INVERTED), | ||||||
|  |         .IS_OPMODE_INVERTED(IS_OPMODE_INVERTED) | ||||||
|  |     ) _TECHMAP_REPLACE_ ( | ||||||
|  |         .ACOUT(ACOUT), | ||||||
|  |         .BCOUT(BCOUT), | ||||||
|  |         .CARRYCASCOUT(CARRYCASCOUT), | ||||||
|  |         .CARRYOUT(CARRYOUT), | ||||||
|  |         .MULTSIGNOUT(MULTSIGNOUT), | ||||||
|  |         .OVERFLOW(OVERFLOW), | ||||||
|  |         .P(P), | ||||||
|  |         .PATTERNBDETECT(PATTERNBDETECT), | ||||||
|  |         .PATTERNDETECT(PATTERNDETECT), | ||||||
|  |         .PCOUT(PCOUT), | ||||||
|  |         .UNDERFLOW(UNDERFLOW), | ||||||
|  |         .A(A), | ||||||
|  |         .ACIN(ACIN), | ||||||
|  |         .ALUMODE(ALUMODE), | ||||||
|  |         .B(B), | ||||||
|  |         .BCIN(BCIN), | ||||||
|  |         .C(C), | ||||||
|  |         .CARRYCASCIN(CARRYCASCIN), | ||||||
|  |         .CARRYIN(CARRYIN), | ||||||
|  |         .CARRYINSEL(CARRYINSEL), | ||||||
|  |         .CEA1(CEA1), | ||||||
|  |         .CEA2(CEA2), | ||||||
|  |         .CEAD(CEAD), | ||||||
|  |         .CEALUMODE(CEALUMODE), | ||||||
|  |         .CEB1(CEB1), | ||||||
|  |         .CEB2(CEB2), | ||||||
|  |         .CEC(CEC), | ||||||
|  |         .CECARRYIN(CECARRYIN), | ||||||
|  |         .CECTRL(CECTRL), | ||||||
|  |         .CED(CED), | ||||||
|  |         .CEINMODE(CEINMODE), | ||||||
|  |         .CEM(CEM), | ||||||
|  |         .CEP(CEP), | ||||||
|  |         .CLK(CLK), | ||||||
|  |         .D(D), | ||||||
|  |         .INMODE(INMODE), | ||||||
|  |         .MULTSIGNIN(MULTSIGNIN), | ||||||
|  |         .OPMODE(OPMODE), | ||||||
|  |         .PCIN(PCIN), | ||||||
|  |         .RSTA(RSTA), | ||||||
|  |         .RSTALLCARRYIN(RSTALLCARRYIN), | ||||||
|  |         .RSTALUMODE(RSTALUMODE), | ||||||
|  |         .RSTB(RSTB), | ||||||
|  |         .RSTC(RSTC), | ||||||
|  |         .RSTCTRL(RSTCTRL), | ||||||
|  |         .RSTD(RSTD), | ||||||
|  |         .RSTINMODE(RSTINMODE), | ||||||
|  |         .RSTM(RSTM), | ||||||
|  |         .RSTP(RSTP) | ||||||
|  |     ); | ||||||
|  | endmodule | ||||||
|  |  | ||||||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -525,3 +525,466 @@ module SRLC32E ( | ||||||
|       always @(posedge CLK) if (CE) r <= { r[30:0], D }; |       always @(posedge CLK) if (CE) r <= { r[30:0], D }; | ||||||
|   endgenerate |   endgenerate | ||||||
| endmodule | endmodule | ||||||
|  | 
 | ||||||
|  | module DSP48E1 ( | ||||||
|  |     output [29:0] ACOUT, | ||||||
|  |     output [17:0] BCOUT, | ||||||
|  |     output reg CARRYCASCOUT, | ||||||
|  |     output reg [3:0] CARRYOUT, | ||||||
|  |     output reg MULTSIGNOUT, | ||||||
|  |     output OVERFLOW, | ||||||
|  |     output reg signed [47:0] P, | ||||||
|  |     output reg PATTERNBDETECT, | ||||||
|  |     output reg PATTERNDETECT, | ||||||
|  |     output [47:0] PCOUT, | ||||||
|  |     output UNDERFLOW, | ||||||
|  |     input signed [29:0] A, | ||||||
|  |     input [29:0] ACIN, | ||||||
|  |     input [3:0] ALUMODE, | ||||||
|  |     input signed [17:0] B, | ||||||
|  |     input [17:0] BCIN, | ||||||
|  |     input [47:0] C, | ||||||
|  |     input CARRYCASCIN, | ||||||
|  |     input CARRYIN, | ||||||
|  |     input [2:0] CARRYINSEL, | ||||||
|  |     input CEA1, | ||||||
|  |     input CEA2, | ||||||
|  |     input CEAD, | ||||||
|  |     input CEALUMODE, | ||||||
|  |     input CEB1, | ||||||
|  |     input CEB2, | ||||||
|  |     input CEC, | ||||||
|  |     input CECARRYIN, | ||||||
|  |     input CECTRL, | ||||||
|  |     input CED, | ||||||
|  |     input CEINMODE, | ||||||
|  |     input CEM, | ||||||
|  |     input CEP, | ||||||
|  |     (* clkbuf_sink *) input CLK, | ||||||
|  |     input [24:0] D, | ||||||
|  |     input [4:0] INMODE, | ||||||
|  |     input MULTSIGNIN, | ||||||
|  |     input [6:0] OPMODE, | ||||||
|  |     input [47:0] PCIN, | ||||||
|  |     input RSTA, | ||||||
|  |     input RSTALLCARRYIN, | ||||||
|  |     input RSTALUMODE, | ||||||
|  |     input RSTB, | ||||||
|  |     input RSTC, | ||||||
|  |     input RSTCTRL, | ||||||
|  |     input RSTD, | ||||||
|  |     input RSTINMODE, | ||||||
|  |     input RSTM, | ||||||
|  |     input RSTP | ||||||
|  | ); | ||||||
|  |     parameter integer ACASCREG = 1; | ||||||
|  |     parameter integer ADREG = 1; | ||||||
|  |     parameter integer ALUMODEREG = 1; | ||||||
|  |     parameter integer AREG = 1; | ||||||
|  |     parameter AUTORESET_PATDET = "NO_RESET"; | ||||||
|  |     parameter A_INPUT = "DIRECT"; | ||||||
|  |     parameter integer BCASCREG = 1; | ||||||
|  |     parameter integer BREG = 1; | ||||||
|  |     parameter B_INPUT = "DIRECT"; | ||||||
|  |     parameter integer CARRYINREG = 1; | ||||||
|  |     parameter integer CARRYINSELREG = 1; | ||||||
|  |     parameter integer CREG = 1; | ||||||
|  |     parameter integer DREG = 1; | ||||||
|  |     parameter integer INMODEREG = 1; | ||||||
|  |     parameter integer MREG = 1; | ||||||
|  |     parameter integer OPMODEREG = 1; | ||||||
|  |     parameter integer PREG = 1; | ||||||
|  |     parameter SEL_MASK = "MASK"; | ||||||
|  |     parameter SEL_PATTERN = "PATTERN"; | ||||||
|  |     parameter USE_DPORT = "FALSE"; | ||||||
|  |     parameter USE_MULT = "MULTIPLY"; | ||||||
|  |     parameter USE_PATTERN_DETECT = "NO_PATDET"; | ||||||
|  |     parameter USE_SIMD = "ONE48"; | ||||||
|  |     parameter [47:0] MASK = 48'h3FFFFFFFFFFF; | ||||||
|  |     parameter [47:0] PATTERN = 48'h000000000000; | ||||||
|  |     parameter [3:0] IS_ALUMODE_INVERTED = 4'b0; | ||||||
|  |     parameter [0:0] IS_CARRYIN_INVERTED = 1'b0; | ||||||
|  |     parameter [0:0] IS_CLK_INVERTED = 1'b0; | ||||||
|  |     parameter [4:0] IS_INMODE_INVERTED = 5'b0; | ||||||
|  |     parameter [6:0] IS_OPMODE_INVERTED = 7'b0; | ||||||
|  | 
 | ||||||
|  |     initial begin | ||||||
|  | `ifdef __ICARUS__ | ||||||
|  |         if (AUTORESET_PATDET != "NO_RESET") $fatal(1, "Unsupported AUTORESET_PATDET value"); | ||||||
|  |         if (SEL_MASK != "MASK")     $fatal(1, "Unsupported SEL_MASK value"); | ||||||
|  |         if (SEL_PATTERN != "PATTERN") $fatal(1, "Unsupported SEL_PATTERN value"); | ||||||
|  |         if (USE_SIMD != "ONE48" && USE_SIMD != "TWO24" && USE_SIMD != "FOUR12")    $fatal(1, "Unsupported USE_SIMD value"); | ||||||
|  |         if (IS_ALUMODE_INVERTED != 4'b0) $fatal(1, "Unsupported IS_ALUMODE_INVERTED value"); | ||||||
|  |         if (IS_CARRYIN_INVERTED != 1'b0) $fatal(1, "Unsupported IS_CARRYIN_INVERTED value"); | ||||||
|  |         if (IS_CLK_INVERTED != 1'b0) $fatal(1, "Unsupported IS_CLK_INVERTED value"); | ||||||
|  |         if (IS_INMODE_INVERTED != 5'b0) $fatal(1, "Unsupported IS_INMODE_INVERTED value"); | ||||||
|  |         if (IS_OPMODE_INVERTED != 7'b0) $fatal(1, "Unsupported IS_OPMODE_INVERTED value"); | ||||||
|  | `endif | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     wire signed [29:0] A_muxed; | ||||||
|  |     wire signed [17:0] B_muxed; | ||||||
|  | 
 | ||||||
|  |     generate | ||||||
|  |         if (A_INPUT == "CASCADE") assign A_muxed = ACIN; | ||||||
|  |         else assign A_muxed = A; | ||||||
|  | 
 | ||||||
|  |         if (B_INPUT == "CASCADE") assign B_muxed = BCIN; | ||||||
|  |         else assign B_muxed = B; | ||||||
|  |     endgenerate | ||||||
|  | 
 | ||||||
|  |     reg signed [29:0] Ar1, Ar2; | ||||||
|  |     reg signed [24:0] Dr; | ||||||
|  |     reg signed [17:0] Br1, Br2; | ||||||
|  |     reg signed [47:0] Cr; | ||||||
|  |     reg        [4:0]  INMODEr = 5'b0; | ||||||
|  |     reg        [6:0]  OPMODEr = 7'b0; | ||||||
|  |     reg        [3:0]  ALUMODEr = 4'b0; | ||||||
|  |     reg        [2:0]  CARRYINSELr = 3'b0; | ||||||
|  | 
 | ||||||
|  |     generate | ||||||
|  |         // Configurable A register | ||||||
|  |         if (AREG == 2) begin | ||||||
|  |             initial Ar1 = 30'b0; | ||||||
|  |             initial Ar2 = 30'b0; | ||||||
|  |             always @(posedge CLK) | ||||||
|  |                 if (RSTA) begin | ||||||
|  |                     Ar1 <= 30'b0; | ||||||
|  |                     Ar2 <= 30'b0; | ||||||
|  |                 end else begin | ||||||
|  |                     if (CEA1) Ar1 <= A_muxed; | ||||||
|  |                     if (CEA2) Ar2 <= Ar1; | ||||||
|  |                 end | ||||||
|  |         end else if (AREG == 1) begin | ||||||
|  |             //initial Ar1 = 30'b0; | ||||||
|  |             initial Ar2 = 30'b0; | ||||||
|  |             always @(posedge CLK) | ||||||
|  |                 if (RSTA) begin | ||||||
|  |                     Ar1 <= 30'b0; | ||||||
|  |                     Ar2 <= 30'b0; | ||||||
|  |                 end else begin | ||||||
|  |                     if (CEA1) Ar1 <= A_muxed; | ||||||
|  |                     if (CEA2) Ar2 <= A_muxed; | ||||||
|  |                 end | ||||||
|  |         end else begin | ||||||
|  |             always @* Ar1 <= A_muxed; | ||||||
|  |             always @* Ar2 <= A_muxed; | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         // Configurable B register | ||||||
|  |         if (BREG == 2) begin | ||||||
|  |             initial Br1 = 25'b0; | ||||||
|  |             initial Br2 = 25'b0; | ||||||
|  |             always @(posedge CLK) | ||||||
|  |                 if (RSTB) begin | ||||||
|  |                     Br1 <= 18'b0; | ||||||
|  |                     Br2 <= 18'b0; | ||||||
|  |                 end else begin | ||||||
|  |                     if (CEB1) Br1 <= B_muxed; | ||||||
|  |                     if (CEB2) Br2 <= Br1; | ||||||
|  |                 end | ||||||
|  |         end else if (BREG == 1) begin | ||||||
|  |             //initial Br1 = 25'b0; | ||||||
|  |             initial Br2 = 25'b0; | ||||||
|  |             always @(posedge CLK) | ||||||
|  |                 if (RSTB) begin | ||||||
|  |                     Br1 <= 18'b0; | ||||||
|  |                     Br2 <= 18'b0; | ||||||
|  |                 end else begin | ||||||
|  |                     if (CEB1) Br1 <= B_muxed; | ||||||
|  |                     if (CEB2) Br2 <= B_muxed; | ||||||
|  |                 end | ||||||
|  |         end else begin | ||||||
|  |             always @* Br1 <= B_muxed; | ||||||
|  |             always @* Br2 <= B_muxed; | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         // C and D registers | ||||||
|  |         if (CREG == 1) initial Cr = 48'b0; | ||||||
|  |         if (CREG == 1) begin always @(posedge CLK) if (RSTC) Cr <= 48'b0; else if (CEC) Cr <= C; end | ||||||
|  |         else           always @* Cr <= C; | ||||||
|  | 
 | ||||||
|  |         if (CREG == 1) initial Dr = 25'b0; | ||||||
|  |         if (DREG == 1) begin always @(posedge CLK) if (RSTD) Dr <= 25'b0; else if (CED) Dr <= D; end | ||||||
|  |         else           always @* Dr <= D; | ||||||
|  | 
 | ||||||
|  |         // Control registers | ||||||
|  |         if (INMODEREG == 1) initial INMODEr = 5'b0; | ||||||
|  |         if (INMODEREG == 1) begin always @(posedge CLK) if (RSTINMODE) INMODEr <= 5'b0; else if (CEINMODE) INMODEr <= INMODE; end | ||||||
|  |         else           always @* INMODEr <= INMODE; | ||||||
|  |         if (OPMODEREG == 1) initial OPMODEr = 7'b0; | ||||||
|  |         if (OPMODEREG == 1) begin always @(posedge CLK) if (RSTCTRL) OPMODEr <= 7'b0; else if (CECTRL) OPMODEr <= OPMODE; end | ||||||
|  |         else           always @* OPMODEr <= OPMODE; | ||||||
|  |         if (ALUMODEREG == 1) initial ALUMODEr = 4'b0; | ||||||
|  |         if (ALUMODEREG == 1) begin always @(posedge CLK) if (RSTALUMODE) ALUMODEr <= 4'b0; else if (CEALUMODE) ALUMODEr <= ALUMODE; end | ||||||
|  |         else           always @* ALUMODEr <= ALUMODE; | ||||||
|  |         if (CARRYINSELREG == 1) initial CARRYINSELr = 3'b0; | ||||||
|  |         if (CARRYINSELREG == 1) begin always @(posedge CLK) if (RSTCTRL) CARRYINSELr <= 3'b0; else if (CECTRL) CARRYINSELr <= CARRYINSEL; end | ||||||
|  |         else           always @* CARRYINSELr <= CARRYINSEL; | ||||||
|  |     endgenerate | ||||||
|  | 
 | ||||||
|  |     // A and B cascade | ||||||
|  |     generate | ||||||
|  |         if (ACASCREG == 1 && AREG == 2) assign ACOUT = Ar1; | ||||||
|  |         else assign ACOUT = Ar2; | ||||||
|  |         if (BCASCREG == 1 && BREG == 2) assign BCOUT = Br1; | ||||||
|  |         else assign BCOUT = Br2; | ||||||
|  |     endgenerate | ||||||
|  | 
 | ||||||
|  |     // A/D input selection and pre-adder | ||||||
|  |     wire signed [29:0] Ar12_muxed = INMODEr[0] ? Ar1 : Ar2; | ||||||
|  |     wire signed [24:0] Ar12_gated = INMODEr[1] ? 25'b0 : Ar12_muxed; | ||||||
|  |     wire signed [24:0] Dr_gated   = INMODEr[2] ? Dr : 25'b0; | ||||||
|  |     wire signed [24:0] AD_result  = INMODEr[3] ? (Dr_gated - Ar12_gated) : (Dr_gated + Ar12_gated); | ||||||
|  |     reg  signed [24:0] ADr; | ||||||
|  | 
 | ||||||
|  |     generate | ||||||
|  |         if (ADREG == 1) initial ADr = 25'b0; | ||||||
|  |         if (ADREG == 1) begin always @(posedge CLK) if (RSTD) ADr <= 25'b0; else if (CEAD) ADr <= AD_result; end | ||||||
|  |         else            always @* ADr <= AD_result; | ||||||
|  |     endgenerate | ||||||
|  | 
 | ||||||
|  |     // 25x18 multiplier | ||||||
|  |     wire signed [24:0] A_MULT; | ||||||
|  |     wire signed [17:0] B_MULT = INMODEr[4] ? Br1 : Br2; | ||||||
|  |     generate | ||||||
|  |         if (USE_DPORT == "TRUE") assign A_MULT = ADr; | ||||||
|  |         else assign A_MULT = Ar12_gated; | ||||||
|  |     endgenerate | ||||||
|  | 
 | ||||||
|  |     wire signed [42:0] M = A_MULT * B_MULT; | ||||||
|  |     wire signed [42:0] Mx = (CARRYINSEL == 3'b010) ? 43'bx : M; | ||||||
|  |     reg  signed [42:0] Mr = 43'b0; | ||||||
|  | 
 | ||||||
|  |     // Multiplier result register | ||||||
|  |     generate | ||||||
|  |         if (MREG == 1) begin always @(posedge CLK) if (RSTM) Mr <= 43'b0; else if (CEM) Mr <= Mx; end | ||||||
|  |         else           always @* Mr <= Mx; | ||||||
|  |     endgenerate | ||||||
|  | 
 | ||||||
|  |     wire signed [42:0] Mrx = (CARRYINSELr == 3'b010) ? 43'bx : Mr; | ||||||
|  | 
 | ||||||
|  |     // X, Y and Z ALU inputs | ||||||
|  |     reg signed [47:0] X, Y, Z; | ||||||
|  | 
 | ||||||
|  |     always @* begin | ||||||
|  |         // X multiplexer | ||||||
|  |         case (OPMODEr[1:0]) | ||||||
|  |             2'b00: X = 48'b0; | ||||||
|  |             2'b01: begin X = $signed(Mrx); | ||||||
|  | `ifdef __ICARUS__ | ||||||
|  |                 if (OPMODEr[3:2] != 2'b01) $fatal(1, "OPMODEr[3:2] must be 2'b01 when OPMODEr[1:0] is 2'b01"); | ||||||
|  | `endif | ||||||
|  |             end | ||||||
|  |             2'b10: begin X = P; | ||||||
|  | `ifdef __ICARUS__ | ||||||
|  |                 if (PREG != 1) $fatal(1, "PREG must be 1 when OPMODEr[1:0] is 2'b10"); | ||||||
|  | `endif | ||||||
|  |             end | ||||||
|  |             2'b11: X = $signed({Ar2, Br2}); | ||||||
|  |             default: X = 48'bx; | ||||||
|  |         endcase | ||||||
|  | 
 | ||||||
|  |         // Y multiplexer | ||||||
|  |         case (OPMODEr[3:2]) | ||||||
|  |             2'b00: Y = 48'b0; | ||||||
|  |             2'b01: begin Y = 48'b0; // FIXME: more accurate partial product modelling? | ||||||
|  | `ifdef __ICARUS__ | ||||||
|  |                 if (OPMODEr[1:0] != 2'b01) $fatal(1, "OPMODEr[1:0] must be 2'b01 when OPMODEr[3:2] is 2'b01"); | ||||||
|  | `endif | ||||||
|  |             end | ||||||
|  |             2'b10: Y = {48{1'b1}}; | ||||||
|  |             2'b11: Y = Cr; | ||||||
|  |             default: Y = 48'bx; | ||||||
|  |         endcase | ||||||
|  | 
 | ||||||
|  |         // Z multiplexer | ||||||
|  |         case (OPMODEr[6:4]) | ||||||
|  |             3'b000: Z = 48'b0; | ||||||
|  |             3'b001: Z = PCIN; | ||||||
|  |             3'b010: begin Z = P; | ||||||
|  | `ifdef __ICARUS__ | ||||||
|  |                 if (PREG != 1) $fatal(1, "PREG must be 1 when OPMODEr[6:4] i0s 3'b010"); | ||||||
|  | `endif | ||||||
|  |             end | ||||||
|  |             3'b011: Z = Cr; | ||||||
|  |             3'b100: begin Z = P; | ||||||
|  | `ifdef __ICARUS__ | ||||||
|  |                 if (PREG != 1) $fatal(1, "PREG must be 1 when OPMODEr[6:4] is 3'b100"); | ||||||
|  |                 if (OPMODEr[3:0] != 4'b1000) $fatal(1, "OPMODEr[3:0] must be 4'b1000 when OPMODEr[6:4] i0s 3'b100"); | ||||||
|  | `endif | ||||||
|  |             end | ||||||
|  |             3'b101: Z = $signed(PCIN[47:17]); | ||||||
|  |             3'b110: Z = $signed(P[47:17]); | ||||||
|  |             default: Z = 48'bx; | ||||||
|  |         endcase | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     // Carry in | ||||||
|  |     wire A24_xnor_B17d = A_MULT[24] ~^ B_MULT[17]; | ||||||
|  |     reg CARRYINr = 1'b0, A24_xnor_B17 = 1'b0; | ||||||
|  |     generate | ||||||
|  |         if (CARRYINREG == 1) begin always @(posedge CLK) if (RSTALLCARRYIN) CARRYINr <= 1'b0; else if (CECARRYIN) CARRYINr <= CARRYIN; end | ||||||
|  |         else                 always @* CARRYINr = CARRYIN; | ||||||
|  | 
 | ||||||
|  |         if (MREG == 1) begin always @(posedge CLK) if (RSTALLCARRYIN) A24_xnor_B17 <= 1'b0; else if (CEM) A24_xnor_B17 <= A24_xnor_B17d; end | ||||||
|  |         else                 always @* A24_xnor_B17 = A24_xnor_B17d; | ||||||
|  |     endgenerate | ||||||
|  | 
 | ||||||
|  |     reg cin_muxed; | ||||||
|  | 
 | ||||||
|  |     always @(*) begin | ||||||
|  |         case (CARRYINSELr) | ||||||
|  |             3'b000: cin_muxed = CARRYINr; | ||||||
|  |             3'b001: cin_muxed = ~PCIN[47]; | ||||||
|  |             3'b010: cin_muxed = CARRYCASCIN; | ||||||
|  |             3'b011: cin_muxed = PCIN[47]; | ||||||
|  |             3'b100: cin_muxed = CARRYCASCOUT; | ||||||
|  |             3'b101: cin_muxed = ~P[47]; | ||||||
|  |             3'b110: cin_muxed = A24_xnor_B17; | ||||||
|  |             3'b111: cin_muxed = P[47]; | ||||||
|  |             default: cin_muxed = 1'bx; | ||||||
|  |         endcase | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     wire alu_cin = (ALUMODEr[3] || ALUMODEr[2]) ? 1'b0 : cin_muxed; | ||||||
|  | 
 | ||||||
|  |     // ALU core | ||||||
|  |     wire [47:0] Z_muxinv = ALUMODEr[0] ? ~Z : Z; | ||||||
|  |     wire [47:0] xor_xyz = X ^ Y ^ Z_muxinv; | ||||||
|  |     wire [47:0] maj_xyz = (X & Y) | (X & Z_muxinv) | (Y & Z_muxinv); | ||||||
|  | 
 | ||||||
|  |     wire [47:0] xor_xyz_muxed = ALUMODEr[3] ? maj_xyz : xor_xyz; | ||||||
|  |     wire [47:0] maj_xyz_gated = ALUMODEr[2] ? 48'b0 :  maj_xyz; | ||||||
|  | 
 | ||||||
|  |     wire [48:0] maj_xyz_simd_gated; | ||||||
|  |     wire [3:0] int_carry_in, int_carry_out, ext_carry_out; | ||||||
|  |     wire [47:0] alu_sum; | ||||||
|  |     assign int_carry_in[0] = 1'b0; | ||||||
|  |     wire [3:0] carryout_reset; | ||||||
|  | 
 | ||||||
|  |     generate | ||||||
|  |         if (USE_SIMD == "FOUR12") begin | ||||||
|  |             assign maj_xyz_simd_gated = { | ||||||
|  |                     maj_xyz_gated[47:36], | ||||||
|  |                     1'b0, maj_xyz_gated[34:24], | ||||||
|  |                     1'b0, maj_xyz_gated[22:12], | ||||||
|  |                     1'b0, maj_xyz_gated[10:0], | ||||||
|  |                     alu_cin | ||||||
|  |                 }; | ||||||
|  |             assign int_carry_in[3:1] = 3'b000; | ||||||
|  |             assign ext_carry_out = { | ||||||
|  |                     int_carry_out[3], | ||||||
|  |                     maj_xyz_gated[35] ^ int_carry_out[2], | ||||||
|  |                     maj_xyz_gated[23] ^ int_carry_out[1], | ||||||
|  |                     maj_xyz_gated[11] ^ int_carry_out[0] | ||||||
|  |                 }; | ||||||
|  |             assign carryout_reset = 4'b0000; | ||||||
|  |         end else if (USE_SIMD == "TWO24") begin | ||||||
|  |             assign maj_xyz_simd_gated = { | ||||||
|  |                     maj_xyz_gated[47:24], | ||||||
|  |                     1'b0, maj_xyz_gated[22:0], | ||||||
|  |                     alu_cin | ||||||
|  |                 }; | ||||||
|  |             assign int_carry_in[3:1] = {int_carry_out[2], 1'b0, int_carry_out[0]}; | ||||||
|  |             assign ext_carry_out = { | ||||||
|  |                     int_carry_out[3], | ||||||
|  |                     1'bx, | ||||||
|  |                     maj_xyz_gated[23] ^ int_carry_out[1], | ||||||
|  |                     1'bx | ||||||
|  |                 }; | ||||||
|  |             assign carryout_reset = 4'b0x0x; | ||||||
|  |         end else begin | ||||||
|  |             assign maj_xyz_simd_gated = {maj_xyz_gated, alu_cin}; | ||||||
|  |             assign int_carry_in[3:1] = int_carry_out[2:0]; | ||||||
|  |             assign ext_carry_out = { | ||||||
|  |                     int_carry_out[3], | ||||||
|  |                     3'bxxx | ||||||
|  |                 }; | ||||||
|  |             assign carryout_reset = 4'b0xxx; | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         genvar i; | ||||||
|  |         for (i = 0; i < 4; i = i + 1) | ||||||
|  |             assign {int_carry_out[i], alu_sum[i*12 +: 12]} = {1'b0, maj_xyz_simd_gated[i*12 +: ((i == 3) ? 13 : 12)]} | ||||||
|  |                                                               + xor_xyz_muxed[i*12 +: 12] + int_carry_in[i]; | ||||||
|  |     endgenerate | ||||||
|  | 
 | ||||||
|  |     wire signed [47:0] Pd = ALUMODEr[1] ? ~alu_sum : alu_sum; | ||||||
|  |     wire [3:0] CARRYOUTd = (OPMODEr[3:0] == 4'b0101 || ALUMODEr[3:2] != 2'b00) ? 4'bxxxx : | ||||||
|  |                            ((ALUMODEr[0] & ALUMODEr[1]) ? ~ext_carry_out : ext_carry_out); | ||||||
|  |     wire CARRYCASCOUTd = ext_carry_out[3]; | ||||||
|  |     wire MULTSIGNOUTd = Mrx[42]; | ||||||
|  | 
 | ||||||
|  |     generate | ||||||
|  |         if (PREG == 1) begin | ||||||
|  |             initial P = 48'b0; | ||||||
|  |             initial CARRYOUT = carryout_reset; | ||||||
|  |             initial CARRYCASCOUT = 1'b0; | ||||||
|  |             initial MULTSIGNOUT = 1'b0; | ||||||
|  |             always @(posedge CLK) | ||||||
|  |                 if (RSTP) begin | ||||||
|  |                     P <= 48'b0; | ||||||
|  |                     CARRYOUT <= carryout_reset; | ||||||
|  |                     CARRYCASCOUT <= 1'b0; | ||||||
|  |                     MULTSIGNOUT <= 1'b0; | ||||||
|  |                 end else if (CEP) begin | ||||||
|  |                     P <= Pd; | ||||||
|  |                     CARRYOUT <= CARRYOUTd; | ||||||
|  |                     CARRYCASCOUT <= CARRYCASCOUTd; | ||||||
|  |                     MULTSIGNOUT <= MULTSIGNOUTd; | ||||||
|  |                 end | ||||||
|  |         end else begin | ||||||
|  |             always @* begin | ||||||
|  |                 P = Pd; | ||||||
|  |                 CARRYOUT = CARRYOUTd; | ||||||
|  |                 CARRYCASCOUT = CARRYCASCOUTd; | ||||||
|  |                 MULTSIGNOUT = MULTSIGNOUTd; | ||||||
|  |             end | ||||||
|  |         end | ||||||
|  |     endgenerate | ||||||
|  | 
 | ||||||
|  |     assign PCOUT = P; | ||||||
|  | 
 | ||||||
|  |     generate | ||||||
|  |         wire PATTERNDETECTd, PATTERNBDETECTd; | ||||||
|  | 
 | ||||||
|  |         if (USE_PATTERN_DETECT == "PATDET") begin | ||||||
|  |             // TODO: Support SEL_PATTERN != "PATTERN" and SEL_MASK != "MASK | ||||||
|  |             assign PATTERNDETECTd = &(~(Pd ^ PATTERN) | MASK); | ||||||
|  |             assign PATTERNBDETECTd = &((Pd ^ PATTERN) | MASK); | ||||||
|  |         end else begin | ||||||
|  |             assign PATTERNDETECTd = 1'b1; | ||||||
|  |             assign PATTERNBDETECTd = 1'b1; | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         if (PREG == 1) begin | ||||||
|  |             reg PATTERNDETECTPAST, PATTERNBDETECTPAST; | ||||||
|  |             initial PATTERNDETECT = 1'b0; | ||||||
|  |             initial PATTERNBDETECT = 1'b0; | ||||||
|  |             initial PATTERNDETECTPAST = 1'b0; | ||||||
|  |             initial PATTERNBDETECTPAST = 1'b0; | ||||||
|  |             always @(posedge CLK) | ||||||
|  |                 if (RSTP) begin | ||||||
|  |                     PATTERNDETECT <= 1'b0; | ||||||
|  |                     PATTERNBDETECT <= 1'b0; | ||||||
|  |                     PATTERNDETECTPAST <= 1'b0; | ||||||
|  |                     PATTERNBDETECTPAST <= 1'b0; | ||||||
|  |                 end else if (CEP) begin | ||||||
|  |                     PATTERNDETECT <= PATTERNDETECTd; | ||||||
|  |                     PATTERNBDETECT <= PATTERNBDETECTd; | ||||||
|  |                     PATTERNDETECTPAST <= PATTERNDETECT; | ||||||
|  |                     PATTERNBDETECTPAST <= PATTERNBDETECT; | ||||||
|  |                 end | ||||||
|  |             assign OVERFLOW = &{PATTERNDETECTPAST, ~PATTERNBDETECT, ~PATTERNDETECT}; | ||||||
|  |             assign UNDERFLOW = &{PATTERNBDETECTPAST, ~PATTERNBDETECT, ~PATTERNDETECT}; | ||||||
|  |         end else begin | ||||||
|  |             always @* begin | ||||||
|  |                 PATTERNDETECT = PATTERNDETECTd; | ||||||
|  |                 PATTERNBDETECT = PATTERNBDETECTd; | ||||||
|  |             end | ||||||
|  |             assign OVERFLOW = 1'bx, UNDERFLOW = 1'bx; | ||||||
|  |         end | ||||||
|  |     endgenerate | ||||||
|  | 
 | ||||||
|  | endmodule | ||||||
|  |  | ||||||
							
								
								
									
										49
									
								
								techlibs/xilinx/dsp_map.v
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								techlibs/xilinx/dsp_map.v
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,49 @@ | ||||||
|  | module \$__MUL25X18 (input [24:0] A, input [17:0] B, output [42:0] Y); | ||||||
|  | 	parameter A_SIGNED = 0; | ||||||
|  | 	parameter B_SIGNED = 0; | ||||||
|  | 	parameter A_WIDTH = 0; | ||||||
|  | 	parameter B_WIDTH = 0; | ||||||
|  | 	parameter Y_WIDTH = 0; | ||||||
|  | 
 | ||||||
|  | 	wire [47:0] P_48; | ||||||
|  | 	DSP48E1 #( | ||||||
|  | 		// Disable all registers | ||||||
|  | 		.ACASCREG(0), | ||||||
|  | 		.ADREG(0), | ||||||
|  | 		.A_INPUT("DIRECT"), | ||||||
|  | 		.ALUMODEREG(0), | ||||||
|  | 		.AREG(0), | ||||||
|  | 		.BCASCREG(0), | ||||||
|  | 		.B_INPUT("DIRECT"), | ||||||
|  | 		.BREG(0), | ||||||
|  | 		.CARRYINREG(0), | ||||||
|  | 		.CARRYINSELREG(0), | ||||||
|  | 		.CREG(0), | ||||||
|  | 		.DREG(0), | ||||||
|  | 		.INMODEREG(0), | ||||||
|  | 		.MREG(0), | ||||||
|  | 		.OPMODEREG(0), | ||||||
|  | 		.PREG(0), | ||||||
|  | 		.USE_MULT("MULTIPLY"), | ||||||
|  | 		.USE_SIMD("ONE48"), | ||||||
|  | 		.USE_DPORT("FALSE") | ||||||
|  | 	) _TECHMAP_REPLACE_ ( | ||||||
|  | 		//Data path | ||||||
|  | 		.A({{5{A[24]}}, A}), | ||||||
|  | 		.B(B), | ||||||
|  | 		.C(48'b0), | ||||||
|  | 		.D(25'b0), | ||||||
|  | 		.P(P_48), | ||||||
|  | 
 | ||||||
|  | 		.INMODE(5'b00000), | ||||||
|  | 		.ALUMODE(4'b0000), | ||||||
|  | 		.OPMODE(7'b000101), | ||||||
|  | 		.CARRYINSEL(3'b000), | ||||||
|  | 
 | ||||||
|  | 		.ACIN(30'b0), | ||||||
|  | 		.BCIN(18'b0), | ||||||
|  | 		.PCIN(48'b0), | ||||||
|  | 		.CARRYIN(1'b0) | ||||||
|  | 	); | ||||||
|  | 	assign Y = P_48; | ||||||
|  | endmodule | ||||||
|  | @ -81,6 +81,9 @@ struct SynthXilinxPass : public ScriptPass | ||||||
| 		log("    -nowidelut\n"); | 		log("    -nowidelut\n"); | ||||||
| 		log("        do not use MUXF[78] resources to implement LUTs larger than LUT6s\n"); | 		log("        do not use MUXF[78] resources to implement LUTs larger than LUT6s\n"); | ||||||
| 		log("\n"); | 		log("\n"); | ||||||
|  | 		log("    -nodsp\n"); | ||||||
|  | 		log("        do not use DSP48E1s to implement multipliers and associated logic\n"); | ||||||
|  | 		log("\n"); | ||||||
| 		log("    -iopad\n"); | 		log("    -iopad\n"); | ||||||
| 		log("        enable I/O buffer insertion (selected automatically by -ise)\n"); | 		log("        enable I/O buffer insertion (selected automatically by -ise)\n"); | ||||||
| 		log("\n"); | 		log("\n"); | ||||||
|  | @ -116,7 +119,7 @@ struct SynthXilinxPass : public ScriptPass | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	std::string top_opt, edif_file, blif_file, family; | 	std::string top_opt, edif_file, blif_file, family; | ||||||
| 	bool flatten, retime, vpr, ise, iopad, noiopad, noclkbuf, nobram, nolutram, nosrl, nocarry, nowidelut, abc9; | 	bool flatten, retime, vpr, ise, iopad, noiopad, noclkbuf, nobram, nolutram, nosrl, nocarry, nowidelut, nodsp, abc9; | ||||||
| 	bool flatten_before_abc; | 	bool flatten_before_abc; | ||||||
| 	int widemux; | 	int widemux; | ||||||
| 
 | 
 | ||||||
|  | @ -139,6 +142,7 @@ struct SynthXilinxPass : public ScriptPass | ||||||
| 		nosrl = false; | 		nosrl = false; | ||||||
| 		nocarry = false; | 		nocarry = false; | ||||||
| 		nowidelut = false; | 		nowidelut = false; | ||||||
|  | 		nodsp = false; | ||||||
| 		abc9 = false; | 		abc9 = false; | ||||||
| 		flatten_before_abc = false; | 		flatten_before_abc = false; | ||||||
| 		widemux = 0; | 		widemux = 0; | ||||||
|  | @ -240,6 +244,10 @@ struct SynthXilinxPass : public ScriptPass | ||||||
| 				abc9 = true; | 				abc9 = true; | ||||||
| 				continue; | 				continue; | ||||||
| 			} | 			} | ||||||
|  | 			if (args[argidx] == "-nodsp") { | ||||||
|  | 				nodsp = true; | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
| 			break; | 			break; | ||||||
| 		} | 		} | ||||||
| 		extra_args(args, argidx, design); | 		extra_args(args, argidx, design); | ||||||
|  | @ -302,10 +310,10 @@ struct SynthXilinxPass : public ScriptPass | ||||||
| 			run(stringf("hierarchy -check %s", top_opt.c_str())); | 			run(stringf("hierarchy -check %s", top_opt.c_str())); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if (check_label("coarse")) { | 		if (check_label("prepare")) { | ||||||
| 			run("proc"); | 			run("proc"); | ||||||
| 			if (help_mode || flatten) | 			if (flatten || help_mode) | ||||||
| 				run("flatten", "(if -flatten)"); | 				run("flatten", "(with '-flatten')"); | ||||||
| 			run("opt_expr"); | 			run("opt_expr"); | ||||||
| 			run("opt_clean"); | 			run("opt_clean"); | ||||||
| 			run("check"); | 			run("check"); | ||||||
|  | @ -329,6 +337,26 @@ struct SynthXilinxPass : public ScriptPass | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			run("techmap -map +/cmp2lut.v -D LUT_WIDTH=6"); | 			run("techmap -map +/cmp2lut.v -D LUT_WIDTH=6"); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (check_label("map_dsp"), "(skip if '-nodsp')") { | ||||||
|  | 			if (!nodsp || help_mode) { | ||||||
|  | 				// NB: Xilinx multipliers are signed only
 | ||||||
|  | 				run("techmap -map +/mul2dsp.v -map +/xilinx/dsp_map.v -D DSP_A_MAXWIDTH=25 -D DSP_A_MAXWIDTH_PARTIAL=18 -D DSP_B_MAXWIDTH=18 " | ||||||
|  | 						"-D DSP_A_MINWIDTH=2 -D DSP_B_MINWIDTH=2 " // Blocks Nx1 multipliers
 | ||||||
|  | 						"-D DSP_Y_MINWIDTH=9 " // UG901 suggests small multiplies are those 4x4 and smaller
 | ||||||
|  | 						"-D DSP_SIGNEDONLY=1 -D DSP_NAME=$__MUL25X18"); | ||||||
|  | 				run("select a:mul2dsp"); | ||||||
|  | 				run("setattr -unset mul2dsp"); | ||||||
|  | 				run("opt_expr -fine"); | ||||||
|  | 				run("wreduce"); | ||||||
|  | 				run("select -clear"); | ||||||
|  | 				run("xilinx_dsp"); | ||||||
|  | 				run("chtype -set $mul t:$__soft_mul"); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (check_label("coarse")) { | ||||||
| 			run("alumacc"); | 			run("alumacc"); | ||||||
| 			run("share"); | 			run("share"); | ||||||
| 			run("opt"); | 			run("opt"); | ||||||
|  |  | ||||||
							
								
								
									
										5
									
								
								techlibs/xilinx/tests/.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								techlibs/xilinx/tests/.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -4,3 +4,8 @@ bram1_[0-9]*/ | ||||||
| bram2.log | bram2.log | ||||||
| bram2_syn.v | bram2_syn.v | ||||||
| bram2_tb | bram2_tb | ||||||
|  | dsp_work*/ | ||||||
|  | test_dsp_model_ref.v | ||||||
|  | test_dsp_model_uut.v | ||||||
|  | test_dsp_model | ||||||
|  | *.vcd | ||||||
|  |  | ||||||
							
								
								
									
										14
									
								
								techlibs/xilinx/tests/test_dsp_model.sh
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								techlibs/xilinx/tests/test_dsp_model.sh
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | ||||||
|  | #!/bin/bash | ||||||
|  | set -ex | ||||||
|  | sed 's/DSP48E1/DSP48E1_UUT/; /DSP48E1_UUT/,/endmodule/ p; d;' < ../cells_sim.v > test_dsp_model_uut.v | ||||||
|  | if [ ! -f "test_dsp_model_ref.v" ]; then | ||||||
|  | 	cat /opt/Xilinx/Vivado/2019.1/data/verilog/src/unisims/DSP48E1.v > test_dsp_model_ref.v | ||||||
|  | fi | ||||||
|  | for tb in macc_overflow_underflow \ | ||||||
|  |     simd24_preadd_noreg_nocasc simd12_preadd_noreg_nocasc \ | ||||||
|  |     mult_allreg_nopreadd_nocasc mult_noreg_nopreadd_nocasc  \ | ||||||
|  | 	mult_allreg_preadd_nocasc mult_noreg_preadd_nocasc mult_inreg_preadd_nocasc | ||||||
|  | do | ||||||
|  | 	iverilog -s $tb -s glbl -o test_dsp_model test_dsp_model.v test_dsp_model_uut.v test_dsp_model_ref.v /opt/Xilinx/Vivado/2019.1/data/verilog/src/glbl.v | ||||||
|  | 	vvp -N ./test_dsp_model | ||||||
|  | done | ||||||
							
								
								
									
										652
									
								
								techlibs/xilinx/tests/test_dsp_model.v
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										652
									
								
								techlibs/xilinx/tests/test_dsp_model.v
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,652 @@ | ||||||
|  | `timescale 1ns / 1ps | ||||||
|  | 
 | ||||||
|  | module testbench; | ||||||
|  |     parameter integer ACASCREG = 1; | ||||||
|  |     parameter integer ADREG = 1; | ||||||
|  |     parameter integer ALUMODEREG = 1; | ||||||
|  |     parameter integer AREG = 1; | ||||||
|  |     parameter AUTORESET_PATDET = "NO_RESET"; | ||||||
|  |     parameter A_INPUT = "DIRECT"; | ||||||
|  |     parameter integer BCASCREG = 1; | ||||||
|  |     parameter integer BREG = 1; | ||||||
|  |     parameter B_INPUT = "DIRECT"; | ||||||
|  |     parameter integer CARRYINREG = 1; | ||||||
|  |     parameter integer CARRYINSELREG = 1; | ||||||
|  |     parameter integer CREG = 1; | ||||||
|  |     parameter integer DREG = 1; | ||||||
|  |     parameter integer INMODEREG = 1; | ||||||
|  |     parameter integer MREG = 1; | ||||||
|  |     parameter integer OPMODEREG = 1; | ||||||
|  |     parameter integer PREG = 1; | ||||||
|  |     parameter SEL_MASK = "MASK"; | ||||||
|  |     parameter SEL_PATTERN = "PATTERN"; | ||||||
|  |     parameter USE_DPORT = "FALSE"; | ||||||
|  |     parameter USE_MULT = "MULTIPLY"; | ||||||
|  |     parameter USE_PATTERN_DETECT = "NO_PATDET"; | ||||||
|  |     parameter USE_SIMD = "ONE48"; | ||||||
|  |     parameter [47:0] MASK = 48'h3FFFFFFFFFFF; | ||||||
|  |     parameter [47:0] PATTERN = 48'h000000000000; | ||||||
|  |     parameter [3:0] IS_ALUMODE_INVERTED = 4'b0; | ||||||
|  |     parameter [0:0] IS_CARRYIN_INVERTED = 1'b0; | ||||||
|  |     parameter [0:0] IS_CLK_INVERTED = 1'b0; | ||||||
|  |     parameter [4:0] IS_INMODE_INVERTED = 5'b0; | ||||||
|  |     parameter [6:0] IS_OPMODE_INVERTED = 7'b0; | ||||||
|  | 
 | ||||||
|  | 	reg CLK; | ||||||
|  | 	reg CEA1, CEA2, CEAD, CEALUMODE, CEB1, CEB2, CEC, CECARRYIN, CECTRL; | ||||||
|  | 	reg CED, CEINMODE, CEM, CEP; | ||||||
|  | 	reg RSTA, RSTALLCARRYIN, RSTALUMODE, RSTB, RSTC, RSTCTRL, RSTD, RSTINMODE, RSTM, RSTP; | ||||||
|  | 	reg [29:0] A, ACIN; | ||||||
|  | 	reg [17:0] B, BCIN; | ||||||
|  | 	reg [47:0] C; | ||||||
|  | 	reg [24:0] D; | ||||||
|  | 	reg [47:0] PCIN; | ||||||
|  | 	reg [3:0] ALUMODE; | ||||||
|  | 	reg [2:0] CARRYINSEL; | ||||||
|  | 	reg [4:0] INMODE; | ||||||
|  | 	reg [6:0] OPMODE; | ||||||
|  | 	reg CARRYCASCIN, CARRYIN, MULTSIGNIN; | ||||||
|  | 
 | ||||||
|  |     output [29:0] ACOUT, REF_ACOUT; | ||||||
|  |     output [17:0] BCOUT, REF_BCOUT; | ||||||
|  |     output CARRYCASCOUT, REF_CARRYCASCOUT; | ||||||
|  |     output [3:0] CARRYOUT, REF_CARRYOUT; | ||||||
|  |     output MULTSIGNOUT, REF_MULTSIGNOUT; | ||||||
|  |     output OVERFLOW, REF_OVERFLOW; | ||||||
|  |     output [47:0] P, REF_P; | ||||||
|  |     output PATTERNBDETECT, REF_PATTERNBDETECT; | ||||||
|  |     output PATTERNDETECT, REF_PATTERNDETECT; | ||||||
|  |     output [47:0] PCOUT, REF_PCOUT; | ||||||
|  |     output UNDERFLOW, REF_UNDERFLOW; | ||||||
|  | 
 | ||||||
|  | 	integer errcount = 0; | ||||||
|  | 
 | ||||||
|  | 	reg ERROR_FLAG = 0; | ||||||
|  | 
 | ||||||
|  | 	task clkcycle; | ||||||
|  | 		begin | ||||||
|  | 			#5; | ||||||
|  | 			CLK = ~CLK; | ||||||
|  | 			#10; | ||||||
|  | 			CLK = ~CLK; | ||||||
|  | 			#2; | ||||||
|  | 			ERROR_FLAG = 0; | ||||||
|  | 			if (REF_P !== P) begin | ||||||
|  | 				$display("ERROR at %1t: REF_P=%b UUT_P=%b DIFF=%b", $time, REF_P, P, REF_P ^ P); | ||||||
|  | 				errcount = errcount + 1; | ||||||
|  | 				ERROR_FLAG = 1; | ||||||
|  | 			end | ||||||
|  | 			if (REF_CARRYOUT !== CARRYOUT) begin | ||||||
|  | 				$display("ERROR at %1t: REF_CARRYOUT=%b UUT_CARRYOUT=%b", $time, REF_CARRYOUT, CARRYOUT); | ||||||
|  | 				errcount = errcount + 1; | ||||||
|  | 				ERROR_FLAG = 1; | ||||||
|  | 			end | ||||||
|  | 			if (REF_PATTERNDETECT !== PATTERNDETECT) begin | ||||||
|  | 				$display("ERROR at %1t: REF_PATTERNDETECT=%b UUT_PATTERNDETECT=%b DIFF=%b REF_P=%b P=%b", $time, REF_PATTERNDETECT, PATTERNDETECT, REF_PATTERNDETECT ^ PATTERNDETECT, REF_P, P); | ||||||
|  | 				errcount = errcount + 1; | ||||||
|  | 				ERROR_FLAG = 1; | ||||||
|  | 			end | ||||||
|  | 			if (REF_PATTERNBDETECT !== PATTERNBDETECT) begin | ||||||
|  | 				$display("ERROR at %1t: REF_PATTERNBDETECT=%b UUT_PATTERNBDETECT=%b DIFF=%b", $time, REF_PATTERNBDETECT, PATTERNBDETECT, REF_PATTERNBDETECT ^ PATTERNBDETECT); | ||||||
|  | 				errcount = errcount + 1; | ||||||
|  | 				ERROR_FLAG = 1; | ||||||
|  | 			end | ||||||
|  | 			if (REF_OVERFLOW !== OVERFLOW) begin | ||||||
|  | 				$display("ERROR at %1t: REF_OVERFLOW=%b UUT_OVERFLOW=%b DIFF=%b", $time, REF_OVERFLOW, OVERFLOW, REF_OVERFLOW ^ OVERFLOW); | ||||||
|  | 				errcount = errcount + 1; | ||||||
|  | 				ERROR_FLAG = 1; | ||||||
|  | 			end | ||||||
|  | 			if (REF_UNDERFLOW !== UNDERFLOW) begin | ||||||
|  | 				$display("ERROR at %1t: REF_UNDERFLOW=%b UUT_UNDERFLOW=%b DIFF=%b", $time, REF_UNDERFLOW, UNDERFLOW, REF_UNDERFLOW ^ UNDERFLOW); | ||||||
|  | 				errcount = errcount + 1; | ||||||
|  | 				ERROR_FLAG = 1; | ||||||
|  | 			end | ||||||
|  | 			#3; | ||||||
|  | 		end | ||||||
|  | 	endtask | ||||||
|  | 
 | ||||||
|  | 	reg config_valid = 0; | ||||||
|  | 	task drc; | ||||||
|  | 		begin | ||||||
|  | 			config_valid = 1; | ||||||
|  | 			if (AREG != 2 && INMODE[0]) config_valid = 0; | ||||||
|  | 			if (BREG != 2 && INMODE[4]) config_valid = 0; | ||||||
|  | 
 | ||||||
|  | 			if (USE_SIMD != "ONE48" && OPMODE[3:0] == 4'b0101) config_valid = 0; | ||||||
|  | 
 | ||||||
|  | 			if (OPMODE[1:0] == 2'b10 && PREG != 1) config_valid = 0; | ||||||
|  | 			if ((OPMODE[3:2] == 2'b01) ^ (OPMODE[1:0] == 2'b01) == 1'b1) config_valid = 0; | ||||||
|  | 			if ((OPMODE[6:4] == 3'b010 || OPMODE[6:4] == 3'b110) && PREG != 1) config_valid = 0; | ||||||
|  | 			if ((OPMODE[6:4] == 3'b100) && (PREG != 1 || OPMODE[3:0] != 4'b1000 || ALUMODE[3:2] == 2'b01 || ALUMODE[3:2] == 2'b11)) config_valid = 0; | ||||||
|  | 			if ((CARRYINSEL == 3'b100 || CARRYINSEL == 3'b101 || CARRYINSEL == 3'b111) && (PREG != 1)) config_valid = 0; | ||||||
|  | 			if (OPMODE[6:4] == 3'b111) config_valid = 0; | ||||||
|  | 			if ((OPMODE[3:0] == 4'b0101) && CARRYINSEL == 3'b010) config_valid = 0; | ||||||
|  | 			if (CARRYINSEL == 3'b000 && OPMODE == 7'b1001000) config_valid = 0; | ||||||
|  | 
 | ||||||
|  | 			if ((ALUMODE[3:2] == 2'b01 || ALUMODE[3:2] == 2'b11) && OPMODE[3:2] != 2'b00 && OPMODE[3:2] != 2'b10) config_valid = 0; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 		end | ||||||
|  | 	endtask | ||||||
|  | 
 | ||||||
|  | 	initial begin | ||||||
|  | 		$dumpfile("test_dsp_model.vcd"); | ||||||
|  | 		$dumpvars(0, testbench); | ||||||
|  | 
 | ||||||
|  | 		#2; | ||||||
|  | 		CLK = 1'b0; | ||||||
|  | 		{CEA1, CEA2, CEAD, CEALUMODE, CEB1, CEB2, CEC, CECARRYIN, CECTRL} = 9'b111111111; | ||||||
|  | 		{CED, CEINMODE, CEM, CEP} = 4'b1111; | ||||||
|  | 
 | ||||||
|  | 		{A, B, C, D} = 0; | ||||||
|  | 		{ACIN, BCIN, PCIN} = 0; | ||||||
|  | 		{ALUMODE, CARRYINSEL, INMODE} = 0; | ||||||
|  | 		{OPMODE, CARRYCASCIN, CARRYIN, MULTSIGNIN} = 0; | ||||||
|  | 
 | ||||||
|  | 		{RSTA, RSTALLCARRYIN, RSTALUMODE, RSTB, RSTC, RSTCTRL, RSTD, RSTINMODE, RSTM, RSTP} = ~0; | ||||||
|  | 		repeat (10) begin | ||||||
|  | 			#10; | ||||||
|  | 			CLK = 1'b1; | ||||||
|  | 			#10; | ||||||
|  | 			CLK = 1'b0; | ||||||
|  | 			#10; | ||||||
|  | 			CLK = 1'b1; | ||||||
|  | 			#10; | ||||||
|  | 			CLK = 1'b0; | ||||||
|  | 		end | ||||||
|  | 		{RSTA, RSTALLCARRYIN, RSTALUMODE, RSTB, RSTC, RSTCTRL, RSTD, RSTINMODE, RSTM, RSTP} = 0; | ||||||
|  | 
 | ||||||
|  | 		repeat (10000) begin | ||||||
|  | 			clkcycle; | ||||||
|  | 			config_valid = 0; | ||||||
|  | 			while (!config_valid) begin | ||||||
|  | 				A = $urandom; | ||||||
|  | 				ACIN = $urandom; | ||||||
|  | 				B = $urandom; | ||||||
|  | 				BCIN = $urandom; | ||||||
|  | 				C = {$urandom, $urandom}; | ||||||
|  | 				D = $urandom; | ||||||
|  | 				PCIN = {$urandom, $urandom}; | ||||||
|  | 
 | ||||||
|  | 				{CEA1, CEA2, CEAD, CEALUMODE, CEB1, CEB2, CEC, CECARRYIN, CECTRL} = $urandom | $urandom | $urandom; | ||||||
|  | 				{CED, CEINMODE, CEM, CEP} = $urandom | $urandom | $urandom | $urandom; | ||||||
|  | 
 | ||||||
|  | 				// Otherwise we can accidentally create illegal configs | ||||||
|  | 				CEINMODE = CECTRL; | ||||||
|  | 				CEALUMODE = CECTRL; | ||||||
|  | 
 | ||||||
|  | 				{RSTA, RSTALLCARRYIN, RSTALUMODE, RSTB, RSTC, RSTCTRL, RSTD, RSTINMODE, RSTM, RSTP} = $urandom & $urandom & $urandom & $urandom & $urandom & $urandom; | ||||||
|  | 				{ALUMODE, INMODE} = $urandom; | ||||||
|  | 				CARRYINSEL = $urandom & $urandom & $urandom; | ||||||
|  | 				OPMODE = $urandom;  | ||||||
|  | 				if ($urandom & 1'b1) | ||||||
|  | 					OPMODE[3:0] = 4'b0101; // test multiply more than other modes | ||||||
|  | 				{CARRYCASCIN, CARRYIN, MULTSIGNIN} = $urandom; | ||||||
|  | 
 | ||||||
|  | 				// So few valid options in these modes, just force one valid option | ||||||
|  | 				if (CARRYINSEL == 3'b001) OPMODE = 7'b1010101; | ||||||
|  | 				if (CARRYINSEL == 3'b010) OPMODE = 7'b0001010; | ||||||
|  | 				if (CARRYINSEL == 3'b011) OPMODE = 7'b0011011; | ||||||
|  | 				if (CARRYINSEL == 3'b100) OPMODE = 7'b0110011; | ||||||
|  | 				if (CARRYINSEL == 3'b101) OPMODE = 7'b0011010; | ||||||
|  | 				if (CARRYINSEL == 3'b110) OPMODE = 7'b0010101; | ||||||
|  | 				if (CARRYINSEL == 3'b111) OPMODE = 7'b0100011; | ||||||
|  | 
 | ||||||
|  | 				drc; | ||||||
|  | 			end | ||||||
|  | 		end | ||||||
|  | 
 | ||||||
|  | 		if (errcount == 0) begin | ||||||
|  | 			$display("All tests passed."); | ||||||
|  | 			$finish; | ||||||
|  | 		end else begin | ||||||
|  | 			$display("Caught %1d errors.", errcount); | ||||||
|  | 			$stop; | ||||||
|  | 		end | ||||||
|  | 	end | ||||||
|  | 
 | ||||||
|  | 	DSP48E1 #( | ||||||
|  | 		.ACASCREG           (ACASCREG), | ||||||
|  | 		.ADREG              (ADREG), | ||||||
|  | 		.ALUMODEREG         (ALUMODEREG), | ||||||
|  | 		.AREG               (AREG), | ||||||
|  | 		.AUTORESET_PATDET   (AUTORESET_PATDET), | ||||||
|  | 		.A_INPUT            (A_INPUT), | ||||||
|  | 		.BCASCREG           (BCASCREG), | ||||||
|  | 		.BREG               (BREG), | ||||||
|  | 		.B_INPUT            (B_INPUT), | ||||||
|  | 		.CARRYINREG         (CARRYINREG), | ||||||
|  | 		.CARRYINSELREG      (CARRYINSELREG), | ||||||
|  | 		.CREG               (CREG), | ||||||
|  | 		.DREG               (DREG), | ||||||
|  | 		.INMODEREG          (INMODEREG), | ||||||
|  | 		.MREG               (MREG), | ||||||
|  | 		.OPMODEREG          (OPMODEREG), | ||||||
|  | 		.PREG               (PREG), | ||||||
|  | 		.SEL_MASK           (SEL_MASK), | ||||||
|  | 		.SEL_PATTERN        (SEL_PATTERN), | ||||||
|  | 		.USE_DPORT          (USE_DPORT), | ||||||
|  | 		.USE_MULT           (USE_MULT), | ||||||
|  | 		.USE_PATTERN_DETECT (USE_PATTERN_DETECT), | ||||||
|  | 		.USE_SIMD           (USE_SIMD), | ||||||
|  | 		.MASK               (MASK), | ||||||
|  | 		.PATTERN            (PATTERN), | ||||||
|  | 		.IS_ALUMODE_INVERTED(IS_ALUMODE_INVERTED), | ||||||
|  | 		.IS_CARRYIN_INVERTED(IS_CARRYIN_INVERTED), | ||||||
|  | 		.IS_CLK_INVERTED    (IS_CLK_INVERTED), | ||||||
|  | 		.IS_INMODE_INVERTED (IS_INMODE_INVERTED), | ||||||
|  | 		.IS_OPMODE_INVERTED (IS_OPMODE_INVERTED) | ||||||
|  | 	) ref ( | ||||||
|  | 		.ACOUT         (REF_ACOUT), | ||||||
|  | 		.BCOUT         (REF_BCOUT), | ||||||
|  | 		.CARRYCASCOUT  (REF_CARRYCASCOUT), | ||||||
|  | 		.CARRYOUT      (REF_CARRYOUT), | ||||||
|  | 		.MULTSIGNOUT   (REF_MULTSIGNOUT), | ||||||
|  | 		.OVERFLOW      (REF_OVERFLOW), | ||||||
|  | 		.P             (REF_P), | ||||||
|  | 		.PATTERNBDETECT(REF_PATTERNBDETECT), | ||||||
|  | 		.PATTERNDETECT (REF_PATTERNDETECT), | ||||||
|  | 		.PCOUT         (REF_PCOUT), | ||||||
|  | 		.UNDERFLOW     (REF_UNDERFLOW), | ||||||
|  | 		.A             (A), | ||||||
|  | 		.ACIN          (ACIN), | ||||||
|  | 		.ALUMODE       (ALUMODE), | ||||||
|  | 		.B             (B), | ||||||
|  | 		.BCIN          (BCIN), | ||||||
|  | 		.C             (C), | ||||||
|  | 		.CARRYCASCIN   (CARRYCASCIN), | ||||||
|  | 		.CARRYINSEL    (CARRYINSEL), | ||||||
|  | 		.CEA1          (CEA1), | ||||||
|  | 		.CEA2          (CEA2), | ||||||
|  | 		.CEAD          (CEAD), | ||||||
|  | 		.CEALUMODE     (CEALUMODE), | ||||||
|  | 		.CEB1          (CEB1), | ||||||
|  | 		.CEB2          (CEB2), | ||||||
|  | 		.CEC           (CEC), | ||||||
|  | 		.CECARRYIN     (CECARRYIN), | ||||||
|  | 		.CECTRL        (CECTRL), | ||||||
|  | 		.CED           (CED), | ||||||
|  | 		.CEINMODE      (CEINMODE), | ||||||
|  | 		.CEM           (CEM), | ||||||
|  | 		.CEP           (CEP), | ||||||
|  | 		.CLK           (CLK), | ||||||
|  | 		.D             (D), | ||||||
|  | 		.INMODE        (INMODE), | ||||||
|  | 		.MULTSIGNIN    (MULTSIGNIN), | ||||||
|  | 		.OPMODE        (OPMODE), | ||||||
|  | 		.PCIN          (PCIN), | ||||||
|  | 		.RSTA          (RSTA), | ||||||
|  | 		.RSTALLCARRYIN (RSTALLCARRYIN), | ||||||
|  | 		.RSTALUMODE    (RSTALUMODE), | ||||||
|  | 		.RSTB          (RSTB), | ||||||
|  | 		.RSTC          (RSTC), | ||||||
|  | 		.RSTCTRL       (RSTCTRL), | ||||||
|  | 		.RSTD          (RSTD), | ||||||
|  | 		.RSTINMODE     (RSTINMODE), | ||||||
|  | 		.RSTM          (RSTM), | ||||||
|  | 		.RSTP          (RSTP) | ||||||
|  | 	); | ||||||
|  | 
 | ||||||
|  | 	DSP48E1_UUT #( | ||||||
|  | 		.ACASCREG           (ACASCREG), | ||||||
|  | 		.ADREG              (ADREG), | ||||||
|  | 		.ALUMODEREG         (ALUMODEREG), | ||||||
|  | 		.AREG               (AREG), | ||||||
|  | 		.AUTORESET_PATDET   (AUTORESET_PATDET), | ||||||
|  | 		.A_INPUT            (A_INPUT), | ||||||
|  | 		.BCASCREG           (BCASCREG), | ||||||
|  | 		.BREG               (BREG), | ||||||
|  | 		.B_INPUT            (B_INPUT), | ||||||
|  | 		.CARRYINREG         (CARRYINREG), | ||||||
|  | 		.CARRYINSELREG      (CARRYINSELREG), | ||||||
|  | 		.CREG               (CREG), | ||||||
|  | 		.DREG               (DREG), | ||||||
|  | 		.INMODEREG          (INMODEREG), | ||||||
|  | 		.MREG               (MREG), | ||||||
|  | 		.OPMODEREG          (OPMODEREG), | ||||||
|  | 		.PREG               (PREG), | ||||||
|  | 		.SEL_MASK           (SEL_MASK), | ||||||
|  | 		.SEL_PATTERN        (SEL_PATTERN), | ||||||
|  | 		.USE_DPORT          (USE_DPORT), | ||||||
|  | 		.USE_MULT           (USE_MULT), | ||||||
|  | 		.USE_PATTERN_DETECT (USE_PATTERN_DETECT), | ||||||
|  | 		.USE_SIMD           (USE_SIMD), | ||||||
|  | 		.MASK               (MASK), | ||||||
|  | 		.PATTERN            (PATTERN), | ||||||
|  | 		.IS_ALUMODE_INVERTED(IS_ALUMODE_INVERTED), | ||||||
|  | 		.IS_CARRYIN_INVERTED(IS_CARRYIN_INVERTED), | ||||||
|  | 		.IS_CLK_INVERTED    (IS_CLK_INVERTED), | ||||||
|  | 		.IS_INMODE_INVERTED (IS_INMODE_INVERTED), | ||||||
|  | 		.IS_OPMODE_INVERTED (IS_OPMODE_INVERTED) | ||||||
|  | 	) uut ( | ||||||
|  | 		.ACOUT         (ACOUT), | ||||||
|  | 		.BCOUT         (BCOUT), | ||||||
|  | 		.CARRYCASCOUT  (CARRYCASCOUT), | ||||||
|  | 		.CARRYOUT      (CARRYOUT), | ||||||
|  | 		.MULTSIGNOUT   (MULTSIGNOUT), | ||||||
|  | 		.OVERFLOW      (OVERFLOW), | ||||||
|  | 		.P             (P), | ||||||
|  | 		.PATTERNBDETECT(PATTERNBDETECT), | ||||||
|  | 		.PATTERNDETECT (PATTERNDETECT), | ||||||
|  | 		.PCOUT         (PCOUT), | ||||||
|  | 		.UNDERFLOW     (UNDERFLOW), | ||||||
|  | 		.A             (A), | ||||||
|  | 		.ACIN          (ACIN), | ||||||
|  | 		.ALUMODE       (ALUMODE), | ||||||
|  | 		.B             (B), | ||||||
|  | 		.BCIN          (BCIN), | ||||||
|  | 		.C             (C), | ||||||
|  | 		.CARRYCASCIN   (CARRYCASCIN), | ||||||
|  | 		.CARRYINSEL    (CARRYINSEL), | ||||||
|  | 		.CEA1          (CEA1), | ||||||
|  | 		.CEA2          (CEA2), | ||||||
|  | 		.CEAD          (CEAD), | ||||||
|  | 		.CEALUMODE     (CEALUMODE), | ||||||
|  | 		.CEB1          (CEB1), | ||||||
|  | 		.CEB2          (CEB2), | ||||||
|  | 		.CEC           (CEC), | ||||||
|  | 		.CECARRYIN     (CECARRYIN), | ||||||
|  | 		.CECTRL        (CECTRL), | ||||||
|  | 		.CED           (CED), | ||||||
|  | 		.CEINMODE      (CEINMODE), | ||||||
|  | 		.CEM           (CEM), | ||||||
|  | 		.CEP           (CEP), | ||||||
|  | 		.CLK           (CLK), | ||||||
|  | 		.D             (D), | ||||||
|  | 		.INMODE        (INMODE), | ||||||
|  | 		.MULTSIGNIN    (MULTSIGNIN), | ||||||
|  | 		.OPMODE        (OPMODE), | ||||||
|  | 		.PCIN          (PCIN), | ||||||
|  | 		.RSTA          (RSTA), | ||||||
|  | 		.RSTALLCARRYIN (RSTALLCARRYIN), | ||||||
|  | 		.RSTALUMODE    (RSTALUMODE), | ||||||
|  | 		.RSTB          (RSTB), | ||||||
|  | 		.RSTC          (RSTC), | ||||||
|  | 		.RSTCTRL       (RSTCTRL), | ||||||
|  | 		.RSTD          (RSTD), | ||||||
|  | 		.RSTINMODE     (RSTINMODE), | ||||||
|  | 		.RSTM          (RSTM), | ||||||
|  | 		.RSTP          (RSTP) | ||||||
|  | 	); | ||||||
|  | endmodule | ||||||
|  | 
 | ||||||
|  | module mult_noreg_nopreadd_nocasc; | ||||||
|  | 	testbench #( | ||||||
|  | 		.ACASCREG           (0), | ||||||
|  | 		.ADREG              (0), | ||||||
|  | 		.ALUMODEREG         (0), | ||||||
|  | 		.AREG               (0), | ||||||
|  | 		.AUTORESET_PATDET   ("NO_RESET"), | ||||||
|  | 		.A_INPUT            ("DIRECT"), | ||||||
|  | 		.BCASCREG           (0), | ||||||
|  | 		.BREG               (0), | ||||||
|  | 		.B_INPUT            ("DIRECT"), | ||||||
|  | 		.CARRYINREG         (0), | ||||||
|  | 		.CARRYINSELREG      (0), | ||||||
|  | 		.CREG               (0), | ||||||
|  | 		.DREG               (0), | ||||||
|  | 		.INMODEREG          (0), | ||||||
|  | 		.MREG               (0), | ||||||
|  | 		.OPMODEREG          (0), | ||||||
|  | 		.PREG               (0), | ||||||
|  | 		.SEL_MASK           ("MASK"), | ||||||
|  | 		.SEL_PATTERN        ("PATTERN"), | ||||||
|  | 		.USE_DPORT          ("FALSE"), | ||||||
|  | 		.USE_MULT           ("DYNAMIC"), | ||||||
|  | 		.USE_PATTERN_DETECT ("NO_PATDET"), | ||||||
|  | 		.USE_SIMD           ("ONE48"), | ||||||
|  | 		.MASK               (48'h3FFFFFFFFFFF), | ||||||
|  | 		.PATTERN            (48'h000000000000), | ||||||
|  | 		.IS_ALUMODE_INVERTED(4'b0), | ||||||
|  | 		.IS_CARRYIN_INVERTED(1'b0), | ||||||
|  | 		.IS_CLK_INVERTED    (1'b0), | ||||||
|  | 		.IS_INMODE_INVERTED (5'b0), | ||||||
|  | 		.IS_OPMODE_INVERTED (7'b0) | ||||||
|  | 	) testbench (); | ||||||
|  | endmodule | ||||||
|  | 
 | ||||||
|  | module mult_allreg_nopreadd_nocasc; | ||||||
|  | 	testbench #( | ||||||
|  | 		.ACASCREG           (1), | ||||||
|  | 		.ADREG              (1), | ||||||
|  | 		.ALUMODEREG         (1), | ||||||
|  | 		.AREG               (2), | ||||||
|  | 		.AUTORESET_PATDET   ("NO_RESET"), | ||||||
|  | 		.A_INPUT            ("DIRECT"), | ||||||
|  | 		.BCASCREG           (1), | ||||||
|  | 		.BREG               (2), | ||||||
|  | 		.B_INPUT            ("DIRECT"), | ||||||
|  | 		.CARRYINREG         (1), | ||||||
|  | 		.CARRYINSELREG      (1), | ||||||
|  | 		.CREG               (1), | ||||||
|  | 		.DREG               (1), | ||||||
|  | 		.INMODEREG          (1), | ||||||
|  | 		.MREG               (1), | ||||||
|  | 		.OPMODEREG          (1), | ||||||
|  | 		.PREG               (1), | ||||||
|  | 		.SEL_MASK           ("MASK"), | ||||||
|  | 		.SEL_PATTERN        ("PATTERN"), | ||||||
|  | 		.USE_DPORT          ("FALSE"), | ||||||
|  | 		.USE_MULT           ("DYNAMIC"), | ||||||
|  | 		.USE_PATTERN_DETECT ("NO_PATDET"), | ||||||
|  | 		.USE_SIMD           ("ONE48"), | ||||||
|  | 		.MASK               (48'h3FFFFFFFFFFF), | ||||||
|  | 		.PATTERN            (48'h000000000000), | ||||||
|  | 		.IS_ALUMODE_INVERTED(4'b0), | ||||||
|  | 		.IS_CARRYIN_INVERTED(1'b0), | ||||||
|  | 		.IS_CLK_INVERTED    (1'b0), | ||||||
|  | 		.IS_INMODE_INVERTED (5'b0), | ||||||
|  | 		.IS_OPMODE_INVERTED (7'b0) | ||||||
|  | 	) testbench (); | ||||||
|  | endmodule | ||||||
|  | 
 | ||||||
|  | module mult_noreg_preadd_nocasc; | ||||||
|  | 	testbench #( | ||||||
|  | 		.ACASCREG           (0), | ||||||
|  | 		.ADREG              (0), | ||||||
|  | 		.ALUMODEREG         (0), | ||||||
|  | 		.AREG               (0), | ||||||
|  | 		.AUTORESET_PATDET   ("NO_RESET"), | ||||||
|  | 		.A_INPUT            ("DIRECT"), | ||||||
|  | 		.BCASCREG           (0), | ||||||
|  | 		.BREG               (0), | ||||||
|  | 		.B_INPUT            ("DIRECT"), | ||||||
|  | 		.CARRYINREG         (0), | ||||||
|  | 		.CARRYINSELREG      (0), | ||||||
|  | 		.CREG               (0), | ||||||
|  | 		.DREG               (0), | ||||||
|  | 		.INMODEREG          (0), | ||||||
|  | 		.MREG               (0), | ||||||
|  | 		.OPMODEREG          (0), | ||||||
|  | 		.PREG               (0), | ||||||
|  | 		.SEL_MASK           ("MASK"), | ||||||
|  | 		.SEL_PATTERN        ("PATTERN"), | ||||||
|  | 		.USE_DPORT          ("TRUE"), | ||||||
|  | 		.USE_MULT           ("DYNAMIC"), | ||||||
|  | 		.USE_PATTERN_DETECT ("NO_PATDET"), | ||||||
|  | 		.USE_SIMD           ("ONE48"), | ||||||
|  | 		.MASK               (48'h3FFFFFFFFFFF), | ||||||
|  | 		.PATTERN            (48'h000000000000), | ||||||
|  | 		.IS_ALUMODE_INVERTED(4'b0), | ||||||
|  | 		.IS_CARRYIN_INVERTED(1'b0), | ||||||
|  | 		.IS_CLK_INVERTED    (1'b0), | ||||||
|  | 		.IS_INMODE_INVERTED (5'b0), | ||||||
|  | 		.IS_OPMODE_INVERTED (7'b0) | ||||||
|  | 	) testbench (); | ||||||
|  | endmodule | ||||||
|  | 
 | ||||||
|  | module mult_allreg_preadd_nocasc; | ||||||
|  | 	testbench #( | ||||||
|  | 		.ACASCREG           (1), | ||||||
|  | 		.ADREG              (1), | ||||||
|  | 		.ALUMODEREG         (1), | ||||||
|  | 		.AREG               (2), | ||||||
|  | 		.AUTORESET_PATDET   ("NO_RESET"), | ||||||
|  | 		.A_INPUT            ("DIRECT"), | ||||||
|  | 		.BCASCREG           (1), | ||||||
|  | 		.BREG               (2), | ||||||
|  | 		.B_INPUT            ("DIRECT"), | ||||||
|  | 		.CARRYINREG         (1), | ||||||
|  | 		.CARRYINSELREG      (1), | ||||||
|  | 		.CREG               (1), | ||||||
|  | 		.DREG               (1), | ||||||
|  | 		.INMODEREG          (1), | ||||||
|  | 		.MREG               (1), | ||||||
|  | 		.OPMODEREG          (1), | ||||||
|  | 		.PREG               (1), | ||||||
|  | 		.SEL_MASK           ("MASK"), | ||||||
|  | 		.SEL_PATTERN        ("PATTERN"), | ||||||
|  | 		.USE_DPORT          ("TRUE"), | ||||||
|  | 		.USE_MULT           ("DYNAMIC"), | ||||||
|  | 		.USE_PATTERN_DETECT ("NO_PATDET"), | ||||||
|  | 		.USE_SIMD           ("ONE48"), | ||||||
|  | 		.MASK               (48'h3FFFFFFFFFFF), | ||||||
|  | 		.PATTERN            (48'h000000000000), | ||||||
|  | 		.IS_ALUMODE_INVERTED(4'b0), | ||||||
|  | 		.IS_CARRYIN_INVERTED(1'b0), | ||||||
|  | 		.IS_CLK_INVERTED    (1'b0), | ||||||
|  | 		.IS_INMODE_INVERTED (5'b0), | ||||||
|  | 		.IS_OPMODE_INVERTED (7'b0) | ||||||
|  | 	) testbench (); | ||||||
|  | endmodule | ||||||
|  | 
 | ||||||
|  | module mult_inreg_preadd_nocasc; | ||||||
|  | 	testbench #( | ||||||
|  | 		.ACASCREG           (1), | ||||||
|  | 		.ADREG              (0), | ||||||
|  | 		.ALUMODEREG         (0), | ||||||
|  | 		.AREG               (1), | ||||||
|  | 		.AUTORESET_PATDET   ("NO_RESET"), | ||||||
|  | 		.A_INPUT            ("DIRECT"), | ||||||
|  | 		.BCASCREG           (1), | ||||||
|  | 		.BREG               (1), | ||||||
|  | 		.B_INPUT            ("DIRECT"), | ||||||
|  | 		.CARRYINREG         (0), | ||||||
|  | 		.CARRYINSELREG      (0), | ||||||
|  | 		.CREG               (1), | ||||||
|  | 		.DREG               (1), | ||||||
|  | 		.INMODEREG          (0), | ||||||
|  | 		.MREG               (0), | ||||||
|  | 		.OPMODEREG          (0), | ||||||
|  | 		.PREG               (0), | ||||||
|  | 		.SEL_MASK           ("MASK"), | ||||||
|  | 		.SEL_PATTERN        ("PATTERN"), | ||||||
|  | 		.USE_DPORT          ("TRUE"), | ||||||
|  | 		.USE_MULT           ("DYNAMIC"), | ||||||
|  | 		.USE_PATTERN_DETECT ("NO_PATDET"), | ||||||
|  | 		.USE_SIMD           ("ONE48"), | ||||||
|  | 		.MASK               (48'h3FFFFFFFFFFF), | ||||||
|  | 		.PATTERN            (48'h000000000000), | ||||||
|  | 		.IS_ALUMODE_INVERTED(4'b0), | ||||||
|  | 		.IS_CARRYIN_INVERTED(1'b0), | ||||||
|  | 		.IS_CLK_INVERTED    (1'b0), | ||||||
|  | 		.IS_INMODE_INVERTED (5'b0), | ||||||
|  | 		.IS_OPMODE_INVERTED (7'b0) | ||||||
|  | 	) testbench (); | ||||||
|  | endmodule | ||||||
|  | 
 | ||||||
|  | module simd12_preadd_noreg_nocasc; | ||||||
|  | 	testbench #( | ||||||
|  | 		.ACASCREG           (0), | ||||||
|  | 		.ADREG              (0), | ||||||
|  | 		.ALUMODEREG         (0), | ||||||
|  | 		.AREG               (0), | ||||||
|  | 		.AUTORESET_PATDET   ("NO_RESET"), | ||||||
|  | 		.A_INPUT            ("DIRECT"), | ||||||
|  | 		.BCASCREG           (0), | ||||||
|  | 		.BREG               (0), | ||||||
|  | 		.B_INPUT            ("DIRECT"), | ||||||
|  | 		.CARRYINREG         (0), | ||||||
|  | 		.CARRYINSELREG      (0), | ||||||
|  | 		.CREG               (0), | ||||||
|  | 		.DREG               (0), | ||||||
|  | 		.INMODEREG          (0), | ||||||
|  | 		.MREG               (0), | ||||||
|  | 		.OPMODEREG          (0), | ||||||
|  | 		.PREG               (0), | ||||||
|  | 		.SEL_MASK           ("MASK"), | ||||||
|  | 		.SEL_PATTERN        ("PATTERN"), | ||||||
|  | 		.USE_DPORT          ("TRUE"), | ||||||
|  | 		.USE_MULT           ("DYNAMIC"), | ||||||
|  | 		.USE_PATTERN_DETECT ("NO_PATDET"), | ||||||
|  | 		.USE_SIMD           ("FOUR12"), | ||||||
|  | 		.MASK               (48'h3FFFFFFFFFFF), | ||||||
|  | 		.PATTERN            (48'h000000000000), | ||||||
|  | 		.IS_ALUMODE_INVERTED(4'b0), | ||||||
|  | 		.IS_CARRYIN_INVERTED(1'b0), | ||||||
|  | 		.IS_CLK_INVERTED    (1'b0), | ||||||
|  | 		.IS_INMODE_INVERTED (5'b0), | ||||||
|  | 		.IS_OPMODE_INVERTED (7'b0) | ||||||
|  | 	) testbench (); | ||||||
|  | endmodule | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | module simd24_preadd_noreg_nocasc; | ||||||
|  | 	testbench #( | ||||||
|  | 		.ACASCREG           (0), | ||||||
|  | 		.ADREG              (0), | ||||||
|  | 		.ALUMODEREG         (0), | ||||||
|  | 		.AREG               (0), | ||||||
|  | 		.AUTORESET_PATDET   ("NO_RESET"), | ||||||
|  | 		.A_INPUT            ("DIRECT"), | ||||||
|  | 		.BCASCREG           (0), | ||||||
|  | 		.BREG               (0), | ||||||
|  | 		.B_INPUT            ("DIRECT"), | ||||||
|  | 		.CARRYINREG         (0), | ||||||
|  | 		.CARRYINSELREG      (0), | ||||||
|  | 		.CREG               (0), | ||||||
|  | 		.DREG               (0), | ||||||
|  | 		.INMODEREG          (0), | ||||||
|  | 		.MREG               (0), | ||||||
|  | 		.OPMODEREG          (0), | ||||||
|  | 		.PREG               (0), | ||||||
|  | 		.SEL_MASK           ("MASK"), | ||||||
|  | 		.SEL_PATTERN        ("PATTERN"), | ||||||
|  | 		.USE_DPORT          ("TRUE"), | ||||||
|  | 		.USE_MULT           ("DYNAMIC"), | ||||||
|  | 		.USE_PATTERN_DETECT ("NO_PATDET"), | ||||||
|  | 		.USE_SIMD           ("TWO24"), | ||||||
|  | 		.MASK               (48'h3FFFFFFFFFFF), | ||||||
|  | 		.PATTERN            (48'h000000000000), | ||||||
|  | 		.IS_ALUMODE_INVERTED(4'b0), | ||||||
|  | 		.IS_CARRYIN_INVERTED(1'b0), | ||||||
|  | 		.IS_CLK_INVERTED    (1'b0), | ||||||
|  | 		.IS_INMODE_INVERTED (5'b0), | ||||||
|  | 		.IS_OPMODE_INVERTED (7'b0) | ||||||
|  | 	) testbench (); | ||||||
|  | endmodule | ||||||
|  | 
 | ||||||
|  | module macc_overflow_underflow; | ||||||
|  | 	testbench #( | ||||||
|  | 		.ACASCREG           (0), | ||||||
|  | 		.ADREG              (0), | ||||||
|  | 		.ALUMODEREG         (0), | ||||||
|  | 		.AREG               (0), | ||||||
|  | 		.AUTORESET_PATDET   ("NO_RESET"), | ||||||
|  | 		.A_INPUT            ("DIRECT"), | ||||||
|  | 		.BCASCREG           (0), | ||||||
|  | 		.BREG               (0), | ||||||
|  | 		.B_INPUT            ("DIRECT"), | ||||||
|  | 		.CARRYINREG         (0), | ||||||
|  | 		.CARRYINSELREG      (0), | ||||||
|  | 		.CREG               (0), | ||||||
|  | 		.DREG               (0), | ||||||
|  | 		.INMODEREG          (0), | ||||||
|  | 		.MREG               (0), | ||||||
|  | 		.OPMODEREG          (0), | ||||||
|  | 		.PREG               (1), | ||||||
|  | 		.SEL_MASK           ("MASK"), | ||||||
|  | 		.SEL_PATTERN        ("PATTERN"), | ||||||
|  | 		.USE_DPORT          ("FALSE"), | ||||||
|  | 		.USE_MULT           ("DYNAMIC"), | ||||||
|  | 		.USE_PATTERN_DETECT ("PATDET"), | ||||||
|  | 		.USE_SIMD           ("ONE48"), | ||||||
|  | 		.MASK               (48'h1FFFFFFFFFFF), | ||||||
|  | 		.PATTERN            (48'h000000000000), | ||||||
|  | 		.IS_ALUMODE_INVERTED(4'b0), | ||||||
|  | 		.IS_CARRYIN_INVERTED(1'b0), | ||||||
|  | 		.IS_CLK_INVERTED    (1'b0), | ||||||
|  | 		.IS_INMODE_INVERTED (5'b0), | ||||||
|  | 		.IS_OPMODE_INVERTED (7'b0) | ||||||
|  | 	) testbench (); | ||||||
|  | endmodule | ||||||
|  | @ -13,13 +13,35 @@ reg [(A_WIDTH + B_WIDTH - 1):0] reg_tmp_c; | ||||||
| assign c = reg_tmp_c; | assign c = reg_tmp_c; | ||||||
| always @(posedge clk) | always @(posedge clk) | ||||||
| begin | begin | ||||||
| if(set) |     if(set) | ||||||
| begin |     begin | ||||||
| reg_tmp_c <= 0; |         reg_tmp_c <= 0; | ||||||
| end |     end | ||||||
| else |     else | ||||||
| begin |     begin | ||||||
| reg_tmp_c <= a * b + c; |         reg_tmp_c <= a * b + c; | ||||||
| end |     end | ||||||
|  | end | ||||||
|  | endmodule | ||||||
|  | 
 | ||||||
|  | module top2(clk,a,b,c,hold); | ||||||
|  | parameter A_WIDTH = 6 /*4*/; | ||||||
|  | parameter B_WIDTH = 6 /*3*/; | ||||||
|  | input hold; | ||||||
|  | input clk; | ||||||
|  | input signed [(A_WIDTH - 1):0] a; | ||||||
|  | input signed [(B_WIDTH - 1):0] b; | ||||||
|  | output signed [(A_WIDTH + B_WIDTH - 1):0] c; | ||||||
|  | reg signed [A_WIDTH-1:0] reg_a; | ||||||
|  | reg signed [B_WIDTH-1:0] reg_b; | ||||||
|  | reg [(A_WIDTH + B_WIDTH - 1):0] reg_tmp_c; | ||||||
|  | assign c = reg_tmp_c; | ||||||
|  | always @(posedge clk) | ||||||
|  | begin | ||||||
|  |     if (!hold) begin | ||||||
|  |         reg_a <= a; | ||||||
|  |         reg_b <= b; | ||||||
|  |         reg_tmp_c <= reg_a * reg_b + c; | ||||||
|  |     end | ||||||
| end | end | ||||||
| endmodule | endmodule | ||||||
|  |  | ||||||
|  | @ -1,13 +1,25 @@ | ||||||
| read_verilog macc.v | read_verilog macc.v | ||||||
| proc | proc | ||||||
|  | design -save read | ||||||
|  | 
 | ||||||
| hierarchy -top top | hierarchy -top top | ||||||
| #equiv_opt -assert -map +/ice40/cells_sim.v synth_ice40 -dsp # equivalency check | equiv_opt -assert -multiclock -map +/ice40/cells_sim.v synth_ice40 -dsp # equivalency check | ||||||
| 
 |  | ||||||
| equiv_opt -run :prove -map +/ice40/cells_sim.v synth_ice40 -dsp |  | ||||||
| async2sync |  | ||||||
| equiv_opt -run prove: -assert null |  | ||||||
| 
 |  | ||||||
| design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) | design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) | ||||||
| cd top # Constrain all select calls below inside the top module | cd top # Constrain all select calls below inside the top module | ||||||
| select -assert-count 1 t:SB_MAC16 | select -assert-count 1 t:SB_MAC16 | ||||||
| select -assert-none t:SB_MAC16 %% t:* %D | select -assert-none t:SB_MAC16 %% t:* %D | ||||||
|  | 
 | ||||||
|  | design -load read | ||||||
|  | hierarchy -top top2 | ||||||
|  | 
 | ||||||
|  | #equiv_opt -multiclock -assert -map +/ice40/cells_sim.v synth_ice40 -dsp # equivalency check | ||||||
|  | 
 | ||||||
|  | equiv_opt -run :prove -multiclock -assert -map +/ice40/cells_sim.v synth_ice40 -dsp # equivalency check | ||||||
|  | clk2fflogic | ||||||
|  | miter -equiv -flatten -make_assert -make_outputs gold gate miter | ||||||
|  | sat -set-init-zero -seq 4 -verify -prove-asserts -show-ports miter | ||||||
|  | 
 | ||||||
|  | design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) | ||||||
|  | cd top2 # Constrain all select calls below inside the top module | ||||||
|  | select -assert-count 1 t:SB_MAC16 | ||||||
|  | select -assert-none t:SB_MAC16 %% t:* %D | ||||||
|  |  | ||||||
							
								
								
									
										1
									
								
								tests/xilinx/.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								tests/xilinx/.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -1,3 +1,4 @@ | ||||||
| /*.log | /*.log | ||||||
| /*.out | /*.out | ||||||
| /run-test.mk | /run-test.mk | ||||||
|  | /*_uut.v | ||||||
|  |  | ||||||
							
								
								
									
										25
									
								
								tests/xilinx/dsp_simd.ys
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								tests/xilinx/dsp_simd.ys
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,25 @@ | ||||||
|  | read_verilog <<EOT | ||||||
|  | module simd(input [12*4-1:0] a, input [12*4-1:0] b, (* use_dsp="simd" *) output [7*12-1:0] o12, (* use_dsp="simd" *) output [2*24-1:0] o24); | ||||||
|  | generate | ||||||
|  |     genvar i; | ||||||
|  |     // 4 x 12-bit adder | ||||||
|  |     for (i = 0; i < 4; i++) | ||||||
|  |         assign o12[i*12+:12] = a[i*12+:12] + b[i*12+:12]; | ||||||
|  |     // 2 x 24-bit subtract | ||||||
|  |     for (i = 0; i < 2; i++) | ||||||
|  |         assign o24[i*24+:24] = a[i*24+:24] - b[i*24+:24]; | ||||||
|  | endgenerate | ||||||
|  | reg [3*12-1:0] ro; | ||||||
|  | always @* begin | ||||||
|  |     ro[0*12+:12] = a[0*10+:10] + b[0*10+:10]; | ||||||
|  |     ro[1*12+:12] = a[1*10+:10] + b[1*10+:10]; | ||||||
|  |     ro[2*12+:12] = a[2*8+:8] + b[2*8+:8]; | ||||||
|  | end | ||||||
|  | assign o12[4*12+:3*12] = ro; | ||||||
|  | endmodule | ||||||
|  | EOT | ||||||
|  | 
 | ||||||
|  | proc | ||||||
|  | equiv_opt -assert -map +/xilinx/cells_sim.v synth_xilinx | ||||||
|  | design -load postopt | ||||||
|  | select -assert-count 3 t:DSP48E1 | ||||||
							
								
								
									
										3
									
								
								tests/xilinx/macc.sh
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								tests/xilinx/macc.sh
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | ||||||
|  | ../../yosys -qp "synth_xilinx -top macc2; rename -top macc2_uut" macc.v -o macc_uut.v | ||||||
|  | iverilog -o test_macc macc_tb.v macc_uut.v macc.v ../../techlibs/xilinx/cells_sim.v | ||||||
|  | vvp -N ./test_macc | ||||||
							
								
								
									
										84
									
								
								tests/xilinx/macc.v
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								tests/xilinx/macc.v
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,84 @@ | ||||||
|  | // Signed 40-bit streaming accumulator with 16-bit inputs | ||||||
|  | // File: HDL_Coding_Techniques/multipliers/multipliers4.v | ||||||
|  | // | ||||||
|  | // Source: | ||||||
|  | // https://www.xilinx.com/support/documentation/sw_manuals/xilinx2014_2/ug901-vivado-synthesis.pdf p.90 | ||||||
|  | // | ||||||
|  | module macc # (parameter SIZEIN = 16, SIZEOUT = 40) ( | ||||||
|  | 	input clk, ce, sload, | ||||||
|  | 	input signed [SIZEIN-1:0] a, b, | ||||||
|  | 	output signed [SIZEOUT-1:0] accum_out | ||||||
|  | ); | ||||||
|  | // Declare registers for intermediate values | ||||||
|  | reg signed [SIZEIN-1:0] a_reg, b_reg; | ||||||
|  | reg sload_reg; | ||||||
|  | reg signed [2*SIZEIN-1:0] mult_reg; | ||||||
|  | reg signed [SIZEOUT-1:0] adder_out, old_result; | ||||||
|  | always @* /*(adder_out or sload_reg)*/ begin // Modification necessary to fix sim/synth mismatch | ||||||
|  | 	if (sload_reg) | ||||||
|  | 		old_result <= 0; | ||||||
|  | 	else | ||||||
|  | 		// 'sload' is now active (=low) and opens the accumulation loop. | ||||||
|  | 		// The accumulator takes the next multiplier output in | ||||||
|  | 		// the same cycle. | ||||||
|  | 		old_result <= adder_out; | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | always @(posedge clk) | ||||||
|  | 	if (ce) | ||||||
|  | 	begin | ||||||
|  | 		a_reg <= a; | ||||||
|  | 		b_reg <= b; | ||||||
|  | 		mult_reg <= a_reg * b_reg; | ||||||
|  | 		sload_reg <= sload; | ||||||
|  | 		// Store accumulation result into a register | ||||||
|  | 		adder_out <= old_result + mult_reg; | ||||||
|  | 	end | ||||||
|  | 
 | ||||||
|  | // Output accumulation result | ||||||
|  | assign accum_out = adder_out; | ||||||
|  | 
 | ||||||
|  | endmodule | ||||||
|  | 
 | ||||||
|  | // Adapted variant of above | ||||||
|  | module macc2 # (parameter SIZEIN = 16, SIZEOUT = 40) ( | ||||||
|  | 	input clk, | ||||||
|  | 	input ce, | ||||||
|  | 	input rst, | ||||||
|  | 	input signed [SIZEIN-1:0] a, b, | ||||||
|  | 	output signed [SIZEOUT-1:0] accum_out, | ||||||
|  | 	output overflow | ||||||
|  | ); | ||||||
|  | // Declare registers for intermediate values | ||||||
|  | reg signed [SIZEIN-1:0] a_reg, b_reg, a_reg2, b_reg2; | ||||||
|  | reg signed [2*SIZEIN-1:0] mult_reg = 0; | ||||||
|  | reg signed [SIZEOUT:0] adder_out = 0; | ||||||
|  | reg overflow_reg; | ||||||
|  | always @(posedge clk) begin | ||||||
|  | 	//if (ce) | ||||||
|  | 	begin | ||||||
|  | 		a_reg <= a; | ||||||
|  | 		b_reg <= b; | ||||||
|  | 		a_reg2 <= a_reg; | ||||||
|  | 		b_reg2 <= b_reg; | ||||||
|  | 		mult_reg <= a_reg2 * b_reg2; | ||||||
|  | 		// Store accumulation result into a register | ||||||
|  | 		adder_out <= adder_out + mult_reg; | ||||||
|  | 		overflow_reg <= overflow; | ||||||
|  | 	end | ||||||
|  | 	if (rst) begin | ||||||
|  | 		a_reg <= 0; | ||||||
|  | 		a_reg2 <= 0; | ||||||
|  | 		b_reg <= 0; | ||||||
|  | 		b_reg2 <= 0; | ||||||
|  | 		mult_reg <= 0; | ||||||
|  | 		adder_out <= 0; | ||||||
|  | 		overflow_reg <= 1'b0; | ||||||
|  | 	end | ||||||
|  | end | ||||||
|  | assign overflow = (adder_out >= 2**(SIZEOUT-1)) | overflow_reg; | ||||||
|  | 
 | ||||||
|  | // Output accumulation result | ||||||
|  | assign accum_out = overflow ? 2**(SIZEOUT-1)-1 : adder_out; | ||||||
|  | 
 | ||||||
|  | endmodule | ||||||
							
								
								
									
										31
									
								
								tests/xilinx/macc.ys
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								tests/xilinx/macc.ys
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,31 @@ | ||||||
|  | read_verilog macc.v | ||||||
|  | design -save read | ||||||
|  | 
 | ||||||
|  | proc | ||||||
|  | hierarchy -top macc | ||||||
|  | #equiv_opt -assert -map +/xilinx/cells_sim.v synth_xilinx ### TODO | ||||||
|  | equiv_opt -run :prove -map +/xilinx/cells_sim.v synth_xilinx | ||||||
|  | miter -equiv -flatten -make_assert -make_outputs gold gate miter | ||||||
|  | sat -verify -prove-asserts -seq 10 -show-inputs -show-outputs miter | ||||||
|  | design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) | ||||||
|  | cd macc # Constrain all select calls below inside the top module | ||||||
|  | select -assert-count 1 t:BUFG | ||||||
|  | select -assert-count 1 t:FDRE | ||||||
|  | select -assert-count 1 t:DSP48E1 | ||||||
|  | select -assert-none t:BUFG t:FDRE t:DSP48E1 %% t:* %D | ||||||
|  | 
 | ||||||
|  | design -load read | ||||||
|  | proc | ||||||
|  | hierarchy -top macc2 | ||||||
|  | #equiv_opt -assert -map +/xilinx/cells_sim.v synth_xilinx ### TODO | ||||||
|  | equiv_opt -run :prove -map +/xilinx/cells_sim.v synth_xilinx | ||||||
|  | miter -equiv -flatten -make_assert -make_outputs gold gate miter | ||||||
|  | sat -verify -prove-asserts -seq 10 -show-inputs -show-outputs miter | ||||||
|  | design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) | ||||||
|  | cd macc2 # Constrain all select calls below inside the top module | ||||||
|  | select -assert-count 1 t:BUFG | ||||||
|  | select -assert-count 1 t:DSP48E1 | ||||||
|  | select -assert-count 1 t:FDRE | ||||||
|  | select -assert-count 1 t:LUT2 | ||||||
|  | select -assert-count 41 t:LUT3 | ||||||
|  | select -assert-none t:BUFG t:DSP48E1 t:FDRE t:LUT2 t:LUT3 %% t:* %D | ||||||
							
								
								
									
										96
									
								
								tests/xilinx/macc_tb.v
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								tests/xilinx/macc_tb.v
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,96 @@ | ||||||
|  | `timescale 1ns / 1ps | ||||||
|  | 
 | ||||||
|  | module testbench; | ||||||
|  | 
 | ||||||
|  |     parameter SIZEIN = 16, SIZEOUT = 40; | ||||||
|  | 	reg clk, ce, rst; | ||||||
|  |    	reg signed [SIZEIN-1:0] a, b; | ||||||
|  | 	output signed [SIZEOUT-1:0] REF_accum_out, accum_out; | ||||||
|  | 	output REF_overflow, overflow; | ||||||
|  | 
 | ||||||
|  | 	integer errcount = 0; | ||||||
|  | 
 | ||||||
|  | 	reg ERROR_FLAG = 0; | ||||||
|  | 
 | ||||||
|  | 	task clkcycle; | ||||||
|  | 		begin | ||||||
|  | 			#5; | ||||||
|  | 			clk = ~clk; | ||||||
|  | 			#10; | ||||||
|  | 			clk = ~clk; | ||||||
|  | 			#2; | ||||||
|  | 			ERROR_FLAG = 0; | ||||||
|  | 			if (REF_accum_out !== accum_out) begin | ||||||
|  | 				$display("ERROR at %1t: REF_accum_out=%b UUT_accum_out=%b DIFF=%b", $time, REF_accum_out, accum_out, REF_accum_out ^ accum_out); | ||||||
|  | 				errcount = errcount + 1; | ||||||
|  | 				ERROR_FLAG = 1; | ||||||
|  | 			end | ||||||
|  | 			if (REF_overflow !== overflow) begin | ||||||
|  | 				$display("ERROR at %1t: REF_overflow=%b UUT_overflow=%b DIFF=%b", $time, REF_overflow, overflow, REF_overflow ^ overflow); | ||||||
|  | 				errcount = errcount + 1; | ||||||
|  | 				ERROR_FLAG = 1; | ||||||
|  | 			end | ||||||
|  | 			#3; | ||||||
|  | 		end | ||||||
|  | 	endtask | ||||||
|  | 
 | ||||||
|  | 	initial begin | ||||||
|  | 		//$dumpfile("test_macc.vcd"); | ||||||
|  | 		//$dumpvars(0, testbench); | ||||||
|  | 
 | ||||||
|  | 		#2; | ||||||
|  | 		clk = 1'b0; | ||||||
|  |         ce = 1'b0; | ||||||
|  |         a = 0; | ||||||
|  |         b = 0; | ||||||
|  | 
 | ||||||
|  |         rst = 1'b1; | ||||||
|  | 		repeat (10) begin | ||||||
|  | 			#10; | ||||||
|  | 			clk = 1'b1; | ||||||
|  | 			#10; | ||||||
|  | 			clk = 1'b0; | ||||||
|  | 			#10; | ||||||
|  | 			clk = 1'b1; | ||||||
|  | 			#10; | ||||||
|  | 			clk = 1'b0; | ||||||
|  | 		end | ||||||
|  | 		rst = 1'b0; | ||||||
|  | 
 | ||||||
|  | 		repeat (10000) begin | ||||||
|  | 			clkcycle; | ||||||
|  |             ce = 1; //$urandom & $urandom; | ||||||
|  | 			//rst = $urandom & $urandom & $urandom & $urandom & $urandom & $urandom; | ||||||
|  | 			a = $urandom & ~(1 << (SIZEIN-1)); | ||||||
|  | 			b = $urandom & ~(1 << (SIZEIN-1)); | ||||||
|  | 		end | ||||||
|  | 
 | ||||||
|  | 		if (errcount == 0) begin | ||||||
|  | 			$display("All tests passed."); | ||||||
|  | 			$finish; | ||||||
|  | 		end else begin | ||||||
|  | 			$display("Caught %1d errors.", errcount); | ||||||
|  | 			$stop; | ||||||
|  | 		end | ||||||
|  | 	end | ||||||
|  | 
 | ||||||
|  | 	macc2 ref ( | ||||||
|  |         .clk(clk), | ||||||
|  |         .ce(ce), | ||||||
|  |         .rst(rst), | ||||||
|  |         .a(a), | ||||||
|  |         .b(b), | ||||||
|  |         .accum_out(REF_accum_out), | ||||||
|  |         .overflow(REF_overflow) | ||||||
|  | 	); | ||||||
|  | 
 | ||||||
|  | 	macc2_uut uut ( | ||||||
|  |         .clk(clk), | ||||||
|  |         .ce(ce), | ||||||
|  |         .rst(rst), | ||||||
|  |         .a(a), | ||||||
|  |         .b(b), | ||||||
|  |         .accum_out(accum_out), | ||||||
|  |         .overflow(overflow) | ||||||
|  | 	); | ||||||
|  | endmodule | ||||||
							
								
								
									
										30
									
								
								tests/xilinx/mul_unsigned.v
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								tests/xilinx/mul_unsigned.v
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,30 @@ | ||||||
|  | /* | ||||||
|  | Example from: https://www.xilinx.com/support/documentation/sw_manuals/xilinx2019_1/ug901-vivado-synthesis.pdf [p. 89]. | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | // Unsigned 16x24-bit Multiplier | ||||||
|  | // 1 latency stage on operands | ||||||
|  | // 3 latency stage after the multiplication | ||||||
|  | // File: multipliers2.v | ||||||
|  | // | ||||||
|  | module mul_unsigned (clk, A, B, RES); | ||||||
|  | parameter WIDTHA = /*16*/ 6; | ||||||
|  | parameter WIDTHB = /*24*/ 9; | ||||||
|  | input clk; | ||||||
|  | input [WIDTHA-1:0] A; | ||||||
|  | input [WIDTHB-1:0] B; | ||||||
|  | output [WIDTHA+WIDTHB-1:0] RES; | ||||||
|  | reg [WIDTHA-1:0] rA; | ||||||
|  | reg [WIDTHB-1:0] rB; | ||||||
|  | reg [WIDTHA+WIDTHB-1:0] M [3:0]; | ||||||
|  | integer i; | ||||||
|  | always @(posedge clk) | ||||||
|  |  begin | ||||||
|  |  rA <= A; | ||||||
|  |  rB <= B; | ||||||
|  |  M[0] <= rA * rB; | ||||||
|  |  for (i = 0; i < 3; i = i+1) | ||||||
|  |  M[i+1] <= M[i]; | ||||||
|  |  end | ||||||
|  | assign RES = M[3]; | ||||||
|  | endmodule | ||||||
							
								
								
									
										10
									
								
								tests/xilinx/mul_unsigned.ys
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								tests/xilinx/mul_unsigned.ys
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | ||||||
|  | read_verilog mul_unsigned.v | ||||||
|  | proc | ||||||
|  | hierarchy -top mul_unsigned | ||||||
|  | equiv_opt -assert -map +/xilinx/cells_sim.v synth_xilinx # equivalency check | ||||||
|  | design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) | ||||||
|  | cd mul_unsigned # Constrain all select calls below inside the top module | ||||||
|  | select -assert-count 1 t:BUFG | ||||||
|  | select -assert-count 1 t:DSP48E1 | ||||||
|  | select -assert-count 30 t:FDRE | ||||||
|  | select -assert-none t:DSP48E1 t:FDRE t:BUFG %% t:* %D | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue