Merge pull request #34 from antmicro/clock_detection

Enchance clock detection
diff --git a/tests/clocks/README.md b/tests/clocks/README.md
index e762b25..e969e29 100644
--- a/tests/clocks/README.md
+++ b/tests/clocks/README.md
@@ -1,14 +1,14 @@
 # `clocks` tests
 
 This directory contains test for the clock detection functionality for the
-`v2x_to_model.py` tool.
+`vlog_to_model.py` and `vlog_to_pbtype.py` tool.
 
 
 ## Detection of clock signals
 
- - [ ] Signal is named `clk`.
- - [ ] Signal has `clk` in the name.
- - [ ] Manually set via the `(* CLOCK *)` Verilog attribute.
+ - [ ] Signal name matches the regexp `[a-z_]*clk[a-z0-9]*$`
+ - [ ] Manually set via the `(* CLOCK *)` or `(* CLOCK=1 *)` Verilog attribute.
+ - [ ] Manually cleared via the `(* CLOCK=0 *)` Verilog attribute.
  - [ ] Signal drives synchronous logic (IE flipflop).
  - [ ] Detection in recursive module includes.
 
diff --git a/tests/clocks/input_attr_not_clock/block.sim.v b/tests/clocks/input_attr_not_clock/block.sim.v
new file mode 100644
index 0000000..f87f8b2
--- /dev/null
+++ b/tests/clocks/input_attr_not_clock/block.sim.v
@@ -0,0 +1,17 @@
+/*
+ * `input wire a` should be detected as a clock because it drives the flip
+ * flop. However, it has the attribute CLOCK set to 0 which should force it
+ * to be a regular input.
+ */
+module BLOCK(a, b, c);
+    (* CLOCK=0 *)
+    input wire a;
+    input wire b;
+    output wire c;
+    
+    reg r;
+    always @ ( posedge a ) begin
+    	r <= b;
+    end
+    assign c = r;
+endmodule
diff --git a/tests/clocks/input_attr_not_clock/golden.model.xml b/tests/clocks/input_attr_not_clock/golden.model.xml
new file mode 100644
index 0000000..51ff8df
--- /dev/null
+++ b/tests/clocks/input_attr_not_clock/golden.model.xml
@@ -0,0 +1,11 @@
+<models xmlns:xi="http://www.w3.org/2001/XInclude">
+  <model name="BLOCK">
+    <input_ports>
+      <port clock="a" combinational_sink_ports="c" name="a"/>
+      <port clock="a" name="b"/>
+    </input_ports>
+    <output_ports>
+      <port clock="a" name="c"/>
+    </output_ports>
+  </model>
+</models>
diff --git a/tests/clocks/input_attr_not_clock/golden.pb_type.xml b/tests/clocks/input_attr_not_clock/golden.pb_type.xml
new file mode 100644
index 0000000..c748dd3
--- /dev/null
+++ b/tests/clocks/input_attr_not_clock/golden.pb_type.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8'?>
+<pb_type xmlns:xi="http://www.w3.org/2001/XInclude" name="BLOCK" num_pb="1">
+  <blif_model>.subckt BLOCK</blif_model>
+  <input name="a" num_pins="1"/>
+  <input name="b" num_pins="1"/>
+  <output name="c" num_pins="1"/>
+</pb_type>
diff --git a/tests/clocks/input_named_regex/block.sim.v b/tests/clocks/input_named_regex/block.sim.v
new file mode 100644
index 0000000..3870d30
--- /dev/null
+++ b/tests/clocks/input_named_regex/block.sim.v
@@ -0,0 +1,14 @@
+(* whitebox *)
+module BLOCK(
+    input  wire clk,
+    input  wire Clk,
+    input  wire CLK,
+    input  wire clkX,
+    input  wire clkBus,
+    input  wire sys_clk,
+    input  wire sys_clk10,
+    input  wire regular_input,
+    output wire o
+);
+
+endmodule
diff --git a/tests/clocks/input_named_regex/golden.model.xml b/tests/clocks/input_named_regex/golden.model.xml
new file mode 100644
index 0000000..3f5edd2
--- /dev/null
+++ b/tests/clocks/input_named_regex/golden.model.xml
@@ -0,0 +1,17 @@
+<models xmlns:xi="http://www.w3.org/2001/XInclude">
+  <model name="BLOCK">
+    <input_ports>
+      <port is_clock="1" name="CLK"/>
+      <port is_clock="1" name="Clk"/>
+      <port is_clock="1" name="clk"/>
+      <port is_clock="1" name="clkBus"/>
+      <port is_clock="1" name="clkX"/>
+      <port name="regular_input"/>
+      <port is_clock="1" name="sys_clk"/>
+      <port is_clock="1" name="sys_clk10"/>
+    </input_ports>
+    <output_ports>
+      <port name="o"/>
+    </output_ports>
+  </model>
+</models>
diff --git a/tests/clocks/input_named_regex/golden.pb_type.xml b/tests/clocks/input_named_regex/golden.pb_type.xml
new file mode 100644
index 0000000..2edf093
--- /dev/null
+++ b/tests/clocks/input_named_regex/golden.pb_type.xml
@@ -0,0 +1,13 @@
+<?xml version='1.0' encoding='utf-8'?>
+<pb_type xmlns:xi="http://www.w3.org/2001/XInclude" name="BLOCK" num_pb="1">
+  <blif_model>.subckt BLOCK</blif_model>
+  <clock name="CLK" num_pins="1"/>
+  <clock name="Clk" num_pins="1"/>
+  <clock name="clk" num_pins="1"/>
+  <clock name="clkBus" num_pins="1"/>
+  <clock name="clkX" num_pins="1"/>
+  <clock name="sys_clk" num_pins="1"/>
+  <clock name="sys_clk10" num_pins="1"/>
+  <input name="regular_input" num_pins="1"/>
+  <output name="o" num_pins="1"/>
+</pb_type>
diff --git a/v2x/vlog_to_model.py b/v2x/vlog_to_model.py
index a4df516..3477de1 100755
--- a/v2x/vlog_to_model.py
+++ b/v2x/vlog_to_model.py
@@ -3,7 +3,9 @@
 Convert a Verilog simulation model to a VPR `model.xml`
 
 The following Verilog attributes are considered on ports:
-    - `(* CLOCK *)` : force a given port to be a clock
+    - `(* CLOCK *)` or `(* CLOCK=1 *)` : force a given port to be a clock
+
+    - `(* CLOCK=0 *)` : force a given port not to be a clock
 
     - `(* ASSOC_CLOCK="RDCLK" *)` : force a port's associated
                                     clock to a given value
@@ -26,6 +28,7 @@
 
 from .yosys import run
 from .yosys.json import YosysJSON
+from .yosys import utils as utils
 
 from .xmlinc import xmlinc
 
@@ -173,6 +176,13 @@
 
             for name, width, bits, iodir in ports:
                 nocomb = tmod.net_attr(name, "NO_COMB")
+
+                is_clock = name in clocks or utils.is_clock_name(name)
+
+                port_attrs = tmod.port_attrs(name)
+                if "CLOCK" in port_attrs:
+                    is_clock = int(port_attrs["CLOCK"]) != 0
+
                 attrs = dict(name=name)
                 sinks = run.get_combinational_sinks(infiles, top, name)
 
@@ -183,7 +193,7 @@
 
                 # FIXME: Check if ignoring clock for "combination_sink_ports"
                 # is a valid thing to do.
-                if name in clocks or "clk" in name.lower():
+                if is_clock:
                     attrs["is_clock"] = "1"
                 else:
                     clks = list()
diff --git a/v2x/vlog_to_pbtype.py b/v2x/vlog_to_pbtype.py
index d109dfb..7146992 100755
--- a/v2x/vlog_to_pbtype.py
+++ b/v2x/vlog_to_pbtype.py
@@ -44,7 +44,9 @@
                               with this wire
 
 The following are allowed on ports:
-    - `(* CLOCK *)` : force a given port to be a clock
+    - `(* CLOCK *)` or `(* CLOCK=1 *)` : force a given port to be a clock
+
+    - `(* CLOCK=0 *)` : force a given port not to be a clock
 
     - `(* ASSOC_CLOCK="RDCLK" *)` : force a port's associated clock to a
                                     given value
@@ -68,6 +70,7 @@
 
 from .yosys import run
 from .yosys.json import YosysJSON
+from .yosys import utils as utils
 
 from .xmlinc import xmlinc  # noqa: E402
 
@@ -808,7 +811,28 @@
         ET.SubElement(pb_type_xml, "pb_class", {}).text = pb_attrs["class"]
 
     # Create the pins for this pb_type
-    clocks = run.list_clocks(infiles, mod.name)
+    clocks = set(run.list_clocks(infiles, mod.name))
+
+    # Add extra clocks inferred from port names
+    # Mask out clocks with the attribute "CLOCK" not equal to 1
+    for name, width, bits, iodir in mod.ports:
+        port_attrs = mod.port_attrs(name)
+
+        is_clock = utils.is_clock_name(name)
+
+        # In pb_type "clock" ports can be only inputs. Clock outputs must
+        # be declared as "output".
+        if iodir == "output":
+            is_clock = False
+
+        if "CLOCK" in port_attrs:
+            is_clock = int(port_attrs["CLOCK"]) != 0
+
+        if is_clock:
+            clocks.add(name)
+        else:
+            clocks.discard(name)
+
     make_ports(clocks, mod, pb_type_xml, "clocks")
     make_ports(clocks, mod, pb_type_xml, "inputs")
     make_ports(clocks, mod, pb_type_xml, "outputs")
diff --git a/v2x/yosys/run.py b/v2x/yosys/run.py
index 91e361d..ef2627e 100755
--- a/v2x/yosys/run.py
+++ b/v2x/yosys/run.py
@@ -287,7 +287,7 @@
     """
     return do_select(
         infiles, module,
-        "c:* %x:+[CLK]:+[clk]:+[clock]:+[CLOCK] a:CLOCK=1 %u c:* %d x:* %i"
+        "c:* %x:+[CLK]:+[clk]:+[clock]:+[CLOCK] c:* %d x:* %i"
     )
 
 
diff --git a/v2x/yosys/utils.py b/v2x/yosys/utils.py
index 722ac9b..f6c3597 100644
--- a/v2x/yosys/utils.py
+++ b/v2x/yosys/utils.py
@@ -1,11 +1,41 @@
 #!/usr/bin/env python3
 import re
-"""The JSON Yosys outputs isn't acutally compliant JSON, as it contains C-style
-comments. These must be stripped."""
+
+CLOCK_NAME_REGEX = re.compile(r"[a-z_]*clk[a-z0-9]*$")
 
 
 def strip_yosys_json(text):
+    """The JSON Yosys outputs isn't acutally compliant JSON, as it contains C-style
+    comments. These must be stripped."""
     stripped = re.sub(r'\\\n', '', text)
     stripped = re.sub(r'//.*\n', '\n', stripped)
     stripped = re.sub(r'/\*.*\*/', '', stripped)
     return stripped
+
+
+def is_clock_name(name):
+    """
+    Returns true if the port name correspond to a clock according to arbitrary
+    regular expressions.
+
+    >>> is_clock_name("data")
+    False
+    >>> is_clock_name("clk")
+    True
+    >>> is_clock_name("Clk")
+    True
+    >>> is_clock_name("Clk_Rst0")
+    False
+    >>> is_clock_name("Data_clk")
+    True
+    >>> is_clock_name("clk99")
+    True
+    >>> is_clock_name("bus_clk99")
+    True
+    >>> is_clock_name("busclk15")
+    True
+    >>> is_clock_name("clkb")
+    True
+    """
+    match = CLOCK_NAME_REGEX.match(name.lower())
+    return match is not None