mirror of https://github.com/VLSIDA/OpenRAM.git
Merge remote-tracking branch 'private/dev' into dev
This commit is contained in:
commit
2c8a52f32d
|
|
@ -13,3 +13,6 @@ technology/freepdk45/ncsu_basekit
|
|||
technology/sky130/*_lib
|
||||
technology/sky130/tech/.magicrc
|
||||
.idea
|
||||
compiler/tests/results/
|
||||
sky*/
|
||||
open_pdks/
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ class design(hierarchy_design):
|
|||
for pin_name in self.pins:
|
||||
pins = self.get_pins(pin_name)
|
||||
for pin in pins:
|
||||
print(pin_name, pin)
|
||||
debug.info(0, "{0} {1}".format(pin_name, pin))
|
||||
|
||||
def setup_multiport_constants(self):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -141,30 +141,28 @@ class layout():
|
|||
layout.active_space)
|
||||
|
||||
# These are for debugging previous manual rules
|
||||
if False:
|
||||
print("poly_width", layout.poly_width)
|
||||
print("poly_space", layout.poly_space)
|
||||
print("m1_width", layout.m1_width)
|
||||
print("m1_space", layout.m1_space)
|
||||
print("m2_width", layout.m2_width)
|
||||
print("m2_space", layout.m2_space)
|
||||
print("m3_width", layout.m3_width)
|
||||
print("m3_space", layout.m3_space)
|
||||
print("m4_width", layout.m4_width)
|
||||
print("m4_space", layout.m4_space)
|
||||
print("active_width", layout.active_width)
|
||||
print("active_space", layout.active_space)
|
||||
print("contact_width", layout.contact_width)
|
||||
print("poly_to_active", layout.poly_to_active)
|
||||
print("poly_extend_active", layout.poly_extend_active)
|
||||
print("poly_to_contact", layout.poly_to_contact)
|
||||
print("active_contact_to_gate", layout.active_contact_to_gate)
|
||||
print("poly_contact_to_gate", layout.poly_contact_to_gate)
|
||||
print("well_enclose_active", layout.well_enclose_active)
|
||||
print("implant_enclose_active", layout.implant_enclose_active)
|
||||
print("implant_space", layout.implant_space)
|
||||
import sys
|
||||
sys.exit(1)
|
||||
level=99
|
||||
debug.info(level, "poly_width".format(layout.poly_width))
|
||||
debug.info(level, "poly_space".format(layout.poly_space))
|
||||
debug.info(level, "m1_width".format(layout.m1_width))
|
||||
debug.info(level, "m1_space".format(layout.m1_space))
|
||||
debug.info(level, "m2_width".format(layout.m2_width))
|
||||
debug.info(level, "m2_space".format(layout.m2_space))
|
||||
debug.info(level, "m3_width".format(layout.m3_width))
|
||||
debug.info(level, "m3_space".format(layout.m3_space))
|
||||
debug.info(level, "m4_width".format(layout.m4_width))
|
||||
debug.info(level, "m4_space".format(layout.m4_space))
|
||||
debug.info(level, "active_width".format(layout.active_width))
|
||||
debug.info(level, "active_space".format(layout.active_space))
|
||||
debug.info(level, "contact_width".format(layout.contact_width))
|
||||
debug.info(level, "poly_to_active".format(layout.poly_to_active))
|
||||
debug.info(level, "poly_extend_active".format(layout.poly_extend_active))
|
||||
debug.info(level, "poly_to_contact".format(layout.poly_to_contact))
|
||||
debug.info(level, "active_contact_to_gate".format(layout.active_contact_to_gate))
|
||||
debug.info(level, "poly_contact_to_gate".format(layout.poly_contact_to_gate))
|
||||
debug.info(level, "well_enclose_active".format(layout.well_enclose_active))
|
||||
debug.info(level, "implant_enclose_active".format(layout.implant_enclose_active))
|
||||
debug.info(level, "implant_space".format(layout.implant_space))
|
||||
|
||||
@classmethod
|
||||
def setup_layer_constants(layout):
|
||||
|
|
@ -202,21 +200,19 @@ class layout():
|
|||
"{}_nonpref_pitch".format(layer_id),
|
||||
layout.compute_pitch(layer_id, False))
|
||||
|
||||
if False:
|
||||
for name in tech_layer_indices:
|
||||
if name == "active":
|
||||
continue
|
||||
try:
|
||||
print("{0} width {1} space {2}".format(name,
|
||||
getattr(layout, "{}_width".format(name)),
|
||||
getattr(layout, "{}_space".format(name))))
|
||||
level=99
|
||||
for name in tech_layer_indices:
|
||||
if name == "active":
|
||||
continue
|
||||
try:
|
||||
debug.info(level, "{0} width {1} space {2}".format(name,
|
||||
getattr(layout, "{}_width".format(name)),
|
||||
getattr(layout, "{}_space".format(name))))
|
||||
|
||||
print("pitch {0} nonpref {1}".format(getattr(layout, "{}_pitch".format(name)),
|
||||
getattr(layout, "{}_nonpref_pitch".format(name))))
|
||||
except AttributeError:
|
||||
pass
|
||||
import sys
|
||||
sys.exit(1)
|
||||
debug.info(level, "pitch {0} nonpref {1}".format(getattr(layout, "{}_pitch".format(name)),
|
||||
getattr(layout, "{}_nonpref_pitch".format(name))))
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def compute_pitch(layer, preferred=True):
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ class verilog:
|
|||
self.vf.write("// OpenRAM SRAM model\n")
|
||||
self.vf.write("// Words: {0}\n".format(self.num_words))
|
||||
self.vf.write("// Word size: {0}\n".format(self.word_size))
|
||||
if self.write_size:
|
||||
if self.write_size != self.word_size:
|
||||
self.vf.write("// Write size: {0}\n\n".format(self.write_size))
|
||||
else:
|
||||
self.vf.write("\n")
|
||||
|
|
@ -38,7 +38,10 @@ class verilog:
|
|||
except KeyError:
|
||||
self.gnd_name = "gnd"
|
||||
|
||||
self.vf.write("module {0}(\n".format(self.name))
|
||||
if self.num_banks > 1:
|
||||
self.vf.write("module {0}(\n".format(self.name))
|
||||
else:
|
||||
self.vf.write("module {0}(\n".format(self.name))
|
||||
self.vf.write("`ifdef USE_POWER_PINS\n")
|
||||
self.vf.write(" {},\n".format(self.vdd_name))
|
||||
self.vf.write(" {},\n".format(self.gnd_name))
|
||||
|
|
@ -53,14 +56,14 @@ class verilog:
|
|||
self.vf.write("// Port {0}: W\n".format(port))
|
||||
if port in self.readwrite_ports:
|
||||
self.vf.write(" clk{0},csb{0},web{0},".format(port))
|
||||
if self.write_size:
|
||||
if self.write_size != self.word_size:
|
||||
self.vf.write("wmask{},".format(port))
|
||||
if self.num_spare_cols > 0:
|
||||
self.vf.write("spare_wen{0},".format(port))
|
||||
self.vf.write("addr{0},din{0},dout{0}".format(port))
|
||||
elif port in self.write_ports:
|
||||
self.vf.write(" clk{0},csb{0},".format(port))
|
||||
if self.write_size:
|
||||
if self.write_size != self.word_size:
|
||||
self.vf.write("wmask{},".format(port))
|
||||
if self.num_spare_cols > 0:
|
||||
self.vf.write("spare_wen{0},".format(port))
|
||||
|
|
@ -72,11 +75,11 @@ class verilog:
|
|||
self.vf.write(",\n")
|
||||
self.vf.write("\n );\n\n")
|
||||
|
||||
if self.write_size:
|
||||
if self.write_size != self.word_size:
|
||||
self.num_wmasks = int(math.ceil(self.word_size / self.write_size))
|
||||
self.vf.write(" parameter NUM_WMASKS = {0} ;\n".format(self.num_wmasks))
|
||||
self.vf.write(" parameter DATA_WIDTH = {0} ;\n".format(self.word_size + self.num_spare_cols))
|
||||
self.vf.write(" parameter ADDR_WIDTH = {0} ;\n".format(self.addr_size))
|
||||
self.vf.write(" parameter ADDR_WIDTH = {0} ;\n".format(self.bank_addr_size))
|
||||
self.vf.write(" parameter RAM_DEPTH = 1 << ADDR_WIDTH;\n")
|
||||
self.vf.write(" // FIXME: This delay is arbitrary.\n")
|
||||
self.vf.write(" parameter DELAY = 3 ;\n")
|
||||
|
|
@ -125,7 +128,7 @@ class verilog:
|
|||
if port in self.readwrite_ports:
|
||||
self.vf.write(" reg web{0}_reg;\n".format(port))
|
||||
if port in self.write_ports:
|
||||
if self.write_size:
|
||||
if self.write_size != self.word_size:
|
||||
self.vf.write(" reg [NUM_WMASKS-1:0] wmask{0}_reg;\n".format(port))
|
||||
if self.num_spare_cols > 1:
|
||||
self.vf.write(" reg [{1}:0] spare_wen{0}_reg;".format(port, self.num_spare_cols - 1))
|
||||
|
|
@ -149,7 +152,7 @@ class verilog:
|
|||
if port in self.readwrite_ports:
|
||||
self.vf.write(" web{0}_reg = web{0};\n".format(port))
|
||||
if port in self.write_ports:
|
||||
if self.write_size:
|
||||
if self.write_size != self.word_size:
|
||||
self.vf.write(" wmask{0}_reg = wmask{0};\n".format(port))
|
||||
if self.num_spare_cols:
|
||||
self.vf.write(" spare_wen{0}_reg = spare_wen{0};\n".format(port))
|
||||
|
|
@ -169,13 +172,13 @@ class verilog:
|
|||
self.vf.write(" $display($time,\" Reading %m addr{0}=%b dout{0}=%b\",addr{0}_reg,mem[addr{0}_reg]);\n".format(port))
|
||||
if port in self.readwrite_ports:
|
||||
self.vf.write(" if ( !csb{0}_reg && !web{0}_reg && VERBOSE )\n".format(port))
|
||||
if self.write_size:
|
||||
if self.write_size != self.word_size:
|
||||
self.vf.write(" $display($time,\" Writing %m addr{0}=%b din{0}=%b wmask{0}=%b\",addr{0}_reg,din{0}_reg,wmask{0}_reg);\n".format(port))
|
||||
else:
|
||||
self.vf.write(" $display($time,\" Writing %m addr{0}=%b din{0}=%b\",addr{0}_reg,din{0}_reg);\n".format(port))
|
||||
elif port in self.write_ports:
|
||||
self.vf.write(" if ( !csb{0}_reg && VERBOSE )\n".format(port))
|
||||
if self.write_size:
|
||||
if self.write_size != self.word_size:
|
||||
self.vf.write(" $display($time,\" Writing %m addr{0}=%b din{0}=%b wmask{0}=%b\",addr{0}_reg,din{0}_reg,wmask{0}_reg);\n".format(port))
|
||||
else:
|
||||
self.vf.write(" $display($time,\" Writing %m addr{0}=%b din{0}=%b\",addr{0}_reg,din{0}_reg);\n".format(port))
|
||||
|
|
@ -193,7 +196,7 @@ class verilog:
|
|||
|
||||
self.vf.write(" input [ADDR_WIDTH-1:0] addr{0};\n".format(port))
|
||||
if port in self.write_ports:
|
||||
if self.write_size:
|
||||
if self.write_size != self.word_size:
|
||||
self.vf.write(" input [NUM_WMASKS-1:0] wmask{0}; // write mask\n".format(port))
|
||||
if self.num_spare_cols == 1:
|
||||
self.vf.write(" input spare_wen{0}; // spare mask\n".format(port))
|
||||
|
|
@ -218,7 +221,7 @@ class verilog:
|
|||
else:
|
||||
self.vf.write(" if (!csb{0}_reg) begin\n".format(port))
|
||||
|
||||
if self.write_size:
|
||||
if self.write_size != self.word_size:
|
||||
for mask in range(0, self.num_wmasks):
|
||||
lower = mask * self.write_size
|
||||
upper = lower + self.write_size - 1
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ class cacti(simulation):
|
|||
# self.targ_read_ports = []
|
||||
# self.targ_write_ports = []
|
||||
# self.period = 0
|
||||
# if self.write_size:
|
||||
# if self.write_size != self.word_size:
|
||||
# self.num_wmasks = int(math.ceil(self.word_size / self.write_size))
|
||||
# else:
|
||||
# self.num_wmasks = 0
|
||||
|
|
@ -51,7 +51,7 @@ class cacti(simulation):
|
|||
debug.warning("In analytical mode, all ports have the timing of the first read port.")
|
||||
|
||||
# Probe set to 0th bit, does not matter for analytical delay.
|
||||
self.set_probe('0' * self.addr_size, 0)
|
||||
self.set_probe('0' * self.bank_addr_size, 0)
|
||||
self.create_graph()
|
||||
self.set_internal_spice_names()
|
||||
self.create_measurement_names()
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ class delay(simulation):
|
|||
self.targ_read_ports = []
|
||||
self.targ_write_ports = []
|
||||
self.period = 0
|
||||
if self.write_size:
|
||||
if self.write_size != self.word_size:
|
||||
self.num_wmasks = int(math.ceil(self.word_size / self.write_size))
|
||||
else:
|
||||
self.num_wmasks = 0
|
||||
|
|
@ -342,7 +342,7 @@ class delay(simulation):
|
|||
except ValueError:
|
||||
debug.error("Probe Address is not of binary form: {0}".format(self.probe_address), 1)
|
||||
|
||||
if len(self.probe_address) != self.addr_size:
|
||||
if len(self.probe_address) != self.bank_addr_size:
|
||||
debug.error("Probe Address's number of bits does not correspond to given SRAM", 1)
|
||||
|
||||
if not isinstance(self.probe_data, int) or self.probe_data>self.word_size or self.probe_data<0:
|
||||
|
|
@ -455,7 +455,7 @@ class delay(simulation):
|
|||
self.stim.gen_constant(sig_name="{0}{1}_{2} ".format(self.din_name, write_port, i),
|
||||
v_val=0)
|
||||
for port in self.all_ports:
|
||||
for i in range(self.addr_size):
|
||||
for i in range(self.bank_addr_size):
|
||||
self.stim.gen_constant(sig_name="{0}{1}_{2}".format(self.addr_name, port, i),
|
||||
v_val=0)
|
||||
|
||||
|
|
@ -1391,7 +1391,7 @@ class delay(simulation):
|
|||
"""
|
||||
|
||||
for port in self.all_ports:
|
||||
for i in range(self.addr_size):
|
||||
for i in range(self.bank_addr_size):
|
||||
sig_name = "{0}{1}_{2}".format(self.addr_name, port, i)
|
||||
self.stim.gen_pwl(sig_name, self.cycle_times, self.addr_values[port][i], self.period, self.slew, 0.05)
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ class elmore(simulation):
|
|||
# self.targ_read_ports = []
|
||||
# self.targ_write_ports = []
|
||||
# self.period = 0
|
||||
# if self.write_size:
|
||||
# if self.write_size != self.word_size:
|
||||
# self.num_wmasks = int(math.ceil(self.word_size / self.write_size))
|
||||
# else:
|
||||
# self.num_wmasks = 0
|
||||
|
|
@ -45,7 +45,7 @@ class elmore(simulation):
|
|||
debug.warning("In analytical mode, all ports have the timing of the first read port.")
|
||||
|
||||
# Probe set to 0th bit, does not matter for analytical delay.
|
||||
self.set_probe('0' * self.addr_size, 0)
|
||||
self.set_probe('0' * self.bank_addr_size, 0)
|
||||
self.create_graph()
|
||||
self.set_internal_spice_names()
|
||||
self.create_measurement_names()
|
||||
|
|
@ -106,4 +106,4 @@ class elmore(simulation):
|
|||
power.leakage /= 1e6
|
||||
debug.info(1, "Dynamic Power: {0} mW".format(power.dynamic))
|
||||
debug.info(1, "Leakage Power: {0} mW".format(power.leakage))
|
||||
return power
|
||||
return power
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ class functional(simulation):
|
|||
else:
|
||||
self.output_path = output_path
|
||||
|
||||
if self.write_size:
|
||||
if self.write_size != self.word_size:
|
||||
self.num_wmasks = int(math.ceil(self.word_size / self.write_size))
|
||||
else:
|
||||
self.num_wmasks = 0
|
||||
|
|
@ -60,7 +60,7 @@ class functional(simulation):
|
|||
self.addr_spare_index = -int(math.log(self.words_per_row) / math.log(2))
|
||||
else:
|
||||
# This will select the entire address when one word per row
|
||||
self.addr_spare_index = self.addr_size
|
||||
self.addr_spare_index = self.bank_addr_size
|
||||
# If trim is set, specify the valid addresses
|
||||
self.valid_addresses = set()
|
||||
self.max_address = self.num_rows * self.words_per_row - 1
|
||||
|
|
@ -68,7 +68,7 @@ class functional(simulation):
|
|||
for i in range(self.words_per_row):
|
||||
self.valid_addresses.add(i)
|
||||
self.valid_addresses.add(self.max_address - i - 1)
|
||||
self.probe_address, self.probe_data = '0' * self.addr_size, 0
|
||||
self.probe_address, self.probe_data = '0' * self.bank_addr_size, 0
|
||||
self.set_corner(corner)
|
||||
self.set_spice_constants()
|
||||
self.set_stimulus_variables()
|
||||
|
|
@ -133,7 +133,7 @@ class functional(simulation):
|
|||
|
||||
def create_random_memory_sequence(self):
|
||||
# Select randomly, but have 3x more reads to increase probability
|
||||
if self.write_size:
|
||||
if self.write_size != self.word_size:
|
||||
rw_ops = ["noop", "write", "partial_write", "read", "read"]
|
||||
w_ops = ["noop", "write", "partial_write"]
|
||||
else:
|
||||
|
|
@ -142,7 +142,7 @@ class functional(simulation):
|
|||
r_ops = ["noop", "read"]
|
||||
|
||||
# First cycle idle is always an idle cycle
|
||||
comment = self.gen_cycle_comment("noop", "0" * self.word_size, "0" * self.addr_size, "0" * self.num_wmasks, 0, self.t_current)
|
||||
comment = self.gen_cycle_comment("noop", "0" * self.word_size, "0" * self.bank_addr_size, "0" * self.num_wmasks, 0, self.t_current)
|
||||
self.add_noop_all_ports(comment)
|
||||
|
||||
|
||||
|
|
@ -244,7 +244,7 @@ class functional(simulation):
|
|||
self.t_current += self.period
|
||||
|
||||
# Last cycle idle needed to correctly measure the value on the second to last clock edge
|
||||
comment = self.gen_cycle_comment("noop", "0" * self.word_size, "0" * self.addr_size, "0" * self.num_wmasks, 0, self.t_current)
|
||||
comment = self.gen_cycle_comment("noop", "0" * self.word_size, "0" * self.bank_addr_size, "0" * self.num_wmasks, 0, self.t_current)
|
||||
self.add_noop_all_ports(comment)
|
||||
|
||||
def gen_masked_data(self, old_word, word, wmask):
|
||||
|
|
@ -366,7 +366,7 @@ class functional(simulation):
|
|||
random_value = random.sample(self.valid_addresses, 1)[0]
|
||||
else:
|
||||
random_value = random.randint(0, self.max_address)
|
||||
addr_bits = binary_repr(random_value, self.addr_size)
|
||||
addr_bits = binary_repr(random_value, self.bank_addr_size)
|
||||
return addr_bits
|
||||
|
||||
def get_data(self):
|
||||
|
|
@ -426,7 +426,7 @@ class functional(simulation):
|
|||
|
||||
# Generate address bits
|
||||
for port in self.all_ports:
|
||||
for bit in range(self.addr_size):
|
||||
for bit in range(self.bank_addr_size):
|
||||
sig_name="{0}{1}_{2} ".format(self.addr_name, port, bit)
|
||||
self.stim.gen_pwl(sig_name, self.cycle_times, self.addr_values[port][bit], self.period, self.slew, 0.05)
|
||||
|
||||
|
|
@ -440,7 +440,7 @@ class functional(simulation):
|
|||
|
||||
# Generate wmask bits
|
||||
for port in self.write_ports:
|
||||
if self.write_size:
|
||||
if self.write_size != self.word_size:
|
||||
self.sf.write("\n* Generation of wmask signals\n")
|
||||
for bit in range(self.num_wmasks):
|
||||
sig_name = "WMASK{0}_{1} ".format(port, bit)
|
||||
|
|
|
|||
|
|
@ -183,7 +183,8 @@ class lib:
|
|||
# set the read and write port as inputs.
|
||||
self.write_data_bus(port)
|
||||
self.write_addr_bus(port)
|
||||
if self.sram.write_size and port in self.write_ports:
|
||||
if self.sram.write_size != self.sram.word_size and \
|
||||
port in self.write_ports:
|
||||
self.write_wmask_bus(port)
|
||||
# need to split this into sram and port control signals
|
||||
self.write_control_pins(port)
|
||||
|
|
@ -193,8 +194,8 @@ class lib:
|
|||
|
||||
def write_footer(self):
|
||||
""" Write the footer """
|
||||
self.lib.write(" }\n") #Closing brace for the cell
|
||||
self.lib.write("}\n") #Closing brace for the library
|
||||
self.lib.write(" }\n") # Closing brace for the cell
|
||||
self.lib.write("}\n") # Closing brace for the library
|
||||
|
||||
def write_header(self):
|
||||
""" Write the header information """
|
||||
|
|
@ -378,7 +379,7 @@ class lib:
|
|||
self.lib.write(" bit_to : 0;\n")
|
||||
self.lib.write(" }\n\n")
|
||||
|
||||
if self.sram.write_size:
|
||||
if self.sram.write_size != self.sram.word_size:
|
||||
self.lib.write(" type (wmask){\n")
|
||||
self.lib.write(" base_type : array;\n")
|
||||
self.lib.write(" data_type : bit;\n")
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ class simulation():
|
|||
|
||||
self.name = self.sram.name
|
||||
self.word_size = self.sram.word_size
|
||||
self.addr_size = self.sram.addr_size
|
||||
self.bank_addr_size = self.sram.bank_addr_size
|
||||
self.write_size = self.sram.write_size
|
||||
self.num_spare_rows = self.sram.num_spare_rows
|
||||
if not self.sram.num_spare_cols:
|
||||
|
|
@ -39,7 +39,7 @@ class simulation():
|
|||
self.words_per_row = self.sram.words_per_row
|
||||
self.num_rows = self.sram.num_rows
|
||||
self.num_cols = self.sram.num_cols
|
||||
if self.write_size:
|
||||
if self.write_size != self.word_size:
|
||||
self.num_wmasks = int(math.ceil(self.word_size / self.write_size))
|
||||
else:
|
||||
self.num_wmasks = 0
|
||||
|
|
@ -80,7 +80,7 @@ class simulation():
|
|||
self.dout_name = "dout"
|
||||
self.pins = self.gen_pin_names(port_signal_names=(self.addr_name, self.din_name, self.dout_name),
|
||||
port_info=(len(self.all_ports), self.write_ports, self.read_ports),
|
||||
abits=self.addr_size,
|
||||
abits=self.bank_addr_size,
|
||||
dbits=self.word_size + self.num_spare_cols)
|
||||
debug.check(len(self.sram.pins) == len(self.pins),
|
||||
"Number of pins generated for characterization \
|
||||
|
|
@ -103,7 +103,7 @@ class simulation():
|
|||
self.spare_wen_value = {port: [] for port in self.write_ports}
|
||||
|
||||
# Three dimensional list to handle each addr and data bits for each port over the number of checks
|
||||
self.addr_values = {port: [[] for bit in range(self.addr_size)] for port in self.all_ports}
|
||||
self.addr_values = {port: [[] for bit in range(self.bank_addr_size)] for port in self.all_ports}
|
||||
self.data_values = {port: [[] for bit in range(self.word_size + self.num_spare_cols)] for port in self.write_ports}
|
||||
self.wmask_values = {port: [[] for bit in range(self.num_wmasks)] for port in self.write_ports}
|
||||
self.spare_wen_values = {port: [[] for bit in range(self.num_spare_cols)] for port in self.write_ports}
|
||||
|
|
@ -174,10 +174,10 @@ class simulation():
|
|||
|
||||
def add_address(self, address, port):
|
||||
""" Add the array of address values """
|
||||
debug.check(len(address)==self.addr_size, "Invalid address size.")
|
||||
debug.check(len(address)==self.bank_addr_size, "Invalid address size.")
|
||||
|
||||
self.addr_value[port].append(address)
|
||||
bit = self.addr_size - 1
|
||||
bit = self.bank_addr_size - 1
|
||||
for c in address:
|
||||
if c=="0":
|
||||
self.addr_values[port][bit].append(0)
|
||||
|
|
@ -330,7 +330,7 @@ class simulation():
|
|||
try:
|
||||
self.add_address(self.addr_value[port][-1], port)
|
||||
except:
|
||||
self.add_address("0" * self.addr_size, port)
|
||||
self.add_address("0" * self.bank_addr_size, port)
|
||||
|
||||
# If the port is also a readwrite then add
|
||||
# the same value as previous cycle
|
||||
|
|
@ -464,7 +464,7 @@ class simulation():
|
|||
for port in range(total_ports):
|
||||
pin_names.append("{0}{1}".format("clk", port))
|
||||
|
||||
if self.write_size:
|
||||
if self.write_size != self.word_size:
|
||||
for port in write_index:
|
||||
for bit in range(self.num_wmasks):
|
||||
pin_names.append("WMASK{0}_{1}".format(port, bit))
|
||||
|
|
|
|||
|
|
@ -97,6 +97,10 @@ log.create_file = True
|
|||
|
||||
def info(lev, str):
|
||||
from globals import OPTS
|
||||
# 99 is a special never print level
|
||||
if lev == 99:
|
||||
return
|
||||
|
||||
if (OPTS.verbose_level >= lev):
|
||||
frm = inspect.stack()[1]
|
||||
mod = inspect.getmodule(frm[0])
|
||||
|
|
|
|||
|
|
@ -378,6 +378,11 @@ def read_config(config_file, is_unit_test=True):
|
|||
ports,
|
||||
OPTS.tech_name)
|
||||
|
||||
# If write size is not defined, set it equal to word size
|
||||
if OPTS.write_size is None:
|
||||
OPTS.write_size = OPTS.word_size
|
||||
|
||||
|
||||
def end_openram():
|
||||
""" Clean up openram for a proper exit """
|
||||
cleanup_paths()
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class bank(design):
|
|||
|
||||
self.sram_config = sram_config
|
||||
sram_config.set_local_config(self)
|
||||
if self.write_size:
|
||||
if self.write_size != self.word_size:
|
||||
self.num_wmasks = int(ceil(self.word_size / self.write_size))
|
||||
else:
|
||||
self.num_wmasks = 0
|
||||
|
|
@ -44,10 +44,7 @@ class bank(design):
|
|||
# The local control signals are gated when we have bank select logic,
|
||||
# so this prefix will be added to all of the input signals to create
|
||||
# the internal gated signals.
|
||||
if self.num_banks>1:
|
||||
self.prefix="gated_"
|
||||
else:
|
||||
self.prefix=""
|
||||
self.prefix=""
|
||||
|
||||
self.create_netlist()
|
||||
if not OPTS.netlist_only:
|
||||
|
|
@ -93,14 +90,11 @@ class bank(design):
|
|||
for bit in range(self.word_size + self.num_spare_cols):
|
||||
self.add_pin("din{0}_{1}".format(port, bit), "INPUT")
|
||||
for port in self.all_ports:
|
||||
for bit in range(self.addr_size):
|
||||
for bit in range(self.bank_addr_size):
|
||||
self.add_pin("addr{0}_{1}".format(port, bit), "INPUT")
|
||||
|
||||
# For more than one bank, we have a bank select and name
|
||||
# the signals gated_*.
|
||||
if self.num_banks > 1:
|
||||
for port in self.all_ports:
|
||||
self.add_pin("bank_sel{}".format(port), "INPUT")
|
||||
for port in self.read_ports:
|
||||
self.add_pin("s_en{0}".format(port), "INPUT")
|
||||
for port in self.all_ports:
|
||||
|
|
@ -128,8 +122,6 @@ class bank(design):
|
|||
self.route_port_address(port)
|
||||
self.route_column_address_lines(port)
|
||||
self.route_control_lines(port)
|
||||
if self.num_banks > 1:
|
||||
self.route_bank_select(port)
|
||||
|
||||
self.route_supplies()
|
||||
|
||||
|
|
@ -171,7 +163,6 @@ class bank(design):
|
|||
self.create_port_data()
|
||||
self.create_port_address()
|
||||
self.create_column_decoder()
|
||||
self.create_bank_select()
|
||||
|
||||
def compute_instance_offsets(self):
|
||||
"""
|
||||
|
|
@ -252,8 +243,6 @@ class bank(design):
|
|||
y_offset = min(self.column_decoder_offsets[port].y, self.port_data[port].column_mux_offset.y)
|
||||
else:
|
||||
y_offset = self.port_address_offsets[port].y
|
||||
if self.num_banks > 1:
|
||||
y_offset += self.bank_select.height + drc("well_to_well")
|
||||
self.bank_select_offsets[port] = vector(-x_offset, -y_offset)
|
||||
|
||||
def compute_instance_port1_offsets(self):
|
||||
|
|
@ -311,22 +300,25 @@ class bank(design):
|
|||
self.place_port_address(self.port_address_offsets)
|
||||
|
||||
self.place_column_decoder(self.column_decoder_offsets)
|
||||
self.place_bank_select(self.bank_select_offsets)
|
||||
# self.place_bank_select(self.bank_select_offsets)
|
||||
|
||||
def compute_sizes(self):
|
||||
""" Computes the required sizes to create the bank """
|
||||
|
||||
self.num_words_per_bank = self.num_words / self.num_banks
|
||||
self.num_bits_per_bank = self.word_size * self.num_words_per_bank
|
||||
|
||||
self.num_cols = int(self.words_per_row * self.word_size)
|
||||
self.num_rows_temp = int(self.num_words / self.words_per_row)
|
||||
self.num_rows_temp = int(self.num_words_per_bank / self.words_per_row)
|
||||
self.num_rows = self.num_rows_temp + self.num_spare_rows
|
||||
|
||||
self.row_addr_size = ceil(log(self.num_rows, 2))
|
||||
self.col_addr_size = int(log(self.words_per_row, 2))
|
||||
self.addr_size = self.col_addr_size + self.row_addr_size
|
||||
self.bank_addr_size = self.col_addr_size + self.row_addr_size
|
||||
|
||||
debug.check(self.num_rows_temp * self.num_cols==self.word_size * self.num_words,
|
||||
debug.check(self.num_rows_temp * self.num_cols * self.num_banks ==self.word_size * self.num_words,
|
||||
"Invalid bank sizes.")
|
||||
debug.check(self.addr_size==self.col_addr_size + self.row_addr_size,
|
||||
debug.check(self.bank_addr_size==self.col_addr_size + self.row_addr_size,
|
||||
"Invalid address break down.")
|
||||
|
||||
# The order of the control signals on the control bus:
|
||||
|
|
@ -351,11 +343,7 @@ class bank(design):
|
|||
# These will be outputs of the gaters if this is multibank, if not, normal signals.
|
||||
self.control_signals = []
|
||||
for port in self.all_ports:
|
||||
if self.num_banks > 1:
|
||||
self.control_signals.append(["gated_" + str for str in self.input_control_signals[port]])
|
||||
else:
|
||||
self.control_signals.append(self.input_control_signals[port])
|
||||
|
||||
self.control_signals.append(self.input_control_signals[port])
|
||||
|
||||
# The central bus is the column address (one hot) and row address (binary)
|
||||
if self.col_addr_size>0:
|
||||
|
|
@ -366,8 +354,8 @@ class bank(design):
|
|||
|
||||
# Gap between decoder and array
|
||||
self.decoder_gap = max(2 * drc("pwell_to_nwell") + drc("nwell_enclose_active"),
|
||||
2 * self.m2_pitch,
|
||||
drc("nwell_to_nwell"))
|
||||
2 * self.m2_pitch,
|
||||
drc("nwell_to_nwell"))
|
||||
|
||||
def add_modules(self):
|
||||
""" Add all the modules using the class loader """
|
||||
|
|
@ -405,9 +393,6 @@ class bank(design):
|
|||
port=port,
|
||||
bit_offsets=self.bit_offsets))
|
||||
|
||||
if(self.num_banks > 1):
|
||||
self.bank_select = factory.create(module_type="bank_select")
|
||||
|
||||
def create_bitcell_array(self):
|
||||
""" Creating Bitcell Array """
|
||||
self.bitcell_array_inst=self.add_inst(name="bitcell_array",
|
||||
|
|
@ -560,7 +545,7 @@ class bank(design):
|
|||
def create_bank_select(self):
|
||||
""" Create the bank select logic. """
|
||||
|
||||
if not self.num_banks > 1:
|
||||
if not self.num_banks < 2:
|
||||
return
|
||||
|
||||
self.bank_select_inst = [None] * len(self.all_ports)
|
||||
|
|
@ -578,7 +563,7 @@ class bank(design):
|
|||
def place_bank_select(self, offsets):
|
||||
""" Place the bank select logic. """
|
||||
|
||||
if not self.num_banks > 1:
|
||||
if not self.num_banks < 2:
|
||||
return
|
||||
|
||||
debug.check(len(offsets)>=len(self.all_ports),
|
||||
|
|
@ -603,7 +588,7 @@ class bank(design):
|
|||
self.copy_layout_pin(inst, "gnd")
|
||||
|
||||
if 'vpb' in self.bitcell_array_inst.mod.pins and 'vnb' in self.bitcell_array_inst.mod.pins:
|
||||
for pin_name, supply_name in zip(['vnb','vpb'],['gnd','vdd']):
|
||||
for pin_name, supply_name in zip(['vnb', 'vpb'], ['gnd', 'vdd']):
|
||||
self.copy_layout_pin(self.bitcell_array_inst, pin_name, new_name=supply_name)
|
||||
|
||||
# If we use the pinvbuf as the decoder, we need to add power pins.
|
||||
|
|
@ -684,7 +669,7 @@ class bank(design):
|
|||
names=self.control_signals[0],
|
||||
length=control_bus_length,
|
||||
vertical=True,
|
||||
make_pins=(self.num_banks==1),
|
||||
make_pins=(True),
|
||||
pitch=self.m3_pitch)
|
||||
|
||||
self.copy_layout_pin(self.port_address_inst[0], "wl_en", self.prefix + "wl_en0")
|
||||
|
|
@ -701,7 +686,7 @@ class bank(design):
|
|||
names=list(reversed(self.control_signals[1])),
|
||||
length=control_bus_length,
|
||||
vertical=True,
|
||||
make_pins=(self.num_banks==1),
|
||||
make_pins=(True),
|
||||
pitch=self.m3_pitch)
|
||||
|
||||
self.copy_layout_pin(self.port_address_inst[1], "wl_en", self.prefix + "wl_en1")
|
||||
|
|
@ -763,7 +748,7 @@ class bank(design):
|
|||
din_name = "din{0}_{1}".format(port, row)
|
||||
self.copy_layout_pin(self.port_data_inst[port], data_name, din_name)
|
||||
|
||||
if self.write_size:
|
||||
if self.write_size != self.word_size:
|
||||
for row in range(self.num_wmasks):
|
||||
wmask_name = "bank_wmask_{}".format(row)
|
||||
bank_wmask_name = "bank_wmask{0}_{1}".format(port, row)
|
||||
|
|
|
|||
|
|
@ -1,313 +0,0 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2021 Regents of the University of California and The Board
|
||||
# of Regents for the Oklahoma Agricultural and Mechanical College
|
||||
# (acting for and on behalf of Oklahoma State University)
|
||||
# All rights reserved.
|
||||
#
|
||||
from tech import drc
|
||||
from base import design
|
||||
from base import vector
|
||||
from pgates import pinv
|
||||
from pgates import pnand2
|
||||
from pgates import pnor2
|
||||
from sram_factory import factory
|
||||
from globals import OPTS
|
||||
|
||||
class bank_select(design):
|
||||
"""Create a bank select signal that is combined with an array of
|
||||
NOR+INV gates to gate the control signals in case of multiple
|
||||
banks are created in upper level SRAM module
|
||||
"""
|
||||
|
||||
def __init__(self, name="bank_select", port="rw"):
|
||||
super().__init__(name)
|
||||
|
||||
self.port = port
|
||||
|
||||
self.create_netlist()
|
||||
if not OPTS.netlist_only:
|
||||
self.create_layout()
|
||||
|
||||
def create_netlist(self):
|
||||
self.add_pins()
|
||||
self.add_modules()
|
||||
self.create_instances()
|
||||
|
||||
def create_layout(self):
|
||||
self.calculate_module_offsets()
|
||||
self.place_instances()
|
||||
self.route_instances()
|
||||
|
||||
self.height = max([x.uy() for x in self.inv_inst]) + self.m1_width
|
||||
self.width = max([x.rx() for x in self.inv_inst])
|
||||
|
||||
self.add_boundary()
|
||||
self.DRC_LVS()
|
||||
|
||||
def add_pins(self):
|
||||
|
||||
# Number of control lines in the bus
|
||||
if self.port == "rw":
|
||||
self.num_control_lines = 4
|
||||
else:
|
||||
self.num_control_lines = 3
|
||||
# The order of the control signals on the control bus:
|
||||
# FIXME: Update for multiport (these names are not right)
|
||||
self.input_control_signals = ["clk_buf", "clk_buf_bar"]
|
||||
if (self.port == "rw") or (self.port == "w"):
|
||||
self.input_control_signals.append("w_en")
|
||||
if (self.port == "rw") or (self.port == "r"):
|
||||
self.input_control_signals.append("s_en")
|
||||
# These will be outputs of the gaters if this is multibank
|
||||
self.control_signals = ["gated_" + str for str in self.input_control_signals]
|
||||
|
||||
self.add_pin_list(self.input_control_signals, "INPUT")
|
||||
self.add_pin("bank_sel")
|
||||
self.add_pin_list(self.control_signals, "OUTPUT")
|
||||
self.add_pin("vdd", "POWER")
|
||||
self.add_pin("gnd", "GROUND")
|
||||
|
||||
def add_modules(self):
|
||||
""" Create modules for later instantiation """
|
||||
self.dff = factory.create(module_type="dff")
|
||||
height = self.dff.height + drc("poly_to_active")
|
||||
|
||||
# 1x Inverter
|
||||
self.inv_sel = factory.create(module_type="pinv", height=height)
|
||||
|
||||
# 4x Inverter
|
||||
self.inv4x = factory.create(module_type="pinv", height=height, size=4)
|
||||
|
||||
self.nor2 = factory.create(module_type="pnor2", height=height)
|
||||
|
||||
self.inv4x_nor = factory.create(module_type="pinv", height=height, size=4)
|
||||
|
||||
self.nand2 = factory.create(module_type="pnand2", height=height)
|
||||
|
||||
def calculate_module_offsets(self):
|
||||
|
||||
self.xoffset_nand = self.inv4x.width + 3 * self.m2_pitch + drc("pwell_to_nwell")
|
||||
self.xoffset_nor = self.inv4x.width + 3 * self.m2_pitch + drc("pwell_to_nwell")
|
||||
self.xoffset_bank_sel_inv = 0
|
||||
self.xoffset_inputs = 0
|
||||
self.yoffset_maxpoint = self.num_control_lines * self.inv4x.height
|
||||
|
||||
def create_instances(self):
|
||||
|
||||
self.bank_sel_inv=self.add_inst(name="bank_sel_inv",
|
||||
mod=self.inv_sel)
|
||||
self.connect_inst(["bank_sel", "bank_sel_bar", "vdd", "gnd"])
|
||||
|
||||
self.logic_inst = []
|
||||
self.inv_inst = []
|
||||
for i in range(self.num_control_lines):
|
||||
input_name = self.input_control_signals[i]
|
||||
gated_name = self.control_signals[i]
|
||||
name_nand = "nand_{}".format(input_name)
|
||||
name_nor = "nor_{}".format(input_name)
|
||||
name_inv = "inv_{}".format(input_name)
|
||||
|
||||
# These require OR (nor2+inv) gates since they are active low.
|
||||
# (writes occur on clk low)
|
||||
if input_name in ("clk_buf"):
|
||||
|
||||
self.logic_inst.append(self.add_inst(name=name_nor,
|
||||
mod=self.nor2))
|
||||
self.connect_inst([input_name,
|
||||
"bank_sel_bar",
|
||||
gated_name + "_temp_bar",
|
||||
"vdd",
|
||||
"gnd"])
|
||||
|
||||
# They all get inverters on the output
|
||||
self.inv_inst.append(self.add_inst(name=name_inv,
|
||||
mod=self.inv4x_nor))
|
||||
self.connect_inst([gated_name + "_temp_bar",
|
||||
gated_name,
|
||||
"vdd",
|
||||
"gnd"])
|
||||
|
||||
# the rest are AND (nand2+inv) gates
|
||||
else:
|
||||
self.logic_inst.append(self.add_inst(name=name_nand,
|
||||
mod=self.nand2))
|
||||
self.connect_inst([input_name,
|
||||
"bank_sel",
|
||||
gated_name + "_temp_bar",
|
||||
"vdd",
|
||||
"gnd"])
|
||||
|
||||
# They all get inverters on the output
|
||||
self.inv_inst.append(self.add_inst(name=name_inv,
|
||||
mod=self.inv4x))
|
||||
self.connect_inst([gated_name + "_temp_bar",
|
||||
gated_name,
|
||||
"vdd",
|
||||
"gnd"])
|
||||
|
||||
def place_instances(self):
|
||||
|
||||
# bank select inverter
|
||||
self.bank_select_inv_position = vector(self.xoffset_bank_sel_inv, 0)
|
||||
|
||||
# bank select inverter (must be made unique if more than one OR)
|
||||
self.bank_sel_inv.place(vector(self.xoffset_bank_sel_inv, 0))
|
||||
|
||||
for i in range(self.num_control_lines):
|
||||
|
||||
logic_inst = self.logic_inst[i]
|
||||
inv_inst = self.inv_inst[i]
|
||||
|
||||
input_name = self.input_control_signals[i]
|
||||
|
||||
if i == 0:
|
||||
y_offset = 0
|
||||
else:
|
||||
y_offset = self.inv4x_nor.height + self.inv4x.height * (i - 1)
|
||||
|
||||
if i % 2:
|
||||
y_offset += self.inv4x.height
|
||||
mirror = "MX"
|
||||
else:
|
||||
mirror = ""
|
||||
|
||||
# These require OR (nor2+inv) gates since they are active low.
|
||||
# (writes occur on clk low)
|
||||
if input_name in ("clk_buf"):
|
||||
|
||||
logic_inst.place(offset=[self.xoffset_nor, y_offset],
|
||||
mirror=mirror)
|
||||
|
||||
# the rest are AND (nand2+inv) gates
|
||||
else:
|
||||
logic_inst.place(offset=[self.xoffset_nand, y_offset],
|
||||
mirror=mirror)
|
||||
|
||||
# They all get inverters on the output
|
||||
inv_inst.place(offset=[logic_inst.rx(), y_offset],
|
||||
mirror=mirror)
|
||||
|
||||
def route_instances(self):
|
||||
|
||||
# bank_sel is vertical wire
|
||||
bank_sel_inv_pin = self.bank_sel_inv.get_pin("A")
|
||||
xoffset_bank_sel = bank_sel_inv_pin.lx()
|
||||
bank_sel_line_pos = vector(xoffset_bank_sel, 0)
|
||||
bank_sel_line_end = vector(xoffset_bank_sel, self.yoffset_maxpoint)
|
||||
self.add_path("m2", [bank_sel_line_pos, bank_sel_line_end])
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=bank_sel_inv_pin.center())
|
||||
|
||||
# Route the pin to the left edge as well
|
||||
bank_sel_pin_pos=vector(0, 0)
|
||||
bank_sel_pin_end=vector(bank_sel_line_pos.x, bank_sel_pin_pos.y)
|
||||
self.add_layout_pin_segment_center(text="bank_sel",
|
||||
layer="m3",
|
||||
start=bank_sel_pin_pos,
|
||||
end=bank_sel_pin_end)
|
||||
self.add_via_center(layers=self.m2_stack,
|
||||
offset=bank_sel_pin_end,
|
||||
directions=("H", "H"))
|
||||
|
||||
# bank_sel_bar is vertical wire
|
||||
bank_sel_bar_pin = self.bank_sel_inv.get_pin("Z")
|
||||
xoffset_bank_sel_bar = bank_sel_bar_pin.rx()
|
||||
self.add_label_pin(text="bank_sel_bar",
|
||||
layer="m2",
|
||||
offset=vector(xoffset_bank_sel_bar, 0),
|
||||
height=self.inv4x.height)
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=bank_sel_bar_pin.rc())
|
||||
|
||||
for i in range(self.num_control_lines):
|
||||
|
||||
logic_inst = self.logic_inst[i]
|
||||
inv_inst = self.inv_inst[i]
|
||||
|
||||
input_name = self.input_control_signals[i]
|
||||
gated_name = self.control_signals[i]
|
||||
if input_name in ("clk_buf"):
|
||||
xoffset_bank_signal = xoffset_bank_sel_bar
|
||||
else:
|
||||
xoffset_bank_signal = xoffset_bank_sel
|
||||
|
||||
# Connect the logic output to inverter input
|
||||
out_pin = logic_inst.get_pin("Z")
|
||||
out_pos = out_pin.center()
|
||||
in_pin = inv_inst.get_pin("A")
|
||||
in_pos = in_pin.center()
|
||||
mid1_pos = vector(0.5 * (out_pos.x + in_pos.x), out_pos.y)
|
||||
mid2_pos = vector(0.5 * (out_pos.x + in_pos.x), in_pos.y)
|
||||
self.add_path("m1", [out_pos, mid1_pos, mid2_pos, in_pos])
|
||||
|
||||
# Connect the logic B input to bank_sel / bank_sel_bar
|
||||
logic_pin = logic_inst.get_pin("B")
|
||||
logic_pos = logic_pin.center()
|
||||
input_pos = vector(xoffset_bank_signal, logic_pos.y)
|
||||
self.add_path("m3", [logic_pos, input_pos])
|
||||
self.add_via_center(self.m2_stack,
|
||||
input_pos)
|
||||
self.add_via_stack_center(from_layer=logic_pin.layer,
|
||||
to_layer="m3",
|
||||
offset=logic_pos)
|
||||
|
||||
# Connect the logic A input to the input pin
|
||||
logic_pin = logic_inst.get_pin("A")
|
||||
logic_pos = logic_pin.center()
|
||||
input_pos = vector(0, logic_pos.y)
|
||||
self.add_via_stack_center(from_layer=logic_pin.layer,
|
||||
to_layer="m3",
|
||||
offset=logic_pos)
|
||||
self.add_layout_pin_segment_center(text=input_name,
|
||||
layer="m3",
|
||||
start=input_pos,
|
||||
end=logic_pos)
|
||||
|
||||
# Add output pins
|
||||
out_pin = inv_inst.get_pin("Z")
|
||||
self.add_layout_pin(text=gated_name,
|
||||
layer=out_pin.layer,
|
||||
offset=out_pin.ll(),
|
||||
width=inv_inst.rx() - out_pin.lx(),
|
||||
height=out_pin.height())
|
||||
|
||||
# Find the x offsets for where the vias/pins should be placed
|
||||
a_xoffset = self.logic_inst[0].lx()
|
||||
b_xoffset = self.inv_inst[0].lx()
|
||||
for num in range(self.num_control_lines):
|
||||
# Route both supplies
|
||||
for n in ["vdd", "gnd"]:
|
||||
supply_pin = self.inv_inst[num].get_pin(n)
|
||||
supply_offset = supply_pin.ll().scale(0, 1)
|
||||
self.add_rect(layer="m1",
|
||||
offset=supply_offset,
|
||||
width=self.width)
|
||||
|
||||
# Add pins in two locations
|
||||
for xoffset in [a_xoffset, b_xoffset]:
|
||||
pin_pos = vector(xoffset, supply_pin.cy())
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=pin_pos,
|
||||
directions=("H", "H"))
|
||||
self.add_via_center(layers=self.m2_stack,
|
||||
offset=pin_pos,
|
||||
directions=("H", "H"))
|
||||
self.add_layout_pin_rect_center(text=n,
|
||||
layer="m3",
|
||||
offset=pin_pos)
|
||||
|
||||
# Add vdd/gnd supply rails
|
||||
gnd_pin = self.inv_inst[num].get_pin("gnd")
|
||||
left_gnd_pos = vector(0, gnd_pin.cy())
|
||||
self.add_layout_pin_segment_center(text="gnd",
|
||||
layer="m1",
|
||||
start=left_gnd_pos,
|
||||
end=gnd_pin.rc())
|
||||
|
||||
vdd_pin = self.inv_inst[num].get_pin("vdd")
|
||||
left_vdd_pos = vector(0, vdd_pin.cy())
|
||||
self.add_layout_pin_segment_center(text="vdd",
|
||||
layer="m1",
|
||||
start=left_vdd_pos,
|
||||
end=vdd_pin.rc())
|
||||
|
|
@ -5,74 +5,22 @@
|
|||
# (acting for and on behalf of Oklahoma State University)
|
||||
# All rights reserved.
|
||||
#
|
||||
from base import design
|
||||
import debug
|
||||
from sram_factory import factory
|
||||
import math
|
||||
from base import vector
|
||||
from globals import OPTS
|
||||
from base import logical_effort
|
||||
from .control_logic_base import control_logic_base
|
||||
|
||||
|
||||
class control_logic(design):
|
||||
class control_logic(control_logic_base):
|
||||
"""
|
||||
Dynamically generated Control logic for the total SRAM circuit.
|
||||
"""
|
||||
|
||||
def __init__(self, num_rows, words_per_row, word_size, spare_columns=None, sram=None, port_type="rw", name=""):
|
||||
""" Constructor """
|
||||
name = "control_logic_" + port_type
|
||||
super().__init__(name)
|
||||
debug.info(1, "Creating {}".format(name))
|
||||
self.add_comment("num_rows: {0}".format(num_rows))
|
||||
self.add_comment("words_per_row: {0}".format(words_per_row))
|
||||
self.add_comment("word_size {0}".format(word_size))
|
||||
|
||||
self.sram=sram
|
||||
self.num_rows = num_rows
|
||||
self.words_per_row = words_per_row
|
||||
self.word_size = word_size
|
||||
self.port_type = port_type
|
||||
|
||||
if not spare_columns:
|
||||
self.num_spare_cols = 0
|
||||
else:
|
||||
self.num_spare_cols = spare_columns
|
||||
|
||||
self.num_cols = word_size * words_per_row + self.num_spare_cols
|
||||
self.num_words = num_rows * words_per_row
|
||||
|
||||
self.enable_delay_chain_resizing = False
|
||||
self.inv_parasitic_delay = logical_effort.pinv
|
||||
|
||||
# Determines how much larger the sen delay should be. Accounts for possible error in model.
|
||||
# FIXME: This should be made a parameter
|
||||
self.wl_timing_tolerance = 1
|
||||
self.wl_stage_efforts = None
|
||||
self.sen_stage_efforts = None
|
||||
|
||||
if self.port_type == "rw":
|
||||
self.num_control_signals = 2
|
||||
else:
|
||||
self.num_control_signals = 1
|
||||
|
||||
self.create_netlist()
|
||||
if not OPTS.netlist_only:
|
||||
self.create_layout()
|
||||
|
||||
def create_netlist(self):
|
||||
self.setup_signal_busses()
|
||||
self.add_pins()
|
||||
self.add_modules()
|
||||
self.create_instances()
|
||||
|
||||
def create_layout(self):
|
||||
""" Create layout and route between modules """
|
||||
self.place_instances()
|
||||
self.route_all()
|
||||
# self.add_lvs_correspondence_points()
|
||||
self.add_boundary()
|
||||
self.DRC_LVS()
|
||||
super().__init__(num_rows, words_per_row, word_size, spare_columns, sram, port_type, name)
|
||||
|
||||
def add_pins(self):
|
||||
""" Add the pins to the control logic module. """
|
||||
|
|
@ -151,93 +99,6 @@ class control_logic(design):
|
|||
self.delay_chain=factory.create(module_type="delay_chain",
|
||||
fanout_list = OPTS.delay_chain_stages * [ OPTS.delay_chain_fanout_per_stage ])
|
||||
|
||||
def get_dynamic_delay_chain_size(self, previous_stages, previous_fanout):
|
||||
"""Determine the size of the delay chain used for the Sense Amp Enable using path delays"""
|
||||
from math import ceil
|
||||
previous_delay_chain_delay = (previous_fanout + 1 + self.inv_parasitic_delay) * previous_stages
|
||||
debug.info(2, "Previous delay chain produced {} delay units".format(previous_delay_chain_delay))
|
||||
|
||||
# This can be anything >=2
|
||||
delay_fanout = 3
|
||||
# The delay chain uses minimum sized inverters. There are (fanout+1)*stages inverters and each
|
||||
# inverter adds 1 unit of delay (due to minimum size). This also depends on the pinv value
|
||||
required_delay = self.wl_delay * self.wl_timing_tolerance - (self.sen_delay - previous_delay_chain_delay)
|
||||
debug.check(required_delay > 0, "Cannot size delay chain to have negative delay")
|
||||
delay_per_stage = delay_fanout + 1 + self.inv_parasitic_delay
|
||||
delay_stages = ceil(required_delay / delay_per_stage)
|
||||
# force an even number of stages.
|
||||
if delay_stages % 2 == 1:
|
||||
delay_stages += 1
|
||||
# Fanout can be varied as well but is a little more complicated but potentially optimal.
|
||||
debug.info(1, "Setting delay chain to {} stages with {} fanout to match {} delay".format(delay_stages, delay_fanout, required_delay))
|
||||
return (delay_stages, delay_fanout)
|
||||
|
||||
def get_dynamic_delay_fanout_list(self, previous_stages, previous_fanout):
|
||||
"""Determine the size of the delay chain used for the Sense Amp Enable using path delays"""
|
||||
|
||||
previous_delay_per_stage = previous_fanout + 1 + self.inv_parasitic_delay
|
||||
previous_delay_chain_delay = previous_delay_per_stage * previous_stages
|
||||
debug.info(2, "Previous delay chain produced {} delay units".format(previous_delay_chain_delay))
|
||||
|
||||
fanout_rise = fanout_fall = 2 # This can be anything >=2
|
||||
# The delay chain uses minimum sized inverters. There are (fanout+1)*stages inverters and each
|
||||
# inverter adds 1 unit of delay (due to minimum size). This also depends on the pinv value
|
||||
required_delay_fall = self.wl_delay_fall * self.wl_timing_tolerance - \
|
||||
(self.sen_delay_fall - previous_delay_chain_delay / 2)
|
||||
required_delay_rise = self.wl_delay_rise * self.wl_timing_tolerance - \
|
||||
(self.sen_delay_rise - previous_delay_chain_delay / 2)
|
||||
debug.info(2,
|
||||
"Required delays from chain: fall={}, rise={}".format(required_delay_fall,
|
||||
required_delay_rise))
|
||||
|
||||
# If the fanout is different between rise/fall by this amount. Stage algorithm is made more pessimistic.
|
||||
WARNING_FANOUT_DIFF = 5
|
||||
stages_close = False
|
||||
# The stages need to be equal (or at least a even number of stages with matching rise/fall delays)
|
||||
while True:
|
||||
stages_fall = self.calculate_stages_with_fixed_fanout(required_delay_fall,
|
||||
fanout_fall)
|
||||
stages_rise = self.calculate_stages_with_fixed_fanout(required_delay_rise,
|
||||
fanout_rise)
|
||||
debug.info(1,
|
||||
"Fall stages={}, rise stages={}".format(stages_fall,
|
||||
stages_rise))
|
||||
if abs(stages_fall - stages_rise) == 1 and not stages_close:
|
||||
stages_close = True
|
||||
safe_fanout_rise = fanout_rise
|
||||
safe_fanout_fall = fanout_fall
|
||||
|
||||
if stages_fall == stages_rise:
|
||||
break
|
||||
elif abs(stages_fall - stages_rise) == 1 and WARNING_FANOUT_DIFF < abs(fanout_fall - fanout_rise):
|
||||
debug.info(1, "Delay chain fanouts between stages are large. Making chain size larger for safety.")
|
||||
fanout_rise = safe_fanout_rise
|
||||
fanout_fall = safe_fanout_fall
|
||||
break
|
||||
# There should also be a condition to make sure the fanout does not get too large.
|
||||
# Otherwise, increase the fanout of delay with the most stages, calculate new stages
|
||||
elif stages_fall>stages_rise:
|
||||
fanout_fall+=1
|
||||
else:
|
||||
fanout_rise+=1
|
||||
|
||||
total_stages = max(stages_fall, stages_rise) * 2
|
||||
debug.info(1, "New Delay chain: stages={}, fanout_rise={}, fanout_fall={}".format(total_stages, fanout_rise, fanout_fall))
|
||||
|
||||
# Creates interleaved fanout list of rise/fall delays. Assumes fall is the first stage.
|
||||
stage_list = [fanout_fall if i % 2==0 else fanout_rise for i in range(total_stages)]
|
||||
return stage_list
|
||||
|
||||
def calculate_stages_with_fixed_fanout(self, required_delay, fanout):
|
||||
from math import ceil
|
||||
# Delay being negative is not an error. It implies that any amount of stages would have a negative effect on the overall delay
|
||||
# 3 is the minimum delay per stage (with pinv=0).
|
||||
if required_delay <= 3 + self.inv_parasitic_delay:
|
||||
return 1
|
||||
delay_per_stage = fanout + 1 + self.inv_parasitic_delay
|
||||
delay_stages = ceil(required_delay / delay_per_stage)
|
||||
return delay_stages
|
||||
|
||||
def setup_signal_busses(self):
|
||||
""" Setup bus names, determine the size of the busses etc """
|
||||
|
||||
|
|
@ -277,17 +138,6 @@ class control_logic(design):
|
|||
|
||||
self.supply_list = ["vdd", "gnd"]
|
||||
|
||||
def route_rails(self):
|
||||
""" Add the input signal inverted tracks """
|
||||
height = self.control_logic_center.y - self.m2_pitch
|
||||
# DFF spacing plus the power routing
|
||||
offset = vector(self.ctrl_dff_array.width + self.m4_pitch, 0)
|
||||
|
||||
self.input_bus = self.create_vertical_bus("m2",
|
||||
offset,
|
||||
self.internal_bus_list,
|
||||
height)
|
||||
|
||||
def create_instances(self):
|
||||
""" Create all the instances """
|
||||
self.create_dffs()
|
||||
|
|
@ -303,21 +153,8 @@ class control_logic(design):
|
|||
self.create_delay()
|
||||
self.create_pen_row()
|
||||
|
||||
def place_instances(self):
|
||||
""" Place all the instances """
|
||||
# Keep track of all right-most instances to determine row boundary
|
||||
# and add the vdd/gnd pins
|
||||
self.row_end_inst = []
|
||||
|
||||
# Add the control flops on the left of the bus
|
||||
self.place_dffs()
|
||||
|
||||
# All of the control logic is placed to the right of the DFFs and bus
|
||||
# as well as the power supply stripe
|
||||
self.control_x_offset = self.ctrl_dff_array.width + self.internal_bus_width + self.m4_pitch
|
||||
|
||||
def place_logic_rows(self):
|
||||
row = 0
|
||||
# Add the logic on the right of the bus
|
||||
self.place_clk_buf_row(row)
|
||||
row += 1
|
||||
self.place_gated_clk_bar_row(row)
|
||||
|
|
@ -336,24 +173,8 @@ class control_logic(design):
|
|||
self.place_rbl_delay_row(row)
|
||||
row += 1
|
||||
self.place_wlen_row(row)
|
||||
row += 1
|
||||
|
||||
control_center_y = self.wl_en_inst.uy() + self.m3_pitch
|
||||
|
||||
# Delay chain always gets placed at row 4
|
||||
self.place_delay(4)
|
||||
height = self.delay_inst.uy()
|
||||
|
||||
# This offset is used for placement of the control logic in the SRAM level.
|
||||
self.control_logic_center = vector(self.ctrl_dff_inst.rx(), control_center_y)
|
||||
|
||||
# Extra pitch on top and right
|
||||
self.height = height + 2 * self.m1_pitch
|
||||
# Max of modules or logic rows
|
||||
self.width = max([inst.rx() for inst in self.row_end_inst])
|
||||
if (self.port_type == "rw") or (self.port_type == "r"):
|
||||
self.width = max(self.delay_inst.rx(), self.width)
|
||||
self.width += self.m2_pitch
|
||||
self.control_center_y = self.wl_en_inst.uy() + self.m3_pitch
|
||||
|
||||
def route_all(self):
|
||||
""" Routing between modules """
|
||||
|
|
@ -373,24 +194,12 @@ class control_logic(design):
|
|||
self.route_supplies()
|
||||
|
||||
def create_delay(self):
|
||||
""" Create the replica bitline """
|
||||
""" Create the delay chain """
|
||||
self.delay_inst=self.add_inst(name="delay_chain",
|
||||
mod=self.delay_chain)
|
||||
# rbl_bl_delay is asserted (1) when the bitline has been discharged
|
||||
self.connect_inst(["rbl_bl", "rbl_bl_delay", "vdd", "gnd"])
|
||||
|
||||
def place_delay(self, row):
|
||||
""" Place the replica bitline """
|
||||
debug.check(row % 2 == 0, "Must place delay chain at even row for supply alignment.")
|
||||
|
||||
# It is flipped on X axis
|
||||
y_off = row * self.and2.height + self.delay_chain.height
|
||||
|
||||
# Add the RBL above the rows
|
||||
# Add to the right of the control rows and routing channel
|
||||
offset = vector(0, y_off)
|
||||
self.delay_inst.place(offset, mirror="MX")
|
||||
|
||||
def route_delay(self):
|
||||
|
||||
out_pos = self.delay_inst.get_pin("out").center()
|
||||
|
|
@ -406,109 +215,6 @@ class control_logic(design):
|
|||
# Input from RBL goes to the delay line for futher delay
|
||||
self.copy_layout_pin(self.delay_inst, "in", "rbl_bl")
|
||||
|
||||
def create_clk_buf_row(self):
|
||||
""" Create the multistage and gated clock buffer """
|
||||
self.clk_buf_inst = self.add_inst(name="clkbuf",
|
||||
mod=self.clk_buf_driver)
|
||||
self.connect_inst(["clk", "clk_buf", "vdd", "gnd"])
|
||||
|
||||
def place_clk_buf_row(self, row):
|
||||
x_offset = self.control_x_offset
|
||||
|
||||
x_offset = self.place_util(self.clk_buf_inst, x_offset, row)
|
||||
|
||||
self.row_end_inst.append(self.clk_buf_inst)
|
||||
|
||||
def route_clk_buf(self):
|
||||
clk_pin = self.clk_buf_inst.get_pin("A")
|
||||
clk_pos = clk_pin.center()
|
||||
self.add_layout_pin_rect_center(text="clk",
|
||||
layer="m2",
|
||||
offset=clk_pos)
|
||||
self.add_via_stack_center(from_layer=clk_pin.layer,
|
||||
to_layer="m2",
|
||||
offset=clk_pos)
|
||||
|
||||
self.route_output_to_bus_jogged(self.clk_buf_inst,
|
||||
"clk_buf")
|
||||
self.connect_output(self.clk_buf_inst, "Z", "clk_buf")
|
||||
|
||||
def create_gated_clk_bar_row(self):
|
||||
self.clk_bar_inst = self.add_inst(name="inv_clk_bar",
|
||||
mod=self.inv)
|
||||
self.connect_inst(["clk_buf", "clk_bar", "vdd", "gnd"])
|
||||
|
||||
self.gated_clk_bar_inst = self.add_inst(name="and2_gated_clk_bar",
|
||||
mod=self.and2)
|
||||
self.connect_inst(["clk_bar", "cs", "gated_clk_bar", "vdd", "gnd"])
|
||||
|
||||
def place_gated_clk_bar_row(self, row):
|
||||
x_offset = self.control_x_offset
|
||||
|
||||
x_offset = self.place_util(self.clk_bar_inst, x_offset, row)
|
||||
x_offset = self.place_util(self.gated_clk_bar_inst, x_offset, row)
|
||||
|
||||
self.row_end_inst.append(self.gated_clk_bar_inst)
|
||||
|
||||
def route_gated_clk_bar(self):
|
||||
clkbuf_map = zip(["A"], ["clk_buf"])
|
||||
self.connect_vertical_bus(clkbuf_map, self.clk_bar_inst, self.input_bus)
|
||||
|
||||
out_pin = self.clk_bar_inst.get_pin("Z")
|
||||
out_pos = out_pin.center()
|
||||
in_pin = self.gated_clk_bar_inst.get_pin("A")
|
||||
in_pos = in_pin.center()
|
||||
self.add_zjog(out_pin.layer, out_pos, in_pos)
|
||||
self.add_via_stack_center(from_layer=out_pin.layer,
|
||||
to_layer=in_pin.layer,
|
||||
offset=in_pos)
|
||||
|
||||
|
||||
# This is the second gate over, so it needs to be on M3
|
||||
clkbuf_map = zip(["B"], ["cs"])
|
||||
self.connect_vertical_bus(clkbuf_map,
|
||||
self.gated_clk_bar_inst,
|
||||
self.input_bus,
|
||||
self.m2_stack[::-1])
|
||||
# The pin is on M1, so we need another via as well
|
||||
b_pin = self.gated_clk_bar_inst.get_pin("B")
|
||||
self.add_via_stack_center(from_layer=b_pin.layer,
|
||||
to_layer="m3",
|
||||
offset=b_pin.center())
|
||||
|
||||
# This is the second gate over, so it needs to be on M3
|
||||
self.route_output_to_bus_jogged(self.gated_clk_bar_inst,
|
||||
"gated_clk_bar")
|
||||
|
||||
def create_gated_clk_buf_row(self):
|
||||
self.gated_clk_buf_inst = self.add_inst(name="and2_gated_clk_buf",
|
||||
mod=self.and2)
|
||||
self.connect_inst(["clk_buf", "cs", "gated_clk_buf", "vdd", "gnd"])
|
||||
|
||||
def place_gated_clk_buf_row(self, row):
|
||||
x_offset = self.control_x_offset
|
||||
|
||||
x_offset = self.place_util(self.gated_clk_buf_inst, x_offset, row)
|
||||
|
||||
self.row_end_inst.append(self.gated_clk_buf_inst)
|
||||
|
||||
def route_gated_clk_buf(self):
|
||||
clkbuf_map = zip(["A", "B"], ["clk_buf", "cs"])
|
||||
self.connect_vertical_bus(clkbuf_map,
|
||||
self.gated_clk_buf_inst,
|
||||
self.input_bus)
|
||||
|
||||
clkbuf_map = zip(["Z"], ["gated_clk_buf"])
|
||||
self.connect_vertical_bus(clkbuf_map,
|
||||
self.gated_clk_buf_inst,
|
||||
self.input_bus,
|
||||
self.m2_stack[::-1])
|
||||
# The pin is on M1, so we need another via as well
|
||||
z_pin = self.gated_clk_buf_inst.get_pin("Z")
|
||||
self.add_via_stack_center(from_layer=z_pin.layer,
|
||||
to_layer="m2",
|
||||
offset=z_pin.center())
|
||||
|
||||
def create_wlen_row(self):
|
||||
# input pre_p_en, output: wl_en
|
||||
self.wl_en_inst=self.add_inst(name="buf_wl_en",
|
||||
|
|
@ -651,194 +357,3 @@ class control_logic(design):
|
|||
self.connect_vertical_bus(wen_map, self.w_en_gate_inst, self.input_bus)
|
||||
|
||||
self.connect_output(self.w_en_gate_inst, "Z", "w_en")
|
||||
|
||||
def create_dffs(self):
|
||||
self.ctrl_dff_inst=self.add_inst(name="ctrl_dffs",
|
||||
mod=self.ctrl_dff_array)
|
||||
inst_pins = self.input_list + self.dff_output_list + ["clk_buf"] + self.supply_list
|
||||
self.connect_inst(inst_pins)
|
||||
|
||||
def place_dffs(self):
|
||||
self.ctrl_dff_inst.place(vector(0, 0))
|
||||
|
||||
def route_dffs(self):
|
||||
if self.port_type == "rw":
|
||||
dff_out_map = zip(["dout_bar_0", "dout_bar_1", "dout_1"], ["cs", "we", "we_bar"])
|
||||
elif self.port_type == "r":
|
||||
dff_out_map = zip(["dout_bar_0", "dout_0"], ["cs", "cs_bar"])
|
||||
else:
|
||||
dff_out_map = zip(["dout_bar_0"], ["cs"])
|
||||
self.connect_vertical_bus(dff_out_map, self.ctrl_dff_inst, self.input_bus, self.m2_stack[::-1])
|
||||
|
||||
# Connect the clock rail to the other clock rail
|
||||
# by routing in the supply rail track to avoid channel conflicts
|
||||
in_pos = self.ctrl_dff_inst.get_pin("clk").uc()
|
||||
mid_pos = vector(in_pos.x, self.gated_clk_buf_inst.get_pin("vdd").cy() - self.m1_pitch)
|
||||
rail_pos = vector(self.input_bus["clk_buf"].cx(), mid_pos.y)
|
||||
self.add_wire(self.m1_stack, [in_pos, mid_pos, rail_pos])
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=rail_pos)
|
||||
|
||||
self.copy_layout_pin(self.ctrl_dff_inst, "din_0", "csb")
|
||||
if (self.port_type == "rw"):
|
||||
self.copy_layout_pin(self.ctrl_dff_inst, "din_1", "web")
|
||||
|
||||
def get_offset(self, row):
|
||||
""" Compute the y-offset and mirroring """
|
||||
y_off = row * self.and2.height
|
||||
if row % 2:
|
||||
y_off += self.and2.height
|
||||
mirror="MX"
|
||||
else:
|
||||
mirror="R0"
|
||||
|
||||
return (y_off, mirror)
|
||||
|
||||
def connect_output(self, inst, pin_name, out_name):
|
||||
""" Create an output pin on the right side from the pin of a given instance. """
|
||||
|
||||
out_pin = inst.get_pin(pin_name)
|
||||
out_pos = out_pin.center()
|
||||
right_pos = out_pos + vector(self.width - out_pin.cx(), 0)
|
||||
|
||||
self.add_via_stack_center(from_layer=out_pin.layer,
|
||||
to_layer="m2",
|
||||
offset=out_pos)
|
||||
self.add_layout_pin_segment_center(text=out_name,
|
||||
layer="m2",
|
||||
start=out_pos,
|
||||
end=right_pos)
|
||||
|
||||
def route_supplies(self):
|
||||
""" Add vdd and gnd to the instance cells """
|
||||
|
||||
pin_layer = self.dff.get_pin("vdd").layer
|
||||
supply_layer = self.supply_stack[2]
|
||||
|
||||
|
||||
# FIXME: We should be able to replace this with route_vertical_pins instead
|
||||
# but we may have to make the logic gates a separate module so that they
|
||||
# have row pins of the same width
|
||||
max_row_x_loc = max([inst.rx() for inst in self.row_end_inst])
|
||||
min_row_x_loc = self.control_x_offset
|
||||
|
||||
vdd_pin_locs = []
|
||||
gnd_pin_locs = []
|
||||
|
||||
last_via = None
|
||||
for inst in self.row_end_inst:
|
||||
pins = inst.get_pins("vdd")
|
||||
for pin in pins:
|
||||
if pin.layer == pin_layer:
|
||||
row_loc = pin.rc()
|
||||
pin_loc = vector(max_row_x_loc, pin.rc().y)
|
||||
vdd_pin_locs.append(pin_loc)
|
||||
last_via = self.add_via_stack_center(from_layer=pin_layer,
|
||||
to_layer=supply_layer,
|
||||
offset=pin_loc,
|
||||
min_area=True)
|
||||
self.add_path(pin_layer, [row_loc, pin_loc])
|
||||
|
||||
pins = inst.get_pins("gnd")
|
||||
for pin in pins:
|
||||
if pin.layer == pin_layer:
|
||||
row_loc = pin.rc()
|
||||
pin_loc = vector(min_row_x_loc, pin.rc().y)
|
||||
gnd_pin_locs.append(pin_loc)
|
||||
last_via = self.add_via_stack_center(from_layer=pin_layer,
|
||||
to_layer=supply_layer,
|
||||
offset=pin_loc,
|
||||
min_area=True)
|
||||
self.add_path(pin_layer, [row_loc, pin_loc])
|
||||
|
||||
if last_via:
|
||||
via_height=last_via.mod.second_layer_height
|
||||
via_width=last_via.mod.second_layer_width
|
||||
else:
|
||||
via_height=None
|
||||
via_width=0
|
||||
|
||||
min_y = min([x.y for x in vdd_pin_locs])
|
||||
max_y = max([x.y for x in vdd_pin_locs])
|
||||
bot_pos = vector(max_row_x_loc, min_y - 0.5 * via_height)
|
||||
top_pos = vector(max_row_x_loc, max_y + 0.5 * via_height)
|
||||
self.add_layout_pin_segment_center(text="vdd",
|
||||
layer=supply_layer,
|
||||
start=bot_pos,
|
||||
end=top_pos,
|
||||
width=via_width)
|
||||
|
||||
min_y = min([x.y for x in gnd_pin_locs])
|
||||
max_y = max([x.y for x in gnd_pin_locs])
|
||||
bot_pos = vector(min_row_x_loc, min_y - 0.5 * via_height)
|
||||
top_pos = vector(min_row_x_loc, max_y + 0.5 * via_height)
|
||||
self.add_layout_pin_segment_center(text="gnd",
|
||||
layer=supply_layer,
|
||||
start=bot_pos,
|
||||
end=top_pos,
|
||||
width=via_width)
|
||||
|
||||
self.copy_layout_pin(self.delay_inst, "gnd")
|
||||
self.copy_layout_pin(self.delay_inst, "vdd")
|
||||
|
||||
self.copy_layout_pin(self.ctrl_dff_inst, "gnd")
|
||||
self.copy_layout_pin(self.ctrl_dff_inst, "vdd")
|
||||
|
||||
def add_lvs_correspondence_points(self):
|
||||
""" This adds some points for easier debugging if LVS goes wrong.
|
||||
These should probably be turned off by default though, since extraction
|
||||
will show these as ports in the extracted netlist.
|
||||
"""
|
||||
# pin=self.clk_inv1.get_pin("Z")
|
||||
# self.add_label_pin(text="clk1_bar",
|
||||
# layer="m1",
|
||||
# offset=pin.ll(),
|
||||
# height=pin.height(),
|
||||
# width=pin.width())
|
||||
|
||||
# pin=self.clk_inv2.get_pin("Z")
|
||||
# self.add_label_pin(text="clk2",
|
||||
# layer="m1",
|
||||
# offset=pin.ll(),
|
||||
# height=pin.height(),
|
||||
# width=pin.width())
|
||||
|
||||
pin=self.delay_inst.get_pin("out")
|
||||
self.add_label_pin(text="out",
|
||||
layer=pin.layer,
|
||||
offset=pin.ll(),
|
||||
height=pin.height(),
|
||||
width=pin.width())
|
||||
|
||||
def graph_exclude_dffs(self):
|
||||
"""Exclude dffs from graph as they do not represent critical path"""
|
||||
|
||||
self.graph_inst_exclude.add(self.ctrl_dff_inst)
|
||||
if self.port_type=="rw" or self.port_type=="w":
|
||||
self.graph_inst_exclude.add(self.w_en_gate_inst)
|
||||
|
||||
def place_util(self, inst, x_offset, row):
|
||||
""" Utility to place a row and compute the next offset """
|
||||
|
||||
(y_offset, mirror) = self.get_offset(row)
|
||||
offset = vector(x_offset, y_offset)
|
||||
inst.place(offset, mirror)
|
||||
return x_offset + inst.width
|
||||
|
||||
def route_output_to_bus_jogged(self, inst, name):
|
||||
# Connect this at the bottom of the buffer
|
||||
out_pin = inst.get_pin("Z")
|
||||
out_pos = out_pin.center()
|
||||
mid1 = vector(out_pos.x, out_pos.y - 0.3 * inst.mod.height)
|
||||
mid2 = vector(self.input_bus[name].cx(), mid1.y)
|
||||
bus_pos = self.input_bus[name].center()
|
||||
self.add_wire(self.m2_stack[::-1], [out_pos, mid1, mid2, bus_pos])
|
||||
self.add_via_stack_center(from_layer=out_pin.layer,
|
||||
to_layer="m2",
|
||||
offset=out_pos)
|
||||
|
||||
def get_left_pins(self, name):
|
||||
"""
|
||||
Return the left side supply pins to connect to a vertical stripe.
|
||||
"""
|
||||
return(self.cntrl_dff_inst.get_pins(name) + self.delay_inst.get_pins(name))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,508 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2021 Regents of the University of California and The Board
|
||||
# of Regents for the Oklahoma Agricultural and Mechanical College
|
||||
# (acting for and on behalf of Oklahoma State University)
|
||||
# All rights reserved.
|
||||
#
|
||||
from base import design
|
||||
import debug
|
||||
from sram_factory import factory
|
||||
import math
|
||||
from base import vector
|
||||
from globals import OPTS
|
||||
from base import logical_effort
|
||||
|
||||
|
||||
class control_logic_base(design):
|
||||
"""
|
||||
Generic base class for SRAM control logic.
|
||||
"""
|
||||
|
||||
def __init__(self, num_rows, words_per_row, word_size, spare_columns=None, sram=None, port_type="rw", name=""):
|
||||
""" Constructor """
|
||||
name = "control_logic_" + port_type
|
||||
super().__init__(name)
|
||||
debug.info(1, "Creating {}".format(name))
|
||||
self.add_comment("num_rows: {0}".format(num_rows))
|
||||
self.add_comment("words_per_row: {0}".format(words_per_row))
|
||||
self.add_comment("word_size {0}".format(word_size))
|
||||
|
||||
self.sram=sram
|
||||
self.num_rows = num_rows
|
||||
self.words_per_row = words_per_row
|
||||
self.word_size = word_size
|
||||
self.port_type = port_type
|
||||
|
||||
if not spare_columns:
|
||||
self.num_spare_cols = 0
|
||||
else:
|
||||
self.num_spare_cols = spare_columns
|
||||
|
||||
self.num_cols = word_size * words_per_row + self.num_spare_cols
|
||||
self.num_words = num_rows * words_per_row
|
||||
|
||||
self.enable_delay_chain_resizing = False
|
||||
self.inv_parasitic_delay = logical_effort.pinv
|
||||
|
||||
# Determines how much larger the sen delay should be. Accounts for possible error in model.
|
||||
# FIXME: This should be made a parameter
|
||||
self.wl_timing_tolerance = 1
|
||||
self.wl_stage_efforts = None
|
||||
self.sen_stage_efforts = None
|
||||
|
||||
if self.port_type == "rw":
|
||||
self.num_control_signals = 2
|
||||
else:
|
||||
self.num_control_signals = 1
|
||||
|
||||
self.create_netlist()
|
||||
if not OPTS.netlist_only:
|
||||
self.create_layout()
|
||||
|
||||
def create_netlist(self):
|
||||
self.setup_signal_busses()
|
||||
self.add_pins()
|
||||
self.add_modules()
|
||||
self.create_instances()
|
||||
|
||||
def create_layout(self):
|
||||
""" Create layout and route between modules """
|
||||
self.place_instances()
|
||||
self.route_all()
|
||||
# self.add_lvs_correspondence_points()
|
||||
self.add_boundary()
|
||||
self.DRC_LVS()
|
||||
|
||||
def get_dynamic_delay_chain_size(self, previous_stages, previous_fanout):
|
||||
"""Determine the size of the delay chain used for the Sense Amp Enable using path delays"""
|
||||
from math import ceil
|
||||
previous_delay_chain_delay = (previous_fanout + 1 + self.inv_parasitic_delay) * previous_stages
|
||||
debug.info(2, "Previous delay chain produced {} delay units".format(previous_delay_chain_delay))
|
||||
|
||||
# This can be anything >=2
|
||||
delay_fanout = 3
|
||||
# The delay chain uses minimum sized inverters. There are (fanout+1)*stages inverters and each
|
||||
# inverter adds 1 unit of delay (due to minimum size). This also depends on the pinv value
|
||||
required_delay = self.wl_delay * self.wl_timing_tolerance - (self.sen_delay - previous_delay_chain_delay)
|
||||
debug.check(required_delay > 0, "Cannot size delay chain to have negative delay")
|
||||
delay_per_stage = delay_fanout + 1 + self.inv_parasitic_delay
|
||||
delay_stages = ceil(required_delay / delay_per_stage)
|
||||
# force an even number of stages.
|
||||
if delay_stages % 2 == 1:
|
||||
delay_stages += 1
|
||||
# Fanout can be varied as well but is a little more complicated but potentially optimal.
|
||||
debug.info(1, "Setting delay chain to {} stages with {} fanout to match {} delay".format(delay_stages, delay_fanout, required_delay))
|
||||
return (delay_stages, delay_fanout)
|
||||
|
||||
def get_dynamic_delay_fanout_list(self, previous_stages, previous_fanout):
|
||||
"""Determine the size of the delay chain used for the Sense Amp Enable using path delays"""
|
||||
|
||||
previous_delay_per_stage = previous_fanout + 1 + self.inv_parasitic_delay
|
||||
previous_delay_chain_delay = previous_delay_per_stage * previous_stages
|
||||
debug.info(2, "Previous delay chain produced {} delay units".format(previous_delay_chain_delay))
|
||||
|
||||
fanout_rise = fanout_fall = 2 # This can be anything >=2
|
||||
# The delay chain uses minimum sized inverters. There are (fanout+1)*stages inverters and each
|
||||
# inverter adds 1 unit of delay (due to minimum size). This also depends on the pinv value
|
||||
required_delay_fall = self.wl_delay_fall * self.wl_timing_tolerance - \
|
||||
(self.sen_delay_fall - previous_delay_chain_delay / 2)
|
||||
required_delay_rise = self.wl_delay_rise * self.wl_timing_tolerance - \
|
||||
(self.sen_delay_rise - previous_delay_chain_delay / 2)
|
||||
debug.info(2,
|
||||
"Required delays from chain: fall={}, rise={}".format(required_delay_fall,
|
||||
required_delay_rise))
|
||||
|
||||
# If the fanout is different between rise/fall by this amount. Stage algorithm is made more pessimistic.
|
||||
WARNING_FANOUT_DIFF = 5
|
||||
stages_close = False
|
||||
# The stages need to be equal (or at least a even number of stages with matching rise/fall delays)
|
||||
while True:
|
||||
stages_fall = self.calculate_stages_with_fixed_fanout(required_delay_fall,
|
||||
fanout_fall)
|
||||
stages_rise = self.calculate_stages_with_fixed_fanout(required_delay_rise,
|
||||
fanout_rise)
|
||||
debug.info(1,
|
||||
"Fall stages={}, rise stages={}".format(stages_fall,
|
||||
stages_rise))
|
||||
if abs(stages_fall - stages_rise) == 1 and not stages_close:
|
||||
stages_close = True
|
||||
safe_fanout_rise = fanout_rise
|
||||
safe_fanout_fall = fanout_fall
|
||||
|
||||
if stages_fall == stages_rise:
|
||||
break
|
||||
elif abs(stages_fall - stages_rise) == 1 and WARNING_FANOUT_DIFF < abs(fanout_fall - fanout_rise):
|
||||
debug.info(1, "Delay chain fanouts between stages are large. Making chain size larger for safety.")
|
||||
fanout_rise = safe_fanout_rise
|
||||
fanout_fall = safe_fanout_fall
|
||||
break
|
||||
# There should also be a condition to make sure the fanout does not get too large.
|
||||
# Otherwise, increase the fanout of delay with the most stages, calculate new stages
|
||||
elif stages_fall>stages_rise:
|
||||
fanout_fall+=1
|
||||
else:
|
||||
fanout_rise+=1
|
||||
|
||||
total_stages = max(stages_fall, stages_rise) * 2
|
||||
debug.info(1, "New Delay chain: stages={}, fanout_rise={}, fanout_fall={}".format(total_stages, fanout_rise, fanout_fall))
|
||||
|
||||
# Creates interleaved fanout list of rise/fall delays. Assumes fall is the first stage.
|
||||
stage_list = [fanout_fall if i % 2==0 else fanout_rise for i in range(total_stages)]
|
||||
return stage_list
|
||||
|
||||
def calculate_stages_with_fixed_fanout(self, required_delay, fanout):
|
||||
from math import ceil
|
||||
# Delay being negative is not an error. It implies that any amount of stages would have a negative effect on the overall delay
|
||||
# 3 is the minimum delay per stage (with pinv=0).
|
||||
if required_delay <= 3 + self.inv_parasitic_delay:
|
||||
return 1
|
||||
delay_per_stage = fanout + 1 + self.inv_parasitic_delay
|
||||
delay_stages = ceil(required_delay / delay_per_stage)
|
||||
return delay_stages
|
||||
|
||||
def route_rails(self):
|
||||
""" Add the input signal inverted tracks """
|
||||
height = self.control_logic_center.y - self.m2_pitch
|
||||
# DFF spacing plus the power routing
|
||||
offset = vector(self.ctrl_dff_array.width + self.m4_pitch, 0)
|
||||
|
||||
self.input_bus = self.create_vertical_bus("m2",
|
||||
offset,
|
||||
self.internal_bus_list,
|
||||
height)
|
||||
|
||||
def place_instances(self):
|
||||
""" Place all the instances """
|
||||
# Keep track of all right-most instances to determine row boundary
|
||||
# and add the vdd/gnd pins
|
||||
self.row_end_inst = []
|
||||
|
||||
# Add the control flops on the left of the bus
|
||||
self.place_dffs()
|
||||
|
||||
# All of the control logic is placed to the right of the DFFs and bus
|
||||
# as well as the power supply stripe
|
||||
self.control_x_offset = self.ctrl_dff_array.width + self.internal_bus_width + self.m4_pitch
|
||||
|
||||
self.place_logic_rows()
|
||||
|
||||
# Delay chain always gets placed at row 4
|
||||
self.place_delay(4)
|
||||
height = self.delay_inst.uy()
|
||||
|
||||
# This offset is used for placement of the control logic in the SRAM level.
|
||||
self.control_logic_center = vector(self.ctrl_dff_inst.rx(), self.control_center_y)
|
||||
|
||||
# Extra pitch on top and right
|
||||
self.height = height + 2 * self.m1_pitch
|
||||
# Max of modules or logic rows
|
||||
self.width = max([inst.rx() for inst in self.row_end_inst])
|
||||
if (self.port_type == "rw") or (self.port_type == "r"):
|
||||
self.width = max(self.delay_inst.rx(), self.width)
|
||||
self.width += self.m2_pitch
|
||||
|
||||
def place_delay(self, row):
|
||||
""" Place the delay chain """
|
||||
debug.check(row % 2 == 0, "Must place delay chain at even row for supply alignment.")
|
||||
|
||||
# It is flipped on X axis
|
||||
y_off = row * self.and2.height + self.delay_chain.height
|
||||
|
||||
# Add to the right of the control rows and routing channel
|
||||
offset = vector(0, y_off)
|
||||
self.delay_inst.place(offset, mirror="MX")
|
||||
|
||||
def create_clk_buf_row(self):
|
||||
""" Create the multistage and gated clock buffer """
|
||||
self.clk_buf_inst = self.add_inst(name="clkbuf",
|
||||
mod=self.clk_buf_driver)
|
||||
self.connect_inst(["clk", "clk_buf", "vdd", "gnd"])
|
||||
|
||||
def place_clk_buf_row(self, row):
|
||||
x_offset = self.control_x_offset
|
||||
|
||||
x_offset = self.place_util(self.clk_buf_inst, x_offset, row)
|
||||
|
||||
self.row_end_inst.append(self.clk_buf_inst)
|
||||
|
||||
def route_clk_buf(self):
|
||||
clk_pin = self.clk_buf_inst.get_pin("A")
|
||||
clk_pos = clk_pin.center()
|
||||
self.add_layout_pin_rect_center(text="clk",
|
||||
layer="m2",
|
||||
offset=clk_pos)
|
||||
self.add_via_stack_center(from_layer=clk_pin.layer,
|
||||
to_layer="m2",
|
||||
offset=clk_pos)
|
||||
|
||||
self.route_output_to_bus_jogged(self.clk_buf_inst,
|
||||
"clk_buf")
|
||||
self.connect_output(self.clk_buf_inst, "Z", "clk_buf")
|
||||
|
||||
def create_gated_clk_bar_row(self):
|
||||
self.clk_bar_inst = self.add_inst(name="inv_clk_bar",
|
||||
mod=self.inv)
|
||||
self.connect_inst(["clk_buf", "clk_bar", "vdd", "gnd"])
|
||||
|
||||
self.gated_clk_bar_inst = self.add_inst(name="and2_gated_clk_bar",
|
||||
mod=self.and2)
|
||||
self.connect_inst(["clk_bar", "cs", "gated_clk_bar", "vdd", "gnd"])
|
||||
|
||||
def place_gated_clk_bar_row(self, row):
|
||||
x_offset = self.control_x_offset
|
||||
|
||||
x_offset = self.place_util(self.clk_bar_inst, x_offset, row)
|
||||
x_offset = self.place_util(self.gated_clk_bar_inst, x_offset, row)
|
||||
|
||||
self.row_end_inst.append(self.gated_clk_bar_inst)
|
||||
|
||||
def route_gated_clk_bar(self):
|
||||
clkbuf_map = zip(["A"], ["clk_buf"])
|
||||
self.connect_vertical_bus(clkbuf_map, self.clk_bar_inst, self.input_bus)
|
||||
|
||||
out_pin = self.clk_bar_inst.get_pin("Z")
|
||||
out_pos = out_pin.center()
|
||||
in_pin = self.gated_clk_bar_inst.get_pin("A")
|
||||
in_pos = in_pin.center()
|
||||
self.add_zjog(out_pin.layer, out_pos, in_pos)
|
||||
self.add_via_stack_center(from_layer=out_pin.layer,
|
||||
to_layer=in_pin.layer,
|
||||
offset=in_pos)
|
||||
|
||||
|
||||
# This is the second gate over, so it needs to be on M3
|
||||
clkbuf_map = zip(["B"], ["cs"])
|
||||
self.connect_vertical_bus(clkbuf_map,
|
||||
self.gated_clk_bar_inst,
|
||||
self.input_bus,
|
||||
self.m2_stack[::-1])
|
||||
# The pin is on M1, so we need another via as well
|
||||
b_pin = self.gated_clk_bar_inst.get_pin("B")
|
||||
self.add_via_stack_center(from_layer=b_pin.layer,
|
||||
to_layer="m3",
|
||||
offset=b_pin.center())
|
||||
|
||||
# This is the second gate over, so it needs to be on M3
|
||||
self.route_output_to_bus_jogged(self.gated_clk_bar_inst,
|
||||
"gated_clk_bar")
|
||||
|
||||
def create_gated_clk_buf_row(self):
|
||||
self.gated_clk_buf_inst = self.add_inst(name="and2_gated_clk_buf",
|
||||
mod=self.and2)
|
||||
self.connect_inst(["clk_buf", "cs", "gated_clk_buf", "vdd", "gnd"])
|
||||
|
||||
def place_gated_clk_buf_row(self, row):
|
||||
x_offset = self.control_x_offset
|
||||
|
||||
x_offset = self.place_util(self.gated_clk_buf_inst, x_offset, row)
|
||||
|
||||
self.row_end_inst.append(self.gated_clk_buf_inst)
|
||||
|
||||
def route_gated_clk_buf(self):
|
||||
clkbuf_map = zip(["A", "B"], ["clk_buf", "cs"])
|
||||
self.connect_vertical_bus(clkbuf_map,
|
||||
self.gated_clk_buf_inst,
|
||||
self.input_bus)
|
||||
|
||||
clkbuf_map = zip(["Z"], ["gated_clk_buf"])
|
||||
self.connect_vertical_bus(clkbuf_map,
|
||||
self.gated_clk_buf_inst,
|
||||
self.input_bus,
|
||||
self.m2_stack[::-1])
|
||||
# The pin is on M1, so we need another via as well
|
||||
z_pin = self.gated_clk_buf_inst.get_pin("Z")
|
||||
self.add_via_stack_center(from_layer=z_pin.layer,
|
||||
to_layer="m2",
|
||||
offset=z_pin.center())
|
||||
|
||||
def create_dffs(self):
|
||||
self.ctrl_dff_inst=self.add_inst(name="ctrl_dffs",
|
||||
mod=self.ctrl_dff_array)
|
||||
inst_pins = self.input_list + self.dff_output_list + ["clk_buf"] + self.supply_list
|
||||
self.connect_inst(inst_pins)
|
||||
|
||||
def place_dffs(self):
|
||||
self.ctrl_dff_inst.place(vector(0, 0))
|
||||
|
||||
def route_dffs(self):
|
||||
if self.port_type == "rw":
|
||||
dff_out_map = zip(["dout_bar_0", "dout_bar_1", "dout_1"], ["cs", "we", "we_bar"])
|
||||
elif self.port_type == "r":
|
||||
dff_out_map = zip(["dout_bar_0", "dout_0"], ["cs", "cs_bar"])
|
||||
else:
|
||||
dff_out_map = zip(["dout_bar_0"], ["cs"])
|
||||
self.connect_vertical_bus(dff_out_map, self.ctrl_dff_inst, self.input_bus, self.m2_stack[::-1])
|
||||
|
||||
# Connect the clock rail to the other clock rail
|
||||
# by routing in the supply rail track to avoid channel conflicts
|
||||
in_pos = self.ctrl_dff_inst.get_pin("clk").uc()
|
||||
mid_pos = vector(in_pos.x, self.gated_clk_buf_inst.get_pin("vdd").cy() - self.m1_pitch)
|
||||
rail_pos = vector(self.input_bus["clk_buf"].cx(), mid_pos.y)
|
||||
self.add_wire(self.m1_stack, [in_pos, mid_pos, rail_pos])
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=rail_pos)
|
||||
|
||||
self.copy_layout_pin(self.ctrl_dff_inst, "din_0", "csb")
|
||||
if (self.port_type == "rw"):
|
||||
self.copy_layout_pin(self.ctrl_dff_inst, "din_1", "web")
|
||||
|
||||
def get_offset(self, row):
|
||||
""" Compute the y-offset and mirroring """
|
||||
y_off = row * self.and2.height
|
||||
if row % 2:
|
||||
y_off += self.and2.height
|
||||
mirror="MX"
|
||||
else:
|
||||
mirror="R0"
|
||||
|
||||
return (y_off, mirror)
|
||||
|
||||
def connect_output(self, inst, pin_name, out_name):
|
||||
""" Create an output pin on the right side from the pin of a given instance. """
|
||||
|
||||
out_pin = inst.get_pin(pin_name)
|
||||
out_pos = out_pin.center()
|
||||
right_pos = out_pos + vector(self.width - out_pin.cx(), 0)
|
||||
|
||||
self.add_via_stack_center(from_layer=out_pin.layer,
|
||||
to_layer="m2",
|
||||
offset=out_pos)
|
||||
self.add_layout_pin_segment_center(text=out_name,
|
||||
layer="m2",
|
||||
start=out_pos,
|
||||
end=right_pos)
|
||||
|
||||
def route_supplies(self):
|
||||
""" Add vdd and gnd to the instance cells """
|
||||
|
||||
pin_layer = self.dff.get_pin("vdd").layer
|
||||
supply_layer = self.supply_stack[2]
|
||||
|
||||
|
||||
# FIXME: We should be able to replace this with route_vertical_pins instead
|
||||
# but we may have to make the logic gates a separate module so that they
|
||||
# have row pins of the same width
|
||||
max_row_x_loc = max([inst.rx() for inst in self.row_end_inst])
|
||||
min_row_x_loc = self.control_x_offset
|
||||
|
||||
vdd_pin_locs = []
|
||||
gnd_pin_locs = []
|
||||
|
||||
last_via = None
|
||||
for inst in self.row_end_inst:
|
||||
pins = inst.get_pins("vdd")
|
||||
for pin in pins:
|
||||
if pin.layer == pin_layer:
|
||||
row_loc = pin.rc()
|
||||
pin_loc = vector(max_row_x_loc, pin.rc().y)
|
||||
vdd_pin_locs.append(pin_loc)
|
||||
last_via = self.add_via_stack_center(from_layer=pin_layer,
|
||||
to_layer=supply_layer,
|
||||
offset=pin_loc,
|
||||
min_area=True)
|
||||
self.add_path(pin_layer, [row_loc, pin_loc])
|
||||
|
||||
pins = inst.get_pins("gnd")
|
||||
for pin in pins:
|
||||
if pin.layer == pin_layer:
|
||||
row_loc = pin.rc()
|
||||
pin_loc = vector(min_row_x_loc, pin.rc().y)
|
||||
gnd_pin_locs.append(pin_loc)
|
||||
last_via = self.add_via_stack_center(from_layer=pin_layer,
|
||||
to_layer=supply_layer,
|
||||
offset=pin_loc,
|
||||
min_area=True)
|
||||
self.add_path(pin_layer, [row_loc, pin_loc])
|
||||
|
||||
if last_via:
|
||||
via_height=last_via.mod.second_layer_height
|
||||
via_width=last_via.mod.second_layer_width
|
||||
else:
|
||||
via_height=None
|
||||
via_width=0
|
||||
|
||||
min_y = min([x.y for x in vdd_pin_locs])
|
||||
max_y = max([x.y for x in vdd_pin_locs])
|
||||
bot_pos = vector(max_row_x_loc, min_y - 0.5 * via_height)
|
||||
top_pos = vector(max_row_x_loc, max_y + 0.5 * via_height)
|
||||
self.add_layout_pin_segment_center(text="vdd",
|
||||
layer=supply_layer,
|
||||
start=bot_pos,
|
||||
end=top_pos,
|
||||
width=via_width)
|
||||
|
||||
min_y = min([x.y for x in gnd_pin_locs])
|
||||
max_y = max([x.y for x in gnd_pin_locs])
|
||||
bot_pos = vector(min_row_x_loc, min_y - 0.5 * via_height)
|
||||
top_pos = vector(min_row_x_loc, max_y + 0.5 * via_height)
|
||||
self.add_layout_pin_segment_center(text="gnd",
|
||||
layer=supply_layer,
|
||||
start=bot_pos,
|
||||
end=top_pos,
|
||||
width=via_width)
|
||||
|
||||
self.copy_layout_pin(self.delay_inst, "gnd")
|
||||
self.copy_layout_pin(self.delay_inst, "vdd")
|
||||
|
||||
self.copy_layout_pin(self.ctrl_dff_inst, "gnd")
|
||||
self.copy_layout_pin(self.ctrl_dff_inst, "vdd")
|
||||
|
||||
def add_lvs_correspondence_points(self):
|
||||
""" This adds some points for easier debugging if LVS goes wrong.
|
||||
These should probably be turned off by default though, since extraction
|
||||
will show these as ports in the extracted netlist.
|
||||
"""
|
||||
# pin=self.clk_inv1.get_pin("Z")
|
||||
# self.add_label_pin(text="clk1_bar",
|
||||
# layer="m1",
|
||||
# offset=pin.ll(),
|
||||
# height=pin.height(),
|
||||
# width=pin.width())
|
||||
|
||||
# pin=self.clk_inv2.get_pin("Z")
|
||||
# self.add_label_pin(text="clk2",
|
||||
# layer="m1",
|
||||
# offset=pin.ll(),
|
||||
# height=pin.height(),
|
||||
# width=pin.width())
|
||||
|
||||
pin=self.delay_inst.get_pin("out")
|
||||
self.add_label_pin(text="out",
|
||||
layer=pin.layer,
|
||||
offset=pin.ll(),
|
||||
height=pin.height(),
|
||||
width=pin.width())
|
||||
|
||||
def graph_exclude_dffs(self):
|
||||
"""Exclude dffs from graph as they do not represent critical path"""
|
||||
|
||||
self.graph_inst_exclude.add(self.ctrl_dff_inst)
|
||||
if self.port_type=="rw" or self.port_type=="w":
|
||||
self.graph_inst_exclude.add(self.w_en_gate_inst)
|
||||
|
||||
def place_util(self, inst, x_offset, row):
|
||||
""" Utility to place a row and compute the next offset """
|
||||
|
||||
(y_offset, mirror) = self.get_offset(row)
|
||||
offset = vector(x_offset, y_offset)
|
||||
inst.place(offset, mirror)
|
||||
return x_offset + inst.width
|
||||
|
||||
def route_output_to_bus_jogged(self, inst, name):
|
||||
# Connect this at the bottom of the buffer
|
||||
out_pin = inst.get_pin("Z")
|
||||
out_pos = out_pin.center()
|
||||
mid1 = vector(out_pos.x, out_pos.y - 0.3 * inst.mod.height)
|
||||
mid2 = vector(self.input_bus[name].cx(), mid1.y)
|
||||
bus_pos = self.input_bus[name].center()
|
||||
self.add_wire(self.m2_stack[::-1], [out_pos, mid1, mid2, bus_pos])
|
||||
self.add_via_stack_center(from_layer=out_pin.layer,
|
||||
to_layer="m2",
|
||||
offset=out_pos)
|
||||
|
||||
def get_left_pins(self, name):
|
||||
"""
|
||||
Return the left side supply pins to connect to a vertical stripe.
|
||||
"""
|
||||
return(self.cntrl_dff_inst.get_pins(name) + self.delay_inst.get_pins(name))
|
||||
|
|
@ -25,7 +25,7 @@ class port_data(design):
|
|||
|
||||
sram_config.set_local_config(self)
|
||||
self.port = port
|
||||
if self.write_size is not None:
|
||||
if self.write_size != self.word_size:
|
||||
self.num_wmasks = int(math.ceil(self.word_size / self.write_size))
|
||||
else:
|
||||
self.num_wmasks = 0
|
||||
|
|
@ -93,7 +93,7 @@ class port_data(design):
|
|||
|
||||
if self.write_driver_array:
|
||||
self.create_write_driver_array()
|
||||
if self.write_size is not None:
|
||||
if self.write_size != self.word_size:
|
||||
self.create_write_mask_and_array()
|
||||
else:
|
||||
self.write_mask_and_array_inst = None
|
||||
|
|
@ -245,7 +245,7 @@ class port_data(design):
|
|||
offsets=self.bit_offsets,
|
||||
write_size=self.write_size,
|
||||
num_spare_cols=self.num_spare_cols)
|
||||
if self.write_size is not None:
|
||||
if self.write_size != self.word_size:
|
||||
# RBLs don't get a write mask
|
||||
self.write_mask_and_array = factory.create(module_type="write_mask_and_array",
|
||||
columns=self.num_cols,
|
||||
|
|
@ -391,13 +391,13 @@ class port_data(design):
|
|||
temp.append("sparebl_{0}".format(bit))
|
||||
temp.append("sparebr_{0}".format(bit))
|
||||
|
||||
if self.write_size is not None:
|
||||
if self.write_size != self.word_size:
|
||||
for i in range(self.num_wmasks):
|
||||
temp.append("wdriver_sel_{}".format(i))
|
||||
for i in range(self.num_spare_cols):
|
||||
temp.append("bank_spare_wen{}".format(i))
|
||||
|
||||
elif self.num_spare_cols and not self.write_size:
|
||||
elif self.num_spare_cols and self.write_size == self.word_size:
|
||||
temp.append("w_en")
|
||||
for i in range(self.num_spare_cols):
|
||||
temp.append("bank_spare_wen{}".format(i))
|
||||
|
|
|
|||
|
|
@ -36,14 +36,10 @@ class sram():
|
|||
|
||||
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)
|
||||
from .sram_1bank import sram_1bank as sram
|
||||
|
||||
self.s = sram(name, sram_config)
|
||||
|
||||
self.s.create_netlist()
|
||||
if not OPTS.netlist_only:
|
||||
self.s.create_layout()
|
||||
|
|
@ -62,6 +58,10 @@ class sram():
|
|||
|
||||
def verilog_write(self, name):
|
||||
self.s.verilog_write(name)
|
||||
if self.num_banks != 1:
|
||||
from .sram_multibank import sram_multibank
|
||||
mb = sram_multibank(self.s)
|
||||
mb.verilog_write(name[:-2] + '_top.v')
|
||||
|
||||
def extended_config_write(self, name):
|
||||
"""Dump config file with all options.
|
||||
|
|
@ -166,7 +166,7 @@ class sram():
|
|||
|
||||
# Write a verilog model
|
||||
start_time = datetime.datetime.now()
|
||||
vname = OPTS.output_path + self.s.name + ".v"
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -6,18 +6,756 @@
|
|||
# All rights reserved.
|
||||
#
|
||||
from base import vector
|
||||
from .sram_base import sram_base
|
||||
from base import channel_route
|
||||
from router import router_tech
|
||||
from globals import OPTS
|
||||
from globals import OPTS, print_time
|
||||
import datetime
|
||||
import debug
|
||||
from math import ceil
|
||||
from importlib import reload
|
||||
from base import design
|
||||
from base import verilog
|
||||
from base import lef
|
||||
from sram_factory import factory
|
||||
from tech import spice
|
||||
|
||||
|
||||
class sram_1bank(sram_base):
|
||||
class sram_1bank(design, verilog, lef):
|
||||
"""
|
||||
Procedures specific to a one bank SRAM.
|
||||
"""
|
||||
def __init__(self, name, sram_config):
|
||||
sram_base.__init__(self, name, sram_config)
|
||||
design.__init__(self, name)
|
||||
lef.__init__(self, ["m1", "m2", "m3", "m4"])
|
||||
verilog.__init__(self)
|
||||
|
||||
self.sram_config = sram_config
|
||||
sram_config.set_local_config(self)
|
||||
|
||||
self.bank_insts = []
|
||||
|
||||
if self.write_size != self.word_size:
|
||||
self.num_wmasks = int(ceil(self.word_size / self.write_size))
|
||||
else:
|
||||
self.num_wmasks = 0
|
||||
|
||||
if not self.num_spare_cols:
|
||||
self.num_spare_cols = 0
|
||||
|
||||
try:
|
||||
from tech import power_grid
|
||||
self.supply_stack = power_grid
|
||||
except ImportError:
|
||||
# if no power_grid is specified by tech we use sensible defaults
|
||||
# Route a M3/M4 grid
|
||||
self.supply_stack = self.m3_stack
|
||||
|
||||
def add_pins(self):
|
||||
""" Add pins for entire SRAM. """
|
||||
|
||||
for port in self.write_ports:
|
||||
for bit in range(self.word_size + self.num_spare_cols):
|
||||
self.add_pin("din{0}[{1}]".format(port, bit), "INPUT")
|
||||
for port in self.all_ports:
|
||||
for bit in range(self.bank_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")
|
||||
# add the optional write mask pins
|
||||
for port in self.write_ports:
|
||||
for bit in range(self.num_wmasks):
|
||||
self.add_pin("wmask{0}[{1}]".format(port, bit), "INPUT")
|
||||
if self.num_spare_cols == 1:
|
||||
self.add_pin("spare_wen{0}".format(port), "INPUT")
|
||||
else:
|
||||
for bit in range(self.num_spare_cols):
|
||||
self.add_pin("spare_wen{0}[{1}]".format(port, bit), "INPUT")
|
||||
for port in self.read_ports:
|
||||
for bit in range(self.word_size + self.num_spare_cols):
|
||||
self.add_pin("dout{0}[{1}]".format(port, bit), "OUTPUT")
|
||||
|
||||
# Standard supply and ground names
|
||||
try:
|
||||
self.vdd_name = spice["power"]
|
||||
except KeyError:
|
||||
self.vdd_name = "vdd"
|
||||
try:
|
||||
self.gnd_name = spice["ground"]
|
||||
except KeyError:
|
||||
self.gnd_name = "gnd"
|
||||
|
||||
self.add_pin(self.vdd_name, "POWER")
|
||||
self.add_pin(self.gnd_name, "GROUND")
|
||||
self.ext_supplies = [self.vdd_name, self.gnd_name]
|
||||
self.ext_supply = {"vdd" : self.vdd_name, "gnd" : self.gnd_name}
|
||||
|
||||
def add_global_pex_labels(self):
|
||||
"""
|
||||
Add pex labels at the sram level for spice analysis
|
||||
"""
|
||||
|
||||
|
||||
|
||||
# add pex labels for bitcells
|
||||
for bank_num in range(len(self.bank_insts)):
|
||||
bank = self.bank_insts[bank_num]
|
||||
pex_data = bank.reverse_transformation_bitcell(self.bitcell.name)
|
||||
|
||||
bank_offset = pex_data[0] # offset bank relative to sram
|
||||
Q_offset = pex_data[1] # offset of storage relative to bank
|
||||
Q_bar_offset = pex_data[2] # offset of storage relative to bank
|
||||
bl_offsets = pex_data[3]
|
||||
br_offsets = pex_data[4]
|
||||
bl_meta = pex_data[5]
|
||||
br_meta = pex_data[6]
|
||||
|
||||
bl = []
|
||||
br = []
|
||||
|
||||
storage_layer_name = "m1"
|
||||
bitline_layer_name = self.bitcell.get_pin("bl").layer
|
||||
|
||||
for cell in range(len(bank_offset)):
|
||||
Q = [bank_offset[cell][0] + Q_offset[cell][0],
|
||||
bank_offset[cell][1] + Q_offset[cell][1]]
|
||||
Q_bar = [bank_offset[cell][0] + Q_bar_offset[cell][0],
|
||||
bank_offset[cell][1] + Q_bar_offset[cell][1]]
|
||||
OPTS.words_per_row = self.words_per_row
|
||||
row = int(cell % (OPTS.num_words / self.words_per_row))
|
||||
col = int(cell / (OPTS.num_words))
|
||||
self.add_layout_pin_rect_center("bitcell_Q_b{}_r{}_c{}".format(bank_num,
|
||||
row,
|
||||
col),
|
||||
storage_layer_name,
|
||||
Q)
|
||||
self.add_layout_pin_rect_center("bitcell_Q_bar_b{}_r{}_c{}".format(bank_num,
|
||||
row,
|
||||
col),
|
||||
storage_layer_name,
|
||||
Q_bar)
|
||||
|
||||
for cell in range(len(bl_offsets)):
|
||||
col = bl_meta[cell][0][2]
|
||||
for bitline in range(len(bl_offsets[cell])):
|
||||
bitline_location = [float(bank_offset[cell][0]) + bl_offsets[cell][bitline][0],
|
||||
float(bank_offset[cell][1]) + bl_offsets[cell][bitline][1]]
|
||||
bl.append([bitline_location, bl_meta[cell][bitline][3], col])
|
||||
|
||||
for cell in range(len(br_offsets)):
|
||||
col = br_meta[cell][0][2]
|
||||
for bitline in range(len(br_offsets[cell])):
|
||||
bitline_location = [float(bank_offset[cell][0]) + br_offsets[cell][bitline][0],
|
||||
float(bank_offset[cell][1]) + br_offsets[cell][bitline][1]]
|
||||
br.append([bitline_location, br_meta[cell][bitline][3], col])
|
||||
|
||||
for i in range(len(bl)):
|
||||
self.add_layout_pin_rect_center("bl{0}_{1}".format(bl[i][1], bl[i][2]),
|
||||
bitline_layer_name, bl[i][0])
|
||||
|
||||
for i in range(len(br)):
|
||||
self.add_layout_pin_rect_center("br{0}_{1}".format(br[i][1], br[i][2]),
|
||||
bitline_layer_name, br[i][0])
|
||||
|
||||
# add pex labels for control logic
|
||||
for i in range(len(self.control_logic_insts)):
|
||||
instance = self.control_logic_insts[i]
|
||||
control_logic_offset = instance.offset
|
||||
for output in instance.mod.output_list:
|
||||
pin = instance.mod.get_pin(output)
|
||||
pin.transform([0, 0], instance.mirror, instance.rotate)
|
||||
offset = [control_logic_offset[0] + pin.center()[0],
|
||||
control_logic_offset[1] + pin.center()[1]]
|
||||
self.add_layout_pin_rect_center("{0}{1}".format(pin.name, i),
|
||||
storage_layer_name,
|
||||
offset)
|
||||
|
||||
def create_netlist(self):
|
||||
""" Netlist creation """
|
||||
|
||||
start_time = datetime.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.datetime.now(), start_time)
|
||||
|
||||
def create_layout(self):
|
||||
""" Layout creation """
|
||||
start_time = datetime.datetime.now()
|
||||
self.place_instances()
|
||||
if not OPTS.is_unit_test:
|
||||
print_time("Placement", datetime.datetime.now(), start_time)
|
||||
|
||||
start_time = datetime.datetime.now()
|
||||
self.route_layout()
|
||||
|
||||
if not OPTS.is_unit_test:
|
||||
print_time("Routing", datetime.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]
|
||||
if OPTS.use_pex and OPTS.pex_exe[0] != "calibre":
|
||||
debug.info(2, "adding global pex labels")
|
||||
self.add_global_pex_labels()
|
||||
self.add_boundary(ll=vector(0, 0),
|
||||
ur=vector(self.width, self.height))
|
||||
|
||||
start_time = datetime.datetime.now()
|
||||
if not OPTS.is_unit_test:
|
||||
# We only enable final verification if we have routed the design
|
||||
# Only run this if not a unit test, because unit test will also verify it.
|
||||
self.DRC_LVS(final_verification=OPTS.route_supplies, force_check=OPTS.check_lvsdrc)
|
||||
print_time("Verification", datetime.datetime.now(), start_time)
|
||||
|
||||
def create_modules(self):
|
||||
debug.error("Must override pure virtual function.", -1)
|
||||
|
||||
def route_supplies(self, bbox=None):
|
||||
""" 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 pin_name in ["vdd", "gnd"]:
|
||||
for inst in self.insts:
|
||||
self.copy_power_pins(inst, pin_name, self.ext_supply[pin_name])
|
||||
|
||||
if not OPTS.route_supplies:
|
||||
# Do not route the power supply (leave as must-connect pins)
|
||||
return
|
||||
elif OPTS.route_supplies == "grid":
|
||||
from router import supply_grid_router as router
|
||||
else:
|
||||
from router import supply_tree_router as router
|
||||
rtr=router(layers=self.supply_stack,
|
||||
design=self,
|
||||
bbox=bbox,
|
||||
pin_type=OPTS.supply_pin_type)
|
||||
|
||||
rtr.route()
|
||||
|
||||
if OPTS.supply_pin_type in ["left", "right", "top", "bottom", "ring"]:
|
||||
# Find the lowest leftest pin for vdd and gnd
|
||||
for pin_name in ["vdd", "gnd"]:
|
||||
# Copy the pin shape(s) to rectangles
|
||||
for pin in self.get_pins(pin_name):
|
||||
self.add_rect(pin.layer,
|
||||
pin.ll(),
|
||||
pin.width(),
|
||||
pin.height())
|
||||
|
||||
# Remove the pin shape(s)
|
||||
self.remove_layout_pin(pin_name)
|
||||
|
||||
# Get new pins
|
||||
pins = rtr.get_new_pins(pin_name)
|
||||
for pin in pins:
|
||||
self.add_layout_pin(self.ext_supply[pin_name],
|
||||
pin.layer,
|
||||
pin.ll(),
|
||||
pin.width(),
|
||||
pin.height())
|
||||
|
||||
elif OPTS.route_supplies and OPTS.supply_pin_type == "single":
|
||||
# Update these as we may have routed outside the region (perimeter pins)
|
||||
lowest_coord = self.find_lowest_coords()
|
||||
|
||||
# Find the lowest leftest pin for vdd and gnd
|
||||
for pin_name in ["vdd", "gnd"]:
|
||||
# Copy the pin shape(s) to rectangles
|
||||
for pin in self.get_pins(pin_name):
|
||||
self.add_rect(pin.layer,
|
||||
pin.ll(),
|
||||
pin.width(),
|
||||
pin.height())
|
||||
|
||||
# Remove the pin shape(s)
|
||||
self.remove_layout_pin(pin_name)
|
||||
|
||||
# Get the lowest, leftest pin
|
||||
pin = rtr.get_ll_pin(pin_name)
|
||||
|
||||
pin_width = 2 * getattr(self, "{}_width".format(pin.layer))
|
||||
|
||||
# Add it as an IO pin to the perimeter
|
||||
route_width = pin.rx() - lowest_coord.x
|
||||
pin_offset = vector(lowest_coord.x, pin.by())
|
||||
self.add_rect(pin.layer,
|
||||
pin_offset,
|
||||
route_width,
|
||||
pin.height())
|
||||
|
||||
self.add_layout_pin(self.ext_supply[pin_name],
|
||||
pin.layer,
|
||||
pin_offset,
|
||||
pin_width,
|
||||
pin.height())
|
||||
else:
|
||||
# Grid is left with many top level pins
|
||||
pass
|
||||
|
||||
def route_escape_pins(self, bbox):
|
||||
"""
|
||||
Add the top-level pins for a single bank SRAM with control.
|
||||
"""
|
||||
|
||||
# List of pin to new pin name
|
||||
pins_to_route = []
|
||||
for port in self.all_ports:
|
||||
# Connect the control pins as inputs
|
||||
for signal in self.control_logic_inputs[port]:
|
||||
if signal.startswith("rbl"):
|
||||
continue
|
||||
if signal=="clk":
|
||||
pins_to_route.append("{0}{1}".format(signal, port))
|
||||
else:
|
||||
pins_to_route.append("{0}{1}".format(signal, port))
|
||||
|
||||
if port in self.write_ports:
|
||||
for bit in range(self.word_size + self.num_spare_cols):
|
||||
pins_to_route.append("din{0}[{1}]".format(port, bit))
|
||||
|
||||
if port in self.readwrite_ports or port in self.read_ports:
|
||||
for bit in range(self.word_size + self.num_spare_cols):
|
||||
pins_to_route.append("dout{0}[{1}]".format(port, bit))
|
||||
|
||||
for bit in range(self.col_addr_size):
|
||||
pins_to_route.append("addr{0}[{1}]".format(port, bit))
|
||||
|
||||
for bit in range(self.row_addr_size):
|
||||
pins_to_route.append("addr{0}[{1}]".format(port, bit + self.col_addr_size))
|
||||
|
||||
if port in self.write_ports:
|
||||
if self.write_size != self.word_size:
|
||||
for bit in range(self.num_wmasks):
|
||||
pins_to_route.append("wmask{0}[{1}]".format(port, bit))
|
||||
|
||||
if port in self.write_ports:
|
||||
if self.num_spare_cols == 1:
|
||||
pins_to_route.append("spare_wen{0}".format(port))
|
||||
else:
|
||||
for bit in range(self.num_spare_cols):
|
||||
pins_to_route.append("spare_wen{0}[{1}]".format(port, bit))
|
||||
|
||||
from router import signal_escape_router as router
|
||||
rtr=router(layers=self.m3_stack,
|
||||
design=self,
|
||||
bbox=bbox)
|
||||
rtr.escape_route(pins_to_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.bank_addr_size + self.control_size + 1# + 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, pen])
|
||||
else:
|
||||
self.control_bus_names[port].extend([sen, wen, pen])
|
||||
self.vert_control_bus_positions = self.create_vertical_bus(layer="m2",
|
||||
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.bank_addr_size)]
|
||||
self.vert_control_bus_positions.update(self.create_vertical_pin_bus(layer="m2",
|
||||
pitch=self.m2_pitch,
|
||||
offset=self.addr_bus_offset,
|
||||
names=self.addr_bus_names,
|
||||
length=self.addr_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="m3",
|
||||
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="m1",
|
||||
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="m1",
|
||||
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="m1",
|
||||
pitch=self.m1_pitch,
|
||||
offset=self.control_bus_offset,
|
||||
names=self.control_bus_names[port],
|
||||
length=self.control_bus_width))
|
||||
|
||||
def add_modules(self):
|
||||
self.bitcell = factory.create(module_type=OPTS.bitcell)
|
||||
self.dff = factory.create(module_type="dff")
|
||||
|
||||
# Create the bank module (up to four are instantiated)
|
||||
self.bank = factory.create("bank", sram_config=self.sram_config, module_name="bank")
|
||||
|
||||
self.num_spare_cols = self.bank.num_spare_cols
|
||||
|
||||
# Create the address and control flops (but not the clk)
|
||||
self.row_addr_dff = factory.create("dff_array", module_name="row_addr_dff", rows=self.row_addr_size, columns=1)
|
||||
|
||||
if self.col_addr_size > 0:
|
||||
self.col_addr_dff = factory.create("dff_array", module_name="col_addr_dff", rows=1, columns=self.col_addr_size)
|
||||
else:
|
||||
self.col_addr_dff = None
|
||||
|
||||
self.data_dff = factory.create("dff_array", module_name="data_dff", rows=1, columns=self.word_size + self.num_spare_cols)
|
||||
|
||||
if self.write_size != self.word_size:
|
||||
self.wmask_dff = factory.create("dff_array", module_name="wmask_dff", rows=1, columns=self.num_wmasks)
|
||||
|
||||
if self.num_spare_cols:
|
||||
self.spare_wen_dff = factory.create("dff_array", module_name="spare_wen_dff", rows=1, columns=self.num_spare_cols)
|
||||
|
||||
self.bank_count = 0
|
||||
|
||||
c = reload(__import__('modules.' + 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,
|
||||
spare_columns=self.num_spare_cols,
|
||||
sram=self,
|
||||
port_type="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,
|
||||
spare_columns=self.num_spare_cols,
|
||||
sram=self,
|
||||
port_type="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,
|
||||
spare_columns=self.num_spare_cols,
|
||||
sram=self,
|
||||
port_type="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 + self.num_spare_cols):
|
||||
temp.append("dout{0}[{1}]".format(port, bit))
|
||||
for port in self.all_ports:
|
||||
temp.append("rbl_bl{0}".format(port))
|
||||
for port in self.write_ports:
|
||||
for bit in range(self.word_size + self.num_spare_cols):
|
||||
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))
|
||||
for port in self.read_ports:
|
||||
temp.append("s_en{0}".format(port))
|
||||
for port in self.all_ports:
|
||||
temp.append("p_en_bar{0}".format(port))
|
||||
for port in self.write_ports:
|
||||
temp.append("w_en{0}".format(port))
|
||||
for bit in range(self.num_wmasks):
|
||||
temp.append("bank_wmask{0}_{1}".format(port, bit))
|
||||
for bit in range(self.num_spare_cols):
|
||||
temp.append("bank_spare_wen{0}_{1}".format(port, bit))
|
||||
for port in self.all_ports:
|
||||
temp.append("wl_en{0}".format(port))
|
||||
temp.extend(self.ext_supplies)
|
||||
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)] + self.ext_supplies)
|
||||
|
||||
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)] + self.ext_supplies)
|
||||
|
||||
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 + self.num_spare_cols):
|
||||
inputs.append("din{}[{}]".format(port, bit))
|
||||
outputs.append("bank_din{}_{}".format(port, bit))
|
||||
|
||||
self.connect_inst(inputs + outputs + ["clk_buf{}".format(port)] + self.ext_supplies)
|
||||
|
||||
return insts
|
||||
|
||||
def create_wmask_dff(self):
|
||||
""" Add and place all wmask flops """
|
||||
insts = []
|
||||
for port in self.all_ports:
|
||||
if port in self.write_ports:
|
||||
insts.append(self.add_inst(name="wmask_dff{}".format(port),
|
||||
mod=self.wmask_dff))
|
||||
else:
|
||||
insts.append(None)
|
||||
continue
|
||||
|
||||
# inputs, outputs/output/bar
|
||||
inputs = []
|
||||
outputs = []
|
||||
for bit in range(self.num_wmasks):
|
||||
inputs.append("wmask{}[{}]".format(port, bit))
|
||||
outputs.append("bank_wmask{}_{}".format(port, bit))
|
||||
|
||||
self.connect_inst(inputs + outputs + ["clk_buf{}".format(port)] + self.ext_supplies)
|
||||
|
||||
return insts
|
||||
|
||||
def create_spare_wen_dff(self):
|
||||
""" Add all spare write enable flops """
|
||||
insts = []
|
||||
for port in self.all_ports:
|
||||
if port in self.write_ports:
|
||||
insts.append(self.add_inst(name="spare_wen_dff{}".format(port),
|
||||
mod=self.spare_wen_dff))
|
||||
else:
|
||||
insts.append(None)
|
||||
continue
|
||||
|
||||
# inputs, outputs/output/bar
|
||||
inputs = []
|
||||
outputs = []
|
||||
for bit in range(self.num_spare_cols):
|
||||
inputs.append("spare_wen{}[{}]".format(port, bit))
|
||||
outputs.append("bank_spare_wen{}_{}".format(port, bit))
|
||||
|
||||
self.connect_inst(inputs + outputs + ["clk_buf{}".format(port)] + self.ext_supplies)
|
||||
|
||||
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))
|
||||
temp.append("rbl_bl{}".format(port))
|
||||
|
||||
# Outputs
|
||||
if port in self.read_ports:
|
||||
temp.append("s_en{}".format(port))
|
||||
if port in self.write_ports:
|
||||
temp.append("w_en{}".format(port))
|
||||
temp.append("p_en_bar{}".format(port))
|
||||
temp.extend(["wl_en{}".format(port), "clk_buf{}".format(port)] + self.ext_supplies)
|
||||
self.connect_inst(temp)
|
||||
|
||||
return insts
|
||||
|
||||
def sp_write(self, sp_name, lvs=False, trim=False):
|
||||
# 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("* Trimmed: {}\n".format(trim))
|
||||
sp.write("* LVS: {}\n".format(lvs))
|
||||
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, lvs=lvs, trim=trim)
|
||||
del usedMODS
|
||||
sp.close()
|
||||
|
||||
def graph_exclude_bits(self, targ_row, targ_col):
|
||||
"""
|
||||
Excludes bits in column from being added to graph except target
|
||||
"""
|
||||
self.bank.graph_exclude_bits(targ_row, targ_col)
|
||||
|
||||
def clear_exclude_bits(self):
|
||||
"""
|
||||
Clears the bit exclusions
|
||||
"""
|
||||
self.bank.clear_exclude_bits()
|
||||
|
||||
def graph_exclude_column_mux(self, column_include_num, port):
|
||||
"""
|
||||
Excludes all columns muxes unrelated to the target bit being simulated.
|
||||
"""
|
||||
self.bank.graph_exclude_column_mux(column_include_num, port)
|
||||
|
||||
def graph_clear_column_mux(self, port):
|
||||
"""
|
||||
Clear mux exclusions to allow different bit tests.
|
||||
"""
|
||||
self.bank.graph_clear_column_mux(port)
|
||||
|
||||
def create_modules(self):
|
||||
"""
|
||||
|
|
@ -34,7 +772,7 @@ class sram_1bank(sram_base):
|
|||
if self.col_addr_dff:
|
||||
self.col_addr_dff_insts = self.create_col_addr_dff()
|
||||
|
||||
if self.write_size:
|
||||
if self.write_size != self.word_size:
|
||||
self.wmask_dff_insts = self.create_wmask_dff()
|
||||
self.data_dff_insts = self.create_data_dff()
|
||||
else:
|
||||
|
|
@ -178,7 +916,7 @@ class sram_1bank(sram_base):
|
|||
self.col_addr_pos[port] = vector(x_offset, 0)
|
||||
|
||||
if port in self.write_ports:
|
||||
if self.write_size:
|
||||
if self.write_size != self.word_size:
|
||||
# Add the write mask flops below the write mask AND array.
|
||||
self.wmask_pos[port] = vector(x_offset,
|
||||
y_offset)
|
||||
|
|
@ -230,7 +968,7 @@ class sram_1bank(sram_base):
|
|||
self.spare_wen_dff_insts[port].place(self.spare_wen_pos[port], mirror="MX")
|
||||
x_offset = self.spare_wen_dff_insts[port].lx()
|
||||
|
||||
if self.write_size:
|
||||
if self.write_size != self.word_size:
|
||||
# Add the write mask flops below the write mask AND array.
|
||||
self.wmask_pos[port] = vector(x_offset - self.wmask_dff_insts[port].width,
|
||||
y_offset)
|
||||
|
|
@ -297,7 +1035,7 @@ class sram_1bank(sram_base):
|
|||
start_layer=pin_layer)
|
||||
|
||||
if port in self.write_ports:
|
||||
if self.write_size:
|
||||
if self.write_size != self.word_size:
|
||||
for bit in range(self.num_wmasks):
|
||||
self.add_io_pin(self.wmask_dff_insts[port],
|
||||
"din_{}".format(bit),
|
||||
|
|
@ -609,7 +1347,7 @@ class sram_1bank(sram_base):
|
|||
# Data dffs and wmask 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)
|
||||
if self.write_size:
|
||||
if self.write_size != self.word_size:
|
||||
for inst in self.wmask_dff_insts:
|
||||
self.graph_inst_exclude.add(inst)
|
||||
if self.num_spare_cols:
|
||||
|
|
|
|||
|
|
@ -1,240 +0,0 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2021 Regents of the University of California and The Board
|
||||
# of Regents for the Oklahoma Agricultural and Mechanical College
|
||||
# (acting for and on behalf of Oklahoma State University)
|
||||
# All rights reserved.
|
||||
#
|
||||
import sys
|
||||
from tech import drc, spice
|
||||
import debug
|
||||
from math import log,sqrt,ceil
|
||||
import datetime
|
||||
import getpass
|
||||
from base import vector
|
||||
from globals import OPTS, print_time
|
||||
|
||||
from .sram_base import sram_base
|
||||
from modules import bank
|
||||
from modules import dff_buf_array
|
||||
from modules 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=="m2":
|
||||
pin_pos = pin.bc()
|
||||
break
|
||||
rail_pos = vector(pin_pos.x,self.horz_control_bus_positions[n].y)
|
||||
self.add_path("m2",[pin_pos,rail_pos])
|
||||
self.add_via_center(self.m1_stack,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(self.m1_stack,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("m3",[pin_pos,rail_pos])
|
||||
self.add_via_center(self.m2_stack,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 != "m1": 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("m1",[vdd_pos,down_pos])
|
||||
self.add_via_center(self.m1_stack,down_pos,rotate=90)
|
||||
self.add_path("m2",[down_pos,rail_pos])
|
||||
self.add_via_center(self.m1_stack,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 != "m2": 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("m2",[gnd_pos,rail_pos])
|
||||
self.add_via_center(self.m1_stack,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 == "m3":
|
||||
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("m3",[msb_pin_pos,rail_pos])
|
||||
self.add_via_center(self.m2_stack,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("m2",[msb_out_pos,out_extend_right_pos,out_extend_up_pos])
|
||||
self.add_wire(("m3","via2","m2"),[out_extend_right_pos,out_extend_up_pos,rail_pos])
|
||||
self.add_via_center(self.m2_stack,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("m2",[msb_out_pos,out_extend_right_pos,out_extend_down_pos])
|
||||
self.add_wire(("m3","via2","m2"),[out_extend_right_pos,out_extend_down_pos,rail_pos])
|
||||
self.add_via_center(self.m2_stack,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("m1",[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("m2",[pin_pos,rail_pos])
|
||||
self.add_via_center(self.m2_stack,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("m3",[pin0_pos,pin1_pos])
|
||||
self.add_via_center(self.m2_stack,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="m2",
|
||||
offset=self.vert_control_bus_positions[n])
|
||||
for n in self.bank_sel_bus_names:
|
||||
self.add_label(text=n,
|
||||
layer="m2",
|
||||
offset=self.vert_control_bus_positions[n])
|
||||
|
|
@ -1,782 +0,0 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2021 Regents of the University of California and The Board
|
||||
# of Regents for the Oklahoma Agricultural and Mechanical College
|
||||
# (acting for and on behalf of Oklahoma State University)
|
||||
# All rights reserved.
|
||||
#
|
||||
import datetime
|
||||
import debug
|
||||
from globals import OPTS, print_time
|
||||
from math import log, ceil
|
||||
import importlib
|
||||
from base.vector import vector
|
||||
from base.design import design
|
||||
from base.verilog import verilog
|
||||
from base.lef import lef
|
||||
from sram_factory import factory
|
||||
from tech import spice
|
||||
|
||||
|
||||
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, ["m1", "m2", "m3", "m4"])
|
||||
verilog.__init__(self)
|
||||
|
||||
self.sram_config = sram_config
|
||||
sram_config.set_local_config(self)
|
||||
|
||||
self.bank_insts = []
|
||||
|
||||
if self.write_size:
|
||||
self.num_wmasks = int(ceil(self.word_size / self.write_size))
|
||||
else:
|
||||
self.num_wmasks = 0
|
||||
|
||||
if not self.num_spare_cols:
|
||||
self.num_spare_cols = 0
|
||||
|
||||
|
||||
def add_pins(self):
|
||||
""" Add pins for entire SRAM. """
|
||||
|
||||
for port in self.write_ports:
|
||||
for bit in range(self.word_size + self.num_spare_cols):
|
||||
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")
|
||||
# add the optional write mask pins
|
||||
for port in self.write_ports:
|
||||
for bit in range(self.num_wmasks):
|
||||
self.add_pin("wmask{0}[{1}]".format(port, bit), "INPUT")
|
||||
if self.num_spare_cols == 1:
|
||||
self.add_pin("spare_wen{0}".format(port), "INPUT")
|
||||
else:
|
||||
for bit in range(self.num_spare_cols):
|
||||
self.add_pin("spare_wen{0}[{1}]".format(port, bit), "INPUT")
|
||||
for port in self.read_ports:
|
||||
for bit in range(self.word_size + self.num_spare_cols):
|
||||
self.add_pin("dout{0}[{1}]".format(port, bit), "OUTPUT")
|
||||
|
||||
# Standard supply and ground names
|
||||
try:
|
||||
self.vdd_name = spice["power"]
|
||||
except KeyError:
|
||||
self.vdd_name = "vdd"
|
||||
try:
|
||||
self.gnd_name = spice["ground"]
|
||||
except KeyError:
|
||||
self.gnd_name = "gnd"
|
||||
|
||||
self.add_pin(self.vdd_name, "POWER")
|
||||
self.add_pin(self.gnd_name, "GROUND")
|
||||
self.ext_supplies = [self.vdd_name, self.gnd_name]
|
||||
self.ext_supply = {"vdd" : self.vdd_name, "gnd" : self.gnd_name}
|
||||
|
||||
def add_global_pex_labels(self):
|
||||
"""
|
||||
Add pex labels at the sram level for spice analysis
|
||||
"""
|
||||
|
||||
|
||||
|
||||
# add pex labels for bitcells
|
||||
for bank_num in range(len(self.bank_insts)):
|
||||
bank = self.bank_insts[bank_num]
|
||||
pex_data = bank.reverse_transformation_bitcell(self.bitcell.name)
|
||||
|
||||
bank_offset = pex_data[0] # offset bank relative to sram
|
||||
Q_offset = pex_data[1] # offset of storage relative to bank
|
||||
Q_bar_offset = pex_data[2] # offset of storage relative to bank
|
||||
bl_offsets = pex_data[3]
|
||||
br_offsets = pex_data[4]
|
||||
bl_meta = pex_data[5]
|
||||
br_meta = pex_data[6]
|
||||
|
||||
bl = []
|
||||
br = []
|
||||
|
||||
storage_layer_name = "m1"
|
||||
bitline_layer_name = self.bitcell.get_pin("bl").layer
|
||||
|
||||
for cell in range(len(bank_offset)):
|
||||
Q = [bank_offset[cell][0] + Q_offset[cell][0],
|
||||
bank_offset[cell][1] + Q_offset[cell][1]]
|
||||
Q_bar = [bank_offset[cell][0] + Q_bar_offset[cell][0],
|
||||
bank_offset[cell][1] + Q_bar_offset[cell][1]]
|
||||
OPTS.words_per_row = self.words_per_row
|
||||
row = int(cell % (OPTS.num_words / self.words_per_row))
|
||||
col = int(cell / (OPTS.num_words))
|
||||
self.add_layout_pin_rect_center("bitcell_Q_b{}_r{}_c{}".format(bank_num,
|
||||
row,
|
||||
col),
|
||||
storage_layer_name,
|
||||
Q)
|
||||
self.add_layout_pin_rect_center("bitcell_Q_bar_b{}_r{}_c{}".format(bank_num,
|
||||
row,
|
||||
col),
|
||||
storage_layer_name,
|
||||
Q_bar)
|
||||
|
||||
for cell in range(len(bl_offsets)):
|
||||
col = bl_meta[cell][0][2]
|
||||
for bitline in range(len(bl_offsets[cell])):
|
||||
bitline_location = [float(bank_offset[cell][0]) + bl_offsets[cell][bitline][0],
|
||||
float(bank_offset[cell][1]) + bl_offsets[cell][bitline][1]]
|
||||
bl.append([bitline_location, bl_meta[cell][bitline][3], col])
|
||||
|
||||
for cell in range(len(br_offsets)):
|
||||
col = br_meta[cell][0][2]
|
||||
for bitline in range(len(br_offsets[cell])):
|
||||
bitline_location = [float(bank_offset[cell][0]) + br_offsets[cell][bitline][0],
|
||||
float(bank_offset[cell][1]) + br_offsets[cell][bitline][1]]
|
||||
br.append([bitline_location, br_meta[cell][bitline][3], col])
|
||||
|
||||
for i in range(len(bl)):
|
||||
self.add_layout_pin_rect_center("bl{0}_{1}".format(bl[i][1], bl[i][2]),
|
||||
bitline_layer_name, bl[i][0])
|
||||
|
||||
for i in range(len(br)):
|
||||
self.add_layout_pin_rect_center("br{0}_{1}".format(br[i][1], br[i][2]),
|
||||
bitline_layer_name, br[i][0])
|
||||
|
||||
# add pex labels for control logic
|
||||
for i in range(len(self.control_logic_insts)):
|
||||
instance = self.control_logic_insts[i]
|
||||
control_logic_offset = instance.offset
|
||||
for output in instance.mod.output_list:
|
||||
pin = instance.mod.get_pin(output)
|
||||
pin.transform([0, 0], instance.mirror, instance.rotate)
|
||||
offset = [control_logic_offset[0] + pin.center()[0],
|
||||
control_logic_offset[1] + pin.center()[1]]
|
||||
self.add_layout_pin_rect_center("{0}{1}".format(pin.name, i),
|
||||
storage_layer_name,
|
||||
offset)
|
||||
|
||||
def create_netlist(self):
|
||||
""" Netlist creation """
|
||||
|
||||
start_time = datetime.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.datetime.now(), start_time)
|
||||
|
||||
def create_layout(self):
|
||||
""" Layout creation """
|
||||
start_time = datetime.datetime.now()
|
||||
self.place_instances()
|
||||
if not OPTS.is_unit_test:
|
||||
print_time("Placement", datetime.datetime.now(), start_time)
|
||||
|
||||
start_time = datetime.datetime.now()
|
||||
self.route_layout()
|
||||
|
||||
if not OPTS.is_unit_test:
|
||||
print_time("Routing", datetime.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]
|
||||
if OPTS.use_pex and OPTS.pex_exe[0] != "calibre":
|
||||
debug.info(2, "adding global pex labels")
|
||||
self.add_global_pex_labels()
|
||||
self.add_boundary(ll=vector(0, 0),
|
||||
ur=vector(self.width, self.height))
|
||||
|
||||
start_time = datetime.datetime.now()
|
||||
if not OPTS.is_unit_test:
|
||||
# We only enable final verification if we have routed the design
|
||||
# Only run this if not a unit test, because unit test will also verify it.
|
||||
self.DRC_LVS(final_verification=OPTS.route_supplies, force_check=OPTS.check_lvsdrc)
|
||||
print_time("Verification", datetime.datetime.now(), start_time)
|
||||
|
||||
def create_modules(self):
|
||||
debug.error("Must override pure virtual function.", -1)
|
||||
|
||||
def route_supplies(self, bbox=None):
|
||||
""" 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 pin_name in ["vdd", "gnd"]:
|
||||
for inst in self.insts:
|
||||
self.copy_power_pins(inst, pin_name, self.ext_supply[pin_name])
|
||||
|
||||
# Pick the router type
|
||||
if not OPTS.route_supplies:
|
||||
# Do not route the power supply (leave as must-connect pins)
|
||||
return
|
||||
elif OPTS.route_supplies == "grid":
|
||||
from router import supply_grid_router as router
|
||||
else:
|
||||
from router import supply_tree_router as router
|
||||
|
||||
rtr=router(layers=self.supply_stack,
|
||||
design=self,
|
||||
bbox=bbox,
|
||||
pin_type=OPTS.supply_pin_type)
|
||||
|
||||
rtr.route()
|
||||
|
||||
# This removes the original pre-supply routing pins and replaces them
|
||||
# with the ring or peripheral power pins
|
||||
if OPTS.supply_pin_type in ["left", "right", "top", "bottom", "ring"]:
|
||||
# Find the lowest leftest pin for vdd and gnd
|
||||
for pin_name in ["vdd", "gnd"]:
|
||||
# Copy the pin shape(s) to rectangles
|
||||
for pin in self.get_pins(pin_name):
|
||||
self.add_rect(pin.layer,
|
||||
pin.ll(),
|
||||
pin.width(),
|
||||
pin.height())
|
||||
|
||||
# Remove the pin shape(s)
|
||||
self.remove_layout_pin(pin_name)
|
||||
|
||||
# Get new pins
|
||||
pins = rtr.get_new_pins(pin_name)
|
||||
for pin in pins:
|
||||
self.add_layout_pin(self.ext_supply[pin_name],
|
||||
pin.layer,
|
||||
pin.ll(),
|
||||
pin.width(),
|
||||
pin.height())
|
||||
|
||||
elif OPTS.route_supplies and OPTS.supply_pin_type == "single":
|
||||
# Update these as we may have routed outside the region (perimeter pins)
|
||||
lowest_coord = self.find_lowest_coords()
|
||||
|
||||
# Find the lowest leftest pin for vdd and gnd
|
||||
for pin_name in ["vdd", "gnd"]:
|
||||
# Copy the pin shape(s) to rectangles
|
||||
for pin in self.get_pins(pin_name):
|
||||
self.add_rect(pin.layer,
|
||||
pin.ll(),
|
||||
pin.width(),
|
||||
pin.height())
|
||||
|
||||
# Remove the pin shape(s)
|
||||
self.remove_layout_pin(pin_name)
|
||||
|
||||
# Get the lowest, leftest pin
|
||||
pin = rtr.get_ll_pin(pin_name)
|
||||
|
||||
pin_width = 2 * getattr(self, "{}_width".format(pin.layer))
|
||||
|
||||
# Add it as an IO pin to the perimeter
|
||||
route_width = pin.rx() - lowest_coord.x
|
||||
pin_offset = vector(lowest_coord.x, pin.by())
|
||||
self.add_rect(pin.layer,
|
||||
pin_offset,
|
||||
route_width,
|
||||
pin.height())
|
||||
|
||||
self.add_layout_pin(self.ext_supply[pin_name],
|
||||
pin.layer,
|
||||
pin_offset,
|
||||
pin_width,
|
||||
pin.height())
|
||||
else:
|
||||
# Grid is left with many top level pins
|
||||
pass
|
||||
|
||||
def route_escape_pins(self, bbox):
|
||||
"""
|
||||
Add the top-level pins for a single bank SRAM with control.
|
||||
"""
|
||||
|
||||
# List of pin to new pin name
|
||||
pins_to_route = []
|
||||
for port in self.all_ports:
|
||||
# Connect the control pins as inputs
|
||||
for signal in self.control_logic_inputs[port]:
|
||||
if signal.startswith("rbl"):
|
||||
continue
|
||||
if signal=="clk":
|
||||
pins_to_route.append("{0}{1}".format(signal, port))
|
||||
else:
|
||||
pins_to_route.append("{0}{1}".format(signal, port))
|
||||
|
||||
if port in self.write_ports:
|
||||
for bit in range(self.word_size + self.num_spare_cols):
|
||||
pins_to_route.append("din{0}[{1}]".format(port, bit))
|
||||
|
||||
if port in self.readwrite_ports or port in self.read_ports:
|
||||
for bit in range(self.word_size + self.num_spare_cols):
|
||||
pins_to_route.append("dout{0}[{1}]".format(port, bit))
|
||||
|
||||
for bit in range(self.col_addr_size):
|
||||
pins_to_route.append("addr{0}[{1}]".format(port, bit))
|
||||
|
||||
for bit in range(self.row_addr_size):
|
||||
pins_to_route.append("addr{0}[{1}]".format(port, bit + self.col_addr_size))
|
||||
|
||||
if port in self.write_ports:
|
||||
if self.write_size:
|
||||
for bit in range(self.num_wmasks):
|
||||
pins_to_route.append("wmask{0}[{1}]".format(port, bit))
|
||||
|
||||
if port in self.write_ports:
|
||||
if self.num_spare_cols == 1:
|
||||
pins_to_route.append("spare_wen{0}".format(port))
|
||||
else:
|
||||
for bit in range(self.num_spare_cols):
|
||||
pins_to_route.append("spare_wen{0}[{1}]".format(port, bit))
|
||||
|
||||
from router import signal_escape_router as router
|
||||
rtr=router(layers=self.m3_stack,
|
||||
design=self,
|
||||
bbox=bbox)
|
||||
rtr.escape_route(pins_to_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, pen])
|
||||
else:
|
||||
self.control_bus_names[port].extend([sen, wen, pen])
|
||||
self.vert_control_bus_positions = self.create_vertical_bus(layer="m2",
|
||||
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="m2",
|
||||
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="m2",
|
||||
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="m3",
|
||||
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="m1",
|
||||
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="m1",
|
||||
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="m1",
|
||||
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)
|
||||
|
||||
if self.num_banks>2:
|
||||
self.msb_decoder = self.bank.decoder.pre2_4
|
||||
|
||||
def add_modules(self):
|
||||
self.bitcell = factory.create(module_type=OPTS.bitcell)
|
||||
self.dff = factory.create(module_type="dff")
|
||||
|
||||
# Create the bank module (up to four are instantiated)
|
||||
self.bank = factory.create("bank", sram_config=self.sram_config, module_name="bank")
|
||||
|
||||
self.num_spare_cols = self.bank.num_spare_cols
|
||||
|
||||
# Create the address and control flops (but not the clk)
|
||||
self.row_addr_dff = factory.create("dff_array", module_name="row_addr_dff", rows=self.row_addr_size, columns=1)
|
||||
|
||||
if self.col_addr_size > 0:
|
||||
self.col_addr_dff = factory.create("dff_array", module_name="col_addr_dff", rows=1, columns=self.col_addr_size)
|
||||
else:
|
||||
self.col_addr_dff = None
|
||||
|
||||
self.data_dff = factory.create("dff_array", module_name="data_dff", rows=1, columns=self.word_size + self.num_spare_cols)
|
||||
|
||||
if self.write_size:
|
||||
self.wmask_dff = factory.create("dff_array", module_name="wmask_dff", rows=1, columns=self.num_wmasks)
|
||||
|
||||
if self.num_spare_cols:
|
||||
self.spare_wen_dff = factory.create("dff_array", module_name="spare_wen_dff", rows=1, columns=self.num_spare_cols)
|
||||
|
||||
# Create bank decoder
|
||||
if(self.num_banks > 1):
|
||||
self.add_multi_bank_modules()
|
||||
|
||||
self.bank_count = 0
|
||||
|
||||
c = importlib.import_module("modules." + 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,
|
||||
spare_columns=self.num_spare_cols,
|
||||
sram=self,
|
||||
port_type="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,
|
||||
spare_columns=self.num_spare_cols,
|
||||
sram=self,
|
||||
port_type="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,
|
||||
spare_columns=self.num_spare_cols,
|
||||
sram=self,
|
||||
port_type="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 + self.num_spare_cols):
|
||||
temp.append("dout{0}[{1}]".format(port, bit))
|
||||
for port in self.all_ports:
|
||||
temp.append("rbl_bl{0}".format(port))
|
||||
for port in self.write_ports:
|
||||
for bit in range(self.word_size + self.num_spare_cols):
|
||||
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.all_ports:
|
||||
temp.append("p_en_bar{0}".format(port))
|
||||
for port in self.write_ports:
|
||||
temp.append("w_en{0}".format(port))
|
||||
for bit in range(self.num_wmasks):
|
||||
temp.append("bank_wmask{0}_{1}".format(port, bit))
|
||||
for bit in range(self.num_spare_cols):
|
||||
temp.append("bank_spare_wen{0}_{1}".format(port, bit))
|
||||
for port in self.all_ports:
|
||||
temp.append("wl_en{0}".format(port))
|
||||
temp.extend(self.ext_supplies)
|
||||
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)] + self.ext_supplies)
|
||||
|
||||
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)] + self.ext_supplies)
|
||||
|
||||
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 + self.num_spare_cols):
|
||||
inputs.append("din{}[{}]".format(port, bit))
|
||||
outputs.append("bank_din{}_{}".format(port, bit))
|
||||
|
||||
self.connect_inst(inputs + outputs + ["clk_buf{}".format(port)] + self.ext_supplies)
|
||||
|
||||
return insts
|
||||
|
||||
def create_wmask_dff(self):
|
||||
""" Add and place all wmask flops """
|
||||
insts = []
|
||||
for port in self.all_ports:
|
||||
if port in self.write_ports:
|
||||
insts.append(self.add_inst(name="wmask_dff{}".format(port),
|
||||
mod=self.wmask_dff))
|
||||
else:
|
||||
insts.append(None)
|
||||
continue
|
||||
|
||||
# inputs, outputs/output/bar
|
||||
inputs = []
|
||||
outputs = []
|
||||
for bit in range(self.num_wmasks):
|
||||
inputs.append("wmask{}[{}]".format(port, bit))
|
||||
outputs.append("bank_wmask{}_{}".format(port, bit))
|
||||
|
||||
self.connect_inst(inputs + outputs + ["clk_buf{}".format(port)] + self.ext_supplies)
|
||||
|
||||
return insts
|
||||
|
||||
def create_spare_wen_dff(self):
|
||||
""" Add all spare write enable flops """
|
||||
insts = []
|
||||
for port in self.all_ports:
|
||||
if port in self.write_ports:
|
||||
insts.append(self.add_inst(name="spare_wen_dff{}".format(port),
|
||||
mod=self.spare_wen_dff))
|
||||
else:
|
||||
insts.append(None)
|
||||
continue
|
||||
|
||||
# inputs, outputs/output/bar
|
||||
inputs = []
|
||||
outputs = []
|
||||
for bit in range(self.num_spare_cols):
|
||||
if self.num_spare_cols == 1:
|
||||
inputs.append("spare_wen{0}".format(port))
|
||||
else:
|
||||
inputs.append("spare_wen{0}[{1}]".format(port, bit))
|
||||
outputs.append("bank_spare_wen{}_{}".format(port, bit))
|
||||
|
||||
self.connect_inst(inputs + outputs + ["clk_buf{}".format(port)] + self.ext_supplies)
|
||||
|
||||
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))
|
||||
temp.append("rbl_bl{}".format(port))
|
||||
|
||||
# Outputs
|
||||
if port in self.read_ports:
|
||||
temp.append("s_en{}".format(port))
|
||||
if port in self.write_ports:
|
||||
temp.append("w_en{}".format(port))
|
||||
temp.append("p_en_bar{}".format(port))
|
||||
temp.extend(["wl_en{}".format(port), "clk_buf{}".format(port)] + self.ext_supplies)
|
||||
self.connect_inst(temp)
|
||||
|
||||
return insts
|
||||
|
||||
def sp_write(self, sp_name, lvs=False, trim=False):
|
||||
# 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("* Trimmed: {}\n".format(trim))
|
||||
sp.write("* LVS: {}\n".format(lvs))
|
||||
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, lvs=lvs, trim=trim)
|
||||
del usedMODS
|
||||
sp.close()
|
||||
|
||||
def graph_exclude_bits(self, targ_row, targ_col):
|
||||
"""
|
||||
Excludes bits in column from being added to graph except target
|
||||
"""
|
||||
self.bank.graph_exclude_bits(targ_row, targ_col)
|
||||
|
||||
def clear_exclude_bits(self):
|
||||
"""
|
||||
Clears the bit exclusions
|
||||
"""
|
||||
self.bank.clear_exclude_bits()
|
||||
|
||||
def graph_exclude_column_mux(self, column_include_num, port):
|
||||
"""
|
||||
Excludes all columns muxes unrelated to the target bit being simulated.
|
||||
"""
|
||||
self.bank.graph_exclude_column_mux(column_include_num, port)
|
||||
|
||||
def graph_clear_column_mux(self, port):
|
||||
"""
|
||||
Clear mux exclusions to allow different bit tests.
|
||||
"""
|
||||
self.bank.graph_clear_column_mux(port)
|
||||
|
|
@ -18,10 +18,11 @@ class sram_config:
|
|||
self.word_size = word_size
|
||||
self.num_words = num_words
|
||||
# Don't add a write mask if it is the same size as the data word
|
||||
if write_size and write_size==word_size:
|
||||
self.write_size = None
|
||||
else:
|
||||
self.write_size_init = write_size
|
||||
if write_size:
|
||||
self.write_size = write_size
|
||||
else:
|
||||
self.write_size = word_size
|
||||
self.num_banks = num_banks
|
||||
self.num_spare_rows = num_spare_rows
|
||||
self.num_spare_cols = num_spare_cols
|
||||
|
|
@ -72,8 +73,8 @@ class sram_config:
|
|||
|
||||
bitcell = factory.create(module_type=OPTS.bitcell)
|
||||
|
||||
debug.check(self.num_banks in [1, 2, 4],
|
||||
"Valid number of banks are 1 , 2 and 4.")
|
||||
debug.check(ceil(log(self.num_banks, 2)) == log(self.num_banks, 2) ,
|
||||
"Number of banks should be power of 2.")
|
||||
|
||||
self.num_words_per_bank = self.num_words / self.num_banks
|
||||
self.num_bits_per_bank = self.word_size * self.num_words_per_bank
|
||||
|
|
@ -117,11 +118,18 @@ class sram_config:
|
|||
self.num_rows = self.num_rows_temp + self.num_spare_rows
|
||||
debug.info(1, "Rows: {} Cols: {}".format(self.num_rows_temp, self.num_cols))
|
||||
|
||||
# Fix the write_size
|
||||
if self.write_size_init:
|
||||
self.write_size = self.write_size_init
|
||||
else:
|
||||
self.write_size = self.word_size
|
||||
|
||||
# Compute the address and bank sizes
|
||||
self.row_addr_size = ceil(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))
|
||||
#self.addr_size = self.bank_addr_size
|
||||
debug.info(1, "Row addr size: {}".format(self.row_addr_size)
|
||||
+ " Col addr size: {}".format(self.col_addr_size)
|
||||
+ " Bank addr size: {}".format(self.bank_addr_size))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
from .template import template
|
||||
from globals import OPTS
|
||||
import os
|
||||
from math import ceil, log
|
||||
import re
|
||||
|
||||
|
||||
class sram_multibank:
|
||||
|
||||
def __init__(self, sram):
|
||||
rw_ports = [i for i in sram.all_ports if i in sram.read_ports and i in sram.write_ports]
|
||||
r_ports = [i for i in sram.all_ports if i in sram.read_ports and i not in sram.write_ports]
|
||||
w_ports = [i for i in sram.all_ports if i not in sram.read_ports and i in sram.write_ports]
|
||||
self.dict = {
|
||||
'module_name': sram.name + '_top',
|
||||
'bank_module_name': sram.name,
|
||||
'vdd': 'vdd',
|
||||
'gnd': 'gnd',
|
||||
'ports': sram.all_ports,
|
||||
'rw_ports': rw_ports,
|
||||
'r_ports': r_ports,
|
||||
'w_ports': w_ports,
|
||||
'banks': list(range(sram.num_banks)),
|
||||
'data_width': sram.word_size,
|
||||
'addr_width': sram.bank_addr_size + ceil(log(sram.num_banks, 2)),
|
||||
'bank_sel': ceil(log(sram.num_banks, 2)),
|
||||
'num_wmask': sram.num_wmasks,
|
||||
'write_size': sram.write_size
|
||||
}
|
||||
|
||||
def verilog_write(self, name):
|
||||
template_filename = os.path.join(os.path.abspath(os.environ["OPENRAM_HOME"]), "modules/sram_multibank_template.v")
|
||||
t = template(template_filename, self.dict)
|
||||
t.write(name)
|
||||
with open(name, 'r') as f:
|
||||
text = f.read()
|
||||
badComma = re.compile(r',(\s*\n\s*\);)')
|
||||
text = badComma.sub(r'\1', text)
|
||||
with open(name, 'w') as f:
|
||||
f.write(text)
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
|
||||
module {{ module_name }} (
|
||||
`ifdef USE_POWER_PINS
|
||||
{{ vdd }},
|
||||
{{ gnd }},
|
||||
`endif
|
||||
{% for port in rw_ports %}
|
||||
clk{{ port }},
|
||||
addr{{ port }},
|
||||
din{{ port }},
|
||||
csb{{ port }},
|
||||
{% if num_wmask > 1 %}
|
||||
wmask{{ port }},
|
||||
{% endif %}
|
||||
web{{ port }},
|
||||
dout{{ port }},
|
||||
{% endfor %}
|
||||
{% for port in r_ports %}
|
||||
clk{{ port }},
|
||||
addr{{ port }},
|
||||
csb{{ port }},
|
||||
dout{{ port }},
|
||||
{% endfor %}
|
||||
{% for port in w_ports %}
|
||||
clk{{ port }},
|
||||
addr{{ port }},
|
||||
din{{ port }},
|
||||
csb{{ port }},
|
||||
{% if num_wmask > 1 %}
|
||||
wmask{{ port }},
|
||||
{% endif %}
|
||||
web{{ port }},
|
||||
{% endfor %}
|
||||
);
|
||||
|
||||
parameter DATA_WIDTH = {{ data_width }};
|
||||
parameter ADDR_WIDTH= {{ addr_width }};
|
||||
|
||||
parameter BANK_SEL = {{ bank_sel }};
|
||||
parameter NUM_WMASK = {{ num_wmask }};
|
||||
|
||||
`ifdef USE_POWER_PINS
|
||||
inout {{ vdd }};
|
||||
inout {{ gnd }};
|
||||
`endif
|
||||
{% for port in rw_ports %}
|
||||
input clk{{ port }};
|
||||
input [ADDR_WIDTH - 1 : 0] addr{{ port }};
|
||||
input [DATA_WIDTH - 1: 0] din{{ port }};
|
||||
input csb{{ port }};
|
||||
input web{{ port }};
|
||||
{% if num_wmask > 1 %}
|
||||
input [NUM_WMASK - 1 : 0] wmask{{ port }};
|
||||
{% endif %}
|
||||
output reg [DATA_WIDTH - 1 : 0] dout{{ port }};
|
||||
{% endfor %}
|
||||
{% for port in r_ports %}
|
||||
input clk{{ port }};
|
||||
input [ADDR_WIDTH - 1 : 0] addr{{ port }};
|
||||
input csb{{ port }};
|
||||
output reg [DATA_WIDTH - 1 : 0] dout{{ port }};
|
||||
{% endfor %}
|
||||
{% for port in w_ports %}
|
||||
input clk{{ port }};
|
||||
input [ADDR_WIDTH - 1 : 0] addr{{ port }};
|
||||
input [DATA_WIDTH - 1: 0] din{{ port }};
|
||||
input csb{{ port }};
|
||||
input web{{ port }};
|
||||
{% if num_wmask > 1 %}
|
||||
input [NUM_WMASK - 1 : 0] wmask{{ port }};
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% for port in ports %}
|
||||
reg [BANK_SEL - 1 : 0] addr{{ port }}_reg;
|
||||
|
||||
{% for bank in banks %}
|
||||
wire [DATA_WIDTH - 1 : 0] dout{{ port }}_bank{{ bank }};
|
||||
|
||||
reg web{{ port }}_bank{{ bank }};
|
||||
|
||||
reg csb{{ port }}_bank{{ bank }};
|
||||
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
|
||||
{% for bank in banks %}
|
||||
{{ bank_module_name }} bank{{ bank }} (
|
||||
`ifdef USE_POWER_PINS
|
||||
.{{ vdd }}({{ vdd }}),
|
||||
.{{ gnd }}({{ gnd }}),
|
||||
`endif
|
||||
{% for port in rw_ports %}
|
||||
.clk{{ port }}(clk{{ port }}),
|
||||
.addr{{ port }}(addr{{ port }}[ADDR_WIDTH - BANK_SEL - 1 : 0]),
|
||||
.din{{ port }}(din{{ port }}),
|
||||
.csb{{ port }}(csb{{ port }}_bank{{ bank }}),
|
||||
.web{{ port }}(web{{ port }}_bank{{ bank }}),
|
||||
{% if num_wmask > 1 %}
|
||||
.wmask{{ port }}(wmask{{ port }}),
|
||||
{% endif %}
|
||||
.dout{{ port }}(dout{{ port }}_bank{{ bank }}),
|
||||
{% endfor %}
|
||||
{% for port in r_ports %}
|
||||
.clk{{ port }}(clk{{ port }}),
|
||||
.addr{{ port }}(addr{{ port }}[ADDR_WIDTH - BANK_SEL - 1 : 0]),
|
||||
.csb{{ port }}(csb{{ port }}_bank{{ bank }}),
|
||||
.dout{{ port }}(dout{{ port }}_bank{{ bank }}),
|
||||
{% endfor %}
|
||||
{% for port in w_ports %}
|
||||
.clk{{ port }}(clk{{ port }}),
|
||||
.addr{{ port }}(addr{{ port }}[ADDR_WIDTH - BANK_SEL - 1 : 0]),
|
||||
.din{{ port }}(din{{ port }}),
|
||||
.csb{{ port }}(csb{{ port }}_bank{{ bank }}),
|
||||
{% if num_wmask > 1 %}
|
||||
.wmask{{ port }}(wmask{{ port }}),
|
||||
{% endif %}
|
||||
.web{{ port }}(web{{ port }}_bank{{ bank }}),
|
||||
{% endfor %}
|
||||
);
|
||||
{% endfor %}
|
||||
|
||||
{% for port in ports %}
|
||||
always @(posedge clk{{ port }}) begin
|
||||
addr{{ port }}_reg <= addr{{ port }}[ADDR_WIDTH - 1 : ADDR_WIDTH - BANK_SEL];
|
||||
end
|
||||
{% endfor %}
|
||||
|
||||
{% for port in ports %}
|
||||
always @(*) begin
|
||||
case (addr{{ port }}_reg)
|
||||
{% for bank in banks %}
|
||||
{{ bank }}: begin
|
||||
dout{{ port }} = dout{{ port }}_bank{{ bank }};
|
||||
end
|
||||
{% endfor %}
|
||||
endcase
|
||||
end
|
||||
{% endfor %}
|
||||
|
||||
{% for port in rw_ports %}
|
||||
always @(*) begin
|
||||
{% for bank in banks %}
|
||||
csb{{ port }}_bank{{ bank }} = 1'b1;
|
||||
web{{ port }}_bank{{ bank }} = 1'b1;
|
||||
{% endfor %}
|
||||
case (addr{{ port }}[ADDR_WIDTH - 1 : ADDR_WIDTH - BANK_SEL])
|
||||
{% for bank in banks %}
|
||||
{{ bank }}: begin
|
||||
web{{ port }}_bank{{ bank }} = web{{ port }};
|
||||
csb{{ port }}_bank{{ bank }} = csb{{ port }};
|
||||
end
|
||||
{% endfor %}
|
||||
endcase
|
||||
end
|
||||
{% endfor %}
|
||||
|
||||
{% for port in w_ports %}
|
||||
always @(*) begin
|
||||
{% for bank in banks %}
|
||||
csb{{ port }}_bank{{ bank }} = 1'b1;
|
||||
web{{ port }}_bank{{ bank }} = 1'b1;
|
||||
{% endfor %}
|
||||
case (addr{{ port }}[ADDR_WIDTH - 1 : ADDR_WIDTH - BANK_SEL])
|
||||
{% for bank in banks %}
|
||||
{{ bank }}: begin
|
||||
web{{ port }}_bank{{ bank }} = web{{ port }};
|
||||
csb{{ port }}_bank{{ bank }} = csb{{ port }};
|
||||
end
|
||||
{% endfor %}
|
||||
endcase
|
||||
end
|
||||
{% endfor %}
|
||||
|
||||
{% for port in r_ports %}
|
||||
always @(*) begin
|
||||
{% for bank in banks %}
|
||||
csb{{ port }}_bank{{ bank }} = 1'b1;
|
||||
{% endfor %}
|
||||
case (addr{{ port }}[ADDR_WIDTH - 1 : ADDR_WIDTH - BANK_SEL])
|
||||
{% for bank in banks %}
|
||||
{{ bank }}: begin
|
||||
csb{{ port }}_bank{{ bank }} = csb{{ port }};
|
||||
end
|
||||
{% endfor %}
|
||||
endcase
|
||||
end
|
||||
{% endfor %}
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2021 Regents of the University of California
|
||||
# Santa Cruz
|
||||
# All rights reserved.
|
||||
#
|
||||
import re
|
||||
|
||||
|
||||
class baseSection:
|
||||
"""
|
||||
This is the base section class for other section classes to inherit.
|
||||
It is also used as the top most section.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.children = []
|
||||
|
||||
def expand(self, dict, fd):
|
||||
for c in self.children:
|
||||
c.expand(dict, fd)
|
||||
|
||||
|
||||
class loopSection(baseSection):
|
||||
"""
|
||||
This section is for looping elements. It will repeat the children
|
||||
sections based on the key list.
|
||||
"""
|
||||
|
||||
def __init__(self, var, key):
|
||||
baseSection.__init__(self)
|
||||
self.var = var
|
||||
self.key = key
|
||||
|
||||
def expand(self, dict, fd):
|
||||
for ind in dict[self.key]:
|
||||
dict[self.var] = ind
|
||||
for c in self.children:
|
||||
c.expand(dict, fd)
|
||||
if self.var in dict:
|
||||
del dict[self.var]
|
||||
|
||||
|
||||
class conditionalSection(baseSection):
|
||||
"""
|
||||
This class will conditionally print it's children based on the 'cond'
|
||||
element.
|
||||
"""
|
||||
def __init__(self, cond):
|
||||
baseSection.__init__(self)
|
||||
self.cond = cond
|
||||
|
||||
def expand(self, dict, fd):
|
||||
run = eval(self.cond, dict)
|
||||
if run:
|
||||
for c in self.children:
|
||||
c.expand(dict, fd)
|
||||
|
||||
|
||||
class textSection(baseSection):
|
||||
"""
|
||||
This is plain text section. It can contain parameters that can be
|
||||
replaced based on the dictionary.
|
||||
"""
|
||||
|
||||
def __init__(self, text):
|
||||
self.text = text
|
||||
|
||||
def expand(self, dict, fd):
|
||||
varRE = re.compile(r'\{\{ (\S*) \}\}')
|
||||
vars = varRE.finditer(self.text)
|
||||
newText = self.text
|
||||
for var in vars:
|
||||
newText = newText.replace('{{ ' + var.group(1) + ' }}', str(dict[var.group(1)]))
|
||||
fd.write(newText)
|
||||
|
||||
|
||||
class template:
|
||||
"""
|
||||
The template class will read a template and generate an output file
|
||||
based on the template and the given dictionary.
|
||||
"""
|
||||
|
||||
def __init__(self, template, dict):
|
||||
self.template = template
|
||||
self.dict = dict
|
||||
|
||||
def readTemplate(self):
|
||||
lines = []
|
||||
with open(self.template, 'r') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
self.baseSectionSection = baseSection()
|
||||
context = [self.baseSectionSection]
|
||||
forRE = re.compile(r'\s*\{% for (\S*) in (\S*) %\}')
|
||||
endforRE = re.compile(r'\s*\{% endfor %\}')
|
||||
ifRE = re.compile(r'\s*{% if (.*) %\}')
|
||||
endifRE = re.compile(r'\s*\{% endif %\}')
|
||||
for line in lines:
|
||||
m = forRE.match(line)
|
||||
if m:
|
||||
section = loopSection(m.group(1), m.group(2))
|
||||
context[-1].children.append(section)
|
||||
context.append(section)
|
||||
continue
|
||||
m = ifRE.match(line)
|
||||
if m:
|
||||
section = conditionalSection(m.group(1))
|
||||
context[-1].children.append(section)
|
||||
context.append(section)
|
||||
continue
|
||||
if endforRE.match(line) or endifRE.match(line):
|
||||
context.pop()
|
||||
else:
|
||||
context[-1].children.append(textSection(line))
|
||||
|
||||
def write(self, filename):
|
||||
fd = open(filename, 'w')
|
||||
self.readTemplate()
|
||||
self.baseSectionSection.expand(self.dict, fd)
|
||||
fd.close()
|
||||
|
|
@ -29,7 +29,10 @@ class write_driver_array(design):
|
|||
|
||||
self.columns = columns
|
||||
self.word_size = word_size
|
||||
self.write_size = write_size
|
||||
if write_size is None:
|
||||
self.write_size = word_size
|
||||
else:
|
||||
self.write_size = write_size
|
||||
self.offsets = offsets
|
||||
self.column_offset = column_offset
|
||||
self.words_per_row = int(columns / word_size)
|
||||
|
|
@ -38,8 +41,10 @@ class write_driver_array(design):
|
|||
else:
|
||||
self.num_spare_cols = num_spare_cols
|
||||
|
||||
if self.write_size:
|
||||
if self.write_size != self.word_size:
|
||||
self.num_wmasks = int(math.ceil(self.word_size / self.write_size))
|
||||
else:
|
||||
self.num_wmasks = 0
|
||||
|
||||
self.create_netlist()
|
||||
if not OPTS.netlist_only:
|
||||
|
|
@ -82,10 +87,10 @@ class write_driver_array(design):
|
|||
for i in range(self.word_size + self.num_spare_cols):
|
||||
self.add_pin(self.get_bl_name() + "_{0}".format(i), "OUTPUT")
|
||||
self.add_pin(self.get_br_name() + "_{0}".format(i), "OUTPUT")
|
||||
if self.write_size:
|
||||
if self.write_size != self.word_size:
|
||||
for i in range(self.num_wmasks + self.num_spare_cols):
|
||||
self.add_pin(self.en_name + "_{0}".format(i), "INPUT")
|
||||
elif self.num_spare_cols and not self.write_size:
|
||||
elif self.num_spare_cols and self.write_size == self.word_size:
|
||||
for i in range(self.num_spare_cols + 1):
|
||||
self.add_pin(self.en_name + "_{0}".format(i), "INPUT")
|
||||
else:
|
||||
|
|
@ -110,7 +115,7 @@ class write_driver_array(design):
|
|||
self.local_insts.append(self.add_inst(name=name,
|
||||
mod=self.driver))
|
||||
|
||||
if self.write_size:
|
||||
if self.write_size != self.word_size:
|
||||
self.connect_inst([self.data_name + "_{0}".format(index),
|
||||
self.get_bl_name() + "_{0}".format(index),
|
||||
self.get_br_name() + "_{0}".format(index),
|
||||
|
|
@ -121,7 +126,7 @@ class write_driver_array(design):
|
|||
w = 0
|
||||
windex+=1
|
||||
|
||||
elif self.num_spare_cols and not self.write_size:
|
||||
elif self.num_spare_cols and self.write_size == self.word_size:
|
||||
self.connect_inst([self.data_name + "_{0}".format(index),
|
||||
self.get_bl_name() + "_{0}".format(index),
|
||||
self.get_br_name() + "_{0}".format(index),
|
||||
|
|
@ -135,7 +140,7 @@ class write_driver_array(design):
|
|||
|
||||
for i in range(self.num_spare_cols):
|
||||
index = self.word_size + i
|
||||
if self.write_size:
|
||||
if self.write_size != self.word_size:
|
||||
offset = self.num_wmasks
|
||||
else:
|
||||
offset = 1
|
||||
|
|
@ -205,7 +210,7 @@ class write_driver_array(design):
|
|||
width=br_pin.width(),
|
||||
height=br_pin.height())
|
||||
|
||||
if self.write_size:
|
||||
if self.write_size != self.word_size:
|
||||
for bit in range(self.num_wmasks):
|
||||
inst = self.local_insts[bit * self.write_size]
|
||||
en_pin = inst.get_pin(inst.mod.en_name)
|
||||
|
|
@ -229,7 +234,7 @@ class write_driver_array(design):
|
|||
layer="m1",
|
||||
offset=en_pin.lr() + vector(-drc("minwidth_m1"),0))
|
||||
|
||||
elif self.num_spare_cols and not self.write_size:
|
||||
elif self.num_spare_cols and self.write_size == self.word_size:
|
||||
# shorten enable rail to accomodate those for spare write drivers
|
||||
left_inst = self.local_insts[0]
|
||||
left_en_pin = left_inst.get_pin(inst.mod.en_name)
|
||||
|
|
|
|||
|
|
@ -805,7 +805,7 @@ class router(router_tech):
|
|||
try:
|
||||
x = track[0]*self.track_width - 0.5*self.track_width
|
||||
except TypeError:
|
||||
print(track[0], type(track[0]), self.track_width, type(self.track_width))
|
||||
debug.warning("{} {} {} {}".format(track[0], type(track[0]), self.track_width, type(self.track_width)))
|
||||
y = track[1]*self.track_width - 0.5*self.track_width
|
||||
# offset lowest corner object to to (-track halo,-track halo)
|
||||
ll = snap_to_grid(vector(x, y))
|
||||
|
|
|
|||
|
|
@ -145,13 +145,14 @@ class supply_tree_router(router):
|
|||
connections.append((x, y))
|
||||
|
||||
# Route MST components
|
||||
level=99
|
||||
for index, (src, dest) in enumerate(connections):
|
||||
if not (index % 25):
|
||||
debug.info(1, "{0} supply segments routed, {1} remaining.".format(index, len(connections) - index))
|
||||
self.route_signal(pin_name, src, dest)
|
||||
if False and pin_name == "gnd":
|
||||
print("\nSRC {}: ".format(src) + str(self.pin_groups[pin_name][src].grids) + str(self.pin_groups[pin_name][src].blockages))
|
||||
print("DST {}: ".format(dest) + str(self.pin_groups[pin_name][dest].grids) + str(self.pin_groups[pin_name][dest].blockages))
|
||||
debug.info(level, "\nSRC {}: ".format(src) + str(self.pin_groups[pin_name][src].grids) + str(self.pin_groups[pin_name][src].blockages))
|
||||
debug.info(level, ("DST {}: ".format(dest) + str(self.pin_groups[pin_name][dest].grids) + str(self.pin_groups[pin_name][dest].blockages)))
|
||||
self.write_debug_gds("post_{0}_{1}.gds".format(src, dest), False)
|
||||
|
||||
#self.write_debug_gds("final_tree_router_{}.gds".format(pin_name), False)
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
from .sram_1bank import *
|
||||
from .sram_2bank import *
|
||||
from .sram_base import *
|
||||
from .sram_config import *
|
||||
from .sram import *
|
||||
|
|
@ -125,6 +125,11 @@ def check_file_format_whitespace(file_name):
|
|||
def check_print_output(file_name):
|
||||
"""Check if any files (except debug.py) call the _print_ function. We should
|
||||
use the debug output with verbosity instead!"""
|
||||
|
||||
skip_files = ["printGDS.py", "uniquifyGDS.py", "processGDS.py", "model_data_util.py"]
|
||||
base_file_name = os.path.basename(file_name)
|
||||
if base_file_name in skip_files:
|
||||
return(0)
|
||||
file = open(file_name, "r+b")
|
||||
line = file.read().decode('utf-8')
|
||||
# skip comments with a hash
|
||||
|
|
|
|||
|
|
@ -92,8 +92,8 @@ class model_delay_test(openram_test):
|
|||
else:
|
||||
self.assertTrue(False) # other techs fail
|
||||
|
||||
print('spice_delays', spice_delays)
|
||||
print('model_delays', model_delays)
|
||||
debug.info(3, 'spice_delays {}'.fomrat(spice_delays))
|
||||
debug.info(3, 'model_delays {}'.format(model_delays))
|
||||
|
||||
# Check if no too many or too few results
|
||||
self.assertTrue(len(spice_delays.keys())==len(model_delays.keys()))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
#!/usr/bin/env python3
|
||||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2021 Regents of the University of California and The Board
|
||||
# of Regents for the Oklahoma Agricultural and Mechanical College
|
||||
# (acting for and on behalf of Oklahoma State University)
|
||||
# All rights reserved.
|
||||
#
|
||||
import unittest
|
||||
from testutils import *
|
||||
import sys, os
|
||||
sys.path.append(os.getenv("OPENRAM_HOME"))
|
||||
import globals
|
||||
from globals import OPTS
|
||||
import debug
|
||||
|
||||
|
||||
class multibank_verilog_test(openram_test):
|
||||
|
||||
def runTest(self):
|
||||
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
|
||||
globals.init_openram(config_file)
|
||||
OPTS.route_supplies=False
|
||||
OPTS.check_lvsdrc=False
|
||||
OPTS.netlist_only=True
|
||||
from modules import sram
|
||||
from modules import sram_config
|
||||
c = sram_config(word_size=2,
|
||||
num_words=16,
|
||||
num_banks=2)
|
||||
c.words_per_row=1
|
||||
c.recompute_sizes()
|
||||
debug.info(1, "Testing Verilog for sample 2 bit, 16 words SRAM with 2 bank")
|
||||
# This doesn't have to use the factory since worst case
|
||||
# it will just replaece the top-level module of the same name
|
||||
s = sram(c, "sram_2_16_2_{0}".format(OPTS.tech_name))
|
||||
|
||||
vfile = s.name + "_top.v"
|
||||
vname = OPTS.openram_temp + vfile
|
||||
|
||||
v1bfile = s.name + ".v"
|
||||
v1bname = OPTS.openram_temp + v1bfile
|
||||
|
||||
s.verilog_write(v1bname)
|
||||
|
||||
# let's diff the result with a golden model
|
||||
multi_golden = "{0}/golden/{1}".format(os.path.dirname(os.path.realpath(__file__)), vfile)
|
||||
self.assertTrue(self.isdiff(vname, multi_golden))
|
||||
|
||||
one_golden = "{0}/golden/{1}".format(os.path.dirname(os.path.realpath(__file__)), v1bfile)
|
||||
self.assertTrue(self.isdiff(v1bname, one_golden))
|
||||
|
||||
globals.end_openram()
|
||||
|
||||
|
||||
# run the test from the command line
|
||||
if __name__ == "__main__":
|
||||
(OPTS, args) = globals.parse_args()
|
||||
del sys.argv[1:]
|
||||
header(__file__, OPTS.tech_name)
|
||||
unittest.main(testRunner=debugTestRunner())
|
||||
|
|
@ -72,6 +72,7 @@ BROKEN_STAMPS = \
|
|||
sky130/23_lib_sram_prune_test.ok \
|
||||
sky131/23_lib_sram_test.ok \
|
||||
%/26_hspice_pex_pinv_test.ok \
|
||||
%/27_verilog_multibank_test.ok \
|
||||
%/50_riscv_1k_1rw1r_func_test.ok \
|
||||
%/50_riscv_1k_1rw_func_test.ok \
|
||||
%/50_riscv_1rw1r_func_test.ok \
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
// OpenRAM SRAM model
|
||||
// Words: 16
|
||||
// Word size: 2
|
||||
|
||||
module sram_2_16_2_freepdk45(
|
||||
`ifdef USE_POWER_PINS
|
||||
vdd,
|
||||
gnd,
|
||||
`endif
|
||||
// Port 0: RW
|
||||
clk0,csb0,web0,addr0,din0,dout0
|
||||
);
|
||||
|
||||
parameter DATA_WIDTH = 2 ;
|
||||
parameter ADDR_WIDTH = 3 ;
|
||||
parameter RAM_DEPTH = 1 << ADDR_WIDTH;
|
||||
// FIXME: This delay is arbitrary.
|
||||
parameter DELAY = 3 ;
|
||||
parameter VERBOSE = 1 ; //Set to 0 to only display warnings
|
||||
parameter T_HOLD = 1 ; //Delay to hold dout value after posedge. Value is arbitrary
|
||||
|
||||
`ifdef USE_POWER_PINS
|
||||
inout vdd;
|
||||
inout gnd;
|
||||
`endif
|
||||
input clk0; // clock
|
||||
input csb0; // active low chip select
|
||||
input web0; // active low write control
|
||||
input [ADDR_WIDTH-1:0] addr0;
|
||||
input [DATA_WIDTH-1:0] din0;
|
||||
output [DATA_WIDTH-1:0] dout0;
|
||||
|
||||
reg [DATA_WIDTH-1:0] mem [0:RAM_DEPTH-1];
|
||||
|
||||
reg csb0_reg;
|
||||
reg web0_reg;
|
||||
reg [ADDR_WIDTH-1:0] addr0_reg;
|
||||
reg [DATA_WIDTH-1:0] din0_reg;
|
||||
reg [DATA_WIDTH-1:0] dout0;
|
||||
|
||||
// All inputs are registers
|
||||
always @(posedge clk0)
|
||||
begin
|
||||
csb0_reg = csb0;
|
||||
web0_reg = web0;
|
||||
addr0_reg = addr0;
|
||||
din0_reg = din0;
|
||||
#(T_HOLD) dout0 = 2'bx;
|
||||
if ( !csb0_reg && web0_reg && VERBOSE )
|
||||
$display($time," Reading %m addr0=%b dout0=%b",addr0_reg,mem[addr0_reg]);
|
||||
if ( !csb0_reg && !web0_reg && VERBOSE )
|
||||
$display($time," Writing %m addr0=%b din0=%b",addr0_reg,din0_reg);
|
||||
end
|
||||
|
||||
|
||||
// Memory Write Block Port 0
|
||||
// Write Operation : When web0 = 0, csb0 = 0
|
||||
always @ (negedge clk0)
|
||||
begin : MEM_WRITE0
|
||||
if ( !csb0_reg && !web0_reg ) begin
|
||||
mem[addr0_reg][1:0] = din0_reg[1:0];
|
||||
end
|
||||
end
|
||||
|
||||
// Memory Read Block Port 0
|
||||
// Read Operation : When web0 = 1, csb0 = 0
|
||||
always @ (negedge clk0)
|
||||
begin : MEM_READ0
|
||||
if (!csb0_reg && web0_reg)
|
||||
dout0 <= #(DELAY) mem[addr0_reg];
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
|
||||
module sram_2_16_2_freepdk45_top (
|
||||
`ifdef USE_POWER_PINS
|
||||
vdd,
|
||||
gnd,
|
||||
`endif
|
||||
clk0,
|
||||
addr0,
|
||||
din0,
|
||||
csb0,
|
||||
web0,
|
||||
dout0
|
||||
);
|
||||
|
||||
parameter DATA_WIDTH = 2;
|
||||
parameter ADDR_WIDTH= 4;
|
||||
|
||||
parameter BANK_SEL = 1;
|
||||
parameter NUM_WMASK = 0;
|
||||
|
||||
`ifdef USE_POWER_PINS
|
||||
inout vdd;
|
||||
inout gnd;
|
||||
`endif
|
||||
input clk0;
|
||||
input [ADDR_WIDTH - 1 : 0] addr0;
|
||||
input [DATA_WIDTH - 1: 0] din0;
|
||||
input csb0;
|
||||
input web0;
|
||||
output reg [DATA_WIDTH - 1 : 0] dout0;
|
||||
|
||||
reg [BANK_SEL - 1 : 0] addr0_reg;
|
||||
|
||||
wire [DATA_WIDTH - 1 : 0] dout0_bank0;
|
||||
|
||||
reg web0_bank0;
|
||||
|
||||
reg csb0_bank0;
|
||||
|
||||
wire [DATA_WIDTH - 1 : 0] dout0_bank1;
|
||||
|
||||
reg web0_bank1;
|
||||
|
||||
reg csb0_bank1;
|
||||
|
||||
|
||||
sram_2_16_2_freepdk45 bank0 (
|
||||
`ifdef USE_POWER_PINS
|
||||
.vdd(vdd),
|
||||
.gnd(gnd),
|
||||
`endif
|
||||
.clk0(clk0),
|
||||
.addr0(addr0[ADDR_WIDTH - BANK_SEL - 1 : 0]),
|
||||
.din0(din0),
|
||||
.csb0(csb0_bank0),
|
||||
.web0(web0_bank0),
|
||||
.dout0(dout0_bank0)
|
||||
);
|
||||
sram_2_16_2_freepdk45 bank1 (
|
||||
`ifdef USE_POWER_PINS
|
||||
.vdd(vdd),
|
||||
.gnd(gnd),
|
||||
`endif
|
||||
.clk0(clk0),
|
||||
.addr0(addr0[ADDR_WIDTH - BANK_SEL - 1 : 0]),
|
||||
.din0(din0),
|
||||
.csb0(csb0_bank1),
|
||||
.web0(web0_bank1),
|
||||
.dout0(dout0_bank1)
|
||||
);
|
||||
|
||||
always @(posedge clk0) begin
|
||||
addr0_reg <= addr0[ADDR_WIDTH - 1 : ADDR_WIDTH - BANK_SEL];
|
||||
end
|
||||
|
||||
always @(*) begin
|
||||
case (addr0_reg)
|
||||
0: begin
|
||||
dout0 = dout0_bank0;
|
||||
end
|
||||
1: begin
|
||||
dout0 = dout0_bank1;
|
||||
end
|
||||
endcase
|
||||
end
|
||||
|
||||
always @(*) begin
|
||||
csb0_bank0 = 1'b1;
|
||||
web0_bank0 = 1'b1;
|
||||
csb0_bank1 = 1'b1;
|
||||
web0_bank1 = 1'b1;
|
||||
case (addr0[ADDR_WIDTH - 1 : ADDR_WIDTH - BANK_SEL])
|
||||
0: begin
|
||||
web0_bank0 = web0;
|
||||
csb0_bank0 = csb0;
|
||||
end
|
||||
1: begin
|
||||
web0_bank1 = web0;
|
||||
csb0_bank1 = csb0;
|
||||
end
|
||||
endcase
|
||||
end
|
||||
|
||||
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
// OpenRAM SRAM model
|
||||
// Words: 16
|
||||
// Word size: 2
|
||||
|
||||
module sram_2_16_2_scn4m_subm(
|
||||
`ifdef USE_POWER_PINS
|
||||
vdd,
|
||||
gnd,
|
||||
`endif
|
||||
// Port 0: RW
|
||||
clk0,csb0,web0,addr0,din0,dout0
|
||||
);
|
||||
|
||||
parameter DATA_WIDTH = 2 ;
|
||||
parameter ADDR_WIDTH = 3 ;
|
||||
parameter RAM_DEPTH = 1 << ADDR_WIDTH;
|
||||
// FIXME: This delay is arbitrary.
|
||||
parameter DELAY = 3 ;
|
||||
parameter VERBOSE = 1 ; //Set to 0 to only display warnings
|
||||
parameter T_HOLD = 1 ; //Delay to hold dout value after posedge. Value is arbitrary
|
||||
|
||||
`ifdef USE_POWER_PINS
|
||||
inout vdd;
|
||||
inout gnd;
|
||||
`endif
|
||||
input clk0; // clock
|
||||
input csb0; // active low chip select
|
||||
input web0; // active low write control
|
||||
input [ADDR_WIDTH-1:0] addr0;
|
||||
input [DATA_WIDTH-1:0] din0;
|
||||
output [DATA_WIDTH-1:0] dout0;
|
||||
|
||||
reg [DATA_WIDTH-1:0] mem [0:RAM_DEPTH-1];
|
||||
|
||||
reg csb0_reg;
|
||||
reg web0_reg;
|
||||
reg [ADDR_WIDTH-1:0] addr0_reg;
|
||||
reg [DATA_WIDTH-1:0] din0_reg;
|
||||
reg [DATA_WIDTH-1:0] dout0;
|
||||
|
||||
// All inputs are registers
|
||||
always @(posedge clk0)
|
||||
begin
|
||||
csb0_reg = csb0;
|
||||
web0_reg = web0;
|
||||
addr0_reg = addr0;
|
||||
din0_reg = din0;
|
||||
#(T_HOLD) dout0 = 2'bx;
|
||||
if ( !csb0_reg && web0_reg && VERBOSE )
|
||||
$display($time," Reading %m addr0=%b dout0=%b",addr0_reg,mem[addr0_reg]);
|
||||
if ( !csb0_reg && !web0_reg && VERBOSE )
|
||||
$display($time," Writing %m addr0=%b din0=%b",addr0_reg,din0_reg);
|
||||
end
|
||||
|
||||
|
||||
// Memory Write Block Port 0
|
||||
// Write Operation : When web0 = 0, csb0 = 0
|
||||
always @ (negedge clk0)
|
||||
begin : MEM_WRITE0
|
||||
if ( !csb0_reg && !web0_reg ) begin
|
||||
mem[addr0_reg][1:0] = din0_reg[1:0];
|
||||
end
|
||||
end
|
||||
|
||||
// Memory Read Block Port 0
|
||||
// Read Operation : When web0 = 1, csb0 = 0
|
||||
always @ (negedge clk0)
|
||||
begin : MEM_READ0
|
||||
if (!csb0_reg && web0_reg)
|
||||
dout0 <= #(DELAY) mem[addr0_reg];
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
|
||||
module sram_2_16_2_scn4m_subm_top (
|
||||
`ifdef USE_POWER_PINS
|
||||
vdd,
|
||||
gnd,
|
||||
`endif
|
||||
clk0,
|
||||
addr0,
|
||||
din0,
|
||||
csb0,
|
||||
web0,
|
||||
dout0
|
||||
);
|
||||
|
||||
parameter DATA_WIDTH = 2;
|
||||
parameter ADDR_WIDTH= 4;
|
||||
|
||||
parameter BANK_SEL = 1;
|
||||
parameter NUM_WMASK = 0;
|
||||
|
||||
`ifdef USE_POWER_PINS
|
||||
inout vdd;
|
||||
inout gnd;
|
||||
`endif
|
||||
input clk0;
|
||||
input [ADDR_WIDTH - 1 : 0] addr0;
|
||||
input [DATA_WIDTH - 1: 0] din0;
|
||||
input csb0;
|
||||
input web0;
|
||||
output reg [DATA_WIDTH - 1 : 0] dout0;
|
||||
|
||||
reg [BANK_SEL - 1 : 0] addr0_reg;
|
||||
|
||||
wire [DATA_WIDTH - 1 : 0] dout0_bank0;
|
||||
|
||||
reg web0_bank0;
|
||||
|
||||
reg csb0_bank0;
|
||||
|
||||
wire [DATA_WIDTH - 1 : 0] dout0_bank1;
|
||||
|
||||
reg web0_bank1;
|
||||
|
||||
reg csb0_bank1;
|
||||
|
||||
|
||||
sram_2_16_2_scn4m_subm bank0 (
|
||||
`ifdef USE_POWER_PINS
|
||||
.vdd(vdd),
|
||||
.gnd(gnd),
|
||||
`endif
|
||||
.clk0(clk0),
|
||||
.addr0(addr0[ADDR_WIDTH - BANK_SEL - 1 : 0]),
|
||||
.din0(din0),
|
||||
.csb0(csb0_bank0),
|
||||
.web0(web0_bank0),
|
||||
.dout0(dout0_bank0)
|
||||
);
|
||||
sram_2_16_2_scn4m_subm bank1 (
|
||||
`ifdef USE_POWER_PINS
|
||||
.vdd(vdd),
|
||||
.gnd(gnd),
|
||||
`endif
|
||||
.clk0(clk0),
|
||||
.addr0(addr0[ADDR_WIDTH - BANK_SEL - 1 : 0]),
|
||||
.din0(din0),
|
||||
.csb0(csb0_bank1),
|
||||
.web0(web0_bank1),
|
||||
.dout0(dout0_bank1)
|
||||
);
|
||||
|
||||
always @(posedge clk0) begin
|
||||
addr0_reg <= addr0[ADDR_WIDTH - 1 : ADDR_WIDTH - BANK_SEL];
|
||||
end
|
||||
|
||||
always @(*) begin
|
||||
case (addr0_reg)
|
||||
0: begin
|
||||
dout0 = dout0_bank0;
|
||||
end
|
||||
1: begin
|
||||
dout0 = dout0_bank1;
|
||||
end
|
||||
endcase
|
||||
end
|
||||
|
||||
always @(*) begin
|
||||
csb0_bank0 = 1'b1;
|
||||
web0_bank0 = 1'b1;
|
||||
csb0_bank1 = 1'b1;
|
||||
web0_bank1 = 1'b1;
|
||||
case (addr0[ADDR_WIDTH - 1 : ADDR_WIDTH - BANK_SEL])
|
||||
0: begin
|
||||
web0_bank0 = web0;
|
||||
csb0_bank0 = csb0;
|
||||
end
|
||||
1: begin
|
||||
web0_bank1 = web0;
|
||||
csb0_bank1 = csb0;
|
||||
end
|
||||
endcase
|
||||
end
|
||||
|
||||
|
||||
endmodule
|
||||
|
|
@ -26,7 +26,6 @@ import shutil
|
|||
import debug
|
||||
from globals import OPTS
|
||||
from .run_script import *
|
||||
|
||||
# Keep track of statistics
|
||||
num_drc_runs = 0
|
||||
num_lvs_runs = 0
|
||||
|
|
@ -96,13 +95,24 @@ def write_drc_script(cell_name, gds_name, extract, final_verification, output_pa
|
|||
f.write("gds warning default\n")
|
||||
# Flatten the transistors
|
||||
# Bug in Netgen 1.5.194 when using this...
|
||||
f.write("gds flatglob *_?mos_m*\n")
|
||||
try:
|
||||
from tech import blackbox_cells
|
||||
except ImportError:
|
||||
blackbox_cells = []
|
||||
|
||||
try:
|
||||
from tech import flatglob
|
||||
except ImportError:
|
||||
flatglob = []
|
||||
f.write("gds readonly true\n")
|
||||
|
||||
for entry in flatglob:
|
||||
f.write("gds flatglob " +entry + "\n")
|
||||
# These two options are temporarily disabled until Tim fixes a bug in magic related
|
||||
# to flattening channel routes and vias (hierarchy with no devices in it). Otherwise,
|
||||
# they appear to be disconnected.
|
||||
f.write("gds flatten true\n")
|
||||
f.write("gds ordering true\n")
|
||||
f.write("gds readonly true\n")
|
||||
f.write("gds read {}\n".format(gds_name))
|
||||
f.write('puts "Finished reading gds {}"\n'.format(gds_name))
|
||||
f.write("load {}\n".format(cell_name))
|
||||
|
|
@ -164,10 +174,6 @@ def write_drc_script(cell_name, gds_name, extract, final_verification, output_pa
|
|||
f.write("#!/bin/sh\n")
|
||||
f.write('export OPENRAM_TECH="{}"\n'.format(os.environ['OPENRAM_TECH']))
|
||||
# Copy the bitcell mag files if they exist
|
||||
try:
|
||||
from tech import blackbox_cells
|
||||
except ImportError:
|
||||
blackbox_cells = []
|
||||
for blackbox_cell_name in blackbox_cells:
|
||||
mag_file = OPTS.openram_tech + "maglef_lib/" + blackbox_cell_name + ".mag"
|
||||
debug.check(os.path.isfile(mag_file), "Could not find blackbox cell {}".format(mag_file))
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ RUN apt-get --no-install-recommends -y upgrade
|
|||
# General tools for building etc.
|
||||
RUN apt-get install --no-install-recommends -y build-essential git ssh vim gosu autoconf automake libtool bison flex
|
||||
# Use bash instead of dash
|
||||
# Must be on one line or else ln won't work without a shell!
|
||||
RUN rm /bin/sh && ln -s /bin/bash /bin/sh
|
||||
# Needed by OpenRAM
|
||||
RUN apt-get install --no-install-recommends -y python3 python3-numpy python3-scipy python3-pip python3-matplotlib python3-venv python3-sklearn python3-subunit python3-coverage
|
||||
|
|
@ -30,8 +31,8 @@ WORKDIR /root
|
|||
RUN git clone https://github.com/KLayout/klayout
|
||||
WORKDIR /root/klayout
|
||||
RUN git checkout ${KLAYOUT_COMMIT}
|
||||
RUN ./build.sh -qt5 -debug -j 8 \
|
||||
&& cp -r bin-debug /usr/local/klayout
|
||||
RUN ./build.sh -qt5 -debug -j$(nproc)
|
||||
RUN cp -r bin-debug /usr/local/klayout
|
||||
RUN rm -rf /root/klayout
|
||||
|
||||
### Trilinos ###
|
||||
|
|
@ -99,15 +100,15 @@ RUN ../configure CXXFLAGS="-O3 -std=c++11" \
|
|||
RUN make -j 4 install
|
||||
|
||||
### Ngspice ###
|
||||
ARG NGSPICE_COMIT=032b1c32c4dbad45ff132bcfac1dbecadbd8abb0
|
||||
ARG NGSPICE_COMMIT=032b1c32c4dbad45ff132bcfac1dbecadbd8abb0
|
||||
WORKDIR /root
|
||||
RUN git clone git://git.code.sf.net/p/ngspice/ngspice
|
||||
WORKDIR /root/ngspice
|
||||
RUN git checkout ${NGSPICE_COMMIT}
|
||||
RUN ./autogen.sh \
|
||||
&& ./configure --enable-openmp --with-readline \
|
||||
&& make \
|
||||
&& make install
|
||||
RUN ./autogen.sh
|
||||
RUN ./configure --enable-openmp --with-readline
|
||||
RUN make
|
||||
RUN make install
|
||||
RUN rm -rf /root/ngspice
|
||||
|
||||
### Netgen ###
|
||||
|
|
@ -118,9 +119,9 @@ WORKDIR /root
|
|||
RUN git clone git://opencircuitdesign.com/netgen netgen
|
||||
WORKDIR /root/netgen
|
||||
RUN git checkout ${NETGEN_COMMIT}
|
||||
RUN ./configure \
|
||||
&& make -j$(nproc) \
|
||||
&& make install
|
||||
RUN ./configure
|
||||
RUN make -j$(nproc)
|
||||
RUN make install
|
||||
RUN rm -rf /root/netgen
|
||||
|
||||
### iVerilog ###
|
||||
|
|
@ -137,9 +138,9 @@ WORKDIR /root/magic
|
|||
RUN git checkout ${MAGIC_COMMIT}
|
||||
COPY mrg.patch /root/magic
|
||||
RUN git apply mrg.patch
|
||||
RUN ./configure \
|
||||
&& make \
|
||||
&& make install
|
||||
RUN ./configure
|
||||
RUN make
|
||||
RUN make install
|
||||
RUN rm -rf /root/magic
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -90,8 +90,8 @@ class sky130_replica_column(sky130_bitcell_base_array):
|
|||
self.add_pin("vdd", "POWER")
|
||||
self.add_pin("gnd", "GROUND")
|
||||
|
||||
self.add_pin("top_gate", "INPUT")
|
||||
self.add_pin("bot_gate", "INPUT")
|
||||
#self.add_pin("top_gate", "INPUT")
|
||||
#self.add_pin("bot_gate", "INPUT")
|
||||
|
||||
def add_modules(self):
|
||||
self.replica_cell = factory.create(module_type="replica_bitcell_1port", version="opt1")
|
||||
|
|
@ -220,6 +220,16 @@ class sky130_replica_column(sky130_bitcell_base_array):
|
|||
width=self.width,
|
||||
height=wl_pin.height())
|
||||
|
||||
# for colend in [self.cell_inst[0], self.cell_inst[self.row_size]]:
|
||||
# inst = self.cell_inst[row]
|
||||
# for pin_name in ["top_gate", "bot_gate"]:
|
||||
# pin = inst.get_pin("gate")
|
||||
# self.add_layout_pin(text=pin_name,
|
||||
# layer=pin.layer,
|
||||
# offset=pin.ll(),
|
||||
# width=pin.width(),
|
||||
# height=pin.height())
|
||||
|
||||
for row in range(self.row_size + 2):
|
||||
inst = self.cell_inst[row]
|
||||
# add only 1 label per col
|
||||
|
|
@ -235,7 +245,6 @@ class sky130_replica_column(sky130_bitcell_base_array):
|
|||
self.objs.append(geometry.label("vdd", layer["nwell"], pin.center()))
|
||||
|
||||
if 'VNB' or 'vnb' in self.cell_inst[row].mod.pins:
|
||||
print("welling")
|
||||
try:
|
||||
from tech import layer_override
|
||||
if layer_override['VNB']:
|
||||
|
|
|
|||
|
|
@ -781,6 +781,16 @@ library_prefix_name = "sky130_fd_bd_sram__"
|
|||
# List of cells to skip running DRC/LVS on directly
|
||||
# This will look for a maglef file and copy it over the mag file
|
||||
# before DRC after extraction
|
||||
|
||||
flatglob = ["*_?mos_m*",
|
||||
"sky130_fd_bd_sram__sram_sp_cell_fom_serifs",
|
||||
"sky130_fd_bd_sram__openram_sp_cell_opt1a_cell",
|
||||
"sky130_fd_bd_sram__openram_sp_cell_opt1a_replica_ce",
|
||||
"sky130_fd_bd_sram__openram_sp_cell_opt1_replica_cell",
|
||||
"sky130_fd_bd_sram__openram_sp_cell_opt1_replica_ce",
|
||||
"sky130_fd_bd_sram__openram_sp_cell_opt1a_cell",
|
||||
"sky130_fd_bd_sram__sram_sp_cell_fom_serifs"]
|
||||
|
||||
blackbox_cells = ["sky130_fd_bd_sram__openram_dp_cell",
|
||||
"sky130_fd_bd_sram__openram_dp_cell_dummy",
|
||||
"sky130_fd_bd_sram__openram_dp_cell_replica",
|
||||
|
|
|
|||
Loading…
Reference in New Issue