Files
DRAMSys/dram/resources/scripts/tests.py

388 lines
15 KiB
Python

import sys
import sqlite3
import math
class DramConfig(object):
"""Holds the timing constraints in the standard and the configuration of the DRAM"""
unitOfTime = "ns"
nActivateWindow = 0
clk = numberOfBanks = 0
tRP = tRAS = tRC = tRRD = tRCD = tTAW = tRL = tWL = tWTR = tRFC = tWR = 0
tReadLength = tWriteLength = 0
bankwiseRefresh = False
bankwisePowerdown = False
def clkAlign(self, value):
return math.ceil(1.0*value/self.clk)*self.clk
def parseFromXml(self):
self.clk = 6
self.numberOfBanks = 8
self.nActivateWindow = 2
self.burstLength = 2
self.tRP = 3*self.clk
self.tRAS = 6*self.clk
self.tRRD = 2*self.clk
self.tRC = self.tRP + self.tRAS
self.tRCD = 3*self.clk
self.tRL = 3*self.clk
self.tWL = 1*self.clk
self.tWTR = 3*self.clk
self.tWR = 2*self.clk
self.tTAW = self.clkAlign(50)
self.tRFC = self.clkAlign(130)
self.tCKESR = self.clkAlign(max(3*self.clk, 15))
def __init__(self):
self.parseFromXml()
dramconfig = DramConfig()
def calculateReadLength(burstLength):
return dramconfig.tRL + burstLength * dramconfig.clk
def calculateWriteLength(burstLength):
return dramconfig.tWL + burstLength * dramconfig.clk
# ----------- test utils ---------------------------------------
tests = []
def test(function):
tests.append(function)
return function
class TestResult(object):
passed = True
message = ''
def __init__(self, passed = True, message = ''):
self.passed = passed
self.message = message
def TestSuceeded():
return TestResult()
def TestFailed(message):
return TestResult(False,message);
def formatTime(time):
return ('{0} {1}'.format(time, dramconfig.unitOfTime))
# ----------- command bus checks ---------------------------------------
@test
def commands_are_clockaligned(connection):
"""Checks that all commands on the command bus are aligned to the system clock"""
passedTest = True
cursor = connection.cursor()
query = "select ID,PhaseBegin,PhaseEnd from phases where phaseName NOT IN ('REQ','RESP') AND (phaseBegin%:clk!=0 OR phaseEnd%:clk!=0)"
cursor.execute(query, {"clk": 6})
result = cursor.fetchone()
if(result != None):
return TestFailed("Command with PhaseID {0} starts at {1} and ends at, which is not aligned to system clock ({2})".format(
result[0], formatTime(result[1]), formatTime(result[2]), formatTime(dramconfig.clk)))
return TestSuceeded()
@test
def commandbus_slots_are_used_once(connection):
"""Checks that no two phases on the command bus start at the same time"""
cursor = connection.cursor()
if dramconfig.bankwisePowerdown and dramconfig.bankwiseRefresh:
excludedPhases = "('REQ','RESP','PRE_ALL')"
elif (not dramconfig.bankwisePowerdown and dramconfig.bankwiseRefresh):
excludedPhases = "('REQ','RESP','PRE_ALL','PDNA','PDNP','SREF')"
elif dramconfig.bankwisePowerdown and not dramconfig.bankwiseRefresh:
excludedPhases = "('REQ','RESP','PRE_ALL','AUTO_REFRESH')"
else:
excludedPhases = "('REQ','RESP','PRE_ALL','PDNA','PDNP','SREF','AUTO_REFRESH')"
query = """SELECT PhaseBegin,count FROM (SELECT phaseBegin,count(phasebegin) AS count
FROM Phases WHERE PhaseName NOT IN """ + excludedPhases + """ AND phasebegin>0 GROUP BY phaseBegin) WHERE count>1"""
cursor.execute(query)
result = cursor.fetchone()
if(result != None):
return TestFailed("Slot on commandbus at time {0} is used multiple times".format(formatTime(result[0])))
return TestSuceeded()
@test
def command_sequences_are_valid(connection):
cursor = connection.cursor()
cursor.execute("SELECT group_concat(phaseName),transact FROM phases GROUP BY transact")
validSequences = set(['AUTO_REFRESH','PDNA','PDNP','SREF',
'PRE,AUTO_REFRESH', 'PRE_ALL,AUTO_REFRESH',
'REQ,RD,RESP', 'REQ,WR,RESP', 'REQ,RDA,RESP', 'REQ,WRA,RESP',
'REQ,ACT,RD,RESP', 'REQ,ACT,WR,RESP', 'REQ,ACT,RDA,RESP', 'REQ,ACT,WRA,RESP',
'REQ,PRE,ACT,RD,RESP', 'REQ,PRE,ACT,WR,RESP', 'REQ,PRE,ACT,RDA,RESP', 'REQ,PRE,ACT,WRA,RESP'
])
for currentRow in cursor:
commandSequence = currentRow[0]
if(commandSequence not in validSequences):
return TestFailed("Transaction {0} generated invalid command sequence {1}".format(currentRow[1], commandSequence))
return TestSuceeded()
@test
def row_buffer_is_used_correctly(connection):
cursor = connection.cursor()
query = """SELECT PhaseName, phases.ID FROM phases INNER JOIN transactions ON phases.transact=transactions.ID WHERE TBank=:bank
AND PhaseName NOT IN ('REQ','RESP') ORDER BY PhaseBegin"""
for bankNumber in range(dramconfig.numberOfBanks):
cursor.execute(query,{"bank": bankNumber})
rowBufferIsClosed = True
prechargingPhases = set(['PRE','PRE_ALL','RDA','WRA'])
accessingPhases = set(['RD,RDA,WR,WRA,SREF,AUTO_REFRESH'])
for currentRow in cursor:
if(currentRow[0] in accessingPhases and rowBufferIsClosed == True):
return TestFailed("Phase {0}({1}) acesses a closed rowbuffer".format(currentRow[1], currentRow[0]))
if(currentRow[0] == 'ACT'):
if(rowBufferIsClosed == True):
rowBufferIsClosed = False
else:
return TestFailed("Phase {0}({1}) activates an already activated rowbuffer".format(currentRow[1],currentRow[0]))
if(currentRow[0] in prechargingPhases):
if(rowBufferIsClosed == True and currentRow[0] != 'PRE_ALL'):
return TestFailed("Phase {0}({1}) closes an already closed rowbuffer".format(currentRow[1],currentRow[0]))
else:
rowBufferIsClosed = True
return TestSuceeded()
@test
def phases_on_bank_are_sequential(connection):
cursor = connection.cursor()
query = """SELECT PhaseBegin,PhaseEnd,phases.ID,PhaseName FROM phases INNER JOIN transactions on transactions.ID = phases.transact WHERE Tbank=:bank AND phaseName not IN ('REQ','RESP')"""
for bankNumber in range(dramconfig.numberOfBanks):
cursor.execute(query,{"bank": bankNumber})
lastRow = cursor.fetchone()
for currentRow in cursor:
if(currentRow[0] < lastRow[1] and not ((currentRow[3]=="RD" and lastRow[3]=="RD")
or (currentRow[3]=="PRE" and lastRow[3]=="RD") or (currentRow[3] == "WR" and lastRow[3] == "WR"))):
return TestFailed("Phases with PhaseIDs {0}({1}) and {2}({3}) should not interleave".format(
currentRow[2], currentRow[3],lastRow[2],lastRow[3]))
lastRow = currentRow
return TestSuceeded()
@test
def phase_lengths_are_correct(connection):
query = """SELECT phases.ID,PhaseName, PhaseEnd-PhaseBegin,Burstlength FROM Phases INNER JOIN transactions ON transactions.ID = phases.transact """
cursor = connection.cursor()
cursor.execute(query)
for currentRow in cursor:
command = currentRow[1]
commandLength = currentRow[2]
burstlength = currentRow[3]
if(command == "RD" and commandLength != calculateReadLength(burstlength) or
command == "WR" and commandLength != calculateWriteLength(burstLength) or
command == "RDA" and commandLength != calculateReadLength(burstlength)+dramconfig.tRP or
command == "WRA" and commandLength != calculateReadLength(burstlength)+dramconfig.tRP or
(command == "PRE" or command=="PRE_ALL") and commandLength != dramconfig.tRP or
command == "AUTO_REFRESH" and commandLength != dramconfig.tRFC):
return TestFailed("Phase with ID {0}({1}) has invalid length of {2}".format(currentRow[0],command,formatTime(commandLength)))
return TestSuceeded()
#----------- activate checks ---------------------------------------
@test
def activate_to_activate(connection):
"""Checks minimal time between two activates (JEDEC 229, P. 27)"""
cursor = connection.cursor()
cursor.execute("SELECT ID,PhaseBegin from Phases WHERE PhaseName = 'ACT' ORDER BY PhaseBegin")
lastRow = cursor.fetchone()
for currentRow in cursor:
timeBetweenActivates = currentRow[1] - lastRow[1];
if(timeBetweenActivates < dramconfig.tRRD):
return TestFailed("Activates with PhaseIDs {0} and {1} are {2} apart. Minimum time between two activates is {3}".format(currentRow[0], lastRow[0],formatTime(timeBetweenActivates), dramconfig.tRRD))
else:
lastRow = currentRow
return TestSuceeded()
@test
def activate_to_activate_on_same_bank(connection):
"""Checks minimal time between two activates on the same bank (JEDEC 229, P. 27)"""
cursor = connection.cursor()
query = "SELECT Phases.ID,PhaseBegin from Phases INNER JOIN Transactions ON Phases.Transact = Transactions.ID WHERE PhaseName = 'ACT' AND TBANK = :bank ORDER BY PhaseBegin"
for bankNumber in range(dramconfig.numberOfBanks):
cursor.execute(query,{"bank": bankNumber})
lastRow = cursor.fetchone()
for currentRow in cursor:
timeBetweenActivates = currentRow[1] - lastRow[1];
if(timeBetweenActivates < dramconfig.tRC):
return TestFailed("Activates with PhaseIDs {0} and {1} are {2} apart. Minimum time between two activates is {3}, since they are on the same bank({4})".
format(currentRow[0], lastRow[0],formatTime(timeBetweenActivates), dramconfig.tRC))
else:
lastRow = currentRow
return TestSuceeded()
@test
def n_activate_window(connection):
"""Checks n-Activate constraint (JEDEC 229, P. 27)"""
cursor = connection.cursor()
cursor.execute("SELECT ID,PhaseBegin from Phases WHERE PhaseName = 'ACT' ORDER BY PhaseBegin")
activateWindow = []
for currentRow in cursor:
activateWindow.append(currentRow[1])
if(len(activateWindow) > dramconfig.nActivateWindow + 1):
activateWindow.pop(0)
if(activateWindow[dramconfig.nActivateWindow] - activateWindow[0] < dramconfig.tTAW):
return TestFailed("Activate with PhaseID {0} and the {1} preceeding activates violate the '{1} activate window' constraint."
" No more than {1} activates should be in rolling time window of {2}".format(currentRow[0], dramconfig.nActivateWindow,formatTime(dramconfig.tTAW)))
return TestSuceeded()
# ----------- read/write checks ---------------------------------------
@test
def read_to_read(connection):
"""Checks minimal time between two reads(JEDEC 229, P. 29)"""
cursor = connection.cursor()
cursor.execute("SELECT Phases.ID, PhaseBegin, PhaseName from Phases WHERE PhaseName IN ('RD','RDA') ORDER BY PhaseBegin")
lastRow = cursor.fetchone()
for currentRow in cursor:
if(currentRow[1] < lastRow[1] + dramconfig.tReadLength):
timeBetweenReads = currentRow[1] - lastRow[1];
clocksBetweenReads = round(timeBetweenReads/dramconfig.clk)
if(clocksBetweenReads % 2 == 1):
return TestFailed("{0} with PhaseID {1} interrupts data acess of {2} {3}. They are {4} clocks ({5}) apart. Numbers of clock between interrupting reads must be even.".
format(currentRow[2], currentRow[0], lastRow[2], lastRow[0], clocksBetweenReads, formatTime(timeBetweenReads)))
lastRow = currentRow
return TestSuceeded()
@test
def write_to_read_and_read_to_write(connection):
"""Checks minimal time between write and read/read and write (JEDEC 229, P. 33/34)"""
cursor = connection.cursor()
query = """SELECT Phases.ID,PhaseBegin,PhaseName from Phases INNER JOIN Transactions ON Phases.Transact = Transactions.ID
WHERE PhaseName IN ('RD','WR','RDA','WRA') ORDER BY PhaseBegin"""
cursor.execute(query)
lastRow = cursor.fetchone()
for currentRow in cursor:
if(currentRow[2] in ["RD","RDA"] and lastRow[2] in ["WR","WRA"]):
writeEndToReadBegin = currentRow[1] - (lastRow[1] + dramconfig.tWriteLength);
if(writeEndToReadBegin < dramconfig.tWTR ):
return TestFailed("Read with PhaseID {0} starts {1} after end of data access of write {2}. Minimum time between end of write and start of read is {3}".
format(currentRow[0],formatTime(writeEndToReadBegin),lastRow[0], formatTime(dramconfig.tWTR )))
elif(currentRow[2] in ["WR","WRA"] and lastRow[2] in ["RD","RDA"]):
if(currentRow[1] < (lastRow[1]+dramconfig.tReadLength)):
return TestFailed("WR with PhaseID {0} starts before end of data acess of read {1}".
format(currentRow[0], lastRow[0]))
lastRow = currentRow
return TestSuceeded()
@test
def write_or_read_to_precharge(connection):
"""Checks minimal time between write/read and precharge (JEDEC 229, P. 37)"""
cursor = connection.cursor()
query = """SELECT Phases.ID,PhaseBegin,PhaseEnd,PhaseName from Phases INNER JOIN Transactions ON Phases.Transact = Transactions.ID
WHERE PhaseName IN ('RD','WR', 'PRE', 'PRE_ALL') AND TBANK = :bank ORDER BY PhaseBegin"""
minReadStartToPrecharge = dramconfig.burstLength * dramconfig.clk
minWriteEndToPreStart = dramconfig.tWR
for bankNumber in range(dramconfig.numberOfBanks):
cursor.execute(query,{"bank": bankNumber})
lastRow = cursor.fetchone()
for currentRow in cursor:
if(currentRow[3] in ["PRE","PRE_ALL"] and lastRow[3] == "RD"):
readStartToPrecharge = currentRow[1] - lastRow[1]
if(readStartToPrecharge < minReadStartToPrecharge):
return TestFailed("Precharge with PhaseID {0} starts {1} after start of RD {2}. Minimum time between end of read and start of precharge is {3}".
format(currentRow[0],formatTime(readStartToPrecharge),lastRow[0], formatTime(minReadStartToPrecharge)))
elif(currentRow[3] in ["PRE","PRE_ALL"] and lastRow[3] == "WR"):
writeEndToPrecharge = currentRow[1] - lastRow[2]
if(writeEndToPrecharge < minWriteEndToPreStart):
return TestFailed("Precharge with PhaseID {0} starts {1} after end of WR {2}. Minimum time between end of write and start of precharge is {3}".
format(currentRow[0],formatTime(writeEndToPrecharge),lastRow[0], formatTime(minWriteEndToPreStart)))
lastRow = currentRow
return TestSuceeded()
@test
def sref_active_for_minimal_time(connection):
"""Checks if SREF is active for at least a minimal time (JEDEC 229, P. 41)"""
cursor = connection.cursor()
cursor.execute("SELECT ID, PhaseEnd-PhaseBegin FROM Phases WHERE PhaseName = 'SREF'")
for currentRow in cursor:
if(currentRow[1] < dramconfig.tCKESR):
return TestFailed("SREF with ID {0} is {1} long. Minimal time in SREF is {2}".format(currentRow[0], formatTime(currentRow[1]), dramconfig.tCKESR))
return TestSuceeded()
# -------------------------- interface methods --------------------
def runTests(pathToTrace):
connection = sqlite3.connect(pathToTrace)
testResults = []
numberOfFailedTest = 0
print("================================")
print("RUNNING TEST ON {0}".format(pathToTrace))
print("-----------------------------\n")
for test in tests:
testResult = test(connection)
testName = test.__name__.replace("_"," ")
testResults.append((testName, testResult.passed,testResult.message))
if(testResult.passed):
print("{0} passed".format(testName))
else:
print(">>>>>>{0} failed. Message: {1}".format(testName, testResult.message))
numberOfFailedTest = numberOfFailedTest + 1
print("\n-----------------------------")
if(numberOfFailedTest == 0):
print("All tests passed")
else:
print("{0} of {1} tests passed".format(len(tests) - numberOfFailedTest,len(tests)))
print("================================")
connection.close()
return testResults
if __name__ == "__main__":
path = sys.argv[1]
runTests(path)