Merged and fixed conflicts with dev

This commit is contained in:
Hunter Nichols
2019-06-25 16:55:50 -07:00
311 changed files with 14271 additions and 1503 deletions
+136
View File
@@ -0,0 +1,136 @@
# See LICENSE for licensing information.
#
# 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.
#
import sys
import datetime
import getpass
import debug
from globals import OPTS, print_time
from sram_config import sram_config
class sram():
"""
This is not a design module, but contains an SRAM design instance.
It could later try options of number of banks and oganization to compare
results.
We can later add visualizer and other high-level functions as needed.
"""
def __init__(self, sram_config, name):
sram_config.set_local_config(self)
# reset the static duplicate name checker for unit tests
# in case we create more than one SRAM
from design import design
design.name_map=[]
debug.info(2, "create sram of size {0} with {1} num of words {2} banks".format(self.word_size,
self.num_words,
self.num_banks))
start_time = datetime.datetime.now()
self.name = name
if self.num_banks == 1:
from sram_1bank import sram_1bank as sram
elif self.num_banks == 2:
from sram_2bank import sram_2bank as sram
else:
debug.error("Invalid number of banks.",-1)
self.s = sram(name, sram_config)
self.s.create_netlist()
if not OPTS.netlist_only:
self.s.create_layout()
if not OPTS.is_unit_test:
print_time("SRAM creation", datetime.datetime.now(), start_time)
def sp_write(self,name):
self.s.sp_write(name)
def lef_write(self,name):
self.s.lef_write(name)
def gds_write(self,name):
self.s.gds_write(name)
def verilog_write(self,name):
self.s.verilog_write(name)
def save(self):
""" Save all the output files while reporting time to do it as well. """
if not OPTS.netlist_only:
# Create a LEF physical model
start_time = datetime.datetime.now()
lefname = OPTS.output_path + self.s.name + ".lef"
debug.print_raw("LEF: Writing to {0}".format(lefname))
self.lef_write(lefname)
print_time("LEF", datetime.datetime.now(), start_time)
if OPTS.route_supplies:
# Write the layout
start_time = datetime.datetime.now()
gdsname = OPTS.output_path + self.s.name + ".gds"
debug.print_raw("GDS: Writing to {0}".format(gdsname))
self.gds_write(gdsname)
print_time("GDS", datetime.datetime.now(), start_time)
# Save the spice file
start_time = datetime.datetime.now()
spname = OPTS.output_path + self.s.name + ".sp"
debug.print_raw("SP: Writing to {0}".format(spname))
self.sp_write(spname)
print_time("Spice writing", datetime.datetime.now(), start_time)
# Save the extracted spice file
if OPTS.use_pex:
import verify
start_time = datetime.datetime.now()
# Output the extracted design if requested
sp_file = OPTS.output_path + "temp_pex.sp"
verify.run_pex(self.s.name, gdsname, spname, output=sp_file)
print_time("Extraction", datetime.datetime.now(), start_time)
else:
# Use generated spice file for characterization
sp_file = spname
# Characterize the design
start_time = datetime.datetime.now()
from characterizer import lib
debug.print_raw("LIB: Characterizing... ")
lib(out_dir=OPTS.output_path, sram=self.s, sp_file=sp_file)
print_time("Characterization", datetime.datetime.now(), start_time)
# Write the config file
start_time = datetime.datetime.now()
from shutil import copyfile
copyfile(OPTS.config_file + '.py', OPTS.output_path + OPTS.output_name + '.py')
debug.print_raw("Config: Writing to {0}".format(OPTS.output_path + OPTS.output_name + '.py'))
print_time("Config", datetime.datetime.now(), start_time)
# Write the datasheet
start_time = datetime.datetime.now()
from datasheet_gen import datasheet_gen
dname = OPTS.output_path + self.s.name + ".html"
debug.print_raw("Datasheet: Writing to {0}".format(dname))
datasheet_gen.datasheet_write(dname)
print_time("Datasheet", datetime.datetime.now(), start_time)
# Write a verilog model
start_time = datetime.datetime.now()
vname = OPTS.output_path + self.s.name + ".v"
debug.print_raw("Verilog: Writing to {0}".format(vname))
self.verilog_write(vname)
print_time("Verilog", datetime.datetime.now(), start_time)
+354
View File
@@ -0,0 +1,354 @@
# See LICENSE for licensing information.
#
# 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.
#
import sys
from tech import drc, spice
import debug
from math import log,sqrt,ceil
import datetime
import getpass
import numpy as np
from vector import vector
from globals import OPTS, print_time
from sram_base import sram_base
from bank import bank
from contact import m2m3
from dff_buf_array import dff_buf_array
from dff_array import dff_array
class sram_1bank(sram_base):
"""
Procedures specific to a one bank SRAM.
"""
def __init__(self, name, sram_config):
sram_base.__init__(self, name, sram_config)
def create_modules(self):
"""
This adds the modules for a single bank SRAM with control
logic.
"""
self.bank_inst=self.create_bank(0)
self.control_logic_insts = self.create_control_logic()
self.row_addr_dff_insts = self.create_row_addr_dff()
if self.col_addr_dff:
self.col_addr_dff_insts = self.create_col_addr_dff()
self.data_dff_insts = self.create_data_dff()
def place_instances(self):
"""
This places the instances for a single bank SRAM with control
logic and up to 2 ports.
"""
# No orientation or offset
self.place_bank(self.bank_inst, [0, 0], 1, 1)
# 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
# the sense amps/column mux and cell array)
# The x-coordinate is placed to allow a single clock wire (plus an extra pitch)
# up to the row address DFFs.
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)
# 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
# Port 0
port = 0
# This includes 2 M2 pitches for the row addr clock line.
# It is also placed to align with the column decoder (if it exists hence the bank gap)
control_pos[port] = vector(-self.control_logic_insts[port].width - 2*self.m2_pitch,
self.bank.bank_array_ll.y - self.control_logic_insts[port].mod.control_logic_center.y - self.bank.m2_gap)
self.control_logic_insts[port].place(control_pos[port])
# The row address bits are placed above the control logic aligned on the right.
x_offset = self.control_logic_insts[port].rx() - self.row_addr_dff_insts[port].width
# It is aove the control logic but below the top of the bitcell array
y_offset = max(self.control_logic_insts[port].uy(), self.bank.bank_array_ur.y - self.row_addr_dff_insts[port].height)
row_addr_pos[port] = vector(x_offset, y_offset)
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:
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])
# 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.data_dff_insts[port].height)
self.data_dff_insts[port].place(data_pos[port])
if len(self.all_ports)>1:
# Port 1
port = 1
# This includes 2 M2 pitches for the row addr clock line
# It is also placed to align with the column decoder (if it exists hence the bank gap)
control_pos[port] = vector(self.bank_inst.rx() + self.control_logic_insts[port].width + 2*self.m2_pitch,
self.bank.bank_array_ll.y - self.control_logic_insts[port].mod.control_logic_center.y + self.bank.m2_gap)
self.control_logic_insts[port].place(control_pos[port], mirror="MY")
# 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
# 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.bank_array_ur.y - self.row_addr_dff_insts[port].height)
row_addr_pos[port] = vector(x_offset, y_offset)
self.row_addr_dff_insts[port].place(row_addr_pos[port], mirror="MY")
# 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.col_addr_dff_insts[port].height)
self.col_addr_dff_insts[port].place(col_addr_pos[port], mirror="MX")
# 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.data_dff_insts[port].height)
self.data_dff_insts[port].place(data_pos[port], mirror="MX")
def add_layout_pins(self):
"""
Add the top-level pins for a single bank SRAM with control.
"""
for port in self.all_ports:
# Connect the control pins as inputs
for signal in self.control_logic_inputs[port] + ["clk"]:
self.copy_layout_pin(self.control_logic_insts[port], signal, signal+"{}".format(port))
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))
# Lower address bits
for bit in range(self.col_addr_size):
self.copy_layout_pin(self.col_addr_dff_insts[port], "din_{}".format(bit),"ADDR{0}[{1}]".format(port,bit))
# Upper address bits
for bit in range(self.row_addr_size):
self.copy_layout_pin(self.row_addr_dff_insts[port], "din_{}".format(bit),"ADDR{0}[{1}]".format(port,bit+self.col_addr_size))
if port in self.write_ports:
for bit in range(self.word_size):
self.copy_layout_pin(self.data_dff_insts[port], "din_{}".format(bit), "DIN{0}[{1}]".format(port,bit))
def route_layout(self):
""" Route a single bank SRAM """
self.add_layout_pins()
self.route_clk()
self.route_control_logic()
self.route_row_addr_dff()
if self.col_addr_dff:
self.route_col_addr_dff()
self.route_data_dff()
def route_clk(self):
""" Route the clock network """
# This is the actual input to the SRAM
for port in self.all_ports:
self.copy_layout_pin(self.control_logic_insts[port], "clk", "clk{}".format(port))
# 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
control_clk_buf_pin = self.control_logic_insts[port].get_pin("clk_buf")
control_clk_buf_pos = control_clk_buf_pin.center()
# 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.
row_addr_clk_pin = self.row_addr_dff_insts[port].get_pin("clk")
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)
# 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"),
offset=clk_steiner_pos)
# 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])
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)
# 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"),[data_dff_clk_pos, mid_pos, clk_steiner_pos])
def route_control_logic(self):
""" Route the outputs from the control logic module """
for port in self.all_ports:
for signal in self.control_logic_outputs[port]:
# The clock gets routed separately and is not a part of the bank
if "clk" in signal:
continue
src_pin = self.control_logic_insts[port].get_pin(signal)
dest_pin = self.bank_inst.get_pin(signal+"{}".format(port))
self.connect_rail_from_left_m2m3(src_pin, dest_pin)
self.add_via_center(layers=("metal1","via1","metal2"),
offset=src_pin.rc())
def route_row_addr_dff(self):
""" Connect the output of the row flops to the bank pins """
for port in self.all_ports:
for bit in range(self.row_addr_size):
flop_name = "dout_{}".format(bit)
bank_name = "addr{0}_{1}".format(port,bit+self.col_addr_size)
flop_pin = self.row_addr_dff_insts[port].get_pin(flop_name)
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"),
offset=flop_pos)
def route_col_addr_dff(self):
""" Connect the output of the row flops to the bank pins """
for port in self.all_ports:
bus_names = ["addr_{}".format(x) for x in range(self.col_addr_size)]
col_addr_bus_offsets = self.create_horizontal_bus(layer="metal1",
pitch=self.m1_pitch,
offset=self.col_addr_dff_insts[port].ul() + vector(0, self.m1_pitch),
names=bus_names,
length=self.col_addr_dff_insts[port].width)
dff_names = ["dout_{}".format(x) for x in range(self.col_addr_size)]
data_dff_map = zip(dff_names, bus_names)
self.connect_horizontal_bus(data_dff_map, self.col_addr_dff_insts[port], col_addr_bus_offsets)
bank_names = ["addr{0}_{1}".format(port,x) for x in range(self.col_addr_size)]
data_bank_map = zip(bank_names, bus_names)
self.connect_horizontal_bus(data_bank_map, self.bank_inst, col_addr_bus_offsets)
def route_data_dff(self):
""" Connect the output of the data flops to the write driver """
# This is where the channel will start (y-dimension at least)
for port in self.write_ports:
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)
dff_names = ["dout_{}".format(x) for x in range(self.word_size)]
dff_pins = [self.data_dff_insts[port].get_pin(x) for x in dff_names]
bank_names = ["din{0}_{1}".format(port,x) for x in range(self.word_size)]
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)
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.
"""
for n in self.control_logic_outputs[0]:
pin = self.control_logic_insts[0].get_pin(n)
self.add_label(text=n,
layer=pin.layer,
offset=pin.center())
def graph_exclude_data_dff(self):
"""Removes data dff from search graph. """
#Data dffs are only for writing so are not useful for evaluating read delay.
for inst in self.data_dff_insts:
self.graph_inst_exclude.add(inst)
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:
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()
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:
debug.error("Internal signal={} contained in port list. Name defined by the parent.")
return "X{}.{}".format(sram_name, sen_name)
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
return self.bank_inst.mod.get_cell_name(inst_name+'.x'+self.bank_inst.name, row, col)
+240
View File
@@ -0,0 +1,240 @@
# See LICENSE for licensing information.
#
# 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.
#
import sys
from tech import drc, spice
import debug
from math import log,sqrt,ceil
import datetime
import getpass
from vector import vector
from globals import OPTS, print_time
from sram_base import sram_base
from bank import bank
from dff_buf_array import dff_buf_array
from dff_array import dff_array
class sram_2bank(sram_base):
"""
Procedures specific to a two bank SRAM.
"""
def __init__(self, name, sram_config):
sram_base.__init__(self, name, sram_config)
def compute_bank_offsets(self):
""" Compute the overall offsets for a two bank SRAM """
# In 2 bank SRAM, the height is determined by the control bus which is higher than the msb address
self.vertical_bus_height = self.bank.height + 2*self.bank_to_bus_distance + self.data_bus_height + self.control_bus_height
# The address bus extends down through the power rails, but control and bank_sel bus don't
self.addr_bus_height = self.vertical_bus_height
self.vertical_bus_offset = vector(self.bank.width + self.bank_to_bus_distance, 0)
self.data_bus_offset = vector(0, self.bank.height + self.bank_to_bus_distance)
self.supply_bus_offset = vector(0, self.data_bus_offset.y + self.data_bus_height)
self.control_bus_offset = vector(0, self.supply_bus_offset.y + self.supply_bus_height)
self.bank_sel_bus_offset = self.vertical_bus_offset + vector(self.m2_pitch*self.control_size,0)
self.addr_bus_offset = self.bank_sel_bus_offset.scale(1,0) + vector(self.m2_pitch*self.num_banks,0)
# Control is placed at the top above the control bus and everything
self.control_logic_position = vector(0, self.control_bus_offset.y + self.control_bus_height + self.m1_pitch)
# Bank select flops get put to the right of control logic above bank1 and the buses
# Leave a pitch to get the vdd rails up to M2
self.msb_address_position = vector(self.bank_inst[1].lx() + 3*self.supply_rail_pitch,
self.supply_bus_offset.y + self.supply_bus_height \
+ 2*self.m1_pitch + self.msb_address.width)
def add_modules(self):
""" Adds the modules and the buses to the top level """
self.compute_bus_sizes()
self.add_banks()
self.compute_bank_offsets()
self.add_busses()
self.add_logic()
self.width = self.bank_inst[1].ur().x
self.height = self.control_logic_inst.uy()
def add_banks(self):
# Placement of bank 0 (left)
bank_position_0 = vector(self.bank.width,
self.bank.height)
self.bank_inst=[self.add_bank(0, bank_position_0, -1, -1)]
# Placement of bank 1 (right)
x_off = self.bank.width + self.vertical_bus_width + 2*self.bank_to_bus_distance
bank_position_1 = vector(x_off, bank_position_0.y)
self.bank_inst.append(self.add_bank(1, bank_position_1, -1, 1))
def add_logic(self):
""" Add the control and MSB logic """
self.add_control_logic(position=self.control_logic_position)
self.msb_address_inst = self.add_inst(name="msb_address",
mod=self.msb_address,
offset=self.msb_address_position,
rotate=270)
self.msb_bank_sel_addr = "ADDR[{}]".format(self.addr_size-1)
self.connect_inst([self.msb_bank_sel_addr,"bank_sel[1]","bank_sel[0]","clk_buf", "vdd", "gnd"])
def route_shared_banks(self):
""" Route the shared signals for two and four bank configurations. """
# create the input control pins
for n in self.control_logic_inputs + ["clk"]:
self.copy_layout_pin(self.control_logic_inst, n)
# connect the control logic to the control bus
for n in self.control_logic_outputs + ["vdd", "gnd"]:
pins = self.control_logic_inst.get_pins(n)
for pin in pins:
if pin.layer=="metal2":
pin_pos = pin.bc()
break
rail_pos = vector(pin_pos.x,self.horz_control_bus_positions[n].y)
self.add_path("metal2",[pin_pos,rail_pos])
self.add_via_center(("metal1","via1","metal2"),rail_pos)
# connect the control logic cross bar
for n in self.control_logic_outputs:
cross_pos = vector(self.vert_control_bus_positions[n].x,self.horz_control_bus_positions[n].y)
self.add_via_center(("metal1","via1","metal2"),cross_pos)
# connect the bank select signals to the vertical bus
for i in range(self.num_banks):
pin = self.bank_inst[i].get_pin("bank_sel")
pin_pos = pin.rc() if i==0 else pin.lc()
rail_pos = vector(self.vert_control_bus_positions["bank_sel[{}]".format(i)].x,pin_pos.y)
self.add_path("metal3",[pin_pos,rail_pos])
self.add_via_center(("metal2","via2","metal3"),rail_pos)
def route_single_msb_address(self):
""" Route one MSB address bit for 2-bank SRAM """
# connect the bank MSB flop supplies
vdd_pins = self.msb_address_inst.get_pins("vdd")
for vdd_pin in vdd_pins:
if vdd_pin.layer != "metal1": continue
vdd_pos = vdd_pin.bc()
down_pos = vdd_pos - vector(0,self.m1_pitch)
rail_pos = vector(vdd_pos.x,self.horz_control_bus_positions["vdd"].y)
self.add_path("metal1",[vdd_pos,down_pos])
self.add_via_center(("metal1","via1","metal2"),down_pos,rotate=90)
self.add_path("metal2",[down_pos,rail_pos])
self.add_via_center(("metal1","via1","metal2"),rail_pos)
gnd_pins = self.msb_address_inst.get_pins("gnd")
# Only add the ground connection to the lowest metal2 rail in the flop array
# FIXME: SCMOS doesn't have a vertical rail in the cell, or we could use those
lowest_y = None
for gnd_pin in gnd_pins:
if gnd_pin.layer != "metal2": continue
if lowest_y==None or gnd_pin.by()<lowest_y:
lowest_y=gnd_pin.by()
gnd_pos = gnd_pin.ur()
rail_pos = vector(gnd_pos.x,self.horz_control_bus_positions["gnd"].y)
self.add_path("metal2",[gnd_pos,rail_pos])
self.add_via_center(("metal1","via1","metal2"),rail_pos)
# connect the MSB flop to the address input bus
msb_pins = self.msb_address_inst.get_pins("din[0]")
for msb_pin in msb_pins:
if msb_pin.layer == "metal3":
msb_pin_pos = msb_pin.lc()
break
rail_pos = vector(self.vert_control_bus_positions[self.msb_bank_sel_addr].x,msb_pin_pos.y)
self.add_path("metal3",[msb_pin_pos,rail_pos])
self.add_via_center(("metal2","via2","metal3"),rail_pos)
# Connect the output bar to select 0
msb_out_pin = self.msb_address_inst.get_pin("dout_bar[0]")
msb_out_pos = msb_out_pin.rc()
out_extend_right_pos = msb_out_pos + vector(2*self.m2_pitch,0)
out_extend_up_pos = out_extend_right_pos + vector(0,self.m2_width)
rail_pos = vector(self.vert_control_bus_positions["bank_sel[0]"].x,out_extend_up_pos.y)
self.add_path("metal2",[msb_out_pos,out_extend_right_pos,out_extend_up_pos])
self.add_wire(("metal3","via2","metal2"),[out_extend_right_pos,out_extend_up_pos,rail_pos])
self.add_via_center(("metal2","via2","metal3"),rail_pos)
# Connect the output to select 1
msb_out_pin = self.msb_address_inst.get_pin("dout[0]")
msb_out_pos = msb_out_pin.rc()
out_extend_right_pos = msb_out_pos + vector(2*self.m2_pitch,0)
out_extend_down_pos = out_extend_right_pos - vector(0,2*self.m1_pitch)
rail_pos = vector(self.vert_control_bus_positions["bank_sel[1]"].x,out_extend_down_pos.y)
self.add_path("metal2",[msb_out_pos,out_extend_right_pos,out_extend_down_pos])
self.add_wire(("metal3","via2","metal2"),[out_extend_right_pos,out_extend_down_pos,rail_pos])
self.add_via_center(("metal2","via2","metal3"),rail_pos)
# Connect clk
clk_pin = self.msb_address_inst.get_pin("clk")
clk_pos = clk_pin.bc()
rail_pos = self.horz_control_bus_positions["clk_buf"]
bend_pos = vector(clk_pos.x,self.horz_control_bus_positions["clk_buf"].y)
self.add_path("metal1",[clk_pos,bend_pos,rail_pos])
def route(self):
""" Route all of the signals for the two bank SRAM. """
self.route_shared_banks()
# connect the horizontal control bus to the vertical bus
# connect the data output to the data bus
for n in self.data_bus_names:
for i in [0,1]:
pin_pos = self.bank_inst[i].get_pin(n).uc()
rail_pos = vector(pin_pos.x,self.data_bus_positions[n].y)
self.add_path("metal2",[pin_pos,rail_pos])
self.add_via_center(("metal2","via2","metal3"),rail_pos)
self.route_single_msb_address()
# connect the banks to the vertical address bus
# connect the banks to the vertical control bus
for n in self.addr_bus_names + self.control_bus_names:
# Skip these from the horizontal bus
if n in ["vdd", "gnd"]: continue
# This will be the bank select, so skip it
if n == self.msb_bank_sel_addr: continue
pin0_pos = self.bank_inst[0].get_pin(n).rc()
pin1_pos = self.bank_inst[1].get_pin(n).lc()
rail_pos = vector(self.vert_control_bus_positions[n].x,pin0_pos.y)
self.add_path("metal3",[pin0_pos,pin1_pos])
self.add_via_center(("metal2","via2","metal3"),rail_pos)
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.
"""
if self.num_banks==1: return
for n in self.control_bus_names:
self.add_label(text=n,
layer="metal2",
offset=self.vert_control_bus_positions[n])
for n in self.bank_sel_bus_names:
self.add_label(text=n,
layer="metal2",
offset=self.vert_control_bus_positions[n])
+590
View File
@@ -0,0 +1,590 @@
# See LICENSE for licensing information.
#
# 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.
#
import sys
import datetime
import getpass
import debug
from datetime import datetime
from importlib import reload
from vector import vector
from globals import OPTS, print_time
import logical_effort
from design import design
from verilog import verilog
from lef import lef
from sram_factory import factory
import logical_effort
class sram_base(design, verilog, lef):
"""
Dynamically generated SRAM by connecting banks to control logic. The
number of banks should be 1 , 2 or 4
"""
def __init__(self, name, sram_config):
design.__init__(self, name)
lef.__init__(self, ["metal1", "metal2", "metal3"])
verilog.__init__(self)
self.sram_config = sram_config
sram_config.set_local_config(self)
self.bank_insts = []
#For logical effort delay calculations.
self.all_mods_except_control_done = False
def add_pins(self):
""" Add pins for entire SRAM. """
for port in self.write_ports:
for bit in range(self.word_size):
self.add_pin("DIN{0}[{1}]".format(port,bit),"INPUT")
for port in self.all_ports:
for bit in range(self.addr_size):
self.add_pin("ADDR{0}[{1}]".format(port,bit),"INPUT")
# These are used to create the physical pins
self.control_logic_inputs = []
self.control_logic_outputs = []
for port in self.all_ports:
if port in self.readwrite_ports:
self.control_logic_inputs.append(self.control_logic_rw.get_inputs())
self.control_logic_outputs.append(self.control_logic_rw.get_outputs())
elif port in self.write_ports:
self.control_logic_inputs.append(self.control_logic_w.get_inputs())
self.control_logic_outputs.append(self.control_logic_w.get_outputs())
else:
self.control_logic_inputs.append(self.control_logic_r.get_inputs())
self.control_logic_outputs.append(self.control_logic_r.get_outputs())
for port in self.all_ports:
self.add_pin("csb{}".format(port),"INPUT")
for port in self.readwrite_ports:
self.add_pin("web{}".format(port),"INPUT")
for port in self.all_ports:
self.add_pin("clk{}".format(port),"INPUT")
for port in self.read_ports:
for bit in range(self.word_size):
self.add_pin("DOUT{0}[{1}]".format(port,bit),"OUTPUT")
self.add_pin("vdd","POWER")
self.add_pin("gnd","GROUND")
def create_netlist(self):
""" Netlist creation """
start_time = datetime.now()
# Must create the control logic before pins to get the pins
self.add_modules()
self.add_pins()
self.create_modules()
# This is for the lib file if we don't create layout
self.width=0
self.height=0
if not OPTS.is_unit_test:
print_time("Submodules",datetime.now(), start_time)
def create_layout(self):
""" Layout creation """
start_time = datetime.now()
self.place_instances()
if not OPTS.is_unit_test:
print_time("Placement",datetime.now(), start_time)
start_time = datetime.now()
self.route_layout()
self.route_supplies()
if not OPTS.is_unit_test:
print_time("Routing",datetime.now(), start_time)
self.add_lvs_correspondence_points()
self.offset_all_coordinates()
highest_coord = self.find_highest_coords()
self.width = highest_coord[0]
self.height = highest_coord[1]
start_time = datetime.now()
# We only enable final verification if we have routed the design
self.DRC_LVS(final_verification=OPTS.route_supplies, top_level=True)
if not OPTS.is_unit_test:
print_time("Verification",datetime.now(), start_time)
def create_modules(self):
debug.error("Must override pure virtual function.",-1)
def route_supplies(self):
""" Route the supply grid and connect the pins to them. """
# Copy the pins to the top level
# This will either be used to route or left unconnected.
for inst in self.insts:
self.copy_power_pins(inst,"vdd")
self.copy_power_pins(inst,"gnd")
import tech
if not OPTS.route_supplies:
# Do not route the power supply (leave as must-connect pins)
return
elif "metal4" in tech.layer:
# Route a M3/M4 grid
from supply_grid_router import supply_grid_router as router
rtr=router(("metal3","via3","metal4"), self)
elif "metal3" in tech.layer:
from supply_tree_router import supply_tree_router as router
rtr=router(("metal3",), self)
rtr.route()
def compute_bus_sizes(self):
""" Compute the independent bus widths shared between two and four bank SRAMs """
# address size + control signals + one-hot bank select signals
self.num_vertical_line = self.addr_size + self.control_size + log(self.num_banks,2) + 1
# data bus size
self.num_horizontal_line = self.word_size
self.vertical_bus_width = self.m2_pitch*self.num_vertical_line
# vertical bus height depends on 2 or 4 banks
self.data_bus_height = self.m3_pitch*self.num_horizontal_line
self.data_bus_width = 2*(self.bank.width + self.bank_to_bus_distance) + self.vertical_bus_width
self.control_bus_height = self.m1_pitch*(self.control_size+2)
self.control_bus_width = self.bank.width + self.bank_to_bus_distance + self.vertical_bus_width
self.supply_bus_height = self.m1_pitch*2 # 2 for vdd/gnd placed with control bus
self.supply_bus_width = self.data_bus_width
# Sanity check to ensure we can fit the control logic above a single bank (0.9 is a hack really)
debug.check(self.bank.width + self.vertical_bus_width > 0.9*self.control_logic.width,
"Bank is too small compared to control logic.")
def add_busses(self):
""" Add the horizontal and vertical busses """
# Vertical bus
# The order of the control signals on the control bus:
self.control_bus_names = []
for port in self.all_ports:
self.control_bus_names[port] = ["clk_buf{}".format(port)]
wen = "w_en{}".format(port)
sen = "s_en{}".format(port)
pen = "p_en_bar{}".format(port)
if self.port_id[port] == "r":
self.control_bus_names[port].extend([sen, pen])
elif self.port_id[port] == "w":
self.control_bus_names[port].extend([wen])
else:
self.control_bus_names[port].extend([sen, wen, pen])
self.vert_control_bus_positions = self.create_vertical_bus(layer="metal2",
pitch=self.m2_pitch,
offset=self.vertical_bus_offset,
names=self.control_bus_names[port],
length=self.vertical_bus_height)
self.addr_bus_names=["A{0}[{1}]".format(port,i) for i in range(self.addr_size)]
self.vert_control_bus_positions.update(self.create_vertical_pin_bus(layer="metal2",
pitch=self.m2_pitch,
offset=self.addr_bus_offset,
names=self.addr_bus_names,
length=self.addr_bus_height))
self.bank_sel_bus_names = ["bank_sel{0}_{1}".format(port,i) for i in range(self.num_banks)]
self.vert_control_bus_positions.update(self.create_vertical_pin_bus(layer="metal2",
pitch=self.m2_pitch,
offset=self.bank_sel_bus_offset,
names=self.bank_sel_bus_names,
length=self.vertical_bus_height))
# Horizontal data bus
self.data_bus_names = ["DATA{0}[{1}]".format(port,i) for i in range(self.word_size)]
self.data_bus_positions = self.create_horizontal_pin_bus(layer="metal3",
pitch=self.m3_pitch,
offset=self.data_bus_offset,
names=self.data_bus_names,
length=self.data_bus_width)
# Horizontal control logic bus
# vdd/gnd in bus go along whole SRAM
# FIXME: Fatten these wires?
self.horz_control_bus_positions = self.create_horizontal_bus(layer="metal1",
pitch=self.m1_pitch,
offset=self.supply_bus_offset,
names=["vdd"],
length=self.supply_bus_width)
# The gnd rail must not be the entire width since we protrude the right-most vdd rail up for
# the decoder in 4-bank SRAMs
self.horz_control_bus_positions.update(self.create_horizontal_bus(layer="metal1",
pitch=self.m1_pitch,
offset=self.supply_bus_offset+vector(0,self.m1_pitch),
names=["gnd"],
length=self.supply_bus_width))
self.horz_control_bus_positions.update(self.create_horizontal_bus(layer="metal1",
pitch=self.m1_pitch,
offset=self.control_bus_offset,
names=self.control_bus_names[port],
length=self.control_bus_width))
def add_multi_bank_modules(self):
""" Create the multibank address flops and bank decoder """
from dff_buf_array import dff_buf_array
self.msb_address = dff_buf_array(name="msb_address",
rows=1,
columns=self.num_banks/2)
self.add_mod(self.msb_address)
if self.num_banks>2:
self.msb_decoder = self.bank.decoder.pre2_4
self.add_mod(self.msb_decoder)
def add_modules(self):
self.bitcell = factory.create(module_type=OPTS.bitcell)
# Create the address and control flops (but not the clk)
from dff_array import dff_array
self.row_addr_dff = dff_array(name="row_addr_dff", rows=self.row_addr_size, columns=1)
self.add_mod(self.row_addr_dff)
if self.col_addr_size > 0:
self.col_addr_dff = dff_array(name="col_addr_dff", rows=1, columns=self.col_addr_size)
self.add_mod(self.col_addr_dff)
else:
self.col_addr_dff = None
self.data_dff = dff_array(name="data_dff", rows=1, columns=self.word_size)
self.add_mod(self.data_dff)
# Create the bank module (up to four are instantiated)
from bank import bank
self.bank = bank(self.sram_config,
name="bank")
self.add_mod(self.bank)
# Create bank decoder
if(self.num_banks > 1):
self.add_multi_bank_modules()
self.bank_count = 0
self.supply_rail_width = self.bank.supply_rail_width
self.supply_rail_pitch = self.bank.supply_rail_pitch
#The control logic can resize itself based on the other modules. Requires all other modules added before control logic.
self.all_mods_except_control_done = True
c = reload(__import__(OPTS.control_logic))
self.mod_control_logic = getattr(c, OPTS.control_logic)
# Create the control logic module for each port type
if len(self.readwrite_ports)>0:
self.control_logic_rw = self.mod_control_logic(num_rows=self.num_rows,
words_per_row=self.words_per_row,
word_size=self.word_size,
sram=self,
port_type="rw")
self.add_mod(self.control_logic_rw)
if len(self.writeonly_ports)>0:
self.control_logic_w = self.mod_control_logic(num_rows=self.num_rows,
words_per_row=self.words_per_row,
word_size=self.word_size,
sram=self,
port_type="w")
self.add_mod(self.control_logic_w)
if len(self.readonly_ports)>0:
self.control_logic_r = self.mod_control_logic(num_rows=self.num_rows,
words_per_row=self.words_per_row,
word_size=self.word_size,
sram=self,
port_type="r")
self.add_mod(self.control_logic_r)
def create_bank(self,bank_num):
""" Create a bank """
self.bank_insts.append(self.add_inst(name="bank{0}".format(bank_num),
mod=self.bank))
temp = []
for port in self.read_ports:
for bit in range(self.word_size):
temp.append("DOUT{0}[{1}]".format(port,bit))
for port in self.write_ports:
for bit in range(self.word_size):
temp.append("BANK_DIN{0}[{1}]".format(port,bit))
for port in self.all_ports:
for bit in range(self.bank_addr_size):
temp.append("A{0}[{1}]".format(port,bit))
if(self.num_banks > 1):
for port in self.all_ports:
temp.append("bank_sel{0}[{1}]".format(port,bank_num))
for port in self.read_ports:
temp.append("s_en{0}".format(port))
for port in self.read_ports:
temp.append("p_en_bar{0}".format(port))
for port in self.write_ports:
temp.append("w_en{0}".format(port))
for port in self.all_ports:
temp.append("wl_en{0}".format(port))
temp.extend(["vdd", "gnd"])
self.connect_inst(temp)
return self.bank_insts[-1]
def place_bank(self, bank_inst, position, x_flip, y_flip):
""" Place a bank at the given position with orientations """
# x_flip == 1 --> no flip in x_axis
# x_flip == -1 --> flip in x_axis
# y_flip == 1 --> no flip in y_axis
# y_flip == -1 --> flip in y_axis
# x_flip and y_flip are used for position translation
if x_flip == -1 and y_flip == -1:
bank_rotation = 180
else:
bank_rotation = 0
if x_flip == y_flip:
bank_mirror = "R0"
elif x_flip == -1:
bank_mirror = "MX"
elif y_flip == -1:
bank_mirror = "MY"
else:
bank_mirror = "R0"
bank_inst.place(offset=position,
mirror=bank_mirror,
rotate=bank_rotation)
return bank_inst
def create_row_addr_dff(self):
""" Add all address flops for the main decoder """
insts = []
for port in self.all_ports:
insts.append(self.add_inst(name="row_address{}".format(port),
mod=self.row_addr_dff))
# inputs, outputs/output/bar
inputs = []
outputs = []
for bit in range(self.row_addr_size):
inputs.append("ADDR{}[{}]".format(port,bit+self.col_addr_size))
outputs.append("A{}[{}]".format(port,bit+self.col_addr_size))
self.connect_inst(inputs + outputs + ["clk_buf{}".format(port), "vdd", "gnd"])
return insts
def create_col_addr_dff(self):
""" Add and place all address flops for the column decoder """
insts = []
for port in self.all_ports:
insts.append(self.add_inst(name="col_address{}".format(port),
mod=self.col_addr_dff))
# inputs, outputs/output/bar
inputs = []
outputs = []
for bit in range(self.col_addr_size):
inputs.append("ADDR{}[{}]".format(port,bit))
outputs.append("A{}[{}]".format(port,bit))
self.connect_inst(inputs + outputs + ["clk_buf{}".format(port), "vdd", "gnd"])
return insts
def create_data_dff(self):
""" Add and place all data flops """
insts = []
for port in self.all_ports:
if port in self.write_ports:
insts.append(self.add_inst(name="data_dff{}".format(port),
mod=self.data_dff))
else:
insts.append(None)
continue
# inputs, outputs/output/bar
inputs = []
outputs = []
for bit in range(self.word_size):
inputs.append("DIN{}[{}]".format(port,bit))
outputs.append("BANK_DIN{}[{}]".format(port,bit))
self.connect_inst(inputs + outputs + ["clk_buf{}".format(port), "vdd", "gnd"])
return insts
def create_control_logic(self):
""" Add control logic instances """
insts = []
for port in self.all_ports:
if port in self.readwrite_ports:
mod = self.control_logic_rw
elif port in self.write_ports:
mod = self.control_logic_w
else:
mod = self.control_logic_r
insts.append(self.add_inst(name="control{}".format(port), mod=mod))
# Inputs
temp = ["csb{}".format(port)]
if port in self.readwrite_ports:
temp.append("web{}".format(port))
temp.append("clk{}".format(port))
# Ouputs
if port in self.read_ports:
temp.append("s_en{}".format(port))
if port in self.write_ports:
temp.append("w_en{}".format(port))
if port in self.read_ports:
temp.append("p_en_bar{}".format(port))
temp.extend(["wl_en{}".format(port), "clk_buf{}".format(port), "vdd", "gnd"])
self.connect_inst(temp)
return insts
def connect_rail_from_left_m2m3(self, src_pin, dest_pin):
""" Helper routine to connect an unrotated/mirrored oriented instance to the rails """
in_pos = src_pin.rc()
out_pos = dest_pin.center()
self.add_wire(("metal3","via2","metal2"),[in_pos, vector(out_pos.x,in_pos.y),out_pos])
self.add_via_center(layers=("metal2","via2","metal3"),
offset=src_pin.rc())
def connect_rail_from_left_m2m1(self, src_pin, dest_pin):
""" Helper routine to connect an unrotated/mirrored oriented instance to the rails """
in_pos = src_pin.rc()
out_pos = vector(dest_pin.cx(), in_pos.y)
self.add_wire(("metal2","via1","metal1"),[in_pos, out_pos, out_pos - vector(0,self.m2_pitch)])
def sp_write(self, sp_name):
# Write the entire spice of the object to the file
############################################################
# Spice circuit
############################################################
sp = open(sp_name, 'w')
sp.write("**************************************************\n")
sp.write("* OpenRAM generated memory.\n")
sp.write("* Words: {}\n".format(self.num_words))
sp.write("* Data bits: {}\n".format(self.word_size))
sp.write("* Banks: {}\n".format(self.num_banks))
sp.write("* Column mux: {}:1\n".format(self.words_per_row))
sp.write("**************************************************\n")
# This causes unit test mismatch
# sp.write("* Created: {0}\n".format(datetime.datetime.now()))
# sp.write("* User: {0}\n".format(getpass.getuser()))
# sp.write(".global {0} {1}\n".format(spice["vdd_name"],
# spice["gnd_name"]))
usedMODS = list()
self.sp_write_file(sp, usedMODS)
del usedMODS
sp.close()
def analytical_delay(self, corner, slew,load):
""" Estimates the delay from clk -> DOUT
LH and HL are the same in analytical model. """
delays = {}
for port in self.all_ports:
if port in self.readonly_ports:
control_logic = self.control_logic_r
elif port in self.readwrite_ports:
control_logic = self.control_logic_rw
else:
continue
clk_to_wlen_delays = control_logic.analytical_delay(corner, slew, load)
wlen_to_dout_delays = self.bank.analytical_delay(corner,slew,load,port) #port should probably be specified...
all_delays = clk_to_wlen_delays+wlen_to_dout_delays
total_delay = logical_effort.calculate_absolute_delay(all_delays)
total_delay = self.apply_corners_analytically(total_delay, corner)
last_slew = .1*all_delays[-1].get_absolute_delay() #slew approximated as 10% of delay
last_slew = self.apply_corners_analytically(last_slew, corner)
delays[port] = self.return_delay(delay=total_delay, slew=last_slew)
return delays
def get_wordline_stage_efforts(self, inp_is_rise=True):
"""Get the all the stage efforts for each stage in the path from clk_buf to a wordline"""
stage_effort_list = []
#Clk_buf originates from the control logic so only the bank is related to the wordline path
external_wordline_cout = 0 #No loading on the wordline other than in the bank.
stage_effort_list += self.bank.determine_wordline_stage_efforts(external_wordline_cout, inp_is_rise)
return stage_effort_list
def get_wl_en_cin(self):
"""Gets the capacitive load the of clock (clk_buf) for the sram"""
#Only the wordline drivers within the bank use this signal
return self.bank.get_wl_en_cin()
def get_w_en_cin(self):
"""Gets the capacitive load the of write enable (w_en) for the sram"""
#Only the write drivers within the bank use this signal
return self.bank.get_w_en_cin()
def get_p_en_bar_cin(self):
"""Gets the capacitive load the of precharge enable (p_en_bar) for the sram"""
#Only the precharges within the bank use this signal
return self.bank.get_p_en_bar_cin()
def get_clk_bar_cin(self):
"""Gets the capacitive load the of clock (clk_buf_bar) for the sram"""
#As clk_buf_bar is an output of the control logic. The cap for that module is not determined here.
#Only the precharge cells use this signal (other than the control logic)
return self.bank.get_clk_bar_cin()
def get_sen_cin(self):
"""Gets the capacitive load the of sense amp enable for the sram"""
#Only the sense_amps use this signal (other than the control logic)
return self.bank.get_sen_cin()
def get_dff_clk_buf_cin(self):
"""Get the relative capacitance of the clk_buf signal.
Does not get the control logic loading but everything else"""
total_cin = 0
total_cin += self.row_addr_dff.get_clk_cin()
total_cin += self.data_dff.get_clk_cin()
if self.col_addr_size > 0:
total_cin += self.col_addr_dff.get_clk_cin()
return total_cin
+118
View File
@@ -0,0 +1,118 @@
# See LICENSE for licensing information.
#
# 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.
#
import debug
from math import log,sqrt,ceil
from importlib import reload
from globals import OPTS
from sram_factory import factory
class sram_config:
""" This is a structure that is used to hold the SRAM configuration options. """
def __init__(self, word_size, num_words, num_banks=1, words_per_row=None):
self.word_size = word_size
self.num_words = num_words
self.num_banks = num_banks
# This will get over-written when we determine the organization
self.words_per_row = words_per_row
self.compute_sizes()
def set_local_config(self, module):
""" Copy all of the member variables to the given module for convenience """
members = [attr for attr in dir(self) if not callable(getattr(self, attr)) and not attr.startswith("__")]
# Copy all the variables to the local module
for member in members:
setattr(module,member,getattr(self,member))
def compute_sizes(self):
""" Computes the organization of the memory using bitcell size by trying to make it square."""
self.bitcell = factory.create(module_type="bitcell")
debug.check(self.num_banks in [1,2,4], "Valid number of banks are 1 , 2 and 4.")
self.num_words_per_bank = self.num_words/self.num_banks
self.num_bits_per_bank = self.word_size*self.num_words_per_bank
# If this was hard coded, don't dynamically compute it!
if not self.words_per_row:
# Compute the area of the bitcells and estimate a square bank (excluding auxiliary circuitry)
self.bank_area = self.bitcell.width*self.bitcell.height*self.num_bits_per_bank
self.bank_side_length = sqrt(self.bank_area)
# Estimate the words per row given the height of the bitcell and the square side length
self.tentative_num_cols = int(self.bank_side_length/self.bitcell.width)
self.words_per_row = self.estimate_words_per_row(self.tentative_num_cols, self.word_size)
# Estimate the number of rows given the tentative words per row
self.tentative_num_rows = self.num_bits_per_bank / (self.words_per_row*self.word_size)
self.words_per_row = self.amend_words_per_row(self.tentative_num_rows, self.words_per_row)
debug.info(1,"Words per row: {}".format(self.words_per_row))
self.recompute_sizes()
def recompute_sizes(self):
"""
Calculate the auxiliary values assuming fixed number of words per row.
This can be called multiple times from the unit test when we reconfigure an
SRAM for testing.
"""
# If the banks changed
self.num_words_per_bank = self.num_words/self.num_banks
self.num_bits_per_bank = self.word_size*self.num_words_per_bank
# Fix the number of columns and rows
self.num_cols = int(self.words_per_row*self.word_size)
self.num_rows = int(self.num_words_per_bank/self.words_per_row)
# Compute the address and bank sizes
self.row_addr_size = int(log(self.num_rows, 2))
self.col_addr_size = int(log(self.words_per_row, 2))
self.bank_addr_size = self.col_addr_size + self.row_addr_size
self.addr_size = self.bank_addr_size + int(log(self.num_banks, 2))
def estimate_words_per_row(self,tentative_num_cols, word_size):
"""
This provides a heuristic rounded estimate for the number of words
per row.
"""
if tentative_num_cols < 1.5*word_size:
return 1
elif tentative_num_cols < 3*word_size:
return 2
elif tentative_num_cols < 6*word_size:
return 4
else:
if tentative_num_cols > 16*word_size:
debug.warning("Reaching column mux size limit. Consider increasing above 8-way.")
return 8
def amend_words_per_row(self,tentative_num_rows, words_per_row):
"""
This picks the number of words per row more accurately by limiting
it to a minimum and maximum.
"""
# Recompute the words per row given a hard max
if(not OPTS.is_unit_test and tentative_num_rows > 512):
debug.check(tentative_num_rows*words_per_row <= 2048, "Number of words exceeds 2048")
return int(words_per_row*tentative_num_rows/512)
# Recompute the words per row given a hard min
if(not OPTS.is_unit_test and tentative_num_rows < 16):
debug.check(tentative_num_rows*words_per_row >= 16, "Minimum number of rows is 16, but given {0}".format(tentative_num_rows))
return int(words_per_row*tentative_num_rows/16)
return words_per_row