From a0f965f67b7bc93ed2144fb2322f2dda79361837 Mon Sep 17 00:00:00 2001 From: jeffnye-gh <112903691+jeffnye-gh@users.noreply.github.com> Date: Mon, 15 Apr 2024 11:49:15 -0500 Subject: [PATCH] 1st commit of fusion capable decoder, json support (#163) fusion decoder implementation. This is suitable for analysis. Adds fusion parameters to fusion.yaml. And specifies external file definition of fusion groups using JSON New statistics for fused instructions and fusion group stats HCache as separate structure in fusion/fusion Includes the FSL.md domain language description Inst.hpp modified to add FUSED and FUSION_GHOST status as extended status Support for non-sequential program ID. Each instr has a PID increment value, and methods Formatting, clean up of Inst.hpp JSON support added to Fusion.hpp New exceptions for JSON errors Fusion default transform no longer modifies the input buffer HCache testbench functions --------- Co-authored-by: Jeff Nye --- CMakeLists.txt | 4 +- arches/fusion.yaml | 13 + arches/fusion/dhrystone.json | 37 + core/CMakeLists.txt | 1 + core/Decode.cpp | 146 +++- core/Decode.hpp | 311 ++++++- core/FusionDecode.cpp | 228 ++++++ core/Inst.cpp | 14 + core/Inst.hpp | 136 ++- core/InstGroup.hpp | 8 + core/ROB.cpp | 16 +- fusion/CMakeLists.txt | 26 +- fusion/Doxyfile | 2 + fusion/FSL.md | 1231 ++++++++++++++++++++++++++++ fusion/README.md | 8 +- fusion/fusion/FieldExtractor.hpp | 106 +-- fusion/fusion/Fusion.hpp | 340 +++++++- fusion/fusion/FusionContext.hpp | 181 ++-- fusion/fusion/FusionExceptions.hpp | 143 +++- fusion/fusion/FusionGroup.hpp | 547 ++++++------ fusion/fusion/FusionTypes.hpp | 80 +- fusion/fusion/HCache.hpp | 164 ++++ fusion/fusion/Instruction.hpp | 264 +++--- fusion/fusion/MachineInfo.hpp | 4 +- fusion/fusion/RadixTrie.hpp | 23 +- fusion/fusion/uArchInfo.hpp | 349 ++++---- fusion/test/CMakeLists.txt | 23 +- fusion/test/FslTests.cpp | 103 +++ fusion/test/Msg.hpp | 38 +- fusion/test/Options.cpp | 19 +- fusion/test/Options.hpp | 11 +- fusion/test/TestBench.cpp | 200 ++++- fusion/test/TestBench.hpp | 98 ++- fusion/test/TestData.cpp | 5 + fusion/test/TestFieldExtractor.cpp | 15 +- fusion/test/main.cpp | 6 + test/CMakeLists.txt | 1 + test/fusion/CMakeLists.txt | 21 + 38 files changed, 3958 insertions(+), 964 deletions(-) create mode 100644 arches/fusion.yaml create mode 100644 arches/fusion/dhrystone.json create mode 100644 core/FusionDecode.cpp create mode 100644 fusion/FSL.md create mode 100644 fusion/fusion/HCache.hpp create mode 100644 fusion/test/FslTests.cpp create mode 100644 test/fusion/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 4fc4298b..09e6c25b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,13 +61,15 @@ set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g") # Include directories include_directories (core mss sim) +include_directories (SYSTEM fusion) include_directories (SYSTEM mavis) include_directories (SYSTEM stf_lib) -# Mavis, the Core, MSS, and the simulator +# Mavis, the Core, MSS, the simulator and Fusion add_subdirectory (mavis) add_subdirectory (core) add_subdirectory (mss) +add_subdirectory (fusion) # Add STF library to the build add_subdirectory (${STF_LIB_BASE}) diff --git a/arches/fusion.yaml b/arches/fusion.yaml new file mode 100644 index 00000000..654de05a --- /dev/null +++ b/arches/fusion.yaml @@ -0,0 +1,13 @@ +include: big_core.yaml +top.cpu.core0: + decode: + params: + num_to_decode: 8 + fusion_enable: true + fusion_debug: false + fusion_enable_register: 0xFFFFFFFF + fusion_max_latency: 8 + fusion_match_max_tries: 1023 + fusion_max_group_size: 8 + fusion_summary_report: fusion_summary.txt + fusion_group_definitions: [ arches/fusion/dhrystone.json ] diff --git a/arches/fusion/dhrystone.json b/arches/fusion/dhrystone.json new file mode 100644 index 00000000..3672f241 --- /dev/null +++ b/arches/fusion/dhrystone.json @@ -0,0 +1,37 @@ +{ + "fusiongroups" : [ + { "name" : "uf039", "uids" : ["0xd","0xa"], "tx" : "dfltXform_" }, + { "name" : "uf038", "uids" : ["0x3","0xe"], "tx" : "dfltXform_" }, + { "name" : "uf037", "uids" : ["0x20","0x4"], "tx" : "dfltXform_" }, + { "name" : "uf036", "uids" : ["0x9","0x2d"], "tx" : "dfltXform_" }, + { "name" : "uf035", "uids" : ["0x18","0xe"], "tx" : "dfltXform_" }, + { "name" : "uf034", "uids" : ["0x20","0x18"], "tx" : "dfltXform_" }, + { "name" : "uf033", "uids" : ["0xe","0xd","0xa"], "tx" : "dfltXform_" }, + { "name" : "uf032", "uids" : ["0x10","0x10"], "tx" : "dfltXform_" }, + { "name" : "uf031", "uids" : ["0x18","0x20"], "tx" : "dfltXform_" }, + { "name" : "uf030", "uids" : ["0x22","0x26"], "tx" : "dfltXform_" }, + { "name" : "uf029", "uids" : ["0x26","0x34"], "tx" : "dfltXform_" }, + { "name" : "uf028", "uids" : ["0x21","0x20"], "tx" : "dfltXform_" }, + { "name" : "uf027", "uids" : ["0x34","0x35"], "tx" : "dfltXform_" }, + { "name" : "uf026", "uids" : ["0x2d","0x22"], "tx" : "dfltXform_" }, + { "name" : "uf025", "uids" : ["0x2e","0x2d"], "tx" : "dfltXform_" }, + { "name" : "uf024", "uids" : ["0x2e","0x21"], "tx" : "dfltXform_" }, + { "name" : "uf023", "uids" : ["0xd","0xa","0x22"], "tx" : "dfltXform_" }, + { "name" : "uf022", "uids" : ["0x26","0x34","0x9"], "tx" : "dfltXform_" }, + { "name" : "uf021", "uids" : ["0xa","0x22","0x26"], "tx" : "dfltXform_" }, + { "name" : "uf020", "uids" : ["0x18","0x20","0x4"], "tx" : "dfltXform_" }, + { "name" : "uf019", "uids" : ["0x22","0x26","0x34"], "tx" : "dfltXform_" }, + { "name" : "uf018", "uids" : ["0x2e","0x21","0x20"], "tx" : "dfltXform_" }, + { "name" : "uf017", "uids" : ["0x21","0x20","0x18"], "tx" : "dfltXform_" }, + { "name" : "uf016", "uids" : ["0x20","0x18","0x20"], "tx" : "dfltXform_" }, + { "name" : "uf008", "uids" : ["0xd","0x35"], "tx" : "dfltXform_" }, + { "name" : "uf007", "uids" : ["0xa","0x22"], "tx" : "dfltXform_" }, + { "name" : "uf005", "uids" : ["0xe","0xd"], "tx" : "dfltXform_" }, + { "name" : "uf004", "uids" : ["0xe","0x34"], "tx" : "dfltXform_" }, + { "name" : "uf003", "uids" : ["0x34","0x9"], "tx" : "dfltXform_" }, + { "name" : "uf002", "uids" : ["0x2e","0x35"], "tx" : "dfltXform_" }, + { "name" : "uf001", "uids" : ["0x35","0x35"], "tx" : "dfltXform_" }, + { "name" : "uf213", "uids" : ["0x2e","0x2e"], "tx" : "dfltXform_" }, + { "name" : "uf000", "uids" : ["0x35","0x2e"], "tx" : "dfltXform_" } + ] +} diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index ce27d253..0b2daaac 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -1,5 +1,6 @@ project (core) add_library(core + FusionDecode.cpp Core.cpp SimpleBranchPred.cpp Fetch.cpp diff --git a/core/Decode.cpp b/core/Decode.cpp index 62752ce5..68182296 100644 --- a/core/Decode.cpp +++ b/core/Decode.cpp @@ -1,45 +1,94 @@ // -*- C++ -*- - -#include - #include "Decode.hpp" +#include "fusion/FusionTypes.hpp" #include "sparta/events/StartupEvent.hpp" #include "sparta/utils/LogUtils.hpp" +#include +#include + +using namespace std; + namespace olympia { constexpr char Decode::name[]; - Decode::Decode(sparta::TreeNode * node, - const DecodeParameterSet * p) : + Decode::Decode(sparta::TreeNode* node, const DecodeParameterSet* p) : sparta::Unit(node), + fetch_queue_("FetchQueue", p->fetch_queue_size, node->getClock(), &unit_stat_set_), - num_to_decode_(p->num_to_decode) + + fusion_num_fuse_instructions_(&unit_stat_set_, "fusion_num_fuse_instructions", + "The number of custom instructions created by fusion", + sparta::Counter::COUNT_NORMAL), + + fusion_num_ghost_instructions_(&unit_stat_set_, "fusion_num_ghost_instructions", + "The number of instructions eliminated by fusion", + sparta::Counter::COUNT_NORMAL), + + fusion_num_groups_defined_(&unit_stat_set_, "fusion_num_groups_defined", + "Number of fusion groups compiled or read at run time", + sparta::Counter::COUNT_LATEST), + + fusion_num_groups_utilized_(&unit_stat_set_, "fusion_num_groups_utilized", + "Incremented on first use of a fusion group", + sparta::Counter::COUNT_LATEST), + + fusion_pred_cycles_saved_(&unit_stat_set_, "fusion_pred_cycles_saved", + "Optimistic prediction of the cycles saved by fusion", + sparta::Counter::COUNT_NORMAL), + + num_to_decode_(p->num_to_decode), + fusion_enable_(p->fusion_enable), + fusion_debug_(p->fusion_debug), + fusion_enable_register_(p->fusion_enable_register), + fusion_max_latency_(p->fusion_max_latency), + fusion_match_max_tries_(p->fusion_match_max_tries), + fusion_max_group_size_(p->fusion_max_group_size), + fusion_summary_report_(p->fusion_summary_report), + fusion_group_definitions_(p->fusion_group_definitions) { + initializeFusion_(); + fetch_queue_.enableCollection(node); - fetch_queue_write_in_. - registerConsumerHandler(CREATE_SPARTA_HANDLER_WITH_DATA(Decode, fetchBufferAppended_, InstGroupPtr)); - uop_queue_credits_in_. - registerConsumerHandler(CREATE_SPARTA_HANDLER_WITH_DATA(Decode, receiveUopQueueCredits_, uint32_t)); - in_reorder_flush_. - registerConsumerHandler(CREATE_SPARTA_HANDLER_WITH_DATA(Decode, handleFlush_, FlushManager::FlushingCriteria)); + fetch_queue_write_in_.registerConsumerHandler( + CREATE_SPARTA_HANDLER_WITH_DATA(Decode, fetchBufferAppended_, InstGroupPtr)); + uop_queue_credits_in_.registerConsumerHandler( + CREATE_SPARTA_HANDLER_WITH_DATA(Decode, receiveUopQueueCredits_, uint32_t)); + in_reorder_flush_.registerConsumerHandler( + CREATE_SPARTA_HANDLER_WITH_DATA(Decode, handleFlush_, FlushManager::FlushingCriteria)); sparta::StartupEvent(node, CREATE_SPARTA_HANDLER(Decode, sendInitialCredits_)); } // Send fetch the initial credit count - void Decode::sendInitialCredits_() + void Decode::sendInitialCredits_() { fetch_queue_credits_outp_.send(fetch_queue_.capacity()); } + + // ------------------------------------------------------------------- + // ------------------------------------------------------------------- + void Decode::initializeFusion_() { - fetch_queue_credits_outp_.send(fetch_queue_.capacity()); + if (fusion_enable_) + { + fuser_ = std::make_unique(fusion_group_definitions_); + hcache_ = fusion::HCache(FusionGroupType::jenkins_1aat); + fusion_num_groups_defined_ = fuser_->getFusionGroupContainer().size(); + } + else + { + fuser_ = nullptr; + } } // Receive Uop credits from Dispatch - void Decode::receiveUopQueueCredits_(const uint32_t & credits) { + void Decode::receiveUopQueueCredits_(const uint32_t & credits) + { uop_queue_credits_ += credits; - if (fetch_queue_.size() > 0) { + if (fetch_queue_.size() > 0) + { ev_decode_insts_event_.schedule(sparta::Clock::Cycle(0)); } @@ -52,12 +101,13 @@ namespace olympia void Decode::fetchBufferAppended_(const InstGroupPtr & insts) { // Cache the instructions in the instruction queue if we can't decode this cycle - for(auto & i : *insts) + for (auto & i : *insts) { fetch_queue_.push(i); ILOG("Received: " << i); } - if (uop_queue_credits_ > 0) { + if (uop_queue_credits_ > 0) + { ev_decode_insts_event_.schedule(sparta::Clock::Cycle(0)); } } @@ -76,25 +126,76 @@ namespace olympia uint32_t num_decode = std::min(uop_queue_credits_, fetch_queue_.size()); num_decode = std::min(num_decode, num_to_decode_); - if(num_decode > 0) + // buffer to maximize the chances of a group match limited + // by max allowed latency, bounded by max group size + if (fusion_enable_) + { + if (num_decode < fusion_max_group_size_ && latency_count_ < fusion_max_latency_) + { + ++latency_count_; + return; + } + } + + latency_count_ = 0; + + if (num_decode > 0) { InstGroupPtr insts = sparta::allocate_sparta_shared_pointer(instgroup_allocator); + + InstUidListType uids; // Send instructions on their way to rename - for(uint32_t i = 0; i < num_decode; ++i) { + for (uint32_t i = 0; i < num_decode; ++i) + { const auto & inst = fetch_queue_.read(0); insts->emplace_back(inst); inst->setStatus(Inst::Status::DECODED); + if (fusion_enable_) + { + uids.push_back(inst->getMavisUid()); + } + ILOG("Decoded: " << inst); fetch_queue_.pop(); } + if (fusion_enable_) + { + MatchInfoListType matches; + uint32_t max_itrs = 0; + FusionGroupContainerType & container = fuser_->getFusionGroupContainer(); + do + { + matchFusionGroups_(matches, insts, uids, container); + processMatches_(matches, insts, uids); + // Future feature whereIsEgon(insts,numGhosts); + ++max_itrs; + } while (matches.size() > 0 && max_itrs < fusion_match_max_tries_); + + if (max_itrs >= fusion_match_max_tries_) + { + throw sparta::SpartaException("Fusion group match watch dog exceeded."); + } + } + + // Debug statement + if (fusion_debug_ && fusion_enable_) + infoInsts_(cout, insts); // Send decoded instructions to rename uop_queue_outp_.send(insts); + // TODO: whereisegon() would remove the ghosts, + // Commented out for now, in practice insts + // would be smaller due to the fused ops + // uint32_t unfusedInstsSize = insts->size(); + // Decrement internal Uop Queue credits + sparta_assert(uop_queue_credits_ >= insts->size(), + "Attempt to decrement d0q credits below what is available"); + uop_queue_credits_ -= insts->size(); // Send credits back to Fetch to get more instructions @@ -103,8 +204,9 @@ namespace olympia // If we still have credits to send instructions as well as // instructions in the queue, schedule another decode session - if(uop_queue_credits_ > 0 && fetch_queue_.size() > 0) { + if (uop_queue_credits_ > 0 && fetch_queue_.size() > 0) + { ev_decode_insts_event_.schedule(1); } } -} +} // namespace olympia diff --git a/core/Decode.hpp b/core/Decode.hpp index 73b600bc..ee4e4493 100644 --- a/core/Decode.hpp +++ b/core/Decode.hpp @@ -1,18 +1,29 @@ // -*- C++ -*- +//! \file Decode.hpp +#pragma once +#include "CoreTypes.hpp" +#include "FlushManager.hpp" +#include "InstGroup.hpp" +#include "fusion/FieldExtractor.hpp" +#include "fusion/Fusion.hpp" +#include "fusion/FusionGroup.hpp" +#include "fusion/FusionTypes.hpp" +#include "fusion/HCache.hpp" +#include "fusion/MachineInfo.hpp" -#pragma once - -#include #include "sparta/ports/DataPort.hpp" #include "sparta/events/UniqueEvent.hpp" #include "sparta/simulation/Unit.hpp" #include "sparta/simulation/TreeNode.hpp" #include "sparta/simulation/ParameterSet.hpp" -#include "CoreTypes.hpp" -#include "FlushManager.hpp" -#include "InstGroup.hpp" + +#include +#include +#include +#include +#include namespace olympia { @@ -27,17 +38,88 @@ namespace olympia */ class Decode : public sparta::Unit { - public: + + //! \brief ... + using FusionGroupType = fusion::FusionGroup; + //! \brief ... + using FusionType = fusion::Fusion; + //! \brief ... + using FusionGroupListType = std::vector; + //! \brief ... + using FusionGroupContainerType = std::unordered_map; + //! \brief ... + using FusionGroupCfgType = fusion::FusionGroupCfg; + //! \brief ... + using FusionGroupCfgListType = std::vector; + //! \brief ... + using InstUidListType = fusion::InstUidListType; + //! \brief the is the return reference type for group matches + using MatchInfoListType = std::vector; + //! \brief ... + using HashPair = pair; + //! \brief ... + using HashPairListType = std::vector; + //! \brief ... + using FileNameListType = fusion::FileNameListType; + + public: //! \brief Parameters for Decode model class DecodeParameterSet : public sparta::ParameterSet { - public: - DecodeParameterSet(sparta::TreeNode* n) : - sparta::ParameterSet(n) - { } + public: + DecodeParameterSet(sparta::TreeNode* n) : sparta::ParameterSet(n) {} - PARAMETER(uint32_t, num_to_decode, 4, "Number of instructions to process") + //! \brief width of decoder + PARAMETER(uint32_t, num_to_decode, 4, "Decode group size") + + //! \brief depth of the input instruction buffer PARAMETER(uint32_t, fetch_queue_size, 10, "Size of the fetch queue") + + //! \brief enable fusion operations + //! + //! master enable, when false fusion_* parmeters have no effect + PARAMETER(bool, fusion_enable, false, "enable the fusion logic") + + //! \brief enable fusion specific debug statements + PARAMETER(bool, fusion_debug, false, "enable fusion debug logic") + + //! \brief fusion group enable register + //! + //! This is a bit mask that enables each fusion transform + //! Rather than a series of parameters for each fusion group + //! this uses one bit per fusion group. Default is all on. + //! + //! \see fusion_enable_register_ for the encoding + //! in the yaml to choose a transform my name + PARAMETER(uint32_t, fusion_enable_register, + std::numeric_limits::max(), + "bit-wise fusion group enable") + + //! \brief max acceptable latency created by gathering uops + //! + //! Depending on the max fusion tuple size uops will be gathered, + //! when downstream is not busy this introduces some latency. + //! This knob allows studying the effects. fusion_enabled must + //! be true + PARAMETER(uint32_t, fusion_max_latency, 4, "max fusion latency") + + //! \brief fusion group match max iterations + PARAMETER(uint32_t, fusion_match_max_tries, 1023, "fusion group match attempts limit") + + //! \brief records the largest fusion group size + //! + //! Used with fusion_max_latency to gather instructions for + //! possible fusion. If number of decode is >= fusion group size + //! there is no need to continue to gather instructions. + PARAMETER(uint32_t, fusion_max_group_size, 4, "max fusion group isze") + + //! \brief ... + PARAMETER(std::string, fusion_summary_report, "fusion_summary.txt", + "Path to fusion report file") + + //! \brief ... + PARAMETER(FileNameListType, fusion_group_definitions, {}, + "Lists of fusion group UID json files") }; /** @@ -46,41 +128,216 @@ namespace olympia * @param node The node that represents (has a pointer to) the Decode * @param p The Decode's parameter set */ - Decode(sparta::TreeNode * node, - const DecodeParameterSet * p); + Decode(sparta::TreeNode* node, const DecodeParameterSet* p); //! \brief Name of this resource. Required by sparta::UnitFactory static constexpr char name[] = "decode"; - private: - + private: // The internal instruction queue InstQueue fetch_queue_; // Port listening to the fetch queue appends - Note the 1 cycle delay - sparta::DataInPort fetch_queue_write_in_ {&unit_port_set_, "in_fetch_queue_write", 1}; - sparta::DataOutPort fetch_queue_credits_outp_ {&unit_port_set_, "out_fetch_queue_credits"}; + sparta::DataInPort fetch_queue_write_in_{&unit_port_set_, + "in_fetch_queue_write", 1}; + sparta::DataOutPort fetch_queue_credits_outp_{&unit_port_set_, + "out_fetch_queue_credits"}; // Port to the uop queue in dispatch (output and credits) - sparta::DataOutPort uop_queue_outp_ {&unit_port_set_, "out_uop_queue_write"}; - sparta::DataInPort uop_queue_credits_in_{&unit_port_set_, "in_uop_queue_credits", sparta::SchedulingPhase::Tick, 0}; + sparta::DataOutPort uop_queue_outp_{&unit_port_set_, "out_uop_queue_write"}; + sparta::DataInPort uop_queue_credits_in_{&unit_port_set_, "in_uop_queue_credits", + sparta::SchedulingPhase::Tick, 0}; // For flush - sparta::DataInPort in_reorder_flush_ - {&unit_port_set_, "in_reorder_flush", sparta::SchedulingPhase::Flush, 1}; + sparta::DataInPort in_reorder_flush_{ + &unit_port_set_, "in_reorder_flush", sparta::SchedulingPhase::Flush, 1}; + + //! \brief decode instruction event + sparta::UniqueEvent<> ev_decode_insts_event_{&unit_event_set_, "decode_insts_event", + CREATE_SPARTA_HANDLER(Decode, decodeInsts_)}; + + //! \brief initialize the fusion group configuration + void constructFusionGroups_(); + + //! \brief compare the dynamic uid vector to the predefined groups + //! + //! returns a sorted list of matches + //! Matches the fusionGroup sets against the input. There is a continuation + //! check invoked if the fusionGroup is larger than the input list. + //! FusionGroups must exactly match a segment of the input. + //! + //! During construction each fusion group has a pre-calculated Hash + //! created from the UIDs in the group. + //! + //! The group hash is matched against hashes formed from the input UIDs. + //! + //! In order to match the input to the group, hashes must be created of + //! the input. + //! + //! A 'cache' of the inputUids is created on entry. The cache is indexed + //! by group size. The cache data is a vector of pairs, pair.first is the + //! input position within inputUids, pair.second is the hash of the + //! inputUids in that segment. + void matchFusionGroups_(MatchInfoListType & matches, InstGroupPtr & insts, + InstUidListType & inputUIDS, FusionGroupContainerType &); + + //! \brief process the fusion matches + void processMatches_(MatchInfoListType &, InstGroupPtr & insts, + const InstUidListType & inputUIDS); - // The decode instruction event - sparta::UniqueEvent<> ev_decode_insts_event_ {&unit_event_set_, "decode_insts_event", CREATE_SPARTA_HANDLER(Decode, decodeInsts_)}; + //! \brief Remove the ghost ops + //! + //! The matching pass identifies the master fusion op (FUSED) + //! and the ops that will be eliminated (FUSION_GHOST). + //! This pass removes the ghosts. Future feature: not currently used. + void whereIsEgon_(InstGroupPtr &, uint32_t &); + + //! \brief ... + void infoInsts_(std::ostream & os, const InstGroupPtr & insts); + + //! \brief initialize the fusion api structures + //! + //! This can construct the FusionGroup lists using multiple + //! methods, as static objects, as objects read from files, etc. + //! + //! This called a small number of support methods. + void initializeFusion_(); + + //! \brief assign the current fusion group configs to a named context + //! + //! The Fusion API supports multiple contexts. There is a single + //! context in CAM at the moment. + //! + //! This method constructs the context and throws if there are + //! problems detected during construction + void setFusionContext_(FusionGroupListType &); + + //! \brief fusion groups are contructed statically or from DSL files + void constructGroups_(const FusionGroupCfgListType &, FusionGroupListType &); + + //! \brief record stats for fusionGroup name + void updateFusionGroupUtilization_(std::string name); + + //! \brief number of custom instructions created for fusion + sparta::Counter fusion_num_fuse_instructions_; + + //! \brief number of instructions eliminated by fusion + sparta::Counter fusion_num_ghost_instructions_; + + //! \brief the number of entries in the fusion group struct + //! + //! This is a run-time determined value extracted from parsing + //! the JSON files. Future feature: FSL file support + sparta::Counter fusion_num_groups_defined_; + + //! \brief the number of fusion groups utilizaed + //! + //! Some number of run-time or compile-time fusion groups + //! are defined. + //! + //! This is a measure of how many defined groups were + //! used during execution. A fusion group used 100 times + //! counts only once. + sparta::Counter fusion_num_groups_utilized_; + + //! \brief the predicted number of cycles saved by fusion + //! + //! For the simplist case a fusion group of N instructions + //! saves N-1 cycles. Modified for the cases where some + //! instructions and some fusion groups are multi-cycle. + //! + //! This is a prediction without concern for pipeline + //! behavior. This is compared against measured cycles + //! with fusion enabled vs. disabled to show effectiveness + //! of the defined fusion groups. + sparta::Counter fusion_pred_cycles_saved_; + + //! \brief temporary for number of instructions to decode this time + const uint32_t num_to_decode_; + + //! \brief master fusion enable data member + //! + //! This is the model's fusion enable parameter. When not enabled + //! the fusion methods have no impact on function or performance + //! of the model. \see fusion_enable_register_; + const bool fusion_enable_{false}; + + //! \brief fusion debug switch setting + //! + //! There are infrequent debug related statements that are gated + //! with this parameter. + const bool fusion_debug_{false}; + + //! \brief represents the FER in h/w + //! + //! The register holds an encoded value. Each fusion group + //! has an associated page, and an enable. The upper 4 bits + //! represent a page, the lower 28 bits are the enables. + //! PAGE=0xF all enabled, PAGE=0x0 all disabled. This is preferred + //! over a separate page register and enable register. + //! + //! Currently defined but not used + const uint32_t fusion_enable_register_{0}; + + //! \brief maximum allowed latency induced by fusion + //! + //! This typically zero, a parameter is provided to allow + //! experimenting with instruction gathering + const uint32_t fusion_max_latency_{0}; + + //! \brief max attempts to match UIDs of instrs in the XIQ + const uint32_t fusion_match_max_tries_{0}; + + //! \brief the longest chain of UIDs across all groups + //! + //! \see FusionGroup + uint32_t fusion_max_group_size_{0}; + + //! \brief running count of fusion latency + uint32_t latency_count_{0}; + + //! \brief the Fusion API instance + //! + //! \see Fusion.hpp + std::unique_ptr fuser_{nullptr}; + + //! \brief fusion group matching hash + fusion::HCache hcache_; + + //! \brief fusion function object callback proxies + struct cbProxy_; + //! \brief this counts the number of times a group is used + std::map matchedFusionGroups_; + //! \brief fusion specific report file name + const std::string fusion_summary_report_; + //! \brief the fusion group definition files, JSON or (future) FSL + const std::vector fusion_group_definitions_; ////////////////////////////////////////////////////////////////////// // Decoder callbacks void sendInitialCredits_(); - void fetchBufferAppended_ (const InstGroupPtr &); + void fetchBufferAppended_(const InstGroupPtr &); void receiveUopQueueCredits_(const uint32_t &); void decodeInsts_(); void handleFlush_(const FlushManager::FlushingCriteria & criteria); uint32_t uop_queue_credits_ = 0; - const uint32_t num_to_decode_; }; -} + + //! \brief the fusion functor/function objects + //! + //! there is one function object assigned to each group, + //! the objects can be shared across groups + struct Decode::cbProxy_ + { + //! \brief this is the default 'no fusion' operation + //! + //! false indicates no transform was performed + static bool dfltXform_(FusionGroupType &, fusion::InstPtrListType &, + fusion::InstPtrListType &) + { + return false; + } + }; + +} // namespace olympia diff --git a/core/FusionDecode.cpp b/core/FusionDecode.cpp new file mode 100644 index 00000000..bc2cb34b --- /dev/null +++ b/core/FusionDecode.cpp @@ -0,0 +1,228 @@ +// TODO: add Condor header - this is a new file +// contact jeff at condor +#include "fusion/FusionTypes.hpp" +#include "Decode.hpp" + +#include "sparta/events/StartupEvent.hpp" +#include "sparta/utils/LogUtils.hpp" + +#include +#include +#include +#include +#include + +using namespace std; +using namespace fusion; + +namespace olympia +{ + // ------------------------------------------------------------------- + // Remove the ghost fusion ops + // Kept, but currently not called + // ------------------------------------------------------------------- + void Decode::whereIsEgon_(InstGroupPtr & insts, uint32_t & numGhosts) + { + if (insts == nullptr) + { + // DLOG("whereIsEgon() called with insts null"); + return; + } + + // DLOG("BEFORE # --------------------------------------"); + // infoInsts(cout,insts); + + for (auto it = insts->begin(); it != insts->end();) + { + if ((*it)->getExtendedStatus() == olympia::Inst::Status::FUSION_GHOST) + { + ++numGhosts; + it = insts->erase(it); + } + else + { + ++it; + } + } + // DLOG("AFTER # --------------------------------------"); + // infoInsts(cout,insts); + // DLOG("DONE # --------------------------------------"); + } + + // ------------------------------------------------------------------- + // ------------------------------------------------------------------- + void Decode::processMatches_(MatchInfoListType & matches, InstGroupPtr & insts, + const InstUidListType & inputUids) + { + if (matches.size() == 0) + return; + + // Kept for upcoming additional work on reducing ROB entry requirements + // DLOG("PROCESS MATCHES "<infoUids(cout,inputUids,"before "); cout<info(cout,matches,inputUids); cout<begin(); + std::advance(itr, thisMatch.startIdx); + if ((*itr)->getExtendedStatus() == Inst::Status::UNMOD) + { + (*itr)->setExtendedStatus(Inst::Status::FUSED); + ++fusion_num_fuse_instructions_; + // Kept for upcoming additional work on reducing ROB + // entry requirements. This manages the sequential expectations + // of the ROB for program ID. + //(*itr)->setProgramIDIncrement(thisMatch.size()); + + for (size_t i = 1; i < thisMatch.size(); ++i) + { + ++itr; + sparta_assert(itr != insts->end(), + "processMatches inst iterator exceeded range"); + (*itr)->setExtendedStatus(Inst::Status::FUSION_GHOST); + ++fusion_num_ghost_instructions_; + ++fusion_pred_cycles_saved_; + } + updateFusionGroupUtilization_(thisMatch.name); + } + } + + matches.clear(); + } + + // ---------------------------------------------------------------------- + // TODO: this performs well for the FusionGroup set sizes tested, ~256. + // More testing is intended for 2048 (10x any practical size). + // Further improvement is deferred until a decsion on uop/trace cache + // implementations. Regardless, if warranted for model performance, + // an abstracted uop$ can be built using what is below adding the more + // conventional address indexing but retaining the UIDs as abstractions + // to fully decoded instrs. + // ---------------------------------------------------------------------- + void Decode::matchFusionGroups_(MatchInfoListType & matches, InstGroupPtr & insts, + InstUidListType & inputUids, + FusionGroupContainerType & fusionGroups) + { + matches.clear(); // Clear any existing matches + + // The cache is a map of cachelines, indexed by size, + // + // + // 3 1:hash 2:hash 3:hash ... modulo size, inputUids.size() % 3 + hcache_.clear(); + + for (auto & fgPair : fusionGroups) + { + auto & fGrp = fgPair.second; + size_t grpSize = fGrp.uids().size(); + hcache_.buildHashCacheEntry(inputUids, grpSize); + } + + for (auto & fgPair : fusionGroups) + { + + HashType grpHash = fgPair.first; + auto & fGrp = fgPair.second; + size_t grpSize = fGrp.uids().size(); + + // No match possible if the fg is bigger than the input + if (grpSize > inputUids.size()) + { + /*++filtered;*/ continue; + } + + // If the grpSize is not in the hash cache we need to calculate it + if (hcache_.find(grpSize) == hcache_.end()) + { + hcache_.buildHashCacheEntry(inputUids, grpSize); + } + + // If this fires something unexplained happened, the entry hit + // in the first place or it was constructed on a miss, + // either way this should never fire. + sparta_assert(hcache_.find(grpSize) != hcache_.end()) + + // pairs is from the cache entries with the same size as fGrp. + // a pair is , index is the position in the input + HashPairListType pairs = hcache_[grpSize]; + + // For each of the pairs that have the same size as fGrp. + for (auto & ep : pairs) + { + // if the pair.hash matches the group's hash + if (ep.second == grpHash) + { + + // DLOG("HASH HIT, index "< rhs.size(); + }); + } + + // ------------------------------------------------------------------------ + // If we get here we know name has been matched, update the stats + // ------------------------------------------------------------------------ + void Decode::updateFusionGroupUtilization_(std::string name) + { + // update the map containing per group utilization counts + // rely on the default behavior of map[] for new entries + matchedFusionGroups_[name]++; + // groups used more than once still only count as one in this stat + fusion_num_groups_utilized_ = matchedFusionGroups_.size(); + } + + // ------------------------------------------------------------------------ + // ------------------------------------------------------------------------ + void Decode::infoInsts_(std::ostream & os, const InstGroupPtr & insts) + { + InstGroup::iterator instItr; + os << "INSTS: "; + for (instItr = insts->begin(); instItr != insts->end(); ++instItr) + { + os << (*instItr)->info() << endl; + } + } + +} // namespace olympia diff --git a/core/Inst.cpp b/core/Inst.cpp index 9a067076..5289be44 100644 --- a/core/Inst.cpp +++ b/core/Inst.cpp @@ -1,9 +1,23 @@ // -*- C++ -*- #include "Inst.hpp" +#include namespace olympia { + const std::unordered_map Inst::status2String = { + { Inst::Status::FETCHED, "FETCHED" }, + { Inst::Status::DECODED, "DECODED" }, + { Inst::Status::RENAMED, "RENAMED" }, + { Inst::Status::DISPATCHED, "DISPATCHED" }, + { Inst::Status::SCHEDULED, "SCHEDULED" }, + { Inst::Status::COMPLETED, "COMPLETED" }, + { Inst::Status::RETIRED, "RETIRED" }, + { Inst::Status::FLUSHED, "FLUSHED" }, + { Inst::Status::UNMOD, "UNMOD" }, + { Inst::Status::FUSED, "FUSED" }, + { Inst::Status::FUSION_GHOST,"FUSION_GHOST" } + }; bool isCallInstruction(const mavis::OpcodeInfo::PtrType & opcode_info) { diff --git a/core/Inst.hpp b/core/Inst.hpp index 3188f5ef..94d43ac7 100644 --- a/core/Inst.hpp +++ b/core/Inst.hpp @@ -19,8 +19,9 @@ #include #include -#include +#include #include +#include namespace olympia { @@ -92,6 +93,9 @@ namespace olympia COMPLETED, RETIRED, FLUSHED, + UNMOD, //no extended status + FUSED, + FUSION_GHOST, __LAST }; @@ -111,12 +115,6 @@ namespace olympia // implement it and let the compiler do it for us for speed. Inst(const Inst & other) = default; - const Status & getStatus() const { return status_state_; } - - bool getCompletedStatus() const { return getStatus() == olympia::Inst::Status::COMPLETED; } - - bool getFlushedStatus() const { return getStatus() == olympia::Inst::Status::FLUSHED; } - void setStatus(Status status) { sparta_assert(status_state_ != status, @@ -134,7 +132,52 @@ namespace olympia } } - InstArchInfo::TargetPipe getPipe() const { return inst_arch_info_->getTargetPipe(); } + const Status & getStatus() const + { + return status_state_; + } + + bool getCompletedStatus() const + { + return getStatus() == olympia::Inst::Status::COMPLETED; + } + + bool getFlushedStatus() const + { + return getStatus() == olympia::Inst::Status::FLUSHED; + } + + void setMispredicted() + { + is_mispredicted_ = true; + } + + // Is this branch instruction mispredicted? + bool isMispredicted() const + { + return is_mispredicted_; + } + + const Status & getExtendedStatus() const + { + return extended_status_state_; + } + + void setExtendedStatus(Status status) + { + sparta_assert( + extended_status_state_ == Inst::Status::UNMOD + || extended_status_state_ == Inst::Status::FUSED + || extended_status_state_ == Inst::Status::FUSION_GHOST, + "Attempt to set unknown extended status : " + << status << " " << *this); + + extended_status_state_ = status; + } + + InstArchInfo::TargetPipe getPipe() const + { + return inst_arch_info_->getTargetPipe(); } // ROB handling -- mark this instruction as the oldest in the machine void setOldest(bool oldest, sparta::Scheduleable* rob_retire_event) @@ -160,11 +203,25 @@ namespace olympia // Set the instruction's Program ID. This ID is specific to // an instruction's retire pointer. The same instruction in a // trace will have the same program ID (as compared to - // UniqueID). + // UniqueID). Fusion changes this a bit see below void setProgramID(uint64_t prog_id) { program_id_ = prog_id; } uint64_t getProgramID() const { return program_id_; } + //A fused operation will modify the program_id_increment_ based on + //the number of instructions fused. A-B-C-D -> fA incr becomes 4 + //This is planned, but not currently used. + void setProgramIDIncrement(uint64_t incr) + { + program_id_increment_ = incr; + } + + //This is planned, but not currently used. + uint64_t getProgramIDIncrement() const + { + return program_id_increment_; + } + // Set the instruction's PC void setPC(sparta::memory::addr_t inst_pc) { inst_pc_ = inst_pc; } @@ -178,9 +235,6 @@ namespace olympia // Branch instruction was taken (always set for JAL/JALR) void setTakenBranch(bool taken) { is_taken_branch_ = taken; } - // Is this branch instruction mispredicted? - bool isMispredicted() const { return is_mispredicted_; } - void setMispredicted() { is_mispredicted_ = true; } // TBD -- add branch prediction void setSpeculative(bool spec) { is_speculative_ = spec; } @@ -192,6 +246,9 @@ namespace olympia uint32_t getOpCode() const { return static_cast(opcode_info_->getOpcode()); } + mavis::InstructionUniqueID getMavisUid() const + { return opcode_info_->getInstructionUniqueID(); } + // Operand information using OpInfoList = mavis::DecodedInstructionInfo::OpInfoList; @@ -263,6 +320,38 @@ namespace olympia const RenameData & getRenameData() const { return rename_data; } + // Duplicates stream operator but does not change EXPECT logs + std::string info() + { + std::string rStatus = "DONTCARE"; + std::string eStatus = "UNKNOWN"; + + if(getExtendedStatus() == Inst::Status::UNMOD) { + eStatus = "UNMOD"; + } else if(getExtendedStatus() == Inst::Status::FUSED) { + eStatus = "FUSED"; + } else if(getExtendedStatus() == Inst::Status::FUSION_GHOST) { + eStatus = "GHOST"; + } + + std::stringstream ss; + + ss << "uid: " << std::dec //<< std::hex << std::setfill('0') + << getUniqueID() + << " pid: " << std::dec //<< std::hex << std::setfill('0') + << getProgramID() + << " mav: 0x" << std::hex << std::setw(2) << std::setfill('0') + << getMavisUid() + << " inc: " << std::dec + << getProgramIDIncrement() + << " pc: 0x" << std::hex << std::setw(8) << std::setfill('0') + << getPC() + << " " << std::setw(10) << std::setfill(' ') << rStatus + << " " << std::setw(12) << std::setfill(' ') << eStatus + << " '" << getDisasm() << "'"; + + return ss.str(); + } private: mavis::OpcodeInfo::PtrType opcode_info_; InstArchInfo::PtrType inst_arch_info_; @@ -273,6 +362,7 @@ namespace olympia bool is_oldest_ = false; uint64_t unique_id_ = 0; // Supplied by Fetch uint64_t program_id_ = 0; // Supplied by a trace Reader or execution backend + uint64_t program_id_increment_ = 1; bool is_speculative_ = false; // Is this instruction soon to be flushed? const bool is_store_; const bool is_transfer_; // Is this a transfer instruction (F2I/I2F) @@ -286,6 +376,7 @@ namespace olympia bool is_taken_branch_ = false; sparta::Scheduleable* ev_retire_ = nullptr; Status status_state_; + Status extended_status_state_{Inst::Status::UNMOD}; using JSONIterator = uint64_t; using RewindIterator = std::variant; @@ -298,6 +389,7 @@ namespace olympia RegisterBitMaskArray dest_reg_bit_masks_; RegisterBitMaskArray store_data_mask_; RenameData rename_data; + static const std::unordered_map status2String; }; using InstPtr = Inst::PtrType; @@ -331,12 +423,26 @@ namespace olympia case Inst::Status::FLUSHED: os << "FLUSHED"; break; + //Used in extended_status_state as default case + case Inst::Status::UNMOD: + os << "UNMOD"; + break; + //The new opcode/instruction as a result of fusion + case Inst::Status::FUSED: + os << "FUSED"; + break; + //The opcodes/instruction no longer present due to fusion + case Inst::Status::FUSION_GHOST: + os << "FUSION_GHOST"; + break; case Inst::Status::__LAST: throw sparta::SpartaException("__LAST cannot be a valid enum state."); } return os; } + // Expect log info system uses simple diff + // - any changes here will break EXPECT inline std::ostream & operator<<(std::ostream & os, const Inst & inst) { os << "uid: " << inst.getUniqueID() << " " << std::setw(10) << inst.getStatus() << " " @@ -347,7 +453,11 @@ namespace olympia inline std::ostream & operator<<(std::ostream & os, const InstPtr & inst) { - os << *inst; + if(inst) { + os << *inst; + } else { + os << "nullptr"; + } return os; } diff --git a/core/InstGroup.hpp b/core/InstGroup.hpp index a387f3e0..51a2e717 100644 --- a/core/InstGroup.hpp +++ b/core/InstGroup.hpp @@ -29,6 +29,14 @@ namespace olympia insts_.emplace_back(inst); } + iterator erase(iterator first, iterator last) { + return insts_.erase(first, last); + } + + iterator erase(iterator pos) { + return insts_.erase(pos); + } + iterator begin() { return insts_.begin(); } iterator end() { return insts_.end(); } diff --git a/core/ROB.cpp b/core/ROB.cpp index 09637814..ef953169 100644 --- a/core/ROB.cpp +++ b/core/ROB.cpp @@ -156,11 +156,17 @@ namespace olympia // Use the program ID to verify that the program order has been maintained. sparta_assert(ex_inst.getProgramID() == expected_program_id_, - "Unexpected program ID when retiring instruction" - << "(suggests wrong program order)" - << " expected: " << expected_program_id_ - << " received: " << ex_inst.getProgramID()); - ++expected_program_id_; + "\nUnexpected program ID when retiring instruction" + << "\n(suggests wrong program order)" + << "\n expected: " << expected_program_id_ + << "\n received: " << ex_inst.getProgramID() + << "\n UID: " << ex_inst_ptr->getMavisUid() + << "\n incr: " << ex_inst_ptr->getProgramIDIncrement() + << "\n inst "< M holds, this is the case for fusion. + +When N < M holds, this is the case for fracture. + +Informally, fusion converts simpler operations into more complex operations +with the intent of improving execution attributes. Fracture commonly replaces +more complex operations with a set of simpler operations, with the +same intent of improving execution attributes. + +Often N-tuple instructions are elements of a standardized ISA and +the M-tuple are customizations of the N-tuple set. The M-tuple +instructions are the result of transformation and are internal to the +processor. + +FSL assists the expression of the properties of these transformations and +takes advantage of the bounds of the domain to provide an efficient syntax +for these expressions. + +## High Level Operation + +Each N to M tuple mapping is expressed in a named FSL transform specification. + +The input to a transform is a sequence of instructions. The output of +a transform is a possibly transformed sequence. The input sequence can +be modified in place or into a separate buffer. This is controlled by +FSL syntax. + +The operation of an FSL transform consists of a matching phase, +a constraints checking phase, and the conversion phase. The transform +phases are represented by three clauses in an FSL transform specification. + +A phase returns an indication of success or failure. If a phase reports +a failure, control is returned to the Fusion API. The API will continue +processing and notify the system as required. + +The sequence clause declares the matching attributes to be applied to +the input sequence. The matching attributes are a list of +instruction objects, either as assembly language or as a list of UIDs. + +The output of the sequence clause indicates which instructions +are to be transformed, if any. If no match is found the sequence clause +returns fail. + +In the simplest case, a positive match is indicated by an index and a length. +The index is the position of the first matching instruction in the input +sequence, length is the number of instructions to be transformed, inclusive of +the index. + +A valid sequence indication is passed to the constraints clause. Subsequently, +the constraints specification is applied to the indicated range and a +true/false flag is returned. + +If the constraints are satisfied, the constraints clause passes true to the +conversion clause, along with the sequence indication. + +If the conversion clause receives true, it will apply the transformation +on the input sequence beginning at the specified index and places the result +in an output sequence or modifies the input sequence in place as directed +by syntax. + +## FSL Example + +An example of an FSL transform specification written in the encapsulated +style is shown below. + +The three clauses, sequence, constraints and conversion, are preceded by +the variables which declare the instruction set architecture, +the micro-architecture of the processor and a +sequence container reference, ioput. ioput links the transform to the +performance model's decoded instructions containers. + +The isa/uarch/ioput are referred to as the 'Prolog' in discussions +below. + +``` +transform fs1 { + + isa rv64g + uarch oly1 + ioput iop1 + + sequence seq1(iop1,rv64g) { + c.lui g1, c1 + c.addi g1, g1, c2 + _req_ 2 + c.xor g2, g2, g1 + c.slli g2, g2, c3 + _opt_ 1 + c.srli g2, c3 + } + + constraints cons1(iop1,seq1,rv64g,oly1) { + gpr g1,g2 + s20 c1 + s12 c2 + u6 c3 + + g1 != g2 + } + + conversion conv1(iop1,seq1,cons1) { + instr fused(opcode=0x1234) + iop1.input.replace(seq1,fused) + } +} +``` + +## Is/Is not + +FSL is not a programming language; it is a "constrained +transformation expression language". FSL operates on stylized or symbolic +microprocessor instructions. + +As such many aspects of a programming language are not required by FSL. + +The FSL syntax style is a reduced boilerplate style of C. e.g. braces are +used, no semi-colons, indentation is not a syntax element. + +There are no user defined functions. + +There are a limited number of data types unique to FSL. + +The standard native types are not in the syntax. + +For user defined variables type assignment is done by inference. + +This is no need for string or container types. The typical native +types such as float, double, and string are not required by FSL and +therefore not inferred. + +FSL uses single assignment for variables. When using object methods +multiple assignments with the same method is supported. + +There are no console or file I/O mechanisms. + +Expression operations are limited. + +Arithmetic expressions are limited. + +const'ness is not a feature of FSL. + +There are no namespaces. + +---------------------------------------------------------------- +# Language Description + +Note: This is the current state of the definition. The design choices +made for simplicity may change as the definition matures. + +## Scope + +Note: I'd like to keep the scoping requirements to a minimum. Time will +tell if this is practical. + +There is global scope for named tranforms and a local scoping +convention. Variable scoping is lexical. + +Anonymous block scoping, { ...scope... }, is not necessary for FSL operation. + +## Keywords + +The set of keywords is small. Some objects have process/method names +where legal use is indicated by context and are not keywords. + +``` +_req_ _opt_ _pass_ _fail_ emit +transform constraints sequence conversion +ioput isa uarch iword +instr encoding encode_order instr +csr fpr gpr vvr +``` + +## Identifiers + +Legal variable names are conventional. + +``` +^[a-zA-Z_][a-zA-Z0-9_]*$ +``` + +---------------------------------------------------------------- +## Constants + +The constraints and conversion clauses are the target use cases for +defining constant support in FSL. + +Constants are expressed as decimal, hexadecimal or the verilog style. + +``` +123 +0x456 +8'b10101010 +``` + +For Verilog style constants the '?' value indicates a match all or +don't care element. The width of '?' is relative to the width of +native elements within the constant's base. + +``` + 8'b1010101? bit 0 is a don't care +12'h45? bits [3:0] are a don't care +``` + +---------------------------------------------------------------- +## Operators + +The constraints clause is the target use case for defining operator +support in FSL. + +The list of operators and their support in FSL is listed below. FSL adds +the range operator to the standard C style operators. The syntax is +similar to Verilog. + +These operators are unnecessary in FSL: Divide, modulus, size_of, +pointer operations, type conversions cast/implicit/explicit. + +The comma operator support is limited to concatentaion expressions, operation +chaining is not necessary in FSL. + +Within the supported operators FSL uses conventional operator precedence. + + +### Arithmetic Operators + +``` + + (Addition) + - (Subtraction) + * (Multiplication) +``` + +### Range Operators + +``` + [M:N] (range select) + [M] (index) +``` + +### Relational Operators + +``` + == (Equal to) + != (Not equal to) + > (Greater than) + < (Less than) + >= (Greater than or equal to) + <= (Less than or equal to) +``` + +### Logical Operators + +``` + && (Logical AND) + || (Logical OR) + ! (Logical NOT) +``` + + +### Assignment Operator + +``` + = (Simple assignment) +``` + + +### Concatenation Operator + +Concatenation expressions use a style similar to verilog, {a,b,c}; + +``` + xyz = { 3'b00, 4'h3,8'd10 } + abc = { a, b, c } + +``` + +## Comments + +Comments use '# ' (hash/space) for single line comments or '###' as +the delimiter for comment blocks. Comments can be embedded in other comments. + +``` +# ### commenting out +# my multi line +# comment by +# prefixing the '#' +# end of ML comment ### +``` + +Mixing block and line comments is supported, the first comment token +takes precedence. This practice is not recommended. + +``` +# ### commenting out +# my multi line + this line will be processed +# prefixing the '#' +# end of ML comment ### +``` + +In this case the block comment start and stop markers are not processed +because they are delimited by line comments. This is supported but 'this +line will be processed' will be tokenized and tested for legal grammar. + +## Preprocessor Support + +The FSL parser uses a pre-processor with features similar to that found +in the C pre-processor. Within the constraints of the FSL language most +pre-processor operations found in C will be available in FSL with slight +changes for FSL syntax choices. + +Note that the typical # indicator, as in #include, is replaced with +'`' as in `include. + +### Conditional Compilation + +FSL supports a common set of conditional compilation directives, but +with the typical # replaced by back-tick. + +``` +`ifdef X +`ifndef X +`if X == Y + +`endif +`endif +`endif +``` + +### Includes + +The include keyword declares external files. The parser handles +included files by expanding them into the current position in +the current translation unit. Include recursion is illegal. + +The syntax is: + +`include myfile.fh + +The .fh extension is preferred but not enforced by the parser. Quotes +around the file name are not used. + +The single inclusion pragma is declared as: + +`once + +This pragma must be expressed before any other non-comment statement. + +## Transform Structure + +FSL transform specifications are read at runtime. The FSL file +list is passed to the Fusion API constructor. The API invokes the +FSL parser. + +A transform's internal structure consists of a number of clauses, +the sequence, constraints, and conversion clauses, along with a number +elements in the prolog which specify the context of the fusion operation, +ISA, microarchitecture and hooks into the performance model. + +## Transform Specification + +The transform statement is a top level structure. Each transform statement +is named. The name must be unique across all translation units. + +The generic form is shown below. The order of the sections and clauses +is arbitrary. What is shown is the recommended convention. + +``` +transform { + + + + +} +``` + +## Prolog Elements + +Prolog is not a keyword. The prolog elements provide the necessary +context for the transform operation and they are commonly referred to +as a group. The common elements are shown: + +``` + isa myIsa + uarch myImplementation + ioput iop1 +``` + +Following parsing/interpretation of the FSL files, the API will walk through +each transform specification to determine its pertinence to the current +context. + +The prolog elements are used for this purpose. The elements +of isa, uarch and ioput are matched to references in the Fusion API. +Unmatched references cause the API to throw an exception. + +## isa Element + +The isa element name is used by the Fusion API to match an instruction set +description API to the transform specification. The API looks up the +'myIsa' string to find the associated object pointer. + +The Fusion API will throw an exception if the named isa element does not have an +associated object. + +The ISA description API is used to validate instruction references in +the transform specification. + +Mavis is the instruction set description API supported in this release. +More information on Mavis can be found here [Mavis](https://github.com/sparcians/mavis) +and in the Mavis section under Tools and Utilities below. + +### isa Methods + +The isa object has no internal methods accessible through FSL syntax. +Operations which use the isa element do so implicitly during FSL +parsing. + +## uarch Element + +The uarch element name is used by the Fusion API to match a micro-architecture +description class instance to the transform specification. + +The Fusion API will throw an exception if the named uarch element does not +have an associated object. + +The microarchitecture description class is used to validate processor +implementation queries made in the constraints and conversion clauses. + +### uarch methods + +The uarch methods are documented in MachineInfo.hpp as part of the +Fusion API doxygen documentation. + +## ioput Element + +The Fusion API links references to C++ buffers containing instruction +representations to the FSL parser. This linkage is done through a similar +name matching scheme as the other prolog elements. + +ioput is the keyword. Within ioput are two objects representing two +instruction buffers, by name they are simply .input and .output. + +During construction the Fusion API maps ioput.input and ioput.output +to the appropriate model containers. 'input' and 'output' are indexes +into a map containing references to the model's instruction buffers. + +The Fusion API will throw an exception if it can not match the elements in +ioput to STL container objects. Using Olympia as an example, the Fusion +API will attempt to match the specified name, ioput.input, to a +Fusion::InstQueue reference. + +Note: The FSL syntax implies a copy-on-write idiom. However by mapping the +ioput.input and ioput.output objects to the same buffer a modification +in-place idiom can be implemented. This isolates the style used in +the conversion clause from these external mechanics. + +### ioput methods + +ioput elements have these methods available. + +``` +.clear() empties the associated container +.erase(index,size) removes size elements begining with index, + size is inclusive of the index position. +.insert(index,instr) insert an instr object before index. + instr is an FSL type +.replace(sequence,instr) removes sequence.length objects beginning + at sequence.index, and inserts instr +``` + +## Sequence clause + +The sequence clause is used to match the incoming instructions to a +known transform. + +The arguments to the sequence clause are the input sequence name and +the Mavis facade name, declared using ioput and isa, respectively. + +The ioput and isa objects give the sequence clause access to the +instruction containers and known instruction definitions. This +access is used for validation of the sequence and for matching. + +The sequence clause implicitly uses the 1st element of ioput, the +input container. The sequence clause does not modify the ioput containers. + +``` +ioput iop1 + +sequence seq1 { + # sequence of instructions +} +``` + +The FSL parser uses the Fusion API and access to the isa object to +validate the instruction sequence. An exception is thrown if a problem +is detected. + +The specified sequence of instructions is pattern matched against +the contents of the input container. If a match is detected the sequence +object, seq1, will update its internal index field and length field. This +information is passed to the constraints clause. On a match the sequence +clause returns \_pass\_ to the Fusion API. + +The index field is the zero-referenced position of the start of the match +within ioput. The length is the number of instructions, inclusive of the +starting instruction. + +An instruction sequence is expressed as a simple list of Mavis-assigned +unique identifiers which are independent of operands, or as a set of +assembly language instructions with abstracted operands. The choice is +based on the constraints. + +The UID list is sufficient if there are no constraints on the operands +in the list of instructions in the sequence. + +For example, these two instructions are a frequent sequence in compiled +C. The left column is the encoding, the comment is the Mavis unique identifier +for each instruction which is independent of the operands. + +``` +0x0512 c.slli x10,4 ; 0xf +0x8111 c.srli x10,4 ; 0x13 +``` + +If you do not require constraints on the operands and therefore have no +need to refer to the operands by name, you can simply specify the UIDs +as a list, one per line. + +``` +ioput iop1 + +sequence seq1(iop1,rv64g) { + 0xf + 0x13 +} +``` + +With the UID sequence expression there are no operand specific +constraints and therefore the conversion clause must handle any legal +combinations of the two rd and two constants. + +This results in a transform to a generalized fusion op of a shift left +followed by right and not the zero extension expressed by x10,4. + +If the constraints clause implements operand contraints the instruction +sequence should be expressed using the abstract assembly syntax so the +operands can be referenced by name. + +The expression below takes advantage of positional constraints: + +``` +ioput iop1 + +sequence seq1(iop1,rv64g) { + c.slli g1,c1 + c.srli g1,c1 +} +``` + +In this case the operands labeled g1 are, by association, required to be +the same value, similarly with the constant operands. + +Another option would be to fully enumerate the operands and let the +constraints clause enforce the required limits on operands. + +``` +ioput iop1 + +sequence seq1(iop1,rv64g) { + c.slli g1,c1 + c.srli g2,c2 +} +``` + +Note both the later two cases can be made equivalent by construction of +the equivalent constraints in the clause. + +Now in both cases the transformation expresses a fusion operation for +zero extention of the common rd register by the common constant. + +``` +g1 = g2 +c1 = c2 +``` + +In some cases positional constraints can save development time at no +expense to performance. + +The example sequences above have an implied strict ordering. There are +sequence pragmas to relax the strictness of sequence matching. + +The required pragma, \_req\_, indicates that an unspecified instruction +is required in that position. The \_req\_ case does not constrain +what instruction can be present in the gap. A trailing integer +can specify more than one required instruction. + +The optional pragma, \_opt\_, indicates that the match will not be +rejected if there is an instruction or not in this position. A +trailing integer can be used to specify up to N optional instructions. + +``` +_opt_ 2 indicates 0 - 2 instructions will match +_req_ 2 indicates there must be 2 instructions in this position + in the sequence. +``` + +The trailing integer is optional in the syntax: +``` +_opt_ and _opt_ 1 are equivalent +_req_ and _req_ 1 are equivalent +``` + +``` +ioput iop1 + +sequence seq1(iop1,rv64g) { + c.lui g1, c1 + c.addi g1, g1, c2 + _req_ 2 + c.xor g2, g2, g1 + c.slli g2, g2, c3 + _opt_ 2 + c.srli g2, c3 +} +``` + +Notes: The sequence pragmas are also supported for the UID case. Mixing UID +and abstract operand sequences is supported. + +### Sequence methods + +sequence objects have implicit data members + +``` +.state This is _pass_ or _fail_ match status +.length number of matching instructions, if non-zero this is + the number of UIDs or instruction object epxress in the + sequence. length is 0 if no match is found. + +.index if length is non-zero this index of the first matching + instruction +``` + +## Constraints clause + +The constraints clause defines relationships between operands and the +machine implementation. + +The sequence clause is provided to give the constraints access to +the operand abstractions and UID list. The ioput object is supplied +so the constraints clause can query the encoding of the instructions in +the input buffer. The isa object is supplied to validate field widths etc +used in the constraints declarations. Finally the uarch object provides +the constraints clause access to machine implementatain details. + +When the sequence object contains only UIDs there are no operand +specific constraints, and the constraints clause can simply return +\_pass\_. + +``` +constraints cons1() { + _pass_ +} +``` + +Having a populated argument list in this case is optional. +But if the arguments to the constraints clause is empty +the parser will expect only a \_pass\_ or (a \_fail\_) statement. +Anything else indicates a programming error. + +This is a contrived example for explanation. + +``` +ioput iop1 + +sequence seq1(iop1,rv64g) { + c.slli g1,c1 + c.srli g2,c2 +} + +constraints cons1(seq1,iop1,rv64g,oly1) { + gpr g1,g2 + u6 c1,c2 + + g1 != g2 + c1[0] == c2[1] + +} +``` + +Variables beginning with s or u are signed/unsigned constants. +The number in the name is the bit width of the constant. + +gpr is a keyword, the signed and unsigned designators are treated +as keywords. + +When performing comparisons on register operands the comparison +is performed on the index of the operand and not the contents +of the operand. The inequality statement, g1 != g2, is testing that +g1 and g2 are not the same register. + +The \_pass\_ or \_fail\_ pragmas are implicit. If any statement is +false \_fail\_ is returned. If the clause is processed to the end +\_pass\_ is returned. + +For error checking any named operand in the sequence clause must +have a declaration in the constraints clause even if no operation +uses that declaration. + +``` +ioput iop1 + +sequence seq1(iop1,rv64g) { + c.lui g1, c1 # c1 is s6 + c.addi g1, c2 # c2 is s6 + c.xor g2, g1 + c.slli g2, c3 # c3 is u6 + c.srli g2, c3 # c3 is u6 +} +``` + +As an example for this sequence the declared constraints clause +will return \_pass\_. + +``` +ioput iop1 + +constraints cons1(seq1,iop1,rv64g,oly1) { + gpr g1,g2 + u6 c1,c2,c3 +} +``` + +However this example will fail, because c3 is not declared. + +``` +ioput iop1 + +constraints cons1(seq1,iop1,rv64g,oly1) { + gpr g1,g2 + u6 c1,c2 # _fail_ because no c3 +} +``` + +Unused declarations also fail. This is considered a programming +aberration that should be reported to the developer. + +When specifying constraints on register operands the constraints +themselves are verified against the ISA. Constraints that violate +the requirements of the ISA are failed. + +``` +ioput iop1 + +sequence seq1(iop1,rv64g) { + c.slli g1,c1 + c.srli g1,c1 +} + +constraints cons1(seq1,iop1,rv64g,oly1) { + gpr g1,g2 + u6 c1,c2 + + g1 == 0 # _fail_ the rd of c.slli/c.srli can not be x0 +} +``` + +Being strict with operand declarations is intended to assist +development by highlighting unexpected code construction. +TODO: is this having the desired effect? + +### Constraints methods + +The constraints clause has a single method. + +``` +.state This is _pass_ or _fail_ constraints status +``` + +In addition to the .state method, the constraints clause declarations +are made available without prefix to the conversion clause. This is +described in the next section. + +### Conversion clause + +The conversion clause performs the instruction morphing to create +the new instruction(s). + +The conversion clause takes a reference to the sequence clause for +access to the named operands or for access to the UID lists. + +The conversion clause takes a reference to the ioput object for +reading the input instruction list as a destination for converted +instructions. + +The constraints clause is provided to the conversion clause for +access to operand relationships and types. + +The conversion clause can modify the ioput object's input field +in place with converted instructions or it can place modified +instructions in the output field of the ioput object. + +instr objects are used to hold the newly created instructions. The +annotation fields for a new instruction are assigned to the instr +object. + +In the example presume the sequence match has returned an index of 2 and a +length of 3. In this conversion the 3 unfused instructions are replaced +by 1 fusion created instruction. + +The new instruction attributes are assigned. The ioput container +is modifed to remove the unfused instructions and insert the +fusion replacement. + +``` +ioput iop1 + +conversion conv1(seq1,iop1,cons1) { + instr newInstr + + newInstr(mnemonic="SEQ1",opc=0x1234,uid=0x3) + newInstr(src={g1},dst={g2},imm={c1,c1}) + newInstr(type="fused") + + iop1.input.replace(seq1,newInstr) +} +``` + +There are a large number of fields available in the instr object. Not +all are required by all models. Inclusion of these fields is implementation +dependent. + +### Conversion methods + +The conversion clause has a single method. + +``` +.state This is _pass_ or _fail_ constraints status +``` + +The instr type is valid within a conversion clause. + +---------------------------------------------------------------- +# Tools and Utilities + +A summary of tools and utilities useful for instruction transform work. + +## Condor Fusion API + +FSL domain specific language is a component of a larger C++ API. With this API +it is possible to directly create instruction transforms in C++, or through +the FSL domain specific language or other ad hoc schemes. The API has +been tested with the Condor Performance Model and the Olympia +performance model. + +The Fusion API shares a repository with the Olympia performance model. The +shared URL is contained here: +https://github.com/riscv-software-src/riscv-perf-model/tree/master/fusion. +Doxygen documentation is available for this API. + +## Olympia Performance Model + +Olympia is a performance model written in C++ for the RISC-V community as an +example of an Out-of-Order RISC-V CPU Performance Model based on the Sparta +Modeling Framework. + +The repository URL is https://github.com/riscv-software-src/riscv-perf-model/tree/master. + +## Mavis + +Mavis is a header only framework designed for decoding the RISC-V ISA into +custom instruction class types, along with custom extensions to those +class types. + +The repository URL is https://github.com/sparcians/mavis/tree/main. Doxygen +documentation is available for this API. + +## STF Library + +The STF (Simulation Tracing Format) library provides an API for +reading and generation of STF trace files. + +The repository URL is https://github.com/sparcians/stf_lib. Doxygen +documentation is available for this API. + +---------------------------------------------------------------- +# Appendix + +## FSL BNF + +## FSL style considerations + +There are two styles of FSL shown, the first shows an encapsulated +style, the second shows a library element style. + +Subjectively I think the first style is easier to comprehend and the second +style is where large implementations will likely migrate, because of the +opportunities for reuse. + +In operation there is no difference in the two examples. + +### Encapsulated Style +``` +transform fs1 { + + isa rv64g + uarch oly1 + ioput iop1 + + sequence seq1(iop1,rv64g) { + c.lui g1, c1 + c.addi g1, g1, c2 + _req_ + c.xor g2, g2, g1 + c.slli g2, g2, c3 + _opt_ + c.srli g2, c3 + } + + constraints cons1(seq1,iop1,rv64g,oly1) { + gpr g1,g2 + s20 c1 + s12 c2 + u6 c3 + + g1 != g2 + } + + conversion conv1(seq1,iop1,cons1) { + instr fused(opcode=0x1234) + iop1.input.replace(seq1,fused) + } +} +``` + +### Library Style + +transform fs1 { + prolog prolog1 + sequence seq1 + constraints cons1 + conversion conv1 +} + +prolog prolog1 { + isa rv64g + uarch oly1 + ioput iop1 +} + +sequence seq1(iop1,rv64g) { + c.lui g1, c1 + c.addi g1, g1, c2 + _req_ + c.xor g2, g2, g1 + c.slli g2, g2, c3 + _opt_ + c.srli g2, c3 +} + +constraints cons1(seq1,iop1,rv64g,oly1) { + gpr g1,g2 + s20 c1 + s12 c2 + u6 c3 + + g1 != g2 +} + +conversion conv1(seq1,iop1,cons1) { + instr fused(mnemonic="SEQ1",opcode=0x1234) + iop1.input.replace(seq1,fused) +} + +conversion conv2(seq1,iop1,cons1) { + encoding word1(seq1,mnemonic="SEQ1",opc=0x1234) { + u10 opc # 57:48 unsigned 10b + u6 c3 # 47:42 unsigned 6b + s12 c2 # 41:30 signed 12b + s20 c1 # 29:10 signed 20b + gpr g1 # 9:5 gpr 5b + gpr g2 # 4:0 gpr 5b + encode_order{opc,c3,c2,c1,g1,g2} + } + + instr fused(encoding=word1) + iop1.input.replace(seq1,fused) +} + +## FSL Instruction Types + +Instruction types have a mapping to the ISA description API. The +lower case strings here are mapped to an equivalent type in the ISA +API. When assigning a type to an FSL instr object use the lower +case strings below, without quotes. + +``` +"int" INT +"float" FLOAT +"arith" ARITH +"mul" MULTIPLY +"div" DIVIDE +"branch" BRANCH +"pc" PC +"cond" CONDITIONAL +"jal" JAL +"jalr" JALR +"load" LOAD +"store" STORE +"mac" MAC +"sqrt" SQRT +"convert" CONVERT +"compare" COMPARE +"move" MOVE +"classify" CLASSIFY +"vector" VECTOR +"maskable" MASKABLE +"unit_stride" UNIT_STRIDE +"stride" STRIDE +"ordered_indexed" ORDERED_INDEXED +"unordered_indexed" UNORDERED_INDEXED +"segment" SEGMENT +"faultfirst" FAULTFIRST +"whole" WHOLE +"mask" MASK +"widening" WIDENING +"hypervisor" HYPERVISOR +"crypto" CRYPTO +"prefetch" PREFETCH +"ntl" NTL +"hint" HINT +"cache" CACHE +"atomic" ATOMIC +"fence" FENCE +"system" SYSTEM +"csr" CSR +``` + +## Syntax Highlight Control Files + +### VIM +``` +" Vim syntax file +" Language: Fusion/Fracture Specification Language +" Maintainer: Jeff Nye +" Last Update: 2024 + +" For version 6.x: Quit when a syntax file was already loaded +if version < 600 + syntax clear +elseif exists("b:current_syntax") + finish +endif + +" Set the local value of the 'iskeyword' option. +if version >= 600 + setlocal iskeyword=@,48-57,63,_,192-255 +else + set iskeyword=@,48-57,63,_,192-255 +endif + +" Rules +syn keyword fslKeyword transform sequence constraints emit conversion +syn keyword fslKeyword isa uarch ioput +syn keyword fslSequence _req_ _opt_ _pass_ _fail_ + +syn match fslFunction /\w\+\ze\s*(/ + +syn keyword fslType fpr vvr gpr csr opc instr encoding encode_order iword +syn match fslType "\<[su]\d\+\>" + +syn keyword fslTODO contained TODO FIXME + +syn match fslOperator "[&|~>" +syn match fslVlogNumber "\(\<\d\+\|\)'[sS]\?[bB]\s*[0-1_xXzZ?]\+\>" +syn match fslVlogNumber "\(\<\d\+\|\)'[sS]\?[oO]\s*[0-7_xXzZ?]\+\>" +syn match fslVlogNumber "\(\<\d\+\|\)'[sS]\?[dD]\s*[0-9_xXzZ?]\+\>" +syn match fslVlogNumber "\(\<\d\+\|\)'[sS]\?[hH]\s*[0-9a-fA-F_xXzZ?]\+\>" +syn match fslVlogNumber "\<[+-]\=[0-9_]\+\(\.[0-9_]*\|\)\(e[0-9_]*\|\)\>" + +"Modify the following as needed. The trade-off is performance versus +"functionality. + +syn sync minlines=50 + +hi def link fslKeyword Statement +hi def link fslType FslType +hi def link fslSequence Label +hi def link fslDirective Label + +hi def link fslTODO Todo +hi def link fslComment Comment +hi def link fslFunction Function + +hi def link fslString String + +hi def link fslHexNumber Number +hi def link fslVlogNumber Number + +hi def link fslOperator Special + +hi def link fslIncline SpecialComment +hi def link fslFileName ThisType + +highlight FslType ctermfg=2 gui=bold guifg=SeaGreen + +let b:current_syntax = "fsl" +``` + +1. References + +[1] Celio, Christopher, et al. "The renewed case for the reduced instruction + set computer: Avoiding isa bloat with macro-op fusion for risc-v." arXiv + preprint arXiv:1607.02318 (2016). diff --git a/fusion/README.md b/fusion/README.md index 93588243..ebf66688 100644 --- a/fusion/README.md +++ b/fusion/README.md @@ -1,4 +1,4 @@ -# Fusion API draft +# Fusion API ``` Contact: Jeff Nye @@ -65,7 +65,7 @@ mkdir release; cd release; cmake .. -DCMAKE_BUILD_TYPE=Release cd ../fusion mkdir -p release; cd release; cmake .. make -j32 regress -make docs +make fusion_docs ``` # C++ API and testbench @@ -148,3 +148,7 @@ API definition unobtrusive. The remainder of the code and support classes are template based. +## Domain specific language + +A domain specific language is available, the Fusion/Fracture Specification +Language, FSL, is documented in FSL.MD. diff --git a/fusion/fusion/FieldExtractor.hpp b/fusion/fusion/FieldExtractor.hpp index c89d76e2..3de286d7 100644 --- a/fusion/fusion/FieldExtractor.hpp +++ b/fusion/fusion/FieldExtractor.hpp @@ -3,14 +3,15 @@ // //! \file FieldExtractor.hpp mavis shim, extract fields from encodings #pragma once -#include "FusionExceptions.hpp" -#include "uArchInfo.hpp" -#include "Instruction.hpp" -#include "Mavis.h" -#include +#include "fusion/FusionExceptions.hpp" +#include "fusion/Instruction.hpp" +#include "fusion/uArchInfo.hpp" +#include "mavis/Mavis.h" + +#include #include #include -#include +#include using namespace std; //! \class FieldExtractor @@ -43,7 +44,8 @@ class FieldExtractor //! \brief type for getXRegs operations from mavis using MavisBitMaskType = mavis::DecodedInstructionInfo::BitMask; //! \brief type for getXRegs operations from mavis - using RegsGetter = MavisBitMaskType (Instruction::*)() const; + using RegsGetter = + MavisBitMaskType (Instruction::*)() const; //! \brief duplicated arguments do not need to be explicitly expressed using OptArg = std::optional; @@ -57,14 +59,19 @@ class FieldExtractor //! \brief extract value of named encoding field //! //! handles field name and immediate checking + //! \note RS_MAX is overloaded to identify get's for immediate fields uint32_t getField(InstPtrType inst, FieldName field) const { bool isDest = false; if (!checkInstHasField(inst, field, isDest)) { - throw fusion::FieldExtUnknownField((uint32_t)field, inst->dasmString()); + throw fusion::FieldExtUnknownField((uint32_t)field, + inst->dasmString()); } + if (field == FieldName::RS_MAX) + return getImmField(inst); + return getFieldById(inst, field, isDest); } @@ -81,12 +88,19 @@ class FieldExtractor uint32_t value = inst->getSpecialField(field); if (value == 0) { - throw fusion::FieldExtUnknownSpecialField((uint32_t)field, inst->dasmString()); + throw fusion::FieldExtUnknownSpecialField((uint32_t)field, + inst->dasmString()); } return value; // return getSFieldById(inst, field); } + //! \brief get the encoded value of the full immediate field + //! + //! split immediate fields are (will be) ordered msb:lsb and + //! concatenated into one unsigned value + uint32_t getImmField(InstPtrType inst) const { return 0; } + //! \brief helper function for getField, src/dst switch //! //! isDest is set previously by checkInstHasField() @@ -107,14 +121,16 @@ class FieldExtractor //! //! This is legally marked const, it does not modify *this, //! but it does modifies the argument isDest. - bool checkInstHasField(InstPtrType inst, FieldName field, bool & isDest) const + bool checkInstHasField(InstPtrType inst, FieldName field, + bool & isDest) const { if (inst->hasImmediate() && field == FieldName::RS_MAX) { return true; } - if (inst->getSourceOpInfo().hasFieldID(field) && field != FieldName::RS_MAX) + if (inst->getSourceOpInfo().hasFieldID(field) + && field != FieldName::RS_MAX) { return true; } @@ -142,48 +158,49 @@ class FieldExtractor } // catch fall through - throw fusion::FieldExtUnknownField((uint32_t)field, inst->dasmString()); + throw fusion::FieldExtUnknownField((uint32_t)field, + inst->dasmString()); return false; ///... } //! \brief equality - bool eq(const InstPtrListType & input, size_t a, size_t b, const FieldName f1, - const OptArg f2 = std::nullopt) const + bool eq(const InstPtrListType & input, size_t a, size_t b, + const FieldName f1, const OptArg f2 = std::nullopt) const { return compare(input[a], input[b], f1, f2, EQ); } //! \brief less than - bool lt(const InstPtrListType & input, size_t a, size_t b, const FieldName f1, - const OptArg f2 = std::nullopt) const + bool lt(const InstPtrListType & input, size_t a, size_t b, + const FieldName f1, const OptArg f2 = std::nullopt) const { return compare(input[a], input[b], f1, f2, LT); } //! \brief not equal - bool noteq(const InstPtrListType & input, size_t a, size_t b, const FieldName f1, - const OptArg f2 = std::nullopt) const + bool noteq(const InstPtrListType & input, size_t a, size_t b, + const FieldName f1, const OptArg f2 = std::nullopt) const { return !compare(input[a], input[b], f1, f2, EQ); } //! \brief greater than - bool gt(const InstPtrListType & input, size_t a, size_t b, const FieldName f1, - const OptArg f2 = std::nullopt) const + bool gt(const InstPtrListType & input, size_t a, size_t b, + const FieldName f1, const OptArg f2 = std::nullopt) const { return compare(input[b], input[a], f1, f2, LT); } //! \brief less than or equal - bool lteq(const InstPtrListType & input, size_t a, size_t b, const FieldName f1, - const OptArg f2 = std::nullopt) const + bool lteq(const InstPtrListType & input, size_t a, size_t b, + const FieldName f1, const OptArg f2 = std::nullopt) const { return !compare(input[b], input[a], f1, f2, LT); } //! \brief greater than or equal - bool gteq(const InstPtrListType & input, size_t a, size_t b, const FieldName f1, - const OptArg f2 = std::nullopt) const + bool gteq(const InstPtrListType & input, size_t a, size_t b, + const FieldName f1, const OptArg f2 = std::nullopt) const { return !compare(input[a], input[b], f1, f2, LT); } @@ -191,7 +208,8 @@ class FieldExtractor //! \brief return the integer read ports used by the input fusion group uint32_t getIntRdPorts(const InstPtrListType & input) const { - return countPorts(input, &Instruction::getIntSourceRegs); + return countPorts(input, + &Instruction::getIntSourceRegs); } //! \brief return the integer write ports used by the input fusion @@ -204,61 +222,53 @@ class FieldExtractor //! \brief return the float read ports used by the input fusion group uint32_t getFloatRdPorts(const InstPtrListType & input) const { - return countPorts(input, &Instruction::getFloatSourceRegs); + return countPorts(input, + &Instruction::getFloatSourceRegs); } //! \brief return the float write ports used by the input fusion group uint32_t getFloatWrPorts(const InstPtrListType & input) const { - return countPorts(input, &Instruction::getFloatDestRegs); + return countPorts(input, + &Instruction::getFloatDestRegs); } //! \brief return the vector read ports used by the input fusion group uint32_t getVecRdPorts(const InstPtrListType & input) const { - return countPorts(input, &Instruction::getVectorSourceRegs); + return countPorts(input, + &Instruction::getVectorSourceRegs); } //! \brief return the vector write ports used by the input fusion group uint32_t getVecWrPorts(const InstPtrListType & input) const { - return countPorts(input, &Instruction::getVectorDestRegs); + return countPorts(input, + &Instruction::getVectorDestRegs); } private: //! \brief compare common method - bool compare(const InstPtrType LHS, const InstPtrType RHS, const FieldName f1, const OptArg _f2, - FUNC func) const + bool compare(const InstPtrType LHS, const InstPtrType RHS, + const FieldName f1, const OptArg _f2, FUNC func) const { FieldName f2 = _f2.value_or(f1); auto lhs = getField(LHS, f1); auto rhs = getField(RHS, f2); - // clang-format off switch (func) { - case FUNC::LT: - { - return lhs < rhs; - } - case FUNC::EQ: - { - return lhs == rhs; - } - default: - { - throw fusion::FieldExtUnknownFunction((uint32_t)func); - } + case FUNC::LT: { return lhs < rhs; } + case FUNC::EQ: { return lhs == rhs; } + default: { return false; } } - // clang-format on - - return false; } //! \brief count the number of read or write ports required by the //! group - uint32_t countPorts(const InstPtrListType & input, RegsGetter getRegs) const + uint32_t countPorts(const InstPtrListType & input, + RegsGetter getRegs) const { mavis::DecodedInstructionInfo::BitMask mask; for (const auto & i : input) diff --git a/fusion/fusion/Fusion.hpp b/fusion/fusion/Fusion.hpp index a3885acd..6e4c6131 100644 --- a/fusion/fusion/Fusion.hpp +++ b/fusion/fusion/Fusion.hpp @@ -3,18 +3,19 @@ // //! \file Fusion.hpp top level fusion API #pragma once -#include "FieldExtractor.hpp" -//#include "FslParser.hpp" -#include "FusionContext.hpp" -#include "FusionExceptions.hpp" -#include "FusionGroup.hpp" -#include "FusionTypes.hpp" -#include "MachineInfo.hpp" +#include "json.hpp" +#include "fusion/FieldExtractor.hpp" +#include "fusion/FusionContext.hpp" +#include "fusion/FusionExceptions.hpp" +#include "fusion/FusionGroup.hpp" +#include "fusion/FusionTypes.hpp" +#include "fusion/MachineInfo.hpp" -#include +#include +#include #include +#include #include -#include namespace fusion { @@ -22,12 +23,12 @@ namespace fusion //! //! \brief top level fusion class //! - //! In this implementation the allocators are placeholder for - //! more complex use cases. They are provided for future + //! In this implementation the allocators are placeholders + //! for more complex use cases. They are provided for future //! extensions. //! //! Input needed to create a fusion 'context' can come - //! from explicity construction of FusionGroups, construction + //! from explicitly construction of FusionGroups, construction //! from the helper class FusionGroupCfg, and eventually //! from the DSL or from Json. //! @@ -45,34 +46,64 @@ namespace fusion //! //! For usage see the testbench.cpp in fusion/test. For the //! final PR there will more usage examples. - template , - typename MachineInfoTypeAlloc = fusion::ShrPtrAlloc, - typename FieldExtractorTypeAlloc = fusion::ShrPtrAlloc> + template , + typename MachineInfoTypeAlloc = + fusion::ShrPtrAlloc, + typename FieldExtractorTypeAlloc = + fusion::ShrPtrAlloc> struct Fusion { - //! \brief ... + //! \brief list of fusion groups using FusionGroupListType = std::vector; - //! \brief ... - using FusionGroupCfgType = fusion::FusionGroupCfg; - //! \brief ... + + //! \brief the fusion context container type + //! + //! \see FusionContext.hpp + using FusionGroupContainerType = + std::unordered_map; + + //! \brief group cfg is a ctor helper class + using FusionGroupCfgType = + fusion::FusionGroupCfg; + + //! \brief list of group cfg instances using FusionGroupCfgListType = std::vector; - //! \brief ... - using TransformFuncType = bool (*)(FusionGroupType &, InstPtrListType &, InstPtrListType &); - //! \brief ... - using FusionContextType = fusion::FusionContext; - //! \brief ... - using FusionFuncType = std::function; + //! \brief transformation function object type + //! + //! support for future feature + using TransformFuncType = bool (*)(FusionGroupType &, + InstPtrListType &, + InstPtrListType &); + //! \brief context type is based on fusion groups and instr ptrs + using FusionContextType + = fusion::FusionContext; + + //! \brief custom fusion operator signature type + using FusionFuncType = std::function; + + //! \brief list of fusion group match attributes + //! + //! Multiple matches may occur, not all will meet their + //! constraints. This list is sorted longest match to shorter. + //! Longest match that meets constraints is selected + using MatchInfoListType = std::vector ; //! \brief main ctor - Fusion( - const FusionGroupListType & fusiongroup_list, - const FusionGroupCfgListType & fusiongroupcfg_list, - const FusionGroupTypeAlloc fusiongroup_alloc = fusion::ShrPtrAlloc(), - const MachineInfoTypeAlloc machine_info_alloc = fusion::ShrPtrAlloc(), - const FieldExtractorType field_extractor_alloc = - fusion::ShrPtrAlloc()) : + Fusion(const FusionGroupListType & fusiongroup_list, + const FusionGroupCfgListType & fusiongroupcfg_list, + const FileNameListType & txt_file_list, + const FusionGroupTypeAlloc fusiongroup_alloc = + fusion::ShrPtrAlloc(), + const MachineInfoTypeAlloc machine_info_alloc = + fusion::ShrPtrAlloc(), + const FieldExtractorType field_extractor_alloc = + fusion::ShrPtrAlloc()) : fusiongroup_alloc_(fusiongroup_alloc), machine_info_alloc_(machine_info_alloc), fusionOpr(defaultFusionOpr) @@ -81,20 +112,32 @@ namespace fusion initialize(fusiongroup_list); else if (fusiongroupcfg_list.size() > 0) initialize(fusiongroupcfg_list); + else if (txt_file_list.size() > 0) + initialize(txt_file_list); context_.makeContext("fbase", fusiongroup_list); } //! \brief ctor from group list Fusion(const FusionGroupListType & fusiongroup_list) : - Fusion(fusiongroup_list, {}, FusionGroupTypeAlloc(), MachineInfoTypeAlloc(), - FieldExtractorType()) + Fusion(fusiongroup_list, {}, {}, FusionGroupTypeAlloc(), + MachineInfoTypeAlloc(), FieldExtractorType()) { } //! \brief ctor from cfg group list Fusion(const FusionGroupCfgListType & fusiongroupcfg_list) : - Fusion({}, fusiongroupcfg_list, FusionGroupTypeAlloc(), MachineInfoTypeAlloc(), - FieldExtractorType()) + Fusion({}, fusiongroupcfg_list, {}, FusionGroupTypeAlloc(), + MachineInfoTypeAlloc(), FieldExtractorType()) + { + } + + //! \brief ctor from txt file list + //! + //! Supports FSL or JSON, type is inferred from file extension + //! All files in the list must be one or the other. + Fusion(const FileNameListType &txt_file_list) : + Fusion({}, {}, txt_file_list, FusionGroupTypeAlloc(), + MachineInfoTypeAlloc(), FieldExtractorType()) { } @@ -106,7 +149,6 @@ namespace fusion registerGroup(grp); } } - //! \brief initialize from a group cfg list void initialize(const FusionGroupCfgListType & grp_list) { @@ -116,9 +158,114 @@ namespace fusion registerGroup(f); } } + //! \brief initialize from a text file list + //! + //! For simplicity assume all files are the same type + //! as first file. I do not see a need at the moment + //! for mixing types. + //! + //! Future: JSON/DSL syntax is being created and reviewed. + void initialize(const FileNameListType &txt_file_list) + { + if (txt_file_list.size() == 0) return; + + //Uses file extension, relies parsing to capture + //cases the contents are not as indicated by the extension + //FIXME: in this draft version of the code only handling + //the first JSON file. + bool useJson = hasExtension(txt_file_list[0],".json"); + if (useJson) + { + FusionGroupCfgListType cfgGroups; + std::string fn = txt_file_list[0]; + parseJsonFusionGroups(txt_file_list[0],cfgGroups); + } + else + { + //Future feature + //fp.setInputFiles(txt_file_list); + //int ret = fp.parse(); + //if (ret) + //{ + // // Future feature - needs additional handling + // throw fusion::FslSyntaxError(fp.errMsg, fp.lineNo); + //} + throw fusion::FslSyntaxError("FSL parsing is not supported",0); + } + } + //! \brief populate a list of FusionGroups from a JSON file + void parseJsonFusionGroups(const std::string fn, + FusionGroupCfgListType &cfgGroups) + { + std::ifstream in(fn); + if(!in.is_open()) + { + throw JsonRuntimeError( + std::string("Could not open file '" + fn +"'")); + } + + nlohmann::json jparser; + + try + { + in >> jparser; + } + catch(const std::exception&) + { + throw JsonSyntaxError(); + } + + for(const auto& item : jparser["fusiongroups"]) + { + checkForJsonField(item,"name"); + checkForJsonField(item,"uids"); + checkForJsonField(item,"tx"); + + string grpName = item["name"]; + string txName = item["tx"]; + + FusionGroupCfgType fg { + .name = grpName, + .uids = std::nullopt, + .transformName = txName + }; + + InstUidListType uids; + for(const auto& uid_str : item["uids"]) + { + int uid = std::stoi(uid_str.get(), + nullptr,16); + uids.push_back((uint32_t) uid); + } + fg.uids = uids; + cfgGroups.push_back(std::move(fg)); + } + initialize(cfgGroups); + } + + //! \brief throw if field is missing + void checkForJsonField(const nlohmann::json& item, + const std::string& fieldName) const + { + if (!item.contains(fieldName)) + { + throw JsonRuntimeError("missing field "+fieldName); + } + } + + //! \brief check file extension + bool hasExtension(const std::string filePath, + const std::string ext) const + { + std::filesystem::path fPath(filePath); + return fPath.extension() == ext; + } //! \brief alias for context_.insertGroup() - void registerGroup(FusionGroupType & grp) { context_.insertGroup(grp); } + void registerGroup(const FusionGroupType & grp) + { + context_.insertGroup(grp); + } //! \brief create a single context from a list of fusiongroups //! @@ -126,11 +273,35 @@ namespace fusion //! not seen an immediate need for dynamic switching //! between multiple fusion contexts in a simulation. //! Something to consider for the future. - void makeContext(const std::string & name, const FusionGroupListType & fusiongroup_list) + void makeContext(const std::string & name, + const FusionGroupListType & fusiongroup_list) { context_.makeContext(name, fusiongroup_list); } + //! \brief return a ref to current context + //! + //! There is support for multiple named contexts. There + //! is need for only one context in the current implementation. + //! + //! \see FusionContext.hpp + FusionContextType & getCurrentContext() const + { + return context_; + } + + //! \brief return a ref to the group container in the current context + //! + //! Consider friendship to skip a level of indirection, but + //! at the moment there is no indication this matters for speed. + //! And this is a cleaner form of encapsulation. + //! + //! \see FusionContext.hpp + FusionGroupContainerType& getFusionGroupContainer() + { + return context_.getFusionGroupContainer(); + } + //! \brief interface to the fusion operation //! //! This is the principle interface to the fusion operation. @@ -143,17 +314,102 @@ namespace fusion fusionOpr(*this, in, out); } + //! \brief report fusion groups + //! + //! Dump the group info to a file + void reportGroups(const std::string reportFileName) const + { + std::ofstream out(reportFileName.c_str()); + if(!out.is_open()) throw fusion::FileIoError("open",reportFileName); + reportGroups(out); + out.close(); + } + + void reportGroups(std::ostream & os) const + { + os<<"Fusion groups: "<insert(grp); + // } + // + // typename FusionGroupType::PtrType makeFusionGroup( + // const std::string name, + // const InstUidListType &uidlist, + // TransformFuncType func) + // { + // FusionGroupType grp(name,uidlist,func); + // return context_.rtrie()->insert(grp); + // } + + //! \brief DSL parser + //! + //! At present this performs syntax checking of the input + //! files. The remaining operations have not been implemented + //! Future: DSL syntax is being redesigned + //FslParser fslParser; //! \brief default fusion operator appends in to out and clears //! out. - static void defaultFusionOpr(Fusion & inst, InstPtrListType & in, InstPtrListType & out) + static void defaultFusionOpr(Fusion & inst, InstPtrListType & in, + InstPtrListType & out) { out.insert(out.end(), in.begin(), in.end()); in.clear(); } + //! \brief emit group container groups + void infoGroups(std::ostream &os, + const FusionGroupContainerType &fgroups) const + { + os<<"INFO fusionGroups "< #include #include #include @@ -13,77 +15,130 @@ namespace fusion { - // ----------------------------------------------------------------------- - //! \class FusionContext - //! - //! - //! \brief Holds an searchable list of the current FusionGroups - //! - //! In this implementation the groups are held and searched - //! within a uo/map. There is a trie implementation but that - //! is not used currently. Before adding the trie if will be - //! useful to spend more time with large fusion group definitions - //! and see how the map performs vs the trie (or alternatives). - //! - //! Future abstration : typename container or typename tree: - //! typename FusionGroupContainerType = - //! std::unordered_map, - //! typename LookupType = fusion::ShrPtrAlloc - // ----------------------------------------------------------------------- - template class FusionContext +// ---------------------------------------------------------------------- +//! \class FusionGroupMatchInfo +// +//! \brief fusion group match return structure +//! +//! Signed integer for startIdx is for no-match reporting without +//! increasing the ctor signature. +// ---------------------------------------------------------------------- +struct FusionGroupMatchInfo { + FusionGroupMatchInfo(std::string name_, + int32_t startIdx_, + uint32_t groupIdx_, + InstUidListType _matchedUids) + : name(name_), + startIdx(startIdx_), + groupIdx(groupIdx_), + matchedUids(_matchedUids) + { } + + //this is only kept for stats reporting, fgroup utilization maps, etc + std::string name = ""; + int32_t startIdx = -1; + int32_t groupIdx = -1; + size_t size() const { return matchedUids.size(); } + + InstUidListType matchedUids; + + friend std::ostream& operator<<(std::ostream& os, + const FusionGroupMatchInfo& matchInfo) { - //! \brief for now this is a uo/map with uint key - using FusionGroupListType = std::vector; - //! \brief for now this is a uo/map with uint key - using FusionGroupContainerType = std::unordered_map; - // Future : using LookupType = RadixTrie<4>; + os << std::dec << std::setfill(' ') + << " name " << matchInfo.name + << " groupIdx " << std::setw(3) << matchInfo.groupIdx + << " startIdx " << std::setw(3) << matchInfo.startIdx + << " size " << std::setw(3) << matchInfo.size(); + return os; + } +}; +// ----------------------------------------------------------------------- +//! \class FusionContext +//! +//! +//! \brief Holds an searchable list of the current FusionGroups +//! +//! In this implementation the groups are held and searched +//! within a uo/map. There is a trie implementation but that +//! is not used currently. Before adding the trie if will be +//! useful to spend more time with large fusion group definitions +//! and see how the map performs vs the trie (or alternatives). +//! +//! Future abstration : typename container or typename tree: +//! typename FusionGroupContainerType = +//! std::unordered_map, +//! typename LookupType = fusion::ShrPtrAlloc +// ----------------------------------------------------------------------- +template +class FusionContext +{ + //! \brief for now this is a uo/map with uint key + using FusionGroupListType = std::vector; + //! \brief for now this is a uo/map with uint key + using FusionGroupContainerType = + std::unordered_map; + //! \brief return struct for matching groups during context search + using MatchInfoListType = std::vector; + //! \brief mavis assigned UID list type + using InstUidListType = fusion::InstUidListType; - public: - FusionContext() = default; + public: + FusionContext() = default; - //! \brief basic ctor - //! - //! name_ member is assigned in makeContext - FusionContext(const std::string & name, const FusionGroupListType & groups) + //! \brief basic ctor + //! + //! name_ member is assigned in makeContext + FusionContext(const std::string & name, + const FusionGroupListType & groups) + { + makeContext(name_, groups); + } + + //! \brief insert each group into the (only/current) context + void makeContext(const std::string & n, + const FusionGroupListType & groups) + { + name_ = n; + for (const auto & grp : groups) { - makeContext(name, groups); + insertGroup(grp); } + } - //! \brief insert each group into the (only/current) context - void makeContext(const std::string & n, const FusionGroupListType & groups) + //! \brief insert each group w/ catch + void insertGroup(const FusionGroupType & group) + { + fusion::HashType hash = group.hash(); + if (hash == 0) { - name_ = n; - for (const auto & grp : groups) - { - insertGroup(grp); - } + throw fusion::HashIllegalValueError(group.name(), + group.hash()); } - //! \brief insert each group in current context w/ catch - void insertGroup(const FusionGroupType & group) + if (container_.find(hash) != container_.end()) { - fusion::HashType hash = group.hash(); - if (hash == 0) - { - throw fusion::HashIllegalValueError(group.name(), group.hash()); - } - - if (container_.find(hash) != container_.end()) - { - throw fusion::HashDuplicateError(group.name(), group.hash()); - } - container_.insert(std::make_pair(hash, group)); + throw fusion::HashDuplicateError(group.name(), group.hash()); } - private: - //! \brief context name - //! - //! When this is used in (future) multi-context implementations - //! the name must be unique - std::string name_{"base_ctx"}; - //! \brief this is an alias for map in this version - FusionGroupContainerType container_; - // Future: LookupType lookup_; - }; + container_.insert(std::make_pair(hash, group)); + } + + //! \brief public function to get group list from container + FusionGroupContainerType& getFusionGroupContainer() + { + return container_; + } + + private: + //! \brief context name + //! + //! When this is used in (future) multi-context implementations + //! the name must be unique + std::string name_{""}; + //! \brief this is an alias for map in this version + FusionGroupContainerType container_; +}; -} // namespace fusion +} diff --git a/fusion/fusion/FusionExceptions.hpp b/fusion/fusion/FusionExceptions.hpp index 25281364..d1270505 100644 --- a/fusion/fusion/FusionExceptions.hpp +++ b/fusion/fusion/FusionExceptions.hpp @@ -3,20 +3,27 @@ // //! \file FusionExceptions.hpp fusion defined exceptions #pragma once -#include "FusionTypes.hpp" +#include "fusion/FusionTypes.hpp" #include +#include #include #include -#include -//! \class FusionExceptionBase -//! \class HashDuplicateError -//! \class HashIllegalValueError //! \class ContextDuplicateError //! \class FieldExtUnknownField //! \class FieldExtUnknownSpecialField -//! \class FieldExtUnknownFunction +//! \class FileIoError +//! \class FslRuntimeError +//! \class FslSyntaxError +//! \class FusionExceptionBase +//! \class FusionExceptionBase +//! \class FusionInitializationError +//! \class HashDuplicateError +//! \class HashIllegalValueError +//! \class JsonRuntimeError +//! \class JsonSyntaxError +//! \class JsonUnknownError namespace fusion { @@ -38,6 +45,21 @@ namespace fusion std::stringstream ss; }; + //! \brief report any issues during the Fusion context + //! + //! Fusion is initialized by a call from the Decoder ctor to + //! an initialization function. This handles default or + //! non-specific initialization failures. + struct FusionInitializationError : FusionExceptionBase + { + //! \brief ... + explicit FusionInitializationError(const std::string & cause) + { + ss << "Fusion intialization failed: " << cause; + why_ = ss.str(); + } + }; + //! \brief hashes within a context can not overlap //! //! Each fusion group has a hash formed from the uids of the @@ -46,7 +68,8 @@ namespace fusion struct HashDuplicateError : FusionExceptionBase { //! \brief ... - explicit HashDuplicateError(const std::string & name, const fusion::HashType & hash) + explicit HashDuplicateError(const std::string & name, + const fusion::HashType & hash) { ss << "Duplicated hash detected, '" << name << "'" << " 0x" << std::hex << hash; @@ -60,7 +83,8 @@ namespace fusion struct HashIllegalValueError : FusionExceptionBase { //! \brief ... - explicit HashIllegalValueError(const std::string & name, const fusion::HashType & hash) + explicit HashIllegalValueError(const std::string & name, + const fusion::HashType & hash) { ss << "Illegal hash value detected, '" << name << "'" << " 0x" << std::hex << hash; @@ -82,7 +106,94 @@ namespace fusion } }; + //! \brief QParser will throw this + //! + //! Throwing this from the parser has not been implemented + //! yet in this draft PR. + struct FslSyntaxError : FusionExceptionBase + { + //! \brief ... + explicit FslSyntaxError(const std::string & msg, + const int & lineno) + { + ss << "DSL syntax error '" << msg << "'" << std::endl + << " at line: " << lineno; + why_ = ss.str(); + } + }; + + //! \brief for errors discovered when using dsl values + struct FslRuntimeError : FusionExceptionBase + { + //! \brief ... + explicit FslRuntimeError(const std::string & msg) + { + ss << "FslRuntimeError placeholder: " << msg; + why_ = ss.str(); + } + }; + + //! \brief catch all from the DSL code + struct FslUnknownError : FusionExceptionBase + { + //! \brief ... + explicit FslUnknownError(const std::string & msg) + { + ss << "FslUnknownError placeholder: " << msg; + why_ = ss.str(); + } + }; + + //! \brief json syntax errors + //! + //! JSON support is planned but not implemented + //! + //! FIXME: check if nlohmann bombs out before + //! we get here. If so remove this. + struct JsonSyntaxError : FusionExceptionBase + { + //! \brief ... + explicit JsonSyntaxError(const int & lineno) + { + ss << "JSON syntax error line: " << lineno; + why_ = ss.str(); + } + //! \brief ... + explicit JsonSyntaxError() + { + ss << "JSON syntax error reported by nlohmann"; + why_ = ss.str(); + } + }; + + //! \brief for errors discovered when using json values + struct JsonRuntimeError : FusionExceptionBase + { + //! \brief ... + explicit JsonRuntimeError(const std::string & msg) + { + ss << "JsonRuntimeError: " << msg; + why_ = ss.str(); + } + }; + + //! \brief catch all from the JSON code + //! + //! JSON support is planned but not implemented + struct JsonUnknownError : FusionExceptionBase + { + //! \brief ... + explicit JsonUnknownError(const std::string & msg) + { + ss << "JsonUnknownError: " << msg; + why_ = ss.str(); + } + }; + //! \brief field extractor unknown field name + //! + //! FIXME: should add a field enum to string func, + //! convert int to FieldName string struct FieldExtUnknownField : FusionExceptionBase { //! \brief ... @@ -94,23 +205,29 @@ namespace fusion }; //! \brief field extractor unknown special field name + //! + //! FIXME: should add a field enum to string func, + //! convert int to SFieldName string struct FieldExtUnknownSpecialField : FusionExceptionBase { //! \brief ... - explicit FieldExtUnknownSpecialField(uint32_t sfn, std::string dasm) + explicit FieldExtUnknownSpecialField(uint32_t sfn, + std::string dasm) { ss << "Unknown special field: " << sfn << " in " << dasm; why_ = ss.str(); } }; - //! \brief field extractor unknown function - struct FieldExtUnknownFunction : FusionExceptionBase + //! \brief file access exception + struct FileIoError : FusionExceptionBase { //! \brief ... - explicit FieldExtUnknownFunction(uint32_t func) + explicit FileIoError(std::string action, + std::string fileName) { - ss << "Unknown function selection: " << func; + ss << "File access error on '" << action + << "' for file " << fileName; why_ = ss.str(); } }; diff --git a/fusion/fusion/FusionGroup.hpp b/fusion/fusion/FusionGroup.hpp index 931b8b21..58fb2120 100644 --- a/fusion/fusion/FusionGroup.hpp +++ b/fusion/fusion/FusionGroup.hpp @@ -3,312 +3,331 @@ // //! \file FusionGroup.hpp holds fusion definitions and transforms #pragma once -#include "FusionTypes.hpp" -#include "Instruction.hpp" -#include "Mavis.h" +#include "fusion/FusionTypes.hpp" +#include "fusion/Instruction.hpp" +#include "mavis/Mavis.h" -#include -#include #include +#include +#include +#include #include #include -#include -#include +#include //! \class FusionGroupCfg namespace fusion { - //! \brief forward decl - template class FusionGroup; - - // --------------------------------------------------------------- - //! \brief FusionGroup ctor helper - //! - //! FusionGroupCfg helps construct FusionGroups from combinations - //! of ctor arguments. There is more work to do here. +// ---------------------------------------------------------------------- +// ---------------------------------------------------------------------- +//! \brief forward decl +template +class FusionGroup; +// --------------------------------------------------------------- +//! \brief FusionGroup ctor helper +//! +//! FusionGroupCfg helps construct FusionGroups from combinations +//! of ctor arguments. There is more work to do here. +//! +//! MachineInfoType provides access to implementation details of the machine +//! +//! FieldExtractor provides an interface to mavis and support functions +//! for boolean operations. +//! +//! (will) support: +//! UIDs implemented +//! opcodes not implemented, future feature +//! asm text not implemented, future feature +// --------------------------------------------------------------- +template +struct FusionGroupCfg +{ + //! \brief convenient group type + using FusionGroupType = + FusionGroup; + + //! \brief transform functor signature + using TransformFuncType = + bool (*)(FusionGroup &, + InstPtrListType &, InstPtrListType &); + + // Future feature + // uids can be derived from opcs, but not the other way + // if conversion from opcs to uids is required so is Mavis + // std::optional opcs; + // std::optional asms; + // fusion::MavisType *mavis{nullptr}; + + //! \brief default transform functor //! - //! MachineInfoType provides access to implementation details of the machine + //! The default transform makes no changes to machine state, it + //! provides an argument signature for TransformFuncType. + static bool default_transform(FusionGroupType &, InstPtrListType & in, + InstPtrListType & out) + { + (void) in; + (void) out; + return true; + } + //! \brief convenient name string + const std::string name; + //! \brief list of UIDs representing the group + std::optional uids; + //! \brief string key look up for transformFunc mapping //! - //! FieldExtractor provides an interface to mavis and support functions - //! for boolean operations. + //! When used the transformName is the lookup key into an + //! external map containing function objects to perform transforms. + const std::string transformName; + //! \brief handle for the transform function //! - //! (will) support: - //! UIDs implemented - //! opcodes not implemented, future feature - //! asm text not implemented, future feature - // --------------------------------------------------------------- - template struct FusionGroupCfg - { - //! \brief convenient group type - using FusionGroupType = FusionGroup; - - //! \brief transform functor signature - using TransformFuncType = bool (*)(FusionGroup &, - InstPtrListType &, InstPtrListType &); - + //! In previous implementations constraints checking and + //! transformation were enforced as split operations. This is + //! no longer required. + std::optional transformFunc = default_transform; +}; +// --------------------------------------------------------------- +//! \brief FusionGroup parent +//! +//! opcs & asm statements are not supported yet, future +// --------------------------------------------------------------- +class FusionGroupBase +{ + public: + //! \brief save typing + using UidType = fusion::UidType; + //! \brief save typing + using HashType = fusion::HashType; + //! \brief save typing + using InstPtrListType = fusion::InstPtrListType; + //! \brief save typing + using InstUidListType = fusion::InstUidListType; + + //! \brief base ctor + FusionGroupBase(std::string n = "", + InstUidListType u = InstUidListType(), + HashType h = 0) : + name_(n), + uids_(u), // Future feature - // uids can be derived from opcs, but not the other way - // if conversion from opcs to uids is required so is Mavis - // std::optional opcs; - // std::optional asms; - // fusion::MavisType *mavis{nullptr}; - - //! \brief default transform functor - //! - //! The group is not fused, input is appended to out. input is cleared. - static bool default_transform(FusionGroupType &, InstPtrListType & in, - InstPtrListType & out) - { - out.insert(std::end(out), std::begin(in), std::end(in)); - in.clear(); - return true; - } + // opcs_(fusion::OpcodeListType()), + // asms_(fusion::AsmStmtListType()), + hash_(h) + { + } - //! \brief convenient name string - const std::string name; - //! \brief list of UIDs representing the group - std::optional uids; - //! \brief handle for the transform function - //! - //! In previous implementations constraints checking and - //! transformation were enforced as split operations. This is - //! no longer required. - std::optional transformFunc = default_transform; - }; - - // --------------------------------------------------------------- - //! \brief FusionGroup parent - //! - //! opcs & asm statements are not supported yet, future - // --------------------------------------------------------------- - class FusionGroupBase + virtual ~FusionGroupBase() { - public: - //! \brief save typing - using UidType = fusion::UidType; - //! \brief save typing - using HashType = fusion::HashType; - //! \brief save typing - using InstPtrListType = fusion::InstPtrListType; - //! \brief save typing - using InstUidListType = fusion::InstUidListType; - - //! \brief base ctor - FusionGroupBase(std::string n = "", InstUidListType u = InstUidListType(), HashType h = 0) : - name_(n), - uids_(u), - // Future feature - // opcs_(fusion::OpcodeListType()), - // asms_(fusion::AsmStmtListType()), - hash_(h) - { - } + } - //! \brief capture the UIDs and create the hash key - virtual void setUids(InstUidListType & u) - { - uids_ = u; - initHash(); - } + //! \brief capture the UIDs and create the hash key + virtual void setUids(InstUidListType & u) + { + uids_ = u; + initHash(); + } - //! \brief uids accessor - virtual InstUidListType & uids() { return uids_; } + //! \brief uids accessor + virtual InstUidListType & uids() { return uids_; } - // virtual OpcodeListType& opcs() { return opcs_; } - // virtual AsmStmtListType& asms() { return asms_; } + // virtual OpcodeListType& opcs() { return opcs_; } + // virtual AsmStmtListType& asms() { return asms_; } - //! \brief hash setter - virtual void setHash(HashType hash) { hash_ = hash; } + //! \brief hash setter + virtual void setHash(HashType hash) { hash_ = hash; } - //! \brief refresh the hash from uids_ - virtual void initHash() { hash_ = jenkins_1aat(uids_); } + //! \brief refresh the hash from uids_ + virtual void initHash() { hash_ = jenkins_1aat(uids_); } - //! \brief hash accessor - virtual HashType hash() const { return hash_; } + //! \brief hash accessor + virtual HashType hash() const { return hash_; } - //! \brief convenience function - virtual void setName(std::string n) { name_ = n; } + //! \brief convenience function + virtual void setName(std::string n) { name_ = n; } - //! \brief name accessor - virtual std::string name() const { return name_; } + //! \brief name accessor + virtual std::string name() const { return name_; } - //! \brief report fgroup state to stream - //! - //! I prefer this instead of overloading << - virtual void info(std::ostream & os = std::cout) const - { - std::cout << "Name: " << name() << std::endl; - std::cout << " HASH: " << std::hex << " 0x" << hash() << std::endl; - std::cout << " UIDS: "; - for (auto u : uids_) - std::cout << std::hex << " 0x" << u; - std::cout << std::endl; - // Future feature - // std::cout<<" OPCs: "; - // for(auto o : opcs_) std::cout< & v) + //! \brief report fgroup state to stream + friend std::ostream& operator<<(std::ostream& os, + const FusionGroupBase& grp) + { + os << "X Name: " << grp.name(); + os << " Hash: " << std::hex << " " << grp.hash(); + os << " Uids: "; + for (auto u : grp.uids_) { - HashType hash = 0; - - for (auto i : v) - { - hash += i; - hash += (hash << 10); - hash ^= (hash >> 6); - } - - hash += (hash << 3); - hash ^= (hash >> 11); - hash += (hash << 15); - - return hash; + os << " " << std::hex << std::setw(2) << std::setfill('0') << u; } + return os; + } - private: - //! \brief convenient name string - std::string name_{""}; - //! \brief the instruction UIDs known to mavis - InstUidListType uids_; - // Future feature - // OpcodeListType opcs_; - // AsmStmtListType asms_; - //! \brief uint32_t hash key - HashType hash_{0}; - }; - - // --------------------------------------------------------------- - //! \brief A fusion group is the basis for fusion detection and transformation + public: + //! \brief Method to calculate hash based on UIDs //! - //! A fusion group is a list of UIDs that represent data useful for - //! matching a group against incoming Inst::PtrTypes as well as - //! constraints checking. - //! - //! TransformFuncType transform_ is the functor handle. The default - //! is expected to be overridden externally. - // --------------------------------------------------------------- - template - class FusionGroup : public FusionGroupBase + //! In future consider making this a user controlled functor. + //! In hardware the adds are not desirable. + static HashType jenkins_1aat(const std::vector & v) { - //! \brief convenient typedef - using FusionGroupType = FusionGroup; - - //! \brief conform to convention used else where - using MavisType = Mavis, uArchInfo>; - - //! \brief convenient typedef - using FusionGroupCfgType = FusionGroupCfg; + HashType hash = 0; - //! \brief functor signature and typedef - using TransformFuncType = bool (*)(FusionGroup &, - InstPtrListType &, InstPtrListType &); - - public: - //! \brief conform to convention used elsewhere - typedef typename std::shared_ptr> PtrType; - - // -------------------------------------------------------------------- - //! \brief default ctor - FusionGroup(std::string n = "", InstUidListType u = InstUidListType(), - TransformFuncType t = nullptr) : - FusionGroupBase(n, u), - transform_(t) + for (auto i : v) { - initHash(); + hash += i; + hash += (hash << 10); + hash ^= (hash >> 6); } - // -------------------------------------------------------------------- - //! \brief groupcfg ctor - FusionGroup(const FusionGroupCfgType & cfg) : - FusionGroupBase(cfg.name, - cfg.uids ? *cfg.uids : InstUidListType() //, - // cfg.opcs ? *cfg.opcs : OpcodeListType() - ), - transform_(cfg.transformFunc ? *cfg.transformFunc : nullptr) - { - if (uids().size() == 0) - { - throw std::invalid_argument("For " + cfg.name - + " uids are required in this implementation"); - } - initHash(); - } - - //! \brief transform elements of input to append to output - //! - //! transform() is called when a fusiongroup is selected by - //! Fusion (fusion.h). The return value returns true if - //! the transform function met the criterion. False if not. - //! On false Fusion continues to search the context. - //! - //! The transform operation is expected to modify in if fusion - //! occurs and also to append to out with the transformation - //! results. All combinations of true/false, modifying/not modifying - //! input and output are valid. - bool transform(InstPtrListType & in, InstPtrListType & out) + hash += (hash << 3); + hash ^= (hash >> 11); + hash += (hash << 15); + + return hash; + } + + private: + //! \brief convenient name string + std::string name_{""}; + //! \brief the instruction UIDs known to mavis + InstUidListType uids_; + // Future feature + // OpcodeListType opcs_; + // AsmStmtListType asms_; + //! \brief uint32_t hash key + HashType hash_{0}; +}; + +// --------------------------------------------------------------- +//! \brief A fusion group is the basis for fusion detection and transformation +//! +//! A fusion group is a list of UIDs that represent data useful for +//! matching a group against incoming Inst::PtrTypes as well as +//! constraints checking. +//! +//! TransformFuncType transform_ is the functor handle. The default +//! is expected to be overridden externally. +// --------------------------------------------------------------- +template +class FusionGroup : public FusionGroupBase +{ + //! \brief convenient typedef + using FusionGroupType = + FusionGroup; + + //! \brief conform to convention used else where + using MavisType = Mavis, uArchInfo>; + + //! \brief convenient typedef + using FusionGroupCfgType = + FusionGroupCfg; + + //! \brief functor signature and typedef + using TransformFuncType = + bool (*)(FusionGroup &, + InstPtrListType &, InstPtrListType &); + + public: + //! \brief conform to convention used elsewhere + typedef typename std::shared_ptr< + FusionGroup> + PtrType; + + // -------------------------------------------------------------------- + //! \brief default ctor + FusionGroup(std::string n = "", InstUidListType u = InstUidListType(), + TransformFuncType t = nullptr) : + FusionGroupBase(n, u), + transform_(t) + { + initHash(); + } + + // -------------------------------------------------------------------- + //! \brief groupcfg ctor + FusionGroup(const FusionGroupCfgType & cfg) : + FusionGroupBase(cfg.name, + cfg.uids ? *cfg.uids : InstUidListType() //, + // cfg.opcs ? *cfg.opcs : OpcodeListType() + ), + transform_(cfg.transformFunc ? *cfg.transformFunc : nullptr) + { + if (uids().size() == 0) { - if (transform_) - return transform_(*this, in, out); - return false; + throw std::invalid_argument( + "For " + cfg.name + + " uids are required in this implementation"); } + initHash(); + } + + // Future support + // void opc2uid(const OpcodeListType &opcs, + // const fusion::MavisType *mavis, + // InstUidListType &uid_vec) + //{ + // } + + // Future support + // void asm2uid(const AsmStmtListType &asm, + // const fusion::MavisType *mavis, + // InstUidListType &uid_vec) + //{ + // } + + //! \brief transform elements of input to append to output + //! + //! transform() is called when a fusiongroup is selected by + //! Fusion (fusion.h). The return value returns true if + //! the transform function met the criterion. False if not. + //! On false Fusion continues to search the context. + //! + //! The transform operation is expected to modify in if fusion + //! occurs and also to append to out with the transformation + //! results. All combinations of true/false, modifying/not modifying + //! input and output are valid. + bool transform(InstPtrListType & in, InstPtrListType & out) + { + if (transform_) + return transform_(*this, in, out); + return false; + } - //! \brief default transform functor - //! - //! The group is not fused, input is appended to out. input is cleared. - static bool default_transform(FusionGroupType &, InstPtrListType & in, - InstPtrListType & out) - { - out.insert(std::end(out), std::begin(in), std::end(in)); - in.clear(); - return true; - } + //! \brief default transform functor + //! + //! The group is not fused, input is appended to out. input is cleared. + static bool default_transform(FusionGroupType &, InstPtrListType & in, + InstPtrListType & out) + { + out.insert(std::end(out), std::begin(in), std::end(in)); + in.clear(); + return true; + } - //! \brief user method for changing the default transform functor - void setTransform(TransformFuncType func) { transform_ = func; } + //! \brief user method for changing the default transform functor + void setTransform(TransformFuncType func) { transform_ = func; } - //! \brief tranform handle accessor - TransformFuncType getTransform() { return transform_; } + //! \brief tranform handle accessor + TransformFuncType getTransform() { return transform_; } - //! \brief machine info handle accessor - MachineInfoType & mi() { return mi_; } + //! \brief machine info handle accessor + MachineInfoType & mi() { return mi_; } - //! \brief machine info handle accessor - MachineInfoType & machineInfo() { return mi(); } + //! \brief machine info handle accessor + MachineInfoType & machineInfo() { return mi(); } - //! \brief field extractor handle accessor - FieldExtractorType & fe() { return fe_; } + //! \brief field extractor handle accessor + FieldExtractorType & fe() { return fe_; } - //! \brief field extractor handle accessor - FieldExtractorType & fieldExtractor() { return fe(); } + //! \brief field extractor handle accessor + FieldExtractorType & fieldExtractor() { return fe(); } - private: - //! \brief MachineInfo provides access to uarch details - MachineInfoType mi_; - //! \brief FieldExtractor provides field access methods - FieldExtractorType fe_; - //! \brief handle to transform functor - TransformFuncType transform_ = default_transform; - }; + private: + //! \brief MachineInfo provides access to uarch details + MachineInfoType mi_; + //! \brief FieldExtractor provides field access methods + FieldExtractorType fe_; + //! \brief handle to transform functor + TransformFuncType transform_ = default_transform; +}; } // namespace fusion diff --git a/fusion/fusion/FusionTypes.hpp b/fusion/fusion/FusionTypes.hpp index c224e513..a7c5bf8a 100644 --- a/fusion/fusion/FusionTypes.hpp +++ b/fusion/fusion/FusionTypes.hpp @@ -3,55 +3,55 @@ // //! \file FusionTypes.hpp marshalled types used by fusion #pragma once -#include "Mavis.h" +#include "fusion/Instruction.hpp" +#include "fusion/uArchInfo.hpp" #include "mavis/DecoderTypes.h" -#include "Instruction.hpp" -#include "uArchInfo.hpp" +#include "mavis/Mavis.h" + #include #include #include namespace fusion +{ +//! \brief ... +using MavisType = Mavis, uArchInfo>; +//! \brief ... +using FileNameListType = std::vector; +//! \brief ... +using UidType = mavis::InstructionUniqueID; +//! \brief ... +using InstUidListType = std::vector; +//! \brief ... +using FieldName = mavis::InstMetaData::OperandFieldID; +//! \brief ... +using SFieldName = mavis::ExtractorIF::SpecialField; +//! \brief ... +using Opcode = mavis::Opcode; +//! \brief ... +using OpcodeListType = std::vector; +//! \brief ... +using AsmStmtListType = std::vector; +//! \brief ... +using InstPtrType = Instruction::PtrType; +//! \brief ... +using InstPtrListType = std::vector::PtrType>; +//! \brief ... +using HashType = uint32_t; +//! \brief ... +using HashListType = std::vector; + +//! \brief This is provided but has limited use currently +template struct ShrPtrAlloc { //! \brief ... - using MavisType = Mavis, uArchInfo>; - - //! \brief ... - using FileNameListType = std::vector; - - //! \brief ... - using UidType = mavis::InstructionUniqueID; - //! \brief ... - using InstUidListType = std::vector; - - //! \brief ... - using FieldName = mavis::InstMetaData::OperandFieldID; - //! \brief ... - using SFieldName = mavis::ExtractorIF::SpecialField; - //! \brief ... - using Opcode = mavis::Opcode; - //! \brief ... - using OpcodeListType = std::vector; - - //! \brief ... - using AsmStmtListType = std::vector; + using Tptr = std::shared_ptr; //! \brief ... - using InstPtrListType = std::vector::PtrType>; - //! \brief ... - using HashType = uint32_t; - - //! \brief This is provided but has limited use currently - template struct ShrPtrAlloc + template Tptr operator()(Args &&... args) { - //! \brief ... - using Tptr = std::shared_ptr; - - //! \brief ... - template Tptr operator()(Args &&... args) - { - return std::make_shared(std::forward(args)...); - } - }; + return std::make_shared(std::forward(args)...); + } +}; -} // namespace fusion +} diff --git a/fusion/fusion/HCache.hpp b/fusion/fusion/HCache.hpp new file mode 100644 index 00000000..028137ac --- /dev/null +++ b/fusion/fusion/HCache.hpp @@ -0,0 +1,164 @@ +// Header placeholder +// contact jeff at condor +//! \file Hcache.hpp +#pragma once +//TODO: do general performance tests for map, uo_map, array etc. +//TODO: template +#include "fusion/FusionTypes.hpp" + +#include +#include +#include + +namespace fusion +{ + +//! \class HCache +//! +//! \brief Length indexed fusion group hash lookup structure +//! +//! HCache provides performance benefit to the model's execution when +//! there are a large number of FusionGroups to compare UID sequences +//! against. +struct HCache +{ + //! \brief 'words' in the $ 'line' are pairs, length and resulting hash + using HashPair = pair; + //! \brief value type for cache + using HashPairListType = std::vector; + //! \brief type used by the 'cache' array + using HCacheType = std::map; + //! \brief non-const iterator + using HCacheItrType = HCacheType::iterator; + //! \brief function object type + using HashFuncType + = fusion::HashType (*)(const std::vector&); + //! \brief ctor with hash function object argument + HCache(HashFuncType func = nullptr) + : hashFunc(func) + {} + + // ------------------------------------------------------------------ + //! \brief create an entry in the cache + //! + // Create a size-indexed entry for a hash of inputUid fragments of + // length grpSize. This entry is added to the hashCache. + // + // On entry into this function a walking hash is created for the + // fusion group size. e.g. if grpSize is three, and the input is + // length 5, three hashes will be created. + // + // a b c d e input + // F F F hash 1 + // F F F hash 2 + // F F F hash 3 + // + // These hashes are cached, indexed by length. + // + // A cache line is a list of pairs, a list of + // The cache is a map of cachelines, indexed by size, + // + // ------------------------------------------------------------------ + void buildHashCacheEntry(const InstUidListType &inputUids,size_t grpSize) + { + //create the sub-divisions of length grpSize + vector fragments; + subDivideUids(fragments,inputUids,grpSize); + + //this will be the 'cacheline' of hcache, calculate the + //hash of each fragment. pair.first is the index into fragments + HashPairListType cacheLine; + + size_t i=0; + for(auto & uidVec : fragments) { + fusion::HashType hash = hashFunc(uidVec); + cacheLine.push_back(make_pair(i++,hash)); + } + + hcache.insert(make_pair(grpSize,cacheLine)); + } + + // ------------------------------------------------------------------ + //! \brief prepare a vector of uids for the hash operation + //! + //! Uids groups based on the length of the input. The hash will + //! be formed for each sub-division + // ------------------------------------------------------------------ + void subDivideUids(std::vector &output, + const InstUidListType &inputUids,size_t length) + { + if(length == 0 || inputUids.size() == 0) return; + + output.clear(); + + // Calculate how many vectors of reference's length can be generated + size_t numVectors = inputUids.size() - length + 1; + + for (size_t i = 0; i < numVectors; ++i) { + InstUidListType temp; + + // Extract elements from input starting at index i, + // with the same length as reference + for (size_t j = 0; j < length; ++j) { + temp.push_back(inputUids[i + j]); + } + + output.push_back(temp); + } + } + + // ------------------------------------------------------------------ + //! \brief output cache entries to a stream + // ------------------------------------------------------------------ + void infoHCache(std::ostream &os) + { + os<<"INFO hcache"; + for(auto & hc : hcache) + { + os<<" "<second; + } + +private: + //! \brief function object for hash creation + HashFuncType hashFunc; + + //! \brief future feature: support for template version of HCache + std::map hcache; +}; + +} diff --git a/fusion/fusion/Instruction.hpp b/fusion/fusion/Instruction.hpp index accab0bf..8584dcb3 100644 --- a/fusion/fusion/Instruction.hpp +++ b/fusion/fusion/Instruction.hpp @@ -3,7 +3,8 @@ // // JN: original file name was Inst.h -#pragma once +#ifndef DTABLEPROTO03_INST_H +#define DTABLEPROTO03_INST_H #include #include @@ -13,140 +14,133 @@ /** * EXAMPLE Instruction */ -template class Instruction -{ - public: - typedef typename std::shared_ptr> PtrType; - - public: - // Instruction(const mavis::DecodedInstructionInfo::PtrType& dii, const - // uint64_t icode, const mavis::ExtractorIF::PtrType& extractor, const - // typename AnnotationType::PtrType& ui) : - Instruction(const mavis::OpcodeInfo::PtrType & dinfo, - const typename AnnotationType::PtrType & ui, uint32_t dummy) : - dinfo_(dinfo), - uinfo_(ui) - { - } - - // Copy construction (needed for cloning) - Instruction(const Instruction &) = default; - - // Morph into a different instruction (new mavis info) - void morph(const typename mavis::OpcodeInfo::PtrType & new_dinfo, - const typename AnnotationType::PtrType & new_ui) - { - // dinfo_.reset(new_dinfo); - dinfo_ = new_dinfo; - // uinfo_.reset(new_ui); - uinfo_ = new_ui; - - // TBD: Instruction user may need to reset any information cached with this - // instruction - } - - /** - * User code to "recycle" an instruction (which DTable has cached and is - * attempting to reuse) - */ - void recycle() {} - - typename mavis::OpcodeInfo::PtrType getOpInfo() const { return dinfo_; } - - std::string getMnemonic() const { return dinfo_->getMnemonic(); } - - std::string dasmString() const { return dinfo_->dasmString(); } - - bool isInstType(mavis::InstMetaData::InstructionTypes itype) const - { - return dinfo_->isInstType(itype); - } +template class Instruction { +public: + typedef typename std::shared_ptr> PtrType; - bool isExtInstType(mavis::DecodedInstructionInfo::ExtractedInstTypes itype) const - { - return dinfo_->isExtractedInstType(itype); - } - - int64_t getSignedOffset() const { return dinfo_->getSignedOffset(); } - - mavis::DecodedInstructionInfo::BitMask getSourceAddressRegs() const - { - return dinfo_->getSourceAddressRegs(); - } - - mavis::DecodedInstructionInfo::BitMask getSourceDataRegs() const - { - return dinfo_->getSourceDataRegs(); - } - - mavis::DecodedInstructionInfo::BitMask getIntSourceRegs() const - { - return dinfo_->getIntSourceRegs(); - } - - mavis::DecodedInstructionInfo::BitMask getFloatSourceRegs() const - { - return dinfo_->getFloatSourceRegs(); - } - - mavis::DecodedInstructionInfo::BitMask getVectorSourceRegs() const - { - return dinfo_->getVectorSourceRegs(); - } - - mavis::DecodedInstructionInfo::BitMask getIntDestRegs() const - { - return dinfo_->getIntDestRegs(); - } - - mavis::DecodedInstructionInfo::BitMask getFloatDestRegs() const - { - return dinfo_->getFloatDestRegs(); - } - - mavis::DecodedInstructionInfo::BitMask getVectorDestRegs() const - { - return dinfo_->getVectorDestRegs(); - } - - uint64_t getSpecialField(mavis::OpcodeInfo::SpecialField sfid) const - { - return dinfo_->getSpecialField(sfid); - } - - const mavis::OperandInfo & getSourceOpInfo() const { return dinfo_->getSourceOpInfo(); } - - const mavis::OperandInfo & getDestOpInfo() const { return dinfo_->getDestOpInfo(); } - - mavis::InstructionUniqueID getUID() const { return dinfo_->getInstructionUniqueID(); } - - bool hasImmediate() const { return dinfo_->hasImmediate(); } - - const typename AnnotationType::PtrType & getuArchInfo() const { return uinfo_; } - - // private: - typename mavis::OpcodeInfo::PtrType dinfo_; - typename AnnotationType::PtrType uinfo_; - - void print(std::ostream & os) const - { - std::ios_base::fmtflags os_state(os.flags()); - os << dinfo_->getMnemonic() << ", src[" << dinfo_->numSourceRegs() - << "]: " << dinfo_->getSourceRegs() << " (addr: " << dinfo_->getSourceAddressRegs() - << ")" - << ", dst: " << dinfo_->getDestRegs() << ", data size: " << std::dec - << dinfo_->getDataSize(); - if (uinfo_ != nullptr) - { - os << ", uInfo: " << *uinfo_; - } - os.flags(os_state); - } - - public: - friend std::ostream & operator<<(std::ostream & os, const Instruction & i) - { - i.print(os); - return os; +public: + // Instruction(const mavis::DecodedInstructionInfo::PtrType& dii, const + // uint64_t icode, const mavis::ExtractorIF::PtrType& extractor, const + // typename AnnotationType::PtrType& ui) : + Instruction(const mavis::OpcodeInfo::PtrType &dinfo, + const typename AnnotationType::PtrType &ui, uint32_t dummy) + : dinfo_(dinfo), uinfo_(ui) {} + + // Copy construction (needed for cloning) + Instruction(const Instruction &) = default; + + // Morph into a different instruction (new mavis info) + void morph(const typename mavis::OpcodeInfo::PtrType &new_dinfo, + const typename AnnotationType::PtrType &new_ui) { + // dinfo_.reset(new_dinfo); + dinfo_ = new_dinfo; + // uinfo_.reset(new_ui); + uinfo_ = new_ui; + + // TBD: Instruction user may need to reset any information cached with this + // instruction + } + + /** + * User code to "recycle" an instruction (which DTable has cached and is + * attempting to reuse) + */ + void recycle() {} + + typename mavis::OpcodeInfo::PtrType getOpInfo() const { return dinfo_; } + + std::string getMnemonic() const { return dinfo_->getMnemonic(); } + + std::string dasmString() const { return dinfo_->dasmString(); } + + bool isInstType(mavis::InstMetaData::InstructionTypes itype) const { + return dinfo_->isInstType(itype); + } + + bool + isExtInstType(mavis::DecodedInstructionInfo::ExtractedInstTypes itype) const { + return dinfo_->isExtractedInstType(itype); + } + + int64_t getSignedOffset() const { return dinfo_->getSignedOffset(); } + + mavis::DecodedInstructionInfo::BitMask getSourceAddressRegs() const { + return dinfo_->getSourceAddressRegs(); + } + + mavis::DecodedInstructionInfo::BitMask getSourceDataRegs() const { + return dinfo_->getSourceDataRegs(); + } + + mavis::DecodedInstructionInfo::BitMask getIntSourceRegs() const { + return dinfo_->getIntSourceRegs(); + } + + mavis::DecodedInstructionInfo::BitMask getFloatSourceRegs() const { + return dinfo_->getFloatSourceRegs(); + } + + mavis::DecodedInstructionInfo::BitMask getVectorSourceRegs() const { + return dinfo_->getVectorSourceRegs(); + } + + mavis::DecodedInstructionInfo::BitMask getIntDestRegs() const { + return dinfo_->getIntDestRegs(); + } + + mavis::DecodedInstructionInfo::BitMask getFloatDestRegs() const { + return dinfo_->getFloatDestRegs(); + } + + mavis::DecodedInstructionInfo::BitMask getVectorDestRegs() const { + return dinfo_->getVectorDestRegs(); + } + + uint64_t getSpecialField(mavis::OpcodeInfo::SpecialField sfid) const { + return dinfo_->getSpecialField(sfid); + } + + const mavis::OperandInfo &getSourceOpInfo() const { + return dinfo_->getSourceOpInfo(); + } + + const mavis::OperandInfo &getDestOpInfo() const { + return dinfo_->getDestOpInfo(); + } + + mavis::InstructionUniqueID getUID() const { + return dinfo_->getInstructionUniqueID(); + } + + bool hasImmediate() const { return dinfo_->hasImmediate(); } + + const typename AnnotationType::PtrType &getuArchInfo() const { + return uinfo_; + } + +//private: + typename mavis::OpcodeInfo::PtrType dinfo_; + typename AnnotationType::PtrType uinfo_; + + void print(std::ostream &os) const { + std::ios_base::fmtflags os_state(os.flags()); + os << dinfo_->getMnemonic() << ", src[" << dinfo_->numSourceRegs() + << "]: " << dinfo_->getSourceRegs() + << " (addr: " << dinfo_->getSourceAddressRegs() << ")" + << ", dst: " << dinfo_->getDestRegs() << ", data size: " << std::dec + << dinfo_->getDataSize(); + if (uinfo_ != nullptr) { + os << ", uInfo: " << *uinfo_; } + os.flags(os_state); + } + +public: + friend std::ostream &operator<<(std::ostream &os, + const Instruction &i) { + i.print(os); + return os; + } }; + +#endif // DTABLEPROTO03_INST_H diff --git a/fusion/fusion/MachineInfo.hpp b/fusion/fusion/MachineInfo.hpp index 4c018592..cf83607b 100644 --- a/fusion/fusion/MachineInfo.hpp +++ b/fusion/fusion/MachineInfo.hpp @@ -4,9 +4,9 @@ //! \file MachineInfo.hpp processor implementation details #pragma once #include -#include -#include #include +#include +#include //! \brief Placeholder for uarch and implementation details //! diff --git a/fusion/fusion/RadixTrie.hpp b/fusion/fusion/RadixTrie.hpp index 6f7b738f..82f9ee28 100644 --- a/fusion/fusion/RadixTrie.hpp +++ b/fusion/fusion/RadixTrie.hpp @@ -67,12 +67,15 @@ template class RadixTrie void insert(uint32_t key) { insertRecursive(root, key, 0); } //! \brief find... - bool search(uint32_t key) const { return searchRecursive(root, key, 0); } + bool search(uint32_t key) const + { + return searchRecursive(root, key, 0); + } private: //! \brief ... - void insertRecursive(std::unique_ptr> & node, uint32_t key, - uint32_t depth) + void insertRecursive(std::unique_ptr> & node, + uint32_t key, uint32_t depth) { if (!node) { @@ -85,13 +88,15 @@ template class RadixTrie return; } - uint32_t index = (key >> (BIT_WIDTH * (MAX_DEPTH - depth - 1))) & ((1 << BIT_WIDTH) - 1); + uint32_t index = (key >> (BIT_WIDTH * (MAX_DEPTH - depth - 1))) + & ((1 << BIT_WIDTH) - 1); insertRecursive(node->children[index], key, depth + 1); } //! \brief ... - bool searchRecursive(const std::unique_ptr> & node, uint32_t key, - uint32_t depth) const + bool + searchRecursive(const std::unique_ptr> & node, + uint32_t key, uint32_t depth) const { if (!node) { @@ -101,7 +106,8 @@ template class RadixTrie { return node->isEndOfWord; } - uint32_t index = (key >> (BIT_WIDTH * (MAX_DEPTH - depth - 1))) & ((1 << BIT_WIDTH) - 1); + uint32_t index = (key >> (BIT_WIDTH * (MAX_DEPTH - depth - 1))) + & ((1 << BIT_WIDTH) - 1); return searchRecursive(node->children[index], key, depth + 1); } @@ -109,5 +115,6 @@ template class RadixTrie std::unique_ptr> root; //! \brief keep a limit on depth based on the template parameter - static constexpr uint32_t MAX_DEPTH = std::numeric_limits::digits / BIT_WIDTH; + static constexpr uint32_t MAX_DEPTH = + std::numeric_limits::digits / BIT_WIDTH; }; diff --git a/fusion/fusion/uArchInfo.hpp b/fusion/fusion/uArchInfo.hpp index 74415582..7980732a 100644 --- a/fusion/fusion/uArchInfo.hpp +++ b/fusion/fusion/uArchInfo.hpp @@ -3,9 +3,9 @@ // #pragma once #include "json.hpp" +#include "fusion/uArchInfoExceptions.hpp" #include "mavis/DecoderExceptions.h" #include "mavis/DecoderTypes.h" -#include "uArchInfoExceptions.hpp" #include #include @@ -15,200 +15,177 @@ /** * uArchInfo: encapsulates "static" u-arch specific information */ -class uArchInfo -{ - public: - typedef std::shared_ptr PtrType; - - // TODO: flesh this out - enum class UnitSet : uint64_t - { - AGU = 1ull << 0, - INT = 1ull << 1, - FLOAT = 1ull << 2, - MULTIPLY = 1ull << 3, - DIVIDE = 1ull << 4, - BRANCH = 1ull << 5, - LOAD = 1ull << 6, - STORE = 1ull << 7, - SYSTEM = 1ull << 8, - VECTOR = 1ull << 9, - }; - - // BEGIN: Stuff imported from the old StaticInstructionData object - enum RegFile - { - INTEGER, - FLOAT, - INVALID, - N_REGFILES = INVALID - }; - - static inline const char* const regfile_names[] = {"integer", "float"}; - - // TODO: resolve this against the UnitSet - enum IssueTarget : std::uint16_t - { - IEX, - FEX, - BR, - LSU, - ROB, // Instructions that go right to retire - N_ISSUE_TARGETS - }; - - static constexpr uint32_t MAX_ARCH_REGS = 5; - // END: Stuff imported from the old StaticInstructionData object - - private: - // Map unit names to unit ID's - static inline std::map umap_ = { - {"agu", UnitSet::AGU}, {"int", UnitSet::INT}, {"float", UnitSet::FLOAT}, - {"mul", UnitSet::MULTIPLY}, {"div", UnitSet::DIVIDE}, {"branch", UnitSet::BRANCH}, - {"load", UnitSet::LOAD}, {"store", UnitSet::STORE}, {"system", UnitSet::SYSTEM}, - {"vector", UnitSet::VECTOR}, - }; - - // TEMPORARY: map unit names to TargetUnit for back-level compatibility - static inline std::map issue_target_map_ = { - {"int", IssueTarget::IEX}, {"float", IssueTarget::FEX}, - {"branch", IssueTarget::BR}, {"load", IssueTarget::LSU}, - {"store", IssueTarget::LSU}, {"system", IssueTarget::ROB}, // TEMPORARY! - {"vector", IssueTarget::FEX}, // TEMPORARY! - {"rob", IssueTarget::ROB}, // TEMPORARY! - }; - - public: - /** - * \brief This object encapsulates all the micro-architectural information - * that depends on the instruction type. It is "static" and cached by Mavis in - * the instruction factories. Mavis will pass the nlohmann::json object to - * this constructor so that the user can parse any of the desired fields from - * the u-arch JSON file supplied to Mavis \param jobj nlohmann::json object - * for the given instruction - */ - explicit uArchInfo(const nlohmann::json & jobj) { parse_(jobj); } - - uArchInfo() = default; - uArchInfo(const uArchInfo &) = delete; - - void update(const nlohmann::json & jobj) - { - // TODO: identical to constructor(jobj) for now, but we may want to provide - // update restrictions - parse_(jobj); +class uArchInfo { +public: + typedef std::shared_ptr PtrType; + + // TODO: flesh this out + enum class UnitSet : uint64_t { + AGU = 1ull << 0, + INT = 1ull << 1, + FLOAT = 1ull << 2, + MULTIPLY = 1ull << 3, + DIVIDE = 1ull << 4, + BRANCH = 1ull << 5, + LOAD = 1ull << 6, + STORE = 1ull << 7, + SYSTEM = 1ull << 8, + VECTOR = 1ull << 9, + }; + + // BEGIN: Stuff imported from the old StaticInstructionData object + enum RegFile { INTEGER, FLOAT, INVALID, N_REGFILES = INVALID }; + + static inline const char *const regfile_names[] = {"integer", "float"}; + + // TODO: resolve this against the UnitSet + enum IssueTarget : std::uint16_t { + IEX, + FEX, + BR, + LSU, + ROB, // Instructions that go right to retire + N_ISSUE_TARGETS + }; + + static constexpr uint32_t MAX_ARCH_REGS = 5; + // END: Stuff imported from the old StaticInstructionData object + +private: + // Map unit names to unit ID's + static inline std::map umap_ = { + {"agu", UnitSet::AGU}, {"int", UnitSet::INT}, + {"float", UnitSet::FLOAT}, {"mul", UnitSet::MULTIPLY}, + {"div", UnitSet::DIVIDE}, {"branch", UnitSet::BRANCH}, + {"load", UnitSet::LOAD}, {"store", UnitSet::STORE}, + {"system", UnitSet::SYSTEM}, {"vector", UnitSet::VECTOR}, + }; + + // TEMPORARY: map unit names to TargetUnit for back-level compatibility + static inline std::map issue_target_map_ = { + {"int", IssueTarget::IEX}, {"float", IssueTarget::FEX}, + {"branch", IssueTarget::BR}, {"load", IssueTarget::LSU}, + {"store", IssueTarget::LSU}, {"system", IssueTarget::ROB}, // TEMPORARY! + {"vector", IssueTarget::FEX}, // TEMPORARY! + {"rob", IssueTarget::ROB}, // TEMPORARY! + }; + +public: + /** + * \brief This object encapsulates all the micro-architectural information + * that depends on the instruction type. It is "static" and cached by Mavis in + * the instruction factories. Mavis will pass the nlohmann::json object to + * this constructor so that the user can parse any of the desired fields from + * the u-arch JSON file supplied to Mavis \param jobj nlohmann::json object + * for the given instruction + */ + explicit uArchInfo(const nlohmann::json &jobj) { parse_(jobj); } + + uArchInfo() = default; + uArchInfo(const uArchInfo &) = delete; + + void update(const nlohmann::json &jobj) { + // TODO: identical to constructor(jobj) for now, but we may want to provide + // update restrictions + parse_(jobj); + } + + bool isUnit(UnitSet u) const { + return (static_cast(u) & units_) != 0; + } + + IssueTarget getIssueTarget() const { return issue_target_; } + + uint32_t getLatency() const { return latency_; } + + bool isPipelined() const { return pipelined_; } + + bool isSerialized() const { return serialize_; } + + bool isROBGrpStart() const { return rob_grp_start_; } + + bool isROBGrpEnd() const { return rob_grp_end_; } + +private: + uint64_t units_ = 0; ///< Bit mask of target execution units (from UnitSet) + IssueTarget issue_target_ = IssueTarget::N_ISSUE_TARGETS; ///< Issue target + uint32_t latency_ = 0; ///< Execution latency + bool pipelined_ = true; ///< Pipelined execution (non-blocking)? + bool serialize_ = false; ///< Serializes execution? + bool rob_grp_start_ = false; ///< Starts a new ROB group? + bool rob_grp_end_ = false; ///< Ends a ROB group? + +private: + friend std::ostream &operator<<(std::ostream &os, const uArchInfo &ui); + void print(std::ostream &os) const { + os << "{units: 0x" << std::hex << units_ << ", lat: " << std::dec + << latency_ << ", piped: " << pipelined_ << ", serialize: " << serialize_ + << ", ROB group begin: " << rob_grp_start_ + << ", ROB group end: " << rob_grp_end_ << "}"; + } + + /** + * \brief Parse the JSON file + * \param jobj + */ + void parse_(const nlohmann::json &jobj) { + // Issue target (from IssueTarget) + if (jobj.find("issue") != jobj.end()) { + const auto itr = issue_target_map_.find(jobj["issue"]); + if (itr == issue_target_map_.end()) { + throw uArchInfoUnknownIssueTarget(jobj["mnemonic"], jobj["issue"]); + } + issue_target_ = itr->second; } - bool isUnit(UnitSet u) const { return (static_cast(u) & units_) != 0; } - - IssueTarget getIssueTarget() const { return issue_target_; } - - uint32_t getLatency() const { return latency_; } - - bool isPipelined() const { return pipelined_; } - - bool isSerialized() const { return serialize_; } - - bool isROBGrpStart() const { return rob_grp_start_; } - - bool isROBGrpEnd() const { return rob_grp_end_; } - - private: - uint64_t units_ = 0; ///< Bit mask of target execution units (from UnitSet) - IssueTarget issue_target_ = IssueTarget::N_ISSUE_TARGETS; ///< Issue target - uint32_t latency_ = 0; ///< Execution latency - bool pipelined_ = true; ///< Pipelined execution (non-blocking)? - bool serialize_ = false; ///< Serializes execution? - bool rob_grp_start_ = false; ///< Starts a new ROB group? - bool rob_grp_end_ = false; ///< Ends a ROB group? - - private: - friend std::ostream & operator<<(std::ostream & os, const uArchInfo & ui); - - void print(std::ostream & os) const - { - os << "{units: 0x" << std::hex << units_ << ", lat: " << std::dec << latency_ - << ", piped: " << pipelined_ << ", serialize: " << serialize_ - << ", ROB group begin: " << rob_grp_start_ << ", ROB group end: " << rob_grp_end_ << "}"; - } - - /** - * \brief Parse the JSON file - * \param jobj - */ - void parse_(const nlohmann::json & jobj) - { - // Issue target (from IssueTarget) - if (jobj.find("issue") != jobj.end()) - { - const auto itr = issue_target_map_.find(jobj["issue"]); - if (itr == issue_target_map_.end()) - { - throw uArchInfoUnknownIssueTarget(jobj["mnemonic"], jobj["issue"]); - } - issue_target_ = itr->second; - } - - // Target execution unit (from UnitSet) -- bit mask allows multiple targets - if (jobj.find("unit") != jobj.end()) - { - mavis::UnitNameListType ulist = jobj["unit"].get(); - for (const auto & u : ulist) - { - const auto itr = umap_.find(u); - if (itr == umap_.end()) - { - throw uArchInfoUnknownUnit(jobj["mnemonic"], u); - } - units_ |= static_cast(itr->second); - } + // Target execution unit (from UnitSet) -- bit mask allows multiple targets + if (jobj.find("unit") != jobj.end()) { + mavis::UnitNameListType ulist = + jobj["unit"].get(); + for (const auto &u : ulist) { + const auto itr = umap_.find(u); + if (itr == umap_.end()) { + throw uArchInfoUnknownUnit(jobj["mnemonic"], u); } + units_ |= static_cast(itr->second); + } + } - // Instruction latency - if (jobj.find("latency") != jobj.end()) - { - latency_ = jobj["latency"]; - } + // Instruction latency + if (jobj.find("latency") != jobj.end()) { + latency_ = jobj["latency"]; + } - // Whether the instruction is piplined (non-blocking) - if (jobj.find("pipelined") != jobj.end()) - { - pipelined_ = jobj["pipelined"]; - } + // Whether the instruction is piplined (non-blocking) + if (jobj.find("pipelined") != jobj.end()) { + pipelined_ = jobj["pipelined"]; + } - // Whether the instruction serializes execution - if (jobj.find("serialize") != jobj.end()) - { - serialize_ = jobj["serialize"]; - } + // Whether the instruction serializes execution + if (jobj.find("serialize") != jobj.end()) { + serialize_ = jobj["serialize"]; + } - // Whether the instruction starts a new ROB group - if (jobj.find("rob_group") != jobj.end()) - { - mavis::StringListType slist = jobj["rob_group"].get(); - for (const auto & str : slist) - { - if (str == "begin") - { - rob_grp_start_ = true; - } - else if (str == "end") - { - rob_grp_end_ = true; - } - else - { - throw uArchInfoROBGroupParseError(jobj["mnemonic"], str); - } - } + // Whether the instruction starts a new ROB group + if (jobj.find("rob_group") != jobj.end()) { + mavis::StringListType slist = + jobj["rob_group"].get(); + for (const auto &str : slist) { + if (str == "begin") { + rob_grp_start_ = true; + } else if (str == "end") { + rob_grp_end_ = true; + } else { + throw uArchInfoROBGroupParseError(jobj["mnemonic"], str); } - - std::cout << "uArchInfo: " << jobj["mnemonic"] << std::endl; + } } + + std::cout << "uArchInfo: " << jobj["mnemonic"] << std::endl; + } }; -inline std::ostream & operator<<(std::ostream & os, const uArchInfo & ui) -{ - ui.print(os); - return os; +inline std::ostream &operator<<(std::ostream &os, const uArchInfo &ui) { + ui.print(os); + return os; } diff --git a/fusion/test/CMakeLists.txt b/fusion/test/CMakeLists.txt index 6e6524f1..0800e667 100644 --- a/fusion/test/CMakeLists.txt +++ b/fusion/test/CMakeLists.txt @@ -15,30 +15,32 @@ endif() add_library(mavis STATIC IMPORTED) -set_target_properties(mavis PROPERTIES IMPORTED_LOCATION ${MAVIS_LIBRARY}) +set_target_properties(mavis PROPERTIES IMPORTED_LOCATION + ${MAVIS_LIBRARY}) add_executable(fusiontest TestBench.cpp TestData.cpp TestFieldExtractor.cpp + FslTests.cpp main.cpp Options.cpp ) -target_compile_options(fusiontest PRIVATE -Wall -Wextra - -Wno-unused-parameter # mavis - ) +target_link_libraries(fusiontest PRIVATE FusionLib FusionDSL mavis) + find_package(Boost REQUIRED COMPONENTS program_options) if(Boost_FOUND) target_include_directories(fusiontest PRIVATE ${Boost_INCLUDE_DIRS}) target_link_libraries(fusiontest - PRIVATE FusionLib mavis + PRIVATE FusionLib FusionDSL mavis PRIVATE ${Boost_LIBRARIES} pthread ) else() message(SEND_ERROR "Boost is a required package for the testbench") endif() set(JSON_BASE "${FUSION_TOP}/test/json") +set(FSL_BASE "${FUSION_TOP}/test/fsl") set(TB_OPTS --tb_verbose) @@ -47,9 +49,20 @@ set(ISA_FILES --isa_file ${JSON_BASE}/isa_rv64g.json --isa_file ${JSON_BASE}/isa_rv64c.json) +set(FSL_FILES + --fsl_file "${FSL_BASE}/test1.fsl" + --fsl_file "${FSL_BASE}/test2.fsl") + +set(FSL_SYNTAX_FILES + --fsl_syntax_file "${FSL_BASE}/syntax1.fsl" + --fsl_syntax_file "${FSL_BASE}/syntax2.fsl") + list(APPEND OPTS ${ISA_FILES} + ${FSL_FILES} + ${FSL_SYNTAX_FILES} ${TB_OPTS}) add_custom_target(regress COMMAND fusiontest ${OPTS} + DEPENDS fusiontest copy_symtab_expect ) diff --git a/fusion/test/FslTests.cpp b/fusion/test/FslTests.cpp new file mode 100644 index 00000000..393bda94 --- /dev/null +++ b/fusion/test/FslTests.cpp @@ -0,0 +1,103 @@ +// HEADER PLACEHOLDER +// contact Jeff Nye, jeffnye-gh, Condor Computing Corp. +// +#include "Fusion.hpp" +#include "FslParser.hpp" +#include "Msg.hpp" +#include "Options.hpp" +#include "TestBench.hpp" + +#include +using namespace std; +extern FslParser* FP; +extern uint32_t lineNo; + +// -------------------------------------------------------------------- +// More tests will be written +// -------------------------------------------------------------------- +bool TestBench::fslTests(bool) +{ + if (!fslSyntaxTest()) + return false; + + return true; +} + +// -------------------------------------------------------------------- +// -------------------------------------------------------------------- +bool TestBench::checkSyntax(vector & files, bool) +{ + bool ok = true; + for (auto fn : files) + { + + if (verbose) + { + std::filesystem::path fp(fn); + string fandext = + fp.filename().string() + fp.extension().string(); + msg->imsg("parsing " + fandext); + } + + FP->warmReset(); + + if (!(FP->parse(fn))) + { + // yyerror() reports the error message + ok = false; + } + } + + return ok; +} + +// -------------------------------------------------------------------- +// -------------------------------------------------------------------- +bool TestBench::fslSyntaxTest(bool) +{ + if (verbose) + msg->imsg("fslSyntaxTest BEGIN"); + + // There should be at least one file for this test + if (opts->fsl_syntax_files.size() == 0) + { + msg->emsg("No FSL syntax test files specified"); + return false; + } + + bool ok = true; + + FP->coldReset(); + + // Check the files specifically that hold syntax corner cases + if (!checkSyntax(opts->fsl_syntax_files)) + ok = false; + + //See the cmake command that copies expected to the bin dir. + string actualFN = "symtab_actual.txt"; + string expectFN = "symtab_expect.txt"; + + //Write the symtab to a file, (true) use the file name.ext + //only option + ofstream actual(actualFN.c_str()); + FP->symtab.info(actual, true); + actual.close(); + + // (true) emit differences + if(!compareFiles(actualFN,expectFN)) { + ok = false; + msg->emsg("Symbol table does not match expect"); + msg->emsg(" Actual data : "+actualFN); + msg->emsg(" Expect data : "+expectFN); + } + + // Check all the other files, looking for things to add to + // corner cases tests + if (!checkSyntax(opts->fsl_files)) + ok = false; + + if (verbose) + msg->imsg("fslSyntaxTest END"); + + return ok; +} diff --git a/fusion/test/Msg.hpp b/fusion/test/Msg.hpp index 53533b55..8d8d1724 100644 --- a/fusion/test/Msg.hpp +++ b/fusion/test/Msg.hpp @@ -34,7 +34,10 @@ struct Msg void setWho(std::string _w) { w = _w + ": "; } //! \brief shared message method - void mmsg(std::string p, std::string m) const { std::cout << p << w << m << std::endl; } + void mmsg(std::string p, std::string m) const + { + std::cout << p << w << m << std::endl; + } //! \brief debug messages void dmsg(std::string m = "", int v = 4) const @@ -64,13 +67,44 @@ struct Msg mmsg("-W: ", m); } + //! \brief warining messages + void mmsg(std::ostream & o, std::string p, std::string m) const + { + o << p << w << m << std::endl; + } + + // ---------------------------------------------------------------- + //! \brief dmsg should be v level 4 + void dmsg(std::ostream & o, std::string m = "", int v = 4) const + { + mmsg(o, "-D: ", m); + } + + //! \brief ... + void emsg(std::ostream & o, std::string m = "", int v = 1) const + { + mmsg(o, "-E: ", m); + } + + //! \brief ... + void imsg(std::ostream & o, std::string m = "", int v = 3) const + { + mmsg(o, "-I: ", m); + } + + //! \brief ... + void wmsg(std::ostream & o, std::string m = "", int v = 2) const + { + mmsg(o, "-W: ", m); + } + //! \brief ... void msg(std::string m) const { std::cout << m << std::endl; } + // ---------------------------------------------------------------- //! \brief helper to show potentially empty strings std::string tq(std::string s) const { return "'" + s + "'"; } - // ---------------------------------------------------------------- //! \brief ... std::string w; /** diff --git a/fusion/test/Options.cpp b/fusion/test/Options.cpp index b52e7b77..1a521dac 100644 --- a/fusion/test/Options.cpp +++ b/fusion/test/Options.cpp @@ -13,15 +13,17 @@ void Options::setupOptions(int ac, char** av) { notify_error = false; - po::options_description visibleOpts("\nFusion API test\n " - "Usage:: test [--help|-h|--version|-v] { options }"); + po::options_description visibleOpts( + "\nFusion API test\n " + "Usage:: test [--help|-h|--version|-v] { options }"); po::options_description stdOpts("Standard options"); buildOptions(stdOpts); try { - po::store(po::command_line_parser(ac, av).options(stdOpts).run(), vm); + po::store(po::command_line_parser(ac, av).options(stdOpts).run(), + vm); // Without positional option po::parse_command_line can be used // po::store(po::parse_command_line(ac, av, allOpts), vm); @@ -60,15 +62,22 @@ void Options::buildOptions(po::options_description & stdOpts) ("isa_file", po::value>(&isa_files), "Multiple --isa_file accepted") + ("fsl_file", po::value>(&fsl_files), + "Multiple --fsl_file accepted") + + ("fsl_syntax_file", po::value>(&fsl_syntax_files), + "Syntax stress test files. Multiple " + "--fsl_syntax_file accepted") + ("tb_verbose", po::bool_switch(&tb_verbose) ->default_value(false), "Test bench message control"); } - // clang-format on // -------------------------------------------------------------------- // Check sanity on the options, handle --help, --version // -------------------------------------------------------------------- -bool Options::checkOptions(po::variables_map & vm, po::options_description & stdOpts, +bool Options::checkOptions(po::variables_map & vm, + po::options_description & stdOpts, bool firstPass) { if (firstPass) diff --git a/fusion/test/Options.hpp b/fusion/test/Options.hpp index 697749cf..37ec657a 100644 --- a/fusion/test/Options.hpp +++ b/fusion/test/Options.hpp @@ -29,13 +29,16 @@ struct Options void buildOptions(po::options_description &); //! \brief ... - bool checkOptions(po::variables_map &, po::options_description &, bool); + bool checkOptions(po::variables_map &, po::options_description &,bool); //! \brief ... void setupOptions(int, char**); //! \brief ... - void usage(po::options_description & o) const { std::cout << o << std::endl; } + void usage(po::options_description & o) const + { + std::cout << o << std::endl; + } //! \brief ... void version() const; @@ -47,6 +50,10 @@ struct Options std::string output_file{""}; //! \brief ... std::vector isa_files; + //! \brief ... + std::vector fsl_files; + //! \brief files with contorted style + std::vector fsl_syntax_files; //! \brief enable extra messages from the tb bool tb_verbose{false}; // ---------------------------------------------------------------- diff --git a/fusion/test/TestBench.cpp b/fusion/test/TestBench.cpp index c5ed2ce4..86b8f36e 100644 --- a/fusion/test/TestBench.cpp +++ b/fusion/test/TestBench.cpp @@ -58,6 +58,10 @@ bool TestBench::run() if (!fieldExtractorTests(true)) return false; + // domain language tests + if (!fslTests(true)) + return false; + return true; } @@ -68,7 +72,8 @@ bool TestBench::fusionContextTest(bool debug) if (verbose) msg->imsg("fusionContextTest BEGIN"); - using FusionGroupType = fusion::FusionGroup; + using FusionGroupType = + fusion::FusionGroup; using FusionGroupListType = std::vector; fusion::FusionContext context_; @@ -93,6 +98,8 @@ bool TestBench::fusionContextTest(bool debug) return false; } + // Future: fusion::RadixTree *rtrie = context_.getTree(); + if (verbose) msg->imsg("fusionContextTest END"); return true; @@ -106,29 +113,63 @@ bool TestBench::fusionSearchTest(bool debug) msg->imsg("fusionSearchTest BEGIN"); bool ok = true; - using FusionGroupType = fusion::FusionGroup; - using FusionGroupCfgType = fusion::FusionGroupCfg; - using FusionType = fusion::Fusion; + using FusionGroupType = + fusion::FusionGroup; + using FusionGroupCfgType = + fusion::FusionGroupCfg; + using FusionType = + fusion::Fusion; - FusionType::FusionGroupCfgListType testCases = { + //This is the struct with transformFunc directly assigned + FusionType::FusionGroupCfgListType testCasesFunc = { FusionGroupCfgType{.name = {"UF1"}, .uids = uf1, - .transformFunc = &TestBench::cbProxy::uf1_func}, + .transformFunc = + &TestBench::cbProxy::uf1_func}, FusionGroupCfgType{.name = {"UF1_1"}, .uids = uf1_1, - .transformFunc = &TestBench::cbProxy::uf1_1_func}, + .transformFunc = + &TestBench::cbProxy::uf1_1_func}, FusionGroupCfgType{.name = {"UF1_2"}, .uids = uf1_2, - .transformFunc = &TestBench::cbProxy::uf1_2_func}, + .transformFunc = + &TestBench::cbProxy::uf1_2_func}, FusionGroupCfgType{.name = {"UF1_3"}, .uids = uf1_3, - .transformFunc = &TestBench::cbProxy::uf1_3_func}, + .transformFunc = + &TestBench::cbProxy::uf1_3_func}, FusionGroupCfgType{.name = {"UF2"}, .uids = uf2, - .transformFunc = &TestBench::cbProxy::uf2_func}, + .transformFunc = + &TestBench::cbProxy::uf2_func}, FusionGroupCfgType{.name = {"UF3"}, .uids = uf3, - .transformFunc = &TestBench::cbProxy::uf3_func}}; + .transformFunc = + &TestBench::cbProxy::uf3_func}}; + +//FIXME: A test for this needs to be created +// +// //This is the struct with transformFunc to be set through +// //a function map +// FusionType::FusionGroupCfgListType testCasesName = { +// FusionGroupCfgType{.name = {"UF1"}, +// .uids = uf1, +// .transformName = "cbProxy::uf1_func"}, +// FusionGroupCfgType{.name = {"UF1_1"}, +// .uids = uf1_1, +// .transformName = "cbProxy::uf1_1_func"}, +// FusionGroupCfgType{.name = {"UF1_2"}, +// .uids = uf1_2, +// .transformName = "cbProxy::uf1_2_func"}, +// FusionGroupCfgType{.name = {"UF1_3"}, +// .uids = uf1_3, +// .transformName = "cbProxy::uf1_3_func"}, +// FusionGroupCfgType{.name = {"UF2"}, +// .uids = uf2, +// .transformName = "cbProxy::uf2_func"}, +// FusionGroupCfgType{.name = {"UF3"}, +// .uids = uf3, +// .transformName = "cbProxy::uf3_func"}}; // Future add specific tests for hash creation // std::unordered_map expHashes; @@ -142,25 +183,26 @@ bool TestBench::fusionSearchTest(bool debug) size_t outSize = out.size(); size_t inSize = in.size(); - FusionType f(testCases); + FusionType f(testCasesFunc); f.fusionOperator(in, out); - // The default operator appends in to out and clears in + // The default operator changes no machine state if (in.size() != 0) { - msg->emsg("fusionOperator failed to clean input vector"); + msg->emsg("fusionOperator modified the input vector"); ok = false; } if (out.size() != (outSize + inSize)) { - msg->emsg("fusionOperator failed to properly append to output vector"); + msg->emsg( + "fusionOperator failed to properly modify the output vector"); ok = false; } // Test the custom operator as lambda - auto customLambda = - [](FusionType & inst, fusion::InstPtrListType & in, fusion::InstPtrListType & out) + auto customLambda = [](FusionType & inst, fusion::InstPtrListType & in, + fusion::InstPtrListType & out) { out = in; // in is not cleared }; @@ -199,8 +241,9 @@ bool TestBench::fusionSearchTest(bool debug) // -------------------------------------------------------------------- // -------------------------------------------------------------------- -void TestBench::generateExpectHashes(unordered_map & exp, - const FusionType::FusionGroupCfgListType & in) +void TestBench::generateExpectHashes( + unordered_map & exp, + const FusionType::FusionGroupCfgListType & in) { for (size_t i = 0; i < in.size(); ++i) { @@ -218,14 +261,24 @@ bool TestBench::fusionCtorCompileTest(bool debug) msg->imsg("fusionCtorCompileTest BEGIN"); bool ok = true; - using FusionGroupType = fusion::FusionGroup; - using FusionType = fusion::Fusion; + using FusionGroupType = + fusion::FusionGroup; + using FusionType = + fusion::Fusion; + + // compile checks + //Removed FusionType f1; + //Removed FusionType f2{}; + //Removed FusionType f3 = + // fusion::Fusion(); const FusionType::FusionGroupListType fusionGroup_list = {}; const FusionType::FusionGroupCfgListType fusionGroupCfg_list = {}; + const fusion::FileNameListType txt_file_list = {}; FusionType f4(fusionGroup_list); FusionType f5(fusionGroupCfg_list); + FusionType f6(txt_file_list); if (verbose) msg->imsg("fusionCtorCompileTest END"); @@ -239,7 +292,7 @@ bool TestBench::basicMavisTest(bool debug) if (verbose) msg->imsg("basicMavisTest BEGIN"); InstUidListType goldenUid = uf1; // { 0xb, 0xd, 0x1c, 0xf, 0x13 }; - OpcodeListType goldenOpc = of1; // { 0x76e9,0x685,0x8d35,0x1542,0x9141 }; + OpcodeListType goldenOpc = of1; // { 0x76e9,0x685,0x8d35,0x1542,0x9141 }; Instruction::PtrType inst = nullptr; @@ -263,18 +316,26 @@ bool TestBench::basicMavisTest(bool debug) } InstUidListType uids; - for (const auto & inst : instrs) + for (const auto inst : instrs) { uids.emplace_back(inst->getUID()); } - if (instrs.size() != goldenUid.size() || instrs.size() != goldenOpc.size() + if (instrs.size() != goldenUid.size() + || instrs.size() != goldenOpc.size() || instrs.size() != uids.size()) { msg->emsg("basicMavisTest size mismatch in inst vector"); return false; } + // FIXME: There is an unexplained difference in UID creation + // if (goldenUid != uids) + // { + // msg->emsg("basicMavisTest inst to uid conversion + // failed"); return false; + // } + if (debug) info(goldenUid, uids, instrs); @@ -307,7 +368,8 @@ bool TestBench::fusionGroupAltCtorTest() }; // Alternative machineinfo and extractor - using AltFusionGroupType = fusion::FusionGroup; + using AltFusionGroupType = + fusion::FusionGroup; InstUidListType alt_uid = {}; @@ -316,7 +378,8 @@ bool TestBench::fusionGroupAltCtorTest() struct proxy { - static bool alt_func(AltFusionGroupType &, InstPtrListType &, InstPtrListType &) + static bool alt_func(AltFusionGroupType &, InstPtrListType &, + InstPtrListType &) { return true; } @@ -353,7 +416,8 @@ bool TestBench::fusionGroupCfgCtorTest() { if (verbose) msg->imsg("fusionGroupCfgCtorTest BEGIN"); - using FusionGroupCfgType = fusion::FusionGroupCfg; + using FusionGroupCfgType = + fusion::FusionGroupCfg; // --------------------------------------------------------------- // Test that hash created from the F1CfgUid matches the hash from a @@ -371,10 +435,11 @@ bool TestBench::fusionGroupCfgCtorTest() bool ok = true; - using FusionGroupType = fusion::FusionGroup; + using FusionGroupType = + fusion::FusionGroup; FusionGroupType F1fromF1CfgUid(F1CfgUid); - F1fromF1CfgUid.info(); + //F1fromF1CfgUid.info(); if (referenceHash != F1fromF1CfgUid.hash()) { @@ -496,7 +561,8 @@ bool TestBench::radixTrieTest(bool) bool ok = true; std::mt19937 generator(std::random_device{}()); - std::uniform_int_distribution distribution(0, std::numeric_limits::max()); + std::uniform_int_distribution distribution( + 0, std::numeric_limits::max()); auto start = std::chrono::high_resolution_clock::now(); for (uint32_t i = 0; i < num_values; ++i) @@ -507,7 +573,8 @@ bool TestBench::radixTrieTest(bool) auto end = std::chrono::high_resolution_clock::now(); std::chrono::duration insertDuration = end - start; - std::cout << "Time taken for insertion: " << insertDuration.count() << " seconds" << std::endl; + std::cout << "Time taken for insertion: " << insertDuration.count() + << " seconds" << std::endl; start = std::chrono::high_resolution_clock::now(); for (uint32_t i = 0; i < num_values; ++i) @@ -518,14 +585,18 @@ bool TestBench::radixTrieTest(bool) end = std::chrono::high_resolution_clock::now(); std::chrono::duration searchDuration = end - start; - std::cout << "Time taken for searching: " << searchDuration.count() << " seconds" << std::endl; + std::cout << "Time taken for searching: " << searchDuration.count() + << " seconds" << std::endl; trie.insert(12345); trie.insert(67890); - std::cout << "Found '12345' " << (trie.search(12345) ? "Yes" : "No") << std::endl; - std::cout << "Found '67890' " << (trie.search(67890) ? "Yes" : "No") << std::endl; - std::cout << "Found '54321' " << (trie.search(54321) ? "Yes" : "No") << std::endl; + std::cout << "Found '12345' " << (trie.search(12345) ? "Yes" : "No") + << std::endl; + std::cout << "Found '67890' " << (trie.search(67890) ? "Yes" : "No") + << std::endl; + std::cout << "Found '54321' " << (trie.search(54321) ? "Yes" : "No") + << std::endl; if (trie.search(12345) && trie.search(67890) && !trie.search(54321)) { @@ -589,7 +660,8 @@ void TestBench::assign(InstPtrListType & in, OpcodeListType & opcodes, // - rgrp[2].RD == rgrp[3].RD == rgrp[4].RD // - rgrp[3].IMM == rgrp[4].IMM getField IMM not implemented // ------------------------------------------------------------------------ -bool TestBench::f1_constraints(FusionGroupType & g, InstPtrListType & in, InstPtrListType & out) +bool TestBench::f1_constraints(FusionGroupType & g, InstPtrListType & in, + InstPtrListType & out) { // This goup expects at least 5 instruction positions in the input if (in.size() < 5) @@ -608,8 +680,9 @@ bool TestBench::f1_constraints(FusionGroupType & g, InstPtrListType & in, InstPt // Operand field encodings comparison against constraints // The indexes are positions in the group, 0 = 1st instruction - if (g.fe().noteq(in, 0, 1, RD) || g.fe().noteq(in, 0, 2, RD, RS2) || g.fe().noteq(in, 2, 3, RD) - || g.fe().noteq(in, 2, 4, RD)) + if (g.fe().noteq(in, 0, 1, RD) || g.fe().noteq(in, 0, 2, RD, RS2) + || g.fe().noteq(in, 2, 3, RD) || g.fe().noteq(in, 2, 4, RD)) + // || g.fe().noteq(in,3,4,IMM)) FIXME: IMM not implemented yet { return false; } @@ -622,7 +695,8 @@ bool TestBench::f1_constraints(FusionGroupType & g, InstPtrListType & in, InstPt // -------------------------------------------------------------------- // -------------------------------------------------------------------- -fusion::HashType TestBench::jenkinsOneAtATime(const ::vector & v) +fusion::HashType +TestBench::jenkinsOneAtATime(const ::vector & v) { fusion::HashType hash = 0; @@ -641,7 +715,8 @@ fusion::HashType TestBench::jenkinsOneAtATime(const ::vector & } // -------------------------------------------------------------------- -void TestBench::info(InstUidListType & aUIDs, InstUidListType & bUIDs, InstPtrListType & instrs) +void TestBench::info(InstUidListType & aUIDs, InstUidListType & bUIDs, + InstPtrListType & instrs) { cout << "aUIDs "; for (auto uid : aUIDs) @@ -663,3 +738,48 @@ void TestBench::info(InstUidListType & aUIDs, InstUidListType & bUIDs, InstPtrLi cout << pad << inst << endl; } } +// -------------------------------------------------------------------- +bool TestBench::compareFiles(const string actual, + const string expect,bool showDiffs) +{ + std::ifstream act(actual); + std::ifstream exp(expect); + + // Check existence + if (!act.is_open() || !exp.is_open()) { + msg->emsg("Error opening files"); + if(!act.is_open()) { + msg->emsg("Could not open "+actual); + } + + if(!exp.is_open()) { + msg->emsg("Could not open "+expect); + } + + return false; + } + + std::string actLine, expLine; + int lineNo = 1; + + // Check lines + while (std::getline(act, actLine) && std::getline(exp, expLine)) { + if (actLine != expLine) { + msg->emsg("Difference found at line "+::to_string(lineNo)+":"); + if(showDiffs) { + msg->emsg("Actual: '"+actLine+"'"); + msg->emsg("Expect: '"+expLine+"'"); + } + return false; + } + lineNo++; + } + + // Check lengths + if (!std::getline(act, actLine) ^ !std::getline(exp, expLine)) { + msg->emsg("Files differ in length"); + return false; + } + + return true; +} diff --git a/fusion/test/TestBench.hpp b/fusion/test/TestBench.hpp index d9c801e3..5e352e05 100644 --- a/fusion/test/TestBench.hpp +++ b/fusion/test/TestBench.hpp @@ -3,7 +3,6 @@ // //! \file TestBench.hpp testbench interface and utils #pragma once -#include "Msg.hpp" #include "FieldExtractor.hpp" #include "Fusion.hpp" #include "FusionGroup.hpp" @@ -17,15 +16,18 @@ struct TestBench { //! \brief save typing - using FusionGroupType = fusion::FusionGroup; + using FusionGroupType = + fusion::FusionGroup; //! \brief save typing using FusionGroupListType = std::vector; //! \brief save typing - using FusionGroupCfgType = fusion::FusionGroupCfg; + using FusionGroupCfgType = + fusion::FusionGroupCfg; //! \brief save typing - using FusionType = fusion::Fusion; + using FusionType = + fusion::Fusion; //! \brief save typing using InstUidListType = fusion::InstUidListType; @@ -91,19 +93,40 @@ struct TestBench FieldExtractor::InstPtrType makeInst(MavisType &, uint32_t); //! \brief helper for testing field values - bool testFieldValue(uint32_t, std::string, uint32_t act, uint32_t exp); + bool testFieldValue(uint32_t, std::string, uint32_t act,uint32_t exp); + + // ------------------------------------------------------------- + // fusion domain language tests and support + // ------------------------------------------------------------- + //! \brief this calls the fsl sub-tests + bool fslTests(bool debug = false); + + //! \brief tests/(will test) syntax edge cases + bool fslSyntaxTest(bool debug = false); + + //! \brief support for fsl_syntax_test + bool checkSyntax(std::vector &, bool debug = false); + + //! \brief return true if files are identical + //! + //! whitespace is significant + bool compareFiles(const std::string,const std::string,bool=true); + // ------------------------------------------------------------- //! \brief transform funcs - static bool f1_constraints(FusionGroupType &, InstPtrListType &, InstPtrListType &); + static bool f1_constraints(FusionGroupType &, InstPtrListType &, + InstPtrListType &); //! \brief assign to InstPtrListType from opcodes - void assign(InstPtrListType &, std::vector &, const FileNameListType &); + void assign(InstPtrListType &, std::vector &, + const FileNameListType &); //! \brief info debug function void info(InstUidListType &, InstUidListType &, InstPtrListType &); //! \brief support golden reference hashes - void generateExpectHashes(std::unordered_map &, - const FusionType::FusionGroupCfgListType &); + void generateExpectHashes( + std::unordered_map &, + const FusionType::FusionGroupCfgListType &); //! \brief duplicate of hash function found in fusion group //! @@ -150,6 +173,8 @@ struct TestBench static InstUidListType uf5, uf5_1, uf5_2, uf5_3; static OpcodeListType of5, of5_1, of5_2, of5_3; //! }@ + +// static const std::string symbolTableExpectData; }; // --------------------------------------------------------------------- @@ -160,79 +185,90 @@ struct TestBench struct TestBench::cbProxy { //! \brief ... - static bool uf1_func(FusionGroupType &, InstPtrListType &, InstPtrListType &) + static bool uf1_func(FusionGroupType &, InstPtrListType &, + InstPtrListType &) { - msg->imsg("uf1_func called"); + cout << "HERE uf1_func called" << endl; return true; } //! \brief ... - static bool uf1_1_func(FusionGroupType &, InstPtrListType &, InstPtrListType &) + static bool uf1_1_func(FusionGroupType &, InstPtrListType &, + InstPtrListType &) { - msg->imsg("uf1_1_func called"); + cout << "HERE uf1_1_func called" << endl; return true; } //! \brief ... - static bool uf1_2_func(FusionGroupType &, InstPtrListType &, InstPtrListType &) + static bool uf1_2_func(FusionGroupType &, InstPtrListType &, + InstPtrListType &) { - msg->imsg("uf1_2_func called"); + cout << "HERE uf1_2_func called" << endl; return true; } //! \brief ... - static bool uf1_3_func(FusionGroupType &, InstPtrListType &, InstPtrListType &) + static bool uf1_3_func(FusionGroupType &, InstPtrListType &, + InstPtrListType &) { - msg->imsg("uf1_3_func called"); + cout << "HERE uf1_3_func called" << endl; return true; } //! \brief ... - static bool uf2_func(FusionGroupType &, InstPtrListType &, InstPtrListType &) + static bool uf2_func(FusionGroupType &, InstPtrListType &, + InstPtrListType &) { - msg->imsg("uf2_func called"); + cout << "HERE uf2_func called" << endl; return true; } //! \brief ... - static bool uf3_func(FusionGroupType &, InstPtrListType &, InstPtrListType &) + static bool uf3_func(FusionGroupType &, InstPtrListType &, + InstPtrListType &) { - msg->imsg("uf3_func called"); + cout << "HERE uf3_func called" << endl; return true; } //! \brief ... - static bool uf4_func(FusionGroupType &, InstPtrListType &, InstPtrListType &) + static bool uf4_func(FusionGroupType &, InstPtrListType &, + InstPtrListType &) { - msg->imsg("uf4_func called"); + cout << "HERE uf4_func called" << endl; return true; } //! \brief ... - static bool uf5_func(FusionGroupType &, InstPtrListType &, InstPtrListType &) + static bool uf5_func(FusionGroupType &, InstPtrListType &, + InstPtrListType &) { - msg->imsg("uf5_func called"); + cout << "HERE uf5_func called" << endl; return true; } //! \brief ... - static bool uf5_1_func(FusionGroupType &, InstPtrListType &, InstPtrListType &) + static bool uf5_1_func(FusionGroupType &, InstPtrListType &, + InstPtrListType &) { - msg->imsg("uf5_1_func called"); + cout << "HERE uf5_1_func called" << endl; return true; } //! \brief ... - static bool uf5_2_func(FusionGroupType &, InstPtrListType &, InstPtrListType &) + static bool uf5_2_func(FusionGroupType &, InstPtrListType &, + InstPtrListType &) { - msg->imsg("uf5_2_func called"); + cout << "HERE uf5_2_func called" << endl; return true; } //! \brief ... - static bool uf5_3_func(FusionGroupType &, InstPtrListType &, InstPtrListType &) + static bool uf5_3_func(FusionGroupType &, InstPtrListType &, + InstPtrListType &) { - msg->imsg("uf5_3_func called"); + cout << "HERE uf5_3_func called" << endl; return true; } }; diff --git a/fusion/test/TestData.cpp b/fusion/test/TestData.cpp index 951c8b8e..a49db3ef 100644 --- a/fusion/test/TestData.cpp +++ b/fusion/test/TestData.cpp @@ -123,3 +123,8 @@ TestBench::OpcodeListType TestBench::of5_3 = { 0xe014, // "c.fsw f13, 0(x8)", 0x34 0x86a2 // "c.mv x13, x8" 0x17 }; +// -------------------------------------------------------------------- +// -------------------------------------------------------------------- +// const vector TestBench::std_isa_files = { +// "../../../mavis/json/isa_rv64g.json", +// "../../../mavis/json/isa_rv64c.json"}; diff --git a/fusion/test/TestFieldExtractor.cpp b/fusion/test/TestFieldExtractor.cpp index 5dff01eb..aedaf76e 100644 --- a/fusion/test/TestFieldExtractor.cpp +++ b/fusion/test/TestFieldExtractor.cpp @@ -84,7 +84,8 @@ bool TestBench::fieldExtractorTests(bool debug) // -------------------------------------------------------------------- // -------------------------------------------------------------------- -FieldExtractor::InstPtrType TestBench::makeInst(MavisType & m, uint32_t opc) +FieldExtractor::InstPtrType TestBench::makeInst(MavisType & m, + uint32_t opc) { FieldExtractor::InstPtrType inst; @@ -97,8 +98,10 @@ FieldExtractor::InstPtrType TestBench::makeInst(MavisType & m, uint32_t opc) if (!inst) { std::ostringstream ss; - ss << "0x" << std::setw(8) << std::setfill('0') << std::hex << opc; - msg->emsg("Mavis could not create instruction from " + ss.str()); + ss << "0x" << std::setw(8) << std::setfill('0') << std::hex + << opc; + msg->emsg("Mavis could not create instruction from " + + ss.str()); return nullptr; } } @@ -108,11 +111,13 @@ FieldExtractor::InstPtrType TestBench::makeInst(MavisType & m, uint32_t opc) // -------------------------------------------------------------------- // -------------------------------------------------------------------- -bool TestBench::testFieldValue(uint32_t id, string name, uint32_t act, uint32_t exp) +bool TestBench::testFieldValue(uint32_t id, string name, uint32_t act, + uint32_t exp) { if (act != exp) { - msg->emsg("ID:" + ::to_string(id) + ":FIELD:" + name + ": value mismatch"); + msg->emsg("ID:" + ::to_string(id) + ":FIELD:" + name + + ": value mismatch"); return false; } diff --git a/fusion/test/main.cpp b/fusion/test/main.cpp index 6a3851d9..3aa83eeb 100644 --- a/fusion/test/main.cpp +++ b/fusion/test/main.cpp @@ -1,6 +1,7 @@ // HEADER PLACEHOLDER // contact Jeff Nye, jeffnye-gh, Condor Computing Corp. // +#include "FslParser.hpp" #include "TestBench.hpp" #include "Msg.hpp" #include "Options.hpp" @@ -9,6 +10,8 @@ #include using namespace std; +FslParser* FP; + Options* Options::instance = 0; std::shared_ptr opts(Options::getInstance()); @@ -18,6 +21,9 @@ std::unique_ptr msg(Msg::getInstance()); // ------------------------------------------------------------------------ int main(int ac, char** av) { + FslParser fp; + FP = &fp; + TestBench tb(ac, av); ofstream out("PASSFAIL"); if (!out.is_open()) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index be0e6c6e..82e365e1 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -33,3 +33,4 @@ add_subdirectory(core/rename) add_subdirectory(core/lsu) add_subdirectory(core/issue_queue) add_subdirectory(core/branch_pred) +add_subdirectory(fusion) diff --git a/test/fusion/CMakeLists.txt b/test/fusion/CMakeLists.txt new file mode 100644 index 00000000..c643c747 --- /dev/null +++ b/test/fusion/CMakeLists.txt @@ -0,0 +1,21 @@ +project(olympia_test) +sparta_regress (olympia) + +file(CREATE_LINK ${SIM_BASE}/reports + ${CMAKE_CURRENT_BINARY_DIR}/reports SYMBOLIC) + +file(CREATE_LINK ${SIM_BASE}/arches + ${CMAKE_CURRENT_BINARY_DIR}/arches SYMBOLIC) + +file(CREATE_LINK ${SIM_BASE}/mavis/json + ${CMAKE_CURRENT_BINARY_DIR}/mavis_isa_files SYMBOLIC) + +file(CREATE_LINK ${SIM_BASE}/traces + ${CMAKE_CURRENT_BINARY_DIR}/traces SYMBOLIC) + +sparta_named_test(fusion_test olympia -i 1M + -l top debug debug.log + --arch-search-dir arches + --arch fusion + --report-all fusion.rpt text + --workload traces/dhry_riscv.zstf)