/************************************************** * This file includes member functions for the * data structures in mux_graph.h *************************************************/ #include #include #include #include #include "util.h" #include "vtr_assert.h" #include "mux_utils.h" #include "mux_graph.h" /************************************************** * 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, SPICE_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_masks(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((SPICE_MODEL_MUX == circuit_lib.model_type(circuit_model)) || (SPICE_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_spice_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 SPICE_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 SPICE_MODEL_STRUCTURE_ONELEVEL: { build_onelevel_mux_graph(impl_mux_size, circuit_lib.pass_gate_logic_model(circuit_model)); break; } case SPICE_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: vpr_printf(TIO_MESSAGE_ERROR, "(File:%s, [LINE%d]) Invalid multiplexer structure for circuit model (name=%s)!\n", __FILE__, __LINE__, 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 ( (SPICE_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 of Member functions for the class MuxGraph *************************************************/