import debug import design from tech import drc from pinv import pinv import contact from bitcell_array import bitcell_array from ptx import ptx from vector import vector from globals import OPTS class replica_bitline(design.design): """ Generate a module that simulate the delay of control logic and bit line charging. Used for memory timing control """ def __init__(self, rows, name="replica_bitline"): design.design.__init__(self, name) g = reload(__import__(OPTS.delay_chain)) self.mod_delay_chain = getattr(g, OPTS.delay_chain) g = reload(__import__(OPTS.replica_bitcell)) self.mod_replica_bitcell = getattr(g, OPTS.replica_bitcell) c = reload(__import__(OPTS.bitcell)) self.mod_bitcell = getattr(c, OPTS.bitcell) for pin in ["en", "out", "vdd", "gnd"]: self.add_pin(pin) self.rows = rows self.create_modules() self.calculate_module_offsets() self.add_modules() self.route() self.add_layout_pins() self.add_lvs_correspondence_points() self.DRC_LVS() def calculate_module_offsets(self): """ Calculate all the module offsets """ # These aren't for instantiating, but we use them to get the dimensions self.poly_contact_offset = vector(0.5*contact.poly.width,0.5*contact.poly.height) # M1/M2 routing pitch is based on contacted pitch self.m1_pitch = max(contact.m1m2.width,contact.m1m2.height) + max(self.m1_space,self.m2_space) self.m2_pitch = max(contact.m2m3.width,contact.m2m3.height) + max(self.m2_space,self.m3_space) # This corrects the offset pitch difference between M2 and M1 self.offset_fix = vector(0.5*(self.m2_width-self.m1_width),0) # delay chain will be rotated 90, so move it over a width # we move it up a inv height just for some routing room self.rbl_inv_offset = vector(self.delay_chain.height, self.inv.width) # access TX goes right on top of inverter, leave space for an inverter which is # about the same as a TX. We'll need to add rails though. self.access_tx_offset = vector(1.25*self.inv.height,self.rbl_inv_offset.y) + vector(0,2.5*self.inv.width) self.delay_chain_offset = self.rbl_inv_offset + vector(0,4*self.inv.width) # Replica bitline and such are not rotated, but they must be placed far enough # away from the delay chain/inverter with space for three M2 tracks self.bitcell_offset = self.rbl_inv_offset + vector(2*self.m2_pitch, 0) + vector(0, self.bitcell.height + self.inv.width) self.rbl_offset = self.bitcell_offset self.height = self.rbl_offset.y + self.rbl.height + self.m2_pitch self.width = self.rbl_offset.x + self.bitcell.width def create_modules(self): """ Create modules for later instantiation """ self.bitcell = self.replica_bitcell = self.mod_replica_bitcell() self.add_mod(self.bitcell) # This is the replica bitline load column that is the height of our array self.rbl = bitcell_array(name="bitline_load", cols=1, rows=self.rows) self.add_mod(self.rbl) self.delay_chain = self.mod_delay_chain([1, 1, 1]) self.add_mod(self.delay_chain) self.inv = pinv() self.add_mod(self.inv) self.access_tx = ptx(tx_type="pmos") self.add_mod(self.access_tx) def add_modules(self): """ Add all of the module instances in the logical netlist """ # This is the threshold detect inverter on the output of the RBL self.rbl_inv_inst=self.add_inst(name="rbl_inv", mod=self.inv, offset=self.rbl_inv_offset+vector(0,self.inv.width), rotate=270, mirror="MX") self.connect_inst(["bl[0]", "out", "vdd", "gnd"]) self.tx_inst=self.add_inst(name="rbl_access_tx", mod=self.access_tx, offset=self.access_tx_offset, rotate=90) # D, G, S, B self.connect_inst(["vdd", "delayed_en", "bl[0]", "vdd"]) # add the well and poly contact self.dc_inst=self.add_inst(name="delay_chain", mod=self.delay_chain, offset=self.delay_chain_offset, rotate=90) self.connect_inst(["en", "delayed_en", "vdd", "gnd"]) self.rbc_inst=self.add_inst(name="bitcell", mod=self.replica_bitcell, offset=self.bitcell_offset, mirror="MX") self.connect_inst(["bl[0]", "br[0]", "delayed_en", "vdd", "gnd"]) self.rbl_inst=self.add_inst(name="load", mod=self.rbl, offset=self.rbl_offset) self.connect_inst(["bl[0]", "br[0]"] + ["gnd"]*self.rows + ["vdd", "gnd"]) def route(self): """ Connect all the signals together """ self.route_gnd() self.route_vdd() self.route_access_tx() def route_access_tx(self): # GATE ROUTE # 1. Add the poly contact and nwell enclosure # Determines the y-coordinate of where to place the gate input poly pin # (middle in between the pmos and nmos) poly_pin = self.tx_inst.get_pin("G") poly_offset = poly_pin.rc() # This centers the contact on the poly contact_offset = poly_offset.scale(0,1) + self.dc_inst.get_pin("out").bc().scale(1,0) self.add_contact_center(layers=("poly", "contact", "metal1"), offset=contact_offset) self.add_rect(layer="poly", offset=poly_pin.lr(), width=contact_offset.x-poly_offset.x, height=self.poly_width) nwell_offset = self.rbl_inv_offset + vector(-self.inv.height,self.inv.width) self.add_rect(layer="nwell", offset=nwell_offset, width=0.5*self.inv.height, height=self.delay_chain_offset.y-nwell_offset.y) # 2. Route delay chain output to access tx gate delay_en_offset = self.dc_inst.get_pin("out").bc() self.add_path("metal1", [delay_en_offset,contact_offset]) # 3. Route the mid-point of previous route to the bitcell WL # route bend of previous net to bitcell WL wl_offset = self.rbc_inst.get_pin("WL").lc() wl_mid = vector(contact_offset.x,wl_offset.y) self.add_path("metal1", [contact_offset, wl_mid, wl_offset]) # DRAIN ROUTE # Route the drain to the vdd rail drain_offset = self.tx_inst.get_pin("D").lc() inv_vdd_offset = self.rbl_inv_inst.get_pin("vdd").uc() vdd_offset = inv_vdd_offset.scale(1,0) + drain_offset.scale(0,1) self.add_path("metal1", [drain_offset, vdd_offset]) # SOURCE ROUTE # Route the source to the RBL inverter input source_offset = self.tx_inst.get_pin("S").bc() mid1 = source_offset.scale(1,0) + vector(0,self.rbl_inv_offset.y+self.inv.width+self.m2_pitch) inv_A_offset = self.rbl_inv_inst.get_pin("A").uc() mid2 = vector(inv_A_offset.x, mid1.y) self.add_path("metal1",[source_offset, mid1, mid2, inv_A_offset]) # Route the connection of the source route (mid2) to the RBL bitline (left) source_offset = mid2 # Route the M2 to the right of the vdd rail between rbl_inv and bitcell gnd_pin = self.rbl_inv_inst.get_pin("gnd").ll() mid1 = vector(gnd_pin.x+self.m2_pitch,source_offset.y) # Via will go halfway down from the bitcell bl_offset = self.rbc_inst.get_pin("BL").bc() via_offset = bl_offset - vector(0,0.5*self.inv.width) mid2 = vector(mid1.x,via_offset.y) # self.add_contact(layers=("metal1", "via1", "metal2"), # offset=via_offset - vector(0.5*self.m2_width,0.5*self.m1_width)) self.add_wire(("metal1","via1","metal2"),[source_offset,mid1,mid2,via_offset,bl_offset]) #self.add_path("metal2",[via_offset,bl_offset]) def route_vdd(self): # Add a rail in M2 that is to the right of the inverter gnd pin # The replica column may not fit in a single standard cell pitch, so add the vdd rail to the # right of it. vdd_start = vector(self.bitcell_offset.x + self.bitcell.width + self.m1_pitch,0) # It is the height of the entire RBL and bitcell self.add_layout_pin(text="vdd", layer="metal1", offset=vdd_start, width=self.m1_width, height=self.rbl.height+self.bitcell.height+2*self.inv.width+0.5*self.m1_width) # Connect the vdd pins of the bitcell load directly to vdd vdd_pins = self.rbl_inst.get_pins("vdd") for pin in vdd_pins: offset = vector(vdd_start.x,pin.by()) self.add_rect(layer="metal1", offset=offset, width=self.rbl_offset.x-vdd_start.x, height=self.m1_width) # Also connect the replica bitcell vdd pin to vdd pin = self.rbc_inst.get_pin("vdd") offset = vector(vdd_start.x,pin.by()) self.add_rect(layer="metal1", offset=offset, width=self.bitcell_offset.x-vdd_start.x, height=self.m1_width) # Add a second vdd pin. No need for full length. It is must connect at the next level. inv_vdd_offset = self.rbl_inv_inst.get_pin("vdd").ll() self.add_layout_pin(text="vdd", layer="metal1", offset=inv_vdd_offset.scale(1,0), width=self.m1_width, height=self.delay_chain_offset.y) def route_gnd(self): """ Route all signals connected to gnd """ # Add a rail in M1 from bottom to two along delay chain gnd_start = self.rbl_inv_inst.get_pin("gnd").ll() - self.offset_fix # It is the height of the entire RBL and bitcell self.add_rect(layer="metal2", offset=gnd_start, width=self.m2_width, height=self.rbl.height+self.bitcell.height+self.inv.width+self.m2_pitch) self.add_layout_pin(text="gnd", layer="metal1", offset=gnd_start.scale(1,0), width=self.m2_width, height=2*self.inv.width) # Connect the WL pins directly to gnd for row in range(self.rows): wl = "wl[{}]".format(row) pin = self.rbl_inst.get_pin(wl) offset = vector(gnd_start.x,pin.by()) self.add_rect(layer="metal1", offset=offset, width=self.rbl_offset.x-gnd_start.x, height=self.m1_width) self.add_via(layers=("metal1", "via1", "metal2"), offset=offset) # Add via for the delay chain offset = self.delay_chain_offset - vector(0.5*self.m1_width,0) - self.offset_fix self.add_via(layers=("metal1", "via1", "metal2"), offset=offset) # Add via for the inverter offset = self.rbl_inv_offset - vector(0.5*self.m1_width,contact.m1m2.height) - self.offset_fix self.add_via(layers=("metal1", "via1", "metal2"), offset=offset) # Connect the bitcell gnd pin to the rail gnd_pins = self.get_pins("gnd") gnd_start = gnd_pins[0].uc() rbl_gnd_pins = self.rbl_inst.get_pins("gnd") # Find the left most rail on M2 gnd_pin = None for pin in rbl_gnd_pins: if gnd_pin == None or (pin.layer=="metal2" and pin.lx()