2019-04-26 21:21:50 +02:00
|
|
|
# See LICENSE for licensing information.
|
|
|
|
|
#
|
2019-06-14 17:43:41 +02:00
|
|
|
# Copyright (c) 2016-2019 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.
|
2019-04-26 21:21:50 +02:00
|
|
|
#
|
2018-07-10 23:17:09 +02:00
|
|
|
import sys
|
|
|
|
|
from tech import drc, spice
|
|
|
|
|
import debug
|
|
|
|
|
from math import log,sqrt,ceil
|
|
|
|
|
import datetime
|
|
|
|
|
import getpass
|
2018-08-21 18:28:07 +02:00
|
|
|
import numpy as np
|
2018-07-10 23:17:09 +02:00
|
|
|
from vector import vector
|
|
|
|
|
from globals import OPTS, print_time
|
|
|
|
|
|
2018-07-12 19:30:45 +02:00
|
|
|
from sram_base import sram_base
|
2018-07-10 23:17:09 +02:00
|
|
|
from bank import bank
|
2018-12-06 20:58:57 +01:00
|
|
|
from contact import m2m3
|
2018-07-10 23:17:09 +02:00
|
|
|
from dff_buf_array import dff_buf_array
|
|
|
|
|
from dff_array import dff_array
|
|
|
|
|
|
|
|
|
|
|
2018-07-12 19:30:45 +02:00
|
|
|
class sram_1bank(sram_base):
|
2018-07-10 23:17:09 +02:00
|
|
|
"""
|
2018-07-12 19:30:45 +02:00
|
|
|
Procedures specific to a one bank SRAM.
|
2018-07-10 23:17:09 +02:00
|
|
|
"""
|
2018-09-04 19:47:24 +02:00
|
|
|
def __init__(self, name, sram_config):
|
|
|
|
|
sram_base.__init__(self, name, sram_config)
|
2019-01-11 23:15:16 +01:00
|
|
|
|
2018-08-27 23:18:32 +02:00
|
|
|
def create_modules(self):
|
2018-07-10 23:17:09 +02:00
|
|
|
"""
|
2018-08-27 23:18:32 +02:00
|
|
|
This adds the modules for a single bank SRAM with control
|
|
|
|
|
logic.
|
|
|
|
|
"""
|
|
|
|
|
|
2018-08-28 01:42:48 +02:00
|
|
|
self.bank_inst=self.create_bank(0)
|
2018-08-27 23:18:32 +02:00
|
|
|
|
2018-11-14 01:05:22 +01:00
|
|
|
self.control_logic_insts = self.create_control_logic()
|
2018-08-27 23:18:32 +02:00
|
|
|
|
2018-11-14 01:05:22 +01:00
|
|
|
self.row_addr_dff_insts = self.create_row_addr_dff()
|
2018-08-27 23:18:32 +02:00
|
|
|
|
|
|
|
|
if self.col_addr_dff:
|
2018-11-14 01:05:22 +01:00
|
|
|
self.col_addr_dff_insts = self.create_col_addr_dff()
|
2018-08-27 23:18:32 +02:00
|
|
|
|
2018-11-14 01:05:22 +01:00
|
|
|
self.data_dff_insts = self.create_data_dff()
|
2019-07-03 22:17:12 +02:00
|
|
|
|
2019-07-19 23:58:37 +02:00
|
|
|
if self.write_size is not None:
|
2019-07-04 19:34:14 +02:00
|
|
|
self.wmask_dff_insts = self.create_wmask_dff()
|
2019-07-04 20:14:32 +02:00
|
|
|
|
2018-08-28 01:42:48 +02:00
|
|
|
|
2018-11-15 02:05:23 +01:00
|
|
|
def place_instances(self):
|
2018-08-27 23:18:32 +02:00
|
|
|
"""
|
2018-11-15 02:05:23 +01:00
|
|
|
This places the instances for a single bank SRAM with control
|
|
|
|
|
logic and up to 2 ports.
|
2018-07-10 23:17:09 +02:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# No orientation or offset
|
2018-08-27 23:18:32 +02:00
|
|
|
self.place_bank(self.bank_inst, [0, 0], 1, 1)
|
2018-07-10 23:17:09 +02:00
|
|
|
|
2018-08-14 19:36:35 +02:00
|
|
|
# The control logic is placed such that the vertical center (between the delay/RBL and
|
|
|
|
|
# the actual control logic is aligned with the vertical center of the bank (between
|
2018-08-14 19:09:41 +02:00
|
|
|
# the sense amps/column mux and cell array)
|
2018-08-14 19:36:35 +02:00
|
|
|
# The x-coordinate is placed to allow a single clock wire (plus an extra pitch)
|
|
|
|
|
# up to the row address DFFs.
|
2018-11-15 02:05:23 +01:00
|
|
|
control_pos = [None]*len(self.all_ports)
|
|
|
|
|
row_addr_pos = [None]*len(self.all_ports)
|
|
|
|
|
col_addr_pos = [None]*len(self.all_ports)
|
|
|
|
|
data_pos = [None]*len(self.all_ports)
|
2019-07-04 19:34:14 +02:00
|
|
|
wmask_pos = [None]*len(self.all_ports)
|
2018-11-15 02:05:23 +01:00
|
|
|
|
2018-11-29 18:54:29 +01:00
|
|
|
# This is M2 pitch even though it is on M1 to help stem via spacings on the trunk
|
|
|
|
|
# The M1 pitch is for supply rail spacings
|
|
|
|
|
max_gap_size = self.m2_pitch*max(self.word_size+1,self.col_addr_size+1) + 2*self.m1_pitch
|
|
|
|
|
|
2018-11-15 02:05:23 +01:00
|
|
|
# Port 0
|
|
|
|
|
port = 0
|
2018-11-29 18:54:29 +01:00
|
|
|
|
2019-07-03 22:17:12 +02:00
|
|
|
# Add the data flops below the bank to the right of the lower-left of bank array
|
|
|
|
|
# This relies on the lower-left of the array of the bank
|
|
|
|
|
# decoder in upper left, bank in upper right, sensing in lower right.
|
|
|
|
|
# These flops go below the sensing and leave a gap to channel route to the
|
|
|
|
|
# sense amps.
|
|
|
|
|
if port in self.write_ports:
|
|
|
|
|
data_pos[port] = vector(self.bank.bank_array_ll.x,
|
|
|
|
|
-max_gap_size - self.dff.height)
|
|
|
|
|
self.data_dff_insts[port].place(data_pos[port])
|
2019-08-14 20:45:22 +02:00
|
|
|
|
2019-07-03 22:17:12 +02:00
|
|
|
else:
|
|
|
|
|
data_pos[port] = vector(self.bank.bank_array_ll.x,0)
|
|
|
|
|
|
2019-08-14 20:45:22 +02:00
|
|
|
# Add the write mask flops below the din flops.
|
|
|
|
|
if self.write_size is not None:
|
|
|
|
|
if port in self.write_ports:
|
|
|
|
|
wmask_pos[port] = vector(self.bank.bank_array_ll.x,
|
|
|
|
|
- 2*max_gap_size - 2*self.dff.height)
|
|
|
|
|
self.wmask_dff_insts[port].place(wmask_pos[port])
|
|
|
|
|
else:
|
|
|
|
|
wmask_pos[port] = vector(self.bank.bank_array_ll.x, 0)
|
|
|
|
|
|
2019-07-03 22:17:12 +02:00
|
|
|
# Add the col address flops below the bank to the left of the lower-left of bank array
|
|
|
|
|
if self.col_addr_dff:
|
|
|
|
|
col_addr_pos[port] = vector(self.bank.bank_array_ll.x - self.col_addr_dff_insts[port].width - self.bank.m2_gap,
|
|
|
|
|
-max_gap_size - self.col_addr_dff_insts[port].height)
|
|
|
|
|
self.col_addr_dff_insts[port].place(col_addr_pos[port])
|
|
|
|
|
else:
|
|
|
|
|
col_addr_pos[port] = vector(self.bank.bank_array_ll.x,0)
|
|
|
|
|
|
2018-11-29 18:54:29 +01:00
|
|
|
# This includes 2 M2 pitches for the row addr clock line.
|
2018-11-15 02:05:23 +01:00
|
|
|
control_pos[port] = vector(-self.control_logic_insts[port].width - 2*self.m2_pitch,
|
2019-07-16 18:04:58 +02:00
|
|
|
self.bank.bank_array_ll.y - self.control_logic_insts[port].mod.control_logic_center.y - 2*self.bank.m2_gap )
|
2018-11-15 02:05:23 +01:00
|
|
|
self.control_logic_insts[port].place(control_pos[port])
|
|
|
|
|
|
|
|
|
|
# The row address bits are placed above the control logic aligned on the right.
|
2018-11-19 17:41:26 +01:00
|
|
|
x_offset = self.control_logic_insts[port].rx() - self.row_addr_dff_insts[port].width
|
2019-07-16 18:04:58 +02:00
|
|
|
# It is above the control logic but below the top of the bitcell array
|
|
|
|
|
y_offset = max(self.control_logic_insts[port].uy(), self.bank_inst.uy() - self.row_addr_dff_insts[port].height)
|
2018-11-19 17:41:26 +01:00
|
|
|
row_addr_pos[port] = vector(x_offset, y_offset)
|
2018-11-15 02:05:23 +01:00
|
|
|
self.row_addr_dff_insts[port].place(row_addr_pos[port])
|
|
|
|
|
|
|
|
|
|
# Add the col address flops below the bank to the left of the lower-left of bank array
|
|
|
|
|
if self.col_addr_dff:
|
2018-11-29 00:30:52 +01:00
|
|
|
col_addr_pos[port] = vector(self.bank.bank_array_ll.x - self.col_addr_dff_insts[port].width - self.bank.m2_gap,
|
2018-11-29 18:54:29 +01:00
|
|
|
-max_gap_size - self.col_addr_dff_insts[port].height)
|
2018-11-15 02:05:23 +01:00
|
|
|
self.col_addr_dff_insts[port].place(col_addr_pos[port])
|
|
|
|
|
|
|
|
|
|
# Add the data flops below the bank to the right of the lower-left of bank array
|
|
|
|
|
# This relies on the lower-left of the array of the bank
|
|
|
|
|
# decoder in upper left, bank in upper right, sensing in lower right.
|
|
|
|
|
# These flops go below the sensing and leave a gap to channel route to the
|
|
|
|
|
# sense amps.
|
|
|
|
|
if port in self.write_ports:
|
|
|
|
|
data_pos[port] = vector(self.bank.bank_array_ll.x,
|
2018-11-29 18:54:29 +01:00
|
|
|
-max_gap_size - self.data_dff_insts[port].height)
|
2018-11-15 02:05:23 +01:00
|
|
|
self.data_dff_insts[port].place(data_pos[port])
|
|
|
|
|
|
2019-08-14 20:45:22 +02:00
|
|
|
# # Add the write mask flops below the din flops.
|
|
|
|
|
# if self.write_size is not None:
|
|
|
|
|
# if port in self.write_ports:
|
|
|
|
|
# wmask_pos[port] = vector(self.bank.bank_array_ll.x,
|
|
|
|
|
# -max_gap_size - self.wmask_dff_insts[port].height)
|
|
|
|
|
# # wmask_pos[port] = vector(self.bank.bank_array_ll.x - self.control_logic_insts[port].width,
|
|
|
|
|
# # -max_gap_size - self.wmask_dff_insts[port].height)
|
|
|
|
|
# self.wmask_dff_insts[port].place(wmask_pos[port])
|
2019-07-04 19:34:14 +02:00
|
|
|
|
2018-11-15 02:05:23 +01:00
|
|
|
|
2018-11-15 02:11:23 +01:00
|
|
|
if len(self.all_ports)>1:
|
|
|
|
|
# Port 1
|
|
|
|
|
port = 1
|
2018-11-29 18:54:29 +01:00
|
|
|
|
2018-11-15 02:11:23 +01:00
|
|
|
# Add the data flops above the bank to the left of the upper-right of bank array
|
|
|
|
|
# This relies on the upper-right of the array of the bank
|
|
|
|
|
# decoder in upper left, bank in upper right, sensing in lower right.
|
|
|
|
|
# These flops go below the sensing and leave a gap to channel route to the
|
2019-07-03 22:17:12 +02:00
|
|
|
# sense amps.
|
2018-11-15 02:11:23 +01:00
|
|
|
if port in self.write_ports:
|
|
|
|
|
data_pos[port] = vector(self.bank.bank_array_ur.x - self.data_dff_insts[port].width,
|
2019-07-03 22:17:12 +02:00
|
|
|
self.bank.height + max_gap_size + self.dff.height)
|
2018-11-15 02:11:23 +01:00
|
|
|
self.data_dff_insts[port].place(data_pos[port], mirror="MX")
|
2019-07-04 19:34:14 +02:00
|
|
|
|
2019-08-14 20:45:22 +02:00
|
|
|
# Add the write mask flops below the din flops.
|
|
|
|
|
if self.write_size is not None:
|
|
|
|
|
if port in self.write_ports:
|
|
|
|
|
wmask_pos[port] = vector(self.bank.bank_array_ur.x - self.data_dff_insts[port].width,
|
|
|
|
|
self.bank.height + 2*max_gap_size + 2*self.dff.height)
|
|
|
|
|
self.wmask_dff_insts[port].place(wmask_pos[port], mirror="MX")
|
|
|
|
|
|
2019-07-04 19:34:14 +02:00
|
|
|
# Add the write mask flops to the left of the din flops.
|
2019-07-19 23:58:37 +02:00
|
|
|
if self.write_size is not None:
|
2019-07-04 19:34:14 +02:00
|
|
|
if port in self.write_ports:
|
|
|
|
|
wmask_pos[port] = vector(self.bank.bank_array_ur.x - self.data_dff_insts[port].width,
|
|
|
|
|
self.bank.height + max_gap_size + self.data_dff_insts[port].height)
|
|
|
|
|
self.wmask_dff_insts[port].place(wmask_pos[port], mirror="MX")
|
2019-07-03 22:17:12 +02:00
|
|
|
else:
|
|
|
|
|
data_pos[port] = self.bank_inst.ur()
|
|
|
|
|
|
|
|
|
|
# Add the col address flops above the bank to the right of the upper-right of bank array
|
|
|
|
|
if self.col_addr_dff:
|
|
|
|
|
col_addr_pos[port] = vector(self.bank.bank_array_ur.x + self.bank.m2_gap,
|
|
|
|
|
self.bank.height + max_gap_size + self.dff.height)
|
|
|
|
|
self.col_addr_dff_insts[port].place(col_addr_pos[port], mirror="MX")
|
|
|
|
|
else:
|
|
|
|
|
col_addr_pos[port] = self.bank_inst.ur()
|
2018-08-27 23:18:32 +02:00
|
|
|
|
2019-07-03 22:17:12 +02:00
|
|
|
# This includes 2 M2 pitches for the row addr clock line
|
|
|
|
|
control_pos[port] = vector(self.bank_inst.rx() + self.control_logic_insts[port].width + 2*self.m2_pitch,
|
2019-07-16 18:04:58 +02:00
|
|
|
self.bank.bank_array_ur.y + self.control_logic_insts[port].height -
|
|
|
|
|
(self.control_logic_insts[port].height - self.control_logic_insts[port].mod.control_logic_center.y)
|
|
|
|
|
+ 2*self.bank.m2_gap)
|
|
|
|
|
#import pdb; pdb.set_trace()
|
2019-07-03 22:17:12 +02:00
|
|
|
self.control_logic_insts[port].place(control_pos[port], mirror="XY")
|
|
|
|
|
|
|
|
|
|
# The row address bits are placed above the control logic aligned on the left.
|
|
|
|
|
x_offset = control_pos[port].x - self.control_logic_insts[port].width + self.row_addr_dff_insts[port].width
|
2019-07-16 18:04:58 +02:00
|
|
|
# It is below the control logic but below the bottom of the bitcell array
|
|
|
|
|
y_offset = min(self.control_logic_insts[port].by(), self.bank_inst.by() + self.row_addr_dff_insts[port].height)
|
2019-07-03 22:17:12 +02:00
|
|
|
row_addr_pos[port] = vector(x_offset, y_offset)
|
|
|
|
|
self.row_addr_dff_insts[port].place(row_addr_pos[port], mirror="XY")
|
|
|
|
|
|
2019-07-16 18:04:58 +02:00
|
|
|
# Add the data flops above the bank to the left of the upper-right of bank array
|
|
|
|
|
# This relies on the upper-right of the array of the bank
|
|
|
|
|
# decoder in upper left, bank in upper right, sensing in lower right.
|
|
|
|
|
# These flops go below the sensing and leave a gap to channel route to the
|
|
|
|
|
# sense amps.
|
|
|
|
|
if port in self.write_ports:
|
|
|
|
|
data_pos[port] = vector(self.bank.bank_array_ur.x - self.data_dff_insts[port].width,
|
|
|
|
|
self.bank.height + max_gap_size + self.dff.height)
|
|
|
|
|
self.data_dff_insts[port].place(data_pos[port], mirror="MX")
|
|
|
|
|
|
2019-08-14 20:45:22 +02:00
|
|
|
# # Add the write mask flops to the left of the din flops.
|
|
|
|
|
# if self.write_size is not None:
|
|
|
|
|
# if port in self.write_ports:
|
|
|
|
|
# wmask_pos[port] = vector(self.bank.bank_array_ur.x - self.data_dff_insts[port].width,
|
|
|
|
|
# self.bank.height + max_gap_size + self.data_dff_insts[port].height)
|
|
|
|
|
# self.wmask_dff_insts[port].place(wmask_pos[port], mirror="MX")
|
|
|
|
|
#
|
2019-07-04 20:14:32 +02:00
|
|
|
|
2018-07-13 18:30:14 +02:00
|
|
|
def add_layout_pins(self):
|
2018-07-10 23:17:09 +02:00
|
|
|
"""
|
|
|
|
|
Add the top-level pins for a single bank SRAM with control.
|
|
|
|
|
"""
|
2018-11-08 21:19:40 +01:00
|
|
|
for port in self.all_ports:
|
2018-09-27 04:10:24 +02:00
|
|
|
# Connect the control pins as inputs
|
|
|
|
|
for signal in self.control_logic_inputs[port] + ["clk"]:
|
2018-11-14 01:05:22 +01:00
|
|
|
self.copy_layout_pin(self.control_logic_insts[port], signal, signal+"{}".format(port))
|
2018-09-27 04:10:24 +02:00
|
|
|
|
2018-11-08 21:19:40 +01:00
|
|
|
if port in self.read_ports:
|
|
|
|
|
for bit in range(self.word_size):
|
|
|
|
|
self.copy_layout_pin(self.bank_inst, "dout{0}_{1}".format(port,bit), "DOUT{0}[{1}]".format(port,bit))
|
2018-09-27 04:10:24 +02:00
|
|
|
|
|
|
|
|
# Lower address bits
|
|
|
|
|
for bit in range(self.col_addr_size):
|
2018-11-14 01:05:22 +01:00
|
|
|
self.copy_layout_pin(self.col_addr_dff_insts[port], "din_{}".format(bit),"ADDR{0}[{1}]".format(port,bit))
|
2018-09-27 04:10:24 +02:00
|
|
|
# Upper address bits
|
|
|
|
|
for bit in range(self.row_addr_size):
|
2018-11-14 01:05:22 +01:00
|
|
|
self.copy_layout_pin(self.row_addr_dff_insts[port], "din_{}".format(bit),"ADDR{0}[{1}]".format(port,bit+self.col_addr_size))
|
2018-09-27 04:10:24 +02:00
|
|
|
|
2018-11-08 21:19:40 +01:00
|
|
|
if port in self.write_ports:
|
|
|
|
|
for bit in range(self.word_size):
|
2018-11-14 01:05:22 +01:00
|
|
|
self.copy_layout_pin(self.data_dff_insts[port], "din_{}".format(bit), "DIN{0}[{1}]".format(port,bit))
|
2019-08-14 20:45:22 +02:00
|
|
|
if self.write_size is not None:
|
|
|
|
|
for bit in range(self.num_wmasks):
|
|
|
|
|
self.copy_layout_pin(self.wmask_dff_insts[port], "din_{}".format(bit), "wmask{0}[{1}]".format(port,bit))
|
|
|
|
|
|
2018-07-13 18:30:14 +02:00
|
|
|
|
2018-11-15 02:05:23 +01:00
|
|
|
def route_layout(self):
|
2018-07-10 23:17:09 +02:00
|
|
|
""" Route a single bank SRAM """
|
|
|
|
|
|
2018-07-13 18:30:14 +02:00
|
|
|
self.add_layout_pins()
|
2018-07-17 23:24:44 +02:00
|
|
|
|
|
|
|
|
self.route_clk()
|
2018-07-13 18:30:14 +02:00
|
|
|
|
2018-07-17 23:24:44 +02:00
|
|
|
self.route_control_logic()
|
|
|
|
|
|
|
|
|
|
self.route_row_addr_dff()
|
|
|
|
|
|
|
|
|
|
if self.col_addr_dff:
|
|
|
|
|
self.route_col_addr_dff()
|
|
|
|
|
|
|
|
|
|
self.route_data_dff()
|
2019-08-14 20:45:22 +02:00
|
|
|
self.route_wmask_dff()
|
2018-07-17 23:24:44 +02:00
|
|
|
|
|
|
|
|
def route_clk(self):
|
|
|
|
|
""" Route the clock network """
|
2018-08-14 19:09:41 +02:00
|
|
|
|
|
|
|
|
# This is the actual input to the SRAM
|
2018-11-08 21:19:40 +01:00
|
|
|
for port in self.all_ports:
|
2018-11-14 01:05:22 +01:00
|
|
|
self.copy_layout_pin(self.control_logic_insts[port], "clk", "clk{}".format(port))
|
2018-08-14 19:09:41 +02:00
|
|
|
|
2018-09-27 04:10:24 +02:00
|
|
|
# Connect all of these clock pins to the clock in the central bus
|
|
|
|
|
# This is something like a "spine" clock distribution. The two spines
|
|
|
|
|
# are clk_buf and clk_buf_bar
|
2018-11-29 00:30:52 +01:00
|
|
|
control_clk_buf_pin = self.control_logic_insts[port].get_pin("clk_buf")
|
|
|
|
|
control_clk_buf_pos = control_clk_buf_pin.center()
|
2018-09-27 04:10:24 +02:00
|
|
|
|
2018-11-15 19:30:38 +01:00
|
|
|
# This uses a metal2 track to the right (for port0) of the control/row addr DFF
|
|
|
|
|
# to route vertically. For port1, it is to the left.
|
2018-11-14 01:05:22 +01:00
|
|
|
row_addr_clk_pin = self.row_addr_dff_insts[port].get_pin("clk")
|
2018-11-15 19:30:38 +01:00
|
|
|
if port%2:
|
|
|
|
|
control_clk_buf_pos = control_clk_buf_pin.lc()
|
|
|
|
|
row_addr_clk_pos = row_addr_clk_pin.lc()
|
|
|
|
|
mid1_pos = vector(self.row_addr_dff_insts[port].lx() - self.m2_pitch,
|
|
|
|
|
row_addr_clk_pos.y)
|
|
|
|
|
else:
|
|
|
|
|
control_clk_buf_pos = control_clk_buf_pin.rc()
|
|
|
|
|
row_addr_clk_pos = row_addr_clk_pin.rc()
|
|
|
|
|
mid1_pos = vector(self.row_addr_dff_insts[port].rx() + self.m2_pitch,
|
|
|
|
|
row_addr_clk_pos.y)
|
2018-11-29 00:30:52 +01:00
|
|
|
|
|
|
|
|
# This is the steiner point where the net branches out
|
|
|
|
|
clk_steiner_pos = vector(mid1_pos.x, control_clk_buf_pos.y)
|
|
|
|
|
self.add_path("metal1", [control_clk_buf_pos, clk_steiner_pos])
|
|
|
|
|
self.add_via_center(layers=("metal1","via1","metal2"),
|
2019-04-01 23:23:47 +02:00
|
|
|
offset=clk_steiner_pos)
|
2018-11-29 00:30:52 +01:00
|
|
|
|
2018-12-06 21:45:45 +01:00
|
|
|
# Note, the via to the control logic is taken care of above
|
|
|
|
|
self.add_wire(("metal3","via2","metal2"),[row_addr_clk_pos, mid1_pos, clk_steiner_pos])
|
2018-08-14 19:09:41 +02:00
|
|
|
|
2018-11-29 00:30:52 +01:00
|
|
|
if self.col_addr_dff:
|
|
|
|
|
dff_clk_pin = self.col_addr_dff_insts[port].get_pin("clk")
|
|
|
|
|
dff_clk_pos = dff_clk_pin.center()
|
|
|
|
|
mid_pos = vector(clk_steiner_pos.x, dff_clk_pos.y)
|
|
|
|
|
self.add_wire(("metal3","via2","metal2"),[dff_clk_pos, mid_pos, clk_steiner_pos])
|
|
|
|
|
|
|
|
|
|
if port in self.write_ports:
|
|
|
|
|
data_dff_clk_pin = self.data_dff_insts[port].get_pin("clk")
|
|
|
|
|
data_dff_clk_pos = data_dff_clk_pin.center()
|
|
|
|
|
mid_pos = vector(clk_steiner_pos.x, data_dff_clk_pos.y)
|
2018-12-06 20:58:57 +01:00
|
|
|
# In some designs, the steiner via will be too close to the mid_pos via
|
|
|
|
|
# so make the wire as wide as the contacts
|
|
|
|
|
self.add_path("metal2",[mid_pos, clk_steiner_pos], width=max(m2m3.width,m2m3.height))
|
2018-11-29 00:30:52 +01:00
|
|
|
self.add_wire(("metal3","via2","metal2"),[data_dff_clk_pos, mid_pos, clk_steiner_pos])
|
|
|
|
|
|
2019-08-14 20:45:22 +02:00
|
|
|
if self.write_size is not None:
|
|
|
|
|
wmask_dff_clk_pin = self.wmask_dff_insts[port].get_pin("clk")
|
|
|
|
|
wmask_dff_clk_pos = wmask_dff_clk_pin.center()
|
|
|
|
|
mid_pos = vector(clk_steiner_pos.x, wmask_dff_clk_pos.y)
|
|
|
|
|
# In some designs, the steiner via will be too close to the mid_pos via
|
|
|
|
|
# so make the wire as wide as the contacts
|
|
|
|
|
self.add_path("metal2", [mid_pos, clk_steiner_pos], width=max(m2m3.width, m2m3.height))
|
|
|
|
|
self.add_wire(("metal3", "via2", "metal2"), [wmask_dff_clk_pos, mid_pos, clk_steiner_pos])
|
|
|
|
|
|
2018-08-21 18:28:07 +02:00
|
|
|
|
2018-07-17 23:24:44 +02:00
|
|
|
def route_control_logic(self):
|
2019-07-16 18:04:58 +02:00
|
|
|
""" Route the control logic pins that are not inputs """
|
|
|
|
|
|
2018-11-08 21:19:40 +01:00
|
|
|
for port in self.all_ports:
|
2018-09-27 04:10:24 +02:00
|
|
|
for signal in self.control_logic_outputs[port]:
|
2018-11-29 00:30:52 +01:00
|
|
|
# The clock gets routed separately and is not a part of the bank
|
|
|
|
|
if "clk" in signal:
|
|
|
|
|
continue
|
2018-11-14 01:05:22 +01:00
|
|
|
src_pin = self.control_logic_insts[port].get_pin(signal)
|
2018-09-27 04:10:24 +02:00
|
|
|
dest_pin = self.bank_inst.get_pin(signal+"{}".format(port))
|
2019-07-16 18:04:58 +02:00
|
|
|
self.connect_vbus_m2m3(src_pin, dest_pin)
|
|
|
|
|
|
2019-08-10 21:53:07 +02:00
|
|
|
for port in self.all_ports:
|
2019-07-16 18:04:58 +02:00
|
|
|
# Only input (besides pins) is the replica bitline
|
|
|
|
|
src_pin = self.control_logic_insts[port].get_pin("rbl_bl")
|
|
|
|
|
dest_pin = self.bank_inst.get_pin("rbl_bl{}".format(port))
|
|
|
|
|
self.connect_vbus_m2m3(src_pin, dest_pin)
|
|
|
|
|
|
2018-07-10 23:17:09 +02:00
|
|
|
|
2018-07-17 23:24:44 +02:00
|
|
|
def route_row_addr_dff(self):
|
|
|
|
|
""" Connect the output of the row flops to the bank pins """
|
2018-11-08 21:19:40 +01:00
|
|
|
for port in self.all_ports:
|
2018-09-27 04:10:24 +02:00
|
|
|
for bit in range(self.row_addr_size):
|
2018-10-11 18:53:08 +02:00
|
|
|
flop_name = "dout_{}".format(bit)
|
|
|
|
|
bank_name = "addr{0}_{1}".format(port,bit+self.col_addr_size)
|
2018-11-14 01:05:22 +01:00
|
|
|
flop_pin = self.row_addr_dff_insts[port].get_pin(flop_name)
|
2018-09-27 04:10:24 +02:00
|
|
|
bank_pin = self.bank_inst.get_pin(bank_name)
|
|
|
|
|
flop_pos = flop_pin.center()
|
|
|
|
|
bank_pos = bank_pin.center()
|
|
|
|
|
mid_pos = vector(bank_pos.x,flop_pos.y)
|
|
|
|
|
self.add_wire(("metal3","via2","metal2"),[flop_pos, mid_pos,bank_pos])
|
|
|
|
|
self.add_via_center(layers=("metal2","via2","metal3"),
|
2019-04-01 23:23:47 +02:00
|
|
|
offset=flop_pos)
|
2018-07-13 23:45:46 +02:00
|
|
|
|
2018-07-17 23:24:44 +02:00
|
|
|
def route_col_addr_dff(self):
|
|
|
|
|
""" Connect the output of the row flops to the bank pins """
|
2018-11-08 21:19:40 +01:00
|
|
|
for port in self.all_ports:
|
2019-07-03 22:17:12 +02:00
|
|
|
if port%2:
|
|
|
|
|
offset = self.col_addr_dff_insts[port].ll() - vector(0, (self.word_size+2)*self.m1_pitch)
|
|
|
|
|
else:
|
|
|
|
|
offset = self.col_addr_dff_insts[port].ul() + vector(0, 2*self.m1_pitch)
|
|
|
|
|
|
2018-10-11 18:53:08 +02:00
|
|
|
bus_names = ["addr_{}".format(x) for x in range(self.col_addr_size)]
|
2018-09-27 04:10:24 +02:00
|
|
|
col_addr_bus_offsets = self.create_horizontal_bus(layer="metal1",
|
|
|
|
|
pitch=self.m1_pitch,
|
2019-07-03 22:17:12 +02:00
|
|
|
offset=offset,
|
2018-09-27 04:10:24 +02:00
|
|
|
names=bus_names,
|
2018-11-14 01:05:22 +01:00
|
|
|
length=self.col_addr_dff_insts[port].width)
|
2018-09-27 04:10:24 +02:00
|
|
|
|
2018-10-11 18:53:08 +02:00
|
|
|
dff_names = ["dout_{}".format(x) for x in range(self.col_addr_size)]
|
2018-09-27 04:10:24 +02:00
|
|
|
data_dff_map = zip(dff_names, bus_names)
|
2018-11-14 01:05:22 +01:00
|
|
|
self.connect_horizontal_bus(data_dff_map, self.col_addr_dff_insts[port], col_addr_bus_offsets)
|
2018-09-27 04:10:24 +02:00
|
|
|
|
2018-10-11 18:53:08 +02:00
|
|
|
bank_names = ["addr{0}_{1}".format(port,x) for x in range(self.col_addr_size)]
|
2018-09-27 04:10:24 +02:00
|
|
|
data_bank_map = zip(bank_names, bus_names)
|
|
|
|
|
self.connect_horizontal_bus(data_bank_map, self.bank_inst, col_addr_bus_offsets)
|
2018-07-17 23:24:44 +02:00
|
|
|
|
2018-07-10 23:17:09 +02:00
|
|
|
|
2018-07-17 23:24:44 +02:00
|
|
|
def route_data_dff(self):
|
|
|
|
|
""" Connect the output of the data flops to the write driver """
|
2018-07-25 20:13:30 +02:00
|
|
|
# This is where the channel will start (y-dimension at least)
|
2018-11-08 21:19:40 +01:00
|
|
|
for port in self.write_ports:
|
2018-11-29 21:12:10 +01:00
|
|
|
if port%2:
|
|
|
|
|
offset = self.data_dff_insts[port].ll() - vector(0, (self.word_size+2)*self.m1_pitch)
|
|
|
|
|
else:
|
|
|
|
|
offset = self.data_dff_insts[port].ul() + vector(0, 2*self.m1_pitch)
|
|
|
|
|
|
2018-09-27 04:10:24 +02:00
|
|
|
|
2018-10-11 18:53:08 +02:00
|
|
|
dff_names = ["dout_{}".format(x) for x in range(self.word_size)]
|
2018-11-29 21:12:10 +01:00
|
|
|
dff_pins = [self.data_dff_insts[port].get_pin(x) for x in dff_names]
|
|
|
|
|
|
2018-10-11 18:53:08 +02:00
|
|
|
bank_names = ["din{0}_{1}".format(port,x) for x in range(self.word_size)]
|
2018-11-29 21:12:10 +01:00
|
|
|
bank_pins = [self.bank_inst.get_pin(x) for x in bank_names]
|
|
|
|
|
|
|
|
|
|
route_map = list(zip(bank_pins, dff_pins))
|
|
|
|
|
self.create_horizontal_channel_route(route_map, offset)
|
2019-08-14 20:45:22 +02:00
|
|
|
|
|
|
|
|
def route_wmask_dff(self):
|
|
|
|
|
""" Connect the output of the wmask flops to the write mask AND array """
|
|
|
|
|
# This is where the channel will start (y-dimension at least)
|
|
|
|
|
for port in self.write_ports:
|
|
|
|
|
if port % 2:
|
|
|
|
|
offset = self.wmask_dff_insts[port].ll() - vector(0, (self.word_size + 2) * self.m1_pitch)
|
|
|
|
|
else:
|
|
|
|
|
offset = self.wmask_dff_insts[port].ul() + vector(0, 2 * self.m1_pitch)
|
|
|
|
|
|
|
|
|
|
dff_names = ["dout_{}".format(x) for x in range(self.num_wmasks)]
|
|
|
|
|
dff_pins = [self.wmask_dff_insts[port].get_pin(x) for x in dff_names]
|
|
|
|
|
|
|
|
|
|
bank_names = ["bank_wmask{0}_{1}".format(port, x) for x in range(self.num_wmasks)]
|
|
|
|
|
bank_pins = [self.bank_inst.get_pin(x) for x in bank_names]
|
|
|
|
|
|
|
|
|
|
route_map = list(zip(bank_pins, dff_pins))
|
|
|
|
|
self.create_horizontal_channel_route(route_map, offset)
|
2018-07-17 23:24:44 +02:00
|
|
|
|
2018-07-10 23:17:09 +02:00
|
|
|
|
2018-07-18 23:29:04 +02:00
|
|
|
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.
|
|
|
|
|
"""
|
|
|
|
|
|
2018-09-27 04:10:24 +02:00
|
|
|
for n in self.control_logic_outputs[0]:
|
2018-11-14 01:05:22 +01:00
|
|
|
pin = self.control_logic_insts[0].get_pin(n)
|
2018-07-18 23:29:04 +02:00
|
|
|
self.add_label(text=n,
|
2018-07-19 00:10:57 +02:00
|
|
|
layer=pin.layer,
|
|
|
|
|
offset=pin.center())
|
2019-04-24 23:23:22 +02:00
|
|
|
|
|
|
|
|
def graph_exclude_data_dff(self):
|
2019-07-04 19:34:14 +02:00
|
|
|
"""Removes data dff and wmask dff (if applicable) from search graph. """
|
|
|
|
|
#Data dffs and wmask dffs are only for writing so are not useful for evaluating read delay.
|
2019-04-24 23:23:22 +02:00
|
|
|
for inst in self.data_dff_insts:
|
|
|
|
|
self.graph_inst_exclude.add(inst)
|
2019-07-19 23:58:37 +02:00
|
|
|
if self.write_size is not None:
|
2019-07-04 19:34:14 +02:00
|
|
|
for inst in self.wmask_dff_insts:
|
|
|
|
|
self.graph_inst_exclude.add(inst)
|
2019-04-24 23:23:22 +02:00
|
|
|
|
|
|
|
|
def graph_exclude_addr_dff(self):
|
|
|
|
|
"""Removes data dff from search graph. """
|
|
|
|
|
#Address is considered not part of the critical path, subjectively removed
|
|
|
|
|
for inst in self.row_addr_dff_insts:
|
|
|
|
|
self.graph_inst_exclude.add(inst)
|
|
|
|
|
|
|
|
|
|
if self.col_addr_dff:
|
|
|
|
|
for inst in self.col_addr_dff_insts:
|
2019-04-25 08:51:09 +02:00
|
|
|
self.graph_inst_exclude.add(inst)
|
|
|
|
|
|
|
|
|
|
def graph_exclude_ctrl_dffs(self):
|
|
|
|
|
"""Exclude dffs for CSB, WEB, etc from graph"""
|
|
|
|
|
#Insts located in control logic, exclusion function called here
|
|
|
|
|
for inst in self.control_logic_insts:
|
|
|
|
|
inst.mod.graph_exclude_dffs()
|
2019-05-14 23:44:49 +02:00
|
|
|
|
|
|
|
|
def get_sen_name(self, sram_name, port=0):
|
|
|
|
|
"""Returns the s_en spice name."""
|
|
|
|
|
#Naming scheme is hardcoded using this function, should be built into the
|
|
|
|
|
#graph in someway.
|
|
|
|
|
sen_name = "s_en{}".format(port)
|
|
|
|
|
control_conns = self.get_conns(self.control_logic_insts[port])
|
|
|
|
|
#Sanity checks
|
|
|
|
|
if sen_name not in control_conns:
|
|
|
|
|
debug.error("Signal={} not contained in control logic connections={}"\
|
|
|
|
|
.format(sen_name, control_conns))
|
|
|
|
|
if sen_name in self.pins:
|
2019-05-21 03:35:52 +02:00
|
|
|
debug.error("Internal signal={} contained in port list. Name defined by the parent.")
|
2019-05-14 23:44:49 +02:00
|
|
|
return "X{}.{}".format(sram_name, sen_name)
|
|
|
|
|
|
2019-05-21 03:35:52 +02:00
|
|
|
def get_cell_name(self, inst_name, row, col):
|
|
|
|
|
"""Gets the spice name of the target bitcell."""
|
|
|
|
|
#Sanity check in case it was forgotten
|
|
|
|
|
if inst_name.find('x') != 0:
|
|
|
|
|
inst_name = 'x'+inst_name
|
2019-07-12 17:42:36 +02:00
|
|
|
return self.bank_inst.mod.get_cell_name(inst_name+'.x'+self.bank_inst.name, row, col)
|