/* * Copyright (c) 2020 ARM Limited * All rights reserved * * The license below extends only to copyright in the software and shall * not be construed as granting a license to any other intellectual * property including but not limited to intellectual property relating * to a hardware implementation of the functionality of the software * licensed hereunder. You may use the software subject to the license * terms below provided that you ensure that this notice is replicated * unmodified and in its entirety in all distributions of the software, * modified or unmodified, in source code or in binary form. * * Copyright 2020 Google, Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer; * redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution; * neither the name of the copyright holders nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma GCC diagnostic push // __GNUC__ defined for both clang and gcc // -Wdeprecated-copy has been added in clang10.0.0 and gcc9.0 #if defined(__GNUC__) # if (defined(__clang__) && __GNUC__ >= 10) || \ (!defined(__clang__) && __GNUC__ >= 9) # pragma GCC diagnostic ignored "-Wdeprecated-copy" # endif #endif #include #include #pragma GCC diagnostic pop #include #include "dev/reg_bank.hh" // Compare the elements of an array against expected values. using testing::ElementsAre; // This version is needed with enough elements, empirically more than 10. using testing::ElementsAreArray; /* * The RegisterRaz (read as zero) type. */ class RegisterRazTest : public testing::Test { protected: static constexpr size_t BufSize = 12; static constexpr size_t BufOffset = 4; static constexpr size_t RazSize = 4; std::array buf; RegisterBankLE::RegisterRaz raz; RegisterRazTest() : raz("raz", RazSize) { buf.fill(0xff); } }; // Needed by C++14 and lower constexpr size_t RegisterRazTest::RazSize; TEST_F(RegisterRazTest, Name) { EXPECT_EQ(raz.name(), "raz"); } TEST_F(RegisterRazTest, Size) { EXPECT_EQ(raz.size(), RazSize); } // Accessing the entire register at once. TEST_F(RegisterRazTest, FullAccess) { raz.write(buf.data() + BufOffset); raz.read(buf.data() + BufOffset); EXPECT_THAT(buf, ElementsAreArray({0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff})); } // Partial access, excluding the start of the register. TEST_F(RegisterRazTest, PartialAccessHigh) { raz.write(buf.data() + BufOffset, 1, 3); raz.read(buf.data() + BufOffset, 1, 3); EXPECT_THAT(buf, ElementsAreArray({0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff})); } // Partial access, excluding the end of the register. TEST_F(RegisterRazTest, PartialAccessLow) { raz.write(buf.data() + BufOffset, 0, 3); raz.read(buf.data() + BufOffset, 0, 3); EXPECT_THAT(buf, ElementsAreArray({0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff})); } // Partial access, excluding both ends of the register. TEST_F(RegisterRazTest, PartialAccessMid) { raz.write(buf.data() + BufOffset, 1, 2); raz.read(buf.data() + BufOffset, 1, 2); EXPECT_THAT(buf, ElementsAreArray({0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff})); } TEST_F(RegisterRazTest, Serialize) { std::ostringstream os; raz.serialize(os); EXPECT_EQ(os.str(), ""); } TEST_F(RegisterRazTest, Unserialize) { std::string s; EXPECT_TRUE(raz.unserialize(s)); } /* * The RegisterRao (read as one) type. */ class RegisterRaoTest : public testing::Test { protected: static constexpr size_t BufSize = 12; static constexpr size_t BufOffset = 4; static constexpr size_t RaoSize = 4; std::array buf; RegisterBankLE::RegisterRao rao; RegisterRaoTest() : rao("rao", RaoSize) { buf.fill(0x00); } }; // Needed by C++14 and lower constexpr size_t RegisterRaoTest::RaoSize; TEST_F(RegisterRaoTest, Name) { EXPECT_EQ(rao.name(), "rao"); } TEST_F(RegisterRaoTest, Size) { EXPECT_EQ(rao.size(), RaoSize); } // Accessing the entire register at once. TEST_F(RegisterRaoTest, FullAccess) { rao.write(buf.data() + BufOffset); rao.read(buf.data() + BufOffset); EXPECT_THAT(buf, ElementsAreArray({0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00})); } // Partial access, excluding the start of the register. TEST_F(RegisterRaoTest, PartialAccessHigh) { rao.write(buf.data() + BufOffset, 1, 3); rao.read(buf.data() + BufOffset, 1, 3); EXPECT_THAT(buf, ElementsAreArray({0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00})); } // Partial access, excluding the end of the register. TEST_F(RegisterRaoTest, PartialAccessLow) { rao.write(buf.data() + BufOffset, 0, 3); rao.read(buf.data() + BufOffset, 0, 3); EXPECT_THAT(buf, ElementsAreArray({0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00})); } // Partial access, excluding both ends of the register. TEST_F(RegisterRaoTest, PartialAccessMid) { rao.write(buf.data() + BufOffset, 1, 2); rao.read(buf.data() + BufOffset, 1, 2); EXPECT_THAT(buf, ElementsAreArray({0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); } TEST_F(RegisterRaoTest, Serialize) { std::ostringstream os; rao.serialize(os); EXPECT_EQ(os.str(), ""); } TEST_F(RegisterRaoTest, Unserialize) { std::string s; EXPECT_TRUE(rao.unserialize(s)); } /* * The RegisterBuf type. */ class RegisterBufTest : public testing::Test { protected: static constexpr size_t RegSize = 4; RegisterBankLE::RegisterBuf reg; std::array buf; std::array backing; public: RegisterBufTest() : reg("buf_reg", backing.data() + RegSize, RegSize), buf{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc}, backing{0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xa0, 0xb0, 0xc0} {} }; // Needed by C++14 and lower constexpr size_t RegisterBufTest::RegSize; TEST_F(RegisterBufTest, Name) { EXPECT_EQ(reg.name(), "buf_reg"); } TEST_F(RegisterBufTest, Size) { EXPECT_EQ(reg.size(), RegSize); } // Read the entire register. TEST_F(RegisterBufTest, FullRead) { reg.read(buf.data() + RegSize); EXPECT_THAT(buf, ElementsAreArray({0x1, 0x2, 0x3, 0x4, 0x50, 0x60, 0x70, 0x80, 0x9, 0xa, 0xb, 0xc})); EXPECT_THAT(backing, ElementsAreArray({0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xa0, 0xb0, 0xc0})); } // Write the entire register. TEST_F(RegisterBufTest, FullWrite) { reg.write(buf.data() + RegSize); EXPECT_THAT(buf, ElementsAreArray({0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc})); EXPECT_THAT(backing, ElementsAreArray({0x10, 0x20, 0x30, 0x40, 0x5, 0x6, 0x7, 0x8, 0x90, 0xa0, 0xb0, 0xc0})); } // Partial read, excluding the start of the register. TEST_F(RegisterBufTest, PartialReadHigh) { reg.read(buf.data() + RegSize, 1, 3); EXPECT_THAT(buf, ElementsAreArray({0x1, 0x2, 0x3, 0x4, 0x60, 0x70, 0x80, 0x8, 0x9, 0xa, 0xb, 0xc})); EXPECT_THAT(backing, ElementsAreArray({0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xa0, 0xb0, 0xc0})); } // Partial write, excluding the start of the register. TEST_F(RegisterBufTest, PartialWriteHigh) { reg.write(buf.data() + RegSize, 1, 3); EXPECT_THAT(buf, ElementsAreArray({0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc})); EXPECT_THAT(backing, ElementsAreArray({0x10, 0x20, 0x30, 0x40, 0x50, 0x5, 0x6, 0x7, 0x90, 0xa0, 0xb0, 0xc0})); } // Partial read, excluding the end of the register. TEST_F(RegisterBufTest, PartialReadLow) { reg.read(buf.data() + RegSize, 0, 3); EXPECT_THAT(buf, ElementsAreArray({0x1, 0x2, 0x3, 0x4, 0x50, 0x60, 0x70, 0x8, 0x9, 0xa, 0xb, 0xc})); EXPECT_THAT(backing, ElementsAreArray({0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xa0, 0xb0, 0xc0})); } // Partial write, excluding the end of the register. TEST_F(RegisterBufTest, PartialWriteLow) { reg.write(buf.data() + RegSize, 0, 3); EXPECT_THAT(buf, ElementsAreArray({0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc})); EXPECT_THAT(backing, ElementsAreArray({0x10, 0x20, 0x30, 0x40, 0x5, 0x6, 0x7, 0x80, 0x90, 0xa0, 0xb0, 0xc0})); } // Partial read, excluding both ends of the register. TEST_F(RegisterBufTest, PartialReadMid) { reg.read(buf.data() + RegSize, 1, 2); EXPECT_THAT(buf, ElementsAreArray({0x1, 0x2, 0x3, 0x4, 0x60, 0x70, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc})); EXPECT_THAT(backing, ElementsAreArray({0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xa0, 0xb0, 0xc0})); } // Partial write, excluding both ends of the register. TEST_F(RegisterBufTest, PartialWriteMid) { reg.write(buf.data() + RegSize, 1, 2); EXPECT_THAT(buf, ElementsAreArray({0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc})); EXPECT_THAT(backing, ElementsAreArray({0x10, 0x20, 0x30, 0x40, 0x50, 0x5, 0x6, 0x80, 0x90, 0xa0, 0xb0, 0xc0})); } TEST_F(RegisterBufTest, Serialize) { std::ostringstream os; reg.serialize(os); EXPECT_EQ(os.str(), ""); } TEST_F(RegisterBufTest, Unserialize) { std::string s; EXPECT_TRUE(reg.unserialize(s)); } /* * The RegisterLBuf type. Since it's so similar to RegisterBuf, just do a * basic check that it's applying it's locally managed buffer to it's parent * type. */ class RegisterLBufTest : public testing::Test { protected: static constexpr size_t RegSize = 12; RegisterBankLE::RegisterLBuf<12> reg; std::array to_write; public: RegisterLBufTest() : reg("lbuf_reg"), to_write{0x1, 0x2, 0x3, 0x4} { reg.buffer.fill(0xff); } }; TEST_F(RegisterLBufTest, Name) { EXPECT_EQ(reg.name(), "lbuf_reg"); } TEST_F(RegisterLBufTest, PartialWrite) { reg.write(to_write.data(), 4, 4); EXPECT_THAT(reg.buffer, ElementsAreArray({0xff, 0xff, 0xff, 0xff, 0x1, 0x2, 0x3, 0x4, 0xff, 0xff, 0xff, 0xff})); } TEST_F(RegisterLBufTest, Serialize) { std::ostringstream os; for (int i = 0; i < reg.buffer.size(); i++) reg.buffer[i] = i; reg.serialize(os); EXPECT_EQ(os.str(), "0 1 2 3 4 5 6 7 8 9 10 11"); } TEST_F(RegisterLBufTest, UnserializeSucess) { std::string s = "0 1 2 3 4 5 6 7 8 9 10 11"; EXPECT_TRUE(reg.unserialize(s)); EXPECT_THAT(reg.buffer, ElementsAreArray({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11})); } TEST_F(RegisterLBufTest, UnserializeFailure) { std::string s = "0 1 2 3 4 5 6 7 8 9 10"; EXPECT_FALSE(reg.unserialize(s)); EXPECT_THAT(reg.buffer, ElementsAreArray({0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff})); } /* * The templated Register<> type which takes a backing type and endianness * as template parameters. */ class TypedRegisterTest : public testing::Test { protected: using BackingType = uint16_t; static constexpr size_t RegSize = sizeof(BackingType); // We'll typically test with the little endian version, since it only // matters for a few methods. RegisterBankLE::Register reg; RegisterBankBE::Register regBE; std::array buf; TypedRegisterTest() : reg("le_reg", 0x1122), regBE("be_reg", 0x1122), buf{0x1, 0x2, 0x3, 0x4, 0x5, 0x6} {} }; // Needed by C++14 and lower constexpr size_t TypedRegisterTest::RegSize; TEST_F(TypedRegisterTest, DefaultConstructor) { RegisterBankLE::Register def("def"); EXPECT_EQ(def.get(), 0); } TEST_F(TypedRegisterTest, Name) { EXPECT_EQ(reg.name(), "le_reg"); } TEST_F(TypedRegisterTest, Size) { EXPECT_EQ(reg.size(), RegSize); } TEST_F(TypedRegisterTest, Writable) { // By default, all bits of the registers are writeable. EXPECT_EQ(reg.writeable(), 0xffff); } // Verify that get returns the initial value of the reg. TEST_F(TypedRegisterTest, GetInitial) { EXPECT_EQ(reg.get(), 0x1122); } TEST_F(TypedRegisterTest, Get) { reg.get() = 0x1020; EXPECT_EQ(reg.get(), 0x1020); reg.get() = 0x3040; EXPECT_EQ(reg.get(), 0x3040); } // Do a full big endian read using the default read handler. TEST_F(TypedRegisterTest, BigEndianDefaultFullRead) { regBE.read(buf.data() + RegSize); EXPECT_EQ(regBE.get(), 0x1122); EXPECT_THAT(buf, ElementsAre(0x1, 0x2, 0x11, 0x22, 0x5, 0x6)); } // Do a full big endian write using the default write handler. TEST_F(TypedRegisterTest, BigEndianDefaultFullWrite) { regBE.write(buf.data() + RegSize); EXPECT_EQ(regBE.get(), 0x0304); EXPECT_THAT(buf, ElementsAre(0x1, 0x2, 0x3, 0x4, 0x5, 0x6)); } // Do a partial big endian read of the low half of the register. TEST_F(TypedRegisterTest, BigEndianDefaultPartialReadLow) { regBE.read(buf.data() + RegSize, 0, 1); EXPECT_EQ(regBE.get(), 0x1122); EXPECT_THAT(buf, ElementsAre(0x1, 0x2, 0x11, 0x4, 0x5, 0x6)); } // Do a partial big endian read of the high half of the register. TEST_F(TypedRegisterTest, BigEndianDefaultPartialReadHigh) { regBE.read(buf.data() + RegSize, 1, 1); EXPECT_EQ(regBE.get(), 0x1122); EXPECT_THAT(buf, ElementsAre(0x1, 0x2, 0x22, 0x4, 0x5, 0x6)); } // Do a partial big endian write of the low half of the register. TEST_F(TypedRegisterTest, BigEndianDefaultPartialWriteLow) { regBE.write(buf.data() + RegSize, 0, 1); EXPECT_EQ(regBE.get(), 0x0322); EXPECT_THAT(buf, ElementsAre(0x1, 0x2, 0x3, 0x4, 0x5, 0x6)); } // Do a partial big endian write of the High half of the register. TEST_F(TypedRegisterTest, BigEndianDefaultPartialWriteHigh) { regBE.write(buf.data() + RegSize, 1, 1); EXPECT_EQ(regBE.get(), 0x1103); EXPECT_THAT(buf, ElementsAre(0x1, 0x2, 0x3, 0x4, 0x5, 0x6)); } // Do a full little endian read using the default read handler. TEST_F(TypedRegisterTest, LittleEndianDefaultFullRead) { reg.read(buf.data() + RegSize); EXPECT_EQ(reg.get(), 0x1122); EXPECT_THAT(buf, ElementsAre(0x1, 0x2, 0x22, 0x11, 0x5, 0x6)); } // Do a full little endian write using the default write handler. TEST_F(TypedRegisterTest, LittleEndianDefaultFullWrite) { reg.write(buf.data() + RegSize); EXPECT_EQ(reg.get(), 0x0403); EXPECT_THAT(buf, ElementsAre(0x1, 0x2, 0x3, 0x4, 0x5, 0x6)); } // Do a partial little endian read of the low half of the register. TEST_F(TypedRegisterTest, LittleEndianDefaultPartialReadLow) { reg.read(buf.data() + RegSize, 0, 1); EXPECT_EQ(reg.get(), 0x1122); EXPECT_THAT(buf, ElementsAre(0x1, 0x2, 0x22, 0x4, 0x5, 0x6)); } // Do a partial little endian read of the high half of the register. TEST_F(TypedRegisterTest, LittleEndianDefaultPartialReadHigh) { reg.read(buf.data() + RegSize, 1, 1); EXPECT_EQ(reg.get(), 0x1122); EXPECT_THAT(buf, ElementsAre(0x1, 0x2, 0x11, 0x4, 0x5, 0x6)); } // Do a partial little endian write of the low half of the register. TEST_F(TypedRegisterTest, LittleEndianDefaultPartialWriteLow) { reg.write(buf.data() + RegSize, 0, 1); EXPECT_EQ(reg.get(), 0x1103); EXPECT_THAT(buf, ElementsAre(0x1, 0x2, 0x3, 0x4, 0x5, 0x6)); } // Do a partial little endian write of the High half of the register. TEST_F(TypedRegisterTest, LittleEndianDefaultPartialWriteHigh) { reg.write(buf.data() + RegSize, 1, 1); EXPECT_EQ(reg.get(), 0x0322); EXPECT_THAT(buf, ElementsAre(0x1, 0x2, 0x3, 0x4, 0x5, 0x6)); } // Set a mask for use on writes. TEST_F(TypedRegisterTest, SetWriteable) { reg.writeable(0xff00); reg.write(buf.data() + RegSize); EXPECT_EQ(reg.get(), 0x0422); regBE.writeable(0xff00); regBE.write(buf.data() + RegSize); EXPECT_EQ(regBE.get(), 0x0322); } // Make a register read only. TEST_F(TypedRegisterTest, ReadOnly) { reg.readonly(); reg.write(buf.data() + RegSize); EXPECT_EQ(reg.get(), 0x1122); } // Update a register with an explicit mask. TEST_F(TypedRegisterTest, UpdateWithMask) { reg.update(0xeeee, 0x0ff0); EXPECT_EQ(reg.get(), 0x1ee2); } // Update a register using the register's built in mask. TEST_F(TypedRegisterTest, UpdateDefaultMask) { reg.writeable(0xf00f); reg.update(0xeeee); EXPECT_EQ(reg.get(), 0xe12e); } // Set a custom read handler for a register. TEST_F(TypedRegisterTest, Reader) { RegisterBankLE::Register *reg_ptr = nullptr; BackingType ret = 0x3344; reg.reader([®_ptr, &ret](auto &r){ reg_ptr = &r; return ret; }); reg.read(buf.data() + RegSize); EXPECT_THAT(buf, ElementsAre(0x1, 0x2, 0x44, 0x33, 0x5, 0x6)); EXPECT_EQ(reg_ptr, ®); } // Set a custom read handler for a register which is a class method. TEST_F(TypedRegisterTest, ReaderMF) { using Reg = RegisterBankLE::Register; struct ReadStruct { Reg *reg_ptr = nullptr; BackingType ret = 0x3344; BackingType reader(Reg &r) { reg_ptr = &r; return ret; } } read_struct; reg.reader(&read_struct, &ReadStruct::reader); reg.read(buf.data() + RegSize); EXPECT_THAT(buf, ElementsAre(0x1, 0x2, 0x44, 0x33, 0x5, 0x6)); EXPECT_EQ(read_struct.reg_ptr, ®); } // Set a custom write handler for a register. TEST_F(TypedRegisterTest, Writer) { RegisterBankLE::Register *reg_ptr = nullptr; BackingType value = 0; reg.writer([®_ptr, &value](auto &r, const BackingType &v) { reg_ptr = &r; value = v; }); reg.write(buf.data() + RegSize); EXPECT_EQ(reg_ptr, ®); EXPECT_EQ(value, 0x0403); } // Set a custom write handler for a register which is a class method. TEST_F(TypedRegisterTest, WriterMF) { using Reg = RegisterBankLE::Register; struct WriteStruct { Reg *reg_ptr = nullptr; BackingType value = 0; void writer(Reg &r, const BackingType &v) { reg_ptr = &r; value = v; } } write_struct; reg.writer(&write_struct, &WriteStruct::writer); reg.write(buf.data() + RegSize); EXPECT_EQ(write_struct.reg_ptr, ®); EXPECT_THAT(write_struct.value, 0x0403); } // Set a custom partial read handler for a register. TEST_F(TypedRegisterTest, PartialReader) { RegisterBankLE::Register *reg_ptr = nullptr; int first = 0; int last = 0; BackingType ret = 0x3344; reg.partialReader([®_ptr, &first, &last, ret](auto &r, int f, int l) { reg_ptr = &r; first = f; last = l; return ret; }); reg.read(buf.data() + RegSize, 1, 1); EXPECT_THAT(buf, ElementsAre(0x1, 0x2, 0x33, 0x4, 0x5, 0x6)); EXPECT_EQ(reg_ptr, ®); EXPECT_EQ(first, 15); EXPECT_EQ(last, 8); } // Set a custom partial read handler for a register which is a class method. TEST_F(TypedRegisterTest, PartialReaderMF) { using Reg = RegisterBankLE::Register; struct ReadStruct { Reg *reg_ptr = nullptr; int first = 0; int last = 0; BackingType ret = 0x3344; BackingType reader(Reg &r, int f, int l) { reg_ptr = &r; first = f; last = l; return ret; } } read_struct; reg.partialReader(&read_struct, &ReadStruct::reader); reg.read(buf.data() + RegSize, 1, 1); EXPECT_THAT(buf, ElementsAre(0x1, 0x2, 0x33, 0x4, 0x5, 0x6)); EXPECT_EQ(read_struct.reg_ptr, ®); EXPECT_EQ(read_struct.first, 15); EXPECT_EQ(read_struct.last, 8); } // Set a custom partial write handler for a register. TEST_F(TypedRegisterTest, PartialWriter) { RegisterBankLE::Register *reg_ptr = nullptr; BackingType value = 0; int first = 0; int last = 0; reg.partialWriter([®_ptr, &value, &first, &last]( auto &r, const BackingType &v, int f, int l) { reg_ptr = &r; value = v; first = f; last = l; }); reg.write(buf.data() + RegSize, 1, 1); EXPECT_EQ(reg_ptr, ®); EXPECT_EQ(value, 0x300); EXPECT_EQ(first, 15); EXPECT_EQ(last, 8); } // Set a custom partial write handler for a register which is a class method. TEST_F(TypedRegisterTest, PartialWriterMF) { using Reg = RegisterBankLE::Register; struct WriteStruct { Reg *reg_ptr = nullptr; BackingType value = 0; int first = 0; int last = 0; void writer(Reg &r, const BackingType &v, int f, int l) { reg_ptr = &r; value = v; first = f; last = l; } } write_struct; reg.partialWriter(&write_struct, &WriteStruct::writer); reg.write(buf.data() + RegSize, 1, 1); EXPECT_EQ(write_struct.reg_ptr, ®); EXPECT_EQ(write_struct.value, 0x300); EXPECT_EQ(write_struct.first, 15); EXPECT_EQ(write_struct.last, 8); } // Default partial reader with a custom read handler. TEST_F(TypedRegisterTest, PartialReaderReader) { RegisterBankLE::Register *reg_ptr = nullptr; BackingType ret = 0x3344; reg.reader([®_ptr, &ret](auto &r){ reg_ptr = &r; return ret; }); reg.read(buf.data() + RegSize, 1, 1); EXPECT_THAT(buf, ElementsAre(0x1, 0x2, 0x33, 0x4, 0x5, 0x6)); EXPECT_EQ(reg_ptr, ®); } // Default partial writer with custome read and write handlers. TEST_F(TypedRegisterTest, PartialWriterReaderWriter) { RegisterBankLE::Register *read_reg_ptr = nullptr; BackingType read_ret = 0x3344; RegisterBankLE::Register *write_reg_ptr = nullptr; BackingType write_value = 0; reg.reader([&read_reg_ptr, read_ret](auto &r){ read_reg_ptr = &r; return read_ret; }).writer([&write_reg_ptr, &write_value](auto &r, const BackingType &v) { write_reg_ptr = &r; write_value = v; }); reg.write(buf.data() + RegSize, 1, 1); EXPECT_THAT(buf, ElementsAre(0x1, 0x2, 0x3, 0x4, 0x5, 0x6)); EXPECT_EQ(read_reg_ptr, ®); EXPECT_EQ(write_reg_ptr, ®); EXPECT_EQ(write_value, 0x0344); } TEST_F(TypedRegisterTest, Serialize) { std::ostringstream os; reg.serialize(os); EXPECT_EQ(os.str(), "4386"); } TEST_F(TypedRegisterTest, UnserializeSucess) { std::string s = "1234"; EXPECT_TRUE(reg.unserialize(s)); EXPECT_EQ(reg.get(), 1234); } TEST_F(TypedRegisterTest, UnserializeFailure) { std::string s = "not_a_number"; EXPECT_FALSE(reg.unserialize(s)); } /* * The RegisterBank itself. */ class RegisterBankTest : public testing::Test { protected: class TestRegBank : public RegisterBankLE { public: TestRegBank(const std::string &new_name, Addr new_base) : RegisterBankLE(new_name, new_base) {} }; enum AccessType { Read, Write, PartialRead, PartialWrite }; struct Access { AccessType type; uint32_t value = 0; int first = 0; int last = 0; uint32_t ret = 0; Access(AccessType _type) : type(_type) {} Access(AccessType _type, uint32_t _value, int _first, int _last, uint32_t _ret) : type(_type), value(_value), first(_first), last(_last), ret(_ret) {} bool operator == (const Access &other) const { return type == other.type && value == other.value && first == other.first && last == other.last && ret == other.ret; } }; // A 32 bit register which keeps track of what happens to it. class TestReg : public TestRegBank::Register32 { public: std::vector accesses; TestReg(const std::string &new_name, uint32_t initial) : TestRegBank::Register32(new_name, initial) { reader([this](auto &r) { Access access(Read); access.ret = defaultReader(r); accesses.push_back(access); return access.ret; }); writer([this](auto &r, const uint32_t &v) { Access access(Write); access.value = v; defaultWriter(r, v); accesses.push_back(access); }); partialReader([this](auto &r, int f, int l) { Access access(PartialRead); access.first = f; access.last = l; access.ret = defaultPartialReader(r, f, l); accesses.push_back(access); return access.ret; }); partialWriter([this](auto &r, const uint32_t &v, int f, int l) { Access access(PartialWrite); access.value = v; access.first = f; access.last = l; defaultPartialWriter(r, v, f, l); accesses.push_back(access); }); } }; TestReg reg0, reg1, reg2; TestRegBank emptyBank, fullBank; std::array buf; RegisterBankTest() : reg0("reg0", 0xd3d2d1d0), reg1("reg1", 0xe3e2e1e0), reg2("reg2", 0xf3f2f1f0), emptyBank("empty", 0x12345), fullBank("full", 0x1000), buf{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc} { fullBank.addRegisters({reg0, reg1, reg2}); } }; // Some basic accessors. TEST_F(RegisterBankTest, Name) { EXPECT_EQ(emptyBank.name(), "empty"); EXPECT_EQ(fullBank.name(), "full"); } TEST_F(RegisterBankTest, Base) { EXPECT_EQ(emptyBank.base(), 0x12345); EXPECT_EQ(fullBank.base(), 0x1000); } // Adding registers, and the size accessor. With registers, size is boring. TEST_F(RegisterBankTest, AddRegistersSize) { EXPECT_EQ(emptyBank.size(), 0); emptyBank.addRegister(reg0); EXPECT_EQ(emptyBank.size(), 4); emptyBank.addRegisters({reg1, reg2}); EXPECT_EQ(emptyBank.size(), 12); } // Reads. TEST_F(RegisterBankTest, ReadOneAlignedFirst) { fullBank.read(0x1000, buf.data() + 4, 4); EXPECT_THAT(buf, ElementsAreArray({0x11, 0x22, 0x33, 0x44, 0xd0, 0xd1, 0xd2, 0xd3, 0x99, 0xaa, 0xbb, 0xcc})); EXPECT_THAT(reg0.accesses, ElementsAre( Access(Read, 0, 0, 0, 0xd3d2d1d0) )); EXPECT_TRUE(reg1.accesses.empty()); EXPECT_TRUE(reg2.accesses.empty()); } TEST_F(RegisterBankTest, ReadOneAlignedMid) { fullBank.read(0x1004, buf.data() + 4, 4); EXPECT_THAT(buf, ElementsAreArray({0x11, 0x22, 0x33, 0x44, 0xe0, 0xe1, 0xe2, 0xe3, 0x99, 0xaa, 0xbb, 0xcc})); EXPECT_TRUE(reg0.accesses.empty()); EXPECT_THAT(reg1.accesses, ElementsAre( Access(Read, 0, 0, 0, 0xe3e2e1e0) )); EXPECT_TRUE(reg2.accesses.empty()); } TEST_F(RegisterBankTest, ReadOneAlignedLast) { fullBank.read(0x1008, buf.data() + 4, 4); EXPECT_THAT(buf, ElementsAreArray({0x11, 0x22, 0x33, 0x44, 0xf0, 0xf1, 0xf2, 0xf3, 0x99, 0xaa, 0xbb, 0xcc})); EXPECT_TRUE(reg0.accesses.empty()); EXPECT_TRUE(reg1.accesses.empty()); EXPECT_THAT(reg2.accesses, ElementsAre( Access(Read, 0, 0, 0, 0xf3f2f1f0) )); } TEST_F(RegisterBankTest, ReadTwoAligned) { fullBank.read(0x1004, buf.data() + 2, 8); EXPECT_THAT(buf, ElementsAreArray({0x11, 0x22, 0xe0, 0xe1, 0xe2, 0xe3, 0xf0, 0xf1, 0xf2, 0xf3, 0xbb, 0xcc})); EXPECT_TRUE(reg0.accesses.empty()); EXPECT_THAT(reg1.accesses, ElementsAre( Access(Read, 0, 0, 0, 0xe3e2e1e0) )); EXPECT_THAT(reg2.accesses, ElementsAre( Access(Read, 0, 0, 0, 0xf3f2f1f0) )); } TEST_F(RegisterBankTest, ReadContained) { fullBank.read(0x1001, buf.data() + 4, 2); EXPECT_THAT(buf, ElementsAreArray({0x11, 0x22, 0x33, 0x44, 0xd1, 0xd2, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc})); EXPECT_THAT(reg0.accesses, ElementsAre( Access(Read, 0, 0, 0, 0xd3d2d1d0), Access(PartialRead, 0, 23, 8, 0x00d2d100) )); EXPECT_TRUE(reg1.accesses.empty()); EXPECT_TRUE(reg2.accesses.empty()); } TEST_F(RegisterBankTest, ReadOneSpanning) { fullBank.read(0x1002, buf.data() + 4, 4); EXPECT_THAT(buf, ElementsAreArray({0x11, 0x22, 0x33, 0x44, 0xd2, 0xd3, 0xe0, 0xe1, 0x99, 0xaa, 0xbb, 0xcc})); EXPECT_THAT(reg0.accesses, ElementsAre( Access(Read, 0, 0, 0, 0xd3d2d1d0), Access(PartialRead, 0, 31, 16, 0xd3d20000) )); EXPECT_THAT(reg1.accesses, ElementsAre( Access(Read, 0, 0, 0, 0xe3e2e1e0), Access(PartialRead, 0, 15, 0, 0x0000e1e0) )); EXPECT_TRUE(reg2.accesses.empty()); } TEST_F(RegisterBankTest, ReadTwoSpanning) { fullBank.read(0x1002, buf.data() + 2, 8); EXPECT_THAT(buf, ElementsAreArray({0x11, 0x22, 0xd2, 0xd3, 0xe0, 0xe1, 0xe2, 0xe3, 0xf0, 0xf1, 0xbb, 0xcc})); EXPECT_THAT(reg0.accesses, ElementsAre( Access(Read, 0, 0, 0, 0xd3d2d1d0), Access(PartialRead, 0, 31, 16, 0xd3d20000) )); EXPECT_THAT(reg1.accesses, ElementsAre( Access(Read, 0, 0, 0, 0xe3e2e1e0) )); EXPECT_THAT(reg2.accesses, ElementsAre( Access(Read, 0, 0, 0, 0xf3f2f1f0), Access(PartialRead, 0, 15, 0, 0x0000f1f0) )); } TEST_F(RegisterBankTest, ReadPartialFull) { fullBank.read(0x1002, buf.data() + 4, 6); EXPECT_THAT(buf, ElementsAreArray({0x11, 0x22, 0x33, 0x44, 0xd2, 0xd3, 0xe0, 0xe1, 0xe2, 0xe3, 0xbb, 0xcc})); EXPECT_THAT(reg0.accesses, ElementsAre( Access(Read, 0, 0, 0, 0xd3d2d1d0), Access(PartialRead, 0, 31, 16, 0xd3d20000) )); EXPECT_THAT(reg1.accesses, ElementsAre( Access(Read, 0, 0, 0, 0xe3e2e1e0) )); EXPECT_TRUE(reg2.accesses.empty()); } TEST_F(RegisterBankTest, ReadFullPartial) { fullBank.read(0x1004, buf.data() + 4, 6); EXPECT_THAT(buf, ElementsAreArray({0x11, 0x22, 0x33, 0x44, 0xe0, 0xe1, 0xe2, 0xe3, 0xf0, 0xf1, 0xbb, 0xcc})); EXPECT_TRUE(reg0.accesses.empty()); EXPECT_THAT(reg1.accesses, ElementsAre( Access(Read, 0, 0, 0, 0xe3e2e1e0) )); EXPECT_THAT(reg2.accesses, ElementsAre( Access(Read, 0, 0, 0, 0xf3f2f1f0), Access(PartialRead, 0, 15, 0, 0x0000f1f0) )); } TEST_F(RegisterBankTest, ReadLastPartial) { fullBank.read(0x100a, buf.data() + 4, 2); EXPECT_THAT(buf, ElementsAreArray({0x11, 0x22, 0x33, 0x44, 0xf2, 0xf3, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc})); EXPECT_TRUE(reg0.accesses.empty()); EXPECT_TRUE(reg1.accesses.empty()); EXPECT_THAT(reg2.accesses, ElementsAre( Access(Read, 0, 0, 0, 0xf3f2f1f0), Access(PartialRead, 0, 31, 16, 0xf3f20000) )); } // Write. TEST_F(RegisterBankTest, WriteOneAlignedFirst) { fullBank.write(0x1000, buf.data() + 4, 4); EXPECT_EQ(reg0.get(), 0x88776655); EXPECT_EQ(reg1.get(), 0xe3e2e1e0); EXPECT_EQ(reg2.get(), 0xf3f2f1f0); EXPECT_THAT(reg0.accesses, ElementsAre( Access(Write, 0x88776655, 0, 0, 0) )); EXPECT_TRUE(reg1.accesses.empty()); EXPECT_TRUE(reg2.accesses.empty()); } TEST_F(RegisterBankTest, WriteOneAlignedMid) { fullBank.write(0x1004, buf.data() + 4, 4); EXPECT_EQ(reg0.get(), 0xd3d2d1d0); EXPECT_EQ(reg1.get(), 0x88776655); EXPECT_EQ(reg2.get(), 0xf3f2f1f0); EXPECT_TRUE(reg0.accesses.empty()); EXPECT_THAT(reg1.accesses, ElementsAre( Access(Write, 0x88776655, 0, 0, 0) )); EXPECT_TRUE(reg2.accesses.empty()); } TEST_F(RegisterBankTest, WriteOneAlignedLast) { fullBank.write(0x1008, buf.data() + 4, 4); EXPECT_EQ(reg0.get(), 0xd3d2d1d0); EXPECT_EQ(reg1.get(), 0xe3e2e1e0); EXPECT_EQ(reg2.get(), 0x88776655); EXPECT_TRUE(reg0.accesses.empty()); EXPECT_TRUE(reg1.accesses.empty()); EXPECT_THAT(reg2.accesses, ElementsAre( Access(Write, 0x88776655, 0, 0, 0) )); } TEST_F(RegisterBankTest, WriteTwoAligned) { fullBank.write(0x1004, buf.data() + 2, 8); EXPECT_EQ(reg0.get(), 0xd3d2d1d0); EXPECT_EQ(reg1.get(), 0x66554433); EXPECT_EQ(reg2.get(), 0xaa998877); EXPECT_TRUE(reg0.accesses.empty()); EXPECT_THAT(reg1.accesses, ElementsAre( Access(Write, 0x66554433, 0, 0, 0) )); EXPECT_THAT(reg2.accesses, ElementsAre( Access(Write, 0xaa998877, 0, 0, 0) )); } TEST_F(RegisterBankTest, WriteContained) { fullBank.write(0x1001, buf.data() + 4, 2); EXPECT_EQ(reg0.get(), 0xd36655d0); EXPECT_EQ(reg1.get(), 0xe3e2e1e0); EXPECT_EQ(reg2.get(), 0xf3f2f1f0); EXPECT_THAT(reg0.accesses, ElementsAre( Access(Read, 0, 0, 0, 0xd3d2d1d0), Access(Write, 0xd36655d0, 0, 0, 0), Access(PartialWrite, 0x00665500, 23, 8, 0) )); EXPECT_TRUE(reg1.accesses.empty()); EXPECT_TRUE(reg2.accesses.empty()); } TEST_F(RegisterBankTest, WriteOneSpanning) { fullBank.write(0x1002, buf.data() + 4, 4); EXPECT_EQ(reg0.get(), 0x6655d1d0); EXPECT_EQ(reg1.get(), 0xe3e28877); EXPECT_EQ(reg2.get(), 0xf3f2f1f0); EXPECT_THAT(reg0.accesses, ElementsAre( Access(Read, 0, 0, 0, 0xd3d2d1d0), Access(Write, 0x6655d1d0, 0, 0, 0), Access(PartialWrite, 0x66550000, 31, 16, 0) )); EXPECT_THAT(reg1.accesses, ElementsAre( Access(Read, 0, 0, 0, 0xe3e2e1e0), Access(Write, 0xe3e28877, 0, 0, 0), Access(PartialWrite, 0x00008877, 15, 0, 0) )); EXPECT_TRUE(reg2.accesses.empty()); } TEST_F(RegisterBankTest, WriteTwoSpanning) { fullBank.write(0x1002, buf.data() + 2, 8); EXPECT_EQ(reg0.get(), 0x4433d1d0); EXPECT_EQ(reg1.get(), 0x88776655); EXPECT_EQ(reg2.get(), 0xf3f2aa99); EXPECT_THAT(reg0.accesses, ElementsAre( Access(Read, 0, 0, 0, 0xd3d2d1d0), Access(Write, 0x4433d1d0, 0, 0, 0), Access(PartialWrite, 0x44330000, 31, 16, 0) )); EXPECT_THAT(reg1.accesses, ElementsAre( Access(Write, 0x88776655, 0, 0, 0) )); EXPECT_THAT(reg2.accesses, ElementsAre( Access(Read, 0, 0, 0, 0xf3f2f1f0), Access(Write, 0xf3f2aa99, 0, 0, 0), Access(PartialWrite, 0x0000aa99, 15, 0, 0) )); } TEST_F(RegisterBankTest, WritePartialFull) { fullBank.write(0x1002, buf.data() + 4, 6); EXPECT_EQ(reg0.get(), 0x6655d1d0); EXPECT_EQ(reg1.get(), 0xaa998877); EXPECT_EQ(reg2.get(), 0xf3f2f1f0); EXPECT_THAT(reg0.accesses, ElementsAre( Access(Read, 0, 0, 0, 0xd3d2d1d0), Access(Write, 0x6655d1d0, 0, 0, 0), Access(PartialWrite, 0x66550000, 31, 16, 0) )); EXPECT_THAT(reg1.accesses, ElementsAre( Access(Write, 0xaa998877, 0, 0, 0) )); EXPECT_TRUE(reg2.accesses.empty()); } TEST_F(RegisterBankTest, WriteFullPartial) { fullBank.write(0x1004, buf.data() + 4, 6); EXPECT_EQ(reg0.get(), 0xd3d2d1d0); EXPECT_EQ(reg1.get(), 0x88776655); EXPECT_EQ(reg2.get(), 0xf3f2aa99); EXPECT_TRUE(reg0.accesses.empty()); EXPECT_THAT(reg1.accesses, ElementsAre( Access(Write, 0x88776655, 0, 0, 0) )); EXPECT_THAT(reg2.accesses, ElementsAre( Access(Read, 0, 0, 0, 0xf3f2f1f0), Access(Write, 0xf3f2aa99, 0, 0, 0), Access(PartialWrite, 0x0000aa99, 15, 0, 0) )); }