Elaborate generated error info in tst_cosimulate
MODIFY
src/edittab.h
src/edittab.h
@@ -62,7 +62,6 @@ public slots:
*/
void enableAssemblyInput();
-private slots:
void showSymbolNavigator();
void sourceCodeChanged();
void on_disassembledViewButton_toggled();

MODIFY
test/tst_cosimulate.cpp
test/tst_cosimulate.cpp
using namespace Ripes;
using namespace vsrtl::core;
using RVISA = ISAInfo<ISA::RV32I>;
+struct RegisterChange {
+ unsigned index;
+ unsigned newValue;
+};
// Maximum cycle count
static constexpr unsigned s_maxCycles = 1000000;
using Registers = std::map<int, uint32_t>;
-using Trace = std::map<int, Registers>;
-static constexpr unsigned s_ecallreg = 10; // a0
+struct TraceEntry {
+ Registers regs;
+ unsigned long cycle;
+ unsigned long pc;
+};
+
+using Trace = std::vector<TraceEntry>;
// Reference model
// All tests will be compared against the trace generated by this processor model.
@@ -37,6 +46,10 @@ static constexpr ProcessorID s_referenceModel = ProcessorID::RVSS;
// s_testFiles denotes all of the test files that will be included in the cosimulation run for each processor.
const QString s_testdir = VSRTL_RISCV_TEST_DIR;
const std::vector<LoadFileParams> s_testFiles = {
+ LoadFileParams{QString(s_testdir + QDir::separator() + "../../examples/assembly/complexMul.s"),
+ SourceType::Assembly, 0, 0},
+ LoadFileParams{QString(s_testdir + QDir::separator() + "../../examples/assembly/factorial.s"), SourceType::Assembly,
+ 0, 0},
LoadFileParams{QString(s_testdir + QDir::separator() + "../../examples/ELF/RanPi"), SourceType::ExternalELF, 0, 0}};
class tst_Cosimulate : public QObject {
@@ -44,17 +57,20 @@ class tst_Cosimulate : public QObject {
private:
void cosimulate(const ProcessorID& id, const QStringList& extensions);
- Trace generateReferenceTrace(const QStringList& extensions);
+ const Trace& generateReferenceTrace(const QStringList& extensions);
void handleSysCall();
void executeSimulator(Trace& outTrace, const Trace* refTrace = nullptr);
Registers dumpRegs();
- QString generateErrorReport(const Registers& lhs, const Registers& rhs, unsigned cycles) const;
+ QString generateErrorReport(const RegisterChange& change, const TraceEntry& lhs, const TraceEntry& rhs) const;
+ void loadCurrentTest();
bool m_stop = false;
QString m_err;
LoadFileParams m_currentTest;
EditTab* m_loader;
+ std::map<QString, Trace> m_referenceTraces;
+
private slots:
/**
* PROCESSOR MODELS TO TEST
@@ -64,8 +80,11 @@ private slots:
};
void tst_Cosimulate::handleSysCall() {
- unsigned status = ProcessorHandler::get()->getProcessor()->getRegister(RegisterFileType::GPR, s_ecallreg);
- if (status == 10) {
+ unsigned status = ProcessorHandler::get()->getProcessor()->getRegister(
+ RegisterFileType::GPR, ProcessorHandler::get()->getProcessor()->implementsISA()->syscallReg());
+
+ /// @todo: Generalize this by having ISA report exit syscall codes
+ if (status == ISAInfo<ISA::RV32I>::SysCall::Exit || status == ISAInfo<ISA::RV32I>::SysCall::Exit2) {
m_stop = true;
}
}
@@ -78,32 +97,61 @@ Registers tst_Cosimulate::dumpRegs() {
return regs;
}
-std::optional<int> regNeq(const Registers& lhs, const Registers& rhs) {
- bool eq = true;
+std::optional<std::vector<int>> regNeq(const Registers& lhs, const Registers& rhs) {
+ std::vector<int> uneqIdxes;
for (const auto& it : lhs) {
- eq &= it.second == rhs.at(it.first);
- if (!eq) {
- return it.first;
+ if (it.second != rhs.at(it.first)) {
+ uneqIdxes.push_back(it.first);
}
}
+
+ if (uneqIdxes.size() > 0) {
+ return {uneqIdxes};
+ } else {
return std::nullopt;
}
+}
-QString tst_Cosimulate::generateErrorReport(const Registers& lhs, const Registers& rhs, unsigned cycles) const {
- QString err;
- err +=
- "\nDiscrepancy at cycle " + QString::number(cycles) + " while executing test: " + m_currentTest.filepath + "\n";
- err +=
- "Processor PC is: 0x" + QString::number(ProcessorHandler::get()->getProcessor()->getPcForStage(0), 16) + "\n";
+std::vector<RegisterChange> registerChange(const Registers& before, const Registers& after) {
+ std::vector<RegisterChange> change;
+ for (unsigned i = 0; i < ProcessorHandler::get()->currentISA()->regCnt(); i++) {
+ if (before.at(i) != after.at(i)) {
+ change.push_back({i, after.at(i)});
+ }
+ }
+ return change;
+}
- int idx = regNeq(lhs, rhs).value();
- err += "Difference in register #: " + QString::number(idx) + "\n";
- err +=
- "\t expected: 0x" + QString::number(rhs.at(idx), 16) + "\tactual: 0x" + QString::number(lhs.at(idx), 16) + "\n";
+QString tst_Cosimulate::generateErrorReport(const RegisterChange& change, const TraceEntry& lhs,
+ const TraceEntry& rhs) const {
+ QString err;
+ err += "\nRegister change discrepancy detected while executing test: " + m_currentTest.filepath;
+ err += "\nUnexpected change was: x" + QString::number(change.index) + " -> 0x" +
+ QString::number(change.newValue, 16) + "\n";
+ err += "\nTest processor state: \t\tPC: 0x" + QString::number(lhs.pc, 16) +
+ "\t Cycle #: " + QString::number(lhs.cycle);
+ err += "\nReference processor state: \tPC: 0x" + QString::number(rhs.pc, 16) +
+ "\t Cycle #: " + QString::number(rhs.cycle);
+ err += "\n";
+
+ std::vector<int> idxes = regNeq(lhs.regs, rhs.regs).value();
+ for (const auto idx : idxes) {
+ err += "Difference in register x" + QString::number(idx) + ":";
+ err += "\t expected: 0x" + QString::number(rhs.regs.at(idx), 16) + "\tactual: 0x" +
+ QString::number(lhs.regs.at(idx), 16) + "\n";
+ }
return err;
}
+void tst_Cosimulate::loadCurrentTest() {
+ // Some manual work is required here since we are not instantiating the full application
+ m_loader->loadFile(m_currentTest);
+ if (m_currentTest.type == SourceType::Assembly) {
+ m_loader->sourceCodeChanged();
+ }
+}
+
/**
* @brief tst_Cosimulate::executeSimulator
* Runs the currently loaded simulator on the currently loaded program, generating a register trace while doing so.
@@ -111,42 +159,56 @@ QString tst_Cosimulate::generateErrorReport(const Registers& lhs, const Register
* discrepancy is detected.
*/
void tst_Cosimulate::executeSimulator(Trace& trace, const Trace* refTrace) {
- m_loader->loadFile(m_currentTest);
+ loadCurrentTest();
m_stop = false;
m_err = QString();
bool maxCyclesReached = false;
unsigned cycles = 0;
- trace[cycles] = dumpRegs();
+ trace.push_back(TraceEntry{dumpRegs(), cycles, ProcessorHandler::get()->getProcessor()->getPcForStage(0)});
- const Registers* cmpRegStatePtr = nullptr;
typeof(refTrace->begin()) cmpRegState;
if (refTrace) {
cmpRegState = refTrace->begin();
cmpRegState++; // skip initial state
- cmpRegStatePtr = &cmpRegState->second;
}
+ Registers preRegs = dumpRegs();
+
do {
ProcessorHandler::get()->getProcessorNonConst()->clock();
cycles++;
Registers regs = dumpRegs();
+ auto regChange = registerChange(preRegs, regs);
// Detect change in current register state
- if (regNeq(regs, trace.rbegin()->second)) {
- trace[cycles] = regs;
-
- // Check whether change corresponds to expected change in comparison trace
- if (cmpRegStatePtr) {
- if (regNeq(regs, *cmpRegStatePtr)) {
- const QString err = generateErrorReport(regs, *cmpRegStatePtr, cycles);
+ if (regNeq(regs, trace.rbegin()->regs)) {
+ trace.push_back(TraceEntry{dumpRegs(), cycles, ProcessorHandler::get()->getProcessor()->getPcForStage(0)});
+
+ // Check whether change corresponds to expected change in comparison trace. regChange might contain multiple
+ // register changes (for processors that can commit >1 instruction per cycle). So we try to locate each of
+ // the changes in the next expected register set (cmpRegStatePtr), until we have accounted for all register
+ // changes. If we incremented the expected register set, but did not find an accompanying register change,
+ // then we've reached a point of divergence.
+ if (refTrace != nullptr) {
+ while (regChange.size() > 0) {
+ bool foundChange = false;
+ for (auto changeIt = regChange.begin(); changeIt != regChange.end(); changeIt++) {
+ if (cmpRegState->regs.at(changeIt->index) == changeIt->newValue) {
+ regChange.erase(changeIt);
+ cmpRegState++;
+ foundChange = true;
+ break;
+ }
+ }
+ if (!foundChange) {
+ const QString err = generateErrorReport(*regChange.begin(), *trace.rbegin(), *cmpRegState);
QFAIL(err.toStdString().c_str());
}
-
- cmpRegState++;
- cmpRegStatePtr = &cmpRegState->second;
}
}
+ }
+ preRegs = regs;
maxCyclesReached = cycles >= s_maxCycles;
m_stop |= maxCyclesReached || ProcessorHandler::get()->getProcessor()->finished();
@@ -161,11 +223,17 @@ void tst_Cosimulate::executeSimulator(Trace& trace, const Trace* refTrace) {
* @brief tst_Cosimulate::generateTrace
* Executes program @p spProgram on the single-cycle processor model, to generate a reference trace.
*/
-Trace tst_Cosimulate::generateReferenceTrace(const QStringList& extensions) {
+const Trace& tst_Cosimulate::generateReferenceTrace(const QStringList& extensions) {
+ auto refTrace = m_referenceTraces.find(m_currentTest.filepath);
+ if (refTrace == m_referenceTraces.end()) {
+ std::cout << "First time running test; generating reference trace..." << std::endl;
ProcessorHandler::get()->selectProcessor(s_referenceModel, extensions);
Trace trace;
executeSimulator(trace);
- return trace;
+ m_referenceTraces[m_currentTest.filepath] = trace;
+ refTrace = m_referenceTraces.find(m_currentTest.filepath);
+ }
+ return refTrace->second;
}
/**
@@ -189,13 +257,14 @@ void tst_Cosimulate::cosimulate(const ProcessorID& id, const QStringList& extens
// reached.
ProcessorHandler::get()->getProcessorNonConst()->handleSysCall.Connect(this, &tst_Cosimulate::handleSysCall);
- std::cout << "Generating reference trace..." << std::endl;
+ std::cout << test.filepath.toStdString() << std::endl;
auto referenceTrace = generateReferenceTrace(extensions);
ProcessorHandler::get()->selectProcessor(id, extensions);
Trace trace;
- std::cout << "Cosimulating..." << std::endl;
+ std::cout << "Cosimulating... " << std::flush;
executeSimulator(trace, &referenceTrace);
+ std::cout << "PASS!\n" << std::endl;
}
}

Elaborate generated error info in tst_cosimulate