OpenRAM/compiler/base/design.py

330 lines
13 KiB
Python
Raw Normal View History

# See LICENSE for licensing information.
#
2021-01-22 20:23:28 +01:00
# Copyright (c) 2016-2021 Regents of the University of California and The Board
2019-06-14 17:43:41 +02:00
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
from hierarchy_design import hierarchy_design
2020-11-14 00:55:55 +01:00
import utils
import contact
2020-11-14 00:55:55 +01:00
from tech import GDS, layer
2020-10-27 17:23:11 +01:00
from tech import preferred_directions
from tech import cell_properties as props
from globals import OPTS
import re
import debug
2019-10-03 01:26:02 +02:00
2019-12-18 00:07:01 +01:00
class design(hierarchy_design):
"""
This is the same as the hierarchy_design class except it contains
some DRC/layer constants and analytical models for other modules to reuse.
"""
2020-11-03 15:29:17 +01:00
def __init__(self, name, cell_name=None, prop=None):
# This allows us to use different GDS/spice circuits for hard cells instead of the default ones
# Except bitcell names are generated automatically by the globals.py setup_bitcells routines
# depending on the number of ports.
2020-11-18 19:47:05 +01:00
if name in props.names:
2020-11-19 01:27:28 +01:00
if type(props.names[name]) is list:
num_ports = OPTS.num_rw_ports + OPTS.num_r_ports + OPTS.num_w_ports - 1
2020-11-18 19:47:05 +01:00
cell_name = props.names[name][num_ports]
else:
2020-11-19 01:27:28 +01:00
cell_name = props.names[name]
2020-11-18 19:47:05 +01:00
elif not cell_name:
cell_name = name
super().__init__(name, cell_name)
# This means it is a custom cell.
# It could have properties and not be a hard cell too (e.g. dff_buf)
if prop and prop.hard_cell:
2020-11-16 20:04:03 +01:00
# The pins get added from the spice file, so just check
# that they matched here
debug.check(prop.port_names == self.pins,
"Custom cell pin names do not match spice file:\n{0} vs {1}".format(prop.port_names, self.pins))
self.add_pin_indices(prop.port_indices)
2020-11-16 20:04:03 +01:00
self.add_pin_names(prop.port_map)
self.add_pin_types(prop.port_types)
2021-02-10 05:51:50 +01:00
(width, height) = utils.get_libcell_size(self.cell_name,
GDS["unit"],
layer[prop.boundary_layer])
self.pin_map = utils.get_libcell_pins(self.pins,
self.cell_name,
GDS["unit"])
2020-11-14 00:55:55 +01:00
2021-01-22 00:22:54 +01:00
# Convert names back to the original names
# so that copying will use the new names
for pin_name in self.pin_map:
for index1, pin in enumerate(self.pin_map[pin_name]):
self.pin_map[pin_name][index1].name = self.get_original_pin_name(pin.name)
self.width = width
self.height = height
2020-11-14 00:55:55 +01:00
2020-10-27 17:28:21 +01:00
self.setup_multiport_constants()
2020-11-03 15:29:17 +01:00
2022-02-19 00:02:45 +01:00
try:
from tech import power_grid
self.supply_stack = power_grid
except ImportError:
# if no power_grid is specified by tech we use sensible defaults
# Route a M3/M4 grid
self.supply_stack = self.m3_stack
2020-09-08 22:31:50 +02:00
def check_pins(self):
for pin_name in self.pins:
pins = self.get_pins(pin_name)
for pin in pins:
print(pin_name, pin)
2020-10-27 17:23:11 +01:00
@classmethod
def setup_drc_constants(design):
"""
These are some DRC constants used in many places
in the compiler.
"""
# Make some local rules for convenience
from tech import drc
for rule in drc.keys():
# Single layer width rules
match = re.search(r"minwidth_(.*)", rule)
if match:
if match.group(1) == "active_contact":
setattr(design, "contact_width", drc(match.group(0)))
else:
setattr(design, match.group(1) + "_width", drc(match.group(0)))
# Single layer area rules
match = re.search(r"minarea_(.*)", rule)
if match:
setattr(design, match.group(0), drc(match.group(0)))
2020-11-03 15:29:17 +01:00
2020-10-27 17:23:11 +01:00
# Single layer spacing rules
match = re.search(r"(.*)_to_(.*)", rule)
if match and match.group(1) == match.group(2):
setattr(design, match.group(1) + "_space", drc(match.group(0)))
elif match and match.group(1) != match.group(2):
if match.group(2) == "poly_active":
setattr(design, match.group(1) + "_to_contact",
drc(match.group(0)))
else:
setattr(design, match.group(0), drc(match.group(0)))
2020-11-03 15:29:17 +01:00
2020-10-27 17:23:11 +01:00
match = re.search(r"(.*)_enclose_(.*)", rule)
if match:
setattr(design, match.group(0), drc(match.group(0)))
match = re.search(r"(.*)_extend_(.*)", rule)
if match:
setattr(design, match.group(0), drc(match.group(0)))
# Create the maximum well extend active that gets used
# by cells to extend the wells for interaction with other cells
from tech import layer
design.well_extend_active = 0
if "nwell" in layer:
design.well_extend_active = max(design.well_extend_active, design.nwell_extend_active)
if "pwell" in layer:
design.well_extend_active = max(design.well_extend_active, design.pwell_extend_active)
# The active offset is due to the well extension
if "pwell" in layer:
design.pwell_enclose_active = drc("pwell_enclose_active")
else:
design.pwell_enclose_active = 0
if "nwell" in layer:
design.nwell_enclose_active = drc("nwell_enclose_active")
else:
design.nwell_enclose_active = 0
# Use the max of either so that the poly gates will align properly
design.well_enclose_active = max(design.pwell_enclose_active,
design.nwell_enclose_active,
design.active_space)
2020-11-03 15:29:17 +01:00
2020-10-27 17:23:11 +01:00
# These are for debugging previous manual rules
if False:
print("poly_width", design.poly_width)
print("poly_space", design.poly_space)
print("m1_width", design.m1_width)
print("m1_space", design.m1_space)
print("m2_width", design.m2_width)
print("m2_space", design.m2_space)
print("m3_width", design.m3_width)
print("m3_space", design.m3_space)
print("m4_width", design.m4_width)
print("m4_space", design.m4_space)
print("active_width", design.active_width)
print("active_space", design.active_space)
print("contact_width", design.contact_width)
print("poly_to_active", design.poly_to_active)
print("poly_extend_active", design.poly_extend_active)
print("poly_to_contact", design.poly_to_contact)
print("active_contact_to_gate", design.active_contact_to_gate)
print("poly_contact_to_gate", design.poly_contact_to_gate)
print("well_enclose_active", design.well_enclose_active)
print("implant_enclose_active", design.implant_enclose_active)
print("implant_space", design.implant_space)
import sys
sys.exit(1)
@classmethod
def setup_layer_constants(design):
"""
2019-12-18 00:07:01 +01:00
These are some layer constants used
in many places in the compiler.
"""
2020-11-03 15:29:17 +01:00
from tech import layer_indices
import tech
for layer_id in layer_indices:
key = "{}_stack".format(layer_id)
# Set the stack as a local helper
try:
layer_stack = getattr(tech, key)
2020-10-27 17:23:11 +01:00
setattr(design, key, layer_stack)
except AttributeError:
pass
# Skip computing the pitch for non-routing layers
if layer_id in ["active", "nwell"]:
continue
2020-11-03 15:29:17 +01:00
# Add the pitch
2020-10-27 17:23:11 +01:00
setattr(design,
"{}_pitch".format(layer_id),
design.compute_pitch(layer_id, True))
2020-11-03 15:29:17 +01:00
# Add the non-preferrd pitch (which has vias in the "wrong" way)
2020-10-27 17:23:11 +01:00
setattr(design,
"{}_nonpref_pitch".format(layer_id),
design.compute_pitch(layer_id, False))
2020-11-03 15:29:17 +01:00
2019-12-18 18:30:00 +01:00
if False:
from tech import preferred_directions
print(preferred_directions)
from tech import layer_indices
for name in layer_indices:
if name == "active":
continue
try:
print("{0} width {1} space {2}".format(name,
2020-10-27 17:23:11 +01:00
getattr(design, "{}_width".format(name)),
getattr(design, "{}_space".format(name))))
2020-10-27 17:23:11 +01:00
print("pitch {0} nonpref {1}".format(getattr(design, "{}_pitch".format(name)),
getattr(design, "{}_nonpref_pitch".format(name))))
except AttributeError:
pass
2019-12-18 18:30:00 +01:00
import sys
sys.exit(1)
2020-11-03 15:29:17 +01:00
@staticmethod
2020-10-27 17:23:11 +01:00
def compute_pitch(layer, preferred=True):
2020-11-03 15:29:17 +01:00
"""
This is the preferred direction pitch
i.e. we take the minimum or maximum contact dimension
"""
# Find the layer stacks this is used in
from tech import layer_stacks
pitches = []
for stack in layer_stacks:
# Compute the pitch with both vias above and below (if they exist)
if stack[0] == layer:
2020-10-27 17:23:11 +01:00
pitches.append(design.compute_layer_pitch(stack, preferred))
if stack[2] == layer:
2020-10-27 17:23:11 +01:00
pitches.append(design.compute_layer_pitch(stack[::-1], True))
return max(pitches)
2020-10-27 17:23:11 +01:00
@staticmethod
def get_preferred_direction(layer):
return preferred_directions[layer]
2020-11-03 15:29:17 +01:00
2020-10-27 17:23:11 +01:00
@staticmethod
def compute_layer_pitch(layer_stack, preferred):
(layer1, via, layer2) = layer_stack
try:
if layer1 == "poly" or layer1 == "active":
contact1 = getattr(contact, layer1 + "_contact")
else:
contact1 = getattr(contact, layer1 + "_via")
except AttributeError:
contact1 = getattr(contact, layer2 + "_via")
2019-12-18 00:07:01 +01:00
if preferred:
2020-10-27 17:23:11 +01:00
if preferred_directions[layer1] == "V":
contact_width = contact1.first_layer_width
else:
contact_width = contact1.first_layer_height
2020-01-30 02:45:33 +01:00
else:
2020-10-27 17:23:11 +01:00
if preferred_directions[layer1] == "V":
contact_width = contact1.first_layer_height
else:
contact_width = contact1.first_layer_width
2020-10-27 17:23:11 +01:00
layer_space = getattr(design, layer1 + "_space")
#print(layer_stack)
#print(contact1)
pitch = contact_width + layer_space
2020-11-14 00:55:55 +01:00
return utils.round_to_grid(pitch)
2020-10-27 17:28:21 +01:00
def setup_multiport_constants(self):
2020-11-03 15:29:17 +01:00
"""
These are contants and lists that aid multiport design.
Ports are always in the order RW, W, R.
Port indices start from 0 and increment.
A first RW port will have clk0, csb0, web0, addr0, data0
A first W port (with no RW ports) will be: clk0, csb0, addr0, data0
"""
total_ports = OPTS.num_rw_ports + OPTS.num_w_ports + OPTS.num_r_ports
# These are the read/write port indices.
2020-10-27 17:28:21 +01:00
self.readwrite_ports = []
# These are the read/write and write-only port indices
2020-10-27 17:28:21 +01:00
self.write_ports = []
# These are the write-only port indices.
2020-10-27 17:28:21 +01:00
self.writeonly_ports = []
2020-08-17 23:35:39 +02:00
# These are the read/write and read-only port indices
2020-10-27 17:28:21 +01:00
self.read_ports = []
# These are the read-only port indices.
2020-10-27 17:28:21 +01:00
self.readonly_ports = []
# These are all the ports
2020-10-27 17:28:21 +01:00
self.all_ports = list(range(total_ports))
2020-08-17 23:35:39 +02:00
# The order is always fixed as RW, W, R
port_number = 0
for port in range(OPTS.num_rw_ports):
2020-10-27 17:28:21 +01:00
self.readwrite_ports.append(port_number)
self.write_ports.append(port_number)
self.read_ports.append(port_number)
port_number += 1
for port in range(OPTS.num_w_ports):
2020-10-27 17:28:21 +01:00
self.write_ports.append(port_number)
self.writeonly_ports.append(port_number)
port_number += 1
for port in range(OPTS.num_r_ports):
2020-10-27 17:28:21 +01:00
self.read_ports.append(port_number)
self.readonly_ports.append(port_number)
2019-10-03 01:26:02 +02:00
port_number += 1
2020-11-03 15:29:17 +01:00
def analytical_power(self, corner, load):
""" Get total power of a module """
total_module_power = self.return_power()
for inst in self.insts:
total_module_power += inst.mod.analytical_power(corner, load)
return total_module_power
2020-11-03 15:29:17 +01:00
2020-10-27 17:23:11 +01:00
design.setup_drc_constants()
design.setup_layer_constants()
2020-11-03 15:29:17 +01:00