--- /dev/null
+/*
+ * 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 <algorithm>
+#include <bitset>
+#include <cstring>
+#include <functional>
+#include <initializer_list>
+#include <iostream>
+#include <map>
+#include <sstream>
+#include <utility>
+
+#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<Data> type and redefining its read/write
+ * methods, reads and writes are implemented using replaceable handlers with
+ * these signatures:
+ *
+ * Data read(Register<Data> ®);
+ * Data partialRead(Register<Data> ®, int first, int last);
+ * void write(Register<Data> ®, const Data &value);
+ * void partialWrite(Register<Data> ®, 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<Foo>, 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 <ByteOrder BankByteOrder>
+class RegisterBank : public RegisterBankBase
+{
+ public:
+ // Static helper methods for implementing register types.
+ template <typename Data>
+ static constexpr Data
+ readWithMask(const Data &value, const Data &bitmask)
+ {
+ return value & bitmask;
+ }
+
+ template <typename Data>
+ 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 <int BufBytes>
+ class RegisterLBuf : public RegisterBuf
+ {
+ public:
+ std::array<uint8_t, BufBytes> buffer;
+
+ RegisterLBuf(const std::string &new_name) :
+ RegisterBuf(new_name, buffer.data(), BufBytes)
+ {}
+
+ void
+ serialize(std::ostream &os) const override
+ {
+ if (BufBytes)
+ ShowParam<uint8_t>::show(os, buffer[0]);
+ for (int i = 1; i < BufBytes; i++) {
+ os << " ";
+ ShowParam<uint8_t>::show(os, buffer[i]);
+ }
+ }
+
+ bool
+ unserialize(const std::string &s) override
+ {
+ std::vector<std::string> 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<uint8_t>::parse(tokens[i], buffer[i]))
+ return false;
+ }
+
+ return true;
+ }
+ };
+
+ template <typename Data, ByteOrder RegByteOrder=BankByteOrder>
+ class Register : public RegisterBase
+ {
+ protected:
+ using This = Register<Data, RegByteOrder>;
+
+ public:
+ using ReadFunc = std::function<Data (This ®)>;
+ using PartialReadFunc = std::function<
+ Data (This ®, int first, int last)>;
+ using WriteFunc = std::function<void (This ®, const Data &value)>;
+ 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<Data>(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 <class Parent, class... Args>
+ constexpr This &
+ reader(Parent *parent, Data (Parent::*nr)(Args... args))
+ {
+ auto wrapper = [parent, nr](Args&&... args) -> Data {
+ return (parent->*nr)(std::forward<Args>(args)...);
+ };
+ return reader(wrapper);
+ }
+ constexpr This &
+ writer(const WriteFunc &new_writer)
+ {
+ _writer = new_writer;
+ return *this;
+ }
+ template <class Parent, class... Args>
+ constexpr This &
+ writer(Parent *parent, void (Parent::*nw)(Args... args))
+ {
+ auto wrapper = [parent, nw](Args&&... args) {
+ (parent->*nw)(std::forward<Args>(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 <class Parent, class... Args>
+ constexpr This &
+ partialReader(Parent *parent, Data (Parent::*nr)(Args... args))
+ {
+ auto wrapper = [parent, nr](Args&&... args) -> Data {
+ return (parent->*nr)(std::forward<Args>(args)...);
+ };
+ return partialReader(wrapper);
+ }
+ constexpr This &
+ partialWriter(const PartialWriteFunc &new_writer)
+ {
+ _partialWriter = new_writer;
+ return *this;
+ }
+ template <class Parent, class... Args>
+ constexpr This &
+ partialWriter(Parent *parent, void (Parent::*nw)(Args... args))
+ {
+ auto wrapper = [parent, nw](Args&&... args) {
+ return (parent->*nw)(std::forward<Args>(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<Data>::show(os, get());
+ }
+
+ bool
+ unserialize(const std::string &s) override
+ {
+ return ParseParam<Data>::parse(s, get());
+ }
+ };
+
+ private:
+ std::map<Addr, std::reference_wrapper<RegisterBase>> _offsetMap;
+
+ Addr _base = 0;
+ Addr _size = 0;
+ const std::string _name;
+
+ public:
+
+ using Register8 = Register<uint8_t>;
+ using Register8LE = Register<uint8_t, ByteOrder::little>;
+ using Register8BE = Register<uint8_t, ByteOrder::big>;
+ using Register16 = Register<uint16_t>;
+ using Register16LE = Register<uint16_t, ByteOrder::little>;
+ using Register16BE = Register<uint16_t, ByteOrder::big>;
+ using Register32 = Register<uint32_t>;
+ using Register32LE = Register<uint32_t, ByteOrder::little>;
+ using Register32BE = Register<uint32_t, ByteOrder::big>;
+ using Register64 = Register<uint64_t>;
+ using Register64LE = Register<uint64_t, ByteOrder::little>;
+ using Register64BE = Register<uint64_t, ByteOrder::big>;
+
+
+ constexpr RegisterBank(const std::string &new_name, Addr new_base) :
+ _base(new_base), _name(new_name)
+ {}
+
+ virtual ~RegisterBank() {}
+
+ void
+ addRegisters(
+ std::initializer_list<std::reference_wrapper<RegisterBase>> 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<ByteOrder::little>;
+using RegisterBankBE = RegisterBank<ByteOrder::big>;
+
+// Delegate serialization to the individual RegisterBase subclasses.
+template <class T>
+struct ParseParam<T, std::enable_if_t<std::is_base_of<
+ typename RegisterBankBase::RegisterBaseBase, T>::value>>
+{
+ static bool
+ parse(const std::string &s, T &value)
+ {
+ return value.unserialize(s);
+ }
+};
+
+template <class T>
+struct ShowParam<T, std::enable_if_t<std::is_base_of<
+ typename RegisterBankBase::RegisterBaseBase, T>::value>>
+{
+ static void
+ show(std::ostream &os, const T &value)
+ {
+ value.serialize(os);
+ }
+};
+
+#endif // __DEV_REG_BANK_HH__
--- /dev/null
+/*
+ * 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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#pragma GCC diagnostic pop
+
+#include <vector>
+
+#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<uint8_t, BufSize> 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<uint8_t, BufSize> 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<uint8_t, RegSize * 3> buf;
+ std::array<uint8_t, RegSize * 3> 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<uint8_t, 4> 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<BackingType> reg;
+ RegisterBankBE::Register<BackingType> regBE;
+
+ std::array<uint8_t, RegSize * 3> buf;
+
+ TypedRegisterTest() : reg("le_reg", 0x1122), regBE("be_reg", 0x1122),
+ buf{0x1, 0x2, 0x3, 0x4, 0x5, 0x6}
+ {}
+};
+
+TEST_F(TypedRegisterTest, DefaultConstructor)
+{
+ RegisterBankLE::Register<uint32_t> 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<BackingType> *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<BackingType>;
+
+ 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<BackingType> *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<BackingType>;
+
+ 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<BackingType> *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<BackingType>;
+
+ 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<BackingType> *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<BackingType>;
+
+ 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<BackingType> *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<BackingType> *read_reg_ptr = nullptr;
+ BackingType read_ret = 0x3344;
+
+ RegisterBankLE::Register<BackingType> *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<Access> 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<uint8_t, 12> 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)
+ ));
+}