Change signature for normalise_name to support family-specific normalisation. Remaining code not updated.
diff --git a/util/common/nets/__init__.py b/util/common/nets/__init__.py
index 845adcb..d917991 100644
--- a/util/common/nets/__init__.py
+++ b/util/common/nets/__init__.py
@@ -1,3 +1,4 @@
 from .general import *
-from .ecp5 import *
+import ecp5
+import machxo2
 from .util import *
diff --git a/util/common/nets/__main__.py b/util/common/nets/__main__.py
index 844e24f..e7f6206 100644
--- a/util/common/nets/__main__.py
+++ b/util/common/nets/__main__.py
@@ -1,25 +1,25 @@
 from nets import *
 
-assert is_global("R2C7_HPBX0100")
-assert is_global("R24C12_VPTX0700")
-assert is_global("R22C40_HPRX0300")
-assert is_global("R34C67_ULPCLK7")
-assert not is_global("R22C67_H06E0003")
-assert is_global("R24C67_VPFS0800")
-assert is_global("R1C67_JPCLKT01")
+assert ecp5.is_global("R2C7_HPBX0100")
+assert ecp5.is_global("R24C12_VPTX0700")
+assert ecp5.is_global("R22C40_HPRX0300")
+assert ecp5.is_global("R34C67_ULPCLK7")
+assert not ecp5.is_global("R22C67_H06E0003")
+assert ecp5.is_global("R24C67_VPFS0800")
+assert ecp5.is_global("R1C67_JPCLKT01")
 
 assert is_cib("R47C61_Q4")
 assert is_cib("R47C58_H06W0003")
 assert is_cib("R47C61_CLK0")
 
-assert normalise_name((95, 126), "R48C26", "R48C26_B1", 0) == "B1"
-assert normalise_name((95, 126), "R48C26", "R48C26_HPBX0600", 0) == "G_HPBX0600"
-assert normalise_name((95, 126), "R48C26", "R48C25_H02E0001", 0) == "W1_H02E0001"
-assert normalise_name((95, 126), "R48C1", "R48C1_H02E0002", 0) == "W1_H02E0001"
-assert normalise_name((95, 126), "R82C90", "R79C90_V06S0003", 0) == "N3_V06S0003"
-assert normalise_name((95, 126), "R5C95", "R3C95_V06S0004", 0) == "N3_V06S0003"
-assert normalise_name((95, 126), "R1C95", "R1C95_V06S0006", 0) == "N3_V06S0003"
-assert normalise_name((95, 126), "R3C95", "R2C95_V06S0005", 0) == "N3_V06S0003"
-assert normalise_name((95, 126), "R82C95", "R85C95_V06N0303", 0) == "S3_V06N0303"
-assert normalise_name((95, 126), "R90C95", "R92C95_V06N0304", 0) == "S3_V06N0303"
-assert normalise_name((95, 126), "R93C95", "R94C95_V06N0305", 0) == "S3_V06N0303"
+assert normalise_name((95, 126), "R48C26", "R48C26_B1", "ECP5") == "B1"
+assert normalise_name((95, 126), "R48C26", "R48C26_HPBX0600", "ECP5") == "G_HPBX0600"
+assert normalise_name((95, 126), "R48C26", "R48C25_H02E0001", "ECP5") == "W1_H02E0001"
+assert normalise_name((95, 126), "R48C1", "R48C1_H02E0002", "ECP5") == "W1_H02E0001"
+assert normalise_name((95, 126), "R82C90", "R79C90_V06S0003", "ECP5") == "N3_V06S0003"
+assert normalise_name((95, 126), "R5C95", "R3C95_V06S0004", "ECP5") == "N3_V06S0003"
+assert normalise_name((95, 126), "R1C95", "R1C95_V06S0006", "ECP5") == "N3_V06S0003"
+assert normalise_name((95, 126), "R3C95", "R2C95_V06S0005", "ECP5") == "N3_V06S0003"
+assert normalise_name((95, 126), "R82C95", "R85C95_V06N0303", "ECP5") == "S3_V06N0303"
+assert normalise_name((95, 126), "R90C95", "R92C95_V06N0304", "ECP5") == "S3_V06N0303"
+assert normalise_name((95, 126), "R93C95", "R94C95_V06N0305", "ECP5") == "S3_V06N0303"
diff --git a/util/common/nets/ecp5.py b/util/common/nets/ecp5.py
index e96ac92..0158ca8 100644
--- a/util/common/nets/ecp5.py
+++ b/util/common/nets/ecp5.py
@@ -80,3 +80,25 @@
                 center_clk_re.match(wire) or
                 cib_eclk_re.match(wire) or
                 is_global_brgeclk(wire))
+
+def handle_family_net(tile, wire, prefix_pos, tile_pos, netname):
+    if tile.startswith("TAP") and netname.startswith("H"):
+        if prefix_pos[1] < tile_pos[1]:
+            return "L_" + netname
+        elif prefix_pos[1] > tile_pos[1]:
+            return "R_" + netname
+        else:
+            assert False, "bad TAP_DRIVE netname"
+    elif is_global(wire):
+        return "G_" + netname
+    elif dqs_ssig_re.match(wire):
+        return "DQSG_" + netname
+    elif bnk_eclk_re.match(wire):
+        if "ECLK" in tile:
+            return "G_" + netname
+        else:
+            return "BNK_" + bnk_eclk_re.match(wire).group(1)
+    elif netname in ("INRD", "LVDS"):
+        return "BNK_" + netname
+    else:
+        return None
diff --git a/util/common/nets/general.py b/util/common/nets/general.py
index 1639919..fa649e2 100644
--- a/util/common/nets/general.py
+++ b/util/common/nets/general.py
@@ -1,7 +1,7 @@
 import re
 import tiles
 
-from .ecp5 import *
+import ecp5
 
 
 # General inter-tile routing
@@ -128,7 +128,7 @@
     return netname, wire_pos
 
 
-def normalise_name(chip_size, tile, wire, bias):
+def normalise_name(chip_size, tile, wire, family):
     """
     Wire name normalisation for tile wires and fuzzing
     All net names that we have access too are canonical, global names
@@ -156,33 +156,34 @@
     chip_size: chip size as tuple (max_row, max_col)
     tile: name of the relevant tile
     wire: full Lattice name of the wire
-    bias: Use 1-based column indexing
+    family: Device family to normalise. Affects column indexing (e.g. MachXO2 uses 1-based
+          column indexing) and naming of global wires, TAP_DRIVEs, DQS, bank wires,
+          etc.
 
     Returns the normalised netname
     """
+
+    if family == "ECP5":
+        def handle_family_net(tile, wire, prefix_pos, tile_pos, netname):
+            return ecp5.handle_family_net(tile, wire, prefix_pos, tile_pos, netname)
+        bias = 0
+    elif family == "MachXO2":
+        def handle_family_net(tile, wire, prefix_pos, tile_pos, netname):
+            return machxo2.handle_family_net(tile, wire, prefix_pos, tile_pos, netname)
+        bias = 1
+    else:
+        raise ValueError("Unknown device family.")
+
     upos = wire.index("_")
     prefix = wire[:upos]
     prefix_pos = tiles.pos_from_name(prefix, chip_size, bias)
     tile_pos = tiles.pos_from_name(tile, chip_size, bias)
     netname = wire[upos+1:]
-    if tile.startswith("TAP") and netname.startswith("H"):
-        if prefix_pos[1] < tile_pos[1]:
-            return "L_" + netname
-        elif prefix_pos[1] > tile_pos[1]:
-            return "R_" + netname
-        else:
-            assert False, "bad TAP_DRIVE netname"
-    elif is_global(wire):
-        return "G_" + netname
-    elif dqs_ssig_re.match(wire):
-        return "DQSG_" + netname
-    elif bnk_eclk_re.match(wire):
-        if "ECLK" in tile:
-            return "G_" + netname
-        else:
-            return "BNK_" + bnk_eclk_re.match(wire).group(1)
-    elif netname in ("INRD", "LVDS"):
-        return "BNK_" + netname
+
+    family_net = handle_family_net(tile, wire, prefix_pos, tile_pos, netname)
+    if family_net:
+        return family_net
+
     netname, prefix_pos = handle_edge_name(chip_size, tile_pos, prefix_pos, netname)
     if tile_pos == prefix_pos:
         return netname
diff --git a/util/common/nets/machxo2.py b/util/common/nets/machxo2.py
new file mode 100644
index 0000000..fbfd3e2
--- /dev/null
+++ b/util/common/nets/machxo2.py
@@ -0,0 +1,14 @@
+import re
+import tiles
+
+# REGEXs for global/clock signals
+
+# Oscillator output
+osc_clk_re = re.compile(r'R\d+C\d+_J?OSC')
+
+def is_global(wire):
+    """Return true if a wire is part of the global clock network"""
+    return bool(osc_clk_re.match(wire))
+
+def handle_family_net(tile, wire, prefix_pos, tile_pos, netname):
+    raise NotImplementedError("MachXO2 device family not implemented.")