# print(rtlil.convert(frag, ports=[ctr.o, ctr.ce]))
 print(verilog.convert(frag, ports=[ctr.o, ctr.ce]))
 
-sim = pysim.Simulator(frag, vcd_file=open("ctrl.vcd", "w"))
+sim = pysim.Simulator(frag,
+    vcd_file=open("ctrl.vcd", "w"),
+    gtkw_file=open("ctrl.gtkw", "w"),
+    gtkw_signals=[ctr.ce, ctr.v, ctr.o])
 sim.add_clock("sync", 1e-6)
 def ce_proc():
     yield; yield; yield
 
+import math
 from vcd import VCDWriter
+from vcd.gtkw import GTKWSave
 
 from ..tools import flatten
 from ..fhdl.ast import *
 
 
 class Simulator:
-    def __init__(self, fragment=None, vcd_file=None):
+    def __init__(self, fragment=None, vcd_file=None, gtkw_file=None, gtkw_signals=()):
         self._fragments       = {}            # fragment -> hierarchy
 
         self._domains         = {}            # str/domain -> ClockDomain
         self._started         = False
         self._timestamp       = 0.
         self._epsilon         = 1e-10
+        self._fastest_clock   = self._epsilon
         self._state           = _State()
 
         self._processes       = set()         # {process}
         self._vcd_file        = vcd_file
         self._vcd_writer      = None
         self._vcd_signals     = ValueDict()   # signal -> set(vcd_signal)
+        self._vcd_names       = ValueDict()   # signal -> str/name
+
+        self._gtkw_file       = gtkw_file
+        self._gtkw_signals    = gtkw_signals
 
         if fragment is not None:
             fragment = fragment.prepare()
         self._processes.add(process)
 
     def add_clock(self, domain, period):
-        clk = self._domains[domain].clk
+        if self._fastest_clock == self._epsilon or period < self._fastest_clock:
+            self._fastest_clock = period
+
         half_period = period / 2
+        clk = self._domains[domain].clk
         def clk_process():
             yield Passive()
             yield Delay(half_period)
                         self._vcd_signals[signal].add(self._vcd_writer.register_var(
                             scope=".".join(self._fragments[fragment]), name=name_suffix,
                             var_type="wire", size=signal.nbits, init=signal.reset))
+                        if signal not in self._vcd_names:
+                            self._vcd_names[signal] = \
+                                ".".join(self._fragments[fragment] + (name_suffix,))
                         break
                     except KeyError:
                         suffix = (suffix or 0) + 1
     def __exit__(self, *args):
         if self._vcd_writer:
             self._vcd_writer.close(self._timestamp / self._epsilon)
+
+        if self._vcd_file and self._gtkw_file:
+            gtkw_save = GTKWSave(self._gtkw_file)
+            if hasattr(self._vcd_file, "name"):
+                gtkw_save.dumpfile(self._vcd_file.name)
+            if hasattr(self._vcd_file, "tell"):
+                gtkw_save.dumpfile_size(self._vcd_file.tell())
+
+            gtkw_save.treeopen("top")
+            gtkw_save.zoom_markers(math.log(self._epsilon / self._fastest_clock) - 14)
+
+            for domain, cd in self._domains.items():
+                with gtkw_save.group("d.{}".format(domain)):
+                    if cd.rst is not None:
+                        gtkw_save.trace("top.{}".format(cd.rst.name))
+                    gtkw_save.trace("top.{}".format(cd.clk.name))
+
+            for signal in self._gtkw_signals:
+                if signal in self._vcd_names:
+                    if len(signal) > 1:
+                        suffix = "[{}:0]".format(len(signal) - 1)
+                    else:
+                        suffix = ""
+                    gtkw_save.trace(self._vcd_names[signal] + suffix)