From d609e4ea04774dcf574ba7b64b36b81ac965276b Mon Sep 17 00:00:00 2001 From: mrg Date: Tue, 6 Apr 2021 17:01:52 -0700 Subject: [PATCH] Reimplement trim options (except on unit tests). Allow trim netlist to be used for delay and functional simulation. Each class implements a "trim_insts" set of instances that can be removed. By default far left, right, top and bottom cells in the bitcell arrays are kept. Use lvs option in sp_write Fix lvs option in sram. --- compiler/base/hierarchy_design.py | 6 +- compiler/base/hierarchy_spice.py | 33 +++++----- compiler/characterizer/delay.py | 10 +-- compiler/characterizer/functional.py | 30 ++++++--- compiler/characterizer/lib.py | 7 +-- compiler/characterizer/simulation.py | 5 +- compiler/characterizer/trim_spice.py | 26 ++++---- compiler/modules/bitcell_array.py | 6 +- compiler/sram/sram.py | 7 +-- compiler/sram/sram_base.py | 9 ++- compiler/tests/21_hspice_delay_test.py | 63 +++++++++---------- compiler/tests/21_ngspice_delay_test.py | 58 ++++++++--------- .../tests/22_psram_1bank_2mux_func_test.py | 6 +- .../tests/22_psram_1bank_4mux_func_test.py | 6 +- .../tests/22_psram_1bank_8mux_func_test.py | 6 +- .../tests/22_psram_1bank_nomux_func_test.py | 6 +- .../tests/22_sram_1bank_2mux_func_test.py | 6 +- .../22_sram_1bank_2mux_global_func_test.py | 6 +- .../22_sram_1bank_2mux_sparecols_func_test.py | 6 +- .../tests/22_sram_1bank_4mux_func_test.py | 6 +- .../tests/22_sram_1bank_8mux_func_test.py | 6 +- .../22_sram_1bank_nomux_1rw_1r_func_test.py | 6 +- .../tests/22_sram_1bank_nomux_func_test.py | 5 +- ...22_sram_1bank_nomux_sparecols_func_test.py | 5 +- .../22_sram_1bank_wmask_1rw_1r_func_test.py | 6 +- compiler/tests/22_sram_wmask_func_test.py | 6 +- compiler/tests/23_lib_sram_test.py | 1 - compiler/tests/26_sram_pex_test.py | 2 +- compiler/tests/50_riscv_func_test.py | 6 +- compiler/tests/testutils.py | 2 +- 30 files changed, 147 insertions(+), 206 deletions(-) diff --git a/compiler/base/hierarchy_design.py b/compiler/base/hierarchy_design.py index fe1f4c55..d99c7363 100644 --- a/compiler/base/hierarchy_design.py +++ b/compiler/base/hierarchy_design.py @@ -53,7 +53,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): elif (OPTS.inline_lvsdrc or force_check or final_verification): tempspice = "{}.sp".format(self.name) - self.lvs_write("{0}{1}".format(OPTS.openram_temp, tempspice)) + self.sp_write("{0}{1}".format(OPTS.openram_temp, tempspice), lvs=True) tempgds = "{}.gds".format(self.name) self.gds_write("{0}{1}".format(OPTS.openram_temp, tempgds)) # Final verification option does not allow nets to be connected by label. @@ -82,7 +82,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): return elif (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)): tempspice = "{}.sp".format(self.name) - self.lvs_write("{0}{1}".format(OPTS.openram_temp, tempspice)) + self.sp_write("{0}{1}".format(OPTS.openram_temp, tempspice), lvs=True) tempgds = "{}.gds".format(self.cell_name) self.gds_write("{0}{1}".format(OPTS.openram_temp, tempgds)) num_errors = verify.run_drc(self.cell_name, tempgds, tempspice, final_verification=final_verification) @@ -102,7 +102,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): return elif (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)): tempspice = "{}.sp".format(self.cell_name) - self.lvs_write("{0}{1}".format(OPTS.openram_temp, tempspice)) + self.sp_write("{0}{1}".format(OPTS.openram_temp, tempspice), lvs=True) tempgds = "{}.gds".format(self.name) self.gds_write("{0}{1}".format(OPTS.openram_temp, tempgds)) num_errors = verify.run_lvs(self.name, tempgds, tempspice, final_verification=final_verification) diff --git a/compiler/base/hierarchy_spice.py b/compiler/base/hierarchy_spice.py index 51d2c3b7..2f2d3ec9 100644 --- a/compiler/base/hierarchy_spice.py +++ b/compiler/base/hierarchy_spice.py @@ -63,6 +63,8 @@ class spice(): self.conns = [] # If this is set, it will out output subckt or isntances of this (for row/col caps etc.) self.no_instances = False + # If we are doing a trimmed netlist, these are the instance that will be filtered + self.trim_insts = set() # Keep track of any comments to add the the spice try: self.commments @@ -312,10 +314,11 @@ class spice(): return True return False - def sp_write_file(self, sp, usedMODS, lvs_netlist=False): + def sp_write_file(self, sp, usedMODS, lvs=False, trim=False): """ Recursive spice subcircuit write; - Writes the spice subcircuit from the library or the dynamically generated one + Writes the spice subcircuit from the library or the dynamically generated one. + Trim netlist is intended ONLY for bitcell arrays. """ if self.no_instances: @@ -328,7 +331,7 @@ class spice(): if self.contains(i, usedMODS): continue usedMODS.append(i) - i.sp_write_file(sp, usedMODS, lvs_netlist) + i.sp_write_file(sp, usedMODS, lvs, trim) if len(self.insts) == 0: return @@ -371,10 +374,16 @@ class spice(): # these are wires and paths if self.conns[i] == []: continue + # Instance with no devices in it needs no subckt/instance if self.insts[i].mod.no_instances: continue - if lvs_netlist and hasattr(self.insts[i].mod, "lvs_device"): + + # If this is a trimmed netlist, skip it by adding comment char + if trim and self.insts[i].name in self.trim_insts: + sp.write("* ") + + if lvs and hasattr(self.insts[i].mod, "lvs_device"): sp.write(self.insts[i].mod.lvs_device.format(self.insts[i].name, " ".join(self.conns[i]))) sp.write("\n") @@ -394,30 +403,20 @@ class spice(): # Including the file path makes the unit test fail for other users. # if os.path.isfile(self.sp_file): # sp.write("\n* {0}\n".format(self.sp_file)) - if lvs_netlist and hasattr(self, "lvs"): + if lvs and hasattr(self, "lvs"): sp.write("\n".join(self.lvs)) else: sp.write("\n".join(self.spice)) sp.write("\n") - def sp_write(self, spname): + def sp_write(self, spname, lvs=False, trim=False): """Writes the spice to files""" debug.info(3, "Writing to {0}".format(spname)) spfile = open(spname, 'w') spfile.write("*FIRST LINE IS A COMMENT\n") usedMODS = list() - self.sp_write_file(spfile, usedMODS) - del usedMODS - spfile.close() - - def lvs_write(self, spname): - """Writes the lvs to files""" - debug.info(3, "Writing to {0}".format(spname)) - spfile = open(spname, 'w') - spfile.write("*FIRST LINE IS A COMMENT\n") - usedMODS = list() - self.sp_write_file(spfile, usedMODS, True) + self.sp_write_file(spfile, usedMODS, lvs=lvs, trim=trim) del usedMODS spfile.close() diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index f491d8b0..168a73e9 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -1100,14 +1100,8 @@ class delay(simulation): # Set up to trim the netlist here if that is enabled if OPTS.trim_netlist: - self.trim_sp_file = "{}reduced.sp".format(OPTS.openram_temp) - self.trimsp=trim_spice(self.sp_file, self.trim_sp_file) - self.trimsp.set_configuration(self.num_banks, - self.num_rows, - self.num_cols, - self.word_size, - self.num_spare_rows) - self.trimsp.trim(self.probe_address, self.probe_data) + self.trim_sp_file = "{}trimmed.sp".format(OPTS.openram_temp) + 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 = "{}sram.sp".format(OPTS.openram_temp) diff --git a/compiler/characterizer/functional.py b/compiler/characterizer/functional.py index 35444b15..0435d22e 100644 --- a/compiler/characterizer/functional.py +++ b/compiler/characterizer/functional.py @@ -21,13 +21,17 @@ class functional(simulation): for successful SRAM operation. """ - def __init__(self, sram, spfile, corner=None, cycles=15, period=None, output_path=None): + def __init__(self, sram, spfile=None, corner=None, cycles=15, period=None, output_path=None): super().__init__(sram, spfile, corner) # Seed the characterizer with a constant seed for unit tests if OPTS.is_unit_test: random.seed(12345) + if not spfile: + # self.sp_file is assigned in base class + sram.sp_write(self.sp_file, trim=OPTS.trim_netlist) + if not corner: corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) @@ -46,7 +50,18 @@ class functional(simulation): if not self.num_spare_cols: self.num_spare_cols = 0 + if self.num_spare_cols > 0: + debug.error("Functional simulation not debugged with spare columns.") + # FIXME: we need to remember the correct value of the spare columns + self.max_value = 2 ** (self.word_size + self.num_spare_cols) - 1 + # If trim is set, specify the valid addresses + self.valid_addresses = set() + self.max_address = 2**self.addr_size - 1 + (self.num_spare_rows * self.words_per_row) + if OPTS.trim_netlist: + for i in range(self.words_per_row): + self.valid_addresses.add(i) + self.valid_addresses.add(self.max_address - i) self.probe_address, self.probe_data = '0' * self.addr_size, 0 self.set_corner(corner) self.set_spice_constants() @@ -300,21 +315,16 @@ class functional(simulation): def gen_data(self): """ Generates a random word to write. """ - if not self.num_spare_cols: - random_value = random.randint(0, (2 ** self.word_size) - 1) - else: - random_value1 = random.randint(0, (2 ** self.word_size) - 1) - random_value2 = random.randint(0, (2 ** self.num_spare_cols) - 1) - random_value = random_value1 + random_value2 + random_value = random.randint(0, self.max_value) data_bits = self.convert_to_bin(random_value, False) return data_bits def gen_addr(self): """ Generates a random address value to write to. """ - if self.num_spare_rows==0: - random_value = random.randint(0, (2 ** self.addr_size) - 1) + if self.valid_addresses: + random_value = random.sample(self.valid_addresses, 1)[0] else: - random_value = random.randint(0, ((2 ** (self.addr_size - 1) - 1)) + (self.num_spare_rows * self.words_per_row)) + random_value = random.randint(0, self.max_address) addr_bits = self.convert_to_bin(random_value, True) return addr_bits diff --git a/compiler/characterizer/lib.py b/compiler/characterizer/lib.py index d37119a9..aa892b3d 100644 --- a/compiler/characterizer/lib.py +++ b/compiler/characterizer/lib.py @@ -5,9 +5,8 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import os,sys,re +import os import debug -import math import datetime from .setup_hold import * from .delay import * @@ -16,6 +15,7 @@ import tech import numpy as np from globals import OPTS + class lib: """ lib file generation.""" @@ -601,7 +601,6 @@ class lib: from .elmore import elmore as model else: debug.error("{} model not recognized. See options.py for available models.".format(OPTS.model_name)) - import math m = model(self.sram, self.sp_file, self.corner) char_results = m.get_lib_values(self.slews,self.loads) @@ -834,4 +833,4 @@ class lib: #FIXME: should be read_fall_power datasheet.write("{0},{1},".format('write_fall_power_{}'.format(port), read0_power)) - \ No newline at end of file + diff --git a/compiler/characterizer/simulation.py b/compiler/characterizer/simulation.py index 0afe2459..5becbacf 100644 --- a/compiler/characterizer/simulation.py +++ b/compiler/characterizer/simulation.py @@ -27,7 +27,10 @@ class simulation(): self.num_spare_cols = 0 else: self.num_spare_cols = self.sram.num_spare_cols - self.sp_file = spfile + if not spfile: + self.sp_file = OPTS.openram_temp + "sram.sp" + else: + self.sp_file = spfile self.all_ports = self.sram.all_ports self.readwrite_ports = self.sram.readwrite_ports diff --git a/compiler/characterizer/trim_spice.py b/compiler/characterizer/trim_spice.py index d659212c..e8499d5c 100644 --- a/compiler/characterizer/trim_spice.py +++ b/compiler/characterizer/trim_spice.py @@ -9,6 +9,7 @@ import debug from math import log,ceil import re + class trim_spice(): """ A utility to trim redundant parts of an SRAM spice netlist. @@ -29,7 +30,6 @@ class trim_spice(): for i in range(len(self.spice)): self.spice[i] = self.spice[i].rstrip(" \n") - self.sp_buffer = self.spice def set_configuration(self, banks, rows, columns, word_size): @@ -46,21 +46,23 @@ class trim_spice(): self.col_addr_size = int(log(self.words_per_row, 2)) self.bank_addr_size = self.col_addr_size + self.row_addr_size self.addr_size = self.bank_addr_size + int(log(self.num_banks, 2)) - - + def trim(self, address, data_bit): - """ Reduce the spice netlist but KEEP the given bits at the - address (and things that will add capacitive load!)""" + """ + Reduce the spice netlist but KEEP the given bits at the + address (and things that will add capacitive load!) + """ # Always start fresh if we do multiple reductions self.sp_buffer = self.spice # Split up the address and convert to an int - wl_address = int(address[self.col_addr_size:],2) - if self.col_addr_size>0: - col_address = int(address[0:self.col_addr_size],2) + wl_address = int(address[self.col_addr_size:], 2) + if self.col_addr_size > 0: + col_address = int(address[0:self.col_addr_size], 2) else: col_address = 0 + # 1. Keep cells in the bitcell array based on WL and BL wl_name = "wl_{}".format(wl_address) bl_name = "bl_{}".format(int(self.words_per_row*data_bit + col_address)) @@ -81,7 +83,6 @@ class trim_spice(): self.sp_buffer.insert(0, "* It should NOT be used for LVS!!") self.sp_buffer.insert(0, "* WARNING: This is a TRIMMED NETLIST.") - wl_regex = r"wl\d*_{}".format(wl_address) bl_regex = r"bl\d*_{}".format(int(self.words_per_row*data_bit + col_address)) self.remove_insts("bitcell_array",[wl_regex,bl_regex]) @@ -91,11 +92,11 @@ class trim_spice(): #self.remove_insts("sense_amp_array",[bl_regex]) # 3. Keep column muxes basd on BL - self.remove_insts("column_mux_array",[bl_regex]) + self.remove_insts("column_mux_array", [bl_regex]) # 4. Keep write driver based on DATA data_regex = r"data_{}".format(data_bit) - self.remove_insts("write_driver_array",[data_regex]) + self.remove_insts("write_driver_array", [data_regex]) # 5. Keep wordline driver based on WL # Need to keep the gater too @@ -111,7 +112,6 @@ class trim_spice(): sp.write("\n".join(self.sp_buffer)) sp.close() - def remove_insts(self, subckt_name, keep_inst_list): """This will remove all of the instances in the list from the named subckt that DO NOT contain a term in the list. It just does a @@ -119,7 +119,7 @@ class trim_spice(): net connection, the instance name, anything.. """ removed_insts = 0 - #Expects keep_inst_list are regex patterns. Compile them here. + # Expects keep_inst_list are regex patterns. Compile them here. compiled_patterns = [re.compile(pattern) for pattern in keep_inst_list] start_name = ".SUBCKT {}".format(subckt_name) diff --git a/compiler/modules/bitcell_array.py b/compiler/modules/bitcell_array.py index c7d8ff81..9d1cc0de 100644 --- a/compiler/modules/bitcell_array.py +++ b/compiler/modules/bitcell_array.py @@ -64,7 +64,11 @@ class bitcell_array(bitcell_base_array): self.cell_inst[row, col]=self.add_inst(name=name, mod=self.cell) self.connect_inst(self.get_bitcell_pins(row, col)) - + + # If it is a "core" cell, it could be trimmed for sim time + if col>0 and col0 and row