mirror of
https://github.com/ApfelTeeSaft/WebMetal.git
synced 2026-08-27 03:53:24 +00:00
212 lines
7.5 KiB
C++
212 lines
7.5 KiB
C++
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
|
|
#include "doctest.h"
|
|
|
|
#include <fstream>
|
|
#include <sstream>
|
|
|
|
#include <nlohmann/json.hpp>
|
|
|
|
#include "webmetal/engine.h"
|
|
|
|
using nlohmann::json;
|
|
using webmetal::Engine;
|
|
|
|
#ifndef WEBMETAL_CONFORMANCE_PATH
|
|
#error "WEBMETAL_CONFORMANCE_PATH must be defined by the build"
|
|
#endif
|
|
|
|
namespace {
|
|
|
|
json loadVectorFile() {
|
|
std::ifstream in(WEBMETAL_CONFORMANCE_PATH);
|
|
REQUIRE_MESSAGE(in.good(), "cannot open " WEBMETAL_CONFORMANCE_PATH);
|
|
std::stringstream buffer;
|
|
buffer << in.rdbuf();
|
|
json j = json::parse(buffer.str(), nullptr, false);
|
|
REQUIRE_FALSE(j.is_discarded());
|
|
return j;
|
|
}
|
|
|
|
struct Layout {
|
|
std::uint32_t pc = 0;
|
|
std::uint32_t status = 1;
|
|
std::uint32_t flagsOffset = 4;
|
|
std::vector<std::string> flagNames;
|
|
struct Entry {
|
|
std::uint32_t offset = 0;
|
|
std::uint32_t count = 0;
|
|
};
|
|
std::map<std::string, Entry> banks;
|
|
std::map<std::string, Entry> memories;
|
|
};
|
|
|
|
Layout parseLayout(const std::string& text) {
|
|
const json j = json::parse(text, nullptr, false);
|
|
REQUIRE_FALSE(j.is_discarded());
|
|
Layout layout;
|
|
layout.pc = j["pc"].get<std::uint32_t>();
|
|
layout.status = j["status"].get<std::uint32_t>();
|
|
layout.flagsOffset = j["flags"]["offset"].get<std::uint32_t>();
|
|
for (const auto& name : j["flags"]["names"]) {
|
|
layout.flagNames.push_back(name.get<std::string>());
|
|
}
|
|
for (const auto& b : j["banks"]) {
|
|
layout.banks[b["name"].get<std::string>()] = {
|
|
b["offset"].get<std::uint32_t>(), b["count"].get<std::uint32_t>()};
|
|
}
|
|
for (const auto& m : j["memories"]) {
|
|
layout.memories[m["name"].get<std::string>()] = {
|
|
m["offset"].get<std::uint32_t>(), m["size"].get<std::uint32_t>()};
|
|
}
|
|
return layout;
|
|
}
|
|
|
|
int flagIndexOf(const Layout& layout, const std::string& name) {
|
|
for (std::size_t i = 0; i < layout.flagNames.size(); i++) {
|
|
if (layout.flagNames[i] == name) return static_cast<int>(i);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void applySetup(Engine& engine, const Layout& layout, const json& setup) {
|
|
auto* state = reinterpret_cast<std::uint32_t*>(engine.stateBufferPtr());
|
|
if (setup.contains("banks")) {
|
|
for (const auto& [name, values] : setup["banks"].items()) {
|
|
REQUIRE(layout.banks.count(name) == 1);
|
|
const auto entry = layout.banks.at(name);
|
|
std::uint32_t i = 0;
|
|
for (const auto& v : values) {
|
|
REQUIRE(i < entry.count);
|
|
state[entry.offset + i++] = v.get<std::uint32_t>();
|
|
}
|
|
}
|
|
}
|
|
if (setup.contains("memories")) {
|
|
for (const auto& [name, cells] : setup["memories"].items()) {
|
|
REQUIRE(layout.memories.count(name) == 1);
|
|
const auto entry = layout.memories.at(name);
|
|
for (const auto& [addr, v] : cells.items()) {
|
|
state[entry.offset + std::stoul(addr)] = v.get<std::uint32_t>();
|
|
}
|
|
}
|
|
}
|
|
if (setup.contains("flags")) {
|
|
for (const auto& [name, v] : setup["flags"].items()) {
|
|
const int index = flagIndexOf(layout, name);
|
|
REQUIRE(index >= 0);
|
|
state[layout.flagsOffset + index] = v.get<bool>() ? 1 : 0;
|
|
}
|
|
}
|
|
if (setup.contains("pc")) {
|
|
state[layout.pc] = setup["pc"].get<std::uint32_t>();
|
|
}
|
|
}
|
|
|
|
std::vector<std::string> compareExpect(const Engine& engine,
|
|
const Layout& layout,
|
|
const json& expect) {
|
|
std::vector<std::string> problems;
|
|
const auto* state =
|
|
reinterpret_cast<const std::uint32_t*>(engine.stateBufferPtr());
|
|
const auto mismatch = [&](const std::string& what, std::uint32_t got,
|
|
std::uint32_t want) {
|
|
problems.push_back(what + " = " + std::to_string(got) + ", want " +
|
|
std::to_string(want));
|
|
};
|
|
if (expect.contains("banks")) {
|
|
for (const auto& [name, values] : expect["banks"].items()) {
|
|
const auto entry = layout.banks.at(name);
|
|
std::uint32_t i = 0;
|
|
for (const auto& v : values) {
|
|
const auto want = v.get<std::uint32_t>();
|
|
const auto got = state[entry.offset + i];
|
|
if (got != want) {
|
|
mismatch(name + "[" + std::to_string(i) + "]", got, want);
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
if (expect.contains("memories")) {
|
|
for (const auto& [name, cells] : expect["memories"].items()) {
|
|
const auto entry = layout.memories.at(name);
|
|
for (const auto& [addr, v] : cells.items()) {
|
|
const auto want = v.get<std::uint32_t>();
|
|
const auto got = state[entry.offset + std::stoul(addr)];
|
|
if (got != want) mismatch(name + "[" + addr + "]", got, want);
|
|
}
|
|
}
|
|
}
|
|
if (expect.contains("flags")) {
|
|
for (const auto& [name, v] : expect["flags"].items()) {
|
|
const int index = flagIndexOf(layout, name);
|
|
const bool want = v.get<bool>();
|
|
const bool got = state[layout.flagsOffset + index] != 0;
|
|
if (got != want) {
|
|
problems.push_back("flag " + name + " = " +
|
|
(got ? "true" : "false"));
|
|
}
|
|
}
|
|
}
|
|
if (expect.contains("pc")) {
|
|
const auto want = expect["pc"].get<std::uint32_t>();
|
|
if (state[layout.pc] != want) mismatch("pc", state[layout.pc], want);
|
|
}
|
|
if (expect.contains("halted")) {
|
|
const bool want = expect["halted"].get<bool>();
|
|
const bool got = (state[layout.status] & 1u) != 0;
|
|
if (got != want) {
|
|
problems.push_back(std::string("halted = ") +
|
|
(got ? "true" : "false"));
|
|
}
|
|
}
|
|
return problems;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("C++ engine passes every shared conformance vector") {
|
|
const json file = loadVectorFile();
|
|
const json defaultModel = file["defaultModel"];
|
|
|
|
for (const auto& vector : file["vectors"]) {
|
|
const std::string name = vector["name"].get<std::string>();
|
|
CAPTURE(name);
|
|
|
|
Engine engine;
|
|
const json model =
|
|
vector.contains("model") ? vector["model"] : defaultModel;
|
|
REQUIRE_MESSAGE(engine.loadModel(model.dump()), name);
|
|
const Layout layout = parseLayout(engine.stateLayout());
|
|
|
|
if (vector.contains("setup")) {
|
|
applySetup(engine, layout, vector["setup"]);
|
|
}
|
|
|
|
std::string error;
|
|
for (const auto& step : vector["run"]) {
|
|
const std::string operands =
|
|
step.contains("operands") ? step["operands"].dump() : "{}";
|
|
error = engine.execSequence(step["microOps"].dump(), operands);
|
|
if (!error.empty()) break;
|
|
}
|
|
|
|
if (vector.contains("expectError")) {
|
|
const auto substring = vector["expectError"].get<std::string>();
|
|
CHECK_MESSAGE(!error.empty(), name, ": expected an error");
|
|
CHECK_MESSAGE(error.find(substring) != std::string::npos, name,
|
|
": error \"", error, "\" lacks \"", substring, "\"");
|
|
continue;
|
|
}
|
|
|
|
CHECK_MESSAGE(error.empty(), name, ": unexpected error: ", error);
|
|
if (vector.contains("expect")) {
|
|
const auto problems =
|
|
compareExpect(engine, layout, vector["expect"]);
|
|
for (const auto& problem : problems) {
|
|
FAIL_CHECK(name << ": " << problem);
|
|
}
|
|
}
|
|
}
|
|
}
|