From 5c548ad30e60eb0ba46da305e5ec18d193fec881 Mon Sep 17 00:00:00 2001 From: Gabe Black Date: Tue, 20 Oct 2020 20:03:13 -0700 Subject: [PATCH] dev: Add a new RegisterBank which helps handle device registers. This change includes both the RegisterBank class and register classes, and a unit test which exercises them. Change-Id: I28ef0c0b9192ad786625ac83f096f69d8e5af00f Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/35856 Reviewed-by: Matthew Poremba Maintainer: Gabe Black Tested-by: kokoro --- src/dev/SConscript | 2 + src/dev/reg_bank.hh | 945 ++++++++++++++++++++++++++++ src/dev/reg_bank.test.cc | 1276 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 2223 insertions(+) create mode 100644 src/dev/reg_bank.hh create mode 100644 src/dev/reg_bank.test.cc diff --git a/src/dev/SConscript b/src/dev/SConscript index 6af5ee241..02f973315 100644 --- a/src/dev/SConscript +++ b/src/dev/SConscript @@ -54,3 +54,5 @@ Source('pixelpump.cc') DebugFlag('Intel8254Timer') DebugFlag('MC146818') + +GTest('reg_bank.test', 'reg_bank.test.cc') diff --git a/src/dev/reg_bank.hh b/src/dev/reg_bank.hh new file mode 100644 index 000000000..7abe02c27 --- /dev/null +++ b/src/dev/reg_bank.hh @@ -0,0 +1,945 @@ +/* + * 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. + */ + +#ifndef __DEV_REG_BANK_HH__ +#define __DEV_REG_BANK_HH__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "base/bitfield.hh" +#include "base/logging.hh" +#include "base/types.hh" +#include "sim/byteswap.hh" +#include "sim/serialize_handlers.hh" + +/* + * Device models often have contiguous banks of registers which can each + * have unique and arbitrary behavior when they are completely or partially + * read or written. Historically it's been up to each model to map an access + * which covers an arbitrary portion of that register bank down to individual + * registers. It must handle cases where registers are only partially accessed, + * or where multiple registers are accessed at the same time, or a combination + * of both. + * + * + * == RegisterBank == + * + * The RegisterBank class(es), defined below, handle that mapping, and let the + * device model focus on defining what each of the registers actually do when + * read or written. Once it's set up, it has two primary interfaces which + * access the registers it contains: + * + * void read(Addr addr, void *buf, Addr bytes); + * void write(Addr addr, const void *buf, Addr bytes); + * + * These two methods will handle a read or write contained within the register + * bank starting at address "addr". The data that will be written or has been + * read is pointed to by "buf", and is "bytes" bytes long. + * + * These methods are virtual, so if you need to implement extra rules, like + * for instance that registers can only be accessed one at a time, that + * accesses have to be aligned, have to access complete registers, etc, that + * can be added in a subclass. + * + * Additionally, each RegisterBank has a name and a base address which is + * passed into the constructor. The meaning of the "base" value can be whatever + * makes sense for your device, and is considered the lowest address contained + * in the bank. The value could be the offset of this bank of registers within + * the device itself, with the device's own offset subtracted out before read + * or write are called. It could alternatively be the base address of the + * entire device, with the address from accesses passed into read or write + * unmodified. + * + * To add actual registers to the RegisterBank (discussed below), you can use + * either the addRegister method which adds a single register, or addRegisters + * which adds an initializer list of them all at once. The register will be + * appended to the end of the bank as they're added, contiguous to the + * existing registers. The size of the bank is automatically accumulated as + * registers are added. + * + * The base(), size() and name() methods can be used to access each of those + * read only properties of the RegisterBank instance. + * + * While the RegisterBank itself doesn't have any data in it directly and so + * has no endianness, it's very likely all the registers within it will have + * the same endinanness. The bank itself therefore has a default endianness + * which, unless specified otherwise, will be passed on to the register types + * within it. The RegisterBank class is templated on its endianness. There are + * RegisterBankLE and RegisterBankBE aliases to make it a little easier to + * refer to one or the other version. + * + * + * == Register interface == + * + * Every register in a RegisterBank needs to inherit, directly or indirectly, + * from the RegisterBase class. Each register must have a name (for debugging), + * and a well defined size. The following methods define the interface the + * register bank uses to access the register, and where the register can + * implement its special behaviors: + * + * void read(void *buf); + * void read(void *buf, off_t offset, size_t bytes); + * + * void write(const void *buf); + * void write(const void *buf, off_t offset, size_t bytes); + * + * The single argument versions of these methods completely overwrite the + * register's contents with whatever is pointed to by buf. + * + * The version which also takes "offset" and "bytes" arguments reads or writes + * only a portion of the register, starting "offset" bytes from the start of + * the register, and writing or reading the next "bytes" bytes. + * + * Each register also needs to implement serialize or unserialize methods + * which make it accessible to the checkpointing mechanism. If a register + * doesn't need to be serialized (for instance if it has a fixed value) then + * it still has to implement these methods, but they don't have to actually do + * anything. + * + * + * == Basic Register types == + * + * Some simple register types have been defined which handle basic, common + * behaviors found in many devices: + * + * = RegisterRaz and RegisterRao = + * + * RegisterRaz (read as zero) and RegisterRao (read as one) will ignore writes, + * and will return all zeroes or ones, respectively, when read. These can have + * arbitrary alignment and size, and can be used for, for instance, + * unimplemented registers that still need to take up a certain amount of + * space, or for gaps between registers which still need to handle accesses + * even though they don't do anything or hold any data. + * + * For instance, a device might have several regions of registers which are + * aligned on different boundaries, but which might not take up all of the + * space in each region. The extra space can be filled with a RegisterRaz or + * RegisterRao, making it possible to implement all the registers as a single + * bank. + * + * If you need a register with a different fill pattern, you can subclass the + * RegisterRoFill type and implement its "fill" method. This should behave + * like the three argument form of the read() method, described above. + * + * = RegisterBuf and RegisterLBuf = + * + * These two types act like inert blobs of storage. They don't have any + * special behavior and can have any arbitrary size like the RegisterRao and + * RegisterRaz types above, but these registers actually store what's written + * to them. + * + * The RegisterBuf type acts as an interface to a buffer stored elsewhere. That + * makes it possible to, for instance, alias the same buffer to different parts + * of the register space, or to expose some other object which needs to exist + * outside of the register bank for some reason. + * + * The RegisterLBuf does the same thing, except it uses a local buffer it + * manages. That makes it a little easier to work with if you don't need the + * flexibility of the RegisterBuf type. + * + * + * == Typed Registers == + * + * The Register template class is for more complex registers with side effects, + * and/or which hold structured data. The template arguments define what type + * the register should hold, and also its endianness. + * + * = Access handlers = + * + * Instead of subclassing the Register type and redefining its read/write + * methods, reads and writes are implemented using replaceable handlers with + * these signatures: + * + * Data read(Register ®); + * Data partialRead(Register ®, int first, int last); + * void write(Register ®, const Data &value); + * void partialWrite(Register ®, const Data &value, + * int first, int last); + * + * The "partial" version of these handlers take "first" and "last" arguments + * which specify what bits of the register to modify. They should be + * interpreted like the same arguments in base/bitfield.hh. The endianness + * of the register will have already been dealt with by the time the handler + * is called. + * + * The read and partialRead handlers should generate whatever value reading the + * register should return, based on (or not based on) the state of "reg". The + * partial handler should keep the bits it returns in place. For example, if + * bits 15-8 are read from a 16 bit register with the value 0x1234, it should + * return 0x1200, not 0x0012. + * + * The write and partialWrite handlers work the same way, except in they write + * instead of read. They are responsible for updating the value in reg in + * whatever way and to whatever value is appropriate, based on + * (or not based on) the value of "value" and the state of "reg". + * + * The default implementations of the read and write handlers simply return or + * update the value stored in reg. The default partial read calls the read + * handler (which may not be the default), and trims down the data as required. + * The default partial write handler calls the read handler (which may not be + * the default), updates the value as requested, and then calls the write + * handler (which may not be the default). + * + * Overriding the partial read or write methods might be necessary if reads or + * writes have side effects which should affect only the part of the register + * read or written. For instance, there might be some status bits which will + * be cleared when accessed. Only the bits which were actually accessed should + * be affected, even if they're grouped together logically with the other bits + * in a single register. + * + * To set your own handlers, you can use the "reader", "writer", + * "partialReader", and "partialWriter" methods. Each of these takes a single + * callable argument (lambda, functor, function pointer, etc.) which will + * replace the current corresponding handler. + * + * These methods all return a reference to the current Register so that they + * can be strung together without having to respecify what object you're + * modifying over and over again. + * + * There are also versions of these which will set up methods on some object as + * the handlers. These take a pointer to whatever object will handle the call, + * and a member function pointer to the method that will actually implement + * the handler. This can be used if, for instance, the registers are all + * members of a RegisterBank subclass, and need to call methods on their + * parent class to actually implement the behavior. These methods must have + * the same signature as above, with the exception that they are methods and + * not bare functions. + * + * When updating the register's value in custom write or partialWrite handlers, + * be sure to use the "update" method which will honor read only bits. There + * is an alternative form of update which also takes a custom bitmask, if you + * need to update bits other than the normally writeable ones. + * + * = Read only bits = + * + * Often registers have bits which are fixed and not affected by writes. To + * specify which bits are writeable, use the "writeable" method which takes a + * single argument the same type as the type of the register. It should hold a + * bitmask where a 1 bit can be written, and a 0 cannot. Calling writeable with + * no arguments will return the current bitmask. + * + * A shorthand "readonly" method marks all bits as read only. + * + * Both methods return a reference to the current Register so they can be + * strung together into a sequence when configuring it. + * + * = Underlying data and serialization = + * + * The "get" method returns a reference to the underlying storage inside the + * register. That can be used to manually update the entire register, even bits + * which are normally read only, or for structured data, to access members of + * the underlying data type. + * + * For instance, if the register holds a BitUnion, you could use the get() + * method to access the bitfields within it: + * + * reg.get().bitfieldA = reg.get().bitfieldB; + * + * The serialize and unserialize methods for these types will pass through the + * underlying data within the register. For instance, when serializing a + * Register, the value in the checkpoint will be the same as if you had + * serialized a Foo directly, with the value stored in the register. + * + * = Aliases = + * + * Some convenient aliases have been defined for frequently used versions of + * the Register class. These are + * + * Register(8|16|32|64)(LE|BE|) + * + * Where the underlying type of the register is a uint8_t, uint16_t, etc, and + * the endianness is little endian, big endian, or whatever the default is for + * the RegisterBank. + */ + +// Common bases to make it easier to identify both endiannesses at once. +class RegisterBankBase +{ + public: + class RegisterBaseBase {}; +}; + +template +class RegisterBank : public RegisterBankBase +{ + public: + // Static helper methods for implementing register types. + template + static constexpr Data + readWithMask(const Data &value, const Data &bitmask) + { + return value & bitmask; + } + + template + static constexpr Data + writeWithMask(const Data &old, const Data &value, const Data &bitmask) + { + return readWithMask( + old, (Data)~bitmask) | readWithMask(value, bitmask); + } + + class RegisterBase : public RegisterBankBase::RegisterBaseBase + { + protected: + const std::string _name; + size_t _size = 0; + + public: + constexpr RegisterBase(const std::string &new_name, size_t new_size) : + _name(new_name), _size(new_size) + {} + virtual ~RegisterBase() {} + + // Read the register's name. + virtual const std::string &name() const { return _name; } + + // Read the register's size in bytes. + size_t size() const { return _size; } + + // Perform a read on the register. + virtual void read(void *buf) = 0; + virtual void read(void *buf, off_t offset, size_t bytes) = 0; + + // Perform a write on the register. + virtual void write(const void *buf) = 0; + virtual void write(const void *buf, off_t offset, size_t bytes) = 0; + + // Methods for implementing serialization for checkpoints. + virtual void serialize(std::ostream &os) const = 0; + virtual bool unserialize(const std::string &s) = 0; + }; + + // Filler registers which return a fixed pattern. + class RegisterRoFill : public RegisterBase + { + protected: + constexpr RegisterRoFill( + const std::string &new_name, size_t new_size) : + RegisterBase(new_name, new_size) + {} + + virtual void fill(void *buf, off_t offset, size_t bytes) = 0; + + public: + // Ignore writes. + void write(const void *buf) override {} + void write(const void *buf, off_t offset, size_t bytes) override {} + + // Use fill() to handle reads. + void read(void *buf) override { fill(buf, 0, this->size()); } + void + read(void *buf, off_t offset, size_t bytes) override + { + fill(buf, offset, bytes); + } + + void serialize(std::ostream &os) const override {} + bool unserialize(const std::string &s) override { return true; } + }; + + // Register which reads as all zeroes. + class RegisterRaz : public RegisterRoFill + { + protected: + void + fill(void *buf, off_t offset, size_t bytes) override + { + bzero(buf, bytes); + } + + public: + RegisterRaz(const std::string &new_name, size_t new_size) : + RegisterRoFill(new_name, new_size) + {} + }; + + // Register which reads as all ones. + class RegisterRao : public RegisterRoFill + { + protected: + void + fill(void *buf, off_t offset, size_t bytes) override + { + memset(buf, 0xff, bytes); + } + + public: + RegisterRao(const std::string &new_name, size_t new_size) : + RegisterRoFill(new_name, new_size) + {} + }; + + // Register which acts as a simple buffer. + class RegisterBuf : public RegisterBase + { + private: + void *_ptr = nullptr; + + public: + RegisterBuf(const std::string &new_name, void *ptr, size_t bytes) : + RegisterBase(new_name, bytes), _ptr(ptr) + {} + + void write(const void *buf) override { write(buf, 0, this->size()); } + void + write(const void *buf, off_t offset, size_t bytes) override + { + assert(offset + bytes <= this->size()); + memcpy((uint8_t *)_ptr + offset, buf, bytes); + } + + void read(void *buf) override { read(buf, 0, this->size()); } + void + read(void *buf, off_t offset, size_t bytes) override + { + assert(offset + bytes <= this->size()); + memcpy(buf, (uint8_t *)_ptr + offset, bytes); + } + + // The buffer's owner is responsible for serializing it. + void serialize(std::ostream &os) const override {} + bool unserialize(const std::string &s) override { return true; } + }; + + // Same as above, but which keeps its storage locally. + template + class RegisterLBuf : public RegisterBuf + { + public: + std::array buffer; + + RegisterLBuf(const std::string &new_name) : + RegisterBuf(new_name, buffer.data(), BufBytes) + {} + + void + serialize(std::ostream &os) const override + { + if (BufBytes) + ShowParam::show(os, buffer[0]); + for (int i = 1; i < BufBytes; i++) { + os << " "; + ShowParam::show(os, buffer[i]); + } + } + + bool + unserialize(const std::string &s) override + { + std::vector tokens; + std::istringstream is(s); + + std::string token; + while (is >> token) + tokens.push_back(token); + + if (tokens.size() != BufBytes) { + warn("Size mismatch unserialing %s, expected %d, got %d", + this->name(), BufBytes, tokens.size()); + return false; + } + + for (int i = 0; i < BufBytes; i++) { + if (!ParseParam::parse(tokens[i], buffer[i])) + return false; + } + + return true; + } + }; + + template + class Register : public RegisterBase + { + protected: + using This = Register; + + public: + using ReadFunc = std::function; + using PartialReadFunc = std::function< + Data (This ®, int first, int last)>; + using WriteFunc = std::function; + using PartialWriteFunc = std::function< + void (This ®, const Data &value, int first, int last)>; + + private: + Data _data = {}; + Data _writeMask = mask(sizeof(Data) * 8); + + ReadFunc _reader = defaultReader; + WriteFunc _writer = defaultWriter; + PartialWriteFunc _partialWriter = defaultPartialWriter; + PartialReadFunc _partialReader = defaultPartialReader; + + protected: + static Data defaultReader(This ®) { return reg.get(); } + + static Data + defaultPartialReader(This ®, int first, int last) + { + return mbits(reg._reader(reg), first, last); + } + + static void + defaultWriter(This ®, const Data &value) + { + reg.update(value); + } + + static void + defaultPartialWriter(This ®, const Data &value, int first, int last) + { + reg._writer(reg, writeWithMask(reg._reader(reg), value, + mask(first, last))); + } + + constexpr Data + htoreg(Data data) + { + switch (RegByteOrder) { + case ByteOrder::big: + return htobe(data); + case ByteOrder::little: + return htole(data); + default: + panic("Unrecognized byte order %d.", (unsigned)RegByteOrder); + } + } + + constexpr Data + regtoh(Data data) + { + switch (RegByteOrder) { + case ByteOrder::big: + return betoh(data); + case ByteOrder::little: + return letoh(data); + default: + panic("Unrecognized byte order %d.", (unsigned)RegByteOrder); + } + } + + public: + + /* + * Interface for setting up the register. + */ + + // Constructor which lets data default initialize itself. + constexpr Register(const std::string &new_name) : + RegisterBase(new_name, sizeof(Data)) + {} + + // Constructor and move constructor with an initial data value. + constexpr Register(const std::string &new_name, const Data &new_data) : + RegisterBase(new_name, sizeof(Data)), _data(new_data) + {} + constexpr Register(const std::string &new_name, + const Data &&new_data) : + RegisterBase(new_name, sizeof(Data)), _data(new_data) + {} + + // Set which bits of the register are writeable. + constexpr This & + writeable(const Data &new_mask) + { + _writeMask = new_mask; + return *this; + } + + // Set the register as read only. + constexpr This &readonly() { return writeable(0); } + + // Set the callables which handles reads or writes. + // The default reader just returns the register value. + // The default writer uses the write mask to update the register value. + constexpr This & + reader(const ReadFunc &new_reader) + { + _reader = new_reader; + return *this; + } + template + constexpr This & + reader(Parent *parent, Data (Parent::*nr)(Args... args)) + { + auto wrapper = [parent, nr](Args&&... args) -> Data { + return (parent->*nr)(std::forward(args)...); + }; + return reader(wrapper); + } + constexpr This & + writer(const WriteFunc &new_writer) + { + _writer = new_writer; + return *this; + } + template + constexpr This & + writer(Parent *parent, void (Parent::*nw)(Args... args)) + { + auto wrapper = [parent, nw](Args&&... args) { + (parent->*nw)(std::forward(args)...); + }; + return writer(wrapper); + } + + // Set the callables which handle reads or writes. These may need to + // be handled specially if, for instance, accessing bits outside of + // the enables would have side effects that shouldn't happen. + // + // The default partial reader just uses the byte enables to mask off + // bits that are not being read. + // + // The default partial writer reads the current value of the register, + // uses the byte enables to update only the bytes that are changing, + // and then writes the result back to the register. + constexpr This & + partialReader(const PartialReadFunc &new_reader) + { + _partialReader = new_reader; + return *this; + } + template + constexpr This & + partialReader(Parent *parent, Data (Parent::*nr)(Args... args)) + { + auto wrapper = [parent, nr](Args&&... args) -> Data { + return (parent->*nr)(std::forward(args)...); + }; + return partialReader(wrapper); + } + constexpr This & + partialWriter(const PartialWriteFunc &new_writer) + { + _partialWriter = new_writer; + return *this; + } + template + constexpr This & + partialWriter(Parent *parent, void (Parent::*nw)(Args... args)) + { + auto wrapper = [parent, nw](Args&&... args) { + return (parent->*nw)(std::forward(args)...); + }; + return partialWriter(wrapper); + } + + + /* + * Interface for accessing the register's state, for use by the + * register's helper functions and the register bank. + */ + + const Data &writeable() const { return _writeMask; } + + // Directly access the underlying data value. + const Data &get() const { return _data; } + Data &get() { return _data; } + + // Update data while applying a mask. + void + update(const Data &new_data, const Data &bitmask) + { + _data = writeWithMask(_data, new_data, bitmask); + } + // This version uses the default write mask. + void + update(const Data &new_data) + { + _data = writeWithMask(_data, new_data, _writeMask); + } + + + /* + * Interface for reading/writing the register, for use by the + * register bank. + */ + + // Perform a read on the register. + void + read(void *buf) override + { + Data data = htoreg(_reader(*this)); + memcpy(buf, (uint8_t *)&data, sizeof(data)); + } + + void + read(void *buf, off_t offset, size_t bytes) override + { + // Move the region we're reading to be little endian, since that's + // what gem5 uses internally in BitUnions, masks, etc. + const off_t host_off = (RegByteOrder != ByteOrder::little) ? + sizeof(Data) - (offset + bytes) : offset; + + const int first = (host_off + bytes) * 8 - 1; + const int last = host_off * 8; + Data data = htoreg(_partialReader(*this, first, last)); + + memcpy(buf, (uint8_t *)&data + offset, bytes); + } + + // Perform a write on the register. + void + write(const void *buf) override + { + Data data; + memcpy((uint8_t *)&data, buf, sizeof(data)); + data = regtoh(data); + _writer(*this, data); + } + + void + write(const void *buf, off_t offset, size_t bytes) override + { + Data data = {}; + memcpy((uint8_t *)&data + offset, buf, bytes); + + data = regtoh(data); + + // Move the region we're reading to be little endian, since that's + // what gem5 uses internally in BitUnions, masks, etc. + const off_t host_off = (RegByteOrder != ByteOrder::little) ? + sizeof(Data) - (offset + bytes) : offset; + + const int first = (host_off + bytes) * 8 - 1; + const int last = host_off * 8; + _partialWriter(*this, data, first, last); + } + + // Serialize our data using existing mechanisms. + void + serialize(std::ostream &os) const override + { + ShowParam::show(os, get()); + } + + bool + unserialize(const std::string &s) override + { + return ParseParam::parse(s, get()); + } + }; + + private: + std::map> _offsetMap; + + Addr _base = 0; + Addr _size = 0; + const std::string _name; + + public: + + using Register8 = Register; + using Register8LE = Register; + using Register8BE = Register; + using Register16 = Register; + using Register16LE = Register; + using Register16BE = Register; + using Register32 = Register; + using Register32LE = Register; + using Register32BE = Register; + using Register64 = Register; + using Register64LE = Register; + using Register64BE = Register; + + + constexpr RegisterBank(const std::string &new_name, Addr new_base) : + _base(new_base), _name(new_name) + {} + + virtual ~RegisterBank() {} + + void + addRegisters( + std::initializer_list> regs) + { + panic_if(regs.size() == 0, "Adding an empty list of registers to %s?", + name()); + for (auto ®: regs) { + _offsetMap.emplace(_base + _size, reg); + _size += reg.get().size(); + } + } + + void addRegister(RegisterBase ®) { addRegisters({reg}); } + + Addr base() const { return _base; } + Addr size() const { return _size; } + const std::string &name() const { return _name; } + + virtual void + read(Addr addr, void *buf, Addr bytes) + { + uint8_t *ptr = (uint8_t *)buf; + // Number of bytes we've transferred. + Addr done = 0; + + panic_if(addr - base() + bytes > size(), + "Out of bounds read in register bank %s, address %#x, size %d.", + name(), addr, bytes); + + auto it = _offsetMap.lower_bound(addr); + if (it == _offsetMap.end() || it->first > addr) + it--; + + if (it->first < addr) { + RegisterBase ® = it->second.get(); + // Skip at least the beginning of the first register. + + // Figure out what parts of it we're accessing. + const off_t reg_off = addr - it->first; + const size_t reg_bytes = std::min(reg.size() - reg_off, + bytes - done); + + // Actually do the access. + reg.read(ptr, reg_off, reg_bytes); + done += reg_bytes; + it++; + + // Was that everything? + if (done == bytes) + return; + } + + while (true) { + RegisterBase ® = it->second.get(); + + const size_t reg_size = reg.size(); + const size_t remaining = bytes - done; + + if (remaining == reg_size) { + // A complete register read, and then we're done. + reg.read(ptr + done); + return; + } else if (remaining > reg_size) { + // A complete register read, with more to go. + reg.read(ptr + done); + done += reg_size; + it++; + } else { + // Skip the end of the register, and then we're done. + reg.read(ptr + done, 0, remaining); + return; + } + } + } + + virtual void + write(Addr addr, const void *buf, Addr bytes) + { + const uint8_t *ptr = (const uint8_t *)buf; + // Number of bytes we've transferred. + Addr done = 0; + + panic_if(addr - base() + bytes > size(), + "Out of bounds write in register bank %s, address %#x, size %d.", + name(), addr, bytes); + + auto it = _offsetMap.lower_bound(addr); + if (it == _offsetMap.end() || it->first > addr) + it--; + + if (it->first < addr) { + RegisterBase ® = it->second.get(); + // Skip at least the beginning of the first register. + + // Figure out what parts of it we're accessing. + const off_t reg_off = addr - it->first; + const size_t reg_bytes = std::min(reg.size() - reg_off, + bytes - done); + + // Actually do the access. + reg.write(ptr, reg_off, reg_bytes); + done += reg_bytes; + it++; + + // Was that everything? + if (done == bytes) + return; + } + + while (true) { + RegisterBase ® = it->second.get(); + + const size_t reg_size = reg.size(); + const size_t remaining = bytes - done; + + if (remaining == reg_size) { + // A complete register write, and then we're done. + reg.write(ptr + done); + return; + } else if (remaining > reg_size) { + // A complete register write, with more to go. + reg.write(ptr + done); + done += reg_size; + it++; + } else { + // Skip the end of the register, and then we're done. + reg.write(ptr + done, 0, remaining); + return; + } + } + } +}; + +using RegisterBankLE = RegisterBank; +using RegisterBankBE = RegisterBank; + +// Delegate serialization to the individual RegisterBase subclasses. +template +struct ParseParam::value>> +{ + static bool + parse(const std::string &s, T &value) + { + return value.unserialize(s); + } +}; + +template +struct ShowParam::value>> +{ + static void + show(std::ostream &os, const T &value) + { + value.serialize(os); + } +}; + +#endif // __DEV_REG_BANK_HH__ diff --git a/src/dev/reg_bank.test.cc b/src/dev/reg_bank.test.cc new file mode 100644 index 000000000..348900be7 --- /dev/null +++ b/src/dev/reg_bank.test.cc @@ -0,0 +1,1276 @@ +/* + * 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 +#pragma GCC diagnostic ignored "-Wdeprecated-copy" +#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); + } +}; + +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); + } +}; + +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} + {} +}; + +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} + {} +}; + +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) + )); +} -- 2.30.2