Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion tools/svdconv/test/integtests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
SET(SOURCE_FILES SvdConvIntegTestEnv.cpp SvdConvIntegTests.cpp SvdConvTestUtils.cpp SvdConvTestCmdLine.cpp)
SET(SOURCE_FILES SvdConvIntegTestEnv.cpp SvdConvIntegTests.cpp SvdConvTestUtils.cpp SvdConvTestCmdLine.cpp
SvdConvEnumHeaderTests.cpp)
SET(HEADER_FILES SvdConvIntegTestEnv.h SvdConvTestUtils.h)

list(TRANSFORM SOURCE_FILES PREPEND src/)
Expand Down
83 changes: 83 additions & 0 deletions tools/svdconv/test/integtests/src/SvdConvEnumHeaderTests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright (c) 2026 Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "SvdConvIntegTestEnv.h"
#include "SvdConvTestUtils.h"
#include "SVDConv.h"
#include "ErrLog.h"

#include <cstdint>
#include <initializer_list>
#include <regex>
#include <string>
#include <utility>

using namespace std;

namespace {

class SvdConvEnumHeaderTests : public testing::Test {
protected:
void SetUp() override {
ErrLog::Get()->ClearLogMessages();
}

void TearDown() override {
ErrLog::Get()->Save();
ErrLog::Get()->ClearLogMessages();
}

void GenerateHeader(const string& fixture, const string& name, string& header) {
const string input = SvdConvIntegTestEnv::localtestdata_dir + "/" + fixture;
const string output = SvdConvIntegTestEnv::testoutput_dir + "/enumHeader/" + name;
ASSERT_TRUE(RteFsUtils::Exists(input));
Arguments args("SVDConv.exe", input);
args.add({"-o", output, "--generate=header", "--fields=enum", "--create-folder"});
SvdConv svdConv;
ASSERT_EQ(0, svdConv.Check(args, args, nullptr));

for(const auto& message : ErrLog::Get()->GetLogMessages()) {
EXPECT_EQ(string::npos, message.find("M227")) << message;
}
ASSERT_TRUE(RteFsUtils::ReadFile(output + "/" + name + ".h", header));
ASSERT_FALSE(header.empty());
}

static void ExpectEnumeration(const string& header, const string& type,
initializer_list<pair<const char*, uint32_t>> values) {
const regex declaration(R"(typedef\s+enum\s*\{([^}]+)\}\s*)" + type + R"(\s*;)");
const auto types = SvdConvTestUtils::FindRegex(header, declaration);
ASSERT_EQ(1U, types.size()) << type;
const string body = types.front()[1].str();
for(const auto& [name, expected] : values) {
SCOPED_TRACE(name);
const regex assignment(string(R"(\b)") + name + R"(\s*=\s*(0[xX][0-9a-fA-F]+|[0-9]+)[uUlL]*\s*,)");
const auto entries = SvdConvTestUtils::FindRegex(body, assignment);
ASSERT_EQ(1U, entries.size());
EXPECT_EQ(expected, stoul(entries.front()[1].str(), nullptr, 0));
}
}
};

TEST_F(SvdConvEnumHeaderTests, FieldEnumsPreserveValuesWithoutBitOffsetShift) {
string header;
ASSERT_NO_FATAL_FAILURE(GenerateHeader("enumComboWidth/EnumComboWidth.svd", "EnumComboWidth", header));

ExpectEnumeration(header, "TEST_CTRL_SIX_BIT_Enum", {{"TEST_CTRL_SIX_BIT_ONE", 1U}});
// SEVEN_BIT starts at bit 8, but its enum value is the field value, not a register mask.
ExpectEnumeration(header, "TEST_CTRL_SEVEN_BIT_Enum", {{"TEST_CTRL_SEVEN_BIT_NINE", 9U}});
}

TEST_F(SvdConvEnumHeaderTests, ClusterAndDerivedRegisterEnumsHaveDistinctTypesAndValues) {
string header;
ASSERT_NO_FATAL_FAILURE(GenerateHeader("option_n/option_n.svd", "option_n", header));

ExpectEnumeration(header, "DCB_DSCSR_Clust_DSCSR_CDS_Enum",
{{"DCB_DSCSR_Clust_DSCSR_CDS_Disable", 0U}, {"DCB_DSCSR_Clust_DSCSR_CDS_Enable", 1U}});
ExpectEnumeration(header, "DCB_DSCSR_CDS_Enum",
{{"DCB_DSCSR_CDS_Disable", 0U}, {"DCB_DSCSR_CDS_Enable", 1U}});
}

} // namespace
81 changes: 81 additions & 0 deletions tools/svdconv/test/integtests/src/SvdConvIntegTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,87 @@ TEST_F(SvdConvIntegTests, CheckSfdGeneration) {
EXPECT_FALSE(sfd.empty());
}

TEST_F(SvdConvIntegTests, GeneratesMemoryMapsAtRequestedDetailLevel) {
const string inFile = SvdConvIntegTestEnv::localtestdata_dir + "/option_n/option_n.svd";
const string testOut = SvdConvIntegTestEnv::testoutput_dir + "/memoryMaps";
ASSERT_TRUE(RteFsUtils::Exists(inFile));

struct MapMode {
const char* option;
const char* suffix;
bool registers;
bool fields;
};
const MapMode modes[] = {
{"peripheralMap", "MapPeripherals", false, false},
{"registerMap", "MapRegisters", true, false},
{"fieldMap", "MapFields", true, true},
};
for(const auto& mode : modes) {
SCOPED_TRACE(mode.option);
Arguments args("SVDConv.exe", inFile);
args.add({"-o", testOut, string("--generate=") + mode.option, "--create-folder"});
SvdConv svdConv;
ASSERT_EQ(0, svdConv.Check(args, args, nullptr));

string listing;
ASSERT_TRUE(RteFsUtils::ReadFile(testOut + "/option_n_" + mode.suffix + ".txt", listing));
EXPECT_NE(string::npos, listing.find("Peripheral Map"));
for(const auto* address : {"0x40003000", "0x40004000", "0xe000ee08"}) {
EXPECT_NE(string::npos, listing.find(string("Base Address: ") + address));
}
EXPECT_NE(string::npos, listing.find("AddressBlock:"));
EXPECT_TRUE(regex_search(listing, regex(R"(000\s+---)")));
EXPECT_TRUE(regex_search(listing, regex(R"(001\s+---)")));
EXPECT_TRUE(regex_search(listing, regex(R"(002\s+IWDG\s+IWDG Interrupt)")));

// Derived peripherals and nested clusters must retain their resolved addresses.
for(const auto* pattern : {
R"(\bKR\b[^\n]*Address: 0x40003000,[^\n]*Access: wo)",
R"(\bKR\b[^\n]*Address: 0x40004000,[^\n]*Access: wo)",
R"(\bPR\b[^\n]*Address: 0x40003004,[^\n]*Offset: 0x00000004,[^\n]*Width: 4,[^\n]*Access: rw)",
R"(\bDSCSR_Clust\b[^\n]*Address: 0xe000ee08)",
R"(\bDSCSR\b[^\n]*Address: 0xe000ee08)",
R"(\bDSCSR\b[^\n]*Address: 0xe000ee10)"}) {
EXPECT_EQ(mode.registers, regex_search(listing, regex(pattern))) << pattern;
}
for(const auto* pattern : {
R"(\bKEY\b[^\n]*\[15 \.\.\. 0\][^\n]*Bits: 16)",
R"(\bCDS\b[^\n]*\[16 \.\.\. 16\][^\n]*Bits: 1)"}) {
EXPECT_EQ(mode.fields, regex_search(listing, regex(pattern))) << pattern;
}
}
}

TEST_F(SvdConvIntegTests, ExpandsRegisterAndFieldDimensionsInMemoryMap) {
const string inFile = SvdConvIntegTestEnv::localtestdata_dir + "/posMaskDim/PosMaskDim.svd";
const string testOut = SvdConvIntegTestEnv::testoutput_dir + "/dimensionMemoryMap";
ASSERT_TRUE(RteFsUtils::Exists(inFile));
Arguments args("SVDConv.exe", inFile);
args.add({"-o", testOut, "--generate=fieldMap", "--create-folder"});
SvdConv svdConv;
ASSERT_EQ(0, svdConv.Check(args, args, nullptr));

string listing;
ASSERT_TRUE(RteFsUtils::ReadFile(testOut + "/PosMaskDim_MapFields.txt", listing));
const regex registers(R"(\bDATA(\d+)\b[^\n]*Address: 0x([0-9a-f]+))");
unsigned count = 0;
for(auto it = sregex_iterator(listing.begin(), listing.end(), registers); it != sregex_iterator(); ++it) {
EXPECT_EQ(count, stoul((*it)[1].str()));
EXPECT_EQ(0x40000000UL + count * 4U, stoul((*it)[2].str(), nullptr, 16));
count++;
}
EXPECT_EQ(256U, count);
for(unsigned bit = 0; bit < 8; bit++) {
SCOPED_TRACE(bit);
const string value = to_string(bit);
const regex field("\\bPIN" + value + R"(\b[^\n]*\[ )" + value + R"( \.\.\. )" + value +
R"(\][^\n]*Bits: 1)");
const auto matches = SvdConvTestUtils::FindRegex(listing, field);
EXPECT_EQ(256U, matches.size());
}
}

#ifndef _WIN32
TEST_F(SvdConvIntegTests, CheckSfrUnsupportedPlatform) {
const string inFile = SvdConvIntegTestEnv::localtestdata_dir + "/option_n/option_n.svd";
Expand Down
11 changes: 10 additions & 1 deletion tools/svdconv/test/unittests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
set(TEST_SOURCE_FILES SvdUtilsTest.cpp GeneratorTest.cpp SvdInterruptTest.cpp)
set(TEST_SOURCE_FILES
SvdUtilsTest.cpp
SvdUtilsConversionTest.cpp
GeneratorTest.cpp
SvdInterruptTest.cpp
SvdCpuModelTest.cpp
SvdCExpressionParserTest.cpp
SvdOptionsTest.cpp
SvdModelValidationTest.cpp
)

list(TRANSFORM TEST_SOURCE_FILES PREPEND src/)
list(TRANSFORM TEST_HEADER_FILES PREPEND src/)
Expand Down
84 changes: 84 additions & 0 deletions tools/svdconv/test/unittests/src/SvdCExpressionParserTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright (c) 2026 Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "SvdCExpressionParser.h"

#include "gtest/gtest.h"
#include <string>
#include <utility>
#include <vector>

using namespace std;

namespace {

using Parser = SvdCExpressionParser;
using ExpectedToken = pair<const char*, Parser::XETYPE>;

TEST(SvdCExpressionParserTest, RecognizesOperatorsAndQualifiers) {
const vector<ExpectedToken> cases{
{".", Parser::xme_qual}, {"->", Parser::xme_qual}, {"-", Parser::xme_minus},
{"(", Parser::xme_openbr}, {")", Parser::xme_closebr},
{"[", Parser::xme_opendim}, {"]", Parser::xme_closedim},
{"+", Parser::xme_plus}, {"*", Parser::xme_mul}, {"/", Parser::xme_div},
{"^", Parser::xme_xor}, {"!", Parser::xme_not}, {"~", Parser::xme_compl},
{"<", Parser::xme_lt}, {"<<", Parser::xme_lsh}, {">>", Parser::xme_rsh},
{"&", Parser::xme_and}, {"&&", Parser::xme_land},
{"|", Parser::xme_or}, {"||", Parser::xme_lor},
{"=", Parser::xme_asn}, {"==", Parser::xme_equ},
};
for(const auto& [text, type] : cases) {
SCOPED_TRACE(text);
Parser parser(text);
ASSERT_TRUE(parser.Parse());
const auto& tokens = parser.GetTokenList();
ASSERT_EQ(1U, tokens.size());
EXPECT_EQ(type, tokens.front().type);
EXPECT_EQ(text, tokens.front().text);
}
}

TEST(SvdCExpressionParserTest, PreservesTokenOrderAndTextInRegisterExpression) {
Parser parser(" \t(GPIO1->CTRL.value & 0x10) == _state2[3]\r\n");
ASSERT_TRUE(parser.Parse());
const vector<ExpectedToken> expected{
{"(", Parser::xme_openbr}, {"GPIO1", Parser::xme_identi}, {"->", Parser::xme_qual},
{"CTRL", Parser::xme_identi}, {".", Parser::xme_qual}, {"value", Parser::xme_identi},
{"&", Parser::xme_and}, {"0x10", Parser::xme_const}, {")", Parser::xme_closebr},
{"==", Parser::xme_equ}, {"_state2", Parser::xme_identi}, {"[", Parser::xme_opendim},
{"3", Parser::xme_const}, {"]", Parser::xme_closedim},
};
const auto& tokens = parser.GetTokenList();
ASSERT_EQ(expected.size(), tokens.size());
auto actual = tokens.begin();
for(const auto& [text, type] : expected) {
SCOPED_TRACE(text);
EXPECT_EQ(type, actual->type);
EXPECT_EQ(text, actual->text);
++actual;
}
}

TEST(SvdCExpressionParserTest, HandlesEmptyInputAndTokensAtEndOfInput) {
for(const auto* text : {"", " \t\r\n"}) {
Parser parser(text);
ASSERT_TRUE(parser.Parse());
EXPECT_TRUE(parser.GetTokenList().empty());
}
const vector<ExpectedToken> cases{
{"REGISTER_2", Parser::xme_identi}, {"42", Parser::xme_const}, {"0XFF", Parser::xme_const},
};
for(const auto& [text, type] : cases) {
SCOPED_TRACE(text);
Parser parser(text);
ASSERT_TRUE(parser.Parse());
const auto& tokens = parser.GetTokenList();
ASSERT_EQ(1U, tokens.size());
EXPECT_EQ(type, tokens.front().type);
EXPECT_EQ(text, tokens.front().text);
}
}

} // namespace
Loading
Loading