mirror of
				https://github.com/YosysHQ/yosys
				synced 2025-10-31 03:32:29 +00:00 
			
		
		
		
	Merge 0aa2a4c260 into 89f32a415b
				
					
				
			This commit is contained in:
		
						commit
						ea84ab0bf3
					
				
					 14 changed files with 950 additions and 5 deletions
				
			
		
							
								
								
									
										1
									
								
								Makefile
									
										
									
									
									
								
							
							
						
						
									
										1
									
								
								Makefile
									
										
									
									
									
								
							|  | @ -886,6 +886,7 @@ MK_TEST_DIRS += tests/arch/xilinx | |||
| MK_TEST_DIRS += tests/bugpoint | ||||
| MK_TEST_DIRS += tests/opt | ||||
| MK_TEST_DIRS += tests/sat | ||||
| MK_TEST_DIRS += tests/sdc | ||||
| MK_TEST_DIRS += tests/sim | ||||
| MK_TEST_DIRS += tests/svtypes | ||||
| MK_TEST_DIRS += tests/techmap | ||||
|  |  | |||
|  | @ -188,7 +188,7 @@ extern char yosys_path[PATH_MAX]; | |||
| #endif | ||||
| #ifdef YOSYS_ENABLE_TCL | ||||
| namespace Yosys { | ||||
| 	extern int yosys_tcl_iterp_init(Tcl_Interp *interp); | ||||
| 	extern int yosys_tcl_interp_init(Tcl_Interp *interp); | ||||
| 	extern void yosys_tcl_activate_repl(); | ||||
| }; | ||||
| #endif | ||||
|  | @ -610,7 +610,7 @@ int main(int argc, char **argv) | |||
| 	if (run_tcl_shell) { | ||||
| #ifdef YOSYS_ENABLE_TCL | ||||
| 		yosys_tcl_activate_repl(); | ||||
| 		Tcl_Main(argc, argv, yosys_tcl_iterp_init); | ||||
| 		Tcl_Main(argc, argv, yosys_tcl_interp_init); | ||||
| #else | ||||
| 		log_error("Can't exectue TCL shell: this version of yosys is not built with TCL support enabled.\n"); | ||||
| #endif | ||||
|  |  | |||
|  | @ -533,7 +533,7 @@ static int tcl_set_param(ClientData, Tcl_Interp *interp, int objc, Tcl_Obj *cons | |||
| 	return TCL_OK; | ||||
| } | ||||
| 
 | ||||
| int yosys_tcl_iterp_init(Tcl_Interp *interp) | ||||
| int yosys_tcl_interp_init(Tcl_Interp *interp) | ||||
| { | ||||
| 	if (Tcl_Init(interp)!=TCL_OK) | ||||
| 		log_warning("Tcl_Init() call failed - %s\n",Tcl_ErrnoMsg(Tcl_GetErrno())); | ||||
|  |  | |||
|  | @ -95,6 +95,7 @@ CellTypes yosys_celltypes; | |||
| 
 | ||||
| #ifdef YOSYS_ENABLE_TCL | ||||
| Tcl_Interp *yosys_tcl_interp = NULL; | ||||
| Tcl_Interp *yosys_sdc_interp = NULL; | ||||
| #endif | ||||
| 
 | ||||
| std::set<std::string> yosys_input_files, yosys_output_files; | ||||
|  | @ -392,17 +393,18 @@ void rewrite_filename(std::string &filename) | |||
| #ifdef YOSYS_ENABLE_TCL | ||||
| 
 | ||||
| // defined in tclapi.cc
 | ||||
| extern int yosys_tcl_iterp_init(Tcl_Interp *interp); | ||||
| extern int yosys_tcl_interp_init(Tcl_Interp *interp); | ||||
| 
 | ||||
| extern Tcl_Interp *yosys_get_tcl_interp() | ||||
| { | ||||
| 	if (yosys_tcl_interp == NULL) { | ||||
| 		yosys_tcl_interp = Tcl_CreateInterp(); | ||||
| 		yosys_tcl_iterp_init(yosys_tcl_interp); | ||||
| 		yosys_tcl_interp_init(yosys_tcl_interp); | ||||
| 	} | ||||
| 	return yosys_tcl_interp; | ||||
| } | ||||
| 
 | ||||
| // Also see SdcPass
 | ||||
| struct TclPass : public Pass { | ||||
| 	TclPass() : Pass("tcl", "execute a TCL script file") { } | ||||
| 	void help() override { | ||||
|  | @ -445,6 +447,7 @@ struct TclPass : public Pass { | |||
| 		Tcl_Release(interp); | ||||
| 	} | ||||
| } TclPass; | ||||
| 
 | ||||
| #endif | ||||
| 
 | ||||
| #if defined(__linux__) || defined(__CYGWIN__) | ||||
|  |  | |||
|  | @ -57,3 +57,5 @@ OBJS += passes/cmds/abstract.o | |||
| OBJS += passes/cmds/test_select.o | ||||
| OBJS += passes/cmds/timeest.o | ||||
| OBJS += passes/cmds/linecoverage.o | ||||
| 
 | ||||
| include $(YOSYS_SRC)/passes/cmds/sdc/Makefile.inc | ||||
|  |  | |||
							
								
								
									
										3
									
								
								passes/cmds/sdc/Makefile.inc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								passes/cmds/sdc/Makefile.inc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| OBJS += passes/cmds/sdc/sdc.o | ||||
| 
 | ||||
| $(eval $(call add_share_file,share/sdc,passes/cmds/sdc/graph-stubs.sdc)) | ||||
							
								
								
									
										18
									
								
								passes/cmds/sdc/graph-stubs.sdc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								passes/cmds/sdc/graph-stubs.sdc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | |||
| proc unknown {args} { | ||||
|     global sdc_call_index | ||||
|     global sdc_calls | ||||
|     if {![info exists sdc_call_index]} { | ||||
|         set sdc_call_index 0 | ||||
|     } | ||||
|     if {![info exists sdc_calls]} { | ||||
|         set sdc_calls {} | ||||
|     } | ||||
|     set ret "YOSYS_SDC_MAGIC_NODE_$sdc_call_index" | ||||
|     incr sdc_call_index | ||||
|     lappend sdc_calls $args | ||||
|     # puts "unknown $args, returning YOSYS_SDC_MAGIC_NODE_$sdc_call_index" | ||||
|     return $ret | ||||
| } | ||||
| proc list {args} { | ||||
|     return [unknown "list" {*}$args] | ||||
| } | ||||
							
								
								
									
										762
									
								
								passes/cmds/sdc/sdc.cc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										762
									
								
								passes/cmds/sdc/sdc.cc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,762 @@ | |||
| #ifdef YOSYS_ENABLE_TCL | ||||
| 
 | ||||
| #include "kernel/register.h" | ||||
| #include "kernel/rtlil.h" | ||||
| #include "kernel/log.h" | ||||
| #include <tcl.h> | ||||
| #include <list> | ||||
| #include <optional> | ||||
| #include <iostream> | ||||
| 
 | ||||
| 
 | ||||
| USING_YOSYS_NAMESPACE | ||||
| PRIVATE_NAMESPACE_BEGIN | ||||
| 
 | ||||
| struct TclCall { | ||||
| 	Tcl_Interp *interp; | ||||
| 	int objc; | ||||
| 	Tcl_Obj* const* objv; | ||||
| }; | ||||
| 
 | ||||
| static int redirect_unknown(TclCall call); | ||||
| static size_t get_node_count(Tcl_Interp* interp); | ||||
| 
 | ||||
| struct BitSelection { | ||||
| 	bool all = false; | ||||
| 	std::vector<bool> bits = {}; | ||||
| 	void set_all() { | ||||
| 		bits.clear(); | ||||
| 		all = true; | ||||
| 	} | ||||
| 	void clear() { | ||||
| 		bits.clear(); | ||||
| 		all = false; | ||||
| 	} | ||||
| 	void set(size_t idx) { | ||||
| 		if (all) | ||||
| 			return; | ||||
| 		if (idx >= bits.size()) | ||||
| 			bits.resize(idx + 1); | ||||
| 		bits[idx] = true; | ||||
| 	} | ||||
| 	void merge(const BitSelection& other) { | ||||
| 		if (all) | ||||
| 			return; | ||||
| 		if (other.all) { | ||||
| 			set_all(); | ||||
| 			return; | ||||
| 		} | ||||
| 		if (other.bits.size() > bits.size()) | ||||
| 			bits.resize(other.bits.size()); | ||||
| 		for (size_t other_idx = 0; other_idx < other.bits.size(); other_idx++) { | ||||
| 			bool other_bit = other.bits[other_idx]; | ||||
| 			if (other_bit) | ||||
| 				set(other_idx); | ||||
| 		} | ||||
| 	} | ||||
| 	void dump() { | ||||
| 		if (!all) { | ||||
| 			for (size_t i = 0; i < bits.size(); i++) | ||||
| 				if (bits[i]) | ||||
| 					log("\t\t [%zu]\n", i); | ||||
| 		} else { | ||||
| 			log("\t\t FULL\n"); | ||||
| 		} | ||||
| 	} | ||||
| 	bool is_set(size_t idx) const { | ||||
| 		if (all) | ||||
| 			return true; | ||||
| 		if (idx >= bits.size()) | ||||
| 			return false; | ||||
| 		return bits[idx]; | ||||
| 	} | ||||
| 	// TODO actually use this
 | ||||
| 	void compress(size_t size) { | ||||
| 		if (bits.size() < size) | ||||
| 			return; | ||||
| 		for (size_t i = 0; i < size; i++) | ||||
| 			if (!bits[i]) | ||||
| 				return; | ||||
| 		bits.clear(); | ||||
| 		bits.shrink_to_fit(); | ||||
| 		all = true; | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| struct SdcObjects { | ||||
| 	enum CollectMode { | ||||
| 		// getter-side object tracking with minimal features
 | ||||
| 		SimpleGetter, | ||||
| 		// getter-side object tracking with everything
 | ||||
| 		FullGetter, | ||||
| 		// constraint-side tracking
 | ||||
| 		FullConstraint, | ||||
| 	} collect_mode; | ||||
| 	using CellPin = std::pair<Cell*, IdString>; | ||||
| 	Design* design; | ||||
| 	std::vector<std::pair<std::string, Wire*>> design_ports; | ||||
| 	std::vector<std::pair<std::string, Cell*>> design_cells; | ||||
| 	std::vector<std::pair<std::string, CellPin>> design_pins; | ||||
| 	std::vector<std::pair<std::string, Wire*>> design_nets; | ||||
| 
 | ||||
| 	using PortPattern = std::tuple<std::string, Wire*, BitSelection>; | ||||
| 	using PinPattern = std::tuple<std::string, SdcObjects::CellPin, BitSelection>; | ||||
| 	std::vector<std::vector<PortPattern>> resolved_port_pattern_sets; | ||||
| 	std::vector<std::vector<PinPattern>> resolved_pin_pattern_sets; | ||||
| 	// TODO
 | ||||
| 
 | ||||
| 	dict<std::pair<std::string, Wire*>, BitSelection> constrained_ports; | ||||
| 	pool<std::pair<std::string, Cell*>> constrained_cells; | ||||
| 	dict<std::pair<std::string, CellPin>, BitSelection> constrained_pins; | ||||
| 	dict<std::pair<std::string, Wire*>, BitSelection> constrained_nets; | ||||
| 
 | ||||
| 	void sniff_module(std::list<std::string>& hierarchy, Module* mod) { | ||||
| 		std::string prefix; | ||||
| 		for (auto mod_name : hierarchy) { | ||||
| 			if (prefix.length()) | ||||
| 				prefix += "/"; // TODO seperator?
 | ||||
| 			prefix += mod_name; | ||||
| 		} | ||||
| 
 | ||||
| 		for (auto* wire : mod->wires()) { | ||||
| 			std::string name = wire->name.str(); | ||||
| 			log_assert(name.length()); | ||||
| 			// TODO: really skip internal wires?
 | ||||
| 			if (name[0] == '$') | ||||
| 				continue; | ||||
| 			name = name.substr(1); | ||||
| 			std::string path = prefix; | ||||
| 			if (path.length()) | ||||
| 				path += "/"; | ||||
| 			path += name; | ||||
| 			design_nets.push_back(std::make_pair(path, wire)); | ||||
| 		} | ||||
| 
 | ||||
| 		for (auto* cell : mod->cells()) { | ||||
| 			std::string name = cell->name.str(); | ||||
| 			// TODO: really skip internal cells?
 | ||||
| 			if (name[0] == '$') | ||||
| 				continue; | ||||
| 			name = name.substr(1); | ||||
| 			std::string path = prefix; | ||||
| 			if (path.length()) | ||||
| 				path += "/"; | ||||
| 			path += name; | ||||
| 			design_cells.push_back(std::make_pair(path, cell)); | ||||
| 			for (auto pin : cell->connections()) { | ||||
| 				IdString pin_name = pin.first; | ||||
| 				std::string pin_name_sdc = path + "/" + pin.first.str().substr(1); | ||||
| 				design_pins.push_back(std::make_pair(pin_name_sdc, std::make_pair(cell, pin_name))); | ||||
| 			} | ||||
| 			if (auto sub_mod = mod->design->module(cell->type)) { | ||||
| 				hierarchy.push_back(name); | ||||
| 				sniff_module(hierarchy, sub_mod); | ||||
| 				hierarchy.pop_back(); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	SdcObjects(Design* design) : design(design) { | ||||
| 		Module* top = design->top_module(); | ||||
| 		if (!top) | ||||
| 			log_error("Top module couldn't be determined. Check 'top' attribute usage"); | ||||
| 		for (auto port : top->ports) { | ||||
| 			design_ports.push_back(std::make_pair(port.str().substr(1), top->wire(port))); | ||||
| 		} | ||||
| 		std::list<std::string> hierarchy{}; | ||||
| 		sniff_module(hierarchy, top); | ||||
| 	} | ||||
| 	~SdcObjects() = default; | ||||
| 
 | ||||
| 	template <typename T, typename U> | ||||
| 	void build_normal_result(Tcl_Interp* interp, std::vector<std::tuple<std::string, T, BitSelection>>&& resolved, U& tgt, std::function<size_t(T&)> width, Tcl_Obj*& result) { | ||||
| 		if (!result) | ||||
| 			result = Tcl_NewListObj(resolved.size(), nullptr); | ||||
| 		for (auto [name, obj, matching_bits] : resolved) { | ||||
| 			for (size_t i = 0; i < width(obj); i++) | ||||
| 				if (matching_bits.is_set(i)) { | ||||
| 					Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(name.c_str(), name.size())); | ||||
| 					break; | ||||
| 				} | ||||
| 
 | ||||
| 		} | ||||
| 		size_t node_count = get_node_count(interp); | ||||
| 		tgt.emplace_back(std::move(resolved)); | ||||
| 		log("%zu %zu\n", node_count, tgt.size()); | ||||
| 		log_assert(node_count == tgt.size()); | ||||
| 	} | ||||
| 	template <typename T> | ||||
| 	void merge_as_constrained(std::vector<std::tuple<std::string, T, BitSelection>>&& resolved) { | ||||
| 		for (auto [name, obj, matching_bits] : resolved) { | ||||
| 			merge_or_init(std::make_pair(name, obj), constrained_pins, matching_bits); | ||||
| 		} | ||||
| 	} | ||||
| 	void dump() { | ||||
| 		std::sort(design_ports.begin(), design_ports.end()); | ||||
| 		std::sort(design_cells.begin(), design_cells.end()); | ||||
| 		std::sort(design_pins.begin(), design_pins.end()); | ||||
| 		std::sort(design_nets.begin(), design_nets.end()); | ||||
| 		constrained_ports.sort(); | ||||
| 		constrained_cells.sort(); | ||||
| 		constrained_pins.sort(); | ||||
| 		constrained_nets.sort(); | ||||
| 		// log("Design ports:\n");
 | ||||
| 		// for (auto name : design_ports) {
 | ||||
| 		// 	log("\t%s\n", name.c_str());
 | ||||
| 		// }
 | ||||
| 		// log("Design cells:\n");
 | ||||
| 		// for (auto [name, cell] : design_cells) {
 | ||||
| 		// 	(void)cell;
 | ||||
| 		// 	log("\t%s\n", name.c_str());
 | ||||
| 		// }
 | ||||
| 		// log("Design pins:\n");
 | ||||
| 		// for (auto [name, pin] : design_pins) {
 | ||||
| 		// 	(void)pin;
 | ||||
| 		// 	log("\t%s\n", name.c_str());
 | ||||
| 		// }
 | ||||
| 		// log("Design nets:\n");
 | ||||
| 		// for (auto [name, net] : design_nets) {
 | ||||
| 		// 	(void)net;
 | ||||
| 		// 	log("\t%s\n", name.c_str());
 | ||||
| 		// }
 | ||||
| 		// log("\n");
 | ||||
| 		log("Constrained ports:\n"); | ||||
| 		for (auto [ref, bits] : constrained_ports) { | ||||
| 			auto [name, port] = ref; | ||||
| 			(void)port; | ||||
| 			log("\t%s\n", name.c_str()); | ||||
| 			bits.dump(); | ||||
| 		} | ||||
| 		log("Constrained cells:\n"); | ||||
| 		for (auto& [name, cell] : constrained_cells) { | ||||
| 			(void)cell; | ||||
| 			log("\t%s\n", name.c_str()); | ||||
| 		} | ||||
| 		log("Constrained pins:\n"); | ||||
| 		for (auto& [ref, bits] : constrained_pins) { | ||||
| 			auto [name, pin] = ref; | ||||
| 			(void)pin; | ||||
| 			log("\t%s\n", name.c_str()); | ||||
| 			bits.dump(); | ||||
| 		} | ||||
| 		log("Constrained nets:\n"); | ||||
| 		for (auto& [ref, bits] : constrained_nets) { | ||||
| 			auto [name, net] = ref; | ||||
| 			(void)net; | ||||
| 			log("\t%s\n", name.c_str()); | ||||
| 			bits.dump(); | ||||
| 		} | ||||
| 		log("\n"); | ||||
| 	} | ||||
| 
 | ||||
| 	class KeepHierarchyWorker { | ||||
| 		std::unordered_set<Module*> tracked_modules = {}; | ||||
| 		Design* design = nullptr; | ||||
| 		bool mark(Module* mod) { | ||||
| 			for (auto* cell : mod->cells()) { | ||||
| 				if (auto* submod = design->module(cell->type)) | ||||
| 					if (mark(submod)) { | ||||
| 						mod->set_bool_attribute(ID::keep_hierarchy); | ||||
| 						return true; | ||||
| 					} | ||||
| 			} | ||||
| 
 | ||||
| 			if (tracked_modules.count(mod)) { | ||||
| 				mod->set_bool_attribute(ID::keep_hierarchy); | ||||
| 				return true; | ||||
| 			} | ||||
| 
 | ||||
| 			return false; | ||||
| 		} | ||||
| 	public: | ||||
| 		KeepHierarchyWorker(SdcObjects* objects, Design* d) : design(d) { | ||||
| 			for (auto [ref, _] : objects->constrained_ports) { | ||||
| 				tracked_modules.insert(ref.second->module); | ||||
| 			} | ||||
| 			for (auto& [_, cell] : objects->constrained_cells) { | ||||
| 				tracked_modules.insert(cell->module); | ||||
| 			} | ||||
| 			for (auto& [ref, _] : objects->constrained_pins) { | ||||
| 				tracked_modules.insert(ref.second.first->module); | ||||
| 			} | ||||
| 			for (auto& [ref, _] : objects->constrained_nets) { | ||||
| 				tracked_modules.insert(ref.second->module); | ||||
| 			} | ||||
| 			log_debug("keep_hierarchy tracked modules:\n"); | ||||
| 			for (auto* mod : tracked_modules) | ||||
| 				log_debug("\t%s\n", mod->name); | ||||
| 		} | ||||
| 		bool mark() { | ||||
| 			return mark(design->top_module()); | ||||
| 		} | ||||
| 	}; | ||||
| 	void keep_hierarchy() { | ||||
| 		(void)KeepHierarchyWorker(this, design).mark(); | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| // TODO vectors
 | ||||
| // TODO cell arrays?
 | ||||
| struct MatchConfig { | ||||
| 	enum MatchMode { | ||||
| 		WILDCARD, | ||||
| 		REGEX, | ||||
| 	} match; | ||||
| 	bool match_case; | ||||
| 	enum HierMode { | ||||
| 		FLAT, | ||||
| 		TREE, | ||||
| 	} hier; | ||||
| 	MatchConfig(bool regexp_flag, bool nocase_flag, bool hierarchical_flag) : | ||||
| 		match(regexp_flag ? REGEX : WILDCARD), | ||||
| 		match_case(!nocase_flag), | ||||
| 		hier(hierarchical_flag ? FLAT : TREE) { } | ||||
| }; | ||||
| 
 | ||||
| static std::pair<bool, BitSelection> matches(std::string name, const std::string& pat, const MatchConfig& config) { | ||||
| 	(void)config; | ||||
| 	bool got_bit_index = false;; | ||||
| 	int bit_idx; | ||||
| 	std::string pat_base = pat; | ||||
| 	size_t pos = pat.rfind('['); | ||||
| 	if (pos != std::string::npos) { | ||||
| 		got_bit_index = true; | ||||
| 		pat_base = pat.substr(0, pos); | ||||
| 		std::string bit_selector = pat.substr(pos + 1, pat.rfind(']') - pos - 1); | ||||
| 		for (auto c : bit_selector) | ||||
| 			if (!std::isdigit(c)) | ||||
| 				log_error("Unsupported bit selector %s in SDC pattern %s\n", | ||||
| 							bit_selector.c_str(), pat.c_str()); | ||||
| 		bit_idx = std::stoi(bit_selector); | ||||
| 
 | ||||
| 	} | ||||
| 	BitSelection bits = {}; | ||||
| 	if (name == pat_base) { | ||||
| 		if (got_bit_index) { | ||||
| 			bits.set(bit_idx); | ||||
| 			return std::make_pair(true, bits); | ||||
| 		} else { | ||||
| 			bits.set_all(); | ||||
| 			return std::make_pair(true, bits); | ||||
| 
 | ||||
| 		} | ||||
| 	} else { | ||||
| 		return std::make_pair(false, bits); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static int graph_node(TclCall call) { | ||||
| 	// TODO is that it?
 | ||||
| 	return redirect_unknown(call); | ||||
| } | ||||
| 
 | ||||
| static int redirect_unknown(TclCall call) { | ||||
| 	// TODO redirect to different command
 | ||||
| 	Tcl_Obj *newCmd = Tcl_NewStringObj("unknown", -1); | ||||
| 	auto newObjc = call.objc + 1; | ||||
| 	Tcl_Obj **newObjv = new Tcl_Obj*[newObjc]; | ||||
| 	newObjv[0] = newCmd; | ||||
| 	for (int i = 1; i < newObjc; i++) { | ||||
| 		newObjv[i] = call.objv[i - 1]; | ||||
| 	} | ||||
| 	int result = Tcl_EvalObjv(call.interp, newObjc, newObjv, 0); | ||||
| 	Tcl_DecrRefCount(newCmd); | ||||
| 	delete[] newObjv; | ||||
| 	return result; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| struct SdcGraphNode { | ||||
| 	using Child = std::variant<SdcGraphNode*, std::string>; | ||||
| 	std::vector<Child> children; | ||||
| 	SdcGraphNode() = default; | ||||
| 	void addChild(SdcGraphNode* child) { | ||||
| 		children.push_back(child); | ||||
| 	} | ||||
| 	void addChild(std::string tcl_string) { | ||||
| 		children.push_back(tcl_string); | ||||
| 	} | ||||
| 	void dump(std::ostream& os) const { | ||||
| 		bool first = true; | ||||
| 		for (auto child : children) { | ||||
| 			if (first) { | ||||
| 				first = false; | ||||
| 			} else { | ||||
| 				os << " "; | ||||
| 			} | ||||
| 			if (auto* s = std::get_if<std::string>(&child)) | ||||
| 				os << *s; | ||||
| 			else if (SdcGraphNode*& c = *std::get_if<SdcGraphNode*>(&child)) { | ||||
| 				os << "["; | ||||
| 				c->dump(os); | ||||
| 				os << "]"; | ||||
| 			} else { | ||||
| 				log_assert(false); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| static size_t get_node_count(Tcl_Interp* interp) { | ||||
| 	const char* idx_raw = Tcl_GetVar(interp, "sdc_call_index", TCL_GLOBAL_ONLY); | ||||
| 	log_assert(idx_raw); | ||||
| 	std::string idx(idx_raw); | ||||
| 	for (auto c : idx) | ||||
| 		if (!std::isdigit(c)) | ||||
| 			log_error("sdc_call_index non-numeric value %s\n", idx.c_str()); | ||||
| 	return std::stoi(idx); | ||||
| } | ||||
| 
 | ||||
| std::vector<std::vector<std::string>> gather_nested_calls(Tcl_Interp* interp) { | ||||
| 
 | ||||
| 	Tcl_Obj* listObj = Tcl_GetVar2Ex(interp, "sdc_calls", nullptr, TCL_GLOBAL_ONLY); | ||||
| 	int listLength; | ||||
| 
 | ||||
| 	std::vector<std::vector<std::string>> sdc_calls; | ||||
| 	if (Tcl_ListObjLength(interp, listObj, &listLength) == TCL_OK) { | ||||
| 		for (int i = 0; i < listLength; i++) { | ||||
| 			Tcl_Obj* subListObj; | ||||
| 			std::vector<std::string> subList; | ||||
| 			if (Tcl_ListObjIndex(interp, listObj, i, &subListObj) != TCL_OK) { | ||||
| 				log_error("broken list of lists\n"); | ||||
| 			} | ||||
| 			int subListLength; | ||||
| 			if (Tcl_ListObjLength(interp, subListObj, &subListLength) == TCL_OK) { | ||||
| 				// Valid list - extract elements
 | ||||
| 				for (int j = 0; j < subListLength; j++) { | ||||
| 					Tcl_Obj* elementObj; | ||||
| 					if (Tcl_ListObjIndex(interp, subListObj, j, &elementObj) == TCL_OK) { | ||||
| 						const char* elementStr = Tcl_GetString(elementObj); | ||||
| 						subList.push_back(std::string(elementStr)); | ||||
| 					} | ||||
| 				} | ||||
| 			} else { | ||||
| 				// Single element, not a list
 | ||||
| 				const char* elementStr = Tcl_GetString(subListObj); | ||||
| 				subList.push_back(std::string(elementStr)); | ||||
| 			} | ||||
| 			sdc_calls.push_back(subList); | ||||
| 		} | ||||
| 	} | ||||
| 	log_assert(sdc_calls.size() == get_node_count(interp)); | ||||
| 	return sdc_calls; | ||||
| } | ||||
| 
 | ||||
| std::vector<SdcGraphNode> build_graph(const std::vector<std::vector<std::string>>& sdc_calls) { | ||||
| 	size_t node_count = sdc_calls.size(); | ||||
| 	std::vector<SdcGraphNode> graph(node_count); | ||||
| 	for (size_t i = 0; i < node_count; i++) { | ||||
| 		auto& new_node = graph[i]; | ||||
| 		for (size_t j = 0; j < sdc_calls[i].size(); j++) { | ||||
| 			auto arg = sdc_calls[i][j]; | ||||
| 			const std::string prefix = "YOSYS_SDC_MAGIC_NODE_"; | ||||
| 			auto pos = arg.find(prefix); | ||||
| 			if (pos != std::string::npos) { | ||||
| 				std::string rest = arg.substr(pos + prefix.length()); | ||||
| 				for (auto c : rest) | ||||
| 					if (!std::isdigit(c)) | ||||
| 						log_error("weird thing %s in %s\n", rest.c_str(), arg.c_str()); | ||||
| 				size_t arg_node_idx = std::stoi(rest); | ||||
| 				log_assert(arg_node_idx < graph.size()); | ||||
| 				new_node.addChild(&graph[arg_node_idx]); | ||||
| 			} else { | ||||
| 				new_node.addChild(arg); | ||||
| 			} | ||||
| 
 | ||||
| 		} | ||||
| 	} | ||||
| 	return graph; | ||||
| } | ||||
| 
 | ||||
| std::vector<bool> node_ownership(const std::vector<SdcGraphNode>& graph) { | ||||
| 	std::vector<bool> has_parent(graph.size()); | ||||
| 	for (auto node : graph) { | ||||
| 		for (auto child : node.children) { | ||||
| 			if (SdcGraphNode** pp = std::get_if<SdcGraphNode*>(&child)) { | ||||
| 				size_t idx = std::distance(&graph.front(), (const SdcGraphNode*)*pp); | ||||
| 				log_assert(idx < has_parent.size()); | ||||
| 				has_parent[idx] = true; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return has_parent; | ||||
| } | ||||
| 
 | ||||
| void dump_sdc_graph(const std::vector<SdcGraphNode>& graph, const std::vector<bool>& has_parent) { | ||||
| 	for (size_t i = 0; i < graph.size(); i++) { | ||||
| 		if (!has_parent[i]) { | ||||
| 			graph[i].dump(std::cout); | ||||
| 			std::cout << "\n"; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void inspect_globals(Tcl_Interp* interp, bool dump_mode) { | ||||
| 	std::vector<std::vector<std::string>> sdc_calls = gather_nested_calls(interp); | ||||
| 	std::vector<SdcGraphNode> graph = build_graph(sdc_calls); | ||||
| 	if (dump_mode) | ||||
| 		dump_sdc_graph(graph, node_ownership(graph)); | ||||
| } | ||||
| 
 | ||||
| // patterns -> (pattern-object-bit)s
 | ||||
| template <typename T, typename U> | ||||
| std::vector<std::tuple<std::string, T, BitSelection>> | ||||
| find_matching(U objects, const MatchConfig& config, const std::vector<std::string> &patterns, const char* obj_type) | ||||
| { | ||||
| 	std::vector<std::tuple<std::string, T, BitSelection>> resolved; | ||||
| 	for (auto pat : patterns) { | ||||
| 		bool found = false; | ||||
| 		for (auto [name, obj] : objects) { | ||||
| 			auto [does_match, matching_bits] = matches(name, pat, config); | ||||
| 			if (does_match) { | ||||
| 				found = true; | ||||
| 				resolved.push_back(std::make_tuple(name, obj, matching_bits)); | ||||
| 				// TODO add a continue statement, conditional on config
 | ||||
| 			} | ||||
| 		} | ||||
| 		if (!found) | ||||
| 			log_warning("No matches in design for %s %s\n", obj_type, pat.c_str()); | ||||
| 	} | ||||
| 	return resolved; | ||||
| } | ||||
| 
 | ||||
| struct TclOpts { | ||||
| 	const char* name; | ||||
| 	std::initializer_list<const char*> legals; | ||||
| 	TclOpts(const char* name, std::initializer_list<const char*> legals) : name(name), legals(legals)  {} | ||||
| 	bool parse_opt(Tcl_Obj* obj, const char* opt_name) { | ||||
| 		char* arg = Tcl_GetString(obj); | ||||
| 		std::string expected = std::string("-") + opt_name; | ||||
| 		if (expected == arg) { | ||||
| 			if (!std::find_if(legals.begin(), legals.end(), | ||||
| 					   [&opt_name](const char* str) { return opt_name == str; })) | ||||
| 				log_cmd_error("Illegal argument %s for %s.\n", expected.c_str(), name); | ||||
| 			return true; | ||||
| 		} | ||||
| 		return false; | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| struct GetterOpts : TclOpts { | ||||
| 	bool hierarchical_flag = false; | ||||
| 	bool regexp_flag = false; | ||||
| 	bool nocase_flag = false; | ||||
| 	std::string separator = "/"; | ||||
| 	Tcl_Obj* of_objects = nullptr; | ||||
| 	std::vector<std::string> patterns = {}; | ||||
| 	GetterOpts(const char* name, std::initializer_list<const char*> legals) : TclOpts(name, legals) {} | ||||
| 	template<typename T> | ||||
| 	bool parse_flag(Tcl_Obj* obj, const char* flag_name, T& flag_var) { | ||||
| 		bool ret = parse_opt(obj, flag_name); | ||||
| 		if (ret) | ||||
| 			flag_var = true; | ||||
| 		return ret; | ||||
| 	} | ||||
| 	void parse(int objc, Tcl_Obj* const objv[]) { | ||||
| 		int i = 1; | ||||
| 		for (; i < objc; i++) { | ||||
| 			if (parse_flag(objv[i], "hierarchical", hierarchical_flag)) continue; | ||||
| 			if (parse_flag(objv[i], "hier", hierarchical_flag)) continue; | ||||
| 			if (parse_flag(objv[i], "regexp", regexp_flag)) continue; | ||||
| 			if (parse_flag(objv[i], "nocase", nocase_flag)) continue; | ||||
| 			if (parse_opt(objv[i], "hsc")) { | ||||
| 				log_assert(i + 1 < objc); | ||||
| 				separator = Tcl_GetString(objv[++i]); | ||||
| 				continue; | ||||
| 			} | ||||
| 			if (parse_opt(objv[i], "of_objects")) { | ||||
| 				log_assert(i + 1 < objc); | ||||
| 				of_objects = objv[++i]; | ||||
| 				continue; | ||||
| 			} | ||||
| 			break; | ||||
| 		} | ||||
| 		for (; i < objc; i++) { | ||||
| 			patterns.push_back(Tcl_GetString(objv[i])); | ||||
| 		} | ||||
| 	}; | ||||
| 	void check_simple() { | ||||
| 		if (regexp_flag || hierarchical_flag || nocase_flag || separator != "/" || of_objects) { | ||||
| 			log_error("%s got unexpected flags in simple mode\n", name); | ||||
| 		} | ||||
| 		if (patterns.size() != 1) | ||||
| 			log_error("%s got unexpected number of patterns in simple mode: %zu\n", name, patterns.size()); | ||||
| 	} | ||||
| 	void check_simple_sep() { | ||||
| 		if (separator != "/") | ||||
| 			log_error("Only '/' accepted as separator"); | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| template <typename T> | ||||
| void merge_or_init(const T& key, dict<T, BitSelection>& dst, const BitSelection& src) { | ||||
| 	if (dst.count(key) == 0) { | ||||
| 		dst[key] = src; | ||||
| 	} else { | ||||
| 		dst[key].merge(src); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) | ||||
| { | ||||
| 	auto* objects = (SdcObjects*)data; | ||||
| 	GetterOpts opts("get_pins", {"hierarchical", "hier", "regexp", "nocase", "hsc", "of_objects"}); | ||||
| 	opts.parse(objc, objv); | ||||
| 	if (objects->collect_mode == SdcObjects::CollectMode::SimpleGetter) | ||||
| 		opts.check_simple(); | ||||
| 	opts.check_simple_sep(); | ||||
| 
 | ||||
| 	MatchConfig config(opts.regexp_flag, opts.nocase_flag, opts.hierarchical_flag); | ||||
| 	std::vector<std::tuple<std::string, SdcObjects::CellPin, BitSelection>> resolved; | ||||
| 	const auto& pins = objects->design_pins; | ||||
| 	resolved = find_matching<SdcObjects::CellPin, decltype(pins)>(pins, config, opts.patterns, "pin"); | ||||
| 
 | ||||
| 	return graph_node(TclCall{interp, objc, objv}); | ||||
| } | ||||
| 
 | ||||
| static int sdc_get_ports_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) | ||||
| { | ||||
| 	auto* objects = (SdcObjects*)data; | ||||
| 	GetterOpts opts("get_ports", {"regexp", "nocase"}); | ||||
| 	opts.parse(objc, objv); | ||||
| 	if (objects->collect_mode == SdcObjects::CollectMode::SimpleGetter) | ||||
| 		opts.check_simple(); | ||||
| 
 | ||||
| 	MatchConfig config(opts.regexp_flag, opts.nocase_flag, false); | ||||
| 	std::vector<std::tuple<std::string, Wire*, BitSelection>> resolved; | ||||
| 	const auto& ports = objects->design_ports; | ||||
| 	resolved = find_matching<Wire*, decltype(ports)>(ports, config, opts.patterns, "port"); | ||||
| 
 | ||||
| 	for (auto [name, wire, matching_bits] : resolved) { | ||||
| 		if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) | ||||
| 			merge_or_init(std::make_pair(name, wire), objects->constrained_ports, matching_bits); | ||||
| 	} | ||||
| 
 | ||||
| 	return graph_node(TclCall{interp, objc, objv}); | ||||
| } | ||||
| 
 | ||||
| static int sdc_get_nets_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) | ||||
| { | ||||
| 	auto* objects = (SdcObjects*)data; | ||||
| 	GetterOpts opts("get_nets", {"hierarchical", "hier", "regexp", "nocase", "hsc", "of_objects"}); | ||||
| 	opts.parse(objc, objv); | ||||
| 	if (objects->collect_mode == SdcObjects::CollectMode::SimpleGetter) | ||||
| 		opts.check_simple(); | ||||
| 
 | ||||
| 	MatchConfig config(opts.regexp_flag, opts.nocase_flag, false); | ||||
| 	std::vector<std::tuple<std::string, Wire*, BitSelection>> resolved; | ||||
| 	const auto& ports = objects->design_nets; | ||||
| 	resolved = find_matching<Wire*, decltype(ports)>(ports, config, opts.patterns, "net"); | ||||
| 
 | ||||
| 	for (auto [name, wire, matching_bits] : resolved) { | ||||
| 		if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) | ||||
| 			merge_or_init(std::make_pair(name, wire), objects->constrained_nets, matching_bits); | ||||
| 	} | ||||
| 
 | ||||
| 	return graph_node(TclCall{interp, objc, objv}); | ||||
| } | ||||
| 
 | ||||
| class SDCInterpreter | ||||
| { | ||||
| private: | ||||
| 	Tcl_Interp* interp = nullptr; | ||||
| public: | ||||
| 	std::unique_ptr<SdcObjects> objects; | ||||
| 	~SDCInterpreter() { | ||||
| 		if (interp) | ||||
| 			Tcl_DeleteInterp(interp); | ||||
| 	} | ||||
| 	static SDCInterpreter& get() { | ||||
| 		static SDCInterpreter instance; | ||||
| 		return instance; | ||||
| 	} | ||||
| 	Tcl_Interp* fresh_interp(Design* design) { | ||||
| 		if (interp) | ||||
| 			Tcl_DeleteInterp(interp); | ||||
| 
 | ||||
| 		interp = Tcl_CreateInterp(); | ||||
| 		if (Tcl_Init(interp)!=TCL_OK) | ||||
| 			log_error("Tcl_Init() call failed - %s\n",Tcl_ErrnoMsg(Tcl_GetErrno())); | ||||
| 
 | ||||
| 		objects = std::make_unique<SdcObjects>(design); | ||||
| 		objects->collect_mode = SdcObjects::CollectMode::SimpleGetter; | ||||
| 		Tcl_CreateObjCommand(interp, "get_pins", sdc_get_pins_cmd, (ClientData) objects.get(), NULL); | ||||
| 		Tcl_CreateObjCommand(interp, "get_nets", sdc_get_nets_cmd, (ClientData) objects.get(), NULL); | ||||
| 		Tcl_CreateObjCommand(interp, "get_ports", sdc_get_ports_cmd, (ClientData) objects.get(), NULL); | ||||
| 		return interp; | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| // Also see TclPass
 | ||||
| struct SdcPass : public Pass { | ||||
| 	void help() override | ||||
| 	{ | ||||
| 		//   |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
 | ||||
| 		log("\n"); | ||||
| 		log("    sdc [options] file\n"); | ||||
| 		log("\n"); | ||||
| 		log("Read the SDC file for the current design.\n"); | ||||
| 		log("\n"); | ||||
| 		log("    -dump\n"); | ||||
| 		log("        Dump the referenced design objects.\n"); | ||||
| 		log("\n"); | ||||
| 		log("    -dump-graph\n"); | ||||
| 		log("        Dump the uninterpreted call graph.\n"); | ||||
| 		log("\n"); | ||||
| 		log("    -keep_hierarchy\n"); | ||||
| 		log("        Add keep_hierarchy attributes while retaining SDC validity.\n"); | ||||
| 		log("\n"); | ||||
| 	} | ||||
| 	SdcPass() : Pass("sdc", "sniff at some SDC") { } | ||||
| 	void execute(std::vector<std::string> args, RTLIL::Design *design) override { | ||||
| 		log_header(design, "Executing SDC pass.\n"); | ||||
| 		log_experimental("sdc"); | ||||
| 		size_t argidx; | ||||
| 		bool dump_mode = false; | ||||
| 		bool dump_graph_mode = false; | ||||
| 		bool keep_hierarchy_mode = false; | ||||
| 		std::vector<std::string> stubs_paths; | ||||
| 		for (argidx = 1; argidx < args.size(); argidx++) { | ||||
| 			if (args[argidx] == "-dump") { | ||||
| 				dump_mode = true; | ||||
| 				continue; | ||||
| 			} else if (args[argidx] == "-dump-graph") { | ||||
| 				dump_graph_mode = true; | ||||
| 				continue; | ||||
| 			} else if (args[argidx] == "-keep_hierarchy") { | ||||
| 				keep_hierarchy_mode = true; | ||||
| 				continue; | ||||
| 			} else if (args[argidx] == "-stubs" && argidx+1 < args.size()) { | ||||
| 				stubs_paths.push_back(args[++argidx]); | ||||
| 				continue; | ||||
| 			} | ||||
| 			break; | ||||
| 		} | ||||
| 		if (argidx >= args.size()) | ||||
| 			log_cmd_error("Missing SDC file.\n"); | ||||
| 
 | ||||
| 		std::string sdc_path = args[argidx++]; | ||||
| 		if (argidx < args.size()) | ||||
| 			log_cmd_error("Unexpected extra positional argument %s after SDC file %s.\n", args[argidx], sdc_path); | ||||
| 		SDCInterpreter& sdc = SDCInterpreter::get(); | ||||
| 		Tcl_Interp *interp = sdc.fresh_interp(design); | ||||
| 		Tcl_Preserve(interp); | ||||
| 		std::string stub_path = "+/sdc/graph-stubs.sdc"; | ||||
| 		rewrite_filename(stub_path); | ||||
| 		if (Tcl_EvalFile(interp, stub_path.c_str()) != TCL_OK) | ||||
| 			log_cmd_error("SDC interpreter returned an error in stub preamble file: %s\n", Tcl_GetStringResult(interp)); | ||||
| 		for (auto path : stubs_paths) | ||||
| 			if (Tcl_EvalFile(interp, path.c_str()) != TCL_OK) | ||||
| 				log_cmd_error("SDC interpreter returned an error in OpenSTA stub file %s: %s\n", path.c_str(), Tcl_GetStringResult(interp)); | ||||
| 		if (Tcl_EvalFile(interp, sdc_path.c_str()) != TCL_OK) | ||||
| 			log_cmd_error("SDC interpreter returned an error: %s\n", Tcl_GetStringResult(interp)); | ||||
| 		if (dump_mode) | ||||
| 			sdc.objects->dump(); | ||||
| 		if (keep_hierarchy_mode) | ||||
| 			sdc.objects->keep_hierarchy(); | ||||
| 		inspect_globals(interp, dump_graph_mode); | ||||
| 		Tcl_Release(interp); | ||||
| 	} | ||||
| } SdcPass; | ||||
| 
 | ||||
| YOSYS_NAMESPACE_END | ||||
| #endif | ||||
							
								
								
									
										70
									
								
								tests/sdc/alu_sub.sdc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								tests/sdc/alu_sub.sdc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,70 @@ | |||
| ############################################################################### | ||||
| # Created by write_sdc | ||||
| # Fri Oct  3 11:26:00 2025 | ||||
| ############################################################################### | ||||
| current_design wrapper | ||||
| ############################################################################### | ||||
| # Timing Constraints | ||||
| ############################################################################### | ||||
| create_clock -name this_clk -period 1.0000 [get_ports {clk}] | ||||
| create_clock -name that_clk -period 2.0000  | ||||
| create_clock -name another_clk -period 2.0000 \ | ||||
|     [list [get_ports {A[0]}]\ | ||||
|           [get_ports {A[1]}]\ | ||||
|           [get_ports {A[2]}]\ | ||||
|           [get_ports {A[3]}]\ | ||||
|           [get_ports {A[4]}]\ | ||||
|           [get_ports {A[5]}]\ | ||||
|           [get_ports {A[6]}]\ | ||||
|           [get_ports {A[7]}]\ | ||||
|           [get_ports {B[0]}]\ | ||||
|           [get_ports {B[1]}]\ | ||||
|           [get_ports {B[2]}]\ | ||||
|           [get_ports {B[3]}]\ | ||||
|           [get_ports {B[4]}]\ | ||||
|           [get_ports {B[5]}]\ | ||||
|           [get_ports {B[6]}]\ | ||||
|           [get_ports {B[7]}]] | ||||
| set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {A[0]}] | ||||
| set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {A[0]}] | ||||
| set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {A[1]}] | ||||
| set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {A[1]}] | ||||
| set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {A[2]}] | ||||
| set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {A[2]}] | ||||
| set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {A[3]}] | ||||
| set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {A[3]}] | ||||
| set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {A[4]}] | ||||
| set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {A[4]}] | ||||
| set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {A[5]}] | ||||
| set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {A[5]}] | ||||
| set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {A[6]}] | ||||
| set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {A[6]}] | ||||
| set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {A[7]}] | ||||
| set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {A[7]}] | ||||
| set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {B[0]}] | ||||
| set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {B[0]}] | ||||
| set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {B[1]}] | ||||
| set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {B[1]}] | ||||
| set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {B[2]}] | ||||
| set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {B[2]}] | ||||
| set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {B[3]}] | ||||
| set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {B[3]}] | ||||
| set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {B[4]}] | ||||
| set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {B[4]}] | ||||
| set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {B[5]}] | ||||
| set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {B[5]}] | ||||
| set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {B[6]}] | ||||
| set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {B[6]}] | ||||
| set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {B[7]}] | ||||
| set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {B[7]}] | ||||
| group_path -name operation_group\ | ||||
|     -through [list [get_nets {alu/operation[0]}]\ | ||||
|            [get_nets {alu/operation[1]}]\ | ||||
|            [get_nets {alu/operation[2]}]\ | ||||
|            [get_nets {alu/operation[3]}]] | ||||
| ############################################################################### | ||||
| # Environment | ||||
| ############################################################################### | ||||
| ############################################################################### | ||||
| # Design Rules | ||||
| ############################################################################### | ||||
							
								
								
									
										62
									
								
								tests/sdc/alu_sub.v
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								tests/sdc/alu_sub.v
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,62 @@ | |||
| module adder( | ||||
|     input [7:0] a, input [7:0] b, output [7:0] y | ||||
| ); | ||||
|     assign y = a + b; | ||||
| endmodule | ||||
| 
 | ||||
| module wrapper( | ||||
| 	input clk, | ||||
| 	input [7:0] A, | ||||
| 	input [7:0] B, | ||||
| 	input [3:0] op, | ||||
| 	output reg [7:0] result | ||||
| ); | ||||
| 	wire CF, ZF, SF; | ||||
| 	alu alu( | ||||
| 		.clk(clk), | ||||
| 		.A(A), | ||||
| 		.B(B), | ||||
| 		.operation(op), | ||||
| 		.result(result), | ||||
| 		.CF(CF), | ||||
| 		.ZF(ZF), | ||||
| 		.SF(SF) | ||||
| 	); | ||||
| endmodule | ||||
| 
 | ||||
| module alu( | ||||
| 	input clk, | ||||
| 	input [7:0] A, | ||||
| 	input [7:0] B, | ||||
| 	input [3:0] operation, | ||||
| 	output reg [7:0] result, | ||||
| 	output reg CF, | ||||
| 	output reg ZF, | ||||
| 	output reg SF | ||||
| ); | ||||
| 
 | ||||
| 	localparam ALU_OP_ADD /* verilator public_flat */ = 4'b0000; | ||||
| 	localparam ALU_OP_SUB /* verilator public_flat */ = 4'b0001; | ||||
| 
 | ||||
| 	reg [8:0] tmp; | ||||
| 	reg [7:0] added; | ||||
| 
 | ||||
| 	adder adder(.a(A), .b(B), .y(added)); | ||||
| 
 | ||||
| 	always @(posedge clk) | ||||
| 	begin | ||||
| 		case (operation) | ||||
| 			ALU_OP_ADD : | ||||
| 				tmp = added; | ||||
| 			ALU_OP_SUB : | ||||
| 				tmp = A - B; | ||||
| 		endcase | ||||
| 
 | ||||
| 		CF <= tmp[8]; | ||||
| 		ZF <= tmp[7:0] == 0; | ||||
| 		SF <= tmp[7]; | ||||
| 
 | ||||
| 		result <= tmp[7:0]; | ||||
| 	end | ||||
| endmodule | ||||
| 
 | ||||
							
								
								
									
										14
									
								
								tests/sdc/alu_sub.ys
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								tests/sdc/alu_sub.ys
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | |||
| read_verilog alu_sub.v | ||||
| proc | ||||
| hierarchy -auto-top | ||||
| 
 | ||||
| select -assert-mod-count 1 adder | ||||
| select -assert-mod-count 1 wrapper | ||||
| select -assert-mod-count 1 alu | ||||
| 
 | ||||
| sdc -keep_hierarchy alu_sub.sdc | ||||
| flatten | ||||
| 
 | ||||
| select -assert-mod-count 0 adder | ||||
| select -assert-mod-count 1 wrapper | ||||
| select -assert-mod-count 1 alu | ||||
							
								
								
									
										4
									
								
								tests/sdc/run-test.sh
									
										
									
									
									
										Executable file
									
								
							
							
						
						
									
										4
									
								
								tests/sdc/run-test.sh
									
										
									
									
									
										Executable file
									
								
							|  | @ -0,0 +1,4 @@ | |||
| #!/usr/bin/env bash | ||||
| set -eu | ||||
| source ../gen-tests-makefile.sh | ||||
| generate_mk --yosys-scripts --bash | ||||
							
								
								
									
										2
									
								
								tests/sdc/side-effects.sdc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								tests/sdc/side-effects.sdc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,2 @@ | |||
| puts "This should print something:" | ||||
| puts [get_ports {A[0]}] | ||||
							
								
								
									
										4
									
								
								tests/sdc/side-effects.sh
									
										
									
									
									
										Executable file
									
								
							
							
						
						
									
										4
									
								
								tests/sdc/side-effects.sh
									
										
									
									
									
										Executable file
									
								
							|  | @ -0,0 +1,4 @@ | |||
| #!/usr/bin/env bash | ||||
| 
 | ||||
| ../../yosys -p 'read_verilog alu_sub.v; proc; hierarchy -auto-top; sdc side-effects.sdc' | grep 'This should print something: | ||||
| YOSYS_SDC_MAGIC_NODE_0' | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue