Functional tests working with new RBL.

This commit is contained in:
mrg 2019-07-12 08:42:36 -07:00
parent 0b13225913
commit 043018e8ba
18 changed files with 152 additions and 113 deletions

View File

@ -71,7 +71,12 @@ class bitcell(design.design):
"""Get bl name"""
debug.check(port==0,"One port for bitcell only.")
return "br"
def get_wl_name(self, port=0):
"""Get wl name"""
debug.check(port==0,"One port for bitcell only.")
return "wl"
def analytical_power(self, corner, load):
"""Bitcell power in nW. Only characterizes leakage."""
from tech import spice

View File

@ -104,6 +104,11 @@ class bitcell_1rw_1r(design.design):
"""Get bl name by port"""
debug.check(port<2,"Two ports for bitcell_1rw_1r only.")
return "br{}".format(port)
def get_wl_name(self, port=0):
"""Get wl name by port"""
debug.check(port<2,"Two ports for bitcell_1rw_1r only.")
return "wl{}".format(port)
def analytical_power(self, corner, load):
"""Bitcell power in nW. Only characterizes leakage."""

View File

@ -103,6 +103,11 @@ class bitcell_1w_1r(design.design):
"""Get bl name by port"""
return "br{}".format(port)
def get_wl_name(self, port=0):
"""Get wl name by port"""
debug.check(port<2,"Two ports for bitcell_1rw_1r only.")
return "wl{}".format(port)
def analytical_power(self, corner, load):
"""Bitcell power in nW. Only characterizes leakage."""
from tech import spice

View File

@ -902,7 +902,13 @@ class pbitcell(design.design):
def get_br_name(self, port=0):
"""Get bl name by port"""
return "br{}".format(port)
return "br{}".format(port)
def get_wl_name(self, port=0):
"""Get wl name by port"""
debug.check(port<2,"Two ports for bitcell_1rw_1r only.")
return "wl{}".format(port)
def analytical_delay(self, corner, slew, load=0, swing = 0.5):
parasitic_delay = 1

View File

@ -78,6 +78,9 @@ class bank(design.design):
for port in self.read_ports:
for bit in range(self.word_size):
self.add_pin("dout{0}_{1}".format(port,bit),"OUT")
self.add_pin("rbl_bl{0}_{0}".format(port),"OUT")
for port in self.read_ports:
self.add_pin("rbl_wl{0}_{0}".format(port),"IN")
for port in self.write_ports:
for bit in range(self.word_size):
self.add_pin("din{0}_{1}".format(port,bit),"IN")
@ -108,6 +111,7 @@ class bank(design.design):
for port in self.all_ports:
self.route_bitlines(port)
self.route_rbl(port)
self.route_port_address(port)
self.route_column_address_lines(port)
self.route_control_lines(port)
@ -116,6 +120,20 @@ class bank(design.design):
self.route_supplies()
def route_rbl(self,port):
""" Route the rbl_bl and rbl_wl """
if self.port_data[port].has_rbl():
bl_name = self.bitcell.get_bl_name(port)
bl_pin = self.bitcell_array_inst.get_pin("rbl_{0}_{1}".format(bl_name,port))
self.add_layout_pin(text="rbl_bl{0}".format(port),
layer=bl_pin.layer,
offset=bl_pin.ll(),
height=bl_pin.height(),
width=bl_pin.width())
def route_bitlines(self, port):
""" Route the bitlines depending on the port type rw, w, or r. """
@ -281,13 +299,13 @@ class bank(design.design):
self.input_control_signals = []
port_num = 0
for port in range(OPTS.num_rw_ports):
self.input_control_signals.append(["wl_en{}".format(port_num), "w_en{}".format(port_num), "s_en{}".format(port_num), "p_en_bar{}".format(port_num)])
self.input_control_signals.append(["wl_en{}".format(port_num), "w_en{}".format(port_num), "s_en{}".format(port_num), "p_en_bar{}".format(port_num), "rbl_wl{}".format(port_num)])
port_num += 1
for port in range(OPTS.num_w_ports):
self.input_control_signals.append(["wl_en{}".format(port_num), "w_en{}".format(port_num)])
port_num += 1
for port in range(OPTS.num_r_ports):
self.input_control_signals.append(["wl_en{}".format(port_num), "s_en{}".format(port_num), "p_en_bar{}".format(port_num)])
self.input_control_signals.append(["wl_en{}".format(port_num), "s_en{}".format(port_num), "p_en_bar{}".format(port_num), "rbl_wl{}".format(port_num)])
port_num += 1
# Number of control lines in the bus for each port
@ -904,7 +922,10 @@ class bank(design.design):
connection = []
if port in self.read_ports:
connection.append((self.prefix+"p_en_bar{}".format(port), self.port_data_inst[port].get_pin("p_en_bar").lc()))
if port in self.read_ports:
connection.append((self.prefix+"rbl_wl{}".format(port), self.bitcell_array_inst.get_pin("rbl_{0}_{1}".format(self.bitcell.get_wl_name(port),port)).lc()))
if port in self.write_ports:
connection.append((self.prefix+"w_en{}".format(port), self.port_data_inst[port].get_pin("w_en").lc()))

View File

@ -39,7 +39,7 @@ class control_logic(design.design):
self.num_cols = word_size*words_per_row
self.num_words = num_rows*words_per_row
self.enable_delay_chain_resizing = True
self.enable_delay_chain_resizing = False
self.inv_parasitic_delay = logical_effort.logical_effort.pinv
#Determines how much larger the sen delay should be. Accounts for possible error in model.
@ -73,10 +73,8 @@ class control_logic(design.design):
def add_pins(self):
""" Add the pins to the control logic module. """
for pin in self.input_list + ["clk"]:
self.add_pin(pin,"INPUT")
for pin in self.output_list:
self.add_pin(pin,"OUTPUT")
self.add_pin_list(self.input_list + ["clk"] + self.rbl_list, "INPUT")
self.add_pin_list(self.output_list,"OUTPUT")
self.add_pin("vdd","POWER")
self.add_pin("gnd","GROUND")
@ -139,46 +137,48 @@ class control_logic(design.design):
height=dff_height)
self.add_mod(self.p_en_bar_driver)
if (self.port_type == "rw") or (self.port_type == "r"):
from importlib import reload
self.delay_chain_resized = False
c = reload(__import__(OPTS.replica_bitline))
replica_bitline = getattr(c, OPTS.replica_bitline)
bitcell_loads = int(math.ceil(self.num_rows * OPTS.rbl_delay_percentage))
#Use a model to determine the delays with that heuristic
if OPTS.use_tech_delay_chain_size: #Use tech parameters if set.
fanout_list = OPTS.delay_chain_stages*[OPTS.delay_chain_fanout_per_stage]
debug.info(1, "Using tech parameters to size delay chain: fanout_list={}".format(fanout_list))
self.replica_bitline = factory.create(module_type="replica_bitline",
delay_fanout_list=fanout_list,
bitcell_loads=bitcell_loads)
if self.sram != None: #Calculate model value even for specified sizes
self.set_sen_wl_delays()
# if (self.port_type == "rw") or (self.port_type == "r"):
# from importlib import reload
# self.delay_chain_resized = False
# c = reload(__import__(OPTS.replica_bitline))
# replica_bitline = getattr(c, OPTS.replica_bitline)
# bitcell_loads = int(math.ceil(self.num_rows * OPTS.rbl_delay_percentage))
# #Use a model to determine the delays with that heuristic
# if OPTS.use_tech_delay_chain_size: #Use tech parameters if set.
# fanout_list = OPTS.delay_chain_stages*[OPTS.delay_chain_fanout_per_stage]
# debug.info(1, "Using tech parameters to size delay chain: fanout_list={}".format(fanout_list))
# self.replica_bitline = factory.create(module_type="replica_bitline",
# delay_fanout_list=fanout_list,
# bitcell_loads=bitcell_loads)
# if self.sram != None: #Calculate model value even for specified sizes
# self.set_sen_wl_delays()
else: #Otherwise, use a heuristic and/or model based sizing.
#First use a heuristic
delay_stages_heuristic, delay_fanout_heuristic = self.get_heuristic_delay_chain_size()
self.replica_bitline = factory.create(module_type="replica_bitline",
delay_fanout_list=[delay_fanout_heuristic]*delay_stages_heuristic,
bitcell_loads=bitcell_loads)
#Resize if necessary, condition depends on resizing method
if self.sram != None and self.enable_delay_chain_resizing and not self.does_sen_rise_fall_timing_match():
#This resizes to match fall and rise delays, can make the delay chain weird sizes.
stage_list = self.get_dynamic_delay_fanout_list(delay_stages_heuristic, delay_fanout_heuristic)
self.replica_bitline = factory.create(module_type="replica_bitline",
delay_fanout_list=stage_list,
bitcell_loads=bitcell_loads)
# else: #Otherwise, use a heuristic and/or model based sizing.
# #First use a heuristic
# delay_stages_heuristic, delay_fanout_heuristic = self.get_heuristic_delay_chain_size()
# self.replica_bitline = factory.create(module_type="replica_bitline",
# delay_fanout_list=[delay_fanout_heuristic]*delay_stages_heuristic,
# bitcell_loads=bitcell_loads)
# #Resize if necessary, condition depends on resizing method
# if self.sram != None and self.enable_delay_chain_resizing and not self.does_sen_rise_fall_timing_match():
# #This resizes to match fall and rise delays, can make the delay chain weird sizes.
# stage_list = self.get_dynamic_delay_fanout_list(delay_stages_heuristic, delay_fanout_heuristic)
# self.replica_bitline = factory.create(module_type="replica_bitline",
# delay_fanout_list=stage_list,
# bitcell_loads=bitcell_loads)
#This resizes based on total delay.
# delay_stages, delay_fanout = self.get_dynamic_delay_chain_size(delay_stages_heuristic, delay_fanout_heuristic)
# self.replica_bitline = factory.create(module_type="replica_bitline",
# delay_fanout_list=[delay_fanout]*delay_stages,
# bitcell_loads=bitcell_loads)
# #This resizes based on total delay.
# # delay_stages, delay_fanout = self.get_dynamic_delay_chain_size(delay_stages_heuristic, delay_fanout_heuristic)
# # self.replica_bitline = factory.create(module_type="replica_bitline",
# # delay_fanout_list=[delay_fanout]*delay_stages,
# # bitcell_loads=bitcell_loads)
self.sen_delay_rise,self.sen_delay_fall = self.get_delays_to_sen() #get the new timing
self.delay_chain_resized = True
self.add_mod(self.replica_bitline)
# self.sen_delay_rise,self.sen_delay_fall = self.get_delays_to_sen() #get the new timing
# self.delay_chain_resized = True
self.delay_chain=factory.create(module_type="delay_chain",
fanout_list = OPTS.delay_chain_stages*[OPTS.delay_chain_fanout_per_stage])
self.add_mod(self.delay_chain)
def get_heuristic_delay_chain_size(self):
"""Use a basic heuristic to determine the size of the delay chain used for the Sense Amp Enable """
@ -312,8 +312,13 @@ class control_logic(design.design):
# List of input control signals
if self.port_type == "rw":
self.input_list = ["csb", "web"]
self.rbl_list = ["rbl_bl"]
elif self.port_type == "r":
self.input_list = ["csb"]
self.rbl_list = ["rbl_bl"]
else:
self.input_list = ["csb"]
self.rbl_list = []
if self.port_type == "rw":
self.dff_output_list = ["cs_bar", "cs", "we_bar", "we"]
@ -332,9 +337,9 @@ class control_logic(design.design):
# Outputs to the bank
if self.port_type == "rw":
self.output_list = ["s_en", "w_en", "p_en_bar"]
self.output_list = ["rbl_wl", "s_en", "w_en", "p_en_bar"]
elif self.port_type == "r":
self.output_list = ["s_en", "p_en_bar"]
self.output_list = ["rbl_wl", "s_en", "p_en_bar"]
else:
self.output_list = ["w_en"]
self.output_list.append("wl_en")
@ -361,11 +366,11 @@ class control_logic(design.design):
if (self.port_type == "rw") or (self.port_type == "w"):
self.create_wen_row()
if self.port_type == "rw":
self.create_rbl_in_row()
self.create_rbl_row()
if (self.port_type == "rw") or (self.port_type == "r"):
self.create_pen_row()
self.create_sen_row()
self.create_rbl()
self.create_delay()
def place_instances(self):
@ -395,17 +400,16 @@ class control_logic(design.design):
height = self.w_en_inst.uy()
control_center_y = self.w_en_inst.uy()
row += 1
if self.port_type == "rw":
self.place_rbl_in_row(row)
row += 1
if (self.port_type == "rw") or (self.port_type == "r"):
self.place_rbl_row(row)
row += 1
self.place_pen_row(row)
row += 1
self.place_sen_row(row)
row += 1
self.place_rbl(row)
height = self.rbl_inst.uy()
control_center_y = self.rbl_inst.by()
self.place_delay(row)
height = self.delay_inst.uy()
control_center_y = self.delay_inst.by()
# This offset is used for placement of the control logic in the SRAM level.
self.control_logic_center = vector(self.ctrl_dff_inst.rx(), control_center_y)
@ -415,7 +419,7 @@ class control_logic(design.design):
# Max of modules or logic rows
self.width = max([inst.rx() for inst in self.row_end_inst])
if (self.port_type == "rw") or (self.port_type == "r"):
self.width = max(self.rbl_inst.rx() , self.width)
self.width = max(self.delay_inst.rx() , self.width)
self.width += self.m2_pitch
def route_all(self):
@ -426,7 +430,7 @@ class control_logic(design.design):
if (self.port_type == "rw") or (self.port_type == "w"):
self.route_wen()
if (self.port_type == "rw") or (self.port_type == "r"):
self.route_rbl_in()
self.route_rbl()
self.route_pen()
self.route_sen()
self.route_clk_buf()
@ -435,24 +439,20 @@ class control_logic(design.design):
self.route_supply()
def create_rbl(self):
def create_delay(self):
""" Create the replica bitline """
if self.port_type == "r":
input_name = "gated_clk_bar"
else:
input_name = "rbl_in"
self.rbl_inst=self.add_inst(name="replica_bitline",
mod=self.replica_bitline)
self.connect_inst([input_name, "pre_s_en", "vdd", "gnd"])
self.delay_inst=self.add_inst(name="delay_chain",
mod=self.delay_chain)
self.connect_inst(["rbl_bl", "pre_s_en", "vdd", "gnd"])
def place_rbl(self,row):
def place_delay(self,row):
""" Place the replica bitline """
y_off = row * self.and2.height + 2*self.m1_pitch
# Add the RBL above the rows
# Add to the right of the control rows and routing channel
offset = vector(0, y_off)
self.rbl_inst.place(offset)
self.delay_inst.place(offset)
def create_clk_buf_row(self):
@ -588,44 +588,41 @@ class control_logic(design.design):
self.connect_vertical_bus(wlen_map, self.wl_en_inst, self.rail_offsets)
self.connect_output(self.wl_en_inst, "Z", "wl_en")
def create_rbl_in_row(self):
def create_rbl_row(self):
# input: gated_clk_bar, we_bar, output: rbl_in
self.rbl_in_inst=self.add_inst(name="and2_rbl_in",
self.rbl_inst=self.add_inst(name="and2_rbl",
mod=self.and2)
self.connect_inst(["gated_clk_bar", "we_bar", "rbl_in", "vdd", "gnd"])
self.connect_inst(["gated_clk_bar", "we_bar", "rbl_wl", "vdd", "gnd"])
def place_rbl_in_row(self,row):
def place_rbl_row(self,row):
x_off = self.control_x_offset
(y_off,mirror)=self.get_offset(row)
offset = vector(x_off, y_off)
self.rbl_in_inst.place(offset, mirror)
self.rbl_inst.place(offset, mirror)
self.row_end_inst.append(self.rbl_in_inst)
self.row_end_inst.append(self.rbl_inst)
def route_rbl_in(self):
def route_rbl(self):
""" Connect the logic for the rbl_in generation """
if self.port_type == "rw":
input_name = "we_bar"
# Connect the NAND gate inputs to the bus
rbl_in_map = zip(["A", "B"], ["gated_clk_bar", "we_bar"])
self.connect_vertical_bus(rbl_in_map, self.rbl_in_inst, self.rail_offsets)
self.connect_vertical_bus(rbl_in_map, self.rbl_inst, self.rail_offsets)
# Connect the output of the precharge enable to the RBL input
if self.port_type == "rw":
out_pos = self.rbl_in_inst.get_pin("Z").center()
else:
out_pos = vector(self.rail_offsets["gated_clk_bar"].x, self.rbl_inst.by()-3*self.m2_pitch)
in_pos = self.rbl_inst.get_pin("en").center()
mid1 = vector(in_pos.x,out_pos.y)
self.add_wire(("metal3","via2","metal2"),[out_pos, mid1, in_pos])
self.add_via_center(layers=("metal1","via1","metal2"),
offset=out_pos)
self.add_via_center(layers=("metal2","via2","metal3"),
offset=out_pos)
#if self.port_type == "rw":
# out_pos = self.rbl_in_inst.get_pin("Z").center()
#else:
# out_pos = vector(self.rail_offsets["gated_clk_bar"].x, self.rbl_inst.by()-3*self.m2_pitch)
self.copy_layout_pin(self.rbl_inst, "Z", "rbl_wl")
self.copy_layout_pin(self.delay_inst, "in", "rbl_bl")
def create_pen_row(self):
if self.port_type == "rw":
@ -698,7 +695,7 @@ class control_logic(design.design):
def route_sen(self):
out_pos = self.rbl_inst.get_pin("out").bc()
out_pos = self.delay_inst.get_pin("out").bc()
in_pos = self.s_en_inst.get_pin("A").lc()
mid1 = vector(out_pos.x,in_pos.y)
self.add_wire(("metal1","via1","metal2"),[out_pos, mid1,in_pos])
@ -817,8 +814,8 @@ class control_logic(design.design):
self.add_path("metal1", [row_loc, pin_loc])
if (self.port_type == "rw") or (self.port_type == "r"):
self.copy_layout_pin(self.rbl_inst,"gnd")
self.copy_layout_pin(self.rbl_inst,"vdd")
self.copy_layout_pin(self.delay_inst,"gnd")
self.copy_layout_pin(self.delay_inst,"vdd")
self.copy_layout_pin(self.ctrl_dff_inst,"gnd")
self.copy_layout_pin(self.ctrl_dff_inst,"vdd")
@ -844,7 +841,7 @@ class control_logic(design.design):
# height=pin.height(),
# width=pin.width())
pin=self.rbl_inst.get_pin("out")
pin=self.delay_inst.get_pin("out")
self.add_label_pin(text="out",
layer=pin.layer,
offset=pin.ll(),

View File

@ -427,3 +427,8 @@ class replica_bitcell_array(design.design):
bitcell_wl_cin = self.cell.get_wl_cin()
total_cin = bitcell_wl_cin * self.column_size
return total_cin
def graph_exclude_bits(self, targ_row, targ_col):
"""Excludes bits in column from being added to graph except target"""
self.bitcell_array.graph_exclude_bits(targ_row, targ_col)

View File

@ -43,11 +43,11 @@ class options(optparse.Values):
###################
# Optimization options
###################
rbl_delay_percentage = .5 #Approximate percentage of delay compared to bitlines
rbl_delay_percentage = 0.5 #Approximate percentage of delay compared to bitlines
# Allow manual adjustment of the delay chain over automatic
use_tech_delay_chain_size = False
delay_chain_stages = 4
delay_chain_stages = 5
delay_chain_fanout_per_stage = 3

View File

@ -243,6 +243,8 @@ class sram_1bank(sram_base):
# The clock gets routed separately and is not a part of the bank
if "clk" in signal:
continue
if signal.startswith("rbl"):
continue
src_pin = self.control_logic_insts[port].get_pin(signal)
dest_pin = self.bank_inst.get_pin(signal+"{}".format(port))
self.connect_rail_from_left_m2m3(src_pin, dest_pin)
@ -364,4 +366,4 @@ class sram_1bank(sram_base):
#Sanity check in case it was forgotten
if inst_name.find('x') != 0:
inst_name = 'x'+inst_name
return self.bank_inst.mod.get_cell_name(inst_name+'.x'+self.bank_inst.name, row, col)
return self.bank_inst.mod.get_cell_name(inst_name+'.x'+self.bank_inst.name, row, col)

View File

@ -283,8 +283,7 @@ class sram_base(design, verilog, lef):
# Create the bank module (up to four are instantiated)
from bank import bank
self.bank = bank(self.sram_config,
name="bank",
num_ports=len(self.all_ports))
name="bank")
self.add_mod(self.bank)
# Create bank decoder
@ -331,6 +330,10 @@ class sram_base(design, verilog, lef):
for port in self.read_ports:
for bit in range(self.word_size):
temp.append("DOUT{0}[{1}]".format(port,bit))
for port in self.read_ports:
temp.append("rbl_bl{0}".format(port))
for port in self.read_ports:
temp.append("rbl_wl{0}".format(port))
for port in self.write_ports:
for bit in range(self.word_size):
temp.append("BANK_DIN{0}[{1}]".format(port,bit))
@ -465,8 +468,12 @@ class sram_base(design, verilog, lef):
if port in self.readwrite_ports:
temp.append("web{}".format(port))
temp.append("clk{}".format(port))
if port in self.read_ports:
temp.append("rbl_bl{}".format(port))
# Ouputs
if port in self.read_ports:
temp.append("rbl_wl{}".format(port))
if port in self.read_ports:
temp.append("s_en{}".format(port))
if port in self.write_ports:

View File

@ -53,8 +53,6 @@ class psram_1bank_2mux_1rw_1r_1w_func_test(openram_test):
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
f = functional(s.s, tempspice, corner)
d = delay(s.s, tempspice, corner)
feasible_period = self.find_feasible_test_period(d, s.s, f.load, f.slew)
f.num_cycles = 10
(fail, error) = f.run()

View File

@ -53,8 +53,6 @@ class psram_1bank_4mux_func_test(openram_test):
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
f = functional(s.s, tempspice, corner)
d = delay(s.s, tempspice, corner)
feasible_period = self.find_feasible_test_period(d, s.s, f.load, f.slew)
f.num_cycles = 10
(fail, error) = f.run()

View File

@ -53,8 +53,6 @@ class psram_1bank_8mux_func_test(openram_test):
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
f = functional(s.s, tempspice, corner)
d = delay(s.s, tempspice, corner)
feasible_period = self.find_feasible_test_period(d, s.s, f.load, f.slew)
f.num_cycles = 10
(fail, error) = f.run()

View File

@ -53,8 +53,6 @@ class psram_1bank_nomux_func_test(openram_test):
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
f = functional(s.s, tempspice, corner)
d = delay(s.s, tempspice, corner)
feasible_period = self.find_feasible_test_period(d, s.s, f.load, f.slew)
f.num_cycles = 10
(fail, error) = f.run()

View File

@ -45,11 +45,9 @@ class sram_1bank_2mux_func_test(openram_test):
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
f = functional(s.s, tempspice, corner)
d = delay(s.s, tempspice, corner)
feasible_period = self.find_feasible_test_period(d, s.s, f.load, f.slew)
f.num_cycles = 10
(fail, error) = f.run(feasible_period)
(fail, error) = f.run()
self.assertTrue(fail,error)
globals.end_openram()

View File

@ -45,8 +45,6 @@ class sram_1bank_4mux_func_test(openram_test):
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
f = functional(s.s, tempspice, corner)
d = delay(s.s, tempspice, corner)
feasible_period = self.find_feasible_test_period(d, s.s, f.load, f.slew)
f.num_cycles = 10
(fail, error) = f.run()

View File

@ -48,8 +48,6 @@ class sram_1bank_8mux_func_test(openram_test):
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
f = functional(s.s, tempspice, corner)
d = delay(s.s, tempspice, corner)
feasible_period = self.find_feasible_test_period(d, s.s, f.load, f.slew)
f.num_cycles = 10
(fail, error) = f.run()

View File

@ -61,7 +61,7 @@ class openram_test(unittest.TestCase):
self.fail("LVS mismatch: {}".format(a.name))
# For debug...
#import pdb; pdb.set_trace()
import pdb; pdb.set_trace()
if OPTS.purge_temp:
self.cleanup()