Merge pull request #27 from antmicro/sdc_plugin
SDC plugin
diff --git a/Makefile b/Makefile
index 6818481..0427c0d 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-PLUGIN_LIST := fasm xdc params selection
+PLUGIN_LIST := fasm xdc params selection sdc
PLUGINS := $(foreach plugin,$(PLUGIN_LIST),$(plugin).so)
PLUGINS_INSTALL := $(foreach plugin,$(PLUGIN_LIST),install_$(plugin))
PLUGINS_CLEAN := $(foreach plugin,$(PLUGIN_LIST),clean_$(plugin))
diff --git a/sdc-plugin/.clang-format b/sdc-plugin/.clang-format
new file mode 100644
index 0000000..3b94352
--- /dev/null
+++ b/sdc-plugin/.clang-format
@@ -0,0 +1,151 @@
+---
+Language: Cpp
+BasedOnStyle: Chromium
+AccessModifierOffset: -1
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlines: Left
+AlignOperands: true
+AlignTrailingComments: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: All
+AllowShortIfStatementsOnASingleLine: true
+AllowShortLoopsOnASingleLine: true
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: true
+AlwaysBreakTemplateDeclarations: Yes
+BinPackArguments: true
+BinPackParameters: true
+BraceWrapping:
+ AfterClass: false
+ AfterControlStatement: false
+ AfterEnum: false
+ AfterFunction: false
+ AfterNamespace: false
+ AfterObjCDeclaration: false
+ AfterStruct: false
+ AfterUnion: false
+ AfterExternBlock: false
+ BeforeCatch: false
+ BeforeElse: false
+ IndentBraces: false
+ SplitEmptyFunction: true
+ SplitEmptyRecord: true
+ SplitEmptyNamespace: true
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Attach
+BreakBeforeInheritanceComma: false
+BreakInheritanceList: BeforeColon
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializersBeforeComma: false
+BreakConstructorInitializers: BeforeColon
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: true
+ColumnLimit: 80
+CommentPragmas: '^ IWYU pragma:'
+CompactNamespaces: false
+ConstructorInitializerAllOnOneLineOrOnePerLine: true
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DerivePointerAlignment: true
+DisableFormat: false
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: true
+ForEachMacros:
+ - foreach
+ - Q_FOREACH
+ - BOOST_FOREACH
+IncludeBlocks: Preserve
+IncludeCategories:
+ - Regex: '^<ext/.*\.h>'
+ Priority: 2
+ - Regex: '^<.*\.h>'
+ Priority: 1
+ - Regex: '^<.*'
+ Priority: 2
+ - Regex: '.*'
+ Priority: 3
+IncludeIsMainRegex: '([-_](test|unittest))?$'
+IndentCaseLabels: true
+IndentPPDirectives: None
+IndentWidth: 4
+IndentWrappedFunctionNames: false
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: false
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBinPackProtocolList: Never
+ObjCBlockIndentWidth: 2
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakAssignment: 2
+PenaltyBreakBeforeFirstCallParameter: 1
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakString: 1000
+PenaltyBreakTemplateDeclaration: 10
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 200
+PointerAlignment: Left
+RawStringFormats:
+ - Language: Cpp
+ Delimiters:
+ - cc
+ - CC
+ - cpp
+ - Cpp
+ - CPP
+ - 'c++'
+ - 'C++'
+ CanonicalDelimiter: ''
+ BasedOnStyle: google
+ - Language: TextProto
+ Delimiters:
+ - pb
+ - PB
+ - proto
+ - PROTO
+ EnclosingFunctions:
+ - EqualsProto
+ - EquivToProto
+ - PARSE_PARTIAL_TEXT_PROTO
+ - PARSE_TEST_PROTO
+ - PARSE_TEXT_PROTO
+ - ParseTextOrDie
+ - ParseTextProtoOrDie
+ CanonicalDelimiter: ''
+ BasedOnStyle: google
+ReflowComments: true
+SortIncludes: true
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: ControlStatements
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 2
+SpacesInAngles: false
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard: Auto
+StatementMacros:
+ - Q_UNUSED
+ - QT_REQUIRE_VERSION
+TabWidth: 8
+UseTab: ForIndentation
+...
+
diff --git a/sdc-plugin/Makefile b/sdc-plugin/Makefile
new file mode 100644
index 0000000..b4694ca
--- /dev/null
+++ b/sdc-plugin/Makefile
@@ -0,0 +1,25 @@
+CXX = $(shell yosys-config --cxx)
+CXXFLAGS = $(shell yosys-config --cxxflags)
+LDFLAGS = $(shell yosys-config --ldflags)
+LDLIBS = $(shell yosys-config --ldlibs)
+PLUGINS_DIR = $(shell yosys-config --datdir)/plugins
+
+OBJS = buffers.o clocks.o propagation.o sdc.o
+
+sdc.so: $(OBJS)
+ $(CXX) $(CXXFLAGS) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS)
+
+install_plugin: sdc.so
+ mkdir -p $(PLUGINS_DIR)
+ cp $< $(PLUGINS_DIR)/$<
+
+test:
+ $(MAKE) -C tests all
+
+.PHONY: install
+install: install_plugin
+
+clean:
+ rm -f *.d *.o *.so
+ $(MAKE) -C tests clean
+
diff --git a/sdc-plugin/buffers.cc b/sdc-plugin/buffers.cc
new file mode 100644
index 0000000..eb4d0a7
--- /dev/null
+++ b/sdc-plugin/buffers.cc
@@ -0,0 +1,24 @@
+/*
+ * yosys -- Yosys Open SYnthesis Suite
+ *
+ * Copyright (C) 2020 The Symbiflow Authors
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include "buffers.h"
+
+const std::vector<std::string> Pll::inputs = {"CLKIN1", "CLKIN2"};
+const std::vector<std::string> Pll::outputs = {"CLKOUT0", "CLKOUT1", "CLKOUT2",
+ "CLKOUT3", "CLKOUT4", "CLKOUT5"};
+const float Pll::delay = 0;
+const std::string Pll::name = "PLLE2_ADV";
diff --git a/sdc-plugin/buffers.h b/sdc-plugin/buffers.h
new file mode 100644
index 0000000..fcce64f
--- /dev/null
+++ b/sdc-plugin/buffers.h
@@ -0,0 +1,121 @@
+/*
+ * yosys -- Yosys Open SYnthesis Suite
+ *
+ * Copyright (C) 2020 The Symbiflow Authors
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifndef _BUFFERS_H_
+#define _BUFFERS_H_
+
+#include <cassert>
+#include <initializer_list>
+#include <string>
+#include <unordered_map>
+#include <vector>
+#include "kernel/rtlil.h"
+
+USING_YOSYS_NAMESPACE
+
+struct Buffer {
+ Buffer(float delay, const std::string& name, const std::string& output)
+ : delay(delay), name(name), output(output) {}
+ float delay;
+ std::string name;
+ std::string output;
+};
+
+struct IBuf : Buffer {
+ IBuf() : Buffer(0, "IBUF", "O"){};
+};
+
+struct Bufg : Buffer {
+ Bufg() : Buffer(1, "BUFG", "O"){};
+};
+
+struct Pll {
+ Pll(RTLIL::Cell* cell) : cell(cell) {
+ assert(RTLIL::unescape_id(cell->type) == "PLLE2_ADV");
+ if (cell->hasParam(ID(CLKIN1_PERIOD))) {
+ clkin1_period =
+ std::stof(cell->getParam(ID(CLKIN1_PERIOD)).decode_string());
+ }
+ if (cell->hasParam(ID(CLKIN2_PERIOD))) {
+ clkin2_period =
+ std::stof(cell->getParam(ID(CLKIN2_PERIOD)).decode_string());
+ }
+ if (cell->hasParam(ID(CLKFBOUT_MULT))) {
+ clk_mult = cell->getParam(ID(CLKFBOUT_MULT)).as_int();
+ }
+ if (cell->hasParam(ID(DIVCLK_DIVIDE))) {
+ divclk_divisor = cell->getParam(ID(DIVCLK_DIVIDE)).as_int();
+ }
+ for (auto clk_output : outputs) {
+ // CLKOUT[0-5]_DIVIDE
+ RTLIL::IdString param(RTLIL::escape_id(clk_output + "_DIVIDE"));
+ if (cell->hasParam(param)) {
+ clkout_divisors[clk_output] = cell->getParam(param).as_int();
+ } else {
+ clkout_divisors[clk_output] = 1;
+ }
+ clkout_period[clk_output] = CalculatePeriod(clk_output);
+
+ // CLKOUT[0-5]_PHASE
+ param = RTLIL::escape_id(clk_output + "_PHASE");
+ if (cell->hasParam(param)) {
+ clkout_phase[clk_output] = std::stof(cell->getParam(param).decode_string());
+ } else {
+ clkout_phase[clk_output] = 0.0;
+ }
+ // Take the delay off the PLL into account
+ clkout_shift[clk_output] = CalculateShift(clk_output) + delay;
+
+ // CLKOUT[0-5]_DUTY_CYCLE
+ param = RTLIL::escape_id(clk_output + "_DUTY_CYCLE");
+ if (cell->hasParam(param)) {
+ clkout_duty_cycle[clk_output] = std::stof(cell->getParam(param).decode_string());
+ } else {
+ clkout_duty_cycle[clk_output] = 0.5;
+ }
+ }
+ };
+
+ // CLKOUT[0-5]_PERIOD = CLKIN1_PERIOD * CLKOUT[0-5]_DIVIDE * DIVCLK_DIVIDE /
+ // CLKFBOUT_MULT
+ // TODO Check the value on CLKINSEL
+ float CalculatePeriod(const std::string& output) {
+ return clkin1_period * clkout_divisors.at(output) / clk_mult *
+ divclk_divisor;
+ }
+
+ float CalculateShift(const std::string& output) {
+ return clkout_period.at(output) * clkout_phase.at(output) / 360.0;
+ }
+
+ static const float delay;
+ static const std::string name;
+ static const std::vector<std::string> inputs;
+ static const std::vector<std::string> outputs;
+ RTLIL::Cell* cell;
+ float clkin1_period = 0;
+ float clkin2_period = 0;
+ std::unordered_map<std::string, float> clkout_period;
+ std::unordered_map<std::string, float> clkout_duty_cycle;
+ std::unordered_map<std::string, float> clkout_phase;
+ std::unordered_map<std::string, float> clkout_shift;
+ std::unordered_map<std::string, int> clkout_divisors;
+ int divclk_divisor = 1;
+ int clk_mult = 5;
+};
+
+#endif // _BUFFERS_H_
diff --git a/sdc-plugin/clocks.cc b/sdc-plugin/clocks.cc
new file mode 100644
index 0000000..9fa811e
--- /dev/null
+++ b/sdc-plugin/clocks.cc
@@ -0,0 +1,246 @@
+/*
+ * yosys -- Yosys Open SYnthesis Suite
+ *
+ * Copyright (C) 2020 The Symbiflow Authors
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include "clocks.h"
+#include <cassert>
+#include "kernel/log.h"
+#include "kernel/register.h"
+#include "propagation.h"
+
+void Clocks::AddClock(const std::string& name, std::vector<RTLIL::Wire*> wires,
+ float period, float rising_edge, float falling_edge) {
+ std::for_each(wires.begin(), wires.end(), [&, this](RTLIL::Wire* wire) {
+ AddClock(name, wire, period, rising_edge, falling_edge);
+ });
+}
+
+void Clocks::AddClock(const std::string& name, RTLIL::Wire* wire, float period,
+ float rising_edge, float falling_edge) {
+ auto clock =
+ std::find_if(clocks_.begin(), clocks_.end(),
+ [&](Clock& clock) { return clock.Name() == name; });
+ if (clock != clocks_.end()) {
+ log("Clock %s already exists and will be overwritten\n", name.c_str());
+ clock->UpdateClock(wire, period, rising_edge, falling_edge);
+ } else {
+ log("Inserting clock %s with period %f, r:%f, f:%f\n", name.c_str(),
+ period, rising_edge, falling_edge);
+ if (falling_edge > period) {
+ log_error("Phase shift on clock %s exceeds 360 degrees\nRising edge: %f, Falling edge: %f, Clock period:%f\n", name.c_str(), rising_edge, falling_edge, period);
+ }
+ clocks_.emplace_back(name, wire, period, rising_edge, falling_edge);
+ }
+}
+
+void Clocks::AddClock(Clock& clock) {
+ AddClock(clock.Name(), clock.GetClockWires(), clock.Period(),
+ clock.RisingEdge(), clock.FallingEdge());
+}
+
+std::vector<std::string> Clocks::GetClockNames() {
+ std::vector<std::string> res;
+ for (auto clock : clocks_) {
+ res.push_back(clock.Name());
+#ifdef SDC_DEBUG
+ std::stringstream ss;
+ for (auto clock_wire : clock.GetClockWires()) {
+ ss << RTLIL::unescape_id(clock_wire->name) << " ";
+ }
+ log("create_clock -period %f -name %s -waveform {%f %f} %s\n",
+ clock.Period(), clock.Name().c_str(), clock.RisingEdge(),
+ clock.FallingEdge(), ss.str().c_str());
+#endif
+ }
+ return res;
+}
+
+void Clocks::Propagate(NaturalPropagation* pass) {
+#ifdef SDC_DEBUG
+ log("Start natural clock propagation\n");
+#endif
+ for (auto clock : clocks_) {
+#ifdef SDC_DEBUG
+ log("Processing clock %s\n", clock.Name().c_str());
+#endif
+ auto clock_wires = clock.GetClockWires();
+ for (auto clock_wire : clock_wires) {
+ auto aliases = pass->FindAliasWires(clock_wire);
+ AddClock(clock.Name(), aliases, clock.Period(),
+ clock.RisingEdge(), clock.FallingEdge());
+ }
+ }
+#ifdef SDC_DEBUG
+ log("Finish natural clock propagation\n\n");
+#endif
+}
+
+void Clocks::Propagate(BufferPropagation* pass) {
+#ifdef SDC_DEBUG
+ log("Start buffer clock propagation\n");
+ log("IBUF pass\n");
+#endif
+ std::vector<Clock> clocks(clocks_);
+ for (auto clock : clocks) {
+#ifdef SDC_DEBUG
+ log("Processing clock %s\n", clock.Name().c_str());
+#endif
+ PropagateThroughBuffer(pass, clock, IBuf());
+ }
+#ifdef SDC_DEBUG
+ log("BUFG pass\n");
+#endif
+ clocks = clocks_;
+ for (auto clock : clocks) {
+#ifdef SDC_DEBUG
+ log("Processing clock %s\n", clock.Name().c_str());
+#endif
+ PropagateThroughBuffer(pass, clock, Bufg());
+ }
+#ifdef SDC_DEBUG
+ log("Finish buffer clock propagation\n\n");
+#endif
+}
+
+void Clocks::Propagate(ClockDividerPropagation* pass) {
+#ifdef SDC_DEBUG
+ log("Start clock divider clock propagation\n");
+#endif
+ for (auto clock : clocks_) {
+#ifdef SDC_DEBUG
+ log("Processing clock %s\n", clock.Name().c_str());
+#endif
+ auto clock_wires = clock.GetClockWires();
+ for (auto clock_wire : clock_wires) {
+ auto pll_clocks =
+ pass->FindSinkClocksForCellType(clock_wire, "PLLE2_ADV");
+ for (auto pll_clock : pll_clocks) {
+#ifdef SDC_DEBUG
+ log("PLL clock: %s\n", pll_clock.Name().c_str());
+#endif
+ pll_clock.ApplyShift(clock.RisingEdge());
+ AddClock(pll_clock);
+ PropagateThroughBuffer(pass, pll_clock, Bufg());
+ }
+ }
+ }
+#ifdef SDC_DEBUG
+ log("Finish clock divider clock propagation\n\n");
+#endif
+}
+
+void Clocks::PropagateThroughBuffer(Propagation* pass, Clock& clock,
+ Buffer buffer) {
+ auto clock_wires = clock.GetClockWires();
+ for (auto clock_wire : clock_wires) {
+#ifdef SDC_DEBUG
+ log("Clock wire %s\n", RTLIL::unescape_id(clock_wire->name).c_str());
+#endif
+ auto buf_wires = pass->FindSinkWiresForCellType(clock_wire, buffer.name,
+ buffer.output);
+ int path_delay(0);
+ for (auto wire : buf_wires) {
+#ifdef SDC_DEBUG
+ log("%s wire: %s\n", buffer.name.c_str(),
+ RTLIL::unescape_id(wire->name).c_str());
+#endif
+ path_delay += buffer.delay;
+ AddClock(RTLIL::unescape_id(wire->name), wire, clock.Period(),
+ clock.RisingEdge() + path_delay,
+ clock.FallingEdge() + path_delay);
+ }
+ }
+}
+
+void Clocks::WriteSdc(std::ostream& file) {
+ for (auto& clock : clocks_) {
+ auto clock_wires = clock.GetClockWires();
+ // FIXME: Input port nets are not found in VPR
+ if (std::all_of(clock_wires.begin(), clock_wires.end(),
+ [&](RTLIL::Wire* wire) { return wire->port_input; })) {
+ continue;
+ }
+ file << "create_clock -period " << clock.Period();
+ file << " -waveform {" << clock.RisingEdge() << " "
+ << clock.FallingEdge() << "}";
+ for (auto clock_wire : clock_wires) {
+ if (clock_wire->port_input) {
+ continue;
+ }
+ file << " " << Clock::ClockWireName(clock_wire);
+ }
+ file << std::endl;
+ }
+}
+
+Clock::Clock(const std::string& name, RTLIL::Wire* wire, float period,
+ float rising_edge, float falling_edge)
+ : name_(name),
+ period_(period),
+ rising_edge_(rising_edge),
+ falling_edge_(falling_edge) {
+ UpdateWires(wire);
+}
+
+Clock::Clock(const std::string& name, std::vector<RTLIL::Wire*> wires,
+ float period, float rising_edge, float falling_edge)
+ : name_(name),
+ period_(period),
+ rising_edge_(rising_edge),
+ falling_edge_(falling_edge) {
+ std::for_each(wires.begin(), wires.end(),
+ [&, this](RTLIL::Wire* wire) { UpdateWires(wire); });
+}
+
+void Clock::UpdateClock(RTLIL::Wire* wire, float period, float rising_edge,
+ float falling_edge) {
+ UpdateWires(wire);
+ UpdatePeriod(period);
+ UpdateWaveform(rising_edge, falling_edge);
+}
+
+void Clock::UpdateWires(RTLIL::Wire* wire) {
+ if (std::find(clock_wires_.begin(), clock_wires_.end(), wire) ==
+ clock_wires_.end()) {
+ clock_wires_.push_back(wire);
+ }
+}
+
+void Clock::UpdatePeriod(float period) {
+ period_ = period;
+}
+
+void Clock::UpdateWaveform(float rising_edge, float falling_edge) {
+ rising_edge_ = rising_edge;
+ falling_edge_ = falling_edge;
+ if (falling_edge_ > period_) {
+ log_error("Phase shift on clock %s exceeds 360 degrees\nRising edge: %f, Falling edge: %f, Clock period:%f\n", name_.c_str(), rising_edge_, falling_edge_, period_);
+ }
+}
+
+void Clock::ApplyShift(float rising_edge) {
+ float new_rising_edge = rising_edge_ + rising_edge;
+ float new_falling_edge = falling_edge_ + rising_edge;
+ UpdateWaveform(new_rising_edge, new_falling_edge);
+}
+
+std::string Clock::ClockWireName(RTLIL::Wire* wire) {
+ if (!wire) {
+ return std::string();
+ }
+ std::string wire_name(RTLIL::unescape_id(wire->name));
+ return std::regex_replace(wire_name, std::regex{"\\$"}, "\\$");
+}
diff --git a/sdc-plugin/clocks.h b/sdc-plugin/clocks.h
new file mode 100644
index 0000000..28cd7f7
--- /dev/null
+++ b/sdc-plugin/clocks.h
@@ -0,0 +1,80 @@
+/*
+ * yosys -- Yosys Open SYnthesis Suite
+ *
+ * Copyright (C) 2020 The Symbiflow Authors
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifndef _CLOCKS_H_
+#define _CLOCKS_H_
+
+#include <unordered_map>
+#include <vector>
+#include "buffers.h"
+#include "kernel/rtlil.h"
+
+USING_YOSYS_NAMESPACE
+
+class NaturalPropagation;
+class BufferPropagation;
+class ClockDividerPropagation;
+class Propagation;
+
+class Clock {
+ public:
+ Clock(const std::string& name, RTLIL::Wire* wire, float period,
+ float rising_edge, float falling_edge);
+ Clock(const std::string& name, std::vector<RTLIL::Wire*> wires,
+ float period, float rising_edge, float falling_edge);
+ std::vector<RTLIL::Wire*> GetClockWires() { return clock_wires_; }
+ const std::string& Name() const { return name_; }
+ float Period() { return period_; }
+ float RisingEdge() { return rising_edge_; }
+ float FallingEdge() { return falling_edge_; }
+ void UpdateClock(RTLIL::Wire* wire, float period, float rising_edge,
+ float falling_edge);
+ void ApplyShift(float shift);
+ static std::string ClockWireName(RTLIL::Wire* wire);
+
+ private:
+ std::string name_;
+ std::vector<RTLIL::Wire*> clock_wires_;
+ float period_;
+ float rising_edge_;
+ float falling_edge_;
+
+ void UpdateWires(RTLIL::Wire* wire);
+ void UpdatePeriod(float period);
+ void UpdateWaveform(float rising_edge, float falling_edge);
+};
+
+class Clocks {
+ public:
+ void AddClock(const std::string& name, std::vector<RTLIL::Wire*> wires,
+ float period, float rising_edge, float falling_edge);
+ void AddClock(const std::string& name, RTLIL::Wire* wire, float period,
+ float rising_edge, float falling_edge);
+ void AddClock(Clock& clock);
+ std::vector<std::string> GetClockNames();
+ void Propagate(NaturalPropagation* pass);
+ void Propagate(BufferPropagation* pass);
+ void Propagate(ClockDividerPropagation* pass);
+ void WriteSdc(std::ostream& file);
+
+ private:
+ std::vector<Clock> clocks_;
+ void PropagateThroughBuffer(Propagation* pass, Clock& clock,
+ Buffer buffer);
+};
+
+#endif // _CLOCKS_H_
diff --git a/sdc-plugin/propagation.cc b/sdc-plugin/propagation.cc
new file mode 100644
index 0000000..39ea476
--- /dev/null
+++ b/sdc-plugin/propagation.cc
@@ -0,0 +1,171 @@
+/*
+ * yosys -- Yosys Open SYnthesis Suite
+ *
+ * Copyright (C) 2020 The Symbiflow Authors
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include "propagation.h"
+#include <cassert>
+
+USING_YOSYS_NAMESPACE
+
+std::vector<RTLIL::Wire*> NaturalPropagation::FindAliasWires(
+ RTLIL::Wire* wire) {
+ RTLIL::Module* top_module = design_->top_module();
+ assert(top_module);
+ std::vector<RTLIL::Wire*> alias_wires;
+ pass_->extra_args(
+ std::vector<std::string>{
+ top_module->name.str() + "/w:" + wire->name.str(), "%a"},
+ 0, design_);
+ for (auto module : design_->selected_modules()) {
+ for (auto wire : module->selected_wires()) {
+ alias_wires.push_back(wire);
+ }
+ }
+ return alias_wires;
+}
+
+std::vector<Clock> ClockDividerPropagation::FindSinkClocksForCellType(
+ RTLIL::Wire* driver_wire, const std::string& cell_type) {
+ std::vector<Clock> clocks;
+ if (cell_type == "PLLE2_ADV") {
+ RTLIL::Cell* cell = NULL;
+ for (auto input : Pll::inputs) {
+ cell = FindSinkCellOnPort(driver_wire, input);
+ if (cell and RTLIL::unescape_id(cell->type) == cell_type) {
+ break;
+ }
+ }
+ if (!cell) {
+ return clocks;
+ }
+ Pll pll(cell);
+ for (auto output : Pll::outputs) {
+ RTLIL::Wire* wire = FindSinkWireOnPort(cell, output);
+ if (wire) {
+ float clkout_period(pll.clkout_period.at(output));
+ float clkout_shift(pll.clkout_shift.at(output));
+ float clkout_duty_cycle(pll.clkout_duty_cycle.at(output));
+ Clock clock(RTLIL::unescape_id(wire->name), wire, clkout_period, clkout_shift,
+ clkout_shift + clkout_duty_cycle * clkout_period);
+ clocks.push_back(clock);
+ auto further_clocks =
+ FindSinkClocksForCellType(wire, cell_type);
+ std::copy(further_clocks.begin(), further_clocks.end(),
+ std::back_inserter(clocks));
+ }
+ }
+ }
+ return clocks;
+}
+
+RTLIL::Cell* Propagation::FindSinkCellOfType(RTLIL::Wire* wire,
+ const std::string& type) {
+ RTLIL::Cell* sink_cell = NULL;
+ if (!wire) {
+ return sink_cell;
+ }
+ RTLIL::Module* top_module = design_->top_module();
+ assert(top_module);
+ std::string base_selection =
+ top_module->name.str() + "/w:" + wire->name.str();
+ pass_->extra_args(std::vector<std::string>{base_selection, "%co:+" + type,
+ base_selection, "%d"},
+ 0, design_);
+ auto selected_cells = top_module->selected_cells();
+ // FIXME Handle more than one sink
+ assert(selected_cells.size() <= 1);
+ if (selected_cells.size() > 0) {
+ sink_cell = selected_cells.at(0);
+#ifdef SDC_DEBUG
+ log("Found sink cell: %s\n",
+ RTLIL::unescape_id(sink_cell->name).c_str());
+#endif
+ }
+ return sink_cell;
+}
+
+std::vector<RTLIL::Wire*> Propagation::FindSinkWiresForCellType(
+ RTLIL::Wire* driver_wire, const std::string& cell_type,
+ const std::string& cell_port) {
+ std::vector<RTLIL::Wire*> wires;
+ if (!driver_wire) {
+ return wires;
+ }
+ auto cell = FindSinkCellOfType(driver_wire, cell_type);
+ RTLIL::Wire* wire = FindSinkWireOnPort(cell, cell_port);
+ if (wire) {
+ wires.push_back(wire);
+ auto further_wires =
+ FindSinkWiresForCellType(wire, cell_type, cell_port);
+ std::copy(further_wires.begin(), further_wires.end(),
+ std::back_inserter(wires));
+ }
+ return wires;
+}
+
+RTLIL::Cell* Propagation::FindSinkCellOnPort(RTLIL::Wire* wire,
+ const std::string& port) {
+ RTLIL::Cell* sink_cell = NULL;
+ if (!wire) {
+ return sink_cell;
+ }
+ RTLIL::Module* top_module = design_->top_module();
+ assert(top_module);
+ std::string base_selection =
+ top_module->name.str() + "/w:" + wire->name.str();
+ pass_->extra_args(
+ std::vector<std::string>{base_selection, "%co:+[" + port + "]",
+ base_selection, "%d"},
+ 0, design_);
+ auto selected_cells = top_module->selected_cells();
+ // FIXME Handle more than one sink
+ assert(selected_cells.size() <= 1);
+ if (selected_cells.size() > 0) {
+ sink_cell = selected_cells.at(0);
+#ifdef SDC_DEBUG
+ log("Found sink cell: %s\n",
+ RTLIL::unescape_id(sink_cell->name).c_str());
+#endif
+ }
+ return sink_cell;
+}
+
+RTLIL::Wire* Propagation::FindSinkWireOnPort(RTLIL::Cell* cell,
+ const std::string& port_name) {
+ RTLIL::Wire* sink_wire = NULL;
+ if (!cell) {
+ return sink_wire;
+ }
+ RTLIL::Module* top_module = design_->top_module();
+ assert(top_module);
+ std::string base_selection =
+ top_module->name.str() + "/c:" + cell->name.str();
+ pass_->extra_args(
+ std::vector<std::string>{base_selection, "%co:+[" + port_name + "]",
+ base_selection, "%d"},
+ 0, design_);
+ auto selected_wires = top_module->selected_wires();
+ // FIXME Handle more than one sink
+ assert(selected_wires.size() <= 1);
+ if (selected_wires.size() > 0) {
+ sink_wire = selected_wires.at(0);
+#ifdef SDC_DEBUG
+ log("Found sink wire: %s\n",
+ RTLIL::unescape_id(sink_wire->name).c_str());
+#endif
+ }
+ return sink_wire;
+}
diff --git a/sdc-plugin/propagation.h b/sdc-plugin/propagation.h
new file mode 100644
index 0000000..1f3125c
--- /dev/null
+++ b/sdc-plugin/propagation.h
@@ -0,0 +1,71 @@
+/*
+ * yosys -- Yosys Open SYnthesis Suite
+ *
+ * Copyright (C) 2020 The Symbiflow Authors
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifndef _PROPAGATION_H_
+#define _PROPAGATION_H_
+
+#include "clocks.h"
+
+USING_YOSYS_NAMESPACE
+
+class Propagation {
+ public:
+ Propagation(RTLIL::Design* design, Pass* pass)
+ : design_(design), pass_(pass) {}
+
+ virtual void Run(Clocks& clocks) = 0;
+ std::vector<RTLIL::Wire*> FindSinkWiresForCellType(
+ RTLIL::Wire* driver_wire, const std::string& cell_type,
+ const std::string& cell_port);
+
+ protected:
+ RTLIL::Design* design_;
+ Pass* pass_;
+
+ RTLIL::Cell* FindSinkCellOfType(RTLIL::Wire* wire, const std::string& type);
+ RTLIL::Cell* FindSinkCellOnPort(RTLIL::Wire* wire, const std::string& port);
+ RTLIL::Wire* FindSinkWireOnPort(RTLIL::Cell* cell,
+ const std::string& port_name);
+};
+
+class NaturalPropagation : public Propagation {
+ public:
+ NaturalPropagation(RTLIL::Design* design, Pass* pass)
+ : Propagation(design, pass) {}
+
+ void Run(Clocks& clocks) override { clocks.Propagate(this); }
+ std::vector<RTLIL::Wire*> FindAliasWires(RTLIL::Wire* wire);
+};
+
+class BufferPropagation : public Propagation {
+ public:
+ BufferPropagation(RTLIL::Design* design, Pass* pass)
+ : Propagation(design, pass) {}
+
+ void Run(Clocks& clocks) override { clocks.Propagate(this); }
+};
+
+class ClockDividerPropagation : public Propagation {
+ public:
+ ClockDividerPropagation(RTLIL::Design* design, Pass* pass)
+ : Propagation(design, pass) {}
+
+ void Run(Clocks& clocks) override { clocks.Propagate(this); }
+ std::vector<Clock> FindSinkClocksForCellType(RTLIL::Wire* driver_wire,
+ const std::string& cell_type);
+};
+#endif // PROPAGATION_H_
diff --git a/sdc-plugin/sdc.cc b/sdc-plugin/sdc.cc
new file mode 100644
index 0000000..888fbdf
--- /dev/null
+++ b/sdc-plugin/sdc.cc
@@ -0,0 +1,266 @@
+/*
+ * yosys -- Yosys Open SYnthesis Suite
+ *
+ * Copyright (C) 2020 The Symbiflow Authors
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <algorithm>
+#include "clocks.h"
+#include "kernel/log.h"
+#include "kernel/register.h"
+#include "kernel/rtlil.h"
+#include "propagation.h"
+
+USING_YOSYS_NAMESPACE
+
+PRIVATE_NAMESPACE_BEGIN
+
+struct ReadSdcCmd : public Frontend {
+ ReadSdcCmd() : Frontend("sdc", "Read SDC file") {}
+
+ void help() override {
+ log("\n");
+ log(" read_sdc <filename>\n");
+ log("\n");
+ log("Read SDC file.\n");
+ log("\n");
+ }
+
+ void execute(std::istream*& f, std::string filename,
+ std::vector<std::string> args, RTLIL::Design*) override {
+ if (args.size() < 2) {
+ log_cmd_error("Missing script file.\n");
+ }
+ log("\nReading clock constraints file(SDC)\n\n");
+ size_t argidx = 1;
+ extra_args(f, filename, args, argidx);
+ std::string content{std::istreambuf_iterator<char>(*f),
+ std::istreambuf_iterator<char>()};
+ log("%s\n", content.c_str());
+ Tcl_Interp* interp = yosys_get_tcl_interp();
+ if (Tcl_EvalFile(interp, args[argidx].c_str()) != TCL_OK) {
+ log_cmd_error("TCL interpreter returned an error: %s\n",
+ Tcl_GetStringResult(interp));
+ }
+ }
+};
+
+struct WriteSdcCmd : public Backend {
+ WriteSdcCmd(Clocks& clocks)
+ : Backend("sdc", "Write SDC file"), clocks_(clocks) {}
+
+ void help() override {
+ log("\n");
+ log(" write_sdc <filename>\n");
+ log("\n");
+ log("Write SDC file.\n");
+ log("\n");
+ }
+
+ void execute(std::ostream*& f, std::string filename,
+ std::vector<std::string> args, RTLIL::Design*) override {
+ if (args.size() < 2) {
+ log_cmd_error("Missing output file.\n");
+ }
+ log("\nWriting out clock constraints file(SDC)\n");
+ extra_args(f, filename, args, 1);
+ clocks_.WriteSdc(*f);
+ }
+
+ Clocks& clocks_;
+};
+
+struct CreateClockCmd : public Pass {
+ CreateClockCmd(Clocks& clocks)
+ : Pass("create_clock", "Create clock object"), clocks_(clocks) {}
+
+ void help() override {
+ log("\n");
+ log(" create_clock [ -name clock_name ] -period period_value "
+ "[-waveform <edge_list>] <target>\n");
+ log("Define a clock.\n");
+ log("If name is not specified then the name of the first target is "
+ "selected as the clock's name.\n");
+ log("Period is expressed in nanoseconds.\n");
+ log("The waveform option specifies the duty cycle (the rising a "
+ "falling edges) of the clock.\n");
+ log("It is specified as a list of two elements/time values: the first "
+ "rising edge and the next falling edge.\n");
+ log("\n");
+ }
+
+ void execute(std::vector<std::string> args,
+ RTLIL::Design* design) override {
+ size_t argidx;
+ std::string name;
+ bool is_waveform_specified(false);
+ float rising_edge(0);
+ float falling_edge(0);
+ float period(0);
+ if (args.size() < 4) {
+ log_cmd_error("Incorrect number of arguments\n");
+ }
+ for (argidx = 1; argidx < args.size(); argidx++) {
+ std::string arg = args[argidx];
+ if (arg == "-name" && argidx + 1 < args.size()) {
+ name = args[++argidx];
+ continue;
+ }
+ if (arg == "-period" && argidx + 1 < args.size()) {
+ period = std::stof(args[++argidx]);
+ continue;
+ }
+ if (arg == "-waveform" && argidx + 1 < args.size()) {
+ std::string edges(args[++argidx]);
+ std::copy_if(edges.begin(), edges.end(), edges.begin(),
+ [](char c) { return c != '{' or c != '}'; });
+ std::stringstream ss(edges);
+ ss >> rising_edge >> falling_edge;
+ is_waveform_specified = true;
+ continue;
+ }
+ break;
+ }
+ if (period <= 0) {
+ log_cmd_error("Incorrect period value\n");
+ }
+ // Add "w:" prefix to selection arguments to enforce wire object
+ // selection
+ AddWirePrefix(args, argidx);
+ extra_args(args, argidx, design);
+ // If clock name is not specified then take the name of the first target
+ std::vector<RTLIL::Wire*> selected_wires;
+ for (auto module : design->modules()) {
+ if (!design->selected(module)) {
+ continue;
+ }
+ for (auto wire : module->wires()) {
+ if (design->selected(module, wire)) {
+#ifdef SDC_DEBUG
+ log("Selected wire %s\n",
+ RTLIL::unescape_id(wire->name).c_str());
+#endif
+ selected_wires.push_back(wire);
+ }
+ }
+ }
+ if (selected_wires.size() == 0) {
+ log_cmd_error("Target selection is empty\n");
+ }
+ if (name.empty()) {
+ name = RTLIL::unescape_id(selected_wires.at(0)->name);
+ }
+ if (!is_waveform_specified) {
+ rising_edge = 0;
+ falling_edge = period / 2;
+ }
+ clocks_.AddClock(name, selected_wires, period, rising_edge,
+ falling_edge);
+ log("Created clock %s with period %f, waveform {%f %f}\n", name.c_str(),
+ period, rising_edge, falling_edge);
+ }
+
+ void AddWirePrefix(std::vector<std::string>& args, size_t argidx) {
+ auto selection_begin = args.begin() + argidx;
+ std::transform(selection_begin, args.end(), selection_begin,
+ [](std::string& w) { return "w:" + w; });
+ }
+
+ Clocks& clocks_;
+};
+
+struct GetClocksCmd : public Pass {
+ GetClocksCmd(Clocks& clocks)
+ : Pass("get_clocks", "Create clock object"), clocks_(clocks) {}
+
+ void help() override {
+ log("\n");
+ log(" get_clocks\n");
+ log("\n");
+ log("Returns all clocks in the design.\n");
+ log("\n");
+ }
+
+ void execute(__attribute__((unused)) std::vector<std::string> args,
+ __attribute__((unused)) RTLIL::Design* design) override {
+ std::vector<std::string> clock_names(clocks_.GetClockNames());
+ if (clock_names.size() == 0) {
+ log_warning("No clocks found in design\n");
+ }
+ Tcl_Interp* interp = yosys_get_tcl_interp();
+ Tcl_Obj* tcl_list = Tcl_NewListObj(0, NULL);
+ for (auto name : clock_names) {
+ Tcl_Obj* name_obj = Tcl_NewStringObj(name.c_str(), name.size());
+ Tcl_ListObjAppendElement(interp, tcl_list, name_obj);
+ }
+ Tcl_SetObjResult(interp, tcl_list);
+ }
+
+ Clocks& clocks_;
+};
+
+struct PropagateClocksCmd : public Pass {
+ PropagateClocksCmd(Clocks& clocks)
+ : Pass("propagate_clocks", "Propagate clock information"),
+ clocks_(clocks) {}
+
+ void help() override {
+ log("\n");
+ log(" propagate_clocks\n");
+ log("\n");
+ log("Propagate clock information throughout the design.\n");
+ log("\n");
+ }
+
+ void execute(__attribute__((unused)) std::vector<std::string> args,
+ RTLIL::Design* design) override {
+ if (!design->top_module()) {
+ log_cmd_error("No top module selected\n");
+ }
+
+ std::array<std::unique_ptr<Propagation>, 2> passes{
+ std::unique_ptr<BufferPropagation>(
+ new BufferPropagation(design, this)),
+ std::unique_ptr<ClockDividerPropagation>(
+ new ClockDividerPropagation(design, this))};
+
+ for (auto& pass : passes) {
+ pass->Run(clocks_);
+ }
+ }
+
+ Clocks& clocks_;
+};
+
+class SdcPlugin {
+ public:
+ SdcPlugin()
+ : write_sdc_cmd_(clocks_),
+ create_clock_cmd_(clocks_),
+ get_clocks_cmd_(clocks_),
+ propagate_clocks_cmd_(clocks_) {
+ log("Loaded SDC plugin\n");
+ }
+
+ ReadSdcCmd read_sdc_cmd_;
+ WriteSdcCmd write_sdc_cmd_;
+ CreateClockCmd create_clock_cmd_;
+ GetClocksCmd get_clocks_cmd_;
+ PropagateClocksCmd propagate_clocks_cmd_;
+
+ private:
+ Clocks clocks_;
+} SdcPlugin;
+
+PRIVATE_NAMESPACE_END
diff --git a/sdc-plugin/tests/Makefile b/sdc-plugin/tests/Makefile
new file mode 100644
index 0000000..68c3e45
--- /dev/null
+++ b/sdc-plugin/tests/Makefile
@@ -0,0 +1,34 @@
+TESTS = counter counter2 pll
+.PHONY: $(TESTS)
+
+counter_verify = $(call compare,counter,sdc) && $(call compare,counter,txt)
+counter2_verify = $(call compare,counter2,sdc) && $(call compare,counter2,txt)
+pll_verify = $(call compare,pll,sdc)
+
+all: $(TESTS)
+compare = diff $(1)/$(1).golden.$(2) $(1)/$(1).$(2)
+
+define test_tpl =
+$(1): $(1)/$(1).sdc
+ $$($(1)_verify)
+ RETVAL=$$$$? ; \
+ if [ $$$$RETVAL -eq 0 ]; then \
+ echo "$(1) PASS"; \
+ true; \
+ else \
+ echo "$(1) FAIL"; \
+ false; \
+ fi
+
+$(1)/$(1).sdc: $(1)/$(1).v
+ cd $(1); \
+ INPUT_SDC_FILE=$(1).input.sdc \
+ OUTPUT_SDC_FILE=$(1).sdc \
+ yosys -p "tcl $(1).tcl" -l yosys.log
+
+endef
+
+$(foreach test,$(TESTS),$(eval $(call test_tpl,$(test))))
+
+clean:
+ rm -rf $(foreach test,$(TESTS),$(test)/$(test).sdc $(test)/$(test).txt $(test)/yosys.log)
diff --git a/sdc-plugin/tests/counter/counter.golden.sdc b/sdc-plugin/tests/counter/counter.golden.sdc
new file mode 100644
index 0000000..a70e95d
--- /dev/null
+++ b/sdc-plugin/tests/counter/counter.golden.sdc
@@ -0,0 +1,6 @@
+create_clock -period 10 -waveform {0 5} clk_int_1
+create_clock -period 10 -waveform {0 5} ibuf_proxy_out
+create_clock -period 10 -waveform {0 5} \$auto\$clkbufmap.cc:247:execute\$1918
+create_clock -period 10 -waveform {0 5} \$auto\$clkbufmap.cc:247:execute\$1920
+create_clock -period 10 -waveform {1 6} middle_inst_1.clk_int
+create_clock -period 10 -waveform {1 6} middle_inst_4.clk
diff --git a/sdc-plugin/tests/counter/counter.golden.txt b/sdc-plugin/tests/counter/counter.golden.txt
new file mode 100644
index 0000000..065b110
--- /dev/null
+++ b/sdc-plugin/tests/counter/counter.golden.txt
@@ -0,0 +1 @@
+clk_int_1 clk ibuf_proxy_out {$auto$clkbufmap.cc:247:execute$1918} {$auto$clkbufmap.cc:247:execute$1920} middle_inst_1.clk_int middle_inst_4.clk
diff --git a/sdc-plugin/tests/counter/counter.input.sdc b/sdc-plugin/tests/counter/counter.input.sdc
new file mode 100644
index 0000000..01debad
--- /dev/null
+++ b/sdc-plugin/tests/counter/counter.input.sdc
@@ -0,0 +1,2 @@
+create_clock -period 10.0 -waveform {0.000 5.000} clk_int_1
+create_clock -period 10.0 -name clk -waveform {0.000 5.000} clk clk2
diff --git a/sdc-plugin/tests/counter/counter.tcl b/sdc-plugin/tests/counter/counter.tcl
new file mode 100644
index 0000000..a27caf9
--- /dev/null
+++ b/sdc-plugin/tests/counter/counter.tcl
@@ -0,0 +1,26 @@
+yosys -import
+plugin -i sdc
+# Import the commands from the plugins to the tcl interpreter
+yosys -import
+
+read_verilog counter.v
+read_verilog -specify -lib -D_EXPLICIT_CARRY +/xilinx/cells_sim.v
+read_verilog -lib +/xilinx/cells_xtra.v
+hierarchy -check -auto-top
+# Start flow after library reading
+synth_xilinx -vpr -flatten -abc9 -nosrl -nodsp -iopad -run prepare:check
+
+# Read the design's timing constraints
+read_sdc $::env(INPUT_SDC_FILE)
+
+# Propagate the clocks
+propagate_clocks
+
+# Write the clocks to file
+set fh [open counter.txt w]
+set clocks [get_clocks]
+puts $fh $clocks
+close $fh
+
+# Write out the SDC file after the clock propagation step
+write_sdc $::env(OUTPUT_SDC_FILE)
diff --git a/sdc-plugin/tests/counter/counter.v b/sdc-plugin/tests/counter/counter.v
new file mode 100644
index 0000000..564fae5
--- /dev/null
+++ b/sdc-plugin/tests/counter/counter.v
@@ -0,0 +1,36 @@
+module top(input clk,
+ input clk2,
+ input [1:0] in,
+ output [5:0] out );
+
+reg [1:0] cnt = 0;
+wire clk_int_1, clk_int_2;
+IBUF ibuf_proxy(.I(clk), .O(ibuf_proxy_out));
+IBUF ibuf_inst(.I(ibuf_proxy_out), .O(ibuf_out));
+assign clk_int_1 = ibuf_out;
+assign clk_int_2 = clk_int_1;
+
+always @(posedge clk_int_2) begin
+ cnt <= cnt + 1;
+end
+
+middle middle_inst_1(.clk(ibuf_out), .out(out[2]));
+middle middle_inst_2(.clk(clk_int_1), .out(out[3]));
+middle middle_inst_3(.clk(clk_int_2), .out(out[4]));
+middle middle_inst_4(.clk(clk2), .out(out[5]));
+
+assign out[1:0] = {cnt[0], in[0]};
+endmodule
+
+module middle(input clk,
+ output out);
+
+reg [1:0] cnt = 0;
+wire clk_int;
+assign clk_int = clk;
+always @(posedge clk_int) begin
+ cnt <= cnt + 1;
+end
+
+assign out = cnt[0];
+endmodule
diff --git a/sdc-plugin/tests/counter2/counter.txt b/sdc-plugin/tests/counter2/counter.txt
new file mode 100644
index 0000000..065b110
--- /dev/null
+++ b/sdc-plugin/tests/counter2/counter.txt
@@ -0,0 +1 @@
+clk_int_1 clk ibuf_proxy_out {$auto$clkbufmap.cc:247:execute$1918} {$auto$clkbufmap.cc:247:execute$1920} middle_inst_1.clk_int middle_inst_4.clk
diff --git a/sdc-plugin/tests/counter2/counter2.golden.sdc b/sdc-plugin/tests/counter2/counter2.golden.sdc
new file mode 100644
index 0000000..b3e8cca
--- /dev/null
+++ b/sdc-plugin/tests/counter2/counter2.golden.sdc
@@ -0,0 +1,6 @@
+create_clock -period 10 -waveform {0 5} clk_int_1
+create_clock -period 10 -waveform {0 5} ibuf_proxy_out
+create_clock -period 10 -waveform {0 5} \$auto\$clkbufmap.cc:247:execute\$1918
+create_clock -period 10 -waveform {1 6} \$auto\$clkbufmap.cc:247:execute\$1920
+create_clock -period 10 -waveform {1 6} middle_inst_1.clk_int
+create_clock -period 10 -waveform {2 7} middle_inst_4.clk
diff --git a/sdc-plugin/tests/counter2/counter2.golden.txt b/sdc-plugin/tests/counter2/counter2.golden.txt
new file mode 100644
index 0000000..ca1b187
--- /dev/null
+++ b/sdc-plugin/tests/counter2/counter2.golden.txt
@@ -0,0 +1 @@
+clk_int_1 clk clk2 ibuf_proxy_out {$auto$clkbufmap.cc:247:execute$1918} {$auto$clkbufmap.cc:247:execute$1920} middle_inst_1.clk_int middle_inst_4.clk
diff --git a/sdc-plugin/tests/counter2/counter2.input.sdc b/sdc-plugin/tests/counter2/counter2.input.sdc
new file mode 100644
index 0000000..3b1ac05
--- /dev/null
+++ b/sdc-plugin/tests/counter2/counter2.input.sdc
@@ -0,0 +1,3 @@
+create_clock -period 10.0 -waveform {0.000 5.000} clk_int_1
+create_clock -period 10.0 clk
+create_clock -period 10.0 -waveform {1.000 6.000} clk2
diff --git a/sdc-plugin/tests/counter2/counter2.tcl b/sdc-plugin/tests/counter2/counter2.tcl
new file mode 100644
index 0000000..80bce1b
--- /dev/null
+++ b/sdc-plugin/tests/counter2/counter2.tcl
@@ -0,0 +1,26 @@
+yosys -import
+plugin -i sdc
+# Import the commands from the plugins to the tcl interpreter
+yosys -import
+
+read_verilog counter2.v
+read_verilog -specify -lib -D_EXPLICIT_CARRY +/xilinx/cells_sim.v
+read_verilog -lib +/xilinx/cells_xtra.v
+hierarchy -check -auto-top
+# Start flow after library reading
+synth_xilinx -vpr -flatten -abc9 -nosrl -nodsp -iopad -run prepare:check
+
+# Read the design's timing constraints
+read_sdc $::env(INPUT_SDC_FILE)
+
+# Propagate the clocks
+propagate_clocks
+
+# Write the clocks to file
+set fh [open counter2.txt w]
+set clocks [get_clocks]
+puts $fh $clocks
+close $fh
+
+# Write out the SDC file after the clock propagation step
+write_sdc $::env(OUTPUT_SDC_FILE)
diff --git a/sdc-plugin/tests/counter2/counter2.v b/sdc-plugin/tests/counter2/counter2.v
new file mode 100644
index 0000000..564fae5
--- /dev/null
+++ b/sdc-plugin/tests/counter2/counter2.v
@@ -0,0 +1,36 @@
+module top(input clk,
+ input clk2,
+ input [1:0] in,
+ output [5:0] out );
+
+reg [1:0] cnt = 0;
+wire clk_int_1, clk_int_2;
+IBUF ibuf_proxy(.I(clk), .O(ibuf_proxy_out));
+IBUF ibuf_inst(.I(ibuf_proxy_out), .O(ibuf_out));
+assign clk_int_1 = ibuf_out;
+assign clk_int_2 = clk_int_1;
+
+always @(posedge clk_int_2) begin
+ cnt <= cnt + 1;
+end
+
+middle middle_inst_1(.clk(ibuf_out), .out(out[2]));
+middle middle_inst_2(.clk(clk_int_1), .out(out[3]));
+middle middle_inst_3(.clk(clk_int_2), .out(out[4]));
+middle middle_inst_4(.clk(clk2), .out(out[5]));
+
+assign out[1:0] = {cnt[0], in[0]};
+endmodule
+
+module middle(input clk,
+ output out);
+
+reg [1:0] cnt = 0;
+wire clk_int;
+assign clk_int = clk;
+always @(posedge clk_int) begin
+ cnt <= cnt + 1;
+end
+
+assign out = cnt[0];
+endmodule
diff --git a/sdc-plugin/tests/pll/pll.golden.sdc b/sdc-plugin/tests/pll/pll.golden.sdc
new file mode 100644
index 0000000..8f82a93
--- /dev/null
+++ b/sdc-plugin/tests/pll/pll.golden.sdc
@@ -0,0 +1,6 @@
+create_clock -period 10 -waveform {0 5} \$auto\$clkbufmap.cc:247:execute\$1827
+create_clock -period 10 -waveform {1 6} \$techmap1716\FDCE_0.C
+create_clock -period 10 -waveform {3.5 8.5} \$auto\$clkbufmap.cc:247:execute\$1829
+create_clock -period 10 -waveform {4.5 9.5} main_clkout0
+create_clock -period 5 -waveform {1 3.5} \$auto\$clkbufmap.cc:247:execute\$1831
+create_clock -period 5 -waveform {2 4.5} main_clkout1
diff --git a/sdc-plugin/tests/pll/pll.input.sdc b/sdc-plugin/tests/pll/pll.input.sdc
new file mode 100644
index 0000000..00354d7
--- /dev/null
+++ b/sdc-plugin/tests/pll/pll.input.sdc
@@ -0,0 +1 @@
+create_clock -period 10 -waveform {0 5} clk
diff --git a/sdc-plugin/tests/pll/pll.tcl b/sdc-plugin/tests/pll/pll.tcl
new file mode 100644
index 0000000..61e67f6
--- /dev/null
+++ b/sdc-plugin/tests/pll/pll.tcl
@@ -0,0 +1,21 @@
+yosys -import
+plugin -i sdc
+# Import the commands from the plugins to the tcl interpreter
+yosys -import
+
+read_verilog pll.v
+read_verilog -specify -lib -D_EXPLICIT_CARRY +/xilinx/cells_sim.v
+read_verilog -lib +/xilinx/cells_xtra.v
+hierarchy -check -auto-top
+
+# Start flow after library reading
+synth_xilinx -vpr -flatten -abc9 -nosrl -nodsp -iopad -run prepare:check
+
+# Read the design timing constraints
+read_sdc $::env(INPUT_SDC_FILE)
+
+# Propagate the clocks
+propagate_clocks
+
+# Write out the SDC file after the clock propagation step
+write_sdc $::env(OUTPUT_SDC_FILE)
diff --git a/sdc-plugin/tests/pll/pll.v b/sdc-plugin/tests/pll/pll.v
new file mode 100644
index 0000000..64b9750
--- /dev/null
+++ b/sdc-plugin/tests/pll/pll.v
@@ -0,0 +1,80 @@
+module top(
+ input clk,
+ input cpu_reset,
+ input data_in,
+ output[4:0] data_out
+);
+
+wire [4:0] data_out;
+wire builder_pll_fb;
+wire fdce_0_out, fdce_1_out;
+wire main_locked;
+
+FDCE FDCE_0 (
+ .D(data_in),
+ .C(clk),
+ .CE(1'b1),
+ .CLR(1'b0),
+ .Q(fdce_0_out)
+);
+
+FDCE FDCE_1 (
+ .D(fdce_0_out),
+ .C(clk),
+ .CE(1'b1),
+ .CLR(1'b0),
+ .Q(data_out[0])
+);
+
+PLLE2_ADV #(
+ .CLKFBOUT_MULT(4'd12),
+ .CLKIN1_PERIOD(10.0),
+ .CLKOUT0_DIVIDE(4'd12),
+ .CLKOUT0_PHASE(90.0),
+ .CLKOUT1_DIVIDE(3'd6),
+ .CLKOUT1_PHASE(0.0),
+ .DIVCLK_DIVIDE(1'd1),
+ .REF_JITTER1(0.01),
+ .STARTUP_WAIT("FALSE")
+) PLLE2_ADV (
+ .CLKFBIN(builder_pll_fb),
+ .CLKIN1(clk),
+ .RST(cpu_reset),
+ .CLKFBOUT(builder_pll_fb),
+ .CLKOUT0(main_clkout0),
+ .CLKOUT1(main_clkout1),
+ .LOCKED(main_locked)
+);
+
+FDCE FDCE_PLLx1 (
+ .D(data_in),
+ .C(main_clkout0),
+ .CE(1'b1),
+ .CLR(1'b0),
+ .Q(data_out[1])
+);
+
+FDCE FDCE_PLLx4_0 (
+ .D(data_in),
+ .C(main_clkout1),
+ .CE(1'b1),
+ .CLR(1'b0),
+ .Q(data_out[2])
+);
+
+FDCE FDCE_PLLx4_1 (
+ .D(data_in),
+ .C(main_clkout1),
+ .CE(1'b1),
+ .CLR(1'b0),
+ .Q(data_out[3])
+);
+
+FDCE FDCE_PLLx4_2 (
+ .D(data_in),
+ .C(main_clkout1),
+ .CE(1'b1),
+ .CLR(1'b0),
+ .Q(data_out[4])
+);
+endmodule