Merge pull request #27 from antmicro/no_comb

Added NO_COMB annotation.
diff --git a/README.md b/README.md
index 29ebfac..37dac98 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,9 @@
 # python-symbiflow-v2x
-Tool for converting specialized annotated Verilog models into XML needed for Verilog to Routing flow.
+
+[![Documentation Status](https://readthedocs.org/projects/python-symbiflow-v2x/badge/?version=latest)](https://python-symbiflow-v2x.readthedocs.io/en/latest/?badge=latest) [![Build Status](https://travis-ci.com/SymbiFlow/python-symbiflow-v2x.svg?branch=master)](https://travis-ci.com/SymbiFlow/python-symbiflow-v2x)
+
+Tool for converting specialized annotated Verilog models into XML needed for
+[Verilog to Routing flow](https://docs.verilogtorouting.org/en/latest/arch/reference/).
+
+Documentation can be found at https://python-symbiflow-v2x.readthedocs.io/en/latest/
+
diff --git a/tests/internal_conn/README.rst b/tests/internal_conn/README.rst
new file mode 100644
index 0000000..27067d3
--- /dev/null
+++ b/tests/internal_conn/README.rst
@@ -0,0 +1,13 @@
+A test for cells with "passthrough" modes and direct pin-to-pin connections.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+This test demonstrates two use cases.
+
+1. A non-primitive cell that has direct input to output connections inside.
+
+    Such a cell has child cells that are connected to its ports. But it also contains direct connection(s) between its input and output ports. In such a case the direct connections have to be expressed within its interconnect along with regular connections to child cells.
+
+
+2. A cell with modes in which one of them is a "passthrough"
+
+    A passthrough mode defines a direct input to output connection and no child cells. In such a case no pb_type is to be generated for the "passthrough" mode. Instead, an interconnect defining the direect connection is placed directly under the "mode" tag.
diff --git a/tests/internal_conn/child/child.model.xml b/tests/internal_conn/child/child.model.xml
new file mode 100644
index 0000000..90cfeb8
--- /dev/null
+++ b/tests/internal_conn/child/child.model.xml
@@ -0,0 +1,10 @@
+<models xmlns:xi="http://www.w3.org/2001/XInclude">
+  <model name="CHILD">
+    <input_ports>
+      <port name="I"/>
+    </input_ports>
+    <output_ports>
+      <port name="O"/>
+    </output_ports>
+  </model>
+</models>
diff --git a/tests/internal_conn/child/child.pb_type.xml b/tests/internal_conn/child/child.pb_type.xml
new file mode 100644
index 0000000..0fda113
--- /dev/null
+++ b/tests/internal_conn/child/child.pb_type.xml
@@ -0,0 +1,6 @@
+<?xml version='1.0' encoding='utf-8'?>
+<pb_type xmlns:xi="http://www.w3.org/2001/XInclude" num_pb="1" name="CHILD">
+  <blif_model>.subckt CHILD</blif_model>
+  <input name="I" num_pins="1"/>
+  <output name="O" num_pins="1"/>
+</pb_type>
diff --git a/tests/internal_conn/child/child.sim.v b/tests/internal_conn/child/child.sim.v
new file mode 100644
index 0000000..e6aee44
--- /dev/null
+++ b/tests/internal_conn/child/child.sim.v
@@ -0,0 +1,7 @@
+(* blackbox *)
+module CHILD(
+  input  wire I,
+  output wire O
+);
+
+endmodule
diff --git a/tests/internal_conn/golden.model.xml b/tests/internal_conn/golden.model.xml
new file mode 100644
index 0000000..9ca9fbd
--- /dev/null
+++ b/tests/internal_conn/golden.model.xml
@@ -0,0 +1,3 @@
+<models xmlns:xi="http://www.w3.org/2001/XInclude">
+  <xi:include href="child/child.model.xml" xpointer="xpointer(models/child::node())"/>
+</models>
diff --git a/tests/internal_conn/golden.pb_type.xml b/tests/internal_conn/golden.pb_type.xml
new file mode 100644
index 0000000..646785c
--- /dev/null
+++ b/tests/internal_conn/golden.pb_type.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0"?>
+<pb_type xmlns:xi="http://www.w3.org/2001/XInclude" name="PARENT" num_pb="1">
+  <input name="I0" num_pins="1"/>
+  <input name="I1" num_pins="1"/>
+  <output name="O0" num_pins="1"/>
+  <output name="O1" num_pins="1"/>
+  <pb_type blif_model=".subckt CHILD" name="child" num_pb="1">
+    <input name="I" num_pins="1"/>
+    <output name="O" num_pins="1"/>
+  </pb_type>
+  <interconnect>
+    <direct input="child.O" name="PARENT-O0" output="PARENT.O0"/>
+    <direct input="PARENT.I1" name="PARENT-O1" output="PARENT.O1"/>
+    <direct input="PARENT.I0" name="child-I" output="child.I"/>
+  </interconnect>
+</pb_type>
diff --git a/tests/internal_conn/parent.sim.v b/tests/internal_conn/parent.sim.v
new file mode 100644
index 0000000..f837c7b
--- /dev/null
+++ b/tests/internal_conn/parent.sim.v
@@ -0,0 +1,18 @@
+`include "./child/child.sim.v"
+
+module PARENT (
+  input  wire I0,
+  input  wire I1,
+  output wire O0,
+  output wire O1
+);
+
+  CHILD child (
+  .I(I0),
+  .O(O0)
+  );
+
+  // An direct connection from an input to the output pins.
+  assign O1 = I1;
+
+endmodule
diff --git a/tests/io/README.rst b/tests/io/README.rst
new file mode 100644
index 0000000..20f15d6
--- /dev/null
+++ b/tests/io/README.rst
@@ -0,0 +1,7 @@
+Tests for modelling I/O primitives
+++++++++++++++++++++++++++++++++++
+
+In VPR top-level I/O pins are represented using `.input` and `.output` BLIF models that are implemented by leaf pb_types. In order to model those using verilog you need to assing the specific `CLASS` attribute so the V2X would know that it has to use either `.input` or `.output` BLIF model for a cell.
+
+The I/O BLIF models are built-in into the VPR so there is no need for generating models for them. By specifying that a cell is of an I/O class the V2X won't generate model for it.
+
diff --git a/tests/io/input/golden.model.xml b/tests/io/input/golden.model.xml
new file mode 100644
index 0000000..07a7652
--- /dev/null
+++ b/tests/io/input/golden.model.xml
@@ -0,0 +1,3 @@
+<models xmlns:xi="http://www.w3.org/2001/XInclude">
+  <!--this file is intentionally left blank-->
+</models>
diff --git a/tests/io/input/golden.pb_type.xml b/tests/io/input/golden.pb_type.xml
new file mode 100644
index 0000000..77a41cb
--- /dev/null
+++ b/tests/io/input/golden.pb_type.xml
@@ -0,0 +1,5 @@
+<?xml version='1.0' encoding='utf-8'?>
+<pb_type xmlns:xi="http://www.w3.org/2001/XInclude" name="IPAD" num_pb="1">
+  <blif_model>.input</blif_model>
+  <output name="inpad" num_pins="1"/>
+</pb_type>
diff --git a/tests/io/input/ipad.sim.v b/tests/io/input/ipad.sim.v
new file mode 100644
index 0000000..5288d42
--- /dev/null
+++ b/tests/io/input/ipad.sim.v
@@ -0,0 +1,5 @@
+(* CLASS="input" *)
+module IPAD(inpad);
+    output wire inpad;
+
+endmodule
diff --git a/tests/io/output/golden.model.xml b/tests/io/output/golden.model.xml
new file mode 100644
index 0000000..07a7652
--- /dev/null
+++ b/tests/io/output/golden.model.xml
@@ -0,0 +1,3 @@
+<models xmlns:xi="http://www.w3.org/2001/XInclude">
+  <!--this file is intentionally left blank-->
+</models>
diff --git a/tests/io/output/golden.pb_type.xml b/tests/io/output/golden.pb_type.xml
new file mode 100644
index 0000000..437e760
--- /dev/null
+++ b/tests/io/output/golden.pb_type.xml
@@ -0,0 +1,5 @@
+<?xml version='1.0' encoding='utf-8'?>
+<pb_type xmlns:xi="http://www.w3.org/2001/XInclude" name="OPAD" num_pb="1">
+  <blif_model>.output</blif_model>
+  <input name="outpad" num_pins="1"/>
+</pb_type>
diff --git a/tests/io/output/opad.sim.v b/tests/io/output/opad.sim.v
new file mode 100644
index 0000000..9ce27e1
--- /dev/null
+++ b/tests/io/output/opad.sim.v
@@ -0,0 +1,5 @@
+(* CLASS="output" *)
+module OPAD(outpad);
+    input  wire outpad;
+
+endmodule
diff --git a/tests/modes/golden.model.xml b/tests/modes/golden.model.xml
new file mode 100644
index 0000000..36811d0
--- /dev/null
+++ b/tests/modes/golden.model.xml
@@ -0,0 +1,3 @@
+<models xmlns:xi="http://www.w3.org/2001/XInclude">
+  <xi:include href="not/not.model.xml" xpointer="xpointer(models/child::node())"/>
+</models>
diff --git a/tests/modes/golden.pb_type.xml b/tests/modes/golden.pb_type.xml
new file mode 100644
index 0000000..d97e5ad
--- /dev/null
+++ b/tests/modes/golden.pb_type.xml
@@ -0,0 +1,43 @@
+<?xml version='1.0' encoding='utf-8'?>
+<pb_type xmlns:xi="http://www.w3.org/2001/XInclude" num_pb="1" name="INV">
+  <input name="I" num_pins="1"/>
+  <output name="O" num_pins="1"/>
+  <mode name="PASSTHROUGH">
+    <interconnect>
+      <direct>
+        <port name="I" type="input"/>
+        <port name="O" type="output"/>
+      </direct>
+    </interconnect>
+  </mode>
+  <mode name="INVERT">
+    <pb_type num_pb="1" name="INVERT">
+      <input name="I" num_pins="1"/>
+      <output name="O" num_pins="1"/>
+      <pb_type num_pb="1" name="inverter">
+        <!--old_name NOT-->
+        <xi:include href="not/not.pb_type.xml" xpointer="xpointer(pb_type/child::node())"/>
+      </pb_type>
+      <interconnect>
+        <direct>
+          <port name="I" type="input"/>
+          <port name="I" type="output" from="inverter"/>
+        </direct>
+        <direct>
+          <port name="O" type="input" from="inverter"/>
+          <port name="O" type="output"/>
+        </direct>
+      </interconnect>
+    </pb_type>
+    <interconnect>
+      <direct>
+        <port name="I" type="input"/>
+        <port name="I" type="output" from="INVERT"/>
+      </direct>
+      <direct>
+        <port name="O" type="input" from="INVERT"/>
+        <port name="O" type="output"/>
+      </direct>
+    </interconnect>
+  </mode>
+</pb_type>
diff --git a/tests/modes/inv.sim.v b/tests/modes/inv.sim.v
new file mode 100644
index 0000000..45fd3df
--- /dev/null
+++ b/tests/modes/inv.sim.v
@@ -0,0 +1,19 @@
+`include "./not/not.sim.v"
+
+(* MODES="PASSTHROUGH;INVERT" *)
+module INV(I, O);
+    input  wire I;
+    output wire O;
+
+    parameter MODE="PASSTHROUGH";
+
+    // Passthrough (no inversion) mode
+    generate if (MODE == "PASSTHROUGH") begin
+        assign O = I;
+
+    // Inversion with placeable inverter
+    end else if (MODE == "INVERT") begin
+        NOT inverter(I, O);
+
+    end endgenerate
+endmodule
diff --git a/tests/modes/not/not.model.xml b/tests/modes/not/not.model.xml
new file mode 100644
index 0000000..e135dda
--- /dev/null
+++ b/tests/modes/not/not.model.xml
@@ -0,0 +1,10 @@
+<models xmlns:xi="http://www.w3.org/2001/XInclude">
+  <model name="NOT">
+    <input_ports>
+      <port name="I" combinational_sink_ports="O"/>
+    </input_ports>
+    <output_ports>
+      <port name="O"/>
+    </output_ports>
+  </model>
+</models>
diff --git a/tests/modes/not/not.pb_type.xml b/tests/modes/not/not.pb_type.xml
new file mode 100644
index 0000000..5a6798f
--- /dev/null
+++ b/tests/modes/not/not.pb_type.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8'?>
+<pb_type xmlns:xi="http://www.w3.org/2001/XInclude" num_pb="1" name="NOT">
+  <blif_model>.subckt NOT</blif_model>
+  <input name="I" num_pins="1"/>
+  <output name="O" num_pins="1"/>
+  <delay_constant in_port="I" out_port="O" max="1e-10"/>
+</pb_type>
diff --git a/tests/modes/not/not.sim.v b/tests/modes/not/not.sim.v
new file mode 100644
index 0000000..c4b4d1d
--- /dev/null
+++ b/tests/modes/not/not.sim.v
@@ -0,0 +1,10 @@
+(* whitebox *)
+module NOT (I, O);
+
+  input  wire I;
+  (* DELAY_CONST_I="1e-10" *)
+  output wire O;
+
+  assign O = ~I;
+
+endmodule
diff --git a/tests/net_attr/README.rst b/tests/net_attr/README.rst
new file mode 100644
index 0000000..d0d98ed
--- /dev/null
+++ b/tests/net_attr/README.rst
@@ -0,0 +1,4 @@
+Test for handling nets consisting of multiple wires
++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+This test verifies correct handling of nets that pass through multiple wires. In such cases Yosys assigns multiple names to that net.
\ No newline at end of file
diff --git a/tests/net_attr/child/child.model.xml b/tests/net_attr/child/child.model.xml
new file mode 100644
index 0000000..90cfeb8
--- /dev/null
+++ b/tests/net_attr/child/child.model.xml
@@ -0,0 +1,10 @@
+<models xmlns:xi="http://www.w3.org/2001/XInclude">
+  <model name="CHILD">
+    <input_ports>
+      <port name="I"/>
+    </input_ports>
+    <output_ports>
+      <port name="O"/>
+    </output_ports>
+  </model>
+</models>
diff --git a/tests/net_attr/child/child.pb_type.xml b/tests/net_attr/child/child.pb_type.xml
new file mode 100644
index 0000000..0cba455
--- /dev/null
+++ b/tests/net_attr/child/child.pb_type.xml
@@ -0,0 +1,6 @@
+<?xml version='1.0' encoding='utf-8'?>
+<pb_type xmlns:xi="http://www.w3.org/2001/XInclude" name="CHILD" num_pb="1">
+  <blif_model>.subckt CHILD</blif_model>
+  <input name="I" num_pins="1"/>
+  <output name="O" num_pins="1"/>
+</pb_type>
diff --git a/tests/net_attr/child/child.sim.v b/tests/net_attr/child/child.sim.v
new file mode 100644
index 0000000..e09db94
--- /dev/null
+++ b/tests/net_attr/child/child.sim.v
@@ -0,0 +1,6 @@
+module CHILD(
+    input  wire I,
+    output wire O
+);
+
+endmodule
diff --git a/tests/net_attr/golden.model.xml b/tests/net_attr/golden.model.xml
new file mode 100644
index 0000000..0d2461a
--- /dev/null
+++ b/tests/net_attr/golden.model.xml
@@ -0,0 +1,3 @@
+<models xmlns:xi="http://www.w3.org/2001/XInclude">
+  <xi:include href="./child/child.model.xml" xpointer="xpointer(models/child::node())"/>
+</models>
diff --git a/tests/net_attr/golden.pb_type.xml b/tests/net_attr/golden.pb_type.xml
new file mode 100644
index 0000000..354b299
--- /dev/null
+++ b/tests/net_attr/golden.pb_type.xml
@@ -0,0 +1,19 @@
+<?xml version='1.0' encoding='utf-8'?>
+<pb_type xmlns:xi="http://www.w3.org/2001/XInclude" name="PARENT" num_pb="1">
+  <input name="I" num_pins="1"/>
+  <output name="O" num_pins="1"/>
+  <pb_type name="child" num_pb="1">
+    <!--old_name CHILD-->
+    <xi:include href="./child/child.pb_type.xml" xpointer="xpointer(pb_type/child::node())"/>
+  </pb_type>
+  <interconnect>
+    <direct>
+      <port name="I" type="input"/>
+      <port from="child" name="I" type="output"/>
+    </direct>
+    <direct>
+      <port from="child" name="O" type="input"/>
+      <port name="O" type="output"/>
+    </direct>
+  </interconnect>
+</pb_type>
diff --git a/tests/net_attr/parent.sim.v b/tests/net_attr/parent.sim.v
new file mode 100644
index 0000000..599456a
--- /dev/null
+++ b/tests/net_attr/parent.sim.v
@@ -0,0 +1,15 @@
+`include "./child/child.sim.v"
+
+module PARENT(
+    input  wire I,
+    output wire O
+);
+
+    wire hop1 = I;
+
+    CHILD child (
+    .I(hop1),
+    .O(O)
+    );
+
+endmodule
diff --git a/v2x/vlog_to_model.py b/v2x/vlog_to_model.py
index 42b6ef2..a4df516 100755
--- a/v2x/vlog_to_model.py
+++ b/v2x/vlog_to_model.py
@@ -162,7 +162,7 @@
         ), "Leaf model names should be all uppercase!"
         modclass = tmod.attr("CLASS", "")
 
-        if modclass not in ("lut", "routing", "flipflop"):
+        if modclass not in ("input", "output", "lut", "routing", "flipflop"):
             model_xml = ET.SubElement(models_xml, "model", {'name': topname})
             ports = tmod.ports
 
diff --git a/v2x/vlog_to_pbtype.py b/v2x/vlog_to_pbtype.py
index 66f100e..d109dfb 100755
--- a/v2x/vlog_to_pbtype.py
+++ b/v2x/vlog_to_pbtype.py
@@ -9,8 +9,8 @@
     This will also set the BLIF model to be `.subckt <name>` unless CLASS is
     also specified.
 
-    - `(* CLASS="lut|routing|mux|flipflop|mem" *)` : specify the class of an
-    given instance.
+    - `(* CLASS="input|output|lut|routing|mux|flipflop|mem" *)` : specify
+    the class of an given instance.
 
     - `(* MODES="mode1; mode2; ..." *)` : specify that the module has more
     than one functional mode, each with a given name. The module will be
@@ -212,7 +212,7 @@
         smod = yj.module(sink_type)
         potential_attrs.append(filter_src(smod.port_attrs(sink_pin)))
 
-    net_attrs = filter_src(mod.net_attrs(mod.net_name(netid)))
+    net_attrs = filter_src(mod.net_attrs_by_netid(netid))
     copy_attrs(net_attrs, potential_attrs)
     return net_attrs
 
@@ -283,6 +283,8 @@
         A dictionary containing with a list of sink pins for each driver pin.
     """
     interconn = defaultdict(list)
+
+    # Cell connections
     for cname, ctype in mod.cells:
         pb_name = strip_name(cname)
         assert pb_name in valid_names
@@ -328,6 +330,36 @@
                 )
                 interconn[(pb_name, pin)].append(((None, sink_pin), net_attr))
 
+    # Passthrough connections. Get ports along with connections
+    inp_ports = [p for p in mod.ports if p[3] == "input"]
+    out_ports = [p for p in mod.ports if p[3] == "output"]
+
+    # Loop over outputs and assign them with connected inputs
+    for out_port in out_ports:
+        for out_bit, out_net in enumerate(out_port[2]):
+
+            # Format full output port name
+            if out_port[1] == 1:
+                out_name = out_port[0]
+            else:
+                out_name = "{}[{}]".format(out_port[0], out_bit)
+
+            # Find input
+            for inp_port in inp_ports:
+                for inp_bit, inp_net in enumerate(inp_port[2]):
+
+                    # Format full input port name
+                    if inp_port[1] == 1:
+                        inp_name = inp_port[0]
+                    else:
+                        inp_name = "{}[{}]".format(inp_port[0], inp_bit)
+
+                    # Find matching nets
+                    if out_net == inp_net:
+                        key = (None, inp_name)
+                        val = ((None, out_name), {})
+                        interconn[key].append(val)
+
     import pprint
     pprint.pprint(list(interconn.values()))
 
@@ -344,13 +376,23 @@
     return interconn
 
 
-def mode_interconnects(mod, mode_name) -> List[(CellPin)]:
-    interconn = []
+def mode_interconnects(mod, mode_name) -> Dict[CellPin, List[CellPin]]:
+    """
+    This function returns a definition of an interconnect used to connect
+    a child pb_type for the given mode with its parent pb_type that provides
+    the modes.
+
+    The returned dict is indexed by tuples containing source (driver) mode
+    names and pin names. Its values contain lists of sink modes and pin names
+    that are driven by the driver. If the mode name is None then the connection
+    refers to the parent pb_type.
+    """
+    interconn = {}
     for name, width, bits, iodir in mod.ports:
         if iodir == "input":
-            interconn.append(((None, name), (mode_name, name)))
+            interconn[(None, name)] = [((mode_name, name,), {},)]
         else:
-            interconn.append(((mode_name, name), (None, name)))
+            interconn[(mode_name, name)] = [((None, name,), {},)]
     return interconn
 
 
@@ -624,7 +666,9 @@
 
 
 def make_leaf_pb(outfile, yj, mod, mod_pname, pb_type_xml):
-    # As leaf node, need to generate timing information
+
+    # As leaf node with "blif_model" set is a site., need to generate timing
+    # information.
     def process_clocked_tmg(tmgspec, port, iodir, xmltype, xml_parent):
         """Add a suitable timing spec if necessary to the pb_type"""
         if tmgspec is not None:
@@ -715,7 +759,11 @@
     ), "Model name should be uppercase. {}".format(model_name)
     mod_cls = mod.CLASS
     if mod_cls is not None:
-        if mod_cls == "lut":
+        if mod_cls == "input":
+            pb_attrs["blif_model"] = ".input"
+        elif mod_cls == "output":
+            pb_attrs["blif_model"] = ".output"
+        elif mod_cls == "lut":
             pb_attrs["blif_model"] = ".names"
             pb_attrs["class"] = "lut"
         elif mod_cls == "routing":
@@ -780,23 +828,33 @@
                 )
             )
             mode_mod = mode_yj.module(mod.name)
-            make_pb_type(infiles, outfile, mode_yj, mode_mod,
-                         True, mode_xml, smode)
 
-            # if mode pb_type contains interconnect tag,
-            # add new connctions there
+            inter = {}
+
+            # The mode has no children. Don't generate a pb_type then. Make
+            # only the interconnect instead.
+            if len(mode_mod.cells) == 0:
+                inter.update(get_interconnects(
+                    mode_yj, mode_mod, smode, [smode]))
+
+            # The mode has children, recurse
+            else:
+                make_pb_type(infiles, outfile, mode_yj, mode_mod,
+                             True, mode_xml, smode)
+                inter.update(mode_interconnects(mod, smode))
+
+            # Add or update the interconnect.
             ic_xml = mode_xml.find("interconnect")
-            print("ic_xml is", ic_xml, file=sys.stderr)
             if ic_xml is None:
                 ic_xml = ET.SubElement(mode_xml, "interconnect")
 
-            for (driver_cell,
-                 driver_pin), (sink_cell,
-                               sink_pin) in mode_interconnects(mod, smode):
-                make_direct_conn(
-                    ic_xml, (driver_cell, driver_pin), (sink_cell, sink_pin),
-                    {}
-                )
+            for (driv_cell, driv_pin), sinks in inter.items():
+                for (sink_cell, sink_pin), attrs in sinks:
+                    make_direct_conn(
+                        ic_xml,
+                        (driv_cell, driv_pin),
+                        (sink_cell, sink_pin),
+                        attrs)
 
     if not modes or mode_processing:
         routing = children = []
diff --git a/v2x/yosys/json.py b/v2x/yosys/json.py
index 7278773..93a32da 100755
--- a/v2x/yosys/json.py
+++ b/v2x/yosys/json.py
@@ -273,10 +273,49 @@
         for n, props in self.data["netnames"].items():
             if netid in props['bits']:
                 names.append(n)
+        # FIXME: This fails when there are two wires with different names
+        # connected to the same netid (yes, that's possible). Which name
+        # should be returned in that case ?
         if len(names) != 1:
             raise KeyError("Net id {} not found".format(netid))
         return names[0]
 
+    def net_attrs_by_netid(self, netid):
+        """
+        Returns attributes of a given netid. Raises RuntimeError if the same
+        attribute is defined for two or more wires belonging to the same net.
+        The attribute 'src' is an exception, 'src' strings are concatenated
+        when defined for more than one wire.
+
+        Returns dict:
+        -------
+        netid : int
+        """
+
+        attributes = {}
+
+        for name, data in self.data["netnames"].items():
+            if netid in data['bits']:
+
+                # Join attributes
+                for attr, value in data['attributes'].items():
+
+                    # Allow multiple definitions of the same attribute only
+                    # for the 'src'. Otherwise raise an exception
+                    if attr in attributes and attr != 'src':
+                        raise RuntimeError(
+                            "Conflicting attributes '{}' on netid {}".format(
+                                attr, netid))
+
+                    # Join 'src' attribute strings
+                    if attr in attributes and attr == 'src':
+                        attributes[attr] += ";{}".format(value)
+                    # Store the value
+                    else:
+                        attributes[attr] = value
+
+        return attributes
+
     def net_drivers(self, net):
         """Returns a list of drivers of a given net, both top level inputs.
         and cell outputs. "cell" is set to the name of the module for top level