diff options
| author | whitequark <whitequark@whitequark.org> | 2020-12-13 07:03:16 +0000 | 
|---|---|---|
| committer | whitequark <whitequark@whitequark.org> | 2020-12-14 01:27:27 +0000 | 
| commit | ece25a45d4b12f0436be238a13e622b58282036e (patch) | |
| tree | 28ffff0e325e2dd9f9df3c3edbc017619ec85633 | |
| parent | 3b5a1314cd02d093cb1328d7c2f7abced876a514 (diff) | |
| download | yosys-ece25a45d4b12f0436be238a13e622b58282036e.tar.gz yosys-ece25a45d4b12f0436be238a13e622b58282036e.tar.bz2 yosys-ece25a45d4b12f0436be238a13e622b58282036e.zip  | |
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!
| -rw-r--r-- | backends/cxxrtl/cxxrtl.h | 41 | ||||
| -rw-r--r-- | backends/cxxrtl/cxxrtl_backend.cc | 232 | ||||
| -rw-r--r-- | backends/cxxrtl/cxxrtl_capi.cc | 4 | ||||
| -rw-r--r-- | backends/cxxrtl/cxxrtl_capi.h | 40 | ||||
| -rw-r--r-- | backends/cxxrtl/cxxrtl_vcd.h | 32 | 
5 files changed, 278 insertions, 71 deletions
diff --git a/backends/cxxrtl/cxxrtl.h b/backends/cxxrtl/cxxrtl.h index d850fdba4..59393e415 100644 --- a/backends/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/cxxrtl.h @@ -36,6 +36,7 @@  #include <map>  #include <algorithm>  #include <memory> +#include <functional>  #include <sstream>  #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.  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.  // 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 {  	// Object types.  	enum : uint32_t { -		VALUE  = CXXRTL_VALUE, -		WIRE   = CXXRTL_WIRE, -		MEMORY = CXXRTL_MEMORY, -		ALIAS  = CXXRTL_ALIAS, +		VALUE   = CXXRTL_VALUE, +		WIRE    = CXXRTL_WIRE, +		MEMORY  = CXXRTL_MEMORY, +		ALIAS   = CXXRTL_ALIAS, +		OUTLINE = CXXRTL_OUTLINE,  	};  	// Object flags. @@ -881,6 +886,7 @@ struct debug_item : ::cxxrtl_object {  		zero_at = 0;  		curr    = item.data;  		next    = item.data; +		outline = nullptr;  	}  	template<size_t Bits> @@ -895,6 +901,7 @@ struct debug_item : ::cxxrtl_object {  		zero_at = 0;  		curr    = const_cast<chunk_t*>(item.data);  		next    = nullptr; +		outline = nullptr;  	}  	template<size_t Bits> @@ -910,6 +917,7 @@ struct debug_item : ::cxxrtl_object {  		zero_at = 0;  		curr    = item.curr.data;  		next    = item.next.data; +		outline = nullptr;  	}  	template<size_t Width> @@ -924,6 +932,7 @@ struct debug_item : ::cxxrtl_object {  		zero_at = zero_offset;  		curr    = item.data.empty() ? nullptr : item.data[0].data;  		next    = nullptr; +		outline = nullptr;  	}  	template<size_t Bits> @@ -938,6 +947,7 @@ struct debug_item : ::cxxrtl_object {  		zero_at = 0;  		curr    = const_cast<chunk_t*>(item.data);  		next    = nullptr; +		outline = nullptr;  	}  	template<size_t Bits> @@ -953,6 +963,22 @@ struct debug_item : ::cxxrtl_object {  		zero_at = 0;  		curr    = const_cast<chunk_t*>(item.curr.data);  		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"); @@ -1029,11 +1055,16 @@ struct module {  } // 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 {  	std::unique_ptr<cxxrtl::module> module;  } *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  // and indepenent of Yosys implementation details.  // diff --git a/backends/cxxrtl/cxxrtl_backend.cc b/backends/cxxrtl/cxxrtl_backend.cc index ca7f3a3cc..5e2f4f31a 100644 --- a/backends/cxxrtl/cxxrtl_backend.cc +++ b/backends/cxxrtl/cxxrtl_backend.cc @@ -539,6 +539,7 @@ struct CxxrtlWorker {  	bool inline_public = false;  	bool debug_info = false; +	bool debug_eval = false;  	std::ostringstream f;  	std::string indent; @@ -553,8 +554,9 @@ struct CxxrtlWorker {  	pool<const RTLIL::Wire*> unbuffered_wires;  	pool<const RTLIL::Wire*> localized_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*, const RTLIL::Wire*> debug_alias_wires; +	pool<const RTLIL::Wire*> debug_outlined_wires;  	dict<RTLIL::SigBit, bool> bit_has_state;  	dict<const RTLIL::Module*, pool<std::string>> blackbox_specializations;  	dict<const RTLIL::Module*, bool> eval_converges; @@ -786,22 +788,22 @@ struct CxxrtlWorker {  		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) {  			dump_const(chunk.data, chunk.width, chunk.offset);  			return false;  		} else { -			if (inlined_wires.count(chunk.wire)) { +			if (inlined_wires.count(chunk.wire) && (!for_debug || !debug_outlined_wires[chunk.wire])) {  				log_assert(!is_lhs);  				const FlowGraph::Node &node = inlined_wires[chunk.wire];  				switch (node.type) {  					case FlowGraph::Node::Type::CONNECT: -						dump_connect_expr(node.connect); +						dump_connect_expr(node.connect, for_debug);  						break;  					case FlowGraph::Node::Type::CELL_EVAL:  						log_assert(is_inlinable_cell(node.cell->type)); -						dump_cell_expr(node.cell); +						dump_cell_expr(node.cell, for_debug);  						break;  					default:  						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()) {  			f << "value<0>()";  			return false;  		} else if (sig.is_chunk()) { -			return dump_sigchunk(sig.as_chunk(), is_lhs); +			return dump_sigchunk(sig.as_chunk(), is_lhs, for_debug);  		} 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) {  				f << ".concat("; -				dump_sigchunk(*it, is_lhs); +				dump_sigchunk(*it, is_lhs, for_debug);  				f << ")";  			}  			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>`,  		// 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, 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)  			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) @@ -885,6 +887,14 @@ struct CxxrtlWorker {  		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)  	{  		if (!is_connect_inlined(conn)) @@ -893,16 +903,18 @@ struct CxxrtlWorker {  		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;  		f << indent << "// connection\n";  		f << indent; -		dump_sigspec_lhs(conn.first); +		dump_sigspec_lhs(conn.first, for_debug);  		f << " = "; -		dump_connect_expr(conn); +		dump_connect_expr(conn, for_debug);  		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  		if (is_unary_cell(cell->type)) { @@ -927,7 +939,7 @@ struct CxxrtlWorker {  			if (is_extending_cell(cell->type))  				f << '_' << (cell->getParam(ID::A_SIGNED).as_bool() ? 's' : 'u');  			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 << ")";  		// Binary cells  		} else if (is_binary_cell(cell->type)) { @@ -936,18 +948,18 @@ struct CxxrtlWorker {  				f << '_' << (cell->getParam(ID::A_SIGNED).as_bool() ? 's' : 'u') <<  				            (cell->getParam(ID::B_SIGNED).as_bool() ? 's' : 'u');  			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 << ", "; -			dump_sigspec_rhs(cell->getPort(ID::B)); +			dump_sigspec_rhs(cell->getPort(ID::B), for_debug);  			f << ")";  		// Muxes  		} else if (cell->type == ID($mux)) {  			f << "("; -			dump_sigspec_rhs(cell->getPort(ID::S)); +			dump_sigspec_rhs(cell->getPort(ID::S), for_debug);  			f << " ? "; -			dump_sigspec_rhs(cell->getPort(ID::B)); +			dump_sigspec_rhs(cell->getPort(ID::B), for_debug);  			f << " : "; -			dump_sigspec_rhs(cell->getPort(ID::A)); +			dump_sigspec_rhs(cell->getPort(ID::A), for_debug);  			f << ")";  		// Parallel (one-hot) muxes  		} else if (cell->type == ID($pmux)) { @@ -955,24 +967,24 @@ struct CxxrtlWorker {  			int s_width = cell->getParam(ID::S_WIDTH).as_int();  			for (int part = 0; part < s_width; part++) {  				f << "("; -				dump_sigspec_rhs(cell->getPort(ID::S).extract(part)); +				dump_sigspec_rhs(cell->getPort(ID::S).extract(part), for_debug);  				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 << " : ";  			} -			dump_sigspec_rhs(cell->getPort(ID::A)); +			dump_sigspec_rhs(cell->getPort(ID::A), for_debug);  			for (int part = 0; part < s_width; part++) {  				f << ")";  			}  		// Concats  		} else if (cell->type == ID($concat)) { -			dump_sigspec_rhs(cell->getPort(ID::B)); +			dump_sigspec_rhs(cell->getPort(ID::B), for_debug);  			f << ".concat("; -			dump_sigspec_rhs(cell->getPort(ID::A)); +			dump_sigspec_rhs(cell->getPort(ID::A), for_debug);  			f << ").val()";  		// Slices  		} 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 << cell->getParam(ID::OFFSET).as_int() + cell->getParam(ID::Y_WIDTH).as_int() - 1;  			f << ","; @@ -989,6 +1001,17 @@ struct CxxrtlWorker {  			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)  	{  		if (!is_cell_inlined(cell)) @@ -1000,9 +1023,11 @@ struct CxxrtlWorker {  				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;  		if (cell->type == ID($meminit))  			return; // Handled elsewhere. @@ -1026,9 +1051,9 @@ struct CxxrtlWorker {  		// Elidable cells  		if (is_inlinable_cell(cell->type)) {  			f << indent; -			dump_sigspec_lhs(cell->getPort(ID::Y)); +			dump_sigspec_lhs(cell->getPort(ID::Y), for_debug);  			f << " = "; -			dump_cell_expr(cell); +			dump_cell_expr(cell, for_debug);  			f << ";\n";  		// Flip-flops  		} else if (is_ff_cell(cell->type)) { @@ -1460,14 +1485,11 @@ struct CxxrtlWorker {  	void dump_wire(const RTLIL::Wire *wire, bool is_local)  	{ -		if (inlined_wires.count(wire)) -			return; - -		if (localized_wires[wire] && is_local) { +		if (is_local && localized_wires[wire] && !inlined_wires.count(wire)) {  			dump_attrs(wire);  			f << indent << "value<" << wire->width << "> " << mangle(wire) << ";\n";  		} -		if (!localized_wires[wire] && !is_local) { +		if (!is_local && !localized_wires[wire]) {  			std::string width;  			if (wire->module->has_attribute(ID(cxxrtl_blackbox)) && wire->has_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)  	{  		vector<const RTLIL::Cell*> init_cells; @@ -1619,6 +1658,27 @@ struct CxxrtlWorker {  		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)  	{  		inc_indent(); @@ -1656,6 +1716,7 @@ struct CxxrtlWorker {  		size_t count_public_wires = 0;  		size_t count_const_wires = 0;  		size_t count_alias_wires = 0; +		size_t count_inline_wires = 0;  		size_t count_member_wires = 0;  		size_t count_skipped_wires = 0;  		size_t count_driven_sync = 0; @@ -1685,6 +1746,12 @@ struct CxxrtlWorker {  					f << ", debug_item(debug_alias(), " << mangle(debug_alias_wires[wire]) << ", ";  					f << wire->start_offset << "));\n";  					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)) {  					// Member wire  					std::vector<std::string> flags; @@ -1738,6 +1805,7 @@ struct CxxrtlWorker {  					f << "));\n";  					count_member_wires++;  				} else { +					// Localized or inlined wire with no debug information  					count_skipped_wires++;  				}  			} @@ -1761,14 +1829,16 @@ struct CxxrtlWorker {  		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("    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("      Driven sync:  %zu\n", count_driven_sync);  		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("    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) @@ -1855,7 +1925,8 @@ struct CxxrtlWorker {  			inc_indent();  				for (auto wire : module->wires())  					dump_wire(wire, /*is_local=*/false); -				f << "\n"; +				for (auto wire : module->wires()) +					dump_debug_wire(wire, /*is_local=*/false);  				bool has_memories = false;  				for (auto memory : module->memories) {  					dump_memory(module, memory.second); @@ -1927,8 +1998,20 @@ struct CxxrtlWorker {  				f << "\n";  				f << indent << "bool eval() 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"; +				}  			dec_indent();  			f << indent << "}; // struct " << mangle(module) << "\n";  			f << "\n"; @@ -1948,6 +2031,12 @@ struct CxxrtlWorker {  		f << indent << "}\n";  		f << "\n";  		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";  			dump_debug_info_method(module);  			f << indent << "}\n"; @@ -2251,6 +2340,11 @@ struct CxxrtlWorker {  				for (auto node : wire_comb_def.second)  					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;  			dict<FlowGraph::Node*, Scheduler<FlowGraph::Node>::Vertex*, hash_ptr_ops> node_map;  			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) {  			// Although both non-feedback buffered combinatorial wires and apparent feedback wires may be eliminated @@ -2457,8 +2575,7 @@ struct CxxrtlWorker {  struct CxxrtlBackend : public Backend {  	static const int DEFAULT_OPT_LEVEL = 6; -	static const int OPT_LEVEL_DEBUG = 4; -	static const int DEFAULT_DEBUG_LEVEL = 1; +	static const int DEFAULT_DEBUG_LEVEL = 2;  	CxxrtlBackend() : Backend("cxxrtl", "convert design to C++ RTL simulation") { }  	void help() override @@ -2671,10 +2788,6 @@ struct CxxrtlBackend : public Backend {  		log("    -O6\n");  		log("        like -O5, and inline public wires not marked (*keep*) if possible.\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("        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"); @@ -2686,6 +2799,10 @@ struct CxxrtlBackend : public Backend {  		log("        debug information for non-optimized public wires. this also makes it\n");  		log("        possible to use the C API.\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 @@ -2715,12 +2832,14 @@ struct CxxrtlBackend : public Backend {  				continue;  			}  			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;  			}  			if (args[argidx] == "-O" && argidx+1 < args.size() && args[argidx+1] == "g") {  				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;  			}  			if (args[argidx] == "-O" && argidx+1 < args.size()) { @@ -2781,6 +2900,9 @@ struct CxxrtlBackend : public Backend {  		}  		switch (debug_level) {  			// the highest level here must match DEFAULT_DEBUG_LEVEL +			case 2: +				worker.debug_eval = true; +				YS_FALLTHROUGH  			case 1:  				worker.debug_info = true;  				YS_FALLTHROUGH diff --git a/backends/cxxrtl/cxxrtl_capi.cc b/backends/cxxrtl/cxxrtl_capi.cc index f92709b46..227173ba8 100644 --- a/backends/cxxrtl/cxxrtl_capi.cc +++ b/backends/cxxrtl/cxxrtl_capi.cc @@ -86,3 +86,7 @@ void cxxrtl_enum(cxxrtl_handle handle, void *data,  	for (auto &it : handle->objects.table)  		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(); +} diff --git a/backends/cxxrtl/cxxrtl_capi.h b/backends/cxxrtl/cxxrtl_capi.h index d67c58f94..7d9c60ac5 100644 --- a/backends/cxxrtl/cxxrtl_capi.h +++ b/backends/cxxrtl/cxxrtl_capi.h @@ -128,6 +128,18 @@ enum cxxrtl_type {  	// pointer is always NULL.  	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.  }; @@ -171,8 +183,8 @@ enum cxxrtl_flag {  	// 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 -	// with `CXXRTL_DRIVEN_SYNC` and `CXXRTL_UNDRIVEN`, as well as other flags. +	// This flag can be set on objects of type `CXXRTL_VALUE`, `CXXRTL_WIRE`, and `CXXRTL_OUTLINE`. +	// 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,  	// 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.  // -// The `data` array can be accessed directly to inspect and, if applicable, modify the bits -// stored in the object. +// The `curr` and `next` arrays can be accessed directly to inspect and, if applicable, modify +// the bits stored in the object.  struct cxxrtl_object {  	// Type of the object.  	// @@ -231,6 +243,12 @@ struct cxxrtl_object {  	uint32_t *curr;  	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.  }; @@ -272,6 +290,20 @@ void cxxrtl_enum(cxxrtl_handle handle, void *data,                   void (*callback)(void *data, const char *name,                                    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  }  #endif diff --git a/backends/cxxrtl/cxxrtl_vcd.h b/backends/cxxrtl/cxxrtl_vcd.h index dbeabbaf2..6ee98b428 100644 --- a/backends/cxxrtl/cxxrtl_vcd.h +++ b/backends/cxxrtl/cxxrtl_vcd.h @@ -28,10 +28,13 @@ class vcd_writer {  		size_t ident;  		size_t width;  		chunk_t *curr; -		size_t prev_off; +		size_t cache_offset; +		debug_outline *outline; +		bool *outline_warm;  	};  	std::vector<std::string> current_scope; +	std::map<debug_outline*, bool> outlines;  	std::vector<variable> variables;  	std::vector<chunk_t> cache;  	std::map<chunk_t*, size_t> aliases; @@ -112,16 +115,22 @@ class vcd_writer {  		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)) {  			return variables[aliases[curr]];  		} 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);  			aliases[curr] = variables.size();  			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 { -				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]);  			}  			return variables.back(); @@ -129,13 +138,17 @@ class vcd_writer {  	}  	bool test_variable(const variable &var) { -		if (var.prev_off == (size_t)-1) +		if (var.cache_offset == (size_t)-1)  			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); -		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;  		} 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;  		}  	} @@ -197,6 +210,10 @@ public:  				emit_var(register_variable(item.width, item.curr),  				         "wire", name, item.lsb_at, multipart);  				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_enddefinitions();  		} +		reset_outlines();  		emit_time(timestamp);  		for (auto var : variables)  			if (test_variable(var) || first_sample) {  | 
