From 4367dba9b713b5734aa6641770dd0b8b9d52bd90 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Tue, 11 Feb 2020 21:02:58 -0700 Subject: [PATCH] move mux graph and decoder builders to vpr8 integration; ready to link the rr_switch to circuit models --- openfpga/src/mux_lib/decoder_library.cpp | 109 ++ openfpga/src/mux_lib/decoder_library.h | 95 ++ openfpga/src/mux_lib/decoder_library_fwd.h | 23 + openfpga/src/mux_lib/mux_graph.cpp | 1254 ++++++++++++++++++ openfpga/src/mux_lib/mux_graph.h | 213 +++ openfpga/src/mux_lib/mux_graph_fwd.h | 33 + openfpga/src/mux_lib/mux_library.cpp | 121 ++ openfpga/src/mux_lib/mux_library.h | 64 + openfpga/src/mux_lib/mux_library_fwd.h | 25 + openfpga/src/utils/decoder_library_utils.cpp | 60 + openfpga/src/utils/decoder_library_utils.h | 21 + openfpga/src/utils/mux_utils.cpp | 458 +++++++ openfpga/src/utils/mux_utils.h | 59 + 13 files changed, 2535 insertions(+) create mode 100644 openfpga/src/mux_lib/decoder_library.cpp create mode 100644 openfpga/src/mux_lib/decoder_library.h create mode 100644 openfpga/src/mux_lib/decoder_library_fwd.h create mode 100644 openfpga/src/mux_lib/mux_graph.cpp create mode 100644 openfpga/src/mux_lib/mux_graph.h create mode 100644 openfpga/src/mux_lib/mux_graph_fwd.h create mode 100644 openfpga/src/mux_lib/mux_library.cpp create mode 100644 openfpga/src/mux_lib/mux_library.h create mode 100644 openfpga/src/mux_lib/mux_library_fwd.h create mode 100644 openfpga/src/utils/decoder_library_utils.cpp create mode 100644 openfpga/src/utils/decoder_library_utils.h create mode 100644 openfpga/src/utils/mux_utils.cpp create mode 100644 openfpga/src/utils/mux_utils.h diff --git a/openfpga/src/mux_lib/decoder_library.cpp b/openfpga/src/mux_lib/decoder_library.cpp new file mode 100644 index 000000000..a7c4a8840 --- /dev/null +++ b/openfpga/src/mux_lib/decoder_library.cpp @@ -0,0 +1,109 @@ +/*************************************************************************************** + * This file includes memeber functions for data structure DecoderLibrary + **************************************************************************************/ +#include "vtr_assert.h" +#include "decoder_library.h" + +/* Begin namespace openfpga */ +namespace openfpga { + +/*************************************************************************************** + * Public Accessors: Aggregators + **************************************************************************************/ +DecoderLibrary::decoder_range DecoderLibrary::decoders() const { + return vtr::make_range(decoder_ids_.begin(), decoder_ids_.end()); +} + +/*************************************************************************************** + * Public Accessors: Data query + **************************************************************************************/ +/* Get the size of address input of a decoder */ +size_t DecoderLibrary::addr_size(const DecoderId& decoder) const { + VTR_ASSERT_SAFE(valid_decoder_id(decoder)); + return addr_sizes_[decoder]; +} + +/* Get the size of data output of a decoder */ +size_t DecoderLibrary::data_size(const DecoderId& decoder) const { + VTR_ASSERT_SAFE(valid_decoder_id(decoder)); + return data_sizes_[decoder]; +} + +/* Get the flag if a decoder includes an ENABLE signal */ +bool DecoderLibrary::use_enable(const DecoderId& decoder) const { + VTR_ASSERT_SAFE(valid_decoder_id(decoder)); + return use_enable_[decoder]; +} + +/* Get the flag if a decoder includes an DATA_IN signal */ +bool DecoderLibrary::use_data_in(const DecoderId& decoder) const { + VTR_ASSERT_SAFE(valid_decoder_id(decoder)); + return use_data_in_[decoder]; +} + +/* Get the flag if a decoder includes a data_inv port which is an inversion of the regular data output port */ +bool DecoderLibrary::use_data_inv_port(const DecoderId& decoder) const { + VTR_ASSERT_SAFE(valid_decoder_id(decoder)); + return use_data_inv_port_[decoder]; +} + +/* Find a decoder to the library, with the specification. + * If found, return the id of decoder. + * If not found, return an invalid id of decoder + * To avoid duplicated decoders, this function should be used before adding a decoder + * Example: + * DecoderId decoder_id == decoder_lib.find_decoder(); + * if (DecoderId::INVALID() == decoder_id) { + * // Add decoder + * } + */ +DecoderId DecoderLibrary::find_decoder(const size_t& addr_size, + const size_t& data_size, + const bool& use_enable, + const bool& use_data_in, + const bool& use_data_inv_port) const { + for (auto decoder : decoders()) { + if ( (addr_size == addr_sizes_[decoder]) + && (data_size == data_sizes_[decoder]) + && (use_enable == use_enable_[decoder]) + && (use_data_in == use_data_in_[decoder]) + && (use_data_inv_port == use_data_inv_port_[decoder]) ) { + return decoder; + } + } + + /* Not found, return an invalid id by default */ + return DecoderId::INVALID(); +} + +/*************************************************************************************** + * Public Validators + **************************************************************************************/ +/* Validate ids */ +bool DecoderLibrary::valid_decoder_id(const DecoderId& decoder) const { + return size_t(decoder) < decoder_ids_.size() && decoder_ids_[decoder] == decoder; +} + +/*************************************************************************************** + * Public Mutators : Basic Operations + **************************************************************************************/ +/* Add a decoder to the library */ +DecoderId DecoderLibrary::add_decoder(const size_t& addr_size, + const size_t& data_size, + const bool& use_enable, + const bool& use_data_in, + const bool& use_data_inv_port) { + DecoderId decoder = DecoderId(decoder_ids_.size()); + /* Push to the decoder list */ + decoder_ids_.push_back(decoder); + /* Resize the other related vectors */ + addr_sizes_.push_back(addr_size); + data_sizes_.push_back(data_size); + use_enable_.push_back(use_enable); + use_data_in_.push_back(use_data_in); + use_data_inv_port_.push_back(use_data_inv_port); + + return decoder; +} + +} /* End namespace openfpga*/ diff --git a/openfpga/src/mux_lib/decoder_library.h b/openfpga/src/mux_lib/decoder_library.h new file mode 100644 index 000000000..397308db5 --- /dev/null +++ b/openfpga/src/mux_lib/decoder_library.h @@ -0,0 +1,95 @@ +/*************************************************************************************** + * This file includes key data structures to describe decoders which are used + * in FPGA fabrics + * A decoder is a circuit to convert a binary input to one-hot codes + * The outputs are assumes to be one-hot codes (at most only one '1' exist) + * Therefore, the number of inputs is ceil(log(num_of_outputs)/log(2)) + * All the decoders are assumed to follow the port map : + * + * Inputs + * | | ... | + * v v v + * +-----------+ + * / \ + * / Decoder \ + * +-----------------+ + * | | | ... | | | + * v v v v v v + * Outputs + + ***************************************************************************************/ + +#ifndef DECODER_LIBRARY_H +#define DECODER_LIBRARY_H + +#include "vtr_vector.h" +#include "vtr_range.h" +#include "decoder_library_fwd.h" + +/* Begin namespace openfpga */ +namespace openfpga { + +class DecoderLibrary { + public: /* Types and ranges */ + typedef vtr::vector::const_iterator decoder_iterator; + + typedef vtr::Range decoder_range; + + public: /* Public accessors: Aggregates */ + /* Get all the decoders */ + decoder_range decoders() const; + + public: /* Public accessors: Data query */ + /* Get the size of address input of a decoder */ + size_t addr_size(const DecoderId& decoder) const; + /* Get the size of data output of a decoder */ + size_t data_size(const DecoderId& decoder) const; + /* Get the flag if a decoder includes an ENABLE signal */ + bool use_enable(const DecoderId& decoder) const; + /* Get the flag if a decoder includes an DATA_IN signal */ + bool use_data_in(const DecoderId& decoder) const; + /* Get the flag if a decoder includes a data_inv port which is an inversion of the regular data output port */ + bool use_data_inv_port(const DecoderId& decoder) const; + /* Find a decoder to the library, with the specification. + * If found, return the id of decoder. + * If not found, return an invalid id of decoder + * To avoid duplicated decoders, this function should be used before adding a decoder + * Example: + * DecoderId decoder_id == decoder_lib.find_decoder(); + * if (DecoderId::INVALID() == decoder_id) { + * // Add decoder + * } + */ + DecoderId find_decoder(const size_t& addr_size, + const size_t& data_size, + const bool& use_enable, + const bool& use_data_in, + const bool& use_data_inv_port) const; + + public: /* Public validators */ + /* valid ids */ + bool valid_decoder_id(const DecoderId& decoder) const; + + public: /* Private mutators : basic operations */ + /* Add a decoder to the library */ + DecoderId add_decoder(const size_t& addr_size, + const size_t& data_size, + const bool& use_enable, + const bool& use_data_in, + const bool& use_data_inv_port); + + private: /* Internal Data */ + vtr::vector decoder_ids_; + vtr::vector addr_sizes_; + vtr::vector data_sizes_; + vtr::vector use_enable_; + vtr::vector use_data_in_; + vtr::vector use_data_inv_port_; +}; + +} /* End namespace openfpga*/ + + + +#endif + diff --git a/openfpga/src/mux_lib/decoder_library_fwd.h b/openfpga/src/mux_lib/decoder_library_fwd.h new file mode 100644 index 000000000..e56a7ce82 --- /dev/null +++ b/openfpga/src/mux_lib/decoder_library_fwd.h @@ -0,0 +1,23 @@ +/************************************************** + * This file includes only declarations for + * the data structures to describe decoders + * Please refer to decoder_library.h for more details + *************************************************/ +#ifndef DECODER_LIBRARY_FWD_H +#define DECODER_LIBRARY_FWD_H + +#include "vtr_strong_id.h" + +/* Begin namespace openfpga */ +namespace openfpga { + +/* Strong Ids for MUXes */ +struct decoder_id_tag; + +typedef vtr::StrongId DecoderId; + +class DecoderLibrary; + +} /* End namespace openfpga*/ + +#endif diff --git a/openfpga/src/mux_lib/mux_graph.cpp b/openfpga/src/mux_lib/mux_graph.cpp new file mode 100644 index 000000000..5b0f81828 --- /dev/null +++ b/openfpga/src/mux_lib/mux_graph.cpp @@ -0,0 +1,1254 @@ +/************************************************** + * This file includes member functions for the + * data structures in mux_graph.h + *************************************************/ +#include +#include +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" + +#include "mux_utils.h" +#include "mux_graph.h" + +/* Begin namespace openfpga */ +namespace openfpga { + +/************************************************** + * Member functions for the class MuxGraph + *************************************************/ + +/************************************************** + * Public Constructors + *************************************************/ + +/* Create an object based on a Circuit Model which is MUX */ +MuxGraph::MuxGraph(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const size_t& mux_size) { + /* Build the graph for a given multiplexer model */ + build_mux_graph(circuit_lib, circuit_model, mux_size); +} + +/************************************************** + * Private Constructors + *************************************************/ +/* Create an empty graph */ +MuxGraph::MuxGraph() { + return; +} + +/************************************************** + * Public Accessors : Aggregates + *************************************************/ +//Accessors +MuxGraph::node_range MuxGraph::nodes() const { + return vtr::make_range(node_ids_.begin(), node_ids_.end()); +} + +/* Find the non-input nodes */ +std::vector MuxGraph::non_input_nodes() const { + /* Must be an valid graph */ + VTR_ASSERT_SAFE(valid_mux_graph()); + std::vector node_list; + + /* Build the node list, level by level */ + for (size_t level = 0; level < num_node_levels(); ++level) { + for (size_t node_type = 0; node_type < size_t(NUM_MUX_NODE_TYPES); ++node_type) { + /* Bypass any nodes which are not OUTPUT and INTERNAL */ + if (size_t(MUX_INPUT_NODE) == node_type) { + continue; + } + /* Reach here, this is either an OUTPUT or INTERNAL node */ + for (auto node : node_lookup_[level][node_type]) { + node_list.push_back(node); + } + } + } + + return node_list; +} + +MuxGraph::edge_range MuxGraph::edges() const { + return vtr::make_range(edge_ids_.begin(), edge_ids_.end()); +} + +MuxGraph::mem_range MuxGraph::memories() const { + return vtr::make_range(mem_ids_.begin(), mem_ids_.end()); +} + +std::vector MuxGraph::levels() const { + std::vector graph_levels; + for (size_t lvl = 0; lvl < num_levels(); ++lvl) { + graph_levels.push_back(lvl); + } + return graph_levels; +} + +std::vector MuxGraph::node_levels() const { + std::vector graph_levels; + for (size_t lvl = 0; lvl < num_node_levels(); ++lvl) { + graph_levels.push_back(lvl); + } + return graph_levels; +} + +/************************************************** + * Public Accessors: Data query + *************************************************/ + +/* Find the number of inputs in the MUX graph */ +size_t MuxGraph::num_inputs() const { + /* need to check if the graph is valid or not */ + VTR_ASSERT_SAFE(valid_mux_graph()); + /* Sum up the number of INPUT nodes in each level */ + size_t num_inputs = 0; + for (auto node_per_level : node_lookup_) { + num_inputs += node_per_level[MUX_INPUT_NODE].size(); + } + return num_inputs; +} + +/* Return the node ids of all the inputs of the multiplexer */ +std::vector MuxGraph::inputs() const { + std::vector input_nodes; + /* need to check if the graph is valid or not */ + VTR_ASSERT_SAFE(valid_mux_graph()); + /* Add the input nodes in each level */ + for (auto node_per_level : node_lookup_) { + input_nodes.insert(input_nodes.end(), node_per_level[MUX_INPUT_NODE].begin(), node_per_level[MUX_INPUT_NODE].end()); + } + return input_nodes; +} + +/* Find the number of outputs in the MUX graph */ +size_t MuxGraph::num_outputs() const { + /* need to check if the graph is valid or not */ + VTR_ASSERT_SAFE(valid_mux_graph()); + /* Sum up the number of INPUT nodes in each level */ + size_t num_outputs = 0; + for (auto node_per_level : node_lookup_) { + num_outputs += node_per_level[MUX_OUTPUT_NODE].size(); + } + return num_outputs; +} + +/* Return the node ids of all the outputs of the multiplexer */ +std::vector MuxGraph::outputs() const { + std::vector output_nodes; + /* need to check if the graph is valid or not */ + VTR_ASSERT_SAFE(valid_mux_graph()); + /* Add the output nodes in each level */ + for (auto node_per_level : node_lookup_) { + output_nodes.insert(output_nodes.end(), node_per_level[MUX_OUTPUT_NODE].begin(), node_per_level[MUX_OUTPUT_NODE].end()); + } + return output_nodes; +} + +/* Find the edge between two MUX nodes */ +std::vector MuxGraph::find_edges(const MuxNodeId& from_node, const MuxNodeId& to_node) const { + std::vector edges; + + VTR_ASSERT(valid_node_id(from_node)); + VTR_ASSERT(valid_node_id(to_node)); + + for (const auto& edge : node_out_edges_[from_node]) { + for (const auto& cand : edge_sink_nodes_[edge]) { + if (cand == to_node) { + /* This is the wanted edge, add to list */ + edges.push_back(edge); + } + } + } + + return edges; +} + +/* Find the number of levels in the MUX graph */ +size_t MuxGraph::num_levels() const { + /* need to check if the graph is valid or not */ + VTR_ASSERT_SAFE(valid_mux_graph()); + /* The num_levels by definition excludes the level for outputs, so a deduection is applied */ + return node_lookup_.size() - 1; +} + +/* Find the actual number of levels in the MUX graph */ +size_t MuxGraph::num_node_levels() const { + /* need to check if the graph is valid or not */ + VTR_ASSERT_SAFE(valid_mux_graph()); + return node_lookup_.size(); +} + +/* Find the number of configuration memories in the MUX graph */ +size_t MuxGraph::num_memory_bits() const { + /* need to check if the graph is valid or not */ + VTR_ASSERT_SAFE(valid_mux_graph()); + return mem_ids_.size(); +} + +/* Find the number of SRAMs at a level in the MUX graph */ +size_t MuxGraph::num_memory_bits_at_level(const size_t& level) const { + /* need to check if the graph is valid or not */ + VTR_ASSERT_SAFE(valid_level(level)); + VTR_ASSERT_SAFE(valid_mux_graph()); + return mem_lookup_[level].size(); +} + +/* Return memory id at level */ +std::vector MuxGraph::memories_at_level(const size_t& level) const { + /* need to check if the graph is valid or not */ + VTR_ASSERT_SAFE(valid_level(level)); + VTR_ASSERT_SAFE(valid_mux_graph()); + return mem_lookup_[level]; +} + +/* Find the number of nodes at a given level in the MUX graph */ +size_t MuxGraph::num_nodes_at_level(const size_t& level) const { + /* validate the level numbers */ + VTR_ASSERT_SAFE(valid_level(level)); + VTR_ASSERT_SAFE(valid_mux_graph()); + + size_t num_nodes = 0; + for (size_t node_type = 0; node_type < size_t(NUM_MUX_NODE_TYPES); ++node_type) { + num_nodes += node_lookup_[level][node_type].size(); + } + return num_nodes; +} + +/* Find the level of a node */ +size_t MuxGraph::node_level(const MuxNodeId& node) const { + /* validate the node */ + VTR_ASSERT(valid_node_id(node)); + return node_levels_[node]; +} + +/* Find the index of a node at its level */ +size_t MuxGraph::node_index_at_level(const MuxNodeId& node) const { + /* validate the node */ + VTR_ASSERT(valid_node_id(node)); + return node_ids_at_level_[node]; +} + +/* Find the input edges for a node */ +std::vector MuxGraph::node_in_edges(const MuxNodeId& node) const { + /* validate the node */ + VTR_ASSERT(valid_node_id(node)); + return node_in_edges_[node]; +} + +/* Find the input nodes for a edge */ +std::vector MuxGraph::edge_src_nodes(const MuxEdgeId& edge) const { + /* validate the edge */ + VTR_ASSERT(valid_edge_id(edge)); + return edge_src_nodes_[edge]; +} + +/* Find the mem that control the edge */ +MuxMemId MuxGraph::find_edge_mem(const MuxEdgeId& edge) const { + /* validate the edge */ + VTR_ASSERT(valid_edge_id(edge)); + return edge_mem_ids_[edge]; +} + +/* Identify if the edge is controlled by the inverted output of a mem */ +bool MuxGraph::is_edge_use_inv_mem(const MuxEdgeId& edge) const { + /* validate the edge */ + VTR_ASSERT(valid_edge_id(edge)); + return edge_inv_mem_[edge]; +} + +/* Find the sizes of each branch of a MUX */ +std::vector MuxGraph::branch_sizes() const { + std::vector branch; + /* Visit each internal nodes/output nodes and find the the number of incoming edges */ + for (auto node : node_ids_ ) { + /* Bypass input nodes */ + if ( (MUX_OUTPUT_NODE != node_types_[node]) + && (MUX_INTERNAL_NODE != node_types_[node]) ) { + continue; + } + + size_t branch_size = node_in_edges_[node].size(); + + /* make sure the branch size is valid */ + VTR_ASSERT_SAFE(valid_mux_implementation_num_inputs(branch_size)); + + /* Nodes with the same number of incoming edges, indicate the same size of branch circuit */ + std::vector::iterator it; + it = std::find(branch.begin(), branch.end(), branch_size); + /* if already exists a branch with the same size, skip updating the vector */ + if (it != branch.end()) { + continue; + } + branch.push_back(branch_size); + } + + /* Sort the branch by size */ + std::sort(branch.begin(), branch.end()); + + return branch; +} + +/* Find the sizes of each branch of a MUX at a given level */ +std::vector MuxGraph::branch_sizes(const size_t& level) const { + std::vector branch; + /* Visit each internal nodes/output nodes and find the the number of incoming edges */ + for (auto node : node_ids_ ) { + /* Bypass input nodes */ + if ( (MUX_OUTPUT_NODE != node_types_[node]) + && (MUX_INTERNAL_NODE != node_types_[node]) ) { + continue; + } + /* Bypass nodes that is not at the level */ + if ( level != node_levels_[node]) { + continue; + } + + size_t branch_size = node_in_edges_[node].size(); + + /* make sure the branch size is valid */ + VTR_ASSERT_SAFE(valid_mux_implementation_num_inputs(branch_size)); + + /* Nodes with the same number of incoming edges, indicate the same size of branch circuit */ + std::vector::iterator it; + it = std::find(branch.begin(), branch.end(), branch_size); + /* if already exists a branch with the same size, skip updating the vector */ + if (it != branch.end()) { + continue; + } + branch.push_back(branch_size); + } + + /* Sort the branch by size */ + std::sort(branch.begin(), branch.end()); + + return branch; +} + + +/* Build a subgraph from the given node + * The strategy is very simple, we just + * extract a 1-level graph from here + */ +MuxGraph MuxGraph::subgraph(const MuxNodeId& root_node) const { + /* Validate the node */ + VTR_ASSERT_SAFE(this->valid_node_id(root_node)); + + /* Generate an empty graph */ + MuxGraph mux_graph; + + /* A map to record node-to-node mapping from origin graph to subgraph */ + std::map node2node_map; + + /* A map to record edge-to-edge mapping from origin graph to subgraph */ + std::map edge2edge_map; + + /* Add output nodes to subgraph */ + MuxNodeId to_node_subgraph = mux_graph.add_node(MUX_OUTPUT_NODE); + mux_graph.node_levels_[to_node_subgraph] = 1; + mux_graph.node_ids_at_level_[to_node_subgraph] = 0; + mux_graph.node_output_ids_[to_node_subgraph] = MuxOutputId(0); + /* Update the node-to-node map */ + node2node_map[root_node] = to_node_subgraph; + + /* Add input nodes and edges to subgraph */ + size_t input_cnt = 0; + for (auto edge_origin : this->node_in_edges_[root_node]) { + VTR_ASSERT_SAFE(1 == edge_src_nodes_[edge_origin].size()); + /* Add nodes */ + MuxNodeId from_node_origin = this->edge_src_nodes_[edge_origin][0]; + MuxNodeId from_node_subgraph = mux_graph.add_node(MUX_INPUT_NODE); + /* Configure the nodes */ + mux_graph.node_levels_[from_node_subgraph] = 0; + mux_graph.node_ids_at_level_[from_node_subgraph] = input_cnt; + mux_graph.node_input_ids_[from_node_subgraph] = MuxInputId(input_cnt); + input_cnt++; + /* Update the node-to-node map */ + node2node_map[from_node_origin] = from_node_subgraph; + + /* Add edges */ + MuxEdgeId edge_subgraph = mux_graph.add_edge(node2node_map[from_node_origin], node2node_map[root_node]); + edge2edge_map[edge_origin] = edge_subgraph; + /* Configure edges */ + mux_graph.edge_models_[edge_subgraph] = this->edge_models_[edge_origin]; + mux_graph.edge_inv_mem_[edge_subgraph] = this->edge_inv_mem_[edge_origin]; + } + + /* A map to record mem-to-mem mapping from origin graph to subgraph */ + std::map mem2mem_map; + + /* Add memory bits and configure edges */ + for (auto edge_origin : this->node_in_edges_[root_node]) { + MuxMemId mem_origin = this->edge_mem_ids_[edge_origin]; + /* Try to find if the mem is already in the list */ + std::map::iterator it = mem2mem_map.find(mem_origin); + if (it != mem2mem_map.end()) { + /* Found, we skip mem addition. But make sure we have a valid one */ + VTR_ASSERT_SAFE(MuxMemId::INVALID() != mem2mem_map[mem_origin]); + /* configure the edge */ + mux_graph.edge_mem_ids_[edge2edge_map[edge_origin]] = mem2mem_map[mem_origin]; + continue; + } + /* Not found, we add a memory bit and record in the mem-to-mem map */ + MuxMemId mem_subgraph = mux_graph.add_mem(); + mux_graph.set_mem_level(mem_subgraph, 0); + mem2mem_map[mem_origin] = mem_subgraph; + /* configure the edge */ + mux_graph.edge_mem_ids_[edge2edge_map[edge_origin]] = mem_subgraph; + } + + /* Since the graph is finalized, it is time to build the fast look-up */ + mux_graph.build_node_lookup(); + mux_graph.build_mem_lookup(); + + return mux_graph; +} + +/* Generate MUX graphs for its branches + * Similar to the branch_sizes() method, + * we search all the internal nodes and + * find out what are the input sizes of + * the branches. + * Then we extract unique subgraphs and return + */ +std::vector MuxGraph::build_mux_branch_graphs() const { + std::map branch_done; /* A map showing the status of graph generation */ + + std::vector branch_graphs; + + /* Visit each internal nodes/output nodes and find the the number of incoming edges */ + for (auto node : node_ids_ ) { + /* Bypass input nodes */ + if ( (MUX_OUTPUT_NODE != node_types_[node]) + && (MUX_INTERNAL_NODE != node_types_[node]) ) { + continue; + } + + size_t branch_size = node_in_edges_[node].size(); + + /* make sure the branch size is valid */ + VTR_ASSERT_SAFE(valid_mux_implementation_num_inputs(branch_size)); + + /* check if the branch have been done in sub-graph extraction! */ + std::map::iterator it = branch_done.find(branch_size); + /* if it is done, we can skip */ + if (it != branch_done.end()) { + VTR_ASSERT(branch_done[branch_size]); + continue; + } + + /* Generate a subgraph and push back */ + branch_graphs.push_back(subgraph(node)); + + /* Mark it is done for this branch size */ + branch_done[branch_size] = true; + } + + return branch_graphs; +} + +/* Get the input id of a given node */ +MuxInputId MuxGraph::input_id(const MuxNodeId& node_id) const { + /* Validate node id */ + VTR_ASSERT(valid_node_id(node_id)); + /* Must be an input */ + VTR_ASSERT(MUX_INPUT_NODE == node_types_[node_id]); + return node_input_ids_[node_id]; +} + +/* Identify if the node is an input of the MUX */ +bool MuxGraph::is_node_input(const MuxNodeId& node_id) const { + /* Validate node id */ + VTR_ASSERT(true == valid_node_id(node_id)); + return (MUX_INPUT_NODE == node_types_[node_id]); +} + +/* Get the output id of a given node */ +MuxOutputId MuxGraph::output_id(const MuxNodeId& node_id) const { + /* Validate node id */ + VTR_ASSERT(valid_node_id(node_id)); + /* Must be an output */ + VTR_ASSERT(MUX_OUTPUT_NODE == node_types_[node_id]); + return node_output_ids_[node_id]; +} + +/* Identify if the node is an output of the MUX */ +bool MuxGraph::is_node_output(const MuxNodeId& node_id) const { + /* Validate node id */ + VTR_ASSERT(true == valid_node_id(node_id)); + return (MUX_OUTPUT_NODE == node_types_[node_id]); +} + +/* Get the node id of a given input */ +MuxNodeId MuxGraph::node_id(const MuxInputId& input_id) const { + /* Use the node_lookup to accelerate the search */ + for (const auto& lvl : node_lookup_) { + for (const auto& cand_node : lvl[MUX_INPUT_NODE]) { + if (input_id == node_input_ids_[cand_node]) { + return cand_node; + } + } + } + + return MuxNodeId::INVALID(); +} + +/* Get the node id of a given output */ +MuxNodeId MuxGraph::node_id(const MuxOutputId& output_id) const { + /* Use the node_lookup to accelerate the search */ + for (const auto& lvl : node_lookup_) { + for (const auto& cand_node : lvl[MUX_OUTPUT_NODE]) { + if (output_id == node_output_ids_[cand_node]) { + return cand_node; + } + } + } + + return MuxNodeId::INVALID(); +} + + +/* Get the node id w.r.t. the node level and node_index at the level + * Return an invalid value if not found + */ +MuxNodeId MuxGraph::node_id(const size_t& node_level, const size_t& node_index_at_level) const { + /* Ensure we have a valid node_look-up */ + VTR_ASSERT_SAFE(valid_node_lookup()); + + MuxNodeId ret_node = MuxNodeId::INVALID(); + + /* Search in the fast look up */ + if (node_level >= node_lookup_.size()) { + return ret_node; + } + + size_t node_cnt = 0; + /* Node level is valid, search in the node list */ + for (const auto& nodes_by_type : node_lookup_[node_level]) { + /* Search the node_index_at_level of each node */ + for (const auto& node : nodes_by_type) { + if (node_index_at_level != node_ids_at_level_[node]) { + continue; + } + /* Find the node, assign value and update the counter */ + ret_node = node; + node_cnt++; + } + } + + /* We should either find a node or nothing */ + VTR_ASSERT((0 == node_cnt) || (1 == node_cnt)); + + return ret_node; +} + +/* Decode memory bits based on an input id and an output id */ +vtr::vector MuxGraph::decode_memory_bits(const MuxInputId& input_id, + const MuxOutputId& output_id) const { + /* initialize the memory bits: TODO: support default value */ + vtr::vector mem_bits(mem_ids_.size(), false); + + /* valid the input and output */ + VTR_ASSERT_SAFE(valid_input_id(input_id)); + VTR_ASSERT_SAFE(valid_output_id(output_id)); + + /* Mark all the nodes as not visited */ + vtr::vector visited(nodes().size(), false); + + /* Create a queue for Breadth-First Search */ + std::list queue; + + /* Mark the input node as visited and enqueue it */ + visited[node_id(input_id)] = true; + queue.push_back(node_id(input_id)); + + /* Create a flag to indicate if the route is success or not */ + bool route_success = false; + + while(!queue.empty()) { + /* Dequeue a mux node from queue, + * we will walk through all the fan-in of this node in this loop + */ + MuxNodeId node_to_expand = queue.front(); + queue.pop_front(); + /* Get all fan-in nodes of the dequeued node + * If the node has not been visited, + * then mark it visited and enqueue it + */ + VTR_ASSERT_SAFE (1 == node_out_edges_[node_to_expand].size()); + MuxEdgeId edge = node_out_edges_[node_to_expand][0]; + + /* Configure the mem bits: + * if inv_mem is enabled, it means 0 to enable this edge + * otherwise, it is 1 to enable this edge + */ + MuxMemId mem = edge_mem_ids_[edge]; + VTR_ASSERT_SAFE (valid_mem_id(mem)); + if (true == edge_inv_mem_[edge]) { + mem_bits[mem] = false; + } else { + mem_bits[mem] = true; + } + + /* each edge must have 1 fan-out */ + VTR_ASSERT_SAFE (1 == edge_sink_nodes_[edge].size()); + + /* Get the fan-out node */ + MuxNodeId next_node = edge_sink_nodes_[edge][0]; + + /* If next node is the output node we want, we can finish here */ + if (next_node == node_id(output_id)) { + route_success = true; + break; + } + + /* Add next node to the queue if not visited yet */ + if (false == visited[next_node]) { + visited[next_node] = true; + queue.push_back(next_node); + } + } + + /* Routing must be success! */ + VTR_ASSERT(true == route_success); + + return mem_bits; +} + +/* Find the input node that the memory bits will route an output node to + * This function backward propagate from the output node to an input node + * assuming the memory bits are applied + */ +MuxInputId MuxGraph::find_input_node_driven_by_output_node(const std::map& memory_bits, + const MuxOutputId& output_id) const { + /* Ensure that the memory bits fit the size of memory bits in this MUX */ + VTR_ASSERT(memory_bits.size() == mem_ids_.size()); + + /* valid the output */ + VTR_ASSERT_SAFE(valid_output_id(output_id)); + + /* Start from the output node */ + /* Mark all the nodes as not visited */ + vtr::vector visited(nodes().size(), false); + + /* Create a queue for Breadth-First Search */ + std::list queue; + + /* Mark the output node as visited and enqueue it */ + visited[node_id(output_id)] = true; + queue.push_back(node_id(output_id)); + + /* Record the destination input id */ + MuxInputId des_input_id = MuxInputId::INVALID(); + + while(!queue.empty()) { + /* Dequeue a mux node from queue, + * we will walk through all the fan-in of this node in this loop + */ + MuxNodeId node_to_expand = queue.front(); + queue.pop_front(); + /* Get all fan-in nodes of the dequeued node + * If the node has not been visited, + * then mark it visited and enqueue it + */ + MuxEdgeId next_edge = MuxEdgeId::INVALID(); + for (const MuxEdgeId& edge : node_in_edges_[node_to_expand]) { + /* Configure the mem bits and find the edge that will propagate the signal + * if inv_mem is enabled, it means false to enable this edge + * otherwise, it is true to enable this edge + */ + MuxMemId mem = edge_mem_ids_[edge]; + VTR_ASSERT_SAFE (valid_mem_id(mem)); + if (edge_inv_mem_[edge] == !memory_bits.at(mem)) { + next_edge = edge; + break; + } + } + /* We must have a valid next edge */ + VTR_ASSERT(MuxEdgeId::INVALID() != next_edge); + + /* each edge must have 1 fan-out */ + VTR_ASSERT_SAFE (1 == edge_src_nodes_[next_edge].size()); + + /* Get the fan-in node */ + MuxNodeId next_node = edge_src_nodes_[next_edge][0]; + + /* If next node is an input node, we can finish here */ + if (true == is_node_input(next_node)) { + des_input_id = input_id(next_node); + break; + } + + /* Add next node to the queue if not visited yet */ + if (false == visited[next_node]) { + visited[next_node] = true; + queue.push_back(next_node); + } + } + + /* Routing must be success! */ + VTR_ASSERT(MuxInputId::INVALID() != des_input_id); + + return des_input_id; +} + +/************************************************** + * Private mutators: basic operations + *************************************************/ +/* Add a unconfigured node to the MuxGraph */ +MuxNodeId MuxGraph::add_node(const enum e_mux_graph_node_type& node_type) { + MuxNodeId node = MuxNodeId(node_ids_.size()); + /* Push to the node list */ + node_ids_.push_back(node); + /* Resize the other node-related vectors */ + node_types_.push_back(node_type); + node_input_ids_.push_back(MuxInputId::INVALID()); + node_output_ids_.push_back(MuxOutputId::INVALID()); + node_levels_.push_back(-1); + node_ids_at_level_.push_back(-1); + node_in_edges_.emplace_back(); + node_out_edges_.emplace_back(); + + return node; +} + +/* Add a edge connecting two nodes */ +MuxEdgeId MuxGraph::add_edge(const MuxNodeId& from_node, const MuxNodeId& to_node) { + MuxEdgeId edge = MuxEdgeId(edge_ids_.size()); + /* Push to the node list */ + edge_ids_.push_back(edge); + /* Resize the other node-related vectors */ + edge_models_.push_back(CircuitModelId::INVALID()); + edge_mem_ids_.push_back(MuxMemId::INVALID()); + edge_inv_mem_.push_back(false); + + /* update the edge-node connections */ + VTR_ASSERT(valid_node_id(from_node)); + edge_src_nodes_.emplace_back(); + edge_src_nodes_[edge].push_back(from_node); + node_out_edges_[from_node].push_back(edge); + + VTR_ASSERT(valid_node_id(to_node)); + edge_sink_nodes_.emplace_back(); + edge_sink_nodes_[edge].push_back(to_node); + node_in_edges_[to_node].push_back(edge); + + return edge; +} + +/* Add a memory bit to the MuxGraph */ +MuxMemId MuxGraph::add_mem() { + MuxMemId mem = MuxMemId(mem_ids_.size()); + /* Push to the node list */ + mem_ids_.push_back(mem); + mem_levels_.push_back(size_t(-1)); + /* Resize the other node-related vectors */ + + return mem; +} + +/* Configure the level of a memory */ +void MuxGraph::set_mem_level(const MuxMemId& mem, const size_t& level) { + /* Make sure we have valid edge and mem */ + VTR_ASSERT( valid_mem_id(mem) ); + + mem_levels_[mem] = level; +} + +/* Link an edge to a memory bit */ +void MuxGraph::set_edge_mem_id(const MuxEdgeId& edge, const MuxMemId& mem) { + /* Make sure we have valid edge and mem */ + VTR_ASSERT( valid_edge_id(edge) && valid_mem_id(mem) ); + + edge_mem_ids_[edge] = mem; +} + +/************************************************** + * Private mutators: graph builders + *************************************************/ + +/* Build a graph for a multi-level multiplexer implementation + * support both generic multi-level and tree-like multiplexers + * + * a N:1 multi-level MUX + * ---------------------- + * + * input_node --->+ + * | + * input_node --->| + * |--->+ + * ... | | + * | | + * input_node --->+ |---> ... + * | + * ... --->+ --->+ + * | + * ... ... |---> output_node + * | + * ... --->+ --->+ + * | + * input_node --->+ |---> ... + * | | + * input_node --->| | + * |--->+ + * ... | + * | + * input_node --->+ + * + * tree-like multiplexer graph will look like: + * -------------------------------------------- + * + * input_node --->+ + * |--->+ + * input_node --->+ |---> ... + * | + * --->+ --->+ + * ... ... ... |----> output_node + * ... --->+ --->+ + * |---> ... + * input_node --->+ | + * |--->+ + * input_node --->+ + * + */ +void MuxGraph::build_multilevel_mux_graph(const size_t& mux_size, + const size_t& num_levels, const size_t& num_inputs_per_branch, + const CircuitModelId& pgl_model) { + /* Make sure mux_size for each branch is valid */ + VTR_ASSERT(valid_mux_implementation_num_inputs(num_inputs_per_branch)); + + /* In regular cases, there is 1 mem bit for each input of a branch */ + size_t num_mems_per_level = num_inputs_per_branch; + /* For 2-input branch, only 1 mem bit is needed for each level! */ + if (2 == num_inputs_per_branch) { + num_mems_per_level = 1; + } + /* Number of memory bits is definite, add them */ + for (size_t ilvl = 0; ilvl < num_levels; ++ilvl) { + for (size_t imem = 0; imem < num_mems_per_level; ++imem) { + MuxMemId mem = add_mem(); + mem_levels_[mem] = ilvl; + } + } + + /* Create a fast node lookup locally. + * Only used for building the graph + * it sorts the nodes by levels and ids at each level + */ + std::vector> node_lookup; /* [num_levels][num_nodes_per_level] */ + node_lookup.resize(num_levels + 1); + + /* Number of outputs is definite, add and configure */ + MuxNodeId output_node = add_node(MUX_OUTPUT_NODE); + node_levels_[output_node] = num_levels; + node_ids_at_level_[output_node] = 0; + node_output_ids_[output_node] = MuxOutputId(0); + /* Update node lookup */ + node_lookup[num_levels].push_back(output_node); + + /* keep a list of node ids which can be candidates for input nodes */ + std::vector input_node_ids; + + /* Add internal nodes level by level, + * we start from the last level, following a strategy like tree growing + */ + for (size_t lvl = num_levels - 1; ; --lvl) { + /* Expand from the existing nodes + * Last level should expand from output_node + * Other levels will expand from internal nodes! + */ + size_t node_cnt_per_level = 0; /* A counter to record node indices at each level */ + for (MuxNodeId seed_node : node_lookup[lvl + 1]) { + /* Add a new node and connect to seed_node, until we reach the num_inputs_per_branch */ + for (size_t i = 0; i < num_inputs_per_branch; ++i) { + /* We deposite a type of INTERNAL_NODE, + * later it will be configured to INPUT if it is in the input list + */ + MuxNodeId expand_node = add_node(MUX_INTERNAL_NODE); + + /* Node level is deterministic */ + node_levels_[expand_node] = lvl; + node_ids_at_level_[expand_node] = node_cnt_per_level; + /* update level node counter */ + node_cnt_per_level++; + + /* Create an edge and connect the two nodes */ + MuxEdgeId edge = add_edge(expand_node, seed_node); + /* Configure the edge */ + edge_models_[edge] = pgl_model; + + /* Memory id depends on the level and offset in the current branch + * if number of inputs per branch is 2, it indicates a tree-like multiplexer, + * every two edges will share one memory bit + * otherwise, each edge corresponds to a memory bit + */ + + if ( 2 == num_inputs_per_branch) { + MuxMemId mem_id = MuxMemId(lvl); + set_edge_mem_id(edge, mem_id); + /* If this is a second edge in the branch, we will assign it to an inverted edge */ + if (0 != i % num_inputs_per_branch) { + edge_inv_mem_[edge] = true; + } + } else { + MuxMemId mem_id = MuxMemId( lvl * num_inputs_per_branch + i ); + set_edge_mem_id(edge, mem_id); + } + + /* Update node lookup */ + node_lookup[lvl].push_back(expand_node); + + /* Push the node to input list, and then remove the seed_node from the list */ + input_node_ids.push_back(expand_node); + /* Remove the node if the seed node is the list */ + std::vector::iterator it = find(input_node_ids.begin(), input_node_ids.end(), seed_node); + if (it != input_node_ids.end()) { + input_node_ids.erase(it); + } + + /* Check the number of input nodes, if already meet the demand, we can finish here */ + if (mux_size != input_node_ids.size()) { + continue; /* We need more inputs, keep looping */ + } + + /* The graph is done, we configure the input nodes and then we can return */ + /* We must be in level 0 !*/ + VTR_ASSERT( 0 == lvl ) ; + for (MuxNodeId input_node : input_node_ids) { + node_types_[input_node] = MUX_INPUT_NODE; + } + + /* Sort the nodes by the levels and offset */ + size_t input_cnt = 0; + for (auto lvl_nodes : node_lookup) { + for (MuxNodeId cand_node : lvl_nodes) { + if (MUX_INPUT_NODE != node_types_[cand_node]) { + continue; + } + /* Update the input node ids */ + node_input_ids_[cand_node] = MuxInputId(input_cnt); + /* Update the counter */ + input_cnt++; + } + } + /* Make sure we visited all the inputs in the cache */ + VTR_ASSERT(input_cnt == input_node_ids.size()); + /* Finish building the graph for a multi-level multiplexer */ + return; + } + } + } + /* Finish building the graph for a multi-level multiplexer */ +} + +/* Build the graph for a given one-level multiplexer implementation + * a N:1 one-level MUX + * + * input_node --->+ + * | + * input_node --->| + * |--> output_node + * ... | + * | + * input_node --->+ + */ +void MuxGraph::build_onelevel_mux_graph(const size_t& mux_size, + const CircuitModelId& pgl_model) { + /* Make sure mux_size is valid */ + VTR_ASSERT(valid_mux_implementation_num_inputs(mux_size)); + + /* We definitely know how many nodes we need, + * N inputs, 1 output and 0 internal nodes + */ + MuxNodeId output_node = add_node(MUX_OUTPUT_NODE); + node_levels_[output_node] = 1; + node_ids_at_level_[output_node] = 0; + node_output_ids_[output_node] = MuxOutputId(0); + + for (size_t i = 0; i < mux_size; ++i) { + MuxNodeId input_node = add_node(MUX_INPUT_NODE); + /* All the node belong to level 0 (we have only 1 level) */ + node_input_ids_[input_node] = MuxInputId(i); + node_levels_[input_node] = 0; + node_ids_at_level_[input_node] = i; + + /* We definitely know how many edges we need, + * the same as mux_size, add a edge connecting two nodes + */ + MuxEdgeId edge = add_edge(input_node, output_node); + /* Configure the edge */ + edge_models_[edge] = pgl_model; + + /* Create a memory bit*/ + MuxMemId mem = add_mem(); + mem_levels_[mem] = 0; + /* Link the edge to a memory bit */ + set_edge_mem_id(edge, mem); + } + /* Finish building the graph for a one-level multiplexer */ +} + +/* Convert some internal nodes to be additional outputs + * according to the fracturable LUT port definition + * We will iterate over each output port of a circuit model + * and find the frac_level and output_mask + * Then, the internal nodes at the frac_level will be converted + * to output nodes with a given output_mask + */ +void MuxGraph::add_fracturable_outputs(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model) { + /* Iterate over output ports */ + for (const auto& port : circuit_lib.model_ports_by_type(circuit_model, CIRCUIT_MODEL_PORT_OUTPUT, true)) { + /* Get the fracturable_level */ + size_t frac_level = circuit_lib.port_lut_frac_level(port); + /* Bypass invalid frac_level */ + if (size_t(-1) == frac_level) { + continue; + } + /* Iterate over output masks */ + for (const auto& output_idx : circuit_lib.port_lut_output_mask(port)) { + size_t num_matched_nodes = 0; + /* Iterate over node and find the internal nodes, which match the frac_level and output_idx */ + for (const auto& node : node_lookup_[frac_level][MUX_INTERNAL_NODE]) { + if (node_ids_at_level_[node] != output_idx) { + /* Bypass condition */ + continue; + } + /* Reach here, this is the node we want + * Convert it to output nodes and update the counter + */ + node_types_[node] = MUX_OUTPUT_NODE; + node_output_ids_[node] = MuxOutputId(num_outputs()); + num_matched_nodes++; + } + /* Either find 1 or 0 matched nodes */ + if (0 != num_matched_nodes) { + /* We should find only one node that matches! */ + VTR_ASSERT(1 == num_matched_nodes); + /* Rebuild the node look-up */ + build_node_lookup(); + continue; /* Finish here, go to next */ + } + /* Sometime the wanted node is already an output, do a double check */ + for (const auto& node : node_lookup_[frac_level][MUX_OUTPUT_NODE]) { + if (node_ids_at_level_[node] != output_idx) { + /* Bypass condition */ + continue; + } + /* Reach here, this is the node we want + * Just update the counter + */ + num_matched_nodes++; + } + /* We should find only one node that matches! */ + VTR_ASSERT(1 == num_matched_nodes); + } + } +} + +/* Build the graph for a given multiplexer model */ +void MuxGraph::build_mux_graph(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const size_t& mux_size) { + /* Make sure this model is a MUX */ + VTR_ASSERT((CIRCUIT_MODEL_MUX == circuit_lib.model_type(circuit_model)) + || (CIRCUIT_MODEL_LUT == circuit_lib.model_type(circuit_model)) ); + + /* Make sure mux_size is valid */ + VTR_ASSERT(valid_mux_implementation_num_inputs(mux_size)); + + size_t impl_mux_size = find_mux_implementation_num_inputs(circuit_lib, circuit_model, mux_size); + + /* Depends on the mux size, the implemented multiplexer structure may change! */ + enum e_circuit_model_structure impl_structure = find_mux_implementation_structure(circuit_lib, circuit_model, impl_mux_size); + + /* Branch on multiplexer structures, leading to different building strategies */ + switch (impl_structure) { + case CIRCUIT_MODEL_STRUCTURE_TREE: { + /* Find the number of levels */ + size_t num_levels = find_treelike_mux_num_levels(impl_mux_size); + + /* Find the number of inputs per branch, this is not final */ + size_t num_inputs_per_branch = 2; + + /* Build a multilevel mux graph */ + build_multilevel_mux_graph(impl_mux_size, num_levels, num_inputs_per_branch, circuit_lib.pass_gate_logic_model(circuit_model)); + break; + } + case CIRCUIT_MODEL_STRUCTURE_ONELEVEL: { + build_onelevel_mux_graph(impl_mux_size, circuit_lib.pass_gate_logic_model(circuit_model)); + break; + } + case CIRCUIT_MODEL_STRUCTURE_MULTILEVEL: { + /* Find the number of inputs per branch, this is not final */ + size_t num_inputs_per_branch = find_multilevel_mux_branch_num_inputs(impl_mux_size, circuit_lib.mux_num_levels(circuit_model)); + + /* Build a multilevel mux graph */ + build_multilevel_mux_graph(impl_mux_size, circuit_lib.mux_num_levels(circuit_model), + num_inputs_per_branch, + circuit_lib.pass_gate_logic_model(circuit_model)); + break; + } + default: + VTR_LOG_ERROR("Invalid multiplexer structure for circuit model '%s'!\n", + circuit_lib.model_name(circuit_model).c_str()); + exit(1); + } + + /* Since the graph is finalized, it is time to build the fast look-up */ + build_node_lookup(); + build_mem_lookup(); + + /* For fracturable LUTs, we need to add more outputs to the MUX graph */ + if ( (CIRCUIT_MODEL_LUT == circuit_lib.model_type(circuit_model)) + && (true == circuit_lib.is_lut_fracturable(circuit_model)) ) { + add_fracturable_outputs(circuit_lib, circuit_model); + } +} + +/* Build fast node lookup */ +void MuxGraph::build_node_lookup() { + /* Invalidate the node lookup if necessary */ + invalidate_node_lookup(); + + /* Find the maximum number of levels */ + size_t num_levels = 0; + for (auto node : nodes()) { + num_levels = std::max((int)node_levels_[node], (int)num_levels); + } + + /* Resize node_lookup */ + node_lookup_.resize(num_levels + 1); + for (size_t lvl = 0; lvl < node_lookup_.size(); ++lvl) { + /* Resize by number of node types */ + node_lookup_[lvl].resize(NUM_MUX_NODE_TYPES); + } + + /* Fill the node lookup */ + for (auto node : nodes()) { + node_lookup_[node_levels_[node]][size_t(node_types_[node])].push_back(node); + } +} + +/* Build fast mem lookup */ +void MuxGraph::build_mem_lookup() { + /* Invalidate the mem lookup if necessary */ + invalidate_mem_lookup(); + + /* Find the maximum number of levels */ + size_t num_levels = 0; + for (auto mem : memories()) { + num_levels = std::max((int)mem_levels_[mem], (int)num_levels); + } + + /* Resize mem_lookup */ + mem_lookup_.resize(num_levels + 1); + for (auto mem : memories()) { + /* Categorize mem nodes into mem_lookup */ + mem_lookup_[mem_levels_[mem]].push_back(mem); + } +} + +/* Invalidate (empty) the node fast lookup*/ +void MuxGraph::invalidate_node_lookup() { + node_lookup_.clear(); +} + +/* Invalidate (empty) the mem fast lookup*/ +void MuxGraph::invalidate_mem_lookup() { + mem_lookup_.clear(); +} + +/************************************************** + * Private validators + *************************************************/ + +/* valid ids */ +bool MuxGraph::valid_node_id(const MuxNodeId& node) const { + return size_t(node) < node_ids_.size() && node_ids_[node] == node; +} + +bool MuxGraph::valid_edge_id(const MuxEdgeId& edge) const { + return size_t(edge) < edge_ids_.size() && edge_ids_[edge] == edge; +} + +bool MuxGraph::valid_mem_id(const MuxMemId& mem) const { + return size_t(mem) < mem_ids_.size() && mem_ids_[mem] == mem; +} + +/* validate an input id (from which data path signal will be progagated to the output) */ +bool MuxGraph::valid_input_id(const MuxInputId& input_id) const { + for (const auto& lvl : node_lookup_) { + for (const auto& node : lvl[MUX_INPUT_NODE]) { + if (size_t(input_id) > size_t(node_input_ids_[node])) { + return false; + } + } + } + + return true; +} + +/* validate an output id */ +bool MuxGraph::valid_output_id(const MuxOutputId& output_id) const { + for (const auto& lvl : node_lookup_) { + for (const auto& node : lvl[MUX_OUTPUT_NODE]) { + if (size_t(output_id) > size_t(node_output_ids_[node])) { + return false; + } + } + } + + return true; +} + +bool MuxGraph::valid_level(const size_t& level) const { + return level < num_node_levels(); +} + +bool MuxGraph::valid_node_lookup() const { + return node_lookup_.empty(); +} + +/* validate a mux graph and see if it is valid */ +bool MuxGraph::valid_mux_graph() const { + /* A valid MUX graph should be + * 1. every node has 1 fan-out except output node + * 2. every input can be routed to the output node + */ + for (const auto& node : nodes()) { + /* output node has 0 fan-out*/ + if (MUX_OUTPUT_NODE == node_types_[node]) { + continue; + } + /* other nodes should have 1 fan-out */ + if (1 != node_out_edges_[node].size()) { + return false; + } + } + + /* Try to route to output */ + for (const auto& node : nodes()) { + if (MUX_INPUT_NODE == node_types_[node]) { + MuxNodeId next_node = node; + while ( 0 < node_out_edges_[next_node].size() ) { + MuxEdgeId edge = node_out_edges_[next_node][0]; + /* each edge must have 1 fan-out */ + if (1 != edge_sink_nodes_[edge].size()) { + return false; + } + next_node = edge_sink_nodes_[edge][0]; + } + if (MUX_OUTPUT_NODE != node_types_[next_node]) { + return false; + } + } + } + + return true; +} + +} /* End namespace openfpga*/ diff --git a/openfpga/src/mux_lib/mux_graph.h b/openfpga/src/mux_lib/mux_graph.h new file mode 100644 index 000000000..fd2fbbf6d --- /dev/null +++ b/openfpga/src/mux_lib/mux_graph.h @@ -0,0 +1,213 @@ +#ifndef MUX_GRAPH_H +#define MUX_GRAPH_H + +/******************************************************************** + * Include header files required by the data structure definition + *******************************************************************/ +#include +#include "vtr_vector.h" +#include "vtr_range.h" +#include "mux_graph_fwd.h" +#include "circuit_library.h" + +/* Begin namespace openfpga */ +namespace openfpga { + +/************************************************** + * This file includes a data structure to describe + * the internal structure of a multiplexer + * using a generic graph representation + * A Branch is a N:1 MUX in the part of MUX graph + * + * branch_input --->+ + * | + * branch_input --->| + * |--> branch_out + * ... | + * | + * branch_input --->+ + * + * A short example of how a two-level MUX is organized by branches + * + * +-----------+ +--------+ + * mux_inputs--->| Branch[0] |--->| | + * +-----------+ | | + * ... | Branch |---> mux_out + * +-----------+ | [N+1] | + * mux_inputs--->| Branch[N] |--->| | + * +-----------+ +--------+ + * + *************************************************/ +class MuxGraph { + private: /* data types used only in this class */ + enum e_mux_graph_node_type { + MUX_INPUT_NODE, + MUX_INTERNAL_NODE, + MUX_OUTPUT_NODE, + NUM_MUX_NODE_TYPES + }; + public: /* Types and ranges */ + typedef vtr::vector::const_iterator node_iterator; + typedef vtr::vector::const_iterator edge_iterator; + typedef vtr::vector::const_iterator mem_iterator; + + typedef vtr::Range node_range; + typedef vtr::Range edge_range; + typedef vtr::Range mem_range; + public: /* Public Constructors */ + /* Create an object based on a Circuit Model which is MUX */ + MuxGraph(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const size_t& mux_size); + private: /* Private Constructors*/ + /* Create an empty graph */ + MuxGraph(); + public: /* Public accessors: Aggregates */ + node_range nodes() const; + /* Find the non-input nodes */ + std::vector non_input_nodes() const; + edge_range edges() const; + mem_range memories() const; + /* Find the number of levels in terms of the multiplexer */ + std::vector levels() const; + /* Find the actual number of levels in the graph */ + std::vector node_levels() const; + public: /* Public accessors: Data query */ + /* Find the number of inputs in the MUX graph */ + size_t num_inputs() const; + std::vector inputs() const; + /* Find the number of outputs in the MUX graph */ + size_t num_outputs() const; + std::vector outputs() const; + /* Find the edge between two MUX nodes */ + std::vector find_edges(const MuxNodeId& from_node, const MuxNodeId& to_node) const; + /* Find the number of levels in the MUX graph */ + size_t num_levels() const; + size_t num_node_levels() const; + /* Find the number of SRAMs in the MUX graph */ + size_t num_memory_bits() const; + /* Find the number of SRAMs at a level in the MUX graph */ + size_t num_memory_bits_at_level(const size_t& level) const; + /* Return memory id at level */ + std::vector memories_at_level(const size_t& level) const; + /* Find the number of nodes at a given level in the MUX graph */ + size_t num_nodes_at_level(const size_t& level) const; + /* Find the level of a node */ + size_t node_level(const MuxNodeId& node) const; + /* Find the index of a node at its level */ + size_t node_index_at_level(const MuxNodeId& node) const; + /* Find the input edges for a node */ + std::vector node_in_edges(const MuxNodeId& node) const; + /* Find the input nodes for a edge */ + std::vector edge_src_nodes(const MuxEdgeId& edge) const; + /* Find the mem that control the edge */ + MuxMemId find_edge_mem(const MuxEdgeId& edge) const; + /* Identify if the edge is controlled by the inverted output of a mem */ + bool is_edge_use_inv_mem(const MuxEdgeId& edge) const; + /* Find the sizes of each branch of a MUX */ + std::vector branch_sizes() const; + /* Find the sizes of each branch of a MUX at a given level */ + std::vector branch_sizes(const size_t& level) const; + /* Generate MUX graphs for its branches */ + MuxGraph subgraph(const MuxNodeId& node) const; + std::vector build_mux_branch_graphs() const; + /* Get the node id of a given input */ + MuxNodeId node_id(const MuxInputId& input_id) const; + /* Get the node id of a given output */ + MuxNodeId node_id(const MuxOutputId& output_id) const; + /* Get the node id w.r.t. the node level and node_index at the level */ + MuxNodeId node_id(const size_t& node_level, const size_t& node_index_at_level) const; + /* Get the input id of a given node */ + MuxInputId input_id(const MuxNodeId& node_id) const; + /* Identify if the node is an input of the MUX */ + bool is_node_input(const MuxNodeId& node_id) const; + /* Get the output id of a given node */ + MuxOutputId output_id(const MuxNodeId& node_id) const; + /* Identify if the node is an output of the MUX */ + bool is_node_output(const MuxNodeId& node_id) const; + /* Decode memory bits based on an input id and an output id + * This function will start from the input node + * and do a forward propagation until reaching the output node + */ + vtr::vector decode_memory_bits(const MuxInputId& input_id, + const MuxOutputId& output_id) const; + /* Find the input node that the memory bits will route an output node to + * This function backward propagate from the output node to an input node + * assuming the memory bits are applied + * Note: This function is mainly used for decoding LUT MUXes + */ + MuxInputId find_input_node_driven_by_output_node(const std::map& memory_bits, + const MuxOutputId& output_id) const; + private: /* Private mutators : basic operations */ + /* Add a unconfigured node to the MuxGraph */ + MuxNodeId add_node(const enum e_mux_graph_node_type& node_type); + /* Add a edge connecting two nodes */ + MuxEdgeId add_edge(const MuxNodeId& from_node, const MuxNodeId& to_node); + /* Add a memory bit to the MuxGraph */ + MuxMemId add_mem(); + /* Configure the level of a memory */ + void set_mem_level(const MuxMemId& mem, const size_t& level); + /* Link an edge to a mem */ + void set_edge_mem_id(const MuxEdgeId& edge, const MuxMemId& mem); + private: /* Private mutators : graph builders */ + void build_multilevel_mux_graph(const size_t& mux_size, + const size_t& num_levels, const size_t& num_inputs_per_branch, + const CircuitModelId& pgl_model) ; + /* Build the graph for a given one-level multiplexer implementation */ + void build_onelevel_mux_graph(const size_t& mux_size, + const CircuitModelId& pgl_model) ; + /* Build the graph for a given multiplexer model */ + void build_mux_graph(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const size_t& mux_size); + /* Convert some internal node to outputs according to fracturable LUT circuit design specifications */ + void add_fracturable_outputs(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model); + /* Build fast node lookup */ + void build_node_lookup(); + /* Build fast mem lookup */ + void build_mem_lookup(); + private: /* Private validators */ + /* valid ids */ + bool valid_node_id(const MuxNodeId& node) const; + bool valid_edge_id(const MuxEdgeId& edge) const; + bool valid_mem_id(const MuxMemId& mem) const; + bool valid_input_id(const MuxInputId& input_id) const; + bool valid_output_id(const MuxOutputId& output_id) const; + bool valid_level(const size_t& level) const; + /* validate/invalidate node lookup */ + bool valid_node_lookup() const; + void invalidate_node_lookup(); + void invalidate_mem_lookup(); + /* validate graph */ + bool valid_mux_graph() const; + private: /* Internal data */ + vtr::vector node_ids_; /* Unique ids for each node */ + vtr::vector node_types_; /* type of each node, input/output/internal */ + vtr::vector node_input_ids_; /* Unique ids for each node as an input of the MUX */ + vtr::vector node_output_ids_; /* Unique ids for each node as an input of the MUX */ + vtr::vector node_levels_; /* at which level, each node belongs to */ + vtr::vector node_ids_at_level_; /* the index at the level that each node belongs to */ + vtr::vector> node_in_edges_; /* ids of incoming edges to each node */ + vtr::vector> node_out_edges_; /* ids of outgoing edges from each node */ + + vtr::vector edge_ids_; /* Unique ids for each edge */ + vtr::vector> edge_src_nodes_; /* source nodes drive this edge */ + vtr::vector> edge_sink_nodes_; /* sink nodes this edge drives */ + vtr::vector edge_models_; /* type of each edge: tgate/pass-gate */ + vtr::vector edge_mem_ids_; /* ids of memory bit that control the edge */ + vtr::vector edge_inv_mem_; /* if the edge is controlled by an inverted output of a memory bit */ + + vtr::vector mem_ids_; /* ids of configuration memories */ + vtr::vector mem_levels_; /* ids of configuration memories */ + + /* fast look-up */ + typedef std::vector>> NodeLookup; + mutable NodeLookup node_lookup_; /* [num_levels][num_types][num_nodes_per_level] */ + typedef std::vector> MemLookup; + mutable MemLookup mem_lookup_; /* [num_levels][num_mems_per_level] */ +}; + +} /* End namespace openfpga*/ + +#endif diff --git a/openfpga/src/mux_lib/mux_graph_fwd.h b/openfpga/src/mux_lib/mux_graph_fwd.h new file mode 100644 index 000000000..75fd5b1e1 --- /dev/null +++ b/openfpga/src/mux_lib/mux_graph_fwd.h @@ -0,0 +1,33 @@ +/************************************************** + * This file includes only declarations for + * the data structures to describe multiplexer structures + * Please refer to mux_graph.h for more details + *************************************************/ +#ifndef MUX_GRAPH_FWD_H +#define MUX_GRAPH_FWD_H + +#include "vtr_strong_id.h" + +/* begin namespace openfpga */ +namespace openfpga { + + +/* Strong Ids for MUXes */ +struct mux_node_id_tag; +struct mux_edge_id_tag; +struct mux_mem_id_tag; +struct mux_input_id_tag; +struct mux_output_id_tag; + +typedef vtr::StrongId MuxNodeId; +typedef vtr::StrongId MuxEdgeId; +typedef vtr::StrongId MuxMemId; +typedef vtr::StrongId MuxInputId; +typedef vtr::StrongId MuxOutputId; + +class MuxGraph; + +} /* end namespace openfpga */ + + +#endif diff --git a/openfpga/src/mux_lib/mux_library.cpp b/openfpga/src/mux_lib/mux_library.cpp new file mode 100644 index 000000000..20e590fdf --- /dev/null +++ b/openfpga/src/mux_lib/mux_library.cpp @@ -0,0 +1,121 @@ +/************************************************** + * This file includes member functions for the + * data structures in mux_library.h + *************************************************/ + +#include "vtr_assert.h" + +#include "mux_library.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/************************************************** + * Member functions for the class MuxLibrary + *************************************************/ + +/************************************************** + * Public accessors: aggregates + *************************************************/ +MuxLibrary::mux_range MuxLibrary::muxes() const { + return vtr::make_range(mux_ids_.begin(), mux_ids_.end()); +} + +/************************************************** + * Public accessors: data query + *************************************************/ +/* Get a MUX graph (read-only) */ +MuxId MuxLibrary::mux_graph(const CircuitModelId& circuit_model, + const size_t& mux_size) const { + /* Make sure we have a valid mux look-up */ + VTR_ASSERT_SAFE(valid_mux_lookup()); + /* Validate circuit model id and mux_size */ + VTR_ASSERT_SAFE(valid_mux_size(circuit_model, mux_size)); + + return mux_lookup_[circuit_model][mux_size]; +} + +const MuxGraph& MuxLibrary::mux_graph(const MuxId& mux_id) const { + VTR_ASSERT_SAFE(valid_mux_id(mux_id)); + return mux_graphs_[mux_id]; +} + +/* Get a mux circuit model id */ +CircuitModelId MuxLibrary::mux_circuit_model(const MuxId& mux_id) const { + VTR_ASSERT_SAFE(valid_mux_id(mux_id)); + return mux_circuit_models_[mux_id]; +} + +/* Find the maximum mux size among the mux graphs */ +size_t MuxLibrary::max_mux_size() const { + /* Iterate over all the mux graphs and find their sizes */ + size_t max_mux_size = 0; + for (const auto& mux : mux_ids_) { + max_mux_size = std::max(max_mux_size, mux_graphs_[mux].num_inputs()); + } + return max_mux_size; +} + +/************************************************** + * Private mutators: + *************************************************/ +/* Add a mux to the library */ +void MuxLibrary::add_mux(const CircuitLibrary& circuit_lib, const CircuitModelId& circuit_model, const size_t& mux_size) { + /* First, check if there is already an existing graph */ + if (valid_mux_size(circuit_model, mux_size)) { + return; + } + + /* create a new id for the mux */ + MuxId mux = MuxId(mux_ids_.size()); + /* Push to the node list */ + mux_ids_.push_back(mux); + /* Add a mux graph */ + mux_graphs_.push_back(MuxGraph(circuit_lib, circuit_model, mux_size)); + /* Recorde mux cirucit model id */ + mux_circuit_models_.push_back(circuit_model); + + /* update mux_lookup*/ + mux_lookup_[circuit_model][mux_size] = mux; +} + +/************************************************** + * Private accessors: validator and invalidators + *************************************************/ +bool MuxLibrary::valid_mux_id(const MuxId& mux) const { + return size_t(mux) < mux_ids_.size() && mux_ids_[mux] == mux; +} + +bool MuxLibrary::valid_mux_lookup() const { + return mux_lookup_.empty(); +} + +bool MuxLibrary::valid_mux_circuit_model_id(const CircuitModelId& circuit_model) const { + MuxLookup::iterator it = mux_lookup_.find(circuit_model); + return (it != mux_lookup_.end()); +} + +bool MuxLibrary::valid_mux_size(const CircuitModelId& circuit_model, const size_t& mux_size) const { + if (false == valid_mux_circuit_model_id(circuit_model)) { + return false; + } + std::map::iterator it = mux_lookup_[circuit_model].find(mux_size); + return (it != mux_lookup_[circuit_model].end()); +} + +/************************************************** + * Private mutators: validator and invalidators + *************************************************/ + +/* Build fast node lookup */ +void MuxLibrary::build_mux_lookup() { + /* Invalidate the mux lookup if necessary */ + invalidate_mux_lookup(); +} + +/* Invalidate (empty) the mux fast lookup*/ +void MuxLibrary::invalidate_mux_lookup() { + mux_lookup_.clear(); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/mux_lib/mux_library.h b/openfpga/src/mux_lib/mux_library.h new file mode 100644 index 000000000..03f406738 --- /dev/null +++ b/openfpga/src/mux_lib/mux_library.h @@ -0,0 +1,64 @@ +/************************************************** + * This file includes a data structure to describe + * the multiplexer implementations in FPGA architectures + * MuxLibrary is a collection of multiplexers + * with various circuit-level description (related to + * the information available in CircuitLibrary + * and the input size of multiplexers) + *************************************************/ + +#ifndef MUX_LIBRARY_H +#define MUX_LIBRARY_H + +#include +#include "mux_graph.h" +#include "mux_library_fwd.h" + +/* begin namespace openfpga */ +namespace openfpga { + +class MuxLibrary { + public: /* Types and ranges */ + typedef vtr::vector::const_iterator mux_iterator; + + typedef vtr::Range mux_range; + public: /* Public accessors: Aggregates */ + mux_range muxes() const; + public: /* Public accessors */ + /* Get a MUX graph (read-only) */ + MuxId mux_graph(const CircuitModelId& circuit_model, const size_t& mux_size) const; + const MuxGraph& mux_graph(const MuxId& mux_id) const; + /* Get a mux circuit model id */ + CircuitModelId mux_circuit_model(const MuxId& mux_id) const; + /* Find the mux sizes */ + size_t max_mux_size() const; + public: /* Public mutators */ + /* Add a mux to the library */ + void add_mux(const CircuitLibrary& circuit_lib, const CircuitModelId& circuit_model, const size_t& mux_size); + public: /* Public validators */ + bool valid_mux_id(const MuxId& mux) const; + private: /* Private accessors */ + bool valid_mux_lookup() const; + bool valid_mux_circuit_model_id(const CircuitModelId& circuit_model) const; + bool valid_mux_size(const CircuitModelId& circuit_model, const size_t& mux_size) const; + private: /* Private mutators: mux_lookup */ + void build_mux_lookup(); + /* Invalidate (empty) the mux fast lookup*/ + void invalidate_mux_lookup(); + private: /* Internal data */ + /* MUX graph-based desription */ + vtr::vector mux_ids_; /* Unique identifier for each mux graph */ + vtr::vector mux_graphs_; /* Graphs describing MUX internal structures */ + vtr::vector mux_circuit_models_; /* circuit model id in circuit library */ + + /* Local encoder description */ + //vtr::vector mux_local_encoders_; /* Graphs describing MUX internal structures */ + + /* a fast look-up to search mux_graphs with given circuit model and mux size */ + typedef std::map> MuxLookup; + mutable MuxLookup mux_lookup_; +}; + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/mux_lib/mux_library_fwd.h b/openfpga/src/mux_lib/mux_library_fwd.h new file mode 100644 index 000000000..f9a8697d6 --- /dev/null +++ b/openfpga/src/mux_lib/mux_library_fwd.h @@ -0,0 +1,25 @@ +/************************************************** + * This file includes only declarations for + * the data structures to describe multiplexer structures + * Please refer to mux_library.h for more details + *************************************************/ +#ifndef MUX_LIBRARY_FWD_H +#define MUX_LIBRARY_FWD_H + +#include "vtr_strong_id.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/* Strong Ids for MUXes */ +struct mux_id_tag; +struct mux_local_decoder_id_tag; + +typedef vtr::StrongId MuxId; +typedef vtr::StrongId MuxLocalDecoderId; + +class MuxLibrary; + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/utils/decoder_library_utils.cpp b/openfpga/src/utils/decoder_library_utils.cpp new file mode 100644 index 000000000..a41e439ef --- /dev/null +++ b/openfpga/src/utils/decoder_library_utils.cpp @@ -0,0 +1,60 @@ +/*************************************************************************************** + * This file includes most utilized functions for the DecoderLibrary data structure + ***************************************************************************************/ +#include + +#include "vtr_assert.h" + +#include "decoder_library_utils.h" + +/* Begin namespace openfpga */ +namespace openfpga { + +/*************************************************************************************** + * NOTE: This function is mainly designed for local decoders inside multiplexers + * Find the size of address lines for a decoder with a given data output size + * Addr lines + * | | ... | + * v v v + * +-----------+ + * / Local \ + * / Decoder \ + * +-----------------+ + * | | | ... | | | + * v v v v v v + * Data outputs + * + * The outputs are assumes to be one-hot codes (at most only one '1' exist) + * Considering this fact, there are only num_of_outputs + 1 conditions to be encoded. + * Therefore, the number of inputs is ceil(log(num_of_outputs+1)/log(2)) + * We plus 1, which is all-zero condition for outputs + ***************************************************************************************/ +size_t find_mux_local_decoder_addr_size(const size_t& data_size) { + /* if data size is 1, it is an corner case for the decoder (addr = 1) */ + if (1 == data_size) { + return 1; + } + VTR_ASSERT (2 <= data_size); + return ceil(log(data_size) / log(2)); +} + +/*************************************************************************************** + * Try to find if the decoder already exists in the library, + * If there is no such decoder, add it to the library + ***************************************************************************************/ +DecoderId add_mux_local_decoder_to_library(DecoderLibrary& decoder_lib, + const size_t data_size) { + size_t addr_size = find_mux_local_decoder_addr_size(data_size); + + DecoderId decoder_id = decoder_lib.find_decoder(addr_size, data_size, false, false, true); + + if (DecoderId::INVALID() == decoder_id) { + /* Add the decoder */ + return decoder_lib.add_decoder(addr_size, data_size, false, false, true); + } + + /* There is already a decoder in the library, return the decoder id */ + return decoder_id; +} + +} /* End namespace openfpga*/ diff --git a/openfpga/src/utils/decoder_library_utils.h b/openfpga/src/utils/decoder_library_utils.h new file mode 100644 index 000000000..d55a0750a --- /dev/null +++ b/openfpga/src/utils/decoder_library_utils.h @@ -0,0 +1,21 @@ +/*************************************************************************************** + * Header file for most utilized functions for the DecoderLibrary data structure + ***************************************************************************************/ +#ifndef DECODER_LIBRARY_UTILS_H +#define DECODER_LIBRARY_UTILS_H + +#include "decoder_library.h" + +/* Begin namespace openfpga */ +namespace openfpga { + +bool need_mux_local_decoder(const size_t& data_size); + +size_t find_mux_local_decoder_addr_size(const size_t& data_size); + +DecoderId add_mux_local_decoder_to_library(DecoderLibrary& decoder_lib, + const size_t data_size); + +} /* End namespace openfpga*/ + +#endif diff --git a/openfpga/src/utils/mux_utils.cpp b/openfpga/src/utils/mux_utils.cpp new file mode 100644 index 000000000..b921f0589 --- /dev/null +++ b/openfpga/src/utils/mux_utils.cpp @@ -0,0 +1,458 @@ +/************************************************** + * This file includes a series of most utilized functions + * that are used to implement a multiplexer + *************************************************/ +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_log.h" +#include "vtr_assert.h" + +/* Headers from readarchopenfpga library */ +#include "circuit_types.h" + +#include "decoder_library_utils.h" +#include "mux_utils.h" + +/* Begin namespace openfpga */ +namespace openfpga { + +/* Validate the number of inputs for a multiplexer implementation, + * the minimum supported size is 2 + * otherwise, there is no need for a MUX + */ +bool valid_mux_implementation_num_inputs(const size_t& mux_size) { + return (2 <= mux_size); +} + +/************************************************** + * Find the actual number of datapath inputs for a multiplexer implementation + * 1. if there are no requirements on constant inputs, mux_size is the actual one + * 2. if there exist constant inputs, mux_size should minus 1 + * This function is mainly used to recover the number of datapath inputs + * for MUXGraphs which is a generic representation without labelling datapath inputs + *************************************************/ +size_t find_mux_num_datapath_inputs(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const size_t& mux_size) { + /* Should be either MUX or LUT + * LUTs do have an tree-like MUX, but there is no need for a constant input! + */ + VTR_ASSERT ((CIRCUIT_MODEL_MUX == circuit_lib.model_type(circuit_model)) + || (CIRCUIT_MODEL_LUT == circuit_lib.model_type(circuit_model)) ); + + if (CIRCUIT_MODEL_LUT == circuit_lib.model_type(circuit_model)) { + return mux_size; + } + + if (true == circuit_lib.mux_add_const_input(circuit_model)) { + return mux_size - 1; + } + return mux_size; +} + +/************************************************** + * Find the actual number of inputs for a multiplexer implementation + * 1. if there are no requirements on constant inputs, mux_size is the actual one + * 2. if there exist constant inputs, mux_size should plus 1 + *************************************************/ +size_t find_mux_implementation_num_inputs(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const size_t& mux_size) { + /* Should be either MUX or LUT + * LUTs do have an tree-like MUX, but there is no need for a constant input! + */ + VTR_ASSERT ((CIRCUIT_MODEL_MUX == circuit_lib.model_type(circuit_model)) + || (CIRCUIT_MODEL_LUT == circuit_lib.model_type(circuit_model)) ); + + if (CIRCUIT_MODEL_LUT == circuit_lib.model_type(circuit_model)) { + return mux_size; + } + + if (true == circuit_lib.mux_add_const_input(circuit_model)) { + return mux_size + 1; + } + return mux_size; +} + +/************************************************** + * Find the structure for a multiplexer implementation + * 1. In most cases, the structure should follow the + * mux_structure defined by users in the CircuitLibrary + * 2. However, a special case may apply when mux_size is 2 + * In such case, we will force a TREE structure + * regardless of users' specification as this is the + * most efficient structure + *************************************************/ +enum e_circuit_model_structure find_mux_implementation_structure(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const size_t& mux_size) { + /* Ensure the mux size is valid ! */ + VTR_ASSERT(valid_mux_implementation_num_inputs(mux_size)); + + /* Branch on the mux sizes */ + if (2 == mux_size) { + /* Tree-like is the best structure of CMOS MUX2 */ + if (CIRCUIT_MODEL_DESIGN_CMOS == circuit_lib.design_tech_type(circuit_model)) { + return CIRCUIT_MODEL_STRUCTURE_TREE; + } + VTR_ASSERT_SAFE(CIRCUIT_MODEL_DESIGN_RRAM == circuit_lib.design_tech_type(circuit_model)); + /* One-level is the best structure of RRAM MUX2 */ + return CIRCUIT_MODEL_STRUCTURE_ONELEVEL; + } + + return circuit_lib.mux_structure(circuit_model); +} + +/************************************************** + * Find the number of levels for a tree-like multiplexer implementation + *************************************************/ +size_t find_treelike_mux_num_levels(const size_t& mux_size) { + /* Do log2(mux_size), have a basic number */ + size_t level = (size_t)(log((double)mux_size)/log(2.)); + /* Fix the error, i.e. mux_size=5, level = 2, we have to complete */ + while (mux_size > pow(2.,(double)level)) { + level++; + } + + return level; +} + +/************************************************** + * Find the number of inputs for majority of branches + * in a multi-level multiplexer implementation + *************************************************/ +size_t find_multilevel_mux_branch_num_inputs(const size_t& mux_size, + const size_t& mux_level) { + /* Special Case: mux_size = 2 */ + if (2 == mux_size) { + return mux_size; + } + + if (1 == mux_level) { + return mux_size; + } + + if (2 == mux_level) { + size_t num_input_per_unit = (size_t)sqrt(mux_size); + while ( num_input_per_unit * num_input_per_unit < mux_size) { + num_input_per_unit++; + } + return num_input_per_unit; + } + + VTR_ASSERT_SAFE(2 < mux_level); + + size_t num_input_per_unit = 2; + while (pow((double)num_input_per_unit, (double)mux_level) < mux_size) { + num_input_per_unit++; + } + + if (!valid_mux_implementation_num_inputs(num_input_per_unit)) { + VTR_LOG_ERROR("Number of inputs of each basis should be at least 2!\n"); + exit(1); + } + + return num_input_per_unit; +} + +/************************************************** + * Build a location map for intermediate buffers + * that may appear at the multiplexing structure of a LUT + * Here is a tricky thing: + * By default, the first and last stage should not exist any intermediate buffers + * For example: + * There are 5 stages in a 4-stage multiplexer is available for buffering + * but only 3 stages [1,2,3] are intermedate buffers + * and these are users' specification + * + * +-------+ +-------+ +-------+ +-------+ + * location | stage | location | stage | location | stage | location | stage | location + * [0] | [0] | [1] | [1] | [2] | [2] | [3] | [3] | [5] + * +-------+ +-------+ +-------+ +-------+ + * + * We will check if the length of location map matches the number of + * multiplexer levels. And then complete a location map + * for the given multiplexers + *************************************************/ +std::vector build_mux_intermediate_buffer_location_map(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const size_t& num_mux_levels) { + /* Deposite a default location map */ + std::vector location_map(num_mux_levels, false); + std::string location_map_str; + + /* ONLY for LUTs: intermediate buffers may exist if specified */ + if (CIRCUIT_MODEL_LUT != circuit_lib.model_type(circuit_model)) { + return location_map; + } + + /* Get location map when the flag of intermediate buffer is on */ + if (true == circuit_lib.is_lut_intermediate_buffered(circuit_model)) { + location_map_str = circuit_lib.lut_intermediate_buffer_location_map(circuit_model); + } + + /* If no location map is specified, we can return here */ + if (location_map_str.empty()) { + return location_map; + } + + /* Check if the user-defined location map matches the number of mux levels*/ + VTR_ASSERT(num_mux_levels - 2 == location_map_str.length()); + + /* Apply the location_map string to the intermediate stages of multiplexers */ + for (size_t i = 0; i < location_map_str.length(); ++i) { + /* '1' indicates that an intermediate buffer is needed at the location */ + if ('1' == location_map_str[i]) { + location_map[i + 1] = true; + } + } + + return location_map; +} + +/************************************************** + * Find the number of reserved configuration bits for a multiplexer + * The reserved configuration bits is only used by ReRAM-based multiplexers + * It is actually the shared BL/WLs among ReRAMs + *************************************************/ +size_t find_mux_num_reserved_config_bits(const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const MuxGraph& mux_graph) { + if (CIRCUIT_MODEL_DESIGN_RRAM != circuit_lib.design_tech_type(mux_model)) { + return 0; + } + + std::vector mux_branch_sizes = mux_graph.branch_sizes(); + /* For tree-like multiplexers: they have two shared configuration bits */ + if ( (1 == mux_branch_sizes.size()) + && (2 == mux_branch_sizes[0]) ) { + return mux_branch_sizes[0]; + } + /* One-level multiplexer */ + if ( 1 == mux_graph.num_levels() ) { + return mux_graph.num_inputs(); + } + /* Multi-level multiplexers: TODO: This should be better tested and clarified + * Now the multi-level multiplexers are treated as cascaded one-level multiplexers + * Use the maximum branch sizes and multiply it by the number of levels + */ + std::vector::iterator max_mux_branch_size = std::max_element(mux_branch_sizes.begin(), mux_branch_sizes.end()); + return mux_graph.num_levels() * (*max_mux_branch_size); +} + +/************************************************** + * Find the number of configuration bits for a CMOS multiplexer + * In general, the number of configuration bits is + * the number of memory bits for a mux_graph + * However, when local decoders are used, + * the number of configuration bits are reduced to log2(X) + *************************************************/ +static +size_t find_cmos_mux_num_config_bits(const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const MuxGraph& mux_graph, + const e_config_protocol_type& sram_orgz_type) { + size_t num_config_bits = 0; + + switch (sram_orgz_type) { + case CONFIG_MEM_MEMORY_BANK: + case CONFIG_MEM_SCAN_CHAIN: + case CONFIG_MEM_STANDALONE: + num_config_bits = mux_graph.num_memory_bits(); + break; + default: + VTR_LOG_ERROR("Invalid type of SRAM organization!\n"); + exit(1); + } + + if (false == circuit_lib.mux_use_local_encoder(mux_model)) { + return num_config_bits; + } + + num_config_bits = 0; + /* Multiplexer local encoders are applied to memory bits at each stage */ + for (const auto& lvl : mux_graph.levels()) { + num_config_bits += find_mux_local_decoder_addr_size(mux_graph.num_memory_bits_at_level(lvl)); + } + + return num_config_bits; +} + +/************************************************** + * Find the number of configuration bits for a RRAM multiplexer + * In general, the number of configuration bits is + * the number of levels for a mux_graph + * This is due to only the last BL/WL of the multiplexer is + * independent from each other + * However, when local decoders are used, + * the number of configuration bits should be consider all the + * shared(reserved) configuration bits and independent bits + *************************************************/ +static +size_t find_rram_mux_num_config_bits(const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const MuxGraph& mux_graph, + const e_config_protocol_type& sram_orgz_type) { + size_t num_config_bits = 0; + switch (sram_orgz_type) { + case CONFIG_MEM_MEMORY_BANK: + /* In memory bank, by intensively share the Bit/Word Lines, + * we only need 1 additional BL and WL for each MUX level. + */ + num_config_bits = mux_graph.num_levels(); + break; + case CONFIG_MEM_SCAN_CHAIN: + case CONFIG_MEM_STANDALONE: + /* Currently we DO NOT SUPPORT THESE, given an invalid number */ + num_config_bits = size_t(-1); + break; + default: + VTR_LOG_ERROR("Invalid type of SRAM organization!\n"); + exit(1); + } + + if (true == circuit_lib.mux_use_local_encoder(mux_model)) { + /* TODO: this is a to-do work for ReRAM-based multiplexers and FPGAs + * The number of states of a local decoder only depends on how many + * memory bits that the multiplexer will have + * This may NOT be correct!!! + */ + return find_mux_local_decoder_addr_size(mux_graph.num_memory_bits()); + } + + return num_config_bits; +} + +/************************************************** + * Find the number of configuration bits for + * a routing multiplexer + * Two cases are considered here. + * They are placed in different branches (sub-functions) + * in order to be easy in extending to new technology! + *************************************************/ +size_t find_mux_num_config_bits(const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const MuxGraph& mux_graph, + const e_config_protocol_type& sram_orgz_type) { + size_t num_config_bits = size_t(-1); + + switch (circuit_lib.design_tech_type(mux_model)) { + case CIRCUIT_MODEL_DESIGN_CMOS: + num_config_bits = find_cmos_mux_num_config_bits(circuit_lib, mux_model, mux_graph, sram_orgz_type); + break; + case CIRCUIT_MODEL_DESIGN_RRAM: + num_config_bits = find_rram_mux_num_config_bits(circuit_lib, mux_model, mux_graph, sram_orgz_type); + break; + default: + VTR_LOG_ERROR("Invalid design_technology of MUX(name: %s)\n", + circuit_lib.model_name(mux_model).c_str()); + exit(1); + } + + return num_config_bits; +} + +/************************************************** + * Find the number of shared configuration bits for a CMOS multiplexer + * Currently, all the supported CMOS multiplexers + * do NOT require any shared configuration bits + *************************************************/ +static +size_t find_cmos_mux_num_shared_config_bits(const e_config_protocol_type& sram_orgz_type) { + size_t num_shared_config_bits = 0; + + switch (sram_orgz_type) { + case CONFIG_MEM_MEMORY_BANK: + case CONFIG_MEM_SCAN_CHAIN: + case CONFIG_MEM_STANDALONE: + num_shared_config_bits = 0; + break; + default: + VTR_LOG_ERROR("Invalid type of SRAM organization!\n"); + exit(1); + } + + return num_shared_config_bits; +} + +/************************************************** + * Find the number of shared configuration bits for a ReRAM multiplexer + *************************************************/ +static +size_t find_rram_mux_num_shared_config_bits(const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const MuxGraph& mux_graph, + const e_config_protocol_type& sram_orgz_type) { + size_t num_shared_config_bits = 0; + switch (sram_orgz_type) { + case CONFIG_MEM_MEMORY_BANK: { + /* In memory bank, the number of shared configuration bits is + * the sum of largest branch size at each level + */ + for (auto lvl : mux_graph.node_levels()) { + /* Find the maximum branch size: + * Note that branch_sizes() returns a sorted vector + * The last one is the maximum + */ + num_shared_config_bits += mux_graph.branch_sizes(lvl).back(); + } + break; + } + case CONFIG_MEM_SCAN_CHAIN: + case CONFIG_MEM_STANDALONE: + /* Currently we DO NOT SUPPORT THESE, given an invalid number */ + num_shared_config_bits = size_t(-1); + break; + default: + VTR_LOG_ERROR("Invalid type of SRAM organization!\n"); + exit(1); + } + + if (true == circuit_lib.mux_use_local_encoder(mux_model)) { + /* TODO: this is a to-do work for ReRAM-based multiplexers and FPGAs + * The number of states of a local decoder only depends on how many + * memory bits that the multiplexer will have + * This may NOT be correct!!! + * If local encoders are introduced, zero shared configuration bits are required + */ + return 0; + } + + return num_shared_config_bits; +} + +/************************************************** + * Find the number of shared configuration bits for + * a routing multiplexer + * Two cases are considered here. + * They are placed in different branches (sub-functions) + * in order to be easy in extending to new technology! + * + * Note: currently, shared configuration bits are demanded + * by ReRAM-based multiplexers only + *************************************************/ +size_t find_mux_num_shared_config_bits(const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const MuxGraph& mux_graph, + const e_config_protocol_type& sram_orgz_type) { + size_t num_shared_config_bits = size_t(-1); + + switch (circuit_lib.design_tech_type(mux_model)) { + case CIRCUIT_MODEL_DESIGN_CMOS: + num_shared_config_bits = find_cmos_mux_num_shared_config_bits(sram_orgz_type); + break; + case CIRCUIT_MODEL_DESIGN_RRAM: + num_shared_config_bits = find_rram_mux_num_shared_config_bits(circuit_lib, mux_model, mux_graph, sram_orgz_type); + break; + default: + VTR_LOG_ERROR("Invalid design_technology of MUX(name: %s)\n", + circuit_lib.model_name(mux_model).c_str()); + exit(1); + } + + return num_shared_config_bits; +} + +} /* End namespace openfpga*/ diff --git a/openfpga/src/utils/mux_utils.h b/openfpga/src/utils/mux_utils.h new file mode 100644 index 000000000..cf2ce95ad --- /dev/null +++ b/openfpga/src/utils/mux_utils.h @@ -0,0 +1,59 @@ +#ifndef MUX_UTILS_H +#define MUX_UTILS_H + +/******************************************************************** + * Include header files required by the data structure definition + *******************************************************************/ + +#include + +#include "circuit_library.h" +#include "mux_library.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* Begin namespace openfpga */ +namespace openfpga { + +bool valid_mux_implementation_num_inputs(const size_t& mux_size); + +size_t find_mux_num_datapath_inputs(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const size_t& mux_size); + +size_t find_mux_implementation_num_inputs(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const size_t& mux_size); + +enum e_circuit_model_structure find_mux_implementation_structure(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const size_t& mux_size); + +size_t find_treelike_mux_num_levels(const size_t& mux_size); + +size_t find_multilevel_mux_branch_num_inputs(const size_t& mux_size, + const size_t& mux_level); + +std::vector build_mux_intermediate_buffer_location_map(const CircuitLibrary& circuit_lib, + const CircuitModelId& circuit_model, + const size_t& num_mux_levels); + +size_t find_mux_num_reserved_config_bits(const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const MuxGraph& mux_graph); + +size_t find_mux_num_config_bits(const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const MuxGraph& mux_graph, + const e_config_protocol_type& sram_orgz_type); + +size_t find_mux_num_shared_config_bits(const CircuitLibrary& circuit_lib, + const CircuitModelId& mux_model, + const MuxGraph& mux_graph, + const e_config_protocol_type& sram_orgz_type); + +} /* End namespace openfpga*/ + +#endif