blob: 3a89a00ab6c9c118eb7783903dc939eb6a1536e0 [file] [log] [blame]
/*
* 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 <cassert>
#include <cstdint>
#include <cstring>
#include <functional>
#include <initializer_list>
#include <iostream>
#include <map>
#include <optional>
#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.
*
* The base(), size() and name() methods can be used to access each of those
* read only properties of the RegisterBank instance.
*
* 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.
*
* When adding a lot of registers, you might accidentally add an extra,
* or accidentally skip one in a long list. Because the offset is handled
* automatically, some of your registers might end up shifted higher or lower
* than you expect. To help mitigate this, you can set what offset you expect
* a register to have by specifying it as an offset, register pair.
*
* addRegisters({{0x1000, reg0}, reg1, reg2});
*
* If the register would end up at a different offset, gem5 will panic. You
* can also leave off the register if you want to just check the offset, for
* instance between groups of registers.
*
* addRegisters({reg0, reg1, reg2, 0x100c})
*
* 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.
*
* A RegisterBank also has a reset() method which will (by default) call the
* reset() method on each register within it. This method is virtual, and so
* can be overridden if something additional or different needs to be done to
* reset the hardware model.
*
*
* == 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.
*
* Each register also has a "reset" method, which will reset the register as
* if its containing device is being reset. By default, this will just restore
* the initial value of the register, but can be overridden to implement
* additional behavior like resetting other aspects of the device which are
* controlled by the value of the register.
*
*
* == 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> &reg);
* Data partialRead(Register<Data> &reg, int first, int last);
* void write(Register<Data> &reg, const Data &value);
* void partialWrite(Register<Data> &reg, 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.
*
* Similarly, you can set a "resetter" handler which is responsible for
* resetting the register. It takes a reference to the current Register, and
* no other parameters. The "initialValue" accessor can retrieve the value the
* register was constructed with. The register is simply set to this value
* in the default resetter implementation.
*
* = 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.
*/
namespace gem5
{
// 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;
// Reset the register.
virtual void reset() = 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; }
// Resetting a read only register doesn't need to do anything.
void reset() override {}
};
// 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; }
// Assume since the buffer is managed externally, it will be reset
// externally.
void reset() override {}
protected:
/**
* This method exists so that derived classes that need to initialize
* their buffers before they can be set can do so.
*
* @param buf The pointer to the backing buffer.
*/
void
setBuffer(void *buf)
{
assert(_ptr == nullptr);
assert(buf != nullptr);
_ptr = buf;
}
};
// 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, nullptr, BufBytes)
{
this->setBuffer(buffer.data());
}
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;
}
void reset() override { buffer = std::array<uint8_t, BufBytes>{}; }
};
template <typename Data, ByteOrder RegByteOrder=BankByteOrder>
class Register : public RegisterBase
{
protected:
using This = Register<Data, RegByteOrder>;
public:
using ReadFunc = std::function<Data (This &reg)>;
using PartialReadFunc = std::function<
Data (This &reg, int first, int last)>;
using WriteFunc = std::function<void (This &reg, const Data &value)>;
using PartialWriteFunc = std::function<
void (This &reg, const Data &value, int first, int last)>;
using ResetFunc = std::function<void (This &reg)>;
private:
Data _data = {};
Data _resetData = {};
Data _writeMask = mask(sizeof(Data) * 8);
ReadFunc _reader = defaultReader;
WriteFunc _writer = defaultWriter;
PartialWriteFunc _partialWriter = defaultPartialWriter;
PartialReadFunc _partialReader = defaultPartialReader;
ResetFunc _resetter = defaultResetter;
protected:
static Data defaultReader(This &reg) { return reg.get(); }
static Data
defaultPartialReader(This &reg, int first, int last)
{
return mbits(reg._reader(reg), first, last);
}
static void
defaultWriter(This &reg, const Data &value)
{
reg.update(value);
}
static void
defaultPartialWriter(This &reg, const Data &value, int first, int last)
{
reg._writer(reg, writeWithMask<Data>(reg._reader(reg), value,
mask(first, last)));
}
static void
defaultResetter(This &reg)
{
reg.get() = reg.initialValue();
}
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),
_resetData(new_data)
{}
constexpr Register(const std::string &new_name,
const Data &&new_data) :
RegisterBase(new_name, sizeof(Data)), _data(new_data),
_resetData(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);
}
// Set the callables which handle resetting.
//
// The default resetter restores the initial value used in the
// constructor.
constexpr This &
resetter(const ResetFunc &new_resetter)
{
_resetter = new_resetter;
return *this;
}
template <class Parent, class... Args>
constexpr This &
resetter(Parent *parent, void (Parent::*nr)(Args... args))
{
auto wrapper = [parent, nr](Args&&... args) {
return (parent->*nr)(std::forward<Args>(args)...);
};
return resetter(wrapper);
}
// An accessor which returns the initial value as set in the
// constructor. This is intended to be used in a resetter function.
const Data &initialValue() const { return _resetData; }
// Reset the initial value, which is normally set in the constructor,
// to the register's current value.
void resetInitialValue() { _resetData = _data; }
/*
* 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());
}
// Reset our data to its initial value.
void reset() override { _resetter(*this); }
};
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() {}
class RegisterAdder
{
private:
std::optional<Addr> offset;
std::optional<RegisterBase *> reg;
public:
// Nothing special to do for this register.
RegisterAdder(RegisterBase &new_reg) : reg(&new_reg) {}
// Ensure that this register is added at a particular offset.
RegisterAdder(Addr new_offset, RegisterBase &new_reg) :
offset(new_offset), reg(&new_reg)
{}
// No register, just check that the offset is what we expect.
RegisterAdder(Addr new_offset) : offset(new_offset) {}
friend class RegisterBank;
};
void
addRegisters(std::initializer_list<RegisterAdder> adders)
{
panic_if(std::empty(adders),
"Adding an empty list of registers to %s?", name());
for (auto &adder: adders) {
const Addr offset = _base + _size;
if (adder.reg) {
auto *reg = adder.reg.value();
if (adder.offset && adder.offset.value() != offset) {
panic(
"Expected offset of register %s.%s to be %#x, is %#x.",
name(), reg->name(), adder.offset.value(), offset);
}
_offsetMap.emplace(offset, *reg);
_size += reg->size();
} else if (adder.offset) {
if (adder.offset.value() != offset) {
panic("Expected current offset of %s to be %#x, is %#x.",
name(), adder.offset.value(), offset);
}
}
}
}
void addRegister(RegisterAdder reg) { 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 &reg = 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 &reg = 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 &reg = 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 &reg = 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;
}
}
}
// By default, reset all the registers in the bank.
virtual void
reset()
{
for (auto &it: _offsetMap)
it.second.get().reset();
}
};
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_v<
typename RegisterBankBase::RegisterBaseBase, T>>>
{
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_v<
typename RegisterBankBase::RegisterBaseBase, T>>>
{
static void
show(std::ostream &os, const T &value)
{
value.serialize(os);
}
};
} // namespace gem5
#endif // __DEV_REG_BANK_HH__