mirror of
				https://github.com/YosysHQ/yosys
				synced 2025-10-29 18:52:30 +00:00 
			
		
		
		
	cxxrtl: generate debug information for non-localized public wires.
Debug information describes values, wires, and memories with a simple C-compatible layout. It can be emitted on demand into a map, which has no runtime cost when it is unused, and allows late bound designs. The `hdlname` attribute is used as the lookup key such that original names, as emitted by the frontend, can be used for debugging and introspection.
This commit is contained in:
		
							parent
							
								
									784bfec67c
								
							
						
					
					
						commit
						f6e16e7f4c
					
				
					 2 changed files with 131 additions and 2 deletions
				
			
		|  | @ -502,6 +502,15 @@ std::string escape_cxx_string(const std::string &input) | ||||||
| 	return output; | 	return output; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | template<class T> | ||||||
|  | std::string get_hdl_name(T *object) | ||||||
|  | { | ||||||
|  | 	if (object->has_attribute(ID::hdlname)) | ||||||
|  | 		return object->get_string_attribute(ID::hdlname); | ||||||
|  | 	else | ||||||
|  | 		return object->name.str(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| struct CxxrtlWorker { | struct CxxrtlWorker { | ||||||
| 	bool split_intf = false; | 	bool split_intf = false; | ||||||
| 	std::string intf_filename; | 	std::string intf_filename; | ||||||
|  | @ -516,6 +525,8 @@ struct CxxrtlWorker { | ||||||
| 	bool run_proc_flatten = false; | 	bool run_proc_flatten = false; | ||||||
| 	bool max_opt_level = false; | 	bool max_opt_level = false; | ||||||
| 
 | 
 | ||||||
|  | 	bool debug_info = false; | ||||||
|  | 
 | ||||||
| 	std::ostringstream f; | 	std::ostringstream f; | ||||||
| 	std::string indent; | 	std::string indent; | ||||||
| 	int temporary = 0; | 	int temporary = 0; | ||||||
|  | @ -1593,6 +1604,34 @@ struct CxxrtlWorker { | ||||||
| 		dec_indent(); | 		dec_indent(); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	void dump_debug_info_method(RTLIL::Module *module) | ||||||
|  | 	{ | ||||||
|  | 		inc_indent(); | ||||||
|  | 			f << indent << "assert(path.empty() || path[path.size() - 1] == ' ');\n"; | ||||||
|  | 			for (auto wire : module->wires()) { | ||||||
|  | 				if (wire->name[0] != '\\') | ||||||
|  | 					continue; | ||||||
|  | 				if (localized_wires.count(wire)) | ||||||
|  | 					continue; | ||||||
|  | 				f << indent << "items.emplace(path + " << escape_cxx_string(get_hdl_name(wire)); | ||||||
|  | 				f << ", debug_item(" << mangle(wire) << "));\n"; | ||||||
|  | 			} | ||||||
|  | 			for (auto &memory_it : module->memories) { | ||||||
|  | 				if (memory_it.first[0] != '\\') | ||||||
|  | 					continue; | ||||||
|  | 				f << indent << "items.emplace(path + " << escape_cxx_string(get_hdl_name(memory_it.second)); | ||||||
|  | 				f << ", debug_item(" << mangle(memory_it.second) << "));\n"; | ||||||
|  | 			} | ||||||
|  | 			for (auto cell : module->cells()) { | ||||||
|  | 				if (is_internal_cell(cell->type)) | ||||||
|  | 					continue; | ||||||
|  | 				const char *access = is_cxxrtl_blackbox_cell(cell) ? "->" : "."; | ||||||
|  | 				f << indent << mangle(cell) << access << "debug_info(items, "; | ||||||
|  | 				f << "path + " << escape_cxx_string(get_hdl_name(cell) + ' ') << ");\n"; | ||||||
|  | 			} | ||||||
|  | 		dec_indent(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	void dump_metadata_map(const dict<RTLIL::IdString, RTLIL::Const> &metadata_map) | 	void dump_metadata_map(const dict<RTLIL::IdString, RTLIL::Const> &metadata_map) | ||||||
| 	{ | 	{ | ||||||
| 		if (metadata_map.empty()) { | 		if (metadata_map.empty()) { | ||||||
|  | @ -1641,6 +1680,12 @@ struct CxxrtlWorker { | ||||||
| 				dump_commit_method(module); | 				dump_commit_method(module); | ||||||
| 				f << indent << "}\n"; | 				f << indent << "}\n"; | ||||||
| 				f << "\n"; | 				f << "\n"; | ||||||
|  | 				if (debug_info) { | ||||||
|  | 					f << indent << "void debug_info(debug_items &items, std::string path = \"\") override {\n"; | ||||||
|  | 					dump_debug_info_method(module); | ||||||
|  | 					f << indent << "}\n"; | ||||||
|  | 					f << "\n"; | ||||||
|  | 				} | ||||||
| 				f << indent << "static std::unique_ptr<" << mangle(module); | 				f << indent << "static std::unique_ptr<" << mangle(module); | ||||||
| 				f << template_params(module, /*is_decl=*/false) << "> "; | 				f << template_params(module, /*is_decl=*/false) << "> "; | ||||||
| 				f << "create(std::string name, metadata_map parameters, metadata_map attributes);\n"; | 				f << "create(std::string name, metadata_map parameters, metadata_map attributes);\n"; | ||||||
|  | @ -1689,7 +1734,7 @@ struct CxxrtlWorker { | ||||||
| 					if (cell_module->get_bool_attribute(ID(cxxrtl_blackbox))) { | 					if (cell_module->get_bool_attribute(ID(cxxrtl_blackbox))) { | ||||||
| 						f << indent << "std::unique_ptr<" << mangle(cell_module) << template_args(cell) << "> "; | 						f << indent << "std::unique_ptr<" << mangle(cell_module) << template_args(cell) << "> "; | ||||||
| 						f << mangle(cell) << " = " << mangle(cell_module) << template_args(cell); | 						f << mangle(cell) << " = " << mangle(cell_module) << template_args(cell); | ||||||
| 						f << "::create(" << escape_cxx_string(cell->name.str()) << ", "; | 						f << "::create(" << escape_cxx_string(get_hdl_name(cell)) << ", "; | ||||||
| 						dump_metadata_map(cell->parameters); | 						dump_metadata_map(cell->parameters); | ||||||
| 						f << ", "; | 						f << ", "; | ||||||
| 						dump_metadata_map(cell->attributes); | 						dump_metadata_map(cell->attributes); | ||||||
|  | @ -1703,6 +1748,8 @@ 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) | ||||||
|  | 					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"; | ||||||
|  | @ -1721,6 +1768,12 @@ struct CxxrtlWorker { | ||||||
| 		dump_commit_method(module); | 		dump_commit_method(module); | ||||||
| 		f << indent << "}\n"; | 		f << indent << "}\n"; | ||||||
| 		f << "\n"; | 		f << "\n"; | ||||||
|  | 		if (debug_info) { | ||||||
|  | 			f << indent << "void " << mangle(module) << "::debug_info(debug_items &items, std::string path) {\n"; | ||||||
|  | 			dump_debug_info_method(module); | ||||||
|  | 			f << indent << "}\n"; | ||||||
|  | 			f << "\n"; | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	void dump_design(RTLIL::Design *design) | 	void dump_design(RTLIL::Design *design) | ||||||
|  | @ -2120,6 +2173,7 @@ struct CxxrtlWorker { | ||||||
| 
 | 
 | ||||||
| struct CxxrtlBackend : public Backend { | struct CxxrtlBackend : public Backend { | ||||||
| 	static const int DEFAULT_OPT_LEVEL = 5; | 	static const int DEFAULT_OPT_LEVEL = 5; | ||||||
|  | 	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() YS_OVERRIDE | 	void help() YS_OVERRIDE | ||||||
|  | @ -2313,10 +2367,22 @@ struct CxxrtlBackend : public Backend { | ||||||
| 		log("    -O5\n"); | 		log("    -O5\n"); | ||||||
| 		log("        like -O4, and run `proc; flatten` first.\n"); | 		log("        like -O4, and run `proc; flatten` first.\n"); | ||||||
| 		log("\n"); | 		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"); | ||||||
|  | 		log("\n"); | ||||||
|  | 		log("    -g0\n"); | ||||||
|  | 		log("        no debug information.\n"); | ||||||
|  | 		log("\n"); | ||||||
|  | 		log("    -g1\n"); | ||||||
|  | 		log("        debug information for non-localized public wires.\n"); | ||||||
|  | 		log("\n"); | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
| 	void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE | 	void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE | ||||||
| 	{ | 	{ | ||||||
| 		int opt_level = DEFAULT_OPT_LEVEL; | 		int opt_level = DEFAULT_OPT_LEVEL; | ||||||
|  | 		int debug_level = DEFAULT_DEBUG_LEVEL; | ||||||
| 		CxxrtlWorker worker; | 		CxxrtlWorker worker; | ||||||
| 
 | 
 | ||||||
| 		log_header(design, "Executing CXXRTL backend.\n"); | 		log_header(design, "Executing CXXRTL backend.\n"); | ||||||
|  | @ -2332,6 +2398,14 @@ struct CxxrtlBackend : public Backend { | ||||||
| 				opt_level = std::stoi(args[argidx].substr(2)); | 				opt_level = std::stoi(args[argidx].substr(2)); | ||||||
| 				continue; | 				continue; | ||||||
| 			} | 			} | ||||||
|  | 			if (args[argidx] == "-g" && argidx+1 < args.size()) { | ||||||
|  | 				debug_level = std::stoi(args[++argidx]); | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  | 			if (args[argidx].substr(0, 2) == "-g" && args[argidx].size() == 3 && isdigit(args[argidx][2])) { | ||||||
|  | 				debug_level = std::stoi(args[argidx].substr(2)); | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
| 			if (args[argidx] == "-header") { | 			if (args[argidx] == "-header") { | ||||||
| 				worker.split_intf = true; | 				worker.split_intf = true; | ||||||
| 				continue; | 				continue; | ||||||
|  | @ -2368,6 +2442,17 @@ struct CxxrtlBackend : public Backend { | ||||||
| 				log_cmd_error("Invalid optimization level %d.\n", opt_level); | 				log_cmd_error("Invalid optimization level %d.\n", opt_level); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		switch (debug_level) { | ||||||
|  | 			// the highest level here must match DEFAULT_DEBUG_LEVEL
 | ||||||
|  | 			case 1: | ||||||
|  | 				worker.debug_info = true; | ||||||
|  | 				YS_FALLTHROUGH | ||||||
|  | 			case 0: | ||||||
|  | 				break; | ||||||
|  | 			default: | ||||||
|  | 				log_cmd_error("Invalid optimization level %d.\n", opt_level); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		std::ofstream intf_f; | 		std::ofstream intf_f; | ||||||
| 		if (worker.split_intf) { | 		if (worker.split_intf) { | ||||||
| 			if (filename == "<stdout>") | 			if (filename == "<stdout>") | ||||||
|  |  | ||||||
|  | @ -49,6 +49,8 @@ namespace cxxrtl { | ||||||
| // invisible to the compiler, (b) we often operate on non-power-of-2 values and have to clear the high bits anyway.
 | // invisible to the compiler, (b) we often operate on non-power-of-2 values and have to clear the high bits anyway.
 | ||||||
| // Therefore, using relatively wide chunks and clearing the high bits explicitly and only when we know they may be
 | // Therefore, using relatively wide chunks and clearing the high bits explicitly and only when we know they may be
 | ||||||
| // clobbered results in simpler generated code.
 | // clobbered results in simpler generated code.
 | ||||||
|  | typedef uint32_t chunk_t; | ||||||
|  | 
 | ||||||
| template<typename T> | template<typename T> | ||||||
| struct chunk_traits { | struct chunk_traits { | ||||||
| 	static_assert(std::is_integral<T>::value && std::is_unsigned<T>::value, | 	static_assert(std::is_integral<T>::value && std::is_unsigned<T>::value, | ||||||
|  | @ -65,7 +67,7 @@ template<size_t Bits> | ||||||
| struct value : public expr_base<value<Bits>> { | struct value : public expr_base<value<Bits>> { | ||||||
| 	static constexpr size_t bits = Bits; | 	static constexpr size_t bits = Bits; | ||||||
| 
 | 
 | ||||||
| 	using chunk = chunk_traits<uint32_t>; | 	using chunk = chunk_traits<chunk_t>; | ||||||
| 	static constexpr chunk::type msb_mask = (Bits % chunk::bits == 0) ? chunk::mask | 	static constexpr chunk::type msb_mask = (Bits % chunk::bits == 0) ? chunk::mask | ||||||
| 		: chunk::mask >> (chunk::bits - (Bits % chunk::bits)); | 		: chunk::mask >> (chunk::bits - (Bits % chunk::bits)); | ||||||
| 
 | 
 | ||||||
|  | @ -712,6 +714,46 @@ struct metadata { | ||||||
| 
 | 
 | ||||||
| typedef std::map<std::string, metadata> metadata_map; | typedef std::map<std::string, metadata> metadata_map; | ||||||
| 
 | 
 | ||||||
|  | // 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++.
 | ||||||
|  | struct debug_item { | ||||||
|  | 	enum : uint32_t { | ||||||
|  | 		VALUE  = 0, | ||||||
|  | 		WIRE   = 1, | ||||||
|  | 		MEMORY = 2, | ||||||
|  | 	} type; | ||||||
|  | 
 | ||||||
|  | 	size_t width; // in bits
 | ||||||
|  | 	size_t depth; // 1 if `type != MEMORY`
 | ||||||
|  | 	chunk_t *curr; | ||||||
|  | 	chunk_t *next; // nullptr if `type == VALUE || type == MEMORY`
 | ||||||
|  | 
 | ||||||
|  | 	template<size_t Bits> | ||||||
|  | 	debug_item(value<Bits> &item) : type(VALUE), width(Bits), depth(1), | ||||||
|  | 		curr(item.data), next(nullptr) { | ||||||
|  | 			static_assert(sizeof(item) == value<Bits>::chunks * sizeof(chunk_t), | ||||||
|  | 			              "value<Bits> is not compatible with C layout"); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 	template<size_t Bits> | ||||||
|  | 	debug_item(wire<Bits> &item) : type(WIRE), width(Bits), depth(1), | ||||||
|  | 		curr(item.curr.data), next(item.next.data) { | ||||||
|  | 			static_assert(sizeof(item.curr) == value<Bits>::chunks * sizeof(chunk_t) && | ||||||
|  | 			              sizeof(item.next) == value<Bits>::chunks * sizeof(chunk_t), | ||||||
|  | 			              "wire<Bits> is not compatible with C layout"); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 	template<size_t Width> | ||||||
|  | 	debug_item(memory<Width> &item) : type(MEMORY), width(Width), depth(item.data.size()), | ||||||
|  | 		curr(item.data.empty() ? nullptr : item.data[0].data), next(nullptr) { | ||||||
|  | 			static_assert(sizeof(item.data[0]) == value<Width>::chunks * sizeof(chunk_t), | ||||||
|  | 			              "memory<Width> is not compatible with C layout"); | ||||||
|  | 		} | ||||||
|  | }; | ||||||
|  | static_assert(std::is_standard_layout<debug_item>::value, "debug_item is not compatible with C layout"); | ||||||
|  | 
 | ||||||
|  | typedef std::map<std::string, debug_item> debug_items; | ||||||
|  | 
 | ||||||
| struct module { | struct module { | ||||||
| 	module() {} | 	module() {} | ||||||
| 	virtual ~module() {} | 	virtual ~module() {} | ||||||
|  | @ -731,6 +773,8 @@ struct module { | ||||||
| 		} while (commit() && !converged); | 		} while (commit() && !converged); | ||||||
| 		return deltas; | 		return deltas; | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	virtual void debug_info(debug_items &items, std::string path = "") {} | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // namespace cxxrtl
 | } // namespace cxxrtl
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue