diff --git a/Makefile b/Makefile index b744763c8..f162741a1 100644 --- a/Makefile +++ b/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 diff --git a/kernel/driver.cc b/kernel/driver.cc index 795fd9fc5..c7c009a63 100644 --- a/kernel/driver.cc +++ b/kernel/driver.cc @@ -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 diff --git a/kernel/tclapi.cc b/kernel/tclapi.cc index a2ebaffa2..ec0483a4a 100644 --- a/kernel/tclapi.cc +++ b/kernel/tclapi.cc @@ -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())); diff --git a/kernel/yosys.cc b/kernel/yosys.cc index ca1db3aa6..774ce0026 100644 --- a/kernel/yosys.cc +++ b/kernel/yosys.cc @@ -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 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__) diff --git a/passes/cmds/Makefile.inc b/passes/cmds/Makefile.inc index 9bf615a7e..0aec115d9 100644 --- a/passes/cmds/Makefile.inc +++ b/passes/cmds/Makefile.inc @@ -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 diff --git a/passes/cmds/sdc/Makefile.inc b/passes/cmds/sdc/Makefile.inc new file mode 100644 index 000000000..7c8036550 --- /dev/null +++ b/passes/cmds/sdc/Makefile.inc @@ -0,0 +1,3 @@ +OBJS += passes/cmds/sdc/sdc.o + +$(eval $(call add_share_file,share/sdc,passes/cmds/sdc/graph-stubs.sdc)) diff --git a/passes/cmds/sdc/graph-stubs.sdc b/passes/cmds/sdc/graph-stubs.sdc new file mode 100644 index 000000000..b668f4d18 --- /dev/null +++ b/passes/cmds/sdc/graph-stubs.sdc @@ -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] +} \ No newline at end of file diff --git a/passes/cmds/sdc/sdc.cc b/passes/cmds/sdc/sdc.cc new file mode 100644 index 000000000..030a57c6c --- /dev/null +++ b/passes/cmds/sdc/sdc.cc @@ -0,0 +1,762 @@ +#ifdef YOSYS_ENABLE_TCL + +#include "kernel/register.h" +#include "kernel/rtlil.h" +#include "kernel/log.h" +#include +#include +#include +#include + + +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 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; + Design* design; + std::vector> design_ports; + std::vector> design_cells; + std::vector> design_pins; + std::vector> design_nets; + + using PortPattern = std::tuple; + using PinPattern = std::tuple; + std::vector> resolved_port_pattern_sets; + std::vector> resolved_pin_pattern_sets; + // TODO + + dict, BitSelection> constrained_ports; + pool> constrained_cells; + dict, BitSelection> constrained_pins; + dict, BitSelection> constrained_nets; + + void sniff_module(std::list& 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 hierarchy{}; + sniff_module(hierarchy, top); + } + ~SdcObjects() = default; + + template + void build_normal_result(Tcl_Interp* interp, std::vector>&& resolved, U& tgt, std::function 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 + void merge_as_constrained(std::vector>&& 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 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 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; + std::vector 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(&child)) + os << *s; + else if (SdcGraphNode*& c = *std::get_if(&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> gather_nested_calls(Tcl_Interp* interp) { + + Tcl_Obj* listObj = Tcl_GetVar2Ex(interp, "sdc_calls", nullptr, TCL_GLOBAL_ONLY); + int listLength; + + std::vector> sdc_calls; + if (Tcl_ListObjLength(interp, listObj, &listLength) == TCL_OK) { + for (int i = 0; i < listLength; i++) { + Tcl_Obj* subListObj; + std::vector 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 build_graph(const std::vector>& sdc_calls) { + size_t node_count = sdc_calls.size(); + std::vector 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 node_ownership(const std::vector& graph) { + std::vector has_parent(graph.size()); + for (auto node : graph) { + for (auto child : node.children) { + if (SdcGraphNode** pp = std::get_if(&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& graph, const std::vector& 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> sdc_calls = gather_nested_calls(interp); + std::vector graph = build_graph(sdc_calls); + if (dump_mode) + dump_sdc_graph(graph, node_ownership(graph)); +} + +// patterns -> (pattern-object-bit)s +template +std::vector> +find_matching(U objects, const MatchConfig& config, const std::vector &patterns, const char* obj_type) +{ + std::vector> 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 legals; + TclOpts(const char* name, std::initializer_list 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 patterns = {}; + GetterOpts(const char* name, std::initializer_list legals) : TclOpts(name, legals) {} + template + 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 +void merge_or_init(const T& key, dict& 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> resolved; + const auto& pins = objects->design_pins; + resolved = find_matching(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> resolved; + const auto& ports = objects->design_ports; + resolved = find_matching(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> resolved; + const auto& ports = objects->design_nets; + resolved = find_matching(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 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(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 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 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 diff --git a/tests/sdc/alu_sub.sdc b/tests/sdc/alu_sub.sdc new file mode 100644 index 000000000..f298d20bc --- /dev/null +++ b/tests/sdc/alu_sub.sdc @@ -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 +############################################################################### diff --git a/tests/sdc/alu_sub.v b/tests/sdc/alu_sub.v new file mode 100644 index 000000000..d66cad18e --- /dev/null +++ b/tests/sdc/alu_sub.v @@ -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 + diff --git a/tests/sdc/alu_sub.ys b/tests/sdc/alu_sub.ys new file mode 100644 index 000000000..7e804e7a0 --- /dev/null +++ b/tests/sdc/alu_sub.ys @@ -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 diff --git a/tests/sdc/run-test.sh b/tests/sdc/run-test.sh new file mode 100755 index 000000000..971664bdb --- /dev/null +++ b/tests/sdc/run-test.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -eu +source ../gen-tests-makefile.sh +generate_mk --yosys-scripts --bash \ No newline at end of file diff --git a/tests/sdc/side-effects.sdc b/tests/sdc/side-effects.sdc new file mode 100644 index 000000000..2c2126f84 --- /dev/null +++ b/tests/sdc/side-effects.sdc @@ -0,0 +1,2 @@ +puts "This should print something:" +puts [get_ports {A[0]}] \ No newline at end of file diff --git a/tests/sdc/side-effects.sh b/tests/sdc/side-effects.sh new file mode 100755 index 000000000..88d6154a1 --- /dev/null +++ b/tests/sdc/side-effects.sh @@ -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'