OpenRAM/compiler/modules/control_logic.py

572 lines
22 KiB
Python

from math import log
import design
from tech import drc, parameter
import debug
import contact
from pinv import pinv
from pbuf import pbuf
from pand2 import pand2
from pnand2 import pnand2
from pinvbuf import pinvbuf
from dff_inv import dff_inv
from dff_inv_array import dff_inv_array
import math
from vector import vector
from globals import OPTS
class control_logic(design.design):
"""
Dynamically generated Control logic for the total SRAM circuit.
"""
def __init__(self, num_rows, words_per_row, port_type="rw"):
""" Constructor """
name = "control_logic_" + port_type
design.design.__init__(self, name)
debug.info(1, "Creating {}".format(name))
self.num_rows = num_rows
self.words_per_row = words_per_row
self.port_type = port_type
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.DRC_LVS()
def add_pins(self):
""" Add the pins to the control logic module. """
for pin in self.input_list + ["clk"]:
self.add_pin(pin,"INPUT")
for pin in self.output_list:
self.add_pin(pin,"OUTPUT")
self.add_pin("vdd","POWER")
self.add_pin("gnd","GROUND")
def add_modules(self):
""" Add all the required modules """
dff = dff_inv()
dff_height = dff.height
self.ctrl_dff_array = dff_inv_array(rows=self.num_control_signals,columns=1)
self.add_mod(self.ctrl_dff_array)
self.and2 = pand2(height=dff_height)
self.add_mod(self.and2)
self.nand2 = pnand2(height=dff_height)
self.add_mod(self.nand2)
# Special gates: inverters for buffering
# Size the clock for the number of rows (fanout)
clock_driver_size = max(1,int(self.num_rows/4))
self.clkbuf = pbuf(size=clock_driver_size, height=dff_height)
self.add_mod(self.clkbuf)
self.pbuf8 = pbuf(size=8, height=dff_height)
self.add_mod(self.pbuf8)
self.pbuf1 = pbuf(size=1, height=dff_height)
self.add_mod(self.pbuf1)
# self.inv = self.inv1 = pinv(size=1, height=dff_height)
# self.add_mod(self.inv1)
# self.inv2 = pinv(size=4, height=dff_height)
# self.add_mod(self.inv2)
# self.inv8 = pinv(size=16, height=dff_height)
# self.add_mod(self.inv8)
if (self.port_type == "rw") or (self.port_type == "r"):
from importlib import reload
c = reload(__import__(OPTS.replica_bitline))
replica_bitline = getattr(c, OPTS.replica_bitline)
delay_stages, delay_fanout = self.get_delay_chain_size()
bitcell_loads = int(math.ceil(self.num_rows / 2.0))
self.replica_bitline = replica_bitline(delay_stages, delay_fanout, bitcell_loads, name="replica_bitline_"+self.port_type)
self.add_mod(self.replica_bitline)
def get_delay_chain_size(self):
"""Determine the size of the delay chain used for the Sense Amp Enable """
# FIXME: These should be tuned according to the additional size parameters
delay_fanout = 3 # This can be anything >=2
# Delay stages Must be non-inverting
if self.words_per_row >= 8:
delay_stages = 8
elif self.words_per_row == 4:
delay_stages = 6
else:
delay_stages = 4
return (delay_stages, delay_fanout)
def setup_signal_busses(self):
""" Setup bus names, determine the size of the busses etc """
# List of input control signals
if self.port_type == "rw":
self.input_list = ["csb", "web"]
else:
self.input_list = ["csb"]
if self.port_type == "rw":
self.dff_output_list = ["cs_bar", "cs", "we_bar", "we"]
else:
self.dff_output_list = ["cs_bar", "cs"]
# list of output control signals (for making a vertical bus)
if self.port_type == "rw":
self.internal_bus_list = ["clk_buf", "gated_clk", "we", "we_bar", "cs"]
else:
self.internal_bus_list = ["clk_buf", "gated_clk", "cs"]
# leave space for the bus plus one extra space
self.internal_bus_width = (len(self.internal_bus_list)+1)*self.m2_pitch
# Outputs to the bank
if self.port_type == "r":
self.output_list = ["s_en", "p_en"]
elif self.port_type == "w":
self.output_list = ["w_en"]
else:
self.output_list = ["s_en", "w_en", "p_en"]
self.output_list.append("clk_buf")
self.supply_list = ["vdd", "gnd"]
def route_rails(self):
""" Add the input signal inverted tracks """
height = self.control_logic_center.y - self.m2_pitch
offset = vector(self.ctrl_dff_array.width,0)
self.rail_offsets = self.create_vertical_bus("metal2", self.m2_pitch, offset, self.internal_bus_list, height)
def create_instances(self):
""" Create all the instances """
self.create_dffs()
self.create_clk_rows()
if (self.port_type == "rw") or (self.port_type == "w"):
self.create_we_row()
if (self.port_type == "rw") or (self.port_type == "r"):
self.create_pen_row()
self.create_sen_row()
self.create_rbl()
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()
row = 0
# Add the logic on the right of the bus
self.place_clkbuf_row(row=row)
row += 1
self.place_gated_clk_row(row=row)
row += 1
if (self.port_type == "rw") or (self.port_type == "w"):
self.place_we_row(row=row)
height = self.w_en_inst.uy()
control_center_y = self.w_en_inst.uy()
row += 1
if (self.port_type == "rw") or (self.port_type == "r"):
self.place_pen_row(row=row)
self.place_sen_row(row=row+1)
self.place_rbl(row=row+2)
height = self.rbl_inst.uy()
control_center_y = self.rbl_inst.by()
# 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
if (self.port_type == "rw") or (self.port_type == "r"):
self.width = max(self.rbl_inst.rx(), max([inst.rx() for inst in self.row_end_inst])) + self.m2_pitch
else:
self.width = max([inst.rx() for inst in self.row_end_inst]) + self.m2_pitch
def route_all(self):
""" Routing between modules """
self.route_rails()
self.route_dffs()
if (self.port_type == "rw") or (self.port_type == "w"):
self.route_wen()
if (self.port_type == "rw") or (self.port_type == "r"):
self.route_rbl()
self.route_sen()
self.route_clk()
self.route_supply()
def create_rbl(self):
""" Create the replica bitline """
self.rbl_inst=self.add_inst(name="replica_bitline",
mod=self.replica_bitline)
self.connect_inst(["pre_p_en", "pre_s_en", "vdd", "gnd"])
def place_rbl(self,row):
""" Place the replica bitline """
y_off = row * self.nand2.height + 2*self.m1_pitch
# Add the RBL above the rows
# Add to the right of the control rows and routing channel
self.replica_bitline_offset = vector(0, y_off)
self.rbl_inst.place(self.replica_bitline_offset)
def create_clk_rows(self):
""" Create the multistage and gated clock buffer """
self.clkbuf_inst = self.add_inst(name="clkbuf",
mod=self.clkbuf)
self.connect_inst(["clk","clk_buf","vdd","gnd"])
self.gated_clk_inst = self.add_inst(name="gated_clkbuf",
mod=self.nand2)
self.connect_inst(["cs","clk_buf","gated_clk","vdd","gnd"])
def place_clkbuf_row(self,row):
""" Place the multistage clock buffer below the control flops """
x_off = self.ctrl_dff_array.width + self.internal_bus_width
(y_off,mirror)=self.get_offset(row)
offset = vector(x_off,y_off)
self.clkbuf_inst.place(offset)
self.row_end_inst.append(self.clkbuf_inst)
def place_gated_clk_row(self,row):
""" Place the gated clk logic below the control flops """
x_off = self.ctrl_dff_array.width + self.internal_bus_width
(y_off,mirror)=self.get_offset(row)
offset = vector(x_off,y_off)
self.gated_clk_inst.place(offset)
self.row_end_inst.append(self.gated_clk_inst)
def create_pen_row(self):
# input: gated_clk, we_bar, output: pre_p_en
self.pre_p_en_inst=self.add_inst(name="and2_pre_p_en",
mod=self.and2)
self.connect_inst(["gated_clk", "we_bar", "pre_p_en", "vdd", "gnd"])
# input: pre_p_en, output: p_en
self.p_en_inst=self.add_inst(name="buf_p_en",
mod=self.pbuf8)
self.connect_inst(["pre_p_en", "p_en", "vdd", "gnd"])
def place_pen_row(self,row):
x_off = self.ctrl_dff_array.width + self.internal_bus_width
(y_off,mirror)=self.get_offset(row)
self.pre_p_en_offset = vector(x_off, y_off)
self.pre_p_en_inst.place(offset=self.pre_p_en_offset,
mirror=mirror)
x_off += self.and2.width
self.row_end_inst.append(self.pre_p_en_inst)
def create_sen_row(self):
""" Create the sense enable buffer. """
# BUFFER FOR S_EN
# input: pre_s_en, output: s_en
self.s_en_inst=self.add_inst(name="buf_s_en",
mod=self.pbuf8)
self.connect_inst(["pre_s_en", "s_en", "vdd", "gnd"])
def place_sen_row(self,row):
"""
The sense enable buffer gets placed to the far right of the
row.
"""
x_off = self.ctrl_dff_array.width + self.internal_bus_width
(y_off,mirror)=self.get_offset(row)
self.s_en_offset = vector(x_off, y_off)
self.s_en_inst.place(offset=self.s_en_offset,
mirror=mirror)
self.row_end_inst.append(self.s_en_inst)
def route_dffs(self):
""" Route the input inverters """
if self.port_type == "r":
control_inputs = ["cs"]
else:
control_inputs = ["cs", "we"]
dff_out_map = zip(["dout_bar_{}".format(i) for i in range(2*self.num_control_signals - 1)], control_inputs)
self.connect_vertical_bus(dff_out_map, self.ctrl_dff_inst, self.rail_offsets)
# Connect the clock rail to the other clock rail
in_pos = self.ctrl_dff_inst.get_pin("clk").uc()
mid_pos = in_pos + vector(0,2*self.m2_pitch)
rail_pos = vector(self.rail_offsets["clk_buf"].x, mid_pos.y)
self.add_wire(("metal1","via1","metal2"),[in_pos, mid_pos, rail_pos])
self.add_via_center(layers=("metal1","via1","metal2"),
offset=rail_pos,
rotate=90)
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 create_dffs(self):
""" Add the three input DFFs (with inverters) """
self.ctrl_dff_inst=self.add_inst(name="ctrl_dffs",
mod=self.ctrl_dff_array)
self.connect_inst(self.input_list + self.dff_output_list + ["clk_buf"] + self.supply_list)
def place_dffs(self):
""" Place the input DFFs (with inverters) """
self.ctrl_dff_inst.place(vector(0,0))
def get_offset(self,row):
""" Compute the y-offset and mirroring """
y_off = row*self.nand2.height
if row % 2:
y_off += self.nand2.height
mirror="MX"
else:
mirror="R0"
return (y_off,mirror)
def create_we_row(self):
# input: we, gated_clk output: pre_w_en
if self.port_type == "rw":
self.pre_w_en_inst = self.add_inst(name="and_pre_w_en",
mod=self.and2)
self.connect_inst(["gated_clk", "we", "pre_w_en", "vdd", "gnd"])
input_name = "pre_w_en"
else:
# No we signal is needed for write-only ports
input_name = "gated_clk"
# BUFFER FOR W_EN
self.w_en_inst = self.add_inst(name="w_en_buf",
mod=self.pbuf8)
self.connect_inst([input_name, "w_en", "vdd", "gnd"])
def place_we_row(self,row):
x_off = self.ctrl_dff_inst.width + self.internal_bus_width
(y_off,mirror)=self.get_offset(row)
if self.port_type == "rw":
pre_w_en_offset = vector(x_off, y_off)
self.pre_w_en_inst.place(offset=pre_w_en_offset,
mirror=mirror)
x_off += self.nand2.width
w_en_offset = vector(x_off, y_off)
self.w_en_inst.place(offset=w_en_offset,
mirror=mirror)
self.row_end_inst.append(self.w_en_inst)
def route_rbl(self):
""" Connect the logic for the rbl_in generation """
# Connect the NAND gate inputs to the bus
pre_p_en_in_map = zip(["A", "B"], ["gated_clk", "we_bar"])
self.connect_vertical_bus(pre_p_en_in_map, self.pre_p_en_inst, self.rail_offsets)
# Connect the output of the precharge enable to the RBL input
pre_p_en_out_pos = self.pre_p_en_inst.get_pin("Z").center()
rbl_in_pos = self.rbl_inst.get_pin("en").center()
mid1 = vector(rbl_in_pos.x,pre_p_en_out_pos.y)
self.add_wire(("metal3","via2","metal2"),[pre_p_en_out_pos,mid1,rbl_in_pos])
self.add_via_center(layers=("metal1","via1","metal2"),
offset=pre_p_en_out_pos,
rotate=90)
self.add_via_center(layers=("metal2","via2","metal3"),
offset=pre_p_en_out_pos,
rotate=90)
def connect_rail_from_right(self,inst, pin, rail):
""" Helper routine to connect an unrotated/mirrored oriented instance to the rails """
in_pos = inst.get_pin(pin).center()
rail_pos = vector(self.rail_offsets[rail].x, in_pos.y)
self.add_wire(("metal1","via1","metal2"),[in_pos, rail_pos])
self.add_via_center(layers=("metal1","via1","metal2"),
offset=rail_pos,
rotate=90)
def connect_rail_from_right_m2m3(self,inst, pin, rail):
""" Helper routine to connect an unrotated/mirrored oriented instance to the rails """
in_pos = inst.get_pin(pin).center()
rail_pos = vector(self.rail_offsets[rail].x, in_pos.y)
self.add_wire(("metal3","via2","metal2"),[in_pos, rail_pos])
# Bring it up to M2 for M2/M3 routing
self.add_via_center(layers=("metal1","via1","metal2"),
offset=in_pos,
rotate=90)
self.add_via_center(layers=("metal2","via2","metal3"),
offset=in_pos,
rotate=90)
self.add_via_center(layers=("metal2","via2","metal3"),
offset=rail_pos,
rotate=90)
def connect_rail_from_left(self,inst, pin, rail):
""" Helper routine to connect an unrotated/mirrored oriented instance to the rails """
in_pos = inst.get_pin(pin).lc()
rail_pos = vector(self.rail_offsets[rail].x, in_pos.y)
self.add_wire(("metal1","via1","metal2"),[in_pos, rail_pos])
self.add_via_center(layers=("metal1","via1","metal2"),
offset=rail_pos,
rotate=90)
def connect_rail_from_left_m2m3(self,inst, pin, rail):
""" Helper routine to connect an unrotated/mirrored oriented instance to the rails """
in_pos = inst.get_pin(pin).lc()
rail_pos = vector(self.rail_offsets[rail].x, in_pos.y)
self.add_wire(("metal3","via2","metal2"),[in_pos, rail_pos])
self.add_via_center(layers=("metal2","via2","metal3"),
offset=in_pos,
rotate=90)
self.add_via_center(layers=("metal2","via2","metal3"),
offset=rail_pos,
rotate=90)
def route_wen(self):
if self.port_type == "rw":
wen_map = zip(["A", "B"], ["gated_clk", "we"])
self.connect_vertical_bus(wen_map, self.pre_w_en_inst, self.rail_offsets)
self.add_path("metal1",[self.pre_w_en_inst.get_pin("Z").center(), self.w_en_inst.get_pin("A").center()])
else:
wen_map = zip(["A"], ["gated_clk"])
self.connect_vertical_bus(wen_map, self.w_en_inst, self.rail_offsets)
self.connect_output(self.w_en_inst, "Z", "w_en")
def route_sen(self):
rbl_out_pos = self.rbl_inst.get_pin("out").bc()
in_pos = self.s_en_inst.get_pin("A").lc()
mid1 = vector(rbl_out_pos.x,in_pos.y)
self.add_wire(("metal1","via1","metal2"),[rbl_out_pos,mid1,in_pos])
self.connect_output(self.s_en_inst, "Z", "s_en")
def route_clk(self):
""" Route the clk and clk_buf_bar signal internally """
clk_pin = self.clkbuf_inst.get_pin("A")
self.add_layout_pin_segment_center(text="clk",
layer="metal2",
start=clk_pin.bc(),
end=clk_pin.bc().scale(1,0))
clkbuf_map = zip(["Z"], ["clk_buf"])
self.connect_vertical_bus(clkbuf_map, self.clkbuf_inst, self.rail_offsets, ("metal3", "via2", "metal2"))
clkbuf_map = zip(["Z"], ["gated_clk"])
self.connect_vertical_bus(clkbuf_map, self.gated_clk_inst, self.rail_offsets, ("metal3", "via2", "metal2"))
self.connect_output(self.clkbuf_inst, "Z", "clk_buf")
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)
right_pos=out_pin.center() + vector(self.width-out_pin.cx(),0)
self.add_layout_pin_segment_center(text=out_name,
layer="metal1",
start=out_pin.center(),
end=right_pos)
def route_supply(self):
""" Add vdd and gnd to the instance cells """
max_row_x_loc = max([inst.rx() for inst in self.row_end_inst])
for inst in self.row_end_inst:
pins = inst.get_pins("vdd")
for pin in pins:
if pin.layer == "metal1":
row_loc = pin.rc()
pin_loc = vector(max_row_x_loc, pin.rc().y)
self.add_power_pin("vdd", pin_loc)
self.add_path("metal1", [row_loc, pin_loc])
pins = inst.get_pins("gnd")
for pin in pins:
if pin.layer == "metal1":
row_loc = pin.rc()
pin_loc = vector(max_row_x_loc, pin.rc().y)
self.add_power_pin("gnd", pin_loc)
self.add_path("metal1", [row_loc, pin_loc])
if (self.port_type == "rw") or (self.port_type == "r"):
self.copy_layout_pin(self.rbl_inst,"gnd")
self.copy_layout_pin(self.rbl_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="metal1",
# offset=pin.ll(),
# height=pin.height(),
# width=pin.width())
# pin=self.clk_inv2.get_pin("Z")
# self.add_label_pin(text="clk2",
# layer="metal1",
# offset=pin.ll(),
# height=pin.height(),
# width=pin.width())
pin=self.rbl_inst.get_pin("out")
self.add_label_pin(text="out",
layer=pin.layer,
offset=pin.ll(),
height=pin.height(),
width=pin.width())