Merge pull request #141 from cr1901/nets-refactor

Refactor `nets` module to support more families.
diff --git a/fuzzers/ECP5/001-plc2_routing/fuzzer.py b/fuzzers/ECP5/001-plc2_routing/fuzzer.py
index 14ebcaf..fde5c4a 100644
--- a/fuzzers/ECP5/001-plc2_routing/fuzzer.py
+++ b/fuzzers/ECP5/001-plc2_routing/fuzzer.py
@@ -16,7 +16,7 @@
     def nn_filter(net, netnames):
         """ Match nets that are: in the tile according to Tcl, global nets, or span-1 nets that are accidentally
         left out by Tcl"""
-        return net in netnames or nets.is_global(net) or span1_re.match(net)
+        return net in netnames or nets.ecp5.is_global(net) or span1_re.match(net)
 
     interconnect.fuzz_interconnect(config=cfg, location=(19, 33),
                                    netname_predicate=nn_filter,
diff --git a/fuzzers/ECP5/030-cib_routing/fuzzer.py b/fuzzers/ECP5/030-cib_routing/fuzzer.py
index 3679b98..1feb23d 100644
--- a/fuzzers/ECP5/030-cib_routing/fuzzer.py
+++ b/fuzzers/ECP5/030-cib_routing/fuzzer.py
@@ -16,7 +16,7 @@
     def nn_filter(net, netnames):
         """ Match nets that are: in the tile according to Tcl, global nets, or span-1 nets that are accidentally
         left out by Tcl"""
-        return ((net in netnames or span1_re.match(net)) and nets.is_cib(net)) or nets.is_global(net)
+        return ((net in netnames or span1_re.match(net)) and nets.is_cib(net)) or nets.ecp5.is_global(net)
 
     def fc_filter(arc, netnames):
         """ Ignore connections between two general routing nets. These are edge buffers which vary based on location
diff --git a/fuzzers/ECP5/031-cib_lr_routing/fuzzer.py b/fuzzers/ECP5/031-cib_lr_routing/fuzzer.py
index cb84abc..7802ff1 100644
--- a/fuzzers/ECP5/031-cib_lr_routing/fuzzer.py
+++ b/fuzzers/ECP5/031-cib_lr_routing/fuzzer.py
@@ -16,7 +16,7 @@
     def nn_filter(net, netnames):
         """ Match nets that are: in the tile according to Tcl, global nets, or span-1 nets that are accidentally
         left out by Tcl"""
-        return ((net in netnames or span1_re.match(net)) and nets.is_cib(net)) or nets.is_global(net)
+        return ((net in netnames or span1_re.match(net)) and nets.is_cib(net)) or nets.ecp5.is_global(net)
 
     def fc_filter(arc, netnames):
         """ Ignore connections between two general routing nets. These are edge buffers which vary based on location
diff --git a/tools/connectivity.py b/tools/connectivity.py
index 04b53b7..ee8923a 100755
--- a/tools/connectivity.py
+++ b/tools/connectivity.py
@@ -39,7 +39,7 @@
             if net.startswith("G_"):
                 tnet = net
             else:
-                tnet = nets.normalise_name(chip_size, tname, net, bias)
+                tnet = nets.normalise_name(chip_size, tname, net, c.info.family)
             tdb = pytrellis.get_tile_bitdata(pytrellis.TileLocator(c.info.family, c.info.name, tinf.type))
             try:
                 mux = tdb.get_mux_data_for_sink(tnet)
@@ -66,7 +66,7 @@
             if net.startswith("G_"):
                 tnet = net
             else:
-                tnet = nets.normalise_name(chip_size, tname, net, bias)
+                tnet = nets.normalise_name(chip_size, tname, net, c.info.family)
             tdb = pytrellis.get_tile_bitdata(pytrellis.TileLocator(c.info.family, c.info.name, tinf.type))
             for sink in tdb.get_sinks():
                 mux = tdb.get_mux_data_for_sink(sink)
diff --git a/tools/demobuilder/route.py b/tools/demobuilder/route.py
index 36975f5..c6012e3 100755
--- a/tools/demobuilder/route.py
+++ b/tools/demobuilder/route.py
@@ -50,7 +50,7 @@
                     if wire.startswith("G_"):
                         twire = wire
                     else:
-                        twire = nets.normalise_name(self.chip_size, tname, wire, self.bias)
+                        twire = nets.normalise_name(self.chip_size, tname, wire, self.chip.info.family)
 
                     tdb = pytrellis.get_tile_bitdata(
                         pytrellis.TileLocator(self.chip.info.family, self.chip.info.name, tinf.type))
@@ -74,8 +74,8 @@
         else:
             self.net_to_wire[net] = {dest_wire}
         if configurable and not exists:
-            src_wirename = nets.normalise_name(self.chip_size, tile, uphill_wire, self.bias)
-            sink_wirename = nets.normalise_name(self.chip_size, tile, dest_wire, self.bias)
+            src_wirename = nets.normalise_name(self.chip_size, tile, uphill_wire, self.chip.info.family)
+            sink_wirename = nets.normalise_name(self.chip_size, tile, dest_wire, self.chip.info.family)
             config[tile].add_arc(sink_wirename, src_wirename)
 
     # Bind a net to a wire (used for port connections)
diff --git a/util/common/nets/__init__.py b/util/common/nets/__init__.py
new file mode 100644
index 0000000..d917991
--- /dev/null
+++ b/util/common/nets/__init__.py
@@ -0,0 +1,4 @@
+from .general import *
+import ecp5
+import machxo2
+from .util import *
diff --git a/util/common/nets/__main__.py b/util/common/nets/__main__.py
new file mode 100644
index 0000000..e7f6206
--- /dev/null
+++ b/util/common/nets/__main__.py
@@ -0,0 +1,25 @@
+from nets import *
+
+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", "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
new file mode 100644
index 0000000..0158ca8
--- /dev/null
+++ b/util/common/nets/ecp5.py
@@ -0,0 +1,104 @@
+import re
+import tiles
+
+# REGEXs for global/clock signals
+
+# Globals including spine inputs, TAP_DRIVE inputs and TAP_DRIVE outputs
+global_spine_tap_re = re.compile(r'R\d+C\d+_[HV]P[TLBR]X(\d){2}00')
+# CMUX outputs
+global_cmux_out_re = re.compile(r'R\d+C\d+_[UL][LR]PCLK\d+')
+# CMUX inputs
+global_cmux_in_re = re.compile(r'R\d+C\d+_[HV]PF[NESW](\d){2}00')
+# Clock pins
+clock_pin_re = re.compile(r'R\d+C\d+_J?PCLK[TBLR]\d+')
+# PLL global outputs
+pll_out_re = re.compile(r'R\d+C\d+_J?[UL][LR][QC]PLL\dCLKO[PS]\d?')
+
+# CIB clock inputs
+cib_clk_re = re.compile(r'R\d+C\d+_J?[ULTB][LR][QCM]PCLKCIB\d+')
+# Oscillator output
+osc_clk_re = re.compile(r'R\d+C\d+_J?OSC')
+# Clock dividers
+cdivx_clk_re = re.compile(r'R\d+C\d+_J?[UL]CDIVX\d+')
+# SED clock output
+sed_clk_re = re.compile(r'R\d+C\d+_J?SEDCLKOUT')
+
+# SERDES reference clocks
+pcs_clk_re = re.compile(r'R\d+C\d+_J?PCS[AB][TR]XCLK\d')
+
+
+# DDRDEL delay signals
+ddr_delay_re = re.compile(r'R\d+C\d+_[UL][LR]DDRDEL')
+
+# DCC signals
+dcc_clk_re = re.compile(r'R\d+C\d+_J?(CLK[IO]|CE)_[BLTR]?DCC(\d+|[BT][LR])')
+# DCC inputs
+dcc_clki_re = re.compile(r'R\d+C\d+_[BLTR]?DCC(\d+|[BT][LR])CLKI')
+# DCS signals
+dcs_sig_re = re.compile(r'R\d+C\d+_J?(CLK\d|SEL\d|DCSOUT|MODESEL)_DCS\d')
+# DCS clocks
+dcs_clk_re = re.compile(r'R\d+C\d+_DCS\d(CLK\d)?')
+# Misc. center clocks
+center_clk_re = re.compile(r'R\d+C\d+_J?(LE|RE)CLK\d')
+
+# Shared DQS signals
+dqs_ssig_re = re.compile(r'R\d+C\d+_(DQS[RW]\d*|(RD|WR)PNTR\d)$')
+
+# Bank edge clocks
+bnk_eclk_re = re.compile('R\d+C\d+_BANK\d+(ECLK\d+)')
+# CIB ECLK inputs
+cib_eclk_re = re.compile(r'R\d+C\d+_J?[ULTB][LR][QCM]ECLKCIB\d+')
+
+brg_eclk_re = re.compile(r'R\d+C(\d+)_JBRGECLK\d+')
+
+
+def is_global_brgeclk(wire):
+    m = brg_eclk_re.match(wire)
+    if not m:
+        return False
+    if m:
+        x = int(m.group(1))
+        return x > 5 and x < 67
+
+def is_global(wire):
+    """Return true if a wire is part of the global clock network"""
+    return bool(global_spine_tap_re.match(wire) or
+                global_cmux_out_re.match(wire) or
+                global_cmux_in_re.match(wire) or
+                clock_pin_re.match(wire) or
+                pll_out_re.match(wire) or
+                cib_clk_re.match(wire) or
+                osc_clk_re.match(wire) or
+                cdivx_clk_re.match(wire) or
+                sed_clk_re.match(wire) or
+                ddr_delay_re.match(wire) or
+                dcc_clk_re.match(wire) or
+                dcc_clki_re.match(wire) or
+                dcs_sig_re.match(wire) or
+                dcs_clk_re.match(wire) or
+                pcs_clk_re.match(wire) or
+                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.py b/util/common/nets/general.py
similarity index 66%
rename from util/common/nets.py
rename to util/common/nets/general.py
index b05cdff..fa649e2 100644
--- a/util/common/nets.py
+++ b/util/common/nets/general.py
@@ -1,85 +1,7 @@
 import re
 import tiles
 
-# REGEXs for global/clock signals
-
-# Globals including spine inputs, TAP_DRIVE inputs and TAP_DRIVE outputs
-global_spine_tap_re = re.compile(r'R\d+C\d+_[HV]P[TLBR]X(\d){2}00')
-# CMUX outputs
-global_cmux_out_re = re.compile(r'R\d+C\d+_[UL][LR]PCLK\d+')
-# CMUX inputs
-global_cmux_in_re = re.compile(r'R\d+C\d+_[HV]PF[NESW](\d){2}00')
-# Clock pins
-clock_pin_re = re.compile(r'R\d+C\d+_J?PCLK[TBLR]\d+')
-# PLL global outputs
-pll_out_re = re.compile(r'R\d+C\d+_J?[UL][LR][QC]PLL\dCLKO[PS]\d?')
-
-# CIB clock inputs
-cib_clk_re = re.compile(r'R\d+C\d+_J?[ULTB][LR][QCM]PCLKCIB\d+')
-# Oscillator output
-osc_clk_re = re.compile(r'R\d+C\d+_J?OSC')
-# Clock dividers
-cdivx_clk_re = re.compile(r'R\d+C\d+_J?[UL]CDIVX\d+')
-# SED clock output
-sed_clk_re = re.compile(r'R\d+C\d+_J?SEDCLKOUT')
-
-# SERDES reference clocks
-pcs_clk_re = re.compile(r'R\d+C\d+_J?PCS[AB][TR]XCLK\d')
-
-
-# DDRDEL delay signals
-ddr_delay_re = re.compile(r'R\d+C\d+_[UL][LR]DDRDEL')
-
-# DCC signals
-dcc_clk_re = re.compile(r'R\d+C\d+_J?(CLK[IO]|CE)_[BLTR]?DCC(\d+|[BT][LR])')
-# DCC inputs
-dcc_clki_re = re.compile(r'R\d+C\d+_[BLTR]?DCC(\d+|[BT][LR])CLKI')
-# DCS signals
-dcs_sig_re = re.compile(r'R\d+C\d+_J?(CLK\d|SEL\d|DCSOUT|MODESEL)_DCS\d')
-# DCS clocks
-dcs_clk_re = re.compile(r'R\d+C\d+_DCS\d(CLK\d)?')
-# Misc. center clocks
-center_clk_re = re.compile(r'R\d+C\d+_J?(LE|RE)CLK\d')
-
-# Shared DQS signals
-dqs_ssig_re = re.compile(r'R\d+C\d+_(DQS[RW]\d*|(RD|WR)PNTR\d)$')
-
-# Bank edge clocks
-bnk_eclk_re = re.compile('R\d+C\d+_BANK\d+(ECLK\d+)')
-# CIB ECLK inputs
-cib_eclk_re = re.compile(r'R\d+C\d+_J?[ULTB][LR][QCM]ECLKCIB\d+')
-
-brg_eclk_re = re.compile(r'R\d+C(\d+)_JBRGECLK\d+')
-
-
-def is_global_brgeclk(wire):
-    m = brg_eclk_re.match(wire)
-    if not m:
-        return False
-    if m:
-        x = int(m.group(1))
-        return x > 5 and x < 67
-
-def is_global(wire):
-    """Return true if a wire is part of the global clock network"""
-    return bool(global_spine_tap_re.match(wire) or
-                global_cmux_out_re.match(wire) or
-                global_cmux_in_re.match(wire) or
-                clock_pin_re.match(wire) or
-                pll_out_re.match(wire) or
-                cib_clk_re.match(wire) or
-                osc_clk_re.match(wire) or
-                cdivx_clk_re.match(wire) or
-                sed_clk_re.match(wire) or
-                ddr_delay_re.match(wire) or
-                dcc_clk_re.match(wire) or
-                dcc_clki_re.match(wire) or
-                dcs_sig_re.match(wire) or
-                dcs_clk_re.match(wire) or
-                pcs_clk_re.match(wire) or
-                center_clk_re.match(wire) or
-                cib_eclk_re.match(wire) or
-                is_global_brgeclk(wire))
+import ecp5
 
 
 # General inter-tile routing
@@ -206,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
@@ -234,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
@@ -279,7 +202,6 @@
 
 rel_netname_re = re.compile(r'^([NS]\d+)?([EW]\d+)?_.*')
 
-
 def canonicalise_name(chip_size, tile, wire, bias):
     """
     Convert a normalised name in a given tile back to a canonical global name
@@ -310,43 +232,3 @@
     if wire_pos[0] < 0 or wire_pos[0] > chip_size[0] or wire_pos[1] < 0 or wire_pos[1] > chip_size[1]:
         return None #TODO: edge normalisation
     return "R{}C{}_{}".format(wire_pos[0], wire_pos[1], wire)
-
-
-# Useful functions for constructing nets.
-def char_range(c1, c2):
-    """Generates the characters from `c1` to `c2`, exclusive."""
-    for c in range(ord(c1), ord(c2)):
-        yield chr(c)
-
-def net_product(net_list, range_iter):
-    return [n.format(i) for i in range_iter for n in net_list]
-
-
-def main():
-    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 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"
-
-
-if __name__ == "__main__":
-    main()
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.")
diff --git a/util/common/nets/util.py b/util/common/nets/util.py
new file mode 100644
index 0000000..e13413f
--- /dev/null
+++ b/util/common/nets/util.py
@@ -0,0 +1,8 @@
+# Useful functions for constructing nets.
+def char_range(c1, c2):
+    """Generates the characters from `c1` to `c2`, exclusive."""
+    for c in range(ord(c1), ord(c2)):
+        yield chr(c)
+
+def net_product(net_list, range_iter):
+    return [n.format(i) for i in range_iter for n in net_list]
diff --git a/util/fuzz/interconnect.py b/util/fuzz/interconnect.py
index 5774f78..0d23ee4 100644
--- a/util/fuzz/interconnect.py
+++ b/util/fuzz/interconnect.py
@@ -28,8 +28,7 @@
                       func_cib=False,
                       fc_prefix="",
                       nonlocal_prefix="",
-                      netdir_override=dict(),
-                      bias=0):
+                      netdir_override=dict()):
     """
     The fully-automatic interconnect fuzzer function. This performs the fuzzing and updates the database with the
     results. It is expected that PyTrellis has already been initialised with the database prior to this function being
@@ -54,8 +53,6 @@
     only consulted if ispTcl returns "---" for the direction of a given net. This dictionary overrides
     func_cib=False for the nets in question.
     :param nonlocal_prefix: add a prefix to non-global and non-neighbour wires for device-specific fuzzers
-    :param bias: Apply offset correction for n-based column numbering, n > 0. Used used by Lattice
-    on certain families.
     """
     netdata = isptcl.get_wires_at_position(config.ncd_prf, location)
     netnames = [x[0] for x in netdata]
@@ -75,7 +72,7 @@
     if func_cib and not netname_filter_union:
         netnames = list(filter(lambda x: netname_predicate(x, netnames), netnames))
     fuzz_interconnect_with_netnames(config, netnames, netname_predicate, arc_predicate, fc_predicate, func_cib,
-                                    netname_filter_union, False, fc_prefix, nonlocal_prefix, netdir_override, bias)
+                                    netname_filter_union, False, fc_prefix, nonlocal_prefix, netdir_override)
 
 
 def fuzz_interconnect_with_netnames(
@@ -89,8 +86,7 @@
         full_mux_style=False,
         fc_prefix="",
         nonlocal_prefix="",
-        netdir_override=dict(),
-        bias=0):
+        netdir_override=dict()):
     """
     Fuzz interconnect given a list of netnames to analyse. Arcs associated these netnames will be found using the Tcl
     API and bits identified as described above.
@@ -110,8 +106,6 @@
     specified as "-->" in ispTcl), or drive other nets (`"driver"`, specified as "<--" in ispTcl). The dictionary is
     only consulted if ispTcl returns "---" for the direction of a given net. This dictionary overrides
     bidir=False for the nets in question.
-    :param bias: Apply offset correction for n-based column numbering, n > 0. Used used by Lattice
-    on certain families.
     """
     net_arcs = isptcl.get_arcs_on_wires(config.ncd_prf, netnames, not bidir, netdir_override)
     baseline_bitf = config.build_design(config.ncl, {}, "base_")
@@ -121,7 +115,7 @@
     max_col = baseline_chip.get_max_col()
 
     def normalise_arc_in_tile(tile, arc):
-        return tuple(nets.normalise_name((max_row, max_col), tile, x, bias) for x in arc)
+        return tuple(nets.normalise_name((max_row, max_col), tile, x, config.family) for x in arc)
 
     def add_nonlocal_prefix(wire):
         if wire.startswith("G_"):