| import pytrellis |
| import database |
| import itertools |
| import json |
| import argparse |
| |
| # This mirrors center_map in libtrellis. Somehow expose center_map. |
| center_map = { |
| # 256HC |
| (7, 9): (3, 4), |
| # 640HC |
| (8, 17): (3, 7), |
| # 1200HC |
| (12, 21): (6, 12), |
| # 2000HC |
| (15, 25): (8, 13), |
| # 4000HC |
| (22, 31): (11, 15), |
| # 7000HC |
| (26, 40): (13, 18), |
| } |
| |
| row_spans = { |
| # 1200HC |
| (12, 21): (5, 5), |
| } |
| |
| start_stride = { |
| # 256HC |
| (7, 9): (0, 4), |
| # 640HC |
| (8, 17): (1, 5), |
| # 1200HC |
| (12, 21): (0, 4), |
| # 2000HC |
| (15, 25): (3, 7), |
| # 4000HC |
| (22, 31): (1, 5), |
| # 7000HC |
| (26, 40): (2, 6), |
| } |
| |
| # There are 8 global nets. For a given column, globals are routed in pairs. |
| # Convert from a pair to an index of global pairs. |
| global_group = { |
| (0, 4): 0, |
| (1, 5): 1, |
| (2, 6): 2, |
| (3, 7): 3 |
| } |
| |
| inv_global_group = [(0, 4), (1, 5), (2, 6), (3, 7)] |
| |
| # Generate which columns route which globals. |
| def column_routing(num_cols, col_1=(0, 4)): |
| i = 1 |
| stride = (0, 4, 1, 5, 2, 6, 3, 7) |
| |
| # Find which globals in column 0 will be routed, given which globals |
| # are routed in column 1. |
| # |
| # Column "0" in prjtrellis ("1" in Lattice numbering) always has six of |
| # the globals routed. The explanation for the final column applies here, |
| # except we are missing the four globals that would span from the right |
| # side of the U/D routing connection (and thus approach column 0 from the |
| # left). |
| col_0 = [] |
| |
| for g in stride: |
| if g not in col_1: |
| col_0.append(g) |
| |
| yield tuple(col_0) |
| |
| # Rotate the stride so the correct pair of globals are at the beginning |
| # of the list. |
| idx = global_group[col_1]*2 |
| rotated_stride = stride[idx:] + stride[:idx] |
| |
| # Take two at a time: https://docs.python.org/3/library/functions.html#zip |
| col_iter = itertools.cycle(zip(*[iter(rotated_stride)]*2)) |
| |
| for c in col_iter: |
| yield c |
| |
| i = i + 1 |
| if i >= num_cols: |
| break |
| |
| # The final column will have 4 globals routed- the two expected globals |
| # for the column as well as the next two globals in the stride. This is |
| # because BRANCH wires that connect globals to CIBs span two columns to the |
| # right and one column to the left from where they connect to U/D routing. |
| # Since we are at the right bound of the chip, the globals we would expect |
| # to span from the left side of the U/D routing (and thus approach the |
| # final column from the right) don't physically exist! So we take care |
| # of them here. |
| yield next(col_iter) + next(col_iter) |
| |
| # Generate how far branches span (exclusive span, in tiles) from u/d column |
| # routing. |
| def branch_spans(num_cols, col_1=(0, 4)): |
| i = 1 |
| col_0 = [None, (0, 0), (0, 1), (0, 2)] |
| default = { |
| (0, 4): [(1, 2), None, None, None], |
| (1, 5): [None, (1, 2), None, None], |
| (2, 6): [None, None, (1, 2), None], |
| (3, 7): [None, None, None, (1, 2)] |
| } |
| |
| idx = global_group[col_1] |
| # This works, somehow... |
| rotated_col0 = col_0[-idx:] + col_0[:-idx] |
| |
| yield rotated_col0 |
| |
| col_iter = itertools.islice(column_routing(num_cols, col_1), 1, None) |
| for c in col_iter: |
| yield default[c] |
| |
| i = i + 1 |
| if i > (num_cols - 2): |
| break |
| |
| # At the second-to-last row of the chip, the branch, which spans two |
| # columns to the right, will be truncated by the chip's edge. |
| second_last = [None, None, None, None] |
| curr_idx = global_group[next(col_iter)] |
| second_last[curr_idx] = (1, 1) |
| yield second_last |
| |
| # At the last row of the chip, BRANCHES connecting to U/D routing (which |
| # which normally span two column to the right) will be truncated by the |
| # chip's edge. |
| last = [None, None, None, None] |
| curr_idx = (curr_idx + 1) % 4 |
| last[curr_idx] = (1, 0) |
| |
| # The remaining two globals should come from BRANCHES from the right. |
| # But since we run into the chip's edge, we route them to the current |
| # column (and only the current column!) here. |
| curr_idx = (curr_idx + 1) % 4 |
| last[curr_idx] = (0, 0) |
| yield last |
| |
| # 256: 9, (0, 4): L: 3, 7 has DCCs R: 0, 4 has DCCs |
| # 640: 17, (1, 5): L: 0, 4 has DCCs R: 1, 5 has DCCs |
| # 1200: 21, (0, 4): L: 3, 7 has DCCs R: 0, 4 has DCCs |
| # 2000: 25, (3, 7), L: 2, 6 has DCCs R: 3, 7 has DCCs |
| # 4000: 31, (1, 5), L: 0, 4 has DCCs R: 3, 7 has DCCs |
| # 7000: 40, (2, 6), L: 1, 5 has DCCs R: 1, 5 has DCCs (both top and bottom) |
| def main(args): |
| pytrellis.load_database(database.get_db_root()) |
| ci = pytrellis.get_chip_info(pytrellis.find_device_by_name(args.device)) |
| chip_size = (ci.max_row, ci.max_col) |
| |
| globals_json = dict() |
| globals_json["lr-conns"] = { |
| "lr1" : { |
| "row" : center_map[chip_size][0], |
| "row-span" : row_spans[chip_size] |
| } |
| } |
| |
| globals_json["ud-conns"] = {} |
| |
| for n, c in enumerate(column_routing(chip_size[1], start_stride[chip_size])): |
| globals_json["ud-conns"][str(n)] = c |
| if n == chip_size[1] - 1: |
| last_stride = c |
| |
| globals_json["branch-spans"] = {} |
| |
| for col, grps in enumerate(branch_spans(chip_size[1], start_stride[chip_size])): |
| span_dict = {} |
| for gn, span in enumerate(grps): |
| if span: |
| for glb_no in inv_global_group[gn]: |
| span_dict[str(glb_no)] = span |
| |
| globals_json["branch-spans"][str(col)] = span_dict |
| |
| |
| # For the first and last columns, globals at the stride's current |
| # position have DCCs when viewed in EPIC. These DCCs don't appear to |
| # physically exist on-chip. See minitests/machxo2/dcc/dcc2.v. However, |
| # in the bitstream (for the first and last columns) global conns going |
| # into "DCCs" have different bits controlling them as opposed to globals |
| # without DCC connections. |
| zero_col_dccs = set(inv_global_group[(global_group[start_stride[chip_size]] - 1) % 4]) |
| zero_col_conns = set(globals_json["ud-conns"]["0"]) |
| missing_dccs_l = tuple(zero_col_conns.difference(zero_col_dccs)) |
| |
| last_col_dccs = set(inv_global_group[(global_group[last_stride] + 1) % 4]) |
| last_col_conns = set(globals_json["ud-conns"][str(chip_size[1])]) |
| missing_dccs_r = tuple(last_col_conns.difference(last_col_dccs)) |
| |
| globals_json["missing-dccs"] = { |
| "0" : missing_dccs_l, |
| str(chip_size[1]) : missing_dccs_r |
| } |
| |
| with args.outfile as jsonf: |
| jsonf.write(json.dumps(globals_json, indent=4, separators=(',', ': '))) |
| |
| |
| if __name__ == "__main__": |
| parser = argparse.ArgumentParser("Store MachXO2 global information into globals.json.") |
| parser.add_argument('device', type=str, |
| help="Device for which to generate globals.json.") |
| parser.add_argument('outfile', type=argparse.FileType('w'), |
| help="Output json file (globals.json in the database).") |
| args = parser.parse_args() |
| |
| main(args) |