| #!/usr/bin/env python3 |
| """ Script for addressing CARRY4 output congestion in elaborated netlists. |
| |
| Usage: |
| |
| python3 fix_carry.py < input-netlist-json > output-netlist-json |
| |
| Description: |
| |
| In the 7-series SLICEL (and SLICEM) sites, there can be output congestion |
| if both the CO and O of the CARRY4 are used. This congestion can be |
| avoided by using a transparent/open latch or register on the output of the |
| CARRY4. |
| |
| VPR does not currently support either of those options, so for now, if |
| both CO and O are used, the CO output is converted into a LUT equation to |
| recompute the CO output from O, DI and S. See carry_map.v and |
| clean_carry_map.v for details. |
| |
| If VPR could emit the transparent/open latch on output congestion, this |
| would no longer be required. The major problem with transparent latch |
| support is that it requires constants to be routed to the G/GE/CLR/PRE |
| ports, which VPR cannot express as a result of packing. |
| |
| This script identifies CARRY4 chains in the netlist, identifies if there |
| is output congestion on the O and CO ports, and marks the congestion by |
| changing CARRY_CO_DIRECT (e.g. directly use the CO port) to CARRY_CO_LUT |
| (compute the CO value using a LUT equation). |
| |
| |
| Diagram showing one row of the 7-series CLE, focusing on O/CO congestion. |
| This diagram shows that if both the O and CO outputs are needed, once must |
| pass through the flip flop (xFF in the diagram). |
| |
| CLE Row |
| |
| +--------------------------------------------------------------------------+ |
| | | |
| | | |
| | +---+ | |
| | | + | |
| | | + | |
| | +-------->+ O + | |
| | CO CHAIN | | + | |
| | | | +---------------------> xMUX |
| | ^ | +---->+ CO + | |
| | | | | | + | |
| | | | | | + | |
| | +---------+----------+ | | | + | |
| | | | | | +---+ | |
| | | CARRY ROW | | | | |
| | +--->+ S O +--------+ | xOUTMUX | |
| | | | | | | |
| | | | + | | |
| | +--->+ DI CO +-------+o+--+ | |
| | | CI CHAIN | + | | |
| | | | | | | |
| | +---------+----------+ | | xFFMUX | |
| | ^ | | | |
| | | | | +---+ | |
| | + | | | + | |
| | | + | + +-----------+ | |
| | +--+o+--->+ O + | | | |
| | + | + | xFF | | |
| | | | +->--D---- Q +------> xQ |
| | | | + | | | |
| | +---->+ CO + | | | |
| | | + +-----------+ | |
| | | + | |
| | +---+ | |
| | | |
| | | |
| +--------------------------------------------------------------------------+ |
| |
| |
| This script operates on a slightly different cell structure than a plain CARRY4. |
| carry_map.v converts the CARRY4 into: |
| |
| +------------------+ +-----------------+ |
| | | | | |
| | CO3 +->+ CARRY_CO_DIRECT | |
| | | | | |
| | DI3 | +-----------------+ |
| | | |
| | S3 O3 | |
| | | |
| | DI2 | +-----------------+ |
| | | | | |
| | S2 CO2 +->+ CARRY_CO_DIRECT | |
| | | | | |
| | DI1 | +-----------------+ |
| | | |
| | S1 O2 | |
| | CARRY4 | |
| | DI0 (chained) | +-----------------+ |
| | | | | |
| | S0 CO1 +->+ CARRY_CO_DIRECT | |
| | | | | |
| | CYINIT | +-----------------+ |
| | | |
| +-----------------+ | O1 | |
| | | | | |
| +->+ CARRY_COUT_PLUG +->+ CI | +-----------------+ |
| | | | | | | | |
| | +-----------------+ | CO0 +->+ CARRY_CO_DIRECT | |
| | | | | | |
| | | | +-----------------+ |
| +-------------------+ | | |
| | | O0 | |
| +------------------+ +-----------------+ | | | |
| | | | | | +------------------+ |
| | CO3 +->+ CARRY_CO_DIRECT +-+ |
| | | | | |
| | DI3 | +-----------------+ |
| | | |
| | S3 O3 | |
| | | |
| | DI2 | +-----------------+ |
| | | | | |
| | S2 CO2 +->+ CARRY_CO_DIRECT | |
| | | | | |
| | DI1 | +-----------------+ |
| | | |
| | S1 O2 | |
| | CARRY4 | |
| | DI0 (root) | +-----------------+ |
| | | | | |
| | S0 CO1 +->+ CARRY_CO_DIRECT | |
| | | | | |
| | CYINIT | +-----------------+ |
| | | |
| | O1 | |
| | | |
| | CI | +-----------------+ |
| | | | | |
| | CO0 +->+ CARRY_CO_DIRECT | |
| | | | | |
| | | +-----------------+ |
| | | |
| | O0 | |
| | | |
| +------------------+ |
| |
| Each CARRY4 spans the 4 rows of the SLICEL/SLICEM. |
| Row 0 is the S0/DI0/O0/CO0 ports, row 1 is S1/DI1/O1/CO1 ports, etc. |
| |
| So there are five cases the script has to handle: |
| |
| - No congestion is present between O and CO -> |
| |
| Do nothing. |
| |
| - Congestion is present on rows 0-2 and row above is in use -> |
| |
| Change CARRY_CO_DIRECT to CARRY_CO_LUT. |
| |
| Routing and LUT delays are incurred in this case. |
| |
| - Congestion is present on rows 0-2 and row above is not in use -> |
| |
| Remap CO to O from the row above, and set S on the next row to 0 to |
| ensure O outputs CI from the row below. |
| |
| No additional delays for this change. |
| |
| - Congestion is present on row 3 and CO3 is not connected to another CARRY -> |
| |
| Change CARRY_CO_DIRECT to CARRY_CO_TOP_POP. This adds 1 dummy layer to |
| the carry chain to output the CO. |
| |
| No additional delays for this change. |
| |
| - Congestion is present on row 3 and CO3 is connected directly to another |
| CARRY4 -> |
| |
| Change CARRY_CO_DIRECT to CARRY_CO_LUT *and* change the chained |
| CARRY_COUT_PLUG to be directly connected to the previous CO3. |
| |
| Routing and LUT delays are incurred in this case. |
| |
| Diagram for this case: |
| |
| +-------------------+ +-----------------+ |
| | | | | |
| | CO3 +->+ CARRY_CO_DIRECT | |
| | | | | |
| | DI3 | +-----------------+ |
| | | |
| | S3 O3 | |
| | | |
| | DI2 | +-----------------+ |
| | | | | |
| | S2 CO2 +->+ CARRY_CO_DIRECT | |
| | | | | |
| | DI1 | +-----------------+ |
| | | |
| | S1 O2 | |
| | CARRY4 | |
| | DI0 (chained) | +-----------------+ |
| | | | | |
| | S0 CO1 +->+ CARRY_CO_DIRECT | |
| | | | | |
| | CYINIT | +-----------------+ |
| | | |
| +-----------------+ | O1 | |
| | | | | |
| +->+ CARRY_COUT_PLUG +--->+ CI | +-----------------+ |
| | | | | | | | |
| | +-----------------+ | CO0 +->+ CARRY_CO_DIRECT | |
| | | | | | |
| +-------------------+ | +-----------------+ | | +-----------------+ |
| | | | | | | | |
| | CO3 +--+->+ CARRY_CO_LUT +-+ | O0 | |
| | | | | | | | |
| | DI3 | +-----------------+ | +-------------------+ |
| | | | |
| | S3 O3 | +------> |
| | | |
| | DI2 | +-----------------+ |
| | | | | |
| | S2 CO2 +---->+ CARRY_CO_DIRECT | |
| | | | | |
| | DI1 | +-----------------+ |
| | | |
| | S1 O2 | |
| | CARRY4 | |
| | DI0 (root) | +-----------------+ |
| | | | | |
| | S0 CO1 +---->+ CARRY_CO_DIRECT | |
| | | | | |
| | CYINIT | +-----------------+ |
| | | |
| | O1 | |
| | | |
| | CI | +-----------------+ |
| | | | | |
| | CO0 +---->+ CARRY_CO_DIRECT | |
| | | | | |
| | | +-----------------+ |
| | | |
| | O0 | |
| | | |
| +-------------------+ |
| |
| After this script is run, clean_carry_map.v is used to convert CARRY_CO_DIRECT |
| into a direct connection, and CARRY_CO_LUT is mapped to a LUT to compute the |
| carry output. |
| |
| """ |
| import json |
| import sys |
| |
| |
| def find_top_module(design): |
| """ |
| Looks for the top-level module in the design. Returns its name. Throws |
| an exception if none was found. |
| """ |
| |
| for name, module in design["modules"].items(): |
| attrs = module["attributes"] |
| if "top" in attrs and int(attrs["top"]) == 1: |
| return name |
| |
| raise RuntimeError("No top-level module found in the design!") |
| |
| |
| def find_carry4_chains(design, top_module, bit_to_cells): |
| """ Identify CARRY4 carry chains starting from the root CARRY4. |
| |
| All non-root CARRY4 cells should end up as part of a chain, otherwise |
| an assertion is raised. |
| |
| Arguments: |
| design (dict) - "design" field from Yosys JSON format |
| top_module (str) - Name of top module. |
| bit_to_cells (dict) - Map of net bit identifier and cell information. |
| Computes in "create_bit_to_cell_map". |
| |
| Returns: |
| list of list of strings - List of CARRY4 chains. Each chain is a list |
| of cellnames. The cells are listed in chain order, starting from |
| the root. |
| |
| """ |
| cells = design["modules"][top_module]["cells"] |
| |
| used_carry4s = set() |
| root_carry4s = [] |
| nonroot_carry4s = {} |
| for cellname in cells: |
| cell = cells[cellname] |
| if cell["type"] != "CARRY4_VPR": |
| continue |
| connections = cell["connections"] |
| |
| if "CIN" in connections: |
| cin_connections = connections["CIN"] |
| assert len(cin_connections) == 1 |
| |
| # Goto driver of CIN, should be a CARRY_COUT_PLUG. |
| plug_cellname, port, bit_idx = bit_to_cells[cin_connections[0]][0] |
| plug_cell = cells[plug_cellname] |
| assert plug_cell["type"] == "CARRY_COUT_PLUG", plug_cellname |
| assert port == "COUT" |
| |
| plug_connections = plug_cell["connections"] |
| |
| cin_connections = plug_connections["CIN"] |
| assert len(cin_connections) == 1 |
| |
| # Goto driver of CIN, should be a CARRY_CO_DIRECT. |
| direct_cellname, port, bit_idx = bit_to_cells[cin_connections[0] |
| ][0] |
| direct_cell = cells[direct_cellname] |
| assert direct_cell["type"] == "CARRY_CO_DIRECT", direct_cellname |
| assert port == "OUT" |
| |
| direct_connections = direct_cell["connections"] |
| |
| co_connections = direct_connections["CO"] |
| assert len(co_connections) == 1 |
| |
| nonroot_carry4s[co_connections[0]] = cellname |
| else: |
| used_carry4s.add(cellname) |
| root_carry4s.append(cellname) |
| |
| # Walk from each root CARRY4 to each child CARRY4 module. |
| chains = [] |
| for cellname in root_carry4s: |
| chain = [cellname] |
| |
| while True: |
| # Follow CO3 to the next CARRY4, if any. |
| cell = cells[cellname] |
| connections = cell["connections"] |
| |
| co3_connections = connections.get("CO3", None) |
| if co3_connections is None: |
| # No next CARRY4, stop here. |
| break |
| |
| found_next_link = False |
| for connection in co3_connections: |
| next_cellname = nonroot_carry4s.get(connection, None) |
| if next_cellname is not None: |
| cellname = next_cellname |
| used_carry4s.add(cellname) |
| chain.append(cellname) |
| found_next_link = True |
| break |
| |
| if not found_next_link: |
| break |
| |
| chains.append(chain) |
| |
| # Make sure all non-root CARRY4's got used. |
| for bit, cellname in nonroot_carry4s.items(): |
| assert cellname in used_carry4s, (bit, cellname) |
| |
| return chains |
| |
| |
| def create_bit_to_cell_map(design, top_module): |
| """ Create map from net bit identifier to cell information. |
| |
| Arguments: |
| design (dict) - "design" field from Yosys JSON format |
| top_module (str) - Name of top module. |
| |
| Returns: |
| bit_to_cells (dict) - Map of net bit identifier and cell information. |
| |
| The map keys are the net bit identifier used to mark which net a cell port |
| is connected too. The map values are a list of cell ports that are in the |
| net. The first element of the list is the driver port, and the remaining |
| elements are sink ports. |
| |
| The list elements are 3-tuples with: |
| cellname (str) - The name of the cell this port belongs too |
| port (str) - The name of the port this element is connected too. |
| bit_idx (int) - For multi bit ports, a 0-based index into the port. |
| |
| """ |
| bit_to_cells = {} |
| |
| cells = design["modules"][top_module]["cells"] |
| |
| for cellname in cells: |
| cell = cells[cellname] |
| port_directions = cell["port_directions"] |
| for port, connections in cell["connections"].items(): |
| is_output = port_directions[port] == "output" |
| for bit_idx, bit in enumerate(connections): |
| |
| list_of_cells = bit_to_cells.get(bit, None) |
| if list_of_cells is None: |
| list_of_cells = [None] |
| bit_to_cells[bit] = list_of_cells |
| |
| if is_output: |
| # First element of list of cells is net driver. |
| assert list_of_cells[0] is None, ( |
| bit, list_of_cells[0], cellname |
| ) |
| list_of_cells[0] = (cellname, port, bit_idx) |
| else: |
| list_of_cells.append((cellname, port, bit_idx)) |
| |
| return bit_to_cells |
| |
| |
| def is_bit_used(bit_to_cells, bit): |
| """ Is the net bit specified used by any sinks? """ |
| list_of_cells = bit_to_cells[bit] |
| |
| return len(list_of_cells) > 1 |
| |
| |
| def is_bit_used_other_than_carry4_cin(design, top_module, bit, bit_to_cells): |
| """ Is the net bit specified used by any sinks other than a carry chain? """ |
| cells = design["modules"][top_module]["cells"] |
| list_of_cells = bit_to_cells[bit] |
| assert len(list_of_cells) == 2, bit |
| |
| direct_cellname, port, _ = list_of_cells[1] |
| direct_cell = cells[direct_cellname] |
| assert direct_cell['type'] == "CARRY_CO_DIRECT" |
| assert port == "CO" |
| |
| # Follow to output |
| connections = direct_cell["connections"]["OUT"] |
| assert len(connections) == 1 |
| |
| for cellname, port, bit_idx in bit_to_cells[connections[0]][1:]: |
| cell = cells[cellname] |
| if cell["type"] == "CARRY_COUT_PLUG" and port == "CIN": |
| continue |
| else: |
| return True, direct_cellname |
| |
| return False, direct_cellname |
| |
| |
| def create_bit_to_net_map(design, top_module): |
| """ Create map from net bit identifier to net information. |
| |
| Arguments: |
| design (dict) - "design" field from Yosys JSON format |
| top_module (str) - Name of top module. |
| |
| Returns: |
| bit_to_nets (dict) - Map of net bit identifier to net information. |
| """ |
| bit_to_nets = {} |
| |
| nets = design["modules"][top_module]["netnames"] |
| |
| for net in nets: |
| for bit_idx, bit in enumerate(nets[net]["bits"]): |
| bit_to_nets[bit] = (net, bit_idx) |
| |
| return bit_to_nets |
| |
| |
| def fixup_cin(design, top_module, bit_to_cells, co_bit, direct_cellname): |
| """ Move connection from CARRY_CO_LUT.OUT -> CARRY_COUT_PLUG.CIN to |
| directly to preceeding CARRY4. |
| """ |
| cells = design["modules"][top_module]["cells"] |
| |
| direct_cell = cells[direct_cellname] |
| assert direct_cell["type"] == "CARRY_CO_LUT" |
| |
| # Follow to output |
| connections = direct_cell["connections"]["OUT"] |
| assert len(connections) == 1 |
| |
| for cellname, port, bit_idx in bit_to_cells[connections[0]][1:]: |
| cell = cells[cellname] |
| if cell["type"] == "CARRY_COUT_PLUG" and port == "CIN": |
| assert bit_idx == 0 |
| |
| cells[cellname]["connections"]["CIN"][0] = co_bit |
| |
| |
| def fixup_congested_rows(design, top_module, bit_to_cells, bit_to_nets, chain): |
| """ Walk the specified carry chain, and identify if any outputs are congested. |
| |
| Arguments: |
| design (dict) - "design" field from Yosys JSON format |
| top_module (str) - Name of top module. |
| bit_to_cells (dict) - Map of net bit identifier and cell information. |
| Computes in "create_bit_to_cell_map". |
| bit_to_nets (dict) - Map of net bit identifier to net information. |
| Computes in "create_bit_to_net_map". |
| chain (list of str) - List of cells in the carry chain. |
| |
| """ |
| cells = design["modules"][top_module]["cells"] |
| |
| O_ports = ["O0", "O1", "O2", "O3"] |
| CO_ports = ["CO0", "CO1", "CO2", "CO3"] |
| |
| def check_if_rest_of_carry4_is_unused(cellname, cell_idx): |
| assert cell_idx < len(O_ports) |
| |
| cell = cells[cellname] |
| connections = cell["connections"] |
| |
| for o, co in zip(O_ports[cell_idx:], CO_ports[cell_idx:]): |
| o_conns = connections[o] |
| assert len(o_conns) == 1 |
| o_bit = o_conns[0] |
| if is_bit_used(bit_to_cells, o_bit): |
| return False |
| |
| co_conns = connections[co] |
| assert len(co_conns) == 1 |
| co_bit = co_conns[0] |
| if is_bit_used(bit_to_cells, co_bit): |
| return False |
| |
| return True |
| |
| # Carry chain is congested if both O and CO is used at the same level. |
| # CO to next element in the chain is fine. |
| for chain_idx, cellname in enumerate(chain): |
| cell = cells[cellname] |
| connections = cell["connections"] |
| for cell_idx, (o, co) in enumerate(zip(O_ports, CO_ports)): |
| o_conns = connections[o] |
| assert len(o_conns) == 1 |
| o_bit = o_conns[0] |
| |
| co_conns = connections[co] |
| assert len(co_conns) == 1 |
| co_bit = co_conns[0] |
| |
| is_o_used = is_bit_used(bit_to_cells, o_bit) |
| is_co_used, direct_cellname = is_bit_used_other_than_carry4_cin( |
| design, top_module, co_bit, bit_to_cells |
| ) |
| |
| if is_o_used and is_co_used: |
| # Output at this row is congested. |
| direct_cell = cells[direct_cellname] |
| |
| if co == 'CO3' and chain_idx == len(chain) - 1: |
| # This congestion is on the top of the carry chain, |
| # emit a dummy layer to the chain. |
| direct_cell["type"] = "CARRY_CO_TOP_POP" |
| assert int(direct_cell["parameters"]["TOP_OF_CHAIN"]) == 1 |
| # If this is the last CARRY4 in the chain, see if the |
| # remaining part of the chain is idle. |
| elif chain_idx == len(chain) - 1 and \ |
| check_if_rest_of_carry4_is_unused(cellname, cell_idx + 1): |
| |
| # Because the rest of the CARRY4 is idle, it is safe to |
| # use the next row up to output the top of the carry. |
| connections["S{}".format(cell_idx + 1)] = ["1'b0"] |
| |
| next_o_conns = connections[O_ports[cell_idx + 1]] |
| assert len(next_o_conns) == 1 |
| direct_cell["connections"]["CO"][0] = next_o_conns[0] |
| |
| netname, bit_idx = bit_to_nets[next_o_conns[0]] |
| assert bit_idx == 0 |
| |
| # Update annotation that this net is now in use. |
| net = design["module"][top_module]["netnames"][netname] |
| assert net["attributes"].get("unused_bits", None) == "0 " |
| del net["attributes"]["unused_bits"] |
| else: |
| # The previous two stragies (use another layer of carry) |
| # only work for the top of the chain. This appears to be |
| # in the middle of the chain, so just spill it out to a |
| # LUT, and fixup the direct carry chain (if any). |
| direct_cell["type"] = "CARRY_CO_LUT" |
| |
| fixup_cin( |
| design, top_module, bit_to_cells, co_bit, |
| direct_cellname |
| ) |
| |
| |
| def main(): |
| design = json.load(sys.stdin) |
| top_module = find_top_module(design) |
| |
| bit_to_cells = create_bit_to_cell_map(design, top_module) |
| bit_to_nets = create_bit_to_net_map(design, top_module) |
| |
| for chain in find_carry4_chains(design, top_module, bit_to_cells): |
| fixup_congested_rows( |
| design, top_module, bit_to_cells, bit_to_nets, chain |
| ) |
| |
| json.dump(design, sys.stdout, indent=2) |
| |
| |
| if __name__ == "__main__": |
| main() |