Merge branch 'work/vcd_export' into 'develop'

Add export option for VCD dumps

See merge request ems/astdm/dram.sys!299
This commit is contained in:
Lukas Steiner
2021-08-16 07:45:56 +00:00
10 changed files with 331 additions and 2 deletions

View File

@@ -108,6 +108,7 @@ add_executable(TraceAnalyzer
scripts/metrics.py
scripts/tests.py
scripts/plots.py
scripts/vcdExport.py
scripts/sonification.pl
scripts/dataExtractForNN.pl
)

View File

@@ -56,7 +56,9 @@ PythonCaller::PythonCaller() :
pathToScripts(QApplication::applicationDirPath().toStdString() +
"/../../DRAMSys/traceAnalyzer/scripts/"),
plotsModuleName("plots"),
plotsFunctionName("generatePlots")
plotsFunctionName("generatePlots"),
vcdExportModuleName("vcdExport"),
vcdExportFunctionName("dumpVcd")
{
Py_Initialize();
PyObject *sysPath = PySys_GetObject((char *)"path");
@@ -67,6 +69,7 @@ PythonCaller::PythonCaller() :
qDebug() << "Test:" << testModuleName.c_str() << testFunctionName.c_str();
qDebug() << "Metric:" << metricModuleName.c_str() << metricFunctionName.c_str();
qDebug() << "Plot:" << plotsModuleName.c_str() << plotsFunctionName.c_str();
qDebug() << "VcdExport:" << vcdExportModuleName.c_str() << vcdExportFunctionName.c_str();
qDebug() << "Script: " << pathToScripts.c_str();
pRunTestsFunction = loadFunctionFromModule(testModuleName, testFunctionName);
@@ -75,6 +78,8 @@ PythonCaller::PythonCaller() :
pGenPlotsFunction = loadFunctionFromModule(plotsModuleName, plotsFunctionName);
pGetMetricsFunction = loadFunctionFromModule(metricModuleName,
getMetricFunctionName);
pVcdExportFunction = loadFunctionFromModule(vcdExportModuleName, vcdExportFunctionName);
}
@@ -220,3 +225,12 @@ QString PythonCaller::generatePlotsOnTrace(QString pathToTrace)
return outputFiles;
}
QString PythonCaller::exportAsVcd(QString pathToTrace)
{
PyObject *pResult = callFunctionWithStringArgument(pVcdExportFunction, pathToTrace);
QString dump(PyUnicode_AsUTF8(pResult));
Py_DECREF(pResult);
return dump;
}

View File

@@ -62,15 +62,20 @@ public:
std::vector<std::string> getMetrics(QString pathToTrace);
QString generatePlotsOnTrace(QString pathToTrace);
QString exportAsVcd(QString pathToTrace);
private:
PyObject *pRunTestsFunction, *pCalculateMetricsFunction, *pGetMetricsFunction;
PyObject *pGenPlotsFunction;
PyObject *pVcdExportFunction;
PyObject *loadFunctionFromModule(std::string moduleName,
std::string functionName);
std::string testModuleName, testFunctionName, metricModuleName,
metricFunctionName, getMetricFunctionName, pathToScripts;
std::string plotsModuleName;
std::string plotsFunctionName;
std::string vcdExportModuleName;
std::string vcdExportFunctionName;
PyObject *callFunctionWithStringArgument(PyObject *function, QString argument);
PyObject *callMetricsFunction(PyObject *function, QString argument,

View File

@@ -48,6 +48,11 @@ def getClock(dbconnection):
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 getNumberOfBanks(dbconnection):
cursor = dbconnection.cursor()
@@ -55,6 +60,17 @@ def getNumberOfBanks(dbconnection):
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 getWindowSize(connection):
cursor = connection.cursor()
@@ -116,3 +132,9 @@ def get_total_time_in_phase(connection, phase):
if (totalTime is None):
totalTime = 0.0
return totalTime
def getCommandLengthForPhase(connection, phase):
cursor = connection.cursor()
cursor.execute("SELECT " + phase + " FROM CommandLengths")
length = cursor.fetchone()
return length[0]

View File

@@ -0,0 +1,248 @@
#!/usr/bin/env python3
import sqlite3
import io
import sys
import enum
import datetime
from abc import ABC, abstractmethod
from memUtil import *
from vcd import VCDWriter
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, dataStrobeBegin, dataStrobeEnd):
self.rank = rank
self.bankgroup = bankgroup
self.bank = bank
self.dataStrobeBegin = dataStrobeBegin
self.dataStrobeEnd = dataStrobeEnd
class Granularity(enum.Enum):
Bankwise = 0
Groupwise = 1
Rankwise = 2
def getGranularity(phase):
if phase == "PRESB" or phase == "REFSB":
return Granularity.Groupwise
elif phase == "PREA" or phase == "REFA" 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 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):
setOfPhases = set()
setOfPhases.add(NumericSignal("REQ"))
setOfPhases.add(NumericSignal("RESP"))
(ranks, bankgroups, banks) = getRanksBankgroupsBanks(connection)
for name in getBankNames(ranks, bankgroups, banks):
setOfPhases.add(NumericSignal(name))
setOfPhases.add(StringSignal("Command_Bus"))
setOfPhases.add(NumericSignal("Data_Bus"))
return setOfPhases
def getDataBusEvents(connection, eventDict):
cursor = connection.cursor()
cursor.execute("SELECT ID, DataStrobeBegin, DataStrobeEnd FROM Transactions")
transactions = getNumberOfTransactions(connection)
for transactionId, begin, end in cursor.fetchall():
if eventDict.get(begin) == None:
eventDict[begin] = []
if eventDict.get(end) == None:
eventDict[end] = []
eventDict[begin].append(Event("Data_Bus", transactionId))
eventDict[end].append(Event("Data_Bus", "z"))
def getCommandBusEvents(connection, eventDict, transactionDict):
cursor = connection.cursor()
cursor.execute("SELECT PhaseName, PhaseBegin, PhaseEnd, Transact FROM Phases")
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))
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 _bank in range(banks):
currentBanks.append((rank, bankgroup, _bank))
else:
currentBanks.append((rank, bankgroup, bank))
for _rank, _bankgroup, _bank in currentBanks:
currentBankName = getBankName(_rank, _bankgroup, _bank)
eventDict[begin].append(Event(currentBankName, transactionId))
eventDict[end].append(Event(currentBankName, "z"))
def getReqAndRespPhases(connection, eventDict):
cursor = connection.cursor()
cursor.execute("SELECT PhaseName, PhaseBegin, PhaseEnd, Transact FROM Phases")
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] = []
eventDict[begin].append(Event(phase, transactionId))
eventDict[end].append(Event(phase, "z"))
def getTransactions(connection, transactionDict):
cursor = connection.cursor()
cursor.execute("SELECT ID, TRank, TBankgroup, TBank, DataStrobeBegin, DataStrobeEnd FROM Transactions")
(ranks, bankgroups, banks) = getRanksBankgroupsBanks(connection)
for transactionId, rank, bankgroup, bank, dataStrobeBegin, dataStrobeEnd in cursor.fetchall():
rank = rank % ranks
bankgroup = bankgroup % bankgroups
bank = bank % banks
currentTransaction = Transaction(rank, bankgroup, bank, dataStrobeBegin, dataStrobeEnd)
transactionDict[transactionId] = currentTransaction
def dumpVcd(pathToTrace):
connection = sqlite3.connect(pathToTrace)
eventDict = {}
transactionDict = {}
getTransactions(connection, transactionDict)
signalList = getOccurringSignals(connection)
getReqAndRespPhases(connection, eventDict)
getDataBusEvents(connection, eventDict)
getCommandBusEvents(connection, eventDict, transactionDict)
# Sort the eventDict so that VCDWriter can work with it.
eventDict = sorted(eventDict.items(), key=lambda x: x[0])
with io.StringIO() as f:
currentDate = datetime.date.today().strftime("%B %d, %Y")
with VCDWriter(f, timescale='1 ps', 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 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()
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage:", sys.argv[0], "<tracefile>")
else:
dump = dumpVcd(sys.argv[1])
print(dump)

View File

@@ -66,6 +66,7 @@ TraceAnalyzer::TraceAnalyzer(QWidget *parent) :
// Disable actions except for "Open" until some file is open.
ui->actionReload_all->setEnabled(false);
ui->actionSaveChangesToDB->setEnabled(false);
ui->actionExportAsVCD->setEnabled(false);
ui->actionClose_all->setEnabled(false);
ui->actionTest->setEnabled(false);
ui->actionMetrics->setEnabled(false);
@@ -120,6 +121,7 @@ void TraceAnalyzer::openTracefile(const QString &path)
// Enable actions
ui->actionReload_all->setEnabled(true);
ui->actionSaveChangesToDB->setEnabled(true);
ui->actionExportAsVCD->setEnabled(true);
ui->actionClose_all->setEnabled(true);
ui->actionTest->setEnabled(true);
ui->actionMetrics->setEnabled(true);
@@ -144,6 +146,7 @@ void TraceAnalyzer::on_actionClose_all_triggered()
// All files closed. Disable actions except for "Open".
ui->actionReload_all->setEnabled(false);
ui->actionSaveChangesToDB->setEnabled(false);
ui->actionExportAsVCD->setEnabled(false);
ui->actionClose_all->setEnabled(false);
ui->actionTest->setEnabled(false);
ui->actionMetrics->setEnabled(false);
@@ -199,6 +202,12 @@ void TraceAnalyzer::on_actionSaveChangesToDB_triggered()
}
}
void TraceAnalyzer::on_actionExportAsVCD_triggered()
{
TraceFileTab *traceFileTab = static_cast<TraceFileTab *>(ui->traceFileTabs->currentWidget());
traceFileTab->exportAsVCD();
}
void TraceAnalyzer::statusChanged(QString message, bool saveChangesEnable)
{
statusLabel->setText(message + QTime::currentTime().toString());

View File

@@ -76,6 +76,7 @@ private Q_SLOTS:
void on_actionOpen_triggered();
void on_actionReload_all_triggered();
void on_actionSaveChangesToDB_triggered();
void on_actionExportAsVCD_triggered();
void on_traceFileTabs_tabCloseRequested(int index);
void on_actionClose_all_triggered();
void on_actionTest_triggered();

View File

@@ -49,7 +49,7 @@
<x>0</x>
<y>0</y>
<width>800</width>
<height>22</height>
<height>36</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
@@ -59,6 +59,7 @@
<addaction name="actionOpen"/>
<addaction name="actionReload_all"/>
<addaction name="actionSaveChangesToDB"/>
<addaction name="actionExportAsVCD"/>
<addaction name="actionClose_all"/>
<addaction name="separator"/>
<addaction name="actionTest"/>
@@ -141,6 +142,14 @@
<string>Ctrl+M</string>
</property>
</action>
<action name="actionExportAsVCD">
<property name="text">
<string>Export as VCD</string>
</property>
<property name="shortcut">
<string>Ctrl+E</string>
</property>
</action>
</widget>
<resources/>
<connections/>

View File

@@ -41,10 +41,12 @@
#include "queryeditor.h"
#include "QFileInfo"
#include "qmessagebox.h"
#include "businessObjects/pythoncaller.h"
#include <iostream>
#include <QStandardItemModel>
#include <QString>
#include <QItemDelegate>
#include <QFileDialog>
#include "qwt_plot_histogram.h"
#include "qwt_plot_curve.h"
#include "qwt_plot_layout.h"
@@ -88,6 +90,23 @@ void TraceFileTab::commitChangesToDB()
navigator->commitChangesToDB();
}
void TraceFileTab::exportAsVCD()
{
QString filename = QFileDialog::getSaveFileName(this, "Export to VCD", "",
"VCD files (*.vcd)");
PythonCaller pythonCaller;
QString dump = pythonCaller.exportAsVcd(path);
if (filename != "") {
QFile file(filename);
file.open(QIODevice::WriteOnly | QIODevice::Text);
QTextStream out(&file);
out << dump;
file.close();
}
}
void TraceFileTab::setUpTraceplotScrollbar()
{
QObject::connect(ui->traceplotScrollbar, SIGNAL(valueChanged(int)),

View File

@@ -67,6 +67,7 @@ public:
return path;
}
void commitChangesToDB(void);
void exportAsVCD();
private:
QString path;