Skip to content

More fixes to sync_netlists_to_routing_flat #2867

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Apr 28, 2025
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
06d02f4
more fixes for bitstream generation with flat router
fkosar-ql Jan 21, 2025
9ca4776
Merge branch 'master' of https://github.com/verilog-to-routing/vtr-ve…
amin1377 Apr 23, 2025
ad8bfce
[vpr][pack] fix merge issues w/ flat sync list
amin1377 Apr 23, 2025
fbd2015
make format
amin1377 Apr 23, 2025
8b15437
make format 2
amin1377 Apr 23, 2025
ae07129
[vpr][base] fix assigned pb_graph_pin when graph node is not primitive
amin1377 Apr 25, 2025
b78b3eb
[vpr][pack] pass logical type to alloc_and_laod_pb_route
amin1377 Apr 25, 2025
b0d7afc
[vpr][pack] update alloc_and_load_pb_route header file
amin1377 Apr 25, 2025
250329b
[vpr][pack] fix pb_graph_pin assignment in load_trace_to_pb_route
amin1377 Apr 25, 2025
f9c1714
Merge branch 'master' of https://github.com/verilog-to-routing/vtr-ve…
amin1377 Apr 25, 2025
a839dc2
[vpr][pack] add intra_lb_pb_pin_lookup_ to cluster legalizer
amin1377 Apr 26, 2025
de84b8a
[vpr][pack] initializer intra_lb_pb_pin_lookup and pass it to alloc_a…
amin1377 Apr 26, 2025
8902090
[vpr][pack] use intra_lb_pb_pin_lookup to get pb_pin from pin number
amin1377 Apr 26, 2025
3594124
make format
amin1377 Apr 26, 2025
623132e
[vpr][pack] remove casting net id
amin1377 Apr 28, 2025
2468e8a
[vpr][pack] add doxygen comment for alloc_and_load_pb_route
amin1377 Apr 28, 2025
86491fd
[vpr][pack] remove redundant parameters
amin1377 Apr 28, 2025
798055c
[vpr][pack] polish load_trace_to_pb_route
amin1377 Apr 28, 2025
241589b
make format
amin1377 Apr 28, 2025
cffeaa1
Merge branch 'master' of https://github.com/verilog-to-routing/vtr-ve…
amin1377 Apr 28, 2025
f5bb0eb
[vpr][pack] fix parameter shadowing
amin1377 Apr 28, 2025
b79a174
Merge branch 'master' of https://github.com/verilog-to-routing/vtr-ve…
amin1377 Apr 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion vpr/src/base/netlist_writer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1275,7 +1275,10 @@ class NetlistWriterVisitor : public NetlistVisitor {

//Add the single output connection
{
auto atom_net_id = top_pb_route[sink_cluster_pin_idx].atom_net_id; //Connected net in atom netlist
/* Check if the output is connected */
AtomNetId atom_net_id = AtomNetId::INVALID();
if (top_pb_route.count(sink_cluster_pin_idx))
atom_net_id = top_pb_route[sink_cluster_pin_idx].atom_net_id; //Connected net in atom netlist

std::string net;
if (!atom_net_id) {
Expand Down
2 changes: 1 addition & 1 deletion vpr/src/base/read_netlist.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -823,7 +823,7 @@ static void processPorts(pugi::xml_node Parent, t_pb* pb, t_pb_routes& pb_route,
//Why does this not use the output pin used to deterimine the rr node index?
pb_route.insert(std::make_pair(rr_node_index, t_pb_route()));
pb_route[rr_node_index].driver_pb_pin_id = pin_node[0][0]->pin_count_in_cluster;
pb_route[rr_node_index].pb_graph_pin = pin_node[0][0];
pb_route[rr_node_index].pb_graph_pin = &pb->pb_graph_node->output_pins[out_port][i];

found = false;
for (j = 0; j < pin_node[0][0]->num_output_edges; j++) {
Expand Down
29 changes: 29 additions & 0 deletions vpr/src/base/vpr_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ static void free_complex_block_types();
static void free_device(const t_det_routing_arch& routing_arch);
static void free_circuit();

/** Set all port equivalences in the architecture to NONE. This is used in the
* case of the flat router where port equivalence does not make sense.
* We could just keep it set and ignore it, but that prevents compatibility
* with OpenFPGA which takes it seriously. */
static void unset_port_equivalences(DeviceContext& device_ctx);

/* Local subroutines end */

///@brief Display general VPR information
Expand Down Expand Up @@ -365,6 +371,25 @@ void vpr_init_with_options(const t_options* options, t_vpr_setup* vpr_setup, t_a
device_ctx.pad_loc_type = vpr_setup->PlacerOpts.pad_loc_type;
}

/** Port equivalence does not make sense during flat routing.
* Remove port equivalence from all ports in the architecture */
static void unset_port_equivalences(DeviceContext& device_ctx) {
for (auto& physical_type : device_ctx.physical_tile_types) {
for (auto& sub_tile : physical_type.sub_tiles) {
for (auto& port : sub_tile.ports) {
port.equivalent = PortEquivalence::NONE;
}
}
}
for (auto& logical_type : device_ctx.logical_block_types) {
if (!logical_type.pb_type)
continue;
for (int i = 0; i < logical_type.pb_type->num_ports; i++) {
logical_type.pb_type->ports[i].equivalent = PortEquivalence::NONE;
}
}
}

bool vpr_flow(t_vpr_setup& vpr_setup, t_arch& arch) {
if (vpr_setup.exit_before_pack) {
VTR_LOG_WARN("Exiting before packing as requested.\n");
Expand Down Expand Up @@ -443,6 +468,10 @@ bool vpr_flow(t_vpr_setup& vpr_setup, t_arch& arch) {

bool is_flat = vpr_setup.RouterOpts.flat_routing;
const Netlist<>& router_net_list = is_flat ? (const Netlist<>&)g_vpr_ctx.atom().netlist() : (const Netlist<>&)g_vpr_ctx.clustering().clb_nlist;
if (is_flat) {
VTR_LOG_WARN("Disabling port equivalence in the architecture since flat routing is enabled.\n");
unset_port_equivalences(g_vpr_ctx.mutable_device());
}
RouteStatus route_status;
{ //Route
route_status = vpr_route_flow(router_net_list, vpr_setup, arch, is_flat);
Expand Down
4 changes: 2 additions & 2 deletions vpr/src/pack/cluster_legalizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1571,8 +1571,7 @@ void ClusterLegalizer::clean_cluster(LegalizationClusterId cluster_id) {
// Load the pb_route so we can free the cluster router data.
// The pb_route is used when creating a netlist from the legalized clusters.
std::vector<t_intra_lb_net>* saved_lb_nets = cluster.router_data->saved_lb_nets;
t_pb_graph_node* pb_graph_node = cluster.pb->pb_graph_node;
cluster.pb->pb_route = alloc_and_load_pb_route(saved_lb_nets, pb_graph_node);
cluster.pb->pb_route = alloc_and_load_pb_route(saved_lb_nets, cluster.type, intra_lb_pb_pin_lookup_);
// Free the router data.
free_router_data(cluster.router_data);
cluster.router_data = nullptr;
Expand Down Expand Up @@ -1632,6 +1631,7 @@ ClusterLegalizer::ClusterLegalizer(const AtomNetlist& atom_netlist,
log_verbosity_ = log_verbosity;
VTR_ASSERT(g_vpr_ctx.atom().lookup().atom_pb_bimap().is_empty());
atom_pb_lookup_ = AtomPBBimap();
intra_lb_pb_pin_lookup_ = IntraLbPbPinLookup(g_vpr_ctx.device().logical_block_types);
}

void ClusterLegalizer::reset() {
Expand Down
6 changes: 6 additions & 0 deletions vpr/src/pack/cluster_legalizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "vtr_vector.h"
#include "vtr_vector_map.h"
#include "atom_pb_bimap.h"
#include "vpr_utils.h"

// Forward declarations
class Prepacker;
Expand Down Expand Up @@ -524,6 +525,8 @@ class ClusterLegalizer {
inline const AtomPBBimap& atom_pb_lookup() const { return atom_pb_lookup_; }
inline AtomPBBimap& mutable_atom_pb_lookup() { return atom_pb_lookup_; }

inline const IntraLbPbPinLookup& intra_lb_pb_pin_lookup() const { return intra_lb_pb_pin_lookup_; }

/// @brief Destructor of the class. Frees allocated data.
~ClusterLegalizer();

Expand Down Expand Up @@ -595,4 +598,7 @@ class ClusterLegalizer {
/// @brief A two way map between AtomBlockIds and pb types. This is a copy
/// of the AtomPBBimap in the global context's AtomLookup
AtomPBBimap atom_pb_lookup_;

/// @brief A lookup table for the pin mapping of the intra-lb pb pins.
IntraLbPbPinLookup intra_lb_pb_pin_lookup_;
};
28 changes: 22 additions & 6 deletions vpr/src/pack/cluster_router.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,13 @@ static bool is_route_success(t_lb_router_data* router_data);
static t_lb_trace* find_node_in_rt(t_lb_trace* rt, int rt_index);
static void reset_explored_node_tb(t_lb_router_data* router_data);
static void save_and_reset_lb_route(t_lb_router_data* router_data);
static void load_trace_to_pb_route(t_pb_routes& pb_route, const int total_pins, const AtomNetId net_id, const int prev_pin_id, const t_lb_trace* trace);
static void load_trace_to_pb_route(t_pb_routes& pb_route,
const int total_pins,
const AtomNetId net_id,
const int prev_pin_id,
const t_lb_trace* trace,
t_logical_block_type_ptr logic_block_type,
const IntraLbPbPinLookup& intra_lb_pb_pin_lookup);

static std::string describe_lb_type_rr_node(int inode,
const t_lb_router_data* router_data);
Expand Down Expand Up @@ -545,13 +551,15 @@ bool try_intra_lb_route(t_lb_router_data* router_data,

/* Creates an array [0..num_pb_graph_pins-1] lookup for intra-logic block routing. Given pb_graph_pin id for clb, lookup atom net that uses that pin.
* If pin is not used, stores OPEN at that pin location */
t_pb_routes alloc_and_load_pb_route(const std::vector<t_intra_lb_net>* intra_lb_nets, t_pb_graph_node* pb_graph_head) {
t_pb_routes alloc_and_load_pb_route(const std::vector<t_intra_lb_net>* intra_lb_nets,
t_logical_block_type_ptr logic_block_type,
const IntraLbPbPinLookup& intra_lb_pb_pin_lookup) {
const std::vector<t_intra_lb_net>& lb_nets = *intra_lb_nets;
int total_pins = pb_graph_head->total_pb_pins;
int total_pins = logic_block_type->pb_graph_head->total_pb_pins;
t_pb_routes pb_route;

for (int inet = 0; inet < (int)lb_nets.size(); inet++) {
load_trace_to_pb_route(pb_route, total_pins, lb_nets[inet].atom_net_id, OPEN, lb_nets[inet].rt_tree);
load_trace_to_pb_route(pb_route, total_pins, lb_nets[inet].atom_net_id, OPEN, lb_nets[inet].rt_tree, logic_block_type, intra_lb_pb_pin_lookup);
}

return pb_route;
Expand Down Expand Up @@ -582,7 +590,13 @@ void free_intra_lb_nets(std::vector<t_intra_lb_net>* intra_lb_nets) {
****************************************************************************/

/* Recurse through route tree trace to populate pb pin to atom net lookup array */
static void load_trace_to_pb_route(t_pb_routes& pb_route, const int total_pins, const AtomNetId net_id, const int prev_pin_id, const t_lb_trace* trace) {
static void load_trace_to_pb_route(t_pb_routes& pb_route,
const int total_pins,
const AtomNetId net_id,
const int prev_pin_id,
const t_lb_trace* trace,
t_logical_block_type_ptr logic_block_type,
const IntraLbPbPinLookup& intra_lb_pb_pin_lookup) {
int ipin = trace->current_node;
int driver_pb_pin_id = prev_pin_id;
int cur_pin_id = OPEN;
Expand All @@ -593,12 +607,14 @@ static void load_trace_to_pb_route(t_pb_routes& pb_route, const int total_pins,
pb_route.insert(std::make_pair(cur_pin_id, t_pb_route()));
pb_route[cur_pin_id].atom_net_id = net_id;
pb_route[cur_pin_id].driver_pb_pin_id = driver_pb_pin_id;
auto pb_graph_pin = intra_lb_pb_pin_lookup.pb_gpin(logic_block_type->index, cur_pin_id);
pb_route[cur_pin_id].pb_graph_pin = pb_graph_pin;
} else {
VTR_ASSERT(pb_route[cur_pin_id].atom_net_id == net_id);
}
}
for (int itrace = 0; itrace < (int)trace->next_nodes.size(); itrace++) {
load_trace_to_pb_route(pb_route, total_pins, net_id, cur_pin_id, &trace->next_nodes[itrace]);
load_trace_to_pb_route(pb_route, total_pins, net_id, cur_pin_id, &trace->next_nodes[itrace], logic_block_type, intra_lb_pb_pin_lookup);
}
}

Expand Down
4 changes: 3 additions & 1 deletion vpr/src/pack/cluster_router.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ bool try_intra_lb_route(t_lb_router_data* router_data, int verbosity, t_mode_sel
void reset_intra_lb_route(t_lb_router_data* router_data);

/* Accessor Functions */
t_pb_routes alloc_and_load_pb_route(const std::vector<t_intra_lb_net>* intra_lb_nets, t_pb_graph_node* pb_graph_head);
t_pb_routes alloc_and_load_pb_route(const std::vector<t_intra_lb_net>* intra_lb_nets,
t_logical_block_type_ptr logic_block_type,
const IntraLbPbPinLookup& intra_lb_pb_pin_lookup);
void free_pb_route(t_pb_route* free_pb_route);

#endif
1 change: 1 addition & 0 deletions vpr/src/pack/post_routing_pb_pin_fixup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1033,6 +1033,7 @@ void sync_netlists_to_routing(const Netlist<>& net_list,
/* Create net-to-rr_node mapping */
vtr::vector<RRNodeId, ClusterNetId> rr_node_nets = annotate_rr_node_nets(clustering_ctx,
device_ctx,
atom_ctx,
verbose);

IntraLbPbPinLookup intra_lb_pb_pin_lookup(device_ctx.logical_block_types);
Expand Down
84 changes: 40 additions & 44 deletions vpr/src/pack/sync_netlists_to_routing_flat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,23 +42,6 @@ static void fixup_atom_pb_graph_pin_mapping(void);

/* Function definitions */

/** Is the clock net found in the routing results?
* (If not, clock_modeling is probably ideal and we should preserve clock routing while rebuilding.) */
inline bool is_clock_net_routed(void) {
auto& atom_ctx = g_vpr_ctx.atom();
auto& route_ctx = g_vpr_ctx.routing();

for (auto net_id : atom_ctx.netlist().nets()) {
auto& tree = route_ctx.route_trees[net_id];
if (!tree)
continue;
if (route_ctx.is_clock_net[net_id]) /* Clock net has routing */
return true;
}

return false;
}

/** Get the ClusterBlockId for a given RRNodeId. */
inline ClusterBlockId get_cluster_block_from_rr_node(RRNodeId inode) {
auto& device_ctx = g_vpr_ctx.device();
Expand Down Expand Up @@ -185,18 +168,16 @@ static void sync_pb_routes_to_routing(void) {
auto& route_ctx = g_vpr_ctx.routing();
auto& rr_graph = device_ctx.rr_graph;

/* Was the clock net routed? */
bool clock_net_is_routed = is_clock_net_routed();

/* Clear out existing pb_routes: they were made by the intra cluster router and are invalid now */
for (ClusterBlockId clb_blk_id : cluster_ctx.clb_nlist.blocks()) {
/* If we don't have routing for the clock net, don't erase entries associated with a clock net.
* Otherwise we won't have data to rebuild them */
/* Don't erase entries for nets without routing in place (clocks, globals...) */
std::vector<int> pins_to_erase;
auto& pb_routes = cluster_ctx.clb_nlist.block_pb(clb_blk_id)->pb_route;
for (auto& [pin, pb_route] : pb_routes) {
if (clock_net_is_routed || !route_ctx.is_clock_net[pb_route.atom_net_id])
pins_to_erase.push_back(pin);
/* No route tree: no routing in place, it is global or clock */
if (!route_ctx.route_trees[ParentNetId(int(pb_route.atom_net_id))])
continue;
pins_to_erase.push_back(pin);
}

for (int pin : pins_to_erase) {
Expand Down Expand Up @@ -276,37 +257,37 @@ static void sync_clustered_netlist_to_routing(void) {
auto& atom_ctx = g_vpr_ctx.mutable_atom();
auto& atom_lookup = atom_ctx.lookup();

bool clock_net_is_routed = is_clock_net_routed();

/* 1. Remove all nets, pins and ports from the clustered netlist.
* If the clock net is not routed, don't remove entries for the clock net
* otherwise we won't have data to rebuild them. */
* Do not remove entries for nets without an existing route tree,
* since we don't have the information to rebuild those parts. */
std::vector<ClusterNetId> nets_to_remove;
std::vector<ClusterPinId> pins_to_remove;
std::vector<ClusterPortId> ports_to_remove;

for (auto net_id : clb_netlist.nets()) {
auto atom_net_id = atom_lookup.atom_net(net_id);
if (!clock_net_is_routed && route_ctx.is_clock_net[atom_net_id])
if (!route_ctx.route_trees[ParentNetId(int(atom_net_id))])
continue;

nets_to_remove.push_back(net_id);
}
for (auto pin_id : clb_netlist.pins()) {
ClusterNetId clb_net_id = clb_netlist.pin_net(pin_id);
auto atom_net_id = atom_lookup.atom_net(clb_net_id);
if (!clock_net_is_routed && atom_net_id && route_ctx.is_clock_net[atom_net_id])
continue;

pins_to_remove.push_back(pin_id);
}
/* Mark ports and pins for removal. Don't remove a port if
* it has at least one pin remaining */
for (auto port_id : clb_netlist.ports()) {
ClusterNetId clb_net_id = clb_netlist.port_net(port_id, 0);
auto atom_net_id = atom_lookup.atom_net(clb_net_id);
if (!clock_net_is_routed && atom_net_id && route_ctx.is_clock_net[atom_net_id])
continue;
size_t skipped_pins = 0;

for (auto pin_id : clb_netlist.port_pins(port_id)) {
ClusterNetId clb_net_id = clb_netlist.pin_net(pin_id);
auto atom_net_id = atom_lookup.atom_net(clb_net_id);
if (atom_net_id && !route_ctx.route_trees[ParentNetId(int(atom_net_id))]) {
skipped_pins++;
} else {
pins_to_remove.push_back(pin_id);
}
}

ports_to_remove.push_back(port_id);
if (!skipped_pins) // All pins have been removed, remove port
ports_to_remove.push_back(port_id);
}

/* ClusteredNetlist's iterators rely on internal lookups, so we mark for removal
Expand Down Expand Up @@ -354,15 +335,15 @@ static void sync_clustered_netlist_to_routing(void) {
* Due to how the route tree is traversed, all nodes until the next OPIN on the tile will
* be under this OPIN, so this is valid (we don't need to get the branch explicitly) */
if (node_type == OPIN) {
std::string net_name;
net_name = atom_ctx.netlist().net_name(parent_net_id) + "_" + std::to_string(clb_nets_so_far);
std::string net_name = atom_ctx.netlist().net_name(parent_net_id) + "_" + std::to_string(clb_nets_so_far);
clb_net_id = clb_netlist.create_net(net_name);
atom_ctx.mutable_lookup().add_atom_clb_net(atom_net_id, clb_net_id);
clb_nets_so_far++;
}

t_pb_graph_pin* pb_graph_pin = get_pb_graph_node_pin_from_block_pin(clb, pin_index);

/* Get or create port */
ClusterPortId port_id = clb_netlist.find_port(clb, pb_graph_pin->port->name);
if (!port_id) {
PortType port_type;
Expand All @@ -378,6 +359,15 @@ static void sync_clustered_netlist_to_routing(void) {
}
PinType pin_type = node_type == OPIN ? PinType::DRIVER : PinType::SINK;

/* Pin already exists. This means a global was connected to here. */
if (clb_netlist.port_pin(port_id, pb_graph_pin->pin_number)) {
VTR_LOG_WARN("Pin %s of block %s has a global or clock net"
" connected and it has a routing clash with the flat router."
" This may cause inconsistent results.\n",
pb_graph_pin->to_string().c_str(),
clb_netlist.block_name(clb).c_str());
continue;
}
ClusterPinId new_pin = clb_netlist.create_pin(port_id, pb_graph_pin->pin_number, clb_net_id, pin_type, pb_graph_pin->pin_count_in_cluster);
clb_netlist.set_pin_net(new_pin, pin_type, clb_net_id);
}
Expand Down Expand Up @@ -419,6 +409,12 @@ static void fixup_atom_pb_graph_pin_mapping(void) {

/* Find atom port from pbg pin's model port */
AtomPortId atom_port = atom_ctx.netlist().find_atom_port(atb, atom_pbg_pin->port->model_port);

/* Not an equivalent port, so no need to do fixup */
if (atom_pbg_pin->port->equivalent != PortEquivalence::FULL) {
continue;
}

for (AtomPinId atom_pin : atom_ctx.netlist().port_pins(atom_port)) {
/* Match net IDs from pb_route and atom netlist and connect in lookup */
if (pb_route.atom_net_id == atom_ctx.netlist().pin_net(atom_pin)) {
Expand Down
14 changes: 11 additions & 3 deletions vpr/src/route/annotate_routing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@

vtr::vector<RRNodeId, ClusterNetId> annotate_rr_node_nets(const ClusteringContext& cluster_ctx,
const DeviceContext& device_ctx,
const AtomContext& atom_ctx,
const bool& verbose) {
size_t counter = 0;
vtr::ScopedStartFinishTimer timer("Annotating rr_node with routed nets");

const auto& rr_graph = device_ctx.rr_graph;
auto& atom_lookup = atom_ctx.lookup();

auto& netlist = cluster_ctx.clb_nlist;
vtr::vector<RRNodeId, ClusterNetId> rr_node_nets;
Expand Down Expand Up @@ -47,11 +49,17 @@ vtr::vector<RRNodeId, ClusterNetId> annotate_rr_node_nets(const ClusteringContex
* In some routing architectures, node capacity is more than 1
* which allows a node to be mapped by multiple nets
* Therefore, the sanity check should focus on the nodes
* whose capacity is 1
*/
* whose capacity is 1.
* Flat routing may create two clustered nets from a single
* atom net if the atom net ended up exiting the block through
* different pins. Those clustered nets will point to the same
* atom net routing. Ignore clashes if that is the case. */
AtomNetId my_atom = atom_lookup.atom_net(net_id);
AtomNetId existing_atom = atom_lookup.atom_net(rr_node_nets[rr_node]);
if ((rr_node_nets[rr_node])
&& (1 == rr_graph.node_capacity(rr_node))
&& (net_id != rr_node_nets[rr_node])) {
&& (net_id != rr_node_nets[rr_node])
&& (my_atom != existing_atom)) {
VPR_FATAL_ERROR(VPR_ERROR_ANALYSIS,
"Detect two nets '%s' and '%s' that are mapped to the same rr_node '%ld'!\n%s\n",
netlist.net_name(net_id).c_str(),
Expand Down
Loading