mirror of https://github.com/VLSIDA/OpenRAM.git
Merged with dev, conflict in golden data of hspice delay test.
This commit is contained in:
commit
19a09470d4
|
|
@ -122,6 +122,9 @@ class verilog:
|
||||||
if self.write_size:
|
if self.write_size:
|
||||||
self.vf.write(" wmask{0}_reg = wmask{0};\n".format(port))
|
self.vf.write(" wmask{0}_reg = wmask{0};\n".format(port))
|
||||||
self.vf.write(" addr{0}_reg = addr{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:
|
if port in self.write_ports:
|
||||||
self.vf.write(" din{0}_reg = din{0};\n".format(port))
|
self.vf.write(" din{0}_reg = din{0};\n".format(port))
|
||||||
if port in self.read_ports:
|
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(" if (!csb{0}_reg)\n".format(port))
|
||||||
self.vf.write(" dout{0} <= #(DELAY) mem[addr{0}_reg];\n".format(port))
|
self.vf.write(" dout{0} <= #(DELAY) mem[addr{0}_reg];\n".format(port))
|
||||||
self.vf.write(" end\n")
|
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)
|
||||||
|
|
|
||||||
|
|
@ -1180,44 +1180,57 @@ class delay(simulation):
|
||||||
wmask_zeroes = "0"*self.num_wmasks
|
wmask_zeroes = "0"*self.num_wmasks
|
||||||
|
|
||||||
if self.t_current == 0:
|
if self.t_current == 0:
|
||||||
self.add_noop_all_ports("Idle cycle (no positive clock edge)",
|
self.add_noop_all_ports("Idle cycle (no positive clock edge)")
|
||||||
inverse_address, data_zeros,wmask_zeroes)
|
|
||||||
|
|
||||||
self.add_write("W data 1 address {}".format(inverse_address),
|
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.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
|
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
|
# 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),
|
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.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.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)",
|
self.add_noop_all_ports("Idle cycle (if read takes >1 cycle)")
|
||||||
inverse_address,data_zeros,wmask_zeroes)
|
|
||||||
|
|
||||||
self.add_write("W data 1 address {} to write value".format(self.probe_address),
|
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.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),
|
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
|
# 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),
|
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.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.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.add_noop_all_ports("Idle cycle (if read takes >1 cycle))")
|
||||||
self.probe_address,data_zeros,wmask_zeroes)
|
|
||||||
|
|
||||||
def get_available_port(self,get_read_port):
|
def get_available_port(self,get_read_port):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
import sys,re,shutil
|
import sys,re,shutil
|
||||||
|
import copy
|
||||||
import collections
|
import collections
|
||||||
from design import design
|
from design import design
|
||||||
import debug
|
import debug
|
||||||
|
|
@ -49,28 +50,19 @@ class functional(simulation):
|
||||||
self.create_graph()
|
self.create_graph()
|
||||||
self.set_internal_spice_names()
|
self.set_internal_spice_names()
|
||||||
|
|
||||||
self.initialize_wmask()
|
|
||||||
|
|
||||||
# Number of checks can be changed
|
# Number of checks can be changed
|
||||||
self.num_cycles = 15
|
self.num_cycles = 15
|
||||||
# This is to have ordered keys for random selection
|
# This is to have ordered keys for random selection
|
||||||
self.stored_words = collections.OrderedDict()
|
self.stored_words = collections.OrderedDict()
|
||||||
self.write_check = []
|
|
||||||
self.read_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):
|
def run(self, feasible_period=None):
|
||||||
if feasible_period: #period defaults to tech.py feasible period otherwise.
|
if feasible_period: #period defaults to tech.py feasible period otherwise.
|
||||||
self.period = feasible_period
|
self.period = feasible_period
|
||||||
# Generate a random sequence of reads and writes
|
# Generate a random sequence of reads and writes
|
||||||
self.write_random_memory_sequence()
|
self.create_random_memory_sequence()
|
||||||
|
|
||||||
# Run SPICE simulation
|
# Run SPICE simulation
|
||||||
self.write_functional_stimulus()
|
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.
|
# Check read values with written values. If the values do not match, return an error.
|
||||||
return self.check_stim_results()
|
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:
|
if self.write_size:
|
||||||
rw_ops = ["noop", "write", "partial_write", "read"]
|
rw_ops = ["noop", "write", "partial_write", "read"]
|
||||||
w_ops = ["noop", "write", "partial_write"]
|
w_ops = ["noop", "write", "partial_write"]
|
||||||
|
|
@ -92,35 +102,45 @@ class functional(simulation):
|
||||||
rw_ops = ["noop", "write", "read"]
|
rw_ops = ["noop", "write", "read"]
|
||||||
w_ops = ["noop", "write"]
|
w_ops = ["noop", "write"]
|
||||||
r_ops = ["noop", "read"]
|
r_ops = ["noop", "read"]
|
||||||
rw_read_din_data = "0"*self.word_size
|
|
||||||
check = 0
|
|
||||||
|
|
||||||
# First cycle idle
|
# First cycle idle is always an idle cycle
|
||||||
comment = self.gen_cycle_comment("noop", "0"*self.word_size, "0"*self.addr_size, self.wmask, 0, self.t_current)
|
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, "0"*self.addr_size, "0"*self.word_size, "0"*self.num_wmasks)
|
self.add_noop_all_ports(comment)
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# 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.
|
# 1. Write all the write ports first to seed a bunch of locations.
|
||||||
# This will test the viablilty of the transistor sizing in the bitcell.
|
for port in self.write_ports:
|
||||||
for port in self.all_ports:
|
addr = self.gen_addr()
|
||||||
if port in self.write_ports:
|
word = self.gen_data()
|
||||||
self.add_noop_one_port("0"*self.addr_size, "0"*self.word_size, "0"*self.num_wmasks, port)
|
comment = self.gen_cycle_comment("write", word, addr, "1"*self.num_wmasks, port, self.t_current)
|
||||||
else:
|
self.add_write_one_port(comment, addr, word, "1"*self.num_wmasks, port)
|
||||||
comment = self.gen_cycle_comment("read", word, addr, self.wmask, port, self.t_current)
|
self.stored_words[addr] = word
|
||||||
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])
|
# All other read-only ports are noops.
|
||||||
check += 1
|
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.cycle_times.append(self.t_current)
|
||||||
self.t_current += self.period
|
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
|
# 2. Read at least once. For multiport, it is important that one
|
||||||
# and random write masks (if applicable)
|
# 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):
|
for i in range(self.num_cycles):
|
||||||
w_addrs = []
|
w_addrs = []
|
||||||
for port in self.all_ports:
|
for port in self.all_ports:
|
||||||
|
|
@ -132,63 +152,79 @@ class functional(simulation):
|
||||||
op = random.choice(r_ops)
|
op = random.choice(r_ops)
|
||||||
|
|
||||||
if op == "noop":
|
if op == "noop":
|
||||||
addr = "0"*self.addr_size
|
self.add_noop_one_port(port)
|
||||||
word = "0"*self.word_size
|
|
||||||
wmask = "0" * self.num_wmasks
|
|
||||||
self.add_noop_one_port(addr, word, wmask, port)
|
|
||||||
elif op == "write":
|
elif op == "write":
|
||||||
addr = self.gen_addr()
|
addr = self.gen_addr()
|
||||||
word = self.gen_data()
|
|
||||||
# two ports cannot write to the same address
|
# two ports cannot write to the same address
|
||||||
if addr in w_addrs:
|
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:
|
else:
|
||||||
comment = self.gen_cycle_comment("write", word, addr, self.wmask, port, self.t_current)
|
word = self.gen_data()
|
||||||
self.add_write_one_port(comment, addr, word, self.wmask, port)
|
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
|
self.stored_words[addr] = word
|
||||||
w_addrs.append(addr)
|
w_addrs.append(addr)
|
||||||
elif op == "partial_write":
|
elif op == "partial_write":
|
||||||
# write only to a word that's been written to
|
# write only to a word that's been written to
|
||||||
(addr,old_word) = self.get_data()
|
(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
|
# two ports cannot write to the same address
|
||||||
if addr in w_addrs:
|
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:
|
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)
|
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.add_write_one_port(comment, addr, word, wmask, port)
|
||||||
self.stored_words[addr] = new_word
|
self.stored_words[addr] = new_word
|
||||||
w_addrs.append(addr)
|
w_addrs.append(addr)
|
||||||
else:
|
else:
|
||||||
(addr,word) = random.choice(list(self.stored_words.items()))
|
(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:
|
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:
|
else:
|
||||||
comment = self.gen_cycle_comment("read", word, addr, self.wmask, port, self.t_current)
|
comment = self.gen_cycle_comment("read", word, addr, "0"*self.num_wmasks, port, self.t_current)
|
||||||
self.add_read_one_port(comment, addr, rw_read_din_data, "0"*self.num_wmasks, port)
|
self.add_read_one_port(comment, addr, port)
|
||||||
self.write_check.append([word, "{0}{1}".format(self.dout_name,port), self.t_current+self.period, check])
|
self.add_read_check(word, port)
|
||||||
check += 1
|
|
||||||
|
|
||||||
self.cycle_times.append(self.t_current)
|
self.cycle_times.append(self.t_current)
|
||||||
self.t_current += self.period
|
self.t_current += self.period
|
||||||
|
|
||||||
# Last cycle idle needed to correctly measure the value on the second to last clock edge
|
# 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)
|
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, "0"*self.addr_size, "0"*self.word_size, "0"*self.num_wmasks)
|
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):
|
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:
|
for (word, dout_port, eo_period, check) in self.read_check:
|
||||||
sp_read_value = ""
|
sp_read_value = ""
|
||||||
for bit in range(self.word_size):
|
for bit in range(self.word_size):
|
||||||
value = parse_spice_list("timing", "v{0}.{1}ck{2}".format(dout_port.lower(),bit,check))
|
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)
|
self.v_high)
|
||||||
return (0, error)
|
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")
|
return (1, "SUCCESS")
|
||||||
|
|
||||||
def check_stim_results(self):
|
def check_stim_results(self):
|
||||||
for i in range(len(self.write_check)):
|
for i in range(len(self.read_check)):
|
||||||
if self.write_check[i][0] != self.read_check[i][0]:
|
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_check[i][1],
|
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.read_check[i][0],
|
||||||
self.write_check[i][0],
|
int((self.read_results[i][2]-self.period)/self.period),
|
||||||
int((self.read_check[i][2]-self.period)/self.period),
|
self.read_results[i][2])
|
||||||
self.read_check[i][2])
|
|
||||||
return(0, error)
|
return(0, error)
|
||||||
return(1, "SUCCESS")
|
return(1, "SUCCESS")
|
||||||
|
|
||||||
|
|
@ -359,7 +395,7 @@ class functional(simulation):
|
||||||
|
|
||||||
# Generate dout value measurements
|
# Generate dout value measurements
|
||||||
self.sf.write("\n * Generation of dout measurements\n")
|
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_intital = eo_period - 0.01*self.period
|
||||||
t_final = eo_period + 0.01*self.period
|
t_final = eo_period + 0.01*self.period
|
||||||
for bit in range(self.word_size):
|
for bit in range(self.word_size):
|
||||||
|
|
|
||||||
|
|
@ -60,8 +60,10 @@ class simulation():
|
||||||
port_info=(len(self.all_ports),self.write_ports,self.read_ports),
|
port_info=(len(self.all_ports),self.write_ports,self.read_ports),
|
||||||
abits=self.addr_size,
|
abits=self.addr_size,
|
||||||
dbits=self.word_size)
|
dbits=self.word_size)
|
||||||
debug.check(len(self.sram.pins) == len(self.pins), "Number of pins generated for characterization \
|
debug.check(len(self.sram.pins) == len(self.pins),
|
||||||
do match pins of SRAM\nsram.pins = {0}\npin_names = {1}".format(self.sram.pins,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.
|
#This is TODO once multiport control has been finalized.
|
||||||
#self.control_name = "CSB"
|
#self.control_name = "CSB"
|
||||||
|
|
||||||
|
|
@ -71,13 +73,18 @@ class simulation():
|
||||||
self.t_current = 0
|
self.t_current = 0
|
||||||
|
|
||||||
# control signals: only one cs_b for entire multiported sram, one we_b for each write port
|
# 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.csb_values = {port:[] for port in self.all_ports}
|
||||||
self.web_values = [[] for port in self.readwrite_ports]
|
self.web_values = {port:[] for port in self.readwrite_ports}
|
||||||
|
|
||||||
# Three dimensional list to handle each addr and data bits for wach port over the number of checks
|
# Raw values added as a bit vector
|
||||||
self.addr_values = [[[] for bit in range(self.addr_size)] for port in self.all_ports]
|
self.addr_value = {port:[] for port in self.all_ports}
|
||||||
self.data_values = [[[] for bit in range(self.word_size)] for port in self.write_ports]
|
self.data_value = {port:[] for port in self.write_ports}
|
||||||
self.wmask_values = [[[] for bit in range(self.num_wmasks)] 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
|
# For generating comments in SPICE stimulus
|
||||||
self.cycle_comments = []
|
self.cycle_comments = []
|
||||||
|
|
@ -105,7 +112,8 @@ class simulation():
|
||||||
def add_data(self, data, port):
|
def add_data(self, data, port):
|
||||||
""" Add the array of data values """
|
""" Add the array of data values """
|
||||||
debug.check(len(data)==self.word_size, "Invalid data word size.")
|
debug.check(len(data)==self.word_size, "Invalid data word size.")
|
||||||
|
|
||||||
|
self.data_value[port].append(data)
|
||||||
bit = self.word_size - 1
|
bit = self.word_size - 1
|
||||||
for c in data:
|
for c in data:
|
||||||
if c=="0":
|
if c=="0":
|
||||||
|
|
@ -116,10 +124,12 @@ class simulation():
|
||||||
debug.error("Non-binary data string",1)
|
debug.error("Non-binary data string",1)
|
||||||
bit -= 1
|
bit -= 1
|
||||||
|
|
||||||
|
|
||||||
def add_address(self, address, port):
|
def add_address(self, address, port):
|
||||||
""" Add the array of address values """
|
""" Add the array of address values """
|
||||||
debug.check(len(address)==self.addr_size, "Invalid address size.")
|
debug.check(len(address)==self.addr_size, "Invalid address size.")
|
||||||
|
|
||||||
|
self.addr_value[port].append(address)
|
||||||
bit = self.addr_size - 1
|
bit = self.addr_size - 1
|
||||||
for c in address:
|
for c in address:
|
||||||
if c=="0":
|
if c=="0":
|
||||||
|
|
@ -130,10 +140,12 @@ class simulation():
|
||||||
debug.error("Non-binary address string",1)
|
debug.error("Non-binary address string",1)
|
||||||
bit -= 1
|
bit -= 1
|
||||||
|
|
||||||
|
|
||||||
def add_wmask(self, wmask, port):
|
def add_wmask(self, wmask, port):
|
||||||
""" Add the array of address values """
|
""" Add the array of address values """
|
||||||
debug.check(len(wmask) == self.num_wmasks, "Invalid wmask size.")
|
debug.check(len(wmask) == self.num_wmasks, "Invalid wmask size.")
|
||||||
|
|
||||||
|
self.wmask_value[port].append(wmask)
|
||||||
bit = self.num_wmasks - 1
|
bit = self.num_wmasks - 1
|
||||||
for c in wmask:
|
for c in wmask:
|
||||||
if c == "0":
|
if c == "0":
|
||||||
|
|
@ -143,10 +155,13 @@ class simulation():
|
||||||
else:
|
else:
|
||||||
debug.error("Non-binary wmask string", 1)
|
debug.error("Non-binary wmask string", 1)
|
||||||
bit -= 1
|
bit -= 1
|
||||||
|
|
||||||
|
|
||||||
def add_write(self, comment, address, data, wmask, port):
|
def add_write(self, comment, address, data, wmask, port):
|
||||||
""" Add the control values for a write cycle. """
|
""" 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)
|
debug.info(2, comment)
|
||||||
self.fn_cycle_comments.append(comment)
|
self.fn_cycle_comments.append(comment)
|
||||||
self.append_cycle_comment(port, comment)
|
self.append_cycle_comment(port, comment)
|
||||||
|
|
@ -159,16 +174,16 @@ class simulation():
|
||||||
self.add_address(address,port)
|
self.add_address(address,port)
|
||||||
self.add_wmask(wmask,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.
|
#Add noops to all other ports.
|
||||||
for unselected_port in self.all_ports:
|
for unselected_port in self.all_ports:
|
||||||
if unselected_port != port:
|
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. """
|
""" 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)
|
debug.info(2, comment)
|
||||||
self.fn_cycle_comments.append(comment)
|
self.fn_cycle_comments.append(comment)
|
||||||
self.append_cycle_comment(port, comment)
|
self.append_cycle_comment(port, comment)
|
||||||
|
|
@ -176,21 +191,26 @@ class simulation():
|
||||||
self.cycle_times.append(self.t_current)
|
self.cycle_times.append(self.t_current)
|
||||||
self.t_current += self.period
|
self.t_current += self.period
|
||||||
self.add_control_one_port(port, "read")
|
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)
|
self.add_address(address, port)
|
||||||
|
|
||||||
#This value is hard coded here. Possibly change to member variable or set in add_noop_one_port
|
# If the port is also a readwrite then add
|
||||||
noop_data = "0"*self.word_size
|
# 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.
|
#Add noops to all other ports.
|
||||||
for unselected_port in self.all_ports:
|
for unselected_port in self.all_ports:
|
||||||
if unselected_port != port:
|
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. """
|
""" Add the control values for a noop to all ports. """
|
||||||
debug.info(2, comment)
|
debug.info(2, comment)
|
||||||
self.fn_cycle_comments.append(comment)
|
self.fn_cycle_comments.append(comment)
|
||||||
|
|
@ -200,39 +220,64 @@ class simulation():
|
||||||
self.t_current += self.period
|
self.t_current += self.period
|
||||||
|
|
||||||
for port in self.all_ports:
|
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):
|
def add_write_one_port(self, comment, address, data, wmask, port):
|
||||||
""" Add the control values for a write cycle. Does not increment the period. """
|
""" 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)
|
debug.info(2, comment)
|
||||||
self.fn_cycle_comments.append(comment)
|
self.fn_cycle_comments.append(comment)
|
||||||
|
|
||||||
self.add_control_one_port(port, "write")
|
self.add_control_one_port(port, "write")
|
||||||
self.add_data(data,port)
|
self.add_data(data, port)
|
||||||
self.add_address(address,port)
|
self.add_address(address, port)
|
||||||
self.add_wmask(wmask,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. """
|
""" 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)
|
debug.info(2, comment)
|
||||||
self.fn_cycle_comments.append(comment)
|
self.fn_cycle_comments.append(comment)
|
||||||
|
|
||||||
self.add_control_one_port(port, "read")
|
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)
|
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. """
|
""" Add the control values for a noop to a single port. Does not increment the period. """
|
||||||
self.add_control_one_port(port, "noop")
|
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:
|
if port in self.write_ports:
|
||||||
self.add_data(data,port)
|
try:
|
||||||
self.add_wmask(wmask,port)
|
self.add_data(self.data_value[port][-1], port)
|
||||||
self.add_address(address, 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):
|
def append_cycle_comment(self, port, comment):
|
||||||
"""Add comment to list to be printed in stimulus file"""
|
"""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 = "{0:.2f} ns:".format(self.t_current)
|
||||||
time_spacing = len(time)+6
|
time_spacing = len(time)+6
|
||||||
self.cycle_comments.append("Cycle {0:<6d} Port {1:<6} {2:<{3}}: {4}".format(len(self.cycle_times),
|
self.cycle_comments.append("Cycle {0:<6d} Port {1:<6} {2:<{3}}: {4}".format(len(self.cycle_times),
|
||||||
port,
|
port,
|
||||||
time,
|
time,
|
||||||
time_spacing,
|
time_spacing,
|
||||||
comment))
|
comment))
|
||||||
|
|
||||||
def gen_cycle_comment(self, op, word, addr, wmask, port, t_current):
|
def gen_cycle_comment(self, op, word, addr, wmask, port, t_current):
|
||||||
if op == "noop":
|
if op == "noop":
|
||||||
comment = "\tIdle during cycle {0} ({1}ns - {2}ns)".format(int(t_current/self.period),
|
comment = "\tIdle during cycle {0} ({1}ns - {2}ns)".format(int(t_current/self.period),
|
||||||
t_current,
|
t_current,
|
||||||
t_current+self.period)
|
t_current+self.period)
|
||||||
elif op == "write":
|
elif op == "write":
|
||||||
comment = "\tWriting {0} to address {1} (from port {2}) during cycle {3} ({4}ns - {5}ns)".format(word,
|
comment = "\tWriting {0} to address {1} (from port {2}) during cycle {3} ({4}ns - {5}ns)".format(word,
|
||||||
addr,
|
addr,
|
||||||
|
|
|
||||||
|
|
@ -96,22 +96,6 @@ class stimuli():
|
||||||
self.sf.write(".ENDS test_{0}\n\n".format(buffer_name))
|
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):
|
def gen_pulse(self, sig_name, v1, v2, offset, period, t_rise, t_fall):
|
||||||
"""
|
"""
|
||||||
|
|
@ -276,9 +260,6 @@ class stimuli():
|
||||||
""" Writes supply voltage statements """
|
""" Writes supply voltage statements """
|
||||||
gnd_node_name = "0"
|
gnd_node_name = "0"
|
||||||
self.sf.write("V{0} {0} {1} {2}\n".format(self.vdd_name, gnd_node_name, self.voltage))
|
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.
|
#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")
|
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")
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ class control_logic(design.design):
|
||||||
self.enable_delay_chain_resizing = False
|
self.enable_delay_chain_resizing = False
|
||||||
self.inv_parasitic_delay = logical_effort.logical_effort.pinv
|
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_timing_tolerance = 1
|
||||||
self.wl_stage_efforts = None
|
self.wl_stage_efforts = None
|
||||||
self.sen_stage_efforts = None
|
self.sen_stage_efforts = None
|
||||||
|
|
@ -201,7 +201,7 @@ class control_logic(design.design):
|
||||||
|
|
||||||
def get_heuristic_delay_chain_size(self):
|
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 """
|
"""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
|
delay_fanout = 3 # This can be anything >=3
|
||||||
# Model poorly captures delay of the column mux. Be pessismistic for column mux
|
# Model poorly captures delay of the column mux. Be pessismistic for column mux
|
||||||
if self.words_per_row >= 2:
|
if self.words_per_row >= 2:
|
||||||
|
|
@ -209,8 +209,8 @@ class control_logic(design.design):
|
||||||
else:
|
else:
|
||||||
delay_stages = 2
|
delay_stages = 2
|
||||||
|
|
||||||
#Read ports have a shorter s_en delay. The model is not accurate enough to catch this difference
|
# Read ports have a shorter s_en delay. The model is not accurate enough to catch this difference
|
||||||
#on certain sram configs.
|
# on certain sram configs.
|
||||||
if self.port_type == "r":
|
if self.port_type == "r":
|
||||||
delay_stages+=2
|
delay_stages+=2
|
||||||
|
|
||||||
|
|
@ -226,7 +226,7 @@ class control_logic(design.design):
|
||||||
def does_sen_rise_fall_timing_match(self):
|
def does_sen_rise_fall_timing_match(self):
|
||||||
"""Compare the relative rise/fall delays of the sense amp enable and wordline"""
|
"""Compare the relative rise/fall delays of the sense amp enable and wordline"""
|
||||||
self.set_sen_wl_delays()
|
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
|
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):
|
self.wl_delay_fall*self.wl_timing_tolerance >= self.sen_delay_fall):
|
||||||
return False
|
return False
|
||||||
|
|
@ -236,8 +236,9 @@ class control_logic(design.design):
|
||||||
def does_sen_total_timing_match(self):
|
def does_sen_total_timing_match(self):
|
||||||
"""Compare the total delays of the sense amp enable and wordline"""
|
"""Compare the total delays of the sense amp enable and wordline"""
|
||||||
self.set_sen_wl_delays()
|
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
|
# The sen delay must always be bigger than than the wl
|
||||||
#a re-size is warranted.
|
# 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:
|
if self.wl_delay*self.wl_timing_tolerance >= self.sen_delay:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
|
|
@ -250,14 +251,14 @@ class control_logic(design.design):
|
||||||
debug.info(2, "Previous delay chain produced {} delay units".format(previous_delay_chain_delay))
|
debug.info(2, "Previous delay chain produced {} delay units".format(previous_delay_chain_delay))
|
||||||
|
|
||||||
delay_fanout = 3 # This can be anything >=2
|
delay_fanout = 3 # This can be anything >=2
|
||||||
#The delay chain uses minimum sized inverters. There are (fanout+1)*stages inverters and each
|
# 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
|
# 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)
|
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")
|
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))
|
delay_stages = ceil(required_delay/(delay_fanout+1+self.inv_parasitic_delay))
|
||||||
if delay_stages%2 == 1: #force an even number of stages.
|
if delay_stages%2 == 1: #force an even number of stages.
|
||||||
delay_stages+=1
|
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))
|
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)
|
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))
|
debug.info(2, "Previous delay chain produced {} delay units".format(previous_delay_chain_delay))
|
||||||
|
|
||||||
fanout_rise = fanout_fall = 2 # This can be anything >=2
|
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
|
# 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
|
# 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_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)
|
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))
|
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
|
WARNING_FANOUT_DIFF = 5
|
||||||
stages_close = False
|
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:
|
while True:
|
||||||
stages_fall = self.calculate_stages_with_fixed_fanout(required_delay_fall,fanout_fall)
|
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)
|
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_rise = safe_fanout_rise
|
||||||
fanout_fall = safe_fanout_fall
|
fanout_fall = safe_fanout_fall
|
||||||
break
|
break
|
||||||
#There should also be a condition to make sure the fanout does not get too large.
|
# 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
|
# Otherwise, increase the fanout of delay with the most stages, calculate new stages
|
||||||
elif stages_fall>stages_rise:
|
elif stages_fall>stages_rise:
|
||||||
fanout_fall+=1
|
fanout_fall+=1
|
||||||
else:
|
else:
|
||||||
|
|
@ -304,13 +305,13 @@ class control_logic(design.design):
|
||||||
total_stages = max(stages_fall,stages_rise)*2
|
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))
|
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)]
|
stage_list = [fanout_fall if i%2==0 else fanout_rise for i in range(total_stages)]
|
||||||
return stage_list
|
return stage_list
|
||||||
|
|
||||||
def calculate_stages_with_fixed_fanout(self, required_delay, fanout):
|
def calculate_stages_with_fixed_fanout(self, required_delay, fanout):
|
||||||
from math import ceil
|
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).
|
if required_delay <= 3+self.inv_parasitic_delay: #3 is the minimum delay per stage (with pinv=0).
|
||||||
return 1
|
return 1
|
||||||
delay_stages = ceil(required_delay/(fanout+1+self.inv_parasitic_delay))
|
delay_stages = ceil(required_delay/(fanout+1+self.inv_parasitic_delay))
|
||||||
|
|
@ -421,7 +422,7 @@ class control_logic(design.design):
|
||||||
row += 1
|
row += 1
|
||||||
if (self.port_type == "rw") or (self.port_type == "w"):
|
if (self.port_type == "rw") or (self.port_type == "w"):
|
||||||
self.place_rbl_delay_row(row)
|
self.place_rbl_delay_row(row)
|
||||||
row += 1
|
row += 1
|
||||||
if (self.port_type == "rw") or (self.port_type == "r"):
|
if (self.port_type == "rw") or (self.port_type == "r"):
|
||||||
self.place_sen_row(row)
|
self.place_sen_row(row)
|
||||||
row += 1
|
row += 1
|
||||||
|
|
@ -462,6 +463,7 @@ class control_logic(design.design):
|
||||||
""" Create the replica bitline """
|
""" Create the replica bitline """
|
||||||
self.delay_inst=self.add_inst(name="delay_chain",
|
self.delay_inst=self.add_inst(name="delay_chain",
|
||||||
mod=self.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"])
|
self.connect_inst(["rbl_bl", "rbl_bl_delay", "vdd", "gnd"])
|
||||||
|
|
||||||
def place_delay(self,row):
|
def place_delay(self,row):
|
||||||
|
|
@ -612,6 +614,8 @@ class control_logic(design.design):
|
||||||
def create_pen_row(self):
|
def create_pen_row(self):
|
||||||
self.p_en_bar_nand_inst=self.add_inst(name="nand_p_en_bar",
|
self.p_en_bar_nand_inst=self.add_inst(name="nand_p_en_bar",
|
||||||
mod=self.nand2)
|
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.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",
|
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
|
# GATE FOR S_EN
|
||||||
self.s_en_gate_inst = self.add_inst(name="buf_s_en_and",
|
self.s_en_gate_inst = self.add_inst(name="buf_s_en_and",
|
||||||
mod=self.sen_and3)
|
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"])
|
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")
|
self.connect_output(self.s_en_gate_inst, "Z", "s_en")
|
||||||
|
|
||||||
|
|
||||||
def create_rbl_delay_row(self):
|
def create_rbl_delay_row(self):
|
||||||
|
|
||||||
self.rbl_bl_delay_inv_inst = self.add_inst(name="rbl_bl_delay_inv",
|
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"])
|
rbl_map = zip(["A"], ["rbl_bl_delay"])
|
||||||
self.connect_vertical_bus(rbl_map, self.rbl_bl_delay_inv_inst, self.rail_offsets)
|
self.connect_vertical_bus(rbl_map, self.rbl_bl_delay_inv_inst, self.rail_offsets)
|
||||||
|
|
||||||
|
|
||||||
def create_wen_row(self):
|
def create_wen_row(self):
|
||||||
|
|
||||||
# input: we (or cs) output: w_en
|
# input: we (or cs) output: w_en
|
||||||
|
|
@ -709,6 +716,7 @@ class control_logic(design.design):
|
||||||
# GATE THE W_EN
|
# GATE THE W_EN
|
||||||
self.w_en_gate_inst = self.add_inst(name="w_en_and",
|
self.w_en_gate_inst = self.add_inst(name="w_en_and",
|
||||||
mod=self.wen_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"])
|
self.connect_inst([input_name, "rbl_bl_delay_bar", "gated_clk_bar", "w_en", "vdd", "gnd"])
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,16 +61,16 @@ class timing_sram_test(openram_test):
|
||||||
data.update(port_data[0])
|
data.update(port_data[0])
|
||||||
|
|
||||||
if OPTS.tech_name == "freepdk45":
|
if OPTS.tech_name == "freepdk45":
|
||||||
golden_data = {'delay_hl': [0.2181231],
|
golden_data = {'delay_hl': [0.2383338],
|
||||||
'delay_lh': [0.2181231],
|
'delay_lh': [0.2383338],
|
||||||
'leakage_power': 0.0025453999999999997,
|
'leakage_power': 0.0014532999999999998,
|
||||||
'min_period': 0.781,
|
'min_period': 0.898,
|
||||||
'read0_power': [0.34664159999999994],
|
'read0_power': [0.30059800000000003],
|
||||||
'read1_power': [0.32656349999999995],
|
'read1_power': [0.30061810000000005],
|
||||||
'slew_hl': [0.21136519999999998],
|
'slew_hl': [0.25358420000000004],
|
||||||
'slew_lh': [0.21136519999999998],
|
'slew_lh': [0.25358420000000004],
|
||||||
'write0_power': [0.37980179999999997],
|
'write0_power': [0.34616749999999996],
|
||||||
'write1_power': [0.3532026]}
|
'write1_power': [0.2792924]}
|
||||||
elif OPTS.tech_name == "scn4m_subm":
|
elif OPTS.tech_name == "scn4m_subm":
|
||||||
golden_data = {'delay_hl': [1.7445000000000002],
|
golden_data = {'delay_hl': [1.7445000000000002],
|
||||||
'delay_lh': [1.7445000000000002],
|
'delay_lh': [1.7445000000000002],
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue