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,