| // 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_ |