From 969cca28e4d94a4baec6cdc9522d1842a2f21aab Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Fri, 6 Sep 2019 07:16:50 -0700 Subject: [PATCH 01/12] Enable sensing during writes. Need to add dedicated test. --- compiler/characterizer/functional.py | 6 +++--- compiler/modules/control_logic.py | 14 +++----------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/compiler/characterizer/functional.py b/compiler/characterizer/functional.py index 49907b4b..a9489b23 100644 --- a/compiler/characterizer/functional.py +++ b/compiler/characterizer/functional.py @@ -70,7 +70,7 @@ class functional(simulation): if feasible_period: #period defaults to tech.py feasible period otherwise. self.period = feasible_period # Generate a random sequence of reads and writes - self.write_random_memory_sequence() + self.create_random_memory_sequence() # Run SPICE simulation self.write_functional_stimulus() @@ -84,7 +84,7 @@ class functional(simulation): # Check read values with written values. If the values do not match, return an error. return self.check_stim_results() - def write_random_memory_sequence(self): + def create_random_memory_sequence(self): if self.write_size: rw_ops = ["noop", "write", "partial_write", "read"] w_ops = ["noop", "write", "partial_write"] @@ -187,7 +187,7 @@ class functional(simulation): self.add_noop_all_ports(comment, "0"*self.addr_size, "0"*self.word_size, "0"*self.num_wmasks) def read_stim_results(self): - # Extrat dout values from spice timing.lis + # Extract dout values from spice timing.lis for (word, dout_port, eo_period, check) in self.write_check: sp_read_value = "" for bit in range(self.word_size): diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py index 06883125..20f4997f 100644 --- a/compiler/modules/control_logic.py +++ b/compiler/modules/control_logic.py @@ -639,14 +639,11 @@ class control_logic(design.design): def create_sen_row(self): """ Create the sense enable buffer. """ - if self.port_type=="rw": - input_name = "we_bar" - else: - input_name = "cs_bar" # GATE FOR S_EN + # Uses cs_bar (not we_bar) for feed-thru reads self.s_en_gate_inst = self.add_inst(name="buf_s_en_and", mod=self.sen_and3) - self.connect_inst(["rbl_bl_delay", "gated_clk_bar", input_name, "s_en", "vdd", "gnd"]) + self.connect_inst(["rbl_bl_delay", "gated_clk_bar", "cs_bar", "s_en", "vdd", "gnd"]) def place_sen_row(self,row): @@ -659,12 +656,7 @@ class control_logic(design.design): def route_sen(self): - if self.port_type=="rw": - input_name = "we_bar" - else: - input_name = "cs_bar" - - sen_map = zip(["A", "B", "C"], ["rbl_bl_delay", "gated_clk_bar", input_name]) + sen_map = zip(["A", "B", "C"], ["rbl_bl_delay", "gated_clk_bar", "cs_bar"]) self.connect_vertical_bus(sen_map, self.s_en_gate_inst, self.rail_offsets) self.connect_output(self.s_en_gate_inst, "Z", "s_en") From 6bee66f9dc0899a07d5aa77788e9029a6b1602fc Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Fri, 6 Sep 2019 09:29:23 -0700 Subject: [PATCH 02/12] Forgot to add cs_bar to rw port rails. --- compiler/modules/control_logic.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py index 20f4997f..1119f588 100644 --- a/compiler/modules/control_logic.py +++ b/compiler/modules/control_logic.py @@ -42,7 +42,7 @@ class control_logic(design.design): 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. + # Determines how much larger the sen delay should be. Accounts for possible error in model. self.wl_timing_tolerance = 1 self.wl_stage_efforts = None self.sen_stage_efforts = None @@ -66,7 +66,7 @@ class control_logic(design.design): """ Create layout and route between modules """ self.place_instances() self.route_all() - #self.add_lvs_correspondence_points() + # self.add_lvs_correspondence_points() self.add_boundary() self.DRC_LVS() @@ -343,7 +343,7 @@ class control_logic(design.design): # list of output control signals (for making a vertical bus) if self.port_type == "rw": - self.internal_bus_list = ["rbl_bl_delay_bar", "rbl_bl_delay", "gated_clk_bar", "gated_clk_buf", "we", "clk_buf", "we_bar", "cs"] + self.internal_bus_list = ["rbl_bl_delay_bar", "rbl_bl_delay", "gated_clk_bar", "gated_clk_buf", "we", "clk_buf", "cs_bar", "cs"] elif self.port_type == "r": self.internal_bus_list = ["rbl_bl_delay_bar", "rbl_bl_delay", "gated_clk_bar", "gated_clk_buf", "clk_buf", "cs_bar", "cs"] else: @@ -733,7 +733,7 @@ class control_logic(design.design): def route_dffs(self): if self.port_type == "rw": - dff_out_map = zip(["dout_bar_0", "dout_bar_1", "dout_1"], ["cs", "we", "we_bar"]) + dff_out_map = zip(["dout_bar_0", "dout_bar_1", "dout_0"], ["cs", "we", "cs_bar"]) elif self.port_type == "r": dff_out_map = zip(["dout_bar_0", "dout_0"], ["cs", "cs_bar"]) else: From 86c22c8904c673ff46d60f039b24da43cf68ebd1 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Fri, 6 Sep 2019 12:09:12 -0700 Subject: [PATCH 03/12] Clean and simplify simulation code. Feedthru check added. --- compiler/characterizer/delay.py | 41 ++++-- compiler/characterizer/functional.py | 184 ++++++++++++++++----------- compiler/characterizer/simulation.py | 139 +++++++++++++------- 3 files changed, 232 insertions(+), 132 deletions(-) diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index 31dc898a..2a8d5293 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -1180,44 +1180,57 @@ class delay(simulation): wmask_zeroes = "0"*self.num_wmasks if self.t_current == 0: - self.add_noop_all_ports("Idle cycle (no positive clock edge)", - inverse_address, data_zeros,wmask_zeroes) + self.add_noop_all_ports("Idle cycle (no positive clock edge)") self.add_write("W data 1 address {}".format(inverse_address), - inverse_address,data_ones,wmask_ones,write_port) + inverse_address, + data_ones, + wmask_ones, + write_port) self.add_write("W data 0 address {} to write value".format(self.probe_address), - self.probe_address,data_zeros,wmask_ones,write_port) + self.probe_address, + data_zeros, + wmask_ones, + write_port) self.measure_cycles[write_port][sram_op.WRITE_ZERO] = len(self.cycle_times)-1 # This also ensures we will have a H->L transition on the next read self.add_read("R data 1 address {} to set dout caps".format(inverse_address), - inverse_address,data_zeros,wmask_ones,read_port) + inverse_address, + read_port) self.add_read("R data 0 address {} to check W0 worked".format(self.probe_address), - self.probe_address,data_zeros,wmask_ones,read_port) + self.probe_address, + read_port) self.measure_cycles[read_port][sram_op.READ_ZERO] = len(self.cycle_times)-1 - self.add_noop_all_ports("Idle cycle (if read takes >1 cycle)", - inverse_address,data_zeros,wmask_zeroes) + self.add_noop_all_ports("Idle cycle (if read takes >1 cycle)") self.add_write("W data 1 address {} to write value".format(self.probe_address), - self.probe_address,data_ones,wmask_ones,write_port) + self.probe_address, + data_ones, + wmask_ones, + write_port) self.measure_cycles[write_port][sram_op.WRITE_ONE] = len(self.cycle_times)-1 self.add_write("W data 0 address {} to clear din caps".format(inverse_address), - inverse_address,data_zeros,wmask_ones,write_port) + inverse_address, + data_zeros, + wmask_ones, + write_port) # This also ensures we will have a L->H transition on the next read self.add_read("R data 0 address {} to clear dout caps".format(inverse_address), - inverse_address,data_zeros,wmask_ones,read_port) + inverse_address, + read_port) self.add_read("R data 1 address {} to check W1 worked".format(self.probe_address), - self.probe_address,data_zeros,wmask_ones,read_port) + self.probe_address, + read_port) self.measure_cycles[read_port][sram_op.READ_ONE] = len(self.cycle_times)-1 - self.add_noop_all_ports("Idle cycle (if read takes >1 cycle))", - self.probe_address,data_zeros,wmask_zeroes) + self.add_noop_all_ports("Idle cycle (if read takes >1 cycle))") def get_available_port(self,get_read_port): diff --git a/compiler/characterizer/functional.py b/compiler/characterizer/functional.py index a9489b23..6dbb756c 100644 --- a/compiler/characterizer/functional.py +++ b/compiler/characterizer/functional.py @@ -6,6 +6,7 @@ # All rights reserved. # import sys,re,shutil +import copy import collections from design import design import debug @@ -49,23 +50,14 @@ class functional(simulation): self.create_graph() self.set_internal_spice_names() - self.initialize_wmask() - # Number of checks can be changed self.num_cycles = 15 # This is to have ordered keys for random selection self.stored_words = collections.OrderedDict() - self.write_check = [] self.read_check = [] + self.read_results = [] - def initialize_wmask(self): - self.wmask = "" - if self.write_size: - # initialize all wmask bits to 1 - for bit in range(self.num_wmasks): - self.wmask += "1" - def run(self, feasible_period=None): if feasible_period: #period defaults to tech.py feasible period otherwise. self.period = feasible_period @@ -83,7 +75,25 @@ class functional(simulation): # Check read values with written values. If the values do not match, return an error. return self.check_stim_results() - + + def check_lengths(self): + """ Do a bunch of assertions. """ + + for port in self.all_ports: + checks = [] + if port in self.read_ports: + checks.append((self.addr_value[port],"addr")) + if port in self.write_ports: + checks.append((self.data_value[port],"data")) + checks.append((self.wmask_value[port],"wmask")) + + for (val, name) in checks: + debug.check(len(self.cycle_times)==len(val), + "Port {2} lengths don't match. {0} clock values, {1} {3} values".format(len(self.cycle_times), + len(val), + port, + name)) + def create_random_memory_sequence(self): if self.write_size: rw_ops = ["noop", "write", "partial_write", "read"] @@ -92,35 +102,61 @@ class functional(simulation): rw_ops = ["noop", "write", "read"] w_ops = ["noop", "write"] r_ops = ["noop", "read"] - rw_read_din_data = "0"*self.word_size - check = 0 - # First cycle idle - comment = self.gen_cycle_comment("noop", "0"*self.word_size, "0"*self.addr_size, self.wmask, 0, self.t_current) - self.add_noop_all_ports(comment, "0"*self.addr_size, "0"*self.word_size, "0"*self.num_wmasks) - - # Write at least once + # First cycle idle is always an idle cycle + 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 at least once. For multiport, it ensures that + # feedthru reads also work. + + # Port 0 may not be a write port, so find the first write + # port. + first_write_port = self.write_ports[0] addr = self.gen_addr() word = self.gen_data() - comment = self.gen_cycle_comment("write", word, addr, self.wmask, 0, self.t_current) - self.add_write(comment, addr, word, self.wmask, 0) + comment = self.gen_cycle_comment("write", word, addr, "1"*self.num_wmasks, first_write_port, self.t_current) + self.add_write_one_port(comment, addr, word, "1"*self.num_wmasks, first_write_port) self.stored_words[addr] = word - # Read at least once. For multiport, it is important that one read cycle uses all RW and R port to read from the same address simultaniously. - # This will test the viablilty of the transistor sizing in the bitcell. - for port in self.all_ports: - if port in self.write_ports: - self.add_noop_one_port("0"*self.addr_size, "0"*self.word_size, "0"*self.num_wmasks, port) - else: - comment = self.gen_cycle_comment("read", word, addr, self.wmask, port, self.t_current) - self.add_read_one_port(comment, addr, rw_read_din_data, "0"*self.num_wmasks, port) - self.write_check.append([word, "{0}{1}".format(self.dout_name,port), self.t_current+self.period, check]) - check += 1 + # The read port should not be the same as the write port being written. + other_read_ports = copy.copy(self.read_ports) + other_read_ports.remove(first_write_port) + # If we have one, check the feedthru read worked. + if len(other_read_ports)>0: + first_read_port = other_read_ports[0] + comment = self.gen_cycle_comment("read (feedthru)", word, addr, "0"*self.num_wmasks, first_read_port, self.t_current) + self.add_read_one_port(comment, addr, first_read_port) + self.add_read_check(word, first_read_port) + + # All other ports are noops. + other_ports = copy.copy(self.all_ports) + other_ports.remove(first_write_port) + other_ports.remove(first_read_port) + for port in other_ports: + self.add_nop_one_port(port) self.cycle_times.append(self.t_current) self.t_current += self.period + self.check_lengths() - # Perform a random sequence of writes and reads on random ports, using random addresses and random words - # and random write masks (if applicable) + # 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 + # address simultaniously. This will test the viablilty of the + # transistor sizing in the bitcell. + for port in self.all_ports: + if port in self.write_ports: + self.add_noop_one_port(port) + else: + comment = self.gen_cycle_comment("read", word, addr, "0"*self.num_wmasks, port, self.t_current) + self.add_read_one_port(comment, addr, port) + self.add_read_check(word, port) + self.cycle_times.append(self.t_current) + self.t_current += self.period + self.check_lengths() + + # 3. Perform a random sequence of writes and reads on random + # ports, using random addresses and random words and random + # write masks (if applicable) for i in range(self.num_cycles): w_addrs = [] for port in self.all_ports: @@ -132,63 +168,69 @@ class functional(simulation): op = random.choice(r_ops) if op == "noop": - addr = "0"*self.addr_size - word = "0"*self.word_size - wmask = "0" * self.num_wmasks - self.add_noop_one_port(addr, word, wmask, port) + self.add_noop_one_port(port) elif op == "write": addr = self.gen_addr() - word = self.gen_data() # two ports cannot write to the same address if addr in w_addrs: - self.add_noop_one_port("0"*self.addr_size, "0"*self.word_size, "0"*self.num_wmasks, port) + self.add_noop_one_port(port) else: - comment = self.gen_cycle_comment("write", word, addr, self.wmask, port, self.t_current) - self.add_write_one_port(comment, addr, word, self.wmask, port) + word = self.gen_data() + comment = self.gen_cycle_comment("write", word, addr, "1"*self.num_wmasks, port, self.t_current) + self.add_write_one_port(comment, addr, word, "1"*self.num_wmasks, port) self.stored_words[addr] = word w_addrs.append(addr) elif op == "partial_write": # write only to a word that's been written to (addr,old_word) = self.get_data() - word = self.gen_data() - wmask = self.gen_wmask() - new_word = word - for bit in range(len(wmask)): - # When the write mask's bits are 0, the old data values should appear in the new word - # as to not overwrite the old values - if wmask[bit] == "0": - lower = bit * self.write_size - upper = lower + self.write_size - 1 - new_word = new_word[:lower] + old_word[lower:upper+1] + new_word[upper + 1:] # two ports cannot write to the same address if addr in w_addrs: - self.add_noop_one_port("0"*self.addr_size, "0"*self.word_size, "0"*self.num_wmasks, port) + self.add_noop_one_port(port) else: + word = self.gen_data() + wmask = self.gen_wmask() + new_word = word + for bit in range(len(wmask)): + # When the write mask's bits are 0, the old data values should appear in the new word + # as to not overwrite the old values + if wmask[bit] == "0": + lower = bit * self.write_size + upper = lower + self.write_size - 1 + new_word = new_word[:lower] + old_word[lower:upper+1] + new_word[upper + 1:] comment = self.gen_cycle_comment("partial_write", word, addr, wmask, port, self.t_current) self.add_write_one_port(comment, addr, word, wmask, port) self.stored_words[addr] = new_word w_addrs.append(addr) else: (addr,word) = random.choice(list(self.stored_words.items())) - # cannot read from an address that is currently being written to - if addr in w_addrs: - self.add_noop_one_port("0"*self.addr_size, "0"*self.word_size, "0"*self.num_wmasks, port) - else: - comment = self.gen_cycle_comment("read", word, addr, self.wmask, port, self.t_current) - self.add_read_one_port(comment, addr, rw_read_din_data, "0"*self.num_wmasks, port) - self.write_check.append([word, "{0}{1}".format(self.dout_name,port), self.t_current+self.period, check]) - check += 1 + ## cannot read from an address that is currently being written to + # Yes, you can!! + #if addr in w_addrs: + # self.add_noop_one_port(port) + #else: + comment = self.gen_cycle_comment("read", word, addr, "0"*self.num_wmasks, port, self.t_current) + self.add_read_one_port(comment, addr, port) + self.add_read_check(word, port) self.cycle_times.append(self.t_current) self.t_current += self.period # Last cycle idle needed to correctly measure the value on the second to last clock edge - comment = self.gen_cycle_comment("noop", "0"*self.word_size, "0"*self.addr_size, self.wmask, 0, self.t_current) - self.add_noop_all_ports(comment, "0"*self.addr_size, "0"*self.word_size, "0"*self.num_wmasks) - + 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) + + def add_read_check(self, word, port): + """ Add to the check array to ensure a read works. """ + try: + self.check + except: + self.check = 0 + self.read_check.append([word, "{0}{1}".format(self.dout_name,port), self.t_current+self.period, self.check]) + self.check += 1 + def read_stim_results(self): # Extract dout values from spice timing.lis - for (word, dout_port, eo_period, check) in self.write_check: + for (word, dout_port, eo_period, check) in self.read_check: sp_read_value = "" for bit in range(self.word_size): value = parse_spice_list("timing", "v{0}.{1}ck{2}".format(dout_port.lower(),bit,check)) @@ -205,17 +247,17 @@ class functional(simulation): self.v_high) return (0, error) - self.read_check.append([sp_read_value, dout_port, eo_period, check]) + self.read_results.append([sp_read_value, dout_port, eo_period, check]) return (1, "SUCCESS") def check_stim_results(self): - for i in range(len(self.write_check)): - if self.write_check[i][0] != self.read_check[i][0]: - error = "FAILED: {0} value {1} does not match written value {2} read during cycle {3} at time {4}n".format(self.read_check[i][1], + for i in range(len(self.read_check)): + if self.read_check[i][0] != self.read_results[i][0]: + error = "FAILED: {0} value {1} does not match written value {2} read during cycle {3} at time {4}n".format(self.read_results[i][1], + self.read_results[i][0], self.read_check[i][0], - self.write_check[i][0], - int((self.read_check[i][2]-self.period)/self.period), - self.read_check[i][2]) + int((self.read_results[i][2]-self.period)/self.period), + self.read_results[i][2]) return(0, error) return(1, "SUCCESS") @@ -359,7 +401,7 @@ class functional(simulation): # Generate dout value measurements self.sf.write("\n * Generation of dout measurements\n") - for (word, dout_port, eo_period, check) in self.write_check: + for (word, dout_port, eo_period, check) in self.read_check: t_intital = eo_period - 0.01*self.period t_final = eo_period + 0.01*self.period for bit in range(self.word_size): diff --git a/compiler/characterizer/simulation.py b/compiler/characterizer/simulation.py index 5203c732..adbe5f5f 100644 --- a/compiler/characterizer/simulation.py +++ b/compiler/characterizer/simulation.py @@ -60,8 +60,10 @@ class simulation(): port_info=(len(self.all_ports),self.write_ports,self.read_ports), abits=self.addr_size, dbits=self.word_size) - debug.check(len(self.sram.pins) == len(self.pins), "Number of pins generated for characterization \ - do match pins of SRAM\nsram.pins = {0}\npin_names = {1}".format(self.sram.pins,self.pins)) + debug.check(len(self.sram.pins) == len(self.pins), + "Number of pins generated for characterization \ + do match pins of SRAM\nsram.pins = {0}\npin_names = {1}".format(self.sram.pins, + self.pins)) #This is TODO once multiport control has been finalized. #self.control_name = "CSB" @@ -71,13 +73,18 @@ class simulation(): 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 self.all_ports] - self.web_values = [[] for port in self.readwrite_ports] - - # 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 self.all_ports] - self.data_values = [[[] for bit in range(self.word_size)] for port in self.write_ports] - self.wmask_values = [[[] for bit in range(self.num_wmasks)] for port in self.write_ports] + self.csb_values = {port:[] for port in self.all_ports} + self.web_values = {port:[] for port in self.readwrite_ports} + + # Raw values added as a bit vector + self.addr_value = {port:[] for port in self.all_ports} + self.data_value = {port:[] for port in self.write_ports} + self.wmask_value = {port:[] for port in self.write_ports} + + # Three dimensional list to handle each addr and data bits for each port over the number of checks + self.addr_values = {port:[[] for bit in range(self.addr_size)] for port in self.all_ports} + self.data_values = {port:[[] for bit in range(self.word_size)] for port in self.write_ports} + self.wmask_values = {port:[[] for bit in range(self.num_wmasks)] for port in self.write_ports} # For generating comments in SPICE stimulus self.cycle_comments = [] @@ -105,7 +112,8 @@ class simulation(): def add_data(self, data, port): """ Add the array of data values """ debug.check(len(data)==self.word_size, "Invalid data word size.") - + + self.data_value[port].append(data) bit = self.word_size - 1 for c in data: if c=="0": @@ -116,10 +124,12 @@ class simulation(): 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.") + self.addr_value[port].append(address) bit = self.addr_size - 1 for c in address: if c=="0": @@ -130,10 +140,12 @@ class simulation(): debug.error("Non-binary address string",1) bit -= 1 + def add_wmask(self, wmask, port): """ Add the array of address values """ debug.check(len(wmask) == self.num_wmasks, "Invalid wmask size.") + self.wmask_value[port].append(wmask) bit = self.num_wmasks - 1 for c in wmask: if c == "0": @@ -143,10 +155,13 @@ class simulation(): else: debug.error("Non-binary wmask string", 1) bit -= 1 - + + def add_write(self, comment, address, data, wmask, port): """ Add the control values for a write cycle. """ - debug.check(port in self.write_ports, "Cannot add write cycle to a read port. Port {0}, Write Ports {1}".format(port, self.write_ports)) + debug.check(port in self.write_ports, + "Cannot add write cycle to a read port. Port {0}, Write Ports {1}".format(port, + self.write_ports)) debug.info(2, comment) self.fn_cycle_comments.append(comment) self.append_cycle_comment(port, comment) @@ -159,16 +174,16 @@ class simulation(): self.add_address(address,port) self.add_wmask(wmask,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 self.all_ports: if unselected_port != port: - self.add_noop_one_port(address, noop_data, wmask, unselected_port) + self.add_noop_one_port(unselected_port) - def add_read(self, comment, address, din_data, wmask, port): + def add_read(self, comment, address, port): """ Add the control values for a read cycle. """ - debug.check(port in self.read_ports, "Cannot add read cycle to a write port. Port {0}, Read Ports {1}".format(port, self.read_ports)) + debug.check(port in self.read_ports, + "Cannot add read cycle to a write port. Port {0}, Read Ports {1}".format(port, + self.read_ports)) debug.info(2, comment) self.fn_cycle_comments.append(comment) self.append_cycle_comment(port, comment) @@ -176,21 +191,26 @@ class simulation(): 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(din_data,port) - self.add_wmask(wmask,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 + # If the port is also a readwrite then add + # the same value as previous cycle + if port in self.write_ports: + try: + self.add_data(self.data_value[port][-1], port) + except: + self.add_data("0"*self.word_size, port) + try: + self.add_wmask(self.wmask_value[port][-1], port) + except: + self.add_wmask("0"*self.num_wmasks, port) + #Add noops to all other ports. for unselected_port in self.all_ports: if unselected_port != port: - self.add_noop_one_port(address, noop_data, wmask, unselected_port) + self.add_noop_one_port(unselected_port) - def add_noop_all_ports(self, comment, address, data, wmask): + def add_noop_all_ports(self, comment): """ Add the control values for a noop to all ports. """ debug.info(2, comment) self.fn_cycle_comments.append(comment) @@ -200,39 +220,64 @@ class simulation(): self.t_current += self.period for port in self.all_ports: - self.add_noop_one_port(address, data, wmask, port) + self.add_noop_one_port(port) def add_write_one_port(self, comment, address, data, wmask, port): """ Add the control values for a write cycle. Does not increment the period. """ - debug.check(port in self.write_ports, "Cannot add write cycle to a read port. Port {0}, Write Ports {1}".format(port, self.write_ports)) + debug.check(port in self.write_ports, + "Cannot add write cycle to a read port. Port {0}, Write Ports {1}".format(port, + self.write_ports)) debug.info(2, comment) self.fn_cycle_comments.append(comment) self.add_control_one_port(port, "write") - self.add_data(data,port) - self.add_address(address,port) - self.add_wmask(wmask,port) + self.add_data(data, port) + self.add_address(address, port) + self.add_wmask(wmask, port) - def add_read_one_port(self, comment, address, din_data, wmask, port): + def add_read_one_port(self, comment, address, port): """ Add the control values for a read cycle. Does not increment the period. """ - debug.check(port in self.read_ports, "Cannot add read cycle to a write port. Port {0}, Read Ports {1}".format(port, self.read_ports)) + debug.check(port in self.read_ports, + "Cannot add read cycle to a write port. Port {0}, Read Ports {1}".format(port, + self.read_ports)) debug.info(2, comment) self.fn_cycle_comments.append(comment) 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(din_data,port) - self.add_wmask(wmask,port) self.add_address(address, port) + # If the port is also a readwrite then add + # the same value as previous cycle + if port in self.write_ports: + try: + self.add_data(self.data_value[port][-1], port) + except: + self.add_data("0"*self.word_size, port) + try: + self.add_wmask(self.wmask_value[port][-1], port) + except: + self.add_wmask("0"*self.num_wmasks, port) + - def add_noop_one_port(self, address, data, wmask, port): + def add_noop_one_port(self, port): """ Add the control values for a noop to a single port. Does not increment the period. """ self.add_control_one_port(port, "noop") + + try: + self.add_address(self.addr_value[port][-1], port) + except: + self.add_address("0"*self.addr_size, port) + + # If the port is also a readwrite then add + # the same value as previous cycle if port in self.write_ports: - self.add_data(data,port) - self.add_wmask(wmask,port) - self.add_address(address, port) + try: + self.add_data(self.data_value[port][-1], port) + except: + self.add_data("0"*self.word_size, port) + try: + self.add_wmask(self.wmask_value[port][-1], port) + except: + self.add_wmask("0"*self.num_wmasks, port) def append_cycle_comment(self, port, comment): """Add comment to list to be printed in stimulus file""" @@ -240,16 +285,16 @@ class simulation(): 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), - port, - time, - time_spacing, - comment)) + port, + time, + time_spacing, + comment)) def gen_cycle_comment(self, op, word, addr, wmask, port, t_current): if op == "noop": comment = "\tIdle during cycle {0} ({1}ns - {2}ns)".format(int(t_current/self.period), - t_current, - t_current+self.period) + t_current, + t_current+self.period) elif op == "write": comment = "\tWriting {0} to address {1} (from port {2}) during cycle {3} ({4}ns - {5}ns)".format(word, addr, From b5b0e35c8a8c3f968f9ee334e9cad9d1566e456a Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Fri, 6 Sep 2019 12:29:28 -0700 Subject: [PATCH 04/12] Fix syntax error. --- compiler/characterizer/functional.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/characterizer/functional.py b/compiler/characterizer/functional.py index 6dbb756c..cb81f128 100644 --- a/compiler/characterizer/functional.py +++ b/compiler/characterizer/functional.py @@ -122,17 +122,17 @@ class functional(simulation): # The read port should not be the same as the write port being written. other_read_ports = copy.copy(self.read_ports) other_read_ports.remove(first_write_port) + other_ports = copy.copy(self.all_ports) + other_ports.remove(first_write_port) # If we have one, check the feedthru read worked. if len(other_read_ports)>0: first_read_port = other_read_ports[0] comment = self.gen_cycle_comment("read (feedthru)", word, addr, "0"*self.num_wmasks, first_read_port, self.t_current) self.add_read_one_port(comment, addr, first_read_port) self.add_read_check(word, first_read_port) + other_ports.remove(first_read_port) # All other ports are noops. - other_ports = copy.copy(self.all_ports) - other_ports.remove(first_write_port) - other_ports.remove(first_read_port) for port in other_ports: self.add_nop_one_port(port) self.cycle_times.append(self.t_current) From 93c89895c96d2bcf57952673e17f2b9f129d36b5 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Fri, 6 Sep 2019 14:58:47 -0700 Subject: [PATCH 05/12] Remove unused test structures --- compiler/characterizer/stimuli.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/compiler/characterizer/stimuli.py b/compiler/characterizer/stimuli.py index 41a3428f..58a9e3ed 100644 --- a/compiler/characterizer/stimuli.py +++ b/compiler/characterizer/stimuli.py @@ -96,22 +96,6 @@ class stimuli(): self.sf.write(".ENDS test_{0}\n\n".format(buffer_name)) - def inst_buffer(self, buffer_name, signal_list): - """ Adds buffers to each top level signal that is in signal_list (only for sim purposes) """ - for signal in signal_list: - self.sf.write("X{0}_buffer {0} {0}_buf {1} {2} test_{3}\n".format(signal, - "test"+self.vdd_name, - "test"+self.gnd_name, - buffer_name)) - - - def inst_inverter(self, signal_list): - """ Adds inv for each signal that needs its inverted version (only for sim purposes) """ - for signal in signal_list: - self.sf.write("X{0}_inv {0} {0}_inv {1} {2} test_inv\n".format(signal, - "test"+self.vdd_name, - "test"+self.gnd_name)) - def gen_pulse(self, sig_name, v1, v2, offset, period, t_rise, t_fall): """ @@ -276,9 +260,6 @@ class stimuli(): """ Writes supply voltage statements """ gnd_node_name = "0" self.sf.write("V{0} {0} {1} {2}\n".format(self.vdd_name, gnd_node_name, self.voltage)) - # This is for the test power supply - self.sf.write("V{0} {0} {1} {2}\n".format("test"+self.vdd_name, gnd_node_name, self.voltage)) - self.sf.write("V{0} {0} {1} {2}\n".format("test"+self.gnd_name, gnd_node_name, 0.0)) #Adding a commented out supply for simulators where gnd and 0 are not global grounds. self.sf.write("\n*Nodes gnd and 0 are the same global ground node in ngspice/hspice/xa. Otherwise, this source may be needed.\n") From e5db02f7d87aa2dc685aefd7a21e8aa25c996211 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Fri, 6 Sep 2019 14:59:23 -0700 Subject: [PATCH 06/12] Fix wrong function. Except unknown ports. --- compiler/characterizer/functional.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/compiler/characterizer/functional.py b/compiler/characterizer/functional.py index cb81f128..9e9b2596 100644 --- a/compiler/characterizer/functional.py +++ b/compiler/characterizer/functional.py @@ -121,20 +121,27 @@ class functional(simulation): # The read port should not be the same as the write port being written. other_read_ports = copy.copy(self.read_ports) - other_read_ports.remove(first_write_port) - other_ports = copy.copy(self.all_ports) - other_ports.remove(first_write_port) + try: + other_read_ports.remove(first_write_port) + except: + pass # If we have one, check the feedthru read worked. if len(other_read_ports)>0: first_read_port = other_read_ports[0] comment = self.gen_cycle_comment("read (feedthru)", word, addr, "0"*self.num_wmasks, first_read_port, self.t_current) self.add_read_one_port(comment, addr, first_read_port) self.add_read_check(word, first_read_port) - other_ports.remove(first_read_port) # All other ports are noops. + other_ports = copy.copy(self.all_ports) + other_ports.remove(first_write_port) + try: + other_ports.remove(first_read_port) + except: + pass + for port in other_ports: - self.add_nop_one_port(port) + self.add_noop_one_port(port) self.cycle_times.append(self.t_current) self.t_current += self.period self.check_lengths() From 322af0ec09d607e5e43d18e8a2de1e6fc8222772 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Sat, 7 Sep 2019 20:04:48 -0700 Subject: [PATCH 07/12] Remove sense enable during writes --- compiler/modules/control_logic.py | 55 ++++++++++++++++++------------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py index 1119f588..e62770d8 100644 --- a/compiler/modules/control_logic.py +++ b/compiler/modules/control_logic.py @@ -66,7 +66,7 @@ class control_logic(design.design): """ Create layout and route between modules """ self.place_instances() self.route_all() - # self.add_lvs_correspondence_points() + #self.add_lvs_correspondence_points() self.add_boundary() self.DRC_LVS() @@ -201,7 +201,7 @@ class control_logic(design.design): 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 """ - #FIXME: The minimum was 2 fanout, now it will not pass DRC unless it is 3. Why? + # FIXME: The minimum was 2 fanout, now it will not pass DRC unless it is 3. Why? delay_fanout = 3 # This can be anything >=3 # Model poorly captures delay of the column mux. Be pessismistic for column mux if self.words_per_row >= 2: @@ -209,8 +209,8 @@ class control_logic(design.design): else: delay_stages = 2 - #Read ports have a shorter s_en delay. The model is not accurate enough to catch this difference - #on certain sram configs. + # Read ports have a shorter s_en delay. The model is not accurate enough to catch this difference + # on certain sram configs. if self.port_type == "r": delay_stages+=2 @@ -226,7 +226,7 @@ class control_logic(design.design): def does_sen_rise_fall_timing_match(self): """Compare the relative rise/fall delays of the sense amp enable and wordline""" self.set_sen_wl_delays() - #This is not necessarily more reliable than total delay in some cases. + # This is not necessarily more reliable than total delay in some cases. if (self.wl_delay_rise*self.wl_timing_tolerance >= self.sen_delay_rise or self.wl_delay_fall*self.wl_timing_tolerance >= self.sen_delay_fall): return False @@ -236,8 +236,9 @@ class control_logic(design.design): def does_sen_total_timing_match(self): """Compare the total delays of the sense amp enable and wordline""" self.set_sen_wl_delays() - #The sen delay must always be bigger than than the wl delay. This decides how much larger the sen delay must be before - #a re-size is warranted. + # The sen delay must always be bigger than than the wl + # delay. This decides how much larger the sen delay must be + # before a re-size is warranted. if self.wl_delay*self.wl_timing_tolerance >= self.sen_delay: return False else: @@ -250,14 +251,14 @@ class control_logic(design.design): debug.info(2, "Previous delay chain produced {} delay units".format(previous_delay_chain_delay)) delay_fanout = 3 # This can be anything >=2 - #The delay chain uses minimum sized inverters. There are (fanout+1)*stages inverters and each - #inverter adds 1 unit of delay (due to minimum size). This also depends on the pinv value + # The delay chain uses minimum sized inverters. There are (fanout+1)*stages inverters and each + # inverter adds 1 unit of delay (due to minimum size). This also depends on the pinv value required_delay = self.wl_delay*self.wl_timing_tolerance - (self.sen_delay-previous_delay_chain_delay) debug.check(required_delay > 0, "Cannot size delay chain to have negative delay") delay_stages = ceil(required_delay/(delay_fanout+1+self.inv_parasitic_delay)) if delay_stages%2 == 1: #force an even number of stages. delay_stages+=1 - #Fanout can be varied as well but is a little more complicated but potentially optimal. + # Fanout can be varied as well but is a little more complicated but potentially optimal. debug.info(1, "Setting delay chain to {} stages with {} fanout to match {} delay".format(delay_stages, delay_fanout, required_delay)) return (delay_stages, delay_fanout) @@ -268,16 +269,16 @@ class control_logic(design.design): debug.info(2, "Previous delay chain produced {} delay units".format(previous_delay_chain_delay)) fanout_rise = fanout_fall = 2 # This can be anything >=2 - #The delay chain uses minimum sized inverters. There are (fanout+1)*stages inverters and each - #inverter adds 1 unit of delay (due to minimum size). This also depends on the pinv value + # The delay chain uses minimum sized inverters. There are (fanout+1)*stages inverters and each + # inverter adds 1 unit of delay (due to minimum size). This also depends on the pinv value required_delay_fall = self.wl_delay_fall*self.wl_timing_tolerance - (self.sen_delay_fall-previous_delay_chain_delay/2) required_delay_rise = self.wl_delay_rise*self.wl_timing_tolerance - (self.sen_delay_rise-previous_delay_chain_delay/2) debug.info(2,"Required delays from chain: fall={}, rise={}".format(required_delay_fall,required_delay_rise)) - #If the fanout is different between rise/fall by this amount. Stage algorithm is made more pessimistic. + # If the fanout is different between rise/fall by this amount. Stage algorithm is made more pessimistic. WARNING_FANOUT_DIFF = 5 stages_close = False - #The stages need to be equal (or at least a even number of stages with matching rise/fall delays) + # The stages need to be equal (or at least a even number of stages with matching rise/fall delays) while True: stages_fall = self.calculate_stages_with_fixed_fanout(required_delay_fall,fanout_fall) stages_rise = self.calculate_stages_with_fixed_fanout(required_delay_rise,fanout_rise) @@ -294,8 +295,8 @@ class control_logic(design.design): fanout_rise = safe_fanout_rise fanout_fall = safe_fanout_fall break - #There should also be a condition to make sure the fanout does not get too large. - #Otherwise, increase the fanout of delay with the most stages, calculate new stages + # There should also be a condition to make sure the fanout does not get too large. + # Otherwise, increase the fanout of delay with the most stages, calculate new stages elif stages_fall>stages_rise: fanout_fall+=1 else: @@ -304,13 +305,13 @@ class control_logic(design.design): total_stages = max(stages_fall,stages_rise)*2 debug.info(1, "New Delay chain: stages={}, fanout_rise={}, fanout_fall={}".format(total_stages, fanout_rise, fanout_fall)) - #Creates interleaved fanout list of rise/fall delays. Assumes fall is the first stage. + # Creates interleaved fanout list of rise/fall delays. Assumes fall is the first stage. stage_list = [fanout_fall if i%2==0 else fanout_rise for i in range(total_stages)] return stage_list def calculate_stages_with_fixed_fanout(self, required_delay, fanout): from math import ceil - #Delay being negative is not an error. It implies that any amount of stages would have a negative effect on the overall delay + # Delay being negative is not an error. It implies that any amount of stages would have a negative effect on the overall delay if required_delay <= 3+self.inv_parasitic_delay: #3 is the minimum delay per stage (with pinv=0). return 1 delay_stages = ceil(required_delay/(fanout+1+self.inv_parasitic_delay)) @@ -343,7 +344,7 @@ class control_logic(design.design): # list of output control signals (for making a vertical bus) if self.port_type == "rw": - self.internal_bus_list = ["rbl_bl_delay_bar", "rbl_bl_delay", "gated_clk_bar", "gated_clk_buf", "we", "clk_buf", "cs_bar", "cs"] + self.internal_bus_list = ["rbl_bl_delay_bar", "rbl_bl_delay", "gated_clk_bar", "gated_clk_buf", "we", "clk_buf", "we_bar", "cs"] elif self.port_type == "r": self.internal_bus_list = ["rbl_bl_delay_bar", "rbl_bl_delay", "gated_clk_bar", "gated_clk_buf", "clk_buf", "cs_bar", "cs"] else: @@ -639,11 +640,14 @@ class control_logic(design.design): def create_sen_row(self): """ Create the sense enable buffer. """ + if self.port_type=="rw": + input_name = "we_bar" + else: + input_name = "cs_bar" # GATE FOR S_EN - # Uses cs_bar (not we_bar) for feed-thru reads self.s_en_gate_inst = self.add_inst(name="buf_s_en_and", mod=self.sen_and3) - self.connect_inst(["rbl_bl_delay", "gated_clk_bar", "cs_bar", "s_en", "vdd", "gnd"]) + self.connect_inst(["rbl_bl_delay", "gated_clk_bar", input_name, "s_en", "vdd", "gnd"]) def place_sen_row(self,row): @@ -656,7 +660,12 @@ class control_logic(design.design): def route_sen(self): - sen_map = zip(["A", "B", "C"], ["rbl_bl_delay", "gated_clk_bar", "cs_bar"]) + if self.port_type=="rw": + input_name = "we_bar" + else: + input_name = "cs_bar" + + sen_map = zip(["A", "B", "C"], ["rbl_bl_delay", "gated_clk_bar", input_name]) self.connect_vertical_bus(sen_map, self.s_en_gate_inst, self.rail_offsets) self.connect_output(self.s_en_gate_inst, "Z", "s_en") @@ -733,7 +742,7 @@ class control_logic(design.design): def route_dffs(self): if self.port_type == "rw": - dff_out_map = zip(["dout_bar_0", "dout_bar_1", "dout_0"], ["cs", "we", "cs_bar"]) + dff_out_map = zip(["dout_bar_0", "dout_bar_1", "dout_1"], ["cs", "we", "we_bar"]) elif self.port_type == "r": dff_out_map = zip(["dout_bar_0", "dout_0"], ["cs", "cs_bar"]) else: From 35a8dd2eec50757fd25e5c2ad3eb203af35faece Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Sat, 7 Sep 2019 20:05:05 -0700 Subject: [PATCH 08/12] Factor out masking function --- compiler/characterizer/functional.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/compiler/characterizer/functional.py b/compiler/characterizer/functional.py index 9e9b2596..50c4813b 100644 --- a/compiler/characterizer/functional.py +++ b/compiler/characterizer/functional.py @@ -196,14 +196,7 @@ class functional(simulation): else: word = self.gen_data() wmask = self.gen_wmask() - new_word = word - for bit in range(len(wmask)): - # When the write mask's bits are 0, the old data values should appear in the new word - # as to not overwrite the old values - if wmask[bit] == "0": - lower = bit * self.write_size - upper = lower + self.write_size - 1 - new_word = new_word[:lower] + old_word[lower:upper+1] + new_word[upper + 1:] + new_word = self.gen_masked_data(old_word, word, wmask) comment = self.gen_cycle_comment("partial_write", word, addr, wmask, port, self.t_current) self.add_write_one_port(comment, addr, word, wmask, port) self.stored_words[addr] = new_word @@ -226,6 +219,21 @@ 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) + def gen_masked_data(self, old_word, word, wmask): + """ Create the masked data word """ + # Start with the new word + new_word = word + + # When the write mask's bits are 0, the old data values should appear in the new word + # as to not overwrite the old values + for bit in range(len(wmask)): + if wmask[bit] == "0": + lower = bit * self.write_size + upper = lower + self.write_size - 1 + new_word = new_word[:lower] + old_word[lower:upper+1] + new_word[upper + 1:] + + return new_word + def add_read_check(self, word, port): """ Add to the check array to ensure a read works. """ try: From 9ec663e0b1bb03c49ddad1aa4e32215c1143ece3 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Sat, 7 Sep 2019 20:20:44 -0700 Subject: [PATCH 09/12] Write all write ports first cycle. Don't check feedthru. --- compiler/characterizer/functional.py | 45 +++++++--------------------- 1 file changed, 11 insertions(+), 34 deletions(-) diff --git a/compiler/characterizer/functional.py b/compiler/characterizer/functional.py index 50c4813b..50d55d59 100644 --- a/compiler/characterizer/functional.py +++ b/compiler/characterizer/functional.py @@ -107,41 +107,18 @@ 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 at least once. For multiport, it ensures that - # feedthru reads also work. + # 1. Write all the write ports first to seed a bunch of locations. + for port in self.write_ports: + addr = self.gen_addr() + word = self.gen_data() + comment = self.gen_cycle_comment("write", word, addr, "1"*self.num_wmasks, port, self.t_current) + self.add_write_one_port(comment, addr, word, "1"*self.num_wmasks, port) + self.stored_words[addr] = word - # Port 0 may not be a write port, so find the first write - # port. - first_write_port = self.write_ports[0] - addr = self.gen_addr() - word = self.gen_data() - comment = self.gen_cycle_comment("write", word, addr, "1"*self.num_wmasks, first_write_port, self.t_current) - self.add_write_one_port(comment, addr, word, "1"*self.num_wmasks, first_write_port) - self.stored_words[addr] = word - - # The read port should not be the same as the write port being written. - other_read_ports = copy.copy(self.read_ports) - try: - other_read_ports.remove(first_write_port) - except: - pass - # If we have one, check the feedthru read worked. - if len(other_read_ports)>0: - first_read_port = other_read_ports[0] - comment = self.gen_cycle_comment("read (feedthru)", word, addr, "0"*self.num_wmasks, first_read_port, self.t_current) - self.add_read_one_port(comment, addr, first_read_port) - self.add_read_check(word, first_read_port) - - # All other ports are noops. - other_ports = copy.copy(self.all_ports) - other_ports.remove(first_write_port) - try: - other_ports.remove(first_read_port) - except: - pass - - for port in other_ports: - self.add_noop_one_port(port) + # 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() From 99507ba5c590b9fdf3b8ec956bf66eb3cb3c78fa Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Sat, 7 Sep 2019 23:22:01 -0700 Subject: [PATCH 10/12] Remove rbl_bl_delay_bar from w_en logic inputs. --- compiler/modules/control_logic.py | 51 ++++++++----------------------- 1 file changed, 13 insertions(+), 38 deletions(-) diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py index e62770d8..79db4d61 100644 --- a/compiler/modules/control_logic.py +++ b/compiler/modules/control_logic.py @@ -125,7 +125,7 @@ class control_logic(design.design): self.add_mod(self.wl_en_driver) # w_en drives every write driver - self.wen_and = factory.create(module_type="pand3", + self.wen_and = factory.create(module_type="pand2", size=self.word_size+8, height=dff_height) self.add_mod(self.wen_and) @@ -344,11 +344,11 @@ class control_logic(design.design): # list of output control signals (for making a vertical bus) if self.port_type == "rw": - self.internal_bus_list = ["rbl_bl_delay_bar", "rbl_bl_delay", "gated_clk_bar", "gated_clk_buf", "we", "clk_buf", "we_bar", "cs"] + self.internal_bus_list = ["rbl_bl_delay", "gated_clk_bar", "gated_clk_buf", "we", "clk_buf", "we_bar", "cs"] elif self.port_type == "r": - self.internal_bus_list = ["rbl_bl_delay_bar", "rbl_bl_delay", "gated_clk_bar", "gated_clk_buf", "clk_buf", "cs_bar", "cs"] + self.internal_bus_list = ["rbl_bl_delay", "gated_clk_bar", "gated_clk_buf", "clk_buf", "cs_bar", "cs"] else: - self.internal_bus_list = ["rbl_bl_delay_bar", "rbl_bl_delay", "gated_clk_bar", "gated_clk_buf", "clk_buf", "cs"] + self.internal_bus_list = ["rbl_bl_delay", "gated_clk_bar", "gated_clk_buf", "clk_buf", "cs"] # leave space for the bus plus one extra space self.internal_bus_width = (len(self.internal_bus_list)+1)*self.m2_pitch @@ -382,7 +382,6 @@ class control_logic(design.design): self.create_gated_clk_buf_row() self.create_wlen_row() if (self.port_type == "rw") or (self.port_type == "w"): - self.create_rbl_delay_row() self.create_wen_row() if (self.port_type == "rw") or (self.port_type == "r"): self.create_sen_row() @@ -420,9 +419,6 @@ class control_logic(design.design): row += 1 self.place_pen_row(row) row += 1 - if (self.port_type == "rw") or (self.port_type == "w"): - self.place_rbl_delay_row(row) - row += 1 if (self.port_type == "rw") or (self.port_type == "r"): self.place_sen_row(row) row += 1 @@ -447,7 +443,6 @@ class control_logic(design.design): self.route_dffs() self.route_wlen() if (self.port_type == "rw") or (self.port_type == "w"): - self.route_rbl_delay() self.route_wen() if (self.port_type == "rw") or (self.port_type == "r"): self.route_sen() @@ -463,6 +458,7 @@ class control_logic(design.design): """ Create the replica bitline """ self.delay_inst=self.add_inst(name="delay_chain", mod=self.delay_chain) + # rbl_bl_delay is asserted (1) when the bitline has been discharged self.connect_inst(["rbl_bl", "rbl_bl_delay", "vdd", "gnd"]) def place_delay(self,row): @@ -613,6 +609,8 @@ class control_logic(design.design): def create_pen_row(self): self.p_en_bar_nand_inst=self.add_inst(name="nand_p_en_bar", mod=self.nand2) + # We use the rbl_bl_delay here to ensure that the p_en is only asserted when the + # bitlines have already been discharged. Otherwise, it is a combination loop. self.connect_inst(["gated_clk_buf", "rbl_bl_delay", "p_en_bar_unbuf", "vdd", "gnd"]) self.p_en_bar_driver_inst=self.add_inst(name="buf_p_en_bar", @@ -647,6 +645,9 @@ class control_logic(design.design): # GATE FOR S_EN self.s_en_gate_inst = self.add_inst(name="buf_s_en_and", mod=self.sen_and3) + # s_en is asserted in the second half of the cycle during a read. + # we also must wait until the bitline has been discharged enough for proper sensing + # hence we use rbl_bl_delay as well. self.connect_inst(["rbl_bl_delay", "gated_clk_bar", input_name, "s_en", "vdd", "gnd"]) @@ -671,33 +672,6 @@ class control_logic(design.design): self.connect_output(self.s_en_gate_inst, "Z", "s_en") - def create_rbl_delay_row(self): - - self.rbl_bl_delay_inv_inst = self.add_inst(name="rbl_bl_delay_inv", - mod=self.inv) - self.connect_inst(["rbl_bl_delay", "rbl_bl_delay_bar", "vdd", "gnd"]) - - def place_rbl_delay_row(self,row): - x_offset = self.control_x_offset - - x_offset = self.place_util(self.rbl_bl_delay_inv_inst, x_offset, row) - - self.row_end_inst.append(self.rbl_bl_delay_inv_inst) - - def route_rbl_delay(self): - # Connect from delay line - # Connect to rail - - rbl_map = zip(["Z"], ["rbl_bl_delay_bar"]) - self.connect_vertical_bus(rbl_map, self.rbl_bl_delay_inv_inst, self.rail_offsets, ("metal3", "via2", "metal2")) - # The pin is on M1, so we need another via as well - self.add_via_center(layers=("metal1","via1","metal2"), - offset=self.rbl_bl_delay_inv_inst.get_pin("Z").center()) - - - rbl_map = zip(["A"], ["rbl_bl_delay"]) - self.connect_vertical_bus(rbl_map, self.rbl_bl_delay_inv_inst, self.rail_offsets) - def create_wen_row(self): # input: we (or cs) output: w_en @@ -710,7 +684,8 @@ class control_logic(design.design): # GATE THE W_EN self.w_en_gate_inst = self.add_inst(name="w_en_and", mod=self.wen_and) - self.connect_inst([input_name, "rbl_bl_delay_bar", "gated_clk_bar", "w_en", "vdd", "gnd"]) + # Only drive the writes in the second half of the clock cycle during a write operation. + self.connect_inst([input_name, "gated_clk_bar", "w_en", "vdd", "gnd"]) def place_wen_row(self,row): @@ -727,7 +702,7 @@ class control_logic(design.design): # No we for write-only reports, so use cs input_name = "cs" - wen_map = zip(["A", "B", "C"], [input_name, "rbl_bl_delay_bar", "gated_clk_bar"]) + wen_map = zip(["A", "B"], [input_name, "gated_clk_bar"]) self.connect_vertical_bus(wen_map, self.w_en_gate_inst, self.rail_offsets) self.connect_output(self.w_en_gate_inst, "Z", "w_en") From 289d3b3988f0a47a11390cebb4753333c5d32714 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Fri, 27 Sep 2019 14:18:49 -0700 Subject: [PATCH 11/12] Feedthru port edits. Comment about write driver size for write through to work, but disable write through in functional simulation. Provide warning in Verilog about write throughs. --- compiler/base/verilog.py | 30 ++++++++++++++++++- compiler/characterizer/functional.py | 18 +++++++----- compiler/modules/control_logic.py | 44 ++++++++++++++++++++++++---- 3 files changed, 77 insertions(+), 15 deletions(-) diff --git a/compiler/base/verilog.py b/compiler/base/verilog.py index ff13c4f4..04344738 100644 --- a/compiler/base/verilog.py +++ b/compiler/base/verilog.py @@ -122,6 +122,9 @@ class verilog: if self.write_size: self.vf.write(" wmask{0}_reg = wmask{0};\n".format(port)) self.vf.write(" addr{0}_reg = addr{0};\n".format(port)) + if port in self.read_ports: + self.add_write_read_checks(port) + if port in self.write_ports: self.vf.write(" din{0}_reg = din{0};\n".format(port)) if port in self.read_ports: @@ -211,4 +214,29 @@ class verilog: self.vf.write(" if (!csb{0}_reg)\n".format(port)) self.vf.write(" dout{0} <= #(DELAY) mem[addr{0}_reg];\n".format(port)) self.vf.write(" end\n") - + + def add_address_check(self, wport, rport): + """ Output a warning if the two addresses match """ + # If the rport is actually reading... and addresses match. + if rport in self.readwrite_ports: + rport_control = "!csb{0} && web{0}".format(rport) + else: + rport_control = "!csb{0}".format(rport) + if wport in self.readwrite_ports: + wport_control = "!csb{0} && !web{0}".format(wport) + else: + wport_control = "!csb{0}".format(wport) + + self.vf.write(" if ({1} && {3} && (addr{0} == addr{2}))\n".format(wport,wport_control,rport,rport_control)) + self.vf.write(" $display($time,\" WARNING: Writing and reading addr{0}=%b and addr{1}=%b simultaneously!\",addr{0},addr{1});\n".format(wport,rport)) + + def add_write_read_checks(self, rport): + """ + Add a warning if we read from an address that we are currently writing. + Can be fixed if we appropriately size the write drivers to do this . + """ + for wport in self.write_ports: + if wport == rport: + continue + else: + self.add_address_check(wport,rport) diff --git a/compiler/characterizer/functional.py b/compiler/characterizer/functional.py index 50d55d59..6d827aa3 100644 --- a/compiler/characterizer/functional.py +++ b/compiler/characterizer/functional.py @@ -180,14 +180,16 @@ class functional(simulation): w_addrs.append(addr) else: (addr,word) = random.choice(list(self.stored_words.items())) - ## cannot read from an address that is currently being written to - # Yes, you can!! - #if addr in w_addrs: - # self.add_noop_one_port(port) - #else: - comment = self.gen_cycle_comment("read", word, addr, "0"*self.num_wmasks, port, self.t_current) - self.add_read_one_port(comment, addr, port) - self.add_read_check(word, port) + # The write driver is not sized sufficiently to drive through the two + # bitcell access transistors to the read port. So, for now, we do not allow + # a simultaneous write and read to the same address on different ports. This + # could be even more difficult with multiple simultaneous read ports. + if addr in w_addrs: + self.add_noop_one_port(port) + else: + comment = self.gen_cycle_comment("read", word, addr, "0"*self.num_wmasks, port, self.t_current) + self.add_read_one_port(comment, addr, port) + self.add_read_check(word, port) self.cycle_times.append(self.t_current) self.t_current += self.period diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py index 79db4d61..3256c9ac 100644 --- a/compiler/modules/control_logic.py +++ b/compiler/modules/control_logic.py @@ -125,7 +125,7 @@ class control_logic(design.design): self.add_mod(self.wl_en_driver) # w_en drives every write driver - self.wen_and = factory.create(module_type="pand2", + self.wen_and = factory.create(module_type="pand3", size=self.word_size+8, height=dff_height) self.add_mod(self.wen_and) @@ -344,11 +344,11 @@ class control_logic(design.design): # list of output control signals (for making a vertical bus) if self.port_type == "rw": - self.internal_bus_list = ["rbl_bl_delay", "gated_clk_bar", "gated_clk_buf", "we", "clk_buf", "we_bar", "cs"] + self.internal_bus_list = ["rbl_bl_delay_bar", "rbl_bl_delay", "gated_clk_bar", "gated_clk_buf", "we", "clk_buf", "we_bar", "cs"] elif self.port_type == "r": - self.internal_bus_list = ["rbl_bl_delay", "gated_clk_bar", "gated_clk_buf", "clk_buf", "cs_bar", "cs"] + self.internal_bus_list = ["rbl_bl_delay_bar", "rbl_bl_delay", "gated_clk_bar", "gated_clk_buf", "clk_buf", "cs_bar", "cs"] else: - self.internal_bus_list = ["rbl_bl_delay", "gated_clk_bar", "gated_clk_buf", "clk_buf", "cs"] + self.internal_bus_list = ["rbl_bl_delay_bar", "rbl_bl_delay", "gated_clk_bar", "gated_clk_buf", "clk_buf", "cs"] # leave space for the bus plus one extra space self.internal_bus_width = (len(self.internal_bus_list)+1)*self.m2_pitch @@ -382,6 +382,7 @@ class control_logic(design.design): self.create_gated_clk_buf_row() self.create_wlen_row() if (self.port_type == "rw") or (self.port_type == "w"): + self.create_rbl_delay_row() self.create_wen_row() if (self.port_type == "rw") or (self.port_type == "r"): self.create_sen_row() @@ -419,6 +420,9 @@ class control_logic(design.design): row += 1 self.place_pen_row(row) row += 1 + if (self.port_type == "rw") or (self.port_type == "w"): + self.place_rbl_delay_row(row) + row += 1 if (self.port_type == "rw") or (self.port_type == "r"): self.place_sen_row(row) row += 1 @@ -443,6 +447,7 @@ class control_logic(design.design): self.route_dffs() self.route_wlen() if (self.port_type == "rw") or (self.port_type == "w"): + self.route_rbl_delay() self.route_wen() if (self.port_type == "rw") or (self.port_type == "r"): self.route_sen() @@ -671,7 +676,34 @@ class control_logic(design.design): self.connect_output(self.s_en_gate_inst, "Z", "s_en") + def create_rbl_delay_row(self): + + self.rbl_bl_delay_inv_inst = self.add_inst(name="rbl_bl_delay_inv", + mod=self.inv) + self.connect_inst(["rbl_bl_delay", "rbl_bl_delay_bar", "vdd", "gnd"]) + + def place_rbl_delay_row(self,row): + x_offset = self.control_x_offset + + x_offset = self.place_util(self.rbl_bl_delay_inv_inst, x_offset, row) + self.row_end_inst.append(self.rbl_bl_delay_inv_inst) + + def route_rbl_delay(self): + # Connect from delay line + # Connect to rail + + rbl_map = zip(["Z"], ["rbl_bl_delay_bar"]) + self.connect_vertical_bus(rbl_map, self.rbl_bl_delay_inv_inst, self.rail_offsets, ("metal3", "via2", "metal2")) + # The pin is on M1, so we need another via as well + self.add_via_center(layers=("metal1","via1","metal2"), + offset=self.rbl_bl_delay_inv_inst.get_pin("Z").center()) + + + rbl_map = zip(["A"], ["rbl_bl_delay"]) + self.connect_vertical_bus(rbl_map, self.rbl_bl_delay_inv_inst, self.rail_offsets) + + def create_wen_row(self): # input: we (or cs) output: w_en @@ -685,7 +717,7 @@ class control_logic(design.design): self.w_en_gate_inst = self.add_inst(name="w_en_and", mod=self.wen_and) # Only drive the writes in the second half of the clock cycle during a write operation. - self.connect_inst([input_name, "gated_clk_bar", "w_en", "vdd", "gnd"]) + self.connect_inst([input_name, "rbl_bl_delay_bar", "gated_clk_bar", "w_en", "vdd", "gnd"]) def place_wen_row(self,row): @@ -702,7 +734,7 @@ class control_logic(design.design): # No we for write-only reports, so use cs input_name = "cs" - wen_map = zip(["A", "B"], [input_name, "gated_clk_bar"]) + wen_map = zip(["A", "B", "C"], [input_name, "rbl_bl_delay_bar", "gated_clk_bar"]) self.connect_vertical_bus(wen_map, self.w_en_gate_inst, self.rail_offsets) self.connect_output(self.w_en_gate_inst, "Z", "w_en") From b0dcfb5b2d525079a70dad5c04ebc20097f49253 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Fri, 27 Sep 2019 15:14:01 -0700 Subject: [PATCH 12/12] Fix leakage mismatch in results. --- compiler/tests/21_hspice_delay_test.py | 40 +++++++++++++------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/compiler/tests/21_hspice_delay_test.py b/compiler/tests/21_hspice_delay_test.py index e2222fcf..f4553659 100755 --- a/compiler/tests/21_hspice_delay_test.py +++ b/compiler/tests/21_hspice_delay_test.py @@ -61,27 +61,27 @@ class timing_sram_test(openram_test): data.update(port_data[0]) if OPTS.tech_name == "freepdk45": - golden_data = {'delay_hl': [0.2181231], - 'delay_lh': [0.2181231], - 'leakage_power': 0.0025453999999999997, - 'min_period': 0.781, - 'read0_power': [0.34664159999999994], - 'read1_power': [0.32656349999999995], - 'slew_hl': [0.21136519999999998], - 'slew_lh': [0.21136519999999998], - 'write0_power': [0.37980179999999997], - 'write1_power': [0.3532026]} + golden_data = {'delay_hl': [0.2383338], + 'delay_lh': [0.2383338], + 'leakage_power': 0.0014532999999999998, + 'min_period': 0.898, + 'read0_power': [0.30059800000000003], + 'read1_power': [0.30061810000000005], + 'slew_hl': [0.25358420000000004], + 'slew_lh': [0.25358420000000004], + 'write0_power': [0.34616749999999996], + 'write1_power': [0.2792924]} elif OPTS.tech_name == "scn4m_subm": - golden_data = {'delay_hl': [1.4082], - 'delay_lh': [1.4082], - 'leakage_power': 0.0267388, - 'min_period': 4.688, - 'read0_power': [11.5255], - 'read1_power': [10.9406], - 'slew_hl': [1.2979], - 'slew_lh': [1.2979], - 'write0_power': [12.9458], - 'write1_power': [11.7444]} + golden_data = {'delay_hl': [1.5125000000000002], + 'delay_lh': [1.5125000000000002], + 'leakage_power': 0.0017917999999999999, + 'min_period': 5.312, + 'read0_power': [9.8095], + 'read1_power': [9.8079], + 'slew_hl': [1.293], + 'slew_lh': [1.293], + 'write0_power': [11.3636], + 'write1_power': [8.9677]} else: self.assertTrue(False) # other techs fail # Check if no too many or too few results