Simple framework for physical chip design (place & route) based on KLayout.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

149 lines
4.9 KiB

<?xml version="1.0" encoding="utf-8"?>
<klayout-macro>
<description/>
<version/>
<category>pymacros</category>
<prolog/>
<epilog/>
<doc/>
<autorun>false</autorun>
<autorun-early>false</autorun-early>
<shortcut/>
<show-in-menu>false</show-in-menu>
<group-name/>
<menu-path/>
<interpreter>python</interpreter>
<dsl-interpreter-name/>
<text>
def create_dummy_pins(netlist: db.Netlist, circuit: db.Circuit, core_area: db.DSimplePolygon) -&gt; Tuple[
db.Circuit, Dict[int, Tuple[int, int]]]:
"""
Create dummy pin instances and locations that are distributed evenly around the `core_area`.
:param netlist:
:param circuit:
:param core_area:
:return: Tuple[new top level circuit, positions of dummy pins Dict[sub circuit id, (x,y)]]
"""
# Create dummy circuit for pads.
dummy_pad = db.Circuit()
dummy_pad.name = 'dummy_pad'
dummy_pad.create_pin('IO')
netlist.add(dummy_pad)
new_top = db.Circuit()
new_top.name = 'DUMMY_TOP'
netlist.add(new_top)
inst = new_top.create_subcircuit(circuit, 'i_{}'.format(circuit.name))
port_nodes = []
# Create virtual modules
for p in circuit.each_pin():
assert isinstance(p, db.Pin)
pad_inst: db.SubCircuit = new_top.create_subcircuit(dummy_pad, 'i_dummy_pad_{}'.format(p.name()))
# net = circuit.net_for_pin(p)
net = new_top.create_net(p.name())
pad_inst.connect_pin(dummy_pad.pin_by_name('IO'), net)
inst.connect_pin(p, net)
port_nodes.append(pad_inst.id())
top_pin = new_top.create_pin(p.name())
new_top.connect_pin(top_pin, net)
pad_ring_box = core_area.bbox()
# Create dummy node positions for ports.
node_positions = dict()
for i, n in enumerate(port_nodes):
# Distribute them on the edge of a unit square.
p = (pad_ring_box.width() + pad_ring_box.height()) * 2 * i / len(port_nodes)
w = pad_ring_box.width()
h = pad_ring_box.height()
if p &lt; w:
x, y = p, pad_ring_box.p1.y
elif p &lt; w + h:
x, y = pad_ring_box.p2.x, p - h
elif p &lt; w + h + w:
x, y = pad_ring_box.p2.x - (p - w - h), pad_ring_box.p2.y
else:
x, y = pad_ring_box.p1.x, pad_ring_box.p2.y - (p - w - h - w)
node_positions[n] = (x, y)
return new_top, node_positions
# Fetch the correct circuit. TODO: Fetch it by name!
top_circuit_without_pads: db.Circuit = next(netlist.each_circuit_top_down())
logger.info("Top circuit: {}".format(top_circuit_without_pads.name))
# Define area to place the cells within.
design.core_area = db.SimplePolygon(db.Box(db.Point(0, 0), db.Point(20000, 20000))).to_dtype(1)
# Create a new top_circuit with dummy pad instances at dummy positions.
design.top_circuit, fixed_positions = create_dummy_pins(netlist, top_circuit_without_pads, design.core_area)
net_util.flatten(netlist)
# Print number of subcircuit instances in the flat netlist. This includes standar-cells, pads, macros, ...
num_cells = len(list(top_circuit_without_pads.each_subcircuit()))
logger.info('Number of cell instances: {}'.format(num_cells))
# Create top level cell.
top_cell: db.Cell = layout.create_cell('TOP')
design.top_cell = top_cell
# === Insert unplaced cells ===
logger.info('Insert unplaced cells.')
# Store cell instances by the IDs of their sub-circuits.
# This creates the mapping between layout cells and sub-circuits in the netlist.
core_center = design.core_area.bbox().center()
trans = db.DTrans(core_center.x, core_center.y)
for sub_circuit in design.top_circuit.each_subcircuit():
cell_name = sub_circuit.circuit_ref().name
try:
cell = layout.cell_by_name(cell_name)
cell_inst = db.CellInstArray(cell, trans)
cell_inst = top_cell.insert(cell_inst)
# Store subcircuit ID with the cell instance.
cell_inst.set_property('circuit_id', sub_circuit.id())
cell_inst.set_property('fixed', False)
design.cell_instances_by_subcircuit_id[sub_circuit.id()] = cell_inst
except RuntimeError as err:
logger.warning('Cell not found: {}'.format(cell_name))
# (debug) Sketch core area.
core_area_layer = db.LayerInfo(1000, 0, 'core_area')
layout.insert_special_layer(core_area_layer)
core_area_layer = layout.layer(db.LayerInfo(1000, 0, 'core_area'))
top_cell.shapes(core_area_layer).insert(design.core_area.to_itype())
# Compute total used area.
area_used = 0
for inst in design.top_circuit.each_subcircuit():
shape = cell_shapes.get(inst.circuit_ref().name, (0, 0))
area = shape[0] * shape[1]
area_used += area
area_available = design.core_area.area()
area_ratio = area_used / area_available
if area_ratio &gt;= 1:
logger.warning(f"Core area is more than 100% full: {area_ratio*100}%")
logger.info(f"Core area usage: {area_ratio*100}%")
# Show the new cell in KLayout.
cell_view.cell = top_cell
# Make sure all layers are displayed.
cell_view.view().add_missing_layers()
cell_view.view().zoom_fit()</text>
</klayout-macro>