From 34d8a19871ed830c5e1e214fcddf3e1c746451a2 Mon Sep 17 00:00:00 2001 From: Michael Timothy Grimes Date: Thu, 4 Oct 2018 09:29:44 -0700 Subject: [PATCH] Adding simulation.py for common functions between functional and delay tests. Updating functional test. --- compiler/base/design.py | 1 + compiler/characterizer/__init__.py | 1 + compiler/characterizer/functional.py | 276 +++++++------------ compiler/characterizer/simulation.py | 197 +++++++++++++ compiler/characterizer/stimuli.py | 13 +- compiler/tests/22_hspice_psram_func_test.py | 13 +- compiler/tests/22_hspice_sram_func_test.py | 9 +- compiler/tests/22_ngspice_psram_func_test.py | 63 +++++ compiler/tests/22_ngspice_sram_func_test.py | 55 ++++ 9 files changed, 435 insertions(+), 193 deletions(-) create mode 100644 compiler/characterizer/simulation.py create mode 100644 compiler/tests/22_ngspice_psram_func_test.py create mode 100644 compiler/tests/22_ngspice_sram_func_test.py diff --git a/compiler/base/design.py b/compiler/base/design.py index d5aef2e8..80a9e07e 100644 --- a/compiler/base/design.py +++ b/compiler/base/design.py @@ -53,6 +53,7 @@ class design(hierarchy_design): self.total_write = OPTS.num_rw_ports + OPTS.num_w_ports self.total_read = OPTS.num_rw_ports + OPTS.num_r_ports self.total_ports = OPTS.num_rw_ports + OPTS.num_w_ports + OPTS.num_r_ports + self.num_rw_ports = OPTS.num_rw_ports # Port indices used for data, address, and control signals # Port IDs used to identify port type diff --git a/compiler/characterizer/__init__.py b/compiler/characterizer/__init__.py index 8b94d611..ea99c51c 100644 --- a/compiler/characterizer/__init__.py +++ b/compiler/characterizer/__init__.py @@ -6,6 +6,7 @@ from .lib import * from .delay import * from .setup_hold import * from .functional import * +from .simulation import * debug.info(1,"Initializing characterizer...") diff --git a/compiler/characterizer/functional.py b/compiler/characterizer/functional.py index 44a466df..8b979394 100644 --- a/compiler/characterizer/functional.py +++ b/compiler/characterizer/functional.py @@ -5,82 +5,44 @@ import math import tech import random from .stimuli import * -from .trim_spice import * from .charutils import * import utils from globals import OPTS +from .simulation import simulation -class functional(): + +class functional(simulation): """ Functions to write random data values to a random address then read them back and check for successful SRAM operation. """ def __init__(self, sram, spfile, corner): - self.sram = sram - self.name = sram.name - self.word_size = self.sram.word_size - self.addr_size = self.sram.addr_size - self.num_cols = self.sram.num_cols - self.num_rows = self.sram.num_rows - self.num_banks = self.sram.num_banks - self.sp_file = spfile - - self.total_ports = self.sram.total_ports - self.total_write = self.sram.total_write - self.total_read = self.sram.total_read - self.read_index = self.sram.read_index - self.write_index = self.sram.write_index - self.port_id = self.sram.port_id - - # These are the member variables for a simulation + super().__init__(sram, spfile, corner) + self.set_corner(corner) self.set_spice_constants() self.set_stimulus_variables() # Number of checks can be changed - self.num_checks = 2 + self.num_checks = 1 + self.cycles = 0 + self.eo_period = [] # set to 1 if functional simulation fails during any check self.functional_fail = 0 self.error = "" - - def set_corner(self,corner): - """ Set the corner values """ - self.corner = corner - (self.process, self.vdd_voltage, self.temperature) = corner - - def set_spice_constants(self): - """ sets feasible timing parameters """ - self.period = tech.spice["feasible_period"] - self.slew = tech.spice["rise_time"]*2 - self.load = tech.spice["msflop_in_cap"]*4 - self.gnd_voltage = 0 - - def set_stimulus_variables(self): - """ Variables relevant to functional test """ - self.cycles = 0 - self.written_words = [] - - # control signals: only one cs_b for entire multiported sram, one we_b for each write port - self.cs_b = [[] for port in range(self.total_ports)] - self.we_b = [[] for port in range(self.total_write)] - - # "end of period" signal used to keep track of when read output should be analyzed - self.eo_period = [] - - # Three dimensional list to handle each addr and data bits for wach port over the number of checks - self.addresses = [[[] for bit in range(self.addr_size)] for port in range(self.total_ports)] - self.data = [[[] for bit in range(self.word_size)] for port in range(self.total_write)] def run(self): - """ Main function to generate random writes/reads, run spice, and analyze results """ + """ Main function to generate random writes/reads, run spice, and analyze results """ self.noop() self.overwrite_test() self.write_read_test() + self.noop() + # Run SPICE simulation self.write_functional_stimulus() self.stim.run_sim() @@ -90,23 +52,28 @@ class functional(): self.sp_read_value = ["" for port in range(self.total_read)] for port in range(self.total_read): for bit in range(self.word_size): - value = parse_spice_list("timing", "vdout{0}.{1}..ch{2}".format(self.read_index[port],bit,i)) - if value > 0.75 * self.vdd_voltage: + value = parse_spice_list("timing", "vdout{0}.{1}.ck{2}".format(self.read_index[port],bit,i)) + if value > 0.9 * self.vdd_voltage: self.sp_read_value[port] = "1" + self.sp_read_value[port] - elif value < 0.25 * self.vdd_voltage: + elif value < 0.1 * self.vdd_voltage: self.sp_read_value[port] = "0" + self.sp_read_value[port] else: self.functional_fail = 1 - self.error ="FAILED: dout value {0} does not fall within noise margins <{1} or >{2}.".format(value,0.25*self.vdd_voltage,0.75*self.vdd_voltage) + self.error ="FAILED: DOUT{0}[{1}] value {2} at time {3}n does not fall within noise margins <{4} or >{5}.".format(port, + bit, + value, + self.eo_period[i], + 0.1*self.vdd_voltage, + 0.9*self.vdd_voltage) + + if self.functional_fail: + return (self.functional_fail, self.error) if i < self.num_checks: self.read_values_over_test[i].append(self.sp_read_value[port]) else: self.read_values_test[i-self.num_checks].append(self.sp_read_value[port]) - if self.functional_fail: - return (self.functional_fail, self.error) - # Compare written values to read values for i in range(self.num_checks): debug.info(1, "Stored Word - Overwrite Test: {}".format(self.stored_values_over_test[i])) @@ -114,7 +81,8 @@ class functional(): for port in range(self.total_read): if self.stored_values_over_test[i] != self.read_values_over_test[i][port]: self.functional_fail = 1 - self.error ="FAILED: read value {0} does not match writen value {1}.".format(self.read_values_over_test[i][port], self.stored_values_over_test[i]) + self.error ="FAILED: Overwrite Test - read value {0} does not match writen value {1}.".format(self.read_values_over_test[i][port], + self.stored_values_over_test[i]) for i in range(self.num_checks): debug.info(1, "Stored Word - Standard W/R Test: {}".format(self.stored_values_test[i])) @@ -122,7 +90,8 @@ class functional(): for port in range(self.total_read): if self.stored_values_test[i] != self.read_values_test[i][port]: self.functional_fail = 1 - self.error ="FAILED: read value {0} does not match writen value {1}.".format(self.read_values_test[i][port], self.stored_values_test[i]) + self.error ="FAILED: Standard W/R Test - read value {0} does not match writen value {1}.".format(self.read_values_test[i][port], + self.stored_values_test[i]) return (self.functional_fail, self.error) @@ -134,6 +103,8 @@ class functional(): self.overwrite_test() self.write_read_test() + self.noop() + # Run SPICE simulation self.write_functional_stimulus() self.stim.run_sim() @@ -143,14 +114,22 @@ class functional(): self.sp_read_value = ["" for port in range(self.total_read)] for port in range(self.total_read): for bit in range(self.word_size): - value = parse_spice_list("timing", "vdout{0}.{1}..ch{2}".format(self.read_index[port],bit,i)) - if value > 0.75 * self.vdd_voltage: + value = parse_spice_list("timing", "vdout{0}.{1}.ck{2}".format(self.read_index[port],bit,i)) + if value > 0.9 * self.vdd_voltage: self.sp_read_value[port] = "1" + self.sp_read_value[port] - elif value < 0.25 * self.vdd_voltage: + elif value < 0.1 * self.vdd_voltage: self.sp_read_value[port] = "0" + self.sp_read_value[port] else: self.functional_fail = 1 - self.error ="FAILED: dout value {0} does not fall within noise margins <{1} or >{2}.".format(value,0.25*self.vdd_voltage,0.75*self.vdd_voltage) + self.error ="FAILED: DOUT{0}[{1}] value {2} at time {3}n does not fall within noise margins <{4} or >{5}.".format(port, + bit, + value, + self.eo_period[i], + 0.1*self.vdd_voltage, + 0.9*self.vdd_voltage) + + if self.functional_fail: + return (self.functional_fail, self.error) if i < self.num_checks: self.read_values_multi_test[i][self.multi_addrs[i][port]] = self.sp_read_value[port] @@ -159,9 +138,6 @@ class functional(): else: self.read_values_test[i-2*self.num_checks].append(self.sp_read_value[port]) - if self.functional_fail: - return (self.functional_fail, self.error) - # Compare written values to read values for i in range(self.num_checks): debug.info(1, "Stored Words - Multi Test (addr:word): {}".format(self.stored_values_multi_test[i])) @@ -169,7 +145,8 @@ class functional(): for addr in self.multi_addrs[i]: if self.stored_values_multi_test[i][addr] != self.read_values_multi_test[i][addr]: self.functional_fail = 1 - self.error ="FAILED: read value {0} does not match writen value {1}.".format(self.read_values_multi_test[i][addr], self.stored_values_multi_test[i][addr]) + self.error ="FAILED: Multi Test - read value {0} does not match writen value {1}.".format(self.read_values_multi_test[i][addr], + self.stored_values_multi_test[i][addr]) for i in range(self.num_checks): debug.info(1, "Stored Word - Overwrite Test: {}".format(self.stored_values_over_test[i])) @@ -177,7 +154,8 @@ class functional(): for port in range(self.total_read): if self.stored_values_over_test[i] != self.read_values_over_test[i][port]: self.functional_fail = 1 - self.error ="FAILED: read value {0} does not match writen value {1}.".format(self.read_values_over_test[i][port], self.stored_values_over_test[i]) + self.error ="FAILED: Overwrite Test - read value {0} does not match writen value {1}.".format(self.read_values_over_test[i][port], + self.stored_values_over_test[i]) for i in range(self.num_checks): debug.info(1, "Stored Word - Standard W/R Test: {}".format(self.stored_values_test[i])) @@ -185,7 +163,8 @@ class functional(): for port in range(self.total_read): if self.stored_values_test[i] != self.read_values_test[i][port]: self.functional_fail = 1 - self.error ="FAILED: read value {0} does not match writen value {1}.".format(self.read_values_test[i][port], self.stored_values_test[i]) + self.error ="FAILED: Standard W/R Test - read value {0} does not match writen value {1}.".format(self.read_values_test[i][port], + self.stored_values_test[i]) return (self.functional_fail, self.error) @@ -221,7 +200,7 @@ class functional(): for i in range(self.num_checks): # Write a random word to a random address 3 different times, overwriting the stored word twice addr = self.gen_addr() - for j in range(3): + for j in range(2): word = self.gen_data() self.write(addr,word) self.stored_values_over_test.append(word) @@ -246,110 +225,102 @@ class functional(): def noop(self): """ Noop cycle. """ - self.cycles = self.cycles + 1 + self.cycle_times.append(self.t_current) + self.t_current += self.period for port in range(self.total_ports): - self.cs_b[port].append(1) + self.csb_values[port].append(1) for port in range(self.total_write): - self.we_b[port].append(1) + self.web_values[port].append(1) for port in range(self.total_ports): for bit in range(self.addr_size): - self.addresses[port][bit].append(0) + self.addr_values[port][bit].append(0) for port in range(self.total_write): for bit in range(self.word_size): - self.data[port][bit].append(0) + self.data_values[port][bit].append(0) def write(self,addr,word,write_port=0): """ Generates signals for a write cycle. """ debug.info(1, "Writing {0} to address {1} in cycle {2}...".format(word,addr,self.cycles)) - self.cycles = self.cycles + 1 + self.cycles += 1 + self.cycle_times.append(self.t_current) + self.t_current += self.period # Write control signals for port in range(self.total_ports): if port == write_port: - self.cs_b[port].append(0) + self.csb_values[port].append(0) else: - self.cs_b[port].append(1) + self.csb_values[port].append(1) for port in range(self.total_write): if port == write_port: - self.we_b[port].append(0) + self.web_values[port].append(0) else: - self.we_b[port].append(1) + self.web_values[port].append(1) # Write address for port in range(self.total_ports): for bit in range(self.addr_size): current_address_bit = int(addr[self.addr_size-1-bit]) - self.addresses[port][bit].append(current_address_bit) + self.addr_values[port][bit].append(current_address_bit) # Write data for port in range(self.total_write): for bit in range(self.word_size): current_word_bit = int(word[self.word_size-1-bit]) - self.data[port][bit].append(current_word_bit) + self.data_values[port][bit].append(current_word_bit) def read(self,addr,word): """ Generates signals for a read cycle. """ - debug.info(1, "Reading {0} from address {1} in cycle {2},{3}...".format(word,addr,self.cycles,self.cycles+1)) - self.cycles = self.cycles + 2 + debug.info(1, "Reading {0} from address {1} in cycle {2}...".format(word,addr,self.cycles)) + self.cycles += 1 + self.cycle_times.append(self.t_current) + self.t_current += self.period # Read control signals for port in range(self.total_ports): - self.cs_b[port].append(0) + if self.port_id[port] == "w": + self.csb_values[port].append(1) + else: + self.csb_values[port].append(0) for port in range(self.total_write): - self.we_b[port].append(1) + self.web_values[port].append(1) # Read address for port in range(self.total_ports): for bit in range(self.addr_size): current_address_bit = int(addr[self.addr_size-1-bit]) - self.addresses[port][bit].append(current_address_bit) + self.addr_values[port][bit].append(current_address_bit) # Data input doesn't matter during read cycle, so arbitrarily set to 0 for simulation for port in range(self.total_write): for bit in range(self.word_size): - self.data[port][bit].append(0) - - - # Add idle cycle since read may take more than 1 cycle - # Idle control signals - for port in range(self.total_ports): - self.cs_b[port].append(1) - - for port in range(self.total_write): - self.we_b[port].append(1) - - # Address doesn't matter during idle cycle, but keep the same as read cycle for easier debugging - for port in range(self.total_ports): - for bit in range(self.addr_size): - current_address_bit = int(addr[self.addr_size-1-bit]) - self.addresses[port][bit].append(current_address_bit) - - # Data input doesn't matter during idle cycle, so arbitrarily set to 0 for simulation - for port in range(self.total_write): - for bit in range(self.word_size): - self.data[port][bit].append(0) - + self.data_values[port][bit].append(0) # Record the end of the period that the read operation occured in - self.eo_period.append(self.cycles * self.period) + self.eo_period.append(self.t_current) def multi_read(self,addrs,words): """ Generates signals for a read cycle but all ports read from a different address. The inputs 'addrs' and 'words' are lists. """ - debug.info(1, "Reading {0} from addresses {1} in cycles {2},{3}...".format(words,addrs,self.cycles,self.cycles+1)) - self.cycles = self.cycles + 2 + debug.info(1, "Reading {0} from addresses {1} in cycle {2}...".format(words,addrs,self.cycles)) + self.cycles += 1 + self.cycle_times.append(self.t_current) + self.t_current += self.period # Read control signals for port in range(self.total_ports): - self.cs_b[port].append(0) + if self.port_id[port] == "w": + self.csb_values[port].append(1) + else: + self.csb_values[port].append(0) for port in range(self.total_write): - self.we_b[port].append(1) + self.web_values[port].append(1) # Read address addr_index = 0 @@ -360,7 +331,7 @@ class functional(): else: current_address_bit = int(addrs[addr_index][self.addr_size-1-bit]) - self.addresses[port][bit].append(current_address_bit) + self.addr_values[port][bit].append(current_address_bit) if self.port_id[port] != "w": addr_index += 1 @@ -368,39 +339,10 @@ class functional(): # Data input doesn't matter during read cycle, so arbitrarily set to 0 for simulation for port in range(self.total_write): for bit in range(self.word_size): - self.data[port][bit].append(0) - - - # Add idle cycle since read may take more than 1 cycle - # Idle control signals - for port in range(self.total_ports): - self.cs_b[port].append(1) - - for port in range(self.total_write): - self.we_b[port].append(1) - - # Address doesn't matter during idle cycle, but keep the same as read cycle for easier debugging - addr_index = 0 - for port in range(self.total_ports): - for bit in range(self.addr_size): - if self.port_id[port] == "w": - current_address_bit = 0 - else: - current_address_bit = int(addrs[addr_index][self.addr_size-1-bit]) - - self.addresses[port][bit].append(current_address_bit) - - if self.port_id[port] != "w": - addr_index += 1 - - # Data input doesn't matter during idle cycle, so arbitrarily set to 0 for simulation - for port in range(self.total_write): - for bit in range(self.word_size): - self.data[port][bit].append(0) - + self.data_values[port][bit].append(0) # Record the end of the period that the read operation occured in - self.eo_period.append(self.cycles * self.period) + self.eo_period.append(self.t_current) def gen_data(self): """ Generates a random word to write. """ @@ -442,28 +384,9 @@ class functional(): self.cycle_times.append(t_current) t_current += period - def create_port_names(self): - """Generates the port names to be used in characterization and sets default simulation target ports. """ - self.write_ports = [] - self.read_ports = [] - self.total_port_num = OPTS.num_rw_ports + OPTS.num_w_ports + OPTS.num_r_ports - - #save a member variable to avoid accessing global. readwrite ports have different control signals. - self.readwrite_port_num = OPTS.num_rw_ports - - #Generate the port names. readwrite ports are required to be added first for this to work. - for readwrite_port_num in range(OPTS.num_rw_ports): - self.read_ports.append(readwrite_port_num) - self.write_ports.append(readwrite_port_num) - #This placement is intentional. It makes indexing input data easier. See self.data_values - for write_port_num in range(OPTS.num_rw_ports, OPTS.num_rw_ports+OPTS.num_w_ports): - self.write_ports.append(write_port_num) - for read_port_num in range(OPTS.num_rw_ports+OPTS.num_w_ports, OPTS.num_rw_ports+OPTS.num_w_ports+OPTS.num_r_ports): - self.read_ports.append(read_port_num) - def write_functional_stimulus(self): """ Writes SPICE stimulus. """ - self.obtain_cycle_times(self.period) + #self.obtain_cycle_times(self.period) temp_stim = "{0}/stim.sp".format(OPTS.openram_temp) self.sf = open(temp_stim,"w") self.sf.write("* Functional test stimulus file for {}ns period\n\n".format(self.period)) @@ -477,13 +400,15 @@ class functional(): #Write Vdd/Gnd statements self.sf.write("\n* Global Power Supplies\n") self.stim.write_supply() - - self.create_port_names() #Instantiate the SRAM self.sf.write("\n* Instantiation of the SRAM\n") self.stim.inst_full_sram(sram=self.sram, sram_name=self.name) + #self.stim.inst_sram(abits=self.addr_size, + # dbits=self.word_size, + # port_info=(self.total_ports,self.total_write,self.read_index,self.write_index), + # sram_name=self.name) # Add load capacitance to each of the read ports self.sf.write("\n* SRAM output loads\n") @@ -496,21 +421,21 @@ class functional(): for port in range(self.total_write): for bit in range(self.word_size): sig_name = "DIN{0}[{1}]".format(port,bit) - self.stim.gen_pwl(sig_name, self.cycle_times, self.data[port][bit], self.period, self.slew, 0.05) + self.stim.gen_pwl(sig_name, self.cycle_times, self.data_values[port][bit], self.period, self.slew, 0.05) # Generate address bits for port in range(self.total_ports): for bit in range(self.addr_size): sig_name = "ADDR{0}[{1}]".format(port,bit) - self.stim.gen_pwl(sig_name, self.cycle_times, self.addresses[port][bit], self.period, self.slew, 0.05) + self.stim.gen_pwl(sig_name, self.cycle_times, self.addr_values[port][bit], self.period, self.slew, 0.05) # Generate control signals self.sf.write("\n * Generation of control signals\n") for port in range(self.total_ports): - self.stim.gen_pwl("CSB{}".format(port), self.cycle_times , self.cs_b[port], self.period, self.slew, 0.05) + self.stim.gen_pwl("CSB{}".format(port), self.cycle_times , self.csb_values[port], self.period, self.slew, 0.05) for port in range(self.total_write): - self.stim.gen_pwl("WEB{}".format(port), self.cycle_times , self.we_b[port], self.period, self.slew, 0.05) + self.stim.gen_pwl("WEB{}".format(port), self.cycle_times , self.web_values[port], self.period, self.slew, 0.05) # Generate CLK signals for port in range(self.total_ports): @@ -530,13 +455,14 @@ class functional(): self.sf.write("\n * Generation of dout measurements\n") for i in range(num_tests*self.num_checks): + t_intital = self.eo_period[i] - 0.01*self.period + t_final = self.eo_period[i] + 0.01*self.period for port in range(self.total_read): for bit in range(self.word_size): - self.stim.gen_meas_value(meas_name="VDOUT{0}[{1}]_ch{2}".format(self.read_index[port],bit,i), + self.stim.gen_meas_value(meas_name="VDOUT{0}[{1}]ck{2}".format(self.read_index[port],bit,i), dout="DOUT{0}[{1}]".format(self.read_index[port],bit), - eo_period=self.eo_period[i], - slew=self.slew, - setup=0.05) + t_intital=t_intital, + t_final=t_final) self.stim.write_control(self.cycle_times[-1] + self.period) self.sf.close() diff --git a/compiler/characterizer/simulation.py b/compiler/characterizer/simulation.py new file mode 100644 index 00000000..9b280849 --- /dev/null +++ b/compiler/characterizer/simulation.py @@ -0,0 +1,197 @@ +import sys,re,shutil +from design import design +import debug +import math +import tech +import random +from .stimuli import * +from .trim_spice import * +from .charutils import * +import utils +from globals import OPTS + +class simulation(): + + def __init__(self, sram, spfile, corner): + self.sram = sram + + self.name = self.sram.name + self.word_size = self.sram.word_size + self.addr_size = self.sram.addr_size + self.num_cols = self.sram.num_cols + self.num_rows = self.sram.num_rows + self.num_banks = self.sram.num_banks + self.sp_file = spfile + + self.total_ports = self.sram.total_ports + self.total_write = self.sram.total_write + self.total_read = self.sram.total_read + self.read_index = self.sram.read_index + self.write_index = self.sram.write_index + self.port_id = self.sram.port_id + + def set_corner(self,corner): + """ Set the corner values """ + self.corner = corner + (self.process, self.vdd_voltage, self.temperature) = corner + + def set_spice_constants(self): + """ sets feasible timing parameters """ + self.period = tech.spice["feasible_period"] + self.slew = tech.spice["rise_time"]*2 + self.load = tech.spice["msflop_in_cap"]*4 + self.gnd_voltage = 0 + + def set_stimulus_variables(self): + # Clock signals + self.cycle_times = [] + self.t_current = 0 + + # control signals: only one cs_b for entire multiported sram, one we_b for each write port + self.csb_values = [[] for port in range(self.total_ports)] + self.web_values = [[] for port in range(self.total_write)] + + # Three dimensional list to handle each addr and data bits for wach port over the number of checks + self.addr_values = [[[] for bit in range(self.addr_size)] for port in range(self.total_ports)] + self.data_values = [[[] for bit in range(self.word_size)] for port in range(self.total_write)] + + def add_control_one_port(self, port, op): + """Appends control signals for operation to a given port""" + #Determine values to write to port + web_val = 1 + csb_val = 1 + if op == "read": + self.cs_b = 0 + elif op == "write": + csb_val = 0 + web_val = 0 + elif op != "noop": + debug.error("Could not add control signals for port {0}. Command {1} not recognized".format(port,op),1) + + # Append the values depending on the type of port + self.csb_values[port].append(csb_val) + # If port is in both lists, add rw control signal. Condition indicates its a RW port. + if port < self.num_rw_ports: + self.web_values[port].append(web_val) + + def add_data(self, data, port): + """ Add the array of data values """ + debug.check(len(data)==self.word_size, "Invalid data word size.") + #debug.check(port < len(self.data_values), "Port number cannot index data values.") + + bit = self.word_size - 1 + for c in data: + if c=="0": + self.data_values[port][bit].append(0) + elif c=="1": + self.data_values[port][bit].append(1) + else: + debug.error("Non-binary data string",1) + bit -= 1 + + def add_address(self, address, port): + """ Add the array of address values """ + debug.check(len(address)==self.addr_size, "Invalid address size.") + + bit = self.addr_size - 1 + for c in address: + if c=="0": + self.addr_values[port][bit].append(0) + elif c=="1": + self.addr_values[port][bit].append(1) + else: + debug.error("Non-binary address string",1) + bit -= 1 + + def add_write(self, comment, address, data, port): + """ Add the control values for a write cycle. """ + debug.check(port in self.write_ports, "Cannot add read cycle to a read port.") + self.cycle_comments.append("Cycle {0:2d}\tPort {3}\t{1:5.2f}ns:\t{2}".format(len(self.cycle_comments), + self.t_current, + comment, + port)) + self.cycle_times.append(self.t_current) + self.t_current += self.period + + self.add_control_one_port(port, "write") + self.add_data(data,port) + self.add_address(address,port) + + #This value is hard coded here. Possibly change to member variable or set in add_noop_one_port + noop_data = "0"*self.word_size + #Add noops to all other ports. + for unselected_port in range(self.total_port_num): + if unselected_port != port: + self.add_noop_one_port(address, noop_data, unselected_port) + + def add_write_one_port(self, comment, address, data, port): + """ Add the control values for a write cycle. """ + debug.check(port in self.write_ports, "Cannot add read cycle to a read port.") + self.cycle_comments.append("Cycle {0:2d}\tPort {3}\t{1:5.2f}ns:\t{2}".format(len(self.cycle_comments), + self.t_current, + comment, + port)) + self.cycle_times.append(self.t_current) + self.t_current += self.period + + self.add_control_one_port(port, "write") + self.add_data(data,port) + self.add_address(address,port) + + def add_read(self, comment, address, data, port): + """ Add the control values for a read cycle. """ + debug.check(port in self.read_ports, "Cannot add read cycle to a write port.") + self.cycle_comments.append("Cycle {0:2d}\tPort {3}\t{1:5.2f}ns:\t{2}".format(len(self.cycle_comments), + self.t_current, + comment, + port)) + self.cycle_times.append(self.t_current) + self.t_current += self.period + self.add_control_one_port(port, "read") + + #If the port is also a readwrite then add data. + if port in self.write_ports: + self.add_data(data,port) + self.add_address(address, port) + + #This value is hard coded here. Possibly change to member variable or set in add_noop_one_port + noop_data = "0"*self.word_size + #Add noops to all other ports. + for unselected_port in range(self.total_port_num): + if unselected_port != port: + self.add_noop_one_port(address, noop_data, unselected_port) + + def add_read_one_port(self, comment, address, data, port): + """ Add the control values for a read cycle. """ + debug.check(port in self.read_ports, "Cannot add read cycle to a write port.") + self.cycle_comments.append("Cycle {0:2d}\tPort {3}\t{1:5.2f}ns:\t{2}".format(len(self.cycle_comments), + self.t_current, + comment, + port)) + self.cycle_times.append(self.t_current) + self.t_current += self.period + self.add_control_one_port(port, "read") + + #If the port is also a readwrite then add data. + if port in self.write_ports: + self.add_data(data,port) + self.add_address(address, port) + + def add_noop_one_port(self, address, data, port): + """ Add the control values for a noop to a single port. """ + #This is to be used as a helper function for the other add functions. Cycle and comments are omitted. + self.add_control_one_port(port, "noop") + if port in self.write_ports: + self.add_data(data,port) + self.add_address(address, port) + + def add_noop_all_ports(self, comment, address, data): + """ Add the control values for a noop to all ports. """ + self.cycle_comments.append("Cycle {0:2d}\tPort All\t{1:5.2f}ns:\t{2}".format(len(self.cycle_times), + self.t_current, + comment)) + self.cycle_times.append(self.t_current) + self.t_current += self.period + + for port in range(self.total_port_num): + self.add_noop_one_port(address, data, port) \ No newline at end of file diff --git a/compiler/characterizer/stimuli.py b/compiler/characterizer/stimuli.py index 56383d70..d07d3c11 100644 --- a/compiler/characterizer/stimuli.py +++ b/compiler/characterizer/stimuli.py @@ -63,8 +63,11 @@ class stimuli(): self.sf.write("CSB{0} ".format(port)) for readwrite_port in range(readwrite_num): self.sf.write("WEB{0} ".format(readwrite_port)) - - self.sf.write("{0} ".format(tech.spice["clk"])) + + for port in range(total_port_num): + self.sf.write("CLK{0} ".format(port)) + + #self.sf.write("{0} ".format(tech.spice["clk"])) for read_output in read_ports: for i in range(dbits): self.sf.write("DOUT{0}[{1}] ".format(read_output, i)) @@ -225,10 +228,8 @@ class stimuli(): t_initial, t_final)) - def gen_meas_value(self, meas_name, dout, eo_period, setup, slew): - t0 = eo_period - setup - 2*slew - t1 = eo_period - setup - slew - measure_string=".meas tran {0} AVG v({1}) FROM={2}n TO={3}n\n\n".format(meas_name, dout, t0, t1) + def gen_meas_value(self, meas_name, dout, t_intital, t_final): + measure_string=".meas tran {0} AVG v({1}) FROM={2}n TO={3}n\n\n".format(meas_name, dout, t_intital, t_final) self.sf.write(measure_string) def write_control(self, end_time): diff --git a/compiler/tests/22_hspice_psram_func_test.py b/compiler/tests/22_hspice_psram_func_test.py index bd2c4f66..0b3c66a7 100644 --- a/compiler/tests/22_hspice_psram_func_test.py +++ b/compiler/tests/22_hspice_psram_func_test.py @@ -32,14 +32,14 @@ class psram_func_test(openram_test): from sram import sram from sram_config import sram_config - c = sram_config(word_size=2, - num_words=16, + c = sram_config(word_size=4, + num_words=32, num_banks=1) - c.words_per_row=1 + c.words_per_row=2 OPTS.num_rw_ports = 1 OPTS.num_w_ports = 2 - OPTS.num_r_ports = 2 + OPTS.num_r_ports = 4 debug.info(1, "Functional test for 1bit, 16word SRAM, with 1 bank. Multiport with {}RW {}W {}R.".format(OPTS.num_rw_ports, OPTS.num_w_ports, OPTS.num_r_ports)) s = sram(c, name="sram1") @@ -49,10 +49,9 @@ class psram_func_test(openram_test): corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) f = functional(s.s, tempspice, corner) - (fail,error) = f.multiport_run() + (success,error) = f.multiport_run() - if fail: - print(error) + self.assertTrue(not success,error) globals.end_openram() diff --git a/compiler/tests/22_hspice_sram_func_test.py b/compiler/tests/22_hspice_sram_func_test.py index 1bff8a62..62d7679f 100644 --- a/compiler/tests/22_hspice_sram_func_test.py +++ b/compiler/tests/22_hspice_sram_func_test.py @@ -30,9 +30,9 @@ class sram_func_test(openram_test): from sram import sram from sram_config import sram_config c = sram_config(word_size=4, - num_words=16, + num_words=32, num_banks=1) - c.words_per_row=1 + c.words_per_row=2 debug.info(1, "Functional test for 1bit, 16word SRAM, with 1 bank") s = sram(c, name="sram1") @@ -41,10 +41,9 @@ class sram_func_test(openram_test): corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) f = functional(s.s, tempspice, corner) - (fail, error) = f.run() + (success, error) = f.run() - if fail: - print(error) + self.assertTrue(not success,error) globals.end_openram() diff --git a/compiler/tests/22_ngspice_psram_func_test.py b/compiler/tests/22_ngspice_psram_func_test.py new file mode 100644 index 00000000..2768066a --- /dev/null +++ b/compiler/tests/22_ngspice_psram_func_test.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +""" +Run a regression test on various srams +""" + +import unittest +from testutils import header,openram_test +import sys,os +sys.path.append(os.path.join(sys.path[0],"..")) +import globals +from globals import OPTS +import debug + +#@unittest.skip("SKIPPING 22_psram_func_test") +class psram_func_test(openram_test): + + def runTest(self): + globals.init_openram("config_20_{0}".format(OPTS.tech_name)) + OPTS.spice_name="ngspice" + OPTS.analytical_delay = False + OPTS.netlist_only = True + OPTS.bitcell = "pbitcell" + OPTS.replica_bitcell="replica_pbitcell" + + # This is a hack to reload the characterizer __init__ with the spice version + from importlib import reload + import characterizer + reload(characterizer) + from characterizer import functional + if not OPTS.spice_exe: + debug.error("Could not find {} simulator.".format(OPTS.spice_name),-1) + + from sram import sram + from sram_config import sram_config + c = sram_config(word_size=4, + num_words=32, + num_banks=1) + c.words_per_row=2 + + OPTS.num_rw_ports = 1 + OPTS.num_w_ports = 2 + OPTS.num_r_ports = 4 + + debug.info(1, "Functional test for 1bit, 16word SRAM, with 1 bank. Multiport with {}RW {}W {}R.".format(OPTS.num_rw_ports, OPTS.num_w_ports, OPTS.num_r_ports)) + s = sram(c, name="sram1") + + tempspice = OPTS.openram_temp + "temp.sp" + s.sp_write(tempspice) + + corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) + f = functional(s.s, tempspice, corner) + (success,error) = f.multiport_run() + + self.assertTrue(not success,error) + + globals.end_openram() + +# instantiate a copdsay of the class to actually run the test +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main() diff --git a/compiler/tests/22_ngspice_sram_func_test.py b/compiler/tests/22_ngspice_sram_func_test.py new file mode 100644 index 00000000..1177d1c4 --- /dev/null +++ b/compiler/tests/22_ngspice_sram_func_test.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +""" +Run a regression test on various srams +""" + +import unittest +from testutils import header,openram_test +import sys,os +sys.path.append(os.path.join(sys.path[0],"..")) +import globals +from globals import OPTS +import debug + +#@unittest.skip("SKIPPING 22_sram_func_test") +class sram_func_test(openram_test): + + def runTest(self): + globals.init_openram("config_20_{0}".format(OPTS.tech_name)) + OPTS.spice_name="ngspice" + OPTS.analytical_delay = False + + # This is a hack to reload the characterizer __init__ with the spice version + from importlib import reload + import characterizer + reload(characterizer) + from characterizer import functional + if not OPTS.spice_exe: + debug.error("Could not find {} simulator.".format(OPTS.spice_name),-1) + + from sram import sram + from sram_config import sram_config + c = sram_config(word_size=4, + num_words=32, + num_banks=1) + c.words_per_row=2 + debug.info(1, "Functional test for 1bit, 16word SRAM, with 1 bank") + s = sram(c, name="sram1") + + tempspice = OPTS.openram_temp + "temp.sp" + s.sp_write(tempspice) + + corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) + f = functional(s.s, tempspice, corner) + (success, error) = f.run() + + self.assertTrue(not success,error) + + globals.end_openram() + +# instantiate a copdsay of the class to actually run the test +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main()