mirror of
				https://github.com/YosysHQ/yosys
				synced 2025-10-31 11:42:30 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			995 lines
		
	
	
	
		
			31 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			995 lines
		
	
	
	
		
			31 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|  *  yosys -- Yosys Open SYnthesis Suite
 | |
|  *
 | |
|  *  Copyright (C) 2022  Jannis Harder <jix@yosyshq.com> <me@jix.one>
 | |
|  *
 | |
|  *  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/log_help.h"
 | |
| #include "kernel/sigtools.h"
 | |
| #include "kernel/ffinit.h"
 | |
| #include "kernel/ff.h"
 | |
| #include "kernel/modtools.h"
 | |
| #include "kernel/mem.h"
 | |
| 
 | |
| USING_YOSYS_NAMESPACE
 | |
| PRIVATE_NAMESPACE_BEGIN
 | |
| 
 | |
| 
 | |
| // Finds signal values with known constant or known unused values in the initial state
 | |
| struct InitValWorker
 | |
| {
 | |
| 	Module *module;
 | |
| 
 | |
| 	ModWalker modwalker;
 | |
| 	SigMap &sigmap;
 | |
| 	FfInitVals initvals;
 | |
| 
 | |
| 	dict<RTLIL::SigBit, RTLIL::State> initconst_bits;
 | |
| 	dict<RTLIL::SigBit, bool> used_bits;
 | |
| 
 | |
| 	InitValWorker(Module *module) : module(module), modwalker(module->design), sigmap(modwalker.sigmap)
 | |
| 	{
 | |
| 		modwalker.setup(module);
 | |
| 		initvals.set(&modwalker.sigmap, module);
 | |
| 
 | |
| 		for (auto wire : module->wires())
 | |
| 			if (wire->name.isPublic() || wire->get_bool_attribute(ID::keep))
 | |
| 				for (auto bit : SigSpec(wire))
 | |
| 					used_bits[sigmap(bit)] = true;
 | |
| 	}
 | |
| 
 | |
| 	// Sign/Zero-extended indexing of individual port bits
 | |
| 	static SigBit bit_in_port(RTLIL::Cell *cell, RTLIL::IdString port, RTLIL::IdString sign, int index)
 | |
| 	{
 | |
| 		auto sig_port = cell->getPort(port);
 | |
| 		if (index < GetSize(sig_port))
 | |
| 			return sig_port[index];
 | |
| 		else if (cell->getParam(sign).as_bool())
 | |
| 			return GetSize(sig_port) > 0 ? sig_port[GetSize(sig_port) - 1] : State::Sx;
 | |
| 		else
 | |
| 			return State::S0;
 | |
| 	}
 | |
| 
 | |
| 	// Has the signal a known constant value in the initial state?
 | |
| 	//
 | |
| 	// For sync-only FFs outputs, this is their initval. For logic loops,
 | |
| 	// multiple drivers or unknown cells this is Sx. For a small number of
 | |
| 	// handled cells we recurse through their inputs. All results are cached.
 | |
| 	RTLIL::State initconst(SigBit bit)
 | |
| 	{
 | |
| 		sigmap.apply(bit);
 | |
| 
 | |
| 		if (!bit.is_wire())
 | |
| 			return bit.data;
 | |
| 
 | |
| 		auto it = initconst_bits.find(bit);
 | |
| 		if (it != initconst_bits.end())
 | |
| 			return it->second;
 | |
| 
 | |
| 		// Setting this temporarily to x takes care of any logic loops
 | |
| 		initconst_bits[bit] = State::Sx;
 | |
| 
 | |
| 		pool<ModWalker::PortBit> portbits;
 | |
| 		modwalker.get_drivers(portbits, {bit});
 | |
| 
 | |
| 		if (portbits.size() != 1)
 | |
| 			return State::Sx;
 | |
| 
 | |
| 		ModWalker::PortBit portbit = *portbits.begin();
 | |
| 		RTLIL::Cell *cell = portbit.cell;
 | |
| 
 | |
| 		if (cell->is_builtin_ff())
 | |
| 		{
 | |
| 			FfData ff(&initvals, cell);
 | |
| 
 | |
| 			if (ff.has_aload || ff.has_sr || ff.has_arst || (!ff.has_clk && !ff.has_gclk)) {
 | |
| 				for (auto bit_q : ff.sig_q) {
 | |
| 					initconst_bits[sigmap(bit_q)] = State::Sx;
 | |
| 				}
 | |
| 				return State::Sx;
 | |
| 			}
 | |
| 
 | |
| 			for (int i = 0; i < ff.width; i++) {
 | |
| 				initconst_bits[sigmap(ff.sig_q[i])] = ff.val_init[i];
 | |
| 			}
 | |
| 
 | |
| 			return ff.val_init[portbit.offset];
 | |
| 		}
 | |
| 
 | |
| 		if (cell->type.in(ID($mux), ID($and), ID($or), ID($eq), ID($eqx), ID($initstate)))
 | |
| 		{
 | |
| 			if (cell->type == ID($mux))
 | |
| 			{
 | |
| 				SigBit sig_s = sigmap(cell->getPort(ID::S));
 | |
| 				State init_s = initconst(sig_s);
 | |
| 				State init_y;
 | |
| 
 | |
| 				if (init_s == State::S0) {
 | |
| 					init_y = initconst(cell->getPort(ID::A)[portbit.offset]);
 | |
| 				} else if (init_s == State::S1) {
 | |
| 					init_y = initconst(cell->getPort(ID::B)[portbit.offset]);
 | |
| 				} else {
 | |
| 					State init_a = initconst(cell->getPort(ID::A)[portbit.offset]);
 | |
| 					State init_b = initconst(cell->getPort(ID::B)[portbit.offset]);
 | |
| 					init_y = init_a == init_b ? init_a : State::Sx;
 | |
| 				}
 | |
| 				initconst_bits[bit] = init_y;
 | |
| 				return init_y;
 | |
| 			}
 | |
| 
 | |
| 			if (cell->type.in(ID($and), ID($or)))
 | |
| 			{
 | |
| 				State init_a = initconst(bit_in_port(cell, ID::A, ID::A_SIGNED, portbit.offset));
 | |
| 				State init_b = initconst(bit_in_port(cell, ID::B, ID::B_SIGNED, portbit.offset));
 | |
| 				State init_y;
 | |
| 				if (init_a == init_b)
 | |
| 					init_y = init_a;
 | |
| 				else if (cell->type == ID($and) && (init_a == State::S0 || init_b == State::S0))
 | |
| 					init_y = State::S0;
 | |
| 				else if (cell->type == ID($or) && (init_a == State::S1 || init_b == State::S1))
 | |
| 					init_y = State::S1;
 | |
| 				else
 | |
| 					init_y = State::Sx;
 | |
| 
 | |
| 				initconst_bits[bit] = init_y;
 | |
| 				return init_y;
 | |
| 			}
 | |
| 
 | |
| 			if (cell->type.in(ID($eq), ID($eqx))) // Treats $eqx as $eq
 | |
| 			{
 | |
| 				if (portbit.offset > 0) {
 | |
| 					initconst_bits[bit] = State::S0;
 | |
| 					return State::S0;
 | |
| 				}
 | |
| 
 | |
| 				SigSpec sig_a = cell->getPort(ID::A);
 | |
| 				SigSpec sig_b = cell->getPort(ID::B);
 | |
| 
 | |
| 				State init_y = State::S1;
 | |
| 
 | |
| 				for (int i = 0; init_y != State::S0 && i < GetSize(sig_a); i++) {
 | |
| 					State init_ai = initconst(bit_in_port(cell, ID::A, ID::A_SIGNED, i));
 | |
| 					if (init_ai == State::Sx) {
 | |
| 						init_y = State::Sx;
 | |
| 						continue;
 | |
| 					}
 | |
| 					State init_bi = initconst(bit_in_port(cell, ID::B, ID::B_SIGNED, i));
 | |
| 					if (init_bi == State::Sx)
 | |
| 						init_y = State::Sx;
 | |
| 					else if (init_ai != init_bi)
 | |
| 						init_y = State::S0;
 | |
| 				}
 | |
| 
 | |
| 				initconst_bits[bit] = init_y;
 | |
| 				return init_y;
 | |
| 			}
 | |
| 
 | |
| 			if (cell->type == ID($initstate))
 | |
| 			{
 | |
| 				initconst_bits[bit] = State::S1;
 | |
| 				return State::S1;
 | |
| 			}
 | |
| 
 | |
| 			log_assert(false);
 | |
| 		}
 | |
| 
 | |
| 		return State::Sx;
 | |
| 	}
 | |
| 
 | |
| 	RTLIL::Const initconst(SigSpec sig)
 | |
| 	{
 | |
| 		std::vector<RTLIL::State> bits;
 | |
| 		for (auto bit : sig)
 | |
| 			bits.push_back(initconst(bit));
 | |
| 		return bits;
 | |
| 	}
 | |
| 
 | |
| 	// Is the initial value of this signal used?
 | |
| 	//
 | |
| 	// An initial value of a signal is considered as used if it a) aliases a
 | |
| 	// wire with a public name, an output wire or with a keep attribute b)
 | |
| 	// combinationally drives such a wire or c) drive an input to an unknown
 | |
| 	// cell.
 | |
| 	//
 | |
| 	// This recurses into driven cells for a small number of known handled
 | |
| 	// celltypes. Results are cached and initconst is used to detect unused
 | |
| 	// inputs for the handled celltypes.
 | |
| 	bool is_initval_used(SigBit bit)
 | |
| 	{
 | |
| 		if (!bit.is_wire())
 | |
| 			return false;
 | |
| 
 | |
| 		auto it = used_bits.find(bit);
 | |
| 		if (it != used_bits.end())
 | |
| 			return it->second;
 | |
| 
 | |
| 		used_bits[bit] = true; // Temporarily set to guard against logic loops
 | |
| 
 | |
| 		pool<ModWalker::PortBit> portbits;
 | |
| 		modwalker.get_consumers(portbits, {bit});
 | |
| 
 | |
| 		for (auto portbit : portbits) {
 | |
| 			RTLIL::Cell *cell = portbit.cell;
 | |
| 			if (!cell->type.in(ID($mux), ID($and), ID($or), ID($mem_v2)) && !cell->is_builtin_ff()) {
 | |
| 				return true;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		for (auto portbit : portbits)
 | |
| 		{
 | |
| 			RTLIL::Cell *cell = portbit.cell;
 | |
| 			if (cell->is_builtin_ff())
 | |
| 			{
 | |
| 				FfData ff(&initvals, cell);
 | |
| 				if (ff.has_aload || ff.has_sr || ff.has_arst || ff.has_gclk || !ff.has_clk)
 | |
| 					return true;
 | |
| 				if (ff.has_ce && initconst(ff.sig_ce.as_bit()) == (ff.pol_ce ? State::S0 : State::S1))
 | |
| 					continue;
 | |
| 				if (ff.has_srst && initconst(ff.sig_srst.as_bit()) == (ff.pol_srst ? State::S1 : State::S0))
 | |
| 					continue;
 | |
| 
 | |
| 				return true;
 | |
| 			}
 | |
| 			else if (cell->type == ID($mux))
 | |
| 			{
 | |
| 				State init_s = initconst(cell->getPort(ID::S).as_bit());
 | |
| 				if (init_s == State::S0 && portbit.port == ID::B)
 | |
| 					continue;
 | |
| 				if (init_s == State::S1 && portbit.port == ID::A)
 | |
| 					continue;
 | |
| 				auto sig_y = cell->getPort(ID::Y);
 | |
| 
 | |
| 				if (is_initval_used(sig_y[portbit.offset]))
 | |
| 					return true;
 | |
| 			}
 | |
| 			else if (cell->type.in(ID($and), ID($or)))
 | |
| 			{
 | |
| 				auto sig_a = cell->getPort(ID::A);
 | |
| 				auto sig_b = cell->getPort(ID::B);
 | |
| 				auto sig_y = cell->getPort(ID::Y);
 | |
| 				if (GetSize(sig_y) != GetSize(sig_a) || GetSize(sig_y) != GetSize(sig_b))
 | |
| 					return true; // TODO handle more of this
 | |
| 				State absorbing = cell->type == ID($and) ? State::S0 : State::S1;
 | |
| 				if (portbit.port == ID::A && initconst(sig_b[portbit.offset]) == absorbing)
 | |
| 					continue;
 | |
| 				if (portbit.port == ID::B && initconst(sig_a[portbit.offset]) == absorbing)
 | |
| 					continue;
 | |
| 
 | |
| 				if (is_initval_used(sig_y[portbit.offset]))
 | |
| 					return true;
 | |
| 			}
 | |
| 			else if (cell->type == ID($mem_v2))
 | |
| 			{
 | |
| 				// TODO Use mem.h instead to uniformily cover all cases, most
 | |
| 				// likely requires processing all memories when initializing
 | |
| 				// the worker
 | |
| 				if (!portbit.port.in(ID::WR_DATA, ID::WR_ADDR, ID::RD_ADDR))
 | |
| 					return true;
 | |
| 
 | |
| 				if (portbit.port == ID::WR_DATA)
 | |
| 				{
 | |
| 					if (initconst(cell->getPort(ID::WR_EN)[portbit.offset]) == State::S0)
 | |
| 						continue;
 | |
| 				}
 | |
| 				else if (portbit.port == ID::WR_ADDR)
 | |
| 				{
 | |
| 					int port = portbit.offset / cell->getParam(ID::ABITS).as_int();
 | |
| 					auto sig_en = cell->getPort(ID::WR_EN);
 | |
| 					int width = cell->getParam(ID::WIDTH).as_int();
 | |
| 
 | |
| 					for (int i = port * width; i < (port + 1) * width; i++)
 | |
| 						if (initconst(sig_en[i]) != State::S0)
 | |
| 							return true;
 | |
| 
 | |
| 					continue;
 | |
| 				}
 | |
| 				else if (portbit.port == ID::RD_ADDR)
 | |
| 				{
 | |
| 					int port = portbit.offset / cell->getParam(ID::ABITS).as_int();
 | |
| 					auto sig_en = cell->getPort(ID::RD_EN);
 | |
| 
 | |
| 					if (initconst(sig_en[port]) != State::S0)
 | |
| 						return true;
 | |
| 
 | |
| 					continue;
 | |
| 				}
 | |
| 				else
 | |
| 					return true;
 | |
| 			}
 | |
| 			else
 | |
| 				log_assert(false);
 | |
| 		}
 | |
| 
 | |
| 		used_bits[bit] = false;
 | |
| 		return false;
 | |
| 	}
 | |
| };
 | |
| 
 | |
| struct ReplacedPort {
 | |
| 	IdString name;
 | |
| 	int offset;
 | |
| 	bool clk_pol;
 | |
| };
 | |
| 
 | |
| struct HierarchyWorker
 | |
| {
 | |
| 	Design *design;
 | |
| 	pool<Module *> pending;
 | |
| 
 | |
| 	dict<Module *, std::vector<ReplacedPort>> replaced_clk_inputs;
 | |
| 
 | |
| 	HierarchyWorker(Design *design) :
 | |
| 		design(design)
 | |
| 	{
 | |
| 		for (auto module : design->modules())
 | |
| 			pending.insert(module);
 | |
| 	}
 | |
| 
 | |
| 	void propagate();
 | |
| 
 | |
| 	const std::vector<ReplacedPort> &find_replaced_clk_inputs(IdString cell_type);
 | |
| };
 | |
| 
 | |
| // Propagates replaced clock signals
 | |
| struct PropagateWorker
 | |
| {
 | |
| 	HierarchyWorker &hierarchy;
 | |
| 
 | |
| 	Module *module;
 | |
| 	SigMap sigmap;
 | |
| 
 | |
| 	dict<SigBit, bool> replaced_clk_bits;
 | |
| 	dict<SigBit, SigBit> not_drivers;
 | |
| 
 | |
| 	std::vector<ReplacedPort> replaced_clk_inputs;
 | |
| 	std::vector<std::pair<SigBit, bool>> pending;
 | |
| 
 | |
| 	PropagateWorker(Module *module, HierarchyWorker &hierarchy) :
 | |
| 		hierarchy(hierarchy), module(module), sigmap(module)
 | |
| 	{
 | |
| 		hierarchy.pending.erase(module);
 | |
| 
 | |
| 		for (auto wire : module->wires())
 | |
| 			if (wire->has_attribute(ID::replaced_by_gclk))
 | |
| 				replace_clk_bit(SigBit(wire), wire->attributes[ID::replaced_by_gclk].at(0) == State::S1, false);
 | |
| 
 | |
| 		for (auto cell : module->cells()) {
 | |
| 			if (cell->type.in(ID($not), ID($_NOT_))) {
 | |
| 				auto sig_a = cell->getPort(ID::A);
 | |
| 				auto &sig_y = cell->getPort(ID::Y);
 | |
| 				sig_a.extend_u0(GetSize(sig_y), cell->hasParam(ID::A_SIGNED) && cell->parameters.at(ID::A_SIGNED).as_bool());
 | |
| 
 | |
| 				for (int i = 0; i < GetSize(sig_a); i++)
 | |
| 					if (sig_a[i].is_wire())
 | |
| 						not_drivers.emplace(sigmap(sig_y[i]), sigmap(sig_a[i]));
 | |
| 			} else {
 | |
| 				for (auto &port_bit : hierarchy.find_replaced_clk_inputs(cell->type))
 | |
| 					replace_clk_bit(cell->getPort(port_bit.name)[port_bit.offset], port_bit.clk_pol, true);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		while (!pending.empty()) {
 | |
| 			auto current = pending.back();
 | |
| 			pending.pop_back();
 | |
| 			auto it = not_drivers.find(current.first);
 | |
| 			if (it == not_drivers.end())
 | |
| 				continue;
 | |
| 
 | |
| 			replace_clk_bit(it->second, !current.second, true);
 | |
| 		}
 | |
| 
 | |
| 		for (auto cell : module->cells()) {
 | |
| 			if (cell->type.in(ID($not), ID($_NOT_)))
 | |
| 				continue;
 | |
| 			for (auto &conn : cell->connections()) {
 | |
| 				if (!cell->output(conn.first))
 | |
| 					continue;
 | |
| 				for (SigBit bit : conn.second) {
 | |
| 					sigmap.apply(bit);
 | |
| 					if (replaced_clk_bits.count(bit))
 | |
| 						log_error("derived signal %s driven by %s (%s) from module %s is used as clock, derived clocks are only supported with clk2fflogic.\n",
 | |
| 								log_signal(bit), log_id(cell->name), log_id(cell->type), log_id(module));
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		for (auto port : module->ports) {
 | |
| 			auto wire = module->wire(port);
 | |
| 			if (!wire->port_input)
 | |
| 				continue;
 | |
| 			for (int i = 0; i < GetSize(wire); i++) {
 | |
| 				SigBit bit(wire, i);
 | |
| 				sigmap.apply(bit);
 | |
| 				auto it = replaced_clk_bits.find(bit);
 | |
| 				if (it == replaced_clk_bits.end())
 | |
| 					continue;
 | |
| 				replaced_clk_inputs.emplace_back(ReplacedPort {port, i, it->second});
 | |
| 
 | |
| 				if (it->second) {
 | |
| 					bit = module->Not(NEW_ID, bit);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	void replace_clk_bit(SigBit bit, bool polarity, bool add_attribute)
 | |
| 	{
 | |
| 		sigmap.apply(bit);
 | |
| 		if (!bit.is_wire())
 | |
| 			log_error("XXX todo\n");
 | |
| 
 | |
| 		auto it = replaced_clk_bits.find(bit);
 | |
| 		if (it != replaced_clk_bits.end()) {
 | |
| 			if (it->second != polarity)
 | |
| 				log_error("signal %s from module %s is used as clock with different polarities, run clk2fflogic instead.\n",
 | |
| 						log_signal(bit), log_id(module));
 | |
| 			return;
 | |
| 		}
 | |
| 
 | |
| 		replaced_clk_bits.emplace(bit, polarity);
 | |
| 
 | |
| 		if (add_attribute) {
 | |
| 			Wire *clk_wire = bit.wire;
 | |
| 			if (bit.offset != 0 || GetSize(bit.wire) != 1) {
 | |
| 				clk_wire = module->addWire(NEW_ID);
 | |
| 				module->connect(RTLIL::SigBit(clk_wire), bit);
 | |
| 			}
 | |
| 			clk_wire->attributes[ID::replaced_by_gclk] = polarity ? State::S1 : State::S0;
 | |
| 			clk_wire->set_bool_attribute(ID::keep);
 | |
| 		}
 | |
| 
 | |
| 		pending.emplace_back(bit, polarity);
 | |
| 	}
 | |
| };
 | |
| 
 | |
| const std::vector<ReplacedPort> &HierarchyWorker::find_replaced_clk_inputs(IdString cell_type)
 | |
| {
 | |
| 	static const std::vector<ReplacedPort> empty;
 | |
| 	if (!cell_type.isPublic())
 | |
| 		return empty;
 | |
| 
 | |
| 	Module *module = design->module(cell_type);
 | |
| 	if (module == nullptr)
 | |
| 		return empty;
 | |
| 
 | |
| 	auto it = replaced_clk_inputs.find(module);
 | |
| 	if (it != replaced_clk_inputs.end())
 | |
| 		return it->second;
 | |
| 
 | |
| 	if (pending.count(module)) {
 | |
| 		PropagateWorker worker(module, *this);
 | |
| 		return replaced_clk_inputs.emplace(module, std::move(worker.replaced_clk_inputs)).first->second;
 | |
| 	}
 | |
| 
 | |
| 	return empty;
 | |
| }
 | |
| 
 | |
| 
 | |
| void HierarchyWorker::propagate()
 | |
| {
 | |
| 	while (!pending.empty())
 | |
| 		PropagateWorker worker(pending.pop(), *this);
 | |
| }
 | |
| 
 | |
| struct FormalFfPass : public Pass {
 | |
| 	FormalFfPass() : Pass("formalff", "prepare FFs for formal") { }
 | |
| 	bool formatted_help() override {
 | |
| 		auto *help = PrettyHelp::get_current();
 | |
| 		help->set_group("formal");
 | |
| 		return false;
 | |
| 	}
 | |
| 	void help() override
 | |
| 	{
 | |
| 		//   |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
 | |
| 		log("\n");
 | |
| 		log("    formalff [options] [selection]\n");
 | |
| 		log("\n");
 | |
| 		log("This pass transforms clocked flip-flops to prepare a design for formal\n");
 | |
| 		log("verification. If a design contains latches and/or multiple different clocks run\n");
 | |
| 		log("the async2sync or clk2fflogic passes before using this pass.\n");
 | |
| 		log("\n");
 | |
| 		log("    -clk2ff\n");
 | |
| 		log("        Replace all clocked flip-flops with $ff cells that use the implicit\n");
 | |
| 		log("        global clock. This assumes, without checking, that the design uses a\n");
 | |
| 		log("        single global clock. If that is not the case, the clk2fflogic pass\n");
 | |
| 		log("        should be used instead.\n");
 | |
| 		log("\n");
 | |
| 		log("    -ff2anyinit\n");
 | |
| 		log("        Replace uninitialized bits of $ff cells with $anyinit cells. An\n");
 | |
| 		log("        $anyinit cell behaves exactly like an $ff cell with an undefined\n");
 | |
| 		log("        initialization value. The difference is that $anyinit inhibits\n");
 | |
| 		log("        don't-care optimizations and is used to track solver-provided values\n");
 | |
| 		log("        in witness traces.\n");
 | |
| 		log("\n");
 | |
| 		log("        If combined with -clk2ff this also affects newly created $ff cells.\n");
 | |
| 		log("\n");
 | |
| 		log("    -anyinit2ff\n");
 | |
| 		log("        Replaces $anyinit cells with uninitialized $ff cells. This performs the\n");
 | |
| 		log("        reverse of -ff2anyinit and can be used, before running a backend pass\n");
 | |
| 		log("        (or similar) that is not yet aware of $anyinit cells.\n");
 | |
| 		log("\n");
 | |
| 		log("        Note that after running -anyinit2ff, in general, performing don't-care\n");
 | |
| 		log("        optimizations is not sound in a formal verification setting.\n");
 | |
| 		log("\n");
 | |
| 		log("    -fine\n");
 | |
| 		log("        Emit fine-grained $_FF_ cells instead of coarse-grained $ff cells for\n");
 | |
| 		log("        -anyinit2ff. Cannot be combined with -clk2ff or -ff2anyinit.\n");
 | |
| 		log("\n");
 | |
| 		log("    -setundef\n");
 | |
| 		log("        Find FFs with undefined initialization values for which changing the\n");
 | |
| 		log("        initialization does not change the observable behavior and initialize\n");
 | |
| 		log("        them. For -ff2anyinit, this reduces the number of generated $anyinit\n");
 | |
| 		log("        cells that drive wires with private names.\n");
 | |
| 		log("\n");
 | |
| 		log("    -hierarchy\n");
 | |
| 		log("        Propagates the 'replaced_by_gclk' attribute set by clk2ff upwards\n");
 | |
| 		log("        through the design hierarchy towards the toplevel inputs. This option\n");
 | |
| 		log("        works on the whole design and ignores the selection.\n");
 | |
| 		log("\n");
 | |
| 		log("    -assume\n");
 | |
| 		log("        Add assumptions that constrain wires with the 'replaced_by_gclk'\n");
 | |
| 		log("        attribute to the value they would have before an active clock edge.\n");
 | |
| 		log("\n");
 | |
| 		log("    -declockgate\n");
 | |
| 		log("        Detect clock-gating patterns and modify any FFs clocked by the gated\n");
 | |
| 		log("        clock to use the ungated clock with the gate signal as clock enable.\n");
 | |
| 		log("        This doesn't affect the design's behavior during FV but can enable the\n");
 | |
| 		log("        use of formal verification methods that only support a single global\n");
 | |
| 		log("        clock.\n");
 | |
| 
 | |
| 		// TODO: An option to check whether all FFs use the same clock before changing it to the global clock
 | |
| 	}
 | |
| 	void execute(std::vector<std::string> args, RTLIL::Design *design) override
 | |
| 	{
 | |
| 		bool flag_clk2ff = false;
 | |
| 		bool flag_ff2anyinit = false;
 | |
| 		bool flag_anyinit2ff = false;
 | |
| 		bool flag_fine = false;
 | |
| 		bool flag_setundef = false;
 | |
| 		bool flag_hierarchy = false;
 | |
| 		bool flag_assume = false;
 | |
| 		bool flag_declockgate = false;
 | |
| 
 | |
| 		log_header(design, "Executing FORMALFF pass.\n");
 | |
| 
 | |
| 		size_t argidx;
 | |
| 		for (argidx = 1; argidx < args.size(); argidx++)
 | |
| 		{
 | |
| 			if (args[argidx] == "-clk2ff") {
 | |
| 				flag_clk2ff = true;
 | |
| 				continue;
 | |
| 			}
 | |
| 			if (args[argidx] == "-ff2anyinit") {
 | |
| 				flag_ff2anyinit = true;
 | |
| 				continue;
 | |
| 			}
 | |
| 			if (args[argidx] == "-anyinit2ff") {
 | |
| 				flag_anyinit2ff = true;
 | |
| 				continue;
 | |
| 			}
 | |
| 			if (args[argidx] == "-fine") {
 | |
| 				flag_fine = true;
 | |
| 				continue;
 | |
| 			}
 | |
| 			if (args[argidx] == "-setundef") {
 | |
| 				flag_setundef = true;
 | |
| 				continue;
 | |
| 			}
 | |
| 			if (args[argidx] == "-hierarchy") {
 | |
| 				flag_hierarchy = true;
 | |
| 				continue;
 | |
| 			}
 | |
| 			if (args[argidx] == "-assume") {
 | |
| 				flag_assume = true;
 | |
| 				continue;
 | |
| 			}
 | |
| 			if (args[argidx] == "-declockgate") {
 | |
| 				flag_declockgate = true;
 | |
| 				continue;
 | |
| 			}
 | |
| 			break;
 | |
| 		}
 | |
| 		extra_args(args, argidx, design);
 | |
| 
 | |
| 		if (!(flag_clk2ff || flag_ff2anyinit || flag_anyinit2ff || flag_hierarchy || flag_assume || flag_declockgate))
 | |
| 			log_cmd_error("One of the options -clk2ff, -ff2anyinit, -anyinit2ff, -hierarchy or -assume must be specified.\n");
 | |
| 
 | |
| 		if (flag_ff2anyinit && flag_anyinit2ff)
 | |
| 			log_cmd_error("The options -ff2anyinit and -anyinit2ff are exclusive.\n");
 | |
| 
 | |
| 		if (flag_ff2anyinit && flag_declockgate)
 | |
| 			log_cmd_error("The options -ff2anyinit and -declockgate are exclusive.\n");
 | |
| 
 | |
| 		if (flag_fine && !flag_anyinit2ff)
 | |
| 			log_cmd_error("The option -fine requries the -anyinit2ff option.\n");
 | |
| 
 | |
| 		if (flag_fine && flag_clk2ff)
 | |
| 			log_cmd_error("The options -fine and -clk2ff are exclusive.\n");
 | |
| 
 | |
| 		if (flag_declockgate)
 | |
| 		{
 | |
| 			for (auto module : design->selected_modules())
 | |
| 			{
 | |
| 				ModWalker modwalker(design);
 | |
| 				modwalker.setup(module);
 | |
| 				SigMap &sigmap = modwalker.sigmap;
 | |
| 				FfInitVals initvals(&modwalker.sigmap, module);
 | |
| 
 | |
| 				dict<IdString, Mem> memories;
 | |
| 
 | |
| 				for (auto mem : Mem::get_selected_memories(module)) {
 | |
| 					if (!mem.packed)
 | |
| 						continue;
 | |
| 					memories.emplace(mem.cell->name, std::move(mem));
 | |
| 				}
 | |
| 
 | |
| 				dict<pair<SigBit, bool>, vector<Cell *>> clk_bits;
 | |
| 				pool<SigBit> input_bits;
 | |
| 				pool<pair<SigBit, bool>> input_clk_bits;
 | |
| 				for (auto cell : module->selected_cells()) {
 | |
| 					if (cell->is_builtin_ff()) {
 | |
| 						FfData ff(&initvals, cell);
 | |
| 						if (!ff.has_clk)
 | |
| 							continue;
 | |
| 						SigBit clk = sigmap(ff.sig_clk);
 | |
| 						clk_bits[{clk, ff.pol_clk}].push_back(cell);
 | |
| 					} else if (cell->type == ID($mem_v2)) {
 | |
| 						auto const &mem = memories.at(cell->name);
 | |
| 						for (auto &rd_port : mem.rd_ports)
 | |
| 							if (rd_port.clk_enable)
 | |
| 								clk_bits[{rd_port.clk, rd_port.clk_polarity}].push_back(mem.cell);
 | |
| 						for (auto &wr_port : mem.wr_ports)
 | |
| 							if (wr_port.clk_enable)
 | |
| 								clk_bits[{wr_port.clk, wr_port.clk_polarity}].push_back(mem.cell);
 | |
| 					}
 | |
| 					// XXX $check $print
 | |
| 				}
 | |
| 
 | |
| 				log_debug("%s has %d clk bits\n", log_id(module), GetSize(clk_bits));
 | |
| 
 | |
| 				for (auto port : module->ports) {
 | |
| 					Wire *wire = module->wire(port);
 | |
| 					if (!wire->port_input)
 | |
| 						continue;
 | |
| 					for (auto bit : SigSpec(wire)) {
 | |
| 						input_bits.insert(bit);
 | |
| 						for (bool pol : {false, true}) {
 | |
| 							if (clk_bits.count({bit, pol})) {
 | |
| 								input_clk_bits.insert({bit, pol});
 | |
| 								clk_bits.erase({bit, pol});
 | |
| 							}
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 				log_debug("%s has %d non-input clk bits\n", log_id(module), GetSize(clk_bits));
 | |
| 
 | |
| 				if (clk_bits.empty())
 | |
| 					continue;
 | |
| 
 | |
| 				for (auto &clk_bit : clk_bits)
 | |
| 				{
 | |
| 					SigBit clk = clk_bit.first.first;
 | |
| 					bool pol_clk = clk_bit.first.second;
 | |
| 					vector<Cell *> &clocked_cells = clk_bit.second;
 | |
| 
 | |
| 					if (!clk.is_wire()) {
 | |
| 						log_debug("constant clk bit %s.%s\n", log_id(module), log_signal(SigSpec(clk)));
 | |
| 						continue;
 | |
| 					}
 | |
| 					if (input_bits.count(clk)) {
 | |
| 						log_debug("input clk bit %s.%s\n", log_id(module), log_signal(SigSpec(clk)));
 | |
| 						continue;
 | |
| 					}
 | |
| 					auto found = modwalker.signal_drivers.find(clk);
 | |
| 					if (found == modwalker.signal_drivers.end() || found->second.empty()) {
 | |
| 						log_debug("undriven clk bit %s.%s\n", log_id(module), log_signal(SigSpec(clk)));
 | |
| 						continue;
 | |
| 					}
 | |
| 
 | |
| 					if (found->second.size() > 1) {
 | |
| 						log_debug("multiple drivers for clk bit %s.%s\n", log_id(module), log_signal(SigSpec(clk)));
 | |
| 						continue;
 | |
| 					}
 | |
| 
 | |
| 					auto driver = *found->second.begin();
 | |
| 
 | |
| 					bool is_gate =
 | |
| 					  pol_clk ? driver.cell->type.in(ID($and), ID($_AND_)) : driver.cell->type.in(ID($or), ID($_OR_));
 | |
| 
 | |
| 					if (!is_gate) {
 | |
| 						log_debug("unsupported gating logic %s.%s (%s) for clock %s %s.%s\n", log_id(module),
 | |
| 							  log_id(driver.cell), log_id(driver.cell->type), pol_clk ? "posedge" : "negedge",
 | |
| 							  log_id(module), log_signal(SigSpec(clk)));
 | |
| 
 | |
| 						continue;
 | |
| 					}
 | |
| 					SigBit gate_clock = sigmap(driver.cell->getPort(ID::A)[driver.offset]);
 | |
| 					SigBit gate_enable = sigmap(driver.cell->getPort(ID::B)[driver.offset]);
 | |
| 
 | |
| 					std::swap(gate_clock, gate_enable);
 | |
| 					for (int i = 0; i < 2; i++) {
 | |
| 						std::swap(gate_clock, gate_enable);
 | |
| 
 | |
| 						log_debug("clock %s.%s for gated clk bit %s.%s\n", log_id(module), log_signal(SigSpec(gate_clock)),
 | |
| 							  log_id(module), log_signal(SigSpec(clk)));
 | |
| 						log_debug("enable %s.%s for gated clk bit %s.%s\n", log_id(module), log_signal(SigSpec(gate_enable)),
 | |
| 							  log_id(module), log_signal(SigSpec(clk)));
 | |
| 
 | |
| 						found = modwalker.signal_drivers.find(gate_enable);
 | |
| 						if (found == modwalker.signal_drivers.end() || found->second.empty()) {
 | |
| 							log_debug("undriven gate enable %s.%s of gated clk bit %s.%s\n", log_id(module),
 | |
| 								  log_signal(SigSpec(gate_enable)), log_id(module), log_signal(SigSpec(clk)));
 | |
| 							continue;
 | |
| 						}
 | |
| 						if (found->second.size() > 1) {
 | |
| 							log_debug("multiple drivers for gate enable %s.%s of gated clk bit %s.%s\n", log_id(module),
 | |
| 								  log_signal(SigSpec(gate_enable)), log_id(module), log_signal(SigSpec(clk)));
 | |
| 							continue;
 | |
| 						}
 | |
| 
 | |
| 						auto gate_driver = *found->second.begin();
 | |
| 
 | |
| 						if (!gate_driver.cell->is_builtin_ff()) {
 | |
| 							log_debug("non FF driver for gate enable %s.%s of gated clk bit %s.%s\n", log_id(module),
 | |
| 								  log_signal(SigSpec(gate_enable)), log_id(module), log_signal(SigSpec(clk)));
 | |
| 							continue;
 | |
| 						}
 | |
| 
 | |
| 						FfData ff(&initvals, gate_driver.cell);
 | |
| 						if (ff.has_gclk || ff.has_ce || ff.has_sr || ff.has_srst || ff.has_arst || (ff.has_aload && ff.has_clk)) {
 | |
| 							log_debug(
 | |
| 							  "FF driver for gate enable %s.%s of gated clk bit %s.%s has incompatible type: %s\n",
 | |
| 							  log_id(module), log_signal(SigSpec(gate_enable)), log_id(module), log_signal(SigSpec(clk)),
 | |
| 							  log_id(gate_driver.cell->type));
 | |
| 							continue;
 | |
| 						}
 | |
| 
 | |
| 						if (ff.has_aload) {
 | |
| 							// this ff is intentionally not emitted!
 | |
| 							ff.has_aload = false;
 | |
| 							ff.has_clk = true;
 | |
| 							ff.pol_clk = !ff.pol_arst;
 | |
| 							ff.sig_clk = ff.sig_aload;
 | |
| 							ff.sig_d = ff.sig_ad;
 | |
| 						}
 | |
| 
 | |
| 						if (!ff.has_clk || sigmap(ff.sig_clk) != gate_clock || ff.pol_clk != pol_clk) {
 | |
| 							log_debug("FF driver for gate enable %s.%s of gated clk bit %s.%s has incompatible clocking: "
 | |
| 								  "%s %s.%s\n",
 | |
| 								  log_id(module), log_signal(SigSpec(gate_enable)), log_id(module),
 | |
| 								  log_signal(SigSpec(clk)), ff.pol_clk ? "posedge" : "negedge", log_id(module),
 | |
| 								  log_signal(SigSpec(ff.sig_clk)));
 | |
| 							continue;
 | |
| 						}
 | |
| 
 | |
| 						SigBit sig_gate = ff.sig_d[gate_driver.offset];
 | |
| 
 | |
| 						log_debug("found clock gate, rewriting %d cells\n", GetSize(clocked_cells));
 | |
| 
 | |
| 						for (auto clocked_cell : clocked_cells) {
 | |
| 							log_debug("rewriting cell %s.%s (%s)\n", log_id(module), log_id(clocked_cell),
 | |
| 								  log_id(clocked_cell->type));
 | |
| 
 | |
| 							if (clocked_cell->is_builtin_ff()) {
 | |
| 
 | |
| 								FfData ff(&initvals, clocked_cell);
 | |
| 								log_assert(ff.has_clk);
 | |
| 								ff.unmap_ce();
 | |
| 								ff.pol_ce = pol_clk;
 | |
| 								ff.sig_ce = sig_gate;
 | |
| 								ff.has_ce = true;
 | |
| 								ff.sig_clk = gate_clock;
 | |
| 								ff.emit();
 | |
| 							} else if (clocked_cell->type == ID($mem_v2)) {
 | |
| 								auto &mem = memories.at(clocked_cell->name);
 | |
| 								bool changed = false;
 | |
| 								for (auto &rd_port : mem.rd_ports) {
 | |
| 									if (rd_port.clk_enable && rd_port.clk == clk && rd_port.clk_polarity == pol_clk) {
 | |
| 										log_debug("patching rd port\n");
 | |
| 										changed = true;
 | |
| 										rd_port.clk = gate_clock;
 | |
| 										SigBit en_bit = pol_clk ? sig_gate : SigBit(module->Not(NEW_ID, sig_gate));
 | |
| 										SigSpec en_mask = SigSpec(en_bit, GetSize(rd_port.en));
 | |
| 										rd_port.en = module->And(NEW_ID, rd_port.en, en_mask);
 | |
| 									}
 | |
| 								}
 | |
| 								for (auto &wr_port : mem.wr_ports) {
 | |
| 									if (wr_port.clk_enable && wr_port.clk == clk && wr_port.clk_polarity == pol_clk) {
 | |
| 										log_debug("patching wr port\n");
 | |
| 										changed = true;
 | |
| 										wr_port.clk = gate_clock;
 | |
| 										SigBit en_bit = pol_clk ? sig_gate : SigBit(module->Not(NEW_ID, sig_gate));
 | |
| 										SigSpec en_mask = SigSpec(en_bit, GetSize(wr_port.en));
 | |
| 										wr_port.en = module->And(NEW_ID, wr_port.en, en_mask);
 | |
| 									}
 | |
| 								}
 | |
| 								if (changed)
 | |
| 									mem.emit();
 | |
| 							}
 | |
| 						}
 | |
| 
 | |
| 						break;
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		for (auto module : design->selected_modules())
 | |
| 		{
 | |
| 			if (flag_setundef)
 | |
| 			{
 | |
| 				InitValWorker worker(module);
 | |
| 
 | |
| 				for (auto cell : module->selected_cells())
 | |
| 				{
 | |
| 					if (cell->is_builtin_ff())
 | |
| 					{
 | |
| 						FfData ff(&worker.initvals, cell);
 | |
| 						if (ff.has_aload || ff.has_sr || ff.has_arst || ff.val_init.is_fully_def())
 | |
| 							continue;
 | |
| 
 | |
| 						if (ff.has_ce && // CE can make the initval stick around
 | |
| 								worker.initconst(ff.sig_ce.as_bit()) != (ff.pol_ce ? State::S1 : State::S0) && // unless it's known active
 | |
| 								(!ff.has_srst || ff.ce_over_srst ||
 | |
| 									worker.initconst(ff.sig_srst.as_bit()) != (ff.pol_srst ? State::S1 : State::S0))) // or a srst with priority is known active
 | |
| 							continue;
 | |
| 
 | |
| 						auto before = ff.val_init;
 | |
| 						for (int i = 0; i < ff.width; i++)
 | |
| 							if (ff.val_init[i] == State::Sx && !worker.is_initval_used(ff.sig_q[i]))
 | |
| 								ff.val_init.set(i, State::S0);
 | |
| 
 | |
| 						if (ff.val_init != before) {
 | |
| 							log("Setting unused undefined initial value of %s.%s (%s) from %s to %s\n",
 | |
| 									log_id(module), log_id(cell), log_id(cell->type),
 | |
| 									log_const(before), log_const(ff.val_init));
 | |
| 							worker.initvals.set_init(ff.sig_q, ff.val_init);
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 			SigMap sigmap(module);
 | |
| 			FfInitVals initvals(&sigmap, module);
 | |
| 
 | |
| 
 | |
| 			for (auto cell : module->selected_cells())
 | |
| 			{
 | |
| 				if (flag_anyinit2ff && cell->type == ID($anyinit))
 | |
| 				{
 | |
| 					FfData ff(&initvals, cell);
 | |
| 					ff.remove();
 | |
| 					ff.is_anyinit = false;
 | |
| 					ff.is_fine = flag_fine;
 | |
| 					if (flag_fine)
 | |
| 						for (int i = 0; i < ff.width; i++)
 | |
| 							ff.slice({i}).emit();
 | |
| 					else
 | |
| 						ff.emit();
 | |
| 
 | |
| 					continue;
 | |
| 				}
 | |
| 
 | |
| 				if (!cell->is_builtin_ff())
 | |
| 					continue;
 | |
| 
 | |
| 				FfData ff(&initvals, cell);
 | |
| 				bool emit = false;
 | |
| 
 | |
| 				if (flag_clk2ff && ff.has_clk) {
 | |
| 					if (ff.sig_clk.is_fully_const())
 | |
| 						log_error("Const CLK on %s (%s) from module %s, run async2sync first.\n",
 | |
| 								log_id(cell), log_id(cell->type), log_id(module));
 | |
| 					if (ff.has_aload || ff.has_arst || ff.has_sr)
 | |
| 						log_error("Async inputs on %s (%s) from module %s, run async2sync first.\n",
 | |
| 								log_id(cell), log_id(cell->type), log_id(module));
 | |
| 
 | |
| 					auto clk_wire = ff.sig_clk.is_wire() ? ff.sig_clk.as_wire() : nullptr;
 | |
| 
 | |
| 					if (clk_wire == nullptr) {
 | |
| 						clk_wire = module->addWire(NEW_ID);
 | |
| 						module->connect(RTLIL::SigBit(clk_wire), ff.sig_clk);
 | |
| 					}
 | |
| 
 | |
| 					auto clk_polarity = ff.pol_clk ? State::S1 : State::S0;
 | |
| 
 | |
| 					std::string attribute = clk_wire->get_string_attribute(ID::replaced_by_gclk);
 | |
| 
 | |
| 					auto &attr = clk_wire->attributes[ID::replaced_by_gclk];
 | |
| 
 | |
| 					if (!attr.empty() && attr != clk_polarity)
 | |
| 						log_error("CLK %s on %s (%s) from module %s also used with opposite polarity, run clk2fflogic instead.\n",
 | |
| 								log_id(clk_wire), log_id(cell), log_id(cell->type), log_id(module));
 | |
| 
 | |
| 					attr = clk_polarity;
 | |
| 					clk_wire->set_bool_attribute(ID::keep);
 | |
| 
 | |
| 					// TODO propagate the replaced_by_gclk attribute upwards throughout the hierarchy
 | |
| 
 | |
| 					ff.unmap_ce_srst();
 | |
| 					ff.has_clk = false;
 | |
| 					ff.has_gclk = true;
 | |
| 					emit = true;
 | |
| 				}
 | |
| 
 | |
| 				if (!ff.has_gclk) {
 | |
| 					continue;
 | |
| 				}
 | |
| 
 | |
| 				if (flag_ff2anyinit && !ff.val_init.is_fully_def())
 | |
| 				{
 | |
| 					ff.remove();
 | |
| 					emit = false;
 | |
| 
 | |
| 					int cursor = 0;
 | |
| 					while (cursor < ff.val_init.size())
 | |
| 					{
 | |
| 						bool is_anyinit = ff.val_init[cursor] == State::Sx;
 | |
| 						std::vector<int> bits;
 | |
| 						bits.push_back(cursor++);
 | |
| 						while (cursor < ff.val_init.size() && (ff.val_init[cursor] == State::Sx) == is_anyinit)
 | |
| 							bits.push_back(cursor++);
 | |
| 
 | |
| 						if ((int)bits.size() == ff.val_init.size()) {
 | |
| 							// This check is only to make the private names more helpful for debugging
 | |
| 							ff.is_anyinit = true;
 | |
| 							ff.is_fine = false;
 | |
| 							emit = true;
 | |
| 							break;
 | |
| 						}
 | |
| 
 | |
| 						auto slice = ff.slice(bits);
 | |
| 						slice.is_anyinit = is_anyinit;
 | |
| 						slice.is_fine = false;
 | |
| 						slice.emit();
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				if (emit)
 | |
| 					ff.emit();
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if (flag_hierarchy) {
 | |
| 			HierarchyWorker worker(design);
 | |
| 			worker.propagate();
 | |
| 		}
 | |
| 
 | |
| 		if (flag_assume) {
 | |
| 			for (auto module : design->selected_modules()) {
 | |
| 				std::vector<pair<SigBit, bool>> found;
 | |
| 				for (auto wire : module->wires()) {
 | |
| 					if (!wire->has_attribute(ID::replaced_by_gclk))
 | |
| 						continue;
 | |
| 					bool clk_pol = wire->attributes[ID::replaced_by_gclk].at(0) == State::S1;
 | |
| 
 | |
| 					found.emplace_back(SigSpec(wire), clk_pol);
 | |
| 				}
 | |
| 				for (auto pair : found) {
 | |
| 					SigBit clk = pair.first;
 | |
| 
 | |
| 					if (pair.second)
 | |
| 						clk = module->Not(NEW_ID, clk);
 | |
| 
 | |
| 					module->addAssume(NEW_ID, clk, State::S1);
 | |
| 
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| } FormalFfPass;
 | |
| 
 | |
| PRIVATE_NAMESPACE_END
 |