mirror of
https://github.com/VLSIDA/OpenRAM.git
synced 2026-09-01 10:39:35 +02:00
Merge branch 'dev' into multibank
This commit is contained in:
@@ -31,5 +31,5 @@ class bitcell_1port(bitcell_base):
|
||||
|
||||
def is_non_inverting(self):
|
||||
"""Return input to output polarity for module"""
|
||||
|
||||
|
||||
return False
|
||||
|
||||
@@ -102,5 +102,5 @@ class bitcell_2port(bitcell_base):
|
||||
|
||||
def is_non_inverting(self):
|
||||
"""Return input to output polarity for module"""
|
||||
|
||||
|
||||
return False
|
||||
|
||||
@@ -169,7 +169,7 @@ class bitcell_base(design):
|
||||
|
||||
"""
|
||||
return
|
||||
|
||||
|
||||
def get_all_wl_names(self):
|
||||
""" Creates a list of all wordline pin names """
|
||||
row_pins = ["wl"]
|
||||
@@ -207,39 +207,39 @@ class bitcell_base(design):
|
||||
is_nchannel = True
|
||||
stack = 2 # for access and inv tx
|
||||
is_cell = False
|
||||
return self.tr_r_on(drc["minwidth_tx"], is_nchannel, stack, is_cell)
|
||||
|
||||
return self.tr_r_on(drc["minwidth_tx"], is_nchannel, stack, is_cell)
|
||||
|
||||
def get_input_capacitance(self):
|
||||
"""Input cap of input, passes width of gates to gate cap function"""
|
||||
# Input cap of both access TX connected to the wordline
|
||||
return self.gate_c(2*parameter["6T_access_size"])
|
||||
|
||||
return self.gate_c(2*parameter["6T_access_size"])
|
||||
|
||||
def get_intrinsic_capacitance(self):
|
||||
"""Get the drain capacitances of the TXs in the gate."""
|
||||
stack = 1
|
||||
mult = 1
|
||||
# FIXME: Need to define TX sizes of bitcell storage node. Using
|
||||
# FIXME: Need to define TX sizes of bitcell storage node. Using
|
||||
# min_width as a temp value
|
||||
|
||||
# Add the inverter drain Cap and the bitline TX drain Cap
|
||||
nmos_drain_c = self.drain_c_(drc["minwidth_tx"]*mult,
|
||||
stack,
|
||||
mult)
|
||||
pmos_drain_c = self.drain_c_(drc["minwidth_tx"]*mult,
|
||||
stack,
|
||||
mult)
|
||||
|
||||
bl_nmos_drain_c = self.drain_c_(parameter["6T_access_size"],
|
||||
stack,
|
||||
mult)
|
||||
|
||||
return nmos_drain_c + pmos_drain_c + bl_nmos_drain_c
|
||||
|
||||
# Add the inverter drain Cap and the bitline TX drain Cap
|
||||
nmos_drain_c = self.drain_c_(drc["minwidth_tx"]*mult,
|
||||
stack,
|
||||
mult)
|
||||
pmos_drain_c = self.drain_c_(drc["minwidth_tx"]*mult,
|
||||
stack,
|
||||
mult)
|
||||
|
||||
bl_nmos_drain_c = self.drain_c_(parameter["6T_access_size"],
|
||||
stack,
|
||||
mult)
|
||||
|
||||
return nmos_drain_c + pmos_drain_c + bl_nmos_drain_c
|
||||
|
||||
def module_wire_c(self):
|
||||
"""Capacitance of bitline"""
|
||||
# FIXME: entire bitline cap is calculated here because of the current
|
||||
# graph implementation so array dims are all re-calculated here. May
|
||||
# be incorrect if dim calculations change
|
||||
# be incorrect if dim calculations change
|
||||
cells_in_col = OPTS.num_words/OPTS.words_per_row
|
||||
return cells_in_col*self.height*spice["wire_c_per_um"]
|
||||
|
||||
@@ -247,15 +247,15 @@ class bitcell_base(design):
|
||||
"""Resistance of bitline"""
|
||||
# FIXME: entire bitline r is calculated here because of the current
|
||||
# graph implementation so array dims are all re-calculated. May
|
||||
# be incorrect if dim calculations change
|
||||
# be incorrect if dim calculations change
|
||||
cells_in_col = OPTS.num_words/OPTS.words_per_row
|
||||
return cells_in_col*self.height*spice["wire_r_per_um"]
|
||||
|
||||
def cacti_rc_delay(self, inputramptime, tf, vs1, vs2, rise, extra_param_dict):
|
||||
return cells_in_col*self.height*spice["wire_r_per_um"]
|
||||
|
||||
def cacti_rc_delay(self, inputramptime, tf, vs1, vs2, rise, extra_param_dict):
|
||||
""" Special RC delay function used by CACTI for bitline delay
|
||||
"""
|
||||
import math
|
||||
vdd = extra_param_dict['vdd']
|
||||
vdd = extra_param_dict['vdd']
|
||||
m = vdd / inputramptime #v_wl = vdd for OpenRAM
|
||||
# vdd == V_b_pre in OpenRAM. Bitline swing is assumed 10% of vdd
|
||||
tstep = tf * math.log(vdd/(vdd - 0.1*vdd))
|
||||
@@ -264,4 +264,4 @@ class bitcell_base(design):
|
||||
else:
|
||||
delay = math.sqrt(2*tstep*(vdd-spice["nom_threshold"])/m)
|
||||
|
||||
return delay
|
||||
return delay
|
||||
|
||||
@@ -128,7 +128,7 @@ class bitcell_base_array(design):
|
||||
if len(self.all_ports) > 1:
|
||||
temp.extend(self.get_rbl_wordline_names(1))
|
||||
return temp
|
||||
|
||||
|
||||
def add_bitline_pins(self):
|
||||
bitline_names = self.cell.get_all_bitline_names()
|
||||
for col in range(self.column_size):
|
||||
@@ -165,7 +165,7 @@ class bitcell_base_array(design):
|
||||
""" Add the layout pins """
|
||||
self.add_bitline_pins()
|
||||
self.add_wl_pins()
|
||||
|
||||
|
||||
def _adjust_x_offset(self, xoffset, col, col_offset):
|
||||
tempx = xoffset
|
||||
dir_y = False
|
||||
|
||||
@@ -12,7 +12,7 @@ from .bitcell_base import bitcell_base
|
||||
|
||||
class col_cap_bitcell_1port(bitcell_base):
|
||||
"""
|
||||
Column end cap cell.
|
||||
Column end cap cell.
|
||||
"""
|
||||
|
||||
def __init__(self, name="col_cap_bitcell_1port"):
|
||||
|
||||
@@ -12,7 +12,7 @@ from .bitcell_base import bitcell_base
|
||||
|
||||
class col_cap_bitcell_2port(bitcell_base):
|
||||
"""
|
||||
Column end cap cell.
|
||||
Column end cap cell.
|
||||
"""
|
||||
|
||||
def __init__(self, name="col_cap_bitcell_2port"):
|
||||
|
||||
@@ -175,7 +175,7 @@ class column_mux_array(design):
|
||||
# Add the column x offset to find the right select bit
|
||||
gate_offset = self.mux_inst[col].get_pin("sel").bc()
|
||||
# use the y offset from the sel pin and the x offset from the gate
|
||||
|
||||
|
||||
offset = vector(gate_offset.x,
|
||||
self.get_pin("sel_{}".format(sel_index)).cy())
|
||||
|
||||
|
||||
@@ -5,74 +5,22 @@
|
||||
# (acting for and on behalf of Oklahoma State University)
|
||||
# All rights reserved.
|
||||
#
|
||||
from base import design
|
||||
import debug
|
||||
from sram_factory import factory
|
||||
import math
|
||||
from base import vector
|
||||
from globals import OPTS
|
||||
from base import logical_effort
|
||||
from .control_logic_base import control_logic_base
|
||||
|
||||
|
||||
class control_logic(design):
|
||||
class control_logic(control_logic_base):
|
||||
"""
|
||||
Dynamically generated Control logic for the total SRAM circuit.
|
||||
"""
|
||||
|
||||
def __init__(self, num_rows, words_per_row, word_size, spare_columns=None, sram=None, port_type="rw", name=""):
|
||||
""" Constructor """
|
||||
name = "control_logic_" + port_type
|
||||
super().__init__(name)
|
||||
debug.info(1, "Creating {}".format(name))
|
||||
self.add_comment("num_rows: {0}".format(num_rows))
|
||||
self.add_comment("words_per_row: {0}".format(words_per_row))
|
||||
self.add_comment("word_size {0}".format(word_size))
|
||||
|
||||
self.sram=sram
|
||||
self.num_rows = num_rows
|
||||
self.words_per_row = words_per_row
|
||||
self.word_size = word_size
|
||||
self.port_type = port_type
|
||||
|
||||
if not spare_columns:
|
||||
self.num_spare_cols = 0
|
||||
else:
|
||||
self.num_spare_cols = spare_columns
|
||||
|
||||
self.num_cols = word_size * words_per_row + self.num_spare_cols
|
||||
self.num_words = num_rows * words_per_row
|
||||
|
||||
self.enable_delay_chain_resizing = False
|
||||
self.inv_parasitic_delay = logical_effort.pinv
|
||||
|
||||
# Determines how much larger the sen delay should be. Accounts for possible error in model.
|
||||
# FIXME: This should be made a parameter
|
||||
self.wl_timing_tolerance = 1
|
||||
self.wl_stage_efforts = None
|
||||
self.sen_stage_efforts = None
|
||||
|
||||
if self.port_type == "rw":
|
||||
self.num_control_signals = 2
|
||||
else:
|
||||
self.num_control_signals = 1
|
||||
|
||||
self.create_netlist()
|
||||
if not OPTS.netlist_only:
|
||||
self.create_layout()
|
||||
|
||||
def create_netlist(self):
|
||||
self.setup_signal_busses()
|
||||
self.add_pins()
|
||||
self.add_modules()
|
||||
self.create_instances()
|
||||
|
||||
def create_layout(self):
|
||||
""" Create layout and route between modules """
|
||||
self.place_instances()
|
||||
self.route_all()
|
||||
# self.add_lvs_correspondence_points()
|
||||
self.add_boundary()
|
||||
self.DRC_LVS()
|
||||
super().__init__(num_rows, words_per_row, word_size, spare_columns, sram, port_type, name)
|
||||
|
||||
def add_pins(self):
|
||||
""" Add the pins to the control logic module. """
|
||||
@@ -151,93 +99,6 @@ class control_logic(design):
|
||||
self.delay_chain=factory.create(module_type="delay_chain",
|
||||
fanout_list = OPTS.delay_chain_stages * [ OPTS.delay_chain_fanout_per_stage ])
|
||||
|
||||
def get_dynamic_delay_chain_size(self, previous_stages, previous_fanout):
|
||||
"""Determine the size of the delay chain used for the Sense Amp Enable using path delays"""
|
||||
from math import ceil
|
||||
previous_delay_chain_delay = (previous_fanout + 1 + self.inv_parasitic_delay) * previous_stages
|
||||
debug.info(2, "Previous delay chain produced {} delay units".format(previous_delay_chain_delay))
|
||||
|
||||
# This can be anything >=2
|
||||
delay_fanout = 3
|
||||
# The delay chain uses minimum sized inverters. There are (fanout+1)*stages inverters and each
|
||||
# inverter adds 1 unit of delay (due to minimum size). This also depends on the pinv value
|
||||
required_delay = self.wl_delay * self.wl_timing_tolerance - (self.sen_delay - previous_delay_chain_delay)
|
||||
debug.check(required_delay > 0, "Cannot size delay chain to have negative delay")
|
||||
delay_per_stage = delay_fanout + 1 + self.inv_parasitic_delay
|
||||
delay_stages = ceil(required_delay / delay_per_stage)
|
||||
# force an even number of stages.
|
||||
if delay_stages % 2 == 1:
|
||||
delay_stages += 1
|
||||
# Fanout can be varied as well but is a little more complicated but potentially optimal.
|
||||
debug.info(1, "Setting delay chain to {} stages with {} fanout to match {} delay".format(delay_stages, delay_fanout, required_delay))
|
||||
return (delay_stages, delay_fanout)
|
||||
|
||||
def get_dynamic_delay_fanout_list(self, previous_stages, previous_fanout):
|
||||
"""Determine the size of the delay chain used for the Sense Amp Enable using path delays"""
|
||||
|
||||
previous_delay_per_stage = previous_fanout + 1 + self.inv_parasitic_delay
|
||||
previous_delay_chain_delay = previous_delay_per_stage * previous_stages
|
||||
debug.info(2, "Previous delay chain produced {} delay units".format(previous_delay_chain_delay))
|
||||
|
||||
fanout_rise = fanout_fall = 2 # This can be anything >=2
|
||||
# The delay chain uses minimum sized inverters. There are (fanout+1)*stages inverters and each
|
||||
# inverter adds 1 unit of delay (due to minimum size). This also depends on the pinv value
|
||||
required_delay_fall = self.wl_delay_fall * self.wl_timing_tolerance - \
|
||||
(self.sen_delay_fall - previous_delay_chain_delay / 2)
|
||||
required_delay_rise = self.wl_delay_rise * self.wl_timing_tolerance - \
|
||||
(self.sen_delay_rise - previous_delay_chain_delay / 2)
|
||||
debug.info(2,
|
||||
"Required delays from chain: fall={}, rise={}".format(required_delay_fall,
|
||||
required_delay_rise))
|
||||
|
||||
# If the fanout is different between rise/fall by this amount. Stage algorithm is made more pessimistic.
|
||||
WARNING_FANOUT_DIFF = 5
|
||||
stages_close = False
|
||||
# The stages need to be equal (or at least a even number of stages with matching rise/fall delays)
|
||||
while True:
|
||||
stages_fall = self.calculate_stages_with_fixed_fanout(required_delay_fall,
|
||||
fanout_fall)
|
||||
stages_rise = self.calculate_stages_with_fixed_fanout(required_delay_rise,
|
||||
fanout_rise)
|
||||
debug.info(1,
|
||||
"Fall stages={}, rise stages={}".format(stages_fall,
|
||||
stages_rise))
|
||||
if abs(stages_fall - stages_rise) == 1 and not stages_close:
|
||||
stages_close = True
|
||||
safe_fanout_rise = fanout_rise
|
||||
safe_fanout_fall = fanout_fall
|
||||
|
||||
if stages_fall == stages_rise:
|
||||
break
|
||||
elif abs(stages_fall - stages_rise) == 1 and WARNING_FANOUT_DIFF < abs(fanout_fall - fanout_rise):
|
||||
debug.info(1, "Delay chain fanouts between stages are large. Making chain size larger for safety.")
|
||||
fanout_rise = safe_fanout_rise
|
||||
fanout_fall = safe_fanout_fall
|
||||
break
|
||||
# There should also be a condition to make sure the fanout does not get too large.
|
||||
# Otherwise, increase the fanout of delay with the most stages, calculate new stages
|
||||
elif stages_fall>stages_rise:
|
||||
fanout_fall+=1
|
||||
else:
|
||||
fanout_rise+=1
|
||||
|
||||
total_stages = max(stages_fall, stages_rise) * 2
|
||||
debug.info(1, "New Delay chain: stages={}, fanout_rise={}, fanout_fall={}".format(total_stages, fanout_rise, fanout_fall))
|
||||
|
||||
# Creates interleaved fanout list of rise/fall delays. Assumes fall is the first stage.
|
||||
stage_list = [fanout_fall if i % 2==0 else fanout_rise for i in range(total_stages)]
|
||||
return stage_list
|
||||
|
||||
def calculate_stages_with_fixed_fanout(self, required_delay, fanout):
|
||||
from math import ceil
|
||||
# Delay being negative is not an error. It implies that any amount of stages would have a negative effect on the overall delay
|
||||
# 3 is the minimum delay per stage (with pinv=0).
|
||||
if required_delay <= 3 + self.inv_parasitic_delay:
|
||||
return 1
|
||||
delay_per_stage = fanout + 1 + self.inv_parasitic_delay
|
||||
delay_stages = ceil(required_delay / delay_per_stage)
|
||||
return delay_stages
|
||||
|
||||
def setup_signal_busses(self):
|
||||
""" Setup bus names, determine the size of the busses etc """
|
||||
|
||||
@@ -277,17 +138,6 @@ class control_logic(design):
|
||||
|
||||
self.supply_list = ["vdd", "gnd"]
|
||||
|
||||
def route_rails(self):
|
||||
""" Add the input signal inverted tracks """
|
||||
height = self.control_logic_center.y - self.m2_pitch
|
||||
# DFF spacing plus the power routing
|
||||
offset = vector(self.ctrl_dff_array.width + self.m4_pitch, 0)
|
||||
|
||||
self.input_bus = self.create_vertical_bus("m2",
|
||||
offset,
|
||||
self.internal_bus_list,
|
||||
height)
|
||||
|
||||
def create_instances(self):
|
||||
""" Create all the instances """
|
||||
self.create_dffs()
|
||||
@@ -303,21 +153,8 @@ class control_logic(design):
|
||||
self.create_delay()
|
||||
self.create_pen_row()
|
||||
|
||||
def place_instances(self):
|
||||
""" Place all the instances """
|
||||
# Keep track of all right-most instances to determine row boundary
|
||||
# and add the vdd/gnd pins
|
||||
self.row_end_inst = []
|
||||
|
||||
# Add the control flops on the left of the bus
|
||||
self.place_dffs()
|
||||
|
||||
# All of the control logic is placed to the right of the DFFs and bus
|
||||
# as well as the power supply stripe
|
||||
self.control_x_offset = self.ctrl_dff_array.width + self.internal_bus_width + self.m4_pitch
|
||||
|
||||
def place_logic_rows(self):
|
||||
row = 0
|
||||
# Add the logic on the right of the bus
|
||||
self.place_clk_buf_row(row)
|
||||
row += 1
|
||||
self.place_gated_clk_bar_row(row)
|
||||
@@ -336,24 +173,8 @@ class control_logic(design):
|
||||
self.place_rbl_delay_row(row)
|
||||
row += 1
|
||||
self.place_wlen_row(row)
|
||||
row += 1
|
||||
|
||||
control_center_y = self.wl_en_inst.uy() + self.m3_pitch
|
||||
|
||||
# Delay chain always gets placed at row 4
|
||||
self.place_delay(4)
|
||||
height = self.delay_inst.uy()
|
||||
|
||||
# This offset is used for placement of the control logic in the SRAM level.
|
||||
self.control_logic_center = vector(self.ctrl_dff_inst.rx(), control_center_y)
|
||||
|
||||
# Extra pitch on top and right
|
||||
self.height = height + 2 * self.m1_pitch
|
||||
# Max of modules or logic rows
|
||||
self.width = max([inst.rx() for inst in self.row_end_inst])
|
||||
if (self.port_type == "rw") or (self.port_type == "r"):
|
||||
self.width = max(self.delay_inst.rx(), self.width)
|
||||
self.width += self.m2_pitch
|
||||
self.control_center_y = self.wl_en_inst.uy() + self.m3_pitch
|
||||
|
||||
def route_all(self):
|
||||
""" Routing between modules """
|
||||
@@ -373,24 +194,12 @@ class control_logic(design):
|
||||
self.route_supplies()
|
||||
|
||||
def create_delay(self):
|
||||
""" Create the replica bitline """
|
||||
""" Create the delay chain """
|
||||
self.delay_inst=self.add_inst(name="delay_chain",
|
||||
mod=self.delay_chain)
|
||||
# rbl_bl_delay is asserted (1) when the bitline has been discharged
|
||||
self.connect_inst(["rbl_bl", "rbl_bl_delay", "vdd", "gnd"])
|
||||
|
||||
def place_delay(self, row):
|
||||
""" Place the replica bitline """
|
||||
debug.check(row % 2 == 0, "Must place delay chain at even row for supply alignment.")
|
||||
|
||||
# It is flipped on X axis
|
||||
y_off = row * self.and2.height + self.delay_chain.height
|
||||
|
||||
# Add the RBL above the rows
|
||||
# Add to the right of the control rows and routing channel
|
||||
offset = vector(0, y_off)
|
||||
self.delay_inst.place(offset, mirror="MX")
|
||||
|
||||
def route_delay(self):
|
||||
|
||||
out_pos = self.delay_inst.get_pin("out").center()
|
||||
@@ -406,109 +215,6 @@ class control_logic(design):
|
||||
# Input from RBL goes to the delay line for futher delay
|
||||
self.copy_layout_pin(self.delay_inst, "in", "rbl_bl")
|
||||
|
||||
def create_clk_buf_row(self):
|
||||
""" Create the multistage and gated clock buffer """
|
||||
self.clk_buf_inst = self.add_inst(name="clkbuf",
|
||||
mod=self.clk_buf_driver)
|
||||
self.connect_inst(["clk", "clk_buf", "vdd", "gnd"])
|
||||
|
||||
def place_clk_buf_row(self, row):
|
||||
x_offset = self.control_x_offset
|
||||
|
||||
x_offset = self.place_util(self.clk_buf_inst, x_offset, row)
|
||||
|
||||
self.row_end_inst.append(self.clk_buf_inst)
|
||||
|
||||
def route_clk_buf(self):
|
||||
clk_pin = self.clk_buf_inst.get_pin("A")
|
||||
clk_pos = clk_pin.center()
|
||||
self.add_layout_pin_rect_center(text="clk",
|
||||
layer="m2",
|
||||
offset=clk_pos)
|
||||
self.add_via_stack_center(from_layer=clk_pin.layer,
|
||||
to_layer="m2",
|
||||
offset=clk_pos)
|
||||
|
||||
self.route_output_to_bus_jogged(self.clk_buf_inst,
|
||||
"clk_buf")
|
||||
self.connect_output(self.clk_buf_inst, "Z", "clk_buf")
|
||||
|
||||
def create_gated_clk_bar_row(self):
|
||||
self.clk_bar_inst = self.add_inst(name="inv_clk_bar",
|
||||
mod=self.inv)
|
||||
self.connect_inst(["clk_buf", "clk_bar", "vdd", "gnd"])
|
||||
|
||||
self.gated_clk_bar_inst = self.add_inst(name="and2_gated_clk_bar",
|
||||
mod=self.and2)
|
||||
self.connect_inst(["clk_bar", "cs", "gated_clk_bar", "vdd", "gnd"])
|
||||
|
||||
def place_gated_clk_bar_row(self, row):
|
||||
x_offset = self.control_x_offset
|
||||
|
||||
x_offset = self.place_util(self.clk_bar_inst, x_offset, row)
|
||||
x_offset = self.place_util(self.gated_clk_bar_inst, x_offset, row)
|
||||
|
||||
self.row_end_inst.append(self.gated_clk_bar_inst)
|
||||
|
||||
def route_gated_clk_bar(self):
|
||||
clkbuf_map = zip(["A"], ["clk_buf"])
|
||||
self.connect_vertical_bus(clkbuf_map, self.clk_bar_inst, self.input_bus)
|
||||
|
||||
out_pin = self.clk_bar_inst.get_pin("Z")
|
||||
out_pos = out_pin.center()
|
||||
in_pin = self.gated_clk_bar_inst.get_pin("A")
|
||||
in_pos = in_pin.center()
|
||||
self.add_zjog(out_pin.layer, out_pos, in_pos)
|
||||
self.add_via_stack_center(from_layer=out_pin.layer,
|
||||
to_layer=in_pin.layer,
|
||||
offset=in_pos)
|
||||
|
||||
|
||||
# This is the second gate over, so it needs to be on M3
|
||||
clkbuf_map = zip(["B"], ["cs"])
|
||||
self.connect_vertical_bus(clkbuf_map,
|
||||
self.gated_clk_bar_inst,
|
||||
self.input_bus,
|
||||
self.m2_stack[::-1])
|
||||
# The pin is on M1, so we need another via as well
|
||||
b_pin = self.gated_clk_bar_inst.get_pin("B")
|
||||
self.add_via_stack_center(from_layer=b_pin.layer,
|
||||
to_layer="m3",
|
||||
offset=b_pin.center())
|
||||
|
||||
# This is the second gate over, so it needs to be on M3
|
||||
self.route_output_to_bus_jogged(self.gated_clk_bar_inst,
|
||||
"gated_clk_bar")
|
||||
|
||||
def create_gated_clk_buf_row(self):
|
||||
self.gated_clk_buf_inst = self.add_inst(name="and2_gated_clk_buf",
|
||||
mod=self.and2)
|
||||
self.connect_inst(["clk_buf", "cs", "gated_clk_buf", "vdd", "gnd"])
|
||||
|
||||
def place_gated_clk_buf_row(self, row):
|
||||
x_offset = self.control_x_offset
|
||||
|
||||
x_offset = self.place_util(self.gated_clk_buf_inst, x_offset, row)
|
||||
|
||||
self.row_end_inst.append(self.gated_clk_buf_inst)
|
||||
|
||||
def route_gated_clk_buf(self):
|
||||
clkbuf_map = zip(["A", "B"], ["clk_buf", "cs"])
|
||||
self.connect_vertical_bus(clkbuf_map,
|
||||
self.gated_clk_buf_inst,
|
||||
self.input_bus)
|
||||
|
||||
clkbuf_map = zip(["Z"], ["gated_clk_buf"])
|
||||
self.connect_vertical_bus(clkbuf_map,
|
||||
self.gated_clk_buf_inst,
|
||||
self.input_bus,
|
||||
self.m2_stack[::-1])
|
||||
# The pin is on M1, so we need another via as well
|
||||
z_pin = self.gated_clk_buf_inst.get_pin("Z")
|
||||
self.add_via_stack_center(from_layer=z_pin.layer,
|
||||
to_layer="m2",
|
||||
offset=z_pin.center())
|
||||
|
||||
def create_wlen_row(self):
|
||||
# input pre_p_en, output: wl_en
|
||||
self.wl_en_inst=self.add_inst(name="buf_wl_en",
|
||||
@@ -651,194 +357,3 @@ class control_logic(design):
|
||||
self.connect_vertical_bus(wen_map, self.w_en_gate_inst, self.input_bus)
|
||||
|
||||
self.connect_output(self.w_en_gate_inst, "Z", "w_en")
|
||||
|
||||
def create_dffs(self):
|
||||
self.ctrl_dff_inst=self.add_inst(name="ctrl_dffs",
|
||||
mod=self.ctrl_dff_array)
|
||||
inst_pins = self.input_list + self.dff_output_list + ["clk_buf"] + self.supply_list
|
||||
self.connect_inst(inst_pins)
|
||||
|
||||
def place_dffs(self):
|
||||
self.ctrl_dff_inst.place(vector(0, 0))
|
||||
|
||||
def route_dffs(self):
|
||||
if self.port_type == "rw":
|
||||
dff_out_map = zip(["dout_bar_0", "dout_bar_1", "dout_1"], ["cs", "we", "we_bar"])
|
||||
elif self.port_type == "r":
|
||||
dff_out_map = zip(["dout_bar_0", "dout_0"], ["cs", "cs_bar"])
|
||||
else:
|
||||
dff_out_map = zip(["dout_bar_0"], ["cs"])
|
||||
self.connect_vertical_bus(dff_out_map, self.ctrl_dff_inst, self.input_bus, self.m2_stack[::-1])
|
||||
|
||||
# Connect the clock rail to the other clock rail
|
||||
# by routing in the supply rail track to avoid channel conflicts
|
||||
in_pos = self.ctrl_dff_inst.get_pin("clk").uc()
|
||||
mid_pos = vector(in_pos.x, self.gated_clk_buf_inst.get_pin("vdd").cy() - self.m1_pitch)
|
||||
rail_pos = vector(self.input_bus["clk_buf"].cx(), mid_pos.y)
|
||||
self.add_wire(self.m1_stack, [in_pos, mid_pos, rail_pos])
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=rail_pos)
|
||||
|
||||
self.copy_layout_pin(self.ctrl_dff_inst, "din_0", "csb")
|
||||
if (self.port_type == "rw"):
|
||||
self.copy_layout_pin(self.ctrl_dff_inst, "din_1", "web")
|
||||
|
||||
def get_offset(self, row):
|
||||
""" Compute the y-offset and mirroring """
|
||||
y_off = row * self.and2.height
|
||||
if row % 2:
|
||||
y_off += self.and2.height
|
||||
mirror="MX"
|
||||
else:
|
||||
mirror="R0"
|
||||
|
||||
return (y_off, mirror)
|
||||
|
||||
def connect_output(self, inst, pin_name, out_name):
|
||||
""" Create an output pin on the right side from the pin of a given instance. """
|
||||
|
||||
out_pin = inst.get_pin(pin_name)
|
||||
out_pos = out_pin.center()
|
||||
right_pos = out_pos + vector(self.width - out_pin.cx(), 0)
|
||||
|
||||
self.add_via_stack_center(from_layer=out_pin.layer,
|
||||
to_layer="m2",
|
||||
offset=out_pos)
|
||||
self.add_layout_pin_segment_center(text=out_name,
|
||||
layer="m2",
|
||||
start=out_pos,
|
||||
end=right_pos)
|
||||
|
||||
def route_supplies(self):
|
||||
""" Add vdd and gnd to the instance cells """
|
||||
|
||||
pin_layer = self.dff.get_pin("vdd").layer
|
||||
supply_layer = self.supply_stack[2]
|
||||
|
||||
|
||||
# FIXME: We should be able to replace this with route_vertical_pins instead
|
||||
# but we may have to make the logic gates a separate module so that they
|
||||
# have row pins of the same width
|
||||
max_row_x_loc = max([inst.rx() for inst in self.row_end_inst])
|
||||
min_row_x_loc = self.control_x_offset
|
||||
|
||||
vdd_pin_locs = []
|
||||
gnd_pin_locs = []
|
||||
|
||||
last_via = None
|
||||
for inst in self.row_end_inst:
|
||||
pins = inst.get_pins("vdd")
|
||||
for pin in pins:
|
||||
if pin.layer == pin_layer:
|
||||
row_loc = pin.rc()
|
||||
pin_loc = vector(max_row_x_loc, pin.rc().y)
|
||||
vdd_pin_locs.append(pin_loc)
|
||||
last_via = self.add_via_stack_center(from_layer=pin_layer,
|
||||
to_layer=supply_layer,
|
||||
offset=pin_loc,
|
||||
min_area=True)
|
||||
self.add_path(pin_layer, [row_loc, pin_loc])
|
||||
|
||||
pins = inst.get_pins("gnd")
|
||||
for pin in pins:
|
||||
if pin.layer == pin_layer:
|
||||
row_loc = pin.rc()
|
||||
pin_loc = vector(min_row_x_loc, pin.rc().y)
|
||||
gnd_pin_locs.append(pin_loc)
|
||||
last_via = self.add_via_stack_center(from_layer=pin_layer,
|
||||
to_layer=supply_layer,
|
||||
offset=pin_loc,
|
||||
min_area=True)
|
||||
self.add_path(pin_layer, [row_loc, pin_loc])
|
||||
|
||||
if last_via:
|
||||
via_height=last_via.mod.second_layer_height
|
||||
via_width=last_via.mod.second_layer_width
|
||||
else:
|
||||
via_height=None
|
||||
via_width=0
|
||||
|
||||
min_y = min([x.y for x in vdd_pin_locs])
|
||||
max_y = max([x.y for x in vdd_pin_locs])
|
||||
bot_pos = vector(max_row_x_loc, min_y - 0.5 * via_height)
|
||||
top_pos = vector(max_row_x_loc, max_y + 0.5 * via_height)
|
||||
self.add_layout_pin_segment_center(text="vdd",
|
||||
layer=supply_layer,
|
||||
start=bot_pos,
|
||||
end=top_pos,
|
||||
width=via_width)
|
||||
|
||||
min_y = min([x.y for x in gnd_pin_locs])
|
||||
max_y = max([x.y for x in gnd_pin_locs])
|
||||
bot_pos = vector(min_row_x_loc, min_y - 0.5 * via_height)
|
||||
top_pos = vector(min_row_x_loc, max_y + 0.5 * via_height)
|
||||
self.add_layout_pin_segment_center(text="gnd",
|
||||
layer=supply_layer,
|
||||
start=bot_pos,
|
||||
end=top_pos,
|
||||
width=via_width)
|
||||
|
||||
self.copy_layout_pin(self.delay_inst, "gnd")
|
||||
self.copy_layout_pin(self.delay_inst, "vdd")
|
||||
|
||||
self.copy_layout_pin(self.ctrl_dff_inst, "gnd")
|
||||
self.copy_layout_pin(self.ctrl_dff_inst, "vdd")
|
||||
|
||||
def add_lvs_correspondence_points(self):
|
||||
""" This adds some points for easier debugging if LVS goes wrong.
|
||||
These should probably be turned off by default though, since extraction
|
||||
will show these as ports in the extracted netlist.
|
||||
"""
|
||||
# pin=self.clk_inv1.get_pin("Z")
|
||||
# self.add_label_pin(text="clk1_bar",
|
||||
# layer="m1",
|
||||
# offset=pin.ll(),
|
||||
# height=pin.height(),
|
||||
# width=pin.width())
|
||||
|
||||
# pin=self.clk_inv2.get_pin("Z")
|
||||
# self.add_label_pin(text="clk2",
|
||||
# layer="m1",
|
||||
# offset=pin.ll(),
|
||||
# height=pin.height(),
|
||||
# width=pin.width())
|
||||
|
||||
pin=self.delay_inst.get_pin("out")
|
||||
self.add_label_pin(text="out",
|
||||
layer=pin.layer,
|
||||
offset=pin.ll(),
|
||||
height=pin.height(),
|
||||
width=pin.width())
|
||||
|
||||
def graph_exclude_dffs(self):
|
||||
"""Exclude dffs from graph as they do not represent critical path"""
|
||||
|
||||
self.graph_inst_exclude.add(self.ctrl_dff_inst)
|
||||
if self.port_type=="rw" or self.port_type=="w":
|
||||
self.graph_inst_exclude.add(self.w_en_gate_inst)
|
||||
|
||||
def place_util(self, inst, x_offset, row):
|
||||
""" Utility to place a row and compute the next offset """
|
||||
|
||||
(y_offset, mirror) = self.get_offset(row)
|
||||
offset = vector(x_offset, y_offset)
|
||||
inst.place(offset, mirror)
|
||||
return x_offset + inst.width
|
||||
|
||||
def route_output_to_bus_jogged(self, inst, name):
|
||||
# Connect this at the bottom of the buffer
|
||||
out_pin = inst.get_pin("Z")
|
||||
out_pos = out_pin.center()
|
||||
mid1 = vector(out_pos.x, out_pos.y - 0.3 * inst.mod.height)
|
||||
mid2 = vector(self.input_bus[name].cx(), mid1.y)
|
||||
bus_pos = self.input_bus[name].center()
|
||||
self.add_wire(self.m2_stack[::-1], [out_pos, mid1, mid2, bus_pos])
|
||||
self.add_via_stack_center(from_layer=out_pin.layer,
|
||||
to_layer="m2",
|
||||
offset=out_pos)
|
||||
|
||||
def get_left_pins(self, name):
|
||||
"""
|
||||
Return the left side supply pins to connect to a vertical stripe.
|
||||
"""
|
||||
return(self.cntrl_dff_inst.get_pins(name) + self.delay_inst.get_pins(name))
|
||||
|
||||
@@ -0,0 +1,508 @@
|
||||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2021 Regents of the University of California and The Board
|
||||
# of Regents for the Oklahoma Agricultural and Mechanical College
|
||||
# (acting for and on behalf of Oklahoma State University)
|
||||
# All rights reserved.
|
||||
#
|
||||
from base import design
|
||||
import debug
|
||||
from sram_factory import factory
|
||||
import math
|
||||
from base import vector
|
||||
from globals import OPTS
|
||||
from base import logical_effort
|
||||
|
||||
|
||||
class control_logic_base(design):
|
||||
"""
|
||||
Generic base class for SRAM control logic.
|
||||
"""
|
||||
|
||||
def __init__(self, num_rows, words_per_row, word_size, spare_columns=None, sram=None, port_type="rw", name=""):
|
||||
""" Constructor """
|
||||
name = "control_logic_" + port_type
|
||||
super().__init__(name)
|
||||
debug.info(1, "Creating {}".format(name))
|
||||
self.add_comment("num_rows: {0}".format(num_rows))
|
||||
self.add_comment("words_per_row: {0}".format(words_per_row))
|
||||
self.add_comment("word_size {0}".format(word_size))
|
||||
|
||||
self.sram=sram
|
||||
self.num_rows = num_rows
|
||||
self.words_per_row = words_per_row
|
||||
self.word_size = word_size
|
||||
self.port_type = port_type
|
||||
|
||||
if not spare_columns:
|
||||
self.num_spare_cols = 0
|
||||
else:
|
||||
self.num_spare_cols = spare_columns
|
||||
|
||||
self.num_cols = word_size * words_per_row + self.num_spare_cols
|
||||
self.num_words = num_rows * words_per_row
|
||||
|
||||
self.enable_delay_chain_resizing = False
|
||||
self.inv_parasitic_delay = logical_effort.pinv
|
||||
|
||||
# Determines how much larger the sen delay should be. Accounts for possible error in model.
|
||||
# FIXME: This should be made a parameter
|
||||
self.wl_timing_tolerance = 1
|
||||
self.wl_stage_efforts = None
|
||||
self.sen_stage_efforts = None
|
||||
|
||||
if self.port_type == "rw":
|
||||
self.num_control_signals = 2
|
||||
else:
|
||||
self.num_control_signals = 1
|
||||
|
||||
self.create_netlist()
|
||||
if not OPTS.netlist_only:
|
||||
self.create_layout()
|
||||
|
||||
def create_netlist(self):
|
||||
self.setup_signal_busses()
|
||||
self.add_pins()
|
||||
self.add_modules()
|
||||
self.create_instances()
|
||||
|
||||
def create_layout(self):
|
||||
""" Create layout and route between modules """
|
||||
self.place_instances()
|
||||
self.route_all()
|
||||
# self.add_lvs_correspondence_points()
|
||||
self.add_boundary()
|
||||
self.DRC_LVS()
|
||||
|
||||
def get_dynamic_delay_chain_size(self, previous_stages, previous_fanout):
|
||||
"""Determine the size of the delay chain used for the Sense Amp Enable using path delays"""
|
||||
from math import ceil
|
||||
previous_delay_chain_delay = (previous_fanout + 1 + self.inv_parasitic_delay) * previous_stages
|
||||
debug.info(2, "Previous delay chain produced {} delay units".format(previous_delay_chain_delay))
|
||||
|
||||
# This can be anything >=2
|
||||
delay_fanout = 3
|
||||
# The delay chain uses minimum sized inverters. There are (fanout+1)*stages inverters and each
|
||||
# inverter adds 1 unit of delay (due to minimum size). This also depends on the pinv value
|
||||
required_delay = self.wl_delay * self.wl_timing_tolerance - (self.sen_delay - previous_delay_chain_delay)
|
||||
debug.check(required_delay > 0, "Cannot size delay chain to have negative delay")
|
||||
delay_per_stage = delay_fanout + 1 + self.inv_parasitic_delay
|
||||
delay_stages = ceil(required_delay / delay_per_stage)
|
||||
# force an even number of stages.
|
||||
if delay_stages % 2 == 1:
|
||||
delay_stages += 1
|
||||
# Fanout can be varied as well but is a little more complicated but potentially optimal.
|
||||
debug.info(1, "Setting delay chain to {} stages with {} fanout to match {} delay".format(delay_stages, delay_fanout, required_delay))
|
||||
return (delay_stages, delay_fanout)
|
||||
|
||||
def get_dynamic_delay_fanout_list(self, previous_stages, previous_fanout):
|
||||
"""Determine the size of the delay chain used for the Sense Amp Enable using path delays"""
|
||||
|
||||
previous_delay_per_stage = previous_fanout + 1 + self.inv_parasitic_delay
|
||||
previous_delay_chain_delay = previous_delay_per_stage * previous_stages
|
||||
debug.info(2, "Previous delay chain produced {} delay units".format(previous_delay_chain_delay))
|
||||
|
||||
fanout_rise = fanout_fall = 2 # This can be anything >=2
|
||||
# The delay chain uses minimum sized inverters. There are (fanout+1)*stages inverters and each
|
||||
# inverter adds 1 unit of delay (due to minimum size). This also depends on the pinv value
|
||||
required_delay_fall = self.wl_delay_fall * self.wl_timing_tolerance - \
|
||||
(self.sen_delay_fall - previous_delay_chain_delay / 2)
|
||||
required_delay_rise = self.wl_delay_rise * self.wl_timing_tolerance - \
|
||||
(self.sen_delay_rise - previous_delay_chain_delay / 2)
|
||||
debug.info(2,
|
||||
"Required delays from chain: fall={}, rise={}".format(required_delay_fall,
|
||||
required_delay_rise))
|
||||
|
||||
# If the fanout is different between rise/fall by this amount. Stage algorithm is made more pessimistic.
|
||||
WARNING_FANOUT_DIFF = 5
|
||||
stages_close = False
|
||||
# The stages need to be equal (or at least a even number of stages with matching rise/fall delays)
|
||||
while True:
|
||||
stages_fall = self.calculate_stages_with_fixed_fanout(required_delay_fall,
|
||||
fanout_fall)
|
||||
stages_rise = self.calculate_stages_with_fixed_fanout(required_delay_rise,
|
||||
fanout_rise)
|
||||
debug.info(1,
|
||||
"Fall stages={}, rise stages={}".format(stages_fall,
|
||||
stages_rise))
|
||||
if abs(stages_fall - stages_rise) == 1 and not stages_close:
|
||||
stages_close = True
|
||||
safe_fanout_rise = fanout_rise
|
||||
safe_fanout_fall = fanout_fall
|
||||
|
||||
if stages_fall == stages_rise:
|
||||
break
|
||||
elif abs(stages_fall - stages_rise) == 1 and WARNING_FANOUT_DIFF < abs(fanout_fall - fanout_rise):
|
||||
debug.info(1, "Delay chain fanouts between stages are large. Making chain size larger for safety.")
|
||||
fanout_rise = safe_fanout_rise
|
||||
fanout_fall = safe_fanout_fall
|
||||
break
|
||||
# There should also be a condition to make sure the fanout does not get too large.
|
||||
# Otherwise, increase the fanout of delay with the most stages, calculate new stages
|
||||
elif stages_fall>stages_rise:
|
||||
fanout_fall+=1
|
||||
else:
|
||||
fanout_rise+=1
|
||||
|
||||
total_stages = max(stages_fall, stages_rise) * 2
|
||||
debug.info(1, "New Delay chain: stages={}, fanout_rise={}, fanout_fall={}".format(total_stages, fanout_rise, fanout_fall))
|
||||
|
||||
# Creates interleaved fanout list of rise/fall delays. Assumes fall is the first stage.
|
||||
stage_list = [fanout_fall if i % 2==0 else fanout_rise for i in range(total_stages)]
|
||||
return stage_list
|
||||
|
||||
def calculate_stages_with_fixed_fanout(self, required_delay, fanout):
|
||||
from math import ceil
|
||||
# Delay being negative is not an error. It implies that any amount of stages would have a negative effect on the overall delay
|
||||
# 3 is the minimum delay per stage (with pinv=0).
|
||||
if required_delay <= 3 + self.inv_parasitic_delay:
|
||||
return 1
|
||||
delay_per_stage = fanout + 1 + self.inv_parasitic_delay
|
||||
delay_stages = ceil(required_delay / delay_per_stage)
|
||||
return delay_stages
|
||||
|
||||
def route_rails(self):
|
||||
""" Add the input signal inverted tracks """
|
||||
height = self.control_logic_center.y - self.m2_pitch
|
||||
# DFF spacing plus the power routing
|
||||
offset = vector(self.ctrl_dff_array.width + self.m4_pitch, 0)
|
||||
|
||||
self.input_bus = self.create_vertical_bus("m2",
|
||||
offset,
|
||||
self.internal_bus_list,
|
||||
height)
|
||||
|
||||
def place_instances(self):
|
||||
""" Place all the instances """
|
||||
# Keep track of all right-most instances to determine row boundary
|
||||
# and add the vdd/gnd pins
|
||||
self.row_end_inst = []
|
||||
|
||||
# Add the control flops on the left of the bus
|
||||
self.place_dffs()
|
||||
|
||||
# All of the control logic is placed to the right of the DFFs and bus
|
||||
# as well as the power supply stripe
|
||||
self.control_x_offset = self.ctrl_dff_array.width + self.internal_bus_width + self.m4_pitch
|
||||
|
||||
self.place_logic_rows()
|
||||
|
||||
# Delay chain always gets placed at row 4
|
||||
self.place_delay(4)
|
||||
height = self.delay_inst.uy()
|
||||
|
||||
# This offset is used for placement of the control logic in the SRAM level.
|
||||
self.control_logic_center = vector(self.ctrl_dff_inst.rx(), self.control_center_y)
|
||||
|
||||
# Extra pitch on top and right
|
||||
self.height = height + 2 * self.m1_pitch
|
||||
# Max of modules or logic rows
|
||||
self.width = max([inst.rx() for inst in self.row_end_inst])
|
||||
if (self.port_type == "rw") or (self.port_type == "r"):
|
||||
self.width = max(self.delay_inst.rx(), self.width)
|
||||
self.width += self.m2_pitch
|
||||
|
||||
def place_delay(self, row):
|
||||
""" Place the delay chain """
|
||||
debug.check(row % 2 == 0, "Must place delay chain at even row for supply alignment.")
|
||||
|
||||
# It is flipped on X axis
|
||||
y_off = row * self.and2.height + self.delay_chain.height
|
||||
|
||||
# Add to the right of the control rows and routing channel
|
||||
offset = vector(0, y_off)
|
||||
self.delay_inst.place(offset, mirror="MX")
|
||||
|
||||
def create_clk_buf_row(self):
|
||||
""" Create the multistage and gated clock buffer """
|
||||
self.clk_buf_inst = self.add_inst(name="clkbuf",
|
||||
mod=self.clk_buf_driver)
|
||||
self.connect_inst(["clk", "clk_buf", "vdd", "gnd"])
|
||||
|
||||
def place_clk_buf_row(self, row):
|
||||
x_offset = self.control_x_offset
|
||||
|
||||
x_offset = self.place_util(self.clk_buf_inst, x_offset, row)
|
||||
|
||||
self.row_end_inst.append(self.clk_buf_inst)
|
||||
|
||||
def route_clk_buf(self):
|
||||
clk_pin = self.clk_buf_inst.get_pin("A")
|
||||
clk_pos = clk_pin.center()
|
||||
self.add_layout_pin_rect_center(text="clk",
|
||||
layer="m2",
|
||||
offset=clk_pos)
|
||||
self.add_via_stack_center(from_layer=clk_pin.layer,
|
||||
to_layer="m2",
|
||||
offset=clk_pos)
|
||||
|
||||
self.route_output_to_bus_jogged(self.clk_buf_inst,
|
||||
"clk_buf")
|
||||
self.connect_output(self.clk_buf_inst, "Z", "clk_buf")
|
||||
|
||||
def create_gated_clk_bar_row(self):
|
||||
self.clk_bar_inst = self.add_inst(name="inv_clk_bar",
|
||||
mod=self.inv)
|
||||
self.connect_inst(["clk_buf", "clk_bar", "vdd", "gnd"])
|
||||
|
||||
self.gated_clk_bar_inst = self.add_inst(name="and2_gated_clk_bar",
|
||||
mod=self.and2)
|
||||
self.connect_inst(["clk_bar", "cs", "gated_clk_bar", "vdd", "gnd"])
|
||||
|
||||
def place_gated_clk_bar_row(self, row):
|
||||
x_offset = self.control_x_offset
|
||||
|
||||
x_offset = self.place_util(self.clk_bar_inst, x_offset, row)
|
||||
x_offset = self.place_util(self.gated_clk_bar_inst, x_offset, row)
|
||||
|
||||
self.row_end_inst.append(self.gated_clk_bar_inst)
|
||||
|
||||
def route_gated_clk_bar(self):
|
||||
clkbuf_map = zip(["A"], ["clk_buf"])
|
||||
self.connect_vertical_bus(clkbuf_map, self.clk_bar_inst, self.input_bus)
|
||||
|
||||
out_pin = self.clk_bar_inst.get_pin("Z")
|
||||
out_pos = out_pin.center()
|
||||
in_pin = self.gated_clk_bar_inst.get_pin("A")
|
||||
in_pos = in_pin.center()
|
||||
self.add_zjog(out_pin.layer, out_pos, in_pos)
|
||||
self.add_via_stack_center(from_layer=out_pin.layer,
|
||||
to_layer=in_pin.layer,
|
||||
offset=in_pos)
|
||||
|
||||
|
||||
# This is the second gate over, so it needs to be on M3
|
||||
clkbuf_map = zip(["B"], ["cs"])
|
||||
self.connect_vertical_bus(clkbuf_map,
|
||||
self.gated_clk_bar_inst,
|
||||
self.input_bus,
|
||||
self.m2_stack[::-1])
|
||||
# The pin is on M1, so we need another via as well
|
||||
b_pin = self.gated_clk_bar_inst.get_pin("B")
|
||||
self.add_via_stack_center(from_layer=b_pin.layer,
|
||||
to_layer="m3",
|
||||
offset=b_pin.center())
|
||||
|
||||
# This is the second gate over, so it needs to be on M3
|
||||
self.route_output_to_bus_jogged(self.gated_clk_bar_inst,
|
||||
"gated_clk_bar")
|
||||
|
||||
def create_gated_clk_buf_row(self):
|
||||
self.gated_clk_buf_inst = self.add_inst(name="and2_gated_clk_buf",
|
||||
mod=self.and2)
|
||||
self.connect_inst(["clk_buf", "cs", "gated_clk_buf", "vdd", "gnd"])
|
||||
|
||||
def place_gated_clk_buf_row(self, row):
|
||||
x_offset = self.control_x_offset
|
||||
|
||||
x_offset = self.place_util(self.gated_clk_buf_inst, x_offset, row)
|
||||
|
||||
self.row_end_inst.append(self.gated_clk_buf_inst)
|
||||
|
||||
def route_gated_clk_buf(self):
|
||||
clkbuf_map = zip(["A", "B"], ["clk_buf", "cs"])
|
||||
self.connect_vertical_bus(clkbuf_map,
|
||||
self.gated_clk_buf_inst,
|
||||
self.input_bus)
|
||||
|
||||
clkbuf_map = zip(["Z"], ["gated_clk_buf"])
|
||||
self.connect_vertical_bus(clkbuf_map,
|
||||
self.gated_clk_buf_inst,
|
||||
self.input_bus,
|
||||
self.m2_stack[::-1])
|
||||
# The pin is on M1, so we need another via as well
|
||||
z_pin = self.gated_clk_buf_inst.get_pin("Z")
|
||||
self.add_via_stack_center(from_layer=z_pin.layer,
|
||||
to_layer="m2",
|
||||
offset=z_pin.center())
|
||||
|
||||
def create_dffs(self):
|
||||
self.ctrl_dff_inst=self.add_inst(name="ctrl_dffs",
|
||||
mod=self.ctrl_dff_array)
|
||||
inst_pins = self.input_list + self.dff_output_list + ["clk_buf"] + self.supply_list
|
||||
self.connect_inst(inst_pins)
|
||||
|
||||
def place_dffs(self):
|
||||
self.ctrl_dff_inst.place(vector(0, 0))
|
||||
|
||||
def route_dffs(self):
|
||||
if self.port_type == "rw":
|
||||
dff_out_map = zip(["dout_bar_0", "dout_bar_1", "dout_1"], ["cs", "we", "we_bar"])
|
||||
elif self.port_type == "r":
|
||||
dff_out_map = zip(["dout_bar_0", "dout_0"], ["cs", "cs_bar"])
|
||||
else:
|
||||
dff_out_map = zip(["dout_bar_0"], ["cs"])
|
||||
self.connect_vertical_bus(dff_out_map, self.ctrl_dff_inst, self.input_bus, self.m2_stack[::-1])
|
||||
|
||||
# Connect the clock rail to the other clock rail
|
||||
# by routing in the supply rail track to avoid channel conflicts
|
||||
in_pos = self.ctrl_dff_inst.get_pin("clk").uc()
|
||||
mid_pos = vector(in_pos.x, self.gated_clk_buf_inst.get_pin("vdd").cy() - self.m1_pitch)
|
||||
rail_pos = vector(self.input_bus["clk_buf"].cx(), mid_pos.y)
|
||||
self.add_wire(self.m1_stack, [in_pos, mid_pos, rail_pos])
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=rail_pos)
|
||||
|
||||
self.copy_layout_pin(self.ctrl_dff_inst, "din_0", "csb")
|
||||
if (self.port_type == "rw"):
|
||||
self.copy_layout_pin(self.ctrl_dff_inst, "din_1", "web")
|
||||
|
||||
def get_offset(self, row):
|
||||
""" Compute the y-offset and mirroring """
|
||||
y_off = row * self.and2.height
|
||||
if row % 2:
|
||||
y_off += self.and2.height
|
||||
mirror="MX"
|
||||
else:
|
||||
mirror="R0"
|
||||
|
||||
return (y_off, mirror)
|
||||
|
||||
def connect_output(self, inst, pin_name, out_name):
|
||||
""" Create an output pin on the right side from the pin of a given instance. """
|
||||
|
||||
out_pin = inst.get_pin(pin_name)
|
||||
out_pos = out_pin.center()
|
||||
right_pos = out_pos + vector(self.width - out_pin.cx(), 0)
|
||||
|
||||
self.add_via_stack_center(from_layer=out_pin.layer,
|
||||
to_layer="m2",
|
||||
offset=out_pos)
|
||||
self.add_layout_pin_segment_center(text=out_name,
|
||||
layer="m2",
|
||||
start=out_pos,
|
||||
end=right_pos)
|
||||
|
||||
def route_supplies(self):
|
||||
""" Add vdd and gnd to the instance cells """
|
||||
|
||||
pin_layer = self.dff.get_pin("vdd").layer
|
||||
supply_layer = self.supply_stack[2]
|
||||
|
||||
|
||||
# FIXME: We should be able to replace this with route_vertical_pins instead
|
||||
# but we may have to make the logic gates a separate module so that they
|
||||
# have row pins of the same width
|
||||
max_row_x_loc = max([inst.rx() for inst in self.row_end_inst])
|
||||
min_row_x_loc = self.control_x_offset
|
||||
|
||||
vdd_pin_locs = []
|
||||
gnd_pin_locs = []
|
||||
|
||||
last_via = None
|
||||
for inst in self.row_end_inst:
|
||||
pins = inst.get_pins("vdd")
|
||||
for pin in pins:
|
||||
if pin.layer == pin_layer:
|
||||
row_loc = pin.rc()
|
||||
pin_loc = vector(max_row_x_loc, pin.rc().y)
|
||||
vdd_pin_locs.append(pin_loc)
|
||||
last_via = self.add_via_stack_center(from_layer=pin_layer,
|
||||
to_layer=supply_layer,
|
||||
offset=pin_loc,
|
||||
min_area=True)
|
||||
self.add_path(pin_layer, [row_loc, pin_loc])
|
||||
|
||||
pins = inst.get_pins("gnd")
|
||||
for pin in pins:
|
||||
if pin.layer == pin_layer:
|
||||
row_loc = pin.rc()
|
||||
pin_loc = vector(min_row_x_loc, pin.rc().y)
|
||||
gnd_pin_locs.append(pin_loc)
|
||||
last_via = self.add_via_stack_center(from_layer=pin_layer,
|
||||
to_layer=supply_layer,
|
||||
offset=pin_loc,
|
||||
min_area=True)
|
||||
self.add_path(pin_layer, [row_loc, pin_loc])
|
||||
|
||||
if last_via:
|
||||
via_height=last_via.mod.second_layer_height
|
||||
via_width=last_via.mod.second_layer_width
|
||||
else:
|
||||
via_height=None
|
||||
via_width=0
|
||||
|
||||
min_y = min([x.y for x in vdd_pin_locs])
|
||||
max_y = max([x.y for x in vdd_pin_locs])
|
||||
bot_pos = vector(max_row_x_loc, min_y - 0.5 * via_height)
|
||||
top_pos = vector(max_row_x_loc, max_y + 0.5 * via_height)
|
||||
self.add_layout_pin_segment_center(text="vdd",
|
||||
layer=supply_layer,
|
||||
start=bot_pos,
|
||||
end=top_pos,
|
||||
width=via_width)
|
||||
|
||||
min_y = min([x.y for x in gnd_pin_locs])
|
||||
max_y = max([x.y for x in gnd_pin_locs])
|
||||
bot_pos = vector(min_row_x_loc, min_y - 0.5 * via_height)
|
||||
top_pos = vector(min_row_x_loc, max_y + 0.5 * via_height)
|
||||
self.add_layout_pin_segment_center(text="gnd",
|
||||
layer=supply_layer,
|
||||
start=bot_pos,
|
||||
end=top_pos,
|
||||
width=via_width)
|
||||
|
||||
self.copy_layout_pin(self.delay_inst, "gnd")
|
||||
self.copy_layout_pin(self.delay_inst, "vdd")
|
||||
|
||||
self.copy_layout_pin(self.ctrl_dff_inst, "gnd")
|
||||
self.copy_layout_pin(self.ctrl_dff_inst, "vdd")
|
||||
|
||||
def add_lvs_correspondence_points(self):
|
||||
""" This adds some points for easier debugging if LVS goes wrong.
|
||||
These should probably be turned off by default though, since extraction
|
||||
will show these as ports in the extracted netlist.
|
||||
"""
|
||||
# pin=self.clk_inv1.get_pin("Z")
|
||||
# self.add_label_pin(text="clk1_bar",
|
||||
# layer="m1",
|
||||
# offset=pin.ll(),
|
||||
# height=pin.height(),
|
||||
# width=pin.width())
|
||||
|
||||
# pin=self.clk_inv2.get_pin("Z")
|
||||
# self.add_label_pin(text="clk2",
|
||||
# layer="m1",
|
||||
# offset=pin.ll(),
|
||||
# height=pin.height(),
|
||||
# width=pin.width())
|
||||
|
||||
pin=self.delay_inst.get_pin("out")
|
||||
self.add_label_pin(text="out",
|
||||
layer=pin.layer,
|
||||
offset=pin.ll(),
|
||||
height=pin.height(),
|
||||
width=pin.width())
|
||||
|
||||
def graph_exclude_dffs(self):
|
||||
"""Exclude dffs from graph as they do not represent critical path"""
|
||||
|
||||
self.graph_inst_exclude.add(self.ctrl_dff_inst)
|
||||
if self.port_type=="rw" or self.port_type=="w":
|
||||
self.graph_inst_exclude.add(self.w_en_gate_inst)
|
||||
|
||||
def place_util(self, inst, x_offset, row):
|
||||
""" Utility to place a row and compute the next offset """
|
||||
|
||||
(y_offset, mirror) = self.get_offset(row)
|
||||
offset = vector(x_offset, y_offset)
|
||||
inst.place(offset, mirror)
|
||||
return x_offset + inst.width
|
||||
|
||||
def route_output_to_bus_jogged(self, inst, name):
|
||||
# Connect this at the bottom of the buffer
|
||||
out_pin = inst.get_pin("Z")
|
||||
out_pos = out_pin.center()
|
||||
mid1 = vector(out_pos.x, out_pos.y - 0.3 * inst.mod.height)
|
||||
mid2 = vector(self.input_bus[name].cx(), mid1.y)
|
||||
bus_pos = self.input_bus[name].center()
|
||||
self.add_wire(self.m2_stack[::-1], [out_pos, mid1, mid2, bus_pos])
|
||||
self.add_via_stack_center(from_layer=out_pin.layer,
|
||||
to_layer="m2",
|
||||
offset=out_pos)
|
||||
|
||||
def get_left_pins(self, name):
|
||||
"""
|
||||
Return the left side supply pins to connect to a vertical stripe.
|
||||
"""
|
||||
return(self.cntrl_dff_inst.get_pins(name) + self.delay_inst.get_pins(name))
|
||||
@@ -597,7 +597,7 @@ class hierarchical_decoder(design):
|
||||
for inst in all_insts:
|
||||
self.copy_layout_pin(inst, "vdd")
|
||||
self.copy_layout_pin(inst, "gnd")
|
||||
|
||||
|
||||
self.route_vertical_pins("vdd", self.and_inst, xside="rx",)
|
||||
self.route_vertical_pins("gnd", self.and_inst, xside="lx",)
|
||||
|
||||
|
||||
@@ -172,7 +172,7 @@ class local_bitcell_array(bitcell_base_array):
|
||||
if len(self.all_ports) > 1:
|
||||
wl_offset = vector(self.bitcell_array_inst.rx() + self.wl_array.width + driver_to_array_spacing,
|
||||
self.bitcell_array.get_replica_bottom() + self.wl_array.height + self.cell.height)
|
||||
self.wl_insts[1].place(wl_offset,
|
||||
self.wl_insts[1].place(wl_offset,
|
||||
mirror="XY")
|
||||
|
||||
self.height = self.bitcell_array.height
|
||||
|
||||
@@ -80,20 +80,20 @@ class nand2_dec(design):
|
||||
is_nchannel = True
|
||||
stack = 2
|
||||
is_cell = False
|
||||
return self.tr_r_on(self.nmos_width, is_nchannel, stack, is_cell)
|
||||
return self.tr_r_on(self.nmos_width, is_nchannel, stack, is_cell)
|
||||
|
||||
def get_input_capacitance(self):
|
||||
"""Input cap of input, passes width of gates to gate cap function"""
|
||||
return self.gate_c(self.nmos_width+self.pmos_width)
|
||||
|
||||
return self.gate_c(self.nmos_width+self.pmos_width)
|
||||
|
||||
def get_intrinsic_capacitance(self):
|
||||
"""Get the drain capacitances of the TXs in the gate."""
|
||||
nmos_stack = 2
|
||||
mult = 1
|
||||
nmos_drain_c = self.drain_c_(self.nmos_width*mult,
|
||||
nmos_drain_c = self.drain_c_(self.nmos_width*mult,
|
||||
nmos_stack,
|
||||
mult)
|
||||
pmos_drain_c = self.drain_c_(self.pmos_width*mult,
|
||||
pmos_drain_c = self.drain_c_(self.pmos_width*mult,
|
||||
1,
|
||||
mult)
|
||||
return nmos_drain_c + pmos_drain_c
|
||||
mult)
|
||||
return nmos_drain_c + pmos_drain_c
|
||||
|
||||
@@ -80,20 +80,20 @@ class nand3_dec(design):
|
||||
is_nchannel = True
|
||||
stack = 3
|
||||
is_cell = False
|
||||
return self.tr_r_on(self.nmos_width, is_nchannel, stack, is_cell)
|
||||
|
||||
return self.tr_r_on(self.nmos_width, is_nchannel, stack, is_cell)
|
||||
|
||||
def get_input_capacitance(self):
|
||||
"""Input cap of input, passes width of gates to gate cap function"""
|
||||
return self.gate_c(self.nmos_width+self.pmos_width)
|
||||
return self.gate_c(self.nmos_width+self.pmos_width)
|
||||
|
||||
def get_intrinsic_capacitance(self):
|
||||
"""Get the drain capacitances of the TXs in the gate."""
|
||||
nmos_stack = 3
|
||||
mult = 1
|
||||
nmos_drain_c = self.drain_c_(self.nmos_width*mult,
|
||||
nmos_drain_c = self.drain_c_(self.nmos_width*mult,
|
||||
nmos_stack,
|
||||
mult)
|
||||
pmos_drain_c = self.drain_c_(self.pmos_width*mult,
|
||||
pmos_drain_c = self.drain_c_(self.pmos_width*mult,
|
||||
1,
|
||||
mult)
|
||||
return nmos_drain_c + pmos_drain_c
|
||||
mult)
|
||||
return nmos_drain_c + pmos_drain_c
|
||||
|
||||
@@ -80,20 +80,20 @@ class nand4_dec(design):
|
||||
is_nchannel = True
|
||||
stack = 4
|
||||
is_cell = False
|
||||
return self.tr_r_on(self.nmos_width, is_nchannel, stack, is_cell)
|
||||
|
||||
return self.tr_r_on(self.nmos_width, is_nchannel, stack, is_cell)
|
||||
|
||||
def get_input_capacitance(self):
|
||||
"""Input cap of input, passes width of gates to gate cap function"""
|
||||
return self.gate_c(self.nmos_width+self.pmos_width)
|
||||
return self.gate_c(self.nmos_width+self.pmos_width)
|
||||
|
||||
def get_intrinsic_capacitance(self):
|
||||
"""Get the drain capacitances of the TXs in the gate."""
|
||||
nmos_stack = 4
|
||||
mult = 1
|
||||
nmos_drain_c = self.drain_c_(self.nmos_width*mult,
|
||||
nmos_drain_c = self.drain_c_(self.nmos_width*mult,
|
||||
nmos_stack,
|
||||
mult)
|
||||
pmos_drain_c = self.drain_c_(self.pmos_width*mult,
|
||||
pmos_drain_c = self.drain_c_(self.pmos_width*mult,
|
||||
1,
|
||||
mult)
|
||||
return nmos_drain_c + pmos_drain_c
|
||||
mult)
|
||||
return nmos_drain_c + pmos_drain_c
|
||||
|
||||
@@ -317,7 +317,7 @@ class pgate(design):
|
||||
contact_xoffset = nmos_pos.x + nmos.active_width \
|
||||
+ self.active_space
|
||||
# Allow an nimplant below it under the rail
|
||||
contact_yoffset = max(0.5 * self.implant_width + self.implant_enclose_active,
|
||||
contact_yoffset = max(0.5 * self.implant_width + self.implant_enclose_active,
|
||||
self.get_tx_insts("nmos")[0].by())
|
||||
contact_offset = vector(contact_xoffset, contact_yoffset)
|
||||
|
||||
|
||||
@@ -368,19 +368,19 @@ class pnand4(pgate):
|
||||
is_nchannel = True
|
||||
stack = 4
|
||||
is_cell = False
|
||||
return self.tr_r_on(self.nmos_width, is_nchannel, stack, is_cell)
|
||||
return self.tr_r_on(self.nmos_width, is_nchannel, stack, is_cell)
|
||||
|
||||
def get_input_capacitance(self):
|
||||
"""Input cap of input, passes width of gates to gate cap function"""
|
||||
return self.gate_c(self.nmos_width+self.pmos_width)
|
||||
|
||||
return self.gate_c(self.nmos_width+self.pmos_width)
|
||||
|
||||
def get_intrinsic_capacitance(self):
|
||||
"""Get the drain capacitances of the TXs in the gate."""
|
||||
nmos_stack = 4
|
||||
nmos_drain_c = self.drain_c_(self.nmos_width*self.tx_mults,
|
||||
nmos_drain_c = self.drain_c_(self.nmos_width*self.tx_mults,
|
||||
nmos_stack,
|
||||
self.tx_mults)
|
||||
pmos_drain_c = self.drain_c_(self.pmos_width*self.tx_mults,
|
||||
pmos_drain_c = self.drain_c_(self.pmos_width*self.tx_mults,
|
||||
1,
|
||||
self.tx_mults)
|
||||
return nmos_drain_c + pmos_drain_c
|
||||
self.tx_mults)
|
||||
return nmos_drain_c + pmos_drain_c
|
||||
|
||||
@@ -88,7 +88,7 @@ class port_address(design):
|
||||
|
||||
self.copy_layout_pin(self.row_decoder_inst, "vdd")
|
||||
self.copy_layout_pin(self.row_decoder_inst, "gnd")
|
||||
|
||||
|
||||
# Also connect the B input of the RBL and_dec to vdd
|
||||
if OPTS.local_array_size == 0:
|
||||
rbl_b_pin = self.rbl_driver_inst.get_pin("B")
|
||||
|
||||
@@ -51,5 +51,5 @@ class replica_bitcell_1port(bitcell_base):
|
||||
|
||||
def is_non_inverting(self):
|
||||
"""Return input to output polarity for module"""
|
||||
|
||||
|
||||
return False
|
||||
|
||||
@@ -52,5 +52,5 @@ class replica_bitcell_2port(bitcell_base):
|
||||
|
||||
def is_non_inverting(self):
|
||||
"""Return input to output polarity for module"""
|
||||
|
||||
|
||||
return False
|
||||
|
||||
@@ -12,7 +12,7 @@ from .bitcell_base import bitcell_base
|
||||
|
||||
class row_cap_bitcell_1port(bitcell_base):
|
||||
"""
|
||||
Row end cap cell.
|
||||
Row end cap cell.
|
||||
"""
|
||||
|
||||
def __init__(self, name="row_cap_bitcell_1port"):
|
||||
|
||||
@@ -12,7 +12,7 @@ from .bitcell_base import bitcell_base
|
||||
|
||||
class row_cap_bitcell_2port(bitcell_base):
|
||||
"""
|
||||
Row end cap cell.
|
||||
Row end cap cell.
|
||||
"""
|
||||
|
||||
def __init__(self, name="row_cap_bitcell_2port"):
|
||||
|
||||
@@ -73,48 +73,48 @@ class sense_amp(design):
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
"""Adds edges based on inputs/outputs. Overrides base class function."""
|
||||
self.add_graph_edges(graph, port_nets)
|
||||
|
||||
|
||||
def is_non_inverting(self):
|
||||
"""Return input to output polarity for module"""
|
||||
|
||||
|
||||
#FIXME: This only applied to bl/br -> dout and not s_en->dout
|
||||
return True
|
||||
return True
|
||||
|
||||
def get_on_resistance(self):
|
||||
"""On resistance of pinv, defined by single nmos"""
|
||||
is_nchannel = True
|
||||
stack = 1
|
||||
is_cell = False
|
||||
return self.tr_r_on(parameter["sa_inv_nmos_size"], is_nchannel, stack, is_cell)
|
||||
|
||||
is_cell = False
|
||||
return self.tr_r_on(parameter["sa_inv_nmos_size"], is_nchannel, stack, is_cell)
|
||||
|
||||
def get_input_capacitance(self):
|
||||
"""Input cap of input, passes width of gates to gate cap function"""
|
||||
return self.gate_c(parameter["sa_inv_nmos_size"])
|
||||
|
||||
return self.gate_c(parameter["sa_inv_nmos_size"])
|
||||
|
||||
def get_intrinsic_capacitance(self):
|
||||
"""Get the drain capacitances of the TXs in the gate."""
|
||||
stack = 1
|
||||
mult = 1
|
||||
# Add the inverter drain Cap and the bitline TX drain Cap
|
||||
nmos_drain_c = self.drain_c_(parameter["sa_inv_nmos_size"]*mult,
|
||||
nmos_drain_c = self.drain_c_(parameter["sa_inv_nmos_size"]*mult,
|
||||
stack,
|
||||
mult)
|
||||
pmos_drain_c = self.drain_c_(parameter["sa_inv_pmos_size"]*mult,
|
||||
pmos_drain_c = self.drain_c_(parameter["sa_inv_pmos_size"]*mult,
|
||||
stack,
|
||||
mult)
|
||||
|
||||
|
||||
bitline_pmos_size = 8
|
||||
bl_pmos_drain_c = self.drain_c_(drc("minwidth_tx")*bitline_pmos_size,
|
||||
bl_pmos_drain_c = self.drain_c_(drc("minwidth_tx")*bitline_pmos_size,
|
||||
stack,
|
||||
mult)
|
||||
mult)
|
||||
return nmos_drain_c + pmos_drain_c + bl_pmos_drain_c
|
||||
|
||||
def cacti_rc_delay(self, inputramptime, tf, vs1, vs2, rise, extra_param_dict):
|
||||
def cacti_rc_delay(self, inputramptime, tf, vs1, vs2, rise, extra_param_dict):
|
||||
""" Special RC delay function used by CACTI for sense amp delay
|
||||
"""
|
||||
import math
|
||||
|
||||
|
||||
c_senseamp = extra_param_dict['load']
|
||||
vdd = extra_param_dict['vdd']
|
||||
vdd = extra_param_dict['vdd']
|
||||
tau = c_senseamp/spice["sa_transconductance"]
|
||||
return tau*math.log(vdd/(0.1*vdd))
|
||||
|
||||
@@ -80,7 +80,7 @@ class sram():
|
||||
def save(self):
|
||||
""" Save all the output files while reporting time to do it as well. """
|
||||
|
||||
# Import this at the last minute so that the proper tech file
|
||||
# Import this at the last minute so that the proper tech file
|
||||
# is loaded and the right tools are selected
|
||||
import verify
|
||||
|
||||
|
||||
@@ -1094,7 +1094,7 @@ class sram_1bank(design, verilog, lef):
|
||||
self.add_dnwell(inflate=2.5)
|
||||
|
||||
# Route the supplies together and/or to the ring/stripes.
|
||||
# This is done with the original bbox since the escape routes need to
|
||||
# This is done with the original bbox since the escape routes need to
|
||||
# be outside of the ring for OpenLane
|
||||
rt = router_tech(self.supply_stack, 1)
|
||||
init_bbox = self.get_bbox(side="ring",
|
||||
|
||||
@@ -124,7 +124,7 @@ class wordline_driver_array(design):
|
||||
en_pin = self.add_layout_pin_segment_center(text="en",
|
||||
layer="m2",
|
||||
start=en_bottom_pos,
|
||||
end=en_top_pos)
|
||||
end=en_top_pos)
|
||||
|
||||
for row in range(self.rows):
|
||||
and_inst = self.wld_inst[row]
|
||||
|
||||
Reference in New Issue
Block a user