Now that we're using c++17, the type_traits with a ::value member have a _v alias which reduces verbosity. Or on other words std::is_integral<T>::value can be replaced with std::is_integral_v<T> Make this substitution throughout the code base. In places where gem5 introduced it's own similar templates, add a V alias, spelled differently to match gem5's internal style. gem5: :IsVarArgs<T>::value => gem5::IsVarArgsV<T> Change-Id: I1d84ffc4a236ad699471569e7916ec17fe5f109a Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/48604 Reviewed-by: Daniel Carvalho <odanrc@yahoo.com.br> Maintainer: Bobby R. Bruce <bbruce@ucdavis.edu> Tested-by: kokoro <noreply+kokoro@google.com>
970 lines
34 KiB
C++
970 lines
34 KiB
C++
/*
|
|
* 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 <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.
|
|
*/
|
|
|
|
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;
|
|
};
|
|
|
|
// 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; }
|
|
|
|
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;
|
|
}
|
|
};
|
|
|
|
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_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__
|