Merge pull request #51 from antmicro/sdc_attributes

Keep the SDC information in Yosys's design structures
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..8d4f8c9
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "third_party/googletest"]
+	path = third_party/googletest
+	url = https://github.com/google/googletest
diff --git a/Makefile_plugin.common b/Makefile_plugin.common
index 6215cbb..4289bd6 100644
--- a/Makefile_plugin.common
+++ b/Makefile_plugin.common
@@ -37,7 +37,7 @@
 # |-- example2-plugin
 # |-- ...
 CXX = $(shell yosys-config --cxx)
-CXXFLAGS = $(shell yosys-config --cxxflags)
+CXXFLAGS = $(shell yosys-config --cxxflags) #-DSDC_DEBUG
 LDFLAGS = $(shell yosys-config --ldflags)
 LDLIBS = $(shell yosys-config --ldlibs)
 PLUGINS_DIR = $(shell yosys-config --datdir)/plugins
diff --git a/Makefile_test.common b/Makefile_test.common
index fe8ffce..a2a3509 100644
--- a/Makefile_test.common
+++ b/Makefile_test.common
@@ -12,11 +12,18 @@
 # test1_verify = $(call diff_test,test1,ext) && test $$(grep "PASS" test1/test1.txt | wc -l) -eq 2
 # test2_verify = $(call diff_test,test2,ext)
 #
+GTEST_DIR = ../../third_party/googletest/googletest
+CXX = $(shell yosys-config --cxx)
+CXXFLAGS = $(shell yosys-config --cxxflags) -I.. -I$(GTEST_DIR)/include
+LDLIBS = $(shell yosys-config --ldlibs) -L$(GTEST_DIR)/build/lib -lgtest -lgtest_main -lpthread
+LDFLAGS = $(shell yosys-config --ldflags)
+
 define test_tpl =
 $(1): $(1)/ok
-	@$$($(1)_verify); \
-	RETVAL=$$$$? ; \
-	if [ $$$$RETVAL -eq 0 ]; then \
+	@echo "Verifying result of test $(1)"
+	@set +e; \
+	$$($(1)_verify); \
+	if [ $$$$? -eq 0 ]; then \
 		echo "Test $(1) PASSED"; \
 		touch $$<; \
 		true; \
@@ -27,19 +34,56 @@
 
 $(1)/ok: $(1)/$(1).v
 	@echo "Running test $(1)"
-	@cd $(1); \
+	@set +e; \
+	cd $(1); \
 	DESIGN_TOP=$(1) \
-	yosys -c $(1).tcl -q -l $(1).log
+	yosys -c $(1).tcl -q -l $(1).log; \
+	RETVAL=$$$$?; \
+	if [ ! -z "$$($(1)_negative)" ] && [ $$($(1)_negative) -eq 1 ]; then \
+		if [ $$$$RETVAL -ne 0 ]; then \
+			echo "Negative test $(1) PASSED"; \
+			true; \
+		else \
+			echo "Negative test $(1) FAILED"; \
+			false; \
+		fi \
+	else \
+		if [ $$$$RETVAL -ne 0 ]; then \
+			echo "Unexpected runtime error"; \
+			false; \
+		fi \
+	fi
+
+endef
+
+define unit_test_tpl =
+$(1): $(1)/$(1).test
+	@$$<
+
+$(1)/$(1).test: $(1)/$(1).test.o $$(GTEST_DIR)/build/lib/libgtest.a
+	@$(CXX) $(LDFLAGS) -o $$@ $$< $(LDLIBS)
+
+$(1)/$(1).test.o: $(1)/$(1).test.cc
+	@$(CXX) $(CXXFLAGS) $(LDFLAGS) -c $$< -o $$@
 
 endef
 
 diff_test = diff $(1)/$(1).golden.$(2) $(1)/$(1).$(2)
 
-all: $(TESTS)
-.PHONY: all clean $(TESTS)
+all: $(TESTS) $(UNIT_TESTS)
+
+$(GTEST_DIR)/build/lib/libgtest.a $(GTEST_DIR)/build/lib/libgtest_main.a:
+	@mkdir -p $(GTEST_DIR)/build
+	@cd $(GTEST_DIR)/build; \
+	cmake ..; \
+	make
+
+.PHONY: all clean $(TESTS) $(UNIT_TESTS)
 
 $(foreach test,$(TESTS),$(eval $(call test_tpl,$(test))))
+$(foreach test,$(UNIT_TESTS),$(eval $(call unit_test_tpl,$(test))))
 
 clean:
 	@rm -rf $(foreach test,$(TESTS),$(test)/$(test).sdc $(test)/$(test).txt $(test)/$(test).eblif $(test)/$(test).json)
+	@rm -rf $(foreach test,$(UNIT_TESTS),$(test)/$(test).test.o $(test)/$(test).test.d $(test)/$(test).test)
 	@find . -name "ok" -or -name "*.log" | xargs rm -rf
diff --git a/sdc-plugin/buffers.cc b/sdc-plugin/buffers.cc
index 919de28..a48a26a 100644
--- a/sdc-plugin/buffers.cc
+++ b/sdc-plugin/buffers.cc
@@ -15,9 +15,9 @@
  *  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"
 #include <cassert>
 #include <cmath>
-#include "buffers.h"
 
 const std::vector<std::string> Pll::inputs = {"CLKIN1", "CLKIN2"};
 const std::vector<std::string> Pll::outputs = {"CLKOUT0", "CLKOUT1", "CLKOUT2",
@@ -25,7 +25,9 @@
 const float Pll::delay = 0;
 const std::string Pll::name = "PLLE2_ADV";
 
-Pll::Pll(RTLIL::Cell* cell, float input_clock_period, float input_clock_rising_edge) {
+Pll::Pll(RTLIL::Cell* cell, float input_clock_period,
+         float input_clock_rising_edge)
+    : ClockDivider({"PLLE2_ADV"}) {
     assert(RTLIL::unescape_id(cell->type) == "PLLE2_ADV");
     FetchParams(cell);
     CheckInputClockPeriod(cell, input_clock_period);
@@ -35,7 +37,9 @@
 
 void Pll::CheckInputClockPeriod(RTLIL::Cell* cell, float input_clock_period) {
     float abs_diff = fabs(ClkinPeriod() - input_clock_period);
-    bool approx_equal = abs_diff < std::max(ClkinPeriod(), input_clock_period) * 10 * std::numeric_limits<float>::epsilon();
+    bool approx_equal = abs_diff < std::max(ClkinPeriod(), input_clock_period) *
+                                       10 *
+                                       std::numeric_limits<float>::epsilon();
     if (!approx_equal) {
 	log_cmd_error(
 	    "CLKIN[1/2]_PERIOD doesn't match the virtual clock constraint "
@@ -53,7 +57,8 @@
     divclk_divisor = FetchParam(cell, "DIVCLK_DIVIDE", 1.0);
     for (auto output : outputs) {
 	// CLKOUT[0-5]_DUTY_CYCLE
-	clkout_duty_cycle[output] = FetchParam(cell, output + "_DUTY_CYCLE", 0.5);
+	clkout_duty_cycle[output] =
+	    FetchParam(cell, output + "_DUTY_CYCLE", 0.5);
 	// CLKOUT[0-5]_DIVIDE
 	clkout_divisor[output] = FetchParam(cell, output + "_DIVIDE", 1.0);
 	// CLKOUT[0-5]_PHASE
@@ -63,22 +68,30 @@
 
 void Pll::CalculateOutputClockPeriods() {
     for (auto output : outputs) {
-	// CLKOUT[0-5]_PERIOD = CLKIN1_PERIOD * CLKOUT[0-5]_DIVIDE * DIVCLK_DIVIDE /
-	// CLKFBOUT_MULT
-	clkout_period[output] = ClkinPeriod() * clkout_divisor.at(output) / clk_mult *
-	    divclk_divisor;
+	// CLKOUT[0-5]_PERIOD = CLKIN1_PERIOD * CLKOUT[0-5]_DIVIDE *
+	// DIVCLK_DIVIDE / CLKFBOUT_MULT
+	clkout_period[output] = ClkinPeriod() * clkout_divisor.at(output) /
+	                        clk_mult * divclk_divisor;
     }
 }
 
 void Pll::CalculateOutputClockWaveforms(float input_clock_rising_edge) {
     for (auto output : outputs) {
 	float output_clock_period = clkout_period.at(output);
-	clkout_rising_edge[output] = fmod(input_clock_rising_edge - (clk_fbout_phase / 360.0) * ClkinPeriod() + output_clock_period * (clkout_phase[output] / 360.0), output_clock_period);
-	clkout_falling_edge[output] = fmod(clkout_rising_edge[output] + clkout_duty_cycle[output] * output_clock_period, output_clock_period);
+	clkout_rising_edge[output] =
+	    fmod(input_clock_rising_edge -
+	             (clk_fbout_phase / 360.0) * ClkinPeriod() +
+	             output_clock_period * (clkout_phase[output] / 360.0),
+	         output_clock_period);
+	clkout_falling_edge[output] =
+	    fmod(clkout_rising_edge[output] +
+	             clkout_duty_cycle[output] * output_clock_period,
+	         output_clock_period);
     }
 }
 
-float Pll::FetchParam(RTLIL::Cell* cell, std::string&& param_name, float default_value) {
+float Pll::FetchParam(RTLIL::Cell* cell, std::string&& param_name,
+                      float default_value) {
     RTLIL::IdString param(RTLIL::escape_id(param_name));
     if (cell->hasParam(param)) {
 	auto param_obj = cell->parameters.at(param);
diff --git a/sdc-plugin/buffers.h b/sdc-plugin/buffers.h
index 5f51535..ba3faa4 100644
--- a/sdc-plugin/buffers.h
+++ b/sdc-plugin/buffers.h
@@ -26,10 +26,10 @@
 USING_YOSYS_NAMESPACE
 
 struct Buffer {
-    Buffer(float delay, const std::string& name, const std::string& output)
-        : delay(delay), name(name), output(output) {}
+    Buffer(float delay, const std::string& type, const std::string& output)
+        : delay(delay), type(type), output(output) {}
     float delay;
-    std::string name;
+    std::string type;
     std::string output;
 };
 
@@ -41,8 +41,14 @@
     Bufg() : Buffer(0, "BUFG", "O"){};
 };
 
-struct Pll {
-    Pll(RTLIL::Cell* cell, float input_clock_period, float input_clock_rising_edge);
+struct ClockDivider {
+    std::string type;
+};
+
+struct Pll : public ClockDivider {
+    Pll() : ClockDivider({"PLLE2_ADV"}) {}
+    Pll(RTLIL::Cell* cell, float input_clock_period,
+        float input_clock_rising_edge);
 
     // Helper function to fetch a cell parameter or return a default value
     static float FetchParam(RTLIL::Cell* cell, std::string&& param_name,
diff --git a/sdc-plugin/clocks.cc b/sdc-plugin/clocks.cc
index c177f6a..95ebaad 100644
--- a/sdc-plugin/clocks.cc
+++ b/sdc-plugin/clocks.cc
@@ -19,220 +19,121 @@
 #include <cassert>
 #include <cmath>
 #include <regex>
-#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 Clock::Add(const std::string& name, RTLIL::Wire* wire, float period,
+                float rising_edge, float falling_edge) {
+    wire->set_string_attribute(RTLIL::escape_id("CLOCK_SIGNAL"), "yes");
+    wire->set_string_attribute(RTLIL::escape_id("CLASS"), "clock");
+    wire->set_string_attribute(RTLIL::escape_id("NAME"), name);
+    wire->set_string_attribute(RTLIL::escape_id("SOURCE_PINS"),
+                               Clock::WireName(wire));
+    wire->set_string_attribute(RTLIL::escape_id("PERIOD"),
+                               std::to_string(period));
+    std::string waveform(std::to_string(rising_edge) + " " +
+                         std::to_string(falling_edge));
+    wire->set_string_attribute(RTLIL::escape_id("WAVEFORM"), waveform);
+}
+
+void Clock::Add(const std::string& name, std::vector<RTLIL::Wire*> wires,
+                float period, float rising_edge, float falling_edge) {
+    std::for_each(wires.begin(), wires.end(), [&](RTLIL::Wire* wire) {
+	Add(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 {
-	rising_edge = fmod(rising_edge, period);
-	falling_edge = fmod(falling_edge, period);
-	clocks_.emplace_back(name, wire, period, rising_edge, falling_edge);
-	log("Added clock %s with period %f, rising_edge:%f, falling_edge:%f\n", name.c_str(),
-	    period, rising_edge, falling_edge);
+void Clock::Add(RTLIL::Wire* wire, float period, float rising_edge,
+                float falling_edge) {
+    Add(Clock::WireName(wire), wire, period, rising_edge, falling_edge);
+}
+
+float Clock::Period(RTLIL::Wire* clock_wire) {
+    if (!clock_wire->has_attribute(RTLIL::escape_id("PERIOD"))) {
+	log_cmd_error("PERIOD has not been specified on wire '%s'.\n",
+	              WireName(clock_wire).c_str());
     }
+    float period(0);
+    std::string period_str;
+    try {
+	period_str =
+	    clock_wire->get_string_attribute(RTLIL::escape_id("PERIOD"));
+	period = std::stof(period_str);
+    } catch (const std::invalid_argument& e) {
+	log_cmd_error(
+	    "Incorrect value '%s' specifed on PERIOD attribute for wire "
+	    "'%s'.\nPERIOD needs to be a float value.\n",
+	    period_str.c_str(), WireName(clock_wire).c_str());
+    }
+    return period;
 }
 
-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) << " ";
+std::pair<float, float> Clock::Waveform(RTLIL::Wire* clock_wire) {
+    if (!clock_wire->has_attribute(RTLIL::escape_id("WAVEFORM"))) {
+	float period(Period(clock_wire));
+	if (!period) {
+	    log_cmd_error(
+	        "Neither PERIOD nor WAVEFORM has been specified for wire %s\n",
+	        WireName(clock_wire).c_str());
+	    return std::make_pair(0, 0);
 	}
-	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
+	float falling_edge = period / 2;
+	log_warning(
+	    "Waveform has not been specified on wire '%s'.\nDefault value {0 %f} "
+	    "will be used\n",
+	    WireName(clock_wire).c_str(), falling_edge);
+	return std::make_pair(0, falling_edge);
     }
-    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());
-	}
+    float rising_edge(0);
+    float falling_edge(0);
+    std::string waveform(
+        clock_wire->get_string_attribute(RTLIL::escape_id("WAVEFORM")));
+    if (std::sscanf(waveform.c_str(), "%f %f", &rising_edge, &falling_edge) !=
+        2) {
+	log_cmd_error(
+	    "Incorrect value '%s' specifed on WAVEFORM attribute for wire "
+	    "'%s'.\nWAVEFORM needs to be specified in form of '<rising_edge> "
+	    "<falling_edge>' where the edge values are floats.\n",
+	    waveform.c_str(), WireName(clock_wire).c_str());
     }
-#ifdef SDC_DEBUG
-    log("Finish natural clock propagation\n\n");
-#endif
+    return std::make_pair(rising_edge, falling_edge);
 }
 
-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());
+float Clock::RisingEdge(RTLIL::Wire* clock_wire) {
+    return Waveform(clock_wire).first;
+}
+
+float Clock::FallingEdge(RTLIL::Wire* clock_wire) {
+    return Waveform(clock_wire).second;
+}
+
+std::string Clock::Name(RTLIL::Wire* clock_wire) {
+    if (clock_wire->has_attribute(RTLIL::escape_id("NAME"))) {
+	return clock_wire->get_string_attribute(RTLIL::escape_id("NAME"));
     }
-#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
+    return WireName(clock_wire);
 }
 
-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 pll_clocks =
-	    pass->FindSinkClocksForCellType(clock, "PLLE2_ADV");
-	for (auto pll_clock : pll_clocks) {
-#ifdef SDC_DEBUG
-	    log("PLL clock: %s\n", pll_clock.Name().c_str());
-#endif
-	    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); });
-}
-
-Clock::Clock(RTLIL::Wire* wire, float period,
-             float rising_edge, float falling_edge)
-    : Clock(RTLIL::id2cstr(wire->name), wire, period, rising_edge, falling_edge) {}
-
-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_ = fmod(rising_edge, period_);
-    falling_edge_ = fmod(falling_edge, period_);
-}
-
-std::string Clock::ClockWireName(RTLIL::Wire* wire) {
+std::string Clock::WireName(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{"\\$"}, "\\$");
+    return AddEscaping(RTLIL::unescape_id(wire->name));
 }
+
+const std::map<std::string, RTLIL::Wire*> Clocks::GetClocks(
+    RTLIL::Design* design) {
+    std::map<std::string, RTLIL::Wire*> clock_wires;
+    RTLIL::Module* top_module = design->top_module();
+    for (auto& wire_obj : top_module->wires_) {
+	auto& wire = wire_obj.second;
+	if (wire->has_attribute(RTLIL::escape_id("CLOCK_SIGNAL"))) {
+	    if (wire->get_string_attribute(RTLIL::escape_id("CLOCK_SIGNAL")) ==
+	        "yes") {
+		clock_wires.insert(std::make_pair(Clock::WireName(wire), wire));
+	    }
+	}
+    }
+    return clock_wires;
+}
+
diff --git a/sdc-plugin/clocks.h b/sdc-plugin/clocks.h
index 67ca272..2bcdf75 100644
--- a/sdc-plugin/clocks.h
+++ b/sdc-plugin/clocks.h
@@ -18,6 +18,7 @@
 #ifndef _CLOCKS_H_
 #define _CLOCKS_H_
 
+#include <map>
 #include <vector>
 #include "buffers.h"
 #include "kernel/rtlil.h"
@@ -31,53 +32,28 @@
 
 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);
-    Clock(RTLIL::Wire* wire, 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);
-    static std::string ClockWireName(RTLIL::Wire* wire);
+    static void Add(const std::string& name, RTLIL::Wire* wire, float period,
+                    float rising_edge, float falling_edge);
+    static void Add(const std::string& name, std::vector<RTLIL::Wire*> wires,
+                    float period, float rising_edge, float falling_edge);
+    static void Add(RTLIL::Wire* wire, float period, float rising_edge,
+                    float falling_edge);
+    static float Period(RTLIL::Wire* clock_wire);
+    static float RisingEdge(RTLIL::Wire* clock_wire);
+    static float FallingEdge(RTLIL::Wire* clock_wire);
+    static std::string Name(RTLIL::Wire* clock_wire);
+    static std::string WireName(RTLIL::Wire* wire);
+    static std::string AddEscaping(const std::string& name) {
+	return std::regex_replace(name, std::regex{"\\$"}, "\\$");
+    }
 
    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);
+    static std::pair<float, float> Waveform(RTLIL::Wire* clock_wire);
 };
 
 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);
-    const std::vector<Clock> GetClocks() {
-	return clocks_;
-    }
-
-   private:
-    std::vector<Clock> clocks_;
-    void PropagateThroughBuffer(Propagation* pass, Clock& clock,
-                                Buffer buffer);
+    static const std::map<std::string, RTLIL::Wire*> GetClocks(RTLIL::Design* design);
 };
 
 #endif  // _CLOCKS_H_
diff --git a/sdc-plugin/propagation.cc b/sdc-plugin/propagation.cc
index e741e1a..a2dffe2 100644
--- a/sdc-plugin/propagation.cc
+++ b/sdc-plugin/propagation.cc
@@ -20,53 +20,45 @@
 
 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);
+void Propagation::PropagateThroughBuffers(Buffer buffer) {
+    for (auto& clock : Clocks::GetClocks(design_)) {
+	auto& clock_wire = clock.second;
+#ifdef SDC_DEBUG
+	log("Clock wire %s\n", Clock::WireName(clock_wire).c_str());
+#endif
+	auto buf_wires =
+	    FindSinkWiresForCellType(clock_wire, buffer.type, buffer.output);
+	int path_delay(0);
+	for (auto wire : buf_wires) {
+#ifdef SDC_DEBUG
+	    log("%s wire: %s\n", buffer.type.c_str(),
+	        RTLIL::id2cstr(wire->name));
+#endif
+	    path_delay += buffer.delay;
+	    Clock::Add(wire, Clock::Period(clock_wire),
+	               Clock::RisingEdge(clock_wire) + path_delay,
+	               Clock::FallingEdge(clock_wire) + path_delay);
 	}
     }
-    return alias_wires;
 }
 
-std::vector<Clock> ClockDividerPropagation::FindSinkClocksForCellType(
-    Clock driving_clock, const std::string& cell_type) {
-    std::vector<Clock> clocks;
-    auto clock_wires = driving_clock.GetClockWires();
-    for (auto clock_wire : clock_wires) {
-	if (cell_type == "PLLE2_ADV") {
-	    RTLIL::Cell* cell = NULL;
-	    for (auto input : Pll::inputs) {
-		cell = FindSinkCellOnPort(clock_wire, input);
-		if (cell and RTLIL::unescape_id(cell->type) == cell_type) {
-		    break;
-		}
-	    }
-	    if (!cell) {
-		return clocks;
-	    }
-	    Pll pll(cell, driving_clock.Period(), driving_clock.RisingEdge());
-	    for (auto output : Pll::outputs) {
-		RTLIL::Wire* wire = FindSinkWireOnPort(cell, output);
-		if (wire) {
-		    float clkout_period(pll.clkout_period.at(output));
-		    float clkout_rising_edge(pll.clkout_rising_edge.at(output));
-		    float clkout_falling_edge(pll.clkout_falling_edge.at(output));
-		    Clock clock(wire, clkout_period, clkout_rising_edge, clkout_falling_edge);
-		    clocks.push_back(clock);
-		}
-	    }
-	}
+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;
     }
-    return clocks;
+    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::FindSinkCellOfType(RTLIL::Wire* wire,
@@ -95,25 +87,6 @@
     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;
@@ -167,3 +140,104 @@
     }
     return sink_wire;
 }
+void NaturalPropagation::Run() {
+#ifdef SDC_DEBUG
+    log("Start natural clock propagation\n");
+#endif
+    for (auto& clock : Clocks::GetClocks(design_)) {
+	auto& clock_wire = clock.second;
+#ifdef SDC_DEBUG
+	log("Processing clock %s\n", RTLIL::id2cstr(clock_wire->name));
+#endif
+	auto aliases = FindAliasWires(clock_wire);
+	Clock::Add(Clock::WireName(clock_wire), aliases,
+	           Clock::Period(clock_wire), Clock::RisingEdge(clock_wire),
+	           Clock::FallingEdge(clock_wire));
+    }
+#ifdef SDC_DEBUG
+    log("Finish natural clock propagation\n\n");
+#endif
+}
+
+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;
+}
+
+void BufferPropagation::Run() {
+#ifdef SDC_DEBUG
+    log("Start buffer clock propagation\n");
+    log("IBUF pass\n");
+#endif
+    PropagateThroughBuffers(IBuf());
+#ifdef SDC_DEBUG
+    log("BUFG pass\n");
+#endif
+    PropagateThroughBuffers(Bufg());
+#ifdef SDC_DEBUG
+    log("Finish buffer clock propagation\n\n");
+#endif
+}
+
+void ClockDividerPropagation::Run() {
+#ifdef SDC_DEBUG
+    log("Start clock divider clock propagation\n");
+#endif
+    PropagateThroughClockDividers(Pll());
+    PropagateThroughBuffers(Bufg());
+#ifdef SDC_DEBUG
+    log("Finish clock divider clock propagation\n\n");
+#endif
+}
+
+void ClockDividerPropagation::PropagateThroughClockDividers(
+    ClockDivider divider) {
+    for (auto& clock : Clocks::GetClocks(design_)) {
+	auto& clock_wire = clock.second;
+#ifdef SDC_DEBUG
+	log("Processing clock %s\n", Clock::WireName(clock_wire).c_str());
+#endif
+	PropagateClocksForCellType(clock_wire, divider.type);
+    }
+}
+
+void ClockDividerPropagation::PropagateClocksForCellType(
+    RTLIL::Wire* driver_wire, const std::string& cell_type) {
+    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;
+	}
+	Pll pll(cell, Clock::Period(driver_wire),
+	        Clock::RisingEdge(driver_wire));
+	for (auto output : Pll::outputs) {
+	    RTLIL::Wire* wire = FindSinkWireOnPort(cell, output);
+	    if (wire) {
+		float clkout_period(pll.clkout_period.at(output));
+		float clkout_rising_edge(pll.clkout_rising_edge.at(output));
+		float clkout_falling_edge(pll.clkout_falling_edge.at(output));
+		Clock::Add(wire, clkout_period, clkout_rising_edge,
+		           clkout_falling_edge);
+	    }
+	}
+    }
+}
+
diff --git a/sdc-plugin/propagation.h b/sdc-plugin/propagation.h
index 6774062..788b138 100644
--- a/sdc-plugin/propagation.h
+++ b/sdc-plugin/propagation.h
@@ -26,17 +26,18 @@
    public:
     Propagation(RTLIL::Design* design, Pass* pass)
         : design_(design), pass_(pass) {}
-    virtual ~Propagation(){}
+    virtual ~Propagation() {}
 
-    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);
+    virtual void Run() = 0;
 
    protected:
     RTLIL::Design* design_;
     Pass* pass_;
 
+    void PropagateThroughBuffers(Buffer buffer);
+    std::vector<RTLIL::Wire*> FindSinkWiresForCellType(
+        RTLIL::Wire* driver_wire, const std::string& cell_type,
+        const std::string& cell_port);
     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,
@@ -48,7 +49,7 @@
     NaturalPropagation(RTLIL::Design* design, Pass* pass)
         : Propagation(design, pass) {}
 
-    void Run(Clocks& clocks) override { clocks.Propagate(this); }
+    void Run() override;
     std::vector<RTLIL::Wire*> FindAliasWires(RTLIL::Wire* wire);
 };
 
@@ -57,7 +58,7 @@
     BufferPropagation(RTLIL::Design* design, Pass* pass)
         : Propagation(design, pass) {}
 
-    void Run(Clocks& clocks) override { clocks.Propagate(this); }
+    void Run() override;
 };
 
 class ClockDividerPropagation : public Propagation {
@@ -65,8 +66,9 @@
     ClockDividerPropagation(RTLIL::Design* design, Pass* pass)
         : Propagation(design, pass) {}
 
-    void Run(Clocks& clocks) override { clocks.Propagate(this); }
-    std::vector<Clock> FindSinkClocksForCellType(Clock driver_wire,
-                                                 const std::string& cell_type);
+    void Run() override;
+    void PropagateClocksForCellType(RTLIL::Wire* driver_wire,
+                                    const std::string& cell_type);
+    void PropagateThroughClockDividers(ClockDivider divider);
 };
 #endif  // PROPAGATION_H_
diff --git a/sdc-plugin/sdc.cc b/sdc-plugin/sdc.cc
index c138b04..880eeb2 100644
--- a/sdc-plugin/sdc.cc
+++ b/sdc-plugin/sdc.cc
@@ -61,8 +61,8 @@
 };
 
 struct WriteSdcCmd : public Backend {
-    WriteSdcCmd(Clocks& clocks, SdcWriter& sdc_writer)
-        : Backend("sdc", "Write SDC file"), clocks_(clocks), sdc_writer_(sdc_writer) {}
+    WriteSdcCmd(SdcWriter& sdc_writer)
+        : Backend("sdc", "Write SDC file"), sdc_writer_(sdc_writer) {}
 
     void help() override {
 	log("\n");
@@ -73,22 +73,21 @@
     }
 
     void execute(std::ostream*& f, std::string filename,
-                 std::vector<std::string> args, RTLIL::Design*) override {
+                 std::vector<std::string> args, RTLIL::Design* 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);
-	sdc_writer_.WriteSdc(clocks_, *f);
+	sdc_writer_.WriteSdc(design, *f);
     }
 
-    Clocks& clocks_;
     SdcWriter& sdc_writer_;
 };
 
 struct CreateClockCmd : public Pass {
-    CreateClockCmd(Clocks& clocks)
-        : Pass("create_clock", "Create clock object"), clocks_(clocks) {}
+    CreateClockCmd()
+        : Pass("create_clock", "Create clock object") {}
 
     void help() override {
 	log("\n");
@@ -170,7 +169,7 @@
 	    rising_edge = 0;
 	    falling_edge = period / 2;
 	}
-	clocks_.AddClock(name, selected_wires, period, rising_edge,
+	Clock::Add(name, selected_wires, period, rising_edge,
 	                 falling_edge);
     }
 
@@ -179,13 +178,11 @@
 	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) {}
+    GetClocksCmd()
+        : Pass("get_clocks", "Create clock object") {}
 
     void help() override {
 	log("\n");
@@ -195,28 +192,30 @@
 	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) {
+    void execute(std::vector<std::string> args,
+                 RTLIL::Design* design) override {
+	if (args.size() > 1) {
+	    log_warning("Command doesn't support arguments, so they will be ignored.\n");
+	}
+	std::map<std::string, RTLIL::Wire*> clocks(Clocks::GetClocks(design));
+	if (clocks.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());
+	for (auto& clock : clocks) {
+	    auto& wire = clock.second;
+	    const char* name = RTLIL::id2cstr(wire->name);
+	    Tcl_Obj* name_obj = Tcl_NewStringObj(name, -1);
 	    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) {}
+    PropagateClocksCmd()
+        : Pass("propagate_clocks", "Propagate clock information") {}
 
     void help() override {
 	log("\n");
@@ -226,35 +225,33 @@
 	log("\n");
     }
 
-    void execute(__attribute__((unused)) std::vector<std::string> args,
+    void execute(std::vector<std::string> args,
                  RTLIL::Design* design) override {
+	if (args.size() > 1) {
+	    log_warning("Command accepts no arguments.\nAll will be ignored.\n");
+	}
 	if (!design->top_module()) {
 	    log_cmd_error("No top module selected\n");
 	}
 
 	std::array<std::unique_ptr<Propagation>, 2> passes{
-	    std::unique_ptr<BufferPropagation>(
+	    std::unique_ptr<Propagation>(
 	        new BufferPropagation(design, this)),
-	    std::unique_ptr<ClockDividerPropagation>(
+	    std::unique_ptr<Propagation>(
 	        new ClockDividerPropagation(design, this))};
 
 	log("Perform clock propagation\n");
 
 	for (auto& pass : passes) {
-	    pass->Run(clocks_);
+	    pass->Run();
 	}
     }
-
-    Clocks& clocks_;
 };
 
 class SdcPlugin {
    public:
     SdcPlugin()
-        : write_sdc_cmd_(clocks_, sdc_writer_),
-          create_clock_cmd_(clocks_),
-          get_clocks_cmd_(clocks_),
-          propagate_clocks_cmd_(clocks_),
+        : write_sdc_cmd_(sdc_writer_),
           set_false_path_cmd_(sdc_writer_),
           set_max_delay_cmd_(sdc_writer_),
           set_clock_groups_cmd_(sdc_writer_) {
@@ -271,7 +268,6 @@
     SetClockGroups set_clock_groups_cmd_;
 
    private:
-    Clocks clocks_;
     SdcWriter sdc_writer_;
 } SdcPlugin;
 
diff --git a/sdc-plugin/sdc_writer.cc b/sdc-plugin/sdc_writer.cc
index 4c7ae57..80c8e5e 100644
--- a/sdc-plugin/sdc_writer.cc
+++ b/sdc-plugin/sdc_writer.cc
@@ -37,30 +37,24 @@
     clock_groups_.Add(clock_group, relation);
 }
 
-void SdcWriter::WriteSdc(Clocks& clocks, std::ostream& file) {
-    WriteClocks(clocks, file);
+void SdcWriter::WriteSdc(RTLIL::Design* design, std::ostream& file) {
+    WriteClocks(design, file);
     WriteFalsePaths(file);
     WriteMaxDelay(file);
     WriteClockGroups(file);
 }
 
-void SdcWriter::WriteClocks(Clocks& clocks, std::ostream& file) {
-    for (auto clock : clocks.GetClocks()) {
-	auto clock_wires = clock.GetClockWires();
+void SdcWriter::WriteClocks(RTLIL::Design* design, std::ostream& file) {
+    for (auto& clock : Clocks::GetClocks(design)) {
 	// 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; })) {
+	auto& clock_wire = clock.second;
+	if (clock_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 << "create_clock -period " << Clock::Period(clock_wire);
+	file << " -waveform {" << Clock::RisingEdge(clock_wire) << " "
+	     << Clock::FallingEdge(clock_wire) << "}";
+	file << " " << Clock::WireName(clock_wire);
 	file << std::endl;
     }
 }
diff --git a/sdc-plugin/sdc_writer.h b/sdc-plugin/sdc_writer.h
index 9bcbcf1..5cdf8a2 100644
--- a/sdc-plugin/sdc_writer.h
+++ b/sdc-plugin/sdc_writer.h
@@ -61,10 +61,10 @@
     void AddFalsePath(FalsePath false_path);
     void SetMaxDelay(TimingPath timing_path);
     void AddClockGroup(ClockGroups::ClockGroup clock_group, ClockGroups::ClockGroupRelation relation);
-    void WriteSdc(Clocks& clocks, std::ostream& file);
+    void WriteSdc(RTLIL::Design* design, std::ostream& file);
 
    private:
-    void WriteClocks(Clocks& clocks, std::ostream& file);
+    void WriteClocks(RTLIL::Design* design, std::ostream& file);
     void WriteFalsePaths(std::ostream& file);
     void WriteMaxDelay(std::ostream& file);
     void WriteClockGroups(std::ostream& file);
diff --git a/sdc-plugin/tests/Makefile b/sdc-plugin/tests/Makefile
index 3b45b57..3d602b7 100644
--- a/sdc-plugin/tests/Makefile
+++ b/sdc-plugin/tests/Makefile
@@ -2,6 +2,10 @@
 # set_false_path - test the set_false_path command
 # set_max_delay - test the set_max_delay command
 # set_clock_groups - test the set_clock_groups command
+# restore_from_json - test clock propagation when design restored from json instead verilog
+# period_check - test if the clock propagation fails if a clock wire is missing the PERIOD attribute
+# waveform_check - test if the WAVEFORM attribute value is correct on wire
+# period_format_check - test if PERIOD attribute value is correct on wire
 
 TESTS = counter \
 	counter2 \
@@ -11,7 +15,13 @@
 	pll_approx_equal \
 	set_false_path \
 	set_max_delay \
-	set_clock_groups
+	set_clock_groups \
+	restore_from_json \
+	period_check \
+	waveform_check \
+	period_format_check
+
+UNIT_TESTS = escaping
 
 include $(shell pwd)/../../Makefile_test.common
 
@@ -24,3 +34,10 @@
 set_false_path_verify = $(call diff_test,set_false_path,sdc)
 set_max_delay_verify = $(call diff_test,set_max_delay,sdc)
 set_clock_groups_verify = $(call diff_test,set_clock_groups,sdc)
+restore_from_json_verify = diff restore_from_json/restore_from_json_1.sdc restore_from_json/restore_from_json_2.sdc
+period_check_verify = true
+period_check_negative = 1
+waveform_check_verify = true
+waveform_check_negative = 1
+period_format_check_verify = true
+period_format_check_negative = 1
diff --git a/sdc-plugin/tests/counter/counter.golden.sdc b/sdc-plugin/tests/counter/counter.golden.sdc
index c1a2ea1..5be53e3 100644
--- a/sdc-plugin/tests/counter/counter.golden.sdc
+++ b/sdc-plugin/tests/counter/counter.golden.sdc
@@ -1,6 +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 {0 5} clk_int_1
+create_clock -period 10 -waveform {0 5} ibuf_proxy_out
 create_clock -period 10 -waveform {0 5} middle_inst_1.clk_int
 create_clock -period 10 -waveform {0 5} middle_inst_4.clk
diff --git a/sdc-plugin/tests/counter/counter.golden.txt b/sdc-plugin/tests/counter/counter.golden.txt
index 065b110..78277ed 100644
--- a/sdc-plugin/tests/counter/counter.golden.txt
+++ b/sdc-plugin/tests/counter/counter.golden.txt
@@ -1 +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
+{$auto$clkbufmap.cc:247:execute$1918} {$auto$clkbufmap.cc:247:execute$1920} clk clk2 clk_int_1 ibuf_proxy_out middle_inst_1.clk_int middle_inst_4.clk
diff --git a/sdc-plugin/tests/counter/counter.tcl b/sdc-plugin/tests/counter/counter.tcl
index 4fcf9be..b045b3c 100644
--- a/sdc-plugin/tests/counter/counter.tcl
+++ b/sdc-plugin/tests/counter/counter.tcl
@@ -24,3 +24,4 @@
 
 # Write out the SDC file after the clock propagation step
 write_sdc $::env(DESIGN_TOP).sdc
+write_json $::env(DESIGN_TOP).json
diff --git a/sdc-plugin/tests/counter2/counter2.golden.sdc b/sdc-plugin/tests/counter2/counter2.golden.sdc
index 4bf0bf2..33152b3 100644
--- a/sdc-plugin/tests/counter2/counter2.golden.sdc
+++ b/sdc-plugin/tests/counter2/counter2.golden.sdc
@@ -1,6 +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 {0 5} clk_int_1
+create_clock -period 10 -waveform {0 5} ibuf_proxy_out
 create_clock -period 10 -waveform {0 5} middle_inst_1.clk_int
 create_clock -period 10 -waveform {1 6} middle_inst_4.clk
diff --git a/sdc-plugin/tests/counter2/counter2.golden.txt b/sdc-plugin/tests/counter2/counter2.golden.txt
index ca1b187..78277ed 100644
--- a/sdc-plugin/tests/counter2/counter2.golden.txt
+++ b/sdc-plugin/tests/counter2/counter2.golden.txt
@@ -1 +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
+{$auto$clkbufmap.cc:247:execute$1918} {$auto$clkbufmap.cc:247:execute$1920} clk clk2 clk_int_1 ibuf_proxy_out middle_inst_1.clk_int middle_inst_4.clk
diff --git a/sdc-plugin/tests/escaping/escaping.test.cc b/sdc-plugin/tests/escaping/escaping.test.cc
new file mode 100644
index 0000000..e17eef6
--- /dev/null
+++ b/sdc-plugin/tests/escaping/escaping.test.cc
@@ -0,0 +1,11 @@
+#include <clocks.h>
+
+#include <gtest/gtest.h>
+
+TEST(ClockTest, EscapeDollarSign) {
+        // convert wire_name to wire_name, i.e. unchanged
+        EXPECT_EQ(Clock::AddEscaping("wire_name"), "wire_name");
+        // convert $wire_name to \$wire_name
+        EXPECT_EQ(Clock::AddEscaping("$wire_name"), "\\$wire_name");
+}
+
diff --git a/sdc-plugin/tests/period_check/period_check.tcl b/sdc-plugin/tests/period_check/period_check.tcl
new file mode 100644
index 0000000..bc613de
--- /dev/null
+++ b/sdc-plugin/tests/period_check/period_check.tcl
@@ -0,0 +1,17 @@
+yosys -import
+plugin -i sdc
+# Import the commands from the plugins to the tcl interpreter
+yosys -import
+
+read_verilog $::env(DESIGN_TOP).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
+
+# Propagate the clocks
+propagate_clocks
+
+# Write out the SDC file after the clock propagation step
+write_sdc $::env(DESIGN_TOP).sdc
diff --git a/sdc-plugin/tests/period_check/period_check.v b/sdc-plugin/tests/period_check/period_check.v
new file mode 100644
index 0000000..77c1f9c
--- /dev/null
+++ b/sdc-plugin/tests/period_check/period_check.v
@@ -0,0 +1,36 @@
+module top((* CLOCK_SIGNAL = "yes", WAVEFORM = "0 5" *) 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/period_format_check/period_format_check.tcl b/sdc-plugin/tests/period_format_check/period_format_check.tcl
new file mode 100644
index 0000000..bc613de
--- /dev/null
+++ b/sdc-plugin/tests/period_format_check/period_format_check.tcl
@@ -0,0 +1,17 @@
+yosys -import
+plugin -i sdc
+# Import the commands from the plugins to the tcl interpreter
+yosys -import
+
+read_verilog $::env(DESIGN_TOP).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
+
+# Propagate the clocks
+propagate_clocks
+
+# Write out the SDC file after the clock propagation step
+write_sdc $::env(DESIGN_TOP).sdc
diff --git a/sdc-plugin/tests/period_format_check/period_format_check.v b/sdc-plugin/tests/period_format_check/period_format_check.v
new file mode 100644
index 0000000..e6ab294
--- /dev/null
+++ b/sdc-plugin/tests/period_format_check/period_format_check.v
@@ -0,0 +1,36 @@
+module top((* CLOCK_SIGNAL = "yes", PERIOD = "bad value", WAVEFORM = "0 5" *) 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
index 7fe2b2a..da025c5 100644
--- a/sdc-plugin/tests/pll/pll.golden.sdc
+++ b/sdc-plugin/tests/pll/pll.golden.sdc
@@ -1,8 +1,8 @@
 create_clock -period 10 -waveform {0 5} \$auto\$clkbufmap.cc:247:execute\$1829
-create_clock -period 10 -waveform {0 5} \$techmap1716\FDCE_0.C
 create_clock -period 10 -waveform {2.5 7.5} \$auto\$clkbufmap.cc:247:execute\$1831
-create_clock -period 10 -waveform {2.5 7.5} main_clkout0
 create_clock -period 2.5 -waveform {0 1.25} \$auto\$clkbufmap.cc:247:execute\$1833
-create_clock -period 2.5 -waveform {0 1.25} main_clkout1
 create_clock -period 5 -waveform {1.25 3.75} \$auto\$clkbufmap.cc:247:execute\$1835
+create_clock -period 10 -waveform {0 5} \$techmap1716\FDCE_0.C
+create_clock -period 10 -waveform {2.5 7.5} main_clkout0
+create_clock -period 2.5 -waveform {0 1.25} main_clkout1
 create_clock -period 5 -waveform {1.25 3.75} main_clkout2
diff --git a/sdc-plugin/tests/pll_approx_equal/pll_approx_equal.golden.sdc b/sdc-plugin/tests/pll_approx_equal/pll_approx_equal.golden.sdc
index add4f6a..b97da3e 100644
--- a/sdc-plugin/tests/pll_approx_equal/pll_approx_equal.golden.sdc
+++ b/sdc-plugin/tests/pll_approx_equal/pll_approx_equal.golden.sdc
@@ -1,8 +1,8 @@
 create_clock -period 10 -waveform {0 5} \$auto\$clkbufmap.cc:247:execute\$1829
-create_clock -period 10 -waveform {0 5} \$techmap1716\FDCE_0.C
 create_clock -period 9.99999 -waveform {0 5} \$auto\$clkbufmap.cc:247:execute\$1831
-create_clock -period 9.99999 -waveform {0 5} main_clkout_x1
 create_clock -period 5 -waveform {-2.5 0} \$auto\$clkbufmap.cc:247:execute\$1833
-create_clock -period 5 -waveform {-2.5 0} main_clkout_x2
 create_clock -period 2.5 -waveform {-1.875 -0.624999} \$auto\$clkbufmap.cc:247:execute\$1835
+create_clock -period 10 -waveform {0 5} \$techmap1716\FDCE_0.C
+create_clock -period 9.99999 -waveform {0 5} main_clkout_x1
+create_clock -period 5 -waveform {-2.5 0} main_clkout_x2
 create_clock -period 2.5 -waveform {-1.875 -0.624999} main_clkout_x4
diff --git a/sdc-plugin/tests/pll_div/pll_div.golden.sdc b/sdc-plugin/tests/pll_div/pll_div.golden.sdc
index 14ea0bc..06031cf 100644
--- a/sdc-plugin/tests/pll_div/pll_div.golden.sdc
+++ b/sdc-plugin/tests/pll_div/pll_div.golden.sdc
@@ -1,8 +1,8 @@
 create_clock -period 10 -waveform {0 5} \$auto\$clkbufmap.cc:247:execute\$1829
-create_clock -period 10 -waveform {0 5} \$techmap1716\FDCE_0.C
 create_clock -period 20 -waveform {5 15} \$auto\$clkbufmap.cc:247:execute\$1831
-create_clock -period 20 -waveform {5 15} main_clkout0
 create_clock -period 5 -waveform {0 2.5} \$auto\$clkbufmap.cc:247:execute\$1833
-create_clock -period 5 -waveform {0 2.5} main_clkout1
 create_clock -period 10 -waveform {2.5 7.5} \$auto\$clkbufmap.cc:247:execute\$1835
+create_clock -period 10 -waveform {0 5} \$techmap1716\FDCE_0.C
+create_clock -period 20 -waveform {5 15} main_clkout0
+create_clock -period 5 -waveform {0 2.5} main_clkout1
 create_clock -period 10 -waveform {2.5 7.5} main_clkout2
diff --git a/sdc-plugin/tests/pll_fbout_phase/pll_fbout_phase.golden.sdc b/sdc-plugin/tests/pll_fbout_phase/pll_fbout_phase.golden.sdc
index e3f281c..df8301d 100644
--- a/sdc-plugin/tests/pll_fbout_phase/pll_fbout_phase.golden.sdc
+++ b/sdc-plugin/tests/pll_fbout_phase/pll_fbout_phase.golden.sdc
@@ -1,8 +1,8 @@
 create_clock -period 10 -waveform {0 5} \$auto\$clkbufmap.cc:247:execute\$1829
-create_clock -period 10 -waveform {0 5} \$techmap1716\FDCE_0.C
 create_clock -period 10 -waveform {0 5} \$auto\$clkbufmap.cc:247:execute\$1831
-create_clock -period 10 -waveform {0 5} main_clkout_x1
 create_clock -period 5 -waveform {-2.5 0} \$auto\$clkbufmap.cc:247:execute\$1833
-create_clock -period 5 -waveform {-2.5 0} main_clkout_x2
 create_clock -period 2.5 -waveform {-1.875 -0.625} \$auto\$clkbufmap.cc:247:execute\$1835
+create_clock -period 10 -waveform {0 5} \$techmap1716\FDCE_0.C
+create_clock -period 10 -waveform {0 5} main_clkout_x1
+create_clock -period 5 -waveform {-2.5 0} main_clkout_x2
 create_clock -period 2.5 -waveform {-1.875 -0.625} main_clkout_x4
diff --git a/sdc-plugin/tests/restore_from_json/restore_from_json.tcl b/sdc-plugin/tests/restore_from_json/restore_from_json.tcl
new file mode 100644
index 0000000..288419b
--- /dev/null
+++ b/sdc-plugin/tests/restore_from_json/restore_from_json.tcl
@@ -0,0 +1,16 @@
+yosys -import
+
+plugin -i sdc
+
+yosys -import
+
+read_verilog $::env(DESIGN_TOP).v
+synth_xilinx
+create_clock -period 10 clk
+propagate_clocks
+write_sdc $::env(DESIGN_TOP)_1.sdc
+write_json $::env(DESIGN_TOP).json
+
+design -push
+read_json $::env(DESIGN_TOP).json
+write_sdc $::env(DESIGN_TOP)_2.sdc
diff --git a/sdc-plugin/tests/restore_from_json/restore_from_json.v b/sdc-plugin/tests/restore_from_json/restore_from_json.v
new file mode 100644
index 0000000..0c35ede
--- /dev/null
+++ b/sdc-plugin/tests/restore_from_json/restore_from_json.v
@@ -0,0 +1,11 @@
+module top(input clk, input i, output o);
+
+reg [0:0] outff = 0;
+
+assign o = outff;
+
+always @(posedge clk) begin
+    outff <= i;
+end
+
+endmodule
diff --git a/sdc-plugin/tests/waveform_check/waveform_check.tcl b/sdc-plugin/tests/waveform_check/waveform_check.tcl
new file mode 100644
index 0000000..bc613de
--- /dev/null
+++ b/sdc-plugin/tests/waveform_check/waveform_check.tcl
@@ -0,0 +1,17 @@
+yosys -import
+plugin -i sdc
+# Import the commands from the plugins to the tcl interpreter
+yosys -import
+
+read_verilog $::env(DESIGN_TOP).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
+
+# Propagate the clocks
+propagate_clocks
+
+# Write out the SDC file after the clock propagation step
+write_sdc $::env(DESIGN_TOP).sdc
diff --git a/sdc-plugin/tests/waveform_check/waveform_check.v b/sdc-plugin/tests/waveform_check/waveform_check.v
new file mode 100644
index 0000000..a7ff226
--- /dev/null
+++ b/sdc-plugin/tests/waveform_check/waveform_check.v
@@ -0,0 +1,36 @@
+module top((* CLOCK_SIGNAL = "yes", PERIOD = "10", WAVEFORM = "bad value" *) 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/third_party/googletest b/third_party/googletest
new file mode 160000
index 0000000..41b5f14
--- /dev/null
+++ b/third_party/googletest
@@ -0,0 +1 @@
+Subproject commit 41b5f149ab306e96b5b2faf523505d75acffd98a