mirror of
				https://github.com/YosysHQ/yosys
				synced 2025-10-31 03:32:29 +00:00 
			
		
		
		
	Merge pull request #2145 from whitequark/cxxrtl-splitnets
cxxrtl: handle multipart signals
This commit is contained in:
		
						commit
						dc6961f3d4
					
				
					 5 changed files with 156 additions and 67 deletions
				
			
		|  | @ -771,76 +771,119 @@ struct debug_item : ::cxxrtl_object { | |||
| 	debug_item(const ::cxxrtl_object &object) : cxxrtl_object(object) {} | ||||
| 
 | ||||
| 	template<size_t Bits> | ||||
| 	debug_item(value<Bits> &item) { | ||||
| 	debug_item(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  = VALUE; | ||||
| 		width = Bits; | ||||
| 		depth = 1; | ||||
| 		curr  = item.data; | ||||
| 		next  = item.data; | ||||
| 		type    = VALUE; | ||||
| 		width   = Bits; | ||||
| 		lsb_at  = lsb_offset; | ||||
| 		depth   = 1; | ||||
| 		zero_at = 0; | ||||
| 		curr    = item.data; | ||||
| 		next    = item.data; | ||||
| 	} | ||||
| 
 | ||||
| 	template<size_t Bits> | ||||
| 	debug_item(const value<Bits> &item) { | ||||
| 	debug_item(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  = VALUE; | ||||
| 		width = Bits; | ||||
| 		depth = 1; | ||||
| 		curr  = const_cast<chunk_t*>(item.data); | ||||
| 		next  = nullptr; | ||||
| 		type    = VALUE; | ||||
| 		width   = Bits; | ||||
| 		lsb_at  = lsb_offset; | ||||
| 		depth   = 1; | ||||
| 		zero_at = 0; | ||||
| 		curr    = const_cast<chunk_t*>(item.data); | ||||
| 		next    = nullptr; | ||||
| 	} | ||||
| 
 | ||||
| 	template<size_t Bits> | ||||
| 	debug_item(wire<Bits> &item) { | ||||
| 	debug_item(wire<Bits> &item, size_t lsb_offset = 0) { | ||||
| 		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"); | ||||
| 		type  = WIRE; | ||||
| 		width = Bits; | ||||
| 		depth = 1; | ||||
| 		curr  = item.curr.data; | ||||
| 		next  = item.next.data; | ||||
| 		type    = WIRE; | ||||
| 		width   = Bits; | ||||
| 		lsb_at  = lsb_offset; | ||||
| 		depth   = 1; | ||||
| 		zero_at = 0; | ||||
| 		curr    = item.curr.data; | ||||
| 		next    = item.next.data; | ||||
| 	} | ||||
| 
 | ||||
| 	template<size_t Width> | ||||
| 	debug_item(memory<Width> &item) { | ||||
| 	debug_item(memory<Width> &item, size_t zero_offset = 0) { | ||||
| 		static_assert(sizeof(item.data[0]) == value<Width>::chunks * sizeof(chunk_t), | ||||
| 		              "memory<Width> is not compatible with C layout"); | ||||
| 		type  = MEMORY; | ||||
| 		width = Width; | ||||
| 		depth = item.data.size(); | ||||
| 		curr  = item.data.empty() ? nullptr : item.data[0].data; | ||||
| 		next  = nullptr; | ||||
| 		type    = MEMORY; | ||||
| 		width   = Width; | ||||
| 		lsb_at  = 0; | ||||
| 		depth   = item.data.size(); | ||||
| 		zero_at = zero_offset; | ||||
| 		curr    = item.data.empty() ? nullptr : item.data[0].data; | ||||
| 		next    = nullptr; | ||||
| 	} | ||||
| 
 | ||||
| 	template<size_t Bits> | ||||
| 	debug_item(debug_alias, const value<Bits> &item) { | ||||
| 	debug_item(debug_alias, 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  = ALIAS; | ||||
| 		width = Bits; | ||||
| 		depth = 1; | ||||
| 		curr  = const_cast<chunk_t*>(item.data); | ||||
| 		next  = nullptr; | ||||
| 		type    = ALIAS; | ||||
| 		width   = Bits; | ||||
| 		lsb_at  = lsb_offset; | ||||
| 		depth   = 1; | ||||
| 		zero_at = 0; | ||||
| 		curr    = const_cast<chunk_t*>(item.data); | ||||
| 		next    = nullptr; | ||||
| 	} | ||||
| 
 | ||||
| 	template<size_t Bits> | ||||
| 	debug_item(debug_alias, const wire<Bits> &item) { | ||||
| 	debug_item(debug_alias, const wire<Bits> &item, size_t lsb_offset = 0) { | ||||
| 		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"); | ||||
| 		type  = ALIAS; | ||||
| 		width = Bits; | ||||
| 		depth = 1; | ||||
| 		curr  = const_cast<chunk_t*>(item.curr.data); | ||||
| 		next  = nullptr; | ||||
| 		type    = ALIAS; | ||||
| 		width   = Bits; | ||||
| 		lsb_at  = lsb_offset; | ||||
| 		depth   = 1; | ||||
| 		zero_at = 0; | ||||
| 		curr    = const_cast<chunk_t*>(item.curr.data); | ||||
| 		next    = nullptr; | ||||
| 	} | ||||
| }; | ||||
| 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 debug_items { | ||||
| 	std::map<std::string, std::vector<debug_item>> table; | ||||
| 
 | ||||
| 	void add(const std::string &name, debug_item &&item) { | ||||
| 		std::vector<debug_item> &parts = table[name]; | ||||
| 		parts.emplace_back(item); | ||||
| 		std::sort(parts.begin(), parts.end(), | ||||
| 			[](const debug_item &a, const debug_item &b) { | ||||
| 				return a.lsb_at < b.lsb_at; | ||||
| 			}); | ||||
| 	} | ||||
| 
 | ||||
| 	size_t count(const std::string &name) const { | ||||
| 		if (table.count(name) == 0) | ||||
| 			return 0; | ||||
| 		return table.at(name).size(); | ||||
| 	} | ||||
| 
 | ||||
| 	const std::vector<debug_item> &parts_at(const std::string &name) const { | ||||
| 		return table.at(name); | ||||
| 	} | ||||
| 
 | ||||
| 	const debug_item &at(const std::string &name) const { | ||||
| 		const std::vector<debug_item> &parts = table.at(name); | ||||
| 		assert(parts.size() == 1); | ||||
| 		return parts.at(0); | ||||
| 	} | ||||
| 
 | ||||
| 	const debug_item &operator [](const std::string &name) const { | ||||
| 		return at(name); | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| struct module { | ||||
| 	module() {} | ||||
|  |  | |||
|  | @ -1627,18 +1627,21 @@ struct CxxrtlWorker { | |||
| 					f << indent << "static const value<" << wire->width << "> const_" << mangle(wire) << " = "; | ||||
| 					dump_const(debug_const_wires[wire]); | ||||
| 					f << ";\n"; | ||||
| 					f << indent << "items.emplace(path + " << escape_cxx_string(get_hdl_name(wire)); | ||||
| 					f << ", debug_item(const_" << mangle(wire) << "));\n"; | ||||
| 					f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(wire)); | ||||
| 					f << ", debug_item(const_" << mangle(wire) << ", "; | ||||
| 					f << wire->start_offset << "));\n"; | ||||
| 					count_const_wires++; | ||||
| 				} else if (debug_alias_wires.count(wire)) { | ||||
| 					// Alias of a member wire
 | ||||
| 					f << indent << "items.emplace(path + " << escape_cxx_string(get_hdl_name(wire)); | ||||
| 					f << ", debug_item(debug_alias(), " << mangle(debug_alias_wires[wire]) << "));\n"; | ||||
| 					f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(wire)); | ||||
| 					f << ", debug_item(debug_alias(), " << mangle(debug_alias_wires[wire]) << ", "; | ||||
| 					f << wire->start_offset << "));\n"; | ||||
| 					count_alias_wires++; | ||||
| 				} else if (!localized_wires.count(wire)) { | ||||
| 					// Member wire
 | ||||
| 					f << indent << "items.emplace(path + " << escape_cxx_string(get_hdl_name(wire)); | ||||
| 					f << ", debug_item(" << mangle(wire) << "));\n"; | ||||
| 					f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(wire)); | ||||
| 					f << ", debug_item(" << mangle(wire) << ", "; | ||||
| 					f << wire->start_offset << "));\n"; | ||||
| 					count_member_wires++; | ||||
| 				} else { | ||||
| 					count_skipped_wires++; | ||||
|  | @ -1647,8 +1650,9 @@ struct CxxrtlWorker { | |||
| 			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"; | ||||
| 				f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(memory_it.second)); | ||||
| 				f << ", debug_item(" << mangle(memory_it.second) << ", "; | ||||
| 				f << memory_it.second->start_offset << "));\n"; | ||||
| 			} | ||||
| 			for (auto cell : module->cells()) { | ||||
| 				if (is_internal_cell(cell->type)) | ||||
|  |  | |||
|  | @ -47,14 +47,17 @@ size_t cxxrtl_step(cxxrtl_handle handle) { | |||
| 	return handle->module->step(); | ||||
| } | ||||
| 
 | ||||
| cxxrtl_object *cxxrtl_get(cxxrtl_handle handle, const char *name) { | ||||
| 	if (handle->objects.count(name) > 0) | ||||
| 		return static_cast<cxxrtl_object*>(&handle->objects.at(name)); | ||||
| 	return nullptr; | ||||
| struct cxxrtl_object *cxxrtl_get_parts(cxxrtl_handle handle, const char *name, size_t *parts) { | ||||
| 	auto it = handle->objects.table.find(name); | ||||
| 	if (it == handle->objects.table.end()) | ||||
| 		return nullptr; | ||||
| 	*parts = it->second.size(); | ||||
| 	return static_cast<cxxrtl_object*>(&it->second[0]); | ||||
| } | ||||
| 
 | ||||
| void cxxrtl_enum(cxxrtl_handle handle, void *data, | ||||
|                  void (*callback)(void *data, const char *name, cxxrtl_object *object)) { | ||||
| 	for (auto &it : handle->objects) | ||||
| 		callback(data, it.first.c_str(), static_cast<cxxrtl_object*>(&it.second)); | ||||
|                  void (*callback)(void *data, const char *name, | ||||
|                                   cxxrtl_object *object, size_t parts)) { | ||||
| 	for (auto &it : handle->objects.table) | ||||
| 		callback(data, it.first.c_str(), static_cast<cxxrtl_object*>(&it.second[0]), it.second.size()); | ||||
| } | ||||
|  |  | |||
|  | @ -26,6 +26,7 @@ | |||
| 
 | ||||
| #include <stddef.h> | ||||
| #include <stdint.h> | ||||
| #include <assert.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
|  | @ -113,9 +114,15 @@ struct cxxrtl_object { | |||
| 	// Width of the object in bits.
 | ||||
| 	size_t width; | ||||
| 
 | ||||
| 	// Index of the least significant bit.
 | ||||
| 	size_t lsb_at; | ||||
| 
 | ||||
| 	// Depth of the object. Only meaningful for memories; for other objects, always 1.
 | ||||
| 	size_t depth; | ||||
| 
 | ||||
| 	// Index of the first word. Only meaningful for memories; for other objects, always 0;
 | ||||
| 	size_t zero_at; | ||||
| 
 | ||||
| 	// Bits stored in the object, as 32-bit chunks, least significant bits first.
 | ||||
| 	//
 | ||||
| 	// The width is rounded up to a multiple of 32; the padding bits are always set to 0 by
 | ||||
|  | @ -140,17 +147,36 @@ struct cxxrtl_object { | |||
| // the top-level module instantiates a module `foo`, which in turn contains a wire `bar`, the full
 | ||||
| // hierarchical name is `\foo \bar`.
 | ||||
| //
 | ||||
| // Returns the object if it was found, NULL otherwise. The returned value is valid until the design
 | ||||
| // is destroyed.
 | ||||
| struct cxxrtl_object *cxxrtl_get(cxxrtl_handle handle, const char *name); | ||||
| // The storage of a single abstract object may be split (usually with the `splitnets` pass) into
 | ||||
| // many physical parts, all of which correspond to the same hierarchical name. To handle such cases,
 | ||||
| // this function returns an array and writes its length to `parts`. The array is sorted by `lsb_at`.
 | ||||
| //
 | ||||
| // Returns the object parts if it was found, NULL otherwise. The returned parts are valid until
 | ||||
| // the design is destroyed.
 | ||||
| struct cxxrtl_object *cxxrtl_get_parts(cxxrtl_handle handle, const char *name, size_t *parts); | ||||
| 
 | ||||
| // Retrieve description of a single part simulated object.
 | ||||
| //
 | ||||
| // This function is a shortcut for the most common use of `cxxrtl_get_parts`. It asserts that,
 | ||||
| // if the object exists, it consists of a single part. If assertions are disabled, it returns NULL
 | ||||
| // for multi-part objects.
 | ||||
| inline struct cxxrtl_object *cxxrtl_get(cxxrtl_handle handle, const char *name) { | ||||
| 	size_t parts = 0; | ||||
| 	struct cxxrtl_object *object = cxxrtl_get_parts(handle, name, &parts); | ||||
| 	assert(object == NULL || parts == 1); | ||||
| 	if (object == NULL || parts == 1) | ||||
| 		return object; | ||||
| 	return NULL; | ||||
| } | ||||
| 
 | ||||
| // Enumerate simulated objects.
 | ||||
| //
 | ||||
| // For every object in the simulation, `callback` is called with the provided `data`, the full
 | ||||
| // hierarchical name of the object (see `cxxrtl_get` for details), and the object description.
 | ||||
| // hierarchical name of the object (see `cxxrtl_get` for details), and the object parts.
 | ||||
| // The provided `name` and `object` values are valid until the design is destroyed.
 | ||||
| void cxxrtl_enum(cxxrtl_handle handle, void *data, | ||||
|                  void (*callback)(void *data, const char *name, struct cxxrtl_object *object)); | ||||
|                  void (*callback)(void *data, const char *name, | ||||
|                                   struct cxxrtl_object *object, size_t parts)); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
|  |  | |||
|  | @ -66,11 +66,19 @@ class vcd_writer { | |||
| 		} while (ident != 0); | ||||
| 	} | ||||
| 
 | ||||
| 	void emit_var(const variable &var, const std::string &type, const std::string &name) { | ||||
| 	void emit_var(const variable &var, const std::string &type, const std::string &name, | ||||
| 	              size_t lsb_at, bool multipart) { | ||||
| 		assert(!streaming); | ||||
| 		buffer += "$var " + type + " " + std::to_string(var.width) + " "; | ||||
| 		emit_ident(var.ident); | ||||
| 		buffer += " " + name + " $end\n"; | ||||
| 		buffer += " " + name; | ||||
| 		if (multipart || name.back() == ']' || lsb_at != 0) { | ||||
| 			if (var.width == 1) | ||||
| 				buffer += " [" + std::to_string(lsb_at) + "]"; | ||||
| 			else | ||||
| 				buffer += " [" + std::to_string(lsb_at + var.width - 1) + ":" + std::to_string(lsb_at) + "]"; | ||||
| 		} | ||||
| 		buffer += " $end\n"; | ||||
| 	} | ||||
| 
 | ||||
| 	void emit_enddefinitions() { | ||||
|  | @ -155,7 +163,7 @@ public: | |||
| 		emit_timescale(number, unit); | ||||
| 	} | ||||
| 
 | ||||
| 	void add(const std::string &hier_name, const debug_item &item) { | ||||
| 	void add(const std::string &hier_name, const debug_item &item, bool multipart = false) { | ||||
| 		std::vector<std::string> scope = split_hierarchy(hier_name); | ||||
| 		std::string name = scope.back(); | ||||
| 		scope.pop_back(); | ||||
|  | @ -164,17 +172,20 @@ public: | |||
| 		switch (item.type) { | ||||
| 			// Not the best naming but oh well...
 | ||||
| 			case debug_item::VALUE: | ||||
| 				emit_var(register_variable(item.width, item.curr, /*constant=*/item.next == nullptr), "wire", name); | ||||
| 				emit_var(register_variable(item.width, item.curr, /*constant=*/item.next == nullptr), | ||||
| 				         "wire", name, item.lsb_at, multipart); | ||||
| 				break; | ||||
| 			case debug_item::WIRE: | ||||
| 				emit_var(register_variable(item.width, item.curr), "reg", name); | ||||
| 				emit_var(register_variable(item.width, item.curr), | ||||
| 				         "reg", name, item.lsb_at, multipart); | ||||
| 				break; | ||||
| 			case debug_item::MEMORY: { | ||||
| 				const size_t stride = (item.width + (sizeof(chunk_t) * 8 - 1)) / (sizeof(chunk_t) * 8); | ||||
| 				for (size_t index = 0; index < item.depth; index++) { | ||||
| 					chunk_t *nth_curr = &item.curr[stride * index]; | ||||
| 					std::string nth_name = name + '[' + std::to_string(index) + ']'; | ||||
| 					emit_var(register_variable(item.width, nth_curr), "reg", nth_name); | ||||
| 					emit_var(register_variable(item.width, nth_curr), | ||||
| 					         "reg", nth_name, item.lsb_at, multipart); | ||||
| 				} | ||||
| 				break; | ||||
| 			} | ||||
|  | @ -183,7 +194,8 @@ public: | |||
| 				// can actually change, and must be tracked. In most cases the VCD identifier will be
 | ||||
| 				// unified with the aliased reg, but we should handle the case where only the alias is
 | ||||
| 				// added to the VCD writer, too.
 | ||||
| 				emit_var(register_variable(item.width, item.curr), "wire", name); | ||||
| 				emit_var(register_variable(item.width, item.curr), | ||||
| 				         "wire", name, item.lsb_at, multipart); | ||||
| 				break; | ||||
| 		} | ||||
| 	} | ||||
|  | @ -192,9 +204,10 @@ public: | |||
| 	void add(const debug_items &items, const Filter &filter) { | ||||
| 		// `debug_items` is a map, so the items are already sorted in an order optimal for emitting
 | ||||
| 		// VCD scope sections.
 | ||||
| 		for (auto &it : items) | ||||
| 			if (filter(it.first, it.second)) | ||||
| 				add(it.first, it.second); | ||||
| 		for (auto &it : items.table) | ||||
| 			for (auto &part : it.second) | ||||
| 				if (filter(it.first, part)) | ||||
| 					add(it.first, part, it.second.size() > 1); | ||||
| 	} | ||||
| 
 | ||||
| 	void add(const debug_items &items) { | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue