add MemContents class to mem.h

This commit is contained in:
Emily Schmidt 2024-07-24 17:23:18 +01:00
parent 6d329e142d
commit 12a31a4418
2 changed files with 332 additions and 0 deletions

View File

@ -1679,3 +1679,219 @@ SigSpec MemWr::decompress_en(const std::vector<int> &swizzle, SigSpec sig) {
res.append(sig[i]);
return res;
}
using addr_t = MemContents::addr_t;
MemContents::MemContents(Mem *mem) :
MemContents(ceil_log2(mem->size), mem->width)
{
for(const auto &init : mem->inits) {
if(init.en.is_fully_zero()) continue;
log_assert(init.en.size() == _data_width);
if(init.en.is_fully_ones())
insert_concatenated(init.addr.as_int(), init.data);
else {
// TODO: this case could be handled more efficiently by adding
// a flag to reserve_range that tells it to preserve
// previous contents
addr_t addr = init.addr.as_int();
addr_t words = init.data.size() / _data_width;
RTLIL::Const data = init.data;
log_assert(data.size() % _data_width == 0);
for(addr_t i = 0; i < words; i++) {
RTLIL::Const previous = (*this)[addr + i];
for(int j = 0; j < _data_width; j++)
if(init.en[j] != State::S1)
data[_data_width * i + j] = previous[j];
}
insert_concatenated(init.addr.as_int(), data);
}
}
}
MemContents::iterator & MemContents::iterator::operator++() {
auto it = _memory->_values.upper_bound(_addr);
if(it == _memory->_values.end()) {
_memory = nullptr;
_addr = ~(addr_t) 0;
} else
_addr = it->first;
return *this;
}
void MemContents::check() {
log_assert(_addr_width > 0 && _addr_width < (int)sizeof(addr_t) * 8);
log_assert(_data_width > 0);
log_assert(_default_value.size() == _data_width);
if(_values.empty()) return;
auto it = _values.begin();
for(;;) {
log_assert(!it->second.empty());
log_assert(it->second.size() % _data_width == 0);
auto end1 = _range_end(it);
log_assert(_range_begin(it) < (1<<_addr_width));
log_assert(end1 <= (1<<_addr_width));
if(++it == _values.end())
break;
// check that ranges neither overlap nor touch
log_assert(_range_begin(it) > end1);
}
}
bool MemContents::_range_contains(std::map<addr_t, RTLIL::Const>::iterator it, addr_t addr) const {
// if addr < begin, the subtraction will overflow, and the comparison will always fail
// (since we have an invariant that begin + size <= 2^(addr_t bits))
return it != _values.end() && addr - _range_begin(it) < _range_size(it);
}
bool MemContents::_range_contains(std::map<addr_t, RTLIL::Const>::iterator it, addr_t begin_addr, addr_t end_addr) const {
// note that we assume begin_addr <= end_addr
return it != _values.end() && _range_begin(it) <= begin_addr && end_addr - _range_begin(it) <= _range_size(it);
}
bool MemContents::_range_overlaps(std::map<addr_t, RTLIL::Const>::iterator it, addr_t begin_addr, addr_t end_addr) const {
if(it == _values.end() || begin_addr >= end_addr)
return false;
auto top1 = _range_end(it) - 1;
auto top2 = end_addr - 1;
return !(top1 < begin_addr || top2 < _range_begin(it));
}
std::map<addr_t, RTLIL::Const>::iterator MemContents::_range_at(addr_t addr) const {
// allow addr == 1<<_addr_width (which will just return end())
log_assert(addr <= 1<<_addr_width);
// get the first range with base > addr
// (we use const_cast since map::iterators are only passed around internally and not exposed to the user
// and using map::iterator in both the const and non-const case simplifies the code a little,
// at the cost of having to be a little careful when implementing const methods)
auto it = const_cast<std::map<addr_t, RTLIL::Const> &>(_values).upper_bound(addr);
// if we get the very first range, all ranges are past the addr, so return the first one
if(it == _values.begin())
return it;
// otherwise, go back to the previous interval
// this must be the last interval with base <= addr
auto it_prev = std::next(it, -1);
if(_range_contains(it_prev, addr))
return it_prev;
else
return it;
}
RTLIL::Const MemContents::operator[](addr_t addr) const {
auto it = _range_at(addr);
if(_range_contains(it, addr))
return it->second.extract(_range_offset(it, addr), _data_width);
else
return _default_value;
}
addr_t MemContents::count_range(addr_t begin_addr, addr_t end_addr) const {
addr_t count = 0;
for(auto it = _range_at(begin_addr); _range_overlaps(it, begin_addr, end_addr); it++) {
auto first = std::max(_range_begin(it), begin_addr);
auto last = std::min(_range_end(it), end_addr);
count += last - first;
}
return count;
}
void MemContents::clear_range(addr_t begin_addr, addr_t end_addr) {
if(begin_addr >= end_addr) return;
// identify which ranges are affected by this operation
// the first iterator affected is the first one containing any addr >= begin_addr
auto begin_it = _range_at(begin_addr);
// the first iterator *not* affected is the first one with base addr > end_addr - 1
auto end_it = _values.upper_bound(end_addr - 1);
if(begin_it == end_it)
return; // nothing to do
// the last iterator affected is one before the first one not affected
auto last_it = std::next(end_it, -1);
// the first and last range may need to be truncated, the rest can just be deleted
// to handle the begin_it == last_it case correctly, do the end case first by inserting a new range past the end
if(_range_contains(last_it, end_addr - 1)) {
auto new_begin = end_addr;
auto end = _range_end(last_it);
// if there is data past the end address, preserve it by creating a new range
if(new_begin != end)
end_it = _values.emplace_hint(last_it, new_begin, last_it->second.extract(_range_offset(last_it, new_begin), (_range_end(last_it) - new_begin) * _data_width));
// the original range will either be truncated in the next if() block or deleted in the erase, so we can leave it untruncated
}
if(_range_contains(begin_it, begin_addr)) {
auto new_end = begin_addr;
// if there is data before the start address, truncate but don't delete
if(new_end != begin_it->first) {
begin_it->second.extu(_range_offset(begin_it, new_end));
++begin_it;
}
// else: begin_it will be deleted
}
_values.erase(begin_it, end_it);
}
std::map<addr_t, RTLIL::Const>::iterator MemContents::_reserve_range(addr_t begin_addr, addr_t end_addr) {
if(begin_addr >= end_addr)
return _values.end(); // need a dummy value to return, end() is cheap
// find the first range containing any addr >= begin_addr - 1
auto lower_it = begin_addr == 0 ? _values.begin() : _range_at(begin_addr - 1);
// check if our range is already covered by a single range
// note that since ranges are not allowed to touch, if any range contains begin_addr, lower_it equals that range
if (_range_contains(lower_it, begin_addr, end_addr))
return lower_it;
// find the first range containing any addr >= end_addr
auto upper_it = _range_at(end_addr);
// check if either of the two ranges we just found touch our range
bool lower_touch = begin_addr > 0 && _range_contains(lower_it, begin_addr - 1);
bool upper_touch = _range_contains(upper_it, end_addr);
if (lower_touch && upper_touch) {
log_assert (lower_it != upper_it); // lower_it == upper_it should be excluded by the check above
// we have two different ranges touching at either end, we need to merge them
auto upper_end = _range_end(upper_it);
// make range bigger (maybe reserve here instead of resize?)
lower_it->second.bits.resize(_range_offset(lower_it, upper_end), State::Sx);
// copy only the data beyond our range
std::copy(_range_data(upper_it, end_addr), _range_data(upper_it, upper_end), _range_data(lower_it, end_addr));
// keep lower_it, but delete upper_it
_values.erase(std::next(lower_it), std::next(upper_it));
return lower_it;
} else if (lower_touch) {
// we have a range to the left, just make it bigger and delete any other that may exist.
lower_it->second.bits.resize(_range_offset(lower_it, end_addr), State::Sx);
// keep lower_it and upper_it
_values.erase(std::next(lower_it), upper_it);
return lower_it;
} else if (upper_touch) {
// we have a range to the right, we need to expand it
// since we need to erase and reinsert to a new address, steal the data
RTLIL::Const data = std::move(upper_it->second);
// note that begin_addr is not in upper_it, otherwise the whole range covered check would have tripped
data.bits.insert(data.bits.begin(), (_range_begin(upper_it) - begin_addr) * _data_width, State::Sx);
// delete lower_it and upper_it, then reinsert
_values.erase(lower_it, std::next(upper_it));
return _values.emplace(begin_addr, std::move(data)).first;
} else {
// no ranges are touching, so just delete all ranges in our range and allocate a new one
// could try to resize an existing range but not sure if that actually helps
_values.erase(lower_it, upper_it);
return _values.emplace(begin_addr, RTLIL::Const(State::Sx, (end_addr - begin_addr) * _data_width)).first;
}
}
void MemContents::insert_concatenated(addr_t addr, RTLIL::Const const &values) {
addr_t words = (values.size() + _data_width - 1) / _data_width;
log_assert(addr < 1<<_addr_width);
log_assert(words <= (1<<_addr_width) - addr);
auto it = _reserve_range(addr, addr + words);
auto to_begin = _range_data(it, addr);
std::copy(values.bits.begin(), values.bits.end(), to_begin);
// if values is not word-aligned, fill any missing bits with 0
std::fill(to_begin + values.size(), to_begin + words * _data_width, State::S0);
}
std::vector<State>::iterator MemContents::_range_write(std::vector<State>::iterator it, RTLIL::Const const &word) {
auto from_end = word.size() <= _data_width ? word.bits.end() : word.bits.begin() + _data_width;
auto to_end = std::copy(word.bits.begin(), from_end, it);
auto it_next = std::next(it, _data_width);
std::fill(to_end, it_next, State::S0);
return it_next;
}

View File

@ -224,6 +224,122 @@ struct Mem : RTLIL::AttrObject {
Mem(Module *module, IdString memid, int width, int start_offset, int size) : module(module), memid(memid), packed(false), mem(nullptr), cell(nullptr), width(width), start_offset(start_offset), size(size) {}
};
// this class is used for implementing operator-> on iterators that return values rather than references
// it's necessary because in C++ operator-> is called recursively until a raw pointer is obtained
template<class T>
struct arrow_proxy {
T v;
explicit arrow_proxy(T const & v) : v(v) {}
T* operator->() { return &v; }
};
// MemContents efficiently represents the contents of a potentially sparse memory by storing only those segments that are actually defined
class MemContents {
public:
class range; class iterator;
using addr_t = uint32_t;
private:
// we ban _addr_width == sizeof(addr_t) * 8 because it adds too many cornercases
int _addr_width;
int _data_width;
RTLIL::Const _default_value;
// for each range, store the concatenation of the words at the start address
// invariants:
// - no overlapping or adjacent ranges
// - no empty ranges
// - all Consts are a multiple of the word size
std::map<addr_t, RTLIL::Const> _values;
// returns an iterator to the range containing addr, if it exists, or the first range past addr
std::map<addr_t, RTLIL::Const>::iterator _range_at(addr_t addr) const;
addr_t _range_size(std::map<addr_t, RTLIL::Const>::iterator it) const { return it->second.size() / _data_width; }
addr_t _range_begin(std::map<addr_t, RTLIL::Const>::iterator it) const { return it->first; }
addr_t _range_end(std::map<addr_t, RTLIL::Const>::iterator it) const { return _range_begin(it) + _range_size(it); }
// check if the iterator points to a range containing addr
bool _range_contains(std::map<addr_t, RTLIL::Const>::iterator it, addr_t addr) const;
// check if the iterator points to a range containing [begin_addr, end_addr). assumes end_addr >= begin_addr.
bool _range_contains(std::map<addr_t, RTLIL::Const>::iterator it, addr_t begin_addr, addr_t end_addr) const;
// check if the iterator points to a range overlapping with [begin_addr, end_addr)
bool _range_overlaps(std::map<addr_t, RTLIL::Const>::iterator it, addr_t begin_addr, addr_t end_addr) const;
// return the offset the addr would have in the range at `it`
size_t _range_offset(std::map<addr_t, RTLIL::Const>::iterator it, addr_t addr) const { return (addr - it->first) * _data_width; }
// assuming _range_contains(it, addr), return an iterator pointing to the data at addr
std::vector<State>::iterator _range_data(std::map<addr_t, RTLIL::Const>::iterator it, addr_t addr) { return it->second.bits.begin() + _range_offset(it, addr); }
// internal version of reserve_range that returns an iterator to the range
std::map<addr_t, RTLIL::Const>::iterator _reserve_range(addr_t begin_addr, addr_t end_addr);
// write a single word at addr, return iterator to next word
std::vector<State>::iterator _range_write(std::vector<State>::iterator it, RTLIL::Const const &data);
public:
class range {
int _data_width;
addr_t _base;
RTLIL::Const const &_values;
friend class iterator;
range(int data_width, addr_t base, RTLIL::Const const &values)
: _data_width(data_width), _base(base), _values(values) {}
public:
addr_t base() const { return _base; }
addr_t size() const { return ((addr_t) _values.size()) / _data_width; }
addr_t limit() const { return _base + size(); }
RTLIL::Const const &concatenated() const { return _values; }
RTLIL::Const operator[](addr_t addr) const {
log_assert(addr - _base < size());
return _values.extract((addr - _base) * _data_width, _data_width);
}
RTLIL::Const at_offset(addr_t offset) const { return (*this)[_base + offset]; }
};
class iterator {
MemContents const *_memory;
// storing addr instead of an iterator gives more well-defined behaviour under insertions/deletions
// use ~0 for end so that all end iterators compare the same
addr_t _addr;
friend class MemContents;
iterator(MemContents const *memory, addr_t addr) : _memory(memory), _addr(addr) {}
public:
using iterator_category = std::input_iterator_tag;
using value_type = range;
using pointer = arrow_proxy<range>;
using reference = range;
using difference_type = addr_t;
reference operator *() const { return range(_memory->_data_width, _addr, _memory->_values.at(_addr)); }
pointer operator->() const { return arrow_proxy<range>(**this); }
bool operator !=(iterator const &other) const { return _memory != other._memory || _addr != other._addr; }
iterator &operator++();
};
MemContents(int addr_width, int data_width, RTLIL::Const default_value)
: _addr_width(addr_width), _data_width(data_width)
, _default_value((default_value.extu(data_width), std::move(default_value)))
{ log_assert(_addr_width > 0 && _addr_width < (int)sizeof(addr_t) * 8); log_assert(_data_width > 0); }
MemContents(int addr_width, int data_width) : MemContents(addr_width, data_width, RTLIL::Const(State::Sx, data_width)) {}
explicit MemContents(Mem *mem);
int addr_width() const { return _addr_width; }
int data_width() const { return _data_width; }
RTLIL::Const const &default_value() const { return _default_value; }
// return the value at the address if it exists, the default_value of the memory otherwise. address must not exceed 2**addr_width.
RTLIL::Const operator [](addr_t addr) const;
// return the number of defined words in the range [begin_addr, end_addr)
addr_t count_range(addr_t begin_addr, addr_t end_addr) const;
// allocate memory for the range [begin_addr, end_addr), but leave the contents undefined.
void reserve_range(addr_t begin_addr, addr_t end_addr) { _reserve_range(begin_addr, end_addr); }
// insert multiple words (provided as a single concatenated RTLIL::Const) at the given address, overriding any previous assignment.
void insert_concatenated(addr_t addr, RTLIL::Const const &values);
// insert multiple words at the given address, overriding any previous assignment.
template<typename Iterator> void insert_range(addr_t addr, Iterator begin, Iterator end) {
auto words = end - begin;
log_assert(addr < 1<<_addr_width); log_assert(words <= (1<<_addr_width) - addr);
auto range = _reserve_range(addr, addr + words);
auto it = _range_data(range, addr);
for(; begin != end; ++begin)
it = _range_write(it, *begin);
}
// undefine all words in the range [begin_addr, end_addr)
void clear_range(addr_t begin_addr, addr_t end_addr);
// check invariants, abort if invariants failed
void check();
iterator end() const { return iterator(nullptr, ~(addr_t) 0); }
iterator begin() const { return _values.empty() ? end() : iterator(this, _values.begin()->first); }
bool empty() const { return _values.empty(); }
};
YOSYS_NAMESPACE_END
#endif