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