Adding verification for tests
This commit is contained in:
parent
147d0d4f52
commit
1952f8a622
|
@ -35,7 +35,7 @@ quickerNESCoreSrc = [
|
||||||
'source/core/Multi_Buffer.cpp',
|
'source/core/Multi_Buffer.cpp',
|
||||||
'source/core/Nes_Emu.cpp',
|
'source/core/Nes_Emu.cpp',
|
||||||
'source/core/nes_ntsc.cpp',
|
'source/core/nes_ntsc.cpp',
|
||||||
'source/core/Nes_Vrc7.cpp'
|
'source/core/Nes_Vrc7.cpp',
|
||||||
]
|
]
|
||||||
|
|
||||||
# quickerNES Core Configuration
|
# quickerNES Core Configuration
|
||||||
|
@ -43,7 +43,7 @@ quickerNESCoreSrc = [
|
||||||
quickerNESCoreDependency = declare_dependency(
|
quickerNESCoreDependency = declare_dependency(
|
||||||
compile_args : [ '-Wfatal-errors','-Wall' ],
|
compile_args : [ '-Wfatal-errors','-Wall' ],
|
||||||
include_directories : include_directories(['source', 'source/core', 'extern']),
|
include_directories : include_directories(['source', 'source/core', 'extern']),
|
||||||
sources : quickerNESCoreSrc,
|
sources : [ quickerNESCoreSrc, 'extern/metrohash128/metrohash128.cpp' ]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Building playback tool
|
# Building playback tool
|
||||||
|
@ -53,7 +53,7 @@ quickerNESPlayerSrc = [
|
||||||
'extern/hqn/hqn_gui_controller.cpp',
|
'extern/hqn/hqn_gui_controller.cpp',
|
||||||
'extern/hqn/hqn_surface.cpp',
|
'extern/hqn/hqn_surface.cpp',
|
||||||
'extern/hqn/hqn_util.cpp',
|
'extern/hqn/hqn_util.cpp',
|
||||||
'extern/hqn/options.cpp'
|
'extern/hqn/options.cpp',
|
||||||
]
|
]
|
||||||
|
|
||||||
executable('player',
|
executable('player',
|
||||||
|
|
|
@ -12,12 +12,6 @@
|
||||||
#include "Nes_Core.h"
|
#include "Nes_Core.h"
|
||||||
class Nes_State;
|
class Nes_State;
|
||||||
|
|
||||||
// Register optional mappers included with Nes_Emu
|
|
||||||
void register_optional_mappers();
|
|
||||||
void register_extra_mappers();
|
|
||||||
|
|
||||||
extern const char unsupported_mapper []; // returned when cartridge uses unsupported mapper
|
|
||||||
|
|
||||||
class Nes_Emu {
|
class Nes_Emu {
|
||||||
public:
|
public:
|
||||||
Nes_Emu();
|
Nes_Emu();
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
#define _LOW_MEM_SIZE 0x800
|
#define _LOW_MEM_SIZE 0x800
|
||||||
#define _HIGH_MEM_SIZE 0x2000
|
#define _HIGH_MEM_SIZE 0x2000
|
||||||
|
#define _NAMETABLES_MEM_SIZE 0x1000
|
||||||
|
|
||||||
class EmuInstance
|
class EmuInstance
|
||||||
{
|
{
|
||||||
|
@ -49,8 +50,11 @@ class EmuInstance
|
||||||
uint8_t* getLowMem() { return _nes->low_mem(); };
|
uint8_t* getLowMem() { return _nes->low_mem(); };
|
||||||
uint8_t* getNametableMem() { return _nes->nametable_mem(); };
|
uint8_t* getNametableMem() { return _nes->nametable_mem(); };
|
||||||
uint8_t* getHighMem() { return _nes->high_mem();};
|
uint8_t* getHighMem() { return _nes->high_mem();};
|
||||||
|
const uint8_t* getChrMem() { return _nes->chr_mem();};
|
||||||
|
size_t getChrMemSize() { return _nes->chr_size();};
|
||||||
|
|
||||||
const std::string getRomSHA1() const { return _romSHA1String; };
|
const std::string getRomSHA1() const { return _romSHA1String; };
|
||||||
|
|
||||||
void loadStateFile(const std::string& stateFilePath)
|
void loadStateFile(const std::string& stateFilePath)
|
||||||
{
|
{
|
||||||
// Loading state data
|
// Loading state data
|
||||||
|
@ -70,6 +74,23 @@ class EmuInstance
|
||||||
|
|
||||||
inline size_t getStateSize() const { return _stateSize; }
|
inline size_t getStateSize() const { return _stateSize; }
|
||||||
|
|
||||||
|
inline hash_t getStateHash()
|
||||||
|
{
|
||||||
|
MetroHash128 hash;
|
||||||
|
|
||||||
|
uint8_t stateData[_stateSize];
|
||||||
|
serializeState(stateData);
|
||||||
|
|
||||||
|
hash.Update(getLowMem(), _LOW_MEM_SIZE);
|
||||||
|
hash.Update(getHighMem(), _HIGH_MEM_SIZE);
|
||||||
|
hash.Update(getNametableMem(), _NAMETABLES_MEM_SIZE);
|
||||||
|
hash.Update(getChrMem(), getChrMemSize());
|
||||||
|
|
||||||
|
hash_t result;
|
||||||
|
hash.Finalize(reinterpret_cast<uint8_t *>(&result));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
void saveStateFile(const std::string& stateFilePath) const
|
void saveStateFile(const std::string& stateFilePath) const
|
||||||
{
|
{
|
||||||
std::string stateData;
|
std::string stateData;
|
||||||
|
|
|
@ -16,6 +16,7 @@ struct stepData_t
|
||||||
std::string input;
|
std::string input;
|
||||||
uint8_t* stateData;
|
uint8_t* stateData;
|
||||||
int32_t curBlit[BLIT_SIZE];
|
int32_t curBlit[BLIT_SIZE];
|
||||||
|
hash_t hash;
|
||||||
};
|
};
|
||||||
|
|
||||||
class PlaybackInstance
|
class PlaybackInstance
|
||||||
|
@ -28,6 +29,7 @@ class PlaybackInstance
|
||||||
step.input = input;
|
step.input = input;
|
||||||
step.stateData = (uint8_t*) malloc(_emu->getStateSize());
|
step.stateData = (uint8_t*) malloc(_emu->getStateSize());
|
||||||
_emu->serializeState(step.stateData);
|
_emu->serializeState(step.stateData);
|
||||||
|
step.hash = _emu->getStateHash();
|
||||||
saveBlit(_emu->getInternalEmulator(), step.curBlit, hqn::HQNState::NES_VIDEO_PALETTE, 0, 0, 0, 0);
|
saveBlit(_emu->getInternalEmulator(), step.curBlit, hqn::HQNState::NES_VIDEO_PALETTE, 0, 0, 0, 0);
|
||||||
|
|
||||||
// Adding the step into the sequence
|
// Adding the step into the sequence
|
||||||
|
@ -179,6 +181,30 @@ class PlaybackInstance
|
||||||
return step.stateData;
|
return step.stateData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hash_t getStateHash(const size_t stepId) const
|
||||||
|
{
|
||||||
|
// Checking the required step id does not exceed contents of the sequence
|
||||||
|
if (stepId > _stepSequence.size()) EXIT_WITH_ERROR("[Error] Attempting to render a step larger than the step sequence");
|
||||||
|
|
||||||
|
// Getting step information
|
||||||
|
const auto& step = _stepSequence[stepId];
|
||||||
|
|
||||||
|
// Returning step input
|
||||||
|
return step.hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string getStateInput(const size_t stepId) const
|
||||||
|
{
|
||||||
|
// Checking the required step id does not exceed contents of the sequence
|
||||||
|
if (stepId > _stepSequence.size()) EXIT_WITH_ERROR("[Error] Attempting to render a step larger than the step sequence");
|
||||||
|
|
||||||
|
// Getting step information
|
||||||
|
const auto& step = _stepSequence[stepId];
|
||||||
|
|
||||||
|
// Returning step input
|
||||||
|
return step.input;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
|
|
@ -72,14 +72,19 @@ int main(int argc, char *argv[])
|
||||||
// Creating emulator instance
|
// Creating emulator instance
|
||||||
auto e = EmuInstance(romFilePath, stateFilePath);
|
auto e = EmuInstance(romFilePath, stateFilePath);
|
||||||
|
|
||||||
|
auto stateHash = e.getStateHash();
|
||||||
|
|
||||||
// Creating playback instance
|
// Creating playback instance
|
||||||
auto p = PlaybackInstance(&e, sequence);
|
auto p = PlaybackInstance(&e, sequence);
|
||||||
|
|
||||||
|
// Getting state size
|
||||||
|
auto stateSize = e.getStateSize();
|
||||||
|
|
||||||
// Flag to continue running playback
|
// Flag to continue running playback
|
||||||
bool continueRunning = true;
|
bool continueRunning = true;
|
||||||
|
|
||||||
// Variable for current step in view
|
// Variable for current step in view
|
||||||
ssize_t sequenceLength = sequence.size();
|
ssize_t sequenceLength = p.getSequenceLength();
|
||||||
ssize_t currentStep = 0;
|
ssize_t currentStep = 0;
|
||||||
|
|
||||||
// Flag to display frame information
|
// Flag to display frame information
|
||||||
|
@ -92,10 +97,13 @@ int main(int argc, char *argv[])
|
||||||
if (disableRender == false) p.renderFrame(currentStep);
|
if (disableRender == false) p.renderFrame(currentStep);
|
||||||
|
|
||||||
// Getting input
|
// Getting input
|
||||||
const auto& input = sequence[currentStep];
|
const auto& input = p.getStateInput(currentStep);
|
||||||
|
|
||||||
|
// Getting state hash
|
||||||
|
const auto hash = p.getStateHash(currentStep);
|
||||||
|
|
||||||
// Getting state data
|
// Getting state data
|
||||||
//const auto stateData = p.getStateData(currentStep);
|
const auto stateData = p.getStateData(currentStep);
|
||||||
|
|
||||||
// Printing data and commands
|
// Printing data and commands
|
||||||
if (showFrameInfo)
|
if (showFrameInfo)
|
||||||
|
@ -104,7 +112,8 @@ int main(int argc, char *argv[])
|
||||||
|
|
||||||
printw("[] ----------------------------------------------------------------\n");
|
printw("[] ----------------------------------------------------------------\n");
|
||||||
printw("[] Current Step #: %lu / %lu\n", currentStep + 1, sequenceLength);
|
printw("[] Current Step #: %lu / %lu\n", currentStep + 1, sequenceLength);
|
||||||
printw("[] Input: %s\n", input.c_str());
|
printw("[] Input: %s\n", input.c_str());
|
||||||
|
printw("[] State Hash: 0x%lX%lX\n", hash.first, hash.second);
|
||||||
|
|
||||||
// Only print commands if not in reproduce mode
|
// Only print commands if not in reproduce mode
|
||||||
if (isReproduce == false) printw("[] Commands: n: -1 m: +1 | h: -10 | j: +10 | y: -100 | u: +100 | k: -1000 | i: +1000 | s: quicksave | p: play | q: quit\n");
|
if (isReproduce == false) printw("[] Commands: n: -1 m: +1 | h: -10 | j: +10 | y: -100 | u: +100 | k: -1000 | i: +1000 | s: quicksave | p: play | q: quit\n");
|
||||||
|
@ -137,7 +146,11 @@ int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
// Storing state file
|
// Storing state file
|
||||||
std::string saveFileName = "quicksave.state";
|
std::string saveFileName = "quicksave.state";
|
||||||
e.saveStateFile(saveFileName);
|
|
||||||
|
std::string saveData;
|
||||||
|
saveData.resize(stateSize);
|
||||||
|
memcpy(saveData.data(), stateData, stateSize);
|
||||||
|
if (saveStringToFile(saveData, saveFileName.c_str()) == false) EXIT_WITH_ERROR("[ERROR] Could not save state file: %s\n", saveFileName.c_str());
|
||||||
printw("[] Saved state to %s\n", saveFileName.c_str());
|
printw("[] Saved state to %s\n", saveFileName.c_str());
|
||||||
|
|
||||||
// Do no show frame info again after this action
|
// Do no show frame info again after this action
|
||||||
|
|
|
@ -39,6 +39,11 @@ int main(int argc, char *argv[])
|
||||||
if (scriptJson["Initial State File"].is_string() == false) EXIT_WITH_ERROR("Script file 'Initial State File' entry is not a string\n");
|
if (scriptJson["Initial State File"].is_string() == false) EXIT_WITH_ERROR("Script file 'Initial State File' entry is not a string\n");
|
||||||
std::string initialStateFilePath = scriptJson["Initial State File"].get<std::string>();
|
std::string initialStateFilePath = scriptJson["Initial State File"].get<std::string>();
|
||||||
|
|
||||||
|
// Getting verification state file path
|
||||||
|
if (scriptJson.contains("Verification State File") == false) EXIT_WITH_ERROR("Script file missing 'Verification State File' entry\n");
|
||||||
|
if (scriptJson["Verification State File"].is_string() == false) EXIT_WITH_ERROR("Script file 'Verification State File' entry is not a string\n");
|
||||||
|
std::string verificationStateFilePath = scriptJson["Verification State File"].get<std::string>();
|
||||||
|
|
||||||
// Getting sequence file path
|
// Getting sequence file path
|
||||||
if (scriptJson.contains("Sequence File") == false) EXIT_WITH_ERROR("Script file missing 'Sequence File' entry\n");
|
if (scriptJson.contains("Sequence File") == false) EXIT_WITH_ERROR("Script file missing 'Sequence File' entry\n");
|
||||||
if (scriptJson["Sequence File"].is_string() == false) EXIT_WITH_ERROR("Script file 'Sequence File' entry is not a string\n");
|
if (scriptJson["Sequence File"].is_string() == false) EXIT_WITH_ERROR("Script file 'Sequence File' entry is not a string\n");
|
||||||
|
@ -51,6 +56,12 @@ int main(int argc, char *argv[])
|
||||||
|
|
||||||
// Creating emulator instance
|
// Creating emulator instance
|
||||||
auto e = EmuInstance(romFilePath, initialStateFilePath);
|
auto e = EmuInstance(romFilePath, initialStateFilePath);
|
||||||
|
|
||||||
|
auto initialHash = e.getStateHash();
|
||||||
|
printf("[] Initial State Hash: 0x%lX%lX\n", initialHash.first, initialHash.second);
|
||||||
|
|
||||||
|
// Getting state size
|
||||||
|
const auto stateSize = e.getStateSize();
|
||||||
|
|
||||||
// Getting actual ROM SHA1
|
// Getting actual ROM SHA1
|
||||||
auto romSHA1 = e.getRomSHA1();
|
auto romSHA1 = e.getRomSHA1();
|
||||||
|
@ -58,9 +69,21 @@ int main(int argc, char *argv[])
|
||||||
// Checking with the expected SHA1 hash
|
// Checking with the expected SHA1 hash
|
||||||
if (romSHA1 != expectedROMSHA1) EXIT_WITH_ERROR("Wrong ROM SHA1. Found: '%s', Expected: '%s'\n", romSHA1.c_str(), expectedROMSHA1.c_str());
|
if (romSHA1 != expectedROMSHA1) EXIT_WITH_ERROR("Wrong ROM SHA1. Found: '%s', Expected: '%s'\n", romSHA1.c_str(), expectedROMSHA1.c_str());
|
||||||
|
|
||||||
|
// Loading state file for verication, if provided
|
||||||
|
std::string verificationState;
|
||||||
|
if (verificationStateFilePath != "")
|
||||||
|
{
|
||||||
|
// Loading file
|
||||||
|
if (loadStringFromFile(verificationState, verificationStateFilePath) == false)
|
||||||
|
EXIT_WITH_ERROR("[ERROR] Could not find or read from verification state file: %s\n", verificationStateFilePath.c_str());
|
||||||
|
|
||||||
|
// Checking size
|
||||||
|
if (verificationState.length() != stateSize) EXIT_WITH_ERROR("[ERROR] The provided verification state has different size from expected: %lu vs %lu\n", verificationState.length(), stateSize);
|
||||||
|
}
|
||||||
|
|
||||||
// Loading sequence file
|
// Loading sequence file
|
||||||
std::string sequenceRaw;
|
std::string sequenceRaw;
|
||||||
if (loadStringFromFile(sequenceRaw, sequenceFilePath.c_str()) == false) EXIT_WITH_ERROR("[ERROR] Could not find or read from input sequence file: %s\n", sequenceFilePath.c_str());
|
if (loadStringFromFile(sequenceRaw, sequenceFilePath) == false) EXIT_WITH_ERROR("[ERROR] Could not find or read from input sequence file: %s\n", sequenceFilePath.c_str());
|
||||||
|
|
||||||
// Building sequence information
|
// Building sequence information
|
||||||
const auto sequence = split(sequenceRaw, ' ');
|
const auto sequence = split(sequenceRaw, ' ');
|
||||||
|
@ -69,11 +92,14 @@ int main(int argc, char *argv[])
|
||||||
const auto sequenceLength = sequence.size();
|
const auto sequenceLength = sequence.size();
|
||||||
|
|
||||||
// Printing test information
|
// Printing test information
|
||||||
printf("[] Running Script: '%s'\n", scriptFilePath.c_str());
|
printf("[] Running Script: '%s'\n", scriptFilePath.c_str());
|
||||||
printf("[] ROM File: '%s'\n", romFilePath.c_str());
|
printf("[] ROM File: '%s'\n", romFilePath.c_str());
|
||||||
printf("[] ROM SHA1: '%s'\n", romSHA1.c_str());
|
printf("[] ROM SHA1: '%s'\n", romSHA1.c_str());
|
||||||
printf("[] Sequence File: '%s'\n", sequenceFilePath.c_str());
|
printf("[] Verification State File: '%s'\n", verificationStateFilePath.c_str());
|
||||||
printf("[] Sequence Length: %lu\n", sequenceLength);
|
printf("[] Sequence File: '%s'\n", sequenceFilePath.c_str());
|
||||||
|
printf("[] Sequence Length: %lu\n", sequenceLength);
|
||||||
|
printf("[] State Size: %lu bytes\n", stateSize);
|
||||||
|
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
|
|
||||||
// Actually running the sequence
|
// Actually running the sequence
|
||||||
|
@ -85,10 +111,33 @@ int main(int argc, char *argv[])
|
||||||
auto dt = std::chrono::duration_cast<std::chrono::nanoseconds>(tf - t0).count();
|
auto dt = std::chrono::duration_cast<std::chrono::nanoseconds>(tf - t0).count();
|
||||||
double elapsedTimeSeconds = (double)dt * 1.0e-9;
|
double elapsedTimeSeconds = (double)dt * 1.0e-9;
|
||||||
|
|
||||||
// Printing time information
|
// Calculating final state hash
|
||||||
printf("[] Elapsed time: %3.3fs\n", (double)dt * 1.0e-9);
|
const auto finalStateHash = e.getStateHash();
|
||||||
printf("[] Performance: %.3f steps / s\n", (double)sequenceLength / elapsedTimeSeconds);
|
|
||||||
|
|
||||||
return 0;
|
// If provided, verify result against the passed verification movie
|
||||||
|
bool verificationPassed = true;
|
||||||
|
hash_t verificationStateHash;
|
||||||
|
if (verificationStateFilePath != "")
|
||||||
|
{
|
||||||
|
// Loading verification state into the emulator
|
||||||
|
e.deserializeState((uint8_t*)verificationState.data());
|
||||||
|
|
||||||
|
// Calculating hash for the verification state
|
||||||
|
verificationStateHash = e.getStateHash();
|
||||||
|
|
||||||
|
// Comparing hashes
|
||||||
|
if (verificationStateHash != finalStateHash) verificationPassed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Printing time information
|
||||||
|
printf("[] -----------------------------------------\n");
|
||||||
|
printf("[] Elapsed time: %3.3fs\n", (double)dt * 1.0e-9);
|
||||||
|
printf("[] Performance: %.3f steps / s\n", (double)sequenceLength / elapsedTimeSeconds);
|
||||||
|
printf("[] Final State Hash: 0x%lX%lX\n", finalStateHash.first, finalStateHash.second);
|
||||||
|
if (verificationStateFilePath != "")
|
||||||
|
printf("[] Verification Hash: 0x%lX%lX (%s)\n", verificationStateHash.first, verificationStateHash.second, verificationPassed ? "Passed" : "Failed");
|
||||||
|
|
||||||
|
// Fail if verification didn't pass
|
||||||
|
return verificationPassed ? 0 : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <metrohash128/metrohash128.h>
|
||||||
|
|
||||||
// If we use NCurses, we need to use the appropriate printing function
|
// If we use NCurses, we need to use the appropriate printing function
|
||||||
#ifdef NCURSES
|
#ifdef NCURSES
|
||||||
|
@ -85,6 +86,17 @@ void refreshTerminal()
|
||||||
|
|
||||||
#endif // NCURSES
|
#endif // NCURSES
|
||||||
|
|
||||||
|
|
||||||
|
typedef _uint128_t hash_t;
|
||||||
|
inline hash_t calculateMetroHash(uint8_t* data, size_t size)
|
||||||
|
{
|
||||||
|
MetroHash128 hash;
|
||||||
|
hash.Update(data, size);
|
||||||
|
hash_t result;
|
||||||
|
hash.Finalize(reinterpret_cast<uint8_t *>(&result));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// Function to split a string into a sub-strings delimited by a character
|
// Function to split a string into a sub-strings delimited by a character
|
||||||
// Taken from stack overflow answer to https://stackoverflow.com/questions/236129/how-do-i-iterate-over-the-words-of-a-string
|
// Taken from stack overflow answer to https://stackoverflow.com/questions/236129/how-do-i-iterate-over-the-words-of-a-string
|
||||||
// By Evan Teran
|
// By Evan Teran
|
||||||
|
@ -137,9 +149,9 @@ inline void exitWithError [[noreturn]] (const char *fileName, const int lineNum
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loads a string from a given file
|
// Loads a string from a given file
|
||||||
inline bool loadStringFromFile(std::string &dst, const char *fileName)
|
inline bool loadStringFromFile(std::string &dst, std::string path)
|
||||||
{
|
{
|
||||||
std::ifstream fi(fileName);
|
std::ifstream fi(path);
|
||||||
|
|
||||||
// If file not found or open, return false
|
// If file not found or open, return false
|
||||||
if (fi.good() == false) return false;
|
if (fi.good() == false) return false;
|
||||||
|
|
Binary file not shown.
|
@ -2,6 +2,6 @@
|
||||||
"Rom File": "Castlevania (U) (PRG0) [!].nes",
|
"Rom File": "Castlevania (U) (PRG0) [!].nes",
|
||||||
"Expected ROM SHA1": "A31B8BD5B370A9103343C866F3C2B2998E889341",
|
"Expected ROM SHA1": "A31B8BD5B370A9103343C866F3C2B2998E889341",
|
||||||
"Initial State File": "",
|
"Initial State File": "",
|
||||||
"Final State File": "anyPercent.final.state",
|
"Verification State File": "anyPercent.final.state",
|
||||||
"Sequence File": "anyPercent.sol"
|
"Sequence File": "anyPercent.sol"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue