feat(python): introduce dramsys Python package
Move all Python scripts out of the Trace Analyzer into a generic dramsys package. To use the dramsys Python module in the Trace Analyzer, the user is expected to install the package into a virtual environment together with its dependencies. The Python package also makes the follwing binaries available: - dramsys_metrics - dramsys_plots - dramsys_tests - dramsys_vcd_export
This commit is contained in:
1
python/.python-version
Normal file
1
python/.python-version
Normal file
@@ -0,0 +1 @@
|
||||
3.14
|
||||
0
python/README.md
Normal file
0
python/README.md
Normal file
24
python/pyproject.toml
Normal file
24
python/pyproject.toml
Normal file
@@ -0,0 +1,24 @@
|
||||
[project]
|
||||
name = "dramsys"
|
||||
version = "0.1.0"
|
||||
description = "DRAMSys utility Python scripts"
|
||||
readme = "README.md"
|
||||
authors = [
|
||||
{ name = "Derek Christ", email = "derek.christ@uni-wuerzburg.de" }
|
||||
]
|
||||
requires-python = ">=3.14"
|
||||
dependencies = [
|
||||
"matplotlib>=3.10.7",
|
||||
"pyvcd>=0.4.1",
|
||||
"tqdm>=4.67.1",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
dramsys_metrics = "dramsys.analysis.metrics:main"
|
||||
dramsys_plots = "dramsys.analysis.plots:main"
|
||||
dramsys_tests = "dramsys.analysis.tests:main"
|
||||
dramsys_vcd_export = "dramsys.tools.vcdExport:main"
|
||||
|
||||
[build-system]
|
||||
requires = ["uv_build>=0.9.7,<0.10.0"]
|
||||
build-backend = "uv_build"
|
||||
0
python/src/dramsys/__init__.py
Normal file
0
python/src/dramsys/__init__.py
Normal file
0
python/src/dramsys/analysis/__init__.py
Normal file
0
python/src/dramsys/analysis/__init__.py
Normal file
1138
python/src/dramsys/analysis/metrics.py
Normal file
1138
python/src/dramsys/analysis/metrics.py
Normal file
File diff suppressed because it is too large
Load Diff
660
python/src/dramsys/analysis/plots.py
Normal file
660
python/src/dramsys/analysis/plots.py
Normal file
@@ -0,0 +1,660 @@
|
||||
import sys
|
||||
import sqlite3
|
||||
import ntpath
|
||||
import os
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from dramsys.common.memUtil import *
|
||||
from math import *
|
||||
from matplotlib.backends.backend_pdf import PdfPages
|
||||
|
||||
numberOfBins = "auto"
|
||||
latencyRange = None
|
||||
|
||||
plots = []
|
||||
|
||||
def plot(function):
|
||||
plots.append(function)
|
||||
return function
|
||||
|
||||
def getThreads(connection):
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("SELECT DISTINCT(Thread) FROM transactions WHERE Thread != 0 ORDER BY Thread")
|
||||
result = []
|
||||
for currentRow in cursor:
|
||||
result.append(currentRow[0])
|
||||
return result
|
||||
|
||||
|
||||
def createOutputFilename(tracePath, plot_type, target_measurement, file_type):
|
||||
name = ntpath.basename(tracePath)
|
||||
basename, extension = os.path.splitext(name)
|
||||
outputFileName = plot_type + '_' + target_measurement + basename + '.' + file_type
|
||||
return outputFileName, target_measurement + basename
|
||||
|
||||
|
||||
def accessDatabase(connection, query):
|
||||
cursor = connection.cursor()
|
||||
# cursor.execute(" ")
|
||||
cursor.execute(query)
|
||||
resultArray = []
|
||||
while True:
|
||||
result = cursor.fetchone()
|
||||
if (result is not None):
|
||||
resultArray.append(result[0])
|
||||
else:
|
||||
break
|
||||
return resultArray
|
||||
|
||||
|
||||
def calculate_bandwidth_util(connection, windowSize, steps, queryFull, queryEnd, queryBegin, queryPart):
|
||||
cursor = connection.cursor()
|
||||
maxDataRate = maximum_data_rate(connection)
|
||||
maximumPercentage = 0
|
||||
bandwidthPercentage = [0] * (steps+1)
|
||||
bandwidth = [0] * (steps+1)
|
||||
bandwidthPercentage[0] = 0
|
||||
bandwidth[0] = 0
|
||||
for i in range(steps):
|
||||
bandwidthPercentage[i+1] = 0
|
||||
cursor.execute(queryPart, (i*windowSize, (i+1)*windowSize))
|
||||
result = cursor.fetchone()
|
||||
if(result is None):
|
||||
cursor.execute(queryFull, (i*windowSize, (i+1)*windowSize))
|
||||
result = cursor.fetchone()
|
||||
if(result[0] is not None):
|
||||
bandwidthPercentage[i+1] += int(result[0])
|
||||
cursor.execute(queryEnd, (i*windowSize, i*windowSize, i*windowSize, (i+1)*windowSize))
|
||||
result = cursor.fetchone()
|
||||
if(result[0] is not None):
|
||||
bandwidthPercentage[i+1] += int(result[0])
|
||||
cursor.execute(queryBegin, ((i+1)*windowSize, i*windowSize, (i+1)*windowSize, (i+1)*windowSize))
|
||||
result = cursor.fetchone()
|
||||
if(result[0] is not None):
|
||||
bandwidthPercentage[i+1] += int(result[0])
|
||||
else:
|
||||
bandwidthPercentage[i+1] = windowSize
|
||||
bandwidthPercentage[i+1] = float(bandwidthPercentage[i+1]/windowSize)
|
||||
bandwidth[i+1] = float(bandwidthPercentage[i+1])*float(maxDataRate)/1024
|
||||
bandwidthPercentage[i+1] *= 100
|
||||
if(maximumPercentage < 100 and maximumPercentage < bandwidthPercentage[i+1]):
|
||||
maximumPercentage = bandwidthPercentage[i+1]
|
||||
|
||||
return bandwidthPercentage, bandwidth, maximumPercentage
|
||||
|
||||
|
||||
def memory_utilisation_window_thread(connection, tracePath, steps, thread_ID):
|
||||
## All possible cases of data transfers inside a time window:
|
||||
|
||||
# The data transfer begins and ends inside the time window
|
||||
queryFull = """
|
||||
SELECT
|
||||
SUM(DataStrobeEnd - DataStrobeBegin)
|
||||
FROM
|
||||
PHASES
|
||||
INNER JOIN
|
||||
Transactions
|
||||
ON Phases.Transact = Transactions.ID
|
||||
WHERE
|
||||
DataStrobeBegin >= ?
|
||||
AND DataStrobeEnd <= ?
|
||||
AND Thread = {0}
|
||||
"""
|
||||
|
||||
# Only the end of the data transfer is inside the time window
|
||||
queryEnd = """
|
||||
SELECT
|
||||
SUM(DataStrobeEnd - ? )
|
||||
FROM
|
||||
PHASES
|
||||
INNER JOIN
|
||||
Transactions
|
||||
ON Phases.Transact = Transactions.ID
|
||||
WHERE
|
||||
DataStrobeBegin < ?
|
||||
AND DataStrobeEnd > ?
|
||||
AND DataStrobeEnd <=?
|
||||
AND Thread = {0}
|
||||
"""
|
||||
|
||||
# Only the beginning of the data transfer is inside the time window
|
||||
queryBegin = """
|
||||
SELECT
|
||||
SUM( ? - DataStrobeBegin)
|
||||
FROM
|
||||
PHASES
|
||||
INNER JOIN
|
||||
Transactions
|
||||
ON Phases.Transact = Transactions.ID
|
||||
WHERE
|
||||
DataStrobeBegin >= ?
|
||||
AND DataStrobeBegin < ?
|
||||
AND DataStrobeEnd > ?
|
||||
AND Thread = {0}
|
||||
"""
|
||||
|
||||
# The data transfer occupies all the time window
|
||||
queryPart = """
|
||||
SELECT
|
||||
DataStrobeBegin
|
||||
FROM
|
||||
PHASES
|
||||
INNER JOIN
|
||||
Transactions
|
||||
ON Phases.Transact = Transactions.ID
|
||||
WHERE
|
||||
DataStrobeBegin <= ?
|
||||
AND DataStrobeEnd >= ?
|
||||
AND Thread = {0}
|
||||
"""
|
||||
|
||||
queryFull = queryFull.format(thread_ID)
|
||||
queryEnd = queryEnd.format(thread_ID)
|
||||
queryBegin = queryBegin.format(thread_ID)
|
||||
queryPart = queryPart.format(thread_ID)
|
||||
|
||||
windowSize = getWindowSize(connection)
|
||||
|
||||
bandwidthPercentage, bandwidth, maximumPercentage = calculate_bandwidth_util(connection, windowSize, steps, queryFull, queryEnd, queryBegin, queryPart)
|
||||
|
||||
outputFileNameBWMatlab, basename = createOutputFilename(tracePath, 'memory_utilization_percent', 'thread_' + str(thread_ID) + '_', 'txt')
|
||||
return bandwidthPercentage, bandwidth, outputFileNameBWMatlab
|
||||
|
||||
@plot
|
||||
def memory_utilisation_window(connection, tracePath, steps):
|
||||
# This function determines the average memory bandwidth over time in
|
||||
# percentage and in Gbit/s. The average bandwidth over time is done
|
||||
# dividing the time into windows of the same length and getting the average
|
||||
# bandwidth in each window. Through data from the database, DataStrobeEnd
|
||||
# and DataStrobeBegin, it is possible to access when a data transfer begins
|
||||
# or ends. Hence, it is achievable to check when a data transfer happens
|
||||
# and if it occupies or is inside a time window. Then, it is attainable to
|
||||
# determine the average bandwidth in percentage. Besides, extracting the
|
||||
# data from the memory specs, it is feasible to calculate the maximum data
|
||||
# rate of the memory and then determine the bandwidth in Gbit/s. The
|
||||
# bandwidth data are then plotted in two graphics.
|
||||
|
||||
windowSize = getWindowSize(connection)
|
||||
maxDataRate = maximum_data_rate(connection)
|
||||
|
||||
## All possible cases of data transfers inside a time window:
|
||||
|
||||
# The data transfer begins and ends inside the time window
|
||||
queryFull = """
|
||||
SELECT
|
||||
SUM(DataStrobeEnd - DataStrobeBegin)
|
||||
FROM
|
||||
Phases
|
||||
WHERE
|
||||
DataStrobeBegin >= ?
|
||||
AND DataStrobeEnd <= ?
|
||||
"""
|
||||
|
||||
# Only the end of the data transfer is inside the time window
|
||||
queryEnd = """
|
||||
SELECT
|
||||
SUM(DataStrobeEnd - ?)
|
||||
FROM
|
||||
Phases
|
||||
WHERE
|
||||
DataStrobeBegin < ?
|
||||
AND DataStrobeEnd > ?
|
||||
AND DataStrobeEnd <= ?
|
||||
"""
|
||||
|
||||
# Only the beginning of the data transfer is inside the time window
|
||||
queryBegin = """
|
||||
SELECT
|
||||
SUM(? - DataStrobeBegin)
|
||||
FROM
|
||||
Phases
|
||||
WHERE
|
||||
DataStrobeBegin >= ?
|
||||
AND DataStrobeBegin < ?
|
||||
AND DataStrobeEnd > ?
|
||||
"""
|
||||
|
||||
# The data transfer occupies all the time window
|
||||
queryPart = """
|
||||
SELECT
|
||||
DataStrobeBegin
|
||||
FROM
|
||||
Phases
|
||||
WHERE
|
||||
DataStrobeBegin <= ?
|
||||
AND DataStrobeEnd >= ?
|
||||
"""
|
||||
|
||||
bandwidthPercentage, bandwidth, maximumPercentage = calculate_bandwidth_util(connection, windowSize, steps, queryFull, queryEnd, queryBegin, queryPart)
|
||||
|
||||
outputFileNameGBPS, basename = createOutputFilename(tracePath, 'memory_utilization_gbps', '', 'pdf')
|
||||
outputFileNamePercent, basename = createOutputFilename(tracePath, 'memory_utilization_percent', '', 'pdf')
|
||||
outputFileNameBWMatlab, basename = createOutputFilename(tracePath, 'memory_utilization_percent', '', 'txt')
|
||||
outputFiles = "{0}\n\t{1}\n\t{2}\n\t".format(outputFileNameGBPS, outputFileNamePercent, outputFileNameBWMatlab)
|
||||
|
||||
# windowSize/1000: picoseconds to nanoseconds conversion
|
||||
time = np.arange(0, (steps+1)*windowSize/1000, windowSize/1000)
|
||||
maxBandwidth = [maxDataRate/1024] * (steps+1)
|
||||
|
||||
f = open(outputFileNameBWMatlab, 'w')
|
||||
for i in range(steps):
|
||||
line = "{} {}\n".format(time[i], bandwidthPercentage[i])
|
||||
f.write(line)
|
||||
|
||||
# Plot Bandwidth in Percent
|
||||
BWPercentageFigure = plt.figure()
|
||||
BWPercentageFigurePlot = BWPercentageFigure.add_subplot(111)
|
||||
BWPercentageFigurePlot.set_xlabel('Time [ns]')
|
||||
BWPercentageFigurePlot.set_ylabel('Bandwidth [%]')
|
||||
BWPercentageFigurePlot.set_ylim(-1, maximumPercentage + (10 - maximumPercentage % 10))
|
||||
BWPercentageFigurePlot.set_title('Memory Utilization in % ' + str(basename))
|
||||
BWPercentageFigurePlot.grid(True)
|
||||
BWPercentageFigurePlot.plot(time, bandwidthPercentage, label='Total')
|
||||
BWPercentageFigurePlot.legend(loc="upper left")
|
||||
|
||||
# Plot absolute bandwidth
|
||||
BWFigure = plt.figure()
|
||||
BWFigurePlot = BWFigure.add_subplot(111)
|
||||
BWFigurePlot.set_xlabel('Time [ns]')
|
||||
BWFigurePlot.set_ylabel('Bandwidth [Gibit/s]')
|
||||
BWFigurePlot.set_title('Memory Utilization in Gbps ' + str(basename))
|
||||
BWFigurePlot.grid(True)
|
||||
BWFigurePlot.plot(time, bandwidth, label='Total')
|
||||
BWFigurePlot.legend(loc="upper left")
|
||||
|
||||
# plt.ylim((-0.01)*float(maxDataRate)/1024, ((maximumPercentage + (10 - maximumPercentage%10))/100)*float(maxDataRate)/1024)
|
||||
|
||||
threads = getThreads(connection)
|
||||
if (len(threads) > 1):
|
||||
for thread in threads:
|
||||
if thread == -1:
|
||||
continue
|
||||
|
||||
threadStr = "Thread " + str(thread)
|
||||
bandwidthPercentage, bandwidth, outputFileNameBWMatlab = memory_utilisation_window_thread(connection, tracePath, steps, thread)
|
||||
BWPercentageFigurePlot.plot(time, bandwidthPercentage, label=threadStr)
|
||||
BWPercentageFigurePlot.legend(loc="upper left")
|
||||
BWFigurePlot.plot(time, bandwidth, label=threadStr)
|
||||
BWFigurePlot.legend(loc="upper left")
|
||||
f = open(outputFileNameBWMatlab, 'w')
|
||||
for i in range(steps):
|
||||
line = "{} {}\n".format(time[i], bandwidthPercentage[i])
|
||||
f.write(line)
|
||||
outputFiles += "{0}\n\t".format(outputFileNameBWMatlab)
|
||||
|
||||
# Save to PDF files
|
||||
pdf = PdfPages(outputFileNamePercent)
|
||||
pdf.savefig(BWPercentageFigure)
|
||||
pdf.close()
|
||||
BWPercentageFigure.clear()
|
||||
pdf = PdfPages(outputFileNameGBPS)
|
||||
BWFigurePlot.plot(time, maxBandwidth)
|
||||
pdf.savefig(BWFigure)
|
||||
pdf.close()
|
||||
BWFigurePlot.clear()
|
||||
plt.close()
|
||||
|
||||
return outputFiles
|
||||
|
||||
@plot
|
||||
def response_latency_window(connection, tracePath, steps):
|
||||
windowSize = getWindowSize(connection)
|
||||
|
||||
cursor = connection.cursor()
|
||||
query = """
|
||||
SELECT
|
||||
avg(RESP.PHASEBEGIN - REQ.PHASEBEGIN) / 1000
|
||||
FROM
|
||||
PHASES REQ,
|
||||
PHASES RESP
|
||||
WHERE
|
||||
REQ.PHASENAME = 'REQ'
|
||||
AND RESP.PHASENAME = 'RESP'
|
||||
AND REQ.TRANSACT = RESP.TRANSACT
|
||||
AND RESP.PHASEBEGIN >= ? and RESP.PHASEEND <= ?
|
||||
"""
|
||||
outputFileName, basename = createOutputFilename(tracePath, 'response_latency', '', 'pdf')
|
||||
outputFile = "{0}\n\t".format(outputFileName)
|
||||
|
||||
LatencyFigure = plt.figure(figsize=(10, 5), dpi=300)
|
||||
LatencyFigurePlot = LatencyFigure.add_subplot(111)
|
||||
LatencyFigurePlot.set_xlabel('Time [ns]')
|
||||
LatencyFigurePlot.set_ylabel('Response Latency [ns]')
|
||||
LatencyFigurePlot.set_title('Average Response Latency: ' + str(basename))
|
||||
LatencyFigurePlot.grid(True)
|
||||
|
||||
time = [None] * steps
|
||||
latency = [None] * steps
|
||||
|
||||
for i in range(steps):
|
||||
cursor.execute(query, (i * windowSize, (i + 1) * windowSize))
|
||||
result = cursor.fetchone()[0]
|
||||
time[i] = ((i * windowSize) - (windowSize / 2)) / 1000 # ps to ns
|
||||
latency[i] = result
|
||||
|
||||
LatencyFigurePlot.plot(time, latency, linewidth=0.5, label="Latency")
|
||||
LatencyFigurePlot.legend(loc="upper left")
|
||||
|
||||
pdf = PdfPages(outputFileName)
|
||||
pdf.savefig(LatencyFigure)
|
||||
pdf.close()
|
||||
LatencyFigurePlot.clear()
|
||||
plt.close()
|
||||
|
||||
return outputFile
|
||||
|
||||
@plot
|
||||
def wr_response_latency_window(connection, tracePath, steps):
|
||||
windowSize = getWindowSize(connection)
|
||||
|
||||
cursor = connection.cursor()
|
||||
query = """
|
||||
SELECT
|
||||
avg(RESP.PHASEBEGIN - REQ.PHASEBEGIN) / 1000
|
||||
FROM
|
||||
PHASES REQ,
|
||||
PHASES RESP
|
||||
INNER JOIN
|
||||
Transactions
|
||||
ON REQ.TRANSACT = Transactions.ID
|
||||
WHERE
|
||||
REQ.PHASENAME = 'REQ'
|
||||
AND RESP.PHASENAME = 'RESP'
|
||||
AND REQ.TRANSACT = RESP.TRANSACT
|
||||
AND RESP.PHASEBEGIN >= ? and RESP.PHASEEND <= ?
|
||||
AND Transactions.Command = "W"
|
||||
"""
|
||||
outputFileName, basename = createOutputFilename(tracePath, 'wr_response_latency', '', 'pdf')
|
||||
outputFile = "{0}\n\t".format(outputFileName)
|
||||
|
||||
LatencyFigure = plt.figure(figsize=(10, 5), dpi=300)
|
||||
LatencyFigurePlot = LatencyFigure.add_subplot(111)
|
||||
LatencyFigurePlot.set_xlabel('Time [ns]')
|
||||
LatencyFigurePlot.set_ylabel('Response Latency [ns]')
|
||||
LatencyFigurePlot.set_title('Average Write Response Latency: ' + str(basename))
|
||||
LatencyFigurePlot.grid(True)
|
||||
|
||||
time = [None] * steps
|
||||
latency = [None] * steps
|
||||
|
||||
for i in range(steps):
|
||||
cursor.execute(query, (i * windowSize, (i + 1) * windowSize))
|
||||
result = cursor.fetchone()[0]
|
||||
time[i] = ((i * windowSize) - (windowSize / 2)) / 1000 # ps to ns
|
||||
latency[i] = result
|
||||
|
||||
LatencyFigurePlot.plot(time, latency, linewidth=0.5, label="Latency")
|
||||
LatencyFigurePlot.legend(loc="upper left")
|
||||
|
||||
pdf = PdfPages(outputFileName)
|
||||
pdf.savefig(LatencyFigure)
|
||||
pdf.close()
|
||||
LatencyFigurePlot.clear()
|
||||
plt.close()
|
||||
|
||||
return outputFile
|
||||
|
||||
@plot
|
||||
def rd_response_latency_window(connection, tracePath, steps):
|
||||
windowSize = getWindowSize(connection)
|
||||
|
||||
cursor = connection.cursor()
|
||||
query = """
|
||||
SELECT
|
||||
avg(RESP.PHASEBEGIN - REQ.PHASEBEGIN) / 1000
|
||||
FROM
|
||||
PHASES REQ,
|
||||
PHASES RESP
|
||||
INNER JOIN
|
||||
Transactions
|
||||
ON REQ.TRANSACT = Transactions.ID
|
||||
WHERE
|
||||
REQ.PHASENAME = 'REQ'
|
||||
AND RESP.PHASENAME = 'RESP'
|
||||
AND REQ.TRANSACT = RESP.TRANSACT
|
||||
AND RESP.PHASEBEGIN >= ? and RESP.PHASEEND <= ?
|
||||
AND Transactions.Command = "R"
|
||||
"""
|
||||
outputFileName, basename = createOutputFilename(tracePath, 'rd_response_latency', '', 'pdf')
|
||||
outputFile = "{0}\n\t".format(outputFileName)
|
||||
|
||||
LatencyFigure = plt.figure(figsize=(10, 5), dpi=300)
|
||||
LatencyFigurePlot = LatencyFigure.add_subplot(111)
|
||||
LatencyFigurePlot.set_xlabel('Time [ns]')
|
||||
LatencyFigurePlot.set_ylabel('Response Latency [ns]')
|
||||
LatencyFigurePlot.set_title('Average Read Response Latency: ' + str(basename))
|
||||
LatencyFigurePlot.grid(True)
|
||||
|
||||
time = [None] * steps
|
||||
latency = [None] * steps
|
||||
|
||||
for i in range(steps):
|
||||
cursor.execute(query, (i * windowSize, (i + 1) * windowSize))
|
||||
result = cursor.fetchone()[0]
|
||||
time[i] = ((i * windowSize) - (windowSize / 2)) / 1000 # ps to ns
|
||||
latency[i] = result
|
||||
|
||||
LatencyFigurePlot.plot(time, latency, linewidth=0.5, label="Latency")
|
||||
LatencyFigurePlot.legend(loc="upper left")
|
||||
|
||||
pdf = PdfPages(outputFileName)
|
||||
pdf.savefig(LatencyFigure)
|
||||
pdf.close()
|
||||
LatencyFigurePlot.clear()
|
||||
plt.close()
|
||||
|
||||
return outputFile
|
||||
|
||||
@plot
|
||||
def command_bus_utilisation_window(connection, tracePath, steps):
|
||||
windowSize = getWindowSize(connection)
|
||||
|
||||
cursor = connection.cursor()
|
||||
|
||||
# Query that sums all lengths that are completely contained in the window
|
||||
query_full = """
|
||||
SELECT
|
||||
SUM(CommandLengths.Length)
|
||||
FROM
|
||||
Phases,
|
||||
GeneralInfo
|
||||
INNER JOIN
|
||||
CommandLengths
|
||||
ON Phases.PhaseName = CommandLengths.Command
|
||||
WHERE
|
||||
Phases.PhaseBegin >= ?
|
||||
AND (Phases.PhaseBegin + (CommandLengths.Length * GeneralInfo.clk)) < ?
|
||||
"""
|
||||
|
||||
# Gets the PhaseBegin of the command that reaches out of the window
|
||||
# query_border = """
|
||||
# SELECT
|
||||
# Phases.PhaseBegin
|
||||
# FROM
|
||||
# Phases,
|
||||
# GeneralInfo
|
||||
# INNER JOIN
|
||||
# CommandLengths
|
||||
# ON Phases.PhaseName = CommandLengths.Command
|
||||
# WHERE
|
||||
# Phases.PhaseBegin >= ?
|
||||
# AND Phases.PhaseBegin < ?
|
||||
# AND (Phases.PhaseBegin + (CommandLengths.Length * GeneralInfo.clk)) >= ?
|
||||
# """
|
||||
|
||||
outputFileName, basename = createOutputFilename(tracePath, 'command_bus_utilisation', '', 'pdf')
|
||||
outputFile = "{0}\n\t".format(outputFileName)
|
||||
|
||||
LatencyFigure = plt.figure(figsize=(10, 5), dpi=300)
|
||||
LatencyFigurePlot = LatencyFigure.add_subplot(111)
|
||||
LatencyFigurePlot.set_xlabel('Time [ns]')
|
||||
LatencyFigurePlot.set_ylabel('Utilization [%]')
|
||||
LatencyFigurePlot.set_title('Command Bus Utilization: ' + str(basename))
|
||||
LatencyFigurePlot.grid(True)
|
||||
|
||||
clk, _ = getClock(connection)
|
||||
|
||||
time = [None] * steps
|
||||
utilization = [None] * steps
|
||||
|
||||
for i in range(steps):
|
||||
left_limit = i * windowSize
|
||||
right_limit = (i + 1) * windowSize
|
||||
|
||||
cursor.execute(query_full, (left_limit, right_limit))
|
||||
result = cursor.fetchone()[0]
|
||||
|
||||
if (result is None):
|
||||
result = 0
|
||||
|
||||
cmdBusOccupied = result * clk
|
||||
|
||||
time[i] = ((i * windowSize) - (windowSize / 2)) / 1000 # ps to ns
|
||||
utilization[i] = cmdBusOccupied / windowSize * 100
|
||||
|
||||
if (utilization[i] > 100):
|
||||
print(left_limit, right_limit)
|
||||
|
||||
LatencyFigurePlot.plot(time, utilization, linewidth=0.5, label="Utilization")
|
||||
LatencyFigurePlot.legend(loc="upper left")
|
||||
|
||||
pdf = PdfPages(outputFileName)
|
||||
pdf.savefig(LatencyFigure)
|
||||
pdf.close()
|
||||
LatencyFigurePlot.clear()
|
||||
plt.close()
|
||||
|
||||
return outputFile
|
||||
|
||||
@plot
|
||||
def queue_window(connection, tracePath, steps):
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("select max(BufferNumber) from BufferDepth;")
|
||||
bufferNumber = int(cursor.fetchone()[0]) + 1
|
||||
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("select MaxBufferDepth from GeneralInfo;")
|
||||
maxBufferDepth = int(cursor.fetchone()[0])
|
||||
|
||||
outputFile = ""
|
||||
outputFileName, basename = createOutputFilename(tracePath, 'queue', '', 'pdf')
|
||||
outputFile = "{0}\n\t".format(outputFileName)
|
||||
|
||||
QueueFigure = plt.figure(figsize=(10, 5), dpi=300)
|
||||
QueueFigurePlot = QueueFigure.add_subplot(111)
|
||||
QueueFigurePlot.set_xlabel('Time [s]')
|
||||
QueueFigurePlot.set_ylabel('Queue Utilization')
|
||||
QueueFigurePlot.set_title('Average Queue Utilization: ' + str(basename))
|
||||
QueueFigurePlot.grid(True)
|
||||
|
||||
|
||||
for b in range(bufferNumber):
|
||||
cursor.execute("select Time, AverageBufferDepth from BufferDepth where BufferNumber = {};".format(b))
|
||||
time = [None] * steps
|
||||
queue = [None] * steps
|
||||
for i in range(steps-1):
|
||||
result = cursor.fetchone()
|
||||
time[i] = result[0]
|
||||
queue[i] = result[1]
|
||||
|
||||
QueueFigurePlot.plot(time, queue, linewidth=0.5, label="Queue {}".format(b))
|
||||
|
||||
QueueFigurePlot.legend(loc="upper left")
|
||||
|
||||
x1,x2,y1,y2 = QueueFigurePlot.axis()
|
||||
QueueFigurePlot.axis((x1,x2,0,maxBufferDepth))
|
||||
|
||||
pdf = PdfPages(outputFileName)
|
||||
pdf.savefig(QueueFigure)
|
||||
pdf.close()
|
||||
QueueFigurePlot.clear()
|
||||
plt.close()
|
||||
|
||||
return outputFile
|
||||
|
||||
#@plot
|
||||
def power_window(connection, tracePath, steps):
|
||||
|
||||
windowSize = getWindowSize(connection)
|
||||
|
||||
outputFile = ""
|
||||
cursor = connection.cursor()
|
||||
|
||||
cursor.execute(" SELECT * FROM Power")
|
||||
|
||||
window = float(windowSize) / pow(10, 12)
|
||||
time = np.arange(0, (windowSize * (steps + 1)) / pow(10, 6), windowSize / pow(10, 6))
|
||||
power = np.full(len(time), 0)
|
||||
|
||||
for i in range(steps):
|
||||
sum = 0.0
|
||||
counter = 0
|
||||
result = cursor.fetchone()
|
||||
|
||||
while (result is not None):
|
||||
sum += float(result[1])
|
||||
counter = counter + 1
|
||||
if(result[0] > window*i):
|
||||
break
|
||||
result = cursor.fetchone()
|
||||
|
||||
if(counter == 0):
|
||||
break
|
||||
|
||||
sum = sum / counter
|
||||
power[i+1] = sum
|
||||
|
||||
outputFileName, basename = createOutputFilename(tracePath, 'power', '', 'pdf')
|
||||
outputFile = "{0}\n\t".format(outputFileName)
|
||||
|
||||
PowFigure = plt.figure(figsize=(10, 5), dpi=300)
|
||||
PowFigurePlot = PowFigure.add_subplot(111)
|
||||
PowFigurePlot.set_xlabel('Time [us]')
|
||||
PowFigurePlot.set_ylabel('Power [mW]')
|
||||
PowFigurePlot.set_title('Power Consumption ' + str(basename))
|
||||
PowFigurePlot.grid(True)
|
||||
PowFigurePlot.plot(time, power, linewidth=0.5)
|
||||
|
||||
pdf = PdfPages(outputFileName)
|
||||
pdf.savefig(PowFigure)
|
||||
pdf.close()
|
||||
PowFigurePlot.clear()
|
||||
plt.close()
|
||||
|
||||
return outputFile
|
||||
|
||||
def generatePlots(pathToTrace):
|
||||
connection = sqlite3.connect(pathToTrace)
|
||||
|
||||
print("================================")
|
||||
print("Generating plots for {0}".format(pathToTrace))
|
||||
|
||||
outputFiles = "Output files are:\n\t"
|
||||
cursor = connection.cursor()
|
||||
cursor.execute(" SELECT WindowSize FROM GeneralInfo")
|
||||
windowSize = float(cursor.fetchone()[0])
|
||||
if(windowSize == 0):
|
||||
outputFiles = "No output file created. Check WindowSize and EnableWindowing configs."
|
||||
else:
|
||||
traceEnd = getTraceEndTime(connection)
|
||||
steps = int(ceil(traceEnd/windowSize))
|
||||
for p in plots:
|
||||
outputFiles += p(connection, pathToTrace, steps)
|
||||
|
||||
connection.close()
|
||||
|
||||
print(outputFiles)
|
||||
|
||||
return outputFiles
|
||||
|
||||
|
||||
def main():
|
||||
path = sys.argv[1]
|
||||
if ((len(sys.argv)) > 2):
|
||||
latencyRange = (0, int(sys.argv[2])) # Optional argument to use a different range
|
||||
if ((len(sys.argv)) > 3):
|
||||
numberOfBins = int(sys.argv[3]) # Optional argument to use a different number of bins
|
||||
generatePlots(path)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
790
python/src/dramsys/analysis/tests.py
Normal file
790
python/src/dramsys/analysis/tests.py
Normal file
@@ -0,0 +1,790 @@
|
||||
import sys
|
||||
import traceback
|
||||
import sqlite3
|
||||
import os
|
||||
import json
|
||||
from dramsys.common.memUtil import *
|
||||
|
||||
|
||||
class DramConfig(object):
|
||||
memoryType = ""
|
||||
scheduler = ""
|
||||
bankwiseLogic = 0
|
||||
RefMode = 1
|
||||
clk = 0
|
||||
unitOfTime = ""
|
||||
dataRate = 0
|
||||
nActivateWindow = numberOfBanks = 0
|
||||
|
||||
clk = 0
|
||||
tRP = 0 # precharge-time (pre -> act same bank)
|
||||
tRAS = 0 # active-time (act -> pre same bank)
|
||||
tRC = 0 # RAS-cycle-time (min time bw 2 succesive ACT to same bank)
|
||||
tCCD_S = 0 # TODO: relevant? max(bl, tCCD)
|
||||
tCCD_L = 0
|
||||
tRTP = 0 # Read to precharge
|
||||
tRRD_S = 0 # min time between 2 succesive ACT to different banks (different bank group)
|
||||
tRRD_L = 0 # min time between 2 succesive ACT to different banks (same bank group)
|
||||
tRCD = 0 # act -> read/write
|
||||
tNAW = 0 # n activate window
|
||||
tRL = 0 # read latency (read command start to data strobe)
|
||||
tWL = 0 # write latency
|
||||
tWR = 0 # write recovery (write to precharge)
|
||||
tWTR_S = 0 # write to read (different bank group)
|
||||
tWTR_L = 0 # write to read (same bank group)
|
||||
tCKESR = 0 # min time in sref
|
||||
tCKE = 0 # min time in pdna or pdnp
|
||||
tXP = 0 # min delay to row access command after pdnpx pdnax
|
||||
tXPDLL = 0 # min delay to row access command after pdnpx pdnax for dll commands
|
||||
tXS = 0 # min delay to row access command after srefx
|
||||
tXSDLL = 0 # min delay to row access command after srefx for dll commands
|
||||
tAL = 0 # additive delay (delayed execution in dram)
|
||||
tRFC = 0 # min ref->act delay
|
||||
tREFI = 0 # time between REF commands
|
||||
|
||||
def readConfigFromFiles(self, connection):
|
||||
print("Parsing dram configuration")
|
||||
|
||||
mcconfig = MCConfig(connection)
|
||||
memspec = MemSpec(connection)
|
||||
|
||||
clkWithUnit = getClock(connection)
|
||||
self.clk = clkWithUnit[0]
|
||||
self.unitOfTime = clkWithUnit[1].lower()
|
||||
|
||||
self.bankwiseLogic = 0
|
||||
self.RefMode = 0
|
||||
self.scheduler = mcconfig.getValue("Scheduler")
|
||||
|
||||
self.numberOfBanks = memspec.getIntValue("memarchitecturespec","nbrOfBanks")
|
||||
self.burstLength = memspec.getIntValue("memarchitecturespec","burstLength")
|
||||
self.memoryType = memspec.getValue("memoryType")
|
||||
self.dataRate = memspec.getIntValue("memarchitecturespec","dataRate")
|
||||
|
||||
if (self.memoryType == "WIDEIO_SDR"):
|
||||
self.nActivateWindow = 2
|
||||
self.tRP = self.clk * memspec.getIntValue("memtimingspec","RP")
|
||||
self.tRAS = self.clk * memspec.getIntValue("memtimingspec","RAS")
|
||||
self.tRC = self.clk * memspec.getIntValue("memtimingspec","RC")
|
||||
self.tRRD_S = self.clk * memspec.getIntValue("memtimingspec","RRD")
|
||||
self.tRRD_L = self.tRRD_S
|
||||
self.tCCD_S = self.clk * memspec.getIntValue("memtimingspec","CCD")
|
||||
self.tCCD_L = self.tCCD_S
|
||||
self.tRCD = self.clk * memspec.getIntValue("memtimingspec","RCD")
|
||||
self.tNAW = self.clk * memspec.getIntValue("memtimingspec","TAW")
|
||||
self.tRL = self.clk * memspec.getIntValue("memtimingspec","RL")
|
||||
self.tWL = self.clk * memspec.getIntValue("memtimingspec","WL")
|
||||
self.tWR = self.clk * memspec.getIntValue("memtimingspec","WR")
|
||||
self.tWTR_S = self.clk * memspec.getIntValue("memtimingspec","WTR")
|
||||
self.tWTR_L = self.tWTR_S
|
||||
self.tRTP = self.clk * memspec.getIntValue("memtimingspec","RTP")
|
||||
self.tCKESR = self.clk * memspec.getIntValue("memtimingspec","CKESR")
|
||||
self.tCKE = self.clk * memspec.getIntValue("memtimingspec","CKE")
|
||||
self.tXP = self.clk * memspec.getIntValue("memtimingspec","XP")
|
||||
self.tXPDLL = self.tXP
|
||||
self.tXS = self.clk * memspec.getIntValue("memtimingspec","XS")
|
||||
self.tXSDLL = self.tXS
|
||||
self.tAL = self.clk * memspec.getIntValue("memtimingspec","AL")
|
||||
self.tRFC = self.clk * memspec.getIntValue("memtimingspec","RFC")
|
||||
self.tREFI = self.clk * memspec.getIntValue("memtimingspec","REFI")
|
||||
|
||||
elif (self. memoryType == "DDR4"):
|
||||
self.nActivateWindow = 4
|
||||
self.tRP = self.clk * memspec.getIntValue("memtimingspec","RP")
|
||||
self.tRAS = self.clk * memspec.getIntValue("memtimingspec","RAS")
|
||||
self.tRC = self.clk * memspec.getIntValue("memtimingspec","RC")
|
||||
self.tRTP = self.clk * memspec.getIntValue("memtimingspec","RTP")
|
||||
self.tRRD_S = self.clk * memspec.getIntValue("memtimingspec","RRD_S")
|
||||
self.tRRD_L = self.clk * memspec.getIntValue("memtimingspec","RRD_L")
|
||||
self.tCCD_S = self.clk * memspec.getIntValue("memtimingspec","CCD_S")
|
||||
self.tCCD_L = self.clk * memspec.getIntValue("memtimingspec","CCD_L")
|
||||
self.tRCD = self.clk * memspec.getIntValue("memtimingspec","RCD")
|
||||
self.tNAW = self.clk * memspec.getIntValue("memtimingspec","FAW")
|
||||
self.tRL = self.clk * memspec.getIntValue("memtimingspec","RL")
|
||||
self.tWL = self.clk * memspec.getIntValue("memtimingspec","WL")
|
||||
self.tWR = self.clk * memspec.getIntValue("memtimingspec","WR")
|
||||
self.tWTR_S = self.clk * memspec.getIntValue("memtimingspec","WTR_S")
|
||||
self.tWTR_L = self.clk * memspec.getIntValue("memtimingspec","WTR_L")
|
||||
self.tCKESR = self.clk * memspec.getIntValue("memtimingspec","CKESR")
|
||||
self.tCKE = self.clk * memspec.getIntValue("memtimingspec","CKE")
|
||||
self.tXP = self.clk * memspec.getIntValue("memtimingspec","XP")
|
||||
self.tXPDLL = self.clk * memspec.getIntValue("memtimingspec","XPDLL")
|
||||
self.tXS = self.clk * memspec.getIntValue("memtimingspec","XS")
|
||||
self.tXSDLL = self.clk * memspec.getIntValue("memtimingspec","XSDLL")
|
||||
self.tAL = self.clk * memspec.getIntValue("memtimingspec","AL")
|
||||
if (self.RefMode == "4"):
|
||||
self.tRFC = self.clk * memspec.getIntValue("memtimingspec","RFC4")
|
||||
self.tREFI = self.clk * (memspec.getIntValue("memtimingspec","REFI") / 4)
|
||||
elif (self.RefMode == "2"):
|
||||
self.tRFC = self.clk * memspec.getIntValue("memtimingspec","RFC2")
|
||||
self.tREFI = self.clk * (memspec.getIntValue("memtimingspec","REFI") / 2)
|
||||
else:
|
||||
self.tRFC = self.clk * memspec.getIntValue("memtimingspec","RFC")
|
||||
self.tREFI = self.clk * memspec.getIntValue("memtimingspec","REFI")
|
||||
|
||||
elif (self. memoryType == "DDR3"):
|
||||
self.nActivateWindow = 4
|
||||
self.tRP = self.clk * memspec.getIntValue("memtimingspec","RP")
|
||||
self.tRAS = self.clk * memspec.getIntValue("memtimingspec","RAS")
|
||||
self.tRC = self.clk * memspec.getIntValue("memtimingspec","RC")
|
||||
self.tRTP = self.clk * memspec.getIntValue("memtimingspec","RTP")
|
||||
self.tRRD_S = self.clk * memspec.getIntValue("memtimingspec","RRD")
|
||||
self.tRRD_L = self.clk * memspec.getIntValue("memtimingspec","RRD")
|
||||
self.tCCD_S = self.clk * memspec.getIntValue("memtimingspec","CCD")
|
||||
self.tCCD_L = self.clk * memspec.getIntValue("memtimingspec","CCD")
|
||||
self.tRCD = self.clk * memspec.getIntValue("memtimingspec","RCD")
|
||||
self.tNAW = self.clk * memspec.getIntValue("memtimingspec","FAW")
|
||||
self.tRL = self.clk * memspec.getIntValue("memtimingspec","RL")
|
||||
self.tWL = self.clk * memspec.getIntValue("memtimingspec","WL")
|
||||
self.tWR = self.clk * memspec.getIntValue("memtimingspec","WR")
|
||||
self.tWTR_S = self.clk * memspec.getIntValue("memtimingspec","WTR")
|
||||
self.tWTR_L = self.clk * memspec.getIntValue("memtimingspec","WTR")
|
||||
self.tCKESR = self.clk * memspec.getIntValue("memtimingspec","CKESR")
|
||||
self.tCKE = self.clk * memspec.getIntValue("memtimingspec","CKE")
|
||||
self.tXP = self.clk * memspec.getIntValue("memtimingspec","XP")
|
||||
self.tXPDLL = self.clk * memspec.getIntValue("memtimingspec","XPDLL")
|
||||
self.tXS = self.clk * memspec.getIntValue("memtimingspec","XS")
|
||||
self.tXSDLL = self.clk * memspec.getIntValue("memtimingspec","XSDLL")
|
||||
self.tAL = self.clk * memspec.getIntValue("memtimingspec","AL")
|
||||
self.tRFC = self.clk * memspec.getIntValue("memtimingspec","RFC")
|
||||
self.tREFI = self.clk * memspec.getIntValue("memtimingspec","REFI")
|
||||
|
||||
else:
|
||||
raise Exception("MemoryType not supported yet. Insert a coin into the coin machine and try again")
|
||||
|
||||
def clkAlign(self, value):
|
||||
return math.ceil(1.0*value/self.clk)*self.clk
|
||||
|
||||
def getWriteAccessTime(self):
|
||||
if (self.dataRate == 1):
|
||||
return self.clk*(self.burstLength - 1)
|
||||
elif (self.memoryType == "DDR4"):
|
||||
return self.clk*self.burstLength/self.dataRate
|
||||
else: # DEFAULT
|
||||
return self.clk*self.burstLength/self.dataRate
|
||||
|
||||
def getReadAccessTime(self):
|
||||
return self.burstLength/self.dataRate * dramconfig.clk
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
|
||||
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))
|
||||
|
||||
# ----------- checks ---------------------------------------
|
||||
|
||||
|
||||
@test
|
||||
def commands_are_clockaligned(connection):
|
||||
"""Checks that all commands on the command bus are aligned to the system clock"""
|
||||
|
||||
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": dramconfig.clk})
|
||||
|
||||
result = cursor.fetchone()
|
||||
|
||||
if (result is not None):
|
||||
return TestFailed("Command with PhaseID {0} starts at {1} and ends at. One of those times. 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.bankwiseLogic == "1"):
|
||||
excludedPhases = "('REQ','RESP','PREAB')"
|
||||
else:
|
||||
excludedPhases = "('REQ','RESP','PREAB','PDNA','PDNP','SREF','REFAB')"
|
||||
|
||||
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 is not None):
|
||||
return TestFailed("Slot on commandbus at time {0} is used multiple times".format(formatTime(result[0])))
|
||||
return TestSuceeded()
|
||||
|
||||
|
||||
@test
|
||||
def phase_transitions_are_valid(connection):
|
||||
"""Checks that all transition of two consequtive phases on the same bank are valid"""
|
||||
|
||||
cursor = connection.cursor()
|
||||
validTransitions = {}
|
||||
|
||||
# validTransitions tells you which phases are allowed to follow the last transaction.
|
||||
|
||||
if (dramconfig.bankwiseLogic == "1"):
|
||||
validTransitions['PREPB'] = set(['ACT', 'REFPB', 'SREFB'])
|
||||
validTransitions['ACT'] = set(['RD', 'RDA', 'WR', 'WRA', 'PREPB', 'PREAB'])
|
||||
|
||||
validTransitions['RD'] = set(['PREPB', 'RD', 'RDA', 'WR', 'WRA', 'PDNAB'])
|
||||
validTransitions['WR'] = set(['PREPB', 'RD', 'RDA', 'WR', 'WRA', 'PDNAB'])
|
||||
validTransitions['RDA'] = set(['ACT', 'REFPB', 'PDNPB'])
|
||||
validTransitions['WRA'] = set(['ACT', 'REFPB', 'PDNPB'])
|
||||
|
||||
validTransitions['REFPB'] = set(['ACT', 'REFPB', 'PDNPB', 'SREFB'])
|
||||
|
||||
validTransitions['PDNAB'] = set(['PREPB', 'RD', 'RDA', 'WR', 'WRA', 'REFPB'])
|
||||
validTransitions['PDNPB'] = set(['ACT', 'REFPB', 'SREFB'])
|
||||
validTransitions['SREFB'] = set(['ACT', 'REFPB'])
|
||||
else:
|
||||
validTransitions['PREPB'] = set(['ACT', 'PREAB', 'REFAB'])
|
||||
validTransitions['PREAB'] = set(['REFAB', 'SREF'])
|
||||
validTransitions['ACT'] = set(['RD', 'RDA', 'WR', 'WRA', 'PREAB'])
|
||||
|
||||
validTransitions['RD'] = set(['PREPB', 'PREAB', 'RD', 'RDA', 'WR', 'WRA', 'PDNA'])
|
||||
validTransitions['WR'] = set(['PREPB', 'PREAB', 'RD', 'RDA', 'WR', 'WRA', 'PDNA'])
|
||||
validTransitions['RDA'] = set(['PREAB', 'ACT', 'REFAB', 'PDNA', 'PDNP'])
|
||||
validTransitions['WRA'] = set(['PREAB', 'ACT', 'REFAB', 'PDNA', 'PDNP'])
|
||||
|
||||
validTransitions['REFAB'] = set(['PREAB', 'ACT', 'REFAB', 'PDNA', 'PDNP', 'SREF'])
|
||||
|
||||
validTransitions['PDNA'] = set(['PREPB', 'PREAB', 'ACT', 'RD', 'RDA', 'WR', 'WRA', 'REFAB', 'PDNA', 'PDNP'])
|
||||
validTransitions['PDNP'] = set(['PREAB', 'ACT', 'REFAB', 'PDNA', 'PDNP', 'SREF'])
|
||||
validTransitions['SREF'] = set(['PREAB', 'ACT', 'REFAB', 'PDNA', 'PDNP'])
|
||||
|
||||
if (dramconfig.bankwiseLogic == "1"):
|
||||
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"""
|
||||
else:
|
||||
# REFAB, PREAB, PDNA, PDNP and SREF are attributed to Bank 0 therefore this must be added to the order evaluation:
|
||||
query = """SELECT
|
||||
PhaseName, phases.ID
|
||||
FROM
|
||||
phases INNER JOIN transactions ON phases.transact=transactions.ID
|
||||
WHERE
|
||||
((TBank=:bank) OR PhaseName IN ('PREAB', 'SREF', 'PDNP', 'PDNA', 'REFAB'))
|
||||
AND PhaseName NOT IN ('REQ','RESP') ORDER BY PhaseBegin"""
|
||||
|
||||
for bankNumber in range(dramconfig.numberOfBanks):
|
||||
cursor.execute(query, {"bank": bankNumber})
|
||||
lastRow = cursor.fetchone()
|
||||
for currentRow in cursor:
|
||||
currentPhase = currentRow[0]
|
||||
lastPhase = lastRow[0]
|
||||
|
||||
if (currentPhase not in validTransitions[lastPhase]):
|
||||
return TestFailed("Phase {0}({1}) is not allowed to follow phase {2}({3})".format(currentRow[1], currentPhase, lastRow[1], lastPhase))
|
||||
lastRow = currentRow
|
||||
|
||||
return TestSuceeded()
|
||||
|
||||
|
||||
def timing_constraint(FirstPhase, SecondPhase):
|
||||
FirstPhaseName = FirstPhase[0]
|
||||
SecondPhaseName = SecondPhase[0]
|
||||
|
||||
if ((FirstPhaseName == "PREPB" or FirstPhaseName == "PREAB") and SecondPhaseName != "PREAB"):
|
||||
return dramconfig.tRP
|
||||
|
||||
elif (FirstPhaseName == "ACT"):
|
||||
return dramconfig.tRCD
|
||||
|
||||
elif (FirstPhaseName == "RD"):
|
||||
if (SecondPhaseName in ["PREPB, PREAB"]):
|
||||
return dramconfig.tRTP
|
||||
elif (SecondPhaseName in ["RD, RDA"]):
|
||||
return max(dramconfig.tCCD_L, dramconfig.getReadAccessTime())
|
||||
elif (SecondPhase in ["WR", "WRA"]):
|
||||
return dramconfig.tRL + dramconfig.getReadAccessTime() - dramconfig.tWL + 2 * dramconfig.clk
|
||||
elif (SecondPhase == "PDNA"):
|
||||
return dramconfig.tRL + dramconfig.getReadAccessTime() + dramconfig.clk
|
||||
|
||||
elif (FirstPhaseName == "WR"):
|
||||
if (SecondPhaseName in ["PREPB, PREAB", "PDNA"]):
|
||||
return dramconfig.tWL + dramconfig.getWriteAccessTime() + dramconfig.tWR
|
||||
elif (SecondPhaseName in ["RD, RDA"]):
|
||||
return dramconfig.tWL + dramconfig.getWriteAccessTime() + dramconfig.tWTR_L
|
||||
elif (SecondPhaseName in ["WR, WRA"]):
|
||||
return max(dramconfig.tCCD_L, burstlength/dramconfig.dataRate)
|
||||
|
||||
elif (FirstPhaseName == "RDA"):
|
||||
if (SecondPhaseName in ["ACT", "REFAB"]):
|
||||
return dramconfig.tRTP + dramconfig.tRP
|
||||
elif (SecondPhaseName == "PREAB"):
|
||||
return dramconfig.tRTP
|
||||
elif (SecondPhaseName in ["PDNA", "PDNP"]):
|
||||
return dramconfig.tRL + dramconfig.getReadAccessTime() + dramconfig.clk
|
||||
|
||||
elif (FirstPhaseName == "WRA"):
|
||||
if (SecondPhaseName in ["ACT", "REFAB"]):
|
||||
return dramconfig.tWL + dramconfig.getWriteAccessTime() + dramconfig.tWR + dramconfig.tRP
|
||||
elif (SecondPhaseName == "PREAB"):
|
||||
return dramconfig.tWL + dramconfig.getWriteAccessTime() + dramconfig.tWR
|
||||
elif (SecondPhaseName in ["PDNA", "PDNP"]):
|
||||
return dramconfig.tWL + dramconfig.getWriteAccessTime() + dramconfig.tWR + dramconfig.clk
|
||||
|
||||
elif (FirstPhaseName == "REFAB"):
|
||||
return dramconfig.tRFC
|
||||
|
||||
elif (FirstPhaseName in ["PDNA", "PDNP"]):
|
||||
# print("{0}".format(FirstPhaseName))
|
||||
# print("{0}".format(formatTime(FirstPhase[3])))
|
||||
# print("{0}".format(formatTime(FirstPhase[2])))
|
||||
# print("{0}".format(formatTime(dramconfig.tXP)))
|
||||
# print("{0}".format(formatTime(dramconfig.clk)))
|
||||
return (FirstPhase[3] - FirstPhase[2]) + dramconfig.tXP - dramconfig.clk
|
||||
|
||||
elif (FirstPhaseName == "SREF"):
|
||||
return (FirstPhase[3] - FirstPhase[2]) + dramconfig.tXS - dramconfig.clk
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
@test
|
||||
def timing_constraits_on_the_same_bank_hold(connection):
|
||||
"""Checks that all transitions of two consecutive phases on the same bank meet their timing constraints"""
|
||||
cursor = connection.cursor()
|
||||
validTransitions = {}
|
||||
|
||||
if (dramconfig.bankwiseLogic == "1"):
|
||||
query = """SELECT PhaseName, phases.ID, PhaseBegin, PhaseEnd FROM phases INNER JOIN transactions ON phases.transact=transactions.ID WHERE TBank=:bank
|
||||
AND PhaseName NOT IN ('REQ','RESP') ORDER BY PhaseBegin"""
|
||||
else:
|
||||
query = """SELECT PhaseName, phases.ID, PhaseBegin, PhaseEnd FROM phases INNER JOIN transactions ON phases.transact=transactions.ID
|
||||
WHERE ((TBank=:bank) OR PhaseName IN ('PREAB', 'SREF', 'PDNP', 'PDNA', 'REFAB')) AND PhaseName NOT IN ('REQ','RESP') ORDER BY PhaseBegin"""
|
||||
for bankNumber in range(dramconfig.numberOfBanks):
|
||||
cursor.execute(query, {"bank": bankNumber})
|
||||
lastRow = cursor.fetchone()
|
||||
|
||||
for currentRow in cursor:
|
||||
constraint = timing_constraint(lastRow, currentRow)
|
||||
if (currentRow[2] - (lastRow[2] + constraint) < 0):
|
||||
return TestFailed("Phase {0}({1}) starts {2} after Start of Phase {3}({4}). Minimal time is {5}".format(currentRow[1], currentRow[0], formatTime(currentRow[2] - lastRow[2]), lastRow[1], lastRow[0], formatTime(constraint)))
|
||||
lastRow = currentRow
|
||||
|
||||
return TestSuceeded()
|
||||
|
||||
|
||||
@test
|
||||
def row_buffer_is_used_correctly(connection):
|
||||
"""Checks that each bank's row buffer is used correctly"""
|
||||
|
||||
cursor = connection.cursor()
|
||||
|
||||
if (dramconfig.bankwiseLogic == "1"):
|
||||
query = """SELECT
|
||||
PhaseName, phases.ID
|
||||
FROM
|
||||
phases INNER JOIN transactions ON phases.transact=transactions.ID
|
||||
WHERE
|
||||
((TBank=:bank) OR (PhaseNAME = "REFAB" AND TBank=0) OR (PhaseNAME = "PREAB" AND TBank=0))
|
||||
AND PhaseName NOT IN ('REQ','RESP') ORDER BY PhaseBegin"""
|
||||
else:
|
||||
# REFAB, PREAB, PDNA, PDNP and SREF are stored to bank0 for all the other banks we have also to grep this command:
|
||||
# PhaseName IN ('PREAB', 'SREF', 'PDNP', 'PDNA', 'REFAB')
|
||||
query = """SELECT
|
||||
PhaseName, phases.ID
|
||||
FROM
|
||||
phases INNER JOIN transactions ON phases.transact=transactions.ID
|
||||
WHERE
|
||||
((TBank=:bank) OR PhaseName IN ('PREAB', 'SREF', 'PDNP', 'PDNA', 'REFAB'))
|
||||
AND PhaseName NOT IN ('REQ','RESP') ORDER BY PhaseBegin"""
|
||||
|
||||
# phases that precharge the bank and close the rowbuffer
|
||||
prechargingPhases = set(['PREPB', 'PREAB', 'RDA', 'WRA'])
|
||||
|
||||
# phases that require the bank to be in active state and the rowbuffer to be opened
|
||||
accessingPhases = set(['RD', 'RDA', 'WR', 'WRA', 'PREPB'])
|
||||
|
||||
# phases that require the bank to be in precharged state and the robuffer to be closed
|
||||
idlePhases = set(['ACT', 'PDNP', 'REFAB', 'SREF'])
|
||||
|
||||
for bankNumber in range(dramconfig.numberOfBanks):
|
||||
cursor.execute(query, {"bank": bankNumber})
|
||||
|
||||
rowBufferIsClosed = True
|
||||
|
||||
for currentRow in cursor:
|
||||
if ((currentRow[0] in accessingPhases) and (rowBufferIsClosed is True)):
|
||||
return TestFailed("Phase {0}({1}) acesses a closed rowbuffer".format(currentRow[1], currentRow[0]))
|
||||
|
||||
if ((currentRow[0] in idlePhases) and (rowBufferIsClosed is False)):
|
||||
return TestFailed("Phase {0}({1}) needs a closed rowbuffer".format(currentRow[1], currentRow[0]))
|
||||
|
||||
if (currentRow[0] == 'ACT'):
|
||||
rowBufferIsClosed = False
|
||||
|
||||
if (currentRow[0] in prechargingPhases):
|
||||
rowBufferIsClosed = True
|
||||
|
||||
return TestSuceeded()
|
||||
|
||||
|
||||
@test
|
||||
def no_commands_during_refresh(connection):
|
||||
"""Checks that no command was scheduled during refresh period"""
|
||||
cursor = connection.cursor()
|
||||
if (dramconfig.bankwiseLogic == "1"):
|
||||
query = """SELECT PhaseBegin, PhaseEnd, TBank FROM phases INNER JOIN transactions ON phases.transact=transactions.ID WHERE PhaseName = 'REFPB' """
|
||||
test_query = """SELECT PhaseName FROM phases INNER JOIN transactions ON phases.transact=transactions.ID WHERE ((PhaseBegin >= ? and PhaseEnd <= ?) or (PhaseBegin <= ? and PhaseEnd > ?) or (PhaseBegin < ? and PhaseEnd >= ?)) and PhaseName NOT IN ('REQ','RESP','REFPB') and TBank = ?"""
|
||||
else:
|
||||
query = """SELECT PhaseBegin, PhaseEnd FROM phases WHERE PhaseName = 'REFAB' """
|
||||
test_query = """SELECT PhaseName FROM phases WHERE ((PhaseBegin >= ? and PhaseEnd <= ?) or (PhaseBegin <= ? and PhaseEnd > ?) or (PhaseBegin < ? and PhaseEnd >= ?)) and PhaseName NOT IN ('REQ','RESP','REFAB')"""
|
||||
|
||||
cursor.execute(query)
|
||||
result = cursor.fetchall()
|
||||
for row in result:
|
||||
if(dramconfig.bankwiseLogic == "1"):
|
||||
cursor.execute(test_query, (row[0], row[1], row[0], row[0], row[1], row[1], row[2]))
|
||||
else:
|
||||
cursor.execute(test_query, (row[0], row[1], row[0], row[0], row[1], row[1]))
|
||||
test = cursor.fetchone()
|
||||
if(test is not None):
|
||||
return TestFailed("A Command {0} was scheduled during a refresh period".format(test[0]))
|
||||
|
||||
return TestSuceeded()
|
||||
|
||||
|
||||
@test
|
||||
def max_number_ref_burst(connection):
|
||||
"""Checks that the maximum number of REFAB commands in a burst is not exceeded"""
|
||||
cursor = connection.cursor()
|
||||
query = """SELECT PhaseBegin, PhaseEnd FROM phases WHERE PhaseName = 'REFAB' """
|
||||
prevrow = [0] * 2
|
||||
cnt = 0
|
||||
flexibleRef = getFlexibleRef(connection)
|
||||
maxRefBurst = getMaxRefBurst(connection)
|
||||
|
||||
cursor.execute(query)
|
||||
result = cursor.fetchall()
|
||||
|
||||
if (flexibleRef):
|
||||
maxRefBurst = maxRefBurst - 1 # Since the intersections will be used for this test, use -1 from the max
|
||||
|
||||
for row in result:
|
||||
if (prevrow[1] == row[0]):
|
||||
cnt += 1
|
||||
else:
|
||||
cnt = 0 # Reset the counter every time a burst ends
|
||||
prevrow = row
|
||||
if(cnt > maxRefBurst):
|
||||
return TestFailed("Maximum number of REFAB in a burst was exceeded at {0} with {1} REFAB in sequence. Maximum allowed is {2}.".format(formatTime(row[0]), cnt, maxRefBurst))
|
||||
|
||||
return TestSuceeded()
|
||||
|
||||
|
||||
@test
|
||||
@test
|
||||
def max_time_without_ref(connection):
|
||||
"""Checks that the maximum time allowed between REFAB/SREF commands is not exceeded"""
|
||||
cursor = connection.cursor()
|
||||
query = """SELECT PhaseBegin, PhaseEnd FROM phases WHERE PhaseName = 'REFAB' OR PhaseName = 'SREF' """
|
||||
prevrow = [0] * 2
|
||||
flexibleRef = getFlexibleRef(connection)
|
||||
maxRefBurst = getMaxRefBurst(connection)
|
||||
|
||||
cursor.execute(query)
|
||||
result = cursor.fetchall()
|
||||
|
||||
if (flexibleRef):
|
||||
maxTimeWithoutRef = ((maxRefBurst + 1) * dramconfig.tREFI) + dramconfig.tRP # Bursts are possible, so max should be the possible burst size + 1
|
||||
else:
|
||||
maxTimeWithoutRef = dramconfig.tREFI + dramconfig.tRP
|
||||
|
||||
tolerance = 0.05
|
||||
|
||||
maxTimeWithoutRef = maxTimeWithoutRef + dramconfig.tREFI*tolerance
|
||||
|
||||
for row in result:
|
||||
timeBetweenRefs = row[0] - prevrow[1]
|
||||
if (timeBetweenRefs > maxTimeWithoutRef):
|
||||
return TestFailed("Maximum time between REF commands was exceeded at {0} with {1} between REFs. Maximum allowed is {2}.".format(formatTime(row[0]), formatTime(timeBetweenRefs), formatTime(maxTimeWithoutRef)))
|
||||
prevrow = row
|
||||
|
||||
return TestSuceeded()
|
||||
|
||||
|
||||
# ----------- activate checks ---------------------------------------
|
||||
@test
|
||||
def activate_to_activate_holds(connection):
|
||||
"""Checks that all activates are far enough apart(JESD229 229, P. 27)"""
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("SELECT phases.ID,PhaseBegin,TBankGroup FROM Phases INNER JOIN transactions ON phases.transact=transactions.ID WHERE PhaseName = 'ACT' ORDER BY PhaseBegin")
|
||||
lastRow = cursor.fetchone()
|
||||
|
||||
for currentRow in cursor:
|
||||
timeBetweenActivates = currentRow[1] - lastRow[1]
|
||||
if (currentRow[2] == lastRow[2]):
|
||||
minTime = dramconfig.tRRD_L
|
||||
else:
|
||||
minTime = dramconfig.tRRD_S
|
||||
if (timeBetweenActivates < minTime):
|
||||
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), formatTime(minTime)))
|
||||
|
||||
lastRow = currentRow
|
||||
|
||||
return TestSuceeded()
|
||||
|
||||
|
||||
@test
|
||||
def activate_to_activate_on_same_bank_holds(connection):
|
||||
"""Checks that all activates on the same bank are far enough apart (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_holds(connection):
|
||||
"""Checks that the n-Activate constraint is met everywhere(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.tNAW):
|
||||
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.tNAW)))
|
||||
return TestSuceeded()
|
||||
|
||||
|
||||
# ----------- read/write checks ---------------------------------------
|
||||
@test
|
||||
def read_to_read_holds(connection):
|
||||
"""Check that the read operations do not intefere with each other on the data bus"""
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("SELECT phases.ID,PhaseBegin,TBankGroup FROM Phases INNER JOIN transactions ON phases.transact=transactions.ID WHERE PhaseName IN ('RD','RDA') ORDER BY PhaseBegin")
|
||||
lastRow = cursor.fetchone()
|
||||
|
||||
for currentRow in cursor:
|
||||
timeBetweenReads = currentRow[1] - lastRow[1]
|
||||
if (currentRow[2] == lastRow[2]):
|
||||
minTime = max(dramconfig.tCCD_L, dramconfig.getReadAccessTime())
|
||||
else:
|
||||
minTime = max(dramconfig.tCCD_S, dramconfig.getReadAccessTime())
|
||||
if (timeBetweenReads < minTime):
|
||||
return TestFailed("Reads with PhaseIDs {0} and {1} are {2} apart. Minimum time between two reads is {3}".format(currentRow[0], lastRow[0], formatTime(timeBetweenReads), minTime))
|
||||
lastRow = currentRow
|
||||
|
||||
return TestSuceeded()
|
||||
|
||||
|
||||
@test
|
||||
def write_to_write_holds(connection):
|
||||
"""Check that the write operations do not intefere with each other on the data bus"""
|
||||
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("SELECT phases.ID,PhaseBegin,TBankGroup FROM Phases INNER JOIN transactions ON phases.transact=transactions.ID WHERE PhaseName IN ('WR','WRA') ORDER BY PhaseBegin")
|
||||
lastRow = cursor.fetchone()
|
||||
|
||||
for currentRow in cursor:
|
||||
timeBetweenWrites = currentRow[1] - lastRow[1]
|
||||
if (currentRow[2] == lastRow[2]):
|
||||
minTime = max(dramconfig.tCCD_L, dramconfig.getWriteAccessTime())
|
||||
else:
|
||||
minTime = max(dramconfig.tCCD_S, dramconfig.getWriteAccessTime())
|
||||
if (timeBetweenWrites < minTime):
|
||||
return TestFailed("Writes with PhaseIDs {0} and {1} are {2} apart. Minimum time between two writes is {3}".format(currentRow[0], lastRow[0], formatTime(timeBetweenWrites), minTime))
|
||||
lastRow = currentRow
|
||||
return TestSuceeded()
|
||||
|
||||
|
||||
@test
|
||||
def write_to_read_and_read_to_write_hold(connection):
|
||||
"""Checks that read and write operation do not interfere with each other on the data bus
|
||||
and that the write-to-read constraint is met"""
|
||||
|
||||
cursor = connection.cursor()
|
||||
query = """SELECT Phases.ID,PhaseBegin,PhaseName,TBankGroup 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"]):
|
||||
# write to read
|
||||
if (currentRow[3] == lastRow[3]):
|
||||
tWTR = dramconfig.tWTR_L
|
||||
else:
|
||||
tWTR = dramconfig.tWTR_S
|
||||
|
||||
minWriteToRead = dramconfig.tWL + dramconfig.getWriteAccessTime() + tWTR
|
||||
writeToRead = currentRow[1] - lastRow[1]
|
||||
|
||||
if (writeToRead < minWriteToRead):
|
||||
return TestFailed("Read {0} starts {1} after start of write {2}. Minimum time is {3}".format(currentRow[0], formatTime(writeToRead), lastRow[0], formatTime(minWriteToRead)))
|
||||
|
||||
elif (currentRow[2] in ["WR", "WRA"] and lastRow[2] in ["RD", "RDA"]):
|
||||
# read to write
|
||||
minReadToWrite = dramconfig.tRL + dramconfig.getReadAccessTime() - dramconfig.tWL + dramconfig.clk * 2
|
||||
readToWrite = currentRow[1] - lastRow[1]
|
||||
if (readToWrite < minReadToWrite):
|
||||
return TestFailed("Write {0} starts {1} after start of read {2}. Minimum time is {3}".format(currentRow[0], formatTime(readToWrite), lastRow[0], formatTime(minWriteToRead)))
|
||||
lastRow = currentRow
|
||||
|
||||
return TestSuceeded()
|
||||
|
||||
|
||||
# TODO: Check if this test still is correct!
|
||||
@test
|
||||
def read_holds_dll_constraint_after_sref(connection):
|
||||
"""Checks that all read operations are delayed long enough after the end of the self refresh powerdown state"""
|
||||
|
||||
cursor = connection.cursor()
|
||||
query = """SELECT Phases.ID,PhaseBegin,PhaseName,TBankGroup from Phases INNER JOIN Transactions ON Phases.Transact = Transactions.ID AND TBank = :bank
|
||||
WHERE PhaseName IN ('RD', 'RDA', 'SREF') ORDER BY PhaseBegin"""
|
||||
|
||||
for bankNumber in range(dramconfig.numberOfBanks):
|
||||
cursor.execute(query, {"bank": bankNumber})
|
||||
lastRow = cursor.fetchone()
|
||||
for currentRow in cursor:
|
||||
if (currentRow[2] in ["RD", "RDA"] and lastRow[2] == 'SREF'):
|
||||
srefEndToRead = currentRow[1] - (lastRow[1] - dramconfig.clk)
|
||||
if (srefEndToRead < dramconfig.tXSDLL):
|
||||
return TestFailed("Read {0} starts {1} after end of sref {2}. Minimum time is {3}".format(currentRow[0], formatTime(srefEndToRead), lastRow[0], formatTime(dramconfig.tXSDLL)))
|
||||
lastRow = currentRow
|
||||
return TestSuceeded()
|
||||
|
||||
|
||||
@test
|
||||
def strict_transaction_order(connection):
|
||||
"""Checks that all transactions are processed in the right order"""
|
||||
cursor = connection.cursor()
|
||||
query = """SELECT distinct t2.ID FROM Transactions t1, Transactions t2 where t2.ID > t1.ID and t2.DataStrobeBegin < t1.DataStrobeBegin and t1.DataStrobeBegin != 0 and t2.DataStrobeBegin !=0 and t1.TThread == t2.TThread;"""
|
||||
|
||||
cursor.execute(query)
|
||||
|
||||
transactions = ""
|
||||
for currentRow in cursor:
|
||||
transactions += str(currentRow[0]) + ","
|
||||
|
||||
if (transactions != ""):
|
||||
if (dramconfig.scheduler == "FifoStrict"):
|
||||
return TestFailed("Transactions {0} is/are not in Order ".format(transactions))
|
||||
else:
|
||||
return TestResult(True, "Transactions are not in Order, however this is okay since no FifoStrict was choosen")
|
||||
return TestSuceeded()
|
||||
|
||||
# ----------- powerdown checks ---------------------------------------
|
||||
|
||||
# @test
|
||||
# def sref_active_for_minimal_time(connection):
|
||||
# """Checks that after entering self refresh powerndown state, the state is active for a minimal time (JEDEC 229, P. 41)"""
|
||||
#
|
||||
# cursor = connection.cursor()
|
||||
# cursor.execute("SELECT ID, PhaseEnd-clk-PhaseBegin FROM Phases, GeneralInfo 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()
|
||||
|
||||
# @test
|
||||
# def pdna_pdnp_active_for_minimal_time(connection):
|
||||
# """Checks that after entering active/precharged powerdown, the state is active for a minimal time (JEDEC 229, P. 41)"""
|
||||
#
|
||||
# cursor = connection.cursor()
|
||||
# cursor.execute("SELECT ID,PhaseName, PhaseEnd-PhaseBegin FROM Phases, GeneralInfo WHERE PhaseName IN ('PDNA', 'PDNP') ")
|
||||
# for currentRow in cursor:
|
||||
# if (currentRow[2] < dramconfig.tCKE):
|
||||
# return TestFailed("{0} with ID {1} is {2} long. Minimal time in SREF is {3}".format(currentRow[1], currentRow[0], formatTime(currentRow[2]), dramconfig.tCKE))
|
||||
# return TestSuceeded()
|
||||
|
||||
# -------------------------- interface methods --------------------
|
||||
|
||||
|
||||
def runTests(pathToTrace):
|
||||
connection = sqlite3.connect(pathToTrace)
|
||||
dramconfig.readConfigFromFiles(connection)
|
||||
|
||||
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("[passed] {0}".format(testName))
|
||||
else:
|
||||
print("[failed] {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
|
||||
|
||||
def main():
|
||||
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w')
|
||||
for i in range(1, len(sys.argv)):
|
||||
runTests(sys.argv[i])
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
174
python/src/dramsys/common/memUtil.py
Normal file
174
python/src/dramsys/common/memUtil.py
Normal file
@@ -0,0 +1,174 @@
|
||||
import json
|
||||
from sqlite3.dbapi2 import Cursor
|
||||
|
||||
class MCConfig(object):
|
||||
""" Memory Controller Configuration Class
|
||||
|
||||
The format used in memory specification XML files differs from the
|
||||
format used in memory controller configuration XML files. Each class
|
||||
uses the proper format when searching for elements.
|
||||
"""
|
||||
def getValue(self, id):
|
||||
return self.jsonMCConfig['mcconfig'][id]
|
||||
|
||||
def getIntValue(self, id):
|
||||
return int(self.getValue(id))
|
||||
|
||||
def __init__(self, dbconnection):
|
||||
cursor = dbconnection.cursor()
|
||||
cursor.execute("SELECT MCconfig FROM GeneralInfo")
|
||||
result = cursor.fetchone()
|
||||
self.jsonMCConfig = json.loads(result[0])
|
||||
|
||||
|
||||
class MemSpec(object):
|
||||
""" Memory Specification Class
|
||||
|
||||
The format used in memory specification XML files differs from the
|
||||
format used in memory configuration XML files. Each class uses the
|
||||
proper format when searching for elements.
|
||||
"""
|
||||
def getValue(self, id):
|
||||
val = self.jsonMemSpec['memspec'][id]
|
||||
return val
|
||||
|
||||
def getIntValue(self, group, id):
|
||||
val = self.jsonMemSpec['memspec'][group][id]
|
||||
return int(val)
|
||||
|
||||
def __init__(self, dbconnection):
|
||||
cursor = dbconnection.cursor()
|
||||
cursor.execute("SELECT Memspec FROM GeneralInfo")
|
||||
result = cursor.fetchone()
|
||||
self.jsonMemSpec = json.loads(result[0])
|
||||
|
||||
|
||||
def getClock(dbconnection):
|
||||
cursor = dbconnection.cursor()
|
||||
cursor.execute("SELECT clk, UnitOfTime FROM GeneralInfo")
|
||||
clock, unit = cursor.fetchone()
|
||||
return (clock, unit)
|
||||
|
||||
def getNumberOfTransactions(dbconnection):
|
||||
cursor = dbconnection.cursor()
|
||||
cursor.execute("SELECT NumberOfTransactions FROM GeneralInfo")
|
||||
result = cursor.fetchone()
|
||||
return result[0]
|
||||
|
||||
def getTraceEndTime(dbconnection):
|
||||
cursor = dbconnection.cursor()
|
||||
cursor.execute("SELECT MAX(PhaseEnd) FROM Phases")
|
||||
result = cursor.fetchone()
|
||||
return result[0]
|
||||
|
||||
def getNumberOfBanks(dbconnection):
|
||||
cursor = dbconnection.cursor()
|
||||
cursor.execute("SELECT NumberOfBanks FROM GeneralInfo")
|
||||
result = cursor.fetchone()
|
||||
return result[0]
|
||||
|
||||
def getNumberOfRanks(dbconnection):
|
||||
cursor = dbconnection.cursor()
|
||||
cursor.execute("SELECT NumberOfRanks FROM GeneralInfo")
|
||||
result = cursor.fetchone()
|
||||
return result[0]
|
||||
|
||||
def getNumberOfBankGroups(dbconnection):
|
||||
cursor = dbconnection.cursor()
|
||||
cursor.execute("SELECT NumberOfBankGroups FROM GeneralInfo")
|
||||
result = cursor.fetchone()
|
||||
return result[0]
|
||||
|
||||
def getPer2BankOffset(dbconnection):
|
||||
cursor = dbconnection.cursor()
|
||||
cursor.execute("SELECT Per2BankOffset FROM GeneralInfo")
|
||||
result = cursor.fetchone()
|
||||
return result[0]
|
||||
|
||||
def getWindowSize(connection):
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("SELECT WindowSize FROM GeneralInfo")
|
||||
windowSize = float(cursor.fetchone()[0])
|
||||
return windowSize
|
||||
|
||||
def getRowColumnCommandBus(connection):
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("SELECT RowColumnCommandBus FROM GeneralInfo")
|
||||
return bool(cursor.fetchone()[0])
|
||||
|
||||
def getPseudoChannelMode(connection):
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("SELECT PseudoChannelMode FROM GeneralInfo")
|
||||
return bool(cursor.fetchone()[0])
|
||||
|
||||
|
||||
def maximum_data_rate(connection):
|
||||
memspec = MemSpec(connection)
|
||||
try:
|
||||
width = memspec.getIntValue("memarchitecturespec", "nbrOfDevices") * memspec.getIntValue("memarchitecturespec", "width")
|
||||
except:
|
||||
width = memspec.getIntValue("memarchitecturespec", "width")
|
||||
|
||||
# Backwards compatibility for traces where clkMHz was not yet replaced with tCK
|
||||
clk = None
|
||||
try:
|
||||
clk = 1000000 / memspec.jsonMemSpec['memspec']['memtimingspec']['tCK']
|
||||
except:
|
||||
clk = memspec.getIntValue("memtimingspec", "clkMhz")
|
||||
|
||||
rate = memspec.getIntValue("memarchitecturespec", "dataRate")
|
||||
if getPseudoChannelMode(connection):
|
||||
maxDataRate = float(clk) * float(width) * float(rate) * 2
|
||||
else:
|
||||
maxDataRate = float(clk) * float(width) * float(rate)
|
||||
return maxDataRate
|
||||
|
||||
|
||||
def getFlexibleRef(connection):
|
||||
cursor = connection.cursor()
|
||||
cursor.execute(" SELECT FlexibleRefresh FROM GeneralInfo ")
|
||||
result = cursor.fetchone()
|
||||
return result[0]
|
||||
|
||||
|
||||
def getMaxRefBurst(connection):
|
||||
cursor = connection.cursor()
|
||||
cursor.execute(" SELECT MaxRefBurst FROM GeneralInfo ")
|
||||
result = cursor.fetchone()
|
||||
return result[0]
|
||||
|
||||
|
||||
def getControllerThread(connection):
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("SELECT ControllerThread FROM GeneralInfo")
|
||||
result = cursor.fetchone()
|
||||
return result[0]
|
||||
|
||||
|
||||
def get_phase_occurrences(connection, phase):
|
||||
cursor = connection.cursor()
|
||||
query = "SELECT count(*) FROM Phases WHERE PhaseName = :phase"
|
||||
cursor.execute(query, {"phase": phase})
|
||||
r = cursor.fetchone()
|
||||
cnt = r[0]
|
||||
if cnt is None:
|
||||
cnt = 0
|
||||
return cnt
|
||||
|
||||
|
||||
def get_total_time_in_phase(connection, phase):
|
||||
cursor = connection.cursor()
|
||||
query = "SELECT SUM(PhaseEnd - PhaseBegin) / 1000 FROM Phases WHERE PhaseName = :phase"
|
||||
cursor.execute(query, {"phase": phase})
|
||||
time = cursor.fetchone()
|
||||
totalTime = time[0]
|
||||
if totalTime is None:
|
||||
totalTime = 0.0
|
||||
return totalTime
|
||||
|
||||
|
||||
def getCommandLengthForPhase(connection, phase):
|
||||
cursor = connection.cursor()
|
||||
cursor.execute(f"SELECT Length FROM CommandLengths WHERE Command=\"{phase}\"")
|
||||
length = cursor.fetchone()
|
||||
return length[0]
|
||||
0
python/src/dramsys/tools/__init__.py
Normal file
0
python/src/dramsys/tools/__init__.py
Normal file
358
python/src/dramsys/tools/vcdExport.py
Normal file
358
python/src/dramsys/tools/vcdExport.py
Normal file
@@ -0,0 +1,358 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright (c) 2025 Fraunhofer IESE. All rights reserved.
|
||||
#
|
||||
# Authors:
|
||||
# Derek Christ
|
||||
|
||||
|
||||
import sqlite3
|
||||
import io
|
||||
import sys
|
||||
import enum
|
||||
import datetime
|
||||
from abc import ABC, abstractmethod
|
||||
from dramsys.common.memUtil import *
|
||||
from tqdm import tqdm
|
||||
from vcd import VCDWriter
|
||||
|
||||
TIME_STEP = 1_000_000
|
||||
|
||||
class Signal(ABC):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
@abstractmethod
|
||||
def getNeutralValue(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def getSignalType(self):
|
||||
pass
|
||||
|
||||
class NumericSignal(Signal):
|
||||
def getNeutralValue(self):
|
||||
return "z"
|
||||
|
||||
def getSignalType(self):
|
||||
return "integer"
|
||||
|
||||
class StringSignal(Signal):
|
||||
def getNeutralValue(self):
|
||||
return ""
|
||||
|
||||
def getSignalType(self):
|
||||
return "string"
|
||||
|
||||
class Event():
|
||||
def __init__(self, signal, value):
|
||||
self.signal = signal
|
||||
self.value = value
|
||||
|
||||
class Transaction():
|
||||
def __init__(self, rank, bankgroup, bank, command):
|
||||
self.rank = rank
|
||||
self.bankgroup = bankgroup
|
||||
self.bank = bank
|
||||
self.command = command
|
||||
|
||||
class Granularity(enum.Enum):
|
||||
Bankwise = 0
|
||||
TwoBankwise = 1
|
||||
Groupwise = 2
|
||||
Rankwise = 3
|
||||
|
||||
class TimeWindow():
|
||||
def __init__(self, windowSize, lastTimestamp):
|
||||
self.currentTime = 0
|
||||
self.windowSize = windowSize
|
||||
self.lastTimestamp = lastTimestamp
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
currentRange = (self.currentTime, self.currentTime + self.windowSize)
|
||||
|
||||
if self.currentTime <= self.lastTimestamp:
|
||||
self.currentTime += self.windowSize
|
||||
return currentRange
|
||||
else:
|
||||
raise StopIteration
|
||||
|
||||
def numberOfIterations(self):
|
||||
return int(self.lastTimestamp / self.windowSize)
|
||||
|
||||
|
||||
def getGranularity(phase):
|
||||
if phase == "PRESB" or phase == "REFSB" or phase == "RFMSB":
|
||||
return Granularity.Groupwise
|
||||
elif phase == "REFP2B" or phase == "RFMP2B":
|
||||
return Granularity.TwoBankwise
|
||||
elif phase == "PREAB" or phase == "PREA" or phase == "REFAB" or phase == "REFA" or phase == "RFMAB" \
|
||||
or phase == "PDNA" or phase == "PDNP" or phase == "SREF":
|
||||
return Granularity.Rankwise
|
||||
else:
|
||||
return Granularity.Bankwise
|
||||
|
||||
|
||||
def getAmountOfCommandBusSpans(phase):
|
||||
if phase == "PDNA" or phase == "PDNAB" or phase == "PDNP" or phase == "PDNPB" or phase == "SREF" or phase == "SREFB":
|
||||
return 2
|
||||
else:
|
||||
return 1
|
||||
|
||||
|
||||
def getUnitOfTime(connection):
|
||||
_, unit = getClock(connection)
|
||||
return unit.lower()
|
||||
|
||||
|
||||
def getLastTimestamp(connection):
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("SELECT DataStrobeEnd FROM Phases ORDER BY DataStrobeEnd DESC LIMIT 1")
|
||||
|
||||
return cursor.fetchone()[0]
|
||||
|
||||
|
||||
def getRanksBankgroupsBanks(connection):
|
||||
ranks = getNumberOfRanks(connection)
|
||||
bankgroups = int(getNumberOfBankGroups(connection) / ranks)
|
||||
banks = int(getNumberOfBanks(connection) / (bankgroups * ranks))
|
||||
|
||||
return (ranks, bankgroups, banks)
|
||||
|
||||
|
||||
def getBankName(rank, bankgroup, bank):
|
||||
return "RA" + str(rank) + "_BG" + str(bankgroup) + "_BA" + str(bank)
|
||||
|
||||
|
||||
def getBankNames(ranks, bankgroups, banks):
|
||||
names = []
|
||||
for rank in range(ranks):
|
||||
for bankgroup in range(bankgroups):
|
||||
for bank in range(banks):
|
||||
names.append(getBankName(rank, bankgroup, bank))
|
||||
|
||||
return names
|
||||
|
||||
|
||||
def getOccurringSignals(connection):
|
||||
setOfSignals = set()
|
||||
|
||||
setOfSignals.add(StringSignal("REQ"))
|
||||
setOfSignals.add(StringSignal("RESP"))
|
||||
|
||||
(ranks, bankgroups, banks) = getRanksBankgroupsBanks(connection)
|
||||
|
||||
for name in getBankNames(ranks, bankgroups, banks):
|
||||
setOfSignals.add(StringSignal(name))
|
||||
|
||||
setOfSignals.add(StringSignal("Command_Bus"))
|
||||
setOfSignals.add(StringSignal("Data_Bus"))
|
||||
|
||||
return setOfSignals
|
||||
|
||||
|
||||
def getDataBusEvents(connection, eventDict, windowRange):
|
||||
beginWindow, endWindow = windowRange
|
||||
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("SELECT Transactions.ID, DataStrobeBegin, DataStrobeEnd, Command FROM Phases INNER JOIN Transactions ON Transactions.ID=Phases.Transact " +
|
||||
"WHERE DataStrobeBegin BETWEEN " + str(beginWindow) + " AND " + str(endWindow) +
|
||||
" AND DataStrobeEnd BETWEEN " + str(beginWindow) + " AND " + str(endWindow))
|
||||
|
||||
for transactionId, begin, end, command in cursor.fetchall():
|
||||
if eventDict.get(begin) == None:
|
||||
eventDict[begin] = []
|
||||
|
||||
if eventDict.get(end) == None:
|
||||
eventDict[end] = []
|
||||
|
||||
eventDict[begin].append(Event("Data_Bus", command + " " + str(transactionId)))
|
||||
eventDict[end].append(Event("Data_Bus", ""))
|
||||
|
||||
|
||||
def getCommandBusEvents(connection, eventDict, transactionDict, windowRange):
|
||||
beginWindow, endWindow = windowRange
|
||||
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("SELECT PhaseName, PhaseBegin, PhaseEnd, Transact FROM Phases " +
|
||||
"WHERE PhaseBegin BETWEEN " + str(beginWindow) + " AND " + str(endWindow) +
|
||||
" AND PhaseEnd BETWEEN " + str(beginWindow) + " AND " + str(endWindow))
|
||||
|
||||
for phase, phaseBegin, phaseEnd, transactionId in cursor.fetchall():
|
||||
if phase == "REQ" or phase == "RESP":
|
||||
continue
|
||||
|
||||
timespans = []
|
||||
commandLengthTime = getCommandLengthForPhase(connection, "RD") * getClock(connection)[0]
|
||||
|
||||
if getAmountOfCommandBusSpans(phase) == 1:
|
||||
timespans.append((phaseBegin, phaseBegin + commandLengthTime))
|
||||
else:
|
||||
timespans.append((phaseBegin, phaseBegin + commandLengthTime))
|
||||
timespans.append((phaseEnd - commandLengthTime, phaseEnd))
|
||||
|
||||
for begin, end in timespans:
|
||||
if eventDict.get(begin) == None:
|
||||
eventDict[begin] = []
|
||||
|
||||
if eventDict.get(end) == None:
|
||||
eventDict[end] = []
|
||||
|
||||
eventDict[begin].append(Event("Command_Bus", phase + " " + str(transactionId)))
|
||||
eventDict[end].append(Event("Command_Bus", ""))
|
||||
|
||||
currentTransaction = transactionDict[transactionId]
|
||||
|
||||
rank = currentTransaction.rank
|
||||
bankgroup = currentTransaction.bankgroup
|
||||
bank = currentTransaction.bank
|
||||
|
||||
(ranks, bankgroups, banks) = getRanksBankgroupsBanks(connection)
|
||||
|
||||
currentBanks = []
|
||||
|
||||
if getGranularity(phase) == Granularity.Rankwise:
|
||||
rank = currentTransaction.rank
|
||||
for _bankgroup in range(bankgroups):
|
||||
for _bank in range(banks):
|
||||
currentBanks.append((rank, _bankgroup, _bank))
|
||||
elif getGranularity(phase) == Granularity.Groupwise:
|
||||
for _bankgroup in range(bankgroups):
|
||||
currentBanks.append((rank, _bankgroup, bank))
|
||||
elif getGranularity(phase) == Granularity.TwoBankwise:
|
||||
currentBanks.append((rank, bankgroup, bank))
|
||||
per2BankOffset = getPer2BankOffset(connection)
|
||||
bankgroupOffset = per2BankOffset // banks
|
||||
bankOffset = per2BankOffset % banks
|
||||
currentBanks.append((rank, bankgroup + bankgroupOffset, bank + bankOffset))
|
||||
else:
|
||||
currentBanks.append((rank, bankgroup, bank))
|
||||
|
||||
for _rank, _bankgroup, _bank in currentBanks:
|
||||
currentBankName = getBankName(_rank, _bankgroup, _bank)
|
||||
|
||||
eventDict[begin].append(Event(currentBankName, phase + " " + str(transactionId)))
|
||||
eventDict[end].append(Event(currentBankName, ""))
|
||||
|
||||
|
||||
def getTransactionRange(connection, transactionRange, windowRange):
|
||||
beginWindow, endWindow = windowRange
|
||||
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("SELECT Transact FROM Phases" +
|
||||
" WHERE PhaseBegin BETWEEN " + str(beginWindow) + " AND " + str(endWindow) +
|
||||
" AND PhaseEnd BETWEEN " + str(beginWindow) + " AND " + str(endWindow))
|
||||
|
||||
minTransaction, maxTransaction = float('inf'), 0
|
||||
|
||||
for transactionId in cursor.fetchall():
|
||||
maxTransaction = max(maxTransaction, transactionId[0])
|
||||
minTransaction = min(minTransaction, transactionId[0])
|
||||
|
||||
if minTransaction == float('inf'):
|
||||
minTransaction = 0
|
||||
|
||||
transactionRange.append(minTransaction)
|
||||
transactionRange.append(maxTransaction)
|
||||
|
||||
|
||||
def getReqAndRespPhases(connection, eventDict, transactionDict, windowRange):
|
||||
beginWindow, endWindow = windowRange
|
||||
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("SELECT PhaseName, PhaseBegin, PhaseEnd, Transact " +
|
||||
"FROM Phases WHERE PhaseBegin BETWEEN " + str(beginWindow) + " AND " + str(endWindow) +
|
||||
" AND PhaseEnd BETWEEN " + str(beginWindow) + " AND " + str(endWindow))
|
||||
|
||||
for phase, begin, end, transactionId in cursor.fetchall():
|
||||
if phase != "REQ" and phase != "RESP":
|
||||
continue
|
||||
|
||||
if eventDict.get(begin) == None:
|
||||
eventDict[begin] = []
|
||||
|
||||
if eventDict.get(end) == None:
|
||||
eventDict[end] = []
|
||||
|
||||
currentTransaction = transactionDict[transactionId]
|
||||
command = currentTransaction.command
|
||||
|
||||
eventDict[begin].append(Event(phase, command + " " + str(transactionId)))
|
||||
eventDict[end].append(Event(phase, ""))
|
||||
|
||||
|
||||
def getTransactions(connection, transactionDict, transactionRange):
|
||||
minTransaction, maxTransaction = transactionRange
|
||||
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("SELECT Transactions.ID, Rank, Bankgroup, Bank, Command FROM Transactions INNER JOIN Phases ON Transactions.ID=Phases.Transact" +
|
||||
" WHERE Transactions.ID BETWEEN " + str(minTransaction) + " AND " + str(maxTransaction))
|
||||
|
||||
for transactionId, rank, bankgroup, bank, command in cursor.fetchall():
|
||||
(ranks, bankgroups, banks) = getRanksBankgroupsBanks(connection)
|
||||
|
||||
rank = rank % ranks
|
||||
bankgroup = bankgroup % bankgroups
|
||||
bank = bank % banks
|
||||
|
||||
currentTransaction = Transaction(rank, bankgroup, bank, command)
|
||||
transactionDict[transactionId] = currentTransaction
|
||||
|
||||
|
||||
def dumpVcd(pathToTrace):
|
||||
connection = sqlite3.connect(pathToTrace)
|
||||
|
||||
signalList = getOccurringSignals(connection)
|
||||
|
||||
window = TimeWindow(TIME_STEP, getLastTimestamp(connection))
|
||||
|
||||
with io.StringIO() as f:
|
||||
currentDate = datetime.date.today().strftime("%B %d, %Y")
|
||||
unit = getUnitOfTime(connection)
|
||||
with VCDWriter(f, timescale="1" + unit, date=currentDate) as writer:
|
||||
variableDict = {}
|
||||
|
||||
for signal in signalList:
|
||||
neutralValue = signal.getNeutralValue()
|
||||
signalType = signal.getSignalType()
|
||||
variableDict[signal.name] = writer.register_var("DRAMSys", signal.name, signalType, init=neutralValue)
|
||||
|
||||
for windowRange in tqdm(window, total=window.numberOfIterations(), desc="VCD export"):
|
||||
eventDict = {}
|
||||
transactionDict = {}
|
||||
transactionRange = []
|
||||
|
||||
getTransactionRange(connection, transactionRange, windowRange)
|
||||
getTransactions(connection, transactionDict, transactionRange)
|
||||
getReqAndRespPhases(connection, eventDict, transactionDict, windowRange)
|
||||
getCommandBusEvents(connection, eventDict, transactionDict, windowRange)
|
||||
getDataBusEvents(connection, eventDict, windowRange)
|
||||
|
||||
# Sort the eventDict so that VCDWriter can work with it.
|
||||
eventDict = sorted(eventDict.items(), key=lambda x: x[0])
|
||||
|
||||
for timestamp, eventList in eventDict:
|
||||
for event in eventList:
|
||||
value_to_change = variableDict.get(event.signal)
|
||||
if value_to_change != None:
|
||||
writer.change(value_to_change, timestamp, event.value)
|
||||
|
||||
f.seek(0)
|
||||
return f.read()
|
||||
|
||||
def main():
|
||||
if len(sys.argv) == 2:
|
||||
dump = dumpVcd(sys.argv[1])
|
||||
print(dump)
|
||||
elif len(sys.argv) == 3:
|
||||
dump = dumpVcd(sys.argv[1])
|
||||
with open(sys.argv[2], 'x') as outputFile:
|
||||
outputFile.write(dump)
|
||||
else:
|
||||
print("Usage: ", sys.argv[0], "<trace_file> [output_file_name]")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
286
python/uv.lock
generated
Normal file
286
python/uv.lock
generated
Normal file
@@ -0,0 +1,286 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.14"
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "contourpy"
|
||||
version = "1.3.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "numpy" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cycler"
|
||||
version = "0.12.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dramsys"
|
||||
version = "0.1.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "matplotlib" },
|
||||
{ name = "pyvcd" },
|
||||
{ name = "tqdm" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "matplotlib", specifier = ">=3.10.7" },
|
||||
{ name = "pyvcd", specifier = ">=0.4.1" },
|
||||
{ name = "tqdm", specifier = ">=4.67.1" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fonttools"
|
||||
version = "4.60.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4b/42/97a13e47a1e51a5a7142475bbcf5107fe3a68fc34aef331c897d5fb98ad0/fonttools-4.60.1.tar.gz", hash = "sha256:ef00af0439ebfee806b25f24c8f92109157ff3fac5731dc7867957812e87b8d9", size = 3559823, upload-time = "2025-09-29T21:13:27.129Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/83/752ca11c1aa9a899b793a130f2e466b79ea0cf7279c8d79c178fc954a07b/fonttools-4.60.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:a884aef09d45ba1206712c7dbda5829562d3fea7726935d3289d343232ecb0d3", size = 2822830, upload-time = "2025-09-29T21:12:24.406Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/17/bbeab391100331950a96ce55cfbbff27d781c1b85ebafb4167eae50d9fe3/fonttools-4.60.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8a44788d9d91df72d1a5eac49b31aeb887a5f4aab761b4cffc4196c74907ea85", size = 2345524, upload-time = "2025-09-29T21:12:26.819Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/2e/d4831caa96d85a84dd0da1d9f90d81cec081f551e0ea216df684092c6c97/fonttools-4.60.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e852d9dda9f93ad3651ae1e3bb770eac544ec93c3807888798eccddf84596537", size = 4843490, upload-time = "2025-09-29T21:12:29.123Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/13/5e2ea7c7a101b6fc3941be65307ef8df92cbbfa6ec4804032baf1893b434/fonttools-4.60.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:154cb6ee417e417bf5f7c42fe25858c9140c26f647c7347c06f0cc2d47eff003", size = 4944184, upload-time = "2025-09-29T21:12:31.414Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/2b/cf9603551c525b73fc47c52ee0b82a891579a93d9651ed694e4e2cd08bb8/fonttools-4.60.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5664fd1a9ea7f244487ac8f10340c4e37664675e8667d6fee420766e0fb3cf08", size = 4890218, upload-time = "2025-09-29T21:12:33.936Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/2f/933d2352422e25f2376aae74f79eaa882a50fb3bfef3c0d4f50501267101/fonttools-4.60.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:583b7f8e3c49486e4d489ad1deacfb8d5be54a8ef34d6df824f6a171f8511d99", size = 4999324, upload-time = "2025-09-29T21:12:36.637Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/99/234594c0391221f66216bc2c886923513b3399a148defaccf81dc3be6560/fonttools-4.60.1-cp314-cp314-win32.whl", hash = "sha256:66929e2ea2810c6533a5184f938502cfdaea4bc3efb7130d8cc02e1c1b4108d6", size = 2220861, upload-time = "2025-09-29T21:12:39.108Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/1d/edb5b23726dde50fc4068e1493e4fc7658eeefcaf75d4c5ffce067d07ae5/fonttools-4.60.1-cp314-cp314-win_amd64.whl", hash = "sha256:f3d5be054c461d6a2268831f04091dc82753176f6ea06dc6047a5e168265a987", size = 2270934, upload-time = "2025-09-29T21:12:41.339Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/da/1392aaa2170adc7071fe7f9cfd181a5684a7afcde605aebddf1fb4d76df5/fonttools-4.60.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b6379e7546ba4ae4b18f8ae2b9bc5960936007a1c0e30b342f662577e8bc3299", size = 2894340, upload-time = "2025-09-29T21:12:43.774Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/a7/3b9f16e010d536ce567058b931a20b590d8f3177b2eda09edd92e392375d/fonttools-4.60.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9d0ced62b59e0430b3690dbc5373df1c2aa7585e9a8ce38eff87f0fd993c5b01", size = 2375073, upload-time = "2025-09-29T21:12:46.437Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/b5/e9bcf51980f98e59bb5bb7c382a63c6f6cac0eec5f67de6d8f2322382065/fonttools-4.60.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:875cb7764708b3132637f6c5fb385b16eeba0f7ac9fa45a69d35e09b47045801", size = 4849758, upload-time = "2025-09-29T21:12:48.694Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/dc/1d2cf7d1cba82264b2f8385db3f5960e3d8ce756b4dc65b700d2c496f7e9/fonttools-4.60.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a184b2ea57b13680ab6d5fbde99ccef152c95c06746cb7718c583abd8f945ccc", size = 5085598, upload-time = "2025-09-29T21:12:51.081Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/4d/279e28ba87fb20e0c69baf72b60bbf1c4d873af1476806a7b5f2b7fac1ff/fonttools-4.60.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:026290e4ec76583881763fac284aca67365e0be9f13a7fb137257096114cb3bc", size = 4957603, upload-time = "2025-09-29T21:12:53.423Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/d4/ff19976305e0c05aa3340c805475abb00224c954d3c65e82c0a69633d55d/fonttools-4.60.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f0e8817c7d1a0c2eedebf57ef9a9896f3ea23324769a9a2061a80fe8852705ed", size = 4974184, upload-time = "2025-09-29T21:12:55.962Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/22/8553ff6166f5cd21cfaa115aaacaa0dc73b91c079a8cfd54a482cbc0f4f5/fonttools-4.60.1-cp314-cp314t-win32.whl", hash = "sha256:1410155d0e764a4615774e5c2c6fc516259fe3eca5882f034eb9bfdbee056259", size = 2282241, upload-time = "2025-09-29T21:12:58.179Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/cb/fa7b4d148e11d5a72761a22e595344133e83a9507a4c231df972e657579b/fonttools-4.60.1-cp314-cp314t-win_amd64.whl", hash = "sha256:022beaea4b73a70295b688f817ddc24ed3e3418b5036ffcd5658141184ef0d0c", size = 2345760, upload-time = "2025-09-29T21:13:00.375Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/93/0dd45cd283c32dea1545151d8c3637b4b8c53cdb3a625aeb2885b184d74d/fonttools-4.60.1-py3-none-any.whl", hash = "sha256:906306ac7afe2156fcf0042173d6ebbb05416af70f6b370967b47f8f00103bbb", size = 1143175, upload-time = "2025-09-29T21:13:24.134Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kiwisolver"
|
||||
version = "1.4.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/32/6cc0fbc9c54d06c2969faa9c1d29f5751a2e51809dd55c69055e62d9b426/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386", size = 123806, upload-time = "2025-08-10T21:27:01.537Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/dd/2bfb1d4a4823d92e8cbb420fe024b8d2167f72079b3bb941207c42570bdf/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552", size = 66605, upload-time = "2025-08-10T21:27:03.335Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/69/00aafdb4e4509c2ca6064646cba9cd4b37933898f426756adb2cb92ebbed/kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3", size = 64925, upload-time = "2025-08-10T21:27:04.339Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/dc/51acc6791aa14e5cb6d8a2e28cefb0dc2886d8862795449d021334c0df20/kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58", size = 1472414, upload-time = "2025-08-10T21:27:05.437Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/bb/93fa64a81db304ac8a246f834d5094fae4b13baf53c839d6bb6e81177129/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4", size = 1281272, upload-time = "2025-08-10T21:27:07.063Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/e6/6df102916960fb8d05069d4bd92d6d9a8202d5a3e2444494e7cd50f65b7a/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df", size = 1298578, upload-time = "2025-08-10T21:27:08.452Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/47/e142aaa612f5343736b087864dbaebc53ea8831453fb47e7521fa8658f30/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6", size = 1345607, upload-time = "2025-08-10T21:27:10.125Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/89/d641a746194a0f4d1a3670fb900d0dbaa786fb98341056814bc3f058fa52/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5", size = 2230150, upload-time = "2025-08-10T21:27:11.484Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/6b/5ee1207198febdf16ac11f78c5ae40861b809cbe0e6d2a8d5b0b3044b199/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf", size = 2325979, upload-time = "2025-08-10T21:27:12.917Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/ff/b269eefd90f4ae14dcc74973d5a0f6d28d3b9bb1afd8c0340513afe6b39a/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5", size = 2491456, upload-time = "2025-08-10T21:27:14.353Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/d4/10303190bd4d30de547534601e259a4fbf014eed94aae3e5521129215086/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce", size = 2294621, upload-time = "2025-08-10T21:27:15.808Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/e0/a9a90416fce5c0be25742729c2ea52105d62eda6c4be4d803c2a7be1fa50/kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7", size = 75417, upload-time = "2025-08-10T21:27:17.436Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/10/6949958215b7a9a264299a7db195564e87900f709db9245e4ebdd3c70779/kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c", size = 66582, upload-time = "2025-08-10T21:27:18.436Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/79/60e53067903d3bc5469b369fe0dfc6b3482e2133e85dae9daa9527535991/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548", size = 126514, upload-time = "2025-08-10T21:27:19.465Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/d1/4843d3e8d46b072c12a38c97c57fab4608d36e13fe47d47ee96b4d61ba6f/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d", size = 67905, upload-time = "2025-08-10T21:27:20.51Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/ae/29ffcbd239aea8b93108de1278271ae764dfc0d803a5693914975f200596/kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c", size = 66399, upload-time = "2025-08-10T21:27:21.496Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/ae/d7ba902aa604152c2ceba5d352d7b62106bedbccc8e95c3934d94472bfa3/kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122", size = 1582197, upload-time = "2025-08-10T21:27:22.604Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/41/27c70d427eddb8bc7e4f16420a20fefc6f480312122a59a959fdfe0445ad/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64", size = 1390125, upload-time = "2025-08-10T21:27:24.036Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/42/b3799a12bafc76d962ad69083f8b43b12bf4fe78b097b12e105d75c9b8f1/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134", size = 1402612, upload-time = "2025-08-10T21:27:25.773Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/b5/a210ea073ea1cfaca1bb5c55a62307d8252f531beb364e18aa1e0888b5a0/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370", size = 1453990, upload-time = "2025-08-10T21:27:27.089Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/ce/a829eb8c033e977d7ea03ed32fb3c1781b4fa0433fbadfff29e39c676f32/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21", size = 2331601, upload-time = "2025-08-10T21:27:29.343Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/4b/b5e97eb142eb9cd0072dacfcdcd31b1c66dc7352b0f7c7255d339c0edf00/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a", size = 2422041, upload-time = "2025-08-10T21:27:30.754Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/be/8eb4cd53e1b85ba4edc3a9321666f12b83113a178845593307a3e7891f44/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f", size = 2594897, upload-time = "2025-08-10T21:27:32.803Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/dd/841e9a66c4715477ea0abc78da039832fbb09dac5c35c58dc4c41a407b8a/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369", size = 2391835, upload-time = "2025-08-10T21:27:34.23Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/28/4b2e5c47a0da96896fdfdb006340ade064afa1e63675d01ea5ac222b6d52/kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891", size = 79988, upload-time = "2025-08-10T21:27:35.587Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/be/3578e8afd18c88cdf9cb4cffde75a96d2be38c5a903f1ed0ceec061bd09e/kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32", size = 70260, upload-time = "2025-08-10T21:27:36.606Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matplotlib"
|
||||
version = "3.10.7"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "contourpy" },
|
||||
{ name = "cycler" },
|
||||
{ name = "fonttools" },
|
||||
{ name = "kiwisolver" },
|
||||
{ name = "numpy" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pillow" },
|
||||
{ name = "pyparsing" },
|
||||
{ name = "python-dateutil" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ae/e2/d2d5295be2f44c678ebaf3544ba32d20c1f9ef08c49fe47f496180e1db15/matplotlib-3.10.7.tar.gz", hash = "sha256:a06ba7e2a2ef9131c79c49e63dad355d2d878413a0376c1727c8b9335ff731c7", size = 34804865, upload-time = "2025-10-09T00:28:00.669Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/4b/e5bc2c321b6a7e3a75638d937d19ea267c34bd5a90e12bee76c4d7c7a0d9/matplotlib-3.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d883460c43e8c6b173fef244a2341f7f7c0e9725c7fe68306e8e44ed9c8fb100", size = 8273787, upload-time = "2025-10-09T00:27:23.27Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/ad/6efae459c56c2fbc404da154e13e3a6039129f3c942b0152624f1c621f05/matplotlib-3.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:07124afcf7a6504eafcb8ce94091c5898bbdd351519a1beb5c45f7a38c67e77f", size = 8131348, upload-time = "2025-10-09T00:27:24.926Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/5a/a4284d2958dee4116359cc05d7e19c057e64ece1b4ac986ab0f2f4d52d5a/matplotlib-3.10.7-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c17398b709a6cce3d9fdb1595c33e356d91c098cd9486cb2cc21ea2ea418e715", size = 9533949, upload-time = "2025-10-09T00:27:26.704Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/ff/f3781b5057fa3786623ad8976fc9f7b0d02b2f28534751fd5a44240de4cf/matplotlib-3.10.7-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7146d64f561498764561e9cd0ed64fcf582e570fc519e6f521e2d0cfd43365e1", size = 9804247, upload-time = "2025-10-09T00:27:28.514Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/5a/993a59facb8444efb0e197bf55f545ee449902dcee86a4dfc580c3b61314/matplotlib-3.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:90ad854c0a435da3104c01e2c6f0028d7e719b690998a2333d7218db80950722", size = 9595497, upload-time = "2025-10-09T00:27:30.418Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/a5/77c95aaa9bb32c345cbb49626ad8eb15550cba2e6d4c88081a6c2ac7b08d/matplotlib-3.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:4645fc5d9d20ffa3a39361fcdbcec731382763b623b72627806bf251b6388866", size = 8252732, upload-time = "2025-10-09T00:27:32.332Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/04/45d269b4268d222390d7817dae77b159651909669a34ee9fdee336db5883/matplotlib-3.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:9257be2f2a03415f9105c486d304a321168e61ad450f6153d77c69504ad764bb", size = 8124240, upload-time = "2025-10-09T00:27:33.94Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/c7/ca01c607bb827158b439208c153d6f14ddb9fb640768f06f7ca3488ae67b/matplotlib-3.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1e4bbad66c177a8fdfa53972e5ef8be72a5f27e6a607cec0d8579abd0f3102b1", size = 8316938, upload-time = "2025-10-09T00:27:35.534Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/d2/5539e66e9f56d2fdec94bb8436f5e449683b4e199bcc897c44fbe3c99e28/matplotlib-3.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d8eb7194b084b12feb19142262165832fc6ee879b945491d1c3d4660748020c4", size = 8178245, upload-time = "2025-10-09T00:27:37.334Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/b5/e6ca22901fd3e4fe433a82e583436dd872f6c966fca7e63cf806b40356f8/matplotlib-3.10.7-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4d41379b05528091f00e1728004f9a8d7191260f3862178b88e8fd770206318", size = 9541411, upload-time = "2025-10-09T00:27:39.387Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/99/a4524db57cad8fee54b7237239a8f8360bfcfa3170d37c9e71c090c0f409/matplotlib-3.10.7-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a74f79fafb2e177f240579bc83f0b60f82cc47d2f1d260f422a0627207008ca", size = 9803664, upload-time = "2025-10-09T00:27:41.492Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/a5/85e2edf76ea0ad4288d174926d9454ea85f3ce5390cc4e6fab196cbf250b/matplotlib-3.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:702590829c30aada1e8cef0568ddbffa77ca747b4d6e36c6d173f66e301f89cc", size = 9594066, upload-time = "2025-10-09T00:27:43.694Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/69/9684368a314f6d83fe5c5ad2a4121a3a8e03723d2e5c8ea17b66c1bad0e7/matplotlib-3.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:f79d5de970fc90cd5591f60053aecfce1fcd736e0303d9f0bf86be649fa68fb8", size = 8342832, upload-time = "2025-10-09T00:27:45.543Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/5f/e22e08da14bc1a0894184640d47819d2338b792732e20d292bf86e5ab785/matplotlib-3.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:cb783436e47fcf82064baca52ce748af71725d0352e1d31564cbe9c95df92b9c", size = 8172585, upload-time = "2025-10-09T00:27:47.185Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "2.3.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/76/65/21b3bc86aac7b8f2862db1e808f1ea22b028e30a225a34a5ede9bf8678f2/numpy-2.3.5.tar.gz", hash = "sha256:784db1dcdab56bf0517743e746dfb0f885fc68d948aba86eeec2cba234bdf1c0", size = 20584950, upload-time = "2025-11-16T22:52:42.067Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/97/1a914559c19e32d6b2e233cf9a6a114e67c856d35b1d6babca571a3e880f/numpy-2.3.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:bf06bc2af43fa8d32d30fae16ad965663e966b1a3202ed407b84c989c3221e82", size = 16735706, upload-time = "2025-11-16T22:51:19.558Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/d4/51233b1c1b13ecd796311216ae417796b88b0616cfd8a33ae4536330748a/numpy-2.3.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:052e8c42e0c49d2575621c158934920524f6c5da05a1d3b9bab5d8e259e045f0", size = 12264507, upload-time = "2025-11-16T22:51:22.492Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/98/2fe46c5c2675b8306d0b4a3ec3494273e93e1226a490f766e84298576956/numpy-2.3.5-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:1ed1ec893cff7040a02c8aa1c8611b94d395590d553f6b53629a4461dc7f7b63", size = 5093049, upload-time = "2025-11-16T22:51:25.171Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/0e/0698378989bb0ac5f1660c81c78ab1fe5476c1a521ca9ee9d0710ce54099/numpy-2.3.5-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:2dcd0808a421a482a080f89859a18beb0b3d1e905b81e617a188bd80422d62e9", size = 6626603, upload-time = "2025-11-16T22:51:27Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/a6/9ca0eecc489640615642a6cbc0ca9e10df70df38c4d43f5a928ff18d8827/numpy-2.3.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:727fd05b57df37dc0bcf1a27767a3d9a78cbbc92822445f32cc3436ba797337b", size = 14262696, upload-time = "2025-11-16T22:51:29.402Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/f6/07ec185b90ec9d7217a00eeeed7383b73d7e709dae2a9a021b051542a708/numpy-2.3.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fffe29a1ef00883599d1dc2c51aa2e5d80afe49523c261a74933df395c15c520", size = 16597350, upload-time = "2025-11-16T22:51:32.167Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/37/164071d1dde6a1a84c9b8e5b414fa127981bad47adf3a6b7e23917e52190/numpy-2.3.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8f7f0e05112916223d3f438f293abf0727e1181b5983f413dfa2fefc4098245c", size = 16040190, upload-time = "2025-11-16T22:51:35.403Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/08/3c/f18b82a406b04859eb026d204e4e1773eb41c5be58410f41ffa511d114ae/numpy-2.3.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2e2eb32ddb9ccb817d620ac1d8dae7c3f641c1e5f55f531a33e8ab97960a75b8", size = 18536749, upload-time = "2025-11-16T22:51:39.698Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/79/f82f572bf44cf0023a2fe8588768e23e1592585020d638999f15158609e1/numpy-2.3.5-cp314-cp314-win32.whl", hash = "sha256:66f85ce62c70b843bab1fb14a05d5737741e74e28c7b8b5a064de10142fad248", size = 6335432, upload-time = "2025-11-16T22:51:42.476Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/2e/235b4d96619931192c91660805e5e49242389742a7a82c27665021db690c/numpy-2.3.5-cp314-cp314-win_amd64.whl", hash = "sha256:e6a0bc88393d65807d751a614207b7129a310ca4fe76a74e5c7da5fa5671417e", size = 12919388, upload-time = "2025-11-16T22:51:45.275Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/2b/29fd75ce45d22a39c61aad74f3d718e7ab67ccf839ca8b60866054eb15f8/numpy-2.3.5-cp314-cp314-win_arm64.whl", hash = "sha256:aeffcab3d4b43712bb7a60b65f6044d444e75e563ff6180af8f98dd4b905dfd2", size = 10476651, upload-time = "2025-11-16T22:51:47.749Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/e1/f6a721234ebd4d87084cfa68d081bcba2f5cfe1974f7de4e0e8b9b2a2ba1/numpy-2.3.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:17531366a2e3a9e30762c000f2c43a9aaa05728712e25c11ce1dbe700c53ad41", size = 16834503, upload-time = "2025-11-16T22:51:50.443Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/1c/baf7ffdc3af9c356e1c135e57ab7cf8d247931b9554f55c467efe2c69eff/numpy-2.3.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d21644de1b609825ede2f48be98dfde4656aefc713654eeee280e37cadc4e0ad", size = 12381612, upload-time = "2025-11-16T22:51:53.609Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/91/f7f0295151407ddc9ba34e699013c32c3c91944f9b35fcf9281163dc1468/numpy-2.3.5-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c804e3a5aba5460c73955c955bdbd5c08c354954e9270a2c1565f62e866bdc39", size = 5210042, upload-time = "2025-11-16T22:51:56.213Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/3b/78aebf345104ec50dd50a4d06ddeb46a9ff5261c33bcc58b1c4f12f85ec2/numpy-2.3.5-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:cc0a57f895b96ec78969c34f682c602bf8da1a0270b09bc65673df2e7638ec20", size = 6724502, upload-time = "2025-11-16T22:51:58.584Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/c6/7c34b528740512e57ef1b7c8337ab0b4f0bddf34c723b8996c675bc2bc91/numpy-2.3.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:900218e456384ea676e24ea6a0417f030a3b07306d29d7ad843957b40a9d8d52", size = 14308962, upload-time = "2025-11-16T22:52:01.698Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/35/09d433c5262bc32d725bafc619e095b6a6651caf94027a03da624146f655/numpy-2.3.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09a1bea522b25109bf8e6f3027bd810f7c1085c64a0c7ce050c1676ad0ba010b", size = 16655054, upload-time = "2025-11-16T22:52:04.267Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/ab/6a7b259703c09a88804fa2430b43d6457b692378f6b74b356155283566ac/numpy-2.3.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04822c00b5fd0323c8166d66c701dc31b7fbd252c100acd708c48f763968d6a3", size = 16091613, upload-time = "2025-11-16T22:52:08.651Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/88/330da2071e8771e60d1038166ff9d73f29da37b01ec3eb43cb1427464e10/numpy-2.3.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d6889ec4ec662a1a37eb4b4fb26b6100841804dac55bd9df579e326cdc146227", size = 18591147, upload-time = "2025-11-16T22:52:11.453Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/41/851c4b4082402d9ea860c3626db5d5df47164a712cb23b54be028b184c1c/numpy-2.3.5-cp314-cp314t-win32.whl", hash = "sha256:93eebbcf1aafdf7e2ddd44c2923e2672e1010bddc014138b229e49725b4d6be5", size = 6479806, upload-time = "2025-11-16T22:52:14.641Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/30/d48bde1dfd93332fa557cff1972fbc039e055a52021fbef4c2c4b1eefd17/numpy-2.3.5-cp314-cp314t-win_amd64.whl", hash = "sha256:c8a9958e88b65c3b27e22ca2a076311636850b612d6bbfb76e8d156aacde2aaf", size = 13105760, upload-time = "2025-11-16T22:52:17.975Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/fd/4b5eb0b3e888d86aee4d198c23acec7d214baaf17ea93c1adec94c9518b9/numpy-2.3.5-cp314-cp314t-win_arm64.whl", hash = "sha256:6203fdf9f3dc5bdaed7319ad8698e685c7a3be10819f41d32a0723e611733b42", size = 10545459, upload-time = "2025-11-16T22:52:20.55Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "25.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pillow"
|
||||
version = "12.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/cace85a1b0c9775a9f8f5d5423c8261c858760e2466c79b2dd184638b056/pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353", size = 47008828, upload-time = "2025-10-15T18:24:14.008Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/54/2a/9a8c6ba2c2c07b71bec92cf63e03370ca5e5f5c5b119b742bcc0cde3f9c5/pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:beeae3f27f62308f1ddbcfb0690bf44b10732f2ef43758f169d5e9303165d3f9", size = 4045531, upload-time = "2025-10-15T18:23:10.121Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/54/836fdbf1bfb3d66a59f0189ff0b9f5f666cee09c6188309300df04ad71fa/pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d4827615da15cd59784ce39d3388275ec093ae3ee8d7f0c089b76fa87af756c2", size = 4120554, upload-time = "2025-10-15T18:23:12.14Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/cd/16aec9f0da4793e98e6b54778a5fbce4f375c6646fe662e80600b8797379/pillow-12.0.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:3e42edad50b6909089750e65c91aa09aaf1e0a71310d383f11321b27c224ed8a", size = 3576812, upload-time = "2025-10-15T18:23:13.962Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/b7/13957fda356dc46339298b351cae0d327704986337c3c69bb54628c88155/pillow-12.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e5d8efac84c9afcb40914ab49ba063d94f5dbdf5066db4482c66a992f47a3a3b", size = 5252689, upload-time = "2025-10-15T18:23:15.562Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/f5/eae31a306341d8f331f43edb2e9122c7661b975433de5e447939ae61c5da/pillow-12.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:266cd5f2b63ff316d5a1bba46268e603c9caf5606d44f38c2873c380950576ad", size = 4650186, upload-time = "2025-10-15T18:23:17.379Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/62/2a88339aa40c4c77e79108facbd307d6091e2c0eb5b8d3cf4977cfca2fe6/pillow-12.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58eea5ebe51504057dd95c5b77d21700b77615ab0243d8152793dc00eb4faf01", size = 6230308, upload-time = "2025-10-15T18:23:18.971Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/33/5425a8992bcb32d1cb9fa3dd39a89e613d09a22f2c8083b7bf43c455f760/pillow-12.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f13711b1a5ba512d647a0e4ba79280d3a9a045aaf7e0cc6fbe96b91d4cdf6b0c", size = 8039222, upload-time = "2025-10-15T18:23:20.909Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/61/3f5d3b35c5728f37953d3eec5b5f3e77111949523bd2dd7f31a851e50690/pillow-12.0.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6846bd2d116ff42cba6b646edf5bf61d37e5cbd256425fa089fee4ff5c07a99e", size = 6346657, upload-time = "2025-10-15T18:23:23.077Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/be/ee90a3d79271227e0f0a33c453531efd6ed14b2e708596ba5dd9be948da3/pillow-12.0.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c98fa880d695de164b4135a52fd2e9cd7b7c90a9d8ac5e9e443a24a95ef9248e", size = 7038482, upload-time = "2025-10-15T18:23:25.005Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/34/a16b6a4d1ad727de390e9bd9f19f5f669e079e5826ec0f329010ddea492f/pillow-12.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa3ed2a29a9e9d2d488b4da81dcb54720ac3104a20bf0bd273f1e4648aff5af9", size = 6461416, upload-time = "2025-10-15T18:23:27.009Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/39/1aa5850d2ade7d7ba9f54e4e4c17077244ff7a2d9e25998c38a29749eb3f/pillow-12.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d034140032870024e6b9892c692fe2968493790dd57208b2c37e3fb35f6df3ab", size = 7131584, upload-time = "2025-10-15T18:23:29.752Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/db/4fae862f8fad0167073a7733973bfa955f47e2cac3dc3e3e6257d10fab4a/pillow-12.0.0-cp314-cp314-win32.whl", hash = "sha256:1b1b133e6e16105f524a8dec491e0586d072948ce15c9b914e41cdadd209052b", size = 6400621, upload-time = "2025-10-15T18:23:32.06Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/24/b350c31543fb0107ab2599464d7e28e6f856027aadda995022e695313d94/pillow-12.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:8dc232e39d409036af549c86f24aed8273a40ffa459981146829a324e0848b4b", size = 7142916, upload-time = "2025-10-15T18:23:34.71Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/9b/0ba5a6fd9351793996ef7487c4fdbde8d3f5f75dbedc093bb598648fddf0/pillow-12.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:d52610d51e265a51518692045e372a4c363056130d922a7351429ac9f27e70b0", size = 2523836, upload-time = "2025-10-15T18:23:36.967Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/7a/ceee0840aebc579af529b523d530840338ecf63992395842e54edc805987/pillow-12.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1979f4566bb96c1e50a62d9831e2ea2d1211761e5662afc545fa766f996632f6", size = 5255092, upload-time = "2025-10-15T18:23:38.573Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/76/20776057b4bfd1aef4eeca992ebde0f53a4dce874f3ae693d0ec90a4f79b/pillow-12.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b2e4b27a6e15b04832fe9bf292b94b5ca156016bbc1ea9c2c20098a0320d6cf6", size = 4653158, upload-time = "2025-10-15T18:23:40.238Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/3f/d9ff92ace07be8836b4e7e87e6a4c7a8318d47c2f1463ffcf121fc57d9cb/pillow-12.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fb3096c30df99fd01c7bf8e544f392103d0795b9f98ba71a8054bcbf56b255f1", size = 6267882, upload-time = "2025-10-15T18:23:42.434Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/7a/4f7ff87f00d3ad33ba21af78bfcd2f032107710baf8280e3722ceec28cda/pillow-12.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7438839e9e053ef79f7112c881cef684013855016f928b168b81ed5835f3e75e", size = 8071001, upload-time = "2025-10-15T18:23:44.29Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/87/fcea108944a52dad8cca0715ae6247e271eb80459364a98518f1e4f480c1/pillow-12.0.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d5c411a8eaa2299322b647cd932586b1427367fd3184ffbb8f7a219ea2041ca", size = 6380146, upload-time = "2025-10-15T18:23:46.065Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/52/0d31b5e571ef5fd111d2978b84603fce26aba1b6092f28e941cb46570745/pillow-12.0.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7e091d464ac59d2c7ad8e7e08105eaf9dafbc3883fd7265ffccc2baad6ac925", size = 7067344, upload-time = "2025-10-15T18:23:47.898Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/f4/2dd3d721f875f928d48e83bb30a434dee75a2531bca839bb996bb0aa5a91/pillow-12.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:792a2c0be4dcc18af9d4a2dfd8a11a17d5e25274a1062b0ec1c2d79c76f3e7f8", size = 6491864, upload-time = "2025-10-15T18:23:49.607Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/4b/667dfcf3d61fc309ba5a15b141845cece5915e39b99c1ceab0f34bf1d124/pillow-12.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:afbefa430092f71a9593a99ab6a4e7538bc9eabbf7bf94f91510d3503943edc4", size = 7158911, upload-time = "2025-10-15T18:23:51.351Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/2f/16cabcc6426c32218ace36bf0d55955e813f2958afddbf1d391849fee9d1/pillow-12.0.0-cp314-cp314t-win32.whl", hash = "sha256:3830c769decf88f1289680a59d4f4c46c72573446352e2befec9a8512104fa52", size = 6408045, upload-time = "2025-10-15T18:23:53.177Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/73/e29aa0c9c666cf787628d3f0dcf379f4791fba79f4936d02f8b37165bdf8/pillow-12.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:905b0365b210c73afb0ebe9101a32572152dfd1c144c7e28968a331b9217b94a", size = 7148282, upload-time = "2025-10-15T18:23:55.316Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/70/6b41bdcddf541b437bbb9f47f94d2db5d9ddef6c37ccab8c9107743748a4/pillow-12.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:99353a06902c2e43b43e8ff74ee65a7d90307d82370604746738a1e0661ccca7", size = 2525630, upload-time = "2025-10-15T18:23:57.149Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyparsing"
|
||||
version = "3.2.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f2/a5/181488fc2b9d093e3972d2a472855aae8a03f000592dbfce716a512b3359/pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6", size = 1099274, upload-time = "2025-09-21T04:11:06.277Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e", size = 113890, upload-time = "2025-09-21T04:11:04.117Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dateutil"
|
||||
version = "2.9.0.post0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "six" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyvcd"
|
||||
version = "0.4.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5a/0e/bbc8c4b8b5c2c1b072f891ecc63d5cca230ca09cf25f94ebb9b46dadc114/pyvcd-0.4.1.tar.gz", hash = "sha256:dc6275e95a7949b8236086ab2e6d03afede73441243ec5109c9ea89077f3d696", size = 39562, upload-time = "2024-11-10T22:58:47.397Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/6d/24f67ec6cbe90ffca470f3c31e24f3d21124abc5b690398ab34a54bd3070/pyvcd-0.4.1-py2.py3-none-any.whl", hash = "sha256:3a4c71d4dce741f1155a2ed11a6278390a0816293068f6162ad9658d20f75578", size = 23561, upload-time = "2024-11-10T22:58:46.015Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.17.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tqdm"
|
||||
version = "4.67.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" },
|
||||
]
|
||||
Reference in New Issue
Block a user