mirror of https://github.com/VLSIDA/OpenRAM.git
Merge branch 'char' into STA-refactor
This commit is contained in:
commit
f41537b508
|
|
@ -23,3 +23,4 @@ sky130A/
|
|||
sky130B/
|
||||
skywater-pdk/
|
||||
sky130_fd_bd_sram/
|
||||
docker/openram-ubuntu.log
|
||||
|
|
|
|||
|
|
@ -126,5 +126,3 @@ class design(hierarchy_design):
|
|||
for inst in self.insts:
|
||||
total_module_power += inst.mod.analytical_power(corner, load)
|
||||
return total_module_power
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -358,11 +358,16 @@ class spice():
|
|||
" ".join(inst.get_connections())))
|
||||
sp.write("\n")
|
||||
else:
|
||||
wrapped_connections = "\n+ ".join(tr.wrap(" ".join(inst.get_connections())))
|
||||
|
||||
sp.write("X{0}\n+ {1}\n+ {2}\n".format(inst.name,
|
||||
wrapped_connections,
|
||||
inst.mod.cell_name))
|
||||
if trim and inst.name in self.trim_insts:
|
||||
wrapped_connections = "\n*+ ".join(tr.wrap(" ".join(inst.get_connections())))
|
||||
sp.write("X{0}\n*+ {1}\n*+ {2}\n".format(inst.name,
|
||||
wrapped_connections,
|
||||
inst.mod.cell_name))
|
||||
else:
|
||||
wrapped_connections = "\n+ ".join(tr.wrap(" ".join(inst.get_connections())))
|
||||
sp.write("X{0}\n+ {1}\n+ {2}\n".format(inst.name,
|
||||
wrapped_connections,
|
||||
inst.mod.cell_name))
|
||||
|
||||
sp.write(".ENDS {0}\n".format(self.cell_name))
|
||||
|
||||
|
|
|
|||
|
|
@ -19,11 +19,12 @@ from .simulation import *
|
|||
from .measurements import *
|
||||
from .model_check import *
|
||||
from .analytical_util import *
|
||||
from .fake_sram import *
|
||||
|
||||
debug.info(1, "Initializing characterizer...")
|
||||
OPTS.spice_exe = ""
|
||||
|
||||
if not OPTS.analytical_delay:
|
||||
if not OPTS.analytical_delay or OPTS.top_process in ["memfunc", "memchar"]:
|
||||
if OPTS.spice_name:
|
||||
# Capitalize Xyce
|
||||
if OPTS.spice_name == "xyce":
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#
|
||||
import os
|
||||
import re
|
||||
from enum import Enum
|
||||
from openram import debug
|
||||
from openram import OPTS
|
||||
|
||||
|
|
@ -107,3 +108,33 @@ def check_dict_values_is_float(dict):
|
|||
if type(value)!=float:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def bidir_search(func, upper, lower, time_out=9):
|
||||
"""
|
||||
Performs bidirectional search over given function with given
|
||||
upper and lower bounds.
|
||||
"""
|
||||
time_count = 0
|
||||
while time_count < time_out:
|
||||
val = (upper + lower) / 2
|
||||
if func(val):
|
||||
return (True, val)
|
||||
time_count += 1
|
||||
return (False, 0)
|
||||
|
||||
|
||||
class bit_polarity(Enum):
|
||||
NONINVERTING = 0
|
||||
INVERTING = 1
|
||||
|
||||
|
||||
class sram_op(Enum):
|
||||
READ_ZERO = 0
|
||||
READ_ONE = 1
|
||||
WRITE_ZERO = 2
|
||||
WRITE_ONE = 3
|
||||
DISABLED_READ_ZERO = 4
|
||||
DISABLED_READ_ONE = 5
|
||||
DISABLED_WRITE_ZERO = 6
|
||||
DISABLED_WRITE_ONE = 7
|
||||
|
|
|
|||
|
|
@ -13,10 +13,10 @@ from openram import OPTS
|
|||
from .stimuli import *
|
||||
from .trim_spice import *
|
||||
from .charutils import *
|
||||
from .sram_op import *
|
||||
from .bit_polarity import *
|
||||
from .simulation import simulation
|
||||
from .measurements import *
|
||||
from os import path
|
||||
import re
|
||||
|
||||
|
||||
class delay(simulation):
|
||||
|
|
@ -37,7 +37,7 @@ class delay(simulation):
|
|||
|
||||
"""
|
||||
|
||||
def __init__(self, sram, spfile, corner):
|
||||
def __init__(self, sram, spfile, corner, output_path=None):
|
||||
super().__init__(sram, spfile, corner)
|
||||
|
||||
self.targ_read_ports = []
|
||||
|
|
@ -47,10 +47,17 @@ class delay(simulation):
|
|||
self.num_wmasks = int(math.ceil(self.word_size / self.write_size))
|
||||
else:
|
||||
self.num_wmasks = 0
|
||||
|
||||
if output_path is None:
|
||||
self.output_path = OPTS.openram_temp
|
||||
else:
|
||||
self.output_path = output_path
|
||||
|
||||
self.set_load_slew(0, 0)
|
||||
self.set_corner(corner)
|
||||
self.create_signal_names()
|
||||
self.add_graph_exclusions()
|
||||
self.meas_id = 0
|
||||
|
||||
def create_measurement_objects(self):
|
||||
""" Create the measurements used for read and write ports """
|
||||
|
|
@ -79,10 +86,12 @@ class delay(simulation):
|
|||
self.clk_frmt = "clk{0}" # Unformatted clock name
|
||||
targ_name = "{0}{{}}_{1}".format(self.dout_name, self.probe_data) # Empty values are the port and probe data bit
|
||||
self.delay_meas = []
|
||||
self.delay_meas.append(delay_measure("delay_lh", self.clk_frmt, targ_name, "RISE", "RISE", measure_scale=1e9))
|
||||
self.delay_meas.append(delay_measure("delay_lh", self.clk_frmt, targ_name, "FALL", "RISE", measure_scale=1e9))
|
||||
self.delay_meas[-1].meta_str = sram_op.READ_ONE # Used to index time delay values when measurements written to spice file.
|
||||
self.delay_meas[-1].meta_add_delay = False
|
||||
self.delay_meas.append(delay_measure("delay_hl", self.clk_frmt, targ_name, "FALL", "FALL", measure_scale=1e9))
|
||||
self.delay_meas[-1].meta_str = sram_op.READ_ZERO
|
||||
self.delay_meas[-1].meta_add_delay = False
|
||||
self.read_lib_meas+=self.delay_meas
|
||||
|
||||
self.slew_meas = []
|
||||
|
|
@ -103,9 +112,10 @@ class delay(simulation):
|
|||
self.read_lib_meas[-1].meta_str = "disabled_read0"
|
||||
|
||||
# This will later add a half-period to the spice time delay. Only for reading 0.
|
||||
for obj in self.read_lib_meas:
|
||||
if obj.meta_str is sram_op.READ_ZERO:
|
||||
obj.meta_add_delay = True
|
||||
# FIXME: Removed this to check, see if it affects anything
|
||||
#for obj in self.read_lib_meas:
|
||||
# if obj.meta_str is sram_op.READ_ZERO:
|
||||
# obj.meta_add_delay = True
|
||||
|
||||
read_measures = []
|
||||
read_measures.append(self.read_lib_meas)
|
||||
|
|
@ -113,7 +123,9 @@ class delay(simulation):
|
|||
read_measures.append(self.create_bitline_measurement_objects())
|
||||
read_measures.append(self.create_debug_measurement_objects())
|
||||
read_measures.append(self.create_read_bit_measures())
|
||||
read_measures.append(self.create_sen_and_bitline_path_measures())
|
||||
# TODO: Maybe don't do this here (?)
|
||||
if OPTS.top_process != "memchar":
|
||||
read_measures.append(self.create_sen_and_bitline_path_measures())
|
||||
|
||||
return read_measures
|
||||
|
||||
|
|
@ -158,6 +170,7 @@ class delay(simulation):
|
|||
write_measures = []
|
||||
write_measures.append(self.write_lib_meas)
|
||||
write_measures.append(self.create_write_bit_measures())
|
||||
|
||||
return write_measures
|
||||
|
||||
def create_debug_measurement_objects(self):
|
||||
|
|
@ -216,8 +229,12 @@ class delay(simulation):
|
|||
|
||||
bit_col = self.get_data_bit_column_number(probe_address, probe_data)
|
||||
bit_row = self.get_address_row_number(probe_address)
|
||||
(cell_name, cell_inst) = self.sram.get_cell_name(self.sram.name, bit_row, bit_col)
|
||||
storage_names = cell_inst.mod.get_storage_net_names()
|
||||
if OPTS.top_process == "memchar":
|
||||
cell_name = self.cell_name.format(bit_row, bit_col)
|
||||
storage_names = ("Q", "Q_bar")
|
||||
else:
|
||||
(cell_name, cell_inst) = self.sram.get_cell_name(self.sram.name, bit_row, bit_col)
|
||||
storage_names = cell_inst.mod.get_storage_net_names()
|
||||
debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes"
|
||||
"supported for characterization. Storage nets={0}").format(storage_names))
|
||||
if OPTS.use_pex and OPTS.pex_exe[0] != "calibre":
|
||||
|
|
@ -239,8 +256,8 @@ class delay(simulation):
|
|||
def create_sen_and_bitline_path_measures(self):
|
||||
"""Create measurements for the s_en and bitline paths for individual delays per stage."""
|
||||
|
||||
# FIXME: There should be a default_read_port variable in this case, pathing is done with this
|
||||
# but is never mentioned otherwise
|
||||
# # FIXME: There should be a default_read_port variable in this case, pathing is done with this
|
||||
# # but is never mentioned otherwise
|
||||
port = self.read_ports[0]
|
||||
sen_and_port = self.sen_name + str(port)
|
||||
bl_and_port = self.bl_name.format(port) # bl_name contains a '{}' for the port
|
||||
|
|
@ -255,8 +272,8 @@ class delay(simulation):
|
|||
bitline_path = bl_paths[0]
|
||||
|
||||
# Get the measures
|
||||
self.sen_path_meas = self.create_delay_path_measures(sen_path)
|
||||
self.bl_path_meas = self.create_delay_path_measures(bitline_path)
|
||||
self.sen_path_meas = self.create_delay_path_measures(sen_path, "sen")
|
||||
self.bl_path_meas = self.create_delay_path_measures(bitline_path, "bl")
|
||||
all_meas = self.sen_path_meas + self.bl_path_meas
|
||||
|
||||
# Paths could have duplicate measurements, remove them before they go to the stim file
|
||||
|
|
@ -278,7 +295,7 @@ class delay(simulation):
|
|||
|
||||
return unique_measures
|
||||
|
||||
def create_delay_path_measures(self, path):
|
||||
def create_delay_path_measures(self, path, process):
|
||||
"""Creates measurements for each net along given path."""
|
||||
|
||||
# Determine the directions (RISE/FALL) of signals
|
||||
|
|
@ -290,6 +307,8 @@ class delay(simulation):
|
|||
cur_net, next_net = path[i], path[i + 1]
|
||||
cur_dir, next_dir = path_dirs[i], path_dirs[i + 1]
|
||||
meas_name = "delay_{0}_to_{1}".format(cur_net, next_net)
|
||||
meas_name += "_" + process + "_id" + str(self.meas_id)
|
||||
self.meas_id += 1
|
||||
if i + 1 != len(path) - 1:
|
||||
path_meas.append(delay_measure(meas_name, cur_net, next_net, cur_dir, next_dir, measure_scale=1e9, has_port=False))
|
||||
else: # Make the last measurement always measure on FALL because is a read 0
|
||||
|
|
@ -385,15 +404,20 @@ class delay(simulation):
|
|||
|
||||
# creates and opens stimulus file for writing
|
||||
self.delay_stim_sp = "delay_stim.sp"
|
||||
temp_stim = "{0}/{1}".format(OPTS.openram_temp, self.delay_stim_sp)
|
||||
temp_stim = path.join(self.output_path, self.delay_stim_sp)
|
||||
self.sf = open(temp_stim, "w")
|
||||
|
||||
# creates and opens measure file for writing
|
||||
self.delay_meas_sp = "delay_meas.sp"
|
||||
temp_meas = path.join(self.output_path, self.delay_meas_sp)
|
||||
self.mf = open(temp_meas, "w")
|
||||
|
||||
if OPTS.spice_name == "spectre":
|
||||
self.sf.write("simulator lang=spice\n")
|
||||
self.sf.write("* Delay stimulus for period of {0}n load={1}fF slew={2}ns\n\n".format(self.period,
|
||||
self.load,
|
||||
self.slew))
|
||||
self.stim = stimuli(self.sf, self.corner)
|
||||
self.stim = stimuli(self.sf, self.mf, self.corner)
|
||||
# include files in stimulus file
|
||||
self.stim.write_include(self.trim_sp_file)
|
||||
|
||||
|
|
@ -418,6 +442,7 @@ class delay(simulation):
|
|||
t_rise=self.slew,
|
||||
t_fall=self.slew)
|
||||
|
||||
self.sf.write(".include {0}".format(temp_meas))
|
||||
# self.load_all_measure_nets()
|
||||
self.write_delay_measures()
|
||||
# self.write_simulation_saves()
|
||||
|
|
@ -426,6 +451,7 @@ class delay(simulation):
|
|||
self.stim.write_control(self.cycle_times[-1] + self.period)
|
||||
|
||||
self.sf.close()
|
||||
self.mf.close()
|
||||
|
||||
def write_power_stimulus(self, trim):
|
||||
""" Creates a stimulus file to measure leakage power only.
|
||||
|
|
@ -435,10 +461,15 @@ class delay(simulation):
|
|||
|
||||
# creates and opens stimulus file for writing
|
||||
self.power_stim_sp = "power_stim.sp"
|
||||
temp_stim = "{0}/{1}".format(OPTS.openram_temp, self.power_stim_sp)
|
||||
temp_stim = path.join(self.output_path, self.power_stim_sp)
|
||||
self.sf = open(temp_stim, "w")
|
||||
self.sf.write("* Power stimulus for period of {0}n\n\n".format(self.period))
|
||||
self.stim = stimuli(self.sf, self.corner)
|
||||
|
||||
# creates and opens measure file for writing
|
||||
self.power_meas_sp = "power_meas.sp"
|
||||
temp_meas = path.join(self.output_path, self.power_meas_sp)
|
||||
self.mf = open(temp_meas, "w")
|
||||
self.stim = stimuli(self.sf, self.mf, self.corner)
|
||||
|
||||
# include UNTRIMMED files in stimulus file
|
||||
if trim:
|
||||
|
|
@ -470,12 +501,14 @@ class delay(simulation):
|
|||
for port in self.all_ports:
|
||||
self.stim.gen_constant(sig_name="CLK{0}".format(port), v_val=0)
|
||||
|
||||
self.sf.write(".include {}".format(temp_meas))
|
||||
self.write_power_measures()
|
||||
|
||||
# run until the end of the cycle time
|
||||
self.stim.write_control(2 * self.period)
|
||||
|
||||
self.sf.close()
|
||||
self.mf.close()
|
||||
|
||||
def get_measure_variants(self, port, measure_obj, measure_type=None):
|
||||
"""
|
||||
|
|
@ -587,15 +620,15 @@ class delay(simulation):
|
|||
# Output some comments to aid where cycles start and
|
||||
# what is happening
|
||||
for comment in self.cycle_comments:
|
||||
self.sf.write("* {0}\n".format(comment))
|
||||
self.mf.write("* {0}\n".format(comment))
|
||||
|
||||
self.sf.write("\n")
|
||||
for read_port in self.targ_read_ports:
|
||||
self.sf.write("* Read ports {0}\n".format(read_port))
|
||||
self.mf.write("* Read ports {0}\n".format(read_port))
|
||||
self.write_delay_measures_read_port(read_port)
|
||||
|
||||
for write_port in self.targ_write_ports:
|
||||
self.sf.write("* Write ports {0}\n".format(write_port))
|
||||
self.mf.write("* Write ports {0}\n".format(write_port))
|
||||
self.write_delay_measures_write_port(write_port)
|
||||
|
||||
def load_pex_net(self, net: str):
|
||||
|
|
@ -605,8 +638,8 @@ class delay(simulation):
|
|||
return net
|
||||
original_net = net
|
||||
net = net[len(prefix):]
|
||||
net = net.replace(".", "_").replace("[", "\[").replace("]", "\]")
|
||||
for pattern in ["\sN_{}_[MXmx]\S+_[gsd]".format(net), net]:
|
||||
net = net.replace(".", "_").replace("[", r"\[").replace("]", r"\]")
|
||||
for pattern in [r"\sN_{}_[MXmx]\S+_[gsd]".format(net), net]:
|
||||
try:
|
||||
match = check_output(["grep", "-m1", "-o", "-iE", pattern, self.sp_file])
|
||||
return prefix + match.decode().strip()
|
||||
|
|
@ -616,8 +649,8 @@ class delay(simulation):
|
|||
|
||||
def load_all_measure_nets(self):
|
||||
measurement_nets = set()
|
||||
for port, meas in zip(self.targ_read_ports * len(self.read_meas_lists) +
|
||||
self.targ_write_ports * len(self.write_meas_lists),
|
||||
for port, meas in zip(self.targ_read_ports * len(self.read_meas_lists)
|
||||
+ self.targ_write_ports * len(self.write_meas_lists),
|
||||
self.read_meas_lists + self.write_meas_lists):
|
||||
for measurement in meas:
|
||||
visited = getattr(measurement, 'pex_visited', False)
|
||||
|
|
@ -684,6 +717,8 @@ class delay(simulation):
|
|||
self.sf.write("\n* Measure statements for idle leakage power\n")
|
||||
|
||||
# add measure statements for power
|
||||
# TODO: Convert to measure statement insted of using stimuli
|
||||
# measure = power_measure('leakage_power',
|
||||
t_initial = self.period
|
||||
t_final = 2 * self.period
|
||||
self.stim.gen_meas_power(meas_name="leakage_power",
|
||||
|
|
@ -791,7 +826,7 @@ class delay(simulation):
|
|||
|
||||
for port in self.targ_write_ports:
|
||||
if not self.check_bit_measures(self.write_bit_meas, port):
|
||||
return(False, {})
|
||||
return (False, {})
|
||||
|
||||
debug.info(2, "Checking write values for port {0}".format(port))
|
||||
write_port_dict = {}
|
||||
|
|
@ -805,7 +840,7 @@ class delay(simulation):
|
|||
for port in self.targ_read_ports:
|
||||
# First, check that the memory has the right values at the right times
|
||||
if not self.check_bit_measures(self.read_bit_meas, port):
|
||||
return(False, {})
|
||||
return (False, {})
|
||||
|
||||
debug.info(2, "Checking read delay values for port {0}".format(port))
|
||||
# Check sen timing, then bitlines, then general measurements.
|
||||
|
|
@ -828,7 +863,8 @@ class delay(simulation):
|
|||
|
||||
result[port].update(read_port_dict)
|
||||
|
||||
self.path_delays = self.check_path_measures()
|
||||
if self.sen_path_meas and self.bl_path_meas:
|
||||
self.path_delays = self.check_path_measures()
|
||||
|
||||
return (True, result)
|
||||
|
||||
|
|
@ -919,7 +955,7 @@ class delay(simulation):
|
|||
def check_bitline_meas(self, v_discharged_bl, v_charged_bl):
|
||||
"""
|
||||
Checks the value of the discharging bitline. Confirms s_en timing errors.
|
||||
Returns true if the bitlines are at there expected value.
|
||||
Returns true if the bitlines are at there their value.
|
||||
"""
|
||||
# The inputs looks at discharge/charged bitline rather than left or right (bl/br)
|
||||
# Performs two checks, discharging bitline is at least 10% away from vdd and there is a
|
||||
|
|
@ -942,7 +978,7 @@ class delay(simulation):
|
|||
if type(val) != float or val > self.period / 2:
|
||||
debug.info(1, 'Failed measurement:{}={}'.format(meas.name, val))
|
||||
value_dict[meas.name] = val
|
||||
#debug.info(0, "value_dict={}".format(value_dict))
|
||||
# debug.info(0, "value_dict={}".format(value_dict))
|
||||
return value_dict
|
||||
|
||||
def run_power_simulation(self):
|
||||
|
|
@ -992,8 +1028,8 @@ class delay(simulation):
|
|||
slews_str = "slew_hl={0} slew_lh={1}".format(slew_hl, slew_lh)
|
||||
# high-to-low delays start at neg. clk edge, so they need to be less than half_period
|
||||
half_period = self.period / 2
|
||||
if abs(delay_hl)>half_period or abs(delay_lh)>self.period or abs(slew_hl)>half_period or abs(slew_lh)>self.period \
|
||||
or delay_hl<0 or delay_lh<0 or slew_hl<0 or slew_lh<0:
|
||||
if abs(delay_hl)>half_period or abs(delay_lh)>half_period or abs(slew_hl)>half_period or abs(slew_lh)>self.period \
|
||||
or (delay_hl<0 and delay_lh<0) or slew_hl<0 or slew_lh<0:
|
||||
debug.info(2, "UNsuccessful simulation (in ns):\n\t\t{0}\n\t\t{1}\n\t\t{2}".format(period_load_slew_str,
|
||||
delays_str,
|
||||
slews_str))
|
||||
|
|
@ -1003,6 +1039,13 @@ class delay(simulation):
|
|||
delays_str,
|
||||
slews_str))
|
||||
|
||||
if delay_lh < 0 and delay_hl > 0:
|
||||
result_dict["delay_lh"] = result_dict["delay_hl"]
|
||||
debug.info(2, "delay_lh captured precharge, using delay_hl instead")
|
||||
elif delay_hl < 0 and delay_lh > 0:
|
||||
result_dict["delay_hl"] = result_dict["delay_lh"]
|
||||
debug.info(2, "delay_hl captured precharge, using delay_lh instead")
|
||||
|
||||
return True
|
||||
|
||||
def find_min_period(self, feasible_delays):
|
||||
|
|
@ -1108,32 +1151,143 @@ class delay(simulation):
|
|||
Netlist reduced for simulation.
|
||||
"""
|
||||
super().set_probe(probe_address, probe_data)
|
||||
self.prepare_netlist()
|
||||
|
||||
def prepare_netlist(self):
|
||||
""" Prepare a trimmed netlist and regular netlist. """
|
||||
|
||||
# Set up to trim the netlist here if that is enabled
|
||||
# TODO: Copy old netlist if memchar
|
||||
if OPTS.trim_netlist:
|
||||
#self.trim_sp_file = "{0}trimmed.sp".format(self.output_path)
|
||||
self.trim_sp_file = "{0}trimmed.sp".format(OPTS.openram_temp)
|
||||
self.sram.sp_write(self.trim_sp_file, lvs=False, trim=True)
|
||||
# Only genrate spice when running openram process
|
||||
if OPTS.top_process != "memchar":
|
||||
self.sram.sp_write(self.trim_sp_file, lvs=False, trim=True)
|
||||
else:
|
||||
# The non-reduced netlist file when it is disabled
|
||||
self.trim_sp_file = "{0}sram.sp".format(OPTS.openram_temp)
|
||||
self.trim_sp_file = "{0}sram.sp".format(self.output_path)
|
||||
|
||||
# The non-reduced netlist file for power simulation
|
||||
self.sim_sp_file = "{0}sram.sp".format(OPTS.openram_temp)
|
||||
self.sim_sp_file = "{0}sram.sp".format(self.output_path)
|
||||
# Make a copy in temp for debugging
|
||||
shutil.copy(self.sp_file, self.sim_sp_file)
|
||||
if self.sp_file != self.sim_sp_file:
|
||||
shutil.copy(self.sp_file, self.sim_sp_file)
|
||||
|
||||
def recover_measurment_objects(self):
|
||||
mf_path = path.join(OPTS.output_path, "delay_meas.sp")
|
||||
self.sen_path_meas = None
|
||||
self.bl_path_meas = None
|
||||
if not path.exists(mf_path):
|
||||
debug.info(1, "Delay measure file not found. Skipping measure recovery")
|
||||
return
|
||||
mf = open(mf_path, "r")
|
||||
measure_text = mf.read()
|
||||
port_iter = re.finditer(r"\* (Read|Write) ports (\d*)", measure_text)
|
||||
port_measure_lines = []
|
||||
loc = 0
|
||||
port_name = ''
|
||||
for port in port_iter:
|
||||
port_measure_lines.append((port_name, measure_text[loc:port.end(0)]))
|
||||
loc = port.start(0)
|
||||
port_name = port.group(1) + port.group(2)
|
||||
|
||||
mf.close()
|
||||
# Cycle comments, not sure if i need this
|
||||
# cycle_lines = port_measure_lines.pop(0)[1]
|
||||
# For now just recover the bit_measures and sen_and_bitline_path_measures
|
||||
self.read_meas_lists.append([])
|
||||
self.read_bit_meas = {bit_polarity.NONINVERTING: [], bit_polarity.INVERTING: []}
|
||||
self.write_bit_meas = {bit_polarity.NONINVERTING: [], bit_polarity.INVERTING: []}
|
||||
# bit_measure_rule = re.compile(r"\.meas tran (v_q_a\d+_b\d+_(read|write)_(zero|one)\d+) FIND v\((.*)\) AT=(\d+(\.\d+)?)n")
|
||||
# for measures in port_measure_lines:
|
||||
# port_name = measures[0]
|
||||
# text = measures[1]
|
||||
# bit_measure_iter = bit_measure_rule.finditer(text)
|
||||
# for bit_measure in bit_measure_iter:
|
||||
# meas_name = bit_measure.group(1)
|
||||
# read = bit_measure.group(2) == "read"
|
||||
# cycle = bit_measure.group(3)
|
||||
# probe = bit_measure.group(4)
|
||||
# polarity = bit_polarity.NONINVERTING
|
||||
# if "q_bar" in meas_name:
|
||||
# polarity = bit_polarity.INVERTING
|
||||
# meas = voltage_at_measure(meas_name, probe)
|
||||
# if read:
|
||||
# if cycle == "one":
|
||||
# meas.meta_str = sram_op.READ_ONE
|
||||
# else:
|
||||
# meas.meta_str = sram_op.READ_ZERO
|
||||
# self.read_bit_meas[polarity].append(meas)
|
||||
# self.read_meas_lists[-1].append(meas)
|
||||
# else:
|
||||
# if cycle == "one":
|
||||
# meas.meta_str = sram_op.WRITE_ONE
|
||||
# else:
|
||||
# meas.meta_str = sram_op.WRITE_ZERO
|
||||
# self.write_bit_meas[polarity].append(meas)
|
||||
# self.write_meas_lists[-1].append(meas)
|
||||
|
||||
delay_path_rule = re.compile(r"\.meas tran delay_(.*)_to_(.*)_(sen|bl)_(id\d*) TRIG v\((.*)\) VAL=(\d+(\.\d+)?) (RISE|FALL)=(\d+) TD=(\d+(\.\d+)?)n TARG v\((.*)\) VAL=(\d+(\.\d+)?) (RISE|FALL)=(\d+) TD=(\d+(\.\d+)?)n")
|
||||
port = self.read_ports[0]
|
||||
meas_buff = []
|
||||
self.sen_path_meas = []
|
||||
self.bl_path_meas = []
|
||||
for measures in port_measure_lines:
|
||||
text = measures[1]
|
||||
delay_path_iter = delay_path_rule.finditer(text)
|
||||
for delay_path_measure in delay_path_iter:
|
||||
from_ = delay_path_measure.group(1)
|
||||
to_ = delay_path_measure.group(2)
|
||||
path_ = delay_path_measure.group(3)
|
||||
id_ = delay_path_measure.group(4)
|
||||
trig_rise = delay_path_measure.group(8)
|
||||
targ_rise = delay_path_measure.group(15)
|
||||
meas_name = "delay_{0}_to_{1}_{2}_{3}".format(from_, to_, path_, id_)
|
||||
meas = delay_measure(meas_name, from_, to_, trig_rise, targ_rise, measure_scale=1e9, has_port=False)
|
||||
meas.meta_str = sram_op.READ_ZERO
|
||||
meas.meta_add_delay = True
|
||||
meas_buff.append(meas)
|
||||
if path_ == "sen":
|
||||
self.sen_path_meas.extend(meas_buff.copy())
|
||||
meas_buff.clear()
|
||||
elif path_ == "bl":
|
||||
self.bl_path_meas.extend(meas_buff.copy())
|
||||
meas_buff.clear()
|
||||
self.read_meas_lists.append(self.sen_path_meas + self.bl_path_meas)
|
||||
|
||||
def set_spice_names(self):
|
||||
"""This is run in place of set_internal_spice_names function from
|
||||
simulation.py when running stand-alone characterizer."""
|
||||
self.bl_name = OPTS.bl_format.format(name=self.sram.name,
|
||||
hier_sep=OPTS.hier_seperator,
|
||||
row="{}",
|
||||
col=self.bitline_column)
|
||||
self.br_name = OPTS.br_format.format(name=self.sram.name,
|
||||
hier_sep=OPTS.hier_seperator,
|
||||
row="{}",
|
||||
col=self.bitline_column)
|
||||
self.sen_name = OPTS.sen_format.format(name=self.sram.name,
|
||||
hier_sep=OPTS.hier_seperator)
|
||||
self.cell_name = OPTS.cell_format.format(name=self.sram.name,
|
||||
hier_sep=OPTS.hier_seperator,
|
||||
row="{}",
|
||||
col="{}")
|
||||
|
||||
def analysis_init(self, probe_address, probe_data):
|
||||
"""Sets values which are dependent on the data address/bit being tested."""
|
||||
|
||||
self.set_probe(probe_address, probe_data)
|
||||
self.create_graph()
|
||||
self.set_internal_spice_names()
|
||||
self.create_measurement_names()
|
||||
self.create_measurement_objects()
|
||||
self.prepare_netlist()
|
||||
if OPTS.top_process == "memchar":
|
||||
self.set_spice_names()
|
||||
self.create_measurement_names()
|
||||
self.create_measurement_objects()
|
||||
self.recover_measurment_objects()
|
||||
else:
|
||||
self.create_graph()
|
||||
self.set_internal_spice_names()
|
||||
self.create_measurement_names()
|
||||
self.create_measurement_objects()
|
||||
|
||||
def analyze(self, probe_address, probe_data, load_slews):
|
||||
"""
|
||||
|
|
@ -1145,7 +1299,7 @@ class delay(simulation):
|
|||
self.analysis_init(probe_address, probe_data)
|
||||
loads = []
|
||||
slews = []
|
||||
for load,slew in load_slews:
|
||||
for load, slew in load_slews:
|
||||
loads.append(load)
|
||||
slews.append(slew)
|
||||
self.load=max(loads)
|
||||
|
|
@ -1168,15 +1322,16 @@ class delay(simulation):
|
|||
# 4) At the minimum period, measure the delay, slew and power for all slew/load pairs.
|
||||
self.period = min_period
|
||||
char_port_data = self.simulate_loads_and_slews(load_slews, leakage_offset)
|
||||
if OPTS.use_specified_load_slew != None and len(load_slews) > 1:
|
||||
if OPTS.use_specified_load_slew is not None and len(load_slews) > 1:
|
||||
debug.warning("Path delay lists not correctly generated for characterizations of more than 1 load,slew")
|
||||
# Get and save the path delays
|
||||
bl_names, bl_delays, sen_names, sen_delays = self.get_delay_lists(self.path_delays)
|
||||
if self.sen_path_meas and self.bl_path_meas:
|
||||
bl_names, bl_delays, sen_names, sen_delays = self.get_delay_lists(self.path_delays)
|
||||
# Removed from characterization output temporarily
|
||||
#char_sram_data["bl_path_measures"] = bl_delays
|
||||
#char_sram_data["sen_path_measures"] = sen_delays
|
||||
#char_sram_data["bl_path_names"] = bl_names
|
||||
#char_sram_data["sen_path_names"] = sen_names
|
||||
# char_sram_data["bl_path_measures"] = bl_delays
|
||||
# char_sram_data["sen_path_measures"] = sen_delays
|
||||
# char_sram_data["bl_path_names"] = bl_names
|
||||
# char_sram_data["sen_path_names"] = sen_names
|
||||
# FIXME: low-to-high delays are altered to be independent of the period. This makes the lib results less accurate.
|
||||
self.alter_lh_char_data(char_port_data)
|
||||
|
||||
|
|
@ -1185,7 +1340,7 @@ class delay(simulation):
|
|||
def alter_lh_char_data(self, char_port_data):
|
||||
"""Copies high-to-low data to low-to-high data to make them consistent on the same clock edge."""
|
||||
|
||||
# This is basically a hack solution which should be removed/fixed later.
|
||||
# This is basically a hack solution which should be removed/fixed later.
|
||||
for port in self.all_ports:
|
||||
char_port_data[port]['delay_lh'] = char_port_data[port]['delay_hl']
|
||||
char_port_data[port]['slew_lh'] = char_port_data[port]['slew_hl']
|
||||
|
|
@ -1194,7 +1349,6 @@ class delay(simulation):
|
|||
"""Simulate all specified output loads and input slews pairs of all ports"""
|
||||
|
||||
measure_data = self.get_empty_measure_data_dict()
|
||||
path_dict = {}
|
||||
# Set the target simulation ports to all available ports. This make sims slower but failed sims exit anyways.
|
||||
self.targ_read_ports = self.read_ports
|
||||
self.targ_write_ports = self.write_ports
|
||||
|
|
@ -1352,9 +1506,9 @@ class delay(simulation):
|
|||
# Get any available read/write port in case only a single write or read ports is being characterized.
|
||||
cur_read_port = self.get_available_port(get_read_port=True)
|
||||
cur_write_port = self.get_available_port(get_read_port=False)
|
||||
debug.check(cur_read_port != None,
|
||||
debug.check(cur_read_port is not None,
|
||||
"Characterizer requires at least 1 read port")
|
||||
debug.check(cur_write_port != None,
|
||||
debug.check(cur_write_port is not None,
|
||||
"Characterizer requires at least 1 write port")
|
||||
|
||||
# Create test cycles for specified target ports.
|
||||
|
|
@ -1402,3 +1556,6 @@ class delay(simulation):
|
|||
self.stim.gen_pwl("CSB{0}".format(port), self.cycle_times, self.csb_values[port], self.period, self.slew, 0.05)
|
||||
if port in self.readwrite_ports:
|
||||
self.stim.gen_pwl("WEB{0}".format(port), self.cycle_times, self.web_values[port], self.period, self.slew, 0.05)
|
||||
if self.sram.num_wmasks:
|
||||
for bit in range(self.sram.num_wmasks):
|
||||
self.stim.gen_pwl("WMASK{0}_{1}".format(port, bit), self.cycle_times, self.wmask_values[port][bit], self.period, self.slew, 0.05)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,108 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
|
||||
# All rights reserved.
|
||||
#
|
||||
from openram import sram_config
|
||||
from math import ceil
|
||||
from openram import OPTS
|
||||
|
||||
|
||||
class fake_sram(sram_config):
|
||||
"""
|
||||
This is an SRAM class that doesn't actually create an instance.
|
||||
It will read neccessary members from HTML file from a previous run.
|
||||
"""
|
||||
def __init__(self, name, word_size, num_words, write_size=None, num_banks=1,
|
||||
words_per_row=None, num_spare_rows=0, num_spare_cols=0):
|
||||
sram_config.__init__(self, word_size, num_words, write_size,
|
||||
num_banks, words_per_row, num_spare_rows,
|
||||
num_spare_cols)
|
||||
self.name = name
|
||||
if write_size and self.write_size != self.word_size:
|
||||
self.num_wmasks = int(ceil(self.word_size / self.write_size))
|
||||
else:
|
||||
self.num_wmasks = 0
|
||||
|
||||
def setup_multiport_constants(self):
|
||||
"""
|
||||
Taken from ../base/design.py
|
||||
These are contants and lists that aid multiport design.
|
||||
Ports are always in the order RW, W, R.
|
||||
Port indices start from 0 and increment.
|
||||
A first RW port will have clk0, csb0, web0, addr0, data0
|
||||
A first W port (with no RW ports) will be: clk0, csb0, addr0, data0
|
||||
|
||||
"""
|
||||
total_ports = OPTS.num_rw_ports + OPTS.num_w_ports + OPTS.num_r_ports
|
||||
|
||||
# These are the read/write port indices.
|
||||
self.readwrite_ports = []
|
||||
# These are the read/write and write-only port indices
|
||||
self.write_ports = []
|
||||
# These are the write-only port indices.
|
||||
self.writeonly_ports = []
|
||||
# These are the read/write and read-only port indices
|
||||
self.read_ports = []
|
||||
# These are the read-only port indices.
|
||||
self.readonly_ports = []
|
||||
# These are all the ports
|
||||
self.all_ports = list(range(total_ports))
|
||||
|
||||
# The order is always fixed as RW, W, R
|
||||
port_number = 0
|
||||
for port in range(OPTS.num_rw_ports):
|
||||
self.readwrite_ports.append(port_number)
|
||||
self.write_ports.append(port_number)
|
||||
self.read_ports.append(port_number)
|
||||
port_number += 1
|
||||
for port in range(OPTS.num_w_ports):
|
||||
self.write_ports.append(port_number)
|
||||
self.writeonly_ports.append(port_number)
|
||||
port_number += 1
|
||||
for port in range(OPTS.num_r_ports):
|
||||
self.read_ports.append(port_number)
|
||||
self.readonly_ports.append(port_number)
|
||||
port_number += 1
|
||||
|
||||
def generate_pins(self):
|
||||
self.pins = ['vdd', 'gnd']
|
||||
self.pins.extend(['clk{}'.format(port) for port in range(
|
||||
OPTS.num_rw_ports + OPTS.num_r_ports + OPTS.num_w_ports)])
|
||||
for port in range(OPTS.num_rw_ports):
|
||||
self.pins.extend(['din{0}[{1}]'.format(port, bit)
|
||||
for bit in range(self.word_size + self.num_spare_cols)])
|
||||
self.pins.extend(['dout{0}[{1}]'.format(port, bit)
|
||||
for bit in range(self.word_size + self.num_spare_cols)])
|
||||
self.pins.extend(['addr{0}[{1}]'.format(port, bit)
|
||||
for bit in range(self.addr_size)])
|
||||
self.pins.extend(['spare_wen{0}[{1}]'.format(port, bit)
|
||||
for bit in range(self.num_spare_cols)])
|
||||
if self.num_wmasks != 0:
|
||||
self.pins.extend(['wmask{0}[{1}]'.format(port, bit)
|
||||
for bit in range(self.num_wmasks)])
|
||||
|
||||
self.pins.extend(['csb{}'.format(port), 'web{}'.format(port)])
|
||||
|
||||
start_port = OPTS.num_rw_ports
|
||||
for port in range(start_port, start_port + OPTS.num_r_ports):
|
||||
self.pins.extend(['dout{0}[{1}]'.format(port, bit)
|
||||
for bit in range(self.word_size + self.num_spare_cols)])
|
||||
self.pins.extend(['addr{0}[{1}]'.format(port, bit)
|
||||
for bit in range(self.word_size)])
|
||||
|
||||
self.pins.extend(['csb{}'.format(port)])
|
||||
|
||||
start_port += OPTS.num_r_ports
|
||||
for port in range(start_port, start_port + OPTS.num_w_ports):
|
||||
self.pins.extend(['din{0}[{1}]'.format(port, bit)
|
||||
for bit in range(self.word_size + self.num_spare_cols)])
|
||||
self.pins.extend(['spare_wen{0}[{1}]'.format(port, bit)
|
||||
for bit in range(self.num_spare_cols)])
|
||||
self.pins.extend(['addr{0}[{1}]'.format(port, bit)
|
||||
for bit in range(self.addr_size)])
|
||||
if self.num_wmasks != 0:
|
||||
self.pins.extend(['wmask{0}[{1}]'.format(port, bit)
|
||||
for bit in range(self.num_wmasks)])
|
||||
|
||||
self.pins.extend(['csb{}'.format(port), 'web{}'.format(port)])
|
||||
|
|
@ -8,12 +8,15 @@
|
|||
import math
|
||||
import random
|
||||
import collections
|
||||
from os import path
|
||||
import shutil
|
||||
from numpy import binary_repr
|
||||
from openram import debug
|
||||
from openram import OPTS
|
||||
from .stimuli import *
|
||||
from .charutils import *
|
||||
from .simulation import simulation
|
||||
from .measurements import voltage_at_measure
|
||||
|
||||
|
||||
class functional(simulation):
|
||||
|
|
@ -32,13 +35,13 @@ class functional(simulation):
|
|||
if not spfile:
|
||||
# self.sp_file is assigned in base class
|
||||
sram.sp_write(self.sp_file, trim=OPTS.trim_netlist)
|
||||
# Copy sp file to temp dir
|
||||
self.temp_spice = path.join(OPTS.openram_temp, "sram.sp")
|
||||
shutil.copy(self.sp_file, self.temp_spice)
|
||||
|
||||
if not corner:
|
||||
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
|
||||
|
||||
if period:
|
||||
self.period = period
|
||||
|
||||
if not output_path:
|
||||
self.output_path = OPTS.openram_temp
|
||||
else:
|
||||
|
|
@ -73,13 +76,20 @@ class functional(simulation):
|
|||
self.set_spice_constants()
|
||||
self.set_stimulus_variables()
|
||||
|
||||
# Override default period
|
||||
if period:
|
||||
self.period = period
|
||||
|
||||
# For the debug signal names
|
||||
self.wordline_row = 0
|
||||
self.bitline_column = 0
|
||||
self.create_signal_names()
|
||||
self.add_graph_exclusions()
|
||||
self.create_graph()
|
||||
self.set_internal_spice_names()
|
||||
#self.add_graph_exclusions()
|
||||
#self.create_graph()
|
||||
#self.set_internal_spice_names()
|
||||
self.bl_name = "xsram:xbank0:bl_0_{}"
|
||||
self.br_name = "xsram:xbank0:br_0_{}"
|
||||
self.sen_name = "xsram:s_en"
|
||||
self.q_name, self.qbar_name = self.get_bit_name()
|
||||
debug.info(2, "q:\t\t{0}".format(self.q_name))
|
||||
debug.info(2, "qbar:\t{0}".format(self.qbar_name))
|
||||
|
|
@ -145,7 +155,6 @@ class functional(simulation):
|
|||
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)
|
||||
|
||||
|
||||
# 1. Write all the write ports 2x to seed a bunch of locations.
|
||||
for i in range(3):
|
||||
for port in self.write_ports:
|
||||
|
|
@ -267,7 +276,7 @@ class functional(simulation):
|
|||
self.read_check.append([word,
|
||||
"{0}{1}".format(self.dout_name, port),
|
||||
self.t_current + self.period,
|
||||
int(self.t_current/self.period)])
|
||||
int(self.t_current / self.period)])
|
||||
|
||||
def read_stim_results(self):
|
||||
# Extract dout values from spice timing.lis
|
||||
|
|
@ -275,7 +284,8 @@ class functional(simulation):
|
|||
sp_read_value = ""
|
||||
for bit in range(self.word_size + self.num_spare_cols):
|
||||
measure_name = "v{0}_{1}ck{2}".format(dout_port.lower(), bit, cycle)
|
||||
value = parse_spice_list("timing", measure_name)
|
||||
# value = parse_spice_list("timing", measure_name)
|
||||
value = self.measures[measure_name].retrieve_measure(port=0)
|
||||
# FIXME: Ignore the spare columns for now
|
||||
if bit >= self.word_size:
|
||||
value = 0
|
||||
|
|
@ -294,10 +304,11 @@ class functional(simulation):
|
|||
self.v_low,
|
||||
self.v_high)
|
||||
except ValueError:
|
||||
error ="FAILED: {0}_{1} value {2} at time {3}n is not a float.".format(dout_port,
|
||||
error ="FAILED: {0}_{1} value {2} at time {3}n is not a float. Measure: {4}".format(dout_port,
|
||||
bit,
|
||||
value,
|
||||
eo_period)
|
||||
eo_period,
|
||||
measure_name)
|
||||
|
||||
return (0, error)
|
||||
self.read_results.append([sp_read_value, dout_port, eo_period, cycle])
|
||||
|
|
@ -318,8 +329,8 @@ class functional(simulation):
|
|||
cycle,
|
||||
self.read_results[i][2],
|
||||
check_name)
|
||||
return(0, error)
|
||||
return(1, "SUCCESS")
|
||||
return (0, error)
|
||||
return (1, "SUCCESS")
|
||||
|
||||
def gen_wmask(self):
|
||||
wmask = ""
|
||||
|
|
@ -347,7 +358,7 @@ class functional(simulation):
|
|||
def gen_data(self):
|
||||
""" Generates a random word to write. """
|
||||
# Don't use 0 or max value
|
||||
random_value = random.randint(1, self.max_data - 1)
|
||||
random_value = random.randint(1, self.max_data)
|
||||
data_bits = binary_repr(random_value, self.word_size)
|
||||
if self.num_spare_cols>0:
|
||||
random_value = random.randint(0, self.max_col_data)
|
||||
|
|
@ -380,13 +391,16 @@ class functional(simulation):
|
|||
def write_functional_stimulus(self):
|
||||
""" Writes SPICE stimulus. """
|
||||
self.stim_sp = "functional_stim.sp"
|
||||
temp_stim = "{0}/{1}".format(self.output_path, self.stim_sp)
|
||||
temp_stim = path.join(self.output_path, self.stim_sp)
|
||||
self.sf = open(temp_stim, "w")
|
||||
self.sf.write("* Functional test stimulus file for {0}ns period\n\n".format(self.period))
|
||||
self.stim = stimuli(self.sf, self.corner)
|
||||
self.meas_sp = "functional_meas.sp"
|
||||
temp_meas = path.join(self.output_path, self.meas_sp)
|
||||
self.mf = open(temp_meas, "w")
|
||||
self.stim = stimuli(self.sf, self.mf, self.corner)
|
||||
|
||||
# Write include statements
|
||||
self.stim.write_include(self.sp_file)
|
||||
self.stim.write_include(self.temp_spice)
|
||||
|
||||
# Write Vdd/Gnd statements
|
||||
self.sf.write("\n* Global Power Supplies\n")
|
||||
|
|
@ -470,6 +484,7 @@ class functional(simulation):
|
|||
|
||||
# Generate dout value measurements
|
||||
self.sf.write("\n * Generation of dout measurements\n")
|
||||
self.measures = {}
|
||||
|
||||
for (word, dout_port, eo_period, cycle) in self.read_check:
|
||||
t_initial = eo_period
|
||||
|
|
@ -477,28 +492,37 @@ class functional(simulation):
|
|||
num_bits = self.word_size + self.num_spare_cols
|
||||
for bit in range(num_bits):
|
||||
signal_name = "{0}_{1}".format(dout_port, bit)
|
||||
measure_name = "V{0}ck{1}".format(signal_name, cycle)
|
||||
measure_name = "v{0}ck{1}".format(signal_name, cycle)
|
||||
voltage_value = self.stim.get_voltage(word[num_bits - bit - 1])
|
||||
|
||||
self.stim.add_comment("* CHECK {0} {1} = {2} time = {3}".format(signal_name,
|
||||
measure_name,
|
||||
voltage_value,
|
||||
eo_period))
|
||||
self.stim.gen_meas_value(meas_name=measure_name,
|
||||
dout=signal_name,
|
||||
t_initial=t_initial,
|
||||
t_final=t_final)
|
||||
# TODO: Convert to measurement statement instead of stimuli
|
||||
meas = voltage_at_measure(measure_name, signal_name)
|
||||
self.measures[measure_name] = meas
|
||||
meas.write_measure(self.stim, ((t_initial + t_final) / 2, 0))
|
||||
# self.stim.gen_meas_value(meas_name=measure_name,
|
||||
# dout=signal_name,
|
||||
# t_initial=t_initial,
|
||||
# t_final=t_final
|
||||
|
||||
self.sf.write(".include {0}\n".format(temp_meas))
|
||||
self.stim.write_control(self.cycle_times[-1] + self.period)
|
||||
self.sf.close()
|
||||
self.mf.close()
|
||||
|
||||
# FIXME: Similar function to delay.py, refactor this
|
||||
def get_bit_name(self):
|
||||
""" Get a bit cell name """
|
||||
(cell_name, cell_inst) = self.sram.get_cell_name(self.sram.name, 0, 0)
|
||||
storage_names = cell_inst.mod.get_storage_net_names()
|
||||
debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes"
|
||||
"supported for characterization. Storage nets={0}").format(storage_names))
|
||||
# TODO: Find a way to get the cell_name and storage_names statically
|
||||
# (cell_name, cell_inst) = self.sram.get_cell_name(self.sram.name, 0, 0)
|
||||
# storage_names = cell_inst.mod.get_storage_net_names()
|
||||
# debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes"
|
||||
# "supported for characterization. Storage nets={0}").format(storage_names))
|
||||
cell_name = "xsram:xbank0:xbitcell_array:xbitcell_array:xbit_r0_c0"
|
||||
storage_names = ("Q", "Q_bar")
|
||||
q_name = cell_name + OPTS.hier_seperator + str(storage_names[0])
|
||||
qbar_name = cell_name + OPTS.hier_seperator + str(storage_names[1])
|
||||
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ class lib:
|
|||
debug.info(1, "Slews: {0}".format(self.slews))
|
||||
debug.info(1, "Loads: {0}".format(self.loads))
|
||||
debug.info(1, "self.load_slews : {0}".format(self.load_slews))
|
||||
|
||||
def create_corners(self):
|
||||
""" Create corners for characterization. """
|
||||
# Get the corners from the options file
|
||||
|
|
@ -801,7 +802,8 @@ class lib:
|
|||
|
||||
# information of checks
|
||||
# run it only the first time
|
||||
datasheet.write("{0},{1},".format(self.sram.drc_errors, self.sram.lvs_errors))
|
||||
if OPTS.top_process != "memchar":
|
||||
datasheet.write("{0},{1},".format(self.sram.drc_errors, self.sram.lvs_errors))
|
||||
|
||||
# write area
|
||||
datasheet.write(str(self.sram.width * self.sram.height) + ',')
|
||||
|
|
|
|||
|
|
@ -15,15 +15,16 @@ from .charutils import *
|
|||
class spice_measurement(ABC):
|
||||
"""Base class for spice stimulus measurements."""
|
||||
def __init__(self, measure_name, measure_scale=None, has_port=True):
|
||||
#Names must be unique for correct spice simulation, but not enforced here.
|
||||
# Names must be unique for correct spice simulation, but not enforced here.
|
||||
self.name = measure_name
|
||||
self.measure_scale = measure_scale
|
||||
self.has_port = has_port #Needed for error checking
|
||||
#Some meta values used externally. variables are added here for consistency accross the objects
|
||||
self.has_port = has_port # Needed for error checking
|
||||
# Some meta values used externally. variables are added here for consistency accross the objects
|
||||
self.meta_str = None
|
||||
self.meta_add_delay = False
|
||||
|
||||
@abstractmethod
|
||||
def get_measure_function(self):
|
||||
def measure_function(self):
|
||||
return None
|
||||
|
||||
@abstractmethod
|
||||
|
|
@ -31,28 +32,25 @@ class spice_measurement(ABC):
|
|||
return None
|
||||
|
||||
def write_measure(self, stim_obj, input_tuple):
|
||||
measure_func = self.get_measure_function()
|
||||
if measure_func == None:
|
||||
debug.error("Did not set measure function",1)
|
||||
measure_vals = self.get_measure_values(*input_tuple)
|
||||
measure_func(stim_obj, *measure_vals)
|
||||
self.measure_function(stim_obj, *measure_vals)
|
||||
|
||||
def retrieve_measure(self, port=None):
|
||||
self.port_error_check(port)
|
||||
if port != None:
|
||||
if port is not None:
|
||||
value = parse_spice_list("timing", "{0}{1}".format(self.name.lower(), port))
|
||||
else:
|
||||
value = parse_spice_list("timing", "{0}".format(self.name.lower()))
|
||||
if type(value)!=float or self.measure_scale == None:
|
||||
if type(value)!=float or self.measure_scale is None:
|
||||
return value
|
||||
else:
|
||||
return value*self.measure_scale
|
||||
return value * self.measure_scale
|
||||
|
||||
def port_error_check(self, port):
|
||||
if self.has_port and port == None:
|
||||
debug.error("Cannot retrieve measurement, port input was expected.",1)
|
||||
elif not self.has_port and port != None:
|
||||
debug.error("Unexpected port input received during measure retrieval.",1)
|
||||
if self.has_port and port is None:
|
||||
debug.error("Cannot retrieve measurement, port input was expected.", 1)
|
||||
elif not self.has_port and port is not None:
|
||||
debug.error("Unexpected port input received during measure retrieval.", 1)
|
||||
|
||||
|
||||
class delay_measure(spice_measurement):
|
||||
|
|
@ -71,8 +69,18 @@ class delay_measure(spice_measurement):
|
|||
spice_measurement.__init__(self, measure_name, measure_scale, has_port)
|
||||
self.set_meas_constants(trig_name, targ_name, trig_dir_str, targ_dir_str, trig_vdd, targ_vdd)
|
||||
|
||||
def get_measure_function(self):
|
||||
return stimuli.gen_meas_delay
|
||||
def measure_function(self, stim_obj, meas_name, trig_name, targ_name, trig_val, targ_val, trig_dir, targ_dir, trig_td, targ_td):
|
||||
""" Creates the .meas statement for the measurement of delay """
|
||||
measure_string=".meas tran {0} TRIG v({1}) VAL={2} {3}=1 TD={4}n TARG v({5}) VAL={6} {7}=1 TD={8}n\n\n"
|
||||
stim_obj.mf.write(measure_string.format(meas_name.lower(),
|
||||
trig_name,
|
||||
trig_val,
|
||||
trig_dir,
|
||||
trig_td,
|
||||
targ_name,
|
||||
targ_val,
|
||||
targ_dir,
|
||||
targ_td))
|
||||
|
||||
def set_meas_constants(self, trig_name, targ_name, trig_dir_str, targ_dir_str, trig_vdd, targ_vdd):
|
||||
"""Set the constants for this measurement: signal names, directions, and trigger scales"""
|
||||
|
|
@ -91,7 +99,7 @@ class delay_measure(spice_measurement):
|
|||
trig_val = self.trig_val_of_vdd * vdd_voltage
|
||||
targ_val = self.targ_val_of_vdd * vdd_voltage
|
||||
|
||||
if port != None:
|
||||
if port is not None:
|
||||
# For dictionary indexing reasons, the name is formatted differently than the signals
|
||||
meas_name = "{}{}".format(self.name, port)
|
||||
trig_name = self.trig_name_no_port.format(port)
|
||||
|
|
@ -121,7 +129,7 @@ class slew_measure(delay_measure):
|
|||
self.trig_val_of_vdd = 0.9
|
||||
self.targ_val_of_vdd = 0.1
|
||||
else:
|
||||
debug.error("Unrecognised slew measurement direction={}".format(slew_dir_str),1)
|
||||
debug.error("Unrecognised slew measurement direction={}".format(slew_dir_str), 1)
|
||||
self.trig_name_no_port = signal_name
|
||||
self.targ_name_no_port = signal_name
|
||||
|
||||
|
|
@ -135,8 +143,18 @@ class power_measure(spice_measurement):
|
|||
spice_measurement.__init__(self, measure_name, measure_scale, has_port)
|
||||
self.set_meas_constants(power_type)
|
||||
|
||||
def get_measure_function(self):
|
||||
return stimuli.gen_meas_power
|
||||
def measure_function(self, stim_obj, meas_name, t_initial, t_final):
|
||||
""" Creates the .meas statement for the measurement of avg power """
|
||||
# power mea cmd is different in different spice:
|
||||
if OPTS.spice_name == "hspice":
|
||||
power_exp = "power"
|
||||
else:
|
||||
# FIXME: Obtain proper vdd and gnd name
|
||||
power_exp = "par('(-1*v(" + "vdd" + ")*I(v" + "vdd" + "))')"
|
||||
stim_obj.mf.write(".meas tran {0} avg {1} from={2}n to={3}n\n\n".format(meas_name.lower(),
|
||||
power_exp,
|
||||
t_initial,
|
||||
t_final))
|
||||
|
||||
def set_meas_constants(self, power_type):
|
||||
"""Sets values useful for power simulations. This value is only meta related to the lib file (rise/fall)"""
|
||||
|
|
@ -146,7 +164,7 @@ class power_measure(spice_measurement):
|
|||
def get_measure_values(self, t_initial, t_final, port=None):
|
||||
"""Constructs inputs to stimulus measurement function. Variant values are inputs here."""
|
||||
self.port_error_check(port)
|
||||
if port != None:
|
||||
if port is not None:
|
||||
meas_name = "{}{}".format(self.name, port)
|
||||
else:
|
||||
meas_name = self.name
|
||||
|
|
@ -160,8 +178,15 @@ class voltage_when_measure(spice_measurement):
|
|||
spice_measurement.__init__(self, measure_name, measure_scale, has_port)
|
||||
self.set_meas_constants(trig_name, targ_name, trig_dir_str, trig_vdd)
|
||||
|
||||
def get_measure_function(self):
|
||||
return stimuli.gen_meas_find_voltage
|
||||
def measure_function(self, stim_obj, meas_name, trig_name, targ_name, trig_val, trig_dir, trig_td):
|
||||
""" Creates the .meas statement for the measurement of delay """
|
||||
measure_string=".meas tran {0} FIND v({1}) WHEN v({2})={3}v {4}=1 TD={5}n \n\n"
|
||||
stim_obj.mf.write(measure_string.format(meas_name.lower(),
|
||||
targ_name,
|
||||
trig_name,
|
||||
trig_val,
|
||||
trig_dir,
|
||||
trig_td))
|
||||
|
||||
def set_meas_constants(self, trig_name, targ_name, trig_dir_str, trig_vdd):
|
||||
"""Sets values useful for power simulations. This value is only meta related to the lib file (rise/fall)"""
|
||||
|
|
@ -173,7 +198,7 @@ class voltage_when_measure(spice_measurement):
|
|||
def get_measure_values(self, trig_td, vdd_voltage, port=None):
|
||||
"""Constructs inputs to stimulus measurement function. Variant values are inputs here."""
|
||||
self.port_error_check(port)
|
||||
if port != None:
|
||||
if port is not None:
|
||||
# For dictionary indexing reasons, the name is formatted differently than the signals
|
||||
meas_name = "{}{}".format(self.name, port)
|
||||
trig_name = self.trig_name_no_port.format(port)
|
||||
|
|
@ -194,8 +219,12 @@ class voltage_at_measure(spice_measurement):
|
|||
spice_measurement.__init__(self, measure_name, measure_scale, has_port)
|
||||
self.set_meas_constants(targ_name)
|
||||
|
||||
def get_measure_function(self):
|
||||
return stimuli.gen_meas_find_voltage_at_time
|
||||
def measure_function(self, stim_obj, meas_name, targ_name, time_at):
|
||||
""" Creates the .meas statement for voltage at time"""
|
||||
measure_string=".meas tran {0} FIND v({1}) AT={2}n \n\n"
|
||||
stim_obj.mf.write(measure_string.format(meas_name.lower(),
|
||||
targ_name,
|
||||
time_at))
|
||||
|
||||
def set_meas_constants(self, targ_name):
|
||||
"""Sets values useful for power simulations. This value is only meta related to the lib file (rise/fall)"""
|
||||
|
|
@ -204,7 +233,7 @@ class voltage_at_measure(spice_measurement):
|
|||
def get_measure_values(self, time_at, port=None):
|
||||
"""Constructs inputs to stimulus measurement function. Variant values are inputs here."""
|
||||
self.port_error_check(port)
|
||||
if port != None:
|
||||
if port is not None:
|
||||
# For dictionary indexing reasons, the name is formatted differently than the signals
|
||||
meas_name = "{}{}".format(self.name, port)
|
||||
targ_name = self.targ_name_no_port.format(port)
|
||||
|
|
|
|||
|
|
@ -42,7 +42,13 @@ class setup_hold():
|
|||
self.stim_sp = "sh_stim.sp"
|
||||
temp_stim = OPTS.openram_temp + self.stim_sp
|
||||
self.sf = open(temp_stim, "w")
|
||||
self.stim = stimuli(self.sf, self.corner)
|
||||
|
||||
# creates and opens the measure file for writing
|
||||
self.meas_sp = "sh_meas.sp"
|
||||
temp_meas = OPTS.openram_temp + self.meas_sp
|
||||
self.mf = open(temp_meas, "w")
|
||||
|
||||
self.stim = stimuli(self.sf, self.mf, self.corner)
|
||||
|
||||
self.write_header(correct_value)
|
||||
|
||||
|
|
@ -56,6 +62,7 @@ class setup_hold():
|
|||
correct_value=correct_value)
|
||||
|
||||
self.write_clock()
|
||||
self.sf.write(".include {}\n".format(temp_meas))
|
||||
|
||||
self.write_measures(mode=mode,
|
||||
correct_value=correct_value)
|
||||
|
|
@ -63,6 +70,7 @@ class setup_hold():
|
|||
self.stim.write_control(4 * self.period)
|
||||
|
||||
self.sf.close()
|
||||
self.mf.close()
|
||||
|
||||
def write_header(self, correct_value):
|
||||
""" Write the header file with all the models and the power supplies. """
|
||||
|
|
@ -131,7 +139,7 @@ class setup_hold():
|
|||
else:
|
||||
dout_rise_or_fall = "FALL"
|
||||
|
||||
self.sf.write("\n* Measure statements for pass/fail verification\n")
|
||||
self.mf.write("\n* Measure statements for pass/fail verification\n")
|
||||
trig_name = "clk"
|
||||
targ_name = "Q"
|
||||
trig_val = targ_val = 0.5 * self.vdd_voltage
|
||||
|
|
@ -168,29 +176,33 @@ class setup_hold():
|
|||
target_time=feasible_bound,
|
||||
correct_value=correct_value)
|
||||
self.stim.run_sim(self.stim_sp)
|
||||
ideal_clk_to_q = convert_to_float(parse_spice_list("timing", "clk2q_delay"))
|
||||
ideal_clk_to_q = convert_to_float(parse_spice_list("timing",
|
||||
"clk2q_delay"))
|
||||
# We use a 1/2 speed clock for some reason...
|
||||
setuphold_time = (feasible_bound - 2 * self.period)
|
||||
if mode == "SETUP": # SETUP is clk-din, not din-clk
|
||||
passing_setuphold_time = -1 * setuphold_time
|
||||
else:
|
||||
passing_setuphold_time = setuphold_time
|
||||
debug.info(2, "*** {0} CHECK: {1} Ideal Clk-to-Q: {2} Setup/Hold: {3}".format(mode,
|
||||
correct_value,
|
||||
ideal_clk_to_q,
|
||||
setuphold_time))
|
||||
debug.info(2, "*** {0} CHECK: {1} Ideal Clk-to-Q: {2} Setup/Hold: {3}"
|
||||
.format(mode,
|
||||
correct_value,
|
||||
ideal_clk_to_q,
|
||||
setuphold_time))
|
||||
|
||||
if type(ideal_clk_to_q)!=float:
|
||||
debug.error("Initial hold time fails for data value feasible "
|
||||
"bound {0} Clk-to-Q {1} Setup/Hold {2}".format(feasible_bound,
|
||||
ideal_clk_to_q,
|
||||
setuphold_time),
|
||||
"bound {0} Clk-to-Q {1} Setup/Hold {2}"
|
||||
.format(feasible_bound,
|
||||
ideal_clk_to_q,
|
||||
setuphold_time),
|
||||
2)
|
||||
|
||||
debug.info(2, "Checked initial {0} time {1}, data at {2}, clock at {3} ".format(mode,
|
||||
setuphold_time,
|
||||
feasible_bound,
|
||||
2 * self.period))
|
||||
debug.info(2, "Checked initial {0} time {1}, data at {2}, clock at {3} "
|
||||
.format(mode,
|
||||
setuphold_time,
|
||||
feasible_bound,
|
||||
2 * self.period))
|
||||
|
||||
while True:
|
||||
target_time = (feasible_bound + infeasible_bound) / 2
|
||||
|
|
@ -198,11 +210,12 @@ class setup_hold():
|
|||
target_time=target_time,
|
||||
correct_value=correct_value)
|
||||
|
||||
debug.info(2, "{0} value: {1} Target time: {2} Infeasible: {3} Feasible: {4}".format(mode,
|
||||
correct_value,
|
||||
target_time,
|
||||
infeasible_bound,
|
||||
feasible_bound))
|
||||
debug.info(2, "{0} value: {1} Target time: {2} Infeasible: {3} Feasible: {4}"
|
||||
.format(mode,
|
||||
correct_value,
|
||||
target_time,
|
||||
infeasible_bound,
|
||||
feasible_bound))
|
||||
|
||||
self.stim.run_sim(self.stim_sp)
|
||||
clk_to_q = convert_to_float(parse_spice_list("timing", "clk2q_delay"))
|
||||
|
|
|
|||
|
|
@ -235,7 +235,7 @@ class simulation():
|
|||
self.add_wmask(wmask, port)
|
||||
self.add_spare_wen("1" * self.num_spare_cols, port)
|
||||
|
||||
#Add noops to all other ports.
|
||||
# Add noops to all other ports.
|
||||
for unselected_port in self.all_ports:
|
||||
if unselected_port != port:
|
||||
self.add_noop_one_port(unselected_port)
|
||||
|
|
@ -267,7 +267,7 @@ class simulation():
|
|||
self.add_wmask("0" * self.num_wmasks, port)
|
||||
self.add_spare_wen("0" * self.num_spare_cols, port)
|
||||
|
||||
#Add noops to all other ports.
|
||||
# Add noops to all other ports.
|
||||
for unselected_port in self.all_ports:
|
||||
if unselected_port != port:
|
||||
self.add_noop_one_port(unselected_port)
|
||||
|
|
@ -356,14 +356,14 @@ class simulation():
|
|||
|
||||
self.add_noop_one_port(port)
|
||||
|
||||
#Add noops to all other ports.
|
||||
# Add noops to all other ports.
|
||||
for unselected_port in self.all_ports:
|
||||
if unselected_port != port:
|
||||
self.add_noop_one_port(unselected_port)
|
||||
|
||||
def append_cycle_comment(self, port, comment):
|
||||
"""Add comment to list to be printed in stimulus file"""
|
||||
#Clean up time before appending. Make spacing dynamic as well.
|
||||
# Clean up time before appending. Make spacing dynamic as well.
|
||||
time = "{0:.2f} ns:".format(self.t_current)
|
||||
time_spacing = len(time) + 6
|
||||
self.cycle_comments.append("Cycle {0:<6d} Port {1:<6} {2:<{3}}: {4}".format(len(self.cycle_times),
|
||||
|
|
@ -388,7 +388,7 @@ class simulation():
|
|||
split_word2 = [x + '_' * (n != 0 and n % 4 == 0) for n, x in enumerate(split_word)]
|
||||
# Join the word unreversed back together
|
||||
new_word = ''.join(reversed(split_word2))
|
||||
return(new_word)
|
||||
return (new_word)
|
||||
|
||||
# Split extra cols
|
||||
if self.num_spare_cols > 0:
|
||||
|
|
@ -414,9 +414,9 @@ class simulation():
|
|||
comment = "\tWriting {0} to address {1} (from port {2}) during cycle {3} ({4}ns - {5}ns)".format(word,
|
||||
addr,
|
||||
port,
|
||||
int(t_current/self.period),
|
||||
int(t_current / self.period),
|
||||
t_current,
|
||||
t_current+self.period)
|
||||
t_current + self.period)
|
||||
elif op == "partial_write":
|
||||
str = "\tWriting (partial) {0} to address {1} with mask bit {2} (from port {3}) during cycle {4} ({5}ns - {6}ns)"
|
||||
comment = str.format(word,
|
||||
|
|
@ -454,7 +454,7 @@ class simulation():
|
|||
for i in range(abits):
|
||||
pin_names.append("{0}{1}_{2}".format(addr_name, port, i))
|
||||
|
||||
#Control signals not finalized.
|
||||
# Control signals not finalized.
|
||||
for port in range(total_ports):
|
||||
pin_names.append("CSB{0}".format(port))
|
||||
for port in range(total_ports):
|
||||
|
|
@ -493,11 +493,12 @@ class simulation():
|
|||
|
||||
# other initializations can only be done during analysis when a bit has been selected
|
||||
# for testing.
|
||||
self.sram.bank.graph_exclude_precharge()
|
||||
self.sram.graph_exclude_addr_dff()
|
||||
self.sram.graph_exclude_data_dff()
|
||||
self.sram.graph_exclude_ctrl_dffs()
|
||||
self.sram.bank.bitcell_array.graph_exclude_replica_col_bits()
|
||||
if OPTS.top_process != 'memchar':
|
||||
self.sram.bank.graph_exclude_precharge()
|
||||
self.sram.graph_exclude_addr_dff()
|
||||
self.sram.graph_exclude_data_dff()
|
||||
self.sram.graph_exclude_ctrl_dffs()
|
||||
self.sram.bank.bitcell_array.graph_exclude_replica_col_bits()
|
||||
|
||||
def set_internal_spice_names(self):
|
||||
"""
|
||||
|
|
@ -515,9 +516,9 @@ class simulation():
|
|||
self.sen_name = sen_with_port
|
||||
debug.warning("Error occurred while determining SEN name. Can cause faults in simulation.")
|
||||
|
||||
column_addr = self.get_column_addr()
|
||||
# column_addr = self.get_column_addr()
|
||||
bl_name_port, br_name_port = self.get_bl_name(self.graph.all_paths, port)
|
||||
port_pos = -1 - len(str(column_addr)) - len(str(port))
|
||||
# port_pos = -1 - len(str(column_addr)) - len(str(port))
|
||||
|
||||
if bl_name_port.endswith(str(port) + "_" + str(self.bitline_column)): # single port SRAM case, bl will not be numbered eg bl_0
|
||||
self.bl_name = bl_name_port
|
||||
|
|
@ -535,7 +536,7 @@ class simulation():
|
|||
'{}{}_{}'.format(self.dout_name, port, self.probe_data))
|
||||
|
||||
self.sen_name = self.get_sen_name(self.graph.all_paths)
|
||||
#debug.info(2, "s_en {}".format(self.sen_name))
|
||||
# debug.info(2, "s_en {}".format(self.sen_name))
|
||||
|
||||
self.bl_name = "bl{0}_{1}".format(port, OPTS.word_size - 1)
|
||||
self.br_name = "br{0}_{1}".format(port, OPTS.word_size - 1)
|
||||
|
|
@ -563,10 +564,10 @@ class simulation():
|
|||
Creates timing graph to generate the timing paths for the SRAM output.
|
||||
"""
|
||||
|
||||
#Make exclusions dependent on the bit being tested.
|
||||
# Make exclusions dependent on the bit being tested.
|
||||
self.sram.clear_exclude_bits() # Removes previous bit exclusions
|
||||
self.sram.graph_exclude_bits(self.wordline_row, self.bitline_column)
|
||||
port=self.read_ports[0] #FIXME, port_data requires a port specification, assuming single port for now
|
||||
port=self.read_ports[0] # FIXME, port_data requires a port specification, assuming single port for now
|
||||
if self.words_per_row > 1:
|
||||
self.sram.graph_clear_column_mux(port)
|
||||
self.sram.graph_exclude_column_mux(self.bitline_column, port)
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 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 enum import Enum
|
||||
|
||||
class sram_op(Enum):
|
||||
READ_ZERO = 0
|
||||
READ_ONE = 1
|
||||
WRITE_ZERO = 2
|
||||
WRITE_ONE = 3
|
||||
|
|
@ -22,7 +22,7 @@ from openram import OPTS
|
|||
class stimuli():
|
||||
""" Class for providing stimuli functions """
|
||||
|
||||
def __init__(self, stim_file, corner):
|
||||
def __init__(self, stim_file, meas_file, corner):
|
||||
self.vdd_name = "vdd"
|
||||
self.gnd_name = "gnd"
|
||||
self.pmos_name = tech.spice["pmos"]
|
||||
|
|
@ -31,6 +31,7 @@ class stimuli():
|
|||
self.tx_length = tech.drc["minlength_channel"]
|
||||
|
||||
self.sf = stim_file
|
||||
self.mf = meas_file
|
||||
|
||||
(self.process, self.voltage, self.temperature) = corner
|
||||
found = False
|
||||
|
|
@ -136,7 +137,7 @@ class stimuli():
|
|||
offset,
|
||||
t_rise,
|
||||
t_fall,
|
||||
0.5*period-0.5*t_rise-0.5*t_fall,
|
||||
0.5 * period - 0.5 * t_rise - 0.5 * t_fall,
|
||||
period))
|
||||
|
||||
def gen_pwl(self, sig_name, clk_times, data_values, period, slew, setup):
|
||||
|
|
@ -181,7 +182,7 @@ class stimuli():
|
|||
def gen_meas_delay(self, meas_name, trig_name, targ_name, trig_val, targ_val, trig_dir, targ_dir, trig_td, targ_td):
|
||||
""" Creates the .meas statement for the measurement of delay """
|
||||
measure_string=".meas tran {0} TRIG v({1}) VAL={2} {3}=1 TD={4}n TARG v({5}) VAL={6} {7}=1 TD={8}n\n\n"
|
||||
self.sf.write(measure_string.format(meas_name.lower(),
|
||||
self.mf.write(measure_string.format(meas_name.lower(),
|
||||
trig_name,
|
||||
trig_val,
|
||||
trig_dir,
|
||||
|
|
@ -194,7 +195,7 @@ class stimuli():
|
|||
def gen_meas_find_voltage(self, meas_name, trig_name, targ_name, trig_val, trig_dir, trig_td):
|
||||
""" Creates the .meas statement for the measurement of delay """
|
||||
measure_string=".meas tran {0} FIND v({1}) WHEN v({2})={3}v {4}=1 TD={5}n \n\n"
|
||||
self.sf.write(measure_string.format(meas_name.lower(),
|
||||
self.mf.write(measure_string.format(meas_name.lower(),
|
||||
targ_name,
|
||||
trig_name,
|
||||
trig_val,
|
||||
|
|
@ -204,7 +205,7 @@ class stimuli():
|
|||
def gen_meas_find_voltage_at_time(self, meas_name, targ_name, time_at):
|
||||
""" Creates the .meas statement for voltage at time"""
|
||||
measure_string=".meas tran {0} FIND v({1}) AT={2}n \n\n"
|
||||
self.sf.write(measure_string.format(meas_name.lower(),
|
||||
self.mf.write(measure_string.format(meas_name.lower(),
|
||||
targ_name,
|
||||
time_at))
|
||||
|
||||
|
|
@ -215,15 +216,15 @@ class stimuli():
|
|||
power_exp = "power"
|
||||
else:
|
||||
power_exp = "par('(-1*v(" + str(self.vdd_name) + ")*I(v" + str(self.vdd_name) + "))')"
|
||||
self.sf.write(".meas tran {0} avg {1} from={2}n to={3}n\n\n".format(meas_name.lower(),
|
||||
self.mf.write(".meas tran {0} avg {1} from={2}n to={3}n\n\n".format(meas_name.lower(),
|
||||
power_exp,
|
||||
t_initial,
|
||||
t_final))
|
||||
|
||||
def gen_meas_value(self, meas_name, dout, t_initial, t_final):
|
||||
measure_string=".meas tran {0} FIND v({1}) AT={2}n\n\n".format(meas_name.lower(), dout, (t_initial + t_final) / 2)
|
||||
#measure_string=".meas tran {0} AVG v({1}) FROM={2}n TO={3}n\n\n".format(meas_name.lower(), dout, t_initial, t_final)
|
||||
self.sf.write(measure_string)
|
||||
# measure_string=".meas tran {0} AVG v({1}) FROM={2}n TO={3}n\n\n".format(meas_name.lower(), dout, t_initial, t_final)
|
||||
self.mf.write(measure_string)
|
||||
|
||||
def write_control(self, end_time, runlvl=4):
|
||||
""" Write the control cards to run and end the simulation """
|
||||
|
|
@ -278,7 +279,7 @@ class stimuli():
|
|||
self.sf.write(".OPTIONS DEVICE TEMP={}\n".format(self.temperature))
|
||||
self.sf.write(".OPTIONS MEASURE MEASFAIL=1\n")
|
||||
self.sf.write(".OPTIONS LINSOL type=klu\n")
|
||||
self.sf.write(".OPTIONS TIMEINT RELTOL=1e-6 ABSTOL=1e-10 method=gear minorder=2\n")
|
||||
self.sf.write(".OPTIONS TIMEINT RELTOL=1e-3 ABSTOL=1e-6 method=gear minorder=2\n")
|
||||
# Format: .TRAN <initial step> <final time> <start time> <step ceiling>
|
||||
self.sf.write(".TRAN {0}p {1}n 0n {0}p\n".format(timestep, end_time))
|
||||
elif OPTS.spice_name:
|
||||
|
|
@ -411,12 +412,12 @@ class stimuli():
|
|||
from openram import CONDA_HOME
|
||||
cmd = "source {0}/bin/activate && {1} && conda deactivate".format(CONDA_HOME, cmd)
|
||||
debug.info(2, cmd)
|
||||
retcode = subprocess.call(cmd, stdout=spice_stdout, stderr=spice_stderr, shell=True)
|
||||
proc = subprocess.run(cmd, stdout=spice_stdout, stderr=spice_stderr, shell=True)
|
||||
|
||||
spice_stdout.close()
|
||||
spice_stderr.close()
|
||||
|
||||
if (retcode > valid_retcode):
|
||||
if (proc.returncode > valid_retcode):
|
||||
debug.error("Spice simulation error: " + cmd, -1)
|
||||
else:
|
||||
end_time = datetime.datetime.now()
|
||||
|
|
|
|||
|
|
@ -85,14 +85,15 @@ class trim_spice():
|
|||
|
||||
wl_regex = r"wl\d*_{}".format(wl_address)
|
||||
bl_regex = r"bl\d*_{}".format(int(self.words_per_row*data_bit + col_address))
|
||||
bl_no_port_regex = r"bl_{}".format(int(self.words_per_row*data_bit + col_address))
|
||||
|
||||
self.remove_insts("bitcell_array",[wl_regex,bl_regex])
|
||||
|
||||
# 2. Keep sense amps basd on BL
|
||||
# FIXME: The bit lines are not indexed the same in sense_amp_array
|
||||
#self.remove_insts("sense_amp_array",[bl_regex])
|
||||
self.remove_insts("sense_amp_array",[bl_no_port_regex])
|
||||
|
||||
# 3. Keep column muxes basd on BL
|
||||
self.remove_insts("column_mux_array", [bl_regex])
|
||||
self.remove_insts("single_level_column_mux_array", [bl_no_port_regex])
|
||||
|
||||
# 4. Keep write driver based on DATA
|
||||
data_regex = r"data_{}".format(data_bit)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 Regents of the University of California and The Board
|
||||
|
|
@ -15,6 +15,7 @@ corner, but should probably be extended.
|
|||
|
||||
import sys
|
||||
from globals import *
|
||||
from importlib import reload
|
||||
|
||||
(OPTS, args) = parse_args()
|
||||
|
||||
|
|
@ -41,36 +42,29 @@ slew = float(args[3])
|
|||
import debug
|
||||
|
||||
init_openram(config_file=config_file, is_unit_test=False)
|
||||
|
||||
from sram_config import sram_config
|
||||
c = sram_config(word_size=OPTS.word_size,
|
||||
num_words=OPTS.num_words,
|
||||
write_size=OPTS.write_size,
|
||||
num_banks=OPTS.num_banks,
|
||||
words_per_row=OPTS.words_per_row,
|
||||
num_spare_rows=OPTS.num_spare_rows,
|
||||
num_spare_cols=OPTS.num_spare_cols)
|
||||
|
||||
OPTS.netlist_only = True
|
||||
OPTS.check_lvsdrc = False
|
||||
# Put the temp output in the output path since it is what we want to generate!
|
||||
old_openram_temp = OPTS.openram_temp
|
||||
OPTS.openram_temp = OPTS.output_path
|
||||
|
||||
|
||||
|
||||
import sram
|
||||
class fake_sram(sram.sram):
|
||||
""" This is an SRAM that doesn't actually create itself, just computes
|
||||
the sizes. """
|
||||
def __init__(self, word_size, num_words, num_banks, name, num_spare_rows):
|
||||
self.name = name
|
||||
self.word_size = word_size
|
||||
self.num_words = num_words
|
||||
self.num_banks = num_banks
|
||||
self.num_spare_rows = num_spare_rows
|
||||
c = reload(__import__(OPTS.bitcell))
|
||||
self.mod_bitcell = getattr(c, OPTS.bitcell)
|
||||
self.bitcell = self.mod_bitcell()
|
||||
# to get the row, col, etc.
|
||||
self.compute_sizes()
|
||||
|
||||
sram = fake_sram(OPTS.word_size, OPTS.num_words, OPTS.num_banks, OPTS.output_name)
|
||||
sp_file = OPTS.output_path+OPTS.output_name + ".sp"
|
||||
from sram import sram
|
||||
s = sram(name=OPTS.output_name, sram_config=c)
|
||||
|
||||
from characterizer import delay
|
||||
import tech
|
||||
# Set up the delay and set to the nominal corner
|
||||
d = delay.delay(sram, sp_file, ("TT", tech.spice["nom_supply_voltage"], tech.spice["nom_temperature"]))
|
||||
d = delay(s, s.get_sp_name(), ("TT", tech.spice["nom_supply_voltage"], tech.spice["nom_temperature"]))
|
||||
# Set the period
|
||||
d.period = period
|
||||
# Set the load of outputs and slew of inputs
|
||||
|
|
@ -91,4 +85,3 @@ print("Output files are:\n{0}stim.sp\n{0}sram.sp\n{0}reduced.sp".format(OPTS.out
|
|||
OPTS.openram_temp = old_openram_temp
|
||||
# Delete temp files, remove the dir, etc.
|
||||
end_openram()
|
||||
|
||||
|
|
|
|||
|
|
@ -104,6 +104,8 @@ class options(optparse.Values):
|
|||
sim_data_path = None
|
||||
# A list of load/slew tuples
|
||||
use_specified_load_slew = None
|
||||
# Spice simulation raw file
|
||||
spice_raw_file = None
|
||||
|
||||
###################
|
||||
# Run-time vs accuracy options.
|
||||
|
|
@ -126,14 +128,18 @@ class options(optparse.Values):
|
|||
# Output config with all options
|
||||
output_extended_config = False
|
||||
# Output temporary file used to format HTML page
|
||||
output_datasheet_info = False
|
||||
output_datasheet_info = True
|
||||
# Determines which analytical model to use.
|
||||
# Available Models: elmore, linear_regression
|
||||
model_name = "elmore"
|
||||
# Write graph to a file
|
||||
write_graph = False
|
||||
|
||||
###################
|
||||
# Tool options
|
||||
###################
|
||||
# Top process that was ran (openram, memchar, memfunc)
|
||||
top_process = None
|
||||
# Use conda to install the default tools
|
||||
# (existing tools will be used if disabled)
|
||||
use_conda = True
|
||||
|
|
@ -174,6 +180,11 @@ class options(optparse.Values):
|
|||
# Purge the temp directory after a successful
|
||||
# run (doesn't purge on errors, anyhow)
|
||||
|
||||
# Bitline, s_en and cell names used in characterizer
|
||||
bl_format = "X{name}{hier_sep}xbank0{hier_sep}bl_{row}_{col}"
|
||||
br_format = "X{name}{hier_sep}xbank0{hier_sep}br_{row}_{col}"
|
||||
sen_format = "X{name}{hier_sep}xbank0{hier_sep}s_en"
|
||||
cell_format = "X{name}{hier_sep}xbank0{hier_sep}xbitcell_array{hier_sep}xreplica_bitcell_array{hier_sep}xbitcell_array{hier_sep}xbit_r{row}_c{col}"
|
||||
# Route the input/output pins to the perimeter
|
||||
perimeter_pins = True
|
||||
|
||||
|
|
|
|||
|
|
@ -60,6 +60,14 @@ class sram():
|
|||
if not OPTS.is_unit_test:
|
||||
print_time("SRAM creation", datetime.datetime.now(), start_time)
|
||||
|
||||
def get_sp_name(self):
|
||||
if OPTS.use_pex:
|
||||
# Use the extracted spice file
|
||||
return self.pex_name
|
||||
else:
|
||||
# Use generated spice file for characterization
|
||||
return self.sp_name
|
||||
|
||||
def sp_write(self, name, lvs=False, trim=False):
|
||||
self.s.sp_write(name, lvs, trim)
|
||||
|
||||
|
|
@ -95,18 +103,40 @@ class sram():
|
|||
# is loaded and the right tools are selected
|
||||
from openram import verify
|
||||
from openram.characterizer import functional
|
||||
from openram.characterizer import delay
|
||||
|
||||
# Save the spice file
|
||||
start_time = datetime.datetime.now()
|
||||
spname = OPTS.output_path + self.s.name + ".sp"
|
||||
debug.print_raw("SP: Writing to {0}".format(spname))
|
||||
self.sp_write(spname)
|
||||
|
||||
# Save a functional simulation file with default period
|
||||
functional(self.s,
|
||||
os.path.basename(spname),
|
||||
spname,
|
||||
cycles=200,
|
||||
output_path=OPTS.output_path)
|
||||
print_time("Spice writing", datetime.datetime.now(), start_time)
|
||||
|
||||
# Save stimulus and measurement file
|
||||
start_time = datetime.datetime.now()
|
||||
debug.print_raw("DELAY: Writing stimulus...")
|
||||
d = delay(self.s, spname, ("TT", 5, 25), output_path=OPTS.output_path)
|
||||
if (self.s.num_spare_rows == 0):
|
||||
probe_address = "1" * self.s.addr_size
|
||||
else:
|
||||
probe_address = "0" + "1" * (self.s.addr_size - 1)
|
||||
probe_data = self.s.word_size - 1
|
||||
d.analysis_init(probe_address, probe_data)
|
||||
d.targ_read_ports.extend(self.s.read_ports)
|
||||
d.targ_write_ports = [self.s.write_ports[0]]
|
||||
d.write_delay_stimulus()
|
||||
print_time("DELAY", datetime.datetime.now(), start_time)
|
||||
|
||||
# Save trimmed spice file
|
||||
temp_trim_sp = "{0}trimmed.sp".format(OPTS.output_path)
|
||||
self.sp_write(temp_trim_sp, lvs=False, trim=True)
|
||||
|
||||
if not OPTS.netlist_only:
|
||||
# Write the layout
|
||||
start_time = datetime.datetime.now()
|
||||
|
|
@ -154,8 +184,6 @@ class sram():
|
|||
# Use generated spice file for characterization
|
||||
sp_file = spname
|
||||
|
||||
# Save a functional simulation file
|
||||
|
||||
# Characterize the design
|
||||
start_time = datetime.datetime.now()
|
||||
from openram.characterizer import lib
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ from openram import OPTS
|
|||
class sram_config:
|
||||
""" This is a structure that is used to hold the SRAM configuration options. """
|
||||
|
||||
def __init__(self, word_size, num_words, write_size=None, num_banks=1, words_per_row=None, num_spare_rows=0, num_spare_cols=0):
|
||||
def __init__(self, word_size, num_words, write_size=None, num_banks=1,
|
||||
words_per_row=None, num_spare_rows=0, num_spare_cols=0):
|
||||
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
|
||||
|
|
@ -38,6 +39,11 @@ class sram_config:
|
|||
except ImportError:
|
||||
self.array_col_multiple = 1
|
||||
|
||||
if not self.num_spare_cols:
|
||||
self.num_spare_cols = 0
|
||||
|
||||
if not self.num_spare_rows:
|
||||
self.num_spare_rows = 0
|
||||
|
||||
# This will get over-written when we determine the organization
|
||||
self.words_per_row = words_per_row
|
||||
|
|
@ -175,3 +181,43 @@ class sram_config:
|
|||
return int(words_per_row * tentative_num_rows / 16)
|
||||
|
||||
return words_per_row
|
||||
|
||||
def setup_multiport_constants(self):
|
||||
"""
|
||||
These are contants and lists that aid multiport design.
|
||||
Ports are always in the order RW, W, R.
|
||||
Port indices start from 0 and increment.
|
||||
A first RW port will have clk0, csb0, web0, addr0, data0
|
||||
A first W port (with no RW ports) will be: clk0, csb0, addr0, data0
|
||||
|
||||
"""
|
||||
total_ports = OPTS.num_rw_ports + OPTS.num_w_ports + OPTS.num_r_ports
|
||||
|
||||
# These are the read/write port indices.
|
||||
self.readwrite_ports = []
|
||||
# These are the read/write and write-only port indices
|
||||
self.write_ports = []
|
||||
# These are the write-only port indices.
|
||||
self.writeonly_ports = []
|
||||
# These are the read/write and read-only port indices
|
||||
self.read_ports = []
|
||||
# These are the read-only port indices.
|
||||
self.readonly_ports = []
|
||||
# These are all the ports
|
||||
self.all_ports = list(range(total_ports))
|
||||
|
||||
# The order is always fixed as RW, W, R
|
||||
port_number = 0
|
||||
for port in range(OPTS.num_rw_ports):
|
||||
self.readwrite_ports.append(port_number)
|
||||
self.write_ports.append(port_number)
|
||||
self.read_ports.append(port_number)
|
||||
port_number += 1
|
||||
for port in range(OPTS.num_w_ports):
|
||||
self.write_ports.append(port_number)
|
||||
self.writeonly_ports.append(port_number)
|
||||
port_number += 1
|
||||
for port in range(OPTS.num_r_ports):
|
||||
self.read_ports.append(port_number)
|
||||
self.readonly_ports.append(port_number)
|
||||
port_number += 1
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ class model_delay_test(openram_test):
|
|||
openram.init_openram(config_file, is_unit_test=True)
|
||||
OPTS.analytical_delay = False
|
||||
OPTS.netlist_only = True
|
||||
OPTS.spice_name = "Xyce"
|
||||
OPTS.num_sim_threads = 8
|
||||
|
||||
# This is a hack to reload the characterizer __init__ with the spice version
|
||||
from importlib import reload
|
||||
|
|
|
|||
|
|
@ -61,35 +61,36 @@ class timing_sram_test(openram_test):
|
|||
data.update(port_data[0])
|
||||
|
||||
if OPTS.tech_name == "freepdk45":
|
||||
golden_data = {'slew_lh': [0.2592187],
|
||||
'slew_hl': [0.2592187],
|
||||
'delay_lh': [0.2465583],
|
||||
'disabled_write0_power': [0.1924678],
|
||||
'disabled_read0_power': [0.152483],
|
||||
'write0_power': [0.3409064],
|
||||
'disabled_read1_power': [0.1737818],
|
||||
'read0_power': [0.3096708],
|
||||
'read1_power': [0.3107916],
|
||||
'delay_hl': [0.2465583],
|
||||
'write1_power': [0.26915849999999997],
|
||||
'leakage_power': 0.002044307,
|
||||
'min_period': 0.898,
|
||||
'disabled_write1_power': [0.201411]}
|
||||
golden_data = {'delay_hl': [0.263898],
|
||||
'delay_lh': [0.263898],
|
||||
'disabled_read0_power': [0.06625703],
|
||||
'disabled_read1_power': [0.07531121],
|
||||
'disabled_write0_power': [0.09350641999999999],
|
||||
'disabled_write1_power': [0.09988823000000001],
|
||||
'leakage_power': 0.01192385,
|
||||
'min_period': 2.031,
|
||||
'read0_power': [0.14745439999999999],
|
||||
'read1_power': [0.1470831],
|
||||
'slew_hl': [0.027165],
|
||||
'slew_lh': [0.027165],
|
||||
'write0_power': [0.1630546],
|
||||
'write1_power': [0.1319501]}
|
||||
elif OPTS.tech_name == "scn4m_subm":
|
||||
golden_data = {'read1_power': [12.11658],
|
||||
'write1_power': [10.52653],
|
||||
'read0_power': [11.956710000000001],
|
||||
'disabled_write0_power': [7.673665],
|
||||
'disabled_write1_power': [7.981922000000001],
|
||||
'slew_lh': [1.868836],
|
||||
'slew_hl': [1.868836],
|
||||
'delay_hl': [1.8598510000000001],
|
||||
'delay_lh': [1.8598510000000001],
|
||||
'leakage_power': 0.005457728,
|
||||
'disabled_read0_power': [5.904712],
|
||||
'min_period': 6.875,
|
||||
'disabled_read1_power': [7.132159],
|
||||
'write0_power': [13.406400000000001]}
|
||||
golden_data = {'delay_hl': [1.8259260000000002],
|
||||
'delay_lh': [1.8259260000000002],
|
||||
'disabled_read0_power': [6.722809],
|
||||
'disabled_read1_power': [8.104113],
|
||||
'disabled_write0_power': [8.900671],
|
||||
'disabled_write1_power': [9.188668],
|
||||
'leakage_power': 0.6977637,
|
||||
'min_period': 6.562,
|
||||
'read0_power': [15.45948],
|
||||
'read1_power': [15.48587],
|
||||
'slew_hl': [0.1936536],
|
||||
'slew_lh': [0.1936536],
|
||||
'write0_power': [17.03442],
|
||||
'write1_power': [13.05424]}
|
||||
|
||||
else:
|
||||
self.assertTrue(False) # other techs fail
|
||||
|
||||
|
|
|
|||
|
|
@ -69,35 +69,36 @@ class timing_sram_test(openram_test):
|
|||
data.update(port_data[0])
|
||||
|
||||
if OPTS.tech_name == "freepdk45":
|
||||
golden_data = {'delay_hl': [0.24671600000000002],
|
||||
'delay_lh': [0.24671600000000002],
|
||||
'disabled_read0_power': [0.1749204],
|
||||
'disabled_read1_power': [0.1873704],
|
||||
'disabled_write0_power': [0.204619],
|
||||
'disabled_write1_power': [0.2262653],
|
||||
'leakage_power': 0.0021375310000000002,
|
||||
'min_period': 0.977,
|
||||
'read0_power': [0.3856875],
|
||||
'read1_power': [0.38856060000000003],
|
||||
'slew_hl': [0.2842019],
|
||||
'slew_lh': [0.2842019],
|
||||
'write0_power': [0.45274410000000004],
|
||||
'write1_power': [0.38727789999999995]}
|
||||
golden_data = {'delay_hl': [0.2764415],
|
||||
'delay_lh': [0.2764415],
|
||||
'disabled_read0_power': [0.18364834],
|
||||
'disabled_read1_power': [0.20878333999999998],
|
||||
'disabled_write0_power': [0.24064433999999998],
|
||||
'disabled_write1_power': [0.27207664],
|
||||
'leakage_power': 0.0443369,
|
||||
'min_period': 0.938,
|
||||
'read0_power': [0.37790994],
|
||||
'read1_power': [0.37646214],
|
||||
'slew_hl': [0.0266144],
|
||||
'slew_lh': [0.0266144],
|
||||
'write0_power': [0.44694044],
|
||||
'write1_power': [0.36824544000000003]}
|
||||
elif OPTS.tech_name == "scn4m_subm":
|
||||
golden_data = {'delay_hl': [1.882508],
|
||||
'delay_lh': [1.882508],
|
||||
'disabled_read0_power': [7.487227],
|
||||
'disabled_read1_power': [8.749013],
|
||||
'disabled_write0_power': [9.268901],
|
||||
'disabled_write1_power': [9.962973],
|
||||
'leakage_power': 0.0046686359999999994,
|
||||
'min_period': 7.188,
|
||||
'read0_power': [16.64011],
|
||||
'read1_power': [17.20825],
|
||||
'slew_hl': [2.039655],
|
||||
'slew_lh': [2.039655],
|
||||
'write0_power': [19.31883],
|
||||
'write1_power': [15.297369999999999]}
|
||||
golden_data = {'delay_hl': [1.905376],
|
||||
'delay_lh': [1.905376],
|
||||
'disabled_read0_power': [7.673850999999999],
|
||||
'disabled_read1_power': [10.051073],
|
||||
'disabled_write0_power': [10.638803],
|
||||
'disabled_write1_power': [10.385253],
|
||||
'leakage_power': 2.704021,
|
||||
'min_period': 6.875,
|
||||
'read0_power': [17.583853],
|
||||
'read1_power': [17.689162999999997],
|
||||
'slew_hl': [0.19331199999999998],
|
||||
'slew_lh': [0.19331199999999998],
|
||||
'write0_power': [20.607043],
|
||||
'write1_power': [16.107403]}
|
||||
|
||||
else:
|
||||
self.assertTrue(False) # other techs fail
|
||||
|
||||
|
|
|
|||
|
|
@ -84,20 +84,20 @@ class timing_sram_test(openram_test):
|
|||
'write0_power': [0.429895901],
|
||||
'write1_power': [0.383337501]}
|
||||
elif OPTS.tech_name == "scn4m_subm":
|
||||
golden_data = {'delay_hl': [1.884186],
|
||||
'delay_lh': [1.884186],
|
||||
'disabled_read0_power': [20.86336],
|
||||
'disabled_read1_power': [22.10636],
|
||||
'disabled_write0_power': [22.62321],
|
||||
'disabled_write1_power': [23.316010000000002],
|
||||
'leakage_power': 13.351170000000002,
|
||||
'min_period': 7.188,
|
||||
'read0_power': [29.90159],
|
||||
'read1_power': [30.47858],
|
||||
'slew_hl': [2.042723],
|
||||
'slew_lh': [2.042723],
|
||||
'write0_power': [32.13199],
|
||||
'write1_power': [28.46703]}
|
||||
golden_data = {'delay_hl': [1.78586],
|
||||
'delay_lh': [1.78586],
|
||||
'disabled_read0_power': [7.8296693788],
|
||||
'disabled_read1_power': [9.1464723788],
|
||||
'disabled_write0_power': [9.6889073788],
|
||||
'disabled_write1_power': [10.4123023788],
|
||||
'leakage_power': 0.0002442851,
|
||||
'min_period': 6.875,
|
||||
'read0_power': [16.995952378800002],
|
||||
'read1_power': [17.5845523788],
|
||||
'slew_hl': [2.039202],
|
||||
'slew_lh': [2.039202],
|
||||
'write0_power': [19.785462378800002],
|
||||
'write1_power': [15.742192378799999]}
|
||||
else:
|
||||
self.assertTrue(False) # other techs fail
|
||||
# Check if no too many or too few results
|
||||
|
|
|
|||
|
|
@ -0,0 +1,99 @@
|
|||
#!/usr/bin/env python3
|
||||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 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, os, re
|
||||
import shutil
|
||||
import getpass
|
||||
import unittest
|
||||
from testutils import *
|
||||
|
||||
import openram
|
||||
from openram import debug
|
||||
from openram import OPTS
|
||||
|
||||
|
||||
class sram_char_test(openram_test):
|
||||
|
||||
def runTest(self):
|
||||
global OPTS
|
||||
out_file = "sram_2_16_1_{0}".format(OPTS.tech_name)
|
||||
out_path = "{0}/testsram_{1}_{2}_{3}".format(OPTS.openram_temp, OPTS.tech_name, getpass.getuser(), os.getpid())
|
||||
OPTS.output_name = out_file
|
||||
OPTS.output_path = out_path
|
||||
|
||||
OPENRAM_HOME = os.path.abspath(os.environ.get("OPENRAM_HOME"))
|
||||
config_file = "{}/tests/configs/config_mem_char_func".format(OPENRAM_HOME)
|
||||
|
||||
openram.init_openram(config_file, is_unit_test=False)
|
||||
sp_file = "{0}/tests/sp_files/{1}.sp".format(OPENRAM_HOME, OPTS.output_name)
|
||||
|
||||
debug.info(1, "Testing commandline characterizer script sram_char.py with 2-bit, 16 word SRAM.")
|
||||
|
||||
# make sure we start without the files existing
|
||||
if os.path.exists(out_path):
|
||||
shutil.rmtree(out_path, ignore_errors=True)
|
||||
self.assertEqual(os.path.exists(out_path), False)
|
||||
|
||||
try:
|
||||
os.makedirs(out_path, 0o0750)
|
||||
except OSError as e:
|
||||
if e.errno == 17: # errno.EEXIST
|
||||
os.chmod(out_path, 0o0750)
|
||||
|
||||
# specify the same verbosity for the system call
|
||||
options = ""
|
||||
for i in range(OPTS.verbose_level):
|
||||
options += " -v"
|
||||
|
||||
if OPTS.spice_name:
|
||||
options += " -s {}".format(OPTS.spice_name)
|
||||
|
||||
if OPTS.tech_name:
|
||||
options += " -t {}".format(OPTS.tech_name)
|
||||
|
||||
options += " -j 2"
|
||||
|
||||
# Always perform code coverage
|
||||
if OPTS.coverage == 0:
|
||||
debug.warning("Failed to find coverage installation. This can be installed with pip3 install coverage")
|
||||
exe_name = "{0}/../sram_char.py ".format(OPENRAM_HOME)
|
||||
else:
|
||||
exe_name = "{0}{1}/../sram_char.py ".format(OPTS.coverage_exe, OPENRAM_HOME)
|
||||
config_name = "{0}/tests/configs/config_mem_char_func.py".format(OPENRAM_HOME)
|
||||
cmd = "{0} -n -o {1} -p {2} {3} {4} {5} 2>&1 > {6}/output.log".format(exe_name,
|
||||
out_file,
|
||||
out_path,
|
||||
options,
|
||||
config_name,
|
||||
sp_file,
|
||||
out_path)
|
||||
debug.info(1, cmd)
|
||||
os.system(cmd)
|
||||
|
||||
# grep any errors from the output
|
||||
output_log = open("{0}/{1}.log".format(out_path, out_file), "r")
|
||||
output = output_log.read()
|
||||
output_log.close()
|
||||
self.assertEqual(len(re.findall('ERROR', output)), 0)
|
||||
self.assertEqual(len(re.findall('WARNING', output)), 0)
|
||||
|
||||
# now clean up the directory
|
||||
if not OPTS.keep_temp:
|
||||
if os.path.exists(out_path):
|
||||
shutil.rmtree(out_path, ignore_errors=True)
|
||||
self.assertEqual(os.path.exists(out_path), False)
|
||||
|
||||
openram.end_openram()
|
||||
|
||||
|
||||
# run the test from the command line
|
||||
if __name__ == "__main__":
|
||||
(OPTS, args) = openram.parse_args()
|
||||
del sys.argv[1:]
|
||||
header(__file__, OPTS.tech_name)
|
||||
unittest.main(testRunner=debugTestRunner())
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
#!/usr/bin/env python3
|
||||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 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, os, re
|
||||
import shutil
|
||||
import getpass
|
||||
import unittest
|
||||
from testutils import *
|
||||
|
||||
import openram
|
||||
from openram import debug
|
||||
from openram import OPTS
|
||||
|
||||
|
||||
class sram_func_test(openram_test):
|
||||
|
||||
def runTest(self):
|
||||
global OPTS
|
||||
out_file = "sram_2_16_1_{0}".format(OPTS.tech_name)
|
||||
out_path = "{0}/testsram_{1}_{2}_{3}".format(OPTS.openram_temp, OPTS.tech_name, getpass.getuser(), os.getpid())
|
||||
OPTS.output_name = out_file
|
||||
OPTS.output_path = out_path
|
||||
|
||||
OPENRAM_HOME = os.path.abspath(os.environ.get("OPENRAM_HOME"))
|
||||
config_file = "{}/tests/configs/config_mem_char_func".format(OPENRAM_HOME)
|
||||
|
||||
openram.init_openram(config_file, is_unit_test=False)
|
||||
sp_file = "{0}/tests/sp_files/{1}.sp".format(OPENRAM_HOME, OPTS.output_name)
|
||||
|
||||
debug.info(1, "Testing commandline functional simulator script sram_char.py with 2-bit, 16 word SRAM.")
|
||||
|
||||
# make sure we start without the files existing
|
||||
if os.path.exists(out_path):
|
||||
shutil.rmtree(out_path, ignore_errors=True)
|
||||
self.assertEqual(os.path.exists(out_path), False)
|
||||
|
||||
try:
|
||||
os.makedirs(out_path, 0o0750)
|
||||
except OSError as e:
|
||||
if e.errno == 17: # errno.EEXIST
|
||||
os.chmod(out_path, 0o0750)
|
||||
|
||||
# specify the same verbosity for the system call
|
||||
options = ""
|
||||
for i in range(OPTS.verbose_level):
|
||||
options += " -v"
|
||||
|
||||
if OPTS.spice_name:
|
||||
options += " -s {}".format(OPTS.spice_name)
|
||||
|
||||
if OPTS.tech_name:
|
||||
options += " -t {}".format(OPTS.tech_name)
|
||||
|
||||
options += " -j 2"
|
||||
|
||||
# Always perform code coverage
|
||||
if OPTS.coverage == 0:
|
||||
debug.warning("Failed to find coverage installation. This can be installed with pip3 install coverage")
|
||||
exe_name = "{0}/../sram_func.py ".format(OPENRAM_HOME)
|
||||
else:
|
||||
exe_name = "{0}{1}/../sram_func.py ".format(OPTS.coverage_exe, OPENRAM_HOME)
|
||||
config_name = "{0}/tests/configs/config_mem_char_func.py".format(OPENRAM_HOME)
|
||||
period_and_cycles = 10
|
||||
cmd = "{0} -n -o {1} -p {2} {3} {4} {5} {6} {6} 2>&1 > {7}/output.log".format(exe_name,
|
||||
out_file,
|
||||
out_path,
|
||||
options,
|
||||
config_name,
|
||||
sp_file,
|
||||
period_and_cycles,
|
||||
out_path)
|
||||
debug.info(1, cmd)
|
||||
os.system(cmd)
|
||||
|
||||
# grep any errors from the output
|
||||
output_log = open("{0}/output.log".format(out_path), "r")
|
||||
output = output_log.read()
|
||||
output_log.close()
|
||||
self.assertEqual(len(re.findall('ERROR', output)), 0)
|
||||
self.assertEqual(len(re.findall('WARNING', output)), 0)
|
||||
|
||||
# now clean up the directory
|
||||
if not OPTS.keep_temp:
|
||||
if os.path.exists(out_path):
|
||||
shutil.rmtree(out_path, ignore_errors=True)
|
||||
self.assertEqual(os.path.exists(out_path), False)
|
||||
|
||||
openram.end_openram()
|
||||
|
||||
# run the test from the command line
|
||||
if __name__ == "__main__":
|
||||
(OPTS, args) = openram.parse_args()
|
||||
del sys.argv[1:]
|
||||
header(__file__, OPTS.tech_name)
|
||||
unittest.main(testRunner=debugTestRunner())
|
||||
|
|
@ -180,6 +180,8 @@ BROKEN_STAMPS = \
|
|||
sky130/30_openram_back_end_test.ok \
|
||||
sky130/30_openram_front_end_library_test.ok \
|
||||
sky130/30_openram_front_end_test.ok \
|
||||
sky130/30_openram_sram_char_test.ok \
|
||||
sky130/30_openram_sram_func_test.ok \
|
||||
|
||||
gettech = $(word 1,$(subst /, ,$*))
|
||||
getfile = $(word 2,$(subst /, ,$*))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
BSIM3V3.1 Parameter Check
|
||||
Model = p
|
||||
W = 6e-07, L = 8e-07
|
||||
Warning: Pd = 0 is less than W.
|
||||
Warning: Ps = 0 is less than W.
|
||||
|
|
@ -5,10 +5,13 @@
|
|||
# (acting for and on behalf of Oklahoma State University)
|
||||
# All rights reserved.
|
||||
#
|
||||
from openram import OPTS
|
||||
word_size = 2
|
||||
num_words = 16
|
||||
|
||||
from enum import Enum
|
||||
|
||||
class bit_polarity(Enum):
|
||||
NONINVERTING = 0
|
||||
INVERTING = 1
|
||||
tech_name = OPTS.tech_name
|
||||
output_name = "sram"
|
||||
|
||||
analytical_delay = False
|
||||
nominal_corner_only = True
|
||||
spice_name = "Xyce"
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,2 @@
|
|||
ERROR: file magic.py: line 358: sram LVS mismatch (results in /tmp/openram_bugra_14157_temp/sram.lvs.report)
|
||||
|
||||
|
|
@ -46,7 +46,8 @@ Measures the timing/power through SPICE simulation:
|
|||
* Testing Support Modules
|
||||
* Other modules are derivatives of the simulation module used in the unit tests
|
||||
|
||||
|
||||
## Stand-alone Charaterizer
|
||||
The stand-alone characterizer is a script ([sram_char.py]()sram_char.py) that can be run without generating an SRAM.
|
||||
|
||||
## Characterization Options
|
||||
* Characterization by Configuration File
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
#!/usr/bin/env python3
|
||||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 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.
|
||||
#
|
||||
"""
|
||||
This script will characterize an SRAM previously generated by OpenRAM given a
|
||||
configuration file. Configuration option "use_pex" determines whether extracted
|
||||
or generated spice is used and option "analytical_delay" determines whether
|
||||
an analytical model or spice simulation is used for characterization.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import datetime
|
||||
|
||||
# You don't need the next two lines if you're sure that openram package is installed
|
||||
from common import *
|
||||
make_openram_package()
|
||||
import openram
|
||||
|
||||
(OPTS, args) = openram.parse_args()
|
||||
|
||||
# Override the usage
|
||||
USAGE = "Usage: {} [options] <config file> <spice netlist>\nUse -h for help.\n".format(__file__)
|
||||
|
||||
# Check that we are left with a single configuration file as argument.
|
||||
if len(args) != 2:
|
||||
print(USAGE)
|
||||
sys.exit(2)
|
||||
|
||||
OPTS.top_process = 'memchar'
|
||||
|
||||
# These depend on arguments, so don't load them until now.
|
||||
from openram import debug
|
||||
|
||||
# Parse config file and set up all the options
|
||||
openram.init_openram(config_file=args[0], is_unit_test=False)
|
||||
|
||||
openram.print_banner()
|
||||
|
||||
# Configure the SRAM organization (duplicated from openram.py)
|
||||
from openram.characterizer import fake_sram
|
||||
s = fake_sram(name=OPTS.output_name,
|
||||
word_size=OPTS.word_size,
|
||||
num_words=OPTS.num_words,
|
||||
write_size=OPTS.write_size,
|
||||
num_banks=OPTS.num_banks,
|
||||
words_per_row=OPTS.words_per_row,
|
||||
num_spare_rows=OPTS.num_spare_rows,
|
||||
num_spare_cols=OPTS.num_spare_cols)
|
||||
|
||||
debug.check(os.path.exists(args[1]), "Spice netlist file {} not found.".format(args[1]))
|
||||
sp_file = args[1]
|
||||
s.generate_pins()
|
||||
s.setup_multiport_constants()
|
||||
|
||||
OPTS.netlist_only = True
|
||||
OPTS.check_lvsdrc = False
|
||||
OPTS.nomimal_corner_only = True
|
||||
|
||||
# TODO: remove this after adding trimmed netlist gen to sram run
|
||||
OPTS.trim_netlist = False
|
||||
|
||||
# Characterize the design
|
||||
start_time = datetime.datetime.now()
|
||||
from openram.characterizer import lib
|
||||
debug.print_raw("LIB: Characterizing... ")
|
||||
lib(out_dir=OPTS.output_path, sram=s, sp_file=sp_file, use_model=False)
|
||||
print_time("Characterization", datetime.datetime.now(), start_time)
|
||||
|
||||
# Output info about this run
|
||||
print("Output files are:\n{0}*.lib".format(OPTS.output_path))
|
||||
#report_status() #could modify this function to provide relevant info
|
||||
|
||||
# Delete temp files, remove the dir, etc.
|
||||
openram.end_openram()
|
||||
|
|
@ -32,6 +32,8 @@ if len(args) != 1:
|
|||
print(openram.USAGE)
|
||||
sys.exit(2)
|
||||
|
||||
# Set top process to openram
|
||||
OPTS.top_process = 'openram'
|
||||
|
||||
# These depend on arguments, so don't load them until now.
|
||||
from openram import debug
|
||||
|
|
@ -76,4 +78,3 @@ s.save()
|
|||
# Delete temp files etc.
|
||||
openram.end_openram()
|
||||
openram.print_time("End", datetime.datetime.now(), start_time)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
#!/usr/bin/env python3
|
||||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 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.
|
||||
#
|
||||
"""
|
||||
This script will functionally simulate an SRAM previously generated by OpenRAM
|
||||
given a configuration file. Configuration option "use_pex" determines whether
|
||||
extracted or generated spice is used. Command line arguments dictate the
|
||||
number of cycles and period to be simulated.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import datetime
|
||||
|
||||
# You don't need the next two lines if you're sure that openram package is installed
|
||||
from common import *
|
||||
make_openram_package()
|
||||
import openram
|
||||
|
||||
(OPTS, args) = openram.parse_args()
|
||||
|
||||
# Override the usage
|
||||
USAGE = "Usage: {} [options] <config file> <sp_file> <cycles> <period>\nUse -h for help.\n".format(__file__)
|
||||
|
||||
# Check that we are left with a single configuration file as argument.
|
||||
if len(args) != 4:
|
||||
print(USAGE)
|
||||
sys.exit(2)
|
||||
|
||||
OPTS.top_process = 'memfunc'
|
||||
|
||||
# Parse argument
|
||||
config_file = args[0]
|
||||
sp_file = args[1]
|
||||
cycles = int(args[2])
|
||||
period = float(args[3])
|
||||
|
||||
# These depend on arguments, so don't load them until now.
|
||||
from openram import debug
|
||||
|
||||
# Parse config file and set up all the options
|
||||
openram.init_openram(config_file=config_file, is_unit_test=False)
|
||||
|
||||
openram.print_banner()
|
||||
|
||||
# Configure the SRAM organization (duplicated from openram.py)
|
||||
from openram.characterizer.fake_sram import fake_sram
|
||||
s = fake_sram(name=OPTS.output_name,
|
||||
word_size=OPTS.word_size,
|
||||
num_words=OPTS.num_words,
|
||||
write_size=OPTS.write_size,
|
||||
num_banks=OPTS.num_banks,
|
||||
words_per_row=OPTS.words_per_row,
|
||||
num_spare_rows=OPTS.num_spare_rows,
|
||||
num_spare_cols=OPTS.num_spare_cols)
|
||||
|
||||
s.generate_pins()
|
||||
s.setup_multiport_constants()
|
||||
|
||||
OPTS.netlist_only = True
|
||||
OPTS.check_lvsdrc = False
|
||||
|
||||
# Generate stimulus and run functional simulation on the design
|
||||
start_time = datetime.datetime.now()
|
||||
from openram.characterizer import functional
|
||||
debug.print_raw("Functional simulation... ")
|
||||
f = functional(s, cycles=cycles, spfile=sp_file, period=period, output_path=OPTS.openram_temp)
|
||||
(fail, error) = f.run()
|
||||
debug.print_raw(error)
|
||||
openram.print_time("Functional simulation", datetime.datetime.now(), start_time)
|
||||
|
||||
# Delete temp files, remove the dir, etc. after success
|
||||
if fail:
|
||||
openram.end_openram()
|
||||
Binary file not shown.
|
|
@ -1,11 +1,16 @@
|
|||
|
||||
.SUBCKT sense_amp bl br dout en vdd gnd
|
||||
M_1 dout net_1 vdd vdd pmos_vtg w=540.0n l=50.0n
|
||||
M_3 net_1 dout vdd vdd pmos_vtg w=540.0n l=50.0n
|
||||
M_2 dout net_1 net_2 gnd nmos_vtg w=270.0n l=50.0n
|
||||
M_8 net_1 dout net_2 gnd nmos_vtg w=270.0n l=50.0n
|
||||
M_5 bl en dout vdd pmos_vtg w=720.0n l=50.0n
|
||||
M_1 dint net_1 vdd vdd pmos_vtg w=540.0n l=50.0n
|
||||
M_3 net_1 dint vdd vdd pmos_vtg w=540.0n l=50.0n
|
||||
M_2 dint net_1 net_2 gnd nmos_vtg w=270.0n l=50.0n
|
||||
M_8 net_1 dint net_2 gnd nmos_vtg w=270.0n l=50.0n
|
||||
M_5 bl en dint vdd pmos_vtg w=720.0n l=50.0n
|
||||
M_6 br en net_1 vdd pmos_vtg w=720.0n l=50.0n
|
||||
M_7 net_2 en gnd gnd nmos_vtg w=270.0n l=50.0n
|
||||
|
||||
M_9 dout_bar dint vdd vdd pmos_vtg w=180.0n l=50.0n
|
||||
M_10 dout_bar dint gnd gnd nmos_vtg w=90.0n l=50.0n
|
||||
M_11 dout dout_bar vdd vdd pmos_vtg w=540.0n l=50.0n
|
||||
M_12 dout dout_bar gnd gnd nmos_vtg w=270.0n l=50.0n
|
||||
.ENDS sense_amp
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1,136 +1,183 @@
|
|||
magic
|
||||
tech scmos
|
||||
timestamp 1536089670
|
||||
timestamp 1681333912
|
||||
<< nwell >>
|
||||
rect 0 0 40 102
|
||||
rect 0 28 40 153
|
||||
<< pwell >>
|
||||
rect 0 102 40 163
|
||||
rect 0 153 40 214
|
||||
rect 0 0 40 28
|
||||
<< ntransistor >>
|
||||
rect 21 130 23 139
|
||||
rect 12 108 14 117
|
||||
rect 20 108 22 117
|
||||
rect 21 181 23 190
|
||||
rect 12 159 14 168
|
||||
rect 20 159 22 168
|
||||
rect 13 10 15 22
|
||||
rect 21 18 23 22
|
||||
<< ptransistor >>
|
||||
rect 12 78 14 96
|
||||
rect 20 78 22 96
|
||||
rect 11 20 13 44
|
||||
rect 27 20 29 44
|
||||
rect 12 129 14 147
|
||||
rect 20 129 22 147
|
||||
rect 11 71 13 95
|
||||
rect 27 71 29 95
|
||||
rect 13 34 15 58
|
||||
rect 21 34 23 42
|
||||
<< ndiffusion >>
|
||||
rect 20 130 21 139
|
||||
rect 23 130 24 139
|
||||
rect 11 108 12 117
|
||||
rect 14 108 15 117
|
||||
rect 19 108 20 117
|
||||
rect 22 108 23 117
|
||||
rect 20 181 21 190
|
||||
rect 23 181 24 190
|
||||
rect 11 159 12 168
|
||||
rect 14 159 15 168
|
||||
rect 19 159 20 168
|
||||
rect 22 159 23 168
|
||||
rect 12 10 13 22
|
||||
rect 15 10 16 22
|
||||
rect 20 18 21 22
|
||||
rect 23 18 24 22
|
||||
<< pdiffusion >>
|
||||
rect 7 94 12 96
|
||||
rect 11 80 12 94
|
||||
rect 7 78 12 80
|
||||
rect 14 94 20 96
|
||||
rect 14 80 15 94
|
||||
rect 19 80 20 94
|
||||
rect 14 78 20 80
|
||||
rect 22 94 27 96
|
||||
rect 22 80 23 94
|
||||
rect 22 78 27 80
|
||||
rect 10 20 11 44
|
||||
rect 13 20 14 44
|
||||
rect 26 20 27 44
|
||||
rect 29 20 30 44
|
||||
rect 7 145 12 147
|
||||
rect 11 131 12 145
|
||||
rect 7 129 12 131
|
||||
rect 14 145 20 147
|
||||
rect 14 131 15 145
|
||||
rect 19 131 20 145
|
||||
rect 14 129 20 131
|
||||
rect 22 145 27 147
|
||||
rect 22 131 23 145
|
||||
rect 22 129 27 131
|
||||
rect 10 71 11 95
|
||||
rect 13 71 14 95
|
||||
rect 26 71 27 95
|
||||
rect 29 71 30 95
|
||||
rect 12 34 13 58
|
||||
rect 15 34 16 58
|
||||
rect 20 34 21 42
|
||||
rect 23 34 24 42
|
||||
<< ndcontact >>
|
||||
rect 16 130 20 139
|
||||
rect 24 130 28 139
|
||||
rect 7 108 11 117
|
||||
rect 15 108 19 117
|
||||
rect 23 108 27 117
|
||||
rect 16 181 20 190
|
||||
rect 24 181 28 190
|
||||
rect 7 159 11 168
|
||||
rect 15 159 19 168
|
||||
rect 23 159 27 168
|
||||
rect 8 10 12 22
|
||||
rect 16 10 20 22
|
||||
rect 24 18 28 22
|
||||
<< pdcontact >>
|
||||
rect 7 80 11 94
|
||||
rect 15 80 19 94
|
||||
rect 23 80 27 94
|
||||
rect 6 20 10 44
|
||||
rect 14 20 18 44
|
||||
rect 22 20 26 44
|
||||
rect 30 20 34 44
|
||||
rect 7 131 11 145
|
||||
rect 15 131 19 145
|
||||
rect 23 131 27 145
|
||||
rect 6 71 10 95
|
||||
rect 14 71 18 95
|
||||
rect 22 71 26 95
|
||||
rect 30 71 34 95
|
||||
rect 8 34 12 58
|
||||
rect 16 34 20 58
|
||||
rect 24 34 28 42
|
||||
<< psubstratepcontact >>
|
||||
rect 32 137 36 141
|
||||
rect 32 188 36 192
|
||||
rect 32 13 36 17
|
||||
<< nsubstratencontact >>
|
||||
rect 27 70 31 74
|
||||
rect 27 121 31 125
|
||||
rect 27 55 31 59
|
||||
<< polysilicon >>
|
||||
rect 21 139 23 149
|
||||
rect 21 129 23 130
|
||||
rect 3 127 23 129
|
||||
rect 3 47 5 127
|
||||
rect 12 122 34 124
|
||||
rect 12 117 14 122
|
||||
rect 20 117 22 119
|
||||
rect 12 96 14 108
|
||||
rect 20 96 22 108
|
||||
rect 32 105 34 122
|
||||
rect 30 101 34 105
|
||||
rect 12 76 14 78
|
||||
rect 20 69 22 78
|
||||
rect 13 67 22 69
|
||||
rect 9 55 11 65
|
||||
rect 32 55 34 101
|
||||
rect 33 51 34 55
|
||||
rect 3 45 13 47
|
||||
rect 11 44 13 45
|
||||
rect 27 44 29 46
|
||||
rect 11 19 13 20
|
||||
rect 27 19 29 20
|
||||
rect 11 17 29 19
|
||||
rect 21 190 23 200
|
||||
rect 21 180 23 181
|
||||
rect 3 178 23 180
|
||||
rect 3 98 5 178
|
||||
rect 12 173 34 175
|
||||
rect 12 168 14 173
|
||||
rect 20 168 22 170
|
||||
rect 12 147 14 159
|
||||
rect 20 147 22 159
|
||||
rect 32 156 34 173
|
||||
rect 30 152 34 156
|
||||
rect 12 127 14 129
|
||||
rect 20 120 22 129
|
||||
rect 13 118 22 120
|
||||
rect 9 106 11 116
|
||||
rect 32 106 34 152
|
||||
rect 33 102 34 106
|
||||
rect 3 96 13 98
|
||||
rect 11 95 13 96
|
||||
rect 27 95 29 97
|
||||
rect 11 70 13 71
|
||||
rect 27 70 29 71
|
||||
rect 11 68 29 70
|
||||
rect 7 63 23 65
|
||||
rect 13 58 15 60
|
||||
rect 21 42 23 63
|
||||
rect 13 31 15 34
|
||||
rect 13 27 14 31
|
||||
rect 13 22 15 27
|
||||
rect 21 22 23 34
|
||||
rect 21 16 23 18
|
||||
rect 13 8 15 10
|
||||
<< polycontact >>
|
||||
rect 20 149 24 153
|
||||
rect 26 101 30 105
|
||||
rect 9 65 13 69
|
||||
rect 9 51 13 55
|
||||
rect 29 51 33 55
|
||||
rect 20 200 24 204
|
||||
rect 26 152 30 156
|
||||
rect 9 116 13 120
|
||||
rect 9 102 13 106
|
||||
rect 29 102 33 106
|
||||
rect 3 63 7 67
|
||||
rect 14 27 18 31
|
||||
<< metal1 >>
|
||||
rect -2 149 20 153
|
||||
rect 24 149 36 153
|
||||
rect 28 133 32 137
|
||||
rect 16 117 19 130
|
||||
rect 7 94 11 108
|
||||
rect 23 105 27 108
|
||||
rect 23 101 26 105
|
||||
rect 7 69 11 80
|
||||
rect 15 94 19 96
|
||||
rect 15 78 19 80
|
||||
rect 23 94 27 101
|
||||
rect 23 78 27 80
|
||||
rect 15 75 18 78
|
||||
rect 15 74 31 75
|
||||
rect 15 72 27 74
|
||||
rect 7 65 9 69
|
||||
rect 6 44 9 54
|
||||
rect 33 51 34 55
|
||||
rect 31 44 34 51
|
||||
rect 3 20 6 23
|
||||
rect 3 15 7 20
|
||||
rect -2 200 20 204
|
||||
rect 24 200 36 204
|
||||
rect 28 184 32 188
|
||||
rect 16 168 19 181
|
||||
rect 7 145 11 159
|
||||
rect 23 156 27 159
|
||||
rect 23 152 26 156
|
||||
rect 7 120 11 131
|
||||
rect 15 145 19 147
|
||||
rect 15 129 19 131
|
||||
rect 23 145 27 152
|
||||
rect 23 129 27 131
|
||||
rect 15 126 18 129
|
||||
rect 15 125 31 126
|
||||
rect 15 123 27 125
|
||||
rect 7 116 9 120
|
||||
rect 6 95 9 105
|
||||
rect 33 102 34 106
|
||||
rect 31 95 34 102
|
||||
rect 3 71 6 74
|
||||
rect 3 67 7 71
|
||||
rect 20 55 27 58
|
||||
rect 8 22 11 34
|
||||
rect 24 30 28 34
|
||||
rect 18 27 28 30
|
||||
rect 24 22 28 27
|
||||
rect 20 13 32 15
|
||||
rect 20 12 36 13
|
||||
rect 8 8 11 10
|
||||
rect 7 5 11 8
|
||||
<< m2contact >>
|
||||
rect 32 133 36 137
|
||||
rect 27 66 31 70
|
||||
rect 13 44 17 48
|
||||
rect 22 44 26 48
|
||||
rect 3 11 7 15
|
||||
rect 32 184 36 188
|
||||
rect 27 117 31 121
|
||||
rect 13 95 17 99
|
||||
rect 22 95 26 99
|
||||
rect 27 51 31 55
|
||||
rect 32 17 36 21
|
||||
rect 3 4 7 8
|
||||
<< metal2 >>
|
||||
rect 10 48 14 163
|
||||
rect 20 48 24 163
|
||||
rect 32 129 36 133
|
||||
rect 27 62 31 66
|
||||
rect 10 44 13 48
|
||||
rect 20 44 22 48
|
||||
rect 3 0 7 11
|
||||
rect 10 0 14 44
|
||||
rect 20 0 24 44
|
||||
rect 10 99 14 214
|
||||
rect 20 99 24 214
|
||||
rect 32 180 36 184
|
||||
rect 27 113 31 117
|
||||
rect 10 95 13 99
|
||||
rect 20 95 22 99
|
||||
rect 3 0 7 4
|
||||
rect 10 0 14 95
|
||||
rect 20 0 24 95
|
||||
rect 27 47 31 51
|
||||
rect 32 21 36 25
|
||||
<< bb >>
|
||||
rect 0 0 34 163
|
||||
rect 0 0 34 214
|
||||
<< labels >>
|
||||
flabel metal1 0 149 0 149 4 FreeSans 26 0 0 0 en
|
||||
rlabel metal2 34 131 34 131 1 gnd
|
||||
rlabel metal2 29 64 29 64 1 vdd
|
||||
rlabel metal2 12 161 12 161 5 bl
|
||||
rlabel metal2 22 161 22 161 5 br
|
||||
rlabel metal2 5 3 5 3 1 dout
|
||||
rlabel metal2 5 2 5 2 1 dout
|
||||
rlabel metal2 29 49 29 49 1 vdd
|
||||
rlabel metal2 22 212 22 212 5 br
|
||||
rlabel metal2 12 212 12 212 5 bl
|
||||
rlabel metal2 29 115 29 115 1 vdd
|
||||
rlabel metal2 34 182 34 182 1 gnd
|
||||
flabel metal1 0 200 0 200 4 FreeSans 26 0 0 0 en
|
||||
rlabel metal2 34 23 34 23 1 gnd
|
||||
<< properties >>
|
||||
string path 270.000 468.000 270.000 486.000 288.000 486.000 288.000 468.000 270.000 468.000
|
||||
<< end >>
|
||||
|
|
|
|||
|
|
@ -5,11 +5,15 @@
|
|||
* SPICE3 file created from sense_amp.ext - technology: scmos
|
||||
|
||||
M1000 gnd en a_56_432# gnd n w=1.8u l=0.4u
|
||||
M1001 a_56_432# a_48_304# dout gnd n w=1.8u l=0.4u
|
||||
M1002 a_48_304# dout a_56_432# gnd n w=1.8u l=0.4u
|
||||
M1003 vdd a_48_304# dout vdd p w=3.6u l=0.4u
|
||||
M1004 a_48_304# dout vdd vdd p w=3.6u l=0.4u
|
||||
M1005 bl en dout vdd p w=4.8u l=0.4u
|
||||
M1001 a_56_432# a_48_304# dint gnd n w=1.8u l=0.4u
|
||||
M1002 a_48_304# dint a_56_432# gnd n w=1.8u l=0.4u
|
||||
M1003 vdd a_48_304# dint vdd p w=3.6u l=0.4u
|
||||
M1004 a_48_304# dint vdd vdd p w=3.6u l=0.4u
|
||||
M1005 bl en dint vdd p w=4.8u l=0.4u
|
||||
M1006 a_48_304# en br vdd p w=4.8u l=0.4u
|
||||
|
||||
M1007 dout_bar dint vdd vdd p w=1.6u l=0.4u
|
||||
M1008 gnd dint dout_bar gnd n w=0.8u l=0.4u
|
||||
M1009 dout dout_bar vdd vdd p w=4.8u l=0.4u
|
||||
M1010 gnd dout_bar dout gnd n w=2.4u l=0.4u
|
||||
.ENDS
|
||||
|
|
|
|||
Loading…
Reference in New Issue