| #!/usr/bin/env python3 |
| |
| import argparse |
| |
| import opendbpy as odb |
| |
| |
| class DiodeInserter: |
| |
| def __init__(self, block, cfg): |
| self.block = block |
| self.cfg = cfg |
| self.diode_master = block.getDataBase().findMaster(self.cfg['diode_cell_true']) |
| self.inserted = {} |
| |
| def net_source(self, net): |
| # See if it's an input pad |
| for bt in net.getBTerms(): |
| if bt.getIoType != 'INPUT': |
| continue |
| good, x, y = bt.getFirstPinLocation() |
| if good: |
| return (x, y) |
| |
| # Or maybe output of a cell |
| x = odb.new_int(0) |
| y = odb.new_int(0) |
| |
| for it in net.getITerms(): |
| if not it.isOutputSignal(): |
| continue |
| if it.getAvgXY(x,y): |
| return ( odb.get_int(x), odb.get_int(y) ) |
| |
| # Nothing found |
| return None |
| |
| def net_has_diode(self, net): |
| for it in net.getITerms(): |
| cell_type = it.getInst().getMaster().getName() |
| cell_pin = it.getMTerm().getConstName() |
| if (cell_type == self.cfg['diode_cell_true']) and (cell_pin == self.cfg['diode_cell_pin']): |
| return True |
| else: |
| return False |
| |
| def net_span(self, net): |
| xs = [] |
| ys = [] |
| |
| for bt in net.getBTerms(): |
| good, x, y = bt.getFirstPinLocation() |
| if good: |
| xs.append(x) |
| ys.append(y) |
| |
| for it in net.getITerms(): |
| x, y = self.pin_position(it) |
| xs.append(x) |
| ys.append(y) |
| |
| if len(xs) == 0: |
| return 0 |
| |
| return (max(ys) - min(ys)) + (max(xs) - min(xs)) |
| |
| def pin_position(self, it): |
| px = odb.new_int(0) |
| py = odb.new_int(0) |
| |
| if it.getAvgXY(px,py): |
| # Got it |
| return odb.get_int(px), odb.get_int(py) |
| else: |
| # Failed, use the center coordinate of the instance as fall back |
| return it.getInst().getLocation() |
| |
| def place_diode_stdcell(self, it, px, py, src_pos=None): |
| # Get information about the instance |
| inst_name = it.getInst().getName() |
| inst_width = it.getInst().getMaster().getWidth() |
| inst_pos = it.getInst().getLocation() |
| inst_ori = it.getInst().getOrient() |
| |
| # Is the pin left-ish, center-ish or right-ish ? |
| th_left = int(inst_pos[0] + inst_width * 0.30) |
| th_right = int(inst_pos[0] + inst_width * 0.70) |
| |
| if px < th_left: |
| pos = 'l' |
| elif px > th_right: |
| pos = 'r' |
| elif src_pos is not None: |
| # Sort of middle, so put it on the side where signal is coming from |
| pos = 'l' if (src_pos[0] < inst_pos[0]) else 'r' |
| else: |
| # Coin toss ... |
| pos = 'l' |
| |
| # FIXME: Seems it's better to always be on the side of the source ? |
| if src_pos is not None: |
| pos = 'l' if (src_pos[0] < inst_pos[0]) else 'r' |
| |
| # X position |
| dw = self.diode_master.getWidth() |
| |
| if pos == 'l': |
| dx = inst_pos[0] - dw * (1 + self.inserted.get((inst_name, 'l'), 0)) |
| else: |
| dx = inst_pos[0] + inst_width + dw * self.inserted.get((inst_name, 'r'), 0) |
| |
| # Record insertion |
| self.inserted[(inst_name, pos)] = self.inserted.get((inst_name, pos), 0) + 1 |
| |
| # Done |
| return dx, inst_pos[1], inst_ori |
| |
| def place_diode_macro(self, it, px, py, src_pos=None): |
| # Scan all rows to see how close we can get to the point |
| best = None |
| |
| for row in self.block.getRows(): |
| rbb = row.getBBox() |
| |
| dx = max(min(rbb.xMax(), px), rbb.xMin()) |
| dy = rbb.yMin() |
| do = row.getOrient() |
| |
| d = abs(px - dx) + abs(py - dy) |
| |
| if (best is None) or (best[0] > d): |
| best = (d, dx, dy, do) |
| |
| return best[1:] |
| |
| def insert_diode(self, it, src_pos): |
| # Get information about the instance |
| inst_cell = it.getInst().getMaster().getName() |
| inst_name = it.getInst().getName() |
| inst_pos = it.getInst().getLocation() |
| |
| # Find where the pin is |
| px, py = self.pin_position(it) |
| |
| # Apply standard cell or macro placement ? |
| if inst_cell.startswith('sky130_fd_sc_hd__'): # FIXME |
| dx, dy, do = self.place_diode_stdcell(it, px, py, src_pos) |
| else: |
| dx, dy, do = self.place_diode_macro(it, px, py, src_pos) |
| |
| # Insert instance and wire it up |
| diode_inst_name = 'ANTENNA_' + inst_name + '_' + it.getMTerm().getConstName() |
| |
| diode_inst = odb.dbInst_create(self.block, self.diode_master, diode_inst_name) |
| |
| diode_inst.setOrient(do) |
| diode_inst.setLocation(dx, dy) |
| diode_inst.setPlacementStatus('PLACED') |
| |
| ait = diode_inst.findITerm(self.cfg['diode_cell_pin']) |
| odb.dbITerm_connect(ait, it.getNet()) |
| |
| def execute(self): |
| # Scan all nets |
| for net in self.block.getNets(): |
| # Skip special nets |
| if net.isSpecial(): |
| print(f"[w] Skipping special net {net.getName():s}") |
| continue |
| |
| # Check if we already have diode on the net |
| # if yes, then we assume that the user took care of that net manually |
| if self.net_has_diode(net): |
| print(f"[w] Skipping manually protected net {net.getName():s}") |
| continue |
| |
| # Find signal source (first one found ...) |
| src_pos = self.net_source(net) |
| |
| # Determine the span of the signal |
| span = self.net_span(net) |
| if span < 50000: |
| print(f"[w] Skipping small net {net.getName():s}") |
| continue |
| |
| # Scan all internal terminals |
| for it in net.getITerms(): |
| if it.isInputSignal(): |
| self.insert_diode(it, src_pos) |
| |
| |
| # Arguments |
| parser = argparse.ArgumentParser( |
| description='Cleanup XXX') |
| |
| parser.add_argument('--lef', '-l', |
| nargs='+', |
| type=str, |
| default=None, |
| required=True, |
| help='Input LEF file(s)') |
| |
| parser.add_argument('--input-def', '-id', required=True, |
| help='DEF view of the design that needs to have its instances placed') |
| |
| parser.add_argument('--output-def', '-o', required=True, |
| help='Output placed DEF file') |
| |
| |
| args = parser.parse_args() |
| input_lef_file_names = args.lef |
| input_def_file_name = args.input_def |
| output_def_file_name = args.output_def |
| |
| # Load |
| db_design = odb.dbDatabase.create() |
| |
| for lef in input_lef_file_names: |
| odb.read_lef(db_design, lef) |
| odb.read_def(db_design, input_def_file_name) |
| |
| chip_design = db_design.getChip() |
| block_design = chip_design.getBlock() |
| top_design_name = block_design.getName() |
| print("Design name:", top_design_name) |
| |
| |
| cfg = { |
| 'diode_cell_true': 'sky130_fd_sc_hd__diode_2', |
| 'diode_cell_fake': 'sky130_ef_sc_hd__fakediode_2', |
| 'diode_cell_pin': 'DIODE', |
| } |
| di = DiodeInserter(block_design, cfg) |
| di.execute() |
| |
| # Write result |
| odb.write_def(block_design, output_def_file_name) |
| |