| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| #ifndef OPENTITAN_HW_DV_VERILATOR_CPP_RANGED_MAP_H_ |
| #define OPENTITAN_HW_DV_VERILATOR_CPP_RANGED_MAP_H_ |
| |
| // Utility class representing disjoint segments of memory |
| |
| #include <cassert> |
| #include <map> |
| |
| // The type used to represent address ranges. This is essentially a std::pair, |
| // but we need a operator< custom for the internal map. |
| template <typename addr_t> |
| struct AddrRange { |
| addr_t lo, hi; |
| }; |
| |
| template <typename addr_t> |
| bool operator<(const AddrRange<addr_t> &a, const AddrRange<addr_t> &b) { |
| return a.lo < b.lo; |
| } |
| |
| template <typename addr_t, typename val_t> |
| class RangedMap { |
| public: |
| using rng_t = AddrRange<addr_t>; |
| |
| // A function used to merge overlapping segments. When called by |
| // Emplace(), val1 will be the newer value and val0 will be the |
| // older. |
| typedef val_t (*MergeFun)(const rng_t &rng0, val_t &&val0, const rng_t &rng1, |
| val_t &&val1); |
| |
| // Insert an entry that covers the address range [min_addr, max_addr] |
| // (inclusive) with value val. |
| void Emplace(addr_t min_addr, addr_t max_addr, val_t &&new_val, |
| MergeFun merge) { |
| assert(min_addr <= max_addr); |
| |
| // Construct hit_lo / hit_hi, a pair of iterators that bound the |
| // segments that touch the new value. |
| auto hit_lo = map_.end(); |
| auto hit_hi = map_.end(); |
| |
| rng_t rng = {.lo = min_addr, .hi = max_addr}; |
| |
| if (!map_.empty()) { |
| // Start by finding the first region that starts strictly above min_addr. |
| auto right_it = map_.upper_bound(rng); |
| hit_hi = right_it; |
| |
| // If hit_hi is map_.end(), every region starts at or below min_addr. If |
| // not, the region it points to overlaps with the range if hit_hi->first |
| // <= max_addr. Increment hit_hi until we get to the end or are no longer |
| // overlapping. The result is the end iterator for our range. |
| while (hit_hi != map_.end() && hit_hi->first.lo <= max_addr) { |
| ++hit_hi; |
| } |
| |
| // Now we need to find the low end of the range. Start at right_it and |
| // decrement while we've not got to the beginning and while the previous |
| // iterator has a top >= min_addr. |
| hit_lo = right_it; |
| while (hit_lo != map_.begin() && |
| min_addr <= std::prev(hit_lo)->first.hi) { |
| --hit_lo; |
| } |
| } |
| |
| // The entry is disjoint from all others iff hit_lo == hit_hi. In which |
| // case, we can just insert it. |
| if (hit_lo == hit_hi) { |
| map_.insert(std::make_pair(rng, std::move(new_val))); |
| return; |
| } |
| |
| // Otherwise, we use the merge function to merge everything together. |
| // Accumulate into a new val_t and update min_addr / max_addr as we go. |
| // Peel off the 1st iteration of the loop to avoid an unnecessary move/copy |
| // of new_val. |
| val_t acc = merge(hit_lo->first, std::move(hit_lo->second), rng, |
| std::move(new_val)); |
| min_addr = std::min(min_addr, hit_lo->first.lo); |
| max_addr = std::max(max_addr, hit_lo->first.hi); |
| |
| for (auto it = std::next(hit_lo); it != hit_hi; ++it) { |
| rng_t rng1 = {.lo = min_addr, .hi = max_addr}; |
| acc = merge(it->first, std::move(it->second), rng1, std::move(acc)); |
| min_addr = std::min(min_addr, it->first.lo); |
| max_addr = std::max(max_addr, it->first.hi); |
| } |
| |
| // We've merged everything, and have possibly trashed the values pointed to |
| // by all the iterators in the range. Throw that lot away and finally |
| // insert the merged result. |
| map_.erase(hit_lo, hit_hi); |
| rng_t rng1 = {.lo = min_addr, .hi = max_addr}; |
| map_.insert(std::make_pair(rng1, std::move(acc))); |
| } |
| |
| // Try to insert an entry that covers the address range [min_addr, max_addr] |
| // (inclusive) with value val. |
| // |
| // If there is an existing entry that overlaps with the range on either side, |
| // the map and val are unchanged and a pointer to the existing entry is |
| // returned. Otherwise, returns nullptr. |
| const val_t *EmplaceDisjoint(addr_t min_addr, addr_t max_addr, val_t &&val) { |
| assert(min_addr <= max_addr); |
| rng_t rng = {.lo = min_addr, .hi = max_addr}; |
| |
| if (!map_.empty()) { |
| // We start by checking for an overlap "from the right". This would be a |
| // region that starts strictly above min_addr, but where it's low address |
| // is still <= max_addr. We can use std::map::upper_bound to find the |
| // first region strictly above min_addr (which returns the end iterator |
| // if there isn't one). |
| auto right_it = map_.upper_bound(rng); |
| if (right_it != map_.end()) { |
| addr_t right_min = right_it->first.lo; |
| if (right_min <= max_addr) { |
| return &right_it->second; |
| } |
| } |
| |
| // We also need to check from the left side. This would be a region that |
| // starts at or before min_addr and extends past it. If right_it is |
| // mem_.begin(), there is no such region (because the lowest addressed |
| // region already starts above min_addr). Otherwise, decrement right_it |
| // to get the highest addressed region that starts at or before min_addr. |
| // Note this still works if right_it is the end iterator: we just pick up |
| // the last region, which we know exists because map_ is not empty. |
| if (right_it != map_.begin()) { |
| auto left_it = std::prev(right_it); |
| addr_t left_max = left_it->first.hi; |
| |
| if (min_addr <= left_max) { |
| return &left_it->second; |
| } |
| } |
| } |
| |
| // Phew, no overlap! |
| map_.insert(std::make_pair(rng, std::move(val))); |
| return nullptr; |
| } |
| |
| // Iteration interface |
| using map_t = std::map<rng_t, val_t>; |
| using const_iterator = typename map_t::const_iterator; |
| |
| const_iterator begin() const { return const_iterator(map_.begin()); } |
| const_iterator end() const { return const_iterator(map_.end()); } |
| size_t size() const { return map_.size(); } |
| |
| // Try to find an entry hitting the given address. Returns end() if there is |
| // none. |
| const_iterator find(addr_t addr) const { |
| // To find the entry containing addr, use upper_bound to find the first |
| // region strictly after it, and then std::prev to step backwards. This |
| // fails if either the map is empty (obviously!) or if ub_it is already the |
| // beginning of the map. |
| if (map_.empty()) |
| return end(); |
| |
| rng_t diag = {.lo = addr, .hi = addr}; |
| auto it = map_.upper_bound(diag); |
| if (it == map_.begin()) |
| return end(); |
| |
| --it; |
| |
| // At this point, it will point at the right region if there is one. We |
| // know that it->first.lo <= addr (because of how upper_bound works). We |
| // now just need to check that addr <= it->first.hi. |
| return (addr <= it->first.hi) ? const_iterator(it) : end(); |
| } |
| |
| private: |
| std::map<rng_t, val_t> map_; |
| }; |
| |
| #endif // OPENTITAN_HW_DV_VERILATOR_CPP_RANGED_MAP_H_ |