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/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 49907b4b..6d827aa3 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,28 +50,19 @@ 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 # 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() @@ -83,8 +75,26 @@ 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 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"] w_ops = ["noop", "write", "partial_write"] @@ -92,35 +102,45 @@ 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 - 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) - self.stored_words[addr] = word + # 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) - # 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 + # 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 + + # 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() - # 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 +152,79 @@ 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 = 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 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 + # 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("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("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 + 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 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: + 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): - # Extrat dout values from spice timing.lis - for (word, dout_port, eo_period, check) in self.write_check: + # Extract dout values from spice timing.lis + 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 +241,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 +395,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, 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") diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py index 06883125..3256c9ac 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 @@ -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)) @@ -421,7 +422,7 @@ class control_logic(design.design): row += 1 if (self.port_type == "rw") or (self.port_type == "w"): self.place_rbl_delay_row(row) - row += 1 + row += 1 if (self.port_type == "rw") or (self.port_type == "r"): self.place_sen_row(row) row += 1 @@ -462,6 +463,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): @@ -612,6 +614,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", @@ -646,6 +650,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"]) @@ -669,7 +676,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", @@ -696,7 +702,8 @@ class control_logic(design.design): 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 @@ -709,6 +716,7 @@ 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) + # Only drive the writes in the second half of the clock cycle during a write operation. self.connect_inst([input_name, "rbl_bl_delay_bar", "gated_clk_bar", "w_en", "vdd", "gnd"]) diff --git a/compiler/tests/21_hspice_delay_test.py b/compiler/tests/21_hspice_delay_test.py index e5317cec..171e4f3f 100755 --- a/compiler/tests/21_hspice_delay_test.py +++ b/compiler/tests/21_hspice_delay_test.py @@ -61,16 +61,16 @@ 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.7445000000000002], 'delay_lh': [1.7445000000000002],