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 burstLengtht = 2 clk = numberOfBanks = 0 tRP = tRAS = tRC = tRRD = tRCD = tTAW = tRL = tWL = tWTR = 0 def clkAlign(self, value): return math.ceil(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.tTAW = self.clkAlign(50) def __init__(self): self.parseFromXml() dramconfig = DramConfig() # ----------- 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() cursor.execute("SELECT ID, PhaseBegin, PhaseEnd from Phases WHERE PhaseName NOT IN ('REQ','RESP') ORDER BY PhaseBegin") lastRow = cursor.fetchone() for currentRow in cursor: phaseBegin = currentRow[1] phaseEnd = currentRow[2] if(phaseBegin % dramconfig.clk != 0): return TestFailed("Command with PhaseID {0} starts at {1} and is not aligned to system clock ({2})".format( currentRow[0], formatTime(phaseBegin), formatTime(dramconfig.clk))) if(phaseEnd % dramconfig.clk != 0): return TestFailed("Command with PhaseID {0} end at {1} and is not aligned to system clock ({2})".format( currentRow[0], formatTime(phaseEnd), 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() cursor.execute("SELECT ID,PhaseBegin from Phases WHERE PhaseName NOT IN ('REQ', 'RESP') ORDER BY PhaseBegin") lastRow = cursor.fetchone() for currentRow in cursor: if(lastRow[1] != currentRow[1]): lastRow = currentRow else: return TestFailed("Commandbus slot is used twice by commands with PhaseID {0} and {1}".format(lastRow[0], currentRow[0])) return TestSuceeded() # ----------- precharge checks --------------------------------------- # ----------- activate checks --------------------------------------- @test def precharge_before_activate(connection): """Checks that all activate commands are preceeded by a precharge or a refresh or a powerdown""" cursor = connection.cursor() query = """SELECT Phases.ID, PhaseName, PhaseBegin FROM Transactions INNER JOIN Phases ON Phases.Transact = Transactions.ID WHERE (TBank = :bank AND PhaseName IN ('ACT','PRE','REFB')) OR PhaseName IN ('REFA','SREF','PDNP','PDNA') ORDER BY PhaseBegin""" for bankNumber in range(dramconfig.numberOfBanks): cursor.execute(query,{"bank": bankNumber}) lastRow = cursor.fetchone() for currentRow in cursor: if(lastRow[1] != currentRow[1] or currentRow[1] != 'ACT'): lastRow = currentRow else: return TestFailed("No precharge between activates with PhaseID {0} and {1}".format(lastRow[0], currentRow[0])) return TestSuceeded() @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 checks --------------------------------------- @test def activate_to_read(connection): """Checks minimal time bewteen activate and following read (JEDEC 229, P. 29)""" cursor = connection.cursor() query = "SELECT Phases.ID,PhaseBegin,PhaseName from Phases INNER JOIN Transactions ON Phases.Transact = Transactions.ID WHERE PhaseName IN ('ACT','RD') AND TBANK = :bank ORDER BY PhaseBegin" for bankNumber in range(dramconfig.numberOfBanks): cursor.execute(query,{"bank": bankNumber}) lastRow = cursor.fetchone() for currentRow in cursor: if(currentRow[2] == "RD" and lastRow[2] == "ACT"): actToReadTime = currentRow[1] - lastRow[1]; if(actToReadTime < dramconfig.tRCD): return TestFailed("Read with PhaseID {0} starts {1} after activate {2}. Minimum activate to read time is {3}". format(currentRow[0],formatTime(actToReadTime),lastRow[0], formatTime(dramconfig.tRCD))) lastRow = currentRow return TestSuceeded() @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, PhaseEnd from Phases WHERE PhaseName = 'RD' ORDER BY PhaseBegin") lastRow = cursor.fetchone() for currentRow in cursor: if(currentRow[1] < lastRow[2]): timeBetweenReads = currentRow[1] - lastRow[1]; clocksBetweenReads = round(timeBetweenReads/dramconfig.clk) if(clocksBetweenReads % 2 == 1): return TestFailed("Read with PhaseID {0} interrupts read {1}. They are {2} clocks ({3}) apart. Numbers of clock between interrupting reads must be even.". format(currentRow[0], lastRow[0], clocksBetweenReads, formatTime(timeBetweenReads))) lastRow = currentRow return TestSuceeded() @test def write_to_read(connection): """Checks minimal time between write and following read (JEDEC 229, P. 34)""" 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') AND TBANK = :bank ORDER BY PhaseBegin" for bankNumber in range(dramconfig.numberOfBanks): cursor.execute(query,{"bank": bankNumber}) lastRow = cursor.fetchone() for currentRow in cursor: if(currentRow[3] == "RD" and lastRow[3] == "WR"): writeEndToReadBegin = currentRow[1] - lastRow[2]; if(writeEndToReadBegin < dramconfig.tWTR ): return TestFailed("Read with PhaseID {0} starts {1} after end 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 ))) lastRow = currentRow 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)