def get_fanin(net): drivers = [] npos = tiles.pos_from_name(net, chip_size, bias) for tile in c.get_all_tiles(): tinf = tile.info tname = tinf.name pos = tiles.pos_from_name(tname, chip_size, bias) if abs(pos[0] - npos[0]) >= 10 or abs(pos[1] - npos[1]) >= 10: continue if net.startswith("G_"): tnet = net else: tnet = nets.normalise_name(chip_size, tname, net, bias) tdb = pytrellis.get_tile_bitdata( pytrellis.TileLocator(c.info.family, c.info.name, tinf.type)) try: mux = tdb.get_mux_data_for_sink(tnet) for src in mux.get_sources(): drivers.append( (nets.canonicalise_name(chip_size, tname, src, bias), True, tname)) except IndexError: pass for fc in tdb.get_fixed_conns(): if fc.sink == tnet: drivers.append( (nets.canonicalise_name(chip_size, tname, fc.source, bias), False, tname)) return drivers
def get_fanout(net): drivers = [] npos = tiles.pos_from_name(net) for tile in c.get_all_tiles(): tinf = tile.info tname = tinf.name pos = tiles.pos_from_name(tname) if abs(pos[0] - npos[0]) >= 12 or abs(pos[1] - npos[1]) >= 12: continue if net.startswith("G_"): tnet = net else: tnet = nets.normalise_name(chip_size, tname, net) 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) if tnet in mux.arcs: drivers.append( (nets.canonicalise_name(chip_size, tname, sink), True, tname)) for fc in tdb.get_fixed_conns(): if fc.source == tnet: drivers.append( (nets.canonicalise_name(chip_size, tname, fc.sink), False, tname)) return drivers
def get_arcs_downhill(self, wire): if wire in self.dh_arc_cache: return self.dh_arc_cache[wire] else: drivers = [] chip_size = (self.chip.get_max_row(), self.chip.get_max_col()) try: npos = tiles.pos_from_name(wire, chip_size, self.bias) except AssertionError: return [] wname = wire.split("_", 1)[1] hspan = 0 vspan = 0 if wname.startswith("H") and wname[1:3].isdigit(): hspan = int(wname[1:3]) if wname.startswith("V") and wname[1:3].isdigit(): vspan = int(wname[1:3]) positions = {(npos[0], npos[1]), (npos[0] + vspan, npos[1]), (npos[0] - vspan, npos[1]), (npos[0], npos[1] + hspan), (npos[0], npos[1] - hspan)} for pos in positions: for tile in self.chip.get_tiles_by_position(pos[0], pos[1]): tinf = tile.info tname = tinf.name if tname.startswith("TAP"): continue pos = tiles.pos_from_name(tname, chip_size, self.bias) if abs(pos[0] - npos[0]) not in ( vspan, 0) or abs(pos[1] - npos[1]) not in (hspan, 0): continue if wire.startswith("G_"): twire = wire else: twire = nets.normalise_name(self.chip_size, tname, wire, self.bias) tdb = pytrellis.get_tile_bitdata( pytrellis.TileLocator(self.chip.info.family, self.chip.info.name, tinf.type)) downhill = tdb.get_downhill_wires(twire) for sink in downhill: nn = nets.canonicalise_name(self.chip_size, tname, sink.first, self.bias) if nn is not None: drivers.append((nn, sink.second, tname)) self.dh_arc_cache[wire] = drivers return drivers
def get_score(x_wire): pos = tiles.pos_from_name(x_wire, chip_size, 0) score = abs(pos[0] - dest_pos[0]) + abs(pos[1] - dest_pos[1]) x_wname = x_wire.split("_", 1)[1] if x_wname[1:3].isdigit() and score > 3: score -= int(x_wname[1:3]) return score
def canonicalise_name(chip_size, tile, wire, bias): """ Convert a normalised name in a given tile back to a canonical global name :param chip_size: chip size as tuple (max_row, max_col) :param tile: tilename :param wire: normalised netname :return: global canonical netname """ if wire.startswith("G_"): return wire m = rel_netname_re.match(wire) tile_pos = tiles.pos_from_name(tile, chip_size, bias) wire_pos = tile_pos if m: assert len(m.groups()) >= 1 for g in m.groups(): if g is not None: delta = int(g[1:]) if g[0] == "N": wire_pos = (wire_pos[0] - delta, wire_pos[1]) elif g[0] == "S": wire_pos = (wire_pos[0] + delta, wire_pos[1]) elif g[0] == "W": wire_pos = (wire_pos[0], wire_pos[1] - delta) elif g[0] == "E": wire_pos = (wire_pos[0], wire_pos[1] + delta) wire = wire.split("_", 1)[1] 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)
def format_rel(a, b): ra, ca = tiles.pos_from_name(a, (126, 95), 0) rb, cb = tiles.pos_from_name(b, (126, 95), 0) rel = "" if rb < ra: rel += "n{}".format(ra - rb) elif rb > ra: rel += "s{}".format(rb - ra) if cb < ca: rel += "w{}".format(ca - cb) elif cb > ca: rel += "e{}".format(cb - ca) if rel != "": rel = "_" + rel return rel
def completer(str, idx): if not tile_net_re.match(str): return None loc = tiles.pos_from_name(str) nets = get_nets_at(loc) for n in nets: if n.startswith(str): if idx > 0: idx -= 1 else: return n return None
def init_bels(self): self.bels_by_type = {"SLICE": []} all_tiles = self.chip.get_all_tiles() for tile in all_tiles: tinf = tile.info tname = tinf.name pos = tiles.pos_from_name(tname) if tinf.type == "PLC2": for loc in ("A", "B", "C", "D"): bel = "R{}C{}{}".format(pos[0], pos[1], loc) self.bels[bel] = ("SLICE", (tname, loc)) self.bels_by_type["SLICE"].append(bel)
def main(argv): args = parser.parse_args(argv[1:]) tilegrid = database.get_tilegrid(args.family, args.device) device_info = database.get_devices()["families"][args.family]["devices"][ args.device] max_row = device_info["max_row"] max_col = device_info["max_col"] bias = device_info["col_bias"] tiles = [] for i in range(max_row + 1): row = [] for j in range(max_col + 1): row.append([]) tiles.append(row) for identifier, data in sorted(tilegrid.items()): name = identifier.split(":")[0] row, col = tilelib.pos_from_name(name, (max_row, max_col), bias) colour = get_colour(data["type"]) tiles[row][col].append((name, data["type"], colour)) f = args.outfile print("""<html> <head><title>{} Tiles</title></head> <body> <h1>{} Tilegrid</h1> <table style='font-size: 8pt; border: 2px solid black; text-align: center'> """.format(args.device, args.device), file=f) for trow in tiles: print("<tr>", file=f) row_max_height = 0 for tloc in trow: row_max_height = max(row_max_height, len(tloc)) row_height = max(75, 30 * row_max_height) for tloc in trow: print("<td style='border: 2px solid black; height: {}px'>".format( row_height), file=f) for tile in tloc: print( "<div style='height: {}%; background-color: {}'><em>{}</em><br/><strong><a href='../tilehtml/{}.html' style='color: black'>{}</a></strong></div>" .format(100 / len(tloc), tile[2], tile[0], tile[1], tile[1]), file=f) print("</td>", file=f) print("</tr>", file=f) print("</table></body></html>", file=f)
def route_net_to_wire(self, net, wire, config): print(" Routing net '{}' to wire/pin '{}'...".format(net, wire)) chip_size = (self.chip.get_max_row(), self.chip.get_max_col()) dest_pos = tiles.pos_from_name(wire, chip_size, self.bias) def get_score(x_wire): pos = tiles.pos_from_name(x_wire, chip_size, self.bias) score = abs(pos[0] - dest_pos[0]) + abs(pos[1] - dest_pos[1]) x_wname = x_wire.split("_", 1)[1] if x_wname[1:3].isdigit() and score > 3: score -= int(x_wname[1:3]) return score assert net in self.net_to_wire bfs_queue = [(get_score(x), x) for x in sorted(self.net_to_wire[net])] heapq.heapify(bfs_queue) seen_wires = set() routed = False backtrace = {} # map wire -> (source, arc) while not routed and len(bfs_queue) > 0: score, curr_wire = heapq.heappop(bfs_queue) #print(curr_wire) arcs = self.get_arcs_downhill(curr_wire) for arc in arcs: dest, cfg, loc = arc if dest == wire: backtrace[dest] = (curr_wire, arc) routed = True break elif dest not in seen_wires: if dest in self.wire_to_net and self.wire_to_net[ dest] != net: continue backtrace[dest] = (curr_wire, arc) heapq.heappush(bfs_queue, (get_score(dest), dest)) seen_wires.add(dest) if routed: cursor = wire while cursor in backtrace: cursor, arc = backtrace[cursor] self.bind_arc(net, cursor, arc, config) else: assert False, "failed to route net {} to wire {}".format(net, wire)
def inst_slice(self, name, a0=None, a1=None, b0=None, b1=None, c0=None, c1=None, d0=None, d1=None, m0=None, m1=None, clk=None, ce=None, lsr=None, f0=None, f1=None, q0=None, q1=None, params=dict()): print("Instantiating slice {}".format(name)) bel = self.bel_for_cell(name, "SLICE") beltype, belloc = self.bels[bel] tile, loc = belloc chip_size = (self.chip.get_max_row(), self.chip.get_max_col()) pos = tiles.pos_from_name(tile, chip_size, self.bias) net_prefix = "R{}C{}".format(pos[0], pos[1]) slice_index = "ABCD".index(loc) lc0 = 2 * slice_index lc1 = 2 * slice_index + 1 self.connect_output("{}_F{}".format(net_prefix, lc0), f0) self.connect_output("{}_F{}".format(net_prefix, lc1), f1) self.connect_output("{}_Q{}".format(net_prefix, lc0), q0) self.connect_output("{}_Q{}".format(net_prefix, lc1), q1) self.connect_input("{}_A{}".format(net_prefix, lc0), a0) self.connect_input("{}_A{}".format(net_prefix, lc1), a1) self.connect_input("{}_B{}".format(net_prefix, lc0), b0) self.connect_input("{}_B{}".format(net_prefix, lc1), b1) self.connect_input("{}_C{}".format(net_prefix, lc0), c0) self.connect_input("{}_C{}".format(net_prefix, lc1), c1) self.connect_input("{}_D{}".format(net_prefix, lc0), d0) self.connect_input("{}_D{}".format(net_prefix, lc1), d1) self.connect_input("{}_M{}".format(net_prefix, lc0), m0) self.connect_input("{}_M{}".format(net_prefix, lc1), m1) self.connect_input("{}_MUXCLK{}".format(net_prefix, slice_index), clk) self.connect_input("{}_CE{}".format(net_prefix, slice_index), ce) self.connect_input("{}_MUXLSR{}".format(net_prefix, slice_index), lsr) for key, value in sorted(params.items()): if key.endswith(".INIT"): bv = pytrellis.BoolVector() for x in value: bv.append(x) self.config[tile].add_word("SLICE{}.{}".format(loc, key), bv) else: self.config[tile].add_enum("SLICE{}.{}".format(loc, key), value) print()
def normalise_name(chip_size, tile, wire, bias): """ Wire name normalisation for tile wires and fuzzing All net names that we have access too are canonical, global names These are thus no good for building up a database that is the same for all tiles of a given type, as the names will be different in each location. Lattice names are of the form R{r}C{c}_{NETNAME} Hence, we normalise names in the following way: - Global wires have the prefix "G_" added - Wires where (r, c) correspond to the current tile have their prefix removed - Wires to the left (in TAP_DRIVEs) are given the prefix L, and wires to the right are given the prefix R - Wires within a DQS group are given the prefix DQSG_ - Wires within a bank are given the prefix BNK_ - Other wires are given a relative position prefix using the syntax ([NS]\d+)?([EW]\d+)?_ so a wire whose nominal location is 6 tiles up would be given a prefix N6_ a wire whose nominal location is 2 tiles down and 1 tile right would be given a prefix S2E1_ TODO: this is more complicated at the edges of the device, where irregular names are used to keep the row and column of the nominal position in bounds. Extra logic will be needed to catch and regularise these cases. 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 Returns the normalised netname """ 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 netname, prefix_pos = handle_edge_name(chip_size, tile_pos, prefix_pos, netname) if tile_pos == prefix_pos: return netname else: prefix = "" if prefix_pos[0] < tile_pos[0]: prefix += "N{}".format(tile_pos[0] - prefix_pos[0]) elif prefix_pos[0] > tile_pos[0]: prefix += "S{}".format(prefix_pos[0] - tile_pos[0]) if prefix_pos[1] > tile_pos[1]: prefix += "E{}".format(prefix_pos[1] - tile_pos[1]) elif prefix_pos[1] < tile_pos[1]: prefix += "W{}".format(tile_pos[1] - prefix_pos[1]) return prefix + "_" + netname
locations have slightly different routing - swapped output tristate and data This script fixes this by patching tile names """ for f, d in [("LIFCL", "LIFCL-40"), ("LIFCL", "LFD2NX-40"), ("LFCPNX", "LFCPNX-100")]: tgp = path.join(database.get_db_root(), f, d, "tilegrid.json") with open(tgp, "r") as infile: tg = json.load(infile)["tiles"] tiles_by_xy = [[]] max_row = 0 max_col = 0 for tile in sorted(tg.keys()): r, c = tiles.pos_from_name(tile) max_row = max(r, max_row) max_col = max(c, max_col) while r >= len(tiles_by_xy): tiles_by_xy.append([]) while c >= len(tiles_by_xy[r]): tiles_by_xy[r].append([]) tiles_by_xy[r][c].append(tile) # Top tiles is_odd = False for col in tiles_by_xy[0]: for tile in col: tt = tiles.type_from_fullname(tile) if not tt.startswith("SYSIO"): continue
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 These are thus no good for building up a database that is the same for all tiles of a given type, as the names will be different in each location. Lattice names are of the form R{r}C{c}_{NETNAME} Hence, we normalise names in the following way: - Global wires have the prefix "G_" added - Wires where (r, c) correspond to the current tile have their prefix removed - Wires to the left (in TAP_DRIVEs) are given the prefix L, and wires to the right are given the prefix R - Wires within a DQS group are given the prefix DQSG_ - Wires within a bank are given the prefix BNK_ - Other wires are given a relative position prefix using the syntax ([NS]\d+)?([EW]\d+)?_ so a wire whose nominal location is 6 tiles up would be given a prefix N6_ a wire whose nominal location is 2 tiles down and 1 tile right would be given a prefix S2E1_ TODO: this is more complicated at the edges of the device, where irregular names are used to keep the row and column of the nominal position in bounds. Extra logic will be needed to catch and regularise these cases. chip_size: chip size as tuple (max_row, max_col) tile: name of the relevant tile wire: full Lattice name of the wire 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:] 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 else: prefix = "" if prefix_pos[0] < tile_pos[0]: prefix += "N{}".format(tile_pos[0] - prefix_pos[0]) elif prefix_pos[0] > tile_pos[0]: prefix += "S{}".format(prefix_pos[0] - tile_pos[0]) if prefix_pos[1] > tile_pos[1]: prefix += "E{}".format(prefix_pos[1] - tile_pos[1]) elif prefix_pos[1] < tile_pos[1]: prefix += "W{}".format(tile_pos[1] - prefix_pos[1]) return prefix + "_" + netname
def main(): for name, max_row, max_col in configs: cfg = FuzzConfig(job="GLOBAL_{}".format(name), device=name, sv="../shared/empty_40.v", tiles=[]) cfg.setup() db_path = path.join(database.get_db_root(), "LIFCL", name, "globals.json") def load_db(): if path.exists(db_path): with open(db_path, "r") as dbf: return json.load(dbf) else: return {"branches": []} def save_db(): with open(db_path, "w") as dbf: print(json.dumps(gdb, sort_keys=True, indent=4), file=dbf) gdb = load_db() # Determine branch driver locations test_row = 4 clock_wires = ["R{}C{}_JCLK0".format(test_row, c) for c in range(1, max_col)] clock_info = lapie.get_node_data(cfg.udb, clock_wires) branch_to_col = {} for n in clock_info: r, c = pos_from_name(n.name) hpbx_c = None for uh in n.uphill_pips: if "_HPBX0" in uh.from_wire: hpbx_r, hpbx_c = pos_from_name(uh.from_wire) assert hpbx_r == r break assert hpbx_c is not None if hpbx_c not in branch_to_col: branch_to_col[hpbx_c] = [] branch_to_col[hpbx_c].append(c) branches = [] branch_wires = ["R{}C{}_HPBX0000".format(test_row, bc) for bc in sorted(branch_to_col.keys())] branch_wire_info = lapie.get_node_data(cfg.udb, branch_wires) branch_driver_col = {} # Branches directly driven by a VPSX # Also, get a test column for the spine exploration later sp_test_col = None for bw in branch_wire_info: r, c = pos_from_name(bw.name) for uh in bw.uphill_pips: if "VPSX" in uh.from_wire: vpsx_r, vpsx_c = pos_from_name(uh.from_wire) branch_driver_col[c] = vpsx_c if sp_test_col is None: sp_test_col = c # Branches driven by another branch for bw in branch_wire_info: r, c = pos_from_name(bw.name) if c in branch_driver_col: continue for uh in bw.uphill_pips: if "HPBX0" in uh.from_wire: hpbx_r, hpbx_c = pos_from_name(uh.from_wire) branch_driver_col[c] = branch_driver_col[hpbx_c] for bc, scs in sorted(branch_to_col.items()): tap_drv_col = branch_driver_col[bc] + 1 side = "R" if tap_drv_col < bc else "L" branches.append(dict(branch_col=bc, tap_driver_col=tap_drv_col, tap_side=side, from_col=min(scs), to_col=max(scs))) gdb["branches"] = branches save_db() # Spines sp_branch_wires = ["R{}C{}_HPBX0000".format(r, sp_test_col) for r in range(1, max_row)] spine_to_branch_row = {} sp_info = lapie.get_node_data(cfg.udb, sp_branch_wires) for n in sp_info: r, c = pos_from_name(n.name) vpsx_r = None for uh in n.uphill_pips: if "VPSX" in uh.from_wire: vpsx_r, vpsx_c = pos_from_name(uh.from_wire) break assert vpsx_r is not None if vpsx_r not in spine_to_branch_row: spine_to_branch_row[vpsx_r] = [] spine_to_branch_row[vpsx_r].append(r) spines = [] sp_test_row = None for sr, brs in sorted(spine_to_branch_row.items()): if sp_test_row is None: sp_test_row = sr spines.append(dict(spine_row=sr, from_row=min(brs), to_row=max(brs))) gdb["spines"] = spines save_db() # HROWs hrow_to_spine_col = {} spine_wires = ["R{}C{}_VPSX0000".format(sp_test_row, c) for c in sorted(set(branch_driver_col.values()))] hr_info = lapie.get_node_data(cfg.udb, spine_wires) for n in hr_info: r, c = pos_from_name(n.name) hrow_c = None for uh in n.uphill_pips: if "HPRX0" in uh.from_wire: hrow_r, hrow_c = pos_from_name(uh.from_wire) break assert hrow_c is not None if hrow_c not in hrow_to_spine_col: hrow_to_spine_col[hrow_c] = [] hrow_to_spine_col[hrow_c].append(c) hrows = [] sp_test_row = None for hrc, scs in sorted(hrow_to_spine_col.items()): hrows.append(dict(hrow_col=hrc, spine_cols=scs)) gdb["hrows"] = hrows save_db()
def get_distance(a, b): ra, ca = tiles.pos_from_name(a, (126, 95), 0) rb, cb = tiles.pos_from_name(b, (126, 95), 0) return abs(ra - rb) + abs(ca - cb)