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; | ||||
| } | ||||
| 
 | ||||
| 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 { | ||||
| 	bool split_intf = false; | ||||
| 	std::string intf_filename; | ||||
|  | @ -516,6 +525,8 @@ struct CxxrtlWorker { | |||
| 	bool run_proc_flatten = false; | ||||
| 	bool max_opt_level = false; | ||||
| 
 | ||||
| 	bool debug_info = false; | ||||
| 
 | ||||
| 	std::ostringstream f; | ||||
| 	std::string indent; | ||||
| 	int temporary = 0; | ||||
|  | @ -1593,6 +1604,34 @@ struct CxxrtlWorker { | |||
| 		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) | ||||
| 	{ | ||||
| 		if (metadata_map.empty()) { | ||||
|  | @ -1641,6 +1680,12 @@ struct CxxrtlWorker { | |||
| 				dump_commit_method(module); | ||||
| 				f << indent << "}\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 << template_params(module, /*is_decl=*/false) << "> "; | ||||
| 				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))) { | ||||
| 						f << indent << "std::unique_ptr<" << 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); | ||||
| 						f << ", "; | ||||
| 						dump_metadata_map(cell->attributes); | ||||
|  | @ -1703,6 +1748,8 @@ struct CxxrtlWorker { | |||
| 					f << "\n"; | ||||
| 				f << indent << "bool eval() 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(); | ||||
| 			f << indent << "}; // struct " << mangle(module) << "\n"; | ||||
| 			f << "\n"; | ||||
|  | @ -1721,6 +1768,12 @@ struct CxxrtlWorker { | |||
| 		dump_commit_method(module); | ||||
| 		f << indent << "}\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) | ||||
|  | @ -2120,6 +2173,7 @@ struct CxxrtlWorker { | |||
| 
 | ||||
| struct CxxrtlBackend : public Backend { | ||||
| 	static const int DEFAULT_OPT_LEVEL = 5; | ||||
| 	static const int DEFAULT_DEBUG_LEVEL = 1; | ||||
| 
 | ||||
| 	CxxrtlBackend() : Backend("cxxrtl", "convert design to C++ RTL simulation") { } | ||||
| 	void help() YS_OVERRIDE | ||||
|  | @ -2313,10 +2367,22 @@ struct CxxrtlBackend : public Backend { | |||
| 		log("    -O5\n"); | ||||
| 		log("        like -O4, and run `proc; flatten` first.\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 | ||||
| 	{ | ||||
| 		int opt_level = DEFAULT_OPT_LEVEL; | ||||
| 		int debug_level = DEFAULT_DEBUG_LEVEL; | ||||
| 		CxxrtlWorker worker; | ||||
| 
 | ||||
| 		log_header(design, "Executing CXXRTL backend.\n"); | ||||
|  | @ -2332,6 +2398,14 @@ struct CxxrtlBackend : public Backend { | |||
| 				opt_level = std::stoi(args[argidx].substr(2)); | ||||
| 				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") { | ||||
| 				worker.split_intf = true; | ||||
| 				continue; | ||||
|  | @ -2368,6 +2442,17 @@ struct CxxrtlBackend : public Backend { | |||
| 				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; | ||||
| 		if (worker.split_intf) { | ||||
| 			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.
 | ||||
| // 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.
 | ||||
| typedef uint32_t chunk_t; | ||||
| 
 | ||||
| template<typename T> | ||||
| struct chunk_traits { | ||||
| 	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>> { | ||||
| 	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 | ||||
| 		: chunk::mask >> (chunk::bits - (Bits % chunk::bits)); | ||||
| 
 | ||||
|  | @ -712,6 +714,46 @@ struct metadata { | |||
| 
 | ||||
| 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 { | ||||
| 	module() {} | ||||
| 	virtual ~module() {} | ||||
|  | @ -731,6 +773,8 @@ struct module { | |||
| 		} while (commit() && !converged); | ||||
| 		return deltas; | ||||
| 	} | ||||
| 
 | ||||
| 	virtual void debug_info(debug_items &items, std::string path = "") {} | ||||
| }; | ||||
| 
 | ||||
| } // namespace cxxrtl
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue