diff --git a/sparta/doc/sparta_docs/modeling.txt b/sparta/doc/sparta_docs/modeling.txt index d50730a95e..1a221a11ee 100644 --- a/sparta/doc/sparta_docs/modeling.txt +++ b/sparta/doc/sparta_docs/modeling.txt @@ -40,6 +40,22 @@ sparta::StartupEvent | \copybrief sparta::StartupEvent   sparta::UniqueEvent | \copybrief sparta::UniqueEvent   + ================================================================================ + \subsection sparta_resources Sparta Resources (sparta/resources) + + Class | Brief Description + --------------------------- | ------------------ + sparta::Array | \copybrief sparta::Array   + sparta::Buffer | \copybrief sparta::Buffer   + sparta::CircularBuffer | \copybrief sparta::CircularBuffer   + sparta::FrontArray | \copybrief sparta::FrontArray   + sparta::Pipe | \copybrief sparta::Pipe   + sparta::Pipeline | \copybrief sparta::Pipeline   + sparta::PriorityQueue | \copybrief sparta::PriorityQueue   + sparta::Queue | \copybrief sparta::Queue   + sparta::Scoreboard | \copybrief sparta::Scoreboard   + sparta::SharedData | \copybrief sparta::SharedData   + ================================================================================ \subsection ports_overview Sparta Ports API (sparta/ports) diff --git a/sparta/sparta/resources/PriorityQueue.hpp b/sparta/sparta/resources/PriorityQueue.hpp new file mode 100644 index 0000000000..84ec98c16b --- /dev/null +++ b/sparta/sparta/resources/PriorityQueue.hpp @@ -0,0 +1,188 @@ +// -*- C++ -*- + + +/** + * \file PriorityQueue.hpp + * \brief Defines a Priority queue similar to STL's, but with more functionality + */ + +#pragma once + +#include +#include +#include + +#include "sparta/utils/SpartaAssert.hpp" +#include "sparta/utils/FastList.hpp" + +namespace sparta +{ + + /** + * \class PriorityQueue + * \brief A data structure that allows pushing/emplacing into it + * with a given sorter + * \tparam DataT The data to be contained and sorted + * \tparam SortingAlgorithmT The sorting algorithm to use + * \tparam bounded_cnt The max number of elements in this PriorityQueue + * + * The PriorityQueue can be used by picking algorithms in a model + * where more than one entry of a block is ready (for whatever + * reason) and the model needs to know which one to "pick" for the + * next operation. + * + * The queue defines a less-than type of sorter by default, but + * allows the modeler to define an operator object that can + * override the behavior. + * + * In addition, entries in the queue can be removed (even in the + * middle). This is handy for items in the queue that are no + * longer participating in the priority. + * + * Finally, the queue supports a basic override to the order, + * allowing a "high priority" DataT object to be pushed to the + * front, even if that object doesn't conform to the ordering + * rules. + * + * If the template parameter bounded_cnt is non-zero, the + * PriorityQueue will be bounded to an upper limit of + * bounded_cnt. This also improves the performance of the + * PriorityQueue (uses sparta::utils::FastList). + * + */ + template , + size_t bounded_cnt=0> + class PriorityQueue + { + private: + using PQueueType = + typename std::conditional, + utils::FastList>::type; + + public: + + // For collection + using size_type = size_t; + using iterator = typename PQueueType::iterator; + using const_iterator = typename PQueueType::const_iterator; + + /** + * \brief Create a priority queue with a default instance of the + * sorting algorithm + */ + PriorityQueue() : + priority_items_(bounded_cnt) + {} + + /** + * \brief Create a priority queue with a specific instance of the + * sorting algorithm + * \param sort_alg Reference to the sorting algorithm instance + */ + PriorityQueue(const SortingAlgorithmT & sort_alg) : + priority_items_(bounded_cnt), + sort_alg_(sort_alg) + {} + + /** + * \brief Inserts the data item into the list using the + * sorting alg. Stops at the first insertion. + * \param data The data to insert + */ + void insert(const DataT & data) + { + const auto eit = priority_items_.end(); + for(auto it = priority_items_.begin(); it != eit; ++it) + { + if(sort_alg_(data, *it)) { + priority_items_.insert(it, data); + return; + } + } + priority_items_.emplace_back(data); + } + + //! Get the number of items in the queue + size_t size() const { + return priority_items_.size(); + } + + //! Is the queue empty? + bool empty() const { + return priority_items_.empty(); + } + + //! Get the first element in the queue + const DataT & top() const { + sparta_assert(false == empty(), "Grabbing top from an empty queue"); + return priority_items_.front(); + } + + //! Get the last element (lowest priority) in the queue + const DataT & back() const { + sparta_assert(false == empty(), "Grabbing back from an empty queue"); + return priority_items_.back(); + } + + //! Pop the front of the queue (highest priority) + void pop() { + sparta_assert(false == empty(), "Popping on an empty priority queue"); + priority_items_.pop_front(); + } + + //! Clear the entire queue + void clear() { + priority_items_.clear(); + } + + //! Remove the item from the queue + void remove(const DataT & data) { + priority_items_.remove(data); + } + + //! Erase the item from the queue (const_iterator/iterator) + void erase(const const_iterator & it) { + priority_items_.erase(it); + } + + /** + * \brief Force a data entry to the front of the queue + * \param data Reference to the data object forced to the + * front of the queue + * + * Push the data item to the front of the queue, bypassing the + * internal sorting algorithm. This is handy if multiple + * items from multiple directions should be prioritized, but a + * last-minute item, which normally is a low-priority item, + * _must be handled_ immediately. + */ + void forceFront(const DataT & data) { + priority_items_.emplace_front(data); + } + + /*! \defgroup iteration Iteration Support */ + /**@{*/ + //! Iterator to beginning of the queue -- highest priority + iterator begin() { return priority_items_.begin(); } + + //! Const Iterator to beginning of the queue -- highest priority + const_iterator begin() const { return priority_items_.begin(); } + + //! Iterator to end of the queue -- end priority + iterator end() { return priority_items_.end(); } + + //! Const Iterator to end of the queue -- end priority + const_iterator end() const { return priority_items_.end(); } + /**@}*/ + + private: + + //! The internal queue + PQueueType priority_items_; + + //! Copy of the sorting algorithm + SortingAlgorithmT sort_alg_; + }; +} diff --git a/sparta/sparta/resources/Scoreboard.hpp b/sparta/sparta/resources/Scoreboard.hpp index c04e76c5d7..de40360e73 100644 --- a/sparta/sparta/resources/Scoreboard.hpp +++ b/sparta/sparta/resources/Scoreboard.hpp @@ -1,3 +1,10 @@ +// -*- C++ -*- + +/** + * \file Scoreboard.hpp + * \brief Class used to track operand dependencies (timed) between units + */ + #pragma once #include @@ -17,17 +24,18 @@ namespace sparta /** * \class Scoreboard + * \brief Class used to track operand dependencies (timed) between units * * The Scoreboard of the model simply keeps track of the readiness * of phyiscal registers in the OOO core. There are two parts to the SB: * - * # The Scoreboard or "master" for each register file type (GPU, - * FPR, Vector, etc). Typically a Rename block is responsible - * for setting/clearing the SB readiness. + * -# The Scoreboard or "master" for each register file type (GPU, + * FPR, Vector, etc). Typically a Rename block is responsible + * for setting/clearing the SB readiness. * - * # The ScoreboardView is created by a Scheduling/Execution block - * and is used to determine if an instruction is ready for - * execution (all operands ready) + * -# The ScoreboardView is created by a Scheduling/Execution block + * and is used to determine if an instruction is ready for + * execution (all operands ready) * */ class Scoreboard : public sparta::Unit diff --git a/sparta/sparta/utils/FastList.hpp b/sparta/sparta/utils/FastList.hpp index 12d804c2ed..c74c45382c 100644 --- a/sparta/sparta/utils/FastList.hpp +++ b/sparta/sparta/utils/FastList.hpp @@ -16,7 +16,9 @@ #include #include #include +#include +#include "sparta/utils/IteratorTraits.hpp" #include "sparta/utils/SpartaAssert.hpp" namespace sparta::utils @@ -40,7 +42,7 @@ namespace sparta::utils * - The API isn't as complete as typical STL container types * */ - template + template class FastList { struct Node @@ -60,7 +62,7 @@ namespace sparta::utils // Stores the memory for an instance of 'T'. // Use placement new to construct the object and // manually invoke its dtor as necessary. - std::aligned_storage_t type_storage; + std::aligned_storage_t type_storage; Node(NodeIdx _index) : index(_index) @@ -81,7 +83,7 @@ namespace sparta::utils } public: - using value_type = T; //!< Handy using + using value_type = DataT; //!< Handy using /** * \class NodeIterator @@ -89,7 +91,7 @@ namespace sparta::utils * */ template - class NodeIterator // : public std::iterator + class NodeIterator : public sparta::utils::IteratorTraits { typedef std::conditional_t RefIteratorType; typedef std::conditional_t PtrIteratorType; @@ -170,7 +172,7 @@ namespace sparta::utils NodeIterator& operator=( NodeIterator &&rhs) = default; private: - friend class FastList; + friend class FastList; NodeIterator(FastListPtrType flist, typename Node::NodeIdx node_idx) : flist_(flist), @@ -217,9 +219,16 @@ namespace sparta::utils //! Obtain an end iterator iterator end() { return iterator(this, -1); } + //! Obtain an end const_iterator const_iterator end() const { return const_iterator(this, -1); } + //! Get the front of the fast list non-const + DataT & front() { return *begin(); } + + //! Get the front of the fast list, const + const DataT & front() const { return *begin(); } + //! \return Is this container empty? bool empty() const { return size_ == 0; } @@ -246,7 +255,7 @@ namespace sparta::utils { const auto node_idx = entry.getIndex(); auto & node_to_erase = nodes_[node_idx]; - reinterpret_cast(&node_to_erase.type_storage)->~T(); + reinterpret_cast(&node_to_erase.type_storage)->~DataT(); int next_elem = -1; if(first_node_ == node_idx) { @@ -295,7 +304,7 @@ namespace sparta::utils auto & new_node = nodes_[free_head_]; free_head_ = new_node.next; - new (&new_node.type_storage) T(args...); + new (&new_node.type_storage) DataT(args...); // Update pointers. Start with a clean slate new_node.next = -1; new_node.prev = -1; @@ -332,7 +341,7 @@ namespace sparta::utils auto & new_node = nodes_[free_head_]; free_head_ = new_node.next; - new (&new_node.type_storage) T(args...); + new (&new_node.type_storage) DataT(args...); // Update pointers. Start with a clean slate new_node.next = -1; @@ -364,7 +373,7 @@ namespace sparta::utils auto & new_node = nodes_[free_head_]; free_head_ = new_node.next; - new (&new_node.type_storage) T(args...); + new (&new_node.type_storage) DataT(args...); // Update pointers. Start with a clean slate new_node.next = -1; @@ -384,6 +393,13 @@ namespace sparta::utils return iterator(this, new_node.index); } + //! Insert an element at a specific place in the list. Really + //! just an alias for emplace + template + iterator insert(const const_iterator & pos, ArgsT&&...args) { + return emplace(pos, args...); + } + //! Pop the last element off of the list void pop_back() { sparta_assert(last_node_ != -1, @@ -401,7 +417,7 @@ namespace sparta::utils private: // Friendly printer - friend std::ostream & operator<<(std::ostream & os, const FastList & fl) + friend std::ostream & operator<<(std::ostream & os, const FastList & fl) { int next_node = fl.first_node_; if(next_node == -1) { @@ -412,7 +428,7 @@ namespace sparta::utils do { const auto & n = fl.nodes_[next_node]; - os << index << " elem=" << *reinterpret_cast(&n.type_storage) + os << index << " elem=" << *reinterpret_cast(&n.type_storage) << " n.next=" << n.next << " n.prev=" << n.prev << std::endl; next_node = n.next; diff --git a/sparta/sparta/utils/IteratorTraits.hpp b/sparta/sparta/utils/IteratorTraits.hpp index 88faa9b9ba..7074dab3e9 100644 --- a/sparta/sparta/utils/IteratorTraits.hpp +++ b/sparta/sparta/utils/IteratorTraits.hpp @@ -10,7 +10,7 @@ namespace sparta::utils { // C++17 deprecates the `std::iterator` in lieu of developers - // being explicit on thier trait types for defining their own + // being explicit on their trait types for defining their own // iterators. For Sparta, we'll put 'em back. template struct IteratorTraits { @@ -20,4 +20,5 @@ namespace sparta::utils using reference = const T&; using iterator_category = category; }; + } diff --git a/sparta/test/CMakeLists.txt b/sparta/test/CMakeLists.txt index edf507a825..c4912bf6b4 100644 --- a/sparta/test/CMakeLists.txt +++ b/sparta/test/CMakeLists.txt @@ -123,3 +123,4 @@ add_subdirectory (MetaTypeList) add_subdirectory (LockedValue) add_subdirectory (EnumCycleHistogram) add_subdirectory (FastList) +add_subdirectory (PriorityQueue) diff --git a/sparta/test/FastList/FastList_test.cpp b/sparta/test/FastList/FastList_test.cpp index 503691497e..2e7f55c2ea 100644 --- a/sparta/test/FastList/FastList_test.cpp +++ b/sparta/test/FastList/FastList_test.cpp @@ -13,6 +13,7 @@ class MyObj explicit MyObj(uint32_t v) : v_(v) {} MyObj(const MyObj &) = delete; + MyObj(MyObj &&) = default; ~MyObj() { ++my_obj_deletions; } @@ -200,6 +201,26 @@ void testFastList() } EXPECT_EQUAL(my_obj_deletions, 10); + //////////////////////////////////////////////////////////// + // insert (which should be the same as emplace()) + fl.clear(); + EXPECT_EQUAL(fl.size(), 0); + for(size_t i = 0; i < 5; ++i) { + fl.emplace_back(i); + } + auto insert_it = fl.begin(); + std::advance(insert_it, 3); + EXPECT_TRUE(*insert_it == 3); + + insert_it = fl.erase(insert_it); + insert_it = fl.insert(insert_it, 3); + + EXPECT_TRUE(*insert_it == 3); + size_t expected_num = 0; + for(auto & num : fl) { + EXPECT_EQUAL(num.getV(), expected_num); + ++expected_num; + } } #define PERF_TEST 100000000 @@ -220,7 +241,7 @@ void testListPerf() } } -int main() +int main(int argc, char **) { std::locale::global(std::locale("")); std::cout.imbue(std::locale()); @@ -228,17 +249,21 @@ int main() testFastList(); - auto start = std::chrono::system_clock::system_clock::now(); - testListPerf>(); - auto end = std::chrono::system_clock::system_clock::now(); - auto dur = std::chrono::duration_cast(end - start).count(); - std::cout << "Raw time (seconds) fast list : " << dur / 1000000.0 << std::endl; - - start = std::chrono::system_clock::system_clock::now(); - testListPerf>(); - end = std::chrono::system_clock::system_clock::now(); - dur = std::chrono::duration_cast(end - start).count(); - std::cout << "Raw time (seconds) old list : " << dur / 1000000.0 << std::endl; + // If any argument is given, bypass the perf test (NOT in regular + // testing) + if (1 == argc) { + auto start = std::chrono::system_clock::system_clock::now(); + testListPerf>(); + auto end = std::chrono::system_clock::system_clock::now(); + auto dur = std::chrono::duration_cast(end - start).count(); + std::cout << "Raw time (seconds) fast list : " << dur / 1000000.0 << std::endl; + + start = std::chrono::system_clock::system_clock::now(); + testListPerf>(); + end = std::chrono::system_clock::system_clock::now(); + dur = std::chrono::duration_cast(end - start).count(); + std::cout << "Raw time (seconds) old list : " << dur / 1000000.0 << std::endl; + } // Done REPORT_ERROR; diff --git a/sparta/test/PriorityQueue/CMakeLists.txt b/sparta/test/PriorityQueue/CMakeLists.txt new file mode 100644 index 0000000000..b150c8602d --- /dev/null +++ b/sparta/test/PriorityQueue/CMakeLists.txt @@ -0,0 +1,5 @@ +project(PriorityQueue_test) + +sparta_add_test_executable(PriorityQueue_test PriorityQueue_test.cpp) + +sparta_test(PriorityQueue_test) diff --git a/sparta/test/PriorityQueue/PriorityQueue_test.cpp b/sparta/test/PriorityQueue/PriorityQueue_test.cpp new file mode 100644 index 0000000000..636bee48fd --- /dev/null +++ b/sparta/test/PriorityQueue/PriorityQueue_test.cpp @@ -0,0 +1,210 @@ + +#include "sparta/resources/PriorityQueue.hpp" + +#include +#include +#include + +#include "sparta/utils/SpartaTester.hpp" + +constexpr bool TESTPERF = false; + +void test_defafult_pq() +{ + sparta::PriorityQueue pqueue; + + for(auto i : {1,3,2,5,6,4,8,7}) { + pqueue.insert(i); + } + + EXPECT_EQUAL(pqueue.size(), 8); + + EXPECT_EQUAL(pqueue.top(), 1); + pqueue.pop(); // 1 + + EXPECT_FALSE(pqueue.empty()); + + EXPECT_EQUAL(pqueue.top(), 2); + + pqueue.insert(100); + + EXPECT_EQUAL(pqueue.top(), 2); + + pqueue.remove(5); + pqueue.pop(); // 2 + pqueue.pop(); // 3 + pqueue.pop(); // 4 + EXPECT_EQUAL(pqueue.top(), 6); + + pqueue.forceFront(500); + EXPECT_EQUAL(pqueue.top(), 500); + pqueue.pop(); // 500 + + EXPECT_EQUAL(pqueue.top(), 6); + + while(!pqueue.empty()) { + pqueue.pop(); + } + + EXPECT_THROW(pqueue.pop()); + + pqueue.insert(100); + EXPECT_EQUAL(pqueue.top(), 100); + + pqueue.clear(); + EXPECT_THROW(pqueue.top()); + EXPECT_THROW(pqueue.back()); + EXPECT_THROW(pqueue.pop()); + + // Removing from an empty queue does nothing + EXPECT_NOTHROW(pqueue.remove(10)); + + pqueue.insert(100); + EXPECT_EQUAL(pqueue.top(), 100); + EXPECT_EQUAL(pqueue.size(), 1); + + auto it = pqueue.begin(); + EXPECT_EQUAL(*it, 100); + pqueue.erase(it); + EXPECT_EQUAL(pqueue.size(), 0); + + pqueue.insert(100); + EXPECT_EQUAL(pqueue.top(), 100); + EXPECT_EQUAL(pqueue.size(), 1); + + const auto pqueue_const_ptr = static_cast *>(&pqueue); + static_assert(std::is_const_v, "Why not const?"); + + auto cit = pqueue_const_ptr->begin(); + static_assert(std::is_same_v::const_iterator >, + "Why iterator not const?"); + pqueue.erase(cit); + EXPECT_EQUAL(pqueue.size(), 0); + +} + +class DynamicSorter +{ +public: + + DynamicSorter(DynamicSorter * sorter) : + dyn_sorter_(sorter) + {} + + bool operator()(const int32_t existing, const int32_t to_be_inserted) const + { + return dyn_sorter_->choose(existing, to_be_inserted); + } + + bool choose(const int32_t existing, const int32_t to_be_inserted) { + if(smaller_first_) { + return existing > to_be_inserted; + } + else { + return existing < to_be_inserted; + } + } + + void toggleSmallerFirst() { + smaller_first_ = !smaller_first_; + } + +private: + DynamicSorter * dyn_sorter_ = this; + bool smaller_first_ = false; +}; + +void test_custom_order_pq() +{ + DynamicSorter dyn_sorter(nullptr); + + sparta::PriorityQueue pqueue({DynamicSorter(&dyn_sorter)}); + + for(auto i : {1,3,2,-5,6,4,-8,7,-3,8,5,-7}) { + pqueue.insert(i); + } + + // for(auto i : pqueue) { + // std::cout << i << std::endl; + // } + + EXPECT_EQUAL(pqueue.top(), -8); + pqueue.pop(); + EXPECT_EQUAL(pqueue.top(), -7); + + pqueue.insert(10); + EXPECT_EQUAL(pqueue.top(), -7); + + dyn_sorter.toggleSmallerFirst(); + + pqueue.insert(11); + EXPECT_EQUAL(pqueue.top(), 11); + + // for(auto i : pqueue) { + // std::cout << i << std::endl; + // } +} + +#define PERF_TEST 100000000 +template +void testListPerf() +{ + ListType fl; + const int num_elems = 10; + for(int i = 0; i < PERF_TEST; ++i) { + for(size_t i = 0; i < num_elems; ++i) { + fl.insert(i); + } + + const auto end = fl.end(); + for(auto it = fl.begin(); it != end;) { + fl.erase(it++); + } + } +} + +void test_fastlist_vs_list() +{ + // Uses sparta::FastList + sparta::PriorityQueue, 10> bounded_pq; + for(auto i : {1,3,2,-7,6,4,-8,7,-3,8}) { + bounded_pq.insert(i); + } + + EXPECT_EQUAL(bounded_pq.top(), -8); + bounded_pq.pop(); + EXPECT_EQUAL(bounded_pq.top(), -7); + + bounded_pq.insert(10); + EXPECT_EQUAL(bounded_pq.top(), -7); + + // out of room + EXPECT_THROW(bounded_pq.insert(11)); + + if constexpr(TESTPERF) + { + auto start = std::chrono::system_clock::system_clock::now(); + testListPerf, 10>>(); + auto end = std::chrono::system_clock::system_clock::now(); + auto dur = std::chrono::duration_cast(end - start).count(); + std::cout << "Raw time (seconds) fast list : " << dur / 1000000.0 << std::endl; + + start = std::chrono::system_clock::system_clock::now(); + testListPerf>(); + end = std::chrono::system_clock::system_clock::now(); + dur = std::chrono::duration_cast(end - start).count(); + std::cout << "Raw time (seconds) old list : " << dur / 1000000.0 << std::endl; + } +} + + +int main() +{ + test_defafult_pq(); + test_custom_order_pq(); + + test_fastlist_vs_list(); + + REPORT_ERROR; + return ERROR_CODE; +}