diff --git a/DRAMSys/traceAnalyzer/CMakeLists.txt b/DRAMSys/traceAnalyzer/CMakeLists.txt index 60431f0a..1e11d963 100644 --- a/DRAMSys/traceAnalyzer/CMakeLists.txt +++ b/DRAMSys/traceAnalyzer/CMakeLists.txt @@ -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 ) diff --git a/DRAMSys/traceAnalyzer/businessObjects/pythoncaller.cpp b/DRAMSys/traceAnalyzer/businessObjects/pythoncaller.cpp index 324707d4..ce890fa5 100644 --- a/DRAMSys/traceAnalyzer/businessObjects/pythoncaller.cpp +++ b/DRAMSys/traceAnalyzer/businessObjects/pythoncaller.cpp @@ -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; +} diff --git a/DRAMSys/traceAnalyzer/businessObjects/pythoncaller.h b/DRAMSys/traceAnalyzer/businessObjects/pythoncaller.h index a0ac37aa..bf50aa0d 100644 --- a/DRAMSys/traceAnalyzer/businessObjects/pythoncaller.h +++ b/DRAMSys/traceAnalyzer/businessObjects/pythoncaller.h @@ -62,15 +62,20 @@ public: std::vector 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, diff --git a/DRAMSys/traceAnalyzer/scripts/memUtil.py b/DRAMSys/traceAnalyzer/scripts/memUtil.py index 049e707a..96b0bc54 100755 --- a/DRAMSys/traceAnalyzer/scripts/memUtil.py +++ b/DRAMSys/traceAnalyzer/scripts/memUtil.py @@ -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] diff --git a/DRAMSys/traceAnalyzer/scripts/vcdExport.py b/DRAMSys/traceAnalyzer/scripts/vcdExport.py new file mode 100755 index 00000000..7854467d --- /dev/null +++ b/DRAMSys/traceAnalyzer/scripts/vcdExport.py @@ -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], "") + else: + dump = dumpVcd(sys.argv[1]) + print(dump) diff --git a/DRAMSys/traceAnalyzer/traceanalyzer.cpp b/DRAMSys/traceAnalyzer/traceanalyzer.cpp index a03728e8..bc79755c 100644 --- a/DRAMSys/traceAnalyzer/traceanalyzer.cpp +++ b/DRAMSys/traceAnalyzer/traceanalyzer.cpp @@ -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(ui->traceFileTabs->currentWidget()); + traceFileTab->exportAsVCD(); +} + void TraceAnalyzer::statusChanged(QString message, bool saveChangesEnable) { statusLabel->setText(message + QTime::currentTime().toString()); diff --git a/DRAMSys/traceAnalyzer/traceanalyzer.h b/DRAMSys/traceAnalyzer/traceanalyzer.h index 9122da44..f28ae7e2 100644 --- a/DRAMSys/traceAnalyzer/traceanalyzer.h +++ b/DRAMSys/traceAnalyzer/traceanalyzer.h @@ -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(); diff --git a/DRAMSys/traceAnalyzer/traceanalyzer.ui b/DRAMSys/traceAnalyzer/traceanalyzer.ui index c4152d16..ba36d631 100644 --- a/DRAMSys/traceAnalyzer/traceanalyzer.ui +++ b/DRAMSys/traceAnalyzer/traceanalyzer.ui @@ -49,7 +49,7 @@ 0 0 800 - 22 + 36 @@ -59,6 +59,7 @@ + @@ -141,6 +142,14 @@ Ctrl+M + + + Export as VCD + + + Ctrl+E + + diff --git a/DRAMSys/traceAnalyzer/tracefiletab.cpp b/DRAMSys/traceAnalyzer/tracefiletab.cpp index 7ca47193..f1618096 100644 --- a/DRAMSys/traceAnalyzer/tracefiletab.cpp +++ b/DRAMSys/traceAnalyzer/tracefiletab.cpp @@ -41,10 +41,12 @@ #include "queryeditor.h" #include "QFileInfo" #include "qmessagebox.h" +#include "businessObjects/pythoncaller.h" #include #include #include #include +#include #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)), diff --git a/DRAMSys/traceAnalyzer/tracefiletab.h b/DRAMSys/traceAnalyzer/tracefiletab.h index b7d52e01..25fa915c 100644 --- a/DRAMSys/traceAnalyzer/tracefiletab.h +++ b/DRAMSys/traceAnalyzer/tracefiletab.h @@ -67,6 +67,7 @@ public: return path; } void commitChangesToDB(void); + void exportAsVCD(); private: QString path;