mirror of https://github.com/VLSIDA/OpenRAM.git
294 lines
14 KiB
Python
294 lines
14 KiB
Python
# See LICENSE for licensing information.
|
|
#
|
|
#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.
|
|
#
|
|
import sys,re,shutil
|
|
from design import design
|
|
import debug
|
|
import math
|
|
import tech
|
|
import random
|
|
from .stimuli import *
|
|
from .charutils import *
|
|
import utils
|
|
from globals import OPTS
|
|
|
|
from .simulation import simulation
|
|
from .delay import delay
|
|
|
|
class functional(simulation):
|
|
"""
|
|
Functions to write random data values to a random address then read them back and check
|
|
for successful SRAM operation.
|
|
"""
|
|
|
|
def __init__(self, sram, spfile, corner):
|
|
simulation.__init__(self, sram, spfile, corner)
|
|
|
|
# Seed the characterizer with a constant seed for unit tests
|
|
if OPTS.is_unit_test:
|
|
random.seed(12345)
|
|
|
|
self.set_corner(corner)
|
|
self.set_spice_constants()
|
|
#self.set_feasible_period(sram, spfile, corner)
|
|
self.set_stimulus_variables()
|
|
self.create_signal_names()
|
|
|
|
|
|
# Number of checks can be changed
|
|
self.num_cycles = 2
|
|
self.stored_words = {}
|
|
self.write_check = []
|
|
self.read_check = []
|
|
|
|
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()
|
|
|
|
# Run SPICE simulation
|
|
self.write_functional_stimulus()
|
|
self.stim.run_sim()
|
|
|
|
# read DOUT values from SPICE simulation. If the values do not fall within the noise margins, return the error.
|
|
(success, error) = self.read_stim_results()
|
|
if not success:
|
|
return (0, error)
|
|
|
|
# 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):
|
|
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, 0, self.t_current)
|
|
self.add_noop_all_ports(comment, "0"*self.addr_size, "0"*self.word_size)
|
|
|
|
# Write at least once
|
|
addr = self.gen_addr()
|
|
word = self.gen_data()
|
|
comment = self.gen_cycle_comment("write", word, addr, 0, self.t_current)
|
|
self.add_write(comment, addr, word, 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.
|
|
# 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, port)
|
|
else:
|
|
comment = self.gen_cycle_comment("read", word, addr, port, self.t_current)
|
|
self.add_read_one_port(comment, addr, rw_read_din_data, port)
|
|
self.write_check.append([word, "{0}{1}".format(self.dout_name,port), self.t_current+self.period, check])
|
|
check += 1
|
|
self.cycle_times.append(self.t_current)
|
|
self.t_current += self.period
|
|
|
|
# Perform a random sequence of writes and reads on random ports, using random addresses and random words
|
|
for i in range(self.num_cycles):
|
|
w_addrs = []
|
|
for port in self.all_ports:
|
|
if port in self.readwrite_ports:
|
|
op = random.choice(rw_ops)
|
|
elif port in self.write_ports:
|
|
op = random.choice(w_ops)
|
|
else:
|
|
op = random.choice(r_ops)
|
|
|
|
if op == "noop":
|
|
addr = "0"*self.addr_size
|
|
word = "0"*self.word_size
|
|
self.add_noop_one_port(addr, word, 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, port)
|
|
else:
|
|
comment = self.gen_cycle_comment("write", word, addr, port, self.t_current)
|
|
self.add_write_one_port(comment, addr, word, port)
|
|
self.stored_words[addr] = word
|
|
w_addrs.append(addr)
|
|
else:
|
|
(addr,word) = random.choice(list(self.stored_words.items()))
|
|
# cannot read from an address that is currently being written to
|
|
if addr in w_addrs:
|
|
self.add_noop_one_port("0"*self.addr_size, "0"*self.word_size, port)
|
|
else:
|
|
comment = self.gen_cycle_comment("read", word, addr, port, self.t_current)
|
|
self.add_read_one_port(comment, addr, rw_read_din_data, port)
|
|
self.write_check.append([word, "{0}{1}".format(self.dout_name,port), self.t_current+self.period, check])
|
|
check += 1
|
|
|
|
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, 0, self.t_current)
|
|
self.add_noop_all_ports(comment, "0"*self.addr_size, "0"*self.word_size)
|
|
|
|
def read_stim_results(self):
|
|
# Extrat DOUT values from spice timing.lis
|
|
for (word, dout_port, eo_period, check) in self.write_check:
|
|
sp_read_value = ""
|
|
for bit in range(self.word_size):
|
|
value = parse_spice_list("timing", "v{0}.{1}ck{2}".format(dout_port.lower(),bit,check))
|
|
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)
|
|
return (0, error)
|
|
|
|
self.read_check.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],
|
|
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])
|
|
return(0, error)
|
|
return(1, "SUCCESS")
|
|
|
|
def gen_data(self):
|
|
""" Generates a random word to write. """
|
|
rand = random.randint(0,(2**self.word_size)-1)
|
|
data_bits = self.convert_to_bin(rand,False)
|
|
return data_bits
|
|
|
|
def gen_addr(self):
|
|
""" Generates a random address value to write to. """
|
|
rand = random.randint(0,(2**self.addr_size)-1)
|
|
addr_bits = self.convert_to_bin(rand,True)
|
|
return addr_bits
|
|
|
|
def get_data(self):
|
|
""" Gets an available address and corresponding word. """
|
|
# Currently unused but may need later depending on how the functional test develops
|
|
addr = random.choice(self.stored_words.keys())
|
|
word = self.stored_words[addr]
|
|
return (addr,word)
|
|
|
|
def convert_to_bin(self,value,is_addr):
|
|
""" Converts addr & word to usable binary values. """
|
|
new_value = str.replace(bin(value),"0b","")
|
|
if(is_addr):
|
|
expected_value = self.addr_size
|
|
else:
|
|
expected_value = self.word_size
|
|
for i in range (expected_value - len(new_value)):
|
|
new_value = "0" + new_value
|
|
|
|
#print("Binary Conversion: {} to {}".format(value, new_value))
|
|
return new_value
|
|
|
|
def create_signal_names(self):
|
|
self.addr_name = "A"
|
|
self.din_name = "DIN"
|
|
self.dout_name = "DOUT"
|
|
|
|
def write_functional_stimulus(self):
|
|
""" Writes SPICE stimulus. """
|
|
temp_stim = "{0}/stim.sp".format(OPTS.openram_temp)
|
|
self.sf = open(temp_stim,"w")
|
|
self.sf.write("* Functional test stimulus file for {}ns period\n\n".format(self.period))
|
|
self.stim = stimuli(self.sf,self.corner)
|
|
|
|
#Write include statements
|
|
self.sram_sp_file = "{}sram.sp".format(OPTS.openram_temp)
|
|
shutil.copy(self.sp_file, self.sram_sp_file)
|
|
self.stim.write_include(self.sram_sp_file)
|
|
|
|
#Write Vdd/Gnd statements
|
|
self.sf.write("\n* Global Power Supplies\n")
|
|
self.stim.write_supply()
|
|
|
|
#Instantiate the SRAM
|
|
self.sf.write("\n* Instantiation of the SRAM\n")
|
|
self.stim.inst_sram(sram=self.sram,
|
|
port_signal_names=(self.addr_name,self.din_name,self.dout_name),
|
|
port_info=(len(self.all_ports), self.write_ports, self.read_ports),
|
|
abits=self.addr_size,
|
|
dbits=self.word_size,
|
|
sram_name=self.name)
|
|
|
|
# Add load capacitance to each of the read ports
|
|
self.sf.write("\n* SRAM output loads\n")
|
|
for port in self.read_ports:
|
|
for bit in range(self.word_size):
|
|
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))
|
|
|
|
# Write debug comments to stim file
|
|
self.sf.write("\n\n * Sequence of operations\n")
|
|
for comment in self.fn_cycle_comments:
|
|
self.sf.write("*{}\n".format(comment))
|
|
|
|
# Generate data input bits
|
|
self.sf.write("\n* Generation of data and address signals\n")
|
|
for port in self.write_ports:
|
|
for bit in range(self.word_size):
|
|
sig_name="{0}{1}_{2} ".format(self.din_name, port, bit)
|
|
self.stim.gen_pwl(sig_name, self.cycle_times, self.data_values[port][bit], self.period, self.slew, 0.05)
|
|
|
|
# Generate address bits
|
|
for port in self.all_ports:
|
|
for bit in range(self.addr_size):
|
|
sig_name="{0}{1}_{2} ".format(self.addr_name, port, bit)
|
|
self.stim.gen_pwl(sig_name, self.cycle_times, self.addr_values[port][bit], self.period, self.slew, 0.05)
|
|
|
|
# Generate control signals
|
|
self.sf.write("\n * Generation of control signals\n")
|
|
for port in self.all_ports:
|
|
self.stim.gen_pwl("CSB{}".format(port), self.cycle_times , self.csb_values[port], self.period, self.slew, 0.05)
|
|
|
|
for port in self.readwrite_ports:
|
|
self.stim.gen_pwl("WEB{}".format(port), self.cycle_times , self.web_values[port], self.period, self.slew, 0.05)
|
|
|
|
# Generate CLK signals
|
|
for port in self.all_ports:
|
|
self.stim.gen_pulse(sig_name="{0}{1}".format(tech.spice["clk"], port),
|
|
v1=self.gnd_voltage,
|
|
v2=self.vdd_voltage,
|
|
offset=self.period,
|
|
period=self.period,
|
|
t_rise=self.slew,
|
|
t_fall=self.slew)
|
|
|
|
# Generate DOUT value measurements
|
|
self.sf.write("\n * Generation of dout measurements\n")
|
|
for (word, dout_port, eo_period, check) in self.write_check:
|
|
t_intital = eo_period - 0.01*self.period
|
|
t_final = eo_period + 0.01*self.period
|
|
for bit in range(self.word_size):
|
|
self.stim.gen_meas_value(meas_name="V{0}_{1}ck{2}".format(dout_port,bit,check),
|
|
dout="{0}_{1}".format(dout_port,bit),
|
|
t_intital=t_intital,
|
|
t_final=t_final)
|
|
|
|
self.stim.write_control(self.cycle_times[-1] + self.period)
|
|
self.sf.close()
|
|
|
|
|