2019-04-26 21:21:50 +02:00
|
|
|
# See LICENSE for licensing information.
|
|
|
|
|
#
|
2019-06-14 17:43:41 +02:00
|
|
|
# Copyright (c) 2016-2019 Regents of the University of California and The Board
|
|
|
|
|
# of Regents for the Oklahoma Agricultural and Mechanical College
|
|
|
|
|
# (acting for and on behalf of Oklahoma State University)
|
|
|
|
|
# All rights reserved.
|
2019-04-26 21:21:50 +02:00
|
|
|
#
|
2019-08-01 21:21:30 +02:00
|
|
|
import collections
|
2018-09-21 00:04:59 +02:00
|
|
|
import debug
|
|
|
|
|
import random
|
2020-09-28 18:53:01 +02:00
|
|
|
import math
|
2018-09-21 00:04:59 +02:00
|
|
|
from .stimuli import *
|
|
|
|
|
from .charutils import *
|
|
|
|
|
from globals import OPTS
|
2018-10-04 18:29:44 +02:00
|
|
|
from .simulation import simulation
|
|
|
|
|
|
2020-06-18 23:55:01 +02:00
|
|
|
|
2018-10-04 18:29:44 +02:00
|
|
|
class functional(simulation):
|
2018-09-21 00:04:59 +02:00
|
|
|
"""
|
|
|
|
|
Functions to write random data values to a random address then read them back and check
|
|
|
|
|
for successful SRAM operation.
|
|
|
|
|
"""
|
|
|
|
|
|
2020-09-29 22:43:54 +02:00
|
|
|
def __init__(self, sram, spfile, corner, cycles=15):
|
2020-08-06 20:33:26 +02:00
|
|
|
super().__init__(sram, spfile, corner)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2018-11-03 18:53:09 +01:00
|
|
|
# Seed the characterizer with a constant seed for unit tests
|
|
|
|
|
if OPTS.is_unit_test:
|
|
|
|
|
random.seed(12345)
|
2018-10-04 18:29:44 +02:00
|
|
|
|
2019-08-21 23:29:57 +02:00
|
|
|
if self.write_size:
|
2020-09-28 18:53:01 +02:00
|
|
|
self.num_wmasks = int(math.ceil(self.word_size / self.write_size))
|
2019-07-19 23:58:37 +02:00
|
|
|
else:
|
|
|
|
|
self.num_wmasks = 0
|
|
|
|
|
|
2020-06-03 14:31:30 +02:00
|
|
|
if not self.num_spare_cols:
|
|
|
|
|
self.num_spare_cols = 0
|
|
|
|
|
|
2020-09-29 19:26:31 +02:00
|
|
|
self.probe_address, self.probe_data = '0' * self.addr_size, 0
|
2018-09-21 00:04:59 +02:00
|
|
|
self.set_corner(corner)
|
|
|
|
|
self.set_spice_constants()
|
|
|
|
|
self.set_stimulus_variables()
|
2018-11-29 01:55:04 +01:00
|
|
|
|
2019-07-26 23:49:53 +02:00
|
|
|
# For the debug signal names
|
2020-09-29 20:35:58 +02:00
|
|
|
self.wordline_row = 0
|
|
|
|
|
self.bitline_column = 0
|
2019-07-26 23:49:53 +02:00
|
|
|
self.create_signal_names()
|
|
|
|
|
self.add_graph_exclusions()
|
|
|
|
|
self.create_graph()
|
|
|
|
|
self.set_internal_spice_names()
|
2020-09-29 19:26:31 +02:00
|
|
|
self.q_name, self.qbar_name = self.get_bit_name()
|
2020-09-04 11:24:18 +02:00
|
|
|
debug.info(2, "q name={}\nqbar name={}".format(self.q_name, self.qbar_name))
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2018-09-21 00:04:59 +02:00
|
|
|
# Number of checks can be changed
|
2020-09-29 22:43:54 +02:00
|
|
|
self.num_cycles = cycles
|
2019-08-01 21:21:30 +02:00
|
|
|
# This is to have ordered keys for random selection
|
|
|
|
|
self.stored_words = collections.OrderedDict()
|
2018-10-08 15:34:36 +02:00
|
|
|
self.read_check = []
|
2019-09-06 21:09:12 +02:00
|
|
|
self.read_results = []
|
2019-07-12 19:34:29 +02:00
|
|
|
|
2018-12-06 08:23:40 +01:00
|
|
|
def run(self, feasible_period=None):
|
2020-09-29 19:26:31 +02:00
|
|
|
if feasible_period: # period defaults to tech.py feasible period otherwise.
|
2018-12-06 08:23:40 +01:00
|
|
|
self.period = feasible_period
|
2018-10-08 15:34:36 +02:00
|
|
|
# Generate a random sequence of reads and writes
|
2019-09-06 16:16:50 +02:00
|
|
|
self.create_random_memory_sequence()
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2018-10-01 06:20:01 +02:00
|
|
|
# Run SPICE simulation
|
2018-09-21 00:04:59 +02:00
|
|
|
self.write_functional_stimulus()
|
|
|
|
|
self.stim.run_sim()
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2019-08-21 22:45:34 +02:00
|
|
|
# read dout values from SPICE simulation. If the values do not fall within the noise margins, return the error.
|
2018-10-08 15:34:36 +02:00
|
|
|
(success, error) = self.read_stim_results()
|
|
|
|
|
if not success:
|
|
|
|
|
return (0, error)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2018-10-08 15:34:36 +02:00
|
|
|
# Check read values with written values. If the values do not match, return an error.
|
|
|
|
|
return self.check_stim_results()
|
2019-09-06 21:09:12 +02:00
|
|
|
|
|
|
|
|
def check_lengths(self):
|
|
|
|
|
""" Do a bunch of assertions. """
|
|
|
|
|
|
|
|
|
|
for port in self.all_ports:
|
|
|
|
|
checks = []
|
|
|
|
|
if port in self.read_ports:
|
2020-06-18 23:55:01 +02:00
|
|
|
checks.append((self.addr_value[port], "addr"))
|
2019-09-06 21:09:12 +02:00
|
|
|
if port in self.write_ports:
|
2020-06-18 23:55:01 +02:00
|
|
|
checks.append((self.data_value[port], "data"))
|
|
|
|
|
checks.append((self.wmask_value[port], "wmask"))
|
|
|
|
|
checks.append((self.spare_wen_value[port], "spare_wen"))
|
2019-09-06 21:09:12 +02:00
|
|
|
|
|
|
|
|
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))
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2019-09-06 16:16:50 +02:00
|
|
|
def create_random_memory_sequence(self):
|
2019-08-21 23:29:57 +02:00
|
|
|
if self.write_size:
|
2019-07-22 20:19:14 +02:00
|
|
|
rw_ops = ["noop", "write", "partial_write", "read"]
|
|
|
|
|
w_ops = ["noop", "write", "partial_write"]
|
|
|
|
|
else:
|
|
|
|
|
rw_ops = ["noop", "write", "read"]
|
|
|
|
|
w_ops = ["noop", "write"]
|
2018-10-08 15:34:36 +02:00
|
|
|
r_ops = ["noop", "read"]
|
2019-07-19 00:26:38 +02:00
|
|
|
|
2019-09-06 21:09:12 +02:00
|
|
|
# First cycle idle is always an idle cycle
|
2020-06-18 23:55:01 +02:00
|
|
|
comment = self.gen_cycle_comment("noop", "0" * self.word_size, "0" * self.addr_size, "0" * self.num_wmasks, 0, self.t_current)
|
2019-09-06 21:09:12 +02:00
|
|
|
self.add_noop_all_ports(comment)
|
|
|
|
|
|
2019-09-08 05:20:44 +02:00
|
|
|
# 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()
|
2020-06-18 23:55:01 +02:00
|
|
|
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)
|
2019-09-08 05:20:44 +02:00
|
|
|
self.stored_words[addr] = word
|
2019-09-06 21:09:12 +02:00
|
|
|
|
2019-09-08 05:20:44 +02:00
|
|
|
# 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)
|
2019-09-06 21:09:12 +02:00
|
|
|
self.cycle_times.append(self.t_current)
|
|
|
|
|
self.t_current += self.period
|
|
|
|
|
self.check_lengths()
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2019-09-06 21:09:12 +02:00
|
|
|
# 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.
|
2018-11-08 21:19:40 +01:00
|
|
|
for port in self.all_ports:
|
|
|
|
|
if port in self.write_ports:
|
2019-09-06 21:09:12 +02:00
|
|
|
self.add_noop_one_port(port)
|
2018-10-04 18:29:44 +02:00
|
|
|
else:
|
2020-06-18 23:55:01 +02:00
|
|
|
comment = self.gen_cycle_comment("read", word, addr, "0" * self.num_wmasks, port, self.t_current)
|
2019-09-06 21:09:12 +02:00
|
|
|
self.add_read_one_port(comment, addr, port)
|
|
|
|
|
self.add_read_check(word, port)
|
2018-10-04 18:29:44 +02:00
|
|
|
self.cycle_times.append(self.t_current)
|
|
|
|
|
self.t_current += self.period
|
2019-09-06 21:09:12 +02:00
|
|
|
self.check_lengths()
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2019-09-06 21:09:12 +02:00
|
|
|
# 3. Perform a random sequence of writes and reads on random
|
|
|
|
|
# ports, using random addresses and random words and random
|
|
|
|
|
# write masks (if applicable)
|
2018-10-08 15:34:36 +02:00
|
|
|
for i in range(self.num_cycles):
|
|
|
|
|
w_addrs = []
|
2018-11-08 21:19:40 +01:00
|
|
|
for port in self.all_ports:
|
|
|
|
|
if port in self.readwrite_ports:
|
2018-10-08 15:34:36 +02:00
|
|
|
op = random.choice(rw_ops)
|
2018-11-08 21:19:40 +01:00
|
|
|
elif port in self.write_ports:
|
2018-10-08 15:34:36 +02:00
|
|
|
op = random.choice(w_ops)
|
2018-10-01 06:20:01 +02:00
|
|
|
else:
|
2018-10-08 15:34:36 +02:00
|
|
|
op = random.choice(r_ops)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2018-10-08 15:34:36 +02:00
|
|
|
if op == "noop":
|
2019-09-06 21:09:12 +02:00
|
|
|
self.add_noop_one_port(port)
|
2018-10-08 15:34:36 +02:00
|
|
|
elif op == "write":
|
|
|
|
|
addr = self.gen_addr()
|
|
|
|
|
# two ports cannot write to the same address
|
|
|
|
|
if addr in w_addrs:
|
2019-09-06 21:09:12 +02:00
|
|
|
self.add_noop_one_port(port)
|
2018-10-08 15:34:36 +02:00
|
|
|
else:
|
2019-09-06 21:09:12 +02:00
|
|
|
word = self.gen_data()
|
2020-06-18 23:55:01 +02:00
|
|
|
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)
|
2018-10-08 15:34:36 +02:00
|
|
|
self.stored_words[addr] = word
|
|
|
|
|
w_addrs.append(addr)
|
2019-07-22 20:19:14 +02:00
|
|
|
elif op == "partial_write":
|
2019-08-07 18:12:21 +02:00
|
|
|
# write only to a word that's been written to
|
2020-06-18 23:55:01 +02:00
|
|
|
(addr, old_word) = self.get_data()
|
2019-07-22 20:19:14 +02:00
|
|
|
# two ports cannot write to the same address
|
|
|
|
|
if addr in w_addrs:
|
2019-09-06 21:09:12 +02:00
|
|
|
self.add_noop_one_port(port)
|
2019-07-22 20:19:14 +02:00
|
|
|
else:
|
2019-09-06 21:09:12 +02:00
|
|
|
word = self.gen_data()
|
|
|
|
|
wmask = self.gen_wmask()
|
2019-09-08 05:05:05 +02:00
|
|
|
new_word = self.gen_masked_data(old_word, word, wmask)
|
2019-07-24 00:58:54 +02:00
|
|
|
comment = self.gen_cycle_comment("partial_write", word, addr, wmask, port, self.t_current)
|
|
|
|
|
self.add_write_one_port(comment, addr, word, wmask, port)
|
2019-07-22 20:19:14 +02:00
|
|
|
self.stored_words[addr] = new_word
|
|
|
|
|
w_addrs.append(addr)
|
2018-10-08 15:34:36 +02:00
|
|
|
else:
|
2020-06-18 23:55:01 +02:00
|
|
|
(addr, word) = random.choice(list(self.stored_words.items()))
|
2019-09-27 23:18:49 +02:00
|
|
|
# The write driver is not sized sufficiently to drive through the two
|
|
|
|
|
# bitcell access transistors to the read port. So, for now, we do not allow
|
|
|
|
|
# a simultaneous write and read to the same address on different ports. This
|
|
|
|
|
# could be even more difficult with multiple simultaneous read ports.
|
|
|
|
|
if addr in w_addrs:
|
|
|
|
|
self.add_noop_one_port(port)
|
|
|
|
|
else:
|
2020-06-18 23:55:01 +02:00
|
|
|
comment = self.gen_cycle_comment("read", word, addr, "0" * self.num_wmasks, port, self.t_current)
|
2019-09-27 23:18:49 +02:00
|
|
|
self.add_read_one_port(comment, addr, port)
|
|
|
|
|
self.add_read_check(word, port)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2018-10-08 15:34:36 +02:00
|
|
|
self.cycle_times.append(self.t_current)
|
|
|
|
|
self.t_current += self.period
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2018-10-08 15:34:36 +02:00
|
|
|
# Last cycle idle needed to correctly measure the value on the second to last clock edge
|
2020-06-18 23:55:01 +02:00
|
|
|
comment = self.gen_cycle_comment("noop", "0" * self.word_size, "0" * self.addr_size, "0" * self.num_wmasks, 0, self.t_current)
|
2019-09-06 21:09:12 +02:00
|
|
|
self.add_noop_all_ports(comment)
|
|
|
|
|
|
2019-09-08 05:05:05 +02:00
|
|
|
def gen_masked_data(self, old_word, word, wmask):
|
|
|
|
|
""" Create the masked data word """
|
|
|
|
|
# Start with the new word
|
|
|
|
|
new_word = word
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2019-09-08 05:05:05 +02:00
|
|
|
# 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
|
2020-06-18 23:55:01 +02:00
|
|
|
new_word = new_word[:lower] + old_word[lower:upper + 1] + new_word[upper + 1:]
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2019-09-08 05:05:05 +02:00
|
|
|
return new_word
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2019-09-06 21:09:12 +02:00
|
|
|
def add_read_check(self, word, port):
|
|
|
|
|
""" Add to the check array to ensure a read works. """
|
|
|
|
|
try:
|
|
|
|
|
self.check
|
|
|
|
|
except:
|
|
|
|
|
self.check = 0
|
2020-06-18 23:55:01 +02:00
|
|
|
self.read_check.append([word, "{0}{1}".format(self.dout_name, port), self.t_current + self.period, self.check])
|
2019-09-06 21:09:12 +02:00
|
|
|
self.check += 1
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2018-10-08 15:34:36 +02:00
|
|
|
def read_stim_results(self):
|
2019-09-06 16:16:50 +02:00
|
|
|
# Extract dout values from spice timing.lis
|
2019-09-06 21:09:12 +02:00
|
|
|
for (word, dout_port, eo_period, check) in self.read_check:
|
2018-10-08 15:34:36 +02:00
|
|
|
sp_read_value = ""
|
2020-06-03 14:31:30 +02:00
|
|
|
for bit in range(self.word_size + self.num_spare_cols):
|
2020-06-18 23:55:01 +02:00
|
|
|
value = parse_spice_list("timing", "v{0}.{1}ck{2}".format(dout_port.lower(), bit, check))
|
2020-09-30 21:40:07 +02:00
|
|
|
try:
|
|
|
|
|
value = float(value)
|
|
|
|
|
if value > self.v_high:
|
|
|
|
|
sp_read_value = "1" + sp_read_value
|
|
|
|
|
elif value < self.v_low:
|
|
|
|
|
sp_read_value = "0" + sp_read_value
|
|
|
|
|
else:
|
|
|
|
|
error ="FAILED: {0}_{1} value {2} at time {3}n does not fall within noise margins <{4} or >{5}.".format(dout_port,
|
|
|
|
|
bit,
|
|
|
|
|
value,
|
|
|
|
|
eo_period,
|
|
|
|
|
self.v_low,
|
|
|
|
|
self.v_high)
|
|
|
|
|
except ValueError:
|
|
|
|
|
error ="FAILED: {0}_{1} value {2} at time {3}n is not a float.".format(dout_port,
|
|
|
|
|
bit,
|
|
|
|
|
value,
|
|
|
|
|
eo_period)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2018-10-08 15:34:36 +02:00
|
|
|
return (0, error)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
|
|
|
|
self.read_results.append([sp_read_value, dout_port, eo_period, check])
|
2018-10-08 15:34:36 +02:00
|
|
|
return (1, "SUCCESS")
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2018-10-08 15:34:36 +02:00
|
|
|
def check_stim_results(self):
|
2019-09-06 21:09:12 +02:00
|
|
|
for i in range(len(self.read_check)):
|
|
|
|
|
if self.read_check[i][0] != self.read_results[i][0]:
|
2020-09-29 19:26:31 +02:00
|
|
|
str = "FAILED: {0} value {1} does not match written value {2} read during cycle {3} at time {4}n"
|
|
|
|
|
error = str.format(self.read_results[i][1],
|
|
|
|
|
self.read_results[i][0],
|
|
|
|
|
self.read_check[i][0],
|
|
|
|
|
int((self.read_results[i][2] - self.period) / self.period),
|
|
|
|
|
self.read_results[i][2])
|
2018-10-08 15:34:36 +02:00
|
|
|
return(0, error)
|
|
|
|
|
return(1, "SUCCESS")
|
2019-07-12 19:34:29 +02:00
|
|
|
|
|
|
|
|
def gen_wmask(self):
|
2019-07-22 20:19:14 +02:00
|
|
|
wmask = ""
|
2019-07-22 21:44:35 +02:00
|
|
|
# generate a random wmask
|
2019-07-19 23:58:37 +02:00
|
|
|
for bit in range(self.num_wmasks):
|
2019-07-12 19:34:29 +02:00
|
|
|
rand = random.randint(0, 1)
|
2019-07-22 20:19:14 +02:00
|
|
|
wmask += str(rand)
|
2019-07-25 22:21:17 +02:00
|
|
|
# prevent the wmask from having all bits on or off (this is not a partial write)
|
2019-07-22 20:19:14 +02:00
|
|
|
all_zeroes = True
|
|
|
|
|
all_ones = True
|
|
|
|
|
for bit in range(self.num_wmasks):
|
|
|
|
|
if wmask[bit]=="0":
|
|
|
|
|
all_ones = False
|
|
|
|
|
elif wmask[bit]=="1":
|
|
|
|
|
all_zeroes = False
|
|
|
|
|
if all_zeroes:
|
|
|
|
|
index = random.randint(0, self.num_wmasks - 1)
|
|
|
|
|
wmask = wmask[:index] + "1" + wmask[index + 1:]
|
|
|
|
|
elif all_ones:
|
|
|
|
|
index = random.randint(0, self.num_wmasks - 1)
|
|
|
|
|
wmask = wmask[:index] + "0" + wmask[index + 1:]
|
2019-07-22 21:44:35 +02:00
|
|
|
# wmask must be reversed since a python list goes right to left and sram bits go left to right.
|
|
|
|
|
return wmask[::-1]
|
2019-07-12 19:34:29 +02:00
|
|
|
|
2018-09-21 00:04:59 +02:00
|
|
|
def gen_data(self):
|
2018-10-01 06:20:01 +02:00
|
|
|
""" Generates a random word to write. """
|
2020-06-03 14:31:30 +02:00
|
|
|
if not self.num_spare_cols:
|
2020-06-18 23:55:01 +02:00
|
|
|
random_value = random.randint(0, (2 ** self.word_size) - 1)
|
2020-06-03 14:31:30 +02:00
|
|
|
else:
|
2020-06-18 23:55:01 +02:00
|
|
|
random_value1 = random.randint(0, (2 ** self.word_size) - 1)
|
|
|
|
|
random_value2 = random.randint(0, (2 ** self.num_spare_cols) - 1)
|
2020-06-03 14:31:30 +02:00
|
|
|
random_value = random_value1 + random_value2
|
2020-06-18 23:55:01 +02:00
|
|
|
data_bits = self.convert_to_bin(random_value, False)
|
2018-09-21 00:04:59 +02:00
|
|
|
return data_bits
|
2019-07-22 20:19:14 +02:00
|
|
|
|
2018-09-21 00:04:59 +02:00
|
|
|
def gen_addr(self):
|
2018-10-01 06:20:01 +02:00
|
|
|
""" Generates a random address value to write to. """
|
2020-06-03 14:31:30 +02:00
|
|
|
if self.num_spare_rows==0:
|
2020-06-18 23:55:01 +02:00
|
|
|
random_value = random.randint(0, (2 ** self.addr_size) - 1)
|
2020-02-20 18:01:52 +01:00
|
|
|
else:
|
2020-06-18 23:55:01 +02:00
|
|
|
random_value = random.randint(0, ((2 ** (self.addr_size - 1) - 1)) + (self.num_spare_rows * self.words_per_row))
|
|
|
|
|
addr_bits = self.convert_to_bin(random_value, True)
|
2019-07-19 00:26:38 +02:00
|
|
|
return addr_bits
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2018-09-21 00:04:59 +02:00
|
|
|
def get_data(self):
|
2018-10-01 06:20:01 +02:00
|
|
|
""" Gets an available address and corresponding word. """
|
2019-08-07 18:12:21 +02:00
|
|
|
# Used for write masks since they should be writing to previously written addresses
|
2019-08-01 21:21:30 +02:00
|
|
|
addr = random.choice(list(self.stored_words.keys()))
|
2018-09-21 00:04:59 +02:00
|
|
|
word = self.stored_words[addr]
|
2020-06-18 23:55:01 +02:00
|
|
|
return (addr, word)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2020-06-18 23:55:01 +02:00
|
|
|
def convert_to_bin(self, value, is_addr):
|
2018-10-01 06:20:01 +02:00
|
|
|
""" Converts addr & word to usable binary values. """
|
2020-06-18 23:55:01 +02:00
|
|
|
new_value = str.replace(bin(value), "0b", "")
|
2018-09-21 00:04:59 +02:00
|
|
|
if(is_addr):
|
|
|
|
|
expected_value = self.addr_size
|
|
|
|
|
else:
|
2020-06-03 14:31:30 +02:00
|
|
|
expected_value = self.word_size + self.num_spare_cols
|
2020-06-18 23:55:01 +02:00
|
|
|
for i in range(expected_value - len(new_value)):
|
2020-10-02 22:32:52 +02:00
|
|
|
new_value = "0" + new_value
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2020-06-18 23:55:01 +02:00
|
|
|
# print("Binary Conversion: {} to {}".format(value, new_value))
|
|
|
|
|
return new_value
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2018-09-21 00:04:59 +02:00
|
|
|
def write_functional_stimulus(self):
|
2018-10-01 06:20:01 +02:00
|
|
|
""" Writes SPICE stimulus. """
|
2018-09-21 00:04:59 +02:00
|
|
|
temp_stim = "{0}/stim.sp".format(OPTS.openram_temp)
|
2020-06-18 23:55:01 +02:00
|
|
|
self.sf = open(temp_stim, "w")
|
2018-09-21 00:04:59 +02:00
|
|
|
self.sf.write("* Functional test stimulus file for {}ns period\n\n".format(self.period))
|
2020-06-18 23:55:01 +02:00
|
|
|
self.stim = stimuli(self.sf, self.corner)
|
2018-09-21 00:04:59 +02:00
|
|
|
|
2020-06-18 23:55:01 +02:00
|
|
|
# Write include statements
|
2019-07-27 20:09:08 +02:00
|
|
|
self.stim.write_include(self.sp_file)
|
2018-09-21 00:04:59 +02:00
|
|
|
|
2020-06-18 23:55:01 +02:00
|
|
|
# Write Vdd/Gnd statements
|
2018-09-21 00:04:59 +02:00
|
|
|
self.sf.write("\n* Global Power Supplies\n")
|
|
|
|
|
self.stim.write_supply()
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2020-06-18 23:55:01 +02:00
|
|
|
# Instantiate the SRAM
|
2018-09-21 00:04:59 +02:00
|
|
|
self.sf.write("\n* Instantiation of the SRAM\n")
|
2019-05-14 10:15:50 +02:00
|
|
|
self.stim.inst_model(pins=self.pins,
|
|
|
|
|
model_name=self.sram.name)
|
2018-09-21 00:04:59 +02:00
|
|
|
|
|
|
|
|
# Add load capacitance to each of the read ports
|
|
|
|
|
self.sf.write("\n* SRAM output loads\n")
|
2018-11-08 21:19:40 +01:00
|
|
|
for port in self.read_ports:
|
2020-06-03 14:31:30 +02:00
|
|
|
for bit in range(self.word_size + self.num_spare_cols):
|
2018-11-08 21:19:40 +01:00
|
|
|
sig_name="{0}{1}_{2} ".format(self.dout_name, port, bit)
|
|
|
|
|
self.sf.write("CD{0}{1} {2} 0 {3}f\n".format(port, bit, sig_name, self.load))
|
2019-07-26 23:49:53 +02:00
|
|
|
|
|
|
|
|
# Write important signals to stim file
|
|
|
|
|
self.sf.write("\n\n* Important signals for debug\n")
|
2020-09-30 21:40:07 +02:00
|
|
|
self.sf.write("* bl: {}\n".format(self.bl_name.format(port)))
|
|
|
|
|
self.sf.write("* br: {}\n".format(self.br_name.format(port)))
|
2019-07-26 23:49:53 +02:00
|
|
|
self.sf.write("* s_en: {}\n".format(self.sen_name))
|
|
|
|
|
self.sf.write("* q: {}\n".format(self.q_name))
|
|
|
|
|
self.sf.write("* qbar: {}\n".format(self.qbar_name))
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2018-10-25 08:29:09 +02:00
|
|
|
# Write debug comments to stim file
|
2019-07-26 23:49:53 +02:00
|
|
|
self.sf.write("\n\n* Sequence of operations\n")
|
2018-10-25 09:58:01 +02:00
|
|
|
for comment in self.fn_cycle_comments:
|
2018-10-25 08:29:09 +02:00
|
|
|
self.sf.write("*{}\n".format(comment))
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2020-06-18 23:55:01 +02:00
|
|
|
# Generate data input bits
|
2018-09-21 00:04:59 +02:00
|
|
|
self.sf.write("\n* Generation of data and address signals\n")
|
2018-11-08 21:19:40 +01:00
|
|
|
for port in self.write_ports:
|
2020-06-03 14:31:30 +02:00
|
|
|
for bit in range(self.word_size + self.num_spare_cols):
|
2018-10-08 15:34:36 +02:00
|
|
|
sig_name="{0}{1}_{2} ".format(self.din_name, port, bit)
|
2018-10-04 18:29:44 +02:00
|
|
|
self.stim.gen_pwl(sig_name, self.cycle_times, self.data_values[port][bit], self.period, self.slew, 0.05)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2018-09-21 00:04:59 +02:00
|
|
|
# Generate address bits
|
2018-11-08 21:19:40 +01:00
|
|
|
for port in self.all_ports:
|
2018-09-21 00:04:59 +02:00
|
|
|
for bit in range(self.addr_size):
|
2018-10-08 15:34:36 +02:00
|
|
|
sig_name="{0}{1}_{2} ".format(self.addr_name, port, bit)
|
2018-10-04 18:29:44 +02:00
|
|
|
self.stim.gen_pwl(sig_name, self.cycle_times, self.addr_values[port][bit], self.period, self.slew, 0.05)
|
2018-09-21 00:04:59 +02:00
|
|
|
|
|
|
|
|
# Generate control signals
|
|
|
|
|
self.sf.write("\n * Generation of control signals\n")
|
2018-11-08 21:19:40 +01:00
|
|
|
for port in self.all_ports:
|
2020-06-18 23:55:01 +02:00
|
|
|
self.stim.gen_pwl("CSB{}".format(port), self.cycle_times, self.csb_values[port], self.period, self.slew, 0.05)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2018-11-08 21:19:40 +01:00
|
|
|
for port in self.readwrite_ports:
|
2020-06-18 23:55:01 +02:00
|
|
|
self.stim.gen_pwl("WEB{}".format(port), self.cycle_times, self.web_values[port], self.period, self.slew, 0.05)
|
2018-09-21 00:04:59 +02:00
|
|
|
|
2019-07-19 22:17:55 +02:00
|
|
|
# Generate wmask bits
|
|
|
|
|
for port in self.write_ports:
|
2019-08-21 23:29:57 +02:00
|
|
|
if self.write_size:
|
2019-07-22 20:19:14 +02:00
|
|
|
self.sf.write("\n* Generation of wmask signals\n")
|
2019-07-19 23:58:37 +02:00
|
|
|
for bit in range(self.num_wmasks):
|
2019-07-19 22:17:55 +02:00
|
|
|
sig_name = "WMASK{0}_{1} ".format(port, bit)
|
2019-07-24 00:58:54 +02:00
|
|
|
# self.stim.gen_pwl(sig_name, self.cycle_times, self.data_values[port][bit], self.period,
|
2019-07-19 22:17:55 +02:00
|
|
|
# self.slew, 0.05)
|
2019-07-24 00:58:54 +02:00
|
|
|
self.stim.gen_pwl(sig_name, self.cycle_times, self.wmask_values[port][bit], self.period,
|
|
|
|
|
self.slew, 0.05)
|
2019-07-19 22:17:55 +02:00
|
|
|
|
2020-06-03 14:31:30 +02:00
|
|
|
# Generate spare enable bits (for spare cols)
|
|
|
|
|
for port in self.write_ports:
|
|
|
|
|
if self.num_spare_cols:
|
|
|
|
|
self.sf.write("\n* Generation of spare enable signals\n")
|
|
|
|
|
for bit in range(self.num_spare_cols):
|
|
|
|
|
sig_name = "SPARE_WEN{0}_{1} ".format(port, bit)
|
|
|
|
|
self.stim.gen_pwl(sig_name, self.cycle_times, self.spare_wen_values[port][bit], self.period,
|
|
|
|
|
self.slew, 0.05)
|
|
|
|
|
|
2018-09-29 08:38:48 +02:00
|
|
|
# Generate CLK signals
|
2018-11-08 21:19:40 +01:00
|
|
|
for port in self.all_ports:
|
2019-09-05 03:59:08 +02:00
|
|
|
self.stim.gen_pulse(sig_name="{0}{1}".format("clk", port),
|
2018-09-29 08:38:48 +02:00
|
|
|
v1=self.gnd_voltage,
|
|
|
|
|
v2=self.vdd_voltage,
|
|
|
|
|
offset=self.period,
|
|
|
|
|
period=self.period,
|
|
|
|
|
t_rise=self.slew,
|
|
|
|
|
t_fall=self.slew)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2019-08-21 22:45:34 +02:00
|
|
|
# Generate dout value measurements
|
2018-09-21 00:04:59 +02:00
|
|
|
self.sf.write("\n * Generation of dout measurements\n")
|
2019-09-06 21:09:12 +02:00
|
|
|
for (word, dout_port, eo_period, check) in self.read_check:
|
2020-06-18 23:55:01 +02:00
|
|
|
t_intital = eo_period - 0.01 * self.period
|
|
|
|
|
t_final = eo_period + 0.01 * self.period
|
2020-06-03 14:31:30 +02:00
|
|
|
for bit in range(self.word_size + self.num_spare_cols):
|
2020-06-18 23:55:01 +02:00
|
|
|
self.stim.gen_meas_value(meas_name="V{0}_{1}ck{2}".format(dout_port, bit, check),
|
|
|
|
|
dout="{0}_{1}".format(dout_port, bit),
|
2018-10-08 15:34:36 +02:00
|
|
|
t_intital=t_intital,
|
|
|
|
|
t_final=t_final)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2018-10-25 09:58:01 +02:00
|
|
|
self.stim.write_control(self.cycle_times[-1] + self.period)
|
2018-09-21 00:04:59 +02:00
|
|
|
self.sf.close()
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2020-09-04 11:24:18 +02:00
|
|
|
#FIXME: Similar function to delay.py, refactor this
|
2019-07-26 23:49:53 +02:00
|
|
|
def get_bit_name(self):
|
|
|
|
|
""" Get a bit cell name """
|
|
|
|
|
(cell_name, cell_inst) = self.sram.get_cell_name(self.sram.name, 0, 0)
|
|
|
|
|
storage_names = cell_inst.mod.get_storage_net_names()
|
|
|
|
|
debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes"
|
|
|
|
|
"supported for characterization. Storage nets={}").format(storage_names))
|
2020-06-18 23:55:01 +02:00
|
|
|
q_name = cell_name + '.' + str(storage_names[0])
|
|
|
|
|
qbar_name = cell_name + '.' + str(storage_names[1])
|
2019-07-26 23:49:53 +02:00
|
|
|
|
2020-06-18 23:55:01 +02:00
|
|
|
return (q_name, qbar_name)
|
2019-07-26 23:49:53 +02:00
|
|
|
|
2020-11-03 15:29:17 +01:00
|
|
|
|