mirror of
				https://github.com/YosysHQ/yosys
				synced 2025-10-29 18:52:30 +00:00 
			
		
		
		
	cxxrtl: implement debug information outlining.
Aggressive wire localization and inlining is necessary for CXXRTL to
achieve high performance. However, that comes with a cost: reduced
debug information coverage. Previously, as a workaround, the `-Og`
option could have been used to guarantee complete coverage, at a cost
of a significant performance penalty.
This commit introduces debug information outlining. The main eval()
function is compiled with the user-specified optimization settings.
In tandem, an auxiliary debug_eval() function, compiled from the same
netlist, can be used to reconstruct the values of localized/inlined
signals on demand. To the extent that it is possible, debug_eval()
reuses the results of computations performed by eval(), only filling
in the missing values.
Benchmarking a representative design (Minerva SoC SRAM) shows that:
  * Switching from `-O4`/`-Og` to `-O6` reduces runtime by ~40%.
  * Switching from `-g1` to `-g2`, both used with `-O6`, increases
    compile time by ~25%.
  * Although `-g2` increases the resident size of generated modules,
    this has no effect on runtime.
Because the impact of `-g2` is minimal and the benefits of having
unconditional 100% debug information coverage (and the performance
improvement as well) are major, this commit removes `-Og` and changes
the defaults to `-O6 -g2`.
We'll have our cake and eat it too!
			
			
This commit is contained in:
		
							parent
							
								
									3b5a1314cd
								
							
						
					
					
						commit
						ece25a45d4
					
				
					 5 changed files with 278 additions and 71 deletions
				
			
		|  | @ -36,6 +36,7 @@ | ||||||
| #include <map> | #include <map> | ||||||
| #include <algorithm> | #include <algorithm> | ||||||
| #include <memory> | #include <memory> | ||||||
|  | #include <functional> | ||||||
| #include <sstream> | #include <sstream> | ||||||
| 
 | 
 | ||||||
| #include <backends/cxxrtl/cxxrtl_capi.h> | #include <backends/cxxrtl/cxxrtl_capi.h> | ||||||
|  | @ -843,6 +844,9 @@ typedef std::map<std::string, metadata> metadata_map; | ||||||
| // Tag class to disambiguate values/wires and their aliases.
 | // Tag class to disambiguate values/wires and their aliases.
 | ||||||
| struct debug_alias {}; | struct debug_alias {}; | ||||||
| 
 | 
 | ||||||
|  | // Tag declaration to disambiguate values and debug outlines.
 | ||||||
|  | using debug_outline = ::_cxxrtl_outline; | ||||||
|  | 
 | ||||||
| // This structure is intended for consumption via foreign function interfaces, like Python's ctypes.
 | // This structure is intended for consumption via foreign function interfaces, like Python's ctypes.
 | ||||||
| // Because of this it uses a C-style layout that is easy to parse rather than more idiomatic C++.
 | // Because of this it uses a C-style layout that is easy to parse rather than more idiomatic C++.
 | ||||||
| //
 | //
 | ||||||
|  | @ -851,10 +855,11 @@ struct debug_alias {}; | ||||||
| struct debug_item : ::cxxrtl_object { | struct debug_item : ::cxxrtl_object { | ||||||
| 	// Object types.
 | 	// Object types.
 | ||||||
| 	enum : uint32_t { | 	enum : uint32_t { | ||||||
| 		VALUE  = CXXRTL_VALUE, | 		VALUE   = CXXRTL_VALUE, | ||||||
| 		WIRE   = CXXRTL_WIRE, | 		WIRE    = CXXRTL_WIRE, | ||||||
| 		MEMORY = CXXRTL_MEMORY, | 		MEMORY  = CXXRTL_MEMORY, | ||||||
| 		ALIAS  = CXXRTL_ALIAS, | 		ALIAS   = CXXRTL_ALIAS, | ||||||
|  | 		OUTLINE = CXXRTL_OUTLINE, | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	// Object flags.
 | 	// Object flags.
 | ||||||
|  | @ -881,6 +886,7 @@ struct debug_item : ::cxxrtl_object { | ||||||
| 		zero_at = 0; | 		zero_at = 0; | ||||||
| 		curr    = item.data; | 		curr    = item.data; | ||||||
| 		next    = item.data; | 		next    = item.data; | ||||||
|  | 		outline = nullptr; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	template<size_t Bits> | 	template<size_t Bits> | ||||||
|  | @ -895,6 +901,7 @@ struct debug_item : ::cxxrtl_object { | ||||||
| 		zero_at = 0; | 		zero_at = 0; | ||||||
| 		curr    = const_cast<chunk_t*>(item.data); | 		curr    = const_cast<chunk_t*>(item.data); | ||||||
| 		next    = nullptr; | 		next    = nullptr; | ||||||
|  | 		outline = nullptr; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	template<size_t Bits> | 	template<size_t Bits> | ||||||
|  | @ -910,6 +917,7 @@ struct debug_item : ::cxxrtl_object { | ||||||
| 		zero_at = 0; | 		zero_at = 0; | ||||||
| 		curr    = item.curr.data; | 		curr    = item.curr.data; | ||||||
| 		next    = item.next.data; | 		next    = item.next.data; | ||||||
|  | 		outline = nullptr; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	template<size_t Width> | 	template<size_t Width> | ||||||
|  | @ -924,6 +932,7 @@ struct debug_item : ::cxxrtl_object { | ||||||
| 		zero_at = zero_offset; | 		zero_at = zero_offset; | ||||||
| 		curr    = item.data.empty() ? nullptr : item.data[0].data; | 		curr    = item.data.empty() ? nullptr : item.data[0].data; | ||||||
| 		next    = nullptr; | 		next    = nullptr; | ||||||
|  | 		outline = nullptr; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	template<size_t Bits> | 	template<size_t Bits> | ||||||
|  | @ -938,6 +947,7 @@ struct debug_item : ::cxxrtl_object { | ||||||
| 		zero_at = 0; | 		zero_at = 0; | ||||||
| 		curr    = const_cast<chunk_t*>(item.data); | 		curr    = const_cast<chunk_t*>(item.data); | ||||||
| 		next    = nullptr; | 		next    = nullptr; | ||||||
|  | 		outline = nullptr; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	template<size_t Bits> | 	template<size_t Bits> | ||||||
|  | @ -953,6 +963,22 @@ struct debug_item : ::cxxrtl_object { | ||||||
| 		zero_at = 0; | 		zero_at = 0; | ||||||
| 		curr    = const_cast<chunk_t*>(item.curr.data); | 		curr    = const_cast<chunk_t*>(item.curr.data); | ||||||
| 		next    = nullptr; | 		next    = nullptr; | ||||||
|  | 		outline = nullptr; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	template<size_t Bits> | ||||||
|  | 	debug_item(debug_outline &group, const value<Bits> &item, size_t lsb_offset = 0) { | ||||||
|  | 		static_assert(sizeof(item) == value<Bits>::chunks * sizeof(chunk_t), | ||||||
|  | 		              "value<Bits> is not compatible with C layout"); | ||||||
|  | 		type    = OUTLINE; | ||||||
|  | 		flags   = DRIVEN_COMB; | ||||||
|  | 		width   = Bits; | ||||||
|  | 		lsb_at  = lsb_offset; | ||||||
|  | 		depth   = 1; | ||||||
|  | 		zero_at = 0; | ||||||
|  | 		curr    = const_cast<chunk_t*>(item.data); | ||||||
|  | 		next    = nullptr; | ||||||
|  | 		outline = &group; | ||||||
| 	} | 	} | ||||||
| }; | }; | ||||||
| static_assert(std::is_standard_layout<debug_item>::value, "debug_item is not compatible with C layout"); | static_assert(std::is_standard_layout<debug_item>::value, "debug_item is not compatible with C layout"); | ||||||
|  | @ -1029,11 +1055,16 @@ struct module { | ||||||
| 
 | 
 | ||||||
| } // namespace cxxrtl
 | } // namespace cxxrtl
 | ||||||
| 
 | 
 | ||||||
| // Internal structure used to communicate with the implementation of the C interface.
 | // Internal structures used to communicate with the implementation of the C interface.
 | ||||||
|  | 
 | ||||||
| typedef struct _cxxrtl_toplevel { | typedef struct _cxxrtl_toplevel { | ||||||
| 	std::unique_ptr<cxxrtl::module> module; | 	std::unique_ptr<cxxrtl::module> module; | ||||||
| } *cxxrtl_toplevel; | } *cxxrtl_toplevel; | ||||||
| 
 | 
 | ||||||
|  | typedef struct _cxxrtl_outline { | ||||||
|  | 	std::function<void()> eval; | ||||||
|  | } *cxxrtl_outline; | ||||||
|  | 
 | ||||||
| // Definitions of internal Yosys cells. Other than the functions in this namespace, CXXRTL is fully generic
 | // Definitions of internal Yosys cells. Other than the functions in this namespace, CXXRTL is fully generic
 | ||||||
| // and indepenent of Yosys implementation details.
 | // and indepenent of Yosys implementation details.
 | ||||||
| //
 | //
 | ||||||
|  |  | ||||||
|  | @ -539,6 +539,7 @@ struct CxxrtlWorker { | ||||||
| 	bool inline_public = false; | 	bool inline_public = false; | ||||||
| 
 | 
 | ||||||
| 	bool debug_info = false; | 	bool debug_info = false; | ||||||
|  | 	bool debug_eval = false; | ||||||
| 
 | 
 | ||||||
| 	std::ostringstream f; | 	std::ostringstream f; | ||||||
| 	std::string indent; | 	std::string indent; | ||||||
|  | @ -553,8 +554,9 @@ struct CxxrtlWorker { | ||||||
| 	pool<const RTLIL::Wire*> unbuffered_wires; | 	pool<const RTLIL::Wire*> unbuffered_wires; | ||||||
| 	pool<const RTLIL::Wire*> localized_wires; | 	pool<const RTLIL::Wire*> localized_wires; | ||||||
| 	dict<const RTLIL::Wire*, FlowGraph::Node> inlined_wires; | 	dict<const RTLIL::Wire*, FlowGraph::Node> inlined_wires; | ||||||
| 	dict<const RTLIL::Wire*, const RTLIL::Wire*> debug_alias_wires; |  | ||||||
| 	dict<const RTLIL::Wire*, RTLIL::Const> debug_const_wires; | 	dict<const RTLIL::Wire*, RTLIL::Const> debug_const_wires; | ||||||
|  | 	dict<const RTLIL::Wire*, const RTLIL::Wire*> debug_alias_wires; | ||||||
|  | 	pool<const RTLIL::Wire*> debug_outlined_wires; | ||||||
| 	dict<RTLIL::SigBit, bool> bit_has_state; | 	dict<RTLIL::SigBit, bool> bit_has_state; | ||||||
| 	dict<const RTLIL::Module*, pool<std::string>> blackbox_specializations; | 	dict<const RTLIL::Module*, pool<std::string>> blackbox_specializations; | ||||||
| 	dict<const RTLIL::Module*, bool> eval_converges; | 	dict<const RTLIL::Module*, bool> eval_converges; | ||||||
|  | @ -786,22 +788,22 @@ struct CxxrtlWorker { | ||||||
| 		dump_const(data, data.size()); | 		dump_const(data, data.size()); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	bool dump_sigchunk(const RTLIL::SigChunk &chunk, bool is_lhs) | 	bool dump_sigchunk(const RTLIL::SigChunk &chunk, bool is_lhs, bool for_debug = false) | ||||||
| 	{ | 	{ | ||||||
| 		if (chunk.wire == NULL) { | 		if (chunk.wire == NULL) { | ||||||
| 			dump_const(chunk.data, chunk.width, chunk.offset); | 			dump_const(chunk.data, chunk.width, chunk.offset); | ||||||
| 			return false; | 			return false; | ||||||
| 		} else { | 		} else { | ||||||
| 			if (inlined_wires.count(chunk.wire)) { | 			if (inlined_wires.count(chunk.wire) && (!for_debug || !debug_outlined_wires[chunk.wire])) { | ||||||
| 				log_assert(!is_lhs); | 				log_assert(!is_lhs); | ||||||
| 				const FlowGraph::Node &node = inlined_wires[chunk.wire]; | 				const FlowGraph::Node &node = inlined_wires[chunk.wire]; | ||||||
| 				switch (node.type) { | 				switch (node.type) { | ||||||
| 					case FlowGraph::Node::Type::CONNECT: | 					case FlowGraph::Node::Type::CONNECT: | ||||||
| 						dump_connect_expr(node.connect); | 						dump_connect_expr(node.connect, for_debug); | ||||||
| 						break; | 						break; | ||||||
| 					case FlowGraph::Node::Type::CELL_EVAL: | 					case FlowGraph::Node::Type::CELL_EVAL: | ||||||
| 						log_assert(is_inlinable_cell(node.cell->type)); | 						log_assert(is_inlinable_cell(node.cell->type)); | ||||||
| 						dump_cell_expr(node.cell); | 						dump_cell_expr(node.cell, for_debug); | ||||||
| 						break; | 						break; | ||||||
| 					default: | 					default: | ||||||
| 						log_assert(false); | 						log_assert(false); | ||||||
|  | @ -821,36 +823,36 @@ struct CxxrtlWorker { | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	bool dump_sigspec(const RTLIL::SigSpec &sig, bool is_lhs) | 	bool dump_sigspec(const RTLIL::SigSpec &sig, bool is_lhs, bool for_debug = false) | ||||||
| 	{ | 	{ | ||||||
| 		if (sig.empty()) { | 		if (sig.empty()) { | ||||||
| 			f << "value<0>()"; | 			f << "value<0>()"; | ||||||
| 			return false; | 			return false; | ||||||
| 		} else if (sig.is_chunk()) { | 		} else if (sig.is_chunk()) { | ||||||
| 			return dump_sigchunk(sig.as_chunk(), is_lhs); | 			return dump_sigchunk(sig.as_chunk(), is_lhs, for_debug); | ||||||
| 		} else { | 		} else { | ||||||
| 			dump_sigchunk(*sig.chunks().rbegin(), is_lhs); | 			dump_sigchunk(*sig.chunks().rbegin(), is_lhs, for_debug); | ||||||
| 			for (auto it = sig.chunks().rbegin() + 1; it != sig.chunks().rend(); ++it) { | 			for (auto it = sig.chunks().rbegin() + 1; it != sig.chunks().rend(); ++it) { | ||||||
| 				f << ".concat("; | 				f << ".concat("; | ||||||
| 				dump_sigchunk(*it, is_lhs); | 				dump_sigchunk(*it, is_lhs, for_debug); | ||||||
| 				f << ")"; | 				f << ")"; | ||||||
| 			} | 			} | ||||||
| 			return true; | 			return true; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	void dump_sigspec_lhs(const RTLIL::SigSpec &sig) | 	void dump_sigspec_lhs(const RTLIL::SigSpec &sig, bool for_debug = false) | ||||||
| 	{ | 	{ | ||||||
| 		dump_sigspec(sig, /*is_lhs=*/true); | 		dump_sigspec(sig, /*is_lhs=*/true, for_debug); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	void dump_sigspec_rhs(const RTLIL::SigSpec &sig) | 	void dump_sigspec_rhs(const RTLIL::SigSpec &sig, bool for_debug = false) | ||||||
| 	{ | 	{ | ||||||
| 		// In the contexts where we want template argument deduction to occur for `template<size_t Bits> ... value<Bits>`,
 | 		// In the contexts where we want template argument deduction to occur for `template<size_t Bits> ... value<Bits>`,
 | ||||||
| 		// it is necessary to have the argument to already be a `value<N>`, since template argument deduction and implicit
 | 		// it is necessary to have the argument to already be a `value<N>`, since template argument deduction and implicit
 | ||||||
| 		// type conversion are mutually exclusive. In these contexts, we use dump_sigspec_rhs() to emit an explicit
 | 		// type conversion are mutually exclusive. In these contexts, we use dump_sigspec_rhs() to emit an explicit
 | ||||||
| 		// type conversion, but only if the expression needs it.
 | 		// type conversion, but only if the expression needs it.
 | ||||||
| 		bool is_complex = dump_sigspec(sig, /*is_lhs=*/false); | 		bool is_complex = dump_sigspec(sig, /*is_lhs=*/false, for_debug); | ||||||
| 		if (is_complex) | 		if (is_complex) | ||||||
| 			f << ".val()"; | 			f << ".val()"; | ||||||
| 	} | 	} | ||||||
|  | @ -875,9 +877,9 @@ struct CxxrtlWorker { | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	void dump_connect_expr(const RTLIL::SigSig &conn) | 	void dump_connect_expr(const RTLIL::SigSig &conn, bool for_debug = false) | ||||||
| 	{ | 	{ | ||||||
| 		dump_sigspec_rhs(conn.second); | 		dump_sigspec_rhs(conn.second, for_debug); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	bool is_connect_inlined(const RTLIL::SigSig &conn) | 	bool is_connect_inlined(const RTLIL::SigSig &conn) | ||||||
|  | @ -885,6 +887,14 @@ struct CxxrtlWorker { | ||||||
| 		return conn.first.is_wire() && inlined_wires.count(conn.first.as_wire()); | 		return conn.first.is_wire() && inlined_wires.count(conn.first.as_wire()); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	bool is_connect_outlined(const RTLIL::SigSig &conn) | ||||||
|  | 	{ | ||||||
|  | 		for (auto chunk : conn.first.chunks()) | ||||||
|  | 			if (debug_outlined_wires.count(chunk.wire)) | ||||||
|  | 				return true; | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	void collect_connect(const RTLIL::SigSig &conn, std::vector<RTLIL::IdString> &cells) | 	void collect_connect(const RTLIL::SigSig &conn, std::vector<RTLIL::IdString> &cells) | ||||||
| 	{ | 	{ | ||||||
| 		if (!is_connect_inlined(conn)) | 		if (!is_connect_inlined(conn)) | ||||||
|  | @ -893,16 +903,18 @@ struct CxxrtlWorker { | ||||||
| 		collect_sigspec_rhs(conn.second, cells); | 		collect_sigspec_rhs(conn.second, cells); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	void dump_connect(const RTLIL::SigSig &conn) | 	void dump_connect(const RTLIL::SigSig &conn, bool for_debug = false) | ||||||
| 	{ | 	{ | ||||||
| 		if (is_connect_inlined(conn)) | 		if (!for_debug && is_connect_inlined(conn)) | ||||||
|  | 			return; | ||||||
|  | 		if (for_debug && !is_connect_outlined(conn)) | ||||||
| 			return; | 			return; | ||||||
| 
 | 
 | ||||||
| 		f << indent << "// connection\n"; | 		f << indent << "// connection\n"; | ||||||
| 		f << indent; | 		f << indent; | ||||||
| 		dump_sigspec_lhs(conn.first); | 		dump_sigspec_lhs(conn.first, for_debug); | ||||||
| 		f << " = "; | 		f << " = "; | ||||||
| 		dump_connect_expr(conn); | 		dump_connect_expr(conn, for_debug); | ||||||
| 		f << ";\n"; | 		f << ";\n"; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -919,7 +931,7 @@ struct CxxrtlWorker { | ||||||
| 				} | 				} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	void dump_cell_expr(const RTLIL::Cell *cell) | 	void dump_cell_expr(const RTLIL::Cell *cell, bool for_debug = false) | ||||||
| 	{ | 	{ | ||||||
| 		// Unary cells
 | 		// Unary cells
 | ||||||
| 		if (is_unary_cell(cell->type)) { | 		if (is_unary_cell(cell->type)) { | ||||||
|  | @ -927,7 +939,7 @@ struct CxxrtlWorker { | ||||||
| 			if (is_extending_cell(cell->type)) | 			if (is_extending_cell(cell->type)) | ||||||
| 				f << '_' << (cell->getParam(ID::A_SIGNED).as_bool() ? 's' : 'u'); | 				f << '_' << (cell->getParam(ID::A_SIGNED).as_bool() ? 's' : 'u'); | ||||||
| 			f << "<" << cell->getParam(ID::Y_WIDTH).as_int() << ">("; | 			f << "<" << cell->getParam(ID::Y_WIDTH).as_int() << ">("; | ||||||
| 			dump_sigspec_rhs(cell->getPort(ID::A)); | 			dump_sigspec_rhs(cell->getPort(ID::A), for_debug); | ||||||
| 			f << ")"; | 			f << ")"; | ||||||
| 		// Binary cells
 | 		// Binary cells
 | ||||||
| 		} else if (is_binary_cell(cell->type)) { | 		} else if (is_binary_cell(cell->type)) { | ||||||
|  | @ -936,18 +948,18 @@ struct CxxrtlWorker { | ||||||
| 				f << '_' << (cell->getParam(ID::A_SIGNED).as_bool() ? 's' : 'u') << | 				f << '_' << (cell->getParam(ID::A_SIGNED).as_bool() ? 's' : 'u') << | ||||||
| 				            (cell->getParam(ID::B_SIGNED).as_bool() ? 's' : 'u'); | 				            (cell->getParam(ID::B_SIGNED).as_bool() ? 's' : 'u'); | ||||||
| 			f << "<" << cell->getParam(ID::Y_WIDTH).as_int() << ">("; | 			f << "<" << cell->getParam(ID::Y_WIDTH).as_int() << ">("; | ||||||
| 			dump_sigspec_rhs(cell->getPort(ID::A)); | 			dump_sigspec_rhs(cell->getPort(ID::A), for_debug); | ||||||
| 			f << ", "; | 			f << ", "; | ||||||
| 			dump_sigspec_rhs(cell->getPort(ID::B)); | 			dump_sigspec_rhs(cell->getPort(ID::B), for_debug); | ||||||
| 			f << ")"; | 			f << ")"; | ||||||
| 		// Muxes
 | 		// Muxes
 | ||||||
| 		} else if (cell->type == ID($mux)) { | 		} else if (cell->type == ID($mux)) { | ||||||
| 			f << "("; | 			f << "("; | ||||||
| 			dump_sigspec_rhs(cell->getPort(ID::S)); | 			dump_sigspec_rhs(cell->getPort(ID::S), for_debug); | ||||||
| 			f << " ? "; | 			f << " ? "; | ||||||
| 			dump_sigspec_rhs(cell->getPort(ID::B)); | 			dump_sigspec_rhs(cell->getPort(ID::B), for_debug); | ||||||
| 			f << " : "; | 			f << " : "; | ||||||
| 			dump_sigspec_rhs(cell->getPort(ID::A)); | 			dump_sigspec_rhs(cell->getPort(ID::A), for_debug); | ||||||
| 			f << ")"; | 			f << ")"; | ||||||
| 		// Parallel (one-hot) muxes
 | 		// Parallel (one-hot) muxes
 | ||||||
| 		} else if (cell->type == ID($pmux)) { | 		} else if (cell->type == ID($pmux)) { | ||||||
|  | @ -955,24 +967,24 @@ struct CxxrtlWorker { | ||||||
| 			int s_width = cell->getParam(ID::S_WIDTH).as_int(); | 			int s_width = cell->getParam(ID::S_WIDTH).as_int(); | ||||||
| 			for (int part = 0; part < s_width; part++) { | 			for (int part = 0; part < s_width; part++) { | ||||||
| 				f << "("; | 				f << "("; | ||||||
| 				dump_sigspec_rhs(cell->getPort(ID::S).extract(part)); | 				dump_sigspec_rhs(cell->getPort(ID::S).extract(part), for_debug); | ||||||
| 				f << " ? "; | 				f << " ? "; | ||||||
| 				dump_sigspec_rhs(cell->getPort(ID::B).extract(part * width, width)); | 				dump_sigspec_rhs(cell->getPort(ID::B).extract(part * width, width), for_debug); | ||||||
| 				f << " : "; | 				f << " : "; | ||||||
| 			} | 			} | ||||||
| 			dump_sigspec_rhs(cell->getPort(ID::A)); | 			dump_sigspec_rhs(cell->getPort(ID::A), for_debug); | ||||||
| 			for (int part = 0; part < s_width; part++) { | 			for (int part = 0; part < s_width; part++) { | ||||||
| 				f << ")"; | 				f << ")"; | ||||||
| 			} | 			} | ||||||
| 		// Concats
 | 		// Concats
 | ||||||
| 		} else if (cell->type == ID($concat)) { | 		} else if (cell->type == ID($concat)) { | ||||||
| 			dump_sigspec_rhs(cell->getPort(ID::B)); | 			dump_sigspec_rhs(cell->getPort(ID::B), for_debug); | ||||||
| 			f << ".concat("; | 			f << ".concat("; | ||||||
| 			dump_sigspec_rhs(cell->getPort(ID::A)); | 			dump_sigspec_rhs(cell->getPort(ID::A), for_debug); | ||||||
| 			f << ").val()"; | 			f << ").val()"; | ||||||
| 		// Slices
 | 		// Slices
 | ||||||
| 		} else if (cell->type == ID($slice)) { | 		} else if (cell->type == ID($slice)) { | ||||||
| 			dump_sigspec_rhs(cell->getPort(ID::A)); | 			dump_sigspec_rhs(cell->getPort(ID::A), for_debug); | ||||||
| 			f << ".slice<"; | 			f << ".slice<"; | ||||||
| 			f << cell->getParam(ID::OFFSET).as_int() + cell->getParam(ID::Y_WIDTH).as_int() - 1; | 			f << cell->getParam(ID::OFFSET).as_int() + cell->getParam(ID::Y_WIDTH).as_int() - 1; | ||||||
| 			f << ","; | 			f << ","; | ||||||
|  | @ -989,6 +1001,17 @@ struct CxxrtlWorker { | ||||||
| 			inlined_wires.count(cell->getPort(ID::Y).as_wire()); | 			inlined_wires.count(cell->getPort(ID::Y).as_wire()); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	bool is_cell_outlined(const RTLIL::Cell *cell) | ||||||
|  | 	{ | ||||||
|  | 		if (is_internal_cell(cell->type)) | ||||||
|  | 			for (auto conn : cell->connections()) | ||||||
|  | 				if (cell->output(conn.first)) | ||||||
|  | 					for (auto chunk : conn.second.chunks()) | ||||||
|  | 						if (debug_outlined_wires.count(chunk.wire)) | ||||||
|  | 							return true; | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	void collect_cell_eval(const RTLIL::Cell *cell, std::vector<RTLIL::IdString> &cells) | 	void collect_cell_eval(const RTLIL::Cell *cell, std::vector<RTLIL::IdString> &cells) | ||||||
| 	{ | 	{ | ||||||
| 		if (!is_cell_inlined(cell)) | 		if (!is_cell_inlined(cell)) | ||||||
|  | @ -1000,9 +1023,11 @@ struct CxxrtlWorker { | ||||||
| 				collect_sigspec_rhs(port.second, cells); | 				collect_sigspec_rhs(port.second, cells); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	void dump_cell_eval(const RTLIL::Cell *cell) | 	void dump_cell_eval(const RTLIL::Cell *cell, bool for_debug = false) | ||||||
| 	{ | 	{ | ||||||
| 		if (is_cell_inlined(cell)) | 		if (!for_debug && is_cell_inlined(cell)) | ||||||
|  | 			return; | ||||||
|  | 		if (for_debug && !is_cell_outlined(cell)) | ||||||
| 			return; | 			return; | ||||||
| 		if (cell->type == ID($meminit)) | 		if (cell->type == ID($meminit)) | ||||||
| 			return; // Handled elsewhere.
 | 			return; // Handled elsewhere.
 | ||||||
|  | @ -1026,9 +1051,9 @@ struct CxxrtlWorker { | ||||||
| 		// Elidable cells
 | 		// Elidable cells
 | ||||||
| 		if (is_inlinable_cell(cell->type)) { | 		if (is_inlinable_cell(cell->type)) { | ||||||
| 			f << indent; | 			f << indent; | ||||||
| 			dump_sigspec_lhs(cell->getPort(ID::Y)); | 			dump_sigspec_lhs(cell->getPort(ID::Y), for_debug); | ||||||
| 			f << " = "; | 			f << " = "; | ||||||
| 			dump_cell_expr(cell); | 			dump_cell_expr(cell, for_debug); | ||||||
| 			f << ";\n"; | 			f << ";\n"; | ||||||
| 		// Flip-flops
 | 		// Flip-flops
 | ||||||
| 		} else if (is_ff_cell(cell->type)) { | 		} else if (is_ff_cell(cell->type)) { | ||||||
|  | @ -1460,14 +1485,11 @@ struct CxxrtlWorker { | ||||||
| 
 | 
 | ||||||
| 	void dump_wire(const RTLIL::Wire *wire, bool is_local) | 	void dump_wire(const RTLIL::Wire *wire, bool is_local) | ||||||
| 	{ | 	{ | ||||||
| 		if (inlined_wires.count(wire)) | 		if (is_local && localized_wires[wire] && !inlined_wires.count(wire)) { | ||||||
| 			return; |  | ||||||
| 
 |  | ||||||
| 		if (localized_wires[wire] && is_local) { |  | ||||||
| 			dump_attrs(wire); | 			dump_attrs(wire); | ||||||
| 			f << indent << "value<" << wire->width << "> " << mangle(wire) << ";\n"; | 			f << indent << "value<" << wire->width << "> " << mangle(wire) << ";\n"; | ||||||
| 		} | 		} | ||||||
| 		if (!localized_wires[wire] && !is_local) { | 		if (!is_local && !localized_wires[wire]) { | ||||||
| 			std::string width; | 			std::string width; | ||||||
| 			if (wire->module->has_attribute(ID(cxxrtl_blackbox)) && wire->has_attribute(ID(cxxrtl_width))) { | 			if (wire->module->has_attribute(ID(cxxrtl_blackbox)) && wire->has_attribute(ID(cxxrtl_width))) { | ||||||
| 				width = wire->get_string_attribute(ID(cxxrtl_width)); | 				width = wire->get_string_attribute(ID(cxxrtl_width)); | ||||||
|  | @ -1530,6 +1552,23 @@ struct CxxrtlWorker { | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	void dump_debug_wire(const RTLIL::Wire *wire, bool is_local) | ||||||
|  | 	{ | ||||||
|  | 		if (!debug_outlined_wires[wire]) | ||||||
|  | 			return; | ||||||
|  | 
 | ||||||
|  | 		bool is_outlined_member = wire->name.isPublic() && | ||||||
|  | 			!(debug_const_wires.count(wire) || debug_alias_wires.count(wire)); | ||||||
|  | 		if (is_local && !is_outlined_member) { | ||||||
|  | 			dump_attrs(wire); | ||||||
|  | 			f << indent << "value<" << wire->width << "> " << mangle(wire) << ";\n"; | ||||||
|  | 		} | ||||||
|  | 		if (!is_local && is_outlined_member) { | ||||||
|  | 			dump_attrs(wire); | ||||||
|  | 			f << indent << "/*outline*/ value<" << wire->width << "> " << mangle(wire) << ";\n"; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	void dump_memory(RTLIL::Module *module, const RTLIL::Memory *memory) | 	void dump_memory(RTLIL::Module *module, const RTLIL::Memory *memory) | ||||||
| 	{ | 	{ | ||||||
| 		vector<const RTLIL::Cell*> init_cells; | 		vector<const RTLIL::Cell*> init_cells; | ||||||
|  | @ -1619,6 +1658,27 @@ struct CxxrtlWorker { | ||||||
| 		dec_indent(); | 		dec_indent(); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	void dump_debug_eval_method(RTLIL::Module *module) | ||||||
|  | 	{ | ||||||
|  | 		inc_indent(); | ||||||
|  | 			for (auto wire : module->wires()) | ||||||
|  | 				dump_debug_wire(wire, /*is_local=*/true); | ||||||
|  | 			for (auto node : schedule[module]) { | ||||||
|  | 				switch (node.type) { | ||||||
|  | 					case FlowGraph::Node::Type::CONNECT: | ||||||
|  | 						dump_connect(node.connect, /*for_debug=*/true); | ||||||
|  | 						break; | ||||||
|  | 					case FlowGraph::Node::Type::CELL_EVAL: | ||||||
|  | 						dump_cell_eval(node.cell, /*for_debug=*/true); | ||||||
|  | 						break; | ||||||
|  | 					case FlowGraph::Node::Type::CELL_SYNC: | ||||||
|  | 					case FlowGraph::Node::Type::PROCESS: | ||||||
|  | 						break; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		dec_indent(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	void dump_commit_method(RTLIL::Module *module) | 	void dump_commit_method(RTLIL::Module *module) | ||||||
| 	{ | 	{ | ||||||
| 		inc_indent(); | 		inc_indent(); | ||||||
|  | @ -1656,6 +1716,7 @@ struct CxxrtlWorker { | ||||||
| 		size_t count_public_wires = 0; | 		size_t count_public_wires = 0; | ||||||
| 		size_t count_const_wires = 0; | 		size_t count_const_wires = 0; | ||||||
| 		size_t count_alias_wires = 0; | 		size_t count_alias_wires = 0; | ||||||
|  | 		size_t count_inline_wires = 0; | ||||||
| 		size_t count_member_wires = 0; | 		size_t count_member_wires = 0; | ||||||
| 		size_t count_skipped_wires = 0; | 		size_t count_skipped_wires = 0; | ||||||
| 		size_t count_driven_sync = 0; | 		size_t count_driven_sync = 0; | ||||||
|  | @ -1685,6 +1746,12 @@ struct CxxrtlWorker { | ||||||
| 					f << ", debug_item(debug_alias(), " << mangle(debug_alias_wires[wire]) << ", "; | 					f << ", debug_item(debug_alias(), " << mangle(debug_alias_wires[wire]) << ", "; | ||||||
| 					f << wire->start_offset << "));\n"; | 					f << wire->start_offset << "));\n"; | ||||||
| 					count_alias_wires++; | 					count_alias_wires++; | ||||||
|  | 				} else if (debug_outlined_wires.count(wire)) { | ||||||
|  | 					// Inlined but rematerializable wire
 | ||||||
|  | 					f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(wire)); | ||||||
|  | 					f << ", debug_item(debug_eval_outline, " << mangle(wire) << ", "; | ||||||
|  | 					f << wire->start_offset << "));\n"; | ||||||
|  | 					count_inline_wires++; | ||||||
| 				} else if (!localized_wires.count(wire)) { | 				} else if (!localized_wires.count(wire)) { | ||||||
| 					// Member wire
 | 					// Member wire
 | ||||||
| 					std::vector<std::string> flags; | 					std::vector<std::string> flags; | ||||||
|  | @ -1738,6 +1805,7 @@ struct CxxrtlWorker { | ||||||
| 					f << "));\n"; | 					f << "));\n"; | ||||||
| 					count_member_wires++; | 					count_member_wires++; | ||||||
| 				} else { | 				} else { | ||||||
|  | 					// Localized or inlined wire with no debug information
 | ||||||
| 					count_skipped_wires++; | 					count_skipped_wires++; | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  | @ -1761,14 +1829,16 @@ struct CxxrtlWorker { | ||||||
| 
 | 
 | ||||||
| 		log_debug("Debug information statistics for module `%s':\n", log_id(module)); | 		log_debug("Debug information statistics for module `%s':\n", log_id(module)); | ||||||
| 		log_debug("  Public wires: %zu, of which:\n", count_public_wires); | 		log_debug("  Public wires: %zu, of which:\n", count_public_wires); | ||||||
| 		log_debug("    Const wires:  %zu\n", count_const_wires); |  | ||||||
| 		log_debug("    Alias wires:  %zu\n", count_alias_wires); |  | ||||||
| 		log_debug("    Member wires: %zu, of which:\n", count_member_wires); | 		log_debug("    Member wires: %zu, of which:\n", count_member_wires); | ||||||
| 		log_debug("      Driven sync:  %zu\n", count_driven_sync); | 		log_debug("      Driven sync:  %zu\n", count_driven_sync); | ||||||
| 		log_debug("      Driven comb:  %zu\n", count_driven_comb); | 		log_debug("      Driven comb:  %zu\n", count_driven_comb); | ||||||
| 		log_debug("      Undriven:     %zu\n", count_undriven); |  | ||||||
| 		log_debug("      Mixed driver: %zu\n", count_mixed_driver); | 		log_debug("      Mixed driver: %zu\n", count_mixed_driver); | ||||||
| 		log_debug("    Other wires:  %zu (no debug information)\n", count_skipped_wires); | 		log_debug("      Undriven:     %zu\n", count_undriven); | ||||||
|  | 		log_debug("    Inline wires:   %zu\n", count_inline_wires); | ||||||
|  | 		log_debug("    Alias wires:    %zu\n", count_alias_wires); | ||||||
|  | 		log_debug("    Const wires:    %zu\n", count_const_wires); | ||||||
|  | 		log_debug("    Other wires:    %zu%s\n", count_skipped_wires, | ||||||
|  | 		          count_skipped_wires > 0 ? " (debug information unavailable)" : ""); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	void dump_metadata_map(const dict<RTLIL::IdString, RTLIL::Const> &metadata_map) | 	void dump_metadata_map(const dict<RTLIL::IdString, RTLIL::Const> &metadata_map) | ||||||
|  | @ -1855,7 +1925,8 @@ struct CxxrtlWorker { | ||||||
| 			inc_indent(); | 			inc_indent(); | ||||||
| 				for (auto wire : module->wires()) | 				for (auto wire : module->wires()) | ||||||
| 					dump_wire(wire, /*is_local=*/false); | 					dump_wire(wire, /*is_local=*/false); | ||||||
| 				f << "\n"; | 				for (auto wire : module->wires()) | ||||||
|  | 					dump_debug_wire(wire, /*is_local=*/false); | ||||||
| 				bool has_memories = false; | 				bool has_memories = false; | ||||||
| 				for (auto memory : module->memories) { | 				for (auto memory : module->memories) { | ||||||
| 					dump_memory(module, memory.second); | 					dump_memory(module, memory.second); | ||||||
|  | @ -1927,8 +1998,20 @@ struct CxxrtlWorker { | ||||||
| 				f << "\n"; | 				f << "\n"; | ||||||
| 				f << indent << "bool eval() override;\n"; | 				f << indent << "bool eval() override;\n"; | ||||||
| 				f << indent << "bool commit() override;\n"; | 				f << indent << "bool commit() override;\n"; | ||||||
| 				if (debug_info) | 				if (debug_info) { | ||||||
|  | 					if (debug_eval) { | ||||||
|  | 						f << "\n"; | ||||||
|  | 						f << indent << "void debug_eval();\n"; | ||||||
|  | 						for (auto wire : module->wires()) | ||||||
|  | 							if (debug_outlined_wires.count(wire)) { | ||||||
|  | 								f << indent << "debug_outline debug_eval_outline { std::bind(&" | ||||||
|  | 								            << mangle(module) << "::debug_eval, this) };\n"; | ||||||
|  | 								break; | ||||||
|  | 							} | ||||||
|  | 					} | ||||||
|  | 					f << "\n"; | ||||||
| 					f << indent << "void debug_info(debug_items &items, std::string path = \"\") override;\n"; | 					f << indent << "void debug_info(debug_items &items, std::string path = \"\") override;\n"; | ||||||
|  | 				} | ||||||
| 			dec_indent(); | 			dec_indent(); | ||||||
| 			f << indent << "}; // struct " << mangle(module) << "\n"; | 			f << indent << "}; // struct " << mangle(module) << "\n"; | ||||||
| 			f << "\n"; | 			f << "\n"; | ||||||
|  | @ -1948,6 +2031,12 @@ struct CxxrtlWorker { | ||||||
| 		f << indent << "}\n"; | 		f << indent << "}\n"; | ||||||
| 		f << "\n"; | 		f << "\n"; | ||||||
| 		if (debug_info) { | 		if (debug_info) { | ||||||
|  | 			if (debug_eval) { | ||||||
|  | 				f << indent << "void " << mangle(module) << "::debug_eval() {\n"; | ||||||
|  | 				dump_debug_eval_method(module); | ||||||
|  | 				f << indent << "}\n"; | ||||||
|  | 				f << "\n"; | ||||||
|  | 			} | ||||||
| 			f << indent << "void " << mangle(module) << "::debug_info(debug_items &items, std::string path) {\n"; | 			f << indent << "void " << mangle(module) << "::debug_info(debug_items &items, std::string path) {\n"; | ||||||
| 			dump_debug_info_method(module); | 			dump_debug_info_method(module); | ||||||
| 			f << indent << "}\n"; | 			f << indent << "}\n"; | ||||||
|  | @ -2251,6 +2340,11 @@ struct CxxrtlWorker { | ||||||
| 				for (auto node : wire_comb_def.second) | 				for (auto node : wire_comb_def.second) | ||||||
| 					node_defs[node].insert(wire_comb_def.first); | 					node_defs[node].insert(wire_comb_def.first); | ||||||
| 
 | 
 | ||||||
|  | 			dict<FlowGraph::Node*, pool<const RTLIL::Wire*>, hash_ptr_ops> node_uses; | ||||||
|  | 			for (auto wire_use : flow.wire_uses) | ||||||
|  | 				for (auto node : wire_use.second) | ||||||
|  | 					node_uses[node].insert(wire_use.first); | ||||||
|  | 
 | ||||||
| 			Scheduler<FlowGraph::Node> scheduler; | 			Scheduler<FlowGraph::Node> scheduler; | ||||||
| 			dict<FlowGraph::Node*, Scheduler<FlowGraph::Node>::Vertex*, hash_ptr_ops> node_map; | 			dict<FlowGraph::Node*, Scheduler<FlowGraph::Node>::Vertex*, hash_ptr_ops> node_map; | ||||||
| 			for (auto node : flow.nodes) | 			for (auto node : flow.nodes) | ||||||
|  | @ -2368,6 +2462,30 @@ struct CxxrtlWorker { | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  | 			if (debug_info && debug_eval) { | ||||||
|  | 				// Find wires that can be be outlined, i.e. whose values can be always recovered from
 | ||||||
|  | 				// the values of other wires. (This is the inverse of inlining--any wire that can be
 | ||||||
|  | 				// inlined can also be outlined.) Although this may seem strictly less efficient, since
 | ||||||
|  | 				// such values are computed at least twice, second-order effects make outlining useful.
 | ||||||
|  | 				pool<const RTLIL::Wire*> worklist, visited; | ||||||
|  | 				for (auto wire : module->wires()) { | ||||||
|  | 					if (!wire->name.isPublic()) | ||||||
|  | 						continue; // only outline public wires
 | ||||||
|  | 					worklist.insert(wire); | ||||||
|  | 				} | ||||||
|  | 				while (!worklist.empty()) { | ||||||
|  | 					const RTLIL::Wire *wire = worklist.pop(); | ||||||
|  | 					visited.insert(wire); | ||||||
|  | 					if (!localized_wires.count(wire) && !inlined_wires.count(wire)) | ||||||
|  | 						continue; // member wire, doesn't need outlining
 | ||||||
|  | 					if (wire->name.isPublic() || !inlined_wires.count(wire)) | ||||||
|  | 						debug_outlined_wires.insert(wire); // allow outlining of internal wires only
 | ||||||
|  | 					for (auto node : flow.wire_comb_defs[wire]) | ||||||
|  | 						for (auto node_use : node_uses[node]) | ||||||
|  | 							if (!visited.count(node_use)) | ||||||
|  | 								worklist.insert(node_use); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 		if (has_feedback_arcs || has_buffered_comb_wires) { | 		if (has_feedback_arcs || has_buffered_comb_wires) { | ||||||
| 			// Although both non-feedback buffered combinatorial wires and apparent feedback wires may be eliminated
 | 			// Although both non-feedback buffered combinatorial wires and apparent feedback wires may be eliminated
 | ||||||
|  | @ -2457,8 +2575,7 @@ struct CxxrtlWorker { | ||||||
| 
 | 
 | ||||||
| struct CxxrtlBackend : public Backend { | struct CxxrtlBackend : public Backend { | ||||||
| 	static const int DEFAULT_OPT_LEVEL = 6; | 	static const int DEFAULT_OPT_LEVEL = 6; | ||||||
| 	static const int OPT_LEVEL_DEBUG = 4; | 	static const int DEFAULT_DEBUG_LEVEL = 2; | ||||||
| 	static const int DEFAULT_DEBUG_LEVEL = 1; |  | ||||||
| 
 | 
 | ||||||
| 	CxxrtlBackend() : Backend("cxxrtl", "convert design to C++ RTL simulation") { } | 	CxxrtlBackend() : Backend("cxxrtl", "convert design to C++ RTL simulation") { } | ||||||
| 	void help() override | 	void help() override | ||||||
|  | @ -2671,10 +2788,6 @@ struct CxxrtlBackend : public Backend { | ||||||
| 		log("    -O6\n"); | 		log("    -O6\n"); | ||||||
| 		log("        like -O5, and inline public wires not marked (*keep*) if possible.\n"); | 		log("        like -O5, and inline public wires not marked (*keep*) if possible.\n"); | ||||||
| 		log("\n"); | 		log("\n"); | ||||||
| 		log("    -Og\n"); |  | ||||||
| 		log("        highest optimization level that provides debug information for all\n"); |  | ||||||
| 		log("        public wires. currently, alias for -O%d.\n", OPT_LEVEL_DEBUG); |  | ||||||
| 		log("\n"); |  | ||||||
| 		log("    -g <level>\n"); | 		log("    -g <level>\n"); | ||||||
| 		log("        set the debug level. the default is -g%d. higher debug levels provide\n", DEFAULT_DEBUG_LEVEL); | 		log("        set the debug level. the default is -g%d. higher debug levels provide\n", DEFAULT_DEBUG_LEVEL); | ||||||
| 		log("        more visibility and generate more code, but do not pessimize evaluation.\n"); | 		log("        more visibility and generate more code, but do not pessimize evaluation.\n"); | ||||||
|  | @ -2686,6 +2799,10 @@ struct CxxrtlBackend : public Backend { | ||||||
| 		log("        debug information for non-optimized public wires. this also makes it\n"); | 		log("        debug information for non-optimized public wires. this also makes it\n"); | ||||||
| 		log("        possible to use the C API.\n"); | 		log("        possible to use the C API.\n"); | ||||||
| 		log("\n"); | 		log("\n"); | ||||||
|  | 		log("    -g2\n"); | ||||||
|  | 		log("        like -g1, and compute debug information on demand for all public wires\n"); | ||||||
|  | 		log("        that were optimized out.\n"); | ||||||
|  | 		log("\n"); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override | 	void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override | ||||||
|  | @ -2715,12 +2832,14 @@ struct CxxrtlBackend : public Backend { | ||||||
| 				continue; | 				continue; | ||||||
| 			} | 			} | ||||||
| 			if (args[argidx] == "-Og") { | 			if (args[argidx] == "-Og") { | ||||||
| 				opt_level = OPT_LEVEL_DEBUG; | 				log_warning("The `-Og` option has been removed. Use `-g2` instead for complete " | ||||||
|  | 				            "design coverage regardless of optimization level.\n"); | ||||||
| 				continue; | 				continue; | ||||||
| 			} | 			} | ||||||
| 			if (args[argidx] == "-O" && argidx+1 < args.size() && args[argidx+1] == "g") { | 			if (args[argidx] == "-O" && argidx+1 < args.size() && args[argidx+1] == "g") { | ||||||
| 				argidx++; | 				argidx++; | ||||||
| 				opt_level = OPT_LEVEL_DEBUG; | 				log_warning("The `-Og` option has been removed. Use `-g2` instead for complete " | ||||||
|  | 				            "design coverage regardless of optimization level.\n"); | ||||||
| 				continue; | 				continue; | ||||||
| 			} | 			} | ||||||
| 			if (args[argidx] == "-O" && argidx+1 < args.size()) { | 			if (args[argidx] == "-O" && argidx+1 < args.size()) { | ||||||
|  | @ -2781,6 +2900,9 @@ struct CxxrtlBackend : public Backend { | ||||||
| 		} | 		} | ||||||
| 		switch (debug_level) { | 		switch (debug_level) { | ||||||
| 			// the highest level here must match DEFAULT_DEBUG_LEVEL
 | 			// the highest level here must match DEFAULT_DEBUG_LEVEL
 | ||||||
|  | 			case 2: | ||||||
|  | 				worker.debug_eval = true; | ||||||
|  | 				YS_FALLTHROUGH | ||||||
| 			case 1: | 			case 1: | ||||||
| 				worker.debug_info = true; | 				worker.debug_info = true; | ||||||
| 				YS_FALLTHROUGH | 				YS_FALLTHROUGH | ||||||
|  |  | ||||||
|  | @ -86,3 +86,7 @@ void cxxrtl_enum(cxxrtl_handle handle, void *data, | ||||||
| 	for (auto &it : handle->objects.table) | 	for (auto &it : handle->objects.table) | ||||||
| 		callback(data, it.first.c_str(), static_cast<cxxrtl_object*>(&it.second[0]), it.second.size()); | 		callback(data, it.first.c_str(), static_cast<cxxrtl_object*>(&it.second[0]), it.second.size()); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | void cxxrtl_outline_eval(cxxrtl_outline outline) { | ||||||
|  | 	outline->eval(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -128,6 +128,18 @@ enum cxxrtl_type { | ||||||
| 	// pointer is always NULL.
 | 	// pointer is always NULL.
 | ||||||
| 	CXXRTL_ALIAS = 3, | 	CXXRTL_ALIAS = 3, | ||||||
| 
 | 
 | ||||||
|  | 	// Outlines correspond to netlist nodes that were optimized in a way that makes them inaccessible
 | ||||||
|  | 	// outside of a module's `eval()` function. At the highest debug information level, every inlined
 | ||||||
|  | 	// node has a corresponding outline object.
 | ||||||
|  | 	//
 | ||||||
|  | 	// Outlines can be inspected via the `curr` pointer and can never be modified; the `next` pointer
 | ||||||
|  | 	// is always NULL. Unlike all other objects, the bits of an outline object are meaningful only
 | ||||||
|  | 	// after a call to `cxxrtl_outline_eval` and until any subsequent modification to the netlist.
 | ||||||
|  | 	// Observing this requirement is the responsibility of the caller; it is not enforced.
 | ||||||
|  | 	//
 | ||||||
|  | 	// Outlines always correspond to combinatorial netlist nodes that are not ports.
 | ||||||
|  | 	CXXRTL_OUTLINE = 4, | ||||||
|  | 
 | ||||||
| 	// More object types may be added in the future, but the existing ones will never change.
 | 	// More object types may be added in the future, but the existing ones will never change.
 | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | @ -171,8 +183,8 @@ enum cxxrtl_flag { | ||||||
| 
 | 
 | ||||||
| 	// Node has bits that are driven by a combinatorial cell or another node.
 | 	// Node has bits that are driven by a combinatorial cell or another node.
 | ||||||
| 	//
 | 	//
 | ||||||
| 	// This flag can be set on objects of type `CXXRTL_VALUE` and `CXXRTL_WIRE`. It may be combined
 | 	// This flag can be set on objects of type `CXXRTL_VALUE`, `CXXRTL_WIRE`, and `CXXRTL_OUTLINE`.
 | ||||||
| 	// with `CXXRTL_DRIVEN_SYNC` and `CXXRTL_UNDRIVEN`, as well as other flags.
 | 	// It may be combined with `CXXRTL_DRIVEN_SYNC` and `CXXRTL_UNDRIVEN`, as well as other flags.
 | ||||||
| 	//
 | 	//
 | ||||||
| 	// This flag is set on objects that have bits connected to the output of a combinatorial cell,
 | 	// This flag is set on objects that have bits connected to the output of a combinatorial cell,
 | ||||||
| 	// or directly to another node. For designs without combinatorial loops, writing to such bits
 | 	// or directly to another node. For designs without combinatorial loops, writing to such bits
 | ||||||
|  | @ -193,8 +205,8 @@ enum cxxrtl_flag { | ||||||
| 
 | 
 | ||||||
| // Description of a simulated object.
 | // Description of a simulated object.
 | ||||||
| //
 | //
 | ||||||
| // The `data` array can be accessed directly to inspect and, if applicable, modify the bits
 | // The `curr` and `next` arrays can be accessed directly to inspect and, if applicable, modify
 | ||||||
| // stored in the object.
 | // the bits stored in the object.
 | ||||||
| struct cxxrtl_object { | struct cxxrtl_object { | ||||||
| 	// Type of the object.
 | 	// Type of the object.
 | ||||||
| 	//
 | 	//
 | ||||||
|  | @ -231,6 +243,12 @@ struct cxxrtl_object { | ||||||
| 	uint32_t *curr; | 	uint32_t *curr; | ||||||
| 	uint32_t *next; | 	uint32_t *next; | ||||||
| 
 | 
 | ||||||
|  | 	// Opaque reference to an outline. Only meaningful for outline objects.
 | ||||||
|  | 	//
 | ||||||
|  | 	// See the documentation of `cxxrtl_outline` for details. When creating a `cxxrtl_object`, set
 | ||||||
|  | 	// this field to NULL.
 | ||||||
|  | 	struct _cxxrtl_outline *outline; | ||||||
|  | 
 | ||||||
| 	// More description fields may be added in the future, but the existing ones will never change.
 | 	// More description fields may be added in the future, but the existing ones will never change.
 | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | @ -272,6 +290,20 @@ void cxxrtl_enum(cxxrtl_handle handle, void *data, | ||||||
|                  void (*callback)(void *data, const char *name, |                  void (*callback)(void *data, const char *name, | ||||||
|                                   struct cxxrtl_object *object, size_t parts)); |                                   struct cxxrtl_object *object, size_t parts)); | ||||||
| 
 | 
 | ||||||
|  | // Opaque reference to an outline.
 | ||||||
|  | //
 | ||||||
|  | // An outline is a group of outline objects that are evaluated simultaneously. The identity of
 | ||||||
|  | // an outline can be compared to determine whether any two objects belong to the same outline.
 | ||||||
|  | typedef struct _cxxrtl_outline *cxxrtl_outline; | ||||||
|  | 
 | ||||||
|  | // Evaluate an outline.
 | ||||||
|  | //
 | ||||||
|  | // After evaluating an outline, the bits of every outline object contained in it are consistent
 | ||||||
|  | // with the current state of the netlist. In general, any further modification to the netlist
 | ||||||
|  | // causes every outline object to become stale, after which the corresponding outline must be
 | ||||||
|  | // re-evaluated, otherwise the bits read from that object are meaningless.
 | ||||||
|  | void cxxrtl_outline_eval(cxxrtl_outline outline); | ||||||
|  | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | @ -28,10 +28,13 @@ class vcd_writer { | ||||||
| 		size_t ident; | 		size_t ident; | ||||||
| 		size_t width; | 		size_t width; | ||||||
| 		chunk_t *curr; | 		chunk_t *curr; | ||||||
| 		size_t prev_off; | 		size_t cache_offset; | ||||||
|  | 		debug_outline *outline; | ||||||
|  | 		bool *outline_warm; | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	std::vector<std::string> current_scope; | 	std::vector<std::string> current_scope; | ||||||
|  | 	std::map<debug_outline*, bool> outlines; | ||||||
| 	std::vector<variable> variables; | 	std::vector<variable> variables; | ||||||
| 	std::vector<chunk_t> cache; | 	std::vector<chunk_t> cache; | ||||||
| 	std::map<chunk_t*, size_t> aliases; | 	std::map<chunk_t*, size_t> aliases; | ||||||
|  | @ -112,16 +115,22 @@ class vcd_writer { | ||||||
| 		buffer += '\n'; | 		buffer += '\n'; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	const variable ®ister_variable(size_t width, chunk_t *curr, bool constant = false) { | 	void reset_outlines() { | ||||||
|  | 		for (auto &outline_it : outlines) | ||||||
|  | 			outline_it.second = /*warm=*/(outline_it.first == nullptr); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	variable ®ister_variable(size_t width, chunk_t *curr, bool constant = false, debug_outline *outline = nullptr) { | ||||||
| 		if (aliases.count(curr)) { | 		if (aliases.count(curr)) { | ||||||
| 			return variables[aliases[curr]]; | 			return variables[aliases[curr]]; | ||||||
| 		} else { | 		} else { | ||||||
|  | 			auto outline_it = outlines.emplace(outline, /*warm=*/(outline == nullptr)).first; | ||||||
| 			const size_t chunks = (width + (sizeof(chunk_t) * 8 - 1)) / (sizeof(chunk_t) * 8); | 			const size_t chunks = (width + (sizeof(chunk_t) * 8 - 1)) / (sizeof(chunk_t) * 8); | ||||||
| 			aliases[curr] = variables.size(); | 			aliases[curr] = variables.size(); | ||||||
| 			if (constant) { | 			if (constant) { | ||||||
| 				variables.emplace_back(variable { variables.size(), width, curr, (size_t)-1 }); | 				variables.emplace_back(variable { variables.size(), width, curr, (size_t)-1, outline_it->first, &outline_it->second }); | ||||||
| 			} else { | 			} else { | ||||||
| 				variables.emplace_back(variable { variables.size(), width, curr, cache.size() }); | 				variables.emplace_back(variable { variables.size(), width, curr, cache.size(), outline_it->first, &outline_it->second }); | ||||||
| 				cache.insert(cache.end(), &curr[0], &curr[chunks]); | 				cache.insert(cache.end(), &curr[0], &curr[chunks]); | ||||||
| 			} | 			} | ||||||
| 			return variables.back(); | 			return variables.back(); | ||||||
|  | @ -129,13 +138,17 @@ class vcd_writer { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	bool test_variable(const variable &var) { | 	bool test_variable(const variable &var) { | ||||||
| 		if (var.prev_off == (size_t)-1) | 		if (var.cache_offset == (size_t)-1) | ||||||
| 			return false; // constant
 | 			return false; // constant
 | ||||||
|  | 		if (!*var.outline_warm) { | ||||||
|  | 			var.outline->eval(); | ||||||
|  | 			*var.outline_warm = true; | ||||||
|  | 		} | ||||||
| 		const size_t chunks = (var.width + (sizeof(chunk_t) * 8 - 1)) / (sizeof(chunk_t) * 8); | 		const size_t chunks = (var.width + (sizeof(chunk_t) * 8 - 1)) / (sizeof(chunk_t) * 8); | ||||||
| 		if (std::equal(&var.curr[0], &var.curr[chunks], &cache[var.prev_off])) { | 		if (std::equal(&var.curr[0], &var.curr[chunks], &cache[var.cache_offset])) { | ||||||
| 			return false; | 			return false; | ||||||
| 		} else { | 		} else { | ||||||
| 			std::copy(&var.curr[0], &var.curr[chunks], &cache[var.prev_off]); | 			std::copy(&var.curr[0], &var.curr[chunks], &cache[var.cache_offset]); | ||||||
| 			return true; | 			return true; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | @ -197,6 +210,10 @@ public: | ||||||
| 				emit_var(register_variable(item.width, item.curr), | 				emit_var(register_variable(item.width, item.curr), | ||||||
| 				         "wire", name, item.lsb_at, multipart); | 				         "wire", name, item.lsb_at, multipart); | ||||||
| 				break; | 				break; | ||||||
|  | 			case debug_item::OUTLINE: | ||||||
|  | 				emit_var(register_variable(item.width, item.curr, /*constant=*/false, item.outline), | ||||||
|  | 				         "wire", name, item.lsb_at, multipart); | ||||||
|  | 				break; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -228,6 +245,7 @@ public: | ||||||
| 			emit_scope({}); | 			emit_scope({}); | ||||||
| 			emit_enddefinitions(); | 			emit_enddefinitions(); | ||||||
| 		} | 		} | ||||||
|  | 		reset_outlines(); | ||||||
| 		emit_time(timestamp); | 		emit_time(timestamp); | ||||||
| 		for (auto var : variables) | 		for (auto var : variables) | ||||||
| 			if (test_variable(var) || first_sample) { | 			if (test_variable(var) || first_sample) { | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue