From bbdc728ac5107bee6b2171b01c255d7861498d18 Mon Sep 17 00:00:00 2001 From: mrg Date: Thu, 1 Jul 2021 09:59:13 -0700 Subject: [PATCH 1/4] Edits to functional simulation. Use correct .TRAN with max timestep. Seed functional sim with a 3 writes to start for more read addresses. Move formatting code to simulation module to share. --- compiler/characterizer/functional.py | 74 +++++++++------------------- compiler/characterizer/simulation.py | 32 ++++++++++++ compiler/characterizer/stimuli.py | 14 ++++-- 3 files changed, 64 insertions(+), 56 deletions(-) diff --git a/compiler/characterizer/functional.py b/compiler/characterizer/functional.py index 9fad2be5..9b3a1fd2 100644 --- a/compiler/characterizer/functional.py +++ b/compiler/characterizer/functional.py @@ -141,23 +141,25 @@ class functional(simulation): comment = self.gen_cycle_comment("noop", "0" * self.word_size, "0" * self.addr_size, "0" * self.num_wmasks, 0, self.t_current) self.add_noop_all_ports(comment) - # 1. Write all the write ports first to seed a bunch of locations. - for port in self.write_ports: - addr = self.gen_addr() - (word, spare) = self.gen_data() - combined_word = self.combine_word(spare, word) - comment = self.gen_cycle_comment("write", combined_word, addr, "1" * self.num_wmasks, port, self.t_current) - self.add_write_one_port(comment, addr, spare + word, "1" * self.num_wmasks, port) - self.stored_words[addr] = word - self.stored_spares[addr[:self.addr_spare_index]] = spare - # All other read-only ports are noops. - for port in self.read_ports: - if port not in self.write_ports: - self.add_noop_one_port(port) - self.cycle_times.append(self.t_current) - self.t_current += self.period - self.check_lengths() + # 1. Write all the write ports 2x to seed a bunch of locations. + for i in range(3): + for port in self.write_ports: + addr = self.gen_addr() + (word, spare) = self.gen_data() + combined_word = self.combine_word(spare, word) + comment = self.gen_cycle_comment("write", combined_word, addr, "1" * self.num_wmasks, port, self.t_current) + self.add_write_one_port(comment, addr, spare + word, "1" * self.num_wmasks, port) + self.stored_words[addr] = word + self.stored_spares[addr[:self.addr_spare_index]] = spare + + # All other read-only ports are noops. + for port in self.read_ports: + if port not in self.write_ports: + self.add_noop_one_port(port) + self.cycle_times.append(self.t_current) + self.t_current += self.period + self.check_lengths() # 2. Read at least once. For multiport, it is important that one # read cycle uses all RW and R port to read from the same @@ -297,38 +299,6 @@ class functional(simulation): self.read_results.append([sp_read_value, dout_port, eo_period, cycle]) return (1, "SUCCESS") - def combine_word(self, spare, word): - if len(spare) > 0: - return spare + "+" + word - - return word - - def format_value(self, value): - """ Format in better readable manner """ - - def delineate(word): - # Create list of chars in reverse order - split_word = list(reversed([x for x in word])) - # Add underscore every 4th char - 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) - - # Split extra cols - if self.num_spare_cols > 0: - vals = value[self.num_spare_cols:] - spare_vals = value[:self.num_spare_cols] - else: - vals = value - spare_vals = "" - - # Insert underscores - vals = delineate(vals) - spare_vals = delineate(spare_vals) - - return self.combine_word(spare_vals, vals) - def check_stim_results(self): for i in range(len(self.read_check)): if self.read_check[i][0] != self.read_results[i][0]: @@ -372,10 +342,12 @@ class functional(simulation): def gen_data(self): """ Generates a random word to write. """ - random_value = random.randint(0, self.max_data) + # Don't use 0 or max value + random_value = random.randint(1, self.max_data - 1) data_bits = binary_repr(random_value, self.word_size) if self.num_spare_cols>0: - random_value = random.randint(0, self.max_col_data) + # Don't use 0 or max value + random_value = random.randint(1, self.max_col_data - 1) spare_bits = binary_repr(random_value, self.num_spare_cols) else: spare_bits = "" @@ -498,7 +470,7 @@ class functional(simulation): for (word, dout_port, eo_period, cycle) in self.read_check: t_initial = eo_period - t_final = eo_period + t_final = eo_period + 0.01 * self.period num_bits = self.word_size + self.num_spare_cols for bit in range(num_bits): signal_name = "{0}_{1}".format(dout_port, bit) diff --git a/compiler/characterizer/simulation.py b/compiler/characterizer/simulation.py index 2f319b93..d7539dc4 100644 --- a/compiler/characterizer/simulation.py +++ b/compiler/characterizer/simulation.py @@ -372,6 +372,38 @@ class simulation(): time_spacing, comment)) + def combine_word(self, spare, word): + if len(spare) > 0: + return spare + "+" + word + + return word + + def format_value(self, value): + """ Format in better readable manner """ + + def delineate(word): + # Create list of chars in reverse order + split_word = list(reversed([x for x in word])) + # Add underscore every 4th char + 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) + + # Split extra cols + if self.num_spare_cols > 0: + vals = value[self.num_spare_cols:] + spare_vals = value[:self.num_spare_cols] + else: + vals = value + spare_vals = "" + + # Insert underscores + vals = delineate(vals) + spare_vals = delineate(spare_vals) + + return self.combine_word(spare_vals, vals) + def gen_cycle_comment(self, op, word, addr, wmask, port, t_current): if op == "noop": str = "\tIdle during cycle {0} ({1}ns - {2}ns)" diff --git a/compiler/characterizer/stimuli.py b/compiler/characterizer/stimuli.py index f546cb96..b9e4b3c7 100644 --- a/compiler/characterizer/stimuli.py +++ b/compiler/characterizer/stimuli.py @@ -222,7 +222,7 @@ class stimuli(): 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) + #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) def write_control(self, end_time, runlvl=4): @@ -237,12 +237,13 @@ class stimuli(): reltol = 0.005 # 0.5% else: reltol = 0.001 # 0.1% - timestep = 10 # ps, was 5ps but ngspice was complaining the timestep was too small in certain tests. + timestep = 10 # ps if OPTS.spice_name == "ngspice": self.sf.write(".TEMP {}\n".format(self.temperature)) # UIC is needed for ngspice to converge - self.sf.write(".TRAN {0}p {1}n UIC\n".format(timestep, end_time)) + # Format: .tran tstep tstop < tstart < tmax >> + self.sf.write(".TRAN {0}p {1}n 0n {0}p UIC\n".format(timestep, end_time)) # ngspice sometimes has convergence problems if not using gear method # which is more accurate, but slower than the default trapezoid method # Do not remove this or it may not converge due to some "pa_00" nodes @@ -268,7 +269,8 @@ class stimuli(): self.sf.write("simulator lang=spice\n") elif OPTS.spice_name in ["hspice", "xa"]: self.sf.write(".TEMP {}\n".format(self.temperature)) - self.sf.write(".TRAN {0}p {1}n UIC\n".format(timestep, end_time)) + # Format: .tran tstep tstop < tstart < tmax >> + self.sf.write(".TRAN {0}p {1}n 0n {0}p UIC\n".format(timestep, end_time)) self.sf.write(".OPTIONS POST=1 RUNLVL={0} PROBE\n".format(runlvl)) self.sf.write(".OPTIONS PSF=1 \n") self.sf.write(".OPTIONS HIER_DELIM=1 \n") @@ -276,7 +278,9 @@ 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(".TRAN {0}p {1}n\n".format(timestep, end_time)) + self.sf.write(".OPTIONS TIMEINT RELTOL=1e-6 ABSTOL=1e-10 method=gear minorder=2\n") + # Format: .TRAN + self.sf.write(".TRAN {0}p {1}n 0n {0}p\n".format(timestep, end_time)) elif OPTS.spice_name: debug.error("Unkown spice simulator {}".format(OPTS.spice_name), -1) From 271109344243f0358643b6e46a389862e9e2aa3e Mon Sep 17 00:00:00 2001 From: mrg Date: Thu, 1 Jul 2021 12:47:17 -0700 Subject: [PATCH 2/4] Improve signal debug output --- compiler/characterizer/functional.py | 19 +++++++++++-------- compiler/characterizer/simulation.py | 7 +++---- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/compiler/characterizer/functional.py b/compiler/characterizer/functional.py index 9b3a1fd2..e7d03f25 100644 --- a/compiler/characterizer/functional.py +++ b/compiler/characterizer/functional.py @@ -81,7 +81,11 @@ class functional(simulation): self.create_graph() self.set_internal_spice_names() self.q_name, self.qbar_name = self.get_bit_name() - debug.info(2, "q name={0}\nqbar name={1}".format(self.q_name, self.qbar_name)) + debug.info(2, "q:\t\t{0}".format(self.q_name)) + debug.info(2, "qbar:\t{0}".format(self.qbar_name)) + debug.info(2, "s_en:\t{0}".format(self.sen_name)) + debug.info(2, "bl:\t{0}".format(self.bl_name)) + debug.info(2, "br:\t{0}".format(self.br_name)) # Number of checks can be changed self.num_cycles = cycles @@ -346,8 +350,7 @@ class functional(simulation): random_value = random.randint(1, self.max_data - 1) data_bits = binary_repr(random_value, self.word_size) if self.num_spare_cols>0: - # Don't use 0 or max value - random_value = random.randint(1, self.max_col_data - 1) + random_value = random.randint(0, self.max_col_data) spare_bits = binary_repr(random_value, self.num_spare_cols) else: spare_bits = "" @@ -403,11 +406,11 @@ class functional(simulation): # Write important signals to stim file self.sf.write("\n\n* Important signals for debug\n") - self.sf.write("* bl: {0}\n".format(self.bl_name.format(port))) - self.sf.write("* br: {0}\n".format(self.br_name.format(port))) - self.sf.write("* s_en: {0}\n".format(self.sen_name)) - self.sf.write("* q: {0}\n".format(self.q_name)) - self.sf.write("* qbar: {0}\n".format(self.qbar_name)) + self.sf.write("* bl:\t{0}\n".format(self.bl_name.format(port))) + self.sf.write("* br:\t{0}\n".format(self.br_name.format(port))) + self.sf.write("* s_en:\t{0}\n".format(self.sen_name)) + self.sf.write("* q:\t{0}\n".format(self.q_name)) + self.sf.write("* qbar:\t{0}\n".format(self.qbar_name)) # Write debug comments to stim file self.sf.write("\n\n* Sequence of operations\n") diff --git a/compiler/characterizer/simulation.py b/compiler/characterizer/simulation.py index d7539dc4..98729bba 100644 --- a/compiler/characterizer/simulation.py +++ b/compiler/characterizer/simulation.py @@ -515,8 +515,6 @@ class simulation(): self.sen_name = sen_with_port debug.warning("Error occurred while determining SEN name. Can cause faults in simulation.") - debug.info(2, "s_en name = {}".format(self.sen_name)) - 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)) @@ -537,11 +535,12 @@ 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 name = {}".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) - debug.info(2, "bl name={}, br name={}".format(self.bl_name, self.br_name)) + # debug.info(2, "bl name={0}".format(self.bl_name)) + # debug.info(2, "br name={0}".format(self.br_name)) def get_sen_name(self, paths, assumed_port=None): """ From 3d2b192682d4ed68a7e156111020c3442c7b4bde Mon Sep 17 00:00:00 2001 From: mrg Date: Thu, 1 Jul 2021 12:49:30 -0700 Subject: [PATCH 3/4] Add conditional spare row/col to a couple unit tests --- compiler/tests/05_bitcell_array_test.py | 10 +++++++++- compiler/tests/50_riscv_1rw_func_test.py | 15 +++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/compiler/tests/05_bitcell_array_test.py b/compiler/tests/05_bitcell_array_test.py index e641bbdb..916a4356 100755 --- a/compiler/tests/05_bitcell_array_test.py +++ b/compiler/tests/05_bitcell_array_test.py @@ -23,7 +23,15 @@ class array_test(openram_test): globals.init_openram(config_file) debug.info(2, "Testing 8x8 array for 6t_cell") - a = factory.create(module_type="bitcell_array", cols=8, rows=8) + + if OPTS.tech_name == "sky130": + num_spare_rows = 1 + num_spare_cols = 1 + else: + num_spare_rows = 0 + num_spare_cols = 0 + + a = factory.create(module_type="bitcell_array", cols=8 + num_spare_cols, rows=8 + num_spare_rows) self.local_check(a) globals.end_openram() diff --git a/compiler/tests/50_riscv_1rw_func_test.py b/compiler/tests/50_riscv_1rw_func_test.py index 00921ec4..10fd0bb1 100755 --- a/compiler/tests/50_riscv_1rw_func_test.py +++ b/compiler/tests/50_riscv_1rw_func_test.py @@ -24,6 +24,15 @@ class riscv_func_test(openram_test): globals.init_openram(config_file) OPTS.analytical_delay = False OPTS.netlist_only = True + OPTS.trim_netlist = False + + if OPTS.tech_name == "sky130": + num_spare_rows = 1 + num_spare_cols = 1 + else: + num_spare_rows = 0 + num_spare_cols = 0 + OPTS.num_rw_ports = 1 OPTS.num_w_ports = 0 OPTS.num_r_ports = 0 @@ -38,7 +47,9 @@ class riscv_func_test(openram_test): c = sram_config(word_size=32, write_size=8, num_words=32, - num_banks=1) + num_banks=1, + num_spare_cols=num_spare_cols, + num_spare_rows=num_spare_rows) c.words_per_row=1 c.recompute_sizes() debug.info(1, "Functional test RISC-V memory" @@ -48,7 +59,7 @@ class riscv_func_test(openram_test): c.num_banks)) s = factory.create(module_type="sram", sram_config=c) corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) - f = functional(s.s, corner=corner, cycles=50) + f = functional(s.s, corner=corner, cycles=25) (fail, error) = f.run() self.assertTrue(fail, error) From 6be24d4c6c0b1ff650099871b3c226e88252a550 Mon Sep 17 00:00:00 2001 From: mrg Date: Thu, 1 Jul 2021 12:50:20 -0700 Subject: [PATCH 4/4] Only 25 cycles --- compiler/tests/50_riscv_1rw1r_func_test.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/tests/50_riscv_1rw1r_func_test.py b/compiler/tests/50_riscv_1rw1r_func_test.py index c643621e..100d5bc7 100755 --- a/compiler/tests/50_riscv_1rw1r_func_test.py +++ b/compiler/tests/50_riscv_1rw1r_func_test.py @@ -24,6 +24,8 @@ class riscv_func_test(openram_test): globals.init_openram(config_file) OPTS.analytical_delay = False OPTS.netlist_only = True + OPTS.trim_netlist = False + OPTS.num_rw_ports = 1 OPTS.num_w_ports = 0 OPTS.num_r_ports = 1 @@ -48,7 +50,7 @@ class riscv_func_test(openram_test): c.num_banks)) s = factory.create(module_type="sram", sram_config=c) corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) - f = functional(s.s, corner=corner, cycles=50) + f = functional(s.s, corner=corner, cycles=25) (fail, error) = f.run() self.assertTrue(fail, error)